From cdb139fb19f770b6a3d13cb7d848917b090b2705 Mon Sep 17 00:00:00 2001 From: radek Date: Tue, 31 Oct 2023 10:11:57 +0100 Subject: [PATCH 001/587] Remove ThreadSafe annotation from LocalDynamicFiltersCollector --- .../java/io/trino/sql/planner/LocalDynamicFiltersCollector.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/LocalDynamicFiltersCollector.java b/core/trino-main/src/main/java/io/trino/sql/planner/LocalDynamicFiltersCollector.java index 296f2b1f702c..406862760b82 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/LocalDynamicFiltersCollector.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/LocalDynamicFiltersCollector.java @@ -18,7 +18,6 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; -import com.google.errorprone.annotations.ThreadSafe; import com.google.errorprone.annotations.concurrent.GuardedBy; import io.trino.Session; import io.trino.spi.connector.ColumnHandle; @@ -48,7 +47,6 @@ import static java.lang.String.format; import static java.util.Objects.requireNonNull; -@ThreadSafe public class LocalDynamicFiltersCollector { private final Session session; From ba88ce01a03d8da1033d42d9bb41f75f22fec725 Mon Sep 17 00:00:00 2001 From: Krzysztof Sobolewski Date: Mon, 30 Oct 2023 17:20:59 +0100 Subject: [PATCH 002/587] Upgrade Error Prone to 2.23.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 62bec69eff3b..11bb11d20752 100644 --- a/pom.xml +++ b/pom.xml @@ -169,7 +169,7 @@ 1.6.12 1.9.10 1.43.3 - 2.22.0 + 2.23.0 1.19.1 1.0.8 7.4.1 From 8ec22fd4aae8c2caab4c8033300f4caea9edb787 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Mon, 30 Oct 2023 22:54:04 -0700 Subject: [PATCH 003/587] Add Trino version to SystemAccessControlContext --- .../trino/security/AccessControlManager.java | 11 ++++++++ .../testing/TestingAccessControlManager.java | 3 ++- .../dispatcher/TestLocalDispatchQuery.java | 1 + .../io/trino/execution/TestCommitTask.java | 2 +- .../trino/execution/TestDeallocateTask.java | 8 +++++- .../io/trino/execution/TestPrepareTask.java | 8 +++++- .../execution/TestQueryStateMachine.java | 1 + .../execution/TestStartTransactionTask.java | 2 +- .../security/TestAccessControlManager.java | 7 +++--- .../TestFileBasedSystemAccessControl.java | 25 ++++++++++++++++--- .../io/trino/sql/analyzer/TestAnalyzer.java | 2 ++ core/trino-spi/pom.xml | 4 +++ .../security/SystemAccessControlFactory.java | 2 ++ 13 files changed, 65 insertions(+), 11 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/security/AccessControlManager.java b/core/trino-main/src/main/java/io/trino/security/AccessControlManager.java index fa6df5f2bcbd..05c67dde8b6f 100644 --- a/core/trino-main/src/main/java/io/trino/security/AccessControlManager.java +++ b/core/trino-main/src/main/java/io/trino/security/AccessControlManager.java @@ -23,6 +23,7 @@ import io.airlift.stats.CounterStat; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.trace.Tracer; +import io.trino.client.NodeVersion; import io.trino.connector.CatalogServiceProvider; import io.trino.eventlistener.EventListenerManager; import io.trino.metadata.QualifiedObjectName; @@ -95,6 +96,7 @@ public class AccessControlManager private static final File CONFIG_FILE = new File("etc/access-control.properties"); private static final String NAME_PROPERTY = "access-control.name"; + private final NodeVersion nodeVersion; private final TransactionManager transactionManager; private final EventListenerManager eventListenerManager; private final List configFiles; @@ -110,12 +112,14 @@ public class AccessControlManager @Inject public AccessControlManager( + NodeVersion nodeVersion, TransactionManager transactionManager, EventListenerManager eventListenerManager, AccessControlConfig config, OpenTelemetry openTelemetry, @DefaultSystemAccessControlName String defaultAccessControlName) { + this.nodeVersion = requireNonNull(nodeVersion, "nodeVersion is null"); this.transactionManager = requireNonNull(transactionManager, "transactionManager is null"); this.eventListenerManager = requireNonNull(eventListenerManager, "eventListenerManager is null"); this.configFiles = ImmutableList.copyOf(config.getAccessControlFiles()); @@ -223,6 +227,13 @@ private SystemAccessControlContext createContext(String systemAccessControlName) return new SystemAccessControlContext() { private final Tracer tracer = openTelemetry.getTracer("trino.system-access-control." + systemAccessControlName); + private final String version = nodeVersion.getVersion(); + + @Override + public String getVersion() + { + return version; + } @Override public OpenTelemetry getOpenTelemetry() diff --git a/core/trino-main/src/main/java/io/trino/testing/TestingAccessControlManager.java b/core/trino-main/src/main/java/io/trino/testing/TestingAccessControlManager.java index 4dbe3145aa24..8ed1e6459d1c 100644 --- a/core/trino-main/src/main/java/io/trino/testing/TestingAccessControlManager.java +++ b/core/trino-main/src/main/java/io/trino/testing/TestingAccessControlManager.java @@ -16,6 +16,7 @@ import com.google.common.collect.ImmutableSet; import com.google.inject.Inject; import io.opentelemetry.api.OpenTelemetry; +import io.trino.client.NodeVersion; import io.trino.eventlistener.EventListenerManager; import io.trino.metadata.QualifiedObjectName; import io.trino.plugin.base.security.DefaultSystemAccessControl; @@ -146,7 +147,7 @@ public TestingAccessControlManager( AccessControlConfig accessControlConfig, OpenTelemetry openTelemetry) { - super(transactionManager, eventListenerManager, accessControlConfig, openTelemetry, DefaultSystemAccessControl.NAME); + super(NodeVersion.UNKNOWN, transactionManager, eventListenerManager, accessControlConfig, openTelemetry, DefaultSystemAccessControl.NAME); } public TestingAccessControlManager(TransactionManager transactionManager, EventListenerManager eventListenerManager) diff --git a/core/trino-main/src/test/java/io/trino/dispatcher/TestLocalDispatchQuery.java b/core/trino-main/src/test/java/io/trino/dispatcher/TestLocalDispatchQuery.java index 77de01cfb286..3fba48fc8ff5 100644 --- a/core/trino-main/src/test/java/io/trino/dispatcher/TestLocalDispatchQuery.java +++ b/core/trino-main/src/test/java/io/trino/dispatcher/TestLocalDispatchQuery.java @@ -97,6 +97,7 @@ public void testSubmittedForDispatchedQuery() Metadata metadata = createTestMetadataManager(); TransactionManager transactionManager = createTestTransactionManager(); AccessControlManager accessControl = new AccessControlManager( + NodeVersion.UNKNOWN, transactionManager, emptyEventListenerManager(), new AccessControlConfig(), diff --git a/core/trino-main/src/test/java/io/trino/execution/TestCommitTask.java b/core/trino-main/src/test/java/io/trino/execution/TestCommitTask.java index 130a1b2e5450..dad9d140e647 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestCommitTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestCommitTask.java @@ -136,7 +136,7 @@ private QueryStateMachine createQueryStateMachine(String query, Session session, new ResourceGroupId("test"), true, transactionManager, - new AccessControlManager(transactionManager, emptyEventListenerManager(), new AccessControlConfig(), OpenTelemetry.noop(), DefaultSystemAccessControl.NAME), + new AccessControlManager(NodeVersion.UNKNOWN, transactionManager, emptyEventListenerManager(), new AccessControlConfig(), OpenTelemetry.noop(), DefaultSystemAccessControl.NAME), executor, metadata, WarningCollector.NOOP, diff --git a/core/trino-main/src/test/java/io/trino/execution/TestDeallocateTask.java b/core/trino-main/src/test/java/io/trino/execution/TestDeallocateTask.java index edc5b2e56aeb..e4ff91bccf9d 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestDeallocateTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestDeallocateTask.java @@ -85,7 +85,13 @@ public void testDeallocateNoSuchStatement() private Set executeDeallocate(String statementName, String sqlString, Session session) { TransactionManager transactionManager = createTestTransactionManager(); - AccessControlManager accessControl = new AccessControlManager(transactionManager, emptyEventListenerManager(), new AccessControlConfig(), OpenTelemetry.noop(), DefaultSystemAccessControl.NAME); + AccessControlManager accessControl = new AccessControlManager( + NodeVersion.UNKNOWN, + transactionManager, + emptyEventListenerManager(), + new AccessControlConfig(), + OpenTelemetry.noop(), + DefaultSystemAccessControl.NAME); accessControl.setSystemAccessControls(List.of(AllowAllSystemAccessControl.INSTANCE)); QueryStateMachine stateMachine = QueryStateMachine.begin( Optional.empty(), diff --git a/core/trino-main/src/test/java/io/trino/execution/TestPrepareTask.java b/core/trino-main/src/test/java/io/trino/execution/TestPrepareTask.java index 19a31d93a484..c70f030d75f1 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestPrepareTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestPrepareTask.java @@ -109,7 +109,13 @@ public void testPrepareInvalidStatement() private Map executePrepare(String statementName, Statement statement, String sqlString, Session session) { TransactionManager transactionManager = createTestTransactionManager(); - AccessControlManager accessControl = new AccessControlManager(transactionManager, emptyEventListenerManager(), new AccessControlConfig(), OpenTelemetry.noop(), DefaultSystemAccessControl.NAME); + AccessControlManager accessControl = new AccessControlManager( + NodeVersion.UNKNOWN, + transactionManager, + emptyEventListenerManager(), + new AccessControlConfig(), + OpenTelemetry.noop(), + DefaultSystemAccessControl.NAME); accessControl.setSystemAccessControls(List.of(AllowAllSystemAccessControl.INSTANCE)); QueryStateMachine stateMachine = QueryStateMachine.begin( Optional.empty(), diff --git a/core/trino-main/src/test/java/io/trino/execution/TestQueryStateMachine.java b/core/trino-main/src/test/java/io/trino/execution/TestQueryStateMachine.java index d4ef655915a5..e4bc392129d0 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestQueryStateMachine.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestQueryStateMachine.java @@ -523,6 +523,7 @@ private QueryStateMachine createQueryStateMachineWithTicker(Ticker ticker) Metadata metadata = createTestMetadataManager(); TransactionManager transactionManager = createTestTransactionManager(); AccessControlManager accessControl = new AccessControlManager( + NodeVersion.UNKNOWN, transactionManager, emptyEventListenerManager(), new AccessControlConfig(), diff --git a/core/trino-main/src/test/java/io/trino/execution/TestStartTransactionTask.java b/core/trino-main/src/test/java/io/trino/execution/TestStartTransactionTask.java index fe912aae7404..c21bee31e90d 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestStartTransactionTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestStartTransactionTask.java @@ -257,7 +257,7 @@ private QueryStateMachine createQueryStateMachine(String query, Session session, new ResourceGroupId("test"), true, transactionManager, - new AccessControlManager(transactionManager, emptyEventListenerManager(), new AccessControlConfig(), OpenTelemetry.noop(), DefaultSystemAccessControl.NAME), + new AccessControlManager(NodeVersion.UNKNOWN, transactionManager, emptyEventListenerManager(), new AccessControlConfig(), OpenTelemetry.noop(), DefaultSystemAccessControl.NAME), executor, metadata, WarningCollector.NOOP, diff --git a/core/trino-main/src/test/java/io/trino/security/TestAccessControlManager.java b/core/trino-main/src/test/java/io/trino/security/TestAccessControlManager.java index 9e71c249b584..62255fa8b648 100644 --- a/core/trino-main/src/test/java/io/trino/security/TestAccessControlManager.java +++ b/core/trino-main/src/test/java/io/trino/security/TestAccessControlManager.java @@ -17,6 +17,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import io.opentelemetry.api.OpenTelemetry; +import io.trino.client.NodeVersion; import io.trino.connector.CatalogServiceProvider; import io.trino.connector.MockConnectorFactory; import io.trino.eventlistener.EventListenerManager; @@ -558,17 +559,17 @@ private AccessControlManager createAccessControlManager(TestingEventListenerMana private AccessControlManager createAccessControlManager(TransactionManager testTransactionManager) { - return new AccessControlManager(testTransactionManager, emptyEventListenerManager(), new AccessControlConfig(), OpenTelemetry.noop(), DefaultSystemAccessControl.NAME); + return new AccessControlManager(NodeVersion.UNKNOWN, testTransactionManager, emptyEventListenerManager(), new AccessControlConfig(), OpenTelemetry.noop(), DefaultSystemAccessControl.NAME); } private AccessControlManager createAccessControlManager(EventListenerManager eventListenerManager, AccessControlConfig config) { - return new AccessControlManager(createTestTransactionManager(), eventListenerManager, config, OpenTelemetry.noop(), DefaultSystemAccessControl.NAME); + return new AccessControlManager(NodeVersion.UNKNOWN, createTestTransactionManager(), eventListenerManager, config, OpenTelemetry.noop(), DefaultSystemAccessControl.NAME); } private AccessControlManager createAccessControlManager(EventListenerManager eventListenerManager, String defaultAccessControlName) { - return new AccessControlManager(createTestTransactionManager(), eventListenerManager, new AccessControlConfig(), OpenTelemetry.noop(), defaultAccessControlName); + return new AccessControlManager(NodeVersion.UNKNOWN, createTestTransactionManager(), eventListenerManager, new AccessControlConfig(), OpenTelemetry.noop(), defaultAccessControlName); } private SystemAccessControlFactory eventListeningSystemAccessControlFactory(String name, EventListener... eventListeners) diff --git a/core/trino-main/src/test/java/io/trino/security/TestFileBasedSystemAccessControl.java b/core/trino-main/src/test/java/io/trino/security/TestFileBasedSystemAccessControl.java index 93a4727732c9..f905dd2ffb8b 100644 --- a/core/trino-main/src/test/java/io/trino/security/TestFileBasedSystemAccessControl.java +++ b/core/trino-main/src/test/java/io/trino/security/TestFileBasedSystemAccessControl.java @@ -17,6 +17,7 @@ import com.google.common.collect.ImmutableSet; import com.google.inject.CreationException; import io.opentelemetry.api.OpenTelemetry; +import io.trino.client.NodeVersion; import io.trino.metadata.Metadata; import io.trino.metadata.MetadataManager; import io.trino.metadata.QualifiedObjectName; @@ -138,7 +139,13 @@ public void testCanImpersonateUserOperations() public void testDocsExample() { TransactionManager transactionManager = createTestTransactionManager(); - AccessControlManager accessControlManager = new AccessControlManager(transactionManager, emptyEventListenerManager(), new AccessControlConfig(), OpenTelemetry.noop(), DefaultSystemAccessControl.NAME); + AccessControlManager accessControlManager = new AccessControlManager( + NodeVersion.UNKNOWN, + transactionManager, + emptyEventListenerManager(), + new AccessControlConfig(), + OpenTelemetry.noop(), + DefaultSystemAccessControl.NAME); accessControlManager.loadSystemAccessControl( FileBasedSystemAccessControl.NAME, ImmutableMap.of("security.config-file", new File("../../docs/src/main/sphinx/security/user-impersonation.json").getAbsolutePath())); @@ -792,7 +799,13 @@ public void testRefreshing() { TransactionManager transactionManager = createTestTransactionManager(); Metadata metadata = MetadataManager.testMetadataManagerBuilder().withTransactionManager(transactionManager).build(); - AccessControlManager accessControlManager = new AccessControlManager(transactionManager, emptyEventListenerManager(), new AccessControlConfig(), OpenTelemetry.noop(), DefaultSystemAccessControl.NAME); + AccessControlManager accessControlManager = new AccessControlManager( + NodeVersion.UNKNOWN, + transactionManager, + emptyEventListenerManager(), + new AccessControlConfig(), + OpenTelemetry.noop(), + DefaultSystemAccessControl.NAME); File configFile = newTemporaryFile(); configFile.deleteOnExit(); copy(new File(getResourcePath("catalog.json")), configFile); @@ -852,7 +865,13 @@ public void testAllowModeInvalidValue() private AccessControlManager newAccessControlManager(TransactionManager transactionManager, String resourceName) { - AccessControlManager accessControlManager = new AccessControlManager(transactionManager, emptyEventListenerManager(), new AccessControlConfig(), OpenTelemetry.noop(), DefaultSystemAccessControl.NAME); + AccessControlManager accessControlManager = new AccessControlManager( + NodeVersion.UNKNOWN, + transactionManager, + emptyEventListenerManager(), + new AccessControlConfig(), + OpenTelemetry.noop(), + DefaultSystemAccessControl.NAME); accessControlManager.loadSystemAccessControl(FileBasedSystemAccessControl.NAME, ImmutableMap.of("security.config-file", getResourcePath(resourceName))); diff --git a/core/trino-main/src/test/java/io/trino/sql/analyzer/TestAnalyzer.java b/core/trino-main/src/test/java/io/trino/sql/analyzer/TestAnalyzer.java index 76296d029339..962eac3b550f 100644 --- a/core/trino-main/src/test/java/io/trino/sql/analyzer/TestAnalyzer.java +++ b/core/trino-main/src/test/java/io/trino/sql/analyzer/TestAnalyzer.java @@ -22,6 +22,7 @@ import io.trino.FeaturesConfig; import io.trino.Session; import io.trino.SystemSessionProperties; +import io.trino.client.NodeVersion; import io.trino.connector.CatalogServiceProvider; import io.trino.connector.MockConnectorFactory; import io.trino.connector.StaticConnectorFactory; @@ -6746,6 +6747,7 @@ public void setup() transactionManager = queryRunner.getTransactionManager(); AccessControlManager accessControlManager = new AccessControlManager( + NodeVersion.UNKNOWN, transactionManager, emptyEventListenerManager(), new AccessControlConfig(), diff --git a/core/trino-spi/pom.xml b/core/trino-spi/pom.xml index 219a412f7305..c0fe41010e89 100644 --- a/core/trino-spi/pom.xml +++ b/core/trino-spi/pom.xml @@ -1061,6 +1061,10 @@ protected package + + java.method.addedToInterface + method java.lang.String io.trino.spi.security.SystemAccessControlFactory.SystemAccessControlContext::getVersion() + diff --git a/core/trino-spi/src/main/java/io/trino/spi/security/SystemAccessControlFactory.java b/core/trino-spi/src/main/java/io/trino/spi/security/SystemAccessControlFactory.java index e86c97168299..8afbb35bdff6 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/security/SystemAccessControlFactory.java +++ b/core/trino-spi/src/main/java/io/trino/spi/security/SystemAccessControlFactory.java @@ -32,6 +32,8 @@ default SystemAccessControl create(Map config, SystemAccessContr interface SystemAccessControlContext { + String getVersion(); + OpenTelemetry getOpenTelemetry(); Tracer getTracer(); From 83bec97dfa576f45e2528728c5c37eaed1b69331 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Fri, 27 Oct 2023 19:43:55 +0200 Subject: [PATCH 004/587] Switch to UBI9 minimal and install required version of JDK --- .java-version | 2 +- core/docker/Dockerfile | 28 ++++++++++---- core/docker/build.sh | 37 ++++++++++++++++++- .../trino/server/TrinoSystemRequirements.java | 2 +- pom.xml | 2 +- 5 files changed, 60 insertions(+), 11 deletions(-) diff --git a/.java-version b/.java-version index 03b6389f32ad..e000eb89469c 100644 --- a/.java-version +++ b/.java-version @@ -1 +1 @@ -17.0 +17.0.9 diff --git a/core/docker/Dockerfile b/core/docker/Dockerfile index 26dc62443b3a..f3c79a07f956 100644 --- a/core/docker/Dockerfile +++ b/core/docker/Dockerfile @@ -13,16 +13,30 @@ # FROM ghcr.io/airlift/jvmkill:latest AS jvmkill -# Use Eclipse Temurin as they have base Docker images for more architectures. -FROM eclipse-temurin:17-jdk +FROM registry.access.redhat.com/ubi9/ubi-minimal:latest AS jdk-download +ARG JDK_DOWNLOAD_LINK +ARG JDK_VERSION +ENV JAVA_HOME="/usr/lib/jvm/jdk-${JDK_VERSION}" + +RUN \ + set -xeuo pipefail && \ + microdnf install -y tar gzip && \ + # Install JDK from the provided archive link \ + echo "Downloading JDK from ${JDK_DOWNLOAD_LINK}" && \ + mkdir -p "${JAVA_HOME}" && \ + curl -#LfS "${JDK_DOWNLOAD_LINK}" | tar -zx --strip 1 -C "${JAVA_HOME}" + +# Use ubi9 minimal as it's more secure +FROM registry.access.redhat.com/ubi9/ubi-minimal:latest +ARG JDK_VERSION +ENV JAVA_HOME="/usr/lib/jvm/jdk-${JDK_VERSION}" +ENV PATH=$PATH:$JAVA_HOME/bin +COPY --from=jdk-download $JAVA_HOME $JAVA_HOME RUN \ set -xeu && \ - echo 'Acquire::Retries "3";' > /etc/apt/apt.conf.d/80-retries && \ - echo 'Acquire::http::Timeout "15";' > /etc/apt/apt.conf.d/80-timeouts && \ - apt-get update -q && \ - apt-get install -y -q less python3 curl && \ - rm -rf /var/lib/apt/lists/* && \ + microdnf update -y && \ + microdnf install -y tar less python3 shadow-utils && \ update-alternatives --install /usr/bin/python python /usr/bin/python3 1 && \ groupadd trino --gid 1000 && \ useradd trino --uid 1000 --gid 1000 --create-home && \ diff --git a/core/docker/build.sh b/core/docker/build.sh index 99b93a33bbc1..7c8439e355ea 100755 --- a/core/docker/build.sh +++ b/core/docker/build.sh @@ -38,6 +38,38 @@ shift $((OPTIND - 1)) SOURCE_DIR="../.." +function temurin_jdk_link() { + JDK_VERSION="${1}" + ARCH="${2}" + + versionsUrl="https://api.adoptium.net/v3/info/release_names?heap_size=normal&image_type=jdk<s=true&os=linux&page=0&page_size=20&project=jdk&release_type=ga&semver=false&sort_method=DEFAULT&sort_order=ASC&vendor=eclipse&version=%28${JDK_VERSION}%2C%5D" + if ! result=$(curl -fLs "$versionsUrl" -H 'accept: application/json'); then + echo >&2 "Failed to fetch release names for JDK version [${JDK_VERSION}, ) from Temurin API : $result" + exit 1 + fi + + if ! RELEASE_NAME=$(echo "$result" | jq -er '.releases[]' | grep "${JDK_VERSION}" | head -n 1); then + echo >&2 "Failed to determine release name: ${RELEASE_NAME}" + exit 1 + fi + + case "${ARCH}" in + arm64) + echo "https://api.adoptium.net/v3/binary/version/${RELEASE_NAME}/linux/aarch64/jdk/hotspot/normal/eclipse?project=jdk" + ;; + amd64) + echo "https://api.adoptium.net/v3/binary/version/${RELEASE_NAME}/linux/x64/jdk/hotspot/normal/eclipse?project=jdk" + ;; + ppc64le) + echo "https://api.adoptium.net/v3/binary/version/${RELEASE_NAME}/linux/ppc64le/jdk/hotspot/normal/eclipse?project=jdk" + ;; + *) + echo "${ARCH} is not supported for Docker image" + exit 1 + ;; + esac +} + # Retrieve the script directory. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" cd "${SCRIPT_DIR}" || exit 2 @@ -68,12 +100,15 @@ cp -R bin "${WORK_DIR}/trino-server-${TRINO_VERSION}" cp -R default "${WORK_DIR}/" TAG_PREFIX="trino:${TRINO_VERSION}" +JDK_VERSION=$(cat "${SOURCE_DIR}/.java-version") for arch in "${ARCHITECTURES[@]}"; do - echo "🫙 Building the image for $arch" + echo "🫙 Building the image for $arch with JDK ${JDK_VERSION}" docker build \ "${WORK_DIR}" \ --pull \ + --build-arg JDK_VERSION="${JDK_VERSION}" \ + --build-arg JDK_DOWNLOAD_LINK="$(temurin_jdk_link "${JDK_VERSION}" "${arch}")" \ --platform "linux/$arch" \ -f Dockerfile \ -t "${TAG_PREFIX}-$arch" \ diff --git a/core/trino-main/src/main/java/io/trino/server/TrinoSystemRequirements.java b/core/trino-main/src/main/java/io/trino/server/TrinoSystemRequirements.java index 0a6ebf2dadb6..3d1ca4de857f 100644 --- a/core/trino-main/src/main/java/io/trino/server/TrinoSystemRequirements.java +++ b/core/trino-main/src/main/java/io/trino/server/TrinoSystemRequirements.java @@ -93,7 +93,7 @@ else if ("Mac OS X".equals(osName)) { private static void verifyJavaVersion() { - Version required = Version.parse("17.0.3"); + Version required = Version.parse("17.0.5"); if (Runtime.version().compareTo(required) < 0) { failRequirement("Trino requires Java %s at minimum (found %s)", required, Runtime.version()); diff --git a/pom.xml b/pom.xml index 11bb11d20752..5ca88740d054 100644 --- a/pom.xml +++ b/pom.xml @@ -146,7 +146,7 @@ true 17 - 17.0.4 + 17.0.5 8 clean verify -DskipTests From 78a84deece7c9dcf67c7e98d88049e782863a02f Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Fri, 27 Oct 2023 19:48:16 +0200 Subject: [PATCH 005/587] Always use plain output for docker build --- core/docker/build.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/core/docker/build.sh b/core/docker/build.sh index 7c8439e355ea..51e93f66b835 100755 --- a/core/docker/build.sh +++ b/core/docker/build.sh @@ -106,6 +106,7 @@ for arch in "${ARCHITECTURES[@]}"; do echo "🫙 Building the image for $arch with JDK ${JDK_VERSION}" docker build \ "${WORK_DIR}" \ + --progress=plain \ --pull \ --build-arg JDK_VERSION="${JDK_VERSION}" \ --build-arg JDK_DOWNLOAD_LINK="$(temurin_jdk_link "${JDK_VERSION}" "${arch}")" \ From 6b1a6338f47a8452a3984941962d27a5b09ca440 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Tue, 16 May 2023 12:43:49 +0200 Subject: [PATCH 006/587] Switch runtime Java version to Temurin 21.0.1 This commit introduces following changes: * All Trino tests runs on JDK 21 (including product tests) * Recommend Eclipse Temurin in RPM preinstall * Add testing of RPM with JDK 21 * Switches docker image to use JDK 21 This commit does not change the required runtime JDK version and doesn't change the language level to 21. This allows this change to be reverted if regressions are reported. --- .github/actions/setup/action.yml | 6 ++++-- .github/workflows/ci.yml | 6 +++--- .github/workflows/docs.yml | 4 ++-- .java-version | 2 +- core/trino-server-rpm/src/main/rpm/preinstall | 19 ++++++++++++------- .../java/io/trino/server/rpm/ServerIT.java | 12 ++++++++---- .../launcher/env/EnvironmentOptions.java | 3 +-- 7 files changed, 31 insertions(+), 21 deletions(-) diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index c455d40906b6..c9f7304a2c36 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -3,7 +3,7 @@ description: "Verify checked out commits and setup Java" inputs: java-version: description: "Java version to setup" - default: 17 + default: 21 cache: description: "Cache Maven repo (true/false/restore)" default: true @@ -50,7 +50,9 @@ runs: if: ${{ format('{0}', inputs.cache) == 'true' }} uses: actions/cache@v3 with: - path: ~/.m2/repository + path: | + ~/.m2/repository + /tmp/pt_java_downloads key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven- diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2b1f31afd155..fe66716f51e5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,6 +36,7 @@ env: # used by actions/cache to retry the download after this time: https://github.com/actions/cache/blob/main/workarounds.md#cache-segment-restore-timeout SEGMENT_DOWNLOAD_TIMEOUT_MINS: 5 CI_SKIP_SECRETS_PRESENCE_CHECKS: ${{ secrets.CI_SKIP_SECRETS_PRESENCE_CHECKS }} + PTL_TMP_DOWNLOAD_PATH: /tmp/pt_java_downloads # Cancel previous PR builds. concurrency: @@ -56,7 +57,7 @@ jobs: fail-fast: false matrix: java-version: - - 17 + - 17 # Keep testing on JDK 17 to ensure basic backward compatibility - 21 timeout-minutes: 45 steps: @@ -571,7 +572,6 @@ jobs: - lib/trino-filesystem-s3 - lib/trino-hdfs - { modules: core/trino-main } - - { modules: core/trino-main, jdk: 21 } - { modules: lib/trino-filesystem-s3, profile: cloud-tests } - { modules: lib/trino-filesystem-azure, profile: cloud-tests } - { modules: plugin/trino-accumulo } @@ -642,8 +642,8 @@ jobs: - uses: ./.github/actions/setup with: cache: restore - java-version: ${{ matrix.jdk != '' && matrix.jdk || '17' }} cleanup-node: ${{ format('{0}', matrix.modules == 'plugin/trino-singlestore') }} + java-version: ${{ matrix.jdk != '' && matrix.jdk || '21' }} - name: Maven Install run: | export MAVEN_OPTS="${MAVEN_INSTALL_OPTS}" diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index f8f49d9408b4..304b31124cdf 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -52,7 +52,7 @@ jobs: - uses: actions/setup-java@v3 with: distribution: 'temurin' # use same JDK distro as in Trino docker images - java-version: 17 + java-version: 21 cache: 'maven' - name: Configure Problem Matchers run: | @@ -86,7 +86,7 @@ jobs: - uses: actions/setup-java@v3 with: distribution: 'temurin' # use same JDK distro as in Trino docker images - java-version: 17 + java-version: 21 cache: 'maven' - name: Configure Problem Matchers run: | diff --git a/.java-version b/.java-version index e000eb89469c..a8f5438c0a58 100644 --- a/.java-version +++ b/.java-version @@ -1 +1 @@ -17.0.9 +21.0.1 diff --git a/core/trino-server-rpm/src/main/rpm/preinstall b/core/trino-server-rpm/src/main/rpm/preinstall index ac37ae58e579..0bf1e4a8fae4 100644 --- a/core/trino-server-rpm/src/main/rpm/preinstall +++ b/core/trino-server-rpm/src/main/rpm/preinstall @@ -36,6 +36,12 @@ if ! check_if_correct_java_version "$JAVA_HOME"; then for candidate in \ /usr/lib/jvm/java-17-* \ /usr/lib/jvm/zulu-17 \ + /usr/lib/jvm/temurin-17 \ + /usr/lib/jvm/temurin-17-* \ + /usr/lib/jvm/java-21-* \ + /usr/lib/jvm/zulu-21 \ + /usr/lib/jvm/temurin-21 \ + /usr/lib/jvm/temurin-21-* \ /usr/lib/jvm/default-java \ /usr/java/default \ / \ @@ -53,15 +59,14 @@ fi if [ "$java_found" = false ]; then cat 1>&2 <>> https://www.azul.com/downloads/zulu-community/ <<< | -| | -| NOTE: This script will attempt to find Java whether you install | -| using the binary or the RPM based installer. | +| NOTE: This script will attempt to find Java whether you install | +| using the binary or the RPM based installer. | +======================================================================+ EOF exit 1 diff --git a/core/trino-server-rpm/src/test/java/io/trino/server/rpm/ServerIT.java b/core/trino-server-rpm/src/test/java/io/trino/server/rpm/ServerIT.java index 4a4107c927b7..66f6a11b6708 100644 --- a/core/trino-server-rpm/src/test/java/io/trino/server/rpm/ServerIT.java +++ b/core/trino-server-rpm/src/test/java/io/trino/server/rpm/ServerIT.java @@ -43,15 +43,17 @@ public class ServerIT { private static final String BASE_IMAGE_PREFIX = "eclipse-temurin:"; - private static final String BASE_IMAGE_SUFFIX = "-jre-centos7"; + private static final String BASE_IMAGE_SUFFIX = "-jre-ubi9-minimal"; @Test(dataProvider = "rpmJavaTestDataProvider") public void testInstall(String rpmHostPath, String javaVersion) { String rpm = "/" + new File(rpmHostPath).getName(); String command = "" + + // install required dependencies that are missing in UBI9-minimal + "microdnf install -y python sudo\n" + // install RPM - "yum localinstall -q -y " + rpm + "\n" + + "rpm -i " + rpm + "\n" + // create Hive catalog file "mkdir /etc/trino/catalog\n" + "echo CONFIG_ENV[HMS_PORT]=9083 >> /etc/trino/env.sh\n" + @@ -93,8 +95,10 @@ public void testUninstall(String rpmHostPath, String javaVersion) { String rpm = "/" + new File(rpmHostPath).getName(); String installAndStartTrino = "" + + // install required dependencies that are missing in UBI9-minimal + "microdnf install -y python sudo\n" + // install RPM - "yum localinstall -q -y " + rpm + "\n" + + "rpm -i " + rpm + "\n" + "/etc/init.d/trino start\n" + // allow tail to work with Docker's non-local file system "tail ---disable-inotify -F /var/log/trino/server.log\n"; @@ -124,7 +128,7 @@ public static Object[][] rpmJavaTestDataProvider() String rpmHostPath = requireNonNull(System.getProperty("rpm"), "rpm is null"); return new Object[][]{ {rpmHostPath, "17"}, - {rpmHostPath, "19"}}; + {rpmHostPath, "21"}}; } private static void assertPathDeleted(GenericContainer container, String path) diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/EnvironmentOptions.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/EnvironmentOptions.java index 1ade2cfd8cd2..d286764008d0 100644 --- a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/EnvironmentOptions.java +++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/EnvironmentOptions.java @@ -23,7 +23,6 @@ import static com.google.common.base.Preconditions.checkArgument; import static io.trino.tests.product.launcher.env.EnvironmentContainers.COORDINATOR; -import static io.trino.tests.product.launcher.env.jdk.BuiltInJdkProvider.BUILT_IN_NAME; import static java.util.Locale.ENGLISH; import static picocli.CommandLine.Option; @@ -58,7 +57,7 @@ public final class EnvironmentOptions public String launcherBin; @Option(names = "--trino-jdk-version", paramLabel = "", description = "JDK to use for running Trino " + DEFAULT_VALUE) - public String jdkProvider = BUILT_IN_NAME; + public String jdkProvider = "temurin21"; @Option(names = "--jdk-tmp-download-path", paramLabel = "", defaultValue = "${env:PTL_TMP_DOWNLOAD_PATH:-${sys:java.io.tmpdir}/ptl-tmp-download}", description = "Path to use to download JDK distributions " + DEFAULT_VALUE) @Nullable From ccdff820f51e2b3203eb11238cba36584895cdcd Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Tue, 24 Oct 2023 19:41:38 +0200 Subject: [PATCH 007/587] Remove no longer supported G1UsePreventiveGC in JDK 21 --- core/docker/default/etc/jvm.config | 2 -- .../src/main/resources/dist/config/jvm.config | 2 -- docs/src/main/sphinx/installation/deployment.md | 5 +---- pom.xml | 1 - 4 files changed, 1 insertion(+), 9 deletions(-) diff --git a/core/docker/default/etc/jvm.config b/core/docker/default/etc/jvm.config index f667372c8db0..ad09e7a867e3 100644 --- a/core/docker/default/etc/jvm.config +++ b/core/docker/default/etc/jvm.config @@ -15,7 +15,5 @@ # Improve AES performance for S3, etc. on ARM64 (JDK-8271567) -XX:+UnlockDiagnosticVMOptions -XX:+UseAESCTRIntrinsics -# Disable Preventive GC for performance reasons (JDK-8293861) --XX:-G1UsePreventiveGC # Reduce starvation of threads by GClocker, recommend to set about the number of cpu cores (JDK-8192647) -XX:GCLockerRetryAllocationCount=32 diff --git a/core/trino-server-rpm/src/main/resources/dist/config/jvm.config b/core/trino-server-rpm/src/main/resources/dist/config/jvm.config index 5725218bd7a9..7945e717e560 100644 --- a/core/trino-server-rpm/src/main/resources/dist/config/jvm.config +++ b/core/trino-server-rpm/src/main/resources/dist/config/jvm.config @@ -14,7 +14,5 @@ # Improve AES performance for S3, etc. on ARM64 (JDK-8271567) -XX:+UnlockDiagnosticVMOptions -XX:+UseAESCTRIntrinsics -# Disable Preventive GC for performance reasons (JDK-8293861) --XX:-G1UsePreventiveGC # Reduce starvation of threads by GClocker, recommend to set about the number of cpu cores (JDK-8192647) -XX:GCLockerRetryAllocationCount=32 diff --git a/docs/src/main/sphinx/installation/deployment.md b/docs/src/main/sphinx/installation/deployment.md index c82c6b423484..1053093152a9 100644 --- a/docs/src/main/sphinx/installation/deployment.md +++ b/docs/src/main/sphinx/installation/deployment.md @@ -141,8 +141,6 @@ The following provides a good starting point for creating `etc/jvm.config`: -XX:+UnlockDiagnosticVMOptions -XX:+UseAESCTRIntrinsics -Dfile.encoding=UTF-8 -# Disable Preventive GC for performance reasons (JDK-8293861) --XX:-G1UsePreventiveGC # Reduce starvation of threads by GClocker, recommend to set about the number of cpu cores (JDK-8192647) -XX:GCLockerRetryAllocationCount=32 ``` @@ -175,8 +173,7 @@ prevents Trino from starting. You can workaround this by overriding the temporary directory by adding `-Djava.io.tmpdir=/path/to/other/tmpdir` to the list of JVM options. -We enable `-XX:+UnlockDiagnosticVMOptions` and `-XX:+UseAESCTRIntrinsics` to improve AES performance for S3, etc. on ARM64 ([JDK-8271567](https://bugs.openjdk.java.net/browse/JDK-8271567)) -We disable Preventive GC (`-XX:-G1UsePreventiveGC`) for performance reasons (see [JDK-8293861](https://bugs.openjdk.org/browse/JDK-8293861)) +We enable `-XX:+UnlockDiagnosticVMOptions` and `-XX:+UseAESCTRIntrinsics` to improve AES performance for S3, etc. on ARM64 ([JDK-8271567](https://bugs.openjdk.java.net/browse/JDK-8271567)) We set GCLocker retry allocation count (`-XX:GCLockerRetryAllocationCount=32`) to avoid OOM too early (see [JDK-8192647](https://bugs.openjdk.org/browse/JDK-8192647)) (config-properties)= diff --git a/pom.xml b/pom.xml index 5ca88740d054..2f7ae7e4cc20 100644 --- a/pom.xml +++ b/pom.xml @@ -209,7 +209,6 @@ -XX:+UnlockDiagnosticVMOptions -XX:GCLockerRetryAllocationCount=10 - -XX:-G1UsePreventiveGC -missing From efd31546bbdb994d7c6f47b77fc866b17567743c Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Tue, 31 Oct 2023 11:28:25 +0100 Subject: [PATCH 008/587] Remove no longer needed UseAESCTRIntrinsics --- core/docker/default/etc/jvm.config | 4 +--- .../src/main/resources/dist/config/jvm.config | 4 +--- docs/src/main/sphinx/installation/deployment.md | 4 +--- .../conf/environment/multinode-all/jvm.config | 2 -- .../conf/environment/multinode-ignite/jvm.config | 2 -- .../conf/environment/multinode/multinode-master-jvm.config | 2 -- .../conf/environment/multinode/multinode-worker-jvm.config | 2 -- .../docker/presto-product-tests/conf/presto/etc/jvm.config | 2 -- 8 files changed, 3 insertions(+), 19 deletions(-) diff --git a/core/docker/default/etc/jvm.config b/core/docker/default/etc/jvm.config index ad09e7a867e3..a5d08bcf8ce8 100644 --- a/core/docker/default/etc/jvm.config +++ b/core/docker/default/etc/jvm.config @@ -12,8 +12,6 @@ -XX:PerBytecodeRecompilationCutoff=10000 -Djdk.attach.allowAttachSelf=true -Djdk.nio.maxCachedBufferSize=2000000 -# Improve AES performance for S3, etc. on ARM64 (JDK-8271567) --XX:+UnlockDiagnosticVMOptions --XX:+UseAESCTRIntrinsics # Reduce starvation of threads by GClocker, recommend to set about the number of cpu cores (JDK-8192647) +-XX:+UnlockDiagnosticVMOptions -XX:GCLockerRetryAllocationCount=32 diff --git a/core/trino-server-rpm/src/main/resources/dist/config/jvm.config b/core/trino-server-rpm/src/main/resources/dist/config/jvm.config index 7945e717e560..18dc47b3e28a 100644 --- a/core/trino-server-rpm/src/main/resources/dist/config/jvm.config +++ b/core/trino-server-rpm/src/main/resources/dist/config/jvm.config @@ -11,8 +11,6 @@ -XX:PerBytecodeRecompilationCutoff=10000 -Djdk.attach.allowAttachSelf=true -Djdk.nio.maxCachedBufferSize=2000000 -# Improve AES performance for S3, etc. on ARM64 (JDK-8271567) --XX:+UnlockDiagnosticVMOptions --XX:+UseAESCTRIntrinsics # Reduce starvation of threads by GClocker, recommend to set about the number of cpu cores (JDK-8192647) +-XX:+UnlockDiagnosticVMOptions -XX:GCLockerRetryAllocationCount=32 diff --git a/docs/src/main/sphinx/installation/deployment.md b/docs/src/main/sphinx/installation/deployment.md index 1053093152a9..e7f723c5ede7 100644 --- a/docs/src/main/sphinx/installation/deployment.md +++ b/docs/src/main/sphinx/installation/deployment.md @@ -138,10 +138,9 @@ The following provides a good starting point for creating `etc/jvm.config`: -XX:PerBytecodeRecompilationCutoff=10000 -Djdk.attach.allowAttachSelf=true -Djdk.nio.maxCachedBufferSize=2000000 --XX:+UnlockDiagnosticVMOptions --XX:+UseAESCTRIntrinsics -Dfile.encoding=UTF-8 # Reduce starvation of threads by GClocker, recommend to set about the number of cpu cores (JDK-8192647) +-XX:+UnlockDiagnosticVMOptions -XX:GCLockerRetryAllocationCount=32 ``` @@ -173,7 +172,6 @@ prevents Trino from starting. You can workaround this by overriding the temporary directory by adding `-Djava.io.tmpdir=/path/to/other/tmpdir` to the list of JVM options. -We enable `-XX:+UnlockDiagnosticVMOptions` and `-XX:+UseAESCTRIntrinsics` to improve AES performance for S3, etc. on ARM64 ([JDK-8271567](https://bugs.openjdk.java.net/browse/JDK-8271567)) We set GCLocker retry allocation count (`-XX:GCLockerRetryAllocationCount=32`) to avoid OOM too early (see [JDK-8192647](https://bugs.openjdk.org/browse/JDK-8192647)) (config-properties)= diff --git a/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/environment/multinode-all/jvm.config b/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/environment/multinode-all/jvm.config index 2e157dfadc1b..94148bab811b 100644 --- a/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/environment/multinode-all/jvm.config +++ b/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/environment/multinode-all/jvm.config @@ -15,5 +15,3 @@ -DHADOOP_USER_NAME=hive -Duser.timezone=Asia/Kathmandu -XX:ErrorFile=/docker/logs/product-tests-presto-jvm-error-file.log --XX:+UnlockDiagnosticVMOptions --XX:+UseAESCTRIntrinsics diff --git a/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/environment/multinode-ignite/jvm.config b/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/environment/multinode-ignite/jvm.config index 2e157dfadc1b..94148bab811b 100644 --- a/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/environment/multinode-ignite/jvm.config +++ b/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/environment/multinode-ignite/jvm.config @@ -15,5 +15,3 @@ -DHADOOP_USER_NAME=hive -Duser.timezone=Asia/Kathmandu -XX:ErrorFile=/docker/logs/product-tests-presto-jvm-error-file.log --XX:+UnlockDiagnosticVMOptions --XX:+UseAESCTRIntrinsics diff --git a/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/environment/multinode/multinode-master-jvm.config b/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/environment/multinode/multinode-master-jvm.config index a97e926e967f..e3fc6a6f7386 100644 --- a/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/environment/multinode/multinode-master-jvm.config +++ b/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/environment/multinode/multinode-master-jvm.config @@ -14,5 +14,3 @@ -DHADOOP_USER_NAME=hive -Duser.timezone=Asia/Kathmandu -XX:ErrorFile=/docker/logs/product-tests-presto-jvm-error-file.log --XX:+UnlockDiagnosticVMOptions --XX:+UseAESCTRIntrinsics diff --git a/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/environment/multinode/multinode-worker-jvm.config b/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/environment/multinode/multinode-worker-jvm.config index 70f2eeb94658..30ad30547841 100644 --- a/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/environment/multinode/multinode-worker-jvm.config +++ b/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/environment/multinode/multinode-worker-jvm.config @@ -13,5 +13,3 @@ -DHADOOP_USER_NAME=hive -Duser.timezone=Asia/Kathmandu -XX:ErrorFile=/docker/logs/product-tests-presto-jvm-error-file.log --XX:+UnlockDiagnosticVMOptions --XX:+UseAESCTRIntrinsics diff --git a/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/presto/etc/jvm.config b/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/presto/etc/jvm.config index 32cbb9ba07be..68fa29d5cecc 100644 --- a/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/presto/etc/jvm.config +++ b/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/presto/etc/jvm.config @@ -14,5 +14,3 @@ -DHADOOP_USER_NAME=hive -Duser.timezone=Asia/Kathmandu -XX:ErrorFile=/docker/logs/product-tests-presto-jvm-error-file.log --XX:+UnlockDiagnosticVMOptions --XX:+UseAESCTRIntrinsics From 82cfb00f7b9ed690a2ab107e3f425274f7d35066 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Tue, 31 Oct 2023 14:30:10 +0100 Subject: [PATCH 009/587] Increase artifact-checks timeout to 45 minutes --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fe66716f51e5..149d9c839380 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,7 +87,7 @@ jobs: artifact-checks: runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 45 steps: - uses: actions/checkout@v4 with: From 1a2cc3c70b75b148f7e4f993d54a1d1b76c22158 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Tue, 31 Oct 2023 13:10:08 -0700 Subject: [PATCH 010/587] Fix handling of inline functions in pushdown --- .../io/trino/sql/planner/ConnectorExpressionTranslator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/ConnectorExpressionTranslator.java b/core/trino-main/src/main/java/io/trino/sql/planner/ConnectorExpressionTranslator.java index 3e2bb5093b91..e9d28509754c 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/ConnectorExpressionTranslator.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/ConnectorExpressionTranslator.java @@ -701,9 +701,9 @@ protected Optional visitFunctionCall(FunctionCall node, Voi FunctionName name; if (isInlineFunction(functionName)) { - throw new IllegalArgumentException("Connector expressions cannot reference inline functions: " + functionName); + return Optional.empty(); } - else if (isBuiltinFunctionName(functionName)) { + if (isBuiltinFunctionName(functionName)) { name = new FunctionName(functionName.getFunctionName()); } else { From b15608b641fc43a72078357ed5c826d78843239d Mon Sep 17 00:00:00 2001 From: David Phillips Date: Tue, 24 Oct 2023 17:35:11 -0700 Subject: [PATCH 011/587] Simplify file system creation calls --- .../java/io/trino/plugin/hive/avro/AvroFileWriterFactory.java | 2 +- .../java/io/trino/plugin/hive/avro/AvroPageSourceFactory.java | 2 +- .../java/io/trino/plugin/hive/line/LineFileWriterFactory.java | 2 +- .../java/io/trino/plugin/hive/line/LinePageSourceFactory.java | 2 +- .../plugin/hive/metastore/SemiTransactionalHiveMetastore.java | 2 +- .../java/io/trino/plugin/hive/orc/OrcPageSourceFactory.java | 2 +- .../io/trino/plugin/hive/rcfile/RcFilePageSourceFactory.java | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroFileWriterFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroFileWriterFactory.java index 9df3eb8aa98c..9b1ca1db2009 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroFileWriterFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroFileWriterFactory.java @@ -109,7 +109,7 @@ public Optional createFileWriter( }).collect(toImmutableList()); try { - TrinoFileSystem fileSystem = fileSystemFactory.create(session.getIdentity()); + TrinoFileSystem fileSystem = fileSystemFactory.create(session); Schema fileSchema = AvroHiveFileUtils.determineSchemaOrThrowException(fileSystem, schema); TrinoOutputFile outputFile = fileSystem.newOutputFile(location); AggregatedMemoryContext outputStreamMemoryContext = newSimpleAggregatedMemoryContext(); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroPageSourceFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroPageSourceFactory.java index 86fa1c60452d..c390680d5985 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroPageSourceFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroPageSourceFactory.java @@ -108,7 +108,7 @@ public Optional createPageSource( .collect(toImmutableList()); } - TrinoFileSystem trinoFileSystem = trinoFileSystemFactory.create(session.getIdentity()); + TrinoFileSystem trinoFileSystem = trinoFileSystemFactory.create(session); TrinoInputFile inputFile = trinoFileSystem.newInputFile(path); HiveTimestampPrecision hiveTimestampPrecision = getTimestampPrecision(session); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/line/LineFileWriterFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/line/LineFileWriterFactory.java index 6015b6dbc5cb..74afba8e4357 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/line/LineFileWriterFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/line/LineFileWriterFactory.java @@ -117,7 +117,7 @@ public Optional createFileWriter( LineSerializer lineSerializer = lineSerializerFactory.create(columns, fromProperties(schema)); try { - TrinoFileSystem fileSystem = fileSystemFactory.create(session.getIdentity()); + TrinoFileSystem fileSystem = fileSystemFactory.create(session); AggregatedMemoryContext outputStreamMemoryContext = newSimpleAggregatedMemoryContext(); OutputStream outputStream = fileSystem.newOutputFile(location).create(outputStreamMemoryContext); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/line/LinePageSourceFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/line/LinePageSourceFactory.java index 54e802a0f557..eae741258e6a 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/line/LinePageSourceFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/line/LinePageSourceFactory.java @@ -132,7 +132,7 @@ public Optional createPageSource( return Optional.of(noProjectionAdaptation(new EmptyPageSource())); } - TrinoFileSystem trinoFileSystem = fileSystemFactory.create(session.getIdentity()); + TrinoFileSystem trinoFileSystem = fileSystemFactory.create(session); TrinoInputFile inputFile = trinoFileSystem.newInputFile(path); try { // buffer file if small diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/SemiTransactionalHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/SemiTransactionalHiveMetastore.java index a49ce5cdaae0..ae16b8fde0c6 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/SemiTransactionalHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/SemiTransactionalHiveMetastore.java @@ -749,7 +749,7 @@ public synchronized void truncateUnpartitionedTable(ConnectorSession session, St } Location location = Location.of(table.getStorage().getLocation()); - TrinoFileSystem fileSystem = fileSystemFactory.create(session.getIdentity()); + TrinoFileSystem fileSystem = fileSystemFactory.create(session); setExclusive(delegate -> { RecursiveDeleteResult recursiveDeleteResult = recursiveDeleteFiles(fileSystem, location, ImmutableSet.of(""), false); if (!recursiveDeleteResult.getNotDeletedEligibleItems().isEmpty()) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcPageSourceFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcPageSourceFactory.java index 4127b01f50dd..19bc4e238ddc 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcPageSourceFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcPageSourceFactory.java @@ -256,7 +256,7 @@ private ConnectorPageSource createOrcPageSource( boolean originalFilesPresent = acidInfo.isPresent() && !acidInfo.get().getOriginalFiles().isEmpty(); try { - TrinoFileSystem fileSystem = fileSystemFactory.create(session.getIdentity()); + TrinoFileSystem fileSystem = fileSystemFactory.create(session); TrinoInputFile inputFile = fileSystem.newInputFile(path, estimatedFileSize); orcDataSource = new HdfsOrcDataSource( new OrcDataSourceId(path.toString()), diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/rcfile/RcFilePageSourceFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/rcfile/RcFilePageSourceFactory.java index e1ebd7f82818..389eb0408c3a 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/rcfile/RcFilePageSourceFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/rcfile/RcFilePageSourceFactory.java @@ -128,7 +128,7 @@ else if (deserializerClassName.equals(COLUMNAR_SERDE_CLASS)) { .collect(toImmutableList()); } - TrinoFileSystem trinoFileSystem = fileSystemFactory.create(session.getIdentity()); + TrinoFileSystem trinoFileSystem = fileSystemFactory.create(session); TrinoInputFile inputFile = trinoFileSystem.newInputFile(path); try { length = min(inputFile.length() - start, length); From 533ea97ce4af9f3a0b32ba562b510f2f7f07d0e5 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Mon, 30 Oct 2023 00:08:34 -0700 Subject: [PATCH 012/587] Add createTemporaryDirectory method to TrinoFileSystem --- .../filesystem/azure/AzureFileSystem.java | 29 ++++++++++ .../manager/SwitchingFileSystem.java | 7 +++ .../io/trino/filesystem/s3/S3FileSystem.java | 8 +++ .../io/trino/filesystem/TrinoFileSystem.java | 14 +++++ .../filesystem/local/LocalFileSystem.java | 7 +++ .../filesystem/memory/MemoryFileSystem.java | 8 +++ .../filesystem/tracing/TracingFileSystem.java | 10 ++++ .../filesystem/TrackingFileSystemFactory.java | 7 +++ .../trino/filesystem/hdfs/HdfsFileSystem.java | 53 +++++++++++++++++++ .../trino/hdfs/TrinoHdfsFileSystemStats.java | 8 +++ 10 files changed, 151 insertions(+) diff --git a/lib/trino-filesystem-azure/src/main/java/io/trino/filesystem/azure/AzureFileSystem.java b/lib/trino-filesystem-azure/src/main/java/io/trino/filesystem/azure/AzureFileSystem.java index 45925f474b98..33bfad0703de 100644 --- a/lib/trino-filesystem-azure/src/main/java/io/trino/filesystem/azure/AzureFileSystem.java +++ b/lib/trino-filesystem-azure/src/main/java/io/trino/filesystem/azure/AzureFileSystem.java @@ -53,6 +53,7 @@ import static io.trino.filesystem.azure.AzureUtils.handleAzureException; import static java.lang.Math.toIntExact; import static java.util.Objects.requireNonNull; +import static java.util.UUID.randomUUID; import static java.util.function.Predicate.not; public class AzureFileSystem @@ -370,6 +371,34 @@ public Set listDirectories(Location location) } } + @Override + public Optional createTemporaryDirectory(Location targetPath, String temporaryPrefix, String relativePrefix) + throws IOException + { + AzureLocation azureLocation = new AzureLocation(targetPath); + if (!isHierarchicalNamespaceEnabled(azureLocation)) { + return Optional.empty(); + } + + // allow for absolute or relative temporary prefix + Location temporary; + if (temporaryPrefix.startsWith("/")) { + String prefix = temporaryPrefix; + while (prefix.startsWith("/")) { + prefix = prefix.substring(1); + } + temporary = azureLocation.baseLocation().appendPath(prefix); + } + else { + temporary = targetPath.appendPath(temporaryPrefix); + } + + temporary = temporary.appendPath(randomUUID().toString()); + + createDirectory(temporary); + return Optional.of(temporary); + } + private Set listGen2Directories(AzureLocation location) throws IOException { diff --git a/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/SwitchingFileSystem.java b/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/SwitchingFileSystem.java index 041a89a720c6..c38043cc0763 100644 --- a/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/SwitchingFileSystem.java +++ b/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/SwitchingFileSystem.java @@ -138,6 +138,13 @@ public Set listDirectories(Location location) return fileSystem(location).listDirectories(location); } + @Override + public Optional createTemporaryDirectory(Location targetPath, String temporaryPrefix, String relativePrefix) + throws IOException + { + return fileSystem(targetPath).createTemporaryDirectory(targetPath, temporaryPrefix, relativePrefix); + } + private TrinoFileSystem fileSystem(Location location) { return createFileSystem(determineFactory(location)); diff --git a/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3FileSystem.java b/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3FileSystem.java index 30d3b4f3e133..605c794ae5ad 100644 --- a/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3FileSystem.java +++ b/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3FileSystem.java @@ -244,6 +244,14 @@ public Set listDirectories(Location location) } } + @Override + public Optional createTemporaryDirectory(Location targetPath, String temporaryPrefix, String relativePrefix) + { + validateS3Location(targetPath); + // S3 does not have directories + return Optional.empty(); + } + @SuppressWarnings("ResultOfObjectAllocationIgnored") private static void validateS3Location(Location location) { diff --git a/lib/trino-filesystem/src/main/java/io/trino/filesystem/TrinoFileSystem.java b/lib/trino-filesystem/src/main/java/io/trino/filesystem/TrinoFileSystem.java index bc4d79ddb40a..97babd4aee16 100644 --- a/lib/trino-filesystem/src/main/java/io/trino/filesystem/TrinoFileSystem.java +++ b/lib/trino-filesystem/src/main/java/io/trino/filesystem/TrinoFileSystem.java @@ -207,4 +207,18 @@ void renameDirectory(Location source, Location target) */ Set listDirectories(Location location) throws IOException; + + /** + * Creates a temporary directory for the target path. The directory will be created + * using the (possibly absolute) prefix such that the directory can be renamed to + * the target path. The relative prefix will be used if the target path does not + * support the temporary prefix (which is typically absolute). + *

+ * The temporary directory is not created for non-hierarchical file systems or for + * target paths that do not support renaming, and an empty optional is returned. + * + * @throws IllegalArgumentException If the target path is not valid for this file system. + */ + Optional createTemporaryDirectory(Location targetPath, String temporaryPrefix, String relativePrefix) + throws IOException; } diff --git a/lib/trino-filesystem/src/main/java/io/trino/filesystem/local/LocalFileSystem.java b/lib/trino-filesystem/src/main/java/io/trino/filesystem/local/LocalFileSystem.java index f96315bf86a8..a6fa596124c5 100644 --- a/lib/trino-filesystem/src/main/java/io/trino/filesystem/local/LocalFileSystem.java +++ b/lib/trino-filesystem/src/main/java/io/trino/filesystem/local/LocalFileSystem.java @@ -219,6 +219,13 @@ public Set listDirectories(Location location) } } + @Override + public Optional createTemporaryDirectory(Location targetPath, String temporaryPrefix, String relativePrefix) + throws IOException + { + throw new IOException("Local file system does not support creating temporary directories"); + } + private Path toFilePath(Location location) { validateLocalLocation(location); diff --git a/lib/trino-filesystem/src/main/java/io/trino/filesystem/memory/MemoryFileSystem.java b/lib/trino-filesystem/src/main/java/io/trino/filesystem/memory/MemoryFileSystem.java index 303f93b79fc3..1b07a632fa69 100644 --- a/lib/trino-filesystem/src/main/java/io/trino/filesystem/memory/MemoryFileSystem.java +++ b/lib/trino-filesystem/src/main/java/io/trino/filesystem/memory/MemoryFileSystem.java @@ -199,6 +199,14 @@ public Set listDirectories(Location location) return directories.build(); } + @Override + public Optional createTemporaryDirectory(Location targetPath, String temporaryPrefix, String relativePrefix) + { + validateMemoryLocation(targetPath); + // memory file system does not have directories + return Optional.empty(); + } + private static String toBlobKey(Location location) { validateMemoryLocation(location); diff --git a/lib/trino-filesystem/src/main/java/io/trino/filesystem/tracing/TracingFileSystem.java b/lib/trino-filesystem/src/main/java/io/trino/filesystem/tracing/TracingFileSystem.java index 2e0c694d0552..0755a924fdd6 100644 --- a/lib/trino-filesystem/src/main/java/io/trino/filesystem/tracing/TracingFileSystem.java +++ b/lib/trino-filesystem/src/main/java/io/trino/filesystem/tracing/TracingFileSystem.java @@ -148,4 +148,14 @@ public Set listDirectories(Location location) .startSpan(); return withTracing(span, () -> delegate.listDirectories(location)); } + + @Override + public Optional createTemporaryDirectory(Location targetPath, String temporaryPrefix, String relativePrefix) + throws IOException + { + Span span = tracer.spanBuilder("FileSystem.createTemporaryDirectory") + .setAttribute(FileSystemAttributes.FILE_LOCATION, targetPath.toString()) + .startSpan(); + return withTracing(span, () -> delegate.createTemporaryDirectory(targetPath, temporaryPrefix, relativePrefix)); + } } diff --git a/lib/trino-filesystem/src/test/java/io/trino/filesystem/TrackingFileSystemFactory.java b/lib/trino-filesystem/src/test/java/io/trino/filesystem/TrackingFileSystemFactory.java index 9bbb650414f6..c9f4bae6e02c 100644 --- a/lib/trino-filesystem/src/test/java/io/trino/filesystem/TrackingFileSystemFactory.java +++ b/lib/trino-filesystem/src/test/java/io/trino/filesystem/TrackingFileSystemFactory.java @@ -194,6 +194,13 @@ public Set listDirectories(Location location) { return delegate.listDirectories(location); } + + @Override + public Optional createTemporaryDirectory(Location targetPath, String temporaryPrefix, String relativePrefix) + throws IOException + { + return delegate.createTemporaryDirectory(targetPath, temporaryPrefix, relativePrefix); + } } private static class TrackingInputFile diff --git a/lib/trino-hdfs/src/main/java/io/trino/filesystem/hdfs/HdfsFileSystem.java b/lib/trino-hdfs/src/main/java/io/trino/filesystem/hdfs/HdfsFileSystem.java index aa1337607e8a..e1ae9400f461 100644 --- a/lib/trino-hdfs/src/main/java/io/trino/filesystem/hdfs/HdfsFileSystem.java +++ b/lib/trino-hdfs/src/main/java/io/trino/filesystem/hdfs/HdfsFileSystem.java @@ -29,6 +29,8 @@ import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.permission.FsPermission; +import org.apache.hadoop.fs.viewfs.ViewFileSystem; +import org.apache.hadoop.hdfs.DistributedFileSystem; import java.io.FileNotFoundException; import java.io.IOException; @@ -42,11 +44,13 @@ import java.util.UUID; import java.util.stream.Stream; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static io.trino.filesystem.hdfs.HadoopPaths.hadoopPath; import static io.trino.filesystem.hdfs.HdfsFileIterator.listedLocation; import static io.trino.hdfs.FileSystemUtils.getRawFileSystem; import static java.util.Objects.requireNonNull; +import static java.util.UUID.randomUUID; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.mapping; import static java.util.stream.Collectors.toList; @@ -375,6 +379,55 @@ public Set listDirectories(Location location) }); } + @Override + public Optional createTemporaryDirectory(Location targetLocation, String temporaryPrefix, String relativePrefix) + throws IOException + { + checkArgument(!relativePrefix.contains("/"), "relativePrefix must not contain slash"); + stats.getCreateTemporaryDirectoryCalls().newCall(); + Path targetPath = hadoopPath(targetLocation); + FileSystem fileSystem = environment.getFileSystem(context, targetPath); + + return environment.doAs(context.getIdentity(), () -> { + try (TimeStat.BlockTimer ignored = stats.getCreateTemporaryDirectoryCalls().time()) { + FileSystem rawFileSystem = getRawFileSystem(fileSystem); + + // use relative temporary directory on ViewFS + String prefix = (rawFileSystem instanceof ViewFileSystem) ? relativePrefix : temporaryPrefix; + + // create a temporary directory on the same file system + Path temporaryRoot = new Path(targetPath, prefix); + Path temporaryPath = new Path(temporaryRoot, randomUUID().toString()); + Location temporaryLocation = Location.of(temporaryPath.toString()); + + if (!hierarchical(fileSystem, temporaryLocation)) { + return Optional.empty(); + } + + // files cannot be moved between encryption zones + if ((rawFileSystem instanceof DistributedFileSystem distributedFileSystem) && + (distributedFileSystem.getEZForPath(targetPath) != null)) { + return Optional.empty(); + } + + Optional permission = environment.getNewDirectoryPermissions(); + if (!fileSystem.mkdirs(temporaryPath, permission.orElse(null))) { + throw new IOException("mkdirs failed for " + temporaryPath); + } + // explicitly set permission since the default umask overrides it on creation + if (permission.isPresent()) { + fileSystem.setPermission(temporaryPath, permission.get()); + } + + return Optional.of(temporaryLocation); + } + catch (IOException e) { + stats.getCreateTemporaryDirectoryCalls().recordException(e); + throw new IOException("Create temporary directory for %s failed: %s".formatted(targetLocation, e.getMessage()), e); + } + }); + } + private boolean hierarchical(FileSystem fileSystem, Location rootLocation) { Boolean knownResult = KNOWN_HIERARCHICAL_FILESYSTEMS.get(fileSystem.getScheme()); diff --git a/lib/trino-hdfs/src/main/java/io/trino/hdfs/TrinoHdfsFileSystemStats.java b/lib/trino-hdfs/src/main/java/io/trino/hdfs/TrinoHdfsFileSystemStats.java index 5b942370a137..c5e46aa187e4 100644 --- a/lib/trino-hdfs/src/main/java/io/trino/hdfs/TrinoHdfsFileSystemStats.java +++ b/lib/trino-hdfs/src/main/java/io/trino/hdfs/TrinoHdfsFileSystemStats.java @@ -28,6 +28,7 @@ public final class TrinoHdfsFileSystemStats private final CallStats createDirectoryCalls = new CallStats(); private final CallStats renameDirectoryCalls = new CallStats(); private final CallStats listDirectoriesCalls = new CallStats(); + private final CallStats createTemporaryDirectoryCalls = new CallStats(); @Managed @Nested @@ -98,4 +99,11 @@ public CallStats getListDirectoriesCalls() { return listDirectoriesCalls; } + + @Managed + @Nested + public CallStats getCreateTemporaryDirectoryCalls() + { + return createTemporaryDirectoryCalls; + } } From b2966fc7a2b59852ed750e97b0bb32886ec31da8 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Mon, 30 Oct 2023 00:48:57 -0700 Subject: [PATCH 013/587] Remove Hadoop dependency for TrinoFileSystemCache stats Referencing TrinoFileSystemCache requires Hadoop to be on the compile classpath due to it having a Hadoop superclass. --- .../java/io/trino/hdfs/TrinoFileSystemCache.java | 12 ++++++------ .../io/trino/hdfs/TrinoFileSystemCacheStats.java | 5 +++++ .../test/java/io/trino/hdfs/TestFileSystemCache.java | 8 ++++---- .../io/trino/hdfs/TestTrinoFileSystemCacheStats.java | 4 ++-- .../main/java/io/trino/plugin/hive/HiveModule.java | 6 +++--- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/lib/trino-hdfs/src/main/java/io/trino/hdfs/TrinoFileSystemCache.java b/lib/trino-hdfs/src/main/java/io/trino/hdfs/TrinoFileSystemCache.java index c3661475db87..1aed906ec67d 100644 --- a/lib/trino-hdfs/src/main/java/io/trino/hdfs/TrinoFileSystemCache.java +++ b/lib/trino-hdfs/src/main/java/io/trino/hdfs/TrinoFileSystemCache.java @@ -61,7 +61,7 @@ public class TrinoFileSystemCache public static final String CACHE_KEY = "fs.cache.credentials"; - public static final TrinoFileSystemCache INSTANCE = new TrinoFileSystemCache(); + static final TrinoFileSystemCache INSTANCE = new TrinoFileSystemCache(); private final AtomicLong unique = new AtomicLong(); @@ -105,6 +105,11 @@ int getCacheSize() return cache.size(); } + TrinoFileSystemCacheStats getStats() + { + return stats; + } + private FileSystem getInternal(URI uri, Configuration conf, long unique) throws IOException { @@ -466,9 +471,4 @@ public LocatedFileStatus next() return delegate.next(); } } - - public TrinoFileSystemCacheStats getFileSystemCacheStats() - { - return stats; - } } diff --git a/lib/trino-hdfs/src/main/java/io/trino/hdfs/TrinoFileSystemCacheStats.java b/lib/trino-hdfs/src/main/java/io/trino/hdfs/TrinoFileSystemCacheStats.java index d4c276fa2ab4..86b2c5904cd3 100644 --- a/lib/trino-hdfs/src/main/java/io/trino/hdfs/TrinoFileSystemCacheStats.java +++ b/lib/trino-hdfs/src/main/java/io/trino/hdfs/TrinoFileSystemCacheStats.java @@ -87,4 +87,9 @@ public void newRemoveCall() { removeCalls.update(1); } + + public static TrinoFileSystemCacheStats instance() + { + return TrinoFileSystemCache.INSTANCE.getStats(); + } } diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFileSystemCache.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFileSystemCache.java index 3e7c34a90e70..02bf0bea8f71 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFileSystemCache.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFileSystemCache.java @@ -90,10 +90,10 @@ public void testFileSystemCacheException() int maxCacheSize = 1000; for (int i = 0; i < maxCacheSize; i++) { - assertEquals(TrinoFileSystemCache.INSTANCE.getFileSystemCacheStats().getCacheSize(), i); + assertEquals(TrinoFileSystemCacheStats.instance().getCacheSize(), i); getFileSystem(environment, ConnectorIdentity.ofUser("user" + i)); } - assertEquals(TrinoFileSystemCache.INSTANCE.getFileSystemCacheStats().getCacheSize(), maxCacheSize); + assertEquals(TrinoFileSystemCacheStats.instance().getCacheSize(), maxCacheSize); assertThatThrownBy(() -> getFileSystem(environment, ConnectorIdentity.ofUser("user" + maxCacheSize))) .isInstanceOf(IOException.class) .hasMessage("FileSystem max cache size has been reached: " + maxCacheSize); @@ -115,10 +115,10 @@ public void testFileSystemCacheConcurrency() } ExecutorService executor = Executors.newFixedThreadPool(numThreads); - assertEquals(TrinoFileSystemCache.INSTANCE.getFileSystemCacheStats().getCacheSize(), 0); + assertEquals(TrinoFileSystemCacheStats.instance().getCacheSize(), 0); executor.invokeAll(callableTasks).forEach(MoreFutures::getFutureValue); executor.shutdown(); - assertEquals(TrinoFileSystemCache.INSTANCE.getFileSystemCacheStats().getCacheSize(), 0, "Cache size is non zero"); + assertEquals(TrinoFileSystemCacheStats.instance().getCacheSize(), 0, "Cache size is non zero"); } private static FileSystem getFileSystem(HdfsEnvironment environment, ConnectorIdentity identity) diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestTrinoFileSystemCacheStats.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestTrinoFileSystemCacheStats.java index 58efe717aa8c..97b6a83dde72 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestTrinoFileSystemCacheStats.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestTrinoFileSystemCacheStats.java @@ -30,7 +30,7 @@ public void testCacheSizeIsCorrect() throws Exception { TrinoFileSystemCache trinoFileSystemCache = new TrinoFileSystemCache(); - TrinoFileSystemCacheStats trinoFileSystemCacheStats = trinoFileSystemCache.getFileSystemCacheStats(); + TrinoFileSystemCacheStats trinoFileSystemCacheStats = trinoFileSystemCache.getStats(); assertEquals(trinoFileSystemCacheStats.getCacheSize(), 0); assertEquals(trinoFileSystemCache.getCacheSize(), 0); @@ -66,7 +66,7 @@ public void testCacheSizeIsCorrect() public void testFailedCallsCountIsCorrect() { TrinoFileSystemCache trinoFileSystemCache = new TrinoFileSystemCache(); - TrinoFileSystemCacheStats trinoFileSystemCacheStats = trinoFileSystemCache.getFileSystemCacheStats(); + TrinoFileSystemCacheStats trinoFileSystemCacheStats = trinoFileSystemCache.getStats(); Configuration configuration = newEmptyConfiguration(); configuration.setInt("fs.cache.max-size", 0); assertThatThrownBy(() -> trinoFileSystemCache.get(new URI("file:///tmp/path/"), configuration)) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveModule.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveModule.java index c081507cc289..0f43d35240f4 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveModule.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveModule.java @@ -22,13 +22,13 @@ import com.google.inject.multibindings.Multibinder; import io.airlift.event.client.EventClient; import io.trino.hdfs.HdfsNamenodeStats; -import io.trino.hdfs.TrinoFileSystemCache; import io.trino.hdfs.TrinoFileSystemCacheStats; import io.trino.plugin.base.CatalogName; import io.trino.plugin.hive.avro.AvroFileWriterFactory; import io.trino.plugin.hive.avro.AvroPageSourceFactory; import io.trino.plugin.hive.fs.CachingDirectoryLister; import io.trino.plugin.hive.fs.TransactionScopeCachingDirectoryListerFactory; +import io.trino.plugin.hive.fs.TrinoFileSystemCache; import io.trino.plugin.hive.line.CsvFileWriterFactory; import io.trino.plugin.hive.line.CsvPageSourceFactory; import io.trino.plugin.hive.line.JsonFileWriterFactory; @@ -117,9 +117,9 @@ public void configure(Binder binder) binder.bind(FileFormatDataSourceStats.class).in(Scopes.SINGLETON); newExporter(binder).export(FileFormatDataSourceStats.class).withGeneratedName(); - binder.bind(TrinoFileSystemCacheStats.class).toInstance(TrinoFileSystemCache.INSTANCE.getFileSystemCacheStats()); + binder.bind(TrinoFileSystemCacheStats.class).toInstance(TrinoFileSystemCacheStats.instance()); newExporter(binder).export(TrinoFileSystemCacheStats.class) - .as(generator -> generator.generatedNameOf(io.trino.plugin.hive.fs.TrinoFileSystemCache.class)); + .as(generator -> generator.generatedNameOf(TrinoFileSystemCache.class)); binder.bind(HdfsNamenodeStats.class).in(Scopes.SINGLETON); newExporter(binder).export(HdfsNamenodeStats.class) From 9d71039bd13eafa392aa578fd9fe6d27de057e45 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sun, 29 Oct 2023 22:54:21 -0700 Subject: [PATCH 014/587] Remove usages of Hadoop from HiveLocationService --- plugin/trino-hive/pom.xml | 9 +- .../plugin/hive/HiveLocationService.java | 58 +++---- .../plugin/hive/util/HiveWriteUtils.java | 154 ++---------------- .../trino/plugin/hive/AbstractTestHive.java | 75 ++------- .../hive/AbstractTestHiveFileSystem.java | 9 +- .../plugin/hive/TestHiveLocationService.java | 7 +- .../trino/plugin/hive/TestHivePageSink.java | 2 +- .../hive/TestHiveS3AndGlueMetastoreTest.java | 10 +- .../plugin/hive/util/TestHiveWriteUtils.java | 32 ---- 9 files changed, 72 insertions(+), 284 deletions(-) diff --git a/plugin/trino-hive/pom.xml b/plugin/trino-hive/pom.xml index 169641c45ae8..e63b4aa40e03 100644 --- a/plugin/trino-hive/pom.xml +++ b/plugin/trino-hive/pom.xml @@ -186,11 +186,6 @@ coral - - io.trino.hadoop - hadoop-apache - - io.trino.hive hive-thrift @@ -305,8 +300,8 @@ - io.trino - trino-hadoop-toolkit + io.trino.hadoop + hadoop-apache runtime diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveLocationService.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveLocationService.java index 6da35e45ba4f..a790c177284d 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveLocationService.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveLocationService.java @@ -15,15 +15,14 @@ import com.google.inject.Inject; import io.trino.filesystem.Location; -import io.trino.hdfs.HdfsContext; -import io.trino.hdfs.HdfsEnvironment; +import io.trino.filesystem.TrinoFileSystem; +import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.plugin.hive.LocationHandle.WriteMode; import io.trino.plugin.hive.metastore.Partition; import io.trino.plugin.hive.metastore.SemiTransactionalHiveMetastore; import io.trino.plugin.hive.metastore.Table; import io.trino.spi.TrinoException; import io.trino.spi.connector.ConnectorSession; -import org.apache.hadoop.fs.Path; import java.util.Optional; @@ -33,10 +32,8 @@ import static io.trino.plugin.hive.LocationHandle.WriteMode.STAGE_AND_MOVE_TO_TARGET_DIRECTORY; import static io.trino.plugin.hive.util.AcidTables.isTransactionalTable; import static io.trino.plugin.hive.util.HiveWriteUtils.createTemporaryPath; +import static io.trino.plugin.hive.util.HiveWriteUtils.directoryExists; import static io.trino.plugin.hive.util.HiveWriteUtils.getTableDefaultLocation; -import static io.trino.plugin.hive.util.HiveWriteUtils.isHdfsEncrypted; -import static io.trino.plugin.hive.util.HiveWriteUtils.isS3FileSystem; -import static io.trino.plugin.hive.util.HiveWriteUtils.pathExists; import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; import static java.lang.String.format; import static java.util.Objects.requireNonNull; @@ -44,14 +41,14 @@ public class HiveLocationService implements LocationService { - private final HdfsEnvironment hdfsEnvironment; + private final TrinoFileSystemFactory fileSystemFactory; private final boolean temporaryStagingDirectoryEnabled; private final String temporaryStagingDirectoryPath; @Inject - public HiveLocationService(HdfsEnvironment hdfsEnvironment, HiveConfig hiveConfig) + public HiveLocationService(TrinoFileSystemFactory fileSystemFactory, HiveConfig hiveConfig) { - this.hdfsEnvironment = requireNonNull(hdfsEnvironment, "hdfsEnvironment is null"); + this.fileSystemFactory = requireNonNull(fileSystemFactory, "fileSystemFactory is null"); this.temporaryStagingDirectoryEnabled = hiveConfig.isTemporaryStagingDirectoryEnabled(); this.temporaryStagingDirectoryPath = hiveConfig.getTemporaryStagingDirectoryPath(); } @@ -59,11 +56,11 @@ public HiveLocationService(HdfsEnvironment hdfsEnvironment, HiveConfig hiveConfi @Override public Location forNewTable(SemiTransactionalHiveMetastore metastore, ConnectorSession session, String schemaName, String tableName) { - HdfsContext context = new HdfsContext(session); - Location targetPath = getTableDefaultLocation(context, metastore, hdfsEnvironment, schemaName, tableName); + TrinoFileSystem fileSystem = fileSystemFactory.create(session); + Location targetPath = getTableDefaultLocation(metastore, fileSystem, schemaName, tableName); // verify the target directory for table - if (pathExists(context, hdfsEnvironment, new Path(targetPath.toString()))) { + if (directoryExists(fileSystem, targetPath).orElse(false)) { throw new TrinoException(HIVE_PATH_ALREADY_EXISTS, format("Target directory for table '%s.%s' already exists: %s", schemaName, tableName, targetPath)); } return targetPath; @@ -72,18 +69,20 @@ public Location forNewTable(SemiTransactionalHiveMetastore metastore, ConnectorS @Override public LocationHandle forNewTableAsSelect(SemiTransactionalHiveMetastore metastore, ConnectorSession session, String schemaName, String tableName, Optional externalLocation) { - HdfsContext context = new HdfsContext(session); - Location targetPath = externalLocation.orElseGet(() -> getTableDefaultLocation(context, metastore, hdfsEnvironment, schemaName, tableName)); + TrinoFileSystem fileSystem = fileSystemFactory.create(session); + Location targetPath = externalLocation.orElseGet(() -> getTableDefaultLocation(metastore, fileSystem, schemaName, tableName)); // verify the target directory for the table - if (pathExists(context, hdfsEnvironment, new Path(targetPath.toString()))) { + if (directoryExists(fileSystem, targetPath).orElse(false)) { throw new TrinoException(HIVE_PATH_ALREADY_EXISTS, format("Target directory for table '%s.%s' already exists: %s", schemaName, tableName, targetPath)); } - // TODO detect when existing table's location is a on a different file system than the temporary directory - if (shouldUseTemporaryDirectory(context, new Path(targetPath.toString()), externalLocation.isPresent())) { - Location writePath = createTemporaryPath(context, hdfsEnvironment, new Path(targetPath.toString()), temporaryStagingDirectoryPath); - return new LocationHandle(targetPath, writePath, STAGE_AND_MOVE_TO_TARGET_DIRECTORY); + // Skip using temporary directory if the destination is external. Target may be on a different file system. + if (temporaryStagingDirectoryEnabled && externalLocation.isEmpty()) { + Optional writePath = createTemporaryPath(fileSystem, session.getIdentity(), targetPath, temporaryStagingDirectoryPath); + if (writePath.isPresent()) { + return new LocationHandle(targetPath, writePath.get(), STAGE_AND_MOVE_TO_TARGET_DIRECTORY); + } } return new LocationHandle(targetPath, targetPath, DIRECT_TO_TARGET_NEW_DIRECTORY); } @@ -91,12 +90,14 @@ public LocationHandle forNewTableAsSelect(SemiTransactionalHiveMetastore metasto @Override public LocationHandle forExistingTable(SemiTransactionalHiveMetastore metastore, ConnectorSession session, Table table) { - HdfsContext context = new HdfsContext(session); + TrinoFileSystem fileSystem = fileSystemFactory.create(session); Location targetPath = Location.of(table.getStorage().getLocation()); - if (shouldUseTemporaryDirectory(context, new Path(targetPath.toString()), false) && !isTransactionalTable(table.getParameters())) { - Location writePath = createTemporaryPath(context, hdfsEnvironment, new Path(targetPath.toString()), temporaryStagingDirectoryPath); - return new LocationHandle(targetPath, writePath, STAGE_AND_MOVE_TO_TARGET_DIRECTORY); + if (temporaryStagingDirectoryEnabled && !isTransactionalTable(table.getParameters())) { + Optional writePath = createTemporaryPath(fileSystem, session.getIdentity(), targetPath, temporaryStagingDirectoryPath); + if (writePath.isPresent()) { + return new LocationHandle(targetPath, writePath.get(), STAGE_AND_MOVE_TO_TARGET_DIRECTORY); + } } return new LocationHandle(targetPath, targetPath, DIRECT_TO_TARGET_EXISTING_DIRECTORY); } @@ -109,17 +110,6 @@ public LocationHandle forOptimize(SemiTransactionalHiveMetastore metastore, Conn return new LocationHandle(targetPath, targetPath, DIRECT_TO_TARGET_EXISTING_DIRECTORY); } - private boolean shouldUseTemporaryDirectory(HdfsContext context, Path path, boolean hasExternalLocation) - { - return temporaryStagingDirectoryEnabled - // skip using temporary directory for S3 - && !isS3FileSystem(context, hdfsEnvironment, path) - // skip using temporary directory if destination is encrypted; it's not possible to move a file between encryption zones - && !isHdfsEncrypted(context, hdfsEnvironment, path) - // Skip using temporary directory if destination is external. Target may be on a different file system. - && !hasExternalLocation; - } - @Override public WriteInfo getQueryWriteInfo(LocationHandle locationHandle) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveWriteUtils.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveWriteUtils.java index 902a8cd0d80b..45807659ea89 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveWriteUtils.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveWriteUtils.java @@ -16,10 +16,7 @@ import com.google.common.base.CharMatcher; import com.google.common.collect.ImmutableList; import io.trino.filesystem.Location; -import io.trino.hdfs.HdfsContext; -import io.trino.hdfs.HdfsEnvironment; -import io.trino.hdfs.rubix.CachingTrinoS3FileSystem; -import io.trino.hdfs.s3.TrinoS3FileSystem; +import io.trino.filesystem.TrinoFileSystem; import io.trino.plugin.hive.HiveReadOnlyException; import io.trino.plugin.hive.HiveType; import io.trino.plugin.hive.metastore.Database; @@ -39,15 +36,11 @@ import io.trino.spi.block.Block; import io.trino.spi.connector.SchemaNotFoundException; import io.trino.spi.connector.SchemaTableName; +import io.trino.spi.security.ConnectorIdentity; import io.trino.spi.type.CharType; import io.trino.spi.type.DecimalType; import io.trino.spi.type.Type; import io.trino.spi.type.VarcharType; -import org.apache.hadoop.fs.FileStatus; -import org.apache.hadoop.fs.FileSystem; -import org.apache.hadoop.fs.Path; -import org.apache.hadoop.fs.viewfs.ViewFileSystem; -import org.apache.hadoop.hdfs.DistributedFileSystem; import java.io.IOException; import java.time.LocalDate; @@ -61,8 +54,6 @@ import java.util.Optional; import static com.google.common.io.BaseEncoding.base16; -import static io.trino.hdfs.FileSystemUtils.getRawFileSystem; -import static io.trino.hdfs.s3.HiveS3Module.EMR_FS_CLASS_NAME; import static io.trino.plugin.hive.HiveErrorCode.HIVE_DATABASE_LOCATION_ERROR; import static io.trino.plugin.hive.HiveErrorCode.HIVE_FILESYSTEM_ERROR; import static io.trino.plugin.hive.HiveErrorCode.HIVE_INVALID_PARTITION_VALUE; @@ -90,7 +81,6 @@ import static java.lang.Math.floorMod; import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.UUID.randomUUID; public final class HiveWriteUtils { @@ -220,163 +210,51 @@ private static void checkWritable( } } - public static Location getTableDefaultLocation(HdfsContext context, SemiTransactionalHiveMetastore metastore, HdfsEnvironment hdfsEnvironment, String schemaName, String tableName) + public static Location getTableDefaultLocation(SemiTransactionalHiveMetastore metastore, TrinoFileSystem fileSystem, String schemaName, String tableName) { Database database = metastore.getDatabase(schemaName) .orElseThrow(() -> new SchemaNotFoundException(schemaName)); - return getTableDefaultLocation(database, context, hdfsEnvironment, schemaName, tableName); + return getTableDefaultLocation(database, fileSystem, schemaName, tableName); } - public static Location getTableDefaultLocation(Database database, HdfsContext context, HdfsEnvironment hdfsEnvironment, String schemaName, String tableName) + public static Location getTableDefaultLocation(Database database, TrinoFileSystem fileSystem, String schemaName, String tableName) { - String location = database.getLocation() + Location location = database.getLocation().map(Location::of) .orElseThrow(() -> new TrinoException(HIVE_DATABASE_LOCATION_ERROR, format("Database '%s' location is not set", schemaName))); - Path databasePath = new Path(location); - if (!isS3FileSystem(context, hdfsEnvironment, databasePath)) { - if (!pathExists(context, hdfsEnvironment, databasePath)) { - throw new TrinoException(HIVE_DATABASE_LOCATION_ERROR, format("Database '%s' location does not exist: %s", schemaName, databasePath)); - } - if (!isDirectory(context, hdfsEnvironment, databasePath)) { - throw new TrinoException(HIVE_DATABASE_LOCATION_ERROR, format("Database '%s' location is not a directory: %s", schemaName, databasePath)); - } + if (!directoryExists(fileSystem, location).orElse(true)) { + throw new TrinoException(HIVE_DATABASE_LOCATION_ERROR, format("Database '%s' location does not exist: %s", schemaName, location)); } - // Note: this results in `databaseLocation` being a "normalized location", e.g. not containing double slashes. - // TODO (https://github.com/trinodb/trino/issues/17803): We need to use normalized location until all relevant Hive connector components are migrated off Hadoop Path. - Location databaseLocation = Location.of(databasePath.toString()); - return databaseLocation.appendPath(escapeTableName(tableName)); + return location.appendPath(escapeTableName(tableName)); } - public static boolean pathExists(HdfsContext context, HdfsEnvironment hdfsEnvironment, Path path) + public static Optional directoryExists(TrinoFileSystem fileSystem, Location path) { try { - return hdfsEnvironment.getFileSystem(context, path).exists(path); + return fileSystem.directoryExists(path); } catch (IOException e) { throw new TrinoException(HIVE_FILESYSTEM_ERROR, "Failed checking path: " + path, e); } } - public static boolean isS3FileSystem(HdfsContext context, HdfsEnvironment hdfsEnvironment, Path path) - { - try { - FileSystem fileSystem = getRawFileSystem(hdfsEnvironment.getFileSystem(context, path)); - return fileSystem instanceof TrinoS3FileSystem || fileSystem.getClass().getName().equals(EMR_FS_CLASS_NAME) || fileSystem instanceof CachingTrinoS3FileSystem; - } - catch (IOException e) { - throw new TrinoException(HIVE_FILESYSTEM_ERROR, "Failed checking path: " + path, e); - } - } - - public static boolean isViewFileSystem(HdfsContext context, HdfsEnvironment hdfsEnvironment, Path path) - { - try { - return getRawFileSystem(hdfsEnvironment.getFileSystem(context, path)) instanceof ViewFileSystem; - } - catch (IOException e) { - throw new TrinoException(HIVE_FILESYSTEM_ERROR, "Failed checking path: " + path, e); - } - } - - private static boolean isDirectory(HdfsContext context, HdfsEnvironment hdfsEnvironment, Path path) - { - try { - return hdfsEnvironment.getFileSystem(context, path).isDirectory(path); - } - catch (IOException e) { - throw new TrinoException(HIVE_FILESYSTEM_ERROR, "Failed checking path: " + path, e); - } - } - - public static boolean isHdfsEncrypted(HdfsContext context, HdfsEnvironment hdfsEnvironment, Path path) - { - try { - FileSystem fileSystem = getRawFileSystem(hdfsEnvironment.getFileSystem(context, path)); - if (fileSystem instanceof DistributedFileSystem) { - return ((DistributedFileSystem) fileSystem).getEZForPath(path) != null; - } - return false; - } - catch (IOException e) { - throw new TrinoException(HIVE_FILESYSTEM_ERROR, "Failed checking encryption status for path: " + path, e); - } - } - public static boolean isFileCreatedByQuery(String fileName, String queryId) { return fileName.startsWith(queryId) || fileName.endsWith(queryId); } - public static Location createTemporaryPath(HdfsContext context, HdfsEnvironment hdfsEnvironment, Path targetPath, String temporaryStagingDirectoryPath) - { - // use a per-user temporary directory to avoid permission problems - String temporaryPrefix = temporaryStagingDirectoryPath.replace("${USER}", context.getIdentity().getUser()); - - // use relative temporary directory on ViewFS - if (isViewFileSystem(context, hdfsEnvironment, targetPath)) { - temporaryPrefix = ".hive-staging"; - } - - // create a temporary directory on the same filesystem - Path temporaryRoot = new Path(targetPath, temporaryPrefix); - Path temporaryPath = new Path(temporaryRoot, randomUUID().toString()); - - createDirectory(context, hdfsEnvironment, temporaryPath); - - if (hdfsEnvironment.isNewFileInheritOwnership()) { - setDirectoryOwner(context, hdfsEnvironment, temporaryPath, targetPath); - } - - return Location.of(temporaryPath.toString()); - } - - private static void setDirectoryOwner(HdfsContext context, HdfsEnvironment hdfsEnvironment, Path path, Path targetPath) + public static Optional createTemporaryPath(TrinoFileSystem fileSystem, ConnectorIdentity identity, Location targetPath, String temporaryStagingDirectoryPath) { - try { - FileSystem fileSystem = hdfsEnvironment.getFileSystem(context, path); - FileStatus fileStatus; - if (!fileSystem.exists(targetPath)) { - // For new table - Path parent = targetPath.getParent(); - if (!fileSystem.exists(parent)) { - return; - } - fileStatus = fileSystem.getFileStatus(parent); - } - else { - // For existing table - fileStatus = fileSystem.getFileStatus(targetPath); - } - String owner = fileStatus.getOwner(); - String group = fileStatus.getGroup(); - fileSystem.setOwner(path, owner, group); - } - catch (IOException e) { - throw new TrinoException(HIVE_FILESYSTEM_ERROR, format("Failed to set owner on %s based on %s", path, targetPath), e); - } - } + // interpolate the username into the temporary directory path to avoid permission problems + String temporaryPrefix = temporaryStagingDirectoryPath.replace("${USER}", identity.getUser()); - public static void createDirectory(HdfsContext context, HdfsEnvironment hdfsEnvironment, Path path) - { try { - if (!hdfsEnvironment.getFileSystem(context, path).mkdirs(path, hdfsEnvironment.getNewDirectoryPermissions().orElse(null))) { - throw new IOException("mkdirs returned false"); - } + return fileSystem.createTemporaryDirectory(targetPath, temporaryPrefix, ".hive-staging"); } catch (IOException e) { - throw new TrinoException(HIVE_FILESYSTEM_ERROR, "Failed to create directory: " + path, e); - } - - if (hdfsEnvironment.getNewDirectoryPermissions().isPresent()) { - // explicitly set permission since the default umask overrides it on creation - try { - hdfsEnvironment.getFileSystem(context, path).setPermission(path, hdfsEnvironment.getNewDirectoryPermissions().get()); - } - catch (IOException e) { - throw new TrinoException(HIVE_FILESYSTEM_ERROR, "Failed to set permission on directory: " + path, e); - } + throw new TrinoException(HIVE_FILESYSTEM_ERROR, e); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java index a3e382ea2601..15028d4afb57 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java @@ -27,12 +27,11 @@ import io.airlift.units.Duration; import io.trino.filesystem.Location; import io.trino.filesystem.TrinoFileSystem; +import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.filesystem.hdfs.HdfsFileSystemFactory; -import io.trino.hdfs.HdfsConfig; import io.trino.hdfs.HdfsContext; import io.trino.hdfs.HdfsEnvironment; import io.trino.hdfs.HdfsNamenodeStats; -import io.trino.hdfs.authentication.NoHdfsAuthentication; import io.trino.operator.GroupByHashPageIndexerFactory; import io.trino.plugin.base.CatalogName; import io.trino.plugin.base.metrics.LongCount; @@ -240,8 +239,8 @@ import static io.trino.plugin.hive.HiveTableProperties.SORTED_BY_PROPERTY; import static io.trino.plugin.hive.HiveTableProperties.STORAGE_FORMAT_PROPERTY; import static io.trino.plugin.hive.HiveTableProperties.TRANSACTIONAL; -import static io.trino.plugin.hive.HiveTestUtils.HDFS_CONFIGURATION; import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; +import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_STATS; import static io.trino.plugin.hive.HiveTestUtils.PAGE_SORTER; import static io.trino.plugin.hive.HiveTestUtils.SESSION; @@ -279,7 +278,6 @@ import static io.trino.plugin.hive.util.HiveUtil.SPARK_TABLE_PROVIDER_KEY; import static io.trino.plugin.hive.util.HiveUtil.columnExtraInfo; import static io.trino.plugin.hive.util.HiveUtil.toPartitionValues; -import static io.trino.plugin.hive.util.HiveWriteUtils.createDirectory; import static io.trino.plugin.hive.util.HiveWriteUtils.getTableDefaultLocation; import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; import static io.trino.spi.StandardErrorCode.TRANSACTION_CONFLICT; @@ -826,14 +824,15 @@ protected final void setup(String databaseName, HiveConfig hiveConfig, HiveMetas metastoreClient = hiveMetastore; hdfsEnvironment = hdfsConfiguration; HivePartitionManager partitionManager = new HivePartitionManager(hiveConfig); - locationService = new HiveLocationService(hdfsEnvironment, hiveConfig); + HdfsFileSystemFactory fileSystemFactory = new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS); + locationService = new HiveLocationService(fileSystemFactory, hiveConfig); JsonCodec partitionUpdateCodec = JsonCodec.jsonCodec(PartitionUpdate.class); countingDirectoryLister = new CountingDirectoryLister(); metadataFactory = new HiveMetadataFactory( new CatalogName("hive"), HiveMetastoreFactory.ofInstance(metastoreClient), getDefaultHiveFileWriterFactories(hiveConfig, hdfsEnvironment), - new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS), + fileSystemFactory, hdfsEnvironment, partitionManager, 10, @@ -910,7 +909,7 @@ public Optional getMaterializedView(Connect splitManager = new HiveSplitManager( transactionManager, partitionManager, - new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS), + fileSystemFactory, new HdfsNamenodeStats(), executor, new CounterStat(), @@ -926,7 +925,7 @@ public Optional getMaterializedView(Connect hiveConfig.getMaxPartitionsPerScan()); pageSinkProvider = new HivePageSinkProvider( getDefaultHiveFileWriterFactories(hiveConfig, hdfsEnvironment), - new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS), + fileSystemFactory, PAGE_SORTER, HiveMetastoreFactory.ofInstance(metastoreClient), new GroupByHashPageIndexerFactory(JOIN_COMPILER), @@ -2617,9 +2616,10 @@ public void testTableCreationWithTrailingSpaceInLocation() try (Transaction transaction = newTransaction()) { ConnectorSession session = newSession(); SemiTransactionalHiveMetastore metastore = transaction.getMetastore(); + TrinoFileSystem fileSystem = HDFS_FILE_SYSTEM_FACTORY.create(session); // Write data - tableDefaultLocationWithTrailingSpace = getTableDefaultLocation(new HdfsContext(session), metastore, HDFS_ENVIRONMENT, tableName.getSchemaName(), tableName.getTableName()) + " "; + tableDefaultLocationWithTrailingSpace = getTableDefaultLocation(metastore, fileSystem, tableName.getSchemaName(), tableName.getTableName()) + " "; Path dataFilePath = new Path(tableDefaultLocationWithTrailingSpace, "foo.txt"); FileSystem fs = hdfsEnvironment.getFileSystem(new HdfsContext(session), new Path(tableDefaultLocationWithTrailingSpace)); try (OutputStream outputStream = fs.create(dataFilePath)) { @@ -3089,7 +3089,8 @@ public void testCreateEmptyTableShouldNotCreateStagingDirectory() HiveConfig hiveConfig = getHiveConfig() .setTemporaryStagingDirectoryPath(temporaryStagingPrefix) .setTemporaryStagingDirectoryEnabled(true); - LocationService locationService = new HiveLocationService(hdfsEnvironment, hiveConfig); + TrinoFileSystemFactory fileSystemFactory = new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS); + LocationService locationService = new HiveLocationService(fileSystemFactory, hiveConfig); Location targetPath = locationService.forNewTable(transaction.getMetastore(), session, schemaName, tableName); Table.Builder tableBuilder = Table.builder() .setDatabaseName(schemaName) @@ -3181,8 +3182,7 @@ public void testHideDeltaLakeTables() .setStorageFormat(fromHiveStorageFormat(PARQUET)) .setLocation(getTableDefaultLocation( metastoreClient.getDatabase(tableName.getSchemaName()).orElseThrow(), - new HdfsContext(session.getIdentity()), - hdfsEnvironment, + new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS).create(session), tableName.getSchemaName(), tableName.getTableName()).toString()); metastoreClient.createTable(table.build(), NO_PRIVILEGES); @@ -3254,8 +3254,7 @@ public void testDisallowQueryingOfIcebergTables() .setStorageFormat(fromHiveStorageFormat(PARQUET)) .setLocation(getTableDefaultLocation( metastoreClient.getDatabase(tableName.getSchemaName()).orElseThrow(), - new HdfsContext(session.getIdentity()), - hdfsEnvironment, + new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS).create(session), tableName.getSchemaName(), tableName.getTableName()).toString()); metastoreClient.createTable(table.build(), NO_PRIVILEGES); @@ -5958,49 +5957,6 @@ public void testCreatePartitionedBucketedTableLayout() } } - @Test - public void testNewDirectoryPermissions() - throws Exception - { - SchemaTableName tableName = temporaryTable("empty_file"); - List columns = ImmutableList.of(new Column("test", HIVE_STRING, Optional.empty())); - createEmptyTable(tableName, ORC, columns, ImmutableList.of(), Optional.empty()); - try { - Transaction transaction = newTransaction(); - ConnectorSession session = newSession(); - ConnectorMetadata metadata = transaction.getMetadata(); - metadata.beginQuery(session); - - Table table = transaction.getMetastore() - .getTable(tableName.getSchemaName(), tableName.getTableName()) - .orElseThrow(); - - // create new directory and set directory permission after creation - HdfsContext context = new HdfsContext(session); - Path location = new Path(table.getStorage().getLocation()); - Path defaultPath = new Path(location + "/defaultperms"); - createDirectory(context, hdfsEnvironment, defaultPath); - FileStatus defaultFsStatus = hdfsEnvironment.getFileSystem(context, defaultPath).getFileStatus(defaultPath); - assertEquals(defaultFsStatus.getPermission().toOctal(), 777); - - // use hdfs config that skips setting directory permissions after creation - HdfsConfig configWithSkip = new HdfsConfig(); - configWithSkip.setNewDirectoryPermissions(HdfsConfig.SKIP_DIR_PERMISSIONS); - HdfsEnvironment hdfsEnvironmentWithSkip = new HdfsEnvironment( - HDFS_CONFIGURATION, - configWithSkip, - new NoHdfsAuthentication()); - - Path skipPath = new Path(location + "/skipperms"); - createDirectory(context, hdfsEnvironmentWithSkip, skipPath); - FileStatus skipFsStatus = hdfsEnvironmentWithSkip.getFileSystem(context, skipPath).getFileStatus(skipPath); - assertEquals(skipFsStatus.getPermission().toOctal(), 755); - } - finally { - dropTable(tableName); - } - } - protected void doTestTransactionDeleteInsert(HiveStorageFormat storageFormat, boolean allowInsertExisting, List testCases) throws Exception { @@ -6334,6 +6290,7 @@ protected class DirectoryRenameFailure @Override public void triggerConflict(ConnectorSession session, SchemaTableName tableName, ConnectorInsertTableHandle insertTableHandle, List partitionUpdates) + throws IOException { Location writePath = getStagingPathRoot(insertTableHandle); Location targetPath = getTargetPathRoot(insertTableHandle); @@ -6343,7 +6300,9 @@ public void triggerConflict(ConnectorSession session, SchemaTableName tableName, } path = new Path(targetPath.appendPath("pk1=b").appendPath("pk2=add2").toString()); context = new HdfsContext(session); - createDirectory(context, hdfsEnvironment, new Path(path.toString())); + if (!hdfsEnvironment.getFileSystem(context, path).mkdirs(path, hdfsEnvironment.getNewDirectoryPermissions().orElse(null))) { + throw new IOException("mkdirs returned false"); + } } @Override diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java index 64893b857cfb..fdb794fa06eb 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java @@ -214,7 +214,8 @@ protected void setup(String host, int port, String databaseName, HdfsConfigurati .build()), getBasePath(), hdfsEnvironment); - locationService = new HiveLocationService(hdfsEnvironment, config); + HdfsFileSystemFactory fileSystemFactory = new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS); + locationService = new HiveLocationService(fileSystemFactory, config); JsonCodec partitionUpdateCodec = JsonCodec.jsonCodec(PartitionUpdate.class); metadataFactory = new HiveMetadataFactory( new CatalogName("hive"), @@ -222,7 +223,7 @@ protected void setup(String host, int port, String databaseName, HdfsConfigurati new HiveMetastoreConfig(), HiveMetastoreFactory.ofInstance(metastoreClient), getDefaultHiveFileWriterFactories(config, hdfsEnvironment), - new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS), + fileSystemFactory, hdfsEnvironment, hivePartitionManager, newDirectExecutorService(), @@ -246,7 +247,7 @@ protected void setup(String host, int port, String databaseName, HdfsConfigurati splitManager = new HiveSplitManager( transactionManager, hivePartitionManager, - new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS), + fileSystemFactory, new HdfsNamenodeStats(), new BoundedExecutor(executor, config.getMaxSplitIteratorThreads()), new CounterStat(), @@ -262,7 +263,7 @@ protected void setup(String host, int port, String databaseName, HdfsConfigurati config.getMaxPartitionsPerScan()); pageSinkProvider = new HivePageSinkProvider( getDefaultHiveFileWriterFactories(config, hdfsEnvironment), - new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS), + fileSystemFactory, PAGE_SORTER, HiveMetastoreFactory.ofInstance(metastoreClient), new GroupByHashPageIndexerFactory(new JoinCompiler(new TypeOperators())), diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveLocationService.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveLocationService.java index b7b16e199821..d87d1275a43f 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveLocationService.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveLocationService.java @@ -13,13 +13,11 @@ */ package io.trino.plugin.hive; -import com.google.common.collect.ImmutableList; import io.trino.filesystem.Location; -import io.trino.hdfs.HdfsEnvironment; import io.trino.plugin.hive.LocationService.WriteInfo; -import io.trino.plugin.hive.TestBackgroundHiveSplitLoader.TestingHdfsEnvironment; import org.junit.jupiter.api.Test; +import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; import static io.trino.plugin.hive.LocationHandle.WriteMode.DIRECT_TO_TARGET_EXISTING_DIRECTORY; import static io.trino.plugin.hive.LocationHandle.WriteMode.DIRECT_TO_TARGET_NEW_DIRECTORY; import static io.trino.plugin.hive.LocationHandle.WriteMode.STAGE_AND_MOVE_TO_TARGET_DIRECTORY; @@ -82,8 +80,7 @@ public static class Assertion public Assertion(LocationHandle locationHandle, boolean overwrite) { - HdfsEnvironment hdfsEnvironment = new TestingHdfsEnvironment(ImmutableList.of()); - LocationService service = new HiveLocationService(hdfsEnvironment, new HiveConfig()); + LocationService service = new HiveLocationService(HDFS_FILE_SYSTEM_FACTORY, new HiveConfig()); this.actual = service.getTableWriteInfo(locationHandle, overwrite); } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java index 7374d7a93439..26eccb6e16e7 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java @@ -304,7 +304,7 @@ private static ConnectorPageSink createPageSink(HiveTransactionHandle transactio TESTING_TYPE_MANAGER, config, sortingFileWriterConfig, - new HiveLocationService(HDFS_ENVIRONMENT, config), + new HiveLocationService(HDFS_FILE_SYSTEM_FACTORY, config), partitionUpdateCodec, new TestingNodeManager("fake-environment"), new HiveEventClient(), diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveS3AndGlueMetastoreTest.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveS3AndGlueMetastoreTest.java index 9180b709ecaf..e5daa7c71a64 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveS3AndGlueMetastoreTest.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveS3AndGlueMetastoreTest.java @@ -192,12 +192,10 @@ public void testBasicOperationsWithProvidedSchemaLocation(boolean partitioned, L assertUpdate("CREATE TABLE " + qualifiedTableName + "(col_str varchar, col_int int)" + partitionQueryPart); try (UncheckedCloseable ignoredDropTable = onClose("DROP TABLE " + qualifiedTableName)) { - String expectedTableLocation = Pattern.quote((schemaLocation.endsWith("/") ? schemaLocation : schemaLocation + "/") + tableName) - // Hive normalizes repeated slashes - .replaceAll("(? computeActual("CREATE TABLE \"" + schemaName + "\"." + tableName + " (col) AS VALUES 1")) + .hasMessage("Error committing write to Hive") + .hasStackTraceContaining("Invalid URI (Service: Amazon S3; Status Code: 400"); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveWriteUtils.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveWriteUtils.java index a0f05cfa6718..57c05f772c0d 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveWriteUtils.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveWriteUtils.java @@ -13,58 +13,26 @@ */ package io.trino.plugin.hive.util; -import io.trino.hdfs.HdfsContext; import io.trino.spi.Page; import io.trino.spi.PageBuilder; import io.trino.spi.block.BlockBuilder; import io.trino.spi.type.DecimalType; import io.trino.spi.type.SqlDecimal; import io.trino.spi.type.Type; -import org.apache.hadoop.fs.Path; import org.apache.hadoop.hive.common.type.HiveDecimal; import org.junit.jupiter.api.Test; import java.util.List; -import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; import static io.trino.plugin.hive.util.HiveWriteUtils.createPartitionValues; -import static io.trino.plugin.hive.util.HiveWriteUtils.isS3FileSystem; -import static io.trino.plugin.hive.util.HiveWriteUtils.isViewFileSystem; import static io.trino.spi.type.DecimalType.createDecimalType; import static io.trino.spi.type.Decimals.writeBigDecimal; import static io.trino.spi.type.Decimals.writeShortDecimal; import static io.trino.spi.type.SqlDecimal.decimal; -import static io.trino.testing.TestingConnectorSession.SESSION; -import static io.trino.testing.TestingNames.randomNameSuffix; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestHiveWriteUtils { - private static final HdfsContext CONTEXT = new HdfsContext(SESSION); - private static final String RANDOM_SUFFIX = randomNameSuffix(); - - @Test - public void testIsS3FileSystem() - { - assertTrue(isS3FileSystem(CONTEXT, HDFS_ENVIRONMENT, new Path("s3://test-bucket-%s/test-folder".formatted(RANDOM_SUFFIX)))); - assertFalse(isS3FileSystem(CONTEXT, HDFS_ENVIRONMENT, new Path("/test-dir-%s/test-folder".formatted(RANDOM_SUFFIX)))); - } - - @Test - public void testIsViewFileSystem() - { - Path viewfsPath = new Path("viewfs://ns-default-%s/test-folder".formatted(RANDOM_SUFFIX)); - Path nonViewfsPath = new Path("hdfs://localhost/test-dir/test-folder"); - - // ViewFS check requires the mount point config - HDFS_ENVIRONMENT.getConfiguration(CONTEXT, viewfsPath).set("fs.viewfs.mounttable.ns-default-%s.link./test-folder".formatted(RANDOM_SUFFIX), "hdfs://localhost/app"); - - assertTrue(isViewFileSystem(CONTEXT, HDFS_ENVIRONMENT, viewfsPath)); - assertFalse(isViewFileSystem(CONTEXT, HDFS_ENVIRONMENT, nonViewfsPath)); - } - @Test public void testCreatePartitionValuesDecimal() { From 44de544eee2a267f8b3e3f0547e7b5daeec89071 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sun, 29 Oct 2023 23:07:10 -0700 Subject: [PATCH 015/587] Remove unused method from TestCachingOrcDataSource --- .../trino/orc/TestCachingOrcDataSource.java | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestCachingOrcDataSource.java b/lib/trino-orc/src/test/java/io/trino/orc/TestCachingOrcDataSource.java index beaf495ae348..601fb51caf6e 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestCachingOrcDataSource.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestCachingOrcDataSource.java @@ -18,36 +18,23 @@ import io.airlift.slice.Slices; import io.airlift.units.DataSize; import io.airlift.units.DataSize.Unit; -import io.trino.hive.orc.OrcConf; -import io.trino.orc.OrcTester.Format; -import io.trino.orc.metadata.CompressionKind; import io.trino.orc.metadata.StripeInformation; import io.trino.orc.stream.OrcDataReader; import io.trino.spi.Page; import io.trino.spi.block.Block; -import org.apache.hadoop.fs.Path; -import org.apache.hadoop.hive.ql.exec.FileSinkOperator; -import org.apache.hadoop.hive.ql.io.IOConstants; -import org.apache.hadoop.hive.ql.io.orc.OrcOutputFormat; -import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector; -import org.apache.hadoop.io.Text; -import org.apache.hadoop.mapred.JobConf; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; -import java.io.File; import java.io.IOException; import java.util.List; import java.util.Map; -import java.util.Properties; import java.util.Random; import java.util.function.Function; import java.util.stream.Stream; import static io.airlift.testing.Assertions.assertGreaterThanOrEqual; import static io.airlift.testing.Assertions.assertInstanceOf; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static io.trino.memory.context.AggregatedMemoryContext.newSimpleAggregatedMemoryContext; import static io.trino.orc.OrcReader.INITIAL_BATCH_SIZE; import static io.trino.orc.OrcRecordReader.LinearProbeRangeFinder.createTinyStripesRangeFinder; @@ -56,7 +43,6 @@ import static io.trino.orc.OrcTester.HIVE_STORAGE_TIME_ZONE; import static io.trino.orc.OrcTester.READER_OPTIONS; import static io.trino.orc.OrcTester.writeOrcColumnsHiveFile; -import static io.trino.orc.metadata.CompressionKind.NONE; import static io.trino.orc.metadata.CompressionKind.ZLIB; import static io.trino.spi.type.VarcharType.VARCHAR; import static java.lang.String.format; @@ -242,27 +228,6 @@ private static void assertNotInstanceOf(T actual, Class expe } } - private static FileSinkOperator.RecordWriter createOrcRecordWriter(File outputFile, Format format, CompressionKind compression, ObjectInspector columnObjectInspector) - throws IOException - { - JobConf jobConf = new JobConf(newEmptyConfiguration()); - OrcConf.WRITE_FORMAT.setString(jobConf, format == ORC_12 ? "0.12" : "0.11"); - OrcConf.COMPRESS.setString(jobConf, compression.name()); - - Properties tableProperties = new Properties(); - tableProperties.setProperty(IOConstants.COLUMNS, "test"); - tableProperties.setProperty(IOConstants.COLUMNS_TYPES, columnObjectInspector.getTypeName()); - tableProperties.setProperty(OrcConf.STRIPE_SIZE.getAttribute(), "120000"); - - return new OrcOutputFormat().getHiveRecordWriter( - jobConf, - new Path(outputFile.toURI()), - Text.class, - compression != NONE, - tableProperties, - () -> {}); - } - private static class FakeOrcDataSource implements OrcDataSource { From 8cb51ec16a0d743ced08e5f651b505240ef87def Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sun, 29 Oct 2023 23:23:50 -0700 Subject: [PATCH 016/587] Remove unused JobConf in TestHiveFileFormatBenchmark --- .../trino/plugin/hive/benchmark/AbstractFileFormat.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/AbstractFileFormat.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/AbstractFileFormat.java index 77143b82792a..5ad7cd9d5f51 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/AbstractFileFormat.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/AbstractFileFormat.java @@ -37,7 +37,6 @@ import io.trino.spi.predicate.TupleDomain; import io.trino.spi.type.Type; import io.trino.sql.planner.TestingConnectorTransactionHandle; -import org.apache.hadoop.mapred.JobConf; import java.io.File; import java.util.List; @@ -49,7 +48,6 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableList.toImmutableList; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static io.trino.plugin.hive.HiveColumnHandle.ColumnType.REGULAR; import static io.trino.plugin.hive.HiveColumnHandle.createBaseColumn; import static io.trino.plugin.hive.HiveType.toHiveType; @@ -65,13 +63,6 @@ public abstract class AbstractFileFormat implements FileFormat { - static final JobConf conf; - - static { - conf = new JobConf(newEmptyConfiguration()); - conf.set("fs.file.impl", "org.apache.hadoop.fs.RawLocalFileSystem"); - } - @Override public boolean supportsDate() { From 3d7121859c6a0a530813e2168fafb851c199506c Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sun, 29 Oct 2023 23:40:37 -0700 Subject: [PATCH 017/587] Remove Hadoop toolkit --- .mvn/modernizer/violations.xml | 24 ------ lib/trino-hadoop-toolkit/pom.xml | 41 ----------- .../hadoop/ConfigurationInstantiator.java | 54 -------------- .../test/java/io/trino/hadoop/TestDummy.java | 22 ------ lib/trino-hdfs/pom.xml | 5 -- .../io/trino/hdfs/ConfigurationUtils.java | 17 ++++- .../trino/hdfs/DynamicHdfsConfiguration.java | 2 +- .../trino/hdfs/TestFSDataInputStreamTail.java | 4 +- .../io/trino/hdfs/TestFileSystemCache.java | 4 +- .../hdfs/TestTrinoFileSystemCacheStats.java | 5 +- .../trino/hdfs/s3/TestS3SecurityMapping.java | 5 +- .../trino/hdfs/s3/TestTrinoS3FileSystem.java | 73 +++++++++---------- lib/trino-hive-formats/pom.xml | 6 -- .../hive/formats/line/csv/TestCsvFormat.java | 5 +- .../formats/line/json/TestJsonFormat.java | 5 +- .../line/openxjson/TestOpenxJsonFormat.java | 7 +- .../formats/line/regex/TestRegexFormat.java | 5 +- .../formats/line/simple/TestSimpleFormat.java | 7 +- .../hive/formats/rcfile/RcFileTester.java | 3 +- lib/trino-orc/pom.xml | 6 -- .../src/test/java/io/trino/orc/OrcTester.java | 6 +- .../io/trino/orc/TestOrcReaderPositions.java | 3 +- plugin/trino-delta-lake/pom.xml | 6 -- .../TestDeltaLakeGcsConnectorSmokeTest.java | 3 +- plugin/trino-exchange-hdfs/pom.xml | 5 -- .../hdfs/HadoopFileSystemExchangeStorage.java | 9 ++- plugin/trino-hive-hadoop2/pom.xml | 6 -- .../hive/AbstractTestHiveFileFormats.java | 10 +-- .../hive/TestOrcPageSourceMemoryTracking.java | 5 +- .../hive/avro/TestAvroSchemaGeneration.java | 6 +- .../orc/TestOrcDeleteDeltaPageSource.java | 4 +- .../plugin/hive/parquet/ParquetTester.java | 5 +- .../hive/parquet/TestBloomFilterStore.java | 3 +- .../TestHiveParquetWithBloomFilters.java | 3 +- .../parquet/TestParquetDecimalScaling.java | 3 +- .../plugin/hive/parquet/TestTimestamp.java | 3 +- .../hive/s3/TestTrinoS3FileSystemAwsS3.java | 3 +- .../hive/s3/TestTrinoS3FileSystemMinio.java | 3 +- .../plugin/hive/util/TestAcidTables.java | 27 ++++--- .../plugin/hive/util/TestHiveAcidUtils.java | 31 ++++---- plugin/trino-hudi/pom.xml | 6 -- plugin/trino-iceberg/pom.xml | 6 -- .../TestIcebergRegisterTableProcedure.java | 4 +- ...tIcebergJdbcCatalogConnectorSmokeTest.java | 4 +- plugin/trino-phoenix5/pom.xml | 5 -- .../phoenix5/ConfigurationInstantiator.java | 14 +--- pom.xml | 7 -- 47 files changed, 138 insertions(+), 352 deletions(-) delete mode 100644 lib/trino-hadoop-toolkit/pom.xml delete mode 100644 lib/trino-hadoop-toolkit/src/main/java/io/trino/hadoop/ConfigurationInstantiator.java delete mode 100644 lib/trino-hadoop-toolkit/src/test/java/io/trino/hadoop/TestDummy.java diff --git a/.mvn/modernizer/violations.xml b/.mvn/modernizer/violations.xml index bdc4a234fa7a..fff440124d23 100644 --- a/.mvn/modernizer/violations.xml +++ b/.mvn/modernizer/violations.xml @@ -176,36 +176,12 @@ SerDeInfo parameters map is nullable in Glue model, which is too easy to forget about. Prefer GlueToTrinoConverter.getSerDeInfoParameters - - org/apache/hadoop/mapred/JobConf."<init>":()V - 1.1 - This constructor reads default configuration resource files implicitly. Prefer new JobConf(Configuration) - - - - org/apache/hadoop/mapred/JobConf."<init>":(Ljava/lang/Class;)V - 1.1 - This constructor reads default configuration resource files implicitly. Prefer new JobConf(Configuration) - - - - org/apache/hadoop/conf/Configuration."<init>":()V - 1.1 - Prefer ConfigurationInstantiator.newEmptyConfiguration() for two reasons: (1) loading default resources is unlikely desired and (2) ConfigurationInstantiator adds additional safety checks - - org/apache/hadoop/fs/FileSystem.close:()V 1.1 Hadoop FileSystem instances are shared and should not be closed - - org/apache/hadoop/conf/Configuration."<init>":(Z)V - 1.1 - Prefer ConfigurationInstantiator.newEmptyConfiguration() - - java/util/TimeZone.getTimeZone:(Ljava/lang/String;)Ljava/util/TimeZone; 1.8 diff --git a/lib/trino-hadoop-toolkit/pom.xml b/lib/trino-hadoop-toolkit/pom.xml deleted file mode 100644 index abb93c98d8d9..000000000000 --- a/lib/trino-hadoop-toolkit/pom.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - 4.0.0 - - - io.trino - trino-root - 432-SNAPSHOT - ../../pom.xml - - - trino-hadoop-toolkit - - - ${project.parent.basedir} - true - - - - - io.trino - trino-spi - - - - io.trino.hadoop - hadoop-apache - - - - org.gaul - modernizer-maven-annotations - - - - org.junit.jupiter - junit-jupiter-api - test - - - diff --git a/lib/trino-hadoop-toolkit/src/main/java/io/trino/hadoop/ConfigurationInstantiator.java b/lib/trino-hadoop-toolkit/src/main/java/io/trino/hadoop/ConfigurationInstantiator.java deleted file mode 100644 index 9bf1262424ba..000000000000 --- a/lib/trino-hadoop-toolkit/src/main/java/io/trino/hadoop/ConfigurationInstantiator.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 io.trino.hadoop; - -import io.trino.spi.classloader.ThreadContextClassLoader; -import org.apache.hadoop.conf.Configuration; -import org.gaul.modernizer_maven_annotations.SuppressModernizer; - -public final class ConfigurationInstantiator -{ - private ConfigurationInstantiator() {} - - public static Configuration newEmptyConfiguration() - { - return newConfiguration(false); - } - - /** - * @see Configuration#Configuration(boolean) - */ - public static Configuration newConfigurationWithDefaultResources() - { - return newConfiguration(true); - } - - private static Configuration newConfiguration(boolean loadDefaults) - { - // Ensure that the context class loader used while instantiating the `Configuration` object corresponds to the - // class loader of the `ConfigurationInstantiator` - try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(ConfigurationInstantiator.class.getClassLoader())) { - return newConfigurationWithTccl(loadDefaults); - } - } - - // Usage of `new Configuration(boolean)` is not allowed. Only ConfigurationInstantiator - // can instantiate Configuration directly. Suppress the violation so that we can use it here. - @SuppressModernizer - private static Configuration newConfigurationWithTccl(boolean loadDefaults) - { - // Note: the Configuration captures current thread context class loader (TCCL), so it may or may not be generally usable. - return new Configuration(loadDefaults); - } -} diff --git a/lib/trino-hadoop-toolkit/src/test/java/io/trino/hadoop/TestDummy.java b/lib/trino-hadoop-toolkit/src/test/java/io/trino/hadoop/TestDummy.java deleted file mode 100644 index ad45437142b3..000000000000 --- a/lib/trino-hadoop-toolkit/src/test/java/io/trino/hadoop/TestDummy.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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 io.trino.hadoop; - -import org.junit.jupiter.api.Test; - -public class TestDummy -{ - @Test - public void buildRequiresTestToExist() {} -} diff --git a/lib/trino-hdfs/pom.xml b/lib/trino-hdfs/pom.xml index de0536a49c0f..a7b55ce5753c 100644 --- a/lib/trino-hdfs/pom.xml +++ b/lib/trino-hdfs/pom.xml @@ -126,11 +126,6 @@ trino-filesystem - - io.trino - trino-hadoop-toolkit - - io.trino trino-memory-context diff --git a/lib/trino-hdfs/src/main/java/io/trino/hdfs/ConfigurationUtils.java b/lib/trino-hdfs/src/main/java/io/trino/hdfs/ConfigurationUtils.java index 9181537b4e93..fb29ab1f4f51 100644 --- a/lib/trino-hdfs/src/main/java/io/trino/hdfs/ConfigurationUtils.java +++ b/lib/trino-hdfs/src/main/java/io/trino/hdfs/ConfigurationUtils.java @@ -13,6 +13,7 @@ */ package io.trino.hdfs; +import io.trino.spi.classloader.ThreadContextClassLoader; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; @@ -21,8 +22,6 @@ import java.util.Map; import static com.google.common.base.Preconditions.checkArgument; -import static io.trino.hadoop.ConfigurationInstantiator.newConfigurationWithDefaultResources; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; public final class ConfigurationUtils { @@ -35,7 +34,7 @@ public final class ConfigurationUtils // must not be transitively reloaded during the future loading of various Hadoop modules // all the required default resources must be declared above INITIAL_CONFIGURATION = newEmptyConfiguration(); - Configuration defaultConfiguration = newConfigurationWithDefaultResources(); + Configuration defaultConfiguration = newConfiguration(true); copy(defaultConfiguration, INITIAL_CONFIGURATION); } @@ -76,4 +75,16 @@ public static Configuration readConfiguration(List resourcePaths) return result; } + + public static Configuration newEmptyConfiguration() + { + return newConfiguration(false); + } + + private static Configuration newConfiguration(boolean loadDefaults) + { + try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(ConfigurationUtils.class.getClassLoader())) { + return new Configuration(loadDefaults); + } + } } diff --git a/lib/trino-hdfs/src/main/java/io/trino/hdfs/DynamicHdfsConfiguration.java b/lib/trino-hdfs/src/main/java/io/trino/hdfs/DynamicHdfsConfiguration.java index 173b2d1162ff..311c377e51c0 100644 --- a/lib/trino-hdfs/src/main/java/io/trino/hdfs/DynamicHdfsConfiguration.java +++ b/lib/trino-hdfs/src/main/java/io/trino/hdfs/DynamicHdfsConfiguration.java @@ -20,9 +20,9 @@ import java.net.URI; import java.util.Set; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static io.trino.hdfs.ConfigurationUtils.copy; import static io.trino.hdfs.ConfigurationUtils.getInitialConfiguration; +import static io.trino.hdfs.ConfigurationUtils.newEmptyConfiguration; import static java.util.Objects.requireNonNull; public class DynamicHdfsConfiguration diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFSDataInputStreamTail.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFSDataInputStreamTail.java index 02077cea6be5..2a509e778f8d 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFSDataInputStreamTail.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFSDataInputStreamTail.java @@ -16,6 +16,7 @@ import io.airlift.slice.Slice; import io.airlift.slice.Slices; +import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FSDataInputStream; import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.Path; @@ -32,7 +33,6 @@ import java.util.Arrays; import static io.airlift.testing.Closeables.closeAll; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertSame; @@ -50,7 +50,7 @@ public void setUp() throws Exception { fs = new RawLocalFileSystem(); - fs.initialize(fs.getUri(), newEmptyConfiguration()); + fs.initialize(fs.getUri(), new Configuration(false)); tempRoot = Files.createTempDirectory("test_fsdatainputstream_tail").toFile(); tempFile = new Path(Files.createTempFile(tempRoot.toPath(), "tempfile", "txt").toUri()); } diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFileSystemCache.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFileSystemCache.java index 02bf0bea8f71..3abe7b2e72cd 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFileSystemCache.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFileSystemCache.java @@ -18,6 +18,7 @@ import io.trino.hdfs.authentication.ImpersonatingHdfsAuthentication; import io.trino.hdfs.authentication.SimpleHadoopAuthentication; import io.trino.spi.security.ConnectorIdentity; +import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.gaul.modernizer_maven_annotations.SuppressModernizer; @@ -34,7 +35,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static io.trino.plugin.base.security.UserNameProvider.SIMPLE_USER_NAME_PROVIDER; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -124,7 +124,7 @@ public void testFileSystemCacheConcurrency() private static FileSystem getFileSystem(HdfsEnvironment environment, ConnectorIdentity identity) throws IOException { - return environment.getFileSystem(identity, new Path("/"), newEmptyConfiguration()); + return environment.getFileSystem(identity, new Path("/"), new Configuration(false)); } @FunctionalInterface diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestTrinoFileSystemCacheStats.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestTrinoFileSystemCacheStats.java index 97b6a83dde72..95a5b307cf88 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestTrinoFileSystemCacheStats.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestTrinoFileSystemCacheStats.java @@ -19,7 +19,6 @@ import java.net.URI; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.testng.Assert.assertEquals; @@ -34,7 +33,7 @@ public void testCacheSizeIsCorrect() assertEquals(trinoFileSystemCacheStats.getCacheSize(), 0); assertEquals(trinoFileSystemCache.getCacheSize(), 0); - Configuration configuration = newEmptyConfiguration(); + Configuration configuration = new Configuration(false); trinoFileSystemCache.get(new URI("file:///tmp/path/"), configuration); assertEquals(trinoFileSystemCacheStats.getGetCalls().getTotalCount(), 1); assertEquals(trinoFileSystemCacheStats.getCacheSize(), 1); @@ -67,7 +66,7 @@ public void testFailedCallsCountIsCorrect() { TrinoFileSystemCache trinoFileSystemCache = new TrinoFileSystemCache(); TrinoFileSystemCacheStats trinoFileSystemCacheStats = trinoFileSystemCache.getStats(); - Configuration configuration = newEmptyConfiguration(); + Configuration configuration = new Configuration(false); configuration.setInt("fs.cache.max-size", 0); assertThatThrownBy(() -> trinoFileSystemCache.get(new URI("file:///tmp/path/"), configuration)) .hasMessageMatching("FileSystem max cache size has been reached: 0"); diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestS3SecurityMapping.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestS3SecurityMapping.java index bbcbb15c0fb6..e1684fb41166 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestS3SecurityMapping.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestS3SecurityMapping.java @@ -29,7 +29,6 @@ import java.util.Set; import static com.google.common.io.Resources.getResource; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static io.trino.hdfs.s3.TestS3SecurityMapping.MappingResult.clusterDefaultRole; import static io.trino.hdfs.s3.TestS3SecurityMapping.MappingResult.credentials; import static io.trino.hdfs.s3.TestS3SecurityMapping.MappingResult.role; @@ -335,7 +334,7 @@ public void testMappingWithRoleSessionNameWithoutIamRoleShouldFail() private static void assertMapping(DynamicConfigurationProvider provider, MappingSelector selector, MappingResult mappingResult) { - Configuration configuration = newEmptyConfiguration(); + Configuration configuration = new Configuration(false); assertNull(configuration.get(S3_ACCESS_KEY)); assertNull(configuration.get(S3_SECRET_KEY)); @@ -354,7 +353,7 @@ private static void assertMapping(DynamicConfigurationProvider provider, Mapping private static void assertMappingFails(DynamicConfigurationProvider provider, MappingSelector selector, String message) { - Configuration configuration = newEmptyConfiguration(); + Configuration configuration = new Configuration(false); assertThatThrownBy(() -> applyMapping(provider, selector, configuration)) .isInstanceOf(AccessDeniedException.class) diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestTrinoS3FileSystem.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestTrinoS3FileSystem.java index dcba948b242f..24945b453405 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestTrinoS3FileSystem.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestTrinoS3FileSystem.java @@ -77,7 +77,6 @@ import static com.google.common.io.MoreFiles.deleteRecursively; import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; import static io.airlift.testing.Assertions.assertInstanceOf; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static io.trino.hdfs.s3.TrinoS3FileSystem.NO_SUCH_BUCKET_ERROR_CODE; import static io.trino.hdfs.s3.TrinoS3FileSystem.NO_SUCH_KEY_ERROR_CODE; import static io.trino.hdfs.s3.TrinoS3FileSystem.S3_ACCESS_KEY; @@ -125,7 +124,7 @@ public class TestTrinoS3FileSystem public void testEmbeddedCredentials() throws Exception { - Configuration config = newEmptyConfiguration(); + Configuration config = new Configuration(false); try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { AWSCredentials credentials = getStaticCredentials(config, fs, "s3n://testAccess:testSecret@test-bucket/"); assertEquals(credentials.getAWSAccessKeyId(), "testAccess"); @@ -138,7 +137,7 @@ public void testEmbeddedCredentials() public void testStaticCredentials() throws Exception { - Configuration config = newEmptyConfiguration(); + Configuration config = new Configuration(false); config.set(S3_ACCESS_KEY, "test_access_key"); config.set(S3_SECRET_KEY, "test_secret_key"); @@ -172,7 +171,7 @@ private static AWSCredentials getStaticCredentials(Configuration config, TrinoS3 public void testEndpointWithPinToCurrentRegionConfiguration() throws Exception { - Configuration config = newEmptyConfiguration(); + Configuration config = new Configuration(false); config.set(S3_ENDPOINT, "test.example.endpoint.com"); config.set(S3_PIN_CLIENT_TO_CURRENT_REGION, "true"); try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { @@ -186,7 +185,7 @@ public void testEndpointWithPinToCurrentRegionConfiguration() public void testEndpointWithExplicitRegionConfiguration() throws Exception { - Configuration config = newEmptyConfiguration(); + Configuration config = new Configuration(false); // Only endpoint set config.set(S3_ENDPOINT, "test.example.endpoint.com"); @@ -215,7 +214,7 @@ public void testEndpointWithExplicitRegionConfiguration() public void testAssumeRoleDefaultCredentials() throws Exception { - Configuration config = newEmptyConfiguration(); + Configuration config = new Configuration(false); config.set(S3_IAM_ROLE, "test_role"); try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { @@ -229,7 +228,7 @@ public void testAssumeRoleDefaultCredentials() public void testAssumeRoleStaticCredentials() throws Exception { - Configuration config = newEmptyConfiguration(); + Configuration config = new Configuration(false); config.set(S3_ACCESS_KEY, "test_access_key"); config.set(S3_SECRET_KEY, "test_secret_key"); config.set(S3_IAM_ROLE, "test_role"); @@ -261,7 +260,7 @@ private static AWSCredentialsProvider getStsCredentialsProvider(TrinoS3FileSyste public void testAssumeRoleCredentialsWithExternalId() throws Exception { - Configuration config = newEmptyConfiguration(); + Configuration config = new Configuration(false); config.set(S3_IAM_ROLE, "role"); config.set(S3_EXTERNAL_ID, "externalId"); @@ -278,7 +277,7 @@ public void testAssumeRoleCredentialsWithExternalId() public void testDefaultCredentials() throws Exception { - Configuration config = newEmptyConfiguration(); + Configuration config = new Configuration(false); try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { fs.initialize(new URI("s3n://test-bucket/"), config); @@ -290,7 +289,7 @@ public void testDefaultCredentials() public void testPathStyleAccess() throws Exception { - Configuration config = newEmptyConfiguration(); + Configuration config = new Configuration(false); config.setBoolean(S3_PATH_STYLE_ACCESS, true); try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { @@ -304,7 +303,7 @@ public void testPathStyleAccess() public void testUnderscoreBucket() throws Exception { - Configuration config = newEmptyConfiguration(); + Configuration config = new Configuration(false); config.setBoolean(S3_PATH_STYLE_ACCESS, true); try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { @@ -328,7 +327,7 @@ public void testReadRetryCounters() int maxRetries = 2; MockAmazonS3 s3 = new MockAmazonS3(); s3.setGetObjectHttpErrorCode(HTTP_INTERNAL_ERROR); - Configuration configuration = newEmptyConfiguration(); + Configuration configuration = new Configuration(false); configuration.set(S3_MAX_BACKOFF_TIME, "1ms"); configuration.set(S3_MAX_RETRY_TIME, "5s"); configuration.setInt(S3_MAX_CLIENT_RETRIES, maxRetries); @@ -354,7 +353,7 @@ public void testGetMetadataRetryCounter() try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { MockAmazonS3 s3 = new MockAmazonS3(); s3.setGetObjectMetadataHttpCode(HTTP_INTERNAL_ERROR); - Configuration configuration = newEmptyConfiguration(); + Configuration configuration = new Configuration(false); configuration.set(S3_MAX_BACKOFF_TIME, "1ms"); configuration.set(S3_MAX_RETRY_TIME, "5s"); configuration.setInt(S3_MAX_CLIENT_RETRIES, maxRetries); @@ -405,7 +404,7 @@ private static void testReadObject(Class exceptionClass, int httpErrorCode, S MockAmazonS3 s3 = new MockAmazonS3(); s3.setGetObjectHttpErrorCode(httpErrorCode); s3.setGetObjectS3ErrorCode(s3ErrorCode); - fs.initialize(new URI("s3n://test-bucket/"), newEmptyConfiguration()); + fs.initialize(new URI("s3n://test-bucket/"), new Configuration(false)); fs.setS3Client(s3); try (FSDataInputStream inputStream = fs.open(new Path("s3n://test-bucket/test"))) { assertThatThrownBy(inputStream::read) @@ -426,7 +425,7 @@ public void testCreateWithNonexistentStagingDirectory() try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { MockAmazonS3 s3 = new MockAmazonS3(); - Configuration conf = newEmptyConfiguration(); + Configuration conf = new Configuration(false); conf.set(S3_STAGING_DIRECTORY, staging.toString()); conf.set(S3_STREAMING_UPLOAD_ENABLED, "false"); fs.initialize(new URI("s3n://test-bucket/"), conf); @@ -449,7 +448,7 @@ public void testCreateWithStagingDirectoryFile() try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { MockAmazonS3 s3 = new MockAmazonS3(); - Configuration conf = newEmptyConfiguration(); + Configuration conf = new Configuration(false); conf.set(S3_STAGING_DIRECTORY, staging.toString()); conf.set(S3_STREAMING_UPLOAD_ENABLED, "false"); fs.initialize(new URI("s3n://test-bucket/"), conf); @@ -482,7 +481,7 @@ public void testCreateWithStagingDirectorySymlink() try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { MockAmazonS3 s3 = new MockAmazonS3(); - Configuration conf = newEmptyConfiguration(); + Configuration conf = new Configuration(false); conf.set(S3_STAGING_DIRECTORY, link.toString()); fs.initialize(new URI("s3n://test-bucket/"), conf); fs.setS3Client(s3); @@ -504,7 +503,7 @@ public void testReadRequestRangeNotSatisfiable() try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { MockAmazonS3 s3 = new MockAmazonS3(); s3.setGetObjectHttpErrorCode(HTTP_RANGE_NOT_SATISFIABLE); - fs.initialize(new URI("s3n://test-bucket/"), newEmptyConfiguration()); + fs.initialize(new URI("s3n://test-bucket/"), new Configuration(false)); fs.setS3Client(s3); try (FSDataInputStream inputStream = fs.open(new Path("s3n://test-bucket/test"))) { assertEquals(inputStream.read(), -1); @@ -519,7 +518,7 @@ public void testGetMetadataForbidden() try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { MockAmazonS3 s3 = new MockAmazonS3(); s3.setGetObjectMetadataHttpCode(HTTP_FORBIDDEN); - fs.initialize(new URI("s3n://test-bucket/"), newEmptyConfiguration()); + fs.initialize(new URI("s3n://test-bucket/"), new Configuration(false)); fs.setS3Client(s3); assertThatThrownBy(() -> fs.getS3ObjectMetadata(new Path("s3n://test-bucket/test"))) .isInstanceOf(IOException.class) @@ -534,7 +533,7 @@ public void testGetMetadataNotFound() try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { MockAmazonS3 s3 = new MockAmazonS3(); s3.setGetObjectMetadataHttpCode(HTTP_NOT_FOUND); - fs.initialize(new URI("s3n://test-bucket/"), newEmptyConfiguration()); + fs.initialize(new URI("s3n://test-bucket/"), new Configuration(false)); fs.setS3Client(s3); assertNull(fs.getS3ObjectMetadata(new Path("s3n://test-bucket/test"))); } @@ -544,7 +543,7 @@ public void testGetMetadataNotFound() public void testEncryptionMaterialsProvider() throws Exception { - Configuration config = newEmptyConfiguration(); + Configuration config = new Configuration(false); config.set(S3_ENCRYPTION_MATERIALS_PROVIDER, TestEncryptionMaterialsProvider.class.getName()); try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { @@ -557,7 +556,7 @@ public void testEncryptionMaterialsProvider() public void testKMSEncryptionMaterialsProvider() throws Exception { - Configuration config = newEmptyConfiguration(); + Configuration config = new Configuration(false); config.set(S3_KMS_KEY_ID, "test-key-id"); try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { @@ -577,7 +576,7 @@ public void testUnrecoverableS3ExceptionMessage() public void testCustomCredentialsProvider() throws Exception { - Configuration config = newEmptyConfiguration(); + Configuration config = new Configuration(false); config.set(S3_CREDENTIALS_PROVIDER, TestCredentialsProvider.class.getName()); try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { fs.initialize(new URI("s3n://test-bucket/"), config); @@ -589,7 +588,7 @@ public void testCustomCredentialsProvider() public void testCustomCredentialsClassCannotBeFound() throws Exception { - Configuration config = newEmptyConfiguration(); + Configuration config = new Configuration(false); config.set(S3_CREDENTIALS_PROVIDER, "com.example.DoesNotExist"); try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { assertThatThrownBy(() -> fs.initialize(new URI("s3n://test-bucket/"), config)) @@ -606,7 +605,7 @@ public void testUserAgentPrefix() throws Exception { String userAgentPrefix = "agent_prefix"; - Configuration config = newEmptyConfiguration(); + Configuration config = new Configuration(false); config.set(S3_USER_AGENT_PREFIX, userAgentPrefix); try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { fs.initialize(new URI("s3n://test-bucket/"), config); @@ -622,7 +621,7 @@ public void testDefaultS3ClientConfiguration() { HiveS3Config defaults = new HiveS3Config(); try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { - fs.initialize(new URI("s3n://test-bucket/"), newEmptyConfiguration()); + fs.initialize(new URI("s3n://test-bucket/"), new Configuration(false)); ClientConfiguration config = getFieldValue(fs.getS3Client(), AmazonWebServiceClient.class, "clientConfiguration", ClientConfiguration.class); assertEquals(config.getMaxErrorRetry(), defaults.getS3MaxErrorRetries()); assertEquals(config.getConnectionTimeout(), defaults.getS3ConnectTimeout().toMillis()); @@ -648,7 +647,7 @@ public void testProxyDefaultsS3ClientConfiguration() HiveS3Config hiveS3Config = new HiveS3Config(); TrinoS3ConfigurationInitializer configurationInitializer = new TrinoS3ConfigurationInitializer(hiveS3Config); - Configuration trinoFsConfiguration = newEmptyConfiguration(); + Configuration trinoFsConfiguration = new Configuration(false); configurationInitializer.initializeConfiguration(trinoFsConfiguration); try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { @@ -678,7 +677,7 @@ public void testOnNoHostProxyDefaultsS3ClientConfiguration() hiveS3Config.setS3PreemptiveBasicProxyAuth(true); TrinoS3ConfigurationInitializer configurationInitializer = new TrinoS3ConfigurationInitializer(hiveS3Config); - Configuration trinoFsConfiguration = newEmptyConfiguration(); + Configuration trinoFsConfiguration = new Configuration(false); configurationInitializer.initializeConfiguration(trinoFsConfiguration); try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { @@ -708,7 +707,7 @@ public void testExplicitProxyS3ClientConfiguration() hiveS3Config.setS3PreemptiveBasicProxyAuth(true); TrinoS3ConfigurationInitializer configurationInitializer = new TrinoS3ConfigurationInitializer(hiveS3Config); - Configuration trinoFsConfiguration = newEmptyConfiguration(); + Configuration trinoFsConfiguration = new Configuration(false); configurationInitializer.initializeConfiguration(trinoFsConfiguration); try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { @@ -727,7 +726,7 @@ public void testExplicitProxyS3ClientConfiguration() private static void assertSkipGlacierObjects(boolean skipGlacierObjects) throws Exception { - Configuration config = newEmptyConfiguration(); + Configuration config = new Configuration(false); config.set(S3_SKIP_GLACIER_OBJECTS, String.valueOf(skipGlacierObjects)); try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { @@ -744,7 +743,7 @@ private static void assertSkipGlacierObjects(boolean skipGlacierObjects) public void testSkipHadoopFolderMarkerObjectsEnabled() throws Exception { - Configuration config = newEmptyConfiguration(); + Configuration config = new Configuration(false); try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { MockAmazonS3 s3 = new MockAmazonS3(); @@ -828,7 +827,7 @@ public void refresh() {} public void testDefaultAcl() throws Exception { - Configuration config = newEmptyConfiguration(); + Configuration config = new Configuration(false); try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { MockAmazonS3 s3 = new MockAmazonS3(); @@ -846,7 +845,7 @@ public void testDefaultAcl() public void testFullBucketOwnerControlAcl() throws Exception { - Configuration config = newEmptyConfiguration(); + Configuration config = new Configuration(false); config.set(S3_ACL_TYPE, "BUCKET_OWNER_FULL_CONTROL"); try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { @@ -865,7 +864,7 @@ public void testFullBucketOwnerControlAcl() public void testStreamingUpload() throws Exception { - Configuration config = newEmptyConfiguration(); + Configuration config = new Configuration(false); config.set(S3_STREAMING_UPLOAD_ENABLED, "true"); try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { @@ -911,7 +910,7 @@ public ObjectMetadata getObjectMetadata(GetObjectMetadataRequest getObjectMetada return super.getObjectMetadata(getObjectMetadataRequest); } }; - fs.initialize(new URI("s3n://test-bucket/"), newEmptyConfiguration()); + fs.initialize(new URI("s3n://test-bucket/"), new Configuration(false)); fs.setS3Client(s3); FileStatus fileStatus = fs.getFileStatus(new Path("s3n://test-bucket/empty-dir/")); @@ -955,7 +954,7 @@ public ListObjectsV2Result listObjectsV2(ListObjectsV2Request listObjectsV2Reque } }; Path rootPath = new Path("s3n://test-bucket/"); - fs.initialize(rootPath.toUri(), newEmptyConfiguration()); + fs.initialize(rootPath.toUri(), new Configuration(false)); fs.setS3Client(s3); List shallowAll = remoteIteratorToList(fs.listLocatedStatus(rootPath)); @@ -988,7 +987,7 @@ public void testThatTrinoS3FileSystemReportsConsumedMemory() try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { MockAmazonS3 s3 = new MockAmazonS3(); Path rootPath = new Path("s3n://test-bucket/"); - fs.initialize(rootPath.toUri(), newEmptyConfiguration()); + fs.initialize(rootPath.toUri(), new Configuration(false)); fs.setS3Client(s3); OutputStream outputStream = fs.create(new Path("s3n://test-bucket/test1"), memoryContext); outputStream.write(new byte[] {1, 2, 3, 4, 5, 6}, 0, 6); diff --git a/lib/trino-hive-formats/pom.xml b/lib/trino-hive-formats/pom.xml index 44045573ba48..c63dd3b79e18 100644 --- a/lib/trino-hive-formats/pom.xml +++ b/lib/trino-hive-formats/pom.xml @@ -120,12 +120,6 @@ runtime - - io.trino - trino-hadoop-toolkit - runtime - - org.xerial.snappy diff --git a/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/line/csv/TestCsvFormat.java b/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/line/csv/TestCsvFormat.java index d0c469daef14..83c99a39d682 100644 --- a/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/line/csv/TestCsvFormat.java +++ b/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/line/csv/TestCsvFormat.java @@ -23,13 +23,13 @@ import io.trino.spi.Page; import io.trino.spi.PageBuilder; import io.trino.spi.type.RowType; +import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hive.serde2.Deserializer; import org.apache.hadoop.hive.serde2.OpenCSVSerde; import org.apache.hadoop.hive.serde2.SerDeException; import org.apache.hadoop.hive.serde2.Serializer; import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector; import org.apache.hadoop.io.Text; -import org.apache.hadoop.mapred.JobConf; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -42,7 +42,6 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static io.trino.hive.formats.FormatTestUtils.createLineBuffer; import static io.trino.hive.formats.FormatTestUtils.getJavaObjectInspector; import static io.trino.hive.formats.FormatTestUtils.toSingleRowPage; @@ -277,7 +276,7 @@ private static void assertInvalidConfig(Optional separatorChar, Optio private static OpenCSVSerde createHiveSerDe(int columnCount, Optional separatorChar, Optional quoteChar, Optional escapeChar) throws SerDeException { - JobConf configuration = new JobConf(newEmptyConfiguration()); + Configuration configuration = new Configuration(false); Properties schema = new Properties(); schema.setProperty(META_TABLE_COLUMNS, IntStream.range(0, columnCount).mapToObj(i -> "value_" + i).collect(joining(","))); diff --git a/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/line/json/TestJsonFormat.java b/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/line/json/TestJsonFormat.java index 947350f97b31..5f949e61284e 100644 --- a/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/line/json/TestJsonFormat.java +++ b/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/line/json/TestJsonFormat.java @@ -37,6 +37,7 @@ import io.trino.spi.type.Type; import io.trino.spi.type.TypeOperators; import io.trino.spi.type.VarcharType; +import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hive.serde2.Deserializer; import org.apache.hadoop.hive.serde2.SerDeException; import org.apache.hadoop.hive.serde2.SerDeUtils; @@ -44,7 +45,6 @@ import org.apache.hadoop.hive.serde2.objectinspector.StructField; import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector; import org.apache.hadoop.io.Text; -import org.apache.hadoop.mapred.JobConf; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -59,7 +59,6 @@ import java.util.Map; import java.util.Properties; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static io.trino.hive.formats.FormatTestUtils.assertColumnValueEquals; import static io.trino.hive.formats.FormatTestUtils.createLineBuffer; import static io.trino.hive.formats.FormatTestUtils.decodeRecordReaderValue; @@ -1011,7 +1010,7 @@ private static List readHiveLine(List columns, String jsonLine, private static Deserializer createHiveDeserializer(List columns, List timestampFormats, boolean hcatalog) throws SerDeException { - JobConf configuration = new JobConf(newEmptyConfiguration()); + Configuration configuration = new Configuration(false); Properties schema = new Properties(); schema.put(LIST_COLUMNS, columns.stream() diff --git a/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/line/openxjson/TestOpenxJsonFormat.java b/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/line/openxjson/TestOpenxJsonFormat.java index f7109e13de0a..6ca6518283b7 100644 --- a/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/line/openxjson/TestOpenxJsonFormat.java +++ b/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/line/openxjson/TestOpenxJsonFormat.java @@ -41,6 +41,7 @@ import io.trino.spi.type.Type; import io.trino.spi.type.TypeOperators; import io.trino.spi.type.VarcharType; +import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hive.serde2.Deserializer; import org.apache.hadoop.hive.serde2.SerDeException; import org.apache.hadoop.hive.serde2.objectinspector.ListObjectInspector; @@ -51,7 +52,6 @@ import org.apache.hadoop.hive.serde2.objectinspector.StructField; import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector; import org.apache.hadoop.io.Text; -import org.apache.hadoop.mapred.JobConf; import org.junit.jupiter.api.Test; import org.openx.data.jsonserde.JsonSerDe; @@ -77,7 +77,6 @@ import java.util.stream.Collectors; import static com.google.common.collect.ImmutableList.toImmutableList; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static io.trino.hive.formats.FormatTestUtils.assertColumnValueEquals; import static io.trino.hive.formats.FormatTestUtils.assertColumnValuesEquals; import static io.trino.hive.formats.FormatTestUtils.createLineBuffer; @@ -1395,7 +1394,7 @@ private static List readHiveLine(List columns, String jsonLine, private static JsonSerDe createHiveSerDe(List columns, OpenXJsonOptions options) { try { - JobConf configuration = new JobConf(newEmptyConfiguration()); + Configuration configuration = new Configuration(false); Properties schema = new Properties(); schema.putAll(createOpenXJsonSerDeProperties(columns, options)); @@ -1552,7 +1551,7 @@ private static void internalAssertValueFailsHive(Type type, String jsonValue, Op private static void assertLineFailsHive(List columns, String jsonLine, OpenXJsonOptions options) { assertThatThrownBy(() -> { - JobConf configuration = new JobConf(newEmptyConfiguration()); + Configuration configuration = new Configuration(false); Properties schema = new Properties(); schema.putAll(createOpenXJsonSerDeProperties(columns, options)); diff --git a/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/line/regex/TestRegexFormat.java b/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/line/regex/TestRegexFormat.java index f6006d0282f8..fd89413088da 100644 --- a/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/line/regex/TestRegexFormat.java +++ b/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/line/regex/TestRegexFormat.java @@ -28,6 +28,7 @@ import io.trino.spi.type.TimestampType; import io.trino.spi.type.Type; import io.trino.spi.type.VarcharType; +import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hive.serde2.Deserializer; import org.apache.hadoop.hive.serde2.RegexSerDe; import org.apache.hadoop.hive.serde2.SerDeException; @@ -35,7 +36,6 @@ import org.apache.hadoop.hive.serde2.objectinspector.StructField; import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector; import org.apache.hadoop.io.Text; -import org.apache.hadoop.mapred.JobConf; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -50,7 +50,6 @@ import java.util.Map; import java.util.Properties; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static io.trino.hive.formats.FormatTestUtils.assertColumnValueEquals; import static io.trino.hive.formats.FormatTestUtils.createLineBuffer; import static io.trino.hive.formats.FormatTestUtils.decodeRecordReaderValue; @@ -666,7 +665,7 @@ private static void assertLineHive(List columns, String line, String reg private static List readLineHive(List columns, String line, String regex, boolean caseSensitive) throws IOException { - JobConf configuration = new JobConf(newEmptyConfiguration()); + Configuration configuration = new Configuration(false); Properties schema = new Properties(); schema.setProperty(META_TABLE_COLUMNS, columns.stream() diff --git a/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/line/simple/TestSimpleFormat.java b/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/line/simple/TestSimpleFormat.java index df8f30eec918..67a37bee7c2c 100644 --- a/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/line/simple/TestSimpleFormat.java +++ b/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/line/simple/TestSimpleFormat.java @@ -40,6 +40,7 @@ import io.trino.spi.type.Type; import io.trino.spi.type.TypeOperators; import io.trino.spi.type.VarcharType; +import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hive.serde2.Deserializer; import org.apache.hadoop.hive.serde2.SerDeException; import org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe; @@ -47,7 +48,6 @@ import org.apache.hadoop.hive.serde2.objectinspector.SettableStructObjectInspector; import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector; import org.apache.hadoop.io.Text; -import org.apache.hadoop.mapred.JobConf; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -70,7 +70,6 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static io.airlift.slice.Slices.utf8Slice; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static io.trino.hive.formats.FormatTestUtils.assertColumnValueEquals; import static io.trino.hive.formats.FormatTestUtils.createLineBuffer; import static io.trino.hive.formats.FormatTestUtils.decodeRecordReaderValue; @@ -223,7 +222,7 @@ private static void testNestingLevels(int nestingLevels, Optional tableP () -> { Properties schema = new Properties(); schema.putAll(createLazySimpleSerDeProperties(columns, options)); - new LazySimpleSerDe().initialize(new JobConf(newEmptyConfiguration()), schema); + new LazySimpleSerDe().initialize(new Configuration(false), schema); }) .isInstanceOf(SerDeException.class) .hasMessageContaining("nesting"); @@ -1426,7 +1425,7 @@ private static LazySimpleSerDe createHiveSerDe(List columns, TextEncodin Properties schema = new Properties(); schema.putAll(createLazySimpleSerDeProperties(columns, textEncodingOptions)); - JobConf configuration = new JobConf(newEmptyConfiguration()); + Configuration configuration = new Configuration(false); LazySimpleSerDe deserializer = new LazySimpleSerDe(); deserializer.initialize(configuration, schema); return deserializer; diff --git a/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/rcfile/RcFileTester.java b/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/rcfile/RcFileTester.java index bc245d8e9572..bd8ea5f9b13f 100644 --- a/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/rcfile/RcFileTester.java +++ b/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/rcfile/RcFileTester.java @@ -86,7 +86,6 @@ import static io.airlift.slice.SizeOf.SIZE_OF_INT; import static io.airlift.slice.SizeOf.SIZE_OF_LONG; import static io.airlift.units.DataSize.Unit.KILOBYTE; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static io.trino.hive.formats.FormatTestUtils.COMPRESSION; import static io.trino.hive.formats.FormatTestUtils.assertColumnValueEquals; import static io.trino.hive.formats.FormatTestUtils.decodeRecordReaderValue; @@ -508,7 +507,7 @@ private static void as Iterable expectedValues) throws Exception { - JobConf configuration = new JobConf(newEmptyConfiguration()); + JobConf configuration = new JobConf(false); configuration.set(READ_COLUMN_IDS_CONF_STR, "0"); configuration.setBoolean(READ_ALL_COLUMNS, false); diff --git a/lib/trino-orc/pom.xml b/lib/trino-orc/pom.xml index 6666bb334ab0..13fa458b251e 100644 --- a/lib/trino-orc/pom.xml +++ b/lib/trino-orc/pom.xml @@ -108,12 +108,6 @@ runtime - - io.trino - trino-hadoop-toolkit - runtime - - io.trino.hadoop hadoop-apache diff --git a/lib/trino-orc/src/test/java/io/trino/orc/OrcTester.java b/lib/trino-orc/src/test/java/io/trino/orc/OrcTester.java index 93d6442a3d31..bee0267389c2 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/OrcTester.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/OrcTester.java @@ -55,6 +55,7 @@ import io.trino.spi.type.TypeSignatureParameter; import io.trino.spi.type.VarbinaryType; import io.trino.spi.type.VarcharType; +import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.hive.common.type.Date; import org.apache.hadoop.hive.common.type.HiveChar; @@ -127,7 +128,6 @@ import static com.google.common.collect.Lists.newArrayList; import static io.airlift.slice.Slices.utf8Slice; import static io.airlift.units.DataSize.Unit.MEGABYTE; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static io.trino.memory.context.AggregatedMemoryContext.newSimpleAggregatedMemoryContext; import static io.trino.orc.OrcReader.MAX_BATCH_SIZE; import static io.trino.orc.OrcTester.Format.ORC_11; @@ -832,7 +832,7 @@ private static void assertFileContentsOrcHive( Iterable expectedValues) throws Exception { - JobConf configuration = new JobConf(newEmptyConfiguration()); + JobConf configuration = new JobConf(new Configuration(false)); configuration.set(READ_COLUMN_IDS_CONF_STR, "0"); configuration.setBoolean(READ_ALL_COLUMNS, false); @@ -1245,7 +1245,7 @@ private static class RecordWriterBuilder private RecordWriterBuilder(File file, Format format, CompressionKind compression) { - this.jobConf = new JobConf(newEmptyConfiguration()); + this.jobConf = new JobConf(new Configuration(false)); this.file = file; this.compression = compression; OrcConf.WRITE_FORMAT.setString(jobConf, format == ORC_12 ? "0.12" : "0.11"); diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcReaderPositions.java b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcReaderPositions.java index 3e680de6c30c..de3fcc84aa4b 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcReaderPositions.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcReaderPositions.java @@ -43,7 +43,6 @@ import java.nio.ByteBuffer; import java.util.Map; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static io.trino.orc.OrcReader.BATCH_SIZE_GROWTH_FACTOR; import static io.trino.orc.OrcReader.INITIAL_BATCH_SIZE; import static io.trino.orc.OrcReader.MAX_BATCH_SIZE; @@ -413,7 +412,7 @@ private static void createMultiStripeFile(File file) private static void createFileWithOnlyUserMetadata(File file, Map metadata) throws IOException { - Configuration conf = newEmptyConfiguration(); + Configuration conf = new Configuration(false); OrcFile.WriterOptions writerOptions = OrcFile.writerOptions(conf) .memory(new NullMemoryManager()) .inspector(createSettableStructObjectInspector("test", BIGINT)) diff --git a/plugin/trino-delta-lake/pom.xml b/plugin/trino-delta-lake/pom.xml index 7f1c229c86fe..e784443e51c8 100644 --- a/plugin/trino-delta-lake/pom.xml +++ b/plugin/trino-delta-lake/pom.xml @@ -268,12 +268,6 @@ runtime - - io.trino - trino-hadoop-toolkit - runtime - - io.trino trino-memory-context diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeGcsConnectorSmokeTest.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeGcsConnectorSmokeTest.java index 5570743d3df0..7a8234bf9d8e 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeGcsConnectorSmokeTest.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeGcsConnectorSmokeTest.java @@ -24,7 +24,6 @@ import io.trino.filesystem.Location; import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoOutputFile; -import io.trino.hadoop.ConfigurationInstantiator; import io.trino.hdfs.gcs.GoogleGcsConfigurationInitializer; import io.trino.hdfs.gcs.HiveGcsConfig; import io.trino.plugin.hive.containers.HiveHadoop; @@ -92,7 +91,7 @@ protected void environmentSetup() gcpCredentialsFile.toFile().deleteOnExit(); Files.write(gcpCredentialsFile, jsonKeyBytes); HiveGcsConfig gcsConfig = new HiveGcsConfig().setJsonKey(gcpCredentials); - Configuration configuration = ConfigurationInstantiator.newEmptyConfiguration(); + Configuration configuration = new Configuration(false); new GoogleGcsConfigurationInitializer(gcsConfig).initializeConfiguration(configuration); } catch (IOException e) { diff --git a/plugin/trino-exchange-hdfs/pom.xml b/plugin/trino-exchange-hdfs/pom.xml index 4a1db2d3dfe6..1b057aedf6d6 100644 --- a/plugin/trino-exchange-hdfs/pom.xml +++ b/plugin/trino-exchange-hdfs/pom.xml @@ -53,11 +53,6 @@ trino-exchange-filesystem - - io.trino - trino-hadoop-toolkit - - io.trino trino-plugin-toolkit diff --git a/plugin/trino-exchange-hdfs/src/main/java/io/trino/plugin/exchange/hdfs/HadoopFileSystemExchangeStorage.java b/plugin/trino-exchange-hdfs/src/main/java/io/trino/plugin/exchange/hdfs/HadoopFileSystemExchangeStorage.java index 09b0614ea679..74ab63ee24af 100644 --- a/plugin/trino-exchange-hdfs/src/main/java/io/trino/plugin/exchange/hdfs/HadoopFileSystemExchangeStorage.java +++ b/plugin/trino-exchange-hdfs/src/main/java/io/trino/plugin/exchange/hdfs/HadoopFileSystemExchangeStorage.java @@ -26,6 +26,7 @@ import io.trino.plugin.exchange.filesystem.ExchangeStorageWriter; import io.trino.plugin.exchange.filesystem.FileStatus; import io.trino.plugin.exchange.filesystem.FileSystemExchangeStorage; +import io.trino.spi.classloader.ThreadContextClassLoader; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.LocatedFileStatus; @@ -46,7 +47,6 @@ import static com.google.common.util.concurrent.Futures.immediateFuture; import static com.google.common.util.concurrent.Futures.immediateVoidFuture; import static io.airlift.slice.SizeOf.instanceSize; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static java.lang.Math.toIntExact; import static java.util.Objects.requireNonNull; @@ -143,6 +143,13 @@ public void close() { } + private static Configuration newEmptyConfiguration() + { + try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(HadoopFileSystemExchangeStorage.class.getClassLoader())) { + return new Configuration(false); + } + } + @ThreadSafe private static class HadoopExchangeStorageReader implements ExchangeStorageReader diff --git a/plugin/trino-hive-hadoop2/pom.xml b/plugin/trino-hive-hadoop2/pom.xml index cbf153442e5d..e94141f8214d 100644 --- a/plugin/trino-hive-hadoop2/pom.xml +++ b/plugin/trino-hive-hadoop2/pom.xml @@ -112,12 +112,6 @@ runtime - - io.trino - trino-hadoop-toolkit - runtime - - io.trino trino-hdfs diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileFormats.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileFormats.java index eddb7e795a38..1e9d8a83e6b4 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileFormats.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileFormats.java @@ -40,6 +40,7 @@ import io.trino.spi.type.VarcharType; import io.trino.testing.MaterializedResult; import io.trino.testing.MaterializedRow; +import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.hive.common.type.Date; import org.apache.hadoop.hive.common.type.HiveChar; @@ -81,7 +82,6 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableList.toImmutableList; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static io.trino.plugin.hive.HiveColumnHandle.ColumnType.PARTITION_KEY; import static io.trino.plugin.hive.HiveColumnHandle.ColumnType.REGULAR; import static io.trino.plugin.hive.HiveColumnHandle.createBaseColumn; @@ -615,9 +615,9 @@ public static FileSplit createTestFileHive( testColumns.stream() .map(TestColumn::getType) .collect(Collectors.joining(","))); - serializer.initialize(newEmptyConfiguration(), tableProperties); + serializer.initialize(new Configuration(false), tableProperties); - JobConf jobConf = new JobConf(newEmptyConfiguration()); + JobConf jobConf = new JobConf(false); configureCompression(jobConf, compressionCodec); RecordWriter recordWriter = outputFormat.getHiveRecordWriter( @@ -629,7 +629,7 @@ public static FileSplit createTestFileHive( () -> {}); try { - serializer.initialize(newEmptyConfiguration(), tableProperties); + serializer.initialize(new Configuration(false), tableProperties); SettableStructObjectInspector objectInspector = getStandardStructObjectInspector( testColumns.stream() @@ -662,7 +662,7 @@ public static FileSplit createTestFileHive( // todo to test with compression, the file must be renamed with the compression extension Path path = new Path(filePath); - path.getFileSystem(newEmptyConfiguration()).setVerifyChecksum(true); + path.getFileSystem(new Configuration(false)).setVerifyChecksum(true); File file = new File(filePath); return new FileSplit(path, 0, file.length(), new String[0]); } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOrcPageSourceMemoryTracking.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOrcPageSourceMemoryTracking.java index 54e956e311db..0af892b06510 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOrcPageSourceMemoryTracking.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOrcPageSourceMemoryTracking.java @@ -96,7 +96,6 @@ import static io.airlift.concurrent.Threads.daemonThreadsNamed; import static io.airlift.testing.Assertions.assertBetweenInclusive; import static io.airlift.units.DataSize.Unit.BYTE; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static io.trino.metadata.FunctionManager.createTestingFunctionManager; import static io.trino.orc.OrcReader.MAX_BATCH_SIZE; import static io.trino.plugin.hive.HiveColumnHandle.ColumnType.PARTITION_KEY; @@ -138,7 +137,7 @@ public class TestOrcPageSourceMemoryTracking { private static final String ORC_RECORD_WRITER = OrcOutputFormat.class.getName() + "$OrcRecordWriter"; private static final Constructor WRITER_CONSTRUCTOR = getOrcWriterConstructor(); - private static final Configuration CONFIGURATION = newEmptyConfiguration(); + private static final Configuration CONFIGURATION = new Configuration(false); private static final int NUM_ROWS = 50000; private static final int STRIPE_ROWS = 20000; private static final FunctionManager functionManager = createTestingFunctionManager(); @@ -670,7 +669,7 @@ private static FileSplit createTestFile( serializer.initialize(CONFIGURATION, tableProperties); - JobConf jobConf = new JobConf(newEmptyConfiguration()); + JobConf jobConf = new JobConf(new Configuration(false)); if (compressionCodec != null) { CompressionCodec codec = new CompressionCodecFactory(CONFIGURATION).getCodecByName(compressionCodec); jobConf.set(COMPRESS_CODEC, codec.getClass().getName()); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/avro/TestAvroSchemaGeneration.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/avro/TestAvroSchemaGeneration.java index 484709c64ccb..017df21ff3d2 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/avro/TestAvroSchemaGeneration.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/avro/TestAvroSchemaGeneration.java @@ -14,12 +14,12 @@ package io.trino.plugin.hive.avro; import io.trino.filesystem.local.LocalFileSystem; -import io.trino.hadoop.ConfigurationInstantiator; import io.trino.plugin.hive.HiveType; import io.trino.plugin.hive.type.TypeInfo; import io.trino.spi.type.RowType; import io.trino.spi.type.VarcharType; import org.apache.avro.Schema; +import org.apache.hadoop.conf.Configuration; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -44,7 +44,7 @@ public void testOldVsNewSchemaGeneration() properties.setProperty(LIST_COLUMNS, "a,b"); properties.setProperty(LIST_COLUMN_TYPES, Stream.of(HiveType.HIVE_INT, HiveType.HIVE_STRING).map(HiveType::getTypeInfo).map(TypeInfo::toString).collect(Collectors.joining(","))); Schema actual = AvroHiveFileUtils.determineSchemaOrThrowException(new LocalFileSystem(Path.of("/")), properties); - Schema expected = new TrinoAvroSerDe().determineSchemaOrReturnErrorSchema(ConfigurationInstantiator.newEmptyConfiguration(), properties); + Schema expected = new TrinoAvroSerDe().determineSchemaOrReturnErrorSchema(new Configuration(false), properties); assertThat(actual).isEqualTo(expected); } @@ -57,7 +57,7 @@ public void testOldVsNewSchemaGenerationWithNested() properties.setProperty(LIST_COLUMNS, "a,b"); properties.setProperty(LIST_COLUMN_TYPES, Stream.of(HiveType.toHiveType(RowType.rowType(RowType.field("a", VarcharType.VARCHAR))), HiveType.HIVE_STRING).map(HiveType::getTypeInfo).map(TypeInfo::toString).collect(Collectors.joining(","))); Schema actual = AvroHiveFileUtils.determineSchemaOrThrowException(new LocalFileSystem(Path.of("/")), properties); - Schema expected = new TrinoAvroSerDe().determineSchemaOrReturnErrorSchema(ConfigurationInstantiator.newEmptyConfiguration(), properties); + Schema expected = new TrinoAvroSerDe().determineSchemaOrReturnErrorSchema(new Configuration(false), properties); assertThat(actual).isEqualTo(expected); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcDeleteDeltaPageSource.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcDeleteDeltaPageSource.java index 218ecea7684d..512b9b66f5a7 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcDeleteDeltaPageSource.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcDeleteDeltaPageSource.java @@ -22,13 +22,13 @@ import io.trino.spi.connector.ConnectorPageSource; import io.trino.testing.MaterializedResult; import io.trino.testing.MaterializedRow; +import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hive.ql.io.AcidOutputFormat; import org.apache.hadoop.hive.ql.io.BucketCodec; import org.junit.jupiter.api.Test; import java.io.File; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static io.trino.plugin.hive.HiveTestUtils.SESSION; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.IntegerType.INTEGER; @@ -51,7 +51,7 @@ public void testReadingDeletedRows() assertEquals(materializedRows.getRowCount(), 1); - AcidOutputFormat.Options bucketOptions = new AcidOutputFormat.Options(newEmptyConfiguration()).bucket(0); + AcidOutputFormat.Options bucketOptions = new AcidOutputFormat.Options(new Configuration(false)).bucket(0); assertEquals(materializedRows.getMaterializedRows().get(0), new MaterializedRow(5, 2L, BucketCodec.V1.encode(bucketOptions), 0L)); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetTester.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetTester.java index 201828ba45ba..8bb3d0108861 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetTester.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetTester.java @@ -108,7 +108,6 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.collect.Iterables.transform; import static io.airlift.slice.Slices.utf8Slice; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static io.trino.parquet.ParquetWriteValidation.ParquetWriteValidationBuilder; import static io.trino.parquet.writer.ParquetSchemaConverter.HIVE_PARQUET_USE_INT96_TIMESTAMP_ENCODING; import static io.trino.parquet.writer.ParquetSchemaConverter.HIVE_PARQUET_USE_LEGACY_DECIMAL_ENCODING; @@ -383,7 +382,7 @@ void assertRoundTripWithHiveWriter( for (CompressionCodec compressionCodec : compressions) { for (ConnectorSession session : sessions) { try (TempFile tempFile = new TempFile("test", "parquet")) { - JobConf jobConf = new JobConf(newEmptyConfiguration()); + JobConf jobConf = new JobConf(false); jobConf.setEnum(COMPRESSION, compressionCodec); jobConf.setBoolean(ENABLE_DICTIONARY, true); jobConf.setEnum(WRITER_VERSION, version); @@ -447,7 +446,7 @@ void assertMaxReadBytes( .build(); try (TempFile tempFile = new TempFile("test", "parquet")) { - JobConf jobConf = new JobConf(newEmptyConfiguration()); + JobConf jobConf = new JobConf(false); jobConf.setEnum(COMPRESSION, compressionCodec); jobConf.setBoolean(ENABLE_DICTIONARY, true); jobConf.setEnum(WRITER_VERSION, PARQUET_1_0); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestBloomFilterStore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestBloomFilterStore.java index 0e3816d7ca35..c52ad23ff6e7 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestBloomFilterStore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestBloomFilterStore.java @@ -52,7 +52,6 @@ import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.Iterables.getOnlyElement; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static io.trino.plugin.hive.HiveTestUtils.toNativeContainerValue; import static io.trino.spi.predicate.Domain.multipleValues; import static io.trino.spi.predicate.TupleDomain.withColumnDomains; @@ -292,7 +291,7 @@ private static BloomFilterStore generateBloomFilterStore(ParquetTester.TempFile List objectInspectors = singletonList(objectInspector); List columnNames = ImmutableList.of(COLUMN_NAME); - JobConf jobConf = new JobConf(newEmptyConfiguration()); + JobConf jobConf = new JobConf(false); jobConf.setEnum(WRITER_VERSION, PARQUET_1_0); jobConf.setBoolean(BLOOM_FILTER_ENABLED, enableBloomFilter); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestHiveParquetWithBloomFilters.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestHiveParquetWithBloomFilters.java index b06884f79682..292f5c7f693a 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestHiveParquetWithBloomFilters.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestHiveParquetWithBloomFilters.java @@ -30,7 +30,6 @@ import java.util.List; import java.util.Optional; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static io.trino.testing.TestingNames.randomNameSuffix; import static java.lang.String.format; import static java.util.Collections.singletonList; @@ -74,7 +73,7 @@ public static void writeParquetFileWithBloomFilter(File tempFile, String columnN List objectInspectors = singletonList(javaIntObjectInspector); List columnNames = ImmutableList.of(columnName); - JobConf jobConf = new JobConf(newEmptyConfiguration()); + JobConf jobConf = new JobConf(false); jobConf.setEnum(WRITER_VERSION, PARQUET_1_0); jobConf.setBoolean(BLOOM_FILTER_ENABLED, true); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestParquetDecimalScaling.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestParquetDecimalScaling.java index 56aa96534f7a..2c8c73f72c4b 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestParquetDecimalScaling.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestParquetDecimalScaling.java @@ -55,7 +55,6 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.Iterables.transform; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static io.trino.plugin.hive.parquet.TestParquetDecimalScaling.ParquetDecimalInsert.maximumValue; import static io.trino.plugin.hive.parquet.TestParquetDecimalScaling.ParquetDecimalInsert.minimumValue; import static io.trino.spi.type.Decimals.overflows; @@ -450,7 +449,7 @@ private static void createParquetFile( { Properties tableProperties = createTableProperties(columnNames, Collections.singletonList(inspector)); - JobConf jobConf = new JobConf(newEmptyConfiguration()); + JobConf jobConf = new JobConf(false); jobConf.setEnum(COMPRESSION, UNCOMPRESSED); jobConf.setBoolean(ENABLE_DICTIONARY, false); jobConf.setEnum(WRITER_VERSION, writerVersion); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestamp.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestamp.java index 2d7cd1f5ca2c..e02c7ab90afc 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestamp.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestamp.java @@ -43,7 +43,6 @@ import static com.google.common.collect.Iterables.cycle; import static com.google.common.collect.Iterables.limit; import static com.google.common.collect.Iterables.transform; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; import static io.trino.plugin.hive.HiveTestUtils.getHiveSession; import static io.trino.spi.type.BigintType.BIGINT; @@ -114,7 +113,7 @@ private static void testRoundTrip(MessageType parquetSchema, Iterable writ List columnNames = ImmutableList.of("test"); try (ParquetTester.TempFile tempFile = new ParquetTester.TempFile("test", "parquet")) { - JobConf jobConf = new JobConf(newEmptyConfiguration()); + JobConf jobConf = new JobConf(false); jobConf.setEnum(WRITER_VERSION, PARQUET_1_0); ParquetTester.writeParquetColumn( diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestTrinoS3FileSystemAwsS3.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestTrinoS3FileSystemAwsS3.java index 9801ea567be5..2c82337f8870 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestTrinoS3FileSystemAwsS3.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestTrinoS3FileSystemAwsS3.java @@ -16,7 +16,6 @@ import org.apache.hadoop.conf.Configuration; import org.testng.annotations.BeforeClass; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static java.util.Objects.requireNonNull; /** @@ -47,7 +46,7 @@ protected String getBucketName() @Override protected Configuration s3Configuration() { - Configuration config = newEmptyConfiguration(); + Configuration config = new Configuration(false); config.set("fs.s3.endpoint", s3Endpoint); return config; } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestTrinoS3FileSystemMinio.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestTrinoS3FileSystemMinio.java index c400cd82c7ef..999fa2fc6899 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestTrinoS3FileSystemMinio.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestTrinoS3FileSystemMinio.java @@ -26,7 +26,6 @@ import java.net.URI; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.testing.containers.Minio.MINIO_ACCESS_KEY; import static io.trino.testing.containers.Minio.MINIO_SECRET_KEY; @@ -75,7 +74,7 @@ protected String getBucketName() @Override protected Configuration s3Configuration() { - Configuration config = newEmptyConfiguration(); + Configuration config = new Configuration(false); config.set("trino.s3.endpoint", minio.getMinioAddress()); config.set("trino.s3.access-key", MINIO_ACCESS_KEY); config.set("trino.s3.secret-key", MINIO_SECRET_KEY); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestAcidTables.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestAcidTables.java index d7e6926a3b8d..ed4893f44486 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestAcidTables.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestAcidTables.java @@ -35,7 +35,6 @@ import java.util.List; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static io.trino.plugin.hive.util.AcidTables.deleteDeltaSubdir; import static io.trino.plugin.hive.util.AcidTables.getAcidState; import static io.trino.plugin.hive.util.AcidTables.parseBase; @@ -110,7 +109,7 @@ public void testParseDelta() public void testOriginal() throws Exception { - MockFileSystem fs = new MockFileSystem(newEmptyConfiguration(), + MockFileSystem fs = new MockFileSystem(new Configuration(false), new MockFile("mock:/tbl/part1/000000_0", 500, FAKE_DATA), new MockFile("mock:/tbl/part1/000000_0_copy_1", 500, FAKE_DATA), new MockFile("mock:/tbl/part1/000000_0_copy_2", 500, FAKE_DATA), @@ -144,7 +143,7 @@ public void testOriginal() public void testOriginalDeltas() throws Exception { - MockFileSystem fs = new MockFileSystem(newEmptyConfiguration(), + MockFileSystem fs = new MockFileSystem(new Configuration(false), new MockFile("mock:/tbl/part1/000000_0", 500, FAKE_DATA), new MockFile("mock:/tbl/part1/000001_1", 500, FAKE_DATA), new MockFile("mock:/tbl/part1/000002_0", 500, FAKE_DATA), @@ -189,7 +188,7 @@ public void testOriginalDeltas() public void testBaseDeltas() throws Exception { - MockFileSystem fs = new MockFileSystem(newEmptyConfiguration(), + MockFileSystem fs = new MockFileSystem(new Configuration(false), new MockFile("mock:/tbl/part1/_tmp/bucket_0", 0, FAKE_DATA), new MockFile("mock:/tbl/part1/_tmp/base_5/bucket_0", 0, FAKE_DATA), new MockFile("mock:/tbl/part1/base_5/bucket_0", 500, FAKE_DATA), @@ -220,7 +219,7 @@ public void testBaseDeltas() public void testObsoleteOriginals() throws Exception { - MockFileSystem fs = new MockFileSystem(newEmptyConfiguration(), + MockFileSystem fs = new MockFileSystem(new Configuration(false), new MockFile("mock:/tbl/part1/base_10/bucket_0", 500, FAKE_DATA), new MockFile("mock:/tbl/part1/base_5/bucket_0", 500, FAKE_DATA), new MockFile("mock:/tbl/part1/000000_0", 500, FAKE_DATA), @@ -237,7 +236,7 @@ public void testObsoleteOriginals() public void testOverlapingDelta() throws Exception { - MockFileSystem fs = new MockFileSystem(newEmptyConfiguration(), + MockFileSystem fs = new MockFileSystem(new Configuration(false), new MockFile("mock:/tbl/part1/delta_0000063_63/bucket_0", 500, FAKE_DATA), new MockFile("mock:/tbl/part1/delta_000062_62/bucket_0", 500, FAKE_DATA), new MockFile("mock:/tbl/part1/delta_00061_61/bucket_0", 500, FAKE_DATA), @@ -264,7 +263,7 @@ public void testOverlapingDelta() public void testOverlapingDelta2() throws Exception { - MockFileSystem fs = new MockFileSystem(newEmptyConfiguration(), + MockFileSystem fs = new MockFileSystem(new Configuration(false), new MockFile("mock:/tbl/part1/delta_0000063_63_0/bucket_0", 500, FAKE_DATA), new MockFile("mock:/tbl/part1/delta_000062_62_0/bucket_0", 500, FAKE_DATA), new MockFile("mock:/tbl/part1/delta_000062_62_3/bucket_0", 500, FAKE_DATA), @@ -296,7 +295,7 @@ public void testOverlapingDelta2() public void deltasWithOpenTxnInRead() throws Exception { - MockFileSystem fs = new MockFileSystem(newEmptyConfiguration(), + MockFileSystem fs = new MockFileSystem(new Configuration(false), new MockFile("mock:/tbl/part1/delta_1_1/bucket_0", 500, FAKE_DATA), new MockFile("mock:/tbl/part1/delta_2_5/bucket_0", 500, FAKE_DATA)); AcidState state = getAcidState( @@ -314,7 +313,7 @@ public void deltasWithOpenTxnInRead() public void deltasWithOpenTxnInRead2() throws Exception { - MockFileSystem fs = new MockFileSystem(newEmptyConfiguration(), + MockFileSystem fs = new MockFileSystem(new Configuration(false), new MockFile("mock:/tbl/part1/delta_1_1/bucket_0", 500, FAKE_DATA), new MockFile("mock:/tbl/part1/delta_2_5/bucket_0", 500, FAKE_DATA), new MockFile("mock:/tbl/part1/delta_4_4_1/bucket_0", 500, FAKE_DATA), @@ -335,7 +334,7 @@ public void deltasWithOpenTxnInRead2() public void testBaseWithDeleteDeltas() throws Exception { - MockFileSystem fs = new MockFileSystem(newEmptyConfiguration(), + MockFileSystem fs = new MockFileSystem(new Configuration(false), new MockFile("mock:/tbl/part1/base_5/bucket_0", 500, FAKE_DATA), new MockFile("mock:/tbl/part1/base_10/bucket_0", 500, FAKE_DATA), new MockFile("mock:/tbl/part1/base_49/bucket_0", 500, FAKE_DATA), @@ -366,7 +365,7 @@ public void testBaseWithDeleteDeltas() public void testOverlapingDeltaAndDeleteDelta() throws Exception { - MockFileSystem fs = new MockFileSystem(newEmptyConfiguration(), + MockFileSystem fs = new MockFileSystem(new Configuration(false), new MockFile("mock:/tbl/part1/delta_0000063_63/bucket_0", 500, FAKE_DATA), new MockFile("mock:/tbl/part1/delta_000062_62/bucket_0", 500, FAKE_DATA), new MockFile("mock:/tbl/part1/delta_00061_61/bucket_0", 500, FAKE_DATA), @@ -400,7 +399,7 @@ public void testMinorCompactedDeltaMakesInBetweenDelteDeltaObsolete() { // This test checks that if we have a minor compacted delta for the txn range [40,60] // then it will make any delete delta in that range as obsolete. - MockFileSystem fs = new MockFileSystem(newEmptyConfiguration(), + MockFileSystem fs = new MockFileSystem(new Configuration(false), new MockFile("mock:/tbl/part1/delta_40_60/bucket_0", 500, FAKE_DATA), new MockFile("mock:/tbl/part1/delete_delta_50_50/bucket_0", 500, FAKE_DATA)); AcidState state = getAcidState( @@ -417,7 +416,7 @@ public void testMinorCompactedDeltaMakesInBetweenDelteDeltaObsolete() public void deleteDeltasWithOpenTxnInRead() throws Exception { - MockFileSystem fs = new MockFileSystem(newEmptyConfiguration(), + MockFileSystem fs = new MockFileSystem(new Configuration(false), new MockFile("mock:/tbl/part1/delta_1_1/bucket_0", 500, FAKE_DATA), new MockFile("mock:/tbl/part1/delta_2_5/bucket_0", 500, FAKE_DATA), new MockFile("mock:/tbl/part1/delete_delta_2_5/bucket_0", 500, FAKE_DATA), @@ -448,7 +447,7 @@ public void testDeleteDeltaSubdirPathGeneration() private static TrinoFileSystem testingTrinoFileSystem(FileSystem fileSystem) { - HdfsConfiguration hdfsConfiguration = (context, uri) -> newEmptyConfiguration(); + HdfsConfiguration hdfsConfiguration = (context, uri) -> new Configuration(false); HdfsEnvironment environment = new HdfsEnvironment(hdfsConfiguration, new HdfsConfig(), new NoHdfsAuthentication()) { diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveAcidUtils.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveAcidUtils.java index c6efa161009c..da4e89ea6cd7 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveAcidUtils.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveAcidUtils.java @@ -28,7 +28,6 @@ import java.util.List; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNull; @@ -44,7 +43,7 @@ public void testParsing() public void testOriginal() throws Exception { - Configuration conf = newEmptyConfiguration(); + Configuration conf = new Configuration(false); MockFileSystem fs = new MockFileSystem(conf, new MockFile("mock:/tbl/part1/000000_0", 500, new byte[0]), new MockFile("mock:/tbl/part1/000000_0" + Utilities.COPY_KEYWORD + "1", 500, new byte[0]), @@ -78,7 +77,7 @@ public void testOriginal() public void testOriginalDeltas() throws Exception { - Configuration conf = newEmptyConfiguration(); + Configuration conf = new Configuration(false); MockFileSystem fs = new MockFileSystem(conf, new MockFile("mock:/tbl/part1/000000_0", 500, new byte[0]), new MockFile("mock:/tbl/part1/000001_1", 500, new byte[0]), @@ -125,7 +124,7 @@ public void testOriginalDeltas() public void testBaseDeltas() throws Exception { - Configuration conf = newEmptyConfiguration(); + Configuration conf = new Configuration(false); MockFileSystem fs = new MockFileSystem(conf, new MockFile("mock:/tbl/part1/_tmp/bucket_0", 0, new byte[0]), new MockFile("mock:/tbl/part1/_tmp/base_5/bucket_0", 0, new byte[0]), @@ -160,7 +159,7 @@ public void testBaseDeltas() public void testObsoleteOriginals() throws Exception { - Configuration conf = newEmptyConfiguration(); + Configuration conf = new Configuration(false); MockFileSystem fs = new MockFileSystem(conf, new MockFile("mock:/tbl/part1/base_10/bucket_0", 500, new byte[0]), new MockFile("mock:/tbl/part1/base_5/bucket_0", 500, new byte[0]), @@ -179,7 +178,7 @@ public void testObsoleteOriginals() public void testOverlapingDelta() throws Exception { - Configuration conf = newEmptyConfiguration(); + Configuration conf = new Configuration(false); MockFileSystem fs = new MockFileSystem(conf, new MockFile("mock:/tbl/part1/delta_0000063_63/bucket_0", 500, new byte[0]), new MockFile("mock:/tbl/part1/delta_000062_62/bucket_0", 500, new byte[0]), @@ -207,7 +206,7 @@ public void testOverlapingDelta() public void testOverlapingDelta2() throws Exception { - Configuration conf = newEmptyConfiguration(); + Configuration conf = new Configuration(false); MockFileSystem fs = new MockFileSystem(conf, new MockFile("mock:/tbl/part1/delta_0000063_63_0/bucket_0", 500, new byte[0]), new MockFile("mock:/tbl/part1/delta_000062_62_0/bucket_0", 500, new byte[0]), @@ -243,7 +242,7 @@ public void testOverlapingDelta2() public void deltasWithOpenTxnInRead() throws Exception { - Configuration conf = newEmptyConfiguration(); + Configuration conf = new Configuration(false); MockFileSystem fs = new MockFileSystem(conf, new MockFile("mock:/tbl/part1/delta_1_1/bucket_0", 500, new byte[0]), new MockFile("mock:/tbl/part1/delta_2_5/bucket_0", 500, new byte[0])); @@ -259,7 +258,7 @@ public void deltasWithOpenTxnInRead() public void deltasWithOpenTxnInRead2() throws Exception { - Configuration conf = newEmptyConfiguration(); + Configuration conf = new Configuration(false); MockFileSystem fs = new MockFileSystem(conf, new MockFile("mock:/tbl/part1/delta_1_1/bucket_0", 500, new byte[0]), new MockFile("mock:/tbl/part1/delta_2_5/bucket_0", 500, new byte[0]), @@ -278,7 +277,7 @@ public void deltasWithOpenTxnInRead2() public void deltasWithOpenTxnsNotInCompact() throws Exception { - Configuration conf = newEmptyConfiguration(); + Configuration conf = new Configuration(false); MockFileSystem fs = new MockFileSystem(conf, new MockFile("mock:/tbl/part1/delta_1_1/bucket_0", 500, new byte[0]), new MockFile("mock:/tbl/part1/delta_2_5/bucket_0", 500, new byte[0])); @@ -293,7 +292,7 @@ public void deltasWithOpenTxnsNotInCompact() public void deltasWithOpenTxnsNotInCompact2() throws Exception { - Configuration conf = newEmptyConfiguration(); + Configuration conf = new Configuration(false); MockFileSystem fs = new MockFileSystem(conf, new MockFile("mock:/tbl/part1/delta_1_1/bucket_0", 500, new byte[0]), new MockFile("mock:/tbl/part1/delta_2_5/bucket_0", 500, new byte[0]), @@ -310,7 +309,7 @@ public void deltasWithOpenTxnsNotInCompact2() public void testBaseWithDeleteDeltas() throws Exception { - Configuration conf = newEmptyConfiguration(); + Configuration conf = new Configuration(false); MockFileSystem fs = new MockFileSystem(conf, new MockFile("mock:/tbl/part1/base_5/bucket_0", 500, new byte[0]), new MockFile("mock:/tbl/part1/base_10/bucket_0", 500, new byte[0]), @@ -347,7 +346,7 @@ public void testBaseWithDeleteDeltas() public void testOverlapingDeltaAndDeleteDelta() throws Exception { - Configuration conf = newEmptyConfiguration(); + Configuration conf = new Configuration(false); MockFileSystem fs = new MockFileSystem(conf, new MockFile("mock:/tbl/part1/delta_0000063_63/bucket_0", 500, new byte[0]), new MockFile("mock:/tbl/part1/delta_000062_62/bucket_0", 500, new byte[0]), @@ -383,7 +382,7 @@ public void testMinorCompactedDeltaMakesInBetweenDelteDeltaObsolete() { // This test checks that if we have a minor compacted delta for the txn range [40,60] // then it will make any delete delta in that range as obsolete. - Configuration conf = newEmptyConfiguration(); + Configuration conf = new Configuration(false); MockFileSystem fs = new MockFileSystem(conf, new MockFile("mock:/tbl/part1/delta_40_60/bucket_0", 500, new byte[0]), new MockFile("mock:/tbl/part1/delete_delta_50_50/bucket_0", 500, new byte[0])); @@ -403,7 +402,7 @@ public void deltasAndDeleteDeltasWithOpenTxnsNotInCompact() { // This tests checks that appropriate delta and delete_deltas are included when minor // compactions specifies a valid open txn range. - Configuration conf = newEmptyConfiguration(); + Configuration conf = new Configuration(false); MockFileSystem fs = new MockFileSystem(conf, new MockFile("mock:/tbl/part1/delta_1_1/bucket_0", 500, new byte[0]), new MockFile("mock:/tbl/part1/delete_delta_2_2/bucket_0", 500, new byte[0]), @@ -424,7 +423,7 @@ public void deltasAndDeleteDeltasWithOpenTxnsNotInCompact() public void deleteDeltasWithOpenTxnInRead() throws Exception { - Configuration conf = newEmptyConfiguration(); + Configuration conf = new Configuration(false); MockFileSystem fs = new MockFileSystem(conf, new MockFile("mock:/tbl/part1/delta_1_1/bucket_0", 500, new byte[0]), new MockFile("mock:/tbl/part1/delta_2_5/bucket_0", 500, new byte[0]), diff --git a/plugin/trino-hudi/pom.xml b/plugin/trino-hudi/pom.xml index 7f67e64a9912..e692e7dfde1a 100644 --- a/plugin/trino-hudi/pom.xml +++ b/plugin/trino-hudi/pom.xml @@ -187,12 +187,6 @@ runtime - - io.trino - trino-hadoop-toolkit - runtime - - io.trino.hadoop hadoop-apache diff --git a/plugin/trino-iceberg/pom.xml b/plugin/trino-iceberg/pom.xml index 76ed1523dc9f..f7665e2b351e 100644 --- a/plugin/trino-iceberg/pom.xml +++ b/plugin/trino-iceberg/pom.xml @@ -348,12 +348,6 @@ runtime - - io.trino - trino-hadoop-toolkit - runtime - - io.trino.hadoop hadoop-apache diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergRegisterTableProcedure.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergRegisterTableProcedure.java index e739d576b140..079f9af759d5 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergRegisterTableProcedure.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergRegisterTableProcedure.java @@ -23,6 +23,7 @@ import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.MaterializedResult; import io.trino.testing.QueryRunner; +import org.apache.hadoop.conf.Configuration; import org.apache.iceberg.DataFile; import org.apache.iceberg.DataFiles; import org.apache.iceberg.FileFormat; @@ -48,7 +49,6 @@ import static com.google.common.base.Verify.verify; import static com.google.common.io.MoreFiles.deleteRecursively; import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; -import static io.trino.hadoop.ConfigurationInstantiator.newEmptyConfiguration; import static io.trino.plugin.hive.metastore.file.TestingFileHiveMetastore.createTestingFileHiveMetastore; import static io.trino.plugin.iceberg.IcebergTestUtils.getFileSystemFactory; import static io.trino.plugin.iceberg.IcebergUtil.METADATA_FOLDER_NAME; @@ -449,7 +449,7 @@ public void testRegisterHadoopTableAndRead() // create hadoop table String hadoopTableName = "hadoop_table_" + randomNameSuffix(); String hadoopTableLocation = metastoreDir.getPath() + "/" + hadoopTableName; - HadoopTables hadoopTables = new HadoopTables(newEmptyConfiguration()); + HadoopTables hadoopTables = new HadoopTables(new Configuration(false)); Schema schema = new Schema(ImmutableList.of( Types.NestedField.optional(1, "id", Types.IntegerType.get()), Types.NestedField.optional(2, "name", Types.StringType.get()))); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/jdbc/TestIcebergJdbcCatalogConnectorSmokeTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/jdbc/TestIcebergJdbcCatalogConnectorSmokeTest.java index 12f5e9d098f7..48bcad1a075e 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/jdbc/TestIcebergJdbcCatalogConnectorSmokeTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/jdbc/TestIcebergJdbcCatalogConnectorSmokeTest.java @@ -15,13 +15,13 @@ import com.google.common.collect.ImmutableMap; import io.trino.filesystem.Location; -import io.trino.hadoop.ConfigurationInstantiator; import io.trino.plugin.iceberg.BaseIcebergConnectorSmokeTest; import io.trino.plugin.iceberg.IcebergConfig; import io.trino.plugin.iceberg.IcebergQueryRunner; import io.trino.testing.QueryRunner; import io.trino.testing.TestingConnectorBehavior; import io.trino.testng.services.ManageTestResources; +import org.apache.hadoop.conf.Configuration; import org.apache.iceberg.BaseTable; import org.apache.iceberg.catalog.TableIdentifier; import org.apache.iceberg.jdbc.JdbcCatalog; @@ -92,7 +92,7 @@ protected QueryRunner createQueryRunner() .put(PROPERTY_PREFIX + "password", PASSWORD) .put(WAREHOUSE_LOCATION, warehouseLocation.getAbsolutePath()) .buildOrThrow(), - ConfigurationInstantiator.newEmptyConfiguration()); + new Configuration(false)); return IcebergQueryRunner.builder() .setIcebergProperties( ImmutableMap.builder() diff --git a/plugin/trino-phoenix5/pom.xml b/plugin/trino-phoenix5/pom.xml index f608cd113b80..61204e245095 100644 --- a/plugin/trino-phoenix5/pom.xml +++ b/plugin/trino-phoenix5/pom.xml @@ -89,11 +89,6 @@ joda-time - - org.gaul - modernizer-maven-annotations - - org.weakref jmxutils diff --git a/plugin/trino-phoenix5/src/main/java/io/trino/plugin/phoenix5/ConfigurationInstantiator.java b/plugin/trino-phoenix5/src/main/java/io/trino/plugin/phoenix5/ConfigurationInstantiator.java index facaa810c483..30a39be1c608 100644 --- a/plugin/trino-phoenix5/src/main/java/io/trino/plugin/phoenix5/ConfigurationInstantiator.java +++ b/plugin/trino-phoenix5/src/main/java/io/trino/plugin/phoenix5/ConfigurationInstantiator.java @@ -15,7 +15,6 @@ import io.trino.spi.classloader.ThreadContextClassLoader; import org.apache.hadoop.conf.Configuration; -import org.gaul.modernizer_maven_annotations.SuppressModernizer; final class ConfigurationInstantiator { @@ -23,19 +22,8 @@ private ConfigurationInstantiator() {} public static Configuration newEmptyConfiguration() { - // Ensure that the context class loader used while instantiating the `Configuration` object corresponds to the - // class loader of the `ConfigurationInstantiator` try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(ConfigurationInstantiator.class.getClassLoader())) { - return newConfigurationWithTccl(); + return new Configuration(false); } } - - // Usage of `new Configuration(boolean)` is not allowed. Only ConfigurationInstantiator - // can instantiate Configuration directly. Suppress the violation so that we can use it here. - @SuppressModernizer - private static Configuration newConfigurationWithTccl() - { - // Note: the Configuration captures current thread context class loader (TCCL), so it may or may not be generally usable. - return new Configuration(false); - } } diff --git a/pom.xml b/pom.xml index 2f7ae7e4cc20..ce89c0403a04 100644 --- a/pom.xml +++ b/pom.xml @@ -46,7 +46,6 @@ lib/trino-filesystem-manager lib/trino-filesystem-s3 lib/trino-geospatial-toolkit - lib/trino-hadoop-toolkit lib/trino-hdfs lib/trino-hive-formats @@ -1023,12 +1022,6 @@ ${project.version} - - io.trino - trino-hadoop-toolkit - ${project.version} - - io.trino trino-hdfs From 2d81975a3558b2ffc4c26fc8699cb27720ac5833 Mon Sep 17 00:00:00 2001 From: chenjian2664 Date: Wed, 1 Nov 2023 14:45:31 +0800 Subject: [PATCH 018/587] Fix kafka protoc compile error This fixes the build on M1 Macs by explicitly specifying the protoc artifact which has support for M1 Macs instead of using the default version specified by the protoc-jar-maven-plugin. Co-authored-by: Mateusz "Serafin" Gajewski --- plugin/trino-kafka/pom.xml | 1 + pom.xml | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/plugin/trino-kafka/pom.xml b/plugin/trino-kafka/pom.xml index 672b210f7cae..62abe96ba5d6 100644 --- a/plugin/trino-kafka/pom.xml +++ b/plugin/trino-kafka/pom.xml @@ -405,6 +405,7 @@ generate-test-sources + com.google.protobuf:protoc:${dep.protobuf.version} ${dep.protobuf.version} none diff --git a/pom.xml b/pom.xml index ce89c0403a04..68254813bb34 100644 --- a/pom.xml +++ b/pom.xml @@ -2442,6 +2442,22 @@ jar MAIN + + com.google.protobuf + protoc + exe + linux-x86_64 + ${dep.protobuf.version} + MAIN + + + com.google.protobuf + protoc + exe + linux-aarch_64 + ${dep.protobuf.version} + MAIN + From e6c145971f02e04d9ffeb8a1c88adbc3f5cc03d9 Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Wed, 1 Nov 2023 11:38:26 +0900 Subject: [PATCH 019/587] Support register_table and unregister_table procedure in Iceberg REST --- .../catalog/rest/TrinoRestCatalog.java | 6 +- ...ergTrinoRestCatalogConnectorSmokeTest.java | 99 ++----------------- 2 files changed, 13 insertions(+), 92 deletions(-) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/rest/TrinoRestCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/rest/TrinoRestCatalog.java index 527a7d6603b5..a2083f33e362 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/rest/TrinoRestCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/rest/TrinoRestCatalog.java @@ -257,13 +257,15 @@ public Transaction newCreateOrReplaceTableTransaction( @Override public void registerTable(ConnectorSession session, SchemaTableName tableName, TableMetadata tableMetadata) { - throw new TrinoException(NOT_SUPPORTED, "registerTable is not supported for Iceberg REST catalog"); + restSessionCatalog.registerTable(convert(session), toIdentifier(tableName), tableMetadata.metadataFileLocation()); } @Override public void unregisterTable(ConnectorSession session, SchemaTableName tableName) { - throw new TrinoException(NOT_SUPPORTED, "unregisterTable is not supported for Iceberg REST catalogs"); + if (!restSessionCatalog.dropTable(convert(session), toIdentifier(tableName))) { + throw new TableNotFoundException(tableName); + } } @Override diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestIcebergTrinoRestCatalogConnectorSmokeTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestIcebergTrinoRestCatalogConnectorSmokeTest.java index 83531c14ae12..7cc4994a426a 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestIcebergTrinoRestCatalogConnectorSmokeTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestIcebergTrinoRestCatalogConnectorSmokeTest.java @@ -33,6 +33,8 @@ import org.junit.jupiter.api.TestInstance; import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.file.Path; import java.util.Optional; @@ -138,7 +140,7 @@ public void testRenameSchema() @Override protected void dropTableFromMetastore(String tableName) { - // used when registering a table, which is not supported by the REST catalog + backend.dropTable(toIdentifier(tableName), false); } @Override @@ -160,94 +162,6 @@ protected boolean locationExists(String location) return java.nio.file.Files.exists(Path.of(location)); } - @Test - @Override - public void testRegisterTableWithTableLocation() - { - assertThatThrownBy(super::testRegisterTableWithTableLocation) - .hasMessageContaining("registerTable is not supported for Iceberg REST catalog"); - } - - @Test - @Override - public void testRegisterTableWithComments() - { - assertThatThrownBy(super::testRegisterTableWithComments) - .hasMessageContaining("registerTable is not supported for Iceberg REST catalog"); - } - - @Test - @Override - public void testRegisterTableWithShowCreateTable() - { - assertThatThrownBy(super::testRegisterTableWithShowCreateTable) - .hasMessageContaining("registerTable is not supported for Iceberg REST catalog"); - } - - @Test - @Override - public void testRegisterTableWithReInsert() - { - assertThatThrownBy(super::testRegisterTableWithReInsert) - .hasMessageContaining("registerTable is not supported for Iceberg REST catalog"); - } - - @Test - @Override - public void testRegisterTableWithDifferentTableName() - { - assertThatThrownBy(super::testRegisterTableWithDifferentTableName) - .hasMessageContaining("registerTable is not supported for Iceberg REST catalog"); - } - - @Test - @Override - public void testRegisterTableWithMetadataFile() - { - assertThatThrownBy(super::testRegisterTableWithMetadataFile) - .hasMessageContaining("registerTable is not supported for Iceberg REST catalog"); - } - - @Test - @Override - public void testRegisterTableWithTrailingSpaceInLocation() - { - assertThatThrownBy(super::testRegisterTableWithTrailingSpaceInLocation) - .hasMessageContaining("registerTable is not supported for Iceberg REST catalog"); - } - - @Test - @Override - public void testUnregisterTable() - { - assertThatThrownBy(super::testUnregisterTable) - .hasMessageContaining("unregisterTable is not supported for Iceberg REST catalogs"); - } - - @Test - @Override - public void testUnregisterBrokenTable() - { - assertThatThrownBy(super::testUnregisterBrokenTable) - .hasMessageContaining("unregisterTable is not supported for Iceberg REST catalogs"); - } - - @Test - @Override - public void testUnregisterTableNotExistingTable() - { - assertThatThrownBy(super::testUnregisterTableNotExistingTable) - .hasMessageContaining("unregisterTable is not supported for Iceberg REST catalogs"); - } - - @Test - @Override - public void testRepeatUnregisterTable() - { - assertThatThrownBy(super::testRepeatUnregisterTable) - .hasMessageContaining("unregisterTable is not supported for Iceberg REST catalogs"); - } - @Test @Override public void testDropTableWithMissingMetadataFile() @@ -300,7 +214,12 @@ protected boolean isFileSorted(Location path, String sortColumnName) @Override protected void deleteDirectory(String location) { - // used when unregistering a table, which is not supported by the REST catalog + try { + deleteRecursively(Path.of(location), ALLOW_INSECURE); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } } private TableIdentifier toIdentifier(String tableName) From cd74f89bbfca6f9ca3b052a8e81148180663d0d6 Mon Sep 17 00:00:00 2001 From: Guy Cohen Date: Sun, 29 Oct 2023 15:34:46 +0200 Subject: [PATCH 020/587] Add tests for inline function --- .../io/trino/testing/BaseConnectorTest.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java b/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java index 031fc152a3fa..5df37b250bc7 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java @@ -6586,6 +6586,27 @@ public void testCreateFunction() assertUpdate("DROP FUNCTION " + recursive2 + "(integer)"); } + @Test + public void testInlineFunction() { + assertQuery(""" + WITH FUNCTION my_func(x bigint) RETURNS bigint RETURN x * 2 + SELECT my_func(nationkey) FROM tpch.tiny.nation WHERE nationkey = 1 + """, + "SELECT 2"); + + assertQuery(""" + WITH FUNCTION my_func(x bigint) RETURNS bigint RETURN x * 2 + SELECT my_func(nationkey) FROM tpch.tiny.nation WHERE nationkey >= 1 + """, + "SELECT nationkey * 2 FROM nation WHERE nationkey >= 1"); + + assertQuery(""" + WITH FUNCTION my_func(x bigint) RETURNS bigint RETURN x * 2 + SELECT my_func(nationkey) FROM tpch.tiny.nation + """, + "SELECT nationkey * 2 FROM nation"); + } + @Test public void testProjectionPushdown() { From e199a23a7e142a4f333c4049fdf1644eda7048f4 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Fri, 27 Oct 2023 16:13:28 -0700 Subject: [PATCH 021/587] Remove improper check in analyzer The isTopLevel argument is meant to indicate whether the statement is at the top of the syntax tree. Inline functions expect to be at the top of the SELECT tree, which might be nested under a CreateTable statement. --- .../src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java index e6771a68affb..8b1c9b25876b 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java @@ -1517,7 +1517,6 @@ protected Scope visitExplainAnalyze(ExplainAnalyze node, Optional scope) @Override protected Scope visitQuery(Query node, Optional scope) { - verify(isTopLevel || node.getFunctions().isEmpty(), "Inline functions must be at the top level"); for (FunctionSpecification function : node.getFunctions()) { if (function.getName().getPrefix().isPresent()) { throw semanticException(SYNTAX_ERROR, function, "Inline function names cannot be qualified: " + function.getName()); From 87f405abfda500a544ee399ca7dc768d1f165388 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Fri, 27 Oct 2023 06:32:40 -0700 Subject: [PATCH 022/587] Remove redundant ORDER BY in CREATE TABLE AS queries --- .../src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java index 8b1c9b25876b..2d74e0a8fbc1 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java @@ -928,7 +928,7 @@ protected Scope visitCreateTableAsSelect(CreateTableAsSelect node, Optional columnsBuilder = ImmutableList.builder(); From b782669ce9580e4f79808249c20885d29907d520 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Wed, 1 Nov 2023 16:52:50 +0100 Subject: [PATCH 023/587] Update JLine to 3.24.1 --- client/trino-cli/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/trino-cli/pom.xml b/client/trino-cli/pom.xml index ca48c0314080..4233e8108017 100644 --- a/client/trino-cli/pom.xml +++ b/client/trino-cli/pom.xml @@ -15,7 +15,7 @@ ${project.parent.basedir} 8 io.trino.cli.Trino - 3.24.0 + 3.24.1 From dc9ed2c58fb608b3cba144b7c8bf32adfaa762cf Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Wed, 1 Nov 2023 10:56:52 -0700 Subject: [PATCH 024/587] Move inline function test to dedicated class It's unnecessary to test a core engine feature against every connector. It makes the tests suite take longer unnecessarily and doesn't provide additional value. --- .../trino/sql/query/TestInlineFunctions.java | 94 +++++++++++++++++++ .../io/trino/testing/BaseConnectorTest.java | 21 ----- 2 files changed, 94 insertions(+), 21 deletions(-) create mode 100644 core/trino-main/src/test/java/io/trino/sql/query/TestInlineFunctions.java diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestInlineFunctions.java b/core/trino-main/src/test/java/io/trino/sql/query/TestInlineFunctions.java new file mode 100644 index 000000000000..98bdfea928be --- /dev/null +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestInlineFunctions.java @@ -0,0 +1,94 @@ +/* + * 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 io.trino.sql.query; + +import com.google.common.collect.ImmutableMap; +import io.trino.Session; +import io.trino.plugin.tpch.TpchConnectorFactory; +import io.trino.testing.LocalQueryRunner; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; + +import static io.trino.plugin.tpch.TpchMetadata.TINY_SCHEMA_NAME; +import static io.trino.testing.TestingHandles.TEST_CATALOG_NAME; +import static io.trino.testing.TestingSession.testSessionBuilder; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; + +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) +public class TestInlineFunctions +{ + private final QueryAssertions assertions; + + public TestInlineFunctions() + { + Session session = testSessionBuilder() + .setCatalog(TEST_CATALOG_NAME) + .setSchema(TINY_SCHEMA_NAME) + .build(); + + LocalQueryRunner runner = LocalQueryRunner.builder(session) + .build(); + + runner.createCatalog(TEST_CATALOG_NAME, new TpchConnectorFactory(1), ImmutableMap.of()); + + assertions = new QueryAssertions(runner); + } + + @AfterAll + public void teardown() + { + assertions.close(); + } + + @Test + public void testInlineFunction() + { + assertThat(assertions.query( + """ + WITH FUNCTION my_func(x bigint) + RETURNS bigint + RETURN x * 2 + SELECT my_func(nationkey) + FROM nation + WHERE nationkey = 1 + """)) + .matches("VALUES BIGINT '2'"); + + assertThat(assertions.query( + """ + WITH FUNCTION my_func(x bigint) + RETURNS bigint + RETURN x * 2 + SELECT my_func(nationkey) + FROM nation + WHERE nationkey >= 1 + """)) + .matches("SELECT nationkey * 2 FROM nation WHERE nationkey >= 1"); + + assertThat(assertions.query( + """ + WITH FUNCTION my_func(x bigint) + RETURNS bigint + RETURN x * 2 + SELECT my_func(nationkey) + FROM nation + """)) + .matches("SELECT nationkey * 2 FROM nation"); + } +} diff --git a/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java b/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java index 5df37b250bc7..031fc152a3fa 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java @@ -6586,27 +6586,6 @@ public void testCreateFunction() assertUpdate("DROP FUNCTION " + recursive2 + "(integer)"); } - @Test - public void testInlineFunction() { - assertQuery(""" - WITH FUNCTION my_func(x bigint) RETURNS bigint RETURN x * 2 - SELECT my_func(nationkey) FROM tpch.tiny.nation WHERE nationkey = 1 - """, - "SELECT 2"); - - assertQuery(""" - WITH FUNCTION my_func(x bigint) RETURNS bigint RETURN x * 2 - SELECT my_func(nationkey) FROM tpch.tiny.nation WHERE nationkey >= 1 - """, - "SELECT nationkey * 2 FROM nation WHERE nationkey >= 1"); - - assertQuery(""" - WITH FUNCTION my_func(x bigint) RETURNS bigint RETURN x * 2 - SELECT my_func(nationkey) FROM tpch.tiny.nation - """, - "SELECT nationkey * 2 FROM nation"); - } - @Test public void testProjectionPushdown() { From 08256da5eda4dc6fc9f0b47b8ff7aa3566f406b0 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Tue, 31 Oct 2023 11:52:04 -0700 Subject: [PATCH 025/587] Update file-based function security rules Update documentation of default function permission Document permissions for CREATE/DROP function --- .../security/file-system-access-control.md | 101 +++++++++++------- 1 file changed, 62 insertions(+), 39 deletions(-) diff --git a/docs/src/main/sphinx/security/file-system-access-control.md b/docs/src/main/sphinx/security/file-system-access-control.md index 64ef7a615471..f8cdf5612208 100644 --- a/docs/src/main/sphinx/security/file-system-access-control.md +++ b/docs/src/main/sphinx/security/file-system-access-control.md @@ -126,13 +126,15 @@ Permissions required for executing functions: * - `SELECT function()` - - `execute`, `grant_execute*` - - `grant_execute` is required when the function is used in a `SECURITY DEFINER` - view. -* - `SELECT FROM TABLE(table_function())` + - `grant_execute` is required when the function is used in a `SECURITY DEFINER` view. +* - `CREATE FUNCTION` - `all` - - `execute`, `grant_execute*` - - `grant_execute` is required when the function is used in a `SECURITY DEFINER` - view. + - `ownership` + - Not all connectors support [catalog routines](routine-catalog). +* - `DROP FUNCTION` + - `all` + - `ownership` + - Not all connectors support [catalog routines](routine-catalog). ::: (system-file-auth-visibility)= @@ -358,7 +360,12 @@ The example below defines the following table access policy: #### Function rules -These rules control the user's ability to execute functions. +These rules control the ability of a user to create, drop, and execute functions. + +When these rules are present, the authorization is based on the first matching +rule, processed from top to bottom. If no rules match, the authorization is +denied. If function rules are not present, only functions in`system.builtin` can +be executed. :::{note} Users always have access to functions in the `system.builtin` schema, and @@ -379,45 +386,20 @@ Each function rule is composed of the following fields: Defaults to `.*`. - `function` (optional): regular expression to match against function names. Defaults to `.*`. -- `privileges` (required): zero or more of `EXECUTE`, `GRANT_EXECUTE`. - -To explicitly allow the system builtin functions in queries (and SECURITY -DEFINER views), you can use the following rule: - -```json -{ - "functions": [ - { - "catalog": "system", - "schema": "builtin", - "privileges": [ - "EXECUTE", - "GRANT_EXECUTE" - ] - } - ] -} -``` +- `privileges` (required): zero or more of `EXECUTE`, `GRANT_EXECUTE`, `OWNERSHIP`. -Care should be taken when granting permission to the `system` schema of any +Care should be taken when granting permission to the `system` schema of a catalog, as this is the schema Trino uses for table function such as `query`. These table functions can be used to access or modify the underlying data of the catalog. -The following example allows the `admin` user to execute `query` table -function from any catalog: +The following example allows the `admin` user to execute `system.query` table function in +any catalog, and allows all users to create, drop, and execute functions (including +`SECURITY DEFINER` views) in the `hive.function` schema: ```json { "functions": [ - { - "catalog": "system", - "schema": "builtin", - "privileges": [ - "EXECUTE", - "GRANT_EXECUTE" - ] - }, { "user": "admin", "schema": "system", @@ -425,6 +407,13 @@ function from any catalog: "privileges": [ "EXECUTE" ] + }, + { + "catalog": "hive", + "schema": "function", + "privileges": [ + "EXECUTE", "GRANT_EXECUTE", "OWNERSHIP" + ] } ] } @@ -816,7 +805,11 @@ These rules apply to `filter_environment` and `mask_environment`. #### Function rules -Each function rule is composed of the following fields: +These rules control the ability of a user to create, drop, and execute functions. + +When these rules are present, the authorization is based on the first matching +rule, processed from top to bottom. If no rules match, the authorization is +denied. If function rules are not present, access is not allowed. - `user` (optional): regular expression to match against user name. Defaults to `.*`. @@ -826,7 +819,37 @@ Each function rule is composed of the following fields: Defaults to `.*`. - `function` (optional): regular expression to match against function names. Defaults to `.*`. -- `privileges` (required): zero or more of `EXECUTE`, `GRANT_EXECUTE`. +- `privileges` (required): zero or more of `EXECUTE`, `GRANT_EXECUTE`, `OWNERSHIP`. + +Care should be taken when granting permission to the `system` schema of a +catalog, as this is the schema Trino uses for table function such as `query`. +These table functions can be used to access or modify the underlying data of +the catalog. + +The following example allows the `admin` user to execute `system.query` table function from +any catalog, and all users to create, drop, and execute functions (including from views) +in the `function` schema of this catalog: + +```json +{ + "functions": [ + { + "user": "admin", + "schema": "system", + "function": "query", + "privileges": [ + "EXECUTE" + ] + }, + { + "schema": "function", + "privileges": [ + "EXECUTE", "GRANT_EXECUTE", "OWNERSHIP" + ] + } + ] +} +``` #### Session property rules From bc5026ac82596b5dfaa1998f001d4dc5bb5926f9 Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Tue, 31 Oct 2023 13:15:36 +0900 Subject: [PATCH 026/587] Static import type constants in CheckpointSchemaManager --- .../checkpoint/CheckpointSchemaManager.java | 100 +++++++++--------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointSchemaManager.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointSchemaManager.java index eaea241a3291..290d66081fc0 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointSchemaManager.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointSchemaManager.java @@ -19,17 +19,12 @@ import io.trino.plugin.deltalake.transactionlog.MetadataEntry; import io.trino.plugin.deltalake.transactionlog.ProtocolEntry; import io.trino.spi.type.ArrayType; -import io.trino.spi.type.BigintType; -import io.trino.spi.type.BooleanType; -import io.trino.spi.type.IntegerType; import io.trino.spi.type.MapType; import io.trino.spi.type.RowType; -import io.trino.spi.type.TimestampType; import io.trino.spi.type.TimestampWithTimeZoneType; import io.trino.spi.type.Type; import io.trino.spi.type.TypeManager; import io.trino.spi.type.TypeSignature; -import io.trino.spi.type.VarcharType; import java.util.List; import java.util.Optional; @@ -38,6 +33,11 @@ import static io.trino.plugin.deltalake.transactionlog.DeltaLakeSchemaSupport.extractSchema; import static io.trino.plugin.deltalake.transactionlog.DeltaLakeSchemaSupport.isDeletionVectorEnabled; import static io.trino.plugin.deltalake.transactionlog.TransactionLogAccess.columnsWithStats; +import static io.trino.spi.type.BigintType.BIGINT; +import static io.trino.spi.type.BooleanType.BOOLEAN; +import static io.trino.spi.type.IntegerType.INTEGER; +import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; +import static io.trino.spi.type.VarcharType.VARCHAR; import static java.util.Objects.requireNonNull; public class CheckpointSchemaManager @@ -45,22 +45,22 @@ public class CheckpointSchemaManager private final TypeManager typeManager; private static final RowType DELETION_VECTORS_TYPE = RowType.from(ImmutableList.builder() - .add(RowType.field("storageType", VarcharType.VARCHAR)) - .add(RowType.field("pathOrInlineDv", VarcharType.VARCHAR)) - .add(RowType.field("offset", IntegerType.INTEGER)) - .add(RowType.field("sizeInBytes", IntegerType.INTEGER)) - .add(RowType.field("cardinality", BigintType.BIGINT)) + .add(RowType.field("storageType", VARCHAR)) + .add(RowType.field("pathOrInlineDv", VARCHAR)) + .add(RowType.field("offset", INTEGER)) + .add(RowType.field("sizeInBytes", INTEGER)) + .add(RowType.field("cardinality", BIGINT)) .build()); private static final RowType TXN_ENTRY_TYPE = RowType.from(ImmutableList.of( - RowType.field("appId", VarcharType.createUnboundedVarcharType()), - RowType.field("version", BigintType.BIGINT), - RowType.field("lastUpdated", BigintType.BIGINT))); + RowType.field("appId", VARCHAR), + RowType.field("version", BIGINT), + RowType.field("lastUpdated", BIGINT))); private static final RowType REMOVE_ENTRY_TYPE = RowType.from(ImmutableList.of( - RowType.field("path", VarcharType.createUnboundedVarcharType()), - RowType.field("deletionTimestamp", BigintType.BIGINT), - RowType.field("dataChange", BooleanType.BOOLEAN))); + RowType.field("path", VARCHAR), + RowType.field("deletionTimestamp", BIGINT), + RowType.field("dataChange", BOOLEAN))); private final RowType metadataEntryType; private final RowType commitInfoEntryType; @@ -71,40 +71,40 @@ public CheckpointSchemaManager(TypeManager typeManager) { this.typeManager = requireNonNull(typeManager, "typeManager is null"); - stringList = (ArrayType) this.typeManager.getType(TypeSignature.arrayType(VarcharType.VARCHAR.getTypeSignature())); - MapType stringMap = (MapType) this.typeManager.getType(TypeSignature.mapType(VarcharType.VARCHAR.getTypeSignature(), VarcharType.VARCHAR.getTypeSignature())); + stringList = (ArrayType) this.typeManager.getType(TypeSignature.arrayType(VARCHAR.getTypeSignature())); + MapType stringMap = (MapType) this.typeManager.getType(TypeSignature.mapType(VARCHAR.getTypeSignature(), VARCHAR.getTypeSignature())); metadataEntryType = RowType.from(ImmutableList.of( - RowType.field("id", VarcharType.createUnboundedVarcharType()), - RowType.field("name", VarcharType.createUnboundedVarcharType()), - RowType.field("description", VarcharType.createUnboundedVarcharType()), + RowType.field("id", VARCHAR), + RowType.field("name", VARCHAR), + RowType.field("description", VARCHAR), RowType.field("format", RowType.from(ImmutableList.of( - RowType.field("provider", VarcharType.createUnboundedVarcharType()), + RowType.field("provider", VARCHAR), RowType.field("options", stringMap)))), - RowType.field("schemaString", VarcharType.createUnboundedVarcharType()), + RowType.field("schemaString", VARCHAR), RowType.field("partitionColumns", stringList), RowType.field("configuration", stringMap), - RowType.field("createdTime", BigintType.BIGINT))); + RowType.field("createdTime", BIGINT))); commitInfoEntryType = RowType.from(ImmutableList.of( - RowType.field("version", BigintType.BIGINT), - RowType.field("timestamp", TimestampType.TIMESTAMP_MILLIS), - RowType.field("userId", VarcharType.createUnboundedVarcharType()), - RowType.field("userName", VarcharType.createUnboundedVarcharType()), - RowType.field("operation", VarcharType.createUnboundedVarcharType()), + RowType.field("version", BIGINT), + RowType.field("timestamp", TIMESTAMP_MILLIS), + RowType.field("userId", VARCHAR), + RowType.field("userName", VARCHAR), + RowType.field("operation", VARCHAR), RowType.field("operationParameters", stringMap), RowType.field("job", RowType.from(ImmutableList.of( - RowType.field("jobId", VarcharType.createUnboundedVarcharType()), - RowType.field("jobName", VarcharType.createUnboundedVarcharType()), - RowType.field("runId", VarcharType.createUnboundedVarcharType()), - RowType.field("jobOwnerId", VarcharType.createUnboundedVarcharType()), - RowType.field("triggerType", VarcharType.createUnboundedVarcharType())))), + RowType.field("jobId", VARCHAR), + RowType.field("jobName", VARCHAR), + RowType.field("runId", VARCHAR), + RowType.field("jobOwnerId", VARCHAR), + RowType.field("triggerType", VARCHAR)))), RowType.field("notebook", RowType.from( - ImmutableList.of(RowType.field("notebookId", VarcharType.createUnboundedVarcharType())))), - RowType.field("clusterId", VarcharType.createUnboundedVarcharType()), - RowType.field("readVersion", BigintType.BIGINT), - RowType.field("isolationLevel", VarcharType.createUnboundedVarcharType()), - RowType.field("isBlindAppend", BooleanType.BOOLEAN))); + ImmutableList.of(RowType.field("notebookId", VARCHAR)))), + RowType.field("clusterId", VARCHAR), + RowType.field("readVersion", BIGINT), + RowType.field("isolationLevel", VARCHAR), + RowType.field("isBlindAppend", BOOLEAN))); } public RowType getMetadataEntryType() @@ -122,7 +122,7 @@ public RowType getAddEntryType(MetadataEntry metadataEntry, ProtocolEntry protoc for (DeltaLakeColumnMetadata dataColumn : minMaxColumns) { Type type = dataColumn.getPhysicalColumnType(); if (type instanceof TimestampWithTimeZoneType) { - minMaxFields.add(RowType.field(dataColumn.getPhysicalName(), TimestampType.TIMESTAMP_MILLIS)); + minMaxFields.add(RowType.field(dataColumn.getPhysicalName(), TIMESTAMP_MILLIS)); } else { minMaxFields.add(RowType.field(dataColumn.getPhysicalName(), type)); @@ -130,7 +130,7 @@ public RowType getAddEntryType(MetadataEntry metadataEntry, ProtocolEntry protoc } ImmutableList.Builder statsColumns = ImmutableList.builder(); - statsColumns.add(RowType.field("numRecords", BigintType.BIGINT)); + statsColumns.add(RowType.field("numRecords", BIGINT)); List minMax = minMaxFields.build(); if (!minMax.isEmpty()) { @@ -143,18 +143,18 @@ public RowType getAddEntryType(MetadataEntry metadataEntry, ProtocolEntry protoc "nullCount", RowType.from(allColumns.stream().map(column -> buildNullCountType(Optional.of(column.getPhysicalName()), column.getPhysicalColumnType())).collect(toImmutableList())))); - MapType stringMap = (MapType) typeManager.getType(TypeSignature.mapType(VarcharType.VARCHAR.getTypeSignature(), VarcharType.VARCHAR.getTypeSignature())); + MapType stringMap = (MapType) typeManager.getType(TypeSignature.mapType(VARCHAR.getTypeSignature(), VARCHAR.getTypeSignature())); ImmutableList.Builder addFields = ImmutableList.builder(); - addFields.add(RowType.field("path", VarcharType.createUnboundedVarcharType())); + addFields.add(RowType.field("path", VARCHAR)); addFields.add(RowType.field("partitionValues", stringMap)); - addFields.add(RowType.field("size", BigintType.BIGINT)); - addFields.add(RowType.field("modificationTime", BigintType.BIGINT)); - addFields.add(RowType.field("dataChange", BooleanType.BOOLEAN)); + addFields.add(RowType.field("size", BIGINT)); + addFields.add(RowType.field("modificationTime", BIGINT)); + addFields.add(RowType.field("dataChange", BOOLEAN)); if (deletionVectorEnabled) { addFields.add(RowType.field("deletionVector", DELETION_VECTORS_TYPE)); } if (requireWriteStatsAsJson) { - addFields.add(RowType.field("stats", VarcharType.createUnboundedVarcharType())); + addFields.add(RowType.field("stats", VARCHAR)); } if (requireWriteStatsAsStruct) { addFields.add(RowType.field("stats_parsed", RowType.from(statsColumns.build()))); @@ -173,7 +173,7 @@ private static RowType.Field buildNullCountType(Optional columnName, Typ .collect(toImmutableList())); return new RowType.Field(columnName, rowTypeFromFields); } - return new RowType.Field(columnName, BigintType.BIGINT); + return new RowType.Field(columnName, BIGINT); } public RowType getRemoveEntryType() @@ -189,8 +189,8 @@ public RowType getTxnEntryType() public RowType getProtocolEntryType(boolean requireReaderFeatures, boolean requireWriterFeatures) { ImmutableList.Builder fields = ImmutableList.builder(); - fields.add(RowType.field("minReaderVersion", IntegerType.INTEGER)); - fields.add(RowType.field("minWriterVersion", IntegerType.INTEGER)); + fields.add(RowType.field("minReaderVersion", INTEGER)); + fields.add(RowType.field("minWriterVersion", INTEGER)); if (requireReaderFeatures) { fields.add(RowType.field("readerFeatures", stringList)); } From 74ea6e288db67023fc35901e7aa01656de0b89a5 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Wed, 1 Nov 2023 13:09:47 -0700 Subject: [PATCH 027/587] Move Hadoop filesystem modules into filesystem manager --- .../trino/filesystem/manager/FileSystemModule.java | 12 ++++++++++-- plugin/trino-delta-lake/pom.xml | 11 ++++++----- .../deltalake/InternalDeltaLakeConnectorFactory.java | 6 ------ .../metastore/glue/TestDeltaLakeGlueMetastore.java | 3 --- .../plugin/hive/InternalHiveConnectorFactory.java | 8 -------- plugin/trino-hudi/pom.xml | 11 ++++++----- .../plugin/hudi/InternalHudiConnectorFactory.java | 6 ------ plugin/trino-iceberg/pom.xml | 11 ++++++----- .../iceberg/InternalIcebergConnectorFactory.java | 6 ------ 9 files changed, 28 insertions(+), 46 deletions(-) diff --git a/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/FileSystemModule.java b/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/FileSystemModule.java index 362f98f796b6..b7c1c35e5f75 100644 --- a/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/FileSystemModule.java +++ b/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/FileSystemModule.java @@ -27,7 +27,11 @@ import io.trino.filesystem.s3.S3FileSystemFactory; import io.trino.filesystem.s3.S3FileSystemModule; import io.trino.filesystem.tracing.TracingFileSystemFactory; +import io.trino.hdfs.HdfsModule; +import io.trino.hdfs.authentication.HdfsAuthenticationModule; import io.trino.hdfs.azure.HiveAzureModule; +import io.trino.hdfs.cos.HiveCosModule; +import io.trino.hdfs.gcs.HiveGcsModule; import io.trino.hdfs.s3.HiveS3Module; import java.util.Map; @@ -48,6 +52,10 @@ protected void setup(Binder binder) if (config.isHadoopEnabled()) { install(new HdfsFileSystemModule()); + install(new HdfsModule()); + install(new HdfsAuthenticationModule()); + install(new HiveCosModule()); + install(new HiveGcsModule()); } var factories = newMapBinder(binder, String.class, TrinoFileSystemFactory.class); @@ -57,7 +65,7 @@ protected void setup(Binder binder) factories.addBinding("abfs").to(AzureFileSystemFactory.class); factories.addBinding("abfss").to(AzureFileSystemFactory.class); } - else { + else if (config.isHadoopEnabled()) { install(new HiveAzureModule()); } @@ -67,7 +75,7 @@ protected void setup(Binder binder) factories.addBinding("s3a").to(S3FileSystemFactory.class); factories.addBinding("s3n").to(S3FileSystemFactory.class); } - else { + else if (config.isHadoopEnabled()) { install(new HiveS3Module()); } } diff --git a/plugin/trino-delta-lake/pom.xml b/plugin/trino-delta-lake/pom.xml index e784443e51c8..8120ba4218c6 100644 --- a/plugin/trino-delta-lake/pom.xml +++ b/plugin/trino-delta-lake/pom.xml @@ -128,11 +128,6 @@ trino-filesystem-manager - - io.trino - trino-hdfs - - io.trino trino-hive @@ -268,6 +263,12 @@ runtime + + io.trino + trino-hdfs + runtime + + io.trino trino-memory-context diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/InternalDeltaLakeConnectorFactory.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/InternalDeltaLakeConnectorFactory.java index 5285c6c74f12..a9379152401b 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/InternalDeltaLakeConnectorFactory.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/InternalDeltaLakeConnectorFactory.java @@ -26,9 +26,6 @@ import io.opentelemetry.api.trace.Tracer; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.filesystem.manager.FileSystemModule; -import io.trino.hdfs.HdfsModule; -import io.trino.hdfs.authentication.HdfsAuthenticationModule; -import io.trino.hdfs.gcs.HiveGcsModule; import io.trino.plugin.base.CatalogName; import io.trino.plugin.base.CatalogNameModule; import io.trino.plugin.base.classloader.ClassLoaderSafeConnectorAccessControl; @@ -87,9 +84,6 @@ public static Connector createConnector( new ConnectorObjectNameGeneratorModule("io.trino.plugin.deltalake", "trino.plugin.deltalake"), new JsonModule(), new MBeanServerModule(), - new HdfsModule(), - new HiveGcsModule(), - new HdfsAuthenticationModule(), new CatalogNameModule(catalogName), metastoreModule.orElse(new DeltaLakeMetastoreModule()), new DeltaLakeModule(), diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeGlueMetastore.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeGlueMetastore.java index b21e3757b972..b908671c6005 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeGlueMetastore.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeGlueMetastore.java @@ -26,7 +26,6 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.trace.Tracer; import io.trino.filesystem.manager.FileSystemModule; -import io.trino.hdfs.HdfsEnvironment; import io.trino.plugin.base.CatalogName; import io.trino.plugin.base.session.SessionPropertiesProvider; import io.trino.plugin.deltalake.DeltaLakeMetadata; @@ -76,7 +75,6 @@ import static io.trino.plugin.deltalake.metastore.HiveMetastoreBackedDeltaLakeMetastore.TABLE_PROVIDER_PROPERTY; import static io.trino.plugin.deltalake.metastore.HiveMetastoreBackedDeltaLakeMetastore.TABLE_PROVIDER_VALUE; import static io.trino.plugin.hive.HiveStorageFormat.PARQUET; -import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; import static io.trino.plugin.hive.HiveType.HIVE_STRING; import static io.trino.plugin.hive.TableType.EXTERNAL_TABLE; import static io.trino.plugin.hive.TableType.VIRTUAL_VIEW; @@ -129,7 +127,6 @@ public void setUp() new DeltaLakeMetastoreModule(), new DeltaLakeModule(), // test setup - binder -> binder.bind(HdfsEnvironment.class).toInstance(HDFS_ENVIRONMENT), new FileSystemModule()); Injector injector = app diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/InternalHiveConnectorFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/InternalHiveConnectorFactory.java index 464a1d1641b8..53a165fd96a4 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/InternalHiveConnectorFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/InternalHiveConnectorFactory.java @@ -27,10 +27,6 @@ import io.opentelemetry.api.trace.Tracer; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.filesystem.manager.FileSystemModule; -import io.trino.hdfs.HdfsModule; -import io.trino.hdfs.authentication.HdfsAuthenticationModule; -import io.trino.hdfs.cos.HiveCosModule; -import io.trino.hdfs.gcs.HiveGcsModule; import io.trino.hdfs.rubix.RubixEnabledConfig; import io.trino.hdfs.rubix.RubixModule; import io.trino.plugin.base.CatalogName; @@ -113,13 +109,9 @@ public static Connector createConnector( new HiveModule(), new PartitionProjectionModule(), new CachingDirectoryListerModule(directoryLister), - new HdfsModule(), - new HiveGcsModule(), - new HiveCosModule(), conditionalModule(RubixEnabledConfig.class, RubixEnabledConfig::isCacheEnabled, new RubixModule()), new HiveMetastoreModule(metastore), new HiveSecurityModule(), - new HdfsAuthenticationModule(), fileSystemFactory .map(factory -> (Module) binder -> binder.bind(TrinoFileSystemFactory.class).toInstance(factory)) .orElseGet(FileSystemModule::new), diff --git a/plugin/trino-hudi/pom.xml b/plugin/trino-hudi/pom.xml index e692e7dfde1a..442dc12fdb1e 100644 --- a/plugin/trino-hudi/pom.xml +++ b/plugin/trino-hudi/pom.xml @@ -85,11 +85,6 @@ trino-filesystem-manager - - io.trino - trino-hdfs - - io.trino trino-hive @@ -187,6 +182,12 @@ runtime + + io.trino + trino-hdfs + runtime + + io.trino.hadoop hadoop-apache diff --git a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/InternalHudiConnectorFactory.java b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/InternalHudiConnectorFactory.java index b5bd60ceb6f8..a65fda489440 100644 --- a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/InternalHudiConnectorFactory.java +++ b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/InternalHudiConnectorFactory.java @@ -26,9 +26,6 @@ import io.opentelemetry.api.trace.Tracer; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.filesystem.manager.FileSystemModule; -import io.trino.hdfs.HdfsModule; -import io.trino.hdfs.authentication.HdfsAuthenticationModule; -import io.trino.hdfs.gcs.HiveGcsModule; import io.trino.plugin.base.CatalogName; import io.trino.plugin.base.classloader.ClassLoaderSafeConnectorPageSourceProvider; import io.trino.plugin.base.classloader.ClassLoaderSafeConnectorSplitManager; @@ -71,9 +68,6 @@ public static Connector createConnector( new JsonModule(), new HudiModule(), new HiveMetastoreModule(metastore), - new HdfsModule(), - new HiveGcsModule(), - new HdfsAuthenticationModule(), fileSystemFactory .map(factory -> (Module) binder -> binder.bind(TrinoFileSystemFactory.class).toInstance(factory)) .orElseGet(FileSystemModule::new), diff --git a/plugin/trino-iceberg/pom.xml b/plugin/trino-iceberg/pom.xml index f7665e2b351e..4613d3d39186 100644 --- a/plugin/trino-iceberg/pom.xml +++ b/plugin/trino-iceberg/pom.xml @@ -130,11 +130,6 @@ trino-filesystem-manager - - io.trino - trino-hdfs - - io.trino trino-hive @@ -348,6 +343,12 @@ runtime + + io.trino + trino-hdfs + runtime + + io.trino.hadoop hadoop-apache diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/InternalIcebergConnectorFactory.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/InternalIcebergConnectorFactory.java index ab1e2445d6f7..6ea876634e07 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/InternalIcebergConnectorFactory.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/InternalIcebergConnectorFactory.java @@ -25,9 +25,6 @@ import io.opentelemetry.api.trace.Tracer; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.filesystem.manager.FileSystemModule; -import io.trino.hdfs.HdfsModule; -import io.trino.hdfs.authentication.HdfsAuthenticationModule; -import io.trino.hdfs.gcs.HiveGcsModule; import io.trino.plugin.base.CatalogName; import io.trino.plugin.base.classloader.ClassLoaderSafeConnectorPageSinkProvider; import io.trino.plugin.base.classloader.ClassLoaderSafeConnectorPageSourceProvider; @@ -89,9 +86,6 @@ public static Connector createConnector( new IcebergModule(), new IcebergSecurityModule(), icebergCatalogModule.orElse(new IcebergCatalogModule()), - new HdfsModule(), - new HiveGcsModule(), - new HdfsAuthenticationModule(), new MBeanServerModule(), fileSystemFactory .map(factory -> (Module) binder -> binder.bind(TrinoFileSystemFactory.class).toInstance(factory)) From 117fd2d5df9f0fb95baa8ebe881fa953174311e8 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Wed, 1 Nov 2023 13:38:25 -0700 Subject: [PATCH 028/587] Remove unused usages of HdfsEnvironment --- .../src/main/java/io/trino/plugin/hive/HiveMetadata.java | 4 ---- .../java/io/trino/plugin/hive/HiveMetadataFactory.java | 7 ------- .../test/java/io/trino/plugin/hive/AbstractTestHive.java | 1 - .../io/trino/plugin/hive/AbstractTestHiveFileSystem.java | 1 - 4 files changed, 13 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java index 0b4d7b2e138d..6a6ac23e4475 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java @@ -32,7 +32,6 @@ import io.trino.filesystem.Location; import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoFileSystemFactory; -import io.trino.hdfs.HdfsEnvironment; import io.trino.plugin.base.CatalogName; import io.trino.plugin.base.projection.ApplyProjectionUtil; import io.trino.plugin.base.projection.ApplyProjectionUtil.ProjectedColumnRepresentation; @@ -368,7 +367,6 @@ public class HiveMetadata private final boolean autoCommit; private final Set fileWriterFactories; private final TrinoFileSystemFactory fileSystemFactory; - private final HdfsEnvironment hdfsEnvironment; private final HivePartitionManager partitionManager; private final TypeManager typeManager; private final MetadataProvider metadataProvider; @@ -397,7 +395,6 @@ public HiveMetadata( boolean autoCommit, Set fileWriterFactories, TrinoFileSystemFactory fileSystemFactory, - HdfsEnvironment hdfsEnvironment, HivePartitionManager partitionManager, boolean writesToNonManagedTablesEnabled, boolean createsOfNonManagedTablesEnabled, @@ -425,7 +422,6 @@ public HiveMetadata( this.autoCommit = autoCommit; this.fileWriterFactories = ImmutableSet.copyOf(requireNonNull(fileWriterFactories, "fileWriterFactories is null")); this.fileSystemFactory = requireNonNull(fileSystemFactory, "fileSystemFactory is null"); - this.hdfsEnvironment = requireNonNull(hdfsEnvironment, "hdfsEnvironment is null"); this.partitionManager = requireNonNull(partitionManager, "partitionManager is null"); this.typeManager = requireNonNull(typeManager, "typeManager is null"); this.metadataProvider = requireNonNull(metadataProvider, "metadataProvider is null"); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadataFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadataFactory.java index 278430f1b3c3..0226e269f7e3 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadataFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadataFactory.java @@ -19,7 +19,6 @@ import io.airlift.json.JsonCodec; import io.airlift.units.Duration; import io.trino.filesystem.TrinoFileSystemFactory; -import io.trino.hdfs.HdfsEnvironment; import io.trino.plugin.base.CatalogName; import io.trino.plugin.hive.aws.athena.PartitionProjectionService; import io.trino.plugin.hive.fs.DirectoryLister; @@ -59,7 +58,6 @@ public class HiveMetadataFactory private final HiveMetastoreFactory metastoreFactory; private final Set fileWriterFactories; private final TrinoFileSystemFactory fileSystemFactory; - private final HdfsEnvironment hdfsEnvironment; private final HivePartitionManager partitionManager; private final TypeManager typeManager; private final MetadataProvider metadataProvider; @@ -90,7 +88,6 @@ public HiveMetadataFactory( HiveMetastoreFactory metastoreFactory, Set fileWriterFactories, TrinoFileSystemFactory fileSystemFactory, - HdfsEnvironment hdfsEnvironment, HivePartitionManager partitionManager, ExecutorService executorService, @ForHiveTransactionHeartbeats ScheduledExecutorService heartbeatService, @@ -113,7 +110,6 @@ public HiveMetadataFactory( metastoreFactory, fileWriterFactories, fileSystemFactory, - hdfsEnvironment, partitionManager, hiveConfig.getMaxConcurrentFileSystemOperations(), hiveConfig.getMaxConcurrentMetastoreDrops(), @@ -152,7 +148,6 @@ public HiveMetadataFactory( HiveMetastoreFactory metastoreFactory, Set fileWriterFactories, TrinoFileSystemFactory fileSystemFactory, - HdfsEnvironment hdfsEnvironment, HivePartitionManager partitionManager, int maxConcurrentFileSystemOperations, int maxConcurrentMetastoreDrops, @@ -199,7 +194,6 @@ public HiveMetadataFactory( this.metastoreFactory = requireNonNull(metastoreFactory, "metastoreFactory is null"); this.fileWriterFactories = ImmutableSet.copyOf(requireNonNull(fileWriterFactories, "fileWriterFactories is null")); this.fileSystemFactory = requireNonNull(fileSystemFactory, "fileSystemFactory is null"); - this.hdfsEnvironment = requireNonNull(hdfsEnvironment, "hdfsEnvironment is null"); this.partitionManager = requireNonNull(partitionManager, "partitionManager is null"); this.typeManager = requireNonNull(typeManager, "typeManager is null"); this.metadataProvider = requireNonNull(metadataProvider, "metadataProvider is null"); @@ -256,7 +250,6 @@ public TransactionalMetadata create(ConnectorIdentity identity, boolean autoComm autoCommit, fileWriterFactories, fileSystemFactory, - hdfsEnvironment, partitionManager, writesToNonManagedTablesEnabled, createsOfNonManagedTablesEnabled, diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java index 15028d4afb57..9167fcf1249c 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java @@ -833,7 +833,6 @@ protected final void setup(String databaseName, HiveConfig hiveConfig, HiveMetas HiveMetastoreFactory.ofInstance(metastoreClient), getDefaultHiveFileWriterFactories(hiveConfig, hdfsEnvironment), fileSystemFactory, - hdfsEnvironment, partitionManager, 10, 10, diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java index fdb794fa06eb..58829ae623ad 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java @@ -224,7 +224,6 @@ protected void setup(String host, int port, String databaseName, HdfsConfigurati HiveMetastoreFactory.ofInstance(metastoreClient), getDefaultHiveFileWriterFactories(config, hdfsEnvironment), fileSystemFactory, - hdfsEnvironment, hivePartitionManager, newDirectExecutorService(), heartbeatService, From a840ad49ecaa6d3bb9221ecb475dde16895722bc Mon Sep 17 00:00:00 2001 From: David Phillips Date: Wed, 1 Nov 2023 13:42:26 -0700 Subject: [PATCH 029/587] Use local copy of AwsCurrentRegionHolder for Glue --- .../glue/AwsCurrentRegionHolder.java | 50 +++++++++++++++++++ .../hive/metastore/glue/GlueClientUtil.java | 4 +- .../glue/GlueCredentialsProvider.java | 4 +- 3 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/AwsCurrentRegionHolder.java diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/AwsCurrentRegionHolder.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/AwsCurrentRegionHolder.java new file mode 100644 index 000000000000..31f11cb2e0b4 --- /dev/null +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/AwsCurrentRegionHolder.java @@ -0,0 +1,50 @@ +/* + * 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 io.trino.plugin.hive.metastore.glue; + +import com.amazonaws.regions.Region; +import com.amazonaws.regions.Regions; +import com.google.common.base.Suppliers; + +import java.util.function.Supplier; + +/** + * Caches the result of calling {@link Regions#getCurrentRegion()} since accessing EC2 instance + * metadata repeatedly can result in being throttled and prevent other metadata accessing operations + * such as refreshing instance credentials from working normally + */ +final class AwsCurrentRegionHolder +{ + private static final Supplier SUPPLIER = Suppliers.memoize(AwsCurrentRegionHolder::loadCurrentRegion); + + private AwsCurrentRegionHolder() {} + + /** + * Attempts to resolve the current region from EC2's instance metadata through {@link Regions#getCurrentRegion()}. + * An exception is thrown if the region cannot be resolved. + */ + public static Region getCurrentRegionFromEc2Metadata() + { + return SUPPLIER.get(); + } + + private static Region loadCurrentRegion() + { + Region result = Regions.getCurrentRegion(); + if (result == null) { + throw new IllegalStateException("Failed to resolve current AWS region from EC2 metadata"); + } + return result; + } +} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueClientUtil.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueClientUtil.java index dbfdcf2067ca..d0aee8a2b7e9 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueClientUtil.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueClientUtil.java @@ -24,7 +24,7 @@ import java.util.Set; import static com.google.common.base.Preconditions.checkArgument; -import static io.trino.hdfs.s3.AwsCurrentRegionHolder.getCurrentRegionFromEC2Metadata; +import static io.trino.plugin.hive.metastore.glue.AwsCurrentRegionHolder.getCurrentRegionFromEc2Metadata; public final class GlueClientUtil { @@ -55,7 +55,7 @@ else if (config.getGlueRegion().isPresent()) { asyncGlueClientBuilder.setRegion(config.getGlueRegion().get()); } else if (config.getPinGlueClientToCurrentRegion()) { - asyncGlueClientBuilder.setRegion(getCurrentRegionFromEC2Metadata().getName()); + asyncGlueClientBuilder.setRegion(getCurrentRegionFromEc2Metadata().getName()); } asyncGlueClientBuilder.setCredentials(credentialsProvider); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueCredentialsProvider.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueCredentialsProvider.java index a6f13e0b6d2a..a1f9a28fa74f 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueCredentialsProvider.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueCredentialsProvider.java @@ -23,7 +23,7 @@ import com.google.inject.Inject; import com.google.inject.Provider; -import static io.trino.hdfs.s3.AwsCurrentRegionHolder.getCurrentRegionFromEC2Metadata; +import static io.trino.plugin.hive.metastore.glue.AwsCurrentRegionHolder.getCurrentRegionFromEc2Metadata; import static java.lang.String.format; public class GlueCredentialsProvider @@ -58,7 +58,7 @@ else if (config.getGlueStsRegion().isPresent()) { stsClientBuilder.setRegion(config.getGlueStsRegion().get()); } else if (config.getPinGlueClientToCurrentRegion()) { - stsClientBuilder.setRegion(getCurrentRegionFromEC2Metadata().getName()); + stsClientBuilder.setRegion(getCurrentRegionFromEc2Metadata().getName()); } provider = new STSAssumeRoleSessionCredentialsProvider From b280e6082a1d7260c47e7d5f67f23cc80c0be2b2 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Wed, 1 Nov 2023 14:19:40 -0700 Subject: [PATCH 030/587] Remove HDFS namenode stats This can be reimplemented in the HDFS filesystem implementation if anyone still cares about these stats. --- .../java/io/trino/hdfs/HdfsNamenodeStats.java | 37 ------------------ .../plugin/hive/TestHiveFileSystemS3.java | 3 -- .../hive/BackgroundHiveSplitLoader.java | 10 ++--- .../java/io/trino/plugin/hive/HiveModule.java | 5 --- .../trino/plugin/hive/HiveSplitManager.java | 7 ---- .../io/trino/plugin/hive/NamenodeStats.java | 19 --------- .../plugin/hive/fs/HiveFileIterator.java | 39 ++----------------- .../trino/plugin/hive/AbstractTestHive.java | 2 - .../hive/AbstractTestHiveFileSystem.java | 6 --- .../hive/TestBackgroundHiveSplitLoader.java | 5 --- 10 files changed, 6 insertions(+), 127 deletions(-) delete mode 100644 lib/trino-hdfs/src/main/java/io/trino/hdfs/HdfsNamenodeStats.java delete mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/NamenodeStats.java diff --git a/lib/trino-hdfs/src/main/java/io/trino/hdfs/HdfsNamenodeStats.java b/lib/trino-hdfs/src/main/java/io/trino/hdfs/HdfsNamenodeStats.java deleted file mode 100644 index 1f8638da1f50..000000000000 --- a/lib/trino-hdfs/src/main/java/io/trino/hdfs/HdfsNamenodeStats.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 io.trino.hdfs; - -import org.weakref.jmx.Managed; -import org.weakref.jmx.Nested; - -public final class HdfsNamenodeStats -{ - private final CallStats listLocatedStatus = new CallStats(); - private final CallStats remoteIteratorNext = new CallStats(); - - @Managed - @Nested - public CallStats getListLocatedStatus() - { - return listLocatedStatus; - } - - @Managed - @Nested - public CallStats getRemoteIteratorNext() - { - return remoteIteratorNext; - } -} diff --git a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveFileSystemS3.java b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveFileSystemS3.java index 448b085b728d..449799b1aa61 100644 --- a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveFileSystemS3.java +++ b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveFileSystemS3.java @@ -32,7 +32,6 @@ import io.trino.hdfs.HdfsConfig; import io.trino.hdfs.HdfsConfiguration; import io.trino.hdfs.HdfsConfigurationInitializer; -import io.trino.hdfs.HdfsNamenodeStats; import io.trino.hdfs.TrinoHdfsFileSystemStats; import io.trino.hdfs.s3.HiveS3Config; import io.trino.hdfs.s3.TrinoS3ConfigurationInitializer; @@ -208,7 +207,6 @@ public void testFileIteratorPartitionedListingNativeS3Client() Location.of(basePath.toString()), trinoFileSystem, new FileSystemDirectoryLister(), - new HdfsNamenodeStats(), HiveFileIterator.NestedDirectoryPolicy.RECURSE); List recursiveListing = Streams.stream(recursiveIterator) @@ -230,7 +228,6 @@ public void testFileIteratorPartitionedListingNativeS3Client() Location.of(basePath.toString()), trinoFileSystem, new FileSystemDirectoryLister(), - new HdfsNamenodeStats(), HiveFileIterator.NestedDirectoryPolicy.IGNORED); List shallowListing = Streams.stream(shallowIterator) .map(TrinoFileStatus::getPath) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/BackgroundHiveSplitLoader.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/BackgroundHiveSplitLoader.java index 8b79ea2c23a6..01d252210f40 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/BackgroundHiveSplitLoader.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/BackgroundHiveSplitLoader.java @@ -29,7 +29,6 @@ import io.trino.filesystem.Location; import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoFileSystemFactory; -import io.trino.hdfs.HdfsNamenodeStats; import io.trino.plugin.hive.HiveSplit.BucketConversion; import io.trino.plugin.hive.HiveSplit.BucketValidation; import io.trino.plugin.hive.fs.DirectoryLister; @@ -149,7 +148,6 @@ public class BackgroundHiveSplitLoader private final long dynamicFilteringWaitTimeoutMillis; private final TypeManager typeManager; private final Optional tableBucketInfo; - private final HdfsNamenodeStats hdfsNamenodeStats; private final DirectoryLister directoryLister; private final TrinoFileSystemFactory fileSystemFactory; private final int loaderConcurrency; @@ -196,7 +194,6 @@ public BackgroundHiveSplitLoader( Optional tableBucketInfo, ConnectorSession session, TrinoFileSystemFactory fileSystemFactory, - HdfsNamenodeStats hdfsNamenodeStats, DirectoryLister directoryLister, Executor executor, int loaderConcurrency, @@ -216,7 +213,6 @@ public BackgroundHiveSplitLoader( checkArgument(loaderConcurrency > 0, "loaderConcurrency must be > 0, found: %s", loaderConcurrency); this.session = session; this.fileSystemFactory = requireNonNull(fileSystemFactory, "fileSystemFactory is null"); - this.hdfsNamenodeStats = hdfsNamenodeStats; this.directoryLister = directoryLister; this.recursiveDirWalkerEnabled = recursiveDirWalkerEnabled; this.ignoreAbsentPartitions = ignoreAbsentPartitions; @@ -501,7 +497,7 @@ private ListenableFuture loadPartition(HivePartitionMetadata partition) private List listBucketFiles(TrinoFileSystem fs, Location location, String partitionName) { try { - HiveFileIterator fileIterator = new HiveFileIterator(table, location, fs, directoryLister, hdfsNamenodeStats, FAIL); + HiveFileIterator fileIterator = new HiveFileIterator(table, location, fs, directoryLister, FAIL); if (!fileIterator.hasNext() && !ignoreAbsentPartitions) { checkPartitionLocationExists(fs, location); } @@ -520,7 +516,7 @@ Iterator buildManifestFileIterator(InternalHiveSplitFactory s TrinoFileSystem trinoFileSystem = fileSystemFactory.create(session); Map fileStatuses = new HashMap<>(); - Iterator fileStatusIterator = new HiveFileIterator(table, location, trinoFileSystem, directoryLister, hdfsNamenodeStats, RECURSE); + Iterator fileStatusIterator = new HiveFileIterator(table, location, trinoFileSystem, directoryLister, RECURSE); if (!fileStatusIterator.hasNext()) { checkPartitionLocationExists(trinoFileSystem, location); } @@ -639,7 +635,7 @@ private static Optional acidInfoForOriginalFiles(boolean fullAcid, Aci private Iterator createInternalHiveSplitIterator(TrinoFileSystem fileSystem, Location location, InternalHiveSplitFactory splitFactory, boolean splittable, Optional acidInfo) { - Iterator iterator = new HiveFileIterator(table, location, fileSystem, directoryLister, hdfsNamenodeStats, recursiveDirWalkerEnabled ? RECURSE : IGNORED); + Iterator iterator = new HiveFileIterator(table, location, fileSystem, directoryLister, recursiveDirWalkerEnabled ? RECURSE : IGNORED); if (!iterator.hasNext() && !ignoreAbsentPartitions) { checkPartitionLocationExists(fileSystem, location); } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveModule.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveModule.java index 0f43d35240f4..a6c0b18ac6b6 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveModule.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveModule.java @@ -21,7 +21,6 @@ import com.google.inject.Singleton; import com.google.inject.multibindings.Multibinder; import io.airlift.event.client.EventClient; -import io.trino.hdfs.HdfsNamenodeStats; import io.trino.hdfs.TrinoFileSystemCacheStats; import io.trino.plugin.base.CatalogName; import io.trino.plugin.hive.avro.AvroFileWriterFactory; @@ -121,10 +120,6 @@ public void configure(Binder binder) newExporter(binder).export(TrinoFileSystemCacheStats.class) .as(generator -> generator.generatedNameOf(TrinoFileSystemCache.class)); - binder.bind(HdfsNamenodeStats.class).in(Scopes.SINGLETON); - newExporter(binder).export(HdfsNamenodeStats.class) - .as(generator -> generator.generatedNameOf(NamenodeStats.class)); - Multibinder pageSourceFactoryBinder = newSetBinder(binder, HivePageSourceFactory.class); pageSourceFactoryBinder.addBinding().to(CsvPageSourceFactory.class).in(Scopes.SINGLETON); pageSourceFactoryBinder.addBinding().to(JsonPageSourceFactory.class).in(Scopes.SINGLETON); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveSplitManager.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveSplitManager.java index 7b04d6bfd01e..e007baeaf52e 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveSplitManager.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveSplitManager.java @@ -24,7 +24,6 @@ import io.airlift.stats.CounterStat; import io.airlift.units.DataSize; import io.trino.filesystem.TrinoFileSystemFactory; -import io.trino.hdfs.HdfsNamenodeStats; import io.trino.plugin.hive.metastore.Column; import io.trino.plugin.hive.metastore.Partition; import io.trino.plugin.hive.metastore.SemiTransactionalHiveMetastore; @@ -105,7 +104,6 @@ public class HiveSplitManager private final HiveTransactionManager transactionManager; private final HivePartitionManager partitionManager; private final TrinoFileSystemFactory fileSystemFactory; - private final HdfsNamenodeStats hdfsNamenodeStats; private final Executor executor; private final int maxOutstandingSplits; private final DataSize maxOutstandingSplitsSize; @@ -125,7 +123,6 @@ public HiveSplitManager( HiveTransactionManager transactionManager, HivePartitionManager partitionManager, TrinoFileSystemFactory fileSystemFactory, - HdfsNamenodeStats hdfsNamenodeStats, ExecutorService executorService, VersionEmbedder versionEmbedder, TypeManager typeManager) @@ -134,7 +131,6 @@ public HiveSplitManager( transactionManager, partitionManager, fileSystemFactory, - hdfsNamenodeStats, versionEmbedder.embedVersion(new BoundedExecutor(executorService, hiveConfig.getMaxSplitIteratorThreads())), new CounterStat(), hiveConfig.getMaxOutstandingSplits(), @@ -153,7 +149,6 @@ public HiveSplitManager( HiveTransactionManager transactionManager, HivePartitionManager partitionManager, TrinoFileSystemFactory fileSystemFactory, - HdfsNamenodeStats hdfsNamenodeStats, Executor executor, CounterStat highMemorySplitSourceCounter, int maxOutstandingSplits, @@ -170,7 +165,6 @@ public HiveSplitManager( this.transactionManager = requireNonNull(transactionManager, "transactionManager is null"); this.partitionManager = requireNonNull(partitionManager, "partitionManager is null"); this.fileSystemFactory = requireNonNull(fileSystemFactory, "fileSystemFactory is null"); - this.hdfsNamenodeStats = requireNonNull(hdfsNamenodeStats, "hdfsNamenodeStats is null"); this.executor = new ErrorCodedExecutor(executor); this.highMemorySplitSourceCounter = requireNonNull(highMemorySplitSourceCounter, "highMemorySplitSourceCounter is null"); checkArgument(maxOutstandingSplits >= 1, "maxOutstandingSplits must be at least 1"); @@ -261,7 +255,6 @@ public ConnectorSplitSource getSplits( createBucketSplitInfo(bucketHandle, bucketFilter), session, fileSystemFactory, - hdfsNamenodeStats, transactionalMetadata.getDirectoryLister(), executor, splitLoaderConcurrency, diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/NamenodeStats.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/NamenodeStats.java deleted file mode 100644 index 84d0be3ed578..000000000000 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/NamenodeStats.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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 io.trino.plugin.hive; - -/** - * Dummy class needed to preserve the legacy JMX object name. - */ -public final class NamenodeStats {} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/fs/HiveFileIterator.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/fs/HiveFileIterator.java index 098b75183086..f17789b40ee4 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/fs/HiveFileIterator.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/fs/HiveFileIterator.java @@ -15,10 +15,8 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.AbstractIterator; -import io.airlift.stats.TimeStat; import io.trino.filesystem.Location; import io.trino.filesystem.TrinoFileSystem; -import io.trino.hdfs.HdfsNamenodeStats; import io.trino.plugin.hive.metastore.Table; import io.trino.spi.TrinoException; @@ -43,11 +41,7 @@ public enum NestedDirectoryPolicy FAIL } - private final Table table; private final Location location; - private final TrinoFileSystem fileSystem; - private final DirectoryLister directoryLister; - private final HdfsNamenodeStats namenodeStats; private final NestedDirectoryPolicy nestedDirectoryPolicy; private final Iterator remoteIterator; @@ -56,23 +50,18 @@ public HiveFileIterator( Location location, TrinoFileSystem fileSystem, DirectoryLister directoryLister, - HdfsNamenodeStats namenodeStats, NestedDirectoryPolicy nestedDirectoryPolicy) { - this.table = requireNonNull(table, "table is null"); this.location = requireNonNull(location, "location is null"); - this.fileSystem = requireNonNull(fileSystem, "fileSystem is null"); - this.directoryLister = requireNonNull(directoryLister, "directoryLister is null"); - this.namenodeStats = requireNonNull(namenodeStats, "namenodeStats is null"); this.nestedDirectoryPolicy = requireNonNull(nestedDirectoryPolicy, "nestedDirectoryPolicy is null"); - this.remoteIterator = getLocatedFileStatusRemoteIterator(location); + this.remoteIterator = new FileStatusIterator(table, location, fileSystem, directoryLister, nestedDirectoryPolicy); } @Override protected TrinoFileStatus computeNext() { while (remoteIterator.hasNext()) { - TrinoFileStatus status = getLocatedFileStatus(remoteIterator); + TrinoFileStatus status = remoteIterator.next(); // Ignore hidden files and directories if (nestedDirectoryPolicy == RECURSE) { @@ -91,23 +80,6 @@ else if (isHiddenFileOrDirectory(Location.of(status.getPath()))) { return endOfData(); } - private Iterator getLocatedFileStatusRemoteIterator(Location location) - { - try (TimeStat.BlockTimer ignored = namenodeStats.getListLocatedStatus().time()) { - return new FileStatusIterator(table, location, fileSystem, directoryLister, namenodeStats, nestedDirectoryPolicy); - } - catch (IOException e) { - throw new TrinoException(HIVE_FILESYSTEM_ERROR, "Failed to list files for location: " + location, e); - } - } - - private TrinoFileStatus getLocatedFileStatus(Iterator iterator) - { - try (TimeStat.BlockTimer ignored = namenodeStats.getRemoteIteratorNext().time()) { - return iterator.next(); - } - } - @VisibleForTesting static boolean isHiddenFileOrDirectory(Location location) { @@ -148,7 +120,6 @@ private static class FileStatusIterator implements Iterator { private final Location location; - private final HdfsNamenodeStats namenodeStats; private final RemoteIterator fileStatusIterator; private FileStatusIterator( @@ -156,12 +127,9 @@ private FileStatusIterator( Location location, TrinoFileSystem fileSystem, DirectoryLister directoryLister, - HdfsNamenodeStats namenodeStats, NestedDirectoryPolicy nestedDirectoryPolicy) - throws IOException { - this.location = location; - this.namenodeStats = namenodeStats; + this.location = requireNonNull(location, "location is null"); try { if (nestedDirectoryPolicy == RECURSE) { this.fileStatusIterator = directoryLister.listFilesRecursively(fileSystem, table, location); @@ -202,7 +170,6 @@ public TrinoFileStatus next() private TrinoException processException(IOException exception) { - namenodeStats.getRemoteIteratorNext().recordException(exception); if (exception instanceof FileNotFoundException) { return new TrinoException(HIVE_FILE_NOT_FOUND, "Partition location does not exist: " + location); } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java index 9167fcf1249c..427648bff612 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java @@ -31,7 +31,6 @@ import io.trino.filesystem.hdfs.HdfsFileSystemFactory; import io.trino.hdfs.HdfsContext; import io.trino.hdfs.HdfsEnvironment; -import io.trino.hdfs.HdfsNamenodeStats; import io.trino.operator.GroupByHashPageIndexerFactory; import io.trino.plugin.base.CatalogName; import io.trino.plugin.base.metrics.LongCount; @@ -909,7 +908,6 @@ public Optional getMaterializedView(Connect transactionManager, partitionManager, fileSystemFactory, - new HdfsNamenodeStats(), executor, new CounterStat(), 100, diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java index 58829ae623ad..8241e918ece5 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java @@ -29,7 +29,6 @@ import io.trino.hdfs.HdfsConfiguration; import io.trino.hdfs.HdfsContext; import io.trino.hdfs.HdfsEnvironment; -import io.trino.hdfs.HdfsNamenodeStats; import io.trino.hdfs.TrinoHdfsFileSystemStats; import io.trino.hdfs.authentication.NoHdfsAuthentication; import io.trino.operator.GroupByHashPageIndexerFactory; @@ -247,7 +246,6 @@ protected void setup(String host, int port, String databaseName, HdfsConfigurati transactionManager, hivePartitionManager, fileSystemFactory, - new HdfsNamenodeStats(), new BoundedExecutor(executor, config.getMaxSplitIteratorThreads()), new CounterStat(), config.getMaxOutstandingSplits(), @@ -478,7 +476,6 @@ public void testFileIteratorListing() Location.of(basePath.toString()), trinoFileSystem, new FileSystemDirectoryLister(), - new HdfsNamenodeStats(), HiveFileIterator.NestedDirectoryPolicy.RECURSE); List recursiveListing = Streams.stream(recursiveIterator) @@ -493,7 +490,6 @@ public void testFileIteratorListing() Location.of(basePath.toString()), trinoFileSystem, new FileSystemDirectoryLister(), - new HdfsNamenodeStats(), HiveFileIterator.NestedDirectoryPolicy.IGNORED); List shallowListing = Streams.stream(shallowIterator) .map(TrinoFileStatus::getPath) @@ -594,7 +590,6 @@ public void testFileIteratorPartitionedListing() Location.of(basePath.toString()), trinoFileSystem, new FileSystemDirectoryLister(), - new HdfsNamenodeStats(), HiveFileIterator.NestedDirectoryPolicy.RECURSE); List recursiveListing = Streams.stream(recursiveIterator) @@ -617,7 +612,6 @@ public void testFileIteratorPartitionedListing() Location.of(basePath.toString()), trinoFileSystem, new FileSystemDirectoryLister(), - new HdfsNamenodeStats(), HiveFileIterator.NestedDirectoryPolicy.IGNORED); List shallowListing = Streams.stream(shallowIterator) .map(TrinoFileStatus::getPath) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java index 17b31225c66e..c5b7666c6dcc 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java @@ -28,7 +28,6 @@ import io.trino.hdfs.HdfsConfig; import io.trino.hdfs.HdfsConfigurationInitializer; import io.trino.hdfs.HdfsEnvironment; -import io.trino.hdfs.HdfsNamenodeStats; import io.trino.hdfs.authentication.NoHdfsAuthentication; import io.trino.plugin.hive.HiveColumnHandle.ColumnType; import io.trino.plugin.hive.fs.CachingDirectoryLister; @@ -491,7 +490,6 @@ public HivePartitionMetadata next() createBucketSplitInfo(Optional.empty(), Optional.empty()), SESSION, new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS), - new HdfsNamenodeStats(), new CachingDirectoryLister(new HiveConfig()), executor, threads, @@ -1111,7 +1109,6 @@ private BackgroundHiveSplitLoader backgroundHiveSplitLoader( createBucketSplitInfo(bucketHandle, hiveBucketFilter), SESSION, new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS), - new HdfsNamenodeStats(), new CachingDirectoryLister(new HiveConfig()), executor, 2, @@ -1154,7 +1151,6 @@ private BackgroundHiveSplitLoader backgroundHiveSplitLoader( Optional.empty(), connectorSession, new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS), - new HdfsNamenodeStats(), directoryLister, executor, 2, @@ -1181,7 +1177,6 @@ private BackgroundHiveSplitLoader backgroundHiveSplitLoaderOfflinePartitions() createBucketSplitInfo(Optional.empty(), Optional.empty()), connectorSession, new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS), - new HdfsNamenodeStats(), new CachingDirectoryLister(new HiveConfig()), executor, 2, From 6e39f0d2be48fc7579009971989ff5c11a2f795f Mon Sep 17 00:00:00 2001 From: David Phillips Date: Wed, 1 Nov 2023 15:19:06 -0700 Subject: [PATCH 031/587] Move testing Glue metastore creation to test code --- plugin/trino-delta-lake/pom.xml | 5 -- ...TestDeltaLakeSharedGlueMetastoreViews.java | 2 +- ...redGlueMetastoreWithTableRedirections.java | 2 +- ...eWithCustomLocationUsingGlueMetastore.java | 2 +- ...keConcurrentModificationGlueMetastore.java | 6 +-- ...ltaLakeRegisterTableProcedureWithGlue.java | 2 +- .../glue/TestDeltaLakeViewsGlueMetastore.java | 2 +- .../glue/TestDeltaS3AndGlueMetastoreTest.java | 2 +- .../metastore/glue/GlueHiveMetastore.java | 31 ----------- ...veConcurrentModificationGlueMetastore.java | 6 +-- .../hive/TestHiveS3AndGlueMetastoreTest.java | 2 +- .../metastore/glue/TestHiveGlueMetastore.java | 6 +-- .../glue/TestingGlueHiveMetastore.java | 53 +++++++++++++++++++ .../TestIcebergGlueCreateTableFailure.java | 2 +- ...ebergGlueTableOperationsInsertFailure.java | 2 +- .../TestIcebergS3AndGlueMetastoreTest.java | 2 +- .../catalog/glue/TestSharedGlueMetastore.java | 2 +- 17 files changed, 70 insertions(+), 59 deletions(-) create mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestingGlueHiveMetastore.java diff --git a/plugin/trino-delta-lake/pom.xml b/plugin/trino-delta-lake/pom.xml index 8120ba4218c6..ae6387e8a956 100644 --- a/plugin/trino-delta-lake/pom.xml +++ b/plugin/trino-delta-lake/pom.xml @@ -27,11 +27,6 @@ - - com.amazonaws - aws-java-sdk-core - - com.amazonaws aws-java-sdk-glue diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSharedGlueMetastoreViews.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSharedGlueMetastoreViews.java index c9d2b942e75c..b12295bdd63b 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSharedGlueMetastoreViews.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSharedGlueMetastoreViews.java @@ -17,7 +17,7 @@ import java.nio.file.Path; -import static io.trino.plugin.hive.metastore.glue.GlueHiveMetastore.createTestingGlueHiveMetastore; +import static io.trino.plugin.hive.metastore.glue.TestingGlueHiveMetastore.createTestingGlueHiveMetastore; /** * Requires AWS credentials, which can be provided any way supported by the DefaultProviderChain diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSharedGlueMetastoreWithTableRedirections.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSharedGlueMetastoreWithTableRedirections.java index 72a95c7fd8a9..83f43ff8940a 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSharedGlueMetastoreWithTableRedirections.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSharedGlueMetastoreWithTableRedirections.java @@ -25,7 +25,7 @@ import java.nio.file.Path; -import static io.trino.plugin.hive.metastore.glue.GlueHiveMetastore.createTestingGlueHiveMetastore; +import static io.trino.plugin.hive.metastore.glue.TestingGlueHiveMetastore.createTestingGlueHiveMetastore; import static io.trino.testing.TestingSession.testSessionBuilder; import static java.lang.String.format; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeTableWithCustomLocationUsingGlueMetastore.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeTableWithCustomLocationUsingGlueMetastore.java index a5ae4e65a9dd..2f4f64b448e7 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeTableWithCustomLocationUsingGlueMetastore.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeTableWithCustomLocationUsingGlueMetastore.java @@ -26,7 +26,7 @@ import static com.google.common.io.MoreFiles.deleteRecursively; import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; import static io.trino.plugin.deltalake.DeltaLakeConnectorFactory.CONNECTOR_NAME; -import static io.trino.plugin.hive.metastore.glue.GlueHiveMetastore.createTestingGlueHiveMetastore; +import static io.trino.plugin.hive.metastore.glue.TestingGlueHiveMetastore.createTestingGlueHiveMetastore; import static io.trino.testing.TestingSession.testSessionBuilder; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeConcurrentModificationGlueMetastore.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeConcurrentModificationGlueMetastore.java index 3de0061d5582..197be9b65088 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeConcurrentModificationGlueMetastore.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeConcurrentModificationGlueMetastore.java @@ -13,10 +13,8 @@ */ package io.trino.plugin.deltalake.metastore.glue; -import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; import com.amazonaws.services.glue.AWSGlueAsync; import com.amazonaws.services.glue.model.ConcurrentModificationException; -import com.google.common.collect.ImmutableSet; import io.trino.Session; import io.trino.plugin.deltalake.TestingDeltaLakePlugin; import io.trino.plugin.deltalake.metastore.TestingDeltaLakeMetastoreModule; @@ -45,7 +43,7 @@ import static com.google.inject.util.Modules.EMPTY_MODULE; import static io.trino.plugin.hive.HiveErrorCode.HIVE_METASTORE_ERROR; import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; -import static io.trino.plugin.hive.metastore.glue.GlueClientUtil.createAsyncGlueClient; +import static io.trino.plugin.hive.metastore.glue.TestingGlueHiveMetastore.createTestingAsyncGlueClient; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.testing.TestingSession.testSessionBuilder; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; @@ -77,7 +75,7 @@ protected QueryRunner createQueryRunner() GlueHiveMetastoreConfig glueConfig = new GlueHiveMetastoreConfig() .setDefaultWarehouseDir(dataDirectory.toUri().toString()); - AWSGlueAsync glueClient = createAsyncGlueClient(glueConfig, DefaultAWSCredentialsProviderChain.getInstance(), ImmutableSet.of(), stats.newRequestMetricsCollector()); + AWSGlueAsync glueClient = createTestingAsyncGlueClient(glueConfig, stats); AWSGlueAsync proxiedGlueClient = newProxy(AWSGlueAsync.class, (proxy, method, args) -> { Object result; try { diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeRegisterTableProcedureWithGlue.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeRegisterTableProcedureWithGlue.java index 1aaf0f79f13a..52baa8b42173 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeRegisterTableProcedureWithGlue.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeRegisterTableProcedureWithGlue.java @@ -18,7 +18,7 @@ import java.nio.file.Path; -import static io.trino.plugin.hive.metastore.glue.GlueHiveMetastore.createTestingGlueHiveMetastore; +import static io.trino.plugin.hive.metastore.glue.TestingGlueHiveMetastore.createTestingGlueHiveMetastore; public class TestDeltaLakeRegisterTableProcedureWithGlue extends BaseDeltaLakeRegisterTableProcedureTest diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeViewsGlueMetastore.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeViewsGlueMetastore.java index ca1176598fa5..f7ddf7a81b11 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeViewsGlueMetastore.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeViewsGlueMetastore.java @@ -33,7 +33,7 @@ import static com.google.common.io.MoreFiles.deleteRecursively; import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; import static com.google.inject.util.Modules.EMPTY_MODULE; -import static io.trino.plugin.hive.metastore.glue.GlueHiveMetastore.createTestingGlueHiveMetastore; +import static io.trino.plugin.hive.metastore.glue.TestingGlueHiveMetastore.createTestingGlueHiveMetastore; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.testing.TestingSession.testSessionBuilder; import static java.lang.String.format; diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaS3AndGlueMetastoreTest.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaS3AndGlueMetastoreTest.java index 880ccb0ac3fe..9119968069d3 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaS3AndGlueMetastoreTest.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaS3AndGlueMetastoreTest.java @@ -26,7 +26,7 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static io.trino.plugin.deltalake.DeltaLakeQueryRunner.DELTA_CATALOG; -import static io.trino.plugin.hive.metastore.glue.GlueHiveMetastore.createTestingGlueHiveMetastore; +import static io.trino.plugin.hive.metastore.glue.TestingGlueHiveMetastore.createTestingGlueHiveMetastore; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java index ad2767817b65..98ff48bd4664 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java @@ -15,7 +15,6 @@ import com.amazonaws.AmazonServiceException; import com.amazonaws.AmazonWebServiceRequest; -import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; import com.amazonaws.handlers.AsyncHandler; import com.amazonaws.services.glue.AWSGlueAsync; import com.amazonaws.services.glue.model.AccessDeniedException; @@ -63,7 +62,6 @@ import com.amazonaws.services.glue.model.UpdateTableRequest; import com.amazonaws.services.glue.model.UpdateUserDefinedFunctionRequest; import com.amazonaws.services.glue.model.UserDefinedFunctionInput; -import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Stopwatch; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; @@ -79,14 +77,6 @@ import io.trino.filesystem.Location; import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoFileSystemFactory; -import io.trino.filesystem.hdfs.HdfsFileSystemFactory; -import io.trino.hdfs.DynamicHdfsConfiguration; -import io.trino.hdfs.HdfsConfig; -import io.trino.hdfs.HdfsConfiguration; -import io.trino.hdfs.HdfsConfigurationInitializer; -import io.trino.hdfs.HdfsEnvironment; -import io.trino.hdfs.TrinoHdfsFileSystemStats; -import io.trino.hdfs.authentication.NoHdfsAuthentication; import io.trino.plugin.hive.HiveColumnStatisticType; import io.trino.plugin.hive.HiveType; import io.trino.plugin.hive.PartitionNotFoundException; @@ -152,7 +142,6 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.collect.ImmutableSet.toImmutableSet; -import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static io.trino.plugin.hive.HiveErrorCode.HIVE_FILESYSTEM_ERROR; import static io.trino.plugin.hive.HiveErrorCode.HIVE_INVALID_METADATA; import static io.trino.plugin.hive.HiveErrorCode.HIVE_METASTORE_ERROR; @@ -162,7 +151,6 @@ import static io.trino.plugin.hive.metastore.MetastoreUtil.toPartitionName; import static io.trino.plugin.hive.metastore.MetastoreUtil.verifyCanDropColumn; import static io.trino.plugin.hive.metastore.glue.AwsSdkUtil.getPaginatedResults; -import static io.trino.plugin.hive.metastore.glue.GlueClientUtil.createAsyncGlueClient; import static io.trino.plugin.hive.metastore.glue.converter.GlueInputConverter.convertFunction; import static io.trino.plugin.hive.metastore.glue.converter.GlueInputConverter.convertPartition; import static io.trino.plugin.hive.metastore.glue.converter.GlueToTrinoConverter.getTableParameters; @@ -234,25 +222,6 @@ public GlueHiveMetastore( this.columnStatisticsProvider = columnStatisticsProviderFactory.createGlueColumnStatisticsProvider(glueClient, stats); } - @VisibleForTesting - public static GlueHiveMetastore createTestingGlueHiveMetastore(java.nio.file.Path defaultWarehouseDir) - { - HdfsConfig hdfsConfig = new HdfsConfig(); - HdfsConfiguration hdfsConfiguration = new DynamicHdfsConfiguration(new HdfsConfigurationInitializer(hdfsConfig), ImmutableSet.of()); - HdfsEnvironment hdfsEnvironment = new HdfsEnvironment(hdfsConfiguration, hdfsConfig, new NoHdfsAuthentication()); - GlueMetastoreStats stats = new GlueMetastoreStats(); - GlueHiveMetastoreConfig glueConfig = new GlueHiveMetastoreConfig() - .setDefaultWarehouseDir(defaultWarehouseDir.toUri().toString()); - return new GlueHiveMetastore( - new HdfsFileSystemFactory(hdfsEnvironment, new TrinoHdfsFileSystemStats()), - glueConfig, - directExecutor(), - new DefaultGlueColumnStatisticsProviderFactory(directExecutor(), directExecutor()), - createAsyncGlueClient(glueConfig, DefaultAWSCredentialsProviderChain.getInstance(), ImmutableSet.of(), stats.newRequestMetricsCollector()), - stats, - table -> true); - } - @Managed @Flatten public GlueMetastoreStats getStats() diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveConcurrentModificationGlueMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveConcurrentModificationGlueMetastore.java index 5c5fd6a1942f..c082dfd97b7f 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveConcurrentModificationGlueMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveConcurrentModificationGlueMetastore.java @@ -13,10 +13,8 @@ */ package io.trino.plugin.hive; -import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; import com.amazonaws.services.glue.AWSGlueAsync; import com.amazonaws.services.glue.model.ConcurrentModificationException; -import com.google.common.collect.ImmutableSet; import io.trino.Session; import io.trino.plugin.hive.metastore.glue.DefaultGlueColumnStatisticsProviderFactory; import io.trino.plugin.hive.metastore.glue.GlueHiveMetastore; @@ -44,7 +42,7 @@ import static com.google.inject.util.Modules.EMPTY_MODULE; import static io.trino.plugin.hive.HiveErrorCode.HIVE_METASTORE_ERROR; import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; -import static io.trino.plugin.hive.metastore.glue.GlueClientUtil.createAsyncGlueClient; +import static io.trino.plugin.hive.metastore.glue.TestingGlueHiveMetastore.createTestingAsyncGlueClient; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.testing.TestingSession.testSessionBuilder; import static org.assertj.core.api.Assertions.assertThat; @@ -77,7 +75,7 @@ protected QueryRunner createQueryRunner() GlueHiveMetastoreConfig glueConfig = new GlueHiveMetastoreConfig() .setDefaultWarehouseDir(dataDirectory.toUri().toString()); - AWSGlueAsync glueClient = createAsyncGlueClient(glueConfig, DefaultAWSCredentialsProviderChain.getInstance(), ImmutableSet.of(), stats.newRequestMetricsCollector()); + AWSGlueAsync glueClient = createTestingAsyncGlueClient(glueConfig, stats); AWSGlueAsync proxiedGlueClient = newProxy(AWSGlueAsync.class, (proxy, method, args) -> { try { if (method.getName().equals("updateTable")) { diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveS3AndGlueMetastoreTest.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveS3AndGlueMetastoreTest.java index e5daa7c71a64..15ff9fc407a9 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveS3AndGlueMetastoreTest.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveS3AndGlueMetastoreTest.java @@ -30,7 +30,7 @@ import static io.trino.plugin.hive.BaseS3AndGlueMetastoreTest.LocationPattern.DOUBLE_SLASH; import static io.trino.plugin.hive.BaseS3AndGlueMetastoreTest.LocationPattern.TRIPLE_SLASH; import static io.trino.plugin.hive.BaseS3AndGlueMetastoreTest.LocationPattern.TWO_TRAILING_SLASHES; -import static io.trino.plugin.hive.metastore.glue.GlueHiveMetastore.createTestingGlueHiveMetastore; +import static io.trino.plugin.hive.metastore.glue.TestingGlueHiveMetastore.createTestingGlueHiveMetastore; import static io.trino.spi.security.SelectedRole.Type.ROLE; import static io.trino.testing.MaterializedResult.resultBuilder; import static io.trino.testing.TestingNames.randomNameSuffix; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestHiveGlueMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestHiveGlueMetastore.java index ad6cdd28cca5..27431383aec2 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestHiveGlueMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestHiveGlueMetastore.java @@ -13,7 +13,6 @@ */ package io.trino.plugin.hive.metastore.glue; -import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; import com.amazonaws.services.glue.AWSGlueAsync; import com.amazonaws.services.glue.AWSGlueAsyncClientBuilder; import com.amazonaws.services.glue.model.CreateTableRequest; @@ -27,7 +26,6 @@ import com.amazonaws.services.glue.model.UpdateTableRequest; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; import io.airlift.concurrent.BoundedExecutor; import io.airlift.log.Logger; import io.airlift.slice.Slice; @@ -100,9 +98,9 @@ import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; import static io.trino.plugin.hive.metastore.HiveColumnStatistics.createIntegerColumnStatistics; import static io.trino.plugin.hive.metastore.glue.AwsSdkUtil.getPaginatedResults; -import static io.trino.plugin.hive.metastore.glue.GlueClientUtil.createAsyncGlueClient; import static io.trino.plugin.hive.metastore.glue.PartitionFilterBuilder.DECIMAL_TYPE; import static io.trino.plugin.hive.metastore.glue.PartitionFilterBuilder.decimalOf; +import static io.trino.plugin.hive.metastore.glue.TestingGlueHiveMetastore.createTestingAsyncGlueClient; import static io.trino.plugin.hive.util.HiveUtil.DELTA_LAKE_PROVIDER; import static io.trino.plugin.hive.util.HiveUtil.ICEBERG_TABLE_TYPE_NAME; import static io.trino.plugin.hive.util.HiveUtil.ICEBERG_TABLE_TYPE_VALUE; @@ -232,7 +230,7 @@ protected HiveMetastore createMetastore(File tempDir) glueConfig, executor, new DefaultGlueColumnStatisticsProviderFactory(executor, executor), - createAsyncGlueClient(glueConfig, DefaultAWSCredentialsProviderChain.getInstance(), ImmutableSet.of(), stats.newRequestMetricsCollector()), + createTestingAsyncGlueClient(glueConfig, stats), stats, new DefaultGlueMetastoreTableFilterProvider(true).get()); } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestingGlueHiveMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestingGlueHiveMetastore.java new file mode 100644 index 000000000000..5a252327b18e --- /dev/null +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestingGlueHiveMetastore.java @@ -0,0 +1,53 @@ +/* + * 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 io.trino.plugin.hive.metastore.glue; + +import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; +import com.amazonaws.services.glue.AWSGlueAsync; +import com.google.common.collect.ImmutableSet; + +import java.nio.file.Path; + +import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; +import static io.trino.plugin.hive.metastore.glue.GlueClientUtil.createAsyncGlueClient; + +public final class TestingGlueHiveMetastore +{ + private TestingGlueHiveMetastore() {} + + public static GlueHiveMetastore createTestingGlueHiveMetastore(Path defaultWarehouseDir) + { + GlueHiveMetastoreConfig glueConfig = new GlueHiveMetastoreConfig() + .setDefaultWarehouseDir(defaultWarehouseDir.toUri().toString()); + GlueMetastoreStats stats = new GlueMetastoreStats(); + return new GlueHiveMetastore( + HDFS_FILE_SYSTEM_FACTORY, + glueConfig, + directExecutor(), + new DefaultGlueColumnStatisticsProviderFactory(directExecutor(), directExecutor()), + createTestingAsyncGlueClient(glueConfig, stats), + stats, + table -> true); + } + + public static AWSGlueAsync createTestingAsyncGlueClient(GlueHiveMetastoreConfig glueConfig, GlueMetastoreStats stats) + { + return createAsyncGlueClient( + glueConfig, + DefaultAWSCredentialsProviderChain.getInstance(), + ImmutableSet.of(), + stats.newRequestMetricsCollector()); + } +} diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCreateTableFailure.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCreateTableFailure.java index 3212dbeee3f8..6ee8c7a30fd1 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCreateTableFailure.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCreateTableFailure.java @@ -46,7 +46,7 @@ import static com.google.common.reflect.Reflection.newProxy; import static com.google.inject.util.Modules.EMPTY_MODULE; import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; -import static io.trino.plugin.hive.metastore.glue.GlueHiveMetastore.createTestingGlueHiveMetastore; +import static io.trino.plugin.hive.metastore.glue.TestingGlueHiveMetastore.createTestingGlueHiveMetastore; import static io.trino.testing.TestingConnectorSession.SESSION; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.testing.TestingSession.testSessionBuilder; diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueTableOperationsInsertFailure.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueTableOperationsInsertFailure.java index fe255c79597c..cc358d58da67 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueTableOperationsInsertFailure.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueTableOperationsInsertFailure.java @@ -37,7 +37,7 @@ import static com.google.common.reflect.Reflection.newProxy; import static com.google.inject.util.Modules.EMPTY_MODULE; -import static io.trino.plugin.hive.metastore.glue.GlueHiveMetastore.createTestingGlueHiveMetastore; +import static io.trino.plugin.hive.metastore.glue.TestingGlueHiveMetastore.createTestingGlueHiveMetastore; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.testing.TestingSession.testSessionBuilder; import static java.lang.String.format; diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergS3AndGlueMetastoreTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergS3AndGlueMetastoreTest.java index 65a03a866ed6..7c16726b970d 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergS3AndGlueMetastoreTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergS3AndGlueMetastoreTest.java @@ -25,7 +25,7 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -import static io.trino.plugin.hive.metastore.glue.GlueHiveMetastore.createTestingGlueHiveMetastore; +import static io.trino.plugin.hive.metastore.glue.TestingGlueHiveMetastore.createTestingGlueHiveMetastore; import static io.trino.testing.TestingNames.randomNameSuffix; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestSharedGlueMetastore.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestSharedGlueMetastore.java index d8684dfb1f9c..014800e00482 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestSharedGlueMetastore.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestSharedGlueMetastore.java @@ -29,7 +29,7 @@ import java.nio.file.Path; -import static io.trino.plugin.hive.metastore.glue.GlueHiveMetastore.createTestingGlueHiveMetastore; +import static io.trino.plugin.hive.metastore.glue.TestingGlueHiveMetastore.createTestingGlueHiveMetastore; import static io.trino.plugin.iceberg.IcebergQueryRunner.ICEBERG_CATALOG; import static io.trino.plugin.tpch.TpchMetadata.TINY_SCHEMA_NAME; import static io.trino.testing.QueryAssertions.copyTpchTables; From c29b591457fb7b92ff7e0c1f68a2e1728b3cb4cf Mon Sep 17 00:00:00 2001 From: David Phillips Date: Wed, 1 Nov 2023 15:24:46 -0700 Subject: [PATCH 032/587] Move Hadoop filesystem cache stats to HDFS module --- lib/trino-hdfs/src/main/java/io/trino/hdfs/HdfsModule.java | 4 ++++ .../src/main/java/io/trino/plugin/hive/HiveModule.java | 6 ------ .../io/trino/tests/product/deltalake/TestDeltaLakeJmx.java | 2 ++ 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/trino-hdfs/src/main/java/io/trino/hdfs/HdfsModule.java b/lib/trino-hdfs/src/main/java/io/trino/hdfs/HdfsModule.java index d358e72d7569..24f1cd876d21 100644 --- a/lib/trino-hdfs/src/main/java/io/trino/hdfs/HdfsModule.java +++ b/lib/trino-hdfs/src/main/java/io/trino/hdfs/HdfsModule.java @@ -21,6 +21,7 @@ import static com.google.inject.multibindings.Multibinder.newSetBinder; import static com.google.inject.multibindings.OptionalBinder.newOptionalBinder; import static io.airlift.configuration.ConfigBinder.configBinder; +import static org.weakref.jmx.guice.ExportBinder.newExporter; public class HdfsModule implements Module @@ -33,6 +34,9 @@ public void configure(Binder binder) binder.bind(HdfsConfiguration.class).to(DynamicHdfsConfiguration.class).in(Scopes.SINGLETON); binder.bind(HdfsEnvironment.class).in(Scopes.SINGLETON); + binder.bind(TrinoFileSystemCacheStats.class).toInstance(TrinoFileSystemCacheStats.instance()); + newExporter(binder).export(TrinoFileSystemCacheStats.class).withGeneratedName(); + binder.bind(HdfsConfigurationInitializer.class).in(Scopes.SINGLETON); newSetBinder(binder, ConfigurationInitializer.class); newSetBinder(binder, DynamicConfigurationProvider.class); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveModule.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveModule.java index a6c0b18ac6b6..e44202c0acf1 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveModule.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveModule.java @@ -21,13 +21,11 @@ import com.google.inject.Singleton; import com.google.inject.multibindings.Multibinder; import io.airlift.event.client.EventClient; -import io.trino.hdfs.TrinoFileSystemCacheStats; import io.trino.plugin.base.CatalogName; import io.trino.plugin.hive.avro.AvroFileWriterFactory; import io.trino.plugin.hive.avro.AvroPageSourceFactory; import io.trino.plugin.hive.fs.CachingDirectoryLister; import io.trino.plugin.hive.fs.TransactionScopeCachingDirectoryListerFactory; -import io.trino.plugin.hive.fs.TrinoFileSystemCache; import io.trino.plugin.hive.line.CsvFileWriterFactory; import io.trino.plugin.hive.line.CsvPageSourceFactory; import io.trino.plugin.hive.line.JsonFileWriterFactory; @@ -116,10 +114,6 @@ public void configure(Binder binder) binder.bind(FileFormatDataSourceStats.class).in(Scopes.SINGLETON); newExporter(binder).export(FileFormatDataSourceStats.class).withGeneratedName(); - binder.bind(TrinoFileSystemCacheStats.class).toInstance(TrinoFileSystemCacheStats.instance()); - newExporter(binder).export(TrinoFileSystemCacheStats.class) - .as(generator -> generator.generatedNameOf(TrinoFileSystemCache.class)); - Multibinder pageSourceFactoryBinder = newSetBinder(binder, HivePageSourceFactory.class); pageSourceFactoryBinder.addBinding().to(CsvPageSourceFactory.class).in(Scopes.SINGLETON); pageSourceFactoryBinder.addBinding().to(JsonPageSourceFactory.class).in(Scopes.SINGLETON); diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeJmx.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeJmx.java index fcd2dfb578c2..62809ed02389 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeJmx.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeJmx.java @@ -34,6 +34,7 @@ public class TestDeltaLakeJmx public void testJmxTablesExposedByDeltaLakeConnectorBackedByGlueMetastore() { assertThat(onTrino().executeQuery("SHOW TABLES IN jmx.current LIKE '%name=delta%'")).containsOnly( + row("io.trino.hdfs:name=delta,type=trinofilesystemcachestats"), row("io.trino.hdfs:name=delta,type=trinohdfsfilesystemstats"), row("io.trino.plugin.hive.metastore.cache:name=delta,type=cachinghivemetastore"), row("io.trino.plugin.hive.metastore.glue:name=delta,type=gluehivemetastore"), @@ -46,6 +47,7 @@ public void testJmxTablesExposedByDeltaLakeConnectorBackedByGlueMetastore() public void testJmxTablesExposedByDeltaLakeConnectorBackedByThriftMetastore() { assertThat(onTrino().executeQuery("SHOW TABLES IN jmx.current LIKE '%name=delta%'")).containsOnly( + row("io.trino.hdfs:name=delta,type=trinofilesystemcachestats"), row("io.trino.hdfs:name=delta,type=trinohdfsfilesystemstats"), row("io.trino.plugin.hive.metastore.cache:name=delta,type=cachinghivemetastore"), row("io.trino.plugin.hive.metastore.thrift:name=delta,type=thrifthivemetastore"), From e36646d0079f3261817166db1ce3a3c212dc4ec7 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Wed, 1 Nov 2023 15:48:22 -0700 Subject: [PATCH 033/587] Move Rubix binding to HDFS module --- .../io/trino/filesystem/manager/FileSystemModule.java | 4 ++++ plugin/trino-hive/pom.xml | 11 ++++++----- .../plugin/hive/InternalHiveConnectorFactory.java | 4 ---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/FileSystemModule.java b/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/FileSystemModule.java index b7c1c35e5f75..b366955b5287 100644 --- a/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/FileSystemModule.java +++ b/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/FileSystemModule.java @@ -32,6 +32,8 @@ import io.trino.hdfs.azure.HiveAzureModule; import io.trino.hdfs.cos.HiveCosModule; import io.trino.hdfs.gcs.HiveGcsModule; +import io.trino.hdfs.rubix.RubixEnabledConfig; +import io.trino.hdfs.rubix.RubixModule; import io.trino.hdfs.s3.HiveS3Module; import java.util.Map; @@ -39,6 +41,7 @@ import static com.google.inject.Scopes.SINGLETON; import static com.google.inject.multibindings.MapBinder.newMapBinder; +import static io.airlift.configuration.ConditionalModule.conditionalModule; public class FileSystemModule extends AbstractConfigurationAwareModule @@ -54,6 +57,7 @@ protected void setup(Binder binder) install(new HdfsFileSystemModule()); install(new HdfsModule()); install(new HdfsAuthenticationModule()); + install(conditionalModule(RubixEnabledConfig.class, RubixEnabledConfig::isCacheEnabled, new RubixModule())); install(new HiveCosModule()); install(new HiveGcsModule()); } diff --git a/plugin/trino-hive/pom.xml b/plugin/trino-hive/pom.xml index e63b4aa40e03..e6e97e1775b5 100644 --- a/plugin/trino-hive/pom.xml +++ b/plugin/trino-hive/pom.xml @@ -151,11 +151,6 @@ trino-filesystem-manager - - io.trino - trino-hdfs - - io.trino trino-hive-formats @@ -299,6 +294,12 @@ runtime + + io.trino + trino-hdfs + runtime + + io.trino.hadoop hadoop-apache diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/InternalHiveConnectorFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/InternalHiveConnectorFactory.java index 53a165fd96a4..4f5c0c2c656a 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/InternalHiveConnectorFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/InternalHiveConnectorFactory.java @@ -27,8 +27,6 @@ import io.opentelemetry.api.trace.Tracer; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.filesystem.manager.FileSystemModule; -import io.trino.hdfs.rubix.RubixEnabledConfig; -import io.trino.hdfs.rubix.RubixModule; import io.trino.plugin.base.CatalogName; import io.trino.plugin.base.CatalogNameModule; import io.trino.plugin.base.TypeDeserializerModule; @@ -73,7 +71,6 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.inject.multibindings.Multibinder.newSetBinder; -import static io.airlift.configuration.ConditionalModule.conditionalModule; import static java.util.Objects.requireNonNull; public final class InternalHiveConnectorFactory @@ -109,7 +106,6 @@ public static Connector createConnector( new HiveModule(), new PartitionProjectionModule(), new CachingDirectoryListerModule(directoryLister), - conditionalModule(RubixEnabledConfig.class, RubixEnabledConfig::isCacheEnabled, new RubixModule()), new HiveMetastoreModule(metastore), new HiveSecurityModule(), fileSystemFactory From 34fa7f26a213538745d3f1d803374d09adae8d47 Mon Sep 17 00:00:00 2001 From: ajantha-bhat Date: Thu, 27 Jul 2023 13:04:13 +0530 Subject: [PATCH 034/587] Support additional configurations for Nessie catalog Nessie server configurations like read-timeout-ms, connect-timeout-ms and enable-compression properties are exposed to have finer control over the Nessie commits. --- docs/src/main/sphinx/connector/metastores.md | 11 +++- .../nessie/IcebergNessieCatalogConfig.java | 50 +++++++++++++++++++ .../nessie/IcebergNessieCatalogModule.java | 12 +++-- .../TestIcebergNessieCatalogConfig.java | 18 ++++++- 4 files changed, 84 insertions(+), 7 deletions(-) diff --git a/docs/src/main/sphinx/connector/metastores.md b/docs/src/main/sphinx/connector/metastores.md index c0f2a8d22c8b..ea62e3d6c585 100644 --- a/docs/src/main/sphinx/connector/metastores.md +++ b/docs/src/main/sphinx/connector/metastores.md @@ -422,10 +422,19 @@ properties: - Nessie API endpoint URI (required). Example: ``https://localhost:19120/api/v1`` * - ``iceberg.nessie-catalog.ref`` - - The branch/tag to use for Nessie, defaults to ``main``. + - The branch/tag to use for Nessie. Defaults to ``main``. * - ``iceberg.nessie-catalog.default-warehouse-dir`` - Default warehouse directory for schemas created without an explicit ``location`` property. Example: ``/tmp`` + * - ``iceberg.nessie-catalog.read-timeout`` + - The read timeout :ref:`duration ` for requests + to the Nessie server. Defaults to ``25s``. + * - ``iceberg.nessie-catalog.connection-timeout`` + - The connection timeout :ref:`duration ` for + connection requests to the Nessie server. Defaults to ``5s``. + * - ``iceberg.nessie-catalog.enable-compression`` + - Configure whether compression should be enabled or not for + requests to the Nessie server. Defaults to ``true``. ``` ```text diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/IcebergNessieCatalogConfig.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/IcebergNessieCatalogConfig.java index 492d0b3fa289..6ce104fb8544 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/IcebergNessieCatalogConfig.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/IcebergNessieCatalogConfig.java @@ -15,16 +15,25 @@ import io.airlift.configuration.Config; import io.airlift.configuration.ConfigDescription; +import io.airlift.units.Duration; +import io.airlift.units.MinDuration; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import java.net.URI; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.projectnessie.client.NessieConfigConstants.DEFAULT_CONNECT_TIMEOUT_MILLIS; +import static org.projectnessie.client.NessieConfigConstants.DEFAULT_READ_TIMEOUT_MILLIS; + public class IcebergNessieCatalogConfig { private String defaultReferenceName = "main"; private String defaultWarehouseDir; private URI serverUri; + private Duration readTimeout = new Duration(DEFAULT_READ_TIMEOUT_MILLIS, MILLISECONDS); + private Duration connectionTimeout = new Duration(DEFAULT_CONNECT_TIMEOUT_MILLIS, MILLISECONDS); + private boolean enableCompression = true; @NotNull public String getDefaultReferenceName() @@ -67,4 +76,45 @@ public IcebergNessieCatalogConfig setDefaultWarehouseDir(String defaultWarehouse this.defaultWarehouseDir = defaultWarehouseDir; return this; } + + @MinDuration("1ms") + public Duration getReadTimeout() + { + return readTimeout; + } + + @Config("iceberg.nessie-catalog.read-timeout") + @ConfigDescription("The read timeout for the client.") + public IcebergNessieCatalogConfig setReadTimeout(Duration readTimeout) + { + this.readTimeout = readTimeout; + return this; + } + + @MinDuration("1ms") + public Duration getConnectionTimeout() + { + return connectionTimeout; + } + + @Config("iceberg.nessie-catalog.connection-timeout") + @ConfigDescription("The connection timeout for the client.") + public IcebergNessieCatalogConfig setConnectionTimeout(Duration connectionTimeout) + { + this.connectionTimeout = connectionTimeout; + return this; + } + + public boolean isCompressionEnabled() + { + return enableCompression; + } + + @Config("iceberg.nessie-catalog.enable-compression") + @ConfigDescription("Configure whether compression should be enabled or not.") + public IcebergNessieCatalogConfig setCompressionEnabled(boolean enableCompression) + { + this.enableCompression = enableCompression; + return this; + } } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/IcebergNessieCatalogModule.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/IcebergNessieCatalogModule.java index f0e5de3ec30a..80d6e06df8d1 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/IcebergNessieCatalogModule.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/IcebergNessieCatalogModule.java @@ -26,6 +26,7 @@ import org.projectnessie.client.http.HttpClientBuilder; import static io.airlift.configuration.ConfigBinder.configBinder; +import static java.lang.Math.toIntExact; import static org.weakref.jmx.guice.ExportBinder.newExporter; public class IcebergNessieCatalogModule @@ -45,10 +46,13 @@ protected void setup(Binder binder) @Singleton public static NessieIcebergClient createNessieIcebergClient(IcebergNessieCatalogConfig icebergNessieCatalogConfig) { - return new NessieIcebergClient( - HttpClientBuilder.builder() - .withUri(icebergNessieCatalogConfig.getServerUri()) - .build(NessieApiV1.class), + HttpClientBuilder builder = HttpClientBuilder.builder() + .withUri(icebergNessieCatalogConfig.getServerUri()) + .withDisableCompression(!icebergNessieCatalogConfig.isCompressionEnabled()) + .withReadTimeout(toIntExact(icebergNessieCatalogConfig.getReadTimeout().toMillis())) + .withConnectionTimeout(toIntExact(icebergNessieCatalogConfig.getConnectionTimeout().toMillis())); + + return new NessieIcebergClient(builder.build(NessieApiV1.class), icebergNessieCatalogConfig.getDefaultReferenceName(), null, ImmutableMap.of()); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/nessie/TestIcebergNessieCatalogConfig.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/nessie/TestIcebergNessieCatalogConfig.java index 805b66dea860..6ad23a9d5047 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/nessie/TestIcebergNessieCatalogConfig.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/nessie/TestIcebergNessieCatalogConfig.java @@ -14,14 +14,19 @@ package io.trino.plugin.iceberg.catalog.nessie; import com.google.common.collect.ImmutableMap; +import io.airlift.units.Duration; import org.junit.jupiter.api.Test; import java.net.URI; import java.util.Map; +import java.util.concurrent.TimeUnit; import static io.airlift.configuration.testing.ConfigAssertions.assertFullMapping; import static io.airlift.configuration.testing.ConfigAssertions.assertRecordedDefaults; import static io.airlift.configuration.testing.ConfigAssertions.recordDefaults; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.projectnessie.client.NessieConfigConstants.DEFAULT_CONNECT_TIMEOUT_MILLIS; +import static org.projectnessie.client.NessieConfigConstants.DEFAULT_READ_TIMEOUT_MILLIS; public class TestIcebergNessieCatalogConfig { @@ -31,7 +36,10 @@ public void testDefaults() assertRecordedDefaults(recordDefaults(IcebergNessieCatalogConfig.class) .setDefaultWarehouseDir(null) .setServerUri(null) - .setDefaultReferenceName("main")); + .setDefaultReferenceName("main") + .setCompressionEnabled(true) + .setConnectionTimeout(new Duration(DEFAULT_CONNECT_TIMEOUT_MILLIS, MILLISECONDS)) + .setReadTimeout(new Duration(DEFAULT_READ_TIMEOUT_MILLIS, MILLISECONDS))); } @Test @@ -41,12 +49,18 @@ public void testExplicitPropertyMapping() .put("iceberg.nessie-catalog.default-warehouse-dir", "/tmp") .put("iceberg.nessie-catalog.uri", "http://localhost:xxx/api/v1") .put("iceberg.nessie-catalog.ref", "someRef") + .put("iceberg.nessie-catalog.enable-compression", "false") + .put("iceberg.nessie-catalog.connection-timeout", "2s") + .put("iceberg.nessie-catalog.read-timeout", "5m") .buildOrThrow(); IcebergNessieCatalogConfig expected = new IcebergNessieCatalogConfig() .setDefaultWarehouseDir("/tmp") .setServerUri(URI.create("http://localhost:xxx/api/v1")) - .setDefaultReferenceName("someRef"); + .setDefaultReferenceName("someRef") + .setCompressionEnabled(false) + .setConnectionTimeout(new Duration(2, TimeUnit.SECONDS)) + .setReadTimeout(new Duration(5, TimeUnit.MINUTES)); assertFullMapping(properties, expected); } From 73b1f75ab638ed9df721f0404afcd2ff364c8039 Mon Sep 17 00:00:00 2001 From: ajantha-bhat Date: Thu, 1 Jun 2023 12:05:09 +0530 Subject: [PATCH 035/587] Support authentication configurations for Nessie catalog Nessie server can be deployed with Authentication mode with keycloak. So, need to expose the Nessie client configurations to handle the Auth. --- docs/src/main/sphinx/connector/metastores.md | 6 + plugin/trino-iceberg/pom.xml | 25 ++++ .../nessie/IcebergNessieCatalogConfig.java | 51 ++++++++ .../nessie/IcebergNessieCatalogModule.java | 4 + .../plugin/iceberg/TestIcebergPlugin.java | 53 ++++++++ .../TestIcebergNessieCatalogConfig.java | 10 +- ...estIcebergNessieCatalogWithBearerAuth.java | 81 ++++++++++++ .../iceberg/containers/KeycloakContainer.java | 123 ++++++++++++++++++ .../iceberg/containers/NessieContainer.java | 15 ++- 9 files changed, 364 insertions(+), 4 deletions(-) create mode 100644 plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/nessie/TestIcebergNessieCatalogWithBearerAuth.java create mode 100644 plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/containers/KeycloakContainer.java diff --git a/docs/src/main/sphinx/connector/metastores.md b/docs/src/main/sphinx/connector/metastores.md index ea62e3d6c585..4e3c5b1f8632 100644 --- a/docs/src/main/sphinx/connector/metastores.md +++ b/docs/src/main/sphinx/connector/metastores.md @@ -435,6 +435,12 @@ properties: * - ``iceberg.nessie-catalog.enable-compression`` - Configure whether compression should be enabled or not for requests to the Nessie server. Defaults to ``true``. + * - ``iceberg.nessie-catalog.authentication.type`` + - The authentication type to use. + Available value is ``BEARER``. Defaults to no authentication. + * - ``iceberg.nessie-catalog.authentication.token`` + - The token to use with ``BEARER`` authentication. + Example: ``SXVLUXUhIExFQ0tFUiEK`` ``` ```text diff --git a/plugin/trino-iceberg/pom.xml b/plugin/trino-iceberg/pom.xml index 4613d3d39186..27e6a8e49c8e 100644 --- a/plugin/trino-iceberg/pom.xml +++ b/plugin/trino-iceberg/pom.xml @@ -24,6 +24,7 @@ TODO (https://github.com/trinodb/trino/issues/11294) remove when we upgrade to surefire with https://issues.apache.org/jira/browse/SUREFIRE-1967 --> instances + 21.1.2 0.71.0 @@ -572,6 +573,30 @@ test + + org.keycloak + keycloak-admin-client-jakarta + ${dep.keycloak.version} + test + + + org.jboss.resteasy + resteasy-jaxb-provider + + + org.jboss.spec.javax.ws.rs + jboss-jaxrs-api_3.0_spec + + + + + + org.keycloak + keycloak-core + ${dep.keycloak.version} + test + + org.testcontainers postgresql diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/IcebergNessieCatalogConfig.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/IcebergNessieCatalogConfig.java index 6ce104fb8544..4e7793c7cea5 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/IcebergNessieCatalogConfig.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/IcebergNessieCatalogConfig.java @@ -15,25 +15,37 @@ import io.airlift.configuration.Config; import io.airlift.configuration.ConfigDescription; +import io.airlift.configuration.ConfigSecuritySensitive; import io.airlift.units.Duration; import io.airlift.units.MinDuration; +import jakarta.validation.constraints.AssertTrue; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import java.net.URI; +import java.util.Optional; +import static io.trino.plugin.iceberg.catalog.nessie.IcebergNessieCatalogConfig.Security.BEARER; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.function.Predicate.isEqual; import static org.projectnessie.client.NessieConfigConstants.DEFAULT_CONNECT_TIMEOUT_MILLIS; import static org.projectnessie.client.NessieConfigConstants.DEFAULT_READ_TIMEOUT_MILLIS; public class IcebergNessieCatalogConfig { + public enum Security + { + BEARER, + } + private String defaultReferenceName = "main"; private String defaultWarehouseDir; private URI serverUri; private Duration readTimeout = new Duration(DEFAULT_READ_TIMEOUT_MILLIS, MILLISECONDS); private Duration connectionTimeout = new Duration(DEFAULT_CONNECT_TIMEOUT_MILLIS, MILLISECONDS); private boolean enableCompression = true; + private Security security; + private Optional bearerToken = Optional.empty(); @NotNull public String getDefaultReferenceName() @@ -117,4 +129,43 @@ public IcebergNessieCatalogConfig setCompressionEnabled(boolean enableCompressio this.enableCompression = enableCompression; return this; } + + public Optional getSecurity() + { + return Optional.ofNullable(security); + } + + @Config("iceberg.nessie-catalog.authentication.type") + @ConfigDescription("The authentication type to use") + public IcebergNessieCatalogConfig setSecurity(Security security) + { + this.security = security; + return this; + } + + public Optional getBearerToken() + { + return bearerToken; + } + + @Config("iceberg.nessie-catalog.authentication.token") + @ConfigDescription("The token to use with BEARER authentication") + @ConfigSecuritySensitive + public IcebergNessieCatalogConfig setBearerToken(String token) + { + this.bearerToken = Optional.ofNullable(token); + return this; + } + + @AssertTrue(message = "'iceberg.nessie-catalog.authentication.token' must be configured only with 'iceberg.nessie-catalog.authentication.type' BEARER") + public boolean isTokenConfiguredWithoutType() + { + return getSecurity().filter(isEqual(BEARER)).isPresent() || getBearerToken().isEmpty(); + } + + @AssertTrue(message = "'iceberg.nessie-catalog.authentication.token' must be configured with 'iceberg.nessie-catalog.authentication.type' BEARER") + public boolean isMissingTokenForBearerAuth() + { + return getSecurity().filter(isEqual(BEARER)).isEmpty() || getBearerToken().isPresent(); + } } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/IcebergNessieCatalogModule.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/IcebergNessieCatalogModule.java index 80d6e06df8d1..7a2008dfc0c4 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/IcebergNessieCatalogModule.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/IcebergNessieCatalogModule.java @@ -23,6 +23,7 @@ import io.trino.plugin.iceberg.catalog.TrinoCatalogFactory; import org.apache.iceberg.nessie.NessieIcebergClient; import org.projectnessie.client.api.NessieApiV1; +import org.projectnessie.client.auth.BearerAuthenticationProvider; import org.projectnessie.client.http.HttpClientBuilder; import static io.airlift.configuration.ConfigBinder.configBinder; @@ -52,6 +53,9 @@ public static NessieIcebergClient createNessieIcebergClient(IcebergNessieCatalog .withReadTimeout(toIntExact(icebergNessieCatalogConfig.getReadTimeout().toMillis())) .withConnectionTimeout(toIntExact(icebergNessieCatalogConfig.getConnectionTimeout().toMillis())); + icebergNessieCatalogConfig.getBearerToken() + .ifPresent(token -> builder.withAuthentication(BearerAuthenticationProvider.create(token))); + return new NessieIcebergClient(builder.build(NessieApiV1.class), icebergNessieCatalogConfig.getDefaultReferenceName(), null, diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergPlugin.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergPlugin.java index a57c3de254f9..5fcc3c7a0751 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergPlugin.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergPlugin.java @@ -288,6 +288,59 @@ public void testNessieCatalog() .shutdown(); } + @Test + public void testNessieCatalogWithBearerAuth() + { + ConnectorFactory factory = getConnectorFactory(); + + factory.create( + "test", + Map.of( + "iceberg.catalog.type", "nessie", + "iceberg.nessie-catalog.default-warehouse-dir", "/tmp", + "iceberg.nessie-catalog.uri", "http://foo:1234", + "iceberg.nessie-catalog.authentication.type", "BEARER", + "iceberg.nessie-catalog.authentication.token", "someToken"), + new TestingConnectorContext()) + .shutdown(); + } + + @Test + public void testNessieCatalogWithNoAuthAndAccessToken() + { + ConnectorFactory factory = getConnectorFactory(); + + assertThatThrownBy(() -> factory.create( + "test", + Map.of( + "iceberg.catalog.type", "nessie", + "iceberg.nessie-catalog.uri", "nessieUri", + "iceberg.nessie-catalog.default-warehouse-dir", "/tmp", + "iceberg.nessie-catalog.authentication.token", "someToken"), + new TestingConnectorContext()) + .shutdown()) + .isInstanceOf(ApplicationConfigurationException.class) + .hasMessageContaining("'iceberg.nessie-catalog.authentication.token' must be configured only with 'iceberg.nessie-catalog.authentication.type' BEARER"); + } + + @Test + public void testNessieCatalogWithNoAccessToken() + { + ConnectorFactory factory = getConnectorFactory(); + + assertThatThrownBy(() -> factory.create( + "test", + Map.of( + "iceberg.catalog.type", "nessie", + "iceberg.nessie-catalog.uri", "nessieUri", + "iceberg.nessie-catalog.default-warehouse-dir", "/tmp", + "iceberg.nessie-catalog.authentication.type", "BEARER"), + new TestingConnectorContext()) + .shutdown()) + .isInstanceOf(ApplicationConfigurationException.class) + .hasMessageContaining("'iceberg.nessie-catalog.authentication.token' must be configured with 'iceberg.nessie-catalog.authentication.type' BEARER"); + } + private static ConnectorFactory getConnectorFactory() { return getOnlyElement(new IcebergPlugin().getConnectorFactories()); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/nessie/TestIcebergNessieCatalogConfig.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/nessie/TestIcebergNessieCatalogConfig.java index 6ad23a9d5047..d01eff2aff21 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/nessie/TestIcebergNessieCatalogConfig.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/nessie/TestIcebergNessieCatalogConfig.java @@ -39,7 +39,9 @@ public void testDefaults() .setDefaultReferenceName("main") .setCompressionEnabled(true) .setConnectionTimeout(new Duration(DEFAULT_CONNECT_TIMEOUT_MILLIS, MILLISECONDS)) - .setReadTimeout(new Duration(DEFAULT_READ_TIMEOUT_MILLIS, MILLISECONDS))); + .setReadTimeout(new Duration(DEFAULT_READ_TIMEOUT_MILLIS, MILLISECONDS)) + .setSecurity(null) + .setBearerToken(null)); } @Test @@ -52,6 +54,8 @@ public void testExplicitPropertyMapping() .put("iceberg.nessie-catalog.enable-compression", "false") .put("iceberg.nessie-catalog.connection-timeout", "2s") .put("iceberg.nessie-catalog.read-timeout", "5m") + .put("iceberg.nessie-catalog.authentication.type", "BEARER") + .put("iceberg.nessie-catalog.authentication.token", "bearerToken") .buildOrThrow(); IcebergNessieCatalogConfig expected = new IcebergNessieCatalogConfig() @@ -60,7 +64,9 @@ public void testExplicitPropertyMapping() .setDefaultReferenceName("someRef") .setCompressionEnabled(false) .setConnectionTimeout(new Duration(2, TimeUnit.SECONDS)) - .setReadTimeout(new Duration(5, TimeUnit.MINUTES)); + .setReadTimeout(new Duration(5, TimeUnit.MINUTES)) + .setSecurity(IcebergNessieCatalogConfig.Security.BEARER) + .setBearerToken("bearerToken"); assertFullMapping(properties, expected); } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/nessie/TestIcebergNessieCatalogWithBearerAuth.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/nessie/TestIcebergNessieCatalogWithBearerAuth.java new file mode 100644 index 000000000000..85ddbcbaa524 --- /dev/null +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/nessie/TestIcebergNessieCatalogWithBearerAuth.java @@ -0,0 +1,81 @@ +/* + * 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 io.trino.plugin.iceberg.catalog.nessie; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.trino.plugin.iceberg.IcebergQueryRunner; +import io.trino.plugin.iceberg.containers.KeycloakContainer; +import io.trino.plugin.iceberg.containers.NessieContainer; +import io.trino.testing.AbstractTestQueryFramework; +import io.trino.testing.QueryRunner; +import io.trino.testing.sql.TestTable; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.Network; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import java.util.Optional; + +import static com.google.common.io.MoreFiles.deleteRecursively; +import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; + +public class TestIcebergNessieCatalogWithBearerAuth + extends AbstractTestQueryFramework +{ + @Override + protected QueryRunner createQueryRunner() + throws Exception + { + Network network = closeAfterClass(Network.newNetwork()); + + KeycloakContainer keycloakContainer = closeAfterClass(KeycloakContainer.builder().withNetwork(network).build()); + keycloakContainer.start(); + + Map envVars = ImmutableMap.builder() + .putAll(NessieContainer.DEFAULT_ENV_VARS) + .put("QUARKUS_OIDC_AUTH_SERVER_URL", KeycloakContainer.SERVER_URL + "/realms/master") + .put("QUARKUS_OIDC_CLIENT_ID", "projectnessie") + .put("NESSIE_SERVER_AUTHENTICATION_ENABLED", "true") + .buildOrThrow(); + + NessieContainer nessieContainer = closeAfterClass(NessieContainer.builder().withEnvVars(envVars).withNetwork(network).build()); + nessieContainer.start(); + + Path tempDir = Files.createTempDirectory("test_trino_nessie_catalog"); + closeAfterClass(() -> deleteRecursively(tempDir, ALLOW_INSECURE)); + + Map properties = ImmutableMap.builder() + .put("iceberg.catalog.type", "nessie") + .put("iceberg.nessie-catalog.uri", nessieContainer.getRestApiUri()) + .put("iceberg.nessie-catalog.default-warehouse-dir", tempDir.toString()) + .put("iceberg.nessie-catalog.authentication.type", "BEARER") + .put("iceberg.nessie-catalog.authentication.token", keycloakContainer.getAccessToken()) + .buildOrThrow(); + + return IcebergQueryRunner.builder() + .setBaseDataDir(Optional.of(tempDir)) + .setIcebergProperties(properties) + .build(); + } + + @Test + public void testWithValidAccessToken() + { + try (TestTable table = new TestTable(getQueryRunner()::execute, "test_valid_access_token", "(a INT, b VARCHAR)", ImmutableList.of("(1, 'a')"))) { + assertQuery("SELECT * FROM " + table.getName(), "VALUES(1, 'a')"); + } + } +} diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/containers/KeycloakContainer.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/containers/KeycloakContainer.java new file mode 100644 index 000000000000..43d5801b4218 --- /dev/null +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/containers/KeycloakContainer.java @@ -0,0 +1,123 @@ +/* + * 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 io.trino.plugin.iceberg.containers; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import io.airlift.log.Logger; +import io.trino.testing.containers.BaseTestContainer; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.KeycloakBuilder; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.representations.idm.RealmRepresentation; +import org.testcontainers.containers.Network; + +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +public class KeycloakContainer + extends BaseTestContainer +{ + private static final Logger log = Logger.get(KeycloakContainer.class); + + public static final String DEFAULT_IMAGE = "quay.io/keycloak/keycloak:21.1.2"; + public static final String DEFAULT_HOST_NAME = "keycloak"; + + public static final String DEFAULT_USER_NAME = "admin"; + public static final String DEFAULT_PASSWORD = "admin"; + + public static final int PORT = 8080; + public static final String SERVER_URL = "http://" + DEFAULT_HOST_NAME + ":" + PORT; + + public static Builder builder() + { + return new Builder(); + } + + private KeycloakContainer( + String image, + String hostName, + Set exposePorts, + Map filesToMount, + Map envVars, + Optional network, + int retryLimit) + { + super(image, hostName, exposePorts, filesToMount, envVars, network, retryLimit); + } + + @Override + protected void setupContainer() + { + super.setupContainer(); + withRunCommand(ImmutableList.of("start-dev")); + } + + @Override + public void start() + { + super.start(); + log.info("Keycloak container started with URL: %s", getUrl()); + } + + public String getUrl() + { + return "http://" + getMappedHostAndPortForExposedPort(PORT); + } + + public String getAccessToken() + { + String realm = "master"; + String clientId = "admin-cli"; + + try (Keycloak keycloak = KeycloakBuilder.builder() + .serverUrl(getUrl()) + .realm(realm) + .clientId(clientId) + .username(DEFAULT_USER_NAME) + .password(DEFAULT_PASSWORD) + .build()) { + RealmResource master = keycloak.realms().realm(realm); + RealmRepresentation masterRep = master.toRepresentation(); + // change access token lifespan from 1 minute (default) to 1 hour + // to keep the token alive in case testcase takes more than a minute to finish execution. + masterRep.setAccessTokenLifespan(3600); + master.update(masterRep); + return keycloak.tokenManager().grantToken().getToken(); + } + } + + public static class Builder + extends BaseTestContainer.Builder + { + private Builder() + { + this.image = DEFAULT_IMAGE; + this.hostName = DEFAULT_HOST_NAME; + this.exposePorts = ImmutableSet.of(PORT); + this.envVars = ImmutableMap.of( + "KEYCLOAK_ADMIN", DEFAULT_USER_NAME, + "KEYCLOAK_ADMIN_PASSWORD", DEFAULT_PASSWORD, + "KC_HOSTNAME_URL", SERVER_URL); + } + + @Override + public KeycloakContainer build() + { + return new KeycloakContainer(image, hostName, exposePorts, filesToMount, envVars, network, startupRetryLimit); + } + } +} diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/containers/NessieContainer.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/containers/NessieContainer.java index d5f27d8f0ff9..a50765d83c5b 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/containers/NessieContainer.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/containers/NessieContainer.java @@ -34,12 +34,23 @@ public class NessieContainer public static final int PORT = 19121; + public static final Map DEFAULT_ENV_VARS = ImmutableMap.of( + "QUARKUS_HTTP_PORT", String.valueOf(PORT), + "NESSIE_VERSION_STORE_TYPE", VERSION_STORE_TYPE); + public static Builder builder() { return new Builder(); } - private NessieContainer(String image, String hostName, Set exposePorts, Map filesToMount, Map envVars, Optional network, int retryLimit) + private NessieContainer( + String image, + String hostName, + Set exposePorts, + Map filesToMount, + Map envVars, + Optional network, + int retryLimit) { super(image, hostName, exposePorts, filesToMount, envVars, network, retryLimit); } @@ -64,7 +75,7 @@ private Builder() this.image = DEFAULT_IMAGE; this.hostName = DEFAULT_HOST_NAME; this.exposePorts = ImmutableSet.of(PORT); - this.envVars = ImmutableMap.of("QUARKUS_HTTP_PORT", String.valueOf(PORT), "NESSIE_VERSION_STORE_TYPE", VERSION_STORE_TYPE); + this.envVars = DEFAULT_ENV_VARS; } @Override From 980fb1095bd93a09dbd78ec4acd259c4ec3c3e94 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Thu, 2 Nov 2023 11:23:07 +0100 Subject: [PATCH 036/587] Fix invalid error message assertion for BigQuery --- .../io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java index 692239f4e65a..85b775e6c2b4 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java @@ -576,7 +576,7 @@ public void testDateYearOfEraPredicate() // Override because the connector throws an exception instead of an empty result when the value is out of supported range assertQuery("SELECT orderdate FROM orders WHERE orderdate = DATE '1997-09-14'", "VALUES DATE '1997-09-14'"); assertThatThrownBy(() -> query("SELECT * FROM orders WHERE orderdate = DATE '-1996-09-14'")) - .hasMessageMatching(".*Row filter for .* is invalid\\. Filter is '\\(`orderdate` = '-1996-09-14'\\)'"); + .hasMessageMatching(".*Could not cast literal \"-1996-09-14\" to type DATE.*"); } @Test From 9d82218f8d0cae7a424cc75886de9eb7220171d1 Mon Sep 17 00:00:00 2001 From: Karol Sobczak Date: Thu, 2 Nov 2023 11:33:10 +0100 Subject: [PATCH 037/587] Remove late materialization support --- .../main/java/io/trino/FeaturesConfig.java | 17 +- .../io/trino/SystemSessionProperties.java | 11 - .../operator/FilterAndProjectOperator.java | 3 +- .../ScanFilterAndProjectOperator.java | 40 +- .../io/trino/operator/TableScanOperator.java | 40 +- .../TableScanWorkProcessorOperator.java | 284 ------- .../WorkProcessorOperatorAdapter.java | 41 +- .../WorkProcessorPipelineSourceOperator.java | 777 ------------------ .../WorkProcessorSourceOperatorAdapter.java | 17 +- .../trino/operator/project/PageProcessor.java | 46 +- .../sql/planner/LocalExecutionPlanner.java | 59 +- ...stWorkProcessorPipelineSourceOperator.java | 567 ------------- ...estWorkProcessorSourceOperatorAdapter.java | 3 +- .../operator/project/TestPageProcessor.java | 44 +- .../sql/analyzer/TestFeaturesConfig.java | 3 - .../AbstractTestEngineOnlyQueries.java | 18 - .../tests/TestLateMaterializationQueries.java | 120 --- 17 files changed, 33 insertions(+), 2057 deletions(-) delete mode 100644 core/trino-main/src/main/java/io/trino/operator/TableScanWorkProcessorOperator.java delete mode 100644 core/trino-main/src/main/java/io/trino/operator/WorkProcessorPipelineSourceOperator.java delete mode 100644 core/trino-main/src/test/java/io/trino/operator/TestWorkProcessorPipelineSourceOperator.java delete mode 100644 testing/trino-tests/src/test/java/io/trino/tests/TestLateMaterializationQueries.java diff --git a/core/trino-main/src/main/java/io/trino/FeaturesConfig.java b/core/trino-main/src/main/java/io/trino/FeaturesConfig.java index 9963288f623c..84ace3eea33a 100644 --- a/core/trino-main/src/main/java/io/trino/FeaturesConfig.java +++ b/core/trino-main/src/main/java/io/trino/FeaturesConfig.java @@ -64,7 +64,8 @@ "spill-window-operator", "experimental.spill-window-operator", "legacy.allow-set-view-authorization", - "parse-decimal-literals-as-double" + "parse-decimal-literals-as-double", + "experimental.late-materialization.enabled" }) public class FeaturesConfig { @@ -94,7 +95,6 @@ public class FeaturesConfig private double spillMaxUsedSpaceThreshold = 0.9; private double memoryRevokingTarget = 0.5; private double memoryRevokingThreshold = 0.9; - private boolean lateMaterializationEnabled; private DataSize filterAndProjectMinOutputPageSize = DataSize.of(500, KILOBYTE); private int filterAndProjectMinOutputPageRowCount = 256; @@ -417,19 +417,6 @@ public FeaturesConfig setMaxGroupingSets(int maxGroupingSets) return this; } - public boolean isLateMaterializationEnabled() - { - return lateMaterializationEnabled; - } - - @Config("experimental.late-materialization.enabled") - @LegacyConfig("experimental.work-processor-pipelines") - public FeaturesConfig setLateMaterializationEnabled(boolean lateMaterializationEnabled) - { - this.lateMaterializationEnabled = lateMaterializationEnabled; - return this; - } - public boolean isLegacyCatalogRoles() { return legacyCatalogRoles; diff --git a/core/trino-main/src/main/java/io/trino/SystemSessionProperties.java b/core/trino-main/src/main/java/io/trino/SystemSessionProperties.java index 1e3c3c6f7a9f..323592aa51ed 100644 --- a/core/trino-main/src/main/java/io/trino/SystemSessionProperties.java +++ b/core/trino-main/src/main/java/io/trino/SystemSessionProperties.java @@ -135,7 +135,6 @@ public final class SystemSessionProperties public static final String ALLOW_PUSHDOWN_INTO_CONNECTORS = "allow_pushdown_into_connectors"; public static final String COMPLEX_EXPRESSION_PUSHDOWN = "complex_expression_pushdown"; public static final String PREDICATE_PUSHDOWN_USE_TABLE_PROPERTIES = "predicate_pushdown_use_table_properties"; - public static final String LATE_MATERIALIZATION = "late_materialization"; public static final String ENABLE_DYNAMIC_FILTERING = "enable_dynamic_filtering"; public static final String ENABLE_COORDINATOR_DYNAMIC_FILTERS_DISTRIBUTION = "enable_coordinator_dynamic_filters_distribution"; public static final String ENABLE_LARGE_DYNAMIC_FILTERS = "enable_large_dynamic_filters"; @@ -664,11 +663,6 @@ public SystemSessionProperties( "Use table properties in predicate pushdown", optimizerConfig.isPredicatePushdownUseTableProperties(), false), - booleanProperty( - LATE_MATERIALIZATION, - "Experimental: Use late materialization (including WorkProcessor pipelines)", - featuresConfig.isLateMaterializationEnabled(), - false), booleanProperty( ENABLE_DYNAMIC_FILTERING, "Enable dynamic filtering", @@ -1542,11 +1536,6 @@ public static boolean isPredicatePushdownUseTableProperties(Session session) return session.getSystemProperty(PREDICATE_PUSHDOWN_USE_TABLE_PROPERTIES, Boolean.class); } - public static boolean isLateMaterializationEnabled(Session session) - { - return session.getSystemProperty(LATE_MATERIALIZATION, Boolean.class); - } - public static boolean isEnableDynamicFiltering(Session session) { return session.getSystemProperty(ENABLE_DYNAMIC_FILTERING, Boolean.class); diff --git a/core/trino-main/src/main/java/io/trino/operator/FilterAndProjectOperator.java b/core/trino-main/src/main/java/io/trino/operator/FilterAndProjectOperator.java index 987d64300f9e..7b9c613524ea 100644 --- a/core/trino-main/src/main/java/io/trino/operator/FilterAndProjectOperator.java +++ b/core/trino-main/src/main/java/io/trino/operator/FilterAndProjectOperator.java @@ -64,8 +64,7 @@ private FilterAndProjectOperator( yieldSignal, outputMemoryContext, metrics, - page, - avoidPageMaterialization)) + page)) .transformProcessor(processor -> mergePages(types, minOutputPageSize.toBytes(), minOutputPageRowCount, processor, localAggregatedMemoryContext)) .blocking(() -> memoryTrackingContext.localUserMemoryContext().setBytes(localAggregatedMemoryContext.getBytes())); } diff --git a/core/trino-main/src/main/java/io/trino/operator/ScanFilterAndProjectOperator.java b/core/trino-main/src/main/java/io/trino/operator/ScanFilterAndProjectOperator.java index c04e5ffca005..56163b947b1d 100644 --- a/core/trino-main/src/main/java/io/trino/operator/ScanFilterAndProjectOperator.java +++ b/core/trino-main/src/main/java/io/trino/operator/ScanFilterAndProjectOperator.java @@ -26,7 +26,6 @@ import io.trino.metadata.TableHandle; import io.trino.operator.WorkProcessor.ProcessState; import io.trino.operator.WorkProcessor.TransformationState; -import io.trino.operator.WorkProcessorSourceOperatorAdapter.AdapterWorkProcessorSourceOperatorFactory; import io.trino.operator.project.CursorProcessor; import io.trino.operator.project.CursorProcessorOutput; import io.trino.operator.project.PageProcessor; @@ -96,8 +95,7 @@ private ScanFilterAndProjectOperator( DynamicFilter dynamicFilter, Iterable types, DataSize minOutputPageSize, - int minOutputPageRowCount, - boolean avoidPageMaterialization) + int minOutputPageRowCount) { pages = splits.flatTransform( new SplitToPages( @@ -112,8 +110,7 @@ private ScanFilterAndProjectOperator( types, memoryTrackingContext.aggregateUserMemoryContext(), minOutputPageSize, - minOutputPageRowCount, - avoidPageMaterialization)); + minOutputPageRowCount)); } @Override @@ -208,7 +205,6 @@ private class SplitToPages final LocalMemoryContext outputMemoryContext; final DataSize minOutputPageSize; final int minOutputPageRowCount; - final boolean avoidPageMaterialization; SplitToPages( Session session, @@ -222,8 +218,7 @@ private class SplitToPages Iterable types, AggregatedMemoryContext aggregatedMemoryContext, DataSize minOutputPageSize, - int minOutputPageRowCount, - boolean avoidPageMaterialization) + int minOutputPageRowCount) { this.session = requireNonNull(session, "session is null"); this.yieldSignal = requireNonNull(yieldSignal, "yieldSignal is null"); @@ -240,7 +235,6 @@ private class SplitToPages this.outputMemoryContext = localAggregatedMemoryContext.newLocalMemoryContext(ScanFilterAndProjectOperator.class.getSimpleName()); this.minOutputPageSize = requireNonNull(minOutputPageSize, "minOutputPageSize is null"); this.minOutputPageRowCount = minOutputPageRowCount; - this.avoidPageMaterialization = avoidPageMaterialization; } @Override @@ -292,8 +286,7 @@ WorkProcessor processPageSource() yieldSignal, outputMemoryContext, pageProcessorMetrics, - page, - avoidPageMaterialization)) + page)) .transformProcessor(processor -> mergePages(types, minOutputPageSize.toBytes(), minOutputPageRowCount, processor, localAggregatedMemoryContext)) .blocking(() -> memoryContext.setBytes(localAggregatedMemoryContext.getBytes())); } @@ -412,7 +405,7 @@ private static ListenableFuture asVoid(ListenableFuture future) } public static class ScanFilterAndProjectOperatorFactory - implements SourceOperatorFactory, AdapterWorkProcessorSourceOperatorFactory + implements SourceOperatorFactory, WorkProcessorSourceOperatorFactory { private final int operatorId; private final PlanNodeId planNodeId; @@ -494,26 +487,6 @@ public WorkProcessorSourceOperator create( MemoryTrackingContext memoryTrackingContext, DriverYieldSignal yieldSignal, WorkProcessor splits) - { - return create(operatorContext, memoryTrackingContext, yieldSignal, splits, true); - } - - @Override - public WorkProcessorSourceOperator createAdapterOperator( - OperatorContext operatorContext, - MemoryTrackingContext memoryTrackingContext, - DriverYieldSignal yieldSignal, - WorkProcessor splits) - { - return create(operatorContext, memoryTrackingContext, yieldSignal, splits, false); - } - - private ScanFilterAndProjectOperator create( - OperatorContext operatorContext, - MemoryTrackingContext memoryTrackingContext, - DriverYieldSignal yieldSignal, - WorkProcessor splits, - boolean avoidPageMaterialization) { return new ScanFilterAndProjectOperator( operatorContext.getSession(), @@ -528,8 +501,7 @@ private ScanFilterAndProjectOperator create( dynamicFilter, types, minOutputPageSize, - minOutputPageRowCount, - avoidPageMaterialization); + minOutputPageRowCount); } @Override diff --git a/core/trino-main/src/main/java/io/trino/operator/TableScanOperator.java b/core/trino-main/src/main/java/io/trino/operator/TableScanOperator.java index b2b663ddf46c..c1fe8d6b4ce7 100644 --- a/core/trino-main/src/main/java/io/trino/operator/TableScanOperator.java +++ b/core/trino-main/src/main/java/io/trino/operator/TableScanOperator.java @@ -19,7 +19,6 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import io.trino.memory.context.LocalMemoryContext; -import io.trino.memory.context.MemoryTrackingContext; import io.trino.metadata.Split; import io.trino.metadata.TableHandle; import io.trino.spi.Page; @@ -46,7 +45,7 @@ public class TableScanOperator implements SourceOperator { public static class TableScanOperatorFactory - implements SourceOperatorFactory, WorkProcessorSourceOperatorFactory + implements SourceOperatorFactory { private final int operatorId; private final PlanNodeId planNodeId; @@ -75,35 +74,17 @@ public TableScanOperatorFactory( this.dynamicFilter = requireNonNull(dynamicFilter, "dynamicFilter is null"); } - @Override - public int getOperatorId() - { - return operatorId; - } - @Override public PlanNodeId getSourceId() { return sourceId; } - @Override - public PlanNodeId getPlanNodeId() - { - return planNodeId; - } - - @Override - public String getOperatorType() - { - return TableScanOperator.class.getSimpleName(); - } - @Override public SourceOperator createOperator(DriverContext driverContext) { checkState(!closed, "Factory is already closed"); - OperatorContext operatorContext = driverContext.addOperatorContext(operatorId, planNodeId, getOperatorType()); + OperatorContext operatorContext = driverContext.addOperatorContext(operatorId, planNodeId, TableScanOperator.class.getSimpleName()); return new TableScanOperator( operatorContext, sourceId, @@ -113,23 +94,6 @@ public SourceOperator createOperator(DriverContext driverContext) dynamicFilter); } - @Override - public WorkProcessorSourceOperator create( - OperatorContext operatorContext, - MemoryTrackingContext memoryTrackingContext, - DriverYieldSignal yieldSignal, - WorkProcessor splits) - { - return new TableScanWorkProcessorOperator( - operatorContext.getSession(), - memoryTrackingContext, - splits, - pageSourceProvider, - table, - columns, - dynamicFilter); - } - @Override public void noMoreOperators() { diff --git a/core/trino-main/src/main/java/io/trino/operator/TableScanWorkProcessorOperator.java b/core/trino-main/src/main/java/io/trino/operator/TableScanWorkProcessorOperator.java deleted file mode 100644 index 5df8b0f47d47..000000000000 --- a/core/trino-main/src/main/java/io/trino/operator/TableScanWorkProcessorOperator.java +++ /dev/null @@ -1,284 +0,0 @@ -/* - * 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 io.trino.operator; - -import com.google.common.collect.ImmutableList; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import io.airlift.units.DataSize; -import io.airlift.units.Duration; -import io.trino.Session; -import io.trino.memory.context.AggregatedMemoryContext; -import io.trino.memory.context.LocalMemoryContext; -import io.trino.memory.context.MemoryTrackingContext; -import io.trino.metadata.Split; -import io.trino.metadata.TableHandle; -import io.trino.operator.WorkProcessor.ProcessState; -import io.trino.operator.WorkProcessor.TransformationState; -import io.trino.spi.Page; -import io.trino.spi.connector.ColumnHandle; -import io.trino.spi.connector.ConnectorPageSource; -import io.trino.spi.connector.DynamicFilter; -import io.trino.spi.connector.EmptyPageSource; -import io.trino.spi.metrics.Metrics; -import io.trino.split.EmptySplit; -import io.trino.split.PageSourceProvider; -import jakarta.annotation.Nullable; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -import static com.google.common.util.concurrent.MoreExecutors.directExecutor; -import static io.airlift.concurrent.MoreFutures.toListenableFuture; -import static io.trino.operator.PageUtils.recordMaterializedBytes; -import static java.util.Objects.requireNonNull; -import static java.util.concurrent.TimeUnit.NANOSECONDS; - -public class TableScanWorkProcessorOperator - implements WorkProcessorSourceOperator -{ - private final WorkProcessor pages; - private final SplitToPages splitToPages; - - public TableScanWorkProcessorOperator( - Session session, - MemoryTrackingContext memoryTrackingContext, - WorkProcessor splits, - PageSourceProvider pageSourceProvider, - TableHandle table, - Iterable columns, - DynamicFilter dynamicFilter) - { - this.splitToPages = new SplitToPages( - session, - pageSourceProvider, - table, - columns, - dynamicFilter, - memoryTrackingContext.aggregateUserMemoryContext()); - this.pages = splits.flatTransform(splitToPages); - } - - @Override - public WorkProcessor getOutputPages() - { - return pages; - } - - @Override - public DataSize getPhysicalInputDataSize() - { - return splitToPages.getPhysicalInputDataSize(); - } - - @Override - public long getPhysicalInputPositions() - { - return splitToPages.getPhysicalInputPositions(); - } - - @Override - public DataSize getInputDataSize() - { - return splitToPages.getInputDataSize(); - } - - @Override - public long getInputPositions() - { - return splitToPages.getInputPositions(); - } - - @Override - public long getDynamicFilterSplitsProcessed() - { - return splitToPages.getDynamicFilterSplitsProcessed(); - } - - @Override - public Metrics getConnectorMetrics() - { - return splitToPages.source.getMetrics(); - } - - @Override - public Duration getReadTime() - { - return splitToPages.getReadTime(); - } - - @Override - public void close() - { - splitToPages.close(); - } - - private static class SplitToPages - implements WorkProcessor.Transformation> - { - final Session session; - final PageSourceProvider pageSourceProvider; - final TableHandle table; - final List columns; - final DynamicFilter dynamicFilter; - final LocalMemoryContext memoryContext; - - long processedBytes; - long processedPositions; - long dynamicFilterSplitsProcessed; - - @Nullable - ConnectorPageSource source; - - SplitToPages( - Session session, - PageSourceProvider pageSourceProvider, - TableHandle table, - Iterable columns, - DynamicFilter dynamicFilter, - AggregatedMemoryContext aggregatedMemoryContext) - { - this.session = requireNonNull(session, "session is null"); - this.pageSourceProvider = requireNonNull(pageSourceProvider, "pageSourceProvider is null"); - this.table = requireNonNull(table, "table is null"); - this.columns = ImmutableList.copyOf(requireNonNull(columns, "columns is null")); - this.dynamicFilter = requireNonNull(dynamicFilter, "dynamicFilter is null"); - this.memoryContext = aggregatedMemoryContext.newLocalMemoryContext(TableScanWorkProcessorOperator.class.getSimpleName()); - } - - @Override - public TransformationState> process(Split split) - { - if (split == null) { - memoryContext.close(); - return TransformationState.finished(); - } - - if (!dynamicFilter.getCurrentPredicate().isAll()) { - dynamicFilterSplitsProcessed++; - } - - if (split.getConnectorSplit() instanceof EmptySplit) { - source = new EmptyPageSource(); - } - else { - source = pageSourceProvider.createPageSource(session, split, table, columns, dynamicFilter); - } - - return TransformationState.ofResult( - WorkProcessor.create(new ConnectorPageSourceToPages(source)) - .map(page -> { - processedPositions += page.getPositionCount(); - recordMaterializedBytes(page, sizeInBytes -> processedBytes += sizeInBytes); - return page; - }) - .blocking(() -> memoryContext.setBytes(source.getMemoryUsage()))); - } - - DataSize getPhysicalInputDataSize() - { - if (source == null) { - return DataSize.ofBytes(0); - } - - return DataSize.ofBytes(source.getCompletedBytes()); - } - - long getPhysicalInputPositions() - { - if (source == null) { - return 0; - } - return source.getCompletedPositions().orElse(processedPositions); - } - - DataSize getInputDataSize() - { - return DataSize.ofBytes(processedBytes); - } - - long getInputPositions() - { - return processedPositions; - } - - long getDynamicFilterSplitsProcessed() - { - return dynamicFilterSplitsProcessed; - } - - Duration getReadTime() - { - if (source == null) { - return new Duration(0, NANOSECONDS); - } - - return new Duration(source.getReadTimeNanos(), NANOSECONDS); - } - - void close() - { - if (source != null) { - try { - source.close(); - } - catch (IOException e) { - throw new UncheckedIOException(e); - } - } - } - } - - private static class ConnectorPageSourceToPages - implements WorkProcessor.Process - { - final ConnectorPageSource pageSource; - - ConnectorPageSourceToPages(ConnectorPageSource pageSource) - { - this.pageSource = pageSource; - } - - @Override - public ProcessState process() - { - if (pageSource.isFinished()) { - return ProcessState.finished(); - } - - CompletableFuture isBlocked = pageSource.isBlocked(); - if (!isBlocked.isDone()) { - return ProcessState.blocked(asVoid(toListenableFuture(isBlocked))); - } - - Page page = pageSource.getNextPage(); - - if (page == null) { - if (pageSource.isFinished()) { - return ProcessState.finished(); - } - return ProcessState.yielded(); - } - - return ProcessState.ofResult(page); - } - } - - private static ListenableFuture asVoid(ListenableFuture future) - { - return Futures.transform(future, v -> null, directExecutor()); - } -} diff --git a/core/trino-main/src/main/java/io/trino/operator/WorkProcessorOperatorAdapter.java b/core/trino-main/src/main/java/io/trino/operator/WorkProcessorOperatorAdapter.java index 9931626a9b0b..eee0359bcb67 100644 --- a/core/trino-main/src/main/java/io/trino/operator/WorkProcessorOperatorAdapter.java +++ b/core/trino-main/src/main/java/io/trino/operator/WorkProcessorOperatorAdapter.java @@ -16,7 +16,6 @@ import com.google.common.util.concurrent.ListenableFuture; import io.trino.memory.context.MemoryTrackingContext; import io.trino.spi.Page; -import io.trino.sql.planner.plan.PlanNodeId; import static java.util.Objects.requireNonNull; @@ -54,10 +53,10 @@ public static OperatorFactory createAdapterOperatorFactory(AdapterWorkProcessorO } /** - * Provides dual {@link OperatorFactory} and {@link WorkProcessorOperatorFactory} interface. + * Provides {@link OperatorFactory} implementation for {@link WorkProcessorSourceOperator}. */ private static class Factory - implements OperatorFactory, WorkProcessorOperatorFactory + implements OperatorFactory { final AdapterWorkProcessorOperatorFactory operatorFactory; @@ -66,8 +65,6 @@ private static class Factory this.operatorFactory = requireNonNull(operatorFactory, "operatorFactory is null"); } - // Methods from OperatorFactory - @Override public Operator createOperator(DriverContext driverContext) { @@ -81,7 +78,7 @@ public Operator createOperator(DriverContext driverContext) @Override public void noMoreOperators() { - close(); + operatorFactory.close(); } @Override @@ -89,38 +86,6 @@ public OperatorFactory duplicate() { return new Factory(operatorFactory.duplicate()); } - - // Methods from WorkProcessorOperatorFactory - - @Override - public int getOperatorId() - { - return operatorFactory.getOperatorId(); - } - - @Override - public PlanNodeId getPlanNodeId() - { - return operatorFactory.getPlanNodeId(); - } - - @Override - public String getOperatorType() - { - return operatorFactory.getOperatorType(); - } - - @Override - public WorkProcessorOperator create(ProcessorContext processorContext, WorkProcessor sourcePages) - { - return operatorFactory.create(processorContext, sourcePages); - } - - @Override - public void close() - { - operatorFactory.close(); - } } private final OperatorContext operatorContext; diff --git a/core/trino-main/src/main/java/io/trino/operator/WorkProcessorPipelineSourceOperator.java b/core/trino-main/src/main/java/io/trino/operator/WorkProcessorPipelineSourceOperator.java deleted file mode 100644 index b6ca026a3d5a..000000000000 --- a/core/trino-main/src/main/java/io/trino/operator/WorkProcessorPipelineSourceOperator.java +++ /dev/null @@ -1,777 +0,0 @@ -/* - * 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 io.trino.operator; - -import com.google.common.base.Suppliers; -import com.google.common.collect.ImmutableList; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.SettableFuture; -import com.google.errorprone.annotations.FormatMethod; -import io.airlift.log.Logger; -import io.airlift.units.DataSize; -import io.airlift.units.Duration; -import io.trino.memory.context.AggregatedMemoryContext; -import io.trino.memory.context.LocalMemoryContext; -import io.trino.memory.context.MemoryTrackingContext; -import io.trino.metadata.Split; -import io.trino.operator.OperationTimer.OperationTiming; -import io.trino.operator.WorkProcessor.ProcessState; -import io.trino.spi.Page; -import io.trino.spi.metrics.Metrics; -import io.trino.spi.type.Type; -import io.trino.sql.planner.LocalExecutionPlanner.OperatorFactoryWithTypes; -import io.trino.sql.planner.plan.PlanNodeId; -import jakarta.annotation.Nullable; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Deque; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; - -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.base.Throwables.throwIfUnchecked; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.util.concurrent.MoreExecutors.directExecutor; -import static io.airlift.units.DataSize.succinctBytes; -import static io.trino.operator.BlockedReason.WAITING_FOR_MEMORY; -import static io.trino.operator.OperatorContext.getOperatorMetrics; -import static io.trino.operator.PageUtils.recordMaterializedBytes; -import static io.trino.operator.WorkProcessor.ProcessState.Type.BLOCKED; -import static io.trino.operator.WorkProcessor.ProcessState.Type.FINISHED; -import static io.trino.operator.project.MergePages.mergePages; -import static java.util.Objects.requireNonNull; -import static java.util.concurrent.TimeUnit.NANOSECONDS; -import static java.util.concurrent.TimeUnit.SECONDS; - -public class WorkProcessorPipelineSourceOperator - implements SourceOperator -{ - private static final Logger log = Logger.get(WorkProcessorPipelineSourceOperator.class); - private static final Duration ZERO_DURATION = new Duration(0, NANOSECONDS); - private static final int OPERATOR_ID = Integer.MAX_VALUE; - - private final int stageId; - private final int pipelineId; - private final PlanNodeId sourceId; - private final OperatorContext operatorContext; - private final WorkProcessor pages; - private final OperationTimer timer; - // operator instances including source operator - private final List workProcessorOperatorContexts = new ArrayList<>(); - private final Deque pendingSplits = new ArrayDeque<>(); - - private ListenableFuture blockedFuture; - private WorkProcessorSourceOperator sourceOperator; - private SettableFuture blockedOnSplits = SettableFuture.create(); - private boolean operatorFinishing; - - public static List convertOperators( - List operatorFactoriesWithTypes, - DataSize minOutputPageSize, - int minOutputPageRowCount) - { - if (operatorFactoriesWithTypes.isEmpty() || !(operatorFactoriesWithTypes.get(0).getOperatorFactory() instanceof WorkProcessorSourceOperatorFactory sourceOperatorFactory)) { - return toOperatorFactories(operatorFactoriesWithTypes); - } - - ImmutableList.Builder workProcessorOperatorFactoriesBuilder = ImmutableList.builder(); - int operatorIndex = 1; - for (; operatorIndex < operatorFactoriesWithTypes.size(); ++operatorIndex) { - OperatorFactory operatorFactory = operatorFactoriesWithTypes.get(operatorIndex).getOperatorFactory(); - if (!(operatorFactory instanceof WorkProcessorOperatorFactory)) { - break; - } - workProcessorOperatorFactoriesBuilder.add((WorkProcessorOperatorFactory) operatorFactory); - } - - List workProcessorOperatorFactories = workProcessorOperatorFactoriesBuilder.build(); - if (workProcessorOperatorFactories.isEmpty()) { - return toOperatorFactories(operatorFactoriesWithTypes); - } - - return ImmutableList.builder() - .add(new WorkProcessorPipelineSourceOperatorFactory( - sourceOperatorFactory, - workProcessorOperatorFactories, - operatorFactoriesWithTypes.get(operatorIndex - 1).getTypes(), - minOutputPageSize, - minOutputPageRowCount)) - .addAll(toOperatorFactories(operatorFactoriesWithTypes.subList(operatorIndex, operatorFactoriesWithTypes.size()))) - .build(); - } - - public static List toOperatorFactories(List operatorFactoriesWithTypes) - { - return operatorFactoriesWithTypes.stream() - .map(OperatorFactoryWithTypes::getOperatorFactory) - .collect(toImmutableList()); - } - - private WorkProcessorPipelineSourceOperator( - DriverContext driverContext, - WorkProcessorSourceOperatorFactory sourceOperatorFactory, - List operatorFactories, - List outputTypes, - DataSize minOutputPageSize, - int minOutputPageRowCount) - { - requireNonNull(driverContext, "driverContext is null"); - requireNonNull(sourceOperatorFactory, "sourceOperatorFactory is null"); - requireNonNull(operatorFactories, "operatorFactories is null"); - this.stageId = driverContext.getTaskId().getStageId().getId(); - this.pipelineId = driverContext.getPipelineContext().getPipelineId(); - this.sourceId = requireNonNull(sourceOperatorFactory.getSourceId(), "sourceId is null"); - this.operatorContext = driverContext.addOperatorContext(OPERATOR_ID, sourceId, WorkProcessorPipelineSourceOperator.class.getSimpleName()); - this.timer = new OperationTimer( - operatorContext.getDriverContext().isCpuTimerEnabled(), - operatorContext.getDriverContext().isCpuTimerEnabled() && operatorContext.getDriverContext().isPerOperatorCpuTimerEnabled()); - - MemoryTrackingContext sourceOperatorMemoryTrackingContext = createMemoryTrackingContext(operatorContext, 0); - sourceOperatorMemoryTrackingContext.initializeLocalMemoryContexts(sourceOperatorFactory.getOperatorType()); - WorkProcessor splits = WorkProcessor.create(new Splits()); - - sourceOperator = sourceOperatorFactory.create( - operatorContext, - sourceOperatorMemoryTrackingContext, - operatorContext.getDriverContext().getYieldSignal(), - splits); - workProcessorOperatorContexts.add(new WorkProcessorOperatorContext( - sourceOperator, - sourceOperatorFactory.getOperatorId(), - sourceOperatorFactory.getPlanNodeId(), - sourceOperatorFactory.getOperatorType(), - sourceOperatorMemoryTrackingContext)); - WorkProcessor pages = sourceOperator.getOutputPages(); - pages = pages - .yielding(() -> operatorContext.getDriverContext().getYieldSignal().isSet()) - .withProcessEntryMonitor(() -> workProcessorOperatorEntryMonitor(0)) - .withProcessStateMonitor(state -> workProcessorOperatorStateMonitor(state, 0)) - .map(page -> recordProcessedOutput(page, 0)); - - for (int i = 0; i < operatorFactories.size(); ++i) { - int operatorIndex = i + 1; - WorkProcessorOperatorFactory operatorFactory = operatorFactories.get(i); - MemoryTrackingContext operatorMemoryTrackingContext = createMemoryTrackingContext(operatorContext, operatorIndex); - operatorMemoryTrackingContext.initializeLocalMemoryContexts(operatorFactory.getOperatorType()); - WorkProcessorOperator operator = operatorFactory.create( - new ProcessorContext(operatorContext.getSession(), operatorMemoryTrackingContext, operatorContext), - pages); - workProcessorOperatorContexts.add(new WorkProcessorOperatorContext( - operator, - operatorFactory.getOperatorId(), - operatorFactory.getPlanNodeId(), - operatorFactory.getOperatorType(), - operatorMemoryTrackingContext)); - pages = operator.getOutputPages(); - if (i == operatorFactories.size() - 1) { - // materialize output pages as there are no semantics guarantees for non WorkProcessor operators - pages = pages.map(Page::getLoadedPage); - pages = pages.transformProcessor(processor -> mergePages( - outputTypes, - minOutputPageSize.toBytes(), - minOutputPageRowCount, - processor, - operatorContext.aggregateUserMemoryContext())); - } - pages = pages - .yielding(() -> operatorContext.getDriverContext().getYieldSignal().isSet()) - .withProcessEntryMonitor(() -> workProcessorOperatorEntryMonitor(operatorIndex)) - .withProcessStateMonitor(state -> workProcessorOperatorStateMonitor(state, operatorIndex)); - pages = pages.map(page -> recordProcessedOutput(page, operatorIndex)); - } - - // finish early when entire pipeline is closed - this.pages = pages.finishWhen(() -> operatorFinishing); - - operatorContext.setNestedOperatorStatsSupplier(this::getNestedOperatorStats); - } - - private void workProcessorOperatorEntryMonitor(int operatorIndex) - { - if (isLastOperator(operatorIndex)) { - // this is the top operator so timer needs to be reset - timer.resetInterval(); - } - else { - // report timing for downstream operator - timer.recordOperationComplete(workProcessorOperatorContexts.get(operatorIndex + 1).operatorTiming); - } - } - - private void workProcessorOperatorStateMonitor(WorkProcessor.ProcessState state, int operatorIndex) - { - // report timing for current operator - WorkProcessorOperatorContext context = workProcessorOperatorContexts.get(operatorIndex); - timer.recordOperationComplete(context.operatorTiming); - - context.metrics.set(context.operator.getMetrics()); - - if (operatorIndex == 0) { - // update input stats for source operator - WorkProcessorSourceOperator sourceOperator = (WorkProcessorSourceOperator) context.operator; - - long deltaPhysicalInputDataSize = deltaAndSet(context.physicalInputDataSize, sourceOperator.getPhysicalInputDataSize().toBytes()); - long deltaPhysicalInputPositions = deltaAndSet(context.physicalInputPositions, sourceOperator.getPhysicalInputPositions()); - - long deltaInternalNetworkInputDataSize = deltaAndSet(context.internalNetworkInputDataSize, sourceOperator.getInternalNetworkInputDataSize().toBytes()); - long deltaInternalNetworkInputPositions = deltaAndSet(context.internalNetworkInputPositions, sourceOperator.getInternalNetworkPositions()); - - long deltaInputDataSize = deltaAndSet(context.inputDataSize, sourceOperator.getInputDataSize().toBytes()); - long deltaInputPositions = deltaAndSet(context.inputPositions, sourceOperator.getInputPositions()); - - long deltaReadTimeNanos = deltaAndSet(context.readTimeNanos, sourceOperator.getReadTime().roundTo(NANOSECONDS)); - - context.dynamicFilterSplitsProcessed.set(sourceOperator.getDynamicFilterSplitsProcessed()); - context.connectorMetrics.set(sourceOperator.getConnectorMetrics()); - - operatorContext.recordPhysicalInputWithTiming(deltaPhysicalInputDataSize, deltaPhysicalInputPositions, deltaReadTimeNanos); - operatorContext.recordNetworkInput(deltaInternalNetworkInputDataSize, deltaInternalNetworkInputPositions); - operatorContext.recordProcessedInput(deltaInputDataSize, deltaInputPositions); - } - - if (state.getType() == FINISHED) { - // immediately close all upstream operators (including finished operator) - closeOperators(operatorIndex); - } - else if (state.getType() == BLOCKED && blockedFuture != state.getBlocked()) { - blockedFuture = state.getBlocked(); - long start = System.nanoTime(); - blockedFuture.addListener( - () -> context.blockedWallNanos.getAndAdd(System.nanoTime() - start), - directExecutor()); - } - } - - private static long deltaAndSet(AtomicLong currentValue, long newValue) - { - return newValue - currentValue.getAndSet(newValue); - } - - private Page recordProcessedOutput(Page page, int operatorIndex) - { - WorkProcessorOperatorContext operatorContext = workProcessorOperatorContexts.get(operatorIndex); - operatorContext.outputPositions.getAndAdd(page.getPositionCount()); - - WorkProcessorOperatorContext downstreamOperatorContext; - if (!isLastOperator(operatorIndex)) { - downstreamOperatorContext = workProcessorOperatorContexts.get(operatorIndex + 1); - downstreamOperatorContext.inputPositions.getAndAdd(page.getPositionCount()); - } - else { - downstreamOperatorContext = null; - } - - // account processed bytes from lazy blocks only when they are loaded - recordMaterializedBytes(page, sizeInBytes -> { - operatorContext.outputDataSize.getAndAdd(sizeInBytes); - if (downstreamOperatorContext != null) { - downstreamOperatorContext.inputDataSize.getAndAdd(sizeInBytes); - } - }); - - return page; - } - - private boolean isLastOperator(int operatorIndex) - { - return operatorIndex + 1 == workProcessorOperatorContexts.size(); - } - - private MemoryTrackingContext createMemoryTrackingContext(OperatorContext operatorContext, int operatorIndex) - { - return new MemoryTrackingContext( - new InternalAggregatedMemoryContext(operatorContext.newAggregateUserMemoryContext(), () -> updatePeakMemoryReservations(operatorIndex)), - new InternalAggregatedMemoryContext(operatorContext.newAggregateRevocableMemoryContext(), () -> updatePeakMemoryReservations(operatorIndex))); - } - - private void updatePeakMemoryReservations(int operatorIndex) - { - workProcessorOperatorContexts.get(operatorIndex).updatePeakMemoryReservations(); - } - - private List getNestedOperatorStats() - { - return workProcessorOperatorContexts.stream() - .map(context -> new OperatorStats( - stageId, - pipelineId, - context.operatorId, - context.planNodeId, - context.operatorType, - 1, - - // WorkProcessorOperator doesn't have addInput call - 0, - new Duration(0, NANOSECONDS), - ZERO_DURATION, - - succinctBytes(context.physicalInputDataSize.get()), - context.physicalInputPositions.get(), - new Duration(context.readTimeNanos.get(), NANOSECONDS), - - succinctBytes(context.internalNetworkInputDataSize.get()), - context.internalNetworkInputPositions.get(), - - succinctBytes(context.physicalInputDataSize.get() + context.internalNetworkInputDataSize.get()), - - succinctBytes(context.inputDataSize.get()), - context.inputPositions.get(), - (double) context.inputPositions.get() * context.inputPositions.get(), - - context.operatorTiming.getCalls(), - new Duration(context.operatorTiming.getWallNanos(), NANOSECONDS), - new Duration(context.operatorTiming.getCpuNanos(), NANOSECONDS), - - succinctBytes(context.outputDataSize.get()), - context.outputPositions.get(), - - context.dynamicFilterSplitsProcessed.get(), - getOperatorMetrics( - context.metrics.get(), - context.inputPositions.get(), - new Duration(context.operatorTiming.getCpuNanos(), NANOSECONDS).convertTo(SECONDS).getValue(), - new Duration(context.operatorTiming.getWallNanos(), NANOSECONDS).convertTo(SECONDS).getValue(), - new Duration(context.blockedWallNanos.get(), NANOSECONDS).convertTo(SECONDS).getValue()), - context.connectorMetrics.get(), - - DataSize.ofBytes(0), - - new Duration(context.blockedWallNanos.get(), NANOSECONDS), - - // WorkProcessorOperator doesn't have finish call - 0, - ZERO_DURATION, - ZERO_DURATION, - - succinctBytes(context.memoryTrackingContext.getUserMemory()), - succinctBytes(context.memoryTrackingContext.getRevocableMemory()), - succinctBytes(context.peakUserMemoryReservation.get()), - succinctBytes(context.peakRevocableMemoryReservation.get()), - succinctBytes(context.peakTotalMemoryReservation.get()), - DataSize.ofBytes(0), - operatorContext.isWaitingForMemory().isDone() ? Optional.empty() : Optional.of(WAITING_FOR_MEMORY), - getOperatorInfo(context))) - .collect(toImmutableList()); - } - - @Nullable - private OperatorInfo getOperatorInfo(WorkProcessorOperatorContext context) - { - WorkProcessorOperator operator = context.operator; - if (operator != null) { - return operator.getOperatorInfo().orElse(null); - } - - return context.finalOperatorInfo; - } - - @Override - public PlanNodeId getSourceId() - { - return sourceId; - } - - @Override - public void addSplit(Split split) - { - if (sourceOperator == null) { - return; - } - - Object splitInfo = split.getInfo(); - if (splitInfo != null) { - operatorContext.setInfoSupplier(Suppliers.ofInstance(new SplitOperatorInfo(split.getCatalogHandle(), splitInfo))); - } - - pendingSplits.add(split); - blockedOnSplits.set(null); - } - - @Override - public void noMoreSplits() - { - blockedOnSplits.set(null); - sourceOperator = null; - } - - @Override - public OperatorContext getOperatorContext() - { - return operatorContext; - } - - @Override - public boolean needsInput() - { - return false; - } - - @Override - public void addInput(Page page) - { - throw new UnsupportedOperationException(); - } - - @Override - public Page getOutput() - { - if (!pages.process()) { - return null; - } - - if (pages.isFinished()) { - return null; - } - - return pages.getResult(); - } - - @Override - public ListenableFuture startMemoryRevoke() - { - // TODO: support spill - throw new UnsupportedOperationException(); - } - - @Override - public void finishMemoryRevoke() - { - // TODO: support spill - throw new UnsupportedOperationException(); - } - - @Override - public void finish() - { - // operator is finished early without waiting for all pages to process - operatorFinishing = true; - noMoreSplits(); - closeOperators(workProcessorOperatorContexts.size() - 1); - } - - @Override - public boolean isFinished() - { - return pages.isFinished(); - } - - @Override - public ListenableFuture isBlocked() - { - if (!pages.isBlocked()) { - return NOT_BLOCKED; - } - - return pages.getBlockedFuture(); - } - - @Override - public void close() - { - finish(); - } - - private class Splits - implements WorkProcessor.Process - { - @Override - public ProcessState process() - { - boolean noMoreSplits = sourceOperator == null; - - if (pendingSplits.isEmpty()) { - if (noMoreSplits) { - return ProcessState.finished(); - } - - blockedOnSplits = SettableFuture.create(); - blockedFuture = blockedOnSplits; - return ProcessState.blocked(blockedOnSplits); - } - - return ProcessState.ofResult(pendingSplits.remove()); - } - } - - private void closeOperators(int lastOperatorIndex) - { - // record the current interrupted status (and clear the flag); we'll reset it later - boolean wasInterrupted = Thread.interrupted(); - Throwable inFlightException = null; - try { - for (int i = 0; i <= lastOperatorIndex; ++i) { - WorkProcessorOperatorContext workProcessorOperatorContext = workProcessorOperatorContexts.get(i); - WorkProcessorOperator operator = workProcessorOperatorContext.operator; - if (operator == null) { - // operator is already closed - continue; - } - - try { - operator.close(); - } - catch (InterruptedException t) { - // don't record the stack - wasInterrupted = true; - } - catch (Throwable t) { - inFlightException = handleOperatorCloseError( - inFlightException, - t, - "Error closing WorkProcessor operator %s for task %s", - workProcessorOperatorContext.operatorId, - operatorContext.getDriverContext().getTaskId()); - } - finally { - workProcessorOperatorContext.metrics.set(operator.getMetrics()); - if (operator instanceof WorkProcessorSourceOperator sourceOperator) { - workProcessorOperatorContext.connectorMetrics.set(sourceOperator.getConnectorMetrics()); - } - workProcessorOperatorContext.memoryTrackingContext.close(); - workProcessorOperatorContext.finalOperatorInfo = operator.getOperatorInfo().orElse(null); - workProcessorOperatorContext.operator = null; - } - } - } - finally { - // reset the interrupted flag - if (wasInterrupted) { - Thread.currentThread().interrupt(); - } - } - if (inFlightException != null) { - throwIfUnchecked(inFlightException); - throw new RuntimeException(inFlightException); - } - } - - @FormatMethod - private static Throwable handleOperatorCloseError(Throwable inFlightException, Throwable newException, String message, final Object... args) - { - if (newException instanceof Error) { - if (inFlightException == null) { - inFlightException = newException; - } - else { - // Self-suppression not permitted - if (inFlightException != newException) { - inFlightException.addSuppressed(newException); - } - } - } - else { - // log normal exceptions instead of rethrowing them - log.error(newException, message, args); - } - return inFlightException; - } - - private static class InternalLocalMemoryContext - implements LocalMemoryContext - { - final LocalMemoryContext delegate; - final Runnable allocationListener; - - InternalLocalMemoryContext(LocalMemoryContext delegate, Runnable allocationListener) - { - this.delegate = requireNonNull(delegate, "delegate is null"); - this.allocationListener = requireNonNull(allocationListener, "allocationListener is null"); - } - - @Override - public long getBytes() - { - return delegate.getBytes(); - } - - @Override - public ListenableFuture setBytes(long bytes) - { - ListenableFuture blocked = delegate.setBytes(bytes); - allocationListener.run(); - return blocked; - } - - @Override - public boolean trySetBytes(long bytes) - { - if (delegate.trySetBytes(bytes)) { - allocationListener.run(); - return true; - } - - return false; - } - - @Override - public void close() - { - delegate.close(); - allocationListener.run(); - } - } - - private static class InternalAggregatedMemoryContext - implements AggregatedMemoryContext - { - final AggregatedMemoryContext delegate; - final Runnable allocationListener; - - InternalAggregatedMemoryContext(AggregatedMemoryContext delegate, Runnable allocationListener) - { - this.delegate = requireNonNull(delegate, "delegate is null"); - this.allocationListener = requireNonNull(allocationListener, "allocationListener is null"); - } - - @Override - public AggregatedMemoryContext newAggregatedMemoryContext() - { - return new InternalAggregatedMemoryContext(delegate.newAggregatedMemoryContext(), allocationListener); - } - - @Override - public LocalMemoryContext newLocalMemoryContext(String allocationTag) - { - return new InternalLocalMemoryContext(delegate.newLocalMemoryContext(allocationTag), allocationListener); - } - - @Override - public long getBytes() - { - return delegate.getBytes(); - } - - @Override - public void close() - { - delegate.close(); - } - } - - private static class WorkProcessorOperatorContext - { - final int operatorId; - final PlanNodeId planNodeId; - final String operatorType; - final MemoryTrackingContext memoryTrackingContext; - - final OperationTiming operatorTiming = new OperationTiming(); - final AtomicLong blockedWallNanos = new AtomicLong(); - - final AtomicLong physicalInputDataSize = new AtomicLong(); - final AtomicLong physicalInputPositions = new AtomicLong(); - - final AtomicLong internalNetworkInputDataSize = new AtomicLong(); - final AtomicLong internalNetworkInputPositions = new AtomicLong(); - - final AtomicLong inputDataSize = new AtomicLong(); - final AtomicLong inputPositions = new AtomicLong(); - - final AtomicLong readTimeNanos = new AtomicLong(); - - final AtomicLong outputDataSize = new AtomicLong(); - final AtomicLong outputPositions = new AtomicLong(); - - final AtomicLong dynamicFilterSplitsProcessed = new AtomicLong(); - final AtomicReference metrics = new AtomicReference<>(Metrics.EMPTY); - final AtomicReference connectorMetrics = new AtomicReference<>(Metrics.EMPTY); - - final AtomicLong peakUserMemoryReservation = new AtomicLong(); - final AtomicLong peakRevocableMemoryReservation = new AtomicLong(); - final AtomicLong peakTotalMemoryReservation = new AtomicLong(); - - @Nullable - volatile WorkProcessorOperator operator; - @Nullable - volatile OperatorInfo finalOperatorInfo; - - private WorkProcessorOperatorContext( - WorkProcessorOperator operator, - int operatorId, - PlanNodeId planNodeId, - String operatorType, - MemoryTrackingContext memoryTrackingContext) - { - this.operator = operator; - this.operatorId = operatorId; - this.planNodeId = planNodeId; - this.operatorType = operatorType; - this.memoryTrackingContext = memoryTrackingContext; - } - - void updatePeakMemoryReservations() - { - long userMemory = memoryTrackingContext.getUserMemory(); - long revocableMemory = memoryTrackingContext.getRevocableMemory(); - long totalMemory = userMemory; - peakUserMemoryReservation.accumulateAndGet(userMemory, Math::max); - peakRevocableMemoryReservation.accumulateAndGet(revocableMemory, Math::max); - peakTotalMemoryReservation.accumulateAndGet(totalMemory, Math::max); - } - } - - public static class WorkProcessorPipelineSourceOperatorFactory - implements SourceOperatorFactory - { - private final WorkProcessorSourceOperatorFactory sourceOperatorFactory; - private final List operatorFactories; - private final List outputTypes; - private final DataSize minOutputPageSize; - private final int minOutputPageRowCount; - private boolean closed; - - private WorkProcessorPipelineSourceOperatorFactory( - WorkProcessorSourceOperatorFactory sourceOperatorFactory, - List operatorFactories, - List outputTypes, - DataSize minOutputPageSize, - int minOutputPageRowCount) - { - this.sourceOperatorFactory = requireNonNull(sourceOperatorFactory, "sourceOperatorFactory is null"); - this.operatorFactories = requireNonNull(operatorFactories, "operatorFactories is null"); - this.outputTypes = requireNonNull(outputTypes, "outputTypes is null"); - this.minOutputPageSize = requireNonNull(minOutputPageSize, "minOutputPageSize is null"); - this.minOutputPageRowCount = minOutputPageRowCount; - } - - @Override - public PlanNodeId getSourceId() - { - return sourceOperatorFactory.getSourceId(); - } - - @Override - public SourceOperator createOperator(DriverContext driverContext) - { - checkState(!closed, "Factory is already closed"); - return new WorkProcessorPipelineSourceOperator( - driverContext, - sourceOperatorFactory, - operatorFactories, - outputTypes, - minOutputPageSize, - minOutputPageRowCount); - } - - @Override - public void noMoreOperators() - { - this.operatorFactories.forEach(WorkProcessorOperatorFactory::close); - closed = true; - } - } -} diff --git a/core/trino-main/src/main/java/io/trino/operator/WorkProcessorSourceOperatorAdapter.java b/core/trino-main/src/main/java/io/trino/operator/WorkProcessorSourceOperatorAdapter.java index 7feb9a353985..5803b50b44a1 100644 --- a/core/trino-main/src/main/java/io/trino/operator/WorkProcessorSourceOperatorAdapter.java +++ b/core/trino-main/src/main/java/io/trino/operator/WorkProcessorSourceOperatorAdapter.java @@ -51,26 +51,13 @@ public class WorkProcessorSourceOperatorAdapter private long previousReadTimeNanos; private long previousDynamicFilterSplitsProcessed; - public interface AdapterWorkProcessorSourceOperatorFactory - extends WorkProcessorSourceOperatorFactory - { - default WorkProcessorSourceOperator createAdapterOperator( - OperatorContext operatorContext, - MemoryTrackingContext memoryTrackingContext, - DriverYieldSignal yieldSignal, - WorkProcessor splits) - { - return create(operatorContext, memoryTrackingContext, yieldSignal, splits); - } - } - - public WorkProcessorSourceOperatorAdapter(OperatorContext operatorContext, AdapterWorkProcessorSourceOperatorFactory sourceOperatorFactory) + public WorkProcessorSourceOperatorAdapter(OperatorContext operatorContext, WorkProcessorSourceOperatorFactory sourceOperatorFactory) { this.operatorContext = requireNonNull(operatorContext, "operatorContext is null"); this.sourceId = sourceOperatorFactory.getSourceId(); this.splitBuffer = new SplitBuffer(); this.sourceOperator = sourceOperatorFactory - .createAdapterOperator( + .create( operatorContext, new MemoryTrackingContext( operatorContext.aggregateUserMemoryContext(), diff --git a/core/trino-main/src/main/java/io/trino/operator/project/PageProcessor.java b/core/trino-main/src/main/java/io/trino/operator/project/PageProcessor.java index 49c6673821ad..c021f9b25823 100644 --- a/core/trino-main/src/main/java/io/trino/operator/project/PageProcessor.java +++ b/core/trino-main/src/main/java/io/trino/operator/project/PageProcessor.java @@ -40,7 +40,6 @@ import static com.google.common.base.Verify.verify; import static com.google.common.base.Verify.verifyNotNull; import static com.google.common.collect.ImmutableList.toImmutableList; -import static io.trino.operator.PageUtils.recordMaterializedBytes; import static io.trino.operator.WorkProcessor.ProcessState.finished; import static io.trino.operator.WorkProcessor.ProcessState.ofResult; import static io.trino.operator.WorkProcessor.ProcessState.yielded; @@ -96,13 +95,7 @@ public PageProcessor(Optional filter, List @VisibleForTesting public Iterator> process(ConnectorSession session, DriverYieldSignal yieldSignal, LocalMemoryContext memoryContext, Page page) { - return process(session, yieldSignal, memoryContext, page, false); - } - - @VisibleForTesting - Iterator> process(ConnectorSession session, DriverYieldSignal yieldSignal, LocalMemoryContext memoryContext, Page page, boolean avoidPageMaterialization) - { - WorkProcessor processor = createWorkProcessor(session, yieldSignal, memoryContext, new PageProcessorMetrics(), page, avoidPageMaterialization); + WorkProcessor processor = createWorkProcessor(session, yieldSignal, memoryContext, new PageProcessorMetrics(), page); return processor.yieldingIterator(); } @@ -111,8 +104,7 @@ public WorkProcessor createWorkProcessor( DriverYieldSignal yieldSignal, LocalMemoryContext memoryContext, PageProcessorMetrics metrics, - Page page, - boolean avoidPageMaterialization) + Page page) { // limit the scope of the dictionary ids to just one page dictionarySourceIdFunction.reset(); @@ -136,7 +128,7 @@ public WorkProcessor createWorkProcessor( } if (selectedPositions.size() != page.getPositionCount()) { - return WorkProcessor.create(new ProjectSelectedPositions(session, yieldSignal, memoryContext, metrics, page, selectedPositions, avoidPageMaterialization)); + return WorkProcessor.create(new ProjectSelectedPositions(session, yieldSignal, memoryContext, metrics, page, selectedPositions)); } } else if (projections.isEmpty()) { @@ -144,7 +136,7 @@ else if (projections.isEmpty()) { return WorkProcessor.of(new Page(page.getPositionCount())); } - return WorkProcessor.create(new ProjectSelectedPositions(session, yieldSignal, memoryContext, metrics, page, positionsRange(0, page.getPositionCount()), avoidPageMaterialization)); + return WorkProcessor.create(new ProjectSelectedPositions(session, yieldSignal, memoryContext, metrics, page, positionsRange(0, page.getPositionCount()))); } private class ProjectSelectedPositions @@ -154,7 +146,6 @@ private class ProjectSelectedPositions private final DriverYieldSignal yieldSignal; private final LocalMemoryContext memoryContext; private final PageProcessorMetrics metrics; - private final boolean avoidPageMaterialization; private Page page; private final Block[] previouslyComputedResults; @@ -175,8 +166,7 @@ private ProjectSelectedPositions( LocalMemoryContext memoryContext, PageProcessorMetrics metrics, Page page, - SelectedPositions selectedPositions, - boolean avoidPageMaterialization) + SelectedPositions selectedPositions) { checkArgument(!selectedPositions.isEmpty(), "selectedPositions is empty"); @@ -185,7 +175,6 @@ private ProjectSelectedPositions( this.metrics = metrics; this.page = page; this.memoryContext = memoryContext; - this.avoidPageMaterialization = avoidPageMaterialization; this.selectedPositions = selectedPositions; this.previouslyComputedResults = new Block[projections.size()]; } @@ -193,11 +182,6 @@ private ProjectSelectedPositions( @Override public ProcessState process() { - if (avoidPageMaterialization && outputPagePositions != -1) { - updateBatchSize(outputPagePositions, outputPageSizeInBytes); - outputPagePositions = -1; - } - int batchSize; while (true) { if (selectedPositions.isEmpty()) { @@ -235,19 +219,7 @@ public ProcessState process() verify(result.isSuccess()); Page resultPage = result.getPage(); - - if (!avoidPageMaterialization) { - updateBatchSize(resultPage.getPositionCount(), resultPage.getSizeInBytes()); - } - else { - // This is executed within WorkProcessorOperator context. - // Therefore it is guaranteed that: - // 1. produced Page is accessed by single thread - // 2. lazy Page can be materialized only before fetching next page from PageProcessor - outputPagePositions = resultPage.getPositionCount(); - outputPageSizeInBytes = 0; - recordMaterializedBytes(resultPage, sizeInBytes -> outputPageSizeInBytes += sizeInBytes); - } + updateBatchSize(resultPage.getPositionCount(), resultPage.getSizeInBytes()); // remove batch from selectedPositions and previouslyComputedResults selectedPositions = selectedPositions.subRange(batchSize, selectedPositions.size()); @@ -356,10 +328,8 @@ private ProcessBatchResult processBatch(int batchSize) blocks[i] = previouslyComputedResults[i]; } - if (!avoidPageMaterialization) { - blocks[i] = blocks[i].getLoadedBlock(); - pageSize += blocks[i].getSizeInBytes(); - } + blocks[i] = blocks[i].getLoadedBlock(); + pageSize += blocks[i].getSizeInBytes(); } return ProcessBatchResult.processBatchSuccess(new Page(positionsBatch.size(), blocks)); } diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/LocalExecutionPlanner.java b/core/trino-main/src/main/java/io/trino/sql/planner/LocalExecutionPlanner.java index f0286d52289e..b00cc38fab58 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/LocalExecutionPlanner.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/LocalExecutionPlanner.java @@ -100,7 +100,6 @@ import io.trino.operator.ValuesOperator.ValuesOperatorFactory; import io.trino.operator.WindowFunctionDefinition; import io.trino.operator.WindowOperator.WindowOperatorFactory; -import io.trino.operator.WorkProcessorPipelineSourceOperator; import io.trino.operator.aggregation.AccumulatorFactory; import io.trino.operator.aggregation.AggregatorFactory; import io.trino.operator.aggregation.DistinctAccumulatorFactory; @@ -312,7 +311,6 @@ import static io.trino.SystemSessionProperties.isEnableLargeDynamicFilters; import static io.trino.SystemSessionProperties.isExchangeCompressionEnabled; import static io.trino.SystemSessionProperties.isForceSpillingOperator; -import static io.trino.SystemSessionProperties.isLateMaterializationEnabled; import static io.trino.SystemSessionProperties.isSpillEnabled; import static io.trino.cache.CacheUtils.uncheckedCacheGet; import static io.trino.cache.SafeCaches.buildNonEvictableCache; @@ -328,7 +326,6 @@ import static io.trino.operator.TableWriterOperator.STATS_START_CHANNEL; import static io.trino.operator.TableWriterOperator.TableWriterOperatorFactory; import static io.trino.operator.WindowFunctionDefinition.window; -import static io.trino.operator.WorkProcessorPipelineSourceOperator.toOperatorFactories; import static io.trino.operator.aggregation.AccumulatorCompiler.generateAccumulatorFactory; import static io.trino.operator.join.JoinUtils.isBuildSideReplicated; import static io.trino.operator.join.NestedLoopBuildOperator.NestedLoopBuildOperatorFactory; @@ -718,26 +715,11 @@ public void addDriverFactory(boolean outputDriver, PhysicalOperation physicalOpe { boolean inputDriver = context.isInputDriver(); OptionalInt driverInstances = context.getDriverInstanceCount(); - List operatorFactoriesWithTypes = physicalOperation.getOperatorFactoriesWithTypes(); - addLookupOuterDrivers(outputDriver, toOperatorFactories(operatorFactoriesWithTypes)); - List operatorFactories; - if (isLateMaterializationEnabled(taskContext.getSession())) { - operatorFactories = handleLateMaterialization(operatorFactoriesWithTypes); - } - else { - operatorFactories = toOperatorFactories(operatorFactoriesWithTypes); - } + List operatorFactories = physicalOperation.getOperatorFactories(); + addLookupOuterDrivers(outputDriver, operatorFactories); addDriverFactory(inputDriver, outputDriver, operatorFactories, driverInstances); } - private List handleLateMaterialization(List operatorFactories) - { - return WorkProcessorPipelineSourceOperator.convertOperators( - operatorFactories, - getFilterAndProjectMinOutputPageSize(taskContext.getSession()), - getFilterAndProjectMinOutputPageRowCount(taskContext.getSession())); - } - private void addLookupOuterDrivers(boolean isOutputDriver, List operatorFactories) { // For an outer join on the lookup side (RIGHT or FULL) add an additional @@ -901,28 +883,6 @@ public List getPartitionedSourceOrder() } } - public static class OperatorFactoryWithTypes - { - private final OperatorFactory operatorFactory; - private final List types; - - public OperatorFactoryWithTypes(OperatorFactory operatorFactory, List types) - { - this.operatorFactory = requireNonNull(operatorFactory, "operatorFactory is null"); - this.types = requireNonNull(types, "types is null"); - } - - public OperatorFactory getOperatorFactory() - { - return operatorFactory; - } - - public List getTypes() - { - return types; - } - } - private class Visitor extends PlanVisitor { @@ -4277,7 +4237,7 @@ private static Set getConsumedDynamicFilterIds(PlanNode node) */ private static class PhysicalOperation { - private final List operatorFactoriesWithTypes; + private final List operatorFactories; private final Map layout; private final List types; @@ -4308,9 +4268,9 @@ private PhysicalOperation( requireNonNull(source, "source is null"); this.types = toTypes(layout, typeProvider); - this.operatorFactoriesWithTypes = ImmutableList.builder() - .addAll(source.map(PhysicalOperation::getOperatorFactoriesWithTypes).orElse(ImmutableList.of())) - .add(new OperatorFactoryWithTypes(operatorFactory, types)) + this.operatorFactories = ImmutableList.builder() + .addAll(source.map(PhysicalOperation::getOperatorFactories).orElse(ImmutableList.of())) + .add(operatorFactory) .build(); this.layout = ImmutableMap.copyOf(layout); } @@ -4348,12 +4308,7 @@ public Map getLayout() private List getOperatorFactories() { - return toOperatorFactories(operatorFactoriesWithTypes); - } - - private List getOperatorFactoriesWithTypes() - { - return operatorFactoriesWithTypes; + return operatorFactories; } } diff --git a/core/trino-main/src/test/java/io/trino/operator/TestWorkProcessorPipelineSourceOperator.java b/core/trino-main/src/test/java/io/trino/operator/TestWorkProcessorPipelineSourceOperator.java deleted file mode 100644 index 1b8a5d335fc8..000000000000 --- a/core/trino-main/src/test/java/io/trino/operator/TestWorkProcessorPipelineSourceOperator.java +++ /dev/null @@ -1,567 +0,0 @@ -/* - * 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 io.trino.operator; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.util.concurrent.MoreExecutors; -import com.google.common.util.concurrent.SettableFuture; -import io.airlift.units.DataSize; -import io.airlift.units.Duration; -import io.trino.memory.context.MemoryTrackingContext; -import io.trino.metadata.Split; -import io.trino.operator.WorkProcessor.Transformation; -import io.trino.operator.WorkProcessor.TransformationState; -import io.trino.operator.WorkProcessorAssertion.Transform; -import io.trino.plugin.base.metrics.LongCount; -import io.trino.spi.Page; -import io.trino.spi.metrics.Metrics; -import io.trino.sql.planner.LocalExecutionPlanner.OperatorFactoryWithTypes; -import io.trino.sql.planner.plan.PlanNodeId; -import io.trino.testing.TestingTaskContext; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.Timeout; - -import java.util.List; -import java.util.Optional; -import java.util.concurrent.ScheduledExecutorService; - -import static com.google.common.collect.Iterables.getOnlyElement; -import static io.trino.RowPagesBuilder.rowPagesBuilder; -import static io.trino.SessionTestUtils.TEST_SESSION; -import static io.trino.operator.WorkProcessorAssertion.transformationFrom; -import static io.trino.spi.type.BigintType.BIGINT; -import static io.trino.testing.TestingHandles.TEST_CATALOG_HANDLE; -import static io.trino.testing.TestingSplit.createLocalSplit; -import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; -import static java.util.concurrent.TimeUnit.NANOSECONDS; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; - -@TestInstance(PER_CLASS) -public class TestWorkProcessorPipelineSourceOperator -{ - private ScheduledExecutorService scheduledExecutor; - - @BeforeAll - public void setUp() - { - scheduledExecutor = newSingleThreadScheduledExecutor(); - } - - @AfterAll - public void tearDown() - { - scheduledExecutor.shutdownNow(); - } - - @Test - @Timeout(10) - public void testWorkProcessorPipelineSourceOperator() - throws InterruptedException - { - Split split = createSplit(); - - Page page1 = createPage(1); - Page page2 = createPage(2); - Page page3 = createPage(3); - Page page4 = createPage(4); - Page page5 = createPage(5); - - Transformation sourceOperatorPages = transformationFrom(ImmutableList.of( - Transform.of(Optional.of(split), TransformationState.ofResult(page1, false)), - Transform.of(Optional.of(split), TransformationState.ofResult(page2, true)))); - - SettableFuture firstBlockedFuture = SettableFuture.create(); - Transformation firstOperatorPages = transformationFrom(ImmutableList.of( - Transform.of(Optional.of(page1), TransformationState.blocked(firstBlockedFuture)), - Transform.of(Optional.of(page1), TransformationState.ofResult(page3, true)), - Transform.of(Optional.of(page2), TransformationState.ofResult(page4, false)), - Transform.of(Optional.of(page2), TransformationState.finished())), - (left, right) -> left.getPositionCount() == right.getPositionCount()); - - SettableFuture secondBlockedFuture = SettableFuture.create(); - Transformation secondOperatorPages = transformationFrom(ImmutableList.of( - Transform.of(Optional.of(page3), TransformationState.ofResult(page5, true)), - Transform.of(Optional.of(page4), TransformationState.needsMoreData()), - Transform.of(Optional.empty(), TransformationState.blocked(secondBlockedFuture))), - (left, right) -> left.getPositionCount() == right.getPositionCount()); - - TestWorkProcessorSourceOperatorFactory sourceOperatorFactory = new TestWorkProcessorSourceOperatorFactory( - 1, - new PlanNodeId("1"), - sourceOperatorPages); - TestWorkProcessorOperatorFactory firstOperatorFactory = new TestWorkProcessorOperatorFactory(2, firstOperatorPages); - TestWorkProcessorOperatorFactory secondOperatorFactory = new TestWorkProcessorOperatorFactory(3, secondOperatorPages); - - SourceOperatorFactory pipelineOperatorFactory = (SourceOperatorFactory) getOnlyElement(WorkProcessorPipelineSourceOperator.convertOperators( - ImmutableList.of( - new OperatorFactoryWithTypes(sourceOperatorFactory, ImmutableList.of(BIGINT)), - new OperatorFactoryWithTypes(firstOperatorFactory, ImmutableList.of(BIGINT)), - new OperatorFactoryWithTypes(secondOperatorFactory, ImmutableList.of(BIGINT))), - DataSize.ofBytes(0), - 0)); - - DriverContext driverContext = TestingTaskContext.builder(MoreExecutors.directExecutor(), scheduledExecutor, TEST_SESSION) - .build() - .addPipelineContext(0, true, true, false) - .addDriverContext(); - SourceOperator pipelineOperator = pipelineOperatorFactory.createOperator(driverContext); - OperatorContext pipelineOperatorContext = pipelineOperator.getOperatorContext(); - - // make sure WorkProcessorOperator memory is accounted for - sourceOperatorFactory.sourceOperator.memoryTrackingContext.localUserMemoryContext().setBytes(123); - assertEquals(driverContext.getMemoryUsage(), 123); - - assertNull(pipelineOperator.getOutput()); - assertFalse(pipelineOperator.isBlocked().isDone()); - // blocking on splits should not account for blocked time for any WorkProcessorOperator - pipelineOperatorContext.getNestedOperatorStats().forEach( - operatorStats -> assertEquals(operatorStats.getBlockedWall().toMillis(), 0)); - - pipelineOperator.addSplit(split); - assertTrue(pipelineOperator.isBlocked().isDone()); - - assertNull(pipelineOperator.getOutput()); - assertFalse(pipelineOperator.isBlocked().isDone()); - - Thread.sleep(100); - firstBlockedFuture.set(null); - assertTrue(pipelineOperator.isBlocked().isDone()); - - // blocking of first WorkProcessorOperator should be accounted for in stats - List operatorStats = pipelineOperatorContext.getNestedOperatorStats(); - assertEquals(operatorStats.get(0).getBlockedWall().toMillis(), 0); - assertTrue(operatorStats.get(1).getBlockedWall().toMillis() > 0); - assertEquals(operatorStats.get(2).getBlockedWall().toMillis(), 0); - - assertEquals(getTestingOperatorInfo(operatorStats.get(1)).count, 2); - assertEquals(getTestingOperatorInfo(operatorStats.get(2)).count, 2); - - assertEquals(pipelineOperator.getOutput().getPositionCount(), page5.getPositionCount()); - - // sourceOperator should yield - driverContext.getYieldSignal().forceYieldForTesting(); - assertNull(pipelineOperator.getOutput()); - driverContext.getYieldSignal().resetYieldForTesting(); - - // firstOperatorPages should finish. This should cause sourceOperator and firstOperatorPages to close. - // secondOperatorPages should block - assertNull(pipelineOperator.getOutput()); - assertFalse(pipelineOperator.isBlocked().isDone()); - assertTrue(sourceOperatorFactory.sourceOperator.closed); - assertTrue(firstOperatorFactory.operator.closed); - assertFalse(secondOperatorFactory.operator.closed); - - // first operator should return final operator info - assertEquals(getTestingOperatorInfo(operatorStats.get(1)).count, 3); - assertEquals(getTestingOperatorInfo(operatorStats.get(2)).count, 2); - operatorStats = pipelineOperatorContext.getNestedOperatorStats(); - assertEquals(getTestingOperatorInfo(operatorStats.get(1)).count, 3); - assertEquals(getTestingOperatorInfo(operatorStats.get(2)).count, 3); - - // cause early operator finish - pipelineOperator.finish(); - - // operator is still blocked on secondBlockedFuture - assertFalse(pipelineOperator.isFinished()); - assertTrue(secondOperatorFactory.operator.closed); - - secondBlockedFuture.set(null); - assertTrue(pipelineOperator.isBlocked().isDone()); - assertNull(pipelineOperator.getOutput()); - assertTrue(pipelineOperator.isFinished()); - - // assert operator stats are correct - operatorStats = pipelineOperatorContext.getNestedOperatorStats(); - assertEquals(operatorStats.get(0).getOutputPositions(), 3); - assertEquals(operatorStats.get(1).getInputPositions(), 3); - assertEquals(operatorStats.get(0).getOutputDataSize().toBytes(), 27); - assertEquals(operatorStats.get(1).getInputDataSize().toBytes(), 27); - - assertEquals(operatorStats.get(1).getOutputPositions(), 7); - assertEquals(operatorStats.get(2).getInputPositions(), 7); - assertEquals(operatorStats.get(1).getOutputDataSize().toBytes(), 63); - assertEquals(operatorStats.get(2).getInputDataSize().toBytes(), 63); - - assertEquals(operatorStats.get(2).getOutputPositions(), 5); - assertEquals(operatorStats.get(2).getOutputDataSize().toBytes(), 45); - - assertThat(operatorStats.get(1).getMetrics().getMetrics()) - .hasSize(5) - .containsEntry("testOperatorMetric", new LongCount(1)); - - // assert source operator stats are correct - OperatorStats sourceOperatorStats = operatorStats.get(0); - - assertThat(sourceOperatorStats.getMetrics().getMetrics()) - .hasSize(6) - .containsEntry("testSourceMetric", new LongCount(1)) - .containsEntry("testSourceClosed", new LongCount(1)); - assertEquals(sourceOperatorStats.getConnectorMetrics().getMetrics(), ImmutableMap.of( - "testSourceConnectorMetric", new LongCount(2), - "testSourceConnectorClosed", new LongCount(1))); - - assertEquals(sourceOperatorStats.getDynamicFilterSplitsProcessed(), 42L); - - assertEquals(sourceOperatorStats.getPhysicalInputDataSize(), DataSize.ofBytes(1)); - assertEquals(sourceOperatorStats.getPhysicalInputPositions(), 2); - - assertEquals(sourceOperatorStats.getInternalNetworkInputDataSize(), DataSize.ofBytes(3)); - assertEquals(sourceOperatorStats.getInternalNetworkInputPositions(), 4); - - assertEquals(sourceOperatorStats.getInputDataSize(), DataSize.ofBytes(5)); - assertEquals(sourceOperatorStats.getInputPositions(), 6); - - assertEquals(sourceOperatorStats.getAddInputWall(), new Duration(0, NANOSECONDS)); - - // pipeline input stats should match source WorkProcessorOperator stats - PipelineStats pipelineStats = pipelineOperator.getOperatorContext().getDriverContext().getPipelineContext().getPipelineStats(); - assertEquals(sourceOperatorStats.getPhysicalInputDataSize(), pipelineStats.getPhysicalInputDataSize()); - assertEquals(sourceOperatorStats.getPhysicalInputPositions(), pipelineStats.getPhysicalInputPositions()); - - assertEquals(sourceOperatorStats.getInternalNetworkInputDataSize(), pipelineStats.getInternalNetworkInputDataSize()); - assertEquals(sourceOperatorStats.getInternalNetworkInputPositions(), pipelineStats.getInternalNetworkInputPositions()); - - assertEquals(sourceOperatorStats.getInputDataSize(), pipelineStats.getProcessedInputDataSize()); - assertEquals(sourceOperatorStats.getInputPositions(), pipelineStats.getProcessedInputPositions()); - - assertThat(sourceOperatorStats.getPhysicalInputReadTime().convertToMostSuccinctTimeUnit()) - .isEqualTo(pipelineStats.getPhysicalInputReadTime().convertToMostSuccinctTimeUnit()); - - // assert pipeline metrics - List operatorSummaries = pipelineStats.getOperatorSummaries(); - assertThat(operatorSummaries.get(0).getMetrics().getMetrics()) - .hasSize(6) - .containsEntry("testSourceMetric", new LongCount(1)) - .containsEntry("testSourceClosed", new LongCount(1)); - assertEquals(operatorSummaries.get(0).getConnectorMetrics().getMetrics(), ImmutableMap.of( - "testSourceConnectorMetric", new LongCount(2), - "testSourceConnectorClosed", new LongCount(1))); - assertThat(operatorSummaries.get(1).getMetrics().getMetrics()) - .hasSize(5) - .containsEntry("testOperatorMetric", new LongCount(1)); - } - - @Test - public void testMergePages() - { - Transformation sourceOperatorPages = split -> TransformationState.ofResult(createPage(1), false); - Transformation firstOperatorPages = page -> TransformationState.ofResult( - getOnlyElement(rowPagesBuilder(BIGINT).addSequencePage(1, 0).build())); - - TestWorkProcessorSourceOperatorFactory sourceOperatorFactory = new TestWorkProcessorSourceOperatorFactory( - 1, - new PlanNodeId("1"), - sourceOperatorPages); - TestWorkProcessorOperatorFactory firstOperatorFactory = new TestWorkProcessorOperatorFactory(2, firstOperatorPages); - - SourceOperatorFactory pipelineOperatorFactory = (SourceOperatorFactory) getOnlyElement(WorkProcessorPipelineSourceOperator.convertOperators( - ImmutableList.of( - new OperatorFactoryWithTypes(sourceOperatorFactory, ImmutableList.of(BIGINT)), - new OperatorFactoryWithTypes(firstOperatorFactory, ImmutableList.of(BIGINT))), - DataSize.ofBytes(100), - 100)); - - DriverContext driverContext = TestingOperatorContext.create(scheduledExecutor).getDriverContext(); - SourceOperator pipelineOperator = pipelineOperatorFactory.createOperator(driverContext); - pipelineOperator.addSplit(createSplit()); - - assertTrue(pipelineOperator.getOutput().getPositionCount() > 100); - } - - private TestOperatorInfo getTestingOperatorInfo(OperatorStats operatorStats) - { - return (TestOperatorInfo) operatorStats.getInfo(); - } - - private Split createSplit() - { - return new Split( - TEST_CATALOG_HANDLE, - createLocalSplit()); - } - - private Page createPage(int pageNumber) - { - return getOnlyElement(rowPagesBuilder(BIGINT).addSequencePage(pageNumber, pageNumber).build()); - } - - private static class TestWorkProcessorSourceOperatorFactory - implements WorkProcessorSourceOperatorFactory, SourceOperatorFactory - { - final int operatorId; - final PlanNodeId sourceId; - final Transformation transformation; - - TestWorkProcessorSourceOperator sourceOperator; - - TestWorkProcessorSourceOperatorFactory(int operatorId, PlanNodeId sourceId, Transformation transformation) - { - this.operatorId = operatorId; - this.sourceId = sourceId; - this.transformation = transformation; - } - - @Override - public int getOperatorId() - { - return operatorId; - } - - @Override - public PlanNodeId getSourceId() - { - return sourceId; - } - - @Override - public PlanNodeId getPlanNodeId() - { - return sourceId; - } - - @Override - public String getOperatorType() - { - return TestWorkProcessorSourceOperatorFactory.class.getSimpleName(); - } - - @Override - public WorkProcessorSourceOperator create(OperatorContext operatorContext, MemoryTrackingContext memoryTrackingContext, DriverYieldSignal yieldSignal, WorkProcessor splits) - { - assertNull(sourceOperator, "source operator already created"); - sourceOperator = new TestWorkProcessorSourceOperator( - splits - .transform(transformation) - .yielding(yieldSignal::isSet), - memoryTrackingContext); - return sourceOperator; - } - - @Override - public SourceOperator createOperator(DriverContext driverContext) - { - throw new UnsupportedOperationException(); - } - - @Override - public void noMoreOperators() - { - throw new UnsupportedOperationException(); - } - } - - private static class TestWorkProcessorSourceOperator - implements WorkProcessorSourceOperator - { - final WorkProcessor pages; - - boolean closed; - MemoryTrackingContext memoryTrackingContext; - - TestWorkProcessorSourceOperator(WorkProcessor pages, MemoryTrackingContext memoryTrackingContext) - { - this.pages = pages; - this.memoryTrackingContext = memoryTrackingContext; - } - - @Override - public DataSize getPhysicalInputDataSize() - { - return DataSize.ofBytes(1); - } - - @Override - public long getPhysicalInputPositions() - { - return 2; - } - - @Override - public DataSize getInternalNetworkInputDataSize() - { - return DataSize.ofBytes(3); - } - - @Override - public long getInternalNetworkPositions() - { - return 4; - } - - @Override - public DataSize getInputDataSize() - { - return DataSize.ofBytes(5); - } - - @Override - public long getInputPositions() - { - return 6; - } - - @Override - public Duration getReadTime() - { - return new Duration(7, NANOSECONDS); - } - - @Override - public long getDynamicFilterSplitsProcessed() - { - return 42; - } - - @Override - public Metrics getMetrics() - { - return new Metrics(ImmutableMap.of( - "testSourceMetric", new LongCount(1), - "testSourceClosed", new LongCount(closed ? 1 : 0))); - } - - @Override - public Metrics getConnectorMetrics() - { - return new Metrics(ImmutableMap.of( - "testSourceConnectorMetric", new LongCount(2), - "testSourceConnectorClosed", new LongCount(closed ? 1 : 0))); - } - - @Override - public WorkProcessor getOutputPages() - { - return pages; - } - - @Override - public void close() - { - closed = true; - } - } - - private static class TestWorkProcessorOperatorFactory - implements WorkProcessorOperatorFactory, OperatorFactory - { - final int operatorId; - final Transformation transformation; - - TestWorkProcessorOperator operator; - - TestWorkProcessorOperatorFactory(int operatorId, Transformation transformation) - { - this.operatorId = operatorId; - this.transformation = transformation; - } - - @Override - public int getOperatorId() - { - return operatorId; - } - - @Override - public PlanNodeId getPlanNodeId() - { - return new PlanNodeId("test-operator"); - } - - @Override - public String getOperatorType() - { - return TestWorkProcessorOperatorFactory.class.getSimpleName(); - } - - @Override - public WorkProcessorOperator create(ProcessorContext processorContext, WorkProcessor sourcePages) - { - assertNull(operator, "source operator already created"); - operator = new TestWorkProcessorOperator(sourcePages.transform(transformation)); - return operator; - } - - @Override - public Operator createOperator(DriverContext driverContext) - { - throw new UnsupportedOperationException(); - } - - @Override - public void noMoreOperators() - { - throw new UnsupportedOperationException(); - } - - @Override - public OperatorFactory duplicate() - { - throw new UnsupportedOperationException(); - } - } - - private static class TestWorkProcessorOperator - implements WorkProcessorOperator - { - final WorkProcessor pages; - final TestOperatorInfo operatorInfo = new TestOperatorInfo(); - - boolean closed; - - TestWorkProcessorOperator(WorkProcessor pages) - { - this.pages = pages; - } - - @Override - public Optional getOperatorInfo() - { - operatorInfo.count++; - return Optional.of(operatorInfo); - } - - @Override - public Metrics getMetrics() - { - return new Metrics(ImmutableMap.of("testOperatorMetric", new LongCount(1))); - } - - @Override - public WorkProcessor getOutputPages() - { - return pages; - } - - @Override - public void close() - { - closed = true; - } - } - - private static class TestOperatorInfo - implements OperatorInfo - { - int count; - } -} diff --git a/core/trino-main/src/test/java/io/trino/operator/TestWorkProcessorSourceOperatorAdapter.java b/core/trino-main/src/test/java/io/trino/operator/TestWorkProcessorSourceOperatorAdapter.java index 5105df05d830..26cdb362a50a 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestWorkProcessorSourceOperatorAdapter.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestWorkProcessorSourceOperatorAdapter.java @@ -18,7 +18,6 @@ import io.airlift.units.Duration; import io.trino.memory.context.MemoryTrackingContext; import io.trino.metadata.Split; -import io.trino.operator.WorkProcessorSourceOperatorAdapter.AdapterWorkProcessorSourceOperatorFactory; import io.trino.plugin.base.metrics.LongCount; import io.trino.spi.Page; import io.trino.spi.metrics.Metrics; @@ -91,7 +90,7 @@ public void testMetrics() } private static class TestWorkProcessorOperatorFactory - implements AdapterWorkProcessorSourceOperatorFactory + implements WorkProcessorSourceOperatorFactory { @Override public WorkProcessorSourceOperator create( diff --git a/core/trino-main/src/test/java/io/trino/operator/project/TestPageProcessor.java b/core/trino-main/src/test/java/io/trino/operator/project/TestPageProcessor.java index e8cae580be26..471225c9edc0 100644 --- a/core/trino-main/src/test/java/io/trino/operator/project/TestPageProcessor.java +++ b/core/trino-main/src/test/java/io/trino/operator/project/TestPageProcessor.java @@ -154,37 +154,6 @@ public void testSelectAllFilter() assertPageEquals(ImmutableList.of(BIGINT), outputPages.get(0).orElse(null), new Page(createLongSequenceBlock(0, 100))); } - @Test - public void testSelectAllFilterLazyBlock() - { - PageProcessor pageProcessor = new PageProcessor(Optional.of(new SelectAllFilter()), ImmutableList.of(new InputPageProjection(0, BIGINT), new InputPageProjection(1, BIGINT)), OptionalInt.of(100)); - - LazyBlock inputFilterBlock = lazyWrapper(createLongSequenceBlock(0, 100)); - LazyBlock inputProjectionBlock = lazyWrapper(createLongSequenceBlock(100, 200)); - Page inputPage = new Page(inputFilterBlock, inputProjectionBlock); - - Iterator> output = processAndAssertRetainedPageSize(pageProcessor, new DriverYieldSignal(), newSimpleAggregatedMemoryContext(), inputPage, true); - List> outputPages = ImmutableList.copyOf(output); - - assertTrue(inputFilterBlock.isLoaded()); - assertFalse(inputProjectionBlock.isLoaded()); - assertEquals(outputPages.size(), 1); - - inputFilterBlock = lazyWrapper(createLongSequenceBlock(0, 200)); - inputProjectionBlock = lazyWrapper(createLongSequenceBlock(100, 300)); - inputPage = new Page(inputFilterBlock, inputProjectionBlock); - - // batch size should increase because filter block was materialized - output = processAndAssertRetainedPageSize(pageProcessor, new DriverYieldSignal(), newSimpleAggregatedMemoryContext(), inputPage, true); - outputPages = ImmutableList.copyOf(output); - - assertEquals(outputPages.size(), 1); - assertTrue(inputFilterBlock.isLoaded()); - assertFalse(inputProjectionBlock.isLoaded()); - assertPageEquals(ImmutableList.of(BIGINT, BIGINT), outputPages.get(0).get(), new Page(createLongSequenceBlock(0, 200), createLongSequenceBlock(100, 300))); - assertTrue(inputProjectionBlock.isLoaded()); - } - @Test public void testSelectNoneFilter() { @@ -532,23 +501,12 @@ private Iterator> processAndAssertRetainedPageSize(PageProcessor } private Iterator> processAndAssertRetainedPageSize(PageProcessor pageProcessor, DriverYieldSignal yieldSignal, AggregatedMemoryContext memoryContext, Page inputPage) - { - return processAndAssertRetainedPageSize(pageProcessor, yieldSignal, memoryContext, inputPage, false); - } - - private Iterator> processAndAssertRetainedPageSize( - PageProcessor pageProcessor, - DriverYieldSignal yieldSignal, - AggregatedMemoryContext memoryContext, - Page inputPage, - boolean avoidPageMaterialization) { Iterator> output = pageProcessor.process( SESSION, yieldSignal, memoryContext.newLocalMemoryContext(PageProcessor.class.getSimpleName()), - inputPage, - avoidPageMaterialization); + inputPage); assertEquals(memoryContext.getBytes(), 0); return output; } diff --git a/core/trino-main/src/test/java/io/trino/sql/analyzer/TestFeaturesConfig.java b/core/trino-main/src/test/java/io/trino/sql/analyzer/TestFeaturesConfig.java index 8f64174a4c72..c1abf12975d9 100644 --- a/core/trino-main/src/test/java/io/trino/sql/analyzer/TestFeaturesConfig.java +++ b/core/trino-main/src/test/java/io/trino/sql/analyzer/TestFeaturesConfig.java @@ -57,7 +57,6 @@ public void testDefaults() .setFilterAndProjectMinOutputPageRowCount(256) .setMaxRecursionDepth(10) .setMaxGroupingSets(2048) - .setLateMaterializationEnabled(false) .setOmitDateTimeTypePrecision(false) .setLegacyCatalogRoles(false) .setIncrementalHashArrayLoadFactorEnabled(true) @@ -92,7 +91,6 @@ public void testExplicitPropertyMappings() .put("filter-and-project-min-output-page-row-count", "2048") .put("max-recursion-depth", "8") .put("analyzer.max-grouping-sets", "2047") - .put("experimental.late-materialization.enabled", "true") .put("deprecated.omit-datetime-type-precision", "true") .put("deprecated.legacy-catalog-roles", "true") .put("incremental-hash-array-load-factor.enabled", "false") @@ -124,7 +122,6 @@ public void testExplicitPropertyMappings() .setFilterAndProjectMinOutputPageRowCount(2048) .setMaxRecursionDepth(8) .setMaxGroupingSets(2047) - .setLateMaterializationEnabled(true) .setOmitDateTimeTypePrecision(true) .setLegacyCatalogRoles(true) .setIncrementalHashArrayLoadFactorEnabled(false) diff --git a/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestEngineOnlyQueries.java b/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestEngineOnlyQueries.java index ce8e53716e6c..5f9f8f8e00af 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestEngineOnlyQueries.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestEngineOnlyQueries.java @@ -34,7 +34,6 @@ import io.trino.type.SqlIntervalYearMonth; import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Timeout; import java.time.LocalDate; import java.time.LocalDateTime; @@ -55,14 +54,9 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.collect.Iterables.getOnlyElement; import static io.trino.SystemSessionProperties.IGNORE_DOWNSTREAM_PREFERENCES; -import static io.trino.SystemSessionProperties.JOIN_DISTRIBUTION_TYPE; -import static io.trino.SystemSessionProperties.JOIN_REORDERING_STRATEGY; -import static io.trino.SystemSessionProperties.LATE_MATERIALIZATION; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.BooleanType.BOOLEAN; import static io.trino.spi.type.VarcharType.VARCHAR; -import static io.trino.sql.planner.OptimizerConfig.JoinDistributionType.BROADCAST; -import static io.trino.sql.planner.OptimizerConfig.JoinReorderingStrategy.NONE; import static io.trino.sql.tree.ExplainType.Type.DISTRIBUTED; import static io.trino.sql.tree.ExplainType.Type.IO; import static io.trino.sql.tree.ExplainType.Type.LOGICAL; @@ -6229,18 +6223,6 @@ private static String pivotQuery(int columnsCount) return format("SELECT * FROM (SELECT %s FROM region LIMIT 1) a(%s) INNER JOIN unnest(ARRAY[%s], ARRAY[%2$s]) b(b1, b2) ON true", fields, columns, literals); } - @Test - @Timeout(30) - public void testLateMaterializationOuterJoin() - { - Session session = Session.builder(getSession()) - .setSystemProperty(LATE_MATERIALIZATION, "true") - .setSystemProperty(JOIN_REORDERING_STRATEGY, NONE.toString()) - .setSystemProperty(JOIN_DISTRIBUTION_TYPE, BROADCAST.toString()) - .build(); - assertQuery(session, "SELECT * FROM (SELECT * FROM nation WHERE nationkey < -1) a RIGHT JOIN nation b ON a.nationkey = b.nationkey"); - } - /** * Regression test for https://github.com/trinodb/trino/pull/7723. */ diff --git a/testing/trino-tests/src/test/java/io/trino/tests/TestLateMaterializationQueries.java b/testing/trino-tests/src/test/java/io/trino/tests/TestLateMaterializationQueries.java deleted file mode 100644 index 65b18bc48389..000000000000 --- a/testing/trino-tests/src/test/java/io/trino/tests/TestLateMaterializationQueries.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * 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 io.trino.tests; - -import io.trino.Session; -import io.trino.execution.QueryInfo; -import io.trino.execution.QueryManager; -import io.trino.testing.AbstractTestQueryFramework; -import io.trino.testing.MaterializedResult; -import io.trino.testing.MaterializedResultWithQueryId; -import io.trino.testing.QueryRunner; -import io.trino.tests.tpch.TpchQueryRunnerBuilder; -import org.intellij.lang.annotations.Language; -import org.junit.jupiter.api.Test; - -import static io.trino.SystemSessionProperties.FILTERING_SEMI_JOIN_TO_INNER; -import static io.trino.SystemSessionProperties.JOIN_DISTRIBUTION_TYPE; -import static io.trino.SystemSessionProperties.JOIN_REORDERING_STRATEGY; -import static io.trino.SystemSessionProperties.LATE_MATERIALIZATION; -import static io.trino.sql.planner.OptimizerConfig.JoinDistributionType.BROADCAST; -import static io.trino.sql.planner.OptimizerConfig.JoinReorderingStrategy.NONE; -import static io.trino.testing.QueryAssertions.assertEqualsIgnoreOrder; -import static org.testng.Assert.assertTrue; - -public class TestLateMaterializationQueries - extends AbstractTestQueryFramework -{ - @Override - protected QueryRunner createQueryRunner() - throws Exception - { - return TpchQueryRunnerBuilder - .builder() - .amendSession(builder -> builder - .setSystemProperty(LATE_MATERIALIZATION, "true") - // disable semi join to inner join rewrite to test semi join operators explicitly - .setSystemProperty(FILTERING_SEMI_JOIN_TO_INNER, "false") - // make sure synthetic order (with effective filtering) is used in tests - .setSystemProperty(JOIN_REORDERING_STRATEGY, NONE.toString()) - .setSystemProperty(JOIN_DISTRIBUTION_TYPE, BROADCAST.toString())) - // make TPCH connector produce multiple pages per split - .withProducePages(true) - .withMaxRowsPerPage(10) - .build(); - } - - @Test - public void testTopN() - { - assertLazyQuery("SELECT * FROM orders ORDER BY totalprice LIMIT 10"); - assertLazyQuery("SELECT * FROM orders WHERE orderkey >= 10 ORDER BY totalprice LIMIT 10"); - } - - @Test - public void testSemiJoin() - { - assertLazyQuery("SELECT * FROM orders WHERE orderkey IN (SELECT orderkey FROM orders WHERE orderdate < DATE '1994-01-01')"); - assertLazyQuery("" + - "SELECT * " + - "FROM (SELECT * FROM orders WHERE orderkey NOT IN (SELECT orderkey FROM orders WHERE orderdate > DATE '2200-01-01')) " + - "WHERE orderkey IN (SELECT orderkey FROM orders WHERE orderdate < DATE '1994-01-01')"); - assertLazyQuery("SELECT * FROM orders WHERE orderkey NOT IN (SELECT orderkey FROM orders WHERE orderdate >= DATE '1994-01-01')"); - } - - @Test - public void testJoin() - { - assertLazyQuery("SELECT * FROM lineitem INNER JOIN part ON lineitem.partkey = part.partkey AND mfgr = 'Manufacturer#5'"); - assertLazyQuery("" + - "SELECT * " + - "FROM (SELECT a.* FROM lineitem a INNER JOIN lineitem b ON a.partkey = b.partkey AND a.suppkey = b.suppkey AND a.orderkey = b.orderkey) c " + - "INNER JOIN part ON c.partkey = part.partkey AND mfgr = 'Manufacturer#5'"); - } - - private void assertLazyQuery(@Language("SQL") String sql) - { - QueryManager queryManager = getDistributedQueryRunner().getCoordinator().getQueryManager(); - - MaterializedResultWithQueryId workProcessorResultResultWithQueryId = getDistributedQueryRunner().executeWithQueryId(lateMaterialization(), sql); - QueryInfo workProcessorQueryInfo = queryManager.getFullQueryInfo(workProcessorResultResultWithQueryId.getQueryId()); - - MaterializedResultWithQueryId noWorkProcessorResultResultWithQueryId = getDistributedQueryRunner().executeWithQueryId(noLateMaterialization(), sql); - QueryInfo noWorkProcessorQueryInfo = queryManager.getFullQueryInfo(noWorkProcessorResultResultWithQueryId.getQueryId()); - - // ensure results are correct - MaterializedResult expected = computeExpected(sql, workProcessorResultResultWithQueryId.getResult().getTypes()); - assertEqualsIgnoreOrder(workProcessorResultResultWithQueryId.getResult(), expected, "For query: \n " + sql); - assertEqualsIgnoreOrder(noWorkProcessorResultResultWithQueryId.getResult(), expected, "For query: \n " + sql); - - // ensure work processor query processed less input data - long workProcessorProcessedInputBytes = workProcessorQueryInfo.getQueryStats().getProcessedInputDataSize().toBytes(); - long noWorkProcessorProcessedInputBytes = noWorkProcessorQueryInfo.getQueryStats().getProcessedInputDataSize().toBytes(); - assertTrue(workProcessorProcessedInputBytes < noWorkProcessorProcessedInputBytes, "Expected work processor query to process less input data"); - } - - private Session lateMaterialization() - { - return Session.builder(getSession()) - .setSystemProperty(LATE_MATERIALIZATION, "true") - .build(); - } - - private Session noLateMaterialization() - { - return Session.builder(getSession()) - .setSystemProperty(LATE_MATERIALIZATION, "false") - .build(); - } -} From 57d68ca62a312d5167d3f547b56ad81160f1962d Mon Sep 17 00:00:00 2001 From: kasiafi <30203062+kasiafi@users.noreply.github.com> Date: Thu, 6 Jul 2023 15:23:26 +0200 Subject: [PATCH 038/587] Fix formatting of json_table columns in SqlFormatter --- .../src/main/java/io/trino/sql/SqlFormatter.java | 2 +- .../src/test/java/io/trino/sql/parser/TestSqlParser.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/trino-parser/src/main/java/io/trino/sql/SqlFormatter.java b/core/trino-parser/src/main/java/io/trino/sql/SqlFormatter.java index 9f17c3a0ebcb..9738cf63fe41 100644 --- a/core/trino-parser/src/main/java/io/trino/sql/SqlFormatter.java +++ b/core/trino-parser/src/main/java/io/trino/sql/SqlFormatter.java @@ -372,7 +372,7 @@ protected Void visitQueryColumn(QueryColumn node, Integer indent) .append(" ") .append(formatExpression(node.getType())) .append(" FORMAT ") - .append(node.getFormat().name()); + .append(node.getFormat().toString()); node.getJsonPath().ifPresent(path -> builder.append(" PATH ") .append(formatExpression(path))); diff --git a/core/trino-parser/src/test/java/io/trino/sql/parser/TestSqlParser.java b/core/trino-parser/src/test/java/io/trino/sql/parser/TestSqlParser.java index 4d922f76d7da..a2409be22c66 100644 --- a/core/trino-parser/src/test/java/io/trino/sql/parser/TestSqlParser.java +++ b/core/trino-parser/src/test/java/io/trino/sql/parser/TestSqlParser.java @@ -5907,7 +5907,7 @@ public void testJsonTableScalarColumns() "ordinal_number FOR ORDINALITY, " + "customer_name varchar PATH 'lax $.cust_no' DEFAULT 'anonymous' ON EMPTY null ON ERROR, " + "customer_countries varchar FORMAT JSON PATH 'lax.cust_ctr[*]' WITH WRAPPER KEEP QUOTES null ON EMPTY ERROR ON ERROR," + - "customer_regions varchar FORMAT JSON PATH 'lax.cust_reg[*]' EMPTY ARRAY ON EMPTY EMPTY OBJECT ON ERROR) " + + "customer_regions varchar FORMAT JSON ENCODING UTF16 PATH 'lax.cust_reg[*]' EMPTY ARRAY ON EMPTY EMPTY OBJECT ON ERROR) " + "EMPTY ON ERROR)")) .isEqualTo(selectAllFrom(new JsonTable( location(1, 15), @@ -5943,8 +5943,8 @@ public void testJsonTableScalarColumns() location(1, 281), new Identifier(location(1, 281), "customer_regions", false), new GenericDataType(location(1, 298), new Identifier(location(1, 298), "varchar", false), ImmutableList.of()), - JSON, - Optional.of(new StringLiteral(location(1, 323), "lax.cust_reg[*]")), + UTF16, + Optional.of(new StringLiteral(location(1, 338), "lax.cust_reg[*]")), JsonQuery.ArrayWrapperBehavior.WITHOUT, Optional.empty(), JsonQuery.EmptyOrErrorBehavior.EMPTY_ARRAY, From 406d0f88bbba5bd323ce83b3e16b11f2543148ee Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Thu, 2 Nov 2023 09:53:24 -0700 Subject: [PATCH 039/587] Remove unnecessary logging It generates unnecessary clutter when running tests. Follow up to https://github.com/trinodb/trino/pull/17725 --- .../io/trino/plugin/iceberg/containers/KeycloakContainer.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/containers/KeycloakContainer.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/containers/KeycloakContainer.java index 43d5801b4218..a43074b99062 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/containers/KeycloakContainer.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/containers/KeycloakContainer.java @@ -16,7 +16,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import io.airlift.log.Logger; import io.trino.testing.containers.BaseTestContainer; import org.keycloak.admin.client.Keycloak; import org.keycloak.admin.client.KeycloakBuilder; @@ -31,8 +30,6 @@ public class KeycloakContainer extends BaseTestContainer { - private static final Logger log = Logger.get(KeycloakContainer.class); - public static final String DEFAULT_IMAGE = "quay.io/keycloak/keycloak:21.1.2"; public static final String DEFAULT_HOST_NAME = "keycloak"; @@ -70,7 +67,6 @@ protected void setupContainer() public void start() { super.start(); - log.info("Keycloak container started with URL: %s", getUrl()); } public String getUrl() From 726f46ec92b1796fcf179972015fb4e733cf3b9a Mon Sep 17 00:00:00 2001 From: Colebow Date: Tue, 31 Oct 2023 11:31:44 -0700 Subject: [PATCH 040/587] Add Trino 432 release notes --- docs/src/main/sphinx/release.md | 1 + docs/src/main/sphinx/release/release-432.md | 63 +++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 docs/src/main/sphinx/release/release-432.md diff --git a/docs/src/main/sphinx/release.md b/docs/src/main/sphinx/release.md index 4359ce3f1425..62a1407ff714 100644 --- a/docs/src/main/sphinx/release.md +++ b/docs/src/main/sphinx/release.md @@ -7,6 +7,7 @@ ```{toctree} :maxdepth: 1 +release/release-432 release/release-431 release/release-430 release/release-429 diff --git a/docs/src/main/sphinx/release/release-432.md b/docs/src/main/sphinx/release/release-432.md new file mode 100644 index 000000000000..176ef6a6cbdf --- /dev/null +++ b/docs/src/main/sphinx/release/release-432.md @@ -0,0 +1,63 @@ +# Release 432 (2 Nov 2023) + +:::{note} +Release notes now include a ⚠️ symbol to highlight any changes as potentially +breaking changes. The following changes are considered and may require +adjustments: +* Removal or renaming of configuration properties that may prevent startup or + require configuration changes +* Changes to default values for configuration properties that may significantly + change the behavior of a system +* Updates to the requirements for external systems or software used with Trino, + such as removal of support for an old version of a data source in a connector +* Non-backwards compatible changes to the SPI which may require plugins to be + updated +* Otherwise significant changes that requires specific attention from teams + managing a Trino deployment +::: + +## General + +* Improve performance of `CREATE TABLE AS ... SELECT` queries that contain a redundant + `ORDER BY` clause. ({issue}`19547`) +* ⚠️ Remove support for late materialization, including the + `experimental.late-materialization.enabled` and + `experimental.work-processor-pipelines` configuration properties. ({issue}`19611`) +* Fix potential query failure when using inline functions. ({issue}`19561`) + +## Docker image + +* Update Java runtime to Java 21. ({issue}`19553`) + +## CLI + +* Fix crashes when using Homebrew's version of the `stty` command. ({issue}`19549`) + +## Delta Lake connector + +* Improve performance of filtering on columns with long strings stored in + Parquet files. ({issue}`19038`) + +## Hive connector + +* Improve performance of filtering on columns with long strings stored in + Parquet files. ({issue}`19038`) + +## Iceberg connector + +* Add support for the `register_table` and `unregister_table` procedures with + the REST catalog. ({issue}`15512`) +* Add support for the [`BEARER` authentication type](https://projectnessie.org/tools/client_config/) + for connecting to the Nessie catalog. ({issue}`17725`) +* Improve performance of filtering on columns with long strings stored in + Parquet files. ({issue}`19038`) + +## MongoDB connector + +* Add support for predicate pushdown on `real` and `double` types. ({issue}`19575`) + +## SPI + +* Add Trino version to SystemAccessControlContext. ({issue}`19585`) +* ⚠️ Remove null-suppression from RowBlock fields. Add new factory methods to + create a `RowBlock`, and remove the old factory methods. ({issue}`19479`) From 4ca25b49b83095cc2a19ffe31386cf0175df2466 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Fri, 3 Nov 2023 00:45:03 +0000 Subject: [PATCH 041/587] [maven-release-plugin] prepare release 432 --- client/trino-cli/pom.xml | 2 +- client/trino-client/pom.xml | 2 +- client/trino-jdbc/pom.xml | 2 +- core/trino-grammar/pom.xml | 2 +- core/trino-main/pom.xml | 2 +- core/trino-parser/pom.xml | 2 +- core/trino-server-main/pom.xml | 2 +- core/trino-server-rpm/pom.xml | 2 +- core/trino-server/pom.xml | 2 +- core/trino-spi/pom.xml | 2 +- docs/pom.xml | 2 +- lib/trino-array/pom.xml | 2 +- lib/trino-cache/pom.xml | 2 +- lib/trino-filesystem-azure/pom.xml | 2 +- lib/trino-filesystem-manager/pom.xml | 2 +- lib/trino-filesystem-s3/pom.xml | 2 +- lib/trino-filesystem/pom.xml | 2 +- lib/trino-geospatial-toolkit/pom.xml | 2 +- lib/trino-hdfs/pom.xml | 2 +- lib/trino-hive-formats/pom.xml | 2 +- lib/trino-ignite-patched/pom.xml | 2 +- lib/trino-matching/pom.xml | 2 +- lib/trino-memory-context/pom.xml | 2 +- lib/trino-orc/pom.xml | 2 +- lib/trino-parquet/pom.xml | 2 +- lib/trino-phoenix5-patched/pom.xml | 2 +- lib/trino-plugin-toolkit/pom.xml | 2 +- lib/trino-record-decoder/pom.xml | 2 +- plugin/trino-accumulo-iterators/pom.xml | 2 +- plugin/trino-accumulo/pom.xml | 2 +- plugin/trino-atop/pom.xml | 2 +- plugin/trino-base-jdbc/pom.xml | 2 +- plugin/trino-bigquery/pom.xml | 2 +- plugin/trino-blackhole/pom.xml | 2 +- plugin/trino-cassandra/pom.xml | 2 +- plugin/trino-clickhouse/pom.xml | 2 +- plugin/trino-delta-lake/pom.xml | 2 +- plugin/trino-druid/pom.xml | 2 +- plugin/trino-elasticsearch/pom.xml | 2 +- plugin/trino-example-http/pom.xml | 2 +- plugin/trino-example-jdbc/pom.xml | 2 +- plugin/trino-exchange-filesystem/pom.xml | 2 +- plugin/trino-exchange-hdfs/pom.xml | 2 +- plugin/trino-geospatial/pom.xml | 2 +- plugin/trino-google-sheets/pom.xml | 2 +- plugin/trino-hive-hadoop2/pom.xml | 2 +- plugin/trino-hive/pom.xml | 2 +- plugin/trino-http-event-listener/pom.xml | 2 +- plugin/trino-hudi/pom.xml | 2 +- plugin/trino-iceberg/pom.xml | 2 +- plugin/trino-ignite/pom.xml | 2 +- plugin/trino-jmx/pom.xml | 2 +- plugin/trino-kafka/pom.xml | 2 +- plugin/trino-kinesis/pom.xml | 2 +- plugin/trino-kudu/pom.xml | 2 +- plugin/trino-local-file/pom.xml | 2 +- plugin/trino-mariadb/pom.xml | 2 +- plugin/trino-memory/pom.xml | 2 +- plugin/trino-ml/pom.xml | 2 +- plugin/trino-mongodb/pom.xml | 2 +- plugin/trino-mysql-event-listener/pom.xml | 2 +- plugin/trino-mysql/pom.xml | 2 +- plugin/trino-oracle/pom.xml | 2 +- plugin/trino-password-authenticators/pom.xml | 2 +- plugin/trino-phoenix5/pom.xml | 2 +- plugin/trino-pinot/pom.xml | 2 +- plugin/trino-postgresql/pom.xml | 2 +- plugin/trino-prometheus/pom.xml | 2 +- plugin/trino-raptor-legacy/pom.xml | 2 +- plugin/trino-redis/pom.xml | 2 +- plugin/trino-redshift/pom.xml | 2 +- plugin/trino-resource-group-managers/pom.xml | 2 +- plugin/trino-session-property-managers/pom.xml | 2 +- plugin/trino-singlestore/pom.xml | 2 +- plugin/trino-sqlserver/pom.xml | 2 +- plugin/trino-teradata-functions/pom.xml | 2 +- plugin/trino-thrift-api/pom.xml | 2 +- plugin/trino-thrift-testing-server/pom.xml | 2 +- plugin/trino-thrift/pom.xml | 2 +- plugin/trino-tpcds/pom.xml | 2 +- plugin/trino-tpch/pom.xml | 2 +- pom.xml | 4 ++-- service/trino-proxy/pom.xml | 2 +- service/trino-verifier/pom.xml | 2 +- testing/trino-benchmark-queries/pom.xml | 2 +- testing/trino-benchmark/pom.xml | 2 +- testing/trino-benchto-benchmarks/pom.xml | 2 +- testing/trino-faulttolerant-tests/pom.xml | 2 +- testing/trino-plugin-reader/pom.xml | 2 +- testing/trino-product-tests-launcher/pom.xml | 2 +- testing/trino-product-tests/pom.xml | 2 +- testing/trino-server-dev/pom.xml | 2 +- testing/trino-test-jdbc-compatibility-old-driver/pom.xml | 4 ++-- testing/trino-test-jdbc-compatibility-old-server/pom.xml | 2 +- testing/trino-testing-containers/pom.xml | 2 +- testing/trino-testing-kafka/pom.xml | 2 +- testing/trino-testing-resources/pom.xml | 2 +- testing/trino-testing-services/pom.xml | 2 +- testing/trino-testing/pom.xml | 2 +- testing/trino-tests/pom.xml | 2 +- 100 files changed, 102 insertions(+), 102 deletions(-) diff --git a/client/trino-cli/pom.xml b/client/trino-cli/pom.xml index 4233e8108017..ee828b643276 100644 --- a/client/trino-cli/pom.xml +++ b/client/trino-cli/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/client/trino-client/pom.xml b/client/trino-client/pom.xml index 57a9ead14300..a7aff6d6bbcc 100644 --- a/client/trino-client/pom.xml +++ b/client/trino-client/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/client/trino-jdbc/pom.xml b/client/trino-jdbc/pom.xml index 6153f48e9896..e5a54ad7b107 100644 --- a/client/trino-jdbc/pom.xml +++ b/client/trino-jdbc/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/core/trino-grammar/pom.xml b/core/trino-grammar/pom.xml index fe40ecae85d2..131d084d222f 100644 --- a/core/trino-grammar/pom.xml +++ b/core/trino-grammar/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/core/trino-main/pom.xml b/core/trino-main/pom.xml index 4054fcfb090b..45c20f04f93e 100644 --- a/core/trino-main/pom.xml +++ b/core/trino-main/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/core/trino-parser/pom.xml b/core/trino-parser/pom.xml index 09f93e578258..28da47e97cb3 100644 --- a/core/trino-parser/pom.xml +++ b/core/trino-parser/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/core/trino-server-main/pom.xml b/core/trino-server-main/pom.xml index 522e3c52d8bf..5fd90192edad 100644 --- a/core/trino-server-main/pom.xml +++ b/core/trino-server-main/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/core/trino-server-rpm/pom.xml b/core/trino-server-rpm/pom.xml index a82e2b6d438f..e5158cafe302 100644 --- a/core/trino-server-rpm/pom.xml +++ b/core/trino-server-rpm/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/core/trino-server/pom.xml b/core/trino-server/pom.xml index f24d140328d6..0982745c4d2a 100644 --- a/core/trino-server/pom.xml +++ b/core/trino-server/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/core/trino-spi/pom.xml b/core/trino-spi/pom.xml index c0fe41010e89..1bd040a81c8d 100644 --- a/core/trino-spi/pom.xml +++ b/core/trino-spi/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/docs/pom.xml b/docs/pom.xml index 8499c4ee0069..5588b4dbba53 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 trino-docs diff --git a/lib/trino-array/pom.xml b/lib/trino-array/pom.xml index 124707cef584..582f31cd67a5 100644 --- a/lib/trino-array/pom.xml +++ b/lib/trino-array/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/lib/trino-cache/pom.xml b/lib/trino-cache/pom.xml index 85136f65fca5..7fc6eabf5325 100644 --- a/lib/trino-cache/pom.xml +++ b/lib/trino-cache/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/lib/trino-filesystem-azure/pom.xml b/lib/trino-filesystem-azure/pom.xml index a62d788616f4..f6041e5dbcef 100644 --- a/lib/trino-filesystem-azure/pom.xml +++ b/lib/trino-filesystem-azure/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/lib/trino-filesystem-manager/pom.xml b/lib/trino-filesystem-manager/pom.xml index 7fea178e1cce..b86472e4d53f 100644 --- a/lib/trino-filesystem-manager/pom.xml +++ b/lib/trino-filesystem-manager/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/lib/trino-filesystem-s3/pom.xml b/lib/trino-filesystem-s3/pom.xml index 3f131e3bba69..215adad62dd6 100644 --- a/lib/trino-filesystem-s3/pom.xml +++ b/lib/trino-filesystem-s3/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/lib/trino-filesystem/pom.xml b/lib/trino-filesystem/pom.xml index f05229d86dab..a6f8a1b40ffb 100644 --- a/lib/trino-filesystem/pom.xml +++ b/lib/trino-filesystem/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/lib/trino-geospatial-toolkit/pom.xml b/lib/trino-geospatial-toolkit/pom.xml index 8f8dd952df8e..097d4627a8ea 100644 --- a/lib/trino-geospatial-toolkit/pom.xml +++ b/lib/trino-geospatial-toolkit/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/lib/trino-hdfs/pom.xml b/lib/trino-hdfs/pom.xml index a7b55ce5753c..518d7fb485d9 100644 --- a/lib/trino-hdfs/pom.xml +++ b/lib/trino-hdfs/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/lib/trino-hive-formats/pom.xml b/lib/trino-hive-formats/pom.xml index c63dd3b79e18..ba21a9dfbef3 100644 --- a/lib/trino-hive-formats/pom.xml +++ b/lib/trino-hive-formats/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/lib/trino-ignite-patched/pom.xml b/lib/trino-ignite-patched/pom.xml index 499c9615acd1..62b484947da7 100644 --- a/lib/trino-ignite-patched/pom.xml +++ b/lib/trino-ignite-patched/pom.xml @@ -6,7 +6,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/lib/trino-matching/pom.xml b/lib/trino-matching/pom.xml index c56c1717acd8..dacd71a8b4e7 100644 --- a/lib/trino-matching/pom.xml +++ b/lib/trino-matching/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/lib/trino-memory-context/pom.xml b/lib/trino-memory-context/pom.xml index 430b3a38a2e4..7fde8c7cf0dd 100644 --- a/lib/trino-memory-context/pom.xml +++ b/lib/trino-memory-context/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/lib/trino-orc/pom.xml b/lib/trino-orc/pom.xml index 13fa458b251e..6f07698cef3d 100644 --- a/lib/trino-orc/pom.xml +++ b/lib/trino-orc/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/lib/trino-parquet/pom.xml b/lib/trino-parquet/pom.xml index b8e98caa64eb..4d6b98ca0d28 100644 --- a/lib/trino-parquet/pom.xml +++ b/lib/trino-parquet/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/lib/trino-phoenix5-patched/pom.xml b/lib/trino-phoenix5-patched/pom.xml index 5652d0ad421d..1fafbf8137bf 100644 --- a/lib/trino-phoenix5-patched/pom.xml +++ b/lib/trino-phoenix5-patched/pom.xml @@ -6,7 +6,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/lib/trino-plugin-toolkit/pom.xml b/lib/trino-plugin-toolkit/pom.xml index dadb793e0cc3..d2985fa0bdc7 100644 --- a/lib/trino-plugin-toolkit/pom.xml +++ b/lib/trino-plugin-toolkit/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/lib/trino-record-decoder/pom.xml b/lib/trino-record-decoder/pom.xml index a244cda84631..86c811d6537a 100644 --- a/lib/trino-record-decoder/pom.xml +++ b/lib/trino-record-decoder/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-accumulo-iterators/pom.xml b/plugin/trino-accumulo-iterators/pom.xml index be46006c0b45..53fda3ef1d52 100644 --- a/plugin/trino-accumulo-iterators/pom.xml +++ b/plugin/trino-accumulo-iterators/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-accumulo/pom.xml b/plugin/trino-accumulo/pom.xml index 0a8d2238c99c..603d756276ce 100644 --- a/plugin/trino-accumulo/pom.xml +++ b/plugin/trino-accumulo/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-atop/pom.xml b/plugin/trino-atop/pom.xml index 1c7cb1783a65..62131af8de32 100644 --- a/plugin/trino-atop/pom.xml +++ b/plugin/trino-atop/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-base-jdbc/pom.xml b/plugin/trino-base-jdbc/pom.xml index a9834698aef9..f7883eceb368 100644 --- a/plugin/trino-base-jdbc/pom.xml +++ b/plugin/trino-base-jdbc/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-bigquery/pom.xml b/plugin/trino-bigquery/pom.xml index 36022823533f..22a1e707cf56 100644 --- a/plugin/trino-bigquery/pom.xml +++ b/plugin/trino-bigquery/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-blackhole/pom.xml b/plugin/trino-blackhole/pom.xml index 6d648599f932..72265465fbf4 100644 --- a/plugin/trino-blackhole/pom.xml +++ b/plugin/trino-blackhole/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-cassandra/pom.xml b/plugin/trino-cassandra/pom.xml index fa19d4cb12ac..9ce8d3b03fc4 100644 --- a/plugin/trino-cassandra/pom.xml +++ b/plugin/trino-cassandra/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-clickhouse/pom.xml b/plugin/trino-clickhouse/pom.xml index c557f2f8dfa6..081b1899e416 100644 --- a/plugin/trino-clickhouse/pom.xml +++ b/plugin/trino-clickhouse/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-delta-lake/pom.xml b/plugin/trino-delta-lake/pom.xml index ae6387e8a956..758dc5fa2e5a 100644 --- a/plugin/trino-delta-lake/pom.xml +++ b/plugin/trino-delta-lake/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-druid/pom.xml b/plugin/trino-druid/pom.xml index 4275e57c2ed3..43bf8053362d 100644 --- a/plugin/trino-druid/pom.xml +++ b/plugin/trino-druid/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-elasticsearch/pom.xml b/plugin/trino-elasticsearch/pom.xml index b3967b96c2ea..24ec7ff2d384 100644 --- a/plugin/trino-elasticsearch/pom.xml +++ b/plugin/trino-elasticsearch/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-example-http/pom.xml b/plugin/trino-example-http/pom.xml index e39df530692f..188ae41c4647 100644 --- a/plugin/trino-example-http/pom.xml +++ b/plugin/trino-example-http/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-example-jdbc/pom.xml b/plugin/trino-example-jdbc/pom.xml index d2faee099f1f..3256b18a2478 100644 --- a/plugin/trino-example-jdbc/pom.xml +++ b/plugin/trino-example-jdbc/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-exchange-filesystem/pom.xml b/plugin/trino-exchange-filesystem/pom.xml index 8f9078d883aa..2b58052c57e7 100644 --- a/plugin/trino-exchange-filesystem/pom.xml +++ b/plugin/trino-exchange-filesystem/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-exchange-hdfs/pom.xml b/plugin/trino-exchange-hdfs/pom.xml index 1b057aedf6d6..41d64dc6af84 100644 --- a/plugin/trino-exchange-hdfs/pom.xml +++ b/plugin/trino-exchange-hdfs/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-geospatial/pom.xml b/plugin/trino-geospatial/pom.xml index 277fd5213323..c8dfc732b111 100644 --- a/plugin/trino-geospatial/pom.xml +++ b/plugin/trino-geospatial/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-google-sheets/pom.xml b/plugin/trino-google-sheets/pom.xml index 2dd5d2aa95b1..c16187180e28 100644 --- a/plugin/trino-google-sheets/pom.xml +++ b/plugin/trino-google-sheets/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-hive-hadoop2/pom.xml b/plugin/trino-hive-hadoop2/pom.xml index e94141f8214d..df400f052638 100644 --- a/plugin/trino-hive-hadoop2/pom.xml +++ b/plugin/trino-hive-hadoop2/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-hive/pom.xml b/plugin/trino-hive/pom.xml index e6e97e1775b5..bfffd0ec408f 100644 --- a/plugin/trino-hive/pom.xml +++ b/plugin/trino-hive/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-http-event-listener/pom.xml b/plugin/trino-http-event-listener/pom.xml index f04806ab18ae..8393a3950fb6 100644 --- a/plugin/trino-http-event-listener/pom.xml +++ b/plugin/trino-http-event-listener/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-hudi/pom.xml b/plugin/trino-hudi/pom.xml index 442dc12fdb1e..502b8c94fa1a 100644 --- a/plugin/trino-hudi/pom.xml +++ b/plugin/trino-hudi/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-iceberg/pom.xml b/plugin/trino-iceberg/pom.xml index 27e6a8e49c8e..d899a7185746 100644 --- a/plugin/trino-iceberg/pom.xml +++ b/plugin/trino-iceberg/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-ignite/pom.xml b/plugin/trino-ignite/pom.xml index 2c2e76ac7935..100b813f5276 100644 --- a/plugin/trino-ignite/pom.xml +++ b/plugin/trino-ignite/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-jmx/pom.xml b/plugin/trino-jmx/pom.xml index b805878bcc31..9ee82bbf920e 100644 --- a/plugin/trino-jmx/pom.xml +++ b/plugin/trino-jmx/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-kafka/pom.xml b/plugin/trino-kafka/pom.xml index 62abe96ba5d6..dcde170ed710 100644 --- a/plugin/trino-kafka/pom.xml +++ b/plugin/trino-kafka/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-kinesis/pom.xml b/plugin/trino-kinesis/pom.xml index 9e54bb42275c..854180321aae 100644 --- a/plugin/trino-kinesis/pom.xml +++ b/plugin/trino-kinesis/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-kudu/pom.xml b/plugin/trino-kudu/pom.xml index 005be69afc4a..3274b1aea220 100644 --- a/plugin/trino-kudu/pom.xml +++ b/plugin/trino-kudu/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-local-file/pom.xml b/plugin/trino-local-file/pom.xml index 6df9d6b9e00d..2558a4a80712 100644 --- a/plugin/trino-local-file/pom.xml +++ b/plugin/trino-local-file/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-mariadb/pom.xml b/plugin/trino-mariadb/pom.xml index bc4c7da00cae..a4e0ddbdcffe 100644 --- a/plugin/trino-mariadb/pom.xml +++ b/plugin/trino-mariadb/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-memory/pom.xml b/plugin/trino-memory/pom.xml index 962440c07c39..c34e9d54e4d4 100644 --- a/plugin/trino-memory/pom.xml +++ b/plugin/trino-memory/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-ml/pom.xml b/plugin/trino-ml/pom.xml index 77dcd4a8b532..9f4d038d47a6 100644 --- a/plugin/trino-ml/pom.xml +++ b/plugin/trino-ml/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-mongodb/pom.xml b/plugin/trino-mongodb/pom.xml index 922b912d5597..425c39d14d6d 100644 --- a/plugin/trino-mongodb/pom.xml +++ b/plugin/trino-mongodb/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-mysql-event-listener/pom.xml b/plugin/trino-mysql-event-listener/pom.xml index 396fd22384cf..9d75a21a16da 100644 --- a/plugin/trino-mysql-event-listener/pom.xml +++ b/plugin/trino-mysql-event-listener/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-mysql/pom.xml b/plugin/trino-mysql/pom.xml index 1ad673e59ea3..023cefc192be 100644 --- a/plugin/trino-mysql/pom.xml +++ b/plugin/trino-mysql/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-oracle/pom.xml b/plugin/trino-oracle/pom.xml index ced7ff63af8e..dd41f1b05ce1 100644 --- a/plugin/trino-oracle/pom.xml +++ b/plugin/trino-oracle/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-password-authenticators/pom.xml b/plugin/trino-password-authenticators/pom.xml index 7d1a849924fa..b69b2fa26e94 100644 --- a/plugin/trino-password-authenticators/pom.xml +++ b/plugin/trino-password-authenticators/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-phoenix5/pom.xml b/plugin/trino-phoenix5/pom.xml index 61204e245095..0b27820ca68d 100644 --- a/plugin/trino-phoenix5/pom.xml +++ b/plugin/trino-phoenix5/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-pinot/pom.xml b/plugin/trino-pinot/pom.xml index 22dc70e91041..37c66d8c4d9a 100755 --- a/plugin/trino-pinot/pom.xml +++ b/plugin/trino-pinot/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-postgresql/pom.xml b/plugin/trino-postgresql/pom.xml index 87c504bfe5e8..2e95ebf27dbd 100644 --- a/plugin/trino-postgresql/pom.xml +++ b/plugin/trino-postgresql/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-prometheus/pom.xml b/plugin/trino-prometheus/pom.xml index 74faea8fe804..2d2a5911013b 100644 --- a/plugin/trino-prometheus/pom.xml +++ b/plugin/trino-prometheus/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-raptor-legacy/pom.xml b/plugin/trino-raptor-legacy/pom.xml index f0b4149b30d6..968775f7be0e 100644 --- a/plugin/trino-raptor-legacy/pom.xml +++ b/plugin/trino-raptor-legacy/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-redis/pom.xml b/plugin/trino-redis/pom.xml index a900c0c8af15..5f04db64793e 100644 --- a/plugin/trino-redis/pom.xml +++ b/plugin/trino-redis/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-redshift/pom.xml b/plugin/trino-redshift/pom.xml index 4be89db9f3cc..7f7451539c78 100644 --- a/plugin/trino-redshift/pom.xml +++ b/plugin/trino-redshift/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-resource-group-managers/pom.xml b/plugin/trino-resource-group-managers/pom.xml index efcc153b9bb1..88cece21a4f0 100644 --- a/plugin/trino-resource-group-managers/pom.xml +++ b/plugin/trino-resource-group-managers/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-session-property-managers/pom.xml b/plugin/trino-session-property-managers/pom.xml index 44e09e5249ea..6c6bfc6f478b 100644 --- a/plugin/trino-session-property-managers/pom.xml +++ b/plugin/trino-session-property-managers/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-singlestore/pom.xml b/plugin/trino-singlestore/pom.xml index 53dc6652ef31..77f6203e5988 100644 --- a/plugin/trino-singlestore/pom.xml +++ b/plugin/trino-singlestore/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-sqlserver/pom.xml b/plugin/trino-sqlserver/pom.xml index 3bb828710c42..3021169fdbb8 100644 --- a/plugin/trino-sqlserver/pom.xml +++ b/plugin/trino-sqlserver/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-teradata-functions/pom.xml b/plugin/trino-teradata-functions/pom.xml index e8a0c7c342c1..9f132bef8ceb 100644 --- a/plugin/trino-teradata-functions/pom.xml +++ b/plugin/trino-teradata-functions/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-thrift-api/pom.xml b/plugin/trino-thrift-api/pom.xml index bfb08482f0e5..930dcbf4ba9a 100644 --- a/plugin/trino-thrift-api/pom.xml +++ b/plugin/trino-thrift-api/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-thrift-testing-server/pom.xml b/plugin/trino-thrift-testing-server/pom.xml index a717b257eb95..0f53c6ddb0b8 100644 --- a/plugin/trino-thrift-testing-server/pom.xml +++ b/plugin/trino-thrift-testing-server/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-thrift/pom.xml b/plugin/trino-thrift/pom.xml index 5ad1af7bf99f..ccff005e906a 100644 --- a/plugin/trino-thrift/pom.xml +++ b/plugin/trino-thrift/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-tpcds/pom.xml b/plugin/trino-tpcds/pom.xml index f405cb30be41..56a206a2b10f 100644 --- a/plugin/trino-tpcds/pom.xml +++ b/plugin/trino-tpcds/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/plugin/trino-tpch/pom.xml b/plugin/trino-tpch/pom.xml index 4c832a619ab4..111dc264f450 100644 --- a/plugin/trino-tpch/pom.xml +++ b/plugin/trino-tpch/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/pom.xml b/pom.xml index 68254813bb34..fd9759fab287 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 pom ${project.artifactId} @@ -133,7 +133,7 @@ scm:git:git://github.com/trinodb/trino.git - HEAD + 432 https://github.com/trinodb/trino diff --git a/service/trino-proxy/pom.xml b/service/trino-proxy/pom.xml index d2c06aab42c6..57fbddd6ee88 100644 --- a/service/trino-proxy/pom.xml +++ b/service/trino-proxy/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/service/trino-verifier/pom.xml b/service/trino-verifier/pom.xml index 3cde6e3776b0..4047b8a0e063 100644 --- a/service/trino-verifier/pom.xml +++ b/service/trino-verifier/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/testing/trino-benchmark-queries/pom.xml b/testing/trino-benchmark-queries/pom.xml index fde1b9916d54..4266569befad 100644 --- a/testing/trino-benchmark-queries/pom.xml +++ b/testing/trino-benchmark-queries/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/testing/trino-benchmark/pom.xml b/testing/trino-benchmark/pom.xml index 54d8de3bd8e2..f98414dc73da 100644 --- a/testing/trino-benchmark/pom.xml +++ b/testing/trino-benchmark/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/testing/trino-benchto-benchmarks/pom.xml b/testing/trino-benchto-benchmarks/pom.xml index b3d6f3d1a6ac..c536f8c1feb4 100644 --- a/testing/trino-benchto-benchmarks/pom.xml +++ b/testing/trino-benchto-benchmarks/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/testing/trino-faulttolerant-tests/pom.xml b/testing/trino-faulttolerant-tests/pom.xml index f1d2141e5950..99eac979dcfc 100644 --- a/testing/trino-faulttolerant-tests/pom.xml +++ b/testing/trino-faulttolerant-tests/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/testing/trino-plugin-reader/pom.xml b/testing/trino-plugin-reader/pom.xml index 9d67e26fb20e..6ab21c782d00 100644 --- a/testing/trino-plugin-reader/pom.xml +++ b/testing/trino-plugin-reader/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/testing/trino-product-tests-launcher/pom.xml b/testing/trino-product-tests-launcher/pom.xml index 7190280da708..4375b28e3f0e 100644 --- a/testing/trino-product-tests-launcher/pom.xml +++ b/testing/trino-product-tests-launcher/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/testing/trino-product-tests/pom.xml b/testing/trino-product-tests/pom.xml index 1647790cf9df..03a6c4c190ba 100644 --- a/testing/trino-product-tests/pom.xml +++ b/testing/trino-product-tests/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/testing/trino-server-dev/pom.xml b/testing/trino-server-dev/pom.xml index d9f4a154ea14..1eb6278c9297 100644 --- a/testing/trino-server-dev/pom.xml +++ b/testing/trino-server-dev/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/testing/trino-test-jdbc-compatibility-old-driver/pom.xml b/testing/trino-test-jdbc-compatibility-old-driver/pom.xml index 8a6e310f8d79..4eae4ed31673 100644 --- a/testing/trino-test-jdbc-compatibility-old-driver/pom.xml +++ b/testing/trino-test-jdbc-compatibility-old-driver/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml @@ -15,7 +15,7 @@ ${project.parent.basedir} - 432-SNAPSHOT + 432 diff --git a/testing/trino-test-jdbc-compatibility-old-server/pom.xml b/testing/trino-test-jdbc-compatibility-old-server/pom.xml index 4e64517e65d0..a2932f9ebd32 100644 --- a/testing/trino-test-jdbc-compatibility-old-server/pom.xml +++ b/testing/trino-test-jdbc-compatibility-old-server/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/testing/trino-testing-containers/pom.xml b/testing/trino-testing-containers/pom.xml index 776141bbce2f..d1c7ce4f520f 100644 --- a/testing/trino-testing-containers/pom.xml +++ b/testing/trino-testing-containers/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/testing/trino-testing-kafka/pom.xml b/testing/trino-testing-kafka/pom.xml index 3071ae69fa24..3aa98f5a78b0 100644 --- a/testing/trino-testing-kafka/pom.xml +++ b/testing/trino-testing-kafka/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/testing/trino-testing-resources/pom.xml b/testing/trino-testing-resources/pom.xml index 802b4cd540a6..34391fad13c9 100644 --- a/testing/trino-testing-resources/pom.xml +++ b/testing/trino-testing-resources/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/testing/trino-testing-services/pom.xml b/testing/trino-testing-services/pom.xml index 998ed95cb34a..1356c22407f8 100644 --- a/testing/trino-testing-services/pom.xml +++ b/testing/trino-testing-services/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/testing/trino-testing/pom.xml b/testing/trino-testing/pom.xml index 32303eb4394f..8a31e55d34fc 100644 --- a/testing/trino-testing/pom.xml +++ b/testing/trino-testing/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml diff --git a/testing/trino-tests/pom.xml b/testing/trino-tests/pom.xml index ced92c35ddcb..27c66c733703 100644 --- a/testing/trino-tests/pom.xml +++ b/testing/trino-tests/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432-SNAPSHOT + 432 ../../pom.xml From a66b269b80459dddd4ccd8460c6ef46ae68280f8 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Fri, 3 Nov 2023 00:45:04 +0000 Subject: [PATCH 042/587] [maven-release-plugin] prepare for next development iteration --- client/trino-cli/pom.xml | 2 +- client/trino-client/pom.xml | 2 +- client/trino-jdbc/pom.xml | 2 +- core/trino-grammar/pom.xml | 2 +- core/trino-main/pom.xml | 2 +- core/trino-parser/pom.xml | 2 +- core/trino-server-main/pom.xml | 2 +- core/trino-server-rpm/pom.xml | 2 +- core/trino-server/pom.xml | 2 +- core/trino-spi/pom.xml | 2 +- docs/pom.xml | 2 +- lib/trino-array/pom.xml | 2 +- lib/trino-cache/pom.xml | 2 +- lib/trino-filesystem-azure/pom.xml | 2 +- lib/trino-filesystem-manager/pom.xml | 2 +- lib/trino-filesystem-s3/pom.xml | 2 +- lib/trino-filesystem/pom.xml | 2 +- lib/trino-geospatial-toolkit/pom.xml | 2 +- lib/trino-hdfs/pom.xml | 2 +- lib/trino-hive-formats/pom.xml | 2 +- lib/trino-ignite-patched/pom.xml | 2 +- lib/trino-matching/pom.xml | 2 +- lib/trino-memory-context/pom.xml | 2 +- lib/trino-orc/pom.xml | 2 +- lib/trino-parquet/pom.xml | 2 +- lib/trino-phoenix5-patched/pom.xml | 2 +- lib/trino-plugin-toolkit/pom.xml | 2 +- lib/trino-record-decoder/pom.xml | 2 +- plugin/trino-accumulo-iterators/pom.xml | 2 +- plugin/trino-accumulo/pom.xml | 2 +- plugin/trino-atop/pom.xml | 2 +- plugin/trino-base-jdbc/pom.xml | 2 +- plugin/trino-bigquery/pom.xml | 2 +- plugin/trino-blackhole/pom.xml | 2 +- plugin/trino-cassandra/pom.xml | 2 +- plugin/trino-clickhouse/pom.xml | 2 +- plugin/trino-delta-lake/pom.xml | 2 +- plugin/trino-druid/pom.xml | 2 +- plugin/trino-elasticsearch/pom.xml | 2 +- plugin/trino-example-http/pom.xml | 2 +- plugin/trino-example-jdbc/pom.xml | 2 +- plugin/trino-exchange-filesystem/pom.xml | 2 +- plugin/trino-exchange-hdfs/pom.xml | 2 +- plugin/trino-geospatial/pom.xml | 2 +- plugin/trino-google-sheets/pom.xml | 2 +- plugin/trino-hive-hadoop2/pom.xml | 2 +- plugin/trino-hive/pom.xml | 2 +- plugin/trino-http-event-listener/pom.xml | 2 +- plugin/trino-hudi/pom.xml | 2 +- plugin/trino-iceberg/pom.xml | 2 +- plugin/trino-ignite/pom.xml | 2 +- plugin/trino-jmx/pom.xml | 2 +- plugin/trino-kafka/pom.xml | 2 +- plugin/trino-kinesis/pom.xml | 2 +- plugin/trino-kudu/pom.xml | 2 +- plugin/trino-local-file/pom.xml | 2 +- plugin/trino-mariadb/pom.xml | 2 +- plugin/trino-memory/pom.xml | 2 +- plugin/trino-ml/pom.xml | 2 +- plugin/trino-mongodb/pom.xml | 2 +- plugin/trino-mysql-event-listener/pom.xml | 2 +- plugin/trino-mysql/pom.xml | 2 +- plugin/trino-oracle/pom.xml | 2 +- plugin/trino-password-authenticators/pom.xml | 2 +- plugin/trino-phoenix5/pom.xml | 2 +- plugin/trino-pinot/pom.xml | 2 +- plugin/trino-postgresql/pom.xml | 2 +- plugin/trino-prometheus/pom.xml | 2 +- plugin/trino-raptor-legacy/pom.xml | 2 +- plugin/trino-redis/pom.xml | 2 +- plugin/trino-redshift/pom.xml | 2 +- plugin/trino-resource-group-managers/pom.xml | 2 +- plugin/trino-session-property-managers/pom.xml | 2 +- plugin/trino-singlestore/pom.xml | 2 +- plugin/trino-sqlserver/pom.xml | 2 +- plugin/trino-teradata-functions/pom.xml | 2 +- plugin/trino-thrift-api/pom.xml | 2 +- plugin/trino-thrift-testing-server/pom.xml | 2 +- plugin/trino-thrift/pom.xml | 2 +- plugin/trino-tpcds/pom.xml | 2 +- plugin/trino-tpch/pom.xml | 2 +- pom.xml | 4 ++-- service/trino-proxy/pom.xml | 2 +- service/trino-verifier/pom.xml | 2 +- testing/trino-benchmark-queries/pom.xml | 2 +- testing/trino-benchmark/pom.xml | 2 +- testing/trino-benchto-benchmarks/pom.xml | 2 +- testing/trino-faulttolerant-tests/pom.xml | 2 +- testing/trino-plugin-reader/pom.xml | 2 +- testing/trino-product-tests-launcher/pom.xml | 2 +- testing/trino-product-tests/pom.xml | 2 +- testing/trino-server-dev/pom.xml | 2 +- testing/trino-test-jdbc-compatibility-old-driver/pom.xml | 4 ++-- testing/trino-test-jdbc-compatibility-old-server/pom.xml | 2 +- testing/trino-testing-containers/pom.xml | 2 +- testing/trino-testing-kafka/pom.xml | 2 +- testing/trino-testing-resources/pom.xml | 2 +- testing/trino-testing-services/pom.xml | 2 +- testing/trino-testing/pom.xml | 2 +- testing/trino-tests/pom.xml | 2 +- 100 files changed, 102 insertions(+), 102 deletions(-) diff --git a/client/trino-cli/pom.xml b/client/trino-cli/pom.xml index ee828b643276..69554e5cc213 100644 --- a/client/trino-cli/pom.xml +++ b/client/trino-cli/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/client/trino-client/pom.xml b/client/trino-client/pom.xml index a7aff6d6bbcc..00b50a1bc5f1 100644 --- a/client/trino-client/pom.xml +++ b/client/trino-client/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/client/trino-jdbc/pom.xml b/client/trino-jdbc/pom.xml index e5a54ad7b107..dce82d2b188d 100644 --- a/client/trino-jdbc/pom.xml +++ b/client/trino-jdbc/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/core/trino-grammar/pom.xml b/core/trino-grammar/pom.xml index 131d084d222f..fcd77174c41c 100644 --- a/core/trino-grammar/pom.xml +++ b/core/trino-grammar/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/core/trino-main/pom.xml b/core/trino-main/pom.xml index 45c20f04f93e..118e99492720 100644 --- a/core/trino-main/pom.xml +++ b/core/trino-main/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/core/trino-parser/pom.xml b/core/trino-parser/pom.xml index 28da47e97cb3..6ffaf3731c4f 100644 --- a/core/trino-parser/pom.xml +++ b/core/trino-parser/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/core/trino-server-main/pom.xml b/core/trino-server-main/pom.xml index 5fd90192edad..2e252f3935b2 100644 --- a/core/trino-server-main/pom.xml +++ b/core/trino-server-main/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/core/trino-server-rpm/pom.xml b/core/trino-server-rpm/pom.xml index e5158cafe302..9b45ced3a455 100644 --- a/core/trino-server-rpm/pom.xml +++ b/core/trino-server-rpm/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/core/trino-server/pom.xml b/core/trino-server/pom.xml index 0982745c4d2a..07e339724f10 100644 --- a/core/trino-server/pom.xml +++ b/core/trino-server/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/core/trino-spi/pom.xml b/core/trino-spi/pom.xml index 1bd040a81c8d..4517f5365f89 100644 --- a/core/trino-spi/pom.xml +++ b/core/trino-spi/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/docs/pom.xml b/docs/pom.xml index 5588b4dbba53..2788bb53831e 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT trino-docs diff --git a/lib/trino-array/pom.xml b/lib/trino-array/pom.xml index 582f31cd67a5..8b0b30ad28d2 100644 --- a/lib/trino-array/pom.xml +++ b/lib/trino-array/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/lib/trino-cache/pom.xml b/lib/trino-cache/pom.xml index 7fc6eabf5325..d4327b002f4c 100644 --- a/lib/trino-cache/pom.xml +++ b/lib/trino-cache/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/lib/trino-filesystem-azure/pom.xml b/lib/trino-filesystem-azure/pom.xml index f6041e5dbcef..ddac08381a87 100644 --- a/lib/trino-filesystem-azure/pom.xml +++ b/lib/trino-filesystem-azure/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/lib/trino-filesystem-manager/pom.xml b/lib/trino-filesystem-manager/pom.xml index b86472e4d53f..f81a732992f9 100644 --- a/lib/trino-filesystem-manager/pom.xml +++ b/lib/trino-filesystem-manager/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/lib/trino-filesystem-s3/pom.xml b/lib/trino-filesystem-s3/pom.xml index 215adad62dd6..6f74fb34a30b 100644 --- a/lib/trino-filesystem-s3/pom.xml +++ b/lib/trino-filesystem-s3/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/lib/trino-filesystem/pom.xml b/lib/trino-filesystem/pom.xml index a6f8a1b40ffb..c60b97e48afc 100644 --- a/lib/trino-filesystem/pom.xml +++ b/lib/trino-filesystem/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/lib/trino-geospatial-toolkit/pom.xml b/lib/trino-geospatial-toolkit/pom.xml index 097d4627a8ea..b1b81002e615 100644 --- a/lib/trino-geospatial-toolkit/pom.xml +++ b/lib/trino-geospatial-toolkit/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/lib/trino-hdfs/pom.xml b/lib/trino-hdfs/pom.xml index 518d7fb485d9..c48da1c1e605 100644 --- a/lib/trino-hdfs/pom.xml +++ b/lib/trino-hdfs/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/lib/trino-hive-formats/pom.xml b/lib/trino-hive-formats/pom.xml index ba21a9dfbef3..0802ef69061c 100644 --- a/lib/trino-hive-formats/pom.xml +++ b/lib/trino-hive-formats/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/lib/trino-ignite-patched/pom.xml b/lib/trino-ignite-patched/pom.xml index 62b484947da7..ca10ab8a9824 100644 --- a/lib/trino-ignite-patched/pom.xml +++ b/lib/trino-ignite-patched/pom.xml @@ -6,7 +6,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/lib/trino-matching/pom.xml b/lib/trino-matching/pom.xml index dacd71a8b4e7..0453adeea674 100644 --- a/lib/trino-matching/pom.xml +++ b/lib/trino-matching/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/lib/trino-memory-context/pom.xml b/lib/trino-memory-context/pom.xml index 7fde8c7cf0dd..8feea57c536d 100644 --- a/lib/trino-memory-context/pom.xml +++ b/lib/trino-memory-context/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/lib/trino-orc/pom.xml b/lib/trino-orc/pom.xml index 6f07698cef3d..85c9545ae6f4 100644 --- a/lib/trino-orc/pom.xml +++ b/lib/trino-orc/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/lib/trino-parquet/pom.xml b/lib/trino-parquet/pom.xml index 4d6b98ca0d28..9a4e103d8a55 100644 --- a/lib/trino-parquet/pom.xml +++ b/lib/trino-parquet/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/lib/trino-phoenix5-patched/pom.xml b/lib/trino-phoenix5-patched/pom.xml index 1fafbf8137bf..81ae850c914f 100644 --- a/lib/trino-phoenix5-patched/pom.xml +++ b/lib/trino-phoenix5-patched/pom.xml @@ -6,7 +6,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/lib/trino-plugin-toolkit/pom.xml b/lib/trino-plugin-toolkit/pom.xml index d2985fa0bdc7..9fa935df3e93 100644 --- a/lib/trino-plugin-toolkit/pom.xml +++ b/lib/trino-plugin-toolkit/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/lib/trino-record-decoder/pom.xml b/lib/trino-record-decoder/pom.xml index 86c811d6537a..6f11ff54873c 100644 --- a/lib/trino-record-decoder/pom.xml +++ b/lib/trino-record-decoder/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-accumulo-iterators/pom.xml b/plugin/trino-accumulo-iterators/pom.xml index 53fda3ef1d52..e18c70de37ba 100644 --- a/plugin/trino-accumulo-iterators/pom.xml +++ b/plugin/trino-accumulo-iterators/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-accumulo/pom.xml b/plugin/trino-accumulo/pom.xml index 603d756276ce..b848256d4a1f 100644 --- a/plugin/trino-accumulo/pom.xml +++ b/plugin/trino-accumulo/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-atop/pom.xml b/plugin/trino-atop/pom.xml index 62131af8de32..88ff850b3b31 100644 --- a/plugin/trino-atop/pom.xml +++ b/plugin/trino-atop/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-base-jdbc/pom.xml b/plugin/trino-base-jdbc/pom.xml index f7883eceb368..832c92425220 100644 --- a/plugin/trino-base-jdbc/pom.xml +++ b/plugin/trino-base-jdbc/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-bigquery/pom.xml b/plugin/trino-bigquery/pom.xml index 22a1e707cf56..e95632cb6a7c 100644 --- a/plugin/trino-bigquery/pom.xml +++ b/plugin/trino-bigquery/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-blackhole/pom.xml b/plugin/trino-blackhole/pom.xml index 72265465fbf4..1d3dda8f122e 100644 --- a/plugin/trino-blackhole/pom.xml +++ b/plugin/trino-blackhole/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-cassandra/pom.xml b/plugin/trino-cassandra/pom.xml index 9ce8d3b03fc4..21a33521d696 100644 --- a/plugin/trino-cassandra/pom.xml +++ b/plugin/trino-cassandra/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-clickhouse/pom.xml b/plugin/trino-clickhouse/pom.xml index 081b1899e416..08d744865162 100644 --- a/plugin/trino-clickhouse/pom.xml +++ b/plugin/trino-clickhouse/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-delta-lake/pom.xml b/plugin/trino-delta-lake/pom.xml index 758dc5fa2e5a..eea245912d0e 100644 --- a/plugin/trino-delta-lake/pom.xml +++ b/plugin/trino-delta-lake/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-druid/pom.xml b/plugin/trino-druid/pom.xml index 43bf8053362d..14f84bedd900 100644 --- a/plugin/trino-druid/pom.xml +++ b/plugin/trino-druid/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-elasticsearch/pom.xml b/plugin/trino-elasticsearch/pom.xml index 24ec7ff2d384..38fd811c6258 100644 --- a/plugin/trino-elasticsearch/pom.xml +++ b/plugin/trino-elasticsearch/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-example-http/pom.xml b/plugin/trino-example-http/pom.xml index 188ae41c4647..56b9836d60e5 100644 --- a/plugin/trino-example-http/pom.xml +++ b/plugin/trino-example-http/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-example-jdbc/pom.xml b/plugin/trino-example-jdbc/pom.xml index 3256b18a2478..5cf8bc485d10 100644 --- a/plugin/trino-example-jdbc/pom.xml +++ b/plugin/trino-example-jdbc/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-exchange-filesystem/pom.xml b/plugin/trino-exchange-filesystem/pom.xml index 2b58052c57e7..f6f4edd20507 100644 --- a/plugin/trino-exchange-filesystem/pom.xml +++ b/plugin/trino-exchange-filesystem/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-exchange-hdfs/pom.xml b/plugin/trino-exchange-hdfs/pom.xml index 41d64dc6af84..a45e79036b98 100644 --- a/plugin/trino-exchange-hdfs/pom.xml +++ b/plugin/trino-exchange-hdfs/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-geospatial/pom.xml b/plugin/trino-geospatial/pom.xml index c8dfc732b111..d81ebcf5b342 100644 --- a/plugin/trino-geospatial/pom.xml +++ b/plugin/trino-geospatial/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-google-sheets/pom.xml b/plugin/trino-google-sheets/pom.xml index c16187180e28..55dfb239acad 100644 --- a/plugin/trino-google-sheets/pom.xml +++ b/plugin/trino-google-sheets/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-hive-hadoop2/pom.xml b/plugin/trino-hive-hadoop2/pom.xml index df400f052638..3e0b96d718de 100644 --- a/plugin/trino-hive-hadoop2/pom.xml +++ b/plugin/trino-hive-hadoop2/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-hive/pom.xml b/plugin/trino-hive/pom.xml index bfffd0ec408f..c849555cef00 100644 --- a/plugin/trino-hive/pom.xml +++ b/plugin/trino-hive/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-http-event-listener/pom.xml b/plugin/trino-http-event-listener/pom.xml index 8393a3950fb6..38b4e665b9ed 100644 --- a/plugin/trino-http-event-listener/pom.xml +++ b/plugin/trino-http-event-listener/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-hudi/pom.xml b/plugin/trino-hudi/pom.xml index 502b8c94fa1a..61a2d3be622e 100644 --- a/plugin/trino-hudi/pom.xml +++ b/plugin/trino-hudi/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-iceberg/pom.xml b/plugin/trino-iceberg/pom.xml index d899a7185746..a8a9450ceefd 100644 --- a/plugin/trino-iceberg/pom.xml +++ b/plugin/trino-iceberg/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-ignite/pom.xml b/plugin/trino-ignite/pom.xml index 100b813f5276..1701669b88f4 100644 --- a/plugin/trino-ignite/pom.xml +++ b/plugin/trino-ignite/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-jmx/pom.xml b/plugin/trino-jmx/pom.xml index 9ee82bbf920e..31475ece5b33 100644 --- a/plugin/trino-jmx/pom.xml +++ b/plugin/trino-jmx/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-kafka/pom.xml b/plugin/trino-kafka/pom.xml index dcde170ed710..efb178a4321c 100644 --- a/plugin/trino-kafka/pom.xml +++ b/plugin/trino-kafka/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-kinesis/pom.xml b/plugin/trino-kinesis/pom.xml index 854180321aae..462f1239dba5 100644 --- a/plugin/trino-kinesis/pom.xml +++ b/plugin/trino-kinesis/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-kudu/pom.xml b/plugin/trino-kudu/pom.xml index 3274b1aea220..37ef5290a99b 100644 --- a/plugin/trino-kudu/pom.xml +++ b/plugin/trino-kudu/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-local-file/pom.xml b/plugin/trino-local-file/pom.xml index 2558a4a80712..7051d34ecc0f 100644 --- a/plugin/trino-local-file/pom.xml +++ b/plugin/trino-local-file/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-mariadb/pom.xml b/plugin/trino-mariadb/pom.xml index a4e0ddbdcffe..b09fe905d208 100644 --- a/plugin/trino-mariadb/pom.xml +++ b/plugin/trino-mariadb/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-memory/pom.xml b/plugin/trino-memory/pom.xml index c34e9d54e4d4..7d79f447fc3c 100644 --- a/plugin/trino-memory/pom.xml +++ b/plugin/trino-memory/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-ml/pom.xml b/plugin/trino-ml/pom.xml index 9f4d038d47a6..2ca7d210f0b7 100644 --- a/plugin/trino-ml/pom.xml +++ b/plugin/trino-ml/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-mongodb/pom.xml b/plugin/trino-mongodb/pom.xml index 425c39d14d6d..85f3997fb054 100644 --- a/plugin/trino-mongodb/pom.xml +++ b/plugin/trino-mongodb/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-mysql-event-listener/pom.xml b/plugin/trino-mysql-event-listener/pom.xml index 9d75a21a16da..f45e0901990c 100644 --- a/plugin/trino-mysql-event-listener/pom.xml +++ b/plugin/trino-mysql-event-listener/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-mysql/pom.xml b/plugin/trino-mysql/pom.xml index 023cefc192be..452dd5a1d1b5 100644 --- a/plugin/trino-mysql/pom.xml +++ b/plugin/trino-mysql/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-oracle/pom.xml b/plugin/trino-oracle/pom.xml index dd41f1b05ce1..596978b79f1d 100644 --- a/plugin/trino-oracle/pom.xml +++ b/plugin/trino-oracle/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-password-authenticators/pom.xml b/plugin/trino-password-authenticators/pom.xml index b69b2fa26e94..12e19498db40 100644 --- a/plugin/trino-password-authenticators/pom.xml +++ b/plugin/trino-password-authenticators/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-phoenix5/pom.xml b/plugin/trino-phoenix5/pom.xml index 0b27820ca68d..62485f040d06 100644 --- a/plugin/trino-phoenix5/pom.xml +++ b/plugin/trino-phoenix5/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-pinot/pom.xml b/plugin/trino-pinot/pom.xml index 37c66d8c4d9a..5a2ab791de05 100755 --- a/plugin/trino-pinot/pom.xml +++ b/plugin/trino-pinot/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-postgresql/pom.xml b/plugin/trino-postgresql/pom.xml index 2e95ebf27dbd..33a016c36495 100644 --- a/plugin/trino-postgresql/pom.xml +++ b/plugin/trino-postgresql/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-prometheus/pom.xml b/plugin/trino-prometheus/pom.xml index 2d2a5911013b..c25370754a5f 100644 --- a/plugin/trino-prometheus/pom.xml +++ b/plugin/trino-prometheus/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-raptor-legacy/pom.xml b/plugin/trino-raptor-legacy/pom.xml index 968775f7be0e..87fdcc4b1b1b 100644 --- a/plugin/trino-raptor-legacy/pom.xml +++ b/plugin/trino-raptor-legacy/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-redis/pom.xml b/plugin/trino-redis/pom.xml index 5f04db64793e..cfd917ee7dd2 100644 --- a/plugin/trino-redis/pom.xml +++ b/plugin/trino-redis/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-redshift/pom.xml b/plugin/trino-redshift/pom.xml index 7f7451539c78..96267ed27f72 100644 --- a/plugin/trino-redshift/pom.xml +++ b/plugin/trino-redshift/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-resource-group-managers/pom.xml b/plugin/trino-resource-group-managers/pom.xml index 88cece21a4f0..4edc41a0f494 100644 --- a/plugin/trino-resource-group-managers/pom.xml +++ b/plugin/trino-resource-group-managers/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-session-property-managers/pom.xml b/plugin/trino-session-property-managers/pom.xml index 6c6bfc6f478b..e5f64c657332 100644 --- a/plugin/trino-session-property-managers/pom.xml +++ b/plugin/trino-session-property-managers/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-singlestore/pom.xml b/plugin/trino-singlestore/pom.xml index 77f6203e5988..d1f9febfae72 100644 --- a/plugin/trino-singlestore/pom.xml +++ b/plugin/trino-singlestore/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-sqlserver/pom.xml b/plugin/trino-sqlserver/pom.xml index 3021169fdbb8..ed0e7f2f0ed9 100644 --- a/plugin/trino-sqlserver/pom.xml +++ b/plugin/trino-sqlserver/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-teradata-functions/pom.xml b/plugin/trino-teradata-functions/pom.xml index 9f132bef8ceb..6485c1b71e24 100644 --- a/plugin/trino-teradata-functions/pom.xml +++ b/plugin/trino-teradata-functions/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-thrift-api/pom.xml b/plugin/trino-thrift-api/pom.xml index 930dcbf4ba9a..0a2ab27c502a 100644 --- a/plugin/trino-thrift-api/pom.xml +++ b/plugin/trino-thrift-api/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-thrift-testing-server/pom.xml b/plugin/trino-thrift-testing-server/pom.xml index 0f53c6ddb0b8..59d37c2b90ab 100644 --- a/plugin/trino-thrift-testing-server/pom.xml +++ b/plugin/trino-thrift-testing-server/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-thrift/pom.xml b/plugin/trino-thrift/pom.xml index ccff005e906a..5d9d66d84c21 100644 --- a/plugin/trino-thrift/pom.xml +++ b/plugin/trino-thrift/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-tpcds/pom.xml b/plugin/trino-tpcds/pom.xml index 56a206a2b10f..089219ed9c5a 100644 --- a/plugin/trino-tpcds/pom.xml +++ b/plugin/trino-tpcds/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-tpch/pom.xml b/plugin/trino-tpch/pom.xml index 111dc264f450..ea9309552c38 100644 --- a/plugin/trino-tpch/pom.xml +++ b/plugin/trino-tpch/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/pom.xml b/pom.xml index fd9759fab287..d238d6781193 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT pom ${project.artifactId} @@ -133,7 +133,7 @@ scm:git:git://github.com/trinodb/trino.git - 432 + HEAD https://github.com/trinodb/trino diff --git a/service/trino-proxy/pom.xml b/service/trino-proxy/pom.xml index 57fbddd6ee88..e28d14e54d45 100644 --- a/service/trino-proxy/pom.xml +++ b/service/trino-proxy/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/service/trino-verifier/pom.xml b/service/trino-verifier/pom.xml index 4047b8a0e063..e0e05ff74dbe 100644 --- a/service/trino-verifier/pom.xml +++ b/service/trino-verifier/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/testing/trino-benchmark-queries/pom.xml b/testing/trino-benchmark-queries/pom.xml index 4266569befad..72b53b86065a 100644 --- a/testing/trino-benchmark-queries/pom.xml +++ b/testing/trino-benchmark-queries/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/testing/trino-benchmark/pom.xml b/testing/trino-benchmark/pom.xml index f98414dc73da..7430833a0bc3 100644 --- a/testing/trino-benchmark/pom.xml +++ b/testing/trino-benchmark/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/testing/trino-benchto-benchmarks/pom.xml b/testing/trino-benchto-benchmarks/pom.xml index c536f8c1feb4..632c3215d112 100644 --- a/testing/trino-benchto-benchmarks/pom.xml +++ b/testing/trino-benchto-benchmarks/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/testing/trino-faulttolerant-tests/pom.xml b/testing/trino-faulttolerant-tests/pom.xml index 99eac979dcfc..d14f4be4174c 100644 --- a/testing/trino-faulttolerant-tests/pom.xml +++ b/testing/trino-faulttolerant-tests/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/testing/trino-plugin-reader/pom.xml b/testing/trino-plugin-reader/pom.xml index 6ab21c782d00..9e44e61e0d5a 100644 --- a/testing/trino-plugin-reader/pom.xml +++ b/testing/trino-plugin-reader/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/testing/trino-product-tests-launcher/pom.xml b/testing/trino-product-tests-launcher/pom.xml index 4375b28e3f0e..55bb40ff257d 100644 --- a/testing/trino-product-tests-launcher/pom.xml +++ b/testing/trino-product-tests-launcher/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/testing/trino-product-tests/pom.xml b/testing/trino-product-tests/pom.xml index 03a6c4c190ba..13ad231c34d4 100644 --- a/testing/trino-product-tests/pom.xml +++ b/testing/trino-product-tests/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/testing/trino-server-dev/pom.xml b/testing/trino-server-dev/pom.xml index 1eb6278c9297..aaa637d3a7ff 100644 --- a/testing/trino-server-dev/pom.xml +++ b/testing/trino-server-dev/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/testing/trino-test-jdbc-compatibility-old-driver/pom.xml b/testing/trino-test-jdbc-compatibility-old-driver/pom.xml index 4eae4ed31673..6f30e13c4a08 100644 --- a/testing/trino-test-jdbc-compatibility-old-driver/pom.xml +++ b/testing/trino-test-jdbc-compatibility-old-driver/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml @@ -15,7 +15,7 @@ ${project.parent.basedir} - 432 + 433-SNAPSHOT diff --git a/testing/trino-test-jdbc-compatibility-old-server/pom.xml b/testing/trino-test-jdbc-compatibility-old-server/pom.xml index a2932f9ebd32..cd717fb5f929 100644 --- a/testing/trino-test-jdbc-compatibility-old-server/pom.xml +++ b/testing/trino-test-jdbc-compatibility-old-server/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/testing/trino-testing-containers/pom.xml b/testing/trino-testing-containers/pom.xml index d1c7ce4f520f..9a5b4421ecbf 100644 --- a/testing/trino-testing-containers/pom.xml +++ b/testing/trino-testing-containers/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/testing/trino-testing-kafka/pom.xml b/testing/trino-testing-kafka/pom.xml index 3aa98f5a78b0..4be42376bb63 100644 --- a/testing/trino-testing-kafka/pom.xml +++ b/testing/trino-testing-kafka/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/testing/trino-testing-resources/pom.xml b/testing/trino-testing-resources/pom.xml index 34391fad13c9..30c4746e114f 100644 --- a/testing/trino-testing-resources/pom.xml +++ b/testing/trino-testing-resources/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/testing/trino-testing-services/pom.xml b/testing/trino-testing-services/pom.xml index 1356c22407f8..71800e189a96 100644 --- a/testing/trino-testing-services/pom.xml +++ b/testing/trino-testing-services/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/testing/trino-testing/pom.xml b/testing/trino-testing/pom.xml index 8a31e55d34fc..f69daed2c3c1 100644 --- a/testing/trino-testing/pom.xml +++ b/testing/trino-testing/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml diff --git a/testing/trino-tests/pom.xml b/testing/trino-tests/pom.xml index 27c66c733703..a52d87b41814 100644 --- a/testing/trino-tests/pom.xml +++ b/testing/trino-tests/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 432 + 433-SNAPSHOT ../../pom.xml From d3575d2f693959899b26b1de60ab68a29185285d Mon Sep 17 00:00:00 2001 From: Marius Grama Date: Mon, 23 Oct 2023 14:29:09 +0200 Subject: [PATCH 043/587] Add boolean to varchar coercion for hive --- .../plugin/hive/coercions/BooleanCoercer.java | 55 ++++++++++++++++ .../plugin/hive/coercions/CoercionUtils.java | 5 ++ .../plugin/hive/orc/OrcTypeTranslator.java | 5 ++ .../plugin/hive/util/HiveCoercionPolicy.java | 4 +- .../hive/coercions/TestBooleanCoercer.java | 63 +++++++++++++++++++ .../product/hive/BaseTestHiveCoercion.java | 9 +++ .../TestHiveCoercionOnPartitionedTable.java | 1 + .../TestHiveCoercionOnUnpartitionedTable.java | 1 + 8 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/BooleanCoercer.java create mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestBooleanCoercer.java diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/BooleanCoercer.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/BooleanCoercer.java new file mode 100644 index 000000000000..91a622ff014f --- /dev/null +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/BooleanCoercer.java @@ -0,0 +1,55 @@ +/* + * 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 io.trino.plugin.hive.coercions; + +import io.airlift.slice.Slice; +import io.trino.spi.TrinoException; +import io.trino.spi.block.Block; +import io.trino.spi.block.BlockBuilder; +import io.trino.spi.type.BooleanType; +import io.trino.spi.type.VarcharType; + +import static io.airlift.slice.SliceUtf8.countCodePoints; +import static io.airlift.slice.Slices.utf8Slice; +import static io.trino.spi.StandardErrorCode.INVALID_ARGUMENTS; +import static io.trino.spi.type.BooleanType.BOOLEAN; +import static java.lang.String.format; + +public final class BooleanCoercer +{ + private static final Slice TRUE = utf8Slice("TRUE"); + private static final Slice FALSE = utf8Slice("FALSE"); + + private BooleanCoercer() {} + + public static class BooleanToVarcharCoercer + extends TypeCoercer + { + public BooleanToVarcharCoercer(VarcharType toType) + { + super(BOOLEAN, toType); + } + + @Override + protected void applyCoercedValue(BlockBuilder blockBuilder, Block block, int position) + { + boolean value = BOOLEAN.getBoolean(block, position); + Slice converted = value ? TRUE : FALSE; + if (!toType.isUnbounded() && countCodePoints(converted) > toType.getBoundedLength()) { + throw new TrinoException(INVALID_ARGUMENTS, format("Varchar representation of %s exceeds %s bounds", value, toType)); + } + toType.writeSlice(blockBuilder, converted); + } + } +} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/CoercionUtils.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/CoercionUtils.java index 9ea765a36048..c35938e76c67 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/CoercionUtils.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/CoercionUtils.java @@ -16,6 +16,7 @@ import com.google.common.collect.ImmutableList; import io.trino.plugin.hive.HiveTimestampPrecision; import io.trino.plugin.hive.HiveType; +import io.trino.plugin.hive.coercions.BooleanCoercer.BooleanToVarcharCoercer; import io.trino.plugin.hive.coercions.DateCoercer.VarcharToDateCoercer; import io.trino.plugin.hive.coercions.TimestampCoercer.VarcharToLongTimestampCoercer; import io.trino.plugin.hive.coercions.TimestampCoercer.VarcharToShortTimestampCoercer; @@ -35,6 +36,7 @@ import io.trino.spi.block.RunLengthEncodedBlock; import io.trino.spi.type.ArrayType; import io.trino.spi.type.BigintType; +import io.trino.spi.type.BooleanType; import io.trino.spi.type.CharType; import io.trino.spi.type.DateType; import io.trino.spi.type.DecimalType; @@ -116,6 +118,9 @@ public static Type createTypeFromCoercer(TypeManager typeManager, HiveType fromH if (fromType instanceof VarcharType fromVarcharType && toType instanceof DateType toDateType) { return Optional.of(new VarcharToDateCoercer(fromVarcharType, toDateType)); } + if (fromType instanceof BooleanType && toType instanceof VarcharType toVarcharType) { + return Optional.of(new BooleanToVarcharCoercer(toVarcharType)); + } if (fromType instanceof CharType fromCharType && toType instanceof CharType toCharType) { if (narrowerThan(toCharType, fromCharType)) { return Optional.of(new CharCoercer(fromCharType, toCharType)); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcTypeTranslator.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcTypeTranslator.java index 069913fcbba2..d9ef312e7878 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcTypeTranslator.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcTypeTranslator.java @@ -14,6 +14,7 @@ package io.trino.plugin.hive.orc; import io.trino.orc.metadata.OrcType.OrcTypeKind; +import io.trino.plugin.hive.coercions.BooleanCoercer.BooleanToVarcharCoercer; import io.trino.plugin.hive.coercions.DateCoercer.VarcharToDateCoercer; import io.trino.plugin.hive.coercions.DoubleToVarcharCoercer; import io.trino.plugin.hive.coercions.TimestampCoercer.LongTimestampToVarcharCoercer; @@ -27,6 +28,7 @@ import java.util.Optional; +import static io.trino.orc.metadata.OrcType.OrcTypeKind.BOOLEAN; import static io.trino.orc.metadata.OrcType.OrcTypeKind.DOUBLE; import static io.trino.orc.metadata.OrcType.OrcTypeKind.STRING; import static io.trino.orc.metadata.OrcType.OrcTypeKind.TIMESTAMP; @@ -58,6 +60,9 @@ private OrcTypeTranslator() {} if (fromOrcType == DOUBLE && toTrinoType instanceof VarcharType varcharType) { return Optional.of(new DoubleToVarcharCoercer(varcharType, true)); } + if (fromOrcType == BOOLEAN && toTrinoType instanceof VarcharType varcharType) { + return Optional.of(new BooleanToVarcharCoercer(varcharType)); + } return Optional.empty(); } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveCoercionPolicy.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveCoercionPolicy.java index a79e398fe4bd..56aecfc755a4 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveCoercionPolicy.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveCoercionPolicy.java @@ -28,6 +28,7 @@ import java.util.List; import static com.google.common.base.Preconditions.checkArgument; +import static io.trino.plugin.hive.HiveType.HIVE_BOOLEAN; import static io.trino.plugin.hive.HiveType.HIVE_BYTE; import static io.trino.plugin.hive.HiveType.HIVE_DATE; import static io.trino.plugin.hive.HiveType.HIVE_DOUBLE; @@ -72,7 +73,8 @@ private boolean canCoerce(HiveType fromHiveType, HiveType toHiveType, HiveTimest return toType instanceof CharType; } if (toType instanceof VarcharType) { - return fromHiveType.equals(HIVE_BYTE) || + return fromHiveType.equals(HIVE_BOOLEAN) || + fromHiveType.equals(HIVE_BYTE) || fromHiveType.equals(HIVE_SHORT) || fromHiveType.equals(HIVE_INT) || fromHiveType.equals(HIVE_LONG) || diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestBooleanCoercer.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestBooleanCoercer.java new file mode 100644 index 000000000000..c12abebba7ba --- /dev/null +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestBooleanCoercer.java @@ -0,0 +1,63 @@ +/* + * 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 io.trino.plugin.hive.coercions; + +import io.airlift.slice.Slice; +import io.trino.plugin.hive.coercions.CoercionUtils.CoercionContext; +import io.trino.spi.TrinoException; +import io.trino.spi.block.Block; +import io.trino.spi.type.Type; +import org.junit.jupiter.api.Test; + +import static io.airlift.slice.Slices.utf8Slice; +import static io.trino.plugin.hive.HiveTimestampPrecision.DEFAULT_PRECISION; +import static io.trino.plugin.hive.HiveType.toHiveType; +import static io.trino.plugin.hive.coercions.CoercionUtils.createCoercer; +import static io.trino.spi.predicate.Utils.blockToNativeValue; +import static io.trino.spi.predicate.Utils.nativeValueToBlock; +import static io.trino.spi.type.BooleanType.BOOLEAN; +import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; +import static io.trino.spi.type.VarcharType.createVarcharType; +import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class TestBooleanCoercer +{ + @Test + public void testBooleanToVarchar() + { + assertBooleanToVarcharCoercion(createUnboundedVarcharType(), true, utf8Slice("TRUE")); + assertBooleanToVarcharCoercion(createUnboundedVarcharType(), false, utf8Slice("FALSE")); + } + + @Test + public void testBooleanToLowerBoundedVarchar() + { + assertThatThrownBy(() -> assertBooleanToVarcharCoercion(createVarcharType(1), true, utf8Slice("T"))) + .isInstanceOf(TrinoException.class) + .hasMessageContaining("Varchar representation of true exceeds varchar(1) bounds"); + assertThatThrownBy(() -> assertBooleanToVarcharCoercion(createVarcharType(1), false, utf8Slice("F"))) + .isInstanceOf(TrinoException.class) + .hasMessageContaining("Varchar representation of false exceeds varchar(1) bounds"); + } + + private void assertBooleanToVarcharCoercion(Type toType, boolean valueToBeCoerced, Slice expectedValue) + { + Block coercedValue = createCoercer(TESTING_TYPE_MANAGER, toHiveType(BOOLEAN), toHiveType(toType), new CoercionContext(DEFAULT_PRECISION, false)).orElseThrow() + .apply(nativeValueToBlock(BOOLEAN, valueToBeCoerced)); + assertThat(blockToNativeValue(toType, coercedValue)) + .isEqualTo(expectedValue); + } +} diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/BaseTestHiveCoercion.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/BaseTestHiveCoercion.java index b8544dacbe3a..23968ff043bf 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/BaseTestHiveCoercion.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/BaseTestHiveCoercion.java @@ -99,6 +99,7 @@ protected void doTestHiveCoercion(HiveTableDefinition tableDefinition) "row_to_row", "list_to_list", "map_to_map", + "boolean_to_varchar", "tinyint_to_smallint", "tinyint_to_int", "tinyint_to_bigint", @@ -175,6 +176,7 @@ protected void insertTableRows(String tableName, String floatToDoubleType) " CAST(ROW ('as is', -1, 100, 2323, 12345, 2) AS ROW(keep VARCHAR, ti2si TINYINT, si2int SMALLINT, int2bi INTEGER, bi2vc BIGINT, lower2uppercase BIGINT)), " + " ARRAY [CAST(ROW (2, -101, 12345, 'removed') AS ROW (ti2int TINYINT, si2bi SMALLINT, bi2vc BIGINT, remove VARCHAR))], " + " MAP (ARRAY [TINYINT '2'], ARRAY [CAST(ROW (-3, 2323, REAL '0.5') AS ROW (ti2bi TINYINT, int2bi INTEGER, float2double %2$s))]), " + + " TRUE, " + " TINYINT '-1', " + " TINYINT '2', " + " TINYINT '-3', " + @@ -225,6 +227,7 @@ protected void insertTableRows(String tableName, String floatToDoubleType) " CAST(ROW (NULL, 1, -100, -2323, -12345, 2) AS ROW(keep VARCHAR, ti2si TINYINT, si2int SMALLINT, int2bi INTEGER, bi2vc BIGINT, lower2uppercase BIGINT)), " + " ARRAY [CAST(ROW (-2, 101, -12345, NULL) AS ROW (ti2int TINYINT, si2bi SMALLINT, bi2vc BIGINT, remove VARCHAR))], " + " MAP (ARRAY [TINYINT '-2'], ARRAY [CAST(ROW (null, -2323, REAL '-1.5') AS ROW (ti2bi TINYINT, int2bi INTEGER, float2double %2$s))]), " + + " FALSE, " + " TINYINT '1', " + " TINYINT '-2', " + " NULL, " + @@ -350,6 +353,9 @@ else if (getHiveVersionMajor() == 3 && isFormat.test("orc")) { .addField("add", null) .build()) : "{-2:{\"ti2bi\":null,\"int2bi\":-2323,\"float2double\":-1.5,\"add\":null}}")) + .put("boolean_to_varchar", ImmutableList.of( + "TRUE", + "FALSE")) .put("tinyint_to_smallint", ImmutableList.of( -1, 1)) @@ -834,6 +840,7 @@ private void assertProperAlteredTableSchema(String tableName) row("row_to_row", "row(keep varchar, ti2si smallint, si2int integer, int2bi bigint, bi2vc varchar, lower2uppercase bigint)"), row("list_to_list", "array(row(ti2int integer, si2bi bigint, bi2vc varchar))"), row("map_to_map", "map(integer, row(ti2bi bigint, int2bi bigint, float2double double, add tinyint))"), + row("boolean_to_varchar", "varchar(5)"), row("tinyint_to_smallint", "smallint"), row("tinyint_to_int", "integer"), row("tinyint_to_bigint", "bigint"), @@ -900,6 +907,7 @@ private void assertColumnTypes( .put("row_to_row", engine == Engine.TRINO ? JAVA_OBJECT : STRUCT) // row .put("list_to_list", ARRAY) // list .put("map_to_map", JAVA_OBJECT) // map + .put("boolean_to_varchar", VARCHAR) .put("tinyint_to_smallint", SMALLINT) .put("tinyint_to_int", INTEGER) .put("tinyint_to_bigint", BIGINT) @@ -965,6 +973,7 @@ private static void alterTableColumnTypes(String tableName) onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN row_to_row row_to_row struct", tableName)); onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN list_to_list list_to_list array>", tableName)); onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN map_to_map map_to_map map>", tableName)); + onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN boolean_to_varchar boolean_to_varchar varchar(5)", tableName)); onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN tinyint_to_smallint tinyint_to_smallint smallint", tableName)); onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN tinyint_to_int tinyint_to_int int", tableName)); onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN tinyint_to_bigint tinyint_to_bigint bigint", tableName)); diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnPartitionedTable.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnPartitionedTable.java index bdf780749326..05af7f4f79a3 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnPartitionedTable.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnPartitionedTable.java @@ -103,6 +103,7 @@ private static HiveTableDefinition.HiveTableDefinitionBuilder tableDefinitionBui " row_to_row STRUCT, " + " list_to_list ARRAY>, " + " map_to_map MAP>, " + + " boolean_to_varchar BOOLEAN," + " tinyint_to_smallint TINYINT," + " tinyint_to_int TINYINT," + " tinyint_to_bigint TINYINT," + diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnUnpartitionedTable.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnUnpartitionedTable.java index a32ef3d969bb..c32aab18ad22 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnUnpartitionedTable.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnUnpartitionedTable.java @@ -52,6 +52,7 @@ private static HiveTableDefinition.HiveTableDefinitionBuilder tableDefinitionBui row_to_row STRUCT, list_to_list ARRAY>, map_to_map MAP>, + boolean_to_varchar BOOLEAN, tinyint_to_smallint TINYINT, tinyint_to_int TINYINT, tinyint_to_bigint TINYINT, From 12b361f343e3e2da3608bfaef9f6a2eeae73258e Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Thu, 2 Nov 2023 20:11:42 +0100 Subject: [PATCH 044/587] Update arrow to 14.0.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d238d6781193..3e1c33dfe8ee 100644 --- a/pom.xml +++ b/pom.xml @@ -154,7 +154,7 @@ 2.7.7-1 4.13.0 237 - 13.0.0 + 14.0.0 1.11.3 ${dep.airlift.version} 2.1.1 From cacd829f9f0fd72ae740645137d8857eee19ef9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Wa=C5=9B?= Date: Mon, 30 Oct 2023 11:14:14 +0100 Subject: [PATCH 045/587] Always use lowercase Hive types --- .../hive/metastore/thrift/ThriftMetastoreUtil.java | 2 +- .../plugin/hive/metastore/TestMetastoreUtil.java | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreUtil.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreUtil.java index 3dcffa63ac71..6dd725b00dc4 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreUtil.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreUtil.java @@ -686,7 +686,7 @@ public static FieldSchema toMetastoreApiFieldSchema(Column column) private static Column fromMetastoreApiFieldSchema(FieldSchema fieldSchema) { - return new Column(fieldSchema.getName(), HiveType.valueOf(fieldSchema.getType()), Optional.ofNullable(fieldSchema.getComment())); + return new Column(fieldSchema.getName(), HiveType.valueOf(fieldSchema.getType().toLowerCase(ENGLISH)), Optional.ofNullable(fieldSchema.getComment())); } private static void fromMetastoreApiStorageDescriptor( diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestMetastoreUtil.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestMetastoreUtil.java index 9b030dc31c02..bf0a31cd1166 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestMetastoreUtil.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestMetastoreUtil.java @@ -30,6 +30,7 @@ import org.junit.jupiter.api.Test; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Properties; @@ -185,6 +186,19 @@ public void testHiveSchemaPartition() assertEquals(actual, TEST_TABLE_METADATA); } + @Test + public void testHiveSchemaCaseInsensitive() + { + List testSchema = TEST_SCHEMA.stream() + .map(fieldSchema -> new FieldSchema(fieldSchema.getName(), fieldSchema.getType().toUpperCase(Locale.ENGLISH), fieldSchema.getComment())) + .toList(); + Properties actualTable = MetastoreUtil.getHiveSchema(ThriftMetastoreUtil.fromMetastoreApiTable(TEST_TABLE_WITH_UNSUPPORTED_FIELDS, testSchema)); + assertEquals(actualTable, TEST_TABLE_METADATA); + + Properties actualPartition = MetastoreUtil.getHiveSchema(ThriftMetastoreUtil.fromMetastoreApiPartition(TEST_PARTITION_WITH_UNSUPPORTED_FIELDS), ThriftMetastoreUtil.fromMetastoreApiTable(TEST_TABLE_WITH_UNSUPPORTED_FIELDS, testSchema)); + assertEquals(actualPartition, TEST_TABLE_METADATA); + } + @Test public void testComputePartitionKeyFilter() { From d4dd8425a71f027673895d0e253596132dd870c2 Mon Sep 17 00:00:00 2001 From: Raunaq Morarka Date: Fri, 3 Nov 2023 16:25:26 +0530 Subject: [PATCH 046/587] Add filename to metadata missing exception in parquet --- .../src/main/java/io/trino/parquet/reader/ParquetReader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/ParquetReader.java b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/ParquetReader.java index 9c214df7d128..b7e111c7fcde 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/ParquetReader.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/ParquetReader.java @@ -511,7 +511,7 @@ private ColumnChunkMetaData getColumnChunkMetaData(BlockMetaData blockMetaData, return metadata; } } - throw new ParquetCorruptionException("Metadata is missing for column: %s", columnDescriptor); + throw new ParquetCorruptionException(dataSource.getId(), "Metadata is missing for column: %s", columnDescriptor); } private void initializeColumnReaders() From dd3817e4cc07499b341e4a09706a72f62a7cc06a Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Thu, 2 Nov 2023 16:57:39 -0700 Subject: [PATCH 047/587] Update usage of deprecated APIs --- .../java/io/trino/operator/scalar/CharacterStringCasts.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/operator/scalar/CharacterStringCasts.java b/core/trino-main/src/main/java/io/trino/operator/scalar/CharacterStringCasts.java index 5f470c2fd008..0d0b0e5cccdb 100644 --- a/core/trino-main/src/main/java/io/trino/operator/scalar/CharacterStringCasts.java +++ b/core/trino-main/src/main/java/io/trino/operator/scalar/CharacterStringCasts.java @@ -107,7 +107,7 @@ public static Slice varcharToCharSaturatedFloorCast(@LiteralParameter("y") long return Slices.allocate(toIntExact(y)); } - int lastCodePoint = codePoints.get(codePoints.size() - 1) - 1; + int lastCodePoint = codePoints.getInt(codePoints.size() - 1) - 1; /* * UTF-8 reserve codepoints from 0xD800 to 0xDFFF for encoding UTF-16 * If the lastCodePoint after -1 operation is in this range, it will lead to an InvalidCodePointException @@ -145,7 +145,7 @@ public static Slice varcharToVarcharSaturatedFloorCast(@LiteralParameter("y") lo private static void trimTrailing(IntList codePoints, int codePointToTrim) { int endIndex = codePoints.size(); - while (endIndex > 0 && codePoints.get(endIndex - 1) == codePointToTrim) { + while (endIndex > 0 && codePoints.getInt(endIndex - 1) == codePointToTrim) { endIndex--; } codePoints.size(endIndex); From 45e00206a60564fcafaabc594e818176f6713eca Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sat, 4 Nov 2023 19:57:27 -0700 Subject: [PATCH 048/587] Move Hadoop S3 filesystem tests to HDFS module --- .github/workflows/ci.yml | 14 ++++++++++++++ lib/trino-hdfs/pom.xml | 19 +++++++++++++++++++ .../s3/AbstractTestTrinoS3FileSystem.java | 5 ++--- .../hdfs}/s3/TestTrinoS3FileSystemAwsS3.java | 4 ++-- .../hdfs}/s3/TestTrinoS3FileSystemMinio.java | 5 ++--- plugin/trino-hive/pom.xml | 2 -- 6 files changed, 39 insertions(+), 10 deletions(-) rename plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/BaseTestTrinoS3FileSystemObjectStorage.java => lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/AbstractTestTrinoS3FileSystem.java (99%) rename {plugin/trino-hive/src/test/java/io/trino/plugin/hive => lib/trino-hdfs/src/test/java/io/trino/hdfs}/s3/TestTrinoS3FileSystemAwsS3.java (94%) rename {plugin/trino-hive/src/test/java/io/trino/plugin/hive => lib/trino-hdfs/src/test/java/io/trino/hdfs}/s3/TestTrinoS3FileSystemMinio.java (97%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 149d9c839380..7857aafefb6f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -574,6 +574,7 @@ jobs: - { modules: core/trino-main } - { modules: lib/trino-filesystem-s3, profile: cloud-tests } - { modules: lib/trino-filesystem-azure, profile: cloud-tests } + - { modules: lib/trino-hdfs, profile: cloud-tests } - { modules: plugin/trino-accumulo } - { modules: plugin/trino-bigquery } - { modules: plugin/trino-bigquery, profile: cloud-tests-arrow } @@ -658,12 +659,25 @@ jobs: && ! (contains(matrix.modules, 'trino-redshift') && contains(matrix.profile, 'fte-tests')) && ! (contains(matrix.modules, 'trino-filesystem-s3') && contains(matrix.profile, 'cloud-tests')) && ! (contains(matrix.modules, 'trino-filesystem-azure') && contains(matrix.profile, 'cloud-tests')) + && ! (contains(matrix.modules, 'trino-hdfs') && contains(matrix.profile, 'cloud-tests')) run: $MAVEN test ${MAVEN_TEST} -pl ${{ matrix.modules }} ${{ matrix.profile != '' && format('-P {0}', matrix.profile) || '' }} # Additional tests for selected modules - name: HDFS file system cache isolated JVM tests if: contains(matrix.modules, 'trino-hdfs') run: | $MAVEN test ${MAVEN_TEST} -pl :trino-hdfs -P test-isolated-jvm-suites + - name: Hadoop FileSystem Cloud Tests + env: + AWS_ACCESS_KEY_ID: ${{ secrets.TRINO_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.TRINO_AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ vars.TRINO_AWS_REGION }} + S3_BUCKET: ${{ vars.TRINO_S3_BUCKET }} + S3_BUCKET_ENDPOINT: "s3.${{ vars.TRINO_AWS_REGION }}.amazonaws.com" + if: >- + contains(matrix.modules, 'trino-hdfs') && contains(matrix.profile, 'cloud-tests') && + (env.CI_SKIP_SECRETS_PRESENCE_CHECKS != '' || env.AWS_ACCESS_KEY_ID != '' || env.AWS_SECRET_ACCESS_KEY != '') + run: | + $MAVEN test ${MAVEN_TEST} -pl :trino-hdfs -P cloud-tests - name: S3 FileSystem Cloud Tests env: AWS_ACCESS_KEY_ID: ${{ secrets.TRINO_AWS_ACCESS_KEY_ID }} diff --git a/lib/trino-hdfs/pom.xml b/lib/trino-hdfs/pom.xml index c48da1c1e605..3db7e77b55eb 100644 --- a/lib/trino-hdfs/pom.xml +++ b/lib/trino-hdfs/pom.xml @@ -300,12 +300,14 @@ ${isolatedJvmTests} + **/TestTrinoS3FileSystemAwsS3.java + test-isolated-jvm-suites @@ -324,5 +326,22 @@ + + + cloud-tests + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/TestTrinoS3FileSystemAwsS3.java + + + + + + diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/BaseTestTrinoS3FileSystemObjectStorage.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/AbstractTestTrinoS3FileSystem.java similarity index 99% rename from plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/BaseTestTrinoS3FileSystemObjectStorage.java rename to lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/AbstractTestTrinoS3FileSystem.java index caf22c0c1788..6eec7a955de3 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/BaseTestTrinoS3FileSystemObjectStorage.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/AbstractTestTrinoS3FileSystem.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.s3; +package io.trino.hdfs.s3; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.ListObjectsV2Request; @@ -20,7 +20,6 @@ import com.amazonaws.services.s3.model.PutObjectRequest; import com.amazonaws.services.s3.model.S3ObjectSummary; import com.google.common.net.MediaType; -import io.trino.hdfs.s3.TrinoS3FileSystem; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.testng.annotations.Test; @@ -38,7 +37,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.testng.Assert.assertTrue; -public abstract class BaseTestTrinoS3FileSystemObjectStorage +public abstract class AbstractTestTrinoS3FileSystem { private static final MediaType DIRECTORY_MEDIA_TYPE = MediaType.create("application", "x-directory"); private static final String PATH_SEPARATOR = "/"; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestTrinoS3FileSystemAwsS3.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestTrinoS3FileSystemAwsS3.java similarity index 94% rename from plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestTrinoS3FileSystemAwsS3.java rename to lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestTrinoS3FileSystemAwsS3.java index 2c82337f8870..1f6e6e68b327 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestTrinoS3FileSystemAwsS3.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestTrinoS3FileSystemAwsS3.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.s3; +package io.trino.hdfs.s3; import org.apache.hadoop.conf.Configuration; import org.testng.annotations.BeforeClass; @@ -25,7 +25,7 @@ * See https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html#credentials-default */ public class TestTrinoS3FileSystemAwsS3 - extends BaseTestTrinoS3FileSystemObjectStorage + extends AbstractTestTrinoS3FileSystem { private String bucketName; private String s3Endpoint; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestTrinoS3FileSystemMinio.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestTrinoS3FileSystemMinio.java similarity index 97% rename from plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestTrinoS3FileSystemMinio.java rename to lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestTrinoS3FileSystemMinio.java index 999fa2fc6899..6a02494548ef 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestTrinoS3FileSystemMinio.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestTrinoS3FileSystemMinio.java @@ -11,10 +11,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.s3; +package io.trino.hdfs.s3; import com.amazonaws.services.s3.AmazonS3; -import io.trino.hdfs.s3.TrinoS3FileSystem; import io.trino.testing.containers.Minio; import io.trino.testing.minio.MinioClient; import io.trino.util.AutoCloseableCloser; @@ -34,7 +33,7 @@ import static org.testng.Assert.assertTrue; public class TestTrinoS3FileSystemMinio - extends BaseTestTrinoS3FileSystemObjectStorage + extends AbstractTestTrinoS3FileSystem { private final String bucketName = "test-bucket-" + randomNameSuffix(); diff --git a/plugin/trino-hive/pom.xml b/plugin/trino-hive/pom.xml index c849555cef00..df6d0b06d72b 100644 --- a/plugin/trino-hive/pom.xml +++ b/plugin/trino-hive/pom.xml @@ -540,7 +540,6 @@ **/TestHiveGlueMetastore.java **/TestHiveS3AndGlueMetastoreTest.java **/TestHiveConcurrentModificationGlueMetastore.java - **/TestTrinoS3FileSystemAwsS3.java **/TestFullParquetReader.java **/TestParquetReader.java **/Test*FailureRecoveryTest.java @@ -598,7 +597,6 @@ **/TestHiveGlueMetastore.java **/TestHiveS3AndGlueMetastoreTest.java **/TestHiveConcurrentModificationGlueMetastore.java - **/TestTrinoS3FileSystemAwsS3.java From 2a1e1f7b3ce59586d704840fc75c442170f7faaf Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sat, 4 Nov 2023 17:52:55 -0700 Subject: [PATCH 049/587] Remove unused S3HiveQueryRunner factory --- .../plugin/hive/s3/S3HiveQueryRunner.java | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/S3HiveQueryRunner.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/S3HiveQueryRunner.java index 05a332b5335c..26deed0099e5 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/S3HiveQueryRunner.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/S3HiveQueryRunner.java @@ -50,25 +50,6 @@ public static DistributedQueryRunner create( .build(); } - public static DistributedQueryRunner create( - HostAndPort hiveMetastoreEndpoint, - String s3Endpoint, - String s3AccessKey, - String s3SecretKey, - String bucketName, - Map additionalHiveProperties) - throws Exception - { - return builder() - .setHiveMetastoreEndpoint(hiveMetastoreEndpoint) - .setS3Endpoint(s3Endpoint) - .setS3AccessKey(s3AccessKey) - .setS3SecretKey(s3SecretKey) - .setBucketName(bucketName) - .setHiveProperties(additionalHiveProperties) - .build(); - } - public static Builder builder(HiveMinioDataLake hiveMinioDataLake) { return builder() From 407a105f8d4358d7e51cf29104803fa103a6b6de Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sat, 4 Nov 2023 17:54:23 -0700 Subject: [PATCH 050/587] Remove unused Hive AWS test properties --- .github/workflows/ci.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7857aafefb6f..429f56cafd19 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -330,9 +330,7 @@ jobs: S3_BUCKET_ENDPOINT: "s3.${{ vars.TRINO_AWS_REGION }}.amazonaws.com" run: | if [ "${AWS_ACCESS_KEY_ID}" != "" ]; then - $MAVEN test ${MAVEN_TEST} -pl :trino-hive -P aws-tests \ - -Ds3.bucket="${S3_BUCKET}" \ - -Ds3.bucket-endpoint="${S3_BUCKET_ENDPOINT}" + $MAVEN test ${MAVEN_TEST} -pl :trino-hive -P aws-tests fi - name: Run Hive Azure ABFS Access Key Tests if: matrix.config != 'config-empty' # Hive 1.x does not support Azure storage From 5f408543ae9bd240905c9241cda1d54308abed06 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Fri, 3 Nov 2023 15:13:44 -0700 Subject: [PATCH 051/587] Move custom test to end of file --- .../s3/AbstractTestS3FileSystem.java | 94 +++++++++---------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/AbstractTestS3FileSystem.java b/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/AbstractTestS3FileSystem.java index 46881729b898..10d81971e2f4 100644 --- a/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/AbstractTestS3FileSystem.java +++ b/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/AbstractTestS3FileSystem.java @@ -63,53 +63,6 @@ final void cleanup() fileSystemFactory = null; } - /** - * Tests same things as {@link #testFileWithTrailingWhitespace()} but with setup and assertions using {@link S3Client}. - */ - @Test - public void testFileWithTrailingWhitespaceAgainstNativeClient() - throws IOException - { - try (S3Client s3Client = createS3Client()) { - String key = "foo/bar with whitespace "; - byte[] contents = "abc foo bar".getBytes(UTF_8); - s3Client.putObject( - request -> request.bucket(bucket()).key(key), - RequestBody.fromBytes(contents.clone())); - try { - // Verify listing - List listing = toList(fileSystem.listFiles(getRootLocation().appendPath("foo"))); - assertThat(listing).hasSize(1); - FileEntry fileEntry = getOnlyElement(listing); - assertThat(fileEntry.location()).isEqualTo(getRootLocation().appendPath(key)); - assertThat(fileEntry.length()).isEqualTo(contents.length); - - // Verify reading - TrinoInputFile inputFile = fileSystem.newInputFile(fileEntry.location()); - assertThat(inputFile.exists()).as("exists").isTrue(); - try (TrinoInputStream inputStream = inputFile.newStream()) { - byte[] bytes = ByteStreams.toByteArray(inputStream); - assertThat(bytes).isEqualTo(contents); - } - - // Verify writing - byte[] newContents = "bar bar baz new content".getBytes(UTF_8); - try (OutputStream outputStream = fileSystem.newOutputFile(fileEntry.location()).createOrOverwrite()) { - outputStream.write(newContents.clone()); - } - assertThat(s3Client.getObjectAsBytes(request -> request.bucket(bucket()).key(key)).asByteArray()) - .isEqualTo(newContents); - - // Verify deleting - fileSystem.deleteFile(fileEntry.location()); - assertThat(inputFile.exists()).as("exists after delete").isFalse(); - } - finally { - s3Client.deleteObject(delete -> delete.bucket(bucket()).key(key)); - } - } - } - @Override protected final boolean isHierarchical() { @@ -174,4 +127,51 @@ protected List toList(FileIterator fileIterator) } return list.build(); } + + /** + * Tests same things as {@link #testFileWithTrailingWhitespace()} but with setup and assertions using {@link S3Client}. + */ + @Test + void testFileWithTrailingWhitespaceAgainstNativeClient() + throws IOException + { + try (S3Client s3Client = createS3Client()) { + String key = "foo/bar with whitespace "; + byte[] contents = "abc foo bar".getBytes(UTF_8); + s3Client.putObject( + request -> request.bucket(bucket()).key(key), + RequestBody.fromBytes(contents.clone())); + try { + // Verify listing + List listing = toList(fileSystem.listFiles(getRootLocation().appendPath("foo"))); + assertThat(listing).hasSize(1); + FileEntry fileEntry = getOnlyElement(listing); + assertThat(fileEntry.location()).isEqualTo(getRootLocation().appendPath(key)); + assertThat(fileEntry.length()).isEqualTo(contents.length); + + // Verify reading + TrinoInputFile inputFile = fileSystem.newInputFile(fileEntry.location()); + assertThat(inputFile.exists()).as("exists").isTrue(); + try (TrinoInputStream inputStream = inputFile.newStream()) { + byte[] bytes = ByteStreams.toByteArray(inputStream); + assertThat(bytes).isEqualTo(contents); + } + + // Verify writing + byte[] newContents = "bar bar baz new content".getBytes(UTF_8); + try (OutputStream outputStream = fileSystem.newOutputFile(fileEntry.location()).createOrOverwrite()) { + outputStream.write(newContents.clone()); + } + assertThat(s3Client.getObjectAsBytes(request -> request.bucket(bucket()).key(key)).asByteArray()) + .isEqualTo(newContents); + + // Verify deleting + fileSystem.deleteFile(fileEntry.location()); + assertThat(inputFile.exists()).as("exists after delete").isFalse(); + } + finally { + s3Client.deleteObject(delete -> delete.bucket(bucket()).key(key)); + } + } + } } From 257569a0ad990350439042761058f6c94680f66f Mon Sep 17 00:00:00 2001 From: David Phillips Date: Fri, 3 Nov 2023 15:31:20 -0700 Subject: [PATCH 052/587] Filter S3 objects that end in slash when listing files --- .../io/trino/filesystem/s3/S3FileSystem.java | 9 ++++--- .../s3/AbstractTestS3FileSystem.java | 26 +++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3FileSystem.java b/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3FileSystem.java index 605c794ae5ad..e9b296349871 100644 --- a/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3FileSystem.java +++ b/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3FileSystem.java @@ -30,12 +30,13 @@ import software.amazon.awssdk.services.s3.model.ObjectIdentifier; import software.amazon.awssdk.services.s3.model.RequestPayer; import software.amazon.awssdk.services.s3.model.S3Error; -import software.amazon.awssdk.services.s3.paginators.ListObjectsV2Iterable; +import software.amazon.awssdk.services.s3.model.S3Object; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -181,8 +182,10 @@ public FileIterator listFiles(Location location) .build(); try { - ListObjectsV2Iterable iterable = client.listObjectsV2Paginator(request); - return new S3FileIterator(s3Location, iterable.contents().iterator()); + Iterator iterator = client.listObjectsV2Paginator(request).contents().stream() + .filter(object -> !object.key().endsWith("/")) + .iterator(); + return new S3FileIterator(s3Location, iterator); } catch (SdkException e) { throw new IOException("Failed to list location: " + location, e); diff --git a/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/AbstractTestS3FileSystem.java b/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/AbstractTestS3FileSystem.java index 10d81971e2f4..6704cccc0bcc 100644 --- a/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/AbstractTestS3FileSystem.java +++ b/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/AbstractTestS3FileSystem.java @@ -174,4 +174,30 @@ void testFileWithTrailingWhitespaceAgainstNativeClient() } } } + + @Test + void testExistingFileWithTrailingSlash() + throws IOException + { + try (S3Client s3Client = createS3Client()) { + String key = "data/file/"; + s3Client.putObject(request -> request.bucket(bucket()).key(key), RequestBody.empty()); + try { + assertThat(fileSystem.listFiles(getRootLocation()).hasNext()).isFalse(); + + Location data = getRootLocation().appendPath("data/"); + assertThat(fileSystem.listDirectories(getRootLocation())).containsExactly(data); + assertThat(fileSystem.listDirectories(data)).containsExactly(data.appendPath("file/")); + + fileSystem.deleteDirectory(data); + assertThat(fileSystem.listDirectories(getRootLocation())).containsExactly(data); + + fileSystem.deleteDirectory(getRootLocation()); + assertThat(fileSystem.listDirectories(getRootLocation())).containsExactly(data); + } + finally { + s3Client.deleteObject(delete -> delete.bucket(bucket()).key(key)); + } + } + } } From 2b6a5a29ba72ba3bf09fdf21685e31b73586a210 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Fri, 3 Nov 2023 15:34:20 -0700 Subject: [PATCH 053/587] Run Hive S3 tests using native file system --- .github/workflows/ci.yml | 1 + .../bin/run_hive_s3_tests.sh | 3 ++- .../hive/TestHiveThriftMetastoreWithS3.java | 5 +++++ .../plugin/hive/TestHive3OnDataLake.java | 4 ++-- .../TestHiveQueryFailureRecoveryTest.java | 6 ----- .../hive/TestHiveTaskFailureRecoveryTest.java | 6 ----- .../plugin/hive/s3/S3HiveQueryRunner.java | 22 +++++++++++++++---- 7 files changed, 28 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 429f56cafd19..bc8b47ee12e5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -310,6 +310,7 @@ jobs: env: AWS_ACCESS_KEY_ID: ${{ secrets.TRINO_AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.TRINO_AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ vars.TRINO_AWS_REGION }} S3_BUCKET: ${{ vars.TRINO_S3_BUCKET }} S3_BUCKET_ENDPOINT: "https://s3.${{ vars.TRINO_AWS_REGION }}.amazonaws.com" run: | diff --git a/plugin/trino-hive-hadoop2/bin/run_hive_s3_tests.sh b/plugin/trino-hive-hadoop2/bin/run_hive_s3_tests.sh index 0b9fb473e6db..82fa2b7fd6e6 100755 --- a/plugin/trino-hive-hadoop2/bin/run_hive_s3_tests.sh +++ b/plugin/trino-hive-hadoop2/bin/run_hive_s3_tests.sh @@ -6,7 +6,7 @@ set -euo pipefail -x abort_if_not_gib_impacted -check_vars S3_BUCKET S3_BUCKET_ENDPOINT \ +check_vars S3_BUCKET S3_BUCKET_ENDPOINT AWS_REGION \ AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY cleanup_hadoop_docker_containers @@ -61,6 +61,7 @@ set +e -Dhive.hadoop2.metastorePort=9083 \ -Dhive.hadoop2.databaseName=default \ -Dhive.hadoop2.s3.endpoint="${S3_BUCKET_ENDPOINT}" \ + -Dhive.hadoop2.s3.region="${AWS_REGION}" \ -Dhive.hadoop2.s3.awsAccessKey="${AWS_ACCESS_KEY_ID}" \ -Dhive.hadoop2.s3.awsSecretKey="${AWS_SECRET_ACCESS_KEY}" \ -Dhive.hadoop2.s3.writableBucket="${S3_BUCKET}" \ diff --git a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveThriftMetastoreWithS3.java b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveThriftMetastoreWithS3.java index d5d3659f291d..76b8251ca653 100644 --- a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveThriftMetastoreWithS3.java +++ b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveThriftMetastoreWithS3.java @@ -46,6 +46,7 @@ public class TestHiveThriftMetastoreWithS3 extends AbstractTestQueryFramework { private final String s3endpoint; + private final String s3Region; private final String awsAccessKey; private final String awsSecretKey; private final String writableBucket; @@ -55,18 +56,21 @@ public class TestHiveThriftMetastoreWithS3 @Parameters({ "hive.hadoop2.s3.endpoint", + "hive.hadoop2.s3.region", "hive.hadoop2.s3.awsAccessKey", "hive.hadoop2.s3.awsSecretKey", "hive.hadoop2.s3.writableBucket", }) public TestHiveThriftMetastoreWithS3( String s3endpoint, + String s3Region, String awsAccessKey, String awsSecretKey, String writableBucket) throws IOException { this.s3endpoint = requireNonNull(s3endpoint, "s3endpoint is null"); + this.s3Region = requireNonNull(s3Region, "s3Region is null"); this.awsAccessKey = requireNonNull(awsAccessKey, "awsAccessKey is null"); this.awsSecretKey = requireNonNull(awsSecretKey, "awsSecretKey is null"); this.writableBucket = requireNonNull(writableBucket, "writableBucket is null"); @@ -99,6 +103,7 @@ protected QueryRunner createQueryRunner() return S3HiveQueryRunner.builder() .setHiveMetastoreEndpoint(hiveHadoop.getHiveMetastoreEndpoint()) .setS3Endpoint(s3endpoint) + .setS3Region(s3Region) .setS3AccessKey(awsAccessKey) .setS3SecretKey(awsSecretKey) .setBucketName(writableBucket) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHive3OnDataLake.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHive3OnDataLake.java index 54bf554e3d7c..36b2a793b453 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHive3OnDataLake.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHive3OnDataLake.java @@ -101,7 +101,7 @@ protected QueryRunner createQueryRunner() .put("hive.metastore-cache-ttl", "1d") .put("hive.metastore-refresh-interval", "1d") // This is required to reduce memory pressure to test writing large files - .put("hive.s3.streaming.part-size", HIVE_S3_STREAMING_PART_SIZE.toString()) + .put("s3.streaming.part-size", HIVE_S3_STREAMING_PART_SIZE.toString()) // This is required to enable AWS Athena partition projection .put("hive.partition-projection-enabled", "true") .buildOrThrow()) @@ -1686,7 +1686,7 @@ public void testCreateTableInvalidName() public void testRenameSchemaToInvalidObjectName() { String schemaName = "test_rename_schema_invalid_name_" + randomNameSuffix(); - assertUpdate("CREATE SCHEMA " + schemaName); + assertUpdate("CREATE SCHEMA %1$s WITH (location='s3a://%2$s/%1$s')".formatted(schemaName, bucketName)); for (String invalidSchemaName : Arrays.asList(".", "..", "foo/bar")) { assertThatThrownBy(() -> assertUpdate("ALTER SCHEMA hive." + schemaName + " RENAME TO \"" + invalidSchemaName + "\"")) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveQueryFailureRecoveryTest.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveQueryFailureRecoveryTest.java index 221391cae726..af44e0512372 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveQueryFailureRecoveryTest.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveQueryFailureRecoveryTest.java @@ -13,7 +13,6 @@ */ package io.trino.plugin.hive; -import com.google.common.collect.ImmutableMap; import io.trino.operator.RetryPolicy; import io.trino.plugin.exchange.filesystem.FileSystemExchangePlugin; import io.trino.plugin.exchange.filesystem.containers.MinioStorage; @@ -62,11 +61,6 @@ protected QueryRunner createQueryRunner( runner.installPlugin(new FileSystemExchangePlugin()); runner.loadExchangeManager("filesystem", getExchangeManagerProperties(minioStorage)); }) - .setHiveProperties(ImmutableMap.of( - // Streaming upload allocates non trivial amount of memory for buffering (16MB per output file by default). - // When streaming upload is enabled insert into a table with high number of buckets / partitions may cause - // the tests to run out of memory as the buffer space is eagerly allocated for each output file. - "hive.s3.streaming.enabled", "false")) .build(); } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveTaskFailureRecoveryTest.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveTaskFailureRecoveryTest.java index 7d6f119d99ac..841117e8a81a 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveTaskFailureRecoveryTest.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveTaskFailureRecoveryTest.java @@ -13,7 +13,6 @@ */ package io.trino.plugin.hive; -import com.google.common.collect.ImmutableMap; import io.trino.operator.RetryPolicy; import io.trino.plugin.exchange.filesystem.FileSystemExchangePlugin; import io.trino.plugin.exchange.filesystem.containers.MinioStorage; @@ -62,11 +61,6 @@ protected QueryRunner createQueryRunner( runner.installPlugin(new FileSystemExchangePlugin()); runner.loadExchangeManager("filesystem", getExchangeManagerProperties(minioStorage)); }) - .setHiveProperties(ImmutableMap.of( - // Streaming upload allocates non trivial amount of memory for buffering (16MB per output file by default). - // When streaming upload is enabled insert into a table with high number of buckets / partitions may cause - // the tests to run out of memory as the buffer space is eagerly allocated for each output file. - "hive.s3.streaming.enabled", "false")) .build(); } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/S3HiveQueryRunner.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/S3HiveQueryRunner.java index 26deed0099e5..54e927776348 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/S3HiveQueryRunner.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/S3HiveQueryRunner.java @@ -33,6 +33,7 @@ import static io.trino.plugin.hive.TestingThriftHiveMetastoreBuilder.testingThriftHiveMetastoreBuilder; import static io.trino.plugin.hive.security.HiveSecurityModule.ALLOW_ALL; import static io.trino.testing.containers.Minio.MINIO_ACCESS_KEY; +import static io.trino.testing.containers.Minio.MINIO_REGION; import static io.trino.testing.containers.Minio.MINIO_SECRET_KEY; import static java.util.Objects.requireNonNull; @@ -55,6 +56,7 @@ public static Builder builder(HiveMinioDataLake hiveMinioDataLake) return builder() .setHiveMetastoreEndpoint(hiveMinioDataLake.getHiveHadoop().getHiveMetastoreEndpoint()) .setS3Endpoint("http://" + hiveMinioDataLake.getMinio().getMinioApiEndpoint()) + .setS3Region(MINIO_REGION) .setS3AccessKey(MINIO_ACCESS_KEY) .setS3SecretKey(MINIO_SECRET_KEY) .setBucketName(hiveMinioDataLake.getBucketName()); @@ -71,6 +73,7 @@ public static class Builder private HostAndPort hiveMetastoreEndpoint; private Duration thriftMetastoreTimeout = TestingTokenAwareMetastoreClientFactory.TIMEOUT; private ThriftMetastoreConfig thriftMetastoreConfig = new ThriftMetastoreConfig(); + private String s3Region; private String s3Endpoint; private String s3AccessKey; private String s3SecretKey; @@ -97,6 +100,13 @@ public Builder setThriftMetastoreConfig(ThriftMetastoreConfig thriftMetastoreCon return this; } + @CanIgnoreReturnValue + public Builder setS3Region(String s3Region) + { + this.s3Region = requireNonNull(s3Region, "s3Region is null"); + return this; + } + @CanIgnoreReturnValue public Builder setS3Endpoint(String s3Endpoint) { @@ -130,6 +140,7 @@ public DistributedQueryRunner build() throws Exception { requireNonNull(hiveMetastoreEndpoint, "hiveMetastoreEndpoint is null"); + requireNonNull(s3Region, "s3Region is null"); requireNonNull(s3Endpoint, "s3Endpoint is null"); requireNonNull(s3AccessKey, "s3AccessKey is null"); requireNonNull(s3SecretKey, "s3SecretKey is null"); @@ -137,10 +148,13 @@ public DistributedQueryRunner build() String lowerCaseS3Endpoint = s3Endpoint.toLowerCase(Locale.ENGLISH); checkArgument(lowerCaseS3Endpoint.startsWith("http://") || lowerCaseS3Endpoint.startsWith("https://"), "Expected http URI for S3 endpoint; got %s", s3Endpoint); - addHiveProperty("hive.s3.endpoint", s3Endpoint); - addHiveProperty("hive.s3.aws-access-key", s3AccessKey); - addHiveProperty("hive.s3.aws-secret-key", s3SecretKey); - addHiveProperty("hive.s3.path-style-access", "true"); + addHiveProperty("fs.hadoop.enabled", "false"); + addHiveProperty("fs.native-s3.enabled", "true"); + addHiveProperty("s3.region", s3Region); + addHiveProperty("s3.endpoint", s3Endpoint); + addHiveProperty("s3.aws-access-key", s3AccessKey); + addHiveProperty("s3.aws-secret-key", s3SecretKey); + addHiveProperty("s3.path-style-access", "true"); setMetastore(distributedQueryRunner -> new BridgingHiveMetastore( testingThriftHiveMetastoreBuilder() .metastoreClient(hiveMetastoreEndpoint, thriftMetastoreTimeout) From 23bccc87a717a83030914d44bafa77ee60732c50 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Fri, 3 Nov 2023 21:04:22 -0700 Subject: [PATCH 054/587] Handle zero length blocks when generating Hudi splits --- .../HudiReadOptimizedDirectoryLister.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/query/HudiReadOptimizedDirectoryLister.java b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/query/HudiReadOptimizedDirectoryLister.java index 869806461c77..d448f18b5098 100644 --- a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/query/HudiReadOptimizedDirectoryLister.java +++ b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/query/HudiReadOptimizedDirectoryLister.java @@ -13,6 +13,8 @@ */ package io.trino.plugin.hudi.query; +import io.airlift.units.DataSize; +import io.trino.filesystem.FileEntry.Block; import io.trino.plugin.hive.HiveColumnHandle; import io.trino.plugin.hive.metastore.Column; import io.trino.plugin.hive.metastore.HiveMetastore; @@ -25,6 +27,7 @@ import io.trino.plugin.hudi.table.HudiTableFileSystemView; import io.trino.plugin.hudi.table.HudiTableMetaClient; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; @@ -32,10 +35,15 @@ import java.util.stream.Collectors; import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.airlift.units.DataSize.Unit.MEGABYTE; +import static java.lang.Math.max; +import static java.lang.Math.min; public class HudiReadOptimizedDirectoryLister implements HudiDirectoryLister { + private static final long MIN_BLOCK_SIZE = DataSize.of(32, MEGABYTE).toBytes(); + private final HudiTableFileSystemView fileSystemView; private final List partitionColumns; private final Map allPartitionInfoMap; @@ -72,7 +80,7 @@ public List listStatus(HudiPartitionInfo partitionInfo) false, fileEntry.length(), fileEntry.lastModified().toEpochMilli(), - fileEntry.blocks().map(listOfBlocks -> (!listOfBlocks.isEmpty()) ? listOfBlocks.get(0).length() : 0).orElse(0L))) + max(blockSize(fileEntry.blocks()), min(fileEntry.length(), MIN_BLOCK_SIZE)))) .collect(toImmutableList()); } @@ -89,4 +97,13 @@ public void close() fileSystemView.close(); } } + + private static long blockSize(Optional> blocks) + { + return blocks.stream() + .flatMap(Collection::stream) + .mapToLong(Block::length) + .findFirst() + .orElse(0); + } } From 066f0cf90fcdb17ad564be892792f3437d460bed Mon Sep 17 00:00:00 2001 From: David Phillips Date: Fri, 3 Nov 2023 15:56:33 -0700 Subject: [PATCH 055/587] Run Hudi S3 tests using native file system --- .../java/io/trino/plugin/hudi/S3HudiQueryRunner.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/S3HudiQueryRunner.java b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/S3HudiQueryRunner.java index 8f29552f6c80..ac470442c84b 100644 --- a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/S3HudiQueryRunner.java +++ b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/S3HudiQueryRunner.java @@ -43,6 +43,7 @@ import static io.trino.plugin.hive.TestingThriftHiveMetastoreBuilder.testingThriftHiveMetastoreBuilder; import static io.trino.testing.TestingSession.testSessionBuilder; import static io.trino.testing.containers.Minio.MINIO_ACCESS_KEY; +import static io.trino.testing.containers.Minio.MINIO_REGION; import static io.trino.testing.containers.Minio.MINIO_SECRET_KEY; import static org.apache.hudi.common.model.HoodieTableType.COPY_ON_WRITE; @@ -87,10 +88,13 @@ public static DistributedQueryRunner create( "hudi", "hudi", ImmutableMap.builder() - .put("hive.s3.aws-access-key", MINIO_ACCESS_KEY) - .put("hive.s3.aws-secret-key", MINIO_SECRET_KEY) - .put("hive.s3.endpoint", hiveMinioDataLake.getMinio().getMinioAddress()) - .put("hive.s3.path-style-access", "true") + .put("fs.hadoop.enabled", "false") + .put("fs.native-s3.enabled", "true") + .put("s3.aws-access-key", MINIO_ACCESS_KEY) + .put("s3.aws-secret-key", MINIO_SECRET_KEY) + .put("s3.region", MINIO_REGION) + .put("s3.endpoint", hiveMinioDataLake.getMinio().getMinioAddress()) + .put("s3.path-style-access", "true") .putAll(connectorProperties) .buildOrThrow()); From b235549f5f176130640d1cd4740d10fc9603b108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Wa=C5=9B?= Date: Sat, 4 Nov 2023 10:06:07 +0100 Subject: [PATCH 056/587] Update the developer guide after last SPI changes --- docs/src/main/sphinx/develop/connectors.md | 26 ++++++++++------------ 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/docs/src/main/sphinx/develop/connectors.md b/docs/src/main/sphinx/develop/connectors.md index b3612eb35a92..3574ac13276a 100644 --- a/docs/src/main/sphinx/develop/connectors.md +++ b/docs/src/main/sphinx/develop/connectors.md @@ -754,22 +754,22 @@ The following example creates a block for an `array(varchar)` column: private Block encodeArray(List names) { BlockBuilder builder = VARCHAR.createBlockBuilder(null, names.size()); - for (String name : names) { + blockBuilder.buildEntry(elementBuilder -> names.forEach(name -> { if (name == null) { - builder.appendNull(); + elementBuilder.appendNull(); } else { - VARCHAR.writeString(builder, name); + VARCHAR.writeString(elementBuilder, name); } - } + })); return builder.build(); } ``` -The following example creates a block for a `map(varchar, varchar)` column: +The following example creates a SqlMap object for a `map(varchar, varchar)` column: ```java -private Block encodeMap(Map map) +private SqlMap encodeMap(Map map) { MapType mapType = typeManager.getType(TypeSignature.mapType( VARCHAR.getTypeSignature(), @@ -780,18 +780,16 @@ private Block encodeMap(Map map) return values.build().getObject(0, Block.class); } BlockBuilder builder = values.beginBlockEntry(); - for (Map.Entry entry : map.entrySet()) { - VARCHAR.writeString(builder, entry.getKey()); - Object value = entry.getValue(); + builder.buildEntry((keyBuilder, valueBuilder) -> map.foreach((key, value) -> { + VARCHAR.writeString(keyBuilder, key); if (value == null) { - builder.appendNull(); + valueBuilder.appendNull(); } else { - VARCHAR.writeString(builder, value.toString()); + VARCHAR.writeString(valueBuilder, value.toString()); } - } - values.closeEntry(); - return values.build().getObject(0, Block.class); + })); + return values.build().getObject(0, SqlMap.class); } ``` From bc3f8654e19dc432f34421670c3286a3c1af9ec3 Mon Sep 17 00:00:00 2001 From: Marius Grama Date: Mon, 6 Nov 2023 12:20:19 +0100 Subject: [PATCH 057/587] Remove outdated TODO comment --- .../main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java index 75aed4c98c62..fc337cdbeb1c 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java @@ -2343,7 +2343,6 @@ private ProtocolEntry protocolEntryForNewTable(boolean containsTimestampType, Ma } ColumnMappingMode columnMappingMode = DeltaLakeTableProperties.getColumnMappingMode(properties); if (columnMappingMode == ID || columnMappingMode == NAME) { - // TODO Add 'columnMapping' feature to reader and writer features when supporting writer version 7 readerVersion = max(readerVersion, COLUMN_MAPPING_MODE_SUPPORTED_READER_VERSION); writerVersion = max(writerVersion, COLUMN_MAPPING_MODE_SUPPORTED_WRITER_VERSION); } From d6568a19011c71c5e6ad236bfbd070f1d2892e69 Mon Sep 17 00:00:00 2001 From: Marcin Rusek Date: Thu, 19 Oct 2023 13:04:40 +0200 Subject: [PATCH 058/587] Remove unused parameters --- .../client/BackpressureRestHighLevelClient.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/client/BackpressureRestHighLevelClient.java b/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/client/BackpressureRestHighLevelClient.java index 2d6dc0ec3f6a..d7c7c1f5cd3d 100644 --- a/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/client/BackpressureRestHighLevelClient.java +++ b/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/client/BackpressureRestHighLevelClient.java @@ -23,7 +23,6 @@ import io.airlift.log.Logger; import io.airlift.stats.TimeStat; import io.trino.plugin.elasticsearch.ElasticsearchConfig; -import org.apache.http.Header; import org.elasticsearch.ElasticsearchStatusException; import org.elasticsearch.action.ActionResponse; import org.elasticsearch.action.search.ClearScrollRequest; @@ -84,22 +83,22 @@ public void close() delegate.close(); } - public SearchResponse search(SearchRequest searchRequest, Header... headers) + public SearchResponse search(SearchRequest searchRequest) throws IOException { - return executeWithRetries(() -> delegate.search(searchRequest, headers)); + return executeWithRetries(() -> delegate.search(searchRequest)); } - public SearchResponse searchScroll(SearchScrollRequest searchScrollRequest, Header... headers) + public SearchResponse searchScroll(SearchScrollRequest searchScrollRequest) throws IOException { - return executeWithRetries(() -> delegate.searchScroll(searchScrollRequest, headers)); + return executeWithRetries(() -> delegate.searchScroll(searchScrollRequest)); } - public ClearScrollResponse clearScroll(ClearScrollRequest clearScrollRequest, Header... headers) + public ClearScrollResponse clearScroll(ClearScrollRequest clearScrollRequest) throws IOException { - return executeWithRetries(() -> delegate.clearScroll(clearScrollRequest, headers)); + return executeWithRetries(() -> delegate.clearScroll(clearScrollRequest)); } private static boolean isBackpressure(Throwable throwable) From b5818a7e9797529f60adfebc43942e22f9db4822 Mon Sep 17 00:00:00 2001 From: Marcin Rusek Date: Thu, 19 Oct 2023 14:31:04 +0200 Subject: [PATCH 059/587] Remove usage of deprecated methods --- .../client/BackpressureRestClient.java | 36 +++++++++++++++++-- .../BackpressureRestHighLevelClient.java | 7 ++-- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/client/BackpressureRestClient.java b/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/client/BackpressureRestClient.java index a0ed13363878..eebc163c2f4d 100644 --- a/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/client/BackpressureRestClient.java +++ b/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/client/BackpressureRestClient.java @@ -26,6 +26,9 @@ import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpHost; +import org.elasticsearch.client.Node; +import org.elasticsearch.client.Request; +import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.Response; import org.elasticsearch.client.ResponseException; import org.elasticsearch.client.RestClient; @@ -36,7 +39,9 @@ import static com.google.common.base.Throwables.throwIfInstanceOf; import static com.google.common.base.Throwables.throwIfUnchecked; +import static com.google.common.collect.ImmutableList.toImmutableList; import static java.time.temporal.ChronoUnit.MILLIS; +import static java.util.Arrays.stream; import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -67,19 +72,44 @@ public BackpressureRestClient(RestClient delegate, ElasticsearchConfig config, T public void setHosts(HttpHost... hosts) { - delegate.setHosts(hosts); + delegate.setNodes(stream(hosts) + .map(Node::new) + .collect(toImmutableList())); } public Response performRequest(String method, String endpoint, Header... headers) throws IOException { - return executeWithRetries(() -> delegate.performRequest(method, endpoint, headers)); + return executeWithRetries(() -> delegate.performRequest(toRequest(method, endpoint, headers))); } public Response performRequest(String method, String endpoint, Map params, HttpEntity entity, Header... headers) throws IOException { - return executeWithRetries(() -> delegate.performRequest(method, endpoint, params, entity, headers)); + return executeWithRetries(() -> delegate.performRequest(toRequest(method, endpoint, params, entity, headers))); + } + + private static Request toRequest(String method, String endpoint, Map params, HttpEntity entity, Header... headers) + { + Request request = toRequest(method, endpoint, headers); + requireNonNull(params, "parameters cannot be null"); + for (Map.Entry entry : params.entrySet()) { + request.addParameter(entry.getKey(), entry.getValue()); + } + request.setEntity(entity); + return request; + } + + private static Request toRequest(String method, String endpoint, Header... headers) + { + requireNonNull(headers, "headers cannot be null"); + Request request = new Request(method, endpoint); + RequestOptions.Builder options = request.getOptions().toBuilder(); + for (Header header : headers) { + options.addHeader(header.getName(), header.getValue()); + } + request.setOptions(options); + return request; } public void close() diff --git a/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/client/BackpressureRestHighLevelClient.java b/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/client/BackpressureRestHighLevelClient.java index d7c7c1f5cd3d..391283144cbe 100644 --- a/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/client/BackpressureRestHighLevelClient.java +++ b/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/client/BackpressureRestHighLevelClient.java @@ -30,6 +30,7 @@ import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchScrollRequest; +import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestClientBuilder; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.rest.RestStatus; @@ -86,19 +87,19 @@ public void close() public SearchResponse search(SearchRequest searchRequest) throws IOException { - return executeWithRetries(() -> delegate.search(searchRequest)); + return executeWithRetries(() -> delegate.search(searchRequest, RequestOptions.DEFAULT)); } public SearchResponse searchScroll(SearchScrollRequest searchScrollRequest) throws IOException { - return executeWithRetries(() -> delegate.searchScroll(searchScrollRequest)); + return executeWithRetries(() -> delegate.scroll(searchScrollRequest, RequestOptions.DEFAULT)); } public ClearScrollResponse clearScroll(ClearScrollRequest clearScrollRequest) throws IOException { - return executeWithRetries(() -> delegate.clearScroll(clearScrollRequest)); + return executeWithRetries(() -> delegate.clearScroll(clearScrollRequest, RequestOptions.DEFAULT)); } private static boolean isBackpressure(Throwable throwable) From 6968931472604626f426e8651c3d5c5ea5d699ba Mon Sep 17 00:00:00 2001 From: Alex Jo Date: Mon, 18 Sep 2023 14:35:04 -0400 Subject: [PATCH 060/587] Support altering table comments in Hive Glue catalogs Additionally, move the comment storage from a parameter to the description field. --- .../metastore/glue/GlueHiveMetastore.java | 7 +++++- .../glue/converter/GlueInputConverter.java | 11 ++++++++- .../glue/converter/GlueToTrinoConverter.java | 12 +++++++++- .../metastore/glue/TestHiveGlueMetastore.java | 23 +++++++++++++++++++ 4 files changed, 50 insertions(+), 3 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java index 98ff48bd4664..dcbe8eaac53f 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java @@ -145,6 +145,7 @@ import static io.trino.plugin.hive.HiveErrorCode.HIVE_FILESYSTEM_ERROR; import static io.trino.plugin.hive.HiveErrorCode.HIVE_INVALID_METADATA; import static io.trino.plugin.hive.HiveErrorCode.HIVE_METASTORE_ERROR; +import static io.trino.plugin.hive.HiveMetadata.TABLE_COMMENT; import static io.trino.plugin.hive.TableType.MANAGED_TABLE; import static io.trino.plugin.hive.TableType.VIRTUAL_VIEW; import static io.trino.plugin.hive.metastore.MetastoreUtil.makePartitionName; @@ -684,7 +685,11 @@ private TableInput convertGlueTableToTableInput(com.amazonaws.services.glue.mode @Override public void commentTable(String databaseName, String tableName, Optional comment) { - throw new TrinoException(NOT_SUPPORTED, "Table comment is not yet supported by Glue service"); + Table oldTable = getExistingTable(databaseName, tableName); + Table newTable = Table.builder(oldTable) + .setParameter(TABLE_COMMENT, comment) + .build(); + replaceTable(databaseName, tableName, newTable, null); } @Override diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/converter/GlueInputConverter.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/converter/GlueInputConverter.java index 4f600006fdbc..994466d64f89 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/converter/GlueInputConverter.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/converter/GlueInputConverter.java @@ -36,9 +36,12 @@ import io.trino.spi.function.LanguageFunction; import java.util.List; +import java.util.Map; import java.util.Optional; import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static io.trino.plugin.hive.HiveMetadata.TABLE_COMMENT; import static io.trino.plugin.hive.metastore.thrift.ThriftMetastoreUtil.metastoreFunctionName; import static io.trino.plugin.hive.metastore.thrift.ThriftMetastoreUtil.toResourceUris; import static io.trino.plugin.hive.metastore.thrift.ThriftMetastoreUtil.updateStatisticsParameters; @@ -61,15 +64,21 @@ public static DatabaseInput convertDatabase(Database database) public static TableInput convertTable(Table table) { + Optional comment = Optional.ofNullable(table.getParameters().get(TABLE_COMMENT)); + Map tableParameters = table.getParameters().entrySet().stream() + .filter(entry -> !entry.getKey().equals(TABLE_COMMENT)) + .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)); + TableInput input = new TableInput(); input.setName(table.getTableName()); input.setOwner(table.getOwner().orElse(null)); input.setTableType(table.getTableType()); input.setStorageDescriptor(convertStorage(table.getStorage(), table.getDataColumns())); input.setPartitionKeys(table.getPartitionColumns().stream().map(GlueInputConverter::convertColumn).collect(toImmutableList())); - input.setParameters(table.getParameters()); + input.setParameters(tableParameters); table.getViewOriginalText().ifPresent(input::setViewOriginalText); table.getViewExpandedText().ifPresent(input::setViewExpandedText); + comment.ifPresent(input::setDescription); return input; } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/converter/GlueToTrinoConverter.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/converter/GlueToTrinoConverter.java index 454402fdafc4..0e0f9b8707d1 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/converter/GlueToTrinoConverter.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/converter/GlueToTrinoConverter.java @@ -53,6 +53,7 @@ import static com.google.common.base.Strings.nullToEmpty; import static io.trino.plugin.hive.HiveErrorCode.HIVE_INVALID_METADATA; import static io.trino.plugin.hive.HiveErrorCode.HIVE_UNSUPPORTED_FORMAT; +import static io.trino.plugin.hive.HiveMetadata.TABLE_COMMENT; import static io.trino.plugin.hive.HiveType.HIVE_INT; import static io.trino.plugin.hive.TableType.EXTERNAL_TABLE; import static io.trino.plugin.hive.ViewReaderUtil.isTrinoMaterializedView; @@ -126,7 +127,16 @@ public static Table convertTable(com.amazonaws.services.glue.model.Table glueTab SchemaTableName table = new SchemaTableName(dbName, glueTable.getName()); String tableType = getTableType(glueTable); - Map tableParameters = ImmutableMap.copyOf(getTableParameters(glueTable)); + + ImmutableMap.Builder parameters = ImmutableMap.builder(); + Optional description = Optional.ofNullable(glueTable.getDescription()); + description.ifPresent(comment -> parameters.put(TABLE_COMMENT, comment)); + getTableParameters(glueTable).entrySet().stream() + // If the description was set we may have two "comment"s, prefer the description field + .filter(entry -> description.isEmpty() || !entry.getKey().equals(TABLE_COMMENT)) + .forEach(parameters::put); + Map tableParameters = parameters.buildOrThrow(); + Table.Builder tableBuilder = Table.builder() .setDatabaseName(table.getSchemaName()) .setTableName(table.getTableName()) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestHiveGlueMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestHiveGlueMetastore.java index 27431383aec2..d806f681d16b 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestHiveGlueMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestHiveGlueMetastore.java @@ -1419,6 +1419,29 @@ public void testGlueObjectsWithoutStorageDescriptor() } } + @Test + public void testAlterTableComment() + throws Exception + { + SchemaTableName tableName = temporaryTable("test_alter_table_comment"); + doCreateEmptyTable(tableName, ORC, ImmutableList.of(new ColumnMetadata("name", BIGINT)), ImmutableList.of()); + try { + assertThat(metastore.getTable(tableName.getSchemaName(), tableName.getTableName()).orElseThrow().getParameters()).doesNotContainKey(TABLE_COMMENT); + metastore.commentTable(tableName.getSchemaName(), tableName.getTableName(), Optional.of("a table comment")); + Map tableParameters = metastore.getTable(tableName.getSchemaName(), tableName.getTableName()).orElseThrow().getParameters(); + assertThat(tableParameters.get(TABLE_COMMENT)).isEqualTo("a table comment"); + + metastore.commentTable(tableName.getSchemaName(), tableName.getTableName(), Optional.empty()); + tableParameters = metastore.getTable(tableName.getSchemaName(), tableName.getTableName()).orElseThrow().getParameters(); + assertThat(tableParameters.get(TABLE_COMMENT)).isNull(); + } + finally { + glueClient.deleteTable(new DeleteTableRequest() + .withDatabaseName(tableName.getSchemaName()) + .withName(tableName.getTableName())); + } + } + @Test public void testAlterColumnComment() throws Exception From 94f12c3811d66733b0c74e142ddb9bf62afa7d48 Mon Sep 17 00:00:00 2001 From: lukasz-stec Date: Mon, 30 Oct 2023 13:51:58 +0100 Subject: [PATCH 061/587] Extract DriverContextBuilder --- .../operator/output/TestPagePartitioner.java | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/core/trino-main/src/test/java/io/trino/operator/output/TestPagePartitioner.java b/core/trino-main/src/test/java/io/trino/operator/output/TestPagePartitioner.java index 7dee87dfe24d..adb93a54e022 100644 --- a/core/trino-main/src/test/java/io/trino/operator/output/TestPagePartitioner.java +++ b/core/trino-main/src/test/java/io/trino/operator/output/TestPagePartitioner.java @@ -533,9 +533,8 @@ public void partitionPage(PagePartitioner pagePartitioner, Page page) public static class PagePartitionerBuilder { public static final PositionsAppenderFactory POSITIONS_APPENDER_FACTORY = new PositionsAppenderFactory(new BlockTypeOperators()); - private final ExecutorService executor; - private final ScheduledExecutorService scheduledExecutor; private final OutputBuffer outputBuffer; + private final DriverContextBuilder driverContextBuilder; private ImmutableList partitionChannels = ImmutableList.of(0); private List> partitionConstants = ImmutableList.of(); @@ -547,9 +546,8 @@ public static class PagePartitionerBuilder PagePartitionerBuilder(ExecutorService executor, ScheduledExecutorService scheduledExecutor, OutputBuffer outputBuffer) { - this.executor = requireNonNull(executor, "executor is null"); - this.scheduledExecutor = requireNonNull(scheduledExecutor, "scheduledExecutor is null"); this.outputBuffer = requireNonNull(outputBuffer, "outputBuffer is null"); + this.driverContextBuilder = new DriverContextBuilder(executor, scheduledExecutor); } public PagePartitionerBuilder withPartitionChannels(Integer... partitionChannels) @@ -621,7 +619,7 @@ public PagePartitionerBuilder withMemoryContext(AggregatedMemoryContext memoryCo public PartitionedOutputOperator buildPartitionedOutputOperator() { - DriverContext driverContext = buildDriverContext(); + DriverContext driverContext = driverContextBuilder.buildDriverContext(); OutputFactory operatorFactory = new PartitionedOutputOperator.PartitionedOutputFactory( partitionFunction, @@ -645,7 +643,7 @@ public PartitionedOutputOperator buildPartitionedOutputOperator() public PagePartitioner build() { - DriverContext driverContext = buildDriverContext(); + DriverContext driverContext = driverContextBuilder.buildDriverContext(); OperatorContext operatorContext = driverContext.addOperatorContext(0, new PlanNodeId("plan-node-0"), PartitionedOutputOperator.class.getSimpleName()); @@ -667,8 +665,20 @@ public PagePartitioner build() return pagePartitioner; } + } + + public static class DriverContextBuilder + { + private final ExecutorService executor; + private final ScheduledExecutorService scheduledExecutor; + + DriverContextBuilder(ExecutorService executor, ScheduledExecutorService scheduledExecutor) + { + this.executor = requireNonNull(executor, "executor is null"); + this.scheduledExecutor = requireNonNull(scheduledExecutor, "scheduledExecutor is null"); + } - private DriverContext buildDriverContext() + public DriverContext buildDriverContext() { return TestingTaskContext.builder(executor, scheduledExecutor, TEST_SESSION) .setMemoryPoolSize(MAX_MEMORY) From 7cc3be5adc27c19619aeb4a0ab828a8a46de4d1e Mon Sep 17 00:00:00 2001 From: lukasz-stec Date: Mon, 30 Oct 2023 13:43:21 +0100 Subject: [PATCH 062/587] Update `PartitionedOutputOperator` output metrics on `addInput` `PartitionedOutputOperator` partition's input pages into `PagePartitioner` shared between operators. Since `PagePartitioner` can be flushed to output buffers on noMoreOperators, and by that time all operators can be finished, we need to update the operator metrics on addInput to get them propagated to the OperatorSummary inside the `PipelineStats`. --- .../operator/output/PagePartitioner.java | 65 +++++-------------- .../output/PartitionedOutputOperator.java | 50 +++++++------- .../io/trino/operator/TestOperatorStats.java | 4 +- .../operator/output/TestPagePartitioner.java | 17 +++-- .../output/TestPartitionedOutputOperator.java | 1 - 5 files changed, 48 insertions(+), 89 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/operator/output/PagePartitioner.java b/core/trino-main/src/main/java/io/trino/operator/output/PagePartitioner.java index 78e4a2ff12f5..4a58724a729a 100644 --- a/core/trino-main/src/main/java/io/trino/operator/output/PagePartitioner.java +++ b/core/trino-main/src/main/java/io/trino/operator/output/PagePartitioner.java @@ -41,14 +41,11 @@ import java.util.Objects; import java.util.Optional; import java.util.OptionalInt; -import java.util.concurrent.atomic.AtomicLong; import java.util.function.IntUnaryOperator; -import java.util.function.Supplier; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Verify.verify; import static io.trino.execution.buffer.PageSplitterUtil.splitPage; -import static io.trino.operator.output.PartitionedOutputOperator.PartitionedOutputInfo; import static io.trino.spi.block.PageBuilderStatus.DEFAULT_MAX_PAGE_SIZE_IN_BYTES; import static java.lang.Math.max; import static java.lang.Math.min; @@ -70,7 +67,6 @@ public class PagePartitioner private final boolean replicatesAnyRow; private final boolean partitionProcessRleAndDictionaryBlocks; private final int nullChannel; // when >= 0, send the position to every partition if this channel is null - private PartitionedOutputInfoSupplier partitionedOutputInfoSupplier; private boolean hasAnyRowBeenReplicated; @@ -132,20 +128,14 @@ public PartitionFunction getPartitionFunction() return partitionFunction; } - // sets up this partitioner for the new operator - public void setupOperator(OperatorContext operatorContext) - { - // for new operator we need to reset the stats gathered by this PagePartitioner - partitionedOutputInfoSupplier = new PartitionedOutputInfoSupplier(outputBuffer, operatorContext); - operatorContext.setInfoSupplier(partitionedOutputInfoSupplier); - } - - public void partitionPage(Page page) + public void partitionPage(Page page, OperatorContext operatorContext) { if (page.getPositionCount() == 0) { return; } + int outputPositionCount = replicatesAnyRow && !hasAnyRowBeenReplicated ? page.getPositionCount() + positionsAppenders.length - 1 : page.getPositionCount(); + long positionsAppendersSizeBefore = getPositionsAppendersSizeInBytes(); if (page.getPositionCount() < partitionFunction.partitionCount() * COLUMNAR_STRATEGY_COEFFICIENT) { // Partition will have on average less than COLUMNAR_STRATEGY_COEFFICIENT rows. // Doing it column-wise would degrade performance, so we fall back to row-wise approach. @@ -156,7 +146,11 @@ public void partitionPage(Page page) else { partitionPageByColumn(page); } + long positionsAppendersSizeAfter = getPositionsAppendersSizeInBytes(); + flushPositionsAppenders(false); updateMemoryUsage(); + + operatorContext.recordOutput(positionsAppendersSizeAfter - positionsAppendersSizeBefore, outputPositionCount); } public void partitionPageByRow(Page page) @@ -201,8 +195,6 @@ public void partitionPageByRow(Page page) positionsAppenders[partition].appendToOutputPartition(page, position); } } - - flushPositionsAppenders(false); } public void partitionPageByColumn(Page page) @@ -216,8 +208,15 @@ public void partitionPageByColumn(Page page) partitionPositions.clear(); } } + } - flushPositionsAppenders(false); + private long getPositionsAppendersSizeInBytes() + { + long sizeInBytes = 0; + for (PositionsAppenderPageBuilder pageBuilder : positionsAppenders) { + sizeInBytes += pageBuilder.getSizeInBytes(); + } + return sizeInBytes; } private IntArrayList[] partitionPositions(Page page) @@ -448,7 +447,6 @@ private void flushPositionsAppenders(boolean force) private void enqueuePage(Page pagePartition, int partition) { outputBuffer.enqueue(partition, splitAndSerializePage(pagePartition)); - partitionedOutputInfoSupplier.recordPage(pagePartition); } private List splitAndSerializePage(Page page) @@ -470,37 +468,4 @@ private void updateMemoryUsage() retainedSizeInBytes += serializer.getRetainedSizeInBytes(); memoryContext.setBytes(retainedSizeInBytes); } - - /** - * Keeps statistics about output pages produced by the partitioner + updates the stats in the operatorContext. - */ - private static class PartitionedOutputInfoSupplier - implements Supplier - { - private final AtomicLong rowsAdded = new AtomicLong(); - private final AtomicLong pagesAdded = new AtomicLong(); - private final OutputBuffer outputBuffer; - private final OperatorContext operatorContext; - - private PartitionedOutputInfoSupplier(OutputBuffer outputBuffer, OperatorContext operatorContext) - { - this.outputBuffer = requireNonNull(outputBuffer, "outputBuffer is null"); - this.operatorContext = requireNonNull(operatorContext, "operatorContext is null"); - } - - @Override - public PartitionedOutputInfo get() - { - // note that outputBuffer.getPeakMemoryUsage() will produce peak across many operators - // this is suboptimal but hard to fix properly - return new PartitionedOutputInfo(rowsAdded.get(), pagesAdded.get(), outputBuffer.getPeakMemoryUsage()); - } - - public void recordPage(Page pagePartition) - { - operatorContext.recordOutput(pagePartition.getSizeInBytes(), pagePartition.getPositionCount()); - pagesAdded.incrementAndGet(); - rowsAdded.addAndGet(pagePartition.getPositionCount()); - } - } } diff --git a/core/trino-main/src/main/java/io/trino/operator/output/PartitionedOutputOperator.java b/core/trino-main/src/main/java/io/trino/operator/output/PartitionedOutputOperator.java index be23b52ec017..0bc28fee8330 100644 --- a/core/trino-main/src/main/java/io/trino/operator/output/PartitionedOutputOperator.java +++ b/core/trino-main/src/main/java/io/trino/operator/output/PartitionedOutputOperator.java @@ -38,6 +38,7 @@ import java.util.Optional; import java.util.OptionalInt; import java.util.function.Function; +import java.util.function.Supplier; import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.base.Preconditions.checkState; @@ -270,7 +271,7 @@ public PartitionedOutputOperator( this.outputBuffer = requireNonNull(outputBuffer, "outputBuffer is null"); this.pagePartitioner = requireNonNull(pagePartitionerPool.poll(), "pagePartitioner is null"); this.skewedPartitionRebalancer = requireNonNull(skewedPartitionRebalancer, "skewedPartitionRebalancer is null"); - this.pagePartitioner.setupOperator(operatorContext); + operatorContext.setInfoSupplier(new PartitionedOutputInfoSupplier(outputBuffer)); } @Override @@ -332,7 +333,7 @@ public void addInput(Page page) } page = pagePreprocessor.apply(page); - pagePartitioner.partitionPage(page); + pagePartitioner.partitionPage(page, operatorContext); // Rebalance skewed partitions in the case of scale writer hash partitioning if (skewedPartitionRebalancer.isPresent()) { @@ -356,34 +357,34 @@ public Page getOutput() return null; } - public static class PartitionedOutputInfo - implements Mergeable, OperatorInfo + public static class PartitionedOutputInfoSupplier + implements Supplier { - private final long rowsAdded; - private final long pagesAdded; - private final long outputBufferPeakMemoryUsage; + private final OutputBuffer outputBuffer; - @JsonCreator - public PartitionedOutputInfo( - @JsonProperty("rowsAdded") long rowsAdded, - @JsonProperty("pagesAdded") long pagesAdded, - @JsonProperty("outputBufferPeakMemoryUsage") long outputBufferPeakMemoryUsage) + PartitionedOutputInfoSupplier(OutputBuffer outputBuffer) { - this.rowsAdded = rowsAdded; - this.pagesAdded = pagesAdded; - this.outputBufferPeakMemoryUsage = outputBufferPeakMemoryUsage; + this.outputBuffer = requireNonNull(outputBuffer, "outputBuffer is null"); } - @JsonProperty - public long getRowsAdded() + @Override + public PartitionedOutputInfo get() { - return rowsAdded; + // note that outputBuffer.getPeakMemoryUsage() will produce peak across many operators + // this is suboptimal but hard to fix properly + return new PartitionedOutputInfo(outputBuffer.getPeakMemoryUsage()); } + } - @JsonProperty - public long getPagesAdded() + public static class PartitionedOutputInfo + implements Mergeable, OperatorInfo + { + private final long outputBufferPeakMemoryUsage; + + @JsonCreator + public PartitionedOutputInfo(@JsonProperty("outputBufferPeakMemoryUsage") long outputBufferPeakMemoryUsage) { - return pagesAdded; + this.outputBufferPeakMemoryUsage = outputBufferPeakMemoryUsage; } @JsonProperty @@ -395,10 +396,7 @@ public long getOutputBufferPeakMemoryUsage() @Override public PartitionedOutputInfo mergeWith(PartitionedOutputInfo other) { - return new PartitionedOutputInfo( - rowsAdded + other.rowsAdded, - pagesAdded + other.pagesAdded, - Math.max(outputBufferPeakMemoryUsage, other.outputBufferPeakMemoryUsage)); + return new PartitionedOutputInfo(Math.max(outputBufferPeakMemoryUsage, other.outputBufferPeakMemoryUsage)); } @Override @@ -411,8 +409,6 @@ public boolean isFinal() public String toString() { return toStringHelper(this) - .add("rowsAdded", rowsAdded) - .add("pagesAdded", pagesAdded) .add("outputBufferPeakMemoryUsage", outputBufferPeakMemoryUsage) .toString(); } diff --git a/core/trino-main/src/test/java/io/trino/operator/TestOperatorStats.java b/core/trino-main/src/test/java/io/trino/operator/TestOperatorStats.java index 3fa52cfe4de9..a57a2acfa48b 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestOperatorStats.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestOperatorStats.java @@ -34,7 +34,7 @@ public class TestOperatorStats { private static final SplitOperatorInfo NON_MERGEABLE_INFO = new SplitOperatorInfo(TEST_CATALOG_HANDLE, "some_info"); - private static final PartitionedOutputInfo MERGEABLE_INFO = new PartitionedOutputInfo(1, 2, 1024); + private static final PartitionedOutputInfo MERGEABLE_INFO = new PartitionedOutputInfo(1024); public static final OperatorStats EXPECTED = new OperatorStats( 0, @@ -287,6 +287,6 @@ public void testAddMergeable() assertEquals(actual.getPeakTotalMemoryReservation(), DataSize.ofBytes(25)); assertEquals(actual.getSpilledDataSize(), DataSize.ofBytes(3 * 26)); assertEquals(actual.getInfo().getClass(), PartitionedOutputInfo.class); - assertEquals(((PartitionedOutputInfo) actual.getInfo()).getPagesAdded(), 3 * MERGEABLE_INFO.getPagesAdded()); + assertEquals(((PartitionedOutputInfo) actual.getInfo()).getOutputBufferPeakMemoryUsage(), MERGEABLE_INFO.getOutputBufferPeakMemoryUsage()); } } diff --git a/core/trino-main/src/test/java/io/trino/operator/output/TestPagePartitioner.java b/core/trino-main/src/test/java/io/trino/operator/output/TestPagePartitioner.java index adb93a54e022..f8205128dff9 100644 --- a/core/trino-main/src/test/java/io/trino/operator/output/TestPagePartitioner.java +++ b/core/trino-main/src/test/java/io/trino/operator/output/TestPagePartitioner.java @@ -144,13 +144,19 @@ public void testOutputForEmptyPage() PagePartitioner pagePartitioner = pagePartitioner(BIGINT).build(); Page page = new Page(createLongsBlock(ImmutableList.of())); - pagePartitioner.partitionPage(page); + pagePartitioner.partitionPage(page, operatorContext()); pagePartitioner.close(); List partitioned = readLongs(outputBuffer.getEnqueuedDeserialized(), 0); assertThat(partitioned).isEmpty(); } + private OperatorContext operatorContext() + { + return new DriverContextBuilder(executor, scheduledExecutor).buildDriverContext() + .addOperatorContext(0, new PlanNodeId("plan-node-0"), PartitionedOutputOperator.class.getSimpleName()); + } + @Test(dataProvider = "partitioningMode") public void testOutputEqualsInput(PartitioningMode partitioningMode) { @@ -643,11 +649,7 @@ public PartitionedOutputOperator buildPartitionedOutputOperator() public PagePartitioner build() { - DriverContext driverContext = driverContextBuilder.buildDriverContext(); - - OperatorContext operatorContext = driverContext.addOperatorContext(0, new PlanNodeId("plan-node-0"), PartitionedOutputOperator.class.getSimpleName()); - - PagePartitioner pagePartitioner = new PagePartitioner( + return new PagePartitioner( partitionFunction, partitionChannels, partitionConstants, @@ -661,9 +663,6 @@ public PagePartitioner build() Optional.empty(), memoryContext, true); - pagePartitioner.setupOperator(operatorContext); - - return pagePartitioner; } } diff --git a/core/trino-main/src/test/java/io/trino/operator/output/TestPartitionedOutputOperator.java b/core/trino-main/src/test/java/io/trino/operator/output/TestPartitionedOutputOperator.java index f633663fe327..4934f4888a87 100644 --- a/core/trino-main/src/test/java/io/trino/operator/output/TestPartitionedOutputOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/output/TestPartitionedOutputOperator.java @@ -63,7 +63,6 @@ public void testOperatorContextStats() Page page = new Page(createLongSequenceBlock(0, 8)); partitionedOutputOperator.addInput(page); - partitionedOutputOperator.finish(); OperatorContext operatorContext = partitionedOutputOperator.getOperatorContext(); assertEquals(operatorContext.getOutputDataSize().getTotalCount(), page.getSizeInBytes()); From 70cf22daace6eb86434d0fa7e13c86d14ea9b9e2 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Mon, 6 Nov 2023 09:22:58 -0800 Subject: [PATCH 063/587] Fix reading ORC long decimal column containing only nulls Return correct block type when ORC long decimal column contains only nulls --- .../src/main/java/io/trino/orc/reader/DecimalColumnReader.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/trino-orc/src/main/java/io/trino/orc/reader/DecimalColumnReader.java b/lib/trino-orc/src/main/java/io/trino/orc/reader/DecimalColumnReader.java index 4f3d8cde5403..0274aba125a8 100644 --- a/lib/trino-orc/src/main/java/io/trino/orc/reader/DecimalColumnReader.java +++ b/lib/trino-orc/src/main/java/io/trino/orc/reader/DecimalColumnReader.java @@ -49,7 +49,6 @@ import static io.trino.orc.reader.ReaderUtils.unpackLongNulls; import static io.trino.orc.reader.ReaderUtils.verifyStreamType; import static io.trino.orc.stream.MissingInputStreamSource.missingStreamSource; -import static io.trino.spi.type.DoubleType.DOUBLE; import static java.util.Objects.requireNonNull; public class DecimalColumnReader @@ -132,7 +131,7 @@ else if (nullCount != nextBatchSize) { block = readNullBlock(isNull, nextBatchSize - nullCount); } else { - block = RunLengthEncodedBlock.create(DOUBLE, null, nextBatchSize); + block = RunLengthEncodedBlock.create(type, null, nextBatchSize); } } From 9853386f78437f39c2ea1d5a10efd0ac78f5e6a3 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Mon, 6 Nov 2023 09:24:08 -0800 Subject: [PATCH 064/587] Simplify ORC UUID all nulls column reader --- .../src/main/java/io/trino/orc/reader/UuidColumnReader.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/trino-orc/src/main/java/io/trino/orc/reader/UuidColumnReader.java b/lib/trino-orc/src/main/java/io/trino/orc/reader/UuidColumnReader.java index ce46c7ae87d7..738f5f37bb74 100644 --- a/lib/trino-orc/src/main/java/io/trino/orc/reader/UuidColumnReader.java +++ b/lib/trino-orc/src/main/java/io/trino/orc/reader/UuidColumnReader.java @@ -42,6 +42,7 @@ import static io.trino.orc.metadata.Stream.StreamKind.PRESENT; import static io.trino.orc.stream.MissingInputStreamSource.missingStreamSource; import static io.trino.spi.StandardErrorCode.GENERIC_INTERNAL_ERROR; +import static io.trino.spi.type.UuidType.UUID; import static java.lang.Math.toIntExact; import static java.lang.String.format; import static java.util.Objects.requireNonNull; @@ -256,7 +257,7 @@ private long[] readNonNullLongs(int valueCount) private Block createAllNullsBlock() { - return RunLengthEncodedBlock.create(new Int128ArrayBlock(1, Optional.of(new boolean[] {true}), new long[2]), nextBatchSize); + return RunLengthEncodedBlock.create(UUID, null, nextBatchSize); } private void openRowGroup() From 1bc7b73a193e4580bbcd52032b68d624b92466cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Wa=C5=9B?= Date: Fri, 27 Oct 2023 09:55:12 +0200 Subject: [PATCH 065/587] Document create/drop catalog --- .../main/sphinx/admin/properties-catalog.md | 85 +++++++++++++++++++ docs/src/main/sphinx/admin/properties.md | 1 + docs/src/main/sphinx/language/sql-support.md | 7 ++ docs/src/main/sphinx/sql.md | 2 + docs/src/main/sphinx/sql/create-catalog.md | 82 ++++++++++++++++++ docs/src/main/sphinx/sql/drop-catalog.md | 38 +++++++++ 6 files changed, 215 insertions(+) create mode 100644 docs/src/main/sphinx/admin/properties-catalog.md create mode 100644 docs/src/main/sphinx/sql/create-catalog.md create mode 100644 docs/src/main/sphinx/sql/drop-catalog.md diff --git a/docs/src/main/sphinx/admin/properties-catalog.md b/docs/src/main/sphinx/admin/properties-catalog.md new file mode 100644 index 000000000000..52dddec22617 --- /dev/null +++ b/docs/src/main/sphinx/admin/properties-catalog.md @@ -0,0 +1,85 @@ +# Catalog management properties + +(prop-catalog-management)= +## `catalog.management` + +- **Type:** [](prop-type-string) +- **Allowed values:** `static`, `dynamic` +- **Default value:** `static` + +When set to `static`, Trino reads catalog property files and configures +available catalogs only on server startup. When set to `dynamic`, catalog +configuration can also be managed using [](/sql/create-catalog) and +[](/sql/drop-catalog). New worker nodes joining the cluster receive the current +catalog configuration from the coordinator node. + +:::{warning} +This feature is experimental only. Because of the security implications the +syntax might change and be backward incompatible. +::: + +:::{warning} +Some connectors are known not to release all resources when dropping a catalog +that uses such connector. This includes all connectors that can read data from +HDFS, S3, GCS, or Azure, which are [](/connector/hive), +[](/connector/iceberg), [](/connector/delta-lake), and +[](/connector/hudi). +::: + +:::{warning} +The complete `CREATE CATALOG` query is logged, and visible in the [Web +UI](/admin/web-interface). This includes any sensitive properties, like +passwords and other credentials. See [](/security/secrets). +::: + +## `catalog.prune.update-interval` + +- **Type:** [](prop-type-duration) +- **Default value:** `5s` +- **Minimum value:** `1s` + +Requires [](prop-catalog-management) to be set to `dynamic`. Interval for +pruning dropped catalogs. Dropping a catalog does not interrupt any running +queries that use it, but makes it unavailable to any new queries. + +(prop-catalog-store)= +## `catalog.store` + +- **Type:** [](prop-type-string) +- **Allowed values:** `file`, `memory` +- **Default value:** `file` + +Requires [](prop-catalog-management) to be set to `dynamic`. When set to +`file`, creating and dropping catalogs using the SQL commands adds and removes +catalog property files on the coordinator node. Trino server process requires +write access in the catalog configuration directory. Existing catalog files are +also read on the coordinator startup. When set to `memory`, catalog +configuration is only managed in memory, and any existing files are ignored on +startup. + +## `catalog.config-dir` + +- **Type:** [](prop-type-string) +- **Default value:** `etc/catalog/` + +Requires [](prop-catalog-management) to be set to `static` or +[](prop-catalog-store) to be set to `file`. The directory with catalog property +files. + +## `catalog.disabled-catalogs` + +- **Type:** [](prop-type-string) + +Requires [](prop-catalog-management) to be set to `static` or +[](prop-catalog-store) to be set to `file`. Comma-separated list of catalogs to +ignore on startup. + +## `catalog.read-only` + +- **Type:** [](prop-type-string) +- **Default value:** `false` + +Requires [](prop-catalog-store) to be set to `file`. If true, existing catalog +property files cannot be removed with `DROP CATALOG`, and now new catalog files +can be written with identical names with `CREATE CATALOG`. As a result, a +coordinator restart resets the known catalogs to the existing files only. diff --git a/docs/src/main/sphinx/admin/properties.md b/docs/src/main/sphinx/admin/properties.md index c0965221a0ae..c039d5742fda 100644 --- a/docs/src/main/sphinx/admin/properties.md +++ b/docs/src/main/sphinx/admin/properties.md @@ -17,6 +17,7 @@ properties, refer to the {doc}`connector documentation `. General Resource management Query management +Catalog management SQL environment Spilling Exchange diff --git a/docs/src/main/sphinx/language/sql-support.md b/docs/src/main/sphinx/language/sql-support.md index 209bbd1a6924..a30b3d1c59d5 100644 --- a/docs/src/main/sphinx/language/sql-support.md +++ b/docs/src/main/sphinx/language/sql-support.md @@ -95,6 +95,13 @@ connector to connector: - {doc}`/sql/drop-materialized-view` - {doc}`/sql/refresh-materialized-view` +(sql-catalog-management)= + +### Catalog management + +- {doc}`/sql/create-catalog` +- {doc}`/sql/drop-catalog` + (sql-schema-table-management)= ### Schema and table management diff --git a/docs/src/main/sphinx/sql.md b/docs/src/main/sphinx/sql.md index da54bc0e1bea..cd138d289b44 100644 --- a/docs/src/main/sphinx/sql.md +++ b/docs/src/main/sphinx/sql.md @@ -19,6 +19,7 @@ sql/analyze sql/call sql/comment sql/commit +sql/create-catalog sql/create-function sql/create-materialized-view sql/create-role @@ -32,6 +33,7 @@ sql/deny sql/describe sql/describe-input sql/describe-output +sql/drop-catalog sql/drop-function sql/drop-materialized-view sql/drop-role diff --git a/docs/src/main/sphinx/sql/create-catalog.md b/docs/src/main/sphinx/sql/create-catalog.md new file mode 100644 index 000000000000..727e96d7e0e1 --- /dev/null +++ b/docs/src/main/sphinx/sql/create-catalog.md @@ -0,0 +1,82 @@ +# CREATE CATALOG + +## Synopsis + +```text +CREATE CATALOG +catalog_name +USING connector_name +[ WITH ( property_name = expression [, ...] ) ] +``` + +## Description + +Create a new catalog using the specified connector. + +The optional `WITH` clause is used to set properties on the newly created +catalog. Property names can be double quoted, which is required if they contain +special characters, like `-`. Refer to the [connectors +documentation](/connector) to learn about all available properties. All +property values must be varchars (single quoted), including numbers and boolean +values. + +The query fails in the following circumstances: + +* A required property is missing. +* An invalid property is set, for example there is a typo in the property name, + or a property name from a different connector was used. +* The value of the property is invalid, for example a numeric value is out of + range, or a string value doesn't match the required pattern. +* The value references an environmental variable that is not set on the + coordinator node. + +:::{warning} +The complete `CREATE CATALOG` query is logged, and visible in the [Web +UI](/admin/web-interface). This includes any sensitive properties, like +passwords and other credentials. See [](/security/secrets). +::: + +:::{note} +This command requires the [catalog management type](/admin/properties-catalog) +to be set to `dynamic`. +::: + +## Examples + +Create a new catalog called `tpch` using the [](/connector/tpch): + +```sql +CREATE CATALOG tpch USING tpch; +``` + +Create a new catalog called `brain` using the [](/connector/memory): + +```sql +CREATE CATALOG brain USING memory +WITH ("memory.max-data-per-node" = '128MB'); +``` + +Notice that the connector property contains dashes (`-`) and needs to quoted +using a double quote (`"`). The value `128MB` is quoted using single quotes, +because it is a string literal. + +Create a new catalog called `example` using the [](/connector/postgresql): + +```sql +CREATE CATALOG example USING postgresql +WITH ( + "connection-url" = 'jdbc:pg:localhost:5432', + "connection-user" = '${ENV:POSTGRES_USER}', + "connection-password" = '${ENV:POSTGRES_PASSWORD}', + "case-insensitive-name-matching" = 'true' +); +``` + +This example assumes that the `POSTGRES_USER` and `POSTGRES_PASSWORD` +environmental variables are set as [secrets](/security/secrets) on the +coordinator node. + +## See also + +* [](/sql/drop-catalog) +* [](/admin/properties-catalog) diff --git a/docs/src/main/sphinx/sql/drop-catalog.md b/docs/src/main/sphinx/sql/drop-catalog.md new file mode 100644 index 000000000000..3ec312172371 --- /dev/null +++ b/docs/src/main/sphinx/sql/drop-catalog.md @@ -0,0 +1,38 @@ +# DROP CATALOG + +## Synopsis + +```text +DROP CATALOG catalog_name +``` + +## Description + +Drops an existing catalog. Dropping a catalog does not interrupt any running +queries that use it, but makes it unavailable to any new queries. + +:::{warning} +Some connectors are known not to release all resources when dropping a catalog +that uses such connector. This includes all connectors that can read data from +HDFS, S3, GCS, or Azure, which are [](/connector/hive), +[](/connector/iceberg), [](/connector/delta-lake), and +[](/connector/hudi). +::: + +:::{note} +This command requires the [catalog management type](/admin/properties-catalog) +to be set to `dynamic`. +::: + +## Examples + +Drop the catalog `example`: + +``` +DROP CATALOG example; +``` + +## See also + +* [](/sql/create-catalog) +* [](/admin/properties-catalog) From a7eb3665748c75960f64d7cb72777f31657d88e0 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Mon, 6 Nov 2023 17:24:42 -0800 Subject: [PATCH 066/587] Fix incorrect activation of HDFS file system tests --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc8b47ee12e5..99091cda7e11 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -672,9 +672,9 @@ jobs: AWS_REGION: ${{ vars.TRINO_AWS_REGION }} S3_BUCKET: ${{ vars.TRINO_S3_BUCKET }} S3_BUCKET_ENDPOINT: "s3.${{ vars.TRINO_AWS_REGION }}.amazonaws.com" - if: >- - contains(matrix.modules, 'trino-hdfs') && contains(matrix.profile, 'cloud-tests') && - (env.CI_SKIP_SECRETS_PRESENCE_CHECKS != '' || env.AWS_ACCESS_KEY_ID != '' || env.AWS_SECRET_ACCESS_KEY != '') + if: >- + contains(matrix.modules, 'trino-hdfs') && contains(matrix.profile, 'cloud-tests') && + (env.CI_SKIP_SECRETS_PRESENCE_CHECKS != '' || env.AWS_ACCESS_KEY_ID != '' || env.AWS_SECRET_ACCESS_KEY != '') run: | $MAVEN test ${MAVEN_TEST} -pl :trino-hdfs -P cloud-tests - name: S3 FileSystem Cloud Tests From aefa96bd844a4bb050aa4c9c90c67fb0150a7a32 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Thu, 2 Nov 2023 21:38:48 -0700 Subject: [PATCH 067/587] Add missing TestSecurityConfig --- .../hive/security/TestSecurityConfig.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/security/TestSecurityConfig.java diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/security/TestSecurityConfig.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/security/TestSecurityConfig.java new file mode 100644 index 000000000000..4ed54cb27e09 --- /dev/null +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/security/TestSecurityConfig.java @@ -0,0 +1,49 @@ +/* + * 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 io.trino.plugin.hive.security; + +import com.google.common.collect.ImmutableMap; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Map; + +import static io.airlift.configuration.testing.ConfigAssertions.assertFullMapping; +import static io.airlift.configuration.testing.ConfigAssertions.assertRecordedDefaults; +import static io.airlift.configuration.testing.ConfigAssertions.recordDefaults; +import static io.trino.plugin.hive.security.HiveSecurityModule.LEGACY; + +public class TestSecurityConfig +{ + @Test + public void testDefaults() + { + assertRecordedDefaults(recordDefaults(SecurityConfig.class) + .setSecuritySystem(LEGACY)); + } + + @Test + public void testExplicitPropertyMappings() + throws IOException + { + Map properties = ImmutableMap.builder() + .put("hive.security", "something") + .buildOrThrow(); + + SecurityConfig expected = new SecurityConfig() + .setSecuritySystem("something"); + + assertFullMapping(properties, expected); + } +} From d50ece2b1f4131e1286638b3dc3f37a569e3ccba Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Thu, 2 Nov 2023 21:44:18 -0700 Subject: [PATCH 068/587] Remove unused HiveErrorCodes --- .../src/main/java/io/trino/plugin/hive/HiveErrorCode.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveErrorCode.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveErrorCode.java index 2018867e6381..fd766d6b2629 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveErrorCode.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveErrorCode.java @@ -35,14 +35,14 @@ public enum HiveErrorCode HIVE_PARTITION_SCHEMA_MISMATCH(8, EXTERNAL), // HIVE_MISSING_DATA(9, EXTERNAL) is deprecated HIVE_INVALID_PARTITION_VALUE(10, EXTERNAL), - HIVE_TIMEZONE_MISMATCH(11, EXTERNAL), + // HIVE_TIMEZONE_MISMATCH(11, EXTERNAL) is deprecated HIVE_INVALID_METADATA(12, EXTERNAL), HIVE_INVALID_VIEW_DATA(13, EXTERNAL), HIVE_DATABASE_LOCATION_ERROR(14, EXTERNAL), HIVE_PATH_ALREADY_EXISTS(15, EXTERNAL), HIVE_FILESYSTEM_ERROR(16, EXTERNAL), // code HIVE_WRITER_ERROR(17) is deprecated - HIVE_SERDE_NOT_FOUND(18, EXTERNAL), + // HIVE_SERDE_NOT_FOUND(18, EXTERNAL) is deprecated HIVE_UNSUPPORTED_FORMAT(19, EXTERNAL), HIVE_PARTITION_READ_ONLY(20, USER_ERROR), HIVE_TOO_MANY_OPEN_PARTITIONS(21, USER_ERROR), @@ -58,7 +58,7 @@ public enum HiveErrorCode HIVE_PARTITION_DROPPED_DURING_QUERY(31, EXTERNAL), HIVE_TABLE_READ_ONLY(32, USER_ERROR), HIVE_PARTITION_NOT_READABLE(33, USER_ERROR), - HIVE_TABLE_NOT_READABLE(34, USER_ERROR), + // HIVE_TABLE_NOT_READABLE(34, USER_ERROR) is deprecated HIVE_TABLE_DROPPED_DURING_QUERY(35, EXTERNAL), // HIVE_TOO_MANY_BUCKET_SORT_FILES(36) is deprecated HIVE_CORRUPTED_COLUMN_STATISTICS(37, EXTERNAL), From 046a29cf0ec200dd6380c57a9baa334613db1a51 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Thu, 2 Nov 2023 20:42:33 -0700 Subject: [PATCH 069/587] Remove unused method, field, and classes in Hive plugin --- .../io/trino/plugin/hive/HivePageSource.java | 5 ---- .../io/trino/plugin/hive/HiveTableHandle.java | 13 --------- .../plugin/hive/HiveTimestampPrecision.java | 1 - .../plugin/hive/PartitionAndStatementId.java | 15 ---------- .../plugin/hive/ReaderProjectionsAdapter.java | 10 +++---- .../hive/RoleAlreadyExistsException.java | 28 ------------------- .../SemiTransactionalHiveMetastore.java | 23 ++------------- .../trino/plugin/hive/orc/OrcPageSource.java | 7 ----- .../plugin/hive/util/SerdeConstants.java | 1 - .../trino/plugin/hive/AbstractTestHive.java | 7 ----- .../plugin/hive/BaseHiveConnectorTest.java | 22 ++------------- .../io/trino/plugin/hive/HiveTestUtils.java | 10 ------- .../hive/TestReaderProjectionsAdapter.java | 5 ---- .../plugin/hive/parquet/ParquetTester.java | 5 ---- .../io/trino/plugin/hive/util/SerDeUtils.java | 28 ------------------- 15 files changed, 8 insertions(+), 172 deletions(-) delete mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/RoleAlreadyExistsException.java diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePageSource.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePageSource.java index 9946d06940f9..b951f6527afd 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePageSource.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePageSource.java @@ -128,11 +128,6 @@ else if (columnMapping.getKind() == PREFILLED) { this.coercers = coercers.build(); } - public ConnectorPageSource getDelegate() - { - return delegate; - } - @Override public long getCompletedBytes() { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveTableHandle.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveTableHandle.java index 8df28e5c557a..6096cb8eb361 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveTableHandle.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveTableHandle.java @@ -420,19 +420,6 @@ public boolean isAcidMerge() return transaction.isMerge(); } - @JsonIgnore - public boolean isInAcidTransaction() - { - return transaction.isAcidTransactionRunning(); - } - - @JsonIgnore - public long getWriteId() - { - checkState(transaction.isAcidTransactionRunning(), "The AcidTransaction is not running"); - return transaction.getWriteId(); - } - @JsonIgnore public boolean isRecordScannedFiles() { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveTimestampPrecision.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveTimestampPrecision.java index 7bebcaabdac8..5d13f83709c9 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveTimestampPrecision.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveTimestampPrecision.java @@ -18,7 +18,6 @@ public enum HiveTimestampPrecision MILLISECONDS(3), MICROSECONDS(6), NANOSECONDS(9); public static final HiveTimestampPrecision DEFAULT_PRECISION = MILLISECONDS; - public static final HiveTimestampPrecision MAX = NANOSECONDS; private final int precision; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/PartitionAndStatementId.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/PartitionAndStatementId.java index 8a9e332ad760..0f91e36702d2 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/PartitionAndStatementId.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/PartitionAndStatementId.java @@ -14,12 +14,8 @@ package io.trino.plugin.hive; import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.collect.ImmutableList; -import io.airlift.json.JsonCodec; -import java.util.List; import java.util.Objects; import java.util.Optional; @@ -27,8 +23,6 @@ public class PartitionAndStatementId { - public static final JsonCodec CODEC = JsonCodec.jsonCodec(PartitionAndStatementId.class); - private final String partitionName; private final int statementId; private final long rowCount; @@ -80,15 +74,6 @@ public Optional getDeltaDirectory() return deltaDirectory; } - @JsonIgnore - public List getAllDirectories() - { - ImmutableList.Builder builder = ImmutableList.builder(); - deltaDirectory.ifPresent(builder::add); - deleteDeltaDirectory.ifPresent(builder::add); - return builder.build(); - } - @Override public boolean equals(Object o) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/ReaderProjectionsAdapter.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/ReaderProjectionsAdapter.java index f24a10a4f57e..0a02670b5742 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/ReaderProjectionsAdapter.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/ReaderProjectionsAdapter.java @@ -81,13 +81,13 @@ public Page adaptPage(@Nullable Page input) ChannelMapping mapping = outputToInputMapping.get(i); Block inputBlock = input.getBlock(mapping.getInputChannelIndex()); - blocks[i] = createAdaptedLazyBlock(inputBlock, mapping.getDereferenceSequence(), outputTypes.get(i)); + blocks[i] = createAdaptedLazyBlock(inputBlock, mapping.getDereferenceSequence()); } return new Page(input.getPositionCount(), blocks); } - private static Block createAdaptedLazyBlock(Block inputBlock, List dereferenceSequence, Type type) + private static Block createAdaptedLazyBlock(Block inputBlock, List dereferenceSequence) { if (dereferenceSequence.size() == 0) { return inputBlock; @@ -97,22 +97,20 @@ private static Block createAdaptedLazyBlock(Block inputBlock, List dere return null; } - return new LazyBlock(inputBlock.getPositionCount(), new DereferenceBlockLoader(inputBlock, dereferenceSequence, type)); + return new LazyBlock(inputBlock.getPositionCount(), new DereferenceBlockLoader(inputBlock, dereferenceSequence)); } private static class DereferenceBlockLoader implements LazyBlockLoader { private final List dereferenceSequence; - private final Type type; private boolean loaded; private Block inputBlock; - DereferenceBlockLoader(Block inputBlock, List dereferenceSequence, Type type) + DereferenceBlockLoader(Block inputBlock, List dereferenceSequence) { this.inputBlock = requireNonNull(inputBlock, "inputBlock is null"); this.dereferenceSequence = requireNonNull(dereferenceSequence, "dereferenceSequence is null"); - this.type = type; } @Override diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/RoleAlreadyExistsException.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/RoleAlreadyExistsException.java deleted file mode 100644 index 85b54d92945d..000000000000 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/RoleAlreadyExistsException.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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 io.trino.plugin.hive; - -import io.trino.spi.TrinoException; - -import static io.trino.spi.StandardErrorCode.ALREADY_EXISTS; -import static java.lang.String.format; - -public class RoleAlreadyExistsException - extends TrinoException -{ - public RoleAlreadyExistsException(String roleName) - { - super(ALREADY_EXISTS, format("Role already exists: '%s'", roleName), null); - } -} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/SemiTransactionalHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/SemiTransactionalHiveMetastore.java index ae16b8fde0c6..4320a8f94da0 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/SemiTransactionalHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/SemiTransactionalHiveMetastore.java @@ -791,8 +791,7 @@ public synchronized void finishMerge( table, Optional.of(principalPrivileges), Optional.of(currentLocation), - partitionUpdateAndMergeResults, - partitions), + partitionUpdateAndMergeResults), session.getIdentity(), session.getQueryId())); return; @@ -2949,18 +2948,11 @@ private static class TableAndMergeResults extends TableAndMore { private final List partitionMergeResults; - private final List partitions; - public TableAndMergeResults(Table table, Optional principalPrivileges, Optional currentLocation, List partitionMergeResults, List partitions) + public TableAndMergeResults(Table table, Optional principalPrivileges, Optional currentLocation, List partitionMergeResults) { super(table, principalPrivileges, currentLocation, Optional.empty(), false, PartitionStatistics.empty(), PartitionStatistics.empty(), false); // retries are not supported for transactional tables this.partitionMergeResults = requireNonNull(partitionMergeResults, "partitionMergeResults is null"); - this.partitions = requireNonNull(partitions, "partitions is nul"); - } - - public List getPartitions() - { - return partitions; } @Override @@ -2969,7 +2961,6 @@ public String toString() return toStringHelper(this) .add("table", getTable()) .add("partitionMergeResults", partitionMergeResults) - .add("partitions", partitions) .add("principalPrivileges", getPrincipalPrivileges()) .add("currentLocation", getCurrentLocation()) .toString(); @@ -3577,21 +3568,11 @@ public void updateTableWriteId(String dbName, String tableName, long transaction delegate.updateTableWriteId(dbName, tableName, transactionId, writeId, rowCountChange); } - public void alterPartitions(String dbName, String tableName, List partitions, long writeId) - { - delegate.alterPartitions(dbName, tableName, partitions, writeId); - } - public void addDynamicPartitions(String dbName, String tableName, List partitionNames, long transactionId, long writeId, AcidOperation operation) { delegate.addDynamicPartitions(dbName, tableName, partitionNames, transactionId, writeId, operation); } - public void commitTransaction(long transactionId) - { - delegate.commitTransaction(transactionId); - } - public static void cleanExtraOutputFiles(TrinoFileSystem fileSystem, String queryId, Location path, Set filesToKeep) { List filesToDelete = new ArrayList<>(); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcPageSource.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcPageSource.java index 9617f8edd126..542b767d1e0b 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcPageSource.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcPageSource.java @@ -22,9 +22,7 @@ import io.trino.orc.OrcDataSource; import io.trino.orc.OrcDataSourceId; import io.trino.orc.OrcRecordReader; -import io.trino.orc.metadata.ColumnMetadata; import io.trino.orc.metadata.CompressionKind; -import io.trino.orc.metadata.OrcType; import io.trino.plugin.base.metrics.LongCount; import io.trino.plugin.hive.FileFormatDataSourceStats; import io.trino.plugin.hive.coercions.TypeCoercer; @@ -134,11 +132,6 @@ public boolean isFinished() return closed; } - public ColumnMetadata getColumnTypes() - { - return recordReader.getColumnTypes(); - } - @Override public Page getNextPage() { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/SerdeConstants.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/SerdeConstants.java index e0d993c3cc72..cded4618b666 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/SerdeConstants.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/SerdeConstants.java @@ -19,7 +19,6 @@ public final class SerdeConstants public static final String SERIALIZATION_NULL_FORMAT = "serialization.null.format"; public static final String FIELD_DELIM = "field.delim"; - public static final String LINE_DELIM = "line.delim"; public static final String ESCAPE_CHAR = "escape.delim"; public static final String HEADER_COUNT = "skip.header.line.count"; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java index 427648bff612..bf835389f1a6 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java @@ -213,7 +213,6 @@ import static io.trino.plugin.hive.HiveBasicStatistics.createZeroStatistics; import static io.trino.plugin.hive.HiveColumnHandle.BUCKET_COLUMN_NAME; import static io.trino.plugin.hive.HiveColumnHandle.ColumnType.PARTITION_KEY; -import static io.trino.plugin.hive.HiveColumnHandle.ColumnType.REGULAR; import static io.trino.plugin.hive.HiveColumnHandle.bucketColumnHandle; import static io.trino.plugin.hive.HiveColumnHandle.createBaseColumn; import static io.trino.plugin.hive.HiveErrorCode.HIVE_INVALID_BUCKET_FILES; @@ -636,7 +635,6 @@ private static RowType toRowType(List columns) protected SchemaTableName tableBucketedDoubleFloat; protected SchemaTableName tablePartitionSchemaChange; protected SchemaTableName tablePartitionSchemaChangeNonCanonical; - protected SchemaTableName tableBucketEvolution; protected ConnectorTableHandle invalidTableHandle; @@ -644,12 +642,10 @@ private static RowType toRowType(List columns) protected ColumnHandle fileFormatColumn; protected ColumnHandle dummyColumn; protected ColumnHandle intColumn; - protected ColumnHandle invalidColumnHandle; protected ColumnHandle pStringColumn; protected ColumnHandle pIntegerColumn; protected ConnectorTableProperties tablePartitionFormatProperties; - protected ConnectorTableProperties tableUnpartitionedProperties; protected List tablePartitionFormatPartitions; protected List tableUnpartitionedPartitions; @@ -717,7 +713,6 @@ protected void setupHive(String databaseName) tableBucketedDoubleFloat = new SchemaTableName(database, "trino_test_bucketed_by_double_float"); tablePartitionSchemaChange = new SchemaTableName(database, "trino_test_partition_schema_change"); tablePartitionSchemaChangeNonCanonical = new SchemaTableName(database, "trino_test_partition_schema_change_non_canonical"); - tableBucketEvolution = new SchemaTableName(database, "trino_test_bucket_evolution"); invalidTableHandle = new HiveTableHandle(database, INVALID_TABLE, ImmutableMap.of(), ImmutableList.of(), ImmutableList.of(), Optional.empty()); @@ -725,7 +720,6 @@ protected void setupHive(String databaseName) fileFormatColumn = createBaseColumn("file_format", -1, HIVE_STRING, VARCHAR, PARTITION_KEY, Optional.empty()); dummyColumn = createBaseColumn("dummy", -1, HIVE_INT, INTEGER, PARTITION_KEY, Optional.empty()); intColumn = createBaseColumn("t_int", -1, HIVE_INT, INTEGER, PARTITION_KEY, Optional.empty()); - invalidColumnHandle = createBaseColumn(INVALID_COLUMN, 0, HIVE_STRING, VARCHAR, REGULAR, Optional.empty()); pStringColumn = createBaseColumn("p_string", -1, HIVE_STRING, VARCHAR, PARTITION_KEY, Optional.empty()); pIntegerColumn = createBaseColumn("p_integer", -1, HIVE_INT, INTEGER, PARTITION_KEY, Optional.empty()); @@ -785,7 +779,6 @@ protected void setupHive(String databaseName) fileFormatColumn, Domain.create(ValueSet.ofRanges(Range.equal(createUnboundedVarcharType(), utf8Slice("rcbinary"))), false), dummyColumn, Domain.create(ValueSet.ofRanges(Range.equal(INTEGER, 4L)), false)))))), ImmutableList.of()); - tableUnpartitionedProperties = new ConnectorTableProperties(); } protected final void setup(HostAndPort metastoreAddress, String databaseName) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java index 607e67117b61..c37f779904fc 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java @@ -24,7 +24,6 @@ import io.trino.cost.StatsAndCosts; import io.trino.execution.QueryInfo; import io.trino.metadata.FunctionManager; -import io.trino.metadata.InsertTableHandle; import io.trino.metadata.Metadata; import io.trino.metadata.QualifiedObjectName; import io.trino.metadata.TableHandle; @@ -37,7 +36,6 @@ import io.trino.plugin.hive.metastore.StorageFormat; import io.trino.plugin.hive.metastore.Table; import io.trino.spi.connector.CatalogSchemaTableName; -import io.trino.spi.connector.ColumnHandle; import io.trino.spi.connector.ColumnMetadata; import io.trino.spi.connector.Constraint; import io.trino.spi.security.Identity; @@ -5614,7 +5612,7 @@ public void testReadWithPartitionSchemaMismatch() private void testReadWithPartitionSchemaMismatch(Session session, HiveStorageFormat format) { - if (isMappingByName(session, format)) { + if (isMappingByName(format)) { testReadWithPartitionSchemaMismatchByName(session, format); } else { @@ -5622,7 +5620,7 @@ private void testReadWithPartitionSchemaMismatch(Session session, HiveStorageFor } } - private boolean isMappingByName(Session session, HiveStorageFormat format) + private boolean isMappingByName(HiveStorageFormat format) { return switch(format) { case PARQUET -> true; @@ -7971,22 +7969,6 @@ public void testPrunePartitionFailure() assertUpdate("DROP TABLE test_prune_failure"); } - private HiveInsertTableHandle getHiveInsertTableHandle(Session session, String tableName) - { - Metadata metadata = getDistributedQueryRunner().getCoordinator().getMetadata(); - return transaction(getQueryRunner().getTransactionManager(), getQueryRunner().getMetadata(), getQueryRunner().getAccessControl()) - .execute(session, transactionSession -> { - QualifiedObjectName objectName = new QualifiedObjectName(catalog, TPCH_SCHEMA, tableName); - Optional handle = metadata.getTableHandle(transactionSession, objectName); - List columns = ImmutableList.copyOf(metadata.getColumnHandles(transactionSession, handle.get()).values()); - InsertTableHandle insertTableHandle = metadata.beginInsert(transactionSession, handle.get(), columns); - HiveInsertTableHandle hiveInsertTableHandle = (HiveInsertTableHandle) insertTableHandle.getConnectorHandle(); - - metadata.finishInsert(transactionSession, insertTableHandle, ImmutableList.of(), ImmutableList.of()); - return hiveInsertTableHandle; - }); - } - @Test public void testUseSortedProperties() { diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/HiveTestUtils.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/HiveTestUtils.java index 3b6663db5b33..ae3ba579bc8c 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/HiveTestUtils.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/HiveTestUtils.java @@ -169,16 +169,6 @@ public static HiveSessionProperties getHiveSessionProperties(HiveConfig hiveConf parquetWriterConfig); } - public static HiveSessionProperties getHiveSessionProperties(HiveConfig hiveConfig, ParquetReaderConfig parquetReaderConfig) - { - return new HiveSessionProperties( - hiveConfig, - new OrcReaderConfig(), - new OrcWriterConfig(), - parquetReaderConfig, - new ParquetWriterConfig()); - } - public static Set getDefaultHivePageSourceFactories(HdfsEnvironment hdfsEnvironment, HiveConfig hiveConfig) { TrinoFileSystemFactory fileSystemFactory = new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestReaderProjectionsAdapter.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestReaderProjectionsAdapter.java index a80a3acdcb06..6a0b33e2a4fe 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestReaderProjectionsAdapter.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestReaderProjectionsAdapter.java @@ -294,11 +294,6 @@ static RowData rowData(Object... data) return new RowData(data); } - List getData() - { - return data; - } - Object getField(int field) { checkArgument(field >= 0 && field < data.size()); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetTester.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetTester.java index 8bb3d0108861..7e71e7f92235 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetTester.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetTester.java @@ -921,11 +921,6 @@ public static ParquetSchemaOptions withSingleLevelArray() return new ParquetSchemaOptions(true, HIVE_PARQUET_USE_LEGACY_DECIMAL_ENCODING, HIVE_PARQUET_USE_INT96_TIMESTAMP_ENCODING); } - public static ParquetSchemaOptions withIntegerBackedDecimals() - { - return new ParquetSchemaOptions(false, false, HIVE_PARQUET_USE_INT96_TIMESTAMP_ENCODING); - } - public static ParquetSchemaOptions withInt64BackedTimestamps() { return new ParquetSchemaOptions(false, false, false); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/SerDeUtils.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/SerDeUtils.java index ee154e624238..560999c12ab3 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/SerDeUtils.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/SerDeUtils.java @@ -62,9 +62,6 @@ import static com.google.common.base.Preconditions.checkArgument; import static io.trino.plugin.base.type.TrinoTimestampEncoderFactory.createTimestampEncoder; -import static io.trino.spi.block.ArrayValueBuilder.buildArrayValue; -import static io.trino.spi.block.MapValueBuilder.buildMapValue; -import static io.trino.spi.block.RowValueBuilder.buildRowValue; import static io.trino.spi.type.Chars.truncateToLengthAndTrimSpaces; import static io.trino.spi.type.Timestamps.NANOSECONDS_PER_SECOND; import static io.trino.spi.type.Timestamps.round; @@ -76,31 +73,6 @@ public final class SerDeUtils { private SerDeUtils() {} - public static Object getBlockObject(Type type, Object object, ObjectInspector inspector) - { - requireNonNull(object, "object is null"); - if (inspector instanceof ListObjectInspector listObjectInspector) { - List list = listObjectInspector.getList(object); - ArrayType arrayType = (ArrayType) type; - ObjectInspector elementInspector = listObjectInspector.getListElementObjectInspector(); - return buildArrayValue(arrayType, list.size(), valuesBuilder -> buildList(list, arrayType.getElementType(), elementInspector, valuesBuilder)); - } - if (inspector instanceof MapObjectInspector mapObjectInspector) { - Map map = mapObjectInspector.getMap(object); - MapType mapType = (MapType) type; - return buildMapValue(mapType, map.size(), (keyBuilder, valueBuilder) -> buildMap(mapType, keyBuilder, valueBuilder, map, mapObjectInspector, true)); - } - if (inspector instanceof StructObjectInspector structObjectInspector) { - RowType rowType = (RowType) type; - return buildRowValue(rowType, fieldBuilders -> buildStruct(rowType, object, structObjectInspector, fieldBuilders)); - } - if (inspector instanceof UnionObjectInspector unionObjectInspector) { - RowType rowType = (RowType) type; - return buildRowValue(rowType, fieldBuilders -> buildUnion(rowType, object, unionObjectInspector, fieldBuilders)); - } - throw new RuntimeException("Unknown object inspector category: " + inspector.getCategory()); - } - public static void serializeObject(Type type, BlockBuilder builder, Object object, ObjectInspector inspector) { requireNonNull(builder, "builder is null"); From 2d75a5dee102068376f6bb2305bbf607d5fec2d9 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Thu, 2 Nov 2023 20:55:30 -0700 Subject: [PATCH 070/587] Remove unused metastore listGrantedPrincipals --- .../plugin/hive/HiveMetastoreClosure.java | 5 ---- .../metastore/ForwardingHiveMetastore.java | 6 ----- .../plugin/hive/metastore/HiveMetastore.java | 2 -- .../SemiTransactionalHiveMetastore.java | 7 ------ .../metastore/cache/CachingHiveMetastore.java | 25 ------------------- .../cache/SharedHiveMetastoreCache.java | 7 ------ .../metastore/file/FileHiveMetastore.java | 8 ------ .../metastore/glue/GlueHiveMetastore.java | 6 ----- .../recording/HiveMetastoreRecording.java | 18 ------------- .../recording/RecordingHiveMetastore.java | 8 ------ .../thrift/BridgingHiveMetastore.java | 6 ----- .../FailureAwareThriftMetastoreClient.java | 7 ------ .../metastore/thrift/ThriftHiveMetastore.java | 21 ---------------- .../thrift/ThriftHiveMetastoreClient.java | 11 -------- .../metastore/thrift/ThriftMetastore.java | 2 -- .../thrift/ThriftMetastoreClient.java | 3 --- .../thrift/ThriftMetastoreStats.java | 8 ------ ...tandardAccessControlMetadataMetastore.java | 2 -- .../CountingAccessHiveMetastore.java | 6 ----- .../metastore/UnimplementedHiveMetastore.java | 6 ----- .../recording/TestRecordingHiveMetastore.java | 7 ------ .../thrift/InMemoryThriftMetastore.java | 6 ----- .../thrift/MockThriftMetastoreClient.java | 6 ----- 23 files changed, 183 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetastoreClosure.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetastoreClosure.java index dc085d1c2827..02a6f01c2697 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetastoreClosure.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetastoreClosure.java @@ -310,11 +310,6 @@ public void revokeRoles(Set roles, Set grantees, boolean delegate.revokeRoles(roles, grantees, adminOption, grantor); } - public Set listGrantedPrincipals(String role) - { - return delegate.listGrantedPrincipals(role); - } - public Set listRoleGrants(HivePrincipal principal) { return delegate.listRoleGrants(principal); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/ForwardingHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/ForwardingHiveMetastore.java index 62fd8819995b..9047d29673d4 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/ForwardingHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/ForwardingHiveMetastore.java @@ -315,12 +315,6 @@ public void revokeRoles(Set roles, Set grantees, boolean delegate.revokeRoles(roles, grantees, adminOption, grantor); } - @Override - public Set listGrantedPrincipals(String role) - { - return delegate.listGrantedPrincipals(role); - } - @Override public Set listRoleGrants(HivePrincipal principal) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastore.java index 0c5528d7f4e0..a7a03b541a75 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastore.java @@ -146,8 +146,6 @@ default void updatePartitionStatistics(Table table, String partitionName, Functi void revokeRoles(Set roles, Set grantees, boolean adminOption, HivePrincipal grantor); - Set listGrantedPrincipals(String role); - Set listRoleGrants(HivePrincipal principal); void grantTablePrivileges(String databaseName, String tableName, String tableOwner, HivePrincipal grantee, HivePrincipal grantor, Set privileges, boolean grantOption); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/SemiTransactionalHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/SemiTransactionalHiveMetastore.java index 4320a8f94da0..8a377fa75504 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/SemiTransactionalHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/SemiTransactionalHiveMetastore.java @@ -1148,13 +1148,6 @@ public synchronized void revokeRoles(Set roles, Set grant setExclusive(delegate -> delegate.revokeRoles(roles, grantees, adminOption, grantor)); } - @Override - public synchronized Set listGrantedPrincipals(String role) - { - checkReadable(); - return delegate.listGrantedPrincipals(role); - } - @Override public synchronized Set listRoleGrants(HivePrincipal principal) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java index f5175b239759..462f593d2a23 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java @@ -130,7 +130,6 @@ public enum StatsRecording private final LoadingCache> tablePrivilegesCache; private final LoadingCache> rolesCache; private final LoadingCache> roleGrantsCache; - private final LoadingCache> grantedPrincipalsCache; private final LoadingCache> configValuesCache; public static CachingHiveMetastoreBuilder builder() @@ -371,7 +370,6 @@ protected CachingHiveMetastore( tablePrivilegesCache = cacheFactory.buildCache(key -> loadTablePrivileges(key.getDatabase(), key.getTable(), key.getOwner(), key.getPrincipal())); rolesCache = cacheFactory.buildCache(ignored -> loadRoles()); roleGrantsCache = cacheFactory.buildCache(this::loadRoleGrants); - grantedPrincipalsCache = cacheFactory.buildCache(this::loadPrincipals); configValuesCache = cacheFactory.buildCache(this::loadConfigValue); partitionStatisticsCache = partitionStatsCacheFactory.buildBulkCache(); @@ -1130,12 +1128,6 @@ public void revokeRoles(Set roles, Set grantees, boolean } } - @Override - public Set listGrantedPrincipals(String role) - { - return get(grantedPrincipalsCache, role); - } - @Override public Set listRoleGrants(HivePrincipal principal) { @@ -1147,11 +1139,6 @@ private Set loadRoleGrants(HivePrincipal principal) return delegate.listRoleGrants(principal); } - private Set loadPrincipals(String role) - { - return delegate.listGrantedPrincipals(role); - } - private void invalidatePartitionCache(String databaseName, String tableName) { invalidatePartitionCache(databaseName, tableName, partitionName -> true); @@ -1546,13 +1533,6 @@ public CacheStatsMBean getRoleGrantsStats() return new CacheStatsMBean(roleGrantsCache); } - @Managed - @Nested - public CacheStatsMBean getGrantedPrincipalsStats() - { - return new CacheStatsMBean(grantedPrincipalsCache); - } - @Managed @Nested public CacheStatsMBean getConfigValuesStats() @@ -1638,11 +1618,6 @@ LoadingCache> getRoleGrantsCache() return roleGrantsCache; } - LoadingCache> getGrantedPrincipalsCache() - { - return grantedPrincipalsCache; - } - LoadingCache> getConfigValuesCache() { return configValuesCache; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/SharedHiveMetastoreCache.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/SharedHiveMetastoreCache.java index 7c2ab0ece44f..c65fc9795c45 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/SharedHiveMetastoreCache.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/SharedHiveMetastoreCache.java @@ -331,13 +331,6 @@ public AggregateCacheStatsMBean getRoleGrantsStats() return new AggregateCacheStatsMBean(CachingHiveMetastore::getRoleGrantsCache); } - @Managed - @Nested - public AggregateCacheStatsMBean getGrantedPrincipalsStats() - { - return new AggregateCacheStatsMBean(CachingHiveMetastore::getGrantedPrincipalsCache); - } - @Managed @Nested public AggregateCacheStatsMBean getConfigValuesStats() diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/file/FileHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/file/FileHiveMetastore.java index 8440287da119..d529b1e22d5b 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/file/FileHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/file/FileHiveMetastore.java @@ -1017,14 +1017,6 @@ public synchronized void revokeRoles(Set roles, Set grant } } - @Override - public synchronized Set listGrantedPrincipals(String role) - { - return listRoleGrantsSanitized().stream() - .filter(grant -> grant.getRoleName().equals(role)) - .collect(toImmutableSet()); - } - @Override public synchronized Set listRoleGrants(HivePrincipal principal) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java index dcbe8eaac53f..4264190cd0bd 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java @@ -1163,12 +1163,6 @@ public void revokeRoles(Set roles, Set grantees, boolean throw new TrinoException(NOT_SUPPORTED, "revokeRoles is not supported by Glue"); } - @Override - public Set listGrantedPrincipals(String role) - { - throw new TrinoException(NOT_SUPPORTED, "listPrincipals is not supported by Glue"); - } - @Override public Set listRoleGrants(HivePrincipal principal) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/recording/HiveMetastoreRecording.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/recording/HiveMetastoreRecording.java index b46de9394972..2fd569416b6d 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/recording/HiveMetastoreRecording.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/recording/HiveMetastoreRecording.java @@ -87,7 +87,6 @@ public class HiveMetastoreRecording private final NonEvictableCache> partitionsByNamesCache; private final NonEvictableCache> tablePrivilegesCache; private final NonEvictableCache> roleGrantsCache; - private final NonEvictableCache> grantedPrincipalsCache; private final NonEvictableCache functionExistsCache; private final NonEvictableCache> functionsByDatabaseCache; private final NonEvictableCache> functionsByNameCache; @@ -115,7 +114,6 @@ public HiveMetastoreRecording(RecordingMetastoreConfig config, JsonCodec listRoles(Supplier> valueSupplier) return result; } - public Set listGrantedPrincipals(String role, Supplier> valueSupplier) - { - return loadValue(grantedPrincipalsCache, role, valueSupplier); - } - public Set listRoleGrants(HivePrincipal principal, Supplier> valueSupplier) { return loadValue(roleGrantsCache, principal, valueSupplier); @@ -308,7 +300,6 @@ public void writeRecording() toPairs(partitionsByNamesCache), toPairs(tablePrivilegesCache), toPairs(roleGrantsCache), - toPairs(grantedPrincipalsCache), toPairs(functionExistsCache), toPairs(functionsByDatabaseCache), toPairs(functionsByNameCache)); @@ -379,7 +370,6 @@ public static class Recording private final List>> partitionsByNames; private final List>> tablePrivileges; private final List>> roleGrants; - private final List>> grantedPrincipals; private final List> functionExists; private final List>> functionsByDatabase; private final List>> functionsByName; @@ -401,7 +391,6 @@ public Recording( @JsonProperty("partitionsByNames") List>> partitionsByNames, @JsonProperty("tablePrivileges") List>> tablePrivileges, @JsonProperty("roleGrants") List>> roleGrants, - @JsonProperty("grantedPrincipals") List>> grantedPrincipals, @JsonProperty("functionExists") List> functionExists, @JsonProperty("functionsByDatabase") List>> functionsByDatabase, @JsonProperty("functionsByName") List>> functionsByName) @@ -421,7 +410,6 @@ public Recording( this.partitionsByNames = partitionsByNames; this.tablePrivileges = tablePrivileges; this.roleGrants = roleGrants; - this.grantedPrincipals = grantedPrincipals; this.functionExists = requireNonNullElse(functionExists, List.of()); this.functionsByDatabase = requireNonNullElse(functionsByDatabase, List.of()); this.functionsByName = requireNonNullElse(functionsByName, List.of()); @@ -511,12 +499,6 @@ public List>> getTablePrivileges() return tablePrivileges; } - @JsonProperty - public List>> getGrantedPrincipals() - { - return grantedPrincipals; - } - @JsonProperty public List>> getRoleGrants() { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/recording/RecordingHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/recording/RecordingHiveMetastore.java index 22c3694bdedf..ee40680388e9 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/recording/RecordingHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/recording/RecordingHiveMetastore.java @@ -404,14 +404,6 @@ public void revokeRoles(Set roles, Set grantees, boolean delegate.revokeRoles(roles, grantees, adminOption, grantor); } - @Override - public Set listGrantedPrincipals(String role) - { - return recording.listGrantedPrincipals( - role, - () -> delegate.listGrantedPrincipals(role)); - } - @Override public Set listRoleGrants(HivePrincipal principal) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/BridgingHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/BridgingHiveMetastore.java index e643409116c2..19c3e2380877 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/BridgingHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/BridgingHiveMetastore.java @@ -432,12 +432,6 @@ public void revokeRoles(Set roles, Set grantees, boolean delegate.revokeRoles(roles, grantees, adminOption, grantor); } - @Override - public Set listGrantedPrincipals(String role) - { - return delegate.listGrantedPrincipals(role); - } - @Override public Set listRoleGrants(HivePrincipal principal) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/FailureAwareThriftMetastoreClient.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/FailureAwareThriftMetastoreClient.java index 3a43d33139f3..6d8d862db773 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/FailureAwareThriftMetastoreClient.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/FailureAwareThriftMetastoreClient.java @@ -331,13 +331,6 @@ public void revokeRole(String role, String granteeName, PrincipalType granteeTyp runWithHandle(() -> delegate.revokeRole(role, granteeName, granteeType, grantOption)); } - @Override - public List listGrantedPrincipals(String role) - throws TException - { - return runWithHandle(() -> delegate.listGrantedPrincipals(role)); - } - @Override public List listRoleGrants(String name, PrincipalType principalType) throws TException diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftHiveMetastore.java index f7127dbe9605..e186819dd13c 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftHiveMetastore.java @@ -823,27 +823,6 @@ private void revokeRole(String role, String granteeName, PrincipalType granteeTy } } - @Override - public Set listGrantedPrincipals(String role) - { - try { - return retry() - .stopOn(MetaException.class) - .stopOnIllegalExceptions() - .run("listPrincipals", stats.getListGrantedPrincipals().wrap(() -> { - try (ThriftMetastoreClient client = createMetastoreClient()) { - return fromRolePrincipalGrants(client.listGrantedPrincipals(role)); - } - })); - } - catch (TException e) { - throw new TrinoException(HIVE_METASTORE_ERROR, e); - } - catch (Exception e) { - throw propagate(e); - } - } - @Override public Set listRoleGrants(HivePrincipal principal) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftHiveMetastoreClient.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftHiveMetastoreClient.java index fdd3ed79a10f..0a87b829f195 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftHiveMetastoreClient.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftHiveMetastoreClient.java @@ -33,8 +33,6 @@ import io.trino.hive.thrift.metastore.EnvironmentContext; import io.trino.hive.thrift.metastore.FieldSchema; import io.trino.hive.thrift.metastore.Function; -import io.trino.hive.thrift.metastore.GetPrincipalsInRoleRequest; -import io.trino.hive.thrift.metastore.GetPrincipalsInRoleResponse; import io.trino.hive.thrift.metastore.GetRoleGrantsForPrincipalRequest; import io.trino.hive.thrift.metastore.GetRoleGrantsForPrincipalResponse; import io.trino.hive.thrift.metastore.GetTableRequest; @@ -612,15 +610,6 @@ private void removeGrant(String role, String granteeName, PrincipalType granteeT } } - @Override - public List listGrantedPrincipals(String role) - throws TException - { - GetPrincipalsInRoleRequest request = new GetPrincipalsInRoleRequest(role); - GetPrincipalsInRoleResponse response = client.getPrincipalsInRole(request); - return ImmutableList.copyOf(response.getPrincipalGrants()); - } - @Override public List listRoleGrants(String principalName, PrincipalType principalType) throws TException diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastore.java index 4a05b588b444..a87b6a53a575 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastore.java @@ -110,8 +110,6 @@ public interface ThriftMetastore void revokeRoles(Set roles, Set grantees, boolean adminOption, HivePrincipal grantor); - Set listGrantedPrincipals(String role); - Set listRoleGrants(HivePrincipal principal); void grantTablePrivileges(String databaseName, String tableName, String tableOwner, HivePrincipal grantee, HivePrincipal grantor, Set privileges, boolean grantOption); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreClient.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreClient.java index f571e018087f..c25506ff9b4c 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreClient.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreClient.java @@ -156,9 +156,6 @@ void grantRole(String role, String granteeName, PrincipalType granteeType, Strin void revokeRole(String role, String granteeName, PrincipalType granteeType, boolean grantOption) throws TException; - List listGrantedPrincipals(String role) - throws TException; - List listRoleGrants(String name, PrincipalType principalType) throws TException; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreStats.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreStats.java index ba33a3950c60..924e0ce6a84f 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreStats.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreStats.java @@ -51,7 +51,6 @@ public class ThriftMetastoreStats private final ThriftMetastoreApiStats grantRole = new ThriftMetastoreApiStats(); private final ThriftMetastoreApiStats revokeRole = new ThriftMetastoreApiStats(); private final ThriftMetastoreApiStats listRoleGrants = new ThriftMetastoreApiStats(); - private final ThriftMetastoreApiStats listGrantedPrincipals = new ThriftMetastoreApiStats(); private final ThriftMetastoreApiStats createRole = new ThriftMetastoreApiStats(); private final ThriftMetastoreApiStats dropRole = new ThriftMetastoreApiStats(); private final ThriftMetastoreApiStats openTransaction = new ThriftMetastoreApiStats(); @@ -296,13 +295,6 @@ public ThriftMetastoreApiStats getRevokeRole() return revokeRole; } - @Managed - @Nested - public ThriftMetastoreApiStats getListGrantedPrincipals() - { - return listGrantedPrincipals; - } - @Managed @Nested public ThriftMetastoreApiStats getListRoleGrants() diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/security/SqlStandardAccessControlMetadataMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/security/SqlStandardAccessControlMetadataMetastore.java index 9d5d60230d5f..4df8f99988e6 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/security/SqlStandardAccessControlMetadataMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/security/SqlStandardAccessControlMetadataMetastore.java @@ -39,8 +39,6 @@ public interface SqlStandardAccessControlMetadataMetastore void revokeRoles(Set roles, Set grantees, boolean adminOption, HivePrincipal grantor); - Set listGrantedPrincipals(String role); - Optional getDatabaseOwner(String databaseName); void revokeTablePrivileges(String databaseName, String tableName, HivePrincipal grantee, HivePrincipal grantor, Set privileges, boolean grantOption); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/CountingAccessHiveMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/CountingAccessHiveMetastore.java index b04b821270d3..1630456391e6 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/CountingAccessHiveMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/CountingAccessHiveMetastore.java @@ -299,12 +299,6 @@ public void revokeRoles(Set roles, Set grantees, boolean throw new UnsupportedOperationException(); } - @Override - public Set listGrantedPrincipals(String role) - { - throw new UnsupportedOperationException(); - } - @Override public Set listRoleGrants(HivePrincipal principal) { diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/UnimplementedHiveMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/UnimplementedHiveMetastore.java index fec6e2277a1a..cb7023359ce7 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/UnimplementedHiveMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/UnimplementedHiveMetastore.java @@ -286,12 +286,6 @@ public void revokeRoles(Set roles, Set grantees, boolean throw new UnsupportedOperationException(); } - @Override - public Set listGrantedPrincipals(String role) - { - throw new UnsupportedOperationException(); - } - @Override public Set listRoleGrants(HivePrincipal principal) { diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/recording/TestRecordingHiveMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/recording/TestRecordingHiveMetastore.java index 7100028294e3..f8c077a0a9c3 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/recording/TestRecordingHiveMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/recording/TestRecordingHiveMetastore.java @@ -191,7 +191,6 @@ private void validateMetadata(HiveMetastore hiveMetastore) assertEquals(hiveMetastore.listTablePrivileges("database", "table", Optional.of("owner"), Optional.of(new HivePrincipal(USER, "user"))), ImmutableSet.of(PRIVILEGE_INFO)); assertEquals(hiveMetastore.listRoles(), ImmutableSet.of("role")); assertEquals(hiveMetastore.listRoleGrants(new HivePrincipal(USER, "user")), ImmutableSet.of(ROLE_GRANT)); - assertEquals(hiveMetastore.listGrantedPrincipals("role"), ImmutableSet.of(ROLE_GRANT)); } private void validatePartitionSubset(HiveMetastore hiveMetastore) @@ -346,12 +345,6 @@ public Set listRoles() return ImmutableSet.of("role"); } - @Override - public Set listGrantedPrincipals(String role) - { - return ImmutableSet.of(ROLE_GRANT); - } - @Override public Set listRoleGrants(HivePrincipal principal) { diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/InMemoryThriftMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/InMemoryThriftMetastore.java index 370b0a5413de..bdac1b83cbed 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/InMemoryThriftMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/InMemoryThriftMetastore.java @@ -534,12 +534,6 @@ public void revokeRoles(Set roles, Set grantees, boolean throw new UnsupportedOperationException(); } - @Override - public Set listGrantedPrincipals(String role) - { - throw new UnsupportedOperationException(); - } - @Override public Set listRoleGrants(HivePrincipal principal) { diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/MockThriftMetastoreClient.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/MockThriftMetastoreClient.java index 02523ad93253..0f1621b034c6 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/MockThriftMetastoreClient.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/MockThriftMetastoreClient.java @@ -505,12 +505,6 @@ public void revokeRole(String role, String granteeName, PrincipalType granteeTyp // No-op } - @Override - public List listGrantedPrincipals(String role) - { - throw new UnsupportedOperationException(); - } - @Override public List listRoleGrants(String name, PrincipalType principalType) { From 8a61c4338c4ab8b1077e18a5033dd7f93ac24e38 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Thu, 2 Nov 2023 20:58:54 -0700 Subject: [PATCH 071/587] Remove unused field in AbstractTestHive --- .../src/test/java/io/trino/plugin/hive/AbstractTestHive.java | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java index bf835389f1a6..07b5a1b41440 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java @@ -344,7 +344,6 @@ public abstract class AbstractTestHive protected static final String INVALID_DATABASE = "totally_invalid_database_name"; protected static final String INVALID_TABLE = "totally_invalid_table_name"; - protected static final String INVALID_COLUMN = "totally_invalid_column_name"; protected static final String TEST_SERVER_VERSION = "test_version"; From 0dda4fe0caf6b9f3d3f0dec8eff6c59ba0012a6b Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Thu, 2 Nov 2023 20:59:41 -0700 Subject: [PATCH 072/587] Remove unused HiveMetastoreClosure.alterPartitions --- .../main/java/io/trino/plugin/hive/HiveMetastoreClosure.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetastoreClosure.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetastoreClosure.java index 02a6f01c2697..21e0141b8f6a 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetastoreClosure.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetastoreClosure.java @@ -398,11 +398,6 @@ public void updateTableWriteId(String dbName, String tableName, long transaction delegate.updateTableWriteId(dbName, tableName, transactionId, writeId, rowCountChange); } - public void alterPartitions(String dbName, String tableName, List partitions, long writeId) - { - delegate.alterPartitions(dbName, tableName, partitions, writeId); - } - public void addDynamicPartitions(String dbName, String tableName, List partitionNames, long transactionId, long writeId, AcidOperation operation) { delegate.addDynamicPartitions(dbName, tableName, partitionNames, transactionId, writeId, operation); From 1be0743fcd09fc152b45323f15f5384cd11face1 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Thu, 2 Nov 2023 21:13:49 -0700 Subject: [PATCH 073/587] Remove unused HiveMetastoreDecorator.PRIORITY_INTIAL --- .../io/trino/plugin/hive/metastore/HiveMetastoreDecorator.java | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastoreDecorator.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastoreDecorator.java index 81de38c0322d..1ed3df5d3325 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastoreDecorator.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastoreDecorator.java @@ -16,7 +16,6 @@ public interface HiveMetastoreDecorator { - int PRIORITY_INTIAL = 0; int PRIORITY_PARTITION_PROJECTION = 50; int PRIORITY_RECORDING = 100; From a5a3303326f5b1235941c8ad8c907060aed60ef1 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Thu, 2 Nov 2023 21:14:02 -0700 Subject: [PATCH 074/587] Remove unused builder methods from HiveColumnStatistics --- .../hive/metastore/HiveColumnStatistics.java | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveColumnStatistics.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveColumnStatistics.java index c68c19f17413..862d6507e8d4 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveColumnStatistics.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveColumnStatistics.java @@ -291,60 +291,30 @@ private Builder(HiveColumnStatistics other) this.distinctValuesCount = other.getDistinctValuesCount(); } - public Builder setIntegerStatistics(Optional integerStatistics) - { - this.integerStatistics = integerStatistics; - return this; - } - public Builder setIntegerStatistics(IntegerStatistics integerStatistics) { this.integerStatistics = Optional.of(integerStatistics); return this; } - public Builder setDoubleStatistics(Optional doubleStatistics) - { - this.doubleStatistics = doubleStatistics; - return this; - } - public Builder setDoubleStatistics(DoubleStatistics doubleStatistics) { this.doubleStatistics = Optional.of(doubleStatistics); return this; } - public Builder setDecimalStatistics(Optional decimalStatistics) - { - this.decimalStatistics = decimalStatistics; - return this; - } - public Builder setDecimalStatistics(DecimalStatistics decimalStatistics) { this.decimalStatistics = Optional.of(decimalStatistics); return this; } - public Builder setDateStatistics(Optional dateStatistics) - { - this.dateStatistics = dateStatistics; - return this; - } - public Builder setDateStatistics(DateStatistics dateStatistics) { this.dateStatistics = Optional.of(dateStatistics); return this; } - public Builder setBooleanStatistics(Optional booleanStatistics) - { - this.booleanStatistics = booleanStatistics; - return this; - } - public Builder setBooleanStatistics(BooleanStatistics booleanStatistics) { this.booleanStatistics = Optional.of(booleanStatistics); From 2bb2fa4e9af781372b2b926c347f63d65131f332 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Thu, 2 Nov 2023 21:15:05 -0700 Subject: [PATCH 075/587] Remove unused methods from CacheStatsAggregator --- .../hive/metastore/cache/SharedHiveMetastoreCache.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/SharedHiveMetastoreCache.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/SharedHiveMetastoreCache.java index c65fc9795c45..329e76df0578 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/SharedHiveMetastoreCache.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/SharedHiveMetastoreCache.java @@ -403,16 +403,6 @@ public long getRequestCount() return requestCount; } - public long getHitCount() - { - return hitCount; - } - - public long getMissCount() - { - return missCount; - } - public double getHitRate() { return (requestCount == 0) ? 1.0 : (double) hitCount / requestCount; From e5662b1633ddac963444dfd073641a94f6c42173 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Thu, 2 Nov 2023 21:16:15 -0700 Subject: [PATCH 076/587] Remove unused GlueColumnStatisticsProvider.getPartitionColumnStatistics --- .../hive/metastore/glue/GlueColumnStatisticsProvider.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueColumnStatisticsProvider.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueColumnStatisticsProvider.java index 3ab0bee62322..f13846d56398 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueColumnStatisticsProvider.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueColumnStatisticsProvider.java @@ -35,11 +35,6 @@ public interface GlueColumnStatisticsProvider Map> getPartitionColumnStatistics(Collection partitions); - default Map getPartitionColumnStatistics(Partition partition) - { - return getPartitionColumnStatistics(ImmutableSet.of(partition)).get(partition); - } - void updateTableColumnStatistics(Table table, Map columnStatistics); default void updatePartitionStatistics(Partition partition, Map columnStatistics) From ff6aa4ec3d4b6b8882ed86e12848248c12b7d47a Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Thu, 2 Nov 2023 21:16:54 -0700 Subject: [PATCH 077/587] Remove unused TestGlueExpressionUtil.getColumn --- .../plugin/hive/metastore/glue/TestGlueExpressionUtil.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestGlueExpressionUtil.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestGlueExpressionUtil.java index 375401e71c24..421985ccc7c9 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestGlueExpressionUtil.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestGlueExpressionUtil.java @@ -14,8 +14,6 @@ package io.trino.plugin.hive.metastore.glue; import com.google.common.collect.ImmutableList; -import io.trino.plugin.hive.HiveType; -import io.trino.plugin.hive.metastore.Column; import io.trino.spi.predicate.Domain; import io.trino.spi.predicate.Range; import io.trino.spi.predicate.TupleDomain; @@ -33,11 +31,6 @@ public class TestGlueExpressionUtil { - private static Column getColumn(String name, String type) - { - return new Column(name, HiveType.valueOf(type), Optional.empty()); - } - @Test public void testBuildGlueExpressionDomainEqualsSingleValue() { From 003f9c942d2fe55f2705332ef9e3832df21fa92f Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Thu, 2 Nov 2023 21:24:41 -0700 Subject: [PATCH 078/587] Remove unused ThriftMetastoreParameterParserUtils.toBoolean --- .../thrift/ThriftMetastoreParameterParserUtils.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreParameterParserUtils.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreParameterParserUtils.java index eff79827f3cb..1276294bc6b8 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreParameterParserUtils.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreParameterParserUtils.java @@ -28,15 +28,6 @@ final class ThriftMetastoreParameterParserUtils { private ThriftMetastoreParameterParserUtils() {} - static Optional toBoolean(@Nullable String parameterValue) - { - if (parameterValue == null) { - return Optional.empty(); - } - Boolean value = Boolean.parseBoolean(parameterValue); - return Optional.of(value); - } - static OptionalLong toLong(@Nullable String parameterValue) { if (parameterValue == null) { From 9dd9f50bb860c98a3a0820810e5320afa982267d Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Thu, 2 Nov 2023 22:01:16 -0700 Subject: [PATCH 079/587] Remove unused builder methods on HiveColumnStatistics --- .../hive/metastore/HiveColumnStatistics.java | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveColumnStatistics.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveColumnStatistics.java index 862d6507e8d4..1469540e3fa5 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveColumnStatistics.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveColumnStatistics.java @@ -254,11 +254,6 @@ public static HiveColumnStatistics createBinaryColumnStatistics(OptionalLong max .build(); } - public static Builder builder(HiveColumnStatistics other) - { - return new Builder(other); - } - public static Builder builder() { return new Builder(); @@ -278,19 +273,6 @@ public static class Builder private Builder() {} - private Builder(HiveColumnStatistics other) - { - this.integerStatistics = other.getIntegerStatistics(); - this.doubleStatistics = other.getDoubleStatistics(); - this.decimalStatistics = other.getDecimalStatistics(); - this.dateStatistics = other.getDateStatistics(); - this.booleanStatistics = other.getBooleanStatistics(); - this.maxValueSizeInBytes = other.getMaxValueSizeInBytes(); - this.totalSizeInBytes = other.getTotalSizeInBytes(); - this.nullsCount = other.getNullsCount(); - this.distinctValuesCount = other.getDistinctValuesCount(); - } - public Builder setIntegerStatistics(IntegerStatistics integerStatistics) { this.integerStatistics = Optional.of(integerStatistics); From 6b50ffb8e6474b0430193464801f91a6d5a3d5be Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Thu, 2 Nov 2023 22:05:37 -0700 Subject: [PATCH 080/587] Remove unused HiveMetastore alterPartitions --- .../metastore/ForwardingHiveMetastore.java | 10 --------- .../plugin/hive/metastore/HiveMetastore.java | 5 ----- .../metastore/cache/CachingHiveMetastore.java | 11 ---------- .../thrift/BridgingHiveMetastore.java | 10 --------- .../metastore/thrift/ThriftHiveMetastore.java | 22 ------------------- .../metastore/thrift/ThriftMetastore.java | 5 ----- .../thrift/ThriftMetastoreStats.java | 8 ------- 7 files changed, 71 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/ForwardingHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/ForwardingHiveMetastore.java index 9047d29673d4..f8b3fcd5926c 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/ForwardingHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/ForwardingHiveMetastore.java @@ -430,16 +430,6 @@ public void updateTableWriteId( delegate.updateTableWriteId(dbName, tableName, transactionId, writeId, rowCountChange); } - @Override - public void alterPartitions( - String dbName, - String tableName, - List partitions, - long writeId) - { - delegate.alterPartitions(dbName, tableName, partitions, writeId); - } - @Override public void addDynamicPartitions( String dbName, diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastore.java index a7a03b541a75..30b91704acea 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastore.java @@ -224,11 +224,6 @@ default void updateTableWriteId(String dbName, String tableName, long transactio throw new UnsupportedOperationException(); } - default void alterPartitions(String dbName, String tableName, List partitions, long writeId) - { - throw new UnsupportedOperationException(); - } - default void addDynamicPartitions(String dbName, String tableName, List partitionNames, long transactionId, long writeId, AcidOperation operation) { throw new UnsupportedOperationException(); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java index 462f593d2a23..cf11bbae1435 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java @@ -1284,17 +1284,6 @@ public void updateTableWriteId(String dbName, String tableName, long transaction } } - @Override - public void alterPartitions(String dbName, String tableName, List partitions, long writeId) - { - try { - delegate.alterPartitions(dbName, tableName, partitions, writeId); - } - finally { - invalidatePartitionCache(dbName, tableName); - } - } - @Override public void addDynamicPartitions(String dbName, String tableName, List partitionNames, long transactionId, long writeId, AcidOperation operation) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/BridgingHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/BridgingHiveMetastore.java index 19c3e2380877..d5926bb35155 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/BridgingHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/BridgingHiveMetastore.java @@ -534,16 +534,6 @@ public void updateTableWriteId(String dbName, String tableName, long transaction delegate.updateTableWriteId(dbName, tableName, transactionId, writeId, rowCountChange); } - @Override - public void alterPartitions(String dbName, String tableName, List partitions, long writeId) - { - List hadoopPartitions = partitions.stream() - .map(ThriftMetastoreUtil::toMetastoreApiPartition) - .peek(partition -> partition.setWriteId(writeId)) - .collect(toImmutableList()); - delegate.alterPartitions(dbName, tableName, hadoopPartitions, writeId); - } - @Override public void addDynamicPartitions(String dbName, String tableName, List partitionNames, long transactionId, long writeId, AcidOperation operation) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftHiveMetastore.java index e186819dd13c..dcd1ada4d8be 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftHiveMetastore.java @@ -1954,28 +1954,6 @@ public void updateTableWriteId(String dbName, String tableName, long transaction } } - @Override - public void alterPartitions(String dbName, String tableName, List partitions, long writeId) - { - checkArgument(writeId > 0, "writeId should be a positive integer, but was %s", writeId); - try { - retry() - .stopOnIllegalExceptions() - .run("alterPartitions", stats.getAlterPartitions().wrap(() -> { - try (ThriftMetastoreClient metastoreClient = createMetastoreClient()) { - metastoreClient.alterPartitions(dbName, tableName, partitions, writeId); - } - return null; - })); - } - catch (TException e) { - throw new TrinoException(HIVE_METASTORE_ERROR, e); - } - catch (Exception e) { - throw propagate(e); - } - } - @Override public void addDynamicPartitions(String dbName, String tableName, List partitionNames, long transactionId, long writeId, AcidOperation operation) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastore.java index a87b6a53a575..03043b64effa 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastore.java @@ -214,11 +214,6 @@ default void updateTableWriteId(String dbName, String tableName, long transactio throw new UnsupportedOperationException(); } - default void alterPartitions(String dbName, String tableName, List partitions, long writeId) - { - throw new UnsupportedOperationException(); - } - default void addDynamicPartitions(String dbName, String tableName, List partitionNames, long transactionId, long writeId, AcidOperation operation) { throw new UnsupportedOperationException(); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreStats.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreStats.java index 924e0ce6a84f..afc03b53ef71 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreStats.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreStats.java @@ -62,7 +62,6 @@ public class ThriftMetastoreStats private final ThriftMetastoreApiStats validWriteIds = new ThriftMetastoreApiStats(); private final ThriftMetastoreApiStats allocateWriteId = new ThriftMetastoreApiStats(); private final ThriftMetastoreApiStats updateTableWriteId = new ThriftMetastoreApiStats(); - private final ThriftMetastoreApiStats alterPartitions = new ThriftMetastoreApiStats(); private final ThriftMetastoreApiStats addDynamicPartitions = new ThriftMetastoreApiStats(); private final ThriftMetastoreApiStats alterTransactionalTable = new ThriftMetastoreApiStats(); private final ThriftMetastoreApiStats getFunction = new ThriftMetastoreApiStats(); @@ -379,13 +378,6 @@ public ThriftMetastoreApiStats getUpdateTableWriteId() return updateTableWriteId; } - @Managed - @Nested - public ThriftMetastoreApiStats getAlterPartitions() - { - return alterPartitions; - } - @Managed @Nested public ThriftMetastoreApiStats getAddDynamicPartitions() From 6ea46367080d24ca14a68b72acdc759508c046a9 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Fri, 3 Nov 2023 17:23:34 -0700 Subject: [PATCH 081/587] Simplify CachingHiveMetastore construction The builder in this class is complex and unnecessary given this class is only constructed in two places in production code, and in just two tests. Additionally the builder was being used in a non-thread safe manner. --- .../deltalake/DeltaLakeMetadataFactory.java | 5 +- .../plugin/hive/HiveMetadataFactory.java | 7 +- .../plugin/hive/HivePageSinkProvider.java | 8 +- .../metastore/cache/CachingHiveMetastore.java | 334 +++++------------- .../cache/SharedHiveMetastoreCache.java | 96 ++--- .../trino/plugin/hive/AbstractTestHive.java | 26 +- .../cache/TestCachingHiveMetastore.java | 127 ++----- .../metastore/file/TestFileHiveMetastore.java | 2 +- .../glue/TestingMetastoreObjects.java | 2 +- .../thrift/MockThriftMetastoreClient.java | 2 +- .../plugin/hudi/HudiMetadataFactory.java | 7 +- .../catalog/hms/TrinoHiveCatalogFactory.java | 4 +- .../iceberg/TestIcebergMergeAppend.java | 6 +- .../TestIcebergOrcMetricsCollection.java | 4 +- .../iceberg/TestIcebergSplitSource.java | 4 +- .../trino/plugin/iceberg/TestIcebergV2.java | 4 +- ...TestTrinoHiveCatalogWithFileMetastore.java | 4 +- ...TestTrinoHiveCatalogWithHiveMetastore.java | 4 +- 18 files changed, 200 insertions(+), 446 deletions(-) diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadataFactory.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadataFactory.java index 158130874644..0bb1efc579ca 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadataFactory.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadataFactory.java @@ -33,7 +33,7 @@ import java.util.Optional; -import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.memoizeMetastore; +import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.createPerTransactionCache; import static java.util.Objects.requireNonNull; public class DeltaLakeMetadataFactory @@ -102,8 +102,7 @@ public DeltaLakeMetadataFactory( public DeltaLakeMetadata create(ConnectorIdentity identity) { - // create per-transaction cache over hive metastore interface - CachingHiveMetastore cachingHiveMetastore = memoizeMetastore( + CachingHiveMetastore cachingHiveMetastore = createPerTransactionCache( hiveMetastoreFactory.createMetastore(Optional.of(identity)), perTransactionMetastoreCacheMaximumSize); AccessControlMetadata accessControlMetadata = accessControlMetadataFactory.create(cachingHiveMetastore); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadataFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadataFactory.java index 0226e269f7e3..9e04878a434a 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadataFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadataFactory.java @@ -26,6 +26,7 @@ import io.trino.plugin.hive.metastore.HiveMetastoreConfig; import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.plugin.hive.metastore.SemiTransactionalHiveMetastore; +import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; import io.trino.plugin.hive.security.AccessControlMetadataFactory; import io.trino.plugin.hive.statistics.MetastoreHiveStatisticsProvider; import io.trino.spi.connector.MetadataProvider; @@ -39,7 +40,7 @@ import java.util.concurrent.ScheduledExecutorService; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; -import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.memoizeMetastore; +import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.createPerTransactionCache; import static java.util.Objects.requireNonNull; public class HiveMetadataFactory @@ -227,8 +228,8 @@ public HiveMetadataFactory( @Override public TransactionalMetadata create(ConnectorIdentity identity, boolean autoCommit) { - HiveMetastoreClosure hiveMetastoreClosure = new HiveMetastoreClosure( - memoizeMetastore(metastoreFactory.createMetastore(Optional.of(identity)), perTransactionCacheMaximumSize)); // per-transaction cache + CachingHiveMetastore cachingHiveMetastore = createPerTransactionCache(metastoreFactory.createMetastore(Optional.of(identity)), perTransactionCacheMaximumSize); + HiveMetastoreClosure hiveMetastoreClosure = new HiveMetastoreClosure(cachingHiveMetastore); DirectoryLister directoryLister = transactionScopeCachingDirectoryListerFactory.get(this.directoryLister); SemiTransactionalHiveMetastore metastore = new SemiTransactionalHiveMetastore( diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePageSinkProvider.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePageSinkProvider.java index e772b27ad6ca..1b4219dce3c5 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePageSinkProvider.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePageSinkProvider.java @@ -25,6 +25,7 @@ import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.plugin.hive.metastore.HivePageSinkMetadataProvider; import io.trino.plugin.hive.metastore.SortingColumn; +import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; import io.trino.spi.NodeManager; import io.trino.spi.PageIndexerFactory; import io.trino.spi.PageSorter; @@ -49,7 +50,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.util.concurrent.MoreExecutors.listeningDecorator; import static io.airlift.concurrent.Threads.daemonThreadsNamed; -import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.memoizeMetastore; +import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.createPerTransactionCache; import static java.util.Objects.requireNonNull; import static java.util.concurrent.Executors.newFixedThreadPool; @@ -154,6 +155,7 @@ private HivePageSink createPageSink(HiveWritableTableHandle handle, boolean isCr sortedBy = handle.getBucketProperty().get().getSortedBy(); } + CachingHiveMetastore cachingHiveMetastore = createPerTransactionCache(metastoreFactory.createMetastore(Optional.of(session.getIdentity())), perTransactionMetastoreCacheMaximumSize); HiveWriterFactory writerFactory = new HiveWriterFactory( fileWriterFactories, fileSystemFactory, @@ -170,9 +172,7 @@ private HivePageSink createPageSink(HiveWritableTableHandle handle, boolean isCr handle.getLocationHandle(), locationService, session.getQueryId(), - new HivePageSinkMetadataProvider( - handle.getPageSinkMetadata(), - new HiveMetastoreClosure(memoizeMetastore(metastoreFactory.createMetastore(Optional.of(session.getIdentity())), perTransactionMetastoreCacheMaximumSize))), + new HivePageSinkMetadataProvider(handle.getPageSinkMetadata(), new HiveMetastoreClosure(cachingHiveMetastore)), typeManager, pageSorter, writerSortBufferSize, diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java index cf11bbae1435..f69b70c35306 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java @@ -22,8 +22,6 @@ import com.google.common.collect.Sets.SetView; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.UncheckedExecutionException; -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import com.google.errorprone.annotations.Immutable; import com.google.errorprone.annotations.ThreadSafe; import io.airlift.jmx.CacheStatsMBean; import io.airlift.units.Duration; @@ -104,7 +102,7 @@ * Hive Metastore Cache */ @ThreadSafe -public class CachingHiveMetastore +public final class CachingHiveMetastore implements HiveMetastore { public enum StatsRecording @@ -113,7 +111,7 @@ public enum StatsRecording DISABLED } - protected final HiveMetastore delegate; + private final HiveMetastore delegate; private final boolean cacheMissing; private final LoadingCache> databaseCache; private final LoadingCache> databaseNamesCache; @@ -132,231 +130,75 @@ public enum StatsRecording private final LoadingCache> roleGrantsCache; private final LoadingCache> configValuesCache; - public static CachingHiveMetastoreBuilder builder() - { - return new CachingHiveMetastoreBuilder(); - } - - public static CachingHiveMetastoreBuilder builder(CachingHiveMetastoreBuilder other) - { - return new CachingHiveMetastoreBuilder( - other.delegate, - other.executor, - other.metadataCacheEnabled, - other.statsCacheEnabled, - other.expiresAfterWriteMillis, - other.statsExpiresAfterWriteMillis, - other.refreshMills, - other.maximumSize, - other.statsRecording, - other.cacheMissing, - other.partitionCacheEnabled); - } - - public static CachingHiveMetastore memoizeMetastore(HiveMetastore delegate, long maximumSize) - { - return builder() - .delegate(delegate) - .metadataCacheEnabled(true) - .statsCacheEnabled(true) - .maximumSize(maximumSize) - .statsRecording(StatsRecording.DISABLED) - .cacheMissing(true) - .partitionCacheEnabled(true) - .build(); - } - - @Immutable - public static class CachingHiveMetastoreBuilder - { - private HiveMetastore delegate; - private Optional executor = Optional.empty(); - private Boolean metadataCacheEnabled; - private Boolean statsCacheEnabled; - private OptionalLong expiresAfterWriteMillis = OptionalLong.empty(); - private OptionalLong statsExpiresAfterWriteMillis = OptionalLong.empty(); - private OptionalLong refreshMills = OptionalLong.empty(); - private Long maximumSize; - private StatsRecording statsRecording = StatsRecording.ENABLED; - private Boolean cacheMissing; - private Boolean partitionCacheEnabled; - - public CachingHiveMetastoreBuilder() {} - - private CachingHiveMetastoreBuilder( - HiveMetastore delegate, - Optional executor, - boolean metadataCacheEnabled, - boolean statsCacheEnabled, - OptionalLong expiresAfterWriteMillis, - OptionalLong statsExpiresAfterWriteMillis, - OptionalLong refreshMills, - Long maximumSize, - StatsRecording statsRecording, - Boolean cacheMissing, - Boolean partitionCacheEnabled) - { - this.delegate = delegate; - this.executor = executor; - this.metadataCacheEnabled = metadataCacheEnabled; - this.statsCacheEnabled = statsCacheEnabled; - this.expiresAfterWriteMillis = expiresAfterWriteMillis; - this.statsExpiresAfterWriteMillis = statsExpiresAfterWriteMillis; - this.refreshMills = refreshMills; - this.maximumSize = maximumSize; - this.statsRecording = statsRecording; - this.cacheMissing = cacheMissing; - this.partitionCacheEnabled = partitionCacheEnabled; - } - - @CanIgnoreReturnValue - public CachingHiveMetastoreBuilder delegate(HiveMetastore delegate) - { - this.delegate = requireNonNull(delegate, "delegate is null"); - return this; - } - - @CanIgnoreReturnValue - public CachingHiveMetastoreBuilder executor(Executor executor) - { - this.executor = Optional.of(requireNonNull(executor, "executor is null")); - return this; - } - - @CanIgnoreReturnValue - public CachingHiveMetastoreBuilder metadataCacheEnabled(boolean metadataCacheEnabled) - { - this.metadataCacheEnabled = metadataCacheEnabled; - return this; - } - - @CanIgnoreReturnValue - public CachingHiveMetastoreBuilder statsCacheEnabled(boolean statsCacheEnabled) - { - this.statsCacheEnabled = statsCacheEnabled; - return this; - } - - @CanIgnoreReturnValue - public CachingHiveMetastoreBuilder cacheTtl(Duration cacheTtl) - { - expiresAfterWriteMillis = OptionalLong.of(requireNonNull(cacheTtl, "cacheTtl is null").toMillis()); - return this; - } - - @CanIgnoreReturnValue - public CachingHiveMetastoreBuilder statsCacheTtl(Duration statsCacheTtl) - { - statsExpiresAfterWriteMillis = OptionalLong.of(requireNonNull(statsCacheTtl, "statsCacheTtl is null").toMillis()); - return this; - } + public static CachingHiveMetastore createPerTransactionCache(HiveMetastore delegate, long maximumSize) + { + return new CachingHiveMetastore( + delegate, + true, + new CacheFactory(maximumSize), + new CacheFactory(maximumSize), + new CacheFactory(maximumSize), + new CacheFactory(maximumSize)); + } - @CanIgnoreReturnValue - public CachingHiveMetastoreBuilder refreshInterval(Duration refreshInterval) - { - return refreshInterval(Optional.of(refreshInterval)); - } + public static CachingHiveMetastore createCachingHiveMetastore( + HiveMetastore delegate, + Duration metadataCacheTtl, + Duration statsCacheTtl, + Optional refreshInterval, + Executor refreshExecutor, + long maximumSize, + StatsRecording statsRecording, + boolean cacheMissing, + boolean partitionCacheEnabled) + { + // refresh executor is only required when the refresh interval is set, but the executor is + // always set, so it is simpler to just enforce that + requireNonNull(refreshExecutor, "refreshExecutor is null"); - @CanIgnoreReturnValue - public CachingHiveMetastoreBuilder refreshInterval(Optional refreshInterval) - { - refreshMills = requireNonNull(refreshInterval, "refreshInterval is null") - .map(Duration::toMillis) - .map(OptionalLong::of) - .orElse(OptionalLong.empty()); - return this; - } + long metadataCacheMillis = metadataCacheTtl.toMillis(); + long statsCacheMillis = statsCacheTtl.toMillis(); + checkArgument(metadataCacheMillis > 0 || statsCacheMillis > 0, "Cache not enabled"); - @CanIgnoreReturnValue - public CachingHiveMetastoreBuilder maximumSize(long maximumSize) - { - this.maximumSize = maximumSize; - return this; - } + OptionalLong refreshMillis = refreshInterval.stream().mapToLong(Duration::toMillis).findAny(); - @CanIgnoreReturnValue - public CachingHiveMetastoreBuilder statsRecording(StatsRecording statsRecording) - { - this.statsRecording = requireNonNull(statsRecording, "statsRecording is null"); - return this; + CacheFactory cacheFactory = CacheFactory.NEVER_CACHE; + CacheFactory partitionCacheFactory = CacheFactory.NEVER_CACHE; + if (metadataCacheMillis > 0) { + cacheFactory = new CacheFactory(OptionalLong.of(metadataCacheMillis), refreshMillis, Optional.of(refreshExecutor), maximumSize, statsRecording); + if (partitionCacheEnabled) { + partitionCacheFactory = cacheFactory; + } } - @CanIgnoreReturnValue - public CachingHiveMetastoreBuilder cacheMissing(boolean cacheMissing) - { - this.cacheMissing = cacheMissing; - return this; + CacheFactory statsCacheFactory = CacheFactory.NEVER_CACHE; + CacheFactory partitionStatsCacheFactory = CacheFactory.NEVER_CACHE; + if (statsCacheMillis > 0) { + statsCacheFactory = new CacheFactory(OptionalLong.of(statsCacheMillis), refreshMillis, Optional.of(refreshExecutor), maximumSize, statsRecording); + if (partitionCacheEnabled) { + partitionStatsCacheFactory = statsCacheFactory; + } } - @CanIgnoreReturnValue - public CachingHiveMetastoreBuilder partitionCacheEnabled(boolean partitionCacheEnabled) - { - this.partitionCacheEnabled = partitionCacheEnabled; - return this; - } + return new CachingHiveMetastore( + delegate, + cacheMissing, + cacheFactory, + partitionCacheFactory, + statsCacheFactory, + partitionStatsCacheFactory); + } - public CachingHiveMetastore build() - { - requireNonNull(metadataCacheEnabled, "metadataCacheEnabled not set"); - requireNonNull(statsCacheEnabled, "statsCacheEnabled is null"); - requireNonNull(delegate, "delegate not set"); - requireNonNull(maximumSize, "maximumSize not set"); - requireNonNull(cacheMissing, "cacheMissing not set"); - requireNonNull(partitionCacheEnabled, "partitionCacheEnabled not set"); - return new CachingHiveMetastore( - delegate, - metadataCacheEnabled, - statsCacheEnabled, - expiresAfterWriteMillis, - statsExpiresAfterWriteMillis, - refreshMills, - executor, - maximumSize, - statsRecording, - cacheMissing, - partitionCacheEnabled); - } - } - - protected CachingHiveMetastore( + private CachingHiveMetastore( HiveMetastore delegate, - boolean metadataCacheEnabled, - boolean statsCacheEnabled, - OptionalLong expiresAfterWriteMillis, - OptionalLong statsExpiresAfterWriteMillis, - OptionalLong refreshMills, - Optional executor, - long maximumSize, - StatsRecording statsRecording, boolean cacheMissing, - boolean partitionCacheEnabled) + CacheFactory cacheFactory, + CacheFactory partitionCacheFactory, + CacheFactory statsCacheFactory, + CacheFactory partitionStatsCacheFactory) { - checkArgument(metadataCacheEnabled || statsCacheEnabled, "Cache not enabled"); this.delegate = requireNonNull(delegate, "delegate is null"); this.cacheMissing = cacheMissing; - requireNonNull(executor, "executor is null"); - - CacheFactory cacheFactory; - CacheFactory partitionCacheFactory; - if (metadataCacheEnabled) { - cacheFactory = cacheFactory(expiresAfterWriteMillis, refreshMills, executor, maximumSize, statsRecording); - partitionCacheFactory = partitionCacheEnabled ? cacheFactory : neverCacheFactory(); - } - else { - cacheFactory = neverCacheFactory(); - partitionCacheFactory = neverCacheFactory(); - } - - CacheFactory statsCacheFactory; - CacheFactory partitionStatsCacheFactory; - if (statsCacheEnabled) { - statsCacheFactory = cacheFactory(statsExpiresAfterWriteMillis, refreshMills, executor, maximumSize, statsRecording); - partitionStatsCacheFactory = partitionCacheEnabled ? statsCacheFactory : neverCacheFactory(); - } - else { - statsCacheFactory = neverCacheFactory(); - partitionStatsCacheFactory = neverCacheFactory(); - } databaseNamesCache = cacheFactory.buildCache(ignored -> loadAllDatabases()); databaseCache = cacheFactory.buildCache(this::loadDatabase); @@ -541,7 +383,7 @@ private static Map getAll( keys.forEach(key -> { // make sure the value holder is retrieved before the new values are loaded - // so that in case of invalidation we will not set the stale value + // so that in case of invalidation, we will not set the stale value AtomicReference currentValueHolder = uncheckedCacheGet(cache, key, AtomicReference::new); V currentValue = currentValueHolder.get(); if (currentValue != null && isSufficient.test(currentValue)) { @@ -830,7 +672,7 @@ public void setDatabaseOwner(String databaseName, HivePrincipal principal) } } - protected void invalidateDatabase(String databaseName) + private void invalidateDatabase(String databaseName) { databaseCache.invalidate(databaseName); databaseNamesCache.invalidateAll(); @@ -1342,26 +1184,6 @@ public void dropFunction(String databaseName, String functionName, String signat delegate.dropFunction(databaseName, functionName, signatureToken); } - private static CacheFactory cacheFactory( - OptionalLong expiresAfterWriteMillis, - OptionalLong refreshMillis, - Optional refreshExecutor, - long maximumSize, - StatsRecording statsRecording) - { - return new CacheFactory(expiresAfterWriteMillis, refreshMillis, refreshExecutor, maximumSize, statsRecording); - } - - private static CacheFactory neverCacheFactory() - { - return cacheFactory( - OptionalLong.of(0), - OptionalLong.empty(), - Optional.empty(), - 0, - StatsRecording.DISABLED); - } - private static LoadingCache buildCache( OptionalLong expiresAfterWriteMillis, OptionalLong refreshMillis, @@ -1612,31 +1434,33 @@ LoadingCache> getConfigValuesCache() return configValuesCache; } - private static class CacheFactory + private record CacheFactory(OptionalLong expiresAfterWriteMillis, OptionalLong refreshMillis, Optional refreshExecutor, long maximumSize, StatsRecording statsRecording) { - private final OptionalLong expiresAfterWriteMillis; - private final OptionalLong refreshMillis; - private final Optional refreshExecutor; - private final long maximumSize; - private final StatsRecording statsRecording; + private static final CacheFactory NEVER_CACHE = new CacheFactory(OptionalLong.empty(), OptionalLong.empty(), Optional.empty(), 0, StatsRecording.DISABLED); + + private CacheFactory(long maximumSize) + { + this(OptionalLong.empty(), OptionalLong.empty(), Optional.empty(), maximumSize, StatsRecording.DISABLED); + } - public CacheFactory(OptionalLong expiresAfterWriteMillis, OptionalLong refreshMillis, Optional refreshExecutor, long maximumSize, StatsRecording statsRecording) + private CacheFactory { - this.expiresAfterWriteMillis = requireNonNull(expiresAfterWriteMillis, "expiresAfterWriteMillis is null"); - this.refreshMillis = requireNonNull(refreshMillis, "refreshMillis is null"); - this.refreshExecutor = requireNonNull(refreshExecutor, "refreshExecutor is null"); - this.maximumSize = maximumSize; - this.statsRecording = requireNonNull(statsRecording, "statsRecording is null"); + requireNonNull(expiresAfterWriteMillis, "expiresAfterWriteMillis is null"); + checkArgument(expiresAfterWriteMillis.isEmpty() || expiresAfterWriteMillis.getAsLong() > 0, "expiresAfterWriteMillis must be empty or at least 1 millisecond"); + requireNonNull(refreshMillis, "refreshMillis is null"); + checkArgument(refreshMillis.isEmpty() || refreshMillis.getAsLong() > 0, "refreshMillis must be empty or at least 1 millisecond"); + requireNonNull(refreshExecutor, "refreshExecutor is null"); + requireNonNull(statsRecording, "statsRecording is null"); } - public LoadingCache buildCache(com.google.common.base.Function loader) + public LoadingCache buildCache(Function loader) { - return CachingHiveMetastore.buildCache(expiresAfterWriteMillis, refreshMillis, refreshExecutor, maximumSize, statsRecording, CacheLoader.from(loader)); + return CachingHiveMetastore.buildCache(expiresAfterWriteMillis, refreshMillis, refreshExecutor, maximumSize, statsRecording, CacheLoader.from(loader::apply)); } - public Cache buildCache(BiFunction reloader) + public Cache buildCache(BiFunction loader) { - CacheLoader onlyReloader = new CacheLoader<>() + CacheLoader cacheLoader = new CacheLoader<>() { @Override public V load(K key) @@ -1650,10 +1474,10 @@ public ListenableFuture reload(K key, V oldValue) requireNonNull(key); requireNonNull(oldValue); // async reloading is configured in CachingHiveMetastore.buildCache if refreshMillis is present - return immediateFuture(reloader.apply(key, oldValue)); + return immediateFuture(loader.apply(key, oldValue)); } }; - return CachingHiveMetastore.buildCache(expiresAfterWriteMillis, refreshMillis, refreshExecutor, maximumSize, statsRecording, onlyReloader); + return CachingHiveMetastore.buildCache(expiresAfterWriteMillis, refreshMillis, refreshExecutor, maximumSize, statsRecording, cacheLoader); } public Cache> buildBulkCache() diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/SharedHiveMetastoreCache.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/SharedHiveMetastoreCache.java index 329e76df0578..aeeefbbf6033 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/SharedHiveMetastoreCache.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/SharedHiveMetastoreCache.java @@ -25,7 +25,6 @@ import io.trino.plugin.base.CatalogName; import io.trino.plugin.hive.metastore.HiveMetastore; import io.trino.plugin.hive.metastore.HiveMetastoreFactory; -import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.CachingHiveMetastoreBuilder; import io.trino.spi.NodeManager; import io.trino.spi.TrinoException; import io.trino.spi.security.ConnectorIdentity; @@ -51,11 +50,18 @@ public class SharedHiveMetastoreCache { private final boolean enabled; private final CatalogName catalogName; + + private final Duration metadataCacheTtl; + private final Duration statsCacheTtl; + + private final Optional metastoreRefreshInterval; + private final long metastoreCacheMaximumSize; private final int maxMetastoreRefreshThreads; private final Duration userMetastoreCacheTtl; private final long userMetastoreCacheMaximumSize; - private final CachingHiveMetastoreBuilder cachingMetastoreBuilder; + private final boolean metastorePartitionCacheEnabled; + private final boolean cacheMissing; private ExecutorService executorService; @@ -70,34 +76,29 @@ public SharedHiveMetastoreCache( requireNonNull(catalogName, "catalogName is null"); this.catalogName = catalogName; + + metadataCacheTtl = config.getMetastoreCacheTtl(); + if (metadataCacheTtl.compareTo(config.getStatsCacheTtl()) > 0) { + statsCacheTtl = metadataCacheTtl; + } + else { + statsCacheTtl = config.getStatsCacheTtl(); + } + maxMetastoreRefreshThreads = config.getMaxMetastoreRefreshThreads(); + metastoreRefreshInterval = config.getMetastoreRefreshInterval(); + metastoreCacheMaximumSize = config.getMetastoreCacheMaximumSize(); + metastorePartitionCacheEnabled = config.isPartitionCacheEnabled(); + cacheMissing = config.isCacheMissing(); + userMetastoreCacheTtl = impersonationCachingConfig.getUserMetastoreCacheTtl(); userMetastoreCacheMaximumSize = impersonationCachingConfig.getUserMetastoreCacheMaximumSize(); // Disable caching on workers, because there currently is no way to invalidate such a cache. // Note: while we could skip CachingHiveMetastoreModule altogether on workers, we retain it so that catalog // configuration can remain identical for all nodes, making cluster configuration easier. - Duration metastoreCacheTtl = config.getMetastoreCacheTtl(); - Duration statsCacheTtl = config.getStatsCacheTtl(); - if (metastoreCacheTtl.compareTo(statsCacheTtl) > 0) { - statsCacheTtl = metastoreCacheTtl; - } - - boolean metadataCacheEnabled = metastoreCacheTtl.toMillis() > 0; - boolean statsCacheEnabled = statsCacheTtl.toMillis() > 0; - enabled = (metadataCacheEnabled || statsCacheEnabled) && - nodeManager.getCurrentNode().isCoordinator() && - config.getMetastoreCacheMaximumSize() > 0; - - cachingMetastoreBuilder = CachingHiveMetastore.builder() - .metadataCacheEnabled(metadataCacheEnabled) - .statsCacheEnabled(statsCacheEnabled) - .cacheTtl(metastoreCacheTtl) - .statsCacheTtl(statsCacheTtl) - .refreshInterval(config.getMetastoreRefreshInterval()) - .maximumSize(config.getMetastoreCacheMaximumSize()) - .cacheMissing(config.isCacheMissing()) - .partitionCacheEnabled(config.isPartitionCacheEnabled()); + enabled = nodeManager.getCurrentNode().isCoordinator() && + (metadataCacheTtl.toMillis() > 0 || statsCacheTtl.toMillis() > 0); } @PostConstruct @@ -133,17 +134,27 @@ public HiveMetastoreFactory createCachingHiveMetastoreFactory(HiveMetastoreFacto if (userMetastoreCacheMaximumSize == 0 || userMetastoreCacheTtl.toMillis() == 0) { return metastoreFactory; } - return new ImpersonationCachingHiveMetastoreFactory(metastoreFactory); + return new ImpersonationCachingHiveMetastoreFactory( + user -> createCachingHiveMetastore(metastoreFactory, Optional.of(ConnectorIdentity.ofUser(user))), + userMetastoreCacheTtl, + userMetastoreCacheMaximumSize); } - CachingHiveMetastore cachingHiveMetastore = CachingHiveMetastore.builder(cachingMetastoreBuilder) - // Loading of cache entry in CachingHiveMetastore might trigger loading of another cache entry for different object type - // In case there are no empty executor slots, such operation would deadlock. Therefore, a reentrant executor needs to be - // used. - .delegate(metastoreFactory.createMetastore(Optional.empty())) - .executor(new ReentrantBoundedExecutor(executorService, maxMetastoreRefreshThreads)) - .build(); - return new CachingHiveMetastoreFactory(cachingHiveMetastore); + return new CachingHiveMetastoreFactory(createCachingHiveMetastore(metastoreFactory, Optional.empty())); + } + + private CachingHiveMetastore createCachingHiveMetastore(HiveMetastoreFactory metastoreFactory, Optional identity) + { + return CachingHiveMetastore.createCachingHiveMetastore( + metastoreFactory.createMetastore(identity), + metadataCacheTtl, + statsCacheTtl, + metastoreRefreshInterval, + new ReentrantBoundedExecutor(executorService, maxMetastoreRefreshThreads), + metastoreCacheMaximumSize, + CachingHiveMetastore.StatsRecording.ENABLED, + cacheMissing, + metastorePartitionCacheEnabled); } public static class CachingHiveMetastoreFactory @@ -176,20 +187,18 @@ public CachingHiveMetastore getMetastore() } } - public class ImpersonationCachingHiveMetastoreFactory + public static class ImpersonationCachingHiveMetastoreFactory implements HiveMetastoreFactory { - private final HiveMetastoreFactory metastoreFactory; private final LoadingCache cache; - public ImpersonationCachingHiveMetastoreFactory(HiveMetastoreFactory metastoreFactory) + public ImpersonationCachingHiveMetastoreFactory(Function cachingHiveMetastoreFactory, Duration userCacheTtl, long userCacheMaximumSize) { - this.metastoreFactory = metastoreFactory; cache = buildNonEvictableCache( CacheBuilder.newBuilder() - .expireAfterWrite(userMetastoreCacheTtl.toMillis(), MILLISECONDS) - .maximumSize(userMetastoreCacheMaximumSize), - CacheLoader.from(this::createUserCachingMetastore)); + .expireAfterWrite(userCacheTtl.toMillis(), MILLISECONDS) + .maximumSize(userCacheMaximumSize), + CacheLoader.from(cachingHiveMetastoreFactory::apply)); } @Override @@ -211,15 +220,6 @@ public HiveMetastore createMetastore(Optional identity) } } - private CachingHiveMetastore createUserCachingMetastore(String user) - { - ConnectorIdentity identity = ConnectorIdentity.ofUser(user); - return CachingHiveMetastore.builder(cachingMetastoreBuilder) - .delegate(metastoreFactory.createMetastore(Optional.of(identity))) - .executor(new ReentrantBoundedExecutor(executorService, maxMetastoreRefreshThreads)) - .build(); - } - @Managed public void flushCache() { diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java index 07b5a1b41440..4ea64841218e 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java @@ -268,6 +268,7 @@ import static io.trino.plugin.hive.metastore.SortingColumn.Order.ASCENDING; import static io.trino.plugin.hive.metastore.SortingColumn.Order.DESCENDING; import static io.trino.plugin.hive.metastore.StorageFormat.fromHiveStorageFormat; +import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.createCachingHiveMetastore; import static io.trino.plugin.hive.orc.OrcPageSource.ORC_CODEC_METRIC_PREFIX; import static io.trino.plugin.hive.util.HiveBucketing.BucketingVersion.BUCKETING_V1; import static io.trino.plugin.hive.util.HiveUtil.DELTA_LAKE_PROVIDER; @@ -787,23 +788,24 @@ protected final void setup(HostAndPort metastoreAddress, String databaseName) .setRcfileTimeZone("UTC"); hdfsEnvironment = HDFS_ENVIRONMENT; - HiveMetastore metastore = CachingHiveMetastore.builder() - .delegate(new BridgingHiveMetastore(testingThriftHiveMetastoreBuilder() + + CachingHiveMetastoreConfig cachingHiveMetastoreConfig = new CachingHiveMetastoreConfig(); + HiveMetastore metastore = createCachingHiveMetastore( + new BridgingHiveMetastore(testingThriftHiveMetastoreBuilder() .metastoreClient(metastoreAddress) .hiveConfig(hiveConfig) .thriftMetastoreConfig(new ThriftMetastoreConfig() .setAssumeCanonicalPartitionKeys(true)) .hdfsEnvironment(hdfsEnvironment) - .build())) - .executor(executor) - .metadataCacheEnabled(true) - .statsCacheEnabled(true) - .cacheTtl(new Duration(1, MINUTES)) - .refreshInterval(new Duration(15, SECONDS)) - .maximumSize(10000) - .cacheMissing(new CachingHiveMetastoreConfig().isCacheMissing()) - .partitionCacheEnabled(new CachingHiveMetastoreConfig().isPartitionCacheEnabled()) - .build(); + .build()), + new Duration(1, MINUTES), + new Duration(1, MINUTES), + Optional.of(new Duration(15, SECONDS)), + executor, + 10000, + CachingHiveMetastore.StatsRecording.ENABLED, + cachingHiveMetastoreConfig.isCacheMissing(), + cachingHiveMetastoreConfig.isPartitionCacheEnabled()); setup(databaseName, hiveConfig, metastore, hdfsEnvironment); } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastore.java index f8486585c11c..1446fb08a0e6 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastore.java @@ -37,7 +37,6 @@ import io.trino.plugin.hive.metastore.PrincipalPrivileges; import io.trino.plugin.hive.metastore.Table; import io.trino.plugin.hive.metastore.UnimplementedHiveMetastore; -import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.CachingHiveMetastoreBuilder; import io.trino.plugin.hive.metastore.thrift.BridgingHiveMetastore; import io.trino.plugin.hive.metastore.thrift.MockThriftMetastoreClient; import io.trino.plugin.hive.metastore.thrift.ThriftHiveMetastore; @@ -66,6 +65,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -73,7 +73,6 @@ import java.util.function.Consumer; import static com.google.common.base.Preconditions.checkState; -import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static com.google.common.util.concurrent.MoreExecutors.listeningDecorator; import static io.airlift.concurrent.Threads.daemonThreadsNamed; import static io.airlift.slice.Slices.utf8Slice; @@ -90,8 +89,7 @@ import static io.trino.plugin.hive.metastore.MetastoreUtil.makePartitionName; import static io.trino.plugin.hive.metastore.StorageFormat.VIEW_STORAGE_FORMAT; import static io.trino.plugin.hive.metastore.StorageFormat.fromHiveStorageFormat; -import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.memoizeMetastore; -import static io.trino.plugin.hive.metastore.cache.TestCachingHiveMetastore.PartitionCachingAssertions.assertThatCachingWithDisabledPartitionCache; +import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.createPerTransactionCache; import static io.trino.plugin.hive.metastore.thrift.MockThriftMetastoreClient.BAD_DATABASE; import static io.trino.plugin.hive.metastore.thrift.MockThriftMetastoreClient.BAD_PARTITION; import static io.trino.plugin.hive.metastore.thrift.MockThriftMetastoreClient.PARTITION_COLUMN_NAMES; @@ -130,10 +128,11 @@ public class TestCachingHiveMetastore .setColumnStatistics(ImmutableMap.of(TEST_COLUMN, createIntegerColumnStatistics(OptionalLong.empty(), OptionalLong.empty(), OptionalLong.empty(), OptionalLong.empty()))) .build(); private static final SchemaTableName TEST_SCHEMA_TABLE = new SchemaTableName(TEST_DATABASE, TEST_TABLE); + private static final Duration CACHE_TTL = new Duration(5, TimeUnit.MINUTES); private MockThriftMetastoreClient mockClient; + private ThriftMetastore thriftHiveMetastore; private ListeningExecutorService executor; - private CachingHiveMetastoreBuilder metastoreBuilder; private CachingHiveMetastore metastore; private CachingHiveMetastore statsOnlyCacheMetastore; private ThriftMetastoreStats stats; @@ -142,25 +141,11 @@ public class TestCachingHiveMetastore public void setUp() { mockClient = new MockThriftMetastoreClient(); - ThriftMetastore thriftHiveMetastore = createThriftHiveMetastore(); + thriftHiveMetastore = createThriftHiveMetastore(); executor = listeningDecorator(newCachedThreadPool(daemonThreadsNamed(getClass().getSimpleName() + "-%s"))); - metastoreBuilder = CachingHiveMetastore.builder() - .delegate(new BridgingHiveMetastore(thriftHiveMetastore)) - .executor(executor) - .metadataCacheEnabled(true) - .statsCacheEnabled(true) - .cacheTtl(new Duration(5, TimeUnit.MINUTES)) - .refreshInterval(new Duration(1, TimeUnit.MINUTES)) - .maximumSize(1000) - .cacheMissing(new CachingHiveMetastoreConfig().isCacheMissing()) - .partitionCacheEnabled(true); - - metastore = metastoreBuilder.build(); - statsOnlyCacheMetastore = CachingHiveMetastore.builder(metastoreBuilder) - .metadataCacheEnabled(false) - .statsCacheEnabled(true) // only cache stats - .build(); + metastore = createCachingHiveMetastore(new BridgingHiveMetastore(thriftHiveMetastore), CACHE_TTL, true, true, executor); + statsOnlyCacheMetastore = createCachingHiveMetastore(new BridgingHiveMetastore(thriftHiveMetastore), Duration.ZERO, true, true, executor); stats = ((ThriftHiveMetastore) thriftHiveMetastore).getStats(); } @@ -638,7 +623,7 @@ public List getTableColumnStatistics(String databaseName, S return result; } }; - CachingHiveMetastore metastore = createMetastore(mockClient); + CachingHiveMetastore metastore = createCachingHiveMetastore(new BridgingHiveMetastore(createThriftHiveMetastore(mockClient)), CACHE_TTL, true, true, executor); Table table = metastore.getTable(TEST_DATABASE, TEST_TABLE).orElseThrow(); @@ -812,7 +797,7 @@ public Map> getPartitionColumnStatistics(Strin return result; } }; - CachingHiveMetastore metastore = createMetastore(mockClient); + CachingHiveMetastore metastore = createCachingHiveMetastore(new BridgingHiveMetastore(createThriftHiveMetastore(mockClient)), CACHE_TTL, true, true, executor); Table table = metastore.getTable(TEST_DATABASE, TEST_TABLE).orElseThrow(); @@ -848,21 +833,6 @@ public Map> getPartitionColumnStatistics(Strin } } - private CachingHiveMetastore createMetastore(MockThriftMetastoreClient mockClient) - { - return CachingHiveMetastore.builder() - .delegate(new BridgingHiveMetastore(createThriftHiveMetastore(mockClient))) - .executor(executor) - .metadataCacheEnabled(true) - .statsCacheEnabled(true) - .cacheTtl(new Duration(5, TimeUnit.MINUTES)) - .refreshInterval(new Duration(1, TimeUnit.MINUTES)) - .maximumSize(1000) - .cacheMissing(new CachingHiveMetastoreConfig().isCacheMissing()) - .partitionCacheEnabled(true) - .build(); - } - @Test public void testUpdatePartitionStatistics() { @@ -911,9 +881,7 @@ public void testNoCacheExceptions() @Test public void testNoCacheMissing() { - CachingHiveMetastore metastore = CachingHiveMetastore.builder(metastoreBuilder) - .cacheMissing(false) - .build(); + CachingHiveMetastore metastore = createCachingHiveMetastore(new BridgingHiveMetastore(thriftHiveMetastore), CACHE_TTL, false, true, executor); mockClient.setReturnTable(false); assertEquals(mockClient.getAccessCount(), 0); @@ -947,24 +915,10 @@ public void testNoCacheMissing() assertEquals(mockClient.getAccessCount(), 4); } - @Test - public void testCachingHiveMetastoreCreationWithTtlOnly() - { - CachingHiveMetastoreConfig config = new CachingHiveMetastoreConfig(); - config.setMetastoreCacheTtl(new Duration(10, TimeUnit.MILLISECONDS)); - - CachingHiveMetastore metastore = createMetastoreWithDirectExecutor(config); - - assertThat(metastore).isNotNull(); - } - @Test public void testCachingHiveMetastoreCreationViaMemoize() { - ThriftMetastore thriftHiveMetastore = createThriftHiveMetastore(); - metastore = memoizeMetastore( - new BridgingHiveMetastore(thriftHiveMetastore), - 1000); + metastore = createPerTransactionCache(new BridgingHiveMetastore(createThriftHiveMetastore()), 1000); assertEquals(mockClient.getAccessCount(), 0); assertEquals(metastore.getAllDatabases(), ImmutableList.of(TEST_DATABASE)); @@ -1053,17 +1007,7 @@ public Map> getPartitionsByNames(Table table, List metastoreInteractions = hiveMetastore -> {}; - static PartitionCachingAssertions assertThatCachingWithDisabledPartitionCache() - { - return new PartitionCachingAssertions(); - } - - private PartitionCachingAssertions() + private PartitionCachingAssertions(Executor refreshExecutor) { thriftClient = new MockThriftMetastoreClient(); - cachingHiveMetastore = CachingHiveMetastore.builder() - .delegate(new BridgingHiveMetastore(createThriftHiveMetastore(thriftClient))) - .executor(listeningDecorator(newCachedThreadPool(daemonThreadsNamed("test-%s")))) - .metadataCacheEnabled(true) - .statsCacheEnabled(true) - .cacheTtl(new Duration(5, TimeUnit.MINUTES)) - .refreshInterval(new Duration(1, TimeUnit.MINUTES)) - .maximumSize(1000) - .cacheMissing(true) - .partitionCacheEnabled(false) - .build(); + cachingHiveMetastore = createCachingHiveMetastore(new BridgingHiveMetastore(createThriftHiveMetastore(thriftClient)), CACHE_TTL, true, false, refreshExecutor); } PartitionCachingAssertions whenExecuting(Consumer interactions) @@ -1309,18 +1243,17 @@ void omitsCacheForNumberOfOperations(int expectedCacheOmittingOperations) } } - private CachingHiveMetastore createMetastoreWithDirectExecutor(CachingHiveMetastoreConfig config) + private static CachingHiveMetastore createCachingHiveMetastore(HiveMetastore hiveMetastore, Duration cacheTtl, boolean cacheMissing, boolean partitionCacheEnabled, Executor executor) { - return CachingHiveMetastore.builder() - .delegate(new BridgingHiveMetastore(createThriftHiveMetastore())) - .executor(directExecutor()) - .metadataCacheEnabled(true) - .statsCacheEnabled(true) - .cacheTtl(config.getMetastoreCacheTtl()) - .refreshInterval(config.getMetastoreRefreshInterval()) - .maximumSize(config.getMetastoreCacheMaximumSize()) - .cacheMissing(config.isCacheMissing()) - .partitionCacheEnabled(config.isPartitionCacheEnabled()) - .build(); + return CachingHiveMetastore.createCachingHiveMetastore( + hiveMetastore, + cacheTtl, + CACHE_TTL, + Optional.of(new Duration(1, TimeUnit.MINUTES)), + executor, + 1000, + CachingHiveMetastore.StatsRecording.ENABLED, + cacheMissing, + partitionCacheEnabled); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/file/TestFileHiveMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/file/TestFileHiveMetastore.java index 5d3062eca37c..24b0280cb17e 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/file/TestFileHiveMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/file/TestFileHiveMetastore.java @@ -15,12 +15,12 @@ import com.google.common.collect.ImmutableMap; import io.trino.plugin.hive.NodeVersion; +import io.trino.plugin.hive.TableType; import io.trino.plugin.hive.metastore.Column; import io.trino.plugin.hive.metastore.Database; import io.trino.plugin.hive.metastore.HiveMetastoreConfig; import io.trino.plugin.hive.metastore.StorageFormat; import io.trino.plugin.hive.metastore.Table; -import org.apache.hadoop.hive.metastore.TableType; import org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat; import org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe; import org.junit.jupiter.api.AfterAll; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestingMetastoreObjects.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestingMetastoreObjects.java index 0594cbf8814b..49c1696ce058 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestingMetastoreObjects.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestingMetastoreObjects.java @@ -22,10 +22,10 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.trino.plugin.hive.HiveType; +import io.trino.plugin.hive.TableType; import io.trino.plugin.hive.metastore.Storage; import io.trino.plugin.hive.metastore.StorageFormat; import io.trino.spi.security.PrincipalType; -import org.apache.hadoop.hive.metastore.TableType; import java.util.List; import java.util.Optional; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/MockThriftMetastoreClient.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/MockThriftMetastoreClient.java index 0f1621b034c6..f8275d7323da 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/MockThriftMetastoreClient.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/MockThriftMetastoreClient.java @@ -37,10 +37,10 @@ import io.trino.hive.thrift.metastore.SerDeInfo; import io.trino.hive.thrift.metastore.StorageDescriptor; import io.trino.hive.thrift.metastore.Table; +import io.trino.plugin.hive.TableType; import io.trino.plugin.hive.acid.AcidOperation; import io.trino.spi.connector.SchemaTableName; import io.trino.testng.services.ManageTestResources; -import org.apache.hadoop.hive.metastore.TableType; import org.apache.hadoop.hive.metastore.Warehouse; import org.apache.hadoop.hive.metastore.api.MetaException; import org.apache.thrift.TException; diff --git a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiMetadataFactory.java b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiMetadataFactory.java index 103dfda0b619..4a5fe257a598 100644 --- a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiMetadataFactory.java +++ b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiMetadataFactory.java @@ -22,7 +22,7 @@ import java.util.Optional; -import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.memoizeMetastore; +import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.createPerTransactionCache; import static java.util.Objects.requireNonNull; public class HudiMetadataFactory @@ -43,10 +43,7 @@ public HudiMetadataFactory(HiveMetastoreFactory metastoreFactory, TrinoFileSyste public HudiMetadata create(ConnectorIdentity identity) { - // create per-transaction cache over hive metastore interface - CachingHiveMetastore cachingHiveMetastore = memoizeMetastore( - metastoreFactory.createMetastore(Optional.of(identity)), - perTransactionMetastoreCacheMaximumSize); + CachingHiveMetastore cachingHiveMetastore = createPerTransactionCache(metastoreFactory.createMetastore(Optional.of(identity)), perTransactionMetastoreCacheMaximumSize); return new HudiMetadata(cachingHiveMetastore, fileSystemFactory, typeManager); } } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalogFactory.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalogFactory.java index cc3d405a65cd..ae5c8f01857e 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalogFactory.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalogFactory.java @@ -30,7 +30,7 @@ import java.util.Optional; -import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.memoizeMetastore; +import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.createPerTransactionCache; import static io.trino.plugin.iceberg.IcebergSecurityConfig.IcebergSecurity.SYSTEM; import static io.trino.plugin.iceberg.catalog.AbstractTrinoCatalog.TRINO_CREATED_BY_VALUE; import static java.util.Objects.requireNonNull; @@ -73,7 +73,7 @@ public TrinoHiveCatalogFactory( @Override public TrinoCatalog create(ConnectorIdentity identity) { - CachingHiveMetastore metastore = memoizeMetastore(metastoreFactory.createMetastore(Optional.of(identity)), 1000); + CachingHiveMetastore metastore = createPerTransactionCache(metastoreFactory.createMetastore(Optional.of(identity)), 1000); return new TrinoHiveCatalog( catalogName, metastore, diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMergeAppend.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMergeAppend.java index 507889779a5e..2889f355fe63 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMergeAppend.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMergeAppend.java @@ -16,7 +16,6 @@ import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.plugin.base.CatalogName; import io.trino.plugin.hive.TrinoViewHiveMetastore; -import io.trino.plugin.hive.metastore.HiveMetastore; import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; import io.trino.plugin.iceberg.catalog.IcebergTableOperationsProvider; import io.trino.plugin.iceberg.catalog.TrinoCatalog; @@ -34,7 +33,7 @@ import java.io.File; -import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.memoizeMetastore; +import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.createPerTransactionCache; import static io.trino.plugin.hive.metastore.file.TestingFileHiveMetastore.createTestingFileHiveMetastore; import static io.trino.plugin.iceberg.IcebergTestUtils.getFileSystemFactory; import static org.testng.Assert.assertEquals; @@ -51,10 +50,9 @@ protected QueryRunner createQueryRunner() { DistributedQueryRunner queryRunner = IcebergQueryRunner.createIcebergQueryRunner(); File baseDir = queryRunner.getCoordinator().getBaseDataDir().resolve("iceberg_data").toFile(); - HiveMetastore metastore = createTestingFileHiveMetastore(baseDir); + CachingHiveMetastore cachingHiveMetastore = createPerTransactionCache(createTestingFileHiveMetastore(baseDir), 1000); TrinoFileSystemFactory fileSystemFactory = getFileSystemFactory(queryRunner); tableOperationsProvider = new FileMetastoreTableOperationsProvider(fileSystemFactory); - CachingHiveMetastore cachingHiveMetastore = memoizeMetastore(metastore, 1000); trinoCatalog = new TrinoHiveCatalog( new CatalogName("catalog"), cachingHiveMetastore, diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergOrcMetricsCollection.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergOrcMetricsCollection.java index d35c1265dcc8..5c7a6e77c155 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergOrcMetricsCollection.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergOrcMetricsCollection.java @@ -48,7 +48,7 @@ import static io.trino.SystemSessionProperties.TASK_CONCURRENCY; import static io.trino.SystemSessionProperties.TASK_MAX_WRITER_COUNT; import static io.trino.SystemSessionProperties.TASK_MIN_WRITER_COUNT; -import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.memoizeMetastore; +import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.createPerTransactionCache; import static io.trino.plugin.hive.metastore.file.TestingFileHiveMetastore.createTestingFileHiveMetastore; import static io.trino.plugin.iceberg.DataFileRecord.toDataFileRecord; import static io.trino.plugin.iceberg.IcebergQueryRunner.ICEBERG_CATALOG; @@ -88,7 +88,7 @@ protected QueryRunner createQueryRunner() TrinoFileSystemFactory fileSystemFactory = getFileSystemFactory(queryRunner); tableOperationsProvider = new FileMetastoreTableOperationsProvider(fileSystemFactory); - CachingHiveMetastore cachingHiveMetastore = memoizeMetastore(metastore, 1000); + CachingHiveMetastore cachingHiveMetastore = createPerTransactionCache(metastore, 1000); trinoCatalog = new TrinoHiveCatalog( new CatalogName("catalog"), cachingHiveMetastore, diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergSplitSource.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergSplitSource.java index fee9595614f9..10aa9f8e3553 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergSplitSource.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergSplitSource.java @@ -68,7 +68,7 @@ import static com.google.common.io.MoreFiles.deleteRecursively; import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; -import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.memoizeMetastore; +import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.createPerTransactionCache; import static io.trino.plugin.hive.metastore.file.TestingFileHiveMetastore.createTestingFileHiveMetastore; import static io.trino.plugin.iceberg.IcebergTestUtils.getFileSystemFactory; import static io.trino.spi.connector.Constraint.alwaysTrue; @@ -112,7 +112,7 @@ protected QueryRunner createQueryRunner() .build(); this.fileSystemFactory = getFileSystemFactory(queryRunner); - CachingHiveMetastore cachingHiveMetastore = memoizeMetastore(metastore, 1000); + CachingHiveMetastore cachingHiveMetastore = createPerTransactionCache(metastore, 1000); this.catalog = new TrinoHiveCatalog( new CatalogName("hive"), cachingHiveMetastore, diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergV2.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergV2.java index a1f032aa82d3..afd14a0ef64b 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergV2.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergV2.java @@ -88,7 +88,7 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.io.MoreFiles.deleteRecursively; import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; -import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.memoizeMetastore; +import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.createPerTransactionCache; import static io.trino.plugin.hive.metastore.file.TestingFileHiveMetastore.createTestingFileHiveMetastore; import static io.trino.plugin.iceberg.IcebergTestUtils.getFileSystemFactory; import static io.trino.plugin.iceberg.IcebergUtil.loadIcebergTable; @@ -1026,7 +1026,7 @@ private Table updateTableToV2(String tableName) private BaseTable loadTable(String tableName) { IcebergTableOperationsProvider tableOperationsProvider = new FileMetastoreTableOperationsProvider(fileSystemFactory); - CachingHiveMetastore cachingHiveMetastore = memoizeMetastore(metastore, 1000); + CachingHiveMetastore cachingHiveMetastore = createPerTransactionCache(metastore, 1000); TrinoCatalog catalog = new TrinoHiveCatalog( new CatalogName("hive"), cachingHiveMetastore, diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestTrinoHiveCatalogWithFileMetastore.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestTrinoHiveCatalogWithFileMetastore.java index a2c0f4603d0f..daf79a1af9ad 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestTrinoHiveCatalogWithFileMetastore.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestTrinoHiveCatalogWithFileMetastore.java @@ -34,7 +34,7 @@ import static com.google.common.io.MoreFiles.deleteRecursively; import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; -import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.memoizeMetastore; +import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.createPerTransactionCache; import static io.trino.plugin.hive.metastore.file.TestingFileHiveMetastore.createTestingFileHiveMetastore; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; @@ -65,7 +65,7 @@ public void tearDown() protected TrinoCatalog createTrinoCatalog(boolean useUniqueTableLocations) { TrinoFileSystemFactory fileSystemFactory = HDFS_FILE_SYSTEM_FACTORY; - CachingHiveMetastore cachingHiveMetastore = memoizeMetastore(metastore, 1000); + CachingHiveMetastore cachingHiveMetastore = createPerTransactionCache(metastore, 1000); return new TrinoHiveCatalog( new CatalogName("catalog"), cachingHiveMetastore, diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/hms/TestTrinoHiveCatalogWithHiveMetastore.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/hms/TestTrinoHiveCatalogWithHiveMetastore.java index 51a139524bff..c5e1454dca6b 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/hms/TestTrinoHiveCatalogWithHiveMetastore.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/hms/TestTrinoHiveCatalogWithHiveMetastore.java @@ -49,7 +49,7 @@ import static com.google.common.base.Verify.verify; import static io.trino.plugin.hive.TestingThriftHiveMetastoreBuilder.testingThriftHiveMetastoreBuilder; import static io.trino.plugin.hive.containers.HiveHadoop.HIVE3_IMAGE; -import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.memoizeMetastore; +import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.createPerTransactionCache; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.testing.containers.Minio.MINIO_ACCESS_KEY; import static io.trino.testing.containers.Minio.MINIO_SECRET_KEY; @@ -105,7 +105,7 @@ protected TrinoCatalog createTrinoCatalog(boolean useUniqueTableLocations) .setReadTimeout(new Duration(1, MINUTES))) .metastoreClient(dataLake.getHiveHadoop().getHiveMetastoreEndpoint()) .build(); - CachingHiveMetastore metastore = memoizeMetastore(new BridgingHiveMetastore(thriftMetastore), 1000); + CachingHiveMetastore metastore = createPerTransactionCache(new BridgingHiveMetastore(thriftMetastore), 1000); return new TrinoHiveCatalog( new CatalogName("catalog"), metastore, From dfc3a3c9d7af7bbf3b84a71dd4478071405471bc Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Fri, 3 Nov 2023 18:05:29 -0700 Subject: [PATCH 082/587] Replace Hadoop TableType with Trino version --- .../test/java/io/trino/plugin/hive/AbstractTestHive.java | 9 ++++----- .../java/io/trino/plugin/hive/AbstractTestHiveLocal.java | 4 ++-- .../trino/plugin/hive/TestBackgroundHiveSplitLoader.java | 4 ++-- .../hive/metastore/cache/TestCachingHiveMetastore.java | 2 +- .../hive/metastore/file/TestFileHiveMetastore.java | 4 ++-- .../hive/metastore/glue/TestGlueToTrinoConverter.java | 2 +- .../hive/metastore/glue/TestHiveGlueMetastore.java | 4 ++-- .../hive/metastore/glue/TestingMetastoreObjects.java | 3 ++- .../hive/metastore/thrift/InMemoryThriftMetastore.java | 8 ++++---- .../hive/metastore/thrift/MockThriftMetastoreClient.java | 4 ++-- .../trino/sql/planner/BaseIcebergCostBasedPlanTest.java | 4 ++-- 11 files changed, 24 insertions(+), 24 deletions(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java index 4ea64841218e..a1ecb6a12e2d 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java @@ -139,7 +139,6 @@ import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; -import org.apache.hadoop.hive.metastore.TableType; import org.assertj.core.api.InstanceOfAssertFactories; import org.joda.time.DateTime; import org.junit.jupiter.api.AfterAll; @@ -255,6 +254,7 @@ import static io.trino.plugin.hive.HiveType.HIVE_STRING; import static io.trino.plugin.hive.HiveType.toHiveType; import static io.trino.plugin.hive.LocationHandle.WriteMode.STAGE_AND_MOVE_TO_TARGET_DIRECTORY; +import static io.trino.plugin.hive.TableType.MANAGED_TABLE; import static io.trino.plugin.hive.TestingThriftHiveMetastoreBuilder.testingThriftHiveMetastoreBuilder; import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; import static io.trino.plugin.hive.metastore.HiveColumnStatistics.createBinaryColumnStatistics; @@ -323,7 +323,6 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.stream.Collectors.toList; import static org.apache.hadoop.hive.common.FileUtils.makePartName; -import static org.apache.hadoop.hive.metastore.TableType.MANAGED_TABLE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.joda.time.DateTimeZone.UTC; @@ -2777,7 +2776,7 @@ private static Table createSimpleTable(SchemaTableName schemaTableName, List storage.setLocation(tableLocation)) .withStorage(storage -> storage.setStorageFormat(ICEBERG_METASTORE_STORAGE_FORMAT)) From c508f88b920b378444861254d3e983e6248b45a6 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Fri, 3 Nov 2023 18:32:23 -0700 Subject: [PATCH 083/587] Replace Hadoop SerdeConstants with Trino version --- .../plugin/hive/TestBackgroundHiveSplitLoader.java | 2 +- .../io/trino/plugin/hive/TestHiveFileFormats.java | 2 +- .../io/trino/plugin/hive/TestHivePageSink.java | 2 +- .../hive/TestNodeLocalDynamicSplitPruning.java | 2 +- .../hive/TestOrcPageSourceMemoryTracking.java | 2 +- .../plugin/hive/benchmark/AbstractFileFormat.java | 2 +- .../metastore/thrift/TestThriftMetastoreUtil.java | 14 +++++++------- .../thrift/TestThriftSparkMetastoreUtil.java | 14 +++++++------- .../plugin/hive/orc/TestOrcPageSourceFactory.java | 2 +- .../trino/plugin/hive/orc/TestOrcPredicates.java | 2 +- .../trino/plugin/hive/parquet/TestOnlyNulls.java | 2 +- .../plugin/hive/parquet/TestTimestampMicros.java | 2 +- .../parquet/write/MapKeyValuesSchemaConverter.java | 9 ++++----- ...ingleLevelArrayMapKeyValuesSchemaConverter.java | 9 ++++----- .../write/SingleLevelArraySchemaConverter.java | 10 +++++----- 15 files changed, 37 insertions(+), 39 deletions(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java index 0bcdc3c3cf12..c5547a24df51 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java @@ -116,6 +116,7 @@ import static io.trino.plugin.hive.TableType.MANAGED_TABLE; import static io.trino.plugin.hive.util.HiveBucketing.BucketingVersion.BUCKETING_V1; import static io.trino.plugin.hive.util.HiveUtil.getRegularColumnHandles; +import static io.trino.plugin.hive.util.SerdeConstants.SERIALIZATION_LIB; import static io.trino.spi.predicate.TupleDomain.withColumnDomains; import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.spi.type.VarcharType.VARCHAR; @@ -126,7 +127,6 @@ import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.TimeUnit.SECONDS; import static org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.FILE_INPUT_FORMAT; -import static org.apache.hadoop.hive.serde.serdeConstants.SERIALIZATION_LIB; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java index 968e99671862..6ac05e3bba1a 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java @@ -98,6 +98,7 @@ import static io.trino.plugin.hive.HiveTestUtils.getHiveSession; import static io.trino.plugin.hive.HiveTestUtils.getTypes; import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; +import static io.trino.plugin.hive.util.SerdeConstants.SERIALIZATION_LIB; import static io.trino.testing.StructuralTestUtil.rowBlockOf; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; @@ -105,7 +106,6 @@ import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; import static org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.FILE_INPUT_FORMAT; -import static org.apache.hadoop.hive.serde.serdeConstants.SERIALIZATION_LIB; import static org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory.getStandardStructObjectInspector; import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.getPrimitiveJavaObjectInspector; import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaStringObjectInspector; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java index 26eccb6e16e7..d67dfa332c1d 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java @@ -79,6 +79,7 @@ import static io.trino.plugin.hive.LocationHandle.WriteMode.DIRECT_TO_TARGET_NEW_DIRECTORY; import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; import static io.trino.plugin.hive.metastore.file.TestingFileHiveMetastore.createTestingFileHiveMetastore; +import static io.trino.plugin.hive.util.SerdeConstants.SERIALIZATION_LIB; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.DateType.DATE; import static io.trino.spi.type.DoubleType.DOUBLE; @@ -90,7 +91,6 @@ import static java.lang.String.format; import static java.util.stream.Collectors.toList; import static org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.FILE_INPUT_FORMAT; -import static org.apache.hadoop.hive.serde.serdeConstants.SERIALIZATION_LIB; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.testng.Assert.assertEquals; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestNodeLocalDynamicSplitPruning.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestNodeLocalDynamicSplitPruning.java index 854c395bd787..4c689c33e862 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestNodeLocalDynamicSplitPruning.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestNodeLocalDynamicSplitPruning.java @@ -48,12 +48,12 @@ import static io.trino.plugin.hive.HiveTestUtils.getDefaultHivePageSourceFactories; import static io.trino.plugin.hive.HiveType.HIVE_INT; import static io.trino.plugin.hive.util.HiveBucketing.BucketingVersion.BUCKETING_V1; +import static io.trino.plugin.hive.util.SerdeConstants.SERIALIZATION_LIB; import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.testing.TestingHandles.TEST_CATALOG_HANDLE; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.util.concurrent.CompletableFuture.completedFuture; import static org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.FILE_INPUT_FORMAT; -import static org.apache.hadoop.hive.serde.serdeConstants.SERIALIZATION_LIB; import static org.testng.Assert.assertEquals; public class TestNodeLocalDynamicSplitPruning diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOrcPageSourceMemoryTracking.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOrcPageSourceMemoryTracking.java index 0af892b06510..f52877646e01 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOrcPageSourceMemoryTracking.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOrcPageSourceMemoryTracking.java @@ -105,6 +105,7 @@ import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; import static io.trino.plugin.hive.HiveTestUtils.SESSION; import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; +import static io.trino.plugin.hive.util.SerdeConstants.SERIALIZATION_LIB; import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; import static io.trino.sql.relational.Expressions.field; import static io.trino.testing.TestingHandles.TEST_CATALOG_HANDLE; @@ -119,7 +120,6 @@ import static java.util.stream.Collectors.toList; import static org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.FILE_INPUT_FORMAT; import static org.apache.hadoop.hive.ql.io.orc.CompressionKind.ZLIB; -import static org.apache.hadoop.hive.serde.serdeConstants.SERIALIZATION_LIB; import static org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory.getStandardStructObjectInspector; import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaStringObjectInspector; import static org.apache.hadoop.mapreduce.lib.output.FileOutputFormat.COMPRESS_CODEC; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/AbstractFileFormat.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/AbstractFileFormat.java index 5ad7cd9d5f51..8e84c5edc61c 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/AbstractFileFormat.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/AbstractFileFormat.java @@ -52,13 +52,13 @@ import static io.trino.plugin.hive.HiveColumnHandle.createBaseColumn; import static io.trino.plugin.hive.HiveType.toHiveType; import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; +import static io.trino.plugin.hive.util.SerdeConstants.SERIALIZATION_LIB; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.lang.String.join; import static java.util.stream.Collectors.joining; import static org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.FILE_INPUT_FORMAT; import static org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.META_TABLE_COLUMNS; import static org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.META_TABLE_COLUMN_TYPES; -import static org.apache.hadoop.hive.serde.serdeConstants.SERIALIZATION_LIB; public abstract class AbstractFileFormat implements FileFormat diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestThriftMetastoreUtil.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestThriftMetastoreUtil.java index 376fd82bb171..14a8e2706802 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestThriftMetastoreUtil.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestThriftMetastoreUtil.java @@ -57,15 +57,15 @@ import static io.trino.plugin.hive.metastore.thrift.ThriftMetastoreUtil.getHiveBasicStatistics; import static io.trino.plugin.hive.metastore.thrift.ThriftMetastoreUtil.toMetastoreDecimal; import static io.trino.plugin.hive.metastore.thrift.ThriftMetastoreUtil.updateStatisticsParameters; +import static io.trino.plugin.hive.util.SerdeConstants.BIGINT_TYPE_NAME; +import static io.trino.plugin.hive.util.SerdeConstants.BINARY_TYPE_NAME; +import static io.trino.plugin.hive.util.SerdeConstants.BOOLEAN_TYPE_NAME; +import static io.trino.plugin.hive.util.SerdeConstants.DATE_TYPE_NAME; +import static io.trino.plugin.hive.util.SerdeConstants.DECIMAL_TYPE_NAME; +import static io.trino.plugin.hive.util.SerdeConstants.DOUBLE_TYPE_NAME; +import static io.trino.plugin.hive.util.SerdeConstants.STRING_TYPE_NAME; import static io.trino.spi.security.PrincipalType.ROLE; import static io.trino.spi.security.PrincipalType.USER; -import static org.apache.hadoop.hive.serde.serdeConstants.BIGINT_TYPE_NAME; -import static org.apache.hadoop.hive.serde.serdeConstants.BINARY_TYPE_NAME; -import static org.apache.hadoop.hive.serde.serdeConstants.BOOLEAN_TYPE_NAME; -import static org.apache.hadoop.hive.serde.serdeConstants.DATE_TYPE_NAME; -import static org.apache.hadoop.hive.serde.serdeConstants.DECIMAL_TYPE_NAME; -import static org.apache.hadoop.hive.serde.serdeConstants.DOUBLE_TYPE_NAME; -import static org.apache.hadoop.hive.serde.serdeConstants.STRING_TYPE_NAME; import static org.assertj.core.api.Assertions.assertThat; import static org.testng.Assert.assertEquals; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestThriftSparkMetastoreUtil.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestThriftSparkMetastoreUtil.java index 468fff71e728..8c58a2a9b0de 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestThriftSparkMetastoreUtil.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestThriftSparkMetastoreUtil.java @@ -30,13 +30,13 @@ import java.util.OptionalLong; import static io.trino.plugin.hive.metastore.thrift.ThriftSparkMetastoreUtil.fromMetastoreColumnStatistics; -import static org.apache.hadoop.hive.serde.serdeConstants.BIGINT_TYPE_NAME; -import static org.apache.hadoop.hive.serde.serdeConstants.BINARY_TYPE_NAME; -import static org.apache.hadoop.hive.serde.serdeConstants.BOOLEAN_TYPE_NAME; -import static org.apache.hadoop.hive.serde.serdeConstants.DATE_TYPE_NAME; -import static org.apache.hadoop.hive.serde.serdeConstants.DECIMAL_TYPE_NAME; -import static org.apache.hadoop.hive.serde.serdeConstants.DOUBLE_TYPE_NAME; -import static org.apache.hadoop.hive.serde.serdeConstants.STRING_TYPE_NAME; +import static io.trino.plugin.hive.util.SerdeConstants.BIGINT_TYPE_NAME; +import static io.trino.plugin.hive.util.SerdeConstants.BINARY_TYPE_NAME; +import static io.trino.plugin.hive.util.SerdeConstants.BOOLEAN_TYPE_NAME; +import static io.trino.plugin.hive.util.SerdeConstants.DATE_TYPE_NAME; +import static io.trino.plugin.hive.util.SerdeConstants.DECIMAL_TYPE_NAME; +import static io.trino.plugin.hive.util.SerdeConstants.DOUBLE_TYPE_NAME; +import static io.trino.plugin.hive.util.SerdeConstants.STRING_TYPE_NAME; import static org.testng.Assert.assertEquals; public class TestThriftSparkMetastoreUtil diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPageSourceFactory.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPageSourceFactory.java index bfacda276d03..cf367a0ec76b 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPageSourceFactory.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPageSourceFactory.java @@ -56,6 +56,7 @@ import static io.trino.plugin.hive.HiveTestUtils.SESSION; import static io.trino.plugin.hive.HiveType.toHiveType; import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; +import static io.trino.plugin.hive.util.SerdeConstants.SERIALIZATION_LIB; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.spi.type.VarcharType.VARCHAR; @@ -67,7 +68,6 @@ import static org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.FILE_INPUT_FORMAT; import static org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.TABLE_IS_TRANSACTIONAL; import static org.apache.hadoop.hive.ql.io.AcidUtils.deleteDeltaSubdir; -import static org.apache.hadoop.hive.serde.serdeConstants.SERIALIZATION_LIB; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPredicates.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPredicates.java index 83d3e8521153..b39d560eb1e3 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPredicates.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPredicates.java @@ -53,13 +53,13 @@ import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; import static io.trino.plugin.hive.HiveTestUtils.getHiveSession; import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; +import static io.trino.plugin.hive.util.SerdeConstants.SERIALIZATION_LIB; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.testing.StructuralTestUtil.rowBlockOf; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.lang.String.format; import static java.util.stream.Collectors.toList; import static org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.FILE_INPUT_FORMAT; -import static org.apache.hadoop.hive.serde.serdeConstants.SERIALIZATION_LIB; import static org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory.getStandardStructObjectInspector; import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaIntObjectInspector; import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaLongObjectInspector; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestOnlyNulls.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestOnlyNulls.java index c13ababd159a..e8ae8033b559 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestOnlyNulls.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestOnlyNulls.java @@ -41,10 +41,10 @@ import static io.trino.plugin.hive.HiveColumnHandle.createBaseColumn; import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; import static io.trino.plugin.hive.HiveTestUtils.getHiveSession; +import static io.trino.plugin.hive.util.SerdeConstants.SERIALIZATION_LIB; import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.testing.MaterializedResult.materializeSourceDataStream; import static java.util.Collections.singletonList; -import static org.apache.hadoop.hive.serde.serdeConstants.SERIALIZATION_LIB; import static org.assertj.core.api.Assertions.assertThat; public class TestOnlyNulls diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestampMicros.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestampMicros.java index 40cc8ee68bb6..3aa9ac15a7f5 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestampMicros.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestampMicros.java @@ -45,10 +45,10 @@ import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; import static io.trino.plugin.hive.HiveTestUtils.getHiveSession; import static io.trino.plugin.hive.HiveType.HIVE_TIMESTAMP; +import static io.trino.plugin.hive.util.SerdeConstants.SERIALIZATION_LIB; import static io.trino.spi.type.TimestampType.createTimestampType; import static io.trino.spi.type.TimestampWithTimeZoneType.createTimestampWithTimeZoneType; import static io.trino.testing.MaterializedResult.materializeSourceDataStream; -import static org.apache.hadoop.hive.serde.serdeConstants.SERIALIZATION_LIB; import static org.assertj.core.api.Assertions.assertThat; public class TestTimestampMicros diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/write/MapKeyValuesSchemaConverter.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/write/MapKeyValuesSchemaConverter.java index 9766ccd8696f..8537cc4adb66 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/write/MapKeyValuesSchemaConverter.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/write/MapKeyValuesSchemaConverter.java @@ -14,7 +14,6 @@ package io.trino.plugin.hive.parquet.write; import org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe; -import org.apache.hadoop.hive.serde.serdeConstants; import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector.Category; import org.apache.hadoop.hive.serde2.typeinfo.DecimalTypeInfo; import org.apache.hadoop.hive.serde2.typeinfo.ListTypeInfo; @@ -34,6 +33,8 @@ import java.util.Locale; import static com.google.common.base.Preconditions.checkState; +import static io.trino.plugin.hive.util.SerdeConstants.CHAR_TYPE_NAME; +import static io.trino.plugin.hive.util.SerdeConstants.VARCHAR_TYPE_NAME; /** * This class is copied from org.apache.hadoop.hive.ql.io.parquet.convert.HiveSchemaConverter @@ -97,12 +98,10 @@ private static Type convertType(String name, TypeInfo typeInfo, Repetition repet if (typeInfo.equals(TypeInfoFactory.voidTypeInfo)) { throw new UnsupportedOperationException("Void type not implemented"); } - if (typeInfo.getTypeName().toLowerCase(Locale.ENGLISH).startsWith( - serdeConstants.CHAR_TYPE_NAME)) { + if (typeInfo.getTypeName().toLowerCase(Locale.ENGLISH).startsWith(CHAR_TYPE_NAME)) { return Types.optional(PrimitiveTypeName.BINARY).as(LogicalTypeAnnotation.stringType()).named(name); } - if (typeInfo.getTypeName().toLowerCase(Locale.ENGLISH).startsWith( - serdeConstants.VARCHAR_TYPE_NAME)) { + if (typeInfo.getTypeName().toLowerCase(Locale.ENGLISH).startsWith(VARCHAR_TYPE_NAME)) { return Types.optional(PrimitiveTypeName.BINARY).as(LogicalTypeAnnotation.stringType()).named(name); } if (typeInfo instanceof DecimalTypeInfo decimalTypeInfo) { diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/write/SingleLevelArrayMapKeyValuesSchemaConverter.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/write/SingleLevelArrayMapKeyValuesSchemaConverter.java index de1289010563..ab73bd78e0bd 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/write/SingleLevelArrayMapKeyValuesSchemaConverter.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/write/SingleLevelArrayMapKeyValuesSchemaConverter.java @@ -14,7 +14,6 @@ package io.trino.plugin.hive.parquet.write; import org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe; -import org.apache.hadoop.hive.serde.serdeConstants; import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector.Category; import org.apache.hadoop.hive.serde2.typeinfo.DecimalTypeInfo; import org.apache.hadoop.hive.serde2.typeinfo.ListTypeInfo; @@ -34,6 +33,8 @@ import java.util.Locale; import static com.google.common.base.Preconditions.checkState; +import static io.trino.plugin.hive.util.SerdeConstants.CHAR_TYPE_NAME; +import static io.trino.plugin.hive.util.SerdeConstants.VARCHAR_TYPE_NAME; /** * This class is copied from org.apache.hadoop.hive.ql.io.parquet.convert.HiveSchemaConverter @@ -98,15 +99,13 @@ private static Type convertType(String name, TypeInfo typeInfo, Repetition repet if (typeInfo.equals(TypeInfoFactory.voidTypeInfo)) { throw new UnsupportedOperationException("Void type not implemented"); } - if (typeInfo.getTypeName().toLowerCase(Locale.ENGLISH).startsWith( - serdeConstants.CHAR_TYPE_NAME)) { + if (typeInfo.getTypeName().toLowerCase(Locale.ENGLISH).startsWith(CHAR_TYPE_NAME)) { if (repetition == Repetition.OPTIONAL) { return Types.optional(PrimitiveTypeName.BINARY).as(LogicalTypeAnnotation.stringType()).named(name); } return Types.repeated(PrimitiveTypeName.BINARY).as(LogicalTypeAnnotation.stringType()).named(name); } - if (typeInfo.getTypeName().toLowerCase(Locale.ENGLISH).startsWith( - serdeConstants.VARCHAR_TYPE_NAME)) { + if (typeInfo.getTypeName().toLowerCase(Locale.ENGLISH).startsWith(VARCHAR_TYPE_NAME)) { if (repetition == Repetition.OPTIONAL) { return Types.optional(PrimitiveTypeName.BINARY).as(LogicalTypeAnnotation.stringType()).named(name); } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/write/SingleLevelArraySchemaConverter.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/write/SingleLevelArraySchemaConverter.java index 437390c329c3..ec087066ce98 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/write/SingleLevelArraySchemaConverter.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/write/SingleLevelArraySchemaConverter.java @@ -14,7 +14,6 @@ package io.trino.plugin.hive.parquet.write; import org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe; -import org.apache.hadoop.hive.serde.serdeConstants; import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector.Category; import org.apache.hadoop.hive.serde2.typeinfo.DecimalTypeInfo; import org.apache.hadoop.hive.serde2.typeinfo.ListTypeInfo; @@ -34,6 +33,9 @@ import java.util.List; import java.util.Locale; +import static io.trino.plugin.hive.util.SerdeConstants.CHAR_TYPE_NAME; +import static io.trino.plugin.hive.util.SerdeConstants.VARCHAR_TYPE_NAME; + /** * This class is copied from org.apache.hadoop.hive.ql.io.parquet.convert.HiveSchemaConverter * and modified to test Array Schema without wrapping anonymous group "bag". @@ -100,15 +102,13 @@ private static Type convertType(String name, TypeInfo typeInfo, if (typeInfo.equals(TypeInfoFactory.voidTypeInfo)) { throw new UnsupportedOperationException("Void type not implemented"); } - if (typeInfo.getTypeName().toLowerCase(Locale.ENGLISH).startsWith( - serdeConstants.CHAR_TYPE_NAME)) { + if (typeInfo.getTypeName().toLowerCase(Locale.ENGLISH).startsWith(CHAR_TYPE_NAME)) { if (repetition == Repetition.OPTIONAL) { return Types.optional(PrimitiveTypeName.BINARY).as(LogicalTypeAnnotation.stringType()).named(name); } return Types.repeated(PrimitiveTypeName.BINARY).as(LogicalTypeAnnotation.stringType()).named(name); } - if (typeInfo.getTypeName().toLowerCase(Locale.ENGLISH).startsWith( - serdeConstants.VARCHAR_TYPE_NAME)) { + if (typeInfo.getTypeName().toLowerCase(Locale.ENGLISH).startsWith(VARCHAR_TYPE_NAME)) { if (repetition == Repetition.OPTIONAL) { return Types.optional(PrimitiveTypeName.BINARY).as(LogicalTypeAnnotation.stringType()).named(name); } From e92752231ee46ae965cbbad675001141a84b9e50 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Fri, 3 Nov 2023 19:38:01 -0700 Subject: [PATCH 084/587] Remove Hadoop dependency from TestFileHiveMetastore --- .../hive/metastore/file/TestFileHiveMetastore.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/file/TestFileHiveMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/file/TestFileHiveMetastore.java index 7ed8b5f03778..094dc44cc043 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/file/TestFileHiveMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/file/TestFileHiveMetastore.java @@ -20,8 +20,6 @@ import io.trino.plugin.hive.metastore.HiveMetastoreConfig; import io.trino.plugin.hive.metastore.StorageFormat; import io.trino.plugin.hive.metastore.Table; -import org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat; -import org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -38,6 +36,8 @@ import static io.trino.plugin.hive.TableType.EXTERNAL_TABLE; import static io.trino.plugin.hive.metastore.PrincipalPrivileges.NO_PRIVILEGES; import static io.trino.plugin.hive.util.HiveClassNames.HUDI_PARQUET_INPUT_FORMAT; +import static io.trino.plugin.hive.util.HiveClassNames.MAPRED_PARQUET_OUTPUT_FORMAT_CLASS; +import static io.trino.plugin.hive.util.HiveClassNames.PARQUET_HIVE_SERDE_CLASS; import static io.trino.spi.security.PrincipalType.USER; import static io.trino.testing.TestingNames.randomNameSuffix; import static java.nio.file.Files.createTempDirectory; @@ -84,10 +84,7 @@ public void tearDown() @Test public void testPreserveHudiInputFormat() { - StorageFormat storageFormat = StorageFormat.create( - ParquetHiveSerDe.class.getName(), - HUDI_PARQUET_INPUT_FORMAT, - MapredParquetOutputFormat.class.getName()); + StorageFormat storageFormat = StorageFormat.create(PARQUET_HIVE_SERDE_CLASS, HUDI_PARQUET_INPUT_FORMAT, MAPRED_PARQUET_OUTPUT_FORMAT_CLASS); Table table = Table.builder() .setDatabaseName("default") From 9b95bd8b6dea02e7e2fc8db611f4b45d6269bacd Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Fri, 3 Nov 2023 20:50:55 -0700 Subject: [PATCH 085/587] Simplify Hive TestAvroSchemaGeneration --- .../hive/avro/TestAvroSchemaGeneration.java | 10 ++--- .../plugin/hive/avro/TrinoAvroSerDe.java | 41 ------------------- 2 files changed, 5 insertions(+), 46 deletions(-) delete mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/avro/TrinoAvroSerDe.java diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/avro/TestAvroSchemaGeneration.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/avro/TestAvroSchemaGeneration.java index 017df21ff3d2..2e67ab71b940 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/avro/TestAvroSchemaGeneration.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/avro/TestAvroSchemaGeneration.java @@ -20,9 +20,9 @@ import io.trino.spi.type.VarcharType; import org.apache.avro.Schema; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hive.serde2.avro.AvroSerdeUtils; import org.junit.jupiter.api.Test; -import java.io.IOException; import java.nio.file.Path; import java.util.Properties; import java.util.stream.Collectors; @@ -37,27 +37,27 @@ public class TestAvroSchemaGeneration { @Test public void testOldVsNewSchemaGeneration() - throws IOException + throws Exception { Properties properties = new Properties(); properties.setProperty(TABLE_NAME, "testingTable"); properties.setProperty(LIST_COLUMNS, "a,b"); properties.setProperty(LIST_COLUMN_TYPES, Stream.of(HiveType.HIVE_INT, HiveType.HIVE_STRING).map(HiveType::getTypeInfo).map(TypeInfo::toString).collect(Collectors.joining(","))); Schema actual = AvroHiveFileUtils.determineSchemaOrThrowException(new LocalFileSystem(Path.of("/")), properties); - Schema expected = new TrinoAvroSerDe().determineSchemaOrReturnErrorSchema(new Configuration(false), properties); + Schema expected = AvroSerdeUtils.determineSchemaOrThrowException(new Configuration(false), properties); assertThat(actual).isEqualTo(expected); } @Test public void testOldVsNewSchemaGenerationWithNested() - throws IOException + throws Exception { Properties properties = new Properties(); properties.setProperty(TABLE_NAME, "testingTable"); properties.setProperty(LIST_COLUMNS, "a,b"); properties.setProperty(LIST_COLUMN_TYPES, Stream.of(HiveType.toHiveType(RowType.rowType(RowType.field("a", VarcharType.VARCHAR))), HiveType.HIVE_STRING).map(HiveType::getTypeInfo).map(TypeInfo::toString).collect(Collectors.joining(","))); Schema actual = AvroHiveFileUtils.determineSchemaOrThrowException(new LocalFileSystem(Path.of("/")), properties); - Schema expected = new TrinoAvroSerDe().determineSchemaOrReturnErrorSchema(new Configuration(false), properties); + Schema expected = AvroSerdeUtils.determineSchemaOrThrowException(new Configuration(false), properties); assertThat(actual).isEqualTo(expected); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/avro/TrinoAvroSerDe.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/avro/TrinoAvroSerDe.java deleted file mode 100644 index e9b1aa1dc164..000000000000 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/avro/TrinoAvroSerDe.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 io.trino.plugin.hive.avro; - -import org.apache.avro.Schema; -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.hive.serde2.avro.AvroSerDe; -import org.apache.hadoop.hive.serde2.avro.AvroSerdeException; -import org.apache.hadoop.hive.serde2.avro.AvroSerdeUtils; - -import java.io.IOException; -import java.util.Properties; - -public class TrinoAvroSerDe - extends AvroSerDe -{ - @Override - public Schema determineSchemaOrReturnErrorSchema(Configuration conf, Properties props) - { - // AvroSerDe does not propagate initialization exceptions. Instead, it stores just an exception's message in - // this.configErrors (see https://issues.apache.org/jira/browse/HIVE-7868). In Trino, such behavior is not - // at all useful, as silenced exception usually carries important information which may be otherwise unavailable. - try { - return AvroSerdeUtils.determineSchemaOrThrowException(conf, props); - } - catch (IOException | AvroSerdeException e) { - throw new RuntimeException(e); - } - } -} From 743d48f87ba6913ee4f718bf3e98d23f77598657 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Fri, 3 Nov 2023 23:21:02 -0700 Subject: [PATCH 086/587] Remove old Hive file format benchmark --- .../hive/benchmark/BenchmarkFileFormat.java | 38 -- .../benchmark/BenchmarkFileFormatsUtils.java | 180 ------- .../benchmark/BenchmarkHiveFileFormat.java | 451 ------------------ .../BenchmarkProjectionPushdownHive.java | 375 --------------- .../plugin/hive/benchmark/FileFormat.java | 61 --- .../plugin/hive/benchmark/FormatWriter.java | 26 - .../hive/benchmark/StandardFileFormats.java | 295 ------------ .../trino/plugin/hive/benchmark/TestData.java | 58 --- .../TestHiveFileFormatBenchmark.java | 80 ---- .../plugin/hive/parquet/ParquetTester.java | 21 +- .../ParquetUtil.java} | 100 +--- .../plugin/hive/parquet/TestOnlyNulls.java | 3 +- .../plugin/hive/parquet/TestTimestamp.java | 3 +- .../hive/parquet/TestTimestampMicros.java | 5 +- 14 files changed, 32 insertions(+), 1664 deletions(-) delete mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/BenchmarkFileFormat.java delete mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/BenchmarkFileFormatsUtils.java delete mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/BenchmarkHiveFileFormat.java delete mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/BenchmarkProjectionPushdownHive.java delete mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/FileFormat.java delete mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/FormatWriter.java delete mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/StandardFileFormats.java delete mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/TestData.java delete mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/TestHiveFileFormatBenchmark.java rename plugin/trino-hive/src/test/java/io/trino/plugin/hive/{benchmark/AbstractFileFormat.java => parquet/ParquetUtil.java} (56%) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/BenchmarkFileFormat.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/BenchmarkFileFormat.java deleted file mode 100644 index 6dd6bb4f4554..000000000000 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/BenchmarkFileFormat.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 io.trino.plugin.hive.benchmark; - -/** - * File formats from StandardFileFormats class captured in an enum for JMH benchmarks - */ -public enum BenchmarkFileFormat -{ - TRINO_RCBINARY(StandardFileFormats.TRINO_RCBINARY), - TRINO_RCTEXT(StandardFileFormats.TRINO_RCTEXT), - TRINO_ORC(StandardFileFormats.TRINO_ORC), - TRINO_PARQUET(StandardFileFormats.TRINO_PARQUET), - /**/; - - private final FileFormat format; - - BenchmarkFileFormat(FileFormat format) - { - this.format = format; - } - - public FileFormat getFormat() - { - return format; - } -} diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/BenchmarkFileFormatsUtils.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/BenchmarkFileFormatsUtils.java deleted file mode 100644 index 903866cd0762..000000000000 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/BenchmarkFileFormatsUtils.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * 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 io.trino.plugin.hive.benchmark; - -import com.google.common.collect.ImmutableList; -import io.airlift.slice.Slices; -import io.airlift.units.DataSize; -import io.trino.spi.Page; -import io.trino.spi.PageBuilder; -import io.trino.spi.block.BlockBuilder; -import io.trino.spi.type.DecimalType; -import io.trino.spi.type.Type; -import io.trino.tpch.TpchColumn; -import io.trino.tpch.TpchEntity; -import io.trino.tpch.TpchTable; -import org.openjdk.jmh.results.RunResult; -import org.openjdk.jmh.util.Statistics; - -import java.io.File; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.util.Collection; -import java.util.List; -import java.util.Random; - -import static io.airlift.units.DataSize.Unit.MEGABYTE; -import static io.trino.spi.type.BigintType.BIGINT; -import static io.trino.spi.type.DateType.DATE; -import static io.trino.spi.type.IntegerType.INTEGER; -import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; -import static java.lang.String.format; -import static java.nio.file.Files.createTempDirectory; -import static java.util.stream.Collectors.toList; - -public final class BenchmarkFileFormatsUtils -{ - public static final long MIN_DATA_SIZE = DataSize.of(50, MEGABYTE).toBytes(); - - private BenchmarkFileFormatsUtils() - { - } - - @SafeVarargs - public static TestData createTpchDataSet(FileFormat format, TpchTable tpchTable, TpchColumn... columns) - { - return createTpchDataSet(format, tpchTable, ImmutableList.copyOf(columns)); - } - - public static TestData createTpchDataSet(FileFormat format, TpchTable tpchTable, List> columns) - { - List columnNames = columns.stream().map(TpchColumn::getColumnName).collect(toList()); - List columnTypes = columns.stream().map(BenchmarkFileFormatsUtils::getColumnType) - .map(type -> format.supportsDate() || !DATE.equals(type) ? type : createUnboundedVarcharType()) - .collect(toList()); - - PageBuilder pageBuilder = new PageBuilder(columnTypes); - ImmutableList.Builder pages = ImmutableList.builder(); - long dataSize = 0; - for (E row : tpchTable.createGenerator(10, 1, 1)) { - pageBuilder.declarePosition(); - for (int i = 0; i < columns.size(); i++) { - TpchColumn column = columns.get(i); - BlockBuilder blockBuilder = pageBuilder.getBlockBuilder(i); - switch (column.getType().getBase()) { - case IDENTIFIER -> BIGINT.writeLong(blockBuilder, column.getIdentifier(row)); - case INTEGER -> INTEGER.writeLong(blockBuilder, column.getInteger(row)); - case DATE -> { - if (format.supportsDate()) { - DATE.writeLong(blockBuilder, column.getDate(row)); - } - else { - createUnboundedVarcharType().writeSlice(blockBuilder, Slices.utf8Slice(column.getString(row))); - } - } - case DOUBLE -> DecimalType.createDecimalType(12, 2).writeLong(blockBuilder, column.getIdentifier(row)); - case VARCHAR -> createUnboundedVarcharType().writeSlice(blockBuilder, Slices.utf8Slice(column.getString(row))); - } - } - if (pageBuilder.isFull()) { - Page page = pageBuilder.build(); - pages.add(page); - pageBuilder.reset(); - dataSize += page.getSizeInBytes(); - - if (dataSize >= MIN_DATA_SIZE) { - break; - } - } - } - if (!pageBuilder.isEmpty()) { - pages.add(pageBuilder.build()); - } - return new TestData(columnNames, columnTypes, pages.build()); - } - - public static Type getColumnType(TpchColumn input) - { - switch (input.getType().getBase()) { - case IDENTIFIER: - return BIGINT; - case INTEGER: - return INTEGER; - case DATE: - return DATE; - case DOUBLE: - return DecimalType.createDecimalType(12, 2); - case VARCHAR: - return createUnboundedVarcharType(); - } - throw new IllegalArgumentException("Unsupported type " + input.getType()); - } - - public static void printResults(Collection results) - { - for (RunResult result : results) { - Statistics inputSizeStats = result.getSecondaryResults().get("inputSize").getStatistics(); - Statistics outputSizeStats = result.getSecondaryResults().get("outputSize").getStatistics(); - double compressionRatio = inputSizeStats.getSum() / outputSizeStats.getSum(); - String compression = result.getParams().getParam("compression"); - String fileFormat = result.getParams().getParam("benchmarkFileFormat"); - String dataSet = result.getParams().getParam("dataSet"); - System.out.printf(" %-10s %-30s %-10s %-25s %2.2f %10s ± %11s (%5.2f%%) (N = %d, \u03B1 = 99.9%%)\n", - result.getPrimaryResult().getLabel(), - dataSet, - compression, - fileFormat, - compressionRatio, - toHumanReadableSpeed((long) inputSizeStats.getMean()), - toHumanReadableSpeed((long) inputSizeStats.getMeanErrorAt(0.999)), - inputSizeStats.getMeanErrorAt(0.999) * 100 / inputSizeStats.getMean(), - inputSizeStats.getN()); - } - System.out.println(); - } - - public static String toHumanReadableSpeed(long bytesPerSecond) - { - String humanReadableSpeed; - if (bytesPerSecond < 1024 * 10L) { - humanReadableSpeed = format("%dB/s", bytesPerSecond); - } - else if (bytesPerSecond < 1024 * 1024 * 10L) { - humanReadableSpeed = format("%.1fkB/s", bytesPerSecond / 1024.0f); - } - else if (bytesPerSecond < 1024 * 1024 * 1024 * 10L) { - humanReadableSpeed = format("%.1fMB/s", bytesPerSecond / (1024.0f * 1024.0f)); - } - else { - humanReadableSpeed = format("%.1fGB/s", bytesPerSecond / (1024.0f * 1024.0f * 1024.0f)); - } - return humanReadableSpeed; - } - - public static int nextRandomBetween(Random random, int min, int max) - { - return min + random.nextInt(max - min); - } - - @SuppressWarnings("SameParameterValue") - public static File createTempDir(String prefix) - { - try { - return createTempDirectory(prefix).toFile(); - } - catch (IOException e) { - throw new UncheckedIOException(e); - } - } -} diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/BenchmarkHiveFileFormat.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/BenchmarkHiveFileFormat.java deleted file mode 100644 index 6740ceb873a4..000000000000 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/BenchmarkHiveFileFormat.java +++ /dev/null @@ -1,451 +0,0 @@ -/* - * 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 io.trino.plugin.hive.benchmark; - -import com.google.common.collect.ImmutableList; -import io.airlift.slice.Slices; -import io.trino.hadoop.HadoopNative; -import io.trino.plugin.hive.HiveCompressionCodec; -import io.trino.plugin.hive.HiveConfig; -import io.trino.spi.Page; -import io.trino.spi.PageBuilder; -import io.trino.spi.block.ArrayBlockBuilder; -import io.trino.spi.block.BlockBuilder; -import io.trino.spi.block.MapBlockBuilder; -import io.trino.spi.connector.ConnectorPageSource; -import io.trino.spi.connector.ConnectorSession; -import io.trino.spi.type.ArrayType; -import io.trino.spi.type.MapType; -import io.trino.spi.type.Type; -import io.trino.tpch.OrderColumn; -import it.unimi.dsi.fastutil.ints.IntArrays; -import org.openjdk.jmh.annotations.AuxCounters; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.Fork; -import org.openjdk.jmh.annotations.Measurement; -import org.openjdk.jmh.annotations.OutputTimeUnit; -import org.openjdk.jmh.annotations.Param; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.Setup; -import org.openjdk.jmh.annotations.State; -import org.openjdk.jmh.annotations.TearDown; -import org.openjdk.jmh.annotations.Warmup; -import org.openjdk.jmh.results.RunResult; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Random; -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -import static com.google.common.io.MoreFiles.deleteRecursively; -import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; -import static io.trino.jmh.Benchmarks.benchmark; -import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; -import static io.trino.plugin.hive.HiveTestUtils.getHiveSession; -import static io.trino.plugin.hive.benchmark.BenchmarkFileFormatsUtils.MIN_DATA_SIZE; -import static io.trino.plugin.hive.benchmark.BenchmarkFileFormatsUtils.createTempDir; -import static io.trino.plugin.hive.benchmark.BenchmarkFileFormatsUtils.createTpchDataSet; -import static io.trino.plugin.hive.benchmark.BenchmarkFileFormatsUtils.nextRandomBetween; -import static io.trino.plugin.hive.benchmark.BenchmarkFileFormatsUtils.printResults; -import static io.trino.spi.type.DoubleType.DOUBLE; -import static io.trino.spi.type.IntegerType.INTEGER; -import static io.trino.spi.type.VarcharType.VARCHAR; -import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; -import static io.trino.tpch.TpchTable.LINE_ITEM; -import static io.trino.tpch.TpchTable.ORDERS; -import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; - -@State(Scope.Thread) -@OutputTimeUnit(TimeUnit.SECONDS) -@Measurement(iterations = 50) -@Warmup(iterations = 20) -@Fork(3) -@SuppressWarnings({"UseOfSystemOutOrSystemErr", "ResultOfMethodCallIgnored"}) -public class BenchmarkHiveFileFormat -{ - private static final ConnectorSession SESSION = getHiveSession(new HiveConfig()); - - static { - HadoopNative.requireHadoopNative(); - } - - @Param({ - "LINEITEM", - "BIGINT_SEQUENTIAL", - "BIGINT_RANDOM", - "VARCHAR_SMALL", - "VARCHAR_LARGE", - "VARCHAR_DICTIONARY", - "MAP_VARCHAR_DOUBLE", - "LARGE_MAP_VARCHAR_DOUBLE", - "MAP_INT_DOUBLE", - "LARGE_MAP_INT_DOUBLE", - "LARGE_ARRAY_VARCHAR"}) - private DataSet dataSet; - - @Param({ - "NONE", - "SNAPPY", - "GZIP"}) - private HiveCompressionCodec compression; - - @Param({ - "TRINO_RCBINARY", - "TRINO_RCTEXT", - "TRINO_ORC", - "TRINO_PARQUET"}) - private BenchmarkFileFormat benchmarkFileFormat; - - private FileFormat fileFormat; - private TestData data; - private File dataFile; - - private final File targetDir = createTempDir("trino-benchmark"); - - public BenchmarkHiveFileFormat() - { - } - - public BenchmarkHiveFileFormat(DataSet dataSet, HiveCompressionCodec compression, BenchmarkFileFormat fileFormat) - { - this.dataSet = dataSet; - this.compression = compression; - this.benchmarkFileFormat = fileFormat; - } - - @Setup - public void setup() - throws IOException - { - fileFormat = benchmarkFileFormat.getFormat(); - data = dataSet.createTestData(fileFormat); - - targetDir.mkdirs(); - dataFile = new File(targetDir, UUID.randomUUID().toString()); - writeData(dataFile); - } - - @TearDown - public void tearDown() - throws IOException - { - deleteRecursively(targetDir.toPath(), ALLOW_INSECURE); - } - - @SuppressWarnings("PublicField") - @AuxCounters - @State(Scope.Thread) - public static class CompressionCounter - { - public long inputSize; - public long outputSize; - } - - @Benchmark - public List read(CompressionCounter counter) - throws IOException - { - if (!fileFormat.supports(data)) { - throw new RuntimeException(fileFormat + " does not support data set " + dataSet); - } - List pages = new ArrayList<>(100); - try (ConnectorPageSource pageSource = fileFormat.createFileFormatReader( - SESSION, - HDFS_ENVIRONMENT, - dataFile, - data.getColumnNames(), - data.getColumnTypes())) { - while (!pageSource.isFinished()) { - Page page = pageSource.getNextPage(); - if (page != null) { - pages.add(page.getLoadedPage()); - } - } - } - counter.inputSize += data.getSize(); - counter.outputSize += dataFile.length(); - return pages; - } - - @Benchmark - public File write(CompressionCounter counter) - throws IOException - { - File targetFile = new File(targetDir, UUID.randomUUID().toString()); - writeData(targetFile); - counter.inputSize += data.getSize(); - counter.outputSize += targetFile.length(); - return targetFile; - } - - private void writeData(File targetFile) - throws IOException - { - List inputPages = data.getPages(); - try (FormatWriter formatWriter = fileFormat.createFileFormatWriter( - SESSION, - targetFile, - data.getColumnNames(), - data.getColumnTypes(), - compression)) { - for (Page page : inputPages) { - formatWriter.writePage(page); - } - } - } - - public enum DataSet - { - LINEITEM { - @Override - public TestData createTestData(FileFormat format) - { - return createTpchDataSet(format, LINE_ITEM, LINE_ITEM.getColumns()); - } - }, - BIGINT_SEQUENTIAL { - @Override - public TestData createTestData(FileFormat format) - { - return createTpchDataSet(format, ORDERS, OrderColumn.ORDER_KEY); - } - }, - BIGINT_RANDOM { - @Override - public TestData createTestData(FileFormat format) - { - return createTpchDataSet(format, ORDERS, OrderColumn.CUSTOMER_KEY); - } - }, - VARCHAR_SMALL { - @Override - public TestData createTestData(FileFormat format) - { - return createTpchDataSet(format, ORDERS, OrderColumn.CLERK); - } - }, - VARCHAR_LARGE { - @Override - public TestData createTestData(FileFormat format) - { - return createTpchDataSet(format, ORDERS, OrderColumn.CLERK); - } - }, - VARCHAR_DICTIONARY { - @Override - public TestData createTestData(FileFormat format) - { - return createTpchDataSet(format, ORDERS, OrderColumn.ORDER_PRIORITY); - } - }, - MAP_VARCHAR_DOUBLE { - private static final int MIN_ENTRIES = 1; - private static final int MAX_ENTRIES = 5; - - @Override - public TestData createTestData(FileFormat format) - { - MapType type = new MapType(VARCHAR, DOUBLE, TESTING_TYPE_MANAGER.getTypeOperators()); - Random random = new Random(1234); - - PageBuilder pageBuilder = new PageBuilder(ImmutableList.of(type)); - ImmutableList.Builder pages = ImmutableList.builder(); - - int[] keys = {1, 2, 3, 4, 5}; - - long dataSize = 0; - while (dataSize < MIN_DATA_SIZE) { - pageBuilder.declarePosition(); - - MapBlockBuilder builder = (MapBlockBuilder) pageBuilder.getBlockBuilder(0); - builder.buildEntry((keyBuilder, valueBuilder) -> { - int entries = nextRandomBetween(random, MIN_ENTRIES, MAX_ENTRIES); - IntArrays.shuffle(keys, random); - for (int entryId = 0; entryId < entries; entryId++) { - VARCHAR.writeSlice(keyBuilder, Slices.utf8Slice("key" + keys[entryId])); - DOUBLE.writeDouble(valueBuilder, random.nextDouble()); - } - }); - - if (pageBuilder.isFull()) { - Page page = pageBuilder.build(); - pages.add(page); - pageBuilder.reset(); - dataSize += page.getSizeInBytes(); - } - } - return new TestData(ImmutableList.of("map"), ImmutableList.of(type), pages.build()); - } - }, - LARGE_MAP_VARCHAR_DOUBLE { - private static final int MIN_ENTRIES = 5_000; - private static final int MAX_ENTRIES = 15_000; - - @Override - public TestData createTestData(FileFormat format) - { - MapType type = new MapType(VARCHAR, DOUBLE, TESTING_TYPE_MANAGER.getTypeOperators()); - Random random = new Random(1234); - - PageBuilder pageBuilder = new PageBuilder(ImmutableList.of(type)); - ImmutableList.Builder pages = ImmutableList.builder(); - long dataSize = 0; - while (dataSize < MIN_DATA_SIZE) { - pageBuilder.declarePosition(); - - MapBlockBuilder builder = (MapBlockBuilder) pageBuilder.getBlockBuilder(0); - builder.buildEntry((keyBuilder, valueBuilder) -> { - int entries = nextRandomBetween(random, MIN_ENTRIES, MAX_ENTRIES); - for (int entryId = 0; entryId < entries; entryId++) { - VARCHAR.writeSlice(keyBuilder, Slices.utf8Slice("key" + random.nextInt(10_000_000))); - DOUBLE.writeDouble(valueBuilder, random.nextDouble()); - } - }); - - if (pageBuilder.isFull()) { - Page page = pageBuilder.build(); - pages.add(page); - pageBuilder.reset(); - dataSize += page.getSizeInBytes(); - } - } - return new TestData(ImmutableList.of("map"), ImmutableList.of(type), pages.build()); - } - }, - MAP_INT_DOUBLE { - private static final int MIN_ENTRIES = 1; - private static final int MAX_ENTRIES = 5; - - @Override - public TestData createTestData(FileFormat format) - { - MapType type = new MapType(INTEGER, DOUBLE, TESTING_TYPE_MANAGER.getTypeOperators()); - Random random = new Random(1234); - - PageBuilder pageBuilder = new PageBuilder(ImmutableList.of(type)); - ImmutableList.Builder pages = ImmutableList.builder(); - - int[] keys = {1, 2, 3, 4, 5}; - - long dataSize = 0; - while (dataSize < MIN_DATA_SIZE) { - pageBuilder.declarePosition(); - - MapBlockBuilder builder = (MapBlockBuilder) pageBuilder.getBlockBuilder(0); - builder.buildEntry((keyBuilder, valueBuilder) -> { - int entries = nextRandomBetween(random, MIN_ENTRIES, MAX_ENTRIES); - IntArrays.shuffle(keys, random); - for (int entryId = 0; entryId < entries; entryId++) { - INTEGER.writeLong(keyBuilder, keys[entryId]); - DOUBLE.writeDouble(valueBuilder, random.nextDouble()); - } - }); - - if (pageBuilder.isFull()) { - Page page = pageBuilder.build(); - pages.add(page); - pageBuilder.reset(); - dataSize += page.getSizeInBytes(); - } - } - return new TestData(ImmutableList.of("map"), ImmutableList.of(type), pages.build()); - } - }, - LARGE_MAP_INT_DOUBLE { - private static final int MIN_ENTRIES = 5_000; - private static final int MAX_ENTRIES = 15_0000; - - @Override - public TestData createTestData(FileFormat format) - { - MapType type = new MapType(INTEGER, DOUBLE, TESTING_TYPE_MANAGER.getTypeOperators()); - Random random = new Random(1234); - - PageBuilder pageBuilder = new PageBuilder(ImmutableList.of(type)); - ImmutableList.Builder pages = ImmutableList.builder(); - long dataSize = 0; - while (dataSize < MIN_DATA_SIZE) { - pageBuilder.declarePosition(); - - MapBlockBuilder builder = (MapBlockBuilder) pageBuilder.getBlockBuilder(0); - builder.buildEntry((keyBuilder, valueBuilder) -> { - int entries = nextRandomBetween(random, MIN_ENTRIES, MAX_ENTRIES); - for (int entryId = 0; entryId < entries; entryId++) { - INTEGER.writeLong(keyBuilder, random.nextInt(10_000_000)); - DOUBLE.writeDouble(valueBuilder, random.nextDouble()); - } - }); - - if (pageBuilder.isFull()) { - Page page = pageBuilder.build(); - pages.add(page); - pageBuilder.reset(); - dataSize += page.getSizeInBytes(); - } - } - return new TestData(ImmutableList.of("map"), ImmutableList.of(type), pages.build()); - } - }, - LARGE_ARRAY_VARCHAR { - private static final int MIN_ENTRIES = 5_000; - private static final int MAX_ENTRIES = 15_0000; - - @Override - public TestData createTestData(FileFormat format) - { - Type type = new ArrayType(createUnboundedVarcharType()); - Random random = new Random(1234); - - PageBuilder pageBuilder = new PageBuilder(ImmutableList.of(type)); - ImmutableList.Builder pages = ImmutableList.builder(); - long dataSize = 0; - while (dataSize < MIN_DATA_SIZE) { - pageBuilder.declarePosition(); - - BlockBuilder builder = pageBuilder.getBlockBuilder(0); - ((ArrayBlockBuilder) builder).buildEntry(elementBuilder -> { - int entries = nextRandomBetween(random, MIN_ENTRIES, MAX_ENTRIES); - for (int entryId = 0; entryId < entries; entryId++) { - createUnboundedVarcharType().writeSlice(elementBuilder, Slices.utf8Slice("key" + random.nextInt(10_000_000))); - } - }); - - if (pageBuilder.isFull()) { - Page page = pageBuilder.build(); - pages.add(page); - pageBuilder.reset(); - dataSize += page.getSizeInBytes(); - } - } - return new TestData(ImmutableList.of("map"), ImmutableList.of(type), pages.build()); - } - }; - - public abstract TestData createTestData(FileFormat format); - } - - public static void main(String[] args) - throws Exception - { - Collection results = benchmark(BenchmarkHiveFileFormat.class) - .withOptions(optionsBuilder -> optionsBuilder.jvmArgsAppend("-Xmx4g", "-Xms4g")) - .run(); - - printResults(results); - } -} diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/BenchmarkProjectionPushdownHive.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/BenchmarkProjectionPushdownHive.java deleted file mode 100644 index c116971d3345..000000000000 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/BenchmarkProjectionPushdownHive.java +++ /dev/null @@ -1,375 +0,0 @@ -/* - * 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 io.trino.plugin.hive.benchmark; - -import com.google.common.collect.ImmutableList; -import io.trino.hadoop.HadoopNative; -import io.trino.plugin.hive.HiveColumnHandle; -import io.trino.plugin.hive.HiveCompressionCodec; -import io.trino.plugin.hive.HiveType; -import io.trino.spi.Page; -import io.trino.spi.block.Block; -import io.trino.spi.block.BlockBuilder; -import io.trino.spi.block.RowBlock; -import io.trino.spi.connector.ColumnHandle; -import io.trino.spi.connector.ConnectorPageSource; -import io.trino.spi.type.ArrayType; -import io.trino.spi.type.RowType; -import io.trino.spi.type.Type; -import io.trino.spi.type.VarcharType; -import org.junit.jupiter.api.Test; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.Fork; -import org.openjdk.jmh.annotations.Measurement; -import org.openjdk.jmh.annotations.OutputTimeUnit; -import org.openjdk.jmh.annotations.Param; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.Setup; -import org.openjdk.jmh.annotations.State; -import org.openjdk.jmh.annotations.TearDown; -import org.openjdk.jmh.annotations.Warmup; - -import java.io.File; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.Random; -import java.util.UUID; -import java.util.concurrent.TimeUnit; -import java.util.stream.IntStream; - -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.io.MoreFiles.deleteRecursively; -import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; -import static io.trino.jmh.Benchmarks.benchmark; -import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; -import static io.trino.plugin.hive.HiveTestUtils.SESSION; -import static io.trino.plugin.hive.HiveType.toHiveType; -import static io.trino.plugin.hive.TestHiveReaderProjectionsUtil.createProjectedColumnHandle; -import static io.trino.plugin.hive.benchmark.BenchmarkFileFormat.TRINO_ORC; -import static io.trino.spi.type.VarcharType.VARCHAR; -import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; -import static java.lang.String.format; -import static java.nio.file.Files.createTempDirectory; - -/** - * Benchmarks the read operations with projection pushdown in Hive. This is useful for comparing the following for different formats - * - Performance difference between reading row columns v/s reading projected VARCHAR subfields - * - Performance difference between reading base VARCHAR columns v/s reading projected VARCHAR subfields - */ -@State(Scope.Thread) -@OutputTimeUnit(TimeUnit.SECONDS) -@Measurement(iterations = 50) -@Warmup(iterations = 20) -@Fork(3) -public class BenchmarkProjectionPushdownHive -{ - static { - HadoopNative.requireHadoopNative(); - } - - private static final String LETTERS = "abcdefghijklmnopqrstuvwxyz"; - private static final int TOTAL_ROW_COUNT = 10_000; - private static final int POSITIONS_PER_PAGE = 1_000; - private static final int DEFAULT_ARRAY_SIZE = 30; - - // Write Strategies - private static final String TOP_LEVEL = "toplevel"; - private static final String STRUCT = "struct"; - - // Read Strategies - private static final String WITH_PUSHDOWN = "with_pushdown"; - private static final String WITHOUT_PUSHDOWN = "without_pushdown"; - - // Types - private static final String ROW_OF_STRINGS = "ROW(f0 VARCHAR, f1 VARCHAR, f2 VARCHAR)"; - private static final String NESTED_STRUCT = "ROW(" + - "f0 VARCHAR, " + - "f1 VARCHAR, " + - "f2 VARCHAR, " + - "f3 ARRAY(ROW(f0 VARCHAR, f1 VARCHAR, f2 VARCHAR)))"; - - @State(Scope.Thread) - public static class BenchmarkContext - { - private final Random random = new Random(); - - private List columnTypesToWrite; - private List columnNamesToWrite; - private List columnHiveTypesToWrite; - - // Layout of the file columns: either in a single struct OR as "flattened" into top-level columns - @Param({STRUCT, TOP_LEVEL}) - private String writeStrategy = STRUCT; - - @Param({WITHOUT_PUSHDOWN, WITH_PUSHDOWN}) - private String readStrategy = WITH_PUSHDOWN; - - // This should be a row typed column - @Param({ROW_OF_STRINGS, NESTED_STRUCT}) - private String columnTypeString = ROW_OF_STRINGS; - - @Param("1") - private int readColumnCount = 1; - - @Param({"TRINO_ORC", "TRINO_PARQUET"}) - private BenchmarkFileFormat benchmarkFileFormat = TRINO_ORC; - - private FileFormat fileFormat; - private TestData dataToWrite; - private File dataFile; - - private final File targetDir = createTempDir("trino-benchmark"); - - @Setup - public void setup() - throws IOException - { - fileFormat = benchmarkFileFormat.getFormat(); - Type columnType = TESTING_TYPE_MANAGER.fromSqlType(columnTypeString); - checkState(columnType instanceof RowType, "expected column to have RowType"); - - if (STRUCT.equals(writeStrategy)) { - columnTypesToWrite = ImmutableList.of(columnType); - columnHiveTypesToWrite = ImmutableList.of(toHiveType(columnType)); - columnNamesToWrite = ImmutableList.of("column_0"); - } - else if (TOP_LEVEL.equals(writeStrategy)) { - List fieldTypes = ((RowType) columnType).getTypeParameters(); - columnTypesToWrite = ImmutableList.copyOf(fieldTypes); - columnHiveTypesToWrite = columnTypesToWrite.stream() - .map(HiveType::toHiveType) - .collect(toImmutableList()); - columnNamesToWrite = IntStream.range(0, columnTypesToWrite.size()) - .mapToObj(Integer::toString) - .map("column_"::concat) - .collect(toImmutableList()); - } - else { - throw new UnsupportedOperationException(format("Write strategy %s not supported", writeStrategy)); - } - - checkState(columnTypesToWrite.stream().allMatch(BenchmarkProjectionPushdownHive::isSupportedType), "Type not supported for benchmark"); - dataToWrite = createTestData(columnTypesToWrite, columnNamesToWrite); - - targetDir.mkdirs(); - dataFile = new File(targetDir, UUID.randomUUID().toString()); - writeData(dataFile); - } - - @TearDown - public void tearDown() - throws IOException - { - deleteRecursively(targetDir.toPath(), ALLOW_INSECURE); - } - - private void writeData(File targetFile) - throws IOException - { - List inputPages = dataToWrite.getPages(); - try (FormatWriter formatWriter = fileFormat.createFileFormatWriter( - SESSION, - targetFile, - dataToWrite.getColumnNames(), - dataToWrite.getColumnTypes(), - HiveCompressionCodec.ZSTD)) { - for (Page page : inputPages) { - formatWriter.writePage(page); - } - } - } - - private ConnectorPageSource createPageSource() - { - if (TOP_LEVEL.equals(writeStrategy)) { - List readColumns = IntStream.range(0, readColumnCount).boxed() - .map(index -> HiveColumnHandle.createBaseColumn( - columnNamesToWrite.get(index), - 0, - columnHiveTypesToWrite.get(index), - columnTypesToWrite.get(index), - HiveColumnHandle.ColumnType.REGULAR, - Optional.empty())) - .collect(toImmutableList()); - - return fileFormat.createGenericReader(SESSION, HDFS_ENVIRONMENT, dataFile, readColumns, columnNamesToWrite, columnTypesToWrite); - } - - if (STRUCT.equals(writeStrategy)) { - // Unflattened schema has one ROW type column - checkState(columnTypesToWrite.size() == 1); - - HiveColumnHandle baseColumn = HiveColumnHandle.createBaseColumn( - columnNamesToWrite.get(0), - 0, - columnHiveTypesToWrite.get(0), - columnTypesToWrite.get(0), - HiveColumnHandle.ColumnType.REGULAR, - Optional.empty()); - - List readColumnHandles; - if (WITH_PUSHDOWN.equals(readStrategy)) { - readColumnHandles = IntStream.range(0, readColumnCount).boxed() - .map(i -> createProjectedColumnHandle(baseColumn, ImmutableList.of(i))) - .collect(toImmutableList()); - } - else if (WITHOUT_PUSHDOWN.equals(readStrategy)) { - readColumnHandles = ImmutableList.of(baseColumn); - } - else { - throw new UnsupportedOperationException(format("Read strategy %s not supported", readStrategy)); - } - - return fileFormat.createGenericReader(SESSION, HDFS_ENVIRONMENT, dataFile, readColumnHandles, columnNamesToWrite, columnTypesToWrite); - } - - throw new UnsupportedOperationException(format("Write strategy %s not supported", writeStrategy)); - } - - private TestData createTestData(List columnTypes, List columnNames) - { - ImmutableList.Builder pages = ImmutableList.builder(); - int pageCount = TOTAL_ROW_COUNT / POSITIONS_PER_PAGE; - - for (int i = 0; i < pageCount; i++) { - Block[] blocks = new Block[columnTypes.size()]; - - for (int column = 0; column < columnTypes.size(); column++) { - Type type = columnTypes.get(column); - blocks[column] = createBlock(type, POSITIONS_PER_PAGE); - } - - pages.add(new Page(blocks)); - } - - return new TestData(columnNames, columnTypes, pages.build()); - } - - private Block createBlock(Type type, int rowCount) - { - checkState(isSupportedType(type), format("Type %s not supported", type.getDisplayName())); - - if (type instanceof RowType) { - List parameters = type.getTypeParameters(); - Block[] fieldBlocks = new Block[parameters.size()]; - - for (int field = 0; field < parameters.size(); field++) { - fieldBlocks[field] = createBlock(parameters.get(field), rowCount); - } - - return RowBlock.fromFieldBlocks(rowCount, fieldBlocks); - } - if (type instanceof VarcharType) { - BlockBuilder builder = VARCHAR.createBlockBuilder(null, rowCount); - for (int i = 0; i < rowCount; i++) { - VARCHAR.writeString(builder, generateRandomString(random, 500)); - } - return builder.build(); - } - if (type instanceof ArrayType arrayType) { - Type elementType = arrayType.getElementType(); - - BlockBuilder blockBuilder = type.createBlockBuilder(null, rowCount); - for (int i = 0; i < rowCount; i++) { - Block elementBlock = createBlock(elementType, DEFAULT_ARRAY_SIZE); - type.writeObject(blockBuilder, elementBlock); - } - - return blockBuilder.build(); - } - - throw new UnsupportedOperationException("Only VARCHAR, ROW and ARRAY types supported"); - } - } - - @Benchmark - public List readPages(BenchmarkContext context) - { - List pages = new ArrayList<>(100); - ConnectorPageSource pageSource = context.createPageSource(); - - while (!pageSource.isFinished()) { - Page page = pageSource.getNextPage(); - if (page != null) { - pages.add(page.getLoadedPage()); - } - } - - return pages; - } - - @Test - public void testBenchmark() - throws IOException - { - BenchmarkContext context = new BenchmarkContext(); - try { - context.setup(); - readPages(context); - } - catch (Throwable t) { - throw new RuntimeException("Benchmark execution failed", t); - } - finally { - context.tearDown(); - } - } - - public static void main(String[] args) - throws Exception - { - benchmark(BenchmarkProjectionPushdownHive.class) - .withOptions(optionsBuilder -> optionsBuilder.jvmArgsAppend("-Xmx4g", "-Xms4g")) - .run(); - } - - @SuppressWarnings("SameParameterValue") - private static File createTempDir(String prefix) - { - try { - return createTempDirectory(prefix).toFile(); - } - catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - private static String generateRandomString(Random random, int length) - { - char[] chars = new char[length]; - for (int i = 0; i < length; i++) { - chars[i] = LETTERS.charAt(random.nextInt(LETTERS.length())); - } - return new String(chars); - } - - private static boolean isSupportedType(Type type) - { - if (type == VARCHAR) { - return true; - } - if (type instanceof RowType) { - return type.getTypeParameters().stream().allMatch(BenchmarkProjectionPushdownHive::isSupportedType); - } - if (type instanceof ArrayType) { - return isSupportedType(((ArrayType) type).getElementType()); - } - - return false; - } -} diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/FileFormat.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/FileFormat.java deleted file mode 100644 index 4f4f301789bd..000000000000 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/FileFormat.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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 io.trino.plugin.hive.benchmark; - -import io.trino.hdfs.HdfsEnvironment; -import io.trino.plugin.hive.HiveCompressionCodec; -import io.trino.plugin.hive.HivePageSourceFactory; -import io.trino.plugin.hive.HiveStorageFormat; -import io.trino.spi.connector.ColumnHandle; -import io.trino.spi.connector.ConnectorPageSource; -import io.trino.spi.connector.ConnectorSession; -import io.trino.spi.type.Type; - -import java.io.File; -import java.io.IOException; -import java.util.List; - -public interface FileFormat -{ - HiveStorageFormat getFormat(); - - FormatWriter createFileFormatWriter( - ConnectorSession session, - File targetFile, - List columnNames, - List columnTypes, - HiveCompressionCodec compressionCodec) - throws IOException; - - boolean supportsDate(); - - HivePageSourceFactory getHivePageSourceFactory(HdfsEnvironment environment); - - ConnectorPageSource createFileFormatReader( - ConnectorSession session, - HdfsEnvironment hdfsEnvironment, - File targetFile, - List columnNames, - List columnTypes); - - ConnectorPageSource createGenericReader( - ConnectorSession session, - HdfsEnvironment hdfsEnvironment, - File targetFile, - List readColumns, - List schemaColumnNames, - List schemaColumnTypes); - - boolean supports(TestData testData); -} diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/FormatWriter.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/FormatWriter.java deleted file mode 100644 index a17ec1d9234b..000000000000 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/FormatWriter.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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 io.trino.plugin.hive.benchmark; - -import io.trino.spi.Page; - -import java.io.Closeable; -import java.io.IOException; - -public interface FormatWriter - extends Closeable -{ - void writePage(Page page) - throws IOException; -} diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/StandardFileFormats.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/StandardFileFormats.java deleted file mode 100644 index 6ab34775965d..000000000000 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/StandardFileFormats.java +++ /dev/null @@ -1,295 +0,0 @@ -/* - * 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 io.trino.plugin.hive.benchmark; - -import com.google.common.collect.ImmutableMap; -import io.trino.filesystem.hdfs.HdfsFileSystemFactory; -import io.trino.filesystem.local.LocalOutputFile; -import io.trino.hdfs.HdfsEnvironment; -import io.trino.hive.formats.encodings.ColumnEncodingFactory; -import io.trino.hive.formats.encodings.binary.BinaryColumnEncodingFactory; -import io.trino.hive.formats.encodings.text.TextColumnEncodingFactory; -import io.trino.hive.formats.rcfile.RcFileWriter; -import io.trino.orc.OrcReaderOptions; -import io.trino.orc.OrcWriter; -import io.trino.orc.OrcWriterOptions; -import io.trino.orc.OrcWriterStats; -import io.trino.orc.OutputStreamOrcDataSink; -import io.trino.orc.metadata.OrcType; -import io.trino.parquet.writer.ParquetSchemaConverter; -import io.trino.parquet.writer.ParquetWriter; -import io.trino.parquet.writer.ParquetWriterOptions; -import io.trino.plugin.hive.FileFormatDataSourceStats; -import io.trino.plugin.hive.HiveCompressionCodec; -import io.trino.plugin.hive.HiveConfig; -import io.trino.plugin.hive.HivePageSourceFactory; -import io.trino.plugin.hive.HiveStorageFormat; -import io.trino.plugin.hive.orc.OrcPageSourceFactory; -import io.trino.plugin.hive.parquet.ParquetPageSourceFactory; -import io.trino.plugin.hive.parquet.ParquetReaderConfig; -import io.trino.plugin.hive.rcfile.RcFilePageSourceFactory; -import io.trino.spi.Page; -import io.trino.spi.connector.ConnectorSession; -import io.trino.spi.type.Type; -import org.joda.time.DateTimeZone; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.List; -import java.util.Optional; - -import static io.trino.orc.OrcWriteValidation.OrcWriteValidationMode.BOTH; -import static io.trino.parquet.writer.ParquetSchemaConverter.HIVE_PARQUET_USE_INT96_TIMESTAMP_ENCODING; -import static io.trino.parquet.writer.ParquetSchemaConverter.HIVE_PARQUET_USE_LEGACY_DECIMAL_ENCODING; -import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; -import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_STATS; -import static org.joda.time.DateTimeZone.UTC; - -public final class StandardFileFormats -{ - private StandardFileFormats() {} - - public static final FileFormat TRINO_RCBINARY = new AbstractFileFormat() - { - @Override - public HiveStorageFormat getFormat() - { - return HiveStorageFormat.RCBINARY; - } - - @Override - public HivePageSourceFactory getHivePageSourceFactory(HdfsEnvironment hdfsEnvironment) - { - return new RcFilePageSourceFactory(HDFS_FILE_SYSTEM_FACTORY, new HiveConfig().setRcfileTimeZone("UTC")); - } - - @Override - public FormatWriter createFileFormatWriter( - ConnectorSession session, - File targetFile, - List columnNames, - List columnTypes, - HiveCompressionCodec compressionCodec) - throws IOException - { - return new PrestoRcFileFormatWriter( - targetFile, - columnTypes, - new BinaryColumnEncodingFactory(UTC), - compressionCodec); - } - }; - - public static final FileFormat TRINO_RCTEXT = new AbstractFileFormat() - { - @Override - public HiveStorageFormat getFormat() - { - return HiveStorageFormat.RCTEXT; - } - - @Override - public HivePageSourceFactory getHivePageSourceFactory(HdfsEnvironment hdfsEnvironment) - { - return new RcFilePageSourceFactory(HDFS_FILE_SYSTEM_FACTORY, new HiveConfig().setRcfileTimeZone("UTC")); - } - - @Override - public FormatWriter createFileFormatWriter( - ConnectorSession session, - File targetFile, - List columnNames, - List columnTypes, - HiveCompressionCodec compressionCodec) - throws IOException - { - return new PrestoRcFileFormatWriter( - targetFile, - columnTypes, - new TextColumnEncodingFactory(), - compressionCodec); - } - }; - - public static final FileFormat TRINO_ORC = new AbstractFileFormat() - { - @Override - public HiveStorageFormat getFormat() - { - return HiveStorageFormat.ORC; - } - - @Override - public HivePageSourceFactory getHivePageSourceFactory(HdfsEnvironment hdfsEnvironment) - { - return new OrcPageSourceFactory(new OrcReaderOptions(), HDFS_FILE_SYSTEM_FACTORY, new FileFormatDataSourceStats(), UTC); - } - - @Override - public FormatWriter createFileFormatWriter( - ConnectorSession session, - File targetFile, - List columnNames, - List columnTypes, - HiveCompressionCodec compressionCodec) - throws IOException - { - return new PrestoOrcFormatWriter( - targetFile, - columnNames, - columnTypes, - compressionCodec); - } - }; - - public static final FileFormat TRINO_PARQUET = new AbstractFileFormat() - { - @Override - public HiveStorageFormat getFormat() - { - return HiveStorageFormat.PARQUET; - } - - @Override - public HivePageSourceFactory getHivePageSourceFactory(HdfsEnvironment hdfsEnvironment) - { - return new ParquetPageSourceFactory( - new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS), - new FileFormatDataSourceStats(), - new ParquetReaderConfig(), - new HiveConfig()); - } - - @Override - public FormatWriter createFileFormatWriter( - ConnectorSession session, - File targetFile, - List columnNames, - List columnTypes, - HiveCompressionCodec compressionCodec) - throws IOException - { - return new PrestoParquetFormatWriter(targetFile, columnNames, columnTypes, compressionCodec); - } - }; - - private static class PrestoParquetFormatWriter - implements FormatWriter - { - private final ParquetWriter writer; - - public PrestoParquetFormatWriter(File targetFile, List columnNames, List types, HiveCompressionCodec compressionCodec) - throws IOException - { - ParquetSchemaConverter schemaConverter = new ParquetSchemaConverter( - types, - columnNames, - HIVE_PARQUET_USE_LEGACY_DECIMAL_ENCODING, - HIVE_PARQUET_USE_INT96_TIMESTAMP_ENCODING); - - writer = new ParquetWriter( - new FileOutputStream(targetFile), - schemaConverter.getMessageType(), - schemaConverter.getPrimitiveTypes(), - ParquetWriterOptions.builder().build(), - compressionCodec.getParquetCompressionCodec(), - "test-version", - Optional.of(DateTimeZone.getDefault()), - Optional.empty()); - } - - @Override - public void writePage(Page page) - throws IOException - { - writer.write(page); - } - - @Override - public void close() - throws IOException - { - writer.close(); - } - } - - private static class PrestoOrcFormatWriter - implements FormatWriter - { - private final OrcWriter writer; - - public PrestoOrcFormatWriter(File targetFile, List columnNames, List types, HiveCompressionCodec compressionCodec) - throws IOException - { - writer = new OrcWriter( - OutputStreamOrcDataSink.create(new LocalOutputFile(targetFile)), - columnNames, - types, - OrcType.createRootOrcType(columnNames, types), - compressionCodec.getOrcCompressionKind(), - new OrcWriterOptions(), - ImmutableMap.of(), - false, - BOTH, - new OrcWriterStats()); - } - - @Override - public void writePage(Page page) - throws IOException - { - writer.write(page); - } - - @Override - public void close() - throws IOException - { - writer.close(); - } - } - - private static class PrestoRcFileFormatWriter - implements FormatWriter - { - private final RcFileWriter writer; - - public PrestoRcFileFormatWriter(File targetFile, List types, ColumnEncodingFactory encoding, HiveCompressionCodec compressionCodec) - throws IOException - { - writer = new RcFileWriter( - new FileOutputStream(targetFile), - types, - encoding, - compressionCodec.getHiveCompressionKind(), - ImmutableMap.of(), - true); - } - - @Override - public void writePage(Page page) - throws IOException - { - writer.write(page); - } - - @Override - public void close() - throws IOException - { - writer.close(); - } - } -} diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/TestData.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/TestData.java deleted file mode 100644 index 89ce02131838..000000000000 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/TestData.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 io.trino.plugin.hive.benchmark; - -import com.google.common.collect.ImmutableList; -import io.trino.spi.Page; -import io.trino.spi.type.Type; - -import java.util.List; - -public class TestData -{ - private final List columnNames; - private final List columnTypes; - - private final List pages; - - private final int size; - - public TestData(List columnNames, List columnTypes, List pages) - { - this.columnNames = ImmutableList.copyOf(columnNames); - this.columnTypes = ImmutableList.copyOf(columnTypes); - this.pages = ImmutableList.copyOf(pages); - this.size = (int) pages.stream().mapToLong(Page::getSizeInBytes).sum(); - } - - public List getColumnNames() - { - return columnNames; - } - - public List getColumnTypes() - { - return columnTypes; - } - - public List getPages() - { - return pages; - } - - public int getSize() - { - return size; - } -} diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/TestHiveFileFormatBenchmark.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/TestHiveFileFormatBenchmark.java deleted file mode 100644 index 82f1292ff4cf..000000000000 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/TestHiveFileFormatBenchmark.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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 io.trino.plugin.hive.benchmark; - -import io.trino.plugin.hive.HiveCompressionCodec; -import io.trino.plugin.hive.benchmark.BenchmarkHiveFileFormat.CompressionCounter; -import io.trino.plugin.hive.benchmark.BenchmarkHiveFileFormat.DataSet; -import org.junit.jupiter.api.Test; - -import java.io.IOException; - -import static io.trino.plugin.hive.HiveCompressionCodec.SNAPPY; -import static io.trino.plugin.hive.benchmark.BenchmarkFileFormat.TRINO_ORC; -import static io.trino.plugin.hive.benchmark.BenchmarkFileFormat.TRINO_PARQUET; -import static io.trino.plugin.hive.benchmark.BenchmarkFileFormat.TRINO_RCBINARY; -import static io.trino.plugin.hive.benchmark.BenchmarkHiveFileFormat.DataSet.LARGE_MAP_VARCHAR_DOUBLE; -import static io.trino.plugin.hive.benchmark.BenchmarkHiveFileFormat.DataSet.LINEITEM; -import static io.trino.plugin.hive.benchmark.BenchmarkHiveFileFormat.DataSet.MAP_VARCHAR_DOUBLE; - -public class TestHiveFileFormatBenchmark -{ - @Test - public void testSomeFormats() - throws Exception - { - executeBenchmark(LINEITEM, SNAPPY, TRINO_RCBINARY); - executeBenchmark(LINEITEM, SNAPPY, TRINO_ORC); - executeBenchmark(LINEITEM, SNAPPY, TRINO_PARQUET); - executeBenchmark(MAP_VARCHAR_DOUBLE, SNAPPY, TRINO_RCBINARY); - executeBenchmark(MAP_VARCHAR_DOUBLE, SNAPPY, TRINO_ORC); - executeBenchmark(LARGE_MAP_VARCHAR_DOUBLE, SNAPPY, TRINO_RCBINARY); - executeBenchmark(LARGE_MAP_VARCHAR_DOUBLE, SNAPPY, TRINO_ORC); - } - - @Test - public void testAllCompression() - throws Exception - { - for (HiveCompressionCodec codec : HiveCompressionCodec.values()) { - executeBenchmark(LINEITEM, codec, TRINO_RCBINARY); - } - } - - @Test - public void testAllDataSets() - throws Exception - { - for (DataSet dataSet : DataSet.values()) { - executeBenchmark(dataSet, SNAPPY, TRINO_RCBINARY); - } - } - - private static void executeBenchmark(DataSet dataSet, HiveCompressionCodec codec, BenchmarkFileFormat format) - throws IOException - { - BenchmarkHiveFileFormat benchmark = new BenchmarkHiveFileFormat(dataSet, codec, format); - try { - benchmark.setup(); - benchmark.read(new CompressionCounter()); - benchmark.write(new CompressionCounter()); - } - catch (Exception e) { - throw new RuntimeException("Failed " + dataSet + " " + codec + " " + format, e); - } - finally { - benchmark.tearDown(); - } - } -} diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetTester.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetTester.java index 7e71e7f92235..4a848cd22a5e 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetTester.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetTester.java @@ -31,8 +31,6 @@ import io.trino.plugin.hive.HiveConfig; import io.trino.plugin.hive.HiveSessionProperties; import io.trino.plugin.hive.HiveStorageFormat; -import io.trino.plugin.hive.benchmark.FileFormat; -import io.trino.plugin.hive.benchmark.StandardFileFormats; import io.trino.plugin.hive.orc.OrcReaderConfig; import io.trino.plugin.hive.orc.OrcWriterConfig; import io.trino.plugin.hive.parquet.write.MapKeyValuesSchemaConverter; @@ -116,6 +114,7 @@ import static io.trino.plugin.hive.HiveSessionProperties.getParquetMaxReadBlockSize; import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; import static io.trino.plugin.hive.HiveTestUtils.getHiveSession; +import static io.trino.plugin.hive.parquet.ParquetUtil.createParquetPageSource; import static io.trino.plugin.hive.util.HiveUtil.isStructuralType; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.BooleanType.BOOLEAN; @@ -152,7 +151,7 @@ import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; -public class ParquetTester +class ParquetTester { private static final int MAX_PRECISION_INT64 = toIntExact(maxPrecision(8)); @@ -170,16 +169,13 @@ public class ParquetTester private final Set sessions; - private final FileFormat fileFormat; - public static ParquetTester quickParquetTester() { return new ParquetTester( ImmutableSet.of(GZIP), ImmutableSet.of(GZIP), ImmutableSet.of(PARQUET_1_0), - ImmutableSet.of(SESSION), - StandardFileFormats.TRINO_PARQUET); + ImmutableSet.of(SESSION)); } public static ParquetTester fullParquetTester() @@ -188,22 +184,19 @@ public static ParquetTester fullParquetTester() ImmutableSet.of(GZIP, UNCOMPRESSED, SNAPPY, LZO, LZ4, ZSTD), ImmutableSet.of(GZIP, UNCOMPRESSED, SNAPPY, ZSTD), ImmutableSet.copyOf(WriterVersion.values()), - ImmutableSet.of(SESSION, SESSION_USE_NAME), - StandardFileFormats.TRINO_PARQUET); + ImmutableSet.of(SESSION, SESSION_USE_NAME)); } public ParquetTester( Set compressions, Set writerCompressions, Set versions, - Set sessions, - FileFormat fileFormat) + Set sessions) { this.compressions = requireNonNull(compressions, "compressions is null"); this.writerCompressions = requireNonNull(writerCompressions, "writerCompressions is null"); this.versions = requireNonNull(versions, "writerCompressions is null"); this.sessions = requireNonNull(sessions, "sessions is null"); - this.fileFormat = requireNonNull(fileFormat, "fileFormat is null"); } public void testRoundTrip(PrimitiveObjectInspector columnObjectInspector, Iterable writeValues, Type parameterType) @@ -462,7 +455,7 @@ void assertMaxReadBytes( DateTimeZone.getDefault()); Iterator[] expectedValues = getIterators(readValues); - try (ConnectorPageSource pageSource = fileFormat.createFileFormatReader( + try (ConnectorPageSource pageSource = createParquetPageSource( session, HDFS_ENVIRONMENT, tempFile.getFile(), @@ -486,7 +479,7 @@ private void assertFileContents( List columnTypes) throws IOException { - try (ConnectorPageSource pageSource = fileFormat.createFileFormatReader( + try (ConnectorPageSource pageSource = createParquetPageSource( session, HDFS_ENVIRONMENT, dataFile, diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/AbstractFileFormat.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetUtil.java similarity index 56% rename from plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/AbstractFileFormat.java rename to plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetUtil.java index 8e84c5edc61c..959c2b2cf6cb 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/benchmark/AbstractFileFormat.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetUtil.java @@ -11,32 +11,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.benchmark; +package io.trino.plugin.hive.parquet; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; import io.trino.filesystem.Location; +import io.trino.filesystem.hdfs.HdfsFileSystemFactory; import io.trino.hdfs.HdfsEnvironment; +import io.trino.plugin.hive.FileFormatDataSourceStats; import io.trino.plugin.hive.HiveColumnHandle; import io.trino.plugin.hive.HiveConfig; import io.trino.plugin.hive.HivePageSourceFactory; -import io.trino.plugin.hive.HivePageSourceProvider; -import io.trino.plugin.hive.HiveSplit; import io.trino.plugin.hive.HiveStorageFormat; -import io.trino.plugin.hive.HiveTableHandle; import io.trino.plugin.hive.HiveType; import io.trino.plugin.hive.HiveTypeName; import io.trino.plugin.hive.ReaderPageSource; -import io.trino.plugin.hive.TableToPartitionMapping; -import io.trino.spi.SplitWeight; -import io.trino.spi.connector.ColumnHandle; import io.trino.spi.connector.ConnectorPageSource; import io.trino.spi.connector.ConnectorSession; -import io.trino.spi.connector.DynamicFilter; import io.trino.spi.predicate.TupleDomain; import io.trino.spi.type.Type; -import io.trino.sql.planner.TestingConnectorTransactionHandle; import java.io.File; import java.util.List; @@ -50,86 +41,37 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static io.trino.plugin.hive.HiveColumnHandle.ColumnType.REGULAR; import static io.trino.plugin.hive.HiveColumnHandle.createBaseColumn; +import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_STATS; import static io.trino.plugin.hive.HiveType.toHiveType; import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; import static io.trino.plugin.hive.util.SerdeConstants.SERIALIZATION_LIB; -import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.lang.String.join; import static java.util.stream.Collectors.joining; -import static org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.FILE_INPUT_FORMAT; -import static org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.META_TABLE_COLUMNS; -import static org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.META_TABLE_COLUMN_TYPES; -public abstract class AbstractFileFormat - implements FileFormat +final class ParquetUtil { - @Override - public boolean supportsDate() + private ParquetUtil() {} + + public static HivePageSourceFactory createHivePageSourceFactory(HdfsEnvironment hdfsEnvironment) { - return true; + return new ParquetPageSourceFactory( + new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS), + new FileFormatDataSourceStats(), + new ParquetReaderConfig(), + new HiveConfig()); } - @Override - public ConnectorPageSource createFileFormatReader( + public static ConnectorPageSource createParquetPageSource( ConnectorSession session, HdfsEnvironment hdfsEnvironment, File targetFile, List columnNames, List columnTypes) { - return createPageSource(getHivePageSourceFactory(hdfsEnvironment), session, targetFile, columnNames, columnTypes, getFormat()); - } - - @Override - public ConnectorPageSource createGenericReader( - ConnectorSession session, - HdfsEnvironment hdfsEnvironment, - File targetFile, - List readColumns, - List schemaColumnNames, - List schemaColumnTypes) - { - HivePageSourceProvider factory = new HivePageSourceProvider( - TESTING_TYPE_MANAGER, - new HiveConfig(), - ImmutableSet.of(getHivePageSourceFactory(hdfsEnvironment))); - - Properties schema = createSchema(getFormat(), schemaColumnNames, schemaColumnTypes); - - HiveSplit split = new HiveSplit( - "", - targetFile.getPath(), - 0, - targetFile.length(), - targetFile.length(), - targetFile.lastModified(), - schema, - ImmutableList.of(), - ImmutableList.of(), - OptionalInt.empty(), - OptionalInt.empty(), - false, - TableToPartitionMapping.empty(), - Optional.empty(), - Optional.empty(), - Optional.empty(), - SplitWeight.standard()); - - return factory.createPageSource( - TestingConnectorTransactionHandle.INSTANCE, - session, split, - new HiveTableHandle("schema_name", "table_name", ImmutableMap.of(), ImmutableList.of(), ImmutableList.of(), Optional.empty()), - readColumns, - DynamicFilter.EMPTY); - } - - @Override - public boolean supports(TestData testData) - { - return true; + return createPageSource(createHivePageSourceFactory(hdfsEnvironment), session, targetFile, columnNames, columnTypes, HiveStorageFormat.PARQUET); } - static ConnectorPageSource createPageSource( + private static ConnectorPageSource createPageSource( HivePageSourceFactory pageSourceFactory, ConnectorSession session, File targetFile, @@ -162,7 +104,7 @@ static ConnectorPageSource createPageSource( return readerPageSourceWithProjections.get().get(); } - static List getBaseColumns(List columnNames, List columnTypes) + private static List getBaseColumns(List columnNames, List columnTypes) { return IntStream.range(0, columnNames.size()) .boxed() @@ -176,13 +118,13 @@ static List getBaseColumns(List columnNames, List columnNames, List columnTypes) + private static Properties createSchema(HiveStorageFormat format, List columnNames, List columnTypes) { Properties schema = new Properties(); schema.setProperty(SERIALIZATION_LIB, format.getSerde()); - schema.setProperty(FILE_INPUT_FORMAT, format.getInputFormat()); - schema.setProperty(META_TABLE_COLUMNS, join(",", columnNames)); - schema.setProperty(META_TABLE_COLUMN_TYPES, columnTypes.stream() + schema.setProperty("file.inputformat", format.getInputFormat()); + schema.setProperty("columns", join(",", columnNames)); + schema.setProperty("columns.types", columnTypes.stream() .map(HiveType::toHiveType) .map(HiveType::getHiveTypeName) .map(HiveTypeName::toString) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestOnlyNulls.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestOnlyNulls.java index e8ae8033b559..d77be2bced20 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestOnlyNulls.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestOnlyNulls.java @@ -21,7 +21,6 @@ import io.trino.plugin.hive.HiveStorageFormat; import io.trino.plugin.hive.HiveType; import io.trino.plugin.hive.acid.AcidTransaction; -import io.trino.plugin.hive.benchmark.StandardFileFormats; import io.trino.spi.connector.ConnectorPageSource; import io.trino.spi.predicate.Domain; import io.trino.spi.predicate.TupleDomain; @@ -81,7 +80,7 @@ public void testOnlyNulls() private static ConnectorPageSource createPageSource(File parquetFile, HiveColumnHandle column, TupleDomain domain) { - HivePageSourceFactory pageSourceFactory = StandardFileFormats.TRINO_PARQUET.getHivePageSourceFactory(HDFS_ENVIRONMENT); + HivePageSourceFactory pageSourceFactory = ParquetUtil.createHivePageSourceFactory(HDFS_ENVIRONMENT); Properties schema = new Properties(); schema.setProperty(SERIALIZATION_LIB, HiveStorageFormat.PARQUET.getSerde()); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestamp.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestamp.java index e02c7ab90afc..46522fcc9f3e 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestamp.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestamp.java @@ -19,7 +19,6 @@ import com.google.common.collect.Range; import io.trino.plugin.hive.HiveConfig; import io.trino.plugin.hive.HiveTimestampPrecision; -import io.trino.plugin.hive.benchmark.StandardFileFormats; import io.trino.spi.Page; import io.trino.spi.block.Block; import io.trino.spi.connector.ConnectorPageSource; @@ -137,7 +136,7 @@ private static void testReadingAs(Type type, ConnectorSession session, ParquetTe throws IOException { Iterator expected = expectedValues.iterator(); - try (ConnectorPageSource pageSource = StandardFileFormats.TRINO_PARQUET.createFileFormatReader(session, HDFS_ENVIRONMENT, tempFile.getFile(), columnNames, ImmutableList.of(type))) { + try (ConnectorPageSource pageSource = ParquetUtil.createParquetPageSource(session, HDFS_ENVIRONMENT, tempFile.getFile(), columnNames, ImmutableList.of(type))) { // skip a page to exercise the decoder's skip() logic Page firstPage = pageSource.getNextPage(); assertTrue(firstPage.getPositionCount() > 0, "Expected first page to have at least 1 row"); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestampMicros.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestampMicros.java index 3aa9ac15a7f5..f9f9f5d0a221 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestampMicros.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestampMicros.java @@ -22,7 +22,6 @@ import io.trino.plugin.hive.HiveType; import io.trino.plugin.hive.ReaderPageSource; import io.trino.plugin.hive.acid.AcidTransaction; -import io.trino.plugin.hive.benchmark.StandardFileFormats; import io.trino.spi.connector.ConnectorPageSource; import io.trino.spi.connector.ConnectorSession; import io.trino.spi.predicate.TupleDomain; @@ -97,9 +96,9 @@ public static Object[][] testTimestampMicrosDataProvider() private ConnectorPageSource createPageSource(ConnectorSession session, File parquetFile, String columnName, HiveType columnHiveType, Type columnType) { // TODO after https://github.com/trinodb/trino/pull/5283, replace the method with - // return FileFormat.PRESTO_PARQUET.createFileFormatReader(session, HDFS_ENVIRONMENT, parquetFile, columnNames, columnTypes); + // return ParquetUtil.PRESTO_PARQUET.createFileFormatReader(session, HDFS_ENVIRONMENT, parquetFile, columnNames, columnTypes); - HivePageSourceFactory pageSourceFactory = StandardFileFormats.TRINO_PARQUET.getHivePageSourceFactory(HDFS_ENVIRONMENT); + HivePageSourceFactory pageSourceFactory = ParquetUtil.createHivePageSourceFactory(HDFS_ENVIRONMENT); Properties schema = new Properties(); schema.setProperty(SERIALIZATION_LIB, HiveStorageFormat.PARQUET.getSerde()); From 0162fcc37a2b921f30ebd6990ccd677b1f81cbfe Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Sat, 4 Nov 2023 00:45:46 -0700 Subject: [PATCH 087/587] Use memory file system in Parquet tests --- .../io/trino/testing/MaterializedResult.java | 5 - .../memory/MemoryFileSystemFactory.java | 4 +- .../plugin/hive/parquet/ParquetTester.java | 9 +- .../plugin/hive/parquet/ParquetUtil.java | 98 +++++++------------ .../plugin/hive/parquet/TestOnlyNulls.java | 42 ++------ .../plugin/hive/parquet/TestTimestamp.java | 3 +- .../hive/parquet/TestTimestampMicros.java | 59 ++--------- 7 files changed, 55 insertions(+), 165 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/testing/MaterializedResult.java b/core/trino-main/src/main/java/io/trino/testing/MaterializedResult.java index f3fffd9a1026..061b6d2ab64d 100644 --- a/core/trino-main/src/main/java/io/trino/testing/MaterializedResult.java +++ b/core/trino-main/src/main/java/io/trino/testing/MaterializedResult.java @@ -472,11 +472,6 @@ else if (trinoValue instanceof SqlDecimal) { return new MaterializedRow(trinoRow.getPrecision(), convertedValues); } - public static MaterializedResult materializeSourceDataStream(Session session, ConnectorPageSource pageSource, List types) - { - return materializeSourceDataStream(session.toConnectorSession(), pageSource, types); - } - public static MaterializedResult materializeSourceDataStream(ConnectorSession session, ConnectorPageSource pageSource, List types) { MaterializedResult.Builder builder = resultBuilder(session, types); diff --git a/lib/trino-filesystem/src/main/java/io/trino/filesystem/memory/MemoryFileSystemFactory.java b/lib/trino-filesystem/src/main/java/io/trino/filesystem/memory/MemoryFileSystemFactory.java index 3521be7f6b77..ea582f975e5d 100644 --- a/lib/trino-filesystem/src/main/java/io/trino/filesystem/memory/MemoryFileSystemFactory.java +++ b/lib/trino-filesystem/src/main/java/io/trino/filesystem/memory/MemoryFileSystemFactory.java @@ -23,9 +23,11 @@ public class MemoryFileSystemFactory implements TrinoFileSystemFactory { + private final MemoryFileSystem memoryFileSystem = new MemoryFileSystem(); + @Override public TrinoFileSystem create(ConnectorIdentity identity) { - return new MemoryFileSystem(); + return memoryFileSystem; } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetTester.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetTester.java index 4a848cd22a5e..ad4faaea3f89 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetTester.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetTester.java @@ -112,9 +112,8 @@ import static io.trino.plugin.hive.AbstractTestHiveFileFormats.getFieldFromCursor; import static io.trino.plugin.hive.HiveErrorCode.HIVE_WRITE_VALIDATION_FAILED; import static io.trino.plugin.hive.HiveSessionProperties.getParquetMaxReadBlockSize; -import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; import static io.trino.plugin.hive.HiveTestUtils.getHiveSession; -import static io.trino.plugin.hive.parquet.ParquetUtil.createParquetPageSource; +import static io.trino.plugin.hive.parquet.ParquetUtil.createPageSource; import static io.trino.plugin.hive.util.HiveUtil.isStructuralType; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.BooleanType.BOOLEAN; @@ -455,9 +454,8 @@ void assertMaxReadBytes( DateTimeZone.getDefault()); Iterator[] expectedValues = getIterators(readValues); - try (ConnectorPageSource pageSource = createParquetPageSource( + try (ConnectorPageSource pageSource = createPageSource( session, - HDFS_ENVIRONMENT, tempFile.getFile(), columnNames, columnTypes)) { @@ -479,9 +477,8 @@ private void assertFileContents( List columnTypes) throws IOException { - try (ConnectorPageSource pageSource = createParquetPageSource( + try (ConnectorPageSource pageSource = createPageSource( session, - HDFS_ENVIRONMENT, dataFile, columnNames, columnTypes)) { diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetUtil.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetUtil.java index 959c2b2cf6cb..dfddba972a4f 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetUtil.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetUtil.java @@ -14,22 +14,22 @@ package io.trino.plugin.hive.parquet; import io.trino.filesystem.Location; -import io.trino.filesystem.hdfs.HdfsFileSystemFactory; -import io.trino.hdfs.HdfsEnvironment; +import io.trino.filesystem.memory.MemoryFileSystemFactory; import io.trino.plugin.hive.FileFormatDataSourceStats; import io.trino.plugin.hive.HiveColumnHandle; import io.trino.plugin.hive.HiveConfig; import io.trino.plugin.hive.HivePageSourceFactory; import io.trino.plugin.hive.HiveStorageFormat; -import io.trino.plugin.hive.HiveType; -import io.trino.plugin.hive.HiveTypeName; -import io.trino.plugin.hive.ReaderPageSource; import io.trino.spi.connector.ConnectorPageSource; import io.trino.spi.connector.ConnectorSession; import io.trino.spi.predicate.TupleDomain; +import io.trino.spi.security.ConnectorIdentity; import io.trino.spi.type.Type; import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; import java.util.List; import java.util.Optional; import java.util.OptionalInt; @@ -37,78 +37,64 @@ import java.util.stream.IntStream; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableList.toImmutableList; import static io.trino.plugin.hive.HiveColumnHandle.ColumnType.REGULAR; import static io.trino.plugin.hive.HiveColumnHandle.createBaseColumn; -import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_STATS; import static io.trino.plugin.hive.HiveType.toHiveType; import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; import static io.trino.plugin.hive.util.SerdeConstants.SERIALIZATION_LIB; -import static java.lang.String.join; -import static java.util.stream.Collectors.joining; final class ParquetUtil { private ParquetUtil() {} - public static HivePageSourceFactory createHivePageSourceFactory(HdfsEnvironment hdfsEnvironment) + public static ConnectorPageSource createPageSource(ConnectorSession session, File parquetFile, List columnNames, List columnTypes) + throws IOException { - return new ParquetPageSourceFactory( - new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS), - new FileFormatDataSourceStats(), - new ParquetReaderConfig(), - new HiveConfig()); + return createPageSource(session, parquetFile, getBaseColumns(columnNames, columnTypes), TupleDomain.all()); } - public static ConnectorPageSource createParquetPageSource( - ConnectorSession session, - HdfsEnvironment hdfsEnvironment, - File targetFile, - List columnNames, - List columnTypes) + public static ConnectorPageSource createPageSource(ConnectorSession session, File parquetFile, List columns, TupleDomain domain) + throws IOException { - return createPageSource(createHivePageSourceFactory(hdfsEnvironment), session, targetFile, columnNames, columnTypes, HiveStorageFormat.PARQUET); - } + // copy the test file into the memory filesystem + MemoryFileSystemFactory fileSystemFactory = new MemoryFileSystemFactory(); + Location location = Location.of("memory:///test.file"); + try (OutputStream out = fileSystemFactory.create(ConnectorIdentity.ofUser("test")).newOutputFile(location).create()) { + out.write(Files.readAllBytes(parquetFile.toPath())); + } - private static ConnectorPageSource createPageSource( - HivePageSourceFactory pageSourceFactory, - ConnectorSession session, - File targetFile, - List columnNames, - List columnTypes, - HiveStorageFormat format) - { - checkArgument(columnNames.size() == columnTypes.size(), "columnNames and columnTypes should have the same size"); - - List readColumns = getBaseColumns(columnNames, columnTypes); + HivePageSourceFactory hivePageSourceFactory = new ParquetPageSourceFactory( + fileSystemFactory, + new FileFormatDataSourceStats(), + new ParquetReaderConfig(), + new HiveConfig()); - Properties schema = createSchema(format, columnNames, columnTypes); - Optional readerPageSourceWithProjections = pageSourceFactory - .createPageSource( + Properties schema = new Properties(); + schema.setProperty(SERIALIZATION_LIB, HiveStorageFormat.PARQUET.getSerde()); + return hivePageSourceFactory.createPageSource( session, - Location.of(targetFile.getAbsolutePath()), + location, 0, - targetFile.length(), - targetFile.length(), + parquetFile.length(), + parquetFile.length(), schema, - readColumns, - TupleDomain.all(), + columns, + domain, Optional.empty(), OptionalInt.empty(), false, - NO_ACID_TRANSACTION); - - checkState(readerPageSourceWithProjections.isPresent(), "readerPageSourceWithProjections is not present"); - checkState(readerPageSourceWithProjections.get().getReaderColumns().isEmpty(), "projection should not be required"); - return readerPageSourceWithProjections.get().get(); + NO_ACID_TRANSACTION) + .orElseThrow() + .get(); } private static List getBaseColumns(List columnNames, List columnTypes) { + checkArgument(columnNames.size() == columnTypes.size(), "columnNames and columnTypes should have the same size"); + return IntStream.range(0, columnNames.size()) - .boxed() - .map(index -> createBaseColumn( + .mapToObj(index -> createBaseColumn( columnNames.get(index), index, toHiveType(columnTypes.get(index)), @@ -117,18 +103,4 @@ private static List getBaseColumns(List columnNames, L Optional.empty())) .collect(toImmutableList()); } - - private static Properties createSchema(HiveStorageFormat format, List columnNames, List columnTypes) - { - Properties schema = new Properties(); - schema.setProperty(SERIALIZATION_LIB, format.getSerde()); - schema.setProperty("file.inputformat", format.getInputFormat()); - schema.setProperty("columns", join(",", columnNames)); - schema.setProperty("columns.types", columnTypes.stream() - .map(HiveType::toHiveType) - .map(HiveType::getHiveTypeName) - .map(HiveTypeName::toString) - .collect(joining(":"))); - return schema; - } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestOnlyNulls.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestOnlyNulls.java index d77be2bced20..2774bca44bf1 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestOnlyNulls.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestOnlyNulls.java @@ -13,16 +13,12 @@ */ package io.trino.plugin.hive.parquet; +import com.google.common.collect.ImmutableList; import com.google.common.io.Resources; -import io.trino.filesystem.Location; import io.trino.plugin.hive.HiveColumnHandle; import io.trino.plugin.hive.HiveConfig; -import io.trino.plugin.hive.HivePageSourceFactory; -import io.trino.plugin.hive.HiveStorageFormat; import io.trino.plugin.hive.HiveType; -import io.trino.plugin.hive.acid.AcidTransaction; import io.trino.spi.connector.ConnectorPageSource; -import io.trino.spi.predicate.Domain; import io.trino.spi.predicate.TupleDomain; import io.trino.spi.type.Type; import io.trino.testing.MaterializedResult; @@ -33,14 +29,14 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.OptionalInt; -import java.util.Properties; import static io.trino.plugin.hive.HiveColumnHandle.ColumnType.REGULAR; import static io.trino.plugin.hive.HiveColumnHandle.createBaseColumn; -import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; +import static io.trino.plugin.hive.HiveTestUtils.SESSION; import static io.trino.plugin.hive.HiveTestUtils.getHiveSession; -import static io.trino.plugin.hive.util.SerdeConstants.SERIALIZATION_LIB; +import static io.trino.plugin.hive.parquet.ParquetUtil.createPageSource; +import static io.trino.spi.predicate.Domain.notNull; +import static io.trino.spi.predicate.Domain.onlyNull; import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.testing.MaterializedResult.materializeSourceDataStream; import static java.util.Collections.singletonList; @@ -60,13 +56,13 @@ public void testOnlyNulls() HiveColumnHandle column = createBaseColumn(columnName, 0, HiveType.toHiveType(columnType), columnType, REGULAR, Optional.empty()); // match not null - try (ConnectorPageSource pageSource = createPageSource(parquetFile, column, TupleDomain.withColumnDomains(Map.of(column, Domain.notNull(columnType))))) { + try (ConnectorPageSource pageSource = createPageSource(SESSION, parquetFile, ImmutableList.of(column), TupleDomain.withColumnDomains(Map.of(column, notNull(columnType))))) { MaterializedResult result = materializeSourceDataStream(getHiveSession(new HiveConfig()), pageSource, List.of(columnType)).toTestTypes(); assertThat(result.getMaterializedRows()).isEmpty(); } // match null - try (ConnectorPageSource pageSource = createPageSource(parquetFile, column, TupleDomain.withColumnDomains(Map.of(column, Domain.onlyNull(columnType))))) { + try (ConnectorPageSource pageSource = createPageSource(SESSION, parquetFile, ImmutableList.of(column), TupleDomain.withColumnDomains(Map.of(column, onlyNull(columnType))))) { MaterializedResult result = materializeSourceDataStream(getHiveSession(new HiveConfig()), pageSource, List.of(columnType)).toTestTypes(); assertThat(result.getMaterializedRows()) @@ -77,28 +73,4 @@ public void testOnlyNulls() new MaterializedRow(singletonList(null)))); } } - - private static ConnectorPageSource createPageSource(File parquetFile, HiveColumnHandle column, TupleDomain domain) - { - HivePageSourceFactory pageSourceFactory = ParquetUtil.createHivePageSourceFactory(HDFS_ENVIRONMENT); - - Properties schema = new Properties(); - schema.setProperty(SERIALIZATION_LIB, HiveStorageFormat.PARQUET.getSerde()); - - return pageSourceFactory.createPageSource( - getHiveSession(new HiveConfig()), - Location.of(parquetFile.getPath()), - 0, - parquetFile.length(), - parquetFile.length(), - schema, - List.of(column), - domain, - Optional.empty(), - OptionalInt.empty(), - false, - AcidTransaction.NO_ACID_TRANSACTION) - .orElseThrow() - .get(); - } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestamp.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestamp.java index 46522fcc9f3e..df390dbdcba2 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestamp.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestamp.java @@ -42,7 +42,6 @@ import static com.google.common.collect.Iterables.cycle; import static com.google.common.collect.Iterables.limit; import static com.google.common.collect.Iterables.transform; -import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; import static io.trino.plugin.hive.HiveTestUtils.getHiveSession; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.TimestampType.createTimestampType; @@ -136,7 +135,7 @@ private static void testReadingAs(Type type, ConnectorSession session, ParquetTe throws IOException { Iterator expected = expectedValues.iterator(); - try (ConnectorPageSource pageSource = ParquetUtil.createParquetPageSource(session, HDFS_ENVIRONMENT, tempFile.getFile(), columnNames, ImmutableList.of(type))) { + try (ConnectorPageSource pageSource = ParquetUtil.createPageSource(session, tempFile.getFile(), columnNames, ImmutableList.of(type))) { // skip a page to exercise the decoder's skip() logic Page firstPage = pageSource.getNextPage(); assertTrue(firstPage.getPositionCount() > 0, "Expected first page to have at least 1 row"); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestampMicros.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestampMicros.java index f9f9f5d0a221..937e4ddbc6b1 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestampMicros.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestampMicros.java @@ -14,16 +14,8 @@ package io.trino.plugin.hive.parquet; import com.google.common.io.Resources; -import io.trino.filesystem.Location; -import io.trino.plugin.hive.HiveConfig; -import io.trino.plugin.hive.HivePageSourceFactory; -import io.trino.plugin.hive.HiveStorageFormat; import io.trino.plugin.hive.HiveTimestampPrecision; -import io.trino.plugin.hive.HiveType; -import io.trino.plugin.hive.ReaderPageSource; -import io.trino.plugin.hive.acid.AcidTransaction; import io.trino.spi.connector.ConnectorPageSource; -import io.trino.spi.connector.ConnectorSession; import io.trino.spi.predicate.TupleDomain; import io.trino.spi.type.Type; import io.trino.testing.MaterializedResult; @@ -36,15 +28,12 @@ import java.time.ZoneId; import java.util.List; import java.util.Optional; -import java.util.OptionalInt; -import java.util.Properties; import static io.trino.plugin.hive.HiveColumnHandle.ColumnType.REGULAR; import static io.trino.plugin.hive.HiveColumnHandle.createBaseColumn; -import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; -import static io.trino.plugin.hive.HiveTestUtils.getHiveSession; +import static io.trino.plugin.hive.HiveTestUtils.SESSION; import static io.trino.plugin.hive.HiveType.HIVE_TIMESTAMP; -import static io.trino.plugin.hive.util.SerdeConstants.SERIALIZATION_LIB; +import static io.trino.plugin.hive.parquet.ParquetUtil.createPageSource; import static io.trino.spi.type.TimestampType.createTimestampType; import static io.trino.spi.type.TimestampWithTimeZoneType.createTimestampWithTimeZoneType; import static io.trino.testing.MaterializedResult.materializeSourceDataStream; @@ -56,13 +45,11 @@ public class TestTimestampMicros public void testTimestampMicros(HiveTimestampPrecision timestampPrecision, LocalDateTime expected) throws Exception { - ConnectorSession session = getHiveSession(new HiveConfig().setTimestampPrecision(timestampPrecision)); - File parquetFile = new File(Resources.getResource("issue-5483.parquet").toURI()); Type columnType = createTimestampType(timestampPrecision.getPrecision()); - try (ConnectorPageSource pageSource = createPageSource(session, parquetFile, "created", HIVE_TIMESTAMP, columnType)) { - MaterializedResult result = materializeSourceDataStream(session, pageSource, List.of(columnType)).toTestTypes(); + try (ConnectorPageSource pageSource = createPageSource(SESSION, parquetFile, List.of(createBaseColumn("created", 0, HIVE_TIMESTAMP, columnType, REGULAR, Optional.empty())), TupleDomain.all())) { + MaterializedResult result = materializeSourceDataStream(SESSION, pageSource, List.of(columnType)).toTestTypes(); assertThat(result.getMaterializedRows()) .containsOnly(new MaterializedRow(List.of(expected))); } @@ -72,13 +59,11 @@ public void testTimestampMicros(HiveTimestampPrecision timestampPrecision, Local public void testTimestampMicrosAsTimestampWithTimeZone(HiveTimestampPrecision timestampPrecision, LocalDateTime expected) throws Exception { - ConnectorSession session = getHiveSession(new HiveConfig().setTimestampPrecision(timestampPrecision)); - File parquetFile = new File(Resources.getResource("issue-5483.parquet").toURI()); Type columnType = createTimestampWithTimeZoneType(timestampPrecision.getPrecision()); - try (ConnectorPageSource pageSource = createPageSource(session, parquetFile, "created", HIVE_TIMESTAMP, columnType)) { - MaterializedResult result = materializeSourceDataStream(session, pageSource, List.of(columnType)).toTestTypes(); + try (ConnectorPageSource pageSource = createPageSource(SESSION, parquetFile, List.of(createBaseColumn("created", 0, HIVE_TIMESTAMP, columnType, REGULAR, Optional.empty())), TupleDomain.all())) { + MaterializedResult result = materializeSourceDataStream(SESSION, pageSource, List.of(columnType)).toTestTypes(); assertThat(result.getMaterializedRows()) .containsOnly(new MaterializedRow(List.of(expected.atZone(ZoneId.of("UTC"))))); } @@ -92,36 +77,4 @@ public static Object[][] testTimestampMicrosDataProvider() {HiveTimestampPrecision.MICROSECONDS, LocalDateTime.parse("2020-10-12T16:26:02.906668")}, {HiveTimestampPrecision.NANOSECONDS, LocalDateTime.parse("2020-10-12T16:26:02.906668")}}; } - - private ConnectorPageSource createPageSource(ConnectorSession session, File parquetFile, String columnName, HiveType columnHiveType, Type columnType) - { - // TODO after https://github.com/trinodb/trino/pull/5283, replace the method with - // return ParquetUtil.PRESTO_PARQUET.createFileFormatReader(session, HDFS_ENVIRONMENT, parquetFile, columnNames, columnTypes); - - HivePageSourceFactory pageSourceFactory = ParquetUtil.createHivePageSourceFactory(HDFS_ENVIRONMENT); - - Properties schema = new Properties(); - schema.setProperty(SERIALIZATION_LIB, HiveStorageFormat.PARQUET.getSerde()); - - ReaderPageSource pageSourceWithProjections = pageSourceFactory.createPageSource( - session, - Location.of(parquetFile.getPath()), - 0, - parquetFile.length(), - parquetFile.length(), - schema, - List.of(createBaseColumn(columnName, 0, columnHiveType, columnType, REGULAR, Optional.empty())), - TupleDomain.all(), - Optional.empty(), - OptionalInt.empty(), - false, - AcidTransaction.NO_ACID_TRANSACTION) - .orElseThrow(); - - pageSourceWithProjections.getReaderColumns().ifPresent(projections -> { - throw new IllegalStateException("Unexpected projections: " + projections); - }); - - return pageSourceWithProjections.get(); - } } From 911a56b0ee7767e0f1dd485c7ad3324b40d87b5f Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Sat, 4 Nov 2023 15:24:02 -0700 Subject: [PATCH 088/587] Remove deprecated HiveType.getType --- .../src/main/java/io/trino/plugin/hive/HiveMetadata.java | 6 +++--- .../src/main/java/io/trino/plugin/hive/HiveType.java | 9 --------- .../main/java/io/trino/plugin/hive/MergeFileWriter.java | 2 +- .../main/java/io/trino/plugin/hive/util/HiveUtil.java | 2 +- .../trino/plugin/hive/AbstractTestHiveFileFormats.java | 8 ++++---- .../java/io/trino/plugin/hive/TestHiveFileFormats.java | 2 +- .../test/java/io/trino/plugin/hive/TestHivePageSink.java | 4 ++-- .../trino/plugin/hive/TestHiveReaderProjectionsUtil.java | 2 +- .../plugin/hive/TestNodeLocalDynamicSplitPruning.java | 4 ++-- .../plugin/hive/TestOrcPageSourceMemoryTracking.java | 2 +- .../io/trino/plugin/hive/util/TestHiveBucketing.java | 4 ++-- 11 files changed, 18 insertions(+), 27 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java index 6a6ac23e4475..a9b31c68234f 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java @@ -1814,7 +1814,7 @@ public Optional finishCreateTable(ConnectorSession sess } Map columnTypes = handle.getInputColumns().stream() - .collect(toImmutableMap(HiveColumnHandle::getName, column -> column.getHiveType().getType(typeManager))); + .collect(toImmutableMap(HiveColumnHandle::getName, column -> typeManager.getType(column.getHiveType().getTypeSignature()))); Map, ComputedStatistics> partitionComputedStatistics = createComputedStatisticsToPartitionMap(computedStatistics, handle.getPartitionedBy(), columnTypes); PartitionStatistics tableStatistics; @@ -2176,7 +2176,7 @@ private Table finishChangingTable(AcidOperation acidOperation, String changeDesc .map(Column::getName) .collect(toImmutableList()); Map columnTypes = handle.getInputColumns().stream() - .collect(toImmutableMap(HiveColumnHandle::getName, column -> column.getHiveType().getType(typeManager))); + .collect(toImmutableMap(HiveColumnHandle::getName, column -> typeManager.getType(column.getHiveType().getTypeSignature()))); Map, ComputedStatistics> partitionComputedStatistics = createComputedStatisticsToPartitionMap(computedStatistics, partitionedBy, columnTypes); ImmutableList.Builder partitionUpdateInfosBuilder = ImmutableList.builder(); @@ -3127,7 +3127,7 @@ private HiveColumnHandle createProjectedColumnHandle(HiveColumnHandle column, Li .addAll(oldHiveType.getHiveDereferenceNames(indices)) .build(), newHiveType, - newHiveType.getType(typeManager)); + typeManager.getType(newHiveType.getTypeSignature())); return new HiveColumnHandle( column.getBaseColumnName(), diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveType.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveType.java index d4c9b7e851d7..0ac49d67f335 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveType.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveType.java @@ -116,15 +116,6 @@ public TypeSignature getTypeSignature(HiveTimestampPrecision timestampPrecision) return toTypeSignature(typeInfo, timestampPrecision); } - /** - * @deprecated Prefer {@link #getType(TypeManager, HiveTimestampPrecision)}. - */ - @Deprecated - public Type getType(TypeManager typeManager) - { - return typeManager.getType(getTypeSignature()); - } - public Type getType(TypeManager typeManager, HiveTimestampPrecision timestampPrecision) { return typeManager.getType(getTypeSignature(timestampPrecision)); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/MergeFileWriter.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/MergeFileWriter.java index 1001e61a0d1e..432c138320bd 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/MergeFileWriter.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/MergeFileWriter.java @@ -111,7 +111,7 @@ public MergeFileWriter( this.session = requireNonNull(session, "session is null"); checkArgument(transaction.isTransactional(), "Not in a transaction: %s", transaction); this.hiveAcidSchema = createAcidSchema(hiveRowType); - this.hiveRowTypeNullsBlock = nativeValueToBlock(hiveRowType.getType(typeManager), null); + this.hiveRowTypeNullsBlock = nativeValueToBlock(typeManager.getType(hiveRowType.getTypeSignature()), null); Matcher matcher = BASE_PATH_MATCHER.matcher(bucketPath); if (!matcher.matches()) { matcher = BUCKET_PATH_MATCHER.matcher(bucketPath); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveUtil.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveUtil.java index a5aae9edb77d..cdd87f1cb87a 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveUtil.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveUtil.java @@ -573,7 +573,7 @@ public static List getPartitionKeyColumnHandles(Table table, T if (!hiveType.isSupportedType(table.getStorage().getStorageFormat())) { throw new TrinoException(NOT_SUPPORTED, format("Unsupported Hive type %s found in partition keys of table %s.%s", hiveType, table.getDatabaseName(), table.getTableName())); } - columns.add(createBaseColumn(field.getName(), -1, hiveType, hiveType.getType(typeManager), PARTITION_KEY, field.getComment())); + columns.add(createBaseColumn(field.getName(), -1, hiveType, typeManager.getType(hiveType.getTypeSignature()), PARTITION_KEY, field.getComment())); } return columns.build(); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileFormats.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileFormats.java index 1e9d8a83e6b4..1e5134b26807 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileFormats.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileFormats.java @@ -496,7 +496,7 @@ protected List getColumnHandles(List testColumns) if (testColumn.getDereferenceNames().size() == 0) { HiveType hiveType = HiveType.valueOf(testColumn.getObjectInspector().getTypeName()); - columns.add(createBaseColumn(testColumn.getName(), columnIndex, hiveType, hiveType.getType(TESTING_TYPE_MANAGER), testColumn.isPartitionKey() ? PARTITION_KEY : REGULAR, Optional.empty())); + columns.add(createBaseColumn(testColumn.getName(), columnIndex, hiveType, TESTING_TYPE_MANAGER.getType(hiveType.getTypeSignature()), testColumn.isPartitionKey() ? PARTITION_KEY : REGULAR, Optional.empty())); } else { HiveType baseHiveType = HiveType.valueOf(testColumn.getBaseObjectInspector().getTypeName()); @@ -505,12 +505,12 @@ protected List getColumnHandles(List testColumns) testColumn.getBaseName(), columnIndex, baseHiveType, - baseHiveType.getType(TESTING_TYPE_MANAGER), + TESTING_TYPE_MANAGER.getType(baseHiveType.getTypeSignature()), Optional.of(new HiveColumnProjectionInfo( testColumn.getDereferenceIndices(), testColumn.getDereferenceNames(), partialHiveType, - partialHiveType.getType(TESTING_TYPE_MANAGER))), + TESTING_TYPE_MANAGER.getType(partialHiveType.getTypeSignature()))), testColumn.isPartitionKey() ? PARTITION_KEY : REGULAR, Optional.empty()); columns.add(hiveColumnHandle); @@ -536,7 +536,7 @@ public static FileSplit createTestFileTrino( List types = testColumns.stream() .map(TestColumn::getType) .map(HiveType::valueOf) - .map(type -> type.getType(TESTING_TYPE_MANAGER)) + .map(type -> TESTING_TYPE_MANAGER.getType(type.getTypeSignature())) .collect(toList()); PageBuilder pageBuilder = new PageBuilder(types); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java index 6ac05e3bba1a..d3924b7e3d8d 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java @@ -990,7 +990,7 @@ private void generateProjectedColumns(List childColumns, ImmutableLi ImmutableList.of(childColumn.getObjectInspector())); HiveType hiveType = (HiveType.valueOf(childColumn.getObjectInspector().getTypeName())); - Type trinoType = hiveType.getType(TESTING_TYPE_MANAGER); + Type trinoType = TESTING_TYPE_MANAGER.getType(hiveType.getTypeSignature()); List list = new ArrayList<>(); list.add(childColumn.getWriteValue()); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java index d67dfa332c1d..d48733a3237f 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java @@ -173,7 +173,7 @@ private static long writeTestFile(HiveConfig config, SortingFileWriterConfig sor List columnTypes = columns.stream() .map(LineItemColumn::getType) .map(TestHivePageSink::getHiveType) - .map(hiveType -> hiveType.getType(TESTING_TYPE_MANAGER)) + .map(hiveType -> TESTING_TYPE_MANAGER.getType(hiveType.getTypeSignature())) .collect(toList()); PageBuilder pageBuilder = new PageBuilder(columnTypes); @@ -320,7 +320,7 @@ private static List getColumnHandles() for (int i = 0; i < columns.size(); i++) { LineItemColumn column = columns.get(i); HiveType hiveType = getHiveType(column.getType()); - handles.add(createBaseColumn(column.getColumnName(), i, hiveType, hiveType.getType(TESTING_TYPE_MANAGER), REGULAR, Optional.empty())); + handles.add(createBaseColumn(column.getColumnName(), i, hiveType, TESTING_TYPE_MANAGER.getType(hiveType.getTypeSignature()), REGULAR, Optional.empty())); } return handles.build(); } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveReaderProjectionsUtil.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveReaderProjectionsUtil.java index 334badbabd58..9a9c4622078f 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveReaderProjectionsUtil.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveReaderProjectionsUtil.java @@ -72,7 +72,7 @@ public static HiveColumnHandle createProjectedColumnHandle(HiveColumnHandle colu List names = baseHiveType.getHiveDereferenceNames(indices); HiveType hiveType = baseHiveType.getHiveTypeForDereferences(indices).get(); - HiveColumnProjectionInfo columnProjection = new HiveColumnProjectionInfo(indices, names, hiveType, hiveType.getType(TESTING_TYPE_MANAGER)); + HiveColumnProjectionInfo columnProjection = new HiveColumnProjectionInfo(indices, names, hiveType, TESTING_TYPE_MANAGER.getType(hiveType.getTypeSignature())); return new HiveColumnHandle( column.getBaseColumnName(), diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestNodeLocalDynamicSplitPruning.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestNodeLocalDynamicSplitPruning.java index 4c689c33e862..b13bd0a4b9c9 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestNodeLocalDynamicSplitPruning.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestNodeLocalDynamicSplitPruning.java @@ -66,7 +66,7 @@ public class TestNodeLocalDynamicSplitPruning BUCKET_COLUMN.getName(), 0, BUCKET_COLUMN.getType(), - BUCKET_COLUMN.getType().getType(TESTING_TYPE_MANAGER), + TESTING_TYPE_MANAGER.getType(BUCKET_COLUMN.getType().getTypeSignature()), Optional.empty(), REGULAR, Optional.empty()); @@ -74,7 +74,7 @@ public class TestNodeLocalDynamicSplitPruning PARTITION_COLUMN.getName(), 0, PARTITION_COLUMN.getType(), - PARTITION_COLUMN.getType().getType(TESTING_TYPE_MANAGER), + TESTING_TYPE_MANAGER.getType(PARTITION_COLUMN.getType().getTypeSignature()), Optional.empty(), PARTITION_KEY, Optional.empty()); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOrcPageSourceMemoryTracking.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOrcPageSourceMemoryTracking.java index f52877646e01..d445b60526b8 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOrcPageSourceMemoryTracking.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOrcPageSourceMemoryTracking.java @@ -532,7 +532,7 @@ public TestPreparer(String tempFilePath, List testColumns, int numRo ObjectInspector inspector = testColumn.getObjectInspector(); HiveType hiveType = HiveType.valueOf(inspector.getTypeName()); - Type type = hiveType.getType(TESTING_TYPE_MANAGER); + Type type = TESTING_TYPE_MANAGER.getType(hiveType.getTypeSignature()); columnsBuilder.add(createBaseColumn(testColumn.getName(), columnIndex, hiveType, type, testColumn.isPartitionKey() ? PARTITION_KEY : REGULAR, Optional.empty())); typesBuilder.add(type); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveBucketing.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveBucketing.java index 086a939315c8..d10b715a0ea4 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveBucketing.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveBucketing.java @@ -232,7 +232,7 @@ private static void assertBucketsEqual(List hiveTypeStrings, List trinoTypes = hiveTypes.stream() - .map(type -> type.getType(TESTING_TYPE_MANAGER)) + .map(type -> TESTING_TYPE_MANAGER.getType(type.getTypeSignature())) .collect(toImmutableList()); ImmutableList.Builder> values = ImmutableList.builder(); @@ -306,7 +306,7 @@ private static int computeTrino(BucketingVersion bucketingVersion, List Object[] nativeContainerValues = new Object[hiveValues.size()]; for (int i = 0; i < hiveTypeStrings.size(); i++) { Object hiveValue = hiveValues.get(i); - Type type = hiveTypes.get(i).getType(TESTING_TYPE_MANAGER); + Type type = TESTING_TYPE_MANAGER.getType(hiveTypes.get(i).getTypeSignature()); BlockBuilder blockBuilder = type.createBlockBuilder(null, 3); // prepend 2 nulls to make sure position is respected when HiveBucketing function From 6a5aa23f93678e4a7ebee95702da2ebd666c2d8d Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Sat, 4 Nov 2023 16:01:27 -0700 Subject: [PATCH 089/587] Cleanup AbstractTestHiveFileFormats and subclasses --- .../hive/AbstractTestHiveFileFormats.java | 27 ++++------ .../plugin/hive/TestHiveFileFormats.java | 49 ++++++++----------- .../plugin/hive/orc/TestOrcPredicates.java | 34 ++++++------- 3 files changed, 47 insertions(+), 63 deletions(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileFormats.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileFormats.java index 1e5134b26807..0a5313df7b16 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileFormats.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileFormats.java @@ -471,15 +471,13 @@ private static Map asMap(K[] keys, V[] values) return map; } - protected List getColumnHandles(List testColumns) + protected static List getColumnHandles(List testColumns) { List columns = new ArrayList<>(); Map hiveColumnIndexes = new HashMap<>(); int nextHiveColumnIndex = 0; - for (int i = 0; i < testColumns.size(); i++) { - TestColumn testColumn = testColumns.get(i); - + for (TestColumn testColumn : testColumns) { int columnIndex; if (testColumn.isPartitionKey()) { columnIndex = -1; @@ -494,13 +492,13 @@ protected List getColumnHandles(List testColumns) } } - if (testColumn.getDereferenceNames().size() == 0) { + if (testColumn.getDereferenceNames().isEmpty()) { HiveType hiveType = HiveType.valueOf(testColumn.getObjectInspector().getTypeName()); columns.add(createBaseColumn(testColumn.getName(), columnIndex, hiveType, TESTING_TYPE_MANAGER.getType(hiveType.getTypeSignature()), testColumn.isPartitionKey() ? PARTITION_KEY : REGULAR, Optional.empty())); } else { HiveType baseHiveType = HiveType.valueOf(testColumn.getBaseObjectInspector().getTypeName()); - HiveType partialHiveType = baseHiveType.getHiveTypeForDereferences(testColumn.getDereferenceIndices()).get(); + HiveType partialHiveType = baseHiveType.getHiveTypeForDereferences(testColumn.getDereferenceIndices()).orElseThrow(); HiveColumnHandle hiveColumnHandle = new HiveColumnHandle( testColumn.getBaseName(), columnIndex, @@ -720,10 +718,10 @@ public static Object getFieldFromCursor(RecordCursor cursor, Type type, int fiel throw new RuntimeException("unknown type"); } - protected void checkPageSource(ConnectorPageSource pageSource, List testColumns, List types, int rowCount) + protected static void checkPageSource(ConnectorPageSource pageSource, List testColumns, List types, int rowCount) throws IOException { - try { + try (pageSource) { MaterializedResult result = materializeSourceDataStream(SESSION, pageSource, types); assertEquals(result.getMaterializedRows().size(), rowCount); for (MaterializedRow row : result) { @@ -757,17 +755,13 @@ else if (testColumn.getObjectInspector().getTypeName().equals("int") || assertEquals(actualValue, expectedValue); } else if (testColumn.getObjectInspector().getTypeName().equals("timestamp")) { - SqlTimestamp expectedTimestamp = sqlTimestampOf(floorDiv((Long) expectedValue, MICROSECONDS_PER_MILLISECOND)); + SqlTimestamp expectedTimestamp = sqlTimestampOf(3, floorDiv((Long) expectedValue, MICROSECONDS_PER_MILLISECOND)); assertEquals(actualValue, expectedTimestamp, "Wrong value for column " + testColumn.getName()); } else if (testColumn.getObjectInspector().getTypeName().startsWith("char")) { assertEquals(actualValue, padSpaces((String) expectedValue, (CharType) type), "Wrong value for column " + testColumn.getName()); } else if (testColumn.getObjectInspector().getCategory() == Category.PRIMITIVE) { - if (expectedValue instanceof Slice) { - expectedValue = ((Slice) expectedValue).toStringUtf8(); - } - if (actualValue instanceof Slice) { actualValue = ((Slice) actualValue).toStringUtf8(); } @@ -789,9 +783,6 @@ else if (testColumn.getObjectInspector().getCategory() == Category.PRIMITIVE) { } } } - finally { - pageSource.close(); - } } public static final class TestColumn @@ -836,7 +827,7 @@ public TestColumn( this.writeValue = writeValue; this.expectedValue = expectedValue; this.partitionKey = partitionKey; - checkArgument(dereferenceNames.size() == 0 || partitionKey == false, "partial column cannot be a partition key"); + checkArgument(dereferenceNames.isEmpty() || !partitionKey, "partial column cannot be a partition key"); } public String getName() @@ -894,7 +885,7 @@ public String toString() { StringBuilder sb = new StringBuilder("TestColumn{"); sb.append("baseName='").append(baseName).append("'"); - sb.append("dereferenceNames=").append("[").append(dereferenceNames.stream().collect(Collectors.joining(","))).append("]"); + sb.append("dereferenceNames=").append("[").append(String.join(",", dereferenceNames)).append("]"); sb.append("name=").append(name); sb.append(", objectInspector=").append(objectInspector); sb.append(", writeValue=").append(writeValue); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java index d3924b7e3d8d..62a23e0a8fce 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java @@ -49,7 +49,6 @@ import io.trino.spi.type.Type; import io.trino.testing.TestingConnectorSession; import org.apache.hadoop.hive.common.type.HiveVarchar; -import org.apache.hadoop.hive.ql.io.SymlinkTextInputFormat; import org.apache.hadoop.hive.serde2.objectinspector.ListObjectInspector; import org.apache.hadoop.hive.serde2.objectinspector.MapObjectInspector; import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector; @@ -75,7 +74,6 @@ import java.util.Properties; import java.util.Set; import java.util.TimeZone; -import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableList.toImmutableList; @@ -97,6 +95,7 @@ import static io.trino.plugin.hive.HiveTestUtils.SESSION; import static io.trino.plugin.hive.HiveTestUtils.getHiveSession; import static io.trino.plugin.hive.HiveTestUtils.getTypes; +import static io.trino.plugin.hive.HiveTimestampPrecision.DEFAULT_PRECISION; import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; import static io.trino.plugin.hive.util.SerdeConstants.SERIALIZATION_LIB; import static io.trino.testing.StructuralTestUtil.rowBlockOf; @@ -188,7 +187,7 @@ public void testCsvFile(int rowCount, long fileSizePadding) throws Exception { List testColumns = TEST_COLUMNS.stream() - // CSV table only support Hive string columns. Notice that CSV does not allow to store null, it uses an empty string instead. + // CSV tables only support Hive string columns. Notice that CSV does not allow to store null, it uses an empty string instead. .filter(column -> column.isPartitionKey() || ("string".equals(column.getType()) && !column.getName().contains("_null_"))) .collect(toImmutableList()); @@ -295,7 +294,7 @@ public void testRcTextOptimizedWriter(int rowCount) public void testRcBinaryPageSource(int rowCount) throws Exception { - // RCBinary does not support complex type as key of a map and interprets empty VARCHAR as nulls + // RCBinary does not support complex types as the key of a map and interprets empty VARCHAR as null // Hive binary writers are broken for timestamps List testColumns = TEST_COLUMNS.stream() .filter(testColumn -> !testColumn.getName().equals("t_empty_varchar")) @@ -360,7 +359,7 @@ public void testOrcOptimizedWriter(int rowCount, long fileSizePadding) .setPropertyMetadata(hiveSessionProperties.getSessionProperties()) .build(); - // A Trino page cannot contain a map with null keys, so a page based writer cannot write null keys + // A Trino page cannot contain a map with null keys, so null keys cannot be written List testColumns = TEST_COLUMNS.stream() .filter(TestHiveFileFormats::withoutNullMapKeyTests) .collect(toList()); @@ -423,7 +422,7 @@ public void testAvro(int rowCount, long fileSizePadding) } @Test(dataProvider = "rowCount") - public void testAvroFileInSymlinkTable(int rowCount) + public static void testAvroFileInSymlinkTable(int rowCount) throws Exception { File file = File.createTempFile("trino_test", AVRO.name()); @@ -431,9 +430,6 @@ public void testAvroFileInSymlinkTable(int rowCount) file.delete(); try { FileSplit split = createTestFileHive(file.getAbsolutePath(), AVRO, HiveCompressionCodec.NONE, getTestColumnsSupportedByAvro(), rowCount); - Properties splitProperties = new Properties(); - splitProperties.setProperty(FILE_INPUT_FORMAT, SymlinkTextInputFormat.class.getName()); - splitProperties.setProperty(SERIALIZATION_LIB, AVRO.getSerde()); testPageSourceFactory(new AvroPageSourceFactory(FILE_SYSTEM_FACTORY), split, AVRO, getTestColumnsSupportedByAvro(), SESSION, file.length(), rowCount); } finally { @@ -500,7 +496,7 @@ public void testParquetPageSourceSchemaEvolution(int rowCount) { List writeColumns = getTestColumnsSupportedByParquet(); - // test index-based access + // test the index-based access List readColumns = writeColumns.stream() .map(column -> new TestColumn( column.getName() + "_new", @@ -516,7 +512,7 @@ public void testParquetPageSourceSchemaEvolution(int rowCount) .withRowsCount(rowCount) .isReadableByPageSource(PARQUET_PAGE_SOURCE_FACTORY); - // test name-based access + // test the name-based access readColumns = Lists.reverse(writeColumns); assertThatFileFormat(PARQUET) .withWriteColumns(writeColumns) @@ -758,7 +754,7 @@ public void testRCTextProjectedColumnsPageSource(int rowCount) public void testRCBinaryProjectedColumns(int rowCount) throws Exception { - // RCBinary does not support complex type as key of a map and interprets empty VARCHAR as nulls + // RCBinary does not support complex types as the key of a map and interprets empty VARCHAR as null List supportedColumns = TEST_COLUMNS.stream() .filter(testColumn -> { String name = testColumn.getName(); @@ -791,7 +787,7 @@ public void testRCBinaryProjectedColumns(int rowCount) public void testRCBinaryProjectedColumnsPageSource(int rowCount) throws Exception { - // RCBinary does not support complex type as key of a map and interprets empty VARCHAR as nulls + // RCBinary does not support complex types as the key of a map and interprets empty VARCHAR as null List supportedColumns = TEST_COLUMNS.stream() .filter(testColumn -> !testColumn.getName().equals("t_empty_varchar")) .collect(toList()); @@ -819,7 +815,6 @@ public void testRCBinaryProjectedColumnsPageSource(int rowCount) @Test public void testFailForLongVarcharPartitionColumn() - throws Exception { TestColumn partitionColumn = new TestColumn("partition_column", getPrimitiveJavaObjectInspector(new VarcharTypeInfo(3)), "test", utf8Slice("tes"), true); TestColumn varcharColumn = new TestColumn("varchar_column", getPrimitiveJavaObjectInspector(new VarcharTypeInfo(3)), new HiveVarchar("tes", 3), utf8Slice("tes")); @@ -847,7 +842,7 @@ public void testFailForLongVarcharPartitionColumn() .isFailingForPageSource(PARQUET_PAGE_SOURCE_FACTORY, expectedErrorCode, expectedMessage); } - private void testPageSourceFactory( + private static void testPageSourceFactory( HivePageSourceFactory sourceFactory, FileSplit split, HiveStorageFormat storageFormat, @@ -875,8 +870,8 @@ private void testPageSourceFactory( } } - splitProperties.setProperty("columns", splitPropertiesColumnNames.build().stream().collect(Collectors.joining(","))); - splitProperties.setProperty("columns.types", splitPropertiesColumnTypes.build().stream().collect(Collectors.joining(","))); + splitProperties.setProperty("columns", String.join(",", splitPropertiesColumnNames.build())); + splitProperties.setProperty("columns.types", String.join(",", splitPropertiesColumnTypes.build())); List partitionKeys = testReadColumns.stream() .filter(TestColumn::isPartitionKey) @@ -923,7 +918,7 @@ private void testPageSourceFactory( checkPageSource(pageSource.get(), testReadColumns, getTypes(columnHandles), rowCount); } - public static boolean hasType(ObjectInspector objectInspector, PrimitiveCategory... types) + private static boolean hasType(ObjectInspector objectInspector, PrimitiveCategory... types) { if (objectInspector instanceof PrimitiveObjectInspector primitiveInspector) { PrimitiveCategory primitiveCategory = primitiveInspector.getPrimitiveCategory(); @@ -968,7 +963,7 @@ private static boolean withoutTimestamps(TestColumn testColumn) !name.equals("t_array_timestamp"); } - private FileFormatAssertion assertThatFileFormat(HiveStorageFormat hiveStorageFormat) + private static FileFormatAssertion assertThatFileFormat(HiveStorageFormat hiveStorageFormat) { return new FileFormatAssertion(hiveStorageFormat.name()) .withStorageFormat(hiveStorageFormat); @@ -980,17 +975,17 @@ private static HiveConfig createParquetHiveConfig(boolean useParquetColumnNames) .setUseParquetColumnNames(useParquetColumnNames); } - private void generateProjectedColumns(List childColumns, ImmutableList.Builder testFullColumnsBuilder, ImmutableList.Builder testDereferencedColumnsBuilder) + private static void generateProjectedColumns(List childColumns, ImmutableList.Builder testFullColumnsBuilder, ImmutableList.Builder testDereferencedColumnsBuilder) { for (int i = 0; i < childColumns.size(); i++) { TestColumn childColumn = childColumns.get(i); - checkState(childColumn.getDereferenceIndices().size() == 0); + checkState(childColumn.getDereferenceIndices().isEmpty()); ObjectInspector newObjectInspector = getStandardStructObjectInspector( ImmutableList.of("field0"), ImmutableList.of(childColumn.getObjectInspector())); HiveType hiveType = (HiveType.valueOf(childColumn.getObjectInspector().getTypeName())); - Type trinoType = TESTING_TYPE_MANAGER.getType(hiveType.getTypeSignature()); + Type trinoType = TESTING_TYPE_MANAGER.getType(hiveType.getTypeSignature(DEFAULT_PRECISION)); List list = new ArrayList<>(); list.add(childColumn.getWriteValue()); @@ -1011,21 +1006,21 @@ private void generateProjectedColumns(List childColumns, ImmutableLi } } - private List getRegularColumns(List columns) + private static List getRegularColumns(List columns) { return columns.stream() .filter(column -> !column.isPartitionKey()) .collect(toImmutableList()); } - private List getPartitionColumns(List columns) + private static List getPartitionColumns(List columns) { return columns.stream() .filter(TestColumn::isPartitionKey) .collect(toImmutableList()); } - private class FileFormatAssertion + private static class FileFormatAssertion { private final String formatName; private HiveStorageFormat storageFormat; @@ -1111,11 +1106,9 @@ public FileFormatAssertion isReadableByPageSource(HivePageSourceFactory pageSour return this; } - public FileFormatAssertion isFailingForPageSource(HivePageSourceFactory pageSourceFactory, HiveErrorCode expectedErrorCode, String expectedMessage) - throws Exception + public void isFailingForPageSource(HivePageSourceFactory pageSourceFactory, HiveErrorCode expectedErrorCode, String expectedMessage) { assertFailure(Optional.of(pageSourceFactory), expectedErrorCode, expectedMessage); - return this; } private void assertRead(Optional pageSourceFactory) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPredicates.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPredicates.java index b39d560eb1e3..b331c8b12caf 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPredicates.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPredicates.java @@ -37,6 +37,7 @@ import org.junit.jupiter.api.Test; import java.io.File; +import java.io.IOException; import java.time.Instant; import java.util.HashSet; import java.util.List; @@ -44,7 +45,6 @@ import java.util.OptionalInt; import java.util.Properties; import java.util.Set; -import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableList.toImmutableList; @@ -67,7 +67,7 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; -public class TestOrcPredicates +class TestOrcPredicates extends AbstractTestHiveFileFormats { private static final int NUM_ROWS = 50000; @@ -83,14 +83,14 @@ public class TestOrcPredicates private static final TestColumn columnPrimitiveBigInt = new TestColumn("column_primitive_bigint", javaLongObjectInspector, 6L, 6L); @Test - public void testOrcPredicates() + void testOrcPredicates() throws Exception { testOrcPredicates(getHiveSession(new HiveConfig(), new OrcReaderConfig().setUseColumnNames(true))); testOrcPredicates(getHiveSession(new HiveConfig(), new OrcReaderConfig())); } - private void testOrcPredicates(ConnectorSession session) + private static void testOrcPredicates(ConnectorSession session) throws Exception { List columnsToWrite = ImmutableList.of(columnPrimitiveInteger, columnStruct, columnPrimitiveBigInt); @@ -137,27 +137,27 @@ private void testOrcPredicates(ConnectorSession session) } } - private void assertFilteredRows( + private static void assertFilteredRows( TupleDomain effectivePredicate, List columnsToRead, ConnectorSession session, FileSplit split, int expectedRows) + throws IOException { - ConnectorPageSource pageSource = createPageSource(effectivePredicate, columnsToRead, session, split); - - int filteredRows = 0; - while (!pageSource.isFinished()) { - Page page = pageSource.getNextPage(); - if (page != null) { - filteredRows += page.getPositionCount(); + try (ConnectorPageSource pageSource = createPageSource(effectivePredicate, columnsToRead, session, split)) { + int filteredRows = 0; + while (!pageSource.isFinished()) { + Page page = pageSource.getNextPage(); + if (page != null) { + filteredRows += page.getPositionCount(); + } } + assertEquals(filteredRows, expectedRows); } - - assertEquals(filteredRows, expectedRows); } - private ConnectorPageSource createPageSource( + private static ConnectorPageSource createPageSource( TupleDomain effectivePredicate, List columnsToRead, ConnectorSession session, @@ -182,8 +182,8 @@ private ConnectorPageSource createPageSource( } } - splitProperties.setProperty("columns", splitPropertiesColumnNames.build().stream().collect(Collectors.joining(","))); - splitProperties.setProperty("columns.types", splitPropertiesColumnTypes.build().stream().collect(Collectors.joining(","))); + splitProperties.setProperty("columns", String.join(",", splitPropertiesColumnNames.build())); + splitProperties.setProperty("columns.types", String.join(",", splitPropertiesColumnTypes.build())); List partitionKeys = columnsToRead.stream() .filter(TestColumn::isPartitionKey) From 7efdaaa31b5f5f6ae08883427bdb1ca50531237d Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Sat, 4 Nov 2023 17:18:45 -0700 Subject: [PATCH 090/587] Simplify TestHiveFileFormats --- .../plugin/hive/TestHiveFileFormats.java | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java index 62a23e0a8fce..12b91218cacb 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java @@ -1102,16 +1102,18 @@ public FileFormatAssertion withFileSizePadding(long fileSizePadding) public FileFormatAssertion isReadableByPageSource(HivePageSourceFactory pageSourceFactory) throws Exception { - assertRead(Optional.of(pageSourceFactory)); + assertRead(pageSourceFactory); return this; } public void isFailingForPageSource(HivePageSourceFactory pageSourceFactory, HiveErrorCode expectedErrorCode, String expectedMessage) { - assertFailure(Optional.of(pageSourceFactory), expectedErrorCode, expectedMessage); + assertTrinoExceptionThrownBy(() -> assertRead(pageSourceFactory)) + .hasErrorCode(expectedErrorCode) + .hasMessage(expectedMessage); } - private void assertRead(Optional pageSourceFactory) + private void assertRead(HivePageSourceFactory pageSourceFactory) throws Exception { assertNotNull(storageFormat, "storageFormat must be specified"); @@ -1143,9 +1145,7 @@ private void assertRead(Optional pageSourceFactory) } long fileSize = split.getLength() + fileSizePadding; - if (pageSourceFactory.isPresent()) { - testPageSourceFactory(pageSourceFactory.get(), split, storageFormat, readColumns, session, fileSize, rowsCount); - } + testPageSourceFactory(pageSourceFactory, split, storageFormat, readColumns, session, fileSize, rowsCount); } finally { //noinspection ResultOfMethodCallIgnored @@ -1153,15 +1153,5 @@ private void assertRead(Optional pageSourceFactory) } } } - - private void assertFailure( - Optional pageSourceFactory, - HiveErrorCode expectedErrorCode, - String expectedMessage) - { - assertTrinoExceptionThrownBy(() -> assertRead(pageSourceFactory)) - .hasErrorCode(expectedErrorCode) - .hasMessage(expectedMessage); - } } } From 1fc745478f8322b20721de8855db1e49bcbbf3a7 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Sat, 4 Nov 2023 17:18:59 -0700 Subject: [PATCH 091/587] Move getFieldFromCursor utility to ParquetTester --- .../hive/AbstractTestHiveFileFormats.java | 54 ------------------- .../plugin/hive/parquet/ParquetTester.java | 51 +++++++++++++++++- 2 files changed, 50 insertions(+), 55 deletions(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileFormats.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileFormats.java index 0a5313df7b16..8438d20bc643 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileFormats.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileFormats.java @@ -24,12 +24,10 @@ import io.trino.spi.block.BlockBuilder; import io.trino.spi.connector.ConnectorPageSource; import io.trino.spi.connector.ConnectorSession; -import io.trino.spi.connector.RecordCursor; import io.trino.spi.type.ArrayType; import io.trino.spi.type.CharType; import io.trino.spi.type.DateType; import io.trino.spi.type.DecimalType; -import io.trino.spi.type.Int128; import io.trino.spi.type.RowType; import io.trino.spi.type.SqlDate; import io.trino.spi.type.SqlDecimal; @@ -37,7 +35,6 @@ import io.trino.spi.type.SqlVarbinary; import io.trino.spi.type.TimestampType; import io.trino.spi.type.Type; -import io.trino.spi.type.VarcharType; import io.trino.testing.MaterializedResult; import io.trino.testing.MaterializedRow; import org.apache.hadoop.conf.Configuration; @@ -68,7 +65,6 @@ import java.io.File; import java.io.IOException; import java.math.BigDecimal; -import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -91,7 +87,6 @@ import static io.trino.plugin.hive.HiveTestUtils.mapType; import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; import static io.trino.plugin.hive.util.CompressionConfigUtil.configureCompression; -import static io.trino.plugin.hive.util.HiveUtil.isStructuralType; import static io.trino.plugin.hive.util.SerDeUtils.serializeObject; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.BooleanType.BOOLEAN; @@ -102,7 +97,6 @@ import static io.trino.spi.type.RealType.REAL; import static io.trino.spi.type.SmallintType.SMALLINT; import static io.trino.spi.type.TinyintType.TINYINT; -import static io.trino.spi.type.VarbinaryType.VARBINARY; import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; import static io.trino.spi.type.VarcharType.createVarcharType; import static io.trino.testing.DateTimeTestingUtils.sqlTimestampOf; @@ -114,7 +108,6 @@ import static io.trino.testing.StructuralTestUtil.sqlMapOf; import static io.trino.type.DateTimes.MICROSECONDS_PER_MILLISECOND; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; -import static java.lang.Float.intBitsToFloat; import static java.lang.Math.floorDiv; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.fill; @@ -671,53 +664,6 @@ private static T newInstance(String className, Class superType) return HiveStorageFormat.class.getClassLoader().loadClass(className).asSubclass(superType).getConstructor().newInstance(); } - public static Object getFieldFromCursor(RecordCursor cursor, Type type, int field) - { - if (cursor.isNull(field)) { - return null; - } - if (BOOLEAN.equals(type)) { - return cursor.getBoolean(field); - } - if (TINYINT.equals(type)) { - return cursor.getLong(field); - } - if (SMALLINT.equals(type)) { - return cursor.getLong(field); - } - if (INTEGER.equals(type)) { - return (int) cursor.getLong(field); - } - if (BIGINT.equals(type)) { - return cursor.getLong(field); - } - if (REAL.equals(type)) { - return intBitsToFloat((int) cursor.getLong(field)); - } - if (DOUBLE.equals(type)) { - return cursor.getDouble(field); - } - if (type instanceof VarcharType || type instanceof CharType || VARBINARY.equals(type)) { - return cursor.getSlice(field); - } - if (DateType.DATE.equals(type)) { - return cursor.getLong(field); - } - if (TimestampType.TIMESTAMP_MILLIS.equals(type)) { - return cursor.getLong(field); - } - if (isStructuralType(type)) { - return cursor.getObject(field); - } - if (type instanceof DecimalType decimalType) { - if (decimalType.isShort()) { - return BigInteger.valueOf(cursor.getLong(field)); - } - return ((Int128) cursor.getObject(field)).toBigInteger(); - } - throw new RuntimeException("unknown type"); - } - protected static void checkPageSource(ConnectorPageSource pageSource, List testColumns, List types, int rowCount) throws IOException { diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetTester.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetTester.java index ad4faaea3f89..5f3ed87812fe 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetTester.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetTester.java @@ -52,6 +52,7 @@ import io.trino.spi.connector.RecordPageSource; import io.trino.spi.type.ArrayType; import io.trino.spi.type.CharType; +import io.trino.spi.type.DateType; import io.trino.spi.type.DecimalType; import io.trino.spi.type.Decimals; import io.trino.spi.type.Int128; @@ -62,6 +63,7 @@ import io.trino.spi.type.SqlDecimal; import io.trino.spi.type.SqlTimestamp; import io.trino.spi.type.SqlVarbinary; +import io.trino.spi.type.TimestampType; import io.trino.spi.type.Type; import io.trino.spi.type.VarcharType; import io.trino.testing.TestingConnectorSession; @@ -109,7 +111,6 @@ import static io.trino.parquet.ParquetWriteValidation.ParquetWriteValidationBuilder; import static io.trino.parquet.writer.ParquetSchemaConverter.HIVE_PARQUET_USE_INT96_TIMESTAMP_ENCODING; import static io.trino.parquet.writer.ParquetSchemaConverter.HIVE_PARQUET_USE_LEGACY_DECIMAL_ENCODING; -import static io.trino.plugin.hive.AbstractTestHiveFileFormats.getFieldFromCursor; import static io.trino.plugin.hive.HiveErrorCode.HIVE_WRITE_VALIDATION_FAILED; import static io.trino.plugin.hive.HiveSessionProperties.getParquetMaxReadBlockSize; import static io.trino.plugin.hive.HiveTestUtils.getHiveSession; @@ -129,6 +130,7 @@ import static io.trino.spi.type.TinyintType.TINYINT; import static io.trino.spi.type.VarbinaryType.VARBINARY; import static io.trino.spi.type.Varchars.truncateToLength; +import static java.lang.Float.intBitsToFloat; import static java.lang.Math.toIntExact; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.stream; @@ -567,6 +569,53 @@ private static Object getActualCursorValue(RecordCursor cursor, Type type, int f return fieldFromCursor; } + private static Object getFieldFromCursor(RecordCursor cursor, Type type, int field) + { + if (cursor.isNull(field)) { + return null; + } + if (BOOLEAN.equals(type)) { + return cursor.getBoolean(field); + } + if (TINYINT.equals(type)) { + return cursor.getLong(field); + } + if (SMALLINT.equals(type)) { + return cursor.getLong(field); + } + if (INTEGER.equals(type)) { + return (int) cursor.getLong(field); + } + if (BIGINT.equals(type)) { + return cursor.getLong(field); + } + if (REAL.equals(type)) { + return intBitsToFloat((int) cursor.getLong(field)); + } + if (DOUBLE.equals(type)) { + return cursor.getDouble(field); + } + if (type instanceof VarcharType || type instanceof CharType || VARBINARY.equals(type)) { + return cursor.getSlice(field); + } + if (DateType.DATE.equals(type)) { + return cursor.getLong(field); + } + if (TimestampType.TIMESTAMP_MILLIS.equals(type)) { + return cursor.getLong(field); + } + if (isStructuralType(type)) { + return cursor.getObject(field); + } + if (type instanceof DecimalType decimalType) { + if (decimalType.isShort()) { + return BigInteger.valueOf(cursor.getLong(field)); + } + return ((Int128) cursor.getObject(field)).toBigInteger(); + } + throw new RuntimeException("unknown type"); + } + private static Map toMapValue(SqlMap sqlMap, Type keyType, Type valueType) { int rawOffset = sqlMap.getRawOffset(); From e5f03e75e43fa18bd75a8f5c4490aa38b9005c2d Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Sat, 4 Nov 2023 19:13:47 -0700 Subject: [PATCH 092/587] Move checkPageSource to TestHiveFileFormats --- .../hive/AbstractTestHiveFileFormats.java | 90 ------------------- .../plugin/hive/TestHiveFileFormats.java | 89 +++++++++++++++++- 2 files changed, 88 insertions(+), 91 deletions(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileFormats.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileFormats.java index 8438d20bc643..7524db00ff8e 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileFormats.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileFormats.java @@ -21,22 +21,13 @@ import io.trino.plugin.hive.metastore.StorageFormat; import io.trino.spi.Page; import io.trino.spi.PageBuilder; -import io.trino.spi.block.BlockBuilder; -import io.trino.spi.connector.ConnectorPageSource; import io.trino.spi.connector.ConnectorSession; import io.trino.spi.type.ArrayType; -import io.trino.spi.type.CharType; import io.trino.spi.type.DateType; import io.trino.spi.type.DecimalType; import io.trino.spi.type.RowType; -import io.trino.spi.type.SqlDate; -import io.trino.spi.type.SqlDecimal; -import io.trino.spi.type.SqlTimestamp; -import io.trino.spi.type.SqlVarbinary; import io.trino.spi.type.TimestampType; import io.trino.spi.type.Type; -import io.trino.testing.MaterializedResult; -import io.trino.testing.MaterializedRow; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.hive.common.type.Date; @@ -48,7 +39,6 @@ import org.apache.hadoop.hive.ql.io.HiveOutputFormat; import org.apache.hadoop.hive.serde2.Serializer; import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector; -import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector.Category; import org.apache.hadoop.hive.serde2.objectinspector.SettableStructObjectInspector; import org.apache.hadoop.hive.serde2.objectinspector.StructField; import org.apache.hadoop.hive.serde2.objectinspector.primitive.JavaHiveCharObjectInspector; @@ -59,11 +49,9 @@ import org.apache.hadoop.mapred.FileSplit; import org.apache.hadoop.mapred.JobConf; import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; import org.joda.time.format.DateTimeFormat; import java.io.File; -import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; @@ -83,7 +71,6 @@ import static io.trino.plugin.hive.HiveColumnHandle.createBaseColumn; import static io.trino.plugin.hive.HiveColumnProjectionInfo.generatePartialName; import static io.trino.plugin.hive.HivePartitionKey.HIVE_DEFAULT_DYNAMIC_PARTITION; -import static io.trino.plugin.hive.HiveTestUtils.SESSION; import static io.trino.plugin.hive.HiveTestUtils.mapType; import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; import static io.trino.plugin.hive.util.CompressionConfigUtil.configureCompression; @@ -91,7 +78,6 @@ import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.BooleanType.BOOLEAN; import static io.trino.spi.type.CharType.createCharType; -import static io.trino.spi.type.Chars.padSpaces; import static io.trino.spi.type.DoubleType.DOUBLE; import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.spi.type.RealType.REAL; @@ -99,8 +85,6 @@ import static io.trino.spi.type.TinyintType.TINYINT; import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; import static io.trino.spi.type.VarcharType.createVarcharType; -import static io.trino.testing.DateTimeTestingUtils.sqlTimestampOf; -import static io.trino.testing.MaterializedResult.materializeSourceDataStream; import static io.trino.testing.StructuralTestUtil.arrayBlockOf; import static io.trino.testing.StructuralTestUtil.decimalArrayBlockOf; import static io.trino.testing.StructuralTestUtil.decimalSqlMapOf; @@ -108,8 +92,6 @@ import static io.trino.testing.StructuralTestUtil.sqlMapOf; import static io.trino.type.DateTimes.MICROSECONDS_PER_MILLISECOND; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; -import static java.lang.Math.floorDiv; -import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.fill; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; @@ -130,14 +112,9 @@ import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaTimestampObjectInspector; import static org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory.getCharTypeInfo; import static org.joda.time.DateTimeZone.UTC; -import static org.testng.Assert.assertEquals; public abstract class AbstractTestHiveFileFormats { - protected static final DateTimeZone HIVE_STORAGE_TIME_ZONE = DateTimeZone.forID("America/Bahia_Banderas"); - - private static final double EPSILON = 0.001; - private static final long DATE_MILLIS_UTC = new DateTime(2011, 5, 6, 0, 0, UTC).getMillis(); private static final long DATE_DAYS = TimeUnit.MILLISECONDS.toDays(DATE_MILLIS_UTC); private static final String DATE_STRING = DateTimeFormat.forPattern("yyyy-MM-dd").withZoneUTC().print(DATE_MILLIS_UTC); @@ -664,73 +641,6 @@ private static T newInstance(String className, Class superType) return HiveStorageFormat.class.getClassLoader().loadClass(className).asSubclass(superType).getConstructor().newInstance(); } - protected static void checkPageSource(ConnectorPageSource pageSource, List testColumns, List types, int rowCount) - throws IOException - { - try (pageSource) { - MaterializedResult result = materializeSourceDataStream(SESSION, pageSource, types); - assertEquals(result.getMaterializedRows().size(), rowCount); - for (MaterializedRow row : result) { - for (int i = 0, testColumnsSize = testColumns.size(); i < testColumnsSize; i++) { - TestColumn testColumn = testColumns.get(i); - Type type = types.get(i); - - Object actualValue = row.getField(i); - Object expectedValue = testColumn.getExpectedValue(); - - if (expectedValue instanceof Slice) { - expectedValue = ((Slice) expectedValue).toStringUtf8(); - } - - if (actualValue == null || expectedValue == null) { - assertEquals(actualValue, expectedValue, "Wrong value for column " + testColumn.getName()); - } - else if (testColumn.getObjectInspector().getTypeName().equals("float")) { - assertEquals((float) actualValue, (float) expectedValue, EPSILON, "Wrong value for column " + testColumn.getName()); - } - else if (testColumn.getObjectInspector().getTypeName().equals("double")) { - assertEquals((double) actualValue, (double) expectedValue, EPSILON, "Wrong value for column " + testColumn.getName()); - } - else if (testColumn.getObjectInspector().getTypeName().equals("date")) { - SqlDate expectedDate = new SqlDate(((Long) expectedValue).intValue()); - assertEquals(actualValue, expectedDate, "Wrong value for column " + testColumn.getName()); - } - else if (testColumn.getObjectInspector().getTypeName().equals("int") || - testColumn.getObjectInspector().getTypeName().equals("smallint") || - testColumn.getObjectInspector().getTypeName().equals("tinyint")) { - assertEquals(actualValue, expectedValue); - } - else if (testColumn.getObjectInspector().getTypeName().equals("timestamp")) { - SqlTimestamp expectedTimestamp = sqlTimestampOf(3, floorDiv((Long) expectedValue, MICROSECONDS_PER_MILLISECOND)); - assertEquals(actualValue, expectedTimestamp, "Wrong value for column " + testColumn.getName()); - } - else if (testColumn.getObjectInspector().getTypeName().startsWith("char")) { - assertEquals(actualValue, padSpaces((String) expectedValue, (CharType) type), "Wrong value for column " + testColumn.getName()); - } - else if (testColumn.getObjectInspector().getCategory() == Category.PRIMITIVE) { - if (actualValue instanceof Slice) { - actualValue = ((Slice) actualValue).toStringUtf8(); - } - if (actualValue instanceof SqlVarbinary) { - actualValue = new String(((SqlVarbinary) actualValue).getBytes(), UTF_8); - } - - if (actualValue instanceof SqlDecimal) { - actualValue = new BigDecimal(actualValue.toString()); - } - assertEquals(actualValue, expectedValue, "Wrong value for column " + testColumn.getName()); - } - else { - BlockBuilder builder = type.createBlockBuilder(null, 1); - type.writeObject(builder, expectedValue); - expectedValue = type.getObjectValue(SESSION, builder.build(), 0); - assertEquals(actualValue, expectedValue, "Wrong value for column " + testColumn.getName()); - } - } - } - } - } - public static final class TestColumn { private final String baseName; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java index 12b91218cacb..93c48b796cb0 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java @@ -16,6 +16,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; +import io.airlift.slice.Slice; import io.trino.filesystem.Location; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.filesystem.hdfs.HdfsFileSystemFactory; @@ -43,10 +44,18 @@ import io.trino.plugin.hive.parquet.ParquetReaderConfig; import io.trino.plugin.hive.parquet.ParquetWriterConfig; import io.trino.plugin.hive.rcfile.RcFilePageSourceFactory; +import io.trino.spi.block.BlockBuilder; import io.trino.spi.connector.ConnectorPageSource; import io.trino.spi.connector.ConnectorSession; import io.trino.spi.predicate.TupleDomain; +import io.trino.spi.type.CharType; +import io.trino.spi.type.SqlDate; +import io.trino.spi.type.SqlDecimal; +import io.trino.spi.type.SqlTimestamp; +import io.trino.spi.type.SqlVarbinary; import io.trino.spi.type.Type; +import io.trino.testing.MaterializedResult; +import io.trino.testing.MaterializedRow; import io.trino.testing.TestingConnectorSession; import org.apache.hadoop.hive.common.type.HiveVarchar; import org.apache.hadoop.hive.serde2.objectinspector.ListObjectInspector; @@ -58,12 +67,14 @@ import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector; import org.apache.hadoop.hive.serde2.typeinfo.VarcharTypeInfo; import org.apache.hadoop.mapred.FileSplit; +import org.joda.time.DateTimeZone; import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.io.File; import java.io.IOException; +import java.math.BigDecimal; import java.time.Instant; import java.util.ArrayList; import java.util.HashSet; @@ -98,10 +109,16 @@ import static io.trino.plugin.hive.HiveTimestampPrecision.DEFAULT_PRECISION; import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; import static io.trino.plugin.hive.util.SerdeConstants.SERIALIZATION_LIB; +import static io.trino.spi.type.Chars.padSpaces; +import static io.trino.testing.DateTimeTestingUtils.sqlTimestampOf; +import static io.trino.testing.MaterializedResult.materializeSourceDataStream; import static io.trino.testing.StructuralTestUtil.rowBlockOf; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; +import static io.trino.type.DateTimes.MICROSECONDS_PER_MILLISECOND; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; +import static java.lang.Math.floorDiv; import static java.lang.String.format; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; import static org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.FILE_INPUT_FORMAT; @@ -117,9 +134,12 @@ // uses a single record writer across all threads. // For example org.apache.parquet.column.values.factory.DefaultValuesWriterFactory#DEFAULT_V1_WRITER_FACTORY is shared mutable state. @Test(singleThreaded = true) -public class TestHiveFileFormats +public final class TestHiveFileFormats extends AbstractTestHiveFileFormats { + private static final DateTimeZone HIVE_STORAGE_TIME_ZONE = DateTimeZone.forID("America/Bahia_Banderas"); + private static final double EPSILON = 0.001; + private static final FileFormatDataSourceStats STATS = new FileFormatDataSourceStats(); private static final ConnectorSession PARQUET_SESSION = getHiveSession(createParquetHiveConfig(false)); private static final ConnectorSession PARQUET_SESSION_USE_NAME = getHiveSession(createParquetHiveConfig(true)); @@ -918,6 +938,73 @@ private static void testPageSourceFactory( checkPageSource(pageSource.get(), testReadColumns, getTypes(columnHandles), rowCount); } + private static void checkPageSource(ConnectorPageSource pageSource, List testColumns, List types, int rowCount) + throws IOException + { + try (pageSource) { + MaterializedResult result = materializeSourceDataStream(SESSION, pageSource, types); + assertEquals(result.getMaterializedRows().size(), rowCount); + for (MaterializedRow row : result) { + for (int i = 0, testColumnsSize = testColumns.size(); i < testColumnsSize; i++) { + TestColumn testColumn = testColumns.get(i); + Type type = types.get(i); + + Object actualValue = row.getField(i); + Object expectedValue = testColumn.getExpectedValue(); + + if (expectedValue instanceof Slice) { + expectedValue = ((Slice) expectedValue).toStringUtf8(); + } + + if (actualValue == null || expectedValue == null) { + assertEquals(actualValue, expectedValue, "Wrong value for column " + testColumn.getName()); + } + else if (testColumn.getObjectInspector().getTypeName().equals("float")) { + assertEquals((float) actualValue, (float) expectedValue, EPSILON, "Wrong value for column " + testColumn.getName()); + } + else if (testColumn.getObjectInspector().getTypeName().equals("double")) { + assertEquals((double) actualValue, (double) expectedValue, EPSILON, "Wrong value for column " + testColumn.getName()); + } + else if (testColumn.getObjectInspector().getTypeName().equals("date")) { + SqlDate expectedDate = new SqlDate(((Long) expectedValue).intValue()); + assertEquals(actualValue, expectedDate, "Wrong value for column " + testColumn.getName()); + } + else if (testColumn.getObjectInspector().getTypeName().equals("int") || + testColumn.getObjectInspector().getTypeName().equals("smallint") || + testColumn.getObjectInspector().getTypeName().equals("tinyint")) { + assertEquals(actualValue, expectedValue); + } + else if (testColumn.getObjectInspector().getTypeName().equals("timestamp")) { + SqlTimestamp expectedTimestamp = sqlTimestampOf(3, floorDiv((Long) expectedValue, MICROSECONDS_PER_MILLISECOND)); + assertEquals(actualValue, expectedTimestamp, "Wrong value for column " + testColumn.getName()); + } + else if (testColumn.getObjectInspector().getTypeName().startsWith("char")) { + assertEquals(actualValue, padSpaces((String) expectedValue, (CharType) type), "Wrong value for column " + testColumn.getName()); + } + else if (testColumn.getObjectInspector().getCategory() == ObjectInspector.Category.PRIMITIVE) { + if (actualValue instanceof Slice) { + actualValue = ((Slice) actualValue).toStringUtf8(); + } + if (actualValue instanceof SqlVarbinary) { + actualValue = new String(((SqlVarbinary) actualValue).getBytes(), UTF_8); + } + + if (actualValue instanceof SqlDecimal) { + actualValue = new BigDecimal(actualValue.toString()); + } + assertEquals(actualValue, expectedValue, "Wrong value for column " + testColumn.getName()); + } + else { + BlockBuilder builder = type.createBlockBuilder(null, 1); + type.writeObject(builder, expectedValue); + expectedValue = type.getObjectValue(SESSION, builder.build(), 0); + assertEquals(actualValue, expectedValue, "Wrong value for column " + testColumn.getName()); + } + } + } + } + } + private static boolean hasType(ObjectInspector objectInspector, PrimitiveCategory... types) { if (objectInspector instanceof PrimitiveObjectInspector primitiveInspector) { From 22ffd0475edcf572c070cbba717f13b2a9a0d7d1 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Sat, 4 Nov 2023 23:15:09 -0700 Subject: [PATCH 093/587] Simplify TestOrcPredicates --- .../plugin/hive/orc/TestOrcPredicates.java | 280 +++++++++--------- 1 file changed, 140 insertions(+), 140 deletions(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPredicates.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPredicates.java index b331c8b12caf..cad9c391c0a1 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPredicates.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPredicates.java @@ -17,70 +17,88 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import io.trino.filesystem.Location; +import io.trino.filesystem.TrinoFileSystemFactory; +import io.trino.filesystem.memory.MemoryFileSystemFactory; import io.trino.orc.OrcReaderOptions; import io.trino.orc.OrcWriterOptions; -import io.trino.plugin.hive.AbstractTestHiveFileFormats; import io.trino.plugin.hive.FileFormatDataSourceStats; +import io.trino.plugin.hive.FileWriter; import io.trino.plugin.hive.HiveColumnHandle; +import io.trino.plugin.hive.HiveColumnProjectionInfo; import io.trino.plugin.hive.HiveCompressionCodec; import io.trino.plugin.hive.HiveConfig; import io.trino.plugin.hive.HivePageSourceProvider; -import io.trino.plugin.hive.HivePartitionKey; +import io.trino.plugin.hive.HiveType; import io.trino.plugin.hive.NodeVersion; import io.trino.plugin.hive.TableToPartitionMapping; +import io.trino.plugin.hive.WriterKind; +import io.trino.plugin.hive.metastore.StorageFormat; import io.trino.spi.Page; +import io.trino.spi.block.Block; +import io.trino.spi.block.IntArrayBlock; +import io.trino.spi.block.LongArrayBlock; +import io.trino.spi.block.RowBlock; +import io.trino.spi.block.RunLengthEncodedBlock; import io.trino.spi.connector.ConnectorPageSource; import io.trino.spi.connector.ConnectorSession; import io.trino.spi.predicate.Domain; import io.trino.spi.predicate.TupleDomain; -import org.apache.hadoop.mapred.FileSplit; +import io.trino.spi.type.RowType; import org.junit.jupiter.api.Test; -import java.io.File; import java.io.IOException; import java.time.Instant; -import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.OptionalInt; import java.util.Properties; -import java.util.Set; +import java.util.stream.Collectors; -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.trino.hive.thrift.metastore.hive_metastoreConstants.FILE_INPUT_FORMAT; +import static io.trino.plugin.hive.HiveColumnHandle.ColumnType.REGULAR; +import static io.trino.plugin.hive.HiveColumnHandle.createBaseColumn; import static io.trino.plugin.hive.HivePageSourceProvider.ColumnMapping.buildColumnMappings; import static io.trino.plugin.hive.HiveStorageFormat.ORC; -import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; import static io.trino.plugin.hive.HiveTestUtils.getHiveSession; import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; import static io.trino.plugin.hive.util.SerdeConstants.SERIALIZATION_LIB; import static io.trino.spi.type.BigintType.BIGINT; -import static io.trino.testing.StructuralTestUtil.rowBlockOf; +import static io.trino.spi.type.IntegerType.INTEGER; +import static io.trino.spi.type.RowType.field; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; -import static java.lang.String.format; import static java.util.stream.Collectors.toList; -import static org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.FILE_INPUT_FORMAT; -import static org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory.getStandardStructObjectInspector; -import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaIntObjectInspector; -import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaLongObjectInspector; import static org.joda.time.DateTimeZone.UTC; import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; class TestOrcPredicates - extends AbstractTestHiveFileFormats { private static final int NUM_ROWS = 50000; private static final FileFormatDataSourceStats STATS = new FileFormatDataSourceStats(); - // Prepare test columns - private static final TestColumn columnPrimitiveInteger = new TestColumn("column_primitive_integer", javaIntObjectInspector, 3, 3); - private static final TestColumn columnStruct = new TestColumn( + private static final HiveColumnHandle INTEGER_COLUMN = createBaseColumn("column_primitive_integer", 0, HiveType.HIVE_INT, INTEGER, REGULAR, Optional.empty()); + private static final HiveColumnHandle STRUCT_COLUMN = createBaseColumn( "column1_struct", - getStandardStructObjectInspector(ImmutableList.of("field0", "field1"), ImmutableList.of(javaLongObjectInspector, javaLongObjectInspector)), - new Long[] {4L, 5L}, - rowBlockOf(ImmutableList.of(BIGINT, BIGINT), 4L, 5L)); - private static final TestColumn columnPrimitiveBigInt = new TestColumn("column_primitive_bigint", javaLongObjectInspector, 6L, 6L); + 1, + HiveType.toHiveType(RowType.rowType(field("field0", BIGINT), field("field1", BIGINT))), + RowType.rowType(field("field0", BIGINT), field("field1", BIGINT)), + REGULAR, + Optional.empty()); + private static final HiveColumnHandle BIGINT_COLUMN = createBaseColumn("column_primitive_bigint", 2, HiveType.HIVE_LONG, BIGINT, REGULAR, Optional.empty()); + private static final List COLUMNS = ImmutableList.of(INTEGER_COLUMN, STRUCT_COLUMN, BIGINT_COLUMN); + + private static final HiveColumnHandle STRUCT_FIELD1_COLUMN = new HiveColumnHandle( + STRUCT_COLUMN.getBaseColumnName(), + STRUCT_COLUMN.getBaseHiveColumnIndex(), + STRUCT_COLUMN.getBaseHiveType(), + STRUCT_COLUMN.getBaseType(), + Optional.of(new HiveColumnProjectionInfo( + ImmutableList.of(1), + ImmutableList.of("field1"), + HiveType.HIVE_LONG, + BIGINT)), + STRUCT_COLUMN.getColumnType(), + STRUCT_COLUMN.getComment()); + private static final List PROJECTED_COLUMNS = ImmutableList.of(BIGINT_COLUMN, STRUCT_FIELD1_COLUMN); @Test void testOrcPredicates() @@ -93,59 +111,33 @@ void testOrcPredicates() private static void testOrcPredicates(ConnectorSession session) throws Exception { - List columnsToWrite = ImmutableList.of(columnPrimitiveInteger, columnStruct, columnPrimitiveBigInt); - - File file = File.createTempFile("test", "orc_predicate"); - file.delete(); - try { - // Write data - OrcFileWriterFactory writerFactory = new OrcFileWriterFactory(TESTING_TYPE_MANAGER, new NodeVersion("test"), STATS, new OrcWriterOptions(), HDFS_FILE_SYSTEM_FACTORY); - FileSplit split = createTestFileTrino(file.getAbsolutePath(), ORC, HiveCompressionCodec.NONE, columnsToWrite, session, NUM_ROWS, writerFactory); - - TupleDomain testingPredicate; - - // Verify predicates on base column - List columnsToRead = columnsToWrite; - // All rows returned for a satisfying predicate - testingPredicate = TupleDomain.withColumnDomains(ImmutableMap.of(columnPrimitiveBigInt, Domain.singleValue(BIGINT, 6L))); - assertFilteredRows(testingPredicate, columnsToRead, session, split, NUM_ROWS); - // No rows returned for a mismatched predicate - testingPredicate = TupleDomain.withColumnDomains(ImmutableMap.of(columnPrimitiveBigInt, Domain.singleValue(BIGINT, 1L))); - assertFilteredRows(testingPredicate, columnsToRead, session, split, 0); - - // Verify predicates on projected column - TestColumn projectedColumn = new TestColumn( - columnStruct.getBaseName(), - columnStruct.getBaseObjectInspector(), - ImmutableList.of("field1"), - ImmutableList.of(1), - javaLongObjectInspector, - 5L, - 5L, - false); - - columnsToRead = ImmutableList.of(columnPrimitiveBigInt, projectedColumn); - // All rows returned for a satisfying predicate - testingPredicate = TupleDomain.withColumnDomains(ImmutableMap.of(projectedColumn, Domain.singleValue(BIGINT, 5L))); - assertFilteredRows(testingPredicate, columnsToRead, session, split, NUM_ROWS); - // No rows returned for a mismatched predicate - testingPredicate = TupleDomain.withColumnDomains(ImmutableMap.of(projectedColumn, Domain.singleValue(BIGINT, 6L))); - assertFilteredRows(testingPredicate, columnsToRead, session, split, 0); - } - finally { - file.delete(); - } + TrinoFileSystemFactory fileSystemFactory = new MemoryFileSystemFactory(); + Location location = Location.of("memory:///test"); + writeTestFile(session, fileSystemFactory, location); + + // Verify predicates on base column + // All rows returned for a satisfying predicate + assertFilteredRows(fileSystemFactory, location, TupleDomain.withColumnDomains(ImmutableMap.of(BIGINT_COLUMN, Domain.singleValue(BIGINT, 6L))), COLUMNS, session, NUM_ROWS); + // No rows returned for a mismatched predicate + assertFilteredRows(fileSystemFactory, location, TupleDomain.withColumnDomains(ImmutableMap.of(BIGINT_COLUMN, Domain.singleValue(BIGINT, 1L))), COLUMNS, session, 0); + + // Verify predicates on projected column + // All rows returned for a satisfying predicate + assertFilteredRows(fileSystemFactory, location, TupleDomain.withColumnDomains(ImmutableMap.of(STRUCT_FIELD1_COLUMN, Domain.singleValue(BIGINT, 5L))), PROJECTED_COLUMNS, session, NUM_ROWS); + // No rows returned for a mismatched predicate + assertFilteredRows(fileSystemFactory, location, TupleDomain.withColumnDomains(ImmutableMap.of(STRUCT_FIELD1_COLUMN, Domain.singleValue(BIGINT, 6L))), PROJECTED_COLUMNS, session, 0); } private static void assertFilteredRows( - TupleDomain effectivePredicate, - List columnsToRead, + TrinoFileSystemFactory fileSystemFactory, + Location location, + TupleDomain effectivePredicate, + List columnsToRead, ConnectorSession session, - FileSplit split, int expectedRows) throws IOException { - try (ConnectorPageSource pageSource = createPageSource(effectivePredicate, columnsToRead, session, split)) { + try (ConnectorPageSource pageSource = createPageSource(fileSystemFactory, location, effectivePredicate, columnsToRead, session)) { int filteredRows = 0; while (!pageSource.isFinished()) { Page page = pageSource.getNextPage(); @@ -158,83 +150,91 @@ private static void assertFilteredRows( } private static ConnectorPageSource createPageSource( - TupleDomain effectivePredicate, - List columnsToRead, - ConnectorSession session, - FileSplit split) + TrinoFileSystemFactory fileSystemFactory, + Location location, + TupleDomain effectivePredicate, + List columns, + ConnectorSession session) + throws IOException { - OrcPageSourceFactory readerFactory = new OrcPageSourceFactory(new OrcReaderOptions(), HDFS_FILE_SYSTEM_FACTORY, STATS, UTC); - - Properties splitProperties = new Properties(); - splitProperties.setProperty(FILE_INPUT_FORMAT, ORC.getInputFormat()); - splitProperties.setProperty(SERIALIZATION_LIB, ORC.getSerde()); - - // Use full columns in split properties - ImmutableList.Builder splitPropertiesColumnNames = ImmutableList.builder(); - ImmutableList.Builder splitPropertiesColumnTypes = ImmutableList.builder(); - Set baseColumnNames = new HashSet<>(); - for (TestColumn columnToRead : columnsToRead) { - String name = columnToRead.getBaseName(); - if (!baseColumnNames.contains(name) && !columnToRead.isPartitionKey()) { - baseColumnNames.add(name); - splitPropertiesColumnNames.add(name); - splitPropertiesColumnTypes.add(columnToRead.getBaseObjectInspector().getTypeName()); - } - } - - splitProperties.setProperty("columns", String.join(",", splitPropertiesColumnNames.build())); - splitProperties.setProperty("columns.types", String.join(",", splitPropertiesColumnTypes.build())); - - List partitionKeys = columnsToRead.stream() - .filter(TestColumn::isPartitionKey) - .map(input -> new HivePartitionKey(input.getName(), (String) input.getWriteValue())) - .collect(toList()); - - String partitionName = String.join("/", partitionKeys.stream() - .map(partitionKey -> format("%s=%s", partitionKey.getName(), partitionKey.getValue())) - .collect(toImmutableList())); - - List columnHandles = getColumnHandles(columnsToRead); - - TupleDomain predicate = effectivePredicate.transformKeys(testColumn -> { - Optional handle = columnHandles.stream() - .filter(column -> testColumn.getName().equals(column.getName())) - .findFirst(); - - checkState(handle.isPresent(), "Predicate on invalid column"); - return handle.get(); - }); + OrcPageSourceFactory readerFactory = new OrcPageSourceFactory(new OrcReaderOptions(), fileSystemFactory, STATS, UTC); + long length = fileSystemFactory.create(session).newInputFile(location).length(); List columnMappings = buildColumnMappings( - partitionName, - partitionKeys, - columnHandles, + "", + ImmutableList.of(), + columns, ImmutableList.of(), TableToPartitionMapping.empty(), - split.getPath().toString(), + location.toString(), OptionalInt.empty(), - split.getLength(), + length, Instant.now().toEpochMilli()); - Optional pageSource = HivePageSourceProvider.createHivePageSource( - ImmutableSet.of(readerFactory), - session, - Location.of(split.getPath().toString()), - OptionalInt.empty(), - split.getStart(), - split.getLength(), - split.getLength(), - splitProperties, - predicate, - TESTING_TYPE_MANAGER, - Optional.empty(), - Optional.empty(), - Optional.empty(), - false, - NO_ACID_TRANSACTION, - columnMappings); - - assertTrue(pageSource.isPresent()); - return pageSource.get(); + return HivePageSourceProvider.createHivePageSource( + ImmutableSet.of(readerFactory), + session, + location, + OptionalInt.empty(), + 0, + length, + length, + getTableProperties(), + effectivePredicate, + TESTING_TYPE_MANAGER, + Optional.empty(), + Optional.empty(), + Optional.empty(), + false, + NO_ACID_TRANSACTION, + columnMappings) + .orElseThrow(); + } + + private static void writeTestFile(ConnectorSession session, TrinoFileSystemFactory fileSystemFactory, Location location) + { + FileWriter fileWriter = new OrcFileWriterFactory(TESTING_TYPE_MANAGER, new NodeVersion("test"), STATS, new OrcWriterOptions(), fileSystemFactory) + .createFileWriter( + location, + COLUMNS.stream().map(HiveColumnHandle::getName).collect(toList()), + StorageFormat.fromHiveStorageFormat(ORC), + HiveCompressionCodec.NONE, + getTableProperties(), + session, + OptionalInt.empty(), + NO_ACID_TRANSACTION, + false, + WriterKind.INSERT) + .orElseThrow(); + + fileWriter.appendRows(new Page( + RunLengthEncodedBlock.create(new IntArrayBlock(1, Optional.empty(), new int[] {3}), NUM_ROWS), + RunLengthEncodedBlock.create( + RowBlock.fromFieldBlocks(1, new Block[] { + new LongArrayBlock(1, Optional.empty(), new long[] {4}), + new LongArrayBlock(1, Optional.empty(), new long[] {5})}), + NUM_ROWS), + RunLengthEncodedBlock.create(new LongArrayBlock(1, Optional.empty(), new long[] {6}), NUM_ROWS))); + + fileWriter.commit(); + } + + private static Properties getTableProperties() + { + Properties tableProperties = new Properties(); + tableProperties.setProperty(FILE_INPUT_FORMAT, ORC.getInputFormat()); + tableProperties.setProperty(SERIALIZATION_LIB, ORC.getSerde()); + tableProperties.setProperty( + "columns", + COLUMNS.stream() + .map(HiveColumnHandle::getName) + .collect(Collectors.joining(","))); + tableProperties.setProperty( + "columns.types", + COLUMNS.stream() + .map(HiveColumnHandle::getHiveType) + .map(HiveType::toString) + .collect(Collectors.joining(","))); + return tableProperties; } } From b9a216c8a29aff5211515eb346ceb363a6fb7a0a Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Sun, 5 Nov 2023 14:57:50 -0800 Subject: [PATCH 094/587] Use memory file system in TestHiveFileFormats --- .../hive/AbstractTestHiveFileFormats.java | 52 ++--- .../plugin/hive/TestHiveFileFormats.java | 195 +++++++++--------- 2 files changed, 124 insertions(+), 123 deletions(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileFormats.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileFormats.java index 7524db00ff8e..dde596c6d7f6 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileFormats.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileFormats.java @@ -18,10 +18,13 @@ import io.airlift.slice.Slice; import io.airlift.slice.Slices; import io.trino.filesystem.Location; +import io.trino.filesystem.TrinoFileSystem; +import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.plugin.hive.metastore.StorageFormat; import io.trino.spi.Page; import io.trino.spi.PageBuilder; import io.trino.spi.connector.ConnectorSession; +import io.trino.spi.security.ConnectorIdentity; import io.trino.spi.type.ArrayType; import io.trino.spi.type.DateType; import io.trino.spi.type.DecimalType; @@ -46,12 +49,12 @@ import org.apache.hadoop.hive.serde2.typeinfo.DecimalTypeInfo; import org.apache.hadoop.io.Text; import org.apache.hadoop.io.Writable; -import org.apache.hadoop.mapred.FileSplit; import org.apache.hadoop.mapred.JobConf; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; import java.io.File; +import java.io.OutputStream; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; @@ -92,6 +95,7 @@ import static io.trino.testing.StructuralTestUtil.sqlMapOf; import static io.trino.type.DateTimes.MICROSECONDS_PER_MILLISECOND; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; +import static java.nio.file.Files.readAllBytes; import static java.util.Arrays.fill; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; @@ -487,8 +491,8 @@ protected static List getColumnHandles(List testCo return columns; } - public static FileSplit createTestFileTrino( - String filePath, + public static void createTestFileTrino( + Location location, HiveStorageFormat storageFormat, HiveCompressionCodec compressionCodec, List testColumns, @@ -536,7 +540,7 @@ public static FileSplit createTestFileTrino( .collect(Collectors.joining(","))); Optional fileWriter = fileWriterFactory.createFileWriter( - Location.of(filePath), + location, testColumns.stream() .map(TestColumn::getName) .collect(toList()), @@ -552,12 +556,11 @@ public static FileSplit createTestFileTrino( FileWriter hiveFileWriter = fileWriter.orElseThrow(() -> new IllegalArgumentException("fileWriterFactory")); hiveFileWriter.appendRows(page); hiveFileWriter.commit(); - - return new FileSplit(new Path(filePath), 0, new File(filePath).length(), new String[0]); } - public static FileSplit createTestFileHive( - String filePath, + public static void createTestFileHive( + TrinoFileSystemFactory fileSystemFactory, + Location location, HiveStorageFormat storageFormat, HiveCompressionCodec compressionCodec, List testColumns, @@ -588,15 +591,17 @@ public static FileSplit createTestFileHive( JobConf jobConf = new JobConf(false); configureCompression(jobConf, compressionCodec); - RecordWriter recordWriter = outputFormat.getHiveRecordWriter( - jobConf, - new Path(filePath), - Text.class, - compressionCodec != HiveCompressionCodec.NONE, - tableProperties, - () -> {}); - + File file = File.createTempFile("trino_test", "data"); + file.delete(); try { + RecordWriter recordWriter = outputFormat.getHiveRecordWriter( + jobConf, + new Path(file.getAbsolutePath()), + Text.class, + compressionCodec != HiveCompressionCodec.NONE, + tableProperties, + () -> {}); + serializer.initialize(new Configuration(false), tableProperties); SettableStructObjectInspector objectInspector = getStandardStructObjectInspector( @@ -623,16 +628,17 @@ public static FileSplit createTestFileHive( Writable record = serializer.serialize(row, objectInspector); recordWriter.write(record); } + recordWriter.close(false); + + // copy the file data to the TrinoFileSystem + TrinoFileSystem fileSystem = fileSystemFactory.create(ConnectorIdentity.ofUser("test")); + try (OutputStream outputStream = fileSystem.newOutputFile(location).create()) { + outputStream.write(readAllBytes(file.toPath())); + } } finally { - recordWriter.close(false); + file.delete(); } - - // todo to test with compression, the file must be renamed with the compression extension - Path path = new Path(filePath); - path.getFileSystem(new Configuration(false)).setVerifyChecksum(true); - File file = new File(filePath); - return new FileSplit(path, 0, file.length(), new String[0]); } private static T newInstance(String className, Class superType) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java index 93c48b796cb0..13af486d250a 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java @@ -18,8 +18,9 @@ import com.google.common.collect.Lists; import io.airlift.slice.Slice; import io.trino.filesystem.Location; +import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoFileSystemFactory; -import io.trino.filesystem.hdfs.HdfsFileSystemFactory; +import io.trino.filesystem.memory.MemoryFileSystemFactory; import io.trino.hive.formats.compression.CompressionKind; import io.trino.orc.OrcReaderOptions; import io.trino.orc.OrcWriterOptions; @@ -48,6 +49,7 @@ import io.trino.spi.connector.ConnectorPageSource; import io.trino.spi.connector.ConnectorSession; import io.trino.spi.predicate.TupleDomain; +import io.trino.spi.security.ConnectorIdentity; import io.trino.spi.type.CharType; import io.trino.spi.type.SqlDate; import io.trino.spi.type.SqlDecimal; @@ -66,13 +68,11 @@ import org.apache.hadoop.hive.serde2.objectinspector.StructField; import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector; import org.apache.hadoop.hive.serde2.typeinfo.VarcharTypeInfo; -import org.apache.hadoop.mapred.FileSplit; import org.joda.time.DateTimeZone; import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import java.io.File; import java.io.IOException; import java.math.BigDecimal; import java.time.Instant; @@ -85,6 +85,7 @@ import java.util.Properties; import java.util.Set; import java.util.TimeZone; +import java.util.function.Function; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableList.toImmutableList; @@ -100,9 +101,6 @@ import static io.trino.plugin.hive.HiveStorageFormat.RCTEXT; import static io.trino.plugin.hive.HiveStorageFormat.SEQUENCEFILE; import static io.trino.plugin.hive.HiveStorageFormat.TEXTFILE; -import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; -import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; -import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_STATS; import static io.trino.plugin.hive.HiveTestUtils.SESSION; import static io.trino.plugin.hive.HiveTestUtils.getHiveSession; import static io.trino.plugin.hive.HiveTestUtils.getTypes; @@ -144,9 +142,6 @@ public final class TestHiveFileFormats private static final ConnectorSession PARQUET_SESSION = getHiveSession(createParquetHiveConfig(false)); private static final ConnectorSession PARQUET_SESSION_USE_NAME = getHiveSession(createParquetHiveConfig(true)); - private static final TrinoFileSystemFactory FILE_SYSTEM_FACTORY = new HdfsFileSystemFactory(HDFS_ENVIRONMENT, HDFS_FILE_SYSTEM_STATS); - private static final HivePageSourceFactory PARQUET_PAGE_SOURCE_FACTORY = new ParquetPageSourceFactory(FILE_SYSTEM_FACTORY, STATS, new ParquetReaderConfig(), new HiveConfig()); - @DataProvider(name = "rowCount") public static Object[][] rowCountProvider() { @@ -181,8 +176,8 @@ public void testTextFile(int rowCount, long fileSizePadding) .withColumns(testColumns) .withRowsCount(rowCount) .withFileSizePadding(fileSizePadding) - .withFileWriterFactory(new SimpleTextFileWriterFactory(HDFS_FILE_SYSTEM_FACTORY, TESTING_TYPE_MANAGER)) - .isReadableByPageSource(new SimpleTextFilePageSourceFactory(HDFS_FILE_SYSTEM_FACTORY, new HiveConfig())); + .withFileWriterFactory(fileSystemFactory -> new SimpleTextFileWriterFactory(fileSystemFactory, TESTING_TYPE_MANAGER)) + .isReadableByPageSource(fileSystemFactory -> new SimpleTextFilePageSourceFactory(fileSystemFactory, new HiveConfig())); } @Test(dataProvider = "validRowAndFileSizePadding") @@ -198,8 +193,8 @@ public void testSequenceFile(int rowCount, long fileSizePadding) .withColumns(testColumns) .withRowsCount(rowCount) .withFileSizePadding(fileSizePadding) - .withFileWriterFactory(new SimpleSequenceFileWriterFactory(HDFS_FILE_SYSTEM_FACTORY, TESTING_TYPE_MANAGER, new NodeVersion("test"))) - .isReadableByPageSource(new SimpleSequenceFilePageSourceFactory(HDFS_FILE_SYSTEM_FACTORY, new HiveConfig())); + .withFileWriterFactory(fileSystemFactory -> new SimpleSequenceFileWriterFactory(fileSystemFactory, TESTING_TYPE_MANAGER, new NodeVersion("test"))) + .isReadableByPageSource(fileSystemFactory -> new SimpleSequenceFilePageSourceFactory(fileSystemFactory, new HiveConfig())); } @Test(dataProvider = "validRowAndFileSizePadding") @@ -217,8 +212,8 @@ public void testCsvFile(int rowCount, long fileSizePadding) .withColumns(testColumns) .withRowsCount(rowCount) .withFileSizePadding(fileSizePadding) - .withFileWriterFactory(new CsvFileWriterFactory(HDFS_FILE_SYSTEM_FACTORY, TESTING_TYPE_MANAGER)) - .isReadableByPageSource(new CsvPageSourceFactory(HDFS_FILE_SYSTEM_FACTORY, new HiveConfig())); + .withFileWriterFactory(fileSystemFactory -> new CsvFileWriterFactory(fileSystemFactory, TESTING_TYPE_MANAGER)) + .isReadableByPageSource(fileSystemFactory -> new CsvPageSourceFactory(fileSystemFactory, new HiveConfig())); } @Test @@ -230,8 +225,8 @@ public void testCsvFileWithNullAndValue() new TestColumn("t_null_string", javaStringObjectInspector, null, utf8Slice("")), // null was converted to empty string! new TestColumn("t_string", javaStringObjectInspector, "test", utf8Slice("test")))) .withRowsCount(2) - .withFileWriterFactory(new CsvFileWriterFactory(HDFS_FILE_SYSTEM_FACTORY, TESTING_TYPE_MANAGER)) - .isReadableByPageSource(new CsvPageSourceFactory(HDFS_FILE_SYSTEM_FACTORY, new HiveConfig())); + .withFileWriterFactory(fileSystemFactory -> new CsvFileWriterFactory(fileSystemFactory, TESTING_TYPE_MANAGER)) + .isReadableByPageSource(fileSystemFactory -> new CsvPageSourceFactory(fileSystemFactory, new HiveConfig())); } @Test(dataProvider = "validRowAndFileSizePadding") @@ -260,8 +255,8 @@ public void testJson(int rowCount, long fileSizePadding) .withColumns(testColumns) .withRowsCount(rowCount) .withFileSizePadding(fileSizePadding) - .withFileWriterFactory(new JsonFileWriterFactory(HDFS_FILE_SYSTEM_FACTORY, TESTING_TYPE_MANAGER)) - .isReadableByPageSource(new JsonPageSourceFactory(HDFS_FILE_SYSTEM_FACTORY, new HiveConfig())); + .withFileWriterFactory(fileSystemFactory -> new JsonFileWriterFactory(fileSystemFactory, TESTING_TYPE_MANAGER)) + .isReadableByPageSource(fileSystemFactory -> new JsonPageSourceFactory(fileSystemFactory, new HiveConfig())); } @Test(dataProvider = "validRowAndFileSizePadding") @@ -279,8 +274,8 @@ public void testOpenXJson(int rowCount, long fileSizePadding) .withFileSizePadding(fileSizePadding) // openx serde is not available for testing .withSkipGenericWriterTest() - .withFileWriterFactory(new OpenXJsonFileWriterFactory(HDFS_FILE_SYSTEM_FACTORY, TESTING_TYPE_MANAGER)) - .isReadableByPageSource(new OpenXJsonPageSourceFactory(HDFS_FILE_SYSTEM_FACTORY, new HiveConfig())); + .withFileWriterFactory(fileSystemFactory -> new OpenXJsonFileWriterFactory(fileSystemFactory, TESTING_TYPE_MANAGER)) + .isReadableByPageSource(fileSystemFactory -> new OpenXJsonPageSourceFactory(fileSystemFactory, new HiveConfig())); } @Test(dataProvider = "validRowAndFileSizePadding") @@ -291,7 +286,7 @@ public void testRcTextPageSource(int rowCount, long fileSizePadding) .withColumns(TEST_COLUMNS) .withRowsCount(rowCount) .withFileSizePadding(fileSizePadding) - .isReadableByPageSource(new RcFilePageSourceFactory(FILE_SYSTEM_FACTORY, new HiveConfig())); + .isReadableByPageSource(fileSystemFactory -> new RcFilePageSourceFactory(fileSystemFactory, new HiveConfig())); } @Test(dataProvider = "rowCount") @@ -306,8 +301,8 @@ public void testRcTextOptimizedWriter(int rowCount) assertThatFileFormat(RCTEXT) .withColumns(testColumns) .withRowsCount(rowCount) - .withFileWriterFactory(new RcFileFileWriterFactory(FILE_SYSTEM_FACTORY, TESTING_TYPE_MANAGER, new NodeVersion("test"), HIVE_STORAGE_TIME_ZONE)) - .isReadableByPageSource(new RcFilePageSourceFactory(FILE_SYSTEM_FACTORY, new HiveConfig())); + .withFileWriterFactory(fileSystemFactory -> new RcFileFileWriterFactory(fileSystemFactory, TESTING_TYPE_MANAGER, new NodeVersion("test"), HIVE_STORAGE_TIME_ZONE)) + .isReadableByPageSource(fileSystemFactory -> new RcFilePageSourceFactory(fileSystemFactory, new HiveConfig())); } @Test(dataProvider = "rowCount") @@ -324,7 +319,7 @@ public void testRcBinaryPageSource(int rowCount) assertThatFileFormat(RCBINARY) .withColumns(testColumns) .withRowsCount(rowCount) - .isReadableByPageSource(new RcFilePageSourceFactory(FILE_SYSTEM_FACTORY, new HiveConfig())); + .isReadableByPageSource(fileSystemFactory -> new RcFilePageSourceFactory(fileSystemFactory, new HiveConfig())); } @Test(dataProvider = "rowCount") @@ -348,8 +343,8 @@ public void testRcBinaryOptimizedWriter(int rowCount) .withRowsCount(rowCount) // generic Hive writer corrupts timestamps .withSkipGenericWriterTest() - .withFileWriterFactory(new RcFileFileWriterFactory(FILE_SYSTEM_FACTORY, TESTING_TYPE_MANAGER, new NodeVersion("test"), HIVE_STORAGE_TIME_ZONE)) - .isReadableByPageSource(new RcFilePageSourceFactory(FILE_SYSTEM_FACTORY, new HiveConfig())) + .withFileWriterFactory(fileSystemFactory -> new RcFileFileWriterFactory(fileSystemFactory, TESTING_TYPE_MANAGER, new NodeVersion("test"), HIVE_STORAGE_TIME_ZONE)) + .isReadableByPageSource(fileSystemFactory -> new RcFilePageSourceFactory(fileSystemFactory, new HiveConfig())) .withColumns(testColumnsNoTimestamps); } @@ -361,7 +356,7 @@ public void testOrc(int rowCount, long fileSizePadding) .withColumns(TEST_COLUMNS) .withRowsCount(rowCount) .withFileSizePadding(fileSizePadding) - .isReadableByPageSource(new OrcPageSourceFactory(new OrcReaderOptions(), HDFS_FILE_SYSTEM_FACTORY, STATS, UTC)); + .isReadableByPageSource(fileSystemFactory -> new OrcPageSourceFactory(new OrcReaderOptions(), fileSystemFactory, STATS, UTC)); } @Test(dataProvider = "validRowAndFileSizePadding") @@ -389,8 +384,8 @@ public void testOrcOptimizedWriter(int rowCount, long fileSizePadding) .withRowsCount(rowCount) .withSession(session) .withFileSizePadding(fileSizePadding) - .withFileWriterFactory(new OrcFileWriterFactory(TESTING_TYPE_MANAGER, new NodeVersion("test"), STATS, new OrcWriterOptions(), HDFS_FILE_SYSTEM_FACTORY)) - .isReadableByPageSource(new OrcPageSourceFactory(new OrcReaderOptions(), HDFS_FILE_SYSTEM_FACTORY, STATS, UTC)); + .withFileWriterFactory(fileSystemFactory -> new OrcFileWriterFactory(TESTING_TYPE_MANAGER, new NodeVersion("test"), STATS, new OrcWriterOptions(), fileSystemFactory)) + .isReadableByPageSource(fileSystemFactory -> new OrcPageSourceFactory(new OrcReaderOptions(), fileSystemFactory, STATS, UTC)); } @Test(dataProvider = "rowCount") @@ -409,7 +404,7 @@ public void testOrcUseColumnNames(int rowCount) .withRowsCount(rowCount) .withReadColumns(Lists.reverse(testColumns)) .withSession(session) - .isReadableByPageSource(new OrcPageSourceFactory(new OrcReaderOptions(), HDFS_FILE_SYSTEM_FACTORY, STATS, UTC)); + .isReadableByPageSource(fileSystemFactory -> new OrcPageSourceFactory(new OrcReaderOptions(), fileSystemFactory, STATS, UTC)); } @Test(dataProvider = "rowCount") @@ -426,7 +421,7 @@ public void testOrcUseColumnNameLowerCaseConversion(int rowCount) .withRowsCount(rowCount) .withReadColumns(TEST_COLUMNS) .withSession(session) - .isReadableByPageSource(new OrcPageSourceFactory(new OrcReaderOptions(), HDFS_FILE_SYSTEM_FACTORY, STATS, UTC)); + .isReadableByPageSource(fileSystemFactory -> new OrcPageSourceFactory(new OrcReaderOptions(), fileSystemFactory, STATS, UTC)); } @Test(dataProvider = "validRowAndFileSizePadding") @@ -437,25 +432,19 @@ public void testAvro(int rowCount, long fileSizePadding) .withColumns(getTestColumnsSupportedByAvro()) .withRowsCount(rowCount) .withFileSizePadding(fileSizePadding) - .withFileWriterFactory(new AvroFileWriterFactory(FILE_SYSTEM_FACTORY, TESTING_TYPE_MANAGER, new NodeVersion("test_version"))) - .isReadableByPageSource(new AvroPageSourceFactory(FILE_SYSTEM_FACTORY)); + .withFileWriterFactory(fileSystemFactory -> new AvroFileWriterFactory(fileSystemFactory, TESTING_TYPE_MANAGER, new NodeVersion("test_version"))) + .isReadableByPageSource(AvroPageSourceFactory::new); } @Test(dataProvider = "rowCount") public static void testAvroFileInSymlinkTable(int rowCount) throws Exception { - File file = File.createTempFile("trino_test", AVRO.name()); - //noinspection ResultOfMethodCallIgnored - file.delete(); - try { - FileSplit split = createTestFileHive(file.getAbsolutePath(), AVRO, HiveCompressionCodec.NONE, getTestColumnsSupportedByAvro(), rowCount); - testPageSourceFactory(new AvroPageSourceFactory(FILE_SYSTEM_FACTORY), split, AVRO, getTestColumnsSupportedByAvro(), SESSION, file.length(), rowCount); - } - finally { - //noinspection ResultOfMethodCallIgnored - file.delete(); - } + TrinoFileSystemFactory fileSystemFactory = new MemoryFileSystemFactory(); + Location location = Location.of("memory:///avro-test"); + createTestFileHive(fileSystemFactory, location, AVRO, HiveCompressionCodec.NONE, getTestColumnsSupportedByAvro(), rowCount); + long fileSize = fileSystemFactory.create(ConnectorIdentity.ofUser("test")).newInputFile(location).length(); + testPageSourceFactory(new AvroPageSourceFactory(fileSystemFactory), location, AVRO, getTestColumnsSupportedByAvro(), SESSION, fileSize, fileSize, rowCount); } private static List getTestColumnsSupportedByAvro() @@ -478,7 +467,7 @@ public void testParquetPageSource(int rowCount, long fileSizePadding) .withSession(PARQUET_SESSION) .withRowsCount(rowCount) .withFileSizePadding(fileSizePadding) - .isReadableByPageSource(PARQUET_PAGE_SOURCE_FACTORY); + .isReadableByPageSource(fileSystemFactory -> new ParquetPageSourceFactory(fileSystemFactory, STATS, new ParquetReaderConfig(), new HiveConfig())); } @Test(dataProvider = "validRowAndFileSizePadding") @@ -492,7 +481,7 @@ public void testParquetPageSourceGzip(int rowCount, long fileSizePadding) .withCompressionCodec(HiveCompressionCodec.GZIP) .withFileSizePadding(fileSizePadding) .withRowsCount(rowCount) - .isReadableByPageSource(PARQUET_PAGE_SOURCE_FACTORY); + .isReadableByPageSource(fileSystemFactory -> new ParquetPageSourceFactory(fileSystemFactory, STATS, new ParquetReaderConfig(), new HiveConfig())); } @Test(dataProvider = "rowCount") @@ -506,8 +495,8 @@ public void testParquetWriter(int rowCount) .withSession(session) .withColumns(testColumns) .withRowsCount(rowCount) - .withFileWriterFactory(new ParquetFileWriterFactory(HDFS_FILE_SYSTEM_FACTORY, new NodeVersion("test-version"), TESTING_TYPE_MANAGER, new HiveConfig(), STATS)) - .isReadableByPageSource(PARQUET_PAGE_SOURCE_FACTORY); + .withFileWriterFactory(fileSystemFactory -> new ParquetFileWriterFactory(fileSystemFactory, new NodeVersion("test-version"), TESTING_TYPE_MANAGER, new HiveConfig(), STATS)) + .isReadableByPageSource(fileSystemFactory -> new ParquetPageSourceFactory(fileSystemFactory, STATS, new ParquetReaderConfig(), new HiveConfig())); } @Test(dataProvider = "rowCount") @@ -530,7 +519,7 @@ public void testParquetPageSourceSchemaEvolution(int rowCount) .withReadColumns(readColumns) .withSession(PARQUET_SESSION) .withRowsCount(rowCount) - .isReadableByPageSource(PARQUET_PAGE_SOURCE_FACTORY); + .isReadableByPageSource(fileSystemFactory -> new ParquetPageSourceFactory(fileSystemFactory, STATS, new ParquetReaderConfig(), new HiveConfig())); // test the name-based access readColumns = Lists.reverse(writeColumns); @@ -538,7 +527,7 @@ public void testParquetPageSourceSchemaEvolution(int rowCount) .withWriteColumns(writeColumns) .withReadColumns(readColumns) .withSession(PARQUET_SESSION_USE_NAME) - .isReadableByPageSource(PARQUET_PAGE_SOURCE_FACTORY); + .isReadableByPageSource(fileSystemFactory -> new ParquetPageSourceFactory(fileSystemFactory, STATS, new ParquetReaderConfig(), new HiveConfig())); } private static List getTestColumnsSupportedByParquet() @@ -569,41 +558,41 @@ public void testTruncateVarcharColumn() assertThatFileFormat(RCTEXT) .withWriteColumns(ImmutableList.of(writeColumn)) .withReadColumns(ImmutableList.of(readColumn)) - .isReadableByPageSource(new RcFilePageSourceFactory(FILE_SYSTEM_FACTORY, new HiveConfig())); + .isReadableByPageSource(fileSystemFactory -> new RcFilePageSourceFactory(fileSystemFactory, new HiveConfig())); assertThatFileFormat(RCBINARY) .withWriteColumns(ImmutableList.of(writeColumn)) .withReadColumns(ImmutableList.of(readColumn)) - .isReadableByPageSource(new RcFilePageSourceFactory(FILE_SYSTEM_FACTORY, new HiveConfig())); + .isReadableByPageSource(fileSystemFactory -> new RcFilePageSourceFactory(fileSystemFactory, new HiveConfig())); assertThatFileFormat(ORC) .withWriteColumns(ImmutableList.of(writeColumn)) .withReadColumns(ImmutableList.of(readColumn)) - .isReadableByPageSource(new OrcPageSourceFactory(new OrcReaderOptions(), HDFS_FILE_SYSTEM_FACTORY, STATS, UTC)); + .isReadableByPageSource(fileSystemFactory -> new OrcPageSourceFactory(new OrcReaderOptions(), fileSystemFactory, STATS, UTC)); assertThatFileFormat(PARQUET) .withWriteColumns(ImmutableList.of(writeColumn)) .withReadColumns(ImmutableList.of(readColumn)) .withSession(PARQUET_SESSION) - .isReadableByPageSource(PARQUET_PAGE_SOURCE_FACTORY); + .isReadableByPageSource(fileSystemFactory -> new ParquetPageSourceFactory(fileSystemFactory, STATS, new ParquetReaderConfig(), new HiveConfig())); assertThatFileFormat(AVRO) .withWriteColumns(ImmutableList.of(writeColumn)) .withReadColumns(ImmutableList.of(readColumn)) - .withFileWriterFactory(new AvroFileWriterFactory(FILE_SYSTEM_FACTORY, TESTING_TYPE_MANAGER, new NodeVersion("test_version"))) - .isReadableByPageSource(new AvroPageSourceFactory(FILE_SYSTEM_FACTORY)); + .withFileWriterFactory(fileSystemFactory -> new AvroFileWriterFactory(fileSystemFactory, TESTING_TYPE_MANAGER, new NodeVersion("test_version"))) + .isReadableByPageSource(AvroPageSourceFactory::new); assertThatFileFormat(SEQUENCEFILE) .withWriteColumns(ImmutableList.of(writeColumn)) .withReadColumns(ImmutableList.of(readColumn)) - .withFileWriterFactory(new SimpleSequenceFileWriterFactory(HDFS_FILE_SYSTEM_FACTORY, TESTING_TYPE_MANAGER, new NodeVersion("test"))) - .isReadableByPageSource(new SimpleSequenceFilePageSourceFactory(HDFS_FILE_SYSTEM_FACTORY, new HiveConfig())); + .withFileWriterFactory(fileSystemFactory -> new SimpleSequenceFileWriterFactory(fileSystemFactory, TESTING_TYPE_MANAGER, new NodeVersion("test"))) + .isReadableByPageSource(fileSystemFactory -> new SimpleSequenceFilePageSourceFactory(fileSystemFactory, new HiveConfig())); assertThatFileFormat(TEXTFILE) .withWriteColumns(ImmutableList.of(writeColumn)) .withReadColumns(ImmutableList.of(readColumn)) - .withFileWriterFactory(new SimpleTextFileWriterFactory(HDFS_FILE_SYSTEM_FACTORY, TESTING_TYPE_MANAGER)) - .isReadableByPageSource(new SimpleTextFilePageSourceFactory(HDFS_FILE_SYSTEM_FACTORY, new HiveConfig())); + .withFileWriterFactory(fileSystemFactory -> new SimpleTextFileWriterFactory(fileSystemFactory, TESTING_TYPE_MANAGER)) + .isReadableByPageSource(fileSystemFactory -> new SimpleTextFilePageSourceFactory(fileSystemFactory, new HiveConfig())); } @Test(dataProvider = "rowCount") @@ -626,8 +615,8 @@ public void testAvroProjectedColumns(int rowCount) .withWriteColumns(writeColumns) .withReadColumns(readColumns) .withRowsCount(rowCount) - .withFileWriterFactory(new AvroFileWriterFactory(FILE_SYSTEM_FACTORY, TESTING_TYPE_MANAGER, new NodeVersion("test_version"))) - .isReadableByPageSource(new AvroPageSourceFactory(FILE_SYSTEM_FACTORY)); + .withFileWriterFactory(fileSystemFactory -> new AvroFileWriterFactory(fileSystemFactory, TESTING_TYPE_MANAGER, new NodeVersion("test_version"))) + .isReadableByPageSource(AvroPageSourceFactory::new); } @Test(dataProvider = "rowCount") @@ -651,14 +640,14 @@ public void testParquetProjectedColumns(int rowCount) .withReadColumns(readColumns) .withRowsCount(rowCount) .withSession(PARQUET_SESSION) - .isReadableByPageSource(PARQUET_PAGE_SOURCE_FACTORY); + .isReadableByPageSource(fileSystemFactory -> new ParquetPageSourceFactory(fileSystemFactory, STATS, new ParquetReaderConfig(), new HiveConfig())); assertThatFileFormat(PARQUET) .withWriteColumns(writeColumns) .withReadColumns(readColumns) .withRowsCount(rowCount) .withSession(PARQUET_SESSION_USE_NAME) - .isReadableByPageSource(PARQUET_PAGE_SOURCE_FACTORY); + .isReadableByPageSource(fileSystemFactory -> new ParquetPageSourceFactory(fileSystemFactory, STATS, new ParquetReaderConfig(), new HiveConfig())); } @Test(dataProvider = "rowCount") @@ -683,13 +672,13 @@ public void testORCProjectedColumns(int rowCount) .withReadColumns(readColumns) .withRowsCount(rowCount) .withSession(session) - .isReadableByPageSource(new OrcPageSourceFactory(new OrcReaderOptions(), HDFS_FILE_SYSTEM_FACTORY, STATS, UTC)); + .isReadableByPageSource(fileSystemFactory -> new OrcPageSourceFactory(new OrcReaderOptions(), fileSystemFactory, STATS, UTC)); assertThatFileFormat(ORC) .withWriteColumns(writeColumns) .withReadColumns(readColumns) .withRowsCount(rowCount) - .isReadableByPageSource(new OrcPageSourceFactory(new OrcReaderOptions(), HDFS_FILE_SYSTEM_FACTORY, STATS, UTC)); + .isReadableByPageSource(fileSystemFactory -> new OrcPageSourceFactory(new OrcReaderOptions(), fileSystemFactory, STATS, UTC)); } @Test(dataProvider = "rowCount") @@ -715,8 +704,8 @@ public void testSequenceFileProjectedColumns(int rowCount) .withWriteColumns(writeColumns) .withReadColumns(readColumns) .withRowsCount(rowCount) - .withFileWriterFactory(new SimpleSequenceFileWriterFactory(HDFS_FILE_SYSTEM_FACTORY, TESTING_TYPE_MANAGER, new NodeVersion("test"))) - .isReadableByPageSource(new SimpleSequenceFilePageSourceFactory(HDFS_FILE_SYSTEM_FACTORY, new HiveConfig())); + .withFileWriterFactory(fileSystemFactory -> new SimpleSequenceFileWriterFactory(fileSystemFactory, TESTING_TYPE_MANAGER, new NodeVersion("test"))) + .isReadableByPageSource(fileSystemFactory -> new SimpleSequenceFilePageSourceFactory(fileSystemFactory, new HiveConfig())); } @Test(dataProvider = "rowCount") @@ -743,8 +732,8 @@ public void testTextFileProjectedColumns(int rowCount) .withWriteColumns(writeColumns) .withReadColumns(readColumns) .withRowsCount(rowCount) - .withFileWriterFactory(new SimpleTextFileWriterFactory(HDFS_FILE_SYSTEM_FACTORY, TESTING_TYPE_MANAGER)) - .isReadableByPageSource(new SimpleTextFilePageSourceFactory(HDFS_FILE_SYSTEM_FACTORY, new HiveConfig())); + .withFileWriterFactory(fileSystemFactory -> new SimpleTextFileWriterFactory(fileSystemFactory, TESTING_TYPE_MANAGER)) + .isReadableByPageSource(fileSystemFactory -> new SimpleTextFilePageSourceFactory(fileSystemFactory, new HiveConfig())); } @Test(dataProvider = "rowCount") @@ -767,7 +756,7 @@ public void testRCTextProjectedColumnsPageSource(int rowCount) .withWriteColumns(writeColumns) .withReadColumns(readColumns) .withRowsCount(rowCount) - .isReadableByPageSource(new RcFilePageSourceFactory(FILE_SYSTEM_FACTORY, new HiveConfig())); + .isReadableByPageSource(fileSystemFactory -> new RcFilePageSourceFactory(fileSystemFactory, new HiveConfig())); } @Test(dataProvider = "rowCount") @@ -799,8 +788,8 @@ public void testRCBinaryProjectedColumns(int rowCount) .withRowsCount(rowCount) // generic Hive writer corrupts timestamps .withSkipGenericWriterTest() - .withFileWriterFactory(new RcFileFileWriterFactory(FILE_SYSTEM_FACTORY, TESTING_TYPE_MANAGER, new NodeVersion("test"), HIVE_STORAGE_TIME_ZONE)) - .isReadableByPageSource(new RcFilePageSourceFactory(FILE_SYSTEM_FACTORY, new HiveConfig())); + .withFileWriterFactory(fileSystemFactory -> new RcFileFileWriterFactory(fileSystemFactory, TESTING_TYPE_MANAGER, new NodeVersion("test"), HIVE_STORAGE_TIME_ZONE)) + .isReadableByPageSource(fileSystemFactory -> new RcFilePageSourceFactory(fileSystemFactory, new HiveConfig())); } @Test(dataProvider = "rowCount") @@ -829,8 +818,8 @@ public void testRCBinaryProjectedColumnsPageSource(int rowCount) .withRowsCount(rowCount) // generic Hive writer corrupts timestamps .withSkipGenericWriterTest() - .withFileWriterFactory(new RcFileFileWriterFactory(FILE_SYSTEM_FACTORY, TESTING_TYPE_MANAGER, new NodeVersion("test"), HIVE_STORAGE_TIME_ZONE)) - .isReadableByPageSource(new RcFilePageSourceFactory(FILE_SYSTEM_FACTORY, new HiveConfig())); + .withFileWriterFactory(fileSystemFactory -> new RcFileFileWriterFactory(fileSystemFactory, TESTING_TYPE_MANAGER, new NodeVersion("test"), HIVE_STORAGE_TIME_ZONE)) + .isReadableByPageSource(fileSystemFactory -> new RcFilePageSourceFactory(fileSystemFactory, new HiveConfig())); } @Test @@ -846,29 +835,30 @@ public void testFailForLongVarcharPartitionColumn() assertThatFileFormat(RCTEXT) .withColumns(columns) - .isFailingForPageSource(new RcFilePageSourceFactory(FILE_SYSTEM_FACTORY, new HiveConfig()), expectedErrorCode, expectedMessage); + .isFailingForPageSource(fileSystemFactory -> new RcFilePageSourceFactory(fileSystemFactory, new HiveConfig()), expectedErrorCode, expectedMessage); assertThatFileFormat(RCBINARY) .withColumns(columns) - .isFailingForPageSource(new RcFilePageSourceFactory(FILE_SYSTEM_FACTORY, new HiveConfig()), expectedErrorCode, expectedMessage); + .isFailingForPageSource(fileSystemFactory -> new RcFilePageSourceFactory(fileSystemFactory, new HiveConfig()), expectedErrorCode, expectedMessage); assertThatFileFormat(ORC) .withColumns(columns) - .isFailingForPageSource(new OrcPageSourceFactory(new OrcReaderOptions(), HDFS_FILE_SYSTEM_FACTORY, STATS, UTC), expectedErrorCode, expectedMessage); + .isFailingForPageSource(fileSystemFactory -> new OrcPageSourceFactory(new OrcReaderOptions(), fileSystemFactory, STATS, UTC), expectedErrorCode, expectedMessage); assertThatFileFormat(PARQUET) .withColumns(columns) .withSession(PARQUET_SESSION) - .isFailingForPageSource(PARQUET_PAGE_SOURCE_FACTORY, expectedErrorCode, expectedMessage); + .isFailingForPageSource(fileSystemFactory -> new ParquetPageSourceFactory(fileSystemFactory, STATS, new ParquetReaderConfig(), new HiveConfig()), expectedErrorCode, expectedMessage); } private static void testPageSourceFactory( HivePageSourceFactory sourceFactory, - FileSplit split, + Location location, HiveStorageFormat storageFormat, List testReadColumns, ConnectorSession session, long fileSize, + long paddedFileSize, int rowCount) throws IOException { @@ -910,19 +900,19 @@ private static void testPageSourceFactory( columnHandles, ImmutableList.of(), TableToPartitionMapping.empty(), - split.getPath().toString(), + location.toString(), OptionalInt.empty(), - fileSize, + paddedFileSize, Instant.now().toEpochMilli()); Optional pageSource = HivePageSourceProvider.createHivePageSource( ImmutableSet.of(sourceFactory), session, - Location.of(split.getPath().toString()), + location, OptionalInt.empty(), - split.getStart(), - split.getLength(), + 0, fileSize, + paddedFileSize, splitProperties, TupleDomain.all(), TESTING_TYPE_MANAGER, @@ -1120,6 +1110,8 @@ private static class FileFormatAssertion private HiveFileWriterFactory fileWriterFactory; private long fileSizePadding; + private final TrinoFileSystemFactory fileSystemFactory = new MemoryFileSystemFactory(); + private FileFormatAssertion(String formatName) { this.formatName = requireNonNull(formatName, "formatName is null"); @@ -1143,9 +1135,9 @@ public FileFormatAssertion withSkipGenericWriterTest() return this; } - public FileFormatAssertion withFileWriterFactory(HiveFileWriterFactory fileWriterFactory) + public FileFormatAssertion withFileWriterFactory(Function fileWriterFactoryBuilder) { - this.fileWriterFactory = requireNonNull(fileWriterFactory, "fileWriterFactory is null"); + this.fileWriterFactory = fileWriterFactoryBuilder.apply(fileSystemFactory); return this; } @@ -1186,15 +1178,16 @@ public FileFormatAssertion withFileSizePadding(long fileSizePadding) return this; } - public FileFormatAssertion isReadableByPageSource(HivePageSourceFactory pageSourceFactory) + public FileFormatAssertion isReadableByPageSource(Function pageSourceFactoryBuilder) throws Exception { - assertRead(pageSourceFactory); + assertRead(pageSourceFactoryBuilder.apply(fileSystemFactory)); return this; } - public void isFailingForPageSource(HivePageSourceFactory pageSourceFactory, HiveErrorCode expectedErrorCode, String expectedMessage) + public void isFailingForPageSource(Function pageSourceFactoryBuilder, HiveErrorCode expectedErrorCode, String expectedMessage) { + HivePageSourceFactory pageSourceFactory = pageSourceFactoryBuilder.apply(fileSystemFactory); assertTrinoExceptionThrownBy(() -> assertRead(pageSourceFactory)) .hasErrorCode(expectedErrorCode) .hasMessage(expectedMessage); @@ -1213,30 +1206,32 @@ private void assertRead(HivePageSourceFactory pageSourceFactory) .map(CompressionKind::getFileExtension) .orElse(""); - File file = File.createTempFile("trino_test", formatName + compressionSuffix); - file.delete(); + Location location = Location.of("memory:///%s-test%s".formatted(formatName, compressionSuffix)); + TrinoFileSystem fileSystem = fileSystemFactory.create(ConnectorIdentity.ofUser("test")); for (boolean testFileWriter : ImmutableList.of(false, true)) { try { - FileSplit split; if (testFileWriter) { if (fileWriterFactory == null) { continue; } - split = createTestFileTrino(file.getAbsolutePath(), storageFormat, compressionCodec, writeColumns, session, rowsCount, fileWriterFactory); + createTestFileTrino(location, storageFormat, compressionCodec, writeColumns, session, rowsCount, fileWriterFactory); } else { if (skipGenericWrite) { continue; } - split = createTestFileHive(file.getAbsolutePath(), storageFormat, compressionCodec, writeColumns, rowsCount); + createTestFileHive(fileSystemFactory, location, storageFormat, compressionCodec, writeColumns, rowsCount); } - long fileSize = split.getLength() + fileSizePadding; - testPageSourceFactory(pageSourceFactory, split, storageFormat, readColumns, session, fileSize, rowsCount); + long fileSize = fileSystem.newInputFile(location).length(); + testPageSourceFactory(pageSourceFactory, location, storageFormat, readColumns, session, fileSize, fileSize + fileSizePadding, rowsCount); } finally { - //noinspection ResultOfMethodCallIgnored - file.delete(); + try { + fileSystem.deleteFile(location); + } + catch (IOException ignored) { + } } } } From 2c534f8055ec715bf2228bd0f57d1740f06da497 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Sun, 5 Nov 2023 15:55:39 -0800 Subject: [PATCH 095/587] Simplify TestHiveFileFormats Use Trino types for test columns, since Trino types cover all test cases Inline abstract base slass, since this is the only use Inline test utilities used exclusively by the test --- .../hive/AbstractTestHiveFileFormats.java | 760 ------------ .../plugin/hive/TestHiveFileFormats.java | 1086 ++++++++++++++--- .../hive/util/CompressionConfigUtil.java | 58 - .../trino/plugin/hive/util/DecimalUtils.java | 55 - .../io/trino/plugin/hive/util/SerDeUtils.java | 293 ----- .../plugin/hive/util/TestSerDeUtils.java | 378 ------ 6 files changed, 935 insertions(+), 1695 deletions(-) delete mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileFormats.java delete mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/CompressionConfigUtil.java delete mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/DecimalUtils.java delete mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/SerDeUtils.java delete mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestSerDeUtils.java diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileFormats.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileFormats.java deleted file mode 100644 index dde596c6d7f6..000000000000 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileFormats.java +++ /dev/null @@ -1,760 +0,0 @@ -/* - * 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 io.trino.plugin.hive; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import io.airlift.slice.Slice; -import io.airlift.slice.Slices; -import io.trino.filesystem.Location; -import io.trino.filesystem.TrinoFileSystem; -import io.trino.filesystem.TrinoFileSystemFactory; -import io.trino.plugin.hive.metastore.StorageFormat; -import io.trino.spi.Page; -import io.trino.spi.PageBuilder; -import io.trino.spi.connector.ConnectorSession; -import io.trino.spi.security.ConnectorIdentity; -import io.trino.spi.type.ArrayType; -import io.trino.spi.type.DateType; -import io.trino.spi.type.DecimalType; -import io.trino.spi.type.RowType; -import io.trino.spi.type.TimestampType; -import io.trino.spi.type.Type; -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.fs.Path; -import org.apache.hadoop.hive.common.type.Date; -import org.apache.hadoop.hive.common.type.HiveChar; -import org.apache.hadoop.hive.common.type.HiveDecimal; -import org.apache.hadoop.hive.common.type.HiveVarchar; -import org.apache.hadoop.hive.common.type.Timestamp; -import org.apache.hadoop.hive.ql.exec.FileSinkOperator.RecordWriter; -import org.apache.hadoop.hive.ql.io.HiveOutputFormat; -import org.apache.hadoop.hive.serde2.Serializer; -import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector; -import org.apache.hadoop.hive.serde2.objectinspector.SettableStructObjectInspector; -import org.apache.hadoop.hive.serde2.objectinspector.StructField; -import org.apache.hadoop.hive.serde2.objectinspector.primitive.JavaHiveCharObjectInspector; -import org.apache.hadoop.hive.serde2.objectinspector.primitive.JavaHiveDecimalObjectInspector; -import org.apache.hadoop.hive.serde2.typeinfo.DecimalTypeInfo; -import org.apache.hadoop.io.Text; -import org.apache.hadoop.io.Writable; -import org.apache.hadoop.mapred.JobConf; -import org.joda.time.DateTime; -import org.joda.time.format.DateTimeFormat; - -import java.io.File; -import java.io.OutputStream; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.OptionalInt; -import java.util.Properties; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static io.trino.plugin.hive.HiveColumnHandle.ColumnType.PARTITION_KEY; -import static io.trino.plugin.hive.HiveColumnHandle.ColumnType.REGULAR; -import static io.trino.plugin.hive.HiveColumnHandle.createBaseColumn; -import static io.trino.plugin.hive.HiveColumnProjectionInfo.generatePartialName; -import static io.trino.plugin.hive.HivePartitionKey.HIVE_DEFAULT_DYNAMIC_PARTITION; -import static io.trino.plugin.hive.HiveTestUtils.mapType; -import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; -import static io.trino.plugin.hive.util.CompressionConfigUtil.configureCompression; -import static io.trino.plugin.hive.util.SerDeUtils.serializeObject; -import static io.trino.spi.type.BigintType.BIGINT; -import static io.trino.spi.type.BooleanType.BOOLEAN; -import static io.trino.spi.type.CharType.createCharType; -import static io.trino.spi.type.DoubleType.DOUBLE; -import static io.trino.spi.type.IntegerType.INTEGER; -import static io.trino.spi.type.RealType.REAL; -import static io.trino.spi.type.SmallintType.SMALLINT; -import static io.trino.spi.type.TinyintType.TINYINT; -import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; -import static io.trino.spi.type.VarcharType.createVarcharType; -import static io.trino.testing.StructuralTestUtil.arrayBlockOf; -import static io.trino.testing.StructuralTestUtil.decimalArrayBlockOf; -import static io.trino.testing.StructuralTestUtil.decimalSqlMapOf; -import static io.trino.testing.StructuralTestUtil.rowBlockOf; -import static io.trino.testing.StructuralTestUtil.sqlMapOf; -import static io.trino.type.DateTimes.MICROSECONDS_PER_MILLISECOND; -import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; -import static java.nio.file.Files.readAllBytes; -import static java.util.Arrays.fill; -import static java.util.Objects.requireNonNull; -import static java.util.stream.Collectors.toList; -import static org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory.getStandardListObjectInspector; -import static org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory.getStandardMapObjectInspector; -import static org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory.getStandardStructObjectInspector; -import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaBooleanObjectInspector; -import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaByteArrayObjectInspector; -import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaByteObjectInspector; -import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaDateObjectInspector; -import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaDoubleObjectInspector; -import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaFloatObjectInspector; -import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaHiveVarcharObjectInspector; -import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaIntObjectInspector; -import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaLongObjectInspector; -import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaShortObjectInspector; -import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaStringObjectInspector; -import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaTimestampObjectInspector; -import static org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory.getCharTypeInfo; -import static org.joda.time.DateTimeZone.UTC; - -public abstract class AbstractTestHiveFileFormats -{ - private static final long DATE_MILLIS_UTC = new DateTime(2011, 5, 6, 0, 0, UTC).getMillis(); - private static final long DATE_DAYS = TimeUnit.MILLISECONDS.toDays(DATE_MILLIS_UTC); - private static final String DATE_STRING = DateTimeFormat.forPattern("yyyy-MM-dd").withZoneUTC().print(DATE_MILLIS_UTC); - private static final Date HIVE_DATE = Date.ofEpochMilli(DATE_MILLIS_UTC); - - private static final DateTime TIMESTAMP = new DateTime(2011, 5, 6, 7, 8, 9, 123, UTC); - private static final long TIMESTAMP_MICROS = TIMESTAMP.getMillis() * MICROSECONDS_PER_MILLISECOND; - private static final String TIMESTAMP_STRING = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS").withZoneUTC().print(TIMESTAMP.getMillis()); - private static final Timestamp HIVE_TIMESTAMP = Timestamp.ofEpochMilli(TIMESTAMP.getMillis()); - - private static final String VARCHAR_MAX_LENGTH_STRING; - - static { - char[] varcharMaxLengthCharArray = new char[HiveVarchar.MAX_VARCHAR_LENGTH]; - fill(varcharMaxLengthCharArray, 'a'); - VARCHAR_MAX_LENGTH_STRING = new String(varcharMaxLengthCharArray); - } - - private static final JavaHiveDecimalObjectInspector DECIMAL_INSPECTOR_PRECISION_2 = - new JavaHiveDecimalObjectInspector(new DecimalTypeInfo(2, 1)); - private static final JavaHiveDecimalObjectInspector DECIMAL_INSPECTOR_PRECISION_4 = - new JavaHiveDecimalObjectInspector(new DecimalTypeInfo(4, 2)); - private static final JavaHiveDecimalObjectInspector DECIMAL_INSPECTOR_PRECISION_8 = - new JavaHiveDecimalObjectInspector(new DecimalTypeInfo(8, 4)); - private static final JavaHiveDecimalObjectInspector DECIMAL_INSPECTOR_PRECISION_17 = - new JavaHiveDecimalObjectInspector(new DecimalTypeInfo(17, 8)); - private static final JavaHiveDecimalObjectInspector DECIMAL_INSPECTOR_PRECISION_18 = - new JavaHiveDecimalObjectInspector(new DecimalTypeInfo(18, 8)); - private static final JavaHiveDecimalObjectInspector DECIMAL_INSPECTOR_PRECISION_38 = - new JavaHiveDecimalObjectInspector(new DecimalTypeInfo(38, 16)); - - private static final DecimalType DECIMAL_TYPE_PRECISION_2 = DecimalType.createDecimalType(2, 1); - private static final DecimalType DECIMAL_TYPE_PRECISION_4 = DecimalType.createDecimalType(4, 2); - private static final DecimalType DECIMAL_TYPE_PRECISION_8 = DecimalType.createDecimalType(8, 4); - private static final DecimalType DECIMAL_TYPE_PRECISION_17 = DecimalType.createDecimalType(17, 8); - private static final DecimalType DECIMAL_TYPE_PRECISION_18 = DecimalType.createDecimalType(18, 8); - private static final DecimalType DECIMAL_TYPE_PRECISION_38 = DecimalType.createDecimalType(38, 16); - - private static final HiveDecimal WRITE_DECIMAL_PRECISION_2 = HiveDecimal.create(new BigDecimal("-1.2")); - private static final HiveDecimal WRITE_DECIMAL_PRECISION_4 = HiveDecimal.create(new BigDecimal("12.3")); - private static final HiveDecimal WRITE_DECIMAL_PRECISION_8 = HiveDecimal.create(new BigDecimal("-1234.5678")); - private static final HiveDecimal WRITE_DECIMAL_PRECISION_17 = HiveDecimal.create(new BigDecimal("123456789.1234")); - private static final HiveDecimal WRITE_DECIMAL_PRECISION_18 = HiveDecimal.create(new BigDecimal("-1234567890.12345678")); - private static final HiveDecimal WRITE_DECIMAL_PRECISION_38 = HiveDecimal.create(new BigDecimal("1234567890123456789012.12345678")); - - private static final BigDecimal EXPECTED_DECIMAL_PRECISION_2 = new BigDecimal("-1.2"); - private static final BigDecimal EXPECTED_DECIMAL_PRECISION_4 = new BigDecimal("12.30"); - private static final BigDecimal EXPECTED_DECIMAL_PRECISION_8 = new BigDecimal("-1234.5678"); - private static final BigDecimal EXPECTED_DECIMAL_PRECISION_17 = new BigDecimal("123456789.12340000"); - private static final BigDecimal EXPECTED_DECIMAL_PRECISION_18 = new BigDecimal("-1234567890.12345678"); - private static final BigDecimal EXPECTED_DECIMAL_PRECISION_38 = new BigDecimal("1234567890123456789012.1234567800000000"); - - private static final JavaHiveCharObjectInspector CHAR_INSPECTOR_LENGTH_10 = - new JavaHiveCharObjectInspector(getCharTypeInfo(10)); - - // TODO: support null values and determine if timestamp and binary are allowed as partition keys - public static final List TEST_COLUMNS = ImmutableList.builder() - .add(new TestColumn("p_empty_string", javaStringObjectInspector, "", Slices.EMPTY_SLICE, true)) - .add(new TestColumn("p_string", javaStringObjectInspector, "test", Slices.utf8Slice("test"), true)) - .add(new TestColumn("p_empty_varchar", javaHiveVarcharObjectInspector, "", Slices.EMPTY_SLICE, true)) - .add(new TestColumn("p_varchar", javaHiveVarcharObjectInspector, "test", Slices.utf8Slice("test"), true)) - .add(new TestColumn("p_varchar_max_length", javaHiveVarcharObjectInspector, VARCHAR_MAX_LENGTH_STRING, Slices.utf8Slice(VARCHAR_MAX_LENGTH_STRING), true)) - .add(new TestColumn("p_char_10", CHAR_INSPECTOR_LENGTH_10, "test", Slices.utf8Slice("test"), true)) - .add(new TestColumn("p_tinyint", javaByteObjectInspector, "1", (byte) 1, true)) - .add(new TestColumn("p_smallint", javaShortObjectInspector, "2", (short) 2, true)) - .add(new TestColumn("p_int", javaIntObjectInspector, "3", 3, true)) - .add(new TestColumn("p_bigint", javaLongObjectInspector, "4", 4L, true)) - .add(new TestColumn("p_float", javaFloatObjectInspector, "5.1", 5.1f, true)) - .add(new TestColumn("p_double", javaDoubleObjectInspector, "6.2", 6.2, true)) - .add(new TestColumn("p_boolean", javaBooleanObjectInspector, "true", true, true)) - .add(new TestColumn("p_date", javaDateObjectInspector, DATE_STRING, DATE_DAYS, true)) - .add(new TestColumn("p_timestamp", javaTimestampObjectInspector, TIMESTAMP_STRING, TIMESTAMP_MICROS, true)) - .add(new TestColumn("p_decimal_precision_2", DECIMAL_INSPECTOR_PRECISION_2, WRITE_DECIMAL_PRECISION_2.toString(), EXPECTED_DECIMAL_PRECISION_2, true)) - .add(new TestColumn("p_decimal_precision_4", DECIMAL_INSPECTOR_PRECISION_4, WRITE_DECIMAL_PRECISION_4.toString(), EXPECTED_DECIMAL_PRECISION_4, true)) - .add(new TestColumn("p_decimal_precision_8", DECIMAL_INSPECTOR_PRECISION_8, WRITE_DECIMAL_PRECISION_8.toString(), EXPECTED_DECIMAL_PRECISION_8, true)) - .add(new TestColumn("p_decimal_precision_17", DECIMAL_INSPECTOR_PRECISION_17, WRITE_DECIMAL_PRECISION_17.toString(), EXPECTED_DECIMAL_PRECISION_17, true)) - .add(new TestColumn("p_decimal_precision_18", DECIMAL_INSPECTOR_PRECISION_18, WRITE_DECIMAL_PRECISION_18.toString(), EXPECTED_DECIMAL_PRECISION_18, true)) - .add(new TestColumn("p_decimal_precision_38", DECIMAL_INSPECTOR_PRECISION_38, WRITE_DECIMAL_PRECISION_38.toString() + "BD", EXPECTED_DECIMAL_PRECISION_38, true)) -// .add(new TestColumn("p_binary", javaByteArrayObjectInspector, "test2", Slices.utf8Slice("test2"), true)) - .add(new TestColumn("p_null_string", javaStringObjectInspector, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) - .add(new TestColumn("p_null_varchar", javaHiveVarcharObjectInspector, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) - .add(new TestColumn("p_null_char", CHAR_INSPECTOR_LENGTH_10, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) - .add(new TestColumn("p_null_tinyint", javaByteObjectInspector, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) - .add(new TestColumn("p_null_smallint", javaShortObjectInspector, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) - .add(new TestColumn("p_null_int", javaIntObjectInspector, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) - .add(new TestColumn("p_null_bigint", javaLongObjectInspector, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) - .add(new TestColumn("p_null_float", javaFloatObjectInspector, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) - .add(new TestColumn("p_null_double", javaDoubleObjectInspector, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) - .add(new TestColumn("p_null_boolean", javaBooleanObjectInspector, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) - .add(new TestColumn("p_null_date", javaDateObjectInspector, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) - .add(new TestColumn("p_null_timestamp", javaTimestampObjectInspector, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) - .add(new TestColumn("p_null_decimal_precision_2", DECIMAL_INSPECTOR_PRECISION_2, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) - .add(new TestColumn("p_null_decimal_precision_4", DECIMAL_INSPECTOR_PRECISION_4, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) - .add(new TestColumn("p_null_decimal_precision_8", DECIMAL_INSPECTOR_PRECISION_8, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) - .add(new TestColumn("p_null_decimal_precision_17", DECIMAL_INSPECTOR_PRECISION_17, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) - .add(new TestColumn("p_null_decimal_precision_18", DECIMAL_INSPECTOR_PRECISION_18, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) - .add(new TestColumn("p_null_decimal_precision_38", DECIMAL_INSPECTOR_PRECISION_38, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) - -// .add(new TestColumn("p_null_binary", javaByteArrayObjectInspector, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) - .add(new TestColumn("t_null_string", javaStringObjectInspector, null, null)) - .add(new TestColumn("t_null_varchar", javaHiveVarcharObjectInspector, null, null)) - .add(new TestColumn("t_null_char", CHAR_INSPECTOR_LENGTH_10, null, null)) - .add(new TestColumn("t_null_array_int", getStandardListObjectInspector(javaIntObjectInspector), null, null)) - .add(new TestColumn("t_null_decimal_precision_2", DECIMAL_INSPECTOR_PRECISION_2, null, null)) - .add(new TestColumn("t_null_decimal_precision_4", DECIMAL_INSPECTOR_PRECISION_4, null, null)) - .add(new TestColumn("t_null_decimal_precision_8", DECIMAL_INSPECTOR_PRECISION_8, null, null)) - .add(new TestColumn("t_null_decimal_precision_17", DECIMAL_INSPECTOR_PRECISION_17, null, null)) - .add(new TestColumn("t_null_decimal_precision_18", DECIMAL_INSPECTOR_PRECISION_18, null, null)) - .add(new TestColumn("t_null_decimal_precision_38", DECIMAL_INSPECTOR_PRECISION_38, null, null)) - .add(new TestColumn("t_empty_string", javaStringObjectInspector, "", Slices.EMPTY_SLICE)) - .add(new TestColumn("t_string", javaStringObjectInspector, "test", Slices.utf8Slice("test"))) - .add(new TestColumn("t_empty_varchar", javaHiveVarcharObjectInspector, new HiveVarchar("", HiveVarchar.MAX_VARCHAR_LENGTH), Slices.EMPTY_SLICE)) - .add(new TestColumn("t_varchar", javaHiveVarcharObjectInspector, new HiveVarchar("test", HiveVarchar.MAX_VARCHAR_LENGTH), Slices.utf8Slice("test"))) - .add(new TestColumn("t_varchar_max_length", javaHiveVarcharObjectInspector, new HiveVarchar(VARCHAR_MAX_LENGTH_STRING, HiveVarchar.MAX_VARCHAR_LENGTH), Slices.utf8Slice(VARCHAR_MAX_LENGTH_STRING))) - .add(new TestColumn("t_char", CHAR_INSPECTOR_LENGTH_10, "test", Slices.utf8Slice("test"))) - .add(new TestColumn("t_tinyint", javaByteObjectInspector, (byte) 1, (byte) 1)) - .add(new TestColumn("t_smallint", javaShortObjectInspector, (short) 2, (short) 2)) - .add(new TestColumn("t_int", javaIntObjectInspector, 3, 3)) - .add(new TestColumn("t_bigint", javaLongObjectInspector, 4L, 4L)) - .add(new TestColumn("t_float", javaFloatObjectInspector, 5.1f, 5.1f)) - .add(new TestColumn("t_double", javaDoubleObjectInspector, 6.2, 6.2)) - .add(new TestColumn("t_boolean_true", javaBooleanObjectInspector, true, true)) - .add(new TestColumn("t_boolean_false", javaBooleanObjectInspector, false, false)) - .add(new TestColumn("t_date", javaDateObjectInspector, HIVE_DATE, DATE_DAYS)) - .add(new TestColumn("t_timestamp", javaTimestampObjectInspector, HIVE_TIMESTAMP, TIMESTAMP_MICROS)) - .add(new TestColumn("t_decimal_precision_2", DECIMAL_INSPECTOR_PRECISION_2, WRITE_DECIMAL_PRECISION_2, EXPECTED_DECIMAL_PRECISION_2)) - .add(new TestColumn("t_decimal_precision_4", DECIMAL_INSPECTOR_PRECISION_4, WRITE_DECIMAL_PRECISION_4, EXPECTED_DECIMAL_PRECISION_4)) - .add(new TestColumn("t_decimal_precision_8", DECIMAL_INSPECTOR_PRECISION_8, WRITE_DECIMAL_PRECISION_8, EXPECTED_DECIMAL_PRECISION_8)) - .add(new TestColumn("t_decimal_precision_17", DECIMAL_INSPECTOR_PRECISION_17, WRITE_DECIMAL_PRECISION_17, EXPECTED_DECIMAL_PRECISION_17)) - .add(new TestColumn("t_decimal_precision_18", DECIMAL_INSPECTOR_PRECISION_18, WRITE_DECIMAL_PRECISION_18, EXPECTED_DECIMAL_PRECISION_18)) - .add(new TestColumn("t_decimal_precision_38", DECIMAL_INSPECTOR_PRECISION_38, WRITE_DECIMAL_PRECISION_38, EXPECTED_DECIMAL_PRECISION_38)) - .add(new TestColumn("t_binary", javaByteArrayObjectInspector, Slices.utf8Slice("test2").getBytes(), Slices.utf8Slice("test2"))) - .add(new TestColumn("t_map_string", - getStandardMapObjectInspector(javaStringObjectInspector, javaStringObjectInspector), - ImmutableMap.of("test", "test"), - sqlMapOf(createUnboundedVarcharType(), createUnboundedVarcharType(), "test", "test"))) - .add(new TestColumn("t_map_tinyint", - getStandardMapObjectInspector(javaByteObjectInspector, javaByteObjectInspector), - ImmutableMap.of((byte) 1, (byte) 1), - sqlMapOf(TINYINT, TINYINT, (byte) 1, (byte) 1))) - .add(new TestColumn("t_map_varchar", - getStandardMapObjectInspector(javaHiveVarcharObjectInspector, javaHiveVarcharObjectInspector), - ImmutableMap.of(new HiveVarchar("test", HiveVarchar.MAX_VARCHAR_LENGTH), new HiveVarchar("test", HiveVarchar.MAX_VARCHAR_LENGTH)), - sqlMapOf(createVarcharType(HiveVarchar.MAX_VARCHAR_LENGTH), createVarcharType(HiveVarchar.MAX_VARCHAR_LENGTH), "test", "test"))) - .add(new TestColumn("t_map_char", - getStandardMapObjectInspector(CHAR_INSPECTOR_LENGTH_10, CHAR_INSPECTOR_LENGTH_10), - ImmutableMap.of(new HiveChar("test", 10), new HiveChar("test", 10)), - sqlMapOf(createCharType(10), createCharType(10), "test", "test"))) - .add(new TestColumn("t_map_smallint", - getStandardMapObjectInspector(javaShortObjectInspector, javaShortObjectInspector), - ImmutableMap.of((short) 2, (short) 2), - sqlMapOf(SMALLINT, SMALLINT, (short) 2, (short) 2))) - .add(new TestColumn("t_map_null_key", - getStandardMapObjectInspector(javaLongObjectInspector, javaLongObjectInspector), - asMap(new Long[] {null, 2L}, new Long[] {0L, 3L}), - sqlMapOf(BIGINT, BIGINT, 2, 3))) - .add(new TestColumn("t_map_int", - getStandardMapObjectInspector(javaIntObjectInspector, javaIntObjectInspector), - ImmutableMap.of(3, 3), - sqlMapOf(INTEGER, INTEGER, 3, 3))) - .add(new TestColumn("t_map_bigint", - getStandardMapObjectInspector(javaLongObjectInspector, javaLongObjectInspector), - ImmutableMap.of(4L, 4L), - sqlMapOf(BIGINT, BIGINT, 4L, 4L))) - .add(new TestColumn("t_map_float", - getStandardMapObjectInspector(javaFloatObjectInspector, javaFloatObjectInspector), - ImmutableMap.of(5.0f, 5.0f), sqlMapOf(REAL, REAL, 5.0f, 5.0f))) - .add(new TestColumn("t_map_double", - getStandardMapObjectInspector(javaDoubleObjectInspector, javaDoubleObjectInspector), - ImmutableMap.of(6.0, 6.0), sqlMapOf(DOUBLE, DOUBLE, 6.0, 6.0))) - .add(new TestColumn("t_map_boolean", - getStandardMapObjectInspector(javaBooleanObjectInspector, javaBooleanObjectInspector), - ImmutableMap.of(true, true), - sqlMapOf(BOOLEAN, BOOLEAN, true, true))) - .add(new TestColumn("t_map_date", - getStandardMapObjectInspector(javaDateObjectInspector, javaDateObjectInspector), - ImmutableMap.of(HIVE_DATE, HIVE_DATE), - sqlMapOf(DateType.DATE, DateType.DATE, DATE_DAYS, DATE_DAYS))) - .add(new TestColumn("t_map_timestamp", - getStandardMapObjectInspector(javaTimestampObjectInspector, javaTimestampObjectInspector), - ImmutableMap.of(HIVE_TIMESTAMP, HIVE_TIMESTAMP), - sqlMapOf(TimestampType.TIMESTAMP_MILLIS, TimestampType.TIMESTAMP_MILLIS, TIMESTAMP_MICROS, TIMESTAMP_MICROS))) - .add(new TestColumn("t_map_decimal_precision_2", - getStandardMapObjectInspector(DECIMAL_INSPECTOR_PRECISION_2, DECIMAL_INSPECTOR_PRECISION_2), - ImmutableMap.of(WRITE_DECIMAL_PRECISION_2, WRITE_DECIMAL_PRECISION_2), - decimalSqlMapOf(DECIMAL_TYPE_PRECISION_2, EXPECTED_DECIMAL_PRECISION_2))) - .add(new TestColumn("t_map_decimal_precision_4", - getStandardMapObjectInspector(DECIMAL_INSPECTOR_PRECISION_4, DECIMAL_INSPECTOR_PRECISION_4), - ImmutableMap.of(WRITE_DECIMAL_PRECISION_4, WRITE_DECIMAL_PRECISION_4), - decimalSqlMapOf(DECIMAL_TYPE_PRECISION_4, EXPECTED_DECIMAL_PRECISION_4))) - .add(new TestColumn("t_map_decimal_precision_8", - getStandardMapObjectInspector(DECIMAL_INSPECTOR_PRECISION_8, DECIMAL_INSPECTOR_PRECISION_8), - ImmutableMap.of(WRITE_DECIMAL_PRECISION_8, WRITE_DECIMAL_PRECISION_8), - decimalSqlMapOf(DECIMAL_TYPE_PRECISION_8, EXPECTED_DECIMAL_PRECISION_8))) - .add(new TestColumn("t_map_decimal_precision_17", - getStandardMapObjectInspector(DECIMAL_INSPECTOR_PRECISION_17, DECIMAL_INSPECTOR_PRECISION_17), - ImmutableMap.of(WRITE_DECIMAL_PRECISION_17, WRITE_DECIMAL_PRECISION_17), - decimalSqlMapOf(DECIMAL_TYPE_PRECISION_17, EXPECTED_DECIMAL_PRECISION_17))) - .add(new TestColumn("t_map_decimal_precision_18", - getStandardMapObjectInspector(DECIMAL_INSPECTOR_PRECISION_18, DECIMAL_INSPECTOR_PRECISION_18), - ImmutableMap.of(WRITE_DECIMAL_PRECISION_18, WRITE_DECIMAL_PRECISION_18), - decimalSqlMapOf(DECIMAL_TYPE_PRECISION_18, EXPECTED_DECIMAL_PRECISION_18))) - .add(new TestColumn("t_map_decimal_precision_38", - getStandardMapObjectInspector(DECIMAL_INSPECTOR_PRECISION_38, DECIMAL_INSPECTOR_PRECISION_38), - ImmutableMap.of(WRITE_DECIMAL_PRECISION_38, WRITE_DECIMAL_PRECISION_38), - decimalSqlMapOf(DECIMAL_TYPE_PRECISION_38, EXPECTED_DECIMAL_PRECISION_38))) - .add(new TestColumn("t_array_empty", getStandardListObjectInspector(javaStringObjectInspector), ImmutableList.of(), arrayBlockOf(createUnboundedVarcharType()))) - .add(new TestColumn("t_array_string", getStandardListObjectInspector(javaStringObjectInspector), ImmutableList.of("test"), arrayBlockOf(createUnboundedVarcharType(), "test"))) - .add(new TestColumn("t_array_tinyint", getStandardListObjectInspector(javaByteObjectInspector), ImmutableList.of((byte) 1), arrayBlockOf(TINYINT, (byte) 1))) - .add(new TestColumn("t_array_smallint", getStandardListObjectInspector(javaShortObjectInspector), ImmutableList.of((short) 2), arrayBlockOf(SMALLINT, (short) 2))) - .add(new TestColumn("t_array_int", getStandardListObjectInspector(javaIntObjectInspector), ImmutableList.of(3), arrayBlockOf(INTEGER, 3))) - .add(new TestColumn("t_array_bigint", getStandardListObjectInspector(javaLongObjectInspector), ImmutableList.of(4L), arrayBlockOf(BIGINT, 4L))) - .add(new TestColumn("t_array_float", getStandardListObjectInspector(javaFloatObjectInspector), ImmutableList.of(5.0f), arrayBlockOf(REAL, 5.0f))) - .add(new TestColumn("t_array_double", getStandardListObjectInspector(javaDoubleObjectInspector), ImmutableList.of(6.0), arrayBlockOf(DOUBLE, 6.0))) - .add(new TestColumn("t_array_boolean", getStandardListObjectInspector(javaBooleanObjectInspector), ImmutableList.of(true), arrayBlockOf(BOOLEAN, true))) - .add(new TestColumn( - "t_array_varchar", - getStandardListObjectInspector(javaHiveVarcharObjectInspector), - ImmutableList.of(new HiveVarchar("test", HiveVarchar.MAX_VARCHAR_LENGTH)), - arrayBlockOf(createVarcharType(HiveVarchar.MAX_VARCHAR_LENGTH), "test"))) - .add(new TestColumn( - "t_array_char", - getStandardListObjectInspector(CHAR_INSPECTOR_LENGTH_10), - ImmutableList.of(new HiveChar("test", 10)), - arrayBlockOf(createCharType(10), "test"))) - .add(new TestColumn("t_array_date", - getStandardListObjectInspector(javaDateObjectInspector), - ImmutableList.of(HIVE_DATE), - arrayBlockOf(DateType.DATE, DATE_DAYS))) - .add(new TestColumn("t_array_timestamp", - getStandardListObjectInspector(javaTimestampObjectInspector), - ImmutableList.of(HIVE_TIMESTAMP), - arrayBlockOf(TimestampType.TIMESTAMP_MILLIS, TIMESTAMP_MICROS))) - .add(new TestColumn("t_array_decimal_precision_2", - getStandardListObjectInspector(DECIMAL_INSPECTOR_PRECISION_2), - ImmutableList.of(WRITE_DECIMAL_PRECISION_2), - decimalArrayBlockOf(DECIMAL_TYPE_PRECISION_2, EXPECTED_DECIMAL_PRECISION_2))) - .add(new TestColumn("t_array_decimal_precision_4", - getStandardListObjectInspector(DECIMAL_INSPECTOR_PRECISION_4), - ImmutableList.of(WRITE_DECIMAL_PRECISION_4), - decimalArrayBlockOf(DECIMAL_TYPE_PRECISION_4, EXPECTED_DECIMAL_PRECISION_4))) - .add(new TestColumn("t_array_decimal_precision_8", - getStandardListObjectInspector(DECIMAL_INSPECTOR_PRECISION_8), - ImmutableList.of(WRITE_DECIMAL_PRECISION_8), - decimalArrayBlockOf(DECIMAL_TYPE_PRECISION_8, EXPECTED_DECIMAL_PRECISION_8))) - .add(new TestColumn("t_array_decimal_precision_17", - getStandardListObjectInspector(DECIMAL_INSPECTOR_PRECISION_17), - ImmutableList.of(WRITE_DECIMAL_PRECISION_17), - decimalArrayBlockOf(DECIMAL_TYPE_PRECISION_17, EXPECTED_DECIMAL_PRECISION_17))) - .add(new TestColumn("t_array_decimal_precision_18", - getStandardListObjectInspector(DECIMAL_INSPECTOR_PRECISION_18), - ImmutableList.of(WRITE_DECIMAL_PRECISION_18), - decimalArrayBlockOf(DECIMAL_TYPE_PRECISION_18, EXPECTED_DECIMAL_PRECISION_18))) - .add(new TestColumn("t_array_decimal_precision_38", - getStandardListObjectInspector(DECIMAL_INSPECTOR_PRECISION_38), - ImmutableList.of(WRITE_DECIMAL_PRECISION_38), - decimalArrayBlockOf(DECIMAL_TYPE_PRECISION_38, EXPECTED_DECIMAL_PRECISION_38))) - .add(new TestColumn("t_struct_bigint", - getStandardStructObjectInspector(ImmutableList.of("s_bigint"), ImmutableList.of(javaLongObjectInspector)), - new Long[] {1L}, - rowBlockOf(ImmutableList.of(BIGINT), 1))) - .add(new TestColumn("t_complex", - getStandardMapObjectInspector( - javaStringObjectInspector, - getStandardListObjectInspector( - getStandardStructObjectInspector( - ImmutableList.of("s_int"), - ImmutableList.of(javaIntObjectInspector)))), - ImmutableMap.of("test", ImmutableList.of(new Integer[] {1})), - sqlMapOf(createUnboundedVarcharType(), new ArrayType(RowType.anonymous(ImmutableList.of(INTEGER))), - "test", arrayBlockOf(RowType.anonymous(ImmutableList.of(INTEGER)), rowBlockOf(ImmutableList.of(INTEGER), 1L))))) - .add(new TestColumn("t_map_null_key_complex_value", - getStandardMapObjectInspector( - javaStringObjectInspector, - getStandardMapObjectInspector(javaLongObjectInspector, javaBooleanObjectInspector)), - asMap(new String[] {null, "k"}, new ImmutableMap[] {ImmutableMap.of(15L, true), ImmutableMap.of(16L, false)}), - sqlMapOf(createUnboundedVarcharType(), mapType(BIGINT, BOOLEAN), "k", sqlMapOf(BIGINT, BOOLEAN, 16L, false)))) - .add(new TestColumn("t_map_null_key_complex_key_value", - getStandardMapObjectInspector( - getStandardListObjectInspector(javaStringObjectInspector), - getStandardMapObjectInspector(javaLongObjectInspector, javaBooleanObjectInspector)), - asMap(new ImmutableList[] {null, ImmutableList.of("k", "ka")}, new ImmutableMap[] {ImmutableMap.of(15L, true), ImmutableMap.of(16L, false)}), - sqlMapOf(new ArrayType(createUnboundedVarcharType()), mapType(BIGINT, BOOLEAN), arrayBlockOf(createUnboundedVarcharType(), "k", "ka"), sqlMapOf(BIGINT, BOOLEAN, 16L, false)))) - .add(new TestColumn("t_struct_nested", getStandardStructObjectInspector(ImmutableList.of("struct_field"), - ImmutableList.of(getStandardListObjectInspector(javaStringObjectInspector))), ImmutableList.of(ImmutableList.of("1", "2", "3")), rowBlockOf(ImmutableList.of(new ArrayType(createUnboundedVarcharType())), arrayBlockOf(createUnboundedVarcharType(), "1", "2", "3")))) - .add(new TestColumn("t_struct_null", getStandardStructObjectInspector(ImmutableList.of("struct_field_null", "struct_field_null2"), - ImmutableList.of(javaStringObjectInspector, javaStringObjectInspector)), Arrays.asList(null, null), rowBlockOf(ImmutableList.of(createUnboundedVarcharType(), createUnboundedVarcharType()), null, null))) - .add(new TestColumn("t_struct_non_nulls_after_nulls", getStandardStructObjectInspector(ImmutableList.of("struct_non_nulls_after_nulls1", "struct_non_nulls_after_nulls2"), - ImmutableList.of(javaIntObjectInspector, javaStringObjectInspector)), Arrays.asList(null, "some string"), rowBlockOf(ImmutableList.of(INTEGER, createUnboundedVarcharType()), null, "some string"))) - .add(new TestColumn("t_nested_struct_non_nulls_after_nulls", - getStandardStructObjectInspector( - ImmutableList.of("struct_field1", "struct_field2", "strict_field3"), - ImmutableList.of( - javaIntObjectInspector, - javaStringObjectInspector, - getStandardStructObjectInspector( - ImmutableList.of("nested_struct_field1", "nested_struct_field2"), - ImmutableList.of(javaIntObjectInspector, javaStringObjectInspector)))), - Arrays.asList(null, "some string", Arrays.asList(null, "nested_string2")), - rowBlockOf( - ImmutableList.of( - INTEGER, - createUnboundedVarcharType(), - RowType.anonymous(ImmutableList.of(INTEGER, createUnboundedVarcharType()))), - null, "some string", rowBlockOf(ImmutableList.of(INTEGER, createUnboundedVarcharType()), null, "nested_string2")))) - .add(new TestColumn("t_map_null_value", - getStandardMapObjectInspector(javaStringObjectInspector, javaStringObjectInspector), - asMap(new String[] {"k1", "k2", "k3"}, new String[] {"v1", null, "v3"}), - sqlMapOf(createUnboundedVarcharType(), createUnboundedVarcharType(), new String[] {"k1", "k2", "k3"}, new String[] {"v1", null, "v3"}))) - .add(new TestColumn("t_array_string_starting_with_nulls", getStandardListObjectInspector(javaStringObjectInspector), Arrays.asList(null, "test"), arrayBlockOf(createUnboundedVarcharType(), null, "test"))) - .add(new TestColumn("t_array_string_with_nulls_in_between", getStandardListObjectInspector(javaStringObjectInspector), Arrays.asList("test-1", null, "test-2"), arrayBlockOf(createUnboundedVarcharType(), "test-1", null, "test-2"))) - .add(new TestColumn("t_array_string_ending_with_nulls", getStandardListObjectInspector(javaStringObjectInspector), Arrays.asList("test", null), arrayBlockOf(createUnboundedVarcharType(), "test", null))) - .add(new TestColumn("t_array_string_all_nulls", getStandardListObjectInspector(javaStringObjectInspector), Arrays.asList(null, null, null), arrayBlockOf(createUnboundedVarcharType(), null, null, null))) - .build(); - - private static Map asMap(K[] keys, V[] values) - { - checkArgument(keys.length == values.length, "array lengths don't match"); - Map map = new HashMap<>(); - int len = keys.length; - for (int i = 0; i < len; i++) { - map.put(keys[i], values[i]); - } - return map; - } - - protected static List getColumnHandles(List testColumns) - { - List columns = new ArrayList<>(); - Map hiveColumnIndexes = new HashMap<>(); - - int nextHiveColumnIndex = 0; - for (TestColumn testColumn : testColumns) { - int columnIndex; - if (testColumn.isPartitionKey()) { - columnIndex = -1; - } - else { - if (hiveColumnIndexes.get(testColumn.getBaseName()) != null) { - columnIndex = hiveColumnIndexes.get(testColumn.getBaseName()); - } - else { - columnIndex = nextHiveColumnIndex++; - hiveColumnIndexes.put(testColumn.getBaseName(), columnIndex); - } - } - - if (testColumn.getDereferenceNames().isEmpty()) { - HiveType hiveType = HiveType.valueOf(testColumn.getObjectInspector().getTypeName()); - columns.add(createBaseColumn(testColumn.getName(), columnIndex, hiveType, TESTING_TYPE_MANAGER.getType(hiveType.getTypeSignature()), testColumn.isPartitionKey() ? PARTITION_KEY : REGULAR, Optional.empty())); - } - else { - HiveType baseHiveType = HiveType.valueOf(testColumn.getBaseObjectInspector().getTypeName()); - HiveType partialHiveType = baseHiveType.getHiveTypeForDereferences(testColumn.getDereferenceIndices()).orElseThrow(); - HiveColumnHandle hiveColumnHandle = new HiveColumnHandle( - testColumn.getBaseName(), - columnIndex, - baseHiveType, - TESTING_TYPE_MANAGER.getType(baseHiveType.getTypeSignature()), - Optional.of(new HiveColumnProjectionInfo( - testColumn.getDereferenceIndices(), - testColumn.getDereferenceNames(), - partialHiveType, - TESTING_TYPE_MANAGER.getType(partialHiveType.getTypeSignature()))), - testColumn.isPartitionKey() ? PARTITION_KEY : REGULAR, - Optional.empty()); - columns.add(hiveColumnHandle); - } - } - return columns; - } - - public static void createTestFileTrino( - Location location, - HiveStorageFormat storageFormat, - HiveCompressionCodec compressionCodec, - List testColumns, - ConnectorSession session, - int numRows, - HiveFileWriterFactory fileWriterFactory) - { - // filter out partition keys, which are not written to the file - testColumns = testColumns.stream() - .filter(column -> !column.isPartitionKey()) - .collect(toImmutableList()); - - List types = testColumns.stream() - .map(TestColumn::getType) - .map(HiveType::valueOf) - .map(type -> TESTING_TYPE_MANAGER.getType(type.getTypeSignature())) - .collect(toList()); - - PageBuilder pageBuilder = new PageBuilder(types); - - for (int rowNumber = 0; rowNumber < numRows; rowNumber++) { - pageBuilder.declarePosition(); - for (int columnNumber = 0; columnNumber < testColumns.size(); columnNumber++) { - serializeObject( - types.get(columnNumber), - pageBuilder.getBlockBuilder(columnNumber), - testColumns.get(columnNumber).getWriteValue(), - testColumns.get(columnNumber).getObjectInspector(), - false); - } - } - Page page = pageBuilder.build(); - - Properties tableProperties = new Properties(); - tableProperties.setProperty( - "columns", - testColumns.stream() - .map(TestColumn::getName) - .collect(Collectors.joining(","))); - - tableProperties.setProperty( - "columns.types", - testColumns.stream() - .map(TestColumn::getType) - .collect(Collectors.joining(","))); - - Optional fileWriter = fileWriterFactory.createFileWriter( - location, - testColumns.stream() - .map(TestColumn::getName) - .collect(toList()), - StorageFormat.fromHiveStorageFormat(storageFormat), - compressionCodec, - tableProperties, - session, - OptionalInt.empty(), - NO_ACID_TRANSACTION, - false, - WriterKind.INSERT); - - FileWriter hiveFileWriter = fileWriter.orElseThrow(() -> new IllegalArgumentException("fileWriterFactory")); - hiveFileWriter.appendRows(page); - hiveFileWriter.commit(); - } - - public static void createTestFileHive( - TrinoFileSystemFactory fileSystemFactory, - Location location, - HiveStorageFormat storageFormat, - HiveCompressionCodec compressionCodec, - List testColumns, - int numRows) - throws Exception - { - HiveOutputFormat outputFormat = newInstance(storageFormat.getOutputFormat(), HiveOutputFormat.class); - Serializer serializer = newInstance(storageFormat.getSerde(), Serializer.class); - - // filter out partition keys, which are not written to the file - testColumns = testColumns.stream() - .filter(column -> !column.isPartitionKey()) - .collect(toImmutableList()); - - Properties tableProperties = new Properties(); - tableProperties.setProperty( - "columns", - testColumns.stream() - .map(TestColumn::getName) - .collect(Collectors.joining(","))); - tableProperties.setProperty( - "columns.types", - testColumns.stream() - .map(TestColumn::getType) - .collect(Collectors.joining(","))); - serializer.initialize(new Configuration(false), tableProperties); - - JobConf jobConf = new JobConf(false); - configureCompression(jobConf, compressionCodec); - - File file = File.createTempFile("trino_test", "data"); - file.delete(); - try { - RecordWriter recordWriter = outputFormat.getHiveRecordWriter( - jobConf, - new Path(file.getAbsolutePath()), - Text.class, - compressionCodec != HiveCompressionCodec.NONE, - tableProperties, - () -> {}); - - serializer.initialize(new Configuration(false), tableProperties); - - SettableStructObjectInspector objectInspector = getStandardStructObjectInspector( - testColumns.stream() - .map(TestColumn::getName) - .collect(toImmutableList()), - testColumns.stream() - .map(TestColumn::getObjectInspector) - .collect(toImmutableList())); - - Object row = objectInspector.create(); - - List fields = ImmutableList.copyOf(objectInspector.getAllStructFieldRefs()); - - for (int rowNumber = 0; rowNumber < numRows; rowNumber++) { - for (int i = 0; i < testColumns.size(); i++) { - Object writeValue = testColumns.get(i).getWriteValue(); - if (writeValue instanceof Slice) { - writeValue = ((Slice) writeValue).getBytes(); - } - objectInspector.setStructFieldData(row, fields.get(i), writeValue); - } - - Writable record = serializer.serialize(row, objectInspector); - recordWriter.write(record); - } - recordWriter.close(false); - - // copy the file data to the TrinoFileSystem - TrinoFileSystem fileSystem = fileSystemFactory.create(ConnectorIdentity.ofUser("test")); - try (OutputStream outputStream = fileSystem.newOutputFile(location).create()) { - outputStream.write(readAllBytes(file.toPath())); - } - } - finally { - file.delete(); - } - } - - private static T newInstance(String className, Class superType) - throws ReflectiveOperationException - { - return HiveStorageFormat.class.getClassLoader().loadClass(className).asSubclass(superType).getConstructor().newInstance(); - } - - public static final class TestColumn - { - private final String baseName; - private final ObjectInspector baseObjectInspector; - private final List dereferenceNames; - private final List dereferenceIndices; - private final String name; - private final ObjectInspector objectInspector; - private final Object writeValue; - private final Object expectedValue; - private final boolean partitionKey; - - public TestColumn(String name, ObjectInspector objectInspector, Object writeValue, Object expectedValue) - { - this(name, objectInspector, writeValue, expectedValue, false); - } - - public TestColumn(String name, ObjectInspector objectInspector, Object writeValue, Object expectedValue, boolean partitionKey) - { - this(name, objectInspector, ImmutableList.of(), ImmutableList.of(), objectInspector, writeValue, expectedValue, partitionKey); - } - - public TestColumn( - String baseName, - ObjectInspector baseObjectInspector, - List dereferenceNames, - List dereferenceIndices, - ObjectInspector objectInspector, - Object writeValue, - Object expectedValue, - boolean partitionKey) - { - this.baseName = requireNonNull(baseName, "baseName is null"); - this.baseObjectInspector = requireNonNull(baseObjectInspector, "baseObjectInspector is null"); - this.dereferenceNames = requireNonNull(dereferenceNames, "dereferenceNames is null"); - this.dereferenceIndices = requireNonNull(dereferenceIndices, "dereferenceIndices is null"); - checkArgument(dereferenceIndices.size() == dereferenceNames.size(), "dereferenceIndices and dereferenceNames should have the same size"); - this.name = baseName + generatePartialName(dereferenceNames); - this.objectInspector = requireNonNull(objectInspector, "objectInspector is null"); - this.writeValue = writeValue; - this.expectedValue = expectedValue; - this.partitionKey = partitionKey; - checkArgument(dereferenceNames.isEmpty() || !partitionKey, "partial column cannot be a partition key"); - } - - public String getName() - { - return name; - } - - public String getBaseName() - { - return baseName; - } - - public List getDereferenceNames() - { - return dereferenceNames; - } - - public List getDereferenceIndices() - { - return dereferenceIndices; - } - - public String getType() - { - return objectInspector.getTypeName(); - } - - public ObjectInspector getBaseObjectInspector() - { - return baseObjectInspector; - } - - public ObjectInspector getObjectInspector() - { - return objectInspector; - } - - public Object getWriteValue() - { - return writeValue; - } - - public Object getExpectedValue() - { - return expectedValue; - } - - public boolean isPartitionKey() - { - return partitionKey; - } - - @Override - public String toString() - { - StringBuilder sb = new StringBuilder("TestColumn{"); - sb.append("baseName='").append(baseName).append("'"); - sb.append("dereferenceNames=").append("[").append(String.join(",", dereferenceNames)).append("]"); - sb.append("name=").append(name); - sb.append(", objectInspector=").append(objectInspector); - sb.append(", writeValue=").append(writeValue); - sb.append(", expectedValue=").append(expectedValue); - sb.append(", partitionKey=").append(partitionKey); - sb.append('}'); - return sb.toString(); - } - } -} diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java index 13af486d250a..f84a458cd221 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java @@ -13,17 +13,22 @@ */ package io.trino.plugin.hive; +import com.google.common.base.VerifyException; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import io.airlift.slice.Slice; +import io.airlift.slice.Slices; import io.trino.filesystem.Location; import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.filesystem.memory.MemoryFileSystemFactory; import io.trino.hive.formats.compression.CompressionKind; +import io.trino.hive.orc.OrcConf; import io.trino.orc.OrcReaderOptions; import io.trino.orc.OrcWriterOptions; +import io.trino.plugin.base.type.DecodedTimestamp; import io.trino.plugin.hive.avro.AvroFileWriterFactory; import io.trino.plugin.hive.avro.AvroPageSourceFactory; import io.trino.plugin.hive.line.CsvFileWriterFactory; @@ -36,6 +41,7 @@ import io.trino.plugin.hive.line.SimpleSequenceFileWriterFactory; import io.trino.plugin.hive.line.SimpleTextFilePageSourceFactory; import io.trino.plugin.hive.line.SimpleTextFileWriterFactory; +import io.trino.plugin.hive.metastore.StorageFormat; import io.trino.plugin.hive.orc.OrcFileWriterFactory; import io.trino.plugin.hive.orc.OrcPageSourceFactory; import io.trino.plugin.hive.orc.OrcReaderConfig; @@ -45,52 +51,93 @@ import io.trino.plugin.hive.parquet.ParquetReaderConfig; import io.trino.plugin.hive.parquet.ParquetWriterConfig; import io.trino.plugin.hive.rcfile.RcFilePageSourceFactory; +import io.trino.spi.Page; +import io.trino.spi.PageBuilder; +import io.trino.spi.block.ArrayBlockBuilder; import io.trino.spi.block.BlockBuilder; +import io.trino.spi.block.MapBlockBuilder; +import io.trino.spi.block.RowBlockBuilder; import io.trino.spi.connector.ConnectorPageSource; import io.trino.spi.connector.ConnectorSession; import io.trino.spi.predicate.TupleDomain; import io.trino.spi.security.ConnectorIdentity; +import io.trino.spi.type.ArrayType; import io.trino.spi.type.CharType; +import io.trino.spi.type.DecimalType; +import io.trino.spi.type.Int128; +import io.trino.spi.type.Int128Math; +import io.trino.spi.type.MapType; +import io.trino.spi.type.RowType; import io.trino.spi.type.SqlDate; -import io.trino.spi.type.SqlDecimal; import io.trino.spi.type.SqlTimestamp; import io.trino.spi.type.SqlVarbinary; +import io.trino.spi.type.TimestampType; import io.trino.spi.type.Type; +import io.trino.spi.type.TypeOperators; +import io.trino.spi.type.VarcharType; import io.trino.testing.MaterializedResult; import io.trino.testing.MaterializedRow; import io.trino.testing.TestingConnectorSession; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.hive.common.type.Date; +import org.apache.hadoop.hive.common.type.HiveChar; +import org.apache.hadoop.hive.common.type.HiveDecimal; import org.apache.hadoop.hive.common.type.HiveVarchar; -import org.apache.hadoop.hive.serde2.objectinspector.ListObjectInspector; -import org.apache.hadoop.hive.serde2.objectinspector.MapObjectInspector; +import org.apache.hadoop.hive.common.type.Timestamp; +import org.apache.hadoop.hive.ql.exec.FileSinkOperator; +import org.apache.hadoop.hive.ql.io.HiveOutputFormat; +import org.apache.hadoop.hive.serde2.Serializer; +import org.apache.hadoop.hive.serde2.io.HiveDecimalWritable; import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector; -import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector; -import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector.PrimitiveCategory; +import org.apache.hadoop.hive.serde2.objectinspector.SettableStructObjectInspector; import org.apache.hadoop.hive.serde2.objectinspector.StructField; -import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector; +import org.apache.hadoop.hive.serde2.typeinfo.CharTypeInfo; +import org.apache.hadoop.hive.serde2.typeinfo.DecimalTypeInfo; import org.apache.hadoop.hive.serde2.typeinfo.VarcharTypeInfo; +import org.apache.hadoop.io.Text; +import org.apache.hadoop.io.Writable; +import org.apache.hadoop.mapred.JobConf; +import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; +import org.apache.parquet.hadoop.ParquetOutputFormat; +import org.joda.time.DateTime; import org.joda.time.DateTimeZone; +import org.joda.time.format.DateTimeFormat; import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import java.io.File; import java.io.IOException; +import java.io.OutputStream; import java.math.BigDecimal; import java.time.Instant; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Optional; import java.util.OptionalInt; import java.util.Properties; import java.util.Set; import java.util.TimeZone; import java.util.function.Function; +import java.util.stream.Collectors; -import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Verify.verify; import static com.google.common.collect.ImmutableList.toImmutableList; import static io.airlift.slice.Slices.utf8Slice; +import static io.trino.plugin.base.type.TrinoTimestampEncoderFactory.createTimestampEncoder; +import static io.trino.plugin.hive.HiveColumnHandle.ColumnType.PARTITION_KEY; +import static io.trino.plugin.hive.HiveColumnHandle.ColumnType.REGULAR; +import static io.trino.plugin.hive.HiveColumnHandle.createBaseColumn; import static io.trino.plugin.hive.HivePageSourceProvider.ColumnMapping.buildColumnMappings; +import static io.trino.plugin.hive.HivePartitionKey.HIVE_DEFAULT_DYNAMIC_PARTITION; import static io.trino.plugin.hive.HiveStorageFormat.AVRO; import static io.trino.plugin.hive.HiveStorageFormat.CSV; import static io.trino.plugin.hive.HiveStorageFormat.JSON; @@ -103,26 +150,65 @@ import static io.trino.plugin.hive.HiveStorageFormat.TEXTFILE; import static io.trino.plugin.hive.HiveTestUtils.SESSION; import static io.trino.plugin.hive.HiveTestUtils.getHiveSession; -import static io.trino.plugin.hive.HiveTestUtils.getTypes; -import static io.trino.plugin.hive.HiveTimestampPrecision.DEFAULT_PRECISION; +import static io.trino.plugin.hive.HiveTestUtils.mapType; import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; import static io.trino.plugin.hive.util.SerdeConstants.SERIALIZATION_LIB; +import static io.trino.spi.type.BigintType.BIGINT; +import static io.trino.spi.type.BooleanType.BOOLEAN; +import static io.trino.spi.type.CharType.createCharType; import static io.trino.spi.type.Chars.padSpaces; +import static io.trino.spi.type.Chars.truncateToLengthAndTrimSpaces; +import static io.trino.spi.type.DateType.DATE; +import static io.trino.spi.type.DoubleType.DOUBLE; +import static io.trino.spi.type.IntegerType.INTEGER; +import static io.trino.spi.type.RealType.REAL; +import static io.trino.spi.type.RowType.field; +import static io.trino.spi.type.RowType.rowType; +import static io.trino.spi.type.SmallintType.SMALLINT; +import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; +import static io.trino.spi.type.Timestamps.round; +import static io.trino.spi.type.TinyintType.TINYINT; +import static io.trino.spi.type.VarbinaryType.VARBINARY; +import static io.trino.spi.type.VarcharType.VARCHAR; +import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; +import static io.trino.spi.type.VarcharType.createVarcharType; import static io.trino.testing.DateTimeTestingUtils.sqlTimestampOf; import static io.trino.testing.MaterializedResult.materializeSourceDataStream; +import static io.trino.testing.StructuralTestUtil.arrayBlockOf; +import static io.trino.testing.StructuralTestUtil.decimalArrayBlockOf; +import static io.trino.testing.StructuralTestUtil.decimalSqlMapOf; import static io.trino.testing.StructuralTestUtil.rowBlockOf; +import static io.trino.testing.StructuralTestUtil.sqlMapOf; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static io.trino.type.DateTimes.MICROSECONDS_PER_MILLISECOND; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.lang.Math.floorDiv; +import static java.lang.Math.toIntExact; import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; +import static java.nio.file.Files.readAllBytes; +import static java.util.Arrays.fill; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; +import static org.apache.hadoop.hive.common.type.HiveVarchar.MAX_VARCHAR_LENGTH; +import static org.apache.hadoop.hive.conf.HiveConf.ConfVars.COMPRESSRESULT; import static org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.FILE_INPUT_FORMAT; +import static org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory.getStandardListObjectInspector; +import static org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory.getStandardMapObjectInspector; import static org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory.getStandardStructObjectInspector; import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.getPrimitiveJavaObjectInspector; +import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaBooleanObjectInspector; +import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaByteArrayObjectInspector; +import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaByteObjectInspector; +import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaDateObjectInspector; +import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaDoubleObjectInspector; +import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaFloatObjectInspector; +import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaIntObjectInspector; +import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaLongObjectInspector; +import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaShortObjectInspector; import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaStringObjectInspector; +import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaTimestampObjectInspector; +import static org.apache.hadoop.io.SequenceFile.CompressionType.BLOCK; import static org.joda.time.DateTimeZone.UTC; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; @@ -133,7 +219,6 @@ // For example org.apache.parquet.column.values.factory.DefaultValuesWriterFactory#DEFAULT_V1_WRITER_FACTORY is shared mutable state. @Test(singleThreaded = true) public final class TestHiveFileFormats - extends AbstractTestHiveFileFormats { private static final DateTimeZone HIVE_STORAGE_TIME_ZONE = DateTimeZone.forID("America/Bahia_Banderas"); private static final double EPSILON = 0.001; @@ -203,7 +288,7 @@ public void testCsvFile(int rowCount, long fileSizePadding) { List testColumns = TEST_COLUMNS.stream() // CSV tables only support Hive string columns. Notice that CSV does not allow to store null, it uses an empty string instead. - .filter(column -> column.isPartitionKey() || ("string".equals(column.getType()) && !column.getName().contains("_null_"))) + .filter(column -> column.partitionKey() || (column.type() instanceof VarcharType varcharType && varcharType.isUnbounded() && !column.name().contains("_null_"))) .collect(toImmutableList()); assertTrue(testColumns.size() > 5); @@ -222,8 +307,8 @@ public void testCsvFileWithNullAndValue() { assertThatFileFormat(CSV) .withColumns(ImmutableList.of( - new TestColumn("t_null_string", javaStringObjectInspector, null, utf8Slice("")), // null was converted to empty string! - new TestColumn("t_string", javaStringObjectInspector, "test", utf8Slice("test")))) + new TestColumn("t_null_string", VARCHAR, null, utf8Slice("")), // null was converted to empty string! + new TestColumn("t_string", VARCHAR, "test", utf8Slice("test")))) .withRowsCount(2) .withFileWriterFactory(fileSystemFactory -> new CsvFileWriterFactory(fileSystemFactory, TESTING_TYPE_MANAGER)) .isReadableByPageSource(fileSystemFactory -> new CsvPageSourceFactory(fileSystemFactory, new HiveConfig())); @@ -235,20 +320,20 @@ public void testJson(int rowCount, long fileSizePadding) { List testColumns = TEST_COLUMNS.stream() // binary is not supported - .filter(column -> !column.getName().equals("t_binary")) + .filter(column -> !column.name().equals("t_binary")) // non-string map keys are not supported - .filter(column -> !column.getName().equals("t_map_tinyint")) - .filter(column -> !column.getName().equals("t_map_smallint")) - .filter(column -> !column.getName().equals("t_map_int")) - .filter(column -> !column.getName().equals("t_map_bigint")) - .filter(column -> !column.getName().equals("t_map_float")) - .filter(column -> !column.getName().equals("t_map_double")) + .filter(column -> !column.name().equals("t_map_tinyint")) + .filter(column -> !column.name().equals("t_map_smallint")) + .filter(column -> !column.name().equals("t_map_int")) + .filter(column -> !column.name().equals("t_map_bigint")) + .filter(column -> !column.name().equals("t_map_float")) + .filter(column -> !column.name().equals("t_map_double")) // null map keys are not supported .filter(TestHiveFileFormats::withoutNullMapKeyTests) // decimal(38) is broken or not supported - .filter(column -> !column.getName().equals("t_decimal_precision_38")) - .filter(column -> !column.getName().equals("t_map_decimal_precision_38")) - .filter(column -> !column.getName().equals("t_array_decimal_precision_38")) + .filter(column -> !column.name().equals("t_decimal_precision_38")) + .filter(column -> !column.name().equals("t_map_decimal_precision_38")) + .filter(column -> !column.name().equals("t_array_decimal_precision_38")) .collect(toList()); assertThatFileFormat(JSON) @@ -312,7 +397,7 @@ public void testRcBinaryPageSource(int rowCount) // RCBinary does not support complex types as the key of a map and interprets empty VARCHAR as null // Hive binary writers are broken for timestamps List testColumns = TEST_COLUMNS.stream() - .filter(testColumn -> !testColumn.getName().equals("t_empty_varchar")) + .filter(testColumn -> !testColumn.name().equals("t_empty_varchar")) .filter(TestHiveFileFormats::withoutTimestamps) .collect(toList()); @@ -328,7 +413,7 @@ public void testRcBinaryOptimizedWriter(int rowCount) { List testColumns = TEST_COLUMNS.stream() // RCBinary interprets empty VARCHAR as nulls - .filter(testColumn -> !testColumn.getName().equals("t_empty_varchar")) + .filter(testColumn -> !testColumn.name().equals("t_empty_varchar")) // t_map_null_key_* must be disabled because Trino cannot produce maps with null keys so the writer will throw .filter(TestHiveFileFormats::withoutNullMapKeyTests) .collect(toList()); @@ -412,7 +497,7 @@ public void testOrcUseColumnNameLowerCaseConversion(int rowCount) throws Exception { List testColumnsUpperCase = TEST_COLUMNS.stream() - .map(testColumn -> new TestColumn(testColumn.getName().toUpperCase(Locale.ENGLISH), testColumn.getObjectInspector(), testColumn.getWriteValue(), testColumn.getExpectedValue(), testColumn.isPartitionKey())) + .map(testColumn -> testColumn.withName(testColumn.name().toUpperCase(Locale.ENGLISH))) .collect(toList()); ConnectorSession session = getHiveSession(new HiveConfig(), new OrcReaderConfig().setUseColumnNames(true)); @@ -451,9 +536,9 @@ private static List getTestColumnsSupportedByAvro() { // Avro only supports String for Map keys, and doesn't support smallint or tinyint. return TEST_COLUMNS.stream() - .filter(column -> !column.getName().startsWith("t_map_") || column.getName().equals("t_map_string")) - .filter(column -> !column.getName().endsWith("_smallint")) - .filter(column -> !column.getName().endsWith("_tinyint")) + .filter(column -> !column.name().startsWith("t_map_") || column.name().equals("t_map_string")) + .filter(column -> !column.name().endsWith("_smallint")) + .filter(column -> !column.name().endsWith("_tinyint")) .collect(toList()); } @@ -507,12 +592,7 @@ public void testParquetPageSourceSchemaEvolution(int rowCount) // test the index-based access List readColumns = writeColumns.stream() - .map(column -> new TestColumn( - column.getName() + "_new", - column.getObjectInspector(), - column.getWriteValue(), - column.getExpectedValue(), - column.isPartitionKey())) + .map(column -> column.withName(column.name() + "_new")) .collect(toList()); assertThatFileFormat(PARQUET) .withWriteColumns(writeColumns) @@ -534,17 +614,13 @@ private static List getTestColumnsSupportedByParquet() { // Write of complex hive data to Parquet is broken // TODO: empty arrays or maps with null keys don't seem to work - // Parquet does not support DATE // Hive binary writers are broken for timestamps return TEST_COLUMNS.stream() .filter(TestHiveFileFormats::withoutTimestamps) .filter(TestHiveFileFormats::withoutNullMapKeyTests) - .filter(column -> !column.getName().equals("t_null_array_int")) - .filter(column -> !column.getName().equals("t_array_empty")) - .filter(column -> column.isPartitionKey() || ( - !hasType(column.getObjectInspector(), PrimitiveCategory.DATE)) && - !hasType(column.getObjectInspector(), PrimitiveCategory.SHORT) && - !hasType(column.getObjectInspector(), PrimitiveCategory.BYTE)) + .filter(column -> !column.name().equals("t_null_array_int")) + .filter(column -> !column.name().equals("t_array_empty")) + .filter(column -> column.partitionKey() || !hasType(column.type(), TINYINT)) .collect(toList()); } @@ -552,8 +628,8 @@ private static List getTestColumnsSupportedByParquet() public void testTruncateVarcharColumn() throws Exception { - TestColumn writeColumn = new TestColumn("varchar_column", getPrimitiveJavaObjectInspector(new VarcharTypeInfo(4)), new HiveVarchar("test", 4), utf8Slice("test")); - TestColumn readColumn = new TestColumn("varchar_column", getPrimitiveJavaObjectInspector(new VarcharTypeInfo(3)), new HiveVarchar("tes", 3), utf8Slice("tes")); + TestColumn writeColumn = new TestColumn("varchar_column", createVarcharType(4), new HiveVarchar("test", 4), utf8Slice("test")); + TestColumn readColumn = new TestColumn("varchar_column", createVarcharType(3), new HiveVarchar("tes", 3), utf8Slice("tes")); assertThatFileFormat(RCTEXT) .withWriteColumns(ImmutableList.of(writeColumn)) @@ -686,7 +762,7 @@ public void testSequenceFileProjectedColumns(int rowCount) throws Exception { List supportedColumns = TEST_COLUMNS.stream() - .filter(column -> !column.getName().equals("t_map_null_key_complex_key_value")) + .filter(column -> !column.name().equals("t_map_null_key_complex_key_value")) .collect(toList()); List regularColumns = getRegularColumns(supportedColumns); @@ -766,7 +842,7 @@ public void testRCBinaryProjectedColumns(int rowCount) // RCBinary does not support complex types as the key of a map and interprets empty VARCHAR as null List supportedColumns = TEST_COLUMNS.stream() .filter(testColumn -> { - String name = testColumn.getName(); + String name = testColumn.name(); return !name.equals("t_map_null_key_complex_key_value") && !name.equals("t_empty_varchar"); }) .collect(toList()); @@ -798,7 +874,7 @@ public void testRCBinaryProjectedColumnsPageSource(int rowCount) { // RCBinary does not support complex types as the key of a map and interprets empty VARCHAR as null List supportedColumns = TEST_COLUMNS.stream() - .filter(testColumn -> !testColumn.getName().equals("t_empty_varchar")) + .filter(testColumn -> !testColumn.name().equals("t_empty_varchar")) .collect(toList()); List regularColumns = getRegularColumns(supportedColumns); @@ -825,8 +901,8 @@ public void testRCBinaryProjectedColumnsPageSource(int rowCount) @Test public void testFailForLongVarcharPartitionColumn() { - TestColumn partitionColumn = new TestColumn("partition_column", getPrimitiveJavaObjectInspector(new VarcharTypeInfo(3)), "test", utf8Slice("tes"), true); - TestColumn varcharColumn = new TestColumn("varchar_column", getPrimitiveJavaObjectInspector(new VarcharTypeInfo(3)), new HiveVarchar("tes", 3), utf8Slice("tes")); + TestColumn partitionColumn = new TestColumn("partition_column", createVarcharType(3), "test", utf8Slice("tes"), true); + TestColumn varcharColumn = new TestColumn("varchar_column", createVarcharType(3), new HiveVarchar("tes", 3), utf8Slice("tes")); List columns = ImmutableList.of(partitionColumn, varcharColumn); @@ -872,11 +948,11 @@ private static void testPageSourceFactory( Set baseColumnNames = new HashSet<>(); for (TestColumn testReadColumn : testReadColumns) { - String name = testReadColumn.getBaseName(); - if (!baseColumnNames.contains(name) && !testReadColumn.isPartitionKey()) { + String name = testReadColumn.baseName(); + if (!baseColumnNames.contains(name) && !testReadColumn.partitionKey()) { baseColumnNames.add(name); splitPropertiesColumnNames.add(name); - splitPropertiesColumnTypes.add(testReadColumn.getBaseObjectInspector().getTypeName()); + splitPropertiesColumnTypes.add(HiveType.toHiveType(testReadColumn.baseType()).toString()); } } @@ -884,20 +960,18 @@ private static void testPageSourceFactory( splitProperties.setProperty("columns.types", String.join(",", splitPropertiesColumnTypes.build())); List partitionKeys = testReadColumns.stream() - .filter(TestColumn::isPartitionKey) - .map(input -> new HivePartitionKey(input.getName(), (String) input.getWriteValue())) + .filter(TestColumn::partitionKey) + .map(input -> new HivePartitionKey(input.name(), (String) input.writeValue())) .collect(toList()); String partitionName = String.join("/", partitionKeys.stream() .map(partitionKey -> format("%s=%s", partitionKey.getName(), partitionKey.getValue())) .collect(toImmutableList())); - List columnHandles = getColumnHandles(testReadColumns); - List columnMappings = buildColumnMappings( partitionName, partitionKeys, - columnHandles, + getColumnHandles(testReadColumns), ImmutableList.of(), TableToPartitionMapping.empty(), location.toString(), @@ -905,7 +979,7 @@ private static void testPageSourceFactory( paddedFileSize, Instant.now().toEpochMilli()); - Optional pageSource = HivePageSourceProvider.createHivePageSource( + ConnectorPageSource pageSource = HivePageSourceProvider.createHivePageSource( ImmutableSet.of(sourceFactory), session, location, @@ -921,112 +995,101 @@ private static void testPageSourceFactory( Optional.empty(), false, NO_ACID_TRANSACTION, - columnMappings); + columnMappings) + .orElseThrow(); + + checkPageSource(pageSource, testReadColumns, rowCount); + } + + private static List getColumnHandles(List testColumns) + { + List columns = new ArrayList<>(testColumns.size()); - assertTrue(pageSource.isPresent()); + int nextHiveColumnIndex = 0; + for (TestColumn testColumn : testColumns) { + int columnIndex; + if (testColumn.partitionKey()) { + columnIndex = -1; + } + else { + columnIndex = nextHiveColumnIndex++; + } - checkPageSource(pageSource.get(), testReadColumns, getTypes(columnHandles), rowCount); + columns.add(testColumn.toHiveColumnHandle(columnIndex)); + } + return columns; } - private static void checkPageSource(ConnectorPageSource pageSource, List testColumns, List types, int rowCount) + private static void checkPageSource(ConnectorPageSource pageSource, List testColumns, int rowCount) throws IOException { try (pageSource) { - MaterializedResult result = materializeSourceDataStream(SESSION, pageSource, types); + MaterializedResult result = materializeSourceDataStream(SESSION, pageSource, testColumns.stream().map(TestColumn::type).collect(toImmutableList())); assertEquals(result.getMaterializedRows().size(), rowCount); for (MaterializedRow row : result) { for (int i = 0, testColumnsSize = testColumns.size(); i < testColumnsSize; i++) { TestColumn testColumn = testColumns.get(i); - Type type = types.get(i); + Type type = testColumn.type(); Object actualValue = row.getField(i); - Object expectedValue = testColumn.getExpectedValue(); + Object expectedValue = testColumn.expectedValue(); if (expectedValue instanceof Slice) { expectedValue = ((Slice) expectedValue).toStringUtf8(); } if (actualValue == null || expectedValue == null) { - assertEquals(actualValue, expectedValue, "Wrong value for column " + testColumn.getName()); + assertEquals(actualValue, expectedValue, "Wrong value for column " + testColumn.name()); } - else if (testColumn.getObjectInspector().getTypeName().equals("float")) { - assertEquals((float) actualValue, (float) expectedValue, EPSILON, "Wrong value for column " + testColumn.getName()); + else if (type == REAL) { + assertEquals((float) actualValue, (float) expectedValue, EPSILON, "Wrong value for column " + testColumn.name()); } - else if (testColumn.getObjectInspector().getTypeName().equals("double")) { - assertEquals((double) actualValue, (double) expectedValue, EPSILON, "Wrong value for column " + testColumn.getName()); + else if (type == DOUBLE) { + assertEquals((double) actualValue, (double) expectedValue, EPSILON, "Wrong value for column " + testColumn.name()); } - else if (testColumn.getObjectInspector().getTypeName().equals("date")) { - SqlDate expectedDate = new SqlDate(((Long) expectedValue).intValue()); - assertEquals(actualValue, expectedDate, "Wrong value for column " + testColumn.getName()); + else if (type == DATE) { + SqlDate expectedDate = new SqlDate(toIntExact((long) expectedValue)); + assertEquals(actualValue, expectedDate, "Wrong value for column " + testColumn.name()); } - else if (testColumn.getObjectInspector().getTypeName().equals("int") || - testColumn.getObjectInspector().getTypeName().equals("smallint") || - testColumn.getObjectInspector().getTypeName().equals("tinyint")) { + else if (type == BIGINT || type == INTEGER || type == SMALLINT || type == TINYINT || type == BOOLEAN) { assertEquals(actualValue, expectedValue); } - else if (testColumn.getObjectInspector().getTypeName().equals("timestamp")) { - SqlTimestamp expectedTimestamp = sqlTimestampOf(3, floorDiv((Long) expectedValue, MICROSECONDS_PER_MILLISECOND)); - assertEquals(actualValue, expectedTimestamp, "Wrong value for column " + testColumn.getName()); + else if (type instanceof TimestampType timestampType && timestampType.getPrecision() == 3) { + // the expected value is in micros to simplify the array, map, and row types + SqlTimestamp expectedTimestamp = sqlTimestampOf(3, floorDiv((long) expectedValue, MICROSECONDS_PER_MILLISECOND)); + assertEquals(actualValue, expectedTimestamp, "Wrong value for column " + testColumn.name()); } - else if (testColumn.getObjectInspector().getTypeName().startsWith("char")) { - assertEquals(actualValue, padSpaces((String) expectedValue, (CharType) type), "Wrong value for column " + testColumn.getName()); + else if (type instanceof CharType) { + assertEquals(actualValue, padSpaces((String) expectedValue, (CharType) type), "Wrong value for column " + testColumn.name()); } - else if (testColumn.getObjectInspector().getCategory() == ObjectInspector.Category.PRIMITIVE) { - if (actualValue instanceof Slice) { - actualValue = ((Slice) actualValue).toStringUtf8(); - } - if (actualValue instanceof SqlVarbinary) { - actualValue = new String(((SqlVarbinary) actualValue).getBytes(), UTF_8); - } - - if (actualValue instanceof SqlDecimal) { - actualValue = new BigDecimal(actualValue.toString()); - } - assertEquals(actualValue, expectedValue, "Wrong value for column " + testColumn.getName()); + else if (type instanceof VarcharType) { + assertEquals(actualValue, expectedValue, "Wrong value for column " + testColumn.name()); + } + else if (type == VARBINARY) { + assertEquals(new String(((SqlVarbinary) actualValue).getBytes(), UTF_8), expectedValue, "Wrong value for column " + testColumn.name()); + } + else if (type instanceof DecimalType) { + assertEquals(new BigDecimal(actualValue.toString()), expectedValue, "Wrong value for column " + testColumn.name()); } else { BlockBuilder builder = type.createBlockBuilder(null, 1); type.writeObject(builder, expectedValue); expectedValue = type.getObjectValue(SESSION, builder.build(), 0); - assertEquals(actualValue, expectedValue, "Wrong value for column " + testColumn.getName()); + assertEquals(actualValue, expectedValue, "Wrong value for column " + testColumn.name()); } } } } } - private static boolean hasType(ObjectInspector objectInspector, PrimitiveCategory... types) + private static boolean hasType(Type actualType, Type testType) { - if (objectInspector instanceof PrimitiveObjectInspector primitiveInspector) { - PrimitiveCategory primitiveCategory = primitiveInspector.getPrimitiveCategory(); - for (PrimitiveCategory type : types) { - if (primitiveCategory == type) { - return true; - } - } - return false; - } - if (objectInspector instanceof ListObjectInspector listInspector) { - return hasType(listInspector.getListElementObjectInspector(), types); - } - if (objectInspector instanceof MapObjectInspector mapInspector) { - return hasType(mapInspector.getMapKeyObjectInspector(), types) || - hasType(mapInspector.getMapValueObjectInspector(), types); - } - if (objectInspector instanceof StructObjectInspector structObjectInspector) { - for (StructField field : structObjectInspector.getAllStructFieldRefs()) { - if (hasType(field.getFieldObjectInspector(), types)) { - return true; - } - } - return false; - } - throw new IllegalArgumentException("Unknown object inspector type " + objectInspector); + return actualType.equals(testType) || actualType.getTypeParameters().stream().anyMatch(type -> hasType(type, testType)); } private static boolean withoutNullMapKeyTests(TestColumn testColumn) { - String name = testColumn.getName(); + String name = testColumn.name(); return !name.equals("t_map_null_key") && !name.equals("t_map_null_key_complex_key_value") && !name.equals("t_map_null_key_complex_value"); @@ -1034,7 +1097,7 @@ private static boolean withoutNullMapKeyTests(TestColumn testColumn) private static boolean withoutTimestamps(TestColumn testColumn) { - String name = testColumn.getName(); + String name = testColumn.name(); return !name.equals("t_timestamp") && !name.equals("t_map_timestamp") && !name.equals("t_array_timestamp"); @@ -1052,48 +1115,36 @@ private static HiveConfig createParquetHiveConfig(boolean useParquetColumnNames) .setUseParquetColumnNames(useParquetColumnNames); } - private static void generateProjectedColumns(List childColumns, ImmutableList.Builder testFullColumnsBuilder, ImmutableList.Builder testDereferencedColumnsBuilder) + private static void generateProjectedColumns(List testColumns, ImmutableList.Builder testFullColumnsBuilder, ImmutableList.Builder testDereferencedColumnsBuilder) { - for (int i = 0; i < childColumns.size(); i++) { - TestColumn childColumn = childColumns.get(i); - checkState(childColumn.getDereferenceIndices().isEmpty()); - ObjectInspector newObjectInspector = getStandardStructObjectInspector( - ImmutableList.of("field0"), - ImmutableList.of(childColumn.getObjectInspector())); - - HiveType hiveType = (HiveType.valueOf(childColumn.getObjectInspector().getTypeName())); - Type trinoType = TESTING_TYPE_MANAGER.getType(hiveType.getTypeSignature(DEFAULT_PRECISION)); - - List list = new ArrayList<>(); - list.add(childColumn.getWriteValue()); - - TestColumn newProjectedColumn = new TestColumn( - "new_col" + i, newObjectInspector, - ImmutableList.of("field0"), - ImmutableList.of(0), - childColumn.getObjectInspector(), - childColumn.getWriteValue(), - childColumn.getExpectedValue(), - false); - - TestColumn newFullColumn = new TestColumn("new_col" + i, newObjectInspector, list, rowBlockOf(ImmutableList.of(trinoType), childColumn.getExpectedValue())); - - testFullColumnsBuilder.add(newFullColumn); - testDereferencedColumnsBuilder.add(newProjectedColumn); + // wrapper every test column in a ROW type with one field, and then dereference that field + for (int i = 0; i < testColumns.size(); i++) { + TestColumn testColumn = testColumns.get(i); + verify(!testColumn.dereference()); + + TestColumn baseColumn = new TestColumn( + "new_col" + i, + rowType(field("field0", testColumn.type())), + Collections.singletonList(testColumn.writeValue()), + rowBlockOf(ImmutableList.of(testColumn.type()), testColumn.expectedValue())); + + TestColumn projectedColumn = baseColumn.withDereferenceFirstField(testColumn.writeValue(), testColumn.expectedValue()); + testFullColumnsBuilder.add(baseColumn); + testDereferencedColumnsBuilder.add(projectedColumn); } } private static List getRegularColumns(List columns) { return columns.stream() - .filter(column -> !column.isPartitionKey()) + .filter(column -> !column.partitionKey()) .collect(toImmutableList()); } private static List getPartitionColumns(List columns) { return columns.stream() - .filter(TestColumn::isPartitionKey) + .filter(TestColumn::partitionKey) .collect(toImmutableList()); } @@ -1236,4 +1287,737 @@ private void assertRead(HivePageSourceFactory pageSourceFactory) } } } + + private static void createTestFileTrino( + Location location, + HiveStorageFormat storageFormat, + HiveCompressionCodec compressionCodec, + List testColumns, + ConnectorSession session, + int numRows, + HiveFileWriterFactory fileWriterFactory) + { + // filter out partition keys, which are not written to the file + testColumns = testColumns.stream() + .filter(column -> !column.partitionKey()) + .collect(toImmutableList()); + + List types = testColumns.stream() + .map(TestColumn::type) + .collect(toList()); + + PageBuilder pageBuilder = new PageBuilder(types); + + for (int rowNumber = 0; rowNumber < numRows; rowNumber++) { + pageBuilder.declarePosition(); + for (int columnNumber = 0; columnNumber < testColumns.size(); columnNumber++) { + TestColumn testColumn = testColumns.get(columnNumber); + writeValue(testColumn.type(), pageBuilder.getBlockBuilder(columnNumber), testColumn.writeValue()); + } + } + Page page = pageBuilder.build(); + + Properties tableProperties = new Properties(); + tableProperties.setProperty( + "columns", + testColumns.stream() + .map(TestColumn::name) + .collect(Collectors.joining(","))); + + tableProperties.setProperty( + "columns.types", + testColumns.stream() + .map(TestColumn::type) + .map(HiveType::toHiveType) + .map(HiveType::toString) + .collect(Collectors.joining(","))); + + Optional fileWriter = fileWriterFactory.createFileWriter( + location, + testColumns.stream() + .map(TestColumn::name) + .collect(toList()), + StorageFormat.fromHiveStorageFormat(storageFormat), + compressionCodec, + tableProperties, + session, + OptionalInt.empty(), + NO_ACID_TRANSACTION, + false, + WriterKind.INSERT); + + FileWriter hiveFileWriter = fileWriter.orElseThrow(() -> new IllegalArgumentException("fileWriterFactory")); + hiveFileWriter.appendRows(page); + hiveFileWriter.commit(); + } + + private static void writeValue(Type type, BlockBuilder builder, Object object) + { + requireNonNull(builder, "builder is null"); + + if (object == null) { + builder.appendNull(); + } + else if (type == BOOLEAN) { + BOOLEAN.writeBoolean(builder, (boolean) object); + } + else if (type == TINYINT) { + TINYINT.writeByte(builder, (byte) object); + } + else if (type == SMALLINT) { + SMALLINT.writeShort(builder, (short) object); + } + else if (type == INTEGER) { + INTEGER.writeInt(builder, (int) object); + } + else if (type == BIGINT) { + BIGINT.writeLong(builder, (long) object); + } + else if (type == REAL) { + REAL.writeFloat(builder, (float) object); + } + else if (type == DOUBLE) { + DOUBLE.writeDouble(builder, (double) object); + } + else if (type instanceof VarcharType varcharType) { + if (object instanceof HiveVarchar) { + object = ((HiveVarchar) object).getValue(); + } + varcharType.writeSlice(builder, utf8Slice((String) object)); + } + else if (type instanceof CharType charType) { + if (object instanceof HiveChar) { + object = ((HiveChar) object).getValue(); + } + charType.writeSlice(builder, truncateToLengthAndTrimSpaces(utf8Slice((String) object), ((CharType) type).getLength())); + } + else if (type == DATE) { + long days = ((Date) object).toEpochDay(); + DATE.writeLong(builder, days); + } + else if (type instanceof TimestampType timestampType) { + Timestamp timestamp = (Timestamp) object; + long epochSecond = timestamp.toEpochSecond(); + int nanosOfSecond = (int) round(timestamp.getNanos(), 9 - timestampType.getPrecision()); + createTimestampEncoder(timestampType, UTC).write(new DecodedTimestamp(epochSecond, nanosOfSecond), builder); + } + else if (type == VARBINARY) { + VARBINARY.writeSlice(builder, Slices.wrappedBuffer((byte[]) object)); + } + else if (type instanceof DecimalType decimalType) { + HiveDecimalWritable hiveDecimal = new HiveDecimalWritable((HiveDecimal) object); + Int128 value = Int128.fromBigEndian(hiveDecimal.getInternalStorage()); + value = Int128Math.rescale(value, decimalType.getScale() - hiveDecimal.getScale()); + if (decimalType.isShort()) { + type.writeLong(builder, value.toLongExact()); + } + else { + type.writeObject(builder, value); + } + } + else if (type instanceof ArrayType arrayType) { + Type elementType = arrayType.getElementType(); + List list = (List) object; + ((ArrayBlockBuilder) builder).buildEntry(elementBuilder -> { + for (Object element : list) { + writeValue(elementType, elementBuilder, element); + } + }); + } + else if (type instanceof MapType mapType) { + Type keyType = mapType.getKeyType(); + Type valueType = mapType.getValueType(); + Map map = (Map) object; + ((MapBlockBuilder) builder).buildEntry((keyBuilder, valueBuilder) -> { + for (Map.Entry entry : map.entrySet()) { + // Hive skips map entries with null keys + if (entry.getKey() != null) { + writeValue(keyType, keyBuilder, entry.getKey()); + writeValue(valueType, valueBuilder, entry.getValue()); + } + } + }); + } + else if (type instanceof RowType rowType) { + List typeParameters = rowType.getTypeParameters(); + List foo = (List) object; + ((RowBlockBuilder) builder).buildEntry(fieldBuilders -> { + for (int i = 0; i < typeParameters.size(); i++) { + writeValue(typeParameters.get(i), fieldBuilders.get(i), foo.get(i)); + } + }); + } + else { + throw new RuntimeException("Unsupported type: " + type); + } + } + + private static void createTestFileHive( + TrinoFileSystemFactory fileSystemFactory, + Location location, + HiveStorageFormat storageFormat, + HiveCompressionCodec compressionCodec, + List testColumns, + int numRows) + throws Exception + { + HiveOutputFormat outputFormat = newInstance(storageFormat.getOutputFormat(), HiveOutputFormat.class); + Serializer serializer = newInstance(storageFormat.getSerde(), Serializer.class); + + // filter out partition keys, which are not written to the file + testColumns = testColumns.stream() + .filter(column -> !column.partitionKey()) + .collect(toImmutableList()); + + Properties tableProperties = new Properties(); + tableProperties.setProperty("columns", testColumns.stream().map(TestColumn::name).collect(Collectors.joining(","))); + tableProperties.setProperty("columns.types", testColumns.stream().map(testColumn -> HiveType.toHiveType(testColumn.type()).toString()).collect(Collectors.joining(","))); + serializer.initialize(new Configuration(false), tableProperties); + + JobConf jobConf = new JobConf(false); + configureCompression(jobConf, compressionCodec); + + File file = File.createTempFile("trino_test", "data"); + file.delete(); + try { + FileSinkOperator.RecordWriter recordWriter = outputFormat.getHiveRecordWriter( + jobConf, + new Path(file.getAbsolutePath()), + Text.class, + compressionCodec != HiveCompressionCodec.NONE, + tableProperties, + () -> {}); + + serializer.initialize(new Configuration(false), tableProperties); + + SettableStructObjectInspector objectInspector = getStandardStructObjectInspector( + testColumns.stream() + .map(TestColumn::name) + .collect(toImmutableList()), + testColumns.stream() + .map(TestColumn::type) + .map(TestHiveFileFormats::getJavaObjectInspector) + .collect(toImmutableList())); + + Object row = objectInspector.create(); + + List fields = ImmutableList.copyOf(objectInspector.getAllStructFieldRefs()); + + for (int rowNumber = 0; rowNumber < numRows; rowNumber++) { + for (int i = 0; i < testColumns.size(); i++) { + objectInspector.setStructFieldData(row, fields.get(i), testColumns.get(i).writeValue()); + } + + Writable record = serializer.serialize(row, objectInspector); + recordWriter.write(record); + } + recordWriter.close(false); + + // copy the file data to the TrinoFileSystem + TrinoFileSystem fileSystem = fileSystemFactory.create(ConnectorIdentity.ofUser("test")); + try (OutputStream outputStream = fileSystem.newOutputFile(location).create()) { + outputStream.write(readAllBytes(file.toPath())); + } + } + finally { + file.delete(); + } + } + + private static void configureCompression(Configuration config, HiveCompressionCodec compressionCodec) + { + boolean compression = compressionCodec != HiveCompressionCodec.NONE; + config.setBoolean(COMPRESSRESULT.varname, compression); + config.setBoolean("mapred.output.compress", compression); + config.setBoolean(FileOutputFormat.COMPRESS, compression); + + // For ORC + OrcConf.COMPRESS.setString(config, compressionCodec.getOrcCompressionKind().name()); + + // For RCFile and Text + if (compressionCodec.getHiveCompressionKind().isPresent()) { + config.set("mapred.output.compression.codec", compressionCodec.getHiveCompressionKind().get().getHadoopClassName()); + config.set(FileOutputFormat.COMPRESS_CODEC, compressionCodec.getHiveCompressionKind().get().getHadoopClassName()); + } + else { + config.unset("mapred.output.compression.codec"); + config.unset(FileOutputFormat.COMPRESS_CODEC); + } + + // For Parquet + config.set(ParquetOutputFormat.COMPRESSION, compressionCodec.getParquetCompressionCodec().name()); + + // For Avro + compressionCodec.getAvroCompressionKind().ifPresent(kind -> config.set("avro.output.codec", kind.toString())); + + // For SequenceFile + config.set(FileOutputFormat.COMPRESS_TYPE, BLOCK.toString()); + } + + private static ObjectInspector getJavaObjectInspector(Type type) + { + if (type.equals(BOOLEAN)) { + return javaBooleanObjectInspector; + } + if (type.equals(BIGINT)) { + return javaLongObjectInspector; + } + if (type.equals(INTEGER)) { + return javaIntObjectInspector; + } + if (type.equals(SMALLINT)) { + return javaShortObjectInspector; + } + if (type.equals(TINYINT)) { + return javaByteObjectInspector; + } + if (type.equals(REAL)) { + return javaFloatObjectInspector; + } + if (type.equals(DOUBLE)) { + return javaDoubleObjectInspector; + } + if (type instanceof VarcharType varcharType) { + return varcharType.getLength() + .map(length -> getPrimitiveJavaObjectInspector(new VarcharTypeInfo(length))) + .orElse(javaStringObjectInspector); + } + if (type instanceof CharType charType) { + return getPrimitiveJavaObjectInspector(new CharTypeInfo(charType.getLength())); + } + if (type.equals(VARBINARY)) { + return javaByteArrayObjectInspector; + } + if (type.equals(DATE)) { + return javaDateObjectInspector; + } + if (type instanceof TimestampType) { + return javaTimestampObjectInspector; + } + if (type instanceof DecimalType decimalType) { + return getPrimitiveJavaObjectInspector(new DecimalTypeInfo(decimalType.getPrecision(), decimalType.getScale())); + } + if (type instanceof ArrayType arrayType) { + return getStandardListObjectInspector(getJavaObjectInspector(arrayType.getElementType())); + } + if (type instanceof MapType mapType) { + ObjectInspector keyObjectInspector = getJavaObjectInspector(mapType.getKeyType()); + ObjectInspector valueObjectInspector = getJavaObjectInspector(mapType.getValueType()); + return getStandardMapObjectInspector(keyObjectInspector, valueObjectInspector); + } + if (type instanceof RowType rowType) { + return getStandardStructObjectInspector( + rowType.getFields().stream() + .map(RowType.Field::getName) + .map(Optional::orElseThrow) + .collect(toList()), + rowType.getFields().stream() + .map(RowType.Field::getType) + .map(TestHiveFileFormats::getJavaObjectInspector) + .collect(toList())); + } + throw new IllegalArgumentException("unsupported type: " + type); + } + + private static T newInstance(String className, Class superType) + throws ReflectiveOperationException + { + return HiveStorageFormat.class.getClassLoader().loadClass(className).asSubclass(superType).getConstructor().newInstance(); + } + + // TEST COLUMNS + + private static final Type VARCHAR_100 = createVarcharType(100); + private static final Type VARCHAR_HIVE_MAX = createVarcharType(MAX_VARCHAR_LENGTH); + private static final Type CHAR_10 = createCharType(10); + private static final String VARCHAR_MAX_LENGTH_STRING; + + static { + char[] varcharMaxLengthCharArray = new char[MAX_VARCHAR_LENGTH]; + fill(varcharMaxLengthCharArray, 'a'); + VARCHAR_MAX_LENGTH_STRING = new String(varcharMaxLengthCharArray); + } + + private static final Date HIVE_DATE = Date.of(2011, 5, 6); + private static final long DATE_DAYS = HIVE_DATE.toEpochDay(); + private static final String DATE_STRING = HIVE_DATE.toString(); + + private static final DateTime TIMESTAMP_VALUE = new DateTime(2011, 5, 6, 7, 8, 9, 123, UTC); + // micros are used for the expected value to simplify the testing of array, map, and row, but only millis are used + private static final long TIMESTAMP_MICROS_VALUE = TIMESTAMP_VALUE.getMillis() * MICROSECONDS_PER_MILLISECOND; + private static final String TIMESTAMP_STRING_VALUE = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS").withZoneUTC().print(TIMESTAMP_VALUE.getMillis()); + private static final Timestamp HIVE_TIMESTAMP = Timestamp.ofEpochMilli(TIMESTAMP_VALUE.getMillis()); + + private static final DecimalType DECIMAL_TYPE_2 = DecimalType.createDecimalType(2, 1); + private static final DecimalType DECIMAL_TYPE_4 = DecimalType.createDecimalType(4, 2); + private static final DecimalType DECIMAL_TYPE_8 = DecimalType.createDecimalType(8, 4); + private static final DecimalType DECIMAL_TYPE_17 = DecimalType.createDecimalType(17, 8); + private static final DecimalType DECIMAL_TYPE_18 = DecimalType.createDecimalType(18, 8); + private static final DecimalType DECIMAL_TYPE_38 = DecimalType.createDecimalType(38, 16); + + private static final HiveDecimal WRITE_DECIMAL_2 = HiveDecimal.create(new BigDecimal("-1.2")); + private static final HiveDecimal WRITE_DECIMAL_4 = HiveDecimal.create(new BigDecimal("12.3")); + private static final HiveDecimal WRITE_DECIMAL_8 = HiveDecimal.create(new BigDecimal("-1234.5678")); + private static final HiveDecimal WRITE_DECIMAL_17 = HiveDecimal.create(new BigDecimal("123456789.1234")); + private static final HiveDecimal WRITE_DECIMAL_18 = HiveDecimal.create(new BigDecimal("-1234567890.12345678")); + private static final HiveDecimal WRITE_DECIMAL_38 = HiveDecimal.create(new BigDecimal("1234567890123456789012.12345678")); + + private static final BigDecimal EXPECTED_DECIMAL_2 = new BigDecimal("-1.2"); + private static final BigDecimal EXPECTED_DECIMAL_4 = new BigDecimal("12.30"); + private static final BigDecimal EXPECTED_DECIMAL_8 = new BigDecimal("-1234.5678"); + private static final BigDecimal EXPECTED_DECIMAL_17 = new BigDecimal("123456789.12340000"); + private static final BigDecimal EXPECTED_DECIMAL_18 = new BigDecimal("-1234567890.12345678"); + private static final BigDecimal EXPECTED_DECIMAL_38 = new BigDecimal("1234567890123456789012.1234567800000000"); + + private static final TypeOperators TYPE_OPERATORS = TESTING_TYPE_MANAGER.getTypeOperators(); + + private static final List TEST_COLUMNS = ImmutableList.builder() + .add(new TestColumn("p_empty_string", VARCHAR, "", Slices.EMPTY_SLICE, true)) + .add(new TestColumn("p_string", VARCHAR, "test", utf8Slice("test"), true)) + .add(new TestColumn("p_empty_varchar", VARCHAR_100, "", Slices.EMPTY_SLICE, true)) + .add(new TestColumn("p_varchar", VARCHAR_100, "test", utf8Slice("test"), true)) + .add(new TestColumn("p_varchar_max_length", VARCHAR_HIVE_MAX, VARCHAR_MAX_LENGTH_STRING, utf8Slice(VARCHAR_MAX_LENGTH_STRING), true)) + .add(new TestColumn("p_char_10", CHAR_10, "test", utf8Slice("test"), true)) + .add(new TestColumn("p_tinyint", TINYINT, "1", (byte) 1, true)) + .add(new TestColumn("p_smallint", SMALLINT, "2", (short) 2, true)) + .add(new TestColumn("p_int", INTEGER, "3", 3, true)) + .add(new TestColumn("p_bigint", BIGINT, "4", 4L, true)) + .add(new TestColumn("p_float", REAL, "5.1", 5.1f, true)) + .add(new TestColumn("p_double", DOUBLE, "6.2", 6.2, true)) + .add(new TestColumn("p_boolean", BOOLEAN, "true", true, true)) + .add(new TestColumn("p_date", DATE, DATE_STRING, DATE_DAYS, true)) + .add(new TestColumn("p_timestamp", TIMESTAMP_MILLIS, TIMESTAMP_STRING_VALUE, TIMESTAMP_MICROS_VALUE, true)) + .add(new TestColumn("p_decimal_2", DECIMAL_TYPE_2, WRITE_DECIMAL_2.toString(), EXPECTED_DECIMAL_2, true)) + .add(new TestColumn("p_decimal_4", DECIMAL_TYPE_4, WRITE_DECIMAL_4.toString(), EXPECTED_DECIMAL_4, true)) + .add(new TestColumn("p_decimal_8", DECIMAL_TYPE_8, WRITE_DECIMAL_8.toString(), EXPECTED_DECIMAL_8, true)) + .add(new TestColumn("p_decimal_17", DECIMAL_TYPE_17, WRITE_DECIMAL_17.toString(), EXPECTED_DECIMAL_17, true)) + .add(new TestColumn("p_decimal_18", DECIMAL_TYPE_18, WRITE_DECIMAL_18.toString(), EXPECTED_DECIMAL_18, true)) + .add(new TestColumn("p_decimal_38", DECIMAL_TYPE_38, WRITE_DECIMAL_38.toString() + "BD", EXPECTED_DECIMAL_38, true)) + .add(new TestColumn("p_null_string", VARCHAR, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) + .add(new TestColumn("p_null_varchar", VARCHAR_100, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) + .add(new TestColumn("p_null_char", CHAR_10, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) + .add(new TestColumn("p_null_tinyint", TINYINT, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) + .add(new TestColumn("p_null_smallint", SMALLINT, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) + .add(new TestColumn("p_null_int", INTEGER, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) + .add(new TestColumn("p_null_bigint", BIGINT, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) + .add(new TestColumn("p_null_float", REAL, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) + .add(new TestColumn("p_null_double", DOUBLE, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) + .add(new TestColumn("p_null_boolean", BOOLEAN, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) + .add(new TestColumn("p_null_date", DATE, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) + .add(new TestColumn("p_null_timestamp", TIMESTAMP_MILLIS, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) + .add(new TestColumn("p_null_decimal_2", DECIMAL_TYPE_2, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) + .add(new TestColumn("p_null_decimal_4", DECIMAL_TYPE_4, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) + .add(new TestColumn("p_null_decimal_8", DECIMAL_TYPE_8, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) + .add(new TestColumn("p_null_decimal_17", DECIMAL_TYPE_17, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) + .add(new TestColumn("p_null_decimal_18", DECIMAL_TYPE_18, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) + .add(new TestColumn("p_null_decimal_38", DECIMAL_TYPE_38, HIVE_DEFAULT_DYNAMIC_PARTITION, null, true)) + + .add(new TestColumn("t_null_string", VARCHAR, null, null)) + .add(new TestColumn("t_null_varchar", VARCHAR_100, null, null)) + .add(new TestColumn("t_null_char", CHAR_10, null, null)) + .add(new TestColumn("t_null_array_int", new ArrayType(INTEGER), null, null)) + .add(new TestColumn("t_null_decimal_2", DECIMAL_TYPE_2, null, null)) + .add(new TestColumn("t_null_decimal_4", DECIMAL_TYPE_4, null, null)) + .add(new TestColumn("t_null_decimal_8", DECIMAL_TYPE_8, null, null)) + .add(new TestColumn("t_null_decimal_17", DECIMAL_TYPE_17, null, null)) + .add(new TestColumn("t_null_decimal_18", DECIMAL_TYPE_18, null, null)) + .add(new TestColumn("t_null_decimal_38", DECIMAL_TYPE_38, null, null)) + .add(new TestColumn("t_empty_string", VARCHAR, "", Slices.EMPTY_SLICE)) + .add(new TestColumn("t_string", VARCHAR, "test", utf8Slice("test"))) + .add(new TestColumn("t_empty_varchar", VARCHAR_HIVE_MAX, new HiveVarchar("", MAX_VARCHAR_LENGTH), Slices.EMPTY_SLICE)) + .add(new TestColumn("t_varchar", VARCHAR_HIVE_MAX, new HiveVarchar("test", MAX_VARCHAR_LENGTH), utf8Slice("test"))) + .add(new TestColumn("t_varchar_max_length", VARCHAR_HIVE_MAX, new HiveVarchar(VARCHAR_MAX_LENGTH_STRING, MAX_VARCHAR_LENGTH), utf8Slice(VARCHAR_MAX_LENGTH_STRING))) + .add(new TestColumn("t_char", CHAR_10, "test", utf8Slice("test"))) + .add(new TestColumn("t_tinyint", TINYINT, (byte) 1, (byte) 1)) + .add(new TestColumn("t_smallint", SMALLINT, (short) 2, (short) 2)) + .add(new TestColumn("t_int", INTEGER, 3, 3)) + .add(new TestColumn("t_bigint", BIGINT, 4L, 4L)) + .add(new TestColumn("t_float", REAL, 5.1f, 5.1f)) + .add(new TestColumn("t_double", DOUBLE, 6.2, 6.2)) + .add(new TestColumn("t_boolean_true", BOOLEAN, true, true)) + .add(new TestColumn("t_boolean_false", BOOLEAN, false, false)) + .add(new TestColumn("t_date", DATE, HIVE_DATE, DATE_DAYS)) + .add(new TestColumn("t_timestamp", TIMESTAMP_MILLIS, HIVE_TIMESTAMP, TIMESTAMP_MICROS_VALUE)) + .add(new TestColumn("t_decimal_2", DECIMAL_TYPE_2, WRITE_DECIMAL_2, EXPECTED_DECIMAL_2)) + .add(new TestColumn("t_decimal_4", DECIMAL_TYPE_4, WRITE_DECIMAL_4, EXPECTED_DECIMAL_4)) + .add(new TestColumn("t_decimal_8", DECIMAL_TYPE_8, WRITE_DECIMAL_8, EXPECTED_DECIMAL_8)) + .add(new TestColumn("t_decimal_17", DECIMAL_TYPE_17, WRITE_DECIMAL_17, EXPECTED_DECIMAL_17)) + .add(new TestColumn("t_decimal_18", DECIMAL_TYPE_18, WRITE_DECIMAL_18, EXPECTED_DECIMAL_18)) + .add(new TestColumn("t_decimal_38", DECIMAL_TYPE_38, WRITE_DECIMAL_38, EXPECTED_DECIMAL_38)) + .add(new TestColumn("t_binary", VARBINARY, utf8Slice("test2").getBytes(), utf8Slice("test2"))) + .add(new TestColumn("t_map_string", + new MapType(VARCHAR, VARCHAR, TYPE_OPERATORS), + ImmutableMap.of("test", "test"), + sqlMapOf(createUnboundedVarcharType(), createUnboundedVarcharType(), "test", "test"))) + .add(new TestColumn("t_map_tinyint", + new MapType(TINYINT, TINYINT, TYPE_OPERATORS), + ImmutableMap.of((byte) 1, (byte) 1), + sqlMapOf(TINYINT, TINYINT, (byte) 1, (byte) 1))) + .add(new TestColumn("t_map_varchar", + new MapType(VARCHAR_HIVE_MAX, VARCHAR_HIVE_MAX, TYPE_OPERATORS), + ImmutableMap.of(new HiveVarchar("test", MAX_VARCHAR_LENGTH), new HiveVarchar("test", MAX_VARCHAR_LENGTH)), + sqlMapOf(createVarcharType(MAX_VARCHAR_LENGTH), createVarcharType(MAX_VARCHAR_LENGTH), "test", "test"))) + .add(new TestColumn("t_map_char", + new MapType(CHAR_10, CHAR_10, TYPE_OPERATORS), + ImmutableMap.of(new HiveChar("test", 10), new HiveChar("test", 10)), + sqlMapOf(createCharType(10), createCharType(10), "test", "test"))) + .add(new TestColumn("t_map_smallint", + new MapType(SMALLINT, SMALLINT, TYPE_OPERATORS), + ImmutableMap.of((short) 2, (short) 2), + sqlMapOf(SMALLINT, SMALLINT, (short) 2, (short) 2))) + .add(new TestColumn("t_map_null_key", + new MapType(BIGINT, BIGINT, TYPE_OPERATORS), + asMap(new Long[] {null, 2L}, new Long[] {0L, 3L}), + sqlMapOf(BIGINT, BIGINT, 2, 3))) + .add(new TestColumn("t_map_int", + new MapType(INTEGER, INTEGER, TYPE_OPERATORS), + ImmutableMap.of(3, 3), + sqlMapOf(INTEGER, INTEGER, 3, 3))) + .add(new TestColumn("t_map_bigint", + new MapType(BIGINT, BIGINT, TYPE_OPERATORS), + ImmutableMap.of(4L, 4L), + sqlMapOf(BIGINT, BIGINT, 4L, 4L))) + .add(new TestColumn("t_map_float", + new MapType(REAL, REAL, TYPE_OPERATORS), + ImmutableMap.of(5.0f, 5.0f), sqlMapOf(REAL, REAL, 5.0f, 5.0f))) + .add(new TestColumn("t_map_double", + new MapType(DOUBLE, DOUBLE, TYPE_OPERATORS), + ImmutableMap.of(6.0, 6.0), sqlMapOf(DOUBLE, DOUBLE, 6.0, 6.0))) + .add(new TestColumn("t_map_boolean", + new MapType(BOOLEAN, BOOLEAN, TYPE_OPERATORS), + ImmutableMap.of(true, true), + sqlMapOf(BOOLEAN, BOOLEAN, true, true))) + .add(new TestColumn("t_map_date", + new MapType(DATE, DATE, TYPE_OPERATORS), + ImmutableMap.of(HIVE_DATE, HIVE_DATE), + sqlMapOf(DATE, DATE, DATE_DAYS, DATE_DAYS))) + .add(new TestColumn("t_map_timestamp", + new MapType(TIMESTAMP_MILLIS, TIMESTAMP_MILLIS, TYPE_OPERATORS), + ImmutableMap.of(HIVE_TIMESTAMP, HIVE_TIMESTAMP), + sqlMapOf(TIMESTAMP_MILLIS, TIMESTAMP_MILLIS, TIMESTAMP_MICROS_VALUE, TIMESTAMP_MICROS_VALUE))) + .add(new TestColumn("t_map_decimal_2", + new MapType(DECIMAL_TYPE_2, DECIMAL_TYPE_2, TYPE_OPERATORS), + ImmutableMap.of(WRITE_DECIMAL_2, WRITE_DECIMAL_2), + decimalSqlMapOf(DECIMAL_TYPE_2, EXPECTED_DECIMAL_2))) + .add(new TestColumn("t_map_decimal_4", + new MapType(DECIMAL_TYPE_4, DECIMAL_TYPE_4, TYPE_OPERATORS), + ImmutableMap.of(WRITE_DECIMAL_4, WRITE_DECIMAL_4), + decimalSqlMapOf(DECIMAL_TYPE_4, EXPECTED_DECIMAL_4))) + .add(new TestColumn("t_map_decimal_8", + new MapType(DECIMAL_TYPE_8, DECIMAL_TYPE_8, TYPE_OPERATORS), + ImmutableMap.of(WRITE_DECIMAL_8, WRITE_DECIMAL_8), + decimalSqlMapOf(DECIMAL_TYPE_8, EXPECTED_DECIMAL_8))) + .add(new TestColumn("t_map_decimal_17", + new MapType(DECIMAL_TYPE_17, DECIMAL_TYPE_17, TYPE_OPERATORS), + ImmutableMap.of(WRITE_DECIMAL_17, WRITE_DECIMAL_17), + decimalSqlMapOf(DECIMAL_TYPE_17, EXPECTED_DECIMAL_17))) + .add(new TestColumn("t_map_decimal_18", + new MapType(DECIMAL_TYPE_18, DECIMAL_TYPE_18, TYPE_OPERATORS), + ImmutableMap.of(WRITE_DECIMAL_18, WRITE_DECIMAL_18), + decimalSqlMapOf(DECIMAL_TYPE_18, EXPECTED_DECIMAL_18))) + .add(new TestColumn("t_map_decimal_38", + new MapType(DECIMAL_TYPE_38, DECIMAL_TYPE_38, TYPE_OPERATORS), + ImmutableMap.of(WRITE_DECIMAL_38, WRITE_DECIMAL_38), + decimalSqlMapOf(DECIMAL_TYPE_38, EXPECTED_DECIMAL_38))) + .add(new TestColumn("t_array_empty", new ArrayType(VARCHAR), ImmutableList.of(), arrayBlockOf(createUnboundedVarcharType()))) + .add(new TestColumn("t_array_string", new ArrayType(VARCHAR), ImmutableList.of("test"), arrayBlockOf(createUnboundedVarcharType(), "test"))) + .add(new TestColumn("t_array_tinyint", new ArrayType(TINYINT), ImmutableList.of((byte) 1), arrayBlockOf(TINYINT, (byte) 1))) + .add(new TestColumn("t_array_smallint", new ArrayType(SMALLINT), ImmutableList.of((short) 2), arrayBlockOf(SMALLINT, (short) 2))) + .add(new TestColumn("t_array_int", new ArrayType(INTEGER), ImmutableList.of(3), arrayBlockOf(INTEGER, 3))) + .add(new TestColumn("t_array_bigint", new ArrayType(BIGINT), ImmutableList.of(4L), arrayBlockOf(BIGINT, 4L))) + .add(new TestColumn("t_array_float", new ArrayType(REAL), ImmutableList.of(5.0f), arrayBlockOf(REAL, 5.0f))) + .add(new TestColumn("t_array_double", new ArrayType(DOUBLE), ImmutableList.of(6.0), arrayBlockOf(DOUBLE, 6.0))) + .add(new TestColumn("t_array_boolean", new ArrayType(BOOLEAN), ImmutableList.of(true), arrayBlockOf(BOOLEAN, true))) + .add(new TestColumn( + "t_array_varchar", + new ArrayType(VARCHAR_HIVE_MAX), + ImmutableList.of(new HiveVarchar("test", MAX_VARCHAR_LENGTH)), + arrayBlockOf(createVarcharType(MAX_VARCHAR_LENGTH), "test"))) + .add(new TestColumn( + "t_array_char", + new ArrayType(CHAR_10), + ImmutableList.of(new HiveChar("test", 10)), + arrayBlockOf(createCharType(10), "test"))) + .add(new TestColumn("t_array_date", + new ArrayType(DATE), + ImmutableList.of(HIVE_DATE), + arrayBlockOf(DATE, DATE_DAYS))) + .add(new TestColumn("t_array_timestamp", + new ArrayType(TIMESTAMP_MILLIS), + ImmutableList.of(HIVE_TIMESTAMP), + arrayBlockOf(TIMESTAMP_MILLIS, TIMESTAMP_MICROS_VALUE))) + .add(new TestColumn("t_array_decimal_2", + new ArrayType(DECIMAL_TYPE_2), + ImmutableList.of(WRITE_DECIMAL_2), + decimalArrayBlockOf(DECIMAL_TYPE_2, EXPECTED_DECIMAL_2))) + .add(new TestColumn("t_array_decimal_4", + new ArrayType(DECIMAL_TYPE_4), + ImmutableList.of(WRITE_DECIMAL_4), + decimalArrayBlockOf(DECIMAL_TYPE_4, EXPECTED_DECIMAL_4))) + .add(new TestColumn("t_array_decimal_8", + new ArrayType(DECIMAL_TYPE_8), + ImmutableList.of(WRITE_DECIMAL_8), + decimalArrayBlockOf(DECIMAL_TYPE_8, EXPECTED_DECIMAL_8))) + .add(new TestColumn("t_array_decimal_17", + new ArrayType(DECIMAL_TYPE_17), + ImmutableList.of(WRITE_DECIMAL_17), + decimalArrayBlockOf(DECIMAL_TYPE_17, EXPECTED_DECIMAL_17))) + .add(new TestColumn("t_array_decimal_18", + new ArrayType(DECIMAL_TYPE_18), + ImmutableList.of(WRITE_DECIMAL_18), + decimalArrayBlockOf(DECIMAL_TYPE_18, EXPECTED_DECIMAL_18))) + .add(new TestColumn("t_array_decimal_38", + new ArrayType(DECIMAL_TYPE_38), + ImmutableList.of(WRITE_DECIMAL_38), + decimalArrayBlockOf(DECIMAL_TYPE_38, EXPECTED_DECIMAL_38))) + .add(new TestColumn("t_struct_bigint", + rowType(field("s_bigint", BIGINT)), + ImmutableList.of(1L), + rowBlockOf(ImmutableList.of(BIGINT), 1))) + .add(new TestColumn("t_complex", + new MapType( + VARCHAR, + new ArrayType(rowType(field("s_int", INTEGER))), + TYPE_OPERATORS), + ImmutableMap.of("test", ImmutableList.of(ImmutableList.of(1))), + sqlMapOf(createUnboundedVarcharType(), new ArrayType(RowType.anonymous(ImmutableList.of(INTEGER))), + "test", arrayBlockOf(RowType.anonymous(ImmutableList.of(INTEGER)), rowBlockOf(ImmutableList.of(INTEGER), 1L))))) + .add(new TestColumn("t_map_null_key_complex_value", + new MapType( + VARCHAR, + new MapType(BIGINT, BOOLEAN, TYPE_OPERATORS), + TYPE_OPERATORS), + asMap(new String[] {null, "k"}, new ImmutableMap[] {ImmutableMap.of(15L, true), ImmutableMap.of(16L, false)}), + sqlMapOf(createUnboundedVarcharType(), mapType(BIGINT, BOOLEAN), "k", sqlMapOf(BIGINT, BOOLEAN, 16L, false)))) + .add(new TestColumn("t_map_null_key_complex_key_value", + new MapType( + new ArrayType(VARCHAR), + new MapType(BIGINT, BOOLEAN, TYPE_OPERATORS), + TYPE_OPERATORS), + asMap(new ImmutableList[] {null, ImmutableList.of("k", "ka")}, new ImmutableMap[] {ImmutableMap.of(15L, true), ImmutableMap.of(16L, false)}), + sqlMapOf(new ArrayType(createUnboundedVarcharType()), mapType(BIGINT, BOOLEAN), arrayBlockOf(createUnboundedVarcharType(), "k", "ka"), sqlMapOf(BIGINT, BOOLEAN, 16L, false)))) + .add(new TestColumn("t_struct_nested", + rowType(field("struct_field", new ArrayType(VARCHAR))), + ImmutableList.of(ImmutableList.of("1", "2", "3")), + rowBlockOf(ImmutableList.of(new ArrayType(createUnboundedVarcharType())), arrayBlockOf(createUnboundedVarcharType(), "1", "2", "3")))) + .add(new TestColumn("t_struct_null", + rowType(field("struct_field_null", VARCHAR), field("struct_field_null2", VARCHAR)), + Arrays.asList(null, null), + rowBlockOf(ImmutableList.of(createUnboundedVarcharType(), createUnboundedVarcharType()), null, null))) + .add(new TestColumn("t_struct_non_nulls_after_nulls", + rowType(field("struct_non_nulls_after_nulls1", INTEGER), field("struct_non_nulls_after_nulls2", VARCHAR)), + Arrays.asList(null, "some string"), + rowBlockOf(ImmutableList.of(INTEGER, createUnboundedVarcharType()), null, "some string"))) + .add(new TestColumn("t_nested_struct_non_nulls_after_nulls", + rowType( + field("struct_field1", INTEGER), + field("struct_field2", VARCHAR), + field("strict_field3", rowType(field("nested_struct_field1", INTEGER), field("nested_struct_field2", VARCHAR)))), + Arrays.asList(null, "some string", Arrays.asList(null, "nested_string2")), + rowBlockOf( + ImmutableList.of( + INTEGER, + createUnboundedVarcharType(), + RowType.anonymous(ImmutableList.of(INTEGER, createUnboundedVarcharType()))), + null, "some string", rowBlockOf(ImmutableList.of(INTEGER, createUnboundedVarcharType()), null, "nested_string2")))) + .add(new TestColumn("t_map_null_value", + new MapType(VARCHAR, VARCHAR, TYPE_OPERATORS), + asMap(new String[] {"k1", "k2", "k3"}, new String[] {"v1", null, "v3"}), + sqlMapOf(createUnboundedVarcharType(), createUnboundedVarcharType(), new String[] {"k1", "k2", "k3"}, new String[] {"v1", null, "v3"}))) + .add(new TestColumn("t_array_string_starting_with_nulls", new ArrayType(VARCHAR), Arrays.asList(null, "test"), arrayBlockOf(createUnboundedVarcharType(), null, "test"))) + .add(new TestColumn("t_array_string_with_nulls_in_between", new ArrayType(VARCHAR), Arrays.asList("test-1", null, "test-2"), arrayBlockOf(createUnboundedVarcharType(), "test-1", null, "test-2"))) + .add(new TestColumn("t_array_string_ending_with_nulls", new ArrayType(VARCHAR), Arrays.asList("test", null), arrayBlockOf(createUnboundedVarcharType(), "test", null))) + .add(new TestColumn("t_array_string_all_nulls", new ArrayType(VARCHAR), Arrays.asList(null, null, null), arrayBlockOf(createUnboundedVarcharType(), null, null, null))) + .build(); + + private static Map asMap(K[] keys, V[] values) + { + checkArgument(keys.length == values.length, "array lengths don't match"); + Map map = new HashMap<>(); + int len = keys.length; + for (int i = 0; i < len; i++) { + map.put(keys[i], values[i]); + } + return map; + } + + public record TestColumn( + String name, + Type type, + String baseName, + Type baseType, + boolean dereference, + Object writeValue, + Object expectedValue, + boolean partitionKey) + { + public TestColumn(String name, Type type, Object writeValue, Object expectedValue) + { + this(name, type, writeValue, expectedValue, false); + } + + public TestColumn(String name, Type type, Object writeValue, Object expectedValue, boolean partitionKey) + { + this(name, type, name, type, false, writeValue, expectedValue, partitionKey); + } + + public TestColumn + { + requireNonNull(name, "name is null"); + requireNonNull(type, "type is null"); + requireNonNull(baseName, "baseName is null"); + requireNonNull(baseType, "baseType is null"); + } + + public HiveColumnHandle toHiveColumnHandle(int columnIndex) + { + checkArgument(partitionKey == (columnIndex == -1)); + + if (!dereference) { + return createBaseColumn(name, columnIndex, HiveType.toHiveType(type), type, partitionKey ? PARTITION_KEY : REGULAR, Optional.empty()); + } + + return new HiveColumnHandle( + baseName, + columnIndex, + HiveType.toHiveType(baseType), + baseType, + Optional.of(new HiveColumnProjectionInfo(ImmutableList.of(0), ImmutableList.of(name), HiveType.toHiveType(type), type)), + partitionKey ? PARTITION_KEY : REGULAR, + Optional.empty()); + } + + public TestColumn withDereferenceFirstField(Object writeValue, Object expectedValue) + { + verify(!partitionKey, "dereference not supported for partition key"); + verify(!dereference, "already dereference"); + if (!(type instanceof RowType rowType)) { + throw new VerifyException("type is not a row type"); + } + + RowType.Field field = rowType.getFields().get(0); + return new TestColumn( + field.getName().orElseThrow(), + field.getType(), + name, + type, + true, + writeValue, + expectedValue, + false); + } + + public TestColumn withName(String newName) + { + return new TestColumn( + newName, + type, + baseName, + baseType, + dereference, + writeValue, + expectedValue, + partitionKey); + } + } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/CompressionConfigUtil.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/CompressionConfigUtil.java deleted file mode 100644 index 368796d108f3..000000000000 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/CompressionConfigUtil.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 io.trino.plugin.hive.util; - -import io.trino.hive.orc.OrcConf; -import io.trino.plugin.hive.HiveCompressionCodec; -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; -import org.apache.parquet.hadoop.ParquetOutputFormat; - -import static org.apache.hadoop.hive.conf.HiveConf.ConfVars.COMPRESSRESULT; -import static org.apache.hadoop.io.SequenceFile.CompressionType.BLOCK; - -public final class CompressionConfigUtil -{ - private CompressionConfigUtil() {} - - public static void configureCompression(Configuration config, HiveCompressionCodec compressionCodec) - { - boolean compression = compressionCodec != HiveCompressionCodec.NONE; - config.setBoolean(COMPRESSRESULT.varname, compression); - config.setBoolean("mapred.output.compress", compression); - config.setBoolean(FileOutputFormat.COMPRESS, compression); - - // For ORC - OrcConf.COMPRESS.setString(config, compressionCodec.getOrcCompressionKind().name()); - - // For RCFile and Text - if (compressionCodec.getHiveCompressionKind().isPresent()) { - config.set("mapred.output.compression.codec", compressionCodec.getHiveCompressionKind().get().getHadoopClassName()); - config.set(FileOutputFormat.COMPRESS_CODEC, compressionCodec.getHiveCompressionKind().get().getHadoopClassName()); - } - else { - config.unset("mapred.output.compression.codec"); - config.unset(FileOutputFormat.COMPRESS_CODEC); - } - - // For Parquet - config.set(ParquetOutputFormat.COMPRESSION, compressionCodec.getParquetCompressionCodec().name()); - - // For Avro - compressionCodec.getAvroCompressionKind().ifPresent(kind -> config.set("avro.output.codec", kind.toString())); - - // For SequenceFile - config.set(FileOutputFormat.COMPRESS_TYPE, BLOCK.toString()); - } -} diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/DecimalUtils.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/DecimalUtils.java deleted file mode 100644 index 22c1c0efb5e9..000000000000 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/DecimalUtils.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 io.trino.plugin.hive.util; - -import io.trino.spi.type.Int128; -import io.trino.spi.type.Int128Math; -import org.apache.hadoop.hive.serde2.io.HiveDecimalWritable; - -import static io.trino.spi.type.Decimals.rescale; - -public final class DecimalUtils -{ - private DecimalUtils() {} - - public static long getShortDecimalValue(HiveDecimalWritable writable, int columnScale) - { - byte[] bytes = writable.getInternalStorage(); - long value = getShortDecimalValue(bytes); - value = rescale(value, writable.getScale(), columnScale); - return value; - } - - public static long getShortDecimalValue(byte[] bytes) - { - long value = 0; - if ((bytes[0] & 0x80) != 0) { - for (int i = 0; i < 8 - bytes.length; ++i) { - value |= 0xFFL << (8 * (7 - i)); - } - } - - for (int i = 0; i < bytes.length; i++) { - value |= ((long) bytes[bytes.length - i - 1] & 0xFFL) << (8 * i); - } - - return value; - } - - public static Int128 getLongDecimalValue(HiveDecimalWritable writable, int columnScale) - { - Int128 value = Int128.fromBigEndian(writable.getInternalStorage()); - return Int128Math.rescale(value, columnScale - writable.getScale()); - } -} diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/SerDeUtils.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/SerDeUtils.java deleted file mode 100644 index 560999c12ab3..000000000000 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/SerDeUtils.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * 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 io.trino.plugin.hive.util; - -import com.google.common.annotations.VisibleForTesting; -import io.airlift.slice.Slices; -import io.trino.plugin.base.type.DecodedTimestamp; -import io.trino.spi.block.ArrayBlockBuilder; -import io.trino.spi.block.BlockBuilder; -import io.trino.spi.block.MapBlockBuilder; -import io.trino.spi.block.RowBlockBuilder; -import io.trino.spi.type.ArrayType; -import io.trino.spi.type.CharType; -import io.trino.spi.type.DecimalType; -import io.trino.spi.type.MapType; -import io.trino.spi.type.RowType; -import io.trino.spi.type.TimestampType; -import io.trino.spi.type.Type; -import org.apache.hadoop.hive.common.type.HiveChar; -import org.apache.hadoop.hive.common.type.Timestamp; -import org.apache.hadoop.hive.serde2.io.DateWritable; -import org.apache.hadoop.hive.serde2.io.HiveDecimalWritable; -import org.apache.hadoop.hive.serde2.io.TimestampWritable; -import org.apache.hadoop.hive.serde2.lazy.LazyDate; -import org.apache.hadoop.hive.serde2.objectinspector.ListObjectInspector; -import org.apache.hadoop.hive.serde2.objectinspector.MapObjectInspector; -import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector; -import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector; -import org.apache.hadoop.hive.serde2.objectinspector.StructField; -import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector; -import org.apache.hadoop.hive.serde2.objectinspector.UnionObjectInspector; -import org.apache.hadoop.hive.serde2.objectinspector.primitive.BinaryObjectInspector; -import org.apache.hadoop.hive.serde2.objectinspector.primitive.BooleanObjectInspector; -import org.apache.hadoop.hive.serde2.objectinspector.primitive.ByteObjectInspector; -import org.apache.hadoop.hive.serde2.objectinspector.primitive.DateObjectInspector; -import org.apache.hadoop.hive.serde2.objectinspector.primitive.DoubleObjectInspector; -import org.apache.hadoop.hive.serde2.objectinspector.primitive.FloatObjectInspector; -import org.apache.hadoop.hive.serde2.objectinspector.primitive.HiveCharObjectInspector; -import org.apache.hadoop.hive.serde2.objectinspector.primitive.HiveDecimalObjectInspector; -import org.apache.hadoop.hive.serde2.objectinspector.primitive.HiveVarcharObjectInspector; -import org.apache.hadoop.hive.serde2.objectinspector.primitive.IntObjectInspector; -import org.apache.hadoop.hive.serde2.objectinspector.primitive.LongObjectInspector; -import org.apache.hadoop.hive.serde2.objectinspector.primitive.ShortObjectInspector; -import org.apache.hadoop.hive.serde2.objectinspector.primitive.StringObjectInspector; -import org.apache.hadoop.hive.serde2.objectinspector.primitive.TimestampObjectInspector; -import org.joda.time.DateTimeZone; - -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import static com.google.common.base.Preconditions.checkArgument; -import static io.trino.plugin.base.type.TrinoTimestampEncoderFactory.createTimestampEncoder; -import static io.trino.spi.type.Chars.truncateToLengthAndTrimSpaces; -import static io.trino.spi.type.Timestamps.NANOSECONDS_PER_SECOND; -import static io.trino.spi.type.Timestamps.round; -import static io.trino.spi.type.TinyintType.TINYINT; -import static java.lang.Float.floatToRawIntBits; -import static java.util.Objects.requireNonNull; - -public final class SerDeUtils -{ - private SerDeUtils() {} - - public static void serializeObject(Type type, BlockBuilder builder, Object object, ObjectInspector inspector) - { - requireNonNull(builder, "builder is null"); - serializeObject(type, builder, object, inspector, true); - } - - // This version supports optionally disabling the filtering of null map key, which should only be used for building test data sets - // that contain null map keys. For production, null map keys are not allowed. - @VisibleForTesting - public static void serializeObject(Type type, BlockBuilder builder, Object object, ObjectInspector inspector, boolean filterNullMapKeys) - { - requireNonNull(builder, "builder is null"); - - if (object == null) { - builder.appendNull(); - return; - } - - if (inspector instanceof PrimitiveObjectInspector primitiveObjectInspector) { - serializePrimitive(type, builder, object, primitiveObjectInspector); - } - else if (inspector instanceof ListObjectInspector listObjectInspector) { - serializeList(type, builder, object, listObjectInspector); - } - else if (inspector instanceof MapObjectInspector mapObjectInspector) { - serializeMap((MapType) type, (MapBlockBuilder) builder, object, mapObjectInspector, filterNullMapKeys); - } - else if (inspector instanceof StructObjectInspector structObjectInspector) { - serializeStruct(type, builder, object, structObjectInspector); - } - else if (inspector instanceof UnionObjectInspector unionObjectInspector) { - serializeUnion(type, builder, object, unionObjectInspector); - } - else { - throw new RuntimeException("Unknown object inspector category: " + inspector.getCategory()); - } - } - - private static void serializePrimitive(Type type, BlockBuilder builder, Object object, PrimitiveObjectInspector inspector) - { - requireNonNull(builder, "builder is null"); - - switch (inspector.getPrimitiveCategory()) { - case BOOLEAN: - type.writeBoolean(builder, ((BooleanObjectInspector) inspector).get(object)); - return; - case BYTE: - type.writeLong(builder, ((ByteObjectInspector) inspector).get(object)); - return; - case SHORT: - type.writeLong(builder, ((ShortObjectInspector) inspector).get(object)); - return; - case INT: - type.writeLong(builder, ((IntObjectInspector) inspector).get(object)); - return; - case LONG: - type.writeLong(builder, ((LongObjectInspector) inspector).get(object)); - return; - case FLOAT: - type.writeLong(builder, floatToRawIntBits(((FloatObjectInspector) inspector).get(object))); - return; - case DOUBLE: - type.writeDouble(builder, ((DoubleObjectInspector) inspector).get(object)); - return; - case STRING: - type.writeSlice(builder, Slices.utf8Slice(((StringObjectInspector) inspector).getPrimitiveJavaObject(object))); - return; - case VARCHAR: - type.writeSlice(builder, Slices.utf8Slice(((HiveVarcharObjectInspector) inspector).getPrimitiveJavaObject(object).getValue())); - return; - case CHAR: - HiveChar hiveChar = ((HiveCharObjectInspector) inspector).getPrimitiveJavaObject(object); - type.writeSlice(builder, truncateToLengthAndTrimSpaces(Slices.utf8Slice(hiveChar.getValue()), ((CharType) type).getLength())); - return; - case DATE: - type.writeLong(builder, formatDateAsLong(object, (DateObjectInspector) inspector)); - return; - case TIMESTAMP: - TimestampType timestampType = (TimestampType) type; - DecodedTimestamp timestamp = formatTimestamp(timestampType, object, (TimestampObjectInspector) inspector); - createTimestampEncoder(timestampType, DateTimeZone.UTC).write(timestamp, builder); - return; - case BINARY: - type.writeSlice(builder, Slices.wrappedBuffer(((BinaryObjectInspector) inspector).getPrimitiveJavaObject(object))); - return; - case DECIMAL: - DecimalType decimalType = (DecimalType) type; - HiveDecimalWritable hiveDecimal = ((HiveDecimalObjectInspector) inspector).getPrimitiveWritableObject(object); - if (decimalType.isShort()) { - type.writeLong(builder, DecimalUtils.getShortDecimalValue(hiveDecimal, decimalType.getScale())); - } - else { - type.writeObject(builder, DecimalUtils.getLongDecimalValue(hiveDecimal, decimalType.getScale())); - } - return; - case VOID: - case TIMESTAMPLOCALTZ: - case INTERVAL_YEAR_MONTH: - case INTERVAL_DAY_TIME: - case UNKNOWN: - // unsupported - } - throw new RuntimeException("Unknown primitive type: " + inspector.getPrimitiveCategory()); - } - - private static void serializeList(Type type, BlockBuilder builder, Object object, ListObjectInspector inspector) - { - List list = inspector.getList(object); - ArrayType arrayType = (ArrayType) type; - ObjectInspector elementInspector = inspector.getListElementObjectInspector(); - ((ArrayBlockBuilder) builder).buildEntry(elementBuilder -> buildList(list, arrayType.getElementType(), elementInspector, elementBuilder)); - } - - private static void buildList(List list, Type elementType, ObjectInspector elementInspector, BlockBuilder valueBuilder) - { - for (Object element : list) { - serializeObject(elementType, valueBuilder, element, elementInspector); - } - } - - private static void serializeMap(MapType mapType, MapBlockBuilder builder, Object object, MapObjectInspector inspector, boolean filterNullMapKeys) - { - Map map = inspector.getMap(object); - builder.buildEntry((keyBuilder, valueBuilder) -> buildMap(mapType, keyBuilder, valueBuilder, map, inspector, filterNullMapKeys)); - } - - private static void buildMap(MapType mapType, BlockBuilder keyBuilder, BlockBuilder valueBuilder, Map map, MapObjectInspector inspector, boolean filterNullMapKeys) - { - Type keyType = mapType.getKeyType(); - Type valueType = mapType.getValueType(); - ObjectInspector keyInspector = inspector.getMapKeyObjectInspector(); - ObjectInspector valueInspector = inspector.getMapValueObjectInspector(); - - for (Entry entry : map.entrySet()) { - // Hive skips map entries with null keys - if (!filterNullMapKeys || entry.getKey() != null) { - serializeObject(keyType, keyBuilder, entry.getKey(), keyInspector); - serializeObject(valueType, valueBuilder, entry.getValue(), valueInspector); - } - } - } - - private static void serializeStruct(Type type, BlockBuilder builder, Object object, StructObjectInspector inspector) - { - RowType rowType = (RowType) type; - ((RowBlockBuilder) builder).buildEntry(fieldBuilders -> buildStruct(rowType, object, inspector, fieldBuilders)); - } - - private static void buildStruct(RowType type, Object object, StructObjectInspector inspector, List fieldBuilders) - { - List typeParameters = type.getTypeParameters(); - List allStructFieldRefs = inspector.getAllStructFieldRefs(); - checkArgument(typeParameters.size() == allStructFieldRefs.size()); - for (int i = 0; i < typeParameters.size(); i++) { - StructField field = allStructFieldRefs.get(i); - serializeObject(typeParameters.get(i), fieldBuilders.get(i), inspector.getStructFieldData(object, field), field.getFieldObjectInspector()); - } - } - - // Use row blocks to represent union objects when reading - private static void serializeUnion(Type type, BlockBuilder builder, Object object, UnionObjectInspector inspector) - { - RowType rowType = (RowType) type; - ((RowBlockBuilder) builder).buildEntry(fieldBuilders -> buildUnion(rowType, object, inspector, fieldBuilders)); - } - - private static void buildUnion(RowType rowType, Object object, UnionObjectInspector inspector, List fieldBuilders) - { - byte tag = inspector.getTag(object); - TINYINT.writeLong(fieldBuilders.get(0), tag); - - List typeParameters = rowType.getTypeParameters(); - for (int i = 1; i < typeParameters.size(); i++) { - if (i == tag + 1) { - serializeObject(typeParameters.get(i), fieldBuilders.get(i), inspector.getField(object), inspector.getObjectInspectors().get(tag)); - } - else { - fieldBuilders.get(i).appendNull(); - } - } - } - - @SuppressWarnings("deprecation") - private static long formatDateAsLong(Object object, DateObjectInspector inspector) - { - if (object instanceof LazyDate) { - return ((LazyDate) object).getWritableObject().getDays(); - } - if (object instanceof DateWritable) { - return ((DateWritable) object).getDays(); - } - return inspector.getPrimitiveJavaObject(object).toEpochDay(); - } - - private static DecodedTimestamp formatTimestamp(TimestampType type, Object object, TimestampObjectInspector inspector) - { - long epochSecond; - int nanoOfSecond; - - if (object instanceof TimestampWritable timestamp) { - epochSecond = timestamp.getSeconds(); - nanoOfSecond = timestamp.getNanos(); - } - else { - Timestamp timestamp = inspector.getPrimitiveJavaObject(object); - epochSecond = timestamp.toEpochSecond(); - nanoOfSecond = timestamp.getNanos(); - } - - nanoOfSecond = (int) round(nanoOfSecond, 9 - type.getPrecision()); - if (nanoOfSecond == NANOSECONDS_PER_SECOND) { // round nanos up to seconds - epochSecond += 1; - nanoOfSecond = 0; - } - - return new DecodedTimestamp(epochSecond, nanoOfSecond); - } -} diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestSerDeUtils.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestSerDeUtils.java deleted file mode 100644 index 8bedfa7ce274..000000000000 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestSerDeUtils.java +++ /dev/null @@ -1,378 +0,0 @@ -/* - * 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 io.trino.plugin.hive.util; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.reflect.TypeToken; -import io.airlift.slice.DynamicSliceOutput; -import io.airlift.slice.Slice; -import io.airlift.slice.SliceOutput; -import io.airlift.slice.Slices; -import io.trino.block.BlockSerdeUtil; -import io.trino.spi.block.ArrayBlockBuilder; -import io.trino.spi.block.Block; -import io.trino.spi.block.BlockBuilder; -import io.trino.spi.block.BlockEncodingSerde; -import io.trino.spi.block.MapBlockBuilder; -import io.trino.spi.block.RowBlockBuilder; -import io.trino.spi.block.TestingBlockEncodingSerde; -import io.trino.spi.type.ArrayType; -import io.trino.spi.type.MapType; -import io.trino.spi.type.RowType; -import org.apache.hadoop.hive.common.type.Date; -import org.apache.hadoop.hive.common.type.Timestamp; -import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector; -import org.apache.hadoop.io.BytesWritable; -import org.joda.time.DateTime; -import org.junit.jupiter.api.Test; - -import java.lang.reflect.Type; -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; -import java.util.function.Consumer; - -import static io.trino.plugin.hive.HiveTestUtils.mapType; -import static io.trino.plugin.hive.util.SerDeUtils.serializeObject; -import static io.trino.spi.type.BigintType.BIGINT; -import static io.trino.spi.type.BooleanType.BOOLEAN; -import static io.trino.spi.type.DateType.DATE; -import static io.trino.spi.type.DoubleType.DOUBLE; -import static io.trino.spi.type.IntegerType.INTEGER; -import static io.trino.spi.type.RealType.REAL; -import static io.trino.spi.type.SmallintType.SMALLINT; -import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; -import static io.trino.spi.type.TinyintType.TINYINT; -import static io.trino.spi.type.VarbinaryType.VARBINARY; -import static io.trino.spi.type.VarcharType.VARCHAR; -import static io.trino.testing.StructuralTestUtil.rowBlockOf; -import static java.lang.Math.toIntExact; -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory.ObjectInspectorOptions; -import static org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory.getReflectionObjectInspector; -import static org.testng.Assert.assertEquals; - -@SuppressWarnings("PackageVisibleField") -public class TestSerDeUtils -{ - private final BlockEncodingSerde blockEncodingSerde = new TestingBlockEncodingSerde(); - - @SuppressWarnings("UnusedVariable") // these fields are serialized to a Block and verified there - private static class ListHolder - { - List array; - } - - @SuppressWarnings("UnusedVariable") // these fields are serialized to a Block and verified there - private static class InnerStruct - { - public InnerStruct(Integer intVal, Long longVal) - { - this.intVal = intVal; - this.longVal = longVal; - } - - Integer intVal; - Long longVal; - } - - @SuppressWarnings("UnusedVariable") // these fields are serialized to a Block and verified there - private static class OuterStruct - { - Byte byteVal; - Short shortVal; - Integer intVal; - Long longVal; - Float floatVal; - Double doubleVal; - String stringVal; - byte[] byteArray; - List structArray; - Map map; - InnerStruct innerStruct; - } - - private static synchronized ObjectInspector getInspector(Type type) - { - // ObjectInspectorFactory.getReflectionObjectInspector is not thread-safe although it - // gives people a first impression that it is. This may have been fixed in HIVE-11586. - - // Trino only uses getReflectionObjectInspector here, in a test method. Therefore, we - // choose to work around this issue by synchronizing this method. Before synchronizing - // this method, test in this class fails approximately 1 out of 10 runs on Travis. - - return getReflectionObjectInspector(type, ObjectInspectorOptions.JAVA); - } - - @Test - public void testPrimitiveSlice() - { - // boolean - Block expectedBoolean = createSingleValue(BOOLEAN, blockBuilder -> BOOLEAN.writeBoolean(blockBuilder, true)); - Block actualBoolean = toSingleValueBlock(BOOLEAN, true, getInspector(Boolean.class)); - assertBlockEquals(actualBoolean, expectedBoolean); - - // byte - Block expectedByte = createSingleValue(TINYINT, blockBuilder -> TINYINT.writeLong(blockBuilder, 5)); - Block actualByte = toSingleValueBlock(TINYINT, (byte) 5, getInspector(Byte.class)); - assertBlockEquals(actualByte, expectedByte); - - // short - Block expectedShort = createSingleValue(SMALLINT, blockBuilder -> SMALLINT.writeLong(blockBuilder, 2)); - Block actualShort = toSingleValueBlock(SMALLINT, (short) 2, getInspector(Short.class)); - assertBlockEquals(actualShort, expectedShort); - - // int - Block expectedInt = createSingleValue(INTEGER, blockBuilder -> INTEGER.writeLong(blockBuilder, 1)); - Block actualInt = toSingleValueBlock(INTEGER, 1, getInspector(Integer.class)); - assertBlockEquals(actualInt, expectedInt); - - // long - Block expectedLong = createSingleValue(BIGINT, blockBuilder -> BIGINT.writeLong(blockBuilder, 10)); - Block actualLong = toSingleValueBlock(BIGINT, 10L, getInspector(Long.class)); - assertBlockEquals(actualLong, expectedLong); - - // float - Block expectedFloat = createSingleValue(REAL, blockBuilder -> REAL.writeLong(blockBuilder, Float.floatToIntBits(20.0f))); - Block actualFloat = toSingleValueBlock(REAL, 20.0f, getInspector(Float.class)); - assertBlockEquals(actualFloat, expectedFloat); - - // double - Block expectedDouble = createSingleValue(DOUBLE, blockBuilder -> DOUBLE.writeDouble(blockBuilder, 30.12d)); - Block actualDouble = toSingleValueBlock(DOUBLE, 30.12d, getInspector(Double.class)); - assertBlockEquals(actualDouble, expectedDouble); - - // string - Block expectedString = createSingleValue(VARCHAR, blockBuilder -> VARCHAR.writeString(blockBuilder, "value")); - Block actualString = toSingleValueBlock(VARCHAR, "value", getInspector(String.class)); - assertBlockEquals(actualString, expectedString); - - // date - int date = toIntExact(LocalDate.of(2008, 10, 28).toEpochDay()); - Block expectedDate = createSingleValue(DATE, blockBuilder -> DATE.writeLong(blockBuilder, date)); - Block actualDate = toSingleValueBlock(DATE, Date.ofEpochDay(date), getInspector(Date.class)); - assertBlockEquals(actualDate, expectedDate); - - // timestamp - DateTime dateTime = new DateTime(2008, 10, 28, 16, 7, 15, 123); - Block expectedTimestamp = createSingleValue(TIMESTAMP_MILLIS, blockBuilder -> TIMESTAMP_MILLIS.writeLong(blockBuilder, dateTime.getMillis() * 1000)); - Block actualTimestamp = toSingleValueBlock(TIMESTAMP_MILLIS, Timestamp.ofEpochMilli(dateTime.getMillis()), getInspector(Timestamp.class)); - assertBlockEquals(actualTimestamp, expectedTimestamp); - - // binary - byte[] byteArray = {81, 82, 84, 85}; - Block expectedBinary = createSingleValue(VARBINARY, blockBuilder -> VARBINARY.writeSlice(blockBuilder, Slices.wrappedBuffer(byteArray))); - Block actualBinary = toSingleValueBlock(VARBINARY, byteArray, getInspector(byte[].class)); - assertBlockEquals(actualBinary, expectedBinary); - } - - private static Block createSingleValue(io.trino.spi.type.Type type, Consumer outputConsumer) - { - BlockBuilder blockBuilder = type.createBlockBuilder(null, 1); - outputConsumer.accept(blockBuilder); - return blockBuilder.build(); - } - - @Test - public void testListBlock() - { - List array = new ArrayList<>(2); - array.add(new InnerStruct(8, 9L)); - array.add(new InnerStruct(10, 11L)); - ListHolder listHolder = new ListHolder(); - listHolder.array = array; - - RowType arrayValueType = RowType.anonymous(ImmutableList.of(INTEGER, BIGINT)); - ArrayType arrayType = new ArrayType(arrayValueType); - RowType rowWithArrayField = RowType.anonymousRow(arrayType); - - Block actual = toSingleValueBlock(rowWithArrayField, listHolder, getInspector(ListHolder.class)); - - RowBlockBuilder rowBlockBuilder = rowWithArrayField.createBlockBuilder(null, 1); - rowBlockBuilder.buildEntry(fieldBuilders -> { - ((ArrayBlockBuilder) fieldBuilders.get(0)).buildEntry(elementBuilder -> { - arrayValueType.writeObject(elementBuilder, rowBlockOf(ImmutableList.of(INTEGER, BIGINT), 8, 9L)); - arrayValueType.writeObject(elementBuilder, rowBlockOf(ImmutableList.of(INTEGER, BIGINT), 10, 11L)); - }); - }); - Block expected = rowBlockBuilder.build(); - - assertBlockEquals(actual, expected); - } - - private static class MapHolder - { - Map map; - } - - @Test - public void testMapBlock() - { - MapHolder holder = new MapHolder(); - holder.map = new TreeMap<>(); - holder.map.put("twelve", new InnerStruct(13, 14L)); - holder.map.put("fifteen", new InnerStruct(16, 17L)); - - RowType mapValueType = RowType.anonymous(ImmutableList.of(INTEGER, BIGINT)); - MapType mapType = mapType(VARCHAR, mapValueType); - RowType rowOfMapOfVarcharRowType = RowType.anonymousRow(mapType); - - Block actual = toSingleValueBlock(rowOfMapOfVarcharRowType, holder, getInspector(MapHolder.class)); - - RowBlockBuilder rowBlockBuilder = rowOfMapOfVarcharRowType.createBlockBuilder(null, 1); - rowBlockBuilder.buildEntry(fieldBuilders -> { - ((MapBlockBuilder) fieldBuilders.get(0)).buildEntry((keyBuilder, valueBuilder) -> { - VARCHAR.writeString(keyBuilder, "fifteen"); - mapValueType.writeObject(valueBuilder, rowBlockOf(mapValueType.getTypeParameters(), 16, 17L)); - VARCHAR.writeString(keyBuilder, "twelve"); - mapValueType.writeObject(valueBuilder, rowBlockOf(mapValueType.getTypeParameters(), 13, 14L)); - }); - }); - Block expected = rowBlockBuilder.build(); - - assertBlockEquals(actual, expected); - } - - @Test - public void testStructBlock() - { - // test simple structs - InnerStruct innerStruct = new InnerStruct(13, 14L); - - RowType rowType = RowType.anonymousRow(INTEGER, BIGINT); - Block actual = toSingleValueBlock(rowType, innerStruct, getInspector(InnerStruct.class)); - - RowBlockBuilder rowBlockBuilder = rowType.createBlockBuilder(null, 1); - rowBlockBuilder.buildEntry(fieldBuilders -> { - INTEGER.writeLong(fieldBuilders.get(0), 13); - BIGINT.writeLong(fieldBuilders.get(1), 14L); - }); - Block expected = rowBlockBuilder.build(); - assertBlockEquals(actual, expected); - - // test complex structs - OuterStruct outerStruct = new OuterStruct(); - outerStruct.byteVal = (byte) 1; - outerStruct.shortVal = (short) 2; - outerStruct.intVal = 3; - outerStruct.longVal = 4L; - outerStruct.floatVal = 5.01f; - outerStruct.doubleVal = 6.001d; - outerStruct.stringVal = "seven"; - outerStruct.byteArray = new byte[] {'2'}; - InnerStruct is1 = new InnerStruct(2, -5L); - InnerStruct is2 = new InnerStruct(-10, 0L); - outerStruct.structArray = new ArrayList<>(2); - outerStruct.structArray.add(is1); - outerStruct.structArray.add(is2); - outerStruct.map = new TreeMap<>(); - outerStruct.map.put("twelve", new InnerStruct(0, 5L)); - outerStruct.map.put("fifteen", new InnerStruct(-5, -10L)); - outerStruct.innerStruct = new InnerStruct(18, 19L); - - RowType innerRowType = RowType.anonymousRow(INTEGER, BIGINT); - ArrayType arrayOfInnerRowType = new ArrayType(innerRowType); - MapType mapOfInnerRowType = mapType(VARCHAR, innerRowType); - RowType outerRowType = RowType.anonymousRow( - TINYINT, - SMALLINT, - INTEGER, - BIGINT, - REAL, - DOUBLE, - VARCHAR, - VARCHAR, - arrayOfInnerRowType, - mapOfInnerRowType, - innerRowType); - - actual = toSingleValueBlock(outerRowType, outerStruct, getInspector(OuterStruct.class)); - - rowBlockBuilder = outerRowType.createBlockBuilder(null, 1); - rowBlockBuilder.buildEntry(fieldBuilders -> { - TINYINT.writeLong(fieldBuilders.get(0), (byte) 1); - SMALLINT.writeLong(fieldBuilders.get(1), (short) 2); - INTEGER.writeLong(fieldBuilders.get(2), 3); - BIGINT.writeLong(fieldBuilders.get(3), 4L); - REAL.writeLong(fieldBuilders.get(4), Float.floatToIntBits(5.01f)); - DOUBLE.writeDouble(fieldBuilders.get(5), 6.001d); - VARCHAR.writeString(fieldBuilders.get(6), "seven"); - VARCHAR.writeString(fieldBuilders.get(7), "2"); - ((ArrayBlockBuilder) fieldBuilders.get(8)).buildEntry(elementBuilder -> { - innerRowType.writeObject(elementBuilder, rowBlockOf(innerRowType.getTypeParameters(), 2, -5L)); - innerRowType.writeObject(elementBuilder, rowBlockOf(innerRowType.getTypeParameters(), -10, 0L)); - }); - ((MapBlockBuilder) fieldBuilders.get(9)).buildEntry((keyBuilder, valueBuilder) -> { - VARCHAR.writeString(keyBuilder, "fifteen"); - innerRowType.writeObject(valueBuilder, rowBlockOf(innerRowType.getTypeParameters(), -5, -10L)); - VARCHAR.writeString(keyBuilder, "twelve"); - innerRowType.writeObject(valueBuilder, rowBlockOf(innerRowType.getTypeParameters(), 0, 5L)); - }); - innerRowType.writeObject(fieldBuilders.get(10), rowBlockOf(innerRowType.getTypeParameters(), 18, 19L)); - }); - expected = rowBlockBuilder.build(); - - assertBlockEquals(actual, expected); - } - - @Test - public void testReuse() - { - BytesWritable value = new BytesWritable(); - - byte[] first = "hello world".getBytes(UTF_8); - value.set(first, 0, first.length); - - byte[] second = "bye".getBytes(UTF_8); - value.set(second, 0, second.length); - - Type type = new TypeToken>() {}.getType(); - ObjectInspector inspector = getInspector(type); - - MapType mapType = mapType(VARCHAR, BIGINT); - Block actual = toSingleValueBlock(mapType, ImmutableMap.of(value, 0L), inspector); - - MapBlockBuilder blockBuilder = mapType.createBlockBuilder(null, 1); - blockBuilder.buildEntry((keyBuilder, valueBuilder) -> { - VARCHAR.writeString(keyBuilder, "bye"); - BIGINT.writeLong(valueBuilder, 0L); - }); - Block expected = blockBuilder.build(); - - assertBlockEquals(actual, expected); - } - - private void assertBlockEquals(Block actual, Block expected) - { - assertEquals(blockToSlice(actual), blockToSlice(expected)); - } - - private Slice blockToSlice(Block block) - { - // This function is strictly for testing use only - SliceOutput sliceOutput = new DynamicSliceOutput(1000); - BlockSerdeUtil.writeBlock(blockEncodingSerde, sliceOutput, block); - return sliceOutput.slice(); - } - - private static Block toSingleValueBlock(io.trino.spi.type.Type type, Object object, ObjectInspector inspector) - { - BlockBuilder builder = type.createBlockBuilder(null, 1); - serializeObject(type, builder, object, inspector); - return builder.build(); - } -} From 2f8f4a178ad40be8f08d3ec80c533f1c194502d8 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Mon, 6 Nov 2023 10:23:07 -0800 Subject: [PATCH 096/587] Replace Path with Location in BaseHiveConnectorTest --- .../test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java index c37f779904fc..1e32e57567d2 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java @@ -23,6 +23,7 @@ import io.trino.Session; import io.trino.cost.StatsAndCosts; import io.trino.execution.QueryInfo; +import io.trino.filesystem.Location; import io.trino.metadata.FunctionManager; import io.trino.metadata.Metadata; import io.trino.metadata.QualifiedObjectName; @@ -2933,7 +2934,7 @@ public void testUnregisterRegisterPartition() List paths = getQueryRunner().execute(getSession(), "SELECT \"$path\" FROM " + tableName + " ORDER BY \"$path\" ASC").toTestTypes().getMaterializedRows(); assertEquals(paths.size(), 3); - String firstPartition = new Path((String) paths.get(0).getField(0)).getParent().toString(); + String firstPartition = Location.of((String) paths.get(0).getField(0)).parentDirectory().toString(); assertAccessDenied( format("CALL system.unregister_partition('%s', '%s', ARRAY['part'], ARRAY['first'])", TPCH_SCHEMA, tableName), From 664b94bbb8811130ac388cca57c13520607360ff Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Mon, 6 Nov 2023 10:44:27 -0800 Subject: [PATCH 097/587] Replace Path with Location in BaseHiveConnectorTest --- .../java/io/trino/plugin/hive/BaseHiveConnectorTest.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java index 1e32e57567d2..255331d15991 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java @@ -66,7 +66,6 @@ import io.trino.testing.sql.TestTable; import io.trino.testing.sql.TrinoSqlExecutor; import io.trino.type.TypeDeserializer; -import org.apache.hadoop.fs.Path; import org.assertj.core.api.AbstractLongAssert; import org.intellij.lang.annotations.Language; import org.testng.SkipException; @@ -4404,8 +4403,7 @@ private void testCreateExternalTable( // Table properties StringJoiner propertiesSql = new StringJoiner(",\n "); - propertiesSql.add( - format("external_location = '%s'", new Path(tempDir.toUri().toASCIIString()))); + propertiesSql.add(format("external_location = '%s'", tempDir.toUri().toASCIIString())); propertiesSql.add("format = 'TEXTFILE'"); tableProperties.forEach(propertiesSql::add); @@ -4737,7 +4735,7 @@ private void testPathHiddenColumn(Session session, HiveStorageFormat storageForm int col0 = (int) row.getField(0); int col1 = (int) row.getField(1); String pathName = (String) row.getField(2); - String parentDirectory = new Path(pathName).getParent().toString(); + String parentDirectory = Location.of(pathName).parentDirectory().toString(); assertTrue(pathName.length() > 0); assertEquals(col0 % 3, col1); From dd05b4bde1e423adf6518c7235d93d1cd2b391a3 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Mon, 6 Nov 2023 11:15:37 -0800 Subject: [PATCH 098/587] Convert TestAcidTables to memory file system --- .../plugin/hive/util/TestAcidTables.java | 394 +++++++++--------- 1 file changed, 194 insertions(+), 200 deletions(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestAcidTables.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestAcidTables.java index ed4893f44486..bc78bccbcc82 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestAcidTables.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestAcidTables.java @@ -16,23 +16,14 @@ import io.trino.filesystem.FileEntry; import io.trino.filesystem.Location; import io.trino.filesystem.TrinoFileSystem; -import io.trino.filesystem.hdfs.HdfsFileSystemFactory; -import io.trino.hdfs.HdfsConfig; -import io.trino.hdfs.HdfsConfiguration; -import io.trino.hdfs.HdfsEnvironment; -import io.trino.hdfs.TrinoHdfsFileSystemStats; -import io.trino.hdfs.authentication.NoHdfsAuthentication; +import io.trino.filesystem.memory.MemoryFileSystem; import io.trino.plugin.hive.util.AcidTables.AcidState; import io.trino.plugin.hive.util.AcidTables.ParsedBase; import io.trino.plugin.hive.util.AcidTables.ParsedDelta; -import io.trino.plugin.hive.util.FileSystemTesting.MockFile; -import io.trino.plugin.hive.util.FileSystemTesting.MockFileSystem; -import io.trino.spi.security.ConnectorIdentity; -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.fs.FileSystem; -import org.apache.hadoop.fs.Path; import org.junit.jupiter.api.Test; +import java.io.IOException; +import java.io.OutputStream; import java.util.List; import static io.trino.plugin.hive.util.AcidTables.deleteDeltaSubdir; @@ -109,20 +100,21 @@ public void testParseDelta() public void testOriginal() throws Exception { - MockFileSystem fs = new MockFileSystem(new Configuration(false), - new MockFile("mock:/tbl/part1/000000_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/000000_0_copy_1", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/000000_0_copy_2", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/000001_1", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/000002_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/random", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/_done", 0, FAKE_DATA), - new MockFile("mock:/tbl/part1/_tmp/000000_0", 0, FAKE_DATA), - new MockFile("mock:/tbl/part1/_tmp/abc/000000_0", 0, FAKE_DATA), - new MockFile("mock:/tbl/part1/subdir/000000_0", 0, FAKE_DATA)); + TrinoFileSystem fileSystem = new MemoryFileSystem(); + createFile(fileSystem, "memory:///tbl/part1/000000_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/000000_0_copy_1", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/000000_0_copy_2", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/000001_1", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/000002_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/random", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/_done", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/_tmp/000000_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/_tmp/abc/000000_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/subdir/000000_0", FAKE_DATA); + AcidState state = getAcidState( - testingTrinoFileSystem(fs), - Location.of("mock:///tbl/part1"), + fileSystem, + Location.of("memory:///tbl/part1"), new ValidWriteIdList("tbl:100:%d:".formatted(Long.MAX_VALUE))); assertThat(state.baseDirectory()).isEmpty(); @@ -130,56 +122,57 @@ public void testOriginal() List files = state.originalFiles(); assertEquals(files.size(), 7); - assertEquals(files.get(0).location(), Location.of("mock:///tbl/part1/000000_0")); - assertEquals(files.get(1).location(), Location.of("mock:///tbl/part1/000000_0_copy_1")); - assertEquals(files.get(2).location(), Location.of("mock:///tbl/part1/000000_0_copy_2")); - assertEquals(files.get(3).location(), Location.of("mock:///tbl/part1/000001_1")); - assertEquals(files.get(4).location(), Location.of("mock:///tbl/part1/000002_0")); - assertEquals(files.get(5).location(), Location.of("mock:///tbl/part1/random")); - assertEquals(files.get(6).location(), Location.of("mock:///tbl/part1/subdir/000000_0")); + assertEquals(files.get(0).location(), Location.of("memory:///tbl/part1/000000_0")); + assertEquals(files.get(1).location(), Location.of("memory:///tbl/part1/000000_0_copy_1")); + assertEquals(files.get(2).location(), Location.of("memory:///tbl/part1/000000_0_copy_2")); + assertEquals(files.get(3).location(), Location.of("memory:///tbl/part1/000001_1")); + assertEquals(files.get(4).location(), Location.of("memory:///tbl/part1/000002_0")); + assertEquals(files.get(5).location(), Location.of("memory:///tbl/part1/random")); + assertEquals(files.get(6).location(), Location.of("memory:///tbl/part1/subdir/000000_0")); } @Test public void testOriginalDeltas() throws Exception { - MockFileSystem fs = new MockFileSystem(new Configuration(false), - new MockFile("mock:/tbl/part1/000000_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/000001_1", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/000002_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/random", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/_done", 0, FAKE_DATA), - new MockFile("mock:/tbl/part1/_tmp/000000_0", 0, FAKE_DATA), - new MockFile("mock:/tbl/part1/_tmp/delta_025_025/000000_0", 0, FAKE_DATA), - new MockFile("mock:/tbl/part1/subdir/000000_0", 0, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_025_025/bucket_0", 0, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_029_029/bucket_0", 0, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_025_030/bucket_0", 0, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_050_100/bucket_0", 0, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_101_101/bucket_0", 0, FAKE_DATA)); + TrinoFileSystem fileSystem = new MemoryFileSystem(); + createFile(fileSystem, "memory:///tbl/part1/000000_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/000001_1", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/000002_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/random", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/_done", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/_tmp/000000_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/_tmp/delta_025_025/000000_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/subdir/000000_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_025_025/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_029_029/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_025_030/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_050_100/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_101_101/bucket_0", FAKE_DATA); + AcidState state = getAcidState( - testingTrinoFileSystem(fs), - Location.of("mock:///tbl/part1"), + fileSystem, + Location.of("memory:///tbl/part1"), new ValidWriteIdList("tbl:100:%d:".formatted(Long.MAX_VALUE))); assertThat(state.baseDirectory()).isEmpty(); List files = state.originalFiles(); assertEquals(files.size(), 5); - assertEquals(files.get(0).location(), Location.of("mock:///tbl/part1/000000_0")); - assertEquals(files.get(1).location(), Location.of("mock:///tbl/part1/000001_1")); - assertEquals(files.get(2).location(), Location.of("mock:///tbl/part1/000002_0")); - assertEquals(files.get(3).location(), Location.of("mock:///tbl/part1/random")); - assertEquals(files.get(4).location(), Location.of("mock:///tbl/part1/subdir/000000_0")); + assertEquals(files.get(0).location(), Location.of("memory:///tbl/part1/000000_0")); + assertEquals(files.get(1).location(), Location.of("memory:///tbl/part1/000001_1")); + assertEquals(files.get(2).location(), Location.of("memory:///tbl/part1/000002_0")); + assertEquals(files.get(3).location(), Location.of("memory:///tbl/part1/random")); + assertEquals(files.get(4).location(), Location.of("memory:///tbl/part1/subdir/000000_0")); List deltas = state.deltas(); assertEquals(deltas.size(), 2); ParsedDelta delta = deltas.get(0); - assertEquals(delta.path(), "mock:///tbl/part1/delta_025_030"); + assertEquals(delta.path(), "memory:///tbl/part1/delta_025_030"); assertEquals(delta.min(), 25); assertEquals(delta.max(), 30); delta = deltas.get(1); - assertEquals(delta.path(), "mock:///tbl/part1/delta_050_100"); + assertEquals(delta.path(), "memory:///tbl/part1/delta_050_100"); assertEquals(delta.min(), 50); assertEquals(delta.max(), 100); } @@ -188,29 +181,30 @@ public void testOriginalDeltas() public void testBaseDeltas() throws Exception { - MockFileSystem fs = new MockFileSystem(new Configuration(false), - new MockFile("mock:/tbl/part1/_tmp/bucket_0", 0, FAKE_DATA), - new MockFile("mock:/tbl/part1/_tmp/base_5/bucket_0", 0, FAKE_DATA), - new MockFile("mock:/tbl/part1/base_5/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/base_10/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/base_49/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_025_025/bucket_0", 0, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_029_029/bucket_0", 0, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_025_030/bucket_0", 0, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_050_105/bucket_0", 0, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_90_120/bucket_0", 0, FAKE_DATA)); + TrinoFileSystem fileSystem = new MemoryFileSystem(); + createFile(fileSystem, "memory:///tbl/part1/_tmp/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/_tmp/base_5/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/base_5/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/base_10/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/base_49/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_025_025/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_029_029/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_025_030/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_050_105/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_90_120/bucket_0", FAKE_DATA); + AcidState dir = getAcidState( - testingTrinoFileSystem(fs), - Location.of("mock:///tbl/part1"), + fileSystem, + Location.of("memory:///tbl/part1"), new ValidWriteIdList("tbl:100:%d:".formatted(Long.MAX_VALUE))); - assertThat(dir.baseDirectory()).contains(Location.of("mock:///tbl/part1/base_49")); + assertThat(dir.baseDirectory()).contains(Location.of("memory:///tbl/part1/base_49")); assertEquals(dir.originalFiles().size(), 0); List deltas = dir.deltas(); assertEquals(deltas.size(), 1); ParsedDelta delta = deltas.get(0); - assertEquals(delta.path(), "mock:///tbl/part1/delta_050_105"); + assertEquals(delta.path(), "memory:///tbl/part1/delta_050_105"); assertEquals(delta.min(), 50); assertEquals(delta.max(), 105); } @@ -219,145 +213,151 @@ public void testBaseDeltas() public void testObsoleteOriginals() throws Exception { - MockFileSystem fs = new MockFileSystem(new Configuration(false), - new MockFile("mock:/tbl/part1/base_10/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/base_5/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/000000_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/000001_1", 500, FAKE_DATA)); + TrinoFileSystem fileSystem = new MemoryFileSystem(); + createFile(fileSystem, "memory:///tbl/part1/base_10/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/base_5/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/000000_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/000001_1", FAKE_DATA); + AcidState state = getAcidState( - testingTrinoFileSystem(fs), - Location.of("mock:///tbl/part1"), + fileSystem, + Location.of("memory:///tbl/part1"), new ValidWriteIdList("tbl:150:%d:".formatted(Long.MAX_VALUE))); - assertThat(state.baseDirectory()).contains(Location.of("mock:///tbl/part1/base_10")); + assertThat(state.baseDirectory()).contains(Location.of("memory:///tbl/part1/base_10")); } @Test public void testOverlapingDelta() throws Exception { - MockFileSystem fs = new MockFileSystem(new Configuration(false), - new MockFile("mock:/tbl/part1/delta_0000063_63/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_000062_62/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_00061_61/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_40_60/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_0060_60/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_052_55/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/base_50/bucket_0", 500, FAKE_DATA)); + TrinoFileSystem fileSystem = new MemoryFileSystem(); + createFile(fileSystem, "memory:///tbl/part1/delta_0000063_63/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_000062_62/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_00061_61/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_40_60/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_0060_60/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_052_55/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/base_50/bucket_0", FAKE_DATA); + AcidState state = getAcidState( - testingTrinoFileSystem(fs), - Location.of("mock:///tbl/part1"), + fileSystem, + Location.of("memory:///tbl/part1"), new ValidWriteIdList("tbl:100:%d:".formatted(Long.MAX_VALUE))); - assertThat(state.baseDirectory()).contains(Location.of("mock:///tbl/part1/base_50")); + assertThat(state.baseDirectory()).contains(Location.of("memory:///tbl/part1/base_50")); List deltas = state.deltas(); assertEquals(deltas.size(), 4); - assertEquals(deltas.get(0).path(), "mock:///tbl/part1/delta_40_60"); - assertEquals(deltas.get(1).path(), "mock:///tbl/part1/delta_00061_61"); - assertEquals(deltas.get(2).path(), "mock:///tbl/part1/delta_000062_62"); - assertEquals(deltas.get(3).path(), "mock:///tbl/part1/delta_0000063_63"); + assertEquals(deltas.get(0).path(), "memory:///tbl/part1/delta_40_60"); + assertEquals(deltas.get(1).path(), "memory:///tbl/part1/delta_00061_61"); + assertEquals(deltas.get(2).path(), "memory:///tbl/part1/delta_000062_62"); + assertEquals(deltas.get(3).path(), "memory:///tbl/part1/delta_0000063_63"); } @Test public void testOverlapingDelta2() throws Exception { - MockFileSystem fs = new MockFileSystem(new Configuration(false), - new MockFile("mock:/tbl/part1/delta_0000063_63_0/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_000062_62_0/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_000062_62_3/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_00061_61_0/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_40_60/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_0060_60_1/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_0060_60_4/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_0060_60_7/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_052_55/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_058_58/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/base_50/bucket_0", 500, FAKE_DATA)); + TrinoFileSystem fileSystem = new MemoryFileSystem(); + createFile(fileSystem, "memory:///tbl/part1/delta_0000063_63_0/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_000062_62_0/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_000062_62_3/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_00061_61_0/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_40_60/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_0060_60_1/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_0060_60_4/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_0060_60_7/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_052_55/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_058_58/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/base_50/bucket_0", FAKE_DATA); + AcidState state = getAcidState( - testingTrinoFileSystem(fs), - Location.of("mock:///tbl/part1"), + fileSystem, + Location.of("memory:///tbl/part1"), new ValidWriteIdList("tbl:100:%d:".formatted(Long.MAX_VALUE))); - assertThat(state.baseDirectory()).contains(Location.of("mock:///tbl/part1/base_50")); + assertThat(state.baseDirectory()).contains(Location.of("memory:///tbl/part1/base_50")); List deltas = state.deltas(); assertEquals(deltas.size(), 5); - assertEquals(deltas.get(0).path(), "mock:///tbl/part1/delta_40_60"); - assertEquals(deltas.get(1).path(), "mock:///tbl/part1/delta_00061_61_0"); - assertEquals(deltas.get(2).path(), "mock:///tbl/part1/delta_000062_62_0"); - assertEquals(deltas.get(3).path(), "mock:///tbl/part1/delta_000062_62_3"); - assertEquals(deltas.get(4).path(), "mock:///tbl/part1/delta_0000063_63_0"); + assertEquals(deltas.get(0).path(), "memory:///tbl/part1/delta_40_60"); + assertEquals(deltas.get(1).path(), "memory:///tbl/part1/delta_00061_61_0"); + assertEquals(deltas.get(2).path(), "memory:///tbl/part1/delta_000062_62_0"); + assertEquals(deltas.get(3).path(), "memory:///tbl/part1/delta_000062_62_3"); + assertEquals(deltas.get(4).path(), "memory:///tbl/part1/delta_0000063_63_0"); } @Test public void deltasWithOpenTxnInRead() throws Exception { - MockFileSystem fs = new MockFileSystem(new Configuration(false), - new MockFile("mock:/tbl/part1/delta_1_1/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_2_5/bucket_0", 500, FAKE_DATA)); + TrinoFileSystem fileSystem = new MemoryFileSystem(); + createFile(fileSystem, "memory:///tbl/part1/delta_1_1/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_2_5/bucket_0", FAKE_DATA); + AcidState state = getAcidState( - testingTrinoFileSystem(fs), - Location.of("mock:///tbl/part1"), + fileSystem, + Location.of("memory:///tbl/part1"), new ValidWriteIdList("tbl:100:4:4")); List deltas = state.deltas(); assertEquals(deltas.size(), 2); - assertEquals(deltas.get(0).path(), "mock:///tbl/part1/delta_1_1"); - assertEquals(deltas.get(1).path(), "mock:///tbl/part1/delta_2_5"); + assertEquals(deltas.get(0).path(), "memory:///tbl/part1/delta_1_1"); + assertEquals(deltas.get(1).path(), "memory:///tbl/part1/delta_2_5"); } @Test public void deltasWithOpenTxnInRead2() throws Exception { - MockFileSystem fs = new MockFileSystem(new Configuration(false), - new MockFile("mock:/tbl/part1/delta_1_1/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_2_5/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_4_4_1/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_4_4_3/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_101_101_1/bucket_0", 500, FAKE_DATA)); + TrinoFileSystem fileSystem = new MemoryFileSystem(); + createFile(fileSystem, "memory:///tbl/part1/delta_1_1/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_2_5/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_4_4_1/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_4_4_3/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_101_101_1/bucket_0", FAKE_DATA); + AcidState state = getAcidState( - testingTrinoFileSystem(fs), - Location.of("mock:///tbl/part1"), + fileSystem, + Location.of("memory:///tbl/part1"), new ValidWriteIdList("tbl:100:4:4")); List deltas = state.deltas(); assertEquals(deltas.size(), 2); - assertEquals(deltas.get(0).path(), "mock:///tbl/part1/delta_1_1"); - assertEquals(deltas.get(1).path(), "mock:///tbl/part1/delta_2_5"); + assertEquals(deltas.get(0).path(), "memory:///tbl/part1/delta_1_1"); + assertEquals(deltas.get(1).path(), "memory:///tbl/part1/delta_2_5"); } @Test public void testBaseWithDeleteDeltas() throws Exception { - MockFileSystem fs = new MockFileSystem(new Configuration(false), - new MockFile("mock:/tbl/part1/base_5/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/base_10/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/base_49/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_025_025/bucket_0", 0, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_029_029/bucket_0", 0, FAKE_DATA), - new MockFile("mock:/tbl/part1/delete_delta_029_029/bucket_0", 0, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_025_030/bucket_0", 0, FAKE_DATA), - new MockFile("mock:/tbl/part1/delete_delta_025_030/bucket_0", 0, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_050_105/bucket_0", 0, FAKE_DATA), - new MockFile("mock:/tbl/part1/delete_delta_050_105/bucket_0", 0, FAKE_DATA), - new MockFile("mock:/tbl/part1/delete_delta_110_110/bucket_0", 0, FAKE_DATA)); + TrinoFileSystem fileSystem = new MemoryFileSystem(); + createFile(fileSystem, "memory:///tbl/part1/base_5/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/base_10/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/base_49/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_025_025/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_029_029/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delete_delta_029_029/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_025_030/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delete_delta_025_030/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_050_105/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delete_delta_050_105/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delete_delta_110_110/bucket_0", FAKE_DATA); + AcidState state = getAcidState( - testingTrinoFileSystem(fs), - Location.of("mock:///tbl/part1"), + fileSystem, + Location.of("memory:///tbl/part1"), new ValidWriteIdList("tbl:100:%d:".formatted(Long.MAX_VALUE))); - assertThat(state.baseDirectory()).contains(Location.of("mock:///tbl/part1/base_49")); + assertThat(state.baseDirectory()).contains(Location.of("memory:///tbl/part1/base_49")); assertThat(state.originalFiles()).isEmpty(); List deltas = state.deltas(); assertEquals(deltas.size(), 2); - assertEquals(deltas.get(0).path(), "mock:///tbl/part1/delete_delta_050_105"); - assertEquals(deltas.get(1).path(), "mock:///tbl/part1/delta_050_105"); + assertEquals(deltas.get(0).path(), "memory:///tbl/part1/delete_delta_050_105"); + assertEquals(deltas.get(1).path(), "memory:///tbl/part1/delta_050_105"); // The delete_delta_110_110 should not be read because it is greater than the high watermark. } @@ -365,32 +365,33 @@ public void testBaseWithDeleteDeltas() public void testOverlapingDeltaAndDeleteDelta() throws Exception { - MockFileSystem fs = new MockFileSystem(new Configuration(false), - new MockFile("mock:/tbl/part1/delta_0000063_63/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_000062_62/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_00061_61/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delete_delta_00064_64/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_40_60/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delete_delta_40_60/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_0060_60/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_052_55/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delete_delta_052_55/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/base_50/bucket_0", 500, FAKE_DATA)); + TrinoFileSystem fileSystem = new MemoryFileSystem(); + createFile(fileSystem, "memory:///tbl/part1/delta_0000063_63/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_000062_62/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_00061_61/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delete_delta_00064_64/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_40_60/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delete_delta_40_60/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_0060_60/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_052_55/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delete_delta_052_55/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/base_50/bucket_0", FAKE_DATA); + AcidState state = getAcidState( - testingTrinoFileSystem(fs), - Location.of("mock:///tbl/part1"), + fileSystem, + Location.of("memory:///tbl/part1"), new ValidWriteIdList("tbl:100:%d:".formatted(Long.MAX_VALUE))); - assertThat(state.baseDirectory()).contains(Location.of("mock:///tbl/part1/base_50")); + assertThat(state.baseDirectory()).contains(Location.of("memory:///tbl/part1/base_50")); List deltas = state.deltas(); assertEquals(deltas.size(), 6); - assertEquals(deltas.get(0).path(), "mock:///tbl/part1/delete_delta_40_60"); - assertEquals(deltas.get(1).path(), "mock:///tbl/part1/delta_40_60"); - assertEquals(deltas.get(2).path(), "mock:///tbl/part1/delta_00061_61"); - assertEquals(deltas.get(3).path(), "mock:///tbl/part1/delta_000062_62"); - assertEquals(deltas.get(4).path(), "mock:///tbl/part1/delta_0000063_63"); - assertEquals(deltas.get(5).path(), "mock:///tbl/part1/delete_delta_00064_64"); + assertEquals(deltas.get(0).path(), "memory:///tbl/part1/delete_delta_40_60"); + assertEquals(deltas.get(1).path(), "memory:///tbl/part1/delta_40_60"); + assertEquals(deltas.get(2).path(), "memory:///tbl/part1/delta_00061_61"); + assertEquals(deltas.get(3).path(), "memory:///tbl/part1/delta_000062_62"); + assertEquals(deltas.get(4).path(), "memory:///tbl/part1/delta_0000063_63"); + assertEquals(deltas.get(5).path(), "memory:///tbl/part1/delete_delta_00064_64"); } @Test @@ -399,41 +400,43 @@ public void testMinorCompactedDeltaMakesInBetweenDelteDeltaObsolete() { // This test checks that if we have a minor compacted delta for the txn range [40,60] // then it will make any delete delta in that range as obsolete. - MockFileSystem fs = new MockFileSystem(new Configuration(false), - new MockFile("mock:/tbl/part1/delta_40_60/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delete_delta_50_50/bucket_0", 500, FAKE_DATA)); + TrinoFileSystem fileSystem = new MemoryFileSystem(); + createFile(fileSystem, "memory:///tbl/part1/delta_40_60/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delete_delta_50_50/bucket_0", FAKE_DATA); + AcidState state = getAcidState( - testingTrinoFileSystem(fs), - Location.of("mock:///tbl/part1"), + fileSystem, + Location.of("memory:///tbl/part1"), new ValidWriteIdList("tbl:100:%d:".formatted(Long.MAX_VALUE))); List deltas = state.deltas(); assertEquals(deltas.size(), 1); - assertEquals(deltas.get(0).path(), "mock:///tbl/part1/delta_40_60"); + assertEquals(deltas.get(0).path(), "memory:///tbl/part1/delta_40_60"); } @Test public void deleteDeltasWithOpenTxnInRead() throws Exception { - MockFileSystem fs = new MockFileSystem(new Configuration(false), - new MockFile("mock:/tbl/part1/delta_1_1/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_2_5/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delete_delta_2_5/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delete_delta_3_3/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_4_4_1/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_4_4_3/bucket_0", 500, FAKE_DATA), - new MockFile("mock:/tbl/part1/delta_101_101_1/bucket_0", 500, FAKE_DATA)); + TrinoFileSystem fileSystem = new MemoryFileSystem(); + createFile(fileSystem, "memory:///tbl/part1/delta_1_1/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_2_5/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delete_delta_2_5/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delete_delta_3_3/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_4_4_1/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_4_4_3/bucket_0", FAKE_DATA); + createFile(fileSystem, "memory:///tbl/part1/delta_101_101_1/bucket_0", FAKE_DATA); + AcidState state = getAcidState( - testingTrinoFileSystem(fs), - Location.of("mock:///tbl/part1"), + fileSystem, + Location.of("memory:///tbl/part1"), new ValidWriteIdList("tbl:100:4:4")); List deltas = state.deltas(); assertEquals(deltas.size(), 3); - assertEquals(deltas.get(0).path(), "mock:///tbl/part1/delta_1_1"); - assertEquals(deltas.get(1).path(), "mock:///tbl/part1/delete_delta_2_5"); - assertEquals(deltas.get(2).path(), "mock:///tbl/part1/delta_2_5"); + assertEquals(deltas.get(0).path(), "memory:///tbl/part1/delta_1_1"); + assertEquals(deltas.get(1).path(), "memory:///tbl/part1/delete_delta_2_5"); + assertEquals(deltas.get(2).path(), "memory:///tbl/part1/delta_2_5"); // Note that delete_delta_3_3 should not be read, when a minor compacted // [delete_]delta_2_5 is present. } @@ -445,20 +448,11 @@ public void testDeleteDeltaSubdirPathGeneration() assertEquals(deleteDeltaSubdirPath, "delete_delta_0000013_0000013_0005"); } - private static TrinoFileSystem testingTrinoFileSystem(FileSystem fileSystem) + private static void createFile(TrinoFileSystem fileSystem, String location, byte[] data) + throws IOException { - HdfsConfiguration hdfsConfiguration = (context, uri) -> new Configuration(false); - - HdfsEnvironment environment = new HdfsEnvironment(hdfsConfiguration, new HdfsConfig(), new NoHdfsAuthentication()) - { - @Override - public FileSystem getFileSystem(ConnectorIdentity identity, Path path, Configuration configuration) - { - return fileSystem; - } - }; - - ConnectorIdentity identity = ConnectorIdentity.forUser("test").build(); - return new HdfsFileSystemFactory(environment, new TrinoHdfsFileSystemStats()).create(identity); + try (OutputStream outputStream = fileSystem.newOutputFile(Location.of(location)).create()) { + outputStream.write(data); + } } } From 795de3d50aaf421dde28433caed85b60526ad8ea Mon Sep 17 00:00:00 2001 From: David Phillips Date: Mon, 6 Nov 2023 18:09:16 -0800 Subject: [PATCH 099/587] Sort plugins in server provisio configuration --- core/trino-server/src/main/provisio/trino.xml | 181 +++++++++--------- 1 file changed, 91 insertions(+), 90 deletions(-) diff --git a/core/trino-server/src/main/provisio/trino.xml b/core/trino-server/src/main/provisio/trino.xml index 8e014d0b9a57..acc6fd6005db 100644 --- a/core/trino-server/src/main/provisio/trino.xml +++ b/core/trino-server/src/main/provisio/trino.xml @@ -26,26 +26,26 @@ - - + + - - + + - - + + - - + + @@ -62,247 +62,248 @@ - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + - - + + - - + + - - + + - - + + - - + + From e8e1415f66c4854770ea745c9f9aa1a10f794d52 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Mon, 30 Oct 2023 10:54:37 +0100 Subject: [PATCH 100/587] Remove unused TableChangesSplitSource.addresses field It was always an empty list. --- .../iceberg/functions/tablechanges/TableChangesSplit.java | 5 +---- .../functions/tablechanges/TableChangesSplitSource.java | 3 --- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/functions/tablechanges/TableChangesSplit.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/functions/tablechanges/TableChangesSplit.java index ec95b956e7dc..b656a213ac14 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/functions/tablechanges/TableChangesSplit.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/functions/tablechanges/TableChangesSplit.java @@ -38,7 +38,6 @@ public record TableChangesSplit( long fileSize, long fileRecordCount, IcebergFileFormat fileFormat, - List addresses, String partitionSpecJson, String partitionDataJson, SplitWeight splitWeight) implements ConnectorSplit @@ -50,7 +49,6 @@ public record TableChangesSplit( requireNonNull(changeType, "changeType is null"); requireNonNull(path, "path is null"); requireNonNull(fileFormat, "fileFormat is null"); - addresses = ImmutableList.copyOf(requireNonNull(addresses, "addresses is null")); requireNonNull(partitionSpecJson, "partitionSpecJson is null"); requireNonNull(partitionDataJson, "partitionDataJson is null"); requireNonNull(splitWeight, "splitWeight is null"); @@ -65,7 +63,7 @@ public boolean isRemotelyAccessible() @Override public List getAddresses() { - return addresses; + return ImmutableList.of(); } @Override @@ -89,7 +87,6 @@ public long getRetainedSizeInBytes() { return INSTANCE_SIZE + estimatedSizeOf(path) - + estimatedSizeOf(addresses, HostAddress::getRetainedSizeInBytes) + estimatedSizeOf(partitionSpecJson) + estimatedSizeOf(partitionDataJson) + splitWeight.getRetainedSizeInBytes(); diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/functions/tablechanges/TableChangesSplitSource.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/functions/tablechanges/TableChangesSplitSource.java index 5f3525058e30..edd60e8b9ba2 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/functions/tablechanges/TableChangesSplitSource.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/functions/tablechanges/TableChangesSplitSource.java @@ -13,7 +13,6 @@ */ package io.trino.plugin.iceberg.functions.tablechanges; -import com.google.common.collect.ImmutableList; import com.google.common.io.Closer; import io.trino.plugin.iceberg.IcebergFileFormat; import io.trino.plugin.iceberg.PartitionData; @@ -152,7 +151,6 @@ private TableChangesSplit toSplit(AddedRowsScanTask task) task.file().fileSizeInBytes(), task.file().recordCount(), IcebergFileFormat.fromIceberg(task.file().format()), - ImmutableList.of(), PartitionSpecParser.toJson(task.spec()), PartitionData.toJson(task.file().partition()), SplitWeight.standard()); @@ -171,7 +169,6 @@ private TableChangesSplit toSplit(DeletedDataFileScanTask task) task.file().fileSizeInBytes(), task.file().recordCount(), IcebergFileFormat.fromIceberg(task.file().format()), - ImmutableList.of(), PartitionSpecParser.toJson(task.spec()), PartitionData.toJson(task.file().partition()), SplitWeight.standard()); From ea2e5ee271beec09a44d7ee99f19ce95e7fd3fbd Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Mon, 30 Oct 2023 10:52:50 +0100 Subject: [PATCH 101/587] Provide default implementations in ConnectorSplit Provide default implementations for `ConnectorSplit.isRemotelyAccessible` and `ConnectorSplit.getAddresses` methods. Makes it easier to implement a new split class. Scheduling preferences are opt-in. --- .../java/io/trino/operator/table/Sequence.java | 14 -------------- .../main/java/io/trino/split/EmptySplit.java | 16 ---------------- .../main/java/io/trino/split/RemoteSplit.java | 16 ---------------- .../java/io/trino/connector/MockConnector.java | 13 ------------- .../trino/connector/TestingTableFunctions.java | 13 ------------- .../trino/execution/BenchmarkNodeScheduler.java | 6 ------ .../io/trino/execution/TestNodeScheduler.java | 6 ------ .../trino/execution/TestSqlTaskExecution.java | 14 -------------- .../io/trino/memory/TestMemoryBlocking.java | 13 ------------- .../test/java/io/trino/operator/TestDriver.java | 13 ------------- .../java/io/trino/split/MockSplitSource.java | 13 ------------- .../io/trino/spi/connector/ConnectorSplit.java | 13 +++++++++++-- .../plugin/accumulo/model/AccumuloSplit.java | 6 ------ .../java/io/trino/plugin/jdbc/JdbcSplit.java | 15 --------------- .../io/trino/plugin/bigquery/BigQuerySplit.java | 13 ------------- .../trino/plugin/blackhole/BlackHoleSplit.java | 16 ---------------- .../trino/plugin/cassandra/CassandraSplit.java | 6 ------ .../trino/plugin/deltalake/DeltaLakeSplit.java | 17 ----------------- .../tablechanges/TableChangesSplit.java | 17 ----------------- .../elasticsearch/ElasticsearchSplit.java | 6 ------ .../trino/plugin/google/sheets/SheetsSplit.java | 14 -------------- .../java/io/trino/plugin/hudi/HudiSplit.java | 15 --------------- .../io/trino/plugin/iceberg/IcebergSplit.java | 15 --------------- .../tablechanges/TableChangesSplit.java | 16 ---------------- .../java/io/trino/plugin/kafka/KafkaSplit.java | 6 ------ .../io/trino/plugin/kinesis/KinesisSplit.java | 16 ---------------- .../java/io/trino/plugin/kudu/KuduSplit.java | 6 ------ .../io/trino/plugin/mongodb/MongoSplit.java | 6 ------ .../java/io/trino/plugin/pinot/PinotSplit.java | 13 ------------- .../plugin/prometheus/PrometheusSplit.java | 6 ------ .../java/io/trino/plugin/redis/RedisSplit.java | 6 ------ .../plugin/thrift/ThriftConnectorSplit.java | 6 ------ 32 files changed, 11 insertions(+), 360 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/operator/table/Sequence.java b/core/trino-main/src/main/java/io/trino/operator/table/Sequence.java index bb713debf17b..c111f86c5140 100644 --- a/core/trino-main/src/main/java/io/trino/operator/table/Sequence.java +++ b/core/trino-main/src/main/java/io/trino/operator/table/Sequence.java @@ -19,7 +19,6 @@ import com.google.common.collect.ImmutableMap; import com.google.inject.Provider; import io.trino.plugin.base.classloader.ClassLoaderSafeConnectorTableFunction; -import io.trino.spi.HostAddress; import io.trino.spi.Page; import io.trino.spi.PageBuilder; import io.trino.spi.TrinoException; @@ -43,7 +42,6 @@ import io.trino.spi.function.table.TableFunctionSplitProcessor; import java.math.BigInteger; -import java.util.List; import java.util.Map; import static com.google.common.base.Preconditions.checkArgument; @@ -208,18 +206,6 @@ public long getStop() return stop; } - @Override - public boolean isRemotelyAccessible() - { - return true; - } - - @Override - public List getAddresses() - { - return ImmutableList.of(); - } - @Override public Object getInfo() { diff --git a/core/trino-main/src/main/java/io/trino/split/EmptySplit.java b/core/trino-main/src/main/java/io/trino/split/EmptySplit.java index 3f8fb21693fa..54670d2c4c70 100644 --- a/core/trino-main/src/main/java/io/trino/split/EmptySplit.java +++ b/core/trino-main/src/main/java/io/trino/split/EmptySplit.java @@ -15,13 +15,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.collect.ImmutableList; -import io.trino.spi.HostAddress; import io.trino.spi.connector.CatalogHandle; import io.trino.spi.connector.ConnectorSplit; -import java.util.List; - import static io.airlift.slice.SizeOf.instanceSize; import static java.util.Objects.requireNonNull; @@ -39,18 +35,6 @@ public EmptySplit( this.catalogHandle = requireNonNull(catalogHandle, "catalogHandle is null"); } - @Override - public boolean isRemotelyAccessible() - { - return true; - } - - @Override - public List getAddresses() - { - return ImmutableList.of(); - } - @Override public Object getInfo() { diff --git a/core/trino-main/src/main/java/io/trino/split/RemoteSplit.java b/core/trino-main/src/main/java/io/trino/split/RemoteSplit.java index f3e9fdaaedae..41934d716f60 100644 --- a/core/trino-main/src/main/java/io/trino/split/RemoteSplit.java +++ b/core/trino-main/src/main/java/io/trino/split/RemoteSplit.java @@ -15,13 +15,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.collect.ImmutableList; import io.trino.exchange.ExchangeInput; -import io.trino.spi.HostAddress; import io.trino.spi.connector.ConnectorSplit; -import java.util.List; - import static com.google.common.base.MoreObjects.toStringHelper; import static io.airlift.slice.SizeOf.instanceSize; import static java.util.Objects.requireNonNull; @@ -51,18 +47,6 @@ public Object getInfo() return this; } - @Override - public boolean isRemotelyAccessible() - { - return true; - } - - @Override - public List getAddresses() - { - return ImmutableList.of(); - } - @Override public String toString() { diff --git a/core/trino-main/src/test/java/io/trino/connector/MockConnector.java b/core/trino-main/src/test/java/io/trino/connector/MockConnector.java index a7ddc2d04fd1..a4b9362cdb60 100644 --- a/core/trino-main/src/test/java/io/trino/connector/MockConnector.java +++ b/core/trino-main/src/test/java/io/trino/connector/MockConnector.java @@ -26,7 +26,6 @@ import io.trino.connector.MockConnectorFactory.ApplyTableScanRedirect; import io.trino.connector.MockConnectorFactory.ApplyTopN; import io.trino.connector.MockConnectorFactory.ListRoleGrants; -import io.trino.spi.HostAddress; import io.trino.spi.Page; import io.trino.spi.connector.AggregateFunction; import io.trino.spi.connector.AggregationApplicationResult; @@ -1047,18 +1046,6 @@ public enum MockConnectorSplit { MOCK_CONNECTOR_SPLIT; - @Override - public boolean isRemotelyAccessible() - { - return true; - } - - @Override - public List getAddresses() - { - return ImmutableList.of(); - } - @Override public Object getInfo() { diff --git a/core/trino-main/src/test/java/io/trino/connector/TestingTableFunctions.java b/core/trino-main/src/test/java/io/trino/connector/TestingTableFunctions.java index f00edbfaf7df..8929d8758d4f 100644 --- a/core/trino-main/src/test/java/io/trino/connector/TestingTableFunctions.java +++ b/core/trino-main/src/test/java/io/trino/connector/TestingTableFunctions.java @@ -18,7 +18,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import io.airlift.slice.Slice; -import io.trino.spi.HostAddress; import io.trino.spi.Page; import io.trino.spi.block.Block; import io.trino.spi.block.BlockBuilder; @@ -1369,18 +1368,6 @@ public long getCount() return count; } - @Override - public boolean isRemotelyAccessible() - { - return true; - } - - @Override - public List getAddresses() - { - return ImmutableList.of(); - } - @Override public Object getInfo() { diff --git a/core/trino-main/src/test/java/io/trino/execution/BenchmarkNodeScheduler.java b/core/trino-main/src/test/java/io/trino/execution/BenchmarkNodeScheduler.java index 244cafc36b90..f039d5d9762b 100644 --- a/core/trino-main/src/test/java/io/trino/execution/BenchmarkNodeScheduler.java +++ b/core/trino-main/src/test/java/io/trino/execution/BenchmarkNodeScheduler.java @@ -257,12 +257,6 @@ public TestSplitRemote(int dataHost) hosts = ImmutableList.of(addressForHost(dataHost)); } - @Override - public boolean isRemotelyAccessible() - { - return true; - } - @Override public List getAddresses() { diff --git a/core/trino-main/src/test/java/io/trino/execution/TestNodeScheduler.java b/core/trino-main/src/test/java/io/trino/execution/TestNodeScheduler.java index 3c532212ef2f..4d35bf58773d 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestNodeScheduler.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestNodeScheduler.java @@ -993,12 +993,6 @@ private static class TestSplitRemote this.splitWeight = requireNonNull(splitWeight, "splitWeight is null"); } - @Override - public boolean isRemotelyAccessible() - { - return true; - } - @Override public List getAddresses() { diff --git a/core/trino-main/src/test/java/io/trino/execution/TestSqlTaskExecution.java b/core/trino-main/src/test/java/io/trino/execution/TestSqlTaskExecution.java index a61537db9ffa..9bde68b2e01d 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestSqlTaskExecution.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestSqlTaskExecution.java @@ -46,7 +46,6 @@ import io.trino.operator.SourceOperatorFactory; import io.trino.operator.TaskContext; import io.trino.operator.output.TaskOutputOperator.TaskOutputOperatorFactory; -import io.trino.spi.HostAddress; import io.trino.spi.Page; import io.trino.spi.QueryId; import io.trino.spi.block.TestingBlockEncodingSerde; @@ -56,7 +55,6 @@ import io.trino.sql.planner.plan.PlanNodeId; import org.junit.jupiter.api.Test; -import java.util.List; import java.util.OptionalInt; import java.util.concurrent.ExecutionException; import java.util.concurrent.ScheduledExecutorService; @@ -508,18 +506,6 @@ public TestingSplit(@JsonProperty("begin") int begin, @JsonProperty("end") int e this.end = end; } - @Override - public boolean isRemotelyAccessible() - { - return true; - } - - @Override - public List getAddresses() - { - return ImmutableList.of(); - } - @Override public Object getInfo() { diff --git a/core/trino-main/src/test/java/io/trino/memory/TestMemoryBlocking.java b/core/trino-main/src/test/java/io/trino/memory/TestMemoryBlocking.java index 212313ef8e9b..ebc8bef68e87 100644 --- a/core/trino-main/src/test/java/io/trino/memory/TestMemoryBlocking.java +++ b/core/trino-main/src/test/java/io/trino/memory/TestMemoryBlocking.java @@ -27,7 +27,6 @@ import io.trino.operator.DriverContext; import io.trino.operator.TableScanOperator; import io.trino.operator.TaskContext; -import io.trino.spi.HostAddress; import io.trino.spi.QueryId; import io.trino.spi.connector.ConnectorSplit; import io.trino.spi.connector.DynamicFilter; @@ -155,18 +154,6 @@ private PageConsumerOperator createSinkOperator(List types) private static class TestSplit implements ConnectorSplit { - @Override - public boolean isRemotelyAccessible() - { - return false; - } - - @Override - public List getAddresses() - { - return ImmutableList.of(); - } - @Override public Object getInfo() { diff --git a/core/trino-main/src/test/java/io/trino/operator/TestDriver.java b/core/trino-main/src/test/java/io/trino/operator/TestDriver.java index a173ce730695..199a47c827c1 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestDriver.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestDriver.java @@ -24,7 +24,6 @@ import io.trino.memory.context.LocalMemoryContext; import io.trino.metadata.Split; import io.trino.metadata.TableHandle; -import io.trino.spi.HostAddress; import io.trino.spi.Page; import io.trino.spi.TrinoException; import io.trino.spi.connector.ColumnHandle; @@ -548,18 +547,6 @@ public ListenableFuture isBlocked() private static class MockSplit implements ConnectorSplit { - @Override - public boolean isRemotelyAccessible() - { - return false; - } - - @Override - public List getAddresses() - { - return ImmutableList.of(); - } - @Override public Object getInfo() { diff --git a/core/trino-main/src/test/java/io/trino/split/MockSplitSource.java b/core/trino-main/src/test/java/io/trino/split/MockSplitSource.java index 7b53f9a595c5..c5cdd6e46251 100644 --- a/core/trino-main/src/test/java/io/trino/split/MockSplitSource.java +++ b/core/trino-main/src/test/java/io/trino/split/MockSplitSource.java @@ -19,7 +19,6 @@ import com.google.common.util.concurrent.SettableFuture; import io.trino.annotation.NotThreadSafe; import io.trino.metadata.Split; -import io.trino.spi.HostAddress; import io.trino.spi.connector.CatalogHandle; import io.trino.spi.connector.ConnectorSplit; @@ -148,18 +147,6 @@ public int getNextBatchInvocationCount() public static class MockConnectorSplit implements ConnectorSplit { - @Override - public boolean isRemotelyAccessible() - { - return false; - } - - @Override - public List getAddresses() - { - return ImmutableList.of(); - } - @Override public Object getInfo() { diff --git a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorSplit.java b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorSplit.java index c1320a8ed296..6770563b297d 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorSplit.java +++ b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorSplit.java @@ -27,9 +27,18 @@ public interface ConnectorSplit * during splits assignment. * When false, the split will always be scheduled on one of the addresses returned by {@link #getAddresses()}. */ - boolean isRemotelyAccessible(); + default boolean isRemotelyAccessible() + { + return true; + } - List getAddresses(); + default List getAddresses() + { + if (!isRemotelyAccessible()) { + throw new IllegalStateException("getAddresses must be implemented when for splits with isRemotelyAccessible=false"); + } + return List.of(); + } Object getInfo(); diff --git a/plugin/trino-accumulo/src/main/java/io/trino/plugin/accumulo/model/AccumuloSplit.java b/plugin/trino-accumulo/src/main/java/io/trino/plugin/accumulo/model/AccumuloSplit.java index 31115e4b70b9..d0fad6bb0e50 100644 --- a/plugin/trino-accumulo/src/main/java/io/trino/plugin/accumulo/model/AccumuloSplit.java +++ b/plugin/trino-accumulo/src/main/java/io/trino/plugin/accumulo/model/AccumuloSplit.java @@ -76,12 +76,6 @@ public List getRanges() return ranges.stream().map(SerializedRange::deserialize).collect(Collectors.toList()); } - @Override - public boolean isRemotelyAccessible() - { - return true; - } - @Override public List getAddresses() { diff --git a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/JdbcSplit.java b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/JdbcSplit.java index c8c9e98d3b2a..51076ac1edf5 100644 --- a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/JdbcSplit.java +++ b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/JdbcSplit.java @@ -15,13 +15,10 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.collect.ImmutableList; import io.airlift.slice.SizeOf; -import io.trino.spi.HostAddress; import io.trino.spi.connector.ConnectorSplit; import io.trino.spi.predicate.TupleDomain; -import java.util.List; import java.util.Optional; import static io.airlift.slice.SizeOf.instanceSize; @@ -67,18 +64,6 @@ public TupleDomain getDynamicFilter() return dynamicFilter; } - @Override - public boolean isRemotelyAccessible() - { - return true; - } - - @Override - public List getAddresses() - { - return ImmutableList.of(); - } - @Override public Object getInfo() { diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQuerySplit.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQuerySplit.java index 324d4a868196..61bb3548c52d 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQuerySplit.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQuerySplit.java @@ -16,7 +16,6 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.collect.ImmutableList; -import io.trino.spi.HostAddress; import io.trino.spi.connector.ConnectorSplit; import java.util.List; @@ -123,18 +122,6 @@ public OptionalInt getDataSize() return dataSize; } - @Override - public boolean isRemotelyAccessible() - { - return true; - } - - @Override - public List getAddresses() - { - return ImmutableList.of(); - } - @Override public Object getInfo() { diff --git a/plugin/trino-blackhole/src/main/java/io/trino/plugin/blackhole/BlackHoleSplit.java b/plugin/trino-blackhole/src/main/java/io/trino/plugin/blackhole/BlackHoleSplit.java index 4b0d8913edda..1255eef71e83 100644 --- a/plugin/trino-blackhole/src/main/java/io/trino/plugin/blackhole/BlackHoleSplit.java +++ b/plugin/trino-blackhole/src/main/java/io/trino/plugin/blackhole/BlackHoleSplit.java @@ -13,29 +13,13 @@ */ package io.trino.plugin.blackhole; -import com.google.common.collect.ImmutableList; -import io.trino.spi.HostAddress; import io.trino.spi.connector.ConnectorSplit; -import java.util.List; - public enum BlackHoleSplit implements ConnectorSplit { INSTANCE; - @Override - public boolean isRemotelyAccessible() - { - return true; - } - - @Override - public List getAddresses() - { - return ImmutableList.of(); - } - @Override public Object getInfo() { diff --git a/plugin/trino-cassandra/src/main/java/io/trino/plugin/cassandra/CassandraSplit.java b/plugin/trino-cassandra/src/main/java/io/trino/plugin/cassandra/CassandraSplit.java index d5834a14a496..815d84874e8d 100644 --- a/plugin/trino-cassandra/src/main/java/io/trino/plugin/cassandra/CassandraSplit.java +++ b/plugin/trino-cassandra/src/main/java/io/trino/plugin/cassandra/CassandraSplit.java @@ -69,12 +69,6 @@ public List getAddresses() return addresses; } - @Override - public boolean isRemotelyAccessible() - { - return true; - } - @Override public Object getInfo() { diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeSplit.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeSplit.java index 245c7e07a173..6ae04c0907f4 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeSplit.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeSplit.java @@ -14,18 +14,14 @@ package io.trino.plugin.deltalake; import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.airlift.slice.SizeOf; import io.trino.plugin.deltalake.transactionlog.DeletionVectorEntry; -import io.trino.spi.HostAddress; import io.trino.spi.SplitWeight; import io.trino.spi.connector.ConnectorSplit; import io.trino.spi.predicate.TupleDomain; -import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -78,19 +74,6 @@ public DeltaLakeSplit( this.partitionKeys = requireNonNull(partitionKeys, "partitionKeys is null"); } - @Override - public boolean isRemotelyAccessible() - { - return true; - } - - @JsonIgnore - @Override - public List getAddresses() - { - return ImmutableList.of(); - } - @JsonProperty @Override public SplitWeight getSplitWeight() diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/functions/tablechanges/TableChangesSplit.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/functions/tablechanges/TableChangesSplit.java index bdc85d6e8d5a..fdeaf98c1cc8 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/functions/tablechanges/TableChangesSplit.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/functions/tablechanges/TableChangesSplit.java @@ -14,14 +14,11 @@ package io.trino.plugin.deltalake.functions.tablechanges; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.airlift.slice.SizeOf; -import io.trino.spi.HostAddress; import io.trino.spi.SplitWeight; import io.trino.spi.connector.ConnectorSplit; -import java.util.List; import java.util.Map; import java.util.Optional; @@ -40,20 +37,6 @@ public record TableChangesSplit( { private static final int INSTANCE_SIZE = instanceSize(TableChangesSplit.class); - @JsonIgnore - @Override - public boolean isRemotelyAccessible() - { - return true; - } - - @JsonIgnore - @Override - public List getAddresses() - { - return ImmutableList.of(); - } - @JsonIgnore @Override public Object getInfo() diff --git a/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/ElasticsearchSplit.java b/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/ElasticsearchSplit.java index dccc37383911..a1ed6592e834 100644 --- a/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/ElasticsearchSplit.java +++ b/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/ElasticsearchSplit.java @@ -67,12 +67,6 @@ public Optional getAddress() return address; } - @Override - public boolean isRemotelyAccessible() - { - return true; - } - @Override public List getAddresses() { diff --git a/plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/SheetsSplit.java b/plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/SheetsSplit.java index 738e11639241..8082b350c28d 100644 --- a/plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/SheetsSplit.java +++ b/plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/SheetsSplit.java @@ -15,10 +15,8 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.airlift.slice.SizeOf; -import io.trino.spi.HostAddress; import io.trino.spi.connector.ConnectorSplit; import java.util.List; @@ -47,18 +45,6 @@ public List> getValues() return values; } - @Override - public boolean isRemotelyAccessible() - { - return true; - } - - @Override - public List getAddresses() - { - return ImmutableList.of(); - } - @Override public Object getInfo() { diff --git a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiSplit.java b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiSplit.java index 834515df1c7c..ca43d49834fa 100644 --- a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiSplit.java +++ b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiSplit.java @@ -14,13 +14,11 @@ package io.trino.plugin.hudi; import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.trino.plugin.hive.HiveColumnHandle; import io.trino.plugin.hive.HivePartitionKey; -import io.trino.spi.HostAddress; import io.trino.spi.SplitWeight; import io.trino.spi.connector.ConnectorSplit; import io.trino.spi.predicate.TupleDomain; @@ -73,19 +71,6 @@ public HudiSplit( this.splitWeight = requireNonNull(splitWeight, "splitWeight is null"); } - @Override - public boolean isRemotelyAccessible() - { - return true; - } - - @JsonIgnore - @Override - public List getAddresses() - { - return ImmutableList.of(); - } - @Override public Object getInfo() { diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergSplit.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergSplit.java index 3bc609c03bce..d14178c28689 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergSplit.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergSplit.java @@ -14,13 +14,11 @@ package io.trino.plugin.iceberg; import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.base.MoreObjects.ToStringHelper; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.trino.plugin.iceberg.delete.DeleteFile; -import io.trino.spi.HostAddress; import io.trino.spi.SplitWeight; import io.trino.spi.connector.ConnectorSplit; @@ -72,19 +70,6 @@ public IcebergSplit( this.splitWeight = requireNonNull(splitWeight, "splitWeight is null"); } - @Override - public boolean isRemotelyAccessible() - { - return true; - } - - @JsonIgnore - @Override - public List getAddresses() - { - return ImmutableList.of(); - } - @JsonProperty public String getPath() { diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/functions/tablechanges/TableChangesSplit.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/functions/tablechanges/TableChangesSplit.java index b656a213ac14..cd4718ff6b92 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/functions/tablechanges/TableChangesSplit.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/functions/tablechanges/TableChangesSplit.java @@ -13,16 +13,12 @@ */ package io.trino.plugin.iceberg.functions.tablechanges; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.airlift.slice.SizeOf; import io.trino.plugin.iceberg.IcebergFileFormat; -import io.trino.spi.HostAddress; import io.trino.spi.SplitWeight; import io.trino.spi.connector.ConnectorSplit; -import java.util.List; - import static com.google.common.base.MoreObjects.toStringHelper; import static io.airlift.slice.SizeOf.estimatedSizeOf; import static java.util.Objects.requireNonNull; @@ -54,18 +50,6 @@ public record TableChangesSplit( requireNonNull(splitWeight, "splitWeight is null"); } - @Override - public boolean isRemotelyAccessible() - { - return true; - } - - @Override - public List getAddresses() - { - return ImmutableList.of(); - } - @Override public SplitWeight getSplitWeight() { diff --git a/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/KafkaSplit.java b/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/KafkaSplit.java index 54b1bae38037..0a56fcdd0d96 100644 --- a/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/KafkaSplit.java +++ b/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/KafkaSplit.java @@ -112,12 +112,6 @@ public HostAddress getLeader() return leader; } - @Override - public boolean isRemotelyAccessible() - { - return true; - } - @Override public List getAddresses() { diff --git a/plugin/trino-kinesis/src/main/java/io/trino/plugin/kinesis/KinesisSplit.java b/plugin/trino-kinesis/src/main/java/io/trino/plugin/kinesis/KinesisSplit.java index 70afddd89edf..0ff2d4b3ffdb 100644 --- a/plugin/trino-kinesis/src/main/java/io/trino/plugin/kinesis/KinesisSplit.java +++ b/plugin/trino-kinesis/src/main/java/io/trino/plugin/kinesis/KinesisSplit.java @@ -15,12 +15,8 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.collect.ImmutableList; -import io.trino.spi.HostAddress; import io.trino.spi.connector.ConnectorSplit; -import java.util.List; - import static com.google.common.base.MoreObjects.toStringHelper; import static io.airlift.slice.SizeOf.estimatedSizeOf; import static io.airlift.slice.SizeOf.instanceSize; @@ -95,18 +91,6 @@ public String getShardId() return shardId; } - @Override - public boolean isRemotelyAccessible() - { - return true; - } - - @Override - public List getAddresses() - { - return ImmutableList.of(); - } - @Override public Object getInfo() { diff --git a/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/KuduSplit.java b/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/KuduSplit.java index a606514ed5ad..4fe8345d67df 100755 --- a/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/KuduSplit.java +++ b/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/KuduSplit.java @@ -78,12 +78,6 @@ public int getBucketNumber() return bucketNumber; } - @Override - public boolean isRemotelyAccessible() - { - return true; - } - @JsonProperty @Override public List getAddresses() diff --git a/plugin/trino-mongodb/src/main/java/io/trino/plugin/mongodb/MongoSplit.java b/plugin/trino-mongodb/src/main/java/io/trino/plugin/mongodb/MongoSplit.java index 9bd90154afac..67f87afca0c8 100644 --- a/plugin/trino-mongodb/src/main/java/io/trino/plugin/mongodb/MongoSplit.java +++ b/plugin/trino-mongodb/src/main/java/io/trino/plugin/mongodb/MongoSplit.java @@ -38,12 +38,6 @@ public MongoSplit(@JsonProperty("addresses") List addresses) this.addresses = ImmutableList.copyOf(requireNonNull(addresses, "addresses is null")); } - @Override - public boolean isRemotelyAccessible() - { - return true; - } - @Override @JsonProperty public List getAddresses() diff --git a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/PinotSplit.java b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/PinotSplit.java index 74af1b5c4fb8..677c01f11d01 100755 --- a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/PinotSplit.java +++ b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/PinotSplit.java @@ -17,7 +17,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.collect.ImmutableList; import io.airlift.slice.SizeOf; -import io.trino.spi.HostAddress; import io.trino.spi.connector.ConnectorSplit; import java.util.List; @@ -123,18 +122,6 @@ public String toString() .toString(); } - @Override - public boolean isRemotelyAccessible() - { - return true; - } - - @Override - public List getAddresses() - { - return ImmutableList.of(); - } - @Override public Object getInfo() { diff --git a/plugin/trino-prometheus/src/main/java/io/trino/plugin/prometheus/PrometheusSplit.java b/plugin/trino-prometheus/src/main/java/io/trino/plugin/prometheus/PrometheusSplit.java index 60ec23c82b80..96f4875c2543 100644 --- a/plugin/trino-prometheus/src/main/java/io/trino/plugin/prometheus/PrometheusSplit.java +++ b/plugin/trino-prometheus/src/main/java/io/trino/plugin/prometheus/PrometheusSplit.java @@ -48,12 +48,6 @@ public String getUri() return uri; } - @Override - public boolean isRemotelyAccessible() - { - return true; - } - @Override public List getAddresses() { diff --git a/plugin/trino-redis/src/main/java/io/trino/plugin/redis/RedisSplit.java b/plugin/trino-redis/src/main/java/io/trino/plugin/redis/RedisSplit.java index 1249dade2350..71cefb68b5ff 100644 --- a/plugin/trino-redis/src/main/java/io/trino/plugin/redis/RedisSplit.java +++ b/plugin/trino-redis/src/main/java/io/trino/plugin/redis/RedisSplit.java @@ -141,12 +141,6 @@ public long getEnd() return end; } - @Override - public boolean isRemotelyAccessible() - { - return true; - } - @Override public List getAddresses() { diff --git a/plugin/trino-thrift/src/main/java/io/trino/plugin/thrift/ThriftConnectorSplit.java b/plugin/trino-thrift/src/main/java/io/trino/plugin/thrift/ThriftConnectorSplit.java index 93015bb68a45..e08d047cba30 100644 --- a/plugin/trino-thrift/src/main/java/io/trino/plugin/thrift/ThriftConnectorSplit.java +++ b/plugin/trino-thrift/src/main/java/io/trino/plugin/thrift/ThriftConnectorSplit.java @@ -72,12 +72,6 @@ public long getRetainedSizeInBytes() + estimatedSizeOf(addresses, HostAddress::getRetainedSizeInBytes); } - @Override - public boolean isRemotelyAccessible() - { - return true; - } - @Override public boolean equals(Object obj) { From 10fdc951f03f08cc366bc9f748de92ee9c0d3fa6 Mon Sep 17 00:00:00 2001 From: praveenkrishna Date: Tue, 7 Nov 2023 13:04:21 +0530 Subject: [PATCH 102/587] Add missing Test annotation for Redshift test --- .../plugin/redshift/TestRedshiftAutomaticJoinPushdown.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftAutomaticJoinPushdown.java b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftAutomaticJoinPushdown.java index 349dbfcd768a..1701cd1bee96 100644 --- a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftAutomaticJoinPushdown.java +++ b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftAutomaticJoinPushdown.java @@ -18,6 +18,7 @@ import io.trino.plugin.jdbc.BaseAutomaticJoinPushdownTest; import io.trino.testing.QueryRunner; import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import static io.trino.plugin.redshift.RedshiftQueryRunner.TEST_SCHEMA; import static io.trino.plugin.redshift.RedshiftQueryRunner.createRedshiftQueryRunner; @@ -38,6 +39,7 @@ protected QueryRunner createQueryRunner() ImmutableList.of()); } + @Test @Override @Disabled public void testJoinPushdownWithEmptyStatsInitially() From 0069e74206f86e69e08a8ebbb790e2c0d4f3c8e1 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Tue, 7 Nov 2023 13:37:37 +0100 Subject: [PATCH 103/587] Increase timeout when scaling cluster in test Increase `TestMinWorkerRequirement.testMultipleRequiredWorkerNodesSessionOverride` timeout, hopefully fixing its flakiness. Before the changes, running the test with `@Test(invocationCount = 20, threadPoolSize = 4)` would pretty deterministically reproduce the failure locally (with first 4 runs failing, other succeeding). It is up to future investigation to understand why it sometimes takes more time to register new worker node. --- .../src/main/java/io/trino/testing/DistributedQueryRunner.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/testing/trino-testing/src/main/java/io/trino/testing/DistributedQueryRunner.java b/testing/trino-testing/src/main/java/io/trino/testing/DistributedQueryRunner.java index acd427cda249..b1dc66524a45 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/DistributedQueryRunner.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/DistributedQueryRunner.java @@ -300,7 +300,8 @@ private void waitForAllNodesGloballyVisible() { long start = System.nanoTime(); while (!allNodesGloballyVisible()) { - Assertions.assertLessThan(nanosSince(start), new Duration(10, SECONDS)); + // TODO node announcement should be propagated faster when new node starts + Assertions.assertLessThan(nanosSince(start), new Duration(30, SECONDS)); MILLISECONDS.sleep(10); } log.info("Announced servers in %s", nanosSince(start).convertToMostSuccinctTimeUnit()); From 42636932a1b0b19200518f3dfcf61d8a7c818942 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Mon, 30 Oct 2023 17:47:21 +0100 Subject: [PATCH 104/587] Add TABLE_NOT_FOUND tests with fancy table names --- .../java/io/trino/sql/analyzer/TestAnalyzer.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/core/trino-main/src/test/java/io/trino/sql/analyzer/TestAnalyzer.java b/core/trino-main/src/test/java/io/trino/sql/analyzer/TestAnalyzer.java index 962eac3b550f..9480fae07584 100644 --- a/core/trino-main/src/test/java/io/trino/sql/analyzer/TestAnalyzer.java +++ b/core/trino-main/src/test/java/io/trino/sql/analyzer/TestAnalyzer.java @@ -941,6 +941,14 @@ public void testInvalidTable() assertFails("SELECT * FROM foo FOR VERSION AS OF 'version1'") .hasErrorCode(TABLE_NOT_FOUND) .hasMessage("line 1:15: Table 'tpch.s1.foo' does not exist"); + // table name containing dots + assertFails("SELECT * FROM \"table.not.existing\"") + .hasErrorCode(TABLE_NOT_FOUND) + .hasMessage("line 1:15: Table 'tpch.s1.table.not.existing' does not exist"); + // table name containing whitespace + assertFails("SELECT * FROM \"table' does not exist, or maybe 'view\"") + .hasErrorCode(TABLE_NOT_FOUND) + .hasMessage("line 1:15: Table 'tpch.s1.table' does not exist, or maybe 'view' does not exist"); } @Test @@ -952,6 +960,10 @@ public void testInvalidSchema() assertFails("SHOW TABLES IN NONEXISTENT_SCHEMA LIKE '%'") .hasErrorCode(SCHEMA_NOT_FOUND) .hasMessage("line 1:1: Schema 'nonexistent_schema' does not exist"); + assertFails("SELECT * FROM \"a.b.c.d.e.\".\"f.g.h\" ") + .hasErrorCode(SCHEMA_NOT_FOUND) + // TODO like in TABLE_NOT_FOUND, the error message should include current catalog + .hasMessage("line 1:15: Schema 'a.b.c.d.e.' does not exist"); } @Test From 47d7d3ea46584c877741ca4eea5c9a91a9ca6e19 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Tue, 7 Nov 2023 09:49:55 +0100 Subject: [PATCH 105/587] Add Output.toString --- .../main/java/io/trino/sql/analyzer/Analysis.java | 10 ++++++++++ .../src/main/java/io/trino/sql/analyzer/Output.java | 13 +++++++++++++ .../java/io/trino/sql/analyzer/OutputColumn.java | 10 ++++++++++ 3 files changed, 33 insertions(+) diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/Analysis.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/Analysis.java index 6e8df1cf50a8..cb25fc769147 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/Analysis.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/Analysis.java @@ -102,6 +102,7 @@ import java.util.OptionalLong; import java.util.Set; +import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; @@ -2058,6 +2059,15 @@ public boolean equals(Object obj) return Objects.equals(tableName, entry.tableName) && Objects.equals(columnName, entry.columnName); } + + @Override + public String toString() + { + return toStringHelper(this) + .add("tableName", tableName) + .add("columnName", columnName) + .toString(); + } } private static class RoutineEntry diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/Output.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/Output.java index 513badadde18..e51824b270ae 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/Output.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/Output.java @@ -23,6 +23,7 @@ import java.util.Objects; import java.util.Optional; +import static com.google.common.base.MoreObjects.toStringHelper; import static java.util.Objects.requireNonNull; @Immutable @@ -101,4 +102,16 @@ public int hashCode() { return Objects.hash(catalogName, catalogVersion, schema, table, columns); } + + @Override + public String toString() + { + return toStringHelper(this) + .add("catalogName", catalogName) + .add("catalogVersion", catalogVersion) + .add("schema", schema) + .add("table", table) + .add("columns", columns) + .toString(); + } } diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/OutputColumn.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/OutputColumn.java index c3fd926be3b3..7cbf8b2335a5 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/OutputColumn.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/OutputColumn.java @@ -23,6 +23,7 @@ import java.util.Objects; import java.util.Set; +import static com.google.common.base.MoreObjects.toStringHelper; import static java.util.Objects.requireNonNull; @Immutable @@ -69,4 +70,13 @@ public boolean equals(Object obj) return Objects.equals(column, entry.column) && Objects.equals(sourceColumns, entry.sourceColumns); } + + @Override + public String toString() + { + return toStringHelper(this) + .add("column", column) + .add("sourceColumns", sourceColumns) + .toString(); + } } From 794ca2a8ce40d847054cbcec8cba2bb7789ecaa1 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Tue, 7 Nov 2023 09:51:35 +0100 Subject: [PATCH 106/587] Fix JSON serialization of QualifiedObjectName The QualifiedObjectName with components containing dots did not round-trip. As a desired side effect, format table names in error messages less ambiguously: previously, when table `"a.b.c"."d.e"."f.e.g"` was not found, the message would indicate a `a.b.c.d.e.f.e.g` table without a hint how to parse that. --- .../trino/metadata/QualifiedObjectName.java | 35 +++++++++--- .../java/io/trino/testing/TestingHandles.java | 2 +- .../java/io/trino/execution/TestCallTask.java | 4 +- .../TestCreateMaterializedViewTask.java | 4 +- .../trino/execution/TestCreateSchemaTask.java | 6 +-- .../trino/execution/TestCreateTableTask.java | 4 +- .../trino/execution/TestDropSchemaTask.java | 2 +- .../TestRenameMaterializedViewTask.java | 4 +- .../trino/execution/TestRenameTableTask.java | 4 +- .../trino/execution/TestRenameViewTask.java | 2 +- .../metadata/TestQualifiedObjectName.java | 53 +++++++++++++++++++ .../security/TestAccessControlManager.java | 4 +- .../io/trino/sql/analyzer/TestAnalyzer.java | 4 +- .../io/trino/sql/analyzer/TestOutput.java | 21 ++++++++ .../trino/sql/planner/TestLogicalPlanner.java | 8 +-- .../rule/TestApplyTableScanRedirection.java | 2 +- .../query/TestFilterInaccessibleColumns.java | 14 ++--- .../main/java/io/trino/sql/SqlFormatter.java | 2 +- .../bigquery/BaseBigQueryConnectorTest.java | 2 +- .../deltalake/TestDeltaLakePartitioning.java | 2 +- .../plugin/hive/BaseHiveConnectorTest.java | 10 ++-- .../plugin/hudi/TestHudiSystemTables.java | 2 +- .../ignite/TestIgniteConnectorTest.java | 4 +- .../legacy/BaseRaptorConnectorTest.java | 4 +- .../TestHiveAndDeltaLakeRedirect.java | 10 ++-- .../product/hive/TestHivePartitionsTable.java | 2 +- .../hudi/TestHudiHiveTablesCompatibility.java | 6 +-- .../hudi/TestHudiSparkCompatibility.java | 2 +- .../TestIcebergHiveTablesCompatibility.java | 8 +-- .../iceberg/TestIcebergRedirectionToHive.java | 8 +-- .../io/trino/testing/BaseConnectorTest.java | 2 +- 31 files changed, 165 insertions(+), 72 deletions(-) create mode 100644 core/trino-main/src/test/java/io/trino/metadata/TestQualifiedObjectName.java diff --git a/core/trino-main/src/main/java/io/trino/metadata/QualifiedObjectName.java b/core/trino-main/src/main/java/io/trino/metadata/QualifiedObjectName.java index e052373185c0..5b9905218d89 100644 --- a/core/trino-main/src/main/java/io/trino/metadata/QualifiedObjectName.java +++ b/core/trino-main/src/main/java/io/trino/metadata/QualifiedObjectName.java @@ -15,8 +15,6 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; -import com.google.common.base.Splitter; -import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.Immutable; import io.trino.spi.connector.CatalogSchemaRoutineName; import io.trino.spi.connector.CatalogSchemaTableName; @@ -26,6 +24,8 @@ import java.util.Objects; import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static com.google.common.base.Preconditions.checkArgument; import static io.trino.metadata.MetadataUtil.checkObjectName; @@ -34,15 +34,17 @@ @Immutable public class QualifiedObjectName { + private static final Pattern UNQUOTED_COMPONENT = Pattern.compile("[a-zA-Z0-9_]+"); + private static final String COMPONENT = UNQUOTED_COMPONENT.pattern() + "|\"([^\"]|\"\")*\""; + private static final Pattern PATTERN = Pattern.compile("(?" + COMPONENT + ")\\.(?" + COMPONENT + ")\\.(?" + COMPONENT + ")"); + @JsonCreator public static QualifiedObjectName valueOf(String name) { requireNonNull(name, "name is null"); - - ImmutableList ids = ImmutableList.copyOf(Splitter.on('.').split(name)); - checkArgument(ids.size() == 3, "Invalid name %s", name); - - return new QualifiedObjectName(ids.get(0), ids.get(1), ids.get(2)); + Matcher matcher = PATTERN.matcher(name); + checkArgument(matcher.matches(), "Invalid name %s", name); + return new QualifiedObjectName(unquoteIfNeeded(matcher.group("catalog")), unquoteIfNeeded(matcher.group("schema")), unquoteIfNeeded(matcher.group("table"))); } private final String catalogName; @@ -127,11 +129,28 @@ public int hashCode() @Override public String toString() { - return catalogName + '.' + schemaName + '.' + objectName; + return quoteIfNeeded(catalogName) + '.' + quoteIfNeeded(schemaName) + '.' + quoteIfNeeded(objectName); } public static Function convertFromSchemaTableName(String catalogName) { return input -> new QualifiedObjectName(catalogName, input.getSchemaName(), input.getTableName()); } + + private static String unquoteIfNeeded(String name) + { + if (name.isEmpty() || name.charAt(0) != '"') { + return name; + } + checkArgument(name.charAt(name.length() - 1) == '"', "Invalid name: [%s]", name); + return name.substring(1, name.length() - 1).replace("\"\"", "\""); + } + + private static String quoteIfNeeded(String name) + { + if (UNQUOTED_COMPONENT.matcher(name).matches()) { + return name; + } + return "\"" + name.replace("\"", "\"\"") + "\""; + } } diff --git a/core/trino-main/src/main/java/io/trino/testing/TestingHandles.java b/core/trino-main/src/main/java/io/trino/testing/TestingHandles.java index 79cd18eaef9a..6fbe5177d04b 100644 --- a/core/trino-main/src/main/java/io/trino/testing/TestingHandles.java +++ b/core/trino-main/src/main/java/io/trino/testing/TestingHandles.java @@ -25,7 +25,7 @@ public final class TestingHandles private TestingHandles() {} private static final CatalogVersion TEST_CATALOG_VERSION = new CatalogVersion("test"); - public static final String TEST_CATALOG_NAME = "test-catalog"; + public static final String TEST_CATALOG_NAME = "test_catalog"; public static final CatalogHandle TEST_CATALOG_HANDLE = createTestCatalogHandle(TEST_CATALOG_NAME); public static final TableHandle TEST_TABLE_HANDLE = new TableHandle( TEST_CATALOG_HANDLE, diff --git a/core/trino-main/src/test/java/io/trino/execution/TestCallTask.java b/core/trino-main/src/test/java/io/trino/execution/TestCallTask.java index abcada15cc3d..94742b109201 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestCallTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestCallTask.java @@ -104,7 +104,7 @@ public void testExecuteNoPermission() assertThatThrownBy( () -> executeCallTask(PROCEDURE_METHOD_HANDLE.bindTo(target), transactionManager -> new DenyAllAccessControl())) .isInstanceOf(AccessDeniedException.class) - .hasMessage("Access Denied: Cannot execute procedure test-catalog.test.testing_procedure"); + .hasMessage("Access Denied: Cannot execute procedure test_catalog.test.testing_procedure"); assertThat(target.invoked).isFalse(); } @@ -121,7 +121,7 @@ public void testExecuteNoPermissionOnInsert() return accessControl; })) .isInstanceOf(AccessDeniedException.class) - .hasMessage("Access Denied: Cannot insert into table test-catalog.test.testing_table"); + .hasMessage("Access Denied: Cannot insert into table test_catalog.test.testing_table"); } private void executeCallTask(MethodHandle methodHandle, Function accessControlProvider) diff --git a/core/trino-main/src/test/java/io/trino/execution/TestCreateMaterializedViewTask.java b/core/trino-main/src/test/java/io/trino/execution/TestCreateMaterializedViewTask.java index 5c9f18747183..5ed1056ca709 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestCreateMaterializedViewTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestCreateMaterializedViewTask.java @@ -227,7 +227,7 @@ public void testCreateMaterializedViewWithInvalidProperty() assertTrinoExceptionThrownBy(() -> getFutureValue(new CreateMaterializedViewTask(plannerContext, new AllowAllAccessControl(), parser, analyzerFactory, materializedViewPropertyManager) .execute(statement, queryStateMachine, ImmutableList.of(), WarningCollector.NOOP))) .hasErrorCode(INVALID_MATERIALIZED_VIEW_PROPERTY) - .hasMessage("Catalog 'test-catalog' materialized view property 'baz' does not exist"); + .hasMessage("Catalog 'test_catalog' materialized view property 'baz' does not exist"); assertEquals(metadata.getCreateMaterializedViewCallCount(), 0); } @@ -283,7 +283,7 @@ public void testCreateDenyPermission() assertThatThrownBy(() -> getFutureValue(new CreateMaterializedViewTask(plannerContext, accessControl, parser, analyzerFactory, materializedViewPropertyManager) .execute(statement, queryStateMachine, ImmutableList.of(), WarningCollector.NOOP))) .isInstanceOf(AccessDeniedException.class) - .hasMessageContaining("Cannot create materialized view test-catalog.schema.test_mv"); + .hasMessageContaining("Cannot create materialized view test_catalog.schema.test_mv"); } private QueryStateMachine stateMachine(TransactionManager transactionManager, MetadataManager metadata, AccessControl accessControl) diff --git a/core/trino-main/src/test/java/io/trino/execution/TestCreateSchemaTask.java b/core/trino-main/src/test/java/io/trino/execution/TestCreateSchemaTask.java index 28e7abd70f6e..1a4ab8c50cea 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestCreateSchemaTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestCreateSchemaTask.java @@ -46,7 +46,7 @@ public void testDuplicatedCreateSchema() assertTrue(metadata.schemaExists(testSession, CATALOG_SCHEMA_NAME)); assertThatExceptionOfType(TrinoException.class) .isThrownBy(() -> getFutureValue(task.execute(statement, queryStateMachine, emptyList(), WarningCollector.NOOP))) - .withMessage("Schema 'test-catalog.test_db' already exists"); + .withMessage("Schema 'test_catalog.test_db' already exists"); } @Test @@ -71,14 +71,14 @@ public void failCreateSchema() queryStateMachine, emptyList(), WarningCollector.NOOP))) - .withMessage("TEST create schema fail: test-catalog.test_db"); + .withMessage("TEST create schema fail: test_catalog.test_db"); assertThatExceptionOfType(TrinoException.class) .isThrownBy(() -> getFutureValue(task.execute( new CreateSchema(QualifiedName.of(CATALOG_SCHEMA_NAME.getSchemaName()), true, ImmutableList.of()), queryStateMachine, emptyList(), WarningCollector.NOOP))) - .withMessage("TEST create schema fail: test-catalog.test_db"); + .withMessage("TEST create schema fail: test_catalog.test_db"); } private CreateSchemaTask getCreateSchemaTask() diff --git a/core/trino-main/src/test/java/io/trino/execution/TestCreateTableTask.java b/core/trino-main/src/test/java/io/trino/execution/TestCreateTableTask.java index ef413f26905e..bb9889983524 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestCreateTableTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestCreateTableTask.java @@ -224,7 +224,7 @@ public void testCreateTableWithMaterializedViewPropertyFails() CreateTableTask createTableTask = new CreateTableTask(plannerContext, new AllowAllAccessControl(), columnPropertyManager, tablePropertyManager); assertTrinoExceptionThrownBy(() -> getFutureValue(createTableTask.internalExecute(statement, testSession, emptyList(), output -> {}))) .hasErrorCode(INVALID_TABLE_PROPERTY) - .hasMessage("Catalog 'test-catalog' table property 'foo' does not exist"); + .hasMessage("Catalog 'test_catalog' table property 'foo' does not exist"); assertEquals(metadata.getCreateTableCallCount(), 0); } @@ -276,7 +276,7 @@ public void testCreateWithUnsupportedConnectorThrowsWhenNotNull() assertTrinoExceptionThrownBy(() -> getFutureValue(createTableTask.internalExecute(statement, testSession, emptyList(), output -> {}))) .hasErrorCode(NOT_SUPPORTED) - .hasMessage("Catalog 'test-catalog' does not support non-null column for column name 'b'"); + .hasMessage("Catalog 'test_catalog' does not support non-null column for column name 'b'"); } @Test diff --git a/core/trino-main/src/test/java/io/trino/execution/TestDropSchemaTask.java b/core/trino-main/src/test/java/io/trino/execution/TestDropSchemaTask.java index 57118ccb4631..47a037528ec4 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestDropSchemaTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestDropSchemaTask.java @@ -56,7 +56,7 @@ public void testDropSchemaRestrict() assertThatExceptionOfType(TrinoException.class) .isThrownBy(() -> getFutureValue(dropSchemaTask.execute(dropSchema, queryStateMachine, emptyList(), NOOP))) - .withMessage("Schema 'test-catalog.test_db' does not exist"); + .withMessage("Schema 'test_catalog.test_db' does not exist"); } @Test diff --git a/core/trino-main/src/test/java/io/trino/execution/TestRenameMaterializedViewTask.java b/core/trino-main/src/test/java/io/trino/execution/TestRenameMaterializedViewTask.java index 6189b34633bc..e1527c7c06cf 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestRenameMaterializedViewTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestRenameMaterializedViewTask.java @@ -107,7 +107,7 @@ public void testRenameMaterializedViewOnView() assertTrinoExceptionThrownBy(() -> getFutureValue(executeRenameMaterializedView(viewName, qualifiedName("existing_view_new")))) .hasErrorCode(TABLE_NOT_FOUND) - .hasMessage("Materialized View '%s' does not exist, but a view with that name exists. Did you mean ALTER VIEW test-catalog.schema.existing_view RENAME TO ...?", viewName); + .hasMessage("Materialized View '%s' does not exist, but a view with that name exists. Did you mean ALTER VIEW test_catalog.schema.existing_view RENAME TO ...?", viewName); } @Test @@ -118,7 +118,7 @@ public void testRenameMaterializedViewOnViewIfExists() assertTrinoExceptionThrownBy(() -> getFutureValue(executeRenameMaterializedView(viewName, qualifiedName("existing_view_new"), true))) .hasErrorCode(TABLE_NOT_FOUND) - .hasMessage("Materialized View '%s' does not exist, but a view with that name exists. Did you mean ALTER VIEW test-catalog.schema.existing_view RENAME TO ...?", viewName); + .hasMessage("Materialized View '%s' does not exist, but a view with that name exists. Did you mean ALTER VIEW test_catalog.schema.existing_view RENAME TO ...?", viewName); } @Test diff --git a/core/trino-main/src/test/java/io/trino/execution/TestRenameTableTask.java b/core/trino-main/src/test/java/io/trino/execution/TestRenameTableTask.java index 1ab35e7a1239..b6d06d046c66 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestRenameTableTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestRenameTableTask.java @@ -94,7 +94,7 @@ public void testRenameTableOnMaterializedView() assertTrinoExceptionThrownBy(() -> getFutureValue(executeRenameTable(viewName, qualifiedName("existing_materialized_view_new"), false))) .hasErrorCode(GENERIC_USER_ERROR) - .hasMessage("Table '%s' does not exist, but a materialized view with that name exists. Did you mean ALTER MATERIALIZED VIEW test-catalog.schema.existing_materialized_view RENAME TO ...?", viewName); + .hasMessage("Table '%s' does not exist, but a materialized view with that name exists. Did you mean ALTER MATERIALIZED VIEW test_catalog.schema.existing_materialized_view RENAME TO ...?", viewName); } @Test @@ -105,7 +105,7 @@ public void testRenameTableOnMaterializedViewIfExists() assertTrinoExceptionThrownBy(() -> getFutureValue(executeRenameTable(viewName, qualifiedName("existing_materialized_view_new"), true))) .hasErrorCode(GENERIC_USER_ERROR) - .hasMessage("Table '%s' does not exist, but a materialized view with that name exists. Did you mean ALTER MATERIALIZED VIEW test-catalog.schema.existing_materialized_view RENAME TO ...?", viewName); + .hasMessage("Table '%s' does not exist, but a materialized view with that name exists. Did you mean ALTER MATERIALIZED VIEW test_catalog.schema.existing_materialized_view RENAME TO ...?", viewName); } @Test diff --git a/core/trino-main/src/test/java/io/trino/execution/TestRenameViewTask.java b/core/trino-main/src/test/java/io/trino/execution/TestRenameViewTask.java index 2bee2d32cbd0..00c55d18b0ac 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestRenameViewTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestRenameViewTask.java @@ -75,7 +75,7 @@ public void testRenameViewOnMaterializedView() assertTrinoExceptionThrownBy(() -> getFutureValue(executeRenameView(viewName, qualifiedName("existing_materialized_view_new")))) .hasErrorCode(TABLE_NOT_FOUND) - .hasMessage("View '%s' does not exist, but a materialized view with that name exists. Did you mean ALTER MATERIALIZED VIEW test-catalog.schema.existing_materialized_view RENAME TO ...?", viewName); + .hasMessage("View '%s' does not exist, but a materialized view with that name exists. Did you mean ALTER MATERIALIZED VIEW test_catalog.schema.existing_materialized_view RENAME TO ...?", viewName); } @Test diff --git a/core/trino-main/src/test/java/io/trino/metadata/TestQualifiedObjectName.java b/core/trino-main/src/test/java/io/trino/metadata/TestQualifiedObjectName.java new file mode 100644 index 000000000000..6b407dc0e66b --- /dev/null +++ b/core/trino-main/src/test/java/io/trino/metadata/TestQualifiedObjectName.java @@ -0,0 +1,53 @@ +/* + * 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 io.trino.metadata; + +import io.airlift.json.JsonCodec; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.testng.Assert.assertEquals; + +public class TestQualifiedObjectName +{ + private final JsonCodec codec = JsonCodec.jsonCodec(QualifiedObjectName.class); + + @Test + public void testJsonSerializationRoundTrip() + { + // simple + testRoundTrip(new QualifiedObjectName("catalog", "schema", "table_name")); + + // names with dots + testRoundTrip(new QualifiedObjectName("catalog.twój", "schema.ściema", "tabel.tabelkówna")); + + // names with apostrophes + testRoundTrip(new QualifiedObjectName("cata\"l.o.g\"", "s\"ch.e.ma\"", "\"t.a.b.e.l\"")); + + // non-lowercase (currently illegal but TODO coming in https://github.com/trinodb/trino/issues/17) + assertThatThrownBy(() -> new QualifiedObjectName("CataLOG", "SchemA", "TabEl")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("catalogName is not lowercase: CataLOG"); + + // empty + testRoundTrip(new QualifiedObjectName("", "", "")); + } + + private void testRoundTrip(QualifiedObjectName value) + { + String json = codec.toJson(value); + QualifiedObjectName parsed = codec.fromJson(json); + assertEquals(parsed, value); + } +} diff --git a/core/trino-main/src/test/java/io/trino/security/TestAccessControlManager.java b/core/trino-main/src/test/java/io/trino/security/TestAccessControlManager.java index 62255fa8b648..dad20b7a6465 100644 --- a/core/trino-main/src/test/java/io/trino/security/TestAccessControlManager.java +++ b/core/trino-main/src/test/java/io/trino/security/TestAccessControlManager.java @@ -136,7 +136,7 @@ public void testReadOnlySystemAccessControl() accessControlManager.checkCanInsertIntoTable(new SecurityContext(transactionId, identity, queryId, queryStart), tableName); })) .isInstanceOf(AccessDeniedException.class) - .hasMessage("Access Denied: Cannot insert into table test-catalog.schema.table"); + .hasMessage("Access Denied: Cannot insert into table test_catalog.schema.table"); } @Test @@ -311,7 +311,7 @@ public void testDenyExecuteProcedureBySystem() accessControlManager.addSystemAccessControlFactory(accessControlFactory); accessControlManager.loadSystemAccessControl("deny-all", ImmutableMap.of()); - assertDenyExecuteProcedure(transactionManager, metadata, accessControlManager, "Access Denied: Cannot execute procedure test-catalog.schema.procedure"); + assertDenyExecuteProcedure(transactionManager, metadata, accessControlManager, "Access Denied: Cannot execute procedure test_catalog.schema.procedure"); } @Test diff --git a/core/trino-main/src/test/java/io/trino/sql/analyzer/TestAnalyzer.java b/core/trino-main/src/test/java/io/trino/sql/analyzer/TestAnalyzer.java index 9480fae07584..6f415b9e1b76 100644 --- a/core/trino-main/src/test/java/io/trino/sql/analyzer/TestAnalyzer.java +++ b/core/trino-main/src/test/java/io/trino/sql/analyzer/TestAnalyzer.java @@ -944,11 +944,11 @@ public void testInvalidTable() // table name containing dots assertFails("SELECT * FROM \"table.not.existing\"") .hasErrorCode(TABLE_NOT_FOUND) - .hasMessage("line 1:15: Table 'tpch.s1.table.not.existing' does not exist"); + .hasMessage("line 1:15: Table 'tpch.s1.\"table.not.existing\"' does not exist"); // table name containing whitespace assertFails("SELECT * FROM \"table' does not exist, or maybe 'view\"") .hasErrorCode(TABLE_NOT_FOUND) - .hasMessage("line 1:15: Table 'tpch.s1.table' does not exist, or maybe 'view' does not exist"); + .hasMessage("line 1:15: Table 'tpch.s1.\"table' does not exist, or maybe 'view\"' does not exist"); } @Test diff --git a/core/trino-main/src/test/java/io/trino/sql/analyzer/TestOutput.java b/core/trino-main/src/test/java/io/trino/sql/analyzer/TestOutput.java index ddaf57f7b157..6032c863a2a4 100644 --- a/core/trino-main/src/test/java/io/trino/sql/analyzer/TestOutput.java +++ b/core/trino-main/src/test/java/io/trino/sql/analyzer/TestOutput.java @@ -50,4 +50,25 @@ public void testRoundTrip() assertEquals(actual, expected); } + + @Test + public void testRoundWithComplexIdentifiers() + { + Output expected = new Output( + "catalog.Mój", + new CatalogVersion("default"), + "ści.e-Ma", + "ta.b-Elką go", + Optional.of( + ImmutableList.of( + new OutputColumn( + new Column("ko.LU-mieńka", "type"), + ImmutableSet.of( + new SourceColumn(new QualifiedObjectName("catalog.twój", "schema.ściema", "tabel.tabelkówna"), "co-lumn.słodziak\"")))))); + + String json = codec.toJson(expected); + Output actual = codec.fromJson(json); + + assertEquals(actual, expected); + } } diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestLogicalPlanner.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestLogicalPlanner.java index f75bb051d847..868d59c7fc61 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestLogicalPlanner.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestLogicalPlanner.java @@ -2028,7 +2028,7 @@ public void testGroupingSetsWithDefaultValue() public void testSizeBasedJoin() { // both local.sf100000.nation and local.sf100000.orders don't provide stats, therefore no reordering happens - assertDistributedPlan("SELECT custkey FROM \"test-catalog\".\"sf42.5\".nation, \"test-catalog\".\"sf42.5\".orders WHERE nation.nationkey = orders.custkey", + assertDistributedPlan("SELECT custkey FROM \"test_catalog\".\"sf42.5\".nation, \"test_catalog\".\"sf42.5\".orders WHERE nation.nationkey = orders.custkey", automaticJoinDistribution(), output( join(INNER, builder -> builder @@ -2040,7 +2040,7 @@ public void testSizeBasedJoin() anyTree( tableScan("orders", ImmutableMap.of("CUSTKEY", "custkey"))))))); - assertDistributedPlan("SELECT custkey FROM (VALUES CAST(1 AS BIGINT), CAST(2 AS BIGINT)) t(a), \"test-catalog\".\"sf42.5\".orders WHERE t.a = orders.custkey", + assertDistributedPlan("SELECT custkey FROM (VALUES CAST(1 AS BIGINT), CAST(2 AS BIGINT)) t(a), \"test_catalog\".\"sf42.5\".orders WHERE t.a = orders.custkey", automaticJoinDistribution(), output( join(INNER, builder -> builder @@ -2058,7 +2058,7 @@ public void testSizeBasedJoin() public void testSizeBasedSemiJoin() { // both local.sf100000.nation and local.sf100000.orders don't provide stats, therefore no reordering happens - assertDistributedPlan("SELECT custkey FROM \"test-catalog\".\"sf42.5\".orders WHERE orders.custkey NOT IN (SELECT nationkey FROM \"test-catalog\".\"sf42.5\".nation)", + assertDistributedPlan("SELECT custkey FROM \"test_catalog\".\"sf42.5\".orders WHERE orders.custkey NOT IN (SELECT nationkey FROM \"test_catalog\".\"sf42.5\".nation)", automaticJoinDistribution(), output( anyTree( @@ -2069,7 +2069,7 @@ public void testSizeBasedSemiJoin() tableScan("nation", ImmutableMap.of("NATIONKEY", "nationkey"))))))); // values node provides stats - assertDistributedPlan("SELECT custkey FROM \"test-catalog\".\"sf42.5\".orders WHERE orders.custkey NOT IN (SELECT t.a FROM (VALUES CAST(1 AS BIGINT), CAST(2 AS BIGINT)) t(a))", + assertDistributedPlan("SELECT custkey FROM \"test_catalog\".\"sf42.5\".orders WHERE orders.custkey NOT IN (SELECT t.a FROM (VALUES CAST(1 AS BIGINT), CAST(2 AS BIGINT)) t(a))", automaticJoinDistribution(), output( anyTree( diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestApplyTableScanRedirection.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestApplyTableScanRedirection.java index 0e2e030689a4..e7f287b02e37 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestApplyTableScanRedirection.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestApplyTableScanRedirection.java @@ -184,7 +184,7 @@ public void testMismatchedTypesWithMissingCoercion() WarningCollector.NOOP, createPlanOptimizersStatsCollector())) .isInstanceOf(TrinoException.class) - .hasMessageMatching("Cast not possible from redirected column test-catalog.target_schema.target_table.destination_col_d with type Bogus to source column .*test-catalog.test_schema.test_table.*source_col_a.* with type: varchar"); + .hasMessageMatching("Cast not possible from redirected column test_catalog.target_schema.target_table.destination_col_d with type Bogus to source column .*test_catalog.test_schema.test_table.*source_col_a.* with type: varchar"); return null; }); } diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestFilterInaccessibleColumns.java b/core/trino-main/src/test/java/io/trino/sql/query/TestFilterInaccessibleColumns.java index ef580e30c663..c678938bdff2 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestFilterInaccessibleColumns.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestFilterInaccessibleColumns.java @@ -149,7 +149,7 @@ public void testFilterExplicitSelect() // Select all columns explicitly assertThatThrownBy(() -> assertions.query("SELECT nationkey, name, regionkey, comment FROM nation WHERE name = 'FRANCE'")) - .hasMessage("Access Denied: Cannot select from columns [nationkey, regionkey, name, comment] in table or view test-catalog.tiny.nation"); + .hasMessage("Access Denied: Cannot select from columns [nationkey, regionkey, name, comment] in table or view test_catalog.tiny.nation"); } @Test @@ -185,7 +185,7 @@ public void testRowFilterWithoutAccessToInaccessibleColumn() .build()); accessControl.deny(privilege(USER, "nation.comment", SELECT_COLUMN)); assertThatThrownBy(() -> assertions.query("SELECT * FROM nation WHERE name = 'FRANCE'")) - .hasMessage("Access Denied: Cannot select from columns [nationkey, regionkey, name, comment] in table or view test-catalog.tiny.nation"); + .hasMessage("Access Denied: Cannot select from columns [nationkey, regionkey, name, comment] in table or view test_catalog.tiny.nation"); } @Test @@ -204,7 +204,7 @@ public void testRowFilterAsSessionUserOnInaccessibleColumn() accessControl.rowFilter(table, USER, filter); assertThatThrownBy(() -> assertions.query(user(USER), "SELECT * FROM nation WHERE name = 'FRANCE'")) - .hasMessage("Access Denied: Cannot select from columns [nationkey, regionkey, name, comment] in table or view test-catalog.tiny.nation"); + .hasMessage("Access Denied: Cannot select from columns [nationkey, regionkey, name, comment] in table or view test_catalog.tiny.nation"); assertThat(assertions.query(user(ADMIN), "SELECT * FROM nation WHERE name = 'FRANCE'")) .matches("VALUES (BIGINT '6', CAST('FRANCE' AS VARCHAR(25)), BIGINT '3', CAST('refully final requests. regular, ironi' AS VARCHAR(152)))"); } @@ -244,7 +244,7 @@ public void testMaskingWithoutAccessToInaccessibleColumn() .build()); assertThatThrownBy(() -> assertions.query("SELECT * FROM nation WHERE name = 'FRANCE'")) - .hasMessage("Access Denied: Cannot select from columns [nationkey, regionkey, name, comment] in table or view test-catalog.tiny.nation"); + .hasMessage("Access Denied: Cannot select from columns [nationkey, regionkey, name, comment] in table or view test_catalog.tiny.nation"); } @Test @@ -286,7 +286,7 @@ public void testMaskingAsSessionUserWithCaseOnInaccessibleColumn() accessControl.columnMask(table, "comment", USER, mask); assertThatThrownBy(() -> assertions.query(user(USER), "SELECT * FROM nation WHERE name = 'FRANCE'")) - .hasMessage("Access Denied: Cannot select from columns [nationkey, regionkey, name, comment] in table or view test-catalog.tiny.nation"); + .hasMessage("Access Denied: Cannot select from columns [nationkey, regionkey, name, comment] in table or view test_catalog.tiny.nation"); assertThat(assertions.query(user(ADMIN), "SELECT * FROM nation WHERE name = 'CANADA'")) .matches("VALUES (BIGINT '3', CAST('CANADA' AS VARCHAR(25)), BIGINT '1', CAST('masked-comment' AS VARCHAR(152)))"); } @@ -299,7 +299,7 @@ public void testPredicateOnInaccessibleColumn() // Hide name but use it in the query predicate accessControl.deny(privilege(USER, "nation.name", SELECT_COLUMN)); assertThatThrownBy(() -> assertions.query("SELECT * FROM nation WHERE name = 'FRANCE'")) - .hasMessage("Access Denied: Cannot select from columns [nationkey, regionkey, name, comment] in table or view test-catalog.tiny.nation"); + .hasMessage("Access Denied: Cannot select from columns [nationkey, regionkey, name, comment] in table or view test_catalog.tiny.nation"); } @Test @@ -348,7 +348,7 @@ public void testFunctionOnInaccessibleColumn() accessControl.deny(privilege(USER, "nation.name", SELECT_COLUMN)); assertThatThrownBy(() -> assertions.query("SELECT * FROM (SELECT concat(name,'-test') FROM nation WHERE name = 'FRANCE')")) - .hasMessage("Access Denied: Cannot select from columns [name] in table or view test-catalog.tiny.nation"); + .hasMessage("Access Denied: Cannot select from columns [name] in table or view test_catalog.tiny.nation"); } private Session user(String user) diff --git a/core/trino-parser/src/main/java/io/trino/sql/SqlFormatter.java b/core/trino-parser/src/main/java/io/trino/sql/SqlFormatter.java index 9738cf63fe41..4d4299e39768 100644 --- a/core/trino-parser/src/main/java/io/trino/sql/SqlFormatter.java +++ b/core/trino-parser/src/main/java/io/trino/sql/SqlFormatter.java @@ -218,7 +218,7 @@ private static String formatName(Identifier identifier) return ExpressionFormatter.formatExpression(identifier); } - static String formatName(QualifiedName name) + public static String formatName(QualifiedName name) { return name.getOriginalParts().stream() .map(SqlFormatter::formatName) diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java index 85b775e6c2b4..d9db340152fa 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java @@ -290,7 +290,7 @@ public void testNoDataSystemTable() "Expecting message:\n" + " \"Cannot read partition information from a table that is not partitioned: \\E\\S+\\Q:tpch.nation$data\"\n" + "to match regex:\n" + - " \"line 1:1: Table '\\w+.\\w+.nation\\$data' does not exist\"\n" + + " \"line 1:1: Table '\\w+.\\w+.\"nation\\$data\"' does not exist\"\n" + "but did not."); throw new SkipException("TODO"); } diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakePartitioning.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakePartitioning.java index 3720624a77ac..d614ebc19c03 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakePartitioning.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakePartitioning.java @@ -144,7 +144,7 @@ public void testPartitionsSystemTableDoesNotExist() { assertQueryFails( "SELECT * FROM \"partitions$partitions\"", - ".*'delta\\.tpch\\.partitions\\$partitions' does not exist"); + ".*'delta\\.tpch\\.\"partitions\\$partitions\"' does not exist"); } @Test diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java index 255331d15991..0612e6f01c9f 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java @@ -3627,17 +3627,17 @@ public void testShowColumnsFromPartitions() assertQueryFails( getSession(), "SHOW COLUMNS FROM \"$partitions\"", - ".*Table '.*\\.tpch\\.\\$partitions' does not exist"); + ".*Table '.*\\.tpch\\.\"\\$partitions\"' does not exist"); assertQueryFails( getSession(), "SHOW COLUMNS FROM \"orders$partitions\"", - ".*Table '.*\\.tpch\\.orders\\$partitions' does not exist"); + ".*Table '.*\\.tpch\\.\"orders\\$partitions\"' does not exist"); assertQueryFails( getSession(), "SHOW COLUMNS FROM \"blah$partitions\"", - ".*Table '.*\\.tpch\\.blah\\$partitions' does not exist"); + ".*Table '.*\\.tpch\\.\"blah\\$partitions\"' does not exist"); } @Test @@ -3659,12 +3659,12 @@ public void testPartitionsTableInvalidAccess() assertQueryFails( getSession(), "SELECT * FROM \"test_partitions_invalid$partitions$partitions\"", - ".*Table '.*\\.tpch\\.test_partitions_invalid\\$partitions\\$partitions' does not exist"); + ".*Table '.*\\.tpch\\.\"test_partitions_invalid\\$partitions\\$partitions\"' does not exist"); assertQueryFails( getSession(), "SELECT * FROM \"non_existent$partitions\"", - ".*Table '.*\\.tpch\\.non_existent\\$partitions' does not exist"); + ".*Table '.*\\.tpch\\.\"non_existent\\$partitions\"' does not exist"); } @Test diff --git a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiSystemTables.java b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiSystemTables.java index 80be28fea6b3..1eedbbf22b5c 100644 --- a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiSystemTables.java +++ b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiSystemTables.java @@ -43,6 +43,6 @@ public void testTimelineTable() "VALUES ('20220906063435640', 'commit', 'COMPLETED'), ('20220906063456550', 'commit', 'COMPLETED')"); assertQueryFails("SELECT timestamp, action, state FROM tests.\"non_existing$timeline\"", - ".*Table 'hudi.tests.non_existing\\$timeline' does not exist"); + ".*Table 'hudi.tests.\"non_existing\\$timeline\"' does not exist"); } } diff --git a/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteConnectorTest.java b/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteConnectorTest.java index 94ef4dd034b0..067781018014 100644 --- a/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteConnectorTest.java +++ b/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteConnectorTest.java @@ -148,8 +148,8 @@ public void testDatabaseMetadataSearchEscapedWildCardCharacters() assertThat((String) computeScalar("SHOW CREATE TABLE " + normalTableName)).contains("primary_key = ARRAY['a']"); assertThat((String) computeScalar("SHOW CREATE TABLE " + underscoreTableName)).contains("primary_key = ARRAY['b']"); assertThat((String) computeScalar("SHOW CREATE TABLE " + percentTableName)).contains("primary_key = ARRAY['c']"); - assertQueryFails("SHOW CREATE TABLE " + "\"test%\"", ".*Table 'ignite.public.test%' does not exist"); - assertQueryFails("SHOW COLUMNS FROM " + "\"test%\"", ".*Table 'ignite.public.test%' does not exist"); + assertQueryFails("SHOW CREATE TABLE " + "\"test%\"", ".*Table 'ignite.public.\"test%\"' does not exist"); + assertQueryFails("SHOW COLUMNS FROM " + "\"test%\"", ".*Table 'ignite.public.\"test%\"' does not exist"); } finally { assertUpdate("DROP TABLE IF EXISTS " + normalTableName); diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/BaseRaptorConnectorTest.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/BaseRaptorConnectorTest.java index 5691851118c6..c8fdfa351a5a 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/BaseRaptorConnectorTest.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/BaseRaptorConnectorTest.java @@ -496,7 +496,7 @@ public void testColumnRangesSystemTable() "SELECT min(orderkey), max(orderkey) FROM orders"); // No such table test - assertQueryFails("SELECT * FROM \"no_table$column_ranges\"", ".*'raptor\\.tpch\\.no_table\\$column_ranges' does not exist.*"); + assertQueryFails("SELECT * FROM \"no_table$column_ranges\"", ".*'raptor\\.tpch\\.\"no_table\\$column_ranges\"' does not exist.*"); // No range column for DOUBLE, INTEGER or VARCHAR assertQueryFails("SELECT totalprice_min FROM \"orders$column_ranges\"", ".*Column 'totalprice_min' cannot be resolved.*"); @@ -521,7 +521,7 @@ public void testColumnRangesSystemTable() // Drop table assertUpdate("DROP TABLE column_ranges_test"); assertQueryFails("SELECT a_min, a_max, b_min, b_max FROM \"column_ranges_test$column_ranges\"", - ".*'raptor\\.tpch\\.column_ranges_test\\$column_ranges' does not exist.*"); + ".*'raptor\\.tpch\\.\"column_ranges_test\\$column_ranges\"' does not exist.*"); } @Test diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestHiveAndDeltaLakeRedirect.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestHiveAndDeltaLakeRedirect.java index d72f2f048a3b..51d62b2e3745 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestHiveAndDeltaLakeRedirect.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestHiveAndDeltaLakeRedirect.java @@ -135,8 +135,8 @@ public void testHiveToUnpartitionedDeltaPartitionsRedirectFailure() try { assertQueryFailure(() -> onTrino().executeQuery(format("SELECT * FROM hive.default.\"%s$partitions\"", tableName))) - .hasMessageMatching(".*Table 'hive.default.test_delta_lake_unpartitioned_table_.*\\$partitions' redirected to 'delta.default.test_delta_lake_unpartitioned_table_.*\\$partitions', " + - "but the target table 'delta.default.test_delta_lake_unpartitioned_table_.*\\$partitions' does not exist"); + .hasMessageMatching(".*Table 'hive.default.\"test_delta_lake_unpartitioned_table_.*\\$partitions\"' redirected to 'delta.default.\"test_delta_lake_unpartitioned_table_.*\\$partitions\"', " + + "but the target table 'delta.default.\"test_delta_lake_unpartitioned_table_.*\\$partitions\"' does not exist"); } finally { dropDeltaTableWithRetry(tableName); @@ -152,8 +152,8 @@ public void testHiveToPartitionedDeltaPartitionsRedirectFailure() try { assertQueryFailure(() -> onTrino().executeQuery(format("SELECT * FROM hive.default.\"%s$partitions\"", tableName))) - .hasMessageMatching(".*Table 'hive.default.test_delta_lake_partitioned_table_.*\\$partitions' redirected to 'delta.default.test_delta_lake_partitioned_table_.*\\$partitions', " + - "but the target table 'delta.default.test_delta_lake_partitioned_table_.*\\$partitions' does not exist"); + .hasMessageMatching(".*Table 'hive.default.\"test_delta_lake_partitioned_table_.*\\$partitions\"' redirected to 'delta.default.\"test_delta_lake_partitioned_table_.*\\$partitions\"', " + + "but the target table 'delta.default.\"test_delta_lake_partitioned_table_.*\\$partitions\"' does not exist"); } finally { dropDeltaTableWithRetry(tableName); @@ -291,7 +291,7 @@ public void testDeltaToUnpartitionedHivePartitionsRedirectFailure() try { assertQueryFailure(() -> onTrino().executeQuery(format("SELECT * FROM delta.default.\"%s$partitions\"", tableName))) - .hasMessageMatching(".*Table 'delta.default.test_hive_unpartitioned_table.*partitions' does not exist"); + .hasMessageMatching(".*Table 'delta.default.\"test_hive_unpartitioned_table.*partitions\"' does not exist"); } finally { onTrino().executeQuery("DROP TABLE hive.default." + tableName); diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHivePartitionsTable.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHivePartitionsTable.java index 67584db0bb44..b963baa1e049 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHivePartitionsTable.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHivePartitionsTable.java @@ -129,7 +129,7 @@ public void testShowPartitionsFromHiveTable() public void testShowPartitionsFromUnpartitionedTable() { assertQueryFailure(() -> onTrino().executeQuery("SELECT * FROM \"nation$partitions\"")) - .hasMessageMatching(".*Table 'hive.default.nation\\$partitions' does not exist"); + .hasMessageMatching(".*Table 'hive.default.\"nation\\$partitions\"' does not exist"); } @Test(groups = HIVE_PARTITIONING) diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hudi/TestHudiHiveTablesCompatibility.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hudi/TestHudiHiveTablesCompatibility.java index 695ec6e36e74..2a7c9e202bd8 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hudi/TestHudiHiveTablesCompatibility.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hudi/TestHudiHiveTablesCompatibility.java @@ -45,10 +45,10 @@ public void testHudiSelectFromHiveTable() .hasMessageMatching("Query failed \\(#\\w+\\):\\Q Not a Hudi table: default." + tableName); assertQueryFailure(() -> onTrino().executeQuery("SELECT * FROM hudi.default.\"" + tableName + "$data\"")) - .hasMessageMatching("Query failed \\(#\\w+\\):\\Q line 1:15: Table 'hudi.default." + tableName + "$data' does not exist"); + .hasMessageMatching("Query failed \\(#\\w+\\):\\Q line 1:15: Table 'hudi.default.\"" + tableName + "$data\"' does not exist"); assertQueryFailure(() -> onTrino().executeQuery("SELECT * FROM hudi.default.\"" + tableName + "$timeline\"")) - .hasMessageMatching("Query failed \\(#\\w+\\):\\Q line 1:15: Table 'hudi.default." + tableName + "$timeline' does not exist"); + .hasMessageMatching("Query failed \\(#\\w+\\):\\Q line 1:15: Table 'hudi.default.\"" + tableName + "$timeline\"' does not exist"); assertQueryFailure(() -> onTrino().executeQuery("SELECT * FROM hudi.default.\"" + tableName + "$files\"")) .hasMessageMatching("Query failed \\(#\\w+\\):\\Q Invalid Hudi table name (unknown type 'files'): " + tableName + "$files"); @@ -68,7 +68,7 @@ public void testHiveSelectFromHudiTable() .hasMessageMatching(format("Query failed \\(#\\w+\\):\\Q line 1:15: Table 'hive.default.%s' does not exist", tableName)); assertQueryFailure(() -> onTrino().executeQuery("SELECT * FROM hive.default.\"" + tableName + "$partitions\"")) - .hasMessageMatching("Query failed \\(#\\w+\\):\\Q line 1:15: Table 'hive.default." + tableName + "$partitions' does not exist"); + .hasMessageMatching("Query failed \\(#\\w+\\):\\Q line 1:15: Table 'hive.default.\"" + tableName + "$partitions\"' does not exist"); assertQueryFailure(() -> onTrino().executeQuery("SELECT * FROM hive.default.\"" + tableName + "$properties\"")) .hasMessageMatching("Query failed \\(#\\w+\\):\\Q Table 'default." + tableName + "$properties' not found"); diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hudi/TestHudiSparkCompatibility.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hudi/TestHudiSparkCompatibility.java index 76d0491be87c..139d7dc48b0a 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hudi/TestHudiSparkCompatibility.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hudi/TestHudiSparkCompatibility.java @@ -313,7 +313,7 @@ public void testTimelineTableRedirect() assertThat(onTrino().executeQuery(format("SELECT action, state FROM hive.default.\"%s$timeline\"", tableName))) .containsOnly(row("commit", "COMPLETED")); assertQueryFailure(() -> onTrino().executeQuery(format("SELECT * FROM hive.default.\"%s$timeline\"", nonExistingTableName))) - .hasMessageMatching(".*Table 'hive.default.test_hudi_timeline_system_table_redirect_.*_non_existing\\$timeline' does not exist"); + .hasMessageMatching(".*Table 'hive.default.\"test_hudi_timeline_system_table_redirect_.*_non_existing\\$timeline\"' does not exist"); } finally { onHudi().executeQuery("DROP TABLE " + tableName); diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergHiveTablesCompatibility.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergHiveTablesCompatibility.java index 761ee986054e..9540ad903826 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergHiveTablesCompatibility.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergHiveTablesCompatibility.java @@ -47,10 +47,10 @@ public void testIcebergSelectFromHiveTable() .hasMessageMatching("Query failed \\(#\\w+\\):\\Q Not an Iceberg table: default." + tableName); assertQueryFailure(() -> onTrino().executeQuery("SELECT * FROM iceberg.default.\"" + tableName + "$data\"")) - .hasMessageMatching("Query failed \\(#\\w+\\):\\Q line 1:15: Table 'iceberg.default." + tableName + "$data' does not exist"); + .hasMessageMatching("Query failed \\(#\\w+\\):\\Q line 1:15: Table 'iceberg.default.\"" + tableName + "$data\"' does not exist"); assertQueryFailure(() -> onTrino().executeQuery("SELECT * FROM iceberg.default.\"" + tableName + "$files\"")) - .hasMessageMatching("Query failed \\(#\\w+\\):\\Q line 1:15: Table 'iceberg.default." + tableName + "$files' does not exist"); + .hasMessageMatching("Query failed \\(#\\w+\\):\\Q line 1:15: Table 'iceberg.default.\"" + tableName + "$files\"' does not exist"); onTrino().executeQuery("DROP TABLE hive.default." + tableName); } @@ -65,10 +65,10 @@ public void testHiveSelectFromIcebergTable() .hasMessageMatching(format("Query failed \\(#\\w+\\):\\Q Cannot query Iceberg table 'default.%s'", tableName)); assertQueryFailure(() -> onTrino().executeQuery("SELECT * FROM hive.default.\"" + tableName + "$partitions\"")) - .hasMessageMatching("Query failed \\(#\\w+\\):\\Q line 1:15: Table 'hive.default." + tableName + "$partitions' does not exist"); + .hasMessageMatching("Query failed \\(#\\w+\\):\\Q line 1:15: Table 'hive.default.\"" + tableName + "$partitions\"' does not exist"); assertQueryFailure(() -> onTrino().executeQuery("SELECT * FROM hive.default.\"" + tableName + "$properties\"")) - .hasMessageMatching("Query failed \\(#\\w+\\):\\Q line 1:15: Table 'hive.default." + tableName + "$properties' does not exist"); + .hasMessageMatching("Query failed \\(#\\w+\\):\\Q line 1:15: Table 'hive.default.\"" + tableName + "$properties\"' does not exist"); onTrino().executeQuery("DROP TABLE iceberg.default." + tableName); } diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergRedirectionToHive.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergRedirectionToHive.java index ca8afab6b255..723b6667ffdd 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergRedirectionToHive.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergRedirectionToHive.java @@ -131,12 +131,12 @@ public void testRedirectPartitionsToUnpartitioned() { String tableName = "hive_unpartitioned_table_" + randomNameSuffix(); String hiveTableName = "hive.default." + tableName; - String icebergTableName = "iceberg.default." + tableName; createHiveTable(hiveTableName, false); assertQueryFailure(() -> onTrino().executeQuery("TABLE iceberg.default.\"" + tableName + "$partitions\"")) - .hasMessageMatching("\\QQuery failed (#\\E\\S+\\Q): Table '" + icebergTableName + "$partitions' redirected to '" + hiveTableName + "$partitions', but the target table '" + hiveTableName + "$partitions' does not exist"); + .hasMessageMatching("\\QQuery failed (#\\E\\S+\\Q): Table 'iceberg.default.\"" + tableName + "$partitions\"' redirected to 'hive.default.\"" + tableName + "$partitions\"', " + + "but the target table 'hive.default.\"" + tableName + "$partitions\"' does not exist"); onTrino().executeQuery("DROP TABLE " + hiveTableName); } @@ -146,12 +146,12 @@ public void testRedirectInvalidSystemTable() { String tableName = "hive_invalid_table_" + randomNameSuffix(); String hiveTableName = "hive.default." + tableName; - String icebergTableName = "iceberg.default." + tableName; createHiveTable(hiveTableName, false); assertQueryFailure(() -> onTrino().executeQuery("TABLE iceberg.default.\"" + tableName + "$invalid\"")) - .hasMessageMatching("\\QQuery failed (#\\E\\S+\\Q): Table '" + icebergTableName + "$invalid' redirected to '" + hiveTableName + "$invalid', but the target table '" + hiveTableName + "$invalid' does not exist"); + .hasMessageMatching("\\QQuery failed (#\\E\\S+\\Q): Table 'iceberg.default.\"" + tableName + "$invalid\"' redirected to 'hive.default.\"" + tableName + "$invalid\"', " + + "but the target table 'hive.default.\"" + tableName + "$invalid\"' does not exist"); onTrino().executeQuery("DROP TABLE " + hiveTableName); } diff --git a/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java b/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java index 031fc152a3fa..db262998ed41 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java @@ -5365,7 +5365,7 @@ public void testWrittenDataSize() public void testNoDataSystemTable() { assertQuerySucceeds("TABLE nation"); - assertQueryFails("TABLE \"nation$data\"", "line 1:1: Table '\\w+.\\w+.nation\\$data' does not exist"); + assertQueryFails("TABLE \"nation$data\"", "line 1:1: Table '\\w+.\\w+.\"nation\\$data\"' does not exist"); } @Test(dataProvider = "testColumnNameDataProvider") From 0a8dca8b2dfbb1268a06e9fbf0ec6827a9707849 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sat, 4 Nov 2023 13:05:04 -0700 Subject: [PATCH 107/587] Add format method annotation to corruption exceptions --- lib/trino-orc/pom.xml | 6 ++++++ .../src/main/java/io/trino/orc/OrcCorruptionException.java | 4 ++++ lib/trino-orc/src/main/java/io/trino/orc/OrcReader.java | 3 +++ .../src/main/java/io/trino/orc/OrcRecordReader.java | 3 +++ .../trino/orc/metadata/ExceptionWrappingMetadataReader.java | 2 +- lib/trino-parquet/pom.xml | 6 ++++++ .../java/io/trino/parquet/ParquetCorruptionException.java | 5 +++++ .../main/java/io/trino/parquet/ParquetValidationUtils.java | 4 ++++ .../parquet/predicate/TupleDomainParquetPredicate.java | 4 ++-- .../main/java/io/trino/parquet/reader/ParquetReader.java | 3 +++ 10 files changed, 37 insertions(+), 3 deletions(-) diff --git a/lib/trino-orc/pom.xml b/lib/trino-orc/pom.xml index 85c9545ae6f4..ee95acf44d69 100644 --- a/lib/trino-orc/pom.xml +++ b/lib/trino-orc/pom.xml @@ -16,6 +16,12 @@ + + com.google.errorprone + error_prone_annotations + true + + com.google.guava guava diff --git a/lib/trino-orc/src/main/java/io/trino/orc/OrcCorruptionException.java b/lib/trino-orc/src/main/java/io/trino/orc/OrcCorruptionException.java index 34535830c803..ff6a01142640 100644 --- a/lib/trino-orc/src/main/java/io/trino/orc/OrcCorruptionException.java +++ b/lib/trino-orc/src/main/java/io/trino/orc/OrcCorruptionException.java @@ -13,6 +13,8 @@ */ package io.trino.orc; +import com.google.errorprone.annotations.FormatMethod; + import java.io.IOException; import static java.lang.String.format; @@ -25,11 +27,13 @@ public OrcCorruptionException(OrcDataSourceId orcDataSourceId, String message) this(orcDataSourceId, "%s", message); } + @FormatMethod public OrcCorruptionException(OrcDataSourceId orcDataSourceId, String messageFormat, Object... args) { super(formatMessage(orcDataSourceId, messageFormat, args)); } + @FormatMethod public OrcCorruptionException(Throwable cause, OrcDataSourceId orcDataSourceId, String messageFormat, Object... args) { super(formatMessage(orcDataSourceId, messageFormat, args), cause); diff --git a/lib/trino-orc/src/main/java/io/trino/orc/OrcReader.java b/lib/trino-orc/src/main/java/io/trino/orc/OrcReader.java index 54b0e0ade5f0..14a4c21a90ef 100644 --- a/lib/trino-orc/src/main/java/io/trino/orc/OrcReader.java +++ b/lib/trino-orc/src/main/java/io/trino/orc/OrcReader.java @@ -16,6 +16,7 @@ import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.FormatMethod; import io.airlift.log.Logger; import io.airlift.slice.Slice; import io.airlift.units.DataSize; @@ -393,6 +394,8 @@ private static void checkOrcVersion(OrcDataSource orcDataSource, List v } } + @SuppressWarnings("FormatStringAnnotation") + @FormatMethod private void validateWrite(Predicate test, String messageFormat, Object... args) throws OrcCorruptionException { diff --git a/lib/trino-orc/src/main/java/io/trino/orc/OrcRecordReader.java b/lib/trino-orc/src/main/java/io/trino/orc/OrcRecordReader.java index 04c7f5737ae7..ded8bfbc4598 100644 --- a/lib/trino-orc/src/main/java/io/trino/orc/OrcRecordReader.java +++ b/lib/trino-orc/src/main/java/io/trino/orc/OrcRecordReader.java @@ -19,6 +19,7 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.common.io.Closer; +import com.google.errorprone.annotations.FormatMethod; import io.airlift.slice.Slice; import io.airlift.units.DataSize; import io.trino.memory.context.AggregatedMemoryContext; @@ -570,6 +571,8 @@ private void advanceToNextStripe() orcDataSourceMemoryUsage.setBytes(orcDataSource.getRetainedSize()); } + @SuppressWarnings("FormatStringAnnotation") + @FormatMethod private void validateWrite(Predicate test, String messageFormat, Object... args) throws OrcCorruptionException { diff --git a/lib/trino-orc/src/main/java/io/trino/orc/metadata/ExceptionWrappingMetadataReader.java b/lib/trino-orc/src/main/java/io/trino/orc/metadata/ExceptionWrappingMetadataReader.java index b4287d4172aa..46262fc6487a 100644 --- a/lib/trino-orc/src/main/java/io/trino/orc/metadata/ExceptionWrappingMetadataReader.java +++ b/lib/trino-orc/src/main/java/io/trino/orc/metadata/ExceptionWrappingMetadataReader.java @@ -116,6 +116,6 @@ public List readBloomFilterIndexes(InputStream inputStream) private OrcCorruptionException propagate(Throwable throwable, String message) { propagateIfPossible(throwable, TrinoException.class); - return new OrcCorruptionException(throwable, orcDataSourceId, message); + return new OrcCorruptionException(throwable, orcDataSourceId, "%s", message); } } diff --git a/lib/trino-parquet/pom.xml b/lib/trino-parquet/pom.xml index 9a4e103d8a55..9ce6dbcd45a1 100644 --- a/lib/trino-parquet/pom.xml +++ b/lib/trino-parquet/pom.xml @@ -16,6 +16,12 @@ + + com.google.errorprone + error_prone_annotations + true + + com.google.guava guava diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetCorruptionException.java b/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetCorruptionException.java index 719190d336ea..451c9d5c7c57 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetCorruptionException.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetCorruptionException.java @@ -13,6 +13,8 @@ */ package io.trino.parquet; +import com.google.errorprone.annotations.FormatMethod; + import java.io.IOException; import static java.lang.String.format; @@ -25,16 +27,19 @@ public ParquetCorruptionException(String message) super(message); } + @FormatMethod public ParquetCorruptionException(String messageFormat, Object... args) { super(format(messageFormat, args)); } + @FormatMethod public ParquetCorruptionException(Throwable cause, String messageFormat, Object... args) { super(format(messageFormat, args), cause); } + @FormatMethod public ParquetCorruptionException(ParquetDataSourceId dataSourceId, String messageFormat, Object... args) { super(formatMessage(dataSourceId, messageFormat, args)); diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetValidationUtils.java b/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetValidationUtils.java index c194405c0029..9be1c4477947 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetValidationUtils.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetValidationUtils.java @@ -13,12 +13,15 @@ */ package io.trino.parquet; +import com.google.errorprone.annotations.FormatMethod; + import static java.lang.String.format; public final class ParquetValidationUtils { private ParquetValidationUtils() {} + @FormatMethod public static void validateParquet(boolean condition, String formatString, Object... args) throws ParquetCorruptionException { @@ -27,6 +30,7 @@ public static void validateParquet(boolean condition, String formatString, Objec } } + @FormatMethod public static void validateParquet(boolean condition, ParquetDataSourceId dataSourceId, String formatString, Object... args) throws ParquetCorruptionException { diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/predicate/TupleDomainParquetPredicate.java b/lib/trino-parquet/src/main/java/io/trino/parquet/predicate/TupleDomainParquetPredicate.java index c69d0506a02c..107bdf241b64 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/predicate/TupleDomainParquetPredicate.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/predicate/TupleDomainParquetPredicate.java @@ -595,12 +595,12 @@ private static Domain getDomain(Type type, DictionaryDescriptor dictionaryDescri private static ParquetCorruptionException corruptionException(String column, ParquetDataSourceId id, Statistics statistics, Exception cause) { - return new ParquetCorruptionException(cause, format("Corrupted statistics for column \"%s\" in Parquet file \"%s\": [%s]", column, id, statistics)); + return new ParquetCorruptionException(cause, "Corrupted statistics for column \"%s\" in Parquet file \"%s\": [%s]", column, id, statistics); } private static ParquetCorruptionException corruptionException(String column, ParquetDataSourceId id, ColumnIndex columnIndex, Exception cause) { - return new ParquetCorruptionException(cause, format("Corrupted statistics for column \"%s\" in Parquet file \"%s\". Corrupted column index: [%s]", column, id, columnIndex)); + return new ParquetCorruptionException(cause, "Corrupted statistics for column \"%s\" in Parquet file \"%s\". Corrupted column index: [%s]", column, id, columnIndex); } private static boolean isCorruptedColumnIndex( diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/ParquetReader.java b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/ParquetReader.java index b7e111c7fcde..f02084c451da 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/ParquetReader.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/ParquetReader.java @@ -17,6 +17,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ListMultimap; +import com.google.errorprone.annotations.FormatMethod; import io.airlift.log.Logger; import io.trino.memory.context.AggregatedMemoryContext; import io.trino.parquet.ChunkKey; @@ -627,6 +628,8 @@ private void validateBlockMetadata(List blockMetaData) } } + @SuppressWarnings("FormatStringAnnotation") + @FormatMethod private void validateWrite(java.util.function.Predicate test, String messageFormat, Object... args) throws ParquetCorruptionException { From 7d664e185917f3343348407dca58ac5632a55df2 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sat, 4 Nov 2023 18:27:31 -0700 Subject: [PATCH 108/587] Require filename for Parquet corruption exceptions --- .../parquet/ParquetCompressionUtils.java | 4 ++-- .../parquet/ParquetCorruptionException.java | 14 ++++------- .../trino/parquet/ParquetValidationUtils.java | 11 --------- .../parquet/predicate/PredicateUtils.java | 7 +++--- .../TupleDomainParquetPredicate.java | 4 ++-- .../trino/parquet/reader/MetadataReader.java | 15 ++++++------ .../io/trino/parquet/reader/PageReader.java | 21 ++++++++++++---- .../reader/ParquetColumnChunkIterator.java | 6 ++++- .../trino/parquet/reader/ParquetReader.java | 6 ++--- .../TestTupleDomainParquetPredicate.java | 24 +++++++++---------- .../reader/AbstractColumnReaderBenchmark.java | 4 +++- .../AbstractColumnReaderRowRangesTest.java | 2 ++ .../reader/AbstractColumnReaderTest.java | 2 ++ .../parquet/reader/TestInt96Timestamp.java | 6 ++--- .../trino/parquet/reader/TestPageReader.java | 2 ++ .../reader/flat/TestFlatColumnReader.java | 5 ++-- .../parquet/writer/TestParquetWriter.java | 8 ++++++- .../plugin/deltalake/DeltaLakeWriter.java | 3 ++- 18 files changed, 81 insertions(+), 63 deletions(-) diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetCompressionUtils.java b/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetCompressionUtils.java index d482f627caac..0c8cf74883e1 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetCompressionUtils.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetCompressionUtils.java @@ -40,7 +40,7 @@ public final class ParquetCompressionUtils private ParquetCompressionUtils() {} - public static Slice decompress(CompressionCodec codec, Slice input, int uncompressedSize) + public static Slice decompress(ParquetDataSourceId dataSourceId, CompressionCodec codec, Slice input, int uncompressedSize) throws IOException { requireNonNull(input, "input is null"); @@ -67,7 +67,7 @@ public static Slice decompress(CompressionCodec codec, Slice input, int uncompre // unsupported break; } - throw new ParquetCorruptionException("Codec not supported in Parquet: " + codec); + throw new ParquetCorruptionException(dataSourceId, "Codec not supported in Parquet: %s", codec); } private static Slice decompressSnappy(Slice input, int uncompressedSize) diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetCorruptionException.java b/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetCorruptionException.java index 451c9d5c7c57..7cbddf8a07c4 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetCorruptionException.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetCorruptionException.java @@ -22,21 +22,15 @@ public class ParquetCorruptionException extends IOException { - public ParquetCorruptionException(String message) + public ParquetCorruptionException(ParquetDataSourceId dataSourceId, String message) { - super(message); + this(dataSourceId, "%s", message); } @FormatMethod - public ParquetCorruptionException(String messageFormat, Object... args) + public ParquetCorruptionException(Throwable cause, ParquetDataSourceId dataSourceId, String messageFormat, Object... args) { - super(format(messageFormat, args)); - } - - @FormatMethod - public ParquetCorruptionException(Throwable cause, String messageFormat, Object... args) - { - super(format(messageFormat, args), cause); + super(formatMessage(dataSourceId, messageFormat, args), cause); } @FormatMethod diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetValidationUtils.java b/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetValidationUtils.java index 9be1c4477947..5e31a08f704e 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetValidationUtils.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetValidationUtils.java @@ -15,21 +15,10 @@ import com.google.errorprone.annotations.FormatMethod; -import static java.lang.String.format; - public final class ParquetValidationUtils { private ParquetValidationUtils() {} - @FormatMethod - public static void validateParquet(boolean condition, String formatString, Object... args) - throws ParquetCorruptionException - { - if (!condition) { - throw new ParquetCorruptionException(format(formatString, args)); - } - } - @FormatMethod public static void validateParquet(boolean condition, ParquetDataSourceId dataSourceId, String formatString, Object... args) throws ParquetCorruptionException diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/predicate/PredicateUtils.java b/lib/trino-parquet/src/main/java/io/trino/parquet/predicate/PredicateUtils.java index 9deb275b32f1..3b507aa24685 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/predicate/PredicateUtils.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/predicate/PredicateUtils.java @@ -21,6 +21,7 @@ import io.trino.parquet.BloomFilterStore; import io.trino.parquet.DictionaryPage; import io.trino.parquet.ParquetDataSource; +import io.trino.parquet.ParquetDataSourceId; import io.trino.parquet.ParquetEncoding; import io.trino.spi.predicate.TupleDomain; import io.trino.spi.type.DecimalType; @@ -254,7 +255,7 @@ private static Optional readDictionaryPage( } // Get the dictionary page header and the dictionary in single read Slice buffer = dataSource.readFully(columnMetaData.getStartingPos(), dictionaryPageSize); - return readPageHeaderWithData(buffer.getInput()).map(data -> decodeDictionaryPage(data, columnMetaData)); + return readPageHeaderWithData(buffer.getInput()).map(data -> decodeDictionaryPage(dataSource.getId(), data, columnMetaData)); } private static Optional getDictionaryPageSize(ColumnIndexStore columnIndexStore, ColumnChunkMetaData columnMetaData) @@ -293,7 +294,7 @@ private static Optional readPageHeaderWithData(SliceInput in inputStream.readSlice(pageHeader.getCompressed_page_size()))); } - private static DictionaryPage decodeDictionaryPage(PageHeaderWithData pageHeaderWithData, ColumnChunkMetaData chunkMetaData) + private static DictionaryPage decodeDictionaryPage(ParquetDataSourceId dataSourceId, PageHeaderWithData pageHeaderWithData, ColumnChunkMetaData chunkMetaData) { PageHeader pageHeader = pageHeaderWithData.pageHeader(); DictionaryPageHeader dicHeader = pageHeader.getDictionary_page_header(); @@ -302,7 +303,7 @@ private static DictionaryPage decodeDictionaryPage(PageHeaderWithData pageHeader Slice compressedData = pageHeaderWithData.compressedData(); try { - return new DictionaryPage(decompress(chunkMetaData.getCodec().getParquetCompressionCodec(), compressedData, pageHeader.getUncompressed_page_size()), dictionarySize, encoding); + return new DictionaryPage(decompress(dataSourceId, chunkMetaData.getCodec().getParquetCompressionCodec(), compressedData, pageHeader.getUncompressed_page_size()), dictionarySize, encoding); } catch (IOException e) { throw new ParquetDecodingException("Could not decode the dictionary for " + chunkMetaData.getPath(), e); diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/predicate/TupleDomainParquetPredicate.java b/lib/trino-parquet/src/main/java/io/trino/parquet/predicate/TupleDomainParquetPredicate.java index 107bdf241b64..7f23d4a0fa7b 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/predicate/TupleDomainParquetPredicate.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/predicate/TupleDomainParquetPredicate.java @@ -595,12 +595,12 @@ private static Domain getDomain(Type type, DictionaryDescriptor dictionaryDescri private static ParquetCorruptionException corruptionException(String column, ParquetDataSourceId id, Statistics statistics, Exception cause) { - return new ParquetCorruptionException(cause, "Corrupted statistics for column \"%s\" in Parquet file \"%s\": [%s]", column, id, statistics); + return new ParquetCorruptionException(cause, id, "Corrupted statistics for column \"%s\": [%s]", column, statistics); } private static ParquetCorruptionException corruptionException(String column, ParquetDataSourceId id, ColumnIndex columnIndex, Exception cause) { - return new ParquetCorruptionException(cause, "Corrupted statistics for column \"%s\" in Parquet file \"%s\". Corrupted column index: [%s]", column, id, columnIndex); + return new ParquetCorruptionException(cause, id, "Corrupted statistics for column \"%s\". Corrupted column index: [%s]", column, columnIndex); } private static boolean isCorruptedColumnIndex( diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/MetadataReader.java b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/MetadataReader.java index 9dd28b8614a5..3768645bf199 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/MetadataReader.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/MetadataReader.java @@ -90,7 +90,7 @@ public static ParquetMetadata readFooter(ParquetDataSource dataSource, Optional< // 4 bytes: MetadataLength // MAGIC - validateParquet(dataSource.getEstimatedSize() >= MAGIC.length() + POST_SCRIPT_SIZE, "%s is not a valid Parquet File", dataSource.getId()); + validateParquet(dataSource.getEstimatedSize() >= MAGIC.length() + POST_SCRIPT_SIZE, dataSource.getId(), "%s is not a valid Parquet File", dataSource.getId()); // Read the tail of the file long estimatedFileSize = dataSource.getEstimatedSize(); @@ -98,14 +98,14 @@ public static ParquetMetadata readFooter(ParquetDataSource dataSource, Optional< Slice buffer = dataSource.readTail(toIntExact(expectedReadSize)); Slice magic = buffer.slice(buffer.length() - MAGIC.length(), MAGIC.length()); - validateParquet(MAGIC.equals(magic), "Not valid Parquet file: %s expected magic number: %s got: %s", dataSource.getId(), MAGIC.toStringUtf8(), magic.toStringUtf8()); + validateParquet(MAGIC.equals(magic), dataSource.getId(), "Expected magic number: %s got: %s", MAGIC.toStringUtf8(), magic.toStringUtf8()); int metadataLength = buffer.getInt(buffer.length() - POST_SCRIPT_SIZE); long metadataIndex = estimatedFileSize - POST_SCRIPT_SIZE - metadataLength; validateParquet( metadataIndex >= MAGIC.length() && metadataIndex < estimatedFileSize - POST_SCRIPT_SIZE, - "Corrupted Parquet file: %s metadata index: %s out of range", dataSource.getId(), + "Metadata index: %s out of range", metadataIndex); int completeFooterSize = metadataLength + POST_SCRIPT_SIZE; @@ -116,16 +116,16 @@ public static ParquetMetadata readFooter(ParquetDataSource dataSource, Optional< InputStream metadataStream = buffer.slice(buffer.length() - completeFooterSize, metadataLength).getInput(); FileMetaData fileMetaData = readFileMetaData(metadataStream); - ParquetMetadata parquetMetadata = createParquetMetadata(fileMetaData, dataSource.getId().toString()); + ParquetMetadata parquetMetadata = createParquetMetadata(fileMetaData, dataSource.getId()); validateFileMetadata(dataSource.getId(), parquetMetadata.getFileMetaData(), parquetWriteValidation); return parquetMetadata; } - public static ParquetMetadata createParquetMetadata(FileMetaData fileMetaData, String filename) + public static ParquetMetadata createParquetMetadata(FileMetaData fileMetaData, ParquetDataSourceId dataSourceId) throws ParquetCorruptionException { List schema = fileMetaData.getSchema(); - validateParquet(!schema.isEmpty(), "Empty Parquet schema in file: %s", filename); + validateParquet(!schema.isEmpty(), dataSourceId, "Schema is empty"); MessageType messageType = readParquetSchema(schema); List blocks = new ArrayList<>(); @@ -136,12 +136,13 @@ public static ParquetMetadata createParquetMetadata(FileMetaData fileMetaData, S blockMetaData.setRowCount(rowGroup.getNum_rows()); blockMetaData.setTotalByteSize(rowGroup.getTotal_byte_size()); List columns = rowGroup.getColumns(); - validateParquet(!columns.isEmpty(), "No columns in row group: %s", rowGroup); + validateParquet(!columns.isEmpty(), dataSourceId, "No columns in row group: %s", rowGroup); String filePath = columns.get(0).getFile_path(); for (ColumnChunk columnChunk : columns) { validateParquet( (filePath == null && columnChunk.getFile_path() == null) || (filePath != null && filePath.equals(columnChunk.getFile_path())), + dataSourceId, "all column chunks of the same row group must be in the same file"); ColumnMetaData metaData = columnChunk.meta_data; String[] path = metaData.path_in_schema.stream() diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/PageReader.java b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/PageReader.java index 1c19115cffdd..7dddae62af4d 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/PageReader.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/PageReader.java @@ -21,6 +21,7 @@ import io.trino.parquet.DataPageV2; import io.trino.parquet.DictionaryPage; import io.trino.parquet.Page; +import io.trino.parquet.ParquetDataSourceId; import jakarta.annotation.Nullable; import org.apache.parquet.column.ColumnDescriptor; import org.apache.parquet.column.statistics.Statistics; @@ -36,9 +37,11 @@ import static com.google.common.base.Preconditions.checkState; import static io.trino.parquet.ParquetCompressionUtils.decompress; import static io.trino.parquet.ParquetReaderUtils.isOnlyDictionaryEncodingPages; +import static java.util.Objects.requireNonNull; public final class PageReader { + private final ParquetDataSourceId dataSourceId; private final CompressionCodec codec; private final boolean hasOnlyDictionaryEncodedPages; private final boolean hasNoNulls; @@ -48,6 +51,7 @@ public final class PageReader private int dataPageReadCount; public static PageReader createPageReader( + ParquetDataSourceId dataSourceId, ChunkedInputStream columnChunk, ColumnChunkMetaData metadata, ColumnDescriptor columnDescriptor, @@ -61,21 +65,30 @@ public static PageReader createPageReader( boolean hasNoNulls = columnStatistics != null && columnStatistics.getNumNulls() == 0; boolean hasOnlyDictionaryEncodedPages = isOnlyDictionaryEncodingPages(metadata); ParquetColumnChunkIterator compressedPages = new ParquetColumnChunkIterator( + dataSourceId, fileCreatedBy, columnDescriptor, metadata, columnChunk, offsetIndex); - return new PageReader(metadata.getCodec().getParquetCompressionCodec(), compressedPages, hasOnlyDictionaryEncodedPages, hasNoNulls); + + return new PageReader( + dataSourceId, + metadata.getCodec().getParquetCompressionCodec(), + compressedPages, + hasOnlyDictionaryEncodedPages, + hasNoNulls); } @VisibleForTesting public PageReader( + ParquetDataSourceId dataSourceId, CompressionCodec codec, Iterator compressedPages, boolean hasOnlyDictionaryEncodedPages, boolean hasNoNulls) { + this.dataSourceId = requireNonNull(dataSourceId, "dataSourceId is null"); this.codec = codec; this.compressedPages = Iterators.peekingIterator(compressedPages); this.hasOnlyDictionaryEncodedPages = hasOnlyDictionaryEncodedPages; @@ -106,7 +119,7 @@ public DataPage readPage() return dataPageV1; } return new DataPageV1( - decompress(codec, dataPageV1.getSlice(), dataPageV1.getUncompressedSize()), + decompress(dataSourceId, codec, dataPageV1.getSlice(), dataPageV1.getUncompressedSize()), dataPageV1.getValueCount(), dataPageV1.getUncompressedSize(), dataPageV1.getFirstRowIndex(), @@ -128,7 +141,7 @@ public DataPage readPage() dataPageV2.getRepetitionLevels(), dataPageV2.getDefinitionLevels(), dataPageV2.getDataEncoding(), - decompress(codec, dataPageV2.getSlice(), uncompressedSize), + decompress(dataSourceId, codec, dataPageV2.getSlice(), uncompressedSize), dataPageV2.getUncompressedSize(), dataPageV2.getFirstRowIndex(), dataPageV2.getStatistics(), @@ -150,7 +163,7 @@ public DictionaryPage readDictionaryPage() try { DictionaryPage compressedDictionaryPage = (DictionaryPage) compressedPages.next(); return new DictionaryPage( - decompress(codec, compressedDictionaryPage.getSlice(), compressedDictionaryPage.getUncompressedSize()), + decompress(dataSourceId, codec, compressedDictionaryPage.getSlice(), compressedDictionaryPage.getUncompressedSize()), compressedDictionaryPage.getDictionarySize(), compressedDictionaryPage.getEncoding()); } diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/ParquetColumnChunkIterator.java b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/ParquetColumnChunkIterator.java index 1a14d579bc59..577e5cb602fa 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/ParquetColumnChunkIterator.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/ParquetColumnChunkIterator.java @@ -18,6 +18,7 @@ import io.trino.parquet.DictionaryPage; import io.trino.parquet.Page; import io.trino.parquet.ParquetCorruptionException; +import io.trino.parquet.ParquetDataSourceId; import jakarta.annotation.Nullable; import org.apache.parquet.column.ColumnDescriptor; import org.apache.parquet.column.Encoding; @@ -41,6 +42,7 @@ public final class ParquetColumnChunkIterator implements Iterator { + private final ParquetDataSourceId dataSourceId; private final Optional fileCreatedBy; private final ColumnDescriptor descriptor; private final ColumnChunkMetaData metadata; @@ -51,12 +53,14 @@ public final class ParquetColumnChunkIterator private int dataPageCount; public ParquetColumnChunkIterator( + ParquetDataSourceId dataSourceId, Optional fileCreatedBy, ColumnDescriptor descriptor, ColumnChunkMetaData metadata, ChunkedInputStream input, @Nullable OffsetIndex offsetIndex) { + this.dataSourceId = requireNonNull(dataSourceId, "dataSourceId is null"); this.fileCreatedBy = requireNonNull(fileCreatedBy, "fileCreatedBy is null"); this.descriptor = requireNonNull(descriptor, "descriptor is null"); this.metadata = requireNonNull(metadata, "metadata is null"); @@ -83,7 +87,7 @@ public Page next() switch (pageHeader.type) { case DICTIONARY_PAGE: if (dataPageCount != 0) { - throw new ParquetCorruptionException("Column (%s) has a dictionary page after the first position in column chunk", descriptor); + throw new ParquetCorruptionException(dataSourceId, "Column (%s) has a dictionary page after the first position in column chunk", descriptor); } result = readDictionaryPage(pageHeader, pageHeader.getUncompressed_page_size(), pageHeader.getCompressed_page_size()); break; diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/ParquetReader.java b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/ParquetReader.java index f02084c451da..e650f7f31f50 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/ParquetReader.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/ParquetReader.java @@ -394,7 +394,7 @@ private ColumnChunk readStruct(GroupField field) } if (columnChunk == null) { - throw new ParquetCorruptionException("Struct field does not have any children: " + field); + throw new ParquetCorruptionException(dataSource.getId(), "Struct field does not have any children: %s", field); } StructColumnReader.RowBlockPositions structIsNull = StructColumnReader.calculateStructOffsets(field, columnChunk.getDefinitionLevels(), columnChunk.getRepetitionLevels()); @@ -467,7 +467,7 @@ private ColumnChunk readPrimitive(PrimitiveField field) int fieldId = field.getId(); ColumnReader columnReader = columnReaders.get(fieldId); if (!columnReader.hasPageReader()) { - validateParquet(currentBlockMetadata.getRowCount() > 0, "Row group has 0 rows"); + validateParquet(currentBlockMetadata.getRowCount() > 0, dataSource.getId(), "Row group has 0 rows"); ColumnChunkMetaData metadata = getColumnChunkMetaData(currentBlockMetadata, columnDescriptor); FilteredRowRanges rowRanges = blockRowRanges[currentRowGroup]; OffsetIndex offsetIndex = null; @@ -476,7 +476,7 @@ private ColumnChunk readPrimitive(PrimitiveField field) } ChunkedInputStream columnChunkInputStream = chunkReaders.get(new ChunkKey(fieldId, currentRowGroup)); columnReader.setPageReader( - createPageReader(columnChunkInputStream, metadata, columnDescriptor, offsetIndex, fileCreatedBy), + createPageReader(dataSource.getId(), columnChunkInputStream, metadata, columnDescriptor, offsetIndex, fileCreatedBy), Optional.ofNullable(rowRanges)); } ColumnChunk columnChunk = columnReader.readPrimitive(); diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/TestTupleDomainParquetPredicate.java b/lib/trino-parquet/src/test/java/io/trino/parquet/TestTupleDomainParquetPredicate.java index bbb409273a64..43bd3959a87a 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/TestTupleDomainParquetPredicate.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/TestTupleDomainParquetPredicate.java @@ -159,7 +159,7 @@ public void testBigint() // fail on corrupted statistics assertThatExceptionOfType(ParquetCorruptionException.class) .isThrownBy(() -> getDomain(columnDescriptor, BIGINT, 10, longColumnStats(100L, 10L), ID, UTC)) - .withMessage("Corrupted statistics for column \"[] required int64 BigintColumn\" in Parquet file \"testFile\": [min: 100, max: 10, num_nulls: 0]"); + .withMessage("Malformed Parquet file. Corrupted statistics for column \"[] required int64 BigintColumn\": [min: 100, max: 10, num_nulls: 0] [testFile]"); } @Test @@ -179,7 +179,7 @@ public void testInteger() // fail on corrupted statistics assertThatExceptionOfType(ParquetCorruptionException.class) .isThrownBy(() -> getDomain(columnDescriptor, INTEGER, 10, longColumnStats(2147483648L, 10), ID, UTC)) - .withMessage("Corrupted statistics for column \"[] required int32 IntegerColumn\" in Parquet file \"testFile\": [min: 2147483648, max: 10, num_nulls: 0]"); + .withMessage("Malformed Parquet file. Corrupted statistics for column \"[] required int32 IntegerColumn\": [min: 2147483648, max: 10, num_nulls: 0] [testFile]"); } @Test @@ -199,7 +199,7 @@ public void testSmallint() // fail on corrupted statistics assertThatExceptionOfType(ParquetCorruptionException.class) .isThrownBy(() -> getDomain(columnDescriptor, SMALLINT, 10, longColumnStats(2147483648L, 10), ID, UTC)) - .withMessage("Corrupted statistics for column \"[] required int32 SmallintColumn\" in Parquet file \"testFile\": [min: 2147483648, max: 10, num_nulls: 0]"); + .withMessage("Malformed Parquet file. Corrupted statistics for column \"[] required int32 SmallintColumn\": [min: 2147483648, max: 10, num_nulls: 0] [testFile]"); } @Test @@ -219,7 +219,7 @@ public void testTinyint() // fail on corrupted statistics assertThatExceptionOfType(ParquetCorruptionException.class) .isThrownBy(() -> getDomain(columnDescriptor, TINYINT, 10, longColumnStats(2147483648L, 10), ID, UTC)) - .withMessage("Corrupted statistics for column \"[] required int32 TinyintColumn\" in Parquet file \"testFile\": [min: 2147483648, max: 10, num_nulls: 0]"); + .withMessage("Malformed Parquet file. Corrupted statistics for column \"[] required int32 TinyintColumn\": [min: 2147483648, max: 10, num_nulls: 0] [testFile]"); } @Test @@ -238,7 +238,7 @@ public void testShortDecimal() // fail on corrupted statistics assertThatExceptionOfType(ParquetCorruptionException.class) .isThrownBy(() -> getDomain(columnDescriptor, type, 10, longColumnStats(100L, 10L), ID, UTC)) - .withMessage("Corrupted statistics for column \"[] required int32 ShortDecimalColumn\" in Parquet file \"testFile\": [min: 100, max: 10, num_nulls: 0]"); + .withMessage("Malformed Parquet file. Corrupted statistics for column \"[] required int32 ShortDecimalColumn\": [min: 100, max: 10, num_nulls: 0] [testFile]"); } @Test @@ -257,7 +257,7 @@ public void testShortDecimalWithNoScale() // fail on corrupted statistics assertThatExceptionOfType(ParquetCorruptionException.class) .isThrownBy(() -> getDomain(columnDescriptor, type, 10, longColumnStats(100L, 10L), ID, UTC)) - .withMessage("Corrupted statistics for column \"[] required int32 ShortDecimalColumnWithNoScale\" in Parquet file \"testFile\": [min: 100, max: 10, num_nulls: 0]"); + .withMessage("Malformed Parquet file. Corrupted statistics for column \"[] required int32 ShortDecimalColumnWithNoScale\": [min: 100, max: 10, num_nulls: 0] [testFile]"); } @Test @@ -281,7 +281,7 @@ public void testLongDecimal() // fail on corrupted statistics assertThatExceptionOfType(ParquetCorruptionException.class) .isThrownBy(() -> getDomain(columnDescriptor, type, 10, binaryColumnStats(100L, 10L), ID, UTC)) - .withMessage("Corrupted statistics for column \"[] required fixed_len_byte_array(0) LongDecimalColumn\" in Parquet file \"testFile\": [min: 0x64, max: 0x0A, num_nulls: 0]"); + .withMessage("Malformed Parquet file. Corrupted statistics for column \"[] required fixed_len_byte_array(0) LongDecimalColumn\": [min: 0x64, max: 0x0A, num_nulls: 0] [testFile]"); } @Test @@ -300,7 +300,7 @@ public void testLongDecimalWithNoScale() // fail on corrupted statistics assertThatExceptionOfType(ParquetCorruptionException.class) .isThrownBy(() -> getDomain(columnDescriptor, type, 10, binaryColumnStats(100L, 10L), ID, UTC)) - .withMessage("Corrupted statistics for column \"[] required fixed_len_byte_array(0) LongDecimalColumnWithNoScale\" in Parquet file \"testFile\": [min: 0x64, max: 0x0A, num_nulls: 0]"); + .withMessage("Malformed Parquet file. Corrupted statistics for column \"[] required fixed_len_byte_array(0) LongDecimalColumnWithNoScale\": [min: 0x64, max: 0x0A, num_nulls: 0] [testFile]"); } @Test @@ -329,7 +329,7 @@ public void testDouble() // fail on corrupted statistics assertThatExceptionOfType(ParquetCorruptionException.class) .isThrownBy(() -> getDomain(columnDescriptor, DOUBLE, 10, doubleColumnStats(42.24, 3.3), ID, UTC)) - .withMessage("Corrupted statistics for column \"[] required double DoubleColumn\" in Parquet file \"testFile\": [min: 42.24, max: 3.3, num_nulls: 0]"); + .withMessage("Malformed Parquet file. Corrupted statistics for column \"[] required double DoubleColumn\": [min: 42.24, max: 3.3, num_nulls: 0] [testFile]"); } @Test @@ -348,7 +348,7 @@ public void testString() // fail on corrupted statistics assertThatExceptionOfType(ParquetCorruptionException.class) .isThrownBy(() -> getDomain(columnDescriptor, createUnboundedVarcharType(), 10, stringColumnStats("taco", "apple"), ID, UTC)) - .withMessage("Corrupted statistics for column \"[] required binary StringColumn\" in Parquet file \"testFile\": [min: 0x7461636F, max: 0x6170706C65, num_nulls: 0]"); + .withMessage("Malformed Parquet file. Corrupted statistics for column \"[] required binary StringColumn\": [min: 0x7461636F, max: 0x6170706C65, num_nulls: 0] [testFile]"); } private static BinaryStatistics stringColumnStats(String minimum, String maximum) @@ -391,7 +391,7 @@ public void testFloat() // fail on corrupted statistics assertThatExceptionOfType(ParquetCorruptionException.class) .isThrownBy(() -> getDomain(columnDescriptor, REAL, 10, floatColumnStats(maximum, minimum), ID, UTC)) - .withMessage("Corrupted statistics for column \"[] required float FloatColumn\" in Parquet file \"testFile\": [min: 40.3, max: 4.3, num_nulls: 0]"); + .withMessage("Malformed Parquet file. Corrupted statistics for column \"[] required float FloatColumn\": [min: 40.3, max: 4.3, num_nulls: 0] [testFile]"); } @Test @@ -406,7 +406,7 @@ public void testDate() // fail on corrupted statistics assertThatExceptionOfType(ParquetCorruptionException.class) .isThrownBy(() -> getDomain(columnDescriptor, DATE, 10, intColumnStats(200, 100), ID, UTC)) - .withMessage("Corrupted statistics for column \"[] required int32 DateColumn\" in Parquet file \"testFile\": [min: 200, max: 100, num_nulls: 0]"); + .withMessage("Malformed Parquet file. Corrupted statistics for column \"[] required int32 DateColumn\": [min: 200, max: 100, num_nulls: 0] [testFile]"); } @DataProvider diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/AbstractColumnReaderBenchmark.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/AbstractColumnReaderBenchmark.java index 3fbfa3f18161..7335d3ef19af 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/AbstractColumnReaderBenchmark.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/AbstractColumnReaderBenchmark.java @@ -17,6 +17,7 @@ import io.airlift.slice.Slices; import io.trino.parquet.DataPage; import io.trino.parquet.DataPageV1; +import io.trino.parquet.ParquetDataSourceId; import io.trino.parquet.PrimitiveField; import org.apache.parquet.column.values.ValuesWriter; import org.openjdk.jmh.annotations.Benchmark; @@ -103,7 +104,8 @@ public int read() throws IOException { ColumnReader columnReader = columnReaderFactory.create(field, newSimpleAggregatedMemoryContext()); - columnReader.setPageReader(new PageReader(UNCOMPRESSED, dataPages.iterator(), false, false), Optional.empty()); + PageReader pageReader = new PageReader(new ParquetDataSourceId("test"), UNCOMPRESSED, dataPages.iterator(), false, false); + columnReader.setPageReader(pageReader, Optional.empty()); int rowsRead = 0; while (rowsRead < dataPositions) { int remaining = dataPositions - rowsRead; diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/AbstractColumnReaderRowRangesTest.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/AbstractColumnReaderRowRangesTest.java index 36f502a9f1b4..22c57a04dc71 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/AbstractColumnReaderRowRangesTest.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/AbstractColumnReaderRowRangesTest.java @@ -18,6 +18,7 @@ import io.trino.parquet.DataPage; import io.trino.parquet.DataPageV2; import io.trino.parquet.Page; +import io.trino.parquet.ParquetDataSourceId; import io.trino.parquet.PrimitiveField; import io.trino.parquet.reader.decoders.ValueDecoder; import io.trino.parquet.reader.decoders.ValueDecoders; @@ -558,6 +559,7 @@ else if (dictionaryEncoding == DictionaryEncoding.MIXED) { inputPages = ImmutableList.builder().add(toTrinoDictionaryPage(encoder.toDictPageAndClose())).addAll(inputPages).build(); } return new PageReader( + new ParquetDataSourceId("test"), UNCOMPRESSED, inputPages.iterator(), dictionaryEncoding == DictionaryEncoding.ALL || (dictionaryEncoding == DictionaryEncoding.MIXED && testingPages.size() == 1), diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/AbstractColumnReaderTest.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/AbstractColumnReaderTest.java index 0359a113640e..5712562b7a72 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/AbstractColumnReaderTest.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/AbstractColumnReaderTest.java @@ -23,6 +23,7 @@ import io.trino.parquet.DataPageV2; import io.trino.parquet.DictionaryPage; import io.trino.parquet.Page; +import io.trino.parquet.ParquetDataSourceId; import io.trino.parquet.ParquetEncoding; import io.trino.parquet.PrimitiveField; import io.trino.parquet.reader.TestingColumnReader.ColumnReaderFormat; @@ -686,6 +687,7 @@ protected static PageReader getPageReaderMock(List dataPages, @Nullabl pagesBuilder.add(dictionaryPage); } return new PageReader( + new ParquetDataSourceId("test"), UNCOMPRESSED, pagesBuilder.addAll(dataPages).build().iterator(), dataPages.stream() diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestInt96Timestamp.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestInt96Timestamp.java index 5939dd0253d1..2e2b9fe08d74 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestInt96Timestamp.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestInt96Timestamp.java @@ -17,6 +17,7 @@ import io.airlift.slice.Slices; import io.trino.parquet.DataPage; import io.trino.parquet.DataPageV2; +import io.trino.parquet.ParquetDataSourceId; import io.trino.parquet.PrimitiveField; import io.trino.plugin.base.type.DecodedTimestamp; import io.trino.spi.block.Block; @@ -108,9 +109,8 @@ public void testVariousTimestamps(TimestampType type, BiFunction chunkReader = dataSource.planRead(ImmutableListMultimap.of(0, range), newSimpleAggregatedMemoryContext()); PageReader pageReader = PageReader.createPageReader( + new ParquetDataSourceId("test"), chunkReader.get(0), chunkMetaData, new ColumnDescriptor(new String[] {"columna"}, new PrimitiveType(REQUIRED, INT32, "columna"), 0, 0), @@ -265,7 +267,11 @@ public void testDictionaryPageOffset() assertThat(pageHeader.getType()).isEqualTo(PageType.DICTIONARY_PAGE); assertThat(pageHeader.getDictionary_page_header().getNum_values()).isEqualTo(100); Slice compressedData = inputStream.readSlice(pageHeader.getCompressed_page_size()); - Slice uncompressedData = decompress(chunkMetaData.getCodec().getParquetCompressionCodec(), compressedData, pageHeader.getUncompressed_page_size()); + Slice uncompressedData = decompress( + new ParquetDataSourceId("test"), + chunkMetaData.getCodec().getParquetCompressionCodec(), + compressedData, + pageHeader.getUncompressed_page_size()); int[] ids = new int[100]; uncompressedData.getInts(0, ids, 0, 100); for (int i = 0; i < 100; i++) { diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeWriter.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeWriter.java index 335d28ed217c..b73f58111914 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeWriter.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeWriter.java @@ -18,6 +18,7 @@ import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; import io.trino.filesystem.Location; +import io.trino.parquet.ParquetDataSourceId; import io.trino.parquet.reader.MetadataReader; import io.trino.plugin.deltalake.DataFileInfo.DataFileType; import io.trino.plugin.deltalake.transactionlog.statistics.DeltaLakeJsonFileStatistics; @@ -196,7 +197,7 @@ public DataFileInfo getDataFileInfo() private static DeltaLakeJsonFileStatistics readStatistics(FileMetaData fileMetaData, Location path, Map typeForColumn, long rowCount) throws IOException { - ParquetMetadata parquetMetadata = MetadataReader.createParquetMetadata(fileMetaData, path.fileName()); + ParquetMetadata parquetMetadata = MetadataReader.createParquetMetadata(fileMetaData, new ParquetDataSourceId(path.toString())); ImmutableMultimap.Builder metadataForColumn = ImmutableMultimap.builder(); for (BlockMetaData blockMetaData : parquetMetadata.getBlocks()) { From bcb8af7f77927f5593c3d7ee828c9ec24b1e26d5 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sat, 4 Nov 2023 18:32:13 -0700 Subject: [PATCH 109/587] Use enhanced switch in ParquetCompressionUtils --- .../parquet/ParquetCompressionUtils.java | 28 ++++++------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetCompressionUtils.java b/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetCompressionUtils.java index 0c8cf74883e1..1c9069817ddf 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetCompressionUtils.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetCompressionUtils.java @@ -49,25 +49,15 @@ public static Slice decompress(ParquetDataSourceId dataSourceId, CompressionCode return EMPTY_SLICE; } - switch (codec) { - case GZIP: - return decompressGzip(input, uncompressedSize); - case SNAPPY: - return decompressSnappy(input, uncompressedSize); - case UNCOMPRESSED: - return input; - case LZO: - return decompressLZO(input, uncompressedSize); - case LZ4: - return decompressLz4(input, uncompressedSize); - case ZSTD: - return decompressZstd(input, uncompressedSize); - case BROTLI: - case LZ4_RAW: - // unsupported - break; - } - throw new ParquetCorruptionException(dataSourceId, "Codec not supported in Parquet: %s", codec); + return switch (codec) { + case UNCOMPRESSED -> input; + case GZIP -> decompressGzip(input, uncompressedSize); + case SNAPPY -> decompressSnappy(input, uncompressedSize); + case LZO -> decompressLZO(input, uncompressedSize); + case LZ4 -> decompressLz4(input, uncompressedSize); + case ZSTD -> decompressZstd(input, uncompressedSize); + case BROTLI, LZ4_RAW -> throw new ParquetCorruptionException(dataSourceId, "Codec not supported in Parquet: %s", codec); + }; } private static Slice decompressSnappy(Slice input, int uncompressedSize) From c0d03b60f9f2d6d6fdee32188eca2adecb361bae Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Tue, 31 Oct 2023 20:26:27 +0100 Subject: [PATCH 110/587] Update airbase to 148 Fixes incorrect junit/surefire integration that isn't working with the latest surefire plugin. junit-jupiter-engine should be a module dependency, rather then surefire plugin dependency. --- client/trino-jdbc/pom.xml | 11 ++++++----- core/trino-spi/pom.xml | 17 ++++++----------- lib/trino-parquet/pom.xml | 11 ++++++----- plugin/trino-base-jdbc/pom.xml | 11 ++++++----- plugin/trino-bigquery/pom.xml | 11 ++++++----- plugin/trino-cassandra/pom.xml | 11 ++++++----- plugin/trino-clickhouse/pom.xml | 11 ++++++----- plugin/trino-delta-lake/pom.xml | 11 ++++++----- plugin/trino-druid/pom.xml | 11 ++++++----- plugin/trino-hive-hadoop2/pom.xml | 11 ++++++----- plugin/trino-hudi/pom.xml | 11 ++++++----- plugin/trino-ignite/pom.xml | 11 ++++++----- plugin/trino-kafka/pom.xml | 11 ++++++----- plugin/trino-kinesis/pom.xml | 11 ++++++----- plugin/trino-kudu/pom.xml | 11 ++++++----- plugin/trino-mariadb/pom.xml | 11 ++++++----- plugin/trino-memory/pom.xml | 11 ++++++----- plugin/trino-mysql/pom.xml | 11 ++++++----- plugin/trino-oracle/pom.xml | 11 ++++++----- plugin/trino-phoenix5/pom.xml | 11 ++++++----- plugin/trino-pinot/pom.xml | 11 ++++++----- plugin/trino-postgresql/pom.xml | 11 ++++++----- plugin/trino-prometheus/pom.xml | 11 ++++++----- plugin/trino-raptor-legacy/pom.xml | 11 ++++++----- plugin/trino-redis/pom.xml | 11 ++++++----- plugin/trino-redshift/pom.xml | 11 ++++++----- plugin/trino-session-property-managers/pom.xml | 11 ++++++----- plugin/trino-singlestore/pom.xml | 11 ++++++----- plugin/trino-sqlserver/pom.xml | 11 ++++++----- plugin/trino-thrift-testing-server/pom.xml | 11 ++++++----- pom.xml | 2 +- testing/trino-faulttolerant-tests/pom.xml | 11 ++++++----- testing/trino-testing/pom.xml | 11 ++++++----- testing/trino-tests/pom.xml | 5 ----- 34 files changed, 193 insertions(+), 172 deletions(-) diff --git a/client/trino-jdbc/pom.xml b/client/trino-jdbc/pom.xml index dce82d2b188d..4cc1a21148c2 100644 --- a/client/trino-jdbc/pom.xml +++ b/client/trino-jdbc/pom.xml @@ -270,6 +270,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.postgresql postgresql @@ -362,11 +368,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/core/trino-spi/pom.xml b/core/trino-spi/pom.xml index 4517f5365f89..84b7f29d85be 100644 --- a/core/trino-spi/pom.xml +++ b/core/trino-spi/pom.xml @@ -124,6 +124,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.openjdk.jmh jmh-core @@ -157,17 +163,6 @@ - - org.apache.maven.plugins - maven-surefire-plugin - - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - - - org.revapi revapi-maven-plugin diff --git a/lib/trino-parquet/pom.xml b/lib/trino-parquet/pom.xml index 9ce6dbcd45a1..5854262dbb03 100644 --- a/lib/trino-parquet/pom.xml +++ b/lib/trino-parquet/pom.xml @@ -187,6 +187,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.openjdk.jmh jmh-core @@ -229,11 +235,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/plugin/trino-base-jdbc/pom.xml b/plugin/trino-base-jdbc/pom.xml index 832c92425220..e79f07328899 100644 --- a/plugin/trino-base-jdbc/pom.xml +++ b/plugin/trino-base-jdbc/pom.xml @@ -270,6 +270,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.testng testng @@ -294,11 +300,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/plugin/trino-bigquery/pom.xml b/plugin/trino-bigquery/pom.xml index e95632cb6a7c..3e48836ee351 100644 --- a/plugin/trino-bigquery/pom.xml +++ b/plugin/trino-bigquery/pom.xml @@ -467,6 +467,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.mockito mockito-core @@ -498,11 +504,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/plugin/trino-cassandra/pom.xml b/plugin/trino-cassandra/pom.xml index 21a33521d696..2c158f601b63 100644 --- a/plugin/trino-cassandra/pom.xml +++ b/plugin/trino-cassandra/pom.xml @@ -234,6 +234,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.testcontainers testcontainers @@ -264,11 +270,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/plugin/trino-clickhouse/pom.xml b/plugin/trino-clickhouse/pom.xml index 08d744865162..0771635a2df5 100644 --- a/plugin/trino-clickhouse/pom.xml +++ b/plugin/trino-clickhouse/pom.xml @@ -176,6 +176,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.testcontainers clickhouse @@ -218,11 +224,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/plugin/trino-delta-lake/pom.xml b/plugin/trino-delta-lake/pom.xml index eea245912d0e..faefd9387945 100644 --- a/plugin/trino-delta-lake/pom.xml +++ b/plugin/trino-delta-lake/pom.xml @@ -419,6 +419,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.openjdk.jmh jmh-core @@ -468,11 +474,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/plugin/trino-druid/pom.xml b/plugin/trino-druid/pom.xml index 14f84bedd900..feefb2924c02 100644 --- a/plugin/trino-druid/pom.xml +++ b/plugin/trino-druid/pom.xml @@ -212,6 +212,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.testcontainers testcontainers @@ -248,11 +254,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/plugin/trino-hive-hadoop2/pom.xml b/plugin/trino-hive-hadoop2/pom.xml index 3e0b96d718de..4849d5c19df5 100644 --- a/plugin/trino-hive-hadoop2/pom.xml +++ b/plugin/trino-hive-hadoop2/pom.xml @@ -204,6 +204,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.testng testng @@ -228,11 +234,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/plugin/trino-hudi/pom.xml b/plugin/trino-hudi/pom.xml index 61a2d3be622e..54d4af64b672 100644 --- a/plugin/trino-hudi/pom.xml +++ b/plugin/trino-hudi/pom.xml @@ -332,6 +332,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.testng testng @@ -356,11 +362,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/plugin/trino-ignite/pom.xml b/plugin/trino-ignite/pom.xml index 1701669b88f4..198e3dcca0b8 100644 --- a/plugin/trino-ignite/pom.xml +++ b/plugin/trino-ignite/pom.xml @@ -214,6 +214,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.testcontainers jdbc @@ -250,11 +256,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/plugin/trino-kafka/pom.xml b/plugin/trino-kafka/pom.xml index efb178a4321c..44d7e7837ed5 100644 --- a/plugin/trino-kafka/pom.xml +++ b/plugin/trino-kafka/pom.xml @@ -330,6 +330,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.testng testng @@ -364,11 +370,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/plugin/trino-kinesis/pom.xml b/plugin/trino-kinesis/pom.xml index 462f1239dba5..f1b0e6f5d248 100644 --- a/plugin/trino-kinesis/pom.xml +++ b/plugin/trino-kinesis/pom.xml @@ -179,6 +179,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.testng testng @@ -215,11 +221,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/plugin/trino-kudu/pom.xml b/plugin/trino-kudu/pom.xml index 37ef5290a99b..7efbf00dd725 100644 --- a/plugin/trino-kudu/pom.xml +++ b/plugin/trino-kudu/pom.xml @@ -211,6 +211,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.testcontainers testcontainers @@ -265,11 +271,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/plugin/trino-mariadb/pom.xml b/plugin/trino-mariadb/pom.xml index b09fe905d208..bbe4cbdd3848 100644 --- a/plugin/trino-mariadb/pom.xml +++ b/plugin/trino-mariadb/pom.xml @@ -186,6 +186,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.testcontainers mariadb @@ -222,11 +228,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/plugin/trino-memory/pom.xml b/plugin/trino-memory/pom.xml index 7d79f447fc3c..26b071354ab0 100644 --- a/plugin/trino-memory/pom.xml +++ b/plugin/trino-memory/pom.xml @@ -173,6 +173,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.testng testng @@ -197,11 +203,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/plugin/trino-mysql/pom.xml b/plugin/trino-mysql/pom.xml index 452dd5a1d1b5..70afe09e93f3 100644 --- a/plugin/trino-mysql/pom.xml +++ b/plugin/trino-mysql/pom.xml @@ -215,6 +215,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.testcontainers mysql @@ -251,11 +257,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/plugin/trino-oracle/pom.xml b/plugin/trino-oracle/pom.xml index 596978b79f1d..3da926630307 100644 --- a/plugin/trino-oracle/pom.xml +++ b/plugin/trino-oracle/pom.xml @@ -225,6 +225,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.testcontainers oracle-xe @@ -261,11 +267,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/plugin/trino-phoenix5/pom.xml b/plugin/trino-phoenix5/pom.xml index 62485f040d06..f2ec3257f3b6 100644 --- a/plugin/trino-phoenix5/pom.xml +++ b/plugin/trino-phoenix5/pom.xml @@ -361,6 +361,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.testng testng @@ -385,11 +391,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/plugin/trino-pinot/pom.xml b/plugin/trino-pinot/pom.xml index 5a2ab791de05..0977e3e80c05 100755 --- a/plugin/trino-pinot/pom.xml +++ b/plugin/trino-pinot/pom.xml @@ -641,6 +641,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.testcontainers kafka @@ -687,11 +693,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/plugin/trino-postgresql/pom.xml b/plugin/trino-postgresql/pom.xml index 33a016c36495..92fcfae35a04 100644 --- a/plugin/trino-postgresql/pom.xml +++ b/plugin/trino-postgresql/pom.xml @@ -240,6 +240,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.testcontainers postgresql @@ -276,11 +282,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/plugin/trino-prometheus/pom.xml b/plugin/trino-prometheus/pom.xml index c25370754a5f..592393725c50 100644 --- a/plugin/trino-prometheus/pom.xml +++ b/plugin/trino-prometheus/pom.xml @@ -227,6 +227,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.testcontainers testcontainers @@ -257,11 +263,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/plugin/trino-raptor-legacy/pom.xml b/plugin/trino-raptor-legacy/pom.xml index 87fdcc4b1b1b..20fb51b5713a 100644 --- a/plugin/trino-raptor-legacy/pom.xml +++ b/plugin/trino-raptor-legacy/pom.xml @@ -284,6 +284,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.testcontainers mysql @@ -320,11 +326,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/plugin/trino-redis/pom.xml b/plugin/trino-redis/pom.xml index cfd917ee7dd2..5f695ad17f92 100644 --- a/plugin/trino-redis/pom.xml +++ b/plugin/trino-redis/pom.xml @@ -205,6 +205,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.testcontainers testcontainers @@ -235,11 +241,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/plugin/trino-redshift/pom.xml b/plugin/trino-redshift/pom.xml index 96267ed27f72..ca0f7fcccef8 100644 --- a/plugin/trino-redshift/pom.xml +++ b/plugin/trino-redshift/pom.xml @@ -194,6 +194,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.testng testng @@ -218,11 +224,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/plugin/trino-session-property-managers/pom.xml b/plugin/trino-session-property-managers/pom.xml index e5f64c657332..264e0515f5a0 100644 --- a/plugin/trino-session-property-managers/pom.xml +++ b/plugin/trino-session-property-managers/pom.xml @@ -182,6 +182,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.testcontainers mysql @@ -218,11 +224,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/plugin/trino-singlestore/pom.xml b/plugin/trino-singlestore/pom.xml index d1f9febfae72..06548c38991d 100644 --- a/plugin/trino-singlestore/pom.xml +++ b/plugin/trino-singlestore/pom.xml @@ -194,6 +194,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.testcontainers jdbc @@ -230,11 +236,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/plugin/trino-sqlserver/pom.xml b/plugin/trino-sqlserver/pom.xml index ed0e7f2f0ed9..a11dbd1b9e08 100644 --- a/plugin/trino-sqlserver/pom.xml +++ b/plugin/trino-sqlserver/pom.xml @@ -230,6 +230,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.testcontainers jdbc @@ -272,11 +278,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/plugin/trino-thrift-testing-server/pom.xml b/plugin/trino-thrift-testing-server/pom.xml index 59d37c2b90ab..6ddba66b16fb 100644 --- a/plugin/trino-thrift-testing-server/pom.xml +++ b/plugin/trino-thrift-testing-server/pom.xml @@ -119,6 +119,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.testng testng @@ -143,11 +149,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/pom.xml b/pom.xml index 3e1c33dfe8ee..fc8857e0b1de 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ io.airlift airbase - 147 + 148 io.trino diff --git a/testing/trino-faulttolerant-tests/pom.xml b/testing/trino-faulttolerant-tests/pom.xml index d14f4be4174c..b0e8eea800a8 100644 --- a/testing/trino-faulttolerant-tests/pom.xml +++ b/testing/trino-faulttolerant-tests/pom.xml @@ -375,6 +375,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.openjdk.jmh @@ -442,11 +448,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/testing/trino-testing/pom.xml b/testing/trino-testing/pom.xml index f69daed2c3c1..d0d927660091 100644 --- a/testing/trino-testing/pom.xml +++ b/testing/trino-testing/pom.xml @@ -202,6 +202,12 @@ test + + org.junit.jupiter + junit-jupiter-engine + test + + org.openjdk.jmh @@ -233,11 +239,6 @@ surefire-testng ${dep.plugin.surefire.version} - - org.junit.jupiter - junit-jupiter-engine - ${dep.junit.version} - diff --git a/testing/trino-tests/pom.xml b/testing/trino-tests/pom.xml index a52d87b41814..27ed608fbc64 100644 --- a/testing/trino-tests/pom.xml +++ b/testing/trino-tests/pom.xml @@ -435,11 +435,6 @@ surefire-testng ${dep.plugin.surefire.version} - - - - - From 881cc3987785b269f2d4c3c3aa4dd1691de3a5f5 Mon Sep 17 00:00:00 2001 From: Elon Azoulay Date: Sun, 25 Jun 2023 13:37:18 -0700 Subject: [PATCH 111/587] Add GCS filesystem --- .github/workflows/ci.yml | 11 + lib/trino-filesystem-gcs/pom.xml | 203 +++++++++++++ .../gcs/DefaultGcsStorageFactory.java | 92 ++++++ .../trino/filesystem/gcs/GcsFileIterator.java | 69 +++++ .../trino/filesystem/gcs/GcsFileSystem.java | 278 ++++++++++++++++++ .../filesystem/gcs/GcsFileSystemConfig.java | 161 ++++++++++ .../filesystem/gcs/GcsFileSystemFactory.java | 61 ++++ .../filesystem/gcs/GcsFileSystemModule.java | 32 ++ .../io/trino/filesystem/gcs/GcsInput.java | 125 ++++++++ .../io/trino/filesystem/gcs/GcsInputFile.java | 117 ++++++++ .../trino/filesystem/gcs/GcsInputStream.java | 201 +++++++++++++ .../io/trino/filesystem/gcs/GcsLocation.java | 64 ++++ .../trino/filesystem/gcs/GcsOutputFile.java | 103 +++++++ .../trino/filesystem/gcs/GcsOutputStream.java | 155 ++++++++++ .../filesystem/gcs/GcsStorageFactory.java | 22 ++ .../io/trino/filesystem/gcs/GcsUtils.java | 77 +++++ .../gcs/AbstractTestGcsFileSystem.java | 150 ++++++++++ .../gcs/TestGcsFileSystemConfig.java | 99 +++++++ .../filesystem/gcs/TestGcsFileSystemGcs.java | 31 ++ lib/trino-filesystem-manager/pom.xml | 5 + .../filesystem/manager/FileSystemConfig.java | 13 + .../filesystem/manager/FileSystemModule.java | 10 + .../manager/TestFileSystemConfig.java | 7 +- pom.xml | 103 +++++++ 24 files changed, 2187 insertions(+), 2 deletions(-) create mode 100644 lib/trino-filesystem-gcs/pom.xml create mode 100644 lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/DefaultGcsStorageFactory.java create mode 100644 lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileIterator.java create mode 100644 lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileSystem.java create mode 100644 lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileSystemConfig.java create mode 100644 lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileSystemFactory.java create mode 100644 lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileSystemModule.java create mode 100644 lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsInput.java create mode 100644 lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsInputFile.java create mode 100644 lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsInputStream.java create mode 100644 lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsLocation.java create mode 100644 lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsOutputFile.java create mode 100644 lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsOutputStream.java create mode 100644 lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsStorageFactory.java create mode 100644 lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsUtils.java create mode 100644 lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/AbstractTestGcsFileSystem.java create mode 100644 lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/TestGcsFileSystemConfig.java create mode 100644 lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/TestGcsFileSystemGcs.java diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99091cda7e11..b905c857b1d6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -458,6 +458,7 @@ jobs: !:trino-faulttolerant-tests, !:trino-filesystem, !:trino-filesystem-azure, + !:trino-filesystem-gcs, !:trino-filesystem-manager, !:trino-filesystem-s3, !:trino-google-sheets, @@ -574,6 +575,7 @@ jobs: - { modules: lib/trino-filesystem-s3, profile: cloud-tests } - { modules: lib/trino-filesystem-azure, profile: cloud-tests } - { modules: lib/trino-hdfs, profile: cloud-tests } + - { modules: lib/trino-filesystem-gcs, profile: cloud-tests } - { modules: plugin/trino-accumulo } - { modules: plugin/trino-bigquery } - { modules: plugin/trino-bigquery, profile: cloud-tests-arrow } @@ -659,6 +661,7 @@ jobs: && ! (contains(matrix.modules, 'trino-filesystem-s3') && contains(matrix.profile, 'cloud-tests')) && ! (contains(matrix.modules, 'trino-filesystem-azure') && contains(matrix.profile, 'cloud-tests')) && ! (contains(matrix.modules, 'trino-hdfs') && contains(matrix.profile, 'cloud-tests')) + && ! (contains(matrix.modules, 'trino-filesystem-gcs') && contains(matrix.profile, 'cloud-tests')) run: $MAVEN test ${MAVEN_TEST} -pl ${{ matrix.modules }} ${{ matrix.profile != '' && format('-P {0}', matrix.profile) || '' }} # Additional tests for selected modules - name: HDFS file system cache isolated JVM tests @@ -714,6 +717,14 @@ jobs: (env.CI_SKIP_SECRETS_PRESENCE_CHECKS != '' || env.ABFS_BLOB_ACCOUNT != '' || env.ABFS_BLOB_ACCESS_KEY != '' || env.ABFS_FLAT_ACCOUNT != '' || env.ABFS_FLAT_ACCESS_KEY != '' || env.ABFS_ACCOUNT != '' || env.ABFS_ACCESS_KEY != '') run: | $MAVEN test ${MAVEN_TEST} -pl ${{ matrix.modules }} ${{ format('-P {0}', matrix.profile) }} + - name: GCS FileSystem Cloud Tests + env: + GCP_CREDENTIALS_KEY: ${{ secrets.GCP_CREDENTIALS_KEY }} + if: >- + contains(matrix.modules, 'trino-filesystem-gcs') && contains(matrix.profile, 'cloud-tests') && + (env.CI_SKIP_SECRETS_PRESENCE_CHECKS != '' || env.GCP_CREDENTIALS_KEY != '') + run: | + $MAVEN test ${MAVEN_TEST} -pl ${{ matrix.modules }} ${{ format('-P {0}', matrix.profile) }} - name: Cloud Delta Lake Tests # Cloud tests are separate because they are time intensive, requiring cross-cloud network communication env: diff --git a/lib/trino-filesystem-gcs/pom.xml b/lib/trino-filesystem-gcs/pom.xml new file mode 100644 index 000000000000..cbf748c58d4b --- /dev/null +++ b/lib/trino-filesystem-gcs/pom.xml @@ -0,0 +1,203 @@ + + + 4.0.0 + + + io.trino + trino-root + 433-SNAPSHOT + ../../pom.xml + + + trino-filesystem-gcs + trino-filesystem-gcs + Trino Filesystem - GCS + + + ${project.parent.basedir} + + + + + com.google.api + gax + + + javax.annotation + javax.annotation-api + + + + + + com.google.auth + google-auth-library-credentials + + + + com.google.auth + google-auth-library-oauth2-http + + + + com.google.cloud + google-cloud-core + + + + com.google.cloud + google-cloud-storage + + + com.google.guava + listenablefuture + + + javax.annotation + javax.annotation-api + + + + + + com.google.guava + guava + + + + com.google.http-client + google-http-client + + + commons-logging + commons-logging + + + org.apache.httpcomponents + httpclient + + + org.apache.httpcomponents + httpcore + + + + + + com.google.inject + guice + + + + io.airlift + concurrent + + + + io.airlift + configuration + + + + io.airlift + units + + + + io.trino + trino-filesystem + + + + io.trino + trino-memory-context + + + + jakarta.annotation + jakarta.annotation-api + + + + jakarta.validation + jakarta.validation-api + + + + io.trino + trino-spi + provided + + + + org.jetbrains + annotations + provided + + + + io.airlift + testing + test + + + + io.trino + trino-filesystem + ${project.version} + tests + test + + + + org.assertj + assertj-core + test + + + + org.junit.jupiter + junit-jupiter-api + test + + + + + + default + + true + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/TestDefaultGcsFileSystem.java + + + + + + + + + cloud-tests + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/TestDefaultGcsFileSystem.java + + + + + + + + diff --git a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/DefaultGcsStorageFactory.java b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/DefaultGcsStorageFactory.java new file mode 100644 index 000000000000..8224dafaa6a3 --- /dev/null +++ b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/DefaultGcsStorageFactory.java @@ -0,0 +1,92 @@ +/* + * 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 io.trino.filesystem.gcs; + +import com.google.auth.oauth2.GoogleCredentials; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; +import com.google.common.base.VerifyException; +import com.google.common.collect.ImmutableList; +import com.google.inject.Inject; +import io.trino.spi.security.ConnectorIdentity; + +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.util.List; +import java.util.Optional; + +import static com.google.common.base.Strings.nullToEmpty; +import static java.nio.charset.StandardCharsets.UTF_8; + +public class DefaultGcsStorageFactory + implements GcsStorageFactory +{ + public static final String GCS_OAUTH_KEY = "gcs.oauth"; + public static final List DEFAULT_SCOPES = ImmutableList.of("https://www.googleapis.com/auth/cloud-platform"); + private final String projectId; + private final boolean useGcsAccessToken; + private final Optional jsonGoogleCredential; + + @Inject + public DefaultGcsStorageFactory(GcsFileSystemConfig config) + throws IOException + { + config.validate(); + projectId = config.getProjectId(); + useGcsAccessToken = config.isUseGcsAccessToken(); + String jsonKey = config.getJsonKey(); + String jsonKeyFilePath = config.getJsonKeyFilePath(); + if (jsonKey != null) { + try (InputStream inputStream = new ByteArrayInputStream(jsonKey.getBytes(UTF_8))) { + jsonGoogleCredential = Optional.of(GoogleCredentials.fromStream(inputStream).createScoped(DEFAULT_SCOPES)); + } + } + else if (jsonKeyFilePath != null) { + try (FileInputStream inputStream = new FileInputStream(jsonKeyFilePath)) { + jsonGoogleCredential = Optional.of(GoogleCredentials.fromStream(inputStream).createScoped(DEFAULT_SCOPES)); + } + } + else { + jsonGoogleCredential = Optional.empty(); + } + } + + @Override + public Storage create(ConnectorIdentity identity) + { + try { + GoogleCredentials credentials; + if (useGcsAccessToken) { + String accessToken = nullToEmpty(identity.getExtraCredentials().get(GCS_OAUTH_KEY)); + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(accessToken.getBytes(UTF_8))) { + credentials = GoogleCredentials.fromStream(inputStream).createScoped(DEFAULT_SCOPES); + } + } + else { + credentials = jsonGoogleCredential.orElseThrow(() -> new VerifyException("GCS credentials not configured")); + } + StorageOptions.Builder storageOptionsBuilder = StorageOptions.newBuilder(); + if (projectId != null) { + storageOptionsBuilder.setProjectId(projectId); + } + return storageOptionsBuilder.setCredentials(credentials).build().getService(); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileIterator.java b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileIterator.java new file mode 100644 index 000000000000..5b3f4e42fa4d --- /dev/null +++ b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileIterator.java @@ -0,0 +1,69 @@ +/* + * 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 io.trino.filesystem.gcs; + +import com.google.api.gax.paging.Page; +import com.google.cloud.storage.Blob; +import com.google.common.collect.Iterators; +import io.trino.filesystem.FileEntry; +import io.trino.filesystem.FileIterator; +import io.trino.filesystem.Location; + +import java.io.IOException; +import java.time.Instant; +import java.util.Iterator; +import java.util.Optional; + +import static io.trino.filesystem.gcs.GcsUtils.handleGcsException; +import static java.util.Objects.requireNonNull; + +public class GcsFileIterator + implements FileIterator +{ + private final GcsLocation location; + private Iterator blobIterator; + + public GcsFileIterator(GcsLocation location, Page page) + { + this.location = requireNonNull(location, "location is null"); + // Page::iterateAll handles paging internally + this.blobIterator = Iterators.filter(page.iterateAll().iterator(), blob -> !blob.isDirectory()); + } + + @Override + public boolean hasNext() + throws IOException + { + try { + return blobIterator.hasNext(); + } + catch (RuntimeException e) { + throw handleGcsException(e, "iterate files", location); + } + } + + @Override + public FileEntry next() + throws IOException + { + try { + Blob blob = blobIterator.next(); + long length = requireNonNull(blob.getSize(), "blob size is null"); + return new FileEntry(Location.of(location.getBase() + blob.getName()), length, Instant.from(blob.getUpdateTimeOffsetDateTime()), Optional.empty()); + } + catch (RuntimeException e) { + throw handleGcsException(e, "iterate files", location); + } + } +} diff --git a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileSystem.java b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileSystem.java new file mode 100644 index 000000000000..fcb5c14d3754 --- /dev/null +++ b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileSystem.java @@ -0,0 +1,278 @@ +/* + * 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 io.trino.filesystem.gcs; + +import com.google.api.gax.paging.Page; +import com.google.cloud.storage.Blob; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.Storage.BlobListOption; +import com.google.cloud.storage.StorageBatch; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterators; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import io.trino.filesystem.FileIterator; +import io.trino.filesystem.Location; +import io.trino.filesystem.TrinoFileSystem; +import io.trino.filesystem.TrinoInputFile; +import io.trino.filesystem.TrinoOutputFile; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.OptionalLong; +import java.util.Set; + +import static com.google.api.client.util.Preconditions.checkState; +import static com.google.cloud.storage.Storage.BlobListOption.currentDirectory; +import static com.google.cloud.storage.Storage.BlobListOption.matchGlob; +import static com.google.cloud.storage.Storage.BlobListOption.pageSize; +import static com.google.common.collect.Iterables.partition; +import static io.airlift.concurrent.MoreFutures.getFutureValue; +import static io.trino.filesystem.gcs.GcsUtils.getBlobOrThrow; +import static io.trino.filesystem.gcs.GcsUtils.handleGcsException; +import static java.util.Objects.requireNonNull; + +public class GcsFileSystem + implements TrinoFileSystem +{ + private final ListeningExecutorService executorService; + private final Storage storage; + private final int readBlockSizeBytes; + private final long writeBlockSizeBytes; + private final int pageSize; + private final int batchSize; + + public GcsFileSystem(ListeningExecutorService executorService, Storage storage, int readBlockSizeBytes, long writeBlockSizeBytes, int pageSize, int batchSize) + { + this.executorService = requireNonNull(executorService, "executorService is null"); + this.storage = requireNonNull(storage, "storage is null"); + this.readBlockSizeBytes = readBlockSizeBytes; + this.writeBlockSizeBytes = writeBlockSizeBytes; + this.pageSize = pageSize; + this.batchSize = batchSize; + } + + @Override + public TrinoInputFile newInputFile(Location location) + { + GcsLocation gcsLocation = new GcsLocation(location); + checkIsValidFile(gcsLocation); + return new GcsInputFile(gcsLocation, storage, readBlockSizeBytes, OptionalLong.empty()); + } + + @Override + public TrinoInputFile newInputFile(Location location, long length) + { + GcsLocation gcsLocation = new GcsLocation(location); + checkIsValidFile(gcsLocation); + return new GcsInputFile(gcsLocation, storage, readBlockSizeBytes, OptionalLong.of(length)); + } + + @Override + public TrinoOutputFile newOutputFile(Location location) + { + GcsLocation gcsLocation = new GcsLocation(location); + checkIsValidFile(gcsLocation); + return new GcsOutputFile(gcsLocation, storage, writeBlockSizeBytes); + } + + @Override + public void deleteFile(Location location) + throws IOException + { + GcsLocation gcsLocation = new GcsLocation(location); + checkIsValidFile(gcsLocation); + Blob blob = getBlobOrThrow(storage, gcsLocation); + blob.delete(); + } + + @Override + public void deleteFiles(Collection locations) + throws IOException + { + List> batchFutures = new ArrayList<>(); + try { + for (List locationBatch : partition(locations, batchSize)) { + StorageBatch batch = storage.batch(); + for (Location location : locationBatch) { + batch.delete(getBlobOrThrow(storage, new GcsLocation(location)).getBlobId()); + } + batchFutures.add(executorService.submit(batch::submit)); + } + getFutureValue(Futures.allAsList(batchFutures)); + } + catch (RuntimeException e) { + throw handleGcsException(e, "delete files", locations); + } + } + + @Override + public void deleteDirectory(Location location) + throws IOException + { + GcsLocation gcsLocation = new GcsLocation(normalizeToDirectory(location)); + try { + List> batchFutures = new ArrayList<>(); + + for (List blobBatch : partition(getPage(gcsLocation).iterateAll(), batchSize)) { + StorageBatch batch = storage.batch(); + for (Blob blob : blobBatch) { + batch.delete(blob.getBlobId()); + } + batchFutures.add(executorService.submit(batch::submit)); + } + getFutureValue(Futures.allAsList(batchFutures)); + } + catch (RuntimeException e) { + throw handleGcsException(e, "delete directory", gcsLocation); + } + } + + @Override + public void renameFile(Location source, Location target) + throws IOException + { + throw new IOException("GCS does not support renames"); + } + + @Override + public FileIterator listFiles(Location location) + throws IOException + { + GcsLocation gcsLocation = new GcsLocation(normalizeToDirectory(location)); + try { + return new GcsFileIterator(gcsLocation, getPage(gcsLocation)); + } + catch (RuntimeException e) { + throw handleGcsException(e, "list files", gcsLocation); + } + } + + private static Location normalizeToDirectory(Location location) + { + String path = location.path(); + if (!path.isEmpty() && !path.endsWith("/")) { + return location.appendSuffix("/"); + } + return location; + } + + private static void checkIsValidFile(GcsLocation gcsLocation) + { + checkState(!gcsLocation.path().isEmpty(), "Location path is empty: %s", gcsLocation); + checkState(!gcsLocation.path().endsWith("/"), "Location path ends with a slash: %s", gcsLocation); + } + + private Page getPage(GcsLocation location, BlobListOption... blobListOptions) + { + List optionsBuilder = new ArrayList<>(); + + if (!location.path().isEmpty()) { + optionsBuilder.add(BlobListOption.prefix(location.path())); + } + Arrays.stream(blobListOptions).forEach(optionsBuilder::add); + optionsBuilder.add(pageSize(this.pageSize)); + return storage.list(location.bucket(), optionsBuilder.toArray(BlobListOption[]::new)); + } + + @Override + public Optional directoryExists(Location location) + throws IOException + { + // Notes: + // GCS is not hierarchical, there is no way to determine the difference + // between an empty blob and a directory: for this case Optional.empty() will be returned per the super + // method spec. + // + // Note on blob.isDirectory: The isDirectory() method returns false unless invoked via storage.list() + // with currentDirectory() enabled for empty blobs intended to be used as directories. + // The only time blob.isDirectory() is true is when an object was created that introduced the path: + // + // Example 1: createBlob("bucket", "dir") creates an empty blob intended to be used as a "directory" + // you can then create a file "bucket", "dir/file") + // Invoking blob.isDirectory() on "dir" returns false even after the "dir/file" object is created. + // + // Example 2: createBlob("bucket", "dir2/file") when "dir2" does not exist will return true for isDirectory() + // when invoked on the "dir2/" path. Also note that the blob name has a trailing slash. + // This behavior is only enabled with BlobListOption.currentDirectory() and isDirectory() is only true when the blob + // is returned from a storage.list operation. + + GcsLocation gcsLocation = new GcsLocation(location); + if (gcsLocation.path().isEmpty()) { + return Optional.of(bucketExists(gcsLocation.bucket())); + } + if (listFiles(location).hasNext()) { + return Optional.of(true); + } + return Optional.empty(); + } + + private boolean bucketExists(String bucket) + { + return storage.get(bucket) != null; + } + + @Override + public void createDirectory(Location location) + throws IOException + { + validateGcsLocation(location); + } + + @Override + public void renameDirectory(Location source, Location target) + throws IOException + { + throw new IOException("GCS does not support directory renames"); + } + + @Override + public Set listDirectories(Location location) + throws IOException + { + GcsLocation gcsLocation = new GcsLocation(normalizeToDirectory(location)); + try { + Page page = getPage(gcsLocation, currentDirectory(), matchGlob(gcsLocation.path() + "*/")); + Iterator blobIterator = Iterators.filter(page.iterateAll().iterator(), blob -> blob.isDirectory()); + ImmutableSet.Builder locationBuilder = ImmutableSet.builder(); + while (blobIterator.hasNext()) { + locationBuilder.add(Location.of(gcsLocation.getBase() + blobIterator.next().getName())); + } + return locationBuilder.build(); + } + catch (RuntimeException e) { + throw handleGcsException(e, "list directories", gcsLocation); + } + } + + @Override + public Optional createTemporaryDirectory(Location targetPath, String temporaryPrefix, String relativePrefix) + { + validateGcsLocation(targetPath); + // GCS does not have directories + return Optional.empty(); + } + + @SuppressWarnings("ResultOfObjectAllocationIgnored") + private static void validateGcsLocation(Location location) + { + new GcsLocation(location); + } +} diff --git a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileSystemConfig.java b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileSystemConfig.java new file mode 100644 index 000000000000..915d1dcf1e66 --- /dev/null +++ b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileSystemConfig.java @@ -0,0 +1,161 @@ +/* + * 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 io.trino.filesystem.gcs; + +import io.airlift.configuration.Config; +import io.airlift.configuration.ConfigDescription; +import io.airlift.configuration.ConfigSecuritySensitive; +import io.airlift.configuration.validation.FileExists; +import io.airlift.units.DataSize; +import jakarta.annotation.Nullable; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; + +import static com.google.common.base.Preconditions.checkState; +import static io.airlift.units.DataSize.Unit.MEGABYTE; + +public class GcsFileSystemConfig +{ + private DataSize readBlockSize = DataSize.of(2, MEGABYTE); + private DataSize writeBlockSize = DataSize.of(16, MEGABYTE); + private int pageSize = 100; + private int batchSize = 100; + + private String projectId; + + private boolean useGcsAccessToken; + private String jsonKey; + private String jsonKeyFilePath; + + @NotNull + public DataSize getReadBlockSize() + { + return readBlockSize; + } + + @Config("gcs.read-block-size") + @ConfigDescription("Minimum size that will be read in one RPC. The default size is 2MiB, see com.google.cloud.BaseStorageReadChannel.") + public GcsFileSystemConfig setReadBlockSize(DataSize readBlockSize) + { + this.readBlockSize = readBlockSize; + return this; + } + + @NotNull + public DataSize getWriteBlockSize() + { + return writeBlockSize; + } + + @Config("gcs.write-block-size") + @ConfigDescription("Minimum size that will be written in one RPC. The default size is 16MiB, see com.google.cloud.BaseStorageWriteChannel.") + public GcsFileSystemConfig setWriteBlockSize(DataSize writeBlockSize) + { + this.writeBlockSize = writeBlockSize; + return this; + } + + @Min(1) + public int getPageSize() + { + return pageSize; + } + + @Config("gcs.page-size") + @ConfigDescription("The maximum number of blobs to return per page.") + public GcsFileSystemConfig setPageSize(int pageSize) + { + this.pageSize = pageSize; + return this; + } + + @Min(1) + public int getBatchSize() + { + return batchSize; + } + + @Config("gcs.batch-size") + @ConfigDescription("Number of blobs to delete per batch. Recommended batch size is 100: https://cloud.google.com/storage/docs/batch") + public GcsFileSystemConfig setBatchSize(int batchSize) + { + this.batchSize = batchSize; + return this; + } + + @Nullable + public String getProjectId() + { + return projectId; + } + + @Config("gcs.projectId") + public GcsFileSystemConfig setProjectId(String projectId) + { + this.projectId = projectId; + return this; + } + + public boolean isUseGcsAccessToken() + { + return useGcsAccessToken; + } + + @Config("gcs.use-access-token") + public GcsFileSystemConfig setUseGcsAccessToken(boolean useGcsAccessToken) + { + this.useGcsAccessToken = useGcsAccessToken; + return this; + } + + @Nullable + public String getJsonKey() + { + return jsonKey; + } + + @Config("gcs.json-key") + @ConfigSecuritySensitive + public GcsFileSystemConfig setJsonKey(String jsonKey) + { + this.jsonKey = jsonKey; + return this; + } + + @Nullable + @FileExists + public String getJsonKeyFilePath() + { + return jsonKeyFilePath; + } + + @Config("gcs.json-key-file-path") + @ConfigDescription("JSON key file used to access Google Cloud Storage") + public GcsFileSystemConfig setJsonKeyFilePath(String jsonKeyFilePath) + { + this.jsonKeyFilePath = jsonKeyFilePath; + return this; + } + + public void validate() + { + // This cannot be normal validation, as it would make it impossible to write TestHiveGcsConfig.testExplicitPropertyMappings + + if (useGcsAccessToken) { + checkState(jsonKey == null, "Cannot specify 'hive.gcs.json-key' when 'hive.gcs.use-access-token' is set"); + checkState(jsonKeyFilePath == null, "Cannot specify 'hive.gcs.json-key-file-path' when 'hive.gcs.use-access-token' is set"); + } + checkState(jsonKey == null || jsonKeyFilePath == null, "'hive.gcs.json-key' and 'hive.gcs.json-key-file-path' cannot be both set"); + } +} diff --git a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileSystemFactory.java b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileSystemFactory.java new file mode 100644 index 000000000000..4901dc5f50fc --- /dev/null +++ b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileSystemFactory.java @@ -0,0 +1,61 @@ +/* + * 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 io.trino.filesystem.gcs; + +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.inject.Inject; +import io.trino.filesystem.TrinoFileSystem; +import io.trino.filesystem.TrinoFileSystemFactory; +import io.trino.spi.security.ConnectorIdentity; +import jakarta.annotation.PreDestroy; + +import static com.google.common.util.concurrent.MoreExecutors.listeningDecorator; +import static io.airlift.concurrent.Threads.daemonThreadsNamed; +import static java.lang.Math.toIntExact; +import static java.util.Objects.requireNonNull; +import static java.util.concurrent.Executors.newCachedThreadPool; + +public class GcsFileSystemFactory + implements TrinoFileSystemFactory +{ + private final int readBlockSizeBytes; + private final long writeBlockSizeBytes; + private final int pageSize; + private final int batchSize; + private final ListeningExecutorService executorService; + private final GcsStorageFactory storageFactory; + + @Inject + public GcsFileSystemFactory(GcsFileSystemConfig config, GcsStorageFactory storageFactory) + { + this.readBlockSizeBytes = toIntExact(config.getReadBlockSize().toBytes()); + this.writeBlockSizeBytes = config.getWriteBlockSize().toBytes(); + this.pageSize = config.getPageSize(); + this.batchSize = config.getBatchSize(); + this.storageFactory = requireNonNull(storageFactory, "storageFactory is null"); + this.executorService = listeningDecorator(newCachedThreadPool(daemonThreadsNamed("trino-filesystem-gcs-%S"))); + } + + @PreDestroy + public void stop() + { + executorService.shutdownNow(); + } + + @Override + public TrinoFileSystem create(ConnectorIdentity identity) + { + return new GcsFileSystem(executorService, storageFactory.create(identity), readBlockSizeBytes, writeBlockSizeBytes, pageSize, batchSize); + } +} diff --git a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileSystemModule.java b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileSystemModule.java new file mode 100644 index 000000000000..7d4479d3557f --- /dev/null +++ b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileSystemModule.java @@ -0,0 +1,32 @@ +/* + * 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 io.trino.filesystem.gcs; + +import com.google.inject.Binder; +import com.google.inject.Module; +import com.google.inject.Scopes; + +import static io.airlift.configuration.ConfigBinder.configBinder; + +public class GcsFileSystemModule + implements Module +{ + @Override + public void configure(Binder binder) + { + configBinder(binder).bindConfig(GcsFileSystemConfig.class); + binder.bind(GcsStorageFactory.class).to(DefaultGcsStorageFactory.class).in(Scopes.SINGLETON); + binder.bind(GcsFileSystemFactory.class).in(Scopes.SINGLETON); + } +} diff --git a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsInput.java b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsInput.java new file mode 100644 index 000000000000..89600751777b --- /dev/null +++ b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsInput.java @@ -0,0 +1,125 @@ +/* + * 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 io.trino.filesystem.gcs; + +import com.google.cloud.ReadChannel; +import com.google.cloud.storage.Blob; +import com.google.cloud.storage.Storage; +import io.trino.filesystem.TrinoInput; + +import java.io.EOFException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.OptionalLong; + +import static com.google.common.base.Preconditions.checkArgument; +import static io.trino.filesystem.gcs.GcsUtils.getBlobOrThrow; +import static io.trino.filesystem.gcs.GcsUtils.getReadChannel; +import static io.trino.filesystem.gcs.GcsUtils.handleGcsException; +import static java.util.Objects.checkFromIndexSize; +import static java.util.Objects.requireNonNull; + +final class GcsInput + implements TrinoInput +{ + private final GcsLocation location; + private final Storage storage; + private final int readBlockSize; + private OptionalLong length; + private boolean closed; + + public GcsInput(GcsLocation location, Storage storage, int readBlockSize, OptionalLong length) + { + this.location = requireNonNull(location, "location is null"); + this.storage = requireNonNull(storage, "storage is null"); + checkArgument(readBlockSize >= 0, "readBlockSize is negative"); + this.readBlockSize = readBlockSize; + this.length = requireNonNull(length, "length is null"); + } + + @Override + public void readFully(long position, byte[] buffer, int bufferOffset, int bufferLength) + throws IOException + { + ensureOpen(); + if (position < 0) { + throw new IOException("Negative seek offset"); + } + checkFromIndexSize(bufferOffset, bufferLength, buffer.length); + if (bufferLength == 0) { + return; + } + + try (ReadChannel readChannel = getReadChannel(getBlobOrThrow(storage, location), location, position, readBlockSize, length)) { + int readSize = readNBytes(readChannel, buffer, bufferOffset, bufferLength); + if (readSize != bufferLength) { + throw new EOFException("End of file reached before reading fully: " + location); + } + } + catch (RuntimeException e) { + throw handleGcsException(e, "reading file", location); + } + } + + @Override + public int readTail(byte[] buffer, int bufferOffset, int bufferLength) + throws IOException + { + ensureOpen(); + checkFromIndexSize(bufferOffset, bufferLength, buffer.length); + Blob blob = getBlobOrThrow(storage, location); + long offset = Math.max(0, length.orElse(blob.getSize()) - bufferLength); + try (ReadChannel readChannel = getReadChannel(blob, location, offset, readBlockSize, length)) { + return readNBytes(readChannel, buffer, bufferOffset, bufferLength); + } + catch (RuntimeException e) { + throw handleGcsException(e, "read file", location); + } + } + + private void ensureOpen() + throws IOException + { + if (closed) { + throw new IOException("Input stream closed: " + location); + } + } + + @Override + public void close() + { + closed = true; + } + + @Override + public String toString() + { + return location.toString(); + } + + private int readNBytes(ReadChannel readChannel, byte[] buffer, int bufferOffset, int bufferLength) + throws IOException + { + ByteBuffer wrappedBuffer = ByteBuffer.wrap(buffer, bufferOffset, bufferLength); + int readSize = 0; + while (readSize < bufferLength) { + int bytesRead = readChannel.read(wrappedBuffer); + if (bytesRead == -1) { + break; + } + readSize += bytesRead; + } + return readSize; + } +} diff --git a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsInputFile.java b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsInputFile.java new file mode 100644 index 000000000000..2c6879bf3314 --- /dev/null +++ b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsInputFile.java @@ -0,0 +1,117 @@ +/* + * 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 io.trino.filesystem.gcs; + +import com.google.cloud.storage.Blob; +import com.google.cloud.storage.Storage; +import io.trino.filesystem.Location; +import io.trino.filesystem.TrinoInput; +import io.trino.filesystem.TrinoInputFile; +import io.trino.filesystem.TrinoInputStream; + +import java.io.IOException; +import java.time.Instant; +import java.util.Optional; +import java.util.OptionalLong; + +import static io.trino.filesystem.gcs.GcsUtils.getBlob; +import static io.trino.filesystem.gcs.GcsUtils.getBlobOrThrow; +import static io.trino.filesystem.gcs.GcsUtils.handleGcsException; +import static java.util.Objects.requireNonNull; + +public class GcsInputFile + implements TrinoInputFile +{ + private final GcsLocation location; + private final Storage storage; + private final int readBlockSize; + private OptionalLong length; + private OptionalLong predeclaredLength; + private Optional lastModified = Optional.empty(); + + public GcsInputFile(GcsLocation location, Storage storage, int readBockSize, OptionalLong predeclaredLength) + { + this.location = requireNonNull(location, "location is null"); + this.storage = requireNonNull(storage, "storage is null"); + this.readBlockSize = readBockSize; + this.predeclaredLength = requireNonNull(predeclaredLength, "length is null"); + this.length = OptionalLong.empty(); + } + + @Override + public TrinoInput newInput() + throws IOException + { + // Note: Only pass predeclared length, to keep the contract of TrinoFileSystem.newInputFile + return new GcsInput(location, storage, readBlockSize, predeclaredLength); + } + + @Override + public TrinoInputStream newStream() + throws IOException + { + Blob blob = getBlobOrThrow(storage, location); + return new GcsInputStream(location, blob, readBlockSize, predeclaredLength); + } + + @Override + public long length() + throws IOException + { + if (predeclaredLength.isPresent()) { + return predeclaredLength.getAsLong(); + } + if (length.isEmpty()) { + loadProperties(); + } + return length.orElseThrow(); + } + + @Override + public Instant lastModified() + throws IOException + { + if (lastModified.isEmpty()) { + loadProperties(); + } + return lastModified.orElseThrow(); + } + + @Override + public boolean exists() + throws IOException + { + Optional blob = getBlob(storage, location); + return blob.isPresent() && blob.get().exists(); + } + + @Override + public Location location() + { + return location.location(); + } + + private void loadProperties() + throws IOException + { + Blob blob = getBlobOrThrow(storage, location); + try { + length = OptionalLong.of(blob.getSize()); + lastModified = Optional.of(Instant.from(blob.getUpdateTimeOffsetDateTime())); + } + catch (RuntimeException e) { + throw handleGcsException(e, "fetching properties for file", location); + } + } +} diff --git a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsInputStream.java b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsInputStream.java new file mode 100644 index 000000000000..4f6f39fae3d7 --- /dev/null +++ b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsInputStream.java @@ -0,0 +1,201 @@ +/* + * 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 io.trino.filesystem.gcs; + +import com.google.cloud.ReadChannel; +import com.google.cloud.storage.Blob; +import com.google.common.primitives.Ints; +import io.trino.filesystem.TrinoInputStream; + +import java.io.EOFException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.OptionalLong; + +import static com.google.common.primitives.Longs.constrainToRange; +import static io.trino.filesystem.gcs.GcsUtils.getReadChannel; +import static io.trino.filesystem.gcs.GcsUtils.handleGcsException; +import static java.util.Objects.checkFromIndexSize; +import static java.util.Objects.requireNonNull; + +public class GcsInputStream + extends TrinoInputStream +{ + private final GcsLocation location; + private final Blob blob; + private final int readBlockSizeBytes; + private final long fileSize; + private final OptionalLong predeclaredLength; + private ReadChannel readChannel; + // Used for read(). Similar to sun.nio.ch.ChannelInputStream + private ByteBuffer readBuffer = ByteBuffer.allocate(1); + private long currentPosition; + private long nextPosition; + private boolean closed; + + public GcsInputStream(GcsLocation location, Blob blob, int readBlockSizeBytes, OptionalLong predeclaredLength) + throws IOException + { + this.location = requireNonNull(location, "location is null"); + this.blob = requireNonNull(blob, "blob is null"); + this.readBlockSizeBytes = readBlockSizeBytes; + this.predeclaredLength = requireNonNull(predeclaredLength, "predeclaredLength is null"); + this.fileSize = predeclaredLength.orElse(blob.getSize()); + openStream(); + } + + @Override + public int available() + throws IOException + { + ensureOpen(); + repositionStream(); + return Ints.saturatedCast(fileSize - currentPosition); + } + + @Override + public long getPosition() + throws IOException + { + return nextPosition; + } + + @Override + public void seek(long newPosition) + throws IOException + { + ensureOpen(); + if (newPosition < 0) { + throw new IOException("Negative seek offset"); + } + if (newPosition > fileSize) { + throw new IOException("Cannot seek to %s. File size is %s: %s".formatted(newPosition, fileSize, location)); + } + nextPosition = newPosition; + } + + @Override + public int read() + throws IOException + { + ensureOpen(); + repositionStream(); + try { + // Similar to sun.nio.ch.ChannelInputStream::read but uses a byte buffer and is not synchronized + readBuffer.position(0); + int bytesRead = readChannel.read(readBuffer); + + if (bytesRead == 1) { + currentPosition++; + nextPosition++; + return readBuffer.get(0) & 0xff; + } + return -1; + } + catch (IOException e) { + throw new IOException("Error reading file: " + location, e); + } + } + + @Override + public int read(byte[] buffer, int offset, int length) + throws IOException + { + checkFromIndexSize(offset, length, buffer.length); + ensureOpen(); + repositionStream(); + ByteBuffer wrappedBuffer = ByteBuffer.wrap(buffer, offset, length); + try { + int readSize = readChannel.read(wrappedBuffer); + if (readSize > 0) { + currentPosition += readSize; + nextPosition += readSize; + } + return readSize; + } + catch (IOException e) { + throw new IOException("Error reading file: " + location, e); + } + } + + @Override + public long skip(long n) + throws IOException + { + ensureOpen(); + long skipSize = constrainToRange(n, 0, fileSize - nextPosition); + nextPosition += skipSize; + return skipSize; + } + + @Override + public void skipNBytes(long n) + throws IOException + { + ensureOpen(); + if (n <= 0) { + return; + } + + long position = nextPosition + n; + if ((position < 0) || (position > fileSize)) { + throw new EOFException("Unable to skip %s bytes (position=%s, fileSize=%s): %s".formatted(n, nextPosition, fileSize, location)); + } + nextPosition = position; + } + + @Override + public void close() + throws IOException + { + if (!closed) { + closed = true; + try { + readChannel.close(); + } + catch (RuntimeException e) { + throw handleGcsException(e, "closing file", location); + } + } + } + + private void ensureOpen() + throws IOException + { + if (closed) { + throw new IOException("Output stream closed: " + location); + } + } + + private void openStream() + throws IOException + { + try { + this.readChannel = getReadChannel(blob, location, 0L, readBlockSizeBytes, predeclaredLength); + } + catch (RuntimeException e) { + throw handleGcsException(e, "read file", location); + } + } + + private void repositionStream() + throws IOException + { + if (nextPosition == currentPosition) { + return; + } + readChannel.seek(nextPosition); + currentPosition = nextPosition; + } +} diff --git a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsLocation.java b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsLocation.java new file mode 100644 index 000000000000..fc802cdee67e --- /dev/null +++ b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsLocation.java @@ -0,0 +1,64 @@ +/* + * 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 io.trino.filesystem.gcs; + +import io.trino.filesystem.Location; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Objects.requireNonNull; + +record GcsLocation(Location location) +{ + GcsLocation + { + // Note: Underscores are allowed in bucket names, see https://cloud.google.com/storage/docs/buckets#naming. + // Generation numbers are not supported, i.e. gs://bucket/path#generation. + // For more details on generation numbers see https://cloud.google.com/storage/docs/object-versioning. + + requireNonNull(location, "location"); + checkArgument(location.scheme().isPresent(), "No scheme for GCS location: %s", location); + checkArgument(location.scheme().get().equals("gs"), "Wrong scheme for S3 location: %s", location); + checkArgument(location.host().isPresent(), "No bucket for GCS location: %s", location); + checkArgument(location.userInfo().isEmpty(), "GCS location contains user info: %s", location); + checkArgument(location.port().isEmpty(), "GCS location contains port: %s", location); + checkArgument(!location.path().contains("#"), "GCS generation numbers are not supported: %s", location); + checkArgument(!location.path().contains("?"), "Invalid character '?': %s", location); + } + + public String scheme() + { + return location.scheme().orElseThrow(); + } + + public String bucket() + { + return location.host().orElseThrow(); + } + + public String path() + { + return location.path(); + } + + public String getBase() + { + return "%s://%s/".formatted(scheme(), bucket()); + } + + @Override + public String toString() + { + return location.toString(); + } +} diff --git a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsOutputFile.java b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsOutputFile.java new file mode 100644 index 000000000000..12344c9a0f98 --- /dev/null +++ b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsOutputFile.java @@ -0,0 +1,103 @@ +/* + * 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 io.trino.filesystem.gcs; + +import com.google.cloud.storage.Blob; +import com.google.cloud.storage.BlobId; +import com.google.cloud.storage.BlobInfo; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.Storage.BlobTargetOption; +import io.trino.filesystem.Location; +import io.trino.filesystem.TrinoOutputFile; +import io.trino.memory.context.AggregatedMemoryContext; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.FileAlreadyExistsException; + +import static com.google.common.base.Preconditions.checkArgument; +import static io.trino.filesystem.gcs.GcsUtils.getBlob; +import static io.trino.filesystem.gcs.GcsUtils.handleGcsException; +import static java.util.Objects.requireNonNull; + +public class GcsOutputFile + implements TrinoOutputFile +{ + private static final Storage.BlobTargetOption[] DOES_NOT_EXIST_TARGET_OPTION = {Storage.BlobTargetOption.doesNotExist()}; + private static final Storage.BlobTargetOption[] EMPTY_TARGET_OPTIONS = {}; + + private final GcsLocation location; + private final Storage storage; + private final long writeBlockSizeBytes; + + public GcsOutputFile(GcsLocation location, Storage storage, long writeBlockSizeBytes) + { + this.location = requireNonNull(location, "location is null"); + this.storage = requireNonNull(storage, "storage is null"); + checkArgument(writeBlockSizeBytes >= 0, "writeBlockSizeBytes is negative"); + this.writeBlockSizeBytes = writeBlockSizeBytes; + } + + @Override + public OutputStream create(AggregatedMemoryContext memoryContext) + throws IOException + { + return createOutputStream(memoryContext, false); + } + + @Override + public OutputStream createOrOverwrite(AggregatedMemoryContext memoryContext) + throws IOException + { + return createOutputStream(memoryContext, true); + } + + @Override + public OutputStream createExclusive(AggregatedMemoryContext memoryContext) + throws IOException + { + return create(memoryContext); + } + + private OutputStream createOutputStream(AggregatedMemoryContext memoryContext, boolean overwrite) + throws IOException + { + try { + BlobTargetOption[] blobTargetOptions = EMPTY_TARGET_OPTIONS; + if (!overwrite) { + if (!getBlob(storage, location).isEmpty()) { + throw new FileAlreadyExistsException("File %s already exists".formatted(location)); + } + blobTargetOptions = DOES_NOT_EXIST_TARGET_OPTION; + } + Blob blob = storage.create( + BlobInfo.newBuilder(BlobId.of(location.bucket(), location.path())).build(), + blobTargetOptions); + + return new GcsOutputStream(location, blob, memoryContext, writeBlockSizeBytes); + } + catch (FileAlreadyExistsException e) { + throw e; + } + catch (RuntimeException e) { + throw handleGcsException(e, "writing file", location); + } + } + + @Override + public Location location() + { + return location.location(); + } +} diff --git a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsOutputStream.java b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsOutputStream.java new file mode 100644 index 000000000000..9b081dc66912 --- /dev/null +++ b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsOutputStream.java @@ -0,0 +1,155 @@ +/* + * 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 io.trino.filesystem.gcs; + +import com.google.cloud.WriteChannel; +import com.google.cloud.storage.Blob; +import com.google.common.primitives.Ints; +import io.trino.memory.context.AggregatedMemoryContext; +import io.trino.memory.context.LocalMemoryContext; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.lang.Math.min; +import static java.util.Objects.requireNonNull; + +public class GcsOutputStream + extends OutputStream +{ + private static final int BUFFER_SIZE = 8192; + + private final GcsLocation location; + private final long writeBlockSizeBytes; + private final LocalMemoryContext memoryContext; + private final WriteChannel writeChannel; + private final ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE); + private long writtenBytes; + private boolean closed; + + public GcsOutputStream(GcsLocation location, Blob blob, AggregatedMemoryContext memoryContext, long writeBlockSizeBytes) + { + this.location = requireNonNull(location, "location is null"); + checkArgument(writeBlockSizeBytes >= 0, "writeBlockSizeBytes is negative"); + this.writeBlockSizeBytes = writeBlockSizeBytes; + this.memoryContext = memoryContext.newLocalMemoryContext(GcsOutputStream.class.getSimpleName()); + this.writeChannel = blob.writer(); + this.writeChannel.setChunkSize(Ints.saturatedCast(writeBlockSizeBytes)); + } + + @Override + public void write(int b) + throws IOException + { + ensureOpen(); + if (!buffer.hasRemaining()) { + flush(); + } + buffer.put((byte) b); + recordBytesWritten(1); + } + + @Override + public void write(byte[] buffer, int offset, int length) + throws IOException + { + ensureOpen(); + if (length > BUFFER_SIZE) { + writeDirect(ByteBuffer.wrap(buffer, offset, length)); + } + else { + if (length > this.buffer.remaining()) { + flush(); + } + this.buffer.put(buffer, offset, length); + recordBytesWritten(length); + } + } + + private void writeDirect(ByteBuffer buffer) + throws IOException + { + // Flush write buffer in case calls to write(int) are interleaved with calls to this function + flush(); + int bytesWritten = 0; + try { + bytesWritten = writeChannel.write(buffer); + if (bytesWritten != buffer.remaining()) { + throw new IOException("Unexpected bytes written length: %s should be %s".formatted(bytesWritten, buffer.remaining())); + } + } + catch (IOException e) { + throw new IOException("Error writing file: " + location, e); + } + recordBytesWritten(bytesWritten); + } + + private void ensureOpen() + throws IOException + { + if (closed) { + throw new IOException("Output stream closed: " + location); + } + } + + @Override + public void flush() + throws IOException + { + ensureOpen(); + if (buffer.position() > 0) { + buffer.flip(); + while (buffer.hasRemaining()) { + try { + // WriteChannel is buffered internally: see com.google.cloud.BaseWriteChannel + writeChannel.write(buffer); + } + catch (IOException e) { + throw new IOException("Error writing file: " + location, e); + } + } + buffer.clear(); + } + } + + @Override + public void close() + throws IOException + { + if (!closed) { + flush(); + closed = true; + try { + writeChannel.close(); + } + catch (IOException e) { + throw new IOException("Error closing file: " + location, e); + } + finally { + memoryContext.close(); + } + } + } + + private void recordBytesWritten(int size) + { + if (writtenBytes < writeBlockSizeBytes) { + // assume that there is only one pending block buffer, and that it grows as written bytes grow + memoryContext.setBytes(BUFFER_SIZE + min(writtenBytes + size, writeBlockSizeBytes)); + } + writtenBytes += size; + } +} diff --git a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsStorageFactory.java b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsStorageFactory.java new file mode 100644 index 000000000000..20db0b57b68a --- /dev/null +++ b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsStorageFactory.java @@ -0,0 +1,22 @@ +/* + * 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 io.trino.filesystem.gcs; + +import com.google.cloud.storage.Storage; +import io.trino.spi.security.ConnectorIdentity; + +public interface GcsStorageFactory +{ + Storage create(ConnectorIdentity identity); +} diff --git a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsUtils.java b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsUtils.java new file mode 100644 index 000000000000..bc1c90f32c7b --- /dev/null +++ b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsUtils.java @@ -0,0 +1,77 @@ +/* + * 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 io.trino.filesystem.gcs; + +import com.google.cloud.ReadChannel; +import com.google.cloud.storage.Blob; +import com.google.cloud.storage.BlobId; +import com.google.cloud.storage.Storage; +import io.trino.filesystem.Location; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Collection; +import java.util.Optional; +import java.util.OptionalLong; + +import static com.google.api.client.util.Preconditions.checkArgument; +import static com.google.cloud.storage.Blob.BlobSourceOption.shouldReturnRawInputStream; +import static java.util.Objects.requireNonNull; + +public class GcsUtils +{ + private GcsUtils() {} + + public static IOException handleGcsException(RuntimeException exception, String action, GcsLocation location) + throws IOException + { + throw new IOException("Error %s: %s".formatted(action, location), exception); + } + + public static IOException handleGcsException(RuntimeException exception, String action, Collection locations) + throws IOException + { + throw new IOException("Error %s: %s".formatted(action, locations), exception); + } + + public static ReadChannel getReadChannel(Blob blob, GcsLocation location, long position, int readBlockSize, OptionalLong limit) + throws IOException + { + long fileSize = requireNonNull(blob.getSize(), "blob size is null"); + if (position >= fileSize) { + throw new IOException("Cannot read at %s. File size is %s: %s".formatted(position, fileSize, location)); + } + // Enable shouldReturnRawInputStream: currently set by default but just to ensure the behavior is predictable + ReadChannel readChannel = blob.reader(shouldReturnRawInputStream(true)); + + readChannel.setChunkSize(readBlockSize); + readChannel.seek(position); + if (limit.isPresent()) { + return readChannel.limit(limit.getAsLong()); + } + return readChannel; + } + + public static Optional getBlob(Storage storage, GcsLocation location, Storage.BlobGetOption... blobGetOptions) + { + checkArgument(!location.path().isEmpty(), "Path for location %s is empty", location); + return Optional.ofNullable(storage.get(BlobId.of(location.bucket(), location.path()), blobGetOptions)); + } + + public static Blob getBlobOrThrow(Storage storage, GcsLocation location, Storage.BlobGetOption... blobGetOptions) + throws IOException + { + return getBlob(storage, location, blobGetOptions).orElseThrow(() -> new FileNotFoundException("File %s not found".formatted(location))); + } +} diff --git a/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/AbstractTestGcsFileSystem.java b/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/AbstractTestGcsFileSystem.java new file mode 100644 index 000000000000..805dea0176fc --- /dev/null +++ b/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/AbstractTestGcsFileSystem.java @@ -0,0 +1,150 @@ +/* + * 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 io.trino.filesystem.gcs; + +import com.google.cloud.storage.BucketInfo; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.testing.RemoteStorageHelper; +import io.trino.filesystem.AbstractTestTrinoFileSystem; +import io.trino.filesystem.Location; +import io.trino.filesystem.TrinoFileSystem; +import io.trino.spi.security.ConnectorIdentity; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.TestInstance; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Base64; +import java.util.concurrent.TimeUnit; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Objects.requireNonNull; +import static org.assertj.core.api.Assertions.assertThat; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public abstract class AbstractTestGcsFileSystem + extends AbstractTestTrinoFileSystem +{ + private TrinoFileSystem fileSystem; + private Location rootLocation; + private Storage storage; + private GcsFileSystemFactory gcsFileSystemFactory; + + protected static String getRequiredEnvironmentVariable(String name) + { + return requireNonNull(System.getenv(name), "Environment variable not set: " + name); + } + + protected void initialize(String gcpCredentialKey) + throws IOException + { + // Note: the account needs the following permissions: + // create/get/delete bucket + // create/get/list/delete blob + // For gcp testing this corresponds to the Cluster Storage Admin and Cluster Storage Object Admin roles + byte[] jsonKeyBytes = Base64.getDecoder().decode(gcpCredentialKey); + GcsFileSystemConfig config = new GcsFileSystemConfig().setJsonKey(new String(jsonKeyBytes, UTF_8)); + GcsStorageFactory storageFactory = new TestingGcsStorageFactory(config); + this.gcsFileSystemFactory = new GcsFileSystemFactory(config, storageFactory); + this.storage = storageFactory.create(ConnectorIdentity.ofUser("test")); + String bucket = RemoteStorageHelper.generateBucketName(); + storage.create(BucketInfo.of(bucket)); + this.rootLocation = Location.of("gs://%s/".formatted(bucket)); + this.fileSystem = gcsFileSystemFactory.create(ConnectorIdentity.ofUser("test")); + cleanupFiles(); + } + + @AfterAll + void tearDown() + throws Exception + { + try { + RemoteStorageHelper.forceDelete(storage, rootLocation.host().get(), 5, TimeUnit.SECONDS); + gcsFileSystemFactory.stop(); + } + finally { + fileSystem = null; + storage = null; + rootLocation = null; + gcsFileSystemFactory = null; + } + } + + @AfterEach + void afterEach() + throws IOException + { + cleanupFiles(); + } + + private void cleanupFiles() + throws IOException + { + fileSystem.deleteDirectory(getRootLocation()); + } + + @Override + protected boolean isHierarchical() + { + return false; + } + + @Override + protected TrinoFileSystem getFileSystem() + { + return fileSystem; + } + + @Override + protected Location getRootLocation() + { + return rootLocation; + } + + @Override + protected void verifyFileSystemIsEmpty() + { + String bucket = new GcsLocation(rootLocation).bucket(); + assertThat(storage.list(bucket).iterateAll()).isEmpty(); + } + + @Override + protected final boolean supportsRenameFile() + { + return false; + } + + private static class TestingGcsStorageFactory + implements GcsStorageFactory + { + private final Storage storage; + + public TestingGcsStorageFactory(GcsFileSystemConfig config) + { + requireNonNull(config, "config is null"); + InputStream inputStream = new ByteArrayInputStream(config.getJsonKey().getBytes(UTF_8)); + // Note: the default project id from the credentials file will be used. See StorageOptions.setProjectId() + RemoteStorageHelper helper = RemoteStorageHelper.create(null, inputStream); + this.storage = helper.getOptions().getService(); + } + + @Override + public Storage create(ConnectorIdentity identity) + { + return storage; + } + } +} diff --git a/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/TestGcsFileSystemConfig.java b/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/TestGcsFileSystemConfig.java new file mode 100644 index 000000000000..0a8dad86df75 --- /dev/null +++ b/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/TestGcsFileSystemConfig.java @@ -0,0 +1,99 @@ +/* + * 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 io.trino.filesystem.gcs; + +import com.google.common.collect.ImmutableMap; +import io.airlift.units.DataSize; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +import static io.airlift.configuration.testing.ConfigAssertions.assertFullMapping; +import static io.airlift.configuration.testing.ConfigAssertions.assertRecordedDefaults; +import static io.airlift.configuration.testing.ConfigAssertions.recordDefaults; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class TestGcsFileSystemConfig +{ + @Test + void testDefaults() + { + assertRecordedDefaults(recordDefaults(GcsFileSystemConfig.class) + .setReadBlockSize(DataSize.of(2, DataSize.Unit.MEGABYTE)) + .setWriteBlockSize(DataSize.of(16, DataSize.Unit.MEGABYTE)) + .setPageSize(100) + .setBatchSize(100) + .setProjectId(null) + .setUseGcsAccessToken(false) + .setJsonKey(null) + .setJsonKeyFilePath(null)); + } + + @Test + void testExplicitPropertyMappings() + throws IOException + { + Path jsonKeyFile = Files.createTempFile(null, null); + + Map properties = ImmutableMap.builder() + .put("gcs.read-block-size", "51MB") + .put("gcs.write-block-size", "52MB") + .put("gcs.page-size", "10") + .put("gcs.batch-size", "11") + .put("gcs.projectId", "project") + .put("gcs.use-access-token", "true") + .put("gcs.json-key", "{}") + .put("gcs.json-key-file-path", jsonKeyFile.toString()) + .buildOrThrow(); + + GcsFileSystemConfig expected = new GcsFileSystemConfig() + .setReadBlockSize(DataSize.of(51, DataSize.Unit.MEGABYTE)) + .setWriteBlockSize(DataSize.of(52, DataSize.Unit.MEGABYTE)) + .setPageSize(10) + .setBatchSize(11) + .setProjectId("project") + .setUseGcsAccessToken(true) + .setJsonKey("{}") + .setJsonKeyFilePath(jsonKeyFile.toString()); + assertFullMapping(properties, expected); + } + + @Test + public void testValidation() + { + assertThatThrownBy( + new GcsFileSystemConfig() + .setUseGcsAccessToken(true) + .setJsonKey("{}}")::validate) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Cannot specify 'hive.gcs.json-key' when 'hive.gcs.use-access-token' is set"); + + assertThatThrownBy( + new GcsFileSystemConfig() + .setUseGcsAccessToken(true) + .setJsonKeyFilePath("/dev/null")::validate) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Cannot specify 'hive.gcs.json-key-file-path' when 'hive.gcs.use-access-token' is set"); + + assertThatThrownBy( + new GcsFileSystemConfig() + .setJsonKey("{}") + .setJsonKeyFilePath("/dev/null")::validate) + .isInstanceOf(IllegalStateException.class) + .hasMessage("'hive.gcs.json-key' and 'hive.gcs.json-key-file-path' cannot be both set"); + } +} diff --git a/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/TestGcsFileSystemGcs.java b/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/TestGcsFileSystemGcs.java new file mode 100644 index 000000000000..b9a056e487fc --- /dev/null +++ b/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/TestGcsFileSystemGcs.java @@ -0,0 +1,31 @@ +/* + * 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 io.trino.filesystem.gcs; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.TestInstance; + +import java.io.IOException; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class TestGcsFileSystemGcs + extends AbstractTestGcsFileSystem +{ + @BeforeAll + void setup() + throws IOException + { + initialize(getRequiredEnvironmentVariable("GCP_CREDENTIALS_KEY")); + } +} diff --git a/lib/trino-filesystem-manager/pom.xml b/lib/trino-filesystem-manager/pom.xml index f81a732992f9..1798b5909033 100644 --- a/lib/trino-filesystem-manager/pom.xml +++ b/lib/trino-filesystem-manager/pom.xml @@ -47,6 +47,11 @@ trino-filesystem-azure + + io.trino + trino-filesystem-gcs + + io.trino trino-filesystem-s3 diff --git a/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/FileSystemConfig.java b/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/FileSystemConfig.java index 72e5bdfcee0f..13390b5d420b 100644 --- a/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/FileSystemConfig.java +++ b/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/FileSystemConfig.java @@ -20,6 +20,7 @@ public class FileSystemConfig private boolean hadoopEnabled = true; private boolean nativeAzureEnabled; private boolean nativeS3Enabled; + private boolean nativeGcsEnabled; public boolean isHadoopEnabled() { @@ -56,4 +57,16 @@ public FileSystemConfig setNativeS3Enabled(boolean nativeS3Enabled) this.nativeS3Enabled = nativeS3Enabled; return this; } + + public boolean isNativeGcsEnabled() + { + return nativeGcsEnabled; + } + + @Config("fs.native-gcs.enabled") + public FileSystemConfig setNativeGcsEnabled(boolean nativeGcsEnabled) + { + this.nativeGcsEnabled = nativeGcsEnabled; + return this; + } } diff --git a/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/FileSystemModule.java b/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/FileSystemModule.java index b366955b5287..535067fb74f3 100644 --- a/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/FileSystemModule.java +++ b/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/FileSystemModule.java @@ -22,6 +22,8 @@ import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.filesystem.azure.AzureFileSystemFactory; import io.trino.filesystem.azure.AzureFileSystemModule; +import io.trino.filesystem.gcs.GcsFileSystemFactory; +import io.trino.filesystem.gcs.GcsFileSystemModule; import io.trino.filesystem.hdfs.HdfsFileSystemFactory; import io.trino.filesystem.hdfs.HdfsFileSystemModule; import io.trino.filesystem.s3.S3FileSystemFactory; @@ -82,6 +84,14 @@ else if (config.isHadoopEnabled()) { else if (config.isHadoopEnabled()) { install(new HiveS3Module()); } + + if (config.isNativeGcsEnabled()) { + install(new GcsFileSystemModule()); + factories.addBinding("gs").to(GcsFileSystemFactory.class); + } + else { + install(new HiveGcsModule()); + } } @Provides diff --git a/lib/trino-filesystem-manager/src/test/java/io/trino/filesystem/manager/TestFileSystemConfig.java b/lib/trino-filesystem-manager/src/test/java/io/trino/filesystem/manager/TestFileSystemConfig.java index a9b9bad5dad0..4e765b70ccee 100644 --- a/lib/trino-filesystem-manager/src/test/java/io/trino/filesystem/manager/TestFileSystemConfig.java +++ b/lib/trino-filesystem-manager/src/test/java/io/trino/filesystem/manager/TestFileSystemConfig.java @@ -30,7 +30,8 @@ public void testDefaults() assertRecordedDefaults(recordDefaults(FileSystemConfig.class) .setHadoopEnabled(true) .setNativeAzureEnabled(false) - .setNativeS3Enabled(false)); + .setNativeS3Enabled(false) + .setNativeGcsEnabled(false)); } @Test @@ -40,12 +41,14 @@ public void testExplicitPropertyMappings() .put("fs.hadoop.enabled", "false") .put("fs.native-azure.enabled", "true") .put("fs.native-s3.enabled", "true") + .put("fs.native-gcs.enabled", "true") .buildOrThrow(); FileSystemConfig expected = new FileSystemConfig() .setHadoopEnabled(false) .setNativeAzureEnabled(true) - .setNativeS3Enabled(true); + .setNativeS3Enabled(true) + .setNativeGcsEnabled(true); assertFullMapping(properties, expected); } diff --git a/pom.xml b/pom.xml index fc8857e0b1de..2de19d7d8578 100644 --- a/pom.xml +++ b/pom.xml @@ -43,6 +43,7 @@ lib/trino-cache lib/trino-filesystem lib/trino-filesystem-azure + lib/trino-filesystem-gcs lib/trino-filesystem-manager lib/trino-filesystem-s3 lib/trino-geospatial-toolkit @@ -992,6 +993,12 @@ ${project.version} + + io.trino + trino-filesystem-gcs + ${project.version} + + io.trino trino-filesystem-manager @@ -2293,6 +2300,102 @@ shaded.parquet.it.unimi.dsi.fastutil + + + + com.google.cloud.bigdataoss + gcs-connector + + + io.opencensus + opencensus-proto + + + + opencensus/proto/agent/common/v1/common.proto + opencensus/proto/agent/metrics/v1/metrics_service.proto + opencensus/proto/agent/trace/v1/trace_service.proto + opencensus/proto/metrics/v1/metrics.proto + opencensus/proto/resource/v1/resource.proto + opencensus/proto/stats/v1/stats.proto + opencensus/proto/trace/v1/trace.proto + opencensus/proto/trace/v1/trace_config.proto + + + + + + io.grpc + grpc-services + + + org.alluxio + alluxio-shaded-client + + + + grpc/binlog/v1/binarylog.proto + grpc/health/v1/health.proto + grpc/reflection/v1alpha/reflection.proto + grpc/channelz/v1/channelz.proto + + + + + + com.google.android + annotations + + + org.alluxio + alluxio-shaded-client + + + + android.annotation.SuppressLint + android.annotation.TargetApi + + + + + + com.google.re2j + re2j + + + io.trino + re2j + + + + com.google.re2j + + + + + + org.alluxio + alluxio-shaded-client + + + com.google.protobuf + protobuf-java + + + + google/protobuf/any.proto + google/protobuf/api.proto + google/protobuf/descriptor.proto + google/protobuf/duration.proto + google/protobuf/empty.proto + google/protobuf/field_mask.proto + google/protobuf/source_context.proto + google/protobuf/struct.proto + google/protobuf/timestamp.proto + google/protobuf/type.proto + google/protobuf/wrappers.proto + + From 0a9f81fe23ef50d7ea39a517c9db863d324f321a Mon Sep 17 00:00:00 2001 From: Marius Grama Date: Tue, 24 Oct 2023 17:27:29 +0200 Subject: [PATCH 112/587] Add varchar to double coercion for hive tables --- .../plugin/hive/coercions/CoercionUtils.java | 3 + .../coercions/VarcharToDoubleCoercer.java | 53 ++++++++++++++ .../plugin/hive/orc/OrcTypeTranslator.java | 5 ++ .../plugin/hive/util/HiveCoercionPolicy.java | 1 + .../trino/plugin/hive/AbstractTestHive.java | 2 +- .../coercions/TestVarcharToDoubleCoercer.java | 72 +++++++++++++++++++ .../trino-hive/src/test/sql/create-test.sql | 2 +- .../product/hive/BaseTestHiveCoercion.java | 36 ++++++++++ .../TestHiveCoercionOnPartitionedTable.java | 4 ++ .../TestHiveCoercionOnUnpartitionedTable.java | 4 ++ 10 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/VarcharToDoubleCoercer.java create mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestVarcharToDoubleCoercer.java diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/CoercionUtils.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/CoercionUtils.java index c35938e76c67..affcf31e8879 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/CoercionUtils.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/CoercionUtils.java @@ -103,6 +103,9 @@ public static Type createTypeFromCoercer(TypeManager typeManager, HiveType fromH if (fromType instanceof VarcharType fromVarcharType && (toHiveType.equals(HIVE_BYTE) || toHiveType.equals(HIVE_SHORT) || toHiveType.equals(HIVE_INT) || toHiveType.equals(HIVE_LONG))) { return Optional.of(new VarcharToIntegerNumberCoercer<>(fromVarcharType, toType)); } + if (fromType instanceof VarcharType varcharType && toHiveType.equals(HIVE_DOUBLE)) { + return Optional.of(new VarcharToDoubleCoercer(varcharType, coercionContext.treatNaNAsNull())); + } if (fromType instanceof VarcharType varcharType && toType instanceof TimestampType timestampType) { if (timestampType.isShort()) { return Optional.of(new VarcharToShortTimestampCoercer(varcharType, timestampType)); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/VarcharToDoubleCoercer.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/VarcharToDoubleCoercer.java new file mode 100644 index 000000000000..160bf5c4f282 --- /dev/null +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/VarcharToDoubleCoercer.java @@ -0,0 +1,53 @@ +/* + * 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 io.trino.plugin.hive.coercions; + +import io.trino.spi.block.Block; +import io.trino.spi.block.BlockBuilder; +import io.trino.spi.type.DoubleType; +import io.trino.spi.type.VarcharType; + +import static io.trino.spi.type.DoubleType.DOUBLE; + +public class VarcharToDoubleCoercer + extends TypeCoercer +{ + private final boolean treatNaNAsNull; + + public VarcharToDoubleCoercer(VarcharType fromType, boolean treatNaNAsNull) + { + super(fromType, DOUBLE); + this.treatNaNAsNull = treatNaNAsNull; + } + + @Override + protected void applyCoercedValue(BlockBuilder blockBuilder, Block block, int position) + { + double doubleValue; + try { + doubleValue = Double.parseDouble(fromType.getSlice(block, position).toStringUtf8()); + } + catch (NumberFormatException e) { + blockBuilder.appendNull(); + return; + } + + if (Double.isNaN(doubleValue) && treatNaNAsNull) { + blockBuilder.appendNull(); + return; + } + DOUBLE.writeDouble(blockBuilder, doubleValue); + } +} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcTypeTranslator.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcTypeTranslator.java index d9ef312e7878..27e79d31299e 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcTypeTranslator.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcTypeTranslator.java @@ -21,7 +21,9 @@ import io.trino.plugin.hive.coercions.TimestampCoercer.VarcharToLongTimestampCoercer; import io.trino.plugin.hive.coercions.TimestampCoercer.VarcharToShortTimestampCoercer; import io.trino.plugin.hive.coercions.TypeCoercer; +import io.trino.plugin.hive.coercions.VarcharToDoubleCoercer; import io.trino.spi.type.DateType; +import io.trino.spi.type.DoubleType; import io.trino.spi.type.TimestampType; import io.trino.spi.type.Type; import io.trino.spi.type.VarcharType; @@ -55,6 +57,9 @@ private OrcTypeTranslator() {} if (toTrinoType instanceof DateType toDateType) { return Optional.of(new VarcharToDateCoercer(createUnboundedVarcharType(), toDateType)); } + if (toTrinoType instanceof DoubleType) { + return Optional.of(new VarcharToDoubleCoercer(createUnboundedVarcharType(), true)); + } return Optional.empty(); } if (fromOrcType == DOUBLE && toTrinoType instanceof VarcharType varcharType) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveCoercionPolicy.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveCoercionPolicy.java index 56aecfc755a4..d71579a4b72c 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveCoercionPolicy.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveCoercionPolicy.java @@ -66,6 +66,7 @@ private boolean canCoerce(HiveType fromHiveType, HiveType toHiveType, HiveTimest toHiveType.equals(HIVE_SHORT) || toHiveType.equals(HIVE_INT) || toHiveType.equals(HIVE_LONG) || + toHiveType.equals(HIVE_DOUBLE) || toHiveType.equals(HIVE_DATE) || toHiveType.equals(HIVE_TIMESTAMP); } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java index a1ecb6a12e2d..f598b557d4c0 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java @@ -2397,7 +2397,7 @@ public void testPartitionSchemaMismatch() } }) .isInstanceOf(TrinoException.class) - .hasMessageMatching(".*The column 't_data' in table '.*\\.trino_test_partition_schema_change' is declared as type 'double', but partition 'ds=2012-12-29' declared column 't_data' as type 'string'."); + .hasMessageMatching(".*The column 't_data' in table '.*\\.trino_test_partition_schema_change' is declared as type 'float', but partition 'ds=2012-12-29' declared column 't_data' as type 'string'."); } // TODO coercion of non-canonical values should be supported diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestVarcharToDoubleCoercer.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestVarcharToDoubleCoercer.java new file mode 100644 index 000000000000..6bdfd3455921 --- /dev/null +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestVarcharToDoubleCoercer.java @@ -0,0 +1,72 @@ +/* + * 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 io.trino.plugin.hive.coercions; + +import io.trino.plugin.hive.coercions.CoercionUtils.CoercionContext; +import io.trino.spi.block.Block; +import org.junit.jupiter.api.Test; + +import static io.airlift.slice.Slices.utf8Slice; +import static io.trino.plugin.hive.HiveTimestampPrecision.DEFAULT_PRECISION; +import static io.trino.plugin.hive.HiveType.toHiveType; +import static io.trino.plugin.hive.coercions.CoercionUtils.createCoercer; +import static io.trino.spi.predicate.Utils.blockToNativeValue; +import static io.trino.spi.predicate.Utils.nativeValueToBlock; +import static io.trino.spi.type.DoubleType.DOUBLE; +import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; +import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; +import static java.lang.Double.NEGATIVE_INFINITY; +import static java.lang.Double.NaN; +import static java.lang.Double.POSITIVE_INFINITY; +import static org.assertj.core.api.Assertions.assertThat; + +public class TestVarcharToDoubleCoercer +{ + @Test + public void testDoubleToVarcharCoercions() + { + // Below infinity + assertVarcharToDoubleCoercion("-1.7976931348623157e+310", NEGATIVE_INFINITY); + assertVarcharToDoubleCoercion("-Infinity", NEGATIVE_INFINITY); + assertVarcharToDoubleCoercion("1.12e+3", Double.parseDouble("1120.0")); + assertVarcharToDoubleCoercion("123456789.12345678", Double.parseDouble("123456789.12345678")); + assertVarcharToDoubleCoercion("123", Double.parseDouble("123")); + assertVarcharToDoubleCoercion("Infinity", POSITIVE_INFINITY); + assertVarcharToDoubleCoercion("+Infinity", POSITIVE_INFINITY); + // Above infinity + assertVarcharToDoubleCoercion("1.7976931348623157e+310", POSITIVE_INFINITY); + // Invalid string + assertVarcharToDoubleCoercion("Hello", null); + } + + @Test + public void testNaNToVarcharCoercions() + { + assertVarcharToDoubleCoercion("NaN", true, null); + assertVarcharToDoubleCoercion("NaN", false, NaN); + } + + private static void assertVarcharToDoubleCoercion(String actualValue, Double expectedValue) + { + assertVarcharToDoubleCoercion(actualValue, false, expectedValue); + } + + private static void assertVarcharToDoubleCoercion(String actualValue, boolean treatNaNAsNull, Double expectedValue) + { + Block coercedBlock = createCoercer(TESTING_TYPE_MANAGER, toHiveType(createUnboundedVarcharType()), toHiveType(DOUBLE), new CoercionContext(DEFAULT_PRECISION, treatNaNAsNull)).orElseThrow() + .apply(nativeValueToBlock(createUnboundedVarcharType(), utf8Slice(actualValue))); + assertThat(blockToNativeValue(DOUBLE, coercedBlock)) + .isEqualTo(expectedValue); + } +} diff --git a/plugin/trino-hive/src/test/sql/create-test.sql b/plugin/trino-hive/src/test/sql/create-test.sql index a2cff23dc757..bd7576a1a37d 100644 --- a/plugin/trino-hive/src/test/sql/create-test.sql +++ b/plugin/trino-hive/src/test/sql/create-test.sql @@ -228,7 +228,7 @@ DROP TABLE tmp_trino_test; ALTER TABLE trino_test_partition_schema_change ADD PARTITION (ds='2012-12-29'); INSERT OVERWRITE TABLE trino_test_partition_schema_change PARTITION (ds='2012-12-29') SELECT '123', '456' FROM trino_test_sequence; -ALTER TABLE trino_test_partition_schema_change REPLACE COLUMNS (t_data DOUBLE); +ALTER TABLE trino_test_partition_schema_change REPLACE COLUMNS (t_data FLOAT); INSERT OVERWRITE TABLE trino_test_partition_schema_change_non_canonical PARTITION (t_boolean='0') SELECT 'test' FROM trino_test_sequence LIMIT 100; diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/BaseTestHiveCoercion.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/BaseTestHiveCoercion.java index 23968ff043bf..387108803342 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/BaseTestHiveCoercion.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/BaseTestHiveCoercion.java @@ -138,6 +138,10 @@ protected void doTestHiveCoercion(HiveTableDefinition tableDefinition) "varchar_to_smaller_varchar", "varchar_to_date", "varchar_to_distant_date", + "varchar_to_double", + "string_to_double", + "varchar_to_double_infinity", + "varchar_to_special_double", "char_to_bigger_char", "char_to_smaller_char", "timestamp_to_string", @@ -215,6 +219,10 @@ protected void insertTableRows(String tableName, String floatToDoubleType) " 'abc', " + " '2023-09-28', " + " '8000-04-13', " + + " '1234.567', " + + " '1234.01234', " + + " 'Infinity'," + + " 'NaN'," + " 'abc', " + " 'abc', " + " TIMESTAMP '2121-07-15 15:30:12.123', " + @@ -266,6 +274,10 @@ protected void insertTableRows(String tableName, String floatToDoubleType) " '\uD83D\uDCB0\uD83D\uDCB0\uD83D\uDCB0', " + " '2023-09-27', " + " '1900-01-01', " + + " '-12345.6789', " + + " '0', " + + " '-Infinity'," + + " 'Invalid Double'," + " '\uD83D\uDCB0\uD83D\uDCB0\uD83D\uDCB0', " + " '\uD83D\uDCB0\uD83D\uDCB0\uD83D\uDCB0', " + " TIMESTAMP '1970-01-01 00:00:00.123', " + @@ -458,6 +470,18 @@ else if (getHiveVersionMajor() == 3 && isFormat.test("orc")) { .put("varchar_to_distant_date", ImmutableList.of( java.sql.Date.valueOf("8000-04-13"), java.sql.Date.valueOf("1900-01-01"))) + .put("varchar_to_double", ImmutableList.of( + 1234.567, + -12345.6789)) + .put("string_to_double", ImmutableList.of( + 1234.01234, + 0D)) + .put("varchar_to_double_infinity", ImmutableList.of( + Double.POSITIVE_INFINITY, + Double.NEGATIVE_INFINITY)) + .put("varchar_to_special_double", Arrays.asList( + coercedNaN == null ? null : Double.NaN, + null)) .put("char_to_bigger_char", ImmutableList.of( "abc ", "\uD83D\uDCB0\uD83D\uDCB0\uD83D\uDCB0 ")) @@ -879,6 +903,10 @@ private void assertProperAlteredTableSchema(String tableName) row("varchar_to_smaller_varchar", "varchar(2)"), row("varchar_to_date", "date"), row("varchar_to_distant_date", "date"), + row("varchar_to_double", "double"), + row("string_to_double", "double"), + row("varchar_to_double_infinity", "double"), + row("varchar_to_special_double", "double"), row("char_to_bigger_char", "char(4)"), row("char_to_smaller_char", "char(2)"), row("timestamp_to_string", "varchar"), @@ -946,6 +974,10 @@ private void assertColumnTypes( .put("varchar_to_smaller_varchar", VARCHAR) .put("varchar_to_date", DATE) .put("varchar_to_distant_date", DATE) + .put("varchar_to_double", DOUBLE) + .put("string_to_double", DOUBLE) + .put("varchar_to_double_infinity", DOUBLE) + .put("varchar_to_special_double", DOUBLE) .put("char_to_bigger_char", CHAR) .put("char_to_smaller_char", CHAR) .put("id", BIGINT) @@ -1012,6 +1044,10 @@ private static void alterTableColumnTypes(String tableName) onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN varchar_to_smaller_varchar varchar_to_smaller_varchar varchar(2)", tableName)); onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN varchar_to_date varchar_to_date date", tableName)); onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN varchar_to_distant_date varchar_to_distant_date date", tableName)); + onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN varchar_to_double varchar_to_double double", tableName)); + onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN string_to_double string_to_double double", tableName)); + onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN varchar_to_double_infinity varchar_to_double_infinity double", tableName)); + onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN varchar_to_special_double varchar_to_special_double double", tableName)); onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN char_to_bigger_char char_to_bigger_char char(4)", tableName)); onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN char_to_smaller_char char_to_smaller_char char(2)", tableName)); onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN timestamp_to_string timestamp_to_string string", tableName)); diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnPartitionedTable.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnPartitionedTable.java index 05af7f4f79a3..980b2c12afc6 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnPartitionedTable.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnPartitionedTable.java @@ -142,6 +142,10 @@ private static HiveTableDefinition.HiveTableDefinitionBuilder tableDefinitionBui " varchar_to_smaller_varchar VARCHAR(3)," + " varchar_to_date VARCHAR(10)," + " varchar_to_distant_date VARCHAR(12)," + + " varchar_to_double VARCHAR(40)," + + " string_to_double STRING," + + " varchar_to_double_infinity VARCHAR(40)," + + " varchar_to_special_double VARCHAR(40)," + " char_to_bigger_char CHAR(3)," + " char_to_smaller_char CHAR(3)," + " timestamp_to_string TIMESTAMP," + diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnUnpartitionedTable.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnUnpartitionedTable.java index c32aab18ad22..d082f70c5fe3 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnUnpartitionedTable.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnUnpartitionedTable.java @@ -91,6 +91,10 @@ varchar_to_bigger_varchar VARCHAR(3), varchar_to_smaller_varchar VARCHAR(3), varchar_to_date VARCHAR(10), varchar_to_distant_date VARCHAR(12), + varchar_to_double VARCHAR(40), + string_to_double STRING, + varchar_to_double_infinity VARCHAR(40), + varchar_to_special_double VARCHAR(40), char_to_bigger_char CHAR(3), char_to_smaller_char CHAR(3), timestamp_to_string TIMESTAMP, From bd45df74729ff737f0974860bba2b28fa935e2d7 Mon Sep 17 00:00:00 2001 From: Marius Grama Date: Tue, 24 Oct 2023 23:05:36 +0200 Subject: [PATCH 113/587] Add numeric integer to double coercion for hive tables --- .../plugin/hive/coercions/CoercionUtils.java | 30 +++++-- .../IntegerNumberToDoubleCoercer.java | 37 +++++++++ .../plugin/hive/orc/OrcTypeTranslator.java | 23 ++++++ .../plugin/hive/util/HiveCoercionPolicy.java | 9 +- .../TestIntegerNumberToDoubleCoercer.java | 82 +++++++++++++++++++ .../product/hive/BaseTestHiveCoercion.java | 48 +++++++++++ .../TestHiveCoercionOnPartitionedTable.java | 4 + .../TestHiveCoercionOnUnpartitionedTable.java | 4 + 8 files changed, 228 insertions(+), 9 deletions(-) create mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/IntegerNumberToDoubleCoercer.java create mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestIntegerNumberToDoubleCoercer.java diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/CoercionUtils.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/CoercionUtils.java index affcf31e8879..67865c55fbf8 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/CoercionUtils.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/CoercionUtils.java @@ -130,14 +130,32 @@ public static Type createTypeFromCoercer(TypeManager typeManager, HiveType fromH } return Optional.empty(); } - if (fromHiveType.equals(HIVE_BYTE) && (toHiveType.equals(HIVE_SHORT) || toHiveType.equals(HIVE_INT) || toHiveType.equals(HIVE_LONG))) { - return Optional.of(new IntegerNumberUpscaleCoercer<>(fromType, toType)); + if (fromHiveType.equals(HIVE_BYTE)) { + if (toHiveType.equals(HIVE_SHORT) || toHiveType.equals(HIVE_INT) || toHiveType.equals(HIVE_LONG)) { + return Optional.of(new IntegerNumberUpscaleCoercer<>(fromType, toType)); + } + if (toHiveType.equals(HIVE_DOUBLE)) { + return Optional.of(new IntegerNumberToDoubleCoercer<>(fromType)); + } } - if (fromHiveType.equals(HIVE_SHORT) && (toHiveType.equals(HIVE_INT) || toHiveType.equals(HIVE_LONG))) { - return Optional.of(new IntegerNumberUpscaleCoercer<>(fromType, toType)); + if (fromHiveType.equals(HIVE_SHORT)) { + if (toHiveType.equals(HIVE_INT) || toHiveType.equals(HIVE_LONG)) { + return Optional.of(new IntegerNumberUpscaleCoercer<>(fromType, toType)); + } + if (toHiveType.equals(HIVE_DOUBLE)) { + return Optional.of(new IntegerNumberToDoubleCoercer<>(fromType)); + } + } + if (fromHiveType.equals(HIVE_INT)) { + if (toHiveType.equals(HIVE_LONG)) { + return Optional.of(new IntegerToBigintCoercer()); + } + if (toHiveType.equals(HIVE_DOUBLE)) { + return Optional.of(new IntegerNumberToDoubleCoercer<>(fromType)); + } } - if (fromHiveType.equals(HIVE_INT) && toHiveType.equals(HIVE_LONG)) { - return Optional.of(new IntegerToBigintCoercer()); + if (fromHiveType.equals(HIVE_LONG) && toHiveType.equals(HIVE_DOUBLE)) { + return Optional.of(new IntegerNumberToDoubleCoercer<>(fromType)); } if (fromHiveType.equals(HIVE_FLOAT) && toHiveType.equals(HIVE_DOUBLE)) { return Optional.of(new FloatToDoubleCoercer()); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/IntegerNumberToDoubleCoercer.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/IntegerNumberToDoubleCoercer.java new file mode 100644 index 000000000000..a1b0aebf113c --- /dev/null +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/IntegerNumberToDoubleCoercer.java @@ -0,0 +1,37 @@ +/* + * 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 io.trino.plugin.hive.coercions; + +import io.trino.spi.block.Block; +import io.trino.spi.block.BlockBuilder; +import io.trino.spi.type.DoubleType; +import io.trino.spi.type.Type; + +import static io.trino.spi.type.DoubleType.DOUBLE; + +public class IntegerNumberToDoubleCoercer + extends TypeCoercer +{ + public IntegerNumberToDoubleCoercer(F fromType) + { + super(fromType, DOUBLE); + } + + @Override + protected void applyCoercedValue(BlockBuilder blockBuilder, Block block, int position) + { + DOUBLE.writeDouble(blockBuilder, fromType.getLong(block, position)); + } +} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcTypeTranslator.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcTypeTranslator.java index 27e79d31299e..ff2449ce535e 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcTypeTranslator.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcTypeTranslator.java @@ -17,6 +17,7 @@ import io.trino.plugin.hive.coercions.BooleanCoercer.BooleanToVarcharCoercer; import io.trino.plugin.hive.coercions.DateCoercer.VarcharToDateCoercer; import io.trino.plugin.hive.coercions.DoubleToVarcharCoercer; +import io.trino.plugin.hive.coercions.IntegerNumberToDoubleCoercer; import io.trino.plugin.hive.coercions.TimestampCoercer.LongTimestampToVarcharCoercer; import io.trino.plugin.hive.coercions.TimestampCoercer.VarcharToLongTimestampCoercer; import io.trino.plugin.hive.coercions.TimestampCoercer.VarcharToShortTimestampCoercer; @@ -31,11 +32,19 @@ import java.util.Optional; import static io.trino.orc.metadata.OrcType.OrcTypeKind.BOOLEAN; +import static io.trino.orc.metadata.OrcType.OrcTypeKind.BYTE; import static io.trino.orc.metadata.OrcType.OrcTypeKind.DOUBLE; +import static io.trino.orc.metadata.OrcType.OrcTypeKind.INT; +import static io.trino.orc.metadata.OrcType.OrcTypeKind.LONG; +import static io.trino.orc.metadata.OrcType.OrcTypeKind.SHORT; import static io.trino.orc.metadata.OrcType.OrcTypeKind.STRING; import static io.trino.orc.metadata.OrcType.OrcTypeKind.TIMESTAMP; import static io.trino.orc.metadata.OrcType.OrcTypeKind.VARCHAR; +import static io.trino.spi.type.BigintType.BIGINT; +import static io.trino.spi.type.IntegerType.INTEGER; +import static io.trino.spi.type.SmallintType.SMALLINT; import static io.trino.spi.type.TimestampType.TIMESTAMP_NANOS; +import static io.trino.spi.type.TinyintType.TINYINT; import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; public final class OrcTypeTranslator @@ -68,6 +77,20 @@ private OrcTypeTranslator() {} if (fromOrcType == BOOLEAN && toTrinoType instanceof VarcharType varcharType) { return Optional.of(new BooleanToVarcharCoercer(varcharType)); } + if (toTrinoType instanceof DoubleType) { + if (fromOrcType == BYTE) { + return Optional.of(new IntegerNumberToDoubleCoercer<>(TINYINT)); + } + if (fromOrcType == SHORT) { + return Optional.of(new IntegerNumberToDoubleCoercer<>(SMALLINT)); + } + if (fromOrcType == INT) { + return Optional.of(new IntegerNumberToDoubleCoercer<>(INTEGER)); + } + if (fromOrcType == LONG) { + return Optional.of(new IntegerNumberToDoubleCoercer<>(BIGINT)); + } + } return Optional.empty(); } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveCoercionPolicy.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveCoercionPolicy.java index d71579a4b72c..857d64eec157 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveCoercionPolicy.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveCoercionPolicy.java @@ -84,13 +84,16 @@ private boolean canCoerce(HiveType fromHiveType, HiveType toHiveType, HiveTimest fromType instanceof DecimalType; } if (fromHiveType.equals(HIVE_BYTE)) { - return toHiveType.equals(HIVE_SHORT) || toHiveType.equals(HIVE_INT) || toHiveType.equals(HIVE_LONG); + return toHiveType.equals(HIVE_SHORT) || toHiveType.equals(HIVE_INT) || toHiveType.equals(HIVE_LONG) || toHiveType.equals(HIVE_DOUBLE); } if (fromHiveType.equals(HIVE_SHORT)) { - return toHiveType.equals(HIVE_INT) || toHiveType.equals(HIVE_LONG); + return toHiveType.equals(HIVE_INT) || toHiveType.equals(HIVE_LONG) || toHiveType.equals(HIVE_DOUBLE); } if (fromHiveType.equals(HIVE_INT)) { - return toHiveType.equals(HIVE_LONG); + return toHiveType.equals(HIVE_LONG) || toHiveType.equals(HIVE_DOUBLE); + } + if (fromHiveType.equals(HIVE_LONG)) { + return toHiveType.equals(HIVE_DOUBLE); } if (fromHiveType.equals(HIVE_FLOAT)) { return toHiveType.equals(HIVE_DOUBLE) || toType instanceof DecimalType; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestIntegerNumberToDoubleCoercer.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestIntegerNumberToDoubleCoercer.java new file mode 100644 index 000000000000..7981ad089633 --- /dev/null +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestIntegerNumberToDoubleCoercer.java @@ -0,0 +1,82 @@ +/* + * 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 io.trino.plugin.hive.coercions; + +import io.trino.spi.block.Block; +import io.trino.spi.type.Type; +import org.junit.jupiter.api.Test; + +import static io.trino.plugin.hive.HiveTimestampPrecision.DEFAULT_PRECISION; +import static io.trino.plugin.hive.HiveType.toHiveType; +import static io.trino.plugin.hive.coercions.CoercionUtils.createCoercer; +import static io.trino.spi.predicate.Utils.blockToNativeValue; +import static io.trino.spi.predicate.Utils.nativeValueToBlock; +import static io.trino.spi.type.BigintType.BIGINT; +import static io.trino.spi.type.DoubleType.DOUBLE; +import static io.trino.spi.type.IntegerType.INTEGER; +import static io.trino.spi.type.SmallintType.SMALLINT; +import static io.trino.spi.type.TinyintType.TINYINT; +import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; +import static org.assertj.core.api.Assertions.assertThat; + +public class TestIntegerNumberToDoubleCoercer +{ + @Test + public void testTinyintToDoubleCoercion() + { + assertDoubleCoercion(TINYINT, 0L, 0D); + assertDoubleCoercion(TINYINT, 12L, 12D); + assertDoubleCoercion(TINYINT, -12L, -12D); + assertDoubleCoercion(TINYINT, (long) Byte.MAX_VALUE, 127D); + assertDoubleCoercion(TINYINT, (long) Byte.MIN_VALUE, -128D); + } + + @Test + public void testSmallintToDoubleCoercion() + { + assertDoubleCoercion(SMALLINT, 0L, 0D); + assertDoubleCoercion(SMALLINT, 128L, 128D); + assertDoubleCoercion(SMALLINT, -128L, -128D); + assertDoubleCoercion(SMALLINT, (long) Short.MAX_VALUE, 32767D); + assertDoubleCoercion(SMALLINT, (long) Short.MIN_VALUE, -32768D); + } + + @Test + public void testIntToDoubleCoercion() + { + assertDoubleCoercion(INTEGER, 0L, 0D); + assertDoubleCoercion(INTEGER, 128L, 128D); + assertDoubleCoercion(INTEGER, -128L, -128D); + assertDoubleCoercion(INTEGER, (long) Integer.MAX_VALUE, 2147483647D); + assertDoubleCoercion(INTEGER, (long) Integer.MIN_VALUE, -2147483648D); + } + + @Test + public void testBigintToDoubleCoercion() + { + assertDoubleCoercion(BIGINT, 0L, 0D); + assertDoubleCoercion(BIGINT, 128L, 128D); + assertDoubleCoercion(BIGINT, -128L, -128D); + assertDoubleCoercion(BIGINT, Long.MAX_VALUE, 9223372036854775807D); + assertDoubleCoercion(BIGINT, Long.MIN_VALUE, -9223372036854775808D); + } + + private static void assertDoubleCoercion(Type fromType, Object valueToBeCoerced, Double expectedValue) + { + Block coercedValue = createCoercer(TESTING_TYPE_MANAGER, toHiveType(fromType), toHiveType(DOUBLE), new CoercionUtils.CoercionContext(DEFAULT_PRECISION, true)).orElseThrow() + .apply(nativeValueToBlock(fromType, valueToBeCoerced)); + assertThat(blockToNativeValue(DOUBLE, coercedValue)) + .isEqualTo(expectedValue); + } +} diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/BaseTestHiveCoercion.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/BaseTestHiveCoercion.java index 387108803342..c7ae6580e599 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/BaseTestHiveCoercion.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/BaseTestHiveCoercion.java @@ -103,9 +103,13 @@ protected void doTestHiveCoercion(HiveTableDefinition tableDefinition) "tinyint_to_smallint", "tinyint_to_int", "tinyint_to_bigint", + "tinyint_to_double", "smallint_to_int", "smallint_to_bigint", + "smallint_to_double", "int_to_bigint", + "int_to_double", + "bigint_to_double", "bigint_to_varchar", "float_to_double", "double_to_float", @@ -184,9 +188,13 @@ protected void insertTableRows(String tableName, String floatToDoubleType) " TINYINT '-1', " + " TINYINT '2', " + " TINYINT '-3', " + + " TINYINT '4', " + " SMALLINT '100', " + " SMALLINT '-101', " + + " SMALLINT '1024', " + " INTEGER '2323', " + + " INTEGER '16384', " + + " 1234567890, " + " 12345, " + " REAL '0.5', " + " DOUBLE '0.5', " + @@ -239,9 +247,13 @@ protected void insertTableRows(String tableName, String floatToDoubleType) " TINYINT '1', " + " TINYINT '-2', " + " NULL, " + + " TINYINT '-4', " + " SMALLINT '-100', " + " SMALLINT '101', " + + " SMALLINT '-1024', " + " INTEGER '-2323', " + + " INTEGER '-16384', " + + " -1234567890, " + " -12345, " + " REAL '-1.5', " + " DOUBLE '-1.5', " + @@ -377,15 +389,27 @@ else if (getHiveVersionMajor() == 3 && isFormat.test("orc")) { .put("tinyint_to_bigint", Arrays.asList( -3L, null)) + .put("tinyint_to_double", Arrays.asList( + -4D, + 4D)) .put("smallint_to_int", ImmutableList.of( 100, -100)) .put("smallint_to_bigint", ImmutableList.of( -101L, 101L)) + .put("smallint_to_double", ImmutableList.of( + -1024D, + 1024D)) .put("int_to_bigint", ImmutableList.of( 2323L, -2323L)) + .put("int_to_double", ImmutableList.of( + -16384D, + 16384D)) + .put("bigint_to_double", ImmutableList.of( + -1234567890D, + 1234567890D)) .put("bigint_to_varchar", ImmutableList.of( "12345", "-12345")) @@ -746,8 +770,12 @@ protected Map expectedExceptionsWithHiveContext() .put(columnContext("1.1", "parquet", "list_to_list"), "org.apache.hadoop.io.LongWritable cannot be cast to org.apache.hadoop.hive.serde2.io.ShortWritable") .put(columnContext("1.1", "parquet", "map_to_map"), "org.apache.hadoop.io.LongWritable cannot be cast to org.apache.hadoop.hive.serde2.io.ByteWritable") .put(columnContext("1.1", "parquet", "tinyint_to_bigint"), "org.apache.hadoop.io.LongWritable cannot be cast to org.apache.hadoop.hive.serde2.io.ByteWritable") + .put(columnContext("1.1", "parquet", "tinyint_to_double"), "org.apache.hadoop.io.DoubleWritable cannot be cast to org.apache.hadoop.hive.serde2.io.ByteWritable") .put(columnContext("1.1", "parquet", "smallint_to_bigint"), "org.apache.hadoop.io.LongWritable cannot be cast to org.apache.hadoop.hive.serde2.io.ShortWritable") + .put(columnContext("1.1", "parquet", "smallint_to_double"), "org.apache.hadoop.io.DoubleWritable cannot be cast to org.apache.hadoop.hive.serde2.io.ShortWritable") .put(columnContext("1.1", "parquet", "int_to_bigint"), "org.apache.hadoop.io.LongWritable cannot be cast to org.apache.hadoop.io.IntWritable") + .put(columnContext("1.1", "parquet", "int_to_double"), "org.apache.hadoop.io.DoubleWritable cannot be cast to org.apache.hadoop.io.IntWritable") + .put(columnContext("1.1", "parquet", "bigint_to_double"), "org.apache.hadoop.io.DoubleWritable cannot be cast to org.apache.hadoop.io.LongWritable") // Rcbinary .put(columnContext("1.1", "rcbinary", "row_to_row"), "java.util.ArrayList cannot be cast to org.apache.hadoop.hive.serde2.lazybinary.LazyBinaryStruct") .put(columnContext("1.1", "rcbinary", "list_to_list"), "java.util.ArrayList cannot be cast to org.apache.hadoop.hive.serde2.lazybinary.LazyBinaryArray") @@ -775,8 +803,12 @@ protected Map expectedExceptionsWithHiveContext() .put(columnContext("2.1", "parquet", "list_to_list"), "org.apache.hadoop.io.LongWritable cannot be cast to org.apache.hadoop.hive.serde2.io.ShortWritable") .put(columnContext("2.1", "parquet", "map_to_map"), "org.apache.hadoop.io.LongWritable cannot be cast to org.apache.hadoop.hive.serde2.io.ByteWritable") .put(columnContext("2.1", "parquet", "tinyint_to_bigint"), "org.apache.hadoop.io.LongWritable cannot be cast to org.apache.hadoop.hive.serde2.io.ByteWritable") + .put(columnContext("2.1", "parquet", "tinyint_to_double"), "org.apache.hadoop.io.DoubleWritable cannot be cast to org.apache.hadoop.hive.serde2.io.ByteWritable") .put(columnContext("2.1", "parquet", "smallint_to_bigint"), "org.apache.hadoop.io.LongWritable cannot be cast to org.apache.hadoop.hive.serde2.io.ShortWritable") + .put(columnContext("2.1", "parquet", "smallint_to_double"), "org.apache.hadoop.io.DoubleWritable cannot be cast to org.apache.hadoop.hive.serde2.io.ShortWritable") .put(columnContext("2.1", "parquet", "int_to_bigint"), "org.apache.hadoop.io.LongWritable cannot be cast to org.apache.hadoop.io.IntWritable") + .put(columnContext("2.1", "parquet", "int_to_double"), "org.apache.hadoop.io.DoubleWritable cannot be cast to org.apache.hadoop.io.IntWritable") + .put(columnContext("2.1", "parquet", "bigint_to_double"), "org.apache.hadoop.io.DoubleWritable cannot be cast to org.apache.hadoop.io.LongWritable") // Rcbinary .put(columnContext("2.1", "rcbinary", "row_to_row"), "java.util.ArrayList cannot be cast to org.apache.hadoop.hive.serde2.lazybinary.LazyBinaryStruct") .put(columnContext("2.1", "rcbinary", "list_to_list"), "java.util.ArrayList cannot be cast to org.apache.hadoop.hive.serde2.lazybinary.LazyBinaryArray") @@ -788,8 +820,12 @@ protected Map expectedExceptionsWithHiveContext() .put(columnContext("3.1", "parquet", "list_to_list"), "org.apache.hadoop.io.LongWritable cannot be cast to org.apache.hadoop.hive.serde2.io.ShortWritable") .put(columnContext("3.1", "parquet", "map_to_map"), "org.apache.hadoop.io.LongWritable cannot be cast to org.apache.hadoop.hive.serde2.io.ByteWritable") .put(columnContext("3.1", "parquet", "tinyint_to_bigint"), "org.apache.hadoop.io.LongWritable cannot be cast to org.apache.hadoop.hive.serde2.io.ByteWritable") + .put(columnContext("3.1", "parquet", "tinyint_to_double"), "org.apache.hadoop.io.DoubleWritable cannot be cast to org.apache.hadoop.hive.serde2.io.ByteWritable") .put(columnContext("3.1", "parquet", "smallint_to_bigint"), "org.apache.hadoop.io.LongWritable cannot be cast to org.apache.hadoop.hive.serde2.io.ShortWritable") + .put(columnContext("3.1", "parquet", "smallint_to_double"), "org.apache.hadoop.io.DoubleWritable cannot be cast to org.apache.hadoop.hive.serde2.io.ShortWritable") .put(columnContext("3.1", "parquet", "int_to_bigint"), "org.apache.hadoop.io.LongWritable cannot be cast to org.apache.hadoop.io.IntWritable") + .put(columnContext("3.1", "parquet", "int_to_double"), "org.apache.hadoop.io.DoubleWritable cannot be cast to org.apache.hadoop.io.IntWritable") + .put(columnContext("3.1", "parquet", "bigint_to_double"), "org.apache.hadoop.io.DoubleWritable cannot be cast to org.apache.hadoop.io.LongWritable") // Rcbinary .put(columnContext("3.1", "rcbinary", "row_to_row"), "java.util.ArrayList cannot be cast to org.apache.hadoop.hive.serde2.lazybinary.LazyBinaryStruct") .put(columnContext("3.1", "rcbinary", "list_to_list"), "java.util.ArrayList cannot be cast to org.apache.hadoop.hive.serde2.lazybinary.LazyBinaryArray") @@ -868,9 +904,13 @@ private void assertProperAlteredTableSchema(String tableName) row("tinyint_to_smallint", "smallint"), row("tinyint_to_int", "integer"), row("tinyint_to_bigint", "bigint"), + row("tinyint_to_double", "double"), row("smallint_to_int", "integer"), row("smallint_to_bigint", "bigint"), + row("smallint_to_double", "double"), row("int_to_bigint", "bigint"), + row("int_to_double", "double"), + row("bigint_to_double", "double"), row("bigint_to_varchar", "varchar"), row("float_to_double", "double"), row("double_to_float", floatType), @@ -939,9 +979,13 @@ private void assertColumnTypes( .put("tinyint_to_smallint", SMALLINT) .put("tinyint_to_int", INTEGER) .put("tinyint_to_bigint", BIGINT) + .put("tinyint_to_double", DOUBLE) .put("smallint_to_int", INTEGER) .put("smallint_to_bigint", BIGINT) + .put("smallint_to_double", DOUBLE) .put("int_to_bigint", BIGINT) + .put("int_to_double", DOUBLE) + .put("bigint_to_double", DOUBLE) .put("bigint_to_varchar", VARCHAR) .put("float_to_double", DOUBLE) .put("double_to_float", floatType) @@ -1009,9 +1053,13 @@ private static void alterTableColumnTypes(String tableName) onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN tinyint_to_smallint tinyint_to_smallint smallint", tableName)); onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN tinyint_to_int tinyint_to_int int", tableName)); onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN tinyint_to_bigint tinyint_to_bigint bigint", tableName)); + onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN tinyint_to_double tinyint_to_double double", tableName)); onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN smallint_to_int smallint_to_int int", tableName)); onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN smallint_to_bigint smallint_to_bigint bigint", tableName)); + onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN smallint_to_double smallint_to_double double", tableName)); onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN int_to_bigint int_to_bigint bigint", tableName)); + onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN int_to_double int_to_double double", tableName)); + onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN bigint_to_double bigint_to_double double", tableName)); onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN bigint_to_varchar bigint_to_varchar string", tableName)); onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN float_to_double float_to_double double", tableName)); onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN double_to_float double_to_float %s", tableName, floatType)); diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnPartitionedTable.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnPartitionedTable.java index 980b2c12afc6..709d10125f72 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnPartitionedTable.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnPartitionedTable.java @@ -107,9 +107,13 @@ private static HiveTableDefinition.HiveTableDefinitionBuilder tableDefinitionBui " tinyint_to_smallint TINYINT," + " tinyint_to_int TINYINT," + " tinyint_to_bigint TINYINT," + + " tinyint_to_double TINYINT," + " smallint_to_int SMALLINT," + " smallint_to_bigint SMALLINT," + + " smallint_to_double SMALLINT," + " int_to_bigint INT," + + " int_to_double INT," + + " bigint_to_double BIGINT," + " bigint_to_varchar BIGINT," + " float_to_double " + floatType + "," + " double_to_float DOUBLE," + diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnUnpartitionedTable.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnUnpartitionedTable.java index d082f70c5fe3..cca6f5b05fb8 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnUnpartitionedTable.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnUnpartitionedTable.java @@ -56,9 +56,13 @@ private static HiveTableDefinition.HiveTableDefinitionBuilder tableDefinitionBui tinyint_to_smallint TINYINT, tinyint_to_int TINYINT, tinyint_to_bigint TINYINT, + tinyint_to_double TINYINT, smallint_to_int SMALLINT, smallint_to_bigint SMALLINT, + smallint_to_double SMALLINT, int_to_bigint INT, + int_to_double INT, + bigint_to_double BIGINT, bigint_to_varchar BIGINT, float_to_double FLOAT, double_to_float DOUBLE, From 4ab5a303866313ef00bc20e18a22bec654958b64 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Mon, 30 Oct 2023 18:06:22 -0700 Subject: [PATCH 114/587] Execute JUnit tests in parallel --- .../client/auth/external/TestExternalAuthenticator.java | 3 +++ .../src/test/java/io/trino/jdbc/TestJdbcConnection.java | 3 +++ .../test/java/io/trino/jdbc/TestJdbcPreparedStatement.java | 3 +++ .../src/test/java/io/trino/jdbc/TestJdbcStatement.java | 3 +++ .../src/test/java/io/trino/jdbc/TestTrinoDriver.java | 3 +++ .../src/test/java/io/trino/jdbc/TestTrinoDriverAuth.java | 3 +++ .../java/io/trino/jdbc/TestTrinoDriverImpersonateUser.java | 3 +++ .../trino-main/src/test/java/io/trino/TestHiddenColumns.java | 3 +++ .../src/test/java/io/trino/cost/BaseStatsCalculatorTest.java | 3 +++ .../src/test/java/io/trino/cost/TestCostCalculator.java | 3 +++ .../src/test/java/io/trino/cost/TestStatsCalculator.java | 3 +++ .../io/trino/exchange/TestExchangeSourceOutputSelector.java | 3 +++ .../test/java/io/trino/execution/BaseTestSqlTaskManager.java | 3 +++ .../src/test/java/io/trino/execution/TestCallTask.java | 3 +++ .../src/test/java/io/trino/execution/TestCommitTask.java | 3 +++ .../trino/execution/TestCreateTableColumnTypeCoercion.java | 3 +++ .../src/test/java/io/trino/execution/TestDeallocateTask.java | 3 +++ .../test/java/io/trino/execution/TestPlannerWarnings.java | 3 +++ .../src/test/java/io/trino/execution/TestPrepareTask.java | 3 +++ .../test/java/io/trino/execution/TestQueryStateMachine.java | 3 +++ .../test/java/io/trino/execution/TestResetSessionTask.java | 3 +++ .../src/test/java/io/trino/execution/TestRollbackTask.java | 3 +++ .../src/test/java/io/trino/execution/TestSetPathTask.java | 3 +++ .../src/test/java/io/trino/execution/TestSetRoleTask.java | 3 +++ .../io/trino/execution/TestSetSessionAuthorizationTask.java | 3 +++ .../src/test/java/io/trino/execution/TestSetSessionTask.java | 3 +++ .../test/java/io/trino/execution/TestSetTimeZoneTask.java | 3 +++ .../src/test/java/io/trino/execution/TestSqlStage.java | 3 +++ .../src/test/java/io/trino/execution/TestSqlTask.java | 3 +++ .../test/java/io/trino/execution/TestStageStateMachine.java | 3 +++ .../java/io/trino/execution/TestStartTransactionTask.java | 3 +++ .../src/test/java/io/trino/execution/TestStateMachine.java | 3 +++ .../io/trino/execution/buffer/TestArbitraryOutputBuffer.java | 3 +++ .../io/trino/execution/buffer/TestBroadcastOutputBuffer.java | 3 +++ .../test/java/io/trino/execution/buffer/TestPagesSerde.java | 3 +++ .../trino/execution/buffer/TestPartitionedOutputBuffer.java | 3 +++ .../trino/execution/scheduler/TestFixedCountScheduler.java | 3 +++ .../scheduler/TestMultiSourcePartitionedScheduler.java | 3 +++ .../execution/scheduler/TestSourcePartitionedScheduler.java | 3 +++ .../scheduler/faulttolerant/TestEventDrivenTaskSource.java | 3 +++ .../io/trino/metadata/TestInformationSchemaMetadata.java | 3 +++ .../operator/TestDeduplicatingDirectExchangeBuffer.java | 3 +++ .../java/io/trino/operator/TestDirectExchangeClient.java | 3 +++ .../java/io/trino/operator/TestDistinctLimitOperator.java | 3 +++ .../test/java/io/trino/operator/TestExchangeOperator.java | 3 +++ .../java/io/trino/operator/TestHttpPageBufferClient.java | 3 +++ .../test/java/io/trino/operator/TestOperatorAssertion.java | 3 +++ .../java/io/trino/operator/TestOperatorMemoryRevocation.java | 3 +++ .../test/java/io/trino/operator/TestRowNumberOperator.java | 3 +++ .../io/trino/operator/TestScanFilterAndProjectOperator.java | 3 +++ .../test/java/io/trino/operator/TestTableFinishOperator.java | 3 +++ .../test/java/io/trino/operator/TestTableWriterOperator.java | 3 +++ .../test/java/io/trino/operator/TestTopNRankingOperator.java | 3 +++ .../io/trino/operator/TestWorkProcessorOperatorAdapter.java | 3 +++ .../operator/TestWorkProcessorSourceOperatorAdapter.java | 3 +++ .../aggregation/TestQuantileDigestAggregationFunction.java | 3 +++ .../operator/aggregation/TestQuantileDigestFunctions.java | 3 +++ .../io/trino/operator/join/TestNestedLoopBuildOperator.java | 3 +++ .../io/trino/operator/join/TestNestedLoopJoinOperator.java | 3 +++ .../operator/join/unspilled/TestHashBuilderOperator.java | 3 +++ .../io/trino/operator/output/TestPagePartitionerPool.java | 3 +++ .../trino/operator/output/TestPartitionedOutputOperator.java | 3 +++ .../java/io/trino/operator/project/TestPageProcessor.java | 3 +++ .../trino/operator/scalar/AbstractTestRegexpFunctions.java | 3 +++ .../trino/operator/scalar/TestArrayCombinationsFunction.java | 3 +++ .../io/trino/operator/scalar/TestArrayContainsSequence.java | 3 +++ .../io/trino/operator/scalar/TestArrayExceptFunction.java | 3 +++ .../io/trino/operator/scalar/TestArrayFilterFunction.java | 3 +++ .../java/io/trino/operator/scalar/TestArrayFunctions.java | 3 +++ .../io/trino/operator/scalar/TestArrayHistogramFunction.java | 3 +++ .../io/trino/operator/scalar/TestArrayMatchFunctions.java | 3 +++ .../io/trino/operator/scalar/TestArrayNgramsFunction.java | 3 +++ .../io/trino/operator/scalar/TestArrayReduceFunction.java | 3 +++ .../io/trino/operator/scalar/TestArrayTransformFunction.java | 3 +++ .../java/io/trino/operator/scalar/TestArrayTrimFunction.java | 3 +++ .../java/io/trino/operator/scalar/TestBitwiseFunctions.java | 3 +++ .../operator/scalar/TestBlockAndPositionNullConvention.java | 3 +++ .../java/io/trino/operator/scalar/TestConcatWsFunction.java | 3 +++ .../test/java/io/trino/operator/scalar/TestConditions.java | 3 +++ .../java/io/trino/operator/scalar/TestCustomFunctions.java | 3 +++ .../java/io/trino/operator/scalar/TestDataSizeFunctions.java | 3 +++ .../java/io/trino/operator/scalar/TestDateTimeFunctions.java | 3 +++ .../java/io/trino/operator/scalar/TestFailureFunction.java | 3 +++ .../java/io/trino/operator/scalar/TestFormatFunction.java | 3 +++ .../io/trino/operator/scalar/TestFormatNumberFunction.java | 3 +++ .../io/trino/operator/scalar/TestIpAddressFunctions.java | 3 +++ .../java/io/trino/operator/scalar/TestIsNullAnnotation.java | 3 +++ .../test/java/io/trino/operator/scalar/TestJsonExtract.java | 3 +++ .../java/io/trino/operator/scalar/TestJsonFunctions.java | 3 +++ .../io/trino/operator/scalar/TestJsonInputFunctions.java | 3 +++ .../io/trino/operator/scalar/TestJsonOutputFunctions.java | 3 +++ .../java/io/trino/operator/scalar/TestLambdaExpression.java | 3 +++ .../java/io/trino/operator/scalar/TestLikeFunctions.java | 3 +++ .../java/io/trino/operator/scalar/TestLuhnCheckFunction.java | 3 +++ .../java/io/trino/operator/scalar/TestMapFilterFunction.java | 3 +++ .../trino/operator/scalar/TestMapTransformKeysFunction.java | 3 +++ .../operator/scalar/TestMapTransformValuesFunction.java | 3 +++ .../io/trino/operator/scalar/TestMapZipWithFunction.java | 3 +++ .../java/io/trino/operator/scalar/TestMathFunctions.java | 3 +++ .../test/java/io/trino/operator/scalar/TestScalarParser.java | 3 +++ .../java/io/trino/operator/scalar/TestStringFunctions.java | 3 +++ .../test/java/io/trino/operator/scalar/TestTryFunction.java | 3 +++ .../java/io/trino/operator/scalar/TestTypeOfFunction.java | 3 +++ .../test/java/io/trino/operator/scalar/TestUrlFunctions.java | 3 +++ .../io/trino/operator/scalar/TestVarbinaryFunctions.java | 3 +++ .../java/io/trino/operator/scalar/TestWordStemFunction.java | 3 +++ .../test/java/io/trino/operator/scalar/TestZipFunction.java | 3 +++ .../java/io/trino/operator/scalar/TestZipWithFunction.java | 3 +++ .../test/java/io/trino/operator/scalar/date/TestExtract.java | 3 +++ .../trino/operator/scalar/interval/TestIntervalDayTime.java | 3 +++ .../operator/scalar/interval/TestIntervalYearMonth.java | 3 +++ .../test/java/io/trino/operator/scalar/time/TestExtract.java | 3 +++ .../java/io/trino/operator/scalar/time/TestOperators.java | 3 +++ .../test/java/io/trino/operator/scalar/time/TestTime.java | 3 +++ .../io/trino/operator/scalar/timestamp/TestDateTrunc.java | 3 +++ .../java/io/trino/operator/scalar/timestamp/TestExtract.java | 3 +++ .../operator/scalar/timestamp/TestHumanReadableSeconds.java | 3 +++ .../io/trino/operator/scalar/timestamp/TestOperators.java | 3 +++ .../io/trino/operator/scalar/timestamp/TestTimestamp.java | 3 +++ .../io/trino/operator/scalar/timestamptz/TestAtTimeZone.java | 3 +++ .../io/trino/operator/scalar/timestamptz/TestDateTrunc.java | 3 +++ .../io/trino/operator/scalar/timestamptz/TestExtract.java | 3 +++ .../io/trino/operator/scalar/timestamptz/TestOperators.java | 3 +++ .../scalar/timestamptz/TestTimestampWithTimeZone.java | 3 +++ .../java/io/trino/operator/scalar/timetz/TestExtract.java | 3 +++ .../java/io/trino/operator/scalar/timetz/TestOperators.java | 3 +++ .../trino/operator/scalar/timetz/TestTimeWithTimeZone.java | 3 +++ .../io/trino/operator/window/AbstractTestWindowFunction.java | 3 +++ .../test/java/io/trino/server/TestGenerateTokenFilter.java | 3 +++ .../src/test/java/io/trino/server/TestNodeResource.java | 3 +++ .../java/io/trino/server/TestQueryStateInfoResource.java | 3 +++ .../test/java/io/trino/server/TestSliceSerialization.java | 3 +++ .../oauth2/BaseOAuth2WebUiAuthenticationFilterTest.java | 3 +++ .../src/test/java/io/trino/server/ui/TestWebUi.java | 3 +++ .../java/io/trino/spiller/TestFileSingleStreamSpiller.java | 3 +++ .../io/trino/spiller/TestGenericPartitioningSpiller.java | 3 +++ .../src/test/java/io/trino/sql/analyzer/TestAnalyzer.java | 3 +++ .../test/java/io/trino/sql/gen/TestExpressionCompiler.java | 3 +++ .../io/trino/sql/gen/TestVarArgsToArrayAdapterGenerator.java | 3 +++ .../test/java/io/trino/sql/planner/TestDomainTranslator.java | 3 +++ .../java/io/trino/sql/planner/TestLocalExecutionPlanner.java | 3 +++ .../io/trino/sql/planner/TestPlanFragmentPartitionCount.java | 3 +++ .../java/io/trino/sql/planner/assertions/BasePlanTest.java | 3 +++ .../trino/sql/planner/iterative/TestIterativeOptimizer.java | 3 +++ .../iterative/rule/TestDetermineJoinDistributionType.java | 3 +++ .../rule/TestDetermineSemiJoinDistributionType.java | 3 +++ .../rule/TestDetermineTableScanNodePartitioning.java | 3 +++ .../trino/sql/planner/iterative/rule/TestJoinEnumerator.java | 3 +++ .../sql/planner/iterative/rule/TestJoinNodeFlattener.java | 3 +++ .../trino/sql/planner/iterative/rule/TestReorderJoins.java | 3 +++ .../trino/sql/planner/iterative/rule/test/BaseRuleTest.java | 3 +++ .../planner/planprinter/TestAnonymizeJsonRepresentation.java | 3 +++ .../sql/planner/planprinter/TestJsonRepresentation.java | 3 +++ .../src/test/java/io/trino/sql/query/TestAggregation.java | 3 +++ .../java/io/trino/sql/query/TestAggregationOverJoin.java | 3 +++ .../sql/query/TestAggregationsInRowPatternMatching.java | 3 +++ .../io/trino/sql/query/TestArraySortAfterArrayDistinct.java | 3 +++ .../java/io/trino/sql/query/TestComplexTypesWithNull.java | 3 +++ .../query/TestCopyAggregationStateInRowPatternMatching.java | 3 +++ .../java/io/trino/sql/query/TestCorrelatedAggregation.java | 3 +++ .../src/test/java/io/trino/sql/query/TestCorrelatedJoin.java | 3 +++ .../java/io/trino/sql/query/TestDistinctAggregations.java | 3 +++ .../java/io/trino/sql/query/TestDistinctWithOrderBy.java | 3 +++ .../src/test/java/io/trino/sql/query/TestExecute.java | 3 +++ .../test/java/io/trino/sql/query/TestExecuteImmediate.java | 3 +++ .../sql/query/TestExpressionRewriteInRowPatternMatching.java | 3 +++ .../src/test/java/io/trino/sql/query/TestExpressions.java | 3 +++ .../io/trino/sql/query/TestFilterInaccessibleColumns.java | 3 +++ .../src/test/java/io/trino/sql/query/TestFormat.java | 3 +++ .../src/test/java/io/trino/sql/query/TestFullJoin.java | 3 +++ .../src/test/java/io/trino/sql/query/TestGroupBy.java | 3 +++ .../src/test/java/io/trino/sql/query/TestGrouping.java | 3 +++ .../src/test/java/io/trino/sql/query/TestGroupingSets.java | 3 +++ .../src/test/java/io/trino/sql/query/TestHaving.java | 3 +++ .../src/test/java/io/trino/sql/query/TestIssue16101.java | 3 +++ .../src/test/java/io/trino/sql/query/TestJoin.java | 3 +++ .../src/test/java/io/trino/sql/query/TestJoinUsing.java | 3 +++ .../test/java/io/trino/sql/query/TestJsonArrayFunction.java | 3 +++ .../test/java/io/trino/sql/query/TestJsonExistsFunction.java | 3 +++ .../test/java/io/trino/sql/query/TestJsonObjectFunction.java | 3 +++ .../test/java/io/trino/sql/query/TestJsonQueryFunction.java | 3 +++ .../test/java/io/trino/sql/query/TestJsonValueFunction.java | 3 +++ .../test/java/io/trino/sql/query/TestLambdaExpressions.java | 3 +++ .../src/test/java/io/trino/sql/query/TestLateral.java | 3 +++ .../src/test/java/io/trino/sql/query/TestListagg.java | 3 +++ .../java/io/trino/sql/query/TestMergeProjectWithValues.java | 3 +++ .../src/test/java/io/trino/sql/query/TestMinMaxNWindow.java | 3 +++ .../trino/sql/query/TestNestedLogicalBinaryExpression.java | 3 +++ .../test/java/io/trino/sql/query/TestNumericalStability.java | 3 +++ .../test/java/io/trino/sql/query/TestOrderedAggregation.java | 3 +++ .../test/java/io/trino/sql/query/TestPrecomputedHashes.java | 3 +++ .../test/java/io/trino/sql/query/TestPredicatePushdown.java | 3 +++ .../src/test/java/io/trino/sql/query/TestRecursiveCte.java | 3 +++ .../src/test/java/io/trino/sql/query/TestReduceAgg.java | 3 +++ .../test/java/io/trino/sql/query/TestRowPatternMatching.java | 3 +++ .../io/trino/sql/query/TestRowPatternMatchingInWindow.java | 3 +++ .../src/test/java/io/trino/sql/query/TestSelectAll.java | 3 +++ .../test/java/io/trino/sql/query/TestSessionFunctions.java | 3 +++ .../test/java/io/trino/sql/query/TestSetDigestFunctions.java | 3 +++ .../src/test/java/io/trino/sql/query/TestSetOperations.java | 3 +++ .../src/test/java/io/trino/sql/query/TestShowQueries.java | 3 +++ .../src/test/java/io/trino/sql/query/TestSubqueries.java | 3 +++ .../test/java/io/trino/sql/query/TestTDigestFunctions.java | 3 +++ .../src/test/java/io/trino/sql/query/TestTrim.java | 3 +++ .../src/test/java/io/trino/sql/query/TestUnnest.java | 3 +++ .../java/io/trino/sql/query/TestUnwrapCastInComparison.java | 3 +++ .../src/test/java/io/trino/sql/query/TestValues.java | 3 +++ .../src/test/java/io/trino/sql/query/TestWindow.java | 3 +++ .../test/java/io/trino/sql/query/TestWindowFrameGroups.java | 3 +++ .../test/java/io/trino/sql/query/TestWindowFrameRange.java | 3 +++ .../test/java/io/trino/sql/query/TestWindowFrameRows.java | 3 +++ .../src/test/java/io/trino/sql/query/TestWith.java | 3 +++ .../java/io/trino/transaction/TestTransactionManager.java | 3 +++ .../src/test/java/io/trino/type/TestArrayOperators.java | 3 +++ .../src/test/java/io/trino/type/TestBigintOperators.java | 3 +++ .../src/test/java/io/trino/type/TestBooleanOperators.java | 3 +++ .../src/test/java/io/trino/type/TestCastDependencies.java | 3 +++ .../src/test/java/io/trino/type/TestCharOperators.java | 3 +++ .../src/test/java/io/trino/type/TestCharParametricType.java | 3 +++ .../test/java/io/trino/type/TestCharacterStringCasts.java | 3 +++ .../test/java/io/trino/type/TestConventionDependencies.java | 3 +++ core/trino-main/src/test/java/io/trino/type/TestDate.java | 3 +++ .../src/test/java/io/trino/type/TestDecimalCasts.java | 3 +++ .../src/test/java/io/trino/type/TestDecimalOperators.java | 3 +++ .../test/java/io/trino/type/TestDecimalParametricType.java | 3 +++ .../test/java/io/trino/type/TestDecimalToDecimalCasts.java | 3 +++ .../src/test/java/io/trino/type/TestDoubleOperators.java | 3 +++ .../src/test/java/io/trino/type/TestInstanceFunction.java | 3 +++ .../src/test/java/io/trino/type/TestIntegerOperators.java | 3 +++ .../src/test/java/io/trino/type/TestIntervalDayTime.java | 3 +++ .../src/test/java/io/trino/type/TestIntervalYearMonth.java | 3 +++ .../src/test/java/io/trino/type/TestIpAddressOperators.java | 3 +++ .../src/test/java/io/trino/type/TestJsonOperators.java | 3 +++ .../src/test/java/io/trino/type/TestMapOperators.java | 3 +++ .../src/test/java/io/trino/type/TestRealOperators.java | 3 +++ .../src/test/java/io/trino/type/TestRowOperators.java | 3 +++ .../src/test/java/io/trino/type/TestSmallintOperators.java | 3 +++ .../src/test/java/io/trino/type/TestTinyintOperators.java | 3 +++ .../src/test/java/io/trino/type/TestUnknownOperators.java | 3 +++ .../src/test/java/io/trino/type/TestUuidOperators.java | 3 +++ .../src/test/java/io/trino/type/TestVarcharOperators.java | 3 +++ .../io/trino/filesystem/AbstractTestTrinoFileSystem.java | 3 +++ .../test/java/io/trino/hive/formats/avro/TestAvroBase.java | 3 +++ .../trino/hive/formats/rcfile/AbstractTestRcFileReader.java | 3 +++ .../java/io/trino/plugin/accumulo/index/TestIndexer.java | 3 +++ .../io/trino/plugin/jdbc/BaseCaseInsensitiveMappingTest.java | 3 +++ .../plugin/jdbc/TestJdbcFlushMetadataCacheProcedure.java | 3 +++ .../test/java/io/trino/plugin/jdbc/TestJdbcRecordSet.java | 3 +++ .../java/io/trino/plugin/jdbc/TestJdbcRecordSetProvider.java | 3 +++ .../trino/plugin/bigquery/TestBigQueryInstanceCleaner.java | 3 +++ .../java/io/trino/plugin/blackhole/TestBlackHoleSmoke.java | 3 +++ .../plugin/cassandra/BaseCassandraConnectorSmokeTest.java | 2 ++ .../io/trino/plugin/cassandra/TestCassandraConnector.java | 2 ++ .../plugin/cassandra/TestCassandraTokenSplitManager.java | 3 +++ .../trino/plugin/deltalake/BaseDeltaLakeCompatibility.java | 2 ++ .../java/io/trino/plugin/deltalake/TestDeltaLakeBasic.java | 3 +++ .../plugin/deltalake/TestDeltaLakeCreateTableStatistics.java | 2 ++ .../java/io/trino/plugin/deltalake/TestDeltaLakeDelete.java | 2 ++ .../plugin/deltalake/TestDeltaLakeDynamicFiltering.java | 2 ++ .../plugin/deltalake/TestDeltaLakeGcsConnectorSmokeTest.java | 3 +++ .../io/trino/plugin/deltalake/TestDeltaLakeMetadata.java | 3 +++ .../deltalake/metastore/glue/TestDeltaLakeGlueMetastore.java | 3 +++ .../checkpoint/TestCheckpointEntryIterator.java | 3 +++ .../java/io/trino/plugin/druid/TestDruidTypeMapping.java | 2 ++ .../plugin/elasticsearch/TestPasswordAuthentication.java | 3 +++ .../java/io/trino/plugin/example/TestExampleRecordSet.java | 3 +++ .../trino/plugin/example/TestExampleRecordSetProvider.java | 3 +++ .../exchange/filesystem/AbstractTestExchangeManager.java | 3 +++ .../io/trino/plugin/geospatial/TestBingTileFunctions.java | 3 +++ .../plugin/geospatial/TestEncodedPolylineFunctions.java | 3 +++ .../java/io/trino/plugin/geospatial/TestGeoFunctions.java | 3 +++ .../java/io/trino/plugin/geospatial/TestKdbTreeCasts.java | 3 +++ .../trino/plugin/geospatial/TestSphericalGeoFunctions.java | 3 +++ .../aggregation/AbstractTestGeoAggregationFunctions.java | 3 +++ .../src/test/java/io/trino/plugin/hive/TestHive.java | 1 - .../src/test/java/io/trino/plugin/hive/AbstractTestHive.java | 4 +++- .../io/trino/plugin/hive/AbstractTestHiveFileSystem.java | 3 +++ .../io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java | 3 +++ .../java/io/trino/plugin/hive/TestHiveFileBasedSecurity.java | 3 +++ .../java/io/trino/plugin/hive/TestHiveFileMetastore.java | 1 - .../java/io/trino/plugin/hive/TestHiveInMemoryMetastore.java | 1 - .../trino/plugin/hive/TestOrcPageSourceMemoryTracking.java | 3 +++ .../plugin/hive/metastore/file/TestFileHiveMetastore.java | 3 +++ .../metastore/thrift/TestHiveMetastoreAccessOperations.java | 4 +++- .../test/java/io/trino/plugin/hive/util/TestAsyncQueue.java | 3 +++ .../io/trino/plugin/hive/util/TestThrottledAsyncQueue.java | 3 +++ .../plugin/iceberg/BaseIcebergMinioConnectorSmokeTest.java | 3 +++ .../catalog/file/TestTrinoHiveCatalogWithFileMetastore.java | 3 +++ .../catalog/glue/TestIcebergGlueCatalogAccessOperations.java | 3 +++ .../catalog/hms/TestTrinoHiveCatalogWithHiveMetastore.java | 3 +++ .../iceberg/catalog/nessie/TestTrinoNessieCatalog.java | 3 +++ .../src/test/java/io/trino/plugin/jmx/TestJmxQueries.java | 3 +++ .../test/java/io/trino/plugin/jmx/TestJmxSplitManager.java | 3 +++ .../java/io/trino/plugin/mongodb/TestObjectIdFunctions.java | 3 +++ .../plugin/eventlistener/mysql/TestMysqlEventListener.java | 3 +++ .../io/trino/plugin/mysql/TestCredentialPassthrough.java | 3 +++ .../plugin/pinot/TestPinotLatestConnectorSmokeTest.java | 3 +++ .../pinot/TestPinotLatestNoGrpcConnectorSmokeTest.java | 3 +++ .../plugin/pinot/TestPinotSecuredConnectorSmokeTest.java | 2 ++ .../trino/plugin/redis/AbstractTestMinimalFunctionality.java | 3 +++ .../plugin/teradata/functions/TestTeradataDateFunctions.java | 3 +++ .../plugin/teradata/functions/TestTeradataFunctions.java | 3 +++ .../trino/plugin/tpcds/statistics/TestTpcdsLocalStats.java | 3 +++ pom.xml | 5 ++++- .../src/test/java/io/trino/proxy/TestProxyServer.java | 3 +++ .../test/java/io/trino/verifier/TestDatabaseEventClient.java | 3 +++ .../src/test/java/io/trino/verifier/TestShadowing.java | 3 +++ .../java/io/trino/verifier/TestVerifierRewriteQueries.java | 3 +++ .../trino/testing/AbstractDistributedEngineOnlyQueries.java | 3 +++ .../java/io/trino/testing/AbstractTestQueryFramework.java | 3 +++ .../src/main/java/io/trino/testing/BaseConnectorTest.java | 2 ++ .../io/trino/testing/BaseDynamicPartitionPruningTest.java | 2 ++ .../java/io/trino/execution/TestCompletedEventWarnings.java | 3 +++ .../java/io/trino/execution/TestConnectorEventListener.java | 3 +++ .../io/trino/execution/TestDeprecatedFunctionWarning.java | 3 +++ .../test/java/io/trino/execution/TestPendingStageState.java | 3 +++ .../src/test/java/io/trino/execution/TestWarnings.java | 3 +++ .../src/test/java/io/trino/memory/TestMemoryManager.java | 3 +++ .../src/test/java/io/trino/tests/TestGracefulShutdown.java | 3 +++ .../src/test/java/io/trino/tests/TestMetadataManager.java | 3 +++ .../src/test/java/io/trino/tests/TestQueryManager.java | 3 +++ .../src/test/java/io/trino/tests/TestTablesample.java | 3 +++ .../java/io/trino/tests/tpch/TestTpchDistributedStats.java | 3 +++ .../test/java/io/trino/tests/tpch/TestTpchLocalStats.java | 3 +++ 324 files changed, 954 insertions(+), 6 deletions(-) diff --git a/client/trino-client/src/test/java/io/trino/client/auth/external/TestExternalAuthenticator.java b/client/trino-client/src/test/java/io/trino/client/auth/external/TestExternalAuthenticator.java index b7daaa378dac..aee2ccf578ed 100644 --- a/client/trino-client/src/test/java/io/trino/client/auth/external/TestExternalAuthenticator.java +++ b/client/trino-client/src/test/java/io/trino/client/auth/external/TestExternalAuthenticator.java @@ -25,6 +25,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.parallel.Execution; import java.net.URI; import java.net.URISyntaxException; @@ -54,8 +55,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; @TestInstance(PER_CLASS) +@Execution(SAME_THREAD) public class TestExternalAuthenticator { private static final ExecutorService executor = newCachedThreadPool(daemonThreadsNamed(TestExternalAuthenticator.class.getName() + "-%d")); diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcConnection.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcConnection.java index eca4ea92d40c..f157d699c6f8 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcConnection.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcConnection.java @@ -38,6 +38,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.parallel.Execution; import java.sql.Connection; import java.sql.DriverManager; @@ -68,12 +69,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestJdbcConnection { private final ExecutorService executor = newCachedThreadPool(daemonThreadsNamed(getClass().getName())); diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcPreparedStatement.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcPreparedStatement.java index 60f1f0c3f9de..cb5a308dae5e 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcPreparedStatement.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcPreparedStatement.java @@ -26,6 +26,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.math.BigDecimal; import java.math.BigInteger; @@ -70,12 +71,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestJdbcPreparedStatement { private static final int HEADER_SIZE_LIMIT = 16 * 1024; diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcStatement.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcStatement.java index 709b188de1a8..50c402501d7e 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcStatement.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcStatement.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.parallel.Execution; import java.sql.Connection; import java.sql.DriverManager; @@ -44,8 +45,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestJdbcStatement { private final ExecutorService executor = newCachedThreadPool(daemonThreadsNamed(getClass().getName())); diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriver.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriver.java index a92d4504b5ec..548b7eacbf38 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriver.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriver.java @@ -30,6 +30,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.parallel.Execution; import java.math.BigDecimal; import java.net.InetAddress; @@ -82,6 +83,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; @@ -90,6 +92,7 @@ import static org.testng.Assert.fail; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestTrinoDriver { private static final DateTimeZone ASIA_ORAL_ZONE = DateTimeZone.forID("Asia/Oral"); diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriverAuth.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriverAuth.java index d7bd11efc743..3f2a2730066e 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriverAuth.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriverAuth.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import javax.crypto.SecretKey; @@ -49,12 +50,14 @@ import static java.util.Base64.getMimeDecoder; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestTrinoDriverAuth { private static final String TEST_CATALOG = "test_catalog"; diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriverImpersonateUser.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriverImpersonateUser.java index e1de4b559a7e..d937f8689f57 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriverImpersonateUser.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriverImpersonateUser.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.nio.file.Files; @@ -39,10 +40,12 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestTrinoDriverImpersonateUser { private static final String TEST_USER = "test_user"; diff --git a/core/trino-main/src/test/java/io/trino/TestHiddenColumns.java b/core/trino-main/src/test/java/io/trino/TestHiddenColumns.java index 8e3555aa4f64..d20217daaf44 100644 --- a/core/trino-main/src/test/java/io/trino/TestHiddenColumns.java +++ b/core/trino-main/src/test/java/io/trino/TestHiddenColumns.java @@ -21,14 +21,17 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.SessionTestUtils.TEST_SESSION; import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.testing.MaterializedResult.resultBuilder; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestHiddenColumns { private LocalQueryRunner runner; diff --git a/core/trino-main/src/test/java/io/trino/cost/BaseStatsCalculatorTest.java b/core/trino-main/src/test/java/io/trino/cost/BaseStatsCalculatorTest.java index 15a007abb95c..a0e548fe5133 100644 --- a/core/trino-main/src/test/java/io/trino/cost/BaseStatsCalculatorTest.java +++ b/core/trino-main/src/test/java/io/trino/cost/BaseStatsCalculatorTest.java @@ -16,10 +16,13 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public abstract class BaseStatsCalculatorTest { private StatsCalculatorTester tester; diff --git a/core/trino-main/src/test/java/io/trino/cost/TestCostCalculator.java b/core/trino-main/src/test/java/io/trino/cost/TestCostCalculator.java index bdaa49f19b60..dbd6cb93db69 100644 --- a/core/trino-main/src/test/java/io/trino/cost/TestCostCalculator.java +++ b/core/trino-main/src/test/java/io/trino/cost/TestCostCalculator.java @@ -53,6 +53,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Arrays; import java.util.Collection; @@ -79,10 +80,12 @@ import static java.lang.String.format; import static java.util.Objects.requireNonNull; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestCostCalculator { private static final int NUMBER_OF_NODES = 10; diff --git a/core/trino-main/src/test/java/io/trino/cost/TestStatsCalculator.java b/core/trino-main/src/test/java/io/trino/cost/TestStatsCalculator.java index 9076170da0b7..917568f353cf 100644 --- a/core/trino-main/src/test/java/io/trino/cost/TestStatsCalculator.java +++ b/core/trino-main/src/test/java/io/trino/cost/TestStatsCalculator.java @@ -25,6 +25,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.execution.querystats.PlanOptimizersStatsCollector.createPlanOptimizersStatsCollector; import static io.trino.execution.warnings.WarningCollector.NOOP; @@ -34,8 +35,10 @@ import static io.trino.testing.TestingHandles.TEST_CATALOG_NAME; import static io.trino.testing.TestingSession.testSessionBuilder; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestStatsCalculator { private LocalQueryRunner queryRunner; diff --git a/core/trino-main/src/test/java/io/trino/exchange/TestExchangeSourceOutputSelector.java b/core/trino-main/src/test/java/io/trino/exchange/TestExchangeSourceOutputSelector.java index 06988af9a568..718f144f81e5 100644 --- a/core/trino-main/src/test/java/io/trino/exchange/TestExchangeSourceOutputSelector.java +++ b/core/trino-main/src/test/java/io/trino/exchange/TestExchangeSourceOutputSelector.java @@ -27,17 +27,20 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.exchange.ExchangeSourceOutputSelector.Selection.EXCLUDED; import static io.trino.spi.exchange.ExchangeSourceOutputSelector.Selection.INCLUDED; import static io.trino.spi.exchange.ExchangeSourceOutputSelector.Selection.UNKNOWN; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestExchangeSourceOutputSelector { private static final ExchangeId EXCHANGE_ID_1 = new ExchangeId("exchange_1"); diff --git a/core/trino-main/src/test/java/io/trino/execution/BaseTestSqlTaskManager.java b/core/trino-main/src/test/java/io/trino/execution/BaseTestSqlTaskManager.java index 95e7d392277d..3188c833cac8 100644 --- a/core/trino-main/src/test/java/io/trino/execution/BaseTestSqlTaskManager.java +++ b/core/trino-main/src/test/java/io/trino/execution/BaseTestSqlTaskManager.java @@ -53,6 +53,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.parallel.Execution; import java.net.URI; import java.util.List; @@ -77,6 +78,7 @@ import static io.trino.testing.TestingSession.testSessionBuilder; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotEquals; @@ -85,6 +87,7 @@ import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public abstract class BaseTestSqlTaskManager { public static final OutputBufferId OUT = new OutputBufferId(0); diff --git a/core/trino-main/src/test/java/io/trino/execution/TestCallTask.java b/core/trino-main/src/test/java/io/trino/execution/TestCallTask.java index 94742b109201..feae96a48f8d 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestCallTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestCallTask.java @@ -41,6 +41,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.lang.invoke.MethodHandle; import java.net.URI; @@ -62,8 +63,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestCallTask { private static final MethodHandle PROCEDURE_METHOD_HANDLE = methodHandle(TestingProcedure.class, "testingMethod", Target.class, ConnectorAccessControl.class); diff --git a/core/trino-main/src/test/java/io/trino/execution/TestCommitTask.java b/core/trino-main/src/test/java/io/trino/execution/TestCommitTask.java index dad9d140e647..bd656631a04e 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestCommitTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestCommitTask.java @@ -30,6 +30,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.net.URI; import java.util.Optional; @@ -50,11 +51,13 @@ import static java.util.Collections.emptyList; import static java.util.concurrent.Executors.newCachedThreadPool; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestCommitTask { private final Metadata metadata = createTestMetadataManager(); diff --git a/core/trino-main/src/test/java/io/trino/execution/TestCreateTableColumnTypeCoercion.java b/core/trino-main/src/test/java/io/trino/execution/TestCreateTableColumnTypeCoercion.java index fef95cc72dcc..8dfbee1f2488 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestCreateTableColumnTypeCoercion.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestCreateTableColumnTypeCoercion.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Optional; @@ -31,8 +32,10 @@ import static io.trino.testing.TestingSession.testSessionBuilder; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestCreateTableColumnTypeCoercion { private static final String catalogName = "mock"; diff --git a/core/trino-main/src/test/java/io/trino/execution/TestDeallocateTask.java b/core/trino-main/src/test/java/io/trino/execution/TestDeallocateTask.java index e4ff91bccf9d..cd852db09a8b 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestDeallocateTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestDeallocateTask.java @@ -30,6 +30,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.net.URI; import java.util.List; @@ -49,9 +50,11 @@ import static java.util.Collections.emptyList; import static java.util.concurrent.Executors.newCachedThreadPool; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestDeallocateTask { private final Metadata metadata = createTestMetadataManager(); diff --git a/core/trino-main/src/test/java/io/trino/execution/TestPlannerWarnings.java b/core/trino-main/src/test/java/io/trino/execution/TestPlannerWarnings.java index 1cccdef2d1aa..0b7af9a71026 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestPlannerWarnings.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestPlannerWarnings.java @@ -38,6 +38,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.Map; @@ -54,9 +55,11 @@ import static java.util.Objects.requireNonNull; import static java.util.stream.IntStream.range; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.fail; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestPlannerWarnings { private LocalQueryRunner queryRunner; diff --git a/core/trino-main/src/test/java/io/trino/execution/TestPrepareTask.java b/core/trino-main/src/test/java/io/trino/execution/TestPrepareTask.java index c70f030d75f1..90c910eb9009 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestPrepareTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestPrepareTask.java @@ -35,6 +35,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.net.URI; import java.util.List; @@ -59,9 +60,11 @@ import static java.util.Collections.emptyList; import static java.util.concurrent.Executors.newCachedThreadPool; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestPrepareTask { private final Metadata metadata = createTestMetadataManager(); diff --git a/core/trino-main/src/test/java/io/trino/execution/TestQueryStateMachine.java b/core/trino-main/src/test/java/io/trino/execution/TestQueryStateMachine.java index e4bc392129d0..fda2a8797c71 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestQueryStateMachine.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestQueryStateMachine.java @@ -40,6 +40,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.IOException; import java.net.URI; @@ -74,6 +75,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; @@ -81,6 +83,7 @@ import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestQueryStateMachine { private static final String QUERY = "sql"; diff --git a/core/trino-main/src/test/java/io/trino/execution/TestResetSessionTask.java b/core/trino-main/src/test/java/io/trino/execution/TestResetSessionTask.java index 5d4b87d8a58f..b38281f6b586 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestResetSessionTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestResetSessionTask.java @@ -32,6 +32,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.net.URI; import java.util.Optional; @@ -47,9 +48,11 @@ import static java.util.Collections.emptyList; import static java.util.concurrent.Executors.newCachedThreadPool; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestResetSessionTask { private static final String CATALOG_NAME = "my_catalog"; diff --git a/core/trino-main/src/test/java/io/trino/execution/TestRollbackTask.java b/core/trino-main/src/test/java/io/trino/execution/TestRollbackTask.java index 0ae43cb1e8a0..5e56211d22ab 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestRollbackTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestRollbackTask.java @@ -27,6 +27,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.net.URI; import java.util.Optional; @@ -45,11 +46,13 @@ import static java.util.Collections.emptyList; import static java.util.concurrent.Executors.newCachedThreadPool; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestRollbackTask { private final Metadata metadata = createTestMetadataManager(); diff --git a/core/trino-main/src/test/java/io/trino/execution/TestSetPathTask.java b/core/trino-main/src/test/java/io/trino/execution/TestSetPathTask.java index 6c3e3b006f35..de416131518a 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestSetPathTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestSetPathTask.java @@ -30,6 +30,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.net.URI; import java.util.Optional; @@ -45,9 +46,11 @@ import static java.util.concurrent.Executors.newCachedThreadPool; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestSetPathTask { private TransactionManager transactionManager; diff --git a/core/trino-main/src/test/java/io/trino/execution/TestSetRoleTask.java b/core/trino-main/src/test/java/io/trino/execution/TestSetRoleTask.java index 6359ce4760cc..748679558e77 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestSetRoleTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestSetRoleTask.java @@ -34,6 +34,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.net.URI; import java.util.Map; @@ -51,9 +52,11 @@ import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static java.util.concurrent.Executors.newCachedThreadPool; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestSetRoleTask { private static final String CATALOG_NAME = "foo"; diff --git a/core/trino-main/src/test/java/io/trino/execution/TestSetSessionAuthorizationTask.java b/core/trino-main/src/test/java/io/trino/execution/TestSetSessionAuthorizationTask.java index 6a4bc822b752..5d3edc4e1fc2 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestSetSessionAuthorizationTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestSetSessionAuthorizationTask.java @@ -28,6 +28,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.net.URI; import java.util.Optional; @@ -42,9 +43,11 @@ import static java.util.concurrent.Executors.newCachedThreadPool; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestSetSessionAuthorizationTask { private TransactionManager transactionManager; diff --git a/core/trino-main/src/test/java/io/trino/execution/TestSetSessionTask.java b/core/trino-main/src/test/java/io/trino/execution/TestSetSessionTask.java index f00380ce2289..0a5475d52899 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestSetSessionTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestSetSessionTask.java @@ -39,6 +39,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.net.URI; import java.util.List; @@ -61,9 +62,11 @@ import static java.util.concurrent.Executors.newCachedThreadPool; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestSetSessionTask { private static final String CATALOG_NAME = "my_catalog"; diff --git a/core/trino-main/src/test/java/io/trino/execution/TestSetTimeZoneTask.java b/core/trino-main/src/test/java/io/trino/execution/TestSetTimeZoneTask.java index d7ed438a8338..a2209ef5486c 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestSetTimeZoneTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestSetTimeZoneTask.java @@ -31,6 +31,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.net.URI; import java.util.Map; @@ -54,9 +55,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestSetTimeZoneTask { private ExecutorService executor = newCachedThreadPool(daemonThreadsNamed(getClass().getSimpleName() + "-%s")); diff --git a/core/trino-main/src/test/java/io/trino/execution/TestSqlStage.java b/core/trino-main/src/test/java/io/trino/execution/TestSqlStage.java index 44e4dab8b735..68b4f0d3cb45 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestSqlStage.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestSqlStage.java @@ -42,6 +42,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.parallel.Execution; import java.net.URI; import java.util.ArrayList; @@ -68,6 +69,7 @@ import static java.util.concurrent.Executors.newScheduledThreadPool; import static java.util.concurrent.TimeUnit.MINUTES; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertSame; @@ -75,6 +77,7 @@ import static org.testng.Assert.fail; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestSqlStage { private ExecutorService executor; diff --git a/core/trino-main/src/test/java/io/trino/execution/TestSqlTask.java b/core/trino-main/src/test/java/io/trino/execution/TestSqlTask.java index 735c9b5813c3..a673c99f721a 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestSqlTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestSqlTask.java @@ -44,6 +44,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.parallel.Execution; import java.net.URI; import java.util.Optional; @@ -79,6 +80,7 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; @@ -86,6 +88,7 @@ import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestSqlTask { public static final OutputBufferId OUT = new OutputBufferId(0); diff --git a/core/trino-main/src/test/java/io/trino/execution/TestStageStateMachine.java b/core/trino-main/src/test/java/io/trino/execution/TestStageStateMachine.java index 7f06895cb334..a292a0bbcc25 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestStageStateMachine.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestStageStateMachine.java @@ -30,6 +30,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.IOException; import java.sql.SQLException; @@ -43,6 +44,7 @@ import static io.trino.sql.planner.SystemPartitioningHandle.SOURCE_DISTRIBUTION; import static java.util.concurrent.Executors.newCachedThreadPool; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNull; @@ -50,6 +52,7 @@ import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestStageStateMachine { private static final StageId STAGE_ID = new StageId("query", 0); diff --git a/core/trino-main/src/test/java/io/trino/execution/TestStartTransactionTask.java b/core/trino-main/src/test/java/io/trino/execution/TestStartTransactionTask.java index c21bee31e90d..9ebccbee1b73 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestStartTransactionTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestStartTransactionTask.java @@ -37,6 +37,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.net.URI; import java.util.Optional; @@ -61,12 +62,14 @@ import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestStartTransactionTask { private final Metadata metadata = createTestMetadataManager(); diff --git a/core/trino-main/src/test/java/io/trino/execution/TestStateMachine.java b/core/trino-main/src/test/java/io/trino/execution/TestStateMachine.java index 11e2ea7e0c7a..05d0bca2b803 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestStateMachine.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestStateMachine.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.concurrent.ExecutorService; @@ -28,11 +29,13 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestStateMachine { private enum State diff --git a/core/trino-main/src/test/java/io/trino/execution/buffer/TestArbitraryOutputBuffer.java b/core/trino-main/src/test/java/io/trino/execution/buffer/TestArbitraryOutputBuffer.java index da6da6cc64c7..bcb4e368da30 100644 --- a/core/trino-main/src/test/java/io/trino/execution/buffer/TestArbitraryOutputBuffer.java +++ b/core/trino-main/src/test/java/io/trino/execution/buffer/TestArbitraryOutputBuffer.java @@ -28,6 +28,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.ArrayList; import java.util.HashMap; @@ -64,11 +65,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestArbitraryOutputBuffer { private static final String TASK_INSTANCE_ID = "task-instance-id"; diff --git a/core/trino-main/src/test/java/io/trino/execution/buffer/TestBroadcastOutputBuffer.java b/core/trino-main/src/test/java/io/trino/execution/buffer/TestBroadcastOutputBuffer.java index d4cd9f8da586..75f68c73cadd 100644 --- a/core/trino-main/src/test/java/io/trino/execution/buffer/TestBroadcastOutputBuffer.java +++ b/core/trino-main/src/test/java/io/trino/execution/buffer/TestBroadcastOutputBuffer.java @@ -30,6 +30,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.ArrayList; import java.util.List; @@ -70,11 +71,13 @@ import static java.util.concurrent.Executors.newScheduledThreadPool; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestBroadcastOutputBuffer { private static final String TASK_INSTANCE_ID = "task-instance-id"; diff --git a/core/trino-main/src/test/java/io/trino/execution/buffer/TestPagesSerde.java b/core/trino-main/src/test/java/io/trino/execution/buffer/TestPagesSerde.java index 5abb631e5b19..5340489dc313 100644 --- a/core/trino-main/src/test/java/io/trino/execution/buffer/TestPagesSerde.java +++ b/core/trino-main/src/test/java/io/trino/execution/buffer/TestPagesSerde.java @@ -34,6 +34,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import javax.crypto.SecretKey; @@ -53,10 +54,12 @@ import static io.trino.util.Ciphers.createRandomAesEncryptionKey; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestPagesSerde { private BlockEncodingSerde blockEncodingSerde; diff --git a/core/trino-main/src/test/java/io/trino/execution/buffer/TestPartitionedOutputBuffer.java b/core/trino-main/src/test/java/io/trino/execution/buffer/TestPartitionedOutputBuffer.java index f8af1edc8bc2..d360275c86a4 100644 --- a/core/trino-main/src/test/java/io/trino/execution/buffer/TestPartitionedOutputBuffer.java +++ b/core/trino-main/src/test/java/io/trino/execution/buffer/TestPartitionedOutputBuffer.java @@ -27,6 +27,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.ArrayList; import java.util.List; @@ -60,11 +61,13 @@ import static java.util.concurrent.Executors.newScheduledThreadPool; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestPartitionedOutputBuffer { private static final String TASK_INSTANCE_ID = "task-instance-id"; diff --git a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestFixedCountScheduler.java b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestFixedCountScheduler.java index 7c05e37976b1..a30b86acbc45 100644 --- a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestFixedCountScheduler.java +++ b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestFixedCountScheduler.java @@ -24,6 +24,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.net.URI; import java.util.List; @@ -38,10 +39,12 @@ import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestFixedCountScheduler { private ExecutorService executor = newCachedThreadPool(daemonThreadsNamed(getClass().getSimpleName() + "executor-%s")); diff --git a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestMultiSourcePartitionedScheduler.java b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestMultiSourcePartitionedScheduler.java index 8c3a2bc28541..7cbe88fa94ac 100644 --- a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestMultiSourcePartitionedScheduler.java +++ b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestMultiSourcePartitionedScheduler.java @@ -72,6 +72,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.net.URI; import java.util.ArrayList; @@ -118,11 +119,13 @@ import static java.util.concurrent.Executors.newScheduledThreadPool; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestMultiSourcePartitionedScheduler { private static final PlanNodeId TABLE_SCAN_1_NODE_ID = new PlanNodeId("1"); diff --git a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestSourcePartitionedScheduler.java b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestSourcePartitionedScheduler.java index 1cd9b6b8d46e..5338d236642e 100644 --- a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestSourcePartitionedScheduler.java +++ b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestSourcePartitionedScheduler.java @@ -69,6 +69,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.net.URI; import java.util.ArrayList; @@ -110,11 +111,13 @@ import static java.util.concurrent.Executors.newScheduledThreadPool; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestSourcePartitionedScheduler { private static final PlanNodeId TABLE_SCAN_NODE_ID = new PlanNodeId("plan_id"); diff --git a/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestEventDrivenTaskSource.java b/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestEventDrivenTaskSource.java index ab41210d9f16..7b533c70afeb 100644 --- a/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestEventDrivenTaskSource.java +++ b/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestEventDrivenTaskSource.java @@ -49,6 +49,7 @@ import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.parallel.Execution; import java.util.ArrayList; import java.util.Collection; @@ -81,11 +82,13 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestEventDrivenTaskSource { private static final int INVOCATION_COUNT = 20; diff --git a/core/trino-main/src/test/java/io/trino/metadata/TestInformationSchemaMetadata.java b/core/trino-main/src/test/java/io/trino/metadata/TestInformationSchemaMetadata.java index 336c6a4d8292..c650cd4a04a5 100644 --- a/core/trino-main/src/test/java/io/trino/metadata/TestInformationSchemaMetadata.java +++ b/core/trino-main/src/test/java/io/trino/metadata/TestInformationSchemaMetadata.java @@ -43,6 +43,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Map; import java.util.Optional; @@ -55,10 +56,12 @@ import static io.trino.testing.TestingSession.testSessionBuilder; import static java.util.Arrays.stream; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestInformationSchemaMetadata { private static final int MAX_PREFIXES_COUNT = new OptimizerConfig().getMaxPrefetchedInformationSchemaPrefixes(); diff --git a/core/trino-main/src/test/java/io/trino/operator/TestDeduplicatingDirectExchangeBuffer.java b/core/trino-main/src/test/java/io/trino/operator/TestDeduplicatingDirectExchangeBuffer.java index 9a60ae7dbe99..16e02e6dec2f 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestDeduplicatingDirectExchangeBuffer.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestDeduplicatingDirectExchangeBuffer.java @@ -32,6 +32,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.HashSet; import java.util.List; @@ -50,6 +51,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; @@ -57,6 +59,7 @@ import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestDeduplicatingDirectExchangeBuffer { private static final DataSize DEFAULT_BUFFER_CAPACITY = DataSize.of(1, KILOBYTE); diff --git a/core/trino-main/src/test/java/io/trino/operator/TestDirectExchangeClient.java b/core/trino-main/src/test/java/io/trino/operator/TestDirectExchangeClient.java index 179202f29383..85db9d46f04f 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestDirectExchangeClient.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestDirectExchangeClient.java @@ -46,6 +46,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.parallel.Execution; import java.net.URI; import java.util.ArrayList; @@ -85,6 +86,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; @@ -92,6 +94,7 @@ import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestDirectExchangeClient { private ScheduledExecutorService scheduler; diff --git a/core/trino-main/src/test/java/io/trino/operator/TestDistinctLimitOperator.java b/core/trino-main/src/test/java/io/trino/operator/TestDistinctLimitOperator.java index 6856f7e70d84..a102f9e31769 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestDistinctLimitOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestDistinctLimitOperator.java @@ -25,6 +25,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.Optional; @@ -45,9 +46,11 @@ import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestDistinctLimitOperator { private final ExecutorService executor = newCachedThreadPool(daemonThreadsNamed(getClass().getSimpleName() + "-%s")); diff --git a/core/trino-main/src/test/java/io/trino/operator/TestExchangeOperator.java b/core/trino-main/src/test/java/io/trino/operator/TestExchangeOperator.java index 6f97e5130ed5..ab3f7796a793 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestExchangeOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestExchangeOperator.java @@ -38,6 +38,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.ArrayList; import java.util.List; @@ -58,11 +59,13 @@ import static io.trino.testing.TestingTaskContext.createTaskContext; import static java.util.concurrent.Executors.newScheduledThreadPool; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestExchangeOperator { private static final List TYPES = ImmutableList.of(VARCHAR); diff --git a/core/trino-main/src/test/java/io/trino/operator/TestHttpPageBufferClient.java b/core/trino-main/src/test/java/io/trino/operator/TestHttpPageBufferClient.java index 77522edfc4c0..e3c5bd78d0dc 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestHttpPageBufferClient.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestHttpPageBufferClient.java @@ -39,6 +39,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.net.URI; import java.util.ArrayList; @@ -71,10 +72,12 @@ import static io.trino.util.Failures.WORKER_NODE_ERROR; import static java.util.concurrent.Executors.newScheduledThreadPool; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestHttpPageBufferClient { private ScheduledExecutorService scheduler; diff --git a/core/trino-main/src/test/java/io/trino/operator/TestOperatorAssertion.java b/core/trino-main/src/test/java/io/trino/operator/TestOperatorAssertion.java index 70e960d745db..5b37a996df23 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestOperatorAssertion.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestOperatorAssertion.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.concurrent.ScheduledExecutorService; @@ -31,8 +32,10 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestOperatorAssertion { private ScheduledExecutorService executor; diff --git a/core/trino-main/src/test/java/io/trino/operator/TestOperatorMemoryRevocation.java b/core/trino-main/src/test/java/io/trino/operator/TestOperatorMemoryRevocation.java index d24a887dc471..5d8661230e73 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestOperatorMemoryRevocation.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestOperatorMemoryRevocation.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicInteger; @@ -25,11 +26,13 @@ import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestOperatorMemoryRevocation { private ScheduledExecutorService scheduledExecutor; diff --git a/core/trino-main/src/test/java/io/trino/operator/TestRowNumberOperator.java b/core/trino-main/src/test/java/io/trino/operator/TestRowNumberOperator.java index 37de8c8dc2ff..7ea9d3434213 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestRowNumberOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestRowNumberOperator.java @@ -30,6 +30,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Arrays; import java.util.List; @@ -55,10 +56,12 @@ import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestRowNumberOperator { private ExecutorService executor; diff --git a/core/trino-main/src/test/java/io/trino/operator/TestScanFilterAndProjectOperator.java b/core/trino-main/src/test/java/io/trino/operator/TestScanFilterAndProjectOperator.java index 408495cd547a..d1149370393d 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestScanFilterAndProjectOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestScanFilterAndProjectOperator.java @@ -46,6 +46,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.Optional; @@ -77,12 +78,14 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestScanFilterAndProjectOperator { private final Session session = TEST_SESSION; diff --git a/core/trino-main/src/test/java/io/trino/operator/TestTableFinishOperator.java b/core/trino-main/src/test/java/io/trino/operator/TestTableFinishOperator.java index d5f39614ce86..4cb8e2f3712c 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestTableFinishOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestTableFinishOperator.java @@ -35,6 +35,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Collection; import java.util.List; @@ -58,12 +59,14 @@ import static java.util.concurrent.Executors.newScheduledThreadPool; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestTableFinishOperator { private static final TestingAggregationFunction LONG_MAX = new TestingFunctionResolution().getAggregateFunction("max", fromTypes(BIGINT)); diff --git a/core/trino-main/src/test/java/io/trino/operator/TestTableWriterOperator.java b/core/trino-main/src/test/java/io/trino/operator/TestTableWriterOperator.java index fa112a6bf0ca..ca0d25f59c54 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestTableWriterOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestTableWriterOperator.java @@ -44,6 +44,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.ArrayList; import java.util.Collection; @@ -71,12 +72,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestTableWriterOperator { private static final TestingAggregationFunction LONG_MAX = new TestingFunctionResolution().getAggregateFunction("max", fromTypes(BIGINT)); diff --git a/core/trino-main/src/test/java/io/trino/operator/TestTopNRankingOperator.java b/core/trino-main/src/test/java/io/trino/operator/TestTopNRankingOperator.java index 6d21525f31a0..6e1b68a2ccce 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestTopNRankingOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestTopNRankingOperator.java @@ -29,6 +29,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Arrays; import java.util.List; @@ -55,6 +56,7 @@ import static java.util.concurrent.Executors.newScheduledThreadPool; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; @@ -62,6 +64,7 @@ import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestTopNRankingOperator { private final ExecutorService executor = newCachedThreadPool(daemonThreadsNamed(getClass().getSimpleName() + "-%s")); diff --git a/core/trino-main/src/test/java/io/trino/operator/TestWorkProcessorOperatorAdapter.java b/core/trino-main/src/test/java/io/trino/operator/TestWorkProcessorOperatorAdapter.java index 03c9e2a2a9ef..feca1fd694f2 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestWorkProcessorOperatorAdapter.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestWorkProcessorOperatorAdapter.java @@ -26,6 +26,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.concurrent.ScheduledExecutorService; @@ -35,8 +36,10 @@ import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestWorkProcessorOperatorAdapter { private ScheduledExecutorService scheduledExecutor; diff --git a/core/trino-main/src/test/java/io/trino/operator/TestWorkProcessorSourceOperatorAdapter.java b/core/trino-main/src/test/java/io/trino/operator/TestWorkProcessorSourceOperatorAdapter.java index 26cdb362a50a..bcef1a61cd36 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestWorkProcessorSourceOperatorAdapter.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestWorkProcessorSourceOperatorAdapter.java @@ -27,6 +27,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.concurrent.ScheduledExecutorService; @@ -36,8 +37,10 @@ import static java.util.concurrent.TimeUnit.NANOSECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestWorkProcessorSourceOperatorAdapter { private ScheduledExecutorService scheduledExecutor; diff --git a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestQuantileDigestAggregationFunction.java b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestQuantileDigestAggregationFunction.java index 6d2bf1a02cec..28040e61a741 100644 --- a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestQuantileDigestAggregationFunction.java +++ b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestQuantileDigestAggregationFunction.java @@ -29,6 +29,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Arrays; import java.util.Collections; @@ -59,8 +60,10 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestQuantileDigestAggregationFunction { private static final Joiner ARRAY_JOINER = Joiner.on(","); diff --git a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestQuantileDigestFunctions.java b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestQuantileDigestFunctions.java index 42ab1cce672c..e029d8f0496c 100644 --- a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestQuantileDigestFunctions.java +++ b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestQuantileDigestFunctions.java @@ -20,13 +20,16 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.stream.IntStream; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestQuantileDigestFunctions { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/join/TestNestedLoopBuildOperator.java b/core/trino-main/src/test/java/io/trino/operator/join/TestNestedLoopBuildOperator.java index f3e06037a38d..6f96ec32c251 100644 --- a/core/trino-main/src/test/java/io/trino/operator/join/TestNestedLoopBuildOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/join/TestNestedLoopBuildOperator.java @@ -26,6 +26,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.concurrent.ExecutorService; @@ -38,11 +39,13 @@ import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestNestedLoopBuildOperator { private ExecutorService executor; diff --git a/core/trino-main/src/test/java/io/trino/operator/join/TestNestedLoopJoinOperator.java b/core/trino-main/src/test/java/io/trino/operator/join/TestNestedLoopJoinOperator.java index b4aa766662e1..ca9a9573b561 100644 --- a/core/trino-main/src/test/java/io/trino/operator/join/TestNestedLoopJoinOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/join/TestNestedLoopJoinOperator.java @@ -31,6 +31,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.concurrent.ExecutorService; @@ -48,10 +49,12 @@ import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestNestedLoopJoinOperator { private ExecutorService executor; diff --git a/core/trino-main/src/test/java/io/trino/operator/join/unspilled/TestHashBuilderOperator.java b/core/trino-main/src/test/java/io/trino/operator/join/unspilled/TestHashBuilderOperator.java index 9b84046449ea..d2562aca388e 100644 --- a/core/trino-main/src/test/java/io/trino/operator/join/unspilled/TestHashBuilderOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/join/unspilled/TestHashBuilderOperator.java @@ -29,6 +29,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Arrays; import java.util.List; @@ -50,11 +51,13 @@ import static java.util.concurrent.Executors.newScheduledThreadPool; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestHashBuilderOperator { private ExecutorService executor; diff --git a/core/trino-main/src/test/java/io/trino/operator/output/TestPagePartitionerPool.java b/core/trino-main/src/test/java/io/trino/operator/output/TestPagePartitionerPool.java index b104283b42de..4c3c542cf60e 100644 --- a/core/trino-main/src/test/java/io/trino/operator/output/TestPagePartitionerPool.java +++ b/core/trino-main/src/test/java/io/trino/operator/output/TestPagePartitionerPool.java @@ -40,6 +40,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.HashMap; import java.util.List; @@ -60,9 +61,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestPagePartitionerPool { private ScheduledExecutorService driverYieldExecutor; diff --git a/core/trino-main/src/test/java/io/trino/operator/output/TestPartitionedOutputOperator.java b/core/trino-main/src/test/java/io/trino/operator/output/TestPartitionedOutputOperator.java index 4934f4888a87..bc29137f38c1 100644 --- a/core/trino-main/src/test/java/io/trino/operator/output/TestPartitionedOutputOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/output/TestPartitionedOutputOperator.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledExecutorService; @@ -31,9 +32,11 @@ import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestPartitionedOutputOperator { private ExecutorService executor; diff --git a/core/trino-main/src/test/java/io/trino/operator/project/TestPageProcessor.java b/core/trino-main/src/test/java/io/trino/operator/project/TestPageProcessor.java index 471225c9edc0..9a814cee30e9 100644 --- a/core/trino-main/src/test/java/io/trino/operator/project/TestPageProcessor.java +++ b/core/trino-main/src/test/java/io/trino/operator/project/TestPageProcessor.java @@ -36,6 +36,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Arrays; import java.util.Collections; @@ -72,6 +73,7 @@ import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; @@ -79,6 +81,7 @@ import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestPageProcessor { private final ScheduledExecutorService executor = newSingleThreadScheduledExecutor(daemonThreadsNamed(getClass().getSimpleName() + "-%s")); diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/AbstractTestRegexpFunctions.java b/core/trino-main/src/test/java/io/trino/operator/scalar/AbstractTestRegexpFunctions.java index 6aa8cec46086..e4b72cb6f360 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/AbstractTestRegexpFunctions.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/AbstractTestRegexpFunctions.java @@ -29,6 +29,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Collections; @@ -40,8 +41,10 @@ import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public abstract class AbstractTestRegexpFunctions { private final RegexLibrary regexLibrary; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayCombinationsFunction.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayCombinationsFunction.java index 8571b00769ef..7ec1948183bf 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayCombinationsFunction.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayCombinationsFunction.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static com.google.common.math.LongMath.factorial; import static io.trino.operator.scalar.ArrayCombinationsFunction.combinationCount; @@ -35,8 +36,10 @@ import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestArrayCombinationsFunction { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayContainsSequence.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayContainsSequence.java index 6937ddd8a363..6b33ddb4f4a5 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayContainsSequence.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayContainsSequence.java @@ -18,11 +18,14 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestArrayContainsSequence { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayExceptFunction.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayExceptFunction.java index d90fc4bff773..63d87ea67212 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayExceptFunction.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayExceptFunction.java @@ -19,12 +19,15 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.type.UnknownType.UNKNOWN; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestArrayExceptFunction { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayFilterFunction.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayFilterFunction.java index bd20cfb31c42..1f646822550f 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayFilterFunction.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayFilterFunction.java @@ -18,11 +18,14 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestArrayFilterFunction { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayFunctions.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayFunctions.java index 5edf0826a4e3..931277c569e7 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayFunctions.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayFunctions.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.type.DoubleType.DOUBLE; import static io.trino.spi.type.IntegerType.INTEGER; @@ -28,8 +29,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestArrayFunctions { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayHistogramFunction.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayHistogramFunction.java index ee676031a711..1a741e2abbb4 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayHistogramFunction.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayHistogramFunction.java @@ -18,13 +18,16 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestArrayHistogramFunction { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayMatchFunctions.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayMatchFunctions.java index e21ee971a6b0..4f911511dbae 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayMatchFunctions.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayMatchFunctions.java @@ -18,12 +18,15 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.type.BooleanType.BOOLEAN; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestArrayMatchFunctions { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayNgramsFunction.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayNgramsFunction.java index b89fcdb669f9..b55eab1da84d 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayNgramsFunction.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayNgramsFunction.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.type.BooleanType.BOOLEAN; import static io.trino.spi.type.DoubleType.DOUBLE; @@ -30,8 +31,10 @@ import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestArrayNgramsFunction { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayReduceFunction.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayReduceFunction.java index 760fc0b7e842..5d4faf0c86c3 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayReduceFunction.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayReduceFunction.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.StandardErrorCode.FUNCTION_NOT_FOUND; import static io.trino.spi.type.BigintType.BIGINT; @@ -27,8 +28,10 @@ import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestArrayReduceFunction { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayTransformFunction.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayTransformFunction.java index 252c3178a289..b40c8a317b91 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayTransformFunction.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayTransformFunction.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.BooleanType.BOOLEAN; @@ -36,8 +37,10 @@ import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestArrayTransformFunction { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayTrimFunction.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayTrimFunction.java index 946111493015..3d3e138328a6 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayTrimFunction.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestArrayTrimFunction.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.spi.type.VarcharType.createVarcharType; @@ -27,8 +28,10 @@ import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestArrayTrimFunction { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestBitwiseFunctions.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestBitwiseFunctions.java index e23b53c119be..fd5c777d72f7 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestBitwiseFunctions.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestBitwiseFunctions.java @@ -18,12 +18,15 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestBitwiseFunctions { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestBlockAndPositionNullConvention.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestBlockAndPositionNullConvention.java index 40534ddbd362..367c1fbdb67e 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestBlockAndPositionNullConvention.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestBlockAndPositionNullConvention.java @@ -29,6 +29,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.concurrent.atomic.AtomicBoolean; @@ -38,9 +39,11 @@ import static io.trino.spi.type.VarcharType.createVarcharType; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestBlockAndPositionNullConvention { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestConcatWsFunction.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestConcatWsFunction.java index 9e9faae219b7..502677a2d720 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestConcatWsFunction.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestConcatWsFunction.java @@ -19,13 +19,16 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestConcatWsFunction { private static final int MAX_INPUT_VALUES = 254; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestConditions.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestConditions.java index 718a17863bba..1d550fa9363c 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestConditions.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestConditions.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.type.BooleanType.BOOLEAN; import static io.trino.spi.type.DecimalType.createDecimalType; @@ -27,8 +28,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestConditions { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestCustomFunctions.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestCustomFunctions.java index ad54be707150..782d90ff7e3a 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestCustomFunctions.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestCustomFunctions.java @@ -19,11 +19,14 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestCustomFunctions { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestDataSizeFunctions.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestDataSizeFunctions.java index c978672e3ee0..ddb84d9bf82c 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestDataSizeFunctions.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestDataSizeFunctions.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.StandardErrorCode.NUMERIC_VALUE_OUT_OF_RANGE; import static io.trino.spi.type.DecimalType.createDecimalType; @@ -26,8 +27,10 @@ import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestDataSizeFunctions { private static final DecimalType DECIMAL = createDecimalType(38, 0); diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestDateTimeFunctions.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestDateTimeFunctions.java index 681b754e5337..3a2df5e644db 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestDateTimeFunctions.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestDateTimeFunctions.java @@ -25,6 +25,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.time.Instant; import java.time.LocalDate; @@ -45,9 +46,11 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestDateTimeFunctions { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestFailureFunction.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestFailureFunction.java index f33ba26ce890..0c41e8619f4f 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestFailureFunction.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestFailureFunction.java @@ -21,12 +21,15 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.StandardErrorCode.GENERIC_USER_ERROR; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestFailureFunction { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestFormatFunction.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestFormatFunction.java index 64aa5cdae910..2719892c4915 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestFormatFunction.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestFormatFunction.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Arrays; @@ -28,8 +29,10 @@ import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestFormatFunction { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestFormatNumberFunction.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestFormatNumberFunction.java index 628f81d741a9..c0c5bded6cd3 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestFormatNumberFunction.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestFormatNumberFunction.java @@ -18,12 +18,15 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.type.VarcharType.VARCHAR; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestFormatNumberFunction { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestIpAddressFunctions.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestIpAddressFunctions.java index c79e3cde09eb..6b9349aa4356 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestIpAddressFunctions.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestIpAddressFunctions.java @@ -18,13 +18,16 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.type.BooleanType.BOOLEAN; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestIpAddressFunctions { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestIsNullAnnotation.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestIsNullAnnotation.java index 82ff9445e06f..31d22ebc9cab 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestIsNullAnnotation.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestIsNullAnnotation.java @@ -26,14 +26,17 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static com.google.common.base.Preconditions.checkArgument; import static io.airlift.slice.Slices.utf8Slice; import static io.trino.spi.type.VarcharType.VARCHAR; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestIsNullAnnotation { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestJsonExtract.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestJsonExtract.java index 1594bd9aea80..323e8016a904 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestJsonExtract.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestJsonExtract.java @@ -22,6 +22,7 @@ import io.trino.sql.query.QueryAssertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.IOException; import java.util.List; @@ -37,10 +38,12 @@ import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestJsonExtract { @Test diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestJsonFunctions.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestJsonFunctions.java index 8dfa232347d2..846d070208da 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestJsonFunctions.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestJsonFunctions.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.StandardErrorCode.INVALID_FUNCTION_ARGUMENT; import static io.trino.spi.StandardErrorCode.INVALID_LITERAL; @@ -28,8 +29,10 @@ import static io.trino.type.JsonType.JSON; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestJsonFunctions { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestJsonInputFunctions.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestJsonInputFunctions.java index 307844b464de..86eb0e1e8249 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestJsonInputFunctions.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestJsonInputFunctions.java @@ -27,6 +27,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.nio.charset.Charset; @@ -40,8 +41,10 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestJsonInputFunctions { private static final String INPUT = "{\"key1\" : 1e0, \"key2\" : true, \"key3\" : null}"; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestJsonOutputFunctions.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestJsonOutputFunctions.java index 14a607d9fff1..1c5d85284f94 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestJsonOutputFunctions.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestJsonOutputFunctions.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.nio.charset.Charset; @@ -27,8 +28,10 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestJsonOutputFunctions { private static final String JSON_EXPRESSION = "\"$varchar_to_json\"('{\"key1\" : 1e0, \"key2\" : true, \"key3\" : null}', true)"; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestLambdaExpression.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestLambdaExpression.java index d650d3e866f8..df4c0c4608ab 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestLambdaExpression.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestLambdaExpression.java @@ -24,6 +24,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.operator.scalar.ApplyFunction.APPLY_FUNCTION; import static io.trino.operator.scalar.InvokeFunction.INVOKE_FUNCTION; @@ -39,8 +40,10 @@ import static io.trino.util.StructuralTestUtil.mapType; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestLambdaExpression { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestLikeFunctions.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestLikeFunctions.java index be16b5588e8b..9cb5f2fa88f2 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestLikeFunctions.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestLikeFunctions.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Optional; @@ -35,11 +36,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestLikeFunctions { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestLuhnCheckFunction.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestLuhnCheckFunction.java index bd77c64cd50f..32a2777be1fa 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestLuhnCheckFunction.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestLuhnCheckFunction.java @@ -18,14 +18,17 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.StandardErrorCode.INVALID_FUNCTION_ARGUMENT; import static io.trino.spi.type.BooleanType.BOOLEAN; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestLuhnCheckFunction { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestMapFilterFunction.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestMapFilterFunction.java index bb33874e4cef..de3b72f27c40 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestMapFilterFunction.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestMapFilterFunction.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.HashMap; import java.util.Map; @@ -35,8 +36,10 @@ import static io.trino.util.StructuralTestUtil.mapType; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestMapFilterFunction { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestMapTransformKeysFunction.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestMapTransformKeysFunction.java index afad49f4a35d..654521532fab 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestMapTransformKeysFunction.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestMapTransformKeysFunction.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.HashMap; import java.util.Map; @@ -36,8 +37,10 @@ import static io.trino.util.StructuralTestUtil.mapType; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestMapTransformKeysFunction { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestMapTransformValuesFunction.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestMapTransformValuesFunction.java index b1bf7e75295c..20d92a27096d 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestMapTransformValuesFunction.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestMapTransformValuesFunction.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.HashMap; import java.util.Map; @@ -35,8 +36,10 @@ import static io.trino.util.StructuralTestUtil.mapType; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestMapTransformValuesFunction { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestMapZipWithFunction.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestMapZipWithFunction.java index 5bd2e8707721..29fa6af5594b 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestMapZipWithFunction.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestMapZipWithFunction.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.type.BooleanType.BOOLEAN; import static io.trino.spi.type.DoubleType.DOUBLE; @@ -33,8 +34,10 @@ import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestMapZipWithFunction { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestMathFunctions.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestMathFunctions.java index c2be63f15ca9..5f65653b52b7 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestMathFunctions.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestMathFunctions.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.StandardErrorCode.DIVISION_BY_ZERO; import static io.trino.spi.StandardErrorCode.NUMERIC_VALUE_OUT_OF_RANGE; @@ -39,8 +40,10 @@ import static java.util.Collections.nCopies; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestMathFunctions { private static final double[] DOUBLE_VALUES = {123, -123, 123.45, -123.45, 0}; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestScalarParser.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestScalarParser.java index bb5d671f8d15..d7a96ae6f1f6 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestScalarParser.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestScalarParser.java @@ -26,13 +26,16 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.spi.type.VarcharType.createVarcharType; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestScalarParser { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestStringFunctions.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestStringFunctions.java index 79f367ee9978..13e072eb133a 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestStringFunctions.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestStringFunctions.java @@ -31,6 +31,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.StandardErrorCode.FUNCTION_NOT_FOUND; import static io.trino.spi.StandardErrorCode.INVALID_FUNCTION_ARGUMENT; @@ -47,8 +48,10 @@ import static java.util.Collections.nCopies; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestStringFunctions { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestTryFunction.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestTryFunction.java index d341886e02f4..6e7aabe957d6 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestTryFunction.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestTryFunction.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.StandardErrorCode.GENERIC_INTERNAL_ERROR; import static io.trino.spi.type.BigintType.BIGINT; @@ -36,8 +37,10 @@ import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestTryFunction { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestTypeOfFunction.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestTypeOfFunction.java index 02bf8dcc4553..79abb500095e 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestTypeOfFunction.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestTypeOfFunction.java @@ -18,13 +18,16 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.StandardErrorCode.FUNCTION_NOT_FOUND; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestTypeOfFunction { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestUrlFunctions.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestUrlFunctions.java index f5aaa6af182a..918a3cc6f069 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestUrlFunctions.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestUrlFunctions.java @@ -18,13 +18,16 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.VarcharType.createVarcharType; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestUrlFunctions { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestVarbinaryFunctions.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestVarbinaryFunctions.java index 8bacfaea4cb0..f25cdd701b51 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestVarbinaryFunctions.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestVarbinaryFunctions.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Base64; @@ -36,8 +37,10 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestVarbinaryFunctions { private static final byte[] ALL_BYTES; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestWordStemFunction.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestWordStemFunction.java index 8dec17bf4485..f9a5785477a4 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestWordStemFunction.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestWordStemFunction.java @@ -18,13 +18,16 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.type.VarcharType.createVarcharType; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestWordStemFunction { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestZipFunction.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestZipFunction.java index a4eb046de22d..88774c0e956d 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestZipFunction.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestZipFunction.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.stream.IntStream; @@ -35,8 +36,10 @@ import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestZipFunction { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestZipWithFunction.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestZipWithFunction.java index c5d8cfcc9a86..a5386ae7733f 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestZipWithFunction.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestZipWithFunction.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.BooleanType.BOOLEAN; @@ -33,8 +34,10 @@ import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestZipWithFunction { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/date/TestExtract.java b/core/trino-main/src/test/java/io/trino/operator/scalar/date/TestExtract.java index c9e50c07e974..26ee14f5d3ab 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/date/TestExtract.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/date/TestExtract.java @@ -19,12 +19,15 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestExtract { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/interval/TestIntervalDayTime.java b/core/trino-main/src/test/java/io/trino/operator/scalar/interval/TestIntervalDayTime.java index 64bcc20ed581..15ae5b26bce0 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/interval/TestIntervalDayTime.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/interval/TestIntervalDayTime.java @@ -19,12 +19,15 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestIntervalDayTime { protected QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/interval/TestIntervalYearMonth.java b/core/trino-main/src/test/java/io/trino/operator/scalar/interval/TestIntervalYearMonth.java index bd29ccada291..e0fe9c19bd6d 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/interval/TestIntervalYearMonth.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/interval/TestIntervalYearMonth.java @@ -19,12 +19,15 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestIntervalYearMonth { protected QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/time/TestExtract.java b/core/trino-main/src/test/java/io/trino/operator/scalar/time/TestExtract.java index 3e19db946246..94e031887c7e 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/time/TestExtract.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/time/TestExtract.java @@ -20,13 +20,16 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestExtract { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/time/TestOperators.java b/core/trino-main/src/test/java/io/trino/operator/scalar/time/TestOperators.java index e8b8fdc74446..0ccadc0aff3f 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/time/TestOperators.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/time/TestOperators.java @@ -18,12 +18,15 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.function.OperatorType.INDETERMINATE; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestOperators { protected QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/time/TestTime.java b/core/trino-main/src/test/java/io/trino/operator/scalar/time/TestTime.java index c3e940dee6b8..150acbd1797f 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/time/TestTime.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/time/TestTime.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.time.Instant; import java.time.ZoneId; @@ -33,8 +34,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestTime { protected QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/timestamp/TestDateTrunc.java b/core/trino-main/src/test/java/io/trino/operator/scalar/timestamp/TestDateTrunc.java index 04b7367379bb..164e6fc9e400 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/timestamp/TestDateTrunc.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/timestamp/TestDateTrunc.java @@ -20,12 +20,15 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.testing.TestingSession.testSessionBuilder; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestDateTrunc { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/timestamp/TestExtract.java b/core/trino-main/src/test/java/io/trino/operator/scalar/timestamp/TestExtract.java index 83f56ce4376a..07630d2e57bf 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/timestamp/TestExtract.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/timestamp/TestExtract.java @@ -20,13 +20,16 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestExtract { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/timestamp/TestHumanReadableSeconds.java b/core/trino-main/src/test/java/io/trino/operator/scalar/timestamp/TestHumanReadableSeconds.java index ae55a0ca0446..871734ccfc03 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/timestamp/TestHumanReadableSeconds.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/timestamp/TestHumanReadableSeconds.java @@ -20,14 +20,17 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.testing.TestingSession.testSessionBuilder; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestHumanReadableSeconds { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/timestamp/TestOperators.java b/core/trino-main/src/test/java/io/trino/operator/scalar/timestamp/TestOperators.java index a9ff3ee1cdfc..7296662a1ab6 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/timestamp/TestOperators.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/timestamp/TestOperators.java @@ -20,12 +20,15 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.testing.TestingSession.testSessionBuilder; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestOperators { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/timestamp/TestTimestamp.java b/core/trino-main/src/test/java/io/trino/operator/scalar/timestamp/TestTimestamp.java index bd88b424e1c3..30d4c492b27c 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/timestamp/TestTimestamp.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/timestamp/TestTimestamp.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.time.Instant; import java.time.LocalDateTime; @@ -45,8 +46,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestTimestamp { private static final TimeZoneKey SESSION_TIME_ZONE = DEFAULT_TIME_ZONE_KEY; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/timestamptz/TestAtTimeZone.java b/core/trino-main/src/test/java/io/trino/operator/scalar/timestamptz/TestAtTimeZone.java index ef44358f3599..a7f3bd522d9b 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/timestamptz/TestAtTimeZone.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/timestamptz/TestAtTimeZone.java @@ -19,13 +19,16 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.type.TimeZoneKey.getTimeZoneKey; import static io.trino.testing.TestingSession.testSessionBuilder; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestAtTimeZone { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/timestamptz/TestDateTrunc.java b/core/trino-main/src/test/java/io/trino/operator/scalar/timestamptz/TestDateTrunc.java index 483556f1c429..5fcf88e636ac 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/timestamptz/TestDateTrunc.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/timestamptz/TestDateTrunc.java @@ -18,11 +18,14 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestDateTrunc { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/timestamptz/TestExtract.java b/core/trino-main/src/test/java/io/trino/operator/scalar/timestamptz/TestExtract.java index e618fe1898de..3213b7530afd 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/timestamptz/TestExtract.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/timestamptz/TestExtract.java @@ -19,12 +19,15 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestExtract { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/timestamptz/TestOperators.java b/core/trino-main/src/test/java/io/trino/operator/scalar/timestamptz/TestOperators.java index 0c03d5b389c8..cd8a06abf343 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/timestamptz/TestOperators.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/timestamptz/TestOperators.java @@ -18,13 +18,16 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.function.OperatorType.INDETERMINATE; import static io.trino.spi.type.BooleanType.BOOLEAN; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestOperators { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/timestamptz/TestTimestampWithTimeZone.java b/core/trino-main/src/test/java/io/trino/operator/scalar/timestamptz/TestTimestampWithTimeZone.java index 6cf8e535daaa..b8f1e916b363 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/timestamptz/TestTimestampWithTimeZone.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/timestamptz/TestTimestampWithTimeZone.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.time.Instant; import java.time.ZonedDateTime; @@ -39,8 +40,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestTimestampWithTimeZone { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/timetz/TestExtract.java b/core/trino-main/src/test/java/io/trino/operator/scalar/timetz/TestExtract.java index 5720aab320e6..5e492a268a74 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/timetz/TestExtract.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/timetz/TestExtract.java @@ -20,13 +20,16 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestExtract { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/timetz/TestOperators.java b/core/trino-main/src/test/java/io/trino/operator/scalar/timetz/TestOperators.java index ab58bd856399..5accb0e2ded3 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/timetz/TestOperators.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/timetz/TestOperators.java @@ -18,11 +18,14 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestOperators { protected QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/timetz/TestTimeWithTimeZone.java b/core/trino-main/src/test/java/io/trino/operator/scalar/timetz/TestTimeWithTimeZone.java index b5cfdaae5311..2aed7972bc1a 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/timetz/TestTimeWithTimeZone.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/timetz/TestTimeWithTimeZone.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.time.Instant; import java.time.ZonedDateTime; @@ -32,8 +33,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestTimeWithTimeZone { protected QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/operator/window/AbstractTestWindowFunction.java b/core/trino-main/src/test/java/io/trino/operator/window/AbstractTestWindowFunction.java index 32238aa6a194..965a1fcea210 100644 --- a/core/trino-main/src/test/java/io/trino/operator/window/AbstractTestWindowFunction.java +++ b/core/trino-main/src/test/java/io/trino/operator/window/AbstractTestWindowFunction.java @@ -19,13 +19,16 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static com.google.common.base.Preconditions.checkArgument; import static io.airlift.testing.Closeables.closeAllRuntimeException; import static io.trino.SessionTestUtils.TEST_SESSION; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public abstract class AbstractTestWindowFunction { protected LocalQueryRunner queryRunner; diff --git a/core/trino-main/src/test/java/io/trino/server/TestGenerateTokenFilter.java b/core/trino-main/src/test/java/io/trino/server/TestGenerateTokenFilter.java index f7de99e0eba1..fa9e2e4a10ce 100644 --- a/core/trino-main/src/test/java/io/trino/server/TestGenerateTokenFilter.java +++ b/core/trino-main/src/test/java/io/trino/server/TestGenerateTokenFilter.java @@ -31,6 +31,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -48,9 +49,11 @@ import static jakarta.servlet.http.HttpServletResponse.SC_OK; import static java.lang.annotation.RetentionPolicy.RUNTIME; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestGenerateTokenFilter { private JettyHttpClient httpClient; diff --git a/core/trino-main/src/test/java/io/trino/server/TestNodeResource.java b/core/trino-main/src/test/java/io/trino/server/TestNodeResource.java index 69e2a55f33e5..4cec434a5524 100644 --- a/core/trino-main/src/test/java/io/trino/server/TestNodeResource.java +++ b/core/trino-main/src/test/java/io/trino/server/TestNodeResource.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; @@ -30,9 +31,11 @@ import static io.trino.client.ProtocolHeaders.TRINO_HEADERS; import static io.trino.failuredetector.HeartbeatFailureDetector.Stats; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestNodeResource { private TestingTrinoServer server; diff --git a/core/trino-main/src/test/java/io/trino/server/TestQueryStateInfoResource.java b/core/trino-main/src/test/java/io/trino/server/TestQueryStateInfoResource.java index f1eeb7b50d34..cb63b79800fa 100644 --- a/core/trino-main/src/test/java/io/trino/server/TestQueryStateInfoResource.java +++ b/core/trino-main/src/test/java/io/trino/server/TestQueryStateInfoResource.java @@ -28,6 +28,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.IOException; import java.util.List; @@ -51,11 +52,13 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Fail.fail; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestQueryStateInfoResource { private static final String LONG_LASTING_QUERY = "SELECT * FROM tpch.sf1.lineitem"; diff --git a/core/trino-main/src/test/java/io/trino/server/TestSliceSerialization.java b/core/trino-main/src/test/java/io/trino/server/TestSliceSerialization.java index dd2fb65adcb3..b68693f0ff35 100644 --- a/core/trino-main/src/test/java/io/trino/server/TestSliceSerialization.java +++ b/core/trino-main/src/test/java/io/trino/server/TestSliceSerialization.java @@ -25,6 +25,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Objects; import java.util.concurrent.ThreadLocalRandom; @@ -32,9 +33,11 @@ import static com.google.common.base.MoreObjects.toStringHelper; import static java.util.Objects.requireNonNull; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestSliceSerialization { private ObjectMapperProvider provider; diff --git a/core/trino-main/src/test/java/io/trino/server/security/oauth2/BaseOAuth2WebUiAuthenticationFilterTest.java b/core/trino-main/src/test/java/io/trino/server/security/oauth2/BaseOAuth2WebUiAuthenticationFilterTest.java index 72a57e0f212d..6315d9c4481c 100644 --- a/core/trino-main/src/test/java/io/trino/server/security/oauth2/BaseOAuth2WebUiAuthenticationFilterTest.java +++ b/core/trino-main/src/test/java/io/trino/server/security/oauth2/BaseOAuth2WebUiAuthenticationFilterTest.java @@ -41,6 +41,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.IOException; import java.net.CookieManager; @@ -72,9 +73,11 @@ import static jakarta.ws.rs.core.Response.Status.UNAUTHORIZED; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public abstract class BaseOAuth2WebUiAuthenticationFilterTest { protected static final Duration TTL_ACCESS_TOKEN_IN_SECONDS = Duration.ofSeconds(5); diff --git a/core/trino-main/src/test/java/io/trino/server/ui/TestWebUi.java b/core/trino-main/src/test/java/io/trino/server/ui/TestWebUi.java index 4edc52446f3f..c2478ca8cc7f 100644 --- a/core/trino-main/src/test/java/io/trino/server/ui/TestWebUi.java +++ b/core/trino-main/src/test/java/io/trino/server/ui/TestWebUi.java @@ -62,6 +62,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import javax.crypto.SecretKey; @@ -124,11 +125,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestWebUi { private static final String LOCALHOST_KEYSTORE = Resources.getResource("cert/localhost.pem").getPath(); diff --git a/core/trino-main/src/test/java/io/trino/spiller/TestFileSingleStreamSpiller.java b/core/trino-main/src/test/java/io/trino/spiller/TestFileSingleStreamSpiller.java index d067abeb8d42..a66e66e5a95b 100644 --- a/core/trino-main/src/test/java/io/trino/spiller/TestFileSingleStreamSpiller.java +++ b/core/trino-main/src/test/java/io/trino/spiller/TestFileSingleStreamSpiller.java @@ -29,6 +29,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.io.IOException; @@ -51,10 +52,12 @@ import static java.util.concurrent.Executors.newCachedThreadPool; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestFileSingleStreamSpiller { private static final List TYPES = ImmutableList.of(BIGINT, DOUBLE, VARBINARY); diff --git a/core/trino-main/src/test/java/io/trino/spiller/TestGenericPartitioningSpiller.java b/core/trino-main/src/test/java/io/trino/spiller/TestGenericPartitioningSpiller.java index ee754fa020ce..2392cb133e8c 100644 --- a/core/trino-main/src/test/java/io/trino/spiller/TestGenericPartitioningSpiller.java +++ b/core/trino-main/src/test/java/io/trino/spiller/TestGenericPartitioningSpiller.java @@ -31,6 +31,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.UncheckedIOException; import java.nio.channels.ClosedChannelException; @@ -53,9 +54,11 @@ import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestGenericPartitioningSpiller { private static final int FIRST_PARTITION_START = -10; diff --git a/core/trino-main/src/test/java/io/trino/sql/analyzer/TestAnalyzer.java b/core/trino-main/src/test/java/io/trino/sql/analyzer/TestAnalyzer.java index 6f415b9e1b76..a492399247b8 100644 --- a/core/trino-main/src/test/java/io/trino/sql/analyzer/TestAnalyzer.java +++ b/core/trino-main/src/test/java/io/trino/sql/analyzer/TestAnalyzer.java @@ -102,6 +102,7 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.time.Duration; import java.util.List; @@ -209,8 +210,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestAnalyzer { private static final String TPCH_CATALOG = "tpch"; diff --git a/core/trino-main/src/test/java/io/trino/sql/gen/TestExpressionCompiler.java b/core/trino-main/src/test/java/io/trino/sql/gen/TestExpressionCompiler.java index 460adf91ef07..ad3e995827a7 100644 --- a/core/trino-main/src/test/java/io/trino/sql/gen/TestExpressionCompiler.java +++ b/core/trino-main/src/test/java/io/trino/sql/gen/TestExpressionCompiler.java @@ -47,6 +47,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.math.BigDecimal; import java.text.DecimalFormat; @@ -86,8 +87,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.joda.time.DateTimeZone.UTC; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestExpressionCompiler { private static final Boolean[] booleanValues = {true, false, null}; diff --git a/core/trino-main/src/test/java/io/trino/sql/gen/TestVarArgsToArrayAdapterGenerator.java b/core/trino-main/src/test/java/io/trino/sql/gen/TestVarArgsToArrayAdapterGenerator.java index 9129c7d3a1de..7af642a921ce 100644 --- a/core/trino-main/src/test/java/io/trino/sql/gen/TestVarArgsToArrayAdapterGenerator.java +++ b/core/trino-main/src/test/java/io/trino/sql/gen/TestVarArgsToArrayAdapterGenerator.java @@ -28,6 +28,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.lang.invoke.MethodHandle; import java.util.Optional; @@ -43,8 +44,10 @@ import static java.util.stream.Collectors.toSet; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestVarArgsToArrayAdapterGenerator { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestDomainTranslator.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestDomainTranslator.java index 865070c3d949..0ab1f562896a 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestDomainTranslator.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestDomainTranslator.java @@ -59,6 +59,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.math.BigDecimal; import java.util.List; @@ -106,12 +107,14 @@ import static java.util.Collections.nCopies; import static java.util.Objects.requireNonNull; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotEquals; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestDomainTranslator { private static final Symbol C_BIGINT = new Symbol("c_bigint"); diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestLocalExecutionPlanner.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestLocalExecutionPlanner.java index 2aa043e0a83e..8450c7c19927 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestLocalExecutionPlanner.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestLocalExecutionPlanner.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.airlift.testing.Closeables.closeAllRuntimeException; import static io.trino.SessionTestUtils.TEST_SESSION; @@ -29,8 +30,10 @@ import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static java.util.Collections.nCopies; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestLocalExecutionPlanner { private LocalQueryRunner runner; diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestPlanFragmentPartitionCount.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestPlanFragmentPartitionCount.java index 761c7bd41b64..ddcae1df4180 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestPlanFragmentPartitionCount.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestPlanFragmentPartitionCount.java @@ -31,6 +31,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Map; import java.util.Optional; @@ -44,8 +45,10 @@ import static io.trino.transaction.TransactionBuilder.transaction; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestPlanFragmentPartitionCount { private PlanFragmenter planFragmenter; diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/assertions/BasePlanTest.java b/core/trino-main/src/test/java/io/trino/sql/planner/assertions/BasePlanTest.java index 6adee5ed248e..1d86481b4487 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/assertions/BasePlanTest.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/assertions/BasePlanTest.java @@ -33,6 +33,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; @@ -52,8 +53,10 @@ import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class BasePlanTest { private final Map sessionProperties; diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/TestIterativeOptimizer.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/TestIterativeOptimizer.java index c17d5940f8f4..4eefe4e2da99 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/TestIterativeOptimizer.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/TestIterativeOptimizer.java @@ -32,6 +32,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.parallel.Execution; import java.util.Optional; @@ -44,10 +45,12 @@ import static io.trino.testing.TestingSession.testSessionBuilder; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestIterativeOptimizer { @Test diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestDetermineJoinDistributionType.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestDetermineJoinDistributionType.java index 418d2461ce7b..bca8c37d7fb5 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestDetermineJoinDistributionType.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestDetermineJoinDistributionType.java @@ -41,6 +41,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Optional; @@ -66,9 +67,11 @@ import static io.trino.sql.tree.BooleanLiteral.TRUE_LITERAL; import static java.lang.Double.NaN; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestDetermineJoinDistributionType { private static final CostComparator COST_COMPARATOR = new CostComparator(1, 1, 1); diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestDetermineSemiJoinDistributionType.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestDetermineSemiJoinDistributionType.java index 8681c1830222..e27484eca9ad 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestDetermineSemiJoinDistributionType.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestDetermineSemiJoinDistributionType.java @@ -29,6 +29,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Optional; @@ -44,8 +45,10 @@ import static io.trino.sql.planner.plan.SemiJoinNode.DistributionType.REPLICATED; import static io.trino.sql.tree.BooleanLiteral.TRUE_LITERAL; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestDetermineSemiJoinDistributionType { private static final CostComparator COST_COMPARATOR = new CostComparator(1, 1, 1); diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestDetermineTableScanNodePartitioning.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestDetermineTableScanNodePartitioning.java index f2ae09d0ab6a..a700d1c6ed12 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestDetermineTableScanNodePartitioning.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestDetermineTableScanNodePartitioning.java @@ -32,6 +32,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.airlift.testing.Closeables.closeAllRuntimeException; import static io.trino.sql.planner.TestTableScanNodePartitioning.BUCKET_COUNT; @@ -50,8 +51,10 @@ import static io.trino.sql.planner.assertions.MatchResult.NO_MATCH; import static io.trino.sql.planner.assertions.PlanMatchPattern.tableScan; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestDetermineTableScanNodePartitioning { private RuleTester tester; diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestJoinEnumerator.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestJoinEnumerator.java index a2b8c2e58a92..7aaff34c6a79 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestJoinEnumerator.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestJoinEnumerator.java @@ -39,6 +39,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.LinkedHashSet; import java.util.Optional; @@ -49,10 +50,12 @@ import static io.trino.sql.tree.BooleanLiteral.TRUE_LITERAL; import static io.trino.testing.TestingSession.testSessionBuilder; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestJoinEnumerator { private LocalQueryRunner queryRunner; diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestJoinNodeFlattener.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestJoinNodeFlattener.java index 4628fb33d781..88b936f1507d 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestJoinNodeFlattener.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestJoinNodeFlattener.java @@ -39,6 +39,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.LinkedHashSet; import java.util.List; @@ -67,11 +68,13 @@ import static io.trino.testing.TestingSession.testSessionBuilder; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestJoinNodeFlattener { private static final int DEFAULT_JOIN_LIMIT = 10; diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestReorderJoins.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestReorderJoins.java index a4bc5b2ee6b1..8eb6b2411666 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestReorderJoins.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestReorderJoins.java @@ -35,6 +35,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Optional; @@ -58,8 +59,10 @@ import static io.trino.sql.tree.ComparisonExpression.Operator.EQUAL; import static io.trino.sql.tree.ComparisonExpression.Operator.LESS_THAN; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestReorderJoins { private RuleTester tester; diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/test/BaseRuleTest.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/test/BaseRuleTest.java index 434882658d03..781def9256ea 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/test/BaseRuleTest.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/test/BaseRuleTest.java @@ -19,14 +19,17 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.Optional; import static io.airlift.testing.Closeables.closeAllRuntimeException; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public abstract class BaseRuleTest { private RuleTester tester; diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/planprinter/TestAnonymizeJsonRepresentation.java b/core/trino-main/src/test/java/io/trino/sql/planner/planprinter/TestAnonymizeJsonRepresentation.java index 65f0e6215fbf..5a74d5e50de0 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/planprinter/TestAnonymizeJsonRepresentation.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/planprinter/TestAnonymizeJsonRepresentation.java @@ -38,6 +38,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.Optional; @@ -60,8 +61,10 @@ import static io.trino.sql.planner.planprinter.NodeRepresentation.TypedSymbol.typedSymbol; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestAnonymizeJsonRepresentation { private static final JsonCodec JSON_RENDERED_NODE_CODEC = jsonCodec(JsonRenderedNode.class); diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/planprinter/TestJsonRepresentation.java b/core/trino-main/src/test/java/io/trino/sql/planner/planprinter/TestJsonRepresentation.java index e784bae71ddf..c8fd6beb40db 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/planprinter/TestJsonRepresentation.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/planprinter/TestJsonRepresentation.java @@ -34,6 +34,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.Map; @@ -57,8 +58,10 @@ import static io.trino.testing.MaterializedResult.resultBuilder; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestJsonRepresentation { private static final JsonCodec> DISTRIBUTED_PLAN_JSON_CODEC = mapJsonCodec(String.class, JsonRenderedNode.class); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestAggregation.java b/core/trino-main/src/test/java/io/trino/sql/query/TestAggregation.java index ac3c524fb3f4..4cbc28f8bbbd 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestAggregation.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestAggregation.java @@ -16,12 +16,15 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestAggregation { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestAggregationOverJoin.java b/core/trino-main/src/test/java/io/trino/sql/query/TestAggregationOverJoin.java index 9879a37c166e..f6752e084604 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestAggregationOverJoin.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestAggregationOverJoin.java @@ -15,11 +15,14 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestAggregationOverJoin { @Test diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestAggregationsInRowPatternMatching.java b/core/trino-main/src/test/java/io/trino/sql/query/TestAggregationsInRowPatternMatching.java index a48e63020105..87b9f4e5a722 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestAggregationsInRowPatternMatching.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestAggregationsInRowPatternMatching.java @@ -16,11 +16,14 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestAggregationsInRowPatternMatching { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestArraySortAfterArrayDistinct.java b/core/trino-main/src/test/java/io/trino/sql/query/TestArraySortAfterArrayDistinct.java index 484566ca52ee..04f04ec76b65 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestArraySortAfterArrayDistinct.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestArraySortAfterArrayDistinct.java @@ -16,11 +16,14 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestArraySortAfterArrayDistinct { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestComplexTypesWithNull.java b/core/trino-main/src/test/java/io/trino/sql/query/TestComplexTypesWithNull.java index 4bb467150e6d..a0fbc02874b0 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestComplexTypesWithNull.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestComplexTypesWithNull.java @@ -16,14 +16,17 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; /** * Regression test for https://github.com/trinodb/trino/issues/9528 */ @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestComplexTypesWithNull { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestCopyAggregationStateInRowPatternMatching.java b/core/trino-main/src/test/java/io/trino/sql/query/TestCopyAggregationStateInRowPatternMatching.java index 6dc7f25671ce..92fa3b8e229c 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestCopyAggregationStateInRowPatternMatching.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestCopyAggregationStateInRowPatternMatching.java @@ -16,12 +16,15 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestCopyAggregationStateInRowPatternMatching { // at each step of matching, the threads are forked because of the alternation. diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestCorrelatedAggregation.java b/core/trino-main/src/test/java/io/trino/sql/query/TestCorrelatedAggregation.java index 1ffc3bfda70b..bed58ec6b7d8 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestCorrelatedAggregation.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestCorrelatedAggregation.java @@ -16,11 +16,14 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestCorrelatedAggregation { protected final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestCorrelatedJoin.java b/core/trino-main/src/test/java/io/trino/sql/query/TestCorrelatedJoin.java index 1228e7205d4e..9a64722955a5 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestCorrelatedJoin.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestCorrelatedJoin.java @@ -16,11 +16,14 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestCorrelatedJoin { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestDistinctAggregations.java b/core/trino-main/src/test/java/io/trino/sql/query/TestDistinctAggregations.java index 52d2b7110072..69ad22589ed1 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestDistinctAggregations.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestDistinctAggregations.java @@ -17,11 +17,14 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestDistinctAggregations { protected QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestDistinctWithOrderBy.java b/core/trino-main/src/test/java/io/trino/sql/query/TestDistinctWithOrderBy.java index 183f77c3f450..02023a2dd425 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestDistinctWithOrderBy.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestDistinctWithOrderBy.java @@ -16,12 +16,15 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestDistinctWithOrderBy { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestExecute.java b/core/trino-main/src/test/java/io/trino/sql/query/TestExecute.java index e20f2d30e656..87b0b36cccac 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestExecute.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestExecute.java @@ -17,11 +17,14 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestExecute { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestExecuteImmediate.java b/core/trino-main/src/test/java/io/trino/sql/query/TestExecuteImmediate.java index 3547fc2d5b06..66b9ef05b4ff 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestExecuteImmediate.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestExecuteImmediate.java @@ -17,13 +17,16 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestExecuteImmediate { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestExpressionRewriteInRowPatternMatching.java b/core/trino-main/src/test/java/io/trino/sql/query/TestExpressionRewriteInRowPatternMatching.java index ae6de558e889..4e06ffa17499 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestExpressionRewriteInRowPatternMatching.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestExpressionRewriteInRowPatternMatching.java @@ -16,11 +16,14 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestExpressionRewriteInRowPatternMatching { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestExpressions.java b/core/trino-main/src/test/java/io/trino/sql/query/TestExpressions.java index 9b415e6f202d..d7892e1a48a1 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestExpressions.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestExpressions.java @@ -16,12 +16,15 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestExpressions { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestFilterInaccessibleColumns.java b/core/trino-main/src/test/java/io/trino/sql/query/TestFilterInaccessibleColumns.java index c678938bdff2..fe94244b9830 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestFilterInaccessibleColumns.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestFilterInaccessibleColumns.java @@ -25,6 +25,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.plugin.tpch.TpchMetadata.TINY_SCHEMA_NAME; import static io.trino.testing.TestingAccessControlManager.TestingPrivilegeType.SELECT_COLUMN; @@ -34,8 +35,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestFilterInaccessibleColumns { private static final String USER = "user"; diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestFormat.java b/core/trino-main/src/test/java/io/trino/sql/query/TestFormat.java index 1f91163c3b46..44e141d30992 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestFormat.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestFormat.java @@ -16,12 +16,15 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestFormat { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestFullJoin.java b/core/trino-main/src/test/java/io/trino/sql/query/TestFullJoin.java index 87288dd1da41..fbe73bff2079 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestFullJoin.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestFullJoin.java @@ -16,11 +16,14 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestFullJoin { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestGroupBy.java b/core/trino-main/src/test/java/io/trino/sql/query/TestGroupBy.java index 1c0b7dda43f3..c3f02573f71d 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestGroupBy.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestGroupBy.java @@ -16,12 +16,15 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestGroupBy { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestGrouping.java b/core/trino-main/src/test/java/io/trino/sql/query/TestGrouping.java index 4c8f2e6d69f6..d619c7327b03 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestGrouping.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestGrouping.java @@ -16,11 +16,14 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestGrouping { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestGroupingSets.java b/core/trino-main/src/test/java/io/trino/sql/query/TestGroupingSets.java index eb5b90fabad9..4b7369be4d0d 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestGroupingSets.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestGroupingSets.java @@ -16,11 +16,14 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestGroupingSets { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestHaving.java b/core/trino-main/src/test/java/io/trino/sql/query/TestHaving.java index cd5a65dcd197..8f42c43674a4 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestHaving.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestHaving.java @@ -15,11 +15,14 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestHaving { @Test diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestIssue16101.java b/core/trino-main/src/test/java/io/trino/sql/query/TestIssue16101.java index 651eef7fcba7..dc3573302ff8 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestIssue16101.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestIssue16101.java @@ -19,13 +19,16 @@ import io.trino.testing.LocalQueryRunner; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.plugin.tpch.TpchMetadata.TINY_SCHEMA_NAME; import static io.trino.testing.TestingSession.testSessionBuilder; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestIssue16101 { @Test diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestJoin.java b/core/trino-main/src/test/java/io/trino/sql/query/TestJoin.java index 1c76280055c8..2e07a02974c4 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestJoin.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestJoin.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; @@ -34,8 +35,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestJoin { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestJoinUsing.java b/core/trino-main/src/test/java/io/trino/sql/query/TestJoinUsing.java index 55d42787e5e7..44c541341997 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestJoinUsing.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestJoinUsing.java @@ -16,12 +16,15 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestJoinUsing { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestJsonArrayFunction.java b/core/trino-main/src/test/java/io/trino/sql/query/TestJsonArrayFunction.java index 3375bf013cd3..cd2650fad015 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestJsonArrayFunction.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestJsonArrayFunction.java @@ -17,6 +17,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.nio.charset.Charset; @@ -28,8 +29,10 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestJsonArrayFunction { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestJsonExistsFunction.java b/core/trino-main/src/test/java/io/trino/sql/query/TestJsonExistsFunction.java index 7ef3a844364d..bc51646b523c 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestJsonExistsFunction.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestJsonExistsFunction.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.nio.charset.Charset; @@ -28,8 +29,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestJsonExistsFunction { private static final String INPUT = "[\"a\", \"b\", \"c\"]"; diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestJsonObjectFunction.java b/core/trino-main/src/test/java/io/trino/sql/query/TestJsonObjectFunction.java index ef7c4171a214..fa484e56e970 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestJsonObjectFunction.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestJsonObjectFunction.java @@ -17,6 +17,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.nio.charset.Charset; @@ -29,8 +30,10 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestJsonObjectFunction { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestJsonQueryFunction.java b/core/trino-main/src/test/java/io/trino/sql/query/TestJsonQueryFunction.java index 9a2cb19cdfdd..9a575945de25 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestJsonQueryFunction.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestJsonQueryFunction.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.nio.charset.Charset; @@ -29,8 +30,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestJsonQueryFunction { private static final String INPUT = "[\"a\", \"b\", \"c\"]"; diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestJsonValueFunction.java b/core/trino-main/src/test/java/io/trino/sql/query/TestJsonValueFunction.java index 4435d4870395..76e8aa2a7e89 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestJsonValueFunction.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestJsonValueFunction.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.nio.charset.Charset; @@ -29,8 +30,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestJsonValueFunction { private static final String INPUT = "[\"a\", \"b\", \"c\"]"; diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestLambdaExpressions.java b/core/trino-main/src/test/java/io/trino/sql/query/TestLambdaExpressions.java index a92bdd2698aa..4fce396eed86 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestLambdaExpressions.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestLambdaExpressions.java @@ -16,11 +16,14 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestLambdaExpressions { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestLateral.java b/core/trino-main/src/test/java/io/trino/sql/query/TestLateral.java index 65075b9ff43d..5fd23319c24d 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestLateral.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestLateral.java @@ -16,12 +16,15 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestLateral { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestListagg.java b/core/trino-main/src/test/java/io/trino/sql/query/TestListagg.java index 4e244540dd75..725a3b21f9f6 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestListagg.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestListagg.java @@ -18,14 +18,17 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.StandardErrorCode.EXCEEDED_FUNCTION_MEMORY_LIMIT; import static io.trino.spi.block.PageBuilderStatus.DEFAULT_MAX_PAGE_SIZE_IN_BYTES; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestListagg { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestMergeProjectWithValues.java b/core/trino-main/src/test/java/io/trino/sql/query/TestMergeProjectWithValues.java index 457a11765154..df79ac3e070d 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestMergeProjectWithValues.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestMergeProjectWithValues.java @@ -16,11 +16,14 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestMergeProjectWithValues { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestMinMaxNWindow.java b/core/trino-main/src/test/java/io/trino/sql/query/TestMinMaxNWindow.java index 617e92b9b2c3..608b57d24de4 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestMinMaxNWindow.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestMinMaxNWindow.java @@ -16,11 +16,14 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestMinMaxNWindow { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestNestedLogicalBinaryExpression.java b/core/trino-main/src/test/java/io/trino/sql/query/TestNestedLogicalBinaryExpression.java index cb80b9f91cbc..f5fc3c352c35 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestNestedLogicalBinaryExpression.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestNestedLogicalBinaryExpression.java @@ -20,17 +20,20 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.plugin.tpch.TpchMetadata.TINY_SCHEMA_NAME; import static io.trino.testing.TestingHandles.TEST_CATALOG_NAME; import static io.trino.testing.TestingSession.testSessionBuilder; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; /** * Regression test for https://github.com/trinodb/trino/issues/9250 */ @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestNestedLogicalBinaryExpression { private final QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestNumericalStability.java b/core/trino-main/src/test/java/io/trino/sql/query/TestNumericalStability.java index a030bff9430b..fe5ee3e22984 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestNumericalStability.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestNumericalStability.java @@ -16,11 +16,14 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestNumericalStability { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestOrderedAggregation.java b/core/trino-main/src/test/java/io/trino/sql/query/TestOrderedAggregation.java index 33c76b90224c..71a0721c709d 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestOrderedAggregation.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestOrderedAggregation.java @@ -16,12 +16,15 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestOrderedAggregation { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestPrecomputedHashes.java b/core/trino-main/src/test/java/io/trino/sql/query/TestPrecomputedHashes.java index 7b318ee987c4..6ba308a7ff40 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestPrecomputedHashes.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestPrecomputedHashes.java @@ -17,13 +17,16 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.SystemSessionProperties.OPTIMIZE_HASH_GENERATION; import static io.trino.testing.TestingSession.testSessionBuilder; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestPrecomputedHashes { private final QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestPredicatePushdown.java b/core/trino-main/src/test/java/io/trino/sql/query/TestPredicatePushdown.java index 5cd91c0926a1..7b44763ee7a8 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestPredicatePushdown.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestPredicatePushdown.java @@ -16,11 +16,14 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestPredicatePushdown { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestRecursiveCte.java b/core/trino-main/src/test/java/io/trino/sql/query/TestRecursiveCte.java index db947eba9229..49a0e5047e82 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestRecursiveCte.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestRecursiveCte.java @@ -17,14 +17,17 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.SystemSessionProperties.MAX_RECURSION_DEPTH; import static io.trino.SystemSessionProperties.getMaxRecursionDepth; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestRecursiveCte { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestReduceAgg.java b/core/trino-main/src/test/java/io/trino/sql/query/TestReduceAgg.java index 3ae97c85f8d6..acbaffa046f0 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestReduceAgg.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestReduceAgg.java @@ -16,11 +16,14 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestReduceAgg { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestRowPatternMatching.java b/core/trino-main/src/test/java/io/trino/sql/query/TestRowPatternMatching.java index 32c018b0ac42..43dc05412632 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestRowPatternMatching.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestRowPatternMatching.java @@ -16,13 +16,16 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestRowPatternMatching { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestRowPatternMatchingInWindow.java b/core/trino-main/src/test/java/io/trino/sql/query/TestRowPatternMatchingInWindow.java index 25e30a7efab7..deecd41790d2 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestRowPatternMatchingInWindow.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestRowPatternMatchingInWindow.java @@ -16,13 +16,16 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestRowPatternMatchingInWindow { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestSelectAll.java b/core/trino-main/src/test/java/io/trino/sql/query/TestSelectAll.java index 8ad54dc80239..673371b81c49 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestSelectAll.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestSelectAll.java @@ -17,14 +17,17 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestSelectAll { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestSessionFunctions.java b/core/trino-main/src/test/java/io/trino/sql/query/TestSessionFunctions.java index 80dc1ea9aa76..96ec1125a9ec 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestSessionFunctions.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestSessionFunctions.java @@ -19,6 +19,7 @@ import io.trino.sql.SqlPath; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Optional; import java.util.Set; @@ -28,8 +29,10 @@ import static java.util.stream.Collectors.joining; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestSessionFunctions { @Test diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestSetDigestFunctions.java b/core/trino-main/src/test/java/io/trino/sql/query/TestSetDigestFunctions.java index b6a8c3d5c03e..20a72f6c0d53 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestSetDigestFunctions.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestSetDigestFunctions.java @@ -16,11 +16,14 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestSetDigestFunctions { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestSetOperations.java b/core/trino-main/src/test/java/io/trino/sql/query/TestSetOperations.java index c0e50ce95421..96df390fbb79 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestSetOperations.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestSetOperations.java @@ -16,11 +16,14 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestSetOperations { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestShowQueries.java b/core/trino-main/src/test/java/io/trino/sql/query/TestShowQueries.java index ff2f42778a21..2cce17d3179d 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestShowQueries.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestShowQueries.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.testing.TestingSession.testSessionBuilder; @@ -28,8 +29,10 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestShowQueries { private final QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestSubqueries.java b/core/trino-main/src/test/java/io/trino/sql/query/TestSubqueries.java index e0efb844c079..9c1f1eece65f 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestSubqueries.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestSubqueries.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.Optional; @@ -52,8 +53,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestSubqueries { private static final String UNSUPPORTED_CORRELATED_SUBQUERY_ERROR_MSG = "line .*: Given correlated subquery is not supported"; diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestTDigestFunctions.java b/core/trino-main/src/test/java/io/trino/sql/query/TestTDigestFunctions.java index a80fa5294aa3..c8d4a272d01e 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestTDigestFunctions.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestTDigestFunctions.java @@ -17,6 +17,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.ArrayList; import java.util.List; @@ -26,8 +27,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestTDigestFunctions { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestTrim.java b/core/trino-main/src/test/java/io/trino/sql/query/TestTrim.java index 73f52444774e..003869395e05 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestTrim.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestTrim.java @@ -20,13 +20,16 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.StandardErrorCode.INVALID_FUNCTION_ARGUMENT; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestTrim { private final QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestUnnest.java b/core/trino-main/src/test/java/io/trino/sql/query/TestUnnest.java index c0e134dd9c8b..71f4731a9aea 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestUnnest.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestUnnest.java @@ -16,12 +16,15 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestUnnest { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestUnwrapCastInComparison.java b/core/trino-main/src/test/java/io/trino/sql/query/TestUnwrapCastInComparison.java index af53dd983665..d9305890c22e 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestUnwrapCastInComparison.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestUnwrapCastInComparison.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.time.LocalDateTime; import java.time.LocalTime; @@ -31,8 +32,10 @@ import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestUnwrapCastInComparison { private static final List COMPARISON_OPERATORS = asList("=", "<>", ">=", ">", "<=", "<", "IS DISTINCT FROM"); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestValues.java b/core/trino-main/src/test/java/io/trino/sql/query/TestValues.java index 70092cb94892..27702f59a6b9 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestValues.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestValues.java @@ -16,13 +16,16 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.StandardErrorCode.DIVISION_BY_ZERO; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestValues { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestWindow.java b/core/trino-main/src/test/java/io/trino/sql/query/TestWindow.java index acf8abc74132..9996aae42631 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestWindow.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestWindow.java @@ -17,11 +17,14 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestWindow { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestWindowFrameGroups.java b/core/trino-main/src/test/java/io/trino/sql/query/TestWindowFrameGroups.java index 6478b877a708..e2af17ca2515 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestWindowFrameGroups.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestWindowFrameGroups.java @@ -16,6 +16,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.math.BigInteger; @@ -24,8 +25,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestWindowFrameGroups { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestWindowFrameRange.java b/core/trino-main/src/test/java/io/trino/sql/query/TestWindowFrameRange.java index 8f4395b1d57e..5b3e81a5b9e9 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestWindowFrameRange.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestWindowFrameRange.java @@ -16,12 +16,15 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestWindowFrameRange { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestWindowFrameRows.java b/core/trino-main/src/test/java/io/trino/sql/query/TestWindowFrameRows.java index 6c8eaf913530..68aa205d39ac 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestWindowFrameRows.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestWindowFrameRows.java @@ -16,6 +16,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.math.BigInteger; @@ -23,8 +24,10 @@ import static java.math.BigInteger.ONE; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestWindowFrameRows { private final QueryAssertions assertions = new QueryAssertions(); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestWith.java b/core/trino-main/src/test/java/io/trino/sql/query/TestWith.java index 9643538b567a..e335746f2f4e 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestWith.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestWith.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.plugin.tpch.TpchMetadata.TINY_SCHEMA_NAME; import static io.trino.testing.TestingHandles.TEST_CATALOG_NAME; @@ -28,8 +29,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestWith { private final QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/transaction/TestTransactionManager.java b/core/trino-main/src/test/java/io/trino/transaction/TestTransactionManager.java index 793a4ba66188..1ea3c19ac3a5 100644 --- a/core/trino-main/src/test/java/io/trino/transaction/TestTransactionManager.java +++ b/core/trino-main/src/test/java/io/trino/transaction/TestTransactionManager.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.Closeable; import java.util.concurrent.ExecutorService; @@ -41,11 +42,13 @@ import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestTransactionManager { private final ExecutorService finishingExecutor = newCachedThreadPool(daemonThreadsNamed("transaction-%s")); diff --git a/core/trino-main/src/test/java/io/trino/type/TestArrayOperators.java b/core/trino-main/src/test/java/io/trino/type/TestArrayOperators.java index f5927936838c..8442f7ac0173 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestArrayOperators.java +++ b/core/trino-main/src/test/java/io/trino/type/TestArrayOperators.java @@ -35,6 +35,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Collections; @@ -80,9 +81,11 @@ import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestArrayOperators { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/type/TestBigintOperators.java b/core/trino-main/src/test/java/io/trino/type/TestBigintOperators.java index 15b3f8a79bea..ef29de038842 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestBigintOperators.java +++ b/core/trino-main/src/test/java/io/trino/type/TestBigintOperators.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.StandardErrorCode.INVALID_CAST_ARGUMENT; import static io.trino.spi.StandardErrorCode.NUMERIC_VALUE_OUT_OF_RANGE; @@ -38,8 +39,10 @@ import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestBigintOperators { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/type/TestBooleanOperators.java b/core/trino-main/src/test/java/io/trino/type/TestBooleanOperators.java index 3aa735760ce2..8ee0f96bad33 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestBooleanOperators.java +++ b/core/trino-main/src/test/java/io/trino/type/TestBooleanOperators.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.function.OperatorType.EQUAL; import static io.trino.spi.function.OperatorType.INDETERMINATE; @@ -27,8 +28,10 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestBooleanOperators { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/type/TestCastDependencies.java b/core/trino-main/src/test/java/io/trino/type/TestCastDependencies.java index 970b2a0a7331..2d9c0f281735 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestCastDependencies.java +++ b/core/trino-main/src/test/java/io/trino/type/TestCastDependencies.java @@ -26,6 +26,7 @@ import io.trino.sql.query.QueryAssertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.lang.invoke.MethodHandle; @@ -38,8 +39,10 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestCastDependencies { @Test diff --git a/core/trino-main/src/test/java/io/trino/type/TestCharOperators.java b/core/trino-main/src/test/java/io/trino/type/TestCharOperators.java index 2b49dbe0ddc4..15c1dcb68404 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestCharOperators.java +++ b/core/trino-main/src/test/java/io/trino/type/TestCharOperators.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.function.OperatorType.EQUAL; import static io.trino.spi.function.OperatorType.INDETERMINATE; @@ -26,8 +27,10 @@ import static io.trino.spi.function.OperatorType.LESS_THAN_OR_EQUAL; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestCharOperators { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/type/TestCharParametricType.java b/core/trino-main/src/test/java/io/trino/type/TestCharParametricType.java index e61f02eefa74..440733a6cdc4 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestCharParametricType.java +++ b/core/trino-main/src/test/java/io/trino/type/TestCharParametricType.java @@ -16,12 +16,15 @@ import io.trino.sql.query.QueryAssertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.type.CharType.createCharType; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestCharParametricType { @Test diff --git a/core/trino-main/src/test/java/io/trino/type/TestCharacterStringCasts.java b/core/trino-main/src/test/java/io/trino/type/TestCharacterStringCasts.java index 788abdbbeb50..8b1a6e45f4be 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestCharacterStringCasts.java +++ b/core/trino-main/src/test/java/io/trino/type/TestCharacterStringCasts.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.airlift.slice.Slices.utf8Slice; import static io.trino.operator.scalar.CharacterStringCasts.varcharToCharSaturatedFloorCast; @@ -27,9 +28,11 @@ import static io.trino.spi.type.VarcharType.createVarcharType; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestCharacterStringCasts { private static final String NON_BMP_CHARACTER = new String(Character.toChars(0x1F50D)); diff --git a/core/trino-main/src/test/java/io/trino/type/TestConventionDependencies.java b/core/trino-main/src/test/java/io/trino/type/TestConventionDependencies.java index 309179070ac6..98452055cc4d 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestConventionDependencies.java +++ b/core/trino-main/src/test/java/io/trino/type/TestConventionDependencies.java @@ -30,6 +30,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.lang.invoke.MethodHandle; @@ -44,8 +45,10 @@ import static io.trino.spi.type.IntegerType.INTEGER; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestConventionDependencies { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/type/TestDate.java b/core/trino-main/src/test/java/io/trino/type/TestDate.java index 466bb0d18cb3..9d38e5e19553 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestDate.java +++ b/core/trino-main/src/test/java/io/trino/type/TestDate.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.concurrent.TimeUnit; @@ -42,8 +43,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.joda.time.DateTimeZone.UTC; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestDate { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/type/TestDecimalCasts.java b/core/trino-main/src/test/java/io/trino/type/TestDecimalCasts.java index 1cb9ea8c1109..afe68c1987fb 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestDecimalCasts.java +++ b/core/trino-main/src/test/java/io/trino/type/TestDecimalCasts.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.StandardErrorCode.INVALID_CAST_ARGUMENT; import static io.trino.spi.type.DecimalType.createDecimalType; @@ -27,8 +28,10 @@ import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestDecimalCasts { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/type/TestDecimalOperators.java b/core/trino-main/src/test/java/io/trino/type/TestDecimalOperators.java index 463621588d4b..00dae74a0ac2 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestDecimalOperators.java +++ b/core/trino-main/src/test/java/io/trino/type/TestDecimalOperators.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.StandardErrorCode.DIVISION_BY_ZERO; import static io.trino.spi.StandardErrorCode.NUMERIC_VALUE_OUT_OF_RANGE; @@ -40,8 +41,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestDecimalOperators { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/type/TestDecimalParametricType.java b/core/trino-main/src/test/java/io/trino/type/TestDecimalParametricType.java index cd66b8736dba..8458526fb19e 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestDecimalParametricType.java +++ b/core/trino-main/src/test/java/io/trino/type/TestDecimalParametricType.java @@ -18,14 +18,17 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.type.DecimalType.createDecimalType; import static io.trino.spi.type.SqlDecimal.decimal; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestDecimalParametricType { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/type/TestDecimalToDecimalCasts.java b/core/trino-main/src/test/java/io/trino/type/TestDecimalToDecimalCasts.java index 09fe42fcb3c9..f49b2502bdd6 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestDecimalToDecimalCasts.java +++ b/core/trino-main/src/test/java/io/trino/type/TestDecimalToDecimalCasts.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.StandardErrorCode.INVALID_CAST_ARGUMENT; import static io.trino.spi.type.DecimalType.createDecimalType; @@ -25,8 +26,10 @@ import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestDecimalToDecimalCasts { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/type/TestDoubleOperators.java b/core/trino-main/src/test/java/io/trino/type/TestDoubleOperators.java index cfad0b81e47f..85a10b313519 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestDoubleOperators.java +++ b/core/trino-main/src/test/java/io/trino/type/TestDoubleOperators.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.lang.invoke.MethodHandle; @@ -46,10 +47,12 @@ import static java.lang.Double.longBitsToDouble; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestDoubleOperators { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/type/TestInstanceFunction.java b/core/trino-main/src/test/java/io/trino/type/TestInstanceFunction.java index 00b3b71d19a4..1ed1128ab70e 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestInstanceFunction.java +++ b/core/trino-main/src/test/java/io/trino/type/TestInstanceFunction.java @@ -20,11 +20,14 @@ import io.trino.sql.query.QueryAssertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestInstanceFunction { @Test diff --git a/core/trino-main/src/test/java/io/trino/type/TestIntegerOperators.java b/core/trino-main/src/test/java/io/trino/type/TestIntegerOperators.java index 6d8ca7d15639..4b1f835b5305 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestIntegerOperators.java +++ b/core/trino-main/src/test/java/io/trino/type/TestIntegerOperators.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.StandardErrorCode.DIVISION_BY_ZERO; import static io.trino.spi.StandardErrorCode.INVALID_CAST_ARGUMENT; @@ -40,8 +41,10 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestIntegerOperators { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/type/TestIntervalDayTime.java b/core/trino-main/src/test/java/io/trino/type/TestIntervalDayTime.java index 98fa41ef6f11..7f54877fed69 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestIntervalDayTime.java +++ b/core/trino-main/src/test/java/io/trino/type/TestIntervalDayTime.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.StandardErrorCode.INVALID_FUNCTION_ARGUMENT; import static io.trino.spi.function.OperatorType.ADD; @@ -34,9 +35,11 @@ import static java.util.concurrent.TimeUnit.DAYS; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestIntervalDayTime { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/type/TestIntervalYearMonth.java b/core/trino-main/src/test/java/io/trino/type/TestIntervalYearMonth.java index 5204f1cbc567..e17324c209ea 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestIntervalYearMonth.java +++ b/core/trino-main/src/test/java/io/trino/type/TestIntervalYearMonth.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.StandardErrorCode.INVALID_FUNCTION_ARGUMENT; import static io.trino.spi.function.OperatorType.ADD; @@ -33,9 +34,11 @@ import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestIntervalYearMonth { private static final int MAX_SHORT = Short.MAX_VALUE; diff --git a/core/trino-main/src/test/java/io/trino/type/TestIpAddressOperators.java b/core/trino-main/src/test/java/io/trino/type/TestIpAddressOperators.java index e44e893be653..c3deb891fee0 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestIpAddressOperators.java +++ b/core/trino-main/src/test/java/io/trino/type/TestIpAddressOperators.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.StandardErrorCode.INVALID_CAST_ARGUMENT; import static io.trino.spi.function.OperatorType.EQUAL; @@ -33,8 +34,10 @@ import static io.trino.type.IpAddressType.IPADDRESS; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestIpAddressOperators { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/type/TestJsonOperators.java b/core/trino-main/src/test/java/io/trino/type/TestJsonOperators.java index 1357eb6c57c7..535ec50688b8 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestJsonOperators.java +++ b/core/trino-main/src/test/java/io/trino/type/TestJsonOperators.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.StandardErrorCode.INVALID_CAST_ARGUMENT; import static io.trino.spi.StandardErrorCode.INVALID_FUNCTION_ARGUMENT; @@ -48,8 +49,10 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestJsonOperators { // Some of the tests in this class are expected to fail when coercion between primitive Trino types changes behavior diff --git a/core/trino-main/src/test/java/io/trino/type/TestMapOperators.java b/core/trino-main/src/test/java/io/trino/type/TestMapOperators.java index 9b1f905fbe42..4c189b797992 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestMapOperators.java +++ b/core/trino-main/src/test/java/io/trino/type/TestMapOperators.java @@ -30,6 +30,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.HashMap; import java.util.List; @@ -68,8 +69,10 @@ import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestMapOperators { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/type/TestRealOperators.java b/core/trino-main/src/test/java/io/trino/type/TestRealOperators.java index d999bad805f1..bbf8e2a0bd3a 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestRealOperators.java +++ b/core/trino-main/src/test/java/io/trino/type/TestRealOperators.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.lang.invoke.MethodHandle; @@ -45,10 +46,12 @@ import static java.lang.Float.isNaN; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestRealOperators { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/type/TestRowOperators.java b/core/trino-main/src/test/java/io/trino/type/TestRowOperators.java index 8d552121f4ef..afed7687dd03 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestRowOperators.java +++ b/core/trino-main/src/test/java/io/trino/type/TestRowOperators.java @@ -36,6 +36,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.ArrayList; import java.util.Arrays; @@ -77,9 +78,11 @@ import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestRowOperators { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/type/TestSmallintOperators.java b/core/trino-main/src/test/java/io/trino/type/TestSmallintOperators.java index 97b43a04fb1e..de8cfd241587 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestSmallintOperators.java +++ b/core/trino-main/src/test/java/io/trino/type/TestSmallintOperators.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.StandardErrorCode.DIVISION_BY_ZERO; import static io.trino.spi.StandardErrorCode.INVALID_CAST_ARGUMENT; @@ -41,8 +42,10 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestSmallintOperators { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/type/TestTinyintOperators.java b/core/trino-main/src/test/java/io/trino/type/TestTinyintOperators.java index 07e69d8801b0..a6a04464413c 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestTinyintOperators.java +++ b/core/trino-main/src/test/java/io/trino/type/TestTinyintOperators.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.StandardErrorCode.DIVISION_BY_ZERO; import static io.trino.spi.StandardErrorCode.INVALID_CAST_ARGUMENT; @@ -39,8 +40,10 @@ import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestTinyintOperators { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/type/TestUnknownOperators.java b/core/trino-main/src/test/java/io/trino/type/TestUnknownOperators.java index ae94529e6735..452f283913cc 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestUnknownOperators.java +++ b/core/trino-main/src/test/java/io/trino/type/TestUnknownOperators.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.function.OperatorType.EQUAL; import static io.trino.spi.function.OperatorType.INDETERMINATE; @@ -35,8 +36,10 @@ import static io.trino.type.UnknownType.UNKNOWN; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestUnknownOperators { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/type/TestUuidOperators.java b/core/trino-main/src/test/java/io/trino/type/TestUuidOperators.java index d2c72da1b329..09a536af99f2 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestUuidOperators.java +++ b/core/trino-main/src/test/java/io/trino/type/TestUuidOperators.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.StandardErrorCode.INVALID_CAST_ARGUMENT; import static io.trino.spi.function.OperatorType.COMPARISON_UNORDERED_LAST; @@ -33,8 +34,10 @@ import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestUuidOperators { private QueryAssertions assertions; diff --git a/core/trino-main/src/test/java/io/trino/type/TestVarcharOperators.java b/core/trino-main/src/test/java/io/trino/type/TestVarcharOperators.java index 0736c7ddb7b8..854196e5c5b5 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestVarcharOperators.java +++ b/core/trino-main/src/test/java/io/trino/type/TestVarcharOperators.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.function.OperatorType.EQUAL; import static io.trino.spi.function.OperatorType.INDETERMINATE; @@ -28,8 +29,10 @@ import static io.trino.spi.type.VarcharType.createVarcharType; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestVarcharOperators { private QueryAssertions assertions; diff --git a/lib/trino-filesystem/src/test/java/io/trino/filesystem/AbstractTestTrinoFileSystem.java b/lib/trino-filesystem/src/test/java/io/trino/filesystem/AbstractTestTrinoFileSystem.java index 3fd8d6b3911d..82b42b052158 100644 --- a/lib/trino-filesystem/src/test/java/io/trino/filesystem/AbstractTestTrinoFileSystem.java +++ b/lib/trino-filesystem/src/test/java/io/trino/filesystem/AbstractTestTrinoFileSystem.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.api.parallel.Execution; import java.io.Closeable; import java.io.EOFException; @@ -44,8 +45,10 @@ import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; @TestInstance(Lifecycle.PER_CLASS) +@Execution(SAME_THREAD) public abstract class AbstractTestTrinoFileSystem { protected static final String TEST_BLOB_CONTENT_PREFIX = "test blob content for "; diff --git a/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/avro/TestAvroBase.java b/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/avro/TestAvroBase.java index 623761e79d76..6a2fff33bdd8 100644 --- a/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/avro/TestAvroBase.java +++ b/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/avro/TestAvroBase.java @@ -57,6 +57,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.IOException; import java.io.UncheckedIOException; @@ -85,8 +86,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.within; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public abstract class TestAvroBase { protected static final TypeOperators TYPE_OPERATORS = new TypeOperators(); diff --git a/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/rcfile/AbstractTestRcFileReader.java b/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/rcfile/AbstractTestRcFileReader.java index 41476ba1a3ac..bb3cf9e07e55 100644 --- a/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/rcfile/AbstractTestRcFileReader.java +++ b/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/rcfile/AbstractTestRcFileReader.java @@ -25,6 +25,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.math.BigInteger; import java.util.ArrayList; @@ -51,8 +52,10 @@ import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public abstract class AbstractTestRcFileReader { private static final DecimalType DECIMAL_TYPE_PRECISION_2 = DecimalType.createDecimalType(2, 1); diff --git a/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/index/TestIndexer.java b/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/index/TestIndexer.java index dacc8fce0505..26fd8c0a569e 100644 --- a/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/index/TestIndexer.java +++ b/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/index/TestIndexer.java @@ -36,6 +36,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Iterator; import java.util.Map.Entry; @@ -45,10 +46,12 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestIndexer { private static final LexicoderRowSerializer SERIALIZER = new LexicoderRowSerializer(); diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/BaseCaseInsensitiveMappingTest.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/BaseCaseInsensitiveMappingTest.java index 5fc3f0d9c401..90d5cffe669e 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/BaseCaseInsensitiveMappingTest.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/BaseCaseInsensitiveMappingTest.java @@ -23,6 +23,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; import java.nio.file.Path; import java.util.List; @@ -40,6 +42,7 @@ // Tests are using JSON based identifier mapping which is one for all tests @TestInstance(PER_CLASS) +@Execution(ExecutionMode.SAME_THREAD) public abstract class BaseCaseInsensitiveMappingTest extends AbstractTestQueryFramework { diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestJdbcFlushMetadataCacheProcedure.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestJdbcFlushMetadataCacheProcedure.java index 8fd09f0a4b8e..6d2b8d63a04f 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestJdbcFlushMetadataCacheProcedure.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestJdbcFlushMetadataCacheProcedure.java @@ -18,6 +18,7 @@ import io.trino.testing.QueryRunner; import io.trino.testing.sql.JdbcSqlExecutor; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.Map; @@ -26,7 +27,9 @@ import static io.trino.plugin.jdbc.H2QueryRunner.createH2QueryRunner; import static io.trino.tpch.TpchTable.NATION; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; +@Execution(SAME_THREAD) public class TestJdbcFlushMetadataCacheProcedure extends AbstractTestQueryFramework { diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestJdbcRecordSet.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestJdbcRecordSet.java index fd88605969c9..c252771ce6ab 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestJdbcRecordSet.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestJdbcRecordSet.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.LinkedHashMap; import java.util.List; @@ -38,8 +39,10 @@ import static io.trino.testing.TestingConnectorSession.SESSION; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestJdbcRecordSet { private TestingDatabase database; diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestJdbcRecordSetProvider.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestJdbcRecordSetProvider.java index 91e664ebf19f..dd6382beaaa6 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestJdbcRecordSetProvider.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestJdbcRecordSetProvider.java @@ -31,6 +31,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.LinkedHashMap; import java.util.List; @@ -49,8 +50,10 @@ import static io.trino.spi.type.VarcharType.createVarcharType; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestJdbcRecordSetProvider { private static final ConnectorSession SESSION = TestingConnectorSession.builder() diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryInstanceCleaner.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryInstanceCleaner.java index bcd4d2f966c6..08627653c111 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryInstanceCleaner.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryInstanceCleaner.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.AbstractMap.SimpleImmutableEntry; import java.util.Collection; @@ -37,8 +38,10 @@ import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toUnmodifiableSet; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestBigQueryInstanceCleaner { public static final Logger LOG = Logger.get(TestBigQueryInstanceCleaner.class); diff --git a/plugin/trino-blackhole/src/test/java/io/trino/plugin/blackhole/TestBlackHoleSmoke.java b/plugin/trino-blackhole/src/test/java/io/trino/plugin/blackhole/TestBlackHoleSmoke.java index 3597c9b91f11..1c5aa32cbebc 100644 --- a/plugin/trino-blackhole/src/test/java/io/trino/plugin/blackhole/TestBlackHoleSmoke.java +++ b/plugin/trino-blackhole/src/test/java/io/trino/plugin/blackhole/TestBlackHoleSmoke.java @@ -26,6 +26,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.math.BigDecimal; import java.time.LocalDate; @@ -48,8 +49,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; @TestInstance(PER_CLASS) +@Execution(SAME_THREAD) public class TestBlackHoleSmoke { private QueryRunner queryRunner; diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/BaseCassandraConnectorSmokeTest.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/BaseCassandraConnectorSmokeTest.java index 1196d712691f..d869aef5be23 100644 --- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/BaseCassandraConnectorSmokeTest.java +++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/BaseCassandraConnectorSmokeTest.java @@ -17,6 +17,7 @@ import io.trino.testing.TestingConnectorBehavior; import io.trino.testing.sql.TestTable; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; import java.time.ZoneId; import java.time.ZonedDateTime; @@ -26,6 +27,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +@Isolated public abstract class BaseCassandraConnectorSmokeTest extends BaseConnectorSmokeTest { diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnector.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnector.java index 44bcedea1250..b77504ebf5dd 100644 --- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnector.java +++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnector.java @@ -52,6 +52,7 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Isolated; import java.net.InetAddress; import java.net.UnknownHostException; @@ -94,6 +95,7 @@ import static org.testng.Assert.fail; @TestInstance(PER_CLASS) +@Isolated public class TestCassandraConnector { protected static final String INVALID_DATABASE = "totally_invalid_database"; diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraTokenSplitManager.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraTokenSplitManager.java index 2cb21ddd41a1..549fe159f184 100644 --- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraTokenSplitManager.java +++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraTokenSplitManager.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.Optional; @@ -26,9 +27,11 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestCassandraTokenSplitManager { private static final int SPLIT_SIZE = 100; diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeCompatibility.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeCompatibility.java index 3a9ed4155419..b7eea3ba6e04 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeCompatibility.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeCompatibility.java @@ -19,6 +19,7 @@ import io.trino.testing.QueryRunner; import io.trino.tpch.TpchTable; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; import static io.trino.plugin.deltalake.DeltaLakeQueryRunner.DELTA_CATALOG; import static io.trino.plugin.deltalake.DeltaLakeQueryRunner.createS3DeltaLakeQueryRunner; @@ -27,6 +28,7 @@ import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; +@Isolated public abstract class BaseDeltaLakeCompatibility extends AbstractTestQueryFramework { diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeBasic.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeBasic.java index f509397f05a0..6ce5295fd046 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeBasic.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeBasic.java @@ -46,6 +46,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.io.IOException; @@ -80,9 +81,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertFalse; @TestInstance(PER_CLASS) +@Execution(SAME_THREAD) public class TestDeltaLakeBasic extends AbstractTestQueryFramework { diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeCreateTableStatistics.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeCreateTableStatistics.java index d05d12edce43..d9d705864826 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeCreateTableStatistics.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeCreateTableStatistics.java @@ -27,6 +27,7 @@ import io.trino.testing.DistributedQueryRunner; import io.trino.testing.QueryRunner; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; import java.io.IOException; import java.math.BigDecimal; @@ -59,6 +60,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.testng.Assert.assertEquals; +@Isolated public class TestDeltaLakeCreateTableStatistics extends AbstractTestQueryFramework { diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeDelete.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeDelete.java index b50bbf9b4790..17304676ba8f 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeDelete.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeDelete.java @@ -20,6 +20,7 @@ import io.trino.testing.QueryRunner; import io.trino.tpch.TpchTable; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; import java.util.Set; @@ -29,6 +30,7 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; +@Isolated public class TestDeltaLakeDelete extends AbstractTestQueryFramework { diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeDynamicFiltering.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeDynamicFiltering.java index 647c88e48f4e..9665cdb4cea4 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeDynamicFiltering.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeDynamicFiltering.java @@ -39,6 +39,7 @@ import io.trino.transaction.TransactionManager; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.parallel.Isolated; import java.util.ArrayList; import java.util.List; @@ -62,6 +63,7 @@ import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; +@Isolated public class TestDeltaLakeDynamicFiltering extends AbstractTestQueryFramework { diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeGcsConnectorSmokeTest.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeGcsConnectorSmokeTest.java index 7a8234bf9d8e..c99d9616e964 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeGcsConnectorSmokeTest.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeGcsConnectorSmokeTest.java @@ -31,6 +31,7 @@ import org.apache.hadoop.conf.Configuration; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.FileNotFoundException; import java.io.IOException; @@ -53,6 +54,7 @@ import static java.util.Objects.requireNonNull; import static java.util.regex.Matcher.quoteReplacement; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testcontainers.containers.Network.newNetwork; /** @@ -62,6 +64,7 @@ * For example, `cat service-account-key.json | base64` */ @TestInstance(PER_CLASS) +@Execution(SAME_THREAD) public class TestDeltaLakeGcsConnectorSmokeTest extends BaseDeltaLakeConnectorSmokeTest { diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeMetadata.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeMetadata.java index 267eb2f41ffc..4f6e5fad33b5 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeMetadata.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeMetadata.java @@ -67,6 +67,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.io.IOException; @@ -97,8 +98,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestDeltaLakeMetadata { private static final String DATABASE_NAME = "mock_database"; diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeGlueMetastore.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeGlueMetastore.java index b908671c6005..8f5cadb08bb1 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeGlueMetastore.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeGlueMetastore.java @@ -53,6 +53,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.io.IOException; @@ -87,8 +88,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestDeltaLakeGlueMetastore { private File tempDir; diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointEntryIterator.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointEntryIterator.java index 5cd49f81ac57..d31ca20fe438 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointEntryIterator.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointEntryIterator.java @@ -36,6 +36,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.io.IOException; @@ -63,8 +64,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestCheckpointEntryIterator { private static final String TEST_CHECKPOINT = "databricks73/person/_delta_log/00000000000000000010.checkpoint.parquet"; diff --git a/plugin/trino-druid/src/test/java/io/trino/plugin/druid/TestDruidTypeMapping.java b/plugin/trino-druid/src/test/java/io/trino/plugin/druid/TestDruidTypeMapping.java index 3be97ff70345..a13cea76c9b0 100644 --- a/plugin/trino-druid/src/test/java/io/trino/plugin/druid/TestDruidTypeMapping.java +++ b/plugin/trino-druid/src/test/java/io/trino/plugin/druid/TestDruidTypeMapping.java @@ -24,6 +24,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Isolated; import java.io.BufferedWriter; import java.io.FileWriter; @@ -44,6 +45,7 @@ import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; @TestInstance(PER_CLASS) +@Isolated public class TestDruidTypeMapping extends AbstractTestQueryFramework { diff --git a/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/TestPasswordAuthentication.java b/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/TestPasswordAuthentication.java index a9ac0b5f6caf..14cb228e229a 100644 --- a/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/TestPasswordAuthentication.java +++ b/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/TestPasswordAuthentication.java @@ -31,6 +31,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -42,8 +43,10 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestPasswordAuthentication { private static final String USER = "elastic_user"; diff --git a/plugin/trino-example-http/src/test/java/io/trino/plugin/example/TestExampleRecordSet.java b/plugin/trino-example-http/src/test/java/io/trino/plugin/example/TestExampleRecordSet.java index 2e1932d276b8..3eeb11551c42 100644 --- a/plugin/trino-example-http/src/test/java/io/trino/plugin/example/TestExampleRecordSet.java +++ b/plugin/trino-example-http/src/test/java/io/trino/plugin/example/TestExampleRecordSet.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.LinkedHashMap; import java.util.Map; @@ -29,8 +30,10 @@ import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestExampleRecordSet { private ExampleHttpServer exampleHttpServer; diff --git a/plugin/trino-example-http/src/test/java/io/trino/plugin/example/TestExampleRecordSetProvider.java b/plugin/trino-example-http/src/test/java/io/trino/plugin/example/TestExampleRecordSetProvider.java index f4b0e6b8041b..6362d8a74d1c 100644 --- a/plugin/trino-example-http/src/test/java/io/trino/plugin/example/TestExampleRecordSetProvider.java +++ b/plugin/trino-example-http/src/test/java/io/trino/plugin/example/TestExampleRecordSetProvider.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.LinkedHashMap; import java.util.Map; @@ -31,8 +32,10 @@ import static io.trino.testing.TestingConnectorSession.SESSION; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestExampleRecordSetProvider { private ExampleHttpServer exampleHttpServer; diff --git a/plugin/trino-exchange-filesystem/src/test/java/io/trino/plugin/exchange/filesystem/AbstractTestExchangeManager.java b/plugin/trino-exchange-filesystem/src/test/java/io/trino/plugin/exchange/filesystem/AbstractTestExchangeManager.java index 74a7584a1139..ef75b9f49df9 100644 --- a/plugin/trino-exchange-filesystem/src/test/java/io/trino/plugin/exchange/filesystem/AbstractTestExchangeManager.java +++ b/plugin/trino-exchange-filesystem/src/test/java/io/trino/plugin/exchange/filesystem/AbstractTestExchangeManager.java @@ -37,6 +37,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.ArrayDeque; import java.util.List; @@ -56,8 +57,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public abstract class AbstractTestExchangeManager { private ExchangeManager exchangeManager; diff --git a/plugin/trino-geospatial/src/test/java/io/trino/plugin/geospatial/TestBingTileFunctions.java b/plugin/trino-geospatial/src/test/java/io/trino/plugin/geospatial/TestBingTileFunctions.java index 230a916fb85c..e38fe7fde072 100644 --- a/plugin/trino-geospatial/src/test/java/io/trino/plugin/geospatial/TestBingTileFunctions.java +++ b/plugin/trino-geospatial/src/test/java/io/trino/plugin/geospatial/TestBingTileFunctions.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.nio.file.Files; @@ -43,8 +44,10 @@ import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestBingTileFunctions { private QueryAssertions assertions; diff --git a/plugin/trino-geospatial/src/test/java/io/trino/plugin/geospatial/TestEncodedPolylineFunctions.java b/plugin/trino-geospatial/src/test/java/io/trino/plugin/geospatial/TestEncodedPolylineFunctions.java index 78d9750d7b10..805c07ff6a95 100644 --- a/plugin/trino-geospatial/src/test/java/io/trino/plugin/geospatial/TestEncodedPolylineFunctions.java +++ b/plugin/trino-geospatial/src/test/java/io/trino/plugin/geospatial/TestEncodedPolylineFunctions.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.plugin.geospatial.GeometryType.GEOMETRY; import static io.trino.spi.StandardErrorCode.INVALID_FUNCTION_ARGUMENT; @@ -25,8 +26,10 @@ import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestEncodedPolylineFunctions { private QueryAssertions assertions; diff --git a/plugin/trino-geospatial/src/test/java/io/trino/plugin/geospatial/TestGeoFunctions.java b/plugin/trino-geospatial/src/test/java/io/trino/plugin/geospatial/TestGeoFunctions.java index cd43698c6ee7..96e7b5765316 100644 --- a/plugin/trino-geospatial/src/test/java/io/trino/plugin/geospatial/TestGeoFunctions.java +++ b/plugin/trino-geospatial/src/test/java/io/trino/plugin/geospatial/TestGeoFunctions.java @@ -27,6 +27,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Arrays; import java.util.List; @@ -42,8 +43,10 @@ import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestGeoFunctions { private QueryAssertions assertions; diff --git a/plugin/trino-geospatial/src/test/java/io/trino/plugin/geospatial/TestKdbTreeCasts.java b/plugin/trino-geospatial/src/test/java/io/trino/plugin/geospatial/TestKdbTreeCasts.java index 81bbef7e19eb..982c2925761b 100644 --- a/plugin/trino-geospatial/src/test/java/io/trino/plugin/geospatial/TestKdbTreeCasts.java +++ b/plugin/trino-geospatial/src/test/java/io/trino/plugin/geospatial/TestKdbTreeCasts.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.geospatial.KdbTree.buildKdbTree; import static io.trino.spi.StandardErrorCode.INVALID_CAST_ARGUMENT; @@ -29,8 +30,10 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestKdbTreeCasts { private QueryAssertions assertions; diff --git a/plugin/trino-geospatial/src/test/java/io/trino/plugin/geospatial/TestSphericalGeoFunctions.java b/plugin/trino-geospatial/src/test/java/io/trino/plugin/geospatial/TestSphericalGeoFunctions.java index da0d4147b484..0f703ca431b9 100644 --- a/plugin/trino-geospatial/src/test/java/io/trino/plugin/geospatial/TestSphericalGeoFunctions.java +++ b/plugin/trino-geospatial/src/test/java/io/trino/plugin/geospatial/TestSphericalGeoFunctions.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.nio.file.Files; @@ -40,8 +41,10 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestSphericalGeoFunctions { private QueryAssertions assertions; diff --git a/plugin/trino-geospatial/src/test/java/io/trino/plugin/geospatial/aggregation/AbstractTestGeoAggregationFunctions.java b/plugin/trino-geospatial/src/test/java/io/trino/plugin/geospatial/aggregation/AbstractTestGeoAggregationFunctions.java index 64dcbf97b4bd..c785fd2801f2 100644 --- a/plugin/trino-geospatial/src/test/java/io/trino/plugin/geospatial/aggregation/AbstractTestGeoAggregationFunctions.java +++ b/plugin/trino-geospatial/src/test/java/io/trino/plugin/geospatial/aggregation/AbstractTestGeoAggregationFunctions.java @@ -24,6 +24,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Arrays; import java.util.Collections; @@ -37,8 +38,10 @@ import static io.trino.plugin.geospatial.GeometryType.GEOMETRY; import static io.trino.sql.analyzer.TypeSignatureProvider.fromTypes; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public abstract class AbstractTestGeoAggregationFunctions { private LocalQueryRunner runner; diff --git a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHive.java b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHive.java index 5229758a4297..c4568db9be42 100644 --- a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHive.java +++ b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHive.java @@ -28,7 +28,6 @@ import static org.junit.jupiter.api.Assumptions.abort; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -// staging directory is shared mutable state @TestInstance(PER_CLASS) public class TestHive extends AbstractTestHive diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java index f598b557d4c0..8cbba9f75c2b 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java @@ -146,6 +146,7 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.IOException; import java.io.OutputStream; @@ -327,6 +328,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.joda.time.DateTimeZone.UTC; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; @@ -334,8 +336,8 @@ import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; -// staging directory is shared mutable state @TestInstance(PER_CLASS) +@Execution(SAME_THREAD) // staging directory is shared mutable state public abstract class AbstractTestHive { private static final Logger log = Logger.get(AbstractTestHive.class); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java index 8241e918ece5..86d89f66fb67 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java @@ -82,6 +82,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.BufferedReader; import java.io.IOException; @@ -134,11 +135,13 @@ import static java.util.concurrent.Executors.newScheduledThreadPool; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public abstract class AbstractTestHiveFileSystem { protected static final HdfsContext TESTING_CONTEXT = new HdfsContext(ConnectorIdentity.ofUser("test")); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java index c5547a24df51..a791e93b8ae2 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java @@ -64,6 +64,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.io.IOException; @@ -130,11 +131,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestBackgroundHiveSplitLoader { private static final int BUCKET_COUNT = 2; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileBasedSecurity.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileBasedSecurity.java index f1cd3e1cdf33..94ca460cfb0c 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileBasedSecurity.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileBasedSecurity.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; @@ -31,8 +32,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestHiveFileBasedSecurity { private QueryRunner queryRunner; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileMetastore.java index 0616c5768d58..291f667b79c4 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileMetastore.java @@ -23,7 +23,6 @@ import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; import static org.junit.jupiter.api.Assumptions.abort; -// staging directory is shared mutable state public class TestHiveFileMetastore extends AbstractTestHiveLocal { diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveInMemoryMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveInMemoryMetastore.java index 4513dd2ba7f6..47991857d2ba 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveInMemoryMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveInMemoryMetastore.java @@ -27,7 +27,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assumptions.abort; -// staging directory is shared mutable state public class TestHiveInMemoryMetastore extends AbstractTestHiveLocal { diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOrcPageSourceMemoryTracking.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOrcPageSourceMemoryTracking.java index d445b60526b8..eefff9d7c6a0 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOrcPageSourceMemoryTracking.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOrcPageSourceMemoryTracking.java @@ -75,6 +75,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.lang.reflect.Constructor; @@ -126,6 +127,7 @@ import static org.apache.hadoop.mapreduce.lib.output.FileOutputFormat.COMPRESS_TYPE; import static org.joda.time.DateTimeZone.UTC; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; @@ -133,6 +135,7 @@ import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestOrcPageSourceMemoryTracking { private static final String ORC_RECORD_WRITER = OrcOutputFormat.class.getName() + "$OrcRecordWriter"; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/file/TestFileHiveMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/file/TestFileHiveMetastore.java index 094dc44cc043..4752af151dea 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/file/TestFileHiveMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/file/TestFileHiveMetastore.java @@ -24,6 +24,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.IOException; import java.nio.file.Path; @@ -43,8 +44,10 @@ import static java.nio.file.Files.createTempDirectory; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestFileHiveMetastore { private Path tmpDir; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestHiveMetastoreAccessOperations.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestHiveMetastoreAccessOperations.java index 94a24e37a90d..50e5d054a617 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestHiveMetastoreAccessOperations.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestHiveMetastoreAccessOperations.java @@ -25,6 +25,7 @@ import io.trino.testing.QueryRunner; import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; @@ -39,8 +40,9 @@ import static io.trino.plugin.hive.metastore.CountingAccessHiveMetastore.Method.UPDATE_TABLE_STATISTICS; import static io.trino.plugin.hive.metastore.file.TestingFileHiveMetastore.createTestingFileHiveMetastore; import static io.trino.testing.TestingSession.testSessionBuilder; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -// metastore invocation counters shares mutable state so can't be run from many threads simultaneously +@Execution(SAME_THREAD)// metastore invocation counters shares mutable state so can't be run from many threads simultaneously public class TestHiveMetastoreAccessOperations extends AbstractTestQueryFramework { diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestAsyncQueue.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestAsyncQueue.java index 11ad5a99fd01..41383d480af0 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestAsyncQueue.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestAsyncQueue.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.parallel.Execution; import java.util.ArrayList; import java.util.List; @@ -35,11 +36,13 @@ import static io.airlift.concurrent.MoreFutures.getFutureValue; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestAsyncQueue { private ExecutorService executor; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestThrottledAsyncQueue.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestThrottledAsyncQueue.java index 9378cad1eca2..ec17417e5e67 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestThrottledAsyncQueue.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestThrottledAsyncQueue.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.concurrent.ExecutionException; @@ -31,11 +32,13 @@ import static java.util.concurrent.Executors.newCachedThreadPool; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestThrottledAsyncQueue { private ExecutorService executor; diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergMinioConnectorSmokeTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergMinioConnectorSmokeTest.java index c814d974e882..0121280be4af 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergMinioConnectorSmokeTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergMinioConnectorSmokeTest.java @@ -24,6 +24,7 @@ import org.apache.iceberg.FileFormat; import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.Map; @@ -40,7 +41,9 @@ import static java.lang.String.format; import static java.util.Locale.ENGLISH; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; +@Execution(SAME_THREAD) public abstract class BaseIcebergMinioConnectorSmokeTest extends BaseIcebergConnectorSmokeTest { diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestTrinoHiveCatalogWithFileMetastore.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestTrinoHiveCatalogWithFileMetastore.java index daf79a1af9ad..8d7e3dcbe12c 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestTrinoHiveCatalogWithFileMetastore.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestTrinoHiveCatalogWithFileMetastore.java @@ -25,6 +25,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.io.IOException; @@ -37,8 +38,10 @@ import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.createPerTransactionCache; import static io.trino.plugin.hive.metastore.file.TestingFileHiveMetastore.createTestingFileHiveMetastore; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestTrinoHiveCatalogWithFileMetastore extends BaseTrinoCatalogTest { diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogAccessOperations.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogAccessOperations.java index b65004be19fa..01a471bb344a 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogAccessOperations.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogAccessOperations.java @@ -31,6 +31,7 @@ import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.nio.file.Files; @@ -70,12 +71,14 @@ import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toCollection; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; /* * The test currently uses AWS Default Credential Provider Chain, * See https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html#credentials-default * on ways to set your AWS credentials which will be needed to run this test. */ +@Execution(SAME_THREAD) public class TestIcebergGlueCatalogAccessOperations extends AbstractTestQueryFramework { diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/hms/TestTrinoHiveCatalogWithHiveMetastore.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/hms/TestTrinoHiveCatalogWithHiveMetastore.java index c5e1454dca6b..7b01d801a289 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/hms/TestTrinoHiveCatalogWithHiveMetastore.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/hms/TestTrinoHiveCatalogWithHiveMetastore.java @@ -41,6 +41,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Map; import java.util.Optional; @@ -55,8 +56,10 @@ import static io.trino.testing.containers.Minio.MINIO_SECRET_KEY; import static java.util.concurrent.TimeUnit.MINUTES; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestTrinoHiveCatalogWithHiveMetastore extends BaseTrinoCatalogTest { diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/nessie/TestTrinoNessieCatalog.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/nessie/TestTrinoNessieCatalog.java index 0e6c3ffbb29b..8424be77c89d 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/nessie/TestTrinoNessieCatalog.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/nessie/TestTrinoNessieCatalog.java @@ -35,6 +35,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import org.projectnessie.client.api.NessieApiV1; import org.projectnessie.client.http.HttpClientBuilder; @@ -56,8 +57,10 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.fail; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestTrinoNessieCatalog extends BaseTrinoCatalogTest { diff --git a/plugin/trino-jmx/src/test/java/io/trino/plugin/jmx/TestJmxQueries.java b/plugin/trino-jmx/src/test/java/io/trino/plugin/jmx/TestJmxQueries.java index 9ea235463362..9c852b871f40 100644 --- a/plugin/trino-jmx/src/test/java/io/trino/plugin/jmx/TestJmxQueries.java +++ b/plugin/trino-jmx/src/test/java/io/trino/plugin/jmx/TestJmxQueries.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Locale; import java.util.Set; @@ -29,8 +30,10 @@ import static io.trino.plugin.jmx.JmxMetadata.JMX_SCHEMA_NAME; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestJmxQueries { private QueryAssertions assertions; diff --git a/plugin/trino-jmx/src/test/java/io/trino/plugin/jmx/TestJmxSplitManager.java b/plugin/trino-jmx/src/test/java/io/trino/plugin/jmx/TestJmxSplitManager.java index 46685b3c8224..072cf20042e6 100644 --- a/plugin/trino-jmx/src/test/java/io/trino/plugin/jmx/TestJmxSplitManager.java +++ b/plugin/trino-jmx/src/test/java/io/trino/plugin/jmx/TestJmxSplitManager.java @@ -38,6 +38,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.net.URI; import java.util.HashSet; @@ -58,8 +59,10 @@ import static java.util.stream.Collectors.toSet; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestJmxSplitManager { private static final Duration JMX_STATS_DUMP = new Duration(100, TimeUnit.MILLISECONDS); diff --git a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestObjectIdFunctions.java b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestObjectIdFunctions.java index 5e5c863935e4..cec990ecb5c1 100644 --- a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestObjectIdFunctions.java +++ b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestObjectIdFunctions.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.time.ZonedDateTime; @@ -30,8 +31,10 @@ import static java.time.ZoneOffset.UTC; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestObjectIdFunctions { private QueryAssertions assertions; diff --git a/plugin/trino-mysql-event-listener/src/test/java/io/trino/plugin/eventlistener/mysql/TestMysqlEventListener.java b/plugin/trino-mysql-event-listener/src/test/java/io/trino/plugin/eventlistener/mysql/TestMysqlEventListener.java index 88a81c8457a4..5dcbcbd97c93 100644 --- a/plugin/trino-mysql-event-listener/src/test/java/io/trino/plugin/eventlistener/mysql/TestMysqlEventListener.java +++ b/plugin/trino-mysql-event-listener/src/test/java/io/trino/plugin/eventlistener/mysql/TestMysqlEventListener.java @@ -38,6 +38,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import org.testcontainers.containers.MySQLContainer; import java.net.URI; @@ -61,8 +62,10 @@ import static java.time.Duration.ofMillis; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestMysqlEventListener { private static final QueryMetadata FULL_QUERY_METADATA = new QueryMetadata( diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestCredentialPassthrough.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestCredentialPassthrough.java index c77ddb8f24af..3a2a6414cb63 100644 --- a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestCredentialPassthrough.java +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestCredentialPassthrough.java @@ -22,14 +22,17 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Map; import static io.airlift.testing.Closeables.closeAllSuppress; import static io.trino.testing.TestingSession.testSessionBuilder; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestCredentialPassthrough { private TestingMySqlServer mySqlServer; diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotLatestConnectorSmokeTest.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotLatestConnectorSmokeTest.java index e68cfaa7144d..a7f172dfa19a 100644 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotLatestConnectorSmokeTest.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotLatestConnectorSmokeTest.java @@ -13,8 +13,11 @@ */ package io.trino.plugin.pinot; +import org.junit.jupiter.api.parallel.Isolated; + import static io.trino.plugin.pinot.TestingPinotCluster.PINOT_LATEST_IMAGE_NAME; +@Isolated public class TestPinotLatestConnectorSmokeTest extends BasePinotConnectorSmokeTest { diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotLatestNoGrpcConnectorSmokeTest.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotLatestNoGrpcConnectorSmokeTest.java index 229c8c4a3213..f4d11ea14d76 100644 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotLatestNoGrpcConnectorSmokeTest.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotLatestNoGrpcConnectorSmokeTest.java @@ -13,8 +13,11 @@ */ package io.trino.plugin.pinot; +import org.junit.jupiter.api.parallel.Isolated; + import static io.trino.plugin.pinot.TestingPinotCluster.PINOT_LATEST_IMAGE_NAME; +@Isolated public class TestPinotLatestNoGrpcConnectorSmokeTest extends BasePinotConnectorSmokeTest { diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotSecuredConnectorSmokeTest.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotSecuredConnectorSmokeTest.java index 42894ca2ee90..b694ccf1d664 100644 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotSecuredConnectorSmokeTest.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotSecuredConnectorSmokeTest.java @@ -14,11 +14,13 @@ package io.trino.plugin.pinot; import com.google.common.collect.ImmutableMap; +import org.junit.jupiter.api.parallel.Isolated; import java.util.Map; import static io.trino.plugin.pinot.auth.PinotAuthenticationType.PASSWORD; +@Isolated public class TestPinotSecuredConnectorSmokeTest extends BasePinotConnectorSmokeTest { diff --git a/plugin/trino-redis/src/test/java/io/trino/plugin/redis/AbstractTestMinimalFunctionality.java b/plugin/trino-redis/src/test/java/io/trino/plugin/redis/AbstractTestMinimalFunctionality.java index d20b74b9eed0..e6415677fe69 100644 --- a/plugin/trino-redis/src/test/java/io/trino/plugin/redis/AbstractTestMinimalFunctionality.java +++ b/plugin/trino-redis/src/test/java/io/trino/plugin/redis/AbstractTestMinimalFunctionality.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import redis.clients.jedis.Jedis; import java.util.Map; @@ -33,8 +34,10 @@ import static io.trino.plugin.redis.util.RedisTestUtils.loadSimpleTableDescription; import static io.trino.testing.TestingSession.testSessionBuilder; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; @TestInstance(PER_CLASS) +@Execution(SAME_THREAD) public abstract class AbstractTestMinimalFunctionality { protected static final Session SESSION = testSessionBuilder() diff --git a/plugin/trino-teradata-functions/src/test/java/io/trino/plugin/teradata/functions/TestTeradataDateFunctions.java b/plugin/trino-teradata-functions/src/test/java/io/trino/plugin/teradata/functions/TestTeradataDateFunctions.java index c6b2a9cc5bed..05ddb2234836 100644 --- a/plugin/trino-teradata-functions/src/test/java/io/trino/plugin/teradata/functions/TestTeradataDateFunctions.java +++ b/plugin/trino-teradata-functions/src/test/java/io/trino/plugin/teradata/functions/TestTeradataDateFunctions.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.time.LocalDate; @@ -30,8 +31,10 @@ import static java.lang.Math.toIntExact; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestTeradataDateFunctions { private QueryAssertions assertions; diff --git a/plugin/trino-teradata-functions/src/test/java/io/trino/plugin/teradata/functions/TestTeradataFunctions.java b/plugin/trino-teradata-functions/src/test/java/io/trino/plugin/teradata/functions/TestTeradataFunctions.java index 9539fb85a030..5a250032f910 100644 --- a/plugin/trino-teradata-functions/src/test/java/io/trino/plugin/teradata/functions/TestTeradataFunctions.java +++ b/plugin/trino-teradata-functions/src/test/java/io/trino/plugin/teradata/functions/TestTeradataFunctions.java @@ -18,13 +18,16 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.VarcharType.VARCHAR; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestTeradataFunctions { private QueryAssertions assertions; diff --git a/plugin/trino-tpcds/src/test/java/io/trino/plugin/tpcds/statistics/TestTpcdsLocalStats.java b/plugin/trino-tpcds/src/test/java/io/trino/plugin/tpcds/statistics/TestTpcdsLocalStats.java index 69240dde8620..164e31903eba 100644 --- a/plugin/trino-tpcds/src/test/java/io/trino/plugin/tpcds/statistics/TestTpcdsLocalStats.java +++ b/plugin/trino-tpcds/src/test/java/io/trino/plugin/tpcds/statistics/TestTpcdsLocalStats.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.SystemSessionProperties.COLLECT_PLAN_STATISTICS_FOR_ALL_QUERIES; import static io.trino.testing.TestingSession.testSessionBuilder; @@ -32,8 +33,10 @@ import static io.trino.testing.statistics.Metrics.distinctValuesCount; import static java.util.Collections.emptyMap; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestTpcdsLocalStats { private StatisticsAssertion statisticsAssertion; diff --git a/pom.xml b/pom.xml index 2de19d7d8578..3379e05b968d 100644 --- a/pom.xml +++ b/pom.xml @@ -2587,7 +2587,10 @@ junit.jupiter.execution.timeout.thread.mode.default = SEPARATE_THREAD - junit.jupiter.extensions.autodetection.enabled = true + junit.jupiter.extensions.autodetection.enabled = true + junit.jupiter.execution.parallel.enabled = true + junit.jupiter.execution.parallel.mode.default = concurrent + junit.jupiter.execution.parallel.mode.classes.default = concurrent diff --git a/service/trino-proxy/src/test/java/io/trino/proxy/TestProxyServer.java b/service/trino-proxy/src/test/java/io/trino/proxy/TestProxyServer.java index 1c179d875f70..ad44532e9691 100644 --- a/service/trino-proxy/src/test/java/io/trino/proxy/TestProxyServer.java +++ b/service/trino-proxy/src/test/java/io/trino/proxy/TestProxyServer.java @@ -34,6 +34,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.parallel.Execution; import java.io.IOException; import java.net.URI; @@ -59,8 +60,10 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestProxyServer { private Path sharedSecretFile; diff --git a/service/trino-verifier/src/test/java/io/trino/verifier/TestDatabaseEventClient.java b/service/trino-verifier/src/test/java/io/trino/verifier/TestDatabaseEventClient.java index be4875b7f8b4..3e18028d38cc 100644 --- a/service/trino-verifier/src/test/java/io/trino/verifier/TestDatabaseEventClient.java +++ b/service/trino-verifier/src/test/java/io/trino/verifier/TestDatabaseEventClient.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import org.testcontainers.containers.MySQLContainer; import java.sql.Connection; @@ -33,8 +34,10 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestDatabaseEventClient { private static final VerifierQueryEvent FULL_EVENT = new VerifierQueryEvent( diff --git a/service/trino-verifier/src/test/java/io/trino/verifier/TestShadowing.java b/service/trino-verifier/src/test/java/io/trino/verifier/TestShadowing.java index 47fdcb4a4b86..6b8b31baca90 100644 --- a/service/trino-verifier/src/test/java/io/trino/verifier/TestShadowing.java +++ b/service/trino-verifier/src/test/java/io/trino/verifier/TestShadowing.java @@ -32,6 +32,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Optional; @@ -43,8 +44,10 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestShadowing { private static final String CATALOG = "TEST_REWRITE"; diff --git a/service/trino-verifier/src/test/java/io/trino/verifier/TestVerifierRewriteQueries.java b/service/trino-verifier/src/test/java/io/trino/verifier/TestVerifierRewriteQueries.java index 0c2bb1542f04..e96bd3274b1c 100644 --- a/service/trino-verifier/src/test/java/io/trino/verifier/TestVerifierRewriteQueries.java +++ b/service/trino-verifier/src/test/java/io/trino/verifier/TestVerifierRewriteQueries.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.concurrent.TimeUnit; @@ -29,8 +30,10 @@ import static io.trino.verifier.VerifyCommand.rewriteQueries; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; @TestInstance(PER_CLASS) +@Execution(SAME_THREAD) public class TestVerifierRewriteQueries { private static final String CATALOG = "TEST_VERIFIER_REWRITE_QUERIES"; diff --git a/testing/trino-testing/src/main/java/io/trino/testing/AbstractDistributedEngineOnlyQueries.java b/testing/trino-testing/src/main/java/io/trino/testing/AbstractDistributedEngineOnlyQueries.java index 7f8c83704b85..7d42e996f8f9 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/AbstractDistributedEngineOnlyQueries.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/AbstractDistributedEngineOnlyQueries.java @@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.parallel.Execution; import java.time.ZonedDateTime; import java.util.List; @@ -42,8 +43,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public abstract class AbstractDistributedEngineOnlyQueries extends AbstractTestEngineOnlyQueries { diff --git a/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestQueryFramework.java b/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestQueryFramework.java index 8c9e52b67f38..074966f11d2a 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestQueryFramework.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestQueryFramework.java @@ -58,6 +58,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; @@ -87,9 +88,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.fail; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public abstract class AbstractTestQueryFramework { private static final SqlParser SQL_PARSER = new SqlParser(); diff --git a/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java b/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java index db262998ed41..b7695f780e1e 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java @@ -47,6 +47,7 @@ import io.trino.testing.sql.TestView; import io.trino.tpch.TpchTable; import org.intellij.lang.annotations.Language; +import org.junit.jupiter.api.parallel.Isolated; import org.testng.SkipException; import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; @@ -184,6 +185,7 @@ /** * Generic test for connectors. */ +@Isolated public abstract class BaseConnectorTest extends AbstractTestQueries { diff --git a/testing/trino-testing/src/main/java/io/trino/testing/BaseDynamicPartitionPruningTest.java b/testing/trino-testing/src/main/java/io/trino/testing/BaseDynamicPartitionPruningTest.java index 1edd986c17f7..b6e9cdba6c18 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/BaseDynamicPartitionPruningTest.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/BaseDynamicPartitionPruningTest.java @@ -31,6 +31,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.parallel.Isolated; import java.util.List; import java.util.Map; @@ -58,6 +59,7 @@ import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Isolated public abstract class BaseDynamicPartitionPruningTest extends AbstractTestQueryFramework { diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestCompletedEventWarnings.java b/testing/trino-tests/src/test/java/io/trino/execution/TestCompletedEventWarnings.java index 356645ff703d..bd4d2cd300ba 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestCompletedEventWarnings.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestCompletedEventWarnings.java @@ -28,6 +28,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.IOException; import java.util.List; @@ -37,9 +38,11 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet; import static io.trino.SessionTestUtils.TEST_SESSION; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.fail; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestCompletedEventWarnings { private static final int TEST_WARNINGS = 5; diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestConnectorEventListener.java b/testing/trino-tests/src/test/java/io/trino/execution/TestConnectorEventListener.java index 13b0022c4996..061f75bdb984 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestConnectorEventListener.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestConnectorEventListener.java @@ -23,13 +23,16 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.IOException; import static io.trino.SessionTestUtils.TEST_SESSION; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestConnectorEventListener { private final EventsCollector generatedEvents = new EventsCollector(); diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestDeprecatedFunctionWarning.java b/testing/trino-tests/src/test/java/io/trino/execution/TestDeprecatedFunctionWarning.java index e0fc5b0e985c..83c91e612eed 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestDeprecatedFunctionWarning.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestDeprecatedFunctionWarning.java @@ -39,6 +39,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.Set; @@ -47,9 +48,11 @@ import static io.trino.spi.type.DoubleType.DOUBLE; import static io.trino.testing.TestingSession.testSessionBuilder; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.fail; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestDeprecatedFunctionWarning { private static final WarningCode DEPRECATED_FUNCTION_WARNING_CODE = StandardWarningCode.DEPRECATED_FUNCTION.toWarningCode(); diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestPendingStageState.java b/testing/trino-tests/src/test/java/io/trino/execution/TestPendingStageState.java index dd98f0667d8f..a91d84e6dd08 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestPendingStageState.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestPendingStageState.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.SessionTestUtils.TEST_SESSION; import static io.trino.execution.QueryRunnerUtil.createQuery; @@ -32,9 +33,11 @@ import static io.trino.testing.assertions.Assert.assertEventually; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestPendingStageState { private DistributedQueryRunner queryRunner; diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestWarnings.java b/testing/trino-tests/src/test/java/io/trino/execution/TestWarnings.java index f886ba32c278..7c1e2dec3812 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestWarnings.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestWarnings.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.Set; @@ -30,9 +31,11 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet; import static io.trino.spi.connector.StandardWarningCode.TOO_MANY_STAGES; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.fail; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestWarnings { private static final int STAGE_COUNT_WARNING_THRESHOLD = 20; diff --git a/testing/trino-tests/src/test/java/io/trino/memory/TestMemoryManager.java b/testing/trino-tests/src/test/java/io/trino/memory/TestMemoryManager.java index dbcfe9f703dc..980326078adf 100644 --- a/testing/trino-tests/src/test/java/io/trino/memory/TestMemoryManager.java +++ b/testing/trino-tests/src/test/java/io/trino/memory/TestMemoryManager.java @@ -31,6 +31,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.parallel.Execution; import java.nio.file.Paths; import java.util.ArrayList; @@ -56,6 +57,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; @@ -63,6 +65,7 @@ import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestMemoryManager { private static final Session SESSION = testSessionBuilder() diff --git a/testing/trino-tests/src/test/java/io/trino/tests/TestGracefulShutdown.java b/testing/trino-tests/src/test/java/io/trino/tests/TestGracefulShutdown.java index 511b52a9da5c..a807197f8ad1 100644 --- a/testing/trino-tests/src/test/java/io/trino/tests/TestGracefulShutdown.java +++ b/testing/trino-tests/src/test/java/io/trino/tests/TestGracefulShutdown.java @@ -29,6 +29,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.parallel.Execution; import java.util.ArrayList; import java.util.List; @@ -43,10 +44,12 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestGracefulShutdown { private static final long SHUTDOWN_TIMEOUT_MILLIS = 240_000; diff --git a/testing/trino-tests/src/test/java/io/trino/tests/TestMetadataManager.java b/testing/trino-tests/src/test/java/io/trino/tests/TestMetadataManager.java index ccd39bc5a479..e536af92bfca 100644 --- a/testing/trino-tests/src/test/java/io/trino/tests/TestMetadataManager.java +++ b/testing/trino-tests/src/test/java/io/trino/tests/TestMetadataManager.java @@ -38,6 +38,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.Optional; @@ -49,6 +50,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; /** @@ -58,6 +60,7 @@ * This mapping has to be manually cleaned when query finishes execution (Metadata#cleanupQuery method). */ @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestMetadataManager { private DistributedQueryRunner queryRunner; diff --git a/testing/trino-tests/src/test/java/io/trino/tests/TestQueryManager.java b/testing/trino-tests/src/test/java/io/trino/tests/TestQueryManager.java index 4a22efe0e071..41cbb15e88c9 100644 --- a/testing/trino-tests/src/test/java/io/trino/tests/TestQueryManager.java +++ b/testing/trino-tests/src/test/java/io/trino/tests/TestQueryManager.java @@ -32,6 +32,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.parallel.Execution; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static io.trino.SessionTestUtils.TEST_SESSION; @@ -46,11 +47,13 @@ import static io.trino.testing.TestingSession.testSessionBuilder; import static java.util.Arrays.stream; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.fail; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestQueryManager { private DistributedQueryRunner queryRunner; diff --git a/testing/trino-tests/src/test/java/io/trino/tests/TestTablesample.java b/testing/trino-tests/src/test/java/io/trino/tests/TestTablesample.java index 76f4a5a68646..08004a99fa10 100644 --- a/testing/trino-tests/src/test/java/io/trino/tests/TestTablesample.java +++ b/testing/trino-tests/src/test/java/io/trino/tests/TestTablesample.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.SessionTestUtils.TEST_SESSION; import static io.trino.spi.StandardErrorCode.INVALID_ARGUMENTS; @@ -29,8 +30,10 @@ import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestTablesample { private LocalQueryRunner queryRunner; diff --git a/testing/trino-tests/src/test/java/io/trino/tests/tpch/TestTpchDistributedStats.java b/testing/trino-tests/src/test/java/io/trino/tests/tpch/TestTpchDistributedStats.java index b44fae88bf80..efc5927f2833 100644 --- a/testing/trino-tests/src/test/java/io/trino/tests/tpch/TestTpchDistributedStats.java +++ b/testing/trino-tests/src/test/java/io/trino/tests/tpch/TestTpchDistributedStats.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.SystemSessionProperties.COLLECT_PLAN_STATISTICS_FOR_ALL_QUERIES; import static io.trino.plugin.tpch.TpchConnectorFactory.TPCH_COLUMN_NAMING_PROPERTY; @@ -33,8 +34,10 @@ import static io.trino.testing.statistics.Metrics.OUTPUT_ROW_COUNT; import static io.trino.testing.statistics.Metrics.distinctValuesCount; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestTpchDistributedStats { private StatisticsAssertion statisticsAssertion; diff --git a/testing/trino-tests/src/test/java/io/trino/tests/tpch/TestTpchLocalStats.java b/testing/trino-tests/src/test/java/io/trino/tests/tpch/TestTpchLocalStats.java index 5595dd78506b..f1b678cb9566 100644 --- a/testing/trino-tests/src/test/java/io/trino/tests/tpch/TestTpchLocalStats.java +++ b/testing/trino-tests/src/test/java/io/trino/tests/tpch/TestTpchLocalStats.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.SystemSessionProperties.COLLECT_PLAN_STATISTICS_FOR_ALL_QUERIES; import static io.trino.plugin.tpch.TpchConnectorFactory.TPCH_COLUMN_NAMING_PROPERTY; @@ -38,8 +39,10 @@ import static io.trino.testing.statistics.Metrics.lowValue; import static io.trino.testing.statistics.Metrics.nullsFraction; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestTpchLocalStats { private StatisticsAssertion statisticsAssertion; From 35d7f0e06e068d0944e9f1a692fe76a3f1742807 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Wed, 1 Nov 2023 10:48:32 -0700 Subject: [PATCH 115/587] Remove unnecessary test repetition The tests should be deterministic. --- .../src/test/java/io/trino/cache/TestEvictableCache.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/trino-cache/src/test/java/io/trino/cache/TestEvictableCache.java b/lib/trino-cache/src/test/java/io/trino/cache/TestEvictableCache.java index 19329732b07b..a8015fd203f0 100644 --- a/lib/trino-cache/src/test/java/io/trino/cache/TestEvictableCache.java +++ b/lib/trino-cache/src/test/java/io/trino/cache/TestEvictableCache.java @@ -21,7 +21,6 @@ import io.airlift.testing.TestingTicker; import io.trino.cache.EvictableCacheBuilder.DisabledCacheImplementation; import org.gaul.modernizer_maven_annotations.SuppressModernizer; -import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; @@ -287,7 +286,6 @@ public void testLoadStats() assertThat(value).isEqualTo("abc"); } - @RepeatedTest(value = 10, failureThreshold = 5) @Timeout(TEST_TIMEOUT_SECONDS) public void testLoadFailure() throws Exception @@ -477,7 +475,6 @@ public void testInvalidateOngoingLoad() /** * Covers https://github.com/google/guava/issues/1881 */ - @RepeatedTest(10) @Timeout(TEST_TIMEOUT_SECONDS) public void testInvalidateAndLoadConcurrently() throws Exception From ec316231814953bb07cfa13108e09fb1cc101ff0 Mon Sep 17 00:00:00 2001 From: Elon Azoulay Date: Tue, 7 Nov 2023 21:43:40 -0800 Subject: [PATCH 116/587] Fix trino-filesystem-gcs tests --- lib/trino-filesystem-gcs/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/trino-filesystem-gcs/pom.xml b/lib/trino-filesystem-gcs/pom.xml index cbf748c58d4b..87a99864394f 100644 --- a/lib/trino-filesystem-gcs/pom.xml +++ b/lib/trino-filesystem-gcs/pom.xml @@ -175,7 +175,7 @@ maven-surefire-plugin - **/TestDefaultGcsFileSystem.java + **/TestGcsFileSystemGcs.java @@ -192,7 +192,7 @@ maven-surefire-plugin - **/TestDefaultGcsFileSystem.java + **/TestGcsFileSystemGcs.java From df3a531253fa3df22edd4211ff63453f6b8c7af6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Osipiuk?= Date: Wed, 8 Nov 2023 09:12:01 +0100 Subject: [PATCH 117/587] Reorder fields and methods in MemoryManagerConfig Reorder fields and methods so ordering match and related config properties are grouped together. --- .../io/trino/memory/MemoryManagerConfig.java | 113 +++++++++--------- .../trino/memory/TestMemoryManagerConfig.java | 28 ++--- 2 files changed, 69 insertions(+), 72 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/memory/MemoryManagerConfig.java b/core/trino-main/src/main/java/io/trino/memory/MemoryManagerConfig.java index 62bb5efeaadb..54c775693b47 100644 --- a/core/trino-main/src/main/java/io/trino/memory/MemoryManagerConfig.java +++ b/core/trino-main/src/main/java/io/trino/memory/MemoryManagerConfig.java @@ -42,54 +42,13 @@ public class MemoryManagerConfig private double faultTolerantExecutionTaskMemoryGrowthFactor = 3.0; private double faultTolerantExecutionTaskMemoryEstimationQuantile = 0.9; private DataSize faultTolerantExecutionTaskRuntimeMemoryEstimationOverhead = DataSize.of(1, GIGABYTE); - private LowMemoryQueryKillerPolicy lowMemoryQueryKillerPolicy = LowMemoryQueryKillerPolicy.TOTAL_RESERVATION_ON_BLOCKED_NODES; - private LowMemoryTaskKillerPolicy lowMemoryTaskKillerPolicy = LowMemoryTaskKillerPolicy.TOTAL_RESERVATION_ON_BLOCKED_NODES; private boolean faultTolerantExecutionMemoryRequirementIncreaseOnWorkerCrashEnabled = true; private DataSize faultTolerantExecutionEagerSpeculativeTasksNodeMemoryOvercommit = DataSize.of(20, GIGABYTE); - - /** - * default value is overwritten for fault tolerant execution in {@link #applyFaultTolerantExecutionDefaults()}} - */ + private LowMemoryQueryKillerPolicy lowMemoryQueryKillerPolicy = LowMemoryQueryKillerPolicy.TOTAL_RESERVATION_ON_BLOCKED_NODES; + private LowMemoryTaskKillerPolicy lowMemoryTaskKillerPolicy = LowMemoryTaskKillerPolicy.TOTAL_RESERVATION_ON_BLOCKED_NODES; + // default value is overwritten for fault tolerant execution in {@link #applyFaultTolerantExecutionDefaults()}} private Duration killOnOutOfMemoryDelay = new Duration(5, MINUTES); - public LowMemoryQueryKillerPolicy getLowMemoryQueryKillerPolicy() - { - return lowMemoryQueryKillerPolicy; - } - - @Config("query.low-memory-killer.policy") - public MemoryManagerConfig setLowMemoryQueryKillerPolicy(LowMemoryQueryKillerPolicy lowMemoryQueryKillerPolicy) - { - this.lowMemoryQueryKillerPolicy = lowMemoryQueryKillerPolicy; - return this; - } - - public LowMemoryTaskKillerPolicy getLowMemoryTaskKillerPolicy() - { - return lowMemoryTaskKillerPolicy; - } - - @Config("task.low-memory-killer.policy") - public MemoryManagerConfig setLowMemoryTaskKillerPolicy(LowMemoryTaskKillerPolicy lowMemoryTaskKillerPolicy) - { - this.lowMemoryTaskKillerPolicy = lowMemoryTaskKillerPolicy; - return this; - } - - @NotNull - public Duration getKillOnOutOfMemoryDelay() - { - return killOnOutOfMemoryDelay; - } - - @Config("query.low-memory-killer.delay") - @ConfigDescription("Delay between cluster running low on memory and invoking killer") - public MemoryManagerConfig setKillOnOutOfMemoryDelay(Duration killOnOutOfMemoryDelay) - { - this.killOnOutOfMemoryDelay = killOnOutOfMemoryDelay; - return this; - } - @NotNull public DataSize getMaxQueryMemory() { @@ -147,20 +106,6 @@ public MemoryManagerConfig setFaultTolerantExecutionTaskMemory(DataSize faultTol return this; } - @NotNull - public DataSize getFaultTolerantExecutionTaskRuntimeMemoryEstimationOverhead() - { - return faultTolerantExecutionTaskRuntimeMemoryEstimationOverhead; - } - - @Config("fault-tolerant-execution-task-runtime-memory-estimation-overhead") - @ConfigDescription("Extra memory to account for when estimating actual task runtime memory consumption") - public MemoryManagerConfig setFaultTolerantExecutionTaskRuntimeMemoryEstimationOverhead(DataSize faultTolerantExecutionTaskRuntimeMemoryEstimationOverhead) - { - this.faultTolerantExecutionTaskRuntimeMemoryEstimationOverhead = faultTolerantExecutionTaskRuntimeMemoryEstimationOverhead; - return this; - } - @NotNull public double getFaultTolerantExecutionTaskMemoryGrowthFactor() { @@ -192,6 +137,20 @@ public MemoryManagerConfig setFaultTolerantExecutionTaskMemoryEstimationQuantile return this; } + @NotNull + public DataSize getFaultTolerantExecutionTaskRuntimeMemoryEstimationOverhead() + { + return faultTolerantExecutionTaskRuntimeMemoryEstimationOverhead; + } + + @Config("fault-tolerant-execution-task-runtime-memory-estimation-overhead") + @ConfigDescription("Extra memory to account for when estimating actual task runtime memory consumption") + public MemoryManagerConfig setFaultTolerantExecutionTaskRuntimeMemoryEstimationOverhead(DataSize faultTolerantExecutionTaskRuntimeMemoryEstimationOverhead) + { + this.faultTolerantExecutionTaskRuntimeMemoryEstimationOverhead = faultTolerantExecutionTaskRuntimeMemoryEstimationOverhead; + return this; + } + public boolean isFaultTolerantExecutionMemoryRequirementIncreaseOnWorkerCrashEnabled() { return faultTolerantExecutionMemoryRequirementIncreaseOnWorkerCrashEnabled; @@ -217,6 +176,44 @@ public MemoryManagerConfig setFaultTolerantExecutionEagerSpeculativeTasksNodeMem return this; } + public LowMemoryQueryKillerPolicy getLowMemoryQueryKillerPolicy() + { + return lowMemoryQueryKillerPolicy; + } + + @Config("query.low-memory-killer.policy") + public MemoryManagerConfig setLowMemoryQueryKillerPolicy(LowMemoryQueryKillerPolicy lowMemoryQueryKillerPolicy) + { + this.lowMemoryQueryKillerPolicy = lowMemoryQueryKillerPolicy; + return this; + } + + public LowMemoryTaskKillerPolicy getLowMemoryTaskKillerPolicy() + { + return lowMemoryTaskKillerPolicy; + } + + @Config("task.low-memory-killer.policy") + public MemoryManagerConfig setLowMemoryTaskKillerPolicy(LowMemoryTaskKillerPolicy lowMemoryTaskKillerPolicy) + { + this.lowMemoryTaskKillerPolicy = lowMemoryTaskKillerPolicy; + return this; + } + + @NotNull + public Duration getKillOnOutOfMemoryDelay() + { + return killOnOutOfMemoryDelay; + } + + @Config("query.low-memory-killer.delay") + @ConfigDescription("Delay between cluster running low on memory and invoking killer") + public MemoryManagerConfig setKillOnOutOfMemoryDelay(Duration killOnOutOfMemoryDelay) + { + this.killOnOutOfMemoryDelay = killOnOutOfMemoryDelay; + return this; + } + public void applyFaultTolerantExecutionDefaults() { killOnOutOfMemoryDelay = new Duration(0, MINUTES); diff --git a/core/trino-main/src/test/java/io/trino/memory/TestMemoryManagerConfig.java b/core/trino-main/src/test/java/io/trino/memory/TestMemoryManagerConfig.java index e0005b7fbd87..f6f988c85b02 100644 --- a/core/trino-main/src/test/java/io/trino/memory/TestMemoryManagerConfig.java +++ b/core/trino-main/src/test/java/io/trino/memory/TestMemoryManagerConfig.java @@ -36,51 +36,51 @@ public class TestMemoryManagerConfig public void testDefaults() { assertRecordedDefaults(recordDefaults(MemoryManagerConfig.class) - .setLowMemoryQueryKillerPolicy(LowMemoryQueryKillerPolicy.TOTAL_RESERVATION_ON_BLOCKED_NODES) - .setLowMemoryTaskKillerPolicy(LowMemoryTaskKillerPolicy.TOTAL_RESERVATION_ON_BLOCKED_NODES) - .setKillOnOutOfMemoryDelay(new Duration(5, MINUTES)) .setMaxQueryMemory(DataSize.of(20, GIGABYTE)) .setMaxQueryTotalMemory(DataSize.of(40, GIGABYTE)) .setFaultTolerantExecutionCoordinatorTaskMemory(DataSize.of(2, GIGABYTE)) .setFaultTolerantExecutionTaskMemory(DataSize.of(5, GIGABYTE)) - .setFaultTolerantExecutionTaskRuntimeMemoryEstimationOverhead(DataSize.of(1, GIGABYTE)) .setFaultTolerantExecutionTaskMemoryGrowthFactor(3.0) .setFaultTolerantExecutionTaskMemoryEstimationQuantile(0.9) + .setFaultTolerantExecutionTaskRuntimeMemoryEstimationOverhead(DataSize.of(1, GIGABYTE)) .setFaultTolerantExecutionMemoryRequirementIncreaseOnWorkerCrashEnabled(true) - .setFaultTolerantExecutionEagerSpeculativeTasksNodeMemoryOvercommit(DataSize.of(20, GIGABYTE))); + .setFaultTolerantExecutionEagerSpeculativeTasksNodeMemoryOvercommit(DataSize.of(20, GIGABYTE)) + .setLowMemoryQueryKillerPolicy(LowMemoryQueryKillerPolicy.TOTAL_RESERVATION_ON_BLOCKED_NODES) + .setLowMemoryTaskKillerPolicy(LowMemoryTaskKillerPolicy.TOTAL_RESERVATION_ON_BLOCKED_NODES) + .setKillOnOutOfMemoryDelay(new Duration(5, MINUTES))); } @Test public void testExplicitPropertyMappings() { Map properties = ImmutableMap.builder() - .put("query.low-memory-killer.policy", "none") - .put("task.low-memory-killer.policy", "none") - .put("query.low-memory-killer.delay", "20s") .put("query.max-memory", "2GB") .put("query.max-total-memory", "3GB") .put("fault-tolerant-execution-coordinator-task-memory", "123GB") .put("fault-tolerant-execution-task-memory", "2GB") - .put("fault-tolerant-execution-task-runtime-memory-estimation-overhead", "300MB") .put("fault-tolerant-execution-task-memory-growth-factor", "17.3") .put("fault-tolerant-execution-task-memory-estimation-quantile", "0.7") + .put("fault-tolerant-execution-task-runtime-memory-estimation-overhead", "300MB") .put("fault-tolerant-execution.memory-requirement-increase-on-worker-crash-enabled", "false") .put("fault-tolerant-execution-eager-speculative-tasks-node_memory-overcommit", "21GB") + .put("query.low-memory-killer.policy", "none") + .put("task.low-memory-killer.policy", "none") + .put("query.low-memory-killer.delay", "20s") .buildOrThrow(); MemoryManagerConfig expected = new MemoryManagerConfig() - .setLowMemoryQueryKillerPolicy(LowMemoryQueryKillerPolicy.NONE) - .setLowMemoryTaskKillerPolicy(LowMemoryTaskKillerPolicy.NONE) - .setKillOnOutOfMemoryDelay(new Duration(20, SECONDS)) .setMaxQueryMemory(DataSize.of(2, GIGABYTE)) .setMaxQueryTotalMemory(DataSize.of(3, GIGABYTE)) .setFaultTolerantExecutionCoordinatorTaskMemory(DataSize.of(123, GIGABYTE)) .setFaultTolerantExecutionTaskMemory(DataSize.of(2, GIGABYTE)) - .setFaultTolerantExecutionTaskRuntimeMemoryEstimationOverhead(DataSize.of(300, MEGABYTE)) .setFaultTolerantExecutionTaskMemoryGrowthFactor(17.3) .setFaultTolerantExecutionTaskMemoryEstimationQuantile(0.7) + .setFaultTolerantExecutionTaskRuntimeMemoryEstimationOverhead(DataSize.of(300, MEGABYTE)) .setFaultTolerantExecutionMemoryRequirementIncreaseOnWorkerCrashEnabled(false) - .setFaultTolerantExecutionEagerSpeculativeTasksNodeMemoryOvercommit(DataSize.of(21, GIGABYTE)); + .setFaultTolerantExecutionEagerSpeculativeTasksNodeMemoryOvercommit(DataSize.of(21, GIGABYTE)) + .setLowMemoryQueryKillerPolicy(LowMemoryQueryKillerPolicy.NONE) + .setLowMemoryTaskKillerPolicy(LowMemoryTaskKillerPolicy.NONE) + .setKillOnOutOfMemoryDelay(new Duration(20, SECONDS)); assertFullMapping(properties, expected); } From b8e3bac583eaecbea75de0d80cd445555b7eb8aa Mon Sep 17 00:00:00 2001 From: James Petty Date: Mon, 23 Oct 2023 16:11:29 -0400 Subject: [PATCH 118/587] Avoid exponential planning time for LocalExchanges Adds a special case path through StreamPropertyDerivations that avoids calling back into PropertyDerivations to get otherActualProperties. This special case is specifically for PropertyDerivations to check whether a local exchange's only input source is single stream distributed and avoids what would otherwise be a mutually recursive exponential time traversal. --- .../optimizations/PropertyDerivations.java | 18 ++++--- .../StreamPropertyDerivations.java | 53 +++++++++++++++++-- 2 files changed, 58 insertions(+), 13 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/PropertyDerivations.java b/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/PropertyDerivations.java index 5500e3d13dd6..f79b62c9e3c6 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/PropertyDerivations.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/PropertyDerivations.java @@ -106,6 +106,7 @@ import static io.trino.sql.planner.optimizations.ActualProperties.Global.coordinatorSinglePartition; import static io.trino.sql.planner.optimizations.ActualProperties.Global.partitionedOn; import static io.trino.sql.planner.optimizations.ActualProperties.Global.singlePartition; +import static io.trino.sql.planner.optimizations.StreamPropertyDerivations.isLocalExchangesSourceSingleStreamDistributed; import static io.trino.sql.planner.plan.ExchangeNode.Scope.LOCAL; import static io.trino.sql.planner.plan.ExchangeNode.Scope.REMOTE; import static io.trino.sql.tree.PatternRecognitionRelation.RowsPerMatch.ONE; @@ -696,18 +697,19 @@ public ActualProperties visitExchange(ExchangeNode node, List if (node.getScope() == LOCAL) { if (inputProperties.size() == 1) { ActualProperties inputProperty = inputProperties.get(0); - if (inputProperty.isEffectivelySinglePartition() && node.getOrderingScheme().isEmpty()) { + if (inputProperty.isEffectivelySinglePartition() && node.getOrderingScheme().isEmpty() && !inputProperty.getLocalProperties().isEmpty()) { verify(node.getInputs().size() == 1); verify(node.getSources().size() == 1); - PlanNode source = node.getSources().get(0); - StreamPropertyDerivations.StreamProperties streamProperties = StreamPropertyDerivations.derivePropertiesRecursively(source, plannerContext, session, types, typeAnalyzer); - if (streamProperties.isSingleStream()) { - Map inputToOutput = exchangeInputToOutput(node, 0); + Map inputToOutput = exchangeInputToOutput(node, 0); + List> inputLocalProperties = LocalProperties.translate(inputProperty.getLocalProperties(), symbol -> Optional.ofNullable(inputToOutput.get(symbol))); + // If no local properties are present to propagate, then we can skip recursive stream properties derivation + // which traverses all child plan nodes again and is therefore expensive to check + @SuppressWarnings("deprecation") + boolean propagateLocalProperties = !inputLocalProperties.isEmpty() && isLocalExchangesSourceSingleStreamDistributed(node, plannerContext.getMetadata(), session); + if (propagateLocalProperties) { // Single stream input's local sorting and grouping properties are preserved // In case of merging exchange, it's orderingScheme takes precedence - localProperties.addAll(LocalProperties.translate( - inputProperty.getLocalProperties(), - symbol -> Optional.ofNullable(inputToOutput.get(symbol)))); + localProperties.addAll(inputLocalProperties); } } } diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/StreamPropertyDerivations.java b/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/StreamPropertyDerivations.java index a2aab6c4cfa9..76980fd32631 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/StreamPropertyDerivations.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/StreamPropertyDerivations.java @@ -100,6 +100,7 @@ import static io.trino.sql.planner.optimizations.StreamPropertyDerivations.StreamProperties.StreamDistribution.FIXED; import static io.trino.sql.planner.optimizations.StreamPropertyDerivations.StreamProperties.StreamDistribution.MULTIPLE; import static io.trino.sql.planner.optimizations.StreamPropertyDerivations.StreamProperties.StreamDistribution.SINGLE; +import static io.trino.sql.planner.plan.ExchangeNode.Scope.LOCAL; import static io.trino.sql.planner.plan.ExchangeNode.Scope.REMOTE; import static io.trino.sql.tree.SkipTo.Position.PAST_LAST; import static java.lang.String.format; @@ -161,12 +162,9 @@ public static StreamProperties deriveProperties( types, typeAnalyzer); - StreamProperties result = node.accept(new Visitor(plannerContext.getMetadata(), session), inputProperties) + StreamProperties result = deriveStreamPropertiesWithoutActualProperties(node, inputProperties, plannerContext.getMetadata(), session) .withOtherActualProperties(otherProperties); - result.getPartitioningColumns().ifPresent(columns -> - verify(node.getOutputSymbols().containsAll(columns), "Stream-level partitioning properties contain columns not present in node's output")); - Set localPropertyColumns = result.getLocalProperties().stream() .flatMap(property -> property.getColumns().stream()) .collect(Collectors.toSet()); @@ -176,6 +174,50 @@ public static StreamProperties deriveProperties( return result; } + /** + * Determines whether a local exchange is single-stream distributed at its only input source. This method will is expensive + * since it requires traversing the entire sub-plan at each local exchange node, so calling this method should be avoided + * whenever possible and new usages should not be added. + * + * @param exchangeNode a local exchange with a single input source to check for single stream input distribution + * @throws IllegalArgumentException if the exchange is not a local exchange or does not have only a single input source + * @deprecated Only for use by {@link PropertyDerivations} + */ + @Deprecated + static boolean isLocalExchangesSourceSingleStreamDistributed(ExchangeNode exchangeNode, Metadata metadata, Session session) + { + checkArgument(exchangeNode.getScope() == LOCAL, "exchangeNode must be a local exchange"); + checkArgument(exchangeNode.getSources().size() == 1, "exchangeNode must have a single source"); + + return deriveStreamPropertiesWithoutActualPropertiesRecursively(exchangeNode.getSources().get(0), metadata, session).isSingleStream(); + } + + /** + * Derives {@link StreamProperties} without populating {@link StreamProperties#otherActualProperties}. This is necessary to avoid exponential-time, + * mutually recursive sub-plan traversals when {@link PropertyDerivations} attempts to check a local exchange's input source for single stream distribution. + * + * @deprecated For internal use only by {@link StreamPropertyDerivations#isLocalExchangesSourceSingleStreamDistributed(ExchangeNode, Metadata, Session)} + */ + @Deprecated + private static StreamProperties deriveStreamPropertiesWithoutActualPropertiesRecursively(PlanNode node, Metadata metadata, Session session) + { + List inputProperties = node.getSources().stream() + .map(source -> deriveStreamPropertiesWithoutActualPropertiesRecursively(source, metadata, session)) + .collect(toImmutableList()); + + return deriveStreamPropertiesWithoutActualProperties(node, inputProperties, metadata, session); + } + + private static StreamProperties deriveStreamPropertiesWithoutActualProperties(PlanNode node, List inputProperties, Metadata metadata, Session session) + { + StreamProperties result = node.accept(new Visitor(metadata, session), inputProperties); + + result.getPartitioningColumns().ifPresent(columns -> + verify(node.getOutputSymbols().containsAll(columns), "Stream-level partitioning properties contain columns not present in node's output")); + + return result; + } + private static class Visitor extends PlanVisitor> { @@ -833,7 +875,8 @@ public StreamProperties translate(Function> translator) } return Optional.of(newPartitioningColumns.build()); }), - ordered, otherActualProperties.translate(translator)); + ordered, + otherActualProperties == null ? null : otherActualProperties.translate(translator)); } public Optional> getPartitioningColumns() From a4058405c6ce9d030636719c493cf8d5d7d35a15 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Tue, 7 Nov 2023 17:14:07 +0100 Subject: [PATCH 119/587] Increase tolerance in MariaDB testBasic stats tests With previous tolerance the test was observed failing on CI. --- .../io/trino/plugin/mariadb/BaseMariaDbTableStatisticsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/BaseMariaDbTableStatisticsTest.java b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/BaseMariaDbTableStatisticsTest.java index 5078044ce4fb..c764453dfb06 100644 --- a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/BaseMariaDbTableStatisticsTest.java +++ b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/BaseMariaDbTableStatisticsTest.java @@ -129,7 +129,7 @@ public void testBasic() .put("shippriority", 1) .put("comment", varcharNdvToExpected.apply(14995)) .build()); - assertThat(getTableCardinalityFromStats(statsResult)).isCloseTo(15000, withinPercentage(20)); + assertThat(getTableCardinalityFromStats(statsResult)).isCloseTo(15000, withinPercentage(30)); } finally { assertUpdate("DROP TABLE " + tableName); From 89365d6f03fd19b1fe575b11cd29e324ee3a976e Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Wed, 8 Nov 2023 13:34:46 +0100 Subject: [PATCH 120/587] Use random table names in TestJdbcPreparedStatement --- .../trino/jdbc/TestJdbcPreparedStatement.java | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcPreparedStatement.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcPreparedStatement.java index cb5a308dae5e..c889011d54d9 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcPreparedStatement.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcPreparedStatement.java @@ -62,6 +62,7 @@ import static io.trino.jdbc.BaseTestJdbcResultSet.toSqlTime; import static io.trino.jdbc.TestingJdbcUtils.list; import static io.trino.jdbc.TestingJdbcUtils.readRows; +import static io.trino.testing.TestingNames.randomNameSuffix; import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; import static java.sql.ParameterMetaData.parameterModeUnknown; @@ -160,9 +161,10 @@ public void testGetMetadata() private void testGetMetadata(boolean explicitPrepare) throws Exception { + String tableName = "test_get_metadata_" + randomNameSuffix(); try (Connection connection = createConnection("blackhole", "blackhole", explicitPrepare)) { try (Statement statement = connection.createStatement()) { - statement.execute("CREATE TABLE test_get_metadata (" + + statement.execute("CREATE TABLE " + tableName + " (" + "c_boolean boolean, " + "c_decimal decimal, " + "c_decimal_2 decimal(10,3)," + @@ -174,13 +176,13 @@ private void testGetMetadata(boolean explicitPrepare) } try (PreparedStatement statement = connection.prepareStatement( - "SELECT * FROM test_get_metadata")) { + "SELECT * FROM " + tableName)) { ResultSetMetaData metadata = statement.getMetaData(); assertEquals(metadata.getColumnCount(), 8); for (int i = 1; i <= metadata.getColumnCount(); i++) { assertEquals(metadata.getCatalogName(i), "blackhole"); assertEquals(metadata.getSchemaName(i), "blackhole"); - assertEquals(metadata.getTableName(i), "test_get_metadata"); + assertEquals(metadata.getTableName(i), tableName); } assertEquals(metadata.getColumnName(1), "c_boolean"); @@ -209,7 +211,7 @@ private void testGetMetadata(boolean explicitPrepare) } try (Statement statement = connection.createStatement()) { - statement.execute("DROP TABLE test_get_metadata"); + statement.execute("DROP TABLE " + tableName); } } } @@ -225,9 +227,10 @@ public void testGetParameterMetaData() private void testGetParameterMetaData(boolean explicitPrepare) throws Exception { + String tableName = "test_get_parameterMetaData_" + randomNameSuffix(); try (Connection connection = createConnection("blackhole", "blackhole", explicitPrepare)) { try (Statement statement = connection.createStatement()) { - statement.execute("CREATE TABLE test_get_parameterMetaData (" + + statement.execute("CREATE TABLE " + tableName + " (" + "c_boolean boolean, " + "c_decimal decimal, " + "c_decimal_2 decimal(10,3)," + @@ -245,7 +248,7 @@ private void testGetParameterMetaData(boolean explicitPrepare) } try (PreparedStatement statement = connection.prepareStatement( - "SELECT ? FROM test_get_parameterMetaData WHERE c_boolean = ? AND c_decimal = ? " + + "SELECT ? FROM " + tableName + " WHERE c_boolean = ? AND c_decimal = ? " + "AND c_decimal_2 = ? AND c_varchar = ? AND c_varchar_2 = ? AND c_row = ? " + "AND c_array = ? AND c_map = ? AND c_tinyint = ? AND c_integer = ? AND c_bigint = ? " + "AND c_smallint = ? AND c_real = ? AND c_double = ?")) { @@ -362,7 +365,7 @@ private void testGetParameterMetaData(boolean explicitPrepare) } try (Statement statement = connection.createStatement()) { - statement.execute("DROP TABLE test_get_parameterMetaData"); + statement.execute("DROP TABLE " + tableName); } } } @@ -485,9 +488,10 @@ public void testExecuteUpdate() public void testExecuteUpdate(boolean explicitPrepare) throws Exception { + String tableName = "test_execute_update_" + randomNameSuffix(); try (Connection connection = createConnection("blackhole", "blackhole", explicitPrepare)) { try (Statement statement = connection.createStatement()) { - statement.execute("CREATE TABLE test_execute_update (" + + statement.execute("CREATE TABLE " + tableName + " (" + "c_boolean boolean, " + "c_bigint bigint, " + "c_double double, " + @@ -498,7 +502,7 @@ public void testExecuteUpdate(boolean explicitPrepare) } try (PreparedStatement statement = connection.prepareStatement( - "INSERT INTO test_execute_update VALUES (?, ?, ?, ?, ?, ?, ?)")) { + "INSERT INTO " + tableName + " VALUES (?, ?, ?, ?, ?, ?, ?)")) { statement.setBoolean(1, true); statement.setLong(2, 5L); statement.setDouble(3, 7.0d); @@ -515,7 +519,7 @@ public void testExecuteUpdate(boolean explicitPrepare) } try (Statement statement = connection.createStatement()) { - statement.execute("DROP TABLE test_execute_update"); + statement.execute("DROP TABLE " + tableName); } } } @@ -531,13 +535,14 @@ public void testExecuteBatch() private void testExecuteBatch(boolean explicitPrepare) throws Exception { + String tableName = "test_execute_batch_" + randomNameSuffix(); try (Connection connection = createConnection("memory", "default", explicitPrepare)) { try (Statement statement = connection.createStatement()) { - statement.execute("CREATE TABLE test_execute_batch(c_int integer)"); + statement.execute("CREATE TABLE " + tableName + "(c_int integer)"); } try (PreparedStatement preparedStatement = connection.prepareStatement( - "INSERT INTO test_execute_batch VALUES (?)")) { + "INSERT INTO " + tableName + " VALUES (?)")) { // Run executeBatch before addBatch assertEquals(preparedStatement.executeBatch(), new int[] {}); @@ -548,7 +553,7 @@ private void testExecuteBatch(boolean explicitPrepare) assertEquals(preparedStatement.executeBatch(), new int[] {1, 1, 1}); try (Statement statement = connection.createStatement()) { - ResultSet resultSet = statement.executeQuery("SELECT c_int FROM test_execute_batch"); + ResultSet resultSet = statement.executeQuery("SELECT c_int FROM " + tableName); assertThat(readRows(resultSet)) .containsExactlyInAnyOrder( list(0), @@ -569,7 +574,7 @@ private void testExecuteBatch(boolean explicitPrepare) } try (Statement statement = connection.createStatement()) { - statement.execute("DROP TABLE test_execute_batch"); + statement.execute("DROP TABLE " + tableName); } } } @@ -585,13 +590,14 @@ public void testInvalidExecuteBatch() private void testInvalidExecuteBatch(boolean explicitPrepare) throws Exception { + String tableName = "test_execute_invalid_batch_" + randomNameSuffix(); try (Connection connection = createConnection("blackhole", "blackhole", explicitPrepare)) { try (Statement statement = connection.createStatement()) { - statement.execute("CREATE TABLE test_invalid_execute_batch(c_int integer)"); + statement.execute("CREATE TABLE " + tableName + "(c_int integer)"); } try (PreparedStatement statement = connection.prepareStatement( - "INSERT INTO test_invalid_execute_batch VALUES (?)")) { + "INSERT INTO " + tableName + " VALUES (?)")) { statement.setInt(1, 1); statement.addBatch(); @@ -611,7 +617,7 @@ private void testInvalidExecuteBatch(boolean explicitPrepare) } try (Statement statement = connection.createStatement()) { - statement.execute("DROP TABLE test_invalid_execute_batch"); + statement.execute("DROP TABLE " + tableName); } } } @@ -1423,12 +1429,13 @@ private Connection createConnection(String catalog, String schema, boolean expli private void testExplicitPrepareSetting(boolean explicitPrepare, String expectedSql) throws Exception { - String selectSql = "SELECT * FROM blackhole.blackhole.test_table WHERE x = ? AND y = ? AND y <> 'Test'"; - String insertSql = "INSERT INTO blackhole.blackhole.test_table (x, y) VALUES (?, ?)"; + String tableName = "test_table_" + randomNameSuffix(); + String selectSql = "SELECT * FROM blackhole.blackhole." + tableName + " WHERE x = ? AND y = ? AND y <> 'Test'"; + String insertSql = "INSERT INTO blackhole.blackhole." + tableName + " (x, y) VALUES (?, ?)"; try (Connection connection = createConnection(explicitPrepare)) { try (Statement statement = connection.createStatement()) { - assertEquals(statement.executeUpdate("CREATE TABLE blackhole.blackhole.test_table (x bigint, y varchar)"), 0); + assertEquals(statement.executeUpdate("CREATE TABLE blackhole.blackhole." + tableName + " (x bigint, y varchar)"), 0); } try (PreparedStatement ps = connection.prepareStatement(selectSql)) { @@ -1484,7 +1491,7 @@ private void testExplicitPrepareSetting(boolean explicitPrepare, String expected } try (Statement statement = connection.createStatement()) { - assertEquals(statement.executeUpdate("DROP TABLE blackhole.blackhole.test_table"), 0); + assertEquals(statement.executeUpdate("DROP TABLE blackhole.blackhole." + tableName), 0); } } } From 320952a12a6ca0e1e0c8a57ece6feea460c5f87f Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Tue, 7 Nov 2023 20:55:08 -0800 Subject: [PATCH 121/587] Remove unnecessary data provider --- .../plugin/hive/orc/TestOrcWriterOptions.java | 33 +++++++--------- .../plugin/hive/parquet/TestTimestamp.java | 39 ++++++++----------- .../hive/s3/TestHiveS3MinioQueries.java | 25 +++++++----- 3 files changed, 47 insertions(+), 50 deletions(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcWriterOptions.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcWriterOptions.java index b986a86ac67d..5807b72d7af0 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcWriterOptions.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcWriterOptions.java @@ -15,7 +15,6 @@ import io.airlift.units.DataSize; import io.trino.orc.OrcWriterOptions; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.util.Properties; @@ -90,31 +89,29 @@ public void testOrcWriterOptionsFromTableProperties() @Test public void testOrcWriterOptionsWithInvalidFPPValue() { - Properties tableProperties = new Properties(); - tableProperties.setProperty(ORC_BLOOM_FILTER_COLUMNS_KEY, "column_with_bloom_filter"); - tableProperties.setProperty(ORC_BLOOM_FILTER_FPP_KEY, "abc"); + Properties tableProperties = createTablePropertiesWithFpp("abc"); assertThatThrownBy(() -> getOrcWriterOptions(tableProperties, new OrcWriterOptions())) .hasMessage("Invalid value for orc_bloom_filter_fpp property: abc"); } - @Test(dataProvider = "invalidBloomFilterFpp") - public void testOrcBloomFilterWithInvalidRange(String fpp) + @Test + public void testOrcBloomFilterWithInvalidRange() { - Properties tableProperties = new Properties(); - tableProperties.setProperty(ORC_BLOOM_FILTER_COLUMNS_KEY, "column_with_bloom_filter"); - tableProperties.setProperty(ORC_BLOOM_FILTER_FPP_KEY, fpp); - assertThatThrownBy(() -> getOrcWriterOptions(tableProperties, new OrcWriterOptions())) + assertThatThrownBy(() -> getOrcWriterOptions(createTablePropertiesWithFpp("100"), new OrcWriterOptions())) + .hasMessage("bloomFilterFpp should be > 0.0 & < 1.0"); + assertThatThrownBy(() -> getOrcWriterOptions(createTablePropertiesWithFpp("-100"), new OrcWriterOptions())) + .hasMessage("bloomFilterFpp should be > 0.0 & < 1.0"); + assertThatThrownBy(() -> getOrcWriterOptions(createTablePropertiesWithFpp("0"), new OrcWriterOptions())) + .hasMessage("bloomFilterFpp should be > 0.0 & < 1.0"); + assertThatThrownBy(() -> getOrcWriterOptions(createTablePropertiesWithFpp("1"), new OrcWriterOptions())) .hasMessage("bloomFilterFpp should be > 0.0 & < 1.0"); } - @DataProvider - public Object[][] invalidBloomFilterFpp() + private static Properties createTablePropertiesWithFpp(String fpp) { - return new Object[][]{ - {"100"}, - {"-100"}, - {"0"}, - {"1"} - }; + Properties properties = new Properties(); + properties.setProperty(ORC_BLOOM_FILTER_COLUMNS_KEY, "column_with_bloom_filter"); + properties.setProperty(ORC_BLOOM_FILTER_FPP_KEY, fpp); + return properties; } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestamp.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestamp.java index df390dbdcba2..59e8f11247e6 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestamp.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestamp.java @@ -30,11 +30,9 @@ import org.apache.parquet.format.CompressionCodec; import org.apache.parquet.schema.MessageType; import org.joda.time.DateTimeZone; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.io.IOException; -import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Optional; @@ -47,7 +45,6 @@ import static io.trino.spi.type.TimestampType.createTimestampType; import static io.trino.spi.type.Timestamps.NANOSECONDS_PER_MICROSECOND; import static io.trino.spi.type.Timestamps.PICOSECONDS_PER_NANOSECOND; -import static io.trino.testing.DataProviders.toDataProvider; import static java.lang.Math.floorDiv; import static java.lang.Math.floorMod; import static java.util.Arrays.asList; @@ -63,31 +60,27 @@ public class TestTimestamp { - @DataProvider - public static Object[][] timestampPrecisionProvider() - { - return Arrays.stream(HiveTimestampPrecision.values()).collect(toDataProvider()); - } - - @Test(dataProvider = "timestampPrecisionProvider") - public void testTimestampBackedByInt64(HiveTimestampPrecision timestamp) + @Test + public void testTimestampBackedByInt64() throws Exception { - String logicalAnnotation = switch (timestamp) { - case MILLISECONDS -> "TIMESTAMP(MILLIS,true)"; - case MICROSECONDS -> "TIMESTAMP(MICROS,true)"; - case NANOSECONDS -> "TIMESTAMP(NANOS,true)"; - }; - MessageType parquetSchema = parseMessageType("message hive_timestamp { optional int64 test (" + logicalAnnotation + "); }"); + for (HiveTimestampPrecision timestamp : HiveTimestampPrecision.values()) { + String logicalAnnotation = switch (timestamp) { + case MILLISECONDS -> "TIMESTAMP(MILLIS,true)"; + case MICROSECONDS -> "TIMESTAMP(MICROS,true)"; + case NANOSECONDS -> "TIMESTAMP(NANOS,true)"; + }; + MessageType parquetSchema = parseMessageType("message hive_timestamp { optional int64 test (" + logicalAnnotation + "); }"); - Iterable writeNullableDictionaryValues = limit(cycle(asList(1L, null, 3L, 5L, null, null, null, 7L, 11L, null, 13L, 17L)), 30_000); - testRoundTrip(parquetSchema, writeNullableDictionaryValues, timestamp); + Iterable writeNullableDictionaryValues = limit(cycle(asList(1L, null, 3L, 5L, null, null, null, 7L, 11L, null, 13L, 17L)), 30_000); + testRoundTrip(parquetSchema, writeNullableDictionaryValues, timestamp); - Iterable writeDictionaryValues = limit(cycle(asList(1L, 3L, 5L, 7L, 11L, 13L, 17L)), 30_000); - testRoundTrip(parquetSchema, writeDictionaryValues, timestamp); + Iterable writeDictionaryValues = limit(cycle(asList(1L, 3L, 5L, 7L, 11L, 13L, 17L)), 30_000); + testRoundTrip(parquetSchema, writeDictionaryValues, timestamp); - Iterable writeValues = ContiguousSet.create(Range.closedOpen((long) -1_000, (long) 1_000), DiscreteDomain.longs()); - testRoundTrip(parquetSchema, writeValues, timestamp); + Iterable writeValues = ContiguousSet.create(Range.closedOpen((long) -1_000, (long) 1_000), DiscreteDomain.longs()); + testRoundTrip(parquetSchema, writeValues, timestamp); + } } private static void testRoundTrip(MessageType parquetSchema, Iterable writeValues, HiveTimestampPrecision timestamp) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestHiveS3MinioQueries.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestHiveS3MinioQueries.java index 10b57a440e97..29b059e9f009 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestHiveS3MinioQueries.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestHiveS3MinioQueries.java @@ -20,7 +20,6 @@ import io.trino.plugin.hive.metastore.file.FileHiveMetastore; import io.trino.plugin.hive.metastore.file.FileHiveMetastoreConfig; import io.trino.testing.AbstractTestQueryFramework; -import io.trino.testing.DataProviders; import io.trino.testing.QueryRunner; import io.trino.testing.containers.Minio; import org.testng.annotations.AfterClass; @@ -78,20 +77,28 @@ public void cleanUp() minio = null; // closed by closeAfterClass } - @Test(dataProviderClass = DataProviders.class, dataProvider = "trueFalse") - public void testTableLocationTopOfTheBucket(boolean locationWithTrailingSlash) + @Test + public void testTableLocationTopOfTheBucket() { String bucketName = "test-bucket-" + randomNameSuffix(); minio.createBucket(bucketName); minio.writeFile("We are\nawesome at\nmultiple slashes.".getBytes(UTF_8), bucketName, "a_file"); - String location = "s3://%s%s".formatted(bucketName, locationWithTrailingSlash ? "/" : ""); - String tableName = "test_table_top_of_bucket_%s_%s".formatted(locationWithTrailingSlash, randomNameSuffix()); + // without trailing slash + assertQueryFails( + """ + CREATE TABLE %s (a varchar) WITH ( + format='TEXTFILE', + external_location='%s' + ) + """.formatted("test_table_top_of_bucket_" + randomNameSuffix(), "s3://" + bucketName), + "External location is not a valid file system URI: s3://" + bucketName); + + // with trailing slash + String location = "s3://%s/".formatted(bucketName); + String tableName = "test_table_top_of_bucket_%s".formatted(randomNameSuffix()); String create = "CREATE TABLE %s (a varchar) WITH (format='TEXTFILE', external_location='%s')".formatted(tableName, location); - if (!locationWithTrailingSlash) { - assertQueryFails(create, "External location is not a valid file system URI: " + location); - return; - } + assertUpdate(create); // Verify location was not normalized along the way. Glue would not do that. From 20008cb6fa8f9af82c4b90bbc3c85651ad8042e5 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Tue, 7 Nov 2023 20:33:10 -0800 Subject: [PATCH 122/587] Migrate tests to JUnit --- .../s3/AbstractTestTrinoS3FileSystem.java | 2 +- .../hdfs/s3/TestTrinoS3FileSystemAwsS3.java | 8 ++-- .../hdfs/s3/TestTrinoS3FileSystemMinio.java | 20 +++++----- .../hive/TestHiveThriftMetastoreWithS3.java | 38 +++++++------------ .../hive/coercions/TestDateCoercer.java | 2 +- .../fs/BaseCachingDirectoryListerTest.java | 2 +- .../hive/fs/TestCachingDirectoryLister.java | 13 ++----- ...hingDirectoryListerRecursiveFilesOnly.java | 6 ++- ...ransactionScopeCachingDirectoryLister.java | 6 ++- .../plugin/hive/orc/TestOrcWriterOptions.java | 2 +- .../plugin/hive/parquet/TestTimestamp.java | 2 +- .../hive/s3/TestHiveS3MinioQueries.java | 9 +---- ...stIcebergGetTableStatisticsOperations.java | 23 ++++++----- .../trino/plugin/ignite/TestIgniteClient.java | 2 +- .../plugin/ignite/TestIgniteJdbcConfig.java | 2 +- .../trino/plugin/ignite/TestIgnitePlugin.java | 2 +- .../kafka/TestInternalFieldConflict.java | 6 ++- .../trino/plugin/kafka/TestKafkaConfig.java | 2 +- .../plugin/kafka/TestKafkaFilterManager.java | 2 +- .../kafka/TestKafkaIntegrationPushDown.java | 6 ++- .../kafka/TestKafkaInternalFieldManager.java | 2 +- .../trino/plugin/kafka/TestKafkaPlugin.java | 2 +- .../plugin/kafka/TestKafkaSslConfig.java | 2 +- .../kafka/TestMinimalFunctionality.java | 6 ++- .../json/TestCustomJsonDateTimeFormatter.java | 2 +- .../TestISO8601JsonDateTimeFormatter.java | 2 +- .../kafka/encoder/json/TestJsonEncoder.java | 2 +- .../TestRFC2822JsonDateTimeFormatter.java | 2 +- .../encoder/raw/TestRawEncoderMapping.java | 2 +- ...ithSchemaRegistryMinimalFunctionality.java | 6 ++- ...estAvroConfluentContentSchemaProvider.java | 2 +- .../TestAvroConfluentRowDecoder.java | 2 +- .../confluent/TestAvroSchemaConverter.java | 2 +- ...stClassLoaderSafeSchemaRegistryClient.java | 2 +- .../TestConfluentSchemaRegistryConfig.java | 2 +- ...chemaRegistryTableDescriptionSupplier.java | 2 +- .../confluent/TestForwardingSchemaParser.java | 2 +- ...entSchemaRegistryMinimalFunctionality.java | 6 ++- ...estFileTableDescriptionSupplierConfig.java | 2 +- .../kafka/utils/TestPropertiesUtils.java | 2 +- plugin/trino-kinesis/pom.xml | 12 ++++++ .../plugin/kinesis/TestKinesisConfig.java | 2 +- .../plugin/kinesis/TestKinesisPlugin.java | 2 +- .../TestKinesisTableDescriptionSupplier.java | 12 ++++-- 44 files changed, 122 insertions(+), 113 deletions(-) diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/AbstractTestTrinoS3FileSystem.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/AbstractTestTrinoS3FileSystem.java index 6eec7a955de3..1919ed6a1b81 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/AbstractTestTrinoS3FileSystem.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/AbstractTestTrinoS3FileSystem.java @@ -22,7 +22,7 @@ import com.google.common.net.MediaType; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.ByteArrayInputStream; import java.io.InputStream; diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestTrinoS3FileSystemAwsS3.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestTrinoS3FileSystemAwsS3.java index 1f6e6e68b327..413836858627 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestTrinoS3FileSystemAwsS3.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestTrinoS3FileSystemAwsS3.java @@ -14,7 +14,6 @@ package io.trino.hdfs.s3; import org.apache.hadoop.conf.Configuration; -import org.testng.annotations.BeforeClass; import static java.util.Objects.requireNonNull; @@ -27,11 +26,10 @@ public class TestTrinoS3FileSystemAwsS3 extends AbstractTestTrinoS3FileSystem { - private String bucketName; - private String s3Endpoint; + private final String bucketName; + private final String s3Endpoint; - @BeforeClass - public void setup() + public TestTrinoS3FileSystemAwsS3() { bucketName = requireNonNull(System.getenv("S3_BUCKET"), "Environment S3_BUCKET was not set"); s3Endpoint = requireNonNull(System.getenv("S3_BUCKET_ENDPOINT"), "Environment S3_BUCKET_ENDPOINT was not set"); diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestTrinoS3FileSystemMinio.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestTrinoS3FileSystemMinio.java index 6a02494548ef..d5549d2065bf 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestTrinoS3FileSystemMinio.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestTrinoS3FileSystemMinio.java @@ -19,9 +19,9 @@ import io.trino.util.AutoCloseableCloser; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import java.net.URI; @@ -30,20 +30,20 @@ import static io.trino.testing.containers.Minio.MINIO_SECRET_KEY; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.testng.Assert.assertTrue; +@TestInstance(PER_CLASS) public class TestTrinoS3FileSystemMinio extends AbstractTestTrinoS3FileSystem { private final String bucketName = "test-bucket-" + randomNameSuffix(); - private Minio minio; + private final Minio minio; - private MinioClient minioClient; + private final MinioClient minioClient; - @BeforeClass - public void setup() - throws Exception + public TestTrinoS3FileSystemMinio() { minio = Minio.builder().build(); minio.start(); @@ -52,7 +52,7 @@ public void setup() minio.createBucket(bucketName); } - @AfterClass(alwaysRun = true) + @AfterAll public void tearDown() throws Exception { @@ -60,8 +60,6 @@ public void tearDown() closer.register(minio); closer.register(minioClient); } - minioClient = null; - minio = null; } @Override diff --git a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveThriftMetastoreWithS3.java b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveThriftMetastoreWithS3.java index 76b8251ca653..d46a24a7e515 100644 --- a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveThriftMetastoreWithS3.java +++ b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveThriftMetastoreWithS3.java @@ -26,10 +26,10 @@ import io.trino.plugin.hive.s3.S3HiveQueryRunner; import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.QueryRunner; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Parameters; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import java.io.IOException; import java.nio.file.Files; @@ -41,7 +41,9 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +@TestInstance(PER_CLASS) public class TestHiveThriftMetastoreWithS3 extends AbstractTestQueryFramework { @@ -54,26 +56,14 @@ public class TestHiveThriftMetastoreWithS3 private final Path hadoopCoreSiteXmlTempFile; private final AmazonS3 s3Client; - @Parameters({ - "hive.hadoop2.s3.endpoint", - "hive.hadoop2.s3.region", - "hive.hadoop2.s3.awsAccessKey", - "hive.hadoop2.s3.awsSecretKey", - "hive.hadoop2.s3.writableBucket", - }) - public TestHiveThriftMetastoreWithS3( - String s3endpoint, - String s3Region, - String awsAccessKey, - String awsSecretKey, - String writableBucket) + public TestHiveThriftMetastoreWithS3() throws IOException { - this.s3endpoint = requireNonNull(s3endpoint, "s3endpoint is null"); - this.s3Region = requireNonNull(s3Region, "s3Region is null"); - this.awsAccessKey = requireNonNull(awsAccessKey, "awsAccessKey is null"); - this.awsSecretKey = requireNonNull(awsSecretKey, "awsSecretKey is null"); - this.writableBucket = requireNonNull(writableBucket, "writableBucket is null"); + this.s3endpoint = requireNonNull(System.getProperty("hive.hadoop2.s3.endpoint"), "hive.hadoop2.s3.endpoint is null"); + this.s3Region = requireNonNull(System.getProperty("hive.hadoop2.s3.region"), "hive.hadoop2.s3.region is null"); + this.awsAccessKey = requireNonNull(System.getProperty("hive.hadoop2.s3.awsAccessKey"), "hive.hadoop2.s3.awsAccessKey is null"); + this.awsSecretKey = requireNonNull(System.getProperty("hive.hadoop2.s3.awsSecretKey"), "hive.hadoop2.s3.awsSecretKey is null"); + this.writableBucket = requireNonNull(System.getProperty("hive.hadoop2.s3.writableBucket"), "hive.hadoop2.s3.writableBucket is null"); this.schemaName = "test_thrift_s3_" + randomNameSuffix(); String coreSiteXmlContent = Resources.toString(Resources.getResource("s3/hive-core-site.template.xml"), UTF_8) @@ -113,14 +103,14 @@ protected QueryRunner createQueryRunner() .build(); } - @BeforeClass + @BeforeAll public void setUp() { String schemaLocation = "s3a://%s/%s".formatted(writableBucket, schemaName); assertUpdate("CREATE SCHEMA " + schemaName + " WITH (location = '" + schemaLocation + "')"); } - @AfterClass(alwaysRun = true) + @AfterAll public void tearDown() { assertUpdate("DROP SCHEMA IF EXISTS " + schemaName); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestDateCoercer.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestDateCoercer.java index dc791aa9f047..581fb2dbb259 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestDateCoercer.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestDateCoercer.java @@ -17,7 +17,7 @@ import io.trino.spi.block.Block; import io.trino.spi.type.DateType; import io.trino.spi.type.Type; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.time.LocalDate; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/BaseCachingDirectoryListerTest.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/BaseCachingDirectoryListerTest.java index ee26f071ecc7..f305d7a5e514 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/BaseCachingDirectoryListerTest.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/BaseCachingDirectoryListerTest.java @@ -23,7 +23,7 @@ import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.MaterializedRow; import io.trino.testing.QueryRunner; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.nio.file.Path; import java.util.List; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/TestCachingDirectoryLister.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/TestCachingDirectoryLister.java index 3d37a7e05fe5..8c5d805e828a 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/TestCachingDirectoryLister.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/TestCachingDirectoryLister.java @@ -16,14 +16,15 @@ import io.airlift.units.DataSize; import io.airlift.units.Duration; import io.trino.filesystem.Location; -import org.testng.annotations.Test; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import static io.airlift.units.DataSize.Unit.MEGABYTE; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; // some tests may invalidate the whole cache affecting therefore other concurrent tests -@Test(singleThreaded = true) +@Execution(SAME_THREAD) public class TestCachingDirectoryLister extends BaseCachingDirectoryListerTest { @@ -38,12 +39,4 @@ protected boolean isCached(CachingDirectoryLister directoryLister, Location loca { return directoryLister.isCached(location); } - - @Test - public void forceTestNgToRespectSingleThreaded() - { - // TODO: Remove after updating TestNG to 7.4.0+ (https://github.com/trinodb/trino/issues/8571) - // TestNG doesn't enforce @Test(singleThreaded = true) when tests are defined in base class. According to - // https://github.com/cbeust/testng/issues/2361#issuecomment-688393166 a workaround it to add a dummy test to the leaf test class. - } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/TestCachingDirectoryListerRecursiveFilesOnly.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/TestCachingDirectoryListerRecursiveFilesOnly.java index 010b8330145c..5cfb919a2e0a 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/TestCachingDirectoryListerRecursiveFilesOnly.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/TestCachingDirectoryListerRecursiveFilesOnly.java @@ -21,7 +21,8 @@ import io.trino.plugin.hive.metastore.MetastoreUtil; import io.trino.plugin.hive.metastore.Table; import io.trino.testing.QueryRunner; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.NoSuchElementException; @@ -30,11 +31,12 @@ import static io.trino.plugin.hive.HiveQueryRunner.TPCH_SCHEMA; import static io.trino.plugin.hive.metastore.PrincipalPrivileges.NO_PRIVILEGES; import static java.lang.String.format; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; // some tests may invalidate the whole cache affecting therefore other concurrent tests -@Test(singleThreaded = true) +@Execution(SAME_THREAD) public class TestCachingDirectoryListerRecursiveFilesOnly extends BaseCachingDirectoryListerTest { diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/TestTransactionScopeCachingDirectoryLister.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/TestTransactionScopeCachingDirectoryLister.java index a67bb89bf514..bd7817e4d456 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/TestTransactionScopeCachingDirectoryLister.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/TestTransactionScopeCachingDirectoryLister.java @@ -26,7 +26,8 @@ import io.trino.plugin.hive.metastore.Storage; import io.trino.plugin.hive.metastore.StorageFormat; import io.trino.plugin.hive.metastore.Table; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; import java.io.IOException; import java.util.Iterator; @@ -40,9 +41,10 @@ import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; // some tests may invalidate the whole cache affecting therefore other concurrent tests -@Test(singleThreaded = true) +@Execution(SAME_THREAD) public class TestTransactionScopeCachingDirectoryLister extends BaseCachingDirectoryListerTest { diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcWriterOptions.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcWriterOptions.java index 5807b72d7af0..a8b52d6db664 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcWriterOptions.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcWriterOptions.java @@ -15,7 +15,7 @@ import io.airlift.units.DataSize; import io.trino.orc.OrcWriterOptions; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Properties; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestamp.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestamp.java index 59e8f11247e6..8f7a42295e52 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestamp.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestamp.java @@ -30,7 +30,7 @@ import org.apache.parquet.format.CompressionCodec; import org.apache.parquet.schema.MessageType; import org.joda.time.DateTimeZone; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.util.Iterator; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestHiveS3MinioQueries.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestHiveS3MinioQueries.java index 29b059e9f009..ecac36e437fb 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestHiveS3MinioQueries.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestHiveS3MinioQueries.java @@ -22,8 +22,7 @@ import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.QueryRunner; import io.trino.testing.containers.Minio; -import org.testng.annotations.AfterClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.File; import java.util.regex.Matcher; @@ -71,12 +70,6 @@ protected QueryRunner createQueryRunner() .build(); } - @AfterClass(alwaysRun = true) - public void cleanUp() - { - minio = null; // closed by closeAfterClass - } - @Test public void testTableLocationTopOfTheBucket() { diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergGetTableStatisticsOperations.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergGetTableStatisticsOperations.java index 4564006eca7d..46260b412103 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergGetTableStatisticsOperations.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergGetTableStatisticsOperations.java @@ -29,9 +29,10 @@ import io.trino.testing.QueryRunner; import io.trino.tracing.TracingMetadata; import org.intellij.lang.annotations.Language; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.io.IOException; @@ -47,10 +48,13 @@ import static io.trino.sql.planner.LogicalPlanner.Stage.OPTIMIZED_AND_VALIDATED; import static io.trino.testing.TestingSession.testSessionBuilder; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; // Cost-based optimizers' behaviors are affected by the statistics returned by the Connectors. Here is to count the getTableStatistics calls // when CBOs work with Iceberg Connector. -@Test(singleThreaded = true) // counting metadata is a shared mutable state +@TestInstance(PER_CLASS) +@Execution(SAME_THREAD) public class TestIcebergGetTableStatisticsOperations extends AbstractTestQueryFramework { @@ -98,18 +102,15 @@ protected QueryRunner createQueryRunner() return localQueryRunner; } - @AfterClass(alwaysRun = true) + @AfterAll public void tearDown() throws IOException { deleteRecursively(metastoreDir.toPath(), ALLOW_INSECURE); localQueryRunner.close(); - localQueryRunner = null; - spanExporter = null; } - @BeforeMethod - public void resetCounters() + private void resetCounters() { spanExporter.reset(); } @@ -117,6 +118,8 @@ public void resetCounters() @Test public void testTwoWayJoin() { + resetCounters(); + planDistributedQuery("SELECT * " + "FROM iceberg.tiny.orders o, iceberg.tiny.lineitem l " + "WHERE o.orderkey = l.orderkey"); @@ -126,6 +129,8 @@ public void testTwoWayJoin() @Test public void testThreeWayJoin() { + resetCounters(); + planDistributedQuery("SELECT * " + "FROM iceberg.tiny.customer c, iceberg.tiny.orders o, iceberg.tiny.lineitem l " + "WHERE o.orderkey = l.orderkey AND c.custkey = o.custkey"); diff --git a/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteClient.java b/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteClient.java index bad8cc3a0900..0ebb4657e8f0 100644 --- a/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteClient.java +++ b/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteClient.java @@ -26,7 +26,7 @@ import io.trino.spi.connector.ColumnHandle; import io.trino.spi.expression.ConnectorExpression; import io.trino.spi.expression.Variable; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.sql.Types; import java.util.List; diff --git a/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteJdbcConfig.java b/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteJdbcConfig.java index ffdc95dd1559..db9946a63a28 100644 --- a/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteJdbcConfig.java +++ b/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteJdbcConfig.java @@ -14,7 +14,7 @@ package io.trino.plugin.ignite; import jakarta.validation.constraints.AssertTrue; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.airlift.testing.ValidationAssertions.assertFailsValidation; import static io.airlift.testing.ValidationAssertions.assertValidates; diff --git a/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgnitePlugin.java b/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgnitePlugin.java index 535b28cca9b6..e97005439769 100644 --- a/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgnitePlugin.java +++ b/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgnitePlugin.java @@ -17,7 +17,7 @@ import io.trino.spi.Plugin; import io.trino.spi.connector.ConnectorFactory; import io.trino.testing.TestingConnectorContext; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static com.google.common.collect.Iterables.getOnlyElement; diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestInternalFieldConflict.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestInternalFieldConflict.java index 020ee92978c9..52689c909a1e 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestInternalFieldConflict.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestInternalFieldConflict.java @@ -19,7 +19,8 @@ import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.QueryRunner; import io.trino.testing.kafka.TestingKafka; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.plugin.kafka.util.TestUtils.createDescription; import static io.trino.plugin.kafka.util.TestUtils.createOneFieldDescription; @@ -27,8 +28,9 @@ import static io.trino.spi.type.DoubleType.DOUBLE; import static io.trino.spi.type.VarcharType.createVarcharType; import static io.trino.testing.TestingNames.randomNameSuffix; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -@Test(singleThreaded = true) +@Execution(SAME_THREAD) public class TestInternalFieldConflict extends AbstractTestQueryFramework { diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaConfig.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaConfig.java index 004890a711ec..625fdfec865e 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaConfig.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaConfig.java @@ -16,7 +16,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.trino.plugin.kafka.schema.file.FileTableDescriptionSupplier; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.nio.file.Files; diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaFilterManager.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaFilterManager.java index c7a15c20f5eb..a54ba4be6622 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaFilterManager.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaFilterManager.java @@ -17,7 +17,7 @@ import io.trino.spi.predicate.Domain; import io.trino.spi.predicate.Range; import io.trino.spi.predicate.SortedRangeSet; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Set; diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaIntegrationPushDown.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaIntegrationPushDown.java index f763ff6d7628..8bb8504af691 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaIntegrationPushDown.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaIntegrationPushDown.java @@ -24,7 +24,8 @@ import io.trino.testing.kafka.TestingKafka; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.clients.producer.RecordMetadata; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; import java.time.Instant; import java.time.LocalDateTime; @@ -39,9 +40,10 @@ import static io.trino.testing.assertions.Assert.assertEventually; import static java.lang.String.format; import static java.util.Objects.requireNonNull; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; -@Test(singleThreaded = true) +@Execution(SAME_THREAD) public class TestKafkaIntegrationPushDown extends AbstractTestQueryFramework { diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaInternalFieldManager.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaInternalFieldManager.java index 465b1a6b68f1..3f22784685b9 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaInternalFieldManager.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaInternalFieldManager.java @@ -15,7 +15,7 @@ import io.trino.spi.connector.ColumnMetadata; import io.trino.spi.type.BigintType; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Optional; diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaPlugin.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaPlugin.java index 7f669ae3bfa9..78490aaf99bc 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaPlugin.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaPlugin.java @@ -17,7 +17,7 @@ import io.trino.spi.connector.Connector; import io.trino.spi.connector.ConnectorFactory; import io.trino.testing.TestingConnectorContext; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.FileWriter; import java.io.IOException; diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaSslConfig.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaSslConfig.java index 6abb0a18faa8..8cb0ac969f99 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaSslConfig.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaSslConfig.java @@ -17,7 +17,7 @@ import com.google.inject.ConfigurationException; import io.trino.plugin.kafka.security.KafkaEndpointIdentificationAlgorithm; import io.trino.plugin.kafka.security.KafkaSslConfig; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.FileWriter; import java.io.IOException; diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestMinimalFunctionality.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestMinimalFunctionality.java index b76263ce92e8..d8208e3edcee 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestMinimalFunctionality.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestMinimalFunctionality.java @@ -20,15 +20,17 @@ import io.trino.testing.QueryRunner; import io.trino.testing.kafka.TestingKafka; import org.apache.kafka.clients.producer.ProducerRecord; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; import java.util.stream.LongStream; import static io.trino.plugin.kafka.util.TestUtils.createEmptyTopicDescription; import static java.util.UUID.randomUUID; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertTrue; -@Test(singleThreaded = true) +@Execution(SAME_THREAD) public class TestMinimalFunctionality extends AbstractTestQueryFramework { diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestCustomJsonDateTimeFormatter.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestCustomJsonDateTimeFormatter.java index aa3371e40a63..66abeb2f2088 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestCustomJsonDateTimeFormatter.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestCustomJsonDateTimeFormatter.java @@ -20,7 +20,7 @@ import io.trino.spi.type.SqlTimestamp; import io.trino.spi.type.SqlTimestampWithTimeZone; import io.trino.spi.type.TimeZoneKey; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Optional; diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestISO8601JsonDateTimeFormatter.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestISO8601JsonDateTimeFormatter.java index 6b2efe1e2eea..5718547fa4b4 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestISO8601JsonDateTimeFormatter.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestISO8601JsonDateTimeFormatter.java @@ -20,7 +20,7 @@ import io.trino.spi.type.SqlTimestamp; import io.trino.spi.type.SqlTimestampWithTimeZone; import io.trino.spi.type.TimeZoneKey; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Optional; diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestJsonEncoder.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestJsonEncoder.java index 21f6d790ff90..8137b946bff8 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestJsonEncoder.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestJsonEncoder.java @@ -22,7 +22,7 @@ import io.trino.spi.type.Type; import io.trino.testing.TestingConnectorSession; import org.assertj.core.api.ThrowableAssert; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Optional; diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestRFC2822JsonDateTimeFormatter.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestRFC2822JsonDateTimeFormatter.java index 0c2a17ba3b40..a185cd838ea0 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestRFC2822JsonDateTimeFormatter.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestRFC2822JsonDateTimeFormatter.java @@ -17,7 +17,7 @@ import io.trino.spi.type.SqlTimestamp; import io.trino.spi.type.SqlTimestampWithTimeZone; import io.trino.spi.type.TimeZoneKey; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Optional; diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/raw/TestRawEncoderMapping.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/raw/TestRawEncoderMapping.java index c5f5a532ce5d..2ad35a35dfe5 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/raw/TestRawEncoderMapping.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/raw/TestRawEncoderMapping.java @@ -23,7 +23,7 @@ import io.trino.spi.block.LongArrayBlockBuilder; import io.trino.spi.block.VariableWidthBlockBuilder; import io.trino.testing.TestingConnectorSession; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/protobuf/TestKafkaProtobufWithSchemaRegistryMinimalFunctionality.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/protobuf/TestKafkaProtobufWithSchemaRegistryMinimalFunctionality.java index 6b9f6d8d1024..5501062c9c23 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/protobuf/TestKafkaProtobufWithSchemaRegistryMinimalFunctionality.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/protobuf/TestKafkaProtobufWithSchemaRegistryMinimalFunctionality.java @@ -34,7 +34,8 @@ import io.trino.testing.QueryRunner; import io.trino.testing.kafka.TestingKafka; import org.apache.kafka.clients.producer.ProducerRecord; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.net.URI; @@ -64,9 +65,10 @@ import static org.apache.kafka.clients.producer.ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG; import static org.apache.kafka.clients.producer.ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertTrue; -@Test(singleThreaded = true) +@Execution(SAME_THREAD) public class TestKafkaProtobufWithSchemaRegistryMinimalFunctionality extends AbstractTestQueryFramework { diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestAvroConfluentContentSchemaProvider.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestAvroConfluentContentSchemaProvider.java index 086c8ef46dac..0888c7831d04 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestAvroConfluentContentSchemaProvider.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestAvroConfluentContentSchemaProvider.java @@ -25,7 +25,7 @@ import org.apache.avro.Schema; import org.apache.avro.Schema.Parser; import org.apache.avro.SchemaBuilder; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Optional; diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestAvroConfluentRowDecoder.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestAvroConfluentRowDecoder.java index 998569999cb3..87d69e9ffc3b 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestAvroConfluentRowDecoder.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestAvroConfluentRowDecoder.java @@ -32,7 +32,7 @@ import org.apache.avro.generic.GenericRecordBuilder; import org.apache.avro.io.BinaryEncoder; import org.apache.avro.io.EncoderFactory; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.ByteArrayOutputStream; import java.io.IOException; diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestAvroSchemaConverter.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestAvroSchemaConverter.java index 28373e3f2580..600eeb4ff5c3 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestAvroSchemaConverter.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestAvroSchemaConverter.java @@ -24,7 +24,7 @@ import org.apache.avro.Schema; import org.apache.avro.SchemaBuilder; import org.apache.avro.generic.GenericRecordBuilder; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Arrays; import java.util.List; diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestClassLoaderSafeSchemaRegistryClient.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestClassLoaderSafeSchemaRegistryClient.java index 888a197e93c0..f2872b1af53c 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestClassLoaderSafeSchemaRegistryClient.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestClassLoaderSafeSchemaRegistryClient.java @@ -14,7 +14,7 @@ package io.trino.plugin.kafka.schema.confluent; import io.confluent.kafka.schemaregistry.client.SchemaRegistryClient; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.spi.testing.InterfaceTestUtils.assertAllMethodsOverridden; diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestConfluentSchemaRegistryConfig.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestConfluentSchemaRegistryConfig.java index f29acf0dd0c8..d807877e915e 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestConfluentSchemaRegistryConfig.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestConfluentSchemaRegistryConfig.java @@ -15,7 +15,7 @@ import com.google.common.collect.ImmutableMap; import io.airlift.units.Duration; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Map; diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestConfluentSchemaRegistryTableDescriptionSupplier.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestConfluentSchemaRegistryTableDescriptionSupplier.java index 740b4d0418dc..ebb91aab8ff5 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestConfluentSchemaRegistryTableDescriptionSupplier.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestConfluentSchemaRegistryTableDescriptionSupplier.java @@ -29,7 +29,7 @@ import io.trino.testing.TestingConnectorSession; import org.apache.avro.Schema; import org.apache.avro.SchemaBuilder; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.Optional; diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestForwardingSchemaParser.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestForwardingSchemaParser.java index 806f81848726..af8c8b08775d 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestForwardingSchemaParser.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestForwardingSchemaParser.java @@ -13,7 +13,7 @@ */ package io.trino.plugin.kafka.schema.confluent; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.spi.testing.InterfaceTestUtils.assertAllMethodsOverridden; diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestKafkaWithConfluentSchemaRegistryMinimalFunctionality.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestKafkaWithConfluentSchemaRegistryMinimalFunctionality.java index a8df72849ec1..728814b0da97 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestKafkaWithConfluentSchemaRegistryMinimalFunctionality.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestKafkaWithConfluentSchemaRegistryMinimalFunctionality.java @@ -34,7 +34,8 @@ import org.apache.avro.generic.GenericRecordBuilder; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.common.serialization.LongSerializer; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; import java.time.Duration; import java.util.List; @@ -53,9 +54,10 @@ import static org.apache.kafka.clients.producer.ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertTrue; -@Test(singleThreaded = true) +@Execution(SAME_THREAD) public class TestKafkaWithConfluentSchemaRegistryMinimalFunctionality extends AbstractTestQueryFramework { diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/file/TestFileTableDescriptionSupplierConfig.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/file/TestFileTableDescriptionSupplierConfig.java index bb3ad0f30346..e22c137d2210 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/file/TestFileTableDescriptionSupplierConfig.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/file/TestFileTableDescriptionSupplierConfig.java @@ -14,7 +14,7 @@ package io.trino.plugin.kafka.schema.file; import com.google.common.collect.ImmutableMap; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.File; import java.util.Map; diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/utils/TestPropertiesUtils.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/utils/TestPropertiesUtils.java index ed25914f0736..e9328d52dc95 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/utils/TestPropertiesUtils.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/utils/TestPropertiesUtils.java @@ -14,7 +14,7 @@ package io.trino.plugin.kafka.utils; import com.google.common.collect.ImmutableMap; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.File; import java.io.FileOutputStream; diff --git a/plugin/trino-kinesis/pom.xml b/plugin/trino-kinesis/pom.xml index f1b0e6f5d248..1bc0445eaefd 100644 --- a/plugin/trino-kinesis/pom.xml +++ b/plugin/trino-kinesis/pom.xml @@ -154,6 +154,12 @@ provided + + io.airlift + junit-extensions + test + + io.trino trino-main @@ -179,6 +185,12 @@ test + + org.junit.jupiter + junit-jupiter-api + test + + org.junit.jupiter junit-jupiter-engine diff --git a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestKinesisConfig.java b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestKinesisConfig.java index a8a63bcb5fe3..87fbc88338c0 100644 --- a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestKinesisConfig.java +++ b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestKinesisConfig.java @@ -16,7 +16,7 @@ import com.google.common.collect.ImmutableMap; import io.airlift.configuration.testing.ConfigAssertions; import io.airlift.units.Duration; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Map; import java.util.concurrent.TimeUnit; diff --git a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestKinesisPlugin.java b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestKinesisPlugin.java index 9dd87f5792c6..bafe67ffa92a 100644 --- a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestKinesisPlugin.java +++ b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestKinesisPlugin.java @@ -20,7 +20,7 @@ import io.trino.spi.connector.ConnectorMetadata; import io.trino.spi.connector.ConnectorTransactionHandle; import io.trino.testing.TestingConnectorContext; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static com.google.common.collect.Iterables.getOnlyElement; import static io.trino.spi.transaction.IsolationLevel.READ_COMMITTED; diff --git a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestKinesisTableDescriptionSupplier.java b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestKinesisTableDescriptionSupplier.java index 170d1c5a7904..2edd09afc8ca 100644 --- a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestKinesisTableDescriptionSupplier.java +++ b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestKinesisTableDescriptionSupplier.java @@ -22,22 +22,28 @@ import io.trino.spi.connector.ConnectorTableMetadata; import io.trino.spi.connector.ConnectorTransactionHandle; import io.trino.spi.connector.SchemaTableName; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.Map; import static io.trino.testing.TestingConnectorSession.SESSION; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestKinesisTableDescriptionSupplier { private KinesisConnector connector; - @BeforeClass + @BeforeAll public void start() { // Create dependent objects, including the minimal config needed for this test From 23ef8e2b5fd516d7aa6e726fcce82b76f0a5346f Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Wed, 8 Nov 2023 07:50:45 -0800 Subject: [PATCH 123/587] Rename presto to trino in Elasticsearch system table --- .../trino/plugin/elasticsearch/NodesSystemTable.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/NodesSystemTable.java b/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/NodesSystemTable.java index b63ca765a553..84385dbedbf3 100644 --- a/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/NodesSystemTable.java +++ b/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/NodesSystemTable.java @@ -43,8 +43,8 @@ public class NodesSystemTable private static final ConnectorTableMetadata METADATA = new ConnectorTableMetadata( new SchemaTableName("system", "nodes"), ImmutableList.builder() - .add(new ColumnMetadata("presto_node_id", createUnboundedVarcharType())) - .add(new ColumnMetadata("presto_node_address", createUnboundedVarcharType())) + .add(new ColumnMetadata("trino_node_id", createUnboundedVarcharType())) + .add(new ColumnMetadata("trino_node_address", createUnboundedVarcharType())) .add(new ColumnMetadata("elasticsearch_node_id", createUnboundedVarcharType())) .add(new ColumnMetadata("elasticsearch_node_address", createUnboundedVarcharType())) .build()); @@ -79,13 +79,13 @@ public ConnectorPageSource pageSource(ConnectorTransactionHandle transaction, Co Set nodes = client.getNodes(); BlockBuilder nodeId = VARCHAR.createBlockBuilder(null, nodes.size()); - BlockBuilder prestoAddress = VARCHAR.createBlockBuilder(null, nodes.size()); + BlockBuilder trinoAddress = VARCHAR.createBlockBuilder(null, nodes.size()); BlockBuilder elasticsearchNodeId = VARCHAR.createBlockBuilder(null, nodes.size()); BlockBuilder elasticsearchAddress = VARCHAR.createBlockBuilder(null, nodes.size()); for (ElasticsearchNode node : nodes) { VARCHAR.writeString(nodeId, currentNode.getNodeIdentifier()); - VARCHAR.writeString(prestoAddress, currentNode.getHostAndPort().toString()); + VARCHAR.writeString(trinoAddress, currentNode.getHostAndPort().toString()); VARCHAR.writeString(elasticsearchNodeId, node.getId()); if (node.getAddress().isPresent()) { @@ -98,7 +98,7 @@ public ConnectorPageSource pageSource(ConnectorTransactionHandle transaction, Co return new FixedPageSource(ImmutableList.of(new Page( nodeId.build(), - prestoAddress.build(), + trinoAddress.build(), elasticsearchNodeId.build(), elasticsearchAddress.build()))); } From 36895bb93aa054536148cb47d8220c616399c2d0 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Wed, 8 Nov 2023 07:51:08 -0800 Subject: [PATCH 124/587] Rename leftover presto to trino --- .../resources/sql/{presto => trino}/session_set_cbo_flags.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q01.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q02.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q03.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q04.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q05.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q06.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q07.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q08.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q09.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q10.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q11.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q12.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q13.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q14.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q15.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q16.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q17.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q18.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q19.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q20.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q21.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q22.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q23.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q24.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q25.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q26.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q27.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q28.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q29.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q30.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q31.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q32.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q33.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q34.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q35.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q36.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q37.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q38.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q39.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q40.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q41.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q42.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q43.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q44.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q45.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q46.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q47.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q48.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q49.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q50.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q51.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q52.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q53.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q54.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q55.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q56.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q57.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q58.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q59.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q60.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q61.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q62.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q63.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q64.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q65.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q66.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q67.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q68.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q69.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q70.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q71.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q72.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q73.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q74.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q75.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q76.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q77.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q78.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q79.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q80.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q81.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q82.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q83.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q84.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q85.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q86.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q87.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q88.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q89.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q90.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q91.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q92.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q93.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q94.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q95.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q96.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q97.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q98.sql | 0 .../src/main/resources/sql/{presto => trino}/tpcds/q99.sql | 0 .../src/main/resources/sql/{presto => trino}/tpch/q01.sql | 0 .../src/main/resources/sql/{presto => trino}/tpch/q02.sql | 0 .../src/main/resources/sql/{presto => trino}/tpch/q03.sql | 0 .../src/main/resources/sql/{presto => trino}/tpch/q04.sql | 0 .../src/main/resources/sql/{presto => trino}/tpch/q05.sql | 0 .../src/main/resources/sql/{presto => trino}/tpch/q06.sql | 0 .../src/main/resources/sql/{presto => trino}/tpch/q07.sql | 0 .../src/main/resources/sql/{presto => trino}/tpch/q08.sql | 0 .../src/main/resources/sql/{presto => trino}/tpch/q09.sql | 0 .../src/main/resources/sql/{presto => trino}/tpch/q10.sql | 0 .../src/main/resources/sql/{presto => trino}/tpch/q11.sql | 0 .../src/main/resources/sql/{presto => trino}/tpch/q12.sql | 0 .../src/main/resources/sql/{presto => trino}/tpch/q13.sql | 0 .../src/main/resources/sql/{presto => trino}/tpch/q14.sql | 0 .../src/main/resources/sql/{presto => trino}/tpch/q15.sql | 0 .../src/main/resources/sql/{presto => trino}/tpch/q16.sql | 0 .../src/main/resources/sql/{presto => trino}/tpch/q17.sql | 0 .../src/main/resources/sql/{presto => trino}/tpch/q18.sql | 0 .../src/main/resources/sql/{presto => trino}/tpch/q19.sql | 0 .../src/main/resources/sql/{presto => trino}/tpch/q20.sql | 0 .../src/main/resources/sql/{presto => trino}/tpch/q21.sql | 0 .../src/main/resources/sql/{presto => trino}/tpch/q22.sql | 0 .../benchmarks/{presto => trino}/distributed_sort.yaml | 0 .../main/resources/benchmarks/{presto => trino}/kafka.yaml | 0 .../main/resources/benchmarks/{presto => trino}/tpcds.yaml | 0 .../src/main/resources/benchmarks/{presto => trino}/tpch.yaml | 0 .../distributed_sort/session_set_distributed_sort_flag.sql | 0 .../{presto => trino}/distributed_sort/sort_query_1_col.sql | 0 .../{presto => trino}/distributed_sort/sort_query_6_cols.sql | 0 .../src/main/resources/sql/{presto => trino}/kafka/count.sql | 0 .../resources/sql/{presto => trino}/kafka/count_column_1.sql | 0 .../resources/sql/{presto => trino}/kafka/count_column_10.sql | 0 .../sql/{presto => trino}/kafka/count_column_100.sql | 0 .../test/java/io/trino/sql/planner/BaseCostBasedPlanTest.java | 4 ++-- .../sql/{presto => trino}/tpcds/hive/partitioned/q01.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q02.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q03.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q04.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q05.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q06.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q07.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q08.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q09.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q10.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q11.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q12.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q13.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q14.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q15.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q16.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q17.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q18.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q19.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q20.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q21.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q22.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q23.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q24.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q25.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q26.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q27.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q28.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q29.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q30.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q31.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q32.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q33.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q34.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q35.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q36.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q37.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q38.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q39.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q40.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q41.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q42.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q43.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q44.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q45.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q46.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q47.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q48.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q49.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q50.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q51.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q52.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q53.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q54.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q55.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q56.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q57.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q58.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q59.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q60.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q61.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q62.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q63.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q64.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q65.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q66.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q67.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q68.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q69.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q70.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q71.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q72.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q73.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q74.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q75.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q76.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q77.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q78.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q79.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q80.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q81.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q82.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q83.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q84.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q85.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q86.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q87.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q88.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q89.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q90.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q91.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q92.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q93.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q94.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q95.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q96.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q97.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q98.plan.txt | 0 .../sql/{presto => trino}/tpcds/hive/partitioned/q99.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q01.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q02.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q03.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q04.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q05.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q06.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q07.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q08.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q09.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q10.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q11.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q12.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q13.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q14.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q15.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q16.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q17.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q18.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q19.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q20.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q21.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q22.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q23.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q24.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q25.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q26.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q27.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q28.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q29.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q30.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q31.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q32.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q33.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q34.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q35.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q36.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q37.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q38.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q39.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q40.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q41.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q42.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q43.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q44.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q45.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q46.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q47.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q48.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q49.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q50.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q51.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q52.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q53.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q54.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q55.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q56.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q57.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q58.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q59.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q60.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q61.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q62.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q63.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q64.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q65.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q66.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q67.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q68.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q69.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q70.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q71.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q72.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q73.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q74.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q75.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q76.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q77.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q78.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q79.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q80.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q81.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q82.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q83.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q84.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q85.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q86.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q87.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q88.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q89.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q90.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q91.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q92.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q93.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q94.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q95.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q96.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q97.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q98.plan.txt | 0 .../{presto => trino}/tpcds/hive/unpartitioned/q99.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q01.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q02.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q03.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q04.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q05.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q06.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q07.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q08.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q09.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q10.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q11.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q12.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q13.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q14.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q15.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q16.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q17.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q18.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q19.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q20.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q21.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q22.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q23.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q24.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q25.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q26.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q27.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q28.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q29.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q30.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q31.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q32.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q33.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q34.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q35.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q36.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q37.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q38.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q39.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q40.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q41.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q42.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q43.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q44.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q45.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q46.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q47.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q48.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q49.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q50.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q51.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q52.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q53.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q54.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q55.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q56.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q57.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q58.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q59.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q60.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q61.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q62.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q63.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q64.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q65.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q66.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q67.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q68.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q69.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q70.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q71.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q72.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q73.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q74.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q75.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q76.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q77.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q78.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q79.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q80.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q81.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q82.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q83.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q84.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q85.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q86.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q87.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q88.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q89.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q90.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q91.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q92.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q93.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q94.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q95.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q96.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q97.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q98.plan.txt | 0 .../tpcds/iceberg/orc/partitioned/q99.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q01.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q02.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q03.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q04.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q05.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q06.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q07.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q08.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q09.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q10.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q11.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q12.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q13.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q14.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q15.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q16.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q17.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q18.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q19.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q20.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q21.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q22.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q23.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q24.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q25.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q26.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q27.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q28.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q29.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q30.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q31.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q32.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q33.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q34.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q35.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q36.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q37.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q38.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q39.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q40.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q41.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q42.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q43.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q44.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q45.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q46.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q47.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q48.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q49.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q50.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q51.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q52.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q53.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q54.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q55.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q56.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q57.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q58.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q59.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q60.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q61.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q62.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q63.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q64.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q65.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q66.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q67.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q68.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q69.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q70.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q71.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q72.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q73.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q74.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q75.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q76.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q77.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q78.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q79.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q80.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q81.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q82.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q83.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q84.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q85.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q86.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q87.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q88.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q89.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q90.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q91.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q92.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q93.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q94.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q95.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q96.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q97.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q98.plan.txt | 0 .../tpcds/iceberg/orc/unpartitioned/q99.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q01.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q02.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q03.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q04.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q05.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q06.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q07.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q08.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q09.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q10.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q11.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q12.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q13.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q14.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q15.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q16.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q17.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q18.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q19.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q20.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q21.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q22.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q23.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q24.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q25.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q26.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q27.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q28.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q29.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q30.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q31.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q32.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q33.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q34.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q35.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q36.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q37.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q38.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q39.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q40.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q41.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q42.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q43.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q44.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q45.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q46.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q47.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q48.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q49.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q50.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q51.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q52.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q53.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q54.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q55.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q56.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q57.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q58.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q59.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q60.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q61.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q62.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q63.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q64.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q65.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q66.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q67.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q68.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q69.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q70.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q71.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q72.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q73.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q74.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q75.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q76.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q77.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q78.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q79.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q80.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q81.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q82.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q83.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q84.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q85.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q86.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q87.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q88.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q89.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q90.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q91.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q92.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q93.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q94.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q95.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q96.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q97.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q98.plan.txt | 0 .../tpcds/iceberg/parquet/partitioned/q99.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q01.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q02.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q03.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q04.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q05.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q06.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q07.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q08.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q09.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q10.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q11.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q12.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q13.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q14.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q15.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q16.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q17.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q18.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q19.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q20.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q21.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q22.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q23.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q24.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q25.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q26.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q27.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q28.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q29.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q30.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q31.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q32.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q33.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q34.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q35.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q36.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q37.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q38.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q39.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q40.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q41.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q42.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q43.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q44.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q45.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q46.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q47.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q48.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q49.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q50.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q51.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q52.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q53.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q54.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q55.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q56.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q57.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q58.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q59.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q60.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q61.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q62.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q63.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q64.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q65.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q66.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q67.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q68.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q69.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q70.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q71.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q72.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q73.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q74.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q75.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q76.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q77.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q78.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q79.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q80.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q81.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q82.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q83.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q84.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q85.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q86.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q87.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q88.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q89.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q90.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q91.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q92.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q93.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q94.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q95.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q96.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q97.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q98.plan.txt | 0 .../tpcds/iceberg/parquet/unpartitioned/q99.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q01.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q02.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q03.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q04.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q05.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q06.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q07.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q08.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q09.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q10.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q11.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q12.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q13.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q14.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q15.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q16.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q17.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q18.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q19.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q20.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q21.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q22.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q23.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q24.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q25.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q26.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q27.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q28.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q29.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q30.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q31.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q32.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q33.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q34.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q35.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q36.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q37.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q38.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q39.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q40.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q41.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q42.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q43.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q44.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q45.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q46.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q47.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q48.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q49.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q50.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q51.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q52.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q53.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q54.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q55.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q56.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q57.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q58.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q59.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q60.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q61.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q62.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q63.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q64.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q65.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q66.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q67.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q68.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q69.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q70.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q71.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q72.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q73.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q74.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q75.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q76.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q77.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q78.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q79.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q80.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q81.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q82.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q83.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q84.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q85.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q86.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q87.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q88.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q89.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q90.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q91.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q92.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q93.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q94.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q95.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q96.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q97.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q98.plan.txt | 0 .../iceberg_small_files/parquet/unpartitioned/q99.plan.txt | 0 .../sql/{presto => trino}/tpch/hive/partitioned/q01.plan.txt | 0 .../sql/{presto => trino}/tpch/hive/partitioned/q02.plan.txt | 0 .../sql/{presto => trino}/tpch/hive/partitioned/q03.plan.txt | 0 .../sql/{presto => trino}/tpch/hive/partitioned/q04.plan.txt | 0 .../sql/{presto => trino}/tpch/hive/partitioned/q05.plan.txt | 0 .../sql/{presto => trino}/tpch/hive/partitioned/q06.plan.txt | 0 .../sql/{presto => trino}/tpch/hive/partitioned/q07.plan.txt | 0 .../sql/{presto => trino}/tpch/hive/partitioned/q08.plan.txt | 0 .../sql/{presto => trino}/tpch/hive/partitioned/q09.plan.txt | 0 .../sql/{presto => trino}/tpch/hive/partitioned/q10.plan.txt | 0 .../sql/{presto => trino}/tpch/hive/partitioned/q11.plan.txt | 0 .../sql/{presto => trino}/tpch/hive/partitioned/q12.plan.txt | 0 .../sql/{presto => trino}/tpch/hive/partitioned/q13.plan.txt | 0 .../sql/{presto => trino}/tpch/hive/partitioned/q14.plan.txt | 0 .../sql/{presto => trino}/tpch/hive/partitioned/q15.plan.txt | 0 .../sql/{presto => trino}/tpch/hive/partitioned/q16.plan.txt | 0 .../sql/{presto => trino}/tpch/hive/partitioned/q17.plan.txt | 0 .../sql/{presto => trino}/tpch/hive/partitioned/q18.plan.txt | 0 .../sql/{presto => trino}/tpch/hive/partitioned/q19.plan.txt | 0 .../sql/{presto => trino}/tpch/hive/partitioned/q20.plan.txt | 0 .../sql/{presto => trino}/tpch/hive/partitioned/q21.plan.txt | 0 .../sql/{presto => trino}/tpch/hive/partitioned/q22.plan.txt | 0 .../{presto => trino}/tpch/hive/unpartitioned/q01.plan.txt | 0 .../{presto => trino}/tpch/hive/unpartitioned/q02.plan.txt | 0 .../{presto => trino}/tpch/hive/unpartitioned/q03.plan.txt | 0 .../{presto => trino}/tpch/hive/unpartitioned/q04.plan.txt | 0 .../{presto => trino}/tpch/hive/unpartitioned/q05.plan.txt | 0 .../{presto => trino}/tpch/hive/unpartitioned/q06.plan.txt | 0 .../{presto => trino}/tpch/hive/unpartitioned/q07.plan.txt | 0 .../{presto => trino}/tpch/hive/unpartitioned/q08.plan.txt | 0 .../{presto => trino}/tpch/hive/unpartitioned/q09.plan.txt | 0 .../{presto => trino}/tpch/hive/unpartitioned/q10.plan.txt | 0 .../{presto => trino}/tpch/hive/unpartitioned/q11.plan.txt | 0 .../{presto => trino}/tpch/hive/unpartitioned/q12.plan.txt | 0 .../{presto => trino}/tpch/hive/unpartitioned/q13.plan.txt | 0 .../{presto => trino}/tpch/hive/unpartitioned/q14.plan.txt | 0 .../{presto => trino}/tpch/hive/unpartitioned/q15.plan.txt | 0 .../{presto => trino}/tpch/hive/unpartitioned/q16.plan.txt | 0 .../{presto => trino}/tpch/hive/unpartitioned/q17.plan.txt | 0 .../{presto => trino}/tpch/hive/unpartitioned/q18.plan.txt | 0 .../{presto => trino}/tpch/hive/unpartitioned/q19.plan.txt | 0 .../{presto => trino}/tpch/hive/unpartitioned/q20.plan.txt | 0 .../{presto => trino}/tpch/hive/unpartitioned/q21.plan.txt | 0 .../{presto => trino}/tpch/hive/unpartitioned/q22.plan.txt | 0 .../tpch/iceberg/orc/partitioned/q01.plan.txt | 0 .../tpch/iceberg/orc/partitioned/q02.plan.txt | 0 .../tpch/iceberg/orc/partitioned/q03.plan.txt | 0 .../tpch/iceberg/orc/partitioned/q04.plan.txt | 0 .../tpch/iceberg/orc/partitioned/q05.plan.txt | 0 .../tpch/iceberg/orc/partitioned/q06.plan.txt | 0 .../tpch/iceberg/orc/partitioned/q07.plan.txt | 0 .../tpch/iceberg/orc/partitioned/q08.plan.txt | 0 .../tpch/iceberg/orc/partitioned/q09.plan.txt | 0 .../tpch/iceberg/orc/partitioned/q10.plan.txt | 0 .../tpch/iceberg/orc/partitioned/q11.plan.txt | 0 .../tpch/iceberg/orc/partitioned/q12.plan.txt | 0 .../tpch/iceberg/orc/partitioned/q13.plan.txt | 0 .../tpch/iceberg/orc/partitioned/q14.plan.txt | 0 .../tpch/iceberg/orc/partitioned/q15.plan.txt | 0 .../tpch/iceberg/orc/partitioned/q16.plan.txt | 0 .../tpch/iceberg/orc/partitioned/q17.plan.txt | 0 .../tpch/iceberg/orc/partitioned/q18.plan.txt | 0 .../tpch/iceberg/orc/partitioned/q19.plan.txt | 0 .../tpch/iceberg/orc/partitioned/q20.plan.txt | 0 .../tpch/iceberg/orc/partitioned/q21.plan.txt | 0 .../tpch/iceberg/orc/partitioned/q22.plan.txt | 0 .../tpch/iceberg/orc/unpartitioned/q01.plan.txt | 0 .../tpch/iceberg/orc/unpartitioned/q02.plan.txt | 0 .../tpch/iceberg/orc/unpartitioned/q03.plan.txt | 0 .../tpch/iceberg/orc/unpartitioned/q04.plan.txt | 0 .../tpch/iceberg/orc/unpartitioned/q05.plan.txt | 0 .../tpch/iceberg/orc/unpartitioned/q06.plan.txt | 0 .../tpch/iceberg/orc/unpartitioned/q07.plan.txt | 0 .../tpch/iceberg/orc/unpartitioned/q08.plan.txt | 0 .../tpch/iceberg/orc/unpartitioned/q09.plan.txt | 0 .../tpch/iceberg/orc/unpartitioned/q10.plan.txt | 0 .../tpch/iceberg/orc/unpartitioned/q11.plan.txt | 0 .../tpch/iceberg/orc/unpartitioned/q12.plan.txt | 0 .../tpch/iceberg/orc/unpartitioned/q13.plan.txt | 0 .../tpch/iceberg/orc/unpartitioned/q14.plan.txt | 0 .../tpch/iceberg/orc/unpartitioned/q15.plan.txt | 0 .../tpch/iceberg/orc/unpartitioned/q16.plan.txt | 0 .../tpch/iceberg/orc/unpartitioned/q17.plan.txt | 0 .../tpch/iceberg/orc/unpartitioned/q18.plan.txt | 0 .../tpch/iceberg/orc/unpartitioned/q19.plan.txt | 0 .../tpch/iceberg/orc/unpartitioned/q20.plan.txt | 0 .../tpch/iceberg/orc/unpartitioned/q21.plan.txt | 0 .../tpch/iceberg/orc/unpartitioned/q22.plan.txt | 0 .../tpch/iceberg/parquet/partitioned/q01.plan.txt | 0 .../tpch/iceberg/parquet/partitioned/q02.plan.txt | 0 .../tpch/iceberg/parquet/partitioned/q03.plan.txt | 0 .../tpch/iceberg/parquet/partitioned/q04.plan.txt | 0 .../tpch/iceberg/parquet/partitioned/q05.plan.txt | 0 .../tpch/iceberg/parquet/partitioned/q06.plan.txt | 0 .../tpch/iceberg/parquet/partitioned/q07.plan.txt | 0 .../tpch/iceberg/parquet/partitioned/q08.plan.txt | 0 .../tpch/iceberg/parquet/partitioned/q09.plan.txt | 0 .../tpch/iceberg/parquet/partitioned/q10.plan.txt | 0 .../tpch/iceberg/parquet/partitioned/q11.plan.txt | 0 .../tpch/iceberg/parquet/partitioned/q12.plan.txt | 0 .../tpch/iceberg/parquet/partitioned/q13.plan.txt | 0 .../tpch/iceberg/parquet/partitioned/q14.plan.txt | 0 .../tpch/iceberg/parquet/partitioned/q15.plan.txt | 0 .../tpch/iceberg/parquet/partitioned/q16.plan.txt | 0 .../tpch/iceberg/parquet/partitioned/q17.plan.txt | 0 .../tpch/iceberg/parquet/partitioned/q18.plan.txt | 0 .../tpch/iceberg/parquet/partitioned/q19.plan.txt | 0 .../tpch/iceberg/parquet/partitioned/q20.plan.txt | 0 .../tpch/iceberg/parquet/partitioned/q21.plan.txt | 0 .../tpch/iceberg/parquet/partitioned/q22.plan.txt | 0 .../tpch/iceberg/parquet/unpartitioned/q01.plan.txt | 0 .../tpch/iceberg/parquet/unpartitioned/q02.plan.txt | 0 .../tpch/iceberg/parquet/unpartitioned/q03.plan.txt | 0 .../tpch/iceberg/parquet/unpartitioned/q04.plan.txt | 0 .../tpch/iceberg/parquet/unpartitioned/q05.plan.txt | 0 .../tpch/iceberg/parquet/unpartitioned/q06.plan.txt | 0 .../tpch/iceberg/parquet/unpartitioned/q07.plan.txt | 0 .../tpch/iceberg/parquet/unpartitioned/q08.plan.txt | 0 .../tpch/iceberg/parquet/unpartitioned/q09.plan.txt | 0 .../tpch/iceberg/parquet/unpartitioned/q10.plan.txt | 0 .../tpch/iceberg/parquet/unpartitioned/q11.plan.txt | 0 .../tpch/iceberg/parquet/unpartitioned/q12.plan.txt | 0 .../tpch/iceberg/parquet/unpartitioned/q13.plan.txt | 0 .../tpch/iceberg/parquet/unpartitioned/q14.plan.txt | 0 .../tpch/iceberg/parquet/unpartitioned/q15.plan.txt | 0 .../tpch/iceberg/parquet/unpartitioned/q16.plan.txt | 0 .../tpch/iceberg/parquet/unpartitioned/q17.plan.txt | 0 .../tpch/iceberg/parquet/unpartitioned/q18.plan.txt | 0 .../tpch/iceberg/parquet/unpartitioned/q19.plan.txt | 0 .../tpch/iceberg/parquet/unpartitioned/q20.plan.txt | 0 .../tpch/iceberg/parquet/unpartitioned/q21.plan.txt | 0 .../tpch/iceberg/parquet/unpartitioned/q22.plan.txt | 0 959 files changed, 2 insertions(+), 2 deletions(-) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/session_set_cbo_flags.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q01.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q02.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q03.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q04.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q05.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q06.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q07.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q08.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q09.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q10.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q11.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q12.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q13.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q14.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q15.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q16.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q17.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q18.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q19.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q20.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q21.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q22.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q23.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q24.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q25.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q26.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q27.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q28.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q29.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q30.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q31.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q32.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q33.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q34.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q35.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q36.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q37.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q38.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q39.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q40.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q41.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q42.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q43.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q44.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q45.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q46.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q47.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q48.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q49.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q50.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q51.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q52.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q53.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q54.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q55.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q56.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q57.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q58.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q59.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q60.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q61.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q62.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q63.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q64.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q65.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q66.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q67.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q68.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q69.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q70.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q71.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q72.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q73.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q74.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q75.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q76.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q77.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q78.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q79.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q80.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q81.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q82.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q83.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q84.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q85.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q86.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q87.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q88.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q89.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q90.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q91.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q92.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q93.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q94.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q95.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q96.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q97.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q98.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpcds/q99.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpch/q01.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpch/q02.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpch/q03.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpch/q04.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpch/q05.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpch/q06.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpch/q07.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpch/q08.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpch/q09.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpch/q10.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpch/q11.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpch/q12.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpch/q13.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpch/q14.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpch/q15.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpch/q16.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpch/q17.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpch/q18.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpch/q19.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpch/q20.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpch/q21.sql (100%) rename testing/trino-benchmark-queries/src/main/resources/sql/{presto => trino}/tpch/q22.sql (100%) rename testing/trino-benchto-benchmarks/src/main/resources/benchmarks/{presto => trino}/distributed_sort.yaml (100%) rename testing/trino-benchto-benchmarks/src/main/resources/benchmarks/{presto => trino}/kafka.yaml (100%) rename testing/trino-benchto-benchmarks/src/main/resources/benchmarks/{presto => trino}/tpcds.yaml (100%) rename testing/trino-benchto-benchmarks/src/main/resources/benchmarks/{presto => trino}/tpch.yaml (100%) rename testing/trino-benchto-benchmarks/src/main/resources/sql/{presto => trino}/distributed_sort/session_set_distributed_sort_flag.sql (100%) rename testing/trino-benchto-benchmarks/src/main/resources/sql/{presto => trino}/distributed_sort/sort_query_1_col.sql (100%) rename testing/trino-benchto-benchmarks/src/main/resources/sql/{presto => trino}/distributed_sort/sort_query_6_cols.sql (100%) rename testing/trino-benchto-benchmarks/src/main/resources/sql/{presto => trino}/kafka/count.sql (100%) rename testing/trino-benchto-benchmarks/src/main/resources/sql/{presto => trino}/kafka/count_column_1.sql (100%) rename testing/trino-benchto-benchmarks/src/main/resources/sql/{presto => trino}/kafka/count_column_10.sql (100%) rename testing/trino-benchto-benchmarks/src/main/resources/sql/{presto => trino}/kafka/count_column_100.sql (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q01.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q02.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q03.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q04.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q05.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q06.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q07.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q08.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q09.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q10.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q11.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q12.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q13.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q14.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q15.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q16.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q17.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q18.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q19.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q20.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q21.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q22.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q23.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q24.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q25.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q26.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q27.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q28.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q29.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q30.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q31.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q32.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q33.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q34.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q35.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q36.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q37.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q38.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q39.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q40.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q41.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q42.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q43.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q44.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q45.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q46.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q47.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q48.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q49.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q50.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q51.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q52.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q53.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q54.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q55.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q56.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q57.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q58.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q59.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q60.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q61.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q62.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q63.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q64.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q65.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q66.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q67.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q68.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q69.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q70.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q71.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q72.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q73.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q74.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q75.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q76.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q77.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q78.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q79.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q80.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q81.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q82.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q83.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q84.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q85.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q86.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q87.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q88.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q89.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q90.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q91.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q92.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q93.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q94.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q95.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q96.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q97.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q98.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/partitioned/q99.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q01.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q02.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q03.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q04.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q05.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q06.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q07.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q08.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q09.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q10.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q11.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q12.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q13.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q14.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q15.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q16.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q17.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q18.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q19.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q20.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q21.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q22.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q23.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q24.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q25.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q26.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q27.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q28.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q29.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q30.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q31.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q32.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q33.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q34.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q35.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q36.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q37.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q38.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q39.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q40.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q41.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q42.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q43.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q44.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q45.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q46.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q47.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q48.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q49.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q50.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q51.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q52.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q53.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q54.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q55.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q56.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q57.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q58.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q59.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q60.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q61.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q62.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q63.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q64.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q65.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q66.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q67.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q68.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q69.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q70.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q71.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q72.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q73.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q74.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q75.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q76.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q77.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q78.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q79.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q80.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q81.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q82.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q83.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q84.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q85.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q86.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q87.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q88.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q89.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q90.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q91.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q92.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q93.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q94.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q95.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q96.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q97.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q98.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/hive/unpartitioned/q99.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q01.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q02.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q03.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q04.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q05.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q06.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q07.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q08.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q09.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q10.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q11.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q12.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q13.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q14.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q15.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q16.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q17.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q18.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q19.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q20.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q21.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q22.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q23.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q24.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q25.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q26.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q27.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q28.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q29.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q30.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q31.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q32.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q33.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q34.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q35.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q36.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q37.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q38.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q39.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q40.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q41.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q42.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q43.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q44.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q45.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q46.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q47.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q48.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q49.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q50.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q51.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q52.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q53.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q54.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q55.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q56.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q57.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q58.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q59.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q60.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q61.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q62.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q63.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q64.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q65.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q66.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q67.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q68.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q69.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q70.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q71.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q72.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q73.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q74.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q75.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q76.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q77.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q78.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q79.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q80.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q81.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q82.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q83.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q84.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q85.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q86.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q87.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q88.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q89.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q90.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q91.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q92.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q93.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q94.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q95.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q96.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q97.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q98.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/partitioned/q99.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q01.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q02.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q03.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q04.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q05.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q06.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q07.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q08.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q09.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q10.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q11.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q12.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q13.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q14.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q15.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q16.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q17.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q18.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q19.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q20.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q21.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q22.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q23.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q24.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q25.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q26.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q27.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q28.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q29.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q30.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q31.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q32.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q33.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q34.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q35.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q36.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q37.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q38.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q39.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q40.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q41.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q42.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q43.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q44.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q45.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q46.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q47.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q48.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q49.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q50.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q51.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q52.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q53.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q54.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q55.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q56.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q57.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q58.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q59.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q60.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q61.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q62.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q63.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q64.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q65.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q66.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q67.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q68.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q69.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q70.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q71.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q72.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q73.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q74.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q75.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q76.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q77.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q78.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q79.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q80.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q81.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q82.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q83.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q84.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q85.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q86.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q87.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q88.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q89.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q90.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q91.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q92.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q93.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q94.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q95.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q96.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q97.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q98.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/orc/unpartitioned/q99.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q01.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q02.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q03.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q04.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q05.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q06.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q07.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q08.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q09.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q10.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q11.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q12.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q13.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q14.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q15.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q16.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q17.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q18.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q19.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q20.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q21.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q22.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q23.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q24.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q25.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q26.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q27.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q28.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q29.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q30.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q31.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q32.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q33.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q34.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q35.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q36.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q37.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q38.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q39.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q40.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q41.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q42.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q43.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q44.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q45.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q46.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q47.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q48.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q49.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q50.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q51.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q52.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q53.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q54.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q55.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q56.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q57.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q58.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q59.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q60.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q61.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q62.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q63.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q64.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q65.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q66.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q67.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q68.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q69.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q70.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q71.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q72.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q73.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q74.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q75.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q76.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q77.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q78.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q79.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q80.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q81.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q82.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q83.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q84.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q85.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q86.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q87.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q88.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q89.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q90.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q91.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q92.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q93.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q94.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q95.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q96.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q97.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q98.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/partitioned/q99.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q01.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q02.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q03.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q04.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q05.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q06.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q07.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q08.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q09.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q10.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q11.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q12.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q13.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q14.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q15.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q16.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q17.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q18.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q19.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q20.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q21.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q22.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q23.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q24.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q25.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q26.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q27.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q28.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q29.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q30.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q31.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q32.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q33.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q34.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q35.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q36.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q37.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q38.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q39.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q40.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q41.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q42.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q43.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q44.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q45.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q46.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q47.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q48.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q49.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q50.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q51.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q52.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q53.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q54.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q55.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q56.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q57.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q58.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q59.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q60.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q61.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q62.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q63.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q64.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q65.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q66.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q67.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q68.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q69.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q70.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q71.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q72.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q73.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q74.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q75.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q76.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q77.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q78.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q79.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q80.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q81.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q82.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q83.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q84.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q85.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q86.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q87.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q88.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q89.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q90.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q91.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q92.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q93.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q94.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q95.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q96.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q97.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q98.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg/parquet/unpartitioned/q99.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q01.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q02.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q03.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q04.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q05.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q06.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q07.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q08.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q09.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q10.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q11.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q12.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q13.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q14.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q15.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q16.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q17.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q18.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q19.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q20.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q21.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q22.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q23.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q24.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q25.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q26.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q27.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q28.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q29.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q30.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q31.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q32.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q33.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q34.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q35.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q36.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q37.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q38.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q39.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q40.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q41.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q42.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q43.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q44.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q45.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q46.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q47.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q48.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q49.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q50.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q51.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q52.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q53.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q54.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q55.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q56.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q57.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q58.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q59.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q60.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q61.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q62.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q63.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q64.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q65.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q66.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q67.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q68.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q69.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q70.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q71.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q72.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q73.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q74.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q75.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q76.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q77.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q78.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q79.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q80.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q81.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q82.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q83.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q84.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q85.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q86.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q87.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q88.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q89.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q90.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q91.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q92.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q93.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q94.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q95.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q96.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q97.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q98.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpcds/iceberg_small_files/parquet/unpartitioned/q99.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/partitioned/q01.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/partitioned/q02.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/partitioned/q03.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/partitioned/q04.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/partitioned/q05.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/partitioned/q06.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/partitioned/q07.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/partitioned/q08.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/partitioned/q09.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/partitioned/q10.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/partitioned/q11.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/partitioned/q12.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/partitioned/q13.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/partitioned/q14.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/partitioned/q15.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/partitioned/q16.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/partitioned/q17.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/partitioned/q18.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/partitioned/q19.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/partitioned/q20.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/partitioned/q21.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/partitioned/q22.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/unpartitioned/q01.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/unpartitioned/q02.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/unpartitioned/q03.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/unpartitioned/q04.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/unpartitioned/q05.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/unpartitioned/q06.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/unpartitioned/q07.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/unpartitioned/q08.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/unpartitioned/q09.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/unpartitioned/q10.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/unpartitioned/q11.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/unpartitioned/q12.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/unpartitioned/q13.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/unpartitioned/q14.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/unpartitioned/q15.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/unpartitioned/q16.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/unpartitioned/q17.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/unpartitioned/q18.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/unpartitioned/q19.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/unpartitioned/q20.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/unpartitioned/q21.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/hive/unpartitioned/q22.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/partitioned/q01.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/partitioned/q02.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/partitioned/q03.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/partitioned/q04.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/partitioned/q05.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/partitioned/q06.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/partitioned/q07.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/partitioned/q08.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/partitioned/q09.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/partitioned/q10.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/partitioned/q11.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/partitioned/q12.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/partitioned/q13.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/partitioned/q14.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/partitioned/q15.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/partitioned/q16.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/partitioned/q17.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/partitioned/q18.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/partitioned/q19.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/partitioned/q20.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/partitioned/q21.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/partitioned/q22.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/unpartitioned/q01.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/unpartitioned/q02.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/unpartitioned/q03.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/unpartitioned/q04.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/unpartitioned/q05.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/unpartitioned/q06.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/unpartitioned/q07.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/unpartitioned/q08.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/unpartitioned/q09.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/unpartitioned/q10.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/unpartitioned/q11.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/unpartitioned/q12.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/unpartitioned/q13.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/unpartitioned/q14.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/unpartitioned/q15.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/unpartitioned/q16.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/unpartitioned/q17.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/unpartitioned/q18.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/unpartitioned/q19.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/unpartitioned/q20.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/unpartitioned/q21.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/orc/unpartitioned/q22.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/partitioned/q01.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/partitioned/q02.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/partitioned/q03.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/partitioned/q04.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/partitioned/q05.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/partitioned/q06.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/partitioned/q07.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/partitioned/q08.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/partitioned/q09.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/partitioned/q10.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/partitioned/q11.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/partitioned/q12.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/partitioned/q13.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/partitioned/q14.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/partitioned/q15.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/partitioned/q16.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/partitioned/q17.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/partitioned/q18.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/partitioned/q19.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/partitioned/q20.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/partitioned/q21.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/partitioned/q22.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/unpartitioned/q01.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/unpartitioned/q02.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/unpartitioned/q03.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/unpartitioned/q04.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/unpartitioned/q05.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/unpartitioned/q06.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/unpartitioned/q07.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/unpartitioned/q08.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/unpartitioned/q09.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/unpartitioned/q10.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/unpartitioned/q11.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/unpartitioned/q12.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/unpartitioned/q13.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/unpartitioned/q14.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/unpartitioned/q15.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/unpartitioned/q16.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/unpartitioned/q17.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/unpartitioned/q18.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/unpartitioned/q19.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/unpartitioned/q20.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/unpartitioned/q21.plan.txt (100%) rename testing/trino-tests/src/test/resources/sql/{presto => trino}/tpch/iceberg/parquet/unpartitioned/q22.plan.txt (100%) diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/session_set_cbo_flags.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/session_set_cbo_flags.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/session_set_cbo_flags.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/session_set_cbo_flags.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q01.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q01.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q01.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q01.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q02.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q02.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q02.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q02.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q03.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q03.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q03.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q03.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q04.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q04.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q04.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q04.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q05.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q05.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q05.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q05.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q06.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q06.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q06.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q06.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q07.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q07.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q07.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q07.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q08.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q08.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q08.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q08.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q09.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q09.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q09.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q09.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q10.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q10.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q10.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q10.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q11.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q11.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q11.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q11.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q12.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q12.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q12.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q12.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q13.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q13.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q13.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q13.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q14.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q14.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q14.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q14.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q15.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q15.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q15.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q15.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q16.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q16.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q16.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q16.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q17.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q17.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q17.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q17.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q18.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q18.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q18.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q18.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q19.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q19.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q19.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q19.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q20.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q20.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q20.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q20.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q21.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q21.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q21.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q21.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q22.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q22.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q22.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q22.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q23.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q23.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q23.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q23.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q24.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q24.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q24.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q24.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q25.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q25.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q25.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q25.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q26.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q26.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q26.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q26.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q27.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q27.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q27.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q27.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q28.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q28.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q28.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q28.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q29.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q29.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q29.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q29.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q30.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q30.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q30.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q30.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q31.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q31.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q31.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q31.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q32.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q32.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q32.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q32.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q33.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q33.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q33.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q33.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q34.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q34.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q34.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q34.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q35.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q35.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q35.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q35.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q36.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q36.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q36.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q36.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q37.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q37.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q37.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q37.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q38.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q38.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q38.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q38.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q39.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q39.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q39.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q39.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q40.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q40.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q40.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q40.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q41.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q41.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q41.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q41.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q42.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q42.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q42.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q42.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q43.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q43.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q43.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q43.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q44.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q44.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q44.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q44.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q45.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q45.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q45.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q45.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q46.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q46.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q46.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q46.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q47.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q47.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q47.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q47.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q48.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q48.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q48.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q48.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q49.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q49.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q49.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q49.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q50.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q50.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q50.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q50.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q51.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q51.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q51.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q51.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q52.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q52.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q52.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q52.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q53.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q53.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q53.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q53.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q54.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q54.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q54.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q54.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q55.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q55.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q55.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q55.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q56.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q56.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q56.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q56.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q57.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q57.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q57.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q57.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q58.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q58.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q58.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q58.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q59.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q59.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q59.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q59.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q60.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q60.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q60.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q60.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q61.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q61.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q61.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q61.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q62.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q62.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q62.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q62.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q63.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q63.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q63.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q63.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q64.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q64.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q64.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q64.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q65.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q65.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q65.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q65.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q66.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q66.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q66.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q66.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q67.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q67.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q67.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q67.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q68.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q68.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q68.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q68.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q69.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q69.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q69.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q69.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q70.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q70.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q70.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q70.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q71.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q71.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q71.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q71.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q72.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q72.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q72.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q72.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q73.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q73.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q73.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q73.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q74.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q74.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q74.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q74.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q75.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q75.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q75.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q75.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q76.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q76.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q76.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q76.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q77.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q77.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q77.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q77.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q78.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q78.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q78.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q78.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q79.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q79.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q79.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q79.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q80.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q80.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q80.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q80.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q81.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q81.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q81.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q81.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q82.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q82.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q82.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q82.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q83.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q83.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q83.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q83.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q84.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q84.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q84.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q84.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q85.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q85.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q85.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q85.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q86.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q86.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q86.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q86.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q87.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q87.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q87.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q87.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q88.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q88.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q88.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q88.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q89.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q89.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q89.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q89.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q90.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q90.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q90.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q90.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q91.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q91.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q91.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q91.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q92.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q92.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q92.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q92.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q93.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q93.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q93.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q93.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q94.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q94.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q94.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q94.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q95.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q95.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q95.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q95.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q96.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q96.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q96.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q96.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q97.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q97.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q97.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q97.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q98.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q98.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q98.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q98.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q99.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q99.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpcds/q99.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpcds/q99.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q01.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q01.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q01.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q01.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q02.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q02.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q02.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q02.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q03.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q03.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q03.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q03.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q04.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q04.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q04.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q04.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q05.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q05.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q05.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q05.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q06.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q06.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q06.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q06.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q07.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q07.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q07.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q07.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q08.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q08.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q08.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q08.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q09.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q09.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q09.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q09.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q10.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q10.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q10.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q10.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q11.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q11.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q11.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q11.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q12.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q12.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q12.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q12.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q13.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q13.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q13.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q13.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q14.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q14.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q14.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q14.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q15.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q15.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q15.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q15.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q16.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q16.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q16.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q16.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q17.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q17.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q17.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q17.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q18.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q18.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q18.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q18.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q19.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q19.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q19.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q19.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q20.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q20.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q20.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q20.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q21.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q21.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q21.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q21.sql diff --git a/testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q22.sql b/testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q22.sql similarity index 100% rename from testing/trino-benchmark-queries/src/main/resources/sql/presto/tpch/q22.sql rename to testing/trino-benchmark-queries/src/main/resources/sql/trino/tpch/q22.sql diff --git a/testing/trino-benchto-benchmarks/src/main/resources/benchmarks/presto/distributed_sort.yaml b/testing/trino-benchto-benchmarks/src/main/resources/benchmarks/trino/distributed_sort.yaml similarity index 100% rename from testing/trino-benchto-benchmarks/src/main/resources/benchmarks/presto/distributed_sort.yaml rename to testing/trino-benchto-benchmarks/src/main/resources/benchmarks/trino/distributed_sort.yaml diff --git a/testing/trino-benchto-benchmarks/src/main/resources/benchmarks/presto/kafka.yaml b/testing/trino-benchto-benchmarks/src/main/resources/benchmarks/trino/kafka.yaml similarity index 100% rename from testing/trino-benchto-benchmarks/src/main/resources/benchmarks/presto/kafka.yaml rename to testing/trino-benchto-benchmarks/src/main/resources/benchmarks/trino/kafka.yaml diff --git a/testing/trino-benchto-benchmarks/src/main/resources/benchmarks/presto/tpcds.yaml b/testing/trino-benchto-benchmarks/src/main/resources/benchmarks/trino/tpcds.yaml similarity index 100% rename from testing/trino-benchto-benchmarks/src/main/resources/benchmarks/presto/tpcds.yaml rename to testing/trino-benchto-benchmarks/src/main/resources/benchmarks/trino/tpcds.yaml diff --git a/testing/trino-benchto-benchmarks/src/main/resources/benchmarks/presto/tpch.yaml b/testing/trino-benchto-benchmarks/src/main/resources/benchmarks/trino/tpch.yaml similarity index 100% rename from testing/trino-benchto-benchmarks/src/main/resources/benchmarks/presto/tpch.yaml rename to testing/trino-benchto-benchmarks/src/main/resources/benchmarks/trino/tpch.yaml diff --git a/testing/trino-benchto-benchmarks/src/main/resources/sql/presto/distributed_sort/session_set_distributed_sort_flag.sql b/testing/trino-benchto-benchmarks/src/main/resources/sql/trino/distributed_sort/session_set_distributed_sort_flag.sql similarity index 100% rename from testing/trino-benchto-benchmarks/src/main/resources/sql/presto/distributed_sort/session_set_distributed_sort_flag.sql rename to testing/trino-benchto-benchmarks/src/main/resources/sql/trino/distributed_sort/session_set_distributed_sort_flag.sql diff --git a/testing/trino-benchto-benchmarks/src/main/resources/sql/presto/distributed_sort/sort_query_1_col.sql b/testing/trino-benchto-benchmarks/src/main/resources/sql/trino/distributed_sort/sort_query_1_col.sql similarity index 100% rename from testing/trino-benchto-benchmarks/src/main/resources/sql/presto/distributed_sort/sort_query_1_col.sql rename to testing/trino-benchto-benchmarks/src/main/resources/sql/trino/distributed_sort/sort_query_1_col.sql diff --git a/testing/trino-benchto-benchmarks/src/main/resources/sql/presto/distributed_sort/sort_query_6_cols.sql b/testing/trino-benchto-benchmarks/src/main/resources/sql/trino/distributed_sort/sort_query_6_cols.sql similarity index 100% rename from testing/trino-benchto-benchmarks/src/main/resources/sql/presto/distributed_sort/sort_query_6_cols.sql rename to testing/trino-benchto-benchmarks/src/main/resources/sql/trino/distributed_sort/sort_query_6_cols.sql diff --git a/testing/trino-benchto-benchmarks/src/main/resources/sql/presto/kafka/count.sql b/testing/trino-benchto-benchmarks/src/main/resources/sql/trino/kafka/count.sql similarity index 100% rename from testing/trino-benchto-benchmarks/src/main/resources/sql/presto/kafka/count.sql rename to testing/trino-benchto-benchmarks/src/main/resources/sql/trino/kafka/count.sql diff --git a/testing/trino-benchto-benchmarks/src/main/resources/sql/presto/kafka/count_column_1.sql b/testing/trino-benchto-benchmarks/src/main/resources/sql/trino/kafka/count_column_1.sql similarity index 100% rename from testing/trino-benchto-benchmarks/src/main/resources/sql/presto/kafka/count_column_1.sql rename to testing/trino-benchto-benchmarks/src/main/resources/sql/trino/kafka/count_column_1.sql diff --git a/testing/trino-benchto-benchmarks/src/main/resources/sql/presto/kafka/count_column_10.sql b/testing/trino-benchto-benchmarks/src/main/resources/sql/trino/kafka/count_column_10.sql similarity index 100% rename from testing/trino-benchto-benchmarks/src/main/resources/sql/presto/kafka/count_column_10.sql rename to testing/trino-benchto-benchmarks/src/main/resources/sql/trino/kafka/count_column_10.sql diff --git a/testing/trino-benchto-benchmarks/src/main/resources/sql/presto/kafka/count_column_100.sql b/testing/trino-benchto-benchmarks/src/main/resources/sql/trino/kafka/count_column_100.sql similarity index 100% rename from testing/trino-benchto-benchmarks/src/main/resources/sql/presto/kafka/count_column_100.sql rename to testing/trino-benchto-benchmarks/src/main/resources/sql/trino/kafka/count_column_100.sql diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/BaseCostBasedPlanTest.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/BaseCostBasedPlanTest.java index 0312d088fa57..4a85713efb7d 100644 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/BaseCostBasedPlanTest.java +++ b/testing/trino-tests/src/test/java/io/trino/sql/planner/BaseCostBasedPlanTest.java @@ -78,12 +78,12 @@ public abstract class BaseCostBasedPlanTest public static final List TPCH_SQL_FILES = IntStream.rangeClosed(1, 22) .mapToObj(i -> format("q%02d", i)) - .map(queryId -> format("/sql/presto/tpch/%s.sql", queryId)) + .map(queryId -> format("/sql/trino/tpch/%s.sql", queryId)) .collect(toImmutableList()); public static final List TPCDS_SQL_FILES = IntStream.range(1, 100) .mapToObj(i -> format("q%02d", i)) - .map(queryId -> format("/sql/presto/tpcds/%s.sql", queryId)) + .map(queryId -> format("/sql/trino/tpcds/%s.sql", queryId)) .collect(toImmutableList()); private static final String CATALOG_NAME = "local"; diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q01.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q01.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q01.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q01.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q02.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q02.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q02.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q02.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q03.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q03.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q03.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q03.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q04.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q04.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q04.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q04.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q05.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q05.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q05.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q05.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q06.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q06.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q06.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q06.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q07.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q07.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q07.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q07.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q08.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q08.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q08.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q08.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q09.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q09.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q09.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q09.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q10.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q10.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q10.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q10.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q11.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q11.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q11.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q11.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q12.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q12.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q12.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q12.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q13.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q13.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q13.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q13.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q14.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q14.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q14.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q14.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q15.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q15.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q15.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q15.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q16.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q16.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q16.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q16.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q17.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q17.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q17.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q17.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q18.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q18.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q18.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q18.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q19.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q19.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q19.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q19.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q20.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q20.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q20.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q20.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q21.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q21.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q21.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q21.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q22.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q22.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q22.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q22.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q23.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q23.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q23.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q23.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q24.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q24.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q24.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q24.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q25.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q25.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q25.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q25.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q26.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q26.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q26.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q26.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q27.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q27.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q27.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q27.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q28.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q28.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q28.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q28.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q29.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q29.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q29.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q29.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q30.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q30.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q30.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q30.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q31.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q31.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q31.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q31.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q32.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q32.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q32.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q32.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q33.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q33.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q33.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q33.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q34.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q34.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q34.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q34.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q35.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q35.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q35.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q35.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q36.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q36.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q36.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q36.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q37.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q37.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q37.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q37.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q38.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q38.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q38.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q38.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q39.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q39.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q39.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q39.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q40.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q40.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q40.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q40.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q41.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q41.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q41.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q41.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q42.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q42.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q42.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q42.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q43.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q43.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q43.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q43.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q44.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q44.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q44.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q44.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q45.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q45.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q45.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q45.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q46.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q46.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q46.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q46.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q47.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q47.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q47.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q47.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q48.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q48.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q48.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q48.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q49.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q49.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q49.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q49.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q50.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q50.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q50.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q50.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q51.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q51.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q51.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q51.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q52.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q52.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q52.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q52.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q53.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q53.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q53.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q53.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q54.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q54.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q54.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q54.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q55.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q55.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q55.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q55.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q56.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q56.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q56.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q56.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q57.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q57.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q57.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q57.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q58.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q58.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q58.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q58.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q59.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q59.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q59.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q59.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q60.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q60.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q60.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q60.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q61.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q61.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q61.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q61.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q62.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q62.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q62.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q62.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q63.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q63.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q63.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q63.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q64.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q64.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q64.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q64.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q65.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q65.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q65.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q65.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q66.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q66.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q66.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q66.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q67.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q67.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q67.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q67.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q68.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q68.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q68.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q68.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q69.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q69.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q69.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q69.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q70.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q70.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q70.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q70.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q71.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q71.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q71.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q71.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q72.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q72.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q72.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q72.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q73.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q73.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q73.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q73.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q74.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q74.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q74.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q74.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q75.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q75.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q75.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q75.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q76.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q76.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q76.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q76.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q77.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q77.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q77.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q77.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q78.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q78.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q78.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q78.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q79.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q79.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q79.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q79.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q80.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q80.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q80.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q80.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q81.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q81.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q81.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q81.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q82.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q82.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q82.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q82.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q83.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q83.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q83.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q83.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q84.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q84.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q84.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q84.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q85.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q85.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q85.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q85.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q86.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q86.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q86.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q86.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q87.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q87.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q87.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q87.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q88.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q88.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q88.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q88.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q89.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q89.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q89.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q89.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q90.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q90.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q90.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q90.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q91.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q91.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q91.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q91.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q92.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q92.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q92.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q92.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q93.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q93.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q93.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q93.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q94.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q94.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q94.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q94.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q95.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q95.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q95.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q95.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q96.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q96.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q96.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q96.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q97.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q97.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q97.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q97.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q98.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q98.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q98.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q98.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q99.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q99.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/partitioned/q99.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q99.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q01.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q01.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q01.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q01.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q02.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q02.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q02.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q02.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q03.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q03.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q03.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q03.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q04.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q04.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q04.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q04.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q05.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q05.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q05.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q05.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q06.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q06.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q06.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q06.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q07.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q07.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q07.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q07.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q08.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q08.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q08.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q08.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q09.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q09.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q09.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q09.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q10.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q10.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q10.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q10.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q11.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q11.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q11.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q11.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q12.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q12.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q12.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q12.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q13.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q13.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q13.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q13.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q14.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q14.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q14.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q14.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q15.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q15.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q15.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q15.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q16.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q16.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q16.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q16.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q17.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q17.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q17.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q17.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q18.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q18.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q18.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q18.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q19.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q19.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q19.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q19.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q20.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q20.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q20.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q20.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q21.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q21.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q21.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q21.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q22.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q22.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q22.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q22.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q23.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q23.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q23.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q23.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q24.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q24.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q24.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q24.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q25.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q25.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q25.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q25.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q26.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q26.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q26.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q26.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q27.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q27.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q27.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q27.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q28.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q28.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q28.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q28.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q29.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q29.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q29.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q29.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q30.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q30.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q30.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q30.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q31.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q31.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q31.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q31.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q32.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q32.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q32.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q32.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q33.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q33.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q33.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q33.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q34.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q34.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q34.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q34.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q35.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q35.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q35.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q35.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q36.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q36.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q36.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q36.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q37.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q37.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q37.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q37.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q38.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q38.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q38.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q38.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q39.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q39.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q39.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q39.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q40.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q40.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q40.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q40.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q41.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q41.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q41.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q41.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q42.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q42.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q42.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q42.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q43.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q43.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q43.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q43.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q44.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q44.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q44.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q44.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q45.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q45.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q45.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q45.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q46.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q46.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q46.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q46.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q47.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q47.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q47.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q47.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q48.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q48.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q48.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q48.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q49.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q49.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q49.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q49.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q50.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q50.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q50.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q50.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q51.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q51.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q51.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q51.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q52.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q52.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q52.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q52.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q53.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q53.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q53.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q53.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q54.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q54.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q54.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q54.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q55.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q55.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q55.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q55.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q56.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q56.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q56.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q56.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q57.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q57.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q57.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q57.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q58.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q58.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q58.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q58.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q59.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q59.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q59.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q59.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q60.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q60.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q60.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q60.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q61.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q61.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q61.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q61.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q62.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q62.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q62.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q62.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q63.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q63.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q63.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q63.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q64.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q64.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q64.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q64.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q65.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q65.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q65.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q65.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q66.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q66.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q66.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q66.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q67.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q67.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q67.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q67.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q68.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q68.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q68.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q68.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q69.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q69.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q69.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q69.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q70.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q70.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q70.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q70.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q71.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q71.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q71.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q71.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q72.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q72.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q72.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q72.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q73.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q73.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q73.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q73.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q74.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q74.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q74.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q74.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q75.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q75.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q75.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q75.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q76.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q76.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q76.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q76.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q77.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q77.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q77.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q77.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q78.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q78.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q78.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q78.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q79.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q79.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q79.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q79.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q80.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q80.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q80.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q80.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q81.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q81.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q81.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q81.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q82.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q82.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q82.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q82.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q83.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q83.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q83.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q83.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q84.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q84.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q84.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q84.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q85.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q85.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q85.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q85.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q86.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q86.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q86.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q86.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q87.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q87.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q87.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q87.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q88.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q88.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q88.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q88.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q89.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q89.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q89.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q89.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q90.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q90.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q90.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q90.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q91.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q91.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q91.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q91.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q92.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q92.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q92.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q92.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q93.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q93.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q93.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q93.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q94.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q94.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q94.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q94.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q95.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q95.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q95.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q95.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q96.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q96.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q96.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q96.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q97.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q97.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q97.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q97.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q98.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q98.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q98.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q98.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q99.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q99.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/hive/unpartitioned/q99.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q99.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q01.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q01.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q01.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q01.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q02.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q02.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q02.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q02.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q03.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q03.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q03.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q03.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q04.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q04.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q04.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q04.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q05.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q05.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q05.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q05.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q06.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q06.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q06.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q06.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q07.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q07.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q07.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q07.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q08.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q08.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q08.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q08.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q09.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q09.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q09.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q09.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q10.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q10.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q10.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q10.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q11.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q11.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q11.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q11.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q12.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q12.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q12.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q12.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q13.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q13.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q13.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q13.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q14.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q14.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q14.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q14.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q15.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q15.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q15.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q15.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q16.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q16.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q16.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q16.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q17.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q17.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q17.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q17.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q18.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q18.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q18.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q18.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q19.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q19.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q19.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q19.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q20.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q20.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q20.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q20.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q21.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q21.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q21.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q21.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q22.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q22.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q22.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q22.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q23.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q23.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q23.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q23.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q24.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q24.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q24.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q24.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q25.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q25.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q25.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q25.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q26.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q26.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q26.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q26.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q27.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q27.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q27.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q27.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q28.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q28.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q28.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q28.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q29.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q29.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q29.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q29.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q30.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q30.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q30.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q30.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q31.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q31.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q31.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q31.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q32.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q32.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q32.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q32.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q33.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q33.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q33.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q33.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q34.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q34.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q34.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q34.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q35.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q35.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q35.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q35.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q36.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q36.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q36.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q36.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q37.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q37.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q37.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q37.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q38.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q38.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q38.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q38.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q39.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q39.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q39.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q39.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q40.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q40.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q40.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q40.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q41.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q41.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q41.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q41.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q42.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q42.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q42.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q42.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q43.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q43.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q43.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q43.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q44.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q44.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q44.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q44.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q45.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q45.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q45.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q45.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q46.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q46.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q46.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q46.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q47.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q47.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q47.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q47.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q48.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q48.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q48.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q48.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q49.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q49.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q49.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q49.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q50.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q50.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q50.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q50.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q51.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q51.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q51.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q51.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q52.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q52.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q52.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q52.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q53.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q53.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q53.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q53.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q54.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q54.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q54.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q54.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q55.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q55.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q55.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q55.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q56.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q56.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q56.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q56.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q57.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q57.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q57.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q57.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q58.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q58.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q58.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q58.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q59.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q59.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q59.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q59.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q60.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q60.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q60.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q60.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q61.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q61.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q61.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q61.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q62.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q62.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q62.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q62.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q63.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q63.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q63.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q63.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q64.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q64.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q64.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q64.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q65.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q65.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q65.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q65.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q66.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q66.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q66.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q66.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q67.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q67.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q67.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q67.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q68.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q68.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q68.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q68.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q69.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q69.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q69.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q69.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q70.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q70.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q70.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q70.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q71.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q71.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q71.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q71.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q72.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q72.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q72.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q72.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q73.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q73.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q73.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q73.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q74.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q74.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q74.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q74.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q75.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q75.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q75.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q75.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q76.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q76.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q76.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q76.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q77.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q77.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q77.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q77.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q78.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q78.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q78.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q78.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q79.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q79.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q79.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q79.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q80.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q80.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q80.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q80.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q81.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q81.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q81.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q81.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q82.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q82.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q82.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q82.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q83.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q83.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q83.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q83.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q84.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q84.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q84.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q84.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q85.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q85.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q85.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q85.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q86.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q86.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q86.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q86.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q87.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q87.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q87.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q87.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q88.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q88.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q88.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q88.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q89.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q89.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q89.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q89.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q90.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q90.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q90.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q90.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q91.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q91.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q91.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q91.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q92.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q92.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q92.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q92.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q93.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q93.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q93.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q93.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q94.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q94.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q94.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q94.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q95.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q95.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q95.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q95.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q96.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q96.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q96.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q96.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q97.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q97.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q97.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q97.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q98.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q98.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q98.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q98.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q99.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q99.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/partitioned/q99.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q99.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q01.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q01.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q01.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q01.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q02.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q02.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q02.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q02.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q03.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q03.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q03.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q03.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q04.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q04.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q04.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q04.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q05.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q05.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q05.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q05.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q06.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q06.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q06.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q06.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q07.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q07.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q07.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q07.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q08.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q08.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q08.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q08.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q09.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q09.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q09.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q09.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q10.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q10.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q10.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q10.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q11.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q11.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q11.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q11.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q12.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q12.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q12.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q12.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q13.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q13.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q13.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q13.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q14.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q14.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q14.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q14.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q15.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q15.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q15.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q15.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q16.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q16.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q16.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q16.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q17.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q17.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q17.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q17.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q18.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q18.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q18.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q18.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q19.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q19.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q19.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q19.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q20.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q20.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q20.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q20.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q21.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q21.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q21.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q21.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q22.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q22.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q22.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q22.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q23.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q23.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q23.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q23.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q24.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q24.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q24.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q24.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q25.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q25.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q25.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q25.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q26.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q26.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q26.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q26.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q27.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q27.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q27.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q27.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q28.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q28.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q28.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q28.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q29.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q29.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q29.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q29.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q30.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q30.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q30.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q30.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q31.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q31.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q31.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q31.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q32.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q32.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q32.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q32.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q33.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q33.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q33.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q33.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q34.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q34.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q34.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q34.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q35.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q35.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q35.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q35.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q36.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q36.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q36.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q36.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q37.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q37.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q37.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q37.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q38.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q38.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q38.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q38.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q39.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q39.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q39.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q39.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q40.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q40.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q40.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q40.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q41.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q41.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q41.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q41.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q42.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q42.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q42.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q42.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q43.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q43.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q43.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q43.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q44.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q44.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q44.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q44.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q45.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q45.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q45.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q45.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q46.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q46.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q46.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q46.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q47.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q47.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q47.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q47.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q48.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q48.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q48.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q48.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q49.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q49.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q49.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q49.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q50.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q50.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q50.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q50.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q51.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q51.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q51.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q51.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q52.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q52.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q52.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q52.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q53.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q53.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q53.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q53.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q54.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q54.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q54.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q54.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q55.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q55.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q55.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q55.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q56.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q56.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q56.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q56.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q57.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q57.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q57.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q57.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q58.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q58.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q58.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q58.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q59.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q59.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q59.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q59.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q60.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q60.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q60.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q60.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q61.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q61.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q61.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q61.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q62.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q62.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q62.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q62.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q63.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q63.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q63.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q63.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q64.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q64.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q64.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q64.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q65.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q65.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q65.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q65.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q66.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q66.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q66.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q66.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q67.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q67.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q67.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q67.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q68.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q68.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q68.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q68.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q69.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q69.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q69.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q69.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q70.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q70.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q70.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q70.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q71.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q71.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q71.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q71.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q72.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q72.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q72.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q72.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q73.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q73.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q73.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q73.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q74.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q74.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q74.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q74.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q75.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q75.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q75.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q75.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q76.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q76.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q76.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q76.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q77.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q77.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q77.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q77.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q78.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q78.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q78.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q78.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q79.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q79.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q79.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q79.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q80.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q80.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q80.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q80.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q81.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q81.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q81.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q81.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q82.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q82.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q82.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q82.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q83.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q83.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q83.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q83.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q84.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q84.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q84.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q84.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q85.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q85.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q85.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q85.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q86.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q86.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q86.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q86.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q87.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q87.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q87.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q87.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q88.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q88.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q88.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q88.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q89.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q89.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q89.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q89.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q90.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q90.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q90.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q90.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q91.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q91.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q91.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q91.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q92.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q92.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q92.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q92.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q93.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q93.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q93.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q93.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q94.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q94.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q94.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q94.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q95.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q95.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q95.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q95.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q96.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q96.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q96.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q96.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q97.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q97.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q97.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q97.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q98.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q98.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q98.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q98.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q99.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q99.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/orc/unpartitioned/q99.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q99.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q01.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q01.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q01.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q01.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q02.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q02.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q02.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q02.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q03.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q03.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q03.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q03.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q04.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q04.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q04.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q04.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q05.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q05.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q05.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q05.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q06.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q06.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q06.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q06.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q07.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q07.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q07.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q07.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q08.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q08.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q08.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q08.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q09.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q09.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q09.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q09.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q10.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q10.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q10.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q10.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q11.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q11.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q11.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q11.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q12.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q12.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q12.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q12.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q13.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q13.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q13.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q13.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q14.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q14.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q14.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q14.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q15.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q15.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q15.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q15.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q16.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q16.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q16.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q16.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q17.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q17.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q17.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q17.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q18.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q18.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q18.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q18.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q19.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q19.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q19.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q19.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q20.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q20.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q20.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q20.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q21.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q21.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q21.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q21.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q22.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q22.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q22.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q22.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q23.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q23.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q23.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q23.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q24.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q24.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q24.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q24.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q25.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q25.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q25.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q25.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q26.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q26.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q26.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q26.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q27.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q27.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q27.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q27.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q28.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q28.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q28.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q28.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q29.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q29.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q29.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q29.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q30.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q30.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q30.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q30.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q31.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q31.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q31.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q31.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q32.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q32.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q32.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q32.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q33.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q33.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q33.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q33.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q34.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q34.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q34.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q34.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q35.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q35.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q35.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q35.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q36.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q36.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q36.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q36.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q37.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q37.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q37.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q37.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q38.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q38.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q38.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q38.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q39.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q39.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q39.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q39.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q40.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q40.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q40.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q40.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q41.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q41.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q41.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q41.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q42.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q42.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q42.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q42.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q43.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q43.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q43.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q43.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q44.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q44.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q44.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q44.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q45.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q45.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q45.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q45.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q46.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q46.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q46.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q46.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q47.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q47.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q47.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q47.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q48.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q48.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q48.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q48.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q49.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q49.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q49.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q49.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q50.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q50.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q50.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q50.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q51.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q51.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q51.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q51.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q52.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q52.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q52.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q52.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q53.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q53.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q53.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q53.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q54.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q54.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q54.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q54.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q55.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q55.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q55.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q55.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q56.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q56.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q56.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q56.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q57.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q57.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q57.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q57.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q58.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q58.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q58.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q58.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q59.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q59.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q59.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q59.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q60.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q60.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q60.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q60.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q61.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q61.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q61.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q61.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q62.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q62.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q62.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q62.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q63.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q63.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q63.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q63.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q64.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q64.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q64.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q64.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q65.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q65.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q65.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q65.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q66.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q66.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q66.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q66.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q67.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q67.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q67.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q67.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q68.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q68.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q68.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q68.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q69.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q69.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q69.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q69.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q70.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q70.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q70.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q70.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q71.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q71.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q71.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q71.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q72.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q72.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q72.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q72.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q73.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q73.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q73.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q73.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q74.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q74.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q74.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q74.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q75.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q75.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q75.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q75.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q76.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q76.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q76.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q76.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q77.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q77.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q77.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q77.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q78.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q78.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q78.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q78.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q79.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q79.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q79.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q79.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q80.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q80.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q80.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q80.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q81.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q81.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q81.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q81.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q82.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q82.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q82.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q82.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q83.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q83.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q83.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q83.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q84.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q84.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q84.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q84.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q85.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q85.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q85.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q85.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q86.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q86.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q86.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q86.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q87.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q87.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q87.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q87.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q88.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q88.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q88.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q88.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q89.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q89.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q89.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q89.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q90.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q90.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q90.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q90.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q91.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q91.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q91.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q91.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q92.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q92.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q92.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q92.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q93.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q93.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q93.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q93.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q94.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q94.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q94.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q94.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q95.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q95.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q95.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q95.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q96.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q96.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q96.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q96.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q97.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q97.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q97.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q97.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q98.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q98.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q98.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q98.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q99.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q99.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/partitioned/q99.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q99.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q01.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q01.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q01.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q01.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q02.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q02.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q02.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q02.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q03.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q03.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q03.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q03.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q04.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q04.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q04.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q04.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q05.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q05.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q05.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q05.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q06.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q06.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q06.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q06.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q07.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q07.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q07.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q07.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q08.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q08.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q08.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q08.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q09.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q09.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q09.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q09.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q10.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q10.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q10.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q10.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q11.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q11.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q11.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q11.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q12.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q12.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q12.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q12.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q13.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q13.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q13.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q13.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q14.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q14.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q14.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q14.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q15.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q15.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q15.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q15.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q16.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q16.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q16.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q16.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q17.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q17.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q17.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q17.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q18.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q18.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q18.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q18.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q19.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q19.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q19.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q19.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q20.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q20.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q20.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q20.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q21.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q21.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q21.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q21.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q22.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q22.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q22.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q22.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q23.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q23.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q23.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q23.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q24.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q24.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q24.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q24.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q25.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q25.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q25.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q25.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q26.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q26.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q26.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q26.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q27.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q27.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q27.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q27.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q28.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q28.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q28.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q28.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q29.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q29.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q29.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q29.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q30.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q30.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q30.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q30.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q31.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q31.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q31.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q31.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q32.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q32.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q32.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q32.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q33.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q33.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q33.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q33.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q34.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q34.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q34.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q34.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q35.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q35.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q35.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q35.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q36.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q36.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q36.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q36.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q37.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q37.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q37.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q37.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q38.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q38.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q38.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q38.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q39.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q39.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q39.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q39.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q40.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q40.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q40.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q40.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q41.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q41.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q41.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q41.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q42.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q42.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q42.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q42.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q43.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q43.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q43.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q43.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q44.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q44.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q44.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q44.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q45.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q45.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q45.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q45.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q46.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q46.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q46.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q46.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q47.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q47.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q47.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q47.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q48.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q48.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q48.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q48.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q49.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q49.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q49.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q49.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q50.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q50.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q50.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q50.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q51.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q51.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q51.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q51.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q52.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q52.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q52.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q52.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q53.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q53.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q53.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q53.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q54.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q54.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q54.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q54.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q55.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q55.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q55.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q55.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q56.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q56.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q56.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q56.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q57.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q57.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q57.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q57.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q58.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q58.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q58.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q58.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q59.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q59.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q59.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q59.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q60.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q60.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q60.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q60.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q61.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q61.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q61.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q61.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q62.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q62.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q62.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q62.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q63.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q63.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q63.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q63.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q64.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q64.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q64.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q64.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q65.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q65.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q65.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q65.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q66.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q66.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q66.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q66.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q67.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q67.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q67.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q67.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q68.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q68.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q68.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q68.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q69.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q69.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q69.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q69.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q70.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q70.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q70.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q70.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q71.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q71.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q71.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q71.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q72.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q72.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q72.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q72.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q73.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q73.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q73.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q73.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q74.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q74.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q74.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q74.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q75.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q75.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q75.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q75.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q76.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q76.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q76.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q76.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q77.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q77.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q77.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q77.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q78.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q78.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q78.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q78.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q79.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q79.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q79.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q79.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q80.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q80.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q80.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q80.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q81.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q81.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q81.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q81.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q82.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q82.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q82.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q82.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q83.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q83.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q83.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q83.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q84.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q84.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q84.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q84.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q85.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q85.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q85.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q85.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q86.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q86.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q86.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q86.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q87.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q87.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q87.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q87.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q88.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q88.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q88.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q88.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q89.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q89.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q89.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q89.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q90.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q90.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q90.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q90.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q91.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q91.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q91.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q91.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q92.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q92.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q92.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q92.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q93.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q93.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q93.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q93.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q94.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q94.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q94.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q94.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q95.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q95.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q95.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q95.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q96.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q96.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q96.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q96.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q97.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q97.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q97.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q97.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q98.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q98.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q98.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q98.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q99.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q99.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg/parquet/unpartitioned/q99.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q99.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q01.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q01.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q01.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q01.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q02.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q02.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q02.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q02.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q03.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q03.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q03.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q03.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q04.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q04.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q04.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q04.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q05.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q05.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q05.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q05.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q06.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q06.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q06.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q06.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q07.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q07.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q07.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q07.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q08.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q08.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q08.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q08.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q09.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q09.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q09.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q09.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q10.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q10.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q10.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q10.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q11.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q11.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q11.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q11.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q12.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q12.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q12.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q12.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q13.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q13.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q13.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q13.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q14.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q14.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q14.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q14.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q15.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q15.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q15.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q15.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q16.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q16.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q16.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q16.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q17.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q17.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q17.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q17.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q18.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q18.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q18.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q18.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q19.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q19.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q19.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q19.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q20.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q20.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q20.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q20.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q21.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q21.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q21.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q21.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q22.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q22.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q22.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q22.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q23.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q23.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q23.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q23.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q24.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q24.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q24.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q24.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q25.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q25.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q25.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q25.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q26.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q26.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q26.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q26.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q27.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q27.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q27.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q27.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q28.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q28.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q28.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q28.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q29.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q29.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q29.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q29.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q30.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q30.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q30.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q30.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q31.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q31.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q31.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q31.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q32.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q32.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q32.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q32.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q33.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q33.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q33.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q33.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q34.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q34.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q34.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q34.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q35.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q35.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q35.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q35.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q36.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q36.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q36.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q36.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q37.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q37.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q37.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q37.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q38.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q38.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q38.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q38.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q39.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q39.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q39.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q39.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q40.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q40.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q40.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q40.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q41.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q41.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q41.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q41.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q42.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q42.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q42.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q42.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q43.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q43.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q43.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q43.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q44.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q44.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q44.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q44.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q45.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q45.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q45.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q45.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q46.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q46.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q46.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q46.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q47.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q47.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q47.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q47.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q48.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q48.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q48.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q48.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q49.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q49.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q49.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q49.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q50.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q50.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q50.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q50.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q51.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q51.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q51.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q51.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q52.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q52.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q52.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q52.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q53.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q53.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q53.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q53.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q54.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q54.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q54.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q54.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q55.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q55.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q55.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q55.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q56.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q56.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q56.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q56.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q57.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q57.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q57.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q57.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q58.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q58.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q58.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q58.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q59.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q59.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q59.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q59.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q60.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q60.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q60.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q60.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q61.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q61.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q61.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q61.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q62.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q62.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q62.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q62.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q63.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q63.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q63.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q63.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q64.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q64.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q64.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q64.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q65.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q65.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q65.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q65.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q66.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q66.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q66.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q66.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q67.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q67.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q67.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q67.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q68.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q68.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q68.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q68.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q69.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q69.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q69.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q69.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q70.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q70.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q70.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q70.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q71.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q71.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q71.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q71.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q72.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q72.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q72.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q72.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q73.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q73.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q73.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q73.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q74.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q74.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q74.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q74.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q75.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q75.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q75.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q75.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q76.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q76.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q76.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q76.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q77.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q77.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q77.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q77.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q78.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q78.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q78.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q78.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q79.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q79.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q79.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q79.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q80.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q80.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q80.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q80.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q81.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q81.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q81.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q81.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q82.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q82.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q82.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q82.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q83.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q83.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q83.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q83.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q84.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q84.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q84.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q84.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q85.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q85.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q85.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q85.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q86.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q86.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q86.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q86.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q87.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q87.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q87.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q87.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q88.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q88.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q88.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q88.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q89.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q89.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q89.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q89.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q90.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q90.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q90.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q90.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q91.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q91.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q91.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q91.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q92.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q92.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q92.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q92.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q93.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q93.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q93.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q93.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q94.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q94.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q94.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q94.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q95.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q95.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q95.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q95.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q96.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q96.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q96.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q96.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q97.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q97.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q97.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q97.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q98.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q98.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q98.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q98.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q99.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q99.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpcds/iceberg_small_files/parquet/unpartitioned/q99.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q99.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q01.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q01.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q01.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q01.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q02.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q02.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q02.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q02.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q03.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q03.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q03.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q03.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q04.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q04.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q04.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q04.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q05.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q05.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q05.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q05.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q06.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q06.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q06.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q06.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q07.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q07.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q07.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q07.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q08.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q08.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q08.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q08.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q09.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q09.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q09.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q09.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q10.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q10.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q10.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q10.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q11.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q11.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q11.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q11.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q12.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q12.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q12.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q12.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q13.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q13.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q13.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q13.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q14.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q14.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q14.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q14.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q15.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q15.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q15.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q15.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q16.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q16.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q16.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q16.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q17.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q17.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q17.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q17.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q18.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q18.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q18.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q18.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q19.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q19.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q19.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q19.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q20.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q20.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q20.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q20.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q21.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q21.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q21.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q21.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q22.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q22.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/partitioned/q22.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q22.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q01.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q01.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q01.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q01.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q02.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q02.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q02.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q02.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q03.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q03.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q03.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q03.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q04.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q04.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q04.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q04.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q05.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q05.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q05.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q05.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q06.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q06.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q06.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q06.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q07.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q07.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q07.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q07.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q08.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q08.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q08.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q08.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q09.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q09.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q09.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q09.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q10.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q10.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q10.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q10.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q11.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q11.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q11.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q11.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q12.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q12.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q12.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q12.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q13.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q13.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q13.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q13.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q14.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q14.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q14.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q14.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q15.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q15.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q15.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q15.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q16.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q16.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q16.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q16.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q17.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q17.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q17.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q17.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q18.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q18.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q18.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q18.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q19.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q19.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q19.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q19.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q20.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q20.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q20.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q20.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q21.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q21.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q21.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q21.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q22.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q22.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/hive/unpartitioned/q22.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q22.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q01.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q01.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q01.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q01.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q02.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q02.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q02.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q02.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q03.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q03.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q03.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q03.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q04.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q04.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q04.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q04.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q05.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q05.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q05.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q05.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q06.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q06.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q06.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q06.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q07.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q07.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q07.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q07.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q08.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q08.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q08.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q08.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q09.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q09.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q09.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q09.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q10.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q10.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q10.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q10.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q11.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q11.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q11.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q11.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q12.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q12.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q12.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q12.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q13.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q13.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q13.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q13.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q14.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q14.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q14.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q14.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q15.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q15.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q15.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q15.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q16.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q16.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q16.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q16.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q17.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q17.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q17.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q17.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q18.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q18.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q18.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q18.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q19.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q19.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q19.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q19.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q20.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q20.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q20.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q20.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q21.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q21.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q21.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q21.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q22.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q22.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/partitioned/q22.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q22.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q01.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q01.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q01.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q01.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q02.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q02.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q02.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q02.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q03.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q03.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q03.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q03.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q04.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q04.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q04.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q04.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q05.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q05.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q05.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q05.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q06.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q06.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q06.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q06.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q07.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q07.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q07.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q07.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q08.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q08.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q08.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q08.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q09.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q09.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q09.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q09.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q10.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q10.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q10.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q10.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q11.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q11.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q11.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q11.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q12.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q12.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q12.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q12.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q13.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q13.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q13.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q13.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q14.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q14.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q14.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q14.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q15.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q15.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q15.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q15.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q16.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q16.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q16.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q16.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q17.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q17.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q17.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q17.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q18.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q18.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q18.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q18.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q19.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q19.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q19.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q19.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q20.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q20.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q20.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q20.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q21.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q21.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q21.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q21.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q22.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q22.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/orc/unpartitioned/q22.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q22.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q01.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q01.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q01.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q01.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q02.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q02.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q02.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q02.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q03.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q03.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q03.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q03.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q04.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q04.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q04.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q04.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q05.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q05.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q05.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q05.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q06.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q06.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q06.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q06.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q07.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q07.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q07.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q07.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q08.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q08.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q08.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q08.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q09.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q09.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q09.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q09.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q10.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q10.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q10.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q10.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q11.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q11.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q11.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q11.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q12.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q12.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q12.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q12.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q13.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q13.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q13.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q13.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q14.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q14.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q14.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q14.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q15.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q15.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q15.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q15.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q16.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q16.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q16.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q16.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q17.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q17.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q17.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q17.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q18.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q18.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q18.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q18.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q19.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q19.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q19.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q19.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q20.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q20.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q20.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q20.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q21.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q21.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q21.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q21.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q22.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q22.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/partitioned/q22.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q22.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q01.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q01.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q01.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q01.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q02.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q02.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q02.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q02.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q03.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q03.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q03.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q03.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q04.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q04.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q04.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q04.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q05.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q05.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q05.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q05.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q06.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q06.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q06.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q06.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q07.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q07.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q07.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q07.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q08.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q08.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q08.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q08.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q09.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q09.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q09.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q09.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q10.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q10.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q10.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q10.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q11.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q11.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q11.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q11.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q12.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q12.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q12.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q12.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q13.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q13.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q13.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q13.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q14.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q14.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q14.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q14.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q15.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q15.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q15.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q15.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q16.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q16.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q16.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q16.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q17.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q17.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q17.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q17.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q18.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q18.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q18.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q18.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q19.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q19.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q19.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q19.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q20.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q20.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q20.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q20.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q21.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q21.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q21.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q21.plan.txt diff --git a/testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q22.plan.txt b/testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q22.plan.txt similarity index 100% rename from testing/trino-tests/src/test/resources/sql/presto/tpch/iceberg/parquet/unpartitioned/q22.plan.txt rename to testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q22.plan.txt From 3b1fc2c9a269425ac0ea6d5c56a54a1fa24a7f9d Mon Sep 17 00:00:00 2001 From: Manfred Moser Date: Wed, 8 Nov 2023 15:49:04 -0800 Subject: [PATCH 125/587] Improve breaking change release notes render --- docs/src/main/sphinx/conf.py | 4 ++++ docs/src/main/sphinx/release.md | 17 +++++++++++++++++ docs/src/main/sphinx/release/release-432.md | 4 ++-- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/docs/src/main/sphinx/conf.py b/docs/src/main/sphinx/conf.py index 158fe1f2792a..baa17cfe1cfc 100644 --- a/docs/src/main/sphinx/conf.py +++ b/docs/src/main/sphinx/conf.py @@ -119,6 +119,10 @@ def setup(app): "substitution" ] +myst_substitutions = { + "breaking": "⚠️" +} + # -- Options for HTML output --------------------------------------------------- html_theme = 'sphinx_material' diff --git a/docs/src/main/sphinx/release.md b/docs/src/main/sphinx/release.md index 62a1407ff714..b92f2e50d73f 100644 --- a/docs/src/main/sphinx/release.md +++ b/docs/src/main/sphinx/release.md @@ -357,3 +357,20 @@ release/release-0.56 release/release-0.55 release/release-0.54 ``` + +## Breaking changes + +Starting with Trino 432, release notes include a ⚠️ symbol to highlight any +changes as potentially breaking changes. The following changes are considered +and may require adjustments: + +* Removal or renaming of configuration properties that may prevent startup or + require configuration changes +* Changes to default values for configuration properties that may significantly + change the behavior of a system +* Updates to the requirements for external systems or software used with Trino, + such as removal of support for an old version of a data source in a connector +* Non-backwards compatible changes to the SPI which may require plugins to be + updated +* Otherwise significant changes that requires specific attention from teams + managing a Trino deployment diff --git a/docs/src/main/sphinx/release/release-432.md b/docs/src/main/sphinx/release/release-432.md index 176ef6a6cbdf..2f334fda69dc 100644 --- a/docs/src/main/sphinx/release/release-432.md +++ b/docs/src/main/sphinx/release/release-432.md @@ -20,7 +20,7 @@ adjustments: * Improve performance of `CREATE TABLE AS ... SELECT` queries that contain a redundant `ORDER BY` clause. ({issue}`19547`) -* ⚠️ Remove support for late materialization, including the +* {{breaking}} Remove support for late materialization, including the `experimental.late-materialization.enabled` and `experimental.work-processor-pipelines` configuration properties. ({issue}`19611`) * Fix potential query failure when using inline functions. ({issue}`19561`) @@ -59,5 +59,5 @@ adjustments: ## SPI * Add Trino version to SystemAccessControlContext. ({issue}`19585`) -* ⚠️ Remove null-suppression from RowBlock fields. Add new factory methods to +* {{breaking}} Remove null-suppression from RowBlock fields. Add new factory methods to create a `RowBlock`, and remove the old factory methods. ({issue}`19479`) From 8a85b6388ecf423cb0ee8ac66815bc1fcaec548d Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Tue, 31 Oct 2023 12:11:56 +0900 Subject: [PATCH 126/587] Remove unused method from TransactionLogAssertions --- .../deltalake/TransactionLogAssertions.java | 41 ------------------- 1 file changed, 41 deletions(-) diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TransactionLogAssertions.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TransactionLogAssertions.java index c88ecd4831e7..a02a5b58659b 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TransactionLogAssertions.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TransactionLogAssertions.java @@ -15,19 +15,15 @@ import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.ListObjectsV2Result; -import com.google.common.base.Throwables; -import org.testng.Assert.ThrowingRunnable; import java.util.List; import java.util.Optional; import java.util.function.Predicate; import static com.google.common.collect.ImmutableList.toImmutableList; -import static java.lang.Long.parseLong; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.testng.Assert.assertEquals; -import static org.testng.Assert.fail; public class TransactionLogAssertions { @@ -50,43 +46,6 @@ public static void assertTransactionLogVersion(AmazonS3 s3Client, String bucketN assertEquals(lastJsonEntry.get(), format("%020d.json", versionNumber)); } - public static void assertNewVersion(AmazonS3 s3Client, String bucketName, String tableName, ThrowingRunnable runnable) - { - long initialVersion = getTransactionLogVersion(s3Client, bucketName, tableName); - try { - runnable.run(); - } - catch (Throwable e) { - Throwables.throwIfUnchecked(e); - throw new RuntimeException(e); - } - assertThat(getTransactionLogVersion(s3Client, bucketName, tableName)) - .isGreaterThan(initialVersion); - } - - public static void assertNoNewVersion(AmazonS3 s3Client, String bucketName, String tableName, ThrowingRunnable runnable) - { - long initialVersion = getTransactionLogVersion(s3Client, bucketName, tableName); - try { - runnable.run(); - } - catch (Throwable e) { - Throwables.throwIfUnchecked(e); - throw new RuntimeException(e); - } - assertThat(getTransactionLogVersion(s3Client, bucketName, tableName)) - .isEqualTo(initialVersion); - } - - private static long getTransactionLogVersion(AmazonS3 s3Client, String bucketName, String tableName) - { - Optional lastJsonEntry = listJsonLogEntries(s3Client, bucketName, tableName).stream().max(String::compareTo); - if (lastJsonEntry.isEmpty()) { - fail("Cannot determine version for table " + tableName); - } - return parseLong(lastJsonEntry.get().split("\\.")[0]); - } - private static List listJsonLogEntries(AmazonS3 s3Client, String bucketName, String tableName) { return listLogEntries(s3Client, bucketName, tableName, file -> file.endsWith(".json")); From b0c287d02197d1fd0dc4aa33949d3264e49368c0 Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Tue, 31 Oct 2023 12:12:05 +0900 Subject: [PATCH 127/587] Make TransactionLogAssertions final --- .../trino/tests/product/deltalake/TransactionLogAssertions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TransactionLogAssertions.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TransactionLogAssertions.java index a02a5b58659b..e54e69bc8ae9 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TransactionLogAssertions.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TransactionLogAssertions.java @@ -25,7 +25,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.testng.Assert.assertEquals; -public class TransactionLogAssertions +public final class TransactionLogAssertions { private TransactionLogAssertions() {} From f98199b58613fc5ad03a63b5da80ae400344cc95 Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Tue, 31 Oct 2023 12:11:12 +0900 Subject: [PATCH 128/587] Run testReplaceTableWithSchemaChangeOnCheckpoint only with OSS Delta --- .../TestDeltaLakeCreateTableAsSelectCompatibility.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeCreateTableAsSelectCompatibility.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeCreateTableAsSelectCompatibility.java index bf9f5aa00781..be31f3bbeb63 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeCreateTableAsSelectCompatibility.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeCreateTableAsSelectCompatibility.java @@ -32,7 +32,6 @@ import static io.trino.tests.product.TestGroups.DELTA_LAKE_DATABRICKS_104; import static io.trino.tests.product.TestGroups.DELTA_LAKE_DATABRICKS_113; import static io.trino.tests.product.TestGroups.DELTA_LAKE_DATABRICKS_122; -import static io.trino.tests.product.TestGroups.DELTA_LAKE_EXCLUDE_133; import static io.trino.tests.product.TestGroups.DELTA_LAKE_OSS; import static io.trino.tests.product.TestGroups.PROFILE_SPECIFIC_TESTS; import static io.trino.tests.product.deltalake.TransactionLogAssertions.assertLastEntryIsCheckpointed; @@ -172,9 +171,7 @@ public void testReplaceTableWithSchemaChange() } } - // Databricks 11.3, 12.2 and 13.3 don't create a checkpoint file at 'CREATE OR REPLACE TABLE' statement - @Test(groups = {DELTA_LAKE_DATABRICKS, DELTA_LAKE_OSS, DELTA_LAKE_EXCLUDE_133, PROFILE_SPECIFIC_TESTS}) - @Flaky(issue = DATABRICKS_COMMUNICATION_FAILURE_ISSUE, match = DATABRICKS_COMMUNICATION_FAILURE_MATCH) + @Test(groups = {DELTA_LAKE_OSS, PROFILE_SPECIFIC_TESTS}) public void testReplaceTableWithSchemaChangeOnCheckpoint() { String tableName = "test_replace_table_with_schema_change_" + randomNameSuffix(); From f8bdaaaf9ac047d9c355962c0722e84f997d0746 Mon Sep 17 00:00:00 2001 From: dominikzalewski Date: Thu, 9 Nov 2023 11:30:49 +0100 Subject: [PATCH 129/587] Expose EventListenerManager benchmarks through JMX --- .../eventlistener/EventListenerManager.java | 50 +++++++++++++++++++ .../eventlistener/EventListenerModule.java | 3 ++ 2 files changed, 53 insertions(+) diff --git a/core/trino-main/src/main/java/io/trino/eventlistener/EventListenerManager.java b/core/trino-main/src/main/java/io/trino/eventlistener/EventListenerManager.java index 92cca6f199be..8f50f8b8fdad 100644 --- a/core/trino-main/src/main/java/io/trino/eventlistener/EventListenerManager.java +++ b/core/trino-main/src/main/java/io/trino/eventlistener/EventListenerManager.java @@ -17,12 +17,15 @@ import com.google.common.collect.ImmutableList; import com.google.inject.Inject; import io.airlift.log.Logger; +import io.airlift.stats.TimeStat; import io.trino.spi.classloader.ThreadContextClassLoader; import io.trino.spi.eventlistener.EventListener; import io.trino.spi.eventlistener.EventListenerFactory; import io.trino.spi.eventlistener.QueryCompletedEvent; import io.trino.spi.eventlistener.QueryCreatedEvent; import io.trino.spi.eventlistener.SplitCompletedEvent; +import org.weakref.jmx.Managed; +import org.weakref.jmx.Nested; import java.io.File; import java.io.IOException; @@ -43,6 +46,7 @@ import static io.airlift.configuration.ConfigurationLoader.loadPropertiesFrom; import static java.lang.String.format; import static java.util.Objects.requireNonNull; +import static java.util.concurrent.TimeUnit.MILLISECONDS; public class EventListenerManager { @@ -55,6 +59,10 @@ public class EventListenerManager private final AtomicReference> configuredEventListeners = new AtomicReference<>(ImmutableList.of()); private final AtomicBoolean loading = new AtomicBoolean(false); + private final TimeStat queryCreatedTime = new TimeStat(MILLISECONDS); + private final TimeStat queryCompletedTime = new TimeStat(MILLISECONDS); + private final TimeStat splitCompletedTime = new TimeStat(MILLISECONDS); + @Inject public EventListenerManager(EventListenerConfig config) { @@ -133,6 +141,13 @@ private static Map loadEventListenerProperties(File configFile) } public void queryCompleted(Function queryCompletedEventProvider) + { + try (TimeStat.BlockTimer ignored = queryCompletedTime.time()) { + doQueryCompleted(queryCompletedEventProvider); + } + } + + private void doQueryCompleted(Function queryCompletedEventProvider) { for (EventListener listener : configuredEventListeners.get()) { QueryCompletedEvent event = queryCompletedEventProvider.apply(listener.requiresAnonymizedPlan()); @@ -146,6 +161,13 @@ public void queryCompleted(Function queryCompleted } public void queryCreated(QueryCreatedEvent queryCreatedEvent) + { + try (TimeStat.BlockTimer ignored = queryCreatedTime.time()) { + doQueryCreated(queryCreatedEvent); + } + } + + private void doQueryCreated(QueryCreatedEvent queryCreatedEvent) { for (EventListener listener : configuredEventListeners.get()) { try { @@ -158,6 +180,13 @@ public void queryCreated(QueryCreatedEvent queryCreatedEvent) } public void splitCompleted(SplitCompletedEvent splitCompletedEvent) + { + try (TimeStat.BlockTimer ignored = splitCompletedTime.time()) { + doSplitCompleted(splitCompletedEvent); + } + } + + private void doSplitCompleted(SplitCompletedEvent splitCompletedEvent) { for (EventListener listener : configuredEventListeners.get()) { try { @@ -168,4 +197,25 @@ public void splitCompleted(SplitCompletedEvent splitCompletedEvent) } } } + + @Managed + @Nested + public TimeStat getQueryCreatedTime() + { + return queryCreatedTime; + } + + @Managed + @Nested + public TimeStat getQueryCompletedTime() + { + return queryCompletedTime; + } + + @Managed + @Nested + public TimeStat getSplitCompletedTime() + { + return splitCompletedTime; + } } diff --git a/core/trino-main/src/main/java/io/trino/eventlistener/EventListenerModule.java b/core/trino-main/src/main/java/io/trino/eventlistener/EventListenerModule.java index bc582d09fd0a..96c8a40c07b6 100644 --- a/core/trino-main/src/main/java/io/trino/eventlistener/EventListenerModule.java +++ b/core/trino-main/src/main/java/io/trino/eventlistener/EventListenerModule.java @@ -18,6 +18,7 @@ import com.google.inject.Scopes; import static io.airlift.configuration.ConfigBinder.configBinder; +import static org.weakref.jmx.guice.ExportBinder.newExporter; public class EventListenerModule implements Module @@ -27,5 +28,7 @@ public void configure(Binder binder) { configBinder(binder).bindConfig(EventListenerConfig.class); binder.bind(EventListenerManager.class).in(Scopes.SINGLETON); + newExporter(binder).export(EventListenerManager.class) + .as(generator -> generator.generatedNameOf(EventListenerManager.class)); } } From 77aea84b6860f2dd5b42736d98d7873b8ac08a88 Mon Sep 17 00:00:00 2001 From: brandboat Date: Sat, 4 Nov 2023 00:19:54 +0800 Subject: [PATCH 130/587] Fix resource group keep setting incorrect softMemoryLimit --- .../AbstractResourceConfigurationManager.java | 5 ++- .../resourcegroups/db/TestQueuesDb.java | 35 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/plugin/trino-resource-group-managers/src/main/java/io/trino/plugin/resourcegroups/AbstractResourceConfigurationManager.java b/plugin/trino-resource-group-managers/src/main/java/io/trino/plugin/resourcegroups/AbstractResourceConfigurationManager.java index c9c758ca70c0..f6fd1fedc55d 100644 --- a/plugin/trino-resource-group-managers/src/main/java/io/trino/plugin/resourcegroups/AbstractResourceConfigurationManager.java +++ b/plugin/trino-resource-group-managers/src/main/java/io/trino/plugin/resourcegroups/AbstractResourceConfigurationManager.java @@ -191,7 +191,10 @@ protected ResourceGroupSpec getMatchingSpec(ResourceGroup group, SelectionContex protected void configureGroup(ResourceGroup group, ResourceGroupSpec match) { if (match.getSoftMemoryLimit().isPresent()) { - group.setSoftMemoryLimitBytes(match.getSoftMemoryLimit().get().toBytes()); + synchronized (memoryPoolFraction) { + memoryPoolFraction.remove(group); + group.setSoftMemoryLimitBytes(match.getSoftMemoryLimit().get().toBytes()); + } } else { synchronized (memoryPoolFraction) { diff --git a/testing/trino-tests/src/test/java/io/trino/execution/resourcegroups/db/TestQueuesDb.java b/testing/trino-tests/src/test/java/io/trino/execution/resourcegroups/db/TestQueuesDb.java index 7fe884b5ea74..57f99705b588 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/resourcegroups/db/TestQueuesDb.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/resourcegroups/db/TestQueuesDb.java @@ -15,6 +15,8 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import io.airlift.units.DataSize; +import io.airlift.units.Duration; import io.trino.Session; import io.trino.dispatcher.DispatchManager; import io.trino.execution.QueryManager; @@ -38,6 +40,7 @@ import java.util.concurrent.TimeUnit; import static io.airlift.testing.Assertions.assertContains; +import static io.airlift.units.DataSize.Unit.MEGABYTE; import static io.trino.SystemSessionProperties.QUERY_MAX_EXECUTION_TIME; import static io.trino.execution.QueryRunnerUtil.cancelQuery; import static io.trino.execution.QueryRunnerUtil.createQuery; @@ -62,6 +65,7 @@ import static io.trino.spi.StandardErrorCode.QUERY_QUEUE_FULL; import static io.trino.spi.StandardErrorCode.QUERY_REJECTED; import static io.trino.testing.TestingSession.testSessionBuilder; +import static io.trino.testing.assertions.Assert.assertEventually; import static java.lang.String.format; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; @@ -369,6 +373,37 @@ public void testNonLeafGroup() assertEquals(queryRunner.getCoordinator().getDispatchManager().getQueryInfo(invalidResourceGroupQuery).getErrorCode(), INVALID_RESOURCE_GROUP.toErrorCode()); } + @Test + public void testUpdateSoftMemoryLimit() + { + // trigger resource group creation + queryRunner.execute("SELECT COUNT(*), clerk FROM orders GROUP BY clerk"); + InternalResourceGroupManager manager = queryRunner.getCoordinator().getResourceGroupManager().orElseThrow(); + DbResourceGroupConfigurationManager dbConfigurationManager = (DbResourceGroupConfigurationManager) manager.getConfigurationManager(); + + dao.updateResourceGroup(2, "bi-${USER}", "100%", 3, 2, 2, null, null, null, null, null, 1L, TEST_ENVIRONMENT); + dbConfigurationManager.load(); + assertEquals( + manager.tryGetResourceGroupInfo(new ResourceGroupId(new ResourceGroupId("global"), "bi-user")) + .orElseThrow(() -> new IllegalStateException("Resource group not found")) + .getSoftMemoryLimit() + .toBytes(), + queryRunner.getCoordinator().getClusterMemoryManager().getClusterMemoryBytes()); + + dao.updateResourceGroup(2, "bi-${USER}", "123MB", 3, 2, 2, null, null, null, null, null, 1L, TEST_ENVIRONMENT); + dbConfigurationManager.load(); + + // wait for SqlQueryManager which enforce memory limits per second + assertEventually( + new Duration(2, TimeUnit.SECONDS), + new Duration(100, TimeUnit.MILLISECONDS), + () -> assertEquals( + manager.tryGetResourceGroupInfo(new ResourceGroupId(new ResourceGroupId("global"), "bi-user")) + .orElseThrow(() -> new IllegalStateException("Resource group not found")) + .getSoftMemoryLimit(), + DataSize.of(123, MEGABYTE))); + } + private void assertResourceGroupWithClientTags(Set clientTags, ResourceGroupId expectedResourceGroup) throws InterruptedException { From 39b10b8e199676e909827651cae0b177df2fff90 Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Thu, 9 Nov 2023 18:12:26 +0900 Subject: [PATCH 131/587] Add `@Execution(SAME_THREAD)` to TestDeltaLakeMetastoreAccessOperations --- .../metastore/TestDeltaLakeMetastoreAccessOperations.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/TestDeltaLakeMetastoreAccessOperations.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/TestDeltaLakeMetastoreAccessOperations.java index a757a8026074..dda0004e5236 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/TestDeltaLakeMetastoreAccessOperations.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/TestDeltaLakeMetastoreAccessOperations.java @@ -30,6 +30,7 @@ import io.trino.testing.DistributedQueryRunner; import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.util.Optional; @@ -43,7 +44,9 @@ import static io.trino.plugin.hive.metastore.file.TestingFileHiveMetastore.createTestingFileHiveMetastore; import static io.trino.testing.TestingSession.testSessionBuilder; import static java.util.Objects.requireNonNull; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; +@Execution(SAME_THREAD) // metastore invocation counters shares mutable state so can't be run from many threads simultaneously public class TestDeltaLakeMetastoreAccessOperations extends AbstractTestQueryFramework { From 245a2537527060f93596876281a6720b93de982a Mon Sep 17 00:00:00 2001 From: Manfred Moser Date: Mon, 6 Nov 2023 14:21:47 -0800 Subject: [PATCH 132/587] Convert source for tables in connector docs to markdown --- docs/src/main/sphinx/connector/accumulo.md | 167 ++-- docs/src/main/sphinx/connector/atop.md | 176 ++-- .../sphinx/connector/avro-decoder.fragment | 41 +- docs/src/main/sphinx/connector/bigquery.md | 174 ++-- docs/src/main/sphinx/connector/cassandra.md | 253 +++-- docs/src/main/sphinx/connector/clickhouse.md | 244 +++-- .../sphinx/connector/csv-decoder.fragment | 31 +- docs/src/main/sphinx/connector/delta-lake.md | 705 +++++++------ docs/src/main/sphinx/connector/druid.md | 47 +- .../main/sphinx/connector/elasticsearch.md | 238 +++-- .../src/main/sphinx/connector/googlesheets.md | 19 +- docs/src/main/sphinx/connector/hive-azure.md | 108 +- .../src/main/sphinx/connector/hive-caching.md | 71 +- .../sphinx/connector/hive-gcs-tutorial.md | 25 +- docs/src/main/sphinx/connector/hive-s3.md | 202 ++-- .../main/sphinx/connector/hive-security.md | 274 +++--- docs/src/main/sphinx/connector/hive.md | 856 ++++++++-------- docs/src/main/sphinx/connector/hudi.md | 155 ++- docs/src/main/sphinx/connector/iceberg.md | 926 +++++++++--------- docs/src/main/sphinx/connector/ignite.md | 113 ++- docs/src/main/sphinx/connector/kafka.md | 68 +- docs/src/main/sphinx/connector/kudu.md | 148 ++- docs/src/main/sphinx/connector/mariadb.md | 302 +++--- docs/src/main/sphinx/connector/metastores.md | 611 ++++++------ docs/src/main/sphinx/connector/mongodb.md | 144 ++- docs/src/main/sphinx/connector/mysql.md | 286 +++--- .../connector/object-storage-file-formats.md | 149 ++- docs/src/main/sphinx/connector/oracle.md | 304 +++--- docs/src/main/sphinx/connector/phoenix.md | 170 ++-- docs/src/main/sphinx/connector/pinot.md | 67 +- docs/src/main/sphinx/connector/postgresql.md | 273 +++--- docs/src/main/sphinx/connector/prometheus.md | 28 +- docs/src/main/sphinx/connector/singlestore.md | 310 +++--- docs/src/main/sphinx/connector/sqlserver.md | 296 +++--- docs/src/main/sphinx/connector/system.md | 64 +- .../main/sphinx/develop/client-protocol.md | 354 ++++--- docs/src/main/sphinx/develop/connectors.md | 138 ++- 37 files changed, 4195 insertions(+), 4342 deletions(-) diff --git a/docs/src/main/sphinx/connector/accumulo.md b/docs/src/main/sphinx/connector/accumulo.md index 55813bee3723..96656f18b548 100644 --- a/docs/src/main/sphinx/connector/accumulo.md +++ b/docs/src/main/sphinx/connector/accumulo.md @@ -674,104 +674,89 @@ the following sections for type mapping in each direction. The connector maps Accumulo types to the corresponding Trino types following this table: -```{eval-rst} -.. list-table:: Accumulo type to Trino type mapping - :widths: 30, 20, 50 - :header-rows: 1 - - * - Accumulo type - - Trino type - - Notes - * - ``BOOLEAN`` - - ``BOOLEAN`` - - - * - ``TINYINT`` - - ``TINYINT`` - - - * - ``SMALLINT`` - - ``SMALLINT`` - - - * - ``INTEGER`` - - ``INTEGER`` - - - * - ``BIGINT`` - - ``BIGINT`` - - - * - ``REAL`` - - ``REAL`` - - - * - ``DOUBLE`` - - ``DOUBLE`` - - - * - ``VARCHAR(n)`` - - ``VARCHAR(n)`` - - - * - ``VARBINARY`` - - ``VARBINARY`` - - - * - ``DATE`` - - ``DATE`` - - - * - ``TIME(n)`` - - ``TIME(n)`` - - - * - ``TIMESTAMP(n)`` - - ``TIMESTAMP(n)`` - - -``` +:::{list-table} Accumulo type to Trino type mapping +:widths: 50, 50 +:header-rows: 1 + +* - Accumulo type + - Trino type +* - `BOOLEAN` + - `BOOLEAN` +* - `TINYINT` + - `TINYINT` +* - `SMALLINT` + - `SMALLINT` +* - `INTEGER` + - `INTEGER` +* - `BIGINT` + - `BIGINT` +* - `REAL` + - `REAL` +* - `DOUBLE` + - `DOUBLE` +* - `VARCHAR(n)` + - `VARCHAR(n)` +* - `VARBINARY` + - `VARBINARY` +* - `DATE` + - `DATE` +* - `TIME(n)` + - `TIME(n)` +* - `TIMESTAMP(n)` + - `TIMESTAMP(n)` +::: -No other types are supported +No other types are supported. ### Trino type to Accumulo type mapping The connector maps Trino types to the corresponding Trino type to Accumulo type mapping types following this table: -```{eval-rst} -.. list-table:: Trino type to Accumulo type mapping - :widths: 30, 20, 50 - :header-rows: 1 - - * - Trino type - - Accumulo type - - Notes - * - ``BOOLEAN`` - - ``BOOLEAN`` - - - * - ``TINYINT`` - - ``TINYINT`` - - Trino only supports writing values belonging to ``[0, 127]`` - * - ``SMALLINT`` - - ``SMALLINT`` - - - * - ``INTEGER`` - - ``INTEGER`` - - - * - ``BIGINT`` - - ``BIGINT`` - - - * - ``REAL`` - - ``REAL`` - - - * - ``DOUBLE`` - - ``DOUBLE`` - - - * - ``VARCHAR(n)`` - - ``VARCHAR(n)`` - - - * - ``VARBINARY`` - - ``VARBINARY`` - - - * - ``DATE`` - - ``DATE`` - - - * - ``TIME(n)`` - - ``TIME(n)`` - - - * - ``TIMESTAMP(n)`` - - ``TIMESTAMP(n)`` - - -``` +:::{list-table} Trino type to Accumulo type mapping +:widths: 25, 25, 50 +:header-rows: 1 + +* - Trino type + - Accumulo type + - Notes +* - `BOOLEAN` + - `BOOLEAN` + - +* - `TINYINT` + - `TINYINT` + - Trino only supports writing values belonging to `[0, 127]` +* - `SMALLINT` + - `SMALLINT` + - +* - `INTEGER` + - `INTEGER` + - +* - `BIGINT` + - `BIGINT` + - +* - `REAL` + - `REAL` + - +* - `DOUBLE` + - `DOUBLE` + - +* - `VARCHAR(n)` + - `VARCHAR(n)` + - +* - `VARBINARY` + - `VARBINARY` + - +* - `DATE` + - `DATE` + - +* - `TIME(n)` + - `TIME(n)` + - +* - `TIMESTAMP(n)` + - `TIMESTAMP(n)` + - +::: No other types are supported diff --git a/docs/src/main/sphinx/connector/atop.md b/docs/src/main/sphinx/connector/atop.md index 64eca21db683..776cbde67b5e 100644 --- a/docs/src/main/sphinx/connector/atop.md +++ b/docs/src/main/sphinx/connector/atop.md @@ -24,41 +24,40 @@ atop.executable-path=/usr/bin/atop ## Configuration properties -```{eval-rst} -.. list-table:: - :widths: 42, 18, 5, 35 - :header-rows: 1 - - * - Property name - - Default value - - Required - - Description - * - ``atop.concurrent-readers-per-node`` - - ``1`` - - Yes - - The number of concurrent read operations allowed per node. - * - ``atop.executable-path`` - - (none) - - Yes - - The file path on the local file system for the ``atop`` utility. - * - ``atop.executable-read-timeout`` - - ``1ms`` - - Yes - - The timeout when reading from the atop process. - * - ``atop.max-history-days`` - - ``30`` - - Yes - - The maximum number of days in the past to take into account for statistics. - * - ``atop.security`` - - ``ALLOW_ALL`` - - Yes - - The :doc:`access control ` for the connector. - * - ``atop.time-zone`` - - System default - - Yes - - The time zone identifier in which the atop data is collected. Generally the timezone of the host. - Sample time zone identifiers: ``Europe/Vienna``, ``+0100``, ``UTC``. -``` +:::{list-table} +:widths: 42, 18, 5, 35 +:header-rows: 1 + +* - Property name + - Default value + - Required + - Description +* - `atop.concurrent-readers-per-node` + - `1` + - Yes + - The number of concurrent read operations allowed per node. +* - `atop.executable-path` + - (none) + - Yes + - The file path on the local file system for the `atop` utility. +* - `atop.executable-read-timeout` + - `1ms` + - Yes + - The timeout when reading from the atop process. +* - `atop.max-history-days` + - `30` + - Yes + - The maximum number of days in the past to take into account for statistics. +* - `atop.security` + - `ALLOW_ALL` + - Yes + - The [access control](/security/built-in-system-access-control) for the connector. +* - `atop.time-zone` + - System default + - Yes + - The time zone identifier in which the atop data is collected. Generally the timezone of the host. + Sample time zone identifiers: `Europe/Vienna`, `+0100`, `UTC`. +::: ## Usage @@ -80,64 +79,61 @@ SHOW TABLES FROM example.default; The `disks` table offers disk utilization statistics recorded on the Trino node. -```{eval-rst} -.. list-table:: Disks columns - :widths: 30, 30, 40 - :header-rows: 1 - - * - Name - - Type - - Description - * - ``host_ip`` - - ``VARCHAR`` - - Trino worker IP - * - ``start_time`` - - ``TIMESTAMP(3) WITH TIME ZONE`` - - Interval start time for the statistics - * - ``end_time`` - - ``TIMESTAMP(3) WITH TIME ZONE`` - - Interval end time for the statistics - * - ``device_name`` - - ``VARCHAR`` - - Logical volume/hard disk name - * - ``utilization_percent`` - - ``DOUBLE`` - - The percentage of time the unit was busy handling requests - * - ``io_time`` - - ``INTERVAL DAY TO SECOND`` - - Time spent for I/O - * - ``read_requests`` - - ``BIGINT`` - - Number of reads issued - * - ``sectors_read`` - - ``BIGINT`` - - Number of sectors transferred for reads - * - ``write_requests`` - - ``BIGINT`` - - Number of writes issued - * - ``sectors_written`` - - ``BIGINT`` - - Number of sectors transferred for write -``` +:::{list-table} Disks columns +:widths: 30, 30, 40 +:header-rows: 1 + +* - Name + - Type + - Description +* - `host_ip` + - `VARCHAR` + - Trino worker IP +* - `start_time` + - `TIMESTAMP(3) WITH TIME ZONE` + - Interval start time for the statistics +* - `end_time` + - `TIMESTAMP(3) WITH TIME ZONE` + - Interval end time for the statistics +* - `device_name` + - `VARCHAR` + - Logical volume/hard disk name +* - `utilization_percent` + - `DOUBLE` + - The percentage of time the unit was busy handling requests +* - `io_time` + - `INTERVAL DAY TO SECOND` + - Time spent for I/O +* - `read_requests` + - `BIGINT` + - Number of reads issued +* - `sectors_read` + - `BIGINT` + - Number of sectors transferred for reads +* - `write_requests` + - `BIGINT` + - Number of writes issued +* - `sectors_written` + - `BIGINT` + - Number of sectors transferred for write +::: The `reboots` table offers information about the system reboots performed on the Trino node. -```{eval-rst} -.. list-table:: Reboots columns - :widths: 30, 30, 40 - :header-rows: 1 - - * - Name - - Type - - Description - * - ``host_ip`` - - ``VARCHAR`` - - Trino worker IP - * - ``power_on_time`` - - ``TIMESTAMP(3) WITH TIME ZONE`` - - The boot/reboot timestamp - -``` +:::{list-table} Reboots columns +:widths: 30, 30, 40 +:header-rows: 1 + +* - Name + - Type + - Description +* - `host_ip` + - `VARCHAR` + - Trino worker IP +* - `power_on_time` + - `TIMESTAMP(3) WITH TIME ZONE` + - The boot/reboot timestamp +::: ## SQL support diff --git a/docs/src/main/sphinx/connector/avro-decoder.fragment b/docs/src/main/sphinx/connector/avro-decoder.fragment index f4e995e73037..9d2bbfad3dd1 100644 --- a/docs/src/main/sphinx/connector/avro-decoder.fragment +++ b/docs/src/main/sphinx/connector/avro-decoder.fragment @@ -22,28 +22,27 @@ The following attributes are supported: The following table lists the supported Trino types that can be used in `type` for the equivalent Avro field types: -```{eval-rst} -.. list-table:: - :widths: 40, 60 - :header-rows: 1 +:::{list-table} +:widths: 40, 60 +:header-rows: 1 - * - Trino data type - - Allowed Avro data type - * - ``BIGINT`` - - ``INT``, ``LONG`` - * - ``DOUBLE`` - - ``DOUBLE``, ``FLOAT`` - * - ``BOOLEAN`` - - ``BOOLEAN`` - * - ``VARCHAR`` / ``VARCHAR(x)`` - - ``STRING`` - * - ``VARBINARY`` - - ``FIXED``, ``BYTES`` - * - ``ARRAY`` - - ``ARRAY`` - * - ``MAP`` - - ``MAP`` -``` +* - Trino data type + - Allowed Avro data type +* - `BIGINT` + - `INT`, `LONG` +* - `DOUBLE` + - `DOUBLE`, `FLOAT` +* - `BOOLEAN` + - `BOOLEAN` +* - `VARCHAR` / `VARCHAR(x)` + - `STRING` +* - `VARBINARY` + - `FIXED`, `BYTES` +* - `ARRAY` + - `ARRAY` +* - `MAP` + - `MAP` +::: No other types are supported. diff --git a/docs/src/main/sphinx/connector/bigquery.md b/docs/src/main/sphinx/connector/bigquery.md index 6cdfef2c3e19..96db6e68c40a 100644 --- a/docs/src/main/sphinx/connector/bigquery.md +++ b/docs/src/main/sphinx/connector/bigquery.md @@ -165,59 +165,60 @@ each direction. The connector maps BigQuery types to the corresponding Trino types according to the following table: -```{eval-rst} -.. list-table:: BigQuery type to Trino type mapping - :widths: 30, 30, 50 - :header-rows: 1 - - * - BigQuery type - - Trino type - - Notes - * - ``BOOLEAN`` - - ``BOOLEAN`` - - - * - ``INT64`` - - ``BIGINT`` - - ``INT``, ``SMALLINT``, ``INTEGER``, ``BIGINT``, ``TINYINT``, and - ``BYTEINT`` are aliases for ``INT64`` in BigQuery. - * - ``FLOAT64`` - - ``DOUBLE`` - - - * - ``NUMERIC`` - - ``DECIMAL(P,S)`` - - The default precision and scale of ``NUMERIC`` is ``(38, 9)``. - * - ``BIGNUMERIC`` - - ``DECIMAL(P,S)`` - - Precision > 38 is not supported. The default precision and scale of - ``BIGNUMERIC`` is ``(77, 38)``. - * - ``DATE`` - - ``DATE`` - - - * - ``DATETIME`` - - ``TIMESTAMP(6)`` - - - * - ``STRING`` - - ``VARCHAR`` - - - * - ``BYTES`` - - ``VARBINARY`` - - - * - ``TIME`` - - ``TIME(6)`` - - - * - ``TIMESTAMP`` - - ``TIMESTAMP(6) WITH TIME ZONE`` - - Time zone is UTC - * - ``GEOGRAPHY`` - - ``VARCHAR`` - - In `Well-known text (WKT) `_ format - * - ``ARRAY`` - - ``ARRAY`` - - - * - ``RECORD`` - - ``ROW`` - - -``` +:::{list-table} BigQuery type to Trino type mapping +:widths: 30, 30, 50 +:header-rows: 1 + +* - BigQuery type + - Trino type + - Notes +* - `BOOLEAN` + - `BOOLEAN` + - +* - `INT64` + - `BIGINT` + - `INT`, `SMALLINT`, `INTEGER`, `BIGINT`, `TINYINT`, and `BYTEINT` are aliases + for `INT64` in BigQuery. +* - `FLOAT64` + - `DOUBLE` + - +* - `NUMERIC` + - `DECIMAL(P,S)` + - The default precision and scale of `NUMERIC` is `(38, 9)`. +* - `BIGNUMERIC` + - `DECIMAL(P,S)` + - Precision > 38 is not supported. The default precision and scale of + `BIGNUMERIC` is `(77, 38)`. +* - `DATE` + - `DATE` + - +* - `DATETIME` + - `TIMESTAMP(6)` + - +* - `STRING` + - `VARCHAR` + - +* - `BYTES` + - `VARBINARY` + - +* - `TIME` + - `TIME(6)` + - +* - `TIMESTAMP` + - `TIMESTAMP(6) WITH TIME ZONE` + - Time zone is UTC +* - `GEOGRAPHY` + - `VARCHAR` + - In [Well-known text + (WKT)](https://wikipedia.org/wiki/Well-known_text_representation_of_geometry) + format +* - `ARRAY` + - `ARRAY` + - +* - `RECORD` + - `ROW` + - +::: No other types are supported. @@ -226,40 +227,39 @@ No other types are supported. The connector maps Trino types to the corresponding BigQuery types according to the following table: -```{eval-rst} -.. list-table:: Trino type to BigQuery type mapping - :widths: 30, 30, 50 - :header-rows: 1 - - * - Trino type - - BigQuery type - - Notes - * - ``BOOLEAN`` - - ``BOOLEAN`` - - - * - ``VARBINARY`` - - ``BYTES`` - - - * - ``DATE`` - - ``DATE`` - - - * - ``DOUBLE`` - - ``FLOAT`` - - - * - ``BIGINT`` - - ``INT64`` - - ``INT``, ``SMALLINT``, ``INTEGER``, ``BIGINT``, ``TINYINT``, and - ``BYTEINT`` are aliases for ``INT64`` in BigQuery. - * - ``DECIMAL(P,S)`` - - ``NUMERIC`` - - The default precision and scale of ``NUMERIC`` is ``(38, 9)``. - * - ``VARCHAR`` - - ``STRING`` - - - * - ``TIMESTAMP(6)`` - - ``DATETIME`` - - -``` +:::{list-table} Trino type to BigQuery type mapping +:widths: 30, 30, 50 +:header-rows: 1 + +* - Trino type + - BigQuery type + - Notes +* - `BOOLEAN` + - `BOOLEAN` + - +* - `VARBINARY` + - `BYTES` + - +* - `DATE` + - `DATE` + - +* - `DOUBLE` + - `FLOAT` + - +* - `BIGINT` + - `INT64` + - `INT`, `SMALLINT`, `INTEGER`, `BIGINT`, `TINYINT`, and + `BYTEINT` are aliases for `INT64` in BigQuery. +* - `DECIMAL(P,S)` + - `NUMERIC` + - The default precision and scale of `NUMERIC` is `(38, 9)`. +* - `VARCHAR` + - `STRING` + - +* - `TIMESTAMP(6)` + - `DATETIME` + - +::: No other types are supported. diff --git a/docs/src/main/sphinx/connector/cassandra.md b/docs/src/main/sphinx/connector/cassandra.md index 9fd93052117e..21f28ceed26b 100644 --- a/docs/src/main/sphinx/connector/cassandra.md +++ b/docs/src/main/sphinx/connector/cassandra.md @@ -137,87 +137,86 @@ each direction. The connector maps Cassandra types to the corresponding Trino types according to the following table: -```{eval-rst} -.. list-table:: Cassandra type to Trino type mapping - :widths: 30, 25, 50 - :header-rows: 1 - - * - Cassandra type - - Trino type - - Notes - * - ``BOOLEAN`` - - ``BOOLEAN`` - - - * - ``TINYINT`` - - ``TINYINT`` - - - * - ``SMALLINT`` - - ``SMALLINT`` - - - * - ``INT`` - - ``INTEGER`` - - - * - ``BIGINT`` - - ``BIGINT`` - - - * - ``FLOAT`` - - ``REAL`` - - - * - ``DOUBLE`` - - ``DOUBLE`` - - - * - ``DECIMAL`` - - ``DOUBLE`` - - - * - ``ASCII`` - - ``VARCHAR`` - - US-ASCII character string - * - ``TEXT`` - - ``VARCHAR`` - - UTF-8 encoded string - * - ``VARCHAR`` - - ``VARCHAR`` - - UTF-8 encoded string - * - ``VARINT`` - - ``VARCHAR`` - - Arbitrary-precision integer - * - ``BLOB`` - - ``VARBINARY`` - - - * - ``DATE`` - - ``DATE`` - - - * - ``TIME`` - - ``TIME(9)`` - - - * - ``TIMESTAMP`` - - ``TIMESTAMP(3) WITH TIME ZONE`` - - - * - ``LIST`` - - ``VARCHAR`` - - - * - ``MAP`` - - ``VARCHAR`` - - - * - ``SET`` - - ``VARCHAR`` - - - * - ``TUPLE`` - - ``ROW`` with anonymous fields - - - * - ``UDT`` - - ``ROW`` with field names - - - * - ``INET`` - - ``IPADDRESS`` - - - * - ``UUID`` - - ``UUID`` - - - * - ``TIMEUUID`` - - ``UUID`` - - -``` +:::{list-table} Cassandra type to Trino type mapping +:widths: 30, 25, 50 +:header-rows: 1 + +* - Cassandra type + - Trino type + - Notes +* - `BOOLEAN` + - `BOOLEAN` + - +* - `TINYINT` + - `TINYINT` + - +* - `SMALLINT` + - `SMALLINT` + - +* - `INT` + - `INTEGER` + - +* - `BIGINT` + - `BIGINT` + - +* - `FLOAT` + - `REAL` + - +* - `DOUBLE` + - `DOUBLE` + - +* - `DECIMAL` + - `DOUBLE` + - +* - `ASCII` + - `VARCHAR` + - US-ASCII character string +* - `TEXT` + - `VARCHAR` + - UTF-8 encoded string +* - `VARCHAR` + - `VARCHAR` + - UTF-8 encoded string +* - `VARINT` + - `VARCHAR` + - Arbitrary-precision integer +* - `BLOB` + - `VARBINARY` + - +* - `DATE` + - `DATE` + - +* - `TIME` + - `TIME(9)` + - +* - `TIMESTAMP` + - `TIMESTAMP(3) WITH TIME ZONE` + - +* - `LIST` + - `VARCHAR` + - +* - `MAP` + - `VARCHAR` + - +* - `SET` + - `VARCHAR` + - +* - `TUPLE` + - `ROW` with anonymous fields + - +* - `UDT` + - `ROW` with field names + - +* - `INET` + - `IPADDRESS` + - +* - `UUID` + - `UUID` + - +* - `TIMEUUID` + - `UUID` + - +::: No other types are supported. @@ -226,53 +225,51 @@ No other types are supported. The connector maps Trino types to the corresponding Cassandra types according to the following table: -```{eval-rst} -.. list-table:: Trino type to Cassandra type mapping - :widths: 30, 25, 50 - :header-rows: 1 - - * - Trino type - - Cassandra type - - Notes - - * - ``BOOLEAN`` - - ``BOOLEAN`` - - - * - ``TINYINT`` - - ``TINYINT`` - - - * - ``SMALLINT`` - - ``SMALLINT`` - - - * - ``INTEGER`` - - ``INT`` - - - * - ``BIGINT`` - - ``BIGINT`` - - - * - ``REAL`` - - ``FLOAT`` - - - * - ``DOUBLE`` - - ``DOUBLE`` - - - * - ``VARCHAR`` - - ``TEXT`` - - - * - ``DATE`` - - ``DATE`` - - - * - ``TIMESTAMP(3) WITH TIME ZONE`` - - ``TIMESTAMP`` - - - * - ``IPADDRESS`` - - ``INET`` - - - * - ``UUID`` - - ``UUID`` - - - -``` +:::{list-table} Trino type to Cassandra type mapping +:widths: 30, 25, 50 +:header-rows: 1 + +* - Trino type + - Cassandra type + - Notes + +* - `BOOLEAN` + - `BOOLEAN` + - +* - `TINYINT` + - `TINYINT` + - +* - `SMALLINT` + - `SMALLINT` + - +* - `INTEGER` + - `INT` + - +* - `BIGINT` + - `BIGINT` + - +* - `REAL` + - `FLOAT` + - +* - `DOUBLE` + - `DOUBLE` + - +* - `VARCHAR` + - `TEXT` + - +* - `DATE` + - `DATE` + - +* - `TIMESTAMP(3) WITH TIME ZONE` + - `TIMESTAMP` + - +* - `IPADDRESS` + - `INET` + - +* - `UUID` + - `UUID` + - +::: No other types are supported. diff --git a/docs/src/main/sphinx/connector/clickhouse.md b/docs/src/main/sphinx/connector/clickhouse.md index e01b664aa336..611c922707e8 100644 --- a/docs/src/main/sphinx/connector/clickhouse.md +++ b/docs/src/main/sphinx/connector/clickhouse.md @@ -184,77 +184,76 @@ each direction. The connector maps ClickHouse types to the corresponding Trino types according to the following table: -```{eval-rst} -.. list-table:: ClickHouse type to Trino type mapping - :widths: 30, 25, 50 - :header-rows: 1 - - * - ClickHouse type - - Trino type - - Notes - * - ``Int8`` - - ``TINYINT`` - - ``TINYINT``, ``BOOL``, ``BOOLEAN``, and ``INT1`` are aliases of ``Int8`` - * - ``Int16`` - - ``SMALLINT`` - - ``SMALLINT`` and ``INT2`` are aliases of ``Int16`` - * - ``Int32`` - - ``INTEGER`` - - ``INT``, ``INT4``, and ``INTEGER`` are aliases of ``Int32`` - * - ``Int64`` - - ``BIGINT`` - - ``BIGINT`` is an alias of ``Int64`` - * - ``UInt8`` - - ``SMALLINT`` - - - * - ``UInt16`` - - ``INTEGER`` - - - * - ``UInt32`` - - ``BIGINT`` - - - * - ``UInt64`` - - ``DECIMAL(20,0)`` - - - * - ``Float32`` - - ``REAL`` - - ``FLOAT`` is an alias of ``Float32`` - * - ``Float64`` - - ``DOUBLE`` - - ``DOUBLE`` is an alias of ``Float64`` - * - ``Decimal`` - - ``DECIMAL`` - - - * - ``FixedString`` - - ``VARBINARY`` - - Enabling ``clickhouse.map-string-as-varchar`` config property changes the - mapping to ``VARCHAR`` - * - ``String`` - - ``VARBINARY`` - - Enabling ``clickhouse.map-string-as-varchar`` config property changes the - mapping to ``VARCHAR`` - * - ``Date`` - - ``DATE`` - - - * - ``DateTime[(timezone)]`` - - ``TIMESTAMP(0) [WITH TIME ZONE]`` - - - * - ``IPv4`` - - ``IPADDRESS`` - - - * - ``IPv6`` - - ``IPADDRESS`` - - - * - ``Enum8`` - - ``VARCHAR`` - - - * - ``Enum16`` - - ``VARCHAR`` - - - * - ``UUID`` - - ``UUID`` - - -``` +:::{list-table} ClickHouse type to Trino type mapping +:widths: 30, 25, 50 +:header-rows: 1 + +* - ClickHouse type + - Trino type + - Notes +* - `Int8` + - `TINYINT` + - `TINYINT`, `BOOL`, `BOOLEAN`, and `INT1` are aliases of `Int8` +* - `Int16` + - `SMALLINT` + - `SMALLINT` and `INT2` are aliases of `Int16` +* - `Int32` + - `INTEGER` + - `INT`, `INT4`, and `INTEGER` are aliases of `Int32` +* - `Int64` + - `BIGINT` + - `BIGINT` is an alias of `Int64` +* - `UInt8` + - `SMALLINT` + - +* - `UInt16` + - `INTEGER` + - +* - `UInt32` + - `BIGINT` + - +* - `UInt64` + - `DECIMAL(20,0)` + - +* - `Float32` + - `REAL` + - `FLOAT` is an alias of `Float32` +* - `Float64` + - `DOUBLE` + - `DOUBLE` is an alias of `Float64` +* - `Decimal` + - `DECIMAL` + - +* - `FixedString` + - `VARBINARY` + - Enabling `clickhouse.map-string-as-varchar` config property changes the + mapping to `VARCHAR` +* - `String` + - `VARBINARY` + - Enabling `clickhouse.map-string-as-varchar` config property changes the + mapping to `VARCHAR` +* - `Date` + - `DATE` + - +* - `DateTime[(timezone)]` + - `TIMESTAMP(0) [WITH TIME ZONE]` + - +* - `IPv4` + - `IPADDRESS` + - +* - `IPv6` + - `IPADDRESS` + - +* - `Enum8` + - `VARCHAR` + - +* - `Enum16` + - `VARCHAR` + - +* - `UUID` + - `UUID` + - +::: No other types are supported. @@ -263,58 +262,57 @@ No other types are supported. The connector maps Trino types to the corresponding ClickHouse types according to the following table: -```{eval-rst} -.. list-table:: Trino type to ClickHouse type mapping - :widths: 30, 25, 50 - :header-rows: 1 - - * - Trino type - - ClickHouse type - - Notes - * - ``BOOLEAN`` - - ``UInt8`` - - - * - ``TINYINT`` - - ``Int8`` - - ``TINYINT``, ``BOOL``, ``BOOLEAN``, and ``INT1`` are aliases of ``Int8`` - * - ``SMALLINT`` - - ``Int16`` - - ``SMALLINT`` and ``INT2`` are aliases of ``Int16`` - * - ``INTEGER`` - - ``Int32`` - - ``INT``, ``INT4``, and ``INTEGER`` are aliases of ``Int32`` - * - ``BIGINT`` - - ``Int64`` - - ``BIGINT`` is an alias of ``Int64`` - * - ``REAL`` - - ``Float32`` - - ``FLOAT`` is an alias of ``Float32`` - * - ``DOUBLE`` - - ``Float64`` - - ``DOUBLE`` is an alias of ``Float64`` - * - ``DECIMAL(p,s)`` - - ``Decimal(p,s)`` - - - * - ``VARCHAR`` - - ``String`` - - - * - ``CHAR`` - - ``String`` - - - * - ``VARBINARY`` - - ``String`` - - Enabling ``clickhouse.map-string-as-varchar`` config property changes the - mapping to ``VARCHAR`` - * - ``DATE`` - - ``Date`` - - - * - ``TIMESTAMP(0)`` - - ``DateTime`` - - - * - ``UUID`` - - ``UUID`` - - -``` +:::{list-table} Trino type to ClickHouse type mapping +:widths: 30, 25, 50 +:header-rows: 1 + +* - Trino type + - ClickHouse type + - Notes +* - `BOOLEAN` + - `UInt8` + - +* - `TINYINT` + - `Int8` + - `TINYINT`, `BOOL`, `BOOLEAN`, and `INT1` are aliases of `Int8` +* - `SMALLINT` + - `Int16` + - `SMALLINT` and `INT2` are aliases of `Int16` +* - `INTEGER` + - `Int32` + - `INT`, `INT4`, and `INTEGER` are aliases of `Int32` +* - `BIGINT` + - `Int64` + - `BIGINT` is an alias of `Int64` +* - `REAL` + - `Float32` + - `FLOAT` is an alias of `Float32` +* - `DOUBLE` + - `Float64` + - `DOUBLE` is an alias of `Float64` +* - `DECIMAL(p,s)` + - `Decimal(p,s)` + - +* - `VARCHAR` + - `String` + - +* - `CHAR` + - `String` + - +* - `VARBINARY` + - `String` + - Enabling `clickhouse.map-string-as-varchar` config property changes the + mapping to `VARCHAR` +* - `DATE` + - `Date` + - +* - `TIMESTAMP(0)` + - `DateTime` + - +* - `UUID` + - `UUID` + - +::: No other types are supported. diff --git a/docs/src/main/sphinx/connector/csv-decoder.fragment b/docs/src/main/sphinx/connector/csv-decoder.fragment index f6e9044aa0e1..78a0642a563e 100644 --- a/docs/src/main/sphinx/connector/csv-decoder.fragment +++ b/docs/src/main/sphinx/connector/csv-decoder.fragment @@ -13,22 +13,21 @@ For fields, the `type` and `mapping` attributes must be defined: The `dataFormat` and `formatHint` attributes are not supported and must be omitted. -```{eval-rst} -.. list-table:: - :widths: 40, 60 - :header-rows: 1 +:::{list-table} +:widths: 40, 60 +:header-rows: 1 - * - Trino data type - - Decoding rules - * - ``BIGINT``, ``INTEGER``, ``SMALLINT``, ``TINYINT`` - - Decoded using Java ``Long.parseLong()`` - * - ``DOUBLE`` - - Decoded using Java ``Double.parseDouble()`` - * - ``BOOLEAN`` - - "true" character sequence maps to ``true``. Other character sequences map - to ``false`` - * - ``VARCHAR`` / ``VARCHAR(x)`` - - Used as is -``` +* - Trino data type + - Decoding rules +* - `BIGINT`, `INTEGER`, `SMALLINT`, `TINYINT` + - Decoded using Java `Long.parseLong()` +* - `DOUBLE` + - Decoded using Java `Double.parseDouble()` +* - `BOOLEAN` + - "true" character sequence maps to `true`. Other character sequences map + to `false` +* - `VARCHAR` / `VARCHAR(x)` + - Used as is +::: No other types are supported. diff --git a/docs/src/main/sphinx/connector/delta-lake.md b/docs/src/main/sphinx/connector/delta-lake.md index 0bcbbc91fea7..1bbd1bb95e9c 100644 --- a/docs/src/main/sphinx/connector/delta-lake.md +++ b/docs/src/main/sphinx/connector/delta-lake.md @@ -65,146 +65,142 @@ consult the appropriate section of the Hive documentation: The following configuration properties are all using reasonable, tested default values. Typical usage does not require you to configure them. -```{eval-rst} -.. list-table:: Delta Lake configuration properties - :widths: 30, 55, 15 - :header-rows: 1 - - * - Property name - - Description - - Default - * - ``delta.metadata.cache-ttl`` - - Frequency of checks for metadata updates equivalent to transactions to - update the metadata cache specified in :ref:`prop-type-duration`. - - ``5m`` - * - ``delta.metadata.cache-size`` - - The maximum number of Delta table metadata entries to cache. - - ``1000`` - * - ``delta.metadata.live-files.cache-size`` - - Amount of memory allocated for caching information about files. Must - be specified in :ref:`prop-type-data-size` values such as ``64MB``. - Default is calculated to 10% of the maximum memory allocated to the JVM. - - - * - ``delta.metadata.live-files.cache-ttl`` - - Caching duration for active files that correspond to the Delta Lake - tables. - - ``30m`` - * - ``delta.compression-codec`` - - The compression codec to be used when writing new data files. - Possible values are: - - * ``NONE`` - * ``SNAPPY`` - * ``ZSTD`` - * ``GZIP`` - - The equivalent catalog session property is ``compression_codec``. - - ``SNAPPY`` - * - ``delta.max-partitions-per-writer`` - - Maximum number of partitions per writer. - - ``100`` - * - ``delta.hide-non-delta-lake-tables`` - - Hide information about tables that are not managed by Delta Lake. Hiding - only applies to tables with the metadata managed in a Glue catalog, and - does not apply to usage with a Hive metastore service. - - ``false`` - * - ``delta.enable-non-concurrent-writes`` - - Enable :ref:`write support ` for all - supported file systems. Specifically, take note of the warning about - concurrency and checkpoints. - - ``false`` - * - ``delta.default-checkpoint-writing-interval`` - - Default integer count to write transaction log checkpoint entries. If - the value is set to N, then checkpoints are written after every Nth - statement performing table writes. The value can be overridden for a - specific table with the ``checkpoint_interval`` table property. - - ``10`` - * - ``delta.hive-catalog-name`` - - Name of the catalog to which ``SELECT`` queries are redirected when a - Hive table is detected. - - - * - ``delta.checkpoint-row-statistics-writing.enabled`` - - Enable writing row statistics to checkpoint files. - - ``true`` - * - ``delta.dynamic-filtering.wait-timeout`` - - Duration to wait for completion of :doc:`dynamic filtering - ` during split generation. - The equivalent catalog session property is - ``dynamic_filtering_wait_timeout``. - - - * - ``delta.table-statistics-enabled`` - - Enables :ref:`Table statistics ` for - performance improvements. The equivalent catalog session property - is ``statistics_enabled``. - - ``true`` - * - ``delta.extended-statistics.enabled`` - - Enable statistics collection with :doc:`/sql/analyze` and - use of extended statistics. The equivalent catalog session property - is ``extended_statistics_enabled``. - - ``true`` - * - ``delta.extended-statistics.collect-on-write`` - - Enable collection of extended statistics for write operations. - The equivalent catalog session property is - ``extended_statistics_collect_on_write``. - - ``true`` - * - ``delta.per-transaction-metastore-cache-maximum-size`` - - Maximum number of metastore data objects per transaction in - the Hive metastore cache. - - ``1000`` - * - ``delta.delete-schema-locations-fallback`` - - Whether schema locations are deleted when Trino can't - determine whether they contain external files. - - ``false`` - * - ``delta.parquet.time-zone`` - - Time zone for Parquet read and write. - - JVM default - * - ``delta.target-max-file-size`` - - Target maximum size of written files; the actual size could be larger. - The equivalent catalog session property is ``target_max_file_size``. - - ``1GB`` - * - ``delta.unique-table-location`` - - Use randomized, unique table locations. - - ``true`` - * - ``delta.register-table-procedure.enabled`` - - Enable to allow users to call the ``register_table`` procedure. - - ``false`` - * - ``delta.vacuum.min-retention`` - - Minimum retention threshold for the files taken into account - for removal by the :ref:`VACUUM` procedure. - The equivalent catalog session property is - ``vacuum_min_retention``. - - ``7 DAYS`` -``` +:::{list-table} Delta Lake configuration properties +:widths: 30, 55, 15 +:header-rows: 1 + +* - Property name + - Description + - Default +* - `delta.metadata.cache-ttl` + - Frequency of checks for metadata updates equivalent to transactions to + update the metadata cache specified in [](prop-type-duration). + - `5m` +* - `delta.metadata.cache-size` + - The maximum number of Delta table metadata entries to cache. + - `1000` +* - `delta.metadata.live-files.cache-size` + - Amount of memory allocated for caching information about files. Must be + specified in [](prop-type-data-size) values such as `64MB`. Default is + calculated to 10% of the maximum memory allocated to the JVM. + - +* - `delta.metadata.live-files.cache-ttl` + - Caching duration for active files that correspond to the Delta Lake tables. + - `30m` +* - `delta.compression-codec` + - The compression codec to be used when writing new data files. Possible + values are: + + * `NONE` + * `SNAPPY` + * `ZSTD` + * `GZIP` + + The equivalent catalog session property is `compression_codec`. + - `SNAPPY` +* - `delta.max-partitions-per-writer` + - Maximum number of partitions per writer. + - `100` +* - `delta.hide-non-delta-lake-tables` + - Hide information about tables that are not managed by Delta Lake. Hiding + only applies to tables with the metadata managed in a Glue catalog, and does + not apply to usage with a Hive metastore service. + - `false` +* - `delta.enable-non-concurrent-writes` + - Enable [write support](delta-lake-data-management) for all supported file + systems. Specifically, take note of the warning about concurrency and + checkpoints. + - `false` +* - `delta.default-checkpoint-writing-interval` + - Default integer count to write transaction log checkpoint entries. If the + value is set to N, then checkpoints are written after every Nth statement + performing table writes. The value can be overridden for a specific table + with the `checkpoint_interval` table property. + - `10` +* - `delta.hive-catalog-name` + - Name of the catalog to which `SELECT` queries are redirected when a + Hive table is detected. + - +* - `delta.checkpoint-row-statistics-writing.enabled` + - Enable writing row statistics to checkpoint files. + - `true` +* - `delta.dynamic-filtering.wait-timeout` + - Duration to wait for completion of [dynamic + filtering](/admin/dynamic-filtering) during split generation. The equivalent + catalog session property is `dynamic_filtering_wait_timeout`. + - +* - `delta.table-statistics-enabled` + - Enables [Table statistics](delta-lake-table-statistics) for performance + improvements. The equivalent catalog session property is + `statistics_enabled`. + - `true` +* - `delta.extended-statistics.enabled` + - Enable statistics collection with [](/sql/analyze) and use of extended + statistics. The equivalent catalog session property is + `extended_statistics_enabled`. + - `true` +* - `delta.extended-statistics.collect-on-write` + - Enable collection of extended statistics for write operations. The + equivalent catalog session property is + `extended_statistics_collect_on_write`. + - `true` +* - `delta.per-transaction-metastore-cache-maximum-size` + - Maximum number of metastore data objects per transaction in the Hive + metastore cache. + - `1000` +* - `delta.delete-schema-locations-fallback` + - Whether schema locations are deleted when Trino can't determine whether they + contain external files. + - `false` +* - `delta.parquet.time-zone` + - Time zone for Parquet read and write. + - JVM default +* - `delta.target-max-file-size` + - Target maximum size of written files; the actual size could be larger. The + equivalent catalog session property is `target_max_file_size`. + - `1GB` +* - `delta.unique-table-location` + - Use randomized, unique table locations. + - `true` +* - `delta.register-table-procedure.enabled` + - Enable to allow users to call the `register_table` procedure. + - `false` +* - `delta.vacuum.min-retention` + - Minimum retention threshold for the files taken into account for removal by + the [VACUUM](delta-lake-vacuum) procedure. The equivalent catalog session + property is `vacuum_min_retention`. + - `7 DAYS` +::: ### Catalog session properties The following table describes {ref}`catalog session properties ` supported by the Delta Lake connector: -```{eval-rst} -.. list-table:: Catalog session properties - :widths: 40, 60, 20 - :header-rows: 1 - - * - Property name - - Description - - Default - * - ``parquet_max_read_block_size`` - - The maximum block size used when reading Parquet files. - - ``16MB`` - * - ``parquet_writer_block_size`` - - The maximum block size created by the Parquet writer. - - ``128MB`` - * - ``parquet_writer_page_size`` - - The maximum page size created by the Parquet writer. - - ``1MB`` - * - ``parquet_writer_batch_size`` - - Maximum number of rows processed by the Parquet writer in a batch. - - ``10000`` - * - ``projection_pushdown_enabled`` - - Read only projected fields from row columns while performing ``SELECT`` queries - - ``true`` -``` +:::{list-table} Catalog session properties +:widths: 40, 60, 20 +:header-rows: 1 + +* - Property name + - Description + - Default +* - `parquet_max_read_block_size` + - The maximum block size used when reading Parquet files. + - `16MB` +* - `parquet_writer_block_size` + - The maximum block size created by the Parquet writer. + - `128MB` +* - `parquet_writer_page_size` + - The maximum page size created by the Parquet writer. + - `1MB` +* - `parquet_writer_batch_size` + - Maximum number of rows processed by the Parquet writer in a batch. + - `10000` +* - `projection_pushdown_enabled` + - Read only projected fields from row columns while performing `SELECT` + queries. + - `true` +::: (delta-lake-type-mapping)= @@ -225,46 +221,45 @@ specification. The connector maps Delta Lake types to the corresponding Trino types following this table: -```{eval-rst} -.. list-table:: Delta Lake to Trino type mapping - :widths: 40, 60 - :header-rows: 1 - - * - Delta Lake type - - Trino type - * - ``BOOLEAN`` - - ``BOOLEAN`` - * - ``INTEGER`` - - ``INTEGER`` - * - ``BYTE`` - - ``TINYINT`` - * - ``SHORT`` - - ``SMALLINT`` - * - ``LONG`` - - ``BIGINT`` - * - ``FLOAT`` - - ``REAL`` - * - ``DOUBLE`` - - ``DOUBLE`` - * - ``DECIMAL(p,s)`` - - ``DECIMAL(p,s)`` - * - ``STRING`` - - ``VARCHAR`` - * - ``BINARY`` - - ``VARBINARY`` - * - ``DATE`` - - ``DATE`` - * - ``TIMESTAMPNTZ`` (``TIMESTAMP_NTZ``) - - ``TIMESTAMP(6)`` - * - ``TIMESTAMP`` - - ``TIMESTAMP(3) WITH TIME ZONE`` - * - ``ARRAY`` - - ``ARRAY`` - * - ``MAP`` - - ``MAP`` - * - ``STRUCT(...)`` - - ``ROW(...)`` -``` +:::{list-table} Delta Lake to Trino type mapping +:widths: 40, 60 +:header-rows: 1 + +* - Delta Lake type + - Trino type +* - `BOOLEAN` + - `BOOLEAN` +* - `INTEGER` + - `INTEGER` +* - `BYTE` + - `TINYINT` +* - `SHORT` + - `SMALLINT` +* - `LONG` + - `BIGINT` +* - `FLOAT` + - `REAL` +* - `DOUBLE` + - `DOUBLE` +* - `DECIMAL(p,s)` + - `DECIMAL(p,s)` +* - `STRING` + - `VARCHAR` +* - `BINARY` + - `VARBINARY` +* - `DATE` + - `DATE` +* - `TIMESTAMPNTZ` (`TIMESTAMP_NTZ`) + - `TIMESTAMP(6)` +* - `TIMESTAMP` + - `TIMESTAMP(3) WITH TIME ZONE` +* - `ARRAY` + - `ARRAY` +* - `MAP` + - `MAP` +* - `STRUCT(...)` + - `ROW(...)` +::: No other types are supported. @@ -273,46 +268,45 @@ No other types are supported. The connector maps Trino types to the corresponding Delta Lake types following this table: -```{eval-rst} -.. list-table:: Trino to Delta Lake type mapping - :widths: 60, 40 - :header-rows: 1 - - * - Trino type - - Delta Lake type - * - ``BOOLEAN`` - - ``BOOLEAN`` - * - ``INTEGER`` - - ``INTEGER`` - * - ``TINYINT`` - - ``BYTE`` - * - ``SMALLINT`` - - ``SHORT`` - * - ``BIGINT`` - - ``LONG`` - * - ``REAL`` - - ``FLOAT`` - * - ``DOUBLE`` - - ``DOUBLE`` - * - ``DECIMAL(p,s)`` - - ``DECIMAL(p,s)`` - * - ``VARCHAR`` - - ``STRING`` - * - ``VARBINARY`` - - ``BINARY`` - * - ``DATE`` - - ``DATE`` - * - ``TIMESTAMP`` - - ``TIMESTAMPNTZ`` (``TIMESTAMP_NTZ``) - * - ``TIMESTAMP(3) WITH TIME ZONE`` - - ``TIMESTAMP`` - * - ``ARRAY`` - - ``ARRAY`` - * - ``MAP`` - - ``MAP`` - * - ``ROW(...)`` - - ``STRUCT(...)`` -``` +:::{list-table} Trino to Delta Lake type mapping +:widths: 60, 40 +:header-rows: 1 + +* - Trino type + - Delta Lake type +* - `BOOLEAN` + - `BOOLEAN` +* - `INTEGER` + - `INTEGER` +* - `TINYINT` + - `BYTE` +* - `SMALLINT` + - `SHORT` +* - `BIGINT` + - `LONG` +* - `REAL` + - `FLOAT` +* - `DOUBLE` + - `DOUBLE` +* - `DECIMAL(p,s)` + - `DECIMAL(p,s)` +* - `VARCHAR` + - `STRING` +* - `VARBINARY` + - `BINARY` +* - `DATE` + - `DATE` +* - `TIMESTAMP` + - `TIMESTAMPNTZ` (`TIMESTAMP_NTZ`) +* - `TIMESTAMP(3) WITH TIME ZONE` + - `TIMESTAMP` +* - `ARRAY` + - `ARRAY` +* - `MAP` + - `MAP` +* - `ROW(...)` + - `STRUCT(...)` +::: No other types are supported. @@ -330,29 +324,26 @@ Enable authorization checks for the connector by setting the `delta.security` property in the catalog properties file. This property must be one of the security values in the following table: -```{eval-rst} -.. list-table:: Delta Lake security values - :widths: 30, 60 - :header-rows: 1 - - * - Property value - - Description - * - ``ALLOW_ALL`` (default value) - - No authorization checks are enforced. - * - ``SYSTEM`` - - The connector relies on system-level access control. - * - ``READ_ONLY`` - - Operations that read data or metadata, such as :doc:`/sql/select` are - permitted. No operations that write data or metadata, such as - :doc:`/sql/create-table`, :doc:`/sql/insert`, or :doc:`/sql/delete` are - allowed. - * - ``FILE`` - - Authorization checks are enforced using a catalog-level access control - configuration file whose path is specified in the ``security.config-file`` - catalog configuration property. See - :ref:`catalog-file-based-access-control` for information on the - authorization configuration file. -``` +:::{list-table} Delta Lake security values +:widths: 30, 60 +:header-rows: 1 + +* - Property value + - Description +* - `ALLOW_ALL` (default value) + - No authorization checks are enforced. +* - `SYSTEM` + - The connector relies on system-level access control. +* - `READ_ONLY` + - Operations that read data or metadata, such as [](/sql/select) are + permitted. No operations that write data or metadata, such as + [](/sql/create-table), [](/sql/insert), or [](/sql/delete) are allowed. +* - `FILE` + - Authorization checks are enforced using a catalog-level access control + configuration file whose path is specified in the `security.config-file` + catalog configuration property. See [](catalog-file-based-access-control) + for information on the authorization configuration file. ::: +::: (delta-lake-sql-support)= @@ -580,30 +571,29 @@ one of the following conditions: The following table properties are available for use: -```{eval-rst} -.. list-table:: Delta Lake table properties - :widths: 40, 60 - :header-rows: 1 - - * - Property name - - Description - * - ``location`` - - File system location URI for the table. - * - ``partitioned_by`` - - Set partition columns. - * - ``checkpoint_interval`` - - Set the checkpoint interval in number of table writes. - * - ``change_data_feed_enabled`` - - Enables storing change data feed entries. - * - ``column_mapping_mode`` - - Column mapping mode. Possible values are: - - * ``ID`` - * ``NAME`` - * ``NONE`` - - Defaults to ``NONE``. -``` +:::{list-table} Delta Lake table properties +:widths: 40, 60 +:header-rows: 1 + +* - Property name + - Description +* - `location` + - File system location URI for the table. +* - `partitioned_by` + - Set partition columns. +* - `checkpoint_interval` + - Set the checkpoint interval in number of table writes. +* - `change_data_feed_enabled` + - Enables storing change data feed entries. +* - `column_mapping_mode` + - Column mapping mode. Possible values are: + + * `ID` + * `NAME` + * `NONE` + + Defaults to `NONE`. +::: The following example uses all available table properties: @@ -652,45 +642,44 @@ SELECT * FROM "test_table$history" The output of the query has the following history columns: -```{eval-rst} -.. list-table:: History columns - :widths: 30, 30, 40 - :header-rows: 1 - - * - Name - - Type - - Description - * - ``version`` - - ``BIGINT`` - - The version of the table corresponding to the operation - * - ``timestamp`` - - ``TIMESTAMP(3) WITH TIME ZONE`` - - The time when the table version became active - * - ``user_id`` - - ``VARCHAR`` - - The identifier for the user which performed the operation - * - ``user_name`` - - ``VARCHAR`` - - The username for the user which performed the operation - * - ``operation`` - - ``VARCHAR`` - - The name of the operation performed on the table - * - ``operation_parameters`` - - ``map(VARCHAR, VARCHAR)`` - - Parameters of the operation - * - ``cluster_id`` - - ``VARCHAR`` - - The ID of the cluster which ran the operation - * - ``read_version`` - - ``BIGINT`` - - The version of the table which was read in order to perform the operation - * - ``isolation_level`` - - ``VARCHAR`` - - The level of isolation used to perform the operation - * - ``is_blind_append`` - - ``BOOLEAN`` - - Whether or not the operation appended data -``` +:::{list-table} History columns +:widths: 30, 30, 40 +:header-rows: 1 + +* - Name + - Type + - Description +* - `version` + - `BIGINT` + - The version of the table corresponding to the operation +* - `timestamp` + - `TIMESTAMP(3) WITH TIME ZONE` + - The time when the table version became active +* - `user_id` + - `VARCHAR` + - The identifier for the user which performed the operation +* - `user_name` + - `VARCHAR` + - The username for the user which performed the operation +* - `operation` + - `VARCHAR` + - The name of the operation performed on the table +* - `operation_parameters` + - `map(VARCHAR, VARCHAR)` + - Parameters of the operation +* - `cluster_id` + - `VARCHAR` + - The ID of the cluster which ran the operation +* - `read_version` + - `BIGINT` + - The version of the table which was read in order to perform the operation +* - `isolation_level` + - `VARCHAR` + - The level of isolation used to perform the operation +* - `is_blind_append` + - `BOOLEAN` + - Whether or not the operation appended data +::: ##### `$properties` table @@ -1035,62 +1024,60 @@ you use them only to address non-trivial performance issues, and that you keep a backup of the original values if you change them. ::: -```{eval-rst} -.. list-table:: Delta Lake performance tuning configuration properties - :widths: 30, 50, 20 - :header-rows: 1 - - * - Property name - - Description - - Default - * - ``delta.domain-compaction-threshold`` - - Minimum size of query predicates above which Trino compacts the - predicates. Pushing a large list of predicates down to the data source - can compromise performance. For optimization in that situation, Trino - can compact the large predicates. If necessary, adjust the threshold to - ensure a balance between performance and predicate pushdown. - - ``1000`` - * - ``delta.max-outstanding-splits`` - - The target number of buffered splits for each table scan in a query, - before the scheduler tries to pause. - - ``1000`` - * - ``delta.max-splits-per-second`` - - Sets the maximum number of splits used per second to access underlying - storage. Reduce this number if your limit is routinely exceeded, based - on your filesystem limits. This is set to the absolute maximum value, - which results in Trino maximizing the parallelization of data access - by default. Attempting to set it higher results in Trino not being - able to start. - - ``Integer.MAX_VALUE`` - * - ``delta.max-initial-splits`` - - For each query, the coordinator assigns file sections to read first - at the ``initial-split-size`` until the ``max-initial-splits`` is - reached. Then it starts issuing reads of the ``max-split-size`` size. - - ``200`` - * - ``delta.max-initial-split-size`` - - Sets the initial :ref:`prop-type-data-size` for a single read section - assigned to a worker until ``max-initial-splits`` have been processed. - You can also use the corresponding catalog session property - ``.max_initial_split_size``. - - ``32MB`` - * - ``delta.max-split-size`` - - Sets the largest :ref:`prop-type-data-size` for a single read section - assigned to a worker after ``max-initial-splits`` have been processed. - You can also use the corresponding catalog session property - ``.max_split_size``. - - ``64MB`` - * - ``delta.minimum-assigned-split-weight`` - - A decimal value in the range (0, 1] used as a minimum for weights - assigned to each split. A low value might improve performance on tables - with small files. A higher value might improve performance for queries - with highly skewed aggregations or joins. - - ``0.05`` - * - ``delta.projection-pushdown-enabled`` - - Read only projected fields from row columns while performing ``SELECT`` queries - - ``true`` - * - ``delta.query-partition-filter-required`` - - Set to ``true`` to force a query to use a partition filter. You can use - the ``query_partition_filter_required`` catalog session property for - temporary, catalog specific use. - - ``false`` -``` +:::{list-table} Delta Lake performance tuning configuration properties +:widths: 30, 50, 20 +:header-rows: 1 + +* - Property name + - Description + - Default +* - `delta.domain-compaction-threshold` + - Minimum size of query predicates above which Trino compacts the predicates. + Pushing a large list of predicates down to the data source can compromise + performance. For optimization in that situation, Trino can compact the large + predicates. If necessary, adjust the threshold to ensure a balance between + performance and predicate pushdown. + - `1000` +* - `delta.max-outstanding-splits` + - The target number of buffered splits for each table scan in a query, before + the scheduler tries to pause. + - `1000` +* - `delta.max-splits-per-second` + - Sets the maximum number of splits used per second to access underlying + storage. Reduce this number if your limit is routinely exceeded, based on + your filesystem limits. This is set to the absolute maximum value, which + results in Trino maximizing the parallelization of data access by default. + Attempting to set it higher results in Trino not being able to start. + - `Integer.MAX_VALUE` +* - `delta.max-initial-splits` + - For each query, the coordinator assigns file sections to read first at the + `initial-split-size` until the `max-initial-splits` is reached. Then it + starts issuing reads of the `max-split-size` size. + - `200` +* - `delta.max-initial-split-size` + - Sets the initial :ref:`prop-type-data-size` for a single read section + assigned to a worker until `max-initial-splits` have been processed. You can + also use the corresponding catalog session property + `.max_initial_split_size`. + - `32MB` +* - `delta.max-split-size` + - Sets the largest :ref:`prop-type-data-size` for a single read section + assigned to a worker after `max-initial-splits` have been processed. You can + also use the corresponding catalog session property + `.max_split_size`. + - `64MB` +* - `delta.minimum-assigned-split-weight` + - A decimal value in the range (0, 1] used as a minimum for weights assigned + to each split. A low value might improve performance on tables with small + files. A higher value might improve performance for queries with highly + skewed aggregations or joins. + - `0.05` +* - `delta.projection-pushdown-enabled` + - Read only projected fields from row columns while performing `SELECT` queries + - `true` +* - `delta.query-partition-filter-required` + - Set to `true` to force a query to use a partition filter. You can use the + `query_partition_filter_required` catalog session property for temporary, + catalog specific use. + - `false` +::: diff --git a/docs/src/main/sphinx/connector/druid.md b/docs/src/main/sphinx/connector/druid.md index a01f3b612611..8557d6f1d040 100644 --- a/docs/src/main/sphinx/connector/druid.md +++ b/docs/src/main/sphinx/connector/druid.md @@ -83,30 +83,29 @@ connector {ref}`modifies some types ` when reading data. The connector maps Druid types to the corresponding Trino types according to the following table: -```{eval-rst} -.. list-table:: Druid type to Trino type mapping - :widths: 30, 30, 50 - :header-rows: 1 - - * - Druid type - - Trino type - - Notes - * - ``STRING`` - - ``VARCHAR`` - - - * - ``FLOAT`` - - ``REAL`` - - - * - ``DOUBLE`` - - ``DOUBLE`` - - - * - ``LONG`` - - ``BIGINT`` - - Except for the special ``_time`` column, which is mapped to ``TIMESTAMP``. - * - ``TIMESTAMP`` - - ``TIMESTAMP`` - - Only applicable to the special ``_time`` column. -``` +:::{list-table} Druid type to Trino type mapping +:widths: 30, 30, 50 +:header-rows: 1 + +* - Druid type + - Trino type + - Notes +* - `STRING` + - `VARCHAR` + - +* - `FLOAT` + - `REAL` + - +* - `DOUBLE` + - `DOUBLE` + - +* - `LONG` + - `BIGINT` + - Except for the special `_time` column, which is mapped to `TIMESTAMP`. +* - `TIMESTAMP` + - `TIMESTAMP` + - Only applicable to the special `_time` column. +::: No other data types are supported. diff --git a/docs/src/main/sphinx/connector/elasticsearch.md b/docs/src/main/sphinx/connector/elasticsearch.md index 5ec58563f055..337c638b42ef 100644 --- a/docs/src/main/sphinx/connector/elasticsearch.md +++ b/docs/src/main/sphinx/connector/elasticsearch.md @@ -26,61 +26,60 @@ elasticsearch.default-schema-name=default ### Configuration properties -```{eval-rst} -.. list-table:: Elasticsearch configuration properties - :widths: 35, 55, 10 - :header-rows: 1 - - * - Property name - - Description - - Default - * - ``elasticsearch.host`` - - The comma-separated list of host names for the Elasticsearch node to - connect to. This property is required. - - - * - ``elasticsearch.port`` - - Port of the Elasticsearch node to connect to. - - ``9200`` - * - ``elasticsearch.default-schema-name`` - - The schema that contains all tables defined without a qualifying schema - name. - - ``default`` - * - ``elasticsearch.scroll-size`` - - Sets the maximum number of hits that can be returned with each - Elasticsearch scroll request. - - ``1000`` - * - ``elasticsearch.scroll-timeout`` - - Amount of time Elasticsearch keeps the - `search context `_ - alive for scroll requests. - - ``1m`` - * - ``elasticsearch.request-timeout`` - - Timeout value for all Elasticsearch requests. - - ``10s`` - * - ``elasticsearch.connect-timeout`` - - Timeout value for all Elasticsearch connection attempts. - - ``1s`` - * - ``elasticsearch.backoff-init-delay`` - - The minimum duration between backpressure retry attempts for a single - request to Elasticsearch. Setting it too low might overwhelm an already - struggling ES cluster. - - ``500ms`` - * - ``elasticsearch.backoff-max-delay`` - - The maximum duration between backpressure retry attempts for a single - request to Elasticsearch. - - ``20s`` - * - ``elasticsearch.max-retry-time`` - - The maximum duration across all retry attempts for a single request to - Elasticsearch. - - ``20s`` - * - ``elasticsearch.node-refresh-interval`` - - How often the list of available Elasticsearch nodes is refreshed. - - ``1m`` - * - ``elasticsearch.ignore-publish-address`` - - Disables using the address published by Elasticsearch to connect for - queries. - - -``` +:::{list-table} Elasticsearch configuration properties +:widths: 35, 55, 10 +:header-rows: 1 + +* - Property name + - Description + - Default +* - `elasticsearch.host` + - The comma-separated list of host names for the Elasticsearch node to connect + to. This property is required. + - +* - `elasticsearch.port` + - Port of the Elasticsearch node to connect to. + - `9200` +* - `elasticsearch.default-schema-name` + - The schema that contains all tables defined without a qualifying schema + name. + - `default` +* - `elasticsearch.scroll-size` + - Sets the maximum number of hits that can be returned with each Elasticsearch + scroll request. + - `1000` +* - `elasticsearch.scroll-timeout` + - Amount of time Elasticsearch keeps the + [search context](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-scroll.html#scroll-search-context) + alive for scroll requests. + - `1m` +* - `elasticsearch.request-timeout` + - Timeout value for all Elasticsearch requests. + - `10s` +* - `elasticsearch.connect-timeout` + - Timeout value for all Elasticsearch connection attempts. + - `1s` +* - `elasticsearch.backoff-init-delay` + - The minimum duration between backpressure retry attempts for a single + request to Elasticsearch. Setting it too low might overwhelm an already + struggling ES cluster. + - `500ms` +* - `elasticsearch.backoff-max-delay` + - The maximum duration between backpressure retry attempts for a single + request to Elasticsearch. + - `20s` +* - `elasticsearch.max-retry-time` + - The maximum duration across all retry attempts for a single request to + Elasticsearch. + - `20s` +* - `elasticsearch.node-refresh-interval` + - How often the list of available Elasticsearch nodes is refreshed. + - `1m` +* - `elasticsearch.ignore-publish-address` + - Disables using the address published by Elasticsearch to connect for + queries. + - +::: ## TLS security @@ -93,28 +92,27 @@ supports key stores and trust stores in PEM or Java Key Store (JKS) format. The allowed configuration values are: -```{eval-rst} -.. list-table:: TLS Security Properties - :widths: 40, 60 - :header-rows: 1 - - * - Property name - - Description - * - ``elasticsearch.tls.enabled`` - - Enables TLS security. - * - ``elasticsearch.tls.keystore-path`` - - The path to the :doc:`PEM ` or - :doc:`JKS ` key store. - * - ``elasticsearch.tls.truststore-path`` - - The path to :doc:`PEM ` or - :doc:`JKS ` trust store. - * - ``elasticsearch.tls.keystore-password`` - - The key password for the key store specified by - ``elasticsearch.tls.keystore-path``. - * - ``elasticsearch.tls.truststore-password`` - - The key password for the trust store specified by - ``elasticsearch.tls.truststore-path``. -``` +:::{list-table} TLS Security Properties +:widths: 40, 60 +:header-rows: 1 + +* - Property name + - Description +* - `elasticsearch.tls.enabled` + - Enables TLS security. +* - `elasticsearch.tls.keystore-path` + - The path to the [PEM](/security/inspect-pem) or [JKS](/security/inspect-jks) + key store. +* - `elasticsearch.tls.truststore-path` + - The path to [PEM](/security/inspect-pem) or [JKS](/security/inspect-jks) + trust store. +* - `elasticsearch.tls.keystore-password` + - The key password for the key store specified by + `elasticsearch.tls.keystore-path`. +* - `elasticsearch.tls.truststore-password` + - The key password for the trust store specified by + `elasticsearch.tls.truststore-path`. +::: (elasticesearch-type-mapping)= @@ -128,48 +126,47 @@ connector {ref}`maps some types ` when reading data. The connector maps Elasticsearch types to the corresponding Trino types according to the following table: -```{eval-rst} -.. list-table:: Elasticsearch type to Trino type mapping - :widths: 30, 30, 50 - :header-rows: 1 - - * - Elasticsearch type - - Trino type - - Notes - * - ``BOOLEAN`` - - ``BOOLEAN`` - - - * - ``DOUBLE`` - - ``DOUBLE`` - - - * - ``FLOAT`` - - ``REAL`` - - - * - ``BYTE`` - - ``TINYINT`` - - - * - ``SHORT`` - - ``SMALLINT`` - - - * - ``INTEGER`` - - ``INTEGER`` - - - * - ``LONG`` - - ``BIGINT`` - - - * - ``KEYWORD`` - - ``VARCHAR`` - - - * - ``TEXT`` - - ``VARCHAR`` - - - * - ``DATE`` - - ``TIMESTAMP`` - - For more information, see :ref:`elasticsearch-date-types`. - * - ``IPADDRESS`` - - ``IP`` - - -``` +:::{list-table} Elasticsearch type to Trino type mapping +:widths: 30, 30, 50 +:header-rows: 1 + +* - Elasticsearch type + - Trino type + - Notes +* - `BOOLEAN` + - `BOOLEAN` + - +* - `DOUBLE` + - `DOUBLE` + - +* - `FLOAT` + - `REAL` + - +* - `BYTE` + - `TINYINT` + - +* - `SHORT` + - `SMALLINT` + - +* - `INTEGER` + - `INTEGER` + - +* - `LONG` + - `BIGINT` + - +* - `KEYWORD` + - `VARCHAR` + - +* - `TEXT` + - `VARCHAR` + - +* - `DATE` + - `TIMESTAMP` + - For more information, see [](elasticsearch-date-types). +* - `IPADDRESS` + - `IP` + - +::: No other types are supported. @@ -401,8 +398,7 @@ This can be useful for accessing native features which are not available in Trino or for improving query performance in situations where running a query natively may be faster. -```{eval-rst} -.. include:: query-passthrough-warning.fragment +```{include} query-passthrough-warning.fragment ``` The `raw_query` function requires three parameters: diff --git a/docs/src/main/sphinx/connector/googlesheets.md b/docs/src/main/sphinx/connector/googlesheets.md index 8615d6d58637..6be261c1be86 100644 --- a/docs/src/main/sphinx/connector/googlesheets.md +++ b/docs/src/main/sphinx/connector/googlesheets.md @@ -121,16 +121,15 @@ connector {ref}`modifies some types ` when reading data. The connector maps Google Sheets types to the corresponding Trino types following this table: -```{eval-rst} -.. list-table:: Google Sheets type to Trino type mapping - :widths: 30, 20 - :header-rows: 1 - - * - Google Sheets type - - Trino type - * - ``TEXT`` - - ``VARCHAR`` -``` +:::{list-table} Google Sheets type to Trino type mapping +:widths: 30, 20 +:header-rows: 1 + +* - Google Sheets type + - Trino type +* - `TEXT` + - `VARCHAR` +::: No other types are supported. diff --git a/docs/src/main/sphinx/connector/hive-azure.md b/docs/src/main/sphinx/connector/hive-azure.md index 048d90fcf613..dd5bd801a576 100644 --- a/docs/src/main/sphinx/connector/hive-azure.md +++ b/docs/src/main/sphinx/connector/hive-azure.md @@ -28,33 +28,31 @@ To connect to ABFS storage, you may either use the storage account's access key, or a service principal. Do not use both sets of properties at the same time. -```{eval-rst} -.. list-table:: ABFS Access Key - :widths: 30, 70 - :header-rows: 1 - - * - Property name - - Description - * - ``hive.azure.abfs-storage-account`` - - The name of the ADLS Gen2 storage account - * - ``hive.azure.abfs-access-key`` - - The decrypted access key for the ADLS Gen2 storage account -``` +:::{list-table} ABFS Access Key +:widths: 30, 70 +:header-rows: 1 + +* - Property name + - Description +* - `hive.azure.abfs-storage-account` + - The name of the ADLS Gen2 storage account +* - `hive.azure.abfs-access-key` + - The decrypted access key for the ADLS Gen2 storage account +::: -```{eval-rst} -.. list-table:: ABFS Service Principal OAuth - :widths: 30, 70 - :header-rows: 1 - - * - Property name - - Description - * - ``hive.azure.abfs.oauth.endpoint`` - - The service principal / application's OAuth 2.0 token endpoint (v1). - * - ``hive.azure.abfs.oauth.client-id`` - - The service principal's client/application ID. - * - ``hive.azure.abfs.oauth.secret`` - - A client secret for the service principal. -``` +:::{list-table} ABFS Service Principal OAuth +:widths: 30, 70 +:header-rows: 1 + +* - Property name + - Description +* - `hive.azure.abfs.oauth.endpoint` + - The service principal / application's OAuth 2.0 token endpoint (v1). +* - `hive.azure.abfs.oauth.client-id` + - The service principal's client/application ID. +* - `hive.azure.abfs.oauth.secret` + - A client secret for the service principal. +::: When using a service principal, it must have the Storage Blob Data Owner, Contributor, or Reader role on the storage account you are using, depending on @@ -75,39 +73,37 @@ because you won't able to retrieve the key later. Refer to the Azure for details. ::: -```{eval-rst} -.. list-table:: ADLS properties - :widths: 30, 70 - :header-rows: 1 - - * - Property name - - Description - * - ``hive.azure.adl-client-id`` - - Client (Application) ID from the App Registrations for your storage - account - * - ``hive.azure.adl-credential`` - - Value of the new client (application) secret created - * - ``hive.azure.adl-refresh-url`` - - OAuth 2.0 token endpoint url - * - ``hive.azure.adl-proxy-host`` - - Proxy host and port in ``host:port`` format. Use this property to connect - to an ADLS endpoint via a SOCKS proxy. -``` +:::{list-table} ADLS properties +:widths: 30, 70 +:header-rows: 1 + +* - Property name + - Description +* - `hive.azure.adl-client-id` + - Client (Application) ID from the App Registrations for your storage + account +* - `hive.azure.adl-credential` + - Value of the new client (application) secret created +* - `hive.azure.adl-refresh-url` + - OAuth 2.0 token endpoint url +* - `hive.azure.adl-proxy-host` + - Proxy host and port in `host:port` format. Use this property to connect + to an ADLS endpoint via a SOCKS proxy. +::: ### WASB storage (legacy) -```{eval-rst} -.. list-table:: WASB properties - :widths: 30, 70 - :header-rows: 1 - - * - Property name - - Description - * - ``hive.azure.wasb-storage-account`` - - Storage account name of Azure Blob Storage - * - ``hive.azure.wasb-access-key`` - - The decrypted access key for the Azure Blob Storage -``` +:::{list-table} WASB properties +:widths: 30, 70 +:header-rows: 1 + +* - Property name + - Description +* - `hive.azure.wasb-storage-account` + - Storage account name of Azure Blob Storage +* - `hive.azure.wasb-access-key` + - The decrypted access key for the Azure Blob Storage +::: (hive-azure-advanced-config)= diff --git a/docs/src/main/sphinx/connector/hive-caching.md b/docs/src/main/sphinx/connector/hive-caching.md index edc3cf3f5048..c88586900e99 100644 --- a/docs/src/main/sphinx/connector/hive-caching.md +++ b/docs/src/main/sphinx/connector/hive-caching.md @@ -89,42 +89,41 @@ transfer, by default 8898 and 8899, need to be available. To use caching on multiple catalogs, you need to configure different caching directories and different BookKeeper and data-transfer ports. -```{eval-rst} -.. list-table:: **Cache Configuration Parameters** - :widths: 25, 63, 12 - :header-rows: 1 - - * - Property - - Description - - Default - * - ``hive.cache.enabled`` - - Toggle to enable or disable caching - - ``false`` - * - ``hive.cache.location`` - - Required directory location to use for the cache storage on each worker. - Separate multiple directories, which can be mount points for separate - drives, with commas. More tips can be found in the :ref:`recommendations - `. Example: - ``hive.cache.location=/var/lib/trino/cache1,/var/lib/trino/cache2`` - - - * - ``hive.cache.data-transfer-port`` - - The TCP/IP port used to transfer data managed by the cache. - - ``8898`` - * - ``hive.cache.bookkeeper-port`` - - The TCP/IP port used by the BookKeeper managing the cache. - - ``8899`` - * - ``hive.cache.read-mode`` - - Operational mode for the cache as described earlier in the architecture - section. ``async`` and ``read-through`` are the supported modes. - - ``async`` - * - ``hive.cache.ttl`` - - Time to live for objects in the cache. Objects, which have not been - requested for the TTL value, are removed from the cache. - - ``7d`` - * - ``hive.cache.disk-usage-percentage`` - - Percentage of disk space used for cached data. - - 80 -``` +:::{list-table} Cache Configuration Parameters +:widths: 25, 63, 12 +:header-rows: 1 + +* - Property + - Description + - Default +* - `hive.cache.enabled` + - Toggle to enable or disable caching + - `false` +* - `hive.cache.location` + - Required directory location to use for the cache storage on each worker. + Separate multiple directories, which can be mount points for separate + drives, with commas. More tips can be found in the + [recommendations](hive-cache-recommendations). Example: + `hive.cache.location=/var/lib/trino/cache1,/var/lib/trino/cache2` + - +* - `hive.cache.data-transfer-port` + - The TCP/IP port used to transfer data managed by the cache. + - `8898` +* - `hive.cache.bookkeeper-port` + - The TCP/IP port used by the BookKeeper managing the cache. + - `8899` +* - `hive.cache.read-mode` + - Operational mode for the cache as described earlier in the architecture + section. `async` and `read-through` are the supported modes. + - `async` +* - `hive.cache.ttl` + - Time to live for objects in the cache. Objects, which have not been + requested for the TTL value, are removed from the cache. + - `7d` +* - `hive.cache.disk-usage-percentage` + - Percentage of disk space used for cached data. + - 80 +::: (hive-cache-recommendations)= diff --git a/docs/src/main/sphinx/connector/hive-gcs-tutorial.md b/docs/src/main/sphinx/connector/hive-gcs-tutorial.md index 3c5c3a9fa5a6..aeb2820103b8 100644 --- a/docs/src/main/sphinx/connector/hive-gcs-tutorial.md +++ b/docs/src/main/sphinx/connector/hive-gcs-tutorial.md @@ -24,19 +24,18 @@ The default root path used by the `gs:\\` prefix is set in the catalog by the contents of the specified key file, or the key file used to create the OAuth token. -```{eval-rst} -.. list-table:: Google Cloud Storage configuration properties - :widths: 35, 65 - :header-rows: 1 - - * - Property Name - - Description - * - ``hive.gcs.json-key-file-path`` - - JSON key file used to authenticate your Google Cloud service account - with Google Cloud Storage. - * - ``hive.gcs.use-access-token`` - - Use client-provided OAuth token to access Google Cloud Storage. -``` +:::{list-table} Google Cloud Storage configuration properties +:widths: 35, 65 +:header-rows: 1 + +* - Property Name + - Description +* - `hive.gcs.json-key-file-path` + - JSON key file used to authenticate your Google Cloud service account with + Google Cloud Storage. +* - `hive.gcs.use-access-token` + - Use client-provided OAuth token to access Google Cloud Storage. +::: The following uses the Delta Lake connector in an example of a minimal configuration file for an object storage catalog using a JSON key file: diff --git a/docs/src/main/sphinx/connector/hive-s3.md b/docs/src/main/sphinx/connector/hive-s3.md index 8cfdfb450de7..ba1ca51e4f89 100644 --- a/docs/src/main/sphinx/connector/hive-s3.md +++ b/docs/src/main/sphinx/connector/hive-s3.md @@ -12,108 +12,106 @@ Trino uses its own S3 filesystem for the URI prefixes ## S3 configuration properties -```{eval-rst} -.. list-table:: - :widths: 35, 65 - :header-rows: 1 - - * - Property name - - Description - * - ``hive.s3.aws-access-key`` - - Default AWS access key to use. - * - ``hive.s3.aws-secret-key`` - - Default AWS secret key to use. - * - ``hive.s3.iam-role`` - - IAM role to assume. - * - ``hive.s3.external-id`` - - External ID for the IAM role trust policy. - * - ``hive.s3.endpoint`` - - The S3 storage endpoint server. This can be used to connect to an - S3-compatible storage system instead of AWS. When using v4 signatures, - it is recommended to set this to the AWS region-specific endpoint - (e.g., ``http[s]://s3..amazonaws.com``). - * - ``hive.s3.region`` - - Optional property to force the S3 client to connect to the specified - region only. - * - ``hive.s3.storage-class`` - - The S3 storage class to use when writing the data. Currently only - ``STANDARD`` and ``INTELLIGENT_TIERING`` storage classes are supported. - Default storage class is ``STANDARD`` - * - ``hive.s3.signer-type`` - - Specify a different signer type for S3-compatible storage. - Example: ``S3SignerType`` for v2 signer type - * - ``hive.s3.signer-class`` - - Specify a different signer class for S3-compatible storage. - * - ``hive.s3.path-style-access`` - - Use path-style access for all requests to the S3-compatible storage. - This is for S3-compatible storage that doesn't support - virtual-hosted-style access, defaults to ``false``. - * - ``hive.s3.staging-directory`` - - Local staging directory for data written to S3. This defaults to the - Java temporary directory specified by the JVM system property - ``java.io.tmpdir``. - * - ``hive.s3.pin-client-to-current-region`` - - Pin S3 requests to the same region as the EC2 instance where Trino is - running, defaults to ``false``. - * - ``hive.s3.ssl.enabled`` - - Use HTTPS to communicate with the S3 API, defaults to ``true``. - * - ``hive.s3.sse.enabled`` - - Use S3 server-side encryption, defaults to ``false``. - * - ``hive.s3.sse.type`` - - The type of key management for S3 server-side encryption. Use ``S3`` - for S3 managed or ``KMS`` for KMS-managed keys, defaults to ``S3``. - * - ``hive.s3.sse.kms-key-id`` - - The KMS Key ID to use for S3 server-side encryption with KMS-managed - keys. If not set, the default key is used. - * - ``hive.s3.kms-key-id`` - - If set, use S3 client-side encryption and use the AWS KMS to store - encryption keys and use the value of this property as the KMS Key ID for - newly created objects. - * - ``hive.s3.encryption-materials-provider`` - - If set, use S3 client-side encryption and use the value of this property - as the fully qualified name of a Java class which implements the AWS - SDK's ``EncryptionMaterialsProvider`` interface. If the class also - implements ``Configurable`` from the Hadoop API, the Hadoop - configuration will be passed in after the object has been created. - * - ``hive.s3.upload-acl-type`` - - Canned ACL to use while uploading files to S3, defaults to ``PRIVATE``. - If the files are to be uploaded to an S3 bucket owned by a different AWS - user, the canned ACL has to be set to one of the following: - ``AUTHENTICATED_READ``, ``AWS_EXEC_READ``, ``BUCKET_OWNER_FULL_CONTROL``, - ``BUCKET_OWNER_READ``, ``LOG_DELIVERY_WRITE``, ``PUBLIC_READ``, - ``PUBLIC_READ_WRITE``. Refer to the `AWS canned ACL `_ - guide to understand each option's definition. - * - ``hive.s3.skip-glacier-objects`` - - Ignore Glacier objects rather than failing the query. This skips data - that may be expected to be part of the table or partition. Defaults to - ``false``. - * - ``hive.s3.streaming.enabled`` - - Use S3 multipart upload API to upload file in streaming way, without - staging file to be created in the local file system. - * - ``hive.s3.streaming.part-size`` - - The part size for S3 streaming upload. Defaults to ``16MB``. - * - ``hive.s3.proxy.host`` - - Proxy host to use if connecting through a proxy. - * - ``hive.s3.proxy.port`` - - Proxy port to use if connecting through a proxy. - * - ``hive.s3.proxy.protocol`` - - Proxy protocol. HTTP or HTTPS , defaults to ``HTTPS``. - * - ``hive.s3.proxy.non-proxy-hosts`` - - Hosts list to access without going through the proxy. - * - ``hive.s3.proxy.username`` - - Proxy user name to use if connecting through a proxy. - * - ``hive.s3.proxy.password`` - - Proxy password to use if connecting through a proxy. - * - ``hive.s3.proxy.preemptive-basic-auth`` - - Whether to attempt to authenticate preemptively against proxy when using - base authorization, defaults to ``false``. - * - ``hive.s3.sts.endpoint`` - - Optional override for the sts endpoint given that IAM role based - authentication via sts is used. - * - ``hive.s3.sts.region`` - - Optional override for the sts region given that IAM role based - authentication via sts is used. -``` +:::{list-table} +:widths: 35, 65 +:header-rows: 1 + +* - Property name + - Description +* - `hive.s3.aws-access-key` + - Default AWS access key to use. +* - `hive.s3.aws-secret-key` + - Default AWS secret key to use. +* - `hive.s3.iam-role` + - IAM role to assume. +* - `hive.s3.external-id` + - External ID for the IAM role trust policy. +* - `hive.s3.endpoint` + - The S3 storage endpoint server. This can be used to connect to an + S3-compatible storage system instead of AWS. When using v4 signatures, it is + recommended to set this to the AWS region-specific endpoint (e.g., + `http[s]://s3..amazonaws.com`). +* - `hive.s3.region` + - Optional property to force the S3 client to connect to the specified region + only. +* - `hive.s3.storage-class` + - The S3 storage class to use when writing the data. Currently only `STANDARD` + and `INTELLIGENT_TIERING` storage classes are supported. Default storage + class is `STANDARD` +* - `hive.s3.signer-type` + - Specify a different signer type for S3-compatible storage. Example: + `S3SignerType` for v2 signer type +* - `hive.s3.signer-class` + - Specify a different signer class for S3-compatible storage. +* - `hive.s3.path-style-access` + - Use path-style access for all requests to the S3-compatible storage. This is + for S3-compatible storage that doesn't support virtual-hosted-style access, + defaults to `false`. +* - `hive.s3.staging-directory` + - Local staging directory for data written to S3. This defaults to the Java + temporary directory specified by the JVM system property `java.io.tmpdir`. +* - `hive.s3.pin-client-to-current-region` + - Pin S3 requests to the same region as the EC2 instance where Trino is + running, defaults to `false`. +* - `hive.s3.ssl.enabled` + - Use HTTPS to communicate with the S3 API, defaults to `true`. +* - `hive.s3.sse.enabled` + - Use S3 server-side encryption, defaults to `false`. +* - `hive.s3.sse.type` + - The type of key management for S3 server-side encryption. Use `S3` for S3 + managed or `KMS` for KMS-managed keys, defaults to `S3`. +* - `hive.s3.sse.kms-key-id` + - The KMS Key ID to use for S3 server-side encryption with KMS-managed keys. + If not set, the default key is used. +* - `hive.s3.kms-key-id` + - If set, use S3 client-side encryption and use the AWS KMS to store + encryption keys and use the value of this property as the KMS Key ID for + newly created objects. +* - `hive.s3.encryption-materials-provider` + - If set, use S3 client-side encryption and use the value of this property as + the fully qualified name of a Java class which implements the AWS SDK's + `EncryptionMaterialsProvider` interface. If the class also implements + `Configurable` from the Hadoop API, the Hadoop configuration will be passed + in after the object has been created. +* - `hive.s3.upload-acl-type` + - Canned ACL to use while uploading files to S3, defaults to `PRIVATE`. If the + files are to be uploaded to an S3 bucket owned by a different AWS user, the + canned ACL has to be set to one of the following: `AUTHENTICATED_READ`, + `AWS_EXEC_READ`, `BUCKET_OWNER_FULL_CONTROL`, `BUCKET_OWNER_READ`, + `LOG_DELIVERY_WRITE`, `PUBLIC_READ`, `PUBLIC_READ_WRITE`. Refer to the `AWS + canned ACL + `_ + guide to understand each option's definition. +* - `hive.s3.skip-glacier-objects` + - Ignore Glacier objects rather than failing the query. This skips data that + may be expected to be part of the table or partition. Defaults to `false`. +* - `hive.s3.streaming.enabled` + - Use S3 multipart upload API to upload file in streaming way, without staging + file to be created in the local file system. +* - `hive.s3.streaming.part-size` + - The part size for S3 streaming upload. Defaults to `16MB`. +* - `hive.s3.proxy.host` + - Proxy host to use if connecting through a proxy. +* - `hive.s3.proxy.port` + - Proxy port to use if connecting through a proxy. +* - `hive.s3.proxy.protocol` + - Proxy protocol. HTTP or HTTPS , defaults to `HTTPS`. +* - `hive.s3.proxy.non-proxy-hosts` + - Hosts list to access without going through the proxy. +* - `hive.s3.proxy.username` + - Proxy user name to use if connecting through a proxy. +* - `hive.s3.proxy.password` + - Proxy password to use if connecting through a proxy. +* - `hive.s3.proxy.preemptive-basic-auth` + - Whether to attempt to authenticate preemptively against proxy when using + base authorization, defaults to `false`. +* - `hive.s3.sts.endpoint` + - Optional override for the sts endpoint given that IAM role based + authentication via sts is used. +* - `hive.s3.sts.region` + - Optional override for the sts region given that IAM role based + authentication via sts is used. +::: (hive-s3-credentials)= diff --git a/docs/src/main/sphinx/connector/hive-security.md b/docs/src/main/sphinx/connector/hive-security.md index 9ebdd748baa1..6a3f5d479015 100644 --- a/docs/src/main/sphinx/connector/hive-security.md +++ b/docs/src/main/sphinx/connector/hive-security.md @@ -112,68 +112,65 @@ authenticates using Kerberos. Kerberos authentication for the metastore is configured in the connector's properties file using the following optional properties: -```{eval-rst} -.. list-table:: Hive metastore Thrift service authentication properties - :widths: 30, 55, 15 - :header-rows: 1 - - * - Property value - - Description - - Default - * - ``hive.metastore.authentication.type`` - - Hive metastore authentication type. One of ``NONE`` or ``KERBEROS``. When - using the default value of ``NONE``, Kerberos authentication is disabled, - and no other properties must be configured. - - When set to ``KERBEROS`` the Hive connector connects to the Hive metastore - Thrift service using SASL and authenticate using Kerberos. - - ``NONE`` - * - ``hive.metastore.thrift.impersonation.enabled`` - - Enable Hive metastore end user impersonation. See - :ref:`hive-security-metastore-impersonation` for more information. - - ``false`` - * - ``hive.metastore.service.principal`` - - The Kerberos principal of the Hive metastore service. The coordinator - uses this to authenticate the Hive metastore. - - The ``_HOST`` placeholder can be used in this property value. When - connecting to the Hive metastore, the Hive connector substitutes in the - hostname of the **metastore** server it is connecting to. This is useful - if the metastore runs on multiple hosts. - - Example: ``hive/hive-server-host@EXAMPLE.COM`` or - ``hive/_HOST@EXAMPLE.COM``. - - - * - ``hive.metastore.client.principal`` - - The Kerberos principal that Trino uses when connecting to the Hive - metastore service. - - Example: ``trino/trino-server-node@EXAMPLE.COM`` or - ``trino/_HOST@EXAMPLE.COM``. - - The ``_HOST`` placeholder can be used in this property value. When - connecting to the Hive metastore, the Hive connector substitutes in the - hostname of the **worker** node Trino is running on. This is useful if - each worker node has its own Kerberos principal. - - Unless :ref:`hive-security-metastore-impersonation` is enabled, - the principal specified by ``hive.metastore.client.principal`` must have - sufficient privileges to remove files and directories within the - ``hive/warehouse`` directory. - - **Warning:** If the principal does have sufficient permissions, only the - metadata is removed, and the data continues to consume disk space. This - occurs because the Hive metastore is responsible for deleting the - internal table data. When the metastore is configured to use Kerberos - authentication, all of the HDFS operations performed by the metastore are - impersonated. Errors deleting data are silently ignored. - - - * - ``hive.metastore.client.keytab`` - - The path to the keytab file that contains a key for the principal - specified by ``hive.metastore.client.principal``. This file must be +:::{list-table} Hive metastore Thrift service authentication properties +:widths: 30, 55, 15 +:header-rows: 1 + +* - Property value + - Description + - Default +* - `hive.metastore.authentication.type` + - Hive metastore authentication type. One of `NONE` or `KERBEROS`. When using + the default value of `NONE`, Kerberos authentication is disabled, and no + other properties must be configured. + + When set to `KERBEROS` the Hive connector connects to the Hive metastore + Thrift service using SASL and authenticate using Kerberos. + - `NONE` +* - `hive.metastore.thrift.impersonation.enabled` + - Enable Hive metastore end user impersonation. See + [](hive-security-metastore-impersonation) for more information. + - `false` +* - `hive.metastore.service.principal` + - The Kerberos principal of the Hive metastore service. The coordinator uses + this to authenticate the Hive metastore. + + The `_HOST` placeholder can be used in this property value. When connecting + to the Hive metastore, the Hive connector substitutes in the hostname of the + **metastore** server it is connecting to. This is useful if the metastore + runs on multiple hosts. + + Example: `hive/hive-server-host@EXAMPLE.COM` or `hive/_HOST@EXAMPLE.COM`. + - +* - `hive.metastore.client.principal` + - The Kerberos principal that Trino uses when connecting to the Hive metastore + service. + + Example: `trino/trino-server-node@EXAMPLE.COM` or `trino/_HOST@EXAMPLE.COM`. + + The `_HOST` placeholder can be used in this property value. When connecting + to the Hive metastore, the Hive connector substitutes in the hostname of the + **worker** node Trino is running on. This is useful if each worker node has + its own Kerberos principal. + + Unless [](hive-security-metastore-impersonation) is enabled, the principal + specified by `hive.metastore.client.principal` must have sufficient + privileges to remove files and directories within the `hive/warehouse` + directory. + + **Warning:** If the principal does have sufficient permissions, only the + metadata is removed, and the data continues to consume disk space. This + occurs because the Hive metastore is responsible for deleting the internal + table data. When the metastore is configured to use Kerberos authentication, + all of the HDFS operations performed by the metastore are impersonated. + Errors deleting data are silently ignored. + - +* - `hive.metastore.client.keytab` + - The path to the keytab file that contains a key for the principal + specified by `hive.metastore.client.principal`. This file must be readable by the operating system user running Trino. - - -``` + - +::: #### Configuration examples @@ -225,53 +222,51 @@ In a Kerberized Hadoop cluster, Trino authenticates to HDFS using Kerberos. Kerberos authentication for HDFS is configured in the connector's properties file using the following optional properties: -```{eval-rst} -.. list-table:: HDFS authentication properties - :widths: 30, 55, 15 - :header-rows: 1 - - * - Property value - - Description - - Default - * - ``hive.hdfs.authentication.type`` - - HDFS authentication type; one of ``NONE`` or ``KERBEROS``. When using the - default value of ``NONE``, Kerberos authentication is disabled, and no - other properties must be configured. - - When set to ``KERBEROS``, the Hive connector authenticates to HDFS using - Kerberos. - - ``NONE`` - * - ``hive.hdfs.impersonation.enabled`` - - Enable HDFS end-user impersonation. Impersonating the end user can provide - additional security when accessing HDFS if HDFS permissions or ACLs are - used. - - HDFS Permissions and ACLs are explained in the `HDFS Permissions Guide - `_. - - ``false`` - * - ``hive.hdfs.trino.principal`` - - The Kerberos principal Trino uses when connecting to HDFS. - - Example: ``trino-hdfs-superuser/trino-server-node@EXAMPLE.COM`` or - ``trino-hdfs-superuser/_HOST@EXAMPLE.COM``. - - The ``_HOST`` placeholder can be used in this property value. When - connecting to HDFS, the Hive connector substitutes in the hostname of the - **worker** node Trino is running on. This is useful if each worker node - has its own Kerberos principal. - - - * - ``hive.hdfs.trino.keytab`` - - The path to the keytab file that contains a key for the principal - specified by ``hive.hdfs.trino.principal``. This file must be readable by - the operating system user running Trino. - - - * - ``hive.hdfs.wire-encryption.enabled`` - - Enable HDFS wire encryption. In a Kerberized Hadoop cluster that uses HDFS - wire encryption, this must be set to ``true`` to enable Trino to access - HDFS. Note that using wire encryption may impact query execution - performance. - - -``` +:::{list-table} HDFS authentication properties +:widths: 30, 55, 15 +:header-rows: 1 + +* - Property value + - Description + - Default +* - `hive.hdfs.authentication.type` + - HDFS authentication type; one of `NONE` or `KERBEROS`. When using the + default value of `NONE`, Kerberos authentication is disabled, and no other + properties must be configured. + + When set to `KERBEROS`, the Hive connector authenticates to HDFS using + Kerberos. + - `NONE` +* - `hive.hdfs.impersonation.enabled` + - Enable HDFS end-user impersonation. Impersonating the end user can provide + additional security when accessing HDFS if HDFS permissions or ACLs are + used. + + HDFS Permissions and ACLs are explained in the [HDFS Permissions + Guide](https://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-hdfs/HdfsPermissionsGuide.html). + - `false` +* - `hive.hdfs.trino.principal` + - The Kerberos principal Trino uses when connecting to HDFS. + + Example: `trino-hdfs-superuser/trino-server-node@EXAMPLE.COM` or + `trino-hdfs-superuser/_HOST@EXAMPLE.COM`. + + The `_HOST` placeholder can be used in this property value. When connecting + to HDFS, the Hive connector substitutes in the hostname of the **worker** + node Trino is running on. This is useful if each worker node has its own + Kerberos principal. + - +* - `hive.hdfs.trino.keytab` + - The path to the keytab file that contains a key for the principal specified + by `hive.hdfs.trino.principal`. This file must be readable by the operating + system user running Trino. + - +* - `hive.hdfs.wire-encryption.enabled` + - Enable HDFS wire encryption. In a Kerberized Hadoop cluster that uses HDFS + wire encryption, this must be set to `true` to enable Trino to access HDFS. + Note that using wire encryption may impact query execution performance. + - +::: #### Configuration examples @@ -352,38 +347,37 @@ You can enable authorization checks for the {doc}`hive` by setting the `hive.security` property in the Hive catalog properties file. This property must be one of the following values: -```{eval-rst} -.. list-table:: ``hive.security`` property values - :widths: 30, 60 - :header-rows: 1 - - * - Property value - - Description - * - ``legacy`` (default value) - - Few authorization checks are enforced, thus allowing most operations. The - config properties ``hive.allow-drop-table``, ``hive.allow-rename-table``, - ``hive.allow-add-column``, ``hive.allow-drop-column`` and - ``hive.allow-rename-column`` are used. - * - ``read-only`` - - Operations that read data or metadata, such as ``SELECT``, are permitted, - but none of the operations that write data or metadata, such as - ``CREATE``, ``INSERT`` or ``DELETE``, are allowed. - * - ``file`` - - Authorization checks are enforced using a catalog-level access control - configuration file whose path is specified in the ``security.config-file`` - catalog configuration property. See - :ref:`catalog-file-based-access-control` for details. - * - ``sql-standard`` - - Users are permitted to perform the operations as long as they have the - required privileges as per the SQL standard. In this mode, Trino enforces - the authorization checks for queries based on the privileges defined in - Hive metastore. To alter these privileges, use the :doc:`/sql/grant` and - :doc:`/sql/revoke` commands. - - See the :ref:`hive-sql-standard-based-authorization` section for details. - * - ``allow-all`` - - No authorization checks are enforced. -``` +:::{list-table} `hive.security` property values +:widths: 30, 60 +:header-rows: 1 + +* - Property value + - Description +* - `legacy` (default value) + - Few authorization checks are enforced, thus allowing most operations. The + config properties `hive.allow-drop-table`, `hive.allow-rename-table`, + `hive.allow-add-column`, `hive.allow-drop-column` and + `hive.allow-rename-column` are used. +* - `read-only` + - Operations that read data or metadata, such as `SELECT`, are permitted, but + none of the operations that write data or metadata, such as `CREATE`, + `INSERT` or `DELETE`, are allowed. +* - `file` + - Authorization checks are enforced using a catalog-level access control + configuration file whose path is specified in the `security.config-file` + catalog configuration property. See [](catalog-file-based-access-control) + for details. +* - `sql-standard` + - Users are permitted to perform the operations as long as they have the + required privileges as per the SQL standard. In this mode, Trino enforces + the authorization checks for queries based on the privileges defined in Hive + metastore. To alter these privileges, use the [](/sql/grant) and + [](/sql/revoke) commands. + + See the [](hive-sql-standard-based-authorization) section for details. +* - `allow-all` + - No authorization checks are enforced. +::: (hive-sql-standard-based-authorization)= diff --git a/docs/src/main/sphinx/connector/hive.md b/docs/src/main/sphinx/connector/hive.md index 3a4f630c9358..cc715695b1f2 100644 --- a/docs/src/main/sphinx/connector/hive.md +++ b/docs/src/main/sphinx/connector/hive.md @@ -155,197 +155,192 @@ The following table lists general configuration properties for the Hive connector. There are additional sets of configuration properties throughout the Hive connector documentation. -```{eval-rst} -.. list-table:: Hive general configuration properties - :widths: 35, 50, 15 - :header-rows: 1 - - * - Property Name - - Description - - Default - * - ``hive.config.resources`` - - An optional comma-separated list of HDFS configuration files. These - files must exist on the machines running Trino. Only specify this if - absolutely necessary to access HDFS. Example: ``/etc/hdfs-site.xml`` - - - * - ``hive.recursive-directories`` - - Enable reading data from subdirectories of table or partition locations. - If disabled, subdirectories are ignored. This is equivalent to the - ``hive.mapred.supports.subdirectories`` property in Hive. - - ``false`` - * - ``hive.ignore-absent-partitions`` - - Ignore partitions when the file system location does not exist rather - than failing the query. This skips data that may be expected to be part - of the table. - - ``false`` - * - ``hive.storage-format`` - - The default file format used when creating new tables. - - ``ORC`` - * - ``hive.compression-codec`` - - The compression codec to use when writing files. Possible values are - ``NONE``, ``SNAPPY``, ``LZ4``, ``ZSTD``, or ``GZIP``. - - ``GZIP`` - * - ``hive.force-local-scheduling`` - - Force splits to be scheduled on the same node as the Hadoop DataNode - process serving the split data. This is useful for installations where - Trino is collocated with every DataNode. - - ``false`` - * - ``hive.respect-table-format`` - - Should new partitions be written using the existing table format or the - default Trino format? - - ``true`` - * - ``hive.immutable-partitions`` - - Can new data be inserted into existing partitions? If ``true`` then - setting ``hive.insert-existing-partitions-behavior`` to ``APPEND`` is - not allowed. This also affects the ``insert_existing_partitions_behavior`` - session property in the same way. - - ``false`` - * - ``hive.insert-existing-partitions-behavior`` - - What happens when data is inserted into an existing partition? Possible - values are - - * ``APPEND`` - appends data to existing partitions - * ``OVERWRITE`` - overwrites existing partitions - * ``ERROR`` - modifying existing partitions is not allowed - - ``APPEND`` - * - ``hive.target-max-file-size`` - - Best effort maximum size of new files. - - ``1GB`` - * - ``hive.create-empty-bucket-files`` - - Should empty files be created for buckets that have no data? - - ``false`` - * - ``hive.validate-bucketing`` - - Enables validation that data is in the correct bucket when reading - bucketed tables. - - ``true`` - * - ``hive.partition-statistics-sample-size`` - - Specifies the number of partitions to analyze when computing table - statistics. - - 100 - * - ``hive.max-partitions-per-writers`` - - Maximum number of partitions per writer. - - 100 - * - ``hive.max-partitions-for-eager-load`` - - The maximum number of partitions for a single table scan to load eagerly - on the coordinator. Certain optimizations are not possible without eager - loading. - - 100,000 - * - ``hive.max-partitions-per-scan`` - - Maximum number of partitions for a single table scan. - - 1,000,000 - * - ``hive.dfs.replication`` - - Hadoop file system replication factor. - - - * - ``hive.security`` - - See :doc:`hive-security`. - - - * - ``security.config-file`` - - Path of config file to use when ``hive.security=file``. See - :ref:`catalog-file-based-access-control` for details. - - - * - ``hive.non-managed-table-writes-enabled`` - - Enable writes to non-managed (external) Hive tables. - - ``false`` - * - ``hive.non-managed-table-creates-enabled`` - - Enable creating non-managed (external) Hive tables. - - ``true`` - * - ``hive.collect-column-statistics-on-write`` - - Enables automatic column level statistics collection on write. See - `Table Statistics <#table-statistics>`__ for details. - - ``true`` - * - ``hive.file-status-cache-tables`` - - Cache directory listing for specific tables. Examples: - - * ``fruit.apple,fruit.orange`` to cache listings only for tables - ``apple`` and ``orange`` in schema ``fruit`` - * ``fruit.*,vegetable.*`` to cache listings for all tables - in schemas ``fruit`` and ``vegetable`` - * ``*`` to cache listings for all tables in all schemas - - - * - ``hive.file-status-cache.max-retained-size`` - - Maximum retained size of cached file status entries. - - ``1GB`` - * - ``hive.file-status-cache-expire-time`` - - How long a cached directory listing is considered valid. - - ``1m`` - * - ``hive.per-transaction-file-status-cache.max-retained-size`` - - Maximum retained size of all entries in per transaction file status cache. - Retained size limit is shared across all running queries. - - ``100MB`` - * - ``hive.rcfile.time-zone`` - - Adjusts binary encoded timestamp values to a specific time zone. For - Hive 3.1+, this must be set to UTC. - - JVM default - * - ``hive.timestamp-precision`` - - Specifies the precision to use for Hive columns of type ``TIMESTAMP``. - Possible values are ``MILLISECONDS``, ``MICROSECONDS`` and ``NANOSECONDS``. - Values with higher precision than configured are rounded. - - ``MILLISECONDS`` - * - ``hive.temporary-staging-directory-enabled`` - - Controls whether the temporary staging directory configured at - ``hive.temporary-staging-directory-path`` is used for write - operations. Temporary staging directory is never used for writes to - non-sorted tables on S3, encrypted HDFS or external location. Writes to - sorted tables will utilize this path for staging temporary files during - sorting operation. When disabled, the target storage will be used for - staging while writing sorted tables which can be inefficient when - writing to object stores like S3. - - ``true`` - * - ``hive.temporary-staging-directory-path`` - - Controls the location of temporary staging directory that is used for - write operations. The ``${USER}`` placeholder can be used to use a - different location for each user. - - ``/tmp/presto-${USER}`` - * - ``hive.hive-views.enabled`` - - Enable translation for :ref:`Hive views `. - - ``false`` - * - ``hive.hive-views.legacy-translation`` - - Use the legacy algorithm to translate :ref:`Hive views `. - You can use the ``hive_views_legacy_translation`` catalog session - property for temporary, catalog specific use. - - ``false`` - * - ``hive.parallel-partitioned-bucketed-writes`` - - Improve parallelism of partitioned and bucketed table writes. When - disabled, the number of writing threads is limited to number of buckets. - - ``true`` - * - ``hive.fs.new-directory-permissions`` - - Controls the permissions set on new directories created for tables. It - must be either 'skip' or an octal number, with a leading 0. If set to - 'skip', permissions of newly created directories will not be set by - Trino. - - ``0777`` - * - ``hive.fs.cache.max-size`` - - Maximum number of cached file system objects. - - 1000 - * - ``hive.query-partition-filter-required`` - - Set to ``true`` to force a query to use a partition filter. You can use - the ``query_partition_filter_required`` catalog session property for - temporary, catalog specific use. - - ``false`` - * - ``hive.table-statistics-enabled`` - - Enables :doc:`/optimizer/statistics`. The equivalent - :doc:`catalog session property ` is - ``statistics_enabled`` for session specific use. Set to ``false`` to - disable statistics. Disabling statistics means that - :doc:`/optimizer/cost-based-optimizations` can not make smart decisions - about the query plan. - - ``true`` - * - ``hive.auto-purge`` - - Set the default value for the auto_purge table property for managed - tables. See the :ref:`hive-table-properties` for more information on - auto_purge. - - ``false`` - * - ``hive.partition-projection-enabled`` - - Enables Athena partition projection support - - ``false`` - * - ``hive.max-partition-drops-per-query`` - - Maximum number of partitions to drop in a single query. - - 100,000 - * - ``hive.single-statement-writes`` - - Enables auto-commit for all writes. This can be used to disallow - multi-statement write transactions. - - ``false`` -``` +:::{list-table} Hive general configuration properties +:widths: 35, 50, 15 +:header-rows: 1 + +* - Property Name + - Description + - Default +* - `hive.config.resources` + - An optional comma-separated list of HDFS configuration files. These files + must exist on the machines running Trino. Only specify this if absolutely + necessary to access HDFS. Example: `/etc/hdfs-site.xml` + - +* - `hive.recursive-directories` + - Enable reading data from subdirectories of table or partition locations. If + disabled, subdirectories are ignored. This is equivalent to the + `hive.mapred.supports.subdirectories` property in Hive. + - `false` +* - `hive.ignore-absent-partitions` + - Ignore partitions when the file system location does not exist rather than + failing the query. This skips data that may be expected to be part of the + table. + - `false` +* - `hive.storage-format` + - The default file format used when creating new tables. + - `ORC` +* - `hive.compression-codec` + - The compression codec to use when writing files. Possible values are `NONE`, + `SNAPPY`, `LZ4`, `ZSTD`, or `GZIP`. + - `GZIP` +* - `hive.force-local-scheduling` + - Force splits to be scheduled on the same node as the Hadoop DataNode process + serving the split data. This is useful for installations where Trino is + collocated with every DataNode. + - `false` +* - `hive.respect-table-format` + - Should new partitions be written using the existing table format or the + default Trino format? + - `true` +* - `hive.immutable-partitions` + - Can new data be inserted into existing partitions? If `true` then setting + `hive.insert-existing-partitions-behavior` to `APPEND` is not allowed. This + also affects the `insert_existing_partitions_behavior` session property in + the same way. + - `false` +* - `hive.insert-existing-partitions-behavior` + - What happens when data is inserted into an existing partition? Possible + values are + + * `APPEND` - appends data to existing partitions + * `OVERWRITE` - overwrites existing partitions + * `ERROR` - modifying existing partitions is not allowed + - `APPEND` +* - `hive.target-max-file-size` + - Best effort maximum size of new files. + - `1GB` +* - `hive.create-empty-bucket-files` + - Should empty files be created for buckets that have no data? + - `false` +* - `hive.validate-bucketing` + - Enables validation that data is in the correct bucket when reading bucketed + tables. + - `true` +* - `hive.partition-statistics-sample-size` + - Specifies the number of partitions to analyze when computing table + statistics. + - 100 +* - `hive.max-partitions-per-writers` + - Maximum number of partitions per writer. + - 100 +* - `hive.max-partitions-for-eager-load` + - The maximum number of partitions for a single table scan to load eagerly on + the coordinator. Certain optimizations are not possible without eager + loading. + - 100,000 +* - `hive.max-partitions-per-scan` + - Maximum number of partitions for a single table scan. + - 1,000,000 +* - `hive.dfs.replication` + - Hadoop file system replication factor. + - +* - `hive.security` + - See [](/connector/hive-security). + - +* - `security.config-file` + - Path of config file to use when `hive.security=file`. See + [](catalog-file-based-access-control) for details. + - +* - `hive.non-managed-table-writes-enabled` + - Enable writes to non-managed (external) Hive tables. + - `false` +* - `hive.non-managed-table-creates-enabled` + - Enable creating non-managed (external) Hive tables. + - `true` +* - `hive.collect-column-statistics-on-write` + - Enables automatic column level statistics collection on write. See + [](hive-table-statistics) for details. + - `true` +* - `hive.file-status-cache-tables` + - Cache directory listing for specific tables. Examples: + + * `fruit.apple,fruit.orange` to cache listings only for tables + `apple` and `orange` in schema `fruit` + * `fruit.*,vegetable.*` to cache listings for all tables + in schemas `fruit` and `vegetable` + * `*` to cache listings for all tables in all schemas + - +* - `hive.file-status-cache.max-retained-size` + - Maximum retained size of cached file status entries. + - `1GB` +* - `hive.file-status-cache-expire-time` + - How long a cached directory listing is considered valid. + - `1m` +* - `hive.per-transaction-file-status-cache.max-retained-size` + - Maximum retained size of all entries in per transaction file status cache. + Retained size limit is shared across all running queries. + - `100MB` +* - `hive.rcfile.time-zone` + - Adjusts binary encoded timestamp values to a specific time zone. For Hive + 3.1+, this must be set to UTC. + - JVM default +* - `hive.timestamp-precision` + - Specifies the precision to use for Hive columns of type `TIMESTAMP`. + Possible values are `MILLISECONDS`, `MICROSECONDS` and `NANOSECONDS`. Values + with higher precision than configured are rounded. + - `MILLISECONDS` +* - `hive.temporary-staging-directory-enabled` + - Controls whether the temporary staging directory configured at + `hive.temporary-staging-directory-path` is used for write operations. + Temporary staging directory is never used for writes to non-sorted tables on + S3, encrypted HDFS or external location. Writes to sorted tables will + utilize this path for staging temporary files during sorting operation. When + disabled, the target storage will be used for staging while writing sorted + tables which can be inefficient when writing to object stores like S3. + - `true` +* - `hive.temporary-staging-directory-path` + - Controls the location of temporary staging directory that is used for write + operations. The `${USER}` placeholder can be used to use a different + location for each user. + - `/tmp/presto-${USER}` +* - `hive.hive-views.enabled` + - Enable translation for [Hive views](hive-views). + - `false` +* - `hive.hive-views.legacy-translation` + - Use the legacy algorithm to translate [Hive views](hive-views). You + can use the `hive_views_legacy_translation` catalog session property for + temporary, catalog specific use. + - `false` +* - `hive.parallel-partitioned-bucketed-writes` + - Improve parallelism of partitioned and bucketed table writes. When disabled, + the number of writing threads is limited to number of buckets. + - `true` +* - `hive.fs.new-directory-permissions` + - Controls the permissions set on new directories created for tables. It must + be either 'skip' or an octal number, with a leading 0. If set to `skip`, + permissions of newly created directories will not be set by Trino. + - `0777` +* - `hive.fs.cache.max-size` + - Maximum number of cached file system objects. + - 1000 +* - `hive.query-partition-filter-required` + - Set to `true` to force a query to use a partition filter. You can use the + `query_partition_filter_required` catalog session property for temporary, + catalog specific use. + - `false` +* - `hive.table-statistics-enabled` + - Enables [](/optimizer/statistics). The equivalent [catalog session + property](/sql/set-session) is `statistics_enabled` for session specific + use. Set to `false` to disable statistics. Disabling statistics means that + [](/optimizer/cost-based-optimizations) can not make smart decisions about + the query plan. + - `true` +* - `hive.auto-purge` + - Set the default value for the auto_purge table property for managed tables. + See the [](hive-table-properties) for more information on auto_purge. + - `false` +* - `hive.partition-projection-enabled` + - Enables Athena partition projection support + - `false` +* - `hive.max-partition-drops-per-query` + - Maximum number of partitions to drop in a single query. + - 100,000 +* - `hive.single-statement-writes` + - Enables auto-commit for all writes. This can be used to disallow + multi-statement write transactions. + - `false` +::: ## Storage @@ -783,118 +778,114 @@ WITH (format='CSV', csv_escape = '"') ``` -```{eval-rst} -.. list-table:: Hive connector table properties - :widths: 20, 60, 20 - :header-rows: 1 - - * - Property name - - Description - - Default - * - ``auto_purge`` - - Indicates to the configured metastore to perform a purge when a table or - partition is deleted instead of a soft deletion using the trash. - - - * - ``avro_schema_url`` - - The URI pointing to :ref:`hive-avro-schema` for the table. - - - * - ``bucket_count`` - - The number of buckets to group data into. Only valid if used with - ``bucketed_by``. - - 0 - * - ``bucketed_by`` - - The bucketing column for the storage table. Only valid if used with - ``bucket_count``. - - ``[]`` - * - ``bucketing_version`` - - Specifies which Hive bucketing version to use. Valid values are ``1`` - or ``2``. - - - * - ``csv_escape`` - - The CSV escape character. Requires CSV format. - - - * - ``csv_quote`` - - The CSV quote character. Requires CSV format. - - - * - ``csv_separator`` - - The CSV separator character. Requires CSV format. You can use other - separators such as ``|`` or use Unicode to configure invisible separators - such tabs with ``U&'\0009'``. - - ``,`` - * - ``external_location`` - - The URI for an external Hive table on S3, Azure Blob Storage, etc. See the - :ref:`hive-examples` for more information. - - - * - ``format`` - - The table file format. Valid values include ``ORC``, ``PARQUET``, - ``AVRO``, ``RCBINARY``, ``RCTEXT``, ``SEQUENCEFILE``, ``JSON``, - ``TEXTFILE``, ``CSV``, and ``REGEX``. The catalog property - ``hive.storage-format`` sets the default value and can change it to a - different default. - - - * - ``null_format`` - - The serialization format for ``NULL`` value. Requires TextFile, RCText, - or SequenceFile format. - - - * - ``orc_bloom_filter_columns`` - - Comma separated list of columns to use for ORC bloom filter. It improves - the performance of queries using range predicates when reading ORC files. - Requires ORC format. - - ``[]`` - * - ``orc_bloom_filter_fpp`` - - The ORC bloom filters false positive probability. Requires ORC format. - - 0.05 - * - ``partitioned_by`` - - The partitioning column for the storage table. The columns listed in the - ``partitioned_by`` clause must be the last columns as defined in the DDL. - - ``[]`` - * - ``skip_footer_line_count`` - - The number of footer lines to ignore when parsing the file for data. - Requires TextFile or CSV format tables. - - - * - ``skip_header_line_count`` - - The number of header lines to ignore when parsing the file for data. - Requires TextFile or CSV format tables. - - - * - ``sorted_by`` - - The column to sort by to determine bucketing for row. Only valid if - ``bucketed_by`` and ``bucket_count`` are specified as well. - - ``[]`` - * - ``textfile_field_separator`` - - Allows the use of custom field separators, such as '|', for TextFile - formatted tables. - - - * - ``textfile_field_separator_escape`` - - Allows the use of a custom escape character for TextFile formatted tables. - - - * - ``transactional`` - - Set this property to ``true`` to create an ORC ACID transactional table. - Requires ORC format. This property may be shown as true for insert-only - tables created using older versions of Hive. - - - * - ``partition_projection_enabled`` - - Enables partition projection for selected table. - Mapped from AWS Athena table property - `projection.enabled `_. - - - * - ``partition_projection_ignore`` - - Ignore any partition projection properties stored in the metastore for - the selected table. This is a Trino-only property which allows you to - work around compatibility issues on a specific table, and if enabled, - Trino ignores all other configuration options related to partition - projection. - - - * - ``partition_projection_location_template`` - - Projected partition location template, such as - ``s3a://test/name=${name}/``. Mapped from the AWS Athena table property - `storage.location.template `_ - - ``${table_location}/${partition_name}`` - * - ``extra_properties`` - - Additional properties added to a Hive table. The properties are not used by Trino, - and are available in the ``$properties`` metadata table. - The properties are not included in the output of ``SHOW CREATE TABLE`` statements. - - -``` +:::{list-table} Hive connector table properties +:widths: 20, 60, 20 +:header-rows: 1 + +* - Property name + - Description + - Default +* - `auto_purge` + - Indicates to the configured metastore to perform a purge when a table or + partition is deleted instead of a soft deletion using the trash. + - +* - `avro_schema_url` + - The URI pointing to [](hive-avro-schema) for the table. + - +* - `bucket_count` + - The number of buckets to group data into. Only valid if used with + `bucketed_by`. + - 0 +* - `bucketed_by` + - The bucketing column for the storage table. Only valid if used with + `bucket_count`. + - `[]` +* - `bucketing_version` + - Specifies which Hive bucketing version to use. Valid values are `1` or `2`. + - +* - `csv_escape` + - The CSV escape character. Requires CSV format. + - +* - `csv_quote` + - The CSV quote character. Requires CSV format. + - +* - `csv_separator` + - The CSV separator character. Requires CSV format. You can use other + separators such as `|` or use Unicode to configure invisible separators such + tabs with `U&'\0009'`. + - `,` +* - `external_location` + - The URI for an external Hive table on S3, Azure Blob Storage, etc. See the + [](hive-examples) for more information. + - +* - `format` + - The table file format. Valid values include `ORC`, `PARQUET`, `AVRO`, + `RCBINARY`, `RCTEXT`, `SEQUENCEFILE`, `JSON`, `TEXTFILE`, `CSV`, and + `REGEX`. The catalog property `hive.storage-format` sets the default value + and can change it to a different default. + - +* - `null_format` + - The serialization format for `NULL` value. Requires TextFile, RCText, or + SequenceFile format. + - +* - `orc_bloom_filter_columns` + - Comma separated list of columns to use for ORC bloom filter. It improves the + performance of queries using range predicates when reading ORC files. + Requires ORC format. + - `[]` +* - `orc_bloom_filter_fpp` + - The ORC bloom filters false positive probability. Requires ORC format. + - 0.05 +* - `partitioned_by` + - The partitioning column for the storage table. The columns listed in the + `partitioned_by` clause must be the last columns as defined in the DDL. + - `[]` +* - `skip_footer_line_count` + - The number of footer lines to ignore when parsing the file for data. + Requires TextFile or CSV format tables. + - +* - `skip_header_line_count` + - The number of header lines to ignore when parsing the file for data. + Requires TextFile or CSV format tables. + - +* - `sorted_by` + - The column to sort by to determine bucketing for row. Only valid if + `bucketed_by` and `bucket_count` are specified as well. + - `[]` +* - `textfile_field_separator` + - Allows the use of custom field separators, such as '|', for TextFile + formatted tables. + - +* - `textfile_field_separator_escape` + - Allows the use of a custom escape character for TextFile formatted tables. + - +* - `transactional` + - Set this property to `true` to create an ORC ACID transactional table. + Requires ORC format. This property may be shown as true for insert-only + tables created using older versions of Hive. + - +* - `partition_projection_enabled` + - Enables partition projection for selected table. Mapped from AWS Athena + table property + [projection.enabled](https://docs.aws.amazon.com/athena/latest/ug/partition-projection-setting-up.html). + - +* - `partition_projection_ignore` + - Ignore any partition projection properties stored in the metastore for the + selected table. This is a Trino-only property which allows you to work + around compatibility issues on a specific table, and if enabled, Trino + ignores all other configuration options related to partition projection. + - +* - `partition_projection_location_template` + - Projected partition location template, such as `s3a://test/name=${name}/`. + Mapped from the AWS Athena table property + [storage.location.template](https://docs.aws.amazon.com/athena/latest/ug/partition-projection-setting-up.html#partition-projection-specifying-custom-s3-storage-locations) + - `${table_location}/${partition_name}` +* - `extra_properties` + - Additional properties added to a Hive table. The properties are not used by + Trino, and are available in the `$properties` metadata table. The properties + are not included in the output of `SHOW CREATE TABLE` statements. + - +::: (hive-special-tables)= @@ -947,68 +938,64 @@ SELECT * FROM example.web."page_views$partitions"; #### Column properties -```{eval-rst} -.. list-table:: Hive connector column properties - :widths: 20, 60, 20 - :header-rows: 1 - - * - Property name - - Description - - Default - * - ``partition_projection_type`` - - Defines the type of partition projection to use on this column. - May be used only on partition columns. Available types: - ``ENUM``, ``INTEGER``, ``DATE``, ``INJECTED``. - Mapped from the AWS Athena table property - `projection.${columnName}.type `_. - - - * - ``partition_projection_values`` - - Used with ``partition_projection_type`` set to ``ENUM``. Contains a static - list of values used to generate partitions. - Mapped from the AWS Athena table property - `projection.${columnName}.values `_. - - - * - ``partition_projection_range`` - - Used with ``partition_projection_type`` set to ``INTEGER`` or ``DATE`` to - define a range. It is a two-element array, describing the minimum and - maximum range values used to generate partitions. Generation starts from - the minimum, then increments by the defined - ``partition_projection_interval`` to the maximum. For example, the format - is ``['1', '4']`` for a ``partition_projection_type`` of ``INTEGER`` and - ``['2001-01-01', '2001-01-07']`` or ``['NOW-3DAYS', 'NOW']`` for a - ``partition_projection_type`` of ``DATE``. Mapped from the AWS Athena - table property - `projection.${columnName}.range `_. - - - * - ``partition_projection_interval`` - - Used with ``partition_projection_type`` set to ``INTEGER`` or ``DATE``. It - represents the interval used to generate partitions within - the given range ``partition_projection_range``. Mapped from the AWS Athena - table property - `projection.${columnName}.interval `_. - - - * - ``partition_projection_digits`` - - Used with ``partition_projection_type`` set to ``INTEGER``. - The number of digits to be used with integer column projection. - Mapped from the AWS Athena table property - `projection.${columnName}.digits `_. - - - * - ``partition_projection_format`` - - Used with ``partition_projection_type`` set to ``DATE``. - The date column projection format, defined as a string such as ``yyyy MM`` - or ``MM-dd-yy HH:mm:ss`` for use with the - `Java DateTimeFormatter class `_. - Mapped from the AWS Athena table property - `projection.${columnName}.format `_. - - - * - ``partition_projection_interval_unit`` - - Used with ``partition_projection_type=DATA``. - The date column projection range interval unit - given in ``partition_projection_interval``. - Mapped from the AWS Athena table property - `projection.${columnName}.interval.unit `_. - - -``` +:::{list-table} Hive connector column properties +:widths: 20, 60, 20 +:header-rows: 1 + +* - Property name + - Description + - Default +* - `partition_projection_type` + - Defines the type of partition projection to use on this column. May be used + only on partition columns. Available types: `ENUM`, `INTEGER`, `DATE`, + `INJECTED`. Mapped from the AWS Athena table property + [projection.${columnName}.type](https://docs.aws.amazon.com/athena/latest/ug/partition-projection-supported-types.html). + - +* - `partition_projection_values` + - Used with `partition_projection_type` set to `ENUM`. Contains a static list + of values used to generate partitions. Mapped from the AWS Athena table + property + [projection.${columnName}.values](https://docs.aws.amazon.com/athena/latest/ug/partition-projection-supported-types.html). + - +* - `partition_projection_range` + - Used with `partition_projection_type` set to `INTEGER` or `DATE` to define a + range. It is a two-element array, describing the minimum and maximum range + values used to generate partitions. Generation starts from the minimum, then + increments by the defined `partition_projection_interval` to the maximum. + For example, the format is `['1', '4']` for a `partition_projection_type` of + `INTEGER` and `['2001-01-01', '2001-01-07']` or `['NOW-3DAYS', 'NOW']` for a + `partition_projection_type` of `DATE`. Mapped from the AWS Athena table + property + [projection.${columnName}.range](https://docs.aws.amazon.com/athena/latest/ug/partition-projection-supported-types.html). + - +* - `partition_projection_interval` + - Used with `partition_projection_type` set to `INTEGER` or `DATE`. It + represents the interval used to generate partitions within the given range + `partition_projection_range`. Mapped from the AWS Athena table property + [projection.${columnName}.interval](https://docs.aws.amazon.com/athena/latest/ug/partition-projection-supported-types.html). + - +* - `partition_projection_digits` + - Used with `partition_projection_type` set to `INTEGER`. The number of digits + to be used with integer column projection. Mapped from the AWS Athena table + property + [projection.${columnName}.digits](https://docs.aws.amazon.com/athena/latest/ug/partition-projection-supported-types.html). + - +* - `partition_projection_format` + - Used with `partition_projection_type` set to `DATE`. The date column + projection format, defined as a string such as `yyyy MM` or `MM-dd-yy + HH:mm:ss` for use with the [Java DateTimeFormatter + class](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html). + Mapped from the AWS Athena table property + [projection.${columnName}.format](https://docs.aws.amazon.com/athena/latest/ug/partition-projection-supported-types.html). + - +* - `partition_projection_interval_unit` + - Used with `partition_projection_type=DATA`. The date column projection range + interval unit given in `partition_projection_interval`. Mapped from the AWS + Athena table property + [projection.${columnName}.interval.unit](https://docs.aws.amazon.com/athena/latest/ug/partition-projection-supported-types.html). + - + +::: (hive-special-columns)= @@ -1152,6 +1139,8 @@ any retry policy on transactional tables. The connector includes a number of performance improvements, detailed in the following sections. + +(hive-table-statistics)= ### Table statistics The Hive connector supports collecting and managing {doc}`table statistics @@ -1161,40 +1150,39 @@ When writing data, the Hive connector always collects basic statistics (`numFiles`, `numRows`, `rawDataSize`, `totalSize`) and by default will also collect column level statistics: -```{eval-rst} -.. list-table:: Available table statistics - :widths: 35, 65 - :header-rows: 1 - - * - Column type - - Collectible statistics - * - ``TINYINT`` - - Number of nulls, number of distinct values, min/max values - * - ``SMALLINT`` - - Number of nulls, number of distinct values, min/max values - * - ``INTEGER`` - - Number of nulls, number of distinct values, min/max values - * - ``BIGINT`` - - Number of nulls, number of distinct values, min/max values - * - ``DOUBLE`` - - Number of nulls, number of distinct values, min/max values - * - ``REAL`` - - Number of nulls, number of distinct values, min/max values - * - ``DECIMAL`` - - Number of nulls, number of distinct values, min/max values - * - ``DATE`` - - Number of nulls, number of distinct values, min/max values - * - ``TIMESTAMP`` - - Number of nulls, number of distinct values, min/max values - * - ``VARCHAR`` - - Number of nulls, number of distinct values - * - ``CHAR`` - - Number of nulls, number of distinct values - * - ``VARBINARY`` - - Number of nulls - * - ``BOOLEAN`` - - Number of nulls, number of true/false values -``` +:::{list-table} Available table statistics +:widths: 35, 65 +:header-rows: 1 + +* - Column type + - Collectible statistics +* - `TINYINT` + - Number of nulls, number of distinct values, min/max values +* - `SMALLINT` + - Number of nulls, number of distinct values, min/max values +* - `INTEGER` + - Number of nulls, number of distinct values, min/max values +* - `BIGINT` + - Number of nulls, number of distinct values, min/max values +* - `DOUBLE` + - Number of nulls, number of distinct values, min/max values +* - `REAL` + - Number of nulls, number of distinct values, min/max values +* - `DECIMAL` + - Number of nulls, number of distinct values, min/max values +* - `DATE` + - Number of nulls, number of distinct values, min/max values +* - `TIMESTAMP` + - Number of nulls, number of distinct values, min/max values +* - `VARCHAR` + - Number of nulls, number of distinct values +* - `CHAR` + - Number of nulls, number of distinct values +* - `VARBINARY` + - Number of nulls +* - `BOOLEAN` + - Number of nulls, number of true/false values +::: (hive-analyze)= @@ -1304,44 +1292,42 @@ features. Altering these properties from their default values is likely to cause instability and performance degradation. ::: -```{eval-rst} -.. list-table:: - :widths: 30, 50, 20 - :header-rows: 1 - - * - Property name - - Description - - Default value - * - ``hive.max-outstanding-splits`` - - The target number of buffered splits for each table scan in a query, - before the scheduler tries to pause. - - ``1000`` - * - ``hive.max-outstanding-splits-size`` - - The maximum size allowed for buffered splits for each table scan - in a query, before the query fails. - - ``256 MB`` - * - ``hive.max-splits-per-second`` - - The maximum number of splits generated per second per table scan. This - can be used to reduce the load on the storage system. By default, there - is no limit, which results in Trino maximizing the parallelization of - data access. - - - * - ``hive.max-initial-splits`` - - For each table scan, the coordinator first assigns file sections of up - to ``max-initial-split-size``. After ``max-initial-splits`` have been - assigned, ``max-split-size`` is used for the remaining splits. - - ``200`` - * - ``hive.max-initial-split-size`` - - The size of a single file section assigned to a worker until - ``max-initial-splits`` have been assigned. Smaller splits results in - more parallelism, which gives a boost to smaller queries. - - ``32 MB`` - * - ``hive.max-split-size`` - - The largest size of a single file section assigned to a worker. Smaller +:::{list-table} +:widths: 30, 50, 20 +:header-rows: 1 + +* - Property name + - Description + - Default value +* - `hive.max-outstanding-splits` + - The target number of buffered splits for each table scan in a query, before + the scheduler tries to pause. + - `1000` +* - `hive.max-outstanding-splits-size` + - The maximum size allowed for buffered splits for each table scan in a query, + before the query fails. + - `256 MB` +* - `hive.max-splits-per-second` + - The maximum number of splits generated per second per table scan. This can + be used to reduce the load on the storage system. By default, there is no + limit, which results in Trino maximizing the parallelization of data access. + - +* - `hive.max-initial-splits` + - For each table scan, the coordinator first assigns file sections of up to + `max-initial-split-size`. After `max-initial-splits` have been assigned, + `max-split-size` is used for the remaining splits. + - `200` +* - `hive.max-initial-split-size` + - The size of a single file section assigned to a worker until + `max-initial-splits` have been assigned. Smaller splits results in more + parallelism, which gives a boost to smaller queries. + - `32 MB` +* - `hive.max-split-size` + - The largest size of a single file section assigned to a worker. Smaller splits result in more parallelism and thus can decrease latency, but also have more overhead and increase load on the system. - - ``64 MB`` -``` + - `64 MB` +::: ## Hive 3-related limitations diff --git a/docs/src/main/sphinx/connector/hudi.md b/docs/src/main/sphinx/connector/hudi.md index 1236a4b21375..8289e6fb6eb1 100644 --- a/docs/src/main/sphinx/connector/hudi.md +++ b/docs/src/main/sphinx/connector/hudi.md @@ -36,54 +36,54 @@ synced to the metastore by the [Hudi sync tool](https://hudi.apache.org/docs/syn Additionally, following configuration properties can be set depending on the use-case: -```{eval-rst} -.. list-table:: Hudi configuration properties - :widths: 30, 55, 15 - :header-rows: 1 - - * - Property name - - Description - - Default - * - ``hudi.columns-to-hide`` - - List of column names that are hidden from the query output. - It can be used to hide Hudi meta fields. By default, no fields are hidden. - - - * - ``hudi.parquet.use-column-names`` - - Access Parquet columns using names from the file. If disabled, then columns - are accessed using the index. Only applicable to Parquet file format. - - ``true`` - * - ``hudi.split-generator-parallelism`` - - Number of threads to generate splits from partitions. - - ``4`` - * - ``hudi.split-loader-parallelism`` - - Number of threads to run background split loader. - A single background split loader is needed per query. - - ``4`` - * - ``hudi.size-based-split-weights-enabled`` - - Unlike uniform splitting, size-based splitting ensures that each batch of splits - has enough data to process. By default, it is enabled to improve performance. - - ``true`` - * - ``hudi.standard-split-weight-size`` - - The split size corresponding to the standard weight (1.0) - when size-based split weights are enabled. - - ``128MB`` - * - ``hudi.minimum-assigned-split-weight`` - - Minimum weight that a split can be assigned - when size-based split weights are enabled. - - ``0.05`` - * - ``hudi.max-splits-per-second`` - - Rate at which splits are queued for processing. - The queue is throttled if this rate limit is breached. - - ``Integer.MAX_VALUE`` - * - ``hudi.max-outstanding-splits`` - - Maximum outstanding splits in a batch enqueued for processing. - - ``1000`` - * - ``hudi.per-transaction-metastore-cache-maximum-size`` - - Maximum number of metastore data objects per transaction in - the Hive metastore cache. - - ``2000`` - -``` +:::{list-table} Hudi configuration properties +:widths: 30, 55, 15 +:header-rows: 1 + +* - Property name + - Description + - Default +* - `hudi.columns-to-hide` + - List of column names that are hidden from the query output. It can be used + to hide Hudi meta fields. By default, no fields are hidden. + - +* - `hudi.parquet.use-column-names` + - Access Parquet columns using names from the file. If disabled, then columns + are accessed using the index. Only applicable to Parquet file format. + - `true` +* - `hudi.split-generator-parallelism` + - Number of threads to generate splits from partitions. + - `4` +* - `hudi.split-loader-parallelism` + - Number of threads to run background split loader. A single background split + loader is needed per query. + - `4` +* - `hudi.size-based-split-weights-enabled` + - Unlike uniform splitting, size-based splitting ensures that each batch of + splits has enough data to process. By default, it is enabled to improve + performance. + - `true` +* - `hudi.standard-split-weight-size` + - The split size corresponding to the standard weight (1.0) when size-based + split weights are enabled. + - `128MB` +* - `hudi.minimum-assigned-split-weight` + - Minimum weight that a split can be assigned when size-based split weights + are enabled. + - `0.05` +* - `hudi.max-splits-per-second` + - Rate at which splits are queued for processing. The queue is throttled if + this rate limit is breached. + - `Integer.MAX_VALUE` +* - `hudi.max-outstanding-splits` + - Maximum outstanding splits in a batch enqueued for processing. + - `1000` +* - `hudi.per-transaction-metastore-cache-maximum-size` + - Maximum number of metastore data objects per transaction in the Hive + metastore cache. + - `2000` + +::: ## SQL support @@ -144,18 +144,17 @@ Hudi supports [two types of tables](https://hudi.apache.org/docs/table_types) depending on how the data is indexed and laid out on the file system. The following table displays a support matrix of tables types and query types for the connector: -```{eval-rst} -.. list-table:: Hudi configuration properties - :widths: 45, 55 - :header-rows: 1 - - * - Table type - - Supported query type - * - Copy on write - - Snapshot queries - * - Merge on read - - Read-optimized queries -``` +:::{list-table} Hudi configuration properties +:widths: 45, 55 +:header-rows: 1 + +* - Table type + - Supported query type +* - Copy on write + - Snapshot queries +* - Merge on read + - Read-optimized queries +::: (hudi-metadata-tables)= @@ -191,21 +190,21 @@ SELECT * FROM "test_table$timeline" The output of the query has the following columns: -```{eval-rst} -.. list-table:: Timeline columns - :widths: 20, 30, 50 - :header-rows: 1 - - * - Name - - Type - - Description - * - ``timestamp`` - - ``VARCHAR`` - - Instant time is typically a timestamp when the actions performed. - * - ``action`` - - ``VARCHAR`` - - `Type of action `_ performed on the table. - * - ``state`` - - ``VARCHAR`` - - Current state of the instant. -``` +:::{list-table} Timeline columns +:widths: 20, 30, 50 +:header-rows: 1 + +* - Name + - Type + - Description +* - `timestamp` + - `VARCHAR` + - Instant time is typically a timestamp when the actions performed. +* - `action` + - `VARCHAR` + - [Type of action](https://hudi.apache.org/docs/concepts/#timeline) performed + on the table. +* - `state` + - `VARCHAR` + - Current state of the instant. +::: diff --git a/docs/src/main/sphinx/connector/iceberg.md b/docs/src/main/sphinx/connector/iceberg.md index 2658e31af157..e70440af65da 100644 --- a/docs/src/main/sphinx/connector/iceberg.md +++ b/docs/src/main/sphinx/connector/iceberg.md @@ -69,98 +69,95 @@ with {ref}`general metastore configuration properties The following configuration properties are independent of which catalog implementation is used: -```{eval-rst} -.. list-table:: Iceberg general configuration properties - :widths: 30, 58, 12 - :header-rows: 1 - - * - Property name - - Description - - Default - * - ``iceberg.catalog.type`` - - Define the metastore type to use. Possible values are: - - * ``hive_metastore`` - * ``glue`` - * ``jdbc`` - * ``rest`` - * ``nessie`` - - - * - ``iceberg.file-format`` - - Define the data storage file format for Iceberg tables. - Possible values are: - - * ``PARQUET`` - * ``ORC`` - * ``AVRO`` - - ``PARQUET`` - * - ``iceberg.compression-codec`` - - The compression codec used when writing files. - Possible values are: - - * ``NONE`` - * ``SNAPPY`` - * ``LZ4`` - * ``ZSTD`` - * ``GZIP`` - - ``ZSTD`` - * - ``iceberg.use-file-size-from-metadata`` - - Read file sizes from metadata instead of file system. This property must - only be used as a workaround for `this issue - `_. The problem was fixed - in Iceberg version 0.11.0. - - ``true`` - * - ``iceberg.max-partitions-per-writer`` - - Maximum number of partitions handled per writer. - - ``100`` - * - ``iceberg.target-max-file-size`` - - Target maximum size of written files; the actual size may be larger. - - ``1GB`` - * - ``iceberg.unique-table-location`` - - Use randomized, unique table locations. - - ``true`` - * - ``iceberg.dynamic-filtering.wait-timeout`` - - Maximum duration to wait for completion of dynamic filters during split - generation. - - ``0s`` - * - ``iceberg.delete-schema-locations-fallback`` - - Whether schema locations are deleted when Trino can't determine whether - they contain external files. - - ``false`` - * - ``iceberg.minimum-assigned-split-weight`` - - A decimal value in the range (0, 1] used as a minimum for weights assigned - to each split. A low value may improve performance on tables with small - files. A higher value may improve performance for queries with highly - skewed aggregations or joins. - - 0.05 - * - ``iceberg.table-statistics-enabled`` - - Enables :doc:`/optimizer/statistics`. The equivalent :doc:`catalog session - property ` is ``statistics_enabled`` for session - specific use. Set to ``false`` to disable statistics. Disabling statistics - means that :doc:`/optimizer/cost-based-optimizations` cannot make better - decisions about the query plan. - - ``true`` - * - ``iceberg.projection-pushdown-enabled`` - - Enable :doc:`projection pushdown ` - - ``true`` - * - ``iceberg.hive-catalog-name`` - - Catalog to redirect to when a Hive table is referenced. - - - * - ``iceberg.materialized-views.storage-schema`` - - Schema for creating materialized views storage tables. When this property - is not configured, storage tables are created in the same schema as the - materialized view definition. When the ``storage_schema`` materialized - view property is specified, it takes precedence over this catalog - property. - - Empty - * - ``iceberg.register-table-procedure.enabled`` - - Enable to allow user to call ``register_table`` procedure. - - ``false`` - * - ``iceberg.query-partition-filter-required`` - - Set to ``true`` to force a query to use a partition filter. - You can use the ``query_partition_filter_required`` catalog session property for temporary, catalog specific use. - - ``false`` -``` +:::{list-table} Iceberg general configuration properties +:widths: 30, 58, 12 +:header-rows: 1 + +* - Property name + - Description + - Default +* - `iceberg.catalog.type` + - Define the metastore type to use. Possible values are: + + * `hive_metastore` + * `glue` + * `jdbc` + * `rest` + * `nessie` + - +* - `iceberg.file-format` + - Define the data storage file format for Iceberg tables. Possible values are: + + * `PARQUET` + * `ORC` + * `AVRO` + - `PARQUET` +* - `iceberg.compression-codec` + - The compression codec used when writing files. Possible values are: + + * `NONE` + * `SNAPPY` + * `LZ4` + * `ZSTD` + * `GZIP` + - `ZSTD` +* - `iceberg.use-file-size-from-metadata` + - Read file sizes from metadata instead of file system. This property must + only be used as a workaround for [this + issue](https://github.com/apache/iceberg/issues/1980). The problem was fixed + in Iceberg version 0.11.0. + - `true` +* - `iceberg.max-partitions-per-writer` + - Maximum number of partitions handled per writer. + - `100` +* - `iceberg.target-max-file-size` + - Target maximum size of written files; the actual size may be larger. + - `1GB` +* - `iceberg.unique-table-location` + - Use randomized, unique table locations. + - `true` +* - `iceberg.dynamic-filtering.wait-timeout` + - Maximum duration to wait for completion of dynamic filters during split + generation. + - `0s` +* - `iceberg.delete-schema-locations-fallback` + - Whether schema locations are deleted when Trino can't determine whether + they contain external files. + - `false` +* - `iceberg.minimum-assigned-split-weight` + - A decimal value in the range `(0, 1]` used as a minimum for weights assigned + to each split. A low value may improve performance on tables with small + files. A higher value may improve performance for queries with highly skewed + aggregations or joins. + - 0.05 +* - `iceberg.table-statistics-enabled` + - Enables [](/optimizer/statistics). The equivalent [catalog session + property](/sql/set-session) is `statistics_enabled` for session specific + use. Set to `false` to disable statistics. Disabling statistics means that + [](/optimizer/cost-based-optimizations) cannot make better decisions about + the query plan. + - `true` +* - `iceberg.projection-pushdown-enabled` + - Enable [projection pushdown](/optimizer/pushdown) + - `true` +* - `iceberg.hive-catalog-name` + - Catalog to redirect to when a Hive table is referenced. + - +* - `iceberg.materialized-views.storage-schema` + - Schema for creating materialized views storage tables. When this property is + not configured, storage tables are created in the same schema as the + materialized view definition. When the `storage_schema` materialized view + property is specified, it takes precedence over this catalog property. + - Empty +* - `iceberg.register-table-procedure.enabled` + - Enable to allow user to call `register_table` procedure. + - `false` +* - `iceberg.query-partition-filter-required` + - Set to `true` to force a query to use a partition filter. You can use the + `query_partition_filter_required` catalog session property for temporary, + catalog specific use. + - `false` +::: ## Type mapping @@ -185,48 +182,47 @@ formating in the Avro, ORC, or Parquet files: The connector maps Iceberg types to the corresponding Trino types according to the following table: -```{eval-rst} -.. list-table:: Iceberg to Trino type mapping - :widths: 40, 60 - :header-rows: 1 - - * - Iceberg type - - Trino type - * - ``BOOLEAN`` - - ``BOOLEAN`` - * - ``INT`` - - ``INTEGER`` - * - ``LONG`` - - ``BIGINT`` - * - ``FLOAT`` - - ``REAL`` - * - ``DOUBLE`` - - ``DOUBLE`` - * - ``DECIMAL(p,s)`` - - ``DECIMAL(p,s)`` - * - ``DATE`` - - ``DATE`` - * - ``TIME`` - - ``TIME(6)`` - * - ``TIMESTAMP`` - - ``TIMESTAMP(6)`` - * - ``TIMESTAMPTZ`` - - ``TIMESTAMP(6) WITH TIME ZONE`` - * - ``STRING`` - - ``VARCHAR`` - * - ``UUID`` - - ``UUID`` - * - ``BINARY`` - - ``VARBINARY`` - * - ``FIXED (L)`` - - ``VARBINARY`` - * - ``STRUCT(...)`` - - ``ROW(...)`` - * - ``LIST(e)`` - - ``ARRAY(e)`` - * - ``MAP(k,v)`` - - ``MAP(k,v)`` -``` +:::{list-table} Iceberg to Trino type mapping +:widths: 40, 60 +:header-rows: 1 + +* - Iceberg type + - Trino type +* - `BOOLEAN` + - `BOOLEAN` +* - `INT` + - `INTEGER` +* - `LONG` + - `BIGINT` +* - `FLOAT` + - `REAL` +* - `DOUBLE` + - `DOUBLE` +* - `DECIMAL(p,s)` + - `DECIMAL(p,s)` +* - `DATE` + - `DATE` +* - `TIME` + - `TIME(6)` +* - `TIMESTAMP` + - `TIMESTAMP(6)` +* - `TIMESTAMPTZ` + - `TIMESTAMP(6) WITH TIME ZONE` +* - `STRING` + - `VARCHAR` +* - `UUID` + - `UUID` +* - `BINARY` + - `VARBINARY` +* - `FIXED (L)` + - `VARBINARY` +* - `STRUCT(...)` + - `ROW(...)` +* - `LIST(e)` + - `ARRAY(e)` +* - `MAP(k,v)` + - `MAP(k,v)` +::: No other types are supported. @@ -235,46 +231,45 @@ No other types are supported. The connector maps Trino types to the corresponding Iceberg types according to the following table: -```{eval-rst} -.. list-table:: Trino to Iceberg type mapping - :widths: 40, 60 - :header-rows: 1 - - * - Trino type - - Iceberg type - * - ``BOOLEAN`` - - ``BOOLEAN`` - * - ``INTEGER`` - - ``INT`` - * - ``BIGINT`` - - ``LONG`` - * - ``REAL`` - - ``FLOAT`` - * - ``DOUBLE`` - - ``DOUBLE`` - * - ``DECIMAL(p,s)`` - - ``DECIMAL(p,s)`` - * - ``DATE`` - - ``DATE`` - * - ``TIME(6)`` - - ``TIME`` - * - ``TIMESTAMP(6)`` - - ``TIMESTAMP`` - * - ``TIMESTAMP(6) WITH TIME ZONE`` - - ``TIMESTAMPTZ`` - * - ``VARCHAR`` - - ``STRING`` - * - ``UUID`` - - ``UUID`` - * - ``VARBINARY`` - - ``BINARY`` - * - ``ROW(...)`` - - ``STRUCT(...)`` - * - ``ARRAY(e)`` - - ``LIST(e)`` - * - ``MAP(k,v)`` - - ``MAP(k,v)`` -``` +:::{list-table} Trino to Iceberg type mapping +:widths: 40, 60 +:header-rows: 1 + +* - Trino type + - Iceberg type +* - `BOOLEAN` + - `BOOLEAN` +* - `INTEGER` + - `INT` +* - `BIGINT` + - `LONG` +* - `REAL` + - `FLOAT` +* - `DOUBLE` + - `DOUBLE` +* - `DECIMAL(p,s)` + - `DECIMAL(p,s)` +* - `DATE` + - `DATE` +* - `TIME(6)` + - `TIME` +* - `TIMESTAMP(6)` + - `TIMESTAMP` +* - `TIMESTAMP(6) WITH TIME ZONE` + - `TIMESTAMPTZ` +* - `VARCHAR` + - `STRING` +* - `UUID` + - `UUID` +* - `VARBINARY` + - `BINARY` +* - `ROW(...)` + - `STRUCT(...)` +* - `ARRAY(e)` + - `LIST(e)` +* - `MAP(k,v)` + - `MAP(k,v)` +::: No other types are supported. @@ -291,29 +286,26 @@ You can enable authorization checks for the connector by setting the `iceberg.security` property in the catalog properties file. This property must be one of the following values: -```{eval-rst} -.. list-table:: Iceberg security values - :widths: 30, 60 - :header-rows: 1 - - * - Property value - - Description - * - ``ALLOW_ALL`` - - No authorization checks are enforced. - * - ``SYSTEM`` - - The connector relies on system-level access control. - * - ``READ_ONLY`` - - Operations that read data or metadata, such as :doc:`/sql/select` are - permitted. No operations that write data or metadata, such as - :doc:`/sql/create-table`, :doc:`/sql/insert`, or :doc:`/sql/delete` are - allowed. - * - ``FILE`` - - Authorization checks are enforced using a catalog-level access control - configuration file whose path is specified in the ``security.config-file`` - catalog configuration property. See - :ref:`catalog-file-based-access-control` for information on the - authorization configuration file. -``` +:::{list-table} Iceberg security values +:widths: 30, 60 +:header-rows: 1 + +* - Property value + - Description +* - `ALLOW_ALL` + - No authorization checks are enforced. +* - `SYSTEM` + - The connector relies on system-level access control. +* - `READ_ONLY` + - Operations that read data or metadata, such as [](/sql/select) are + permitted. No operations that write data or metadata, such as + [](/sql/create-table), [](/sql/insert), or [](/sql/delete) are allowed. +* - `FILE` + - Authorization checks are enforced using a catalog-level access control + configuration file whose path is specified in the `security.config-file` + catalog configuration property. See [](catalog-file-based-access-control) + for information on the authorization configuration file. +::: (iceberg-sql-support)= @@ -661,35 +653,34 @@ for {doc}`/sql/create-table-as` statements. Table properties are passed to the connector using a {doc}`WITH ` clause. -```{eval-rst} -.. list-table:: Iceberg table properties - :widths: 40, 60 - :header-rows: 1 - - * - Property name - - Description - * - ``format`` - - Optionally specifies the format of table data files; either ``PARQUET``, - ``ORC`, or ``AVRO``. Defaults to the value of the ``iceberg.file-format`` - catalog configuration property, which defaults to ``PARQUET``. - * - ``partitioning`` - - Optionally specifies table partitioning. If a table is partitioned by - columns ``c1`` and ``c2``, the partitioning property is ``partitioning = - ARRAY['c1', 'c2']``. - * - ``location`` - - Optionally specifies the file system location URI for the table. - * - ``format_version`` - - Optionally specifies the format version of the Iceberg specification to - use for new tables; either ``1`` or ``2``. Defaults to ``2``. Version - ``2`` is required for row level deletes. - * - ``orc_bloom_filter_columns`` - - Comma-separated list of columns to use for ORC bloom filter. It improves - the performance of queries using Equality and IN predicates when reading - ORC files. Requires ORC format. Defaults to ``[]``. - * - ``orc_bloom_filter_fpp`` - - The ORC bloom filters false positive probability. Requires ORC format. - Defaults to ``0.05``. -``` +:::{list-table} Iceberg table properties +:widths: 40, 60 +:header-rows: 1 + +* - Property name + - Description +* - `format` + - Optionally specifies the format of table data files; either `PARQUET`, + `ORC`, or `AVRO`. Defaults to the value of the `iceberg.file-format` catalog + configuration property, which defaults to `PARQUET`. +* - `partitioning` + - Optionally specifies table partitioning. If a table is partitioned by + columns `c1` and `c2`, the partitioning property is `partitioning = + ARRAY['c1', 'c2']`. +* - `location` + - Optionally specifies the file system location URI for the table. +* - `format_version` + - Optionally specifies the format version of the Iceberg specification to use + for new tables; either `1` or `2`. Defaults to `2`. Version `2` is required + for row level deletes. +* - `orc_bloom_filter_columns` + - Comma-separated list of columns to use for ORC bloom filter. It improves the + performance of queries using Equality and IN predicates when reading ORC + files. Requires ORC format. Defaults to `[]`. +* - `orc_bloom_filter_fpp` + - The ORC bloom filters false positive probability. Requires ORC format. + Defaults to `0.05`. +::: The table definition below specifies to use Parquet files, partitioning by columns `c1` and `c2`, and a file system location of @@ -775,27 +766,26 @@ SELECT * FROM "test_table$history" The output of the query has the following columns: -```{eval-rst} -.. list-table:: History columns - :widths: 30, 30, 40 - :header-rows: 1 - - * - Name - - Type - - Description - * - ``made_current_at`` - - ``TIMESTAMP(3) WITH TIME ZONE`` - - The time when the snapshot became active. - * - ``snapshot_id`` - - ``BIGINT`` - - The identifier of the snapshot. - * - ``parent_id`` - - ``BIGINT`` - - The identifier of the parent snapshot. - * - ``is_current_ancestor`` - - ``BOOLEAN`` - - Whether or not this snapshot is an ancestor of the current snapshot. -``` +:::{list-table} History columns +:widths: 30, 30, 40 +:header-rows: 1 + +* - Name + - Type + - Description +* - `made_current_at` + - `TIMESTAMP(3) WITH TIME ZONE` + - The time when the snapshot became active. +* - `snapshot_id` + - `BIGINT` + - The identifier of the snapshot. +* - `parent_id` + - `BIGINT` + - The identifier of the parent snapshot. +* - `is_current_ancestor` + - `BOOLEAN` + - Whether or not this snapshot is an ancestor of the current snapshot. +::: ##### `$snapshots` table @@ -819,42 +809,41 @@ SELECT * FROM "test_table$snapshots" The output of the query has the following columns: -```{eval-rst} -.. list-table:: Snapshots columns - :widths: 20, 30, 50 - :header-rows: 1 - - * - Name - - Type - - Description - * - ``committed_at`` - - ``TIMESTAMP(3) WITH TIME ZONE`` - - The time when the snapshot became active. - * - ``snapshot_id`` - - ``BIGINT`` - - The identifier for the snapshot. - * - ``parent_id`` - - ``BIGINT`` - - The identifier for the parent snapshot. - * - ``operation`` - - ``VARCHAR`` - - The type of operation performed on the Iceberg table. The supported - operation types in Iceberg are: - - * ``append`` when new data is appended. - * ``replace`` when files are removed and replaced without changing the - data in the table. - * ``overwrite`` when new data is added to overwrite existing data. - * ``delete`` when data is deleted from the table and no new data is added. - * - ``manifest_list`` - - ``VARCHAR`` - - The list of Avro manifest files containing the detailed information about - the snapshot changes. - * - ``summary`` - - ``map(VARCHAR, VARCHAR)`` - - A summary of the changes made from the previous snapshot to the current - snapshot. -``` +:::{list-table} Snapshots columns +:widths: 20, 30, 50 +:header-rows: 1 + +* - Name + - Type + - Description +* - `committed_at` + - `TIMESTAMP(3) WITH TIME ZONE` + - The time when the snapshot became active. +* - `snapshot_id` + - `BIGINT` + - The identifier for the snapshot. +* - `parent_id` + - `BIGINT` + - The identifier for the parent snapshot. +* - `operation` + - `VARCHAR` + - The type of operation performed on the Iceberg table. The supported + operation types in Iceberg are: + + * `append` when new data is appended. + * `replace` when files are removed and replaced without changing the + data in the table. + * `overwrite` when new data is added to overwrite existing data. + * `delete` when data is deleted from the table and no new data is added. +* - `manifest_list` + - `VARCHAR` + - The list of Avro manifest files containing the detailed information about + the snapshot changes. +* - `summary` + - `map(VARCHAR, VARCHAR)` + - A summary of the changes made from the previous snapshot to the current + snapshot. +::: ##### `$manifests` table @@ -876,53 +865,52 @@ SELECT * FROM "test_table$manifests" The output of the query has the following columns: -```{eval-rst} -.. list-table:: Manifests columns - :widths: 30, 30, 40 - :header-rows: 1 - - * - Name - - Type - - Description - * - ``path`` - - ``VARCHAR`` - - The manifest file location. - * - ``length`` - - ``BIGINT`` - - The manifest file length. - * - ``partition_spec_id`` - - ``INTEGER`` - - The identifier for the partition specification used to write the manifest - file. - * - ``added_snapshot_id`` - - ``BIGINT`` - - The identifier of the snapshot during which this manifest entry has been - added. - * - ``added_data_files_count`` - - ``INTEGER`` - - The number of data files with status ``ADDED`` in the manifest file. - * - ``added_rows_count`` - - ``BIGINT`` - - The total number of rows in all data files with status ``ADDED`` in the - manifest file. - * - ``existing_data_files_count`` - - ``INTEGER`` - - The number of data files with status ``EXISTING`` in the manifest file. - * - ``existing_rows_count`` - - ``BIGINT`` - - The total number of rows in all data files with status ``EXISTING`` in the - manifest file. - * - ``deleted_data_files_count`` - - ``INTEGER`` - - The number of data files with status ``DELETED`` in the manifest file. - * - ``deleted_rows_count`` - - ``BIGINT`` - - The total number of rows in all data files with status ``DELETED`` in the - manifest file. - * - ``partitions`` - - ``ARRAY(row(contains_null BOOLEAN, contains_nan BOOLEAN, lower_bound VARCHAR, upper_bound VARCHAR))`` - - Partition range metadata. -``` +:::{list-table} Manifests columns +:widths: 30, 30, 40 +:header-rows: 1 + +* - Name + - Type + - Description +* - `path` + - `VARCHAR` + - The manifest file location. +* - `length` + - `BIGINT` + - The manifest file length. +* - `partition_spec_id` + - `INTEGER` + - The identifier for the partition specification used to write the manifest + file. +* - `added_snapshot_id` + - `BIGINT` + - The identifier of the snapshot during which this manifest entry has been + added. +* - `added_data_files_count` + - `INTEGER` + - The number of data files with status `ADDED` in the manifest file. +* - `added_rows_count` + - `BIGINT` + - The total number of rows in all data files with status `ADDED` in the + manifest file. +* - `existing_data_files_count` + - `INTEGER` + - The number of data files with status `EXISTING` in the manifest file. +* - `existing_rows_count` + - `BIGINT` + - The total number of rows in all data files with status `EXISTING` in the + manifest file. +* - `deleted_data_files_count` + - `INTEGER` + - The number of data files with status `DELETED` in the manifest file. +* - `deleted_rows_count` + - `BIGINT` + - The total number of rows in all data files with status `DELETED` in the + manifest file. +* - `partitions` + - `ARRAY(row(contains_null BOOLEAN, contains_nan BOOLEAN, lower_bound VARCHAR, upper_bound VARCHAR))` + - Partition range metadata. +::: ##### `$partitions` table @@ -945,31 +933,30 @@ SELECT * FROM "test_table$partitions" The output of the query has the following columns: -```{eval-rst} -.. list-table:: Partitions columns - :widths: 20, 30, 50 - :header-rows: 1 - - * - Name - - Type - - Description - * - ``partition`` - - ``ROW(...)`` - - A row that contains the mapping of the partition column names to the - partition column values. - * - ``record_count`` - - ``BIGINT`` - - The number of records in the partition. - * - ``file_count`` - - ``BIGINT`` - - The number of files mapped in the partition. - * - ``total_size`` - - ``BIGINT`` - - The size of all the files in the partition. - * - ``data`` - - ``ROW(... ROW (min ..., max ... , null_count BIGINT, nan_count BIGINT))`` - - Partition range metadata. -``` +:::{list-table} Partitions columns +:widths: 20, 30, 50 +:header-rows: 1 + +* - Name + - Type + - Description +* - `partition` + - `ROW(...)` + - A row that contains the mapping of the partition column names to the + partition column values. +* - `record_count` + - `BIGINT` + - The number of records in the partition. +* - `file_count` + - `BIGINT` + - The number of files mapped in the partition. +* - `total_size` + - `BIGINT` + - The size of all the files in the partition. +* - `data` + - `ROW(... ROW (min ..., max ... , null_count BIGINT, nan_count BIGINT))` + - Partition range metadata. +::: ##### `$files` table @@ -991,68 +978,67 @@ SELECT * FROM "test_table$files" The output of the query has the following columns: -```{eval-rst} -.. list-table:: Files columns - :widths: 25, 30, 45 - :header-rows: 1 - - * - Name - - Type - - Description - * - ``content`` - - ``INTEGER`` - - Type of content stored in the file. The supported content types in Iceberg - are: - - * ``DATA(0)`` - * ``POSITION_DELETES(1)`` - * ``EQUALITY_DELETES(2)`` - * - ``file_path`` - - ``VARCHAR`` - - The data file location. - * - ``file_format`` - - ``VARCHAR`` - - The format of the data file. - * - ``record_count`` - - ``BIGINT`` - - The number of entries contained in the data file. - * - ``file_size_in_bytes`` - - ``BIGINT`` - - The data file size - * - ``column_sizes`` - - ``map(INTEGER, BIGINT)`` - - Mapping between the Iceberg column ID and its corresponding size in the - file. - * - ``value_counts`` - - ``map(INTEGER, BIGINT)`` - - Mapping between the Iceberg column ID and its corresponding count of - entries in the file. - * - ``null_value_counts`` - - ``map(INTEGER, BIGINT)`` - - Mapping between the Iceberg column ID and its corresponding count of - ``NULL`` values in the file. - * - ``nan_value_counts`` - - ``map(INTEGER, BIGINT)`` - - Mapping between the Iceberg column ID and its corresponding count of non- - numerical values in the file. - * - ``lower_bounds`` - - ``map(INTEGER, BIGINT)`` - - Mapping between the Iceberg column ID and its corresponding lower bound in - the file. - * - ``upper_bounds`` - - ``map(INTEGER, BIGINT)`` - - Mapping between the Iceberg column ID and its corresponding upper bound in - the file. - * - ``key_metadata`` - - ``VARBINARY`` - - Metadata about the encryption key used to encrypt this file, if applicable. - * - ``split_offsets`` - - ``array(BIGINT)`` - - List of recommended split locations. - * - ``equality_ids`` - - ``array(INTEGER)`` - - The set of field IDs used for equality comparison in equality delete files. -``` +:::{list-table} Files columns +:widths: 25, 30, 45 +:header-rows: 1 + +* - Name + - Type + - Description +* - `content` + - `INTEGER` + - Type of content stored in the file. The supported content types in Iceberg + are: + + * `DATA(0)` + * `POSITION_DELETES(1)` + * `EQUALITY_DELETES(2)` +* - `file_path` + - `VARCHAR` + - The data file location. +* - `file_format` + - `VARCHAR` + - The format of the data file. +* - `record_count` + - `BIGINT` + - The number of entries contained in the data file. +* - `file_size_in_bytes` + - `BIGINT` + - The data file size +* - `column_sizes` + - `map(INTEGER, BIGINT)` + - Mapping between the Iceberg column ID and its corresponding size in the + file. +* - `value_counts` + - `map(INTEGER, BIGINT)` + - Mapping between the Iceberg column ID and its corresponding count of entries + in the file. +* - `null_value_counts` + - `map(INTEGER, BIGINT)` + - Mapping between the Iceberg column ID and its corresponding count of `NULL` + values in the file. +* - `nan_value_counts` + - `map(INTEGER, BIGINT)` + - Mapping between the Iceberg column ID and its corresponding count of non- + numerical values in the file. +* - `lower_bounds` + - `map(INTEGER, BIGINT)` + - Mapping between the Iceberg column ID and its corresponding lower bound in + the file. +* - `upper_bounds` + - `map(INTEGER, BIGINT)` + - Mapping between the Iceberg column ID and its corresponding upper bound in + the file. +* - `key_metadata` + - `VARBINARY` + - Metadata about the encryption key used to encrypt this file, if applicable. +* - `split_offsets` + - `array(BIGINT)` + - List of recommended split locations. +* - `equality_ids` + - `array(INTEGER)` + - The set of field IDs used for equality comparison in equality delete files. +::: ##### `$refs` table @@ -1075,34 +1061,33 @@ example_branch | BRANCH | 20000000000 | 20000 | 2 The output of the query has the following columns: -```{eval-rst} -.. list-table:: Refs columns - :widths: 20, 30, 50 - :header-rows: 1 - - * - Name - - Type - - Description - * - ``name`` - - ``VARCHAR`` - - Name of the reference. - * - ``type`` - - ``VARCHAR`` - - Type of the reference, either ``BRANCH`` or ``TAG``. - * - ``snapshot_id`` - - ``BIGINT`` - - The snapshot ID of the reference. - * - ``max_reference_age_in_ms`` - - ``BIGINT`` - - The maximum age of the reference before it could be expired. - * - ``min_snapshots_to_keep`` - - ``INTEGER`` - - For branch only, the minimum number of snapshots to keep in a branch. - * - ``max_snapshot_age_in_ms`` - - ``BIGINT`` - - For branch only, the max snapshot age allowed in a branch. Older snapshots - in the branch will be expired. -``` +:::{list-table} Refs columns +:widths: 20, 30, 50 +:header-rows: 1 + +* - Name + - Type + - Description +* - `name` + - `VARCHAR` + - Name of the reference. +* - `type` + - `VARCHAR` + - Type of the reference, either `BRANCH` or `TAG`. +* - `snapshot_id` + - `BIGINT` + - The snapshot ID of the reference. +* - `max_reference_age_in_ms` + - `BIGINT` + - The maximum age of the reference before it could be expired. +* - `min_snapshots_to_keep` + - `INTEGER` + - For branch only, the minimum number of snapshots to keep in a branch. +* - `max_snapshot_age_in_ms` + - `BIGINT` + - For branch only, the max snapshot age allowed in a branch. Older snapshots + in the branch will be expired. +::: (iceberg-metadata-columns)= @@ -1178,32 +1163,31 @@ Iceberg supports partitioning by specifying transforms over the table columns. A partition is created for each unique tuple value produced by the transforms. Identity transforms are simply the column name. Other transforms are: -```{eval-rst} -.. list-table:: Iceberg column transforms - :widths: 40, 60 - :header-rows: 1 - - * - Transform - - Description - * - ``year(ts)`` - - A partition is created for each year. The partition value is the integer - difference in years between ``ts`` and January 1 1970. - * - ``month(ts)`` - - A partition is created for each month of each year. The partition value - is the integer difference in months between ``ts`` and January 1 1970. - * - ``day(ts)`` - - A partition is created for each day of each year. The partition value is - the integer difference in days between ``ts`` and January 1 1970. - * - ``hour(ts)`` - - A partition is created hour of each day. The partition value is a - timestamp with the minutes and seconds set to zero. - * - ``bucket(x, nbuckets)`` - - The data is hashed into the specified number of buckets. The partition - value is an integer hash of ``x``, with a value between 0 and ``nbuckets - - 1`` inclusive. - * - ``truncate(s, nchars)`` - - The partition value is the first ``nchars`` characters of ``s``. -``` +:::{list-table} Iceberg column transforms +:widths: 40, 60 +:header-rows: 1 + +* - Transform + - Description +* - `year(ts)` + - A partition is created for each year. The partition value is the integer + difference in years between `ts` and January 1 1970. +* - `month(ts)` + - A partition is created for each month of each year. The partition value is + the integer difference in months between `ts` and January 1 1970. +* - `day(ts)` + - A partition is created for each day of each year. The partition value is the + integer difference in days between `ts` and January 1 1970. +* - `hour(ts)` + - A partition is created hour of each day. The partition value is a timestamp + with the minutes and seconds set to zero. +* - `bucket(x, nbuckets)` + - The data is hashed into the specified number of buckets. The partition value + is an integer hash of `x`, with a value between 0 and `nbuckets - 1` + inclusive. +* - `truncate(s, nchars)` + - The partition value is the first `nchars` characters of `s`. +::: In this example, the table is partitioned by the month of `order_date`, a hash of `account_number` (with 10 buckets), and `country`: diff --git a/docs/src/main/sphinx/connector/ignite.md b/docs/src/main/sphinx/connector/ignite.md index 77793bfee44f..ebf5e15d478b 100644 --- a/docs/src/main/sphinx/connector/ignite.md +++ b/docs/src/main/sphinx/connector/ignite.md @@ -100,18 +100,18 @@ WITH ( The following are supported Ignite table properties from [https://ignite.apache.org/docs/latest/sql-reference/ddl](https://ignite.apache.org/docs/latest/sql-reference/ddl) -```{eval-rst} -.. list-table:: - :widths: 30, 10, 100 - :header-rows: 1 - - * - Property name - - Required - - Description - * - ``primary_key`` - - No - - ``The primary key of the table, can chose multi columns as the table primary key. Table at least contains one column not in primary key.`` -``` +:::{list-table} +:widths: 30, 10, 100 +:header-rows: 1 + +* - Property name + - Required + - Description +* - `primary_key` + - No + - The primary key of the table, can chose multi columns as the table primary + key. Table at least contains one column not in primary key. +::: ### `primary_key` @@ -124,51 +124,50 @@ the value is derived from the value generated by the `UUID` function in Ignite. The following are supported Ignite SQL data types from [https://ignite.apache.org/docs/latest/sql-reference/data-types](https://ignite.apache.org/docs/latest/sql-reference/data-types) -````{eval-rst} -.. list-table:: - :widths: 30, 30, 20 - :header-rows: 1 - - * - Ignite SQL data type name - - Map to Trino type - - Possible values - * - ``BOOLEAN`` - - ``BOOLEAN`` - - ``TRUE`` and ``FALSE`` - * - ``BIGINT`` - - ``BIGINT`` - - ``-9223372036854775808``, ``9223372036854775807``, etc. - * - ``DECIMAL`` - - ``DECIMAL`` - - Data type with fixed precision and scale - * - ``DOUBLE`` - - ``DOUBLE`` - - ``3.14``, ``-10.24``, etc. - * - ``INT`` - - ``INT`` - - ``-2147483648``, ``2147483647``, etc. - * - ``REAL`` - - ``REAL``` - - ``3.14``, ``-10.24``, etc. - * - ``SMALLINT`` - - ``SMALLINT`` - - ``-32768``, ``32767``, etc. - * - ``TINYINT`` - - ``TINYINT`` - - ``-128``, ``127``, etc. - * - ``CHAR`` - - ``CHAR`` - - ``hello``, ``Trino``, etc. - * - ``VARCHAR`` - - ``VARCHAR`` - - ``hello``, ``Trino``, etc. - * - ``DATE`` - - ``DATE`` - - ``1972-01-01``, ``2021-07-15``, etc. - * - ``BINARY`` - - ``VARBINARY`` - - Represents a byte array. -```` +:::{list-table} +:widths: 25, 25, 50 +:header-rows: 1 + +* - Ignite SQL data type name + - Map to Trino type + - Possible values +* - `BOOLEAN` + - `BOOLEAN` + - `TRUE` and `FALSE` +* - `BIGINT` + - `BIGINT` + - `-9223372036854775808`, `9223372036854775807`, etc. +* - `DECIMAL` + - `DECIMAL` + - Data type with fixed precision and scale +* - `DOUBLE` + - `DOUBLE` + - `3.14`, `-10.24`, etc. +* - `INT` + - `INT` + - `-2147483648`, `2147483647`, etc. +* - `REAL` + - `REAL` + - `3.14`, `-10.24`, etc. +* - `SMALLINT` + - `SMALLINT` + - `-32768`, `32767`, etc. +* - `TINYINT` + - `TINYINT` + - `-128`, `127`, etc. +* - `CHAR` + - `CHAR` + - `hello`, `Trino`, etc. +* - `VARCHAR` + - `VARCHAR` + - `hello`, `Trino`, etc. +* - `DATE` + - `DATE` + - `1972-01-01`, `2021-07-15`, etc. +* - `BINARY` + - `VARBINARY` + - Represents a byte array. +::: (ignite-sql-support)= diff --git a/docs/src/main/sphinx/connector/kafka.md b/docs/src/main/sphinx/connector/kafka.md index 051063df37f8..b708a8635173 100644 --- a/docs/src/main/sphinx/connector/kafka.md +++ b/docs/src/main/sphinx/connector/kafka.md @@ -409,41 +409,41 @@ schema registry. You must also configure the additional properties in the follow Inserts are not supported, and the only data format supported is AVRO. ::: -```{eval-rst} -.. list-table:: Confluent table description supplier properties - :widths: 30, 55, 15 - :header-rows: 1 - - * - Property name - - Description - - Default value - * - ``kafka.confluent-schema-registry-url`` - - Comma-separated list of URL addresses for the Confluent schema registry. - For example, ``http://schema-registry-1.example.org:8081,http://schema-registry-2.example.org:8081`` - - - * - ``kafka.confluent-schema-registry-client-cache-size`` - - The maximum number of subjects that can be stored in the local cache. The - cache stores the schemas locally by subjectId, and is provided by the - Confluent ``CachingSchemaRegistry`` client. - - 1000 - * - ``kafka.empty-field-strategy`` - - Avro allows empty struct fields, but this is not allowed in Trino. - There are three strategies for handling empty struct fields: - - * ``IGNORE`` - Ignore structs with no fields. This propagates to parents. - For example, an array of structs with no fields is ignored. - * ``FAIL`` - Fail the query if a struct with no fields is defined. - * ``MARK`` - Add a marker field named ``$empty_field_marker``, which of type boolean with a null value. - This may be desired if the struct represents a marker field. - - This can also be modified via the ``empty_field_strategy`` session property. - - ``IGNORE`` - * - ``kafka.confluent-subjects-cache-refresh-interval`` - - The interval used for refreshing the list of subjects and the definition - of the schema for the subject in the subject's cache. - - ``1s`` +:::{list-table} Confluent table description supplier properties +:widths: 30, 55, 15 +:header-rows: 1 -``` +* - Property name + - Description + - Default value +* - `kafka.confluent-schema-registry-url` + - Comma-separated list of URL addresses for the Confluent schema registry. + For example, `http://schema-registry-1.example.org:8081,http://schema-registry-2.example.org:8081` + - +* - `kafka.confluent-schema-registry-client-cache-size` + - The maximum number of subjects that can be stored in the local cache. The + cache stores the schemas locally by subjectId, and is provided by the + Confluent `CachingSchemaRegistry` client. + - 1000 +* - `kafka.empty-field-strategy` + - Avro allows empty struct fields, but this is not allowed in Trino. There are + three strategies for handling empty struct fields: + + * `IGNORE` - Ignore structs with no fields. This propagates to parents. + For example, an array of structs with no fields is ignored. + * `FAIL` - Fail the query if a struct with no fields is defined. + * `MARK` - Add a marker field named `$empty_field_marker`, which of type + boolean with a null value. This may be desired if the struct represents + a marker field. + + This can also be modified via the `empty_field_strategy` session property. + - `IGNORE` +* - `kafka.confluent-subjects-cache-refresh-interval` + - The interval used for refreshing the list of subjects and the definition + of the schema for the subject in the subject's cache. + - `1s` + +::: #### Confluent subject to table name mapping diff --git a/docs/src/main/sphinx/connector/kudu.md b/docs/src/main/sphinx/connector/kudu.md index 70fab26ba2ce..622525c3846f 100644 --- a/docs/src/main/sphinx/connector/kudu.md +++ b/docs/src/main/sphinx/connector/kudu.md @@ -193,36 +193,35 @@ each direction. The connector maps Kudu types to the corresponding Trino types following this table: -```{eval-rst} -.. list-table:: Kudu type to Trino type mapping - :widths: 30, 20 - :header-rows: 1 - - * - Kudu type - - Trino type - * - ``BOOL`` - - ``BOOLEAN`` - * - ``INT8`` - - ``TINYINT`` - * - ``INT16`` - - ``SMALLINT`` - * - ``INT32`` - - ``INTEGER`` - * - ``INT64`` - - ``BIGINT`` - * - ``FLOAT`` - - ``REAL`` - * - ``DOUBLE`` - - ``DOUBLE`` - * - ``DECIMAL(p,s)`` - - ``DECIMAL(p,s)`` - * - ``STRING`` - - ``VARCHAR`` - * - ``BINARY`` - - ``VARBINARY`` - * - ``UNIXTIME_MICROS`` - - ``TIMESTAMP(3)`` -``` +:::{list-table} Kudu type to Trino type mapping +:widths: 50, 50 +:header-rows: 1 + +* - Kudu type + - Trino type +* - `BOOL` + - `BOOLEAN` +* - `INT8` + - `TINYINT` +* - `INT16` + - `SMALLINT` +* - `INT32` + - `INTEGER` +* - `INT64` + - `BIGINT` +* - `FLOAT` + - `REAL` +* - `DOUBLE` + - `DOUBLE` +* - `DECIMAL(p,s)` + - `DECIMAL(p,s)` +* - `STRING` + - `VARCHAR` +* - `BINARY` + - `VARBINARY` +* - `UNIXTIME_MICROS` + - `TIMESTAMP(3)` +::: No other types are supported. @@ -231,51 +230,50 @@ No other types are supported. The connector maps Trino types to the corresponding Kudu types following this table: -```{eval-rst} -.. list-table:: Trino type to Kudu type mapping - :widths: 30, 20, 50 - :header-rows: 1 - - * - Trino type - - Kudu type - - Notes - * - ``BOOLEAN`` - - ``BOOL`` - - - * - ``TINYINT`` - - ``INT8`` - - - * - ``SMALLINT`` - - ``INT16`` - - - * - ``INTEGER`` - - ``INT32`` - - - * - ``BIGINT`` - - ``INT64`` - - - * - ``REAL`` - - ``FLOAT`` - - - * - ``DOUBLE`` - - ``DOUBLE`` - - - * - ``DECIMAL(p,s)`` - - ``DECIMAL(p,s)`` - - Only supported for Kudu server >= 1.7.0 - * - ``VARCHAR`` - - ``STRING`` - - The optional maximum length is lost - * - ``VARBINARY`` - - ``BINARY`` - - - * - ``DATE`` - - ``STRING`` - - - * - ``TIMESTAMP(3)`` - - ``UNIXTIME_MICROS`` - - µs resolution in Kudu column is reduced to ms resolution -``` +:::{list-table} Trino type to Kudu type mapping +:widths: 25, 25, 50 +:header-rows: 1 + +* - Trino type + - Kudu type + - Notes +* - `BOOLEAN` + - `BOOL` + - +* - `TINYINT` + - `INT8` + - +* - `SMALLINT` + - `INT16` + - +* - `INTEGER` + - `INT32` + - +* - `BIGINT` + - `INT64` + - +* - `REAL` + - `FLOAT` + - +* - `DOUBLE` + - `DOUBLE` + - +* - `DECIMAL(p,s)` + - `DECIMAL(p,s)` + - Only supported for Kudu server >= 1.7.0 +* - `VARCHAR` + - `STRING` + - The optional maximum length is lost +* - `VARBINARY` + - `BINARY` + - +* - `DATE` + - `STRING` + - +* - `TIMESTAMP(3)` + - `UNIXTIME_MICROS` + - µs resolution in Kudu column is reduced to ms resolution +::: No other types are supported. diff --git a/docs/src/main/sphinx/connector/mariadb.md b/docs/src/main/sphinx/connector/mariadb.md index 2f78a0d702d5..700843215c71 100644 --- a/docs/src/main/sphinx/connector/mariadb.md +++ b/docs/src/main/sphinx/connector/mariadb.md @@ -103,96 +103,94 @@ each direction. The connector maps MariaDB types to the corresponding Trino types according to the following table: -```{eval-rst} -.. list-table:: MariaDB type to Trino type mapping - :widths: 30, 30, 50 - :header-rows: 1 - - * - MariaDB type - - Trino type - - Notes - * - ``BOOLEAN`` - - ``TINYINT`` - - ``BOOL`` and ``BOOLEAN`` are aliases of ``TINYINT(1)`` - * - ``TINYINT`` - - ``TINYINT`` - - - * - ``TINYINT UNSIGNED`` - - ``SMALLINT`` - - - * - ``SMALLINT`` - - ``SMALLINT`` - - - * - ``SMALLINT UNSIGNED`` - - ``INTEGER`` - - - * - ``INT`` - - ``INTEGER`` - - - * - ``INT UNSIGNED`` - - ``BIGINT`` - - - * - ``BIGINT`` - - ``BIGINT`` - - - * - ``BIGINT UNSIGNED`` - - ``DECIMAL(20, 0)`` - - - * - ``FLOAT`` - - ``REAL`` - - - * - ``DOUBLE`` - - ``DOUBLE`` - - - * - ``DECIMAL(p,s)`` - - ``DECIMAL(p,s)`` - - - * - ``CHAR(n)`` - - ``CHAR(n)`` - - - * - ``TINYTEXT`` - - ``VARCHAR(255)`` - - - * - ``TEXT`` - - ``VARCHAR(65535)`` - - - * - ``MEDIUMTEXT`` - - ``VARCHAR(16777215)`` - - - * - ``LONGTEXT`` - - ``VARCHAR`` - - - * - ``VARCHAR(n)`` - - ``VARCHAR(n)`` - - - * - ``TINYBLOB`` - - ``VARBINARY`` - - - * - ``BLOB`` - - ``VARBINARY`` - - - * - ``MEDIUMBLOB`` - - ``VARBINARY`` - - - * - ``LONGBLOB`` - - ``VARBINARY`` - - - * - ``VARBINARY(n)`` - - ``VARBINARY`` - - - * - ``DATE`` - - ``DATE`` - - - * - ``TIME(n)`` - - ``TIME(n)`` - - - * - ``TIMESTAMP(n)`` - - ``TIMESTAMP(n)`` - - MariaDB stores the current timestamp by default. Enable - `explicit_defaults_for_timestamp - `_ - to avoid implicit default values and use ``NULL`` as the default value. -``` +:::{list-table} MariaDB type to Trino type mapping +:widths: 30, 30, 50 +:header-rows: 1 + +* - MariaDB type + - Trino type + - Notes +* - `BOOLEAN` + - `TINYINT` + - `BOOL` and `BOOLEAN` are aliases of `TINYINT(1)` +* - `TINYINT` + - `TINYINT` + - +* - `TINYINT UNSIGNED` + - `SMALLINT` + - +* - `SMALLINT` + - `SMALLINT` + - +* - `SMALLINT UNSIGNED` + - `INTEGER` + - +* - `INT` + - `INTEGER` + - +* - `INT UNSIGNED` + - `BIGINT` + - +* - `BIGINT` + - `BIGINT` + - +* - `BIGINT UNSIGNED` + - `DECIMAL(20, 0)` + - +* - `FLOAT` + - `REAL` + - +* - `DOUBLE` + - `DOUBLE` + - +* - `DECIMAL(p,s)` + - `DECIMAL(p,s)` + - +* - `CHAR(n)` + - `CHAR(n)` + - +* - `TINYTEXT` + - `VARCHAR(255)` + - +* - `TEXT` + - `VARCHAR(65535)` + - +* - `MEDIUMTEXT` + - `VARCHAR(16777215)` + - +* - `LONGTEXT` + - `VARCHAR` + - +* - `VARCHAR(n)` + - `VARCHAR(n)` + - +* - `TINYBLOB` + - `VARBINARY` + - +* - `BLOB` + - `VARBINARY` + - +* - `MEDIUMBLOB` + - `VARBINARY` + - +* - `LONGBLOB` + - `VARBINARY` + - +* - `VARBINARY(n)` + - `VARBINARY` + - +* - `DATE` + - `DATE` + - +* - `TIME(n)` + - `TIME(n)` + - +* - `TIMESTAMP(n)` + - `TIMESTAMP(n)` + - MariaDB stores the current timestamp by default. Enable + [explicit_defaults_for_timestamp](https://mariadb.com/docs/reference/mdb/system-variables/explicit_defaults_for_timestamp/) + to avoid implicit default values and use `NULL` as the default value. +::: No other types are supported. @@ -201,70 +199,70 @@ No other types are supported. The connector maps Trino types to the corresponding MariaDB types according to the following table: -```{eval-rst} -.. list-table:: Trino type mapping to MariaDB type mapping - :widths: 30, 25, 50 - :header-rows: 1 - - * - Trino type - - MariaDB type - - Notes - * - ``BOOLEAN`` - - ``BOOLEAN`` - - - * - ``TINYINT`` - - ``TINYINT`` - - - * - ``SMALLINT`` - - ``SMALLINT`` - - - * - ``INTEGER`` - - ``INT`` - - - * - ``BIGINT`` - - ``BIGINT`` - - - * - ``REAL`` - - ``FLOAT`` - - - * - ``DOUBLE`` - - ``DOUBLE`` - - - * - ``DECIMAL(p,s)`` - - ``DECIMAL(p,s)`` - - - * - ``CHAR(n)`` - - ``CHAR(n)`` - - - * - ``VARCHAR(255)`` - - ``TINYTEXT`` - - Maps on ``VARCHAR`` of length 255 or less. - * - ``VARCHAR(65535)`` - - ``TEXT`` - - Maps on ``VARCHAR`` of length between 256 and 65535, inclusive. - * - ``VARCHAR(16777215)`` - - ``MEDIUMTEXT`` - - Maps on ``VARCHAR`` of length between 65536 and 16777215, inclusive. - * - ``VARCHAR`` - - ``LONGTEXT`` - - ``VARCHAR`` of length greater than 16777215 and unbounded ``VARCHAR`` map - to ``LONGTEXT``. - * - ``VARBINARY`` - - ``MEDIUMBLOB`` - - - * - ``DATE`` - - ``DATE`` - - - * - ``TIME(n)`` - - ``TIME(n)`` - - - * - ``TIMESTAMP(n)`` - - ``TIMESTAMP(n)`` - - MariaDB stores the current timestamp by default. Enable +:::{list-table} Trino type mapping to MariaDB type mapping +:widths: 30, 25, 50 +:header-rows: 1 + +* - Trino type + - MariaDB type + - Notes +* - `BOOLEAN` + - `BOOLEAN` + - +* - `TINYINT` + - `TINYINT` + - +* - `SMALLINT` + - `SMALLINT` + - +* - `INTEGER` + - `INT` + - +* - `BIGINT` + - `BIGINT` + - +* - `REAL` + - `FLOAT` + - +* - `DOUBLE` + - `DOUBLE` + - +* - `DECIMAL(p,s)` + - `DECIMAL(p,s)` + - +* - `CHAR(n)` + - `CHAR(n)` + - +* - `VARCHAR(255)` + - `TINYTEXT` + - Maps on `VARCHAR` of length 255 or less. +* - `VARCHAR(65535)` + - `TEXT` + - Maps on `VARCHAR` of length between 256 and 65535, inclusive. +* - `VARCHAR(16777215)` + - `MEDIUMTEXT` + - Maps on `VARCHAR` of length between 65536 and 16777215, inclusive. +* - `VARCHAR` + - `LONGTEXT` + - `VARCHAR` of length greater than 16777215 and unbounded `VARCHAR` map + to `LONGTEXT`. +* - `VARBINARY` + - `MEDIUMBLOB` + - +* - `DATE` + - `DATE` + - +* - `TIME(n)` + - `TIME(n)` + - +* - `TIMESTAMP(n)` + - `TIMESTAMP(n)` + - MariaDB stores the current timestamp by default. Enable `explicit_defaults_for_timestamp `_ - to avoid implicit default values and use ``NULL`` as the default value. -``` + to avoid implicit default values and use `NULL` as the default value. + +::: No other types are supported. diff --git a/docs/src/main/sphinx/connector/metastores.md b/docs/src/main/sphinx/connector/metastores.md index 4e3c5b1f8632..739cb5c8fafa 100644 --- a/docs/src/main/sphinx/connector/metastores.md +++ b/docs/src/main/sphinx/connector/metastores.md @@ -24,61 +24,57 @@ property to define the type of metastore to use. Additional configuration properties specific to the Thrift and Glue Metastores are also available. They are discussed later in this topic. -```{eval-rst} -.. list-table:: General metastore configuration properties - :widths: 35, 50, 15 - :header-rows: 1 - - * - Property Name - - Description - - Default - * - ``hive.metastore`` - - The type of Hive metastore to use. Trino currently supports the default - Hive Thrift metastore (``thrift``), and the AWS Glue Catalog (``glue``) - as metadata sources. You must use this for all object storage catalogs - except Iceberg. - - ``thrift`` - * - ``iceberg.catalog.type`` - - The Iceberg table format manages most metadata in metadata files in the - object storage itself. A small amount of metadata, however, still - requires the use of a metastore. In the Iceberg ecosystem, these smaller - metastores are called Iceberg metadata catalogs, or just catalogs. The - examples in each subsection depict the contents of a Trino catalog file - that uses the the Iceberg connector to configures different Iceberg - metadata catalogs. - - You must set this property in all Iceberg catalog property files. - Valid values are ``HIVE_METASTORE``, ``GLUE``, ``JDBC``, ``REST``, and - ``NESSIE``. - - - * - ``hive.metastore-cache.cache-partitions`` - - Enable caching for partition metadata. You can disable caching to avoid - inconsistent behavior that results from it. - - ``true`` - * - ``hive.metastore-cache-ttl`` - - Duration of how long cached metastore data is considered valid. - - ``0s`` - * - ``hive.metastore-stats-cache-ttl`` - - Duration of how long cached metastore statistics are considered valid. - If ``hive.metastore-cache-ttl`` is larger then it takes precedence - over ``hive.metastore-stats-cache-ttl``. - - ``5m`` - * - ``hive.metastore-cache-maximum-size`` - - Maximum number of metastore data objects in the Hive metastore cache. - - ``10000`` - * - ``hive.metastore-refresh-interval`` - - Asynchronously refresh cached metastore data after access if it is older - than this but is not yet expired, allowing subsequent accesses to see - fresh data. - - - * - ``hive.metastore-refresh-max-threads`` - - Maximum threads used to refresh cached metastore data. - - ``10`` - * - ``hive.hide-delta-lake-tables`` - - Controls whether to hide Delta Lake tables in table listings. Currently - applies only when using the AWS Glue metastore. - - ``false`` -``` +:::{list-table} General metastore configuration properties +:widths: 35, 50, 15 +:header-rows: 1 + +* - Property Name + - Description + - Default +* - `hive.metastore` + - The type of Hive metastore to use. Trino currently supports the default Hive + Thrift metastore (`thrift`), and the AWS Glue Catalog (`glue`) as metadata + sources. You must use this for all object storage catalogs except Iceberg. + - `thrift` +* - `iceberg.catalog.type` + - The Iceberg table format manages most metadata in metadata files in the + object storage itself. A small amount of metadata, however, still requires + the use of a metastore. In the Iceberg ecosystem, these smaller metastores + are called Iceberg metadata catalogs, or just catalogs. The examples in each + subsection depict the contents of a Trino catalog file that uses the the + Iceberg connector to configures different Iceberg metadata catalogs. + + You must set this property in all Iceberg catalog property files. Valid + values are `HIVE_METASTORE`, `GLUE`, `JDBC`, `REST`, and `NESSIE`. + - +* - `hive.metastore-cache.cache-partitions` + - Enable caching for partition metadata. You can disable caching to avoid + inconsistent behavior that results from it. + - `true` +* - `hive.metastore-cache-ttl` + - Duration of how long cached metastore data is considered valid. + - `0s` +* - `hive.metastore-stats-cache-ttl` + - Duration of how long cached metastore statistics are considered valid. If + `hive.metastore-cache-ttl` is larger then it takes precedence over + `hive.metastore-stats-cache-ttl`. + - `5m` +* - `hive.metastore-cache-maximum-size` + - Maximum number of metastore data objects in the Hive metastore cache. + - `10000` +* - `hive.metastore-refresh-interval` + - Asynchronously refresh cached metastore data after access if it is older + than this but is not yet expired, allowing subsequent accesses to see fresh + data. + - +* - `hive.metastore-refresh-max-threads` + - Maximum threads used to refresh cached metastore data. + - `10` +* - `hive.hide-delta-lake-tables` + - Controls whether to hide Delta Lake tables in table listings. Currently + applies only when using the AWS Glue metastore. + - `false` +::: (hive-thrift-metastore)= @@ -88,109 +84,108 @@ In order to use a Hive Thrift metastore, you must configure the metastore with `hive.metastore=thrift` and provide further details with the following properties: -```{eval-rst} -.. list-table:: Thrift metastore configuration properties - :widths: 35, 50, 15 - :header-rows: 1 - - * - Property name - - Description - - Default - * - ``hive.metastore.uri`` - - The URIs of the Hive metastore to connect to using the Thrift protocol. - If a comma-separated list of URIs is provided, the first URI is used by - default, and the rest of the URIs are fallback metastores. This property - is required. Example: ``thrift://192.0.2.3:9083`` or - ``thrift://192.0.2.3:9083,thrift://192.0.2.4:9083`` - - - * - ``hive.metastore.username`` - - The username Trino uses to access the Hive metastore. - - - * - ``hive.metastore.authentication.type`` - - Hive metastore authentication type. Possible values are ``NONE`` or - ``KERBEROS``. - - ``NONE`` - * - ``hive.metastore.thrift.client.connect-timeout`` - - Socket connect timeout for metastore client. - - ``10s`` - * - ``hive.metastore.thrift.client.read-timeout`` - - Socket read timeout for metastore client. - - ``10s`` - * - ``hive.metastore.thrift.impersonation.enabled`` - - Enable Hive metastore end user impersonation. - - - * - ``hive.metastore.thrift.use-spark-table-statistics-fallback`` - - Enable usage of table statistics generated by Apache Spark when Hive - table statistics are not available. - - ``true`` - * - ``hive.metastore.thrift.delegation-token.cache-ttl`` - - Time to live delegation token cache for metastore. - - ``1h`` - * - ``hive.metastore.thrift.delegation-token.cache-maximum-size`` - - Delegation token cache maximum size. - - ``1000`` - * - ``hive.metastore.thrift.client.ssl.enabled`` - - Use SSL when connecting to metastore. - - ``false`` - * - ``hive.metastore.thrift.client.ssl.key`` - - Path to private key and client certification (key store). - - - * - ``hive.metastore.thrift.client.ssl.key-password`` - - Password for the private key. - - - * - ``hive.metastore.thrift.client.ssl.trust-certificate`` - - Path to the server certificate chain (trust store). Required when SSL is - enabled. - - - * - ``hive.metastore.thrift.client.ssl.trust-certificate-password`` - - Password for the trust store. - - - * - ``hive.metastore.thrift.batch-fetch.enabled`` - - Enable fetching tables and views from all schemas in a single request. - - ``true`` - * - ``hive.metastore.service.principal`` - - The Kerberos principal of the Hive metastore service. - - - * - ``hive.metastore.client.principal`` - - The Kerberos principal that Trino uses when connecting to the Hive - metastore service. - - - * - ``hive.metastore.client.keytab`` - - Hive metastore client keytab location. - - - * - ``hive.metastore.thrift.delete-files-on-drop`` - - Actively delete the files for managed tables when performing drop table - or partition operations, for cases when the metastore does not delete the - files. - - ``false`` - * - ``hive.metastore.thrift.assume-canonical-partition-keys`` - - Allow the metastore to assume that the values of partition columns can be - converted to string values. This can lead to performance improvements in - queries which apply filters on the partition columns. Partition keys with - a ``TIMESTAMP`` type do not get canonicalized. - - ``false`` - * - ``hive.metastore.thrift.client.socks-proxy`` - - SOCKS proxy to use for the Thrift Hive metastore. - - - * - ``hive.metastore.thrift.client.max-retries`` - - Maximum number of retry attempts for metastore requests. - - ``9`` - * - ``hive.metastore.thrift.client.backoff-scale-factor`` - - Scale factor for metastore request retry delay. - - ``2.0`` - * - ``hive.metastore.thrift.client.max-retry-time`` - - Total allowed time limit for a metastore request to be retried. - - ``30s`` - * - ``hive.metastore.thrift.client.min-backoff-delay`` - - Minimum delay between metastore request retries. - - ``1s`` - * - ``hive.metastore.thrift.client.max-backoff-delay`` - - Maximum delay between metastore request retries. - - ``1s`` - * - ``hive.metastore.thrift.txn-lock-max-wait`` - - Maximum time to wait to acquire hive transaction lock. - - ``10m`` -``` +:::{list-table} Thrift metastore configuration properties +:widths: 35, 50, 15 +:header-rows: 1 + +* - Property name + - Description + - Default +* - `hive.metastore.uri` + - The URIs of the Hive metastore to connect to using the Thrift protocol. + If a comma-separated list of URIs is provided, the first URI is used by + default, and the rest of the URIs are fallback metastores. This property + is required. Example: `thrift://192.0.2.3:9083` or + `thrift://192.0.2.3:9083,thrift://192.0.2.4:9083` + - +* - `hive.metastore.username` + - The username Trino uses to access the Hive metastore. + - +* - `hive.metastore.authentication.type` + - Hive metastore authentication type. Possible values are `NONE` or + `KERBEROS`. + - `NONE` +* - `hive.metastore.thrift.client.connect-timeout` + - Socket connect timeout for metastore client. + - `10s` +* - `hive.metastore.thrift.client.read-timeout` + - Socket read timeout for metastore client. + - `10s` +* - `hive.metastore.thrift.impersonation.enabled` + - Enable Hive metastore end user impersonation. + - +* - `hive.metastore.thrift.use-spark-table-statistics-fallback` + - Enable usage of table statistics generated by Apache Spark when Hive table + statistics are not available. + - `true` +* - `hive.metastore.thrift.delegation-token.cache-ttl` + - Time to live delegation token cache for metastore. + - `1h` +* - `hive.metastore.thrift.delegation-token.cache-maximum-size` + - Delegation token cache maximum size. + - `1000` +* - `hive.metastore.thrift.client.ssl.enabled` + - Use SSL when connecting to metastore. + - `false` +* - `hive.metastore.thrift.client.ssl.key` + - Path to private key and client certification (key store). + - +* - `hive.metastore.thrift.client.ssl.key-password` + - Password for the private key. + - +* - `hive.metastore.thrift.client.ssl.trust-certificate` + - Path to the server certificate chain (trust store). Required when SSL is + enabled. + - +* - `hive.metastore.thrift.client.ssl.trust-certificate-password` + - Password for the trust store. + - +* - `hive.metastore.thrift.batch-fetch.enabled` + - Enable fetching tables and views from all schemas in a single request. + - `true` +* - `hive.metastore.service.principal` + - The Kerberos principal of the Hive metastore service. + - +* - `hive.metastore.client.principal` + - The Kerberos principal that Trino uses when connecting to the Hive metastore + service. + - +* - `hive.metastore.client.keytab` + - Hive metastore client keytab location. + - +* - `hive.metastore.thrift.delete-files-on-drop` + - Actively delete the files for managed tables when performing drop table or + partition operations, for cases when the metastore does not delete the + files. + - `false` +* - `hive.metastore.thrift.assume-canonical-partition-keys` + - Allow the metastore to assume that the values of partition columns can be + converted to string values. This can lead to performance improvements in + queries which apply filters on the partition columns. Partition keys with a + `TIMESTAMP` type do not get canonicalized. + - `false` +* - `hive.metastore.thrift.client.socks-proxy` + - SOCKS proxy to use for the Thrift Hive metastore. + - +* - `hive.metastore.thrift.client.max-retries` + - Maximum number of retry attempts for metastore requests. + - `9` +* - `hive.metastore.thrift.client.backoff-scale-factor` + - Scale factor for metastore request retry delay. + - `2.0` +* - `hive.metastore.thrift.client.max-retry-time` + - Total allowed time limit for a metastore request to be retried. + - `30s` +* - `hive.metastore.thrift.client.min-backoff-delay` + - Minimum delay between metastore request retries. + - `1s` +* - `hive.metastore.thrift.client.max-backoff-delay` + - Maximum delay between metastore request retries. + - `1s` +* - `hive.metastore.thrift.txn-lock-max-wait` + - Maximum time to wait to acquire hive transaction lock. + - `10m` +::: (hive-glue-metastore)= @@ -202,86 +197,84 @@ follows: `hive.metastore=glue` and provide further details with the following properties: -```{eval-rst} -.. list-table:: AWS Glue catalog configuration properties - :widths: 35, 50, 15 - :header-rows: 1 - - * - Property Name - - Description - - Default - * - ``hive.metastore.glue.region`` - - AWS region of the Glue Catalog. This is required when not running in - EC2, or when the catalog is in a different region. Example: - ``us-east-1`` - - - * - ``hive.metastore.glue.endpoint-url`` - - Glue API endpoint URL (optional). Example: - ``https://glue.us-east-1.amazonaws.com`` - - - * - ``hive.metastore.glue.sts.region`` - - AWS region of the STS service to authenticate with. This is required - when running in a GovCloud region. Example: ``us-gov-east-1`` - - - * - ``hive.metastore.glue.proxy-api-id`` - - The ID of the Glue Proxy API, when accessing Glue via an VPC endpoint in - API Gateway. - - - * - ``hive.metastore.glue.sts.endpoint`` - - STS endpoint URL to use when authenticating to Glue (optional). Example: - ``https://sts.us-gov-east-1.amazonaws.com`` - - - * - ``hive.metastore.glue.pin-client-to-current-region`` - - Pin Glue requests to the same region as the EC2 instance where Trino is - running. - - ``false`` - * - ``hive.metastore.glue.max-connections`` - - Max number of concurrent connections to Glue. - - ``30`` - * - ``hive.metastore.glue.max-error-retries`` - - Maximum number of error retries for the Glue client. - - ``10`` - * - ``hive.metastore.glue.default-warehouse-dir`` - - Default warehouse directory for schemas created without an explicit - ``location`` property. - - - * - ``hive.metastore.glue.aws-credentials-provider`` - - Fully qualified name of the Java class to use for obtaining AWS - credentials. Can be used to supply a custom credentials provider. - - - * - ``hive.metastore.glue.aws-access-key`` - - AWS access key to use to connect to the Glue Catalog. If specified along - with ``hive.metastore.glue.aws-secret-key``, this parameter takes - precedence over ``hive.metastore.glue.iam-role``. - - - * - ``hive.metastore.glue.aws-secret-key`` - - AWS secret key to use to connect to the Glue Catalog. If specified along - with ``hive.metastore.glue.aws-access-key``, this parameter takes - precedence over ``hive.metastore.glue.iam-role``. - - - * - ``hive.metastore.glue.catalogid`` - - The ID of the Glue Catalog in which the metadata database resides. - - - * - ``hive.metastore.glue.iam-role`` - - ARN of an IAM role to assume when connecting to the Glue Catalog. - - - * - ``hive.metastore.glue.external-id`` - - External ID for the IAM role trust policy when connecting to the Glue - Catalog. - - - * - ``hive.metastore.glue.partitions-segments`` - - Number of segments for partitioned Glue tables. - - ``5`` - * - ``hive.metastore.glue.get-partition-threads`` - - Number of threads for parallel partition fetches from Glue. - - ``20`` - * - ``hive.metastore.glue.read-statistics-threads`` - - Number of threads for parallel statistic fetches from Glue. - - ``5`` - * - ``hive.metastore.glue.write-statistics-threads`` - - Number of threads for parallel statistic writes to Glue. - - ``5`` -``` +:::{list-table} AWS Glue catalog configuration properties +:widths: 35, 50, 15 +:header-rows: 1 + +* - Property Name + - Description + - Default +* - `hive.metastore.glue.region` + - AWS region of the Glue Catalog. This is required when not running in EC2, or + when the catalog is in a different region. Example: `us-east-1` + - +* - `hive.metastore.glue.endpoint-url` + - Glue API endpoint URL (optional). Example: + `https://glue.us-east-1.amazonaws.com` + - +* - `hive.metastore.glue.sts.region` + - AWS region of the STS service to authenticate with. This is required when + running in a GovCloud region. Example: `us-gov-east-1` + - +* - `hive.metastore.glue.proxy-api-id` + - The ID of the Glue Proxy API, when accessing Glue via an VPC endpoint in API + Gateway. + - +* - `hive.metastore.glue.sts.endpoint` + - STS endpoint URL to use when authenticating to Glue (optional). Example: + `https://sts.us-gov-east-1.amazonaws.com` + - +* - `hive.metastore.glue.pin-client-to-current-region` + - Pin Glue requests to the same region as the EC2 instance where Trino is + running. + - `false` +* - `hive.metastore.glue.max-connections` + - Max number of concurrent connections to Glue. + - `30` +* - `hive.metastore.glue.max-error-retries` + - Maximum number of error retries for the Glue client. + - `10` +* - `hive.metastore.glue.default-warehouse-dir` + - Default warehouse directory for schemas created without an explicit + `location` property. + - +* - `hive.metastore.glue.aws-credentials-provider` + - Fully qualified name of the Java class to use for obtaining AWS credentials. + Can be used to supply a custom credentials provider. + - +* - `hive.metastore.glue.aws-access-key` + - AWS access key to use to connect to the Glue Catalog. If specified along + with `hive.metastore.glue.aws-secret-key`, this parameter takes precedence + over `hive.metastore.glue.iam-role`. + - +* - `hive.metastore.glue.aws-secret-key` + - AWS secret key to use to connect to the Glue Catalog. If specified along + with `hive.metastore.glue.aws-access-key`, this parameter takes precedence + over `hive.metastore.glue.iam-role`. + - +* - `hive.metastore.glue.catalogid` + - The ID of the Glue Catalog in which the metadata database resides. + - +* - `hive.metastore.glue.iam-role` + - ARN of an IAM role to assume when connecting to the Glue Catalog. + - +* - `hive.metastore.glue.external-id` + - External ID for the IAM role trust policy when connecting to the Glue + Catalog. + - +* - `hive.metastore.glue.partitions-segments` + - Number of segments for partitioned Glue tables. + - `5` +* - `hive.metastore.glue.get-partition-threads` + - Number of threads for parallel partition fetches from Glue. + - `20` +* - `hive.metastore.glue.read-statistics-threads` + - Number of threads for parallel statistic fetches from Glue. + - `5` +* - `hive.metastore.glue.write-statistics-threads` + - Number of threads for parallel statistic writes to Glue. + - `5` +::: (iceberg-glue-catalog)= @@ -291,20 +284,19 @@ When using the Glue catalog, the Iceberg connector supports the same {ref}`general Glue configuration properties ` as previously described with the following additional property: -```{eval-rst} -.. list-table:: Iceberg Glue catalog configuration property - :widths: 35, 50, 15 - :header-rows: 1 - - * - Property name - - Description - - Default - * - ``iceberg.glue.skip-archive`` - - Skip archiving an old table version when creating a new version in a - commit. See `AWS Glue Skip Archive - `_. - - ``false`` -``` +:::{list-table} Iceberg Glue catalog configuration property +:widths: 35, 50, 15 +:header-rows: 1 + +* - Property name + - Description + - Default +* - `iceberg.glue.skip-archive` + - Skip archiving an old table version when creating a new version in a commit. + See [AWS Glue Skip + Archive](https://iceberg.apache.org/docs/latest/aws/#skip-archive). + - `false` +::: ## Iceberg-specific metastores @@ -325,34 +317,32 @@ In order to use the Iceberg REST catalog, configure the catalog type with `iceberg.catalog.type=rest`, and provide further details with the following properties: -```{eval-rst} -.. list-table:: Iceberg REST catalog configuration properties - :widths: 40, 60 - :header-rows: 1 - - * - Property name - - Description - * - ``iceberg.rest-catalog.uri`` - - REST server API endpoint URI (required). - Example: ``http://iceberg-with-rest:8181`` - * - ``iceberg.rest-catalog.warehouse`` - - Warehouse identifier/location for the catalog (optional). - Example: ``s3://my_bucket/warehouse_location`` - * - ``iceberg.rest-catalog.security`` - - The type of security to use (default: ``NONE``). ``OAUTH2`` requires - either a ``token`` or ``credential``. Example: ``OAUTH2`` - * - ``iceberg.rest-catalog.session`` - - Session information included when communicating with the REST Catalog. - Options are ``NONE`` or ``USER`` (default: ``NONE``). - * - ``iceberg.rest-catalog.oauth2.token`` - - The bearer token used for interactions with the server. A - ``token`` or ``credential`` is required for ``OAUTH2`` security. - Example: ``AbCdEf123456`` - * - ``iceberg.rest-catalog.oauth2.credential`` - - The credential to exchange for a token in the OAuth2 client credentials - flow with the server. A ``token`` or ``credential`` is required for - ``OAUTH2`` security. Example: ``AbCdEf123456`` -``` +:::{list-table} Iceberg REST catalog configuration properties +:widths: 40, 60 +:header-rows: 1 + +* - Property name + - Description +* - `iceberg.rest-catalog.uri` + - REST server API endpoint URI (required). Example: + `http://iceberg-with-rest:8181` +* - `iceberg.rest-catalog.warehouse` + - Warehouse identifier/location for the catalog (optional). Example: + `s3://my_bucket/warehouse_location` +* - `iceberg.rest-catalog.security` + - The type of security to use (default: `NONE`). `OAUTH2` requires either a + `token` or `credential`. Example: `OAUTH2` +* - `iceberg.rest-catalog.session` + - Session information included when communicating with the REST Catalog. + Options are `NONE` or `USER` (default: `NONE`). +* - `iceberg.rest-catalog.oauth2.token` + - The bearer token used for interactions with the server. A `token` or + `credential` is required for `OAUTH2` security. Example: `AbCdEf123456` +* - `iceberg.rest-catalog.oauth2.credential` + - The credential to exchange for a token in the OAuth2 client credentials flow + with the server. A `token` or `credential` is required for `OAUTH2` + security. Example: `AbCdEf123456` +::: The following example shows a minimal catalog configuration using an Iceberg REST metadata catalog: @@ -411,37 +401,36 @@ In order to use a Nessie catalog, configure the catalog type with `iceberg.catalog.type=nessie` and provide further details with the following properties: -```{eval-rst} -.. list-table:: Nessie catalog configuration properties - :widths: 40, 60 - :header-rows: 1 - - * - Property name - - Description - * - ``iceberg.nessie-catalog.uri`` - - Nessie API endpoint URI (required). - Example: ``https://localhost:19120/api/v1`` - * - ``iceberg.nessie-catalog.ref`` - - The branch/tag to use for Nessie. Defaults to ``main``. - * - ``iceberg.nessie-catalog.default-warehouse-dir`` - - Default warehouse directory for schemas created without an explicit - ``location`` property. Example: ``/tmp`` - * - ``iceberg.nessie-catalog.read-timeout`` - - The read timeout :ref:`duration ` for requests - to the Nessie server. Defaults to ``25s``. - * - ``iceberg.nessie-catalog.connection-timeout`` - - The connection timeout :ref:`duration ` for - connection requests to the Nessie server. Defaults to ``5s``. - * - ``iceberg.nessie-catalog.enable-compression`` - - Configure whether compression should be enabled or not for - requests to the Nessie server. Defaults to ``true``. - * - ``iceberg.nessie-catalog.authentication.type`` - - The authentication type to use. - Available value is ``BEARER``. Defaults to no authentication. - * - ``iceberg.nessie-catalog.authentication.token`` - - The token to use with ``BEARER`` authentication. - Example: ``SXVLUXUhIExFQ0tFUiEK`` -``` +:::{list-table} Nessie catalog configuration properties +:widths: 40, 60 +:header-rows: 1 + +* - Property name + - Description +* - `iceberg.nessie-catalog.uri` + - Nessie API endpoint URI (required). Example: + `https://localhost:19120/api/v1` +* - `iceberg.nessie-catalog.ref` + - The branch/tag to use for Nessie. Defaults to `main`. +* - `iceberg.nessie-catalog.default-warehouse-dir` + - Default warehouse directory for schemas created without an explicit + `location` property. Example: `/tmp` +* - `iceberg.nessie-catalog.read-timeout` + - The read timeout [duration](prop-type-duration) for requests to the Nessie + server. Defaults to `25s`. +* - `iceberg.nessie-catalog.connection-timeout` + - The connection timeout [duration](prop-type-duration) for connection + requests to the Nessie server. Defaults to `5s`. +* - `iceberg.nessie-catalog.enable-compression` + - Configure whether compression should be enabled or not for requests to the + Nessie server. Defaults to `true`. +* - `iceberg.nessie-catalog.authentication.type` + - The authentication type to use. Available value is `BEARER`. Defaults to no + authentication. +* - `iceberg.nessie-catalog.authentication.token` + - The token to use with `BEARER` authentication. Example: +`SXVLUXUhIExFQ0tFUiEK` +::: ```text connector.name=iceberg diff --git a/docs/src/main/sphinx/connector/mongodb.md b/docs/src/main/sphinx/connector/mongodb.md index d0953d936f9c..77269717f780 100644 --- a/docs/src/main/sphinx/connector/mongodb.md +++ b/docs/src/main/sphinx/connector/mongodb.md @@ -363,51 +363,50 @@ each direction. The connector maps MongoDB types to the corresponding Trino types following this table: -```{eval-rst} -.. list-table:: MongoDB to Trino type mapping - :widths: 30, 20, 50 - :header-rows: 1 - - * - MongoDB type - - Trino type - - Notes - * - ``Boolean`` - - ``BOOLEAN`` - - - * - ``Int32`` - - ``BIGINT`` - - - * - ``Int64`` - - ``BIGINT`` - - - * - ``Double`` - - ``DOUBLE`` - - - * - ``Decimal128`` - - ``DECIMAL(p, s)`` - - - * - ``Date`` - - ``TIMESTAMP(3)`` - - - * - ``String`` - - ``VARCHAR`` - - - * - ``Binary`` - - ``VARBINARY`` - - - * - ``ObjectId`` - - ``ObjectId`` - - - * - ``Object`` - - ``ROW`` - - - * - ``Array`` - - ``ARRAY`` - - Map to ``ROW`` if the element type is not unique. - * - ``DBRef`` - - ``ROW`` - - -``` +:::{list-table} MongoDB to Trino type mapping +:widths: 30, 20, 50 +:header-rows: 1 + +* - MongoDB type + - Trino type + - Notes +* - `Boolean` + - `BOOLEAN` + - +* - `Int32` + - `BIGINT` + - +* - `Int64` + - `BIGINT` + - +* - `Double` + - `DOUBLE` + - +* - `Decimal128` + - `DECIMAL(p, s)` + - +* - `Date` + - `TIMESTAMP(3)` + - +* - `String` + - `VARCHAR` + - +* - `Binary` + - `VARBINARY` + - +* - `ObjectId` + - `ObjectId` + - +* - `Object` + - `ROW` + - +* - `Array` + - `ARRAY` + - Map to `ROW` if the element type is not unique. +* - `DBRef` + - `ROW` + - +::: No other types are supported. @@ -416,34 +415,33 @@ No other types are supported. The connector maps Trino types to the corresponding MongoDB types following this table: -```{eval-rst} -.. list-table:: Trino to MongoDB type mapping - :widths: 30, 20 - :header-rows: 1 - - * - Trino type - - MongoDB type - * - ``BOOLEAN`` - - ``Boolean`` - * - ``BIGINT`` - - ``Int64`` - * - ``DOUBLE`` - - ``Double`` - * - ``DECIMAL(p, s)`` - - ``Decimal128`` - * - ``TIMESTAMP(3)`` - - ``Date`` - * - ``VARCHAR`` - - ``String`` - * - ``VARBINARY`` - - ``Binary`` - * - ``ObjectId`` - - ``ObjectId`` - * - ``ROW`` - - ``Object`` - * - ``ARRAY`` - - ``Array`` -``` +:::{list-table} Trino to MongoDB type mapping +:widths: 30, 20 +:header-rows: 1 + +* - Trino type + - MongoDB type +* - `BOOLEAN` + - `Boolean` +* - `BIGINT` + - `Int64` +* - `DOUBLE` + - `Double` +* - `DECIMAL(p, s)` + - `Decimal128` +* - `TIMESTAMP(3)` + - `Date` +* - `VARCHAR` + - `String` +* - `VARBINARY` + - `Binary` +* - `ObjectId` + - `ObjectId` +* - `ROW` + - `Object` +* - `ARRAY` + - `Array` +::: No other types are supported. diff --git a/docs/src/main/sphinx/connector/mysql.md b/docs/src/main/sphinx/connector/mysql.md index a2e9fd42d2bc..32ab59ba3ce2 100644 --- a/docs/src/main/sphinx/connector/mysql.md +++ b/docs/src/main/sphinx/connector/mysql.md @@ -121,96 +121,95 @@ each direction. The connector maps MySQL types to the corresponding Trino types following this table: -```{eval-rst} -.. list-table:: MySQL to Trino type mapping - :widths: 30, 20, 50 - :header-rows: 1 - - * - MySQL database type - - Trino type - - Notes - * - ``BIT`` - - ``BOOLEAN`` - - - * - ``BOOLEAN`` - - ``TINYINT`` - - - * - ``TINYINT`` - - ``TINYINT`` - - - * - ``TINYINT UNSIGNED`` - - ``SMALLINT`` - - - * - ``SMALLINT`` - - ``SMALLINT`` - - - * - ``SMALLINT UNSIGNED`` - - ``INTEGER`` - - - * - ``INTEGER`` - - ``INTEGER`` - - - * - ``INTEGER UNSIGNED`` - - ``BIGINT`` - - - * - ``BIGINT`` - - ``BIGINT`` - - - * - ``BIGINT UNSIGNED`` - - ``DECIMAL(20, 0)`` - - - * - ``DOUBLE PRECISION`` - - ``DOUBLE`` - - - * - ``FLOAT`` - - ``REAL`` - - - * - ``REAL`` - - ``REAL`` - - - * - ``DECIMAL(p, s)`` - - ``DECIMAL(p, s)`` - - See :ref:`MySQL DECIMAL type handling ` - * - ``CHAR(n)`` - - ``CHAR(n)`` - - - * - ``VARCHAR(n)`` - - ``VARCHAR(n)`` - - - * - ``TINYTEXT`` - - ``VARCHAR(255)`` - - - * - ``TEXT`` - - ``VARCHAR(65535)`` - - - * - ``MEDIUMTEXT`` - - ``VARCHAR(16777215)`` - - - * - ``LONGTEXT`` - - ``VARCHAR`` - - - * - ``ENUM(n)`` - - ``VARCHAR(n)`` - - - * - ``BINARY``, ``VARBINARY``, ``TINYBLOB``, ``BLOB``, ``MEDIUMBLOB``, ``LONGBLOB`` - - ``VARBINARY`` - - - * - ``JSON`` - - ``JSON`` - - - * - ``DATE`` - - ``DATE`` - - - * - ``TIME(n)`` - - ``TIME(n)`` - - - * - ``DATETIME(n)`` - - ``TIMESTAMP(n)`` - - - * - ``TIMESTAMP(n)`` - - ``TIMESTAMP(n) WITH TIME ZONE`` - - -``` +:::{list-table} MySQL to Trino type mapping +:widths: 30, 30, 40 +:header-rows: 1 + +* - MySQL database type + - Trino type + - Notes +* - `BIT` + - `BOOLEAN` + - +* - `BOOLEAN` + - `TINYINT` + - +* - `TINYINT` + - `TINYINT` + - +* - `TINYINT UNSIGNED` + - `SMALLINT` + - +* - `SMALLINT` + - `SMALLINT` + - +* - `SMALLINT UNSIGNED` + - `INTEGER` + - +* - `INTEGER` + - `INTEGER` + - +* - `INTEGER UNSIGNED` + - `BIGINT` + - +* - `BIGINT` + - `BIGINT` + - +* - `BIGINT UNSIGNED` + - `DECIMAL(20, 0)` + - +* - `DOUBLE PRECISION` + - `DOUBLE` + - +* - `FLOAT` + - `REAL` + - +* - `REAL` + - `REAL` + - +* - `DECIMAL(p, s)` + - `DECIMAL(p, s)` + - See [MySQL DECIMAL type handling](mysql-decimal-handling) +* - `CHAR(n)` + - `CHAR(n)` + - +* - `VARCHAR(n)` + - `VARCHAR(n)` + - +* - `TINYTEXT` + - `VARCHAR(255)` + - +* - `TEXT` + - `VARCHAR(65535)` + - +* - `MEDIUMTEXT` + - `VARCHAR(16777215)` + - +* - `LONGTEXT` + - `VARCHAR` + - +* - `ENUM(n)` + - `VARCHAR(n)` + - +* - `BINARY`, `VARBINARY`, `TINYBLOB`, `BLOB`, `MEDIUMBLOB`, `LONGBLOB` + - `VARBINARY` + - +* - `JSON` + - `JSON` + - +* - `DATE` + - `DATE` + - +* - `TIME(n)` + - `TIME(n)` + - +* - `DATETIME(n)` + - `TIMESTAMP(n)` + - +* - `TIMESTAMP(n)` + - `TIMESTAMP(n) WITH TIME ZONE` + - +::: No other types are supported. @@ -219,60 +218,59 @@ No other types are supported. The connector maps Trino types to the corresponding MySQL types following this table: -```{eval-rst} -.. list-table:: Trino to MySQL type mapping - :widths: 30, 20, 50 - :header-rows: 1 - - * - Trino type - - MySQL type - - Notes - * - ``BOOLEAN`` - - ``TINYINT`` - - - * - ``TINYINT`` - - ``TINYINT`` - - - * - ``SMALLINT`` - - ``SMALLINT`` - - - * - ``INTEGER`` - - ``INTEGER`` - - - * - ``BIGINT`` - - ``BIGINT`` - - - * - ``REAL`` - - ``REAL`` - - - * - ``DOUBLE`` - - ``DOUBLE PRECISION`` - - - * - ``DECIMAL(p, s)`` - - ``DECIMAL(p, s)`` - - :ref:`MySQL DECIMAL type handling ` - * - ``CHAR(n)`` - - ``CHAR(n)`` - - - * - ``VARCHAR(n)`` - - ``VARCHAR(n)`` - - - * - ``JSON`` - - ``JSON`` - - - * - ``DATE`` - - ``DATE`` - - - * - ``TIME(n)`` - - ``TIME(n)`` - - - * - ``TIMESTAMP(n)`` - - ``DATETIME(n)`` - - - * - ``TIMESTAMP(n) WITH TIME ZONE`` - - ``TIMESTAMP(n)`` - - -``` +:::{list-table} Trino to MySQL type mapping +:widths: 30, 30, 40 +:header-rows: 1 + +* - Trino type + - MySQL type + - Notes +* - `BOOLEAN` + - `TINYINT` + - +* - `TINYINT` + - `TINYINT` + - +* - `SMALLINT` + - `SMALLINT` + - +* - `INTEGER` + - `INTEGER` + - +* - `BIGINT` + - `BIGINT` + - +* - `REAL` + - `REAL` + - +* - `DOUBLE` + - `DOUBLE PRECISION` + - +* - `DECIMAL(p, s)` + - `DECIMAL(p, s)` + - [MySQL DECIMAL type handling](mysql-decimal-handling) +* - `CHAR(n)` + - `CHAR(n)` + - +* - `VARCHAR(n)` + - `VARCHAR(n)` + - +* - `JSON` + - `JSON` + - +* - `DATE` + - `DATE` + - +* - `TIME(n)` + - `TIME(n)` + - +* - `TIMESTAMP(n)` + - `DATETIME(n)` + - +* - `TIMESTAMP(n) WITH TIME ZONE` + - `TIMESTAMP(n)` + - +::: No other types are supported. diff --git a/docs/src/main/sphinx/connector/object-storage-file-formats.md b/docs/src/main/sphinx/connector/object-storage-file-formats.md index cfa5c23712a7..45e9f59c34e5 100644 --- a/docs/src/main/sphinx/connector/object-storage-file-formats.md +++ b/docs/src/main/sphinx/connector/object-storage-file-formats.md @@ -18,30 +18,29 @@ In the case of serializable formats, only specific The following properties are used to configure the read and write operations with ORC files performed by supported object storage connectors: -```{eval-rst} -.. list-table:: ORC format configuration properties - :widths: 30, 50, 20 - :header-rows: 1 +:::{list-table} ORC format configuration properties +:widths: 30, 50, 20 +:header-rows: 1 - * - Property Name - - Description - - Default - * - ``hive.orc.time-zone`` - - Sets the default time zone for legacy ORC files that did not declare a - time zone. - - JVM default - * - ``hive.orc.use-column-names`` - - Access ORC columns by name. By default, columns in ORC files are - accessed by their ordinal position in the Hive table definition. The - equivalent catalog session property is ``orc_use_column_names``. - - ``false`` - * - ``hive.orc.bloom-filters.enabled`` - - Enable bloom filters for predicate pushdown. - - ``false`` - * - ``hive.orc.read-legacy-short-zone-id`` - - Allow reads on ORC files with short zone ID in the stripe footer. - - ``false`` -``` +* - Property Name + - Description + - Default +* - `hive.orc.time-zone` + - Sets the default time zone for legacy ORC files that did not declare a time + zone. + - JVM default +* - `hive.orc.use-column-names` + - Access ORC columns by name. By default, columns in ORC files are accessed by + their ordinal position in the Hive table definition. The equivalent catalog + session property is `orc_use_column_names`. + - `false` +* - `hive.orc.bloom-filters.enabled` + - Enable bloom filters for predicate pushdown. + - `false` +* - `hive.orc.read-legacy-short-zone-id` + - Allow reads on ORC files with short zone ID in the stripe footer. + - `false` +::: (hive-parquet-configuration)= @@ -50,56 +49,56 @@ with ORC files performed by supported object storage connectors: The following properties are used to configure the read and write operations with Parquet files performed by supported object storage connectors: -```{eval-rst} -.. list-table:: Parquet format configuration properties - :widths: 30, 50, 20 - :header-rows: 1 +:::{list-table} Parquet format configuration properties +:widths: 30, 50, 20 +:header-rows: 1 - * - Property Name - - Description - - Default - * - ``hive.parquet.time-zone`` - - Adjusts timestamp values to a specific time zone. For Hive 3.1+, set - this to UTC. - - JVM default - * - ``hive.parquet.use-column-names`` - - Access Parquet columns by name by default. Set this property to - ``false`` to access columns by their ordinal position in the Hive table - definition. The equivalent catalog session property is - ``parquet_use_column_names``. - - ``true`` - * - ``parquet.writer.validation-percentage`` - - Percentage of parquet files to validate after write by re-reading the whole file. - The equivalent catalog session property is ``parquet_optimized_writer_validation_percentage``. - Validation can be turned off by setting this property to ``0``. - - ``5`` - * - ``parquet.writer.page-size`` - - Maximum page size for the Parquet writer. - - ``1 MB`` - * - ``parquet.writer.block-size`` - - Maximum row group size for the Parquet writer. - - ``128 MB`` - * - ``parquet.writer.batch-size`` - - Maximum number of rows processed by the parquet writer in a batch. - - ``10000`` - * - ``parquet.use-bloom-filter`` - - Whether bloom filters are used for predicate pushdown when reading - Parquet files. Set this property to ``false`` to disable the usage of - bloom filters by default. The equivalent catalog session property is - ``parquet_use_bloom_filter``. - - ``true`` - * - ``parquet.use-column-index`` - - Skip reading Parquet pages by using Parquet column indices. The - equivalent catalog session property is ``parquet_use_column_index``. - Only supported by the Delta Lake and Hive connectors. - - ``true`` - * - ``parquet.max-read-block-row-count`` - - Sets the maximum number of rows read in a batch. The equivalent catalog - session property is named ``parquet_max_read_block_row_count`` and - supported by the Delta Lake, Hive, and Iceberg connectors. - - ``8192`` - * - ``parquet.small-file-threshold`` - - :ref:`Data size ` below which a Parquet file is - read entirely. The equivalent catalog session property is named - ``parquet_small_file_threshold``. - - ``3MB`` +* - Property Name + - Description + - Default +* - `hive.parquet.time-zone` + - Adjusts timestamp values to a specific time zone. For Hive 3.1+, set this to + UTC. + - JVM default +* - `hive.parquet.use-column-names` + - Access Parquet columns by name by default. Set this property to `false` to + access columns by their ordinal position in the Hive table definition. The + equivalent catalog session property is `parquet_use_column_names`. + - `true` +* - `parquet.writer.validation-percentage` + - Percentage of parquet files to validate after write by re-reading the whole + file. The equivalent catalog session property is + `parquet_optimized_writer_validation_percentage`. Validation can be turned + off by setting this property to `0`. + - `5` +* - `parquet.writer.page-size` + - Maximum page size for the Parquet writer. + - `1 MB` +* - `parquet.writer.block-size` + - Maximum row group size for the Parquet writer. + - `128 MB` +* - `parquet.writer.batch-size` + - Maximum number of rows processed by the parquet writer in a batch. + - `10000` +* - `parquet.use-bloom-filter` + - Whether bloom filters are used for predicate pushdown when reading Parquet + files. Set this property to `false` to disable the usage of bloom filters by + default. The equivalent catalog session property is + `parquet_use_bloom_filter`. + - `true` +* - `parquet.use-column-index` + - Skip reading Parquet pages by using Parquet column indices. The equivalent + catalog session property is `parquet_use_column_index`. Only supported by + the Delta Lake and Hive connectors. + - `true` +* - `parquet.max-read-block-row-count` + - Sets the maximum number of rows read in a batch. The equivalent catalog + session property is named `parquet_max_read_block_row_count` and supported + by the Delta Lake, Hive, and Iceberg connectors. + - `8192` +* - `parquet.small-file-threshold` + - [Data size](prop-type-data-size) below which a Parquet file is read + entirely. The equivalent catalog session property is named + `parquet_small_file_threshold`. + - `3MB` +::: diff --git a/docs/src/main/sphinx/connector/oracle.md b/docs/src/main/sphinx/connector/oracle.md index 4c42bf339cc0..ccbfb9a48374 100644 --- a/docs/src/main/sphinx/connector/oracle.md +++ b/docs/src/main/sphinx/connector/oracle.md @@ -161,66 +161,65 @@ each direction. Trino supports selecting Oracle database types. This table shows the Oracle to Trino data type mapping: -```{eval-rst} -.. list-table:: Oracle to Trino type mapping - :widths: 30, 25, 50 - :header-rows: 1 - - * - Oracle database type - - Trino type - - Notes - * - ``NUMBER(p, s)`` - - ``DECIMAL(p, s)`` - - See :ref:`oracle-number-mapping` - * - ``NUMBER(p)`` - - ``DECIMAL(p, 0)`` - - See :ref:`oracle-number-mapping` - * - ``FLOAT[(p)]`` - - ``DOUBLE`` - - - * - ``BINARY_FLOAT`` - - ``REAL`` - - - * - ``BINARY_DOUBLE`` - - ``DOUBLE`` - - - * - ``VARCHAR2(n CHAR)`` - - ``VARCHAR(n)`` - - - * - ``VARCHAR2(n BYTE)`` - - ``VARCHAR(n)`` - - - * - ``NVARCHAR2(n)`` - - ``VARCHAR(n)`` - - - * - ``CHAR(n)`` - - ``CHAR(n)`` - - - * - ``NCHAR(n)`` - - ``CHAR(n)`` - - - * - ``CLOB`` - - ``VARCHAR`` - - - * - ``NCLOB`` - - ``VARCHAR`` - - - * - ``RAW(n)`` - - ``VARBINARY`` - - - * - ``BLOB`` - - ``VARBINARY`` - - - * - ``DATE`` - - ``TIMESTAMP(0)`` - - See :ref:`oracle-datetime-mapping` - * - ``TIMESTAMP(p)`` - - ``TIMESTAMP(p)`` - - See :ref:`oracle-datetime-mapping` - * - ``TIMESTAMP(p) WITH TIME ZONE`` - - ``TIMESTAMP WITH TIME ZONE`` - - See :ref:`oracle-datetime-mapping` -``` +:::{list-table} Oracle to Trino type mapping +:widths: 30, 25, 50 +:header-rows: 1 + +* - Oracle database type + - Trino type + - Notes +* - `NUMBER(p, s)` + - `DECIMAL(p, s)` + - See [](oracle-number-mapping) +* - `NUMBER(p)` + - `DECIMAL(p, 0)` + - See [](oracle-number-mapping) +* - `FLOAT[(p)]` + - `DOUBLE` + - +* - `BINARY_FLOAT` + - `REAL` + - +* - `BINARY_DOUBLE` + - `DOUBLE` + - +* - `VARCHAR2(n CHAR)` + - `VARCHAR(n)` + - +* - `VARCHAR2(n BYTE)` + - `VARCHAR(n)` + - +* - `NVARCHAR2(n)` + - `VARCHAR(n)` + - +* - `CHAR(n)` + - `CHAR(n)` + - +* - `NCHAR(n)` + - `CHAR(n)` + - +* - `CLOB` + - `VARCHAR` + - +* - `NCLOB` + - `VARCHAR` + - +* - `RAW(n)` + - `VARBINARY` + - +* - `BLOB` + - `VARBINARY` + - +* - `DATE` + - `TIMESTAMP(0)` + - See [](oracle-datetime-mapping) +* - `TIMESTAMP(p)` + - `TIMESTAMP(p)` + - See [](oracle-datetime-mapping) +* - `TIMESTAMP(p) WITH TIME ZONE` + - `TIMESTAMP WITH TIME ZONE` + - See [](oracle-datetime-mapping) +::: No other types are supported. @@ -235,57 +234,56 @@ TABLE
AS SELECT` operations. When data is inserted into existing tables, `Oracle to Trino` type mapping is used. ::: -```{eval-rst} -.. list-table:: Trino to Oracle Type Mapping - :widths: 30, 25, 50 - :header-rows: 1 - - * - Trino type - - Oracle database type - - Notes - * - ``TINYINT`` - - ``NUMBER(3)`` - - - * - ``SMALLINT`` - - ``NUMBER(5)`` - - - * - ``INTEGER`` - - ``NUMBER(10)`` - - - * - ``BIGINT`` - - ``NUMBER(19)`` - - - * - ``DECIMAL(p, s)`` - - ``NUMBER(p, s)`` - - - * - ``REAL`` - - ``BINARY_FLOAT`` - - - * - ``DOUBLE`` - - ``BINARY_DOUBLE`` - - - * - ``VARCHAR`` - - ``NCLOB`` - - - * - ``VARCHAR(n)`` - - ``VARCHAR2(n CHAR)`` or ``NCLOB`` - - See :ref:`oracle-character-mapping` - * - ``CHAR(n)`` - - ``CHAR(n CHAR)`` or ``NCLOB`` - - See :ref:`oracle-character-mapping` - * - ``VARBINARY`` - - ``BLOB`` - - - * - ``DATE`` - - ``DATE`` - - See :ref:`oracle-datetime-mapping` - * - ``TIMESTAMP`` - - ``TIMESTAMP(3)`` - - See :ref:`oracle-datetime-mapping` - * - ``TIMESTAMP WITH TIME ZONE`` - - ``TIMESTAMP(3) WITH TIME ZONE`` - - See :ref:`oracle-datetime-mapping` -``` +:::{list-table} Trino to Oracle Type Mapping +:widths: 30, 25, 50 +:header-rows: 1 + +* - Trino type + - Oracle database type + - Notes +* - `TINYINT` + - `NUMBER(3)` + - +* - `SMALLINT` + - `NUMBER(5)` + - +* - `INTEGER` + - `NUMBER(10)` + - +* - `BIGINT` + - `NUMBER(19)` + - +* - `DECIMAL(p, s)` + - `NUMBER(p, s)` + - +* - `REAL` + - `BINARY_FLOAT` + - +* - `DOUBLE` + - `BINARY_DOUBLE` + - +* - `VARCHAR` + - `NCLOB` + - +* - `VARCHAR(n)` + - `VARCHAR2(n CHAR)` or `NCLOB` + - See [](oracle-character-mapping) +* - `CHAR(n)` + - `CHAR(n CHAR)` or `NCLOB` + - See [](oracle-character-mapping) +* - `VARBINARY` + - `BLOB` + - +* - `DATE` + - `DATE` + - See [](oracle-datetime-mapping) +* - `TIMESTAMP` + - `TIMESTAMP(3)` + - See [](oracle-datetime-mapping) +* - `TIMESTAMP WITH TIME ZONE` + - `TIMESTAMP(3) WITH TIME ZONE` + - See [](oracle-datetime-mapping) +::: No other types are supported. @@ -359,49 +357,47 @@ fails. This is also true for the equivalent `VARCHAR` types. ### Number to decimal configuration properties -```{eval-rst} -.. list-table:: - :widths: 20, 20, 50, 10 - :header-rows: 1 - - * - Configuration property name - - Session property name - - Description - - Default - * - ``oracle.number.default-scale`` - - ``number_default_scale`` - - Default Trino ``DECIMAL`` scale for Oracle ``NUMBER`` (without precision - and scale) date type. When not set then such column is treated as not - supported. - - not set - * - ``oracle.number.rounding-mode`` - - ``number_rounding_mode`` - - Rounding mode for the Oracle ``NUMBER`` data type. This is useful when - Oracle ``NUMBER`` data type specifies higher scale than is supported in - Trino. Possible values are: - - - ``UNNECESSARY`` - Rounding mode to assert that the - requested operation has an exact result, - hence no rounding is necessary. - - ``CEILING`` - Rounding mode to round towards - positive infinity. - - ``FLOOR`` - Rounding mode to round towards negative - infinity. - - ``HALF_DOWN`` - Rounding mode to round towards - ``nearest neighbor`` unless both neighbors are - equidistant, in which case rounding down is used. - - ``HALF_EVEN`` - Rounding mode to round towards the - ``nearest neighbor`` unless both neighbors are equidistant, - in which case rounding towards the even neighbor is - performed. - - ``HALF_UP`` - Rounding mode to round towards - ``nearest neighbor`` unless both neighbors are - equidistant, in which case rounding up is used - - ``UP`` - Rounding mode to round towards zero. - - ``DOWN`` - Rounding mode to round towards zero. - - - ``UNNECESSARY`` -``` +:::{list-table} +:widths: 20, 20, 50, 10 +:header-rows: 1 + +* - Configuration property name + - Session property name + - Description + - Default +* - `oracle.number.default-scale` + - `number_default_scale` + - Default Trino `DECIMAL` scale for Oracle `NUMBER` (without precision and + scale) date type. When not set then such column is treated as not supported. + - not set +* - `oracle.number.rounding-mode` + - `number_rounding_mode` + - Rounding mode for the Oracle `NUMBER` data type. This is useful when Oracle + `NUMBER` data type specifies higher scale than is supported in Trino. + Possible values are: + + - `UNNECESSARY` - Rounding mode to assert that the + requested operation has an exact result, + hence no rounding is necessary. + - `CEILING` - Rounding mode to round towards + positive infinity. + - `FLOOR` - Rounding mode to round towards negative + infinity. + - `HALF_DOWN` - Rounding mode to round towards + `nearest neighbor` unless both neighbors are + equidistant, in which case rounding down is used. + - `HALF_EVEN` - Rounding mode to round towards the + `nearest neighbor` unless both neighbors are equidistant, + in which case rounding towards the even neighbor is + performed. + - `HALF_UP` - Rounding mode to round towards + `nearest neighbor` unless both neighbors are + equidistant, in which case rounding up is used + - `UP` - Rounding mode to round towards zero. + - `DOWN` - Rounding mode to round towards zero. + + - `UNNECESSARY` +::: (oracle-sql-support)= diff --git a/docs/src/main/sphinx/connector/phoenix.md b/docs/src/main/sphinx/connector/phoenix.md index 850368af18aa..af26ce92644e 100644 --- a/docs/src/main/sphinx/connector/phoenix.md +++ b/docs/src/main/sphinx/connector/phoenix.md @@ -112,56 +112,55 @@ each direction. The connector maps Phoenix types to the corresponding Trino types following this table: -```{eval-rst} -.. list-table:: Phoenix type to Trino type mapping - :widths: 30, 20 - :header-rows: 1 - - * - Phoenix database type - - Trino type - * - ``BOOLEAN`` - - ``BOOLEAN`` - * - ``TINYINT`` - - ``TINYINT`` - * - ``UNSIGNED_TINYINT`` - - ``TINYINT`` - * - ``SMALLINT`` - - ``SMALLINT`` - * - ``UNSIGNED_SMALLINT`` - - ``SMALLINT`` - * - ``INTEGER`` - - ``INTEGER`` - * - ``UNSIGNED_INT`` - - ``INTEGER`` - * - ``BIGINT`` - - ``BIGINT`` - * - ``UNSIGNED_LONG`` - - ``BIGINT`` - * - ``FLOAT`` - - ``REAL`` - * - ``UNSIGNED_FLOAT`` - - ``REAL`` - * - ``DOUBLE`` - - ``DOUBLE`` - * - ``UNSIGNED_DOUBLE`` - - ``DOUBLE`` - * - ``DECIMAL(p,s)`` - - ``DECIMAL(p,s)`` - * - ``CHAR(n)`` - - ``CHAR(n)`` - * - ``VARCHAR(n)`` - - ``VARCHAR(n)`` - * - ``BINARY`` - - ``VARBINARY`` - * - ``VARBINARY`` - - ``VARBINARY`` - * - ``DATE`` - - ``DATE`` - * - ``UNSIGNED_DATE`` - - ``DATE`` - * - ``ARRAY`` - - ``ARRAY`` -``` +:::{list-table} Phoenix type to Trino type mapping +:widths: 50, 50 +:header-rows: 1 + +* - Phoenix database type + - Trino type +* - `BOOLEAN` + - `BOOLEAN` +* - `TINYINT` + - `TINYINT` +* - `UNSIGNED_TINYINT` + - `TINYINT` +* - `SMALLINT` + - `SMALLINT` +* - `UNSIGNED_SMALLINT` + - `SMALLINT` +* - `INTEGER` + - `INTEGER` +* - `UNSIGNED_INT` + - `INTEGER` +* - `BIGINT` + - `BIGINT` +* - `UNSIGNED_LONG` + - `BIGINT` +* - `FLOAT` + - `REAL` +* - `UNSIGNED_FLOAT` + - `REAL` +* - `DOUBLE` + - `DOUBLE` +* - `UNSIGNED_DOUBLE` + - `DOUBLE` +* - `DECIMAL(p,s)` + - `DECIMAL(p,s)` +* - `CHAR(n)` + - `CHAR(n)` +* - `VARCHAR(n)` + - `VARCHAR(n)` +* - `BINARY` + - `VARBINARY` +* - `VARBINARY` + - `VARBINARY` +* - `DATE` + - `DATE` +* - `UNSIGNED_DATE` + - `DATE` +* - `ARRAY` + - `ARRAY` +::: No other types are supported. @@ -175,42 +174,41 @@ type. The connector maps Trino types to the corresponding Phoenix types following this table: -```{eval-rst} -.. list-table:: Trino type to Phoenix type mapping - :widths: 30, 20 - :header-rows: 1 - - * - Trino database type - - Phoenix type - * - ``BOOLEAN`` - - ``BOOLEAN`` - * - ``TINYINT`` - - ``TINYINT`` - * - ``SMALLINT`` - - ``SMALLINT`` - * - ``INTEGER`` - - ``INTEGER`` - * - ``BIGINT`` - - ``BIGINT`` - * - ``REAL`` - - ``FLOAT`` - * - ``DOUBLE`` - - ``DOUBLE`` - * - ``DECIMAL(p,s)`` - - ``DECIMAL(p,s)`` - * - ``CHAR(n)`` - - ``CHAR(n)`` - * - ``VARCHAR(n)`` - - ``VARCHAR(n)`` - * - ``VARBINARY`` - - ``VARBINARY`` - * - ``TIME`` - - ``TIME`` - * - ``DATE`` - - ``DATE`` - * - ``ARRAY`` - - ``ARRAY`` -``` +:::{list-table} Trino type to Phoenix type mapping +:widths: 50, 50 +:header-rows: 1 + +* - Trino database type + - Phoenix type +* - `BOOLEAN` + - `BOOLEAN` +* - `TINYINT` + - `TINYINT` +* - `SMALLINT` + - `SMALLINT` +* - `INTEGER` + - `INTEGER` +* - `BIGINT` + - `BIGINT` +* - `REAL` + - `FLOAT` +* - `DOUBLE` + - `DOUBLE` +* - `DECIMAL(p,s)` + - `DECIMAL(p,s)` +* - `CHAR(n)` + - `CHAR(n)` +* - `VARCHAR(n)` + - `VARCHAR(n)` +* - `VARBINARY` + - `VARBINARY` +* - `TIME` + - `TIME` +* - `DATE` + - `DATE` +* - `ARRAY` + - `ARRAY` +::: No other types are supported. diff --git a/docs/src/main/sphinx/connector/pinot.md b/docs/src/main/sphinx/connector/pinot.md index e6013f161bcb..c039daaadc19 100644 --- a/docs/src/main/sphinx/connector/pinot.md +++ b/docs/src/main/sphinx/connector/pinot.md @@ -151,40 +151,39 @@ connector {ref}`maps some types ` when reading data. The connector maps Pinot types to the corresponding Trino types according to the following table: -```{eval-rst} -.. list-table:: Pinot type to Trino type mapping - :widths: 75,60 - :header-rows: 1 - - * - Pinot type - - Trino type - * - ``INT`` - - ``INTEGER`` - * - ``LONG`` - - ``BIGINT`` - * - ``FLOAT`` - - ``REAL`` - * - ``DOUBLE`` - - ``DOUBLE`` - * - ``STRING`` - - ``VARCHAR`` - * - ``BYTES`` - - ``VARBINARY`` - * - ``JSON`` - - ``JSON`` - * - ``TIMESTAMP`` - - ``TIMESTAMP`` - * - ``INT_ARRAY`` - - ``VARCHAR`` - * - ``LONG_ARRAY`` - - ``VARCHAR`` - * - ``FLOAT_ARRAY`` - - ``VARCHAR`` - * - ``DOUBLE_ARRAY`` - - ``VARCHAR`` - * - ``STRING_ARRAY`` - - ``VARCHAR`` -``` +:::{list-table} Pinot type to Trino type mapping +:widths: 75,60 +:header-rows: 1 + +* - Pinot type + - Trino type +* - `INT` + - `INTEGER` +* - `LONG` + - `BIGINT` +* - `FLOAT` + - `REAL` +* - `DOUBLE` + - `DOUBLE` +* - `STRING` + - `VARCHAR` +* - `BYTES` + - `VARBINARY` +* - `JSON` + - `JSON` +* - `TIMESTAMP` + - `TIMESTAMP` +* - `INT_ARRAY` + - `VARCHAR` +* - `LONG_ARRAY` + - `VARCHAR` +* - `FLOAT_ARRAY` + - `VARCHAR` +* - `DOUBLE_ARRAY` + - `VARCHAR` +* - `STRING_ARRAY` + - `VARCHAR` +::: Pinot does not allow null values in any data type. diff --git a/docs/src/main/sphinx/connector/postgresql.md b/docs/src/main/sphinx/connector/postgresql.md index 9ca7f46fed81..abbf2570c830 100644 --- a/docs/src/main/sphinx/connector/postgresql.md +++ b/docs/src/main/sphinx/connector/postgresql.md @@ -134,82 +134,82 @@ each direction. The connector maps PostgreSQL types to the corresponding Trino types following this table: -```{eval-rst} -.. list-table:: PostgreSQL type to Trino type mapping - :widths: 30, 20, 50 - :header-rows: 1 - - * - PostgreSQL type - - Trino type - - Notes - * - ``BIT`` - - ``BOOLEAN`` - - - * - ``BOOLEAN`` - - ``BOOLEAN`` - - - * - ``SMALLINT`` - - ``SMALLINT`` - - - * - ``INTEGER`` - - ``INTEGER`` - - - * - ``BIGINT`` - - ``BIGINT`` - - - * - ``REAL`` - - ``REAL`` - - - * - ``DOUBLE`` - - ``DOUBLE`` - - - * - ``NUMERIC(p, s)`` - - ``DECIMAL(p, s)`` - - ``DECIMAL(p, s)`` is an alias of ``NUMERIC(p, s)``. See - :ref:`postgresql-decimal-type-handling` for more information. - * - ``CHAR(n)`` - - ``CHAR(n)`` - - - * - ``VARCHAR(n)`` - - ``VARCHAR(n)`` - - - * - ``ENUM`` - - ``VARCHAR`` - - - * - ``BYTEA`` - - ``VARBINARY`` - - - * - ``DATE`` - - ``DATE`` - - - * - ``TIME(n)`` - - ``TIME(n)`` - - - * - ``TIMESTAMP(n)`` - - ``TIMESTAMP(n)`` - - - * - ``TIMESTAMPTZ(n)`` - - ``TIMESTAMP(n) WITH TIME ZONE`` - - - * - ``MONEY`` - - ``VARCHAR`` - - - * - ``UUID`` - - ``UUID`` - - - * - ``JSON`` - - ``JSON`` - - - * - ``JSONB`` - - ``JSON`` - - - * - ``HSTORE`` - - ``MAP(VARCHAR, VARCHAR)`` - - - * - ``ARRAY`` - - Disabled, ``ARRAY``, or ``JSON`` - - See :ref:`postgresql-array-type-handling` for more information. -``` + +:::{list-table} PostgreSQL type to Trino type mapping +:widths: 30, 30, 40 +:header-rows: 1 + +* - PostgreSQL type + - Trino type + - Notes +* - `BIT` + - `BOOLEAN` + - +* - `BOOLEAN` + - `BOOLEAN` + - +* - `SMALLINT` + - `SMALLINT` + - +* - `INTEGER` + - `INTEGER` + - +* - `BIGINT` + - `BIGINT` + - +* - `REAL` + - `REAL` + - +* - `DOUBLE` + - `DOUBLE` + - +* - `NUMERIC(p, s)` + - `DECIMAL(p, s)` + - `DECIMAL(p, s)` is an alias of `NUMERIC(p, s)`. See + [](postgresql-decimal-type-handling) for more information. +* - `CHAR(n)` + - `CHAR(n)` + - +* - `VARCHAR(n)` + - `VARCHAR(n)` + - +* - `ENUM` + - `VARCHAR` + - +* - `BYTEA` + - `VARBINARY` + - +* - `DATE` + - `DATE` + - +* - `TIME(n)` + - `TIME(n)` + - +* - `TIMESTAMP(n)` + - `TIMESTAMP(n)` + - +* - `TIMESTAMPTZ(n)` + - `TIMESTAMP(n) WITH TIME ZONE` + - +* - `MONEY` + - `VARCHAR` + - +* - `UUID` + - `UUID` + - +* - `JSON` + - `JSON` + - +* - `JSONB` + - `JSON` + - +* - `HSTORE` + - `MAP(VARCHAR, VARCHAR)` + - +* - `ARRAY` + - Disabled, `ARRAY`, or `JSON` + - See [](postgresql-array-type-handling) for more information. +::: No other types are supported. @@ -218,67 +218,66 @@ No other types are supported. The connector maps Trino types to the corresponding PostgreSQL types following this table: -```{eval-rst} -.. list-table:: Trino type to PostgreSQL type mapping - :widths: 30, 20, 50 - :header-rows: 1 - - * - Trino type - - PostgreSQL type - - Notes - * - ``BOOLEAN`` - - ``BOOLEAN`` - - - * - ``SMALLINT`` - - ``SMALLINT`` - - - * - ``TINYINT`` - - ``SMALLINT`` - - - * - ``INTEGER`` - - ``INTEGER`` - - - * - ``BIGINT`` - - ``BIGINT`` - - - * - ``DOUBLE`` - - ``DOUBLE`` - - - * - ``DECIMAL(p, s)`` - - ``NUMERIC(p, s)`` - - ``DECIMAL(p, s)`` is an alias of ``NUMERIC(p, s)``. See - :ref:`postgresql-decimal-type-handling` for more information. - * - ``CHAR(n)`` - - ``CHAR(n)`` - - - * - ``VARCHAR(n)`` - - ``VARCHAR(n)`` - - - * - ``VARBINARY`` - - ``BYTEA`` - - - * - ``DATE`` - - ``DATE`` - - - * - ``TIME(n)`` - - ``TIME(n)`` - - - * - ``TIMESTAMP(n)`` - - ``TIMESTAMP(n)`` - - - * - ``TIMESTAMP(n) WITH TIME ZONE`` - - ``TIMESTAMPTZ(n)`` - - - * - ``UUID`` - - ``UUID`` - - - * - ``JSON`` - - ``JSONB`` - - - * - ``ARRAY`` - - ``ARRAY`` - - See :ref:`postgresql-array-type-handling` for more information. -``` +:::{list-table} Trino type to PostgreSQL type mapping +:widths: 30, 30, 40 +:header-rows: 1 + +* - Trino type + - PostgreSQL type + - Notes +* - `BOOLEAN` + - `BOOLEAN` + - +* - `SMALLINT` + - `SMALLINT` + - +* - `TINYINT` + - `SMALLINT` + - +* - `INTEGER` + - `INTEGER` + - +* - `BIGINT` + - `BIGINT` + - +* - `DOUBLE` + - `DOUBLE` + - +* - `DECIMAL(p, s)` + - `NUMERIC(p, s)` + - `DECIMAL(p, s)` is an alias of `NUMERIC(p, s)`. See + [](postgresql-decimal-type-handling) for more information. +* - `CHAR(n)` + - `CHAR(n)` + - +* - `VARCHAR(n)` + - `VARCHAR(n)` + - +* - `VARBINARY` + - `BYTEA` + - +* - `DATE` + - `DATE` + - +* - `TIME(n)` + - `TIME(n)` + - +* - `TIMESTAMP(n)` + - `TIMESTAMP(n)` + - +* - `TIMESTAMP(n) WITH TIME ZONE` + - `TIMESTAMPTZ(n)` + - +* - `UUID` + - `UUID` + - +* - `JSON` + - `JSONB` + - +* - `ARRAY` + - `ARRAY` + - See [](postgresql-array-type-handling) for more information. +:::: No other types are supported. diff --git a/docs/src/main/sphinx/connector/prometheus.md b/docs/src/main/sphinx/connector/prometheus.md index 357c68236eb1..aba0eef9afbf 100644 --- a/docs/src/main/sphinx/connector/prometheus.md +++ b/docs/src/main/sphinx/connector/prometheus.md @@ -91,20 +91,20 @@ connector {ref}`modifies some types ` when reading data. The connector returns fixed columns that have a defined mapping to Trino types according to the following table: -```{eval-rst} -.. list-table:: Prometheus column to Trino type mapping - :widths: 50, 50 - :header-rows: 1 - - * - Prometheus column - - Trino type - * - ``labels`` - - ``MAP(VARCHAR,VARCHAR)`` - * - ``TIMESTAMP`` - - ``TIMESTAMP(3) WITH TIMEZONE`` - * - ``value`` - - ``DOUBLE`` -``` + +:::{list-table} Prometheus column to Trino type mapping +:widths: 50, 50 +:header-rows: 1 + +* - Prometheus column + - Trino type +* - `labels ` + - `MAP(VARCHAR,VARCHAR) ` +* - `TIMESTAMP ` + - `TIMESTAMP(3) WITH TIMEZONE ` +* - `value ` + - `DOUBLE ` +::: No other types are supported. diff --git a/docs/src/main/sphinx/connector/singlestore.md b/docs/src/main/sphinx/connector/singlestore.md index b3512d9f094e..92a5eff7acb1 100644 --- a/docs/src/main/sphinx/connector/singlestore.md +++ b/docs/src/main/sphinx/connector/singlestore.md @@ -140,93 +140,92 @@ each direction. The connector maps Singlestore types to the corresponding Trino types following this table: -```{eval-rst} -.. list-table:: Singlestore to Trino type mapping - :widths: 30, 20, 50 - :header-rows: 1 - - * - Singlestore type - - Trino type - - Notes - * - ``BIT`` - - ``BOOLEAN`` - - - * - ``BOOLEAN`` - - ``BOOLEAN`` - - - * - ``TINYINT`` - - ``TINYINT`` - - - * - ``TINYINT UNSIGNED`` - - ``SMALLINT`` - - - * - ``SMALLINT`` - - ``SMALLINT`` - - - * - ``SMALLINT UNSIGNED`` - - ``INTEGER`` - - - * - ``INTEGER`` - - ``INTEGER`` - - - * - ``INTEGER UNSIGNED`` - - ``BIGINT`` - - - * - ``BIGINT`` - - ``BIGINT`` - - - * - ``BIGINT UNSIGNED`` - - ``DECIMAL(20, 0)`` - - - * - ``DOUBLE`` - - ``DOUBLE`` - - - * - ``REAL`` - - ``DOUBLE`` - - - * - ``DECIMAL(p, s)`` - - ``DECIMAL(p, s)`` - - See :ref:`Singlestore DECIMAL type handling ` - * - ``CHAR(n)`` - - ``CHAR(n)`` - - - * - ``TINYTEXT`` - - ``VARCHAR(255)`` - - - * - ``TEXT`` - - ``VARCHAR(65535)`` - - - * - ``MEDIUMTEXT`` - - ``VARCHAR(16777215)`` - - - * - ``LONGTEXT`` - - ``VARCHAR`` - - - * - ``VARCHAR(n)`` - - ``VARCHAR(n)`` - - - * - ``LONGBLOB`` - - ``VARBINARY`` - - - * - ``DATE`` - - ``DATE`` - - - * - ``TIME`` - - ``TIME(0)`` - - - * - ``TIME(6)`` - - ``TIME(6)`` - - - * - ``DATETIME`` - - ``TIMESTAMP(0)`` - - - * - ``DATETIME(6)`` - - ``TIMESTAMP(6)`` - - - * - ``JSON`` - - ``JSON`` - - -``` +:::{list-table} Singlestore to Trino type mapping +:widths: 30, 30, 40 +:header-rows: 1 + +* - Singlestore type + - Trino type + - Notes +* - `BIT` + - `BOOLEAN` + - +* - `BOOLEAN` + - `BOOLEAN` + - +* - `TINYINT` + - `TINYINT` + - +* - `TINYINT UNSIGNED` + - `SMALLINT` + - +* - `SMALLINT` + - `SMALLINT` + - +* - `SMALLINT UNSIGNED` + - `INTEGER` + - +* - `INTEGER` + - `INTEGER` + - +* - `INTEGER UNSIGNED` + - `BIGINT` + - +* - `BIGINT` + - `BIGINT` + - +* - `BIGINT UNSIGNED` + - `DECIMAL(20, 0)` + - +* - `DOUBLE` + - `DOUBLE` + - +* - `REAL` + - `DOUBLE` + - +* - `DECIMAL(p, s)` + - `DECIMAL(p, s)` + - See [Singlestore DECIMAL type handling](singlestore-decimal-handling) +* - `CHAR(n)` + - `CHAR(n)` + - +* - `TINYTEXT` + - `VARCHAR(255)` + - +* - `TEXT` + - `VARCHAR(65535)` + - +* - `MEDIUMTEXT` + - `VARCHAR(16777215)` + - +* - `LONGTEXT` + - `VARCHAR` + - +* - `VARCHAR(n)` + - `VARCHAR(n)` + - +* - `LONGBLOB` + - `VARBINARY` + - +* - `DATE` + - `DATE` + - +* - `TIME` + - `TIME(0)` + - +* - `TIME(6)` + - `TIME(6)` + - +* - `DATETIME` + - `TIMESTAMP(0)` + - +* - `DATETIME(6)` + - `TIMESTAMP(6)` + - +* - `JSON` + - `JSON` + - +::: No other types are supported. @@ -235,75 +234,74 @@ No other types are supported. The connector maps Trino types to the corresponding Singlestore types following this table: -```{eval-rst} -.. list-table:: Trino to Singlestore type mapping - :widths: 30, 20, 50 - :header-rows: 1 - - * - Trino type - - Singlestore type - - Notes - * - ``BOOLEAN`` - - ``BOOLEAN`` - - - * - ``TINYINT`` - - ``TINYINT`` - - - * - ``SMALLINT`` - - ``SMALLINT`` - - - * - ``INTEGER`` - - ``INTEGER`` - - - * - ``BIGINT`` - - ``BIGINT`` - - - * - ``DOUBLE`` - - ``DOUBLE`` - - - * - ``REAL`` - - ``FLOAT`` - - - * - ``DECIMAL(p, s)`` - - ``DECIMAL(p, s)`` - - See :ref:`Singlestore DECIMAL type handling ` - * - ``CHAR(n)`` - - ``CHAR(n)`` - - - * - ``VARCHAR(65535)`` - - ``TEXT`` - - - * - ``VARCHAR(16777215)`` - - ``MEDIUMTEXT`` - - - * - ``VARCHAR`` - - ``LONGTEXT`` - - - * - ``VARCHAR(n)`` - - ``VARCHAR(n)`` - - - * - ``VARBINARY`` - - ``LONGBLOB`` - - - * - ``DATE`` - - ``DATE`` - - - * - ``TIME(0)`` - - ``TIME`` - - - * - ``TIME(6)`` - - ``TIME(6)`` - - - * - ``TIMESTAMP(0)`` - - ``DATETIME`` - - - * - ``TIMESTAMP(6)`` - - ``DATETIME(6)`` - - - * - ``JSON`` - - ``JSON`` - - -``` +:::{list-table} Trino to Singlestore type mapping +:widths: 30, 30, 40 +:header-rows: 1 + +* - Trino type + - Singlestore type + - Notes +* - `BOOLEAN` + - `BOOLEAN` + - +* - `TINYINT` + - `TINYINT` + - +* - `SMALLINT` + - `SMALLINT` + - +* - `INTEGER` + - `INTEGER` + - +* - `BIGINT` + - `BIGINT` + - +* - `DOUBLE` + - `DOUBLE` + - +* - `REAL` + - `FLOAT` + - +* - `DECIMAL(p, s)` + - `DECIMAL(p, s)` + - See [Singlestore DECIMAL type handling](singlestore-decimal-handling) +* - `CHAR(n)` + - `CHAR(n)` + - +* - `VARCHAR(65535)` + - `TEXT` + - +* - `VARCHAR(16777215)` + - `MEDIUMTEXT` + - +* - `VARCHAR` + - `LONGTEXT` + - +* - `VARCHAR(n)` + - `VARCHAR(n)` + - +* - `VARBINARY` + - `LONGBLOB` + - +* - `DATE` + - `DATE` + - +* - `TIME(0)` + - `TIME` + - +* - `TIME(6)` + - `TIME(6)` + - +* - `TIMESTAMP(0)` + - `DATETIME` + - +* - `TIMESTAMP(6)` + - `DATETIME(6)` + - +* - `JSON` + - `JSON` + - +::: No other types are supported. diff --git a/docs/src/main/sphinx/connector/sqlserver.md b/docs/src/main/sphinx/connector/sqlserver.md index 592141a76e0f..108ef8bf2373 100644 --- a/docs/src/main/sphinx/connector/sqlserver.md +++ b/docs/src/main/sphinx/connector/sqlserver.md @@ -97,18 +97,17 @@ catalog named `sales` using the configured connector. The SQL Server connector supports additional catalog properties to configure the behavior of the connector and the issues queries to the database. -```{eval-rst} -.. list-table:: - :widths: 45, 55 - :header-rows: 1 - - * - Property name - - Description - * - ``sqlserver.snapshot-isolation.disabled`` - - Control the automatic use of snapshot isolation for transactions issued by - Trino in SQL Server. Defaults to ``false``, which means that snapshot +:::{list-table} +:widths: 45, 55 +:header-rows: 1 + +* - Property name + - Description +* - `sqlserver.snapshot-isolation.disabled` + - Control the automatic use of snapshot isolation for transactions issued by + Trino in SQL Server. Defaults to `false`, which means that snapshot isolation is enabled. -``` +::: ```{include} jdbc-procedures.fragment ``` @@ -169,131 +168,129 @@ each direction. The connector maps SQL Server types to the corresponding Trino types following this table: -```{eval-rst} -.. list-table:: SQL Server type to Trino type mapping - :widths: 30, 20, 50 - :header-rows: 1 - - * - SQL Server database type - - Trino type - - Notes - * - ``BIT`` - - ``BOOLEAN`` - - - * - ``TINYINT`` - - ``SMALLINT`` - - SQL Server ``TINYINT`` is actually ``unsigned TINYINT`` - * - ``SMALLINT`` - - ``SMALLINT`` - - - * - ``INTEGER`` - - ``INTEGER`` - - - * - ``BIGINT`` - - ``BIGINT`` - - - * - ``DOUBLE PRECISION`` - - ``DOUBLE`` - - - * - ``FLOAT[(n)]`` - - ``REAL`` or ``DOUBLE`` - - See :ref:`sqlserver-numeric-mapping` - * - ``REAL`` - - ``REAL`` - - - * - ``DECIMAL[(p[, s])]``, ``NUMERIC[(p[, s])]`` - - ``DECIMAL(p, s)`` - - - * - ``CHAR[(n)]`` - - ``CHAR(n)`` - - ``1 <= n <= 8000`` - * - ``NCHAR[(n)]`` - - ``CHAR(n)`` - - ``1 <= n <= 4000`` - * - ``VARCHAR[(n | max)]``, ``NVARCHAR[(n | max)]`` - - ``VARCHAR(n)`` - - ``1 <= n <= 8000``, ``max = 2147483647`` - * - ``TEXT`` - - ``VARCHAR(2147483647)`` - - - * - ``NTEXT`` - - ``VARCHAR(1073741823)`` - - - * - ``VARBINARY[(n | max)]`` - - ``VARBINARY`` - - ``1 <= n <= 8000``, ``max = 2147483647`` - * - ``DATE`` - - ``DATE`` - - - * - ``TIME[(n)]`` - - ``TIME(n)`` - - ``0 <= n <= 7`` - * - ``DATETIME2[(n)]`` - - ``TIMESTAMP(n)`` - - ``0 <= n <= 7`` - * - ``SMALLDATETIME`` - - ``TIMESTAMP(0)`` - - - * - ``DATETIMEOFFSET[(n)]`` - - ``TIMESTAMP(n) WITH TIME ZONE`` - - ``0 <= n <= 7`` -``` +:::{list-table} SQL Server type to Trino type mapping +:widths: 30, 30, 40 +:header-rows: 1 + +* - SQL Server database type + - Trino type + - Notes +* - `BIT` + - `BOOLEAN` + - +* - `TINYINT` + - `SMALLINT` + - SQL Server `TINYINT` is actually `unsigned TINYINT` +* - `SMALLINT` + - `SMALLINT` + - +* - `INTEGER` + - `INTEGER` + - +* - `BIGINT` + - `BIGINT` + - +* - `DOUBLE PRECISION` + - `DOUBLE` + - +* - `FLOAT[(n)]` + - `REAL` or `DOUBLE` + - See [](sqlserver-numeric-mapping) +* - `REAL` + - `REAL` + - +* - `DECIMAL[(p[, s])]`, `NUMERIC[(p[, s])]` + - `DECIMAL(p, s)` + - +* - `CHAR[(n)]` + - `CHAR(n)` + - `1 <= n <= 8000` +* - `NCHAR[(n)]` + - `CHAR(n)` + - `1 <= n <= 4000` +* - `VARCHAR[(n | max)]`, `NVARCHAR[(n | max)]` + - `VARCHAR(n)` + - `1 <= n <= 8000`, `max = 2147483647` +* - `TEXT` + - `VARCHAR(2147483647)` + - +* - `NTEXT` + - `VARCHAR(1073741823)` + - +* - `VARBINARY[(n | max)]` + - `VARBINARY` + - `1 <= n <= 8000`, `max = 2147483647` +* - `DATE` + - `DATE` + - +* - `TIME[(n)]` + - `TIME(n)` + - `0 <= n <= 7` +* - `DATETIME2[(n)]` + - `TIMESTAMP(n)` + - `0 <= n <= 7` +* - `SMALLDATETIME` + - `TIMESTAMP(0)` + - +* - `DATETIMEOFFSET[(n)]` + - `TIMESTAMP(n) WITH TIME ZONE` + - `0 <= n <= 7` +::: ### Trino type to SQL Server type mapping The connector maps Trino types to the corresponding SQL Server types following this table: -```{eval-rst} -.. list-table:: Trino type to SQL Server type mapping - :widths: 30, 20, 50 - :header-rows: 1 - - * - Trino type - - SQL Server type - - Notes - * - ``BOOLEAN`` - - ``BIT`` - - - * - ``TINYINT`` - - ``TINYINT`` - - Trino only supports writing values belonging to ``[0, 127]`` - * - ``SMALLINT`` - - ``SMALLINT`` - - - * - ``INTEGER`` - - ``INTEGER`` - - - * - ``BIGINT`` - - ``BIGINT`` - - - * - ``REAL`` - - ``REAL`` - - - * - ``DOUBLE`` - - ``DOUBLE PRECISION`` - - - * - ``DECIMAL(p, s)`` - - ``DECIMAL(p, s)`` - - - * - ``CHAR(n)`` - - ``NCHAR(n)`` or ``NVARCHAR(max)`` - - See :ref:`sqlserver-character-mapping` - * - ``VARCHAR(n)`` - - ``NVARCHAR(n)`` or ``NVARCHAR(max)`` - - See :ref:`sqlserver-character-mapping` - * - ``VARBINARY`` - - ``VARBINARY(max)`` - - - * - ``DATE`` - - ``DATE`` - - - * - ``TIME(n)`` - - ``TIME(n)`` - - ``0 <= n <= 7`` - * - ``TIMESTAMP(n)`` - - ``DATETIME2(n)`` - - ``0 <= n <= 7`` -``` +:::{list-table} Trino type to SQL Server type mapping +:widths: 30, 30, 40 +:header-rows: 1 + +* - Trino type + - SQL Server type + - Notes +* - `BOOLEAN` + - `BIT` + - +* - `TINYINT` + - `TINYINT` + - Trino only supports writing values belonging to `[0, 127]` +* - `SMALLINT` + - `SMALLINT` + - +* - `INTEGER` + - `INTEGER` + - +* - `BIGINT` + - `BIGINT` + - +* - `REAL` + - `REAL` + - +* - `DOUBLE` + - `DOUBLE PRECISION` + - +* - `DECIMAL(p, s)` + - `DECIMAL(p, s)` + - +* - `CHAR(n)` + - `NCHAR(n)` or `NVARCHAR(max)` + - See [](sqlserver-character-mapping) +* - `VARCHAR(n)` + - `NVARCHAR(n)` or `NVARCHAR(max)` + - See [](sqlserver-character-mapping) +* - `VARBINARY` + - `VARBINARY(max)` + - +* - `DATE` + - `DATE` + - +* - `TIME(n)` + - `TIME(n)` + - `0 <= n <= 7` +* - `TIMESTAMP(n)` + - `DATETIME2(n)` + - `0 <= n <= 7` +::: Complete list of [SQL Server data types](https://msdn.microsoft.com/library/ms187752.aspx). @@ -537,25 +534,24 @@ logging requirements](https://docs.microsoft.com/sql/relational-databases/import The following table shows the relevant catalog configuration properties and their default values: -```{eval-rst} -.. list-table:: Bulk load properties - :widths: 30, 60, 10 - :header-rows: 1 - - * - Property name - - Description - - Default - * - ``sqlserver.bulk-copy-for-write.enabled`` - - Use the SQL Server bulk copy API for writes. The corresponding catalog - session property is ``bulk_copy_for_write``. - - ``false`` - * - ``sqlserver.bulk-copy-for-write.lock-destination-table`` - - Obtain a bulk update lock on the destination table for write operations. - The corresponding catalog session property is - ``bulk_copy_for_write_lock_destination_table``. Setting is only used when - ``bulk-copy-for-write.enabled=true``. - - ``false`` -``` +:::{list-table} Bulk load properties +:widths: 30, 60, 10 +:header-rows: 1 + +* - Property name + - Description + - Default +* - `sqlserver.bulk-copy-for-write.enabled` + - Use the SQL Server bulk copy API for writes. The corresponding catalog + session property is `bulk_copy_for_write`. + - `false` +* - `sqlserver.bulk-copy-for-write.lock-destination-table` + - Obtain a bulk update lock on the destination table for write operations. The + corresponding catalog session property is + `bulk_copy_for_write_lock_destination_table`. Setting is only used when + `bulk-copy-for-write.enabled=true`. + - `false` +::: Limitations: diff --git a/docs/src/main/sphinx/connector/system.md b/docs/src/main/sphinx/connector/system.md index a4f034bbe515..07ad18fc87d1 100644 --- a/docs/src/main/sphinx/connector/system.md +++ b/docs/src/main/sphinx/connector/system.md @@ -57,39 +57,37 @@ that can be set when creating a new table. The materialized views table contains the following information about all {ref}`materialized views `: -```{eval-rst} -.. list-table:: Metadata for materialized views - :widths: 30, 70 - :header-rows: 1 - - * - Column - - Description - * - ``catalog_name`` - - Name of the catalog containing the materialized view. - * - ``schema_name`` - - Name of the schema in ``catalog_name`` containing the materialized view. - * - ``name`` - - Name of the materialized view. - * - ``storage_catalog`` - - Name of the catalog used for the storage table backing the materialized - view. - * - ``storage_schema`` - - Name of the schema in ``storage_catalog`` used for the storage table - backing the materialized view. - * - ``storage_table`` - - Name of the storage table backing the materialized view. - * - ``freshness`` - - Freshness of data in the storage table. Queries on the - materialized view access the storage table if not ``STALE``, otherwise - the ``definition`` is used to access the underlying data in the source - tables. - * - ``owner`` - - Username of the creator and owner of the materialized view. - * - ``comment`` - - User supplied text about the materialized view. - * - ``definition`` - - SQL query that defines the data provided by the materialized view. -``` +:::{list-table} Metadata for materialized views +:widths: 30, 70 +:header-rows: 1 + +* - Column + - Description +* - `catalog_name` + - Name of the catalog containing the materialized view. +* - `schema_name` + - Name of the schema in `catalog_name` containing the materialized view. +* - `name` + - Name of the materialized view. +* - `storage_catalog` + - Name of the catalog used for the storage table backing the materialized + view. +* - `storage_schema` + - Name of the schema in `storage_catalog` used for the storage table backing + the materialized view. +* - `storage_table` + - Name of the storage table backing the materialized view. +* - `freshness` + - Freshness of data in the storage table. Queries on the materialized view + access the storage table if not `STALE`, otherwise the `definition` is used + to access the underlying data in the source tables. +* - `owner` + - Username of the creator and owner of the materialized view. +* - `comment` + - User supplied text about the materialized view. +* - `definition` + - SQL query that defines the data provided by the materialized view. +::: ### `metadata.materialized_view_properties` diff --git a/docs/src/main/sphinx/develop/client-protocol.md b/docs/src/main/sphinx/develop/client-protocol.md index 93fffc9dd9b9..aa1f3413fbbc 100644 --- a/docs/src/main/sphinx/develop/client-protocol.md +++ b/docs/src/main/sphinx/develop/client-protocol.md @@ -61,156 +61,142 @@ the REST API endpoints are listed in this table. For more details, refer to the class `io.trino.client.QueryResults` in module `trino-client` in the `client` directory of the Trino source code. -```{eval-rst} -.. list-table:: ``QueryResults attributes`` - :widths: 25, 55 - :header-rows: 1 +:::{list-table} QueryResults attributes +:widths: 25, 55 +:header-rows: 1 - * - Attribute - - Description - * - ``id`` - - The ID of the query. - * - ``nextUri`` - - If present, the URL to use for subsequent ``GET`` or - ``DELETE`` requests. If not present, the query is complete or - ended in error. - * - ``columns`` - - A list of the names and types of the columns returned by the query. - * - ``data`` - - The ``data`` attribute contains a list of the rows returned by the - query request. Each row is itself a list that holds values of the - columns in the row, in the order specified by the ``columns`` - attribute. - * - ``updateType`` - - A human-readable string representing the operation. For a - ``CREATE TABLE`` request, the ``updateType`` is - "CREATE TABLE"; for ``SET SESSION`` it is "SET SESSION"; etc. - * - ``error`` - - If query failed, the ``error`` attribute contains a ``QueryError`` object. - That object contains a ``message``, an ``errorCode`` and other information - about the error. See the ``io.trino.client.QueryError`` class in module - ``trino-client`` in the ``client`` directory for more details. +* - Attribute + - Description +* - `id` + - The ID of the query. +* - `nextUri` + - If present, the URL to use for subsequent `GET` or `DELETE` requests. If not + present, the query is complete or ended in error. +* - `columns` + - A list of the names and types of the columns returned by the query. +* - `data` + - The `data` attribute contains a list of the rows returned by the query + request. Each row is itself a list that holds values of the columns in the + row, in the order specified by the `columns` attribute. +* - `updateType` + - A human-readable string representing the operation. For a `CREATE TABLE` + request, the `updateType` is "CREATE TABLE"; for `SET SESSION` it is "SET + SESSION"; etc. +* - `error` + - If query failed, the `error` attribute contains a `QueryError` object. That + object contains a `message`, an `errorCode` and other information about the + error. See the `io.trino.client.QueryError` class in module `trino-client` + in the `client` directory for more details. -``` +::: ## `QueryResults` diagnostic attributes These `QueryResults` data members may be useful in tracking down problems: -```{eval-rst} -.. list-table:: ``QueryResults diagnostic attributes`` - :widths: 20, 20, 40 - :header-rows: 1 +:::{list-table} QueryResults diagnostic attributes +:widths: 20, 20, 40 +:header-rows: 1 - * - Attribute - - Type - - Description - * - ``queryError`` - - ``QueryError`` - - Non-null only if the query resulted in an error. - * - ``failureInfo`` - - ``FailureInfo`` - - ``failureInfo`` has detail on the reason for the failure, including - a stack trace, and ``FailureInfo.errorLocation``, providing the - query line number and column number where the failure was detected. - * - ``warnings`` - - ``List`` - - A usually-empty list of warnings. - * - ``statementStats`` - - ``StatementStats`` - - A class containing statistics about the query execution. Of - particular interest is ``StatementStats.rootStage``, of type - ``StageStats``, providing statistics on the execution of each of - the stages of query processing. -``` +* - Attribute + - Type + - Description +* - `queryError` + - `QueryError` + - Non-null only if the query resulted in an error. +* - `failureInfo` + - `FailureInfo` + - `failureInfo` has detail on the reason for the failure, including a stack + trace, and `FailureInfo.errorLocation`, providing the query line number and + column number where the failure was detected. +* - `warnings` + - `List` + - A usually-empty list of warnings. +* - `statementStats` + - `StatementStats` + - A class containing statistics about the query execution. Of particular + interest is `StatementStats.rootStage`, of type `StageStats`, providing + statistics on the execution of each of the stages of query processing. +::: (client-request-headers)= ## Client request headers -This table lists all supported client request headers. Many of the -headers can be updated in the client as response headers, and supplied -in subsequent requests, just like browser cookies. +This table lists all supported client request headers. Many of the headers can +be updated in the client as response headers, and supplied in subsequent +requests, just like browser cookies. -```{eval-rst} -.. list-table:: Client request headers - :widths: 30, 50 - :header-rows: 1 +:::{list-table} Client request headers +:widths: 30, 50 +:header-rows: 1 - * - Header name - - Description - * - ``X-Trino-User`` - - Specifies the session user. If not supplied, the session user is - automatically determined via :doc:`/security/user-mapping`. - * - ``X-Trino-Original-User`` - - Specifies the session's original user. - * - ``X-Trino-Source`` - - For reporting purposes, this supplies the name of the software - that submitted the query. - * - ``X-Trino-Catalog`` - - The catalog context for query processing. Set by response - header ``X-Trino-Set-Catalog``. - * - ``X-Trino-Schema`` - - The schema context for query processing. Set by response - header ``X-Trino-Set-Schema``. - * - ``X-Trino-Time-Zone`` - - The timezone for query processing. Defaults to the timezone - of the Trino cluster, and not the timezone of the client. - * - ``X-Trino-Language`` - - The language to use when processing the query and formatting - results, formatted as a Java ``Locale`` string, e.g., ``en-US`` - for US English. The language of the - session can be set on a per-query basis using the - ``X-Trino-Language`` HTTP header. - * - ``X-Trino-Trace-Token`` - - Supplies a trace token to the Trino engine to help identify - log lines that originate with this query request. - * - ``X-Trino-Session`` - - Supplies a comma-separated list of name=value pairs as session - properties. When the Trino client run a - ``SET SESSION name=value`` query, the name=value pair - is returned in the ``X-Set-Trino-Session`` response header, - and added to the client's list of session properties. - If the response header ``X-Trino-Clear-Session`` is returned, - its value is the name of a session property that is - removed from the client's accumulated list. - * - ``X-Trino-Role`` - - Sets the "role" for query processing. A "role" represents - a collection of permissions. Set by response header - ``X-Trino-Set-Role``. See :doc:`/sql/create-role` to - understand roles. - * - ``X-Trino-Prepared-Statement`` - - A comma-separated list of the name=value pairs, where the - names are names of previously prepared SQL statements, and - the values are keys that identify the executable form of the - named prepared statements. - * - ``X-Trino-Transaction-Id`` - - The transaction ID to use for query processing. Set - by response header ``X-Trino-Started-Transaction-Id`` and - cleared by ``X-Trino-Clear-Transaction-Id``. - * - ``X-Trino-Client-Info`` - - Contains arbitrary information about the client program - submitting the query. - * - ``X-Trino-Client-Tags`` - - A comma-separated list of "tag" strings, used to identify - Trino resource groups. - * - ``X-Trino-Resource-Estimate`` - - A comma-separated list of ``resource=value`` type - assigments. The possible choices of ``resource`` are - ``EXECUTION_TIME``, ``CPU_TIME``, ``PEAK_MEMORY`` and - ``PEAK_TASK_MEMORY``. ``EXECUTION_TIME`` and ``CPU_TIME`` - have values specified as airlift ``Duration`` strings - The format is a double precision number followed by - a ``TimeUnit`` string, e.g., of ``s`` for seconds, - ``m`` for minutes, ``h`` for hours, etc. "PEAK_MEMORY" and - "PEAK_TASK_MEMORY" are specified as as airlift ``DataSize`` strings, - whose format is an integer followed by ``B`` for bytes; ``kB`` for - kilobytes; ``mB`` for megabytes, ``gB`` for gigabytes, etc. - * - ``X-Trino-Extra-Credential`` - - Provides extra credentials to the connector. The header is - a name=value string that is saved in the session ``Identity`` - object. The name and value are only meaningful to the connector. -``` +* - Header name + - Description +* - `X-Trino-User` + - Specifies the session user. If not supplied, the session user is + automatically determined via [](/security/user-mapping). +* - `X-Trino-Original-User` + - Specifies the session's original user. +* - `X-Trino-Source` + - For reporting purposes, this supplies the name of the software that + submitted the query. +* - `X-Trino-Catalog` + - The catalog context for query processing. Set by response header + `X-Trino-Set-Catalog`. +* - `X-Trino-Schema` + - The schema context for query processing. Set by response header + `X-Trino-Set-Schema`. +* - `X-Trino-Time-Zone` + - The timezone for query processing. Defaults to the timezone of the Trino + clusßter, and not the timezone of the client. +* - `X-Trino-Language` + - The language to use when processing the query and formatting results, + formatted as a Java `Locale` string, e.g., `en-US` for US English. The + language of the session can be set on a per-query basis using the + `X-Trino-Language` HTTP header. +* - `X-Trino-Trace-Token` + - Supplies a trace token to the Trino engine to help identify log lines that + originate with this query request. +* - `X-Trino-Session` + - Supplies a comma-separated list of name=value pairs as session properties. + When the Trino client run a `SET SESSION name=value` query, the name=value + pair is returned in the `X-Set-Trino-Session` response header, and added to + the client's list of session properties. If the response header + `X-Trino-Clear-Session` is returned, its value is the name of a session + property that is removed from the client's accumulated list. +* - `X-Trino-Role` + - Sets the "role" for query processing. A "role" represents a collection of + permissions. Set by response header `X-Trino-Set-Role`. See + [](/sql/create-role) to understand roles. +* - `X-Trino-Prepared-Statement` + - A comma-separated list of the name=value pairs, where the names are names of + previously prepared SQL statements, and the values are keys that identify + the executable form of the named prepared statements. +* - `X-Trino-Transaction-Id` + - The transaction ID to use for query processing. Set by response header + `X-Trino-Started-Transaction-Id` and cleared by + `X-Trino-Clear-Transaction-Id`. +* - `X-Trino-Client-Info` + - Contains arbitrary information about the client program submitting the + query. +* - `X-Trino-Client-Tags` + - A comma-separated list of "tag" strings, used to identify Trino resource + groups. +* - `X-Trino-Resource-Estimate` + - A comma-separated list of `resource=value` type assignments. The possible + choices of `resource` are `EXECUTION_TIME`, `CPU_TIME`, `PEAK_MEMORY` and + `PEAK_TASK_MEMORY`. `EXECUTION_TIME` and `CPU_TIME` have values specified + as airlift `Duration` strings The format is a double precision number + followed by a `TimeUnit` string, e.g., of `s` for seconds, `m` for minutes, + `h` for hours, etc. "PEAK_MEMORY" and "PEAK_TASK_MEMORY" are specified as + airlift `DataSize` strings, whose format is an integer followed by `B` for + bytes; `kB` for kilobytes; `mB` for megabytes, `gB` for gigabytes, etc. +* - `X-Trino-Extra-Credential` + - Provides extra credentials to the connector. The header is a name=value + string that is saved in the session `Identity` object. The name and value + are only meaningful to the connector. +::: ## Client response headers @@ -218,58 +204,54 @@ This table lists the supported client response headers. After receiving a response, a client must update the request headers used in subsequent requests to be consistent with the response headers received. -```{eval-rst} -.. list-table:: Client response headers - :widths: 30, 50 - :header-rows: 1 +:::{list-table} Client response headers +:widths: 30, 50 +:header-rows: 1 - * - Header name - - Description - * - ``X-Trino-Set-Catalog`` - - Instructs the client to set the catalog in the - ``X-Trino-Catalog`` request header in subsequent client requests. - * - ``X-Trino-Set-Schema`` - - Instructs the client to set the schema in the - ``X-Trino-Schema`` request header in subsequent client requests. - * - ``X-Trino-Set-Authorization-User`` - - Instructs the client to set the session authorization user in the - ``X-Trino-Authorization-User`` request header in subsequent client requests. - * - ``X-Trino-Reset-Authorization-User`` - - Instructs the client to remove ``X-Trino-Authorization-User`` request header - in subsequent client requests to reset the authorization user back to the - original user. - * - ``X-Trino-Set-Session`` - - The value of the ``X-Trino-Set-Session`` response header is a - string of the form *property* = *value*. It - instructs the client include session property *property* with value - *value* in the ``X-Trino-Session`` header of subsequent - client requests. - * - ``X-Trino-Clear-Session`` - - Instructs the client to remove the session property with the - whose name is the value of the ``X-Trino-Clear-Session`` header - from the list of session properties - in the ``X-Trino-Session`` header in subsequent client requests. - * - ``X-Trino-Set-Role`` - - Instructs the client to set ``X-Trino-Role`` request header to the - catalog role supplied by the ``X-Trino-Set-Role`` header - in subsequent client requests. - * - ``X-Trino-Added-Prepare`` - - Instructs the client to add the name=value pair to the set of - prepared statements in the ``X-Trino-Prepared-Statement`` - request header in subsequent client requests. - * - ``X-Trino-Deallocated-Prepare`` - - Instructs the client to remove the prepared statement whose name - is the value of the ``X-Trino-Deallocated-Prepare`` header from - the client's list of prepared statements sent in the - ``X-Trino-Prepared-Statement`` request header in subsequent client - requests. - * - ``X-Trino-Started-Transaction-Id`` - - Provides the transaction ID that the client should pass back in the - ``X-Trino-Transaction-Id`` request header in subsequent requests. - * - ``X-Trino-Clear-Transaction-Id`` - - Instructs the client to clear the ``X-Trino-Transaction-Id`` request - header in subsequent requests. -``` +* - Header name + - Description +* - `X-Trino-Set-Catalog` + - Instructs the client to set the catalog in the `X-Trino-Catalog` request + header in subsequent client requests. +* - `X-Trino-Set-Schema` + - Instructs the client to set the schema in the `X-Trino-Schema` request + header in subsequent client requests. +* - `X-Trino-Set-Authorization-User` + - Instructs the client to set the session authorization user in the + `X-Trino-Authorization-User` request header in subsequent client requests. +* - `X-Trino-Reset-Authorization-User` + - Instructs the client to remove `X-Trino-Authorization-User` request header + in subsequent client requests to reset the authorization user back to the + original user. +* - `X-Trino-Set-Session` + - The value of the `X-Trino-Set-Session` response header is a string of the + form *property* = *value*. It instructs the client include session property + *property* with value *value* in the `X-Trino-Session` header of subsequent + client requests. +* - `X-Trino-Clear-Session` + - Instructs the client to remove the session property with the whose name is + the value of the `X-Trino-Clear-Session` header from the list of session + properties in the `X-Trino-Session` header in subsequent client requests. +* - `X-Trino-Set-Role` + - Instructs the client to set `X-Trino-Role` request header to the catalog + role supplied by the `X-Trino-Set-Role` header in subsequent client + requests. +* - `X-Trino-Added-Prepare` + - Instructs the client to add the name=value pair to the set of prepared + statements in the `X-Trino-Prepared-Statement` request header in subsequent + client requests. +* - `X-Trino-Deallocated-Prepare` + - Instructs the client to remove the prepared statement whose name is the + value of the `X-Trino-Deallocated-Prepare` header from the client's list of + prepared statements sent in the `X-Trino-Prepared-Statement` request header + in subsequent client requests. +* - `X-Trino-Started-Transaction-Id` + - Provides the transaction ID that the client should pass back in the + `X-Trino-Transaction-Id` request header in subsequent requests. +* - `X-Trino-Clear-Transaction-Id` + - Instructs the client to clear the `X-Trino-Transaction-Id` request header in + subsequent requests. +::: ## `ProtocolHeaders` diff --git a/docs/src/main/sphinx/develop/connectors.md b/docs/src/main/sphinx/develop/connectors.md index 3574ac13276a..25c289e1663f 100644 --- a/docs/src/main/sphinx/develop/connectors.md +++ b/docs/src/main/sphinx/develop/connectors.md @@ -653,76 +653,74 @@ record it calls `advanceNextPosition` on the cursor. The built-in SQL data types use different Java types as carrier types. -```{eval-rst} -.. list-table:: SQL type to carrier type mapping - :widths: 45, 55 - :header-rows: 1 - - * - SQL type - - Java type - * - ``BOOLEAN`` - - ``boolean`` - * - ``TINYINT`` - - ``long`` - * - ``SMALLINT`` - - ``long`` - * - ``INTEGER`` - - ``long`` - * - ``BIGINT`` - - ``long`` - * - ``REAL`` - - ``double`` - * - ``DOUBLE`` - - ``double`` - * - ``DECIMAL`` - - ``long`` for precision up to 19, inclusive; - ``Int128`` for precision greater than 19 - * - ``VARCHAR`` - - ``Slice`` - * - ``CHAR`` - - ``Slice`` - * - ``VARBINARY`` - - ``Slice`` - * - ``JSON`` - - ``Slice`` - * - ``DATE`` - - ``long`` - * - ``TIME(P)`` - - ``long`` - * - ``TIME WITH TIME ZONE`` - - ``long`` for precision up to 9; - ``LongTimeWithTimeZone`` for precision greater than 9 - * - ``TIMESTAMP(P)`` - - ``long`` for precision up to 6; - ``LongTimestamp`` for precision greater than 6 - * - ``TIMESTAMP(P) WITH TIME ZONE`` - - ``long`` for precision up to 3; - ``LongTimestampWithTimeZone`` for precision greater than 3 - * - ``INTERVAL YEAR TO MONTH`` - - ``long`` - * - ``INTERVAL DAY TO SECOND`` - - ``long`` - * - ``ARRAY`` - - ``Block`` - * - ``MAP`` - - ``Block`` - * - ``ROW`` - - ``Block`` - * - ``IPADDRESS`` - - ``Slice`` - * - ``UUID`` - - ``Slice`` - * - ``HyperLogLog`` - - ``Slice`` - * - ``P4HyperLogLog`` - - ``Slice`` - * - ``SetDigest`` - - ``Slice`` - * - ``QDigest`` - - ``Slice`` - * - ``TDigest`` - - ``TDigest`` -``` +:::{list-table} SQL type to carrier type mapping +:widths: 45, 55 +:header-rows: 1 + +* - SQL type + - Java type +* - `BOOLEAN` + - `boolean` +* - `TINYINT` + - `long` +* - `SMALLINT` + - `long` +* - `INTEGER` + - `long` +* - `BIGINT` + - `long` +* - `REAL` + - `double` +* - `DOUBLE` + - `double` +* - `DECIMAL` + - `long` for precision up to 19, inclusive; `Int128` for precision greater + than 19 +* - `VARCHAR` + - `Slice` +* - `CHAR` + - `Slice` +* - `VARBINARY` + - `Slice` +* - `JSON` + - `Slice` +* - `DATE` + - `long` +* - `TIME(P)` + - `long` +* - `TIME WITH TIME ZONE` + - `long` for precision up to 9; `LongTimeWithTimeZone` for precision greater + than 9 +* - `TIMESTAMP(P)` + - `long` for precision up to 6; `LongTimestamp` for precision greater than 6 +* - `TIMESTAMP(P) WITH TIME ZONE` + - `long` for precision up to 3; `LongTimestampWithTimeZone` for precision + greater than 3 +* - `INTERVAL YEAR TO MONTH` + - `long` +* - `INTERVAL DAY TO SECOND` + - `long` +* - `ARRAY` + - `Block` +* - `MAP` + - `Block` +* - `ROW` + - `Block` +* - `IPADDRESS` + - `Slice` +* - `UUID` + - `Slice` +* - `HyperLogLog` + - `Slice` +* - `P4HyperLogLog` + - `Slice` +* - `SetDigest` + - `Slice` +* - `QDigest` + - `Slice` +* - `TDigest` + - `TDigest` +::: The `RecordCursor.getType(int field)` method returns the SQL type for a field and the field value is returned by one of the following methods, matching From dba5dedd28506af4dd4cb431b6ed8cd71b87cc15 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 8 Nov 2023 14:23:36 +0100 Subject: [PATCH 133/587] Fix incorrect dynamic filtering on UUID in Iceberg Iceberg library orders UUID values differently from Trino. The predicate pushdown is blocked for the UUID type and so should be dynamic filtering. --- .../plugin/iceberg/ExpressionConverter.java | 18 ++++++++++++++++++ .../trino/plugin/iceberg/IcebergMetadata.java | 8 ++------ .../iceberg/IcebergSessionProperties.java | 2 +- .../plugin/iceberg/IcebergSplitSource.java | 5 ++++- .../iceberg/BaseIcebergConnectorTest.java | 19 +++++++++++++++++++ 5 files changed, 44 insertions(+), 8 deletions(-) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/ExpressionConverter.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/ExpressionConverter.java index ceeeeb7b8e03..eb647f10fe3d 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/ExpressionConverter.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/ExpressionConverter.java @@ -32,8 +32,10 @@ import java.util.function.BiFunction; import static com.google.common.base.Preconditions.checkArgument; +import static io.trino.plugin.hive.util.HiveUtil.isStructuralType; import static io.trino.plugin.iceberg.IcebergMetadataColumn.isMetadataColumnId; import static io.trino.plugin.iceberg.IcebergTypes.convertTrinoValueToIceberg; +import static io.trino.spi.type.UuidType.UUID; import static java.lang.String.format; import static org.apache.iceberg.expressions.Expressions.alwaysFalse; import static org.apache.iceberg.expressions.Expressions.alwaysTrue; @@ -50,6 +52,21 @@ public final class ExpressionConverter { private ExpressionConverter() {} + public static boolean isConvertableToIcebergExpression(Domain domain) + { + if (isStructuralType(domain.getType())) { + // structural types cannot be used to filter a table scan in Iceberg library. + return false; + } + + if (domain.getType() == UUID) { + // Iceberg orders UUID values differently than Trino (perhaps due to https://bugs.openjdk.org/browse/JDK-7025832), so allow only IS NULL / IS NOT NULL checks + return domain.isOnlyNull() || domain.getValues().isAll(); + } + + return true; + } + public static Expression toIcebergExpression(TupleDomain tupleDomain) { if (tupleDomain.isAll()) { @@ -64,6 +81,7 @@ public static Expression toIcebergExpression(TupleDomain tu IcebergColumnHandle columnHandle = entry.getKey(); checkArgument(!isMetadataColumnId(columnHandle.getId()), "Constraint on an unexpected column %s", columnHandle); Domain domain = entry.getValue(); + checkArgument(isConvertableToIcebergExpression(domain), "Unexpected not convertable domain on column %s: %s", columnHandle, domain); conjuncts.add(toIcebergExpression(columnHandle.getQualifiedName(), columnHandle.getType(), domain)); } return and(conjuncts); diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java index 52a3f67e5030..7c0f0d3a28fa 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java @@ -198,8 +198,8 @@ import static io.trino.plugin.base.projection.ApplyProjectionUtil.extractSupportedProjectedColumns; import static io.trino.plugin.base.projection.ApplyProjectionUtil.replaceWithNewVariables; import static io.trino.plugin.base.util.Procedures.checkProcedureArgument; -import static io.trino.plugin.hive.util.HiveUtil.isStructuralType; import static io.trino.plugin.iceberg.ConstraintExtractor.extractTupleDomain; +import static io.trino.plugin.iceberg.ExpressionConverter.isConvertableToIcebergExpression; import static io.trino.plugin.iceberg.ExpressionConverter.toIcebergExpression; import static io.trino.plugin.iceberg.IcebergAnalyzeProperties.getColumnNames; import static io.trino.plugin.iceberg.IcebergColumnHandle.TRINO_MERGE_PARTITION_DATA; @@ -277,7 +277,6 @@ import static io.trino.spi.type.TimestampType.TIMESTAMP_MICROS; import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MICROS; import static io.trino.spi.type.Timestamps.MICROSECONDS_PER_MILLISECOND; -import static io.trino.spi.type.UuidType.UUID; import static java.lang.Math.floorDiv; import static java.lang.String.format; import static java.util.Locale.ENGLISH; @@ -2512,10 +2511,7 @@ public Optional> applyFilter(C Map newUnenforced = new LinkedHashMap<>(); Map domains = predicate.getDomains().orElseThrow(() -> new VerifyException("No domains")); domains.forEach((columnHandle, domain) -> { - // structural types cannot be used to filter a table scan in Iceberg library. - if (isStructuralType(columnHandle.getType()) || - // Iceberg orders UUID values differently than Trino (perhaps due to https://bugs.openjdk.org/browse/JDK-7025832), so allow only IS NULL / IS NOT NULL checks - (columnHandle.getType() == UUID && !(domain.isOnlyNull() || domain.getValues().isAll()))) { + if (!isConvertableToIcebergExpression(domain)) { unsupported.put(columnHandle, domain); } else if (canEnforceColumnConstraintInSpecs(typeManager.getTypeOperators(), icebergTable, partitionSpecIds, columnHandle, domain)) { diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergSessionProperties.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergSessionProperties.java index b79dbd43596f..42868452dc52 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergSessionProperties.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergSessionProperties.java @@ -79,7 +79,7 @@ public final class IcebergSessionProperties private static final String PARQUET_WRITER_BLOCK_SIZE = "parquet_writer_block_size"; private static final String PARQUET_WRITER_PAGE_SIZE = "parquet_writer_page_size"; private static final String PARQUET_WRITER_BATCH_SIZE = "parquet_writer_batch_size"; - private static final String DYNAMIC_FILTERING_WAIT_TIMEOUT = "dynamic_filtering_wait_timeout"; + public static final String DYNAMIC_FILTERING_WAIT_TIMEOUT = "dynamic_filtering_wait_timeout"; private static final String STATISTICS_ENABLED = "statistics_enabled"; public static final String EXTENDED_STATISTICS_ENABLED = "extended_statistics_enabled"; private static final String PROJECTION_PUSHDOWN_ENABLED = "projection_pushdown_enabled"; diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergSplitSource.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergSplitSource.java index 8d65fa52748e..b18feb4c6676 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergSplitSource.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergSplitSource.java @@ -72,6 +72,7 @@ import static com.google.common.collect.Sets.intersection; import static com.google.common.math.LongMath.saturatedAdd; import static io.airlift.slice.Slices.utf8Slice; +import static io.trino.plugin.iceberg.ExpressionConverter.isConvertableToIcebergExpression; import static io.trino.plugin.iceberg.ExpressionConverter.toIcebergExpression; import static io.trino.plugin.iceberg.IcebergColumnHandle.fileModifiedTimeColumnHandle; import static io.trino.plugin.iceberg.IcebergColumnHandle.pathColumnHandle; @@ -182,7 +183,9 @@ public CompletableFuture getNextBatch(int maxSize) if (fileScanIterable == null) { // Used to avoid duplicating work if the Dynamic Filter was already pushed down to the Iceberg API boolean dynamicFilterIsComplete = dynamicFilter.isComplete(); - this.pushedDownDynamicFilterPredicate = dynamicFilter.getCurrentPredicate().transformKeys(IcebergColumnHandle.class::cast); + this.pushedDownDynamicFilterPredicate = dynamicFilter.getCurrentPredicate() + .transformKeys(IcebergColumnHandle.class::cast) + .filter((columnHandle, domain) -> isConvertableToIcebergExpression(domain)); TupleDomain fullPredicate = tableHandle.getUnenforcedPredicate() .intersect(pushedDownDynamicFilterPredicate); // TODO: (https://github.com/trinodb/trino/issues/9743): Consider removing TupleDomain#simplify diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java index 90d403d2b9bc..8ccf374868a9 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java @@ -112,6 +112,7 @@ import static io.trino.plugin.iceberg.IcebergFileFormat.PARQUET; import static io.trino.plugin.iceberg.IcebergQueryRunner.ICEBERG_CATALOG; import static io.trino.plugin.iceberg.IcebergSessionProperties.COLLECT_EXTENDED_STATISTICS_ON_WRITE; +import static io.trino.plugin.iceberg.IcebergSessionProperties.DYNAMIC_FILTERING_WAIT_TIMEOUT; import static io.trino.plugin.iceberg.IcebergSessionProperties.EXTENDED_STATISTICS_ENABLED; import static io.trino.plugin.iceberg.IcebergSplitManager.ICEBERG_DOMAIN_COMPACTION_THRESHOLD; import static io.trino.plugin.iceberg.IcebergTestUtils.getFileSystemFactory; @@ -7298,6 +7299,24 @@ private static Session withPartitionFilterRequired(Session session) .build(); } + @Test + public void testUuidDynamicFilter() + { + String catalog = getSession().getCatalog().orElseThrow(); + try (TestTable dataTable = new TestTable(getQueryRunner()::execute, "data_table", "(value uuid)"); + TestTable filteringTable = new TestTable(getQueryRunner()::execute, "filtering_table", "(filtering_value uuid)")) { + assertUpdate("INSERT INTO " + dataTable.getName() + " VALUES UUID 'f73894f0-5447-41c5-a727-436d04c7f8ab', UUID '4f676658-67c9-4e80-83be-ec75f0b9d0c9'", 2); + assertUpdate("INSERT INTO " + filteringTable.getName() + " VALUES UUID 'f73894f0-5447-41c5-a727-436d04c7f8ab'", 1); + + assertThat(query( + Session.builder(getSession()) + .setCatalogSessionProperty(catalog, DYNAMIC_FILTERING_WAIT_TIMEOUT, "10s") + .build(), + "SELECT value FROM " + dataTable.getName() + " WHERE EXISTS (SELECT 1 FROM " + filteringTable.getName() + " WHERE filtering_value = value)")) + .matches("VALUES UUID 'f73894f0-5447-41c5-a727-436d04c7f8ab'"); + } + } + @Override protected void verifyTableNameLengthFailurePermissible(Throwable e) { From 6aef3d0808e9426212880494bc5b37d2f88a9a89 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 25 Oct 2023 18:12:30 +0200 Subject: [PATCH 134/587] Fix grammar in a comment --- .../test/java/io/trino/plugin/deltalake/TestSplitPruning.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestSplitPruning.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestSplitPruning.java index 38cc4ed9cefb..5c23d74303f5 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestSplitPruning.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestSplitPruning.java @@ -403,7 +403,7 @@ public void testStructStatisticsPruningUppercaseColumn() } /** - * Test that a {@code SELECT count(*)} query returns the expected result an splits. + * Test that a {@code SELECT count(*)} query returns the expected result splits. */ private void testCountQuery(@Language("SQL") String sql, long expectedRowCount, long expectedSplitCount) { From 5cebbf9274ed142f93d3e980673dcb3aa0ddc096 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Tue, 31 Oct 2023 20:36:43 +0100 Subject: [PATCH 135/587] Remove duplicated property --- pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3379e05b968d..94581201ef87 100644 --- a/pom.xml +++ b/pom.xml @@ -153,7 +153,6 @@ 1.10.2 2.7.7-1 - 4.13.0 237 14.0.0 1.11.3 From ab1e89b4aa07077f2c8e7ca61719a1034457fb20 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Tue, 31 Oct 2023 20:47:58 +0100 Subject: [PATCH 136/587] Sort version properties --- pom.xml | 85 +++++++++++++++++++++++++++------------------------------ 1 file changed, 40 insertions(+), 45 deletions(-) diff --git a/pom.xml b/pom.xml index 94581201ef87..4d4feb5ffc2f 100644 --- a/pom.xml +++ b/pom.xml @@ -139,55 +139,14 @@ - ${project.basedir} + 17 true true true - - 17 17.0.5 - 8 - - clean verify -DskipTests - - 1.10.2 - 2.7.7-1 - 237 - 14.0.0 - 1.11.3 - ${dep.airlift.version} - 2.1.1 - 1.12.560 - 2.21.4 - 0.12.3 - 21.9.0.0 - 1.21 - 201 - 2.2.17 - 1.6.12 - 1.9.10 - 1.43.3 - 2.23.0 - 1.19.1 - 1.0.8 - 7.4.1 - 3.6.0 - 4.17.0 - 8.5.6 - 1.4.1 - 3.24.4 - 4.5.0 - 4.1.100.Final - 5.13.0 - 3.6.0 - 9.22.3 - 1.13.1 - 4.13.1 - 9.6 - - 87 - + -missing + ${project.basedir} + 8 America/Bahia_Banderas methods 2 @@ -210,7 +170,42 @@ -XX:GCLockerRetryAllocationCount=10 - -missing + + 2.7.7-1 + 1.10.2 + 237 + 4.13.1 + 14.0.0 + 9.6 + 1.11.3 + 2.21.4 + 1.12.560 + 4.17.0 + 7.4.1 + 87 + 1.21 + 1.0.8 + 2.23.0 + 9.22.3 + 2.2.17 + 1.43.3 + 1.4.1 + 5.13.0 + 0.12.3 + 3.6.0 + 1.9.10 + 8.5.6 + 4.1.100.Final + 3.6.0 + 21.9.0.0 + ${dep.airlift.version} + 1.13.1 + 3.24.4 + 1.6.12 + 2.1.1 + 201 + 1.19.1 + 4.5.0 From 971513272a4a6bee47401b7a84aa2a4b8cfd6fa3 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Tue, 31 Oct 2023 17:40:22 +0100 Subject: [PATCH 137/587] Update azure-sdk-bom to 1.2.18 and pin tcnative-version to 2.0.62.Final --- pom.xml | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 4d4feb5ffc2f..4342b2473da4 100644 --- a/pom.xml +++ b/pom.xml @@ -203,6 +203,7 @@ 3.24.4 1.6.12 2.1.1 + 2.0.62.Final 201 1.19.1 4.5.0 @@ -214,7 +215,7 @@ com.azure azure-sdk-bom - 1.2.17 + 1.2.18 pom import @@ -831,10 +832,85 @@ ${dep.minio.version} + + io.netty + netty-tcnative + ${dep.tcnative.version} + linux-x86_64 + + + + io.netty + netty-tcnative + ${dep.tcnative.version} + linux-x86_64-fedora + + + + io.netty + netty-tcnative + ${dep.tcnative.version} + linux-aarch_64-fedora + + + + io.netty + netty-tcnative + ${dep.tcnative.version} + osx-x86_64 + + + + io.netty + netty-tcnative-boringssl-static + ${dep.tcnative.version} + + + + io.netty + netty-tcnative-boringssl-static + ${dep.tcnative.version} + linux-x86_64 + + + + io.netty + netty-tcnative-boringssl-static + ${dep.tcnative.version} + linux-aarch_64 + + + + io.netty + netty-tcnative-boringssl-static + ${dep.tcnative.version} + osx-x86_64 + + + + io.netty + netty-tcnative-boringssl-static + ${dep.tcnative.version} + osx-aarch_64 + + + + io.netty + netty-tcnative-boringssl-static + ${dep.tcnative.version} + windows-x86_64 + + + + io.netty + netty-tcnative-classes + ${dep.tcnative.version} + + io.projectreactor reactor-core - 3.4.31 + 3.4.33 From 67ba0ffd6332bc88e79672d12a1f86b38565c862 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Tue, 31 Oct 2023 17:49:50 +0100 Subject: [PATCH 138/587] Update AWS SDK v1 to 1.12.581 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4342b2473da4..345f51059f91 100644 --- a/pom.xml +++ b/pom.xml @@ -179,7 +179,7 @@ 9.6 1.11.3 2.21.4 - 1.12.560 + 1.12.581 4.17.0 7.4.1 87 From eb67882628dda5618b3e8ce5a9664f997e6d3a7d Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Tue, 31 Oct 2023 17:50:40 +0100 Subject: [PATCH 139/587] Update AWS SDK v2 to 2.21.15 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 345f51059f91..5cea82c39c5d 100644 --- a/pom.xml +++ b/pom.xml @@ -178,7 +178,7 @@ 14.0.0 9.6 1.11.3 - 2.21.4 + 2.21.15 1.12.581 4.17.0 7.4.1 From b0a664c064a81ee61e14cace08aed57ef4b55b64 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Tue, 31 Oct 2023 17:51:53 +0100 Subject: [PATCH 140/587] Update flyway to 10.0.0 --- plugin/trino-resource-group-managers/pom.xml | 6 ++++++ pom.xml | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/plugin/trino-resource-group-managers/pom.xml b/plugin/trino-resource-group-managers/pom.xml index 4edc41a0f494..c81e30caceee 100644 --- a/plugin/trino-resource-group-managers/pom.xml +++ b/plugin/trino-resource-group-managers/pom.xml @@ -169,6 +169,12 @@ runtime + + org.flywaydb + flyway-database-postgresql + runtime + + org.flywaydb flyway-mysql diff --git a/pom.xml b/pom.xml index 5cea82c39c5d..b9679df6a1b8 100644 --- a/pom.xml +++ b/pom.xml @@ -186,7 +186,7 @@ 1.21 1.0.8 2.23.0 - 9.22.3 + 10.0.0 2.2.17 1.43.3 1.4.1 @@ -1958,6 +1958,12 @@ ${dep.flyway.version} + + org.flywaydb + flyway-database-postgresql + ${dep.flyway.version} + + org.flywaydb flyway-mysql From c419f668dd6ab266f5e5e9733481b02439cbe727 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Tue, 31 Oct 2023 17:53:27 +0100 Subject: [PATCH 141/587] Update kotlin to 1.9.20 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b9679df6a1b8..700b5a92cb75 100644 --- a/pom.xml +++ b/pom.xml @@ -193,7 +193,7 @@ 5.13.0 0.12.3 3.6.0 - 1.9.10 + 1.9.20 8.5.6 4.1.100.Final 3.6.0 From a76e41041fd73d14b409a5f7e285a284a4661556 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Tue, 31 Oct 2023 18:14:27 +0100 Subject: [PATCH 142/587] Update google libraries-bom to 26.26.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 700b5a92cb75..3f9dea5902e1 100644 --- a/pom.xml +++ b/pom.xml @@ -223,7 +223,7 @@ com.google.cloud libraries-bom - 26.25.0 + 26.26.0 pom import From c3606129878eba2c1d96fb0b451ccb9a237252cc Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Tue, 31 Oct 2023 18:24:34 +0100 Subject: [PATCH 143/587] Update oauth2-oidc-sdk to 11.6 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3f9dea5902e1..22da5be9c0ef 100644 --- a/pom.xml +++ b/pom.xml @@ -572,7 +572,7 @@ com.nimbusds oauth2-oidc-sdk - 11.4 + 11.6 jdk11 From 9ec47115e08761d099cacd122c8a4a8cc1720046 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Tue, 31 Oct 2023 18:25:41 +0100 Subject: [PATCH 144/587] Update gax to 2.36.0 It requires updating google auth libraries to 1.20.0 --- plugin/trino-exchange-filesystem/pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin/trino-exchange-filesystem/pom.xml b/plugin/trino-exchange-filesystem/pom.xml index f6f4edd20507..69aff43eb7f3 100644 --- a/plugin/trino-exchange-filesystem/pom.xml +++ b/plugin/trino-exchange-filesystem/pom.xml @@ -67,7 +67,7 @@ com.google.api gax - 2.34.1 + 2.36.0 com.google.protobuf @@ -91,13 +91,13 @@ com.google.auth google-auth-library-credentials - 1.19.0 + 1.20.0 com.google.auth google-auth-library-oauth2-http - 1.19.0 + 1.20.0 commons-logging From c3a4df636371ac0a759b5ff7ba727c114b7f107a Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Tue, 31 Oct 2023 18:32:32 +0100 Subject: [PATCH 145/587] Update redshift-jdbc42 to 2.1.0.21 --- plugin/trino-redshift/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/trino-redshift/pom.xml b/plugin/trino-redshift/pom.xml index ca0f7fcccef8..8d276806cd07 100644 --- a/plugin/trino-redshift/pom.xml +++ b/plugin/trino-redshift/pom.xml @@ -21,7 +21,7 @@ com.amazon.redshift redshift-jdbc42 - 2.1.0.20 + 2.1.0.21 From 919c0acb5465155b9e33f41cad82af7413d15e8f Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Tue, 31 Oct 2023 18:34:14 +0100 Subject: [PATCH 146/587] Update HBase to 2.2.7 --- plugin/trino-phoenix5/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/trino-phoenix5/pom.xml b/plugin/trino-phoenix5/pom.xml index f2ec3257f3b6..db987d630a15 100644 --- a/plugin/trino-phoenix5/pom.xml +++ b/plugin/trino-phoenix5/pom.xml @@ -15,7 +15,7 @@ ${project.parent.basedir} - 2.2.6 + 2.2.7 --add-opens=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/java.nio=ALL-UNNAMED From 97283224fb370d2d73c3075c4f80d6255a0dfd03 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Wed, 1 Nov 2023 11:50:39 +0100 Subject: [PATCH 147/587] Inline dep.aws-sdk-v2.version property --- pom.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 22da5be9c0ef..3917dd821435 100644 --- a/pom.xml +++ b/pom.xml @@ -178,7 +178,6 @@ 14.0.0 9.6 1.11.3 - 2.21.15 1.12.581 4.17.0 7.4.1 @@ -287,7 +286,7 @@ software.amazon.awssdk bom - ${dep.aws-sdk-v2.version} + 2.21.15 pom import From e44196fecf28d44dc2422e60093b2862920cfd1c Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Wed, 1 Nov 2023 11:52:20 +0100 Subject: [PATCH 148/587] Inline dep.asm.version property --- pom.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 3917dd821435..58379d7c4b41 100644 --- a/pom.xml +++ b/pom.xml @@ -176,7 +176,6 @@ 237 4.13.1 14.0.0 - 9.6 1.11.3 1.12.581 4.17.0 @@ -270,7 +269,7 @@ org.ow2.asm asm-bom - ${dep.asm.version} + 9.6 pom import From 50edd737f63edae25f000fd18d7bd656a738cd57 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Wed, 1 Nov 2023 11:53:50 +0100 Subject: [PATCH 149/587] Inline dep.gcs.version property --- pom.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 58379d7c4b41..41ff6edbb59a 100644 --- a/pom.xml +++ b/pom.xml @@ -185,7 +185,6 @@ 1.0.8 2.23.0 10.0.0 - 2.2.17 1.43.3 1.4.1 5.13.0 @@ -490,7 +489,7 @@ com.google.cloud.bigdataoss gcs-connector - hadoop3-${dep.gcs.version} + hadoop3-2.2.17 shaded From 86350e70883989ceb8f1997b6d207a397ec10c9f Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Wed, 1 Nov 2023 11:55:15 +0100 Subject: [PATCH 150/587] Inline dep.minio.version property --- pom.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 41ff6edbb59a..b826e85c2fcb 100644 --- a/pom.xml +++ b/pom.xml @@ -191,7 +191,6 @@ 0.12.3 3.6.0 1.9.20 - 8.5.6 4.1.100.Final 3.6.0 21.9.0.0 @@ -826,7 +825,7 @@ io.minio minio - ${dep.minio.version} + 8.5.6 From 99bd6b5382061f29b49732c221197679e967c524 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Wed, 1 Nov 2023 11:57:16 +0100 Subject: [PATCH 151/587] Inline dep.netty.version property --- pom.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index b826e85c2fcb..4aea193a4d72 100644 --- a/pom.xml +++ b/pom.xml @@ -191,7 +191,6 @@ 0.12.3 3.6.0 1.9.20 - 4.1.100.Final 3.6.0 21.9.0.0 ${dep.airlift.version} @@ -251,7 +250,7 @@ io.netty netty-bom - ${dep.netty.version} + 4.1.100.Final pom import From 1850c886643a2f4a0429e0592ae88c32a40a0fa3 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Wed, 1 Nov 2023 12:00:03 +0100 Subject: [PATCH 152/587] Inline dep.testcontainers.version property --- pom.xml | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/pom.xml b/pom.xml index 4aea193a4d72..b7cebac89246 100644 --- a/pom.xml +++ b/pom.xml @@ -200,7 +200,6 @@ 2.1.1 2.0.62.Final 201 - 1.19.1 4.5.0 @@ -274,7 +273,7 @@ org.testcontainers testcontainers-bom - ${dep.testcontainers.version} + 1.19.1 pom import @@ -2038,18 +2037,6 @@ 1.13.1 - - org.testcontainers - testcontainers - ${dep.testcontainers.version} - - - org.osgi - org.osgi.core - - - - org.xerial.snappy snappy-java From 09e54e59b77e0ed8419006d423ce7acd5cc291ac Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Sun, 5 Nov 2023 09:39:36 +0100 Subject: [PATCH 153/587] Update zstd-jni to 1.5.5-10 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b7cebac89246..3216d8963ca1 100644 --- a/pom.xml +++ b/pom.xml @@ -468,7 +468,7 @@ com.github.luben zstd-jni - 1.5.5-6 + 1.5.5-10 From 9ac0661934c40df70fd42065ed9f9220de1f30f3 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Sun, 5 Nov 2023 09:40:01 +0100 Subject: [PATCH 154/587] Update oshi-core to 6.4.7 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3216d8963ca1..5808dc76e74a 100644 --- a/pom.xml +++ b/pom.xml @@ -474,7 +474,7 @@ com.github.oshi oshi-core - 6.4.6 + 6.4.7 From 93bdff94af1619d6c065c48b498efe04b0d49d8d Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Sun, 5 Nov 2023 09:50:46 +0100 Subject: [PATCH 155/587] Update GCS connector to 2.2.18 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5808dc76e74a..096f3df48049 100644 --- a/pom.xml +++ b/pom.xml @@ -486,7 +486,7 @@ com.google.cloud.bigdataoss gcs-connector - hadoop3-2.2.17 + hadoop3-2.2.18 shaded From 7726a5a0d92310d0f2d1e3a0adaf7d56c3e822f5 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Sun, 5 Nov 2023 09:51:12 +0100 Subject: [PATCH 156/587] Update protobuf to 3.25.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 096f3df48049..79c64b89e03a 100644 --- a/pom.xml +++ b/pom.xml @@ -195,7 +195,7 @@ 21.9.0.0 ${dep.airlift.version} 1.13.1 - 3.24.4 + 3.25.0 1.6.12 2.1.1 2.0.62.Final From 0bc4ba1d54121c7ec9a9f216e066e9fcb8f6a865 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Sun, 5 Nov 2023 09:52:24 +0100 Subject: [PATCH 157/587] Update mssql-jdbc to 12.4.2.jre11 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 79c64b89e03a..42722f504e35 100644 --- a/pom.xml +++ b/pom.xml @@ -548,7 +548,7 @@ com.microsoft.sqlserver mssql-jdbc - 12.4.1.jre11 + 12.4.2.jre11 From 2d8f018536b92c3fcdffd903740a2a6adfba94d6 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Sun, 5 Nov 2023 09:52:48 +0100 Subject: [PATCH 158/587] Update mysql-connector-j to 8.2.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 42722f504e35..d16eb0415dda 100644 --- a/pom.xml +++ b/pom.xml @@ -554,7 +554,7 @@ com.mysql mysql-connector-j - 8.1.0 + 8.2.0 From a5377f00e843f69877dd557f50f45d898a44cd53 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Sun, 5 Nov 2023 09:54:19 +0100 Subject: [PATCH 159/587] Update iceberg to 1.4.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d16eb0415dda..982d53e02b85 100644 --- a/pom.xml +++ b/pom.xml @@ -186,7 +186,7 @@ 2.23.0 10.0.0 1.43.3 - 1.4.1 + 1.4.2 5.13.0 0.12.3 3.6.0 From 4e94b2d74d570cb7fe7d7e252166f3d99ff41a4f Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Sun, 5 Nov 2023 09:54:48 +0100 Subject: [PATCH 160/587] Update checker-qual to 3.40.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 982d53e02b85..653c8a9beaaf 100644 --- a/pom.xml +++ b/pom.xml @@ -1925,7 +1925,7 @@ org.checkerframework checker-qual - 3.39.0 + 3.40.0 From 145aedf414880ffe0de80aac7df474094f572d2d Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Thu, 9 Nov 2023 12:58:14 +0100 Subject: [PATCH 161/587] Update nimbus-jose-jwt to 9.37.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 653c8a9beaaf..9d8f0507b8ef 100644 --- a/pom.xml +++ b/pom.xml @@ -560,7 +560,7 @@ com.nimbusds nimbus-jose-jwt - 9.37 + 9.37.1 From 5144bb5959f3f0d2a72c084a04a197d9b8970b68 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Thu, 9 Nov 2023 13:02:06 +0100 Subject: [PATCH 162/587] Update mariadb-java-client to 3.3.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9d8f0507b8ef..ff0ad0a334cb 100644 --- a/pom.xml +++ b/pom.xml @@ -1998,7 +1998,7 @@ org.mariadb.jdbc mariadb-java-client - 3.2.0 + 3.3.0 From dfcc7e0bf0c875ada0b2ff692a45f109caad4602 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Thu, 9 Nov 2023 13:03:26 +0100 Subject: [PATCH 163/587] Update AWS SDK v2 to 2.21.19 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ff0ad0a334cb..a5077f11d430 100644 --- a/pom.xml +++ b/pom.xml @@ -281,7 +281,7 @@ software.amazon.awssdk bom - 2.21.15 + 2.21.19 pom import From 48ad3f26f821b19a0b5f2669a94949ad4296c801 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Thu, 9 Nov 2023 13:04:31 +0100 Subject: [PATCH 164/587] Update mongo client to 4.11.1 --- plugin/trino-mongodb/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/trino-mongodb/pom.xml b/plugin/trino-mongodb/pom.xml index 85f3997fb054..8b392f7297b7 100644 --- a/plugin/trino-mongodb/pom.xml +++ b/plugin/trino-mongodb/pom.xml @@ -14,7 +14,7 @@ ${project.parent.basedir} - 4.11.0 + 4.11.1 From 57b34c15af54fba86f77c03aa395271c86d9a14a Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Thu, 9 Nov 2023 13:29:59 +0100 Subject: [PATCH 165/587] Update jetty to 11.0.18 --- pom.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pom.xml b/pom.xml index a5077f11d430..5d28b064cf00 100644 --- a/pom.xml +++ b/pom.xml @@ -254,6 +254,14 @@ import + + org.eclipse.jetty + jetty-bom + 11.0.18 + pom + import + + org.jdbi jdbi3-bom From 5d167d4570e99bb052f9130efc07854c047c91d6 Mon Sep 17 00:00:00 2001 From: Manfred Moser Date: Thu, 9 Nov 2023 09:32:17 -0800 Subject: [PATCH 166/587] Add docs for final RETURN in routine - Fix relevant example --- docs/src/main/sphinx/routines/case.md | 1 + docs/src/main/sphinx/routines/function.md | 4 +++- docs/src/main/sphinx/routines/return.md | 4 ++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/src/main/sphinx/routines/case.md b/docs/src/main/sphinx/routines/case.md index 36251cc9b7e5..926c1df925f3 100644 --- a/docs/src/main/sphinx/routines/case.md +++ b/docs/src/main/sphinx/routines/case.md @@ -49,6 +49,7 @@ FUNCTION simple_case(a bigint) WHEN 1 THEN RETURN 'one'; ELSE RETURN 'more than one or negative'; END CASE; + RETURN NULL; END ``` diff --git a/docs/src/main/sphinx/routines/function.md b/docs/src/main/sphinx/routines/function.md index 65c4448c4eb4..151578437c0b 100644 --- a/docs/src/main/sphinx/routines/function.md +++ b/docs/src/main/sphinx/routines/function.md @@ -60,7 +60,9 @@ function to other users as `description`. The information is accessible with [](/sql/show-functions). The body of the routine can either be a simple single `RETURN` statement with an -expression, or compound list of `statements` in a `BEGIN` block. +expression, or compound list of `statements` in a `BEGIN` block. Routines must +contain a `RETURN` statement at the end of the top-level block, even if it's +unreachable. ## Examples diff --git a/docs/src/main/sphinx/routines/return.md b/docs/src/main/sphinx/routines/return.md index 18db948e112e..cc8bfe483989 100644 --- a/docs/src/main/sphinx/routines/return.md +++ b/docs/src/main/sphinx/routines/return.md @@ -27,6 +27,10 @@ Further examples of varying complexity that cover usage of the `RETURN` statement in combination with other statements are available in the [SQL routines examples documentation](/routines/examples). +All routines must contain a `RETURN` statement at the end of the top-level +block in the `FUNCTION` declaration, even if it's unreachable. + ## See also * [](/routines/introduction) +* [](/routines/function) From cb6003cabeada5bfebb889a4136a42c23d313cac Mon Sep 17 00:00:00 2001 From: Raunaq Morarka Date: Fri, 3 Nov 2023 14:33:50 +0530 Subject: [PATCH 167/587] Make MockSplitSource thread-safe --- .../java/io/trino/split/MockSplitSource.java | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/core/trino-main/src/test/java/io/trino/split/MockSplitSource.java b/core/trino-main/src/test/java/io/trino/split/MockSplitSource.java index c5cdd6e46251..94078c43ce32 100644 --- a/core/trino-main/src/test/java/io/trino/split/MockSplitSource.java +++ b/core/trino-main/src/test/java/io/trino/split/MockSplitSource.java @@ -17,7 +17,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; -import io.trino.annotation.NotThreadSafe; +import com.google.errorprone.annotations.ThreadSafe; import io.trino.metadata.Split; import io.trino.spi.connector.CatalogHandle; import io.trino.spi.connector.ConnectorSplit; @@ -33,7 +33,7 @@ import static io.trino.split.MockSplitSource.Action.FINISH; import static io.trino.testing.TestingHandles.TEST_CATALOG_HANDLE; -@NotThreadSafe +@ThreadSafe public class MockSplitSource implements SplitSource { @@ -58,14 +58,14 @@ public MockSplitSource() { } - public MockSplitSource setBatchSize(int batchSize) + public synchronized MockSplitSource setBatchSize(int batchSize) { checkArgument(atSplitDepletion == DO_NOTHING, "cannot modify batch size once split completion action is set"); this.batchSize = batchSize; return this; } - public MockSplitSource increaseAvailableSplits(int count) + public synchronized MockSplitSource increaseAvailableSplits(int count) { checkArgument(atSplitDepletion == DO_NOTHING, "cannot increase available splits once split completion action is set"); totalSplits += count; @@ -73,7 +73,7 @@ public MockSplitSource increaseAvailableSplits(int count) return this; } - public MockSplitSource atSplitCompletion(Action action) + public synchronized MockSplitSource atSplitCompletion(Action action) { atSplitDepletion = action; doGetNextBatch(); @@ -86,9 +86,13 @@ public CatalogHandle getCatalogHandle() throw new UnsupportedOperationException(); } - private void doGetNextBatch() + private synchronized void doGetNextBatch() { checkState(splitsProduced <= totalSplits); + if (nextBatchFuture.isDone()) { + // if nextBatchFuture is already done, we need to wait until new future is created through getNextBatch to produce splits + return; + } if (splitsProduced == totalSplits) { switch (atSplitDepletion) { case FAIL: @@ -111,7 +115,7 @@ private void doGetNextBatch() } @Override - public ListenableFuture getNextBatch(int maxSize) + public synchronized ListenableFuture getNextBatch(int maxSize) { checkState(nextBatchFuture.isDone(), "concurrent getNextBatch invocation"); nextBatchFuture = SettableFuture.create(); @@ -128,7 +132,7 @@ public void close() } @Override - public boolean isFinished() + public synchronized boolean isFinished() { return splitsProduced == totalSplits && atSplitDepletion == FINISH; } @@ -139,7 +143,7 @@ public Optional> getTableExecuteSplitsInfo() return Optional.empty(); } - public int getNextBatchInvocationCount() + public synchronized int getNextBatchInvocationCount() { return nextBatchInvocationCount; } From bebc42fa6a3cad3a6eb599b768edf2707f145813 Mon Sep 17 00:00:00 2001 From: Raunaq Morarka Date: Thu, 12 Oct 2023 10:59:47 +0530 Subject: [PATCH 168/587] Fix possible StackOverflowError in BufferingSplitSource Avoid chaning futures on a directExecutor by submitting requests for fetching splits to a separte thread pool instead --- .../trino/execution/QueryManagerConfig.java | 15 +++ .../io/trino/split/BufferingSplitSource.java | 111 ++++++++++++++---- .../java/io/trino/split/SplitManager.java | 8 +- .../execution/TestQueryManagerConfig.java | 3 + .../trino/split/TestBufferingSplitSource.java | 78 +++++++----- 5 files changed, 159 insertions(+), 56 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/execution/QueryManagerConfig.java b/core/trino-main/src/main/java/io/trino/execution/QueryManagerConfig.java index f977a365ff89..c48c58c58246 100644 --- a/core/trino-main/src/main/java/io/trino/execution/QueryManagerConfig.java +++ b/core/trino-main/src/main/java/io/trino/execution/QueryManagerConfig.java @@ -77,6 +77,7 @@ public class QueryManagerConfig private int queryManagerExecutorPoolSize = 5; private int queryExecutorPoolSize = 1000; private int maxStateMachineCallbackThreads = 5; + private int maxSplitManagerCallbackThreads = 100; /** * default value is overwritten for fault tolerant execution in {@link #applyFaultTolerantExecutionDefaults()} @@ -394,6 +395,20 @@ public QueryManagerConfig setMaxStateMachineCallbackThreads(int maxStateMachineC return this; } + @Min(1) + public int getMaxSplitManagerCallbackThreads() + { + return maxSplitManagerCallbackThreads; + } + + @Config("query.max-split-manager-callback-threads") + @ConfigDescription("The maximum number of threads allowed to run splits generation callbacks concurrently") + public QueryManagerConfig setMaxSplitManagerCallbackThreads(int maxSplitManagerCallbackThreads) + { + this.maxSplitManagerCallbackThreads = maxSplitManagerCallbackThreads; + return this; + } + @NotNull @MinDuration("1s") public Duration getRemoteTaskMaxErrorDuration() diff --git a/core/trino-main/src/main/java/io/trino/split/BufferingSplitSource.java b/core/trino-main/src/main/java/io/trino/split/BufferingSplitSource.java index 084d6722fa99..da721ce70660 100644 --- a/core/trino-main/src/main/java/io/trino/split/BufferingSplitSource.java +++ b/core/trino-main/src/main/java/io/trino/split/BufferingSplitSource.java @@ -13,8 +13,11 @@ */ package io.trino.split; -import com.google.common.util.concurrent.Futures; +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.AbstractFuture; +import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.ListenableFuture; +import com.google.errorprone.annotations.concurrent.GuardedBy; import io.opentelemetry.context.Context; import io.trino.metadata.Split; import io.trino.spi.connector.CatalogHandle; @@ -22,9 +25,11 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.concurrent.Executor; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.util.concurrent.Futures.immediateVoidFuture; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.util.concurrent.Futures.addCallback; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static java.util.Objects.requireNonNull; @@ -33,10 +38,12 @@ public class BufferingSplitSource { private final int bufferSize; private final SplitSource source; + private final Executor executor; - public BufferingSplitSource(SplitSource source, int bufferSize) + public BufferingSplitSource(SplitSource source, Executor executor, int bufferSize) { this.source = requireNonNull(source, "source is null"); + this.executor = requireNonNull(executor, "executor is null"); this.bufferSize = bufferSize; } @@ -50,7 +57,7 @@ public CatalogHandle getCatalogHandle() public ListenableFuture getNextBatch(int maxSize) { checkArgument(maxSize > 0, "Cannot fetch a batch of zero size"); - return GetNextBatch.fetchNextBatchAsync(source, Math.min(bufferSize, maxSize), maxSize); + return GetNextBatch.fetchNextBatchAsync(source, executor, Math.min(bufferSize, maxSize), maxSize); } @Override @@ -72,50 +79,108 @@ public Optional> getTableExecuteSplitsInfo() } private static class GetNextBatch + extends AbstractFuture { private final Context context = Context.current(); private final SplitSource splitSource; + private final Executor executor; private final int min; private final int max; + @GuardedBy("this") private final List splits = new ArrayList<>(); - private boolean noMoreSplits; + @GuardedBy("this") + private ListenableFuture nextBatchFuture; public static ListenableFuture fetchNextBatchAsync( SplitSource splitSource, + Executor executor, int min, int max) { - GetNextBatch getNextBatch = new GetNextBatch(splitSource, min, max); - ListenableFuture future = getNextBatch.fetchSplits(); - return Futures.transform(future, ignored -> new SplitBatch(getNextBatch.splits, getNextBatch.noMoreSplits), directExecutor()); + GetNextBatch getNextBatch = new GetNextBatch(splitSource, executor, min, max); + getNextBatch.fetchSplits(); + return getNextBatch; } - private GetNextBatch(SplitSource splitSource, int min, int max) + private GetNextBatch(SplitSource splitSource, Executor executor, int min, int max) { this.splitSource = requireNonNull(splitSource, "splitSource is null"); + this.executor = requireNonNull(executor, "executor is null"); checkArgument(min <= max, "Min splits greater than max splits"); this.min = min; this.max = max; } - private ListenableFuture fetchSplits() + private synchronized void fetchSplits() { - if (splits.size() >= min) { - return immediateVoidFuture(); - } - ListenableFuture future; + checkState(nextBatchFuture == null || nextBatchFuture.isDone(), "nextBatchFuture is expected to be done"); + try (var ignored = context.makeCurrent()) { - future = splitSource.getNextBatch(max - splits.size()); - } - return Futures.transformAsync(future, splitBatch -> { - splits.addAll(splitBatch.getSplits()); - if (splitBatch.isLastBatch()) { - noMoreSplits = true; - return immediateVoidFuture(); + nextBatchFuture = splitSource.getNextBatch(max - splits.size()); + // If the split source returns completed futures, we process them on + // directExecutor without chaining to avoid the overhead of going through separate executor + while (nextBatchFuture.isDone()) { + addCallback( + nextBatchFuture, + new FutureCallback<>() + { + @Override + public void onSuccess(SplitBatch splitBatch) + { + processBatch(splitBatch); + } + + @Override + public void onFailure(Throwable throwable) + { + setException(throwable); + } + }, + directExecutor()); + if (isDone()) { + return; + } + nextBatchFuture = splitSource.getNextBatch(max - splits.size()); } - return fetchSplits(); - }, directExecutor()); + } + + addCallback( + nextBatchFuture, + new FutureCallback<>() + { + @Override + public void onSuccess(SplitBatch splitBatch) + { + synchronized (GetNextBatch.this) { + if (processBatch(splitBatch)) { + return; + } + fetchSplits(); + } + } + + @Override + public void onFailure(Throwable throwable) + { + setException(throwable); + } + }, + executor); + } + + // Accumulates splits from the returned batch and returns whether + // sufficient splits have been buffered to satisfy min batch size + private synchronized boolean processBatch(SplitBatch splitBatch) + { + splits.addAll(splitBatch.getSplits()); + boolean isLastBatch = splitBatch.isLastBatch(); + if (splits.size() >= min || isLastBatch) { + set(new SplitBatch(ImmutableList.copyOf(splits), isLastBatch)); + splits.clear(); + return true; + } + return false; } } } diff --git a/core/trino-main/src/main/java/io/trino/split/SplitManager.java b/core/trino-main/src/main/java/io/trino/split/SplitManager.java index b6bc7a9c1544..70b388235b2c 100644 --- a/core/trino-main/src/main/java/io/trino/split/SplitManager.java +++ b/core/trino-main/src/main/java/io/trino/split/SplitManager.java @@ -14,6 +14,7 @@ package io.trino.split; import com.google.inject.Inject; +import io.airlift.concurrent.BoundedExecutor; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Context; @@ -31,15 +32,19 @@ import io.trino.tracing.TrinoAttributes; import java.util.Optional; +import java.util.concurrent.Executor; +import static io.airlift.concurrent.Threads.daemonThreadsNamed; import static io.trino.SystemSessionProperties.isAllowPushdownIntoConnectors; import static java.util.Objects.requireNonNull; +import static java.util.concurrent.Executors.newCachedThreadPool; public class SplitManager { private final CatalogServiceProvider splitManagerProvider; private final Tracer tracer; private final int minScheduleSplitBatchSize; + private final Executor executor; @Inject public SplitManager(CatalogServiceProvider splitManagerProvider, Tracer tracer, QueryManagerConfig config) @@ -47,6 +52,7 @@ public SplitManager(CatalogServiceProvider splitManagerPr this.splitManagerProvider = requireNonNull(splitManagerProvider, "splitManagerProvider is null"); this.tracer = requireNonNull(tracer, "tracer is null"); this.minScheduleSplitBatchSize = config.getMinScheduleSplitBatchSize(); + this.executor = new BoundedExecutor(newCachedThreadPool(daemonThreadsNamed("splits-manager-callback-%s")), config.getMaxSplitManagerCallbackThreads()); } public SplitSource getSplits( @@ -77,7 +83,7 @@ public SplitSource getSplits( if (minScheduleSplitBatchSize > 1) { splitSource = new TracingSplitSource(splitSource, tracer, Optional.empty(), "split-batch"); - splitSource = new BufferingSplitSource(splitSource, minScheduleSplitBatchSize); + splitSource = new BufferingSplitSource(splitSource, executor, minScheduleSplitBatchSize); splitSource = new TracingSplitSource(splitSource, tracer, Optional.of(span), "split-buffer"); } else { diff --git a/core/trino-main/src/test/java/io/trino/execution/TestQueryManagerConfig.java b/core/trino-main/src/test/java/io/trino/execution/TestQueryManagerConfig.java index b0958cb8db6c..dc62cc9f36ee 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestQueryManagerConfig.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestQueryManagerConfig.java @@ -57,6 +57,7 @@ public void testDefaults() .setQueryManagerExecutorPoolSize(5) .setQueryExecutorPoolSize(1000) .setMaxStateMachineCallbackThreads(5) + .setMaxSplitManagerCallbackThreads(100) .setRemoteTaskMaxErrorDuration(new Duration(5, MINUTES)) .setRemoteTaskMaxCallbackThreads(1000) .setQueryExecutionPolicy("phased") @@ -132,6 +133,7 @@ public void testExplicitPropertyMappings() .put("query.manager-executor-pool-size", "11") .put("query.executor-pool-size", "111") .put("query.max-state-machine-callback-threads", "112") + .put("query.max-split-manager-callback-threads", "113") .put("query.remote-task.max-error-duration", "60s") .put("query.remote-task.max-callback-threads", "10") .put("query.execution-policy", "foo-bar-execution-policy") @@ -204,6 +206,7 @@ public void testExplicitPropertyMappings() .setQueryManagerExecutorPoolSize(11) .setQueryExecutorPoolSize(111) .setMaxStateMachineCallbackThreads(112) + .setMaxSplitManagerCallbackThreads(113) .setRemoteTaskMaxErrorDuration(new Duration(60, SECONDS)) .setRemoteTaskMaxCallbackThreads(10) .setQueryExecutionPolicy("foo-bar-execution-policy") diff --git a/core/trino-main/src/test/java/io/trino/split/TestBufferingSplitSource.java b/core/trino-main/src/test/java/io/trino/split/TestBufferingSplitSource.java index fc53a4ac1fbd..9356c5d4447e 100644 --- a/core/trino-main/src/test/java/io/trino/split/TestBufferingSplitSource.java +++ b/core/trino-main/src/test/java/io/trino/split/TestBufferingSplitSource.java @@ -15,16 +15,19 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import io.airlift.concurrent.BoundedExecutor; import io.trino.split.SplitSource.SplitBatch; import org.junit.jupiter.api.Test; -import java.util.concurrent.Future; +import java.util.concurrent.Executor; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; -import static io.airlift.concurrent.MoreFutures.tryGetFutureValue; +import static io.airlift.concurrent.MoreFutures.getFutureValue; +import static io.airlift.concurrent.Threads.daemonThreadsNamed; import static io.trino.split.MockSplitSource.Action.FAIL; import static io.trino.split.MockSplitSource.Action.FINISH; import static java.util.Objects.requireNonNull; +import static java.util.concurrent.Executors.newCachedThreadPool; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; @@ -32,6 +35,8 @@ public class TestBufferingSplitSource { + private static final Executor executor = new BoundedExecutor(newCachedThreadPool(daemonThreadsNamed(TestBufferingSplitSource.class.getSimpleName() + "-%s")), 10); + @Test public void testSlowSource() { @@ -39,14 +44,14 @@ public void testSlowSource() .setBatchSize(1) .increaseAvailableSplits(25) .atSplitCompletion(FINISH); - try (SplitSource source = new BufferingSplitSource(mockSource, 10)) { - requireFutureValue(getNextBatch(source, 20)) + try (SplitSource source = new BufferingSplitSource(mockSource, executor, 10)) { + getFutureValue(getNextBatch(source, 20)) .assertSize(10) .assertNoMoreSplits(false); - requireFutureValue(getNextBatch(source, 6)) + getFutureValue(getNextBatch(source, 6)) .assertSize(6) .assertNoMoreSplits(false); - requireFutureValue(getNextBatch(source, 20)) + getFutureValue(getNextBatch(source, 20)) .assertSize(9) .assertNoMoreSplits(true); assertTrue(source.isFinished()); @@ -61,11 +66,11 @@ public void testFastSource() .setBatchSize(11) .increaseAvailableSplits(22) .atSplitCompletion(FINISH); - try (SplitSource source = new BufferingSplitSource(mockSource, 10)) { - requireFutureValue(getNextBatch(source, 200)) + try (SplitSource source = new BufferingSplitSource(mockSource, executor, 10)) { + getFutureValue(getNextBatch(source, 200)) .assertSize(11) .assertNoMoreSplits(false); - requireFutureValue(getNextBatch(source, 200)) + getFutureValue(getNextBatch(source, 200)) .assertSize(11) .assertNoMoreSplits(true); assertTrue(source.isFinished()); @@ -73,14 +78,29 @@ public void testFastSource() } } + @Test + public void testNoStackOverFlow() + { + MockSplitSource mockSource = new MockSplitSource() + .setBatchSize(1) + .increaseAvailableSplits(10000) + .atSplitCompletion(FINISH); + try (SplitSource source = new BufferingSplitSource(mockSource, executor, Integer.MAX_VALUE)) { + while (!source.isFinished()) { + getFutureValue(getNextBatch(source, 1000)) + .assertSize(1000); + } + } + } + @Test public void testEmptySource() { MockSplitSource mockSource = new MockSplitSource() .setBatchSize(1) .atSplitCompletion(FINISH); - try (SplitSource source = new BufferingSplitSource(mockSource, 100)) { - requireFutureValue(getNextBatch(source, 200)) + try (SplitSource source = new BufferingSplitSource(mockSource, executor, 100)) { + getFutureValue(getNextBatch(source, 200)) .assertSize(0) .assertNoMoreSplits(true); assertTrue(source.isFinished()); @@ -93,14 +113,14 @@ public void testBlocked() { MockSplitSource mockSource = new MockSplitSource() .setBatchSize(1); - try (SplitSource source = new BufferingSplitSource(mockSource, 10)) { + try (SplitSource source = new BufferingSplitSource(mockSource, executor, 10)) { // Source has 0 out of 10 needed. ListenableFuture nextBatchFuture = getNextBatch(source, 10); assertFalse(nextBatchFuture.isDone()); mockSource.increaseAvailableSplits(9); assertFalse(nextBatchFuture.isDone()); mockSource.increaseAvailableSplits(1); - requireFutureValue(nextBatchFuture) + getFutureValue(nextBatchFuture) .assertSize(10) .assertNoMoreSplits(false); @@ -108,7 +128,7 @@ public void testBlocked() nextBatchFuture = getNextBatch(source, 10); assertFalse(nextBatchFuture.isDone()); mockSource.atSplitCompletion(FINISH); - requireFutureValue(nextBatchFuture) + getFutureValue(nextBatchFuture) .assertSize(0) .assertNoMoreSplits(true); assertTrue(source.isFinished()); @@ -116,13 +136,13 @@ public void testBlocked() mockSource = new MockSplitSource() .setBatchSize(1); - try (SplitSource source = new BufferingSplitSource(mockSource, 10)) { + try (SplitSource source = new BufferingSplitSource(mockSource, executor, 10)) { // Source has 1 out of 10 needed. mockSource.increaseAvailableSplits(1); ListenableFuture nextBatchFuture = getNextBatch(source, 10); assertFalse(nextBatchFuture.isDone()); mockSource.increaseAvailableSplits(9); - requireFutureValue(nextBatchFuture) + getFutureValue(nextBatchFuture) .assertSize(10) .assertNoMoreSplits(false); @@ -131,7 +151,7 @@ public void testBlocked() mockSource.increaseAvailableSplits(5); assertFalse(nextBatchFuture.isDone()); mockSource.atSplitCompletion(FINISH); - requireFutureValue(nextBatchFuture) + getFutureValue(nextBatchFuture) .assertSize(5) .assertNoMoreSplits(true); assertTrue(source.isFinished()); @@ -139,13 +159,13 @@ public void testBlocked() mockSource = new MockSplitSource() .setBatchSize(1); - try (SplitSource source = new BufferingSplitSource(mockSource, 10)) { + try (SplitSource source = new BufferingSplitSource(mockSource, executor, 10)) { // Source has 9 out of 10 needed. mockSource.increaseAvailableSplits(9); ListenableFuture nextBatchFuture = getNextBatch(source, 10); assertFalse(nextBatchFuture.isDone()); mockSource.increaseAvailableSplits(1); - requireFutureValue(nextBatchFuture) + getFutureValue(nextBatchFuture) .assertSize(10) .assertNoMoreSplits(false); @@ -161,12 +181,12 @@ public void testBlocked() // Fast source: source produce 8 before, and 8 after invocation. BufferedSource should return all 16 at once. mockSource = new MockSplitSource() .setBatchSize(8); - try (SplitSource source = new BufferingSplitSource(mockSource, 10)) { + try (SplitSource source = new BufferingSplitSource(mockSource, executor, 10)) { mockSource.increaseAvailableSplits(8); ListenableFuture nextBatchFuture = getNextBatch(source, 20); assertFalse(nextBatchFuture.isDone()); mockSource.increaseAvailableSplits(8); - requireFutureValue(nextBatchFuture) + getFutureValue(nextBatchFuture) .assertSize(16) .assertNoMoreSplits(false); } @@ -178,8 +198,8 @@ public void testFinishedSetWithoutIndicationFromSplitBatch() MockSplitSource mockSource = new MockSplitSource() .setBatchSize(1) .increaseAvailableSplits(1); - try (SplitSource source = new BufferingSplitSource(mockSource, 100)) { - requireFutureValue(getNextBatch(source, 1)) + try (SplitSource source = new BufferingSplitSource(mockSource, executor, 100)) { + getFutureValue(getNextBatch(source, 1)) .assertSize(1) .assertNoMoreSplits(false); assertFalse(source.isFinished()); @@ -189,7 +209,7 @@ public void testFinishedSetWithoutIndicationFromSplitBatch() // In this case, the preceding getNextBatch() indicates the noMoreSplits is false, // but the next isFinished call will return true. mockSource.atSplitCompletion(FINISH); - requireFutureValue(getNextBatch(source, 1)) + getFutureValue(getNextBatch(source, 1)) .assertSize(0) .assertNoMoreSplits(true); assertTrue(source.isFinished()); @@ -203,7 +223,7 @@ public void testFailImmediate() MockSplitSource mockSource = new MockSplitSource() .setBatchSize(1) .atSplitCompletion(FAIL); - try (SplitSource source = new BufferingSplitSource(mockSource, 100)) { + try (SplitSource source = new BufferingSplitSource(mockSource, executor, 100)) { assertFutureFailsWithMockFailure(getNextBatch(source, 200)); assertEquals(mockSource.getNextBatchInvocationCount(), 1); } @@ -216,7 +236,7 @@ public void testFail() .setBatchSize(1) .increaseAvailableSplits(1) .atSplitCompletion(FAIL); - try (SplitSource source = new BufferingSplitSource(mockSource, 100)) { + try (SplitSource source = new BufferingSplitSource(mockSource, executor, 100)) { assertFutureFailsWithMockFailure(getNextBatch(source, 2)); assertEquals(mockSource.getNextBatchInvocationCount(), 2); } @@ -224,16 +244,10 @@ public void testFail() private static void assertFutureFailsWithMockFailure(ListenableFuture future) { - assertTrue(future.isDone()); assertThatThrownBy(future::get) .hasMessageContaining("Mock failure"); } - private static T requireFutureValue(Future future) - { - return tryGetFutureValue(future).orElseThrow(AssertionError::new); - } - private static ListenableFuture getNextBatch(SplitSource splitSource, int maxSize) { ListenableFuture future = splitSource.getNextBatch(maxSize); From a4631716a272f3ef1aee441bcff9564a56a6b2ef Mon Sep 17 00:00:00 2001 From: James Petty Date: Fri, 20 Oct 2023 13:52:16 -0400 Subject: [PATCH 169/587] Avoid inserting Limit nodes before merging Union nodes --- .../io/trino/sql/planner/PlanOptimizers.java | 25 ++++++++++++++++++- .../planprinter/TestJsonRepresentation.java | 12 ++++----- .../io/trino/sql/query/TestSelectAll.java | 2 +- .../AbstractTestEngineOnlyQueries.java | 2 +- .../execution/TestEventListenerBasic.java | 8 +++--- 5 files changed, 36 insertions(+), 13 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/PlanOptimizers.java b/core/trino-main/src/main/java/io/trino/sql/planner/PlanOptimizers.java index 3a1b8ba82e7d..be206f012741 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/PlanOptimizers.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/PlanOptimizers.java @@ -415,7 +415,6 @@ public PlanOptimizers( ImmutableSet.>builder() .addAll(columnPruningRules) .addAll(projectionPushdownRules) - .addAll(limitPushdownRules) .addAll(new UnwrapRowSubscript().rules()) .addAll(new PushCastIntoRow().rules()) .addAll(ImmutableSet.of( @@ -430,6 +429,7 @@ public PlanOptimizers( new RemoveFullSample(), new EvaluateZeroSample(), new PushOffsetThroughProject(), + new MergeUnion(), new MergeLimits(), new MergeLimitWithSort(), new MergeLimitOverProjectWithSort(), @@ -457,6 +457,29 @@ public PlanOptimizers( new SimplifyCountOverConstant(plannerContext), new PreAggregateCaseAggregations(plannerContext, typeAnalyzer))) .build()), + // MergeUnion and related projection pruning rules must run before limit pushdown rules, otherwise + // an intermediate limit node will prevent unions from being merged later on + new IterativeOptimizer( + plannerContext, + ruleStats, + statsCalculator, + costCalculator, + ImmutableSet.>builder() + .addAll(projectionPushdownRules) + .addAll(columnPruningRules) + .addAll(limitPushdownRules) + .addAll(ImmutableSet.of( + new MergeUnion(), + new RemoveEmptyUnionBranches(), + new MergeFilters(metadata), + new RemoveTrivialFilters(), + new MergeLimits(), + new MergeLimitWithSort(), + new MergeLimitOverProjectWithSort(), + new MergeLimitWithTopN(), + new InlineProjections(plannerContext, typeAnalyzer), + new RemoveRedundantIdentityProjections())) + .build()), new IterativeOptimizer( plannerContext, ruleStats, diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/planprinter/TestJsonRepresentation.java b/core/trino-main/src/test/java/io/trino/sql/planner/planprinter/TestJsonRepresentation.java index c8fd6beb40db..855a8307e8d0 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/planprinter/TestJsonRepresentation.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/planprinter/TestJsonRepresentation.java @@ -100,14 +100,14 @@ public void testDistributedJsonPlan() ImmutableList.of(), ImmutableList.of(new PlanNodeStatsAndCostSummary(10, 90, 0, 0, 0)), ImmutableList.of(new JsonRenderedNode( - "98", + "100", "Limit", ImmutableMap.of("count", "10", "withTies", "", "inputPreSortedBy", "[]"), ImmutableList.of(typedSymbol("quantity", "double")), ImmutableList.of(), ImmutableList.of(new PlanNodeStatsAndCostSummary(10, 90, 90, 0, 0)), ImmutableList.of(new JsonRenderedNode( - "147", + "149", "LocalExchange", ImmutableMap.of( "partitioning", "SINGLE", @@ -125,7 +125,7 @@ public void testDistributedJsonPlan() ImmutableList.of("quantity := tpch:quantity"), ImmutableList.of(new PlanNodeStatsAndCostSummary(60175, 541575, 541575, 0, 0)), ImmutableList.of())))))))); - MaterializedResult expectedPlan = resultBuilder(queryRunner.getDefaultSession(), createVarcharType(2058)) + MaterializedResult expectedPlan = resultBuilder(queryRunner.getDefaultSession(), createVarcharType(2059)) .row(DISTRIBUTED_PLAN_JSON_CODEC.toJson(distributedPlan)) .build(); assertThat(actualPlan).isEqualTo(expectedPlan); @@ -143,14 +143,14 @@ public void testLogicalJsonPlan() ImmutableList.of(), ImmutableList.of(new PlanNodeStatsAndCostSummary(10, 90, 0, 0, 0)), ImmutableList.of(new JsonRenderedNode( - "98", + "100", "Limit", ImmutableMap.of("count", "10", "withTies", "", "inputPreSortedBy", "[]"), ImmutableList.of(typedSymbol("quantity", "double")), ImmutableList.of(), ImmutableList.of(new PlanNodeStatsAndCostSummary(10, 90, 90, 0, 0)), ImmutableList.of(new JsonRenderedNode( - "147", + "149", "LocalExchange", ImmutableMap.of( "partitioning", "SINGLE", @@ -168,7 +168,7 @@ public void testLogicalJsonPlan() ImmutableList.of("quantity := tpch:quantity"), ImmutableList.of(new PlanNodeStatsAndCostSummary(60175, 541575, 541575, 0, 0)), ImmutableList.of()))))))); - MaterializedResult expectedPlan = resultBuilder(queryRunner.getDefaultSession(), createVarcharType(1884)) + MaterializedResult expectedPlan = resultBuilder(queryRunner.getDefaultSession(), createVarcharType(1885)) .row(JSON_RENDERED_NODE_CODEC.toJson(expectedJsonNode)) .build(); assertThat(actualPlan).isEqualTo(expectedPlan); diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestSelectAll.java b/core/trino-main/src/test/java/io/trino/sql/query/TestSelectAll.java index 673371b81c49..b7574a09caa6 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestSelectAll.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestSelectAll.java @@ -226,7 +226,7 @@ public void testSelectAllFromOuterScopeTable() assertThat(assertions.query("SELECT t.a, t2.d FROM (VALUES (0, 1), (2, 3)) t(a, b), LATERAL (SELECT t.*) t2(c, d)")).matches("VALUES (0, 1), (2, 3)"); // limit in lateral relation assertThat(assertions.query("SELECT * FROM (VALUES 0, 1) t(a), LATERAL (SELECT t.* LIMIT 5)")).matches("VALUES (0, 0), (1, 1)"); - assertThatThrownBy(() -> assertions.query("SELECT * FROM (VALUES 0, 1) t(a), LATERAL (SELECT t.* LIMIT 0)")).hasMessageMatching(UNSUPPORTED_DECORRELATION_MESSAGE); + assertThat(assertions.query("SELECT * FROM (VALUES 0, 1) t(a), LATERAL (SELECT t.* LIMIT 0)")).matches(result -> result.getMaterializedRows().isEmpty()); // filter in lateral relation assertThat(assertions.query("SELECT * FROM (VALUES 0, 1) t(a), LATERAL (SELECT t.* WHERE true)")).matches("VALUES (0, 0), (1, 1)"); assertThat(assertions.query("SELECT * FROM (VALUES 0, 1) t(a), LATERAL (SELECT t.* WHERE 0 = 0)")).matches("VALUES (0, 0), (1, 1)"); diff --git a/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestEngineOnlyQueries.java b/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestEngineOnlyQueries.java index 5f9f8f8e00af..833639074dea 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestEngineOnlyQueries.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestEngineOnlyQueries.java @@ -3696,7 +3696,7 @@ public void testSelectAllFromOuterScopeTable() assertQuery("SELECT * FROM region r, LATERAL (SELECT r.*)", "SELECT *, * FROM region"); assertQuery("SELECT * FROM region r, LATERAL (SELECT r.* LIMIT 2)", "SELECT *, * FROM region"); assertQuery("SELECT r.name, t.a FROM region r, LATERAL (SELECT r.* LIMIT 2) t(a, b, c)", "SELECT name, regionkey FROM region"); - assertQueryFails("SELECT * FROM region r, LATERAL (SELECT r.* LIMIT 0)", UNSUPPORTED_CORRELATED_SUBQUERY_ERROR_MSG); + assertQuery("SELECT * FROM region r, LATERAL (SELECT r.* LIMIT 0)", "SELECT *, * FROM region LIMIT 0"); assertQuery("SELECT * FROM region r, LATERAL (SELECT r.* WHERE true)", "SELECT *, * FROM region"); assertQuery("SELECT region.* FROM region, LATERAL (SELECT region.*) region", "SELECT *, * FROM region"); assertQueryFails("SELECT * FROM region r, LATERAL (SELECT r.* WHERE false)", UNSUPPORTED_CORRELATED_SUBQUERY_ERROR_MSG); diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java b/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java index d968cb2ffe45..e1a083b7c8ec 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java @@ -1238,14 +1238,14 @@ public void testAnonymizedJsonPlan() ImmutableList.of(), ImmutableList.of(), ImmutableList.of(new JsonRenderedNode( - "98", + "100", "Limit", ImmutableMap.of("count", "10", "withTies", "", "inputPreSortedBy", "[]"), ImmutableList.of(typedSymbol("symbol_1", "double")), ImmutableList.of(), ImmutableList.of(), ImmutableList.of(new JsonRenderedNode( - "171", + "173", "LocalExchange", ImmutableMap.of( "partitioning", "[connectorHandleType = SystemPartitioningHandle, partitioning = SINGLE, function = SINGLE]", @@ -1256,7 +1256,7 @@ public void testAnonymizedJsonPlan() ImmutableList.of(), ImmutableList.of(), ImmutableList.of(new JsonRenderedNode( - "138", + "140", "RemoteSource", ImmutableMap.of("sourceFragmentIds", "[1]"), ImmutableList.of(typedSymbol("symbol_1", "double")), @@ -1264,7 +1264,7 @@ public void testAnonymizedJsonPlan() ImmutableList.of(), ImmutableList.of()))))))), "1", new JsonRenderedNode( - "137", + "139", "LimitPartial", ImmutableMap.of( "count", "10", From 441800cdc3d151208cb68ee381ed789f40ce93e8 Mon Sep 17 00:00:00 2001 From: James Petty Date: Fri, 20 Oct 2023 13:55:36 -0400 Subject: [PATCH 170/587] Fix partial vs final push-down in LimitPushDown --- .../io/trino/sql/planner/optimizations/LimitPushDown.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/LimitPushDown.java b/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/LimitPushDown.java index 16bd8336951b..d3d1f3b33a65 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/LimitPushDown.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/LimitPushDown.java @@ -135,8 +135,10 @@ public PlanNode visitLimit(LimitNode node, RewriteContext context) } if (!node.requiresPreSortedInputs() && (!node.isWithTies() || (limit != null && node.getCount() >= limit.getCount()))) { + // The new limit context is partial if neither the existing context nor this limit node is final + boolean partial = node.isPartial() && (limit == null || limit.isPartial()); // default visitPlan logic will insert the limit node - return context.rewrite(node.getSource(), new LimitContext(count, false)); + return context.rewrite(node.getSource(), new LimitContext(count, partial)); } return context.defaultRewrite(node, context.get()); From 43b0ad2dbc93bd749eb5ba7953dee37de4621e5a Mon Sep 17 00:00:00 2001 From: James Petty Date: Fri, 20 Oct 2023 13:55:59 -0400 Subject: [PATCH 171/587] Avoid unnecessary full limit pushdown in AddExchanges --- .../java/io/trino/sql/planner/optimizations/AddExchanges.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/AddExchanges.java b/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/AddExchanges.java index aecf89185fc2..ab6d732c81a3 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/AddExchanges.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/AddExchanges.java @@ -643,7 +643,7 @@ public PlanWithProperties visitLimit(LimitNode node, PreferredProperties preferr PlanWithProperties child = planChild(node, PreferredProperties.any()); - if (!child.getProperties().isSingleNode()) { + if (!node.isPartial() && !child.getProperties().isSingleNode()) { child = withDerivedProperties( new LimitNode(idAllocator.getNextId(), child.getNode(), node.getCount(), true), child.getProperties()); From 6e5bbb35871d532cfba7b64befec86097758df2f Mon Sep 17 00:00:00 2001 From: James Petty Date: Mon, 23 Oct 2023 13:48:43 -0400 Subject: [PATCH 172/587] Add more tests for limit pushdown plans --- .../trino/sql/planner/TestLogicalPlanner.java | 29 +++++++++++++++++++ .../optimizations/TestAddExchangesPlans.java | 24 +++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestLogicalPlanner.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestLogicalPlanner.java index 868d59c7fc61..165e571fcafe 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestLogicalPlanner.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestLogicalPlanner.java @@ -1707,6 +1707,35 @@ public void testRedundantLimitNodeRemoval() values(ImmutableList.of("x")))); } + @Test + public void testLimitPushdownThroughUnionNesting() + { + assertPlan( + """ + SELECT col FROM ( + SELECT nationkey FROM nation + UNION ALL + SELECT nationkey FROM nation + UNION ALL + SELECT nationkey FROM nation + ) AS t(col) + LIMIT 2""", + output( + limit( + 2, + ImmutableList.of(), + false, + exchange( + LOCAL, + GATHER, + exchange( + LOCAL, + REPARTITION, + limit(2, ImmutableList.of(), true, tableScan("nation")), + limit(2, ImmutableList.of(), true, tableScan("nation")), + limit(2, ImmutableList.of(), true, tableScan("nation"))))))); + } + @Test public void testRemoveSingleRowSort() { diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestAddExchangesPlans.java b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestAddExchangesPlans.java index 21053f851c1a..a7efeeb847df 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestAddExchangesPlans.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestAddExchangesPlans.java @@ -192,6 +192,30 @@ public void testRepartitionForUnionAllBeforeHashJoin() tableScan("region", ImmutableMap.of("regionkey", "regionkey")))))))); } + @Test + public void testSingleGatheringExchangeForUnionAllWithLimit() + { + assertDistributedPlan(""" + SELECT * FROM ( + SELECT nationkey FROM nation + UNION ALL + SELECT nationkey FROM nation + UNION ALL + SELECT nationkey FROM nation + ) + LIMIT 2 + """, + output( + limit(2, ImmutableList.of(), false, + exchange(LOCAL, GATHER, + exchange(REMOTE, GATHER, + limit(2, ImmutableList.of(), true, + exchange(LOCAL, REPARTITION, + limit(2, ImmutableList.of(), true, tableScan("nation")), + limit(2, ImmutableList.of(), true, tableScan("nation")), + limit(2, ImmutableList.of(), true, tableScan("nation"))))))))); + } + @Test public void testNonSpillableBroadcastJoinAboveTableScan() { From b472acb0f6feaa41dc80afc3c653a25d0da27e02 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Wed, 8 Nov 2023 19:24:27 -0800 Subject: [PATCH 173/587] Fix ARRAY hash code when array value uses a dictionary block --- core/trino-spi/src/main/java/io/trino/spi/type/ArrayType.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/trino-spi/src/main/java/io/trino/spi/type/ArrayType.java b/core/trino-spi/src/main/java/io/trino/spi/type/ArrayType.java index 5b904f16943c..4acfec1d2174 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/type/ArrayType.java +++ b/core/trino-spi/src/main/java/io/trino/spi/type/ArrayType.java @@ -583,9 +583,9 @@ private static long hashOperator(MethodHandle hashOperator, Block array) if (array instanceof DictionaryBlock dictionaryBlock) { ValueBlock valuesBlock = dictionaryBlock.getDictionary(); long hash = 0; - for (int position = 0; position < valuesBlock.getPositionCount(); position++) { + for (int position = 0; position < dictionaryBlock.getPositionCount(); position++) { int index = dictionaryBlock.getId(position); - long elementHash = valuesBlock.isNull(position) ? NULL_HASH_CODE : (long) hashOperator.invokeExact(valuesBlock, index); + long elementHash = valuesBlock.isNull(index) ? NULL_HASH_CODE : (long) hashOperator.invokeExact(valuesBlock, index); hash = 31 * hash + elementHash; } return hash; From 17b4e37f61155c8d77b1a29434811b78f0615569 Mon Sep 17 00:00:00 2001 From: Raunaq Morarka Date: Fri, 10 Nov 2023 09:42:45 +0530 Subject: [PATCH 174/587] Fix reads of 0 length on parquet decoders Some files may contain empty dictionaries which when read can cause plain value decoders to be read with 0 length --- .../parquet/reader/decoders/DeltaByteArrayDecoders.java | 6 ++++++ .../ShortDecimalFixedWidthByteArrayBatchDecoder.java | 9 +++++++++ .../reader/decoders/AbstractValueDecodersTest.java | 3 ++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/decoders/DeltaByteArrayDecoders.java b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/decoders/DeltaByteArrayDecoders.java index 2d7f96c37160..7da0a6d4a925 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/decoders/DeltaByteArrayDecoders.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/decoders/DeltaByteArrayDecoders.java @@ -181,6 +181,9 @@ public void skip(int n) protected void readBounded(BinaryBuffer values, int offset, int length, int totalInputLength) { checkPositionIndexes(inputLengthsOffset, inputLengthsOffset + length, prefixLengths.length); + if (length == 0) { + return; + } int[] outputOffsets = values.getOffsets(); byte[] dataBuffer = readUnbounded(outputOffsets, offset, length, totalInputLength); Slice inputSlice = Slices.wrappedBuffer(dataBuffer); @@ -224,6 +227,9 @@ protected void readBounded(BinaryBuffer values, int offset, int length, int tota protected void readUnbounded(BinaryBuffer values, int offset, int length, int totalInputLength) { checkPositionIndexes(inputLengthsOffset, inputLengthsOffset + length, prefixLengths.length); + if (length == 0) { + return; + } int[] outputOffsets = values.getOffsets(); Slice outputBuffer = Slices.wrappedBuffer(readUnbounded(outputOffsets, offset, length, totalInputLength)); values.addChunk(outputBuffer); diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/decoders/ShortDecimalFixedWidthByteArrayBatchDecoder.java b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/decoders/ShortDecimalFixedWidthByteArrayBatchDecoder.java index 5ba66a888197..fdadb5a951ca 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/decoders/ShortDecimalFixedWidthByteArrayBatchDecoder.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/decoders/ShortDecimalFixedWidthByteArrayBatchDecoder.java @@ -78,6 +78,9 @@ private static final class BigEndianReader7 @Override public void decode(SimpleSliceInputStream input, long[] values, int offset, int length) { + if (length == 0) { + return; + } int bytesOffSet = 0; int endOffset = offset + length; for (int i = offset; i < endOffset - 1; i++) { @@ -106,6 +109,9 @@ private static final class BigEndianReader6 @Override public void decode(SimpleSliceInputStream input, long[] values, int offset, int length) { + if (length == 0) { + return; + } int bytesOffSet = 0; int endOffset = offset + length; for (int i = offset; i < endOffset - 1; i++) { @@ -133,6 +139,9 @@ private static final class BigEndianReader5 @Override public void decode(SimpleSliceInputStream input, long[] values, int offset, int length) { + if (length == 0) { + return; + } int bytesOffSet = 0; int endOffset = offset + length; for (int i = offset; i < endOffset - 1; i++) { diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/decoders/AbstractValueDecodersTest.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/decoders/AbstractValueDecodersTest.java index 0581a5273bfe..34d979db01da 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/decoders/AbstractValueDecodersTest.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/decoders/AbstractValueDecodersTest.java @@ -75,6 +75,7 @@ import static io.trino.testing.DataProviders.concat; import static io.trino.testing.DataProviders.toDataProvider; import static java.lang.Integer.MAX_VALUE; +import static java.lang.Math.max; import static java.lang.Math.min; import static java.lang.String.format; import static java.util.Objects.requireNonNull; @@ -304,7 +305,7 @@ public void run(ValueDecoder decoder, T data, int size) int i = 0; while (i < size) { if (random.nextBoolean()) { - int readBatch = random.nextInt(1, batchSize + 2); + int readBatch = random.nextInt(0, max(batchSize, 1) + 1); decoder.read(data, i, min(readBatch, size - i)); i += readBatch; } From 331116e3febd13388a3d22374526e69a32ca3a17 Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Fri, 10 Nov 2023 10:14:24 +0900 Subject: [PATCH 175/587] Rename gcs.projectId to gcs.project-id config property --- .../main/java/io/trino/filesystem/gcs/GcsFileSystemConfig.java | 2 +- .../java/io/trino/filesystem/gcs/TestGcsFileSystemConfig.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileSystemConfig.java b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileSystemConfig.java index 915d1dcf1e66..f71b4997f695 100644 --- a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileSystemConfig.java +++ b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileSystemConfig.java @@ -100,7 +100,7 @@ public String getProjectId() return projectId; } - @Config("gcs.projectId") + @Config("gcs.project-id") public GcsFileSystemConfig setProjectId(String projectId) { this.projectId = projectId; diff --git a/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/TestGcsFileSystemConfig.java b/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/TestGcsFileSystemConfig.java index 0a8dad86df75..2c6dd4a9992b 100644 --- a/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/TestGcsFileSystemConfig.java +++ b/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/TestGcsFileSystemConfig.java @@ -54,7 +54,7 @@ void testExplicitPropertyMappings() .put("gcs.write-block-size", "52MB") .put("gcs.page-size", "10") .put("gcs.batch-size", "11") - .put("gcs.projectId", "project") + .put("gcs.project-id", "project") .put("gcs.use-access-token", "true") .put("gcs.json-key", "{}") .put("gcs.json-key-file-path", jsonKeyFile.toString()) From ff925bc97d67a3388cd515da2bafdaeed44dde3d Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Fri, 10 Nov 2023 15:15:21 +0900 Subject: [PATCH 176/587] Fix incorrect result of query table function with projection in BigQuery --- .../plugin/bigquery/BigQueryQueryPageSource.java | 4 +++- .../plugin/bigquery/BaseBigQueryConnectorTest.java | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryQueryPageSource.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryQueryPageSource.java index 7330328f5653..5ee3f3065095 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryQueryPageSource.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryQueryPageSource.java @@ -74,6 +74,7 @@ public class BigQueryQueryPageSource .optionalEnd() .toFormatter(); + private final List columnNames; private final List columnTypes; private final PageBuilder pageBuilder; private final TableResult tableResult; @@ -94,6 +95,7 @@ public BigQueryQueryPageSource( requireNonNull(columnTypes, "columnTypes is null"); requireNonNull(filter, "filter is null"); checkArgument(columnNames.size() == columnTypes.size(), "columnNames and columnTypes sizes don't match"); + this.columnNames = ImmutableList.copyOf(columnNames); this.columnTypes = ImmutableList.copyOf(columnTypes); this.pageBuilder = new PageBuilder(columnTypes); String sql = buildSql(table, client.getProjectId(), ImmutableList.copyOf(columnNames), filter); @@ -144,7 +146,7 @@ public Page getNextPage() pageBuilder.declarePosition(); for (int column = 0; column < columnTypes.size(); column++) { BlockBuilder output = pageBuilder.getBlockBuilder(column); - appendTo(columnTypes.get(column), record.get(column), output); + appendTo(columnTypes.get(column), record.get(columnNames.get(column)), output); } } finished = true; diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java index d9db340152fa..15772be3028b 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java @@ -803,6 +803,18 @@ public void testNativeQuerySimple() "VALUES 1"); } + @Test + public void testNativeQuerySimpleWithProjectedColumns() + { + assertQuery( + "SELECT z, y, x FROM (SELECT y, z, x FROM TABLE(bigquery.system.query(query => 'SELECT 1 x, 2 y, 3 z')))", + "VALUES (3, 2, 1)"); + + assertQuery( + "SELECT z FROM (SELECT x, y, z FROM TABLE(bigquery.system.query(query => 'SELECT 1 x, 2 y, 3 z')))", + "VALUES 3"); + } + @Test public void testNativeQuerySelectForCaseSensitiveColumnNames() { From 76071955470442d69c5948a3e312da51c504d348 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 8 Nov 2023 15:51:24 +0100 Subject: [PATCH 177/587] Accept Location in ForwardingOutputFile constructor --- .../io/trino/plugin/iceberg/IcebergFileWriterFactory.java | 2 +- .../java/io/trino/plugin/iceberg/fileio/ForwardingFileIo.java | 2 +- .../io/trino/plugin/iceberg/fileio/ForwardingOutputFile.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergFileWriterFactory.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergFileWriterFactory.java index 9d405b4683aa..b80236f45c9c 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergFileWriterFactory.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergFileWriterFactory.java @@ -296,7 +296,7 @@ private IcebergFileWriter createAvroWriter( .collect(toImmutableList()); return new IcebergAvroFileWriter( - new ForwardingOutputFile(fileSystem, outputPath.toString()), + new ForwardingOutputFile(fileSystem, outputPath), rollbackAction, icebergSchema, columnTypes, diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/fileio/ForwardingFileIo.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/fileio/ForwardingFileIo.java index 03dcb9d109d9..96b2be40424c 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/fileio/ForwardingFileIo.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/fileio/ForwardingFileIo.java @@ -57,7 +57,7 @@ public InputFile newInputFile(String path, long length) @Override public OutputFile newOutputFile(String path) { - return new ForwardingOutputFile(fileSystem, path); + return new ForwardingOutputFile(fileSystem, Location.of(path)); } @Override diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/fileio/ForwardingOutputFile.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/fileio/ForwardingOutputFile.java index 40a65d5b7a36..8d0f956688b7 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/fileio/ForwardingOutputFile.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/fileio/ForwardingOutputFile.java @@ -33,10 +33,10 @@ public class ForwardingOutputFile private final TrinoFileSystem fileSystem; private final TrinoOutputFile outputFile; - public ForwardingOutputFile(TrinoFileSystem fileSystem, String path) + public ForwardingOutputFile(TrinoFileSystem fileSystem, Location location) { this.fileSystem = requireNonNull(fileSystem, "fileSystem is null"); - this.outputFile = fileSystem.newOutputFile(Location.of(path)); + this.outputFile = fileSystem.newOutputFile(location); } @Override From b74d833bc16a9775ed0b29a0a814902723450ab5 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Tue, 7 Nov 2023 17:48:11 +0100 Subject: [PATCH 178/587] Remove redundant TableOperations construction --- .../plugin/iceberg/catalog/glue/TrinoGlueCatalog.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java index 2c069ae085a9..5a0a6d01aed4 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java @@ -53,6 +53,7 @@ import io.trino.plugin.iceberg.UnknownTableTypeException; import io.trino.plugin.iceberg.catalog.AbstractTrinoCatalog; import io.trino.plugin.iceberg.catalog.IcebergTableOperationsProvider; +import io.trino.plugin.iceberg.fileio.ForwardingFileIo; import io.trino.spi.TrinoException; import io.trino.spi.connector.CatalogSchemaTableName; import io.trino.spi.connector.ColumnMetadata; @@ -821,14 +822,7 @@ private Optional getTableAndCacheMetada String metadataLocation = parameters.get(METADATA_LOCATION_PROP); try { // Cache the TableMetadata while we have the Table retrieved anyway - TableOperations operations = tableOperationsProvider.createTableOperations( - this, - session, - schemaTableName.getSchemaName(), - schemaTableName.getTableName(), - Optional.empty(), - Optional.empty()); - FileIO io = operations.io(); + ForwardingFileIo io = new ForwardingFileIo(fileSystemFactory.create(session)); tableMetadataCache.put(schemaTableName, TableMetadataParser.read(io, io.newInputFile(metadataLocation))); } catch (RuntimeException e) { From 12bdcc23d49976668343bcfaa4bcb7656ead8c5e Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 8 Nov 2023 15:35:09 +0100 Subject: [PATCH 179/587] Remove redundant property defaulting for MV table creation The FILE_FORMAT_PROPERTY table property has a default value, so no need for explicit default in the code. --- .../trino/plugin/iceberg/catalog/AbstractTrinoCatalog.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractTrinoCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractTrinoCatalog.java index a6f3fcbad350..45c1637f1063 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractTrinoCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractTrinoCatalog.java @@ -57,7 +57,6 @@ import java.io.IOException; import java.time.Duration; import java.time.temporal.ChronoUnit; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -77,7 +76,6 @@ import static io.trino.plugin.iceberg.IcebergMaterializedViewAdditionalProperties.STORAGE_SCHEMA; import static io.trino.plugin.iceberg.IcebergMaterializedViewAdditionalProperties.getStorageSchema; import static io.trino.plugin.iceberg.IcebergMaterializedViewDefinition.decodeMaterializedViewData; -import static io.trino.plugin.iceberg.IcebergTableProperties.FILE_FORMAT_PROPERTY; import static io.trino.plugin.iceberg.IcebergTableProperties.getPartitioning; import static io.trino.plugin.iceberg.IcebergUtil.commit; import static io.trino.plugin.iceberg.IcebergUtil.getIcebergTableProperties; @@ -97,7 +95,6 @@ import static java.util.Objects.requireNonNull; import static java.util.UUID.randomUUID; import static org.apache.iceberg.TableMetadata.newTableMetadata; -import static org.apache.iceberg.TableProperties.DEFAULT_FILE_FORMAT_DEFAULT; import static org.apache.iceberg.Transactions.createOrReplaceTableTransaction; import static org.apache.iceberg.Transactions.createTableTransaction; @@ -269,8 +266,6 @@ protected SchemaTableName createMaterializedViewStorageTable(ConnectorSession se // Generate a storage table name and create a storage table. The properties in the definition are table properties for the // storage table as indicated in the materialized view definition. String storageTableName = "st_" + randomUUID().toString().replace("-", ""); - Map storageTableProperties = new HashMap<>(definition.getProperties()); - storageTableProperties.putIfAbsent(FILE_FORMAT_PROPERTY, DEFAULT_FILE_FORMAT_DEFAULT); String storageSchema = getStorageSchema(definition.getProperties()).orElse(viewName.getSchemaName()); SchemaTableName storageTable = new SchemaTableName(storageSchema, storageTableName); @@ -315,7 +310,7 @@ protected SchemaTableName createMaterializedViewStorageTable(ConnectorSession se return new ColumnMetadata(column.getName(), type); }); - ConnectorTableMetadata tableMetadata = new ConnectorTableMetadata(storageTable, columns, storageTableProperties, Optional.empty()); + ConnectorTableMetadata tableMetadata = new ConnectorTableMetadata(storageTable, columns, definition.getProperties(), Optional.empty()); Transaction transaction = IcebergUtil.newCreateTableTransaction(this, tableMetadata, session, false); AppendFiles appendFiles = transaction.newAppend(); commit(appendFiles, session); From fef1784b4458800166370b3baf77863e7e459765 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 8 Nov 2023 17:08:26 +0100 Subject: [PATCH 180/587] Remove redundant Optional return type The helper method always returns a value --- .../iceberg/catalog/glue/TrinoGlueCatalog.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java index 5a0a6d01aed4..3438a406ab1d 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java @@ -835,8 +835,8 @@ else if (isTrinoMaterializedView(tableType, parameters)) { } try { - createMaterializedViewDefinition(session, schemaTableName, table) - .ifPresent(materializedView -> materializedViewCache.put(schemaTableName, materializedView)); + ConnectorMaterializedViewDefinition materializedView = createMaterializedViewDefinition(session, schemaTableName, table); + materializedViewCache.put(schemaTableName, materializedView); } catch (RuntimeException e) { LOG.warn(e, "Failed to cache materialized view from %s", schemaTableName); @@ -1261,10 +1261,10 @@ protected Optional doGetMaterializedView(Co return Optional.empty(); } - return createMaterializedViewDefinition(session, viewName, table); + return Optional.of(createMaterializedViewDefinition(session, viewName, table)); } - private Optional createMaterializedViewDefinition( + private ConnectorMaterializedViewDefinition createMaterializedViewDefinition( ConnectorSession session, SchemaTableName viewName, com.amazonaws.services.glue.model.Table table) @@ -1293,11 +1293,11 @@ private Optional createMaterializedViewDefi if (viewOriginalText == null) { throw new TrinoException(ICEBERG_BAD_DATA, "Materialized view did not have original text " + viewName); } - return Optional.of(getMaterializedViewDefinition( + return getMaterializedViewDefinition( icebergTable, Optional.ofNullable(table.getOwner()), viewOriginalText, - storageTableName)); + storageTableName); } @Override From f937d31a36e222efeb34b423cdadd521c4160f0a Mon Sep 17 00:00:00 2001 From: Alex Jo Date: Fri, 25 Aug 2023 14:56:39 -0400 Subject: [PATCH 181/587] Remove Iceberg materialized view storage tables from metastores Storage tables clutter the namespace, and do not need a separate metastore entry. The pointer to the current metadata file is instead stored in the materialized view properties. This applies only to newly created materialized views, existing ones are not effected. --- .../trino/plugin/iceberg/IcebergConfig.java | 21 ++ .../trino/plugin/iceberg/IcebergMetadata.java | 71 +++++-- .../plugin/iceberg/IcebergTableName.java | 7 + .../io/trino/plugin/iceberg/IcebergUtil.java | 2 +- .../io/trino/plugin/iceberg/TableType.java | 3 +- .../AbstractIcebergTableOperations.java | 8 + .../iceberg/catalog/AbstractTrinoCatalog.java | 74 ++++++- .../plugin/iceberg/catalog/TrinoCatalog.java | 3 + .../file/FileMetastoreTableOperations.java | 26 ++- .../glue/GlueIcebergTableOperations.java | 65 ++++-- .../catalog/glue/TrinoGlueCatalog.java | 186 +++++++++++++++--- .../catalog/glue/TrinoGlueCatalogFactory.java | 5 +- .../hms/AbstractMetastoreTableOperations.java | 22 ++- .../hms/HiveMetastoreTableOperations.java | 37 +++- .../iceberg/catalog/hms/TrinoHiveCatalog.java | 183 +++++++++++++++-- .../catalog/hms/TrinoHiveCatalogFactory.java | 5 +- .../jdbc/IcebergJdbcTableOperations.java | 6 + .../catalog/jdbc/TrinoJdbcCatalog.java | 8 +- .../nessie/IcebergNessieTableOperations.java | 6 + .../catalog/nessie/TrinoNessieCatalog.java | 8 +- .../catalog/rest/TrinoRestCatalog.java | 6 + .../iceberg/BaseIcebergConnectorTest.java | 24 --- .../BaseIcebergMaterializedViewTest.java | 166 ++++++---------- .../plugin/iceberg/TestIcebergConfig.java | 17 ++ .../iceberg/TestIcebergMaterializedView.java | 33 +++- .../iceberg/TestIcebergMergeAppend.java | 3 +- .../iceberg/TestIcebergMetadataListing.java | 4 - .../TestIcebergMetastoreAccessOperations.java | 22 ++- .../TestIcebergOrcMetricsCollection.java | 3 +- .../iceberg/TestIcebergSplitSource.java | 3 +- .../trino/plugin/iceberg/TestIcebergV2.java | 3 +- ...TestTrinoHiveCatalogWithFileMetastore.java | 4 +- ...estIcebergGlueCatalogAccessOperations.java | 12 +- ...estIcebergGlueCatalogMaterializedView.java | 15 +- .../catalog/glue/TestTrinoGlueCatalog.java | 7 +- ...TestTrinoHiveCatalogWithHiveMetastore.java | 4 +- .../TestIcebergHiveMetadataListing.java | 10 - 37 files changed, 801 insertions(+), 281 deletions(-) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergConfig.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergConfig.java index 5dce684f6b50..5b3e0d7f8df5 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergConfig.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergConfig.java @@ -20,6 +20,7 @@ import io.airlift.units.DataSize; import io.airlift.units.Duration; import io.trino.plugin.hive.HiveCompressionCodec; +import jakarta.validation.constraints.AssertFalse; import jakarta.validation.constraints.DecimalMax; import jakarta.validation.constraints.DecimalMin; import jakarta.validation.constraints.Max; @@ -71,6 +72,7 @@ public class IcebergConfig // to avoid deleting those files if Trino is unable to check. private boolean deleteSchemaLocationsFallback; private double minimumAssignedSplitWeight = 0.05; + private boolean hideMaterializedViewStorageTable = true; private Optional materializedViewsStorageSchema = Optional.empty(); private boolean sortedWritingEnabled = true; private boolean queryPartitionFilterRequired; @@ -342,6 +344,19 @@ public double getMinimumAssignedSplitWeight() return minimumAssignedSplitWeight; } + public boolean isHideMaterializedViewStorageTable() + { + return hideMaterializedViewStorageTable; + } + + @Config("iceberg.materialized-views.hide-storage-table") + @ConfigDescription("Hide materialized view storage tables in metastore") + public IcebergConfig setHideMaterializedViewStorageTable(boolean hideMaterializedViewStorageTable) + { + this.hideMaterializedViewStorageTable = hideMaterializedViewStorageTable; + return this; + } + @NotNull public Optional getMaterializedViewsStorageSchema() { @@ -381,4 +396,10 @@ public boolean isQueryPartitionFilterRequired() { return queryPartitionFilterRequired; } + + @AssertFalse(message = "iceberg.materialized-views.storage-schema may only be set when iceberg.materialized-views.hide-storage-table is set to false") + public boolean isStorageSchemaSetWhenHidingIsEnabled() + { + return hideMaterializedViewStorageTable && materializedViewsStorageSchema.isPresent(); + } } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java index 7c0f0d3a28fa..05b010d30e0e 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java @@ -224,6 +224,9 @@ import static io.trino.plugin.iceberg.IcebergSessionProperties.isProjectionPushdownEnabled; import static io.trino.plugin.iceberg.IcebergSessionProperties.isQueryPartitionFilterRequired; import static io.trino.plugin.iceberg.IcebergSessionProperties.isStatisticsEnabled; +import static io.trino.plugin.iceberg.IcebergTableName.isDataTable; +import static io.trino.plugin.iceberg.IcebergTableName.isMaterializedViewStorage; +import static io.trino.plugin.iceberg.IcebergTableName.tableNameFrom; import static io.trino.plugin.iceberg.IcebergTableProperties.FILE_FORMAT_PROPERTY; import static io.trino.plugin.iceberg.IcebergTableProperties.FORMAT_VERSION_PROPERTY; import static io.trino.plugin.iceberg.IcebergTableProperties.PARTITIONING_PROPERTY; @@ -265,6 +268,7 @@ import static io.trino.spi.StandardErrorCode.INVALID_ARGUMENTS; import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; import static io.trino.spi.StandardErrorCode.QUERY_REJECTED; +import static io.trino.spi.StandardErrorCode.TABLE_NOT_FOUND; import static io.trino.spi.connector.MaterializedViewFreshness.Freshness.FRESH; import static io.trino.spi.connector.MaterializedViewFreshness.Freshness.STALE; import static io.trino.spi.connector.MaterializedViewFreshness.Freshness.UNKNOWN; @@ -383,7 +387,21 @@ public ConnectorTableHandle getTableHandle( throw new TrinoException(NOT_SUPPORTED, "Read table with start version is not supported"); } - if (!IcebergTableName.isDataTable(tableName.getTableName())) { + if (isMaterializedViewStorage(tableName.getTableName())) { + verify(endVersion.isEmpty(), "Materialized views do not support versioned queries"); + + SchemaTableName materializedViewName = new SchemaTableName(tableName.getSchemaName(), tableNameFrom(tableName.getTableName())); + if (getMaterializedView(session, materializedViewName).isEmpty()) { + throw new TableNotFoundException(tableName); + } + + BaseTable storageTable = catalog.getMaterializedViewStorageTable(session, materializedViewName) + .orElseThrow(() -> new TrinoException(TABLE_NOT_FOUND, "Storage table metadata not found for materialized view " + tableName)); + + return tableHandleForCurrentSnapshot(tableName, storageTable); + } + + if (!isDataTable(tableName.getTableName())) { // Pretend the table does not exist to produce better error message in case of table redirects to Hive return null; } @@ -404,23 +422,36 @@ public ConnectorTableHandle getTableHandle( throw e; } - Optional tableSnapshotId; - Schema tableSchema; - Optional partitionSpec; if (endVersion.isPresent()) { long snapshotId = getSnapshotIdFromVersion(session, table, endVersion.get()); - tableSnapshotId = Optional.of(snapshotId); - tableSchema = schemaFor(table, snapshotId); - partitionSpec = Optional.empty(); - } - else { - tableSnapshotId = Optional.ofNullable(table.currentSnapshot()).map(Snapshot::snapshotId); - tableSchema = table.schema(); - partitionSpec = Optional.of(table.spec()); + return tableHandleForSnapshot( + tableName, + table, + Optional.of(snapshotId), + schemaFor(table, snapshotId), + Optional.empty()); } + return tableHandleForCurrentSnapshot(tableName, table); + } + private IcebergTableHandle tableHandleForCurrentSnapshot(SchemaTableName tableName, BaseTable table) + { + return tableHandleForSnapshot( + tableName, + table, + Optional.ofNullable(table.currentSnapshot()).map(Snapshot::snapshotId), + table.schema(), + Optional.of(table.spec())); + } + + private IcebergTableHandle tableHandleForSnapshot( + SchemaTableName tableName, + BaseTable table, + Optional tableSnapshotId, + Schema tableSchema, + Optional partitionSpec) + { Map tableProperties = table.properties(); - String nameMappingJson = tableProperties.get(TableProperties.DEFAULT_NAME_MAPPING); return new IcebergTableHandle( trinoCatalogHandle, tableName.getSchemaName(), @@ -434,7 +465,7 @@ public ConnectorTableHandle getTableHandle( TupleDomain.all(), OptionalLong.empty(), ImmutableSet.of(), - Optional.ofNullable(nameMappingJson), + Optional.ofNullable(tableProperties.get(TableProperties.DEFAULT_NAME_MAPPING)), table.location(), table.properties(), false, @@ -516,12 +547,12 @@ public Optional getSystemTable(ConnectorSession session, SchemaTabl private Optional getRawSystemTable(ConnectorSession session, SchemaTableName tableName) { - if (IcebergTableName.isDataTable(tableName.getTableName())) { + if (isDataTable(tableName.getTableName()) || isMaterializedViewStorage(tableName.getTableName())) { return Optional.empty(); } // Only when dealing with an actual system table proceed to retrieve the base table for the system table - String name = IcebergTableName.tableNameFrom(tableName.getTableName()); + String name = tableNameFrom(tableName.getTableName()); Table table; try { table = catalog.loadTable(session, new SchemaTableName(tableName.getSchemaName(), name)); @@ -548,6 +579,7 @@ private Optional getRawSystemTable(ConnectorSession session, Schema case FILES -> Optional.of(new FilesTable(systemTableName, typeManager, table, getCurrentSnapshotId(table))); case PROPERTIES -> Optional.of(new PropertiesTable(systemTableName, table)); case REFS -> Optional.of(new RefsTable(systemTableName, table)); + case MATERIALIZED_VIEW_STORAGE -> throw new VerifyException("Unexpected MATERIALIZED_VIEW_STORAGE table type"); }; } @@ -2898,9 +2930,12 @@ public MaterializedViewFreshness getMaterializedViewFreshness(ConnectorSession s .orElseThrow(() -> new IllegalStateException("Storage table missing in definition of materialized view " + materializedViewName)); Table icebergTable = catalog.loadTable(session, storageTableName); - String dependsOnTables = icebergTable.currentSnapshot().summary().getOrDefault(DEPENDS_ON_TABLES, ""); + String dependsOnTables = Optional.ofNullable(icebergTable.currentSnapshot()) + .map(snapshot -> snapshot.summary().getOrDefault(DEPENDS_ON_TABLES, "")) + .orElse(""); if (dependsOnTables.isEmpty()) { - // Information missing. While it's "unknown" whether storage is stale, we return "stale": under no normal circumstances dependsOnTables should be missing. + // Information missing. While it's "unknown" whether storage is stale, we return "stale". + // Normally dependsOnTables may be missing only when there was no refresh yet. return new MaterializedViewFreshness(STALE, Optional.empty()); } Instant refreshTime = Optional.ofNullable(icebergTable.currentSnapshot().summary().get(TRINO_QUERY_START_TIME)) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergTableName.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergTableName.java index 32716889471d..8e5582cc2fb9 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergTableName.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergTableName.java @@ -20,6 +20,7 @@ import java.util.regex.Pattern; import static io.trino.plugin.iceberg.TableType.DATA; +import static io.trino.plugin.iceberg.TableType.MATERIALIZED_VIEW_STORAGE; import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; import static java.util.Locale.ENGLISH; import static java.util.Objects.requireNonNull; @@ -80,4 +81,10 @@ public static boolean isDataTable(String name) String typeString = match.group("type"); return typeString == null; } + + public static boolean isMaterializedViewStorage(String name) + { + Optional tableType = tableTypeFrom(name); + return tableType.isPresent() && tableType.get() == MATERIALIZED_VIEW_STORAGE; + } } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergUtil.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergUtil.java index c13276cb22b1..10815d251d79 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergUtil.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergUtil.java @@ -644,7 +644,7 @@ public static Transaction newCreateTableTransaction(TrinoCatalog catalog, Connec return catalog.newCreateTableTransaction(session, schemaTableName, schema, partitionSpec, sortOrder, targetPath, createTableProperties(tableMetadata)); } - private static Map createTableProperties(ConnectorTableMetadata tableMetadata) + public static Map createTableProperties(ConnectorTableMetadata tableMetadata) { ImmutableMap.Builder propertiesBuilder = ImmutableMap.builder(); IcebergFileFormat fileFormat = IcebergTableProperties.getFileFormat(tableMetadata.getProperties()); diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/TableType.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/TableType.java index 7141f488d7da..cea961b4d51a 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/TableType.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/TableType.java @@ -22,5 +22,6 @@ public enum TableType PARTITIONS, FILES, PROPERTIES, - REFS + REFS, + MATERIALIZED_VIEW_STORAGE, } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractIcebergTableOperations.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractIcebergTableOperations.java index 855f66de3f35..721c4dcf44f9 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractIcebergTableOperations.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractIcebergTableOperations.java @@ -48,6 +48,7 @@ import static io.trino.plugin.hive.util.HiveClassNames.LAZY_SIMPLE_SERDE_CLASS; import static io.trino.plugin.iceberg.IcebergErrorCode.ICEBERG_INVALID_METADATA; import static io.trino.plugin.iceberg.IcebergErrorCode.ICEBERG_MISSING_METADATA; +import static io.trino.plugin.iceberg.IcebergTableName.isMaterializedViewStorage; import static io.trino.plugin.iceberg.IcebergUtil.METADATA_FOLDER_NAME; import static io.trino.plugin.iceberg.IcebergUtil.fixBrokenMetadataLocation; import static io.trino.plugin.iceberg.IcebergUtil.getLocationProvider; @@ -152,6 +153,11 @@ public void commit(@Nullable TableMetadata base, TableMetadata metadata) return; } + if (isMaterializedViewStorage(tableName)) { + commitMaterializedViewRefresh(base, metadata); + return; + } + if (base == null) { if (PROVIDER_PROPERTY_VALUE.equals(metadata.properties().get(PROVIDER_PROPERTY_KEY))) { // Assume this is a table executing migrate procedure @@ -176,6 +182,8 @@ public void commit(@Nullable TableMetadata base, TableMetadata metadata) protected abstract void commitToExistingTable(TableMetadata base, TableMetadata metadata); + protected abstract void commitMaterializedViewRefresh(TableMetadata base, TableMetadata metadata); + @Override public FileIO io() { diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractTrinoCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractTrinoCatalog.java index 45c1637f1063..dc518c966dd3 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractTrinoCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractTrinoCatalog.java @@ -18,12 +18,14 @@ import dev.failsafe.RetryPolicy; import io.trino.filesystem.Location; import io.trino.filesystem.TrinoFileSystem; +import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.plugin.base.CatalogName; import io.trino.plugin.hive.HiveMetadata; import io.trino.plugin.iceberg.ColumnIdentity; import io.trino.plugin.iceberg.IcebergMaterializedViewDefinition; import io.trino.plugin.iceberg.IcebergUtil; import io.trino.plugin.iceberg.PartitionTransforms.ColumnTransform; +import io.trino.plugin.iceberg.fileio.ForwardingOutputFile; import io.trino.spi.TrinoException; import io.trino.spi.connector.CatalogSchemaTableName; import io.trino.spi.connector.ColumnMetadata; @@ -50,6 +52,7 @@ import org.apache.iceberg.SortOrder; import org.apache.iceberg.Table; import org.apache.iceberg.TableMetadata; +import org.apache.iceberg.TableMetadataParser; import org.apache.iceberg.TableOperations; import org.apache.iceberg.Transaction; import org.apache.iceberg.types.Types; @@ -76,13 +79,21 @@ import static io.trino.plugin.iceberg.IcebergMaterializedViewAdditionalProperties.STORAGE_SCHEMA; import static io.trino.plugin.iceberg.IcebergMaterializedViewAdditionalProperties.getStorageSchema; import static io.trino.plugin.iceberg.IcebergMaterializedViewDefinition.decodeMaterializedViewData; +import static io.trino.plugin.iceberg.IcebergTableName.tableNameWithType; import static io.trino.plugin.iceberg.IcebergTableProperties.getPartitioning; +import static io.trino.plugin.iceberg.IcebergTableProperties.getSortOrder; +import static io.trino.plugin.iceberg.IcebergTableProperties.getTableLocation; +import static io.trino.plugin.iceberg.IcebergUtil.METADATA_FOLDER_NAME; import static io.trino.plugin.iceberg.IcebergUtil.commit; +import static io.trino.plugin.iceberg.IcebergUtil.createTableProperties; import static io.trino.plugin.iceberg.IcebergUtil.getIcebergTableProperties; import static io.trino.plugin.iceberg.IcebergUtil.schemaFromMetadata; import static io.trino.plugin.iceberg.PartitionFields.parsePartitionFields; import static io.trino.plugin.iceberg.PartitionTransforms.getColumnTransform; +import static io.trino.plugin.iceberg.SortFieldUtils.parseSortFields; +import static io.trino.plugin.iceberg.TableType.MATERIALIZED_VIEW_STORAGE; import static io.trino.plugin.iceberg.TypeConverter.toTrinoType; +import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; import static io.trino.spi.StandardErrorCode.TABLE_NOT_FOUND; import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.spi.type.SmallintType.SMALLINT; @@ -94,7 +105,10 @@ import static java.lang.String.format; import static java.util.Objects.requireNonNull; import static java.util.UUID.randomUUID; +import static org.apache.iceberg.BaseMetastoreTableOperations.METADATA_LOCATION_PROP; import static org.apache.iceberg.TableMetadata.newTableMetadata; +import static org.apache.iceberg.TableMetadataParser.getFileExtension; +import static org.apache.iceberg.TableProperties.METADATA_COMPRESSION_DEFAULT; import static org.apache.iceberg.Transactions.createOrReplaceTableTransaction; import static org.apache.iceberg.Transactions.createTableTransaction; @@ -108,17 +122,20 @@ public abstract class AbstractTrinoCatalog private final CatalogName catalogName; private final TypeManager typeManager; protected final IcebergTableOperationsProvider tableOperationsProvider; + private final TrinoFileSystemFactory fileSystemFactory; private final boolean useUniqueTableLocation; protected AbstractTrinoCatalog( CatalogName catalogName, TypeManager typeManager, IcebergTableOperationsProvider tableOperationsProvider, + TrinoFileSystemFactory fileSystemFactory, boolean useUniqueTableLocation) { this.catalogName = requireNonNull(catalogName, "catalogName is null"); this.typeManager = requireNonNull(typeManager, "typeManager is null"); this.tableOperationsProvider = requireNonNull(tableOperationsProvider, "tableOperationsProvider is null"); + this.fileSystemFactory = requireNonNull(fileSystemFactory, "fileSystemFactory is null"); this.useUniqueTableLocation = useUniqueTableLocation; } @@ -261,6 +278,32 @@ protected void deleteTableDirectory(TrinoFileSystem fileSystem, SchemaTableName } } + protected Location createMaterializedViewStorage(ConnectorSession session, SchemaTableName viewName, ConnectorMaterializedViewDefinition definition) + { + if (getStorageSchema(definition.getProperties()).isPresent()) { + throw new TrinoException(NOT_SUPPORTED, "Materialized view property '%s' is not supported when hiding materialized view storage tables is enabled".formatted(STORAGE_SCHEMA)); + } + SchemaTableName storageTableName = new SchemaTableName(viewName.getSchemaName(), tableNameWithType(viewName.getTableName(), MATERIALIZED_VIEW_STORAGE)); + String tableLocation = getTableLocation(definition.getProperties()) + .orElseGet(() -> defaultTableLocation(session, viewName)); + List columns = columnsForMaterializedView(definition); + + Schema schema = schemaFromMetadata(columns); + PartitionSpec partitionSpec = parsePartitionFields(schema, getPartitioning(definition.getProperties())); + SortOrder sortOrder = parseSortFields(schema, getSortOrder(definition.getProperties())); + Map properties = createTableProperties(new ConnectorTableMetadata(storageTableName, columns, definition.getProperties(), Optional.empty())); + + TableMetadata metadata = newTableMetadata(schema, partitionSpec, sortOrder, tableLocation, properties); + + String fileName = format("%05d-%s%s", 0, randomUUID(), getFileExtension(METADATA_COMPRESSION_DEFAULT)); + Location metadataFileLocation = Location.of(tableLocation).appendPath(METADATA_FOLDER_NAME).appendPath(fileName); + + TrinoFileSystem fileSystem = fileSystemFactory.create(session); + TableMetadataParser.write(metadata, new ForwardingOutputFile(fileSystem, metadataFileLocation)); + + return metadataFileLocation; + } + protected SchemaTableName createMaterializedViewStorageTable(ConnectorSession session, SchemaTableName viewName, ConnectorMaterializedViewDefinition definition) { // Generate a storage table name and create a storage table. The properties in the definition are table properties for the @@ -269,7 +312,18 @@ protected SchemaTableName createMaterializedViewStorageTable(ConnectorSession se String storageSchema = getStorageSchema(definition.getProperties()).orElse(viewName.getSchemaName()); SchemaTableName storageTable = new SchemaTableName(storageSchema, storageTableName); + List columns = columnsForMaterializedView(definition); + + ConnectorTableMetadata tableMetadata = new ConnectorTableMetadata(storageTable, columns, definition.getProperties(), Optional.empty()); + Transaction transaction = IcebergUtil.newCreateTableTransaction(this, tableMetadata, session, false); + AppendFiles appendFiles = transaction.newAppend(); + commit(appendFiles, session); + transaction.commitTransaction(); + return storageTable; + } + private List columnsForMaterializedView(ConnectorMaterializedViewDefinition definition) + { Schema schemaWithTimestampTzPreserved = schemaFromMetadata(mappedCopy( definition.getColumns(), column -> { @@ -296,7 +350,7 @@ protected SchemaTableName createMaterializedViewStorageTable(ConnectorSession se }) .collect(toImmutableSet()); - List columns = mappedCopy( + return mappedCopy( definition.getColumns(), column -> { Type type = typeManager.getType(column.getType()); @@ -309,13 +363,6 @@ protected SchemaTableName createMaterializedViewStorageTable(ConnectorSession se } return new ColumnMetadata(column.getName(), type); }); - - ConnectorTableMetadata tableMetadata = new ConnectorTableMetadata(storageTable, columns, definition.getProperties(), Optional.empty()); - Transaction transaction = IcebergUtil.newCreateTableTransaction(this, tableMetadata, session, false); - AppendFiles appendFiles = transaction.newAppend(); - commit(appendFiles, session); - transaction.commitTransaction(); - return storageTable; } /** @@ -413,6 +460,17 @@ protected Map createMaterializedViewProperties(ConnectorSession .buildOrThrow(); } + protected Map createMaterializedViewProperties(ConnectorSession session, Location storageMetadataLocation) + { + return ImmutableMap.builder() + .put(PRESTO_QUERY_ID_NAME, session.getQueryId()) + .put(METADATA_LOCATION_PROP, storageMetadataLocation.toString()) + .put(PRESTO_VIEW_FLAG, "true") + .put(TRINO_CREATED_BY, TRINO_CREATED_BY_VALUE) + .put(TABLE_COMMENT, ICEBERG_MATERIALIZED_VIEW_COMMENT) + .buildOrThrow(); + } + protected static class MaterializedViewMayBeBeingRemovedException extends RuntimeException { diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/TrinoCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/TrinoCatalog.java index 03ba84d0f533..05ca9a5a6739 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/TrinoCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/TrinoCatalog.java @@ -24,6 +24,7 @@ import io.trino.spi.connector.RelationCommentMetadata; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.security.TrinoPrincipal; +import org.apache.iceberg.BaseTable; import org.apache.iceberg.PartitionSpec; import org.apache.iceberg.Schema; import org.apache.iceberg.SortOrder; @@ -168,6 +169,8 @@ void createMaterializedView( Optional getMaterializedView(ConnectorSession session, SchemaTableName viewName); + Optional getMaterializedViewStorageTable(ConnectorSession session, SchemaTableName viewName); + void renameMaterializedView(ConnectorSession session, SchemaTableName source, SchemaTableName target); void updateColumnComment(ConnectorSession session, SchemaTableName schemaTableName, ColumnIdentity columnIdentity, Optional comment); diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/file/FileMetastoreTableOperations.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/file/FileMetastoreTableOperations.java index e25fac198f77..05e4b1357b26 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/file/FileMetastoreTableOperations.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/file/FileMetastoreTableOperations.java @@ -27,11 +27,14 @@ import org.apache.iceberg.io.FileIO; import java.util.Optional; +import java.util.function.BiFunction; import static com.google.common.base.Preconditions.checkState; import static io.trino.plugin.hive.HiveErrorCode.HIVE_CONCURRENT_MODIFICATION_DETECTED; import static io.trino.plugin.hive.metastore.PrincipalPrivileges.NO_PRIVILEGES; +import static io.trino.plugin.iceberg.IcebergTableName.tableNameFrom; import static org.apache.iceberg.BaseMetastoreTableOperations.METADATA_LOCATION_PROP; +import static org.apache.iceberg.BaseMetastoreTableOperations.PREVIOUS_METADATA_LOCATION_PROP; @NotThreadSafe public class FileMetastoreTableOperations @@ -53,9 +56,24 @@ public FileMetastoreTableOperations( protected void commitToExistingTable(TableMetadata base, TableMetadata metadata) { Table currentTable = getTable(); + commitTableUpdate(currentTable, metadata, (table, newMetadataLocation) -> Table.builder(table) + .apply(builder -> updateMetastoreTable(builder, metadata, newMetadataLocation, Optional.of(currentMetadataLocation))) + .build()); + } + + @Override + protected void commitMaterializedViewRefresh(TableMetadata base, TableMetadata metadata) + { + Table materializedView = getTable(database, tableNameFrom(tableName)); + commitTableUpdate(materializedView, metadata, (table, newMetadataLocation) -> Table.builder(table) + .apply(builder -> builder.setParameter(METADATA_LOCATION_PROP, newMetadataLocation).setParameter(PREVIOUS_METADATA_LOCATION_PROP, currentMetadataLocation)) + .build()); + } + private void commitTableUpdate(Table table, TableMetadata metadata, BiFunction tableUpdateFunction) + { checkState(currentMetadataLocation != null, "No current metadata location for existing table"); - String metadataLocation = currentTable.getParameters().get(METADATA_LOCATION_PROP); + String metadataLocation = table.getParameters().get(METADATA_LOCATION_PROP); if (!currentMetadataLocation.equals(metadataLocation)) { throw new CommitFailedException("Metadata location [%s] is not same as table metadata location [%s] for %s", currentMetadataLocation, metadataLocation, getSchemaTableName()); @@ -63,15 +81,13 @@ protected void commitToExistingTable(TableMetadata base, TableMetadata metadata) String newMetadataLocation = writeNewMetadata(metadata, version.orElseThrow() + 1); - Table table = Table.builder(currentTable) - .apply(builder -> updateMetastoreTable(builder, metadata, newMetadataLocation, Optional.of(currentMetadataLocation))) - .build(); + Table updatedTable = tableUpdateFunction.apply(table, newMetadataLocation); // todo privileges should not be replaced for an alter PrincipalPrivileges privileges = table.getOwner().map(MetastoreUtil::buildInitialPrivilegeSet).orElse(NO_PRIVILEGES); try { - metastore.replaceTable(database, tableName, table, privileges); + metastore.replaceTable(database, table.getTableName(), updatedTable, privileges); } catch (RuntimeException e) { if (e instanceof TrinoException trinoException && diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/GlueIcebergTableOperations.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/GlueIcebergTableOperations.java index 50e19f2c43fa..70677a25b510 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/GlueIcebergTableOperations.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/GlueIcebergTableOperations.java @@ -38,8 +38,10 @@ import org.apache.iceberg.exceptions.CommitStateUnknownException; import org.apache.iceberg.io.FileIO; +import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.function.BiFunction; import static com.google.common.base.Verify.verify; import static io.trino.plugin.hive.ViewReaderUtil.isTrinoMaterializedView; @@ -48,6 +50,9 @@ import static io.trino.plugin.hive.metastore.glue.converter.GlueToTrinoConverter.getTableType; import static io.trino.plugin.hive.util.HiveUtil.isIcebergTable; import static io.trino.plugin.iceberg.IcebergErrorCode.ICEBERG_INVALID_METADATA; +import static io.trino.plugin.iceberg.IcebergTableName.isMaterializedViewStorage; +import static io.trino.plugin.iceberg.IcebergTableName.tableNameFrom; +import static io.trino.plugin.iceberg.catalog.glue.GlueIcebergUtil.getMaterializedViewTableInput; import static io.trino.plugin.iceberg.catalog.glue.GlueIcebergUtil.getTableInput; import static java.lang.String.format; import static java.util.Objects.requireNonNull; @@ -90,17 +95,25 @@ protected GlueIcebergTableOperations( @Override protected String getRefreshedLocation(boolean invalidateCaches) { - Table table = getTable(invalidateCaches); + boolean isMaterializedViewStorageTable = isMaterializedViewStorage(tableName); + + Table table; + if (isMaterializedViewStorageTable) { + table = getTable(database, tableNameFrom(tableName), invalidateCaches); + } + else { + table = getTable(database, tableName, invalidateCaches); + } glueVersionId = table.getVersionId(); String tableType = getTableType(table); Map parameters = getTableParameters(table); - if (isTrinoView(tableType, parameters) || isTrinoMaterializedView(tableType, parameters)) { + if (!isMaterializedViewStorageTable && (isTrinoView(tableType, parameters) || isTrinoMaterializedView(tableType, parameters))) { // this is a Hive view or Trino/Presto view, or Trino materialized view, hence not a table // TODO table operations should not be constructed for views (remove exception-driven code path) throw new TableNotFoundException(getSchemaTableName()); } - if (!isIcebergTable(parameters)) { + if (!isMaterializedViewStorageTable && !isIcebergTable(parameters)) { throw new UnknownTableTypeException(getSchemaTableName()); } @@ -138,15 +151,43 @@ protected void commitNewTable(TableMetadata metadata) @Override protected void commitToExistingTable(TableMetadata base, TableMetadata metadata) { - String newMetadataLocation = writeNewMetadata(metadata, version.orElseThrow() + 1); - TableInput tableInput = getTableInput( - typeManager, - tableName, - owner, + commitTableUpdate( + getTable(database, tableName, false), + metadata, + (table, newMetadataLocation) -> + getTableInput( + typeManager, + tableName, + owner, + metadata, + newMetadataLocation, + ImmutableMap.of(PREVIOUS_METADATA_LOCATION_PROP, currentMetadataLocation), + cacheTableMetadata)); + } + + @Override + protected void commitMaterializedViewRefresh(TableMetadata base, TableMetadata metadata) + { + commitTableUpdate( + getTable(database, tableNameFrom(tableName), false), metadata, - newMetadataLocation, - ImmutableMap.of(PREVIOUS_METADATA_LOCATION_PROP, currentMetadataLocation), - cacheTableMetadata); + (table, newMetadataLocation) -> { + Map parameters = new HashMap<>(getTableParameters(table)); + parameters.put(METADATA_LOCATION_PROP, newMetadataLocation); + parameters.put(PREVIOUS_METADATA_LOCATION_PROP, currentMetadataLocation); + + return getMaterializedViewTableInput( + table.getName(), + table.getViewOriginalText(), + table.getOwner(), + parameters); + }); + } + + private void commitTableUpdate(Table table, TableMetadata metadata, BiFunction tableUpdateFunction) + { + String newMetadataLocation = writeNewMetadata(metadata, version.orElseThrow() + 1); + TableInput tableInput = tableUpdateFunction.apply(table, newMetadataLocation); UpdateTableRequest updateTableRequest = new UpdateTableRequest() .withDatabaseName(database) @@ -171,7 +212,7 @@ protected void commitToExistingTable(TableMetadata base, TableMetadata metadata) shouldRefresh = true; } - private Table getTable(boolean invalidateCaches) + private Table getTable(String database, String tableName, boolean invalidateCaches) { return getGlueTable.get(new SchemaTableName(database, tableName), invalidateCaches); } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java index 3438a406ab1d..e9d07c5ba332 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java @@ -41,6 +41,8 @@ import dev.failsafe.RetryPolicy; import io.airlift.log.Logger; import io.trino.cache.EvictableCacheBuilder; +import io.trino.filesystem.Location; +import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.plugin.base.CatalogName; import io.trino.plugin.hive.SchemaAlreadyExistsException; @@ -52,6 +54,7 @@ import io.trino.plugin.iceberg.IcebergMetadata; import io.trino.plugin.iceberg.UnknownTableTypeException; import io.trino.plugin.iceberg.catalog.AbstractTrinoCatalog; +import io.trino.plugin.iceberg.catalog.IcebergTableOperations; import io.trino.plugin.iceberg.catalog.IcebergTableOperationsProvider; import io.trino.plugin.iceberg.fileio.ForwardingFileIo; import io.trino.spi.TrinoException; @@ -81,6 +84,7 @@ import org.apache.iceberg.TableMetadataParser; import org.apache.iceberg.TableOperations; import org.apache.iceberg.Transaction; +import org.apache.iceberg.exceptions.NotFoundException; import org.apache.iceberg.io.FileIO; import java.time.Duration; @@ -101,6 +105,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Throwables.throwIfInstanceOf; +import static com.google.common.base.Verify.verify; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static io.trino.cache.CacheUtils.uncheckedCacheGet; @@ -129,6 +134,7 @@ import static io.trino.plugin.iceberg.IcebergMaterializedViewDefinition.encodeMaterializedViewData; import static io.trino.plugin.iceberg.IcebergMaterializedViewDefinition.fromConnectorMaterializedViewDefinition; import static io.trino.plugin.iceberg.IcebergSchemaProperties.LOCATION_PROPERTY; +import static io.trino.plugin.iceberg.IcebergTableName.tableNameWithType; import static io.trino.plugin.iceberg.IcebergUtil.COLUMN_TRINO_NOT_NULL_PROPERTY; import static io.trino.plugin.iceberg.IcebergUtil.COLUMN_TRINO_TYPE_ID_PROPERTY; import static io.trino.plugin.iceberg.IcebergUtil.TRINO_TABLE_METADATA_INFO_VALID_FOR; @@ -137,6 +143,7 @@ import static io.trino.plugin.iceberg.IcebergUtil.getTableComment; import static io.trino.plugin.iceberg.IcebergUtil.quotedTableName; import static io.trino.plugin.iceberg.IcebergUtil.validateTableCanBeDropped; +import static io.trino.plugin.iceberg.TableType.MATERIALIZED_VIEW_STORAGE; import static io.trino.plugin.iceberg.TrinoMetricsReporter.TRINO_METRICS_REPORTER; import static io.trino.plugin.iceberg.catalog.glue.GlueIcebergUtil.getMaterializedViewTableInput; import static io.trino.plugin.iceberg.catalog.glue.GlueIcebergUtil.getTableInput; @@ -167,6 +174,7 @@ public class TrinoGlueCatalog private final Optional defaultSchemaLocation; private final AWSGlueAsync glueClient; private final GlueMetastoreStats stats; + private final boolean hideMaterializedViewStorageTable; private final Cache glueTableCache = EvictableCacheBuilder.newBuilder() // Even though this is query-scoped, this still needs to be bounded. information_schema queries can access large number of tables. @@ -174,7 +182,7 @@ public class TrinoGlueCatalog .build(); private final Map tableMetadataCache = new ConcurrentHashMap<>(); private final Map viewCache = new ConcurrentHashMap<>(); - private final Map materializedViewCache = new ConcurrentHashMap<>(); + private final Map materializedViewCache = new ConcurrentHashMap<>(); public TrinoGlueCatalog( CatalogName catalogName, @@ -186,9 +194,10 @@ public TrinoGlueCatalog( AWSGlueAsync glueClient, GlueMetastoreStats stats, Optional defaultSchemaLocation, - boolean useUniqueTableLocation) + boolean useUniqueTableLocation, + boolean hideMaterializedViewStorageTable) { - super(catalogName, typeManager, tableOperationsProvider, useUniqueTableLocation); + super(catalogName, typeManager, tableOperationsProvider, fileSystemFactory, useUniqueTableLocation); this.trinoVersion = requireNonNull(trinoVersion, "trinoVersion is null"); this.typeManager = requireNonNull(typeManager, "typeManager is null"); this.cacheTableMetadata = cacheTableMetadata; @@ -196,6 +205,7 @@ public TrinoGlueCatalog( this.glueClient = requireNonNull(glueClient, "glueClient is null"); this.stats = requireNonNull(stats, "stats is null"); this.defaultSchemaLocation = requireNonNull(defaultSchemaLocation, "defaultSchemaLocation is null"); + this.hideMaterializedViewStorageTable = hideMaterializedViewStorageTable; } @Override @@ -822,8 +832,7 @@ private Optional getTableAndCacheMetada String metadataLocation = parameters.get(METADATA_LOCATION_PROP); try { // Cache the TableMetadata while we have the Table retrieved anyway - ForwardingFileIo io = new ForwardingFileIo(fileSystemFactory.create(session)); - tableMetadataCache.put(schemaTableName, TableMetadataParser.read(io, io.newInputFile(metadataLocation))); + tableMetadataCache.put(schemaTableName, TableMetadataParser.read(new ForwardingFileIo(fileSystemFactory.create(session)), metadataLocation)); } catch (RuntimeException e) { LOG.warn(e, "Failed to cache table metadata from table at %s", metadataLocation); @@ -836,7 +845,9 @@ else if (isTrinoMaterializedView(tableType, parameters)) { try { ConnectorMaterializedViewDefinition materializedView = createMaterializedViewDefinition(session, schemaTableName, table); - materializedViewCache.put(schemaTableName, materializedView); + materializedViewCache.put(schemaTableName, new MaterializedViewData( + materializedView, + Optional.ofNullable(parameters.get(METADATA_LOCATION_PROP)))); } catch (RuntimeException e) { LOG.warn(e, "Failed to cache materialized view from %s", schemaTableName); @@ -1138,6 +1149,31 @@ public void createMaterializedView( } } + if (hideMaterializedViewStorageTable) { + Location storageMetadataLocation = createMaterializedViewStorage(session, viewName, definition); + TableInput materializedViewTableInput = getMaterializedViewTableInput( + viewName.getTableName(), + encodeMaterializedViewData(fromConnectorMaterializedViewDefinition(definition)), + session.getUser(), + createMaterializedViewProperties(session, storageMetadataLocation)); + if (existing.isPresent()) { + updateTable(viewName.getSchemaName(), materializedViewTableInput); + } + else { + createTable(viewName.getSchemaName(), materializedViewTableInput); + } + } + else { + createMaterializedViewWithStorageTable(session, viewName, definition, existing); + } + } + + private void createMaterializedViewWithStorageTable( + ConnectorSession session, + SchemaTableName viewName, + ConnectorMaterializedViewDefinition definition, + Optional existing) + { // Create the storage table SchemaTableName storageTable = createMaterializedViewStorageTable(session, viewName, definition); // Create a view indicating the storage table @@ -1189,17 +1225,17 @@ public void updateMaterializedViewColumnComment(ConnectorSession session, Schema definition.getPath(), definition.getProperties()); - updateMaterializedView(session, viewName, newDefinition); + updateMaterializedView(viewName, newDefinition); } - private void updateMaterializedView(ConnectorSession session, SchemaTableName viewName, ConnectorMaterializedViewDefinition newDefinition) + private void updateMaterializedView(SchemaTableName viewName, ConnectorMaterializedViewDefinition newDefinition) { + com.amazonaws.services.glue.model.Table table = getTable(viewName, false); TableInput materializedViewTableInput = getMaterializedViewTableInput( viewName.getTableName(), encodeMaterializedViewData(fromConnectorMaterializedViewDefinition(newDefinition)), - session.getUser(), - createMaterializedViewProperties(session, newDefinition.getStorageTable().orElseThrow().getSchemaTableName())); - + table.getOwner(), + getTableParameters(table)); try { updateTable(viewName.getSchemaName(), materializedViewTableInput); } @@ -1241,9 +1277,9 @@ private void dropStorageTable(ConnectorSession session, com.amazonaws.services.g @Override protected Optional doGetMaterializedView(ConnectorSession session, SchemaTableName viewName) { - ConnectorMaterializedViewDefinition materializedViewDefinition = materializedViewCache.get(viewName); - if (materializedViewDefinition != null) { - return Optional.of(materializedViewDefinition); + MaterializedViewData materializedViewData = materializedViewCache.get(viewName); + if (materializedViewData != null) { + return Optional.of(materializedViewData.connectorMaterializedViewDefinition); } if (tableMetadataCache.containsKey(viewName) || viewCache.containsKey(viewName)) { @@ -1271,35 +1307,124 @@ private ConnectorMaterializedViewDefinition createMaterializedViewDefinition( { Map materializedViewParameters = getTableParameters(table); String storageTable = materializedViewParameters.get(STORAGE_TABLE); - checkState(storageTable != null, "Storage table missing in definition of materialized view " + viewName); - String storageSchema = Optional.ofNullable(materializedViewParameters.get(STORAGE_SCHEMA)) - .orElse(viewName.getSchemaName()); - SchemaTableName storageTableName = new SchemaTableName(storageSchema, storageTable); + String storageMetadataLocation = materializedViewParameters.get(METADATA_LOCATION_PROP); + if ((storageTable == null) == (storageMetadataLocation == null)) { + throw new TrinoException(ICEBERG_BAD_DATA, "Materialized view should have exactly one of the %s properties set: %s".formatted( + ImmutableList.of(STORAGE_TABLE, METADATA_LOCATION_PROP), + materializedViewParameters)); + } + if (storageTable != null) { + String storageSchema = Optional.ofNullable(materializedViewParameters.get(STORAGE_SCHEMA)) + .orElse(viewName.getSchemaName()); + SchemaTableName storageTableName = new SchemaTableName(storageSchema, storageTable); + + Table icebergTable; + try { + icebergTable = loadTable(session, storageTableName); + } + catch (RuntimeException e) { + // The materialized view could be removed concurrently. This may manifest in a number of ways, e.g. + // - io.trino.spi.connector.TableNotFoundException + // - org.apache.iceberg.exceptions.NotFoundException when accessing manifest file + // - other failures when reading storage table's metadata files + // Retry, as we're catching broadly. + throw new MaterializedViewMayBeBeingRemovedException(e); + } + + String viewOriginalText = table.getViewOriginalText(); + if (viewOriginalText == null) { + throw new TrinoException(ICEBERG_BAD_DATA, "Materialized view did not have original text " + viewName); + } + return getMaterializedViewDefinition( + icebergTable, + Optional.ofNullable(table.getOwner()), + viewOriginalText, + storageTableName); + } + + SchemaTableName storageTableName = new SchemaTableName(viewName.getSchemaName(), tableNameWithType(viewName.getTableName(), MATERIALIZED_VIEW_STORAGE)); Table icebergTable; try { - icebergTable = loadTable(session, storageTableName); + TableMetadata metadata = getMaterializedViewTableMetadata(session, storageTableName, storageMetadataLocation); + IcebergTableOperations operations = tableOperationsProvider.createTableOperations( + this, + session, + storageTableName.getSchemaName(), + storageTableName.getTableName(), + Optional.empty(), + Optional.empty()); + operations.initializeFromMetadata(metadata); + icebergTable = new BaseTable(operations, quotedTableName(storageTableName), TRINO_METRICS_REPORTER); } catch (RuntimeException e) { // The materialized view could be removed concurrently. This may manifest in a number of ways, e.g. - // - io.trino.spi.connector.TableNotFoundException // - org.apache.iceberg.exceptions.NotFoundException when accessing manifest file // - other failures when reading storage table's metadata files // Retry, as we're catching broadly. throw new MaterializedViewMayBeBeingRemovedException(e); } - String viewOriginalText = table.getViewOriginalText(); - if (viewOriginalText == null) { - throw new TrinoException(ICEBERG_BAD_DATA, "Materialized view did not have original text " + viewName); - } return getMaterializedViewDefinition( icebergTable, Optional.ofNullable(table.getOwner()), - viewOriginalText, + table.getViewOriginalText(), storageTableName); } + @Override + public Optional getMaterializedViewStorageTable(ConnectorSession session, SchemaTableName viewName) + { + String storageMetadataLocation; + MaterializedViewData materializedViewData = materializedViewCache.get(viewName); + if (materializedViewData == null) { + Optional maybeTable = getTableAndCacheMetadata(session, viewName); + if (maybeTable.isEmpty()) { + return Optional.empty(); + } + com.amazonaws.services.glue.model.Table materializedView = maybeTable.get(); + verify(isTrinoMaterializedView(getTableType(materializedView), getTableParameters(materializedView)), + "getMaterializedViewStorageTable received a table, not a materialized view"); + + // TODO getTableAndCacheMetadata saved the value in materializedViewCache, so we could just use that, except when conversion fails + storageMetadataLocation = getTableParameters(materializedView).get(METADATA_LOCATION_PROP); + checkState(storageMetadataLocation != null, "Storage location missing in definition of materialized view " + materializedView.getName()); + } + else { + storageMetadataLocation = materializedViewData.storageMetadataLocation + .orElseThrow(() -> new IllegalStateException("Storage location not defined for materialized view " + viewName)); + } + + SchemaTableName storageTableName = new SchemaTableName(viewName.getSchemaName(), tableNameWithType(viewName.getTableName(), MATERIALIZED_VIEW_STORAGE)); + IcebergTableOperations operations = tableOperationsProvider.createTableOperations( + this, + session, + storageTableName.getSchemaName(), + storageTableName.getTableName(), + Optional.empty(), + Optional.empty()); + + try { + TableMetadata metadata = getMaterializedViewTableMetadata(session, storageTableName, storageMetadataLocation); + operations.initializeFromMetadata(metadata); + return Optional.of(new BaseTable(operations, quotedTableName(storageTableName), TRINO_METRICS_REPORTER)); + } + catch (NotFoundException e) { + // Removed during reading + return Optional.empty(); + } + } + + private TableMetadata getMaterializedViewTableMetadata(ConnectorSession session, SchemaTableName storageTableName, String storageMetadataLocation) + { + requireNonNull(storageTableName, "storageTableName is null"); + requireNonNull(storageMetadataLocation, "storageMetadataLocation is null"); + return tableMetadataCache.computeIfAbsent(storageTableName, ignored -> { + TrinoFileSystem fileSystem = fileSystemFactory.create(session); + return TableMetadataParser.read(new ForwardingFileIo(fileSystem), storageMetadataLocation); + }); + } + @Override public void renameMaterializedView(ConnectorSession session, SchemaTableName source, SchemaTableName target) { @@ -1424,4 +1549,15 @@ private void deleteTable(String schema, String table) .withDatabaseName(schema) .withName(table))); } + + private record MaterializedViewData( + ConnectorMaterializedViewDefinition connectorMaterializedViewDefinition, + Optional storageMetadataLocation) + { + private MaterializedViewData + { + requireNonNull(connectorMaterializedViewDefinition, "connectorMaterializedViewDefinition is null"); + requireNonNull(storageMetadataLocation, "storageMetadataLocation is null"); + } + } } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalogFactory.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalogFactory.java index 054c1bbc1c79..2a76128e0290 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalogFactory.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalogFactory.java @@ -45,6 +45,7 @@ public class TrinoGlueCatalogFactory private final Optional defaultSchemaLocation; private final AWSGlueAsync glueClient; private final boolean isUniqueTableLocation; + private final boolean hideMaterializedViewStorageTable; private final GlueMetastoreStats stats; @Inject @@ -69,6 +70,7 @@ public TrinoGlueCatalogFactory( this.defaultSchemaLocation = glueConfig.getDefaultWarehouseDir(); this.glueClient = requireNonNull(glueClient, "glueClient is null"); this.isUniqueTableLocation = icebergConfig.isUniqueTableLocation(); + this.hideMaterializedViewStorageTable = icebergConfig.isHideMaterializedViewStorageTable(); this.stats = requireNonNull(stats, "stats is null"); } @@ -92,6 +94,7 @@ public TrinoCatalog create(ConnectorIdentity identity) glueClient, stats, defaultSchemaLocation, - isUniqueTableLocation); + isUniqueTableLocation, + hideMaterializedViewStorageTable); } } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/AbstractMetastoreTableOperations.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/AbstractMetastoreTableOperations.java index 39b1f8bf23dc..9769819b3d3b 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/AbstractMetastoreTableOperations.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/AbstractMetastoreTableOperations.java @@ -38,6 +38,8 @@ import static io.trino.plugin.hive.metastore.PrincipalPrivileges.NO_PRIVILEGES; import static io.trino.plugin.hive.util.HiveUtil.isIcebergTable; import static io.trino.plugin.iceberg.IcebergErrorCode.ICEBERG_INVALID_METADATA; +import static io.trino.plugin.iceberg.IcebergTableName.isMaterializedViewStorage; +import static io.trino.plugin.iceberg.IcebergTableName.tableNameFrom; import static java.lang.String.format; import static java.util.Locale.ENGLISH; import static java.util.Objects.requireNonNull; @@ -71,14 +73,23 @@ protected final String getRefreshedLocation(boolean invalidateCaches) if (invalidateCaches) { metastore.invalidateTable(database, tableName); } - Table table = getTable(); - if (isTrinoView(table) || isTrinoMaterializedView(table)) { + boolean isMaterializedViewStorageTable = isMaterializedViewStorage(tableName); + + Table table; + if (isMaterializedViewStorageTable) { + table = getTable(database, tableNameFrom(tableName)); + } + else { + table = getTable(); + } + + if (!isMaterializedViewStorageTable && (isTrinoView(table) || isTrinoMaterializedView(table))) { // this is a Hive view or Trino/Presto view, or Trino materialized view, hence not a table // TODO table operations should not be constructed for views (remove exception-driven code path) throw new TableNotFoundException(getSchemaTableName()); } - if (!isIcebergTable(table)) { + if (!isMaterializedViewStorageTable && !isIcebergTable(table)) { throw new UnknownTableTypeException(getSchemaTableName()); } @@ -132,6 +143,11 @@ protected Table.Builder updateMetastoreTable(Table.Builder builder, TableMetadat } protected Table getTable() + { + return getTable(database, tableName); + } + + protected Table getTable(String database, String tableName) { return metastore.getTable(database, tableName) .orElseThrow(() -> new TableNotFoundException(getSchemaTableName())); diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/HiveMetastoreTableOperations.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/HiveMetastoreTableOperations.java index 5a5d4dead2b1..9a2a60e8958a 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/HiveMetastoreTableOperations.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/HiveMetastoreTableOperations.java @@ -29,13 +29,16 @@ import org.apache.iceberg.io.FileIO; import java.util.Optional; +import java.util.function.BiFunction; import static com.google.common.base.Preconditions.checkState; import static io.trino.plugin.hive.metastore.PrincipalPrivileges.NO_PRIVILEGES; import static io.trino.plugin.hive.metastore.thrift.ThriftMetastoreUtil.fromMetastoreApiTable; +import static io.trino.plugin.iceberg.IcebergTableName.tableNameFrom; import static io.trino.plugin.iceberg.IcebergUtil.fixBrokenMetadataLocation; import static java.util.Objects.requireNonNull; import static org.apache.iceberg.BaseMetastoreTableOperations.METADATA_LOCATION_PROP; +import static org.apache.iceberg.BaseMetastoreTableOperations.PREVIOUS_METADATA_LOCATION_PROP; @NotThreadSafe public class HiveMetastoreTableOperations @@ -61,15 +64,33 @@ public HiveMetastoreTableOperations( @Override protected void commitToExistingTable(TableMetadata base, TableMetadata metadata) { - String newMetadataLocation = writeNewMetadata(metadata, version.orElseThrow() + 1); + Table currentTable = getTable(); + commitTableUpdate(currentTable, metadata, (table, newMetadataLocation) -> Table.builder(table) + .apply(builder -> updateMetastoreTable(builder, metadata, newMetadataLocation, Optional.of(currentMetadataLocation))) + .build()); + } + + @Override + protected final void commitMaterializedViewRefresh(TableMetadata base, TableMetadata metadata) + { + Table materializedView = getTable(database, tableNameFrom(tableName)); + commitTableUpdate(materializedView, metadata, (table, newMetadataLocation) -> Table.builder(table) + .apply(builder -> builder + .setParameter(METADATA_LOCATION_PROP, newMetadataLocation) + .setParameter(PREVIOUS_METADATA_LOCATION_PROP, currentMetadataLocation)) + .build()); + } + private void commitTableUpdate(Table table, TableMetadata metadata, BiFunction tableUpdateFunction) + { + String newMetadataLocation = writeNewMetadata(metadata, version.orElseThrow() + 1); long lockId = thriftMetastore.acquireTableExclusiveLock( new AcidTransactionOwner(session.getUser()), session.getQueryId(), - database, - tableName); + table.getDatabaseName(), + table.getTableName()); try { - Table currentTable = fromMetastoreApiTable(thriftMetastore.getTable(database, tableName) + Table currentTable = fromMetastoreApiTable(thriftMetastore.getTable(database, table.getTableName()) .orElseThrow(() -> new TableNotFoundException(getSchemaTableName()))); checkState(currentMetadataLocation != null, "No current metadata location for existing table"); @@ -79,14 +100,12 @@ protected void commitToExistingTable(TableMetadata base, TableMetadata metadata) currentMetadataLocation, metadataLocation, getSchemaTableName()); } - Table table = Table.builder(currentTable) - .apply(builder -> updateMetastoreTable(builder, metadata, newMetadataLocation, Optional.of(currentMetadataLocation))) - .build(); + Table updatedTable = tableUpdateFunction.apply(table, newMetadataLocation); // todo privileges should not be replaced for an alter PrincipalPrivileges privileges = table.getOwner().map(MetastoreUtil::buildInitialPrivilegeSet).orElse(NO_PRIVILEGES); try { - metastore.replaceTable(database, tableName, table, privileges); + metastore.replaceTable(table.getDatabaseName(), table.getTableName(), updatedTable, privileges); } catch (RuntimeException e) { // Cannot determine whether the `replaceTable` operation was successful, @@ -103,7 +122,7 @@ protected void commitToExistingTable(TableMetadata base, TableMetadata metadata) // So, that underlying iceberg API will not do the metadata cleanup, otherwise table will be in unusable state. // If configured and supported, the unreleased lock will be automatically released by the metastore after not hearing a heartbeat for a while, // or otherwise it might need to be manually deleted from the metastore backend storage. - log.error(e, "Failed to release lock %s when committing to table %s", lockId, tableName); + log.error(e, "Failed to release lock %s when committing to table %s", lockId, table.getTableName()); } } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java index e010255898c9..394568b4e67f 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java @@ -18,6 +18,7 @@ import com.google.common.collect.ImmutableSet; import io.airlift.log.Logger; import io.trino.filesystem.Location; +import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.plugin.base.CatalogName; import io.trino.plugin.hive.HiveSchemaProperties; @@ -29,9 +30,12 @@ import io.trino.plugin.hive.metastore.PrincipalPrivileges; import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; import io.trino.plugin.hive.util.HiveUtil; +import io.trino.plugin.iceberg.IcebergTableName; import io.trino.plugin.iceberg.UnknownTableTypeException; import io.trino.plugin.iceberg.catalog.AbstractTrinoCatalog; +import io.trino.plugin.iceberg.catalog.IcebergTableOperations; import io.trino.plugin.iceberg.catalog.IcebergTableOperationsProvider; +import io.trino.plugin.iceberg.fileio.ForwardingFileIo; import io.trino.spi.TrinoException; import io.trino.spi.connector.CatalogSchemaTableName; import io.trino.spi.connector.ColumnMetadata; @@ -53,7 +57,9 @@ import org.apache.iceberg.SortOrder; import org.apache.iceberg.Table; import org.apache.iceberg.TableMetadata; +import org.apache.iceberg.TableMetadataParser; import org.apache.iceberg.Transaction; +import org.apache.iceberg.exceptions.NotFoundException; import java.io.IOException; import java.util.Iterator; @@ -68,6 +74,7 @@ import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Verify.verify; import static com.google.common.collect.ImmutableList.toImmutableList; import static io.trino.filesystem.Locations.appendPath; import static io.trino.plugin.hive.HiveErrorCode.HIVE_DATABASE_LOCATION_ERROR; @@ -86,13 +93,17 @@ import static io.trino.plugin.hive.metastore.StorageFormat.VIEW_STORAGE_FORMAT; import static io.trino.plugin.hive.util.HiveUtil.isHiveSystemSchema; import static io.trino.plugin.hive.util.HiveUtil.isIcebergTable; +import static io.trino.plugin.iceberg.IcebergErrorCode.ICEBERG_BAD_DATA; import static io.trino.plugin.iceberg.IcebergMaterializedViewAdditionalProperties.STORAGE_SCHEMA; import static io.trino.plugin.iceberg.IcebergMaterializedViewDefinition.encodeMaterializedViewData; import static io.trino.plugin.iceberg.IcebergMaterializedViewDefinition.fromConnectorMaterializedViewDefinition; import static io.trino.plugin.iceberg.IcebergSchemaProperties.LOCATION_PROPERTY; import static io.trino.plugin.iceberg.IcebergUtil.getIcebergTableWithMetadata; import static io.trino.plugin.iceberg.IcebergUtil.loadIcebergTable; +import static io.trino.plugin.iceberg.IcebergUtil.quotedTableName; import static io.trino.plugin.iceberg.IcebergUtil.validateTableCanBeDropped; +import static io.trino.plugin.iceberg.TableType.MATERIALIZED_VIEW_STORAGE; +import static io.trino.plugin.iceberg.TrinoMetricsReporter.TRINO_METRICS_REPORTER; import static io.trino.plugin.iceberg.catalog.AbstractIcebergTableOperations.ICEBERG_METASTORE_STORAGE_FORMAT; import static io.trino.plugin.iceberg.catalog.AbstractIcebergTableOperations.toHiveColumns; import static io.trino.spi.StandardErrorCode.ALREADY_EXISTS; @@ -122,6 +133,7 @@ public class TrinoHiveCatalog private final TrinoFileSystemFactory fileSystemFactory; private final boolean isUsingSystemSecurity; private final boolean deleteSchemaLocationsFallback; + private final boolean hideMaterializedViewStorageTable; private final Map tableMetadataCache = new ConcurrentHashMap<>(); @@ -134,14 +146,16 @@ public TrinoHiveCatalog( IcebergTableOperationsProvider tableOperationsProvider, boolean useUniqueTableLocation, boolean isUsingSystemSecurity, - boolean deleteSchemaLocationsFallback) + boolean deleteSchemaLocationsFallback, + boolean hideMaterializedViewStorageTable) { - super(catalogName, typeManager, tableOperationsProvider, useUniqueTableLocation); + super(catalogName, typeManager, tableOperationsProvider, fileSystemFactory, useUniqueTableLocation); this.metastore = requireNonNull(metastore, "metastore is null"); this.trinoViewHiveMetastore = requireNonNull(trinoViewHiveMetastore, "trinoViewHiveMetastore is null"); this.fileSystemFactory = requireNonNull(fileSystemFactory, "fileSystemFactory is null"); this.isUsingSystemSecurity = isUsingSystemSecurity; this.deleteSchemaLocationsFallback = deleteSchemaLocationsFallback; + this.hideMaterializedViewStorageTable = hideMaterializedViewStorageTable; } public CachingHiveMetastore getMetastore() @@ -539,6 +553,45 @@ public void createMaterializedView( } } + if (hideMaterializedViewStorageTable) { + Location storageMetadataLocation = createMaterializedViewStorage(session, viewName, definition); + + Map viewProperties = createMaterializedViewProperties(session, storageMetadataLocation); + Column dummyColumn = new Column("dummy", HIVE_STRING, Optional.empty(), ImmutableMap.of()); + io.trino.plugin.hive.metastore.Table.Builder tableBuilder = io.trino.plugin.hive.metastore.Table.builder() + .setDatabaseName(viewName.getSchemaName()) + .setTableName(viewName.getTableName()) + .setOwner(isUsingSystemSecurity ? Optional.empty() : Optional.of(session.getUser())) + .setTableType(VIRTUAL_VIEW.name()) + .setDataColumns(ImmutableList.of(dummyColumn)) + .setPartitionColumns(ImmutableList.of()) + .setParameters(viewProperties) + .withStorage(storage -> storage.setStorageFormat(VIEW_STORAGE_FORMAT)) + .withStorage(storage -> storage.setLocation("")) + .setViewOriginalText(Optional.of( + encodeMaterializedViewData(fromConnectorMaterializedViewDefinition(definition)))) + .setViewExpandedText(Optional.of("/* " + ICEBERG_MATERIALIZED_VIEW_COMMENT + " */")); + io.trino.plugin.hive.metastore.Table table = tableBuilder.build(); + PrincipalPrivileges principalPrivileges = isUsingSystemSecurity ? NO_PRIVILEGES : buildInitialPrivilegeSet(session.getUser()); + + if (existing.isPresent()) { + metastore.replaceTable(viewName.getSchemaName(), viewName.getTableName(), table, principalPrivileges); + } + else { + metastore.createTable(table, principalPrivileges); + } + } + else { + createMaterializedViewWithStorageTable(session, viewName, definition, existing); + } + } + + private void createMaterializedViewWithStorageTable( + ConnectorSession session, + SchemaTableName viewName, + ConnectorMaterializedViewDefinition definition, + Optional existing) + { SchemaTableName storageTable = createMaterializedViewStorageTable(session, viewName, definition); // Create a view indicating the storage table @@ -639,6 +692,20 @@ public void dropMaterializedView(ConnectorSession session, SchemaTableName viewN log.warn(e, "Failed to drop storage table '%s.%s' for materialized view '%s'", storageSchema, storageTableName, viewName); } } + + String storageMetadataLocation = view.getParameters().get(METADATA_LOCATION_PROP); + checkState(storageMetadataLocation != null, "Storage location missing in definition of materialized view " + viewName); + + TrinoFileSystem fileSystem = fileSystemFactory.create(session); + TableMetadata metadata = TableMetadataParser.read(new ForwardingFileIo(fileSystem), storageMetadataLocation); + String storageLocation = metadata.location(); + try { + fileSystem.deleteDirectory(Location.of(storageLocation)); + } + catch (IOException e) { + log.warn(e, "Failed to delete storage location '%s' for materialized view '%s'", storageLocation, viewName); + } + metastore.dropTable(viewName.getSchemaName(), viewName.getTableName(), true); } @@ -650,38 +717,116 @@ protected Optional doGetMaterializedView(Co return Optional.empty(); } - io.trino.plugin.hive.metastore.Table table = tableOptional.get(); - if (!isTrinoMaterializedView(table.getTableType(), table.getParameters())) { + io.trino.plugin.hive.metastore.Table materializedView = tableOptional.get(); + if (!isTrinoMaterializedView(materializedView.getTableType(), materializedView.getParameters())) { return Optional.empty(); } - io.trino.plugin.hive.metastore.Table materializedView = tableOptional.get(); String storageTable = materializedView.getParameters().get(STORAGE_TABLE); - checkState(storageTable != null, "Storage table missing in definition of materialized view " + viewName); - String storageSchema = Optional.ofNullable(materializedView.getParameters().get(STORAGE_SCHEMA)) - .orElse(viewName.getSchemaName()); - SchemaTableName storageTableName = new SchemaTableName(storageSchema, storageTable); + String storageMetadataLocation = materializedView.getParameters().get(METADATA_LOCATION_PROP); + if ((storageTable == null) == (storageMetadataLocation == null)) { + throw new TrinoException(ICEBERG_BAD_DATA, "Materialized view should have exactly one of the %s properties set: %s".formatted( + ImmutableList.of(STORAGE_TABLE, METADATA_LOCATION_PROP), + materializedView.getParameters())); + } + + if (storageTable != null) { + String storageSchema = Optional.ofNullable(materializedView.getParameters().get(STORAGE_SCHEMA)) + .orElse(viewName.getSchemaName()); + SchemaTableName storageTableName = new SchemaTableName(storageSchema, storageTable); + + Table icebergTable; + try { + icebergTable = loadTable(session, storageTableName); + } + catch (RuntimeException e) { + // The materialized view could be removed concurrently. This may manifest in a number of ways, e.g. + // - io.trino.spi.connector.TableNotFoundException + // - org.apache.iceberg.exceptions.NotFoundException when accessing manifest file + // - other failures when reading storage table's metadata files + // Retry, as we're catching broadly. + metastore.invalidateTable(viewName.getSchemaName(), viewName.getTableName()); + metastore.invalidateTable(storageSchema, storageTable); + throw new MaterializedViewMayBeBeingRemovedException(e); + } + return Optional.of(getMaterializedViewDefinition( + icebergTable, + materializedView.getOwner(), + materializedView.getViewOriginalText() + .orElseThrow(() -> new TrinoException(HIVE_INVALID_METADATA, "No view original text: " + viewName)), + storageTableName)); + } - Table icebergTable; + SchemaTableName storageTableName = new SchemaTableName(viewName.getSchemaName(), IcebergTableName.tableNameWithType(viewName.getTableName(), MATERIALIZED_VIEW_STORAGE)); + IcebergTableOperations operations = tableOperationsProvider.createTableOperations( + this, + session, + storageTableName.getSchemaName(), + storageTableName.getTableName(), + Optional.empty(), + Optional.empty()); try { - icebergTable = loadTable(session, storageTableName); + TableMetadata metadata = getMaterializedViewTableMetadata(session, storageTableName, materializedView); + operations.initializeFromMetadata(metadata); + Table icebergTable = new BaseTable(operations, quotedTableName(storageTableName), TRINO_METRICS_REPORTER); + + return Optional.of(getMaterializedViewDefinition( + icebergTable, + materializedView.getOwner(), + materializedView.getViewOriginalText() + .orElseThrow(() -> new TrinoException(HIVE_INVALID_METADATA, "No view original text: " + viewName)), + storageTableName)); } catch (RuntimeException e) { // The materialized view could be removed concurrently. This may manifest in a number of ways, e.g. - // - io.trino.spi.connector.TableNotFoundException // - org.apache.iceberg.exceptions.NotFoundException when accessing manifest file // - other failures when reading storage table's metadata files // Retry, as we're catching broadly. metastore.invalidateTable(viewName.getSchemaName(), viewName.getTableName()); - metastore.invalidateTable(storageSchema, storageTable); throw new MaterializedViewMayBeBeingRemovedException(e); } - return Optional.of(getMaterializedViewDefinition( - icebergTable, - table.getOwner(), - materializedView.getViewOriginalText() - .orElseThrow(() -> new TrinoException(HIVE_INVALID_METADATA, "No view original text: " + viewName)), - storageTableName)); + } + + @Override + public Optional getMaterializedViewStorageTable(ConnectorSession session, SchemaTableName viewName) + { + Optional tableOptional = metastore.getTable(viewName.getSchemaName(), viewName.getTableName()); + if (tableOptional.isEmpty()) { + return Optional.empty(); + } + + io.trino.plugin.hive.metastore.Table materializedView = tableOptional.get(); + verify(isTrinoMaterializedView(materializedView.getTableType(), materializedView.getParameters()), + "getMaterializedViewStorageTable received a table, not a materialized view"); + + SchemaTableName storageTableName = new SchemaTableName(viewName.getSchemaName(), IcebergTableName.tableNameWithType(viewName.getTableName(), MATERIALIZED_VIEW_STORAGE)); + IcebergTableOperations operations = tableOperationsProvider.createTableOperations( + this, + session, + storageTableName.getSchemaName(), + storageTableName.getTableName(), + Optional.empty(), + Optional.empty()); + + try { + TableMetadata metadata = getMaterializedViewTableMetadata(session, storageTableName, materializedView); + operations.initializeFromMetadata(metadata); + return Optional.of(new BaseTable(operations, quotedTableName(storageTableName), TRINO_METRICS_REPORTER)); + } + catch (NotFoundException e) { + // Removed during reading + return Optional.empty(); + } + } + + private TableMetadata getMaterializedViewTableMetadata(ConnectorSession session, SchemaTableName storageTableName, io.trino.plugin.hive.metastore.Table materializedView) + { + return tableMetadataCache.computeIfAbsent(storageTableName, ignored -> { + String storageMetadataLocation = materializedView.getParameters().get(METADATA_LOCATION_PROP); + checkState(storageMetadataLocation != null, "Storage location missing in definition of materialized view " + materializedView.getTableName()); + TrinoFileSystem fileSystem = fileSystemFactory.create(session); + return TableMetadataParser.read(new ForwardingFileIo(fileSystem), storageMetadataLocation); + }); } @Override diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalogFactory.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalogFactory.java index ae5c8f01857e..252f0cccb087 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalogFactory.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalogFactory.java @@ -47,6 +47,7 @@ public class TrinoHiveCatalogFactory private final boolean isUniqueTableLocation; private final boolean isUsingSystemSecurity; private final boolean deleteSchemaLocationsFallback; + private final boolean hideMaterializedViewStorageTable; @Inject public TrinoHiveCatalogFactory( @@ -68,6 +69,7 @@ public TrinoHiveCatalogFactory( this.isUniqueTableLocation = config.isUniqueTableLocation(); this.isUsingSystemSecurity = securityConfig.getSecuritySystem() == SYSTEM; this.deleteSchemaLocationsFallback = config.isDeleteSchemaLocationsFallback(); + this.hideMaterializedViewStorageTable = config.isHideMaterializedViewStorageTable(); } @Override @@ -83,6 +85,7 @@ public TrinoCatalog create(ConnectorIdentity identity) tableOperationsProvider, isUniqueTableLocation, isUsingSystemSecurity, - deleteSchemaLocationsFallback); + deleteSchemaLocationsFallback, + hideMaterializedViewStorageTable); } } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/jdbc/IcebergJdbcTableOperations.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/jdbc/IcebergJdbcTableOperations.java index 1b7e3bda1afa..458435828543 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/jdbc/IcebergJdbcTableOperations.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/jdbc/IcebergJdbcTableOperations.java @@ -67,4 +67,10 @@ protected void commitToExistingTable(TableMetadata base, TableMetadata metadata) jdbcClient.alterTable(database, tableName, newMetadataLocation, currentMetadataLocation); shouldRefresh = true; } + + @Override + protected void commitMaterializedViewRefresh(TableMetadata base, TableMetadata metadata) + { + throw new UnsupportedOperationException(); + } } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/jdbc/TrinoJdbcCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/jdbc/TrinoJdbcCatalog.java index 5d04d469baba..52af89f0fbe0 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/jdbc/TrinoJdbcCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/jdbc/TrinoJdbcCatalog.java @@ -91,7 +91,7 @@ public TrinoJdbcCatalog( boolean useUniqueTableLocation, String defaultWarehouseDir) { - super(catalogName, typeManager, tableOperationsProvider, useUniqueTableLocation); + super(catalogName, typeManager, tableOperationsProvider, fileSystemFactory, useUniqueTableLocation); this.jdbcCatalog = requireNonNull(jdbcCatalog, "jdbcCatalog is null"); this.jdbcClient = requireNonNull(jdbcClient, "jdbcClient is null"); this.fileSystemFactory = requireNonNull(fileSystemFactory, "fileSystemFactory is null"); @@ -405,6 +405,12 @@ protected Optional doGetMaterializedView(Co return Optional.empty(); } + @Override + public Optional getMaterializedViewStorageTable(ConnectorSession session, SchemaTableName viewName) + { + throw new TrinoException(NOT_SUPPORTED, "The Iceberg JDBC catalog does not support materialized views"); + } + @Override public void createMaterializedView(ConnectorSession session, SchemaTableName schemaViewName, ConnectorMaterializedViewDefinition definition, boolean replace, boolean ignoreExisting) { diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/IcebergNessieTableOperations.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/IcebergNessieTableOperations.java index 75be83f8293d..602e526a213e 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/IcebergNessieTableOperations.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/IcebergNessieTableOperations.java @@ -126,6 +126,12 @@ protected void commitToExistingTable(TableMetadata base, TableMetadata metadata) shouldRefresh = true; } + @Override + protected void commitMaterializedViewRefresh(TableMetadata base, TableMetadata metadata) + { + throw new UnsupportedOperationException(); + } + private static ContentKey toKey(SchemaTableName tableName) { return ContentKey.of(Namespace.parse(tableName.getSchemaName()), tableName.getTableName()); diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/TrinoNessieCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/TrinoNessieCatalog.java index 7de7de1c58d1..6d5badaa8380 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/TrinoNessieCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/TrinoNessieCatalog.java @@ -81,7 +81,7 @@ public TrinoNessieCatalog( String warehouseLocation, boolean useUniqueTableLocation) { - super(catalogName, typeManager, tableOperationsProvider, useUniqueTableLocation); + super(catalogName, typeManager, tableOperationsProvider, fileSystemFactory, useUniqueTableLocation); this.fileSystemFactory = requireNonNull(fileSystemFactory, "fileSystemFactory is null"); this.warehouseLocation = requireNonNull(warehouseLocation, "warehouseLocation is null"); this.nessieClient = requireNonNull(nessieClient, "nessieClient is null"); @@ -396,6 +396,12 @@ public Optional getMaterializedView(Connect return Optional.empty(); } + @Override + public Optional getMaterializedViewStorageTable(ConnectorSession session, SchemaTableName viewName) + { + throw new TrinoException(NOT_SUPPORTED, "The Iceberg Nessie catalog does not support materialized views"); + } + @Override protected Optional doGetMaterializedView(ConnectorSession session, SchemaTableName schemaViewName) { diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/rest/TrinoRestCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/rest/TrinoRestCatalog.java index a2083f33e362..74927abd75e9 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/rest/TrinoRestCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/rest/TrinoRestCatalog.java @@ -435,6 +435,12 @@ public Optional getMaterializedView(Connect return Optional.empty(); } + @Override + public Optional getMaterializedViewStorageTable(ConnectorSession session, SchemaTableName viewName) + { + throw new TrinoException(NOT_SUPPORTED, "The Iceberg REST catalog does not support materialized views"); + } + @Override public void renameMaterializedView(ConnectorSession session, SchemaTableName source, SchemaTableName target) { diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java index 8ccf374868a9..e139895358fd 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java @@ -6702,30 +6702,6 @@ public void testSnapshotSummariesHaveTrinoQueryIdFormatV2() "WHEN MATCHED THEN UPDATE SET b = t.b * 50", tableName, sourceTableName))); } - @Test - public void testMaterializedViewSnapshotSummariesHaveTrinoQueryId() - { - String matViewName = "test_materialized_view_snapshot_query_ids" + randomNameSuffix(); - String sourceTableName = "test_source_table_for_mat_view" + randomNameSuffix(); - assertUpdate(format("CREATE TABLE %s (a bigint, b bigint)", sourceTableName)); - assertUpdate(format("INSERT INTO %s VALUES (1, 1), (1, 4), (2, 2)", sourceTableName), 3); - - // create a materialized view - QueryId matViewCreateQueryId = getDistributedQueryRunner() - .executeWithQueryId(getSession(), format("CREATE MATERIALIZED VIEW %s WITH (partitioning = ARRAY['a']) AS SELECT * FROM %s", matViewName, sourceTableName)) - .getQueryId(); - - // fetch the underlying storage table name so we can inspect its snapshot summary after the REFRESH - // running queries against "materialized_view$snapshots" is not supported - String storageTable = (String) getDistributedQueryRunner() - .execute(getSession(), format("SELECT storage_table FROM system.metadata.materialized_views WHERE name = '%s'", matViewName)) - .getOnlyValue(); - - assertQueryIdStored(storageTable, matViewCreateQueryId); - - assertQueryIdStored(storageTable, executeWithQueryId(format("REFRESH MATERIALIZED VIEW %s", matViewName))); - } - @Override protected OptionalInt maxTableNameLength() { diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergMaterializedViewTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergMaterializedViewTest.java index 73eaa253a4ac..7c0d00556143 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergMaterializedViewTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergMaterializedViewTest.java @@ -15,32 +15,32 @@ import com.google.common.collect.ImmutableSet; import io.trino.Session; -import io.trino.metadata.MaterializedViewDefinition; -import io.trino.metadata.QualifiedObjectName; -import io.trino.spi.connector.SchemaTableName; +import io.trino.filesystem.Location; +import io.trino.filesystem.local.LocalFileSystem; +import io.trino.plugin.iceberg.fileio.ForwardingFileIo; +import io.trino.spi.QueryId; import io.trino.sql.tree.ExplainType; import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.MaterializedRow; -import io.trino.transaction.TransactionId; -import io.trino.transaction.TransactionManager; +import org.apache.iceberg.PartitionField; +import org.apache.iceberg.TableMetadata; +import org.apache.iceberg.TableMetadataParser; import org.assertj.core.api.Condition; import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import java.nio.file.Path; import java.util.Optional; import java.util.Set; import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static com.google.common.collect.Iterables.getOnlyElement; import static io.trino.SystemSessionProperties.LEGACY_MATERIALIZED_VIEW_GRACE_PERIOD; import static io.trino.testing.MaterializedResult.DEFAULT_PRECISION; -import static io.trino.testing.TestingAccessControlManager.TestingPrivilegeType.DELETE_TABLE; import static io.trino.testing.TestingAccessControlManager.TestingPrivilegeType.DROP_MATERIALIZED_VIEW; -import static io.trino.testing.TestingAccessControlManager.TestingPrivilegeType.INSERT_TABLE; import static io.trino.testing.TestingAccessControlManager.TestingPrivilegeType.REFRESH_MATERIALIZED_VIEW; import static io.trino.testing.TestingAccessControlManager.TestingPrivilegeType.RENAME_MATERIALIZED_VIEW; -import static io.trino.testing.TestingAccessControlManager.TestingPrivilegeType.SELECT_COLUMN; -import static io.trino.testing.TestingAccessControlManager.TestingPrivilegeType.UPDATE_TABLE; import static io.trino.testing.TestingAccessControlManager.privilege; import static io.trino.testing.TestingNames.randomNameSuffix; import static java.lang.String.format; @@ -52,10 +52,10 @@ public abstract class BaseIcebergMaterializedViewTest extends AbstractTestQueryFramework { - protected final String storageSchemaName = "testing_storage_schema_" + randomNameSuffix(); - protected abstract String getSchemaDirectory(); + protected abstract String getStorageMetadataLocation(String materializedViewName); + @BeforeClass public void setUp() { @@ -65,17 +65,13 @@ public void setUp() assertUpdate("CREATE TABLE base_table2 (_varchar VARCHAR, _bigint BIGINT, _date DATE) WITH (partitioning = ARRAY['_bigint', '_date'])"); assertUpdate("INSERT INTO base_table2 VALUES ('a', 0, DATE '2019-09-08'), ('a', 1, DATE '2019-09-08'), ('a', 0, DATE '2019-09-09')", 3); - - assertUpdate("CREATE SCHEMA " + storageSchemaName); } @Test public void testShowTables() { assertUpdate("CREATE MATERIALIZED VIEW materialized_view_show_tables_test AS SELECT * FROM base_table1"); - SchemaTableName storageTableName = getStorageTable("materialized_view_show_tables_test"); - - Set expectedTables = ImmutableSet.of("base_table1", "base_table2", "materialized_view_show_tables_test", storageTableName.getTableName()); + Set expectedTables = ImmutableSet.of("base_table1", "base_table2", "materialized_view_show_tables_test"); Set actualTables = computeActual("SHOW TABLES").getOnlyColumnAsSet().stream() .map(String.class::cast) .collect(toImmutableSet()); @@ -107,20 +103,6 @@ public void testMaterializedViewsMetadata() computeActual("CREATE TABLE small_region AS SELECT * FROM tpch.tiny.region LIMIT 1"); computeActual(format("CREATE MATERIALIZED VIEW %s AS SELECT * FROM small_region LIMIT 1", materializedViewName)); - // test storage table name - assertQuery( - format( - "SELECT storage_catalog, storage_schema, CONCAT(storage_schema, '.', storage_table)" + - "FROM system.metadata.materialized_views WHERE schema_name = '%s' AND name = '%s'", - // TODO (https://github.com/trinodb/trino/issues/9039) remove redundant schema_name filter - schemaName, - materializedViewName), - format( - "VALUES ('%s', '%s', '%s')", - catalogName, - schemaName, - getStorageTable(catalogName, schemaName, materializedViewName))); - // test freshness update assertQuery( // TODO (https://github.com/trinodb/trino/issues/9039) remove redundant schema_name filter @@ -182,7 +164,7 @@ public void testShowCreate() "WITH (\n" + " format = 'ORC',\n" + " format_version = 2,\n" + - " location = '" + getSchemaDirectory() + "/st_\\E[0-9a-f]+-[0-9a-f]+\\Q',\n" + + " location = '" + getSchemaDirectory() + "/test_mv_show_create-\\E[0-9a-f]+\\Q',\n" + " orc_bloom_filter_columns = ARRAY['_date'],\n" + " orc_bloom_filter_fpp = 1E-1,\n" + " partitioning = ARRAY['_date'],\n" + @@ -269,22 +251,6 @@ public void testRefreshDenyPermission() assertUpdate("DROP MATERIALIZED VIEW materialized_view_refresh_deny"); } - @Test - public void testRefreshAllowedWithRestrictedStorageTable() - { - assertUpdate("CREATE MATERIALIZED VIEW materialized_view_refresh AS SELECT * FROM base_table1"); - SchemaTableName storageTable = getStorageTable("materialized_view_refresh"); - - assertAccessAllowed( - "REFRESH MATERIALIZED VIEW materialized_view_refresh", - privilege(storageTable.getTableName(), INSERT_TABLE), - privilege(storageTable.getTableName(), DELETE_TABLE), - privilege(storageTable.getTableName(), UPDATE_TABLE), - privilege(storageTable.getTableName(), SELECT_COLUMN)); - - assertUpdate("DROP MATERIALIZED VIEW materialized_view_refresh"); - } - @Test public void testCreateRefreshSelect() { @@ -524,7 +490,7 @@ public void testSqlFeatures() "WITH (\n" + " format = 'PARQUET',\n" + " format_version = 2,\n" + - " location = '" + getSchemaDirectory() + "/st_\\E[0-9a-f]+-[0-9a-f]+\\Q',\n" + + " location = '" + getSchemaDirectory() + "/materialized_view_window-\\E[0-9a-f]+\\Q',\n" + " partitioning = ARRAY['_date'],\n" + " storage_schema = '" + schema + "'\n" + ") AS\n" + @@ -632,47 +598,6 @@ public void testNestedMaterializedViews() assertUpdate("DROP MATERIALIZED VIEW materialized_view_level2"); } - @Test - public void testStorageSchemaProperty() - { - String schemaName = getSession().getSchema().orElseThrow(); - String viewName = "storage_schema_property_test"; - assertUpdate( - "CREATE MATERIALIZED VIEW " + viewName + " " + - "WITH (storage_schema = '" + storageSchemaName + "') AS " + - "SELECT * FROM base_table1"); - SchemaTableName storageTable = getStorageTable(viewName); - assertThat(storageTable.getSchemaName()).isEqualTo(storageSchemaName); - - assertUpdate("REFRESH MATERIALIZED VIEW " + viewName, 6); - assertThat(computeActual("SELECT * FROM " + viewName).getRowCount()).isEqualTo(6); - assertThat(getExplainPlan("SELECT * FROM " + viewName, ExplainType.Type.IO)) - .doesNotContain("base_table1") - .contains(storageSchemaName); - - assertThat((String) computeScalar("SHOW CREATE MATERIALIZED VIEW " + viewName)) - .contains("storage_schema = '" + storageSchemaName + "'"); - - Set storageSchemaTables = computeActual("SHOW TABLES IN " + storageSchemaName).getOnlyColumnAsSet().stream() - .map(String.class::cast) - .collect(toImmutableSet()); - assertThat(storageSchemaTables).contains(storageTable.getTableName()); - - assertUpdate("DROP MATERIALIZED VIEW " + viewName); - storageSchemaTables = computeActual("SHOW TABLES IN " + storageSchemaName).getOnlyColumnAsSet().stream() - .map(String.class::cast) - .collect(toImmutableSet()); - assertThat(storageSchemaTables).doesNotContain(storageTable.getTableName()); - - assertThatThrownBy(() -> query( - "CREATE MATERIALIZED VIEW " + viewName + " " + - "WITH (storage_schema = 'non_existent') AS " + - "SELECT * FROM base_table1")) - .hasMessageContaining("non_existent not found"); - assertThatThrownBy(() -> query("DESCRIBE " + viewName)) - .hasMessageContaining(format("'iceberg.%s.%s' does not exist", schemaName, viewName)); - } - @Test(dataProvider = "testBucketPartitioningDataProvider") public void testBucketPartitioning(String dataType, String exampleValue) { @@ -683,9 +608,11 @@ public void testBucketPartitioning(String dataType, String exampleValue) assertUpdate("CREATE MATERIALIZED VIEW test_bucket_partitioning WITH (partitioning=ARRAY['bucket(col, 4)']) AS SELECT * FROM (VALUES CAST(NULL AS %s), %s) t(col)" .formatted(dataType, exampleValue)); try { - SchemaTableName storageTable = getStorageTable("test_bucket_partitioning"); - assertThat((String) computeScalar("SHOW CREATE TABLE " + storageTable)) - .contains("partitioning = ARRAY['bucket(col, 4)']"); + TableMetadata storageMetadata = getStorageTableMetadata( "test_bucket_partitioning"); + assertThat(storageMetadata.spec().fields()).hasSize(1); + PartitionField bucketPartitionField = getOnlyElement(storageMetadata.spec().fields()); + assertThat(bucketPartitionField.name()).isEqualTo("col_bucket"); + assertThat(bucketPartitionField.transform().toString()).isEqualTo("bucket[4]"); assertThat(query("SELECT * FROM test_bucket_partitioning WHERE col = " + exampleValue)) .matches("SELECT " + exampleValue); @@ -724,9 +651,11 @@ public void testTruncatePartitioning(String dataType, String exampleValue) assertUpdate("CREATE MATERIALIZED VIEW test_truncate_partitioning WITH (partitioning=ARRAY['truncate(col, 4)']) AS SELECT * FROM (VALUES CAST(NULL AS %s), %s) t(col)" .formatted(dataType, exampleValue)); try { - SchemaTableName storageTable = getStorageTable("test_truncate_partitioning"); - assertThat((String) computeScalar("SHOW CREATE TABLE " + storageTable)) - .contains("partitioning = ARRAY['truncate(col, 4)']"); + TableMetadata storageMetadata = getStorageTableMetadata("test_truncate_partitioning"); + assertThat(storageMetadata.spec().fields()).hasSize(1); + PartitionField bucketPartitionField = getOnlyElement(storageMetadata.spec().fields()); + assertThat(bucketPartitionField.name()).isEqualTo("col_trunc"); + assertThat(bucketPartitionField.transform().toString()).isEqualTo("truncate[4]"); assertThat(query("SELECT * FROM test_truncate_partitioning WHERE col = " + exampleValue)) .matches("SELECT " + exampleValue); @@ -759,9 +688,11 @@ public void testTemporalPartitioning(String partitioning, String dataType, Strin assertUpdate("CREATE MATERIALIZED VIEW test_temporal_partitioning WITH (partitioning=ARRAY['%s(col)']) AS SELECT * FROM (VALUES CAST(NULL AS %s), %s) t(col)" .formatted(partitioning, dataType, exampleValue)); try { - SchemaTableName storageTable = getStorageTable("test_temporal_partitioning"); - assertThat((String) computeScalar("SHOW CREATE TABLE " + storageTable)) - .contains("partitioning = ARRAY['%s(col)']".formatted(partitioning)); + TableMetadata storageMetadata = getStorageTableMetadata("test_temporal_partitioning"); + assertThat(storageMetadata.spec().fields()).hasSize(1); + PartitionField bucketPartitionField = getOnlyElement(storageMetadata.spec().fields()); + assertThat(bucketPartitionField.name()).isEqualTo("col_" + partitioning); + assertThat(bucketPartitionField.transform().toString()).isEqualTo(partitioning); assertThat(query("SELECT * FROM test_temporal_partitioning WHERE col = " + exampleValue)) .matches("SELECT " + exampleValue); @@ -789,25 +720,40 @@ public Object[][] testTemporalPartitioningDataProvider() }; } - protected String getColumnComment(String tableName, String columnName) + @Test + public void testMaterializedViewSnapshotSummariesHaveTrinoQueryId() { - return (String) computeScalar("SELECT comment FROM information_schema.columns WHERE table_schema = '" + getSession().getSchema().orElseThrow() + "' AND table_name = '" + tableName + "' AND column_name = '" + columnName + "'"); + String materializedViewName = "test_materialized_view_snapshot_query_ids" + randomNameSuffix(); + String sourceTableName = "test_source_table_for_mat_view" + randomNameSuffix(); + assertUpdate(format("CREATE TABLE %s (a bigint, b bigint)", sourceTableName)); + QueryId matViewCreateQueryId = getDistributedQueryRunner() + .executeWithQueryId(getSession(), format("CREATE MATERIALIZED VIEW %s WITH (partitioning = ARRAY['a']) AS SELECT * FROM %s", materializedViewName, sourceTableName)) + .getQueryId(); + + try { + assertUpdate(format("INSERT INTO %s VALUES (1, 1), (1, 4), (2, 2)", sourceTableName), 3); + + QueryId refreshQueryId = getDistributedQueryRunner() + .executeWithQueryId(getSession(), format("REFRESH MATERIALIZED VIEW %s", materializedViewName)) + .getQueryId(); + String savedQueryId = getStorageTableMetadata(materializedViewName).currentSnapshot().summary().get("trino_query_id"); + assertThat(savedQueryId).isEqualTo(refreshQueryId.getId()); + } + finally { + assertUpdate("DROP TABLE " + sourceTableName); + assertUpdate("DROP MATERIALIZED VIEW " + materializedViewName); + } } - private SchemaTableName getStorageTable(String materializedViewName) + protected String getColumnComment(String tableName, String columnName) { - return getStorageTable(getSession().getCatalog().orElseThrow(), getSession().getSchema().orElseThrow(), materializedViewName); + return (String) computeScalar("SELECT comment FROM information_schema.columns WHERE table_schema = '" + getSession().getSchema().orElseThrow() + "' AND table_name = '" + tableName + "' AND column_name = '" + columnName + "'"); } - private SchemaTableName getStorageTable(String catalogName, String schemaName, String materializedViewName) + private TableMetadata getStorageTableMetadata(String materializedViewName) { - TransactionManager transactionManager = getQueryRunner().getTransactionManager(); - TransactionId transactionId = transactionManager.beginTransaction(false); - Session session = getSession().beginTransactionId(transactionId, transactionManager, getQueryRunner().getAccessControl()); - Optional materializedView = getQueryRunner().getMetadata() - .getMaterializedView(session, new QualifiedObjectName(catalogName, schemaName, materializedViewName)); - assertThat(materializedView).isPresent(); - return materializedView.get().getStorageTable().get().getSchemaTableName(); + Location metadataLocation = Location.of(getStorageMetadataLocation(materializedViewName)); + return TableMetadataParser.read(new ForwardingFileIo(new LocalFileSystem(Path.of(metadataLocation.parentDirectory().toString()))), "local:///" + metadataLocation); } private long getLatestSnapshotId(String tableName) diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergConfig.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergConfig.java index 5cfcb285f73a..89a5d16e14f4 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergConfig.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergConfig.java @@ -17,6 +17,7 @@ import io.airlift.units.DataSize; import io.airlift.units.Duration; import io.trino.plugin.hive.HiveCompressionCodec; +import jakarta.validation.constraints.AssertFalse; import org.junit.jupiter.api.Test; import java.util.Map; @@ -24,6 +25,7 @@ import static io.airlift.configuration.testing.ConfigAssertions.assertFullMapping; import static io.airlift.configuration.testing.ConfigAssertions.assertRecordedDefaults; import static io.airlift.configuration.testing.ConfigAssertions.recordDefaults; +import static io.airlift.testing.ValidationAssertions.assertFailsValidation; import static io.airlift.units.DataSize.Unit.GIGABYTE; import static io.airlift.units.DataSize.Unit.MEGABYTE; import static io.trino.plugin.hive.HiveCompressionCodec.ZSTD; @@ -59,6 +61,7 @@ public void testDefaults() .setDeleteSchemaLocationsFallback(false) .setTargetMaxFileSize(DataSize.of(1, GIGABYTE)) .setMinimumAssignedSplitWeight(0.05) + .setHideMaterializedViewStorageTable(true) .setMaterializedViewsStorageSchema(null) .setRegisterTableProcedureEnabled(false) .setSortedWritingEnabled(true) @@ -87,6 +90,7 @@ public void testExplicitPropertyMappings() .put("iceberg.delete-schema-locations-fallback", "true") .put("iceberg.target-max-file-size", "1MB") .put("iceberg.minimum-assigned-split-weight", "0.01") + .put("iceberg.materialized-views.hide-storage-table", "false") .put("iceberg.materialized-views.storage-schema", "mv_storage_schema") .put("iceberg.register-table-procedure.enabled", "true") .put("iceberg.sorted-writing-enabled", "false") @@ -112,6 +116,7 @@ public void testExplicitPropertyMappings() .setDeleteSchemaLocationsFallback(true) .setTargetMaxFileSize(DataSize.of(1, MEGABYTE)) .setMinimumAssignedSplitWeight(0.01) + .setHideMaterializedViewStorageTable(false) .setMaterializedViewsStorageSchema("mv_storage_schema") .setRegisterTableProcedureEnabled(true) .setSortedWritingEnabled(false) @@ -119,4 +124,16 @@ public void testExplicitPropertyMappings() assertFullMapping(properties, expected); } + + @Test + public void testValidation() + { + assertFailsValidation( + new IcebergConfig() + .setHideMaterializedViewStorageTable(true) + .setMaterializedViewsStorageSchema("storage_schema"), + "storageSchemaSetWhenHidingIsEnabled", + "iceberg.materialized-views.storage-schema may only be set when iceberg.materialized-views.hide-storage-table is set to false", + AssertFalse.class); + } } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMaterializedView.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMaterializedView.java index 59ba415c1dab..c694902ccd80 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMaterializedView.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMaterializedView.java @@ -14,26 +14,40 @@ package io.trino.plugin.iceberg; import io.trino.Session; +import io.trino.plugin.hive.metastore.HiveMetastore; +import io.trino.plugin.hive.metastore.Table; import io.trino.sql.tree.ExplainType; import io.trino.testing.DistributedQueryRunner; import org.testng.annotations.Test; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Map; import static io.trino.plugin.base.util.Closables.closeAllSuppress; -import static io.trino.plugin.iceberg.IcebergQueryRunner.createIcebergQueryRunner; +import static io.trino.plugin.hive.metastore.file.TestingFileHiveMetastore.createTestingFileHiveMetastore; +import static org.apache.iceberg.BaseMetastoreTableOperations.METADATA_LOCATION_PROP; import static org.assertj.core.api.Assertions.assertThat; public class TestIcebergMaterializedView extends BaseIcebergMaterializedViewTest { private Session secondIceberg; + private String fileMetastoreDirectory; + private HiveMetastore metastore; @Override protected DistributedQueryRunner createQueryRunner() throws Exception { - DistributedQueryRunner queryRunner = createIcebergQueryRunner(); + File metastoreDir = Files.createTempDirectory("test_iceberg_table_smoke_test").toFile(); + metastoreDir.deleteOnExit(); + this.fileMetastoreDirectory = metastoreDir.getAbsolutePath(); + this.metastore = createTestingFileHiveMetastore(metastoreDir); + DistributedQueryRunner queryRunner = IcebergQueryRunner.builder() + .setMetastoreDirectory(metastoreDir) + .build(); try { queryRunner.createCatalog("iceberg2", "iceberg", Map.of( "iceberg.catalog.type", "TESTING_FILE_METASTORE", @@ -56,7 +70,14 @@ protected DistributedQueryRunner createQueryRunner() @Override protected String getSchemaDirectory() { - return getDistributedQueryRunner().getCoordinator().getBaseDataDir().resolve("iceberg_data/tpch").toString(); + return Path.of(fileMetastoreDirectory, "tpch").toString(); + } + + @Override + protected String getStorageMetadataLocation(String materializedViewName) + { + Table table = metastore.getTable("tpch", materializedViewName).orElseThrow(); + return table.getParameters().get(METADATA_LOCATION_PROP); } @Test @@ -83,7 +104,7 @@ AS SELECT sum(value) AS s FROM iceberg2.tpch.common_base_table // After REFRESH, the MV is fresh assertUpdate(defaultIceberg, "REFRESH MATERIALIZED VIEW mv_on_iceberg2", 1); assertThat(getExplainPlan("TABLE mv_on_iceberg2", ExplainType.Type.IO)) - .contains("\"table\" : \"st_") + .contains("\"table\" : \"mv_on_iceberg2$materialized_view_storage") .doesNotContain("common_base_table"); assertThat(query("TABLE mv_on_iceberg2")) .matches("VALUES BIGINT '10'"); @@ -91,7 +112,7 @@ AS SELECT sum(value) AS s FROM iceberg2.tpch.common_base_table // After INSERT to the base table, the MV is still fresh, because it currently does not detect changes to tables in other catalog. assertUpdate(secondIceberg, "INSERT INTO common_base_table VALUES 7", 1); assertThat(getExplainPlan("TABLE mv_on_iceberg2", ExplainType.Type.IO)) - .contains("\"table\" : \"st_") + .contains("\"table\" : \"mv_on_iceberg2$materialized_view_storage") .doesNotContain("common_base_table"); assertThat(query("TABLE mv_on_iceberg2")) .matches("VALUES BIGINT '10'"); @@ -99,7 +120,7 @@ AS SELECT sum(value) AS s FROM iceberg2.tpch.common_base_table // After REFRESH, the MV is fresh again assertUpdate(defaultIceberg, "REFRESH MATERIALIZED VIEW mv_on_iceberg2", 1); assertThat(getExplainPlan("TABLE mv_on_iceberg2", ExplainType.Type.IO)) - .contains("\"table\" : \"st_") + .contains("\"table\" : \"mv_on_iceberg2$materialized_view_storage") .doesNotContain("common_base_table"); assertThat(query("TABLE mv_on_iceberg2")) .matches("VALUES BIGINT '17'"); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMergeAppend.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMergeAppend.java index 2889f355fe63..0997b3768eff 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMergeAppend.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMergeAppend.java @@ -62,7 +62,8 @@ protected QueryRunner createQueryRunner() tableOperationsProvider, false, false, - false); + false, + new IcebergConfig().isHideMaterializedViewStorageTable()); return queryRunner; } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMetadataListing.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMetadataListing.java index 7ec95cbdd7af..d05e92225477 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMetadataListing.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMetadataListing.java @@ -107,7 +107,6 @@ public void testTableListing() "iceberg_table1", "iceberg_table2", "iceberg_materialized_view", - storageTable.getTableName(), "iceberg_view", "hive_table", "hive_view"); @@ -118,7 +117,6 @@ public void testTableListing() "'iceberg_table1', " + "'iceberg_table2', " + "'iceberg_materialized_view', " + - "'" + storageTable.getTableName() + "', " + "'iceberg_view', " + "'hive_table', " + "'hive_view'"); @@ -136,8 +134,6 @@ public void testTableColumnListing() "('iceberg_table2', '_double'), " + "('iceberg_materialized_view', '_string'), " + "('iceberg_materialized_view', '_integer'), " + - "('" + storageTable.getTableName() + "', '_string'), " + - "('" + storageTable.getTableName() + "', '_integer'), " + "('iceberg_view', '_string'), " + "('iceberg_view', '_integer'), " + "('hive_view', '_double')"); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMetastoreAccessOperations.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMetastoreAccessOperations.java index 2d46297e5b2b..005a0cfeccce 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMetastoreAccessOperations.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMetastoreAccessOperations.java @@ -45,6 +45,7 @@ import static io.trino.plugin.iceberg.TableType.FILES; import static io.trino.plugin.iceberg.TableType.HISTORY; import static io.trino.plugin.iceberg.TableType.MANIFESTS; +import static io.trino.plugin.iceberg.TableType.MATERIALIZED_VIEW_STORAGE; import static io.trino.plugin.iceberg.TableType.PARTITIONS; import static io.trino.plugin.iceberg.TableType.PROPERTIES; import static io.trino.plugin.iceberg.TableType.REFS; @@ -223,7 +224,7 @@ public void testSelectFromMaterializedView() assertMetastoreInvocations("SELECT * FROM test_select_mview_view", ImmutableMultiset.builder() - .addCopies(GET_TABLE, 3) + .addCopies(GET_TABLE, 2) .build()); } @@ -235,7 +236,7 @@ public void testSelectFromMaterializedViewWithFilter() assertMetastoreInvocations("SELECT * FROM test_select_mview_where_view WHERE age = 2", ImmutableMultiset.builder() - .addCopies(GET_TABLE, 3) + .addCopies(GET_TABLE, 2) .build()); } @@ -247,7 +248,7 @@ public void testRefreshMaterializedView() assertMetastoreInvocations("REFRESH MATERIALIZED VIEW test_refresh_mview_view", ImmutableMultiset.builder() - .addCopies(GET_TABLE, 6) + .addCopies(GET_TABLE, 2) .addCopies(REPLACE_TABLE, 1) .build()); } @@ -349,9 +350,12 @@ public void testSelectSystemTable() .addCopies(GET_TABLE, 1) .build()); + assertQueryFails("SELECT * FROM \"test_select_snapshots$materialized_view_storage\"", + "Table 'test_schema.test_select_snapshots\\$materialized_view_storage' not found"); + // This test should get updated if a new system table is added. assertThat(TableType.values()) - .containsExactly(DATA, HISTORY, SNAPSHOTS, MANIFESTS, PARTITIONS, FILES, PROPERTIES, REFS); + .containsExactly(DATA, HISTORY, SNAPSHOTS, MANIFESTS, PARTITIONS, FILES, PROPERTIES, REFS, MATERIALIZED_VIEW_STORAGE); } @Test @@ -491,33 +495,33 @@ public void testSystemMetadataMaterializedViews() assertMetastoreInvocations(session, "SELECT * FROM system.metadata.materialized_views WHERE schema_name = CURRENT_SCHEMA", ImmutableMultiset.builder() .add(GET_TABLES_WITH_PARAMETER) - .addCopies(GET_TABLE, 6) + .addCopies(GET_TABLE, 4) .build()); // Bulk retrieval without selecting freshness assertMetastoreInvocations(session, "SELECT schema_name, name FROM system.metadata.materialized_views WHERE schema_name = CURRENT_SCHEMA", ImmutableMultiset.builder() .add(GET_TABLES_WITH_PARAMETER) - .addCopies(GET_TABLE, 4) + .addCopies(GET_TABLE, 2) .build()); // Bulk retrieval for two schemas assertMetastoreInvocations(session, "SELECT * FROM system.metadata.materialized_views WHERE schema_name IN (CURRENT_SCHEMA, 'non_existent')", ImmutableMultiset.builder() .addCopies(GET_TABLES_WITH_PARAMETER, 2) - .addCopies(GET_TABLE, 6) + .addCopies(GET_TABLE, 4) .build()); // Pointed lookup assertMetastoreInvocations(session, "SELECT * FROM system.metadata.materialized_views WHERE schema_name = CURRENT_SCHEMA AND name = 'mv1'", ImmutableMultiset.builder() - .addCopies(GET_TABLE, 4) + .addCopies(GET_TABLE, 3) .build()); // Pointed lookup without selecting freshness assertMetastoreInvocations(session, "SELECT schema_name, name FROM system.metadata.materialized_views WHERE schema_name = CURRENT_SCHEMA AND name = 'mv1'", ImmutableMultiset.builder() - .addCopies(GET_TABLE, 2) + .add(GET_TABLE) .build()); assertUpdate("DROP SCHEMA " + schemaName + " CASCADE"); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergOrcMetricsCollection.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergOrcMetricsCollection.java index 5c7a6e77c155..c19c1349713c 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergOrcMetricsCollection.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergOrcMetricsCollection.java @@ -98,7 +98,8 @@ protected QueryRunner createQueryRunner() tableOperationsProvider, false, false, - false); + false, + new IcebergConfig().isHideMaterializedViewStorageTable()); queryRunner.installPlugin(new TpchPlugin()); queryRunner.createCatalog("tpch", "tpch"); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergSplitSource.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergSplitSource.java index 10aa9f8e3553..2e7812aacda5 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergSplitSource.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergSplitSource.java @@ -122,7 +122,8 @@ protected QueryRunner createQueryRunner() new FileMetastoreTableOperationsProvider(fileSystemFactory), false, false, - false); + false, + new IcebergConfig().isHideMaterializedViewStorageTable()); return queryRunner; } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergV2.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergV2.java index afd14a0ef64b..f8433034db31 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergV2.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergV2.java @@ -1036,7 +1036,8 @@ private BaseTable loadTable(String tableName) tableOperationsProvider, false, false, - false); + false, + new IcebergConfig().isHideMaterializedViewStorageTable()); return (BaseTable) loadIcebergTable(catalog, tableOperationsProvider, SESSION, new SchemaTableName("tpch", tableName)); } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestTrinoHiveCatalogWithFileMetastore.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestTrinoHiveCatalogWithFileMetastore.java index 8d7e3dcbe12c..2798d5ce6d0f 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestTrinoHiveCatalogWithFileMetastore.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestTrinoHiveCatalogWithFileMetastore.java @@ -18,6 +18,7 @@ import io.trino.plugin.hive.TrinoViewHiveMetastore; import io.trino.plugin.hive.metastore.HiveMetastore; import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; +import io.trino.plugin.iceberg.IcebergConfig; import io.trino.plugin.iceberg.catalog.BaseTrinoCatalogTest; import io.trino.plugin.iceberg.catalog.TrinoCatalog; import io.trino.plugin.iceberg.catalog.hms.TrinoHiveCatalog; @@ -78,6 +79,7 @@ protected TrinoCatalog createTrinoCatalog(boolean useUniqueTableLocations) new FileMetastoreTableOperationsProvider(fileSystemFactory), useUniqueTableLocations, false, - false); + false, + new IcebergConfig().isHideMaterializedViewStorageTable()); } } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogAccessOperations.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogAccessOperations.java index 01a471bb344a..42ff3e2a441f 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogAccessOperations.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogAccessOperations.java @@ -53,6 +53,7 @@ import static io.trino.plugin.iceberg.TableType.FILES; import static io.trino.plugin.iceberg.TableType.HISTORY; import static io.trino.plugin.iceberg.TableType.MANIFESTS; +import static io.trino.plugin.iceberg.TableType.MATERIALIZED_VIEW_STORAGE; import static io.trino.plugin.iceberg.TableType.PARTITIONS; import static io.trino.plugin.iceberg.TableType.PROPERTIES; import static io.trino.plugin.iceberg.TableType.REFS; @@ -268,7 +269,7 @@ public void testSelectFromMaterializedView() assertGlueMetastoreApiInvocations("SELECT * FROM test_select_mview_view", ImmutableMultiset.builder() - .addCopies(GET_TABLE, 3) + .addCopies(GET_TABLE, 2) .build()); } finally { @@ -286,7 +287,7 @@ public void testSelectFromMaterializedViewWithFilter() assertGlueMetastoreApiInvocations("SELECT * FROM test_select_mview_where_view WHERE age = 2", ImmutableMultiset.builder() - .addCopies(GET_TABLE, 3) + .addCopies(GET_TABLE, 2) .build()); } finally { @@ -304,7 +305,7 @@ public void testRefreshMaterializedView() assertGlueMetastoreApiInvocations("REFRESH MATERIALIZED VIEW test_refresh_mview_view", ImmutableMultiset.builder() - .addCopies(GET_TABLE, 5) + .addCopies(GET_TABLE, 4) .addCopies(UPDATE_TABLE, 1) .build()); } @@ -444,9 +445,12 @@ public void testSelectSystemTable() .addCopies(GET_TABLE, 1) .build()); + assertQueryFails("SELECT * FROM \"test_select_snapshots$materialized_view_storage\"", + "Table '" + testSchema + ".test_select_snapshots\\$materialized_view_storage' not found"); + // This test should get updated if a new system table is added. assertThat(TableType.values()) - .containsExactly(DATA, HISTORY, SNAPSHOTS, MANIFESTS, PARTITIONS, FILES, PROPERTIES, REFS); + .containsExactly(DATA, HISTORY, SNAPSHOTS, MANIFESTS, PARTITIONS, FILES, PROPERTIES, REFS, MATERIALIZED_VIEW_STORAGE); } finally { getQueryRunner().execute("DROP TABLE IF EXISTS test_select_snapshots"); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogMaterializedView.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogMaterializedView.java index 95b1d315d7ef..f3d54ce2deed 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogMaterializedView.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogMaterializedView.java @@ -17,6 +17,7 @@ import com.amazonaws.services.glue.AWSGlueAsyncClientBuilder; import com.amazonaws.services.glue.model.BatchDeleteTableRequest; import com.amazonaws.services.glue.model.DeleteDatabaseRequest; +import com.amazonaws.services.glue.model.GetTableRequest; import com.amazonaws.services.glue.model.GetTablesRequest; import com.amazonaws.services.glue.model.GetTablesResult; import com.amazonaws.services.glue.model.Table; @@ -36,7 +37,9 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet; import static io.trino.plugin.hive.metastore.glue.AwsSdkUtil.getPaginatedResults; +import static io.trino.plugin.hive.metastore.glue.converter.GlueToTrinoConverter.getTableParameters; import static io.trino.testing.TestingNames.randomNameSuffix; +import static org.apache.iceberg.BaseMetastoreTableOperations.METADATA_LOCATION_PROP; public class TestIcebergGlueCatalogMaterializedView extends BaseIcebergMaterializedViewTest @@ -71,11 +74,21 @@ protected String getSchemaDirectory() return new File(schemaDirectory, schemaName + ".db").getPath(); } + @Override + protected String getStorageMetadataLocation(String materializedViewName) + { + AWSGlueAsync glueClient = AWSGlueAsyncClientBuilder.defaultClient(); + Table table = glueClient.getTable(new GetTableRequest() + .withDatabaseName(schemaName) + .withName(materializedViewName)) + .getTable(); + return getTableParameters(table).get(METADATA_LOCATION_PROP); + } + @AfterClass(alwaysRun = true) public void cleanup() { cleanUpSchema(schemaName); - cleanUpSchema(storageSchemaName); } private static void cleanUpSchema(String schema) diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestTrinoGlueCatalog.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestTrinoGlueCatalog.java index f69cef509c6b..c44dc03ee138 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestTrinoGlueCatalog.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestTrinoGlueCatalog.java @@ -25,6 +25,7 @@ import io.trino.plugin.hive.NodeVersion; import io.trino.plugin.hive.metastore.glue.GlueMetastoreStats; import io.trino.plugin.iceberg.CommitTaskData; +import io.trino.plugin.iceberg.IcebergConfig; import io.trino.plugin.iceberg.IcebergMetadata; import io.trino.plugin.iceberg.TableStatisticsWriter; import io.trino.plugin.iceberg.catalog.BaseTrinoCatalogTest; @@ -79,7 +80,8 @@ protected TrinoCatalog createTrinoCatalog(boolean useUniqueTableLocations) glueClient, new GlueMetastoreStats(), Optional.empty(), - useUniqueTableLocations); + useUniqueTableLocations, + new IcebergConfig().isHideMaterializedViewStorageTable()); } /** @@ -157,7 +159,8 @@ public void testDefaultLocation() glueClient, new GlueMetastoreStats(), Optional.of(tmpDirectory.toAbsolutePath().toString()), - false); + false, + new IcebergConfig().isHideMaterializedViewStorageTable()); String namespace = "test_default_location_" + randomNameSuffix(); String table = "tableName"; diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/hms/TestTrinoHiveCatalogWithHiveMetastore.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/hms/TestTrinoHiveCatalogWithHiveMetastore.java index 7b01d801a289..03adafadfcff 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/hms/TestTrinoHiveCatalogWithHiveMetastore.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/hms/TestTrinoHiveCatalogWithHiveMetastore.java @@ -33,6 +33,7 @@ import io.trino.plugin.hive.metastore.thrift.ThriftMetastore; import io.trino.plugin.hive.metastore.thrift.ThriftMetastoreConfig; import io.trino.plugin.hive.metastore.thrift.ThriftMetastoreFactory; +import io.trino.plugin.iceberg.IcebergConfig; import io.trino.plugin.iceberg.IcebergSchemaProperties; import io.trino.plugin.iceberg.catalog.BaseTrinoCatalogTest; import io.trino.plugin.iceberg.catalog.TrinoCatalog; @@ -132,7 +133,8 @@ public ThriftMetastore createMetastore(Optional identity) }), useUniqueTableLocations, false, - false); + false, + new IcebergConfig().isHideMaterializedViewStorageTable()); } @Override diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergHiveMetadataListing.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergHiveMetadataListing.java index 7564761a75a0..f2e4c4e6c87e 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergHiveMetadataListing.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergHiveMetadataListing.java @@ -22,7 +22,6 @@ import java.util.List; -import static com.google.common.collect.Iterators.getOnlyElement; import static io.trino.tempto.assertions.QueryAssert.Row.row; import static io.trino.tests.product.TestGroups.HMS_ONLY; import static io.trino.tests.product.TestGroups.ICEBERG; @@ -34,7 +33,6 @@ public class TestIcebergHiveMetadataListing extends ProductTest { - private String storageTable; private List preexistingTables; private List preexistingColumns; @@ -55,11 +53,6 @@ public void setUp() onTrino().executeQuery("CREATE TABLE iceberg.default.iceberg_table1 (_string VARCHAR, _integer INTEGER)"); onTrino().executeQuery("CREATE MATERIALIZED VIEW iceberg.default.iceberg_materialized_view AS " + "SELECT * FROM iceberg.default.iceberg_table1"); - storageTable = getOnlyElement(onTrino().executeQuery("SHOW TABLES FROM iceberg.default") - .column(1).stream() - .map(String.class::cast) - .filter(name -> name.startsWith("st_")) - .iterator()); onTrino().executeQuery("CREATE TABLE hive.default.hive_table (_double DOUBLE)"); onTrino().executeQuery("CREATE VIEW hive.default.hive_view AS SELECT * FROM hive.default.hive_table"); @@ -84,7 +77,6 @@ public void testTableListing() .addAll(preexistingTables) .add(row("iceberg_table1")) .add(row("iceberg_materialized_view")) - .add(row(storageTable)) .add(row("iceberg_view")) .add(row("hive_table")) // Iceberg connector supports Trino views created via Hive connector @@ -104,8 +96,6 @@ public void testColumnListing() .add(row("iceberg_table1", "_integer")) .add(row("iceberg_materialized_view", "_string")) .add(row("iceberg_materialized_view", "_integer")) - .add(row(storageTable, "_string")) - .add(row(storageTable, "_integer")) .add(row("iceberg_view", "_string")) .add(row("iceberg_view", "_integer")) .add(row("hive_view", "_double")) From d2bcdf2ae73f5c9030a0d016c787356496fe86a5 Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Wed, 11 Oct 2023 14:04:56 +0900 Subject: [PATCH 182/587] Use dedicated schemas in Delta Lake smoke tests Also, restore the assertion which was deleted in https://github.com/trinodb/trino/commit/ee72515430cdfcc9f86d31c26d84aef81c2b722e --- .../BaseDeltaLakeConnectorSmokeTest.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeConnectorSmokeTest.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeConnectorSmokeTest.java index 73b1e63f232f..ff7601c9b5a5 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeConnectorSmokeTest.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeConnectorSmokeTest.java @@ -481,20 +481,25 @@ public void testHiddenColumns() @Test public void testHiveViewsCannotBeAccessed() { + String schemaName = "test_schema" + randomNameSuffix(); String viewName = "dummy_view"; - hiveHadoop.runOnHive(format("CREATE VIEW %1$s.%2$s AS SELECT * FROM %1$s.customer", SCHEMA, viewName)); - assertThatThrownBy(() -> computeActual("DESCRIBE " + viewName)).hasMessageContaining(format("%s.%s is not a Delta Lake table", SCHEMA, viewName)); - hiveHadoop.runOnHive("DROP VIEW " + viewName); + hiveHadoop.runOnHive("CREATE DATABASE " + schemaName); + hiveHadoop.runOnHive(format("CREATE VIEW %s.%s AS SELECT * FROM %s.customer", schemaName, viewName, SCHEMA)); + assertEquals(computeScalar(format("SHOW TABLES FROM %s LIKE '%s'", schemaName, viewName)), viewName); + assertThatThrownBy(() -> computeActual("DESCRIBE " + schemaName + "." + viewName)).hasMessageContaining(format("%s.%s is not a Delta Lake table", schemaName, viewName)); + hiveHadoop.runOnHive("DROP DATABASE " + schemaName + " CASCADE"); } @Test public void testNonDeltaTablesCannotBeAccessed() { + String schemaName = "test_schema" + randomNameSuffix(); String tableName = "hive_table"; - hiveHadoop.runOnHive(format("CREATE TABLE %s.%s (id BIGINT)", SCHEMA, tableName)); - assertEquals(computeScalar(format("SHOW TABLES LIKE '%s'", tableName)), tableName); - assertThatThrownBy(() -> computeActual("DESCRIBE " + tableName)).hasMessageContaining(tableName + " is not a Delta Lake table"); - hiveHadoop.runOnHive(format("DROP TABLE %s.%s", SCHEMA, tableName)); + hiveHadoop.runOnHive("CREATE DATABASE " + schemaName); + hiveHadoop.runOnHive(format("CREATE TABLE %s.%s (id BIGINT)", schemaName, tableName)); + assertEquals(computeScalar(format("SHOW TABLES FROM %s LIKE '%s'", schemaName, tableName)), tableName); + assertThatThrownBy(() -> computeActual("DESCRIBE " + schemaName + "." + tableName)).hasMessageContaining(tableName + " is not a Delta Lake table"); + hiveHadoop.runOnHive("DROP DATABASE " + schemaName + " CASCADE"); } @Test From 522b3304e0c4cd6ca3c3d0ddf8a020d16ec2a95b Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Fri, 10 Nov 2023 13:52:57 +0100 Subject: [PATCH 183/587] Remove redundant version definitions --- plugin/trino-exchange-filesystem/pom.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/plugin/trino-exchange-filesystem/pom.xml b/plugin/trino-exchange-filesystem/pom.xml index 69aff43eb7f3..03872cb61fd9 100644 --- a/plugin/trino-exchange-filesystem/pom.xml +++ b/plugin/trino-exchange-filesystem/pom.xml @@ -67,7 +67,6 @@ com.google.api gax - 2.36.0 com.google.protobuf @@ -91,13 +90,11 @@ com.google.auth google-auth-library-credentials - 1.20.0 com.google.auth google-auth-library-oauth2-http - 1.20.0 commons-logging @@ -113,7 +110,6 @@ com.google.cloud google-cloud-core - 2.24.1 com.google.protobuf @@ -129,7 +125,6 @@ com.google.cloud google-cloud-storage - 2.27.1 com.google.auto.value From e6f69ec70987fc4b8630ede785c8dd8779cc7857 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Fri, 10 Nov 2023 13:55:42 +0100 Subject: [PATCH 184/587] Update google's libraries-bom to 26.27.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5d28b064cf00..c823ce74f828 100644 --- a/pom.xml +++ b/pom.xml @@ -217,7 +217,7 @@ com.google.cloud libraries-bom - 26.26.0 + 26.27.0 pom import From d5a858f83cd67c3b7338160905d65267a81b9f5b Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Fri, 10 Nov 2023 13:55:56 +0100 Subject: [PATCH 185/587] Upgrade netty to 4.1.101.Final --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c823ce74f828..d01237b2e389 100644 --- a/pom.xml +++ b/pom.xml @@ -249,7 +249,7 @@ io.netty netty-bom - 4.1.100.Final + 4.1.101.Final pom import From 6601a20f46d504ddf244b9be66cfba870d240441 Mon Sep 17 00:00:00 2001 From: Manfred Moser Date: Fri, 10 Nov 2023 08:29:54 -0800 Subject: [PATCH 186/587] Improve routine docs for loop and begin --- docs/src/main/sphinx/routines/begin.md | 2 +- docs/src/main/sphinx/routines/loop.md | 20 ++++++-------------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/docs/src/main/sphinx/routines/begin.md b/docs/src/main/sphinx/routines/begin.md index f9dab3d72302..c1ef143fd70a 100644 --- a/docs/src/main/sphinx/routines/begin.md +++ b/docs/src/main/sphinx/routines/begin.md @@ -18,7 +18,7 @@ case is as first statement within a [](/routines/function). Blocks can also be nested. After the `BEGIN` keyword, you can add variable declarations using -[/routines/declare] statements, followed by one or more statements that define +[](/routines/declare) statements, followed by one or more statements that define the main body of the routine, separated by `;`. The following statements can be used: diff --git a/docs/src/main/sphinx/routines/loop.md b/docs/src/main/sphinx/routines/loop.md index 3bb27745f0ff..a45d42f85302 100644 --- a/docs/src/main/sphinx/routines/loop.md +++ b/docs/src/main/sphinx/routines/loop.md @@ -14,20 +14,14 @@ The `LOOP` statement is an optional construct in [SQL routines](/routines/introduction) to allow processing of a block of statements repeatedly. -The block of statements is processed at least once. After the first, and every -subsequent processing the expression `condidtion` is validated. If the result is -`true`, processing moves to END REPEAT and continues with the next statement in -the function. If the result is `false`, the statements are processed again -repeatedly. +The block of `statements` is processed until an explicit use of `LEAVE` causes +processing to exit the loop. If processing reaches `END LOOP`, another iteration +of processing from the beginning starts. `LEAVE` statements are typically +wrapped in an `IF` statement that declares a condition to stop the loop. -The optional `label` before the `REPEAT` keyword can be used to [name the +The optional `label` before the `LOOP` keyword can be used to [name the block](routine-label). -Note that a `WHILE` statement is very similar, with the difference that for -`REPEAT` the statements are processed at least once, and for `WHILE` blocks the -statements might not be processed at all. - - ## Examples @@ -58,6 +52,4 @@ documentation](/routines/examples). ## See also * [](/routines/introduction) -* [](/routines/repeat) -* [](/routines/while) - +* [](/routines/leave) From b47951a51bb0cd4567b529a29f05fa296eb6bd28 Mon Sep 17 00:00:00 2001 From: Colebow Date: Tue, 7 Nov 2023 11:47:40 -0800 Subject: [PATCH 187/587] Add Trino 433 release notes --- docs/src/main/sphinx/release.md | 1 + docs/src/main/sphinx/release/release-433.md | 60 +++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 docs/src/main/sphinx/release/release-433.md diff --git a/docs/src/main/sphinx/release.md b/docs/src/main/sphinx/release.md index b92f2e50d73f..6c70108306bc 100644 --- a/docs/src/main/sphinx/release.md +++ b/docs/src/main/sphinx/release.md @@ -7,6 +7,7 @@ ```{toctree} :maxdepth: 1 +release/release-433 release/release-432 release/release-431 release/release-430 diff --git a/docs/src/main/sphinx/release/release-433.md b/docs/src/main/sphinx/release/release-433.md new file mode 100644 index 000000000000..2fd63bfeff72 --- /dev/null +++ b/docs/src/main/sphinx/release/release-433.md @@ -0,0 +1,60 @@ +# Release 433 (10 Nov 2023) + +## General + +* Improve planning time and resulting plan efficiency for queries involving + `UNION ALL` with `LIMIT`. ({issue}`19471`) +* Fix long query planning times for queries with multiple window functions. ({issue}`18491`) +* Fix resource groups not noticing updates to the `softMemoryLimit` if it is + changed from a percent-based value to an absolute value. ({issue}`19626`) +* Fix potential query failure for queries involving arrays, `GROUP BY`, + or `DISTINCT`. ({issue}`19596`) + +## BigQuery connector + +* Fix incorrect results for queries involving projections and the `query` table + function. ({issue}`19570`) + +## Delta Lake connector + +* Fix query failure when reading ORC files with a `DECIMAL` column that + contains only null values. ({issue}`19636`) +* Fix possible JVM crash when reading short decimal columns in Parquet files + created by Impala. ({issue}`19697`) + +## Hive connector + +* Add support for reading tables where a column's type has been changed from + `boolean` to `varchar`. ({issue}`19571`) +* Add support for reading tables where a column's type has been changed from + `varchar` to `double`. ({issue}`19517`) +* Add support for reading tables where a column's type has been changed from + `tinyint`, `smallint`, `integer`, or `bigint` to `double`. ({issue}`19520`) +* Add support for altering table comments in the Glue catalog. ({issue}`19073`) +* Fix query failure when reading ORC files with a `DECIMAL` column that + contains only null values. ({issue}`19636`) +* Fix possible JVM crash when reading short decimal columns in Parquet files + created by Impala. ({issue}`19697`) + +## Hudi connector + +* Fix query failure when reading ORC files with a `DECIMAL` column that + contains only null values. ({issue}`19636`) +* Fix possible JVM crash when reading short decimal columns in Parquet files + created by Impala. ({issue}`19697`) + +## Iceberg connector + +* Fix incorrect query results when querying Parquet files with dynamic filtering + on `UUID` columns. ({issue}`19670`) +* Fix query failure when reading ORC files with a `DECIMAL` column that + contains only null values. ({issue}`19636`) +* Fix possible JVM crash when reading short decimal columns in Parquet files + created by Impala. ({issue}`19697`) +* Prevent creation of separate entries for storage tables of materialized views. + ({issue}`18853`) + +## SPI + +* Add JMX metrics for event listeners through + `trino.eventlistener:name=EventListenerManager`. ({issue}`19623`) From 993ddac51dd9db6d4793898192525ca1adfbadc8 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Fri, 10 Nov 2023 11:20:25 -0800 Subject: [PATCH 188/587] Restore release preparation goals This property was lost in ab1e89b4aa07077f2c8e7ca61719a1034457fb20 and is causing the release process to attempt to run all the tests. --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index d01237b2e389..5e62a530e995 100644 --- a/pom.xml +++ b/pom.xml @@ -155,6 +155,7 @@ - had forward change at midnight (1970-01-01 00:00:00 clocks turned forward 1 hour) --> 8 + clean verify -DskipTests America/Bahia_Banderas methods 2 From 301c72792f616310cf227a0ef66fb5e86e07027c Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Fri, 10 Nov 2023 19:44:27 +0000 Subject: [PATCH 189/587] [maven-release-plugin] prepare release 433 --- client/trino-cli/pom.xml | 2 +- client/trino-client/pom.xml | 2 +- client/trino-jdbc/pom.xml | 2 +- core/trino-grammar/pom.xml | 2 +- core/trino-main/pom.xml | 2 +- core/trino-parser/pom.xml | 2 +- core/trino-server-main/pom.xml | 2 +- core/trino-server-rpm/pom.xml | 2 +- core/trino-server/pom.xml | 2 +- core/trino-spi/pom.xml | 2 +- docs/pom.xml | 2 +- lib/trino-array/pom.xml | 2 +- lib/trino-cache/pom.xml | 2 +- lib/trino-filesystem-azure/pom.xml | 2 +- lib/trino-filesystem-gcs/pom.xml | 2 +- lib/trino-filesystem-manager/pom.xml | 2 +- lib/trino-filesystem-s3/pom.xml | 2 +- lib/trino-filesystem/pom.xml | 2 +- lib/trino-geospatial-toolkit/pom.xml | 2 +- lib/trino-hdfs/pom.xml | 2 +- lib/trino-hive-formats/pom.xml | 2 +- lib/trino-ignite-patched/pom.xml | 2 +- lib/trino-matching/pom.xml | 2 +- lib/trino-memory-context/pom.xml | 2 +- lib/trino-orc/pom.xml | 2 +- lib/trino-parquet/pom.xml | 2 +- lib/trino-phoenix5-patched/pom.xml | 2 +- lib/trino-plugin-toolkit/pom.xml | 2 +- lib/trino-record-decoder/pom.xml | 2 +- plugin/trino-accumulo-iterators/pom.xml | 2 +- plugin/trino-accumulo/pom.xml | 2 +- plugin/trino-atop/pom.xml | 2 +- plugin/trino-base-jdbc/pom.xml | 2 +- plugin/trino-bigquery/pom.xml | 2 +- plugin/trino-blackhole/pom.xml | 2 +- plugin/trino-cassandra/pom.xml | 2 +- plugin/trino-clickhouse/pom.xml | 2 +- plugin/trino-delta-lake/pom.xml | 2 +- plugin/trino-druid/pom.xml | 2 +- plugin/trino-elasticsearch/pom.xml | 2 +- plugin/trino-example-http/pom.xml | 2 +- plugin/trino-example-jdbc/pom.xml | 2 +- plugin/trino-exchange-filesystem/pom.xml | 2 +- plugin/trino-exchange-hdfs/pom.xml | 2 +- plugin/trino-geospatial/pom.xml | 2 +- plugin/trino-google-sheets/pom.xml | 2 +- plugin/trino-hive-hadoop2/pom.xml | 2 +- plugin/trino-hive/pom.xml | 2 +- plugin/trino-http-event-listener/pom.xml | 2 +- plugin/trino-hudi/pom.xml | 2 +- plugin/trino-iceberg/pom.xml | 2 +- plugin/trino-ignite/pom.xml | 2 +- plugin/trino-jmx/pom.xml | 2 +- plugin/trino-kafka/pom.xml | 2 +- plugin/trino-kinesis/pom.xml | 2 +- plugin/trino-kudu/pom.xml | 2 +- plugin/trino-local-file/pom.xml | 2 +- plugin/trino-mariadb/pom.xml | 2 +- plugin/trino-memory/pom.xml | 2 +- plugin/trino-ml/pom.xml | 2 +- plugin/trino-mongodb/pom.xml | 2 +- plugin/trino-mysql-event-listener/pom.xml | 2 +- plugin/trino-mysql/pom.xml | 2 +- plugin/trino-oracle/pom.xml | 2 +- plugin/trino-password-authenticators/pom.xml | 2 +- plugin/trino-phoenix5/pom.xml | 2 +- plugin/trino-pinot/pom.xml | 2 +- plugin/trino-postgresql/pom.xml | 2 +- plugin/trino-prometheus/pom.xml | 2 +- plugin/trino-raptor-legacy/pom.xml | 2 +- plugin/trino-redis/pom.xml | 2 +- plugin/trino-redshift/pom.xml | 2 +- plugin/trino-resource-group-managers/pom.xml | 2 +- plugin/trino-session-property-managers/pom.xml | 2 +- plugin/trino-singlestore/pom.xml | 2 +- plugin/trino-sqlserver/pom.xml | 2 +- plugin/trino-teradata-functions/pom.xml | 2 +- plugin/trino-thrift-api/pom.xml | 2 +- plugin/trino-thrift-testing-server/pom.xml | 2 +- plugin/trino-thrift/pom.xml | 2 +- plugin/trino-tpcds/pom.xml | 2 +- plugin/trino-tpch/pom.xml | 2 +- pom.xml | 4 ++-- service/trino-proxy/pom.xml | 2 +- service/trino-verifier/pom.xml | 2 +- testing/trino-benchmark-queries/pom.xml | 2 +- testing/trino-benchmark/pom.xml | 2 +- testing/trino-benchto-benchmarks/pom.xml | 2 +- testing/trino-faulttolerant-tests/pom.xml | 2 +- testing/trino-plugin-reader/pom.xml | 2 +- testing/trino-product-tests-launcher/pom.xml | 2 +- testing/trino-product-tests/pom.xml | 2 +- testing/trino-server-dev/pom.xml | 2 +- testing/trino-test-jdbc-compatibility-old-driver/pom.xml | 4 ++-- testing/trino-test-jdbc-compatibility-old-server/pom.xml | 2 +- testing/trino-testing-containers/pom.xml | 2 +- testing/trino-testing-kafka/pom.xml | 2 +- testing/trino-testing-resources/pom.xml | 2 +- testing/trino-testing-services/pom.xml | 2 +- testing/trino-testing/pom.xml | 2 +- testing/trino-tests/pom.xml | 2 +- 101 files changed, 103 insertions(+), 103 deletions(-) diff --git a/client/trino-cli/pom.xml b/client/trino-cli/pom.xml index 69554e5cc213..ddf78f36bd2a 100644 --- a/client/trino-cli/pom.xml +++ b/client/trino-cli/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/client/trino-client/pom.xml b/client/trino-client/pom.xml index 00b50a1bc5f1..9434d31e151c 100644 --- a/client/trino-client/pom.xml +++ b/client/trino-client/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/client/trino-jdbc/pom.xml b/client/trino-jdbc/pom.xml index 4cc1a21148c2..13aefca9d1e8 100644 --- a/client/trino-jdbc/pom.xml +++ b/client/trino-jdbc/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/core/trino-grammar/pom.xml b/core/trino-grammar/pom.xml index fcd77174c41c..562b348b5317 100644 --- a/core/trino-grammar/pom.xml +++ b/core/trino-grammar/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/core/trino-main/pom.xml b/core/trino-main/pom.xml index 118e99492720..a3c5848645da 100644 --- a/core/trino-main/pom.xml +++ b/core/trino-main/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/core/trino-parser/pom.xml b/core/trino-parser/pom.xml index 6ffaf3731c4f..8ceea4bee4cb 100644 --- a/core/trino-parser/pom.xml +++ b/core/trino-parser/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/core/trino-server-main/pom.xml b/core/trino-server-main/pom.xml index 2e252f3935b2..2a2568af8b64 100644 --- a/core/trino-server-main/pom.xml +++ b/core/trino-server-main/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/core/trino-server-rpm/pom.xml b/core/trino-server-rpm/pom.xml index 9b45ced3a455..b3261bdb7302 100644 --- a/core/trino-server-rpm/pom.xml +++ b/core/trino-server-rpm/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/core/trino-server/pom.xml b/core/trino-server/pom.xml index 07e339724f10..3441013c19c6 100644 --- a/core/trino-server/pom.xml +++ b/core/trino-server/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/core/trino-spi/pom.xml b/core/trino-spi/pom.xml index 84b7f29d85be..455b709d7c05 100644 --- a/core/trino-spi/pom.xml +++ b/core/trino-spi/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/docs/pom.xml b/docs/pom.xml index 2788bb53831e..d22617b03b13 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 trino-docs diff --git a/lib/trino-array/pom.xml b/lib/trino-array/pom.xml index 8b0b30ad28d2..2c42b561f86a 100644 --- a/lib/trino-array/pom.xml +++ b/lib/trino-array/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/lib/trino-cache/pom.xml b/lib/trino-cache/pom.xml index d4327b002f4c..c90e1b0582ba 100644 --- a/lib/trino-cache/pom.xml +++ b/lib/trino-cache/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/lib/trino-filesystem-azure/pom.xml b/lib/trino-filesystem-azure/pom.xml index ddac08381a87..0a2c9db2f339 100644 --- a/lib/trino-filesystem-azure/pom.xml +++ b/lib/trino-filesystem-azure/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/lib/trino-filesystem-gcs/pom.xml b/lib/trino-filesystem-gcs/pom.xml index 87a99864394f..44088afac4e3 100644 --- a/lib/trino-filesystem-gcs/pom.xml +++ b/lib/trino-filesystem-gcs/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/lib/trino-filesystem-manager/pom.xml b/lib/trino-filesystem-manager/pom.xml index 1798b5909033..26d11b053e34 100644 --- a/lib/trino-filesystem-manager/pom.xml +++ b/lib/trino-filesystem-manager/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/lib/trino-filesystem-s3/pom.xml b/lib/trino-filesystem-s3/pom.xml index 6f74fb34a30b..10e739807b1f 100644 --- a/lib/trino-filesystem-s3/pom.xml +++ b/lib/trino-filesystem-s3/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/lib/trino-filesystem/pom.xml b/lib/trino-filesystem/pom.xml index c60b97e48afc..0b2daa7db352 100644 --- a/lib/trino-filesystem/pom.xml +++ b/lib/trino-filesystem/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/lib/trino-geospatial-toolkit/pom.xml b/lib/trino-geospatial-toolkit/pom.xml index b1b81002e615..aa53d104b018 100644 --- a/lib/trino-geospatial-toolkit/pom.xml +++ b/lib/trino-geospatial-toolkit/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/lib/trino-hdfs/pom.xml b/lib/trino-hdfs/pom.xml index 3db7e77b55eb..191371803aa7 100644 --- a/lib/trino-hdfs/pom.xml +++ b/lib/trino-hdfs/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/lib/trino-hive-formats/pom.xml b/lib/trino-hive-formats/pom.xml index 0802ef69061c..24886f5fea89 100644 --- a/lib/trino-hive-formats/pom.xml +++ b/lib/trino-hive-formats/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/lib/trino-ignite-patched/pom.xml b/lib/trino-ignite-patched/pom.xml index ca10ab8a9824..e9eb7ee3184b 100644 --- a/lib/trino-ignite-patched/pom.xml +++ b/lib/trino-ignite-patched/pom.xml @@ -6,7 +6,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/lib/trino-matching/pom.xml b/lib/trino-matching/pom.xml index 0453adeea674..a7bb12575824 100644 --- a/lib/trino-matching/pom.xml +++ b/lib/trino-matching/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/lib/trino-memory-context/pom.xml b/lib/trino-memory-context/pom.xml index 8feea57c536d..275d0d9325b6 100644 --- a/lib/trino-memory-context/pom.xml +++ b/lib/trino-memory-context/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/lib/trino-orc/pom.xml b/lib/trino-orc/pom.xml index ee95acf44d69..7fc99fbc11db 100644 --- a/lib/trino-orc/pom.xml +++ b/lib/trino-orc/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/lib/trino-parquet/pom.xml b/lib/trino-parquet/pom.xml index 5854262dbb03..90c3de20c858 100644 --- a/lib/trino-parquet/pom.xml +++ b/lib/trino-parquet/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/lib/trino-phoenix5-patched/pom.xml b/lib/trino-phoenix5-patched/pom.xml index 81ae850c914f..0f89d83426f6 100644 --- a/lib/trino-phoenix5-patched/pom.xml +++ b/lib/trino-phoenix5-patched/pom.xml @@ -6,7 +6,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/lib/trino-plugin-toolkit/pom.xml b/lib/trino-plugin-toolkit/pom.xml index 9fa935df3e93..6cce7253278b 100644 --- a/lib/trino-plugin-toolkit/pom.xml +++ b/lib/trino-plugin-toolkit/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/lib/trino-record-decoder/pom.xml b/lib/trino-record-decoder/pom.xml index 6f11ff54873c..9740b0a8da25 100644 --- a/lib/trino-record-decoder/pom.xml +++ b/lib/trino-record-decoder/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-accumulo-iterators/pom.xml b/plugin/trino-accumulo-iterators/pom.xml index e18c70de37ba..a3e9fb4affe5 100644 --- a/plugin/trino-accumulo-iterators/pom.xml +++ b/plugin/trino-accumulo-iterators/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-accumulo/pom.xml b/plugin/trino-accumulo/pom.xml index b848256d4a1f..5fdf7dcd4860 100644 --- a/plugin/trino-accumulo/pom.xml +++ b/plugin/trino-accumulo/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-atop/pom.xml b/plugin/trino-atop/pom.xml index 88ff850b3b31..0231b79d8024 100644 --- a/plugin/trino-atop/pom.xml +++ b/plugin/trino-atop/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-base-jdbc/pom.xml b/plugin/trino-base-jdbc/pom.xml index e79f07328899..145b83b004fd 100644 --- a/plugin/trino-base-jdbc/pom.xml +++ b/plugin/trino-base-jdbc/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-bigquery/pom.xml b/plugin/trino-bigquery/pom.xml index 3e48836ee351..b01788d14e10 100644 --- a/plugin/trino-bigquery/pom.xml +++ b/plugin/trino-bigquery/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-blackhole/pom.xml b/plugin/trino-blackhole/pom.xml index 1d3dda8f122e..3ddca482dd22 100644 --- a/plugin/trino-blackhole/pom.xml +++ b/plugin/trino-blackhole/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-cassandra/pom.xml b/plugin/trino-cassandra/pom.xml index 2c158f601b63..5c87e1668b7e 100644 --- a/plugin/trino-cassandra/pom.xml +++ b/plugin/trino-cassandra/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-clickhouse/pom.xml b/plugin/trino-clickhouse/pom.xml index 0771635a2df5..635a9849eb0a 100644 --- a/plugin/trino-clickhouse/pom.xml +++ b/plugin/trino-clickhouse/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-delta-lake/pom.xml b/plugin/trino-delta-lake/pom.xml index faefd9387945..365b164f9ae0 100644 --- a/plugin/trino-delta-lake/pom.xml +++ b/plugin/trino-delta-lake/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-druid/pom.xml b/plugin/trino-druid/pom.xml index feefb2924c02..950b3316a846 100644 --- a/plugin/trino-druid/pom.xml +++ b/plugin/trino-druid/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-elasticsearch/pom.xml b/plugin/trino-elasticsearch/pom.xml index 38fd811c6258..86c193416333 100644 --- a/plugin/trino-elasticsearch/pom.xml +++ b/plugin/trino-elasticsearch/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-example-http/pom.xml b/plugin/trino-example-http/pom.xml index 56b9836d60e5..249b516f5e2a 100644 --- a/plugin/trino-example-http/pom.xml +++ b/plugin/trino-example-http/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-example-jdbc/pom.xml b/plugin/trino-example-jdbc/pom.xml index 5cf8bc485d10..28e4c188d723 100644 --- a/plugin/trino-example-jdbc/pom.xml +++ b/plugin/trino-example-jdbc/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-exchange-filesystem/pom.xml b/plugin/trino-exchange-filesystem/pom.xml index 03872cb61fd9..0b9c48176442 100644 --- a/plugin/trino-exchange-filesystem/pom.xml +++ b/plugin/trino-exchange-filesystem/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-exchange-hdfs/pom.xml b/plugin/trino-exchange-hdfs/pom.xml index a45e79036b98..5e092190e1da 100644 --- a/plugin/trino-exchange-hdfs/pom.xml +++ b/plugin/trino-exchange-hdfs/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-geospatial/pom.xml b/plugin/trino-geospatial/pom.xml index d81ebcf5b342..c3d219700026 100644 --- a/plugin/trino-geospatial/pom.xml +++ b/plugin/trino-geospatial/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-google-sheets/pom.xml b/plugin/trino-google-sheets/pom.xml index 55dfb239acad..bb16565ca9b4 100644 --- a/plugin/trino-google-sheets/pom.xml +++ b/plugin/trino-google-sheets/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-hive-hadoop2/pom.xml b/plugin/trino-hive-hadoop2/pom.xml index 4849d5c19df5..b9783ca27d79 100644 --- a/plugin/trino-hive-hadoop2/pom.xml +++ b/plugin/trino-hive-hadoop2/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-hive/pom.xml b/plugin/trino-hive/pom.xml index df6d0b06d72b..763080b0eca7 100644 --- a/plugin/trino-hive/pom.xml +++ b/plugin/trino-hive/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-http-event-listener/pom.xml b/plugin/trino-http-event-listener/pom.xml index 38b4e665b9ed..0d0fd8fa370f 100644 --- a/plugin/trino-http-event-listener/pom.xml +++ b/plugin/trino-http-event-listener/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-hudi/pom.xml b/plugin/trino-hudi/pom.xml index 54d4af64b672..3f4ca5ee9d67 100644 --- a/plugin/trino-hudi/pom.xml +++ b/plugin/trino-hudi/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-iceberg/pom.xml b/plugin/trino-iceberg/pom.xml index a8a9450ceefd..329b16b0b7c2 100644 --- a/plugin/trino-iceberg/pom.xml +++ b/plugin/trino-iceberg/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-ignite/pom.xml b/plugin/trino-ignite/pom.xml index 198e3dcca0b8..d8b0922896fa 100644 --- a/plugin/trino-ignite/pom.xml +++ b/plugin/trino-ignite/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-jmx/pom.xml b/plugin/trino-jmx/pom.xml index 31475ece5b33..b5def40256b4 100644 --- a/plugin/trino-jmx/pom.xml +++ b/plugin/trino-jmx/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-kafka/pom.xml b/plugin/trino-kafka/pom.xml index 44d7e7837ed5..2485b56f909d 100644 --- a/plugin/trino-kafka/pom.xml +++ b/plugin/trino-kafka/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-kinesis/pom.xml b/plugin/trino-kinesis/pom.xml index 1bc0445eaefd..e024cf181b1c 100644 --- a/plugin/trino-kinesis/pom.xml +++ b/plugin/trino-kinesis/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-kudu/pom.xml b/plugin/trino-kudu/pom.xml index 7efbf00dd725..74e86fc99e6c 100644 --- a/plugin/trino-kudu/pom.xml +++ b/plugin/trino-kudu/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-local-file/pom.xml b/plugin/trino-local-file/pom.xml index 7051d34ecc0f..9d039f7962f9 100644 --- a/plugin/trino-local-file/pom.xml +++ b/plugin/trino-local-file/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-mariadb/pom.xml b/plugin/trino-mariadb/pom.xml index bbe4cbdd3848..bcf751dac4f5 100644 --- a/plugin/trino-mariadb/pom.xml +++ b/plugin/trino-mariadb/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-memory/pom.xml b/plugin/trino-memory/pom.xml index 26b071354ab0..23d1d594f7c4 100644 --- a/plugin/trino-memory/pom.xml +++ b/plugin/trino-memory/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-ml/pom.xml b/plugin/trino-ml/pom.xml index 2ca7d210f0b7..14987ef83d72 100644 --- a/plugin/trino-ml/pom.xml +++ b/plugin/trino-ml/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-mongodb/pom.xml b/plugin/trino-mongodb/pom.xml index 8b392f7297b7..43b15c7ad16e 100644 --- a/plugin/trino-mongodb/pom.xml +++ b/plugin/trino-mongodb/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-mysql-event-listener/pom.xml b/plugin/trino-mysql-event-listener/pom.xml index f45e0901990c..912e04f7f3cd 100644 --- a/plugin/trino-mysql-event-listener/pom.xml +++ b/plugin/trino-mysql-event-listener/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-mysql/pom.xml b/plugin/trino-mysql/pom.xml index 70afe09e93f3..23afa1c99727 100644 --- a/plugin/trino-mysql/pom.xml +++ b/plugin/trino-mysql/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-oracle/pom.xml b/plugin/trino-oracle/pom.xml index 3da926630307..fe05b686b100 100644 --- a/plugin/trino-oracle/pom.xml +++ b/plugin/trino-oracle/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-password-authenticators/pom.xml b/plugin/trino-password-authenticators/pom.xml index 12e19498db40..d78a20487a13 100644 --- a/plugin/trino-password-authenticators/pom.xml +++ b/plugin/trino-password-authenticators/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-phoenix5/pom.xml b/plugin/trino-phoenix5/pom.xml index db987d630a15..b096a4f51000 100644 --- a/plugin/trino-phoenix5/pom.xml +++ b/plugin/trino-phoenix5/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-pinot/pom.xml b/plugin/trino-pinot/pom.xml index 0977e3e80c05..db8cae71da85 100755 --- a/plugin/trino-pinot/pom.xml +++ b/plugin/trino-pinot/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-postgresql/pom.xml b/plugin/trino-postgresql/pom.xml index 92fcfae35a04..8bbdeb1e15fe 100644 --- a/plugin/trino-postgresql/pom.xml +++ b/plugin/trino-postgresql/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-prometheus/pom.xml b/plugin/trino-prometheus/pom.xml index 592393725c50..70bdfba436db 100644 --- a/plugin/trino-prometheus/pom.xml +++ b/plugin/trino-prometheus/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-raptor-legacy/pom.xml b/plugin/trino-raptor-legacy/pom.xml index 20fb51b5713a..da64d65f7302 100644 --- a/plugin/trino-raptor-legacy/pom.xml +++ b/plugin/trino-raptor-legacy/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-redis/pom.xml b/plugin/trino-redis/pom.xml index 5f695ad17f92..58445088d692 100644 --- a/plugin/trino-redis/pom.xml +++ b/plugin/trino-redis/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-redshift/pom.xml b/plugin/trino-redshift/pom.xml index 8d276806cd07..08b34dc54231 100644 --- a/plugin/trino-redshift/pom.xml +++ b/plugin/trino-redshift/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-resource-group-managers/pom.xml b/plugin/trino-resource-group-managers/pom.xml index c81e30caceee..08bbde7d0582 100644 --- a/plugin/trino-resource-group-managers/pom.xml +++ b/plugin/trino-resource-group-managers/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-session-property-managers/pom.xml b/plugin/trino-session-property-managers/pom.xml index 264e0515f5a0..39bf8bd8143c 100644 --- a/plugin/trino-session-property-managers/pom.xml +++ b/plugin/trino-session-property-managers/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-singlestore/pom.xml b/plugin/trino-singlestore/pom.xml index 06548c38991d..00fec503042f 100644 --- a/plugin/trino-singlestore/pom.xml +++ b/plugin/trino-singlestore/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-sqlserver/pom.xml b/plugin/trino-sqlserver/pom.xml index a11dbd1b9e08..c4d62bd2c423 100644 --- a/plugin/trino-sqlserver/pom.xml +++ b/plugin/trino-sqlserver/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-teradata-functions/pom.xml b/plugin/trino-teradata-functions/pom.xml index 6485c1b71e24..c2629ad01ca8 100644 --- a/plugin/trino-teradata-functions/pom.xml +++ b/plugin/trino-teradata-functions/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-thrift-api/pom.xml b/plugin/trino-thrift-api/pom.xml index 0a2ab27c502a..aae8e5116fe5 100644 --- a/plugin/trino-thrift-api/pom.xml +++ b/plugin/trino-thrift-api/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-thrift-testing-server/pom.xml b/plugin/trino-thrift-testing-server/pom.xml index 6ddba66b16fb..949c3167e881 100644 --- a/plugin/trino-thrift-testing-server/pom.xml +++ b/plugin/trino-thrift-testing-server/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-thrift/pom.xml b/plugin/trino-thrift/pom.xml index 5d9d66d84c21..76bed642132c 100644 --- a/plugin/trino-thrift/pom.xml +++ b/plugin/trino-thrift/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-tpcds/pom.xml b/plugin/trino-tpcds/pom.xml index 089219ed9c5a..7f6097a32daa 100644 --- a/plugin/trino-tpcds/pom.xml +++ b/plugin/trino-tpcds/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/plugin/trino-tpch/pom.xml b/plugin/trino-tpch/pom.xml index ea9309552c38..dd3067b08efb 100644 --- a/plugin/trino-tpch/pom.xml +++ b/plugin/trino-tpch/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/pom.xml b/pom.xml index 5e62a530e995..55902afc0723 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 pom ${project.artifactId} @@ -134,7 +134,7 @@ scm:git:git://github.com/trinodb/trino.git - HEAD + 433 https://github.com/trinodb/trino diff --git a/service/trino-proxy/pom.xml b/service/trino-proxy/pom.xml index e28d14e54d45..18e9c91354fc 100644 --- a/service/trino-proxy/pom.xml +++ b/service/trino-proxy/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/service/trino-verifier/pom.xml b/service/trino-verifier/pom.xml index e0e05ff74dbe..0b8806f636d6 100644 --- a/service/trino-verifier/pom.xml +++ b/service/trino-verifier/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/testing/trino-benchmark-queries/pom.xml b/testing/trino-benchmark-queries/pom.xml index 72b53b86065a..c1aeadb637e4 100644 --- a/testing/trino-benchmark-queries/pom.xml +++ b/testing/trino-benchmark-queries/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/testing/trino-benchmark/pom.xml b/testing/trino-benchmark/pom.xml index 7430833a0bc3..507772ba7893 100644 --- a/testing/trino-benchmark/pom.xml +++ b/testing/trino-benchmark/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/testing/trino-benchto-benchmarks/pom.xml b/testing/trino-benchto-benchmarks/pom.xml index 632c3215d112..631ae38f94f4 100644 --- a/testing/trino-benchto-benchmarks/pom.xml +++ b/testing/trino-benchto-benchmarks/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/testing/trino-faulttolerant-tests/pom.xml b/testing/trino-faulttolerant-tests/pom.xml index b0e8eea800a8..ad9dcc689e57 100644 --- a/testing/trino-faulttolerant-tests/pom.xml +++ b/testing/trino-faulttolerant-tests/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/testing/trino-plugin-reader/pom.xml b/testing/trino-plugin-reader/pom.xml index 9e44e61e0d5a..50e6c694b1b4 100644 --- a/testing/trino-plugin-reader/pom.xml +++ b/testing/trino-plugin-reader/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/testing/trino-product-tests-launcher/pom.xml b/testing/trino-product-tests-launcher/pom.xml index 55bb40ff257d..9a31f5d28cf2 100644 --- a/testing/trino-product-tests-launcher/pom.xml +++ b/testing/trino-product-tests-launcher/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/testing/trino-product-tests/pom.xml b/testing/trino-product-tests/pom.xml index 13ad231c34d4..754b95dfcf03 100644 --- a/testing/trino-product-tests/pom.xml +++ b/testing/trino-product-tests/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/testing/trino-server-dev/pom.xml b/testing/trino-server-dev/pom.xml index aaa637d3a7ff..2f4a3b0ad88e 100644 --- a/testing/trino-server-dev/pom.xml +++ b/testing/trino-server-dev/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/testing/trino-test-jdbc-compatibility-old-driver/pom.xml b/testing/trino-test-jdbc-compatibility-old-driver/pom.xml index 6f30e13c4a08..9e59a9ddc439 100644 --- a/testing/trino-test-jdbc-compatibility-old-driver/pom.xml +++ b/testing/trino-test-jdbc-compatibility-old-driver/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml @@ -15,7 +15,7 @@ ${project.parent.basedir} - 433-SNAPSHOT + 433 diff --git a/testing/trino-test-jdbc-compatibility-old-server/pom.xml b/testing/trino-test-jdbc-compatibility-old-server/pom.xml index cd717fb5f929..72f4c49c9391 100644 --- a/testing/trino-test-jdbc-compatibility-old-server/pom.xml +++ b/testing/trino-test-jdbc-compatibility-old-server/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/testing/trino-testing-containers/pom.xml b/testing/trino-testing-containers/pom.xml index 9a5b4421ecbf..56f872568ba4 100644 --- a/testing/trino-testing-containers/pom.xml +++ b/testing/trino-testing-containers/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/testing/trino-testing-kafka/pom.xml b/testing/trino-testing-kafka/pom.xml index 4be42376bb63..0689bd00b2c4 100644 --- a/testing/trino-testing-kafka/pom.xml +++ b/testing/trino-testing-kafka/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/testing/trino-testing-resources/pom.xml b/testing/trino-testing-resources/pom.xml index 30c4746e114f..d7fb9d958562 100644 --- a/testing/trino-testing-resources/pom.xml +++ b/testing/trino-testing-resources/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/testing/trino-testing-services/pom.xml b/testing/trino-testing-services/pom.xml index 71800e189a96..2da0c07a11c2 100644 --- a/testing/trino-testing-services/pom.xml +++ b/testing/trino-testing-services/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/testing/trino-testing/pom.xml b/testing/trino-testing/pom.xml index d0d927660091..1c1256cabb3d 100644 --- a/testing/trino-testing/pom.xml +++ b/testing/trino-testing/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml diff --git a/testing/trino-tests/pom.xml b/testing/trino-tests/pom.xml index 27ed608fbc64..5140e32feef3 100644 --- a/testing/trino-tests/pom.xml +++ b/testing/trino-tests/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433-SNAPSHOT + 433 ../../pom.xml From 8969ecfa20c39df4cf9ce1ef384c010636365d1e Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Fri, 10 Nov 2023 19:44:28 +0000 Subject: [PATCH 190/587] [maven-release-plugin] prepare for next development iteration --- client/trino-cli/pom.xml | 2 +- client/trino-client/pom.xml | 2 +- client/trino-jdbc/pom.xml | 2 +- core/trino-grammar/pom.xml | 2 +- core/trino-main/pom.xml | 2 +- core/trino-parser/pom.xml | 2 +- core/trino-server-main/pom.xml | 2 +- core/trino-server-rpm/pom.xml | 2 +- core/trino-server/pom.xml | 2 +- core/trino-spi/pom.xml | 2 +- docs/pom.xml | 2 +- lib/trino-array/pom.xml | 2 +- lib/trino-cache/pom.xml | 2 +- lib/trino-filesystem-azure/pom.xml | 2 +- lib/trino-filesystem-gcs/pom.xml | 2 +- lib/trino-filesystem-manager/pom.xml | 2 +- lib/trino-filesystem-s3/pom.xml | 2 +- lib/trino-filesystem/pom.xml | 2 +- lib/trino-geospatial-toolkit/pom.xml | 2 +- lib/trino-hdfs/pom.xml | 2 +- lib/trino-hive-formats/pom.xml | 2 +- lib/trino-ignite-patched/pom.xml | 2 +- lib/trino-matching/pom.xml | 2 +- lib/trino-memory-context/pom.xml | 2 +- lib/trino-orc/pom.xml | 2 +- lib/trino-parquet/pom.xml | 2 +- lib/trino-phoenix5-patched/pom.xml | 2 +- lib/trino-plugin-toolkit/pom.xml | 2 +- lib/trino-record-decoder/pom.xml | 2 +- plugin/trino-accumulo-iterators/pom.xml | 2 +- plugin/trino-accumulo/pom.xml | 2 +- plugin/trino-atop/pom.xml | 2 +- plugin/trino-base-jdbc/pom.xml | 2 +- plugin/trino-bigquery/pom.xml | 2 +- plugin/trino-blackhole/pom.xml | 2 +- plugin/trino-cassandra/pom.xml | 2 +- plugin/trino-clickhouse/pom.xml | 2 +- plugin/trino-delta-lake/pom.xml | 2 +- plugin/trino-druid/pom.xml | 2 +- plugin/trino-elasticsearch/pom.xml | 2 +- plugin/trino-example-http/pom.xml | 2 +- plugin/trino-example-jdbc/pom.xml | 2 +- plugin/trino-exchange-filesystem/pom.xml | 2 +- plugin/trino-exchange-hdfs/pom.xml | 2 +- plugin/trino-geospatial/pom.xml | 2 +- plugin/trino-google-sheets/pom.xml | 2 +- plugin/trino-hive-hadoop2/pom.xml | 2 +- plugin/trino-hive/pom.xml | 2 +- plugin/trino-http-event-listener/pom.xml | 2 +- plugin/trino-hudi/pom.xml | 2 +- plugin/trino-iceberg/pom.xml | 2 +- plugin/trino-ignite/pom.xml | 2 +- plugin/trino-jmx/pom.xml | 2 +- plugin/trino-kafka/pom.xml | 2 +- plugin/trino-kinesis/pom.xml | 2 +- plugin/trino-kudu/pom.xml | 2 +- plugin/trino-local-file/pom.xml | 2 +- plugin/trino-mariadb/pom.xml | 2 +- plugin/trino-memory/pom.xml | 2 +- plugin/trino-ml/pom.xml | 2 +- plugin/trino-mongodb/pom.xml | 2 +- plugin/trino-mysql-event-listener/pom.xml | 2 +- plugin/trino-mysql/pom.xml | 2 +- plugin/trino-oracle/pom.xml | 2 +- plugin/trino-password-authenticators/pom.xml | 2 +- plugin/trino-phoenix5/pom.xml | 2 +- plugin/trino-pinot/pom.xml | 2 +- plugin/trino-postgresql/pom.xml | 2 +- plugin/trino-prometheus/pom.xml | 2 +- plugin/trino-raptor-legacy/pom.xml | 2 +- plugin/trino-redis/pom.xml | 2 +- plugin/trino-redshift/pom.xml | 2 +- plugin/trino-resource-group-managers/pom.xml | 2 +- plugin/trino-session-property-managers/pom.xml | 2 +- plugin/trino-singlestore/pom.xml | 2 +- plugin/trino-sqlserver/pom.xml | 2 +- plugin/trino-teradata-functions/pom.xml | 2 +- plugin/trino-thrift-api/pom.xml | 2 +- plugin/trino-thrift-testing-server/pom.xml | 2 +- plugin/trino-thrift/pom.xml | 2 +- plugin/trino-tpcds/pom.xml | 2 +- plugin/trino-tpch/pom.xml | 2 +- pom.xml | 4 ++-- service/trino-proxy/pom.xml | 2 +- service/trino-verifier/pom.xml | 2 +- testing/trino-benchmark-queries/pom.xml | 2 +- testing/trino-benchmark/pom.xml | 2 +- testing/trino-benchto-benchmarks/pom.xml | 2 +- testing/trino-faulttolerant-tests/pom.xml | 2 +- testing/trino-plugin-reader/pom.xml | 2 +- testing/trino-product-tests-launcher/pom.xml | 2 +- testing/trino-product-tests/pom.xml | 2 +- testing/trino-server-dev/pom.xml | 2 +- testing/trino-test-jdbc-compatibility-old-driver/pom.xml | 4 ++-- testing/trino-test-jdbc-compatibility-old-server/pom.xml | 2 +- testing/trino-testing-containers/pom.xml | 2 +- testing/trino-testing-kafka/pom.xml | 2 +- testing/trino-testing-resources/pom.xml | 2 +- testing/trino-testing-services/pom.xml | 2 +- testing/trino-testing/pom.xml | 2 +- testing/trino-tests/pom.xml | 2 +- 101 files changed, 103 insertions(+), 103 deletions(-) diff --git a/client/trino-cli/pom.xml b/client/trino-cli/pom.xml index ddf78f36bd2a..992bd6d8b3a4 100644 --- a/client/trino-cli/pom.xml +++ b/client/trino-cli/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/client/trino-client/pom.xml b/client/trino-client/pom.xml index 9434d31e151c..9c7a41e5e7d7 100644 --- a/client/trino-client/pom.xml +++ b/client/trino-client/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/client/trino-jdbc/pom.xml b/client/trino-jdbc/pom.xml index 13aefca9d1e8..e1a8bb5a67d6 100644 --- a/client/trino-jdbc/pom.xml +++ b/client/trino-jdbc/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/core/trino-grammar/pom.xml b/core/trino-grammar/pom.xml index 562b348b5317..947776a7c73f 100644 --- a/core/trino-grammar/pom.xml +++ b/core/trino-grammar/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/core/trino-main/pom.xml b/core/trino-main/pom.xml index a3c5848645da..f23fc75b47b6 100644 --- a/core/trino-main/pom.xml +++ b/core/trino-main/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/core/trino-parser/pom.xml b/core/trino-parser/pom.xml index 8ceea4bee4cb..e49585ea2a30 100644 --- a/core/trino-parser/pom.xml +++ b/core/trino-parser/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/core/trino-server-main/pom.xml b/core/trino-server-main/pom.xml index 2a2568af8b64..a479f818e695 100644 --- a/core/trino-server-main/pom.xml +++ b/core/trino-server-main/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/core/trino-server-rpm/pom.xml b/core/trino-server-rpm/pom.xml index b3261bdb7302..a62f70bcc8b7 100644 --- a/core/trino-server-rpm/pom.xml +++ b/core/trino-server-rpm/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/core/trino-server/pom.xml b/core/trino-server/pom.xml index 3441013c19c6..60ce1b9ab247 100644 --- a/core/trino-server/pom.xml +++ b/core/trino-server/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/core/trino-spi/pom.xml b/core/trino-spi/pom.xml index 455b709d7c05..7a3175bfeb04 100644 --- a/core/trino-spi/pom.xml +++ b/core/trino-spi/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/docs/pom.xml b/docs/pom.xml index d22617b03b13..2e6bfa8b5f52 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT trino-docs diff --git a/lib/trino-array/pom.xml b/lib/trino-array/pom.xml index 2c42b561f86a..46a55ab0baef 100644 --- a/lib/trino-array/pom.xml +++ b/lib/trino-array/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/lib/trino-cache/pom.xml b/lib/trino-cache/pom.xml index c90e1b0582ba..85e21dda64ff 100644 --- a/lib/trino-cache/pom.xml +++ b/lib/trino-cache/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/lib/trino-filesystem-azure/pom.xml b/lib/trino-filesystem-azure/pom.xml index 0a2c9db2f339..bef5ce798b50 100644 --- a/lib/trino-filesystem-azure/pom.xml +++ b/lib/trino-filesystem-azure/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/lib/trino-filesystem-gcs/pom.xml b/lib/trino-filesystem-gcs/pom.xml index 44088afac4e3..e600136e5b79 100644 --- a/lib/trino-filesystem-gcs/pom.xml +++ b/lib/trino-filesystem-gcs/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/lib/trino-filesystem-manager/pom.xml b/lib/trino-filesystem-manager/pom.xml index 26d11b053e34..8b86115c9acf 100644 --- a/lib/trino-filesystem-manager/pom.xml +++ b/lib/trino-filesystem-manager/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/lib/trino-filesystem-s3/pom.xml b/lib/trino-filesystem-s3/pom.xml index 10e739807b1f..77c262b50d05 100644 --- a/lib/trino-filesystem-s3/pom.xml +++ b/lib/trino-filesystem-s3/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/lib/trino-filesystem/pom.xml b/lib/trino-filesystem/pom.xml index 0b2daa7db352..a50bb94038a5 100644 --- a/lib/trino-filesystem/pom.xml +++ b/lib/trino-filesystem/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/lib/trino-geospatial-toolkit/pom.xml b/lib/trino-geospatial-toolkit/pom.xml index aa53d104b018..51a494da461b 100644 --- a/lib/trino-geospatial-toolkit/pom.xml +++ b/lib/trino-geospatial-toolkit/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/lib/trino-hdfs/pom.xml b/lib/trino-hdfs/pom.xml index 191371803aa7..e2efb802453c 100644 --- a/lib/trino-hdfs/pom.xml +++ b/lib/trino-hdfs/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/lib/trino-hive-formats/pom.xml b/lib/trino-hive-formats/pom.xml index 24886f5fea89..c65f3c421e51 100644 --- a/lib/trino-hive-formats/pom.xml +++ b/lib/trino-hive-formats/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/lib/trino-ignite-patched/pom.xml b/lib/trino-ignite-patched/pom.xml index e9eb7ee3184b..b30e46136b3d 100644 --- a/lib/trino-ignite-patched/pom.xml +++ b/lib/trino-ignite-patched/pom.xml @@ -6,7 +6,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/lib/trino-matching/pom.xml b/lib/trino-matching/pom.xml index a7bb12575824..a76164583863 100644 --- a/lib/trino-matching/pom.xml +++ b/lib/trino-matching/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/lib/trino-memory-context/pom.xml b/lib/trino-memory-context/pom.xml index 275d0d9325b6..61edf3225777 100644 --- a/lib/trino-memory-context/pom.xml +++ b/lib/trino-memory-context/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/lib/trino-orc/pom.xml b/lib/trino-orc/pom.xml index 7fc99fbc11db..291eba2adb92 100644 --- a/lib/trino-orc/pom.xml +++ b/lib/trino-orc/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/lib/trino-parquet/pom.xml b/lib/trino-parquet/pom.xml index 90c3de20c858..088007c94057 100644 --- a/lib/trino-parquet/pom.xml +++ b/lib/trino-parquet/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/lib/trino-phoenix5-patched/pom.xml b/lib/trino-phoenix5-patched/pom.xml index 0f89d83426f6..ef10f9c260b2 100644 --- a/lib/trino-phoenix5-patched/pom.xml +++ b/lib/trino-phoenix5-patched/pom.xml @@ -6,7 +6,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/lib/trino-plugin-toolkit/pom.xml b/lib/trino-plugin-toolkit/pom.xml index 6cce7253278b..417414ae245c 100644 --- a/lib/trino-plugin-toolkit/pom.xml +++ b/lib/trino-plugin-toolkit/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/lib/trino-record-decoder/pom.xml b/lib/trino-record-decoder/pom.xml index 9740b0a8da25..f33c5f9d51fd 100644 --- a/lib/trino-record-decoder/pom.xml +++ b/lib/trino-record-decoder/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-accumulo-iterators/pom.xml b/plugin/trino-accumulo-iterators/pom.xml index a3e9fb4affe5..036f65981a72 100644 --- a/plugin/trino-accumulo-iterators/pom.xml +++ b/plugin/trino-accumulo-iterators/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-accumulo/pom.xml b/plugin/trino-accumulo/pom.xml index 5fdf7dcd4860..1d96733b1626 100644 --- a/plugin/trino-accumulo/pom.xml +++ b/plugin/trino-accumulo/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-atop/pom.xml b/plugin/trino-atop/pom.xml index 0231b79d8024..d957e286b88d 100644 --- a/plugin/trino-atop/pom.xml +++ b/plugin/trino-atop/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-base-jdbc/pom.xml b/plugin/trino-base-jdbc/pom.xml index 145b83b004fd..724676bc88c9 100644 --- a/plugin/trino-base-jdbc/pom.xml +++ b/plugin/trino-base-jdbc/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-bigquery/pom.xml b/plugin/trino-bigquery/pom.xml index b01788d14e10..a8f6083349a8 100644 --- a/plugin/trino-bigquery/pom.xml +++ b/plugin/trino-bigquery/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-blackhole/pom.xml b/plugin/trino-blackhole/pom.xml index 3ddca482dd22..921a84fdc143 100644 --- a/plugin/trino-blackhole/pom.xml +++ b/plugin/trino-blackhole/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-cassandra/pom.xml b/plugin/trino-cassandra/pom.xml index 5c87e1668b7e..22c7d426e5f3 100644 --- a/plugin/trino-cassandra/pom.xml +++ b/plugin/trino-cassandra/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-clickhouse/pom.xml b/plugin/trino-clickhouse/pom.xml index 635a9849eb0a..562f2bfb0cdd 100644 --- a/plugin/trino-clickhouse/pom.xml +++ b/plugin/trino-clickhouse/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-delta-lake/pom.xml b/plugin/trino-delta-lake/pom.xml index 365b164f9ae0..19ba1084696a 100644 --- a/plugin/trino-delta-lake/pom.xml +++ b/plugin/trino-delta-lake/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-druid/pom.xml b/plugin/trino-druid/pom.xml index 950b3316a846..c85fe5d7d2e6 100644 --- a/plugin/trino-druid/pom.xml +++ b/plugin/trino-druid/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-elasticsearch/pom.xml b/plugin/trino-elasticsearch/pom.xml index 86c193416333..7fb5d81fb87b 100644 --- a/plugin/trino-elasticsearch/pom.xml +++ b/plugin/trino-elasticsearch/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-example-http/pom.xml b/plugin/trino-example-http/pom.xml index 249b516f5e2a..3224a64921e9 100644 --- a/plugin/trino-example-http/pom.xml +++ b/plugin/trino-example-http/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-example-jdbc/pom.xml b/plugin/trino-example-jdbc/pom.xml index 28e4c188d723..b9c25edd1dc9 100644 --- a/plugin/trino-example-jdbc/pom.xml +++ b/plugin/trino-example-jdbc/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-exchange-filesystem/pom.xml b/plugin/trino-exchange-filesystem/pom.xml index 0b9c48176442..7c41064d5e05 100644 --- a/plugin/trino-exchange-filesystem/pom.xml +++ b/plugin/trino-exchange-filesystem/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-exchange-hdfs/pom.xml b/plugin/trino-exchange-hdfs/pom.xml index 5e092190e1da..f1dfb810ed67 100644 --- a/plugin/trino-exchange-hdfs/pom.xml +++ b/plugin/trino-exchange-hdfs/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-geospatial/pom.xml b/plugin/trino-geospatial/pom.xml index c3d219700026..1d0d11382103 100644 --- a/plugin/trino-geospatial/pom.xml +++ b/plugin/trino-geospatial/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-google-sheets/pom.xml b/plugin/trino-google-sheets/pom.xml index bb16565ca9b4..5b177c27fc2b 100644 --- a/plugin/trino-google-sheets/pom.xml +++ b/plugin/trino-google-sheets/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-hive-hadoop2/pom.xml b/plugin/trino-hive-hadoop2/pom.xml index b9783ca27d79..e18ca769a954 100644 --- a/plugin/trino-hive-hadoop2/pom.xml +++ b/plugin/trino-hive-hadoop2/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-hive/pom.xml b/plugin/trino-hive/pom.xml index 763080b0eca7..c91a4f9ab119 100644 --- a/plugin/trino-hive/pom.xml +++ b/plugin/trino-hive/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-http-event-listener/pom.xml b/plugin/trino-http-event-listener/pom.xml index 0d0fd8fa370f..f7b33e5a2f2e 100644 --- a/plugin/trino-http-event-listener/pom.xml +++ b/plugin/trino-http-event-listener/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-hudi/pom.xml b/plugin/trino-hudi/pom.xml index 3f4ca5ee9d67..8b7b8545a182 100644 --- a/plugin/trino-hudi/pom.xml +++ b/plugin/trino-hudi/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-iceberg/pom.xml b/plugin/trino-iceberg/pom.xml index 329b16b0b7c2..32e0f0225426 100644 --- a/plugin/trino-iceberg/pom.xml +++ b/plugin/trino-iceberg/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-ignite/pom.xml b/plugin/trino-ignite/pom.xml index d8b0922896fa..fdd28fc8cfad 100644 --- a/plugin/trino-ignite/pom.xml +++ b/plugin/trino-ignite/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-jmx/pom.xml b/plugin/trino-jmx/pom.xml index b5def40256b4..4216f7e105e2 100644 --- a/plugin/trino-jmx/pom.xml +++ b/plugin/trino-jmx/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-kafka/pom.xml b/plugin/trino-kafka/pom.xml index 2485b56f909d..7c9c9e983435 100644 --- a/plugin/trino-kafka/pom.xml +++ b/plugin/trino-kafka/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-kinesis/pom.xml b/plugin/trino-kinesis/pom.xml index e024cf181b1c..1d28d1dbbf63 100644 --- a/plugin/trino-kinesis/pom.xml +++ b/plugin/trino-kinesis/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-kudu/pom.xml b/plugin/trino-kudu/pom.xml index 74e86fc99e6c..b08e9e5c280f 100644 --- a/plugin/trino-kudu/pom.xml +++ b/plugin/trino-kudu/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-local-file/pom.xml b/plugin/trino-local-file/pom.xml index 9d039f7962f9..81fc487e685b 100644 --- a/plugin/trino-local-file/pom.xml +++ b/plugin/trino-local-file/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-mariadb/pom.xml b/plugin/trino-mariadb/pom.xml index bcf751dac4f5..35f782cf91fd 100644 --- a/plugin/trino-mariadb/pom.xml +++ b/plugin/trino-mariadb/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-memory/pom.xml b/plugin/trino-memory/pom.xml index 23d1d594f7c4..9fe592d7078b 100644 --- a/plugin/trino-memory/pom.xml +++ b/plugin/trino-memory/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-ml/pom.xml b/plugin/trino-ml/pom.xml index 14987ef83d72..fc3ddee19a0e 100644 --- a/plugin/trino-ml/pom.xml +++ b/plugin/trino-ml/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-mongodb/pom.xml b/plugin/trino-mongodb/pom.xml index 43b15c7ad16e..92966117d0cd 100644 --- a/plugin/trino-mongodb/pom.xml +++ b/plugin/trino-mongodb/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-mysql-event-listener/pom.xml b/plugin/trino-mysql-event-listener/pom.xml index 912e04f7f3cd..379f74264580 100644 --- a/plugin/trino-mysql-event-listener/pom.xml +++ b/plugin/trino-mysql-event-listener/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-mysql/pom.xml b/plugin/trino-mysql/pom.xml index 23afa1c99727..12c3a606c23f 100644 --- a/plugin/trino-mysql/pom.xml +++ b/plugin/trino-mysql/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-oracle/pom.xml b/plugin/trino-oracle/pom.xml index fe05b686b100..eaf458272ee3 100644 --- a/plugin/trino-oracle/pom.xml +++ b/plugin/trino-oracle/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-password-authenticators/pom.xml b/plugin/trino-password-authenticators/pom.xml index d78a20487a13..c4ad8ff355c4 100644 --- a/plugin/trino-password-authenticators/pom.xml +++ b/plugin/trino-password-authenticators/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-phoenix5/pom.xml b/plugin/trino-phoenix5/pom.xml index b096a4f51000..9e43dfe08959 100644 --- a/plugin/trino-phoenix5/pom.xml +++ b/plugin/trino-phoenix5/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-pinot/pom.xml b/plugin/trino-pinot/pom.xml index db8cae71da85..59edd39af6e5 100755 --- a/plugin/trino-pinot/pom.xml +++ b/plugin/trino-pinot/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-postgresql/pom.xml b/plugin/trino-postgresql/pom.xml index 8bbdeb1e15fe..fb6b2fec4ef5 100644 --- a/plugin/trino-postgresql/pom.xml +++ b/plugin/trino-postgresql/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-prometheus/pom.xml b/plugin/trino-prometheus/pom.xml index 70bdfba436db..f0b5b170b17b 100644 --- a/plugin/trino-prometheus/pom.xml +++ b/plugin/trino-prometheus/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-raptor-legacy/pom.xml b/plugin/trino-raptor-legacy/pom.xml index da64d65f7302..a44d92ce6bff 100644 --- a/plugin/trino-raptor-legacy/pom.xml +++ b/plugin/trino-raptor-legacy/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-redis/pom.xml b/plugin/trino-redis/pom.xml index 58445088d692..b7db43f7912d 100644 --- a/plugin/trino-redis/pom.xml +++ b/plugin/trino-redis/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-redshift/pom.xml b/plugin/trino-redshift/pom.xml index 08b34dc54231..7f47fcaf3700 100644 --- a/plugin/trino-redshift/pom.xml +++ b/plugin/trino-redshift/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-resource-group-managers/pom.xml b/plugin/trino-resource-group-managers/pom.xml index 08bbde7d0582..38ba2d150b32 100644 --- a/plugin/trino-resource-group-managers/pom.xml +++ b/plugin/trino-resource-group-managers/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-session-property-managers/pom.xml b/plugin/trino-session-property-managers/pom.xml index 39bf8bd8143c..c5fad219ba53 100644 --- a/plugin/trino-session-property-managers/pom.xml +++ b/plugin/trino-session-property-managers/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-singlestore/pom.xml b/plugin/trino-singlestore/pom.xml index 00fec503042f..5038f4b47417 100644 --- a/plugin/trino-singlestore/pom.xml +++ b/plugin/trino-singlestore/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-sqlserver/pom.xml b/plugin/trino-sqlserver/pom.xml index c4d62bd2c423..a9781ff81f7a 100644 --- a/plugin/trino-sqlserver/pom.xml +++ b/plugin/trino-sqlserver/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-teradata-functions/pom.xml b/plugin/trino-teradata-functions/pom.xml index c2629ad01ca8..9b2f36af51bd 100644 --- a/plugin/trino-teradata-functions/pom.xml +++ b/plugin/trino-teradata-functions/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-thrift-api/pom.xml b/plugin/trino-thrift-api/pom.xml index aae8e5116fe5..544f0ee62bac 100644 --- a/plugin/trino-thrift-api/pom.xml +++ b/plugin/trino-thrift-api/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-thrift-testing-server/pom.xml b/plugin/trino-thrift-testing-server/pom.xml index 949c3167e881..ae5d0d67627f 100644 --- a/plugin/trino-thrift-testing-server/pom.xml +++ b/plugin/trino-thrift-testing-server/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-thrift/pom.xml b/plugin/trino-thrift/pom.xml index 76bed642132c..480aab9517ce 100644 --- a/plugin/trino-thrift/pom.xml +++ b/plugin/trino-thrift/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-tpcds/pom.xml b/plugin/trino-tpcds/pom.xml index 7f6097a32daa..48ef05bdc128 100644 --- a/plugin/trino-tpcds/pom.xml +++ b/plugin/trino-tpcds/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-tpch/pom.xml b/plugin/trino-tpch/pom.xml index dd3067b08efb..cd38bf7d17f4 100644 --- a/plugin/trino-tpch/pom.xml +++ b/plugin/trino-tpch/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/pom.xml b/pom.xml index 55902afc0723..2768711e83b0 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT pom ${project.artifactId} @@ -134,7 +134,7 @@ scm:git:git://github.com/trinodb/trino.git - 433 + HEAD https://github.com/trinodb/trino diff --git a/service/trino-proxy/pom.xml b/service/trino-proxy/pom.xml index 18e9c91354fc..0eb97deb6968 100644 --- a/service/trino-proxy/pom.xml +++ b/service/trino-proxy/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/service/trino-verifier/pom.xml b/service/trino-verifier/pom.xml index 0b8806f636d6..a0b7df542328 100644 --- a/service/trino-verifier/pom.xml +++ b/service/trino-verifier/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/testing/trino-benchmark-queries/pom.xml b/testing/trino-benchmark-queries/pom.xml index c1aeadb637e4..b9224c4808bd 100644 --- a/testing/trino-benchmark-queries/pom.xml +++ b/testing/trino-benchmark-queries/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/testing/trino-benchmark/pom.xml b/testing/trino-benchmark/pom.xml index 507772ba7893..2e715baae1e6 100644 --- a/testing/trino-benchmark/pom.xml +++ b/testing/trino-benchmark/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/testing/trino-benchto-benchmarks/pom.xml b/testing/trino-benchto-benchmarks/pom.xml index 631ae38f94f4..1142f3cc06dd 100644 --- a/testing/trino-benchto-benchmarks/pom.xml +++ b/testing/trino-benchto-benchmarks/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/testing/trino-faulttolerant-tests/pom.xml b/testing/trino-faulttolerant-tests/pom.xml index ad9dcc689e57..4df8c4a8066d 100644 --- a/testing/trino-faulttolerant-tests/pom.xml +++ b/testing/trino-faulttolerant-tests/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/testing/trino-plugin-reader/pom.xml b/testing/trino-plugin-reader/pom.xml index 50e6c694b1b4..6970b8f4d9ea 100644 --- a/testing/trino-plugin-reader/pom.xml +++ b/testing/trino-plugin-reader/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/testing/trino-product-tests-launcher/pom.xml b/testing/trino-product-tests-launcher/pom.xml index 9a31f5d28cf2..bb633ab5c769 100644 --- a/testing/trino-product-tests-launcher/pom.xml +++ b/testing/trino-product-tests-launcher/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/testing/trino-product-tests/pom.xml b/testing/trino-product-tests/pom.xml index 754b95dfcf03..3e499b85abff 100644 --- a/testing/trino-product-tests/pom.xml +++ b/testing/trino-product-tests/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/testing/trino-server-dev/pom.xml b/testing/trino-server-dev/pom.xml index 2f4a3b0ad88e..3e8636a9417b 100644 --- a/testing/trino-server-dev/pom.xml +++ b/testing/trino-server-dev/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/testing/trino-test-jdbc-compatibility-old-driver/pom.xml b/testing/trino-test-jdbc-compatibility-old-driver/pom.xml index 9e59a9ddc439..4df6ede5e6b8 100644 --- a/testing/trino-test-jdbc-compatibility-old-driver/pom.xml +++ b/testing/trino-test-jdbc-compatibility-old-driver/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml @@ -15,7 +15,7 @@ ${project.parent.basedir} - 433 + 434-SNAPSHOT diff --git a/testing/trino-test-jdbc-compatibility-old-server/pom.xml b/testing/trino-test-jdbc-compatibility-old-server/pom.xml index 72f4c49c9391..9cd6a17b2dd0 100644 --- a/testing/trino-test-jdbc-compatibility-old-server/pom.xml +++ b/testing/trino-test-jdbc-compatibility-old-server/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/testing/trino-testing-containers/pom.xml b/testing/trino-testing-containers/pom.xml index 56f872568ba4..fb18ab47fcc1 100644 --- a/testing/trino-testing-containers/pom.xml +++ b/testing/trino-testing-containers/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/testing/trino-testing-kafka/pom.xml b/testing/trino-testing-kafka/pom.xml index 0689bd00b2c4..5c240f4d9723 100644 --- a/testing/trino-testing-kafka/pom.xml +++ b/testing/trino-testing-kafka/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/testing/trino-testing-resources/pom.xml b/testing/trino-testing-resources/pom.xml index d7fb9d958562..5b47b511eac1 100644 --- a/testing/trino-testing-resources/pom.xml +++ b/testing/trino-testing-resources/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/testing/trino-testing-services/pom.xml b/testing/trino-testing-services/pom.xml index 2da0c07a11c2..d5f6ac13c4ec 100644 --- a/testing/trino-testing-services/pom.xml +++ b/testing/trino-testing-services/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/testing/trino-testing/pom.xml b/testing/trino-testing/pom.xml index 1c1256cabb3d..ef4edb6d5711 100644 --- a/testing/trino-testing/pom.xml +++ b/testing/trino-testing/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml diff --git a/testing/trino-tests/pom.xml b/testing/trino-tests/pom.xml index 5140e32feef3..3e7eeb39666c 100644 --- a/testing/trino-tests/pom.xml +++ b/testing/trino-tests/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 433 + 434-SNAPSHOT ../../pom.xml From 32d69a599f49c028cde95f1f5a7948afef8371c1 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Thu, 9 Nov 2023 16:18:49 +0100 Subject: [PATCH 191/587] Remove unused fields from test class --- .../deltalake/TestDeltaLakeMetadata.java | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeMetadata.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeMetadata.java index 4f6e5fad33b5..a69c526c5bf6 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeMetadata.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeMetadata.java @@ -116,10 +116,6 @@ public class TestDeltaLakeMetadata private static final RowType NESTED_ROW_FIELD = RowType.from(ImmutableList.of( new RowType.Field(Optional.of("child1"), INTEGER), new RowType.Field(Optional.of("child2"), INTEGER))); - private static final RowType HIGHLY_NESTED_ROW_FIELD = RowType.from(ImmutableList.of( - new RowType.Field(Optional.of("grandparent"), RowType.from(ImmutableList.of( - new RowType.Field(Optional.of("parent"), RowType.from(ImmutableList.of( - new RowType.Field(Optional.of("child"), INTEGER))))))))); private static final DeltaLakeColumnHandle BOOLEAN_COLUMN_HANDLE = new DeltaLakeColumnHandle("boolean_column_name", BooleanType.BOOLEAN, OptionalInt.empty(), "boolean_column_name", BooleanType.BOOLEAN, REGULAR, Optional.empty()); @@ -142,32 +138,12 @@ public class TestDeltaLakeMetadata NESTED_ROW_FIELD, REGULAR, Optional.of(new DeltaLakeColumnProjectionInfo(INTEGER, ImmutableList.of(1), ImmutableList.of("child2")))); - private static final DeltaLakeColumnHandle NESTED_COLUMN_HANDLE_WITH_PROJECTION = - new DeltaLakeColumnHandle( - "highly_nested_column_name", - HIGHLY_NESTED_ROW_FIELD, - OptionalInt.empty(), - "highly_nested_column_name", - HIGHLY_NESTED_ROW_FIELD, - REGULAR, - Optional.of(new DeltaLakeColumnProjectionInfo(INTEGER, ImmutableList.of(0, 0), ImmutableList.of("grandparent", "parent")))); - private static final DeltaLakeColumnHandle EXPECTED_NESTED_COLUMN_HANDLE_WITH_PROJECTION = - new DeltaLakeColumnHandle( - "highly_nested_column_name", - HIGHLY_NESTED_ROW_FIELD, - OptionalInt.empty(), - "highly_nested_column_name", - HIGHLY_NESTED_ROW_FIELD, - REGULAR, - Optional.of(new DeltaLakeColumnProjectionInfo(INTEGER, ImmutableList.of(0, 0, 0), ImmutableList.of("grandparent", "parent", "child")))); private static final Map SYNTHETIC_COLUMN_ASSIGNMENTS = ImmutableMap.of( "test_synthetic_column_name_1", BOGUS_COLUMN_HANDLE, "test_synthetic_column_name_2", VARCHAR_COLUMN_HANDLE); private static final Map NESTED_COLUMN_ASSIGNMENTS = ImmutableMap.of("nested_column_name", NESTED_COLUMN_HANDLE); private static final Map EXPECTED_NESTED_COLUMN_ASSIGNMENTS = ImmutableMap.of("nested_column_name#child2", EXPECTED_NESTED_COLUMN_HANDLE); - private static final Map HIGHLY_NESTED_COLUMN_ASSIGNMENTS = ImmutableMap.of("highly_nested_column_name#grandparent#parent", NESTED_COLUMN_HANDLE_WITH_PROJECTION); - private static final Map EXPECTED_HIGHLY_NESTED_COLUMN_ASSIGNMENTS = ImmutableMap.of("highly_nested_column_name#grandparent#parent#child", EXPECTED_NESTED_COLUMN_HANDLE_WITH_PROJECTION); private static final ConnectorExpression DOUBLE_PROJECTION = new Variable("double_projection", DoubleType.DOUBLE); private static final ConnectorExpression BOOLEAN_PROJECTION = new Variable("boolean_projection", BooleanType.BOOLEAN); @@ -182,13 +158,6 @@ public class TestDeltaLakeMetadata private static final ConnectorExpression EXPECTED_NESTED_DEREFERENCE_PROJECTION = new Variable( "nested_column_name#child2", INTEGER); - private static final ConnectorExpression HIGHLY_NESTED_DEREFERENCE_PROJECTION = new FieldDereference( - INTEGER, - new Variable("highly_nested_column_name#grandparent#parent", HIGHLY_NESTED_ROW_FIELD), - 0); - private static final ConnectorExpression EXPECTED_HIGHLY_NESTED_DEREFERENCE_PROJECTION = new Variable( - "highly_nested_column_name#grandparent#parent#child", - INTEGER); private static final List SIMPLE_COLUMN_PROJECTIONS = ImmutableList.of(DOUBLE_PROJECTION, BOOLEAN_PROJECTION); @@ -198,10 +167,6 @@ public class TestDeltaLakeMetadata ImmutableList.of(NESTED_DEREFERENCE_PROJECTION); private static final List EXPECTED_NESTED_DEREFERENCE_COLUMN_PROJECTIONS = ImmutableList.of(EXPECTED_NESTED_DEREFERENCE_PROJECTION); - private static final List HIGHLY_NESTED_DEREFERENCE_COLUMN_PROJECTIONS = - ImmutableList.of(HIGHLY_NESTED_DEREFERENCE_PROJECTION); - private static final List EXPECTED_HIGHLY_NESTED_DEREFERENCE_COLUMN_PROJECTIONS = - ImmutableList.of(EXPECTED_HIGHLY_NESTED_DEREFERENCE_PROJECTION); private static final Set PREDICATE_COLUMNS = ImmutableSet.of(BOOLEAN_COLUMN_HANDLE, DOUBLE_COLUMN_HANDLE); From 4f8b43704ce20cfb1b0184ed8e6831eb8eba190f Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Thu, 9 Nov 2023 16:16:16 +0100 Subject: [PATCH 192/587] Ensure cleanupQuery gets called for procedures Ensure `cleanupQuery` is called for cases when `ConnectorMetadata` is constructed outside of typical query processing (`Connector.getMetadata`). --- .../plugin/base/util/UncheckedCloseable.java | 21 ++ .../tablechanges/TableChangesFunction.java | 70 +++--- .../procedure/DropExtendedStatsProcedure.java | 14 +- .../procedure/RegisterTableProcedure.java | 100 ++++---- .../procedure/UnregisterTableProcedure.java | 19 +- .../deltalake/procedure/VacuumProcedure.java | 214 +++++++++--------- .../deltalake/TestDeltaLakeMetadata.java | 63 ++++-- .../glue/TestDeltaLakeGlueMetastore.java | 2 + .../CreateEmptyPartitionProcedure.java | 84 +++---- .../hive/procedure/DropStatsProcedure.java | 84 +++---- .../procedure/RegisterPartitionProcedure.java | 74 +++--- .../SyncPartitionMetadataProcedure.java | 52 +++-- .../UnregisterPartitionProcedure.java | 38 ++-- .../hive/BaseS3AndGlueMetastoreTest.java | 8 +- .../hive/TestHiveS3AndGlueMetastoreTest.java | 1 + .../TestIcebergS3AndGlueMetastoreTest.java | 1 + 16 files changed, 463 insertions(+), 382 deletions(-) create mode 100644 lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/util/UncheckedCloseable.java diff --git a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/util/UncheckedCloseable.java b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/util/UncheckedCloseable.java new file mode 100644 index 000000000000..2e91f3bf37ba --- /dev/null +++ b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/util/UncheckedCloseable.java @@ -0,0 +1,21 @@ +/* + * 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 io.trino.plugin.base.util; + +public interface UncheckedCloseable + extends AutoCloseable +{ + @Override + void close(); +} diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/functions/tablechanges/TableChangesFunction.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/functions/tablechanges/TableChangesFunction.java index 56cc9849ffb0..416401fee895 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/functions/tablechanges/TableChangesFunction.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/functions/tablechanges/TableChangesFunction.java @@ -15,6 +15,7 @@ import com.google.common.collect.ImmutableList; import io.airlift.slice.Slice; +import io.trino.plugin.base.util.UncheckedCloseable; import io.trino.plugin.deltalake.CorruptedDeltaLakeTableHandle; import io.trino.plugin.deltalake.DeltaLakeColumnHandle; import io.trino.plugin.deltalake.DeltaLakeMetadata; @@ -104,42 +105,45 @@ public TableFunctionAnalysis analyze( long firstReadVersion = sinceVersion + 1; // +1 to ensure that the since_version is exclusive; may overflow DeltaLakeMetadata deltaLakeMetadata = deltaLakeMetadataFactory.create(session.getIdentity()); - SchemaTableName schemaTableName = new SchemaTableName(schemaName, tableName); - ConnectorTableHandle connectorTableHandle = deltaLakeMetadata.getTableHandle(session, schemaTableName); - if (connectorTableHandle == null) { - throw new TableNotFoundException(schemaTableName); - } - if (connectorTableHandle instanceof CorruptedDeltaLakeTableHandle corruptedTableHandle) { - throw corruptedTableHandle.createException(); - } - DeltaLakeTableHandle tableHandle = (DeltaLakeTableHandle) connectorTableHandle; + deltaLakeMetadata.beginQuery(session); + try (UncheckedCloseable ignore = () -> deltaLakeMetadata.cleanupQuery(session)) { + SchemaTableName schemaTableName = new SchemaTableName(schemaName, tableName); + ConnectorTableHandle connectorTableHandle = deltaLakeMetadata.getTableHandle(session, schemaTableName); + if (connectorTableHandle == null) { + throw new TableNotFoundException(schemaTableName); + } + if (connectorTableHandle instanceof CorruptedDeltaLakeTableHandle corruptedTableHandle) { + throw corruptedTableHandle.createException(); + } + DeltaLakeTableHandle tableHandle = (DeltaLakeTableHandle) connectorTableHandle; - if (sinceVersion > tableHandle.getReadVersion()) { - throw new TrinoException(INVALID_FUNCTION_ARGUMENT, format("since_version: %d is higher then current table version: %d", sinceVersion, tableHandle.getReadVersion())); - } - List columnHandles = deltaLakeMetadata.getColumnHandles(session, tableHandle) - .values().stream() - .map(DeltaLakeColumnHandle.class::cast) - .filter(column -> column.getColumnType() != SYNTHESIZED) - .collect(toImmutableList()); - accessControl.checkCanSelectFromColumns(null, schemaTableName, columnHandles.stream() - // Lowercase column names because users don't know the original names - .map(column -> column.getColumnName().toLowerCase(ENGLISH)) - .collect(toImmutableSet())); + if (sinceVersion > tableHandle.getReadVersion()) { + throw new TrinoException(INVALID_FUNCTION_ARGUMENT, format("since_version: %d is higher then current table version: %d", sinceVersion, tableHandle.getReadVersion())); + } + List columnHandles = deltaLakeMetadata.getColumnHandles(session, tableHandle) + .values().stream() + .map(DeltaLakeColumnHandle.class::cast) + .filter(column -> column.getColumnType() != SYNTHESIZED) + .collect(toImmutableList()); + accessControl.checkCanSelectFromColumns(null, schemaTableName, columnHandles.stream() + // Lowercase column names because users don't know the original names + .map(column -> column.getColumnName().toLowerCase(ENGLISH)) + .collect(toImmutableSet())); - ImmutableList.Builder outputFields = ImmutableList.builder(); - columnHandles.stream() - .map(columnHandle -> new Descriptor.Field(columnHandle.getColumnName(), Optional.of(columnHandle.getType()))) - .forEach(outputFields::add); + ImmutableList.Builder outputFields = ImmutableList.builder(); + columnHandles.stream() + .map(columnHandle -> new Descriptor.Field(columnHandle.getColumnName(), Optional.of(columnHandle.getType()))) + .forEach(outputFields::add); - // add at the end to follow Delta Lake convention - outputFields.add(new Descriptor.Field(CHANGE_TYPE_COLUMN_NAME, Optional.of(VARCHAR))); - outputFields.add(new Descriptor.Field(COMMIT_VERSION_COLUMN_NAME, Optional.of(BIGINT))); - outputFields.add(new Descriptor.Field(COMMIT_TIMESTAMP_COLUMN_NAME, Optional.of(TIMESTAMP_TZ_MILLIS))); + // add at the end to follow Delta Lake convention + outputFields.add(new Descriptor.Field(CHANGE_TYPE_COLUMN_NAME, Optional.of(VARCHAR))); + outputFields.add(new Descriptor.Field(COMMIT_VERSION_COLUMN_NAME, Optional.of(BIGINT))); + outputFields.add(new Descriptor.Field(COMMIT_TIMESTAMP_COLUMN_NAME, Optional.of(TIMESTAMP_TZ_MILLIS))); - return TableFunctionAnalysis.builder() - .handle(new TableChangesTableFunctionHandle(schemaTableName, firstReadVersion, tableHandle.getReadVersion(), tableHandle.getLocation(), columnHandles)) - .returnedType(new Descriptor(outputFields.build())) - .build(); + return TableFunctionAnalysis.builder() + .handle(new TableChangesTableFunctionHandle(schemaTableName, firstReadVersion, tableHandle.getReadVersion(), tableHandle.getLocation(), columnHandles)) + .returnedType(new Descriptor(outputFields.build())) + .build(); + } } } diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/procedure/DropExtendedStatsProcedure.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/procedure/DropExtendedStatsProcedure.java index 6728b496a9c6..bdb015b5fd02 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/procedure/DropExtendedStatsProcedure.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/procedure/DropExtendedStatsProcedure.java @@ -15,6 +15,7 @@ import com.google.inject.Inject; import com.google.inject.Provider; +import io.trino.plugin.base.util.UncheckedCloseable; import io.trino.plugin.deltalake.DeltaLakeMetadata; import io.trino.plugin.deltalake.DeltaLakeMetadataFactory; import io.trino.plugin.deltalake.LocatedTableHandle; @@ -79,11 +80,14 @@ public void dropStats(ConnectorSession session, ConnectorAccessControl accessCon SchemaTableName name = new SchemaTableName(schema, table); DeltaLakeMetadata metadata = metadataFactory.create(session.getIdentity()); - LocatedTableHandle tableHandle = metadata.getTableHandle(session, name); - if (tableHandle == null) { - throw new TrinoException(INVALID_PROCEDURE_ARGUMENT, format("Table '%s' does not exist", name)); + metadata.beginQuery(session); + try (UncheckedCloseable ignore = () -> metadata.cleanupQuery(session)) { + LocatedTableHandle tableHandle = metadata.getTableHandle(session, name); + if (tableHandle == null) { + throw new TrinoException(INVALID_PROCEDURE_ARGUMENT, format("Table '%s' does not exist", name)); + } + accessControl.checkCanInsertIntoTable(null, name); + statsAccess.deleteExtendedStatistics(session, name, tableHandle.location()); } - accessControl.checkCanInsertIntoTable(null, name); - statsAccess.deleteExtendedStatistics(session, name, tableHandle.location()); } } diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/procedure/RegisterTableProcedure.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/procedure/RegisterTableProcedure.java index 4ac20e34b303..52429e5e891d 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/procedure/RegisterTableProcedure.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/procedure/RegisterTableProcedure.java @@ -19,7 +19,9 @@ import io.trino.filesystem.Location; import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoFileSystemFactory; +import io.trino.plugin.base.util.UncheckedCloseable; import io.trino.plugin.deltalake.DeltaLakeConfig; +import io.trino.plugin.deltalake.DeltaLakeMetadata; import io.trino.plugin.deltalake.DeltaLakeMetadataFactory; import io.trino.plugin.deltalake.metastore.DeltaLakeMetastore; import io.trino.plugin.deltalake.statistics.CachingExtendedStatisticsAccess; @@ -140,60 +142,64 @@ private void doRegisterTable( checkProcedureArgument(!isNullOrEmpty(tableLocation), "table_location cannot be null or empty"); SchemaTableName schemaTableName = new SchemaTableName(schemaName, tableName); - DeltaLakeMetastore metastore = metadataFactory.create(session.getIdentity()).getMetastore(); + DeltaLakeMetadata metadata = metadataFactory.create(session.getIdentity()); + metadata.beginQuery(session); + try (UncheckedCloseable ignore = () -> metadata.cleanupQuery(session)) { + DeltaLakeMetastore metastore = metadata.getMetastore(); - if (metastore.getDatabase(schemaName).isEmpty()) { - throw new SchemaNotFoundException(schemaTableName.getSchemaName()); - } - - TrinoFileSystem fileSystem = fileSystemFactory.create(session); - try { - Location transactionLogDir = Location.of(getTransactionLogDir(tableLocation)); - if (!fileSystem.listFiles(transactionLogDir).hasNext()) { - throw new TrinoException(GENERIC_USER_ERROR, format("No transaction log found in location %s", transactionLogDir)); + if (metastore.getDatabase(schemaName).isEmpty()) { + throw new SchemaNotFoundException(schemaTableName.getSchemaName()); } - } - catch (IOException e) { - throw new TrinoException(DELTA_LAKE_FILESYSTEM_ERROR, format("Failed checking table location %s", tableLocation), e); - } - Table table = buildTable(session, schemaTableName, tableLocation, true); + TrinoFileSystem fileSystem = fileSystemFactory.create(session); + try { + Location transactionLogDir = Location.of(getTransactionLogDir(tableLocation)); + if (!fileSystem.listFiles(transactionLogDir).hasNext()) { + throw new TrinoException(GENERIC_USER_ERROR, format("No transaction log found in location %s", transactionLogDir)); + } + } + catch (IOException e) { + throw new TrinoException(DELTA_LAKE_FILESYSTEM_ERROR, format("Failed checking table location %s", tableLocation), e); + } - PrincipalPrivileges principalPrivileges = buildInitialPrivilegeSet(table.getOwner().orElseThrow()); - statisticsAccess.invalidateCache(schemaTableName, Optional.of(tableLocation)); - transactionLogAccess.invalidateCache(schemaTableName, Optional.of(tableLocation)); - // Verify we're registering a location with a valid table - try { - TableSnapshot tableSnapshot = transactionLogAccess.loadSnapshot(session, table.getSchemaTableName(), tableLocation); - transactionLogAccess.getMetadataEntry(tableSnapshot, session); // verify metadata exists - } - catch (TrinoException e) { - throw e; - } - catch (IOException | RuntimeException e) { - throw new TrinoException(DELTA_LAKE_INVALID_TABLE, "Failed to access table location: " + tableLocation, e); - } + Table table = buildTable(session, schemaTableName, tableLocation, true); - // Ensure the table has queryId set. This is relied on for exception handling - String queryId = session.getQueryId(); - verify( - getQueryId(table).orElseThrow(() -> new IllegalArgumentException("Query id is not present")).equals(queryId), - "Table '%s' does not have correct query id set", - table); - try { - metastore.createTable( - session, - table, - principalPrivileges); - } - catch (TableAlreadyExistsException e) { - // Ignore TableAlreadyExistsException when table looks like created by us. - // This may happen when an actually successful metastore create call is retried - // e.g. because of a timeout on our side. - Optional
existingTable = metastore.getRawMetastoreTable(schemaName, tableName); - if (existingTable.isEmpty() || !isCreatedBy(existingTable.get(), queryId)) { + PrincipalPrivileges principalPrivileges = buildInitialPrivilegeSet(table.getOwner().orElseThrow()); + statisticsAccess.invalidateCache(schemaTableName, Optional.of(tableLocation)); + transactionLogAccess.invalidateCache(schemaTableName, Optional.of(tableLocation)); + // Verify we're registering a location with a valid table + try { + TableSnapshot tableSnapshot = transactionLogAccess.loadSnapshot(session, table.getSchemaTableName(), tableLocation); + transactionLogAccess.getMetadataEntry(tableSnapshot, session); // verify metadata exists + } + catch (TrinoException e) { throw e; } + catch (IOException | RuntimeException e) { + throw new TrinoException(DELTA_LAKE_INVALID_TABLE, "Failed to access table location: " + tableLocation, e); + } + + // Ensure the table has queryId set. This is relied on for exception handling + String queryId = session.getQueryId(); + verify( + getQueryId(table).orElseThrow(() -> new IllegalArgumentException("Query id is not present")).equals(queryId), + "Table '%s' does not have correct query id set", + table); + try { + metastore.createTable( + session, + table, + principalPrivileges); + } + catch (TableAlreadyExistsException e) { + // Ignore TableAlreadyExistsException when table looks like created by us. + // This may happen when an actually successful metastore create call is retried + // e.g. because of a timeout on our side. + Optional
existingTable = metastore.getRawMetastoreTable(schemaName, tableName); + if (existingTable.isEmpty() || !isCreatedBy(existingTable.get(), queryId)) { + throw e; + } + } } } } diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/procedure/UnregisterTableProcedure.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/procedure/UnregisterTableProcedure.java index 9d800ec8d61e..2e2b62b14ccf 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/procedure/UnregisterTableProcedure.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/procedure/UnregisterTableProcedure.java @@ -16,6 +16,7 @@ import com.google.common.collect.ImmutableList; import com.google.inject.Inject; import com.google.inject.Provider; +import io.trino.plugin.base.util.UncheckedCloseable; import io.trino.plugin.deltalake.DeltaLakeMetadata; import io.trino.plugin.deltalake.DeltaLakeMetadataFactory; import io.trino.plugin.deltalake.LocatedTableHandle; @@ -96,14 +97,16 @@ private void doUnregisterTable(ConnectorAccessControl accessControl, ConnectorSe accessControl.checkCanDropTable(null, schemaTableName); DeltaLakeMetadata metadata = metadataFactory.create(session.getIdentity()); - - LocatedTableHandle tableHandle = metadata.getTableHandle(session, schemaTableName); - if (tableHandle == null) { - throw new TableNotFoundException(schemaTableName); + metadata.beginQuery(session); + try (UncheckedCloseable ignore = () -> metadata.cleanupQuery(session)) { + LocatedTableHandle tableHandle = metadata.getTableHandle(session, schemaTableName); + if (tableHandle == null) { + throw new TableNotFoundException(schemaTableName); + } + metadata.getMetastore().dropTable(session, schemaTableName, tableHandle.location(), false); + // As a precaution, clear the caches + statisticsAccess.invalidateCache(schemaTableName, Optional.of(tableHandle.location())); + transactionLogAccess.invalidateCache(schemaTableName, Optional.of(tableHandle.location())); } - metadata.getMetastore().dropTable(session, schemaTableName, tableHandle.location(), false); - // As a precaution, clear the caches - statisticsAccess.invalidateCache(schemaTableName, Optional.of(tableHandle.location())); - transactionLogAccess.invalidateCache(schemaTableName, Optional.of(tableHandle.location())); } } diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/procedure/VacuumProcedure.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/procedure/VacuumProcedure.java index e8fb95bcfd55..088038075338 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/procedure/VacuumProcedure.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/procedure/VacuumProcedure.java @@ -25,6 +25,7 @@ import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.plugin.base.CatalogName; +import io.trino.plugin.base.util.UncheckedCloseable; import io.trino.plugin.deltalake.DeltaLakeConfig; import io.trino.plugin.deltalake.DeltaLakeMetadata; import io.trino.plugin.deltalake.DeltaLakeMetadataFactory; @@ -168,127 +169,130 @@ private void doVacuum( Instant threshold = Instant.now().minusMillis(retentionDuration.toMillis()); DeltaLakeMetadata metadata = metadataFactory.create(session.getIdentity()); - SchemaTableName tableName = new SchemaTableName(schema, table); - ConnectorTableHandle connectorTableHandle = metadata.getTableHandle(session, tableName); - checkProcedureArgument(connectorTableHandle != null, "Table '%s' does not exist", tableName); - DeltaLakeTableHandle handle = checkValidTableHandle(connectorTableHandle); + metadata.beginQuery(session); + try (UncheckedCloseable ignore = () -> metadata.cleanupQuery(session)) { + SchemaTableName tableName = new SchemaTableName(schema, table); + ConnectorTableHandle connectorTableHandle = metadata.getTableHandle(session, tableName); + checkProcedureArgument(connectorTableHandle != null, "Table '%s' does not exist", tableName); + DeltaLakeTableHandle handle = checkValidTableHandle(connectorTableHandle); - accessControl.checkCanInsertIntoTable(null, tableName); - accessControl.checkCanDeleteFromTable(null, tableName); + accessControl.checkCanInsertIntoTable(null, tableName); + accessControl.checkCanDeleteFromTable(null, tableName); - TableSnapshot tableSnapshot = metadata.getSnapshot(session, tableName, handle.getLocation(), handle.getReadVersion()); - ProtocolEntry protocolEntry = transactionLogAccess.getProtocolEntry(session, tableSnapshot); - if (protocolEntry.getMinWriterVersion() > MAX_WRITER_VERSION) { - throw new TrinoException(NOT_SUPPORTED, "Cannot execute vacuum procedure with %d writer version".formatted(protocolEntry.getMinWriterVersion())); - } - Set unsupportedWriterFeatures = unsupportedWriterFeatures(protocolEntry.getWriterFeatures().orElse(ImmutableSet.of())); - if (!unsupportedWriterFeatures.isEmpty()) { - throw new TrinoException(NOT_SUPPORTED, "Cannot execute vacuum procedure with %s writer features".formatted(unsupportedWriterFeatures)); - } + TableSnapshot tableSnapshot = metadata.getSnapshot(session, tableName, handle.getLocation(), handle.getReadVersion()); + ProtocolEntry protocolEntry = transactionLogAccess.getProtocolEntry(session, tableSnapshot); + if (protocolEntry.getMinWriterVersion() > MAX_WRITER_VERSION) { + throw new TrinoException(NOT_SUPPORTED, "Cannot execute vacuum procedure with %d writer version".formatted(protocolEntry.getMinWriterVersion())); + } + Set unsupportedWriterFeatures = unsupportedWriterFeatures(protocolEntry.getWriterFeatures().orElse(ImmutableSet.of())); + if (!unsupportedWriterFeatures.isEmpty()) { + throw new TrinoException(NOT_SUPPORTED, "Cannot execute vacuum procedure with %s writer features".formatted(unsupportedWriterFeatures)); + } - String tableLocation = tableSnapshot.getTableLocation(); - String transactionLogDir = getTransactionLogDir(tableLocation); - TrinoFileSystem fileSystem = fileSystemFactory.create(session); - String commonPathPrefix = tableLocation.endsWith("/") ? tableLocation : tableLocation + "/"; - String queryId = session.getQueryId(); + String tableLocation = tableSnapshot.getTableLocation(); + String transactionLogDir = getTransactionLogDir(tableLocation); + TrinoFileSystem fileSystem = fileSystemFactory.create(session); + String commonPathPrefix = tableLocation.endsWith("/") ? tableLocation : tableLocation + "/"; + String queryId = session.getQueryId(); - // Retain all active files and every file removed by a "recent" transaction (except for the oldest "recent"). - // Any remaining file are not live, and not needed to read any "recent" snapshot. - List recentVersions = transactionLogAccess.getPastTableVersions(fileSystem, transactionLogDir, threshold, tableSnapshot.getVersion()); - Set retainedPaths = Stream.concat( - transactionLogAccess.getActiveFiles(tableSnapshot, handle.getMetadataEntry(), handle.getProtocolEntry(), session).stream() - .map(AddFileEntry::getPath), - transactionLogAccess.getJsonEntries( - fileSystem, - transactionLogDir, - // discard oldest "recent" snapshot, since we take RemoveFileEntry only, to identify files that are no longer - // active files, but still needed to read a "recent" snapshot - recentVersions.stream().sorted(naturalOrder()) - .skip(1) - .collect(toImmutableList())) - .map(DeltaLakeTransactionLogEntry::getRemove) - .filter(Objects::nonNull) - .map(RemoveFileEntry::getPath)) - .peek(path -> checkState(!path.startsWith(tableLocation), "Unexpected absolute path in transaction log: %s", path)) - .collect(toImmutableSet()); + // Retain all active files and every file removed by a "recent" transaction (except for the oldest "recent"). + // Any remaining file are not live, and not needed to read any "recent" snapshot. + List recentVersions = transactionLogAccess.getPastTableVersions(fileSystem, transactionLogDir, threshold, tableSnapshot.getVersion()); + Set retainedPaths = Stream.concat( + transactionLogAccess.getActiveFiles(tableSnapshot, handle.getMetadataEntry(), handle.getProtocolEntry(), session).stream() + .map(AddFileEntry::getPath), + transactionLogAccess.getJsonEntries( + fileSystem, + transactionLogDir, + // discard oldest "recent" snapshot, since we take RemoveFileEntry only, to identify files that are no longer + // active files, but still needed to read a "recent" snapshot + recentVersions.stream().sorted(naturalOrder()) + .skip(1) + .collect(toImmutableList())) + .map(DeltaLakeTransactionLogEntry::getRemove) + .filter(Objects::nonNull) + .map(RemoveFileEntry::getPath)) + .peek(path -> checkState(!path.startsWith(tableLocation), "Unexpected absolute path in transaction log: %s", path)) + .collect(toImmutableSet()); - log.debug( - "[%s] attempting to vacuum table %s [%s] with %s retention (expiry threshold %s). %s data file paths marked for retention", - queryId, - tableName, - tableLocation, - retention, - threshold, - retainedPaths.size()); + log.debug( + "[%s] attempting to vacuum table %s [%s] with %s retention (expiry threshold %s). %s data file paths marked for retention", + queryId, + tableName, + tableLocation, + retention, + threshold, + retainedPaths.size()); - long allPathsChecked = 0; - long transactionLogFiles = 0; - long retainedKnownFiles = 0; - long retainedUnknownFiles = 0; - long removedFiles = 0; + long allPathsChecked = 0; + long transactionLogFiles = 0; + long retainedKnownFiles = 0; + long retainedUnknownFiles = 0; + long removedFiles = 0; - List filesToDelete = new ArrayList<>(); - FileIterator listing = fileSystem.listFiles(Location.of(tableLocation)); - while (listing.hasNext()) { - FileEntry entry = listing.next(); - String location = entry.location().toString(); - checkState( - location.startsWith(commonPathPrefix), - "Unexpected path [%s] returned when listing files under [%s]", - location, - tableLocation); - String relativePath = location.substring(commonPathPrefix.length()); - if (relativePath.isEmpty()) { - // A file returned for "tableLocation/", might be possible on S3. - continue; - } - allPathsChecked++; + List filesToDelete = new ArrayList<>(); + FileIterator listing = fileSystem.listFiles(Location.of(tableLocation)); + while (listing.hasNext()) { + FileEntry entry = listing.next(); + String location = entry.location().toString(); + checkState( + location.startsWith(commonPathPrefix), + "Unexpected path [%s] returned when listing files under [%s]", + location, + tableLocation); + String relativePath = location.substring(commonPathPrefix.length()); + if (relativePath.isEmpty()) { + // A file returned for "tableLocation/", might be possible on S3. + continue; + } + allPathsChecked++; - // ignore tableLocation/_delta_log/** - if (relativePath.equals(TRANSACTION_LOG_DIRECTORY) || relativePath.startsWith(TRANSACTION_LOG_DIRECTORY + "/")) { - log.debug("[%s] skipping a file inside transaction log dir: %s", queryId, location); - transactionLogFiles++; - continue; - } + // ignore tableLocation/_delta_log/** + if (relativePath.equals(TRANSACTION_LOG_DIRECTORY) || relativePath.startsWith(TRANSACTION_LOG_DIRECTORY + "/")) { + log.debug("[%s] skipping a file inside transaction log dir: %s", queryId, location); + transactionLogFiles++; + continue; + } - // skip retained files - if (retainedPaths.contains(relativePath)) { - log.debug("[%s] retaining a known file: %s", queryId, location); - retainedKnownFiles++; - continue; - } + // skip retained files + if (retainedPaths.contains(relativePath)) { + log.debug("[%s] retaining a known file: %s", queryId, location); + retainedKnownFiles++; + continue; + } + + // ignore recently created files + Instant modificationTime = entry.lastModified(); + if (!modificationTime.isBefore(threshold)) { + log.debug("[%s] retaining an unknown file %s with modification time %s", queryId, location, modificationTime); + retainedUnknownFiles++; + continue; + } - // ignore recently created files - Instant modificationTime = entry.lastModified(); - if (!modificationTime.isBefore(threshold)) { - log.debug("[%s] retaining an unknown file %s with modification time %s", queryId, location, modificationTime); - retainedUnknownFiles++; - continue; + log.debug("[%s] deleting file [%s] with modification time %s", queryId, location, modificationTime); + filesToDelete.add(entry.location()); + if (filesToDelete.size() == DELETE_BATCH_SIZE) { + fileSystem.deleteFiles(filesToDelete); + removedFiles += filesToDelete.size(); + filesToDelete.clear(); + } } - log.debug("[%s] deleting file [%s] with modification time %s", queryId, location, modificationTime); - filesToDelete.add(entry.location()); - if (filesToDelete.size() == DELETE_BATCH_SIZE) { + if (!filesToDelete.isEmpty()) { fileSystem.deleteFiles(filesToDelete); removedFiles += filesToDelete.size(); - filesToDelete.clear(); } - } - if (!filesToDelete.isEmpty()) { - fileSystem.deleteFiles(filesToDelete); - removedFiles += filesToDelete.size(); + log.info( + "[%s] finished vacuuming table %s [%s]: files checked: %s; metadata files: %s; retained known files: %s; retained unknown files: %s; removed files: %s", + queryId, + tableName, + tableLocation, + allPathsChecked, + transactionLogFiles, + retainedKnownFiles, + retainedUnknownFiles, + removedFiles); } - - log.info( - "[%s] finished vacuuming table %s [%s]: files checked: %s; metadata files: %s; retained known files: %s; retained unknown files: %s; removed files: %s", - queryId, - tableName, - tableLocation, - allPathsChecked, - transactionLogFiles, - retainedKnownFiles, - retainedUnknownFiles, - removedFiles); } } diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeMetadata.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeMetadata.java index a69c526c5bf6..1ab69d381f78 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeMetadata.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeMetadata.java @@ -240,12 +240,12 @@ public void tearDown() @Test public void testGetNewTableLayout() { - Optional newTableLayout = deltaLakeMetadataFactory.create(SESSION.getIdentity()) - .getNewTableLayout( - SESSION, - newTableMetadata( - ImmutableList.of(BIGINT_COLUMN_1, BIGINT_COLUMN_2), - ImmutableList.of(BIGINT_COLUMN_2))); + DeltaLakeMetadata deltaLakeMetadata = deltaLakeMetadataFactory.create(SESSION.getIdentity()); + Optional newTableLayout = deltaLakeMetadata.getNewTableLayout( + SESSION, + newTableMetadata( + ImmutableList.of(BIGINT_COLUMN_1, BIGINT_COLUMN_2), + ImmutableList.of(BIGINT_COLUMN_2))); assertThat(newTableLayout).isPresent(); @@ -254,40 +254,45 @@ public void testGetNewTableLayout() assertThat(newTableLayout.get().getPartitionColumns()) .isEqualTo(ImmutableList.of(BIGINT_COLUMN_2.getName())); + + deltaLakeMetadata.cleanupQuery(SESSION); } @Test public void testGetNewTableLayoutNoPartitionColumns() { - assertThat(deltaLakeMetadataFactory.create(SESSION.getIdentity()) - .getNewTableLayout( - SESSION, - newTableMetadata( - ImmutableList.of(BIGINT_COLUMN_1, BIGINT_COLUMN_2), - ImmutableList.of()))) + DeltaLakeMetadata deltaLakeMetadata = deltaLakeMetadataFactory.create(SESSION.getIdentity()); + assertThat(deltaLakeMetadata.getNewTableLayout( + SESSION, + newTableMetadata( + ImmutableList.of(BIGINT_COLUMN_1, BIGINT_COLUMN_2), + ImmutableList.of()))) .isNotPresent(); + + deltaLakeMetadata.cleanupQuery(SESSION); } @Test public void testGetNewTableLayoutInvalidPartitionColumns() { - assertThatThrownBy(() -> deltaLakeMetadataFactory.create(SESSION.getIdentity()) - .getNewTableLayout( - SESSION, - newTableMetadata( - ImmutableList.of(BIGINT_COLUMN_1, BIGINT_COLUMN_2), - ImmutableList.of(BIGINT_COLUMN_2, MISSING_COLUMN)))) + DeltaLakeMetadata deltaLakeMetadata = deltaLakeMetadataFactory.create(SESSION.getIdentity()); + assertThatThrownBy(() -> deltaLakeMetadata.getNewTableLayout( + SESSION, + newTableMetadata( + ImmutableList.of(BIGINT_COLUMN_1, BIGINT_COLUMN_2), + ImmutableList.of(BIGINT_COLUMN_2, MISSING_COLUMN)))) .isInstanceOf(TrinoException.class) .hasMessage("Table property 'partition_by' contained column names which do not exist: [missing_column]"); - assertThatThrownBy(() -> deltaLakeMetadataFactory.create(SESSION.getIdentity()) - .getNewTableLayout( - SESSION, - newTableMetadata( - ImmutableList.of(TIMESTAMP_COLUMN, BIGINT_COLUMN_2), - ImmutableList.of(BIGINT_COLUMN_2)))) + assertThatThrownBy(() -> deltaLakeMetadata.getNewTableLayout( + SESSION, + newTableMetadata( + ImmutableList.of(TIMESTAMP_COLUMN, BIGINT_COLUMN_2), + ImmutableList.of(BIGINT_COLUMN_2)))) .isInstanceOf(TrinoException.class) .hasMessage("Unsupported type: timestamp(3)"); + + deltaLakeMetadata.cleanupQuery(SESSION); } @Test @@ -312,6 +317,8 @@ public void testGetInsertLayout() assertThat(insertLayout.get().getPartitionColumns()) .isEqualTo(getPartitionColumnNames(ImmutableList.of(BIGINT_COLUMN_1))); + + deltaLakeMetadata.cleanupQuery(SESSION); } private ConnectorTableMetadata newTableMetadata(List tableColumns, List partitionTableColumns) @@ -342,6 +349,8 @@ public void testGetInsertLayoutTableUnpartitioned() SESSION, deltaLakeMetadata.getTableHandle(SESSION, tableMetadata.getTable()))) .isNotPresent(); + + deltaLakeMetadata.cleanupQuery(SESSION); } @Test @@ -425,6 +434,8 @@ private void testApplyProjection( assertThat(projection.isPrecalculateStatistics()) .isFalse(); + + deltaLakeMetadata.cleanupQuery(SESSION); } @Test @@ -449,6 +460,8 @@ public void testApplyProjectionWithEmptyResult() ImmutableList.of(), ImmutableMap.of())) .isEmpty(); + + deltaLakeMetadata.cleanupQuery(SESSION); } @Test @@ -461,6 +474,7 @@ public void testGetInputInfoForPartitionedTable() deltaLakeMetadata.createTable(SESSION, tableMetadata, false); DeltaLakeTableHandle tableHandle = (DeltaLakeTableHandle) deltaLakeMetadata.getTableHandle(SESSION, tableMetadata.getTable()); assertThat(deltaLakeMetadata.getInfo(tableHandle)).isEqualTo(Optional.of(new DeltaLakeInputInfo(true))); + deltaLakeMetadata.cleanupQuery(SESSION); } @Test @@ -473,6 +487,7 @@ public void testGetInputInfoForUnPartitionedTable() deltaLakeMetadata.createTable(SESSION, tableMetadata, false); DeltaLakeTableHandle tableHandle = (DeltaLakeTableHandle) deltaLakeMetadata.getTableHandle(SESSION, tableMetadata.getTable()); assertThat(deltaLakeMetadata.getInfo(tableHandle)).isEqualTo(Optional.of(new DeltaLakeInputInfo(false))); + deltaLakeMetadata.cleanupQuery(SESSION); } private static DeltaLakeTableHandle createDeltaLakeTableHandle(Set projectedColumns, Set constrainedColumns) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeGlueMetastore.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeGlueMetastore.java index 8f5cadb08bb1..c06f4a1cb959 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeGlueMetastore.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeGlueMetastore.java @@ -243,6 +243,8 @@ public void testHideNonDeltaLakeTable() .isEmpty(); assertThat(listTableColumns(metadata, new SchemaTablePrefix(databaseName, nonDeltaLakeView1.getTableName()))) .isEmpty(); + + metadata.cleanupQuery(session); } private Set listTableColumns(DeltaLakeMetadata metadata, SchemaTablePrefix tablePrefix) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/CreateEmptyPartitionProcedure.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/CreateEmptyPartitionProcedure.java index 6a1096222001..1e9dc2fdadac 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/CreateEmptyPartitionProcedure.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/CreateEmptyPartitionProcedure.java @@ -19,6 +19,7 @@ import io.airlift.json.JsonCodec; import io.airlift.slice.Slice; import io.airlift.slice.Slices; +import io.trino.plugin.base.util.UncheckedCloseable; import io.trino.plugin.hive.HiveColumnHandle; import io.trino.plugin.hive.HiveInsertTableHandle; import io.trino.plugin.hive.HiveMetastoreClosure; @@ -109,46 +110,49 @@ private void doCreateEmptyPartition(ConnectorSession session, ConnectorAccessCon checkProcedureArgument(partitionValues != null, "partition_values cannot be null"); TransactionalMetadata hiveMetadata = hiveMetadataFactory.create(session.getIdentity(), true); - HiveTableHandle tableHandle = (HiveTableHandle) hiveMetadata.getTableHandle(session, new SchemaTableName(schemaName, tableName)); - if (tableHandle == null) { - throw new TrinoException(INVALID_PROCEDURE_ARGUMENT, format("Table '%s' does not exist", new SchemaTableName(schemaName, tableName))); + hiveMetadata.beginQuery(session); + try (UncheckedCloseable ignore = () -> hiveMetadata.cleanupQuery(session)) { + HiveTableHandle tableHandle = (HiveTableHandle) hiveMetadata.getTableHandle(session, new SchemaTableName(schemaName, tableName)); + if (tableHandle == null) { + throw new TrinoException(INVALID_PROCEDURE_ARGUMENT, format("Table '%s' does not exist", new SchemaTableName(schemaName, tableName))); + } + + accessControl.checkCanInsertIntoTable(null, new SchemaTableName(schemaName, tableName)); + + List actualPartitionColumnNames = tableHandle.getPartitionColumns().stream() + .map(HiveColumnHandle::getName) + .collect(toImmutableList()); + + if (!Objects.equals(partitionColumnNames, actualPartitionColumnNames)) { + throw new TrinoException(INVALID_PROCEDURE_ARGUMENT, "Provided partition column names do not match actual partition column names: " + actualPartitionColumnNames); + } + + HiveMetastoreClosure metastore = hiveMetadata.getMetastore().unsafeGetRawHiveMetastoreClosure(); + if (metastore.getPartition(schemaName, tableName, partitionValues).isPresent()) { + throw new TrinoException(ALREADY_EXISTS, "Partition already exists"); + } + HiveInsertTableHandle hiveInsertTableHandle = (HiveInsertTableHandle) hiveMetadata.beginInsert(session, tableHandle, ImmutableList.of(), NO_RETRIES); + String partitionName = makePartName(actualPartitionColumnNames, partitionValues); + + WriteInfo writeInfo = locationService.getPartitionWriteInfo(hiveInsertTableHandle.getLocationHandle(), Optional.empty(), partitionName); + Slice serializedPartitionUpdate = Slices.wrappedBuffer( + partitionUpdateJsonCodec.toJsonBytes( + new PartitionUpdate( + partitionName, + UpdateMode.NEW, + writeInfo.writePath().toString(), + writeInfo.targetPath().toString(), + ImmutableList.of(), + 0, + 0, + 0))); + + hiveMetadata.finishInsert( + session, + hiveInsertTableHandle, + ImmutableList.of(serializedPartitionUpdate), + ImmutableList.of()); + hiveMetadata.commit(); } - - accessControl.checkCanInsertIntoTable(null, new SchemaTableName(schemaName, tableName)); - - List actualPartitionColumnNames = tableHandle.getPartitionColumns().stream() - .map(HiveColumnHandle::getName) - .collect(toImmutableList()); - - if (!Objects.equals(partitionColumnNames, actualPartitionColumnNames)) { - throw new TrinoException(INVALID_PROCEDURE_ARGUMENT, "Provided partition column names do not match actual partition column names: " + actualPartitionColumnNames); - } - - HiveMetastoreClosure metastore = hiveMetadata.getMetastore().unsafeGetRawHiveMetastoreClosure(); - if (metastore.getPartition(schemaName, tableName, partitionValues).isPresent()) { - throw new TrinoException(ALREADY_EXISTS, "Partition already exists"); - } - HiveInsertTableHandle hiveInsertTableHandle = (HiveInsertTableHandle) hiveMetadata.beginInsert(session, tableHandle, ImmutableList.of(), NO_RETRIES); - String partitionName = makePartName(actualPartitionColumnNames, partitionValues); - - WriteInfo writeInfo = locationService.getPartitionWriteInfo(hiveInsertTableHandle.getLocationHandle(), Optional.empty(), partitionName); - Slice serializedPartitionUpdate = Slices.wrappedBuffer( - partitionUpdateJsonCodec.toJsonBytes( - new PartitionUpdate( - partitionName, - UpdateMode.NEW, - writeInfo.writePath().toString(), - writeInfo.targetPath().toString(), - ImmutableList.of(), - 0, - 0, - 0))); - - hiveMetadata.finishInsert( - session, - hiveInsertTableHandle, - ImmutableList.of(serializedPartitionUpdate), - ImmutableList.of()); - hiveMetadata.commit(); } } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/DropStatsProcedure.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/DropStatsProcedure.java index 834784802597..be2e8e295441 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/DropStatsProcedure.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/DropStatsProcedure.java @@ -16,6 +16,7 @@ import com.google.common.collect.ImmutableList; import com.google.inject.Inject; import com.google.inject.Provider; +import io.trino.plugin.base.util.UncheckedCloseable; import io.trino.plugin.hive.HiveColumnHandle; import io.trino.plugin.hive.HiveMetastoreClosure; import io.trino.plugin.hive.HiveTableHandle; @@ -100,56 +101,59 @@ private void doDropStats(ConnectorSession session, ConnectorAccessControl access checkProcedureArgument(table != null, "table_name cannot be null"); TransactionalMetadata hiveMetadata = hiveMetadataFactory.create(session.getIdentity(), true); - HiveTableHandle handle = (HiveTableHandle) hiveMetadata.getTableHandle(session, new SchemaTableName(schema, table)); - if (handle == null) { - throw new TrinoException(INVALID_PROCEDURE_ARGUMENT, format("Table '%s' does not exist", new SchemaTableName(schema, table))); - } - - accessControl.checkCanInsertIntoTable(null, new SchemaTableName(schema, table)); + hiveMetadata.beginQuery(session); + try (UncheckedCloseable ignore = () -> hiveMetadata.cleanupQuery(session)) { + HiveTableHandle handle = (HiveTableHandle) hiveMetadata.getTableHandle(session, new SchemaTableName(schema, table)); + if (handle == null) { + throw new TrinoException(INVALID_PROCEDURE_ARGUMENT, format("Table '%s' does not exist", new SchemaTableName(schema, table))); + } - Map columns = hiveMetadata.getColumnHandles(session, handle); - List partitionColumns = columns.values().stream() - .map(HiveColumnHandle.class::cast) - .filter(HiveColumnHandle::isPartitionKey) - .map(HiveColumnHandle::getName) - .collect(toImmutableList()); + accessControl.checkCanInsertIntoTable(null, new SchemaTableName(schema, table)); - HiveMetastoreClosure metastore = hiveMetadata.getMetastore().unsafeGetRawHiveMetastoreClosure(); - if (partitionValues != null) { - // drop stats for specified partitions - List> partitionStringValues = partitionValues.stream() - .map(DropStatsProcedure::validateParameterType) + Map columns = hiveMetadata.getColumnHandles(session, handle); + List partitionColumns = columns.values().stream() + .map(HiveColumnHandle.class::cast) + .filter(HiveColumnHandle::isPartitionKey) + .map(HiveColumnHandle::getName) .collect(toImmutableList()); - validatePartitions(partitionStringValues, partitionColumns); - partitionStringValues.forEach(values -> metastore.updatePartitionStatistics( - schema, - table, - makePartName(partitionColumns, values), - stats -> PartitionStatistics.empty())); - } - else { - // no partition specified, so drop stats for the entire table - if (partitionColumns.isEmpty()) { - // for non-partitioned tables, just wipe table stats - metastore.updateTableStatistics( + HiveMetastoreClosure metastore = hiveMetadata.getMetastore().unsafeGetRawHiveMetastoreClosure(); + if (partitionValues != null) { + // drop stats for specified partitions + List> partitionStringValues = partitionValues.stream() + .map(DropStatsProcedure::validateParameterType) + .collect(toImmutableList()); + validatePartitions(partitionStringValues, partitionColumns); + + partitionStringValues.forEach(values -> metastore.updatePartitionStatistics( schema, table, - NO_ACID_TRANSACTION, - stats -> PartitionStatistics.empty()); + makePartName(partitionColumns, values), + stats -> PartitionStatistics.empty())); } else { - // the table is partitioned; remove stats for every partition - metastore.getPartitionNamesByFilter(handle.getSchemaName(), handle.getTableName(), partitionColumns, TupleDomain.all()) - .ifPresent(partitions -> partitions.forEach(partitionName -> metastore.updatePartitionStatistics( - schema, - table, - partitionName, - stats -> PartitionStatistics.empty()))); + // no partition specified, so drop stats for the entire table + if (partitionColumns.isEmpty()) { + // for non-partitioned tables, just wipe table stats + metastore.updateTableStatistics( + schema, + table, + NO_ACID_TRANSACTION, + stats -> PartitionStatistics.empty()); + } + else { + // the table is partitioned; remove stats for every partition + metastore.getPartitionNamesByFilter(handle.getSchemaName(), handle.getTableName(), partitionColumns, TupleDomain.all()) + .ifPresent(partitions -> partitions.forEach(partitionName -> metastore.updatePartitionStatistics( + schema, + table, + partitionName, + stats -> PartitionStatistics.empty()))); + } } - } - hiveMetadata.commit(); + hiveMetadata.commit(); + } } private static List validateParameterType(Object param) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/RegisterPartitionProcedure.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/RegisterPartitionProcedure.java index eaf6295f5042..2dc267fcfe05 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/RegisterPartitionProcedure.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/RegisterPartitionProcedure.java @@ -20,8 +20,10 @@ import io.trino.filesystem.Location; import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoFileSystemFactory; +import io.trino.plugin.base.util.UncheckedCloseable; import io.trino.plugin.hive.HiveConfig; import io.trino.plugin.hive.PartitionStatistics; +import io.trino.plugin.hive.TransactionalMetadata; import io.trino.plugin.hive.TransactionalMetadataFactory; import io.trino.plugin.hive.metastore.Partition; import io.trino.plugin.hive.metastore.SemiTransactionalHiveMetastore; @@ -113,49 +115,53 @@ private void doRegisterPartition(ConnectorSession session, ConnectorAccessContro throw new TrinoException(PERMISSION_DENIED, "register_partition procedure is disabled"); } - SemiTransactionalHiveMetastore metastore = hiveMetadataFactory.create(session.getIdentity(), true).getMetastore(); + TransactionalMetadata hiveMetadata = hiveMetadataFactory.create(session.getIdentity(), true); + hiveMetadata.beginQuery(session); + try (UncheckedCloseable ignore = () -> hiveMetadata.cleanupQuery(session)) { + SemiTransactionalHiveMetastore metastore = hiveMetadata.getMetastore(); - SchemaTableName schemaTableName = new SchemaTableName(schemaName, tableName); + SchemaTableName schemaTableName = new SchemaTableName(schemaName, tableName); - Table table = metastore.getTable(schemaName, tableName) - .orElseThrow(() -> new TableNotFoundException(schemaTableName)); + Table table = metastore.getTable(schemaName, tableName) + .orElseThrow(() -> new TableNotFoundException(schemaTableName)); - accessControl.checkCanInsertIntoTable(null, schemaTableName); + accessControl.checkCanInsertIntoTable(null, schemaTableName); - checkIsPartitionedTable(table); - checkPartitionColumns(table, partitionColumns); + checkIsPartitionedTable(table); + checkPartitionColumns(table, partitionColumns); - Optional partition = metastore.unsafeGetRawHiveMetastoreClosure().getPartition(schemaName, tableName, partitionValues); - if (partition.isPresent()) { - String partitionName = makePartName(partitionColumns, partitionValues); - throw new TrinoException(ALREADY_EXISTS, format("Partition [%s] is already registered with location %s", partitionName, partition.get().getStorage().getLocation())); - } + Optional partition = metastore.unsafeGetRawHiveMetastoreClosure().getPartition(schemaName, tableName, partitionValues); + if (partition.isPresent()) { + String partitionName = makePartName(partitionColumns, partitionValues); + throw new TrinoException(ALREADY_EXISTS, format("Partition [%s] is already registered with location %s", partitionName, partition.get().getStorage().getLocation())); + } - Location partitionLocation = Optional.ofNullable(location) - .map(Location::of) - .orElseGet(() -> Location.of(table.getStorage().getLocation()).appendPath(makePartName(partitionColumns, partitionValues))); + Location partitionLocation = Optional.ofNullable(location) + .map(Location::of) + .orElseGet(() -> Location.of(table.getStorage().getLocation()).appendPath(makePartName(partitionColumns, partitionValues))); - TrinoFileSystem fileSystem = fileSystemFactory.create(session); - try { - if (!fileSystem.directoryExists(partitionLocation).orElse(true)) { - throw new TrinoException(INVALID_PROCEDURE_ARGUMENT, "Partition location does not exist: " + partitionLocation); + TrinoFileSystem fileSystem = fileSystemFactory.create(session); + try { + if (!fileSystem.directoryExists(partitionLocation).orElse(true)) { + throw new TrinoException(INVALID_PROCEDURE_ARGUMENT, "Partition location does not exist: " + partitionLocation); + } + } + catch (IOException e) { + throw new TrinoException(HIVE_FILESYSTEM_ERROR, "Failed checking partition location: " + partitionLocation, e); } - } - catch (IOException e) { - throw new TrinoException(HIVE_FILESYSTEM_ERROR, "Failed checking partition location: " + partitionLocation, e); - } - metastore.addPartition( - session, - table.getDatabaseName(), - table.getTableName(), - buildPartitionObject(session, table, partitionValues, partitionLocation), - partitionLocation, - Optional.empty(), // no need for failed attempts cleanup - PartitionStatistics.empty(), - false); - - metastore.commit(); + metastore.addPartition( + session, + table.getDatabaseName(), + table.getTableName(), + buildPartitionObject(session, table, partitionValues, partitionLocation), + partitionLocation, + Optional.empty(), // no need for failed attempts cleanup + PartitionStatistics.empty(), + false); + + metastore.commit(); + } } private static Partition buildPartitionObject(ConnectorSession session, Table table, List partitionValues, Location location) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/SyncPartitionMetadataProcedure.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/SyncPartitionMetadataProcedure.java index 3be1d4d10d3b..47282bd11a27 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/SyncPartitionMetadataProcedure.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/SyncPartitionMetadataProcedure.java @@ -21,7 +21,9 @@ import io.trino.filesystem.Location; import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoFileSystemFactory; +import io.trino.plugin.base.util.UncheckedCloseable; import io.trino.plugin.hive.PartitionStatistics; +import io.trino.plugin.hive.TransactionalMetadata; import io.trino.plugin.hive.TransactionalMetadataFactory; import io.trino.plugin.hive.metastore.Column; import io.trino.plugin.hive.metastore.Partition; @@ -116,36 +118,40 @@ private void doSyncPartitionMetadata(ConnectorSession session, ConnectorAccessCo checkProcedureArgument(mode != null, "mode cannot be null"); SyncMode syncMode = toSyncMode(mode); - SemiTransactionalHiveMetastore metastore = hiveMetadataFactory.create(session.getIdentity(), true).getMetastore(); - SchemaTableName schemaTableName = new SchemaTableName(schemaName, tableName); + TransactionalMetadata hiveMetadata = hiveMetadataFactory.create(session.getIdentity(), true); + hiveMetadata.beginQuery(session); + try (UncheckedCloseable ignore = () -> hiveMetadata.cleanupQuery(session)) { + SemiTransactionalHiveMetastore metastore = hiveMetadata.getMetastore(); + SchemaTableName schemaTableName = new SchemaTableName(schemaName, tableName); - Table table = metastore.getTable(schemaName, tableName) - .orElseThrow(() -> new TableNotFoundException(schemaTableName)); - if (table.getPartitionColumns().isEmpty()) { - throw new TrinoException(INVALID_PROCEDURE_ARGUMENT, "Table is not partitioned: " + schemaTableName); - } + Table table = metastore.getTable(schemaName, tableName) + .orElseThrow(() -> new TableNotFoundException(schemaTableName)); + if (table.getPartitionColumns().isEmpty()) { + throw new TrinoException(INVALID_PROCEDURE_ARGUMENT, "Table is not partitioned: " + schemaTableName); + } - if (syncMode == SyncMode.ADD || syncMode == SyncMode.FULL) { - accessControl.checkCanInsertIntoTable(null, new SchemaTableName(schemaName, tableName)); - } - if (syncMode == SyncMode.DROP || syncMode == SyncMode.FULL) { - accessControl.checkCanDeleteFromTable(null, new SchemaTableName(schemaName, tableName)); - } + if (syncMode == SyncMode.ADD || syncMode == SyncMode.FULL) { + accessControl.checkCanInsertIntoTable(null, new SchemaTableName(schemaName, tableName)); + } + if (syncMode == SyncMode.DROP || syncMode == SyncMode.FULL) { + accessControl.checkCanDeleteFromTable(null, new SchemaTableName(schemaName, tableName)); + } - Location tableLocation = Location.of(table.getStorage().getLocation()); + Location tableLocation = Location.of(table.getStorage().getLocation()); - Set partitionsInMetastore = metastore.getPartitionNames(schemaName, tableName) - .map(ImmutableSet::copyOf) - .orElseThrow(() -> new TableNotFoundException(schemaTableName)); - Set partitionsInFileSystem = listPartitions(fileSystemFactory.create(session), tableLocation, table.getPartitionColumns(), caseSensitive); + Set partitionsInMetastore = metastore.getPartitionNames(schemaName, tableName) + .map(ImmutableSet::copyOf) + .orElseThrow(() -> new TableNotFoundException(schemaTableName)); + Set partitionsInFileSystem = listPartitions(fileSystemFactory.create(session), tableLocation, table.getPartitionColumns(), caseSensitive); - // partitions in file system but not in metastore - Set partitionsToAdd = difference(partitionsInFileSystem, partitionsInMetastore); + // partitions in file system but not in metastore + Set partitionsToAdd = difference(partitionsInFileSystem, partitionsInMetastore); - // partitions in metastore but not in file system - Set partitionsToDrop = difference(partitionsInMetastore, partitionsInFileSystem); + // partitions in metastore but not in file system + Set partitionsToDrop = difference(partitionsInMetastore, partitionsInFileSystem); - syncPartitions(partitionsToAdd, partitionsToDrop, syncMode, metastore, session, table); + syncPartitions(partitionsToAdd, partitionsToDrop, syncMode, metastore, session, table); + } } private static Set listPartitions(TrinoFileSystem fileSystem, Location directory, List partitionColumns, boolean caseSensitive) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/UnregisterPartitionProcedure.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/UnregisterPartitionProcedure.java index 2ff444f2f30e..d5e03b0332c7 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/UnregisterPartitionProcedure.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/UnregisterPartitionProcedure.java @@ -16,6 +16,8 @@ import com.google.common.collect.ImmutableList; import com.google.inject.Inject; import com.google.inject.Provider; +import io.trino.plugin.base.util.UncheckedCloseable; +import io.trino.plugin.hive.TransactionalMetadata; import io.trino.plugin.hive.TransactionalMetadataFactory; import io.trino.plugin.hive.metastore.Partition; import io.trino.plugin.hive.metastore.SemiTransactionalHiveMetastore; @@ -94,28 +96,32 @@ private void doUnregisterPartition(ConnectorSession session, ConnectorAccessCont SchemaTableName schemaTableName = new SchemaTableName(schemaName, tableName); - SemiTransactionalHiveMetastore metastore = hiveMetadataFactory.create(session.getIdentity(), true).getMetastore(); + TransactionalMetadata hiveMetadata = hiveMetadataFactory.create(session.getIdentity(), true); + hiveMetadata.beginQuery(session); + try (UncheckedCloseable ignore = () -> hiveMetadata.cleanupQuery(session)) { + SemiTransactionalHiveMetastore metastore = hiveMetadata.getMetastore(); - Table table = metastore.getTable(schemaName, tableName) - .orElseThrow(() -> new TableNotFoundException(schemaTableName)); + Table table = metastore.getTable(schemaName, tableName) + .orElseThrow(() -> new TableNotFoundException(schemaTableName)); - accessControl.checkCanDeleteFromTable(null, schemaTableName); + accessControl.checkCanDeleteFromTable(null, schemaTableName); - checkIsPartitionedTable(table); - checkPartitionColumns(table, partitionColumns); + checkIsPartitionedTable(table); + checkPartitionColumns(table, partitionColumns); - String partitionName = makePartName(partitionColumns, partitionValues); + String partitionName = makePartName(partitionColumns, partitionValues); - Partition partition = metastore.unsafeGetRawHiveMetastoreClosure().getPartition(schemaName, tableName, partitionValues) - .orElseThrow(() -> new TrinoException(NOT_FOUND, format("Partition '%s' does not exist", partitionName))); + Partition partition = metastore.unsafeGetRawHiveMetastoreClosure().getPartition(schemaName, tableName, partitionValues) + .orElseThrow(() -> new TrinoException(NOT_FOUND, format("Partition '%s' does not exist", partitionName))); - metastore.dropPartition( - session, - table.getDatabaseName(), - table.getTableName(), - partition.getValues(), - false); + metastore.dropPartition( + session, + table.getDatabaseName(), + table.getTableName(), + partition.getValues(), + false); - metastore.commit(); + metastore.commit(); + } } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseS3AndGlueMetastoreTest.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseS3AndGlueMetastoreTest.java index f43d7a7f72ca..fcf9f741a9a7 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseS3AndGlueMetastoreTest.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseS3AndGlueMetastoreTest.java @@ -18,6 +18,7 @@ import com.amazonaws.services.s3.model.ListObjectsV2Request; import com.amazonaws.services.s3.model.S3ObjectSummary; import io.trino.Session; +import io.trino.plugin.base.util.UncheckedCloseable; import io.trino.plugin.hive.metastore.HiveMetastore; import io.trino.spi.connector.SchemaNotFoundException; import io.trino.testing.AbstractTestQueryFramework; @@ -346,11 +347,4 @@ public String locationForTable(String bucketName, String schemaName, String tabl return locationPattern.formatted(bucketName, schemaName, tableName); } } - - protected interface UncheckedCloseable - extends AutoCloseable - { - @Override - void close(); - } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveS3AndGlueMetastoreTest.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveS3AndGlueMetastoreTest.java index 15ff9fc407a9..d45371b16b0b 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveS3AndGlueMetastoreTest.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveS3AndGlueMetastoreTest.java @@ -15,6 +15,7 @@ import com.google.common.collect.ImmutableMap; import io.trino.Session; +import io.trino.plugin.base.util.UncheckedCloseable; import io.trino.spi.security.Identity; import io.trino.spi.security.SelectedRole; import io.trino.testing.DistributedQueryRunner; diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergS3AndGlueMetastoreTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergS3AndGlueMetastoreTest.java index 7c16726b970d..326b38011ba3 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergS3AndGlueMetastoreTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergS3AndGlueMetastoreTest.java @@ -14,6 +14,7 @@ package io.trino.plugin.iceberg.catalog.glue; import com.google.common.collect.ImmutableMap; +import io.trino.plugin.base.util.UncheckedCloseable; import io.trino.plugin.hive.BaseS3AndGlueMetastoreTest; import io.trino.plugin.iceberg.IcebergQueryRunner; import io.trino.testing.DistributedQueryRunner; From 243a3413c059a00dfa4797f284917165069e9ea6 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Fri, 10 Nov 2023 10:08:51 -0800 Subject: [PATCH 193/587] Fix non-deterministic construction of Kafka plugin There's an order-dependent initialization of Guice modules happening within the Kafka plugin when they are added indirectly via "install()" that causes ConfigurationAwareModule to not consume the expected configuration properties. --- .../plugin/kafka/KafkaConnectorFactory.java | 33 +++++++++++-------- .../io/trino/plugin/kafka/KafkaPlugin.java | 25 ++++---------- .../trino/plugin/kafka/KafkaQueryRunner.java | 27 +++++---------- .../plugin/kafka/KafkaQueryRunnerBuilder.java | 12 ++++--- 4 files changed, 41 insertions(+), 56 deletions(-) diff --git a/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/KafkaConnectorFactory.java b/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/KafkaConnectorFactory.java index e9a33f54de47..77df5cee8fb7 100644 --- a/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/KafkaConnectorFactory.java +++ b/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/KafkaConnectorFactory.java @@ -13,18 +13,21 @@ */ package io.trino.plugin.kafka; +import com.google.common.collect.ImmutableList; import com.google.inject.Injector; import com.google.inject.Module; import io.airlift.bootstrap.Bootstrap; import io.airlift.json.JsonModule; import io.trino.plugin.base.CatalogNameModule; import io.trino.plugin.base.TypeDeserializerModule; +import io.trino.plugin.kafka.security.KafkaSecurityModule; import io.trino.spi.NodeManager; import io.trino.spi.connector.Connector; import io.trino.spi.connector.ConnectorContext; import io.trino.spi.connector.ConnectorFactory; import io.trino.spi.type.TypeManager; +import java.util.List; import java.util.Map; import static io.trino.plugin.base.Versions.checkStrictSpiVersionMatch; @@ -33,11 +36,11 @@ public class KafkaConnectorFactory implements ConnectorFactory { - private final Module extension; + private final List extensions; - public KafkaConnectorFactory(Module extension) + public KafkaConnectorFactory(List extensions) { - this.extension = requireNonNull(extension, "extension is null"); + this.extensions = ImmutableList.copyOf(extensions); } @Override @@ -54,16 +57,20 @@ public Connector create(String catalogName, Map config, Connecto checkStrictSpiVersionMatch(context, this); Bootstrap app = new Bootstrap( - new CatalogNameModule(catalogName), - new JsonModule(), - new TypeDeserializerModule(context.getTypeManager()), - new KafkaConnectorModule(context.getTypeManager()), - extension, - binder -> { - binder.bind(ClassLoader.class).toInstance(KafkaConnectorFactory.class.getClassLoader()); - binder.bind(TypeManager.class).toInstance(context.getTypeManager()); - binder.bind(NodeManager.class).toInstance(context.getNodeManager()); - }); + ImmutableList.builder() + .add(new CatalogNameModule(catalogName)) + .add(new JsonModule()) + .add(new TypeDeserializerModule(context.getTypeManager())) + .add(new KafkaConnectorModule(context.getTypeManager())) + .add(new KafkaClientsModule()) + .add(new KafkaSecurityModule()) + .add(binder -> { + binder.bind(ClassLoader.class).toInstance(KafkaConnectorFactory.class.getClassLoader()); + binder.bind(TypeManager.class).toInstance(context.getTypeManager()); + binder.bind(NodeManager.class).toInstance(context.getNodeManager()); + }) + .addAll(extensions) + .build()); Injector injector = app .doNotInitializeLogging() diff --git a/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/KafkaPlugin.java b/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/KafkaPlugin.java index 1eeaa143c770..18b022cd12d7 100644 --- a/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/KafkaPlugin.java +++ b/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/KafkaPlugin.java @@ -14,43 +14,30 @@ package io.trino.plugin.kafka; import com.google.common.collect.ImmutableList; -import com.google.inject.Binder; import com.google.inject.Module; -import io.airlift.configuration.AbstractConfigurationAwareModule; -import io.trino.plugin.kafka.security.KafkaSecurityModule; import io.trino.spi.Plugin; import io.trino.spi.connector.ConnectorFactory; -import static java.util.Objects.requireNonNull; +import java.util.List; public class KafkaPlugin implements Plugin { - public static final Module DEFAULT_EXTENSION = new AbstractConfigurationAwareModule() - { - @Override - protected void setup(Binder binder) - { - install(new KafkaClientsModule()); - install(new KafkaSecurityModule()); - } - }; - - private final Module extension; + private final List extensions; public KafkaPlugin() { - this(DEFAULT_EXTENSION); + this(ImmutableList.of()); } - public KafkaPlugin(Module extension) + public KafkaPlugin(List extensions) { - this.extension = requireNonNull(extension, "extension is null"); + this.extensions = ImmutableList.copyOf(extensions); } @Override public synchronized Iterable getConnectorFactories() { - return ImmutableList.of(new KafkaConnectorFactory(extension)); + return ImmutableList.of(new KafkaConnectorFactory(extensions)); } } diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/KafkaQueryRunner.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/KafkaQueryRunner.java index eabb93d5cfc1..d47bb15ebd46 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/KafkaQueryRunner.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/KafkaQueryRunner.java @@ -15,7 +15,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.inject.Module; import com.google.inject.Scopes; import io.airlift.json.JsonCodec; import io.airlift.log.Logger; @@ -43,7 +42,6 @@ import static com.google.common.io.ByteStreams.toByteArray; import static io.airlift.configuration.ConditionalModule.conditionalModule; -import static io.airlift.configuration.ConfigurationAwareModule.combine; import static io.airlift.units.Duration.nanosSince; import static io.trino.plugin.kafka.util.TestUtils.loadTpchTopicDescription; import static java.lang.String.format; @@ -91,13 +89,6 @@ public Builder setExtraTopicDescription(Map kafkaConfig.getTableDescriptionSupplier().equalsIgnoreCase(TEST), - binder -> binder.bind(TableDescriptionSupplier.class) - .toInstance(new MapBasedTableDescriptionSupplier(topicDescriptions))), - binder -> binder.bind(ContentSchemaProvider.class).to(FileReadContentSchemaProvider.class).in(Scopes.SINGLETON), - new DecoderModule(), - new EncoderModule())); + addExtension(conditionalModule( + KafkaConfig.class, + kafkaConfig -> kafkaConfig.getTableDescriptionSupplier().equalsIgnoreCase(TEST), + binder -> binder.bind(TableDescriptionSupplier.class) + .toInstance(new MapBasedTableDescriptionSupplier(topicDescriptions)))); + addExtension(binder -> binder.bind(ContentSchemaProvider.class).to(FileReadContentSchemaProvider.class).in(Scopes.SINGLETON)); + addExtension(new DecoderModule()); + addExtension(new EncoderModule()); Map properties = new HashMap<>(extraKafkaProperties); properties.putIfAbsent("kafka.table-description-supplier", TEST); setExtraKafkaProperties(properties); diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/KafkaQueryRunnerBuilder.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/KafkaQueryRunnerBuilder.java index ead9d8bfe4c5..aae5609461ea 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/KafkaQueryRunnerBuilder.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/KafkaQueryRunnerBuilder.java @@ -20,11 +20,12 @@ import io.trino.testing.DistributedQueryRunner; import io.trino.testing.kafka.TestingKafka; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import static io.airlift.testing.Closeables.closeAllSuppress; -import static io.trino.plugin.kafka.KafkaPlugin.DEFAULT_EXTENSION; import static io.trino.testing.TestingSession.testSessionBuilder; import static java.util.Objects.requireNonNull; @@ -33,7 +34,7 @@ public abstract class KafkaQueryRunnerBuilder { protected final TestingKafka testingKafka; protected Map extraKafkaProperties = ImmutableMap.of(); - protected Module extension = DEFAULT_EXTENSION; + private final List extensions = new ArrayList<>(); private final String catalogName; public KafkaQueryRunnerBuilder(TestingKafka testingKafka, String defaultSessionName) @@ -57,9 +58,10 @@ public KafkaQueryRunnerBuilder setExtraKafkaProperties(Map extra return this; } - public KafkaQueryRunnerBuilder setExtension(Module extension) + public KafkaQueryRunnerBuilder addExtension(Module extension) { - this.extension = requireNonNull(extension, "extension is null"); + requireNonNull(extension, "extension is null"); + extensions.add(extension); return this; } @@ -74,7 +76,7 @@ public final DistributedQueryRunner build() try { testingKafka.start(); preInit(queryRunner); - queryRunner.installPlugin(new KafkaPlugin(extension)); + queryRunner.installPlugin(new KafkaPlugin(extensions)); // note: additional copy via ImmutableList so that if fails on nulls Map kafkaProperties = new HashMap<>(ImmutableMap.copyOf(extraKafkaProperties)); kafkaProperties.putIfAbsent("kafka.nodes", testingKafka.getConnectString()); From 97e3ca3dacb7ee3fbe7e0dd0996446577372d48d Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Thu, 9 Nov 2023 09:33:21 -0800 Subject: [PATCH 194/587] Update junit-extensions to version 2 It improves log formatting in tests --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2768711e83b0..cc7582914043 100644 --- a/pom.xml +++ b/pom.xml @@ -653,7 +653,7 @@ io.airlift junit-extensions - 1 + 2 From c827c716552e45ccbf385fd918d7675668b4142c Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Wed, 8 Nov 2023 12:40:53 -0800 Subject: [PATCH 195/587] Remove unnecessary data provider --- .../java/io/trino/server/rpm/ServerIT.java | 38 ++-- .../test/java/io/trino/orc/TestOrcLz4.java | 22 +-- .../TestStringStatisticsBuilder.java | 77 ++++---- .../trino/parquet/reader/TestTimeMillis.java | 23 ++- .../TestKerberosConfiguration.java | 25 +-- ...seFileBasedConnectorAccessControlTest.java | 36 ++-- .../BaseFileBasedSystemAccessControlTest.java | 36 ++-- .../protobuf/ProtobufDataProviders.java | 71 ------- .../decoder/protobuf/TestProtobufDecoder.java | 137 ++++++++++++- .../kafka/protobuf/TestProtobufEncoder.java | 187 +++++++++++++++++- .../redshift/TestRedshiftTypeMapping.java | 64 ++++-- 11 files changed, 479 insertions(+), 237 deletions(-) delete mode 100644 lib/trino-record-decoder/src/test/java/io/trino/decoder/protobuf/ProtobufDataProviders.java diff --git a/core/trino-server-rpm/src/test/java/io/trino/server/rpm/ServerIT.java b/core/trino-server-rpm/src/test/java/io/trino/server/rpm/ServerIT.java index 66f6a11b6708..4caa9da4951a 100644 --- a/core/trino-server-rpm/src/test/java/io/trino/server/rpm/ServerIT.java +++ b/core/trino-server-rpm/src/test/java/io/trino/server/rpm/ServerIT.java @@ -18,7 +18,6 @@ import org.testcontainers.containers.BindMode; import org.testcontainers.containers.Container.ExecResult; import org.testcontainers.containers.GenericContainer; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.io.File; @@ -45,8 +44,21 @@ public class ServerIT private static final String BASE_IMAGE_PREFIX = "eclipse-temurin:"; private static final String BASE_IMAGE_SUFFIX = "-jre-ubi9-minimal"; - @Test(dataProvider = "rpmJavaTestDataProvider") - public void testInstall(String rpmHostPath, String javaVersion) + private final String rpmHostPath; + + public ServerIT() + { + rpmHostPath = requireNonNull(System.getProperty("rpm"), "rpm is null"); + } + + @Test + public void testInstall() + { + testInstall("17"); + testInstall("21"); + } + + private void testInstall(String javaVersion) { String rpm = "/" + new File(rpmHostPath).getName(); String command = "" + @@ -89,8 +101,15 @@ public void testInstall(String rpmHostPath, String javaVersion) } } - @Test(dataProvider = "rpmJavaTestDataProvider") - public void testUninstall(String rpmHostPath, String javaVersion) + @Test + public void testUninstall() + throws Exception + { + testUninstall("17"); + testUninstall("21"); + } + + private void testUninstall(String javaVersion) throws Exception { String rpm = "/" + new File(rpmHostPath).getName(); @@ -122,15 +141,6 @@ public void testUninstall(String rpmHostPath, String javaVersion) } } - @DataProvider - public static Object[][] rpmJavaTestDataProvider() - { - String rpmHostPath = requireNonNull(System.getProperty("rpm"), "rpm is null"); - return new Object[][]{ - {rpmHostPath, "17"}, - {rpmHostPath, "21"}}; - } - private static void assertPathDeleted(GenericContainer container, String path) throws Exception { diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcLz4.java b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcLz4.java index 59b40f444c63..5ae4b7d64e21 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcLz4.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcLz4.java @@ -18,7 +18,6 @@ import io.trino.spi.Page; import io.trino.spi.block.Block; import org.joda.time.DateTimeZone; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.nio.file.Path; @@ -41,8 +40,15 @@ public class TestOrcLz4 { private static final long POSITION_COUNT = 10_000; - @Test(dataProvider = "testOrcDataProvider") - public void testReadLz4(byte[] data) + @Test + public void testReadLz4() + throws Exception + { + testReadLz4(readOrcTestData("apache-lz4.orc")); + testReadLz4(generateOrcTestData()); + } + + private void testReadLz4(byte[] data) throws Exception { OrcReader orcReader = OrcReader.createOrcReader(new MemoryOrcDataSource(new OrcDataSourceId("memory"), Slices.wrappedBuffer(data)), new OrcReaderOptions()) @@ -83,16 +89,6 @@ public void testReadLz4(byte[] data) } } - @DataProvider - public static Object[][] testOrcDataProvider() - throws Exception - { - return new Object[][] { - {readOrcTestData("apache-lz4.orc")}, - {generateOrcTestData()} - }; - } - private static byte[] readOrcTestData(String resourceFile) throws Exception { diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestStringStatisticsBuilder.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestStringStatisticsBuilder.java index dc3bc0d9e2e3..252ae32e9678 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestStringStatisticsBuilder.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestStringStatisticsBuilder.java @@ -16,7 +16,6 @@ import com.google.common.collect.ImmutableList; import io.airlift.slice.Slice; import io.airlift.slice.Slices; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.util.ArrayList; @@ -28,8 +27,6 @@ import static io.trino.orc.metadata.statistics.AbstractStatisticsBuilderTest.StatisticsType.STRING; import static io.trino.orc.metadata.statistics.ColumnStatistics.mergeColumnStatistics; import static io.trino.orc.metadata.statistics.StringStatistics.STRING_VALUE_BYTES_OVERHEAD; -import static io.trino.orc.metadata.statistics.StringStatisticsBuilder.StringCompactor.truncateMax; -import static io.trino.orc.metadata.statistics.StringStatisticsBuilder.StringCompactor.truncateMin; import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.testng.Assert.assertEquals; @@ -338,65 +335,59 @@ public void testBloomFilter() assertFalse(bloomFilter.testSlice(LOW_TOP_VALUE)); } - @DataProvider(name = "computeMin") - public static Object[][] computeMinProvider() + @Test + public void testComputeMin() { - return new Object[][] { - {"simple/case", "simple", 6}, - {"simple/ƒ", "simple/", 8}, - {"simple/語", "simple/", 9}, - {"simple/語", "simple/", 8}, - {"simple/\uD80C\uDE02", "simple/", 10}, - {"simple/\uD80C\uDE02", "simple/", 9}, - {"simple/\uD80C\uDE02", "simple/", 8}, - {"\uDBFF\uDFFF", "", 3}, - }; + assertThat(truncateMin("simple/case", 6)).containsExactly("simple".getBytes(UTF_8)); + assertThat(truncateMin("simple/ƒ", 8)).containsExactly("simple/".getBytes(UTF_8)); + assertThat(truncateMin("simple/語", 9)).containsExactly("simple/".getBytes(UTF_8)); + assertThat(truncateMin("simple/語", 8)).containsExactly("simple/".getBytes(UTF_8)); + assertThat(truncateMin("simple/\uD80C\uDE02", 10)).containsExactly("simple/".getBytes(UTF_8)); + assertThat(truncateMin("simple/\uD80C\uDE02", 9)).containsExactly("simple/".getBytes(UTF_8)); + assertThat(truncateMin("simple/\uD80C\uDE02", 8)).containsExactly("simple/".getBytes(UTF_8)); + assertThat(truncateMin("\uDBFF\uDFFF", 3)).containsExactly("".getBytes(UTF_8)); } - @Test(dataProvider = "computeMin") - public void testComputeMin(String input, String expected, int maxLength) + @Test + public void testComputeMax() { - assertThat(truncateMin(Slices.wrappedBuffer(input.getBytes(UTF_8)), maxLength).getBytes()).containsExactly(expected.getBytes(UTF_8)); + assertThat(truncateMax("simple/case", 6)).containsExactly("simplf".getBytes(UTF_8)); + assertThat(truncateMax("simple/ƒ", 8)).containsExactly("simple0".getBytes(UTF_8)); + assertThat(truncateMax("simple/語", 9)).containsExactly("simple0".getBytes(UTF_8)); + assertThat(truncateMax("simple/語", 8)).containsExactly("simple0".getBytes(UTF_8)); + assertThat(truncateMax("simple/\uD80C\uDE02", 10)).containsExactly("simple0".getBytes(UTF_8)); + assertThat(truncateMax("simple/\uD80C\uDE02", 9)).containsExactly("simple0".getBytes(UTF_8)); + assertThat(truncateMax("simple/\uD80C\uDE02", 8)).containsExactly("simple0".getBytes(UTF_8)); + assertThat(truncateMax("simple/ƒƒ", 10)).containsExactly("simple/Ɠ".getBytes(UTF_8)); + assertThat(truncateMax("\uDBFF\uDFFF", 3)).containsExactly("".getBytes(UTF_8)); } - @DataProvider(name = "computeMax") - public static Object[][] computeMaxProvider() + @Test + public void testComputeMaxForMaxUtf8Chars() { - return new Object[][] { - {"simple/case", "simplf", 6}, - {"simple/ƒ", "simple0", 8}, - {"simple/語", "simple0", 9}, - {"simple/語", "simple0", 8}, - {"simple/\uD80C\uDE02", "simple0", 10}, - {"simple/\uD80C\uDE02", "simple0", 9}, - {"simple/\uD80C\uDE02", "simple0", 8}, - {"simple/ƒƒ", "simple/Ɠ", 10}, - {"\uDBFF\uDFFF", "", 3}, - }; + assertThat(truncateMax("\uDBFF\uDFFF\uDBFF\uDFFF\uDBFF\uDFFF", 11)).isEqualTo(new byte[0]); } - @Test(dataProvider = "computeMax") - public void testComputeMax(String input, String expected, int maxLength) + @Test + public void testComputeMaxSkipsMaxUtf8Chars() { - assertThat(truncateMax(Slices.wrappedBuffer(input.getBytes(UTF_8)), maxLength).getBytes()).containsExactly(expected.getBytes(UTF_8)); + assertThat(truncateMax("a\uDBFF\uDFFF\uDBFF\uDFFF\uDBFF\uDFFF", 12)).containsExactly("b".getBytes(UTF_8)); } @Test - public void testComputeMaxForMaxUtf8Chars() + public void testComputeMixMaxReturnsNullForMaxLengthEquals0() { - assertThat(truncateMax(Slices.wrappedBuffer("\uDBFF\uDFFF\uDBFF\uDFFF\uDBFF\uDFFF".getBytes(UTF_8)), 11)).isEqualTo(EMPTY_SLICE); + assertThat(StringStatisticsBuilder.StringCompactor.truncateMin(Slices.wrappedBuffer("simple/test".getBytes(UTF_8)), 0)).isEqualTo(EMPTY_SLICE); + assertThat(truncateMax("simple/test", 0)).isEqualTo(new byte[0]); } - @Test - public void testComputeMaxSkipsMaxUtf8Chars() + private static byte[] truncateMin(String input, int maxLength) { - assertThat(truncateMax(Slices.wrappedBuffer("a\uDBFF\uDFFF\uDBFF\uDFFF\uDBFF\uDFFF".getBytes(UTF_8)), 12).getBytes()).containsExactly("b".getBytes(UTF_8)); + return StringStatisticsBuilder.StringCompactor.truncateMin(Slices.wrappedBuffer(input.getBytes(UTF_8)), maxLength).getBytes(); } - @Test - public void testComputeMixMaxReturnsNullForMaxLengthEquals0() + private static byte[] truncateMax(String input, int maxLength) { - assertThat(truncateMin(Slices.wrappedBuffer("simple/test".getBytes(UTF_8)), 0)).isEqualTo(EMPTY_SLICE); - assertThat(truncateMax(Slices.wrappedBuffer("simple/test".getBytes(UTF_8)), 0)).isEqualTo(EMPTY_SLICE); + return StringStatisticsBuilder.StringCompactor.truncateMax(Slices.wrappedBuffer(input.getBytes(UTF_8)), maxLength).getBytes(); } } diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestTimeMillis.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestTimeMillis.java index cd781878c1db..bcafc326c101 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestTimeMillis.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestTimeMillis.java @@ -23,13 +23,11 @@ import io.trino.spi.type.TimeType; import io.trino.spi.type.Type; import org.apache.parquet.hadoop.metadata.ParquetMetadata; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.io.File; import java.util.List; import java.util.Optional; -import java.util.stream.Stream; import static io.trino.memory.context.AggregatedMemoryContext.newSimpleAggregatedMemoryContext; import static io.trino.parquet.ParquetTestUtils.createParquetReader; @@ -37,14 +35,22 @@ import static io.trino.spi.type.TimeType.TIME_MILLIS; import static io.trino.spi.type.TimeType.TIME_NANOS; import static io.trino.spi.type.TimeType.TIME_SECONDS; -import static io.trino.testing.DataProviders.toDataProvider; import static io.trino.testing.TestingConnectorSession.SESSION; import static org.assertj.core.api.Assertions.assertThat; public class TestTimeMillis { - @Test(dataProvider = "timeTypeProvider") - public void testTimeMillsInt32(TimeType timeType) + @Test + public void testTimeMillsInt32() + throws Exception + { + testTimeMillsInt32(TIME_SECONDS); + testTimeMillsInt32(TIME_MILLIS); + testTimeMillsInt32(TIME_MICROS); + testTimeMillsInt32(TIME_NANOS); + } + + private void testTimeMillsInt32(TimeType timeType) throws Exception { List columnNames = ImmutableList.of("COLUMN1", "COLUMN2"); @@ -71,11 +77,4 @@ public void testTimeMillsInt32(TimeType timeType) assertThat(timeType.getObjectValue(SESSION, block, 0)) .isEqualTo(SqlTime.newInstance(precision, timeType == TIME_SECONDS ? 0L : 86399999000000000L)); } - - @DataProvider - public static Object[][] timeTypeProvider() - { - return Stream.of(TIME_SECONDS, TIME_MILLIS, TIME_MICROS, TIME_NANOS) - .collect(toDataProvider()); - } } diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/authentication/TestKerberosConfiguration.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/authentication/TestKerberosConfiguration.java index c5c3748333f7..0d1a01d3638b 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/authentication/TestKerberosConfiguration.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/authentication/TestKerberosConfiguration.java @@ -13,7 +13,6 @@ */ package io.trino.plugin.base.authentication; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import static io.trino.plugin.base.authentication.KerberosConfiguration.Builder.getServerPrincipal; @@ -23,22 +22,14 @@ public class TestKerberosConfiguration { private static final String HOST_NAME = "host_name"; - @Test(dataProvider = "kerberosPrincipalPattern") - public void testHostnameSubstitution(String actual, String expected) + @Test + public void testHostnameSubstitution() { - assertThat(getServerPrincipal(actual, HOST_NAME)).isEqualTo(expected); - } - - @DataProvider(name = "kerberosPrincipalPattern") - public Object[][] kerberosPrincipalPattern() - { - return new Object[][] { - {"server/_HOST@REALM.COM", "server/host_name@REALM.COM"}, - {"server/_HOST", "server/host_name"}, - {"server/trino-worker@REALM.COM", "server/trino-worker@REALM.COM"}, - {"server/trino-worker", "server/trino-worker"}, - {"SERVER_HOST/_HOST@REALM.COM", "SERVER_HOST/host_name@REALM.COM"}, - {"SERVER_HOST/_HOST", "SERVER_HOST/host_name"}, - }; + assertThat(getServerPrincipal("server/_HOST@REALM.COM", HOST_NAME)).isEqualTo("server/host_name@REALM.COM"); + assertThat(getServerPrincipal("server/_HOST", HOST_NAME)).isEqualTo("server/host_name"); + assertThat(getServerPrincipal("server/trino-worker@REALM.COM", HOST_NAME)).isEqualTo("server/trino-worker@REALM.COM"); + assertThat(getServerPrincipal("server/trino-worker", HOST_NAME)).isEqualTo("server/trino-worker"); + assertThat(getServerPrincipal("SERVER_HOST/_HOST@REALM.COM", HOST_NAME)).isEqualTo("SERVER_HOST/host_name@REALM.COM"); + assertThat(getServerPrincipal("SERVER_HOST/_HOST", HOST_NAME)).isEqualTo("SERVER_HOST/host_name"); } } diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedConnectorAccessControlTest.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedConnectorAccessControlTest.java index abf4b0b8fa42..6eb93732ab69 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedConnectorAccessControlTest.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedConnectorAccessControlTest.java @@ -32,16 +32,13 @@ import io.trino.spi.security.TrinoPrincipal; import io.trino.spi.security.ViewExpression; import org.testng.Assert.ThrowingRunnable; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.io.File; -import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.stream.Stream; import static com.google.common.io.Files.copy; import static io.trino.spi.security.PrincipalType.ROLE; @@ -201,8 +198,16 @@ public void testSchemaRules() assertDenied(() -> accessControl.checkCanShowCreateSchema(CHARLIE, "test")); } - @Test(dataProvider = "privilegeGrantOption") - public void testGrantSchemaPrivilege(Privilege privilege, boolean grantOption) + @Test + public void testGrantSchemaPrivilege() + { + for (Privilege privilege : Privilege.values()) { + testGrantSchemaPrivilege(privilege, false); + testGrantSchemaPrivilege(privilege, true); + } + } + + private void testGrantSchemaPrivilege(Privilege privilege, boolean grantOption) { ConnectorAccessControl accessControl = createAccessControl("schema.json"); TrinoPrincipal grantee = new TrinoPrincipal(USER, "alice"); @@ -245,8 +250,16 @@ public void testDenySchemaPrivilege() assertDenied(() -> accessControl.checkCanDenySchemaPrivilege(CHARLIE, UPDATE, "test", grantee)); } - @Test(dataProvider = "privilegeGrantOption") - public void testRevokeSchemaPrivilege(Privilege privilege, boolean grantOption) + @Test + public void testRevokeSchemaPrivilege() + { + for (Privilege privilege : Privilege.values()) { + testRevokeSchemaPrivilege(privilege, false); + testRevokeSchemaPrivilege(privilege, true); + } + } + + private void testRevokeSchemaPrivilege(Privilege privilege, boolean grantOption) { ConnectorAccessControl accessControl = createAccessControl("schema.json"); TrinoPrincipal grantee = new TrinoPrincipal(USER, "alice"); @@ -267,15 +280,6 @@ public void testRevokeSchemaPrivilege(Privilege privilege, boolean grantOption) assertDenied(() -> accessControl.checkCanRevokeSchemaPrivilege(CHARLIE, privilege, "test", grantee, grantOption)); } - @DataProvider(name = "privilegeGrantOption") - public Object[][] privilegeGrantOption() - { - return EnumSet.allOf(Privilege.class) - .stream() - .flatMap(privilege -> Stream.of(true, false).map(grantOption -> new Object[] {privilege, grantOption})) - .toArray(Object[][]::new); - } - @Test public void testTableRules() { diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedSystemAccessControlTest.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedSystemAccessControlTest.java index c996a01dc780..f8daf237e5d3 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedSystemAccessControlTest.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedSystemAccessControlTest.java @@ -32,19 +32,16 @@ import io.trino.spi.security.TrinoPrincipal; import io.trino.spi.security.ViewExpression; import org.assertj.core.api.ThrowableAssert.ThrowingCallable; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import javax.security.auth.kerberos.KerberosPrincipal; import java.io.File; import java.time.Instant; -import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.stream.Stream; import static com.google.common.io.Files.copy; import static io.trino.spi.security.PrincipalType.ROLE; @@ -315,8 +312,16 @@ public void testSchemaRulesForCheckCanShowCreateSchema() assertAccessDenied(() -> accessControl.checkCanShowCreateSchema(CHARLIE, new CatalogSchemaName("some-catalog", "test")), SHOW_CREATE_SCHEMA_ACCESS_DENIED_MESSAGE); } - @Test(dataProvider = "privilegeGrantOption") - public void testGrantSchemaPrivilege(Privilege privilege, boolean grantOption) + @Test + public void testGrantSchemaPrivilege() + { + for (Privilege privilege : Privilege.values()) { + testGrantSchemaPrivilege(privilege, false); + testGrantSchemaPrivilege(privilege, true); + } + } + + private void testGrantSchemaPrivilege(Privilege privilege, boolean grantOption) { SystemAccessControl accessControl = newFileBasedSystemAccessControl("file-based-system-access-schema.json"); TrinoPrincipal grantee = new TrinoPrincipal(USER, "alice"); @@ -375,8 +380,16 @@ public void testDenySchemaPrivilege() format(DENY_SCHEMA_ACCESS_DENIED_MESSAGE, UPDATE, "some-catalog.test", "")); } - @Test(dataProvider = "privilegeGrantOption") - public void testRevokeSchemaPrivilege(Privilege privilege, boolean grantOption) + @Test + public void testRevokeSchemaPrivilege() + { + for (Privilege privilege : Privilege.values()) { + testRevokeSchemaPrivilege(privilege, false); + testRevokeSchemaPrivilege(privilege, true); + } + } + + private void testRevokeSchemaPrivilege(Privilege privilege, boolean grantOption) { SystemAccessControl accessControl = newFileBasedSystemAccessControl("file-based-system-access-schema.json"); TrinoPrincipal grantee = new TrinoPrincipal(USER, "alice"); @@ -405,15 +418,6 @@ public void testRevokeSchemaPrivilege(Privilege privilege, boolean grantOption) format(REVOKE_SCHEMA_ACCESS_DENIED_MESSAGE, privilege, "some-catalog.test", "")); } - @DataProvider(name = "privilegeGrantOption") - public Object[][] privilegeGrantOption() - { - return EnumSet.allOf(Privilege.class) - .stream() - .flatMap(privilege -> Stream.of(true, false).map(grantOption -> new Object[] {privilege, grantOption})) - .toArray(Object[][]::new); - } - @Test public void testTableRulesForCheckCanSelectFromColumns() { diff --git a/lib/trino-record-decoder/src/test/java/io/trino/decoder/protobuf/ProtobufDataProviders.java b/lib/trino-record-decoder/src/test/java/io/trino/decoder/protobuf/ProtobufDataProviders.java deleted file mode 100644 index d9383833d32d..000000000000 --- a/lib/trino-record-decoder/src/test/java/io/trino/decoder/protobuf/ProtobufDataProviders.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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 io.trino.decoder.protobuf; - -import org.testng.annotations.DataProvider; - -import java.time.LocalDateTime; - -import static io.trino.testing.DateTimeTestingUtils.sqlTimestampOf; -import static java.lang.Math.PI; -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.stream.Collectors.joining; -import static java.util.stream.IntStream.range; - -public class ProtobufDataProviders -{ - @DataProvider - public Object[][] allTypesDataProvider() - { - return new Object[][] { - { - "Trino", - 1, - 493857959588286460L, - PI, - 3.14f, - true, - "ONE", - sqlTimestampOf(3, LocalDateTime.parse("2020-12-12T15:35:45.923")), - "X'65683F'".getBytes(UTF_8) - }, - { - range(0, 5000) - .mapToObj(Integer::toString) - .collect(joining(", ")), - Integer.MAX_VALUE, - Long.MIN_VALUE, - Double.MAX_VALUE, - Float.MIN_VALUE, - false, - "ZERO", - sqlTimestampOf(3, LocalDateTime.parse("1856-01-12T05:25:14.456")), - new byte[0] - }, - { - range(5000, 10000) - .mapToObj(Integer::toString) - .collect(joining(", ")), - Integer.MIN_VALUE, - Long.MAX_VALUE, - Double.NaN, - Float.NEGATIVE_INFINITY, - false, - "ZERO", - sqlTimestampOf(3, LocalDateTime.parse("0001-01-01T00:00:00.923")), - "X'65683F'".getBytes(UTF_8) - } - }; - } -} diff --git a/lib/trino-record-decoder/src/test/java/io/trino/decoder/protobuf/TestProtobufDecoder.java b/lib/trino-record-decoder/src/test/java/io/trino/decoder/protobuf/TestProtobufDecoder.java index a5c78d760850..db4188d0403d 100644 --- a/lib/trino-record-decoder/src/test/java/io/trino/decoder/protobuf/TestProtobufDecoder.java +++ b/lib/trino-record-decoder/src/test/java/io/trino/decoder/protobuf/TestProtobufDecoder.java @@ -74,6 +74,8 @@ import static java.lang.Math.floorDiv; import static java.lang.Math.floorMod; import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.stream.Collectors.joining; +import static java.util.stream.IntStream.range; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNull; @@ -83,8 +85,49 @@ public class TestProtobufDecoder { private static final ProtobufRowDecoderFactory DECODER_FACTORY = new ProtobufRowDecoderFactory(new FixedSchemaDynamicMessageProvider.Factory(), TESTING_TYPE_MANAGER, new FileDescriptorProvider()); - @Test(dataProvider = "allTypesDataProvider", dataProviderClass = ProtobufDataProviders.class) - public void testAllDataTypes(String stringData, Integer integerData, Long longData, Double doubleData, Float floatData, Boolean booleanData, String enumData, SqlTimestamp sqlTimestamp, byte[] bytesData) + @Test + public void testAllDataTypes() + throws Exception + { + testAllDataTypes( + "Trino", + 1, + 493857959588286460L, + PI, + 3.14f, + true, + "ONE", + sqlTimestampOf(3, LocalDateTime.parse("2020-12-12T15:35:45.923")), + "X'65683F'".getBytes(UTF_8)); + + testAllDataTypes( + range(0, 5000) + .mapToObj(Integer::toString) + .collect(joining(", ")), + Integer.MAX_VALUE, + Long.MIN_VALUE, + Double.MAX_VALUE, + Float.MIN_VALUE, + false, + "ZERO", + sqlTimestampOf(3, LocalDateTime.parse("1856-01-12T05:25:14.456")), + new byte[0]); + + testAllDataTypes( + range(5000, 10000) + .mapToObj(Integer::toString) + .collect(joining(", ")), + Integer.MIN_VALUE, + Long.MAX_VALUE, + Double.NaN, + Float.NEGATIVE_INFINITY, + false, + "ZERO", + sqlTimestampOf(3, LocalDateTime.parse("0001-01-01T00:00:00.923")), + "X'65683F'".getBytes(UTF_8)); + } + + private void testAllDataTypes(String stringData, Integer integerData, Long longData, Double doubleData, Float floatData, Boolean booleanData, String enumData, SqlTimestamp sqlTimestamp, byte[] bytesData) throws Exception { DecoderTestColumnHandle stringColumn = new DecoderTestColumnHandle(0, "stringColumn", createVarcharType(30000), "stringColumn", null, null, false, false, false); @@ -372,8 +415,49 @@ public void testAnyTypeWithFileDescriptor() assertEquals(actual.get("bytesColumn").binaryValue(), bytesData); } - @Test(dataProvider = "allTypesDataProvider", dataProviderClass = ProtobufDataProviders.class) - public void testStructuralDataTypes(String stringData, Integer integerData, Long longData, Double doubleData, Float floatData, Boolean booleanData, String enumData, SqlTimestamp sqlTimestamp, byte[] bytesData) + @Test + public void testStructuralDataTypes() + throws Exception + { + testStructuralDataTypes( + "Trino", + 1, + 493857959588286460L, + PI, + 3.14f, + true, + "ONE", + sqlTimestampOf(3, LocalDateTime.parse("2020-12-12T15:35:45.923")), + "X'65683F'".getBytes(UTF_8)); + + testStructuralDataTypes( + range(0, 5000) + .mapToObj(Integer::toString) + .collect(joining(", ")), + Integer.MAX_VALUE, + Long.MIN_VALUE, + Double.MAX_VALUE, + Float.MIN_VALUE, + false, + "ZERO", + sqlTimestampOf(3, LocalDateTime.parse("1856-01-12T05:25:14.456")), + new byte[0]); + + testStructuralDataTypes( + range(5000, 10000) + .mapToObj(Integer::toString) + .collect(joining(", ")), + Integer.MIN_VALUE, + Long.MAX_VALUE, + Double.NaN, + Float.NEGATIVE_INFINITY, + false, + "ZERO", + sqlTimestampOf(3, LocalDateTime.parse("0001-01-01T00:00:00.923")), + "X'65683F'".getBytes(UTF_8)); + } + + private void testStructuralDataTypes(String stringData, Integer integerData, Long longData, Double doubleData, Float floatData, Boolean booleanData, String enumData, SqlTimestamp sqlTimestamp, byte[] bytesData) throws Exception { DecoderTestColumnHandle listColumn = new DecoderTestColumnHandle(0, "list", new ArrayType(createVarcharType(100)), "list", null, null, false, false, false); @@ -480,8 +564,49 @@ public void testMissingFieldInRowType() .hasMessageMatching("Unknown Field unknown_mapping"); } - @Test(dataProvider = "allTypesDataProvider", dataProviderClass = ProtobufDataProviders.class) - public void testRowFlattening(String stringData, Integer integerData, Long longData, Double doubleData, Float floatData, Boolean booleanData, String enumData, SqlTimestamp sqlTimestamp, byte[] bytesData) + @Test + public void testRowFlattening() + throws Exception + { + testRowFlattening( + "Trino", + 1, + 493857959588286460L, + PI, + 3.14f, + true, + "ONE", + sqlTimestampOf(3, LocalDateTime.parse("2020-12-12T15:35:45.923")), + "X'65683F'".getBytes(UTF_8)); + + testRowFlattening( + range(0, 5000) + .mapToObj(Integer::toString) + .collect(joining(", ")), + Integer.MAX_VALUE, + Long.MIN_VALUE, + Double.MAX_VALUE, + Float.MIN_VALUE, + false, + "ZERO", + sqlTimestampOf(3, LocalDateTime.parse("1856-01-12T05:25:14.456")), + new byte[0]); + + testRowFlattening( + range(5000, 10000) + .mapToObj(Integer::toString) + .collect(joining(", ")), + Integer.MIN_VALUE, + Long.MAX_VALUE, + Double.NaN, + Float.NEGATIVE_INFINITY, + false, + "ZERO", + sqlTimestampOf(3, LocalDateTime.parse("0001-01-01T00:00:00.923")), + "X'65683F'".getBytes(UTF_8)); + } + + private void testRowFlattening(String stringData, Integer integerData, Long longData, Double doubleData, Float floatData, Boolean booleanData, String enumData, SqlTimestamp sqlTimestamp, byte[] bytesData) throws Exception { DecoderTestColumnHandle stringColumn = new DecoderTestColumnHandle(0, "stringColumn", createVarcharType(30000), "row/string_column", null, null, false, false, false); diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/protobuf/TestProtobufEncoder.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/protobuf/TestProtobufEncoder.java index 715ae7213ff5..08be4d298ec7 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/protobuf/TestProtobufEncoder.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/protobuf/TestProtobufEncoder.java @@ -18,7 +18,6 @@ import com.google.protobuf.DynamicMessage; import com.google.protobuf.Timestamp; import io.airlift.slice.Slices; -import io.trino.decoder.protobuf.ProtobufDataProviders; import io.trino.plugin.kafka.KafkaColumnHandle; import io.trino.plugin.kafka.encoder.EncoderColumnHandle; import io.trino.plugin.kafka.encoder.RowEncoder; @@ -38,6 +37,7 @@ import io.trino.testing.TestingConnectorSession; import org.testng.annotations.Test; +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -63,18 +63,64 @@ import static io.trino.spi.type.VarbinaryType.VARBINARY; import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.spi.type.VarcharType.createVarcharType; +import static io.trino.testing.DateTimeTestingUtils.sqlTimestampOf; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.lang.Float.floatToIntBits; +import static java.lang.Math.PI; import static java.lang.Math.floorDiv; import static java.lang.Math.floorMod; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.stream.Collectors.joining; +import static java.util.stream.IntStream.range; import static org.testng.Assert.assertEquals; public class TestProtobufEncoder { private static final ProtobufRowEncoderFactory ENCODER_FACTORY = new ProtobufRowEncoderFactory(); - @Test(dataProvider = "allTypesDataProvider", dataProviderClass = ProtobufDataProviders.class) - public void testAllDataTypes(String stringData, Integer integerData, Long longData, Double doubleData, Float floatData, Boolean booleanData, String enumData, SqlTimestamp sqlTimestamp, byte[] bytesData) + @Test + public void testAllDataTypes() + throws Exception + { + testAllDataTypes( + "Trino", + 1, + 493857959588286460L, + PI, + 3.14f, + true, + "ONE", + sqlTimestampOf(3, LocalDateTime.parse("2020-12-12T15:35:45.923")), + "X'65683F'".getBytes(UTF_8)); + + testAllDataTypes( + range(0, 5000) + .mapToObj(Integer::toString) + .collect(joining(", ")), + Integer.MAX_VALUE, + Long.MIN_VALUE, + Double.MAX_VALUE, + Float.MIN_VALUE, + false, + "ZERO", + sqlTimestampOf(3, LocalDateTime.parse("1856-01-12T05:25:14.456")), + new byte[0]); + + testAllDataTypes( + range(5000, 10000) + .mapToObj(Integer::toString) + .collect(joining(", ")), + Integer.MIN_VALUE, + Long.MAX_VALUE, + Double.NaN, + Float.NEGATIVE_INFINITY, + false, + "ZERO", + sqlTimestampOf(3, LocalDateTime.parse("0001-01-01T00:00:00.923")), + "X'65683F'".getBytes(UTF_8)); + } + + private void testAllDataTypes(String stringData, Integer integerData, Long longData, Double doubleData, Float floatData, Boolean booleanData, String enumData, SqlTimestamp sqlTimestamp, byte[] bytesData) throws Exception { Descriptor descriptor = getDescriptor("all_datatypes.proto"); @@ -115,8 +161,49 @@ public void testAllDataTypes(String stringData, Integer integerData, Long longDa assertEquals(messageBuilder.build().toByteArray(), rowEncoder.toByteArray()); } - @Test(dataProvider = "allTypesDataProvider", dataProviderClass = ProtobufDataProviders.class) - public void testStructuralDataTypes(String stringData, Integer integerData, Long longData, Double doubleData, Float floatData, Boolean booleanData, String enumData, SqlTimestamp sqlTimestamp, byte[] bytesData) + @Test + public void testStructuralDataTypes() + throws Exception + { + testStructuralDataTypes( + "Trino", + 1, + 493857959588286460L, + PI, + 3.14f, + true, + "ONE", + sqlTimestampOf(3, LocalDateTime.parse("2020-12-12T15:35:45.923")), + "X'65683F'".getBytes(UTF_8)); + + testStructuralDataTypes( + range(0, 5000) + .mapToObj(Integer::toString) + .collect(joining(", ")), + Integer.MAX_VALUE, + Long.MIN_VALUE, + Double.MAX_VALUE, + Float.MIN_VALUE, + false, + "ZERO", + sqlTimestampOf(3, LocalDateTime.parse("1856-01-12T05:25:14.456")), + new byte[0]); + + testStructuralDataTypes( + range(5000, 10000) + .mapToObj(Integer::toString) + .collect(joining(", ")), + Integer.MIN_VALUE, + Long.MAX_VALUE, + Double.NaN, + Float.NEGATIVE_INFINITY, + false, + "ZERO", + sqlTimestampOf(3, LocalDateTime.parse("0001-01-01T00:00:00.923")), + "X'65683F'".getBytes(UTF_8)); + } + + private void testStructuralDataTypes(String stringData, Integer integerData, Long longData, Double doubleData, Float floatData, Boolean booleanData, String enumData, SqlTimestamp sqlTimestamp, byte[] bytesData) throws Exception { Descriptor descriptor = getDescriptor("structural_datatypes.proto"); @@ -194,8 +281,49 @@ public void testStructuralDataTypes(String stringData, Integer integerData, Long assertEquals(messageBuilder.build().toByteArray(), rowEncoder.toByteArray()); } - @Test(dataProvider = "allTypesDataProvider", dataProviderClass = ProtobufDataProviders.class) - public void testNestedStructuralDataTypes(String stringData, Integer integerData, Long longData, Double doubleData, Float floatData, Boolean booleanData, String enumData, SqlTimestamp sqlTimestamp, byte[] bytesData) + @Test + public void testNestedStructuralDataTypes() + throws Exception + { + testNestedStructuralDataTypes( + "Trino", + 1, + 493857959588286460L, + PI, + 3.14f, + true, + "ONE", + sqlTimestampOf(3, LocalDateTime.parse("2020-12-12T15:35:45.923")), + "X'65683F'".getBytes(UTF_8)); + + testNestedStructuralDataTypes( + range(0, 5000) + .mapToObj(Integer::toString) + .collect(joining(", ")), + Integer.MAX_VALUE, + Long.MIN_VALUE, + Double.MAX_VALUE, + Float.MIN_VALUE, + false, + "ZERO", + sqlTimestampOf(3, LocalDateTime.parse("1856-01-12T05:25:14.456")), + new byte[0]); + + testNestedStructuralDataTypes( + range(5000, 10000) + .mapToObj(Integer::toString) + .collect(joining(", ")), + Integer.MIN_VALUE, + Long.MAX_VALUE, + Double.NaN, + Float.NEGATIVE_INFINITY, + false, + "ZERO", + sqlTimestampOf(3, LocalDateTime.parse("0001-01-01T00:00:00.923")), + "X'65683F'".getBytes(UTF_8)); + } + + private void testNestedStructuralDataTypes(String stringData, Integer integerData, Long longData, Double doubleData, Float floatData, Boolean booleanData, String enumData, SqlTimestamp sqlTimestamp, byte[] bytesData) throws Exception { Descriptor descriptor = getDescriptor("structural_datatypes.proto"); @@ -294,8 +422,49 @@ public void testNestedStructuralDataTypes(String stringData, Integer integerData assertEquals(messageBuilder.build().toByteArray(), rowEncoder.toByteArray()); } - @Test(dataProvider = "allTypesDataProvider", dataProviderClass = ProtobufDataProviders.class) - public void testRowFlattening(String stringData, Integer integerData, Long longData, Double doubleData, Float floatData, Boolean booleanData, String enumData, SqlTimestamp sqlTimestamp, byte[] bytesData) + @Test + public void testRowFlattening() + throws Exception + { + testRowFlattening( + "Trino", + 1, + 493857959588286460L, + PI, + 3.14f, + true, + "ONE", + sqlTimestampOf(3, LocalDateTime.parse("2020-12-12T15:35:45.923")), + "X'65683F'".getBytes(UTF_8)); + + testRowFlattening( + range(0, 5000) + .mapToObj(Integer::toString) + .collect(joining(", ")), + Integer.MAX_VALUE, + Long.MIN_VALUE, + Double.MAX_VALUE, + Float.MIN_VALUE, + false, + "ZERO", + sqlTimestampOf(3, LocalDateTime.parse("1856-01-12T05:25:14.456")), + new byte[0]); + + testRowFlattening( + range(5000, 10000) + .mapToObj(Integer::toString) + .collect(joining(", ")), + Integer.MIN_VALUE, + Long.MAX_VALUE, + Double.NaN, + Float.NEGATIVE_INFINITY, + false, + "ZERO", + sqlTimestampOf(3, LocalDateTime.parse("0001-01-01T00:00:00.923")), + "X'65683F'".getBytes(UTF_8)); + } + + private void testRowFlattening(String stringData, Integer integerData, Long longData, Double doubleData, Float floatData, Boolean booleanData, String enumData, SqlTimestamp sqlTimestamp, byte[] bytesData) throws Exception { Descriptor descriptor = getDescriptor("structural_datatypes.proto"); diff --git a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftTypeMapping.java b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftTypeMapping.java index 64d8d04616c2..ed6c97fed0b9 100644 --- a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftTypeMapping.java +++ b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftTypeMapping.java @@ -28,7 +28,6 @@ import io.trino.testing.sql.TestTable; import io.trino.testing.sql.TrinoSqlExecutor; import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.sql.SQLException; @@ -373,8 +372,17 @@ public void testUnsupportedTrinoDataTypes() "Unsupported column type: json"); } - @Test(dataProvider = "datetime_test_parameters") - public void testDate(ZoneId sessionZone) + @Test + public void testDate() + { + testDate(UTC); + testDate(jvmZone); + testDate(vilnius); + testDate(kathmandu); + testDate(testZone); + } + + private void testDate(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(getTimeZoneKey(sessionZone.getId())) @@ -403,8 +411,17 @@ public void testDate(ZoneId sessionZone) .execute(getQueryRunner(), session, redshiftCreateAndInsert("test_date")); } - @Test(dataProvider = "datetime_test_parameters") - public void testTime(ZoneId sessionZone) + @Test + public void testTime() + { + testTime(UTC); + testTime(jvmZone); + testTime(vilnius); + testTime(kathmandu); + testTime(testZone); + } + + private void testTime(ZoneId sessionZone) { // Redshift gets bizarre errors if you try to insert after // specifying precision for a time column. @@ -437,7 +454,17 @@ private static SqlDataTypeTest timeTypeTests(String inputType) .addRoundTrip(inputType, "TIME '23:59:59.999999'", createTimeType(6), "TIME '23:59:59.999999'"); } - @Test(dataProvider = "datetime_test_parameters") + @Test + public void testTimestamp() + { + testTimestamp(UTC); + testTimestamp(jvmZone); + testTimestamp(vilnius); + testTimestamp(kathmandu); + testTimestamp(testZone); + } + + @Test public void testTimestamp(ZoneId sessionZone) { Session session = Session.builder(getSession()) @@ -476,8 +503,17 @@ private static SqlDataTypeTest timestampTypeTests(String inputType) .addRoundTrip(inputType, "TIMESTAMP '1970-01-01 00:00:00.999999'", createTimestampType(6), "TIMESTAMP '1970-01-01 00:00:00.999999'"); } - @Test(dataProvider = "datetime_test_parameters") - public void testTimestampWithTimeZone(ZoneId sessionZone) + @Test + public void testTimestampWithTimeZone() + { + testTimestampWithTimeZone(UTC); + testTimestampWithTimeZone(jvmZone); + testTimestampWithTimeZone(vilnius); + testTimestampWithTimeZone(kathmandu); + testTimestampWithTimeZone(testZone); + } + + private void testTimestampWithTimeZone(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(getTimeZoneKey(sessionZone.getId())) @@ -630,18 +666,6 @@ public void testTimestampWithTimeZoneOverflow() } } - @DataProvider(name = "datetime_test_parameters") - public Object[][] dataProviderForDatetimeTests() - { - return new Object[][] { - {UTC}, - {jvmZone}, - {vilnius}, - {kathmandu}, - {testZone}, - }; - } - @Test public void testUnsupportedDateTimeTypes() { From dc8392f65f191f3b598205289d9fb26d31fec91c Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Wed, 8 Nov 2023 12:09:56 -0800 Subject: [PATCH 196/587] Migrate tests to JUnit --- core/trino-server-rpm/pom.xml | 20 ++++-- .../java/io/trino/server/rpm/ServerIT.java | 20 +++--- lib/trino-ignite-patched/pom.xml | 10 ++- .../java/org/apache/ignite/TestDummy.java | 2 +- lib/trino-matching/pom.xml | 40 +++++++++++ .../java/io/trino/matching/TestMatcher.java | 2 +- lib/trino-memory-context/pom.xml | 40 +++++++++++ .../memory/context/TestMemoryContexts.java | 31 +++++--- lib/trino-orc/pom.xml | 31 ++++++++ .../io/trino/orc/AbstractTestOrcReader.java | 7 +- .../trino/orc/BenchmarkOrcDecimalReader.java | 2 +- .../trino/orc/TestCachingOrcDataSource.java | 19 ++--- .../io/trino/orc/TestDictionaryBuilder.java | 2 +- .../TestDictionaryCompressionOptimizer.java | 2 +- .../io/trino/orc/TestOrcBloomFilters.java | 2 +- .../io/trino/orc/TestOrcDataSourceUtils.java | 2 +- .../test/java/io/trino/orc/TestOrcLz4.java | 2 +- .../io/trino/orc/TestOrcOutputBuffer.java | 2 +- .../test/java/io/trino/orc/TestOrcReader.java | 2 +- .../trino/orc/TestOrcReaderMemoryUsage.java | 2 +- .../io/trino/orc/TestOrcReaderPositions.java | 2 +- .../trino/orc/TestOrcWithoutRowGroupInfo.java | 2 +- .../test/java/io/trino/orc/TestOrcWriter.java | 2 +- .../io/trino/orc/TestReadBloomFilter.java | 2 +- .../orc/TestSliceDictionaryColumnReader.java | 2 +- .../orc/TestSliceDictionaryColumnWriter.java | 2 +- .../io/trino/orc/TestStructColumnReader.java | 42 ++++++----- .../io/trino/orc/TestTimestampTzMicros.java | 2 +- .../orc/TestTupleDomainOrcPredicate.java | 2 +- .../orc/metadata/TestOrcMetadataReader.java | 2 +- .../TestStringStatisticsBuilder.java | 2 +- .../orc/stream/TestBooleanOutputStream.java | 2 +- .../trino/orc/stream/TestBooleanStream.java | 2 +- .../trino/orc/stream/TestByteArrayStream.java | 2 +- .../io/trino/orc/stream/TestByteStream.java | 2 +- .../trino/orc/stream/TestDecimalStream.java | 2 +- .../io/trino/orc/stream/TestDoubleStream.java | 2 +- .../io/trino/orc/stream/TestFloatStream.java | 2 +- .../trino/orc/stream/TestLongBitPacker.java | 2 +- .../orc/stream/TestLongDecimalStream.java | 2 +- .../io/trino/orc/stream/TestLongDecode.java | 2 +- .../io/trino/orc/stream/TestLongStreamV1.java | 2 +- .../io/trino/orc/stream/TestLongStreamV2.java | 2 +- .../orc/stream/TestShortDecimalStream.java | 2 +- lib/trino-parquet/pom.xml | 6 ++ .../parquet/TestParquetTimestampUtils.java | 2 +- .../BenchmarkTupleDomainParquetPredicate.java | 2 +- .../parquet/predicate/TestPredicateUtils.java | 2 +- .../reader/TestBinaryColumnBenchmark.java | 2 +- .../reader/TestColumnIndexBuilder.java | 2 +- .../parquet/reader/TestColumnIndexFilter.java | 2 +- .../reader/TestColumnReaderBenchmark.java | 2 +- .../reader/TestParquetReaderMemoryUsage.java | 2 +- .../reader/TestParquetReaderUtils.java | 2 +- .../TestParquetReaderUtilsBenchmarks.java | 2 +- ...TestShortDecimalColumnReaderBenchmark.java | 2 +- .../trino/parquet/reader/TestTimeMillis.java | 2 +- .../TestRleBitPackingDecoderBenchmark.java | 2 +- .../parquet/reader/flat/TestBinaryBuffer.java | 2 +- .../reader/flat/TestBitPackingUtils.java | 2 +- .../reader/flat/TestFlatColumnReader.java | 2 +- .../flat/TestNullsDecoderBenchmark.java | 2 +- .../reader/flat/TestRowRangesIterator.java | 2 +- .../writer/TestParquetSchemaConverter.java | 2 +- .../parquet/writer/TestParquetWriter.java | 2 +- .../writer/TestTrinoValuesWriterFactory.java | 2 +- .../dictionary/TestDictionaryWriter.java | 2 +- lib/trino-phoenix5-patched/pom.xml | 10 ++- .../java/org/apache/phoenix/TestDummy.java | 2 +- lib/trino-plugin-toolkit/pom.xml | 38 ++++++++++ .../TestKerberosConfiguration.java | 2 +- .../TestClassLoaderSafeWrappers.java | 2 +- .../trino/plugin/base/io/TestByteBuffers.java | 2 +- .../plugin/base/ldap/TestLdapConfig.java | 2 +- .../base/logging/TestFormatInterpolator.java | 2 +- .../TestForwardingIdentifierMapping.java | 2 +- .../mapping/TestIdentifierMappingRules.java | 2 +- .../base/mapping/TestMappingConfig.java | 2 +- .../plugin/base/metrics/TestMetrics.java | 14 ++-- .../projection/TestApplyProjectionUtil.java | 2 +- ...seFileBasedConnectorAccessControlTest.java | 2 +- .../BaseFileBasedSystemAccessControlTest.java | 2 +- .../security/TestAllowAllAccessControl.java | 2 +- .../TestAllowAllSystemAccessControl.java | 2 +- .../TestFileBasedAccessControlConfig.java | 2 +- .../TestForwardingConnectorAccessControl.java | 2 +- .../TestForwardingSystemAccessControl.java | 2 +- .../TestHttpFileBasedAccessControl.java | 19 +++-- .../TestHttpFileBasedSystemAccessControl.java | 19 +++-- .../trino/plugin/base/util/TestClosables.java | 2 +- .../trino/plugin/base/util/TestJsonUtils.java | 2 +- .../util/TestLoggingInvocationHandler.java | 2 +- lib/trino-record-decoder/pom.xml | 36 ++++++++++ .../trino/decoder/avro/TestAvroDecoder.java | 2 +- .../io/trino/decoder/csv/TestCsvDecoder.java | 2 +- .../TestCustomDateTimeJsonFieldDecoder.java | 2 +- .../json/TestDefaultJsonFieldDecoder.java | 2 +- .../json/TestISO8601JsonFieldDecoder.java | 2 +- .../trino/decoder/json/TestJsonDecoder.java | 2 +- ...illisecondsSinceEpochJsonFieldDecoder.java | 2 +- .../json/TestRFC2822JsonFieldDecoder.java | 2 +- ...TestSecondsSinceEpochJsonFieldDecoder.java | 2 +- .../decoder/protobuf/TestProtobufDecoder.java | 2 +- .../io/trino/decoder/raw/TestRawDecoder.java | 2 +- .../iceberg/BaseIcebergSystemTables.java | 13 ++-- .../iceberg/BaseSharedMetastoreTest.java | 2 +- .../iceberg/TestSharedHiveMetastore.java | 10 ++- .../catalog/glue/TestSharedGlueMetastore.java | 10 ++- .../kafka/protobuf/TestProtobufEncoder.java | 2 +- .../plugin/mariadb/TestMariaDbClient.java | 2 +- .../plugin/mariadb/TestMariaDbJdbcConfig.java | 2 +- .../plugin/mariadb/TestMariaDbPlugin.java | 2 +- plugin/trino-memory/pom.xml | 12 ++++ .../plugin/memory/TestMemoryMetadata.java | 2 +- .../plugin/memory/TestMemoryPagesStore.java | 20 ++++-- .../memory/TestMemoryTableStatistics.java | 2 +- .../TestMongoCaseInsensitiveMapping.java | 26 ++++--- .../plugin/mongodb/TestMongoClientConfig.java | 2 +- .../trino/plugin/mongodb/TestMongoPlugin.java | 2 +- .../TestMongoProjectionPushdownPlans.java | 16 +++-- .../plugin/mongodb/TestMongoSession.java | 2 +- .../trino/plugin/mongodb/TestMongoSplit.java | 2 +- .../plugin/mongodb/TestMongoSslConfig.java | 2 +- .../plugin/mongodb/TestMongoTableHandle.java | 8 +-- .../trino/plugin/mysql/TestMySqlClient.java | 2 +- .../trino/plugin/mysql/TestMySqlConfig.java | 2 +- .../plugin/mysql/TestMySqlJdbcConfig.java | 2 +- .../mysql/TestMySqlLegacyConnectorTest.java | 2 +- .../trino/plugin/mysql/TestMySqlPlugin.java | 2 +- .../trino/plugin/oracle/TestOracleConfig.java | 2 +- .../trino/plugin/oracle/TestOraclePlugin.java | 2 +- plugin/trino-password-authenticators/pom.xml | 40 +++++++++++ .../password/file/TestEncryptionUtil.java | 2 +- .../plugin/password/file/TestFileConfig.java | 2 +- .../password/file/TestFileGroupConfig.java | 2 +- .../password/file/TestFileGroupProvider.java | 2 +- .../password/file/TestPasswordStore.java | 2 +- .../password/ldap/TestLdapAuthenticator.java | 26 +++---- .../ldap/TestLdapAuthenticatorConfig.java | 2 +- .../TestLdapAuthenticatorWithTimeouts.java | 26 +++---- .../TestSalesforceBasicAuthenticator.java | 25 +++---- .../salesforce/TestSalesforceConfig.java | 2 +- plugin/trino-phoenix5/pom.xml | 12 ++++ .../plugin/phoenix5/TestPhoenixConfig.java | 2 +- .../plugin/phoenix5/TestPhoenixPlugin.java | 2 +- .../plugin/phoenix5/TestPhoenixSplit.java | 2 +- .../trino/plugin/pinot/TestBrokerQueries.java | 10 ++- .../trino/plugin/pinot/TestDynamicTable.java | 2 +- .../io/trino/plugin/pinot/TestInstance.java | 2 +- .../trino/plugin/pinot/TestPinotClient.java | 2 +- .../plugin/pinot/TestPinotColumnHandle.java | 2 +- .../trino/plugin/pinot/TestPinotConfig.java | 2 +- .../TestPinotGrpcServerQueryClientConfig.java | 2 +- ...stPinotGrpcServerQueryClientTlsConfig.java | 2 +- ...estPinotLegacyServerQueryClientConfig.java | 2 +- .../trino/plugin/pinot/TestPinotMetadata.java | 2 +- .../pinot/TestPinotSessionProperties.java | 10 ++- .../plugin/pinot/TestPinotSplitManager.java | 14 ++-- .../plugin/pinot/TestPinotTableHandle.java | 2 +- .../TestPinotAuthenticationTypeConfig.java | 2 +- .../TestPinotEmptyAuthenticationProvider.java | 2 +- ...stPinotPasswordAuthenticationProvider.java | 2 +- ...notPasswordBrokerAuthenticationConfig.java | 2 +- ...asswordControllerAuthenticationConfig.java | 2 +- .../TestJoinReorderingWithJoinPushdown.java | 2 +- .../postgresql/TestPostgreSqlConfig.java | 2 +- .../postgresql/TestPostgreSqlPlugin.java | 2 +- .../TestRemoteQueryCommentLogging.java | 6 +- ...eQueryCommentLoggingDisabledByDefault.java | 6 +- .../TestTestingPostgreSqlServer.java | 25 +++---- plugin/trino-prometheus/pom.xml | 18 +++++ .../prometheus/TestPrometheusBasicAuth.java | 2 +- ...PrometheusCaseInsensitiveNameMatching.java | 25 +++---- .../TestPrometheusColumnHandle.java | 2 +- .../TestPrometheusConnectorConfig.java | 2 +- .../prometheus/TestPrometheusIntegration.java | 2 +- ...estPrometheusQueryMatrixResponseParse.java | 17 +++-- ...estPrometheusQueryScalarResponseParse.java | 46 ++++++------ ...estPrometheusQueryVectorResponseParse.java | 46 ++++++------ .../prometheus/TestPrometheusRecordSet.java | 29 ++++---- .../TestPrometheusRecordSetProvider.java | 32 ++++----- .../prometheus/TestPrometheusSplit.java | 27 ++++--- .../prometheus/TestPrometheusTable.java | 2 +- .../prometheus/TestPrometheusTableHandle.java | 2 +- .../TestPrometheusTimestampDeserializer.java | 2 +- plugin/trino-raptor-legacy/pom.xml | 12 ++++ .../legacy/TestRaptorBucketFunction.java | 2 +- .../raptor/legacy/TestRaptorConnector.java | 17 +++-- .../raptor/legacy/TestRaptorPlugin.java | 2 +- .../legacy/backup/TestBackupConfig.java | 2 +- .../legacy/backup/TestBackupManager.java | 17 +++-- .../legacy/backup/TestFileBackupConfig.java | 2 +- .../legacy/backup/TestHttpBackupConfig.java | 2 +- .../metadata/TestAssignmentLimiter.java | 2 +- .../legacy/metadata/TestDatabaseConfig.java | 2 +- .../metadata/TestDatabaseShardManager.java | 17 +++-- .../legacy/metadata/TestH2DatabaseConfig.java | 2 +- .../legacy/metadata/TestMetadataConfig.java | 2 +- .../legacy/metadata/TestMetadataDao.java | 17 +++-- .../legacy/metadata/TestRaptorMetadata.java | 70 ++++++++++--------- .../metadata/TestRaptorSplitManager.java | 48 ++++++++----- .../legacy/metadata/TestShardCleaner.java | 17 +++-- .../metadata/TestShardCleanerConfig.java | 2 +- .../raptor/legacy/metadata/TestShardDao.java | 17 +++-- .../legacy/metadata/TestShardPredicate.java | 2 +- .../security/TestRaptorFileBasedSecurity.java | 30 +++++--- .../security/TestRaptorReadOnlySecurity.java | 30 +++----- .../security/TestRaptorSecurityConfig.java | 2 +- .../legacy/storage/TestBucketBalancer.java | 17 +++-- .../storage/TestBucketBalancerConfig.java | 2 +- .../storage/TestFileStorageService.java | 17 +++-- .../storage/TestMissingShardComparator.java | 2 +- .../legacy/storage/TestOrcFileRewriter.java | 19 ++--- .../storage/TestRaptorStorageManager.java | 17 +++-- .../legacy/storage/TestShardEjector.java | 19 +++-- .../legacy/storage/TestShardRecovery.java | 26 ++++--- .../legacy/storage/TestShardWriter.java | 18 +++-- .../storage/TestStorageManagerConfig.java | 2 +- .../TestCompactionSetCreator.java | 2 +- .../organization/TestShardCompactor.java | 17 +++-- .../TestShardOrganizationManager.java | 17 +++-- .../organization/TestShardOrganizer.java | 6 +- .../organization/TestShardOrganizerUtil.java | 17 +++-- .../storage/organization/TestShardRange.java | 2 +- .../organization/TestTemporalFunction.java | 2 +- .../storage/organization/TestTuple.java | 19 +++-- .../TestShardMetadataRecordCursor.java | 17 +++-- .../util/TestPrioritizedFifoExecutor.java | 17 +++-- .../plugin/redis/BaseRedisConnectorTest.java | 2 +- .../plugin/redshift/TestRedshiftConfig.java | 2 +- .../plugin/redshift/TestRedshiftPlugin.java | 2 +- .../TestRedshiftTableStatisticsReader.java | 18 ++--- .../redshift/TestRedshiftTypeMapping.java | 6 +- plugin/trino-resource-group-managers/pom.xml | 40 +++++++++++ .../TestFileResourceGroupConfig.java | 2 +- ...FileResourceGroupConfigurationManager.java | 2 +- .../TestResourceGroupIdTemplate.java | 35 ++++++---- .../resourcegroups/TestStaticSelector.java | 2 +- ...seTestDbResourceGroupsFlywayMigration.java | 38 +++++----- .../db/TestDbResourceGroupConfig.java | 2 +- ...stDbResourceGroupConfigurationManager.java | 6 +- ...tDbResourceGroupsMysqlFlywayMigration.java | 5 +- ...DbResourceGroupsOracleFlywayMigration.java | 10 --- ...sourceGroupsPostgresqlFlywayMigration.java | 10 --- .../db/TestDbSourceExactMatchSelector.java | 8 +-- .../db/TestResourceGroupsDao.java | 2 +- .../trino-session-property-managers/pom.xml | 12 ++++ .../TestFileSessionPropertyManagerConfig.java | 2 +- .../singlestore/TestSingleStoreConfig.java | 2 +- .../TestSingleStoreJdbcConfig.java | 2 +- .../singlestore/TestSingleStorePlugin.java | 2 +- .../plugin/sqlserver/TestSqlServerClient.java | 2 +- .../plugin/sqlserver/TestSqlServerConfig.java | 2 +- .../plugin/sqlserver/TestSqlServerPlugin.java | 2 +- plugin/trino-thrift-api/pom.xml | 36 ++++++++++ .../thrift/api/TestNameValidationUtils.java | 2 +- .../plugin/thrift/api/TestReadWrite.java | 10 +-- .../plugin/thrift/api/TestTrinoThriftId.java | 2 +- .../api/datatypes/TestTrinoThriftBigint.java | 2 +- .../TestTrinoThriftAllOrNoneValueSet.java | 2 +- .../TestTrinoThriftEquatableValueSet.java | 2 +- .../TestTrinoThriftRangeValueSet.java | 2 +- plugin/trino-thrift-testing-server/pom.xml | 16 +++++ .../thrift/server/TestListBasedRecordSet.java | 2 +- .../pom.xml | 40 +++++++++++ .../java/io/trino/TestJdbcCompatibility.java | 57 ++++++++------- .../io/trino/testing/TestH2QueryRunner.java | 43 ++++-------- .../trino/testing/TestTestingTrinoClient.java | 19 ++--- .../trino/testing/datatype/TestDataType.java | 2 +- 269 files changed, 1419 insertions(+), 846 deletions(-) diff --git a/core/trino-server-rpm/pom.xml b/core/trino-server-rpm/pom.xml index a62f70bcc8b7..76a59fe85be2 100644 --- a/core/trino-server-rpm/pom.xml +++ b/core/trino-server-rpm/pom.xml @@ -25,6 +25,12 @@ test + + io.airlift + junit-extensions + test + + io.airlift units @@ -50,14 +56,20 @@ - org.testcontainers - testcontainers + org.assertj + assertj-core test - org.testng - testng + org.junit.jupiter + junit-jupiter-api + test + + + + org.testcontainers + testcontainers test diff --git a/core/trino-server-rpm/src/test/java/io/trino/server/rpm/ServerIT.java b/core/trino-server-rpm/src/test/java/io/trino/server/rpm/ServerIT.java index 4caa9da4951a..37b6dbb0b1f2 100644 --- a/core/trino-server-rpm/src/test/java/io/trino/server/rpm/ServerIT.java +++ b/core/trino-server-rpm/src/test/java/io/trino/server/rpm/ServerIT.java @@ -15,10 +15,11 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; import org.testcontainers.containers.BindMode; import org.testcontainers.containers.Container.ExecResult; import org.testcontainers.containers.GenericContainer; -import org.testng.annotations.Test; import java.io.File; import java.sql.Connection; @@ -35,10 +36,11 @@ import static java.util.Arrays.asList; import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testcontainers.containers.wait.strategy.Wait.forLogMessage; -import static org.testng.Assert.assertEquals; -@Test(singleThreaded = true) +@Execution(SAME_THREAD) public class ServerIT { private static final String BASE_IMAGE_PREFIX = "eclipse-temurin:"; @@ -92,12 +94,12 @@ private void testInstall(String javaVersion) .waitingFor(forLogMessage(".*SERVER STARTED.*", 1).withStartupTimeout(Duration.ofMinutes(5))) .start(); QueryRunner queryRunner = new QueryRunner(container.getHost(), container.getMappedPort(8080)); - assertEquals(queryRunner.execute("SHOW CATALOGS"), ImmutableSet.of(asList("system"), asList("hive"), asList("jmx"))); - assertEquals(queryRunner.execute("SELECT node_id FROM system.runtime.nodes"), ImmutableSet.of(asList("test-node-id-injected-via-env"))); + assertThat(queryRunner.execute("SHOW CATALOGS")).isEqualTo(ImmutableSet.of(asList("system"), asList("hive"), asList("jmx"))); + assertThat(queryRunner.execute("SELECT node_id FROM system.runtime.nodes")).isEqualTo(ImmutableSet.of(asList("test-node-id-injected-via-env"))); // TODO remove usage of assertEventually once https://github.com/trinodb/trino/issues/2214 is fixed assertEventually( new io.airlift.units.Duration(1, MINUTES), - () -> assertEquals(queryRunner.execute("SELECT specversion FROM jmx.current.\"java.lang:type=runtime\""), ImmutableSet.of(asList(javaVersion)))); + () -> assertThat(queryRunner.execute("SELECT specversion FROM jmx.current.\"java.lang:type=runtime\"")).isEqualTo(ImmutableSet.of(asList(javaVersion)))); } } @@ -132,7 +134,7 @@ private void testUninstall(String javaVersion) container.execInContainer("sh", "-xeuc", uninstallTrino); ExecResult actual = container.execInContainer("rpm", "-q", "trino-server-rpm"); - assertEquals(actual.getStdout(), "package trino-server-rpm is not installed\n"); + assertThat(actual.getStdout()).isEqualTo("package trino-server-rpm is not installed\n"); assertPathDeleted(container, "/var/lib/trino"); assertPathDeleted(container, "/usr/lib/trino"); @@ -148,8 +150,8 @@ private static void assertPathDeleted(GenericContainer container, String path "sh", "-xeuc", format("test -d %s && echo -n 'path exists' || echo -n 'path deleted'", path)); - assertEquals(actualResult.getStdout(), "path deleted"); - assertEquals(actualResult.getExitCode(), 0); + assertThat(actualResult.getStdout()).isEqualTo("path deleted"); + assertThat(actualResult.getExitCode()).isEqualTo(0); } private static class QueryRunner diff --git a/lib/trino-ignite-patched/pom.xml b/lib/trino-ignite-patched/pom.xml index b30e46136b3d..963b281d63da 100644 --- a/lib/trino-ignite-patched/pom.xml +++ b/lib/trino-ignite-patched/pom.xml @@ -35,8 +35,14 @@ - org.testng - testng + io.airlift + junit-extensions + test + + + + org.junit.jupiter + junit-jupiter-api test diff --git a/lib/trino-ignite-patched/src/test/java/org/apache/ignite/TestDummy.java b/lib/trino-ignite-patched/src/test/java/org/apache/ignite/TestDummy.java index eabaf8f7d7a4..9d34e5d0b188 100644 --- a/lib/trino-ignite-patched/src/test/java/org/apache/ignite/TestDummy.java +++ b/lib/trino-ignite-patched/src/test/java/org/apache/ignite/TestDummy.java @@ -13,7 +13,7 @@ */ package org.apache.ignite; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; public class TestDummy { diff --git a/lib/trino-matching/pom.xml b/lib/trino-matching/pom.xml index a76164583863..b72955e2a4db 100644 --- a/lib/trino-matching/pom.xml +++ b/lib/trino-matching/pom.xml @@ -22,10 +22,50 @@ guava + + io.airlift + junit-extensions + test + + + + org.junit.jupiter + junit-jupiter-api + test + + + + org.junit.jupiter + junit-jupiter-engine + test + + org.testng testng test + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + org.apache.maven.surefire + surefire-junit-platform + ${dep.plugin.surefire.version} + + + org.apache.maven.surefire + surefire-testng + ${dep.plugin.surefire.version} + + + + + diff --git a/lib/trino-matching/src/test/java/io/trino/matching/TestMatcher.java b/lib/trino-matching/src/test/java/io/trino/matching/TestMatcher.java index 9033c249a160..09ec670cee4c 100644 --- a/lib/trino-matching/src/test/java/io/trino/matching/TestMatcher.java +++ b/lib/trino-matching/src/test/java/io/trino/matching/TestMatcher.java @@ -18,7 +18,7 @@ import io.trino.matching.example.rel.ProjectNode; import io.trino.matching.example.rel.RelNode; import io.trino.matching.example.rel.ScanNode; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.NoSuchElementException; diff --git a/lib/trino-memory-context/pom.xml b/lib/trino-memory-context/pom.xml index 61edf3225777..96dd54a2f224 100644 --- a/lib/trino-memory-context/pom.xml +++ b/lib/trino-memory-context/pom.xml @@ -28,6 +28,12 @@ guava + + io.airlift + junit-extensions + test + + io.airlift testing @@ -46,10 +52,44 @@ test + + org.junit.jupiter + junit-jupiter-api + test + + + + org.junit.jupiter + junit-jupiter-engine + test + + org.testng testng test + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + org.apache.maven.surefire + surefire-junit-platform + ${dep.plugin.surefire.version} + + + org.apache.maven.surefire + surefire-testng + ${dep.plugin.surefire.version} + + + + + diff --git a/lib/trino-memory-context/src/test/java/io/trino/memory/context/TestMemoryContexts.java b/lib/trino-memory-context/src/test/java/io/trino/memory/context/TestMemoryContexts.java index df54ce687e1e..df080eecbe5e 100644 --- a/lib/trino-memory-context/src/test/java/io/trino/memory/context/TestMemoryContexts.java +++ b/lib/trino-memory-context/src/test/java/io/trino/memory/context/TestMemoryContexts.java @@ -16,12 +16,13 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import io.airlift.units.DataSize; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static com.google.common.util.concurrent.Futures.immediateVoidFuture; import static io.airlift.units.DataSize.Unit.MEGABYTE; import static io.trino.memory.context.AggregatedMemoryContext.newRootAggregatedMemoryContext; import static io.trino.memory.context.AggregatedMemoryContext.newSimpleAggregatedMemoryContext; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotEquals; @@ -146,22 +147,30 @@ public void testGuaranteedMemoryDoesntBlock() assertEquals(reservationHandler.getReservation(), maxMemory); } - @Test(expectedExceptions = IllegalStateException.class, expectedExceptionsMessageRegExp = "SimpleLocalMemoryContext is already closed") + @Test public void testClosedLocalMemoryContext() { - AggregatedMemoryContext aggregateContext = newSimpleAggregatedMemoryContext(); - LocalMemoryContext localContext = aggregateContext.newLocalMemoryContext("test"); - localContext.close(); - localContext.setBytes(100); + assertThatThrownBy(() -> { + AggregatedMemoryContext aggregateContext = newSimpleAggregatedMemoryContext(); + LocalMemoryContext localContext = aggregateContext.newLocalMemoryContext("test"); + localContext.close(); + localContext.setBytes(100); + }) + .isInstanceOf(IllegalStateException.class) + .hasMessage("SimpleLocalMemoryContext is already closed"); } - @Test(expectedExceptions = IllegalStateException.class, expectedExceptionsMessageRegExp = "SimpleAggregatedMemoryContext is already closed") + @Test public void testClosedAggregateMemoryContext() { - AggregatedMemoryContext aggregateContext = newSimpleAggregatedMemoryContext(); - LocalMemoryContext localContext = aggregateContext.newLocalMemoryContext("test"); - aggregateContext.close(); - localContext.setBytes(100); + assertThatThrownBy(() -> { + AggregatedMemoryContext aggregateContext = newSimpleAggregatedMemoryContext(); + LocalMemoryContext localContext = aggregateContext.newLocalMemoryContext("test"); + aggregateContext.close(); + localContext.setBytes(100); + }) + .isInstanceOf(IllegalStateException.class) + .hasMessage("SimpleAggregatedMemoryContext is already closed"); } private static class TestMemoryReservationHandler diff --git a/lib/trino-orc/pom.xml b/lib/trino-orc/pom.xml index 291eba2adb92..6208e2e87e84 100644 --- a/lib/trino-orc/pom.xml +++ b/lib/trino-orc/pom.xml @@ -120,6 +120,12 @@ runtime + + io.airlift + junit-extensions + test + + io.airlift testing @@ -156,6 +162,18 @@ test + + org.junit.jupiter + junit-jupiter-api + test + + + + org.junit.jupiter + junit-jupiter-engine + test + + org.openjdk.jmh jmh-core @@ -187,6 +205,19 @@ **/TestFullOrcReader.java + + + + org.apache.maven.surefire + surefire-junit-platform + ${dep.plugin.surefire.version} + + + org.apache.maven.surefire + surefire-testng + ${dep.plugin.surefire.version} + + diff --git a/lib/trino-orc/src/test/java/io/trino/orc/AbstractTestOrcReader.java b/lib/trino-orc/src/test/java/io/trino/orc/AbstractTestOrcReader.java index 1019fdabff3e..54202aa652aa 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/AbstractTestOrcReader.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/AbstractTestOrcReader.java @@ -28,8 +28,7 @@ import io.trino.spi.type.SqlTimestampWithTimeZone; import io.trino.spi.type.SqlVarbinary; import org.joda.time.DateTimeZone; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.math.BigInteger; import java.util.ArrayList; @@ -90,11 +89,7 @@ public abstract class AbstractTestOrcReader public AbstractTestOrcReader(OrcTester tester) { this.tester = tester; - } - @BeforeClass - public void setUp() - { assertEquals(DateTimeZone.getDefault(), HIVE_STORAGE_TIME_ZONE); } diff --git a/lib/trino-orc/src/test/java/io/trino/orc/BenchmarkOrcDecimalReader.java b/lib/trino-orc/src/test/java/io/trino/orc/BenchmarkOrcDecimalReader.java index b41801a04521..3d102e218a01 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/BenchmarkOrcDecimalReader.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/BenchmarkOrcDecimalReader.java @@ -19,6 +19,7 @@ import io.trino.spi.type.DecimalType; import io.trino.spi.type.SqlDecimal; import org.joda.time.DateTimeZone; +import org.junit.jupiter.api.Test; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; @@ -30,7 +31,6 @@ import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.TearDown; import org.openjdk.jmh.annotations.Warmup; -import org.testng.annotations.Test; import java.io.File; import java.io.IOException; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestCachingOrcDataSource.java b/lib/trino-orc/src/test/java/io/trino/orc/TestCachingOrcDataSource.java index 601fb51caf6e..246b49dcd841 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestCachingOrcDataSource.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestCachingOrcDataSource.java @@ -22,9 +22,10 @@ import io.trino.orc.stream.OrcDataReader; import io.trino.spi.Page; import io.trino.spi.block.Block; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.IOException; import java.util.List; @@ -46,18 +47,21 @@ import static io.trino.orc.metadata.CompressionKind.ZLIB; import static io.trino.spi.type.VarcharType.VARCHAR; import static java.lang.String.format; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.fail; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestCachingOrcDataSource { private static final int POSITION_COUNT = 50000; - private TempFile tempFile; + private final TempFile tempFile; - @BeforeClass - public void setUp() + public TestCachingOrcDataSource() throws Exception { tempFile = new TempFile(); @@ -72,12 +76,11 @@ public void setUp() .limit(POSITION_COUNT).iterator()); } - @AfterClass(alwaysRun = true) + @AfterAll public void tearDown() throws Exception { tempFile.close(); - tempFile = null; } @Test diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestDictionaryBuilder.java b/lib/trino-orc/src/test/java/io/trino/orc/TestDictionaryBuilder.java index 3a56af57f175..c732e1db7e25 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestDictionaryBuilder.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestDictionaryBuilder.java @@ -16,7 +16,7 @@ import com.google.common.collect.ImmutableSet; import io.trino.orc.writer.DictionaryBuilder; import io.trino.spi.block.VariableWidthBlock; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.HashSet; import java.util.Optional; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestDictionaryCompressionOptimizer.java b/lib/trino-orc/src/test/java/io/trino/orc/TestDictionaryCompressionOptimizer.java index 60d9f6a92686..695047b05247 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestDictionaryCompressionOptimizer.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestDictionaryCompressionOptimizer.java @@ -17,7 +17,7 @@ import io.airlift.units.DataSize; import io.airlift.units.DataSize.Unit; import io.trino.orc.DictionaryCompressionOptimizer.DictionaryColumn; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.OptionalInt; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcBloomFilters.java b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcBloomFilters.java index 457501bbd43e..2649043b2c0d 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcBloomFilters.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcBloomFilters.java @@ -35,7 +35,7 @@ import io.trino.spi.predicate.ValueSet; import io.trino.spi.type.RealType; import io.trino.spi.type.Type; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.InputStream; import java.math.BigDecimal; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcDataSourceUtils.java b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcDataSourceUtils.java index b388fb179e9d..d7a41b9d41b6 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcDataSourceUtils.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcDataSourceUtils.java @@ -15,7 +15,7 @@ import com.google.common.collect.ImmutableList; import io.airlift.units.DataSize; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.List; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcLz4.java b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcLz4.java index 5ae4b7d64e21..830d83ad9df1 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcLz4.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcLz4.java @@ -18,7 +18,7 @@ import io.trino.spi.Page; import io.trino.spi.block.Block; import org.joda.time.DateTimeZone; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.nio.file.Path; import java.util.Random; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcOutputBuffer.java b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcOutputBuffer.java index 1db84d468981..eda0a882705f 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcOutputBuffer.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcOutputBuffer.java @@ -15,7 +15,7 @@ import io.airlift.slice.DynamicSliceOutput; import io.trino.orc.metadata.CompressionKind; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Arrays; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcReader.java b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcReader.java index 259a4b418484..ef7dfdd4234a 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcReader.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcReader.java @@ -13,7 +13,7 @@ */ package io.trino.orc; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.orc.OrcTester.fullOrcTester; import static io.trino.spi.type.DoubleType.DOUBLE; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcReaderMemoryUsage.java b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcReaderMemoryUsage.java index 10058a0144e1..1e7453407a71 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcReaderMemoryUsage.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcReaderMemoryUsage.java @@ -26,7 +26,7 @@ import org.apache.hadoop.hive.serde2.objectinspector.SettableStructObjectInspector; import org.apache.hadoop.hive.serde2.objectinspector.StructField; import org.apache.hadoop.io.Writable; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.util.HashMap; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcReaderPositions.java b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcReaderPositions.java index de3fcc84aa4b..afa7f0f27891 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcReaderPositions.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcReaderPositions.java @@ -35,7 +35,7 @@ import org.apache.hadoop.hive.serde2.objectinspector.SettableStructObjectInspector; import org.apache.hadoop.hive.serde2.objectinspector.StructField; import org.apache.hadoop.io.Writable; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.File; import java.io.IOException; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcWithoutRowGroupInfo.java b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcWithoutRowGroupInfo.java index bc6a53643f25..ebaaaab28f1e 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcWithoutRowGroupInfo.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcWithoutRowGroupInfo.java @@ -21,7 +21,7 @@ import io.trino.spi.predicate.Domain; import io.trino.spi.type.RowType; import org.joda.time.DateTimeZone; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.File; import java.util.OptionalInt; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcWriter.java b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcWriter.java index 19097430462f..acfe70c95a2f 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcWriter.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcWriter.java @@ -33,7 +33,7 @@ import io.trino.spi.block.Block; import io.trino.spi.block.VariableWidthBlockBuilder; import io.trino.spi.type.Type; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.io.InputStream; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestReadBloomFilter.java b/lib/trino-orc/src/test/java/io/trino/orc/TestReadBloomFilter.java index 781265773039..049aeed87c06 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestReadBloomFilter.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestReadBloomFilter.java @@ -20,7 +20,7 @@ import io.trino.spi.type.SqlTimestamp; import io.trino.spi.type.SqlVarbinary; import io.trino.spi.type.Type; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.util.List; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestSliceDictionaryColumnReader.java b/lib/trino-orc/src/test/java/io/trino/orc/TestSliceDictionaryColumnReader.java index 3bdc5cde691b..d0374a72f4e7 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestSliceDictionaryColumnReader.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestSliceDictionaryColumnReader.java @@ -20,7 +20,7 @@ import io.trino.orc.metadata.OrcMetadataReader; import io.trino.orc.metadata.StripeInformation; import io.trino.orc.reader.SliceDictionaryColumnReader; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.File; import java.nio.file.Files; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestSliceDictionaryColumnWriter.java b/lib/trino-orc/src/test/java/io/trino/orc/TestSliceDictionaryColumnWriter.java index d483409e2a54..3d277cdcc37e 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestSliceDictionaryColumnWriter.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestSliceDictionaryColumnWriter.java @@ -32,7 +32,7 @@ import io.trino.spi.block.Block; import io.trino.spi.block.RunLengthEncodedBlock; import io.trino.spi.block.VariableWidthBlockBuilder; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.util.ArrayList; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestStructColumnReader.java b/lib/trino-orc/src/test/java/io/trino/orc/TestStructColumnReader.java index f5926371236e..0b5f52e8235b 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestStructColumnReader.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestStructColumnReader.java @@ -31,9 +31,11 @@ import io.trino.spi.type.Type; import io.trino.spi.type.TypeSignatureParameter; import io.trino.testing.TestingConnectorSession; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.IOException; import java.util.ArrayList; @@ -50,25 +52,29 @@ import static io.trino.orc.metadata.CompressionKind.NONE; import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.joda.time.DateTimeZone.UTC; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNull; -@Test(singleThreaded = true) +@TestInstance(PER_METHOD) +@Execution(SAME_THREAD) public class TestStructColumnReader { private static final String STRUCT_COL_NAME = "struct_col"; private TempFile tempFile; - @BeforeMethod + @BeforeEach public void setUp() throws IOException { tempFile = new TempFile(); } - @AfterMethod(alwaysRun = true) + @AfterEach public void tearDown() throws IOException { @@ -145,19 +151,21 @@ public void testReaderLowerCasesFieldNamesFromType() assertEquals(actual.get(2), "fieldCValue"); } - @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = - "ROW type does not have field names declared: row\\(varchar, varchar, varchar\\)") + @Test public void testThrowsExceptionWhenFieldNameMissing() - throws IOException { - List readerFields = new ArrayList<>(Arrays.asList("field_a", "field_b", "field_c")); - List writerFields = new ArrayList<>(Arrays.asList("field_a", "field_b", "field_c")); - List writerData = new ArrayList<>(Arrays.asList("field_a_value", "field_b_value", "field_c_value")); - Type readerType = getTypeNullName(readerFields.size()); - Type writerType = getType(writerFields); - - write(tempFile, writerType, writerData); - read(tempFile, readerType); + assertThatThrownBy(() -> { + List readerFields = new ArrayList<>(Arrays.asList("field_a", "field_b", "field_c")); + List writerFields = new ArrayList<>(Arrays.asList("field_a", "field_b", "field_c")); + List writerData = new ArrayList<>(Arrays.asList("field_a_value", "field_b_value", "field_c_value")); + Type readerType = getTypeNullName(readerFields.size()); + Type writerType = getType(writerFields); + + write(tempFile, writerType, writerData); + read(tempFile, readerType); + }) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("ROW type does not have field names declared: row(varchar, varchar, varchar)"); } /** diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestTimestampTzMicros.java b/lib/trino-orc/src/test/java/io/trino/orc/TestTimestampTzMicros.java index c2f986d634af..03595c186be7 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestTimestampTzMicros.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestTimestampTzMicros.java @@ -19,7 +19,7 @@ import io.trino.spi.type.TimestampType; import io.trino.spi.type.TimestampWithTimeZoneType; import org.joda.time.DateTimeZone; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static com.google.common.io.Resources.getResource; import static com.google.common.io.Resources.toByteArray; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestTupleDomainOrcPredicate.java b/lib/trino-orc/src/test/java/io/trino/orc/TestTupleDomainOrcPredicate.java index 78333d48bfaf..632f27757f25 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestTupleDomainOrcPredicate.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestTupleDomainOrcPredicate.java @@ -30,7 +30,7 @@ import io.trino.spi.type.LongTimestamp; import io.trino.spi.type.LongTimestampWithTimeZone; import io.trino.spi.type.Type; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.math.BigDecimal; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/TestOrcMetadataReader.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/TestOrcMetadataReader.java index 3efc08b44c8a..5a66a395b878 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/TestOrcMetadataReader.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/TestOrcMetadataReader.java @@ -21,7 +21,7 @@ import io.trino.orc.metadata.statistics.StringStatistics; import io.trino.orc.proto.OrcProto; import io.trino.orc.protobuf.ByteString; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Arrays; import java.util.List; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestStringStatisticsBuilder.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestStringStatisticsBuilder.java index 252ae32e9678..46f985c0e03d 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestStringStatisticsBuilder.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestStringStatisticsBuilder.java @@ -16,7 +16,7 @@ import com.google.common.collect.ImmutableList; import io.airlift.slice.Slice; import io.airlift.slice.Slices; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.List; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestBooleanOutputStream.java b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestBooleanOutputStream.java index 2e03c6389712..d3edb8559d39 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestBooleanOutputStream.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestBooleanOutputStream.java @@ -19,7 +19,7 @@ import io.trino.orc.OrcOutputBuffer; import io.trino.orc.checkpoint.BooleanStreamCheckpoint; import io.trino.orc.checkpoint.ByteStreamCheckpoint; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.List; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestBooleanStream.java b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestBooleanStream.java index 7aaedd90e27e..ea5a8971c63b 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestBooleanStream.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestBooleanStream.java @@ -23,7 +23,7 @@ import io.trino.orc.metadata.Stream.StreamKind; import it.unimi.dsi.fastutil.booleans.BooleanArrayList; import it.unimi.dsi.fastutil.booleans.BooleanList; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.util.ArrayList; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestByteArrayStream.java b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestByteArrayStream.java index ffa54ec1c014..85d8b3de6d3f 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestByteArrayStream.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestByteArrayStream.java @@ -19,7 +19,7 @@ import io.trino.orc.OrcCorruptionException; import io.trino.orc.OrcDecompressor; import io.trino.orc.checkpoint.ByteArrayStreamCheckpoint; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.util.ArrayList; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestByteStream.java b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestByteStream.java index 2306a981f1c7..9af014dac87a 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestByteStream.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestByteStream.java @@ -17,7 +17,7 @@ import io.trino.orc.OrcCorruptionException; import io.trino.orc.OrcDecompressor; import io.trino.orc.checkpoint.ByteStreamCheckpoint; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.util.ArrayList; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestDecimalStream.java b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestDecimalStream.java index 06b7575c529d..b5fb0d2e8e9c 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestDecimalStream.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestDecimalStream.java @@ -20,7 +20,7 @@ import io.trino.orc.OrcCorruptionException; import io.trino.orc.OrcDataSourceId; import io.trino.spi.type.Int128; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.ByteArrayOutputStream; import java.io.IOException; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestDoubleStream.java b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestDoubleStream.java index 68aa2690b947..9b95edf61bdf 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestDoubleStream.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestDoubleStream.java @@ -17,7 +17,7 @@ import io.trino.orc.OrcCorruptionException; import io.trino.orc.OrcDecompressor; import io.trino.orc.checkpoint.DoubleStreamCheckpoint; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.util.ArrayList; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestFloatStream.java b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestFloatStream.java index d5b55f59d6d0..09a76510a93f 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestFloatStream.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestFloatStream.java @@ -17,7 +17,7 @@ import io.trino.orc.OrcCorruptionException; import io.trino.orc.OrcDecompressor; import io.trino.orc.checkpoint.FloatStreamCheckpoint; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.util.ArrayList; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongBitPacker.java b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongBitPacker.java index 208d0ee7c978..5d3f1c3c0f46 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongBitPacker.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongBitPacker.java @@ -13,7 +13,7 @@ */ package io.trino.orc.stream; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.io.InputStream; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongDecimalStream.java b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongDecimalStream.java index 9318f14783e8..ff68aea5a448 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongDecimalStream.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongDecimalStream.java @@ -18,7 +18,7 @@ import io.trino.orc.OrcDecompressor; import io.trino.orc.checkpoint.DecimalStreamCheckpoint; import io.trino.spi.type.Int128; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.math.BigInteger; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongDecode.java b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongDecode.java index 361c006f71f1..f8f731a0627b 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongDecode.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongDecode.java @@ -17,7 +17,7 @@ import io.airlift.slice.SliceOutput; import io.airlift.slice.Slices; import io.trino.orc.OrcDataSourceId; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.EOFException; import java.io.IOException; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongStreamV1.java b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongStreamV1.java index 5145f137b637..18283d4252e3 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongStreamV1.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongStreamV1.java @@ -17,7 +17,7 @@ import io.trino.orc.OrcCorruptionException; import io.trino.orc.OrcDecompressor; import io.trino.orc.checkpoint.LongStreamCheckpoint; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.util.ArrayList; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongStreamV2.java b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongStreamV2.java index 0596ed8e8a56..b8a4106b0728 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongStreamV2.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongStreamV2.java @@ -17,7 +17,7 @@ import io.trino.orc.OrcCorruptionException; import io.trino.orc.OrcDecompressor; import io.trino.orc.checkpoint.LongStreamCheckpoint; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.util.ArrayList; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestShortDecimalStream.java b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestShortDecimalStream.java index 0cb06431bfe3..b7757806b9dc 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestShortDecimalStream.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestShortDecimalStream.java @@ -18,7 +18,7 @@ import io.trino.orc.OrcDecompressor; import io.trino.orc.checkpoint.DecimalStreamCheckpoint; import io.trino.spi.type.Int128; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.util.ArrayList; diff --git a/lib/trino-parquet/pom.xml b/lib/trino-parquet/pom.xml index 088007c94057..389b9f7d3131 100644 --- a/lib/trino-parquet/pom.xml +++ b/lib/trino-parquet/pom.xml @@ -187,6 +187,12 @@ test + + org.junit.jupiter + junit-jupiter-api + test + + org.junit.jupiter junit-jupiter-engine diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/TestParquetTimestampUtils.java b/lib/trino-parquet/src/test/java/io/trino/parquet/TestParquetTimestampUtils.java index fd2cb6a133bf..d38de5fffc4f 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/TestParquetTimestampUtils.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/TestParquetTimestampUtils.java @@ -17,7 +17,7 @@ import io.trino.spi.TrinoException; import org.apache.hadoop.hive.common.type.Timestamp; import org.apache.parquet.io.api.Binary; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.time.LocalDateTime; diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/predicate/BenchmarkTupleDomainParquetPredicate.java b/lib/trino-parquet/src/test/java/io/trino/parquet/predicate/BenchmarkTupleDomainParquetPredicate.java index 13f8df3cff3d..23b15ccd5bf5 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/predicate/BenchmarkTupleDomainParquetPredicate.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/predicate/BenchmarkTupleDomainParquetPredicate.java @@ -20,6 +20,7 @@ import io.trino.spi.predicate.Domain; import org.apache.parquet.column.ColumnDescriptor; import org.apache.parquet.schema.Types; +import org.junit.jupiter.api.Test; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; @@ -32,7 +33,6 @@ import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.runner.RunnerException; -import org.testng.annotations.Test; import java.io.IOException; import java.util.ArrayList; diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/predicate/TestPredicateUtils.java b/lib/trino-parquet/src/test/java/io/trino/parquet/predicate/TestPredicateUtils.java index 6c48d294491b..f70716d7fa6d 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/predicate/TestPredicateUtils.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/predicate/TestPredicateUtils.java @@ -21,7 +21,7 @@ import org.apache.parquet.hadoop.metadata.ColumnChunkMetaData; import org.apache.parquet.schema.PrimitiveType; import org.apache.parquet.schema.Types; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Set; diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestBinaryColumnBenchmark.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestBinaryColumnBenchmark.java index 73a82cf9f5db..a459299c3364 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestBinaryColumnBenchmark.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestBinaryColumnBenchmark.java @@ -16,7 +16,7 @@ import io.trino.parquet.reader.BenchmarkBinaryColumnReader.Encoding; import io.trino.parquet.reader.BenchmarkBinaryColumnReader.FieldType; import io.trino.parquet.reader.BenchmarkBinaryColumnReader.PositionLength; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestColumnIndexBuilder.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestColumnIndexBuilder.java index d6944356fc01..73fff71fd453 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestColumnIndexBuilder.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestColumnIndexBuilder.java @@ -27,7 +27,7 @@ import org.apache.parquet.schema.LogicalTypeAnnotation; import org.apache.parquet.schema.PrimitiveType; import org.apache.parquet.schema.Types; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.math.BigDecimal; import java.nio.ByteBuffer; diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestColumnIndexFilter.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestColumnIndexFilter.java index ade047c61970..ec77c71ed73d 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestColumnIndexFilter.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestColumnIndexFilter.java @@ -29,7 +29,7 @@ import org.apache.parquet.internal.filter2.columnindex.ColumnIndexStore; import org.apache.parquet.internal.filter2.columnindex.RowRanges; import org.apache.parquet.schema.PrimitiveType; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.nio.ByteBuffer; import java.util.ArrayList; diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestColumnReaderBenchmark.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestColumnReaderBenchmark.java index 9bad2f5a4e09..adb3f9366fb7 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestColumnReaderBenchmark.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestColumnReaderBenchmark.java @@ -15,7 +15,7 @@ import com.google.common.collect.ImmutableList; import io.trino.parquet.ParquetEncoding; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestParquetReaderMemoryUsage.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestParquetReaderMemoryUsage.java index 8ade0d3cbfbe..fab5d6284986 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestParquetReaderMemoryUsage.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestParquetReaderMemoryUsage.java @@ -23,7 +23,7 @@ import io.trino.spi.block.LazyBlock; import io.trino.spi.type.Type; import org.apache.parquet.hadoop.metadata.ParquetMetadata; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.util.List; diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestParquetReaderUtils.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestParquetReaderUtils.java index fbf8d4a3ea87..3df221bdd66c 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestParquetReaderUtils.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestParquetReaderUtils.java @@ -15,7 +15,7 @@ import io.airlift.slice.Slices; import org.apache.parquet.bytes.BytesUtils; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.ByteArrayOutputStream; import java.io.IOException; diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestParquetReaderUtilsBenchmarks.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestParquetReaderUtilsBenchmarks.java index b9e92a74a1e0..b4a2141f883f 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestParquetReaderUtilsBenchmarks.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestParquetReaderUtilsBenchmarks.java @@ -13,7 +13,7 @@ */ package io.trino.parquet.reader; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestShortDecimalColumnReaderBenchmark.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestShortDecimalColumnReaderBenchmark.java index fab2dc1bf14b..40b44427d40c 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestShortDecimalColumnReaderBenchmark.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestShortDecimalColumnReaderBenchmark.java @@ -15,7 +15,7 @@ import com.google.common.collect.ImmutableList; import io.trino.parquet.ParquetEncoding; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestTimeMillis.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestTimeMillis.java index bcafc326c101..1d27c37c91eb 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestTimeMillis.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestTimeMillis.java @@ -23,7 +23,7 @@ import io.trino.spi.type.TimeType; import io.trino.spi.type.Type; import org.apache.parquet.hadoop.metadata.ParquetMetadata; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.File; import java.util.List; diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/decoders/TestRleBitPackingDecoderBenchmark.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/decoders/TestRleBitPackingDecoderBenchmark.java index f233b77b583b..3858392cc71a 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/decoders/TestRleBitPackingDecoderBenchmark.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/decoders/TestRleBitPackingDecoderBenchmark.java @@ -13,7 +13,7 @@ */ package io.trino.parquet.reader.decoders; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/flat/TestBinaryBuffer.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/flat/TestBinaryBuffer.java index d81b5e51870d..12acf25b6f12 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/flat/TestBinaryBuffer.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/flat/TestBinaryBuffer.java @@ -15,7 +15,7 @@ import io.airlift.slice.Slice; import io.airlift.slice.Slices; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.testng.Assert.assertSame; diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/flat/TestBitPackingUtils.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/flat/TestBitPackingUtils.java index c9b49614b08e..2aee7249b4e1 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/flat/TestBitPackingUtils.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/flat/TestBitPackingUtils.java @@ -13,7 +13,7 @@ */ package io.trino.parquet.reader.flat; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Random; diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/flat/TestFlatColumnReader.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/flat/TestFlatColumnReader.java index e52ca0fe1353..bae20cf49539 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/flat/TestFlatColumnReader.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/flat/TestFlatColumnReader.java @@ -30,7 +30,7 @@ import org.apache.parquet.column.values.ValuesWriter; import org.apache.parquet.column.values.rle.RunLengthBitPackingHybridValuesWriter; import org.apache.parquet.schema.PrimitiveType; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.util.List; diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/flat/TestNullsDecoderBenchmark.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/flat/TestNullsDecoderBenchmark.java index aa66ce3caacf..9915c88d21cb 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/flat/TestNullsDecoderBenchmark.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/flat/TestNullsDecoderBenchmark.java @@ -14,7 +14,7 @@ package io.trino.parquet.reader.flat; import io.trino.parquet.reader.flat.BenchmarkFlatDefinitionLevelDecoder.DataGenerator; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/flat/TestRowRangesIterator.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/flat/TestRowRangesIterator.java index e5f933b9edee..cf45fc34cf8c 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/flat/TestRowRangesIterator.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/flat/TestRowRangesIterator.java @@ -15,7 +15,7 @@ import com.google.common.base.VerifyException; import io.trino.parquet.reader.FilteredRowRanges; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Optional; import java.util.OptionalLong; diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/writer/TestParquetSchemaConverter.java b/lib/trino-parquet/src/test/java/io/trino/parquet/writer/TestParquetSchemaConverter.java index a78079b43a2e..ff35f5d8a433 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/writer/TestParquetSchemaConverter.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/writer/TestParquetSchemaConverter.java @@ -18,7 +18,7 @@ import org.apache.parquet.schema.LogicalTypeAnnotation; import org.apache.parquet.schema.PrimitiveType; import org.apache.parquet.schema.Type; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.math.BigInteger; diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/writer/TestParquetWriter.java b/lib/trino-parquet/src/test/java/io/trino/parquet/writer/TestParquetWriter.java index 14a6a2011fb8..555bc6275e60 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/writer/TestParquetWriter.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/writer/TestParquetWriter.java @@ -41,7 +41,7 @@ import org.apache.parquet.hadoop.metadata.ColumnChunkMetaData; import org.apache.parquet.hadoop.metadata.ParquetMetadata; import org.apache.parquet.schema.PrimitiveType; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.ByteArrayOutputStream; import java.io.IOException; diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/writer/TestTrinoValuesWriterFactory.java b/lib/trino-parquet/src/test/java/io/trino/parquet/writer/TestTrinoValuesWriterFactory.java index 3abec18fe784..888086c892c3 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/writer/TestTrinoValuesWriterFactory.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/writer/TestTrinoValuesWriterFactory.java @@ -28,7 +28,7 @@ import org.apache.parquet.column.values.plain.FixedLenByteArrayPlainValuesWriter; import org.apache.parquet.column.values.plain.PlainValuesWriter; import org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static java.util.Locale.ENGLISH; import static org.apache.parquet.column.ParquetProperties.WriterVersion.PARQUET_1_0; diff --git a/lib/trino-parquet/src/test/java/org/apache/parquet/column/values/dictionary/TestDictionaryWriter.java b/lib/trino-parquet/src/test/java/org/apache/parquet/column/values/dictionary/TestDictionaryWriter.java index 06afb635a310..018d1f070274 100644 --- a/lib/trino-parquet/src/test/java/org/apache/parquet/column/values/dictionary/TestDictionaryWriter.java +++ b/lib/trino-parquet/src/test/java/org/apache/parquet/column/values/dictionary/TestDictionaryWriter.java @@ -35,7 +35,7 @@ import org.apache.parquet.column.values.dictionary.DictionaryValuesWriter.PlainLongDictionaryValuesWriter; import org.apache.parquet.column.values.plain.PlainValuesWriter; import org.apache.parquet.io.api.Binary; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.nio.charset.StandardCharsets; diff --git a/lib/trino-phoenix5-patched/pom.xml b/lib/trino-phoenix5-patched/pom.xml index ef10f9c260b2..cdbce121d01a 100644 --- a/lib/trino-phoenix5-patched/pom.xml +++ b/lib/trino-phoenix5-patched/pom.xml @@ -25,8 +25,14 @@ - org.testng - testng + io.airlift + junit-extensions + test + + + + org.junit.jupiter + junit-jupiter-api test diff --git a/lib/trino-phoenix5-patched/src/test/java/org/apache/phoenix/TestDummy.java b/lib/trino-phoenix5-patched/src/test/java/org/apache/phoenix/TestDummy.java index 324d7da818bd..5a6de3fbedc3 100644 --- a/lib/trino-phoenix5-patched/src/test/java/org/apache/phoenix/TestDummy.java +++ b/lib/trino-phoenix5-patched/src/test/java/org/apache/phoenix/TestDummy.java @@ -13,7 +13,7 @@ */ package org.apache.phoenix; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; public class TestDummy { diff --git a/lib/trino-plugin-toolkit/pom.xml b/lib/trino-plugin-toolkit/pom.xml index 417414ae245c..67ab71419396 100644 --- a/lib/trino-plugin-toolkit/pom.xml +++ b/lib/trino-plugin-toolkit/pom.xml @@ -145,6 +145,12 @@ test + + io.airlift + junit-extensions + test + + io.airlift testing @@ -170,6 +176,18 @@ test + + org.junit.jupiter + junit-jupiter-api + test + + + + org.junit.jupiter + junit-jupiter-engine + test + + org.testng testng @@ -178,6 +196,7 @@ + true @@ -194,5 +213,24 @@ + + + org.apache.maven.plugins + maven-surefire-plugin + + + + org.apache.maven.surefire + surefire-junit-platform + ${dep.plugin.surefire.version} + + + org.apache.maven.surefire + surefire-testng + ${dep.plugin.surefire.version} + + + + diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/authentication/TestKerberosConfiguration.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/authentication/TestKerberosConfiguration.java index 0d1a01d3638b..91bb1597e527 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/authentication/TestKerberosConfiguration.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/authentication/TestKerberosConfiguration.java @@ -13,7 +13,7 @@ */ package io.trino.plugin.base.authentication; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.plugin.base.authentication.KerberosConfiguration.Builder.getServerPrincipal; import static org.assertj.core.api.Assertions.assertThat; diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/classloader/TestClassLoaderSafeWrappers.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/classloader/TestClassLoaderSafeWrappers.java index 55646e93a8b1..b7274558b7c2 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/classloader/TestClassLoaderSafeWrappers.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/classloader/TestClassLoaderSafeWrappers.java @@ -27,7 +27,7 @@ import io.trino.spi.connector.SystemTable; import io.trino.spi.eventlistener.EventListener; import io.trino.spi.function.table.ConnectorTableFunction; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.lang.reflect.Method; import java.util.Iterator; diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/io/TestByteBuffers.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/io/TestByteBuffers.java index 50da4844039a..2b769545cdaa 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/io/TestByteBuffers.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/io/TestByteBuffers.java @@ -13,7 +13,7 @@ */ package io.trino.plugin.base.io; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.nio.ByteBuffer; diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/ldap/TestLdapConfig.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/ldap/TestLdapConfig.java index 89874bae7b1c..d9e987beca54 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/ldap/TestLdapConfig.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/ldap/TestLdapConfig.java @@ -19,7 +19,7 @@ import jakarta.validation.constraints.AssertTrue; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.nio.file.Files; diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/logging/TestFormatInterpolator.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/logging/TestFormatInterpolator.java index 561777bbbde4..4fb7a245243c 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/logging/TestFormatInterpolator.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/logging/TestFormatInterpolator.java @@ -13,7 +13,7 @@ */ package io.trino.plugin.base.logging; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.testng.Assert.assertFalse; diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/mapping/TestForwardingIdentifierMapping.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/mapping/TestForwardingIdentifierMapping.java index cd264d7ee1d5..42b990cac3dc 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/mapping/TestForwardingIdentifierMapping.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/mapping/TestForwardingIdentifierMapping.java @@ -13,7 +13,7 @@ */ package io.trino.plugin.base.mapping; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.spi.testing.InterfaceTestUtils.assertAllMethodsOverridden; import static io.trino.spi.testing.InterfaceTestUtils.assertProperForwardingMethodsAreCalled; diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/mapping/TestIdentifierMappingRules.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/mapping/TestIdentifierMappingRules.java index 56b4ea1fec08..d312d181652c 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/mapping/TestIdentifierMappingRules.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/mapping/TestIdentifierMappingRules.java @@ -15,7 +15,7 @@ import com.google.common.collect.ImmutableList; import io.airlift.json.JsonCodec; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/mapping/TestMappingConfig.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/mapping/TestMappingConfig.java index d737012a5490..2b50b2d7dd06 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/mapping/TestMappingConfig.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/mapping/TestMappingConfig.java @@ -15,7 +15,7 @@ import com.google.common.collect.ImmutableMap; import io.airlift.units.Duration; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Map; diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/metrics/TestMetrics.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/metrics/TestMetrics.java index 9237577d6e8f..43f8f7be4464 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/metrics/TestMetrics.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/metrics/TestMetrics.java @@ -20,7 +20,7 @@ import io.airlift.units.Duration; import io.trino.spi.metrics.Metric; import io.trino.spi.metrics.Metrics; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Arrays; import java.util.Map; @@ -28,6 +28,7 @@ import static io.trino.spi.metrics.Metrics.accumulator; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; public class TestMetrics { @@ -103,12 +104,15 @@ public void testDurationJson() assertThat(result.getAirliftDuration()).isEqualTo(duration.getAirliftDuration()); } - @Test(expectedExceptions = ClassCastException.class) + @Test public void testFailIncompatibleTypes() { - Metrics m1 = new Metrics(ImmutableMap.of("a", new TDigestHistogram(new TDigest()))); - Metrics m2 = new Metrics(ImmutableMap.of("a", new LongCount(0))); - merge(m1, m2); + assertThatThrownBy(() -> { + Metrics m1 = new Metrics(ImmutableMap.of("a", new TDigestHistogram(new TDigest()))); + Metrics m2 = new Metrics(ImmutableMap.of("a", new LongCount(0))); + merge(m1, m2); + }) + .isInstanceOf(ClassCastException.class); } @Test diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/projection/TestApplyProjectionUtil.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/projection/TestApplyProjectionUtil.java index a5a924355210..5ae338c3c504 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/projection/TestApplyProjectionUtil.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/projection/TestApplyProjectionUtil.java @@ -19,7 +19,7 @@ import io.trino.spi.expression.FieldDereference; import io.trino.spi.expression.Variable; import io.trino.spi.type.RowType; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.plugin.base.projection.ApplyProjectionUtil.extractSupportedProjectedColumns; import static io.trino.plugin.base.projection.ApplyProjectionUtil.isPushdownSupported; diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedConnectorAccessControlTest.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedConnectorAccessControlTest.java index 6eb93732ab69..75d92892b4a9 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedConnectorAccessControlTest.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedConnectorAccessControlTest.java @@ -31,8 +31,8 @@ import io.trino.spi.security.Privilege; import io.trino.spi.security.TrinoPrincipal; import io.trino.spi.security.ViewExpression; +import org.junit.jupiter.api.Test; import org.testng.Assert.ThrowingRunnable; -import org.testng.annotations.Test; import java.io.File; import java.util.List; diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedSystemAccessControlTest.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedSystemAccessControlTest.java index f8daf237e5d3..d9faa620cf24 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedSystemAccessControlTest.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedSystemAccessControlTest.java @@ -32,7 +32,7 @@ import io.trino.spi.security.TrinoPrincipal; import io.trino.spi.security.ViewExpression; import org.assertj.core.api.ThrowableAssert.ThrowingCallable; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import javax.security.auth.kerberos.KerberosPrincipal; diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestAllowAllAccessControl.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestAllowAllAccessControl.java index 5ba6e98a7570..8f9955791e59 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestAllowAllAccessControl.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestAllowAllAccessControl.java @@ -14,7 +14,7 @@ package io.trino.plugin.base.security; import io.trino.spi.connector.ConnectorAccessControl; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.spi.testing.InterfaceTestUtils.assertAllMethodsOverridden; diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestAllowAllSystemAccessControl.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestAllowAllSystemAccessControl.java index aafcd9b63e30..3bf6b9c65602 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestAllowAllSystemAccessControl.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestAllowAllSystemAccessControl.java @@ -14,7 +14,7 @@ package io.trino.plugin.base.security; import io.trino.spi.security.SystemAccessControl; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.spi.testing.InterfaceTestUtils.assertAllMethodsOverridden; diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestFileBasedAccessControlConfig.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestFileBasedAccessControlConfig.java index 7e79e656c65d..425f587c29e7 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestFileBasedAccessControlConfig.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestFileBasedAccessControlConfig.java @@ -18,7 +18,7 @@ import io.airlift.units.MinDuration; import jakarta.validation.constraints.AssertTrue; import jakarta.validation.constraints.NotNull; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.nio.file.Files; diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestForwardingConnectorAccessControl.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestForwardingConnectorAccessControl.java index 0a7276d3c068..8d621a67b1d7 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestForwardingConnectorAccessControl.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestForwardingConnectorAccessControl.java @@ -14,7 +14,7 @@ package io.trino.plugin.base.security; import io.trino.spi.connector.ConnectorAccessControl; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.spi.testing.InterfaceTestUtils.assertAllMethodsOverridden; import static io.trino.spi.testing.InterfaceTestUtils.assertProperForwardingMethodsAreCalled; diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestForwardingSystemAccessControl.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestForwardingSystemAccessControl.java index 7b999e44c1bb..8887b681833c 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestForwardingSystemAccessControl.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestForwardingSystemAccessControl.java @@ -14,7 +14,7 @@ package io.trino.plugin.base.security; import io.trino.spi.security.SystemAccessControl; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.spi.testing.InterfaceTestUtils.assertAllMethodsOverridden; diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestHttpFileBasedAccessControl.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestHttpFileBasedAccessControl.java index a0e15b1e560f..7caf675d38be 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestHttpFileBasedAccessControl.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestHttpFileBasedAccessControl.java @@ -16,27 +16,26 @@ import com.google.common.collect.ImmutableMap; import io.trino.plugin.base.util.TestingHttpServer; import io.trino.spi.connector.ConnectorAccessControl; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.io.IOException; import java.util.Map; import static io.airlift.testing.Closeables.closeAll; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestHttpFileBasedAccessControl extends BaseFileBasedConnectorAccessControlTest { - private TestingHttpServer testingHttpServer; + private final TestingHttpServer testingHttpServer = new TestingHttpServer(); - @BeforeClass - public void setUp() - { - testingHttpServer = new TestingHttpServer(); - } - - @AfterClass(alwaysRun = true) + @AfterAll public void tearDown() throws IOException { diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestHttpFileBasedSystemAccessControl.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestHttpFileBasedSystemAccessControl.java index 461a48498324..c8c93b8e686a 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestHttpFileBasedSystemAccessControl.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestHttpFileBasedSystemAccessControl.java @@ -16,27 +16,26 @@ import com.google.common.collect.ImmutableMap; import io.trino.plugin.base.util.TestingHttpServer; import io.trino.spi.security.SystemAccessControl; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.io.IOException; import java.util.Map; import static io.airlift.testing.Closeables.closeAll; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestHttpFileBasedSystemAccessControl extends BaseFileBasedSystemAccessControlTest { - private TestingHttpServer testingHttpServer; + private final TestingHttpServer testingHttpServer = new TestingHttpServer(); - @BeforeClass - public void setUp() - { - testingHttpServer = new TestingHttpServer(); - } - - @AfterClass(alwaysRun = true) + @AfterAll public void tearDown() throws IOException { diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/util/TestClosables.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/util/TestClosables.java index ec6d20fe3096..2ba41e59907e 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/util/TestClosables.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/util/TestClosables.java @@ -13,7 +13,7 @@ */ package io.trino.plugin.base.util; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Optional; diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/util/TestJsonUtils.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/util/TestJsonUtils.java index 8aded8864c92..48b38f2ca7b9 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/util/TestJsonUtils.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/util/TestJsonUtils.java @@ -16,7 +16,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.StreamReadConstraints; import com.fasterxml.jackson.databind.JsonNode; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.io.UncheckedIOException; diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/util/TestLoggingInvocationHandler.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/util/TestLoggingInvocationHandler.java index 52cc3e5ddb6a..facad3476139 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/util/TestLoggingInvocationHandler.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/util/TestLoggingInvocationHandler.java @@ -13,7 +13,7 @@ */ package io.trino.plugin.base.util; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.lang.reflect.InvocationHandler; import java.util.ArrayList; diff --git a/lib/trino-record-decoder/pom.xml b/lib/trino-record-decoder/pom.xml index f33c5f9d51fd..73e0fd62a3f8 100644 --- a/lib/trino-record-decoder/pom.xml +++ b/lib/trino-record-decoder/pom.xml @@ -117,6 +117,12 @@ test + + io.airlift + junit-extensions + test + + io.airlift testing @@ -148,6 +154,18 @@ test + + org.junit.jupiter + junit-jupiter-api + test + + + + org.junit.jupiter + junit-jupiter-engine + test + + org.testng testng @@ -177,6 +195,24 @@ + + + org.apache.maven.plugins + maven-surefire-plugin + + + + org.apache.maven.surefire + surefire-junit-platform + ${dep.plugin.surefire.version} + + + org.apache.maven.surefire + surefire-testng + ${dep.plugin.surefire.version} + + + diff --git a/lib/trino-record-decoder/src/test/java/io/trino/decoder/avro/TestAvroDecoder.java b/lib/trino-record-decoder/src/test/java/io/trino/decoder/avro/TestAvroDecoder.java index 0a68febae1b7..1593330c4245 100644 --- a/lib/trino-record-decoder/src/test/java/io/trino/decoder/avro/TestAvroDecoder.java +++ b/lib/trino-record-decoder/src/test/java/io/trino/decoder/avro/TestAvroDecoder.java @@ -47,7 +47,7 @@ import org.apache.avro.generic.GenericRecord; import org.apache.avro.generic.GenericRecordBuilder; import org.assertj.core.api.ThrowableAssert; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.ByteArrayOutputStream; import java.io.IOException; diff --git a/lib/trino-record-decoder/src/test/java/io/trino/decoder/csv/TestCsvDecoder.java b/lib/trino-record-decoder/src/test/java/io/trino/decoder/csv/TestCsvDecoder.java index 2d65ebc72318..0d01d5638d8b 100644 --- a/lib/trino-record-decoder/src/test/java/io/trino/decoder/csv/TestCsvDecoder.java +++ b/lib/trino-record-decoder/src/test/java/io/trino/decoder/csv/TestCsvDecoder.java @@ -31,7 +31,7 @@ import io.trino.spi.type.Type; import io.trino.spi.type.VarbinaryType; import org.assertj.core.api.ThrowableAssert; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.nio.charset.StandardCharsets; import java.util.Map; diff --git a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestCustomDateTimeJsonFieldDecoder.java b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestCustomDateTimeJsonFieldDecoder.java index 29c603c8f1c9..a2c281fb8758 100644 --- a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestCustomDateTimeJsonFieldDecoder.java +++ b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestCustomDateTimeJsonFieldDecoder.java @@ -18,7 +18,7 @@ import io.trino.decoder.DecoderTestColumnHandle; import io.trino.decoder.RowDecoderSpec; import io.trino.spi.TrinoException; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.decoder.util.DecoderTestUtil.TESTING_SESSION; import static io.trino.spi.type.DateTimeEncoding.packDateTimeWithZone; diff --git a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestDefaultJsonFieldDecoder.java b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestDefaultJsonFieldDecoder.java index 8326c3d44e9d..82d97e5f8baa 100644 --- a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestDefaultJsonFieldDecoder.java +++ b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestDefaultJsonFieldDecoder.java @@ -14,7 +14,7 @@ package io.trino.decoder.json; import io.trino.spi.type.Type; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.math.BigInteger; diff --git a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestISO8601JsonFieldDecoder.java b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestISO8601JsonFieldDecoder.java index cd1a4f5ad067..35dfd1511a7e 100644 --- a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestISO8601JsonFieldDecoder.java +++ b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestISO8601JsonFieldDecoder.java @@ -14,7 +14,7 @@ package io.trino.decoder.json; import io.trino.spi.type.Type; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.spi.type.DateTimeEncoding.packDateTimeWithZone; import static io.trino.spi.type.DateTimeEncoding.packTimeWithTimeZone; diff --git a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestJsonDecoder.java b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestJsonDecoder.java index 8bf3ef1fd7f2..f1466a43229b 100644 --- a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestJsonDecoder.java +++ b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestJsonDecoder.java @@ -24,7 +24,7 @@ import io.trino.spi.TrinoException; import io.trino.spi.type.Type; import org.assertj.core.api.ThrowableAssert.ThrowingCallable; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.nio.charset.StandardCharsets; import java.util.Map; diff --git a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestMillisecondsSinceEpochJsonFieldDecoder.java b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestMillisecondsSinceEpochJsonFieldDecoder.java index b36acf2e3038..7664d7ff33d7 100644 --- a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestMillisecondsSinceEpochJsonFieldDecoder.java +++ b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestMillisecondsSinceEpochJsonFieldDecoder.java @@ -14,7 +14,7 @@ package io.trino.decoder.json; import io.trino.spi.type.Type; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.concurrent.TimeUnit; diff --git a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestRFC2822JsonFieldDecoder.java b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestRFC2822JsonFieldDecoder.java index 698d92061275..bdc638712db9 100644 --- a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestRFC2822JsonFieldDecoder.java +++ b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestRFC2822JsonFieldDecoder.java @@ -14,7 +14,7 @@ package io.trino.decoder.json; import io.trino.spi.type.Type; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.spi.type.DateTimeEncoding.packDateTimeWithZone; import static io.trino.spi.type.TimeZoneKey.UTC_KEY; diff --git a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestSecondsSinceEpochJsonFieldDecoder.java b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestSecondsSinceEpochJsonFieldDecoder.java index 0ea0e43c492f..25363804d64f 100644 --- a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestSecondsSinceEpochJsonFieldDecoder.java +++ b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestSecondsSinceEpochJsonFieldDecoder.java @@ -14,7 +14,7 @@ package io.trino.decoder.json; import io.trino.spi.type.Type; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.concurrent.TimeUnit; diff --git a/lib/trino-record-decoder/src/test/java/io/trino/decoder/protobuf/TestProtobufDecoder.java b/lib/trino-record-decoder/src/test/java/io/trino/decoder/protobuf/TestProtobufDecoder.java index db4188d0403d..3f3936117c72 100644 --- a/lib/trino-record-decoder/src/test/java/io/trino/decoder/protobuf/TestProtobufDecoder.java +++ b/lib/trino-record-decoder/src/test/java/io/trino/decoder/protobuf/TestProtobufDecoder.java @@ -42,7 +42,7 @@ import io.trino.spi.type.SqlVarbinary; import io.trino.testing.TestingSession; import io.trino.type.JsonType; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.File; import java.net.URI; diff --git a/lib/trino-record-decoder/src/test/java/io/trino/decoder/raw/TestRawDecoder.java b/lib/trino-record-decoder/src/test/java/io/trino/decoder/raw/TestRawDecoder.java index d535a205471b..93378de5edf3 100644 --- a/lib/trino-record-decoder/src/test/java/io/trino/decoder/raw/TestRawDecoder.java +++ b/lib/trino-record-decoder/src/test/java/io/trino/decoder/raw/TestRawDecoder.java @@ -32,7 +32,7 @@ import io.trino.spi.type.VarbinaryType; import io.trino.spi.type.VarcharType; import org.assertj.core.api.ThrowableAssert; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergSystemTables.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergSystemTables.java index 32697ad6090e..c64668c8b50a 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergSystemTables.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergSystemTables.java @@ -18,9 +18,10 @@ import io.trino.testing.DistributedQueryRunner; import io.trino.testing.MaterializedResult; import io.trino.testing.MaterializedRow; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import java.time.LocalDate; import java.util.Map; @@ -31,8 +32,10 @@ import static io.trino.testing.MaterializedResult.DEFAULT_PRECISION; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.testng.Assert.assertEquals; +@TestInstance(PER_CLASS) public abstract class BaseIcebergSystemTables extends AbstractTestQueryFramework { @@ -52,7 +55,7 @@ protected DistributedQueryRunner createQueryRunner() .build(); } - @BeforeClass + @BeforeAll public void setUp() { assertUpdate("CREATE SCHEMA test_schema"); @@ -90,7 +93,7 @@ public void setUp() assertQuery("SELECT count(*) FROM test_schema.test_table_with_dml", "VALUES 7"); } - @AfterClass(alwaysRun = true) + @AfterAll public void tearDown() { assertUpdate("DROP TABLE IF EXISTS test_schema.test_table"); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseSharedMetastoreTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseSharedMetastoreTest.java index 2f497f4180d9..8a867611dd08 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseSharedMetastoreTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseSharedMetastoreTest.java @@ -14,7 +14,7 @@ package io.trino.plugin.iceberg; import io.trino.testing.AbstractTestQueryFramework; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.time.Instant; import java.time.ZonedDateTime; diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestSharedHiveMetastore.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestSharedHiveMetastore.java index c3a3240fd2d1..130edc81cd82 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestSharedHiveMetastore.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestSharedHiveMetastore.java @@ -21,7 +21,9 @@ import io.trino.testing.DistributedQueryRunner; import io.trino.testing.QueryRunner; import io.trino.tpch.TpchTable; -import org.testng.annotations.AfterClass; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.nio.file.Path; @@ -31,7 +33,11 @@ import static io.trino.testing.QueryAssertions.copyTpchTables; import static io.trino.testing.TestingSession.testSessionBuilder; import static java.lang.String.format; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestSharedHiveMetastore extends BaseSharedMetastoreTest { @@ -88,7 +94,7 @@ protected QueryRunner createQueryRunner() return queryRunner; } - @AfterClass(alwaysRun = true) + @AfterAll public void cleanup() { assertQuerySucceeds("DROP TABLE IF EXISTS hive." + schema + ".region"); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestSharedGlueMetastore.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestSharedGlueMetastore.java index 014800e00482..8b18b345f2e3 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestSharedGlueMetastore.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestSharedGlueMetastore.java @@ -25,7 +25,9 @@ import io.trino.testing.DistributedQueryRunner; import io.trino.testing.QueryRunner; import io.trino.tpch.TpchTable; -import org.testng.annotations.AfterClass; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.nio.file.Path; @@ -35,6 +37,8 @@ import static io.trino.testing.QueryAssertions.copyTpchTables; import static io.trino.testing.TestingSession.testSessionBuilder; import static java.lang.String.format; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; /** * Tests metadata operations on a schema which has a mix of Hive and Iceberg tables. @@ -42,6 +46,8 @@ * Requires AWS credentials, which can be provided any way supported by the DefaultProviderChain * See https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html#credentials-default */ +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestSharedGlueMetastore extends BaseSharedMetastoreTest { @@ -102,7 +108,7 @@ protected QueryRunner createQueryRunner() return queryRunner; } - @AfterClass(alwaysRun = true) + @AfterAll public void cleanup() { try { diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/protobuf/TestProtobufEncoder.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/protobuf/TestProtobufEncoder.java index 08be4d298ec7..8e4c38d5d4c6 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/protobuf/TestProtobufEncoder.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/protobuf/TestProtobufEncoder.java @@ -35,7 +35,7 @@ import io.trino.spi.type.SqlTimestamp; import io.trino.spi.type.Type; import io.trino.testing.TestingConnectorSession; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.time.LocalDateTime; import java.util.List; diff --git a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbClient.java b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbClient.java index b184d7a8008b..d56909a046f1 100644 --- a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbClient.java +++ b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbClient.java @@ -27,7 +27,7 @@ import io.trino.spi.connector.ColumnHandle; import io.trino.spi.expression.ConnectorExpression; import io.trino.spi.expression.Variable; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.sql.Types; import java.util.List; diff --git a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbJdbcConfig.java b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbJdbcConfig.java index f5c4556e0a6d..ae18de64de00 100644 --- a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbJdbcConfig.java +++ b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbJdbcConfig.java @@ -13,7 +13,7 @@ */ package io.trino.plugin.mariadb; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; diff --git a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbPlugin.java b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbPlugin.java index 846f8eef1752..ed8b48987e0d 100644 --- a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbPlugin.java +++ b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbPlugin.java @@ -17,7 +17,7 @@ import io.trino.spi.Plugin; import io.trino.spi.connector.ConnectorFactory; import io.trino.testing.TestingConnectorContext; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static com.google.common.collect.Iterables.getOnlyElement; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/plugin/trino-memory/pom.xml b/plugin/trino-memory/pom.xml index 9fe592d7078b..d4229c226040 100644 --- a/plugin/trino-memory/pom.xml +++ b/plugin/trino-memory/pom.xml @@ -112,6 +112,12 @@ runtime + + io.airlift + junit-extensions + test + + io.airlift testing @@ -173,6 +179,12 @@ test + + org.junit.jupiter + junit-jupiter-api + test + + org.junit.jupiter junit-jupiter-engine diff --git a/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryMetadata.java b/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryMetadata.java index aaf9d15ccd1b..15a65ed02dc6 100644 --- a/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryMetadata.java +++ b/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryMetadata.java @@ -26,7 +26,7 @@ import io.trino.spi.connector.SchemaTableName; import io.trino.spi.security.TrinoPrincipal; import io.trino.testing.TestingNodeManager; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.Map; diff --git a/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryPagesStore.java b/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryPagesStore.java index 5f3baeb98605..f24852a6594a 100644 --- a/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryPagesStore.java +++ b/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryPagesStore.java @@ -23,8 +23,10 @@ import io.trino.spi.connector.ConnectorInsertTableHandle; import io.trino.spi.connector.ConnectorOutputTableHandle; import io.trino.spi.connector.ConnectorPageSink; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.OptionalDouble; import java.util.OptionalLong; @@ -33,11 +35,14 @@ import static io.trino.testing.TestingConnectorSession.SESSION; import static io.trino.testing.TestingPageSinkId.TESTING_PAGE_SINK_ID; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; -@Test(singleThreaded = true) +@TestInstance(PER_METHOD) +@Execution(SAME_THREAD) public class TestMemoryPagesStore { private static final int POSITIONS_PER_PAGE = 0; @@ -45,7 +50,7 @@ public class TestMemoryPagesStore private MemoryPagesStore pagesStore; private MemoryPageSinkProvider pageSinkProvider; - @BeforeMethod + @BeforeEach public void setUp() { pagesStore = new MemoryPagesStore(new MemoryConfig().setMaxDataPerNode(DataSize.of(1, DataSize.Unit.MEGABYTE))); @@ -74,10 +79,13 @@ public void testInsertPageWithoutCreate() assertEquals(pagesStore.getPages(0L, 0, 1, new int[] {0}, POSITIONS_PER_PAGE, OptionalLong.empty(), OptionalDouble.empty()).size(), 1); } - @Test(expectedExceptions = TrinoException.class) + @Test public void testReadFromUnknownTable() { - pagesStore.getPages(0L, 0, 1, new int[] {0}, 0, OptionalLong.empty(), OptionalDouble.empty()); + assertThatThrownBy(() -> { + pagesStore.getPages(0L, 0, 1, new int[] {0}, 0, OptionalLong.empty(), OptionalDouble.empty()); + }) + .isInstanceOf(TrinoException.class); } @Test diff --git a/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryTableStatistics.java b/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryTableStatistics.java index d79b4e80d9f5..84c3cc64c337 100644 --- a/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryTableStatistics.java +++ b/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryTableStatistics.java @@ -19,7 +19,7 @@ import io.trino.sql.planner.OptimizerConfig; import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.QueryRunner; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static com.google.common.base.Verify.verify; import static io.trino.SystemSessionProperties.STATISTICS_PRECALCULATION_FOR_PUSHDOWN_ENABLED; diff --git a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoCaseInsensitiveMapping.java b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoCaseInsensitiveMapping.java index c711ac295beb..d12cda7b4e31 100644 --- a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoCaseInsensitiveMapping.java +++ b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoCaseInsensitiveMapping.java @@ -20,27 +20,37 @@ import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.QueryRunner; import org.bson.Document; -import org.testng.annotations.AfterClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.plugin.mongodb.MongoQueryRunner.createMongoClient; import static io.trino.plugin.mongodb.MongoQueryRunner.createMongoQueryRunner; import static io.trino.testing.TestingNames.randomNameSuffix; import static java.util.Locale.ENGLISH; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestMongoCaseInsensitiveMapping extends AbstractTestQueryFramework { - private MongoServer server; - private MongoClient client; + private final MongoServer server; + private final MongoClient client; + + public TestMongoCaseInsensitiveMapping() + { + server = new MongoServer(); + client = createMongoClient(server); + } @Override protected QueryRunner createQueryRunner() throws Exception { - server = new MongoServer(); - client = createMongoClient(server); return createMongoQueryRunner( server, ImmutableMap.of(), @@ -50,13 +60,11 @@ protected QueryRunner createQueryRunner() runner -> {}); } - @AfterClass(alwaysRun = true) + @AfterAll public final void destroy() { server.close(); - server = null; client.close(); - client = null; } @Test diff --git a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoClientConfig.java b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoClientConfig.java index 9576fd534ddb..13bd693dfaf6 100644 --- a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoClientConfig.java +++ b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoClientConfig.java @@ -14,7 +14,7 @@ package io.trino.plugin.mongodb; import com.google.common.collect.ImmutableMap; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Map; diff --git a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoPlugin.java b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoPlugin.java index a50fa8023f36..08d13422617e 100644 --- a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoPlugin.java +++ b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoPlugin.java @@ -18,7 +18,7 @@ import io.trino.spi.connector.ConnectorFactory; import io.trino.spi.type.Type; import io.trino.testing.TestingConnectorContext; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static com.google.common.collect.Iterables.getOnlyElement; import static io.trino.plugin.mongodb.ObjectIdType.OBJECT_ID; diff --git a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoProjectionPushdownPlans.java b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoProjectionPushdownPlans.java index dc85d8249468..918e24eeb65a 100644 --- a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoProjectionPushdownPlans.java +++ b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoProjectionPushdownPlans.java @@ -29,8 +29,10 @@ import io.trino.sql.planner.assertions.BasePushdownPlanTest; import io.trino.sql.planner.assertions.PlanMatchPattern; import io.trino.testing.LocalQueryRunner; -import org.testng.annotations.AfterClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.Map; @@ -52,14 +54,18 @@ import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.testing.TestingSession.testSessionBuilder; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestMongoProjectionPushdownPlans extends BasePushdownPlanTest { private static final String CATALOG = "mongodb"; private static final String SCHEMA = "test"; - private Closer closer; + private final Closer closer = Closer.create(); @Override protected LocalQueryRunner createLocalQueryRunner() @@ -71,7 +77,6 @@ protected LocalQueryRunner createLocalQueryRunner() LocalQueryRunner queryRunner = LocalQueryRunner.create(session); - closer = Closer.create(); MongoServer server = closer.register(new MongoServer()); MongoClient client = closer.register(createMongoClient(server)); @@ -91,12 +96,11 @@ protected LocalQueryRunner createLocalQueryRunner() return queryRunner; } - @AfterClass(alwaysRun = true) + @AfterAll public final void destroy() throws Exception { closer.close(); - closer = null; } @Test diff --git a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoSession.java b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoSession.java index bfc497d769a7..c5fb6146481c 100644 --- a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoSession.java +++ b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoSession.java @@ -21,7 +21,7 @@ import io.trino.spi.predicate.ValueSet; import io.trino.spi.type.Type; import org.bson.Document; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.Optional; diff --git a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoSplit.java b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoSplit.java index 543404686448..d5ecf9c2db81 100644 --- a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoSplit.java +++ b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoSplit.java @@ -15,7 +15,7 @@ import com.google.common.collect.ImmutableList; import io.airlift.json.JsonCodec; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static org.testng.Assert.assertEquals; diff --git a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoSslConfig.java b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoSslConfig.java index 314c93f4c77a..83fa57a9035d 100644 --- a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoSslConfig.java +++ b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoSslConfig.java @@ -14,7 +14,7 @@ package io.trino.plugin.mongodb; import com.google.common.collect.ImmutableMap; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.nio.file.Files; import java.nio.file.Path; diff --git a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoTableHandle.java b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoTableHandle.java index 0e124e227f23..e3777733f21c 100644 --- a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoTableHandle.java +++ b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoTableHandle.java @@ -24,8 +24,7 @@ import io.trino.spi.type.RowType; import io.trino.spi.type.Type; import io.trino.type.TypeDeserializer; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Optional; import java.util.OptionalInt; @@ -38,10 +37,9 @@ public class TestMongoTableHandle { - private JsonCodec codec; + private final JsonCodec codec; - @BeforeClass - public void init() + public TestMongoTableHandle() { ObjectMapperProvider objectMapperProvider = new ObjectMapperProvider(); objectMapperProvider.setJsonDeserializers(ImmutableMap.of(Type.class, new TypeDeserializer(TESTING_TYPE_MANAGER))); diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlClient.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlClient.java index c080db369187..3c793f5cd1d1 100644 --- a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlClient.java +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlClient.java @@ -27,7 +27,7 @@ import io.trino.spi.connector.ColumnHandle; import io.trino.spi.expression.ConnectorExpression; import io.trino.spi.expression.Variable; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.sql.Types; import java.util.List; diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlConfig.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlConfig.java index 446ba35ff7a0..fc85cf936059 100644 --- a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlConfig.java +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlConfig.java @@ -15,7 +15,7 @@ import com.google.common.collect.ImmutableMap; import io.airlift.units.Duration; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Map; import java.util.concurrent.TimeUnit; diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlJdbcConfig.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlJdbcConfig.java index fa7ffb3bd3cc..99afceb7294a 100644 --- a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlJdbcConfig.java +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlJdbcConfig.java @@ -13,7 +13,7 @@ */ package io.trino.plugin.mysql; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlLegacyConnectorTest.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlLegacyConnectorTest.java index 91f6b197f615..eff06d44caef 100644 --- a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlLegacyConnectorTest.java +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlLegacyConnectorTest.java @@ -20,7 +20,7 @@ import io.trino.sql.planner.plan.MarkDistinctNode; import io.trino.testing.QueryRunner; import io.trino.testing.sql.TestTable; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.Optional; diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlPlugin.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlPlugin.java index 7607ca52e961..19a3c5457db0 100644 --- a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlPlugin.java +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlPlugin.java @@ -17,7 +17,7 @@ import io.trino.spi.Plugin; import io.trino.spi.connector.ConnectorFactory; import io.trino.testing.TestingConnectorContext; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static com.google.common.collect.Iterables.getOnlyElement; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleConfig.java b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleConfig.java index 204937256537..659dec813f51 100644 --- a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleConfig.java +++ b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleConfig.java @@ -17,7 +17,7 @@ import io.airlift.units.Duration; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.math.RoundingMode; import java.util.Map; diff --git a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOraclePlugin.java b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOraclePlugin.java index 7b13e1914a85..b76aba6744bf 100644 --- a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOraclePlugin.java +++ b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOraclePlugin.java @@ -17,7 +17,7 @@ import io.trino.spi.Plugin; import io.trino.spi.connector.ConnectorFactory; import io.trino.testing.TestingConnectorContext; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static com.google.common.collect.Iterables.getOnlyElement; diff --git a/plugin/trino-password-authenticators/pom.xml b/plugin/trino-password-authenticators/pom.xml index c4ad8ff355c4..d6b102b822e7 100644 --- a/plugin/trino-password-authenticators/pom.xml +++ b/plugin/trino-password-authenticators/pom.xml @@ -117,6 +117,12 @@ test + + io.airlift + junit-extensions + test + + io.airlift testing @@ -135,6 +141,18 @@ test + + org.junit.jupiter + junit-jupiter-api + test + + + + org.junit.jupiter + junit-jupiter-engine + test + + org.testcontainers testcontainers @@ -153,4 +171,26 @@ test + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + org.apache.maven.surefire + surefire-junit-platform + ${dep.plugin.surefire.version} + + + org.apache.maven.surefire + surefire-testng + ${dep.plugin.surefire.version} + + + + + diff --git a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestEncryptionUtil.java b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestEncryptionUtil.java index 2315882d7ef9..d711e9df5b01 100644 --- a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestEncryptionUtil.java +++ b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestEncryptionUtil.java @@ -13,7 +13,7 @@ */ package io.trino.plugin.password.file; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.plugin.password.file.EncryptionUtil.getHashingAlgorithm; import static io.trino.plugin.password.file.HashingAlgorithm.BCRYPT; diff --git a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestFileConfig.java b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestFileConfig.java index c70b5cd5e77b..8a770797c024 100644 --- a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestFileConfig.java +++ b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestFileConfig.java @@ -16,7 +16,7 @@ import com.google.common.collect.ImmutableMap; import io.airlift.units.Duration; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.nio.file.Files; diff --git a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestFileGroupConfig.java b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestFileGroupConfig.java index 3f437baa9860..93442ff353e9 100644 --- a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestFileGroupConfig.java +++ b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestFileGroupConfig.java @@ -16,7 +16,7 @@ import com.google.common.collect.ImmutableMap; import io.airlift.units.Duration; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.nio.file.Files; diff --git a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestFileGroupProvider.java b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestFileGroupProvider.java index e1749ecc9081..893341ba18c4 100644 --- a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestFileGroupProvider.java +++ b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestFileGroupProvider.java @@ -17,7 +17,7 @@ import com.google.common.collect.ImmutableSet; import com.google.common.io.Resources; import io.trino.spi.security.GroupProvider; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.File; diff --git a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestPasswordStore.java b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestPasswordStore.java index 4187e3fe08d8..77dcd8009ecc 100644 --- a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestPasswordStore.java +++ b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestPasswordStore.java @@ -14,7 +14,7 @@ package io.trino.plugin.password.file; import com.google.common.collect.ImmutableList; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.testng.Assert.assertFalse; diff --git a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/ldap/TestLdapAuthenticator.java b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/ldap/TestLdapAuthenticator.java index 0272fd13226e..4ea836bf3e37 100644 --- a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/ldap/TestLdapAuthenticator.java +++ b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/ldap/TestLdapAuthenticator.java @@ -19,26 +19,29 @@ import io.trino.plugin.password.ldap.TestingOpenLdapServer.DisposableSubContext; import io.trino.spi.security.AccessDeniedException; import io.trino.spi.security.BasicPrincipal; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import org.testcontainers.containers.Network; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestLdapAuthenticator { - private Closer closer; + private final Closer closer; - private TestingOpenLdapServer openLdapServer; - private LdapAuthenticatorClient client; + private final TestingOpenLdapServer openLdapServer; + private final LdapAuthenticatorClient client; - @BeforeClass - public void setup() - throws Exception + public TestLdapAuthenticator() { closer = Closer.create(); Network network = Network.newNetwork(); @@ -52,14 +55,11 @@ public void setup() .setLdapUrl(openLdapServer.getLdapUrl()))); } - @AfterClass(alwaysRun = true) + @AfterAll public void close() throws Exception { closer.close(); - closer = null; - openLdapServer = null; - client = null; } @Test diff --git a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/ldap/TestLdapAuthenticatorConfig.java b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/ldap/TestLdapAuthenticatorConfig.java index 38eff5c9fba7..cf562c9fbacd 100644 --- a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/ldap/TestLdapAuthenticatorConfig.java +++ b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/ldap/TestLdapAuthenticatorConfig.java @@ -17,7 +17,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.airlift.units.Duration; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Map; import java.util.concurrent.TimeUnit; diff --git a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/ldap/TestLdapAuthenticatorWithTimeouts.java b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/ldap/TestLdapAuthenticatorWithTimeouts.java index 7e5e7ca08b9d..1d78d76a88d8 100644 --- a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/ldap/TestLdapAuthenticatorWithTimeouts.java +++ b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/ldap/TestLdapAuthenticatorWithTimeouts.java @@ -19,30 +19,33 @@ import io.trino.plugin.base.ldap.LdapClientConfig; import io.trino.plugin.password.ldap.TestingOpenLdapServer.DisposableSubContext; import io.trino.spi.security.BasicPrincipal; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import org.testcontainers.containers.Network; import org.testcontainers.containers.ToxiproxyContainer; import org.testcontainers.containers.ToxiproxyContainer.ContainerProxy; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; + +import java.io.IOException; import static eu.rekawek.toxiproxy.model.ToxicDirection.DOWNSTREAM; import static io.trino.plugin.password.ldap.TestingOpenLdapServer.LDAP_PORT; import static java.lang.String.format; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.testng.Assert.assertEquals; +@TestInstance(PER_CLASS) public class TestLdapAuthenticatorWithTimeouts { - private Closer closer; + private final Closer closer; - private TestingOpenLdapServer openLdapServer; - private String proxyLdapUrl; + private final TestingOpenLdapServer openLdapServer; + private final String proxyLdapUrl; - @BeforeClass - public void setup() - throws Exception + public TestLdapAuthenticatorWithTimeouts() + throws IOException { closer = Closer.create(); Network network = Network.newNetwork(); @@ -62,14 +65,11 @@ public void setup() proxyLdapUrl = format("ldap://%s:%s", proxy.getContainerIpAddress(), proxy.getProxyPort()); } - @AfterClass(alwaysRun = true) + @AfterAll public void close() throws Exception { closer.close(); - closer = null; - openLdapServer = null; - proxyLdapUrl = null; } @Test diff --git a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/salesforce/TestSalesforceBasicAuthenticator.java b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/salesforce/TestSalesforceBasicAuthenticator.java index 2bd8f069e2f0..932aea459187 100644 --- a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/salesforce/TestSalesforceBasicAuthenticator.java +++ b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/salesforce/TestSalesforceBasicAuthenticator.java @@ -19,9 +19,7 @@ import io.airlift.http.client.testing.TestingHttpClient; import io.airlift.units.Duration; import io.trino.spi.security.AccessDeniedException; -import org.testng.SkipException; -import org.testng.annotations.BeforeSuite; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.security.Principal; import java.util.concurrent.TimeUnit; @@ -32,24 +30,21 @@ import static io.airlift.http.client.testing.TestingResponse.mockResponse; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.abort; import static org.testng.Assert.assertEquals; import static org.testng.Assert.fail; public class TestSalesforceBasicAuthenticator { - private boolean forReal; + private final boolean forReal; private final String successResponse = "https://example.salesforce.com/services/Soap/m/46.0/examplefalsefalsehttps://example.salesforce.com/services/Soap/u/46.0/exampleexampleexamplefalsefalse$5242880USDen_USfalsetrue%sfalseexampleexampleexample7200user@salesforce.comVince Chaseexampleen_USen_US%sAmerica/ChicagoStandardTheme3"; private final String failedResponse = "sf:INVALID_LOGININVALID_LOGIN: Invalid username, password, security token; or user locked out.INVALID_LOGINInvalid username, password, security token; or user locked out."; - @BeforeSuite - public void initOnce() + public TestSalesforceBasicAuthenticator() { - forReal = false; String forRealEnvVar = System.getenv("SALESFORCE_TEST_FORREAL"); - if (forRealEnvVar != null && forRealEnvVar.equalsIgnoreCase("TRUE")) { - forReal = true; - } + forReal = forRealEnvVar != null && forRealEnvVar.equalsIgnoreCase("TRUE"); } @Test @@ -169,12 +164,12 @@ public void createAuthenticatedPrincipalFewOrgs() */ // Test a real login. - @Test(description = "Test principal name for real, yo!") + @Test public void createAuthenticatedPrincipalRealSuccess() { // Skip this test if SALESFORCE_TEST_FORREAL is not set to TRUE. if (!forReal) { - throw new SkipException("Skipping real tests."); + abort("Skipping real tests."); } String org = System.getenv("SALESFORCE_TEST_ORG"); @@ -202,7 +197,7 @@ public void createAuthenticatedPrincipalRealWrongOrg() { // Skip this test if SALESFORCE_TEST_FORREAL is not set to TRUE. if (!forReal) { - throw new SkipException("Skipping real tests."); + abort("Skipping real tests."); } String username = System.getenv("SALESFORCE_TEST_USERNAME"); @@ -228,7 +223,7 @@ public void createAuthenticatedPrincipalRealAllOrgs() { // Skip this test if SALESFORCE_TEST_FORREAL is not set to TRUE. if (!forReal) { - throw new SkipException("Skipping real tests."); + abort("Skipping real tests."); } String username = System.getenv("SALESFORCE_TEST_USERNAME"); @@ -253,7 +248,7 @@ public void createAuthenticatedPrincipalRealBadPassword() { // Skip this test if SALESFORCE_TEST_FORREAL is not set to TRUE. if (!forReal) { - throw new SkipException("Skipping real tests."); + abort("Skipping real tests."); } String org = System.getenv("SALESFORCE_TEST_ORG"); diff --git a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/salesforce/TestSalesforceConfig.java b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/salesforce/TestSalesforceConfig.java index 56c94650427d..98b708319e32 100644 --- a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/salesforce/TestSalesforceConfig.java +++ b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/salesforce/TestSalesforceConfig.java @@ -15,7 +15,7 @@ import com.google.common.collect.ImmutableMap; import io.airlift.units.Duration; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Map; diff --git a/plugin/trino-phoenix5/pom.xml b/plugin/trino-phoenix5/pom.xml index 9e43dfe08959..d85fc8732d7f 100644 --- a/plugin/trino-phoenix5/pom.xml +++ b/plugin/trino-phoenix5/pom.xml @@ -173,6 +173,12 @@ + + io.airlift + junit-extensions + test + + io.airlift testing @@ -361,6 +367,12 @@ test + + org.junit.jupiter + junit-jupiter-api + test + + org.junit.jupiter junit-jupiter-engine diff --git a/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixConfig.java b/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixConfig.java index 14be50d711b8..0a0400dff163 100644 --- a/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixConfig.java +++ b/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixConfig.java @@ -14,7 +14,7 @@ package io.trino.plugin.phoenix5; import com.google.common.collect.ImmutableMap; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.nio.file.Files; diff --git a/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixPlugin.java b/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixPlugin.java index a54f168d1131..274fa7cb42fb 100644 --- a/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixPlugin.java +++ b/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixPlugin.java @@ -17,7 +17,7 @@ import io.trino.spi.Plugin; import io.trino.spi.connector.ConnectorFactory; import io.trino.testing.TestingConnectorContext; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static com.google.common.collect.Iterables.getOnlyElement; diff --git a/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixSplit.java b/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixSplit.java index 8820ad988d5a..f9311bc48047 100644 --- a/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixSplit.java +++ b/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixSplit.java @@ -20,7 +20,7 @@ import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.util.Bytes; import org.apache.phoenix.mapreduce.PhoenixInputSplit; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.List; diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestBrokerQueries.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestBrokerQueries.java index 668a6c33d775..4f43d5456509 100644 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestBrokerQueries.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestBrokerQueries.java @@ -24,8 +24,7 @@ import org.apache.pinot.common.response.broker.ResultTable; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.util.List; @@ -50,7 +49,7 @@ public class TestBrokerQueries private static final ResultTable RESULT_TABLE; private static final int LIMIT_FOR_BROKER_QUERIES = 2; - private PinotClient testingPinotClient; + private final PinotClient testingPinotClient; static { DATA_SCHEMA = new DataSchema(new String[] {"col_1", "col_2", "col_3"}, new ColumnDataType[] {STRING, LONG, STRING}); @@ -63,9 +62,8 @@ public class TestBrokerQueries RESPONSE.setNumDocsScanned(1); } - @BeforeClass - public void setup() - throws Exception + public TestBrokerQueries() + throws IOException { testingPinotClient = new MockPinotClient(pinotConfig, getTestingMetadata(), RESPONSE.toJsonString()); } diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestDynamicTable.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestDynamicTable.java index cfbfac8dd048..00fdba82ba5f 100755 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestDynamicTable.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestDynamicTable.java @@ -24,7 +24,7 @@ import io.trino.spi.predicate.Range; import io.trino.spi.predicate.TupleDomain; import io.trino.spi.predicate.ValueSet; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.List; diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestInstance.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestInstance.java index d93873c5779f..610d8cce0580 100644 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestInstance.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestInstance.java @@ -15,7 +15,7 @@ import org.apache.helix.model.InstanceConfig; import org.apache.pinot.core.transport.ServerInstance; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static org.testng.Assert.assertEquals; diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotClient.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotClient.java index 11f5890e7320..fcdec034438d 100755 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotClient.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotClient.java @@ -24,7 +24,7 @@ import io.trino.plugin.pinot.auth.none.PinotEmptyAuthenticationProvider; import io.trino.plugin.pinot.client.IdentityPinotHostMapper; import io.trino.plugin.pinot.client.PinotClient; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.concurrent.TimeUnit; diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotColumnHandle.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotColumnHandle.java index e7ee4f70acdd..f2056d4d493f 100755 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotColumnHandle.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotColumnHandle.java @@ -14,7 +14,7 @@ package io.trino.plugin.pinot; import io.airlift.testing.EquivalenceTester; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.plugin.pinot.MetadataUtil.COLUMN_CODEC; import static io.trino.spi.type.BigintType.BIGINT; diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotConfig.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotConfig.java index 6101ecf4f111..92ac07b06e20 100755 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotConfig.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotConfig.java @@ -17,7 +17,7 @@ import io.airlift.configuration.testing.ConfigAssertions; import io.airlift.units.DataSize; import io.airlift.units.Duration; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Map; import java.util.concurrent.TimeUnit; diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotGrpcServerQueryClientConfig.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotGrpcServerQueryClientConfig.java index 5d109eec9879..09690f4705bf 100644 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotGrpcServerQueryClientConfig.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotGrpcServerQueryClientConfig.java @@ -17,7 +17,7 @@ import io.airlift.configuration.testing.ConfigAssertions; import io.airlift.units.DataSize; import io.trino.plugin.pinot.client.PinotGrpcServerQueryClientConfig; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Map; diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotGrpcServerQueryClientTlsConfig.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotGrpcServerQueryClientTlsConfig.java index 27ce338851ba..97dfd5754d75 100644 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotGrpcServerQueryClientTlsConfig.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotGrpcServerQueryClientTlsConfig.java @@ -17,7 +17,7 @@ import com.google.inject.ConfigurationException; import io.airlift.configuration.testing.ConfigAssertions; import io.trino.plugin.pinot.client.PinotGrpcServerQueryClientTlsConfig; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.FileWriter; import java.io.IOException; diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotLegacyServerQueryClientConfig.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotLegacyServerQueryClientConfig.java index 5d0dc416e7c5..f4a49ed6d27a 100644 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotLegacyServerQueryClientConfig.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotLegacyServerQueryClientConfig.java @@ -16,7 +16,7 @@ import com.google.common.collect.ImmutableMap; import io.airlift.configuration.testing.ConfigAssertions; import io.trino.plugin.pinot.client.PinotLegacyServerQueryClientConfig; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Map; diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotMetadata.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotMetadata.java index 7249cb5e6301..cf9f4fc2feb2 100755 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotMetadata.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotMetadata.java @@ -18,7 +18,7 @@ import io.trino.spi.connector.ConnectorSession; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.type.TestingTypeManager; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.Optional; diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotSessionProperties.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotSessionProperties.java index 16bf27fa4ae7..c19af44912df 100755 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotSessionProperties.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotSessionProperties.java @@ -16,18 +16,22 @@ import io.airlift.units.Duration; import io.trino.spi.connector.ConnectorSession; import io.trino.testing.TestingConnectorSession; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.concurrent.TimeUnit; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.testng.Assert.assertEquals; public class TestPinotSessionProperties { - @Test(expectedExceptions = IllegalArgumentException.class) + @Test public void testInvalidNumSegmentSplits() { - new PinotConfig().setSegmentsPerSplit(-3); + assertThatThrownBy(() -> { + new PinotConfig().setSegmentsPerSplit(-3); + }) + .isInstanceOf(IllegalArgumentException.class); } @Test diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotSplitManager.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotSplitManager.java index d47f33eb8219..69849d1f1199 100755 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotSplitManager.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotSplitManager.java @@ -22,7 +22,7 @@ import io.trino.spi.connector.SchemaTableName; import io.trino.spi.predicate.TupleDomain; import io.trino.testing.TestingConnectorSession; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.time.Instant; import java.util.ArrayList; @@ -37,6 +37,7 @@ import static io.trino.spi.type.TimeZoneKey.UTC_KEY; import static java.lang.String.format; import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @@ -58,12 +59,15 @@ public void testSplitsBroker() assertSplits(splits, 1, BROKER); } - @Test(expectedExceptions = PinotSplitManager.QueryNotAdequatelyPushedDownException.class) + @Test public void testBrokerNonShortQuery() { - PinotTableHandle pinotTableHandle = new PinotTableHandle(realtimeOnlyTable.getSchemaName(), realtimeOnlyTable.getTableName()); - List splits = getSplitsHelper(pinotTableHandle, 1, true); - assertSplits(splits, 1, BROKER); + assertThatThrownBy(() -> { + PinotTableHandle pinotTableHandle = new PinotTableHandle(realtimeOnlyTable.getSchemaName(), realtimeOnlyTable.getTableName()); + List splits = getSplitsHelper(pinotTableHandle, 1, true); + assertSplits(splits, 1, BROKER); + }) + .isInstanceOf(PinotSplitManager.QueryNotAdequatelyPushedDownException.class); } @Test diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotTableHandle.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotTableHandle.java index b4d992f571ba..b80370f85552 100755 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotTableHandle.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotTableHandle.java @@ -15,7 +15,7 @@ import io.airlift.json.JsonCodec; import io.airlift.testing.EquivalenceTester; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.airlift.json.JsonCodec.jsonCodec; import static org.testng.Assert.assertEquals; diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/TestPinotAuthenticationTypeConfig.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/TestPinotAuthenticationTypeConfig.java index d4032ab3cebb..5b9ef2e5ddda 100644 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/TestPinotAuthenticationTypeConfig.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/TestPinotAuthenticationTypeConfig.java @@ -14,7 +14,7 @@ package io.trino.plugin.pinot.auth; import com.google.common.collect.ImmutableMap; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Map; diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/none/TestPinotEmptyAuthenticationProvider.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/none/TestPinotEmptyAuthenticationProvider.java index 18ab1701f557..35f4923b9fe3 100644 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/none/TestPinotEmptyAuthenticationProvider.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/none/TestPinotEmptyAuthenticationProvider.java @@ -13,7 +13,7 @@ */ package io.trino.plugin.pinot.auth.none; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Optional; diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/TestPinotPasswordAuthenticationProvider.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/TestPinotPasswordAuthenticationProvider.java index 501bbb2289db..4d52831515ed 100644 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/TestPinotPasswordAuthenticationProvider.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/TestPinotPasswordAuthenticationProvider.java @@ -13,7 +13,7 @@ */ package io.trino.plugin.pinot.auth.password; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Optional; diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/inline/TestPinotPasswordBrokerAuthenticationConfig.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/inline/TestPinotPasswordBrokerAuthenticationConfig.java index 211513479a9f..15f0c08ff7e7 100644 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/inline/TestPinotPasswordBrokerAuthenticationConfig.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/inline/TestPinotPasswordBrokerAuthenticationConfig.java @@ -14,7 +14,7 @@ package io.trino.plugin.pinot.auth.password.inline; import com.google.common.collect.ImmutableMap; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Map; diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/inline/TestPinotPasswordControllerAuthenticationConfig.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/inline/TestPinotPasswordControllerAuthenticationConfig.java index c204899ac23f..3e5f4c0348c0 100644 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/inline/TestPinotPasswordControllerAuthenticationConfig.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/inline/TestPinotPasswordControllerAuthenticationConfig.java @@ -14,7 +14,7 @@ package io.trino.plugin.pinot.auth.password.inline; import com.google.common.collect.ImmutableMap; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Map; diff --git a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestJoinReorderingWithJoinPushdown.java b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestJoinReorderingWithJoinPushdown.java index 472b805444bf..d674053690f8 100644 --- a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestJoinReorderingWithJoinPushdown.java +++ b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestJoinReorderingWithJoinPushdown.java @@ -19,7 +19,7 @@ import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.DistributedQueryRunner; import io.trino.testing.QueryRunner; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.Map; diff --git a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlConfig.java b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlConfig.java index 8ca80f04f058..aade2bde29e7 100644 --- a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlConfig.java +++ b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlConfig.java @@ -14,7 +14,7 @@ package io.trino.plugin.postgresql; import com.google.common.collect.ImmutableMap; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Map; diff --git a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlPlugin.java b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlPlugin.java index 606cbd320da0..b1e42e783167 100644 --- a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlPlugin.java +++ b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlPlugin.java @@ -17,7 +17,7 @@ import io.trino.spi.Plugin; import io.trino.spi.connector.ConnectorFactory; import io.trino.testing.TestingConnectorContext; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static com.google.common.collect.Iterables.getOnlyElement; diff --git a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestRemoteQueryCommentLogging.java b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestRemoteQueryCommentLogging.java index a78a3cad2517..1c206794fae0 100644 --- a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestRemoteQueryCommentLogging.java +++ b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestRemoteQueryCommentLogging.java @@ -18,14 +18,16 @@ import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.DistributedQueryRunner; import io.trino.testing.QueryRunner; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.plugin.postgresql.PostgreSqlQueryRunner.createPostgreSqlQueryRunner; import static io.trino.tpch.TpchTable.CUSTOMER; import static io.trino.tpch.TpchTable.NATION; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -@Test(singleThreaded = true) +@Execution(SAME_THREAD) public class TestRemoteQueryCommentLogging extends AbstractTestQueryFramework { diff --git a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestRemoteQueryCommentLoggingDisabledByDefault.java b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestRemoteQueryCommentLoggingDisabledByDefault.java index 0fb0ec7a5eb5..d20be0295f68 100644 --- a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestRemoteQueryCommentLoggingDisabledByDefault.java +++ b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestRemoteQueryCommentLoggingDisabledByDefault.java @@ -18,14 +18,16 @@ import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.DistributedQueryRunner; import io.trino.testing.QueryRunner; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.plugin.postgresql.PostgreSqlQueryRunner.createPostgreSqlQueryRunner; import static io.trino.tpch.TpchTable.CUSTOMER; import static io.trino.tpch.TpchTable.NATION; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -@Test(singleThreaded = true) +@Execution(SAME_THREAD) public class TestRemoteQueryCommentLoggingDisabledByDefault extends AbstractTestQueryFramework { diff --git a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestTestingPostgreSqlServer.java b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestTestingPostgreSqlServer.java index 117aba46c10f..694b09340ca1 100644 --- a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestTestingPostgreSqlServer.java +++ b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestTestingPostgreSqlServer.java @@ -14,9 +14,11 @@ package io.trino.plugin.postgresql; import io.trino.plugin.jdbc.RemoteDatabaseEvent; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.parallel.Execution; import java.sql.Connection; import java.sql.DriverManager; @@ -33,20 +35,18 @@ import static java.util.concurrent.Executors.newCachedThreadPool; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestTestingPostgreSqlServer { private final ExecutorService threadPool = newCachedThreadPool(daemonThreadsNamed("TestTestingPostgreSqlServer-%d")); - private TestingPostgreSqlServer postgreSqlServer; + private final TestingPostgreSqlServer postgreSqlServer = new TestingPostgreSqlServer(); - @BeforeClass - public void setUp() - { - postgreSqlServer = new TestingPostgreSqlServer(); - } - - @AfterClass(alwaysRun = true) + @AfterAll public void tearDown() throws Exception { @@ -69,7 +69,8 @@ public void testCapturingSuccessfulStatement() assertEventually(() -> assertThat(postgreSqlServer.getRemoteDatabaseEvents()).contains(event)); } - @Test(timeOut = 60_000) + @Test + @Timeout(60) public void testCapturingCancelledStatement() throws Exception { diff --git a/plugin/trino-prometheus/pom.xml b/plugin/trino-prometheus/pom.xml index f0b5b170b17b..09d1c5dcecfd 100644 --- a/plugin/trino-prometheus/pom.xml +++ b/plugin/trino-prometheus/pom.xml @@ -146,6 +146,12 @@ test + + io.airlift + junit-extensions + test + + io.airlift testing @@ -214,6 +220,12 @@ 4.4.16 test + + org.aspectj + aspectjweaver + 1.9.9.1 + test + org.assertj @@ -227,6 +239,12 @@ test + + org.junit.jupiter + junit-jupiter-api + test + + org.junit.jupiter junit-jupiter-engine diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusBasicAuth.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusBasicAuth.java index ce907436d263..af5eaec6b444 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusBasicAuth.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusBasicAuth.java @@ -17,7 +17,7 @@ import io.airlift.units.Duration; import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.QueryRunner; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.plugin.prometheus.MetadataUtil.METRIC_CODEC; import static io.trino.plugin.prometheus.PrometheusQueryRunner.createPrometheusQueryRunner; diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusCaseInsensitiveNameMatching.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusCaseInsensitiveNameMatching.java index f584a68a99c4..bad892836dfd 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusCaseInsensitiveNameMatching.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusCaseInsensitiveNameMatching.java @@ -14,9 +14,10 @@ package io.trino.plugin.prometheus; import io.trino.spi.connector.SchemaTableName; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.Optional; @@ -26,30 +27,26 @@ import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.util.Locale.ENGLISH; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestPrometheusCaseInsensitiveNameMatching { - private PrometheusHttpServer prometheusHttpServer; private static final String DEFAULT_SCHEMA = "default"; private static final String UPPER_CASE_METRIC = "UpperCase-Metric"; - @BeforeClass - public void setUp() - { - prometheusHttpServer = new PrometheusHttpServer(); - } + private final PrometheusHttpServer prometheusHttpServer = new PrometheusHttpServer(); - @AfterClass(alwaysRun = true) + @AfterAll public void tearDown() { - if (prometheusHttpServer != null) { - prometheusHttpServer.stop(); - prometheusHttpServer = null; - } + prometheusHttpServer.stop(); } @Test diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusColumnHandle.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusColumnHandle.java index a8c49c2bd863..766a386adaab 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusColumnHandle.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusColumnHandle.java @@ -14,7 +14,7 @@ package io.trino.plugin.prometheus; import io.airlift.testing.EquivalenceTester; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.plugin.prometheus.MetadataUtil.COLUMN_CODEC; import static io.trino.spi.type.BigintType.BIGINT; diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusConnectorConfig.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusConnectorConfig.java index ac8e3c5bee46..fcef75b51264 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusConnectorConfig.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusConnectorConfig.java @@ -16,7 +16,7 @@ import com.google.common.collect.ImmutableMap; import com.google.inject.ConfigurationException; import io.airlift.units.Duration; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.File; import java.net.URI; diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusIntegration.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusIntegration.java index f7a5569ee2d3..9e771bb78791 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusIntegration.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusIntegration.java @@ -21,7 +21,7 @@ import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.MaterializedResult; import io.trino.testing.QueryRunner; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.concurrent.TimeUnit; diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryMatrixResponseParse.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryMatrixResponseParse.java index cc1c2eeb9ad0..01d11ee9e1be 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryMatrixResponseParse.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryMatrixResponseParse.java @@ -15,9 +15,11 @@ import com.google.common.io.Resources; import io.trino.spi.TrinoException; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.IOException; import java.io.InputStream; @@ -26,11 +28,14 @@ import static java.time.Instant.ofEpochMilli; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; -@Test(singleThreaded = true) +@TestInstance(PER_METHOD) +@Execution(SAME_THREAD) public class TestPrometheusQueryMatrixResponseParse { private InputStream promMatrixResponse; @@ -75,7 +80,7 @@ public void verifyOnErrorResponse() .hasMessageContaining("Unable to parse Prometheus response: error bad_data invalid parameter 'query': parse error at char 4: bad duration syntax"); } - @BeforeMethod + @BeforeEach public void setUp() throws Exception { @@ -88,7 +93,7 @@ public void setUp() this.promErrorResponse = promErrorResponse.openStream(); } - @AfterMethod(alwaysRun = true) + @AfterEach public void tearDown() throws Exception { diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryScalarResponseParse.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryScalarResponseParse.java index d2e4d6de62f7..74d2a57b871c 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryScalarResponseParse.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryScalarResponseParse.java @@ -14,9 +14,7 @@ package io.trino.plugin.prometheus; import com.google.common.io.Resources; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.io.InputStream; @@ -28,56 +26,52 @@ import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; -@Test(singleThreaded = true) // see @BeforeMethod public class TestPrometheusQueryScalarResponseParse { - private InputStream promVectorResponse; - @Test public void trueStatusOnSuccessResponse() throws IOException { - assertTrue(new PrometheusQueryResponseParse(promVectorResponse).getStatus()); + try (InputStream promVectorResponse = openStream()) { + assertTrue(new PrometheusQueryResponseParse(promVectorResponse).getStatus()); + } } @Test public void verifyMetricPropertiesResponse() throws IOException { - List results = new PrometheusQueryResponseParse(promVectorResponse).getResults(); - assertEquals(results.get(0).getMetricHeader().get("__name__"), "scalar"); + try (InputStream promVectorResponse = openStream()) { + List results = new PrometheusQueryResponseParse(promVectorResponse).getResults(); + assertEquals(results.get(0).getMetricHeader().get("__name__"), "scalar"); + } } @Test public void verifyMetricTimestampResponse() throws IOException { - List results = new PrometheusQueryResponseParse(promVectorResponse).getResults(); - assertEquals(results.get(0).getTimeSeriesValues().getValues().get(0).getTimestamp(), ofEpochMilli(1566306405073L)); + try (InputStream promVectorResponse = openStream()) { + List results = new PrometheusQueryResponseParse(promVectorResponse).getResults(); + assertEquals(results.get(0).getTimeSeriesValues().getValues().get(0).getTimestamp(), ofEpochMilli(1566306405073L)); + } } @Test public void verifyMetricValueResponse() throws IOException { - List results = new PrometheusQueryResponseParse(promVectorResponse).getResults(); - assertEquals(results.get(0).getTimeSeriesValues().getValues().get(0).getValue(), "1"); + try (InputStream promVectorResponse = openStream()) { + List results = new PrometheusQueryResponseParse(promVectorResponse).getResults(); + assertEquals(results.get(0).getTimeSeriesValues().getValues().get(0).getValue(), "1"); + } } - @BeforeMethod - public void setUp() - throws Exception + private static InputStream openStream() + throws IOException { - URL promMatrixResponse = Resources.getResource(getClass(), "/prometheus-data/up_scalar_response.json"); + URL promMatrixResponse = Resources.getResource(TestPrometheusQueryScalarResponseParse.class, "/prometheus-data/up_scalar_response.json"); assertNotNull(promMatrixResponse, "metadataUrl is null"); - this.promVectorResponse = promMatrixResponse.openStream(); - } - - @AfterMethod(alwaysRun = true) - public void tearDown() - throws Exception - { - promVectorResponse.close(); - promVectorResponse = null; + return promMatrixResponse.openStream(); } } diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryVectorResponseParse.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryVectorResponseParse.java index 15b1f7a51629..0be201d67ae0 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryVectorResponseParse.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryVectorResponseParse.java @@ -14,9 +14,7 @@ package io.trino.plugin.prometheus; import com.google.common.io.Resources; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.io.InputStream; @@ -28,56 +26,52 @@ import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; -@Test(singleThreaded = true) // see @BeforeMethod public class TestPrometheusQueryVectorResponseParse { - private InputStream promVectorResponse; - @Test public void trueStatusOnSuccessResponse() throws IOException { - assertTrue(new PrometheusQueryResponseParse(promVectorResponse).getStatus()); + try (InputStream promVectorResponse = openStream()) { + assertTrue(new PrometheusQueryResponseParse(promVectorResponse).getStatus()); + } } @Test public void verifyMetricPropertiesResponse() throws IOException { - List results = new PrometheusQueryResponseParse(promVectorResponse).getResults(); - assertEquals(results.get(0).getMetricHeader().get("__name__"), "up"); + try (InputStream promVectorResponse = openStream()) { + List results = new PrometheusQueryResponseParse(promVectorResponse).getResults(); + assertEquals(results.get(0).getMetricHeader().get("__name__"), "up"); + } } @Test public void verifyMetricTimestampResponse() throws IOException { - List results = new PrometheusQueryResponseParse(promVectorResponse).getResults(); - assertEquals(results.get(0).getTimeSeriesValues().getValues().get(0).getTimestamp(), ofEpochMilli(1565889995668L)); + try (InputStream promVectorResponse = openStream()) { + List results = new PrometheusQueryResponseParse(promVectorResponse).getResults(); + assertEquals(results.get(0).getTimeSeriesValues().getValues().get(0).getTimestamp(), ofEpochMilli(1565889995668L)); + } } @Test public void verifyMetricValueResponse() throws IOException { - List results = new PrometheusQueryResponseParse(promVectorResponse).getResults(); - assertEquals(results.get(0).getTimeSeriesValues().getValues().get(0).getValue(), "1"); + try (InputStream promVectorResponse = openStream()) { + List results = new PrometheusQueryResponseParse(promVectorResponse).getResults(); + assertEquals(results.get(0).getTimeSeriesValues().getValues().get(0).getValue(), "1"); + } } - @BeforeMethod - public void setUp() - throws Exception + private static InputStream openStream() + throws IOException { - URL promMatrixResponse = Resources.getResource(getClass(), "/prometheus-data/up_vector_response.json"); + URL promMatrixResponse = Resources.getResource(TestPrometheusQueryVectorResponseParse.class, "/prometheus-data/up_vector_response.json"); assertNotNull(promMatrixResponse, "metadataUrl is null"); - this.promVectorResponse = promMatrixResponse.openStream(); - } - - @AfterMethod(alwaysRun = true) - public void tearDown() - throws Exception - { - promVectorResponse.close(); - promVectorResponse = null; + return promMatrixResponse.openStream(); } } diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusRecordSet.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusRecordSet.java index cdac316251cc..fa0f052199a7 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusRecordSet.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusRecordSet.java @@ -19,9 +19,10 @@ import io.trino.spi.connector.RecordCursor; import io.trino.spi.connector.RecordSet; import io.trino.spi.type.DoubleType; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.time.Instant; import java.util.ArrayList; @@ -36,13 +37,17 @@ import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.time.Instant.ofEpochMilli; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestPrometheusRecordSet { - private PrometheusHttpServer prometheusHttpServer; - private String dataUri; + private final PrometheusHttpServer prometheusHttpServer = new PrometheusHttpServer(); + private final String dataUri = prometheusHttpServer.resolve("/prometheus-data/up_matrix_response.json").toString(); @Test public void testCursorSimple() @@ -93,19 +98,9 @@ public void testCursorSimple() } } - @BeforeClass - public void setUp() - { - prometheusHttpServer = new PrometheusHttpServer(); - dataUri = prometheusHttpServer.resolve("/prometheus-data/up_matrix_response.json").toString(); - } - - @AfterClass(alwaysRun = true) + @AfterAll public void tearDown() { - if (prometheusHttpServer != null) { - prometheusHttpServer.stop(); - prometheusHttpServer = null; - } + prometheusHttpServer.stop(); } } diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusRecordSetProvider.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusRecordSetProvider.java index 65613688c21d..44ea49952aaf 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusRecordSetProvider.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusRecordSetProvider.java @@ -20,9 +20,10 @@ import io.trino.spi.connector.RecordCursor; import io.trino.spi.connector.RecordSet; import io.trino.spi.type.DoubleType; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.time.Instant; import java.util.LinkedHashMap; @@ -35,30 +36,23 @@ import static io.trino.testing.TestingConnectorSession.SESSION; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.time.Instant.ofEpochMilli; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestPrometheusRecordSetProvider { - private PrometheusHttpServer prometheusHttpServer; - private String dataUri; - private PrometheusClient client; + private final PrometheusHttpServer prometheusHttpServer = new PrometheusHttpServer(); + private final String dataUri = prometheusHttpServer.resolve("/prometheus-data/up_matrix_response.json").toString(); + private final PrometheusClient client = new PrometheusClient(new PrometheusConnectorConfig(), METRIC_CODEC, TESTING_TYPE_MANAGER); - @BeforeClass - public void setUp() - { - prometheusHttpServer = new PrometheusHttpServer(); - dataUri = prometheusHttpServer.resolve("/prometheus-data/up_matrix_response.json").toString(); - client = new PrometheusClient(new PrometheusConnectorConfig(), METRIC_CODEC, TESTING_TYPE_MANAGER); - } - - @AfterClass(alwaysRun = true) + @AfterAll public void tearDown() { - if (prometheusHttpServer != null) { - prometheusHttpServer.stop(); - prometheusHttpServer = null; - } + prometheusHttpServer.stop(); } @Test diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusSplit.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusSplit.java index 7f347c3ed1cb..11476f1078b6 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusSplit.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusSplit.java @@ -28,9 +28,11 @@ import io.trino.spi.predicate.TupleDomain; import io.trino.spi.predicate.ValueSet; import org.apache.http.NameValuePair; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.math.BigDecimal; import java.net.URI; @@ -59,28 +61,24 @@ import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.apache.http.client.utils.URLEncodedUtils.parse; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotEquals; import static org.testng.Assert.assertTrue; -@Test(singleThreaded = true) +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestPrometheusSplit { - private PrometheusHttpServer prometheusHttpServer; + private final PrometheusHttpServer prometheusHttpServer = new PrometheusHttpServer(); private final PrometheusSplit split = new PrometheusSplit("http://127.0.0.1/test.file"); private static final int NUMBER_MORE_THAN_EXPECTED_NUMBER_SPLITS = 100; - @BeforeClass - public void setUp() - { - prometheusHttpServer = new PrometheusHttpServer(); - } - - @AfterClass(alwaysRun = true) + @AfterAll public void tearDown() { prometheusHttpServer.stop(); - prometheusHttpServer = null; } @Test @@ -316,7 +314,8 @@ public void testPredicatePushDownLowerBoundDirect() assertEquals(predicateTimes.getPredicateLowerTimeBound().orElseThrow(), expected); } - @Test(enabled = false) + @Test + @Disabled public void testPredicatePushDownSetsLowerBoundOnly() { long predicateLowValue = 1568638171999L - 600000L; diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusTable.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusTable.java index aa1ea1aa43c6..69d490c2b40a 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusTable.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusTable.java @@ -16,7 +16,7 @@ import com.google.common.collect.ImmutableList; import io.trino.spi.connector.ColumnMetadata; import io.trino.spi.type.DoubleType; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.plugin.prometheus.MetadataUtil.TABLE_CODEC; import static io.trino.plugin.prometheus.MetadataUtil.varcharMapType; diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusTableHandle.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusTableHandle.java index a4e469dc2d02..64401552011e 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusTableHandle.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusTableHandle.java @@ -15,7 +15,7 @@ import io.airlift.json.JsonCodec; import io.airlift.testing.EquivalenceTester; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.airlift.json.JsonCodec.jsonCodec; import static org.testng.Assert.assertEquals; diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusTimestampDeserializer.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusTimestampDeserializer.java index d8ff21b8acd0..1ecdb62192ba 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusTimestampDeserializer.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusTimestampDeserializer.java @@ -13,7 +13,7 @@ */ package io.trino.plugin.prometheus; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.time.Instant; diff --git a/plugin/trino-raptor-legacy/pom.xml b/plugin/trino-raptor-legacy/pom.xml index a44d92ce6bff..646f7ed4caa7 100644 --- a/plugin/trino-raptor-legacy/pom.xml +++ b/plugin/trino-raptor-legacy/pom.xml @@ -204,6 +204,12 @@ test + + io.airlift + junit-extensions + test + + io.airlift testing @@ -284,6 +290,12 @@ test + + org.junit.jupiter + junit-jupiter-api + test + + org.junit.jupiter junit-jupiter-engine diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorBucketFunction.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorBucketFunction.java index 5bbdd3fa1dcd..16342723cd68 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorBucketFunction.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorBucketFunction.java @@ -18,7 +18,7 @@ import io.trino.spi.block.Block; import io.trino.spi.connector.BucketFunction; import io.trino.spi.type.Type; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.block.BlockAssertions.createIntsBlock; import static io.trino.block.BlockAssertions.createLongsBlock; diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorConnector.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorConnector.java index 2c16880f8715..8e68b678b727 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorConnector.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorConnector.java @@ -44,9 +44,11 @@ import io.trino.testing.TestingNodeManager; import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.Jdbi; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.nio.file.Files; @@ -69,11 +71,14 @@ import static io.trino.testing.TestingPageSinkId.TESTING_PAGE_SINK_ID; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static io.trino.util.DateTimeUtils.parseDate; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; -@Test(singleThreaded = true) +@TestInstance(PER_METHOD) +@Execution(SAME_THREAD) public class TestRaptorConnector { private static final ConnectorSession SESSION = TestingConnectorSession.builder() @@ -85,7 +90,7 @@ public class TestRaptorConnector private File dataDir; private RaptorConnector connector; - @BeforeMethod + @BeforeEach public void setup() throws Exception { @@ -118,7 +123,7 @@ public void setup() dbi); } - @AfterMethod(alwaysRun = true) + @AfterEach public void tearDown() throws Exception { diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorPlugin.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorPlugin.java index d72d31372c65..3db165638310 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorPlugin.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorPlugin.java @@ -17,7 +17,7 @@ import io.trino.spi.Plugin; import io.trino.spi.connector.ConnectorFactory; import io.trino.testing.TestingConnectorContext; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.File; import java.nio.file.Files; diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestBackupConfig.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestBackupConfig.java index 9c8084fdaa35..db9ce39cdabf 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestBackupConfig.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestBackupConfig.java @@ -15,7 +15,7 @@ import com.google.common.collect.ImmutableMap; import io.airlift.units.Duration; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Map; diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestBackupManager.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestBackupManager.java index 672b64e4582e..7520db6e929e 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestBackupManager.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestBackupManager.java @@ -16,9 +16,11 @@ import io.trino.plugin.raptor.legacy.storage.BackupStats; import io.trino.plugin.raptor.legacy.storage.FileStorageService; import io.trino.spi.TrinoException; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.io.IOException; @@ -42,11 +44,14 @@ import static java.util.UUID.randomUUID; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import static org.testng.FileAssert.assertFile; -@Test(singleThreaded = true) +@TestInstance(PER_METHOD) +@Execution(SAME_THREAD) public class TestBackupManager { private static final UUID FAILURE_UUID = randomUUID(); @@ -57,7 +62,7 @@ public class TestBackupManager private FileStorageService storageService; private BackupManager backupManager; - @BeforeMethod + @BeforeEach public void setup() throws IOException { @@ -73,7 +78,7 @@ public void setup() backupManager = new BackupManager(Optional.of(backupStore), storageService, 5); } - @AfterMethod(alwaysRun = true) + @AfterEach public void tearDown() throws Exception { diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestFileBackupConfig.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestFileBackupConfig.java index b9fc8767b54d..a08e89f8529f 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestFileBackupConfig.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestFileBackupConfig.java @@ -14,7 +14,7 @@ package io.trino.plugin.raptor.legacy.backup; import com.google.common.collect.ImmutableMap; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.File; import java.util.Map; diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestHttpBackupConfig.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestHttpBackupConfig.java index f7a123f9816e..db650c9b807f 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestHttpBackupConfig.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestHttpBackupConfig.java @@ -14,7 +14,7 @@ package io.trino.plugin.raptor.legacy.backup; import com.google.common.collect.ImmutableMap; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.net.URI; import java.util.Map; diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestAssignmentLimiter.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestAssignmentLimiter.java index 8f347b8c8ab0..f319ddf6184a 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestAssignmentLimiter.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestAssignmentLimiter.java @@ -17,7 +17,7 @@ import io.airlift.testing.TestingTicker; import io.airlift.units.Duration; import io.trino.spi.ErrorCodeSupplier; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.plugin.raptor.legacy.RaptorErrorCode.RAPTOR_REASSIGNMENT_DELAY; import static io.trino.plugin.raptor.legacy.RaptorErrorCode.RAPTOR_REASSIGNMENT_THROTTLE; diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestDatabaseConfig.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestDatabaseConfig.java index 0de9385fe71a..b9f8daa3e5ff 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestDatabaseConfig.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestDatabaseConfig.java @@ -14,7 +14,7 @@ package io.trino.plugin.raptor.legacy.metadata; import com.google.common.collect.ImmutableMap; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Map; diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestDatabaseShardManager.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestDatabaseShardManager.java index 8545f6980335..64a512fbf559 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestDatabaseShardManager.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestDatabaseShardManager.java @@ -36,9 +36,11 @@ import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.Jdbi; import org.jdbi.v3.core.result.ResultIterator; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.io.IOException; @@ -90,10 +92,13 @@ import static java.time.ZoneOffset.UTC; import static java.util.concurrent.TimeUnit.DAYS; import static java.util.stream.Collectors.toSet; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; -@Test(singleThreaded = true) +@TestInstance(PER_METHOD) +@Execution(SAME_THREAD) public class TestDatabaseShardManager { private Jdbi dbi; @@ -101,7 +106,7 @@ public class TestDatabaseShardManager private File dataDir; private ShardManager shardManager; - @BeforeMethod + @BeforeEach public void setup() throws Exception { @@ -112,7 +117,7 @@ public void setup() shardManager = createShardManager(dbi); } - @AfterMethod(alwaysRun = true) + @AfterEach public void teardown() throws IOException { diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestH2DatabaseConfig.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestH2DatabaseConfig.java index dfffea9e4f3f..8cdd7155c491 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestH2DatabaseConfig.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestH2DatabaseConfig.java @@ -14,7 +14,7 @@ package io.trino.plugin.raptor.legacy.metadata; import com.google.common.collect.ImmutableMap; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Map; diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestMetadataConfig.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestMetadataConfig.java index fb2fd6c889b1..e8e3ecafc2da 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestMetadataConfig.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestMetadataConfig.java @@ -15,7 +15,7 @@ import com.google.common.collect.ImmutableMap; import io.airlift.units.Duration; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Map; diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestMetadataDao.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestMetadataDao.java index 4f76bef29c41..3745933aae04 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestMetadataDao.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestMetadataDao.java @@ -15,9 +15,11 @@ import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.Jdbi; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Optional; import java.util.OptionalInt; @@ -25,17 +27,20 @@ import static io.trino.plugin.raptor.legacy.DatabaseTesting.createTestingJdbi; import static io.trino.plugin.raptor.legacy.metadata.SchemaDaoUtil.createTablesWithRetry; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; -@Test(singleThreaded = true) +@TestInstance(PER_METHOD) +@Execution(SAME_THREAD) public class TestMetadataDao { private MetadataDao dao; private Handle dummyHandle; - @BeforeMethod + @BeforeEach public void setup() { Jdbi dbi = createTestingJdbi(); @@ -44,7 +49,7 @@ public void setup() createTablesWithRetry(dbi); } - @AfterMethod(alwaysRun = true) + @AfterEach public void tearDown() { dummyHandle.close(); diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestRaptorMetadata.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestRaptorMetadata.java index 873aebddf938..b7c4515aacda 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestRaptorMetadata.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestRaptorMetadata.java @@ -42,9 +42,11 @@ import io.trino.testing.TestingNodeManager; import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.Jdbi; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.Map; @@ -73,14 +75,16 @@ import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; -@Test(singleThreaded = true) +@TestInstance(PER_METHOD) +@Execution(SAME_THREAD) public class TestRaptorMetadata { private static final SchemaTableName DEFAULT_TEST_ORDERS = new SchemaTableName("test", "orders"); @@ -94,7 +98,7 @@ public class TestRaptorMetadata private ShardManager shardManager; private RaptorMetadata metadata; - @BeforeMethod + @BeforeEach public void setupDatabase() { dbi = createTestingJdbi(); @@ -107,7 +111,7 @@ public void setupDatabase() metadata = new RaptorMetadata(dbi, shardManager); } - @AfterMethod(alwaysRun = true) + @AfterEach public void cleanupDatabase() { dummyHandle.close(); @@ -461,48 +465,54 @@ public void testCreateBucketedTableExistingDistribution() assertEquals(getTableDistributionId(tableId), Long.valueOf(1)); } - @Test(expectedExceptions = TrinoException.class, expectedExceptionsMessageRegExp = "Ordering column does not exist: orderdatefoo") + @Test public void testInvalidOrderingColumns() { assertNull(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)); - ConnectorTableMetadata ordersTable = getOrdersTable(ImmutableMap.of(ORDERING_PROPERTY, ImmutableList.of("orderdatefoo"))); - metadata.createTable(SESSION, ordersTable, false); - fail("Expected createTable to fail"); + assertThatThrownBy(() -> metadata.createTable(SESSION, getOrdersTable(ImmutableMap.of(ORDERING_PROPERTY, ImmutableList.of("orderdatefoo"))), false)) + .isInstanceOf(TrinoException.class) + .hasMessage("Ordering column does not exist: orderdatefoo"); } - @Test(expectedExceptions = TrinoException.class, expectedExceptionsMessageRegExp = "Temporal column does not exist: foo") + @Test public void testInvalidTemporalColumn() { assertNull(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)); - ConnectorTableMetadata ordersTable = getOrdersTable(ImmutableMap.of(TEMPORAL_COLUMN_PROPERTY, "foo")); - metadata.createTable(SESSION, ordersTable, false); - fail("Expected createTable to fail"); + assertThatThrownBy(() -> metadata.createTable(SESSION, getOrdersTable(ImmutableMap.of(TEMPORAL_COLUMN_PROPERTY, "foo")), false)) + .isInstanceOf(TrinoException.class) + .hasMessage("Temporal column does not exist: foo"); } - @Test(expectedExceptions = TrinoException.class, expectedExceptionsMessageRegExp = "Temporal column must be of type timestamp or date: orderkey") + @Test public void testInvalidTemporalColumnType() { assertNull(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)); - metadata.createTable(SESSION, getOrdersTable(ImmutableMap.of(TEMPORAL_COLUMN_PROPERTY, "orderkey")), false); + assertThatThrownBy(() -> metadata.createTable(SESSION, getOrdersTable(ImmutableMap.of(TEMPORAL_COLUMN_PROPERTY, "orderkey")), false)) + .isInstanceOf(TrinoException.class) + .hasMessage("Temporal column must be of type timestamp or date: orderkey"); } - @Test(expectedExceptions = TrinoException.class, expectedExceptionsMessageRegExp = "Table with temporal columns cannot be organized") + @Test public void testInvalidTemporalOrganization() { assertNull(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)); - metadata.createTable(SESSION, getOrdersTable(ImmutableMap.of( - TEMPORAL_COLUMN_PROPERTY, "orderdate", - ORGANIZED_PROPERTY, true)), - false); + assertThatThrownBy(() -> metadata.createTable(SESSION, getOrdersTable(ImmutableMap.of( + TEMPORAL_COLUMN_PROPERTY, "orderdate", + ORGANIZED_PROPERTY, true)), + false)) + .isInstanceOf(TrinoException.class) + .hasMessage("Table with temporal columns cannot be organized"); } - @Test(expectedExceptions = TrinoException.class, expectedExceptionsMessageRegExp = "Table organization requires an ordering") + @Test public void testInvalidOrderingOrganization() { assertNull(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)); - metadata.createTable(SESSION, getOrdersTable(ImmutableMap.of(ORGANIZED_PROPERTY, true)), false); + assertThatThrownBy(() -> metadata.createTable(SESSION, getOrdersTable(ImmutableMap.of(ORGANIZED_PROPERTY, true)), false)) + .isInstanceOf(TrinoException.class) + .hasMessage("Table organization requires an ordering"); } @Test @@ -622,18 +632,14 @@ public void testViews() .isEmpty(); } - @Test(expectedExceptions = TrinoException.class, expectedExceptionsMessageRegExp = "View already exists: test\\.test_view") + @Test public void testCreateViewWithoutReplace() { SchemaTableName test = new SchemaTableName("test", "test_view"); - try { - metadata.createView(SESSION, test, testingViewDefinition("test"), false); - } - catch (Exception e) { - fail("should have succeeded"); - } - metadata.createView(SESSION, test, testingViewDefinition("test"), false); + assertThatThrownBy(() -> metadata.createView(SESSION, test, testingViewDefinition("test"), false)) + .isInstanceOf(TrinoException.class) + .hasMessage("View already exists: test.test_view"); } @Test diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestRaptorSplitManager.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestRaptorSplitManager.java index d8d1a1035d23..0d038ef3e243 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestRaptorSplitManager.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestRaptorSplitManager.java @@ -39,9 +39,11 @@ import io.trino.testing.TestingNodeManager; import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.Jdbi; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.IOException; import java.net.URI; @@ -66,9 +68,13 @@ import static java.nio.file.Files.createTempDirectory; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; -@Test(singleThreaded = true) +@TestInstance(PER_METHOD) +@Execution(SAME_THREAD) public class TestRaptorSplitManager { private static final ConnectorTableMetadata TEST_TABLE = tableMetadataBuilder(new SchemaTableName("demo", "test_table")) @@ -85,7 +91,7 @@ public class TestRaptorSplitManager private ShardManager shardManager; private long tableId; - @BeforeMethod + @BeforeEach public void setup() throws Exception { @@ -127,7 +133,7 @@ public void setup() raptorSplitManager = new RaptorSplitManager(connectorId, nodeSupplier, shardManager, false); } - @AfterMethod(alwaysRun = true) + @AfterEach public void teardown() throws IOException { @@ -147,13 +153,17 @@ public void testSanity() assertEquals(splitCount, 4); } - @Test(expectedExceptions = TrinoException.class, expectedExceptionsMessageRegExp = "No host for shard .* found: \\[\\]") + @Test public void testNoHostForShard() { - deleteShardNodes(); - - ConnectorSplitSource splitSource = getSplits(raptorSplitManager, tableHandle); - getSplits(splitSource, 1000); + assertThatThrownBy(() -> { + deleteShardNodes(); + + ConnectorSplitSource splitSource = getSplits(raptorSplitManager, tableHandle); + getSplits(splitSource, 1000); + }) + .isInstanceOf(TrinoException.class) + .hasMessageMatching("No host for shard .* found: \\[\\]"); } @Test @@ -173,14 +183,18 @@ public void testAssignRandomNodeWhenBackupAvailable() assertEquals(getOnlyElement(getOnlyElement(batch).getAddresses()), node.getHostAndPort()); } - @Test(expectedExceptions = TrinoException.class, expectedExceptionsMessageRegExp = "No nodes available to run query") + @Test public void testNoNodes() { - deleteShardNodes(); - - RaptorSplitManager raptorSplitManagerWithBackup = new RaptorSplitManager(new CatalogName("fbraptor"), ImmutableSet::of, shardManager, true); - ConnectorSplitSource splitSource = getSplits(raptorSplitManagerWithBackup, tableHandle); - getSplits(splitSource, 1000); + assertThatThrownBy(() -> { + deleteShardNodes(); + + RaptorSplitManager raptorSplitManagerWithBackup = new RaptorSplitManager(new CatalogName("fbraptor"), ImmutableSet::of, shardManager, true); + ConnectorSplitSource splitSource = getSplits(raptorSplitManagerWithBackup, tableHandle); + getSplits(splitSource, 1000); + }) + .isInstanceOf(TrinoException.class) + .hasMessage("No nodes available to run query"); } private void deleteShardNodes() diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardCleaner.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardCleaner.java index a8575a2f5e5f..e52c35c1b54c 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardCleaner.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardCleaner.java @@ -28,9 +28,11 @@ import org.jdbi.v3.sqlobject.config.RegisterArgumentFactory; import org.jdbi.v3.sqlobject.statement.GetGeneratedKeys; import org.jdbi.v3.sqlobject.statement.SqlUpdate; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.io.IOException; @@ -53,11 +55,14 @@ import static java.util.UUID.randomUUID; import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; -@Test(singleThreaded = true) +@TestInstance(PER_METHOD) +@Execution(SAME_THREAD) public class TestShardCleaner { private Jdbi dbi; @@ -68,7 +73,7 @@ public class TestShardCleaner private TestingTicker ticker; private ShardCleaner cleaner; - @BeforeMethod + @BeforeEach public void setup() throws IOException { @@ -105,7 +110,7 @@ public void setup() config.getMaxCompletedTransactionAge()); } - @AfterMethod(alwaysRun = true) + @AfterEach public void teardown() throws IOException { diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardCleanerConfig.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardCleanerConfig.java index 9c4710d7acaf..92fff624f373 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardCleanerConfig.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardCleanerConfig.java @@ -15,7 +15,7 @@ import com.google.common.collect.ImmutableMap; import io.airlift.units.Duration; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Map; diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardDao.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardDao.java index 485ca7741af2..d5f018e943ee 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardDao.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardDao.java @@ -18,9 +18,11 @@ import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.Jdbi; import org.jdbi.v3.core.statement.UnableToExecuteStatementException; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.sql.SQLException; import java.util.OptionalInt; @@ -32,19 +34,22 @@ import static io.trino.plugin.raptor.legacy.DatabaseTesting.createTestingJdbi; import static io.trino.plugin.raptor.legacy.metadata.SchemaDaoUtil.createTablesWithRetry; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; -@Test(singleThreaded = true) +@TestInstance(PER_METHOD) +@Execution(SAME_THREAD) public class TestShardDao { private TestingShardDao dao; private Jdbi dbi; private Handle dummyHandle; - @BeforeMethod + @BeforeEach public void setup() { dbi = createTestingJdbi(); @@ -53,7 +58,7 @@ public void setup() createTablesWithRetry(dbi); } - @AfterMethod(alwaysRun = true) + @AfterEach public void teardown() { dummyHandle.close(); diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardPredicate.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardPredicate.java index cc8e69d82c0d..109f0e49d0c8 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardPredicate.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardPredicate.java @@ -20,7 +20,7 @@ import io.trino.plugin.raptor.legacy.RaptorColumnHandle; import io.trino.spi.predicate.SortedRangeSet; import io.trino.spi.predicate.TupleDomain; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.airlift.slice.Slices.utf8Slice; import static io.trino.plugin.raptor.legacy.RaptorColumnHandle.bucketNumberColumnHandle; diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/security/TestRaptorFileBasedSecurity.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/security/TestRaptorFileBasedSecurity.java index 25aa8f9e8169..95beca87bb85 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/security/TestRaptorFileBasedSecurity.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/security/TestRaptorFileBasedSecurity.java @@ -19,21 +19,26 @@ import io.trino.spi.security.Identity; import io.trino.testing.QueryRunner; import io.trino.tpch.TpchTable; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import static io.trino.plugin.raptor.legacy.RaptorQueryRunner.createRaptorQueryRunner; import static io.trino.testing.TestingSession.testSessionBuilder; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestRaptorFileBasedSecurity { - private QueryRunner queryRunner; + private final QueryRunner queryRunner; - @BeforeClass - public void setUp() + public TestRaptorFileBasedSecurity() throws Exception { String path = new File(Resources.getResource(getClass(), "security.json").toURI()).getPath(); @@ -44,11 +49,10 @@ public void setUp() ImmutableMap.of("security.config-file", path, "raptor.security", "file")); } - @AfterClass(alwaysRun = true) + @AfterAll public void tearDown() { queryRunner.close(); - queryRunner = null; } @Test @@ -58,11 +62,15 @@ public void testAdminCanRead() queryRunner.execute(admin, "SELECT * FROM orders"); } - @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = ".*Access Denied: Cannot select from table tpch.orders.*") + @Test public void testNonAdminCannotRead() { - Session bob = getSession("bob"); - queryRunner.execute(bob, "SELECT * FROM orders"); + assertThatThrownBy(() -> { + Session bob = getSession("bob"); + queryRunner.execute(bob, "SELECT * FROM orders"); + }) + .isInstanceOf(RuntimeException.class) + .hasMessageMatching(".*Access Denied: Cannot select from table tpch.orders.*"); } private Session getSession(String user) diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/security/TestRaptorReadOnlySecurity.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/security/TestRaptorReadOnlySecurity.java index 92db9439fa4c..5f17c2143a6b 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/security/TestRaptorReadOnlySecurity.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/security/TestRaptorReadOnlySecurity.java @@ -16,33 +16,23 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.trino.testing.QueryRunner; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import static io.trino.plugin.raptor.legacy.RaptorQueryRunner.createRaptorQueryRunner; +import static org.assertj.core.api.Assertions.assertThatThrownBy; public class TestRaptorReadOnlySecurity { - private QueryRunner queryRunner; - - @BeforeClass - public void setUp() - throws Exception - { - queryRunner = createRaptorQueryRunner(ImmutableMap.of(), ImmutableList.of(), false, ImmutableMap.of("raptor.security", "read-only")); - } - - @AfterClass(alwaysRun = true) - public void tearDown() - { - queryRunner.close(); - queryRunner = null; - } - - @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = ".*Access Denied: Cannot create .*") + @Test public void testCannotWrite() + throws Exception { - queryRunner.execute("CREATE TABLE test_create (a bigint, b double, c varchar)"); + try (QueryRunner queryRunner = createRaptorQueryRunner(ImmutableMap.of(), ImmutableList.of(), false, ImmutableMap.of("raptor.security", "read-only"))) { + assertThatThrownBy(() -> { + queryRunner.execute("CREATE TABLE test_create (a bigint, b double, c varchar)"); + }) + .isInstanceOf(RuntimeException.class) + .hasMessageMatching(".*Access Denied: Cannot create .*"); + } } } diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/security/TestRaptorSecurityConfig.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/security/TestRaptorSecurityConfig.java index eab26443f5df..449067454b2f 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/security/TestRaptorSecurityConfig.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/security/TestRaptorSecurityConfig.java @@ -14,7 +14,7 @@ package io.trino.plugin.raptor.legacy.security; import com.google.common.collect.ImmutableMap; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Map; diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestBucketBalancer.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestBucketBalancer.java index 33db3766f939..fbaabf8961b9 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestBucketBalancer.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestBucketBalancer.java @@ -32,9 +32,11 @@ import io.trino.testing.TestingNodeManager; import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.Jdbi; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.net.URI; import java.util.List; @@ -50,9 +52,12 @@ import static io.trino.plugin.raptor.legacy.metadata.TestDatabaseShardManager.createShardManager; import static io.trino.spi.type.BigintType.BIGINT; import static java.util.concurrent.TimeUnit.DAYS; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; -@Test(singleThreaded = true) +@TestInstance(PER_METHOD) +@Execution(SAME_THREAD) public class TestBucketBalancer { private static final List AVAILABLE_WORKERS = ImmutableList.of("node1", "node2", "node3", "node4", "node5"); @@ -64,7 +69,7 @@ public class TestBucketBalancer private MetadataDao metadataDao; private BucketBalancer balancer; - @BeforeMethod + @BeforeEach public void setup() { dbi = createTestingJdbi(); @@ -81,7 +86,7 @@ public void setup() balancer = new BucketBalancer(nodeSupplier, shardManager, true, new Duration(1, DAYS), true, true, "test"); } - @AfterMethod(alwaysRun = true) + @AfterEach public void teardown() { if (dummyHandle != null) { diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestBucketBalancerConfig.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestBucketBalancerConfig.java index f4a2b7edbebe..6b91e65febf6 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestBucketBalancerConfig.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestBucketBalancerConfig.java @@ -15,7 +15,7 @@ import com.google.common.collect.ImmutableMap; import io.airlift.units.Duration; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Map; diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestFileStorageService.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestFileStorageService.java index a3fedd7d949b..c6abfcacd9f3 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestFileStorageService.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestFileStorageService.java @@ -14,9 +14,11 @@ package io.trino.plugin.raptor.legacy.storage; import com.google.common.collect.ImmutableSet; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.io.IOException; @@ -30,19 +32,22 @@ import static java.lang.String.format; import static java.nio.file.Files.createTempDirectory; import static java.util.UUID.randomUUID; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import static org.testng.FileAssert.assertDirectory; import static org.testng.FileAssert.assertFile; -@Test(singleThreaded = true) +@TestInstance(PER_METHOD) +@Execution(SAME_THREAD) public class TestFileStorageService { private Path temporary; private FileStorageService store; - @BeforeMethod + @BeforeEach public void setup() throws IOException { @@ -51,7 +56,7 @@ public void setup() store.start(); } - @AfterMethod(alwaysRun = true) + @AfterEach public void tearDown() throws Exception { diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestMissingShardComparator.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestMissingShardComparator.java index f6edb5b1b8dc..00dfc546ec0a 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestMissingShardComparator.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestMissingShardComparator.java @@ -13,7 +13,7 @@ */ package io.trino.plugin.raptor.legacy.storage; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.plugin.raptor.legacy.storage.ShardRecoveryManager.MissingShardComparator; import static io.trino.plugin.raptor.legacy.storage.ShardRecoveryManager.MissingShardRunnable; diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestOrcFileRewriter.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestOrcFileRewriter.java index 8ca7727d3eb6..9f9da1c14827 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestOrcFileRewriter.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestOrcFileRewriter.java @@ -28,9 +28,10 @@ import io.trino.spi.type.Type; import io.trino.spi.type.TypeId; import io.trino.spi.type.TypeSignatureParameter; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.io.IOException; @@ -59,26 +60,28 @@ import static java.nio.file.Files.createTempDirectory; import static java.nio.file.Files.readAllBytes; import static java.util.UUID.randomUUID; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; -@Test(singleThreaded = true) +@TestInstance(PER_CLASS) +@Execution(SAME_THREAD) public class TestOrcFileRewriter { private static final JsonCodec METADATA_CODEC = jsonCodec(OrcFileMetadata.class); - private Path temporary; + private final Path temporary; - @BeforeClass - public void setup() + public TestOrcFileRewriter() throws IOException { temporary = createTempDirectory(null); } - @AfterClass(alwaysRun = true) + @AfterAll public void tearDown() throws Exception { diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestRaptorStorageManager.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestRaptorStorageManager.java index 458698d9ea38..adce54990608 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestRaptorStorageManager.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestRaptorStorageManager.java @@ -49,9 +49,11 @@ import org.joda.time.DateTime; import org.joda.time.Days; import org.joda.time.chrono.ISOChronology; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.io.IOException; @@ -96,6 +98,8 @@ import static java.nio.file.Files.createTempDirectory; import static org.assertj.core.api.Assertions.assertThat; import static org.joda.time.DateTimeZone.UTC; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotEquals; @@ -106,7 +110,8 @@ import static org.testng.FileAssert.assertDirectory; import static org.testng.FileAssert.assertFile; -@Test(singleThreaded = true) +@TestInstance(PER_METHOD) +@Execution(SAME_THREAD) public class TestRaptorStorageManager { private static final ISOChronology UTC_CHRONOLOGY = ISOChronology.getInstanceUTC(); @@ -134,7 +139,7 @@ public class TestRaptorStorageManager private Optional backupStore; private InMemoryShardRecorder shardRecorder; - @BeforeMethod + @BeforeEach public void setup() throws IOException { @@ -159,7 +164,7 @@ public void setup() shardRecorder = new InMemoryShardRecorder(); } - @AfterMethod(alwaysRun = true) + @AfterEach public void tearDown() throws Exception { diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestShardEjector.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestShardEjector.java index 5efdbbb752e3..3756fbdcc921 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestShardEjector.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestShardEjector.java @@ -30,9 +30,11 @@ import io.trino.testing.TestingNodeManager; import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.Jdbi; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.io.IOException; @@ -55,11 +57,14 @@ import static java.util.UUID.randomUUID; import static java.util.concurrent.TimeUnit.HOURS; import static java.util.stream.Collectors.toSet; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; -@Test(singleThreaded = true) +@TestInstance(PER_METHOD) +@Execution(SAME_THREAD) public class TestShardEjector { private Jdbi dbi; @@ -68,7 +73,7 @@ public class TestShardEjector private Path dataDir; private StorageService storageService; - @BeforeMethod + @BeforeEach public void setup() throws IOException { @@ -82,7 +87,7 @@ public void setup() storageService.start(); } - @AfterMethod(alwaysRun = true) + @AfterEach public void teardown() throws Exception { @@ -95,7 +100,7 @@ public void teardown() } } - @Test(invocationCount = 20) + @RepeatedTest(20) public void testEjector() throws Exception { diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestShardRecovery.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestShardRecovery.java index 2a98d916d6a2..739eac07cf32 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestShardRecovery.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestShardRecovery.java @@ -23,9 +23,11 @@ import io.trino.testing.TestingNodeManager; import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.Jdbi; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.io.IOException; @@ -48,13 +50,17 @@ import static java.nio.file.Files.createTempFile; import static java.nio.file.Files.writeString; import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; -@Test(singleThreaded = true) +@TestInstance(PER_METHOD) +@Execution(SAME_THREAD) public class TestShardRecovery { private StorageService storageService; @@ -63,7 +69,7 @@ public class TestShardRecovery private Path temporary; private FileBackupStore backupStore; - @BeforeMethod + @BeforeEach public void setup() throws IOException { @@ -82,7 +88,7 @@ public void setup() recoveryManager = createShardRecoveryManager(storageService, Optional.of(backupStore), shardManager); } - @AfterMethod(alwaysRun = true) + @AfterEach public void tearDown() throws Exception { @@ -235,10 +241,14 @@ public void testShardRecoveryBackupChecksumMismatch() assertTrue(getOnlyElement(quarantined).startsWith(shardUuid + ".orc.corrupt")); } - @Test(expectedExceptions = TrinoException.class, expectedExceptionsMessageRegExp = "No backup file found for shard: .*") + @Test public void testNoBackupException() { - recoveryManager.restoreFromBackup(UUID.randomUUID(), 0, OptionalLong.empty()); + assertThatThrownBy(() -> { + recoveryManager.restoreFromBackup(UUID.randomUUID(), 0, OptionalLong.empty()); + }) + .isInstanceOf(TrinoException.class) + .hasMessageMatching("No backup file found for shard: .*"); } public static ShardRecoveryManager createShardRecoveryManager( diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestShardWriter.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestShardWriter.java index 02b8a99bd321..3979a5c1588a 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestShardWriter.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestShardWriter.java @@ -28,9 +28,10 @@ import io.trino.spi.type.Type; import io.trino.spi.type.TypeId; import io.trino.spi.type.TypeSignatureParameter; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.io.IOException; @@ -56,25 +57,28 @@ import static io.trino.testing.StructuralTestUtil.sqlMapOf; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.nio.file.Files.createTempDirectory; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestShardWriter { - private Path directory; + private final Path directory; private static final JsonCodec METADATA_CODEC = jsonCodec(OrcFileMetadata.class); - @BeforeClass - public void setup() + public TestShardWriter() throws IOException { directory = createTempDirectory(null); } - @AfterClass(alwaysRun = true) + @AfterAll public void tearDown() throws Exception { diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestStorageManagerConfig.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestStorageManagerConfig.java index 57116c729648..17deeaeb059e 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestStorageManagerConfig.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestStorageManagerConfig.java @@ -17,7 +17,7 @@ import io.airlift.units.DataSize; import io.airlift.units.Duration; import jakarta.validation.constraints.NotNull; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.File; import java.util.Map; diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestCompactionSetCreator.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestCompactionSetCreator.java index 3667ca650e3d..82c6c14a95b7 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestCompactionSetCreator.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestCompactionSetCreator.java @@ -18,7 +18,7 @@ import io.airlift.units.DataSize; import io.trino.plugin.raptor.legacy.metadata.Table; import io.trino.spi.type.Type; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.time.Duration; import java.util.HashSet; diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardCompactor.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardCompactor.java index 5e64812b8daf..ca6a3d739575 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardCompactor.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardCompactor.java @@ -36,9 +36,11 @@ import io.trino.testing.MaterializedRow; import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.Jdbi; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.IOException; import java.nio.file.Path; @@ -67,9 +69,12 @@ import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; -@Test(singleThreaded = true) +@TestInstance(PER_METHOD) +@Execution(SAME_THREAD) public class TestShardCompactor { private static final int MAX_SHARD_ROWS = 1000; @@ -85,7 +90,7 @@ public class TestShardCompactor private Path temporary; private Handle dummyHandle; - @BeforeMethod + @BeforeEach public void setup() throws IOException { @@ -96,7 +101,7 @@ public void setup() compactor = new ShardCompactor(storageManager, READER_OPTIONS, new TypeOperators()); } - @AfterMethod(alwaysRun = true) + @AfterEach public void tearDown() throws Exception { diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizationManager.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizationManager.java index 9a875396d528..17f99cf2bf36 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizationManager.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizationManager.java @@ -21,9 +21,11 @@ import io.trino.spi.type.Type; import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.Jdbi; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.Optional; @@ -47,9 +49,12 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.stream.Collectors.toSet; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; -@Test(singleThreaded = true) +@TestInstance(PER_METHOD) +@Execution(SAME_THREAD) public class TestShardOrganizationManager { private Jdbi dbi; @@ -62,7 +67,7 @@ public class TestShardOrganizationManager private static final List types = ImmutableList.of(BIGINT, VARCHAR, DATE, TIMESTAMP_MILLIS); - @BeforeMethod + @BeforeEach public void setup() { dbi = createTestingJdbi(); @@ -73,7 +78,7 @@ public void setup() createTablesWithRetry(dbi); } - @AfterMethod(alwaysRun = true) + @AfterEach public void teardown() { dummyHandle.close(); diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizer.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizer.java index d633aedc8403..5fff0cc87dc4 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizer.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizer.java @@ -14,7 +14,8 @@ package io.trino.plugin.raptor.legacy.storage.organization; import com.google.common.collect.ImmutableSet; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; import java.util.OptionalInt; import java.util.Set; @@ -29,7 +30,8 @@ public class TestShardOrganizer { - @Test(timeOut = 5_000) + @Test + @Timeout(5) public void testShardOrganizerInProgress() throws Exception { diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizerUtil.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizerUtil.java index e3e2eb9cb7fb..8e197063adfb 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizerUtil.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizerUtil.java @@ -30,9 +30,11 @@ import io.trino.spi.type.Type; import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.Jdbi; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.nio.file.Files; @@ -58,9 +60,12 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.spi.type.VarcharType.createVarcharType; import static io.trino.testing.TestingConnectorSession.SESSION; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; -@Test(singleThreaded = true) +@TestInstance(PER_METHOD) +@Execution(SAME_THREAD) public class TestShardOrganizerUtil { private static final List COLUMNS = ImmutableList.of( @@ -75,7 +80,7 @@ public class TestShardOrganizerUtil private MetadataDao metadataDao; private ConnectorMetadata metadata; - @BeforeMethod + @BeforeEach public void setup() throws Exception { @@ -90,7 +95,7 @@ public void setup() shardManager = createShardManager(dbi); } - @AfterMethod(alwaysRun = true) + @AfterEach public void teardown() throws Exception { diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardRange.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardRange.java index 869f941cd789..1dbcf0d1c284 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardRange.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardRange.java @@ -15,7 +15,7 @@ import com.google.common.collect.ImmutableList; import io.trino.spi.type.Type; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.List; diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestTemporalFunction.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestTemporalFunction.java index 536dfd2aa4f4..2adf82c17483 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestTemporalFunction.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestTemporalFunction.java @@ -16,7 +16,7 @@ import io.trino.spi.block.Block; import io.trino.spi.block.BlockBuilder; import org.joda.time.DateTime; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.time.Duration; diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestTuple.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestTuple.java index 344be5490c89..f42eec0b460b 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestTuple.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestTuple.java @@ -15,7 +15,7 @@ import com.google.common.collect.ImmutableList; import io.trino.spi.type.Type; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.List; @@ -28,6 +28,7 @@ import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.spi.type.VarcharType.createVarcharType; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.testng.Assert.assertEquals; public class TestTuple @@ -47,13 +48,17 @@ public void testComparableTuple() assertGreaterThan(tuple1.compareTo(lessThanTuple1), 0); } - @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "types must be the same") + @Test public void testMismatchedTypes() { - List types1 = ImmutableList.of(createVarcharType(3)); - List types2 = ImmutableList.of(createVarcharType(4)); - Tuple tuple1 = new Tuple(types1, ImmutableList.of("abc")); - Tuple tuple2 = new Tuple(types2, ImmutableList.of("abcd")); - tuple1.compareTo(tuple2); + assertThatThrownBy(() -> { + List types1 = ImmutableList.of(createVarcharType(3)); + List types2 = ImmutableList.of(createVarcharType(4)); + Tuple tuple1 = new Tuple(types1, ImmutableList.of("abc")); + Tuple tuple2 = new Tuple(types2, ImmutableList.of("abcd")); + tuple1.compareTo(tuple2); + }) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("types must be the same"); } } diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/systemtables/TestShardMetadataRecordCursor.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/systemtables/TestShardMetadataRecordCursor.java index 3753f3a02075..48a05cdc6e66 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/systemtables/TestShardMetadataRecordCursor.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/systemtables/TestShardMetadataRecordCursor.java @@ -35,9 +35,11 @@ import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.Jdbi; import org.joda.time.DateTime; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.ArrayList; import java.util.List; @@ -60,9 +62,12 @@ import static io.trino.testing.MaterializedResult.DEFAULT_PRECISION; import static io.trino.testing.TestingConnectorSession.SESSION; import static java.util.stream.Collectors.toList; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; -@Test(singleThreaded = true) +@TestInstance(PER_METHOD) +@Execution(SAME_THREAD) public class TestShardMetadataRecordCursor { private static final SchemaTableName DEFAULT_TEST_ORDERS = new SchemaTableName("test", "orders"); @@ -71,7 +76,7 @@ public class TestShardMetadataRecordCursor private ConnectorMetadata metadata; private Jdbi dbi; - @BeforeMethod + @BeforeEach public void setup() { dbi = createTestingJdbi(); @@ -88,7 +93,7 @@ public void setup() createTable(table); } - @AfterMethod(alwaysRun = true) + @AfterEach public void teardown() { dummyHandle.close(); diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/util/TestPrioritizedFifoExecutor.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/util/TestPrioritizedFifoExecutor.java index 822f5e18754d..78e51c1922fa 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/util/TestPrioritizedFifoExecutor.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/util/TestPrioritizedFifoExecutor.java @@ -13,9 +13,11 @@ */ package io.trino.plugin.raptor.legacy.util; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.ArrayList; import java.util.Comparator; @@ -29,24 +31,27 @@ import static com.google.common.util.concurrent.Uninterruptibles.awaitUninterruptibly; import static java.util.concurrent.Executors.newCachedThreadPool; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; -@Test(singleThreaded = true) +@TestInstance(PER_METHOD) +@Execution(SAME_THREAD) public class TestPrioritizedFifoExecutor { private static final Comparator DUMMY_COMPARATOR = (o1, o2) -> 0; private ExecutorService executor; - @BeforeMethod + @BeforeEach public void setUp() { executor = newCachedThreadPool(); } - @AfterMethod(alwaysRun = true) + @AfterEach public void tearDown() { executor.shutdownNow(); diff --git a/plugin/trino-redis/src/test/java/io/trino/plugin/redis/BaseRedisConnectorTest.java b/plugin/trino-redis/src/test/java/io/trino/plugin/redis/BaseRedisConnectorTest.java index 90f929c11f2c..268cb817d481 100644 --- a/plugin/trino-redis/src/test/java/io/trino/plugin/redis/BaseRedisConnectorTest.java +++ b/plugin/trino-redis/src/test/java/io/trino/plugin/redis/BaseRedisConnectorTest.java @@ -16,7 +16,7 @@ import io.trino.sql.planner.plan.FilterNode; import io.trino.testing.BaseConnectorTest; import io.trino.testing.TestingConnectorBehavior; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftConfig.java b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftConfig.java index 2b516055d33d..90b912746ba8 100644 --- a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftConfig.java +++ b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftConfig.java @@ -14,7 +14,7 @@ package io.trino.plugin.redshift; import com.google.common.collect.ImmutableMap; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Map; diff --git a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftPlugin.java b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftPlugin.java index f5a13eae1039..a1d9b6ab73b9 100644 --- a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftPlugin.java +++ b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftPlugin.java @@ -17,7 +17,7 @@ import io.trino.spi.Plugin; import io.trino.spi.connector.ConnectorFactory; import io.trino.testing.TestingConnectorContext; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static com.google.common.collect.Iterables.getOnlyElement; diff --git a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftTableStatisticsReader.java b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftTableStatisticsReader.java index 5a2b85eb3856..fe471829e926 100644 --- a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftTableStatisticsReader.java +++ b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftTableStatisticsReader.java @@ -34,8 +34,7 @@ import io.trino.testing.sql.TestTable; import org.assertj.core.api.InstanceOfAssertFactories; import org.assertj.core.api.SoftAssertions; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.sql.Types; import java.util.List; @@ -78,17 +77,10 @@ public class TestRedshiftTableStatisticsReader createVarcharJdbcColumnHandle("mktsegment", 10), createVarcharJdbcColumnHandle("comment", 117)); - private RedshiftTableStatisticsReader statsReader; - - @BeforeClass - public void setup() - { - DriverConnectionFactory connectionFactory = new DriverConnectionFactory( - new Driver(), - new BaseJdbcConfig().setConnectionUrl(JDBC_URL), - new StaticCredentialProvider(Optional.of(JDBC_USER), Optional.of(JDBC_PASSWORD))); - statsReader = new RedshiftTableStatisticsReader(connectionFactory); - } + private final RedshiftTableStatisticsReader statsReader = new RedshiftTableStatisticsReader(new DriverConnectionFactory( + new Driver(), + new BaseJdbcConfig().setConnectionUrl(JDBC_URL), + new StaticCredentialProvider(Optional.of(JDBC_USER), Optional.of(JDBC_PASSWORD)))); @Override protected QueryRunner createQueryRunner() diff --git a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftTypeMapping.java b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftTypeMapping.java index ed6c97fed0b9..b2ea6798863b 100644 --- a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftTypeMapping.java +++ b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftTypeMapping.java @@ -27,8 +27,7 @@ import io.trino.testing.sql.SqlExecutor; import io.trino.testing.sql.TestTable; import io.trino.testing.sql.TrinoSqlExecutor; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.sql.SQLException; import java.time.LocalDate; @@ -102,8 +101,7 @@ public class TestRedshiftTypeMapping private final LocalDate dayOfMidnightGapInVilnius = LocalDate.of(1983, 4, 1); private final LocalDate dayAfterMidnightSetBackInVilnius = LocalDate.of(1983, 10, 1); - @BeforeClass - public void checkRanges() + public TestRedshiftTypeMapping() { // Timestamps checkIsGap(jvmZone, timeGapInJvmZone); diff --git a/plugin/trino-resource-group-managers/pom.xml b/plugin/trino-resource-group-managers/pom.xml index 38ba2d150b32..b18c76ab044f 100644 --- a/plugin/trino-resource-group-managers/pom.xml +++ b/plugin/trino-resource-group-managers/pom.xml @@ -193,6 +193,12 @@ test + + io.airlift + junit-extensions + test + + io.airlift testing @@ -211,6 +217,18 @@ test + + org.junit.jupiter + junit-jupiter-api + test + + + + org.junit.jupiter + junit-jupiter-engine + test + + org.testcontainers jdbc @@ -247,4 +265,26 @@ test + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + org.apache.maven.surefire + surefire-junit-platform + ${dep.plugin.surefire.version} + + + org.apache.maven.surefire + surefire-testng + ${dep.plugin.surefire.version} + + + + + diff --git a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/TestFileResourceGroupConfig.java b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/TestFileResourceGroupConfig.java index 253c01a84bab..4974a7754fc5 100644 --- a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/TestFileResourceGroupConfig.java +++ b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/TestFileResourceGroupConfig.java @@ -14,7 +14,7 @@ package io.trino.plugin.resourcegroups; import com.google.common.collect.ImmutableMap; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.nio.file.Files; diff --git a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/TestFileResourceGroupConfigurationManager.java b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/TestFileResourceGroupConfigurationManager.java index 567444f20ced..939c1741f03d 100644 --- a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/TestFileResourceGroupConfigurationManager.java +++ b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/TestFileResourceGroupConfigurationManager.java @@ -23,7 +23,7 @@ import io.trino.spi.resourcegroups.SelectionContext; import io.trino.spi.resourcegroups.SelectionCriteria; import io.trino.spi.session.ResourceEstimates; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.time.Duration; import java.util.Optional; diff --git a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/TestResourceGroupIdTemplate.java b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/TestResourceGroupIdTemplate.java index 027731f6322d..74e038862f67 100644 --- a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/TestResourceGroupIdTemplate.java +++ b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/TestResourceGroupIdTemplate.java @@ -19,11 +19,12 @@ import io.trino.spi.resourcegroups.SelectionContext; import io.trino.spi.resourcegroups.SelectionCriteria; import io.trino.spi.session.ResourceEstimates; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Optional; import java.util.regex.Pattern; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; @@ -78,23 +79,31 @@ public void testNoMatch() assertFalse(selector.match(context).isPresent()); } - @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "unresolved variables \\[user\\] in resource group ID.*") + @Test public void testUnresolvedVariableLoadTime() { - ResourceGroupIdTemplate template = new ResourceGroupIdTemplate("test.pipeline.${pipeline}.${user}"); - Pattern sourcePattern = Pattern.compile("scheduler.important.(?[^\\[]*).*"); - StaticSelector selector = new StaticSelector(Optional.empty(), Optional.empty(), Optional.of(sourcePattern), Optional.empty(), Optional.empty(), Optional.empty(), template); - SelectionCriteria context = new SelectionCriteria(true, "user", ImmutableSet.of(), Optional.of("scheduler.important.testpipeline[5]"), ImmutableSet.of(), EMPTY_RESOURCE_ESTIMATES, Optional.empty()); - selector.match(context); + assertThatThrownBy(() -> { + ResourceGroupIdTemplate template = new ResourceGroupIdTemplate("test.pipeline.${pipeline}.${user}"); + Pattern sourcePattern = Pattern.compile("scheduler.important.(?[^\\[]*).*"); + StaticSelector selector = new StaticSelector(Optional.empty(), Optional.empty(), Optional.of(sourcePattern), Optional.empty(), Optional.empty(), Optional.empty(), template); + SelectionCriteria context = new SelectionCriteria(true, "user", ImmutableSet.of(), Optional.of("scheduler.important.testpipeline[5]"), ImmutableSet.of(), EMPTY_RESOURCE_ESTIMATES, Optional.empty()); + selector.match(context); + }) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageMatching("unresolved variables \\[user\\] in resource group ID.*"); } - @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "unresolved variable 'pipeline' in resource group '\\$\\{pipeline\\}', available.*") + @Test public void testUnresolvedVariableRunTime() { - ResourceGroupIdTemplate template = new ResourceGroupIdTemplate("test.pipeline.${pipeline}.${USER}"); - Pattern sourcePattern = Pattern.compile("scheduler.important.(testpipeline\\[|(?[^\\[]*)).*"); - StaticSelector selector = new StaticSelector(Optional.empty(), Optional.empty(), Optional.of(sourcePattern), Optional.empty(), Optional.empty(), Optional.empty(), template); - SelectionCriteria context = new SelectionCriteria(true, "user", ImmutableSet.of(), Optional.of("scheduler.important.testpipeline[5]"), ImmutableSet.of(), EMPTY_RESOURCE_ESTIMATES, Optional.empty()); - selector.match(context); + assertThatThrownBy(() -> { + ResourceGroupIdTemplate template = new ResourceGroupIdTemplate("test.pipeline.${pipeline}.${USER}"); + Pattern sourcePattern = Pattern.compile("scheduler.important.(testpipeline\\[|(?[^\\[]*)).*"); + StaticSelector selector = new StaticSelector(Optional.empty(), Optional.empty(), Optional.of(sourcePattern), Optional.empty(), Optional.empty(), Optional.empty(), template); + SelectionCriteria context = new SelectionCriteria(true, "user", ImmutableSet.of(), Optional.of("scheduler.important.testpipeline[5]"), ImmutableSet.of(), EMPTY_RESOURCE_ESTIMATES, Optional.empty()); + selector.match(context); + }) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageMatching("unresolved variable 'pipeline' in resource group '\\$\\{pipeline\\}', available.*"); } } diff --git a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/TestStaticSelector.java b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/TestStaticSelector.java index 0142361b7952..2d5bfb2100e3 100644 --- a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/TestStaticSelector.java +++ b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/TestStaticSelector.java @@ -22,7 +22,7 @@ import io.trino.spi.resourcegroups.SelectionContext; import io.trino.spi.resourcegroups.SelectionCriteria; import io.trino.spi.session.ResourceEstimates; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Optional; import java.util.Set; diff --git a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/BaseTestDbResourceGroupsFlywayMigration.java b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/BaseTestDbResourceGroupsFlywayMigration.java index f1fc9b59e75b..dcd9780e5061 100644 --- a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/BaseTestDbResourceGroupsFlywayMigration.java +++ b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/BaseTestDbResourceGroupsFlywayMigration.java @@ -15,43 +15,35 @@ import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.Jdbi; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.Isolated; import org.testcontainers.containers.JdbcDatabaseContainer; -import org.testng.annotations.AfterClass; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; import java.util.List; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; -@Test(singleThreaded = true) +@TestInstance(PER_CLASS) +@Execution(SAME_THREAD) +@Isolated public abstract class BaseTestDbResourceGroupsFlywayMigration { - protected JdbcDatabaseContainer container; - protected Jdbi jdbi; - - @BeforeClass - public final void setup() - { - container = startContainer(); - jdbi = Jdbi.create(container.getJdbcUrl(), container.getUsername(), container.getPassword()); - } + protected final JdbcDatabaseContainer container = startContainer(); + protected final Jdbi jdbi = Jdbi.create(container.getJdbcUrl(), container.getUsername(), container.getPassword()); protected abstract JdbcDatabaseContainer startContainer(); - @AfterClass(alwaysRun = true) + @AfterAll public final void close() { container.close(); } - @AfterMethod(alwaysRun = true) - public void cleanup() - { - dropAllTables(); - } - @Test public void testMigrationWithEmptyDatabase() { @@ -61,6 +53,8 @@ public void testMigrationWithEmptyDatabase() .setConfigDbPassword(container.getPassword()); FlywayMigration.migrate(config); verifyResourceGroupsSchema(0); + + dropAllTables(); } @Test @@ -82,6 +76,8 @@ public void testMigrationWithNonEmptyDatabase() jdbiHandle.execute(t1Drop); jdbiHandle.execute(t2Drop); jdbiHandle.close(); + + dropAllTables(); } protected void verifyResourceGroupsSchema(long expectedPropertiesCount) diff --git a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbResourceGroupConfig.java b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbResourceGroupConfig.java index 0832722560ab..3fac39267b5c 100644 --- a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbResourceGroupConfig.java +++ b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbResourceGroupConfig.java @@ -16,7 +16,7 @@ import com.google.common.collect.ImmutableMap; import io.airlift.units.Duration; import jakarta.validation.constraints.AssertTrue; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Map; diff --git a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbResourceGroupConfigurationManager.java b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbResourceGroupConfigurationManager.java index 02513b1ad563..8d4c2e33520e 100644 --- a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbResourceGroupConfigurationManager.java +++ b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbResourceGroupConfigurationManager.java @@ -27,7 +27,8 @@ import io.trino.spi.resourcegroups.SelectionCriteria; import io.trino.spi.session.ResourceEstimates; import org.jdbi.v3.core.statement.UnableToExecuteStatementException; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; import java.time.Duration; import java.util.ArrayList; @@ -177,7 +178,8 @@ public void testMissing() .hasMessage("No matching configuration found for [missing] using [missing]"); } - @Test(timeOut = 60_000) + @Test + @Timeout(60) public void testReconfig() throws Exception { diff --git a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbResourceGroupsMysqlFlywayMigration.java b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbResourceGroupsMysqlFlywayMigration.java index f845586d2c4f..50a31ca2f064 100644 --- a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbResourceGroupsMysqlFlywayMigration.java +++ b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbResourceGroupsMysqlFlywayMigration.java @@ -14,11 +14,10 @@ package io.trino.plugin.resourcegroups.db; import org.jdbi.v3.core.Handle; +import org.junit.jupiter.api.Test; import org.testcontainers.containers.JdbcDatabaseContainer; import org.testcontainers.containers.MySQLContainer; -import org.testng.annotations.Test; -@Test(singleThreaded = true) public class TestDbResourceGroupsMysqlFlywayMigration extends BaseTestDbResourceGroupsFlywayMigration { @@ -43,6 +42,8 @@ public void testMigrationWithOldResourceGroupsSchema() .setConfigDbPassword(container.getPassword()); FlywayMigration.migrate(config); verifyResourceGroupsSchema(1); + + dropAllTables(); } private void createOldSchema() diff --git a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbResourceGroupsOracleFlywayMigration.java b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbResourceGroupsOracleFlywayMigration.java index c4231901e714..4126ec73a87a 100644 --- a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbResourceGroupsOracleFlywayMigration.java +++ b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbResourceGroupsOracleFlywayMigration.java @@ -16,14 +16,12 @@ import org.jdbi.v3.core.Handle; import org.testcontainers.containers.JdbcDatabaseContainer; import org.testcontainers.containers.OracleContainer; -import org.testng.annotations.Test; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Locale; -@Test(singleThreaded = true) public class TestDbResourceGroupsOracleFlywayMigration extends BaseTestDbResourceGroupsFlywayMigration { @@ -82,12 +80,4 @@ private boolean tableExists(Handle jdbiHandle, String tableName) return false; } } - - @Test - public void forceTestNgToRespectSingleThreaded() - { - // TODO: Remove after updating TestNG to 7.4.0+ (https://github.com/trinodb/trino/issues/8571) - // TestNG doesn't enforce @Test(singleThreaded = true) when tests are defined in base class. According to - // https://github.com/cbeust/testng/issues/2361#issuecomment-688393166 a workaround it to add a dummy test to the leaf test class. - } } diff --git a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbResourceGroupsPostgresqlFlywayMigration.java b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbResourceGroupsPostgresqlFlywayMigration.java index 6bda1c6a2008..71fb148caec4 100644 --- a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbResourceGroupsPostgresqlFlywayMigration.java +++ b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbResourceGroupsPostgresqlFlywayMigration.java @@ -15,9 +15,7 @@ import org.testcontainers.containers.JdbcDatabaseContainer; import org.testcontainers.containers.PostgreSQLContainer; -import org.testng.annotations.Test; -@Test(singleThreaded = true) public class TestDbResourceGroupsPostgresqlFlywayMigration extends BaseTestDbResourceGroupsFlywayMigration { @@ -28,12 +26,4 @@ protected final JdbcDatabaseContainer startContainer() container.start(); return container; } - - @Test - public void forceTestNgToRespectSingleThreaded() - { - // TODO: Remove after updating TestNG to 7.4.0+ (https://github.com/trinodb/trino/issues/8571) - // TestNG doesn't enforce @Test(singleThreaded = true) when tests are defined in base class. According to - // https://github.com/cbeust/testng/issues/2361#issuecomment-688393166 a workaround it to add a dummy test to the leaf test class. - } } diff --git a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbSourceExactMatchSelector.java b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbSourceExactMatchSelector.java index 515d28135dc4..e6f9c4ed08a3 100644 --- a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbSourceExactMatchSelector.java +++ b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbSourceExactMatchSelector.java @@ -20,8 +20,7 @@ import io.trino.spi.resourcegroups.SelectionContext; import io.trino.spi.resourcegroups.SelectionCriteria; import io.trino.spi.session.ResourceEstimates; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Optional; import java.util.concurrent.ThreadLocalRandom; @@ -35,10 +34,9 @@ public class TestDbSourceExactMatchSelector { private static final JsonCodec CODEC = JsonCodec.jsonCodec(ResourceGroupId.class); private static final ResourceEstimates EMPTY_RESOURCE_ESTIMATES = new ResourceEstimates(Optional.empty(), Optional.empty(), Optional.empty()); - private H2ResourceGroupsDao dao; + private final H2ResourceGroupsDao dao; - @BeforeClass - public void setup() + public TestDbSourceExactMatchSelector() { DbResourceGroupConfig config = new DbResourceGroupConfig().setConfigDbUrl("jdbc:h2:mem:test_db-exact-match-selector" + System.nanoTime() + ThreadLocalRandom.current().nextLong()); dao = new H2DaoProvider(config).get(); diff --git a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestResourceGroupsDao.java b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestResourceGroupsDao.java index f31ac9692eb4..b0bbd708a773 100644 --- a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestResourceGroupsDao.java +++ b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestResourceGroupsDao.java @@ -22,7 +22,7 @@ import io.trino.plugin.resourcegroups.SelectorResourceEstimate.Range; import io.trino.spi.resourcegroups.ResourceGroupId; import org.jdbi.v3.core.statement.UnableToExecuteStatementException; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.HashMap; import java.util.List; diff --git a/plugin/trino-session-property-managers/pom.xml b/plugin/trino-session-property-managers/pom.xml index c5fad219ba53..804800748261 100644 --- a/plugin/trino-session-property-managers/pom.xml +++ b/plugin/trino-session-property-managers/pom.xml @@ -144,6 +144,12 @@ provided + + io.airlift + junit-extensions + test + + io.airlift testing @@ -182,6 +188,12 @@ test + + org.junit.jupiter + junit-jupiter-api + test + + org.junit.jupiter junit-jupiter-engine diff --git a/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/file/TestFileSessionPropertyManagerConfig.java b/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/file/TestFileSessionPropertyManagerConfig.java index e5b4e5ec8cf0..f75dc0731f6e 100644 --- a/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/file/TestFileSessionPropertyManagerConfig.java +++ b/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/file/TestFileSessionPropertyManagerConfig.java @@ -14,7 +14,7 @@ package io.trino.plugin.session.file; import com.google.common.collect.ImmutableMap; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.nio.file.Files; diff --git a/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreConfig.java b/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreConfig.java index 6d762f5d7ecd..0716a4d4aabc 100644 --- a/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreConfig.java +++ b/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreConfig.java @@ -15,7 +15,7 @@ import com.google.common.collect.ImmutableMap; import io.airlift.units.Duration; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Map; import java.util.concurrent.TimeUnit; diff --git a/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreJdbcConfig.java b/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreJdbcConfig.java index 7957cbc501d5..3ecf9da6eb10 100644 --- a/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreJdbcConfig.java +++ b/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreJdbcConfig.java @@ -18,7 +18,7 @@ import io.trino.spi.Plugin; import io.trino.spi.connector.ConnectorFactory; import io.trino.testing.TestingConnectorContext; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static com.google.common.collect.MoreCollectors.toOptional; import static com.google.common.collect.Streams.stream; diff --git a/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStorePlugin.java b/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStorePlugin.java index b6b5f8f62d23..fc440831e949 100644 --- a/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStorePlugin.java +++ b/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStorePlugin.java @@ -17,7 +17,7 @@ import io.trino.spi.Plugin; import io.trino.spi.connector.ConnectorFactory; import io.trino.testing.TestingConnectorContext; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static com.google.common.collect.MoreCollectors.toOptional; import static com.google.common.collect.Streams.stream; diff --git a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerClient.java b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerClient.java index 8be977683b19..7b050b5bac86 100644 --- a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerClient.java +++ b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerClient.java @@ -27,7 +27,7 @@ import io.trino.spi.connector.ColumnHandle; import io.trino.spi.expression.ConnectorExpression; import io.trino.spi.expression.Variable; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.sql.Types; import java.util.List; diff --git a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerConfig.java b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerConfig.java index d6ad75365f85..02e588d7acd3 100644 --- a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerConfig.java +++ b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerConfig.java @@ -14,7 +14,7 @@ package io.trino.plugin.sqlserver; import com.google.common.collect.ImmutableMap; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Map; diff --git a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerPlugin.java b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerPlugin.java index b683528dd715..6d68ab83e9da 100644 --- a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerPlugin.java +++ b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerPlugin.java @@ -17,7 +17,7 @@ import io.trino.spi.Plugin; import io.trino.spi.connector.ConnectorFactory; import io.trino.testing.TestingConnectorContext; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static com.google.common.collect.Iterables.getOnlyElement; diff --git a/plugin/trino-thrift-api/pom.xml b/plugin/trino-thrift-api/pom.xml index 544f0ee62bac..994e18564d7c 100644 --- a/plugin/trino-thrift-api/pom.xml +++ b/plugin/trino-thrift-api/pom.xml @@ -48,6 +48,12 @@ jakarta.annotation-api + + io.airlift + junit-extensions + test + + io.airlift stats @@ -66,6 +72,18 @@ test + + org.junit.jupiter + junit-jupiter-api + test + + + + org.junit.jupiter + junit-jupiter-engine + test + + org.testng testng @@ -89,6 +107,24 @@ + + + org.apache.maven.plugins + maven-surefire-plugin + + + + org.apache.maven.surefire + surefire-junit-platform + ${dep.plugin.surefire.version} + + + org.apache.maven.surefire + surefire-testng + ${dep.plugin.surefire.version} + + + diff --git a/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/TestNameValidationUtils.java b/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/TestNameValidationUtils.java index 2de62f1375f0..864d66203dca 100644 --- a/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/TestNameValidationUtils.java +++ b/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/TestNameValidationUtils.java @@ -13,7 +13,7 @@ */ package io.trino.plugin.thrift.api; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.plugin.thrift.api.NameValidationUtils.checkValidName; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/TestReadWrite.java b/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/TestReadWrite.java index 56c6a97cfac5..c6856609716d 100644 --- a/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/TestReadWrite.java +++ b/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/TestReadWrite.java @@ -24,7 +24,7 @@ import io.trino.spi.type.ArrayType; import io.trino.spi.type.Type; import io.trino.spi.type.VarcharType; -import org.testng.annotations.Test; +import org.junit.jupiter.api.RepeatedTest; import java.util.ArrayList; import java.util.Calendar; @@ -109,26 +109,26 @@ public class TestReadWrite MAX_GENERATED_DATE = toIntExact(MILLISECONDS.toDays(MAX_GENERATED_TIMESTAMP)); } - @Test(invocationCount = 20) + @RepeatedTest(20) public void testSingleRowPageReadWrite() { testPageReadWrite(new Random(singleRowPageSeedGenerator.incrementAndGet()), 1); } - @Test(invocationCount = 20) + @RepeatedTest(20) public void testSingleRowRecordSetReadWrite() { testRecordSetReadWrite(new Random(singleRowRecordSetSeedGenerator.incrementAndGet()), 1); } - @Test(invocationCount = 20) + @RepeatedTest(20) public void testMultiRowPageReadWrite() { Random random = new Random(multiRowPageSeedGenerator.incrementAndGet()); testPageReadWrite(random, random.nextInt(10000) + 10000); } - @Test(invocationCount = 20) + @RepeatedTest(20) public void testMultiRowRecordSetReadWrite() { Random random = new Random(multiRowRecordSetSeedGenerator.incrementAndGet()); diff --git a/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/TestTrinoThriftId.java b/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/TestTrinoThriftId.java index c68b987c0bc0..d66ddd76e56b 100644 --- a/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/TestTrinoThriftId.java +++ b/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/TestTrinoThriftId.java @@ -14,7 +14,7 @@ package io.trino.plugin.thrift.api; import com.google.common.primitives.UnsignedBytes; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.plugin.thrift.api.TrinoThriftId.summarize; import static org.testng.Assert.assertEquals; diff --git a/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/datatypes/TestTrinoThriftBigint.java b/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/datatypes/TestTrinoThriftBigint.java index e41e35a5071b..77bc56be58b6 100644 --- a/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/datatypes/TestTrinoThriftBigint.java +++ b/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/datatypes/TestTrinoThriftBigint.java @@ -16,7 +16,7 @@ import io.trino.plugin.thrift.api.TrinoThriftBlock; import io.trino.spi.block.Block; import io.trino.spi.block.BlockBuilder; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Arrays; import java.util.List; diff --git a/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/valuesets/TestTrinoThriftAllOrNoneValueSet.java b/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/valuesets/TestTrinoThriftAllOrNoneValueSet.java index bfd7b08d5b40..c354d574b1c2 100644 --- a/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/valuesets/TestTrinoThriftAllOrNoneValueSet.java +++ b/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/valuesets/TestTrinoThriftAllOrNoneValueSet.java @@ -14,7 +14,7 @@ package io.trino.plugin.thrift.api.valuesets; import io.trino.spi.predicate.ValueSet; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.plugin.thrift.api.valuesets.TrinoThriftValueSet.fromValueSet; import static io.trino.spi.type.HyperLogLogType.HYPER_LOG_LOG; diff --git a/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/valuesets/TestTrinoThriftEquatableValueSet.java b/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/valuesets/TestTrinoThriftEquatableValueSet.java index bd371646d9b3..f55a244689ce 100644 --- a/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/valuesets/TestTrinoThriftEquatableValueSet.java +++ b/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/valuesets/TestTrinoThriftEquatableValueSet.java @@ -16,7 +16,7 @@ import com.google.common.collect.ImmutableList; import io.trino.plugin.thrift.api.datatypes.TrinoThriftJson; import io.trino.spi.predicate.ValueSet; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.airlift.slice.Slices.utf8Slice; import static io.trino.plugin.thrift.api.TrinoThriftBlock.jsonData; diff --git a/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/valuesets/TestTrinoThriftRangeValueSet.java b/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/valuesets/TestTrinoThriftRangeValueSet.java index 06888e7a4791..0d24f0446dce 100644 --- a/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/valuesets/TestTrinoThriftRangeValueSet.java +++ b/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/valuesets/TestTrinoThriftRangeValueSet.java @@ -20,7 +20,7 @@ import io.trino.plugin.thrift.api.valuesets.TrinoThriftRangeValueSet.TrinoThriftRange; import io.trino.spi.predicate.Range; import io.trino.spi.predicate.ValueSet; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.plugin.thrift.api.TrinoThriftBlock.bigintData; import static io.trino.plugin.thrift.api.valuesets.TrinoThriftRangeValueSet.TrinoThriftBound.ABOVE; diff --git a/plugin/trino-thrift-testing-server/pom.xml b/plugin/trino-thrift-testing-server/pom.xml index ae5d0d67627f..dddd285c164a 100644 --- a/plugin/trino-thrift-testing-server/pom.xml +++ b/plugin/trino-thrift-testing-server/pom.xml @@ -86,6 +86,10 @@ org.assertj assertj-core + + org.junit.jupiter + junit-jupiter-api + org.testng testng @@ -113,12 +117,24 @@ jakarta.annotation-api + + io.airlift + junit-extensions + test + + org.assertj assertj-core test + + org.junit.jupiter + junit-jupiter-api + test + + org.junit.jupiter junit-jupiter-engine diff --git a/plugin/trino-thrift-testing-server/src/test/java/io/trino/plugin/thrift/server/TestListBasedRecordSet.java b/plugin/trino-thrift-testing-server/src/test/java/io/trino/plugin/thrift/server/TestListBasedRecordSet.java index 82157658e630..a6c28fd9fa3e 100644 --- a/plugin/trino-thrift-testing-server/src/test/java/io/trino/plugin/thrift/server/TestListBasedRecordSet.java +++ b/plugin/trino-thrift-testing-server/src/test/java/io/trino/plugin/thrift/server/TestListBasedRecordSet.java @@ -16,7 +16,7 @@ import com.google.common.collect.ImmutableList; import io.airlift.slice.Slices; import io.trino.spi.connector.RecordCursor; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Arrays; diff --git a/testing/trino-test-jdbc-compatibility-old-driver/pom.xml b/testing/trino-test-jdbc-compatibility-old-driver/pom.xml index 4df6ede5e6b8..c740576d92a5 100644 --- a/testing/trino-test-jdbc-compatibility-old-driver/pom.xml +++ b/testing/trino-test-jdbc-compatibility-old-driver/pom.xml @@ -31,6 +31,12 @@ test + + io.airlift + junit-extensions + test + + io.airlift log @@ -83,10 +89,44 @@ test + + org.junit.jupiter + junit-jupiter-api + test + + + + org.junit.jupiter + junit-jupiter-engine + test + + org.testng testng test + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + org.apache.maven.surefire + surefire-junit-platform + ${dep.plugin.surefire.version} + + + org.apache.maven.surefire + surefire-testng + ${dep.plugin.surefire.version} + + + + + diff --git a/testing/trino-test-jdbc-compatibility-old-driver/src/test/java/io/trino/TestJdbcCompatibility.java b/testing/trino-test-jdbc-compatibility-old-driver/src/test/java/io/trino/TestJdbcCompatibility.java index 61b65583b613..a10e22a225e4 100644 --- a/testing/trino-test-jdbc-compatibility-old-driver/src/test/java/io/trino/TestJdbcCompatibility.java +++ b/testing/trino-test-jdbc-compatibility-old-driver/src/test/java/io/trino/TestJdbcCompatibility.java @@ -18,10 +18,10 @@ import io.airlift.log.Logging; import io.trino.plugin.mongodb.MongoPlugin; import io.trino.server.testing.TestingTrinoServer; -import org.testng.SkipException; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.IOException; import java.math.BigDecimal; @@ -55,6 +55,9 @@ import static java.sql.Types.TIMESTAMP; import static java.sql.Types.TIMESTAMP_WITH_TIMEZONE; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assumptions.abort; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; /** * The main purpose of this class is to test cases when current server implementation breaks older JDBC clients @@ -65,19 +68,33 @@ *

* This test in turn is run using an old JDBC client against current server implementation. */ +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestJdbcCompatibility { private static final Optional VERSION_UNDER_TEST = testedVersion(); private static final int TIMESTAMP_DEFAULT_PRECISION = 3; - private TestingTrinoServer server; - private String serverUrl; + private final TestingTrinoServer server; + private final String serverUrl; + + public TestJdbcCompatibility() + { + Logging.initialize(); + + server = TestingTrinoServer.builder() + .build(); + + server.installPlugin(new MongoPlugin()); + + serverUrl = format("jdbc:trino://%s", server.getAddress()); + } @Test public void ensureProperDriverVersionLoaded() { if (VERSION_UNDER_TEST.isEmpty()) { - throw new SkipException("Information about JDBC version under test is missing"); + abort("Information about JDBC version under test is missing"); } assertThat(driverVersion()) @@ -87,25 +104,11 @@ public void ensureProperDriverVersionLoaded() .isEqualTo(VERSION_UNDER_TEST.get().toString()); } - @BeforeClass - public void setup() - { - Logging.initialize(); - - server = TestingTrinoServer.builder() - .build(); - - server.installPlugin(new MongoPlugin()); - - serverUrl = format("jdbc:trino://%s", server.getAddress()); - } - - @AfterClass(alwaysRun = true) + @AfterAll public void tearDown() throws IOException { server.close(); - server = null; } @Test @@ -134,7 +137,7 @@ public void testSelectTimestamp() public void testSelectTimestampWithTimeZone() { if (hasBrokenParametricTimestampWithTimeZoneSupport()) { - throw new SkipException("This version reports PARAMETRIC_DATETIME client capability but TIMESTAMP WITH TIME ZONE is not supported"); + abort("This version reports PARAMETRIC_DATETIME client capability but TIMESTAMP WITH TIME ZONE is not supported"); } String query = "SELECT timestamp '2012-10-31 01:00 Australia/Eucla'"; @@ -190,7 +193,7 @@ private void testSelectParametricTimestamp(String expression, Object expectedVal public void testSelectParametricTimestampWithTimeZone() { if (hasBrokenParametricTimestampWithTimeZoneSupport()) { - throw new SkipException("This version reports PARAMETRIC_DATETIME client capability but TIMESTAMP WITH TIME ZONE is not supported"); + abort("This version reports PARAMETRIC_DATETIME client capability but TIMESTAMP WITH TIME ZONE is not supported"); } if (!supportsParametricTimestampWithTimeZone()) { @@ -299,7 +302,7 @@ private void testSelectParametricTimestampInMap(String elementExpression, int ex public void testSelectParametricTimestampWithTimeZoneInMap() { if (hasBrokenParametricTimestampWithTimeZoneSupport()) { - throw new SkipException("This version reports PARAMETRIC_DATETIME client capability but TIMESTAMP WITH TIME ZONE is not supported"); + abort("This version reports PARAMETRIC_DATETIME client capability but TIMESTAMP WITH TIME ZONE is not supported"); } if (!supportsParametricTimestampWithTimeZone()) { @@ -422,7 +425,7 @@ private void testSelectParametricTimestampInArray(String elementExpression, int public void testSelectParametricTimestampWithTimeZoneInArray() { if (hasBrokenParametricTimestampWithTimeZoneSupport()) { - throw new SkipException("This version reports PARAMETRIC_DATETIME client capability but TIMESTAMP WITH TIME ZONE is not supported"); + abort("This version reports PARAMETRIC_DATETIME client capability but TIMESTAMP WITH TIME ZONE is not supported"); } if (!supportsParametricTimestampWithTimeZone()) { @@ -544,7 +547,7 @@ private void testSelectParametricTimestampInRow(String elementExpression, int pr public void testSelectParametricTimestampWithTimeZoneInRow() { if (hasBrokenParametricTimestampWithTimeZoneSupport()) { - throw new SkipException("This version reports PARAMETRIC_DATETIME client capability but TIMESTAMP WITH TIME ZONE is not supported"); + abort("This version reports PARAMETRIC_DATETIME client capability but TIMESTAMP WITH TIME ZONE is not supported"); } if (!supportsParametricTimestampWithTimeZone()) { diff --git a/testing/trino-testing/src/test/java/io/trino/testing/TestH2QueryRunner.java b/testing/trino-testing/src/test/java/io/trino/testing/TestH2QueryRunner.java index 2ed5f3aa1f55..038ecfc88f39 100644 --- a/testing/trino-testing/src/test/java/io/trino/testing/TestH2QueryRunner.java +++ b/testing/trino-testing/src/test/java/io/trino/testing/TestH2QueryRunner.java @@ -14,9 +14,7 @@ package io.trino.testing; import com.google.common.collect.ImmutableList; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.time.LocalDate; import java.time.ZoneId; @@ -29,34 +27,21 @@ public class TestH2QueryRunner { - private H2QueryRunner h2QueryRunner; - - @BeforeClass - public void init() - { - h2QueryRunner = new H2QueryRunner(); - } - - @AfterClass(alwaysRun = true) - public void close() - { - h2QueryRunner.close(); - h2QueryRunner = null; - } - @Test public void testDateToTimestampCoercion() { - // allow running tests with a connector that supports TIMESTAMP but not DATE - - // ordinary date - MaterializedResult rows = h2QueryRunner.execute(TEST_SESSION, "SELECT DATE '2018-01-13'", ImmutableList.of(TIMESTAMP_MILLIS)); - assertEquals(rows.getOnlyValue(), LocalDate.of(2018, 1, 13).atStartOfDay()); - - // date, which midnight was skipped in JVM zone - LocalDate forwardOffsetChangeAtMidnightInJvmZone = LocalDate.of(1970, 1, 1); - checkState(ZoneId.systemDefault().getRules().getValidOffsets(forwardOffsetChangeAtMidnightInJvmZone.atStartOfDay()).size() == 0, "This test assumes certain JVM time zone"); - rows = h2QueryRunner.execute(TEST_SESSION, DateTimeFormatter.ofPattern("'SELECT DATE '''uuuu-MM-dd''").format(forwardOffsetChangeAtMidnightInJvmZone), ImmutableList.of(TIMESTAMP_MILLIS)); - assertEquals(rows.getOnlyValue(), forwardOffsetChangeAtMidnightInJvmZone.atStartOfDay()); + try (H2QueryRunner h2QueryRunner = new H2QueryRunner()) { + // allow running tests with a connector that supports TIMESTAMP but not DATE + + // ordinary date + MaterializedResult rows = h2QueryRunner.execute(TEST_SESSION, "SELECT DATE '2018-01-13'", ImmutableList.of(TIMESTAMP_MILLIS)); + assertEquals(rows.getOnlyValue(), LocalDate.of(2018, 1, 13).atStartOfDay()); + + // date, which midnight was skipped in JVM zone + LocalDate forwardOffsetChangeAtMidnightInJvmZone = LocalDate.of(1970, 1, 1); + checkState(ZoneId.systemDefault().getRules().getValidOffsets(forwardOffsetChangeAtMidnightInJvmZone.atStartOfDay()).size() == 0, "This test assumes certain JVM time zone"); + rows = h2QueryRunner.execute(TEST_SESSION, DateTimeFormatter.ofPattern("'SELECT DATE '''uuuu-MM-dd''").format(forwardOffsetChangeAtMidnightInJvmZone), ImmutableList.of(TIMESTAMP_MILLIS)); + assertEquals(rows.getOnlyValue(), forwardOffsetChangeAtMidnightInJvmZone.atStartOfDay()); + } } } diff --git a/testing/trino-testing/src/test/java/io/trino/testing/TestTestingTrinoClient.java b/testing/trino-testing/src/test/java/io/trino/testing/TestTestingTrinoClient.java index e356e43fd205..b1c28bdf3fbb 100644 --- a/testing/trino-testing/src/test/java/io/trino/testing/TestTestingTrinoClient.java +++ b/testing/trino-testing/src/test/java/io/trino/testing/TestTestingTrinoClient.java @@ -27,9 +27,10 @@ import okhttp3.Interceptor; import okhttp3.OkHttpClient; import okhttp3.Request; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.IOException; import java.nio.file.Files; @@ -37,8 +38,12 @@ import java.security.Principal; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestTestingTrinoClient { private static final String TEST_USER = "test_user"; @@ -51,10 +56,9 @@ public class TestTestingTrinoClient .setQueryId(queryIdGenerator.createNextQueryId()) .build(); - private TestingTrinoServer server; + private final TestingTrinoServer server; - @BeforeClass - public void setup() + public TestTestingTrinoClient() throws IOException { Path passwordConfigDummy = Files.createTempFile("passwordConfigDummy", ""); @@ -79,12 +83,11 @@ private static Principal authenticate(String user, String password) throw new AccessDeniedException("Invalid credentials"); } - @AfterClass(alwaysRun = true) + @AfterAll public void teardown() throws Exception { server.close(); - server = null; } @Test diff --git a/testing/trino-testing/src/test/java/io/trino/testing/datatype/TestDataType.java b/testing/trino-testing/src/test/java/io/trino/testing/datatype/TestDataType.java index 82e49a25e285..cea9e9e6048b 100644 --- a/testing/trino-testing/src/test/java/io/trino/testing/datatype/TestDataType.java +++ b/testing/trino-testing/src/test/java/io/trino/testing/datatype/TestDataType.java @@ -13,7 +13,7 @@ */ package io.trino.testing.datatype; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.time.LocalTime; From 9d3896499793e37d1b251e75ec46e3b22d0e2c92 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Thu, 9 Nov 2023 14:53:28 -0800 Subject: [PATCH 197/587] Execute tests in single thread The testes rely on shared state so they can't run concurrently. --- .../java/io/trino/plugin/jdbc/TestJdbcTableProperties.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestJdbcTableProperties.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestJdbcTableProperties.java index 2c354d50aa46..8c69032467c7 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestJdbcTableProperties.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestJdbcTableProperties.java @@ -20,6 +20,7 @@ import io.trino.testing.QueryRunner; import io.trino.tpch.TpchTable; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @@ -27,7 +28,9 @@ import static io.trino.plugin.jdbc.H2QueryRunner.createH2QueryRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; +@Execution(SAME_THREAD) public class TestJdbcTableProperties extends AbstractTestQueryFramework { From 65686fa7ffcf09e567793737eb3610d46bcf5d6a Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Thu, 9 Nov 2023 16:02:00 +0100 Subject: [PATCH 198/587] Remove redundant synchronization around cleanupQuery `QueryCatalogs.finish` synchronizes within method body, so method-level synchronization looks redundant. --- .../src/main/java/io/trino/metadata/MetadataManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java b/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java index 958b8ae7d220..8708411eb790 100644 --- a/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java +++ b/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java @@ -2572,7 +2572,7 @@ private synchronized void registerCatalog(CatalogMetadata catalogMetadata) } } - private synchronized void finish() + private void finish() { List catalogs; synchronized (this) { From 187b01de42922ea523d838527b5101a1ee9fd5e7 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Wed, 8 Nov 2023 10:52:33 -0800 Subject: [PATCH 199/587] Fix ineffective logging configuration --- .../hive/formats/line/openxjson/TestOpenxJsonFormat.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/line/openxjson/TestOpenxJsonFormat.java b/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/line/openxjson/TestOpenxJsonFormat.java index 6ca6518283b7..d765304e27eb 100644 --- a/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/line/openxjson/TestOpenxJsonFormat.java +++ b/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/line/openxjson/TestOpenxJsonFormat.java @@ -19,6 +19,8 @@ import com.google.common.primitives.Ints; import com.google.common.primitives.Shorts; import com.google.common.primitives.SignedBytes; +import io.airlift.log.Level; +import io.airlift.log.Logging; import io.airlift.slice.DynamicSliceOutput; import io.airlift.slice.SliceOutput; import io.trino.hive.formats.FormatTestUtils; @@ -72,8 +74,6 @@ import java.util.Optional; import java.util.Properties; import java.util.function.LongFunction; -import java.util.logging.Level; -import java.util.logging.Logger; import java.util.stream.Collectors; import static com.google.common.collect.ImmutableList.toImmutableList; @@ -125,8 +125,9 @@ public class TestOpenxJsonFormat { static { + Logging logging = Logging.initialize(); // Increase the level of the JsonSerDe logger as it is excessively logs - Logger.getLogger(JsonSerDe.class.getName()).setLevel(Level.SEVERE); + logging.setLevel(JsonSerDe.class.getName(), Level.ERROR); } private static final TypeOperators TYPE_OPERATORS = new TypeOperators(); From cbad236cdb89f2408cce2f1f95822612644a1298 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Fri, 10 Nov 2023 13:58:35 -0800 Subject: [PATCH 200/587] Remove spurious logging in tests --- .../java/io/trino/server/testing/TestingTrinoServer.java | 7 +++++++ lib/trino-hive-formats/pom.xml | 6 ++++++ .../io/trino/plugin/accumulo/AccumuloQueryRunner.java | 9 +++++++++ .../io/trino/plugin/cassandra/CassandraQueryRunner.java | 7 +++++++ .../java/io/trino/plugin/cassandra/CassandraServer.java | 8 ++++---- .../trino/plugin/clickhouse/ClickHouseQueryRunner.java | 7 +++++++ .../io/trino/plugin/deltalake/DeltaLakeQueryRunner.java | 9 +++++++++ .../plugin/elasticsearch/ElasticsearchQueryRunner.java | 7 +++++++ .../test/java/io/trino/plugin/hudi/HudiQueryRunner.java | 6 ++++++ .../java/io/trino/plugin/iceberg/IcebergQueryRunner.java | 6 ++++++ .../java/io/trino/plugin/kafka/KafkaQueryRunner.java | 6 ++++++ .../java/io/trino/plugin/mariadb/MariaDbQueryRunner.java | 7 +++++++ .../java/io/trino/plugin/mongodb/MongoQueryRunner.java | 7 +++++++ .../io/trino/plugin/sqlserver/SqlServerQueryRunner.java | 7 +++++++ 14 files changed, 95 insertions(+), 4 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/server/testing/TestingTrinoServer.java b/core/trino-main/src/main/java/io/trino/server/testing/TestingTrinoServer.java index 9486c0181b97..c3539fef240c 100644 --- a/core/trino-main/src/main/java/io/trino/server/testing/TestingTrinoServer.java +++ b/core/trino-main/src/main/java/io/trino/server/testing/TestingTrinoServer.java @@ -34,6 +34,8 @@ import io.airlift.jaxrs.JaxrsModule; import io.airlift.jmx.testing.TestingJmxModule; import io.airlift.json.JsonModule; +import io.airlift.log.Level; +import io.airlift.log.Logging; import io.airlift.node.testing.TestingNodeModule; import io.airlift.openmetrics.JmxOpenMetricsModule; import io.airlift.tracetoken.TraceTokenModule; @@ -139,6 +141,11 @@ public class TestingTrinoServer implements Closeable { + static { + Logging logging = Logging.initialize(); + logging.setLevel("io.trino.event.QueryMonitor", Level.ERROR); + } + private static final String VERSION = "testversion"; public static TestingTrinoServer create() diff --git a/lib/trino-hive-formats/pom.xml b/lib/trino-hive-formats/pom.xml index c65f3c421e51..546be8bbdd15 100644 --- a/lib/trino-hive-formats/pom.xml +++ b/lib/trino-hive-formats/pom.xml @@ -120,6 +120,12 @@ runtime + + io.airlift + log-manager + runtime + + org.xerial.snappy diff --git a/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/AccumuloQueryRunner.java b/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/AccumuloQueryRunner.java index aef6b7082850..add51e85ec16 100644 --- a/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/AccumuloQueryRunner.java +++ b/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/AccumuloQueryRunner.java @@ -15,7 +15,9 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSortedSet; +import io.airlift.log.Level; import io.airlift.log.Logger; +import io.airlift.log.Logging; import io.trino.Session; import io.trino.metadata.QualifiedObjectName; import io.trino.plugin.accumulo.conf.AccumuloConfig; @@ -38,6 +40,13 @@ public final class AccumuloQueryRunner { + static { + Logging logging = Logging.initialize(); + logging.setLevel("org.apache.accumulo", Level.OFF); + logging.setLevel("org.apache.zookeeper", Level.OFF); + logging.setLevel("org.apache.curator", Level.OFF); + } + private static final Logger LOG = Logger.get(AccumuloQueryRunner.class); private static boolean tpchLoaded; diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/CassandraQueryRunner.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/CassandraQueryRunner.java index 79afa781aaf2..3976685ddf3f 100644 --- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/CassandraQueryRunner.java +++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/CassandraQueryRunner.java @@ -15,7 +15,9 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import io.airlift.log.Level; import io.airlift.log.Logger; +import io.airlift.log.Logging; import io.trino.Session; import io.trino.plugin.tpch.TpchPlugin; import io.trino.testing.DistributedQueryRunner; @@ -31,6 +33,11 @@ public final class CassandraQueryRunner { + static { + Logging logging = Logging.initialize(); + logging.setLevel("com.datastax.oss.driver.internal", Level.OFF); + } + private CassandraQueryRunner() {} public static DistributedQueryRunner createCassandraQueryRunner(CassandraServer server, TpchTable... tables) diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/CassandraServer.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/CassandraServer.java index 3011bbca208d..a6960e7b1a73 100644 --- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/CassandraServer.java +++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/CassandraServer.java @@ -82,7 +82,7 @@ public CassandraServer(String imageName, String configFileName) public CassandraServer(String imageName, Map environmentVariables, String configPath, String configFileName) throws Exception { - log.info("Starting cassandra..."); + log.debug("Starting cassandra..."); this.dockerContainer = new GenericContainer<>(imageName) .withExposedPorts(PORT) @@ -161,7 +161,7 @@ private static void checkConnectivity(CassandraSession session) List rows = result.all(); assertEquals(rows.size(), 1); String version = rows.get(0).getString(0); - log.info("Cassandra version: %s", version); + log.debug("Cassandra version: %s", version); } public void refreshSizeEstimates(String keyspace, String table) @@ -173,10 +173,10 @@ public void refreshSizeEstimates(String keyspace, String table) refreshSizeEstimates(); List sizeEstimates = getSession().getSizeEstimates(keyspace, table); if (!sizeEstimates.isEmpty()) { - log.info("Size estimates for the table %s.%s have been refreshed successfully: %s", keyspace, table, sizeEstimates); + log.debug("Size estimates for the table %s.%s have been refreshed successfully: %s", keyspace, table, sizeEstimates); return; } - log.info("Size estimates haven't been refreshed as expected. Retrying ..."); + log.debug("Size estimates haven't been refreshed as expected. Retrying ..."); SECONDS.sleep(1); } throw new TimeoutException(format("Attempting to refresh size estimates for table %s.%s has timed out after %s", keyspace, table, REFRESH_SIZE_ESTIMATES_TIMEOUT)); diff --git a/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/ClickHouseQueryRunner.java b/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/ClickHouseQueryRunner.java index 90d9039458d2..282662a2dc68 100644 --- a/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/ClickHouseQueryRunner.java +++ b/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/ClickHouseQueryRunner.java @@ -15,7 +15,9 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import io.airlift.log.Level; import io.airlift.log.Logger; +import io.airlift.log.Logging; import io.trino.Session; import io.trino.plugin.tpch.TpchPlugin; import io.trino.testing.DistributedQueryRunner; @@ -32,6 +34,11 @@ public final class ClickHouseQueryRunner { + static { + Logging logging = Logging.initialize(); + logging.setLevel("com.clickhouse.jdbc.internal", Level.OFF); + } + public static final String TPCH_SCHEMA = "tpch"; private ClickHouseQueryRunner() {} diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/DeltaLakeQueryRunner.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/DeltaLakeQueryRunner.java index a0a832626cab..bcc59f61ac1f 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/DeltaLakeQueryRunner.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/DeltaLakeQueryRunner.java @@ -15,7 +15,9 @@ import com.google.common.collect.ImmutableMap; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import io.airlift.log.Level; import io.airlift.log.Logger; +import io.airlift.log.Logging; import io.trino.Session; import io.trino.plugin.hive.containers.HiveHadoop; import io.trino.plugin.hive.containers.HiveMinioDataLake; @@ -46,6 +48,13 @@ public final class DeltaLakeQueryRunner { + static { + Logging logging = Logging.initialize(); + logging.setLevel("org.apache.parquet.filter2.compat.FilterCompat", Level.OFF); + logging.setLevel("com.amazonaws.util.Base64", Level.OFF); + logging.setLevel("com.google.cloud", Level.OFF); + } + private static final Logger log = Logger.get(DeltaLakeQueryRunner.class); public static final String DELTA_CATALOG = "delta"; public static final String TPCH_SCHEMA = "tpch"; diff --git a/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/ElasticsearchQueryRunner.java b/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/ElasticsearchQueryRunner.java index 7e01c4ed17c7..73be8fd31751 100644 --- a/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/ElasticsearchQueryRunner.java +++ b/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/ElasticsearchQueryRunner.java @@ -15,7 +15,9 @@ import com.google.common.collect.ImmutableMap; import com.google.common.net.HostAndPort; +import io.airlift.log.Level; import io.airlift.log.Logger; +import io.airlift.log.Logging; import io.trino.metadata.QualifiedObjectName; import io.trino.plugin.jmx.JmxPlugin; import io.trino.plugin.tpch.TpchPlugin; @@ -40,6 +42,11 @@ public final class ElasticsearchQueryRunner { + static { + Logging logging = Logging.initialize(); + logging.setLevel("org.elasticsearch.client.RestClient", Level.OFF); + } + private ElasticsearchQueryRunner() {} private static final Logger LOG = Logger.get(ElasticsearchQueryRunner.class); diff --git a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/HudiQueryRunner.java b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/HudiQueryRunner.java index ffeb365de864..61b3f6b65ed9 100644 --- a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/HudiQueryRunner.java +++ b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/HudiQueryRunner.java @@ -14,6 +14,7 @@ package io.trino.plugin.hudi; import com.google.common.collect.ImmutableMap; +import io.airlift.log.Level; import io.airlift.log.Logger; import io.airlift.log.Logging; import io.trino.Session; @@ -36,6 +37,11 @@ public final class HudiQueryRunner { + static { + Logging logging = Logging.initialize(); + logging.setLevel("org.apache.hudi", Level.OFF); + } + private static final String SCHEMA_NAME = "tests"; private HudiQueryRunner() {} diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/IcebergQueryRunner.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/IcebergQueryRunner.java index b75c67b9ed10..b38adf6aea7f 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/IcebergQueryRunner.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/IcebergQueryRunner.java @@ -17,6 +17,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.io.Resources; import io.airlift.http.server.testing.TestingHttpServer; +import io.airlift.log.Level; import io.airlift.log.Logger; import io.airlift.log.Logging; import io.trino.plugin.hive.containers.HiveHadoop; @@ -55,6 +56,11 @@ public final class IcebergQueryRunner { public static final String ICEBERG_CATALOG = "iceberg"; + static { + Logging logging = Logging.initialize(); + logging.setLevel("org.apache.iceberg", Level.OFF); + } + private IcebergQueryRunner() {} public static DistributedQueryRunner createIcebergQueryRunner(TpchTable... tables) diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/KafkaQueryRunner.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/KafkaQueryRunner.java index d47bb15ebd46..6f49442393da 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/KafkaQueryRunner.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/KafkaQueryRunner.java @@ -17,6 +17,7 @@ import com.google.common.collect.ImmutableMap; import com.google.inject.Scopes; import io.airlift.json.JsonCodec; +import io.airlift.log.Level; import io.airlift.log.Logger; import io.airlift.log.Logging; import io.trino.decoder.DecoderModule; @@ -50,6 +51,11 @@ public final class KafkaQueryRunner { + static { + Logging logging = Logging.initialize(); + logging.setLevel("org.apache.kafka", Level.OFF); + } + private KafkaQueryRunner() {} private static final Logger log = Logger.get(KafkaQueryRunner.class); diff --git a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/MariaDbQueryRunner.java b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/MariaDbQueryRunner.java index 578a1017ab19..a1530de0d076 100644 --- a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/MariaDbQueryRunner.java +++ b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/MariaDbQueryRunner.java @@ -14,7 +14,9 @@ package io.trino.plugin.mariadb; import com.google.common.collect.ImmutableMap; +import io.airlift.log.Level; import io.airlift.log.Logger; +import io.airlift.log.Logging; import io.trino.Session; import io.trino.plugin.tpch.TpchPlugin; import io.trino.testing.DistributedQueryRunner; @@ -30,6 +32,11 @@ public final class MariaDbQueryRunner { + static { + Logging logging = Logging.initialize(); + logging.setLevel("org.mariadb.jdbc", Level.OFF); + } + private static final String TPCH_SCHEMA = "tpch"; private MariaDbQueryRunner() {} diff --git a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/MongoQueryRunner.java b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/MongoQueryRunner.java index 846ba23bf818..2d513fed466f 100644 --- a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/MongoQueryRunner.java +++ b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/MongoQueryRunner.java @@ -16,7 +16,9 @@ import com.google.common.collect.ImmutableMap; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; +import io.airlift.log.Level; import io.airlift.log.Logger; +import io.airlift.log.Logging; import io.trino.Session; import io.trino.plugin.tpch.TpchPlugin; import io.trino.testing.DistributedQueryRunner; @@ -34,6 +36,11 @@ public final class MongoQueryRunner { + static { + Logging logging = Logging.initialize(); + logging.setLevel("org.mongodb.driver", Level.OFF); + } + private static final String TPCH_SCHEMA = "tpch"; private MongoQueryRunner() {} diff --git a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/SqlServerQueryRunner.java b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/SqlServerQueryRunner.java index 3b23bf818eda..43782631e8a3 100644 --- a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/SqlServerQueryRunner.java +++ b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/SqlServerQueryRunner.java @@ -14,7 +14,9 @@ package io.trino.plugin.sqlserver; import com.google.common.collect.ImmutableMap; +import io.airlift.log.Level; import io.airlift.log.Logger; +import io.airlift.log.Logging; import io.trino.Session; import io.trino.plugin.tpch.TpchPlugin; import io.trino.spi.security.Identity; @@ -33,6 +35,11 @@ public final class SqlServerQueryRunner { + static { + Logging logging = Logging.initialize(); + logging.setLevel("com.microsoft.sqlserver.jdbc", Level.OFF); + } + private static final Logger log = Logger.get(SqlServerQueryRunner.class); private SqlServerQueryRunner() {} From a134f57dec87bf9f6f3ed582f1bc1b8a7cfe73c4 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Wed, 8 Nov 2023 11:00:01 -0800 Subject: [PATCH 201/587] Turn off bootstrap logging in tests --- .../trino/jdbc/TestTrinoDatabaseMetaData.java | 1 + ...seFileBasedConnectorAccessControlTest.java | 1 + .../TestFileBasedSystemAccessControl.java | 7 ++- .../plugin/base/util/TestingHttpServer.java | 1 + .../io/trino/plugin/atop/TestAtopPlugin.java | 7 ++- .../io/trino/plugin/jdbc/TestJmxStats.java | 7 ++- .../plugin/jdbc/TestingH2JdbcModule.java | 4 +- .../plugin/bigquery/TestBigQueryPlugin.java | 3 +- .../cassandra/TestCassandraConnector.java | 3 +- .../clickhouse/TestClickHousePlugin.java | 7 ++- .../plugin/deltalake/TestDeltaLakePlugin.java | 41 ++++++++++---- .../plugin/druid/TestDruidJdbcPlugin.java | 7 ++- .../google/sheets/TestSheetsPlugin.java | 5 +- .../io/trino/plugin/hive/TestHivePlugin.java | 42 ++++++++++++--- .../plugin/hive/TestHiveConnectorFactory.java | 4 +- .../hive/TestingHiveConnectorFactory.java | 14 ++++- .../plugin/hudi/TestHudiConnectorFactory.java | 4 +- .../io/trino/plugin/hudi/TestHudiPlugin.java | 29 +++++++--- .../iceberg/TestIcebergConnectorFactory.java | 5 +- .../plugin/iceberg/TestIcebergPlugin.java | 53 +++++++++++++------ .../trino/plugin/ignite/TestIgnitePlugin.java | 7 ++- .../trino/plugin/jmx/TestJmxSplitManager.java | 3 +- .../trino/plugin/kafka/TestKafkaPlugin.java | 6 +++ .../plugin/kinesis/TestKinesisPlugin.java | 1 + .../TestKinesisTableDescriptionSupplier.java | 1 + .../s3config/TestS3TableConfigClient.java | 1 + .../io/trino/plugin/kudu/TestKuduPlugin.java | 7 ++- .../plugin/mariadb/TestMariaDbPlugin.java | 21 ++++++-- .../trino/plugin/mongodb/TestMongoPlugin.java | 7 ++- .../trino/plugin/mysql/TestMySqlPlugin.java | 20 +++++-- .../trino/plugin/oracle/TestOraclePlugin.java | 7 ++- .../plugin/phoenix5/TestPhoenixPlugin.java | 7 ++- .../postgresql/TestPostgreSqlPlugin.java | 7 ++- .../raptor/legacy/TestRaptorPlugin.java | 1 + .../trino/plugin/redis/TestRedisPlugin.java | 1 + .../plugin/redshift/TestRedshiftPlugin.java | 7 ++- .../TestSingleStoreJdbcConfig.java | 6 ++- .../singlestore/TestSingleStorePlugin.java | 7 ++- .../plugin/sqlserver/TestSqlServerPlugin.java | 7 ++- .../trino/plugin/thrift/TestThriftPlugin.java | 4 +- .../planner/BaseIcebergCostBasedPlanTest.java | 1 + 41 files changed, 302 insertions(+), 72 deletions(-) diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDatabaseMetaData.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDatabaseMetaData.java index b45dd11edaf4..b4c1b4f06a06 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDatabaseMetaData.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDatabaseMetaData.java @@ -122,6 +122,7 @@ public void setupServer() .put("hive.metastore", "file") .put("hive.metastore.catalog.dir", server.getBaseDataDir().resolve("hive").toAbsolutePath().toString()) .put("hive.security", "sql-standard") + .put("bootstrap.quiet", "true") .buildOrThrow()); countingMockConnector = new CountingMockConnector(); diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedConnectorAccessControlTest.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedConnectorAccessControlTest.java index 75d92892b4a9..1e2c0cabd4a9 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedConnectorAccessControlTest.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedConnectorAccessControlTest.java @@ -811,6 +811,7 @@ protected ConnectorAccessControl createAccessControl(Map configP Injector injector = bootstrap .doNotInitializeLogging() + .quiet() .setRequiredConfigurationProperties(configProperties) .initialize(); diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestFileBasedSystemAccessControl.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestFileBasedSystemAccessControl.java index 405ac1624ca1..e70d6e206f33 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestFileBasedSystemAccessControl.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestFileBasedSystemAccessControl.java @@ -25,7 +25,10 @@ public class TestFileBasedSystemAccessControl @Override protected SystemAccessControl newFileBasedSystemAccessControl(File configFile, Map properties) { - return newFileBasedSystemAccessControl(ImmutableMap.builder().putAll(properties).put("security.config-file", - configFile.getAbsolutePath()).buildOrThrow()); + return newFileBasedSystemAccessControl(ImmutableMap.builder() + .putAll(properties) + .put("security.config-file", configFile.getAbsolutePath()) + .put("bootstrap.quiet", "true") + .buildOrThrow()); } } diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/util/TestingHttpServer.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/util/TestingHttpServer.java index 77cba7e0b3cc..47e07a3c19ee 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/util/TestingHttpServer.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/util/TestingHttpServer.java @@ -53,6 +53,7 @@ public TestingHttpServer() Injector injector = app .doNotInitializeLogging() + .quiet() .initialize(); lifeCycleManager = injector.getInstance(LifeCycleManager.class); diff --git a/plugin/trino-atop/src/test/java/io/trino/plugin/atop/TestAtopPlugin.java b/plugin/trino-atop/src/test/java/io/trino/plugin/atop/TestAtopPlugin.java index b070ba0e176c..09665c2e662f 100644 --- a/plugin/trino-atop/src/test/java/io/trino/plugin/atop/TestAtopPlugin.java +++ b/plugin/trino-atop/src/test/java/io/trino/plugin/atop/TestAtopPlugin.java @@ -37,6 +37,11 @@ public void testCreateConnector() ConnectorFactory factory = getOnlyElement(plugin.getConnectorFactories()); Path atopExecutable = Files.createTempFile(null, null); - factory.create("test", ImmutableMap.of("atop.executable-path", atopExecutable.toString()), new TestingConnectorContext()).shutdown(); + factory.create( + "test", + ImmutableMap.of( + "atop.executable-path", atopExecutable.toString(), + "bootstrap.quiet", "true"), + new TestingConnectorContext()).shutdown(); } } diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestJmxStats.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestJmxStats.java index d8c1d4d3ea22..26be008f3309 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestJmxStats.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestJmxStats.java @@ -39,7 +39,12 @@ public void testJmxStatsExposure() { Plugin plugin = new JdbcPlugin("base_jdbc", new TestingH2JdbcModule()); ConnectorFactory factory = getOnlyElement(plugin.getConnectorFactories()); - factory.create("test", ImmutableMap.of("connection-url", "jdbc:driver:"), new TestingConnectorContext()); + factory.create( + "test", + ImmutableMap.of( + "connection-url", "jdbc:driver:", + "bootstrap.quiet", "true"), + new TestingConnectorContext()); MBeanServer mbeanServer = getPlatformMBeanServer(); Set objectNames = mbeanServer.queryNames(new ObjectName("io.trino.plugin.jdbc:*"), null); diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestingH2JdbcModule.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestingH2JdbcModule.java index 814450973758..34ad771b68d4 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestingH2JdbcModule.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestingH2JdbcModule.java @@ -71,7 +71,9 @@ public ConnectionFactory getConnectionFactory(BaseJdbcConfig config, CredentialP public static Map createProperties() { - return ImmutableMap.of("connection-url", createH2ConnectionUrl()); + return ImmutableMap.of( + "connection-url", createH2ConnectionUrl(), + "bootstrap.quiet", "true"); } public static String createH2ConnectionUrl() diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryPlugin.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryPlugin.java index d3bf4f294c8c..85908c42baad 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryPlugin.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryPlugin.java @@ -35,7 +35,8 @@ public void testCreateConnector() "test", Map.of( "bigquery.project-id", "xxx", - "bigquery.credentials-key", "ewogICAgInR5cGUiOiAic2VydmljZV9hY2NvdW50IiwKICAgICJwcm9qZWN0X2lkIjogInByZXN0b3Rlc3QiLAogICAgInByaXZhdGVfa2V5X2lkIjogIngiLAogICAgInByaXZhdGVfa2V5IjogIi0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuTUlJRXZRSUJBREFOQmdrcWhraUc5dzBCQVFFRkFBU0NCS2N3Z2dTakFnRUFBb0lCQVFDM3E0NkcrdlRtdllmR1xuUEVCcUZST2MwWllEUFE4Z1VRYlEvaWRiYXBQc0s3TUxIZEx1RUdzQkF4SjMyYkdKV2FXV1pKMlZvKzA2Y3E4UlxuM1VZdVJ5RDBvNVk5OTV3d0t5YUVMdjVHTHFxMlpkQ0U2MGNqbE8yeXM4dUg4MVJMQStOME9zUzI0WXAvTmw1V1xuMHl4bkVuaW5VSW5Hb1VteVJ0K1V1aTIxNTQ3WTZFeDFKMGdqVndoNWtBNTJBcG0xVzVjZ3JKUWgwMlZTNUZERlxudEtnWjlKNFUyZVQvM2RNUkFlN0dLaWtseGpMTktjSzJ6T3JuYVpzb0pBTnNrZ0xMdjhPaDJEdlpQbWd1dmtxL1xuUU4yRVRCSXVLRUxCbEN4ZHZkRVp5L2pPcUVmdW1xcWI4VTVqVk4vdit6Q0pZVHREcVRGVWxLRVZDbEVNV3BCUFxuR3dIUzg5MlRBZ01CQUFFQ2dnRUFVOWxuTE9vZXFjUTIydUlneWcwck1mbGdrY1ByUnVhV3hReHlMVUsvbXg3c1xuRXhRZmVuMVdURlQ1dG10VXFJNmJrTWdJUlF0Y1BzV2lkUFplbHJ2MEtKc1IrT0kwbEt6dVhZUVNvem1reDdZOVxuZHFEdWppanNSeHZidkFuekhuZjgrOC9raEZUODVFeU96dmFERzk4TDQ5NVp0NnRrT0pZd2RmWjA3Y2x6cGtQSVxudVNDMGRTMldFckZnT0JiK3BJZGFwU3dSN1gxOTlROGNsenhjYlVUUUJJaGJDMnFhUmUvelFBdHNIS3ArMHRRVFxuWEI0Z3A1bitXc0pGM2lmTlYwdkZ0VWRRUlNCNFBmRzExRW1lczBQTFpxV1ZYb2xGdWpVQW8xS0o3dXFWTVpoUlxuQTF6VEpEbzNaaklHUllvbmRHQWRHR1hrMU1rd1JCcGoxR0FRb08xSm9RS0JnUURjUFhLcjF0MlBVbnJMelN1UFxuNVM2ek9WMUVzNkpJbmpVaGtXNFFhcTQ4RFRYNEg2bkdTYkdyYW5tbVpyQXlWNytEeXZPWGVzaC9ITzJROWtlaFxuRlczaUVtQzBCZE9FeWVuRUJUNThidHR5VzlMVUtBVjhjendYTno4Q3lSQ0xGd292UDIzUjFkS3BZdGtsR0l6NVxuWjJaMEF1SEtzcWd2TC9Jeng2bU1QNU0rQXdLQmdRRFZmZ2ttMUJPVzhad3JvNFFjbE43bnlKN01lQ3BCTFZycVxuUU9OcThqeE5EaGpsT0VDSElZZmFTUUZLYXkwa2pBTndTQjRMYURuTXJTbmRJYWxqV3F1LzBtdThMLzNwQzg4MlxuOUhpNU1Mc1Jjb0trNDY5UnRLRVIxWEwvcE5sb1NTd2dkZWJtUWk3clhNUzFCQks4aFZ0UFFObmV0RE1sLy9JTFxuS3YzbmtycFZNUUtCZ0dnaUdiVU1PK2dIUEk1ZUxRbTFlRFkvbWt6Z2pvdTlXaXZNQW5sNnAzVTNYZHc2eEdBL1xuK2VTdHpHVVVTcDBUQmpkL1gxdXhMMW1DeVFUd25YK1pqVUlHSkhrYUJCL1dCRlN0a2hUdHFZN1J3Y2FVUWJ2TlxuRkkxNWpxNTNlUDM2MzlMbEw3eTJXQXZFOUJ6cEZjYmEwQU5zVld3c3V2N01zYjB2MjRlM2k1d1hBb0dCQUswWlxuL2kyZmN5ckdTRXdSendLbHFuN2c2ZkQ3MWJiM0lXb2lwc0tHR21LWDlaT1ZvcXh1Z1lwNSt6UHQ1ckpsWER4a1xuSFFnK3YrNjIwT1RkY0V5QXJoVmdkYjRtWTRmYjdXMnZsMXNBcWcwaGZkQllWRVM1WW9mbE85TVFSTDhMNVYyRVxuZTIxamFFdXA4a3liT3Qza2V2NnRwSG13UG5Dbk1BZmlHZkR6eFdWaEFvR0FYa1k5bjNsSDFISDJBUDhzMkNnNVxuN3o3NVhLYWtxWE9CMkNhTWFuOWxJd0FCVzhSam1IKzRiU2VVQ0kwM1hRRExrY1R3T0N3QStrL3FvZldBeW1ldVxudzU4Vzh5cGlWVGpDVDErUzh5VjhYL0htTERVa3VsTnUvY2psYlJPdnJmSlRIL2pNbVhhTEQxeVZlYXlxOFlGZFxubnl6SmpiR1BwdGsvYVRTYk5rQmpvdWM9XG4tLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tXG4iLAogICAgImNsaWVudF9lbWFpbCI6ICJ4IiwKICAgICJjbGllbnRfaWQiOiAieCIsCiAgICAiYXV0aF91cmkiOiAiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLAogICAgInRva2VuX3VyaSI6ICJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsCiAgICAiYXV0aF9wcm92aWRlcl94NTA5X2NlcnRfdXJsIjogImh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsCiAgICAiY2xpZW50X3g1MDlfY2VydF91cmwiOiAiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9wcmVzdG90ZXN0JTQwcHJlc3RvdGVzdC5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIKfQo="), + "bigquery.credentials-key", "ewogICAgInR5cGUiOiAic2VydmljZV9hY2NvdW50IiwKICAgICJwcm9qZWN0X2lkIjogInByZXN0b3Rlc3QiLAogICAgInByaXZhdGVfa2V5X2lkIjogIngiLAogICAgInByaXZhdGVfa2V5IjogIi0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuTUlJRXZRSUJBREFOQmdrcWhraUc5dzBCQVFFRkFBU0NCS2N3Z2dTakFnRUFBb0lCQVFDM3E0NkcrdlRtdllmR1xuUEVCcUZST2MwWllEUFE4Z1VRYlEvaWRiYXBQc0s3TUxIZEx1RUdzQkF4SjMyYkdKV2FXV1pKMlZvKzA2Y3E4UlxuM1VZdVJ5RDBvNVk5OTV3d0t5YUVMdjVHTHFxMlpkQ0U2MGNqbE8yeXM4dUg4MVJMQStOME9zUzI0WXAvTmw1V1xuMHl4bkVuaW5VSW5Hb1VteVJ0K1V1aTIxNTQ3WTZFeDFKMGdqVndoNWtBNTJBcG0xVzVjZ3JKUWgwMlZTNUZERlxudEtnWjlKNFUyZVQvM2RNUkFlN0dLaWtseGpMTktjSzJ6T3JuYVpzb0pBTnNrZ0xMdjhPaDJEdlpQbWd1dmtxL1xuUU4yRVRCSXVLRUxCbEN4ZHZkRVp5L2pPcUVmdW1xcWI4VTVqVk4vdit6Q0pZVHREcVRGVWxLRVZDbEVNV3BCUFxuR3dIUzg5MlRBZ01CQUFFQ2dnRUFVOWxuTE9vZXFjUTIydUlneWcwck1mbGdrY1ByUnVhV3hReHlMVUsvbXg3c1xuRXhRZmVuMVdURlQ1dG10VXFJNmJrTWdJUlF0Y1BzV2lkUFplbHJ2MEtKc1IrT0kwbEt6dVhZUVNvem1reDdZOVxuZHFEdWppanNSeHZidkFuekhuZjgrOC9raEZUODVFeU96dmFERzk4TDQ5NVp0NnRrT0pZd2RmWjA3Y2x6cGtQSVxudVNDMGRTMldFckZnT0JiK3BJZGFwU3dSN1gxOTlROGNsenhjYlVUUUJJaGJDMnFhUmUvelFBdHNIS3ArMHRRVFxuWEI0Z3A1bitXc0pGM2lmTlYwdkZ0VWRRUlNCNFBmRzExRW1lczBQTFpxV1ZYb2xGdWpVQW8xS0o3dXFWTVpoUlxuQTF6VEpEbzNaaklHUllvbmRHQWRHR1hrMU1rd1JCcGoxR0FRb08xSm9RS0JnUURjUFhLcjF0MlBVbnJMelN1UFxuNVM2ek9WMUVzNkpJbmpVaGtXNFFhcTQ4RFRYNEg2bkdTYkdyYW5tbVpyQXlWNytEeXZPWGVzaC9ITzJROWtlaFxuRlczaUVtQzBCZE9FeWVuRUJUNThidHR5VzlMVUtBVjhjendYTno4Q3lSQ0xGd292UDIzUjFkS3BZdGtsR0l6NVxuWjJaMEF1SEtzcWd2TC9Jeng2bU1QNU0rQXdLQmdRRFZmZ2ttMUJPVzhad3JvNFFjbE43bnlKN01lQ3BCTFZycVxuUU9OcThqeE5EaGpsT0VDSElZZmFTUUZLYXkwa2pBTndTQjRMYURuTXJTbmRJYWxqV3F1LzBtdThMLzNwQzg4MlxuOUhpNU1Mc1Jjb0trNDY5UnRLRVIxWEwvcE5sb1NTd2dkZWJtUWk3clhNUzFCQks4aFZ0UFFObmV0RE1sLy9JTFxuS3YzbmtycFZNUUtCZ0dnaUdiVU1PK2dIUEk1ZUxRbTFlRFkvbWt6Z2pvdTlXaXZNQW5sNnAzVTNYZHc2eEdBL1xuK2VTdHpHVVVTcDBUQmpkL1gxdXhMMW1DeVFUd25YK1pqVUlHSkhrYUJCL1dCRlN0a2hUdHFZN1J3Y2FVUWJ2TlxuRkkxNWpxNTNlUDM2MzlMbEw3eTJXQXZFOUJ6cEZjYmEwQU5zVld3c3V2N01zYjB2MjRlM2k1d1hBb0dCQUswWlxuL2kyZmN5ckdTRXdSendLbHFuN2c2ZkQ3MWJiM0lXb2lwc0tHR21LWDlaT1ZvcXh1Z1lwNSt6UHQ1ckpsWER4a1xuSFFnK3YrNjIwT1RkY0V5QXJoVmdkYjRtWTRmYjdXMnZsMXNBcWcwaGZkQllWRVM1WW9mbE85TVFSTDhMNVYyRVxuZTIxamFFdXA4a3liT3Qza2V2NnRwSG13UG5Dbk1BZmlHZkR6eFdWaEFvR0FYa1k5bjNsSDFISDJBUDhzMkNnNVxuN3o3NVhLYWtxWE9CMkNhTWFuOWxJd0FCVzhSam1IKzRiU2VVQ0kwM1hRRExrY1R3T0N3QStrL3FvZldBeW1ldVxudzU4Vzh5cGlWVGpDVDErUzh5VjhYL0htTERVa3VsTnUvY2psYlJPdnJmSlRIL2pNbVhhTEQxeVZlYXlxOFlGZFxubnl6SmpiR1BwdGsvYVRTYk5rQmpvdWM9XG4tLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tXG4iLAogICAgImNsaWVudF9lbWFpbCI6ICJ4IiwKICAgICJjbGllbnRfaWQiOiAieCIsCiAgICAiYXV0aF91cmkiOiAiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLAogICAgInRva2VuX3VyaSI6ICJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsCiAgICAiYXV0aF9wcm92aWRlcl94NTA5X2NlcnRfdXJsIjogImh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsCiAgICAiY2xpZW50X3g1MDlfY2VydF91cmwiOiAiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9wcmVzdG90ZXN0JTQwcHJlc3RvdGVzdC5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIKfQo=", + "bootstrap.quiet", "true"), new TestingConnectorContext()) .shutdown(); } diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnector.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnector.java index b77504ebf5dd..1eb5bc82075c 100644 --- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnector.java +++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnector.java @@ -128,7 +128,8 @@ public void setup() "cassandra.contact-points", server.getHost(), "cassandra.load-policy.use-dc-aware", "true", "cassandra.load-policy.dc-aware.local-dc", "datacenter1", - "cassandra.native-protocol-port", Integer.toString(server.getPort())), + "cassandra.native-protocol-port", Integer.toString(server.getPort()), + "bootstrap.quiet", "true"), new TestingConnectorContext()); metadata = connector.getMetadata(SESSION, CassandraTransactionHandle.INSTANCE); diff --git a/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/TestClickHousePlugin.java b/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/TestClickHousePlugin.java index 9aac64931529..ba6387cd267e 100644 --- a/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/TestClickHousePlugin.java +++ b/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/TestClickHousePlugin.java @@ -28,6 +28,11 @@ public void testCreateConnector() { Plugin plugin = new ClickHousePlugin(); ConnectorFactory factory = getOnlyElement(plugin.getConnectorFactories()); - factory.create("test", ImmutableMap.of("connection-url", "jdbc:clickhouse://test"), new TestingConnectorContext()); + factory.create( + "test", + ImmutableMap.of( + "connection-url", "jdbc:clickhouse://test", + "bootstrap.quiet", "true"), + new TestingConnectorContext()); } } diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakePlugin.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakePlugin.java index bbb315209d27..cf64442880cc 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakePlugin.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakePlugin.java @@ -34,7 +34,12 @@ public class TestDeltaLakePlugin public void testCreateConnector() { ConnectorFactory factory = getConnectorFactory(); - factory.create("test", ImmutableMap.of("hive.metastore.uri", "thrift://foo:1234"), new TestingConnectorContext()) + factory.create( + "test", + ImmutableMap.of( + "hive.metastore.uri", "thrift://foo:1234", + "bootstrap.quiet", "true"), + new TestingConnectorContext()) .shutdown(); } @@ -43,7 +48,12 @@ public void testCreateTestingConnector() { Plugin plugin = new TestingDeltaLakePlugin(); ConnectorFactory factory = getOnlyElement(plugin.getConnectorFactories()); - factory.create("test", ImmutableMap.of("hive.metastore.uri", "thrift://foo:1234"), new TestingConnectorContext()) + factory.create( + "test", + ImmutableMap.of( + "hive.metastore.uri", "thrift://foo:1234", + "bootstrap.quiet", "true"), + new TestingConnectorContext()) .shutdown(); } @@ -55,7 +65,8 @@ public void testTestingFileMetastore() "test", ImmutableMap.of( "hive.metastore", "file", - "hive.metastore.catalog.dir", "/tmp"), + "hive.metastore.catalog.dir", "/tmp", + "bootstrap.quiet", "true"), new TestingConnectorContext()) .shutdown(); } @@ -68,7 +79,8 @@ public void testThriftMetastore() "test", ImmutableMap.of( "hive.metastore", "thrift", - "hive.metastore.uri", "thrift://foo:1234"), + "hive.metastore.uri", "thrift://foo:1234", + "bootstrap.quiet", "true"), new TestingConnectorContext()) .shutdown(); @@ -77,7 +89,8 @@ public void testThriftMetastore() ImmutableMap.of( "hive.metastore", "thrift", "hive.metastore.uri", "thrift://foo:1234", - "delta.hide-non-delta-lake-tables", "true"), + "delta.hide-non-delta-lake-tables", "true", + "bootstrap.quiet", "true"), new TestingConnectorContext())) .isInstanceOf(ApplicationConfigurationException.class) // TODO support delta.hide-non-delta-lake-tables with thrift metastore @@ -92,7 +105,8 @@ public void testGlueMetastore() "test", ImmutableMap.of( "hive.metastore", "glue", - "hive.metastore.glue.region", "us-east-2"), + "hive.metastore.glue.region", "us-east-2", + "bootstrap.quiet", "true"), new TestingConnectorContext()) .shutdown(); @@ -100,7 +114,8 @@ public void testGlueMetastore() "test", ImmutableMap.of( "hive.metastore", "glue", - "hive.metastore.uri", "thrift://foo:1234"), + "hive.metastore.uri", "thrift://foo:1234", + "bootstrap.quiet", "true"), new TestingConnectorContext())) .isInstanceOf(ApplicationConfigurationException.class) .hasMessageContaining("Error: Configuration property 'hive.metastore.uri' was not used"); @@ -113,7 +128,8 @@ public void testNoCaching() factory.create("test", ImmutableMap.of( "hive.metastore.uri", "thrift://foo:1234", - "delta.metadata.cache-ttl", "0s"), + "delta.metadata.cache-ttl", "0s", + "bootstrap.quiet", "true"), new TestingConnectorContext()) .shutdown(); } @@ -125,7 +141,8 @@ public void testNoActiveDataFilesCaching() factory.create("test", ImmutableMap.of( "hive.metastore.uri", "thrift://foo:1234", - "delta.metadata.live-files.cache-ttl", "0s"), + "delta.metadata.live-files.cache-ttl", "0s", + "bootstrap.quiet", "true"), new TestingConnectorContext()) .shutdown(); } @@ -138,7 +155,8 @@ public void testHiveConfigIsNotBound() ImmutableMap.of( "hive.metastore.uri", "thrift://foo:1234", // Try setting any property provided by HiveConfig class - HiveConfig.CONFIGURATION_HIVE_PARTITION_PROJECTION_ENABLED, "true"), + HiveConfig.CONFIGURATION_HIVE_PARTITION_PROJECTION_ENABLED, "true", + "bootstrap.quiet", "true"), new TestingConnectorContext())) .hasMessageContaining("Error: Configuration property 'hive.partition-projection-enabled' was not used"); } @@ -152,6 +170,7 @@ public void testReadOnlyAllAccessControl() ImmutableMap.builder() .put("hive.metastore.uri", "thrift://foo:1234") .put("delta.security", "read-only") + .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext()) .shutdown(); @@ -166,6 +185,7 @@ public void testSystemAccessControl() ImmutableMap.builder() .put("hive.metastore.uri", "thrift://foo:1234") .put("delta.security", "system") + .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext()); assertThatThrownBy(connector::getAccessControl).isInstanceOf(UnsupportedOperationException.class); @@ -187,6 +207,7 @@ public void testFileBasedAccessControl() .put("hive.metastore.uri", "thrift://foo:1234") .put("delta.security", "file") .put("security.config-file", tempFile.getAbsolutePath()) + .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext()) .shutdown(); diff --git a/plugin/trino-druid/src/test/java/io/trino/plugin/druid/TestDruidJdbcPlugin.java b/plugin/trino-druid/src/test/java/io/trino/plugin/druid/TestDruidJdbcPlugin.java index 72e4dc9bebab..0450592eee17 100644 --- a/plugin/trino-druid/src/test/java/io/trino/plugin/druid/TestDruidJdbcPlugin.java +++ b/plugin/trino-druid/src/test/java/io/trino/plugin/druid/TestDruidJdbcPlugin.java @@ -28,6 +28,11 @@ public void testCreateConnector() { Plugin plugin = new DruidJdbcPlugin(); ConnectorFactory factory = getOnlyElement(plugin.getConnectorFactories()); - factory.create("test", ImmutableMap.of("connection-url", "jdbc:druid:test"), new TestingConnectorContext()).shutdown(); + factory.create( + "test", + ImmutableMap.of( + "connection-url", "jdbc:druid:test", + "bootstrap.quiet", "true"), + new TestingConnectorContext()).shutdown(); } } diff --git a/plugin/trino-google-sheets/src/test/java/io/trino/plugin/google/sheets/TestSheetsPlugin.java b/plugin/trino-google-sheets/src/test/java/io/trino/plugin/google/sheets/TestSheetsPlugin.java index f03fe410009b..a75f89d771ff 100644 --- a/plugin/trino-google-sheets/src/test/java/io/trino/plugin/google/sheets/TestSheetsPlugin.java +++ b/plugin/trino-google-sheets/src/test/java/io/trino/plugin/google/sheets/TestSheetsPlugin.java @@ -53,7 +53,10 @@ public void testCreateConnector() { Plugin plugin = new SheetsPlugin(); ConnectorFactory factory = getOnlyElement(plugin.getConnectorFactories()); - ImmutableMap.Builder propertiesMap = ImmutableMap.builder().put("gsheets.credentials-path", getTestCredentialsPath()).put("gsheets.metadata-sheet-id", TEST_METADATA_SHEET_ID); + ImmutableMap.Builder propertiesMap = ImmutableMap.builder() + .put("gsheets.credentials-path", getTestCredentialsPath()) + .put("gsheets.metadata-sheet-id", TEST_METADATA_SHEET_ID) + .put("bootstrap.quiet", "true"); Connector connector = factory.create(GOOGLE_SHEETS, propertiesMap.buildOrThrow(), new TestingConnectorContext()); assertThat(connector).isNotNull(); connector.shutdown(); diff --git a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHivePlugin.java b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHivePlugin.java index b02ae6e60bba..6957c6d2a309 100644 --- a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHivePlugin.java +++ b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHivePlugin.java @@ -75,7 +75,12 @@ public void testCreateConnector() ConnectorFactory factory = getHiveConnectorFactory(); // simplest possible configuration - factory.create("test", ImmutableMap.of("hive.metastore.uri", "thrift://foo:1234"), new TestingConnectorContext()).shutdown(); + factory.create( + "test", + ImmutableMap.of( + "hive.metastore.uri", "thrift://foo:1234", + "bootstrap.quiet", "true"), + new TestingConnectorContext()).shutdown(); } @Test @@ -86,7 +91,8 @@ public void testTestingFileMetastore() "test", ImmutableMap.of( "hive.metastore", "file", - "hive.metastore.catalog.dir", "/tmp"), + "hive.metastore.catalog.dir", "/tmp", + "bootstrap.quiet", "true"), new TestingConnectorContext()) .shutdown(); } @@ -100,7 +106,8 @@ public void testThriftMetastore() "test", ImmutableMap.of( "hive.metastore", "thrift", - "hive.metastore.uri", "thrift://foo:1234"), + "hive.metastore.uri", "thrift://foo:1234", + "bootstrap.quiet", "true"), new TestingConnectorContext()) .shutdown(); } @@ -114,7 +121,8 @@ public void testGlueMetastore() "test", ImmutableMap.of( "hive.metastore", "glue", - "hive.metastore.glue.region", "us-east-2"), + "hive.metastore.glue.region", "us-east-2", + "bootstrap.quiet", "true"), new TestingConnectorContext()) .shutdown(); @@ -122,7 +130,8 @@ public void testGlueMetastore() "test", ImmutableMap.of( "hive.metastore", "glue", - "hive.metastore.uri", "thrift://foo:1234"), + "hive.metastore.uri", "thrift://foo:1234", + "bootstrap.quiet", "true"), new TestingConnectorContext())) .hasMessageContaining("Error: Configuration property 'hive.metastore.uri' was not used"); } @@ -137,7 +146,8 @@ public void testRecordingMetastore() ImmutableMap.of( "hive.metastore", "thrift", "hive.metastore.uri", "thrift://foo:1234", - "hive.metastore-recording-path", "/tmp"), + "hive.metastore-recording-path", "/tmp", + "bootstrap.quiet", "true"), new TestingConnectorContext()) .shutdown(); @@ -146,7 +156,8 @@ public void testRecordingMetastore() ImmutableMap.of( "hive.metastore", "glue", "hive.metastore.glue.region", "us-east-2", - "hive.metastore-recording-path", "/tmp"), + "hive.metastore-recording-path", "/tmp", + "bootstrap.quiet", "true"), new TestingConnectorContext()) .shutdown(); } @@ -165,6 +176,7 @@ public void testS3SecurityMappingAndHiveCachingMutuallyExclusive() .put("hive.cache.enabled", "true") .put("hive.metastore.uri", "thrift://foo:1234") .put("hive.cache.location", tempDirectory.toString()) + .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext())) .hasMessageContaining("S3 security mapping is not compatible with Hive caching"); @@ -182,6 +194,7 @@ public void testGcsAccessTokenAndHiveCachingMutuallyExclusive() .put("hive.cache.enabled", "true") .put("hive.metastore.uri", "thrift://foo:1234") .put("hive.cache.location", tempDirectory.toString()) + .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext())) .hasMessageContaining("Use of GCS access token is not compatible with Hive caching"); @@ -198,6 +211,7 @@ public void testImmutablePartitionsAndInsertOverwriteMutuallyExclusive() .put("hive.insert-existing-partitions-behavior", "APPEND") .put("hive.immutable-partitions", "true") .put("hive.metastore.uri", "thrift://foo:1234") + .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext())) .hasMessageContaining("insert-existing-partitions-behavior cannot be APPEND when immutable-partitions is true"); @@ -213,6 +227,7 @@ public void testInsertOverwriteIsSetToErrorWhenImmutablePartitionsIsTrue() ImmutableMap.builder() .put("hive.immutable-partitions", "true") .put("hive.metastore.uri", "thrift://foo:1234") + .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext()); assertThat(getDefaultValueInsertExistingPartitionsBehavior(connector)).isEqualTo(ERROR); @@ -226,7 +241,9 @@ public void testInsertOverwriteIsSetToAppendWhenImmutablePartitionsIsFalseByDefa Connector connector = connectorFactory.create( "test", - ImmutableMap.of("hive.metastore.uri", "thrift://foo:1234"), + ImmutableMap.of( + "hive.metastore.uri", "thrift://foo:1234", + "bootstrap.quiet", "true"), new TestingConnectorContext()); assertThat(getDefaultValueInsertExistingPartitionsBehavior(connector)).isEqualTo(APPEND); connector.shutdown(); @@ -252,6 +269,7 @@ public void testHdfsImpersonationAndHiveCachingMutuallyExclusive() .put("hive.cache.enabled", "true") .put("hive.metastore.uri", "thrift://foo:1234") .put("hive.cache.location", tempDirectory.toString()) + .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext())) .hasMessageContaining("HDFS impersonation is not compatible with Hive caching"); @@ -268,6 +286,7 @@ public void testRubixCache() .put("hive.cache.enabled", "true") .put("hive.metastore.uri", "thrift://foo:1234") .put("hive.cache.location", tempDirectory.toString()) + .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext()) .shutdown(); @@ -285,6 +304,7 @@ public void testRubixCacheWithNonExistingCacheDirectory() .put("hive.cache.start-server-on-coordinator", "true") .put("hive.metastore.uri", "thrift://foo:1234") .put("hive.cache.location", "/tmp/non/existing/directory") + .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext())) .hasRootCauseMessage("None of the cache parent directories exists"); @@ -295,6 +315,7 @@ public void testRubixCacheWithNonExistingCacheDirectory() .put("hive.cache.enabled", "true") .put("hive.cache.start-server-on-coordinator", "true") .put("hive.metastore.uri", "thrift://foo:1234") + .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext())) .hasRootCauseMessage("caching directories were not provided"); @@ -305,6 +326,7 @@ public void testRubixCacheWithNonExistingCacheDirectory() ImmutableMap.builder() .put("hive.cache.enabled", "true") .put("hive.metastore.uri", "thrift://foo:1234") + .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext()) .shutdown(); @@ -320,6 +342,7 @@ public void testAllowAllAccessControl() ImmutableMap.builder() .put("hive.metastore.uri", "thrift://foo:1234") .put("hive.security", "allow-all") + .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext()) .shutdown(); @@ -335,6 +358,7 @@ public void testReadOnlyAllAccessControl() ImmutableMap.builder() .put("hive.metastore.uri", "thrift://foo:1234") .put("hive.security", "read-only") + .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext()) .shutdown(); @@ -355,6 +379,7 @@ public void testFileBasedAccessControl() .put("hive.metastore.uri", "thrift://foo:1234") .put("hive.security", "file") .put("security.config-file", tempFile.getAbsolutePath()) + .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext()) .shutdown(); @@ -370,6 +395,7 @@ public void testSystemAccessControl() ImmutableMap.builder() .put("hive.metastore.uri", "thrift://foo:1234") .put("hive.security", "system") + .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext()); assertThatThrownBy(connector::getAccessControl).isInstanceOf(UnsupportedOperationException.class); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveConnectorFactory.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveConnectorFactory.java index 4e0c4b71efdf..a5de8bb83b27 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveConnectorFactory.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveConnectorFactory.java @@ -47,7 +47,9 @@ public void testGetClient() private static void assertCreateConnector(String metastoreUri) { - Map config = ImmutableMap.of("hive.metastore.uri", metastoreUri); + Map config = ImmutableMap.of( + "bootstrap.quiet", "true", + "hive.metastore.uri", metastoreUri); Connector connector = new HiveConnectorFactory().create("hive-test", config, new TestingConnectorContext()); ConnectorTransactionHandle transaction = connector.beginTransaction(READ_UNCOMMITTED, true, true); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestingHiveConnectorFactory.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestingHiveConnectorFactory.java index 086472941d78..ae78ec5fc4a5 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestingHiveConnectorFactory.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestingHiveConnectorFactory.java @@ -13,6 +13,7 @@ */ package io.trino.plugin.hive; +import com.google.common.collect.ImmutableMap; import com.google.inject.Module; import io.opentelemetry.api.OpenTelemetry; import io.trino.plugin.hive.fs.DirectoryLister; @@ -62,6 +63,17 @@ public String getName() @Override public Connector create(String catalogName, Map config, ConnectorContext context) { - return createConnector(catalogName, config, context, module, metastore, Optional.empty(), openTelemetry, directoryLister); + return createConnector( + catalogName, + ImmutableMap.builder() + .putAll(config) + .put("bootstrap.quiet", "true") + .buildOrThrow(), + context, + module, + metastore, + Optional.empty(), + openTelemetry, + directoryLister); } } diff --git a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiConnectorFactory.java b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiConnectorFactory.java index 923256be5c37..93fd1984b38a 100644 --- a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiConnectorFactory.java +++ b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiConnectorFactory.java @@ -48,7 +48,9 @@ public void testCreateConnector() private static void assertCreateConnector(String metastoreUri) { - Map config = ImmutableMap.of("hive.metastore.uri", metastoreUri); + Map config = ImmutableMap.of( + "hive.metastore.uri", metastoreUri, + "bootstrap.quiet", "true"); ConnectorFactory factory = new HudiConnectorFactory(); Connector connector = factory.create("test", config, new TestingConnectorContext()); diff --git a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiPlugin.java b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiPlugin.java index a6cbe73b4c33..71ec2e585916 100644 --- a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiPlugin.java +++ b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiPlugin.java @@ -33,7 +33,12 @@ public class TestHudiPlugin public void testCreateConnector() { ConnectorFactory factory = getConnectorFactory(); - factory.create("test", Map.of("hive.metastore.uri", "thrift://foo:1234"), new TestingConnectorContext()) + factory.create( + "test", + Map.of( + "hive.metastore.uri", "thrift://foo:1234", + "bootstrap.quiet", "true"), + new TestingConnectorContext()) .shutdown(); } @@ -42,7 +47,12 @@ public void testCreateTestingConnector() { Plugin plugin = new TestingHudiPlugin(Optional.empty()); ConnectorFactory factory = getOnlyElement(plugin.getConnectorFactories()); - factory.create("test", Map.of("hive.metastore.uri", "thrift://foo:1234"), new TestingConnectorContext()) + factory.create( + "test", + Map.of( + "hive.metastore.uri", "thrift://foo:1234", + "bootstrap.quiet", "true"), + new TestingConnectorContext()) .shutdown(); } @@ -54,7 +64,8 @@ public void testTestingFileMetastore() "test", ImmutableMap.of( "hive.metastore", "file", - "hive.metastore.catalog.dir", "/tmp"), + "hive.metastore.catalog.dir", "/tmp", + "bootstrap.quiet", "true"), new TestingConnectorContext()) .shutdown(); } @@ -67,7 +78,8 @@ public void testThriftMetastore() "test", Map.of( "hive.metastore", "thrift", - "hive.metastore.uri", "thrift://foo:1234"), + "hive.metastore.uri", "thrift://foo:1234", + "bootstrap.quiet", "true"), new TestingConnectorContext()) .shutdown(); } @@ -80,7 +92,8 @@ public void testGlueMetastore() "test", Map.of( "hive.metastore", "glue", - "hive.metastore.glue.region", "us-east-2"), + "hive.metastore.glue.region", "us-east-2", + "bootstrap.quiet", "true"), new TestingConnectorContext()) .shutdown(); @@ -88,7 +101,8 @@ public void testGlueMetastore() "test", Map.of( "hive.metastore", "glue", - "hive.metastore.uri", "thrift://foo:1234"), + "hive.metastore.uri", "thrift://foo:1234", + "bootstrap.quiet", "true"), new TestingConnectorContext())) .isInstanceOf(ApplicationConfigurationException.class) .hasMessageContaining("Error: Configuration property 'hive.metastore.uri' was not used"); @@ -102,7 +116,8 @@ public void testHiveConfigIsNotBound() Map.of( "hive.metastore.uri", "thrift://foo:1234", // Try setting any property provided by HiveConfig class - HiveConfig.CONFIGURATION_HIVE_PARTITION_PROJECTION_ENABLED, "true"), + HiveConfig.CONFIGURATION_HIVE_PARTITION_PROJECTION_ENABLED, "true", + "bootstrap.quiet", "true"), new TestingConnectorContext())) .hasMessageContaining("Error: Configuration property 'hive.partition-projection-enabled' was not used"); } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergConnectorFactory.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergConnectorFactory.java index dc0b740b30a9..ced9c6a48299 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergConnectorFactory.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergConnectorFactory.java @@ -27,7 +27,9 @@ public class TestIcebergConnectorFactory @Test public void testBasicConfig() { - Map config = ImmutableMap.of("hive.metastore.uri", "thrift://localhost:1234"); + Map config = ImmutableMap.of( + "hive.metastore.uri", "thrift://localhost:1234", + "bootstrap.quiet", "true"); createConnector(config); } @@ -37,6 +39,7 @@ public void testCachingHiveMetastore() Map config = ImmutableMap.builder() .put("hive.metastore.uri", "thrift://localhost:1234") .put("hive.metastore-cache-ttl", "5m") + .put("bootstrap.quiet", "true") .buildOrThrow(); assertThatThrownBy(() -> createConnector(config)) .hasMessageContaining("Hive metastore caching must not be enabled for Iceberg"); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergPlugin.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergPlugin.java index 5fcc3c7a0751..d33c5eaea614 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergPlugin.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergPlugin.java @@ -35,7 +35,12 @@ public void testCreateConnector() { ConnectorFactory factory = getConnectorFactory(); // simplest possible configuration - factory.create("test", Map.of("hive.metastore.uri", "thrift://foo:1234"), new TestingConnectorContext()).shutdown(); + factory.create( + "test", + Map.of( + "hive.metastore.uri", "thrift://foo:1234", + "bootstrap.quiet", "true"), + new TestingConnectorContext()).shutdown(); } @Test @@ -46,7 +51,8 @@ public void testTestingFileMetastore() "test", Map.of( "iceberg.catalog.type", "TESTING_FILE_METASTORE", - "hive.metastore.catalog.dir", "/tmp"), + "hive.metastore.catalog.dir", "/tmp", + "bootstrap.quiet", "true"), new TestingConnectorContext()) .shutdown(); } @@ -60,7 +66,8 @@ public void testThriftMetastore() "test", Map.of( "iceberg.catalog.type", "HIVE_METASTORE", - "hive.metastore.uri", "thrift://foo:1234"), + "hive.metastore.uri", "thrift://foo:1234", + "bootstrap.quiet", "true"), new TestingConnectorContext()) .shutdown(); @@ -69,7 +76,8 @@ public void testThriftMetastore() "test", Map.of( "hive.metastore.uri", "thrift://foo:1234", - "hive.metastore.glue.region", "us-east"), + "hive.metastore.glue.region", "us-east", + "bootstrap.quiet", "true"), new TestingConnectorContext())) .hasMessageContaining("Configuration property 'hive.metastore.glue.region' was not used"); } @@ -83,7 +91,8 @@ public void testHiveMetastoreRejected() "test", Map.of( "hive.metastore", "thrift", - "hive.metastore.uri", "thrift://foo:1234"), + "hive.metastore.uri", "thrift://foo:1234", + "bootstrap.quiet", "true"), new TestingConnectorContext())) .hasMessageContaining("Error: Configuration property 'hive.metastore' was not used"); } @@ -97,7 +106,8 @@ public void testGlueMetastore() "test", Map.of( "iceberg.catalog.type", "glue", - "hive.metastore.glue.region", "us-east-1"), + "hive.metastore.glue.region", "us-east-1", + "bootstrap.quiet", "true"), new TestingConnectorContext()) .shutdown(); @@ -105,7 +115,8 @@ public void testGlueMetastore() "test", Map.of( "iceberg.catalog.type", "glue", - "hive.metastore.uri", "thrift://foo:1234"), + "hive.metastore.uri", "thrift://foo:1234", + "bootstrap.quiet", "true"), new TestingConnectorContext())) .hasMessageContaining("Error: Configuration property 'hive.metastore.uri' was not used"); @@ -114,7 +125,8 @@ public void testGlueMetastore() Map.of( "iceberg.catalog.type", "glue", "hive.metastore.glue.catalogid", "123", - "hive.metastore.glue.region", "us-east-1"), + "hive.metastore.glue.region", "us-east-1", + "bootstrap.quiet", "true"), new TestingConnectorContext()) .shutdown(); } @@ -130,7 +142,8 @@ public void testRecordingMetastore() Map.of( "iceberg.catalog.type", "HIVE_METASTORE", "hive.metastore.uri", "thrift://foo:1234", - "hive.metastore-recording-path", "/tmp"), + "hive.metastore-recording-path", "/tmp", + "bootstrap.quiet", "true"), new TestingConnectorContext()) .shutdown(); @@ -140,7 +153,8 @@ public void testRecordingMetastore() Map.of( "iceberg.catalog.type", "glue", "hive.metastore.glue.region", "us-east-2", - "hive.metastore-recording-path", "/tmp"), + "hive.metastore-recording-path", "/tmp", + "bootstrap.quiet", "true"), new TestingConnectorContext())) .hasMessageContaining("Configuration property 'hive.metastore-recording-path' was not used"); @@ -150,7 +164,8 @@ public void testRecordingMetastore() Map.of( "iceberg.catalog.type", "nessie", "hive.metastore.nessie.region", "us-east-2", - "hive.metastore-recording-path", "/tmp"), + "hive.metastore-recording-path", "/tmp", + "bootstrap.quiet", "true"), new TestingConnectorContext())) .hasMessageContaining("Configuration property 'hive.metastore-recording-path' was not used"); } @@ -166,6 +181,7 @@ public void testAllowAllAccessControl() .put("iceberg.catalog.type", "HIVE_METASTORE") .put("hive.metastore.uri", "thrift://foo:1234") .put("iceberg.security", "allow-all") + .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext()) .shutdown(); @@ -182,6 +198,7 @@ public void testReadOnlyAllAccessControl() .put("iceberg.catalog.type", "HIVE_METASTORE") .put("hive.metastore.uri", "thrift://foo:1234") .put("iceberg.security", "read-only") + .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext()) .shutdown(); @@ -198,6 +215,7 @@ public void testSystemAccessControl() .put("iceberg.catalog.type", "HIVE_METASTORE") .put("hive.metastore.uri", "thrift://foo:1234") .put("iceberg.security", "system") + .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext()); assertThatThrownBy(connector::getAccessControl).isInstanceOf(UnsupportedOperationException.class); @@ -220,6 +238,7 @@ public void testFileBasedAccessControl() .put("hive.metastore.uri", "thrift://foo:1234") .put("iceberg.security", "file") .put("security.config-file", tempFile.getAbsolutePath()) + .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext()) .shutdown(); @@ -235,7 +254,8 @@ public void testIcebergPluginFailsWhenIncorrectPropertyProvided() Map.of( "iceberg.catalog.type", "HIVE_METASTORE", HIVE_VIEWS_ENABLED, "true", - "hive.metastore.uri", "thrift://foo:1234"), + "hive.metastore.uri", "thrift://foo:1234", + "bootstrap.quiet", "true"), new TestingConnectorContext()) .shutdown()) .isInstanceOf(ApplicationConfigurationException.class) @@ -251,7 +271,8 @@ public void testRestCatalog() "test", Map.of( "iceberg.catalog.type", "rest", - "iceberg.rest-catalog.uri", "https://foo:1234"), + "iceberg.rest-catalog.uri", "https://foo:1234", + "bootstrap.quiet", "true"), new TestingConnectorContext()) .shutdown(); } @@ -268,7 +289,8 @@ public void testJdbcCatalog() "iceberg.jdbc-catalog.driver-class", "org.postgresql.Driver", "iceberg.jdbc-catalog.connection-url", "jdbc:postgresql://localhost:5432/test", "iceberg.jdbc-catalog.catalog-name", "test", - "iceberg.jdbc-catalog.default-warehouse-dir", "s3://bucket"), + "iceberg.jdbc-catalog.default-warehouse-dir", "s3://bucket", + "bootstrap.quiet", "true"), new TestingConnectorContext()) .shutdown(); } @@ -283,7 +305,8 @@ public void testNessieCatalog() Map.of( "iceberg.catalog.type", "nessie", "iceberg.nessie-catalog.default-warehouse-dir", "/tmp", - "iceberg.nessie-catalog.uri", "http://foo:1234"), + "iceberg.nessie-catalog.uri", "http://foo:1234", + "bootstrap.quiet", "true"), new TestingConnectorContext()) .shutdown(); } diff --git a/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgnitePlugin.java b/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgnitePlugin.java index e97005439769..c345d8671c52 100644 --- a/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgnitePlugin.java +++ b/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgnitePlugin.java @@ -28,6 +28,11 @@ public void testCreateConnector() { Plugin plugin = new IgnitePlugin(); ConnectorFactory factory = getOnlyElement(plugin.getConnectorFactories()); - factory.create("test", ImmutableMap.of("connection-url", "jdbc:ignite:thin://localhost"), new TestingConnectorContext()).shutdown(); + factory.create( + "test", + ImmutableMap.of( + "connection-url", "jdbc:ignite:thin://localhost", + "bootstrap.quiet", "true"), + new TestingConnectorContext()).shutdown(); } } diff --git a/plugin/trino-jmx/src/test/java/io/trino/plugin/jmx/TestJmxSplitManager.java b/plugin/trino-jmx/src/test/java/io/trino/plugin/jmx/TestJmxSplitManager.java index 072cf20042e6..2f8497152d29 100644 --- a/plugin/trino-jmx/src/test/java/io/trino/plugin/jmx/TestJmxSplitManager.java +++ b/plugin/trino-jmx/src/test/java/io/trino/plugin/jmx/TestJmxSplitManager.java @@ -79,7 +79,8 @@ public class TestJmxSplitManager .create(CONNECTOR_ID, ImmutableMap.of( "jmx.dump-tables", TEST_BEANS, "jmx.dump-period", format("%dms", JMX_STATS_DUMP.toMillis()), - "jmx.max-entries", "1000"), + "jmx.max-entries", "1000", + "bootstrap.quiet", "true"), new ConnectorContext() { @Override diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaPlugin.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaPlugin.java index 78490aaf99bc..cdd40c58607a 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaPlugin.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaPlugin.java @@ -49,6 +49,7 @@ public void testSpinup() .put("kafka.table-names", "test") .put("kafka.nodes", "localhost:9092") .put("kafka.config.resources", resource.toString()) + .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext()); assertNotNull(connector); @@ -85,6 +86,7 @@ public void testSslSpinup() .put("kafka.ssl.truststore.location", truststorePath.toString()) .put("kafka.ssl.truststore.password", "truststore-password") .put("kafka.ssl.endpoint-identification-algorithm", "https") + .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext()); assertNotNull(connector); @@ -116,6 +118,7 @@ public void testSslKeystoreMissingFileSpindown() .put("kafka.ssl.truststore.location", truststorePath.toString()) .put("kafka.ssl.truststore.password", "truststore-password") .put("kafka.ssl.endpoint-identification-algorithm", "https") + .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext())) .hasMessageContaining("Error: Invalid configuration property kafka.ssl.keystore.location: file does not exist: /not/a/real/path"); @@ -146,6 +149,7 @@ public void testSslTruststoreMissingFileSpindown() .put("kafka.ssl.truststore.location", "/not/a/real/path") .put("kafka.ssl.truststore.password", "truststore-password") .put("kafka.ssl.endpoint-identification-algorithm", "https") + .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext())) .hasMessageContaining("Error: Invalid configuration property kafka.ssl.truststore.location: file does not exist: /not/a/real/path"); @@ -166,6 +170,7 @@ public void testResourceConfigMissingFileSpindown() .put("kafka.nodes", "localhost:9092") .put("kafka.security-protocol", "PLAINTEXT") .put("kafka.config.resources", "/not/a/real/path/1,/not/a/real/path/2") + .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext())) .hasMessageContainingAll("Error: Invalid configuration property", ": file does not exist: /not/a/real/path/1", ": file does not exist: /not/a/real/path/2"); @@ -190,6 +195,7 @@ public void testConfigResourceSpinup() .put("kafka.table-names", "test") .put("kafka.nodes", "localhost:9092") .put("kafka.config.resources", nativeKafkaResourcePath.toString()) + .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext()); assertNotNull(connector); diff --git a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestKinesisPlugin.java b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestKinesisPlugin.java index bafe67ffa92a..1ca455ff46c8 100644 --- a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestKinesisPlugin.java +++ b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestKinesisPlugin.java @@ -49,6 +49,7 @@ public void testCreateConnector() .put("kinesis.hide-internal-columns", "false") .put("kinesis.access-key", TestUtils.noneToBlank(accessKey)) .put("kinesis.secret-key", TestUtils.noneToBlank(secretKey)) + .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext()); assertNotNull(c); diff --git a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestKinesisTableDescriptionSupplier.java b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestKinesisTableDescriptionSupplier.java index 2edd09afc8ca..27a28bfe4a42 100644 --- a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestKinesisTableDescriptionSupplier.java +++ b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestKinesisTableDescriptionSupplier.java @@ -51,6 +51,7 @@ public void start() .put("kinesis.table-description-location", "etc/kinesis") .put("kinesis.default-schema", "kinesis") .put("kinesis.hide-internal-columns", "true") + .put("bootstrap.quiet", "true") .buildOrThrow(); KinesisTestClientManager kinesisTestClientManager = new KinesisTestClientManager(); diff --git a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/s3config/TestS3TableConfigClient.java b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/s3config/TestS3TableConfigClient.java index cb2c19cc3444..656d97238767 100644 --- a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/s3config/TestS3TableConfigClient.java +++ b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/s3config/TestS3TableConfigClient.java @@ -94,6 +94,7 @@ public void testTableReading(String tableDescriptionS3, String accessKey, String .put("kinesis.hide-internal-columns", "false") .put("kinesis.access-key", TestUtils.noneToBlank(accessKey)) .put("kinesis.secret-key", TestUtils.noneToBlank(secretKey)) + .put("bootstrap.quiet", "true") .buildOrThrow(); KinesisPlugin kinesisPlugin = new KinesisPlugin(); diff --git a/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduPlugin.java b/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduPlugin.java index e1163a4ac682..3344a62ab157 100644 --- a/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduPlugin.java +++ b/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduPlugin.java @@ -28,6 +28,11 @@ public void testCreateConnector() { Plugin plugin = new KuduPlugin(); ConnectorFactory factory = getOnlyElement(plugin.getConnectorFactories()); - factory.create("test", ImmutableMap.of("kudu.client.master-addresses", "localhost:7051"), new TestingConnectorContext()).shutdown(); + factory.create( + "test", + ImmutableMap.of( + "kudu.client.master-addresses", "localhost:7051", + "bootstrap.quiet", "true"), + new TestingConnectorContext()).shutdown(); } } diff --git a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbPlugin.java b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbPlugin.java index ed8b48987e0d..6ed113f665fb 100644 --- a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbPlugin.java +++ b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbPlugin.java @@ -29,12 +29,27 @@ public void testCreateConnector() { Plugin plugin = new MariaDbPlugin(); ConnectorFactory factory = getOnlyElement(plugin.getConnectorFactories()); - factory.create("test", ImmutableMap.of("connection-url", "jdbc:mariadb://test"), new TestingConnectorContext()).shutdown(); + factory.create( + "test", + ImmutableMap.of( + "connection-url", "jdbc:mariadb://test", + "bootstrap.quiet", "true"), + new TestingConnectorContext()).shutdown(); - assertThatThrownBy(() -> factory.create("test", ImmutableMap.of("connection-url", "test"), new TestingConnectorContext())) + assertThatThrownBy(() -> factory.create( + "test", + ImmutableMap.of( + "connection-url", "test", + "bootstrap.quiet", "true"), + new TestingConnectorContext())) .hasMessageContaining("Invalid JDBC URL for MariaDB connector"); - assertThatThrownBy(() -> factory.create("test", ImmutableMap.of("connection-url", "jdbc:mariadb://test/abc"), new TestingConnectorContext())) + assertThatThrownBy(() -> factory.create( + "test", + ImmutableMap.of( + "connection-url", "jdbc:mariadb://test/abc", + "bootstrap.quiet", "true"), + new TestingConnectorContext())) .hasMessageContaining("Database (catalog) must not be specified in JDBC URL for MariaDB connector"); } } diff --git a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoPlugin.java b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoPlugin.java index 08d13422617e..4da82167001a 100644 --- a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoPlugin.java +++ b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoPlugin.java @@ -32,7 +32,12 @@ public void testCreateConnector() MongoPlugin plugin = new MongoPlugin(); ConnectorFactory factory = getOnlyElement(plugin.getConnectorFactories()); - Connector connector = factory.create("test", ImmutableMap.of("mongodb.connection-url", "mongodb://localhost:27017"), new TestingConnectorContext()); + Connector connector = factory.create( + "test", + ImmutableMap.of( + "mongodb.connection-url", "mongodb://localhost:27017", + "bootstrap.quiet", "true"), + new TestingConnectorContext()); Type type = getOnlyElement(plugin.getTypes()); assertEquals(type, OBJECT_ID); diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlPlugin.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlPlugin.java index 19a3c5457db0..b9b2cb86fd72 100644 --- a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlPlugin.java +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlPlugin.java @@ -30,12 +30,26 @@ public void testCreateConnector() Plugin plugin = new MySqlPlugin(); ConnectorFactory factory = getOnlyElement(plugin.getConnectorFactories()); - factory.create("test", ImmutableMap.of("connection-url", "jdbc:mysql://test"), new TestingConnectorContext()).shutdown(); + factory.create( + "test", ImmutableMap.of( + "connection-url", "jdbc:mysql://test", + "bootstrap.quiet", "true"), + new TestingConnectorContext()).shutdown(); - assertThatThrownBy(() -> factory.create("test", ImmutableMap.of("connection-url", "test"), new TestingConnectorContext())) + assertThatThrownBy(() -> factory.create( + "test", + ImmutableMap.of( + "connection-url", "test", + "bootstrap.quiet", "true"), + new TestingConnectorContext())) .hasMessageContaining("Invalid JDBC URL for MySQL connector"); - assertThatThrownBy(() -> factory.create("test", ImmutableMap.of("connection-url", "jdbc:mysql://test/abc"), new TestingConnectorContext())) + assertThatThrownBy(() -> factory.create( + "test", + ImmutableMap.of( + "connection-url", "jdbc:mysql://test/abc", + "bootstrap.quiet", "true"), + new TestingConnectorContext())) .hasMessageContaining("Database (catalog) must not be specified in JDBC URL for MySQL connector"); } } diff --git a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOraclePlugin.java b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOraclePlugin.java index b76aba6744bf..9cab94e3ad0d 100644 --- a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOraclePlugin.java +++ b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOraclePlugin.java @@ -28,6 +28,11 @@ public void testCreateConnector() { Plugin plugin = new OraclePlugin(); ConnectorFactory factory = getOnlyElement(plugin.getConnectorFactories()); - factory.create("test", ImmutableMap.of("connection-url", "jdbc:oracle:thin//test"), new TestingConnectorContext()).shutdown(); + factory.create( + "test", + ImmutableMap.of( + "connection-url", "jdbc:oracle:thin//test", + "bootstrap.quiet", "true"), + new TestingConnectorContext()).shutdown(); } } diff --git a/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixPlugin.java b/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixPlugin.java index 274fa7cb42fb..8b3a6479dd2d 100644 --- a/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixPlugin.java +++ b/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixPlugin.java @@ -28,7 +28,12 @@ public void testCreateConnector() { Plugin plugin = new PhoenixPlugin(); ConnectorFactory factory = getOnlyElement(plugin.getConnectorFactories()); - factory.create("test", ImmutableMap.of("phoenix.connection-url", "jdbc:phoenix:test"), new TestingConnectorContext()) + factory.create( + "test", + ImmutableMap.of( + "phoenix.connection-url", "jdbc:phoenix:test", + "bootstrap.quiet", "true"), + new TestingConnectorContext()) .shutdown(); } } diff --git a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlPlugin.java b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlPlugin.java index b1e42e783167..35a283207cff 100644 --- a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlPlugin.java +++ b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlPlugin.java @@ -28,6 +28,11 @@ public void testCreateConnector() { Plugin plugin = new PostgreSqlPlugin(); ConnectorFactory factory = getOnlyElement(plugin.getConnectorFactories()); - factory.create("test", ImmutableMap.of("connection-url", "jdbc:postgresql:test"), new TestingConnectorContext()).shutdown(); + factory.create( + "test", + ImmutableMap.of( + "connection-url", "jdbc:postgresql:test", + "bootstrap.quiet", "true"), + new TestingConnectorContext()).shutdown(); } } diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorPlugin.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorPlugin.java index 3db165638310..b713617f8627 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorPlugin.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorPlugin.java @@ -44,6 +44,7 @@ public void testPlugin() .put("metadata.db.type", "h2") .put("metadata.db.filename", tmpDir.getAbsolutePath()) .put("storage.data-directory", tmpDir.getAbsolutePath()) + .put("bootstrap.quiet", "true") .buildOrThrow(); factory.create("test", config, new TestingConnectorContext()).shutdown(); diff --git a/plugin/trino-redis/src/test/java/io/trino/plugin/redis/TestRedisPlugin.java b/plugin/trino-redis/src/test/java/io/trino/plugin/redis/TestRedisPlugin.java index 1ca0df8aac7b..9487abe4678d 100644 --- a/plugin/trino-redis/src/test/java/io/trino/plugin/redis/TestRedisPlugin.java +++ b/plugin/trino-redis/src/test/java/io/trino/plugin/redis/TestRedisPlugin.java @@ -38,6 +38,7 @@ public void testStartup() ImmutableMap.builder() .put("redis.table-names", "test") .put("redis.nodes", "localhost:6379") + .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext()); assertNotNull(connector); diff --git a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftPlugin.java b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftPlugin.java index a1d9b6ab73b9..9d4fdaad3e80 100644 --- a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftPlugin.java +++ b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftPlugin.java @@ -28,6 +28,11 @@ public void testCreateConnector() { Plugin plugin = new RedshiftPlugin(); ConnectorFactory factory = getOnlyElement(plugin.getConnectorFactories()); - factory.create("test", ImmutableMap.of("connection-url", "jdbc:redshift:test"), new TestingConnectorContext()).shutdown(); + factory.create( + "test", + ImmutableMap.of( + "connection-url", "jdbc:redshift:test", + "bootstrap.quiet", "true"), + new TestingConnectorContext()).shutdown(); } } diff --git a/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreJdbcConfig.java b/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreJdbcConfig.java index 3ecf9da6eb10..8f5717189a01 100644 --- a/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreJdbcConfig.java +++ b/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreJdbcConfig.java @@ -37,7 +37,11 @@ public void testCreateConnectorLegacyUrl() .filter(connectorFactory -> connectorFactory.getName().equals("singlestore")) .collect(toOptional()) .orElseThrow(); - assertThatThrownBy(() -> factory.create("test", ImmutableMap.of("connection-url", "jdbc:mariadb:test"), new TestingConnectorContext())) + assertThatThrownBy(() -> factory.create( + "test", ImmutableMap.of( + "connection-url", "jdbc:mariadb:test", + "bootstrap.quiet", "true"), + new TestingConnectorContext())) .isInstanceOf(ApplicationConfigurationException.class) .hasMessageContaining(DRIVER_PROTOCOL_ERROR); } diff --git a/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStorePlugin.java b/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStorePlugin.java index fc440831e949..47a5ac8f45c7 100644 --- a/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStorePlugin.java +++ b/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStorePlugin.java @@ -32,6 +32,11 @@ public void testCreateConnector() .filter(connectorFactory -> connectorFactory.getName().equals("singlestore")) .collect(toOptional()) .orElseThrow(); - factory.create("test", ImmutableMap.of("connection-url", "jdbc:singlestore://test"), new TestingConnectorContext()).shutdown(); + factory.create( + "test", + ImmutableMap.of( + "connection-url", "jdbc:singlestore://test", + "bootstrap.quiet", "true"), + new TestingConnectorContext()).shutdown(); } } diff --git a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerPlugin.java b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerPlugin.java index 6d68ab83e9da..e972712d56a5 100644 --- a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerPlugin.java +++ b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerPlugin.java @@ -28,6 +28,11 @@ public void testCreateConnector() { Plugin plugin = new SqlServerPlugin(); ConnectorFactory factory = getOnlyElement(plugin.getConnectorFactories()); - factory.create("test", ImmutableMap.of("connection-url", "jdbc:sqlserver:test"), new TestingConnectorContext()).shutdown(); + factory.create( + "test", + ImmutableMap.of( + "connection-url", "jdbc:sqlserver:test", + "bootstrap.quiet", "true"), + new TestingConnectorContext()).shutdown(); } } diff --git a/plugin/trino-thrift/src/test/java/io/trino/plugin/thrift/TestThriftPlugin.java b/plugin/trino-thrift/src/test/java/io/trino/plugin/thrift/TestThriftPlugin.java index 234c1114cf6a..bf7d106db935 100644 --- a/plugin/trino-thrift/src/test/java/io/trino/plugin/thrift/TestThriftPlugin.java +++ b/plugin/trino-thrift/src/test/java/io/trino/plugin/thrift/TestThriftPlugin.java @@ -35,7 +35,9 @@ public void testPlugin() ConnectorFactory factory = getOnlyElement(plugin.getConnectorFactories()); assertInstanceOf(factory, ThriftConnectorFactory.class); - Map config = ImmutableMap.of("trino.thrift.client.addresses", "localhost:7779"); + Map config = ImmutableMap.of( + "trino.thrift.client.addresses", "localhost:7779", + "bootstrap.quiet", "true"); Connector connector = factory.create("test", config, new TestingConnectorContext()); assertNotNull(connector); diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/BaseIcebergCostBasedPlanTest.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/BaseIcebergCostBasedPlanTest.java index 756b559bfcfb..c45d687dd611 100644 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/BaseIcebergCostBasedPlanTest.java +++ b/testing/trino-tests/src/test/java/io/trino/sql/planner/BaseIcebergCostBasedPlanTest.java @@ -121,6 +121,7 @@ protected ConnectorFactory createConnectorFactory() .put("s3.endpoint", minio.getMinioAddress()) .put("s3.path-style-access", "true") .put(EXTENDED_STATISTICS_CONFIG, "true") + .put("bootstrap.quiet", "true") .buildOrThrow(); return new IcebergConnectorFactory() From 096772030deec00a8b2a0211bcac1fdcb8d28d62 Mon Sep 17 00:00:00 2001 From: Marius Grama Date: Tue, 24 Oct 2023 14:07:50 +0200 Subject: [PATCH 202/587] Import statically LongTimestampToVarcharCoercer --- .../java/io/trino/plugin/hive/coercions/CoercionUtils.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/CoercionUtils.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/CoercionUtils.java index 67865c55fbf8..c353c9d27af4 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/CoercionUtils.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/CoercionUtils.java @@ -18,6 +18,7 @@ import io.trino.plugin.hive.HiveType; import io.trino.plugin.hive.coercions.BooleanCoercer.BooleanToVarcharCoercer; import io.trino.plugin.hive.coercions.DateCoercer.VarcharToDateCoercer; +import io.trino.plugin.hive.coercions.TimestampCoercer.LongTimestampToVarcharCoercer; import io.trino.plugin.hive.coercions.TimestampCoercer.VarcharToLongTimestampCoercer; import io.trino.plugin.hive.coercions.TimestampCoercer.VarcharToShortTimestampCoercer; import io.trino.plugin.hive.type.Category; @@ -189,7 +190,7 @@ public static Type createTypeFromCoercer(TypeManager typeManager, HiveType fromH return Optional.of(createRealToDecimalCoercer(toDecimalType)); } if (fromType instanceof TimestampType && toType instanceof VarcharType varcharType) { - return Optional.of(new TimestampCoercer.LongTimestampToVarcharCoercer(TIMESTAMP_NANOS, varcharType)); + return Optional.of(new LongTimestampToVarcharCoercer(TIMESTAMP_NANOS, varcharType)); } if (fromType == DOUBLE && toType instanceof VarcharType toVarcharType) { return Optional.of(new DoubleToVarcharCoercer(toVarcharType, coercionContext.treatNaNAsNull())); From 80cffa72ca4a440d663226c7a91f95b9b45347be Mon Sep 17 00:00:00 2001 From: Marius Grama Date: Mon, 6 Nov 2023 18:41:33 +0100 Subject: [PATCH 203/587] Migrate tests to JUnit --- .../hive/coercions/TestTimestampCoercer.java | 158 ++++++++++++------ 1 file changed, 104 insertions(+), 54 deletions(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestTimestampCoercer.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestTimestampCoercer.java index cb1872cc79e8..3971b657c7cd 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestTimestampCoercer.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestTimestampCoercer.java @@ -22,8 +22,7 @@ import io.trino.spi.type.TimestampType; import io.trino.spi.type.Type; import io.trino.spi.type.VarcharType; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.time.LocalDateTime; @@ -46,24 +45,93 @@ public class TestTimestampCoercer { - @Test(dataProvider = "timestampValuesProvider") - public void testTimestampToVarchar(String timestampValue, String hiveTimestampValue) + @Test + public void testTimestampToVarchar() + { + testTimestampToVarchar("1900-01-01T00:00:00.000", "1900-01-01 00:00:00"); + testTimestampToVarchar("1958-01-01T13:18:03.123", "1958-01-01 13:18:03.123"); + // after epoch + testTimestampToVarchar("2019-03-18T10:01:17.987", "2019-03-18 10:01:17.987"); + // time doubled in JVM zone + testTimestampToVarchar("2018-10-28T01:33:17.456", "2018-10-28 01:33:17.456"); + // time doubled in JVM zone + testTimestampToVarchar("2018-10-28T03:33:33.333", "2018-10-28 03:33:33.333"); + // epoch + testTimestampToVarchar("1970-01-01T00:00:00.000", "1970-01-01 00:00:00"); + // time gap in JVM zone + testTimestampToVarchar("1970-01-01T00:13:42.000", "1970-01-01 00:13:42"); + testTimestampToVarchar("2018-04-01T02:13:55.123", "2018-04-01 02:13:55.123"); + // time gap in Vilnius + testTimestampToVarchar("2018-03-25T03:17:17.000", "2018-03-25 03:17:17"); + // time gap in Kathmandu + testTimestampToVarchar("1986-01-01T00:13:07.000", "1986-01-01 00:13:07"); + // before epoch with second fraction + testTimestampToVarchar("1969-12-31T23:59:59.123456", "1969-12-31 23:59:59.123456"); + } + + private static void testTimestampToVarchar(String timestampValue, String hiveTimestampValue) { LocalDateTime localDateTime = LocalDateTime.parse(timestampValue); SqlTimestamp timestamp = SqlTimestamp.fromSeconds(TIMESTAMP_PICOS.getPrecision(), localDateTime.toEpochSecond(UTC), localDateTime.get(NANO_OF_SECOND)); assertLongTimestampToVarcharCoercions(TIMESTAMP_PICOS, new LongTimestamp(timestamp.getEpochMicros(), timestamp.getPicosOfMicros()), createUnboundedVarcharType(), hiveTimestampValue); } - @Test(dataProvider = "timestampValuesProvider") - public void testVarcharToShortTimestamp(String timestampValue, String hiveTimestampValue) + @Test + public void testVarcharToShortTimestamp() + { + testVarcharToShortTimestamp("1900-01-01T00:00:00.000", "1900-01-01 00:00:00"); + testVarcharToShortTimestamp("1958-01-01T13:18:03.123", "1958-01-01 13:18:03.123"); + // after epoch + testVarcharToShortTimestamp("2019-03-18T10:01:17.987", "2019-03-18 10:01:17.987"); + // time doubled in JVM zone + testVarcharToShortTimestamp("2018-10-28T01:33:17.456", "2018-10-28 01:33:17.456"); + // time doubled in JVM zone + testVarcharToShortTimestamp("2018-10-28T03:33:33.333", "2018-10-28 03:33:33.333"); + // epoch + testVarcharToShortTimestamp("1970-01-01T00:00:00.000", "1970-01-01 00:00:00"); + // time gap in JVM zone + testVarcharToShortTimestamp("1970-01-01T00:13:42.000", "1970-01-01 00:13:42"); + testVarcharToShortTimestamp("2018-04-01T02:13:55.123", "2018-04-01 02:13:55.123"); + // time gap in Vilnius + testVarcharToShortTimestamp("2018-03-25T03:17:17.000", "2018-03-25 03:17:17"); + // time gap in Kathmandu + testVarcharToShortTimestamp("1986-01-01T00:13:07.000", "1986-01-01 00:13:07"); + // before epoch with second fraction + testVarcharToShortTimestamp("1969-12-31T23:59:59.123456", "1969-12-31 23:59:59.123456"); + } + + private static void testVarcharToShortTimestamp(String timestampValue, String hiveTimestampValue) { LocalDateTime localDateTime = LocalDateTime.parse(timestampValue); SqlTimestamp timestamp = SqlTimestamp.fromSeconds(TIMESTAMP_MICROS.getPrecision(), localDateTime.toEpochSecond(UTC), localDateTime.get(NANO_OF_SECOND)); assertVarcharToShortTimestampCoercions(createUnboundedVarcharType(), utf8Slice(hiveTimestampValue), TIMESTAMP_MICROS, timestamp.getEpochMicros()); } - @Test(dataProvider = "timestampValuesProvider") - public void testVarcharToLongTimestamp(String timestampValue, String hiveTimestampValue) + @Test + public void testVarcharToLongTimestamp() + { + testVarcharToLongTimestamp("1900-01-01T00:00:00.000", "1900-01-01 00:00:00"); + testVarcharToLongTimestamp("1958-01-01T13:18:03.123", "1958-01-01 13:18:03.123"); + // after epoch + testVarcharToLongTimestamp("2019-03-18T10:01:17.987", "2019-03-18 10:01:17.987"); + // time doubled in JVM zone + testVarcharToLongTimestamp("2018-10-28T01:33:17.456", "2018-10-28 01:33:17.456"); + // time doubled in JVM zone + testVarcharToLongTimestamp("2018-10-28T03:33:33.333", "2018-10-28 03:33:33.333"); + // epoch + testVarcharToLongTimestamp("1970-01-01T00:00:00.000", "1970-01-01 00:00:00"); + // time gap in JVM zone + testVarcharToLongTimestamp("1970-01-01T00:13:42.000", "1970-01-01 00:13:42"); + testVarcharToLongTimestamp("2018-04-01T02:13:55.123", "2018-04-01 02:13:55.123"); + // time gap in Vilnius + testVarcharToLongTimestamp("2018-03-25T03:17:17.000", "2018-03-25 03:17:17"); + // time gap in Kathmandu + testVarcharToLongTimestamp("1986-01-01T00:13:07.000", "1986-01-01 00:13:07"); + // before epoch with second fraction + testVarcharToLongTimestamp("1969-12-31T23:59:59.123456", "1969-12-31 23:59:59.123456"); + } + + private static void testVarcharToLongTimestamp(String timestampValue, String hiveTimestampValue) { LocalDateTime localDateTime = LocalDateTime.parse(timestampValue); SqlTimestamp timestamp = SqlTimestamp.fromSeconds(TIMESTAMP_PICOS.getPrecision(), localDateTime.toEpochSecond(UTC), localDateTime.get(NANO_OF_SECOND)); @@ -122,14 +190,38 @@ public void testHistoricalLongTimestampToVarchar() .hasMessageContaining("Coercion on historical dates is not supported"); } - @Test(dataProvider = "invalidValue") - public void testInvalidVarcharToShortTimestamp(String invalidValue) + @Test + public void testInvalidVarcharToShortTimestamp() + { + testInvalidVarcharToShortTimestamp("Invalid timestamp"); // Invalid string + testInvalidVarcharToShortTimestamp("2022"); // Partial timestamp value + testInvalidVarcharToShortTimestamp("2001-04-01T00:13:42.000"); // ISOFormat date + testInvalidVarcharToShortTimestamp("2001-14-01 00:13:42.000"); // Invalid month + testInvalidVarcharToShortTimestamp("2001-01-32 00:13:42.000"); // Invalid day + testInvalidVarcharToShortTimestamp("2001-04-01 23:59:60.000"); // Invalid second + testInvalidVarcharToShortTimestamp("2001-04-01 23:60:01.000"); // Invalid minute + testInvalidVarcharToShortTimestamp("2001-04-01 27:01:01.000"); // Invalid hour + } + + private static void testInvalidVarcharToShortTimestamp(String invalidValue) { assertVarcharToShortTimestampCoercions(createUnboundedVarcharType(), utf8Slice(invalidValue), TIMESTAMP_MICROS, null); } - @Test(dataProvider = "invalidValue") - public void testInvalidVarcharLongTimestamp(String invalidValue) + @Test + public void testInvalidVarcharLongTimestamp() + { + testInvalidVarcharLongTimestamp("Invalid timestamp"); // Invalid string + testInvalidVarcharLongTimestamp("2022"); // Partial timestamp value + testInvalidVarcharLongTimestamp("2001-04-01T00:13:42.000"); // ISOFormat date + testInvalidVarcharLongTimestamp("2001-14-01 00:13:42.000"); // Invalid month + testInvalidVarcharLongTimestamp("2001-01-32 00:13:42.000"); // Invalid day + testInvalidVarcharLongTimestamp("2001-04-01 23:59:60.000"); // Invalid second + testInvalidVarcharLongTimestamp("2001-04-01 23:60:01.000"); // Invalid minute + testInvalidVarcharLongTimestamp("2001-04-01 27:01:01.000"); // Invalid hour + } + + private static void testInvalidVarcharLongTimestamp(String invalidValue) { assertVarcharToLongTimestampCoercions(createUnboundedVarcharType(), utf8Slice(invalidValue), TIMESTAMP_MICROS, null); } @@ -163,48 +255,6 @@ public void testHistoricalVarcharToLongTimestamp() .hasMessageContaining("Coercion on historical dates is not supported"); } - @DataProvider - public Object[][] timestampValuesProvider() - { - return new Object[][] { - // before epoch - {"1900-01-01T00:00:00.000", "1900-01-01 00:00:00"}, - {"1958-01-01T13:18:03.123", "1958-01-01 13:18:03.123"}, - // after epoch - {"2019-03-18T10:01:17.987", "2019-03-18 10:01:17.987"}, - // time doubled in JVM zone - {"2018-10-28T01:33:17.456", "2018-10-28 01:33:17.456"}, - // time doubled in JVM zone - {"2018-10-28T03:33:33.333", "2018-10-28 03:33:33.333"}, - // epoch - {"1970-01-01T00:00:00.000", "1970-01-01 00:00:00"}, - // time gap in JVM zone - {"1970-01-01T00:13:42.000", "1970-01-01 00:13:42"}, - {"2018-04-01T02:13:55.123", "2018-04-01 02:13:55.123"}, - // time gap in Vilnius - {"2018-03-25T03:17:17.000", "2018-03-25 03:17:17"}, - // time gap in Kathmandu - {"1986-01-01T00:13:07.000", "1986-01-01 00:13:07"}, - // before epoch with second fraction - {"1969-12-31T23:59:59.123456", "1969-12-31 23:59:59.123456"} - }; - } - - @DataProvider - public Object[][] invalidValue() - { - return new Object[][] { - {"Invalid timestamp"}, // Invalid string - {"2022"}, // Partial timestamp value - {"2001-04-01T00:13:42.000"}, // ISOFormat date - {"2001-14-01 00:13:42.000"}, // Invalid month - {"2001-01-32 00:13:42.000"}, // Invalid day - {"2001-04-01 23:59:60.000"}, // Invalid second - {"2001-04-01 23:60:01.000"}, // Invalid minute - {"2001-04-01 27:01:01.000"}, // Invalid hour - }; - } - public static void assertLongTimestampToVarcharCoercions(TimestampType fromType, LongTimestamp valueToBeCoerced, VarcharType toType, String expectedValue) { assertCoercions(fromType, valueToBeCoerced, toType, utf8Slice(expectedValue), NANOSECONDS); From 0e4c32560454eb54fa89f271f0d02ea7ae77df7c Mon Sep 17 00:00:00 2001 From: Marius Grama Date: Tue, 24 Oct 2023 15:35:04 +0200 Subject: [PATCH 204/587] Add timestamp to date coercion for hive --- .../plugin/hive/coercions/CoercionUtils.java | 11 ++- .../hive/coercions/TimestampCoercer.java | 22 +++++ .../plugin/hive/orc/OrcTypeTranslator.java | 11 ++- .../plugin/hive/util/HiveCoercionPolicy.java | 3 + .../hive/coercions/TestTimestampCoercer.java | 44 ++++++++++ .../product/hive/BaseTestHiveCoercion.java | 80 +++++++++++++++---- .../TestHiveCoercionOnPartitionedTable.java | 12 ++- .../TestHiveCoercionOnUnpartitionedTable.java | 10 ++- 8 files changed, 167 insertions(+), 26 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/CoercionUtils.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/CoercionUtils.java index c353c9d27af4..a0aa9435c00e 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/CoercionUtils.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/CoercionUtils.java @@ -18,6 +18,7 @@ import io.trino.plugin.hive.HiveType; import io.trino.plugin.hive.coercions.BooleanCoercer.BooleanToVarcharCoercer; import io.trino.plugin.hive.coercions.DateCoercer.VarcharToDateCoercer; +import io.trino.plugin.hive.coercions.TimestampCoercer.LongTimestampToDateCoercer; import io.trino.plugin.hive.coercions.TimestampCoercer.LongTimestampToVarcharCoercer; import io.trino.plugin.hive.coercions.TimestampCoercer.VarcharToLongTimestampCoercer; import io.trino.plugin.hive.coercions.TimestampCoercer.VarcharToShortTimestampCoercer; @@ -189,8 +190,14 @@ public static Type createTypeFromCoercer(TypeManager typeManager, HiveType fromH if (fromType == REAL && toType instanceof DecimalType toDecimalType) { return Optional.of(createRealToDecimalCoercer(toDecimalType)); } - if (fromType instanceof TimestampType && toType instanceof VarcharType varcharType) { - return Optional.of(new LongTimestampToVarcharCoercer(TIMESTAMP_NANOS, varcharType)); + if (fromType instanceof TimestampType) { + if (toType instanceof VarcharType varcharType) { + return Optional.of(new LongTimestampToVarcharCoercer(TIMESTAMP_NANOS, varcharType)); + } + if (toType instanceof DateType toDateType) { + return Optional.of(new LongTimestampToDateCoercer(TIMESTAMP_NANOS, toDateType)); + } + return Optional.empty(); } if (fromType == DOUBLE && toType instanceof VarcharType toVarcharType) { return Optional.of(new DoubleToVarcharCoercer(toVarcharType, coercionContext.treatNaNAsNull())); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/TimestampCoercer.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/TimestampCoercer.java index 0b903a3561b4..477015889c82 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/TimestampCoercer.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/coercions/TimestampCoercer.java @@ -18,6 +18,7 @@ import io.trino.spi.TrinoException; import io.trino.spi.block.Block; import io.trino.spi.block.BlockBuilder; +import io.trino.spi.type.DateType; import io.trino.spi.type.LongTimestamp; import io.trino.spi.type.TimestampType; import io.trino.spi.type.VarcharType; @@ -94,6 +95,27 @@ protected void applyCoercedValue(BlockBuilder blockBuilder, Block block, int pos } } + public static class LongTimestampToDateCoercer + extends TypeCoercer + { + public LongTimestampToDateCoercer(TimestampType fromType, DateType toType) + { + super(fromType, toType); + } + + @Override + protected void applyCoercedValue(BlockBuilder blockBuilder, Block block, int position) + { + LongTimestamp timestamp = (LongTimestamp) fromType.getObject(block, position); + + long epochSecond = floorDiv(timestamp.getEpochMicros(), MICROSECONDS_PER_SECOND); + if (epochSecond < START_OF_MODERN_ERA_SECONDS) { + throw new TrinoException(HIVE_INVALID_TIMESTAMP_COERCION, "Coercion on historical dates is not supported"); + } + toType.writeLong(blockBuilder, floorDiv(epochSecond, SECONDS_PER_DAY)); + } + } + public static class VarcharToShortTimestampCoercer extends TypeCoercer { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcTypeTranslator.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcTypeTranslator.java index ff2449ce535e..00f050fa97b4 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcTypeTranslator.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcTypeTranslator.java @@ -18,6 +18,7 @@ import io.trino.plugin.hive.coercions.DateCoercer.VarcharToDateCoercer; import io.trino.plugin.hive.coercions.DoubleToVarcharCoercer; import io.trino.plugin.hive.coercions.IntegerNumberToDoubleCoercer; +import io.trino.plugin.hive.coercions.TimestampCoercer.LongTimestampToDateCoercer; import io.trino.plugin.hive.coercions.TimestampCoercer.LongTimestampToVarcharCoercer; import io.trino.plugin.hive.coercions.TimestampCoercer.VarcharToLongTimestampCoercer; import io.trino.plugin.hive.coercions.TimestampCoercer.VarcharToShortTimestampCoercer; @@ -53,8 +54,14 @@ private OrcTypeTranslator() {} public static Optional> createCoercer(OrcTypeKind fromOrcType, Type toTrinoType) { - if (fromOrcType == TIMESTAMP && toTrinoType instanceof VarcharType varcharType) { - return Optional.of(new LongTimestampToVarcharCoercer(TIMESTAMP_NANOS, varcharType)); + if (fromOrcType == TIMESTAMP) { + if (toTrinoType instanceof VarcharType varcharType) { + return Optional.of(new LongTimestampToVarcharCoercer(TIMESTAMP_NANOS, varcharType)); + } + if (toTrinoType instanceof DateType toDateType) { + return Optional.of(new LongTimestampToDateCoercer(TIMESTAMP_NANOS, toDateType)); + } + return Optional.empty(); } if (isVarcharType(fromOrcType)) { if (toTrinoType instanceof TimestampType timestampType) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveCoercionPolicy.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveCoercionPolicy.java index 857d64eec157..d424fcda56ee 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveCoercionPolicy.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveCoercionPolicy.java @@ -83,6 +83,9 @@ private boolean canCoerce(HiveType fromHiveType, HiveType toHiveType, HiveTimest fromHiveType.equals(HIVE_DOUBLE) || fromType instanceof DecimalType; } + if (toHiveType.equals(HIVE_DATE)) { + return fromHiveType.equals(HIVE_TIMESTAMP); + } if (fromHiveType.equals(HIVE_BYTE)) { return toHiveType.equals(HIVE_SHORT) || toHiveType.equals(HIVE_INT) || toHiveType.equals(HIVE_LONG) || toHiveType.equals(HIVE_DOUBLE); } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestTimestampCoercer.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestTimestampCoercer.java index 3971b657c7cd..86f3b12f5a86 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestTimestampCoercer.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestTimestampCoercer.java @@ -24,6 +24,7 @@ import io.trino.spi.type.VarcharType; import org.junit.jupiter.api.Test; +import java.time.LocalDate; import java.time.LocalDateTime; import static io.airlift.slice.Slices.utf8Slice; @@ -33,6 +34,7 @@ import static io.trino.plugin.hive.coercions.CoercionUtils.createCoercer; import static io.trino.spi.predicate.Utils.blockToNativeValue; import static io.trino.spi.predicate.Utils.nativeValueToBlock; +import static io.trino.spi.type.DateType.DATE; import static io.trino.spi.type.TimestampType.TIMESTAMP_MICROS; import static io.trino.spi.type.TimestampType.TIMESTAMP_PICOS; import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; @@ -45,6 +47,41 @@ public class TestTimestampCoercer { + @Test + public void testTimestampToDate() + { + // before epoch + assertTimestampToDateCoercion("1900-01-01T00:00:00.000", "1900-01-01"); + assertTimestampToDateCoercion("1958-01-01T13:18:03.123", "1958-01-01"); + // after epoch + assertTimestampToDateCoercion("2019-03-18T10:01:17.987", "2019-03-18"); + assertTimestampToDateCoercion("2021-12-31T23:59:59.000", "2021-12-31"); + assertTimestampToDateCoercion("2021-12-31T23:59:59.999", "2021-12-31"); + assertTimestampToDateCoercion("2021-12-31T23:59:59.999999", "2021-12-31"); + assertTimestampToDateCoercion("2021-12-31T23:59:59.999999999", "2021-12-31"); + // time doubled in JVM zone + assertTimestampToDateCoercion("2018-10-28T01:33:17.456", "2018-10-28"); + // epoch + assertTimestampToDateCoercion("1970-01-01T00:00:00.000", "1970-01-01"); + // time gap in JVM zone + assertTimestampToDateCoercion("1970-01-01T00:13:42.000", "1970-01-01"); + assertTimestampToDateCoercion("2018-04-01T02:13:55.123", "2018-04-01"); + // time gap in Vilnius + assertTimestampToDateCoercion("2018-03-25T03:17:17.000", "2018-03-25"); + // time gap in Kathmandu + assertTimestampToDateCoercion("1986-01-01T00:13:07.000", "1986-01-01"); + // before epoch with second fraction + assertTimestampToDateCoercion("1969-12-31T23:59:59.123456", "1969-12-31"); + } + + @Test + public void testHistoricalLongTimestampToDate() + { + assertThatThrownBy(() -> assertTimestampToDateCoercion("1899-12-31T23:59:59.999999999", "1899-12-31")) + .isInstanceOf(TrinoException.class) + .hasMessageContaining("Coercion on historical dates is not supported"); + } + @Test public void testTimestampToVarchar() { @@ -277,4 +314,11 @@ public static void assertCoercions(Type fromType, Object valueToBeCoerced, Type assertThat(blockToNativeValue(toType, coercedValue)) .isEqualTo(expectedValue); } + + private static void assertTimestampToDateCoercion(String timestampAsString, String expectedDate) + { + LocalDateTime localDateTime = LocalDateTime.parse(timestampAsString); + SqlTimestamp timestamp = SqlTimestamp.fromSeconds(TIMESTAMP_PICOS.getPrecision(), localDateTime.toEpochSecond(UTC), localDateTime.get(NANO_OF_SECOND)); + assertCoercions(TIMESTAMP_PICOS, new LongTimestamp(timestamp.getEpochMicros(), timestamp.getPicosOfMicros()), DATE, LocalDate.parse(expectedDate).toEpochDay(), NANOSECONDS); + } } diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/BaseTestHiveCoercion.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/BaseTestHiveCoercion.java index c7ae6580e599..f02df83c37c1 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/BaseTestHiveCoercion.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/BaseTestHiveCoercion.java @@ -52,6 +52,7 @@ import static io.trino.tempto.assertions.QueryAssert.Row.row; import static io.trino.tempto.context.ThreadLocalTestContextHolder.testContext; import static io.trino.tempto.fulfillment.table.TableHandle.tableHandle; +import static io.trino.tests.product.utils.JdbcDriverUtils.resetSessionProperty; import static io.trino.tests.product.utils.JdbcDriverUtils.setSessionProperty; import static io.trino.tests.product.utils.QueryExecutors.onHive; import static io.trino.tests.product.utils.QueryExecutors.onTrino; @@ -148,6 +149,9 @@ protected void doTestHiveCoercion(HiveTableDefinition tableDefinition) "varchar_to_special_double", "char_to_bigger_char", "char_to_smaller_char", + "timestamp_millis_to_date", + "timestamp_micros_to_date", + "timestamp_nanos_to_date", "timestamp_to_string", "timestamp_to_bounded_varchar", "timestamp_to_smaller_varchar", @@ -178,6 +182,8 @@ protected void doTestHiveCoercion(HiveTableDefinition tableDefinition) protected void insertTableRows(String tableName, String floatToDoubleType) { + // Insert all the data with nanoseconds precision + setHiveTimestampPrecision(NANOSECONDS); onTrino().executeQuery(format( "INSERT INTO %1$s VALUES " + "(" + @@ -233,6 +239,9 @@ protected void insertTableRows(String tableName, String floatToDoubleType) " 'NaN'," + " 'abc', " + " 'abc', " + + " TIMESTAMP '2022-12-31 23:59:59.999', " + + " TIMESTAMP '2023-12-31 23:59:59.999999', " + + " TIMESTAMP '2024-12-31 23:59:59.999999999', " + " TIMESTAMP '2121-07-15 15:30:12.123', " + " TIMESTAMP '2121-07-15 15:30:12.123', " + " TIMESTAMP '2121-07-15 15:30:12.123', " + @@ -293,6 +302,9 @@ protected void insertTableRows(String tableName, String floatToDoubleType) " '\uD83D\uDCB0\uD83D\uDCB0\uD83D\uDCB0', " + " '\uD83D\uDCB0\uD83D\uDCB0\uD83D\uDCB0', " + " TIMESTAMP '1970-01-01 00:00:00.123', " + + " TIMESTAMP '1970-01-01 00:00:00.123456', " + + " TIMESTAMP '1970-01-01 00:00:00.123456789', " + + " TIMESTAMP '1970-01-01 00:00:00.123', " + " TIMESTAMP '1970-01-01 00:00:00.123', " + " TIMESTAMP '1970-01-01 00:00:00.123', " + " '1970', " + @@ -300,6 +312,7 @@ protected void insertTableRows(String tableName, String floatToDoubleType) " 1)", tableName, floatToDoubleType)); + resetHiveTimestampPrecision(); } protected Map> expectedValuesForEngineProvider(Engine engine, String tableName, String decimalToFloatVal, String floatToDecimalVal) @@ -512,6 +525,15 @@ else if (getHiveVersionMajor() == 3 && isFormat.test("orc")) { .put("char_to_smaller_char", ImmutableList.of( "ab", "\uD83D\uDCB0\uD83D\uDCB0")) + .put("timestamp_millis_to_date", ImmutableList.of( + java.sql.Date.valueOf("2022-12-31"), + java.sql.Date.valueOf("1970-01-01"))) + .put("timestamp_micros_to_date", ImmutableList.of( + java.sql.Date.valueOf("2023-12-31"), + java.sql.Date.valueOf("1970-01-01"))) + .put("timestamp_nanos_to_date", ImmutableList.of( + java.sql.Date.valueOf("2024-12-31"), + java.sql.Date.valueOf("1970-01-01"))) .put("timestamp_to_string", ImmutableList.of( "2121-07-15 15:30:12.123", "1970-01-01 00:00:00.123")) @@ -543,11 +565,12 @@ protected void doTestHiveCoercionWithDifferentTimestampPrecision(HiveTableDefini """ INSERT INTO %s SELECT - (CAST(ROW (timestamp_value, -1, timestamp_value, CAST(timestamp_value AS VARCHAR)) AS ROW(keep TIMESTAMP(9), si2i SMALLINT, timestamp2string TIMESTAMP(9), string2timestamp VARCHAR))), - ARRAY [CAST(ROW (timestamp_value, -1, timestamp_value, CAST(timestamp_value AS VARCHAR)) AS ROW (keep TIMESTAMP(9), si2i SMALLINT, timestamp2string TIMESTAMP(9), string2timestamp VARCHAR))], - MAP (ARRAY [2], ARRAY [CAST(ROW (timestamp_value, -1, timestamp_value, CAST(timestamp_value AS VARCHAR)) AS ROW (keep TIMESTAMP(9), si2i SMALLINT, timestamp2string TIMESTAMP(9), string2timestamp VARCHAR))]), + (CAST(ROW (timestamp_value, -1, timestamp_value, CAST(timestamp_value AS VARCHAR), timestamp_value) AS ROW(keep TIMESTAMP(9), si2i SMALLINT, timestamp2string TIMESTAMP(9), string2timestamp VARCHAR, timestamp2date TIMESTAMP(9)))), + ARRAY [CAST(ROW (timestamp_value, -1, timestamp_value, CAST(timestamp_value AS VARCHAR), timestamp_value) AS ROW (keep TIMESTAMP(9), si2i SMALLINT, timestamp2string TIMESTAMP(9), string2timestamp VARCHAR, timestamp2date TIMESTAMP(9)))], + MAP (ARRAY [2], ARRAY [CAST(ROW (timestamp_value, -1, timestamp_value, CAST(timestamp_value AS VARCHAR), timestamp_value) AS ROW (keep TIMESTAMP(9), si2i SMALLINT, timestamp2string TIMESTAMP(9), string2timestamp VARCHAR, timestamp2date TIMESTAMP(9)))]), timestamp_value, CAST(timestamp_value AS VARCHAR), + timestamp_value, 1 FROM (VALUES (TIMESTAMP '2121-07-15 15:30:12.123499'), @@ -558,21 +581,23 @@ protected void doTestHiveCoercionWithDifferentTimestampPrecision(HiveTableDefini (TIMESTAMP '2121-07-15 15:30:12.123500001')) AS t (timestamp_value) """.formatted(tableName)); - onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN timestamp_row_to_row timestamp_row_to_row struct", tableName)); - onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN timestamp_list_to_list timestamp_list_to_list array>", tableName)); - onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN timestamp_map_to_map timestamp_map_to_map map>", tableName)); + onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN timestamp_row_to_row timestamp_row_to_row struct", tableName)); + onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN timestamp_list_to_list timestamp_list_to_list array>", tableName)); + onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN timestamp_map_to_map timestamp_map_to_map map>", tableName)); onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN timestamp_to_string timestamp_to_string string", tableName)); onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN string_to_timestamp string_to_timestamp TIMESTAMP", tableName)); + onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN timestamp_to_date timestamp_to_date DATE", tableName)); for (HiveTimestampPrecision hiveTimestampPrecision : HiveTimestampPrecision.values()) { String timestampType = "timestamp(%d)".formatted(hiveTimestampPrecision.getPrecision()); setHiveTimestampPrecision(hiveTimestampPrecision); assertThat(onTrino().executeQuery("SHOW COLUMNS FROM " + tableName).project(1, 2)).containsExactlyInOrder( - row("timestamp_row_to_row", "row(keep %1$s, si2i integer, timestamp2string varchar, string2timestamp %1$s)".formatted(timestampType)), - row("timestamp_list_to_list", "array(row(keep %1$s, si2i integer, timestamp2string varchar, string2timestamp %1$s))".formatted(timestampType)), - row("timestamp_map_to_map", "map(integer, row(keep %1$s, si2i integer, timestamp2string varchar, string2timestamp %1$s))".formatted(timestampType)), + row("timestamp_row_to_row", "row(keep %1$s, si2i integer, timestamp2string varchar, string2timestamp %1$s, timestamp2date date)".formatted(timestampType)), + row("timestamp_list_to_list", "array(row(keep %1$s, si2i integer, timestamp2string varchar, string2timestamp %1$s, timestamp2date date))".formatted(timestampType)), + row("timestamp_map_to_map", "map(integer, row(keep %1$s, si2i integer, timestamp2string varchar, string2timestamp %1$s, timestamp2date date))".formatted(timestampType)), row("timestamp_to_string", "varchar"), row("string_to_timestamp", timestampType), + row("timestamp_to_date", "date"), row("id", "bigint")); List allColumns = ImmutableList.of( @@ -581,6 +606,7 @@ protected void doTestHiveCoercionWithDifferentTimestampPrecision(HiveTableDefini "timestamp_map_to_map", "timestamp_to_string", "string_to_timestamp", + "timestamp_to_date", "id"); // For Trino, remove unsupported columns @@ -611,14 +637,15 @@ protected Map> expectedRowsForEngineProvider(Engine engine, "2121-07-15 15:30:12.123499999", "2121-07-15 15:30:12.1235", "2121-07-15 15:30:12.123500001"); + if (engine == Engine.HIVE) { List baseData = ImmutableList.of( - "{\"keep\":\"2121-07-15 15:30:12.123499\",\"si2i\":-1,\"timestamp2string\":\"2121-07-15 15:30:12.123499\",\"string2timestamp\":\"2121-07-15 15:30:12.123499\"}", - "{\"keep\":\"2121-07-15 15:30:12.1235\",\"si2i\":-1,\"timestamp2string\":\"2121-07-15 15:30:12.1235\",\"string2timestamp\":\"2121-07-15 15:30:12.1235\"}", - "{\"keep\":\"2121-07-15 15:30:12.123501\",\"si2i\":-1,\"timestamp2string\":\"2121-07-15 15:30:12.123501\",\"string2timestamp\":\"2121-07-15 15:30:12.123501\"}", - "{\"keep\":\"2121-07-15 15:30:12.123499999\",\"si2i\":-1,\"timestamp2string\":\"2121-07-15 15:30:12.123499999\",\"string2timestamp\":\"2121-07-15 15:30:12.123499999\"}", - "{\"keep\":\"2121-07-15 15:30:12.1235\",\"si2i\":-1,\"timestamp2string\":\"2121-07-15 15:30:12.1235\",\"string2timestamp\":\"2121-07-15 15:30:12.1235\"}", - "{\"keep\":\"2121-07-15 15:30:12.123500001\",\"si2i\":-1,\"timestamp2string\":\"2121-07-15 15:30:12.123500001\",\"string2timestamp\":\"2121-07-15 15:30:12.123500001\"}"); + "{\"keep\":\"2121-07-15 15:30:12.123499\",\"si2i\":-1,\"timestamp2string\":\"2121-07-15 15:30:12.123499\",\"string2timestamp\":\"2121-07-15 15:30:12.123499\",\"timestamp2date\":\"2121-07-15\"}", + "{\"keep\":\"2121-07-15 15:30:12.1235\",\"si2i\":-1,\"timestamp2string\":\"2121-07-15 15:30:12.1235\",\"string2timestamp\":\"2121-07-15 15:30:12.1235\",\"timestamp2date\":\"2121-07-15\"}", + "{\"keep\":\"2121-07-15 15:30:12.123501\",\"si2i\":-1,\"timestamp2string\":\"2121-07-15 15:30:12.123501\",\"string2timestamp\":\"2121-07-15 15:30:12.123501\",\"timestamp2date\":\"2121-07-15\"}", + "{\"keep\":\"2121-07-15 15:30:12.123499999\",\"si2i\":-1,\"timestamp2string\":\"2121-07-15 15:30:12.123499999\",\"string2timestamp\":\"2121-07-15 15:30:12.123499999\",\"timestamp2date\":\"2121-07-15\"}", + "{\"keep\":\"2121-07-15 15:30:12.1235\",\"si2i\":-1,\"timestamp2string\":\"2121-07-15 15:30:12.1235\",\"string2timestamp\":\"2121-07-15 15:30:12.1235\",\"timestamp2date\":\"2121-07-15\"}", + "{\"keep\":\"2121-07-15 15:30:12.123500001\",\"si2i\":-1,\"timestamp2string\":\"2121-07-15 15:30:12.123500001\",\"string2timestamp\":\"2121-07-15 15:30:12.123500001\",\"timestamp2date\":\"2121-07-15\"}"); return ImmutableMap.>builder() .put("timestamp_row_to_row", baseData) .put("timestamp_list_to_list", baseData.stream() @@ -633,6 +660,7 @@ protected Map> expectedRowsForEngineProvider(Engine engine, .map(String.class::cast) .map(Timestamp::valueOf) .collect(toImmutableList())) + .put("timestamp_to_date", nCopies(6, java.sql.Date.valueOf("2121-07-15"))) .put("id", nCopies(6, 1)) .buildOrThrow(); } @@ -669,6 +697,7 @@ protected Map> expectedRowsForEngineProvider(Engine engine, .addField("si2i", -1) .addField("timestamp2string", timestampCoerced) .addField("string2timestamp", timestamp) + .addField("timestamp2date", java.sql.Date.valueOf("2121-07-15")) .build()) .collect(toImmutableList()); @@ -682,6 +711,7 @@ protected Map> expectedRowsForEngineProvider(Engine engine, .collect(toImmutableList())) .put("timestamp_to_string", timestampAsString) .put("string_to_timestamp", timestampValue) + .put("timestamp_to_date", nCopies(6, java.sql.Date.valueOf("2121-07-15"))) .put("id", nCopies(6, 1)) .buildOrThrow(); } @@ -949,6 +979,9 @@ private void assertProperAlteredTableSchema(String tableName) row("varchar_to_special_double", "double"), row("char_to_bigger_char", "char(4)"), row("char_to_smaller_char", "char(2)"), + row("timestamp_millis_to_date", "date"), + row("timestamp_micros_to_date", "date"), + row("timestamp_nanos_to_date", "date"), row("timestamp_to_string", "varchar"), row("timestamp_to_bounded_varchar", "varchar(30)"), row("timestamp_to_smaller_varchar", "varchar(4)"), @@ -1031,6 +1064,10 @@ private void assertColumnTypes( .put("timestamp_to_smaller_varchar", VARCHAR) .put("smaller_varchar_to_timestamp", TIMESTAMP) .put("varchar_to_timestamp", TIMESTAMP) + .put("timestamp_to_date", DATE) + .put("timestamp_millis_to_date", DATE) + .put("timestamp_micros_to_date", DATE) + .put("timestamp_nanos_to_date", DATE) .put("timestamp_to_varchar", VARCHAR) .put("timestamp_row_to_row", engine == Engine.TRINO ? JAVA_OBJECT : STRUCT) // row .put("timestamp_list_to_list", ARRAY) // list @@ -1098,6 +1135,9 @@ private static void alterTableColumnTypes(String tableName) onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN varchar_to_special_double varchar_to_special_double double", tableName)); onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN char_to_bigger_char char_to_bigger_char char(4)", tableName)); onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN char_to_smaller_char char_to_smaller_char char(2)", tableName)); + onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN timestamp_millis_to_date timestamp_millis_to_date date", tableName)); + onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN timestamp_micros_to_date timestamp_micros_to_date date", tableName)); + onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN timestamp_nanos_to_date timestamp_nanos_to_date date", tableName)); onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN timestamp_to_string timestamp_to_string string", tableName)); onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN timestamp_to_bounded_varchar timestamp_to_bounded_varchar varchar(30)", tableName)); onHive().executeQuery(format("ALTER TABLE %s CHANGE COLUMN timestamp_to_smaller_varchar timestamp_to_smaller_varchar varchar(4)", tableName)); @@ -1199,4 +1239,14 @@ private static void setHiveTimestampPrecision(HiveTimestampPrecision hiveTimesta throw new RuntimeException(e); } } + + private static void resetHiveTimestampPrecision() + { + try { + resetSessionProperty(onTrino().getConnection(), "hive.timestamp_precision"); + } + catch (SQLException e) { + throw new RuntimeException(e); + } + } } diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnPartitionedTable.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnPartitionedTable.java index 709d10125f72..8960585ad40a 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnPartitionedTable.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnPartitionedTable.java @@ -152,6 +152,9 @@ private static HiveTableDefinition.HiveTableDefinitionBuilder tableDefinitionBui " varchar_to_special_double VARCHAR(40)," + " char_to_bigger_char CHAR(3)," + " char_to_smaller_char CHAR(3)," + + " timestamp_millis_to_date TIMESTAMP," + + " timestamp_micros_to_date TIMESTAMP," + + " timestamp_nanos_to_date TIMESTAMP," + " timestamp_to_string TIMESTAMP," + " timestamp_to_bounded_varchar TIMESTAMP," + " timestamp_to_smaller_varchar TIMESTAMP," + @@ -169,11 +172,12 @@ private static HiveTableDefinition.HiveTableDefinitionBuilder tableDefinitionFor return HiveTableDefinition.builder(tableName) .setCreateTableDDLTemplate("" + "CREATE TABLE %NAME%(" + - " timestamp_row_to_row STRUCT, " + - " timestamp_list_to_list ARRAY>, " + - " timestamp_map_to_map MAP>," + + " timestamp_row_to_row STRUCT, " + + " timestamp_list_to_list ARRAY>, " + + " timestamp_map_to_map MAP>," + " timestamp_to_string TIMESTAMP," + - " string_to_timestamp STRING" + + " string_to_timestamp STRING," + + " timestamp_to_date TIMESTAMP" + ") " + "PARTITIONED BY (id BIGINT) " + rowFormat.map(s -> format("ROW FORMAT %s ", s)).orElse("") + diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnUnpartitionedTable.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnUnpartitionedTable.java index cca6f5b05fb8..f11ceb4a1520 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnUnpartitionedTable.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCoercionOnUnpartitionedTable.java @@ -101,6 +101,9 @@ varchar_to_double_infinity VARCHAR(40), varchar_to_special_double VARCHAR(40), char_to_bigger_char CHAR(3), char_to_smaller_char CHAR(3), + timestamp_millis_to_date TIMESTAMP, + timestamp_micros_to_date TIMESTAMP, + timestamp_nanos_to_date TIMESTAMP, timestamp_to_string TIMESTAMP, timestamp_to_bounded_varchar TIMESTAMP, timestamp_to_smaller_varchar TIMESTAMP, @@ -116,11 +119,12 @@ private static HiveTableDefinition.HiveTableDefinitionBuilder tableDefinitionFor return HiveTableDefinition.builder(tableName) .setCreateTableDDLTemplate(""" CREATE TABLE %NAME%( - timestamp_row_to_row STRUCT, - timestamp_list_to_list ARRAY>, - timestamp_map_to_map MAP>, + timestamp_row_to_row STRUCT, + timestamp_list_to_list ARRAY>, + timestamp_map_to_map MAP>, timestamp_to_string TIMESTAMP, string_to_timestamp STRING, + timestamp_to_date TIMESTAMP, id BIGINT) STORED AS\s""" + fileFormat); } From 792de92e4ff186065e7df34857e11b45479a488b Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Mon, 6 Nov 2023 16:02:48 -0800 Subject: [PATCH 205/587] Clarify that TestHiveAcidUtils only tests the behavior of code in Hive --- .../test/java/io/trino/plugin/hive/util/TestAcidTables.java | 3 +++ .../test/java/io/trino/plugin/hive/util/TestHiveAcidUtils.java | 3 +++ 2 files changed, 6 insertions(+) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestAcidTables.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestAcidTables.java index bc78bccbcc82..f59df73221a9 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestAcidTables.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestAcidTables.java @@ -33,6 +33,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.testng.Assert.assertEquals; +/** + * See TestHiveAcidUtils for equivalent tests against Hive AcidUtils from the Hive codebase. + */ public class TestAcidTables { private static final byte[] FAKE_DATA = {65, 66, 67}; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveAcidUtils.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveAcidUtils.java index da4e89ea6cd7..23395f8c219e 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveAcidUtils.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveAcidUtils.java @@ -31,6 +31,9 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNull; +/** + * Test the expected behavior of the Hive AcidUtils class from the Hive codebase. + */ public class TestHiveAcidUtils { @Test From 8b3cc7666994713532d0ae3288ea4b4a8457478f Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Mon, 6 Nov 2023 18:01:15 -0800 Subject: [PATCH 206/587] Convert TestBackgroundHiveSplitLoader to memory file system --- .../hive/TestBackgroundHiveSplitLoader.java | 745 ++++++++---------- 1 file changed, 316 insertions(+), 429 deletions(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java index a791e93b8ae2..0d4191f21fc9 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java @@ -16,19 +16,19 @@ import com.google.common.collect.AbstractIterator; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.ListMultimap; +import com.google.common.io.Resources; import io.airlift.stats.CounterStat; import io.airlift.units.DataSize; import io.airlift.units.Duration; +import io.trino.filesystem.FileEntry; +import io.trino.filesystem.FileIterator; import io.trino.filesystem.Location; -import io.trino.filesystem.hdfs.HdfsFileSystemFactory; -import io.trino.hdfs.DynamicHdfsConfiguration; -import io.trino.hdfs.HdfsConfig; -import io.trino.hdfs.HdfsConfigurationInitializer; -import io.trino.hdfs.HdfsEnvironment; -import io.trino.hdfs.authentication.NoHdfsAuthentication; +import io.trino.filesystem.TrinoFileSystem; +import io.trino.filesystem.TrinoFileSystemFactory; +import io.trino.filesystem.TrinoInputFile; +import io.trino.filesystem.TrinoOutputFile; +import io.trino.filesystem.memory.MemoryFileSystemFactory; import io.trino.plugin.hive.HiveColumnHandle.ColumnType; import io.trino.plugin.hive.fs.CachingDirectoryLister; import io.trino.plugin.hive.fs.DirectoryLister; @@ -48,30 +48,17 @@ import io.trino.spi.predicate.Domain; import io.trino.spi.predicate.TupleDomain; import io.trino.spi.security.ConnectorIdentity; -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.fs.BlockLocation; -import org.apache.hadoop.fs.FSDataInputStream; -import org.apache.hadoop.fs.FSDataOutputStream; -import org.apache.hadoop.fs.FileStatus; -import org.apache.hadoop.fs.FileSystem; -import org.apache.hadoop.fs.LocatedFileStatus; -import org.apache.hadoop.fs.Path; -import org.apache.hadoop.fs.RemoteIterator; -import org.apache.hadoop.fs.permission.FsPermission; -import org.apache.hadoop.hive.ql.io.SymlinkTextInputFormat; -import org.apache.hadoop.util.Progressable; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.parallel.Execution; -import java.io.File; import java.io.IOException; -import java.net.URI; -import java.nio.file.Files; -import java.nio.file.Paths; +import java.io.OutputStream; +import java.time.Instant; import java.util.ArrayList; +import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -88,9 +75,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import static com.google.common.base.Throwables.throwIfUnchecked; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.io.MoreFiles.deleteRecursively; -import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; import static com.google.common.io.Resources.getResource; import static io.airlift.concurrent.MoreFutures.unmodifiableFuture; import static io.airlift.concurrent.Threads.daemonThreadsNamed; @@ -107,8 +91,6 @@ import static io.trino.plugin.hive.HiveStorageFormat.AVRO; import static io.trino.plugin.hive.HiveStorageFormat.CSV; import static io.trino.plugin.hive.HiveStorageFormat.ORC; -import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; -import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_STATS; import static io.trino.plugin.hive.HiveTestUtils.SESSION; import static io.trino.plugin.hive.HiveTestUtils.getHiveSession; import static io.trino.plugin.hive.HiveTimestampPrecision.DEFAULT_PRECISION; @@ -116,6 +98,7 @@ import static io.trino.plugin.hive.HiveType.HIVE_STRING; import static io.trino.plugin.hive.TableType.MANAGED_TABLE; import static io.trino.plugin.hive.util.HiveBucketing.BucketingVersion.BUCKETING_V1; +import static io.trino.plugin.hive.util.HiveClassNames.SYMLINK_TEXT_INPUT_FORMAT_CLASS; import static io.trino.plugin.hive.util.HiveUtil.getRegularColumnHandles; import static io.trino.plugin.hive.util.SerdeConstants.SERIALIZATION_LIB; import static io.trino.spi.predicate.TupleDomain.withColumnDomains; @@ -142,31 +125,19 @@ public class TestBackgroundHiveSplitLoader { private static final int BUCKET_COUNT = 2; - private static final String SAMPLE_PATH = "hdfs://VOL1:9000/db_name/table_name/000000_0"; - private static final String SAMPLE_PATH_FILTERED = "hdfs://VOL1:9000/db_name/table_name/000000_1"; + private static final Location LOCATION = Location.of("memory:///db_name/table_name/000000_0"); + private static final Location FILTERED_LOCATION = Location.of("memory:///db_name/table_name/000000_1"); - private static final Path RETURNED_PATH = new Path(SAMPLE_PATH); - private static final Path FILTERED_PATH = new Path(SAMPLE_PATH_FILTERED); + private static final TupleDomain LOCATION_DOMAIN = withColumnDomains(Map.of(pathColumnHandle(), Domain.singleValue(VARCHAR, utf8Slice(LOCATION.toString())))); - private static final TupleDomain RETURNED_PATH_DOMAIN = withColumnDomains( - ImmutableMap.of( - pathColumnHandle(), - Domain.singleValue(VARCHAR, utf8Slice(RETURNED_PATH.toString())))); + private static final List TEST_LOCATIONS = List.of(LOCATION, FILTERED_LOCATION); - private static final List TEST_FILES = ImmutableList.of( - locatedFileStatus(RETURNED_PATH), - locatedFileStatus(FILTERED_PATH)); + private static final List PARTITION_COLUMNS = List.of(new Column("partitionColumn", HIVE_INT, Optional.empty(), Map.of())); + private static final List BUCKET_COLUMN_HANDLES = List.of(createBaseColumn("col1", 0, HIVE_INT, INTEGER, ColumnType.REGULAR, Optional.empty())); - private static final List PARTITION_COLUMNS = ImmutableList.of( - new Column("partitionColumn", HIVE_INT, Optional.empty())); - private static final List BUCKET_COLUMN_HANDLES = ImmutableList.of( - createBaseColumn("col1", 0, HIVE_INT, INTEGER, ColumnType.REGULAR, Optional.empty())); - - private static final Optional BUCKET_PROPERTY = Optional.of( - new HiveBucketProperty(ImmutableList.of("col1"), BUCKETING_V1, BUCKET_COUNT, ImmutableList.of())); - - private static final Table SIMPLE_TABLE = table(ImmutableList.of(), Optional.empty(), ImmutableMap.of()); - private static final Table PARTITIONED_TABLE = table(PARTITION_COLUMNS, BUCKET_PROPERTY, ImmutableMap.of()); + private static final String TABLE_PATH = "memory:///db_name/table_name"; + private static final Table SIMPLE_TABLE = table(TABLE_PATH, List.of(), Optional.empty(), Map.of()); + private static final Table PARTITIONED_TABLE = table(TABLE_PATH, PARTITION_COLUMNS, Optional.of(new HiveBucketProperty(List.of("col1"), BUCKETING_V1, BUCKET_COUNT, List.of())), Map.of()); private final ExecutorService executor = newCachedThreadPool(daemonThreadsNamed(getClass().getSimpleName() + "-%s")); @@ -180,9 +151,7 @@ public void tearDown() public void testNoPathFilter() throws Exception { - BackgroundHiveSplitLoader backgroundHiveSplitLoader = backgroundHiveSplitLoader( - TEST_FILES, - TupleDomain.none()); + BackgroundHiveSplitLoader backgroundHiveSplitLoader = backgroundHiveSplitLoader(TEST_LOCATIONS, TupleDomain.none()); HiveSplitSource hiveSplitSource = hiveSplitSource(backgroundHiveSplitLoader); backgroundHiveSplitLoader.start(hiveSplitSource); @@ -194,28 +163,31 @@ public void testNoPathFilter() public void testCsv() throws Exception { - DataSize fileSize = DataSize.of(2, GIGABYTE); - assertSplitCount(CSV, ImmutableMap.of(), fileSize, 33); - assertSplitCount(CSV, ImmutableMap.of("skip.header.line.count", "1"), fileSize, 33); - assertSplitCount(CSV, ImmutableMap.of("skip.header.line.count", "2"), fileSize, 1); - assertSplitCount(CSV, ImmutableMap.of("skip.footer.line.count", "1"), fileSize, 1); - assertSplitCount(CSV, ImmutableMap.of("skip.header.line.count", "1", "skip.footer.line.count", "1"), fileSize, 1); + FileEntry file = new FileEntry(LOCATION, DataSize.of(2, GIGABYTE).toBytes(), Instant.now(), Optional.empty()); + assertCsvSplitCount(file, Map.of(), 33); + assertCsvSplitCount(file, Map.of("skip.header.line.count", "1"), 33); + assertCsvSplitCount(file, Map.of("skip.header.line.count", "2"), 1); + assertCsvSplitCount(file, Map.of("skip.footer.line.count", "1"), 1); + assertCsvSplitCount(file, Map.of("skip.header.line.count", "1", "skip.footer.line.count", "1"), 1); } - private void assertSplitCount(HiveStorageFormat storageFormat, Map tableProperties, DataSize fileSize, int expectedSplitCount) + private void assertCsvSplitCount(FileEntry file, Map tableProperties, int expectedSplitCount) throws Exception { Table table = table( - ImmutableList.of(), + TABLE_PATH, + List.of(), Optional.empty(), - ImmutableMap.copyOf(tableProperties), - StorageFormat.fromHiveStorageFormat(storageFormat)); + Map.copyOf(tableProperties), + StorageFormat.fromHiveStorageFormat(CSV)); + TrinoFileSystemFactory fileSystemFactory = new ListSingleFileFileSystemFactory(file); BackgroundHiveSplitLoader backgroundHiveSplitLoader = backgroundHiveSplitLoader( - ImmutableList.of(locatedFileStatus(new Path(SAMPLE_PATH), fileSize.toBytes())), + fileSystemFactory, TupleDomain.all(), Optional.empty(), table, + Optional.empty(), Optional.empty()); HiveSplitSource hiveSplitSource = hiveSplitSource(backgroundHiveSplitLoader); @@ -229,14 +201,14 @@ public void testPathFilter() throws Exception { BackgroundHiveSplitLoader backgroundHiveSplitLoader = backgroundHiveSplitLoader( - TEST_FILES, - RETURNED_PATH_DOMAIN); + TEST_LOCATIONS, + LOCATION_DOMAIN); HiveSplitSource hiveSplitSource = hiveSplitSource(backgroundHiveSplitLoader); backgroundHiveSplitLoader.start(hiveSplitSource); List paths = drain(hiveSplitSource); assertEquals(paths.size(), 1); - assertEquals(paths.get(0), RETURNED_PATH.toString()); + assertEquals(paths.get(0), LOCATION.toString()); } @Test @@ -244,17 +216,17 @@ public void testPathFilterOneBucketMatchPartitionedTable() throws Exception { BackgroundHiveSplitLoader backgroundHiveSplitLoader = backgroundHiveSplitLoader( - TEST_FILES, - RETURNED_PATH_DOMAIN, - Optional.of(new HiveBucketFilter(ImmutableSet.of(0, 1))), + TEST_LOCATIONS, + LOCATION_DOMAIN, + Optional.of(new HiveBucketFilter(Set.of(0, 1))), PARTITIONED_TABLE, - Optional.of(new HiveBucketHandle(BUCKET_COLUMN_HANDLES, BUCKETING_V1, BUCKET_COUNT, BUCKET_COUNT, ImmutableList.of()))); + Optional.of(new HiveBucketHandle(BUCKET_COLUMN_HANDLES, BUCKETING_V1, BUCKET_COUNT, BUCKET_COUNT, List.of()))); HiveSplitSource hiveSplitSource = hiveSplitSource(backgroundHiveSplitLoader); backgroundHiveSplitLoader.start(hiveSplitSource); List paths = drain(hiveSplitSource); assertEquals(paths.size(), 1); - assertEquals(paths.get(0), RETURNED_PATH.toString()); + assertEquals(paths.get(0), LOCATION.toString()); } @Test @@ -262,8 +234,8 @@ public void testPathFilterBucketedPartitionedTable() throws Exception { BackgroundHiveSplitLoader backgroundHiveSplitLoader = backgroundHiveSplitLoader( - TEST_FILES, - RETURNED_PATH_DOMAIN, + TEST_LOCATIONS, + LOCATION_DOMAIN, Optional.empty(), PARTITIONED_TABLE, Optional.of( @@ -272,22 +244,30 @@ public void testPathFilterBucketedPartitionedTable() BUCKETING_V1, BUCKET_COUNT, BUCKET_COUNT, - ImmutableList.of()))); + List.of()))); HiveSplitSource hiveSplitSource = hiveSplitSource(backgroundHiveSplitLoader); backgroundHiveSplitLoader.start(hiveSplitSource); List paths = drain(hiveSplitSource); assertEquals(paths.size(), 1); - assertEquals(paths.get(0), RETURNED_PATH.toString()); + assertEquals(paths.get(0), LOCATION.toString()); } @Test public void testEmptyFileWithNoBlocks() throws Exception { + TrinoFileSystemFactory fileSystemFactory = new MemoryFileSystemFactory(); + // create an empty file + fileSystemFactory.create(ConnectorIdentity.ofUser("test")).newOutputFile(LOCATION).create().close(); + BackgroundHiveSplitLoader backgroundHiveSplitLoader = backgroundHiveSplitLoader( - ImmutableList.of(locatedFileStatusWithNoBlocks(RETURNED_PATH)), - TupleDomain.none()); + fileSystemFactory, + TupleDomain.none(), + Optional.empty(), + SIMPLE_TABLE, + Optional.empty(), + Optional.empty()); HiveSplitSource hiveSplitSource = hiveSplitSource(backgroundHiveSplitLoader); backgroundHiveSplitLoader.start(hiveSplitSource); @@ -298,6 +278,7 @@ public void testEmptyFileWithNoBlocks() @Test public void testNoHangIfPartitionIsOffline() + throws IOException { BackgroundHiveSplitLoader backgroundHiveSplitLoader = backgroundHiveSplitLoaderOfflinePartitions(); HiveSplitSource hiveSplitSource = hiveSplitSource(backgroundHiveSplitLoader); @@ -322,7 +303,7 @@ public void testIncompleteDynamicFilterTimeout() @Override public Set getColumnsCovered() { - return ImmutableSet.of(); + return Set.of(); } @Override @@ -368,7 +349,7 @@ public TupleDomain getCurrentPredicate() public void testCachedDirectoryLister() throws Exception { - CachingDirectoryLister cachingDirectoryLister = new CachingDirectoryLister(new Duration(5, TimeUnit.MINUTES), DataSize.of(100, KILOBYTE), ImmutableList.of("test_dbname.test_table")); + CachingDirectoryLister cachingDirectoryLister = new CachingDirectoryLister(new Duration(5, TimeUnit.MINUTES), DataSize.of(100, KILOBYTE), List.of("test_dbname.test_table")); assertEquals(cachingDirectoryLister.getRequestCount(), 0); int totalCount = 100; @@ -376,7 +357,7 @@ public void testCachedDirectoryLister() List>> futures = new ArrayList<>(); futures.add(executor.submit(() -> { - BackgroundHiveSplitLoader backgroundHiveSplitLoader = backgroundHiveSplitLoader(TEST_FILES, cachingDirectoryLister); + BackgroundHiveSplitLoader backgroundHiveSplitLoader = backgroundHiveSplitLoader(TEST_LOCATIONS, cachingDirectoryLister); HiveSplitSource hiveSplitSource = hiveSplitSource(backgroundHiveSplitLoader); backgroundHiveSplitLoader.start(hiveSplitSource); try { @@ -390,7 +371,7 @@ public void testCachedDirectoryLister() for (int i = 0; i < totalCount - 1; i++) { futures.add(executor.submit(() -> { firstVisit.await(); - BackgroundHiveSplitLoader backgroundHiveSplitLoader = backgroundHiveSplitLoader(TEST_FILES, cachingDirectoryLister); + BackgroundHiveSplitLoader backgroundHiveSplitLoader = backgroundHiveSplitLoader(TEST_LOCATIONS, cachingDirectoryLister); HiveSplitSource hiveSplitSource = hiveSplitSource(backgroundHiveSplitLoader); backgroundHiveSplitLoader.start(hiveSplitSource); return drainSplits(hiveSplitSource); @@ -398,7 +379,7 @@ public void testCachedDirectoryLister() } for (Future> future : futures) { - assertEquals(future.get().size(), TEST_FILES.size()); + assertEquals(future.get().size(), TEST_LOCATIONS.size()); } assertEquals(cachingDirectoryLister.getRequestCount(), totalCount); assertEquals(cachingDirectoryLister.getHitCount(), totalCount - 1); @@ -448,6 +429,7 @@ public void testGetAttemptId() @Test @Timeout(60) public void testPropagateException() + throws IOException { testPropagateException(false, 1); testPropagateException(true, 1); @@ -458,10 +440,11 @@ public void testPropagateException() } private void testPropagateException(boolean error, int threads) + throws IOException { AtomicBoolean iteratorUsedAfterException = new AtomicBoolean(); - HdfsEnvironment hdfsEnvironment = new TestingHdfsEnvironment(TEST_FILES); + TrinoFileSystemFactory fileSystemFactory = createTestingFileSystem(TEST_LOCATIONS); BackgroundHiveSplitLoader backgroundHiveSplitLoader = new BackgroundHiveSplitLoader( SIMPLE_TABLE, new Iterator<>() @@ -492,7 +475,7 @@ public HivePartitionMetadata next() TESTING_TYPE_MANAGER, createBucketSplitInfo(Optional.empty(), Optional.empty()), SESSION, - new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS), + fileSystemFactory, new CachingDirectoryLister(new HiveConfig()), executor, threads, @@ -520,12 +503,14 @@ public HivePartitionMetadata next() public void testMultipleSplitsPerBucket() throws Exception { + TrinoFileSystemFactory fileSystemFactory = new ListSingleFileFileSystemFactory(new FileEntry(LOCATION, DataSize.of(1, GIGABYTE).toBytes(), Instant.now(), Optional.empty())); BackgroundHiveSplitLoader backgroundHiveSplitLoader = backgroundHiveSplitLoader( - ImmutableList.of(locatedFileStatus(new Path(SAMPLE_PATH), DataSize.of(1, GIGABYTE).toBytes())), + fileSystemFactory, TupleDomain.all(), Optional.empty(), SIMPLE_TABLE, - Optional.of(new HiveBucketHandle(BUCKET_COLUMN_HANDLES, BUCKETING_V1, BUCKET_COUNT, BUCKET_COUNT, ImmutableList.of()))); + Optional.of(new HiveBucketHandle(BUCKET_COLUMN_HANDLES, BUCKETING_V1, BUCKET_COUNT, BUCKET_COUNT, List.of())), + Optional.empty()); HiveSplitSource hiveSplitSource = hiveSplitSource(backgroundHiveSplitLoader); backgroundHiveSplitLoader.start(hiveSplitSource); @@ -537,27 +522,28 @@ public void testMultipleSplitsPerBucket() public void testSplitsGenerationWithAbortedTransactions() throws Exception { - java.nio.file.Path tablePath = Files.createTempDirectory("TestBackgroundHiveSplitLoader"); + TrinoFileSystemFactory fileSystemFactory = new MemoryFileSystemFactory(); + TrinoFileSystem fileSystem = fileSystemFactory.create(ConnectorIdentity.ofUser("test")); + Location tableLocation = Location.of("memory:///my_table"); + Table table = table( - tablePath.toString(), - ImmutableList.of(), + tableLocation.toString(), + List.of(), Optional.empty(), - ImmutableMap.of( + Map.of( "transactional", "true", "transactional_properties", "insert_only")); - List filePaths = ImmutableList.of( - tablePath + "/delta_0000001_0000001_0000/_orc_acid_version", - tablePath + "/delta_0000001_0000001_0000/bucket_00000", - tablePath + "/delta_0000002_0000002_0000/_orc_acid_version", - tablePath + "/delta_0000002_0000002_0000/bucket_00000", - tablePath + "/delta_0000003_0000003_0000/_orc_acid_version", - tablePath + "/delta_0000003_0000003_0000/bucket_00000"); - - for (String path : filePaths) { - File file = new File(path); - assertTrue(file.getParentFile().exists() || file.getParentFile().mkdirs(), "Failed creating directory " + file.getParentFile()); - createOrcAcidFile(file); + List fileLocations = List.of( + tableLocation.appendPath("delta_0000001_0000001_0000/_orc_acid_version"), + tableLocation.appendPath("delta_0000001_0000001_0000/bucket_00000"), + tableLocation.appendPath("delta_0000002_0000002_0000/_orc_acid_version"), + tableLocation.appendPath("delta_0000002_0000002_0000/bucket_00000"), + tableLocation.appendPath("delta_0000003_0000003_0000/_orc_acid_version"), + tableLocation.appendPath("delta_0000003_0000003_0000/bucket_00000")); + + for (Location fileLocation : fileLocations) { + createOrcAcidFile(fileSystem, fileLocation); } // ValidWriteIdList is of format $.

:::: @@ -565,7 +551,7 @@ public void testSplitsGenerationWithAbortedTransactions() String validWriteIdsList = format("4$%s.%s:3:9223372036854775807::2", table.getDatabaseName(), table.getTableName()); BackgroundHiveSplitLoader backgroundHiveSplitLoader = backgroundHiveSplitLoader( - HDFS_ENVIRONMENT, + fileSystemFactory, TupleDomain.none(), Optional.empty(), table, @@ -575,41 +561,41 @@ public void testSplitsGenerationWithAbortedTransactions() HiveSplitSource hiveSplitSource = hiveSplitSource(backgroundHiveSplitLoader); backgroundHiveSplitLoader.start(hiveSplitSource); List splits = drain(hiveSplitSource); - assertTrue(splits.stream().anyMatch(p -> p.contains(filePaths.get(1))), format("%s not found in splits %s", filePaths.get(1), splits)); - assertTrue(splits.stream().anyMatch(p -> p.contains(filePaths.get(5))), format("%s not found in splits %s", filePaths.get(5), splits)); - - deleteRecursively(tablePath, ALLOW_INSECURE); + assertThat(splits).contains(fileLocations.get(1).toString()); + assertThat(splits).contains(fileLocations.get(5).toString()); } @Test public void testFullAcidTableWithOriginalFiles() throws Exception { - java.nio.file.Path tablePath = Files.createTempDirectory("TestBackgroundHiveSplitLoader"); + TrinoFileSystemFactory fileSystemFactory = new MemoryFileSystemFactory(); + TrinoFileSystem fileSystem = fileSystemFactory.create(ConnectorIdentity.ofUser("test")); + Location tableLocation = Location.of("memory:///my_table"); + Table table = table( - tablePath.toString(), - ImmutableList.of(), + tableLocation.toString(), + List.of(), Optional.empty(), - ImmutableMap.of("transactional", "true")); - - String originalFile = tablePath + "/000000_1"; - List filePaths = ImmutableList.of( - tablePath + "/delta_0000002_0000002_0000/_orc_acid_version", - tablePath + "/delta_0000002_0000002_0000/bucket_00000"); + Map.of("transactional", "true")); - for (String path : filePaths) { - File file = new File(path); - assertTrue(file.getParentFile().exists() || file.getParentFile().mkdirs(), "Failed creating directory " + file.getParentFile()); - createOrcAcidFile(file); + Location originalFile = tableLocation.appendPath("000000_1"); + try (OutputStream outputStream = fileSystem.newOutputFile(originalFile).create()) { + outputStream.write("test".getBytes(UTF_8)); + } + List fileLocations = List.of( + tableLocation.appendPath("delta_0000002_0000002_0000/_orc_acid_version"), + tableLocation.appendPath("delta_0000002_0000002_0000/bucket_00000")); + for (Location fileLocation : fileLocations) { + createOrcAcidFile(fileSystem, fileLocation); } - Files.write(Paths.get(originalFile), "test".getBytes(UTF_8)); // ValidWriteIdsList is of format $.
:::: // This writeId list has high watermark transaction=3 ValidWriteIdList validWriteIdsList = new ValidWriteIdList(format("4$%s.%s:3:9223372036854775807::", table.getDatabaseName(), table.getTableName())); BackgroundHiveSplitLoader backgroundHiveSplitLoader = backgroundHiveSplitLoader( - HDFS_ENVIRONMENT, + fileSystemFactory, TupleDomain.all(), Optional.empty(), table, @@ -618,30 +604,31 @@ public void testFullAcidTableWithOriginalFiles() HiveSplitSource hiveSplitSource = hiveSplitSource(backgroundHiveSplitLoader); backgroundHiveSplitLoader.start(hiveSplitSource); List splits = drain(hiveSplitSource); - assertTrue(splits.stream().anyMatch(p -> p.contains(originalFile)), format("%s not found in splits %s", filePaths.get(0), splits)); - assertTrue(splits.stream().anyMatch(p -> p.contains(filePaths.get(1))), format("%s not found in splits %s", filePaths.get(1), splits)); + assertThat(splits).contains(originalFile.toString()); + assertThat(splits).contains(fileLocations.get(1).toString()); } @Test public void testVersionValidationNoOrcAcidVersionFile() throws Exception { - java.nio.file.Path tablePath = Files.createTempDirectory("TestBackgroundHiveSplitLoader"); + TrinoFileSystemFactory fileSystemFactory = new MemoryFileSystemFactory(); + TrinoFileSystem fileSystem = fileSystemFactory.create(ConnectorIdentity.ofUser("test")); + Location tableLocation = Location.of("memory:///my_table"); + Table table = table( - tablePath.toString(), - ImmutableList.of(), + tableLocation.toString(), + List.of(), Optional.empty(), - ImmutableMap.of("transactional", "true")); + Map.of("transactional", "true")); - List filePaths = ImmutableList.of( - tablePath + "/000000_1", + List fileLocations = List.of( + tableLocation.appendPath("000000_1"), // no /delta_0000002_0000002_0000/_orc_acid_version file - tablePath + "/delta_0000002_0000002_0000/bucket_00000"); + tableLocation.appendPath("delta_0000002_0000002_0000/bucket_00000")); - for (String path : filePaths) { - File file = new File(path); - assertTrue(file.getParentFile().exists() || file.getParentFile().mkdirs(), "Failed creating directory " + file.getParentFile()); - createOrcAcidFile(file); + for (Location fileLocation : fileLocations) { + createOrcAcidFile(fileSystem, fileLocation); } // ValidWriteIdsList is of format $.
:::: @@ -649,7 +636,7 @@ public void testVersionValidationNoOrcAcidVersionFile() ValidWriteIdList validWriteIdsList = new ValidWriteIdList(format("4$%s.%s:3:9223372036854775807::", table.getDatabaseName(), table.getTableName())); BackgroundHiveSplitLoader backgroundHiveSplitLoader = backgroundHiveSplitLoader( - HDFS_ENVIRONMENT, + fileSystemFactory, TupleDomain.all(), Optional.empty(), table, @@ -664,30 +651,29 @@ public void testVersionValidationNoOrcAcidVersionFile() .allMatch(Optional::isPresent) .extracting(Optional::get) .noneMatch(AcidInfo::isOrcAcidVersionValidated); - - deleteRecursively(tablePath, ALLOW_INSECURE); } @Test public void testVersionValidationOrcAcidVersionFileHasVersion2() throws Exception { - java.nio.file.Path tablePath = Files.createTempDirectory("TestBackgroundHiveSplitLoader"); + TrinoFileSystemFactory fileSystemFactory = new MemoryFileSystemFactory(); + TrinoFileSystem fileSystem = fileSystemFactory.create(ConnectorIdentity.ofUser("test")); + Location tableLocation = Location.of("memory:///my_table"); + Table table = table( - tablePath.toString(), - ImmutableList.of(), + tableLocation.toString(), + List.of(), Optional.empty(), - ImmutableMap.of("transactional", "true")); + Map.of("transactional", "true")); - List filePaths = ImmutableList.of( - tablePath + "/000000_1", // _orc_acid_version does not exist so it's assumed to be "ORC ACID version 0" - tablePath + "/delta_0000002_0000002_0000/_orc_acid_version", - tablePath + "/delta_0000002_0000002_0000/bucket_00000"); + List fileLocations = List.of( + tableLocation.appendPath("000000_1"), // _orc_acid_version does not exist, so it's assumed to be "ORC ACID version 0" + tableLocation.appendPath("delta_0000002_0000002_0000/_orc_acid_version"), + tableLocation.appendPath("delta_0000002_0000002_0000/bucket_00000")); - for (String path : filePaths) { - File file = new File(path); - assertTrue(file.getParentFile().exists() || file.getParentFile().mkdirs(), "Failed creating directory " + file.getParentFile()); - createOrcAcidFile(file, 2); + for (Location fileLocation : fileLocations) { + createOrcAcidFile(fileSystem, fileLocation, 2); } // ValidWriteIdsList is of format $.
:::: @@ -695,7 +681,7 @@ public void testVersionValidationOrcAcidVersionFileHasVersion2() ValidWriteIdList validWriteIdsList = new ValidWriteIdList(format("4$%s.%s:3:9223372036854775807::", table.getDatabaseName(), table.getTableName())); BackgroundHiveSplitLoader backgroundHiveSplitLoader = backgroundHiveSplitLoader( - HDFS_ENVIRONMENT, + fileSystemFactory, TupleDomain.all(), Optional.empty(), table, @@ -708,31 +694,30 @@ public void testVersionValidationOrcAcidVersionFileHasVersion2() // We should have it marked in all splits that NO further ORC ACID validation is required assertThat(drainSplits(hiveSplitSource)).extracting(HiveSplit::getAcidInfo) .allMatch(acidInfo -> acidInfo.isEmpty() || acidInfo.get().isOrcAcidVersionValidated()); - - deleteRecursively(tablePath, ALLOW_INSECURE); } @Test public void testVersionValidationOrcAcidVersionFileHasVersion1() throws Exception { - java.nio.file.Path tablePath = Files.createTempDirectory("TestBackgroundHiveSplitLoader"); + TrinoFileSystemFactory fileSystemFactory = new MemoryFileSystemFactory(); + TrinoFileSystem fileSystem = fileSystemFactory.create(ConnectorIdentity.ofUser("test")); + Location tableLocation = Location.of("memory:///my_table"); + Table table = table( - tablePath.toString(), - ImmutableList.of(), + tableLocation.toString(), + List.of(), Optional.empty(), - ImmutableMap.of("transactional", "true")); + Map.of("transactional", "true")); - List filePaths = ImmutableList.of( - tablePath + "/000000_1", - tablePath + "/delta_0000002_0000002_0000/_orc_acid_version", - tablePath + "/delta_0000002_0000002_0000/bucket_00000"); + List fileLocations = List.of( + tableLocation.appendPath("000000_1"), + tableLocation.appendPath("delta_0000002_0000002_0000/_orc_acid_version"), + tableLocation.appendPath("delta_0000002_0000002_0000/bucket_00000")); - for (String path : filePaths) { - File file = new File(path); - assertTrue(file.getParentFile().exists() || file.getParentFile().mkdirs(), "Failed creating directory " + file.getParentFile()); + for (Location fileLocation : fileLocations) { // _orc_acid_version_exists but has version 1 - createOrcAcidFile(file, 1); + createOrcAcidFile(fileSystem, fileLocation, 1); } // ValidWriteIdsList is of format $.
:::: @@ -740,7 +725,7 @@ public void testVersionValidationOrcAcidVersionFileHasVersion1() ValidWriteIdList validWriteIdsList = new ValidWriteIdList(format("4$%s.%s:3:9223372036854775807::", table.getDatabaseName(), table.getTableName())); BackgroundHiveSplitLoader backgroundHiveSplitLoader = backgroundHiveSplitLoader( - HDFS_ENVIRONMENT, + fileSystemFactory, TupleDomain.all(), Optional.empty(), table, @@ -755,8 +740,6 @@ public void testVersionValidationOrcAcidVersionFileHasVersion1() .allMatch(Optional::isPresent) .extracting(Optional::get) .noneMatch(AcidInfo::isOrcAcidVersionValidated); - - deleteRecursively(tablePath, ALLOW_INSECURE); } @Test @@ -791,24 +774,22 @@ public void testValidateFileBuckets() @Test public void testBuildManifestFileIterator() + throws IOException { - CachingDirectoryLister directoryLister = new CachingDirectoryLister(new Duration(0, TimeUnit.MINUTES), DataSize.ofBytes(0), ImmutableList.of()); + CachingDirectoryLister directoryLister = new CachingDirectoryLister(new Duration(0, TimeUnit.MINUTES), DataSize.ofBytes(0), List.of()); Properties schema = new Properties(); - schema.setProperty(FILE_INPUT_FORMAT, SymlinkTextInputFormat.class.getName()); + schema.setProperty(FILE_INPUT_FORMAT, SYMLINK_TEXT_INPUT_FORMAT_CLASS); schema.setProperty(SERIALIZATION_LIB, AVRO.getSerde()); - Location firstFilePath = Location.of("hdfs://VOL1:9000/db_name/table_name/file1"); - Location secondFilePath = Location.of("hdfs://VOL1:9000/db_name/table_name/file2"); - List locations = ImmutableList.of(firstFilePath, secondFilePath); - List files = locations.stream() - .map(TestBackgroundHiveSplitLoader::locatedFileStatus) - .collect(toImmutableList()); + Location firstFilePath = Location.of("memory:///db_name/table_name/file1"); + Location secondFilePath = Location.of("memory:///db_name/table_name/file2"); + List locations = List.of(firstFilePath, secondFilePath); InternalHiveSplitFactory splitFactory = new InternalHiveSplitFactory( "partition", AVRO, schema, - ImmutableList.of(), + List.of(), TupleDomain.all(), () -> true, TableToPartitionMapping.empty(), @@ -818,11 +799,11 @@ public void testBuildManifestFileIterator() false, Optional.empty()); BackgroundHiveSplitLoader backgroundHiveSplitLoader = backgroundHiveSplitLoader( - files, + locations, directoryLister); Iterator splitIterator = backgroundHiveSplitLoader.buildManifestFileIterator( splitFactory, - Location.of("hdfs://VOL1:9000/db_name/table_name"), + Location.of(TABLE_PATH), locations, true); List splits = ImmutableList.copyOf(splitIterator); @@ -833,24 +814,22 @@ public void testBuildManifestFileIterator() @Test public void testBuildManifestFileIteratorNestedDirectory() + throws IOException { - CachingDirectoryLister directoryLister = new CachingDirectoryLister(new Duration(5, TimeUnit.MINUTES), DataSize.of(100, KILOBYTE), ImmutableList.of()); + CachingDirectoryLister directoryLister = new CachingDirectoryLister(new Duration(5, TimeUnit.MINUTES), DataSize.of(100, KILOBYTE), List.of()); Properties schema = new Properties(); - schema.setProperty(FILE_INPUT_FORMAT, SymlinkTextInputFormat.class.getName()); + schema.setProperty("file.inputformat", SYMLINK_TEXT_INPUT_FORMAT_CLASS); schema.setProperty(SERIALIZATION_LIB, AVRO.getSerde()); - Location filePath = Location.of("hdfs://VOL1:9000/db_name/table_name/file1"); - Location directoryPath = Location.of("hdfs://VOL1:9000/db_name/table_name/dir/file2"); - List locations = ImmutableList.of(filePath, directoryPath); - List files = ImmutableList.of( - locatedFileStatus(filePath), - locatedFileStatus(directoryPath)); + Location filePath = Location.of("memory:///db_name/table_name/file1"); + Location directoryPath = Location.of("memory:///db_name/table_name/dir/file2"); + List locations = List.of(filePath, directoryPath); InternalHiveSplitFactory splitFactory = new InternalHiveSplitFactory( "partition", AVRO, schema, - ImmutableList.of(), + List.of(), TupleDomain.all(), () -> true, TableToPartitionMapping.empty(), @@ -861,11 +840,11 @@ public void testBuildManifestFileIteratorNestedDirectory() Optional.empty()); BackgroundHiveSplitLoader backgroundHiveSplitLoader = backgroundHiveSplitLoader( - files, + locations, directoryLister); Iterator splitIterator = backgroundHiveSplitLoader.buildManifestFileIterator( splitFactory, - Location.of("hdfs://VOL1:9000/db_name/table_name"), + Location.of(TABLE_PATH), locations, false); List splits = ImmutableList.copyOf(splitIterator); @@ -878,12 +857,12 @@ public void testBuildManifestFileIteratorNestedDirectory() public void testMaxPartitions() throws Exception { - CachingDirectoryLister directoryLister = new CachingDirectoryLister(new Duration(0, TimeUnit.MINUTES), DataSize.ofBytes(0), ImmutableList.of()); + CachingDirectoryLister directoryLister = new CachingDirectoryLister(new Duration(0, TimeUnit.MINUTES), DataSize.ofBytes(0), List.of()); // zero partitions { BackgroundHiveSplitLoader backgroundHiveSplitLoader = backgroundHiveSplitLoader( - ImmutableList.of(), - ImmutableList.of(), + List.of(), + List.of(), directoryLister, 0); HiveSplitSource hiveSplitSource = hiveSplitSource(backgroundHiveSplitLoader); @@ -894,21 +873,21 @@ public void testMaxPartitions() // single partition, not crossing the limit { BackgroundHiveSplitLoader backgroundHiveSplitLoader = backgroundHiveSplitLoader( - ImmutableList.of(createPartitionMetadata()), - TEST_FILES, + List.of(createPartitionMetadata()), + TEST_LOCATIONS, directoryLister, 1); HiveSplitSource hiveSplitSource = hiveSplitSource(backgroundHiveSplitLoader); backgroundHiveSplitLoader.start(hiveSplitSource); - assertThat(drainSplits(hiveSplitSource)).hasSize(TEST_FILES.size()); + assertThat(drainSplits(hiveSplitSource)).hasSize(TEST_LOCATIONS.size()); } // single partition, crossing the limit { int partitionLimit = 0; BackgroundHiveSplitLoader backgroundHiveSplitLoader = backgroundHiveSplitLoader( - ImmutableList.of(createPartitionMetadata()), - TEST_FILES, + List.of(createPartitionMetadata()), + TEST_LOCATIONS, directoryLister, partitionLimit); HiveSplitSource hiveSplitSource = hiveSplitSource(backgroundHiveSplitLoader); @@ -924,23 +903,23 @@ public void testMaxPartitions() // multiple partitions, not crossing the limit { int partitionLimit = 3; - List partitions = ImmutableList.of(createPartitionMetadata(), createPartitionMetadata(), createPartitionMetadata()); + List partitions = List.of(createPartitionMetadata(), createPartitionMetadata(), createPartitionMetadata()); BackgroundHiveSplitLoader backgroundHiveSplitLoader = backgroundHiveSplitLoader( partitions, - TEST_FILES, + TEST_LOCATIONS, directoryLister, partitionLimit); HiveSplitSource hiveSplitSource = hiveSplitSource(backgroundHiveSplitLoader); backgroundHiveSplitLoader.start(hiveSplitSource); - assertThat(drainSplits(hiveSplitSource)).hasSize(TEST_FILES.size() * partitions.size()); + assertThat(drainSplits(hiveSplitSource)).hasSize(TEST_LOCATIONS.size() * partitions.size()); } // multiple partitions, crossing the limit { int partitionLimit = 3; BackgroundHiveSplitLoader backgroundHiveSplitLoader = backgroundHiveSplitLoader( - ImmutableList.of(createPartitionMetadata(), createPartitionMetadata(), createPartitionMetadata(), createPartitionMetadata()), - TEST_FILES, + List.of(createPartitionMetadata(), createPartitionMetadata(), createPartitionMetadata(), createPartitionMetadata()), + TEST_LOCATIONS, directoryLister, partitionLimit); HiveSplitSource hiveSplitSource = hiveSplitSource(backgroundHiveSplitLoader); @@ -962,20 +941,22 @@ private static HivePartitionMetadata createPartitionMetadata() TableToPartitionMapping.empty()); } - private static void createOrcAcidFile(File file) + private static void createOrcAcidFile(TrinoFileSystem fileSystem, Location location) throws IOException { - createOrcAcidFile(file, 2); + createOrcAcidFile(fileSystem, location, 2); } - private static void createOrcAcidFile(File file, int orcAcidVersion) + private static void createOrcAcidFile(TrinoFileSystem fileSystem, Location location, int orcAcidVersion) throws IOException { - if (file.getName().equals("_orc_acid_version")) { - Files.write(file.toPath(), String.valueOf(orcAcidVersion).getBytes(UTF_8)); - return; + try (OutputStream outputStream = fileSystem.newOutputFile(location).create()) { + if (location.fileName().equals("_orc_acid_version")) { + outputStream.write(String.valueOf(orcAcidVersion).getBytes(UTF_8)); + return; + } + Resources.copy(getResource("fullacidNationTableWithOriginalFiles/000000_0"), outputStream); } - Files.copy(getResource("fullacidNationTableWithOriginalFiles/000000_0").openStream(), file.toPath()); } private static List drain(HiveSplitSource source) @@ -983,7 +964,7 @@ private static List drain(HiveSplitSource source) { return drainSplits(source).stream() .map(HiveSplit::getPath) - .collect(toImmutableList()); + .toList(); } private static List drainSplits(HiveSplitSource source) @@ -1009,9 +990,11 @@ private static List drainSplits(HiveSplitSource source) private BackgroundHiveSplitLoader backgroundHiveSplitLoader( DynamicFilter dynamicFilter, Duration dynamicFilteringProbeBlockingTimeoutMillis) + throws IOException { + TrinoFileSystemFactory fileSystemFactory = createTestingFileSystem(TEST_LOCATIONS); return backgroundHiveSplitLoader( - new TestingHdfsEnvironment(TEST_FILES), + fileSystemFactory, TupleDomain.all(), dynamicFilter, dynamicFilteringProbeBlockingTimeoutMillis, @@ -1021,12 +1004,26 @@ private BackgroundHiveSplitLoader backgroundHiveSplitLoader( Optional.empty()); } + private static TrinoFileSystemFactory createTestingFileSystem(Collection locations) + throws IOException + { + TrinoFileSystemFactory fileSystemFactory = new MemoryFileSystemFactory(); + TrinoFileSystem fileSystem = fileSystemFactory.create(ConnectorIdentity.ofUser("test")); + for (Location location : locations) { + try (OutputStream outputStream = fileSystem.newOutputFile(location).create()) { + outputStream.write(new byte[10]); + } + } + return fileSystemFactory; + } + private BackgroundHiveSplitLoader backgroundHiveSplitLoader( - List files, + List locations, TupleDomain tupleDomain) + throws IOException { return backgroundHiveSplitLoader( - files, + locations, tupleDomain, Optional.empty(), SIMPLE_TABLE, @@ -1034,14 +1031,15 @@ private BackgroundHiveSplitLoader backgroundHiveSplitLoader( } private BackgroundHiveSplitLoader backgroundHiveSplitLoader( - List files, + List locations, TupleDomain compactEffectivePredicate, Optional hiveBucketFilter, Table table, Optional bucketHandle) + throws IOException { return backgroundHiveSplitLoader( - files, + locations, compactEffectivePredicate, hiveBucketFilter, table, @@ -1050,15 +1048,17 @@ private BackgroundHiveSplitLoader backgroundHiveSplitLoader( } private BackgroundHiveSplitLoader backgroundHiveSplitLoader( - List files, + List locations, TupleDomain compactEffectivePredicate, Optional hiveBucketFilter, Table table, Optional bucketHandle, Optional validWriteIds) + throws IOException { + TrinoFileSystemFactory fileSystemFactory = createTestingFileSystem(locations); return backgroundHiveSplitLoader( - new TestingHdfsEnvironment(files), + fileSystemFactory, compactEffectivePredicate, hiveBucketFilter, table, @@ -1067,7 +1067,7 @@ private BackgroundHiveSplitLoader backgroundHiveSplitLoader( } private BackgroundHiveSplitLoader backgroundHiveSplitLoader( - HdfsEnvironment hdfsEnvironment, + TrinoFileSystemFactory fileSystemFactory, TupleDomain compactEffectivePredicate, Optional hiveBucketFilter, Table table, @@ -1075,7 +1075,7 @@ private BackgroundHiveSplitLoader backgroundHiveSplitLoader( Optional validWriteIds) { return backgroundHiveSplitLoader( - hdfsEnvironment, + fileSystemFactory, compactEffectivePredicate, DynamicFilter.EMPTY, new Duration(0, SECONDS), @@ -1086,7 +1086,7 @@ private BackgroundHiveSplitLoader backgroundHiveSplitLoader( } private BackgroundHiveSplitLoader backgroundHiveSplitLoader( - HdfsEnvironment hdfsEnvironment, + TrinoFileSystemFactory fileSystemFactory, TupleDomain compactEffectivePredicate, DynamicFilter dynamicFilter, Duration dynamicFilteringProbeBlockingTimeout, @@ -1096,7 +1096,7 @@ private BackgroundHiveSplitLoader backgroundHiveSplitLoader( Optional validWriteIds) { List hivePartitionMetadatas = - ImmutableList.of( + List.of( new HivePartitionMetadata( new HivePartition(new SchemaTableName("testSchema", "table_name")), Optional.empty(), @@ -1111,7 +1111,7 @@ private BackgroundHiveSplitLoader backgroundHiveSplitLoader( TESTING_TYPE_MANAGER, createBucketSplitInfo(bucketHandle, hiveBucketFilter), SESSION, - new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS), + fileSystemFactory, new CachingDirectoryLister(new HiveConfig()), executor, 2, @@ -1123,27 +1123,29 @@ private BackgroundHiveSplitLoader backgroundHiveSplitLoader( } private BackgroundHiveSplitLoader backgroundHiveSplitLoader( - List files, + List locations, DirectoryLister directoryLister) + throws IOException { - List partitions = ImmutableList.of( + List partitions = List.of( new HivePartitionMetadata( new HivePartition(new SchemaTableName("testSchema", "table_name")), Optional.empty(), TableToPartitionMapping.empty())); - return backgroundHiveSplitLoader(partitions, files, directoryLister, 100); + return backgroundHiveSplitLoader(partitions, locations, directoryLister, 100); } private BackgroundHiveSplitLoader backgroundHiveSplitLoader( List partitions, - List files, + List locations, DirectoryLister directoryLister, int maxPartitions) + throws IOException { ConnectorSession connectorSession = getHiveSession(new HiveConfig() .setMaxSplitSize(DataSize.of(1, GIGABYTE))); - HdfsEnvironment hdfsEnvironment = new TestingHdfsEnvironment(files); + TrinoFileSystemFactory fileSystemFactory = createTestingFileSystem(locations); return new BackgroundHiveSplitLoader( SIMPLE_TABLE, partitions.iterator(), @@ -1153,7 +1155,7 @@ private BackgroundHiveSplitLoader backgroundHiveSplitLoader( TESTING_TYPE_MANAGER, Optional.empty(), connectorSession, - new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS), + fileSystemFactory, directoryLister, executor, 2, @@ -1165,11 +1167,12 @@ private BackgroundHiveSplitLoader backgroundHiveSplitLoader( } private BackgroundHiveSplitLoader backgroundHiveSplitLoaderOfflinePartitions() + throws IOException { ConnectorSession connectorSession = getHiveSession(new HiveConfig() .setMaxSplitSize(DataSize.of(1, GIGABYTE))); - HdfsEnvironment hdfsEnvironment = new TestingHdfsEnvironment(TEST_FILES); + TrinoFileSystemFactory fileSystemFactory = createTestingFileSystem(TEST_LOCATIONS); return new BackgroundHiveSplitLoader( SIMPLE_TABLE, createPartitionMetadataWithOfflinePartitions(), @@ -1179,7 +1182,7 @@ private BackgroundHiveSplitLoader backgroundHiveSplitLoaderOfflinePartitions() TESTING_TYPE_MANAGER, createBucketSplitInfo(Optional.empty(), Optional.empty()), connectorSession, - new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS), + fileSystemFactory, new CachingDirectoryLister(new HiveConfig()), executor, 2, @@ -1203,17 +1206,11 @@ private static Iterator createPartitionMetadataWithOfflin protected HivePartitionMetadata computeNext() { position++; - switch (position) { - case 0: - return new HivePartitionMetadata( - new HivePartition(new SchemaTableName("testSchema", "table_name")), - Optional.empty(), - TableToPartitionMapping.empty()); - case 1: - throw new RuntimeException("OFFLINE"); - default: - return endOfData(); - } + return switch (position) { + case 0 -> new HivePartitionMetadata(new HivePartition(new SchemaTableName("testSchema", "table_name")), Optional.empty(), TableToPartitionMapping.empty()); + case 1 -> throw new RuntimeException("OFFLINE"); + default -> endOfData(); + }; } }; } @@ -1234,41 +1231,17 @@ private HiveSplitSource hiveSplitSource(HiveSplitLoader hiveSplitLoader) false); } - private static Table table( - List partitionColumns, - Optional bucketProperty, - ImmutableMap tableParameters) - { - return table(partitionColumns, - bucketProperty, - tableParameters, - StorageFormat.create(ORC.getSerde(), ORC.getInputFormat(), ORC.getOutputFormat())); - } - private static Table table( String location, List partitionColumns, Optional bucketProperty, - ImmutableMap tableParameters) + Map tableParameters) { return table(location, partitionColumns, bucketProperty, tableParameters, - StorageFormat.create(ORC.getSerde(), ORC.getInputFormat(), ORC.getOutputFormat())); - } - - private static Table table( - List partitionColumns, - Optional bucketProperty, - Map tableParameters, - StorageFormat storageFormat) - { - return table("hdfs://VOL1:9000/db_name/table_name", - partitionColumns, - bucketProperty, - tableParameters, - storageFormat); + StorageFormat.fromHiveStorageFormat(ORC)); } private static Table table( @@ -1290,192 +1263,106 @@ private static Table table( .setOwner(Optional.of("testOwner")) .setTableName("test_table") .setTableType(MANAGED_TABLE.name()) - .setDataColumns(ImmutableList.of(new Column("col1", HIVE_STRING, Optional.empty()))) + .setDataColumns(List.of(new Column("col1", HIVE_STRING, Optional.empty(), Map.of()))) .setParameters(tableParameters) .setPartitionColumns(partitionColumns) .build(); } - private static LocatedFileStatus locatedFileStatus(Location location) - { - return locatedFileStatus(new Path(location.toString()), 10); - } - - private static LocatedFileStatus locatedFileStatus(Path path) - { - return locatedFileStatus(path, 10); - } - - private static LocatedFileStatus locatedFileStatus(Path path, long fileLength) - { - return new LocatedFileStatus( - fileLength, - false, - 0, - 0L, - 0L, - 0L, - null, - null, - null, - null, - path, - new BlockLocation[] {new BlockLocation(new String[1], new String[] {"localhost"}, 0, fileLength)}); - } - - private static LocatedFileStatus locatedFileStatusWithNoBlocks(Path path) - { - return new LocatedFileStatus( - 0L, - false, - 0, - 0L, - 0L, - 0L, - null, - null, - null, - null, - path, - new BlockLocation[] {}); - } - - public static class TestingHdfsEnvironment - extends HdfsEnvironment - { - private final List files; - - public TestingHdfsEnvironment(List files) - { - super( - new DynamicHdfsConfiguration( - new HdfsConfigurationInitializer(new HdfsConfig()), - ImmutableSet.of()), - new HdfsConfig(), - new NoHdfsAuthentication()); - this.files = ImmutableList.copyOf(files); - } - - @Override - public FileSystem getFileSystem(ConnectorIdentity identity, Path path, Configuration configuration) - { - return new TestingHdfsFileSystem(files); - } - } - - private static class TestingHdfsFileSystem - extends FileSystem + private record ListSingleFileFileSystemFactory(FileEntry fileEntry) + implements TrinoFileSystemFactory { - private final List files; - - public TestingHdfsFileSystem(List files) - { - this.files = ImmutableList.copyOf(files); - } - - @Override - public boolean delete(Path f, boolean recursive) - { - throw new UnsupportedOperationException(); - } - @Override - public boolean rename(Path src, Path dst) + public TrinoFileSystem create(ConnectorIdentity identity) { - throw new UnsupportedOperationException(); - } + return new TrinoFileSystem() + { + @Override + public Optional directoryExists(Location location) + { + return Optional.empty(); + } - @Override - public void setWorkingDirectory(Path dir) - { - throw new UnsupportedOperationException(); - } + @Override + public FileIterator listFiles(Location location) + { + Iterator iterator = List.of(fileEntry).iterator(); + return new FileIterator() + { + @Override + public boolean hasNext() + { + return iterator.hasNext(); + } - @Override - public FileStatus[] listStatus(Path f) - { - FileStatus[] fileStatuses = new FileStatus[files.size()]; - for (int i = 0; i < files.size(); i++) { - LocatedFileStatus locatedFileStatus = files.get(i); - fileStatuses[i] = new FileStatus( - locatedFileStatus.getLen(), - locatedFileStatus.isDirectory(), - locatedFileStatus.getReplication(), - locatedFileStatus.getBlockSize(), - locatedFileStatus.getModificationTime(), - locatedFileStatus.getPath()); - } - return fileStatuses; - } + @Override + public FileEntry next() + { + return iterator.next(); + } + }; + } - @Override - public RemoteIterator listLocatedStatus(Path f) - { - return new RemoteIterator<>() - { - private final Iterator iterator = files.iterator(); + @Override + public TrinoInputFile newInputFile(Location location) + { + throw new UnsupportedOperationException(); + } @Override - public boolean hasNext() + public TrinoInputFile newInputFile(Location location, long length) { - return iterator.hasNext(); + throw new UnsupportedOperationException(); } @Override - public LocatedFileStatus next() + public TrinoOutputFile newOutputFile(Location location) { - return iterator.next(); + throw new UnsupportedOperationException(); } - }; - } - @Override - public FSDataOutputStream create( - Path f, - FsPermission permission, - boolean overwrite, - int bufferSize, - short replication, - long blockSize, - Progressable progress) - { - throw new UnsupportedOperationException(); - } + @Override + public void deleteFile(Location location) + { + throw new UnsupportedOperationException(); + } - @Override - public boolean mkdirs(Path f, FsPermission permission) - { - throw new UnsupportedOperationException(); - } + @Override + public void deleteDirectory(Location location) + { + throw new UnsupportedOperationException(); + } - @Override - public FSDataOutputStream append(Path f, int bufferSize, Progressable progress) - { - throw new UnsupportedOperationException(); - } + @Override + public void renameFile(Location source, Location target) + { + throw new UnsupportedOperationException(); + } - @Override - public FSDataInputStream open(Path f, int bufferSize) - { - throw new UnsupportedOperationException(); - } + @Override + public void createDirectory(Location location) + { + throw new UnsupportedOperationException(); + } - @Override - public FileStatus getFileStatus(Path f) - { - throw new UnsupportedOperationException(); - } + @Override + public void renameDirectory(Location source, Location target) + { + throw new UnsupportedOperationException(); + } - @Override - public Path getWorkingDirectory() - { - return new Path(getUri()); - } + @Override + public Set listDirectories(Location location) + { + throw new UnsupportedOperationException(); + } - @Override - public URI getUri() - { - return URI.create("hdfs://VOL1:9000/"); + @Override + public Optional createTemporaryDirectory(Location targetPath, String temporaryPrefix, String relativePrefix) + { + throw new UnsupportedOperationException(); + } + }; } } } From c62c1b9e4cd817eabab1368d6d2a10f8034e2705 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Mon, 6 Nov 2023 19:03:11 -0800 Subject: [PATCH 207/587] Convert TestOrcPageSourceFactory to memory file system --- .../hive/orc/TestOrcPageSourceFactory.java | 131 +++++++++++------- 1 file changed, 79 insertions(+), 52 deletions(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPageSourceFactory.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPageSourceFactory.java index cf367a0ec76b..3ad44049fd8c 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPageSourceFactory.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPageSourceFactory.java @@ -15,8 +15,11 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.io.Resources; import io.trino.filesystem.Location; -import io.trino.filesystem.hdfs.HdfsFileSystemFactory; +import io.trino.filesystem.TrinoFileSystem; +import io.trino.filesystem.TrinoFileSystemFactory; +import io.trino.filesystem.memory.MemoryFileSystemFactory; import io.trino.plugin.hive.AcidInfo; import io.trino.plugin.hive.FileFormatDataSourceStats; import io.trino.plugin.hive.HiveColumnHandle; @@ -27,14 +30,15 @@ import io.trino.spi.connector.ConnectorPageSource; import io.trino.spi.predicate.Domain; import io.trino.spi.predicate.TupleDomain; +import io.trino.spi.security.ConnectorIdentity; import io.trino.spi.type.Type; import io.trino.tpch.Nation; import io.trino.tpch.NationColumn; import io.trino.tpch.NationGenerator; import org.junit.jupiter.api.Test; -import java.io.File; -import java.net.URISyntaxException; +import java.io.IOException; +import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -51,8 +55,7 @@ import static io.trino.plugin.hive.HiveColumnHandle.ColumnType.REGULAR; import static io.trino.plugin.hive.HiveColumnHandle.createBaseColumn; import static io.trino.plugin.hive.HiveStorageFormat.ORC; -import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; -import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_STATS; +import static io.trino.plugin.hive.HiveTableProperties.TRANSACTIONAL; import static io.trino.plugin.hive.HiveTestUtils.SESSION; import static io.trino.plugin.hive.HiveType.toHiveType; import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; @@ -66,8 +69,6 @@ import static io.trino.tpch.NationColumn.REGION_KEY; import static java.util.Collections.nCopies; import static org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.FILE_INPUT_FORMAT; -import static org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.TABLE_IS_TRANSACTIONAL; -import static org.apache.hadoop.hive.ql.io.AcidUtils.deleteDeltaSubdir; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; @@ -75,20 +76,17 @@ public class TestOrcPageSourceFactory { private static final Map ALL_COLUMNS = ImmutableMap.of(NATION_KEY, 0, NAME, 1, REGION_KEY, 2, COMMENT, 3); - private static final HivePageSourceFactory PAGE_SOURCE_FACTORY = new OrcPageSourceFactory( - new OrcReaderConfig(), - new HdfsFileSystemFactory(HDFS_ENVIRONMENT, HDFS_FILE_SYSTEM_STATS), - new FileFormatDataSourceStats(), - new HiveConfig()); @Test public void testFullFileRead() + throws IOException { assertRead(ImmutableMap.of(NATION_KEY, 0, NAME, 1, REGION_KEY, 2, COMMENT, 3), OptionalLong.empty(), Optional.empty(), nationKey -> false); } @Test public void testSingleColumnRead() + throws IOException { assertRead(ImmutableMap.of(REGION_KEY, ALL_COLUMNS.get(REGION_KEY)), OptionalLong.empty(), Optional.empty(), nationKey -> false); } @@ -98,6 +96,7 @@ public void testSingleColumnRead() */ @Test public void testFullFileSkipped() + throws IOException { assertRead(ALL_COLUMNS, OptionalLong.of(100L), Optional.empty(), nationKey -> false); } @@ -107,34 +106,44 @@ public void testFullFileSkipped() */ @Test public void testSomeStripesAndRowGroupRead() + throws IOException { assertRead(ALL_COLUMNS, OptionalLong.of(5L), Optional.empty(), nationKey -> false); } @Test public void testDeletedRows() + throws IOException { - Location partitionLocation = Location.of(getResource("nation_delete_deltas").toString()); - Optional acidInfo = AcidInfo.builder(partitionLocation) - .addDeleteDelta(partitionLocation.appendPath(deleteDeltaSubdir(3L, 3L, 0))) - .addDeleteDelta(partitionLocation.appendPath(deleteDeltaSubdir(4L, 4L, 0))) + TrinoFileSystemFactory fileSystemFactory = new MemoryFileSystemFactory(); + Location fileLocation = copyResource(fileSystemFactory, "nationFile25kRowsSortedOnNationKey/bucket_00000"); + long fileLength = fileSystemFactory.create(ConnectorIdentity.ofUser("test")).newInputFile(fileLocation).length(); + + Location deleteFile3 = copyResource(fileSystemFactory, "nation_delete_deltas/delete_delta_0000003_0000003_0000/bucket_00000"); + Location deleteFile4 = copyResource(fileSystemFactory, "nation_delete_deltas/delete_delta_0000004_0000004_0000/bucket_00000"); + Optional acidInfo = AcidInfo.builder(deleteFile3.parentDirectory().parentDirectory()) + .addDeleteDelta(deleteFile3.parentDirectory()) + .addDeleteDelta(deleteFile4.parentDirectory()) .build(); - assertRead(ALL_COLUMNS, OptionalLong.empty(), acidInfo, nationKey -> nationKey == 5 || nationKey == 19); + List actual = readFile(fileSystemFactory, ALL_COLUMNS, OptionalLong.empty(), acidInfo, fileLocation, fileLength); + + List expected = expectedResult(OptionalLong.empty(), nationKey -> nationKey == 5 || nationKey == 19, 1000); + assertEqualsByColumns(ALL_COLUMNS.keySet(), actual, expected); } @Test public void testReadWithAcidVersionValidationHive3() throws Exception { - File tableFile = new File(getResource("acid_version_validation/acid_version_hive_3/00000_0").toURI()); - Location tablePath = Location.of(tableFile.getParentFile().toURI().toString()); + TrinoFileSystemFactory fileSystemFactory = new MemoryFileSystemFactory(); + Location fileLocation = copyResource(fileSystemFactory, "acid_version_validation/acid_version_hive_3/00000_0"); - Optional acidInfo = AcidInfo.builder(tablePath) + Optional acidInfo = AcidInfo.builder(fileLocation.parentDirectory()) .setOrcAcidVersionValidated(false) .build(); - List result = readFile(Map.of(), OptionalLong.empty(), acidInfo, tableFile.getPath(), 625); + List result = readFile(fileSystemFactory, Map.of(), OptionalLong.empty(), acidInfo, fileLocation, 625); assertEquals(result.size(), 1); } @@ -142,14 +151,14 @@ public void testReadWithAcidVersionValidationHive3() public void testReadWithAcidVersionValidationNoVersionInMetadata() throws Exception { - File tableFile = new File(getResource("acid_version_validation/no_orc_acid_version_in_metadata/00000_0").toURI()); - Location tablePath = Location.of(tableFile.getParentFile().toURI().toString()); + TrinoFileSystemFactory fileSystemFactory = new MemoryFileSystemFactory(); + Location fileLocation = copyResource(fileSystemFactory, "acid_version_validation/no_orc_acid_version_in_metadata/00000_0"); - Optional acidInfo = AcidInfo.builder(tablePath) + Optional acidInfo = AcidInfo.builder(fileLocation.parentDirectory()) .setOrcAcidVersionValidated(false) .build(); - assertThatThrownBy(() -> readFile(Map.of(), OptionalLong.empty(), acidInfo, tableFile.getPath(), 730)) + assertThatThrownBy(() -> readFile(fileSystemFactory, Map.of(), OptionalLong.empty(), acidInfo, fileLocation, 730)) .hasMessageMatching("Hive transactional tables are supported since Hive 3.0. Expected `hive.acid.version` in ORC metadata" + " in .*/acid_version_validation/no_orc_acid_version_in_metadata/00000_0 to be >=2 but was ." + " If you have upgraded from an older version of Hive, make sure a major compaction has been run at least once after the upgrade."); @@ -159,17 +168,19 @@ public void testReadWithAcidVersionValidationNoVersionInMetadata() public void testFullFileReadOriginalFilesTable() throws Exception { - File tableFile = new File(getResource("fullacidNationTableWithOriginalFiles/000000_0").toURI()); - Location tablePath = Location.of(tableFile.toURI().toString()).parentDirectory(); + TrinoFileSystemFactory fileSystemFactory = new MemoryFileSystemFactory(); + Location fileLocation = copyResource(fileSystemFactory, "fullacidNationTableWithOriginalFiles/000000_0"); + Location deleteDeltaLocation = copyResource(fileSystemFactory, "fullacidNationTableWithOriginalFiles/delete_delta_10000001_10000001_0000/bucket_00000"); + Location tablePath = fileLocation.parentDirectory(); AcidInfo acidInfo = AcidInfo.builder(tablePath) - .addDeleteDelta(tablePath.appendPath(deleteDeltaSubdir(10000001, 10000001, 0))) - .addOriginalFile(tablePath.appendPath("000000_0"), 1780, 0) + .addDeleteDelta(deleteDeltaLocation.parentDirectory()) + .addOriginalFile(fileLocation, 1780, 0) .setOrcAcidVersionValidated(true) .buildWithRequiredOriginalFiles(0); List expected = expectedResult(OptionalLong.empty(), nationKey -> nationKey == 24, 1); - List result = readFile(ALL_COLUMNS, OptionalLong.empty(), Optional.of(acidInfo), tablePath + "/000000_0", 1780); + List result = readFile(fileSystemFactory, ALL_COLUMNS, OptionalLong.empty(), Optional.of(acidInfo), fileLocation, 1780); assertEquals(result.size(), expected.size()); int deletedRowKey = 24; @@ -179,6 +190,7 @@ public void testFullFileReadOriginalFilesTable() } private static void assertRead(Map columns, OptionalLong nationKeyPredicate, Optional acidInfo, LongPredicate deletedRows) + throws IOException { List actual = readFile(columns, nationKeyPredicate, acidInfo); @@ -203,18 +215,22 @@ private static List expectedResult(OptionalLong nationKeyPredicate, Long } private static List readFile(Map columns, OptionalLong nationKeyPredicate, Optional acidInfo) + throws IOException { // This file has the contains the TPC-H nation table which each row repeated 1000 times - try { - File testFile = new File(getResource("nationFile25kRowsSortedOnNationKey/bucket_00000").toURI()); - return readFile(columns, nationKeyPredicate, acidInfo, testFile.toURI().getPath(), testFile.length()); - } - catch (URISyntaxException e) { - throw new RuntimeException(e); - } + TrinoFileSystemFactory fileSystemFactory = new MemoryFileSystemFactory(); + Location fileLocation = copyResource(fileSystemFactory, "nationFile25kRowsSortedOnNationKey/bucket_00000"); + long fileLength = fileSystemFactory.create(ConnectorIdentity.ofUser("test")).newInputFile(fileLocation).length(); + return readFile(fileSystemFactory, columns, nationKeyPredicate, acidInfo, fileLocation, fileLength); } - private static List readFile(Map columns, OptionalLong nationKeyPredicate, Optional acidInfo, String filePath, long fileSize) + private static List readFile( + TrinoFileSystemFactory fileSystemFactory, + Map columns, + OptionalLong nationKeyPredicate, + Optional acidInfo, + Location location, + long fileSize) { TupleDomain tupleDomain = TupleDomain.all(); if (nationKeyPredicate.isPresent()) { @@ -229,9 +245,15 @@ private static List readFile(Map columns, Optiona .map(HiveColumnHandle::getName) .collect(toImmutableList()); - Optional pageSourceWithProjections = PAGE_SOURCE_FACTORY.createPageSource( + HivePageSourceFactory pageSourceFactory = new OrcPageSourceFactory( + new OrcReaderConfig(), + fileSystemFactory, + new FileFormatDataSourceStats(), + new HiveConfig()); + + Optional pageSourceWithProjections = pageSourceFactory.createPageSource( SESSION, - Location.of(filePath), + location, 0, fileSize, fileSize, @@ -291,17 +313,11 @@ private static List readFile(Map columns, Optiona private static HiveColumnHandle toHiveColumnHandle(NationColumn nationColumn, int hiveColumnIndex) { - Type trinoType; - switch (nationColumn.getType().getBase()) { - case IDENTIFIER: - trinoType = BIGINT; - break; - case VARCHAR: - trinoType = VARCHAR; - break; - default: - throw new IllegalStateException("Unexpected value: " + nationColumn.getType().getBase()); - } + Type trinoType = switch (nationColumn.getType().getBase()) { + case IDENTIFIER -> BIGINT; + case VARCHAR -> VARCHAR; + default -> throw new IllegalStateException("Unexpected value: " + nationColumn.getType().getBase()); + }; return createBaseColumn( nationColumn.getColumnName(), @@ -317,7 +333,7 @@ private static Properties createSchema() Properties schema = new Properties(); schema.setProperty(SERIALIZATION_LIB, ORC.getSerde()); schema.setProperty(FILE_INPUT_FORMAT, ORC.getInputFormat()); - schema.setProperty(TABLE_IS_TRANSACTIONAL, "true"); + schema.setProperty(TRANSACTIONAL, "true"); return schema; } @@ -333,4 +349,15 @@ private static void assertEqualsByColumns(Set columns, List"); } } + + private static Location copyResource(TrinoFileSystemFactory fileSystemFactory, String resourceName) + throws IOException + { + Location location = Location.of("memory:///" + resourceName); + TrinoFileSystem fileSystem = fileSystemFactory.create(ConnectorIdentity.ofUser("test")); + try (OutputStream outputStream = fileSystem.newOutputFile(location).create()) { + Resources.copy(getResource(resourceName), outputStream); + } + return location; + } } From ed5c60db41de6f1b9f6e4199c05a760179517a8d Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Mon, 6 Nov 2023 20:50:38 -0800 Subject: [PATCH 208/587] Convert TestNodeLocalDynamicSplitPruning to memory file system --- .../io/trino/plugin/hive/HiveTestUtils.java | 12 ++++- .../TestNodeLocalDynamicSplitPruning.java | 49 ++++++++++--------- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/HiveTestUtils.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/HiveTestUtils.java index ae3ba579bc8c..38abf499425b 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/HiveTestUtils.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/HiveTestUtils.java @@ -171,7 +171,11 @@ public static HiveSessionProperties getHiveSessionProperties(HiveConfig hiveConf public static Set getDefaultHivePageSourceFactories(HdfsEnvironment hdfsEnvironment, HiveConfig hiveConfig) { - TrinoFileSystemFactory fileSystemFactory = new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS); + return getDefaultHivePageSourceFactories(new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS), hiveConfig); + } + + public static Set getDefaultHivePageSourceFactories(TrinoFileSystemFactory fileSystemFactory, HiveConfig hiveConfig) + { FileFormatDataSourceStats stats = new FileFormatDataSourceStats(); return ImmutableSet.builder() .add(new CsvPageSourceFactory(fileSystemFactory, hiveConfig)) @@ -189,7 +193,11 @@ public static Set getDefaultHivePageSourceFactories(HdfsE public static Set getDefaultHiveFileWriterFactories(HiveConfig hiveConfig, HdfsEnvironment hdfsEnvironment) { - TrinoFileSystemFactory fileSystemFactory = new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS); + return getDefaultHiveFileWriterFactories(hiveConfig, new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS)); + } + + public static Set getDefaultHiveFileWriterFactories(HiveConfig hiveConfig, TrinoFileSystemFactory fileSystemFactory) + { NodeVersion nodeVersion = new NodeVersion("test_version"); return ImmutableSet.builder() .add(new CsvFileWriterFactory(fileSystemFactory, TESTING_TYPE_MANAGER)) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestNodeLocalDynamicSplitPruning.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestNodeLocalDynamicSplitPruning.java index b13bd0a4b9c9..fd61d85be3dc 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestNodeLocalDynamicSplitPruning.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestNodeLocalDynamicSplitPruning.java @@ -16,7 +16,9 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import io.airlift.testing.TempFile; +import io.trino.filesystem.Location; +import io.trino.filesystem.TrinoFileSystemFactory; +import io.trino.filesystem.memory.MemoryFileSystemFactory; import io.trino.metadata.TableHandle; import io.trino.plugin.hive.metastore.Column; import io.trino.plugin.hive.orc.OrcReaderConfig; @@ -30,10 +32,10 @@ import io.trino.spi.connector.EmptyPageSource; import io.trino.spi.predicate.Domain; import io.trino.spi.predicate.TupleDomain; +import io.trino.spi.security.ConnectorIdentity; import io.trino.testing.TestingConnectorSession; import org.junit.jupiter.api.Test; -import java.io.File; import java.io.IOException; import java.util.Map; import java.util.Optional; @@ -44,7 +46,6 @@ import static io.trino.plugin.hive.HiveColumnHandle.ColumnType.PARTITION_KEY; import static io.trino.plugin.hive.HiveColumnHandle.ColumnType.REGULAR; -import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; import static io.trino.plugin.hive.HiveTestUtils.getDefaultHivePageSourceFactories; import static io.trino.plugin.hive.HiveType.HIVE_INT; import static io.trino.plugin.hive.util.HiveBucketing.BucketingVersion.BUCKETING_V1; @@ -85,14 +86,12 @@ public void testDynamicBucketPruning() { HiveConfig config = new HiveConfig(); HiveTransactionHandle transaction = new HiveTransactionHandle(false); - try (TempFile tempFile = new TempFile()) { - try (ConnectorPageSource emptyPageSource = createTestingPageSource(transaction, config, tempFile.file(), getDynamicFilter(getTupleDomainForBucketSplitPruning()))) { - assertEquals(emptyPageSource.getClass(), EmptyPageSource.class); - } + try (ConnectorPageSource emptyPageSource = createTestingPageSource(transaction, config, getDynamicFilter(getTupleDomainForBucketSplitPruning()))) { + assertEquals(emptyPageSource.getClass(), EmptyPageSource.class); + } - try (ConnectorPageSource nonEmptyPageSource = createTestingPageSource(transaction, config, tempFile.file(), getDynamicFilter(getNonSelectiveBucketTupleDomain()))) { - assertEquals(nonEmptyPageSource.getClass(), HivePageSource.class); - } + try (ConnectorPageSource nonEmptyPageSource = createTestingPageSource(transaction, config, getDynamicFilter(getNonSelectiveBucketTupleDomain()))) { + assertEquals(nonEmptyPageSource.getClass(), HivePageSource.class); } } @@ -102,29 +101,33 @@ public void testDynamicPartitionPruning() { HiveConfig config = new HiveConfig(); HiveTransactionHandle transaction = new HiveTransactionHandle(false); - try (TempFile tempFile = new TempFile()) { - try (ConnectorPageSource emptyPageSource = createTestingPageSource(transaction, config, tempFile.file(), getDynamicFilter(getTupleDomainForPartitionSplitPruning()))) { - assertEquals(emptyPageSource.getClass(), EmptyPageSource.class); - } - try (ConnectorPageSource nonEmptyPageSource = createTestingPageSource(transaction, config, tempFile.file(), getDynamicFilter(getNonSelectivePartitionTupleDomain()))) { - assertEquals(nonEmptyPageSource.getClass(), HivePageSource.class); - } + try (ConnectorPageSource emptyPageSource = createTestingPageSource(transaction, config, getDynamicFilter(getTupleDomainForPartitionSplitPruning()))) { + assertEquals(emptyPageSource.getClass(), EmptyPageSource.class); + } + + try (ConnectorPageSource nonEmptyPageSource = createTestingPageSource(transaction, config, getDynamicFilter(getNonSelectivePartitionTupleDomain()))) { + assertEquals(nonEmptyPageSource.getClass(), HivePageSource.class); } } - private static ConnectorPageSource createTestingPageSource(HiveTransactionHandle transaction, HiveConfig hiveConfig, File outputFile, DynamicFilter dynamicFilter) + private static ConnectorPageSource createTestingPageSource(HiveTransactionHandle transaction, HiveConfig hiveConfig, DynamicFilter dynamicFilter) + throws IOException { + Location location = Location.of("memory:///file"); + TrinoFileSystemFactory fileSystemFactory = new MemoryFileSystemFactory(); + fileSystemFactory.create(ConnectorIdentity.ofUser("test")).newOutputFile(location).create().close(); + Properties splitProperties = new Properties(); splitProperties.setProperty(FILE_INPUT_FORMAT, hiveConfig.getHiveStorageFormat().getInputFormat()); splitProperties.setProperty(SERIALIZATION_LIB, hiveConfig.getHiveStorageFormat().getSerde()); HiveSplit split = new HiveSplit( "", - "file:///" + outputFile.getAbsolutePath(), + location.toString(), + 0, + 0, + 0, 0, - outputFile.length(), - outputFile.length(), - outputFile.lastModified(), splitProperties, ImmutableList.of(new HivePartitionKey(PARTITION_COLUMN.getName(), "42")), ImmutableList.of(), @@ -156,7 +159,7 @@ private static ConnectorPageSource createTestingPageSource(HiveTransactionHandle HivePageSourceProvider provider = new HivePageSourceProvider( TESTING_TYPE_MANAGER, hiveConfig, - getDefaultHivePageSourceFactories(HDFS_ENVIRONMENT, hiveConfig)); + getDefaultHivePageSourceFactories(fileSystemFactory, hiveConfig)); return provider.createPageSource( transaction, From c9f3260ba70a6e060db5d32f01ad035f421e02ae Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Mon, 6 Nov 2023 22:07:00 -0800 Subject: [PATCH 209/587] Cleanup TestNodeLocalDynamicSplitPruning --- .../hive/TestNodeLocalDynamicSplitPruning.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestNodeLocalDynamicSplitPruning.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestNodeLocalDynamicSplitPruning.java index fd61d85be3dc..1122c6ccbff2 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestNodeLocalDynamicSplitPruning.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestNodeLocalDynamicSplitPruning.java @@ -57,17 +57,17 @@ import static org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.FILE_INPUT_FORMAT; import static org.testng.Assert.assertEquals; -public class TestNodeLocalDynamicSplitPruning +class TestNodeLocalDynamicSplitPruning { private static final String SCHEMA_NAME = "test"; private static final String TABLE_NAME = "test"; - private static final Column BUCKET_COLUMN = new Column("l_orderkey", HIVE_INT, Optional.empty()); - private static final Column PARTITION_COLUMN = new Column("l_partkey", HIVE_INT, Optional.empty()); + private static final Column BUCKET_COLUMN = new Column("l_orderkey", HIVE_INT, Optional.empty(), ImmutableMap.of()); + private static final Column PARTITION_COLUMN = new Column("l_partkey", HIVE_INT, Optional.empty(), ImmutableMap.of()); private static final HiveColumnHandle BUCKET_HIVE_COLUMN_HANDLE = new HiveColumnHandle( BUCKET_COLUMN.getName(), 0, BUCKET_COLUMN.getType(), - TESTING_TYPE_MANAGER.getType(BUCKET_COLUMN.getType().getTypeSignature()), + INTEGER, Optional.empty(), REGULAR, Optional.empty()); @@ -75,13 +75,13 @@ public class TestNodeLocalDynamicSplitPruning PARTITION_COLUMN.getName(), 0, PARTITION_COLUMN.getType(), - TESTING_TYPE_MANAGER.getType(PARTITION_COLUMN.getType().getTypeSignature()), + INTEGER, Optional.empty(), PARTITION_KEY, Optional.empty()); @Test - public void testDynamicBucketPruning() + void testDynamicBucketPruning() throws IOException { HiveConfig config = new HiveConfig(); @@ -96,7 +96,7 @@ public void testDynamicBucketPruning() } @Test - public void testDynamicPartitionPruning() + void testDynamicPartitionPruning() throws IOException { HiveConfig config = new HiveConfig(); From 82c9224196bb4bd7e9dd0836850f22c8babd5f53 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Mon, 6 Nov 2023 21:57:46 -0800 Subject: [PATCH 210/587] Convert TestHivePageSink to memory file system --- .../trino/plugin/hive/TestHivePageSink.java | 140 +++++++++--------- .../file/TestingFileHiveMetastore.java | 11 +- 2 files changed, 76 insertions(+), 75 deletions(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java index d48733a3237f..4c3db8347963 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java @@ -18,7 +18,11 @@ import com.google.common.collect.ImmutableMap; import io.airlift.json.JsonCodec; import io.airlift.slice.Slices; +import io.trino.filesystem.FileEntry; +import io.trino.filesystem.FileIterator; import io.trino.filesystem.Location; +import io.trino.filesystem.TrinoFileSystemFactory; +import io.trino.filesystem.memory.MemoryFileSystemFactory; import io.trino.operator.GroupByHashPageIndexerFactory; import io.trino.plugin.hive.metastore.HiveMetastore; import io.trino.plugin.hive.metastore.HiveMetastoreFactory; @@ -33,6 +37,7 @@ import io.trino.spi.connector.ConnectorTableHandle; import io.trino.spi.connector.DynamicFilter; import io.trino.spi.connector.SchemaTableName; +import io.trino.spi.security.ConnectorIdentity; import io.trino.spi.type.Type; import io.trino.spi.type.TypeOperators; import io.trino.sql.gen.JoinCompiler; @@ -45,8 +50,7 @@ import io.trino.tpch.TpchColumnTypes; import org.junit.jupiter.api.Test; -import java.io.File; -import java.nio.file.Files; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -55,27 +59,19 @@ import java.util.stream.Stream; import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.collect.Iterables.getOnlyElement; -import static com.google.common.io.MoreFiles.deleteRecursively; -import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; import static io.airlift.concurrent.MoreFutures.getFutureValue; import static io.airlift.testing.Assertions.assertGreaterThan; +import static io.trino.hive.thrift.metastore.hive_metastoreConstants.FILE_INPUT_FORMAT; import static io.trino.plugin.hive.HiveColumnHandle.ColumnType.REGULAR; import static io.trino.plugin.hive.HiveColumnHandle.createBaseColumn; import static io.trino.plugin.hive.HiveCompressionOption.LZ4; import static io.trino.plugin.hive.HiveCompressionOption.NONE; -import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; import static io.trino.plugin.hive.HiveTestUtils.PAGE_SORTER; import static io.trino.plugin.hive.HiveTestUtils.getDefaultHiveFileWriterFactories; import static io.trino.plugin.hive.HiveTestUtils.getDefaultHivePageSourceFactories; import static io.trino.plugin.hive.HiveTestUtils.getHiveSession; import static io.trino.plugin.hive.HiveTestUtils.getHiveSessionProperties; -import static io.trino.plugin.hive.HiveType.HIVE_DATE; -import static io.trino.plugin.hive.HiveType.HIVE_DOUBLE; -import static io.trino.plugin.hive.HiveType.HIVE_INT; -import static io.trino.plugin.hive.HiveType.HIVE_LONG; -import static io.trino.plugin.hive.HiveType.HIVE_STRING; import static io.trino.plugin.hive.LocationHandle.WriteMode.DIRECT_TO_TARGET_NEW_DIRECTORY; import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; import static io.trino.plugin.hive.metastore.file.TestingFileHiveMetastore.createTestingFileHiveMetastore; @@ -84,13 +80,13 @@ import static io.trino.spi.type.DateType.DATE; import static io.trino.spi.type.DoubleType.DOUBLE; import static io.trino.spi.type.IntegerType.INTEGER; +import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; import static io.trino.testing.TestingPageSinkId.TESTING_PAGE_SINK_ID; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.lang.Math.round; import static java.lang.String.format; import static java.util.stream.Collectors.toList; -import static org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.FILE_INPUT_FORMAT; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.testng.Assert.assertEquals; @@ -108,47 +104,43 @@ public void testAllFormats() { HiveConfig config = new HiveConfig(); SortingFileWriterConfig sortingFileWriterConfig = new SortingFileWriterConfig(); - File tempDir = Files.createTempDirectory(null).toFile(); - try { - HiveMetastore metastore = createTestingFileHiveMetastore(new File(tempDir, "metastore")); - for (HiveStorageFormat format : HiveStorageFormat.values()) { - if (format == HiveStorageFormat.CSV) { - // CSV supports only unbounded VARCHAR type, which is not provided by lineitem + + TrinoFileSystemFactory fileSystemFactory = new MemoryFileSystemFactory(); + HiveMetastore metastore = createTestingFileHiveMetastore(fileSystemFactory, Location.of("memory:///metastore")); + for (HiveStorageFormat format : HiveStorageFormat.values()) { + if (format == HiveStorageFormat.CSV) { + // CSV supports only unbounded VARCHAR type, which is not provided by lineitem + continue; + } + if (format == HiveStorageFormat.REGEX) { + // REGEX format is readonly + continue; + } + config.setHiveStorageFormat(format); + config.setHiveCompressionCodec(NONE); + long uncompressedLength = writeTestFile(fileSystemFactory, config, sortingFileWriterConfig, metastore, makeFileName(config)); + assertGreaterThan(uncompressedLength, 0L); + + for (HiveCompressionOption codec : HiveCompressionOption.values()) { + if (codec == NONE) { continue; } - if (format == HiveStorageFormat.REGEX) { - // REGEX format is readonly + if ((format == HiveStorageFormat.PARQUET) && (codec == LZ4)) { + // TODO (https://github.com/trinodb/trino/issues/9142) LZ4 is not supported with native Parquet writer continue; } - config.setHiveStorageFormat(format); - config.setHiveCompressionCodec(NONE); - long uncompressedLength = writeTestFile(config, sortingFileWriterConfig, metastore, makeFileName(tempDir, config)); - assertGreaterThan(uncompressedLength, 0L); - - for (HiveCompressionOption codec : HiveCompressionOption.values()) { - if (codec == NONE) { - continue; - } - if ((format == HiveStorageFormat.PARQUET) && (codec == LZ4)) { - // TODO (https://github.com/trinodb/trino/issues/9142) LZ4 is not supported with native Parquet writer - continue; - } - config.setHiveCompressionCodec(codec); + config.setHiveCompressionCodec(codec); - if (!isSupportedCodec(format, codec)) { - assertThatThrownBy(() -> writeTestFile(config, sortingFileWriterConfig, metastore, makeFileName(tempDir, config))) - .hasMessage("Compression codec " + codec + " not supported for " + format); - continue; - } - - long length = writeTestFile(config, sortingFileWriterConfig, metastore, makeFileName(tempDir, config)); - assertTrue(uncompressedLength > length, format("%s with %s compressed to %s which is not less than %s", format, codec, length, uncompressedLength)); + if (!isSupportedCodec(format, codec)) { + assertThatThrownBy(() -> writeTestFile(fileSystemFactory, config, sortingFileWriterConfig, metastore, makeFileName(config))) + .hasMessage("Compression codec " + codec + " not supported for " + format); + continue; } + + long length = writeTestFile(fileSystemFactory, config, sortingFileWriterConfig, metastore, makeFileName(config)); + assertTrue(uncompressedLength > length, format("%s with %s compressed to %s which is not less than %s", format, codec, length, uncompressedLength)); } } - finally { - deleteRecursively(tempDir.toPath(), ALLOW_INSECURE); - } } private boolean isSupportedCodec(HiveStorageFormat storageFormat, HiveCompressionOption compressionOption) @@ -159,20 +151,21 @@ private boolean isSupportedCodec(HiveStorageFormat storageFormat, HiveCompressio return true; } - private static String makeFileName(File tempDir, HiveConfig config) + private static Location makeFileName(HiveConfig config) { - return tempDir.getAbsolutePath() + "/" + config.getHiveStorageFormat().name() + "." + config.getHiveCompressionCodec().name(); + return Location.of("memory:///" + config.getHiveStorageFormat().name() + "." + config.getHiveCompressionCodec().name()); } - private static long writeTestFile(HiveConfig config, SortingFileWriterConfig sortingFileWriterConfig, HiveMetastore metastore, String outputPath) + private static long writeTestFile(TrinoFileSystemFactory fileSystemFactory, HiveConfig config, SortingFileWriterConfig sortingFileWriterConfig, HiveMetastore metastore, Location location) + throws IOException { HiveTransactionHandle transaction = new HiveTransactionHandle(false); HiveWriterStats stats = new HiveWriterStats(); - ConnectorPageSink pageSink = createPageSink(transaction, config, sortingFileWriterConfig, metastore, Location.of("file:///" + outputPath), stats); + ConnectorPageSink pageSink = createPageSink(fileSystemFactory, transaction, config, sortingFileWriterConfig, metastore, location, stats); List columns = getTestColumns(); List columnTypes = columns.stream() .map(LineItemColumn::getType) - .map(TestHivePageSink::getHiveType) + .map(TestHivePageSink::getType) .map(hiveType -> TESTING_TYPE_MANAGER.getType(hiveType.getTypeSignature())) .collect(toList()); @@ -212,12 +205,11 @@ private static long writeTestFile(HiveConfig config, SortingFileWriterConfig sor pageSink.appendPage(page); getFutureValue(pageSink.finish()); - File outputDir = new File(outputPath); - List files = ImmutableList.copyOf(outputDir.listFiles((dir, name) -> !name.endsWith(".crc"))); - File outputFile = getOnlyElement(files); - long length = outputFile.length(); + FileIterator fileIterator = fileSystemFactory.create(ConnectorIdentity.ofUser("test")).listFiles(location); + FileEntry fileEntry = fileIterator.next(); + assertThat(fileIterator.hasNext()).isFalse(); - ConnectorPageSource pageSource = createPageSource(transaction, config, outputFile); + ConnectorPageSource pageSource = createPageSource(fileSystemFactory, transaction, config, fileEntry.location()); List pages = new ArrayList<>(); while (!pageSource.isFinished()) { @@ -230,7 +222,7 @@ private static long writeTestFile(HiveConfig config, SortingFileWriterConfig sor MaterializedResult results = toMaterializedResult(getHiveSession(config), columnTypes, pages); assertThat(results).containsExactlyElementsOf(expectedResults); assertEquals(round(stats.getInputPageSizeInBytes().getAllTime().getMax()), page.getRetainedSizeInBytes()); - return length; + return fileEntry.length(); } public static MaterializedResult toMaterializedResult(ConnectorSession session, List types, List pages) @@ -243,8 +235,10 @@ public static MaterializedResult toMaterializedResult(ConnectorSession session, return resultBuilder.build(); } - private static ConnectorPageSource createPageSource(HiveTransactionHandle transaction, HiveConfig config, File outputFile) + private static ConnectorPageSource createPageSource(TrinoFileSystemFactory fileSystemFactory, HiveTransactionHandle transaction, HiveConfig config, Location location) + throws IOException { + long length = fileSystemFactory.create(ConnectorIdentity.ofUser("test")).newInputFile(location).length(); Properties splitProperties = new Properties(); splitProperties.setProperty(FILE_INPUT_FORMAT, config.getHiveStorageFormat().getInputFormat()); splitProperties.setProperty(SERIALIZATION_LIB, config.getHiveStorageFormat().getSerde()); @@ -252,11 +246,11 @@ private static ConnectorPageSource createPageSource(HiveTransactionHandle transa splitProperties.setProperty("columns.types", Joiner.on(',').join(getColumnHandles().stream().map(HiveColumnHandle::getHiveType).map(hiveType -> hiveType.getHiveTypeName().toString()).collect(toImmutableList()))); HiveSplit split = new HiveSplit( "", - "file:///" + outputFile.getAbsolutePath(), + location.toString(), + 0, + length, + length, 0, - outputFile.length(), - outputFile.length(), - outputFile.lastModified(), splitProperties, ImmutableList.of(), ImmutableList.of(), @@ -272,13 +266,13 @@ private static ConnectorPageSource createPageSource(HiveTransactionHandle transa HivePageSourceProvider provider = new HivePageSourceProvider( TESTING_TYPE_MANAGER, config, - getDefaultHivePageSourceFactories(HDFS_ENVIRONMENT, config)); + getDefaultHivePageSourceFactories(fileSystemFactory, config)); return provider.createPageSource(transaction, getHiveSession(config), split, table, ImmutableList.copyOf(getColumnHandles()), DynamicFilter.EMPTY); } - private static ConnectorPageSink createPageSink(HiveTransactionHandle transaction, HiveConfig config, SortingFileWriterConfig sortingFileWriterConfig, HiveMetastore metastore, Location outputPath, HiveWriterStats stats) + private static ConnectorPageSink createPageSink(TrinoFileSystemFactory fileSystemFactory, HiveTransactionHandle transaction, HiveConfig config, SortingFileWriterConfig sortingFileWriterConfig, HiveMetastore metastore, Location location, HiveWriterStats stats) { - LocationHandle locationHandle = new LocationHandle(outputPath, outputPath, DIRECT_TO_TARGET_NEW_DIRECTORY); + LocationHandle locationHandle = new LocationHandle(location, location, DIRECT_TO_TARGET_NEW_DIRECTORY); HiveOutputTableHandle handle = new HiveOutputTableHandle( SCHEMA_NAME, TABLE_NAME, @@ -296,7 +290,7 @@ private static ConnectorPageSink createPageSink(HiveTransactionHandle transactio false); JsonCodec partitionUpdateCodec = JsonCodec.jsonCodec(PartitionUpdate.class); HivePageSinkProvider provider = new HivePageSinkProvider( - getDefaultHiveFileWriterFactories(config, HDFS_ENVIRONMENT), + getDefaultHiveFileWriterFactories(config, fileSystemFactory), HDFS_FILE_SYSTEM_FACTORY, PAGE_SORTER, HiveMetastoreFactory.ofInstance(metastore), @@ -319,8 +313,8 @@ private static List getColumnHandles() List columns = getTestColumns(); for (int i = 0; i < columns.size(); i++) { LineItemColumn column = columns.get(i); - HiveType hiveType = getHiveType(column.getType()); - handles.add(createBaseColumn(column.getColumnName(), i, hiveType, TESTING_TYPE_MANAGER.getType(hiveType.getTypeSignature()), REGULAR, Optional.empty())); + Type type = getType(column.getType()); + handles.add(createBaseColumn(column.getColumnName(), i, HiveType.toHiveType(type), type, REGULAR, Optional.empty())); } return handles.build(); } @@ -333,19 +327,19 @@ private static List getTestColumns() .collect(toList()); } - private static HiveType getHiveType(TpchColumnType type) + private static Type getType(TpchColumnType type) { switch (type.getBase()) { case IDENTIFIER: - return HIVE_LONG; + return BIGINT; case INTEGER: - return HIVE_INT; + return INTEGER; case DATE: - return HIVE_DATE; + return DATE; case DOUBLE: - return HIVE_DOUBLE; + return DOUBLE; case VARCHAR: - return HIVE_STRING; + return VARCHAR; } throw new UnsupportedOperationException(); } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/file/TestingFileHiveMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/file/TestingFileHiveMetastore.java index 140e1adf9c1e..e97982ff7484 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/file/TestingFileHiveMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/file/TestingFileHiveMetastore.java @@ -13,6 +13,8 @@ */ package io.trino.plugin.hive.metastore.file; +import io.trino.filesystem.Location; +import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.plugin.hive.NodeVersion; import io.trino.plugin.hive.metastore.HiveMetastoreConfig; @@ -25,13 +27,18 @@ public final class TestingFileHiveMetastore private TestingFileHiveMetastore() {} public static FileHiveMetastore createTestingFileHiveMetastore(File catalogDirectory) + { + return createTestingFileHiveMetastore(HDFS_FILE_SYSTEM_FACTORY, Location.of(catalogDirectory.toURI().toString())); + } + + public static FileHiveMetastore createTestingFileHiveMetastore(TrinoFileSystemFactory fileSystemFactory, Location catalogDirectory) { return new FileHiveMetastore( new NodeVersion("testversion"), - HDFS_FILE_SYSTEM_FACTORY, + fileSystemFactory, new HiveMetastoreConfig().isHideDeltaLakeTables(), new FileHiveMetastoreConfig() - .setCatalogDirectory(catalogDirectory.toURI().toString()) + .setCatalogDirectory(catalogDirectory.toString()) .setMetastoreUser("test")); } } From 9b936ef12779d761f3c44de64fa146078f6857ec Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Mon, 6 Nov 2023 22:10:17 -0800 Subject: [PATCH 211/587] Cleanup TestHivePageSink --- .../trino/plugin/hive/TestHivePageSink.java | 41 ++++++++----------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java index 4c3db8347963..2c4d60a3da93 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java @@ -99,7 +99,7 @@ public class TestHivePageSink private static final String TABLE_NAME = "test"; @Test - public void testAllFormats() + void testAllFormats() throws Exception { HiveConfig config = new HiveConfig(); @@ -109,7 +109,7 @@ public void testAllFormats() HiveMetastore metastore = createTestingFileHiveMetastore(fileSystemFactory, Location.of("memory:///metastore")); for (HiveStorageFormat format : HiveStorageFormat.values()) { if (format == HiveStorageFormat.CSV) { - // CSV supports only unbounded VARCHAR type, which is not provided by lineitem + // CSV supports only the unbounded VARCHAR type, which is not provided by lineitem continue; } if (format == HiveStorageFormat.REGEX) { @@ -143,7 +143,7 @@ public void testAllFormats() } } - private boolean isSupportedCodec(HiveStorageFormat storageFormat, HiveCompressionOption compressionOption) + private static boolean isSupportedCodec(HiveStorageFormat storageFormat, HiveCompressionOption compressionOption) { if (storageFormat == HiveStorageFormat.AVRO && compressionOption == LZ4) { return false; @@ -209,15 +209,16 @@ private static long writeTestFile(TrinoFileSystemFactory fileSystemFactory, Hive FileEntry fileEntry = fileIterator.next(); assertThat(fileIterator.hasNext()).isFalse(); - ConnectorPageSource pageSource = createPageSource(fileSystemFactory, transaction, config, fileEntry.location()); - List pages = new ArrayList<>(); - while (!pageSource.isFinished()) { - Page nextPage = pageSource.getNextPage(); - if (nextPage != null) { - pages.add(nextPage.getLoadedPage()); + try (ConnectorPageSource pageSource = createPageSource(fileSystemFactory, transaction, config, fileEntry.location())) { + while (!pageSource.isFinished()) { + Page nextPage = pageSource.getNextPage(); + if (nextPage != null) { + pages.add(nextPage.getLoadedPage()); + } } } + MaterializedResult expectedResults = toMaterializedResult(getHiveSession(config), columnTypes, ImmutableList.of(page)); MaterializedResult results = toMaterializedResult(getHiveSession(config), columnTypes, pages); assertThat(results).containsExactlyElementsOf(expectedResults); @@ -225,7 +226,7 @@ private static long writeTestFile(TrinoFileSystemFactory fileSystemFactory, Hive return fileEntry.length(); } - public static MaterializedResult toMaterializedResult(ConnectorSession session, List types, List pages) + static MaterializedResult toMaterializedResult(ConnectorSession session, List types, List pages) { // materialize pages MaterializedResult.Builder resultBuilder = MaterializedResult.resultBuilder(session, types); @@ -329,18 +330,12 @@ private static List getTestColumns() private static Type getType(TpchColumnType type) { - switch (type.getBase()) { - case IDENTIFIER: - return BIGINT; - case INTEGER: - return INTEGER; - case DATE: - return DATE; - case DOUBLE: - return DOUBLE; - case VARCHAR: - return VARCHAR; - } - throw new UnsupportedOperationException(); + return switch (type.getBase()) { + case IDENTIFIER -> BIGINT; + case INTEGER -> INTEGER; + case DATE -> DATE; + case DOUBLE -> DOUBLE; + case VARCHAR -> VARCHAR; + }; } } From 6d1ed932e196a3a6960c657c0da0a469941171f4 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Fri, 10 Nov 2023 13:36:55 -0800 Subject: [PATCH 212/587] Migrate tests to JUnit --- .../jdbc/BaseJdbcFailureRecoveryTest.java | 22 +++++-- .../BaseBigQueryFailureRecoveryTest.java | 17 ++--- .../BaseDeltaFailureRecoveryTest.java | 18 ++---- .../hive/BaseHiveFailureRecoveryTest.java | 29 ++++----- .../TestHiveQueryFailureRecoveryTest.java | 10 ++- .../hive/TestHiveTaskFailureRecoveryTest.java | 10 ++- .../BaseIcebergFailureRecoveryTest.java | 19 ++---- .../TestIcebergQueryFailureRecoveryTest.java | 10 ++- .../TestIcebergTaskFailureRecoveryTest.java | 10 ++- .../mongodb/BaseMongoFailureRecoveryTest.java | 24 ++++--- .../mysql/BaseMySqlFailureRecoveryTest.java | 7 +- .../oracle/BaseOracleFailureRecoveryTest.java | 17 ++--- .../BasePostgresFailureRecoveryTest.java | 7 +- .../BaseRedshiftFailureRecoveryTest.java | 4 +- ...RedshiftQueryFailureRecoverySmokeTest.java | 15 +---- .../BaseSqlServerFailureRecoveryTest.java | 7 +- .../testing/BaseFailureRecoveryTest.java | 64 ++++--------------- .../testing/ExtendedFailureRecoveryTest.java | 30 ++++----- 18 files changed, 152 insertions(+), 168 deletions(-) diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/BaseJdbcFailureRecoveryTest.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/BaseJdbcFailureRecoveryTest.java index 1eb4044d2a01..43221d0512d7 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/BaseJdbcFailureRecoveryTest.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/BaseJdbcFailureRecoveryTest.java @@ -15,12 +15,13 @@ import io.trino.operator.RetryPolicy; import io.trino.testing.BaseFailureRecoveryTest; -import org.testng.SkipException; +import org.junit.jupiter.api.Test; import java.util.Optional; import static io.trino.spi.connector.ConnectorMetadata.MODIFYING_ROWS_MESSAGE; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.abort; public abstract class BaseJdbcFailureRecoveryTest extends BaseFailureRecoveryTest @@ -30,13 +31,15 @@ public BaseJdbcFailureRecoveryTest(RetryPolicy retryPolicy) super(retryPolicy); } + @Test @Override protected void testAnalyzeTable() { assertThatThrownBy(super::testAnalyzeTable).hasMessageMatching("This connector does not support analyze"); - throw new SkipException("skipped"); + abort("skipped"); } + @Test @Override protected void testDelete() { @@ -52,40 +55,45 @@ protected void testDelete() .isCoordinatorOnly(); } + @Test @Override protected void testDeleteWithSubquery() { assertThatThrownBy(super::testDeleteWithSubquery).hasMessageContaining(MODIFYING_ROWS_MESSAGE); - throw new SkipException("skipped"); + abort("skipped"); } + @Test @Override protected void testRefreshMaterializedView() { assertThatThrownBy(super::testRefreshMaterializedView) .hasMessageContaining("This connector does not support creating materialized views"); - throw new SkipException("skipped"); + abort("skipped"); } + @Test @Override protected void testUpdate() { assertThatThrownBy(super::testUpdate).hasMessageContaining(MODIFYING_ROWS_MESSAGE); - throw new SkipException("skipped"); + abort("skipped"); } + @Test @Override protected void testUpdateWithSubquery() { assertThatThrownBy(super::testUpdateWithSubquery).hasMessageContaining(MODIFYING_ROWS_MESSAGE); - throw new SkipException("skipped"); + abort("skipped"); } + @Test @Override protected void testMerge() { assertThatThrownBy(super::testMerge).hasMessageContaining(MODIFYING_ROWS_MESSAGE); - throw new SkipException("skipped"); + abort("skipped"); } @Override diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryFailureRecoveryTest.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryFailureRecoveryTest.java index c3cb503a014b..6fc21af7f025 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryFailureRecoveryTest.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryFailureRecoveryTest.java @@ -19,11 +19,12 @@ import io.trino.testing.BaseFailureRecoveryTest; import io.trino.testing.QueryRunner; import io.trino.tpch.TpchTable; -import org.testng.SkipException; import java.util.List; import java.util.Map; +import static org.junit.jupiter.api.Assumptions.abort; + public abstract class BaseBigQueryFailureRecoveryTest extends BaseFailureRecoveryTest { @@ -62,48 +63,48 @@ protected boolean areWriteRetriesSupported() protected void testAnalyzeTable() { // This connector does not support analyze - throw new SkipException("skipped"); + abort("skipped"); } @Override protected void testDelete() { // This connector does not support modifying table rows - throw new SkipException("skipped"); + abort("skipped"); } @Override protected void testDeleteWithSubquery() { // This connector does not support modifying table rows - throw new SkipException("skipped"); + abort("skipped"); } @Override protected void testMerge() { // This connector does not support modifying table rows - throw new SkipException("skipped"); + abort("skipped"); } @Override protected void testRefreshMaterializedView() { // This connector does not support creating materialized views - throw new SkipException("skipped"); + abort("skipped"); } @Override protected void testUpdate() { // This connector does not support modifying table rows - throw new SkipException("skipped"); + abort("skipped"); } @Override protected void testUpdateWithSubquery() { // This connector does not support modifying table rows - throw new SkipException("skipped"); + abort("skipped"); } } diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaFailureRecoveryTest.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaFailureRecoveryTest.java index ded37cebc24b..73c977c21545 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaFailureRecoveryTest.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaFailureRecoveryTest.java @@ -23,7 +23,7 @@ import io.trino.testing.DistributedQueryRunner; import io.trino.testing.QueryRunner; import io.trino.tpch.TpchTable; -import org.testng.annotations.DataProvider; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.Map; @@ -92,16 +92,7 @@ protected boolean areWriteRetriesSupported() return true; } - @Override - @DataProvider(name = "parallelTests", parallel = true) - public Object[][] parallelTests() - { - return moreParallelTests(super.parallelTests(), - parallelTest("testCreatePartitionedTable", this::testCreatePartitionedTable), - parallelTest("testInsertIntoNewPartition", this::testInsertIntoNewPartition), - parallelTest("testInsertIntoExistingPartition", this::testInsertIntoExistingPartition)); - } - + @Test @Override protected void testDelete() { @@ -186,6 +177,7 @@ protected void testDelete() } } + @Test @Override protected void testUpdate() { @@ -269,6 +261,7 @@ protected void testUpdate() } } + @Test @Override // materialized views are currently not implemented by Delta connector protected void testRefreshMaterializedView() @@ -277,6 +270,7 @@ protected void testRefreshMaterializedView() .hasMessageContaining("This connector does not support creating materialized views"); } + @Test protected void testCreatePartitionedTable() { testTableModification( @@ -285,6 +279,7 @@ protected void testCreatePartitionedTable() Optional.of("DROP TABLE
")); } + @Test protected void testInsertIntoNewPartition() { testTableModification( @@ -293,6 +288,7 @@ protected void testInsertIntoNewPartition() Optional.of("DROP TABLE
")); } + @Test protected void testInsertIntoExistingPartition() { testTableModification( diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveFailureRecoveryTest.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveFailureRecoveryTest.java index 650ce03dcea7..3505decc8c0e 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveFailureRecoveryTest.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveFailureRecoveryTest.java @@ -16,7 +16,7 @@ import io.trino.Session; import io.trino.operator.RetryPolicy; import io.trino.testing.ExtendedFailureRecoveryTest; -import org.testng.annotations.DataProvider; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.Optional; @@ -50,20 +50,7 @@ protected void createPartitionedLineitemTable(String tableName, List col getQueryRunner().execute(sql); } - @Override - @DataProvider(name = "parallelTests", parallel = true) - public Object[][] parallelTests() - { - return moreParallelTests(super.parallelTests(), - parallelTest("testCreatePartitionedTable", this::testCreatePartitionedTable), - parallelTest("testInsertIntoNewPartition", this::testInsertIntoNewPartition), - parallelTest("testInsertIntoExistingPartition", this::testInsertIntoExistingPartition), - parallelTest("testInsertIntoNewPartitionBucketed", this::testInsertIntoNewPartitionBucketed), - parallelTest("testInsertIntoExistingPartitionBucketed", this::testInsertIntoExistingPartitionBucketed), - parallelTest("testReplaceExistingPartition", this::testReplaceExistingPartition), - parallelTest("testDeletePartitionWithSubquery", this::testDeletePartitionWithSubquery)); - } - + @Test @Override // delete is unsupported for non ACID tables protected void testDelete() @@ -72,6 +59,7 @@ protected void testDelete() .hasMessageContaining(MODIFYING_NON_TRANSACTIONAL_TABLE_MESSAGE); } + @Test @Override // delete is unsupported for non ACID tables protected void testDeleteWithSubquery() @@ -80,6 +68,7 @@ protected void testDeleteWithSubquery() .hasMessageContaining(MODIFYING_NON_TRANSACTIONAL_TABLE_MESSAGE); } + @Test @Override // update is unsupported for non ACID tables protected void testUpdate() @@ -88,6 +77,7 @@ protected void testUpdate() .hasMessageContaining(MODIFYING_NON_TRANSACTIONAL_TABLE_MESSAGE); } + @Test @Override // update is unsupported for non ACID tables protected void testUpdateWithSubquery() @@ -96,6 +86,7 @@ protected void testUpdateWithSubquery() .hasMessageContaining(MODIFYING_NON_TRANSACTIONAL_TABLE_MESSAGE); } + @Test @Override protected void testMerge() { @@ -103,6 +94,7 @@ protected void testMerge() .hasMessageContaining("Modifying Hive table rows is only supported for transactional tables"); } + @Test @Override // materialized views are currently not implemented by Hive connector protected void testRefreshMaterializedView() @@ -111,6 +103,7 @@ protected void testRefreshMaterializedView() .hasMessageContaining("This connector does not support creating materialized views"); } + @Test protected void testCreatePartitionedTable() { testTableModification( @@ -119,6 +112,7 @@ protected void testCreatePartitionedTable() Optional.of("DROP TABLE
")); } + @Test protected void testInsertIntoNewPartition() { testTableModification( @@ -127,6 +121,7 @@ protected void testInsertIntoNewPartition() Optional.of("DROP TABLE
")); } + @Test protected void testInsertIntoExistingPartition() { testTableModification( @@ -135,6 +130,7 @@ protected void testInsertIntoExistingPartition() Optional.of("DROP TABLE
")); } + @Test protected void testInsertIntoNewPartitionBucketed() { testTableModification( @@ -143,6 +139,7 @@ protected void testInsertIntoNewPartitionBucketed() Optional.of("DROP TABLE
")); } + @Test protected void testInsertIntoExistingPartitionBucketed() { testTableModification( @@ -151,6 +148,7 @@ protected void testInsertIntoExistingPartitionBucketed() Optional.of("DROP TABLE
")); } + @Test protected void testReplaceExistingPartition() { testTableModification( @@ -162,6 +160,7 @@ protected void testReplaceExistingPartition() Optional.of("DROP TABLE
")); } + @Test protected void testDeletePartitionWithSubquery() { assertThatThrownBy(() -> { diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveQueryFailureRecoveryTest.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveQueryFailureRecoveryTest.java index af44e0512372..72b4554c7ca0 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveQueryFailureRecoveryTest.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveQueryFailureRecoveryTest.java @@ -20,14 +20,20 @@ import io.trino.plugin.hive.s3.S3HiveQueryRunner; import io.trino.testing.QueryRunner; import io.trino.tpch.TpchTable; -import org.testng.annotations.AfterClass; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.Map; import static io.trino.plugin.exchange.filesystem.containers.MinioStorage.getExchangeManagerProperties; import static io.trino.testing.TestingNames.randomNameSuffix; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestHiveQueryFailureRecoveryTest extends BaseHiveFailureRecoveryTest { @@ -64,7 +70,7 @@ protected QueryRunner createQueryRunner( .build(); } - @AfterClass(alwaysRun = true) + @AfterAll public void destroy() throws Exception { diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveTaskFailureRecoveryTest.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveTaskFailureRecoveryTest.java index 841117e8a81a..4e270b3e21a0 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveTaskFailureRecoveryTest.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveTaskFailureRecoveryTest.java @@ -20,14 +20,20 @@ import io.trino.plugin.hive.s3.S3HiveQueryRunner; import io.trino.testing.QueryRunner; import io.trino.tpch.TpchTable; -import org.testng.annotations.AfterClass; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.Map; import static io.trino.plugin.exchange.filesystem.containers.MinioStorage.getExchangeManagerProperties; import static io.trino.testing.TestingNames.randomNameSuffix; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestHiveTaskFailureRecoveryTest extends BaseHiveFailureRecoveryTest { @@ -64,7 +70,7 @@ protected QueryRunner createQueryRunner( .build(); } - @AfterClass(alwaysRun = true) + @AfterAll public void destroy() throws Exception { diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergFailureRecoveryTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergFailureRecoveryTest.java index 4d5e9d6bf89d..c9f8d30f6212 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergFailureRecoveryTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergFailureRecoveryTest.java @@ -16,7 +16,7 @@ import io.trino.operator.RetryPolicy; import io.trino.spi.ErrorType; import io.trino.testing.BaseFailureRecoveryTest; -import org.testng.annotations.DataProvider; +import org.junit.jupiter.api.Test; import java.util.Optional; @@ -41,17 +41,7 @@ protected boolean areWriteRetriesSupported() return true; } - @Override - @DataProvider(name = "parallelTests", parallel = true) - public Object[][] parallelTests() - { - return moreParallelTests(super.parallelTests(), - parallelTest("testCreatePartitionedTable", this::testCreatePartitionedTable), - parallelTest("testInsertIntoNewPartition", this::testInsertIntoNewPartition), - parallelTest("testInsertIntoExistingPartition", this::testInsertIntoExistingPartition), - parallelTest("testMergePartitionedTable", this::testMergePartitionedTable)); - } - + @Test protected void testCreatePartitionedTable() { testTableModification( @@ -61,6 +51,7 @@ protected void testCreatePartitionedTable() } // Copied from BaseDeltaFailureRecoveryTest + @Test @Override protected void testDelete() { @@ -138,6 +129,7 @@ protected void testDelete() } // Copied from BaseDeltaFailureRecoveryTest + @Test @Override protected void testUpdate() { @@ -213,6 +205,7 @@ protected void testUpdate() } } + @Test protected void testInsertIntoNewPartition() { testTableModification( @@ -221,6 +214,7 @@ protected void testInsertIntoNewPartition() Optional.of("DROP TABLE
")); } + @Test protected void testInsertIntoExistingPartition() { testTableModification( @@ -229,6 +223,7 @@ protected void testInsertIntoExistingPartition() Optional.of("DROP TABLE
")); } + @Test protected void testMergePartitionedTable() { testTableModification( diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergQueryFailureRecoveryTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergQueryFailureRecoveryTest.java index ca2861e3ab6e..5d87d5cd51f6 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergQueryFailureRecoveryTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergQueryFailureRecoveryTest.java @@ -18,14 +18,20 @@ import io.trino.plugin.exchange.filesystem.containers.MinioStorage; import io.trino.testing.QueryRunner; import io.trino.tpch.TpchTable; -import org.testng.annotations.AfterClass; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.Map; import static io.trino.plugin.exchange.filesystem.containers.MinioStorage.getExchangeManagerProperties; import static io.trino.testing.TestingNames.randomNameSuffix; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestIcebergQueryFailureRecoveryTest extends BaseIcebergFailureRecoveryTest { @@ -57,7 +63,7 @@ protected QueryRunner createQueryRunner( .build(); } - @AfterClass(alwaysRun = true) + @AfterAll public void destroy() throws Exception { diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergTaskFailureRecoveryTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergTaskFailureRecoveryTest.java index 3c7ffc28847a..fffc9f86f7db 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergTaskFailureRecoveryTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergTaskFailureRecoveryTest.java @@ -18,14 +18,20 @@ import io.trino.plugin.exchange.filesystem.containers.MinioStorage; import io.trino.testing.QueryRunner; import io.trino.tpch.TpchTable; -import org.testng.annotations.AfterClass; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.Map; import static io.trino.plugin.exchange.filesystem.containers.MinioStorage.getExchangeManagerProperties; import static io.trino.testing.TestingNames.randomNameSuffix; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestIcebergTaskFailureRecoveryTest extends BaseIcebergFailureRecoveryTest { @@ -57,7 +63,7 @@ protected QueryRunner createQueryRunner( .build(); } - @AfterClass(alwaysRun = true) + @AfterAll public void destroy() throws Exception { diff --git a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/BaseMongoFailureRecoveryTest.java b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/BaseMongoFailureRecoveryTest.java index 08c3d8f04d62..4c1912dc59f3 100644 --- a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/BaseMongoFailureRecoveryTest.java +++ b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/BaseMongoFailureRecoveryTest.java @@ -19,13 +19,14 @@ import io.trino.testing.BaseFailureRecoveryTest; import io.trino.testing.QueryRunner; import io.trino.tpch.TpchTable; -import org.testng.SkipException; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.Map; import static io.trino.plugin.mongodb.MongoQueryRunner.createMongoQueryRunner; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.abort; public abstract class BaseMongoFailureRecoveryTest extends BaseFailureRecoveryTest @@ -55,54 +56,61 @@ protected QueryRunner createQueryRunner( }); } + @Test @Override protected void testAnalyzeTable() { assertThatThrownBy(super::testAnalyzeTable).hasMessageMatching("This connector does not support analyze"); - throw new SkipException("skipped"); + abort("skipped"); } + @Test @Override protected void testDelete() { assertThatThrownBy(super::testDeleteWithSubquery).hasMessageContaining("This connector does not support modifying table rows"); - throw new SkipException("skipped"); + abort("skipped"); } + @Test @Override protected void testDeleteWithSubquery() { assertThatThrownBy(super::testDeleteWithSubquery).hasMessageContaining("This connector does not support modifying table rows"); - throw new SkipException("skipped"); + abort("skipped"); } + @Test @Override protected void testMerge() { assertThatThrownBy(super::testMerge).hasMessageContaining("This connector does not support modifying table rows"); - throw new SkipException("skipped"); + abort("skipped"); } + @Test @Override protected void testRefreshMaterializedView() { assertThatThrownBy(super::testRefreshMaterializedView) .hasMessageContaining("This connector does not support creating materialized views"); - throw new SkipException("skipped"); + abort("skipped"); } + @Test @Override protected void testUpdate() { assertThatThrownBy(super::testUpdate).hasMessageContaining("This connector does not support modifying table rows"); - throw new SkipException("skipped"); + abort("skipped"); } + @Test @Override protected void testUpdateWithSubquery() { assertThatThrownBy(super::testUpdateWithSubquery).hasMessageContaining("This connector does not support modifying table rows"); - throw new SkipException("skipped"); + abort("skipped"); } @Override diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/BaseMySqlFailureRecoveryTest.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/BaseMySqlFailureRecoveryTest.java index 8ed01bb00bec..660f7f6c370c 100644 --- a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/BaseMySqlFailureRecoveryTest.java +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/BaseMySqlFailureRecoveryTest.java @@ -19,7 +19,7 @@ import io.trino.plugin.jdbc.BaseJdbcFailureRecoveryTest; import io.trino.testing.QueryRunner; import io.trino.tpch.TpchTable; -import org.testng.SkipException; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.Map; @@ -27,6 +27,7 @@ import static io.trino.plugin.mysql.MySqlQueryRunner.createMySqlQueryRunner; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.abort; public abstract class BaseMySqlFailureRecoveryTest extends BaseJdbcFailureRecoveryTest @@ -56,13 +57,15 @@ protected QueryRunner createQueryRunner( }); } + @Test @Override protected void testUpdateWithSubquery() { assertThatThrownBy(super::testUpdateWithSubquery).hasMessageContaining("Unexpected Join over for-update table scan"); - throw new SkipException("skipped"); + abort("skipped"); } + @Test @Override protected void testUpdate() { diff --git a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/BaseOracleFailureRecoveryTest.java b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/BaseOracleFailureRecoveryTest.java index a41f88860013..b1fe92264763 100644 --- a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/BaseOracleFailureRecoveryTest.java +++ b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/BaseOracleFailureRecoveryTest.java @@ -18,10 +18,8 @@ import io.trino.plugin.exchange.filesystem.FileSystemExchangePlugin; import io.trino.plugin.jdbc.BaseJdbcFailureRecoveryTest; import io.trino.testing.QueryRunner; -import io.trino.testng.services.Flaky; import io.trino.tpch.TpchTable; -import org.testng.SkipException; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.Map; @@ -31,6 +29,7 @@ import static io.trino.plugin.oracle.TestingOracleServer.TEST_PASS; import static io.trino.plugin.oracle.TestingOracleServer.TEST_USER; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.abort; public abstract class BaseOracleFailureRecoveryTest extends BaseJdbcFailureRecoveryTest @@ -65,21 +64,15 @@ protected QueryRunner createQueryRunner( }); } - @Override - @Flaky(issue = "https://github.com/trinodb/trino/issues/16277", match = "There should be no remaining tmp_trino tables that are queryable") - @Test(dataProvider = "parallelTests") - public void testParallel(Runnable runnable) - { - super.testParallel(runnable); - } - + @Test @Override protected void testUpdateWithSubquery() { assertThatThrownBy(super::testUpdateWithSubquery).hasMessageContaining("Unexpected Join over for-update table scan"); - throw new SkipException("skipped"); + abort("skipped"); } + @Test @Override protected void testUpdate() { diff --git a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/BasePostgresFailureRecoveryTest.java b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/BasePostgresFailureRecoveryTest.java index 2545eb4f2600..0380f5eefd3c 100644 --- a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/BasePostgresFailureRecoveryTest.java +++ b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/BasePostgresFailureRecoveryTest.java @@ -19,7 +19,7 @@ import io.trino.plugin.jdbc.BaseJdbcFailureRecoveryTest; import io.trino.testing.QueryRunner; import io.trino.tpch.TpchTable; -import org.testng.SkipException; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.Map; @@ -27,6 +27,7 @@ import static io.trino.plugin.postgresql.PostgreSqlQueryRunner.createPostgreSqlQueryRunner; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.abort; public abstract class BasePostgresFailureRecoveryTest extends BaseJdbcFailureRecoveryTest @@ -56,13 +57,15 @@ protected QueryRunner createQueryRunner( }); } + @Test @Override protected void testUpdateWithSubquery() { assertThatThrownBy(super::testUpdateWithSubquery).hasMessageContaining("Unexpected Join over for-update table scan"); - throw new SkipException("skipped"); + abort("skipped"); } + @Test @Override protected void testUpdate() { diff --git a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/BaseRedshiftFailureRecoveryTest.java b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/BaseRedshiftFailureRecoveryTest.java index 7a98f158eaa1..e04f8eda1215 100644 --- a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/BaseRedshiftFailureRecoveryTest.java +++ b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/BaseRedshiftFailureRecoveryTest.java @@ -19,7 +19,6 @@ import io.trino.plugin.jdbc.BaseJdbcFailureRecoveryTest; import io.trino.testing.QueryRunner; import io.trino.tpch.TpchTable; -import org.testng.SkipException; import java.util.List; import java.util.Map; @@ -27,6 +26,7 @@ import static io.trino.plugin.redshift.RedshiftQueryRunner.createRedshiftQueryRunner; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.abort; public abstract class BaseRedshiftFailureRecoveryTest extends BaseJdbcFailureRecoveryTest @@ -59,7 +59,7 @@ protected QueryRunner createQueryRunner( protected void testUpdateWithSubquery() { assertThatThrownBy(super::testUpdateWithSubquery).hasMessageContaining("Unexpected Join over for-update table scan"); - throw new SkipException("skipped"); + abort("skipped"); } @Override diff --git a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftQueryFailureRecoverySmokeTest.java b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftQueryFailureRecoverySmokeTest.java index a71ca7450c4e..9bc46670526e 100644 --- a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftQueryFailureRecoverySmokeTest.java +++ b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftQueryFailureRecoverySmokeTest.java @@ -14,7 +14,7 @@ package io.trino.plugin.redshift; import io.trino.operator.RetryPolicy; -import org.testng.annotations.DataProvider; +import org.junit.jupiter.api.Test; import java.util.Optional; @@ -28,17 +28,8 @@ public TestRedshiftQueryFailureRecoverySmokeTest() super(RetryPolicy.QUERY); } - @Override - @DataProvider(name = "parallelTests", parallel = true) - public Object[][] parallelTests() - { - // Skip the regular FTE tests to execute the smoke test faster - return new Object[][] { - parallelTest("testCreateTableAsSelect", this::testCreateTableAsSelect), - }; - } - - private void testCreateTableAsSelect() + @Test + void testCreateTableAsSelect() { assertThatQuery("CREATE TABLE
AS SELECT * FROM orders") .withCleanupQuery(Optional.of("DROP TABLE
")) diff --git a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerFailureRecoveryTest.java b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerFailureRecoveryTest.java index 11c5d85614bd..ec8aa3d229d9 100644 --- a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerFailureRecoveryTest.java +++ b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerFailureRecoveryTest.java @@ -19,7 +19,7 @@ import io.trino.plugin.jdbc.BaseJdbcFailureRecoveryTest; import io.trino.testing.QueryRunner; import io.trino.tpch.TpchTable; -import org.testng.SkipException; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.Map; @@ -27,6 +27,7 @@ import static io.trino.plugin.sqlserver.SqlServerQueryRunner.createSqlServerQueryRunner; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.abort; public abstract class BaseSqlServerFailureRecoveryTest extends BaseJdbcFailureRecoveryTest @@ -56,13 +57,15 @@ protected QueryRunner createQueryRunner( }); } + @Test @Override protected void testUpdateWithSubquery() { assertThatThrownBy(super::testUpdateWithSubquery).hasMessageContaining("Unexpected Join over for-update table scan"); - throw new SkipException("skipped"); + abort("skipped"); } + @Test @Override protected void testUpdate() { diff --git a/testing/trino-testing/src/main/java/io/trino/testing/BaseFailureRecoveryTest.java b/testing/trino-testing/src/main/java/io/trino/testing/BaseFailureRecoveryTest.java index 9a6e8d1e8938..8be6eeb95e4f 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/BaseFailureRecoveryTest.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/BaseFailureRecoveryTest.java @@ -27,8 +27,7 @@ import io.trino.spi.QueryId; import io.trino.tpch.TpchTable; import org.assertj.core.api.AbstractThrowableAssert; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Arrays; import java.util.HashMap; @@ -39,7 +38,6 @@ import java.util.OptionalInt; import java.util.Set; import java.util.UUID; -import java.util.concurrent.Semaphore; import java.util.concurrent.ThreadLocalRandom; import java.util.function.Consumer; import java.util.function.Function; @@ -80,20 +78,12 @@ public abstract class BaseFailureRecoveryTest { private static final Duration MAX_ERROR_DURATION = new Duration(5, SECONDS); private static final Duration REQUEST_TIMEOUT = new Duration(5, SECONDS); - private static final int DEFAULT_MAX_PARALLEL_TEST_CONCURRENCY = 4; private final RetryPolicy retryPolicy; - private final Semaphore parallelTestsSemaphore; protected BaseFailureRecoveryTest(RetryPolicy retryPolicy) - { - this(retryPolicy, DEFAULT_MAX_PARALLEL_TEST_CONCURRENCY); - } - - protected BaseFailureRecoveryTest(RetryPolicy retryPolicy, int maxParallelTestConcurrency) { this.retryPolicy = requireNonNull(retryPolicy, "retryPolicy is null"); - this.parallelTestsSemaphore = new Semaphore(maxParallelTestConcurrency); } protected RetryPolicy getRetryPolicy() @@ -190,47 +180,7 @@ protected void testSelect(String query, Optional session, Consumer")); } + @Test protected void testInsert() { testTableModification( @@ -247,6 +198,7 @@ protected void testInsert() Optional.of("DROP TABLE
")); } + @Test protected void testDelete() { testTableModification( @@ -255,6 +207,7 @@ protected void testDelete() Optional.of("DROP TABLE
")); } + @Test protected void testDeleteWithSubquery() { testTableModification( @@ -263,6 +216,7 @@ protected void testDeleteWithSubquery() Optional.of("DROP TABLE
")); } + @Test protected void testUpdate() { testTableModification( @@ -271,6 +225,7 @@ protected void testUpdate() Optional.of("DROP TABLE
")); } + @Test protected void testUpdateWithSubquery() { testTableModification( @@ -279,6 +234,7 @@ protected void testUpdateWithSubquery() Optional.of("DROP TABLE
")); } + @Test protected void testAnalyzeTable() { testNonSelect( @@ -289,6 +245,7 @@ protected void testAnalyzeTable() false); } + @Test protected void testMerge() { testTableModification( @@ -305,6 +262,7 @@ protected void testMerge() Optional.of("DROP TABLE
")); } + @Test protected void testRefreshMaterializedView() { testTableModification( @@ -313,6 +271,7 @@ protected void testRefreshMaterializedView() Optional.of("DROP MATERIALIZED VIEW
")); } + @Test protected void testExplainAnalyze() { testSelect("EXPLAIN ANALYZE SELECT orderStatus, count(*) FROM orders GROUP BY orderStatus"); @@ -323,6 +282,7 @@ protected void testExplainAnalyze() Optional.of("DROP TABLE
")); } + @Test protected void testRequestTimeouts() { if (areWriteRetriesSupported()) { diff --git a/testing/trino-testing/src/main/java/io/trino/testing/ExtendedFailureRecoveryTest.java b/testing/trino-testing/src/main/java/io/trino/testing/ExtendedFailureRecoveryTest.java index b78493c49e52..82097ea02fe6 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/ExtendedFailureRecoveryTest.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/ExtendedFailureRecoveryTest.java @@ -20,8 +20,10 @@ import io.trino.server.DynamicFilterService.DynamicFiltersStats; import io.trino.spi.ErrorType; import org.intellij.lang.annotations.Language; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.Optional; @@ -40,7 +42,11 @@ import static io.trino.sql.planner.OptimizerConfig.JoinReorderingStrategy.NONE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public abstract class ExtendedFailureRecoveryTest extends BaseFailureRecoveryTest { @@ -51,7 +57,7 @@ protected ExtendedFailureRecoveryTest(RetryPolicy retryPolicy) super(retryPolicy); } - @BeforeClass + @BeforeAll public void initTables() throws Exception { @@ -61,28 +67,19 @@ public void initTables() protected abstract void createPartitionedLineitemTable(String tableName, List columns, String partitionColumn); - @Override - @DataProvider(name = "parallelTests", parallel = true) - public Object[][] parallelTests() - { - return moreParallelTests(super.parallelTests(), - parallelTest("testSimpleSelect", this::testSimpleSelect), - parallelTest("testAggregation", this::testAggregation), - parallelTest("testJoinDynamicFilteringDisabled", this::testJoinDynamicFilteringDisabled), - parallelTest("testJoinDynamicFilteringEnabled", this::testJoinDynamicFilteringEnabled), - parallelTest("testUserFailure", this::testUserFailure)); - } - + @Test protected void testSimpleSelect() { testSelect("SELECT * FROM nation"); } + @Test protected void testAggregation() { testSelect("SELECT orderStatus, count(*) FROM orders GROUP BY orderStatus"); } + @Test protected void testJoinDynamicFilteringDisabled() { @Language("SQL") String selectQuery = "SELECT * FROM partitioned_lineitem JOIN supplier ON partitioned_lineitem.suppkey = supplier.suppkey " + @@ -90,6 +87,7 @@ protected void testJoinDynamicFilteringDisabled() testSelect(selectQuery, Optional.of(enableDynamicFiltering(false))); } + @Test protected void testJoinDynamicFilteringEnabled() { @Language("SQL") String selectQuery = "SELECT * FROM partitioned_lineitem JOIN supplier ON partitioned_lineitem.suppkey = supplier.suppkey " + @@ -108,6 +106,7 @@ protected void testJoinDynamicFilteringEnabled() }); } + @Test protected void testUserFailure() { // Some connectors have pushdowns enabled for arithmetic operations (like SqlServer), @@ -125,6 +124,7 @@ protected void testUserFailure() .failsAlways(failure -> failure.hasMessageContaining(FAILURE_INJECTION_MESSAGE)); } + @Test @Override protected void testRequestTimeouts() { From b7bfd491e35b7dad9ffe65800657c11be36b6177 Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Mon, 13 Nov 2023 06:40:57 +0900 Subject: [PATCH 213/587] Add missing `@Test` annotation --- .../plugin/bigquery/BaseBigQueryFailureRecoveryTest.java | 9 +++++++++ .../plugin/redshift/BaseRedshiftFailureRecoveryTest.java | 3 +++ 2 files changed, 12 insertions(+) diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryFailureRecoveryTest.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryFailureRecoveryTest.java index 6fc21af7f025..e03130d0c6b4 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryFailureRecoveryTest.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryFailureRecoveryTest.java @@ -19,6 +19,7 @@ import io.trino.testing.BaseFailureRecoveryTest; import io.trino.testing.QueryRunner; import io.trino.tpch.TpchTable; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.Map; @@ -53,12 +54,14 @@ protected QueryRunner createQueryRunner( }); } + @Test @Override protected boolean areWriteRetriesSupported() { return true; } + @Test @Override protected void testAnalyzeTable() { @@ -66,6 +69,7 @@ protected void testAnalyzeTable() abort("skipped"); } + @Test @Override protected void testDelete() { @@ -73,6 +77,7 @@ protected void testDelete() abort("skipped"); } + @Test @Override protected void testDeleteWithSubquery() { @@ -80,6 +85,7 @@ protected void testDeleteWithSubquery() abort("skipped"); } + @Test @Override protected void testMerge() { @@ -87,6 +93,7 @@ protected void testMerge() abort("skipped"); } + @Test @Override protected void testRefreshMaterializedView() { @@ -94,6 +101,7 @@ protected void testRefreshMaterializedView() abort("skipped"); } + @Test @Override protected void testUpdate() { @@ -101,6 +109,7 @@ protected void testUpdate() abort("skipped"); } + @Test @Override protected void testUpdateWithSubquery() { diff --git a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/BaseRedshiftFailureRecoveryTest.java b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/BaseRedshiftFailureRecoveryTest.java index e04f8eda1215..8ef65af2f579 100644 --- a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/BaseRedshiftFailureRecoveryTest.java +++ b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/BaseRedshiftFailureRecoveryTest.java @@ -19,6 +19,7 @@ import io.trino.plugin.jdbc.BaseJdbcFailureRecoveryTest; import io.trino.testing.QueryRunner; import io.trino.tpch.TpchTable; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.Map; @@ -55,6 +56,7 @@ protected QueryRunner createQueryRunner( }); } + @Test @Override protected void testUpdateWithSubquery() { @@ -62,6 +64,7 @@ protected void testUpdateWithSubquery() abort("skipped"); } + @Test @Override protected void testUpdate() { From 375d38d0b9df5375c7c0229147a5530600a864c4 Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Mon, 13 Nov 2023 12:08:35 +0900 Subject: [PATCH 214/587] Increase Spark memory in Delta product test --- .../environment/singlenode-delta-lake-oss/spark-defaults.conf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/environment/singlenode-delta-lake-oss/spark-defaults.conf b/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/environment/singlenode-delta-lake-oss/spark-defaults.conf index f97f2afc7e09..1f6d3bfc7273 100644 --- a/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/environment/singlenode-delta-lake-oss/spark-defaults.conf +++ b/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/environment/singlenode-delta-lake-oss/spark-defaults.conf @@ -1,3 +1,6 @@ +# singlenode-delta-lake-oss causes OOM maybe by too many product tests +spark.driver.memory=2g + spark.sql.catalogImplementation=hive spark.sql.warehouse.dir=hdfs://hadoop-master:9000/user/hive/warehouse spark.sql.hive.thriftServer.singleSession=false From 944aa96d854f9a1fc9729aef5ee84519dd208a28 Mon Sep 17 00:00:00 2001 From: Jack Klamer Date: Fri, 13 Oct 2023 16:23:09 -0500 Subject: [PATCH 215/587] Fix possible race condition in SqlTaskManager There is a possible race condition between SqlTaskManager and CatalogPruneTask on workers. Before the catalogs for a Task are set it is possible for a active catalogs from the prune task to arrive, fail to be agumentted by the catalogs of the task, but then prune the catalogs after they are set and loaded. This fix uses a ReadWriteLock to ensure mutual exclusion of task catalog setting and task scanning/pruning. --- .../io/trino/connector/CatalogPruneTask.java | 21 +- .../CoordinatorDynamicCatalogManager.java | 1 + .../trino/connector/StaticCatalogManager.java | 7 + .../WorkerDynamicCatalogManager.java | 4 +- .../io/trino/execution/SqlTaskManager.java | 20 +- .../io/trino/metadata/CatalogManager.java | 8 + .../io/trino/sql/planner/PlanFragment.java | 16 + .../TestingLocalCatalogPruneTask.java | 91 ++++++ ...estSqlTaskManagerRaceWithCatalogPrune.java | 290 ++++++++++++++++++ 9 files changed, 444 insertions(+), 14 deletions(-) create mode 100644 core/trino-main/src/test/java/io/trino/connector/TestingLocalCatalogPruneTask.java create mode 100644 core/trino-main/src/test/java/io/trino/execution/TestSqlTaskManagerRaceWithCatalogPrune.java diff --git a/core/trino-main/src/main/java/io/trino/connector/CatalogPruneTask.java b/core/trino-main/src/main/java/io/trino/connector/CatalogPruneTask.java index 0dccf39a35ce..a9930d864712 100644 --- a/core/trino-main/src/main/java/io/trino/connector/CatalogPruneTask.java +++ b/core/trino-main/src/main/java/io/trino/connector/CatalogPruneTask.java @@ -29,6 +29,7 @@ import io.airlift.log.Logger; import io.airlift.node.NodeInfo; import io.airlift.units.Duration; +import io.trino.metadata.CatalogManager; import io.trino.metadata.ForNodeManager; import io.trino.server.InternalCommunicationConfig; import io.trino.spi.connector.CatalogHandle; @@ -61,7 +62,8 @@ public class CatalogPruneTask private static final JsonCodec> CATALOG_HANDLES_CODEC = listJsonCodec(CatalogHandle.class); private final TransactionManager transactionManager; - private final CoordinatorDynamicCatalogManager catalogManager; + private final CatalogManager catalogManager; + private final ConnectorServicesProvider connectorServicesProvider; private final NodeInfo nodeInfo; private final ServiceSelector selector; private final HttpClient httpClient; @@ -78,7 +80,8 @@ public class CatalogPruneTask @Inject public CatalogPruneTask( TransactionManager transactionManager, - CoordinatorDynamicCatalogManager catalogManager, + CatalogManager catalogManager, + ConnectorServicesProvider connectorServicesProvider, NodeInfo nodeInfo, @ServiceType("trino") ServiceSelector selector, @ForNodeManager HttpClient httpClient, @@ -87,6 +90,7 @@ public CatalogPruneTask( { this.transactionManager = requireNonNull(transactionManager, "transactionManager is null"); this.catalogManager = requireNonNull(catalogManager, "catalogManager is null"); + this.connectorServicesProvider = requireNonNull(connectorServicesProvider, "connectorServicesProvider is null"); this.nodeInfo = requireNonNull(nodeInfo, "nodeInfo is null"); this.selector = requireNonNull(selector, "selector is null"); this.httpClient = requireNonNull(httpClient, "httpClient is null"); @@ -127,7 +131,7 @@ public ThreadPoolExecutorMBean getExecutor() } @VisibleForTesting - void pruneWorkerCatalogs() + public void pruneWorkerCatalogs() { Set online = selector.selectAllServices().stream() .filter(descriptor -> !nodeInfo.getNodeId().equals(descriptor.getNodeId())) @@ -135,6 +139,14 @@ void pruneWorkerCatalogs() // send message to workers to trigger prune List activeCatalogs = getActiveCatalogs(); + pruneWorkerCatalogs(online, activeCatalogs); + + // prune all inactive catalogs - we pass an empty set here because manager always retains active catalogs + connectorServicesProvider.pruneCatalogs(ImmutableSet.of()); + } + + void pruneWorkerCatalogs(Set online, List activeCatalogs) + { for (ServiceDescriptor service : online) { URI uri = getHttpUri(service); if (uri == null) { @@ -163,9 +175,6 @@ public Object handle(Request request, Response response) } }); } - - // prune all inactive catalogs - we pass an empty set here because manager always retains active catalogs - catalogManager.pruneCatalogs(ImmutableSet.of()); } private List getActiveCatalogs() diff --git a/core/trino-main/src/main/java/io/trino/connector/CoordinatorDynamicCatalogManager.java b/core/trino-main/src/main/java/io/trino/connector/CoordinatorDynamicCatalogManager.java index 68cef226315e..647c51909c8c 100644 --- a/core/trino-main/src/main/java/io/trino/connector/CoordinatorDynamicCatalogManager.java +++ b/core/trino-main/src/main/java/io/trino/connector/CoordinatorDynamicCatalogManager.java @@ -166,6 +166,7 @@ public Optional getCatalog(String catalogName) return Optional.ofNullable(activeCatalogs.get(catalogName)); } + @Override public Set getActiveCatalogs() { return activeCatalogs.values().stream() diff --git a/core/trino-main/src/main/java/io/trino/connector/StaticCatalogManager.java b/core/trino-main/src/main/java/io/trino/connector/StaticCatalogManager.java index 3e0277002b0d..9e55435cae73 100644 --- a/core/trino-main/src/main/java/io/trino/connector/StaticCatalogManager.java +++ b/core/trino-main/src/main/java/io/trino/connector/StaticCatalogManager.java @@ -200,6 +200,13 @@ public Optional getCatalogProperties(CatalogHandle catalogHan return Optional.empty(); } + @Override + public Set getActiveCatalogs() + { + // static catalog manager does not differentiate between active and not. Nor does it need to prune + return ImmutableSet.of(); + } + @Override public ConnectorServices getConnectorServices(CatalogHandle catalogHandle) { diff --git a/core/trino-main/src/main/java/io/trino/connector/WorkerDynamicCatalogManager.java b/core/trino-main/src/main/java/io/trino/connector/WorkerDynamicCatalogManager.java index 7403f3d2d14b..5dc7be99dcca 100644 --- a/core/trino-main/src/main/java/io/trino/connector/WorkerDynamicCatalogManager.java +++ b/core/trino-main/src/main/java/io/trino/connector/WorkerDynamicCatalogManager.java @@ -101,7 +101,7 @@ public void ensureCatalogsLoaded(Session session, List expect checkArgument(!catalog.getCatalogHandle().equals(GlobalSystemConnector.CATALOG_HANDLE), "Global system catalog not registered"); CatalogConnector newCatalog = catalogFactory.createCatalog(catalog); catalogs.put(catalog.getCatalogHandle(), newCatalog); - log.info("Added catalog: " + catalog.getCatalogHandle()); + log.debug("Added catalog: " + catalog.getCatalogHandle()); } } finally { @@ -142,7 +142,7 @@ public void pruneCatalogs(Set catalogsInUse) } if (!removedCatalogs.isEmpty()) { List sortedHandles = removedCatalogs.stream().map(connector -> connector.getCatalogHandle().toString()).sorted().toList(); - log.info("Pruned catalogs: %s", sortedHandles); + log.debug("Pruned catalogs: %s", sortedHandles); } } diff --git a/core/trino-main/src/main/java/io/trino/execution/SqlTaskManager.java b/core/trino-main/src/main/java/io/trino/execution/SqlTaskManager.java index 685d8c2718b1..f4c8fe43ab19 100644 --- a/core/trino-main/src/main/java/io/trino/execution/SqlTaskManager.java +++ b/core/trino-main/src/main/java/io/trino/execution/SqlTaskManager.java @@ -78,7 +78,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Predicate; import static com.google.common.base.Preconditions.checkArgument; @@ -447,13 +447,14 @@ public VersionedDynamicFilterDomains acknowledgeAndGetNewDynamicFilterDomains(Ta return sqlTask.acknowledgeAndGetNewDynamicFilterDomains(currentDynamicFiltersVersion); } - private final ReentrantLock catalogsLock = new ReentrantLock(); + private final ReentrantReadWriteLock catalogsLock = new ReentrantReadWriteLock(); public void pruneCatalogs(Set activeCatalogs) { - catalogsLock.lock(); + Set catalogsInUse = new HashSet<>(activeCatalogs); + ReentrantReadWriteLock.WriteLock pruneLock = catalogsLock.writeLock(); + pruneLock.lock(); try { - Set catalogsInUse = new HashSet<>(activeCatalogs); for (SqlTask task : tasks.asMap().values()) { // add all catalogs being used by a non-done task if (!task.getTaskState().isDone()) { @@ -463,7 +464,7 @@ public void pruneCatalogs(Set activeCatalogs) connectorServicesProvider.pruneCatalogs(catalogsInUse); } finally { - catalogsLock.unlock(); + pruneLock.unlock(); } } @@ -533,7 +534,14 @@ private TaskInfo doUpdateTask( .map(CatalogProperties::getCatalogHandle) .collect(toImmutableSet()); if (sqlTask.setCatalogs(catalogHandles)) { - connectorServicesProvider.ensureCatalogsLoaded(session, activeCatalogs); + ReentrantReadWriteLock.ReadLock catalogInitLock = catalogsLock.readLock(); + catalogInitLock.lock(); + try { + connectorServicesProvider.ensureCatalogsLoaded(session, activeCatalogs); + } + finally { + catalogInitLock.unlock(); + } } }); diff --git a/core/trino-main/src/main/java/io/trino/metadata/CatalogManager.java b/core/trino-main/src/main/java/io/trino/metadata/CatalogManager.java index cf3538aa72cf..31f9628a29df 100644 --- a/core/trino-main/src/main/java/io/trino/metadata/CatalogManager.java +++ b/core/trino-main/src/main/java/io/trino/metadata/CatalogManager.java @@ -44,6 +44,12 @@ public Optional getCatalogProperties(CatalogHandle catalogHan return Optional.empty(); } + @Override + public Set getActiveCatalogs() + { + return ImmutableSet.of(); + } + @Override public void createCatalog(String catalogName, ConnectorName connectorName, Map properties, boolean notExists) { @@ -63,6 +69,8 @@ public void dropCatalog(String catalogName, boolean exists) Optional getCatalogProperties(CatalogHandle catalogHandle); + Set getActiveCatalogs(); + void createCatalog(String catalogName, ConnectorName connectorName, Map properties, boolean notExists); void dropCatalog(String catalogName, boolean exists); diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/PlanFragment.java b/core/trino-main/src/main/java/io/trino/sql/planner/PlanFragment.java index 4b51a8edff02..42db56e5812c 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/PlanFragment.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/PlanFragment.java @@ -356,4 +356,20 @@ public PlanFragment withRoot(PlanNode root) this.languageFunctions, this.jsonRepresentation); } + + public PlanFragment withActiveCatalogs(List activeCatalogs) + { + return new PlanFragment( + this.id, + this.root, + this.symbols, + this.partitioning, + this.partitionCount, + this.partitionedSources, + this.outputPartitioningScheme, + this.statsAndCosts, + activeCatalogs, + this.languageFunctions, + this.jsonRepresentation); + } } diff --git a/core/trino-main/src/test/java/io/trino/connector/TestingLocalCatalogPruneTask.java b/core/trino-main/src/test/java/io/trino/connector/TestingLocalCatalogPruneTask.java new file mode 100644 index 000000000000..ec48bd497d4e --- /dev/null +++ b/core/trino-main/src/test/java/io/trino/connector/TestingLocalCatalogPruneTask.java @@ -0,0 +1,91 @@ +/* + * 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 io.trino.connector; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.ListenableFuture; +import io.airlift.discovery.client.ServiceDescriptor; +import io.airlift.discovery.client.ServiceSelector; +import io.airlift.http.client.testing.TestingHttpClient; +import io.airlift.node.NodeInfo; +import io.trino.execution.SqlTaskManager; +import io.trino.metadata.CatalogManager; +import io.trino.server.InternalCommunicationConfig; +import io.trino.spi.connector.CatalogHandle; +import io.trino.transaction.TransactionManager; + +import java.util.List; +import java.util.Set; + +import static com.google.common.util.concurrent.Futures.immediateFuture; +import static java.util.Objects.requireNonNull; + +public class TestingLocalCatalogPruneTask + extends CatalogPruneTask +{ + private final SqlTaskManager sqlTaskManagerToPrune; + + public TestingLocalCatalogPruneTask( + TransactionManager transactionManager, + CatalogManager catalogManager, + ConnectorServicesProvider connectorServicesProvider, + NodeInfo nodeInfo, + CatalogPruneTaskConfig catalogPruneTaskConfig, + SqlTaskManager sqlTaskManagerToPrune) + { + super( + transactionManager, + catalogManager, + connectorServicesProvider, + nodeInfo, + new ServiceSelector() { + @Override + public String getType() + { + throw new UnsupportedOperationException("No services to select"); + } + + @Override + public String getPool() + { + throw new UnsupportedOperationException("No pool"); + } + + @Override + public List selectAllServices() + { + return ImmutableList.of(); + } + + @Override + public ListenableFuture> refresh() + { + return immediateFuture(ImmutableList.of()); + } + }, + new TestingHttpClient(request -> { + throw new UnsupportedOperationException("Testing Locl Catalog Prune Task does not make http calls"); + }), + catalogPruneTaskConfig, + new InternalCommunicationConfig()); + this.sqlTaskManagerToPrune = requireNonNull(sqlTaskManagerToPrune, "sqlTaskManagerToPrune is null"); + } + + @Override + void pruneWorkerCatalogs(Set online, List activeCatalogs) + { + sqlTaskManagerToPrune.pruneCatalogs(ImmutableSet.copyOf(activeCatalogs)); + } +} diff --git a/core/trino-main/src/test/java/io/trino/execution/TestSqlTaskManagerRaceWithCatalogPrune.java b/core/trino-main/src/test/java/io/trino/execution/TestSqlTaskManagerRaceWithCatalogPrune.java new file mode 100644 index 000000000000..815c8dd85a8d --- /dev/null +++ b/core/trino-main/src/test/java/io/trino/execution/TestSqlTaskManagerRaceWithCatalogPrune.java @@ -0,0 +1,290 @@ +/* + * 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 io.trino.execution; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import io.airlift.node.NodeInfo; +import io.airlift.stats.TestingGcMonitor; +import io.airlift.tracing.Tracing; +import io.airlift.units.Duration; +import io.opentelemetry.api.trace.Span; +import io.trino.Session; +import io.trino.connector.CatalogConnector; +import io.trino.connector.CatalogFactory; +import io.trino.connector.CatalogProperties; +import io.trino.connector.CatalogPruneTask; +import io.trino.connector.CatalogPruneTaskConfig; +import io.trino.connector.ConnectorName; +import io.trino.connector.ConnectorServices; +import io.trino.connector.ConnectorServicesProvider; +import io.trino.connector.MockConnectorFactory; +import io.trino.connector.TestingLocalCatalogPruneTask; +import io.trino.connector.WorkerDynamicCatalogManager; +import io.trino.exchange.ExchangeManagerRegistry; +import io.trino.execution.buffer.PipelinedOutputBuffers; +import io.trino.execution.executor.RunningSplitInfo; +import io.trino.execution.executor.TaskExecutor; +import io.trino.execution.executor.TaskHandle; +import io.trino.memory.LocalMemoryManager; +import io.trino.memory.NodeMemoryConfig; +import io.trino.metadata.WorkerLanguageFunctionProvider; +import io.trino.spi.connector.CatalogHandle; +import io.trino.spi.connector.Connector; +import io.trino.spi.connector.ConnectorFactory; +import io.trino.spiller.LocalSpillManager; +import io.trino.spiller.NodeSpillConfig; +import io.trino.sql.planner.PlanFragment; +import io.trino.testing.TestingConnectorContext; +import io.trino.testing.TestingSession; +import io.trino.transaction.NoOpTransactionManager; +import io.trino.transaction.TransactionInfo; +import io.trino.version.EmbedVersion; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; + +import java.util.List; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.DoubleSupplier; +import java.util.function.Function; +import java.util.function.Predicate; + +import static io.airlift.tracing.Tracing.noopTracer; +import static io.trino.execution.BaseTestSqlTaskManager.OUT; +import static io.trino.execution.TaskTestUtils.PLAN_FRAGMENT; +import static io.trino.execution.TaskTestUtils.TABLE_SCAN_NODE_ID; +import static io.trino.execution.TaskTestUtils.createTestSplitMonitor; +import static io.trino.execution.TaskTestUtils.createTestingPlanner; +import static io.trino.execution.buffer.PipelinedOutputBuffers.BufferType.PARTITIONED; +import static io.trino.metadata.CatalogManager.NO_CATALOGS; +import static io.trino.spi.connector.CatalogHandle.createRootCatalogHandle; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; + +@TestInstance(PER_CLASS) +@Execution(SAME_THREAD) +public class TestSqlTaskManagerRaceWithCatalogPrune +{ + private static final int NUM_TASKS = 20000; + private static final ConnectorServicesProvider NOOP_CONNECTOR_SERVICES_PROVIDER = new ConnectorServicesProvider() + { + @Override + public void loadInitialCatalogs() {} + + @Override + public void ensureCatalogsLoaded(Session session, List catalogs) {} + + @Override + public void pruneCatalogs(Set catalogsInUse) {} + + @Override + public ConnectorServices getConnectorServices(CatalogHandle catalogHandle) + { + return null; + } + }; + private static final CatalogFactory MOCK_CATALOG_FACTORY = new CatalogFactory() + { + @Override + public void addConnectorFactory(ConnectorFactory connectorFactory, Function duplicatePluginClassLoaderFactory) {} + + @Override + public CatalogConnector createCatalog(CatalogProperties catalogProperties) + { + Connector connector = MockConnectorFactory.create().create(catalogProperties.getCatalogHandle().getCatalogName(), catalogProperties.getProperties(), new TestingConnectorContext()); + ConnectorServices noOpConnectorService = new ConnectorServices( + Tracing.noopTracer(), + catalogProperties.getCatalogHandle(), + connector, + () -> {}); + return new CatalogConnector( + catalogProperties.getCatalogHandle(), + new ConnectorName("mock"), + noOpConnectorService, + noOpConnectorService, + noOpConnectorService, + Optional.of(catalogProperties)); + } + + @Override + public CatalogConnector createCatalog(CatalogHandle catalogHandle, ConnectorName connectorName, Connector connector) + { + throw new UnsupportedOperationException("Only implement what is needed by worker catalog manager"); + } + }; + private static final TaskExecutor NOOP_TASK_EXECUTOR = new TaskExecutor() { + @Override + public TaskHandle addTask(TaskId taskId, DoubleSupplier utilizationSupplier, int initialSplitConcurrency, Duration splitConcurrencyAdjustFrequency, OptionalInt maxDriversPerTask) + { + return new TaskHandle() { + @Override + public boolean isDestroyed() + { + return false; + } + }; + } + + @Override + public void removeTask(TaskHandle taskHandle) {} + + @Override + public List> enqueueSplits(TaskHandle taskHandle, boolean intermediate, List taskSplits) + { + return ImmutableList.of(); + } + + @Override + public Set getStuckSplitTaskIds(Duration processingDurationThreshold, Predicate filter) + { + return ImmutableSet.of(); + } + + @Override + public void start() {} + + @Override + public void stop() {} + }; + private final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 2, 10, TimeUnit.MINUTES, new LinkedBlockingDeque<>()); + private final AtomicInteger sequence = new AtomicInteger(1); + + @AfterAll + public void cleanup() + { + threadPoolExecutor.shutdown(); + } + + @Test + public void testMultipleTaskUpdatesWithMultipleCatalogPrunes() + { + ConnectorServicesProvider workerConnectorServiceProvider = new WorkerDynamicCatalogManager(MOCK_CATALOG_FACTORY); + SqlTaskManager workerTaskManager = getWorkerTaskManagerWithConnectorServiceProvider(workerConnectorServiceProvider); + + CatalogPruneTask catalogPruneTask = new TestingLocalCatalogPruneTask( + new NoInfoTransactionManager(), + NO_CATALOGS, + NOOP_CONNECTOR_SERVICES_PROVIDER, + new NodeInfo("testversion"), + new CatalogPruneTaskConfig(), + workerTaskManager); + + Future catalogTaskFuture = Futures.submit(() -> + { + for (int i = 0; i < NUM_TASKS; i++) { + String catalogName = "catalog_" + i; + CatalogHandle catalogHandle = createRootCatalogHandle(catalogName, new CatalogHandle.CatalogVersion(UUID.randomUUID().toString())); + TaskId taskId = newTaskId(); + workerTaskManager.updateTask( + TestingSession.testSession(), + taskId, + Span.getInvalid(), + Optional.of(fragmentWithCatalog(catalogHandle)), + ImmutableList.of(new SplitAssignment(TABLE_SCAN_NODE_ID, ImmutableSet.of(), true)), + PipelinedOutputBuffers.createInitial(PARTITIONED).withBuffer(OUT, 0).withNoMoreBufferIds(), + ImmutableMap.of(), + false); + try { + Thread.sleep(0, ThreadLocalRandom.current().nextInt(25, 75)); + } + catch (InterruptedException e) { + throw new RuntimeException(e); + } + assertDoesNotThrow(() -> workerConnectorServiceProvider.getConnectorServices(catalogHandle)); + workerTaskManager.cancelTask(taskId); + if ((i & 63) == 0) { + workerTaskManager.removeOldTasks(); + } + } + }, threadPoolExecutor); + + Future pruneCatalogsFuture = Futures.submit(() -> + { + for (int i = 0; i < NUM_TASKS; i++) { + catalogPruneTask.pruneWorkerCatalogs(); + try { + Thread.sleep(0, ThreadLocalRandom.current().nextInt(25, 75)); + } + catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + }, threadPoolExecutor); + + assertDoesNotThrow(() -> catalogTaskFuture.get(2, TimeUnit.MINUTES)); + assertDoesNotThrow(() -> pruneCatalogsFuture.get(2, TimeUnit.MINUTES)); + } + + private TaskId newTaskId() + { + return new TaskId(new StageId("query" + sequence.incrementAndGet(), 0), 1, 0); + } + + private static SqlTaskManager getWorkerTaskManagerWithConnectorServiceProvider(ConnectorServicesProvider workerConnectorServiceProvider) + { + return new SqlTaskManager( + new EmbedVersion("testversion"), + workerConnectorServiceProvider, + createTestingPlanner(), + new WorkerLanguageFunctionProvider(), + new BaseTestSqlTaskManager.MockLocationFactory(), + NOOP_TASK_EXECUTOR, + createTestSplitMonitor(), + new NodeInfo("testversion"), + new LocalMemoryManager(new NodeMemoryConfig()), + new TaskManagementExecutor(), + new TaskManagerConfig().setInfoMaxAge(Duration.ZERO), + new NodeMemoryConfig(), + new LocalSpillManager(new NodeSpillConfig()), + new NodeSpillConfig(), + new TestingGcMonitor(), + noopTracer(), + new ExchangeManagerRegistry(), + ignore -> true); + } + + private static PlanFragment fragmentWithCatalog(CatalogHandle catalogHandle) + { + return PLAN_FRAGMENT.withActiveCatalogs(ImmutableList.of( + new CatalogProperties( + catalogHandle, + new ConnectorName("mock"), + ImmutableMap.of()))); + } + + private static class NoInfoTransactionManager + extends NoOpTransactionManager + { + @Override + public List getAllTransactionInfos() + { + return ImmutableList.of(); + } + } +} From 8155c561e374508a5cc210ecf9b46d31c2384984 Mon Sep 17 00:00:00 2001 From: Slawomir Pajak Date: Tue, 7 Nov 2023 14:32:58 +0100 Subject: [PATCH 216/587] Add cause of failures in Thrift and Glue metastore This will be beneficial in case of detecting retry issues --- .../hive/SchemaAlreadyExistsException.java | 11 +++++--- .../hive/TableAlreadyExistsException.java | 6 ++--- .../metastore/glue/GlueHiveMetastore.java | 22 ++++++++-------- .../metastore/thrift/ThriftHiveMetastore.java | 26 +++++++++---------- .../catalog/glue/TrinoGlueCatalog.java | 6 ++--- 5 files changed, 38 insertions(+), 33 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/SchemaAlreadyExistsException.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/SchemaAlreadyExistsException.java index 5511366ddecc..8bff8da39a12 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/SchemaAlreadyExistsException.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/SchemaAlreadyExistsException.java @@ -25,12 +25,17 @@ public class SchemaAlreadyExistsException public SchemaAlreadyExistsException(String schemaName) { - this(schemaName, format("Schema already exists: '%s'", schemaName)); + this(schemaName, null); } - public SchemaAlreadyExistsException(String schemaName, String message) + public SchemaAlreadyExistsException(String schemaName, Throwable cause) { - super(ALREADY_EXISTS, message); + this(schemaName, format("Schema already exists: '%s'", schemaName), cause); + } + + public SchemaAlreadyExistsException(String schemaName, String message, Throwable cause) + { + super(ALREADY_EXISTS, message, cause); this.schemaName = schemaName; } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/TableAlreadyExistsException.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/TableAlreadyExistsException.java index 157e0744f8c1..ff0e9fdae2bb 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/TableAlreadyExistsException.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/TableAlreadyExistsException.java @@ -26,12 +26,12 @@ public class TableAlreadyExistsException public TableAlreadyExistsException(SchemaTableName tableName) { - this(tableName, format("Table already exists: '%s'", tableName)); + this(tableName, null); } - public TableAlreadyExistsException(SchemaTableName tableName, String message) + public TableAlreadyExistsException(SchemaTableName tableName, Throwable cause) { - this(tableName, message, null); + this(tableName, format("Table already exists: '%s'", tableName), cause); } public TableAlreadyExistsException(SchemaTableName tableName, String message, Throwable cause) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java index 4264190cd0bd..1991c2d869ff 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java @@ -355,7 +355,7 @@ private void updateTableStatisticsInternal(String databaseName, String tableName columnStatisticsProvider.updateTableColumnStatistics(table, updatedStatistics.getColumnStatistics()); } catch (EntityNotFoundException e) { - throw new TableNotFoundException(new SchemaTableName(databaseName, tableName)); + throw new TableNotFoundException(new SchemaTableName(databaseName, tableName), e); } catch (AmazonServiceException e) { throw new TrinoException(HIVE_METASTORE_ERROR, e); @@ -487,7 +487,7 @@ public void createDatabase(Database database) glueClient.createDatabase(new CreateDatabaseRequest().withDatabaseInput(databaseInput))); } catch (AlreadyExistsException e) { - throw new SchemaAlreadyExistsException(database.getDatabaseName()); + throw new SchemaAlreadyExistsException(database.getDatabaseName(), e); } catch (AmazonServiceException e) { throw new TrinoException(HIVE_METASTORE_ERROR, e); @@ -519,7 +519,7 @@ public void dropDatabase(String databaseName, boolean deleteData) glueClient.deleteDatabase(new DeleteDatabaseRequest().withName(databaseName))); } catch (EntityNotFoundException e) { - throw new SchemaNotFoundException(databaseName); + throw new SchemaNotFoundException(databaseName, e); } catch (AmazonServiceException e) { throw new TrinoException(HIVE_METASTORE_ERROR, e); @@ -563,10 +563,10 @@ public void createTable(Table table, PrincipalPrivileges principalPrivileges) .withTableInput(input))); } catch (AlreadyExistsException e) { - throw new TableAlreadyExistsException(new SchemaTableName(table.getDatabaseName(), table.getTableName())); + throw new TableAlreadyExistsException(new SchemaTableName(table.getDatabaseName(), table.getTableName()), e); } catch (EntityNotFoundException e) { - throw new SchemaNotFoundException(table.getDatabaseName()); + throw new SchemaNotFoundException(table.getDatabaseName(), e); } catch (AmazonServiceException e) { throw new TrinoException(HIVE_METASTORE_ERROR, e); @@ -626,7 +626,7 @@ public void replaceTable(String databaseName, String tableName, Table newTable, .withTableInput(newTableInput))); } catch (EntityNotFoundException e) { - throw new TableNotFoundException(new SchemaTableName(databaseName, tableName)); + throw new TableNotFoundException(new SchemaTableName(databaseName, tableName), e); } catch (AmazonServiceException e) { throw new TrinoException(HIVE_METASTORE_ERROR, e); @@ -711,7 +711,7 @@ public void setTableOwner(String databaseName, String tableName, HivePrincipal p .withTableInput(newTableInput))); } catch (EntityNotFoundException e) { - throw new TableNotFoundException(new SchemaTableName(databaseName, tableName)); + throw new TableNotFoundException(new SchemaTableName(databaseName, tableName), e); } catch (AmazonServiceException e) { throw new TrinoException(HIVE_METASTORE_ERROR, e); @@ -1126,7 +1126,7 @@ public void alterPartition(String databaseName, String tableName, PartitionWithS partition.getStatistics().getColumnStatistics()); } catch (EntityNotFoundException e) { - throw new PartitionNotFoundException(new SchemaTableName(databaseName, tableName), partition.getPartition().getValues()); + throw new PartitionNotFoundException(new SchemaTableName(databaseName, tableName), partition.getPartition().getValues(), e); } catch (AmazonServiceException e) { throw new TrinoException(HIVE_METASTORE_ERROR, e); @@ -1264,10 +1264,10 @@ public void createFunction(String databaseName, String functionName, LanguageFun .withFunctionInput(functionInput))); } catch (AlreadyExistsException e) { - throw new TrinoException(ALREADY_EXISTS, "Function already exists"); + throw new TrinoException(ALREADY_EXISTS, "Function already exists", e); } catch (EntityNotFoundException e) { - throw new SchemaNotFoundException(databaseName); + throw new SchemaNotFoundException(databaseName, e); } catch (AmazonServiceException e) { throw new TrinoException(HIVE_METASTORE_ERROR, e); @@ -1300,7 +1300,7 @@ public void dropFunction(String databaseName, String functionName, String signat .withFunctionName(metastoreFunctionName(functionName, signatureToken)))); } catch (EntityNotFoundException e) { - throw new TrinoException(FUNCTION_NOT_FOUND, "Function not found"); + throw new TrinoException(FUNCTION_NOT_FOUND, "Function not found", e); } catch (AmazonServiceException e) { throw new TrinoException(HIVE_METASTORE_ERROR, e); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftHiveMetastore.java index dcd1ada4d8be..78342b38b327 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftHiveMetastore.java @@ -366,7 +366,7 @@ private Map getTableColumnStatistics(String databa })); } catch (NoSuchObjectException e) { - throw new TableNotFoundException(new SchemaTableName(databaseName, tableName)); + throw new TableNotFoundException(new SchemaTableName(databaseName, tableName), e); } catch (TException e) { throw new TrinoException(HIVE_METASTORE_ERROR, e); @@ -480,7 +480,7 @@ private Map> getMetastorePartitionColumnStatis })); } catch (NoSuchObjectException e) { - throw new TableNotFoundException(new SchemaTableName(databaseName, tableName)); + throw new TableNotFoundException(new SchemaTableName(databaseName, tableName), e); } catch (TException e) { throw new TrinoException(HIVE_METASTORE_ERROR, e); @@ -561,7 +561,7 @@ private void setTableColumnStatistics(String databaseName, String tableName, Lis })); } catch (NoSuchObjectException e) { - throw new TableNotFoundException(new SchemaTableName(databaseName, tableName)); + throw new TableNotFoundException(new SchemaTableName(databaseName, tableName), e); } catch (TException e) { throw new TrinoException(HIVE_METASTORE_ERROR, e); @@ -585,7 +585,7 @@ private void deleteTableColumnStatistics(String databaseName, String tableName, })); } catch (NoSuchObjectException e) { - throw new TableNotFoundException(new SchemaTableName(databaseName, tableName)); + throw new TableNotFoundException(new SchemaTableName(databaseName, tableName), e); } catch (TException e) { throw new TrinoException(HIVE_METASTORE_ERROR, e); @@ -655,7 +655,7 @@ private void setPartitionColumnStatistics(String databaseName, String tableName, })); } catch (NoSuchObjectException e) { - throw new TableNotFoundException(new SchemaTableName(databaseName, tableName)); + throw new TableNotFoundException(new SchemaTableName(databaseName, tableName), e); } catch (TException e) { throw new TrinoException(HIVE_METASTORE_ERROR, e); @@ -679,7 +679,7 @@ private void deletePartitionColumnStatistics(String databaseName, String tableNa })); } catch (NoSuchObjectException e) { - throw new TableNotFoundException(new SchemaTableName(databaseName, tableName)); + throw new TableNotFoundException(new SchemaTableName(databaseName, tableName), e); } catch (TException e) { throw new TrinoException(HIVE_METASTORE_ERROR, e); @@ -953,7 +953,7 @@ public void createDatabase(Database database) })); } catch (AlreadyExistsException e) { - throw new SchemaAlreadyExistsException(database.getName()); + throw new SchemaAlreadyExistsException(database.getName(), e); } catch (TException e) { throw new TrinoException(HIVE_METASTORE_ERROR, e); @@ -978,7 +978,7 @@ public void dropDatabase(String databaseName, boolean deleteData) })); } catch (NoSuchObjectException e) { - throw new SchemaNotFoundException(databaseName); + throw new SchemaNotFoundException(databaseName, e); } catch (TException e) { throw new TrinoException(HIVE_METASTORE_ERROR, e); @@ -1006,7 +1006,7 @@ public void alterDatabase(String databaseName, Database database) })); } catch (NoSuchObjectException e) { - throw new SchemaNotFoundException(databaseName); + throw new SchemaNotFoundException(databaseName, e); } catch (TException e) { throw new TrinoException(HIVE_METASTORE_ERROR, e); @@ -1032,10 +1032,10 @@ public void createTable(Table table) })); } catch (AlreadyExistsException e) { - throw new TableAlreadyExistsException(new SchemaTableName(table.getDbName(), table.getTableName())); + throw new TableAlreadyExistsException(new SchemaTableName(table.getDbName(), table.getTableName()), e); } catch (NoSuchObjectException e) { - throw new SchemaNotFoundException(table.getDbName()); + throw new SchemaNotFoundException(table.getDbName(), e); } catch (InvalidObjectException e) { boolean databaseMissing; @@ -1047,7 +1047,7 @@ public void createTable(Table table) databaseMissing = false; // we don't know, assume it exists for the purpose of error reporting } if (databaseMissing) { - throw new SchemaNotFoundException(table.getDbName()); + throw new SchemaNotFoundException(table.getDbName(), e); } throw new TrinoException(HIVE_METASTORE_ERROR, e); } @@ -1079,7 +1079,7 @@ public void dropTable(String databaseName, String tableName, boolean deleteData) })); } catch (NoSuchObjectException e) { - throw new TableNotFoundException(new SchemaTableName(databaseName, tableName)); + throw new TableNotFoundException(new SchemaTableName(databaseName, tableName), e); } catch (TException e) { throw new TrinoException(HIVE_METASTORE_ERROR, e); diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java index e9d07c5ba332..995bdb127587 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java @@ -267,7 +267,7 @@ public void dropNamespace(ConnectorSession session, String namespace) glueClient.deleteDatabase(new DeleteDatabaseRequest().withName(namespace))); } catch (EntityNotFoundException e) { - throw new SchemaNotFoundException(namespace); + throw new SchemaNotFoundException(namespace, e); } catch (AmazonServiceException e) { throw new TrinoException(ICEBERG_CATALOG_ERROR, e); @@ -291,7 +291,7 @@ public Map loadNamespaceMetadata(ConnectorSession session, Strin return metadata.buildOrThrow(); } catch (EntityNotFoundException e) { - throw new SchemaNotFoundException(namespace); + throw new SchemaNotFoundException(namespace, e); } catch (AmazonServiceException e) { throw new TrinoException(ICEBERG_CATALOG_ERROR, e); @@ -316,7 +316,7 @@ public void createNamespace(ConnectorSession session, String namespace, Map Date: Mon, 13 Nov 2023 18:47:47 +0900 Subject: [PATCH 217/587] Remove testCreateTableIfNotExists in BigQuery It's covered by BaseConnectorTest#testCreateTable. --- .../trino/plugin/bigquery/BaseBigQueryConnectorTest.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java index 15772be3028b..f6e3f9d0a224 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java @@ -181,14 +181,6 @@ public void testCreateTableAlreadyExists() } } - @Test - public void testCreateTableIfNotExists() - { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_create_table_if_not_exists", "(col1 int)")) { - assertUpdate("CREATE TABLE IF NOT EXISTS " + table.getName() + "(col1 int)"); - } - } - @Test public void testEmptyProjectionTable() { From df4c8eda75c1ec384e6ea6809dab44177d2872f2 Mon Sep 17 00:00:00 2001 From: Slawomir Pajak Date: Tue, 7 Nov 2023 09:48:51 +0100 Subject: [PATCH 218/587] Ignore internal TableAlreadyExistsException in Iceberg CREATE TABLE --- .../io/trino/plugin/iceberg/IcebergUtil.java | 2 +- .../hms/AbstractMetastoreTableOperations.java | 25 +++- .../TestIcebergCreateTableInternalRetry.java | 109 ++++++++++++++++++ 3 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergCreateTableInternalRetry.java diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergUtil.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergUtil.java index 10815d251d79..62696048fc2d 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergUtil.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergUtil.java @@ -171,8 +171,8 @@ public final class IcebergUtil public static final String METADATA_FOLDER_NAME = "metadata"; public static final String METADATA_FILE_EXTENSION = ".metadata.json"; + public static final String TRINO_QUERY_ID_NAME = "trino_query_id"; private static final Pattern SIMPLE_NAME = Pattern.compile("[a-z][a-z0-9]*"); - static final String TRINO_QUERY_ID_NAME = "trino_query_id"; // Metadata file name examples // - 00001-409702ba-4735-4645-8f14-09537cc0b2c8.metadata.json // - 00001-409702ba-4735-4645-8f14-09537cc0b2c8.gz.metadata.json (https://github.com/apache/iceberg/blob/ab398a0d5ff195f763f8c7a4358ac98fa38a8de7/core/src/main/java/org/apache/iceberg/TableMetadataParser.java#L141) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/AbstractMetastoreTableOperations.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/AbstractMetastoreTableOperations.java index 9769819b3d3b..c9458be31721 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/AbstractMetastoreTableOperations.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/AbstractMetastoreTableOperations.java @@ -40,6 +40,7 @@ import static io.trino.plugin.iceberg.IcebergErrorCode.ICEBERG_INVALID_METADATA; import static io.trino.plugin.iceberg.IcebergTableName.isMaterializedViewStorage; import static io.trino.plugin.iceberg.IcebergTableName.tableNameFrom; +import static io.trino.plugin.iceberg.IcebergUtil.TRINO_QUERY_ID_NAME; import static java.lang.String.format; import static java.util.Locale.ENGLISH; import static java.util.Objects.requireNonNull; @@ -124,12 +125,32 @@ protected final void commitNewTable(TableMetadata metadata) try { metastore.createTable(table, privileges); } - catch (SchemaNotFoundException - | TableAlreadyExistsException e) { + catch (SchemaNotFoundException e) { // clean up metadata files corresponding to the current transaction fileIo.deleteFile(newMetadataLocation); throw e; } + catch (TableAlreadyExistsException e) { + // Ignore TableAlreadyExistsException when table looks like created by us. + // This may happen when an actually successful metastore create call is retried + // e.g. because of a timeout on our side. + refreshFromMetadataLocation(getRefreshedLocation(true)); + if (!isCreatedBy(this.currentMetadata, session.getQueryId())) { + fileIo.deleteFile(newMetadataLocation); + throw e; + } + } + } + + public static boolean isCreatedBy(TableMetadata existingTableMetadata, String queryId) + { + Optional tableQueryId = getQueryId(existingTableMetadata); + return tableQueryId.isPresent() && tableQueryId.get().equals(queryId); + } + + private static Optional getQueryId(TableMetadata tableMetadata) + { + return Optional.ofNullable(tableMetadata.currentSnapshot().summary().get(TRINO_QUERY_ID_NAME)); } protected Table.Builder updateMetastoreTable(Table.Builder builder, TableMetadata metadata, String metadataLocation, Optional previousMetadataLocation) diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergCreateTableInternalRetry.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergCreateTableInternalRetry.java new file mode 100644 index 000000000000..d8ebb4aed194 --- /dev/null +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergCreateTableInternalRetry.java @@ -0,0 +1,109 @@ +/* + * 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 io.trino.plugin.iceberg; + +import com.google.common.collect.ImmutableMap; +import io.trino.Session; +import io.trino.plugin.hive.NodeVersion; +import io.trino.plugin.hive.TableAlreadyExistsException; +import io.trino.plugin.hive.metastore.HiveMetastore; +import io.trino.plugin.hive.metastore.HiveMetastoreConfig; +import io.trino.plugin.hive.metastore.PrincipalPrivileges; +import io.trino.plugin.hive.metastore.Table; +import io.trino.plugin.hive.metastore.file.FileHiveMetastore; +import io.trino.plugin.hive.metastore.file.FileHiveMetastoreConfig; +import io.trino.plugin.iceberg.catalog.file.TestingIcebergFileMetastoreCatalogModule; +import io.trino.testing.AbstractTestQueryFramework; +import io.trino.testing.DistributedQueryRunner; +import io.trino.testing.QueryRunner; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +import java.io.File; +import java.io.IOException; +import java.util.Optional; + +import static com.google.common.io.MoreFiles.deleteRecursively; +import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; +import static com.google.inject.util.Modules.EMPTY_MODULE; +import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; +import static io.trino.plugin.iceberg.IcebergQueryRunner.ICEBERG_CATALOG; +import static io.trino.testing.TestingSession.testSessionBuilder; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; + +@TestInstance(PER_CLASS) +public class TestIcebergCreateTableInternalRetry + extends AbstractTestQueryFramework +{ + private static final String SCHEMA_NAME = "iceberg_internal_retry_schema"; + private File metastoreDir; + + @Override + protected QueryRunner createQueryRunner() + throws Exception + { + Session session = testSessionBuilder() + .setCatalog(ICEBERG_CATALOG) + .setSchema(SCHEMA_NAME) + .build(); + DistributedQueryRunner queryRunner = DistributedQueryRunner.builder(session).build(); + metastoreDir = queryRunner.getCoordinator().getBaseDataDir().resolve("test_iceberg_table_smoke_test").toFile(); + this.metastoreDir.deleteOnExit(); + HiveMetastore metastore = new FileHiveMetastore( + new NodeVersion("testversion"), + HDFS_FILE_SYSTEM_FACTORY, + new HiveMetastoreConfig().isHideDeltaLakeTables(), + new FileHiveMetastoreConfig() + .setCatalogDirectory(metastoreDir.toURI().toString()) + .setMetastoreUser("test")) + { + @Override + public synchronized void createTable(Table table, PrincipalPrivileges principalPrivileges) + { + // Simulate retry mechanism with timeout failure of ThriftHiveMetastore. + // 1. createTable correctly create table but timeout is triggered + // 2. Retry to createTable throws TableAlreadyExistsException + super.createTable(table, principalPrivileges); + throw new TableAlreadyExistsException(table.getSchemaTableName()); + } + }; + + queryRunner.installPlugin(new TestingIcebergPlugin(Optional.of(new TestingIcebergFileMetastoreCatalogModule(metastore)), Optional.empty(), EMPTY_MODULE)); + queryRunner.createCatalog(ICEBERG_CATALOG, "iceberg", ImmutableMap.of("iceberg.register-table-procedure.enabled", "true")); + queryRunner.execute("CREATE SCHEMA " + SCHEMA_NAME); + return queryRunner; + } + + @AfterAll + public void tearDown() + throws IOException + { + deleteRecursively(metastoreDir.toPath(), ALLOW_INSECURE); + } + + @Test + public void testCreateTableInternalRetry() + { + assertQuerySucceeds("CREATE TABLE test_ct_internal_retry(a int)"); + assertQuery("SHOW TABLES LIKE 'test_ct_internal_retry'", "VALUES 'test_ct_internal_retry'"); + } + + @Test + public void testCreateTableAsSelectInternalRetry() + { + assertQuerySucceeds("CREATE TABLE test_ctas_internal_retry AS SELECT 1 a"); + assertQuery("SHOW TABLES LIKE 'test_ctas_internal_retry'", "VALUES 'test_ctas_internal_retry'"); + } +} From 856563a55aa05187c005af21800f94e7695a6b7b Mon Sep 17 00:00:00 2001 From: Slawomir Pajak Date: Tue, 7 Nov 2023 09:50:05 +0100 Subject: [PATCH 219/587] Ignore internal TableAlreadyExistsException in register_table procedure --- .../iceberg/catalog/hms/TrinoHiveCatalog.java | 26 ++++++++++- .../TestIcebergCreateTableInternalRetry.java | 46 +++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java index 394568b4e67f..c3bcde1a1b73 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java @@ -22,6 +22,7 @@ import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.plugin.base.CatalogName; import io.trino.plugin.hive.HiveSchemaProperties; +import io.trino.plugin.hive.TableAlreadyExistsException; import io.trino.plugin.hive.TrinoViewHiveMetastore; import io.trino.plugin.hive.metastore.Column; import io.trino.plugin.hive.metastore.Database; @@ -331,11 +332,34 @@ public void registerTable(ConnectorSession session, SchemaTableName schemaTableN .withStorage(storage -> storage.setStorageFormat(ICEBERG_METASTORE_STORAGE_FORMAT)) // This is a must-have property for the EXTERNAL_TABLE table type .setParameter("EXTERNAL", "TRUE") + .setParameter(PRESTO_QUERY_ID_NAME, session.getQueryId()) .setParameter(TABLE_TYPE_PROP, ICEBERG_TABLE_TYPE_VALUE.toUpperCase(ENGLISH)) .setParameter(METADATA_LOCATION_PROP, tableMetadata.metadataFileLocation()); PrincipalPrivileges privileges = owner.map(MetastoreUtil::buildInitialPrivilegeSet).orElse(NO_PRIVILEGES); - metastore.createTable(builder.build(), privileges); + try { + metastore.createTable(builder.build(), privileges); + } + catch (TableAlreadyExistsException e) { + // Ignore TableAlreadyExistsException when table looks like created by us. + // This may happen when an actually successful metastore create call is retried + // e.g. because of a timeout on our side. + Optional existingTable = metastore.getTable(schemaTableName.getSchemaName(), schemaTableName.getTableName()); + if (existingTable.isEmpty() || !isCreatedBy(existingTable.get(), session.getQueryId())) { + throw e; + } + } + } + + public static boolean isCreatedBy(io.trino.plugin.hive.metastore.Table table, String queryId) + { + Optional tableQueryId = getQueryId(table); + return tableQueryId.isPresent() && tableQueryId.get().equals(queryId); + } + + private static Optional getQueryId(io.trino.plugin.hive.metastore.Table table) + { + return Optional.ofNullable(table.getParameters().get(PRESTO_QUERY_ID_NAME)); } @Override diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergCreateTableInternalRetry.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergCreateTableInternalRetry.java index d8ebb4aed194..fce50616513a 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergCreateTableInternalRetry.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergCreateTableInternalRetry.java @@ -34,10 +34,14 @@ import java.io.File; import java.io.IOException; import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import static com.google.common.base.Verify.verify; import static com.google.common.io.MoreFiles.deleteRecursively; import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; import static com.google.inject.util.Modules.EMPTY_MODULE; +import static io.trino.plugin.hive.HiveMetadata.PRESTO_QUERY_ID_NAME; import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; import static io.trino.plugin.iceberg.IcebergQueryRunner.ICEBERG_CATALOG; import static io.trino.testing.TestingSession.testSessionBuilder; @@ -72,6 +76,12 @@ protected QueryRunner createQueryRunner() @Override public synchronized void createTable(Table table, PrincipalPrivileges principalPrivileges) { + if (table.getTableName().startsWith("test_different_session")) { + // By modifying query id test simulates that table was created from different session. + table = Table.builder(table) + .setParameters(ImmutableMap.of(PRESTO_QUERY_ID_NAME, "new_query_id")) + .build(); + } // Simulate retry mechanism with timeout failure of ThriftHiveMetastore. // 1. createTable correctly create table but timeout is triggered // 2. Retry to createTable throws TableAlreadyExistsException @@ -106,4 +116,40 @@ public void testCreateTableAsSelectInternalRetry() assertQuerySucceeds("CREATE TABLE test_ctas_internal_retry AS SELECT 1 a"); assertQuery("SHOW TABLES LIKE 'test_ctas_internal_retry'", "VALUES 'test_ctas_internal_retry'"); } + + @Test + public void testRegisterTableInternalRetry() + { + assertQuerySucceeds("CREATE TABLE test_register_table_internal_retry AS SELECT 1 a"); + String tableLocation = getTableLocation("test_register_table_internal_retry"); + assertUpdate("CALL system.unregister_table(current_schema, 'test_register_table_internal_retry')"); + + assertQuerySucceeds("CALL system.register_table(current_schema, 'test_register_table_internal_retry', '" + tableLocation + "')"); + assertQuery("SHOW TABLES LIKE 'test_register_table_internal_retry'", "VALUES 'test_register_table_internal_retry'"); + } + + @Test + public void testRegisterTableFailureWithDifferentSession() + { + assertQuerySucceeds("CREATE TABLE test_register_table_failure AS SELECT 1 a"); + String tableLocation = getTableLocation("test_register_table_failure"); + assertUpdate("CALL system.unregister_table(current_schema, 'test_register_table_failure')"); + + assertQueryFails( + "CALL system.register_table(current_schema, 'test_different_session_register_table_failure', '" + tableLocation + "')", + "Table already exists: .*"); + assertQuery("SHOW TABLES LIKE 'test_different_session_register_table_failure'", "VALUES 'test_different_session_register_table_failure'"); + } + + private String getTableLocation(String tableName) + { + Pattern locationPattern = Pattern.compile(".*location = '(.*?)'.*", Pattern.DOTALL); + Matcher m = locationPattern.matcher((String) computeActual("SHOW CREATE TABLE " + tableName).getOnlyValue()); + if (m.find()) { + String location = m.group(1); + verify(!m.find(), "Unexpected second match"); + return location; + } + throw new IllegalStateException("Location not found in SHOW CREATE TABLE result"); + } } From 1434317866d8c5d068bdda4d39bd1fc17120d5ab Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Tue, 7 Nov 2023 11:20:30 -0800 Subject: [PATCH 220/587] Do not modify properties argument in AvroHiveFileUtils The determineSchemaOrThrowException was writing the schema back in the properties argument. This behavior is unexpected an unneeded by the current code --- .../java/io/trino/plugin/hive/avro/AvroHiveFileUtils.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroHiveFileUtils.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroHiveFileUtils.java index 2d81ff5d2921..3a08f13d01d4 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroHiveFileUtils.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroHiveFileUtils.java @@ -94,9 +94,7 @@ public static Schema determineSchemaOrThrowException(TrinoFileSystem fileSystem, throw new IOException("Unable to read avro schema file from given path: " + schemaURL, e); } } - Schema schema = getSchemaFromProperties(properties); - properties.setProperty(SCHEMA_LITERAL, schema.toString()); - return schema; + return getSchemaFromProperties(properties); } private static Schema getSchemaFromProperties(Properties properties) From f7ad16a70df7964a485fe5e2940b3e7aebfd59d0 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Sat, 11 Nov 2023 12:45:40 -0800 Subject: [PATCH 221/587] Use Hive constants instead of strings where possible --- .../plugin/hive/metastore/MetastoreUtil.java | 3 ++- .../hive/TestBackgroundHiveSplitLoader.java | 25 +++++++++++-------- .../plugin/hive/TestHiveFileFormats.java | 14 ++++++----- .../trino/plugin/hive/TestHivePageSink.java | 6 +++-- .../hive/TestOrcPageSourceMemoryTracking.java | 11 +++++--- .../hive/metastore/TestMetastoreUtil.java | 21 ++++++++++------ .../plugin/hive/orc/TestOrcPredicates.java | 6 +++-- .../plugin/hive/parquet/ParquetTester.java | 6 +++-- .../parquet/TestParquetDecimalScaling.java | 6 +++-- .../product/hive/TestHiveCreateTable.java | 7 +++--- 10 files changed, 65 insertions(+), 40 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/MetastoreUtil.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/MetastoreUtil.java index 405796542872..b32d0eab64da 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/MetastoreUtil.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/MetastoreUtil.java @@ -75,6 +75,7 @@ import static io.trino.plugin.hive.metastore.thrift.ThriftMetastoreUtil.NUM_ROWS; import static io.trino.plugin.hive.util.HiveClassNames.AVRO_SERDE_CLASS; import static io.trino.plugin.hive.util.HiveUtil.makePartName; +import static io.trino.plugin.hive.util.SerdeConstants.LIST_COLUMN_COMMENTS; import static io.trino.plugin.hive.util.SerdeConstants.SERIALIZATION_LIB; import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; import static io.trino.spi.predicate.TupleDomain.withColumnDomains; @@ -175,7 +176,7 @@ private static Properties getHiveSchema( String columnTypes = columnTypeBuilder.toString(); schema.setProperty(META_TABLE_COLUMNS, columnNames); schema.setProperty(META_TABLE_COLUMN_TYPES, columnTypes); - schema.setProperty("columns.comments", columnCommentBuilder.toString()); + schema.setProperty(LIST_COLUMN_COMMENTS, columnCommentBuilder.toString()); StringBuilder partString = new StringBuilder(); String partStringSep = ""; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java index 0d4191f21fc9..6b0e8067c432 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java @@ -82,6 +82,7 @@ import static io.airlift.units.DataSize.Unit.GIGABYTE; import static io.airlift.units.DataSize.Unit.KILOBYTE; import static io.airlift.units.DataSize.Unit.MEGABYTE; +import static io.trino.hive.thrift.metastore.hive_metastoreConstants.FILE_INPUT_FORMAT; import static io.trino.plugin.hive.BackgroundHiveSplitLoader.BucketSplitInfo.createBucketSplitInfo; import static io.trino.plugin.hive.BackgroundHiveSplitLoader.getBucketNumber; import static io.trino.plugin.hive.BackgroundHiveSplitLoader.hasAttemptId; @@ -91,6 +92,7 @@ import static io.trino.plugin.hive.HiveStorageFormat.AVRO; import static io.trino.plugin.hive.HiveStorageFormat.CSV; import static io.trino.plugin.hive.HiveStorageFormat.ORC; +import static io.trino.plugin.hive.HiveTableProperties.TRANSACTIONAL; import static io.trino.plugin.hive.HiveTestUtils.SESSION; import static io.trino.plugin.hive.HiveTestUtils.getHiveSession; import static io.trino.plugin.hive.HiveTimestampPrecision.DEFAULT_PRECISION; @@ -100,6 +102,8 @@ import static io.trino.plugin.hive.util.HiveBucketing.BucketingVersion.BUCKETING_V1; import static io.trino.plugin.hive.util.HiveClassNames.SYMLINK_TEXT_INPUT_FORMAT_CLASS; import static io.trino.plugin.hive.util.HiveUtil.getRegularColumnHandles; +import static io.trino.plugin.hive.util.SerdeConstants.FOOTER_COUNT; +import static io.trino.plugin.hive.util.SerdeConstants.HEADER_COUNT; import static io.trino.plugin.hive.util.SerdeConstants.SERIALIZATION_LIB; import static io.trino.spi.predicate.TupleDomain.withColumnDomains; import static io.trino.spi.type.IntegerType.INTEGER; @@ -110,7 +114,6 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.FILE_INPUT_FORMAT; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; @@ -165,10 +168,10 @@ public void testCsv() { FileEntry file = new FileEntry(LOCATION, DataSize.of(2, GIGABYTE).toBytes(), Instant.now(), Optional.empty()); assertCsvSplitCount(file, Map.of(), 33); - assertCsvSplitCount(file, Map.of("skip.header.line.count", "1"), 33); - assertCsvSplitCount(file, Map.of("skip.header.line.count", "2"), 1); - assertCsvSplitCount(file, Map.of("skip.footer.line.count", "1"), 1); - assertCsvSplitCount(file, Map.of("skip.header.line.count", "1", "skip.footer.line.count", "1"), 1); + assertCsvSplitCount(file, Map.of(HEADER_COUNT, "1"), 33); + assertCsvSplitCount(file, Map.of(HEADER_COUNT, "2"), 1); + assertCsvSplitCount(file, Map.of(HEADER_COUNT, "1"), 1); + assertCsvSplitCount(file, Map.of(HEADER_COUNT, "1", FOOTER_COUNT, "1"), 1); } private void assertCsvSplitCount(FileEntry file, Map tableProperties, int expectedSplitCount) @@ -531,7 +534,7 @@ public void testSplitsGenerationWithAbortedTransactions() List.of(), Optional.empty(), Map.of( - "transactional", "true", + TRANSACTIONAL, "true", "transactional_properties", "insert_only")); List fileLocations = List.of( @@ -577,7 +580,7 @@ public void testFullAcidTableWithOriginalFiles() tableLocation.toString(), List.of(), Optional.empty(), - Map.of("transactional", "true")); + Map.of(TRANSACTIONAL, "true")); Location originalFile = tableLocation.appendPath("000000_1"); try (OutputStream outputStream = fileSystem.newOutputFile(originalFile).create()) { @@ -620,7 +623,7 @@ public void testVersionValidationNoOrcAcidVersionFile() tableLocation.toString(), List.of(), Optional.empty(), - Map.of("transactional", "true")); + Map.of(TRANSACTIONAL, "true")); List fileLocations = List.of( tableLocation.appendPath("000000_1"), @@ -665,7 +668,7 @@ public void testVersionValidationOrcAcidVersionFileHasVersion2() tableLocation.toString(), List.of(), Optional.empty(), - Map.of("transactional", "true")); + Map.of(TRANSACTIONAL, "true")); List fileLocations = List.of( tableLocation.appendPath("000000_1"), // _orc_acid_version does not exist, so it's assumed to be "ORC ACID version 0" @@ -708,7 +711,7 @@ public void testVersionValidationOrcAcidVersionFileHasVersion1() tableLocation.toString(), List.of(), Optional.empty(), - Map.of("transactional", "true")); + Map.of(TRANSACTIONAL, "true")); List fileLocations = List.of( tableLocation.appendPath("000000_1"), @@ -818,7 +821,7 @@ public void testBuildManifestFileIteratorNestedDirectory() { CachingDirectoryLister directoryLister = new CachingDirectoryLister(new Duration(5, TimeUnit.MINUTES), DataSize.of(100, KILOBYTE), List.of()); Properties schema = new Properties(); - schema.setProperty("file.inputformat", SYMLINK_TEXT_INPUT_FORMAT_CLASS); + schema.setProperty(FILE_INPUT_FORMAT, SYMLINK_TEXT_INPUT_FORMAT_CLASS); schema.setProperty(SERIALIZATION_LIB, AVRO.getSerde()); Location filePath = Location.of("memory:///db_name/table_name/file1"); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java index f84a458cd221..902291918503 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java @@ -152,6 +152,8 @@ import static io.trino.plugin.hive.HiveTestUtils.getHiveSession; import static io.trino.plugin.hive.HiveTestUtils.mapType; import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; +import static io.trino.plugin.hive.util.SerdeConstants.LIST_COLUMNS; +import static io.trino.plugin.hive.util.SerdeConstants.LIST_COLUMN_TYPES; import static io.trino.plugin.hive.util.SerdeConstants.SERIALIZATION_LIB; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.BooleanType.BOOLEAN; @@ -956,8 +958,8 @@ private static void testPageSourceFactory( } } - splitProperties.setProperty("columns", String.join(",", splitPropertiesColumnNames.build())); - splitProperties.setProperty("columns.types", String.join(",", splitPropertiesColumnTypes.build())); + splitProperties.setProperty(LIST_COLUMNS, String.join(",", splitPropertiesColumnNames.build())); + splitProperties.setProperty(LIST_COLUMN_TYPES, String.join(",", splitPropertiesColumnTypes.build())); List partitionKeys = testReadColumns.stream() .filter(TestColumn::partitionKey) @@ -1319,13 +1321,13 @@ private static void createTestFileTrino( Properties tableProperties = new Properties(); tableProperties.setProperty( - "columns", + LIST_COLUMNS, testColumns.stream() .map(TestColumn::name) .collect(Collectors.joining(","))); tableProperties.setProperty( - "columns.types", + LIST_COLUMN_TYPES, testColumns.stream() .map(TestColumn::type) .map(HiveType::toHiveType) @@ -1470,8 +1472,8 @@ private static void createTestFileHive( .collect(toImmutableList()); Properties tableProperties = new Properties(); - tableProperties.setProperty("columns", testColumns.stream().map(TestColumn::name).collect(Collectors.joining(","))); - tableProperties.setProperty("columns.types", testColumns.stream().map(testColumn -> HiveType.toHiveType(testColumn.type()).toString()).collect(Collectors.joining(","))); + tableProperties.setProperty(LIST_COLUMNS, testColumns.stream().map(TestColumn::name).collect(Collectors.joining(","))); + tableProperties.setProperty(LIST_COLUMN_TYPES, testColumns.stream().map(testColumn -> HiveType.toHiveType(testColumn.type()).toString()).collect(Collectors.joining(","))); serializer.initialize(new Configuration(false), tableProperties); JobConf jobConf = new JobConf(false); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java index 2c4d60a3da93..b530e08accd6 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java @@ -75,6 +75,8 @@ import static io.trino.plugin.hive.LocationHandle.WriteMode.DIRECT_TO_TARGET_NEW_DIRECTORY; import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; import static io.trino.plugin.hive.metastore.file.TestingFileHiveMetastore.createTestingFileHiveMetastore; +import static io.trino.plugin.hive.util.SerdeConstants.LIST_COLUMNS; +import static io.trino.plugin.hive.util.SerdeConstants.LIST_COLUMN_TYPES; import static io.trino.plugin.hive.util.SerdeConstants.SERIALIZATION_LIB; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.DateType.DATE; @@ -243,8 +245,8 @@ private static ConnectorPageSource createPageSource(TrinoFileSystemFactory fileS Properties splitProperties = new Properties(); splitProperties.setProperty(FILE_INPUT_FORMAT, config.getHiveStorageFormat().getInputFormat()); splitProperties.setProperty(SERIALIZATION_LIB, config.getHiveStorageFormat().getSerde()); - splitProperties.setProperty("columns", Joiner.on(',').join(getColumnHandles().stream().map(HiveColumnHandle::getName).collect(toImmutableList()))); - splitProperties.setProperty("columns.types", Joiner.on(',').join(getColumnHandles().stream().map(HiveColumnHandle::getHiveType).map(hiveType -> hiveType.getHiveTypeName().toString()).collect(toImmutableList()))); + splitProperties.setProperty(LIST_COLUMNS, Joiner.on(',').join(getColumnHandles().stream().map(HiveColumnHandle::getName).collect(toImmutableList()))); + splitProperties.setProperty(LIST_COLUMN_TYPES, Joiner.on(',').join(getColumnHandles().stream().map(HiveColumnHandle::getHiveType).map(hiveType -> hiveType.getHiveTypeName().toString()).collect(toImmutableList()))); HiveSplit split = new HiveSplit( "", location.toString(), diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOrcPageSourceMemoryTracking.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOrcPageSourceMemoryTracking.java index eefff9d7c6a0..399318cfcb80 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOrcPageSourceMemoryTracking.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOrcPageSourceMemoryTracking.java @@ -106,6 +106,9 @@ import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; import static io.trino.plugin.hive.HiveTestUtils.SESSION; import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; +import static io.trino.plugin.hive.util.SerdeConstants.LIST_COLUMNS; +import static io.trino.plugin.hive.util.SerdeConstants.LIST_COLUMN_COMMENTS; +import static io.trino.plugin.hive.util.SerdeConstants.LIST_COLUMN_TYPES; import static io.trino.plugin.hive.util.SerdeConstants.SERIALIZATION_LIB; import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; import static io.trino.sql.relational.Expressions.field; @@ -507,11 +510,11 @@ public TestPreparer(String tempFilePath, List testColumns, int numRo { OrcSerde serde = new OrcSerde(); schema = new Properties(); - schema.setProperty("columns", + schema.setProperty(LIST_COLUMNS, testColumns.stream() .map(TestColumn::getName) .collect(Collectors.joining(","))); - schema.setProperty("columns.types", + schema.setProperty(LIST_COLUMN_TYPES, testColumns.stream() .map(TestColumn::getType) .collect(Collectors.joining(","))); @@ -659,13 +662,13 @@ private static FileSplit createTestFile( Properties tableProperties = new Properties(); tableProperties.setProperty( - "columns", + LIST_COLUMNS, testColumns.stream() .map(TestColumn::getName) .collect(Collectors.joining(","))); tableProperties.setProperty( - "columns.types", + LIST_COLUMN_COMMENTS, testColumns.stream() .map(TestColumn::getType) .collect(Collectors.joining(","))); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestMetastoreUtil.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestMetastoreUtil.java index bf0a31cd1166..62d1cbe65a61 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestMetastoreUtil.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestMetastoreUtil.java @@ -36,11 +36,18 @@ import java.util.Properties; import static io.airlift.slice.Slices.utf8Slice; +import static io.trino.hive.thrift.metastore.hive_metastoreConstants.FILE_INPUT_FORMAT; +import static io.trino.hive.thrift.metastore.hive_metastoreConstants.FILE_OUTPUT_FORMAT; import static io.trino.plugin.hive.HiveColumnHandle.ColumnType.PARTITION_KEY; import static io.trino.plugin.hive.HiveColumnHandle.bucketColumnHandle; +import static io.trino.plugin.hive.HiveTableProperties.BUCKET_COUNT_PROPERTY; import static io.trino.plugin.hive.HiveType.HIVE_STRING; import static io.trino.plugin.hive.metastore.MetastoreUtil.computePartitionKeyFilter; import static io.trino.plugin.hive.metastore.PrincipalPrivileges.NO_PRIVILEGES; +import static io.trino.plugin.hive.util.SerdeConstants.LIST_COLUMNS; +import static io.trino.plugin.hive.util.SerdeConstants.LIST_COLUMN_COMMENTS; +import static io.trino.plugin.hive.util.SerdeConstants.LIST_COLUMN_TYPES; +import static io.trino.plugin.hive.util.SerdeConstants.SERIALIZATION_LIB; import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.spi.type.VarcharType.VARCHAR; import static org.assertj.core.api.Assertions.assertThat; @@ -137,13 +144,13 @@ public class TestMetastoreUtil // Properties expected = MetaStoreUtils.getTableMetadata(TEST_TABLE_WITH_UNSUPPORTED_FIELDS); // expected.remove(COLUMN_NAME_DELIMITER); private static final Map TEST_TABLE_METADATA = ImmutableMap.builder() - .put("bucket_count", "100") + .put(BUCKET_COUNT_PROPERTY, "100") .put("bucket_field_name", "col2,col3") - .put("columns", "col1,col2,col3") - .put("columns.comments", "comment1\0\0") - .put("columns.types", "bigint:binary:string") - .put("file.inputformat", "com.facebook.hive.orc.OrcInputFormat") - .put("file.outputformat", "com.facebook.hive.orc.OrcOutputFormat") + .put(LIST_COLUMNS, "col1,col2,col3") + .put(LIST_COLUMN_COMMENTS, "comment1\0\0") + .put(LIST_COLUMN_TYPES, "bigint:binary:string") + .put(FILE_INPUT_FORMAT, "com.facebook.hive.orc.OrcInputFormat") + .put(FILE_OUTPUT_FORMAT, "com.facebook.hive.orc.OrcOutputFormat") .put("k1", "v1") .put("k2", "v2") .put("k3", "v3") @@ -153,7 +160,7 @@ public class TestMetastoreUtil .put("partition_columns.types", "string:string") .put("sdk1", "sdv1") .put("sdk2", "sdv2") - .put("serialization.lib", "com.facebook.hive.orc.OrcSerde") + .put(SERIALIZATION_LIB, "com.facebook.hive.orc.OrcSerde") .buildOrThrow(); @Test diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPredicates.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPredicates.java index cad9c391c0a1..416de5dcce8f 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPredicates.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPredicates.java @@ -61,6 +61,8 @@ import static io.trino.plugin.hive.HiveStorageFormat.ORC; import static io.trino.plugin.hive.HiveTestUtils.getHiveSession; import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; +import static io.trino.plugin.hive.util.SerdeConstants.LIST_COLUMNS; +import static io.trino.plugin.hive.util.SerdeConstants.LIST_COLUMN_COMMENTS; import static io.trino.plugin.hive.util.SerdeConstants.SERIALIZATION_LIB; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.IntegerType.INTEGER; @@ -225,12 +227,12 @@ private static Properties getTableProperties() tableProperties.setProperty(FILE_INPUT_FORMAT, ORC.getInputFormat()); tableProperties.setProperty(SERIALIZATION_LIB, ORC.getSerde()); tableProperties.setProperty( - "columns", + LIST_COLUMNS, COLUMNS.stream() .map(HiveColumnHandle::getName) .collect(Collectors.joining(","))); tableProperties.setProperty( - "columns.types", + LIST_COLUMN_COMMENTS, COLUMNS.stream() .map(HiveColumnHandle::getHiveType) .map(HiveType::toString) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetTester.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetTester.java index 5f3ed87812fe..a21ce4788577 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetTester.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetTester.java @@ -116,6 +116,8 @@ import static io.trino.plugin.hive.HiveTestUtils.getHiveSession; import static io.trino.plugin.hive.parquet.ParquetUtil.createPageSource; import static io.trino.plugin.hive.util.HiveUtil.isStructuralType; +import static io.trino.plugin.hive.util.SerdeConstants.LIST_COLUMNS; +import static io.trino.plugin.hive.util.SerdeConstants.LIST_COLUMN_TYPES; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.BooleanType.BOOLEAN; import static io.trino.spi.type.Chars.truncateToLengthAndTrimSpaces; @@ -692,8 +694,8 @@ public static void writeParquetColumn( public static Properties createTableProperties(List columnNames, List objectInspectors) { Properties orderTableProperties = new Properties(); - orderTableProperties.setProperty("columns", Joiner.on(',').join(columnNames)); - orderTableProperties.setProperty("columns.types", Joiner.on(',').join(transform(objectInspectors, ObjectInspector::getTypeName))); + orderTableProperties.setProperty(LIST_COLUMNS, Joiner.on(',').join(columnNames)); + orderTableProperties.setProperty(LIST_COLUMN_TYPES, Joiner.on(',').join(transform(objectInspectors, ObjectInspector::getTypeName))); return orderTableProperties; } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestParquetDecimalScaling.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestParquetDecimalScaling.java index 2c8c73f72c4b..c8e59fc1189c 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestParquetDecimalScaling.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestParquetDecimalScaling.java @@ -57,6 +57,8 @@ import static com.google.common.collect.Iterables.transform; import static io.trino.plugin.hive.parquet.TestParquetDecimalScaling.ParquetDecimalInsert.maximumValue; import static io.trino.plugin.hive.parquet.TestParquetDecimalScaling.ParquetDecimalInsert.minimumValue; +import static io.trino.plugin.hive.util.SerdeConstants.LIST_COLUMNS; +import static io.trino.plugin.hive.util.SerdeConstants.LIST_COLUMN_COMMENTS; import static io.trino.spi.type.Decimals.overflows; import static io.trino.testing.DataProviders.cartesianProduct; import static io.trino.testing.DataProviders.toDataProvider; @@ -506,8 +508,8 @@ private static void writeParquetDecimalsRecord(Path output, List columnNames, List objectInspectors) { Properties tableProperties = new Properties(); - tableProperties.setProperty("columns", Joiner.on(',').join(columnNames)); - tableProperties.setProperty("columns.types", Joiner.on(',').join(transform(objectInspectors, ObjectInspector::getTypeName))); + tableProperties.setProperty(LIST_COLUMNS, Joiner.on(',').join(columnNames)); + tableProperties.setProperty(LIST_COLUMN_COMMENTS, Joiner.on(',').join(transform(objectInspectors, ObjectInspector::getTypeName))); return tableProperties; } diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCreateTable.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCreateTable.java index 90f0c84113d1..1e75070b1613 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCreateTable.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveCreateTable.java @@ -22,6 +22,7 @@ import java.sql.Statement; import java.util.Optional; +import static io.trino.plugin.hive.HiveTableProperties.TRANSACTIONAL; import static io.trino.tempto.assertions.QueryAssert.Row.row; import static io.trino.tests.product.TestGroups.HDP3_ONLY; import static io.trino.tests.product.TestGroups.PROFILE_SPECIFIC_TESTS; @@ -51,7 +52,7 @@ public void testCreateTable() row(null, null, null), row(-42, "abc", -127), row(9223372036854775807L, "abcdefghijklmnopqrstuvwxyz", 32767)); - assertThat(getTableProperty("test_create_table", "transactional")) + assertThat(getTableProperty("test_create_table", TRANSACTIONAL)) // Hive 3 removes "transactional" table property when it has value "false" .isIn(Optional.empty(), Optional.of("false")); onTrino().executeQuery("DROP TABLE test_create_table"); @@ -74,7 +75,7 @@ public void testCreateTableAsSelect() row(null, null, null), row(-42, "abc", -127), row(9223372036854775807L, "abcdefghijklmnopqrstuvwxyz", 32767)); - assertThat(getTableProperty("test_create_table_as_select", "transactional")) + assertThat(getTableProperty("test_create_table_as_select", TRANSACTIONAL)) // Hive 3 removes "transactional" table property when it has value "false" .isIn(Optional.empty(), Optional.of("false")); onTrino().executeQuery("DROP TABLE test_create_table_as_select"); @@ -85,7 +86,7 @@ public void testVerifyEnvironmentHiveTransactionalByDefault() throws SQLException { onHive().executeQuery("CREATE TABLE test_hive_transactional_by_default(a bigint) STORED AS ORC"); - assertThat(getTableProperty("test_hive_transactional_by_default", "transactional")) + assertThat(getTableProperty("test_hive_transactional_by_default", TRANSACTIONAL)) .contains("true"); onHive().executeQuery("DROP TABLE test_hive_transactional_by_default"); } From da81ee1a2419001a034f8c172c97fa194f07069c Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Sat, 11 Nov 2023 12:16:33 -0800 Subject: [PATCH 222/587] Replace Properties with Map in Hive readers and writers --- .../hive/BackgroundHiveSplitLoader.java | 3 +- .../plugin/hive/HiveFileWriterFactory.java | 4 +- .../io/trino/plugin/hive/HiveMetadata.java | 3 +- .../plugin/hive/HivePageSourceFactory.java | 4 +- .../plugin/hive/HivePageSourceProvider.java | 3 +- .../java/io/trino/plugin/hive/HiveSplit.java | 10 ++--- .../trino/plugin/hive/HiveWriterFactory.java | 18 ++++---- .../trino/plugin/hive/InternalHiveSplit.java | 10 ++--- .../io/trino/plugin/hive/MergeFileWriter.java | 12 ++---- .../plugin/hive/RcFileFileWriterFactory.java | 7 ++- .../io/trino/plugin/hive/acid/AcidSchema.java | 23 +++++----- .../hive/avro/AvroFileWriterFactory.java | 4 +- .../plugin/hive/avro/AvroHiveFileUtils.java | 27 ++++++------ .../hive/avro/AvroPageSourceFactory.java | 3 +- .../hive/line/LineFileWriterFactory.java | 13 +++--- .../hive/line/LinePageSourceFactory.java | 9 ++-- .../hive/line/RegexFileWriterFactory.java | 4 +- .../plugin/hive/metastore/MetastoreUtil.java | 43 +++++++++---------- .../plugin/hive/orc/OrcFileWriterFactory.java | 4 +- .../plugin/hive/orc/OrcPageSourceFactory.java | 14 +++--- .../parquet/ParquetFileWriterFactory.java | 4 +- .../parquet/ParquetPageSourceFactory.java | 9 ++-- .../hive/rcfile/RcFilePageSourceFactory.java | 13 +++--- .../io/trino/plugin/hive/util/HiveUtil.java | 33 +++++++------- .../hive/util/InternalHiveSplitFactory.java | 7 ++- .../hive/TestBackgroundHiveSplitLoader.java | 18 ++++---- .../plugin/hive/TestHiveFileFormats.java | 30 +++++-------- .../trino/plugin/hive/TestHivePageSink.java | 13 +++--- .../io/trino/plugin/hive/TestHiveSplit.java | 9 ++-- .../plugin/hive/TestHiveSplitSource.java | 13 ++---- .../TestNodeLocalDynamicSplitPruning.java | 8 ++-- .../hive/TestOrcPageSourceMemoryTracking.java | 21 ++++----- .../hive/avro/TestAvroSchemaGeneration.java | 36 +++++++++++----- .../hive/metastore/TestMetastoreUtil.java | 9 ++-- .../hive/orc/TestOrcPageSourceFactory.java | 15 +++---- .../plugin/hive/orc/TestOrcPredicates.java | 27 ++++-------- .../plugin/hive/orc/TestOrcWriterOptions.java | 22 +++++----- .../plugin/hive/parquet/ParquetUtil.java | 6 +-- .../parquet/TestParquetDecimalScaling.java | 4 +- 39 files changed, 238 insertions(+), 277 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/BackgroundHiveSplitLoader.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/BackgroundHiveSplitLoader.java index 01d252210f40..a79c7a6b2adc 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/BackgroundHiveSplitLoader.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/BackgroundHiveSplitLoader.java @@ -66,7 +66,6 @@ import java.util.Map.Entry; import java.util.Optional; import java.util.OptionalInt; -import java.util.Properties; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; @@ -389,7 +388,7 @@ private ListenableFuture loadPartition(HivePartitionMetadata partition) { HivePartition hivePartition = partition.getHivePartition(); String partitionName = hivePartition.getPartitionId(); - Properties schema = partition.getPartition() + Map schema = partition.getPartition() .map(value -> getHiveSchema(value, table)) .orElseGet(() -> getHiveSchema(table)); List partitionKeys = getPartitionKeys(table, partition.getPartition()); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveFileWriterFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveFileWriterFactory.java index 4efac69d9a8e..05c7feddd4c9 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveFileWriterFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveFileWriterFactory.java @@ -19,9 +19,9 @@ import io.trino.spi.connector.ConnectorSession; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.OptionalInt; -import java.util.Properties; public interface HiveFileWriterFactory { @@ -30,7 +30,7 @@ Optional createFileWriter( List inputColumnNames, StorageFormat storageFormat, HiveCompressionCodec compressionCodec, - Properties schema, + Map schema, ConnectorSession session, OptionalInt bucketNumber, AcidTransaction transaction, diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java index a9b31c68234f..eaa423967fbb 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java @@ -142,7 +142,6 @@ import java.util.Optional; import java.util.OptionalInt; import java.util.OptionalLong; -import java.util.Properties; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.function.Function; @@ -1934,7 +1933,7 @@ private List computeFileNamesForMissingBuckets( private void createEmptyFiles(ConnectorSession session, Location path, Table table, Optional partition, List fileNames) { - Properties schema; + Map schema; StorageFormat format; if (partition.isPresent()) { schema = getHiveSchema(partition.get(), table); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePageSourceFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePageSourceFactory.java index 308fcb58b915..344063332a3b 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePageSourceFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePageSourceFactory.java @@ -19,9 +19,9 @@ import io.trino.spi.predicate.TupleDomain; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.OptionalInt; -import java.util.Properties; public interface HivePageSourceFactory { @@ -31,7 +31,7 @@ Optional createPageSource( long start, long length, long estimatedFileSize, - Properties schema, + Map schema, List columns, TupleDomain effectivePredicate, Optional acidInfo, diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePageSourceProvider.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePageSourceProvider.java index 113c17480f31..ec34a5814c5a 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePageSourceProvider.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePageSourceProvider.java @@ -49,7 +49,6 @@ import java.util.Objects; import java.util.Optional; import java.util.OptionalInt; -import java.util.Properties; import java.util.Set; import java.util.regex.Pattern; @@ -173,7 +172,7 @@ public static Optional createHivePageSource( long start, long length, long estimatedFileSize, - Properties schema, + Map schema, TupleDomain effectivePredicate, TypeManager typeManager, Optional bucketConversion, diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveSplit.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveSplit.java index 36b850648cf1..f93e8a3d6a2a 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveSplit.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveSplit.java @@ -24,9 +24,9 @@ import io.trino.spi.connector.ConnectorSplit; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.OptionalInt; -import java.util.Properties; import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; @@ -46,7 +46,7 @@ public class HiveSplit private final long length; private final long estimatedFileSize; private final long fileModifiedTime; - private final Properties schema; + private final Map schema; private final List partitionKeys; private final List addresses; private final String partitionName; @@ -67,7 +67,7 @@ public HiveSplit( @JsonProperty("length") long length, @JsonProperty("estimatedFileSize") long estimatedFileSize, @JsonProperty("fileModifiedTime") long fileModifiedTime, - @JsonProperty("schema") Properties schema, + @JsonProperty("schema") Map schema, @JsonProperty("partitionKeys") List partitionKeys, @JsonProperty("readBucketNumber") OptionalInt readBucketNumber, @JsonProperty("tableBucketNumber") OptionalInt tableBucketNumber, @@ -105,7 +105,7 @@ public HiveSplit( long length, long estimatedFileSize, long fileModifiedTime, - Properties schema, + Map schema, List partitionKeys, List addresses, OptionalInt readBucketNumber, @@ -188,7 +188,7 @@ public long getFileModifiedTime() } @JsonProperty - public Properties getSchema() + public Map getSchema() { return schema; } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveWriterFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveWriterFactory.java index 4c07825cddca..3456966dd53a 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveWriterFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveWriterFactory.java @@ -57,7 +57,6 @@ import java.util.Map.Entry; import java.util.Optional; import java.util.OptionalInt; -import java.util.Properties; import java.util.Set; import java.util.UUID; import java.util.function.Consumer; @@ -299,7 +298,7 @@ public HiveWriter createWriter(Page partitionColumns, int position, OptionalInt } UpdateMode updateMode; - Properties schema; + Map schema = new HashMap<>(); WriteInfo writeInfo; StorageFormat outputStorageFormat; HiveCompressionCodec compressionCodec; @@ -308,11 +307,10 @@ public HiveWriter createWriter(Page partitionColumns, int position, OptionalInt // Write to: a new partition in a new partitioned table, // or a new unpartitioned table. updateMode = UpdateMode.NEW; - schema = new Properties(); - schema.setProperty(LIST_COLUMNS, dataColumns.stream() + schema.put(LIST_COLUMNS, dataColumns.stream() .map(DataColumn::getName) .collect(joining(","))); - schema.setProperty(LIST_COLUMN_TYPES, dataColumns.stream() + schema.put(LIST_COLUMN_TYPES, dataColumns.stream() .map(DataColumn::getHiveType) .map(HiveType::getHiveTypeName) .map(HiveTypeName::toString) @@ -371,7 +369,7 @@ public HiveWriter createWriter(Page partitionColumns, int position, OptionalInt } } - schema = getHiveSchema(table); + schema.putAll(getHiveSchema(table)); } if (partitionName.isPresent()) { @@ -417,7 +415,7 @@ public HiveWriter createWriter(Page partitionColumns, int position, OptionalInt outputStorageFormat = partition.get().getStorage().getStorageFormat(); compressionCodec = selectCompressionCodec(session, outputStorageFormat); - schema = getHiveSchema(partition.get(), table); + schema.putAll(getHiveSchema(partition.get(), table)); writeInfo = locationService.getPartitionWriteInfo(locationHandle, partition, partitionName.get()); break; @@ -431,7 +429,7 @@ public HiveWriter createWriter(Page partitionColumns, int position, OptionalInt outputStorageFormat = fromHiveStorageFormat(partitionStorageFormat); compressionCodec = selectCompressionCodec(session, partitionStorageFormat); - schema = getHiveSchema(table); + schema.putAll(getHiveSchema(table)); writeInfo = locationService.getPartitionWriteInfo(locationHandle, Optional.empty(), partitionName.get()); break; @@ -442,7 +440,7 @@ public HiveWriter createWriter(Page partitionColumns, int position, OptionalInt } } - additionalTableParameters.forEach(schema::setProperty); + schema.putAll(additionalTableParameters); validateSchema(partitionName, schema); @@ -623,7 +621,7 @@ public SortingFileWriter makeRowIdSortingWriter(FileWriter deleteFileWriter, Loc OrcFileWriterFactory::createOrcDataSink); } - private void validateSchema(Optional partitionName, Properties schema) + private void validateSchema(Optional partitionName, Map schema) { // existing tables may have columns in a different order List fileColumnNames = getColumnNames(schema); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/InternalHiveSplit.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/InternalHiveSplit.java index ae6c28e0c63b..28b11595a7bc 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/InternalHiveSplit.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/InternalHiveSplit.java @@ -20,9 +20,9 @@ import io.trino.spi.HostAddress; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.OptionalInt; -import java.util.Properties; import java.util.function.BooleanSupplier; import static com.google.common.base.MoreObjects.toStringHelper; @@ -37,13 +37,13 @@ @NotThreadSafe public class InternalHiveSplit { - private static final int INSTANCE_SIZE = instanceSize(InternalHiveSplit.class) + instanceSize(Properties.class) + instanceSize(OptionalInt.class); + private static final int INSTANCE_SIZE = instanceSize(InternalHiveSplit.class) + instanceSize(OptionalInt.class); private final String path; private final long end; private final long estimatedFileSize; private final long fileModifiedTime; - private final Properties schema; + private final Map schema; private final List partitionKeys; private final List blocks; private final String partitionName; @@ -67,7 +67,7 @@ public InternalHiveSplit( long end, long estimatedFileSize, long fileModifiedTime, - Properties schema, + Map schema, List partitionKeys, List blocks, OptionalInt readBucketNumber, @@ -141,7 +141,7 @@ public long getFileModifiedTime() return fileModifiedTime; } - public Properties getSchema() + public Map getSchema() { return schema; } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/MergeFileWriter.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/MergeFileWriter.java index 432c138320bd..48d39436a608 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/MergeFileWriter.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/MergeFileWriter.java @@ -31,9 +31,9 @@ import java.io.Closeable; import java.io.IOException; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.OptionalInt; -import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -81,7 +81,7 @@ public class MergeFileWriter private final RowIdSortingFileWriterMaker sortingFileWriterMaker; private final OrcFileWriterFactory orcFileWriterFactory; private final HiveCompressionCodec compressionCodec; - private final Properties hiveAcidSchema; + private final Map hiveAcidSchema; private final String bucketFilename; private Optional deleteFileWriter = Optional.empty(); private Optional insertFileWriter = Optional.empty(); @@ -247,14 +247,12 @@ private Page buildDeletePage(Block rowIds, long writeId) private FileWriter getOrCreateInsertFileWriter() { if (insertFileWriter.isEmpty()) { - Properties schemaCopy = new Properties(); - schemaCopy.putAll(hiveAcidSchema); insertFileWriter = orcFileWriterFactory.createFileWriter( deltaDirectory.appendPath(bucketFilename), ACID_COLUMN_NAMES, fromHiveStorageFormat(ORC), compressionCodec, - schemaCopy, + hiveAcidSchema, session, bucketNumber, transaction, @@ -267,15 +265,13 @@ private FileWriter getOrCreateInsertFileWriter() private FileWriter getOrCreateDeleteFileWriter() { if (deleteFileWriter.isEmpty()) { - Properties schemaCopy = new Properties(); - schemaCopy.putAll(hiveAcidSchema); Location deletePath = deleteDeltaDirectory.appendPath(bucketFilename); FileWriter writer = getWriter(orcFileWriterFactory.createFileWriter( deletePath, ACID_COLUMN_NAMES, fromHiveStorageFormat(ORC), compressionCodec, - schemaCopy, + hiveAcidSchema, session, bucketNumber, transaction, diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/RcFileFileWriterFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/RcFileFileWriterFactory.java index 98717d361a0c..d35a6a863807 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/RcFileFileWriterFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/RcFileFileWriterFactory.java @@ -14,7 +14,6 @@ package io.trino.plugin.hive; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Maps; import com.google.inject.Inject; import io.trino.filesystem.Location; import io.trino.filesystem.TrinoFileSystem; @@ -36,9 +35,9 @@ import java.io.Closeable; import java.io.OutputStream; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.OptionalInt; -import java.util.Properties; import java.util.function.Supplier; import static io.trino.memory.context.AggregatedMemoryContext.newSimpleAggregatedMemoryContext; @@ -91,7 +90,7 @@ public Optional createFileWriter( List inputColumnNames, StorageFormat storageFormat, HiveCompressionCodec compressionCodec, - Properties schema, + Map schema, ConnectorSession session, OptionalInt bucketNumber, AcidTransaction transaction, @@ -107,7 +106,7 @@ public Optional createFileWriter( columnEncodingFactory = new BinaryColumnEncodingFactory(timeZone); } else if (COLUMNAR_SERDE_CLASS.equals(storageFormat.getSerde())) { - columnEncodingFactory = new TextColumnEncodingFactory(TextEncodingOptions.fromSchema(Maps.fromProperties(schema))); + columnEncodingFactory = new TextColumnEncodingFactory(TextEncodingOptions.fromSchema(schema)); } else { return Optional.empty(); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/acid/AcidSchema.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/acid/AcidSchema.java index e63a5b8df801..149cdb2a2c3e 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/acid/AcidSchema.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/acid/AcidSchema.java @@ -14,6 +14,7 @@ package io.trino.plugin.hive.acid; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import io.trino.plugin.hive.HiveType; import io.trino.plugin.hive.HiveTypeName; import io.trino.spi.type.RowType; @@ -21,8 +22,8 @@ import io.trino.spi.type.Type; import java.util.List; +import java.util.Map; import java.util.Optional; -import java.util.Properties; import static com.google.common.base.Preconditions.checkArgument; import static io.trino.plugin.hive.HiveType.HIVE_INT; @@ -61,17 +62,17 @@ public final class AcidSchema private AcidSchema() {} - public static Properties createAcidSchema(HiveType rowType) + public static Map createAcidSchema(HiveType rowType) { - Properties hiveAcidSchema = new Properties(); - hiveAcidSchema.setProperty(LIST_COLUMNS, String.join(",", ACID_COLUMN_NAMES)); - // We must supply an accurate row type, because Apache ORC code we don't control has a consistency - // check that the layout of this "row" must agree with the layout of an inserted row. - hiveAcidSchema.setProperty(LIST_COLUMN_TYPES, createAcidColumnHiveTypes(rowType).stream() - .map(HiveType::getHiveTypeName) - .map(HiveTypeName::toString) - .collect(joining(":"))); - return hiveAcidSchema; + return ImmutableMap.builder() + .put(LIST_COLUMNS, String.join(",", ACID_COLUMN_NAMES)) + // We must supply an accurate row type, because Apache ORC code we don't control has a consistency + // check that the layout of this "row" must agree with the layout of an inserted row. + .put(LIST_COLUMN_TYPES, createAcidColumnHiveTypes(rowType).stream() + .map(HiveType::getHiveTypeName) + .map(HiveTypeName::toString) + .collect(joining(":"))) + .buildOrThrow(); } public static Type createRowType(List names, List types) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroFileWriterFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroFileWriterFactory.java index 9b1ca1db2009..53ff9b636361 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroFileWriterFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroFileWriterFactory.java @@ -38,9 +38,9 @@ import java.io.Closeable; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.OptionalInt; -import java.util.Properties; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableList.toImmutableList; @@ -78,7 +78,7 @@ public Optional createFileWriter( List inputColumnNames, StorageFormat storageFormat, HiveCompressionCodec compressionCodec, - Properties schema, + Map schema, ConnectorSession session, OptionalInt bucketNumber, AcidTransaction transaction, diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroHiveFileUtils.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroHiveFileUtils.java index 3a08f13d01d4..b5ca4bfc242f 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroHiveFileUtils.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroHiveFileUtils.java @@ -42,7 +42,6 @@ import java.util.Locale; import java.util.Map; import java.util.Optional; -import java.util.Properties; import java.util.concurrent.atomic.AtomicInteger; import static com.google.common.collect.ImmutableMap.toImmutableMap; @@ -71,17 +70,17 @@ public final class AvroHiveFileUtils private AvroHiveFileUtils() {} // Lifted and shifted from org.apache.hadoop.hive.serde2.avro.AvroSerdeUtils.determineSchemaOrThrowException - public static Schema determineSchemaOrThrowException(TrinoFileSystem fileSystem, Properties properties) + public static Schema determineSchemaOrThrowException(TrinoFileSystem fileSystem, Map properties) throws IOException { // Try pull schema from literal table property - String schemaString = properties.getProperty(SCHEMA_LITERAL, ""); + String schemaString = properties.getOrDefault(SCHEMA_LITERAL, ""); if (!schemaString.isBlank() && !schemaString.equals(SCHEMA_NONE)) { return getSchemaParser().parse(schemaString); } // Try pull schema directly from URL - String schemaURL = properties.getProperty(SCHEMA_URL, ""); + String schemaURL = properties.getOrDefault(SCHEMA_URL, ""); if (!schemaURL.isBlank()) { TrinoInputFile schemaFile = fileSystem.newInputFile(Location.of(schemaURL)); if (!schemaFile.exists()) { @@ -97,32 +96,32 @@ public static Schema determineSchemaOrThrowException(TrinoFileSystem fileSystem, return getSchemaFromProperties(properties); } - private static Schema getSchemaFromProperties(Properties properties) + private static Schema getSchemaFromProperties(Map schema) throws IOException { - List columnNames = getColumnNames(properties); - List columnTypes = getColumnTypes(properties); + List columnNames = getColumnNames(schema); + List columnTypes = getColumnTypes(schema); if (columnNames.isEmpty() || columnTypes.isEmpty()) { - throw new IOException("Unable to parse column names or column types from job properties to create Avro Schema"); + throw new IOException("Unable to parse column names or column types from schema to create Avro Schema"); } if (columnNames.size() != columnTypes.size()) { throw new IllegalArgumentException("Avro Schema initialization failed. Number of column name and column type differs. columnNames = %s, columnTypes = %s".formatted(columnNames, columnTypes)); } - List columnComments = Optional.ofNullable(properties.getProperty(LIST_COLUMN_COMMENTS)) + List columnComments = Optional.ofNullable(schema.get(LIST_COLUMN_COMMENTS)) .filter(not(String::isBlank)) .map(Splitter.on('\0')::splitToList) .orElse(emptyList()); - final String tableName = properties.getProperty(TABLE_NAME); - final String tableComment = properties.getProperty(TABLE_COMMENT); + String tableName = schema.get(TABLE_NAME); + String tableComment = schema.get(TABLE_COMMENT); return constructSchemaFromParts( columnNames, columnTypes, columnComments, - Optional.ofNullable(properties.getProperty(SCHEMA_NAMESPACE)), - Optional.ofNullable(properties.getProperty(SCHEMA_NAME, tableName)), - Optional.ofNullable(properties.getProperty(SCHEMA_DOC, tableComment))); + Optional.ofNullable(schema.get(SCHEMA_NAMESPACE)), + Optional.ofNullable(schema.getOrDefault(SCHEMA_NAME, tableName)), + Optional.ofNullable(schema.getOrDefault(SCHEMA_DOC, tableComment))); } private static Schema constructSchemaFromParts(List columnNames, List columnTypes, diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroPageSourceFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroPageSourceFactory.java index c390680d5985..e3fc466634a7 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroPageSourceFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroPageSourceFactory.java @@ -47,7 +47,6 @@ import java.util.Objects; import java.util.Optional; import java.util.OptionalInt; -import java.util.Properties; import java.util.Set; import java.util.UUID; @@ -86,7 +85,7 @@ public Optional createPageSource( long start, long length, long estimatedFileSize, - Properties schema, + Map schema, List columns, TupleDomain effectivePredicate, Optional acidInfo, diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/line/LineFileWriterFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/line/LineFileWriterFactory.java index 74afba8e4357..343311340d22 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/line/LineFileWriterFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/line/LineFileWriterFactory.java @@ -41,13 +41,12 @@ import java.io.IOException; import java.io.OutputStream; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.OptionalInt; -import java.util.Properties; import java.util.stream.IntStream; import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.collect.Maps.fromProperties; import static io.trino.memory.context.AggregatedMemoryContext.newSimpleAggregatedMemoryContext; import static io.trino.plugin.hive.HiveErrorCode.HIVE_UNSUPPORTED_FORMAT; import static io.trino.plugin.hive.HiveErrorCode.HIVE_WRITER_OPEN_ERROR; @@ -87,7 +86,7 @@ public Optional createFileWriter( List inputColumnNames, StorageFormat storageFormat, HiveCompressionCodec compressionCodec, - Properties schema, + Map schema, ConnectorSession session, OptionalInt bucketNumber, AcidTransaction transaction, @@ -114,7 +113,7 @@ public Optional createFileWriter( .mapToObj(ordinal -> new Column(fileColumnNames.get(ordinal), fileColumnTypes.get(ordinal), ordinal)) .toList(); - LineSerializer lineSerializer = lineSerializerFactory.create(columns, fromProperties(schema)); + LineSerializer lineSerializer = lineSerializerFactory.create(columns, schema); try { TrinoFileSystem fileSystem = fileSystemFactory.create(session); @@ -141,10 +140,10 @@ public Optional createFileWriter( } } - private Optional getFileHeader(Properties schema, List columns) + private Optional getFileHeader(Map schema, List columns) throws IOException { - String skipHeaderCount = schema.getProperty(SKIP_HEADER_COUNT_KEY, "0"); + String skipHeaderCount = schema.getOrDefault(SKIP_HEADER_COUNT_KEY, "0"); if (skipHeaderCount.equals("0")) { return Optional.empty(); } @@ -157,7 +156,7 @@ private Optional getFileHeader(Properties schema, List columns) columns.stream() .map(column -> new Column(column.name(), VARCHAR, column.ordinal())) .collect(toImmutableList()), - fromProperties(schema)); + schema); PageBuilder pageBuilder = new PageBuilder(headerSerializer.getTypes()); pageBuilder.declarePosition(); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/line/LinePageSourceFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/line/LinePageSourceFactory.java index eae741258e6a..b937f4af9009 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/line/LinePageSourceFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/line/LinePageSourceFactory.java @@ -13,7 +13,6 @@ */ package io.trino.plugin.hive.line; -import com.google.common.collect.Maps; import io.airlift.slice.Slices; import io.airlift.units.DataSize; import io.airlift.units.DataSize.Unit; @@ -40,9 +39,9 @@ import java.io.InputStream; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.OptionalInt; -import java.util.Properties; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableList.toImmutableList; @@ -83,7 +82,7 @@ public Optional createPageSource( long start, long length, long estimatedFileSize, - Properties schema, + Map schema, List columns, TupleDomain effectivePredicate, Optional acidInfo, @@ -91,7 +90,7 @@ public Optional createPageSource( boolean originalFile, AcidTransaction transaction) { - if (!lineReaderFactory.getHiveOutputFormatClassName().equals(schema.getProperty(FILE_INPUT_FORMAT)) || + if (!lineReaderFactory.getHiveOutputFormatClassName().equals(schema.get(FILE_INPUT_FORMAT)) || !lineDeserializerFactory.getHiveSerDeClassNames().contains(getDeserializerClassName(schema))) { return Optional.empty(); } @@ -124,7 +123,7 @@ public Optional createPageSource( projectedReaderColumns.stream() .map(column -> new Column(column.getName(), column.getType(), column.getBaseHiveColumnIndex())) .collect(toImmutableList()), - Maps.fromProperties(schema)); + schema); } // Skip empty inputs diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/line/RegexFileWriterFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/line/RegexFileWriterFactory.java index ae3bc7d2a984..6aa8cc6517c0 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/line/RegexFileWriterFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/line/RegexFileWriterFactory.java @@ -24,9 +24,9 @@ import io.trino.spi.connector.ConnectorSession; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.OptionalInt; -import java.util.Properties; import static io.trino.plugin.hive.HiveErrorCode.HIVE_WRITER_OPEN_ERROR; import static io.trino.plugin.hive.util.HiveClassNames.REGEX_SERDE_CLASS; @@ -40,7 +40,7 @@ public Optional createFileWriter( List inputColumnNames, StorageFormat storageFormat, HiveCompressionCodec compressionCodec, - Properties schema, + Map schema, ConnectorSession session, OptionalInt bucketNumber, AcidTransaction transaction, diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/MetastoreUtil.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/MetastoreUtil.java index b32d0eab64da..40596d8aef2c 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/MetastoreUtil.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/MetastoreUtil.java @@ -51,7 +51,6 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.Properties; import java.util.concurrent.TimeUnit; import static com.google.common.base.Preconditions.checkArgument; @@ -91,7 +90,7 @@ public final class MetastoreUtil private MetastoreUtil() {} - public static Properties getHiveSchema(Table table) + public static Map getHiveSchema(Table table) { // Mimics function in Hive: MetaStoreUtils.getTableMetadata(Table) return getHiveSchema( @@ -104,7 +103,7 @@ public static Properties getHiveSchema(Table table) table.getPartitionColumns()); } - public static Properties getHiveSchema(Partition partition, Table table) + public static Map getHiveSchema(Partition partition, Table table) { // Mimics function in Hive: MetaStoreUtils.getSchema(Partition, Table) return getHiveSchema( @@ -117,7 +116,7 @@ public static Properties getHiveSchema(Partition partition, Table table) table.getPartitionColumns()); } - private static Properties getHiveSchema( + private static Map getHiveSchema( Storage sd, Optional tableSd, List tableDataColumns, @@ -129,33 +128,33 @@ private static Properties getHiveSchema( // Mimics function in Hive: // MetaStoreUtils.getSchema(StorageDescriptor, StorageDescriptor, Map, String, String, List) - Properties schema = new Properties(); + ImmutableMap.Builder schema = ImmutableMap.builder(); - schema.setProperty(FILE_INPUT_FORMAT, sd.getStorageFormat().getInputFormat()); - schema.setProperty(FILE_OUTPUT_FORMAT, sd.getStorageFormat().getOutputFormat()); + schema.put(FILE_INPUT_FORMAT, sd.getStorageFormat().getInputFormat()); + schema.put(FILE_OUTPUT_FORMAT, sd.getStorageFormat().getOutputFormat()); - schema.setProperty(META_TABLE_NAME, databaseName + "." + tableName); - schema.setProperty(META_TABLE_LOCATION, sd.getLocation()); + schema.put(META_TABLE_NAME, databaseName + "." + tableName); + schema.put(META_TABLE_LOCATION, sd.getLocation()); if (sd.getBucketProperty().isPresent()) { - schema.setProperty(BUCKET_FIELD_NAME, Joiner.on(",").join(sd.getBucketProperty().get().getBucketedBy())); - schema.setProperty(BUCKET_COUNT, Integer.toString(sd.getBucketProperty().get().getBucketCount())); + schema.put(BUCKET_FIELD_NAME, Joiner.on(",").join(sd.getBucketProperty().get().getBucketedBy())); + schema.put(BUCKET_COUNT, Integer.toString(sd.getBucketProperty().get().getBucketCount())); } else { - schema.setProperty(BUCKET_COUNT, "0"); + schema.put(BUCKET_COUNT, "0"); } for (Map.Entry param : sd.getSerdeParameters().entrySet()) { - schema.setProperty(param.getKey(), (param.getValue() != null) ? param.getValue() : ""); + schema.put(param.getKey(), (param.getValue() != null) ? param.getValue() : ""); } if (sd.getStorageFormat().getSerde().equals(AVRO_SERDE_CLASS) && tableSd.isPresent()) { for (Map.Entry param : tableSd.get().getSerdeParameters().entrySet()) { - schema.setProperty(param.getKey(), nullToEmpty(param.getValue())); + schema.put(param.getKey(), nullToEmpty(param.getValue())); } } - schema.setProperty(SERIALIZATION_LIB, sd.getStorageFormat().getSerde()); + schema.put(SERIALIZATION_LIB, sd.getStorageFormat().getSerde()); StringBuilder columnNameBuilder = new StringBuilder(); StringBuilder columnTypeBuilder = new StringBuilder(); @@ -174,9 +173,9 @@ private static Properties getHiveSchema( } String columnNames = columnNameBuilder.toString(); String columnTypes = columnTypeBuilder.toString(); - schema.setProperty(META_TABLE_COLUMNS, columnNames); - schema.setProperty(META_TABLE_COLUMN_TYPES, columnTypes); - schema.setProperty(LIST_COLUMN_COMMENTS, columnCommentBuilder.toString()); + schema.put(META_TABLE_COLUMNS, columnNames); + schema.put(META_TABLE_COLUMN_TYPES, columnTypes); + schema.put(LIST_COLUMN_COMMENTS, columnCommentBuilder.toString()); StringBuilder partString = new StringBuilder(); String partStringSep = ""; @@ -193,20 +192,20 @@ private static Properties getHiveSchema( } } if (partString.length() > 0) { - schema.setProperty(META_TABLE_PARTITION_COLUMNS, partString.toString()); - schema.setProperty(META_TABLE_PARTITION_COLUMN_TYPES, partTypesString.toString()); + schema.put(META_TABLE_PARTITION_COLUMNS, partString.toString()); + schema.put(META_TABLE_PARTITION_COLUMN_TYPES, partTypesString.toString()); } if (parameters != null) { for (Map.Entry entry : parameters.entrySet()) { // add non-null parameters to the schema if (entry.getValue() != null) { - schema.setProperty(entry.getKey(), entry.getValue()); + schema.put(entry.getKey(), entry.getValue()); } } } - return schema; + return schema.buildKeepingLast(); } public static ProtectMode getProtectMode(Partition partition) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcFileWriterFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcFileWriterFactory.java index 8ad0a0bd51c3..35359facdadd 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcFileWriterFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcFileWriterFactory.java @@ -44,9 +44,9 @@ import java.io.Closeable; import java.io.IOException; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.OptionalInt; -import java.util.Properties; import java.util.function.Supplier; import static io.trino.orc.metadata.OrcType.createRootOrcType; @@ -125,7 +125,7 @@ public Optional createFileWriter( List inputColumnNames, StorageFormat storageFormat, HiveCompressionCodec compressionCodec, - Properties schema, + Map schema, ConnectorSession session, OptionalInt bucketNumber, AcidTransaction transaction, diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcPageSourceFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcPageSourceFactory.java index 19bc4e238ddc..aca3b9c3f715 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcPageSourceFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcPageSourceFactory.java @@ -15,7 +15,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Maps; import com.google.inject.Inject; import io.airlift.slice.Slice; import io.trino.filesystem.Location; @@ -61,7 +60,6 @@ import java.util.Map; import java.util.Optional; import java.util.OptionalInt; -import java.util.Properties; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -160,12 +158,10 @@ public OrcPageSourceFactory( this.fileSystemFactory = requireNonNull(fileSystemFactory, "fileSystemFactory is null"); } - public static Properties stripUnnecessaryProperties(Properties schema) + public static Map stripUnnecessaryProperties(Map schema) { - if (ORC_SERDE_CLASS.equals(getDeserializerClassName(schema)) && !isFullAcidTable(Maps.fromProperties(schema))) { - Properties stripped = new Properties(); - stripped.put(SERIALIZATION_LIB, schema.getProperty(SERIALIZATION_LIB)); - return stripped; + if (ORC_SERDE_CLASS.equals(getDeserializerClassName(schema)) && !isFullAcidTable(schema)) { + return ImmutableMap.of(SERIALIZATION_LIB, schema.get(SERIALIZATION_LIB)); } return schema; } @@ -177,7 +173,7 @@ public Optional createPageSource( long start, long length, long estimatedFileSize, - Properties schema, + Map schema, List columns, TupleDomain effectivePredicate, Optional acidInfo, @@ -207,7 +203,7 @@ public Optional createPageSource( readerColumnHandles, columns, isUseOrcColumnNames(session), - isFullAcidTable(Maps.fromProperties(schema)), + isFullAcidTable(schema), effectivePredicate, legacyTimeZone, orcReaderOptions diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/parquet/ParquetFileWriterFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/parquet/ParquetFileWriterFactory.java index 49faff70afda..5d830e5440c8 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/parquet/ParquetFileWriterFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/parquet/ParquetFileWriterFactory.java @@ -43,9 +43,9 @@ import java.io.Closeable; import java.io.IOException; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.OptionalInt; -import java.util.Properties; import java.util.function.Supplier; import static io.trino.parquet.writer.ParquetSchemaConverter.HIVE_PARQUET_USE_INT96_TIMESTAMP_ENCODING; @@ -90,7 +90,7 @@ public Optional createFileWriter( List inputColumnNames, StorageFormat storageFormat, HiveCompressionCodec compressionCodec, - Properties schema, + Map schema, ConnectorSession session, OptionalInt bucketNumber, AcidTransaction transaction, diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/parquet/ParquetPageSourceFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/parquet/ParquetPageSourceFactory.java index 5dd96adc51d0..6c2ce5cde12a 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/parquet/ParquetPageSourceFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/parquet/ParquetPageSourceFactory.java @@ -69,7 +69,6 @@ import java.util.Optional; import java.util.OptionalInt; import java.util.OptionalLong; -import java.util.Properties; import java.util.Set; import static com.google.common.base.Preconditions.checkArgument; @@ -148,12 +147,10 @@ public ParquetPageSourceFactory( domainCompactionThreshold = hiveConfig.getDomainCompactionThreshold(); } - public static Properties stripUnnecessaryProperties(Properties schema) + public static Map stripUnnecessaryProperties(Map schema) { if (PARQUET_SERDE_CLASS_NAMES.contains(getDeserializerClassName(schema))) { - Properties stripped = new Properties(); - stripped.put(SERIALIZATION_LIB, schema.getProperty(SERIALIZATION_LIB)); - return stripped; + return ImmutableMap.of(SERIALIZATION_LIB, schema.get(SERIALIZATION_LIB)); } return schema; } @@ -165,7 +162,7 @@ public Optional createPageSource( long start, long length, long estimatedFileSize, - Properties schema, + Map schema, List columns, TupleDomain effectivePredicate, Optional acidInfo, diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/rcfile/RcFilePageSourceFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/rcfile/RcFilePageSourceFactory.java index 389eb0408c3a..a077d12dccc2 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/rcfile/RcFilePageSourceFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/rcfile/RcFilePageSourceFactory.java @@ -14,7 +14,6 @@ package io.trino.plugin.hive.rcfile; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Maps; import com.google.inject.Inject; import io.airlift.slice.Slices; import io.airlift.units.DataSize; @@ -47,9 +46,9 @@ import java.io.InputStream; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.OptionalInt; -import java.util.Properties; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableList.toImmutableList; @@ -80,12 +79,10 @@ public RcFilePageSourceFactory(TrinoFileSystemFactory fileSystemFactory, HiveCon this.timeZone = hiveConfig.getRcfileDateTimeZone(); } - public static Properties stripUnnecessaryProperties(Properties schema) + public static Map stripUnnecessaryProperties(Map schema) { if (LAZY_BINARY_COLUMNAR_SERDE_CLASS.equals(getDeserializerClassName(schema))) { - Properties stripped = new Properties(); - stripped.put(SERIALIZATION_LIB, schema.getProperty(SERIALIZATION_LIB)); - return stripped; + return ImmutableMap.of(SERIALIZATION_LIB, schema.get(SERIALIZATION_LIB)); } return schema; } @@ -97,7 +94,7 @@ public Optional createPageSource( long start, long length, long estimatedFileSize, - Properties schema, + Map schema, List columns, TupleDomain effectivePredicate, Optional acidInfo, @@ -111,7 +108,7 @@ public Optional createPageSource( columnEncodingFactory = new BinaryColumnEncodingFactory(timeZone); } else if (deserializerClassName.equals(COLUMNAR_SERDE_CLASS)) { - columnEncodingFactory = new TextColumnEncodingFactory(TextEncodingOptions.fromSchema(Maps.fromProperties(schema))); + columnEncodingFactory = new TextColumnEncodingFactory(TextEncodingOptions.fromSchema(schema)); } else { return Optional.empty(); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveUtil.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveUtil.java index cdd87f1cb87a..8d370af19d55 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveUtil.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveUtil.java @@ -65,7 +65,6 @@ import java.util.Map; import java.util.Optional; import java.util.OptionalInt; -import java.util.Properties; import java.util.function.Function; import static com.google.common.base.Strings.isNullOrEmpty; @@ -184,9 +183,9 @@ private HiveUtil() { } - public static Optional getInputFormatName(Properties schema) + public static Optional getInputFormatName(Map schema) { - return Optional.ofNullable(schema.getProperty(FILE_INPUT_FORMAT)); + return Optional.ofNullable(schema.get(FILE_INPUT_FORMAT)); } private static long parseHiveDate(String value) @@ -203,9 +202,9 @@ public static long parseHiveTimestamp(String value) return HIVE_TIMESTAMP_PARSER.parseMillis(value) * MICROSECONDS_PER_MILLISECOND; } - public static String getDeserializerClassName(Properties schema) + public static String getDeserializerClassName(Map schema) { - String name = schema.getProperty(SERIALIZATION_LIB); + String name = schema.get(SERIALIZATION_LIB); checkCondition(name != null, HIVE_INVALID_METADATA, "Table or partition is missing Hive deserializer property: %s", SERIALIZATION_LIB); return name; } @@ -710,19 +709,19 @@ public static List extractStructFieldTypes(HiveType hiveType) .collect(toImmutableList()); } - public static int getHeaderCount(Properties schema) + public static int getHeaderCount(Map schema) { return getPositiveIntegerValue(schema, SKIP_HEADER_COUNT_KEY, "0"); } - public static int getFooterCount(Properties schema) + public static int getFooterCount(Map schema) { return getPositiveIntegerValue(schema, SKIP_FOOTER_COUNT_KEY, "0"); } - private static int getPositiveIntegerValue(Properties schema, String key, String defaultValue) + private static int getPositiveIntegerValue(Map schema, String key, String defaultValue) { - String value = schema.getProperty(key, defaultValue); + String value = schema.getOrDefault(key, defaultValue); try { int intValue = parseInt(value); if (intValue < 0) { @@ -735,30 +734,30 @@ private static int getPositiveIntegerValue(Properties schema, String key, String } } - public static List getColumnNames(Properties schema) + public static List getColumnNames(Map schema) { - return COLUMN_NAMES_SPLITTER.splitToList(schema.getProperty(LIST_COLUMNS, "")); + return COLUMN_NAMES_SPLITTER.splitToList(schema.getOrDefault(LIST_COLUMNS, "")); } - public static List getColumnTypes(Properties schema) + public static List getColumnTypes(Map schema) { - return toHiveTypes(schema.getProperty(LIST_COLUMN_TYPES, "")); + return toHiveTypes(schema.getOrDefault(LIST_COLUMN_TYPES, "")); } - public static OrcWriterOptions getOrcWriterOptions(Properties schema, OrcWriterOptions orcWriterOptions) + public static OrcWriterOptions getOrcWriterOptions(Map schema, OrcWriterOptions orcWriterOptions) { if (schema.containsKey(ORC_BLOOM_FILTER_COLUMNS_KEY)) { if (!schema.containsKey(ORC_BLOOM_FILTER_FPP_KEY)) { throw new TrinoException(HIVE_INVALID_METADATA, "FPP for bloom filter is missing"); } try { - double fpp = parseDouble(schema.getProperty(ORC_BLOOM_FILTER_FPP_KEY)); + double fpp = parseDouble(schema.get(ORC_BLOOM_FILTER_FPP_KEY)); return orcWriterOptions - .withBloomFilterColumns(ImmutableSet.copyOf(COLUMN_NAMES_SPLITTER.splitToList(schema.getProperty(ORC_BLOOM_FILTER_COLUMNS_KEY)))) + .withBloomFilterColumns(ImmutableSet.copyOf(COLUMN_NAMES_SPLITTER.splitToList(schema.get(ORC_BLOOM_FILTER_COLUMNS_KEY)))) .withBloomFilterFpp(fpp); } catch (NumberFormatException e) { - throw new TrinoException(HIVE_UNSUPPORTED_FORMAT, format("Invalid value for %s property: %s", ORC_BLOOM_FILTER_FPP, schema.getProperty(ORC_BLOOM_FILTER_FPP_KEY))); + throw new TrinoException(HIVE_UNSUPPORTED_FORMAT, format("Invalid value for %s property: %s", ORC_BLOOM_FILTER_FPP, schema.get(ORC_BLOOM_FILTER_FPP_KEY))); } } return orcWriterOptions; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/InternalHiveSplitFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/InternalHiveSplitFactory.java index 3395cfd426a9..2e6588d3d918 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/InternalHiveSplitFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/InternalHiveSplitFactory.java @@ -38,7 +38,6 @@ import java.util.Map; import java.util.Optional; import java.util.OptionalInt; -import java.util.Properties; import java.util.function.BooleanSupplier; import static com.google.common.base.Preconditions.checkArgument; @@ -51,7 +50,7 @@ public class InternalHiveSplitFactory { private final String partitionName; private final HiveStorageFormat storageFormat; - private final Properties strippedSchema; + private final Map strippedSchema; private final List partitionKeys; private final Optional pathDomain; private final TableToPartitionMapping tableToPartitionMapping; @@ -65,7 +64,7 @@ public class InternalHiveSplitFactory public InternalHiveSplitFactory( String partitionName, HiveStorageFormat storageFormat, - Properties schema, + Map schema, List partitionKeys, TupleDomain effectivePredicate, BooleanSupplier partitionMatchSupplier, @@ -91,7 +90,7 @@ public InternalHiveSplitFactory( checkArgument(minimumTargetSplitSizeInBytes > 0, "minimumTargetSplitSize must be > 0, found: %s", minimumTargetSplitSize); } - private static Properties stripUnnecessaryProperties(Properties schema) + private static Map stripUnnecessaryProperties(Map schema) { // Sending the full schema with every split is costly and can be avoided for formats supported natively schema = OrcPageSourceFactory.stripUnnecessaryProperties(schema); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java index 6b0e8067c432..64a89f0076fc 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java @@ -16,6 +16,7 @@ import com.google.common.collect.AbstractIterator; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ListMultimap; import com.google.common.io.Resources; import io.airlift.stats.CounterStat; @@ -64,7 +65,6 @@ import java.util.Map; import java.util.Optional; import java.util.OptionalInt; -import java.util.Properties; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; @@ -170,7 +170,7 @@ public void testCsv() assertCsvSplitCount(file, Map.of(), 33); assertCsvSplitCount(file, Map.of(HEADER_COUNT, "1"), 33); assertCsvSplitCount(file, Map.of(HEADER_COUNT, "2"), 1); - assertCsvSplitCount(file, Map.of(HEADER_COUNT, "1"), 1); + assertCsvSplitCount(file, Map.of(FOOTER_COUNT, "1"), 1); assertCsvSplitCount(file, Map.of(HEADER_COUNT, "1", FOOTER_COUNT, "1"), 1); } @@ -780,9 +780,10 @@ public void testBuildManifestFileIterator() throws IOException { CachingDirectoryLister directoryLister = new CachingDirectoryLister(new Duration(0, TimeUnit.MINUTES), DataSize.ofBytes(0), List.of()); - Properties schema = new Properties(); - schema.setProperty(FILE_INPUT_FORMAT, SYMLINK_TEXT_INPUT_FORMAT_CLASS); - schema.setProperty(SERIALIZATION_LIB, AVRO.getSerde()); + Map schema = ImmutableMap.builder() + .put(FILE_INPUT_FORMAT, SYMLINK_TEXT_INPUT_FORMAT_CLASS) + .put(SERIALIZATION_LIB, AVRO.getSerde()) + .buildOrThrow(); Location firstFilePath = Location.of("memory:///db_name/table_name/file1"); Location secondFilePath = Location.of("memory:///db_name/table_name/file2"); @@ -820,9 +821,10 @@ public void testBuildManifestFileIteratorNestedDirectory() throws IOException { CachingDirectoryLister directoryLister = new CachingDirectoryLister(new Duration(5, TimeUnit.MINUTES), DataSize.of(100, KILOBYTE), List.of()); - Properties schema = new Properties(); - schema.setProperty(FILE_INPUT_FORMAT, SYMLINK_TEXT_INPUT_FORMAT_CLASS); - schema.setProperty(SERIALIZATION_LIB, AVRO.getSerde()); + Map schema = ImmutableMap.builder() + .put(FILE_INPUT_FORMAT, SYMLINK_TEXT_INPUT_FORMAT_CLASS) + .put(SERIALIZATION_LIB, AVRO.getSerde()) + .buildOrThrow(); Location filePath = Location.of("memory:///db_name/table_name/file1"); Location directoryPath = Location.of("memory:///db_name/table_name/dir/file2"); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java index 902291918503..a598b26c166c 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java @@ -940,10 +940,6 @@ private static void testPageSourceFactory( int rowCount) throws IOException { - Properties splitProperties = new Properties(); - splitProperties.setProperty(FILE_INPUT_FORMAT, storageFormat.getInputFormat()); - splitProperties.setProperty(SERIALIZATION_LIB, storageFormat.getSerde()); - // Use full columns in split properties ImmutableList.Builder splitPropertiesColumnNames = ImmutableList.builder(); ImmutableList.Builder splitPropertiesColumnTypes = ImmutableList.builder(); @@ -958,8 +954,12 @@ private static void testPageSourceFactory( } } - splitProperties.setProperty(LIST_COLUMNS, String.join(",", splitPropertiesColumnNames.build())); - splitProperties.setProperty(LIST_COLUMN_TYPES, String.join(",", splitPropertiesColumnTypes.build())); + Map splitProperties = ImmutableMap.builder() + .put(FILE_INPUT_FORMAT, storageFormat.getInputFormat()) + .put(SERIALIZATION_LIB, storageFormat.getSerde()) + .put(LIST_COLUMNS, String.join(",", splitPropertiesColumnNames.build())) + .put(LIST_COLUMN_TYPES, String.join(",", splitPropertiesColumnTypes.build())) + .buildOrThrow(); List partitionKeys = testReadColumns.stream() .filter(TestColumn::partitionKey) @@ -1319,20 +1319,10 @@ private static void createTestFileTrino( } Page page = pageBuilder.build(); - Properties tableProperties = new Properties(); - tableProperties.setProperty( - LIST_COLUMNS, - testColumns.stream() - .map(TestColumn::name) - .collect(Collectors.joining(","))); - - tableProperties.setProperty( - LIST_COLUMN_TYPES, - testColumns.stream() - .map(TestColumn::type) - .map(HiveType::toHiveType) - .map(HiveType::toString) - .collect(Collectors.joining(","))); + Map tableProperties = ImmutableMap.builder() + .put(LIST_COLUMNS, testColumns.stream().map(TestColumn::name).collect(Collectors.joining(","))) + .put(LIST_COLUMN_TYPES, testColumns.stream().map(TestColumn::type).map(HiveType::toHiveType).map(HiveType::toString).collect(Collectors.joining(","))) + .buildOrThrow(); Optional fileWriter = fileWriterFactory.createFileWriter( location, diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java index b530e08accd6..09974bb0654b 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java @@ -53,9 +53,9 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.OptionalInt; -import java.util.Properties; import java.util.stream.Stream; import static com.google.common.collect.ImmutableList.toImmutableList; @@ -242,11 +242,12 @@ private static ConnectorPageSource createPageSource(TrinoFileSystemFactory fileS throws IOException { long length = fileSystemFactory.create(ConnectorIdentity.ofUser("test")).newInputFile(location).length(); - Properties splitProperties = new Properties(); - splitProperties.setProperty(FILE_INPUT_FORMAT, config.getHiveStorageFormat().getInputFormat()); - splitProperties.setProperty(SERIALIZATION_LIB, config.getHiveStorageFormat().getSerde()); - splitProperties.setProperty(LIST_COLUMNS, Joiner.on(',').join(getColumnHandles().stream().map(HiveColumnHandle::getName).collect(toImmutableList()))); - splitProperties.setProperty(LIST_COLUMN_TYPES, Joiner.on(',').join(getColumnHandles().stream().map(HiveColumnHandle::getHiveType).map(hiveType -> hiveType.getHiveTypeName().toString()).collect(toImmutableList()))); + Map splitProperties = ImmutableMap.builder() + .put(FILE_INPUT_FORMAT, config.getHiveStorageFormat().getInputFormat()) + .put(SERIALIZATION_LIB, config.getHiveStorageFormat().getSerde()) + .put(LIST_COLUMNS, Joiner.on(',').join(getColumnHandles().stream().map(HiveColumnHandle::getName).collect(toImmutableList()))) + .put(LIST_COLUMN_TYPES, Joiner.on(',').join(getColumnHandles().stream().map(HiveColumnHandle::getHiveType).map(hiveType -> hiveType.getHiveTypeName().toString()).collect(toImmutableList()))) + .buildOrThrow(); HiveSplit split = new HiveSplit( "", location.toString(), diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveSplit.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveSplit.java index b8a264a76a38..068aec23941b 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveSplit.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveSplit.java @@ -28,9 +28,9 @@ import org.junit.jupiter.api.Test; import java.time.Instant; +import java.util.Map; import java.util.Optional; import java.util.OptionalInt; -import java.util.Properties; import static io.trino.plugin.hive.HiveColumnHandle.createBaseColumn; import static io.trino.plugin.hive.HiveType.HIVE_LONG; @@ -47,9 +47,10 @@ public void testJsonRoundTrip() objectMapperProvider.setJsonDeserializers(ImmutableMap.of(Type.class, new TypeDeserializer(new TestingTypeManager()))); JsonCodec codec = new JsonCodecFactory(objectMapperProvider).jsonCodec(HiveSplit.class); - Properties schema = new Properties(); - schema.setProperty("foo", "bar"); - schema.setProperty("bar", "baz"); + Map schema = ImmutableMap.builder() + .put("foo", "bar") + .put("bar", "baz") + .buildOrThrow(); ImmutableList partitionKeys = ImmutableList.of(new HivePartitionKey("a", "apple"), new HivePartitionKey("b", "42")); ImmutableList addresses = ImmutableList.of(HostAddress.fromParts("127.0.0.1", 44), HostAddress.fromParts("127.0.0.1", 45)); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveSplitSource.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveSplitSource.java index 9325a2729633..516fa6e1c5cc 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveSplitSource.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveSplitSource.java @@ -14,6 +14,7 @@ package io.trino.plugin.hive; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.SettableFuture; import io.airlift.stats.CounterStat; import io.airlift.units.DataSize; @@ -25,7 +26,6 @@ import java.util.List; import java.util.Optional; import java.util.OptionalInt; -import java.util.Properties; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @@ -231,7 +231,7 @@ public void testReaderWaitsForSplits() // wait for thread to get the split ConnectorSplit split = splits.get(800, TimeUnit.MILLISECONDS); - assertEquals(((HiveSplit) split).getSchema().getProperty("id"), "33"); + assertEquals(((HiveSplit) split).getSchema().get("id"), "33"); } finally { // make sure the thread exits @@ -325,7 +325,7 @@ private TestSplit(int id, OptionalInt bucketNumber, DataSize fileSize, BooleanSu fileSize.toBytes(), fileSize.toBytes(), Instant.now().toEpochMilli(), - properties("id", String.valueOf(id)), + ImmutableMap.of("id", String.valueOf(id)), ImmutableList.of(), ImmutableList.of(new InternalHiveBlock(0, fileSize.toBytes(), ImmutableList.of())), bucketNumber, @@ -338,12 +338,5 @@ private TestSplit(int id, OptionalInt bucketNumber, DataSize fileSize, BooleanSu Optional.empty(), partitionMatchSupplier); } - - private static Properties properties(String key, String value) - { - Properties properties = new Properties(); - properties.setProperty(key, value); - return properties; - } } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestNodeLocalDynamicSplitPruning.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestNodeLocalDynamicSplitPruning.java index 1122c6ccbff2..805b1217cd16 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestNodeLocalDynamicSplitPruning.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestNodeLocalDynamicSplitPruning.java @@ -40,7 +40,6 @@ import java.util.Map; import java.util.Optional; import java.util.OptionalInt; -import java.util.Properties; import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -118,9 +117,10 @@ private static ConnectorPageSource createTestingPageSource(HiveTransactionHandle TrinoFileSystemFactory fileSystemFactory = new MemoryFileSystemFactory(); fileSystemFactory.create(ConnectorIdentity.ofUser("test")).newOutputFile(location).create().close(); - Properties splitProperties = new Properties(); - splitProperties.setProperty(FILE_INPUT_FORMAT, hiveConfig.getHiveStorageFormat().getInputFormat()); - splitProperties.setProperty(SERIALIZATION_LIB, hiveConfig.getHiveStorageFormat().getSerde()); + Map splitProperties = ImmutableMap.builder() + .put(FILE_INPUT_FORMAT, hiveConfig.getHiveStorageFormat().getInputFormat()) + .put(SERIALIZATION_LIB, hiveConfig.getHiveStorageFormat().getSerde()) + .buildOrThrow(); HiveSplit split = new HiveSplit( "", location.toString(), diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOrcPageSourceMemoryTracking.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOrcPageSourceMemoryTracking.java index 399318cfcb80..b9138e10f2e0 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOrcPageSourceMemoryTracking.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOrcPageSourceMemoryTracking.java @@ -14,6 +14,7 @@ package io.trino.plugin.hive; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import io.airlift.slice.Slice; import io.airlift.stats.Distribution; @@ -84,6 +85,7 @@ import java.time.Instant; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.OptionalInt; import java.util.Properties; @@ -491,7 +493,7 @@ public void testScanFilterAndProjectOperator() private class TestPreparer { private final FileSplit fileSplit; - private final Properties schema; + private final Map schema; private final List columns; private final List types; private final String partitionName; @@ -509,17 +511,12 @@ public TestPreparer(String tempFilePath, List testColumns, int numRo throws Exception { OrcSerde serde = new OrcSerde(); - schema = new Properties(); - schema.setProperty(LIST_COLUMNS, - testColumns.stream() - .map(TestColumn::getName) - .collect(Collectors.joining(","))); - schema.setProperty(LIST_COLUMN_TYPES, - testColumns.stream() - .map(TestColumn::getType) - .collect(Collectors.joining(","))); - schema.setProperty(FILE_INPUT_FORMAT, OrcInputFormat.class.getName()); - schema.setProperty(SERIALIZATION_LIB, serde.getClass().getName()); + schema = ImmutableMap.builder() + .put(LIST_COLUMNS, testColumns.stream().map(TestColumn::getName).collect(Collectors.joining(","))) + .put(LIST_COLUMN_TYPES, testColumns.stream().map(TestColumn::getType).collect(Collectors.joining(","))) + .put(FILE_INPUT_FORMAT, OrcInputFormat.class.getName()) + .put(SERIALIZATION_LIB, serde.getClass().getName()) + .buildOrThrow(); partitionKeys = testColumns.stream() .filter(TestColumn::isPartitionKey) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/avro/TestAvroSchemaGeneration.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/avro/TestAvroSchemaGeneration.java index 2e67ab71b940..cdc380572c74 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/avro/TestAvroSchemaGeneration.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/avro/TestAvroSchemaGeneration.java @@ -13,24 +13,28 @@ */ package io.trino.plugin.hive.avro; +import com.google.common.collect.ImmutableMap; import io.trino.filesystem.local.LocalFileSystem; import io.trino.plugin.hive.HiveType; import io.trino.plugin.hive.type.TypeInfo; import io.trino.spi.type.RowType; -import io.trino.spi.type.VarcharType; import org.apache.avro.Schema; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hive.serde2.avro.AvroSerdeUtils; import org.junit.jupiter.api.Test; import java.nio.file.Path; +import java.util.Map; import java.util.Properties; import java.util.stream.Collectors; import java.util.stream.Stream; +import static io.trino.plugin.hive.HiveType.HIVE_STRING; +import static io.trino.plugin.hive.HiveType.toHiveType; import static io.trino.plugin.hive.avro.AvroHiveConstants.TABLE_NAME; import static io.trino.plugin.hive.util.SerdeConstants.LIST_COLUMNS; import static io.trino.plugin.hive.util.SerdeConstants.LIST_COLUMN_TYPES; +import static io.trino.spi.type.VarcharType.VARCHAR; import static org.assertj.core.api.Assertions.assertThat; public class TestAvroSchemaGeneration @@ -39,12 +43,13 @@ public class TestAvroSchemaGeneration public void testOldVsNewSchemaGeneration() throws Exception { - Properties properties = new Properties(); - properties.setProperty(TABLE_NAME, "testingTable"); - properties.setProperty(LIST_COLUMNS, "a,b"); - properties.setProperty(LIST_COLUMN_TYPES, Stream.of(HiveType.HIVE_INT, HiveType.HIVE_STRING).map(HiveType::getTypeInfo).map(TypeInfo::toString).collect(Collectors.joining(","))); + Map properties = ImmutableMap.builder() + .put(TABLE_NAME, "testingTable") + .put(LIST_COLUMNS, "a,b") + .put(LIST_COLUMN_TYPES, Stream.of(HiveType.HIVE_INT, HIVE_STRING).map(HiveType::getTypeInfo).map(TypeInfo::toString).collect(Collectors.joining(","))) + .buildOrThrow(); Schema actual = AvroHiveFileUtils.determineSchemaOrThrowException(new LocalFileSystem(Path.of("/")), properties); - Schema expected = AvroSerdeUtils.determineSchemaOrThrowException(new Configuration(false), properties); + Schema expected = AvroSerdeUtils.determineSchemaOrThrowException(new Configuration(false), toProperties(properties)); assertThat(actual).isEqualTo(expected); } @@ -52,12 +57,21 @@ public void testOldVsNewSchemaGeneration() public void testOldVsNewSchemaGenerationWithNested() throws Exception { - Properties properties = new Properties(); - properties.setProperty(TABLE_NAME, "testingTable"); - properties.setProperty(LIST_COLUMNS, "a,b"); - properties.setProperty(LIST_COLUMN_TYPES, Stream.of(HiveType.toHiveType(RowType.rowType(RowType.field("a", VarcharType.VARCHAR))), HiveType.HIVE_STRING).map(HiveType::getTypeInfo).map(TypeInfo::toString).collect(Collectors.joining(","))); + Map properties = ImmutableMap.builder() + .put(TABLE_NAME, "testingTable") + .put(LIST_COLUMNS, "a,b") + .put(LIST_COLUMN_TYPES, toHiveType(RowType.rowType(RowType.field("a", VARCHAR))) + "," + HIVE_STRING) + .buildOrThrow(); Schema actual = AvroHiveFileUtils.determineSchemaOrThrowException(new LocalFileSystem(Path.of("/")), properties); - Schema expected = AvroSerdeUtils.determineSchemaOrThrowException(new Configuration(false), properties); + + Schema expected = AvroSerdeUtils.determineSchemaOrThrowException(new Configuration(false), toProperties(properties)); assertThat(actual).isEqualTo(expected); } + + private static Properties toProperties(Map properties) + { + Properties hiveProperties = new Properties(); + hiveProperties.putAll(properties); + return hiveProperties; + } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestMetastoreUtil.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestMetastoreUtil.java index 62d1cbe65a61..07cc8fd8b7f5 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestMetastoreUtil.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestMetastoreUtil.java @@ -33,7 +33,6 @@ import java.util.Locale; import java.util.Map; import java.util.Optional; -import java.util.Properties; import static io.airlift.slice.Slices.utf8Slice; import static io.trino.hive.thrift.metastore.hive_metastoreConstants.FILE_INPUT_FORMAT; @@ -182,14 +181,14 @@ public void testPartitionRoundTrip() @Test public void testHiveSchemaTable() { - Properties actual = MetastoreUtil.getHiveSchema(ThriftMetastoreUtil.fromMetastoreApiTable(TEST_TABLE_WITH_UNSUPPORTED_FIELDS, TEST_SCHEMA)); + Map actual = MetastoreUtil.getHiveSchema(ThriftMetastoreUtil.fromMetastoreApiTable(TEST_TABLE_WITH_UNSUPPORTED_FIELDS, TEST_SCHEMA)); assertEquals(actual, TEST_TABLE_METADATA); } @Test public void testHiveSchemaPartition() { - Properties actual = MetastoreUtil.getHiveSchema(ThriftMetastoreUtil.fromMetastoreApiPartition(TEST_PARTITION_WITH_UNSUPPORTED_FIELDS), ThriftMetastoreUtil.fromMetastoreApiTable(TEST_TABLE_WITH_UNSUPPORTED_FIELDS, TEST_SCHEMA)); + Map actual = MetastoreUtil.getHiveSchema(ThriftMetastoreUtil.fromMetastoreApiPartition(TEST_PARTITION_WITH_UNSUPPORTED_FIELDS), ThriftMetastoreUtil.fromMetastoreApiTable(TEST_TABLE_WITH_UNSUPPORTED_FIELDS, TEST_SCHEMA)); assertEquals(actual, TEST_TABLE_METADATA); } @@ -199,10 +198,10 @@ public void testHiveSchemaCaseInsensitive() List testSchema = TEST_SCHEMA.stream() .map(fieldSchema -> new FieldSchema(fieldSchema.getName(), fieldSchema.getType().toUpperCase(Locale.ENGLISH), fieldSchema.getComment())) .toList(); - Properties actualTable = MetastoreUtil.getHiveSchema(ThriftMetastoreUtil.fromMetastoreApiTable(TEST_TABLE_WITH_UNSUPPORTED_FIELDS, testSchema)); + Map actualTable = MetastoreUtil.getHiveSchema(ThriftMetastoreUtil.fromMetastoreApiTable(TEST_TABLE_WITH_UNSUPPORTED_FIELDS, testSchema)); assertEquals(actualTable, TEST_TABLE_METADATA); - Properties actualPartition = MetastoreUtil.getHiveSchema(ThriftMetastoreUtil.fromMetastoreApiPartition(TEST_PARTITION_WITH_UNSUPPORTED_FIELDS), ThriftMetastoreUtil.fromMetastoreApiTable(TEST_TABLE_WITH_UNSUPPORTED_FIELDS, testSchema)); + Map actualPartition = MetastoreUtil.getHiveSchema(ThriftMetastoreUtil.fromMetastoreApiPartition(TEST_PARTITION_WITH_UNSUPPORTED_FIELDS), ThriftMetastoreUtil.fromMetastoreApiTable(TEST_TABLE_WITH_UNSUPPORTED_FIELDS, testSchema)); assertEquals(actualPartition, TEST_TABLE_METADATA); } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPageSourceFactory.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPageSourceFactory.java index 3ad44049fd8c..2dcdd5e86dda 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPageSourceFactory.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPageSourceFactory.java @@ -45,13 +45,13 @@ import java.util.Optional; import java.util.OptionalInt; import java.util.OptionalLong; -import java.util.Properties; import java.util.Set; import java.util.function.LongPredicate; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.io.Resources.getResource; +import static io.trino.hive.thrift.metastore.hive_metastoreConstants.FILE_INPUT_FORMAT; import static io.trino.plugin.hive.HiveColumnHandle.ColumnType.REGULAR; import static io.trino.plugin.hive.HiveColumnHandle.createBaseColumn; import static io.trino.plugin.hive.HiveStorageFormat.ORC; @@ -68,7 +68,6 @@ import static io.trino.tpch.NationColumn.NATION_KEY; import static io.trino.tpch.NationColumn.REGION_KEY; import static java.util.Collections.nCopies; -import static org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.FILE_INPUT_FORMAT; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; @@ -328,13 +327,13 @@ private static HiveColumnHandle toHiveColumnHandle(NationColumn nationColumn, in Optional.empty()); } - private static Properties createSchema() + private static Map createSchema() { - Properties schema = new Properties(); - schema.setProperty(SERIALIZATION_LIB, ORC.getSerde()); - schema.setProperty(FILE_INPUT_FORMAT, ORC.getInputFormat()); - schema.setProperty(TRANSACTIONAL, "true"); - return schema; + return ImmutableMap.builder() + .put(SERIALIZATION_LIB, ORC.getSerde()) + .put(FILE_INPUT_FORMAT, ORC.getInputFormat()) + .put(TRANSACTIONAL, "true") + .buildOrThrow(); } private static void assertEqualsByColumns(Set columns, List actualRows, List expectedRows) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPredicates.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPredicates.java index 416de5dcce8f..bf028866b7ea 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPredicates.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPredicates.java @@ -49,9 +49,9 @@ import java.io.IOException; import java.time.Instant; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.OptionalInt; -import java.util.Properties; import java.util.stream.Collectors; import static io.trino.hive.thrift.metastore.hive_metastoreConstants.FILE_INPUT_FORMAT; @@ -62,7 +62,7 @@ import static io.trino.plugin.hive.HiveTestUtils.getHiveSession; import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; import static io.trino.plugin.hive.util.SerdeConstants.LIST_COLUMNS; -import static io.trino.plugin.hive.util.SerdeConstants.LIST_COLUMN_COMMENTS; +import static io.trino.plugin.hive.util.SerdeConstants.LIST_COLUMN_TYPES; import static io.trino.plugin.hive.util.SerdeConstants.SERIALIZATION_LIB; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.IntegerType.INTEGER; @@ -221,22 +221,13 @@ private static void writeTestFile(ConnectorSession session, TrinoFileSystemFacto fileWriter.commit(); } - private static Properties getTableProperties() + private static Map getTableProperties() { - Properties tableProperties = new Properties(); - tableProperties.setProperty(FILE_INPUT_FORMAT, ORC.getInputFormat()); - tableProperties.setProperty(SERIALIZATION_LIB, ORC.getSerde()); - tableProperties.setProperty( - LIST_COLUMNS, - COLUMNS.stream() - .map(HiveColumnHandle::getName) - .collect(Collectors.joining(","))); - tableProperties.setProperty( - LIST_COLUMN_COMMENTS, - COLUMNS.stream() - .map(HiveColumnHandle::getHiveType) - .map(HiveType::toString) - .collect(Collectors.joining(","))); - return tableProperties; + return ImmutableMap.builder() + .put(FILE_INPUT_FORMAT, ORC.getInputFormat()) + .put(SERIALIZATION_LIB, ORC.getSerde()) + .put(LIST_COLUMNS, COLUMNS.stream().map(HiveColumnHandle::getName).collect(Collectors.joining(","))) + .put(LIST_COLUMN_TYPES, COLUMNS.stream().map(HiveColumnHandle::getHiveType).map(HiveType::toString).collect(Collectors.joining(","))) + .buildOrThrow(); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcWriterOptions.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcWriterOptions.java index a8b52d6db664..3164d0d68c99 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcWriterOptions.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcWriterOptions.java @@ -13,11 +13,12 @@ */ package io.trino.plugin.hive.orc; +import com.google.common.collect.ImmutableMap; import io.airlift.units.DataSize; import io.trino.orc.OrcWriterOptions; import org.junit.jupiter.api.Test; -import java.util.Properties; +import java.util.Map; import static io.airlift.units.DataSize.Unit.KILOBYTE; import static io.airlift.units.DataSize.Unit.MEGABYTE; @@ -76,9 +77,10 @@ public void testOrcWriterOptionsFromOrcWriterConfig() @Test public void testOrcWriterOptionsFromTableProperties() { - Properties tableProperties = new Properties(); - tableProperties.setProperty(ORC_BLOOM_FILTER_COLUMNS_KEY, "column_a, column_b"); - tableProperties.setProperty(ORC_BLOOM_FILTER_FPP_KEY, "0.5"); + Map tableProperties = ImmutableMap.builder() + .put(ORC_BLOOM_FILTER_COLUMNS_KEY, "column_a, column_b") + .put(ORC_BLOOM_FILTER_FPP_KEY, "0.5") + .buildOrThrow(); OrcWriterOptions orcWriterOptions = getOrcWriterOptions(tableProperties, new OrcWriterOptions()); assertThat(orcWriterOptions.getBloomFilterFpp()).isEqualTo(0.5); assertThat(orcWriterOptions.isBloomFilterColumn("column_a")).isTrue(); @@ -89,7 +91,7 @@ public void testOrcWriterOptionsFromTableProperties() @Test public void testOrcWriterOptionsWithInvalidFPPValue() { - Properties tableProperties = createTablePropertiesWithFpp("abc"); + Map tableProperties = createTablePropertiesWithFpp("abc"); assertThatThrownBy(() -> getOrcWriterOptions(tableProperties, new OrcWriterOptions())) .hasMessage("Invalid value for orc_bloom_filter_fpp property: abc"); } @@ -107,11 +109,11 @@ public void testOrcBloomFilterWithInvalidRange() .hasMessage("bloomFilterFpp should be > 0.0 & < 1.0"); } - private static Properties createTablePropertiesWithFpp(String fpp) + private static Map createTablePropertiesWithFpp(String fpp) { - Properties properties = new Properties(); - properties.setProperty(ORC_BLOOM_FILTER_COLUMNS_KEY, "column_with_bloom_filter"); - properties.setProperty(ORC_BLOOM_FILTER_FPP_KEY, fpp); - return properties; + return ImmutableMap.builder() + .put(ORC_BLOOM_FILTER_COLUMNS_KEY, "column_with_bloom_filter") + .put(ORC_BLOOM_FILTER_FPP_KEY, fpp) + .buildOrThrow(); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetUtil.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetUtil.java index dfddba972a4f..30511eb29743 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetUtil.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetUtil.java @@ -13,6 +13,7 @@ */ package io.trino.plugin.hive.parquet; +import com.google.common.collect.ImmutableMap; import io.trino.filesystem.Location; import io.trino.filesystem.memory.MemoryFileSystemFactory; import io.trino.plugin.hive.FileFormatDataSourceStats; @@ -33,7 +34,6 @@ import java.util.List; import java.util.Optional; import java.util.OptionalInt; -import java.util.Properties; import java.util.stream.IntStream; import static com.google.common.base.Preconditions.checkArgument; @@ -70,15 +70,13 @@ public static ConnectorPageSource createPageSource(ConnectorSession session, Fil new ParquetReaderConfig(), new HiveConfig()); - Properties schema = new Properties(); - schema.setProperty(SERIALIZATION_LIB, HiveStorageFormat.PARQUET.getSerde()); return hivePageSourceFactory.createPageSource( session, location, 0, parquetFile.length(), parquetFile.length(), - schema, + ImmutableMap.of(SERIALIZATION_LIB, HiveStorageFormat.PARQUET.getSerde()), columns, domain, Optional.empty(), diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestParquetDecimalScaling.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestParquetDecimalScaling.java index c8e59fc1189c..f9316bac2646 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestParquetDecimalScaling.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestParquetDecimalScaling.java @@ -58,7 +58,7 @@ import static io.trino.plugin.hive.parquet.TestParquetDecimalScaling.ParquetDecimalInsert.maximumValue; import static io.trino.plugin.hive.parquet.TestParquetDecimalScaling.ParquetDecimalInsert.minimumValue; import static io.trino.plugin.hive.util.SerdeConstants.LIST_COLUMNS; -import static io.trino.plugin.hive.util.SerdeConstants.LIST_COLUMN_COMMENTS; +import static io.trino.plugin.hive.util.SerdeConstants.LIST_COLUMN_TYPES; import static io.trino.spi.type.Decimals.overflows; import static io.trino.testing.DataProviders.cartesianProduct; import static io.trino.testing.DataProviders.toDataProvider; @@ -509,7 +509,7 @@ private static Properties createTableProperties(List columnNames, List Date: Tue, 7 Nov 2023 22:17:42 -0800 Subject: [PATCH 223/587] Update to Airlift 238 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index cc7582914043..8b51e490eaf5 100644 --- a/pom.xml +++ b/pom.xml @@ -174,7 +174,7 @@ 2.7.7-1 1.10.2 - 237 + 238 4.13.1 14.0.0 1.11.3 From 65318b2f66e7dbccc516a37b858b409c5faa8152 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Thu, 9 Nov 2023 10:13:27 -0800 Subject: [PATCH 224/587] Remove unnecessary usage of Iceberg version hint method --- .../main/java/io/trino/plugin/iceberg/IcebergMetadata.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java index 05b010d30e0e..e90aeb4f72bf 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java @@ -288,7 +288,6 @@ import static java.util.function.Function.identity; import static java.util.stream.Collectors.joining; import static org.apache.iceberg.ReachableFileUtil.metadataFileLocations; -import static org.apache.iceberg.ReachableFileUtil.versionHintLocation; import static org.apache.iceberg.SnapshotSummary.DELETED_RECORDS_PROP; import static org.apache.iceberg.SnapshotSummary.REMOVED_EQ_DELETES_PROP; import static org.apache.iceberg.SnapshotSummary.REMOVED_POS_DELETES_PROP; @@ -1651,7 +1650,8 @@ private void removeOrphanFiles(Table table, ConnectorSession session, SchemaTabl metadataFileLocations(table, false).stream() .map(IcebergUtil::fileName) .forEach(validMetadataFileNames::add); - validMetadataFileNames.add(fileName(versionHintLocation(table))); + + validMetadataFileNames.add("version-hint.text"); scanAndDeleteInvalidFiles(table, session, schemaTableName, expiration, validDataFileNames.build(), "data"); scanAndDeleteInvalidFiles(table, session, schemaTableName, expiration, validMetadataFileNames.build(), "metadata"); From 4520915e73cab76aba5dc7ff682f5344adb67216 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Thu, 9 Nov 2023 22:30:02 -0800 Subject: [PATCH 225/587] Replace Hadoop-dependent Iceberg LocationProviders --- .../io/trino/plugin/iceberg/IcebergUtil.java | 13 ++- .../iceberg/util/DefaultLocationProvider.java | 61 ++++++++++ .../util/ObjectStoreLocationProvider.java | 105 ++++++++++++++++++ .../util/TestDefaultLocationProvider.java | 62 +++++++++++ .../util/TestObjectStoreLocationProvider.java | 104 +++++++++++++++++ 5 files changed, 343 insertions(+), 2 deletions(-) create mode 100644 plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/util/DefaultLocationProvider.java create mode 100644 plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/util/ObjectStoreLocationProvider.java create mode 100644 plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/util/TestDefaultLocationProvider.java create mode 100644 plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/util/TestObjectStoreLocationProvider.java diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergUtil.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergUtil.java index 62696048fc2d..fe71cdbad39b 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergUtil.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergUtil.java @@ -26,6 +26,8 @@ import io.trino.plugin.iceberg.catalog.IcebergTableOperations; import io.trino.plugin.iceberg.catalog.IcebergTableOperationsProvider; import io.trino.plugin.iceberg.catalog.TrinoCatalog; +import io.trino.plugin.iceberg.util.DefaultLocationProvider; +import io.trino.plugin.iceberg.util.ObjectStoreLocationProvider; import io.trino.spi.TrinoException; import io.trino.spi.connector.ColumnHandle; import io.trino.spi.connector.ColumnMetadata; @@ -151,17 +153,19 @@ import static java.math.RoundingMode.UNNECESSARY; import static java.util.Comparator.comparing; import static java.util.Objects.requireNonNull; -import static org.apache.iceberg.LocationProviders.locationsFor; import static org.apache.iceberg.MetadataTableUtils.createMetadataTableInstance; import static org.apache.iceberg.TableProperties.DEFAULT_FILE_FORMAT; import static org.apache.iceberg.TableProperties.DEFAULT_FILE_FORMAT_DEFAULT; import static org.apache.iceberg.TableProperties.FORMAT_VERSION; +import static org.apache.iceberg.TableProperties.OBJECT_STORE_ENABLED; +import static org.apache.iceberg.TableProperties.OBJECT_STORE_ENABLED_DEFAULT; import static org.apache.iceberg.TableProperties.OBJECT_STORE_PATH; import static org.apache.iceberg.TableProperties.WRITE_DATA_LOCATION; import static org.apache.iceberg.TableProperties.WRITE_LOCATION_PROVIDER_IMPL; import static org.apache.iceberg.TableProperties.WRITE_METADATA_LOCATION; import static org.apache.iceberg.types.Type.TypeID.BINARY; import static org.apache.iceberg.types.Type.TypeID.FIXED; +import static org.apache.iceberg.util.PropertyUtil.propertyAsBoolean; public final class IcebergUtil { @@ -609,7 +613,12 @@ public static LocationProvider getLocationProvider(SchemaTableName schemaTableNa throw new TrinoException(NOT_SUPPORTED, "Table " + schemaTableName + " specifies " + storageProperties.get(WRITE_LOCATION_PROVIDER_IMPL) + " as a location provider. Writing to Iceberg tables with custom location provider is not supported."); } - return locationsFor(tableLocation, storageProperties); + + if (propertyAsBoolean(storageProperties, OBJECT_STORE_ENABLED, OBJECT_STORE_ENABLED_DEFAULT)) { + return new ObjectStoreLocationProvider(tableLocation, storageProperties); + } + + return new DefaultLocationProvider(tableLocation, storageProperties); } public static Schema schemaFromMetadata(List columns) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/util/DefaultLocationProvider.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/util/DefaultLocationProvider.java new file mode 100644 index 000000000000..a63b3ca54066 --- /dev/null +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/util/DefaultLocationProvider.java @@ -0,0 +1,61 @@ +/* + * 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 io.trino.plugin.iceberg.util; + +import org.apache.iceberg.PartitionSpec; +import org.apache.iceberg.StructLike; +import org.apache.iceberg.TableProperties; +import org.apache.iceberg.io.LocationProvider; + +import java.util.Map; + +import static java.lang.String.format; +import static org.apache.iceberg.util.LocationUtil.stripTrailingSlash; + +// based on org.apache.iceberg.LocationProviders.DefaultLocationProvider +public class DefaultLocationProvider + implements LocationProvider +{ + private final String dataLocation; + + public DefaultLocationProvider(String tableLocation, Map properties) + { + this.dataLocation = stripTrailingSlash(dataLocation(properties, tableLocation)); + } + + @SuppressWarnings("deprecation") + private static String dataLocation(Map properties, String tableLocation) + { + String dataLocation = properties.get(TableProperties.WRITE_DATA_LOCATION); + if (dataLocation == null) { + dataLocation = properties.get(TableProperties.WRITE_FOLDER_STORAGE_LOCATION); + if (dataLocation == null) { + dataLocation = format("%s/data", stripTrailingSlash(tableLocation)); + } + } + return dataLocation; + } + + @Override + public String newDataLocation(PartitionSpec spec, StructLike partitionData, String filename) + { + return "%s/%s/%s".formatted(dataLocation, spec.partitionToPath(partitionData), filename); + } + + @Override + public String newDataLocation(String filename) + { + return "%s/%s".formatted(dataLocation, filename); + } +} diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/util/ObjectStoreLocationProvider.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/util/ObjectStoreLocationProvider.java new file mode 100644 index 000000000000..fb552c381d87 --- /dev/null +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/util/ObjectStoreLocationProvider.java @@ -0,0 +1,105 @@ +/* + * 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 io.trino.plugin.iceberg.util; + +import com.google.common.hash.HashFunction; +import io.trino.filesystem.Location; +import org.apache.iceberg.PartitionSpec; +import org.apache.iceberg.StructLike; +import org.apache.iceberg.TableProperties; +import org.apache.iceberg.io.LocationProvider; + +import java.util.Base64; +import java.util.Map; + +import static com.google.common.hash.Hashing.murmur3_32_fixed; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.apache.iceberg.util.LocationUtil.stripTrailingSlash; + +// based on org.apache.iceberg.LocationProviders.ObjectStoreLocationProvider +public class ObjectStoreLocationProvider + implements LocationProvider +{ + private static final HashFunction HASH_FUNC = murmur3_32_fixed(); + private static final Base64.Encoder BASE64_ENCODER = Base64.getUrlEncoder().withoutPadding(); + private final String storageLocation; + private final String context; + + public ObjectStoreLocationProvider(String tableLocation, Map properties) + { + this.storageLocation = stripTrailingSlash(dataLocation(properties, tableLocation)); + // if the storage location is within the table prefix, don't add table and database name context + this.context = storageLocation.startsWith(stripTrailingSlash(tableLocation)) ? null : pathContext(tableLocation); + } + + @SuppressWarnings("deprecation") + private static String dataLocation(Map properties, String tableLocation) + { + String dataLocation = properties.get(TableProperties.WRITE_DATA_LOCATION); + if (dataLocation == null) { + dataLocation = properties.get(TableProperties.OBJECT_STORE_PATH); + if (dataLocation == null) { + dataLocation = properties.get(TableProperties.WRITE_FOLDER_STORAGE_LOCATION); + if (dataLocation == null) { + dataLocation = "%s/data".formatted(stripTrailingSlash(tableLocation)); + } + } + } + return dataLocation; + } + + @Override + public String newDataLocation(PartitionSpec spec, StructLike partitionData, String filename) + { + return newDataLocation("%s/%s".formatted(spec.partitionToPath(partitionData), filename)); + } + + @Override + public String newDataLocation(String filename) + { + String hash = computeHash(filename); + if (context != null) { + return "%s/%s/%s/%s".formatted(storageLocation, hash, context, filename); + } + return "%s/%s/%s".formatted(storageLocation, hash, filename); + } + + private static String pathContext(String tableLocation) + { + Location location; + String name; + try { + location = Location.of(stripTrailingSlash(tableLocation)); + name = location.fileName(); + } + catch (IllegalArgumentException | IllegalStateException e) { + return null; + } + + try { + String parent = stripTrailingSlash(location.parentDirectory().path()); + parent = parent.substring(parent.lastIndexOf('/') + 1); + return "%s/%s".formatted(parent, name); + } + catch (IllegalArgumentException | IllegalStateException e) { + return name; + } + } + + private static String computeHash(String fileName) + { + byte[] bytes = HASH_FUNC.hashString(fileName, UTF_8).asBytes(); + return BASE64_ENCODER.encodeToString(bytes); + } +} diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/util/TestDefaultLocationProvider.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/util/TestDefaultLocationProvider.java new file mode 100644 index 000000000000..13aba3879d37 --- /dev/null +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/util/TestDefaultLocationProvider.java @@ -0,0 +1,62 @@ +/* + * 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 io.trino.plugin.iceberg.util; + +import org.apache.iceberg.TableProperties; +import org.apache.iceberg.io.LocationProvider; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class TestDefaultLocationProvider +{ + @Test + void testDefault() + { + LocationProvider provider = new DefaultLocationProvider("s3://table-location/", Map.of()); + + assertThat(provider.newDataLocation("test")) + .isEqualTo("s3://table-location/data/test"); + } + + @Test + void testDefaultWithCustomDataLocation() + { + LocationProvider provider = new DefaultLocationProvider("s3://table-location/", Map.of( + TableProperties.WRITE_DATA_LOCATION, "s3://write-location/")); + + assertThat(provider.newDataLocation("test")) + .isEqualTo("s3://write-location/test"); + } + + @SuppressWarnings("deprecation") + @Test + void testDefaultPropertyResolution() + { + LocationProvider provider = new DefaultLocationProvider("s3://table-location/", Map.of( + TableProperties.WRITE_FOLDER_STORAGE_LOCATION, "s3://folder-location/")); + + assertThat(provider.newDataLocation("test")) + .isEqualTo("s3://folder-location/test"); + + provider = new DefaultLocationProvider("s3://table-location/", Map.of( + TableProperties.WRITE_FOLDER_STORAGE_LOCATION, "s3://folder-location/", + TableProperties.WRITE_DATA_LOCATION, "s3://data-location/")); + + assertThat(provider.newDataLocation("test")) + .isEqualTo("s3://data-location/test"); + } +} diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/util/TestObjectStoreLocationProvider.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/util/TestObjectStoreLocationProvider.java new file mode 100644 index 000000000000..3bab96bc719b --- /dev/null +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/util/TestObjectStoreLocationProvider.java @@ -0,0 +1,104 @@ +/* + * 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 io.trino.plugin.iceberg.util; + +import org.apache.iceberg.TableProperties; +import org.apache.iceberg.io.LocationProvider; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class TestObjectStoreLocationProvider +{ + @Test + void testObjectStorageWithinTableLocation() + { + LocationProvider provider = new ObjectStoreLocationProvider("s3://table-location/xyz", Map.of()); + + assertThat(provider.newDataLocation("test")) + .isEqualTo("s3://table-location/xyz/data/E9Jrug/test"); + } + + @Test + void testObjectStorageWithContextEmpty() + { + LocationProvider provider = new ObjectStoreLocationProvider( + "s3://table-location/", + Map.of(TableProperties.WRITE_DATA_LOCATION, "s3://data-location/write/")); + + assertThat(provider.newDataLocation("test")) + .isEqualTo("s3://data-location/write/E9Jrug/test"); + } + + @Test + void testObjectStorageWithContextTable() + { + LocationProvider provider = new ObjectStoreLocationProvider( + "s3://table-location/xyz", + Map.of(TableProperties.WRITE_DATA_LOCATION, "s3://data-location/write/")); + + assertThat(provider.newDataLocation("test")) + .isEqualTo("s3://data-location/write/E9Jrug/xyz/test"); + } + + @Test + void testObjectStorageWithContextDatabaseTable() + { + LocationProvider provider = new ObjectStoreLocationProvider( + "s3://table-location/abc/xyz", + Map.of(TableProperties.WRITE_DATA_LOCATION, "s3://data-location/write/")); + + assertThat(provider.newDataLocation("test")) + .isEqualTo("s3://data-location/write/E9Jrug/abc/xyz/test"); + } + + @Test + void testObjectStorageWithContextPrefixDatabaseTable() + { + LocationProvider provider = new ObjectStoreLocationProvider( + "s3://table-location/hello/world/abc/xyz", + Map.of(TableProperties.WRITE_DATA_LOCATION, "s3://data-location/write/")); + + assertThat(provider.newDataLocation("test")) + .isEqualTo("s3://data-location/write/E9Jrug/abc/xyz/test"); + } + + @SuppressWarnings("deprecation") + @Test + void testObjectStoragePropertyResolution() + { + LocationProvider provider = new ObjectStoreLocationProvider("s3://table-location/", Map.of( + TableProperties.WRITE_FOLDER_STORAGE_LOCATION, "s3://folder-location/xyz")); + + assertThat(provider.newDataLocation("test")) + .isEqualTo("s3://folder-location/xyz/E9Jrug/test"); + + provider = new ObjectStoreLocationProvider("s3://table-location/", Map.of( + TableProperties.WRITE_FOLDER_STORAGE_LOCATION, "s3://folder-location/abc", + TableProperties.OBJECT_STORE_PATH, "s3://object-location/xyz")); + + assertThat(provider.newDataLocation("test")) + .isEqualTo("s3://object-location/xyz/E9Jrug/test"); + + provider = new ObjectStoreLocationProvider("s3://table-location/", Map.of( + TableProperties.WRITE_FOLDER_STORAGE_LOCATION, "s3://folder-location/abc", + TableProperties.OBJECT_STORE_PATH, "s3://object-location/abc", + TableProperties.WRITE_DATA_LOCATION, "s3://data-location/xyz")); + + assertThat(provider.newDataLocation("test")) + .isEqualTo("s3://data-location/xyz/E9Jrug/test"); + } +} From 7dcec506c4b4363519a9e9a8de40cb29caa71b6f Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sat, 11 Nov 2023 01:10:05 -0800 Subject: [PATCH 226/587] Extract OrcTypeConverter utility class --- .../iceberg/IcebergFileWriterFactory.java | 2 +- .../plugin/iceberg/IcebergOrcFileWriter.java | 2 +- .../iceberg/IcebergPageSourceProvider.java | 4 +- .../trino/plugin/iceberg/TypeConverter.java | 150 --------------- .../plugin/iceberg/util/OrcTypeConverter.java | 172 ++++++++++++++++++ ...stIcebergNodeLocalDynamicSplitPruning.java | 3 +- 6 files changed, 178 insertions(+), 155 deletions(-) create mode 100644 plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/util/OrcTypeConverter.java diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergFileWriterFactory.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergFileWriterFactory.java index b80236f45c9c..8da4680c3747 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergFileWriterFactory.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergFileWriterFactory.java @@ -71,8 +71,8 @@ import static io.trino.plugin.iceberg.IcebergSessionProperties.getParquetWriterPageSize; import static io.trino.plugin.iceberg.IcebergSessionProperties.isOrcWriterValidate; import static io.trino.plugin.iceberg.IcebergTableProperties.ORC_BLOOM_FILTER_FPP; -import static io.trino.plugin.iceberg.TypeConverter.toOrcType; import static io.trino.plugin.iceberg.TypeConverter.toTrinoType; +import static io.trino.plugin.iceberg.util.OrcTypeConverter.toOrcType; import static io.trino.plugin.iceberg.util.PrimitiveTypeMapBuilder.makeTypeMap; import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; import static java.lang.Double.parseDouble; diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergOrcFileWriter.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergOrcFileWriter.java index 6eb0c19f289b..21e4586e8eb7 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergOrcFileWriter.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergOrcFileWriter.java @@ -60,7 +60,7 @@ import static com.google.common.base.Verify.verify; import static io.trino.orc.metadata.OrcColumnId.ROOT_COLUMN; import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; -import static io.trino.plugin.iceberg.TypeConverter.ORC_ICEBERG_ID_KEY; +import static io.trino.plugin.iceberg.util.OrcTypeConverter.ORC_ICEBERG_ID_KEY; import static io.trino.spi.type.Timestamps.MICROSECONDS_PER_MILLISECOND; import static java.lang.Math.toIntExact; import static java.util.Objects.requireNonNull; diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergPageSourceProvider.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergPageSourceProvider.java index beac48fc875a..e8bc587d93fb 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergPageSourceProvider.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergPageSourceProvider.java @@ -177,10 +177,10 @@ import static io.trino.plugin.iceberg.IcebergUtil.getColumnHandle; import static io.trino.plugin.iceberg.IcebergUtil.getPartitionKeys; import static io.trino.plugin.iceberg.IcebergUtil.schemaFromHandles; -import static io.trino.plugin.iceberg.TypeConverter.ICEBERG_BINARY_TYPE; -import static io.trino.plugin.iceberg.TypeConverter.ORC_ICEBERG_ID_KEY; import static io.trino.plugin.iceberg.delete.EqualityDeleteFilter.readEqualityDeletes; import static io.trino.plugin.iceberg.delete.PositionDeleteFilter.readPositionDeletes; +import static io.trino.plugin.iceberg.util.OrcTypeConverter.ICEBERG_BINARY_TYPE; +import static io.trino.plugin.iceberg.util.OrcTypeConverter.ORC_ICEBERG_ID_KEY; import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; import static io.trino.spi.block.PageBuilderStatus.DEFAULT_MAX_PAGE_SIZE_IN_BYTES; import static io.trino.spi.predicate.Utils.nativeValueToBlock; diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/TypeConverter.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/TypeConverter.java index 5840560106c3..1380f6676c11 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/TypeConverter.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/TypeConverter.java @@ -14,11 +14,6 @@ package io.trino.plugin.iceberg; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import io.trino.orc.metadata.ColumnMetadata; -import io.trino.orc.metadata.OrcColumnId; -import io.trino.orc.metadata.OrcType; -import io.trino.orc.metadata.OrcType.OrcTypeKind; import io.trino.spi.TrinoException; import io.trino.spi.type.ArrayType; import io.trino.spi.type.BigintType; @@ -41,12 +36,10 @@ import io.trino.spi.type.UuidType; import io.trino.spi.type.VarbinaryType; import io.trino.spi.type.VarcharType; -import org.apache.iceberg.Schema; import org.apache.iceberg.types.Types; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; @@ -61,11 +54,6 @@ public final class TypeConverter { - public static final String ORC_ICEBERG_ID_KEY = "iceberg.id"; - public static final String ORC_ICEBERG_REQUIRED_KEY = "iceberg.required"; - public static final String ICEBERG_LONG_TYPE = "iceberg.long-type"; - public static final String ICEBERG_BINARY_TYPE = "iceberg.binary-type"; - private TypeConverter() {} public static Type toTrinoType(org.apache.iceberg.types.Type type, TypeManager typeManager) @@ -246,142 +234,4 @@ private static void checkExactlyOne(Optional columnIdentity, Opt } throw new IllegalArgumentException("Either a column identity or nextFieldId is expected"); } - - public static ColumnMetadata toOrcType(Schema schema) - { - return new ColumnMetadata<>(toOrcStructType(0, schema.asStruct(), ImmutableMap.of())); - } - - private static List toOrcType(int nextFieldTypeIndex, org.apache.iceberg.types.Type type, Map attributes) - { - switch (type.typeId()) { - case BOOLEAN: - return ImmutableList.of(new OrcType(OrcTypeKind.BOOLEAN, ImmutableList.of(), ImmutableList.of(), Optional.empty(), Optional.empty(), Optional.empty(), attributes)); - case INTEGER: - return ImmutableList.of(new OrcType(OrcTypeKind.INT, ImmutableList.of(), ImmutableList.of(), Optional.empty(), Optional.empty(), Optional.empty(), attributes)); - case LONG: - return ImmutableList.of(new OrcType(OrcTypeKind.LONG, ImmutableList.of(), ImmutableList.of(), Optional.empty(), Optional.empty(), Optional.empty(), attributes)); - case FLOAT: - return ImmutableList.of(new OrcType(OrcTypeKind.FLOAT, ImmutableList.of(), ImmutableList.of(), Optional.empty(), Optional.empty(), Optional.empty(), attributes)); - case DOUBLE: - return ImmutableList.of(new OrcType(OrcTypeKind.DOUBLE, ImmutableList.of(), ImmutableList.of(), Optional.empty(), Optional.empty(), Optional.empty(), attributes)); - case DATE: - return ImmutableList.of(new OrcType(OrcTypeKind.DATE, ImmutableList.of(), ImmutableList.of(), Optional.empty(), Optional.empty(), Optional.empty(), attributes)); - case TIME: - attributes = ImmutableMap.builder() - .putAll(attributes) - .put(ICEBERG_LONG_TYPE, "TIME") - .buildOrThrow(); - return ImmutableList.of(new OrcType(OrcTypeKind.LONG, ImmutableList.of(), ImmutableList.of(), Optional.empty(), Optional.empty(), Optional.empty(), attributes)); - case TIMESTAMP: - OrcTypeKind timestampKind = ((Types.TimestampType) type).shouldAdjustToUTC() ? OrcTypeKind.TIMESTAMP_INSTANT : OrcTypeKind.TIMESTAMP; - return ImmutableList.of(new OrcType(timestampKind, ImmutableList.of(), ImmutableList.of(), Optional.empty(), Optional.empty(), Optional.empty(), attributes)); - case STRING: - return ImmutableList.of(new OrcType(OrcTypeKind.STRING, ImmutableList.of(), ImmutableList.of(), Optional.empty(), Optional.empty(), Optional.empty(), attributes)); - case FIXED: - return ImmutableList.of(new OrcType(OrcTypeKind.BINARY, ImmutableList.of(), ImmutableList.of(), Optional.empty(), Optional.empty(), Optional.empty(), attributes)); - case BINARY: - return ImmutableList.of(new OrcType(OrcTypeKind.BINARY, ImmutableList.of(), ImmutableList.of(), Optional.empty(), Optional.empty(), Optional.empty(), attributes)); - case DECIMAL: - Types.DecimalType decimalType = (Types.DecimalType) type; - return ImmutableList.of(new OrcType(OrcTypeKind.DECIMAL, ImmutableList.of(), ImmutableList.of(), Optional.empty(), Optional.of(decimalType.precision()), Optional.of(decimalType.scale()), attributes)); - case UUID: - attributes = ImmutableMap.builder() - .putAll(attributes) - .put(ICEBERG_BINARY_TYPE, "UUID") - .buildOrThrow(); - return ImmutableList.of(new OrcType(OrcTypeKind.BINARY, ImmutableList.of(), ImmutableList.of(), Optional.empty(), Optional.empty(), Optional.empty(), attributes)); - case STRUCT: - return toOrcStructType(nextFieldTypeIndex, (Types.StructType) type, attributes); - case LIST: - return toOrcListType(nextFieldTypeIndex, (Types.ListType) type, attributes); - case MAP: - return toOrcMapType(nextFieldTypeIndex, (Types.MapType) type, attributes); - } - throw new TrinoException(NOT_SUPPORTED, "Unsupported Iceberg type: " + type); - } - - private static List toOrcStructType(int nextFieldTypeIndex, Types.StructType structType, Map attributes) - { - nextFieldTypeIndex++; - List fieldTypeIndexes = new ArrayList<>(); - List fieldNames = new ArrayList<>(); - List> fieldTypesList = new ArrayList<>(); - for (Types.NestedField field : structType.fields()) { - fieldTypeIndexes.add(new OrcColumnId(nextFieldTypeIndex)); - fieldNames.add(field.name()); - Map fieldAttributes = ImmutableMap.builder() - .put(ORC_ICEBERG_ID_KEY, Integer.toString(field.fieldId())) - .put(ORC_ICEBERG_REQUIRED_KEY, Boolean.toString(field.isRequired())) - .buildOrThrow(); - List fieldOrcTypes = toOrcType(nextFieldTypeIndex, field.type(), fieldAttributes); - fieldTypesList.add(fieldOrcTypes); - nextFieldTypeIndex += fieldOrcTypes.size(); - } - - ImmutableList.Builder orcTypes = ImmutableList.builder(); - orcTypes.add(new OrcType( - OrcTypeKind.STRUCT, - fieldTypeIndexes, - fieldNames, - Optional.empty(), - Optional.empty(), - Optional.empty(), - attributes)); - fieldTypesList.forEach(orcTypes::addAll); - - return orcTypes.build(); - } - - private static List toOrcListType(int nextFieldTypeIndex, Types.ListType listType, Map attributes) - { - nextFieldTypeIndex++; - Map elementAttributes = ImmutableMap.builder() - .put(ORC_ICEBERG_ID_KEY, Integer.toString(listType.elementId())) - .put(ORC_ICEBERG_REQUIRED_KEY, Boolean.toString(listType.isElementRequired())) - .buildOrThrow(); - List itemTypes = toOrcType(nextFieldTypeIndex, listType.elementType(), elementAttributes); - - List orcTypes = new ArrayList<>(); - orcTypes.add(new OrcType( - OrcTypeKind.LIST, - ImmutableList.of(new OrcColumnId(nextFieldTypeIndex)), - ImmutableList.of("item"), - Optional.empty(), - Optional.empty(), - Optional.empty(), - attributes)); - - orcTypes.addAll(itemTypes); - return orcTypes; - } - - private static List toOrcMapType(int nextFieldTypeIndex, Types.MapType mapType, Map attributes) - { - nextFieldTypeIndex++; - Map keyAttributes = ImmutableMap.builder() - .put(ORC_ICEBERG_ID_KEY, Integer.toString(mapType.keyId())) - .put(ORC_ICEBERG_REQUIRED_KEY, Boolean.toString(true)) - .buildOrThrow(); - List keyTypes = toOrcType(nextFieldTypeIndex, mapType.keyType(), keyAttributes); - Map valueAttributes = ImmutableMap.builder() - .put(ORC_ICEBERG_ID_KEY, Integer.toString(mapType.valueId())) - .put(ORC_ICEBERG_REQUIRED_KEY, Boolean.toString(mapType.isValueRequired())) - .buildOrThrow(); - List valueTypes = toOrcType(nextFieldTypeIndex + keyTypes.size(), mapType.valueType(), valueAttributes); - - List orcTypes = new ArrayList<>(); - orcTypes.add(new OrcType( - OrcTypeKind.MAP, - ImmutableList.of(new OrcColumnId(nextFieldTypeIndex), new OrcColumnId(nextFieldTypeIndex + keyTypes.size())), - ImmutableList.of("key", "value"), - Optional.empty(), - Optional.empty(), - Optional.empty(), - attributes)); - - orcTypes.addAll(keyTypes); - orcTypes.addAll(valueTypes); - return orcTypes; - } } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/util/OrcTypeConverter.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/util/OrcTypeConverter.java new file mode 100644 index 000000000000..744f17d596c1 --- /dev/null +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/util/OrcTypeConverter.java @@ -0,0 +1,172 @@ +/* + * 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 io.trino.plugin.iceberg.util; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.trino.orc.metadata.ColumnMetadata; +import io.trino.orc.metadata.OrcColumnId; +import io.trino.orc.metadata.OrcType; +import io.trino.orc.metadata.OrcType.OrcTypeKind; +import org.apache.iceberg.Schema; +import org.apache.iceberg.types.Type; +import org.apache.iceberg.types.Types.DecimalType; +import org.apache.iceberg.types.Types.ListType; +import org.apache.iceberg.types.Types.MapType; +import org.apache.iceberg.types.Types.NestedField; +import org.apache.iceberg.types.Types.StructType; +import org.apache.iceberg.types.Types.TimestampType; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public final class OrcTypeConverter +{ + public static final String ORC_ICEBERG_ID_KEY = "iceberg.id"; + public static final String ORC_ICEBERG_REQUIRED_KEY = "iceberg.required"; + public static final String ICEBERG_LONG_TYPE = "iceberg.long-type"; + public static final String ICEBERG_BINARY_TYPE = "iceberg.binary-type"; + + private OrcTypeConverter() {} + + public static ColumnMetadata toOrcType(Schema schema) + { + return new ColumnMetadata<>(toOrcStructType(0, schema.asStruct(), ImmutableMap.of())); + } + + private static List toOrcType(int nextFieldTypeIndex, Type type, Map attributes) + { + return switch (type.typeId()) { + case BOOLEAN -> ImmutableList.of(new OrcType(OrcTypeKind.BOOLEAN, ImmutableList.of(), ImmutableList.of(), Optional.empty(), Optional.empty(), Optional.empty(), attributes)); + case INTEGER -> ImmutableList.of(new OrcType(OrcTypeKind.INT, ImmutableList.of(), ImmutableList.of(), Optional.empty(), Optional.empty(), Optional.empty(), attributes)); + case LONG -> ImmutableList.of(new OrcType(OrcTypeKind.LONG, ImmutableList.of(), ImmutableList.of(), Optional.empty(), Optional.empty(), Optional.empty(), attributes)); + case FLOAT -> ImmutableList.of(new OrcType(OrcTypeKind.FLOAT, ImmutableList.of(), ImmutableList.of(), Optional.empty(), Optional.empty(), Optional.empty(), attributes)); + case DOUBLE -> ImmutableList.of(new OrcType(OrcTypeKind.DOUBLE, ImmutableList.of(), ImmutableList.of(), Optional.empty(), Optional.empty(), Optional.empty(), attributes)); + case DATE -> ImmutableList.of(new OrcType(OrcTypeKind.DATE, ImmutableList.of(), ImmutableList.of(), Optional.empty(), Optional.empty(), Optional.empty(), attributes)); + case TIME -> { + attributes = ImmutableMap.builder() + .putAll(attributes) + .put(ICEBERG_LONG_TYPE, "TIME") + .buildOrThrow(); + yield ImmutableList.of(new OrcType(OrcTypeKind.LONG, ImmutableList.of(), ImmutableList.of(), Optional.empty(), Optional.empty(), Optional.empty(), attributes)); + } + case TIMESTAMP -> { + OrcTypeKind timestampKind = ((TimestampType) type).shouldAdjustToUTC() ? OrcTypeKind.TIMESTAMP_INSTANT : OrcTypeKind.TIMESTAMP; + yield ImmutableList.of(new OrcType(timestampKind, ImmutableList.of(), ImmutableList.of(), Optional.empty(), Optional.empty(), Optional.empty(), attributes)); + } + case STRING -> ImmutableList.of(new OrcType(OrcTypeKind.STRING, ImmutableList.of(), ImmutableList.of(), Optional.empty(), Optional.empty(), Optional.empty(), attributes)); + case FIXED, BINARY -> ImmutableList.of(new OrcType(OrcTypeKind.BINARY, ImmutableList.of(), ImmutableList.of(), Optional.empty(), Optional.empty(), Optional.empty(), attributes)); + case DECIMAL -> { + DecimalType decimalType = (DecimalType) type; + yield ImmutableList.of(new OrcType(OrcTypeKind.DECIMAL, ImmutableList.of(), ImmutableList.of(), Optional.empty(), Optional.of(decimalType.precision()), Optional.of(decimalType.scale()), attributes)); + } + case UUID -> { + attributes = ImmutableMap.builder() + .putAll(attributes) + .put(ICEBERG_BINARY_TYPE, "UUID") + .buildOrThrow(); + yield ImmutableList.of(new OrcType(OrcTypeKind.BINARY, ImmutableList.of(), ImmutableList.of(), Optional.empty(), Optional.empty(), Optional.empty(), attributes)); + } + case STRUCT -> toOrcStructType(nextFieldTypeIndex, (StructType) type, attributes); + case LIST -> toOrcListType(nextFieldTypeIndex, (ListType) type, attributes); + case MAP -> toOrcMapType(nextFieldTypeIndex, (MapType) type, attributes); + }; + } + + private static List toOrcStructType(int nextFieldTypeIndex, StructType structType, Map attributes) + { + nextFieldTypeIndex++; + + List fieldTypeIndexes = new ArrayList<>(); + List fieldNames = new ArrayList<>(); + List> fieldTypesList = new ArrayList<>(); + for (NestedField field : structType.fields()) { + fieldTypeIndexes.add(new OrcColumnId(nextFieldTypeIndex)); + fieldNames.add(field.name()); + Map fieldAttributes = ImmutableMap.builder() + .put(ORC_ICEBERG_ID_KEY, Integer.toString(field.fieldId())) + .put(ORC_ICEBERG_REQUIRED_KEY, Boolean.toString(field.isRequired())) + .buildOrThrow(); + List fieldOrcTypes = toOrcType(nextFieldTypeIndex, field.type(), fieldAttributes); + fieldTypesList.add(fieldOrcTypes); + nextFieldTypeIndex += fieldOrcTypes.size(); + } + + return ImmutableList.builder() + .add(new OrcType( + OrcTypeKind.STRUCT, + fieldTypeIndexes, + fieldNames, + Optional.empty(), + Optional.empty(), + Optional.empty(), + attributes)) + .addAll(fieldTypesList.stream().flatMap(List::stream).iterator()) + .build(); + } + + private static List toOrcListType(int nextFieldTypeIndex, ListType listType, Map attributes) + { + nextFieldTypeIndex++; + + Map elementAttributes = ImmutableMap.builder() + .put(ORC_ICEBERG_ID_KEY, Integer.toString(listType.elementId())) + .put(ORC_ICEBERG_REQUIRED_KEY, Boolean.toString(listType.isElementRequired())) + .buildOrThrow(); + List itemTypes = toOrcType(nextFieldTypeIndex, listType.elementType(), elementAttributes); + + return ImmutableList.builder() + .add(new OrcType( + OrcTypeKind.LIST, + ImmutableList.of(new OrcColumnId(nextFieldTypeIndex)), + ImmutableList.of("item"), + Optional.empty(), + Optional.empty(), + Optional.empty(), + attributes)) + .addAll(itemTypes) + .build(); + } + + private static List toOrcMapType(int nextFieldTypeIndex, MapType mapType, Map attributes) + { + nextFieldTypeIndex++; + + List keyTypes = toOrcType(nextFieldTypeIndex, mapType.keyType(), ImmutableMap.builder() + .put(ORC_ICEBERG_ID_KEY, Integer.toString(mapType.keyId())) + .put(ORC_ICEBERG_REQUIRED_KEY, Boolean.toString(true)) + .buildOrThrow()); + + Map valueAttributes = ImmutableMap.builder() + .put(ORC_ICEBERG_ID_KEY, Integer.toString(mapType.valueId())) + .put(ORC_ICEBERG_REQUIRED_KEY, Boolean.toString(mapType.isValueRequired())) + .buildOrThrow(); + List valueTypes = toOrcType(nextFieldTypeIndex + keyTypes.size(), mapType.valueType(), valueAttributes); + + return ImmutableList.builder() + .add(new OrcType( + OrcTypeKind.MAP, + ImmutableList.of(new OrcColumnId(nextFieldTypeIndex), new OrcColumnId(nextFieldTypeIndex + keyTypes.size())), + ImmutableList.of("key", "value"), + Optional.empty(), + Optional.empty(), + Optional.empty(), + attributes)) + .addAll(keyTypes) + .addAll(valueTypes) + .build(); + } +} diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergNodeLocalDynamicSplitPruning.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergNodeLocalDynamicSplitPruning.java index 4e44345bf34e..581c374e8eb3 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergNodeLocalDynamicSplitPruning.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergNodeLocalDynamicSplitPruning.java @@ -69,6 +69,7 @@ import static io.trino.plugin.hive.HiveType.HIVE_STRING; import static io.trino.plugin.iceberg.ColumnIdentity.TypeCategory.PRIMITIVE; import static io.trino.plugin.iceberg.IcebergFileFormat.ORC; +import static io.trino.plugin.iceberg.util.OrcTypeConverter.toOrcType; import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.testing.TestingHandles.TEST_CATALOG_HANDLE; @@ -137,7 +138,7 @@ private static void writeOrcContent(TrinoOutputFile outputFile) OutputStreamOrcDataSink.create(outputFile), columnNames, types, - TypeConverter.toOrcType(TABLE_SCHEMA), + toOrcType(TABLE_SCHEMA), NONE, new OrcWriterOptions(), ImmutableMap.of(), From 2d7dbc8a12bad694f0f9e8de32dfe9470d9da7da Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sat, 11 Nov 2023 01:26:31 -0800 Subject: [PATCH 227/587] Extract OrcIcebergIds utility class --- .../iceberg/IcebergPageSourceProvider.java | 68 +---------- .../plugin/iceberg/util/OrcIcebergIds.java | 107 ++++++++++++++++++ 2 files changed, 109 insertions(+), 66 deletions(-) create mode 100644 plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/util/OrcIcebergIds.java diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergPageSourceProvider.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergPageSourceProvider.java index e8bc587d93fb..1342adb698af 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergPageSourceProvider.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergPageSourceProvider.java @@ -21,7 +21,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.graph.Traverser; import com.google.inject.Inject; import io.airlift.slice.Slice; import io.trino.annotation.NotThreadSafe; @@ -39,7 +38,6 @@ import io.trino.orc.OrcRecordReader; import io.trino.orc.TupleDomainOrcPredicate; import io.trino.orc.TupleDomainOrcPredicate.TupleDomainOrcPredicateBuilder; -import io.trino.orc.metadata.OrcType; import io.trino.parquet.BloomFilterStore; import io.trino.parquet.Field; import io.trino.parquet.ParquetCorruptionException; @@ -179,6 +177,7 @@ import static io.trino.plugin.iceberg.IcebergUtil.schemaFromHandles; import static io.trino.plugin.iceberg.delete.EqualityDeleteFilter.readEqualityDeletes; import static io.trino.plugin.iceberg.delete.PositionDeleteFilter.readPositionDeletes; +import static io.trino.plugin.iceberg.util.OrcIcebergIds.fileColumnsByIcebergId; import static io.trino.plugin.iceberg.util.OrcTypeConverter.ICEBERG_BINARY_TYPE; import static io.trino.plugin.iceberg.util.OrcTypeConverter.ORC_ICEBERG_ID_KEY; import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; @@ -662,14 +661,7 @@ private static ReaderPageSourceWithRowPositions createOrcPageSource( OrcReader reader = OrcReader.createOrcReader(orcDataSource, options) .orElseThrow(() -> new TrinoException(ICEBERG_BAD_DATA, "ORC file is zero length")); - List fileColumns = reader.getRootColumn().getNestedColumns(); - if (nameMapping.isPresent() && !hasIds(reader.getRootColumn())) { - fileColumns = fileColumns.stream() - .map(orcColumn -> setMissingFieldIds(orcColumn, nameMapping.get(), ImmutableList.of(orcColumn.getColumnName()))) - .collect(toImmutableList()); - } - - Map fileColumnsByIcebergId = mapIdsToOrcFileColumns(fileColumns); + Map fileColumnsByIcebergId = fileColumnsByIcebergId(reader, nameMapping); TupleDomainOrcPredicateBuilder predicateBuilder = TupleDomainOrcPredicate.builder() .setBloomFiltersEnabled(options.isBloomFiltersEnabled()); @@ -803,48 +795,6 @@ else if (orcColumn != null) { } } - private static boolean hasIds(OrcColumn column) - { - if (column.getAttributes().containsKey(ORC_ICEBERG_ID_KEY)) { - return true; - } - - return column.getNestedColumns().stream().anyMatch(IcebergPageSourceProvider::hasIds); - } - - private static OrcColumn setMissingFieldIds(OrcColumn column, NameMapping nameMapping, List qualifiedPath) - { - MappedField mappedField = nameMapping.find(qualifiedPath); - - ImmutableMap.Builder attributes = ImmutableMap.builder() - .putAll(column.getAttributes()); - if (mappedField != null && mappedField.id() != null) { - attributes.put(ORC_ICEBERG_ID_KEY, String.valueOf(mappedField.id())); - } - - return new OrcColumn( - column.getPath(), - column.getColumnId(), - column.getColumnName(), - column.getColumnType(), - column.getOrcDataSourceId(), - column.getNestedColumns().stream() - .map(nestedColumn -> { - ImmutableList.Builder nextQualifiedPath = ImmutableList.builder() - .addAll(qualifiedPath); - if (column.getColumnType() == OrcType.OrcTypeKind.LIST) { - // The Trino ORC reader uses "item" for list element names, but the NameMapper expects "element" - nextQualifiedPath.add("element"); - } - else { - nextQualifiedPath.add(nestedColumn.getColumnName()); - } - return setMissingFieldIds(nestedColumn, nameMapping, nextQualifiedPath.build()); - }) - .collect(toImmutableList()), - attributes.buildOrThrow()); - } - /** * Gets the index based dereference chain to get from the readColumnHandle to the expectedColumnHandle */ @@ -865,20 +815,6 @@ private static List applyProjection(ColumnHandle expectedColumnHandle, return dereferenceChain.build(); } - private static Map mapIdsToOrcFileColumns(List columns) - { - ImmutableMap.Builder columnsById = ImmutableMap.builder(); - Traverser.forTree(OrcColumn::getNestedColumns) - .depthFirstPreOrder(columns) - .forEach(column -> { - String fieldId = column.getAttributes().get(ORC_ICEBERG_ID_KEY); - if (fieldId != null) { - columnsById.put(Integer.parseInt(fieldId), column); - } - }); - return columnsById.buildOrThrow(); - } - private static Integer getIcebergFieldId(OrcColumn column) { String icebergId = column.getAttributes().get(ORC_ICEBERG_ID_KEY); diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/util/OrcIcebergIds.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/util/OrcIcebergIds.java new file mode 100644 index 000000000000..0143bb50198f --- /dev/null +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/util/OrcIcebergIds.java @@ -0,0 +1,107 @@ +/* + * 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 io.trino.plugin.iceberg.util; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.graph.Traverser; +import io.trino.orc.OrcColumn; +import io.trino.orc.OrcReader; +import io.trino.orc.metadata.OrcType.OrcTypeKind; +import org.apache.iceberg.mapping.MappedField; +import org.apache.iceberg.mapping.NameMapping; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.trino.plugin.iceberg.util.OrcTypeConverter.ORC_ICEBERG_ID_KEY; + +public final class OrcIcebergIds +{ + private OrcIcebergIds() {} + + public static Map fileColumnsByIcebergId(OrcReader reader, Optional nameMapping) + { + List fileColumns = reader.getRootColumn().getNestedColumns(); + + if (nameMapping.isPresent() && !hasIds(reader.getRootColumn())) { + fileColumns = fileColumns.stream() + .map(orcColumn -> setMissingFieldIds(orcColumn, nameMapping.get(), ImmutableList.of(orcColumn.getColumnName()))) + .collect(toImmutableList()); + } + + return mapIdsToOrcFileColumns(fileColumns); + } + + private static boolean hasIds(OrcColumn column) + { + if (column.getAttributes().containsKey(ORC_ICEBERG_ID_KEY)) { + return true; + } + + return column.getNestedColumns().stream().anyMatch(OrcIcebergIds::hasIds); + } + + private static OrcColumn setMissingFieldIds(OrcColumn column, NameMapping nameMapping, List qualifiedPath) + { + MappedField mappedField = nameMapping.find(qualifiedPath); + + ImmutableMap.Builder attributes = ImmutableMap.builder(); + attributes.putAll(column.getAttributes()); + if ((mappedField != null) && (mappedField.id() != null)) { + attributes.put(ORC_ICEBERG_ID_KEY, String.valueOf(mappedField.id())); + } + + List orcColumns = column.getNestedColumns().stream() + .map(nestedColumn -> setMissingFieldIds(nestedColumn, nameMapping, ImmutableList.builder() + .addAll(qualifiedPath) + .add(pathName(column, nestedColumn)) + .build())) + .collect(toImmutableList()); + + return new OrcColumn( + column.getPath(), + column.getColumnId(), + column.getColumnName(), + column.getColumnType(), + column.getOrcDataSourceId(), + orcColumns, + attributes.buildOrThrow()); + } + + private static String pathName(OrcColumn column, OrcColumn nestedColumn) + { + // Trino ORC reader uses "item" for list element names, but NameMapper expects "element" + if (column.getColumnType() == OrcTypeKind.LIST) { + return "element"; + } + return nestedColumn.getColumnName(); + } + + private static Map mapIdsToOrcFileColumns(List columns) + { + ImmutableMap.Builder columnsById = ImmutableMap.builder(); + Traverser.forTree(OrcColumn::getNestedColumns) + .depthFirstPreOrder(columns) + .forEach(column -> { + String fieldId = column.getAttributes().get(ORC_ICEBERG_ID_KEY); + if (fieldId != null) { + columnsById.put(Integer.parseInt(fieldId), column); + } + }); + return columnsById.buildOrThrow(); + } +} From 3797dbe022f68309ced753e0351a0b9ece8fd8e2 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sun, 12 Nov 2023 11:54:47 -0800 Subject: [PATCH 228/587] Extract OrcMetrics utility class --- .../plugin/iceberg/IcebergOrcFileWriter.java | 249 +-------------- .../trino/plugin/iceberg/util/OrcMetrics.java | 284 ++++++++++++++++++ 2 files changed, 285 insertions(+), 248 deletions(-) create mode 100644 plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/util/OrcMetrics.java diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergOrcFileWriter.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergOrcFileWriter.java index 21e4586e8eb7..3418ed217cf0 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergOrcFileWriter.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergOrcFileWriter.java @@ -13,9 +13,6 @@ */ package io.trino.plugin.iceberg; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import io.airlift.slice.Slice; import io.trino.orc.OrcDataSink; import io.trino.orc.OrcDataSource; import io.trino.orc.OrcWriteValidation; @@ -23,46 +20,23 @@ import io.trino.orc.OrcWriterStats; import io.trino.orc.metadata.ColumnMetadata; import io.trino.orc.metadata.CompressionKind; -import io.trino.orc.metadata.OrcColumnId; import io.trino.orc.metadata.OrcType; -import io.trino.orc.metadata.statistics.BooleanStatistics; -import io.trino.orc.metadata.statistics.ColumnStatistics; -import io.trino.orc.metadata.statistics.DateStatistics; -import io.trino.orc.metadata.statistics.DecimalStatistics; -import io.trino.orc.metadata.statistics.DoubleStatistics; -import io.trino.orc.metadata.statistics.IntegerStatistics; -import io.trino.orc.metadata.statistics.StringStatistics; -import io.trino.orc.metadata.statistics.TimestampStatistics; import io.trino.plugin.hive.WriterKind; import io.trino.plugin.hive.orc.OrcFileWriter; import io.trino.spi.type.Type; import org.apache.iceberg.Metrics; import org.apache.iceberg.MetricsConfig; -import org.apache.iceberg.MetricsModes; -import org.apache.iceberg.MetricsUtil; import org.apache.iceberg.Schema; -import org.apache.iceberg.expressions.Literal; -import org.apache.iceberg.types.Conversions; -import org.apache.iceberg.types.Types; -import org.apache.iceberg.util.BinaryUtil; -import org.apache.iceberg.util.UnicodeUtil; import java.io.Closeable; -import java.math.BigDecimal; -import java.nio.ByteBuffer; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.OptionalInt; -import java.util.Set; import java.util.function.Supplier; -import static com.google.common.base.Verify.verify; -import static io.trino.orc.metadata.OrcColumnId.ROOT_COLUMN; import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; -import static io.trino.plugin.iceberg.util.OrcTypeConverter.ORC_ICEBERG_ID_KEY; -import static io.trino.spi.type.Timestamps.MICROSECONDS_PER_MILLISECOND; -import static java.lang.Math.toIntExact; +import static io.trino.plugin.iceberg.util.OrcMetrics.computeMetrics; import static java.util.Objects.requireNonNull; public class IcebergOrcFileWriter @@ -100,225 +74,4 @@ public Metrics getMetrics() { return computeMetrics(metricsConfig, icebergSchema, orcColumns, orcWriter.getFileRowCount(), orcWriter.getFileStats()); } - - private static Metrics computeMetrics(MetricsConfig metricsConfig, Schema icebergSchema, ColumnMetadata orcColumns, long fileRowCount, Optional> columnStatistics) - { - if (columnStatistics.isEmpty()) { - return new Metrics(fileRowCount, null, null, null, null, null, null); - } - // Columns that are descendants of LIST or MAP types are excluded because: - // 1. Their stats are not used by Apache Iceberg to filter out data files - // 2. Their record count can be larger than table-level row count. There's no good way to calculate nullCounts for them. - // See https://github.com/apache/iceberg/pull/199#discussion_r429443627 - Set excludedColumns = getExcludedColumns(orcColumns); - - ImmutableMap.Builder valueCountsBuilder = ImmutableMap.builder(); - ImmutableMap.Builder nullCountsBuilder = ImmutableMap.builder(); - ImmutableMap.Builder nanCountsBuilder = ImmutableMap.builder(); - ImmutableMap.Builder lowerBoundsBuilder = ImmutableMap.builder(); - ImmutableMap.Builder upperBoundsBuilder = ImmutableMap.builder(); - - // OrcColumnId(0) is the root column that represents file-level schema - for (int i = 1; i < orcColumns.size(); i++) { - OrcColumnId orcColumnId = new OrcColumnId(i); - if (excludedColumns.contains(orcColumnId)) { - continue; - } - OrcType orcColumn = orcColumns.get(orcColumnId); - ColumnStatistics orcColumnStats = columnStatistics.get().get(orcColumnId); - int icebergId = getIcebergId(orcColumn); - Types.NestedField icebergField = icebergSchema.findField(icebergId); - MetricsModes.MetricsMode metricsMode = MetricsUtil.metricsMode(icebergSchema, metricsConfig, icebergId); - if (metricsMode.equals(MetricsModes.None.get())) { - continue; - } - verify(icebergField != null, "Cannot find Iceberg column with ID %s in schema %s", icebergId, icebergSchema); - valueCountsBuilder.put(icebergId, fileRowCount); - if (orcColumnStats.hasNumberOfValues()) { - nullCountsBuilder.put(icebergId, fileRowCount - orcColumnStats.getNumberOfValues()); - } - if (orcColumnStats.getNumberOfNanValues() > 0) { - nanCountsBuilder.put(icebergId, orcColumnStats.getNumberOfNanValues()); - } - - if (!metricsMode.equals(MetricsModes.Counts.get())) { - toIcebergMinMax(orcColumnStats, icebergField.type(), metricsMode).ifPresent(minMax -> { - lowerBoundsBuilder.put(icebergId, minMax.getMin()); - upperBoundsBuilder.put(icebergId, minMax.getMax()); - }); - } - } - Map valueCounts = valueCountsBuilder.buildOrThrow(); - Map nullCounts = nullCountsBuilder.buildOrThrow(); - Map nanCounts = nanCountsBuilder.buildOrThrow(); - Map lowerBounds = lowerBoundsBuilder.buildOrThrow(); - Map upperBounds = upperBoundsBuilder.buildOrThrow(); - return new Metrics( - fileRowCount, - null, // TODO: Add column size accounting to ORC column writers - valueCounts.isEmpty() ? null : valueCounts, - nullCounts.isEmpty() ? null : nullCounts, - nanCounts.isEmpty() ? null : nanCounts, - lowerBounds.isEmpty() ? null : lowerBounds, - upperBounds.isEmpty() ? null : upperBounds); - } - - private static Set getExcludedColumns(ColumnMetadata orcColumns) - { - ImmutableSet.Builder excludedColumns = ImmutableSet.builder(); - populateExcludedColumns(orcColumns, ROOT_COLUMN, false, excludedColumns); - return excludedColumns.build(); - } - - private static void populateExcludedColumns(ColumnMetadata orcColumns, OrcColumnId orcColumnId, boolean exclude, ImmutableSet.Builder excludedColumns) - { - if (exclude) { - excludedColumns.add(orcColumnId); - } - OrcType orcColumn = orcColumns.get(orcColumnId); - switch (orcColumn.getOrcTypeKind()) { - case LIST: - case MAP: - for (OrcColumnId child : orcColumn.getFieldTypeIndexes()) { - populateExcludedColumns(orcColumns, child, true, excludedColumns); - } - return; - case STRUCT: - for (OrcColumnId child : orcColumn.getFieldTypeIndexes()) { - populateExcludedColumns(orcColumns, child, exclude, excludedColumns); - } - return; - default: - // unexpected, TODO throw - } - } - - private static int getIcebergId(OrcType orcColumn) - { - String icebergId = orcColumn.getAttributes().get(ORC_ICEBERG_ID_KEY); - verify(icebergId != null, "ORC column %s doesn't have an associated Iceberg ID", orcColumn); - return Integer.parseInt(icebergId); - } - - private static Optional toIcebergMinMax(ColumnStatistics orcColumnStats, org.apache.iceberg.types.Type icebergType, MetricsModes.MetricsMode metricsModes) - { - BooleanStatistics booleanStatistics = orcColumnStats.getBooleanStatistics(); - if (booleanStatistics != null) { - boolean hasTrueValues = booleanStatistics.getTrueValueCount() != 0; - boolean hasFalseValues = orcColumnStats.getNumberOfValues() != booleanStatistics.getTrueValueCount(); - return Optional.of(new IcebergMinMax(icebergType, !hasFalseValues, hasTrueValues, metricsModes)); - } - - IntegerStatistics integerStatistics = orcColumnStats.getIntegerStatistics(); - if (integerStatistics != null) { - Object min = integerStatistics.getMin(); - Object max = integerStatistics.getMax(); - if (min == null || max == null) { - return Optional.empty(); - } - if (icebergType.typeId() == org.apache.iceberg.types.Type.TypeID.INTEGER) { - min = toIntExact((Long) min); - max = toIntExact((Long) max); - } - return Optional.of(new IcebergMinMax(icebergType, min, max, metricsModes)); - } - DoubleStatistics doubleStatistics = orcColumnStats.getDoubleStatistics(); - if (doubleStatistics != null) { - Object min = doubleStatistics.getMin(); - Object max = doubleStatistics.getMax(); - if (min == null || max == null) { - return Optional.empty(); - } - if (icebergType.typeId() == org.apache.iceberg.types.Type.TypeID.FLOAT) { - min = ((Double) min).floatValue(); - max = ((Double) max).floatValue(); - } - return Optional.of(new IcebergMinMax(icebergType, min, max, metricsModes)); - } - StringStatistics stringStatistics = orcColumnStats.getStringStatistics(); - if (stringStatistics != null) { - Slice min = stringStatistics.getMin(); - Slice max = stringStatistics.getMax(); - if (min == null || max == null) { - return Optional.empty(); - } - return Optional.of(new IcebergMinMax(icebergType, min.toStringUtf8(), max.toStringUtf8(), metricsModes)); - } - DateStatistics dateStatistics = orcColumnStats.getDateStatistics(); - if (dateStatistics != null) { - Integer min = dateStatistics.getMin(); - Integer max = dateStatistics.getMax(); - if (min == null || max == null) { - return Optional.empty(); - } - return Optional.of(new IcebergMinMax(icebergType, min, max, metricsModes)); - } - DecimalStatistics decimalStatistics = orcColumnStats.getDecimalStatistics(); - if (decimalStatistics != null) { - BigDecimal min = decimalStatistics.getMin(); - BigDecimal max = decimalStatistics.getMax(); - if (min == null || max == null) { - return Optional.empty(); - } - min = min.setScale(((Types.DecimalType) icebergType).scale()); - max = max.setScale(((Types.DecimalType) icebergType).scale()); - return Optional.of(new IcebergMinMax(icebergType, min, max, metricsModes)); - } - TimestampStatistics timestampStatistics = orcColumnStats.getTimestampStatistics(); - if (timestampStatistics != null) { - Long min = timestampStatistics.getMin(); - Long max = timestampStatistics.getMax(); - if (min == null || max == null) { - return Optional.empty(); - } - // Since ORC timestamp statistics are truncated to millisecond precision, this can cause some column values to fall outside the stats range. - // We are appending 999 microseconds to account for the fact that Trino ORC writer truncates timestamps. - return Optional.of(new IcebergMinMax(icebergType, min * MICROSECONDS_PER_MILLISECOND, (max * MICROSECONDS_PER_MILLISECOND) + (MICROSECONDS_PER_MILLISECOND - 1), metricsModes)); - } - return Optional.empty(); - } - - private static class IcebergMinMax - { - private ByteBuffer min; - private ByteBuffer max; - - private IcebergMinMax(org.apache.iceberg.types.Type type, Object min, Object max, MetricsModes.MetricsMode metricsMode) - { - if (metricsMode instanceof MetricsModes.Full) { - this.min = Conversions.toByteBuffer(type, min); - this.max = Conversions.toByteBuffer(type, max); - } - else if (metricsMode instanceof MetricsModes.Truncate truncateMode) { - int truncateLength = truncateMode.length(); - switch (type.typeId()) { - case STRING: - this.min = UnicodeUtil.truncateStringMin(Literal.of((CharSequence) min), truncateLength).toByteBuffer(); - this.max = UnicodeUtil.truncateStringMax(Literal.of((CharSequence) max), truncateLength).toByteBuffer(); - break; - case FIXED: - case BINARY: - this.min = BinaryUtil.truncateBinaryMin(Literal.of((ByteBuffer) min), truncateLength).toByteBuffer(); - this.max = BinaryUtil.truncateBinaryMax(Literal.of((ByteBuffer) max), truncateLength).toByteBuffer(); - break; - default: - this.min = Conversions.toByteBuffer(type, min); - this.max = Conversions.toByteBuffer(type, max); - } - } - else { - throw new UnsupportedOperationException("Unsupported metrics mode for Iceberg Max/Min Bound: " + metricsMode); - } - } - - public ByteBuffer getMin() - { - return min; - } - - public ByteBuffer getMax() - { - return max; - } - } } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/util/OrcMetrics.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/util/OrcMetrics.java new file mode 100644 index 000000000000..63a31a02eb1a --- /dev/null +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/util/OrcMetrics.java @@ -0,0 +1,284 @@ +/* + * 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 io.trino.plugin.iceberg.util; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import io.airlift.slice.Slice; +import io.trino.orc.metadata.ColumnMetadata; +import io.trino.orc.metadata.OrcColumnId; +import io.trino.orc.metadata.OrcType; +import io.trino.orc.metadata.statistics.BooleanStatistics; +import io.trino.orc.metadata.statistics.ColumnStatistics; +import io.trino.orc.metadata.statistics.DateStatistics; +import io.trino.orc.metadata.statistics.DecimalStatistics; +import io.trino.orc.metadata.statistics.DoubleStatistics; +import io.trino.orc.metadata.statistics.IntegerStatistics; +import io.trino.orc.metadata.statistics.StringStatistics; +import io.trino.orc.metadata.statistics.TimestampStatistics; +import org.apache.iceberg.Metrics; +import org.apache.iceberg.MetricsConfig; +import org.apache.iceberg.MetricsModes; +import org.apache.iceberg.MetricsUtil; +import org.apache.iceberg.Schema; +import org.apache.iceberg.expressions.Literal; +import org.apache.iceberg.types.Conversions; +import org.apache.iceberg.types.Type; +import org.apache.iceberg.types.Type.TypeID; +import org.apache.iceberg.types.Types; +import org.apache.iceberg.util.BinaryUtil; +import org.apache.iceberg.util.UnicodeUtil; + +import java.math.BigDecimal; +import java.nio.ByteBuffer; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import static com.google.common.base.Verify.verify; +import static io.trino.orc.metadata.OrcColumnId.ROOT_COLUMN; +import static io.trino.plugin.iceberg.util.OrcTypeConverter.ORC_ICEBERG_ID_KEY; +import static io.trino.spi.type.Timestamps.MICROSECONDS_PER_MILLISECOND; +import static java.lang.Math.toIntExact; + +public final class OrcMetrics +{ + private OrcMetrics() {} + + public static Metrics computeMetrics( + MetricsConfig metricsConfig, + Schema icebergSchema, + ColumnMetadata orcColumns, + long fileRowCount, + Optional> columnStatistics) + { + if (columnStatistics.isEmpty()) { + return new Metrics(fileRowCount, null, null, null, null, null, null); + } + // Columns that are descendants of LIST or MAP types are excluded because: + // 1. Their stats are not used by Apache Iceberg to filter out data files + // 2. Their record count can be larger than table-level row count. There's no good way to calculate nullCounts for them. + // See https://github.com/apache/iceberg/pull/199#discussion_r429443627 + Set excludedColumns = getExcludedColumns(orcColumns); + + ImmutableMap.Builder valueCountsBuilder = ImmutableMap.builder(); + ImmutableMap.Builder nullCountsBuilder = ImmutableMap.builder(); + ImmutableMap.Builder nanCountsBuilder = ImmutableMap.builder(); + ImmutableMap.Builder lowerBoundsBuilder = ImmutableMap.builder(); + ImmutableMap.Builder upperBoundsBuilder = ImmutableMap.builder(); + + // OrcColumnId(0) is the root column that represents file-level schema + for (int i = 1; i < orcColumns.size(); i++) { + OrcColumnId orcColumnId = new OrcColumnId(i); + if (excludedColumns.contains(orcColumnId)) { + continue; + } + OrcType orcColumn = orcColumns.get(orcColumnId); + ColumnStatistics orcColumnStats = columnStatistics.get().get(orcColumnId); + int icebergId = getIcebergId(orcColumn); + Types.NestedField icebergField = icebergSchema.findField(icebergId); + MetricsModes.MetricsMode metricsMode = MetricsUtil.metricsMode(icebergSchema, metricsConfig, icebergId); + if (metricsMode.equals(MetricsModes.None.get())) { + continue; + } + verify(icebergField != null, "Cannot find Iceberg column with ID %s in schema %s", icebergId, icebergSchema); + valueCountsBuilder.put(icebergId, fileRowCount); + if (orcColumnStats.hasNumberOfValues()) { + nullCountsBuilder.put(icebergId, fileRowCount - orcColumnStats.getNumberOfValues()); + } + if (orcColumnStats.getNumberOfNanValues() > 0) { + nanCountsBuilder.put(icebergId, orcColumnStats.getNumberOfNanValues()); + } + + if (!metricsMode.equals(MetricsModes.Counts.get())) { + toIcebergMinMax(orcColumnStats, icebergField.type(), metricsMode).ifPresent(minMax -> { + lowerBoundsBuilder.put(icebergId, minMax.getMin()); + upperBoundsBuilder.put(icebergId, minMax.getMax()); + }); + } + } + Map valueCounts = valueCountsBuilder.buildOrThrow(); + Map nullCounts = nullCountsBuilder.buildOrThrow(); + Map nanCounts = nanCountsBuilder.buildOrThrow(); + Map lowerBounds = lowerBoundsBuilder.buildOrThrow(); + Map upperBounds = upperBoundsBuilder.buildOrThrow(); + return new Metrics( + fileRowCount, + null, // TODO: Add column size accounting to ORC column writers + valueCounts.isEmpty() ? null : valueCounts, + nullCounts.isEmpty() ? null : nullCounts, + nanCounts.isEmpty() ? null : nanCounts, + lowerBounds.isEmpty() ? null : lowerBounds, + upperBounds.isEmpty() ? null : upperBounds); + } + + private static Set getExcludedColumns(ColumnMetadata orcColumns) + { + ImmutableSet.Builder excludedColumns = ImmutableSet.builder(); + populateExcludedColumns(orcColumns, ROOT_COLUMN, false, excludedColumns); + return excludedColumns.build(); + } + + private static void populateExcludedColumns(ColumnMetadata orcColumns, OrcColumnId orcColumnId, boolean exclude, ImmutableSet.Builder excludedColumns) + { + if (exclude) { + excludedColumns.add(orcColumnId); + } + OrcType orcColumn = orcColumns.get(orcColumnId); + switch (orcColumn.getOrcTypeKind()) { + case LIST: + case MAP: + for (OrcColumnId child : orcColumn.getFieldTypeIndexes()) { + populateExcludedColumns(orcColumns, child, true, excludedColumns); + } + return; + case STRUCT: + for (OrcColumnId child : orcColumn.getFieldTypeIndexes()) { + populateExcludedColumns(orcColumns, child, exclude, excludedColumns); + } + return; + default: + // unexpected, TODO throw + } + } + + private static int getIcebergId(OrcType orcColumn) + { + String icebergId = orcColumn.getAttributes().get(ORC_ICEBERG_ID_KEY); + verify(icebergId != null, "ORC column %s doesn't have an associated Iceberg ID", orcColumn); + return Integer.parseInt(icebergId); + } + + private static Optional toIcebergMinMax(ColumnStatistics orcColumnStats, Type icebergType, MetricsModes.MetricsMode metricsModes) + { + BooleanStatistics booleanStatistics = orcColumnStats.getBooleanStatistics(); + if (booleanStatistics != null) { + boolean hasTrueValues = booleanStatistics.getTrueValueCount() != 0; + boolean hasFalseValues = orcColumnStats.getNumberOfValues() != booleanStatistics.getTrueValueCount(); + return Optional.of(new IcebergMinMax(icebergType, !hasFalseValues, hasTrueValues, metricsModes)); + } + + IntegerStatistics integerStatistics = orcColumnStats.getIntegerStatistics(); + if (integerStatistics != null) { + Object min = integerStatistics.getMin(); + Object max = integerStatistics.getMax(); + if (min == null || max == null) { + return Optional.empty(); + } + if (icebergType.typeId() == TypeID.INTEGER) { + min = toIntExact((Long) min); + max = toIntExact((Long) max); + } + return Optional.of(new IcebergMinMax(icebergType, min, max, metricsModes)); + } + DoubleStatistics doubleStatistics = orcColumnStats.getDoubleStatistics(); + if (doubleStatistics != null) { + Object min = doubleStatistics.getMin(); + Object max = doubleStatistics.getMax(); + if (min == null || max == null) { + return Optional.empty(); + } + if (icebergType.typeId() == TypeID.FLOAT) { + min = ((Double) min).floatValue(); + max = ((Double) max).floatValue(); + } + return Optional.of(new IcebergMinMax(icebergType, min, max, metricsModes)); + } + StringStatistics stringStatistics = orcColumnStats.getStringStatistics(); + if (stringStatistics != null) { + Slice min = stringStatistics.getMin(); + Slice max = stringStatistics.getMax(); + if (min == null || max == null) { + return Optional.empty(); + } + return Optional.of(new IcebergMinMax(icebergType, min.toStringUtf8(), max.toStringUtf8(), metricsModes)); + } + DateStatistics dateStatistics = orcColumnStats.getDateStatistics(); + if (dateStatistics != null) { + Integer min = dateStatistics.getMin(); + Integer max = dateStatistics.getMax(); + if (min == null || max == null) { + return Optional.empty(); + } + return Optional.of(new IcebergMinMax(icebergType, min, max, metricsModes)); + } + DecimalStatistics decimalStatistics = orcColumnStats.getDecimalStatistics(); + if (decimalStatistics != null) { + BigDecimal min = decimalStatistics.getMin(); + BigDecimal max = decimalStatistics.getMax(); + if (min == null || max == null) { + return Optional.empty(); + } + min = min.setScale(((Types.DecimalType) icebergType).scale()); + max = max.setScale(((Types.DecimalType) icebergType).scale()); + return Optional.of(new IcebergMinMax(icebergType, min, max, metricsModes)); + } + TimestampStatistics timestampStatistics = orcColumnStats.getTimestampStatistics(); + if (timestampStatistics != null) { + Long min = timestampStatistics.getMin(); + Long max = timestampStatistics.getMax(); + if (min == null || max == null) { + return Optional.empty(); + } + // Since ORC timestamp statistics are truncated to millisecond precision, this can cause some column values to fall outside the stats range. + // We are appending 999 microseconds to account for the fact that Trino ORC writer truncates timestamps. + return Optional.of(new IcebergMinMax(icebergType, min * MICROSECONDS_PER_MILLISECOND, (max * MICROSECONDS_PER_MILLISECOND) + (MICROSECONDS_PER_MILLISECOND - 1), metricsModes)); + } + return Optional.empty(); + } + + private static class IcebergMinMax + { + private final ByteBuffer min; + private final ByteBuffer max; + + private IcebergMinMax(Type type, Object min, Object max, MetricsModes.MetricsMode metricsMode) + { + if (metricsMode instanceof MetricsModes.Full) { + this.min = Conversions.toByteBuffer(type, min); + this.max = Conversions.toByteBuffer(type, max); + } + else if (metricsMode instanceof MetricsModes.Truncate truncateMode) { + int truncateLength = truncateMode.length(); + switch (type.typeId()) { + case STRING: + this.min = UnicodeUtil.truncateStringMin(Literal.of((CharSequence) min), truncateLength).toByteBuffer(); + this.max = UnicodeUtil.truncateStringMax(Literal.of((CharSequence) max), truncateLength).toByteBuffer(); + break; + case FIXED: + case BINARY: + this.min = BinaryUtil.truncateBinaryMin(Literal.of((ByteBuffer) min), truncateLength).toByteBuffer(); + this.max = BinaryUtil.truncateBinaryMax(Literal.of((ByteBuffer) max), truncateLength).toByteBuffer(); + break; + default: + this.min = Conversions.toByteBuffer(type, min); + this.max = Conversions.toByteBuffer(type, max); + } + } + else { + throw new UnsupportedOperationException("Unsupported metrics mode for Iceberg Max/Min Bound: " + metricsMode); + } + } + + public ByteBuffer getMin() + { + return min; + } + + public ByteBuffer getMax() + { + return max; + } + } +} From 3337e7b956a491ba88aac56139f139ab55a2d250 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Mon, 13 Nov 2023 00:08:37 -0800 Subject: [PATCH 229/587] Replace Hadoop-dependent Iceberg OrcMetrics --- plugin/trino-iceberg/pom.xml | 5 -- .../iceberg/procedure/MigrateProcedure.java | 27 ++++---- .../trino/plugin/iceberg/util/OrcMetrics.java | 66 +++++++++++++++++++ 3 files changed, 82 insertions(+), 16 deletions(-) diff --git a/plugin/trino-iceberg/pom.xml b/plugin/trino-iceberg/pom.xml index 32e0f0225426..1c29509ef12d 100644 --- a/plugin/trino-iceberg/pom.xml +++ b/plugin/trino-iceberg/pom.xml @@ -215,11 +215,6 @@ ${dep.iceberg.version} - - org.apache.iceberg - iceberg-orc - - org.apache.iceberg iceberg-parquet diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/procedure/MigrateProcedure.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/procedure/MigrateProcedure.java index 6c57853c9e4b..4c9b37608969 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/procedure/MigrateProcedure.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/procedure/MigrateProcedure.java @@ -40,6 +40,7 @@ import io.trino.plugin.iceberg.catalog.TrinoCatalog; import io.trino.plugin.iceberg.catalog.TrinoCatalogFactory; import io.trino.plugin.iceberg.fileio.ForwardingInputFile; +import io.trino.plugin.iceberg.util.OrcMetrics; import io.trino.spi.TrinoException; import io.trino.spi.classloader.ThreadContextClassLoader; import io.trino.spi.connector.ConnectorSession; @@ -63,10 +64,8 @@ import org.apache.iceberg.Table; import org.apache.iceberg.Transaction; import org.apache.iceberg.avro.Avro; -import org.apache.iceberg.io.InputFile; import org.apache.iceberg.mapping.MappingUtil; import org.apache.iceberg.mapping.NameMapping; -import org.apache.iceberg.orc.OrcMetrics; import org.apache.iceberg.parquet.ParquetUtil; import org.apache.iceberg.types.TypeUtil; import org.apache.iceberg.types.Types; @@ -225,7 +224,7 @@ public void doMigrate(ConnectorSession session, String schemaName, String tableN ImmutableList.Builder dataFilesBuilder = ImmutableList.builder(); if (hiveTable.getPartitionColumns().isEmpty()) { log.debug("Building data files from %s", location); - dataFilesBuilder.addAll(buildDataFiles(session, recursive, storageFormat, location, partitionSpec, new PartitionData(new Object[]{}), nameMapping)); + dataFilesBuilder.addAll(buildDataFiles(session, recursive, storageFormat, location, partitionSpec, new PartitionData(new Object[]{}), schema)); } else { Map> partitions = listAllPartitions(metastore, hiveTable); @@ -235,7 +234,7 @@ public void doMigrate(ConnectorSession session, String schemaName, String tableN log.debug("Building data files from '%s' for partition %d of %d", storage.getLocation(), fileCount++, partitions.size()); HiveStorageFormat partitionStorageFormat = extractHiveStorageFormat(storage.getStorageFormat()); StructLike partitionData = DataFiles.data(partitionSpec, partition.getKey()); - dataFilesBuilder.addAll(buildDataFiles(session, recursive, partitionStorageFormat, storage.getLocation(), partitionSpec, partitionData, nameMapping)); + dataFilesBuilder.addAll(buildDataFiles(session, recursive, partitionStorageFormat, storage.getLocation(), partitionSpec, partitionData, schema)); } } @@ -330,7 +329,14 @@ public Map> listAllPartitions(HiveMetastore metastor return metastore.getPartitionsByNames(table, partitions.get()); } - private List buildDataFiles(ConnectorSession session, RecursiveDirectory recursive, HiveStorageFormat format, String location, PartitionSpec partitionSpec, StructLike partition, NameMapping nameMapping) + private List buildDataFiles( + ConnectorSession session, + RecursiveDirectory recursive, + HiveStorageFormat format, + String location, + PartitionSpec partitionSpec, + StructLike partition, + Schema schema) throws IOException { // TODO: Introduce parallelism @@ -351,7 +357,7 @@ private List buildDataFiles(ConnectorSession session, RecursiveDirecto throw new TrinoException(NOT_SUPPORTED, "Recursive directory must not exist when recursive_directory argument is 'fail': " + file.location()); } - Metrics metrics = loadMetrics(fileSystem.newInputFile(file.location()), format, nameMapping); + Metrics metrics = loadMetrics(fileSystem.newInputFile(file.location()), format, schema); DataFile dataFile = buildDataFile(file, partition, partitionSpec, format.name(), metrics); dataFilesBuilder.add(dataFile); } @@ -377,13 +383,12 @@ private static IcebergFileFormat toIcebergFileFormat(HiveStorageFormat storageFo }; } - private static Metrics loadMetrics(TrinoInputFile file, HiveStorageFormat storageFormat, NameMapping nameMapping) + private static Metrics loadMetrics(TrinoInputFile file, HiveStorageFormat storageFormat, Schema schema) { - InputFile inputFile = new ForwardingInputFile(file); return switch (storageFormat) { - case ORC -> OrcMetrics.fromInputFile(inputFile, METRICS_CONFIG, nameMapping); - case PARQUET -> ParquetUtil.fileMetrics(inputFile, METRICS_CONFIG, nameMapping); - case AVRO -> new Metrics(Avro.rowCount(inputFile), null, null, null, null); + case ORC -> OrcMetrics.fileMetrics(file, METRICS_CONFIG, schema); + case PARQUET -> ParquetUtil.fileMetrics(new ForwardingInputFile(file), METRICS_CONFIG, MappingUtil.create(schema)); + case AVRO -> new Metrics(Avro.rowCount(new ForwardingInputFile(file)), null, null, null, null); default -> throw new TrinoException(NOT_SUPPORTED, "Unsupported storage format: " + storageFormat); }; } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/util/OrcMetrics.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/util/OrcMetrics.java index 63a31a02eb1a..aaa785d75c49 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/util/OrcMetrics.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/util/OrcMetrics.java @@ -16,7 +16,13 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import io.airlift.slice.Slice; +import io.trino.filesystem.TrinoInputFile; +import io.trino.orc.OrcColumn; +import io.trino.orc.OrcDataSource; +import io.trino.orc.OrcReader; +import io.trino.orc.OrcReaderOptions; import io.trino.orc.metadata.ColumnMetadata; +import io.trino.orc.metadata.Footer; import io.trino.orc.metadata.OrcColumnId; import io.trino.orc.metadata.OrcType; import io.trino.orc.metadata.statistics.BooleanStatistics; @@ -27,12 +33,16 @@ import io.trino.orc.metadata.statistics.IntegerStatistics; import io.trino.orc.metadata.statistics.StringStatistics; import io.trino.orc.metadata.statistics.TimestampStatistics; +import io.trino.plugin.hive.FileFormatDataSourceStats; +import io.trino.plugin.iceberg.TrinoOrcDataSource; import org.apache.iceberg.Metrics; import org.apache.iceberg.MetricsConfig; import org.apache.iceberg.MetricsModes; import org.apache.iceberg.MetricsUtil; import org.apache.iceberg.Schema; import org.apache.iceberg.expressions.Literal; +import org.apache.iceberg.mapping.MappingUtil; +import org.apache.iceberg.mapping.NameMapping; import org.apache.iceberg.types.Conversions; import org.apache.iceberg.types.Type; import org.apache.iceberg.types.Type.TypeID; @@ -40,22 +50,78 @@ import org.apache.iceberg.util.BinaryUtil; import org.apache.iceberg.util.UnicodeUtil; +import java.io.IOException; +import java.io.UncheckedIOException; import java.math.BigDecimal; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import static com.google.common.base.Verify.verify; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static io.trino.orc.OrcReader.createOrcReader; import static io.trino.orc.metadata.OrcColumnId.ROOT_COLUMN; +import static io.trino.plugin.iceberg.util.OrcIcebergIds.fileColumnsByIcebergId; import static io.trino.plugin.iceberg.util.OrcTypeConverter.ORC_ICEBERG_ID_KEY; import static io.trino.spi.type.Timestamps.MICROSECONDS_PER_MILLISECOND; import static java.lang.Math.toIntExact; +import static java.util.function.Function.identity; public final class OrcMetrics { private OrcMetrics() {} + public static Metrics fileMetrics(TrinoInputFile file, MetricsConfig metricsConfig, Schema schema) + { + OrcReaderOptions options = new OrcReaderOptions(); + try (OrcDataSource dataSource = new TrinoOrcDataSource(file, options, new FileFormatDataSourceStats())) { + Optional reader = createOrcReader(dataSource, options); + if (reader.isEmpty()) { + return new Metrics(0L, null, null, null, null); + } + Footer footer = reader.get().getFooter(); + + // use name mapping to compute missing Iceberg field IDs + Optional nameMapping = Optional.of(MappingUtil.create(schema)); + Map mappedColumns = fileColumnsByIcebergId(reader.get(), nameMapping) + .values().stream() + .collect(toImmutableMap(OrcColumn::getColumnId, identity())); + + // rebuild type list with mapped columns + List mappedTypes = new ArrayList<>(); + ColumnMetadata types = footer.getTypes(); + for (int i = 0; i < types.size(); i++) { + OrcColumnId id = new OrcColumnId(i); + mappedTypes.add(Optional.ofNullable(mappedColumns.get(id)) + .map(OrcMetrics::toBasicOrcType) + .orElseGet(() -> types.get(id))); + } + + return computeMetrics(metricsConfig, schema, new ColumnMetadata<>(mappedTypes), footer.getNumberOfRows(), footer.getFileStats()); + } + catch (IOException e) { + throw new UncheckedIOException("Failed to read file footer: " + file.location(), e); + } + } + + private static OrcType toBasicOrcType(OrcColumn column) + { + return new OrcType( + column.getColumnType(), + column.getNestedColumns().stream() + .map(OrcColumn::getColumnId) + .collect(toImmutableList()), + null, + Optional.empty(), + Optional.empty(), + Optional.empty(), + column.getAttributes()); + } + public static Metrics computeMetrics( MetricsConfig metricsConfig, Schema icebergSchema, From a575db78b43c5e66ecc6ee8b6508ca1371ebeb78 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Mon, 13 Nov 2023 01:26:04 -0800 Subject: [PATCH 230/587] Replace Hadoop-dependent Iceberg ParquetUtil.fileMetrics --- .../iceberg/procedure/MigrateProcedure.java | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/procedure/MigrateProcedure.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/procedure/MigrateProcedure.java index 4c9b37608969..91283376cbb6 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/procedure/MigrateProcedure.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/procedure/MigrateProcedure.java @@ -25,6 +25,10 @@ import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.filesystem.TrinoInputFile; +import io.trino.parquet.ParquetDataSource; +import io.trino.parquet.ParquetReaderOptions; +import io.trino.parquet.reader.MetadataReader; +import io.trino.plugin.hive.FileFormatDataSourceStats; import io.trino.plugin.hive.HiveStorageFormat; import io.trino.plugin.hive.metastore.Column; import io.trino.plugin.hive.metastore.HiveMetastore; @@ -33,6 +37,7 @@ import io.trino.plugin.hive.metastore.PrincipalPrivileges; import io.trino.plugin.hive.metastore.RawHiveMetastoreFactory; import io.trino.plugin.hive.metastore.Storage; +import io.trino.plugin.hive.parquet.TrinoParquetDataSource; import io.trino.plugin.iceberg.IcebergConfig; import io.trino.plugin.iceberg.IcebergFileFormat; import io.trino.plugin.iceberg.IcebergSecurityConfig; @@ -69,8 +74,10 @@ import org.apache.iceberg.parquet.ParquetUtil; import org.apache.iceberg.types.TypeUtil; import org.apache.iceberg.types.Types; +import org.apache.parquet.hadoop.metadata.ParquetMetadata; import java.io.IOException; +import java.io.UncheckedIOException; import java.lang.invoke.MethodHandle; import java.util.ArrayList; import java.util.HashMap; @@ -78,6 +85,7 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; import static com.google.common.base.Verify.verify; import static com.google.common.collect.ImmutableList.toImmutableList; @@ -387,12 +395,23 @@ private static Metrics loadMetrics(TrinoInputFile file, HiveStorageFormat storag { return switch (storageFormat) { case ORC -> OrcMetrics.fileMetrics(file, METRICS_CONFIG, schema); - case PARQUET -> ParquetUtil.fileMetrics(new ForwardingInputFile(file), METRICS_CONFIG, MappingUtil.create(schema)); + case PARQUET -> parquetMetrics(file, METRICS_CONFIG, MappingUtil.create(schema)); case AVRO -> new Metrics(Avro.rowCount(new ForwardingInputFile(file)), null, null, null, null); default -> throw new TrinoException(NOT_SUPPORTED, "Unsupported storage format: " + storageFormat); }; } + private static Metrics parquetMetrics(TrinoInputFile file, MetricsConfig metricsConfig, NameMapping nameMapping) + { + try (ParquetDataSource dataSource = new TrinoParquetDataSource(file, new ParquetReaderOptions(), new FileFormatDataSourceStats())) { + ParquetMetadata metadata = MetadataReader.readFooter(dataSource, Optional.empty()); + return ParquetUtil.footerMetrics(metadata, Stream.empty(), metricsConfig, nameMapping); + } + catch (IOException e) { + throw new UncheckedIOException("Failed to read file footer: " + file.location(), e); + } + } + private static List toPartitionFields(io.trino.plugin.hive.metastore.Table table) { ImmutableList.Builder fields = ImmutableList.builder(); From 1dd3199b6bba4008c4e3f84755994769d5f5e37b Mon Sep 17 00:00:00 2001 From: David Phillips Date: Mon, 13 Nov 2023 01:51:29 -0800 Subject: [PATCH 231/587] Make IcebergOrcFileWriter not extend OrcFileWriter --- .../plugin/iceberg/IcebergErrorCode.java | 1 + .../plugin/iceberg/IcebergOrcFileWriter.java | 151 +++++++++++++++++- 2 files changed, 144 insertions(+), 8 deletions(-) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergErrorCode.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergErrorCode.java index 0ce831abb7e7..6822462387b3 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergErrorCode.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergErrorCode.java @@ -40,6 +40,7 @@ public enum IcebergErrorCode ICEBERG_CATALOG_ERROR(13, EXTERNAL), ICEBERG_WRITER_CLOSE_ERROR(14, EXTERNAL), ICEBERG_MISSING_METADATA(15, EXTERNAL), + ICEBERG_WRITER_DATA_ERROR(16, EXTERNAL), /**/; private final ErrorCode errorCode; diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergOrcFileWriter.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergOrcFileWriter.java index 3418ed217cf0..abc3ea8986e6 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergOrcFileWriter.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergOrcFileWriter.java @@ -13,39 +13,60 @@ */ package io.trino.plugin.iceberg; +import io.airlift.log.Logger; import io.trino.orc.OrcDataSink; import io.trino.orc.OrcDataSource; -import io.trino.orc.OrcWriteValidation; +import io.trino.orc.OrcWriteValidation.OrcWriteValidationMode; +import io.trino.orc.OrcWriter; import io.trino.orc.OrcWriterOptions; import io.trino.orc.OrcWriterStats; import io.trino.orc.metadata.ColumnMetadata; import io.trino.orc.metadata.CompressionKind; import io.trino.orc.metadata.OrcType; -import io.trino.plugin.hive.WriterKind; -import io.trino.plugin.hive.orc.OrcFileWriter; +import io.trino.spi.Page; +import io.trino.spi.TrinoException; +import io.trino.spi.block.Block; +import io.trino.spi.block.RunLengthEncodedBlock; import io.trino.spi.type.Type; import org.apache.iceberg.Metrics; import org.apache.iceberg.MetricsConfig; import org.apache.iceberg.Schema; import java.io.Closeable; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadMXBean; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.OptionalInt; import java.util.function.Supplier; -import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; +import static com.google.common.base.MoreObjects.toStringHelper; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.airlift.slice.SizeOf.instanceSize; +import static io.trino.plugin.iceberg.IcebergErrorCode.ICEBERG_WRITER_CLOSE_ERROR; +import static io.trino.plugin.iceberg.IcebergErrorCode.ICEBERG_WRITER_DATA_ERROR; +import static io.trino.plugin.iceberg.IcebergErrorCode.ICEBERG_WRITE_VALIDATION_FAILED; import static io.trino.plugin.iceberg.util.OrcMetrics.computeMetrics; import static java.util.Objects.requireNonNull; public class IcebergOrcFileWriter - extends OrcFileWriter implements IcebergFileWriter { + private static final Logger log = Logger.get(IcebergOrcFileWriter.class); + private static final int INSTANCE_SIZE = instanceSize(IcebergOrcFileWriter.class); + private static final ThreadMXBean THREAD_MX_BEAN = ManagementFactory.getThreadMXBean(); + + private final OrcWriter orcWriter; private final Schema icebergSchema; private final ColumnMetadata orcColumns; private final MetricsConfig metricsConfig; + private final Closeable rollbackAction; + private final int[] fileInputColumnIndexes; + private final List nullBlocks; + private final Optional> validationInputFactory; + private long validationCpuNanos; public IcebergOrcFileWriter( MetricsConfig metricsConfig, @@ -60,10 +81,29 @@ public IcebergOrcFileWriter( int[] fileInputColumnIndexes, Map metadata, Optional> validationInputFactory, - OrcWriteValidation.OrcWriteValidationMode validationMode, + OrcWriteValidationMode validationMode, OrcWriterStats stats) { - super(orcDataSink, WriterKind.INSERT, NO_ACID_TRANSACTION, false, OptionalInt.empty(), rollbackAction, columnNames, fileColumnTypes, fileColumnOrcTypes, compression, options, fileInputColumnIndexes, metadata, validationInputFactory, validationMode, stats); + requireNonNull(orcDataSink, "orcDataSink is null"); + this.rollbackAction = requireNonNull(rollbackAction, "rollbackAction is null"); + this.fileInputColumnIndexes = requireNonNull(fileInputColumnIndexes, "fileInputColumnIndexes is null"); + + this.nullBlocks = fileColumnTypes.stream() + .map(type -> type.createBlockBuilder(null, 1, 0).appendNull().build()) + .collect(toImmutableList()); + + this.validationInputFactory = validationInputFactory; + this.orcWriter = new OrcWriter( + orcDataSink, + columnNames, + fileColumnTypes, + fileColumnOrcTypes, + compression, + options, + metadata, + validationInputFactory.isPresent(), + validationMode, + stats); this.icebergSchema = requireNonNull(icebergSchema, "icebergSchema is null"); this.metricsConfig = requireNonNull(metricsConfig, "metricsConfig is null"); orcColumns = fileColumnOrcTypes; @@ -74,4 +114,99 @@ public Metrics getMetrics() { return computeMetrics(metricsConfig, icebergSchema, orcColumns, orcWriter.getFileRowCount(), orcWriter.getFileStats()); } + + @Override + public long getWrittenBytes() + { + return orcWriter.getWrittenBytes() + orcWriter.getBufferedBytes(); + } + + @Override + public long getMemoryUsage() + { + return INSTANCE_SIZE + orcWriter.getRetainedBytes(); + } + + @Override + public void appendRows(Page dataPage) + { + Block[] blocks = new Block[fileInputColumnIndexes.length]; + for (int i = 0; i < fileInputColumnIndexes.length; i++) { + int inputColumnIndex = fileInputColumnIndexes[i]; + if (inputColumnIndex < 0) { + blocks[i] = RunLengthEncodedBlock.create(nullBlocks.get(i), dataPage.getPositionCount()); + } + else { + blocks[i] = dataPage.getBlock(inputColumnIndex); + } + } + + Page page = new Page(dataPage.getPositionCount(), blocks); + try { + orcWriter.write(page); + } + catch (IOException | UncheckedIOException e) { + throw new TrinoException(ICEBERG_WRITER_DATA_ERROR, e); + } + } + + @Override + public Closeable commit() + { + try { + orcWriter.close(); + } + catch (IOException | UncheckedIOException e) { + try { + rollbackAction.close(); + } + catch (IOException | RuntimeException ex) { + if (!e.equals(ex)) { + e.addSuppressed(ex); + } + log.error(ex, "Exception when committing file"); + } + throw new TrinoException(ICEBERG_WRITER_CLOSE_ERROR, "Error committing write to ORC file", e); + } + + if (validationInputFactory.isPresent()) { + try { + try (OrcDataSource input = validationInputFactory.get().get()) { + long startThreadCpuTime = THREAD_MX_BEAN.getCurrentThreadCpuTime(); + orcWriter.validate(input); + validationCpuNanos += THREAD_MX_BEAN.getCurrentThreadCpuTime() - startThreadCpuTime; + } + } + catch (IOException | UncheckedIOException e) { + throw new TrinoException(ICEBERG_WRITE_VALIDATION_FAILED, e); + } + } + + return rollbackAction; + } + + @Override + public void rollback() + { + try (rollbackAction) { + orcWriter.close(); + } + catch (Exception e) { + throw new TrinoException(ICEBERG_WRITER_CLOSE_ERROR, "Error rolling back write to ORC file", e); + } + } + + @Override + public long getValidationCpuNanos() + { + return validationCpuNanos; + } + + @Override + public String toString() + { + return toStringHelper(this) + .add("writer", orcWriter) + .toString(); + } } From 380b0a5a20ee4cf4f268ad5c59bfacab3d901745 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Mon, 13 Nov 2023 13:44:48 -0800 Subject: [PATCH 232/587] Make file writers final --- .../main/java/io/trino/plugin/deltalake/DeltaLakeWriter.java | 2 +- .../src/main/java/io/trino/plugin/hive/MergeFileWriter.java | 2 +- .../src/main/java/io/trino/plugin/hive/RcFileFileWriter.java | 2 +- .../src/main/java/io/trino/plugin/hive/SortingFileWriter.java | 2 +- .../main/java/io/trino/plugin/hive/avro/AvroHiveFileWriter.java | 2 +- .../src/main/java/io/trino/plugin/hive/line/LineFileWriter.java | 2 +- .../src/main/java/io/trino/plugin/hive/orc/OrcFileWriter.java | 2 +- .../java/io/trino/plugin/iceberg/IcebergAvroFileWriter.java | 2 +- .../main/java/io/trino/plugin/iceberg/IcebergOrcFileWriter.java | 2 +- .../java/io/trino/plugin/iceberg/IcebergSortingFileWriter.java | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeWriter.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeWriter.java index b73f58111914..7c27b8d151cd 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeWriter.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeWriter.java @@ -73,7 +73,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.function.UnaryOperator.identity; -public class DeltaLakeWriter +public final class DeltaLakeWriter implements FileWriter { private final ParquetFileWriter fileWriter; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/MergeFileWriter.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/MergeFileWriter.java index 48d39436a608..0a27cb663f75 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/MergeFileWriter.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/MergeFileWriter.java @@ -58,7 +58,7 @@ import static io.trino.spi.type.IntegerType.INTEGER; import static java.util.Objects.requireNonNull; -public class MergeFileWriter +public final class MergeFileWriter implements FileWriter { // The bucketPath looks like this: /root/dir/delta_nnnnnnn_mmmmmmm_ssss/bucket_bbbbb(_aaaa)? diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/RcFileFileWriter.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/RcFileFileWriter.java index f881ae81d5f2..d840f99f9c8f 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/RcFileFileWriter.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/RcFileFileWriter.java @@ -45,7 +45,7 @@ import static io.trino.plugin.hive.HiveErrorCode.HIVE_WRITE_VALIDATION_FAILED; import static java.util.Objects.requireNonNull; -public class RcFileFileWriter +public final class RcFileFileWriter implements FileWriter { private static final int INSTANCE_SIZE = instanceSize(RcFileFileWriter.class); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/SortingFileWriter.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/SortingFileWriter.java index 59b9ee0e096d..61fbcec9a9c4 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/SortingFileWriter.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/SortingFileWriter.java @@ -59,7 +59,7 @@ import static java.util.Comparator.comparing; import static java.util.Objects.requireNonNull; -public class SortingFileWriter +public final class SortingFileWriter implements FileWriter { private static final Logger log = Logger.get(SortingFileWriter.class); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroHiveFileWriter.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroHiveFileWriter.java index 6afed9a0631c..f520c368e787 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroHiveFileWriter.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroHiveFileWriter.java @@ -46,7 +46,7 @@ import static io.trino.plugin.hive.avro.AvroHiveFileUtils.getCanonicalToGivenFieldName; import static java.util.Objects.requireNonNull; -public class AvroHiveFileWriter +public final class AvroHiveFileWriter implements FileWriter { private static final int INSTANCE_SIZE = instanceSize(AvroHiveFileWriter.class); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/line/LineFileWriter.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/line/LineFileWriter.java index cd0ca15d0528..49dc21e96ed6 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/line/LineFileWriter.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/line/LineFileWriter.java @@ -34,7 +34,7 @@ import static io.trino.plugin.hive.HiveErrorCode.HIVE_WRITER_DATA_ERROR; import static java.util.Objects.requireNonNull; -public class LineFileWriter +public final class LineFileWriter implements FileWriter { private static final int INSTANCE_SIZE = instanceSize(LineFileWriter.class); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcFileWriter.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcFileWriter.java index 553ce8255882..30dfac5b77ef 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcFileWriter.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcFileWriter.java @@ -62,7 +62,7 @@ import static java.lang.String.format; import static java.util.Objects.requireNonNull; -public class OrcFileWriter +public final class OrcFileWriter implements FileWriter { private static final Logger log = Logger.get(OrcFileWriter.class); diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergAvroFileWriter.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergAvroFileWriter.java index f0d54a25fd0f..e070f7557a37 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergAvroFileWriter.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergAvroFileWriter.java @@ -38,7 +38,7 @@ import static java.util.Objects.requireNonNull; import static org.apache.iceberg.TableProperties.AVRO_COMPRESSION; -public class IcebergAvroFileWriter +public final class IcebergAvroFileWriter implements IcebergFileWriter { private static final int INSTANCE_SIZE = instanceSize(IcebergAvroFileWriter.class); diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergOrcFileWriter.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergOrcFileWriter.java index abc3ea8986e6..043769e58be6 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergOrcFileWriter.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergOrcFileWriter.java @@ -51,7 +51,7 @@ import static io.trino.plugin.iceberg.util.OrcMetrics.computeMetrics; import static java.util.Objects.requireNonNull; -public class IcebergOrcFileWriter +public final class IcebergOrcFileWriter implements IcebergFileWriter { private static final Logger log = Logger.get(IcebergOrcFileWriter.class); diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergSortingFileWriter.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergSortingFileWriter.java index 99bc122095f0..1785516600cc 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergSortingFileWriter.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergSortingFileWriter.java @@ -30,7 +30,7 @@ import static java.util.Objects.requireNonNull; -public class IcebergSortingFileWriter +public final class IcebergSortingFileWriter implements IcebergFileWriter { private final IcebergFileWriter outputWriter; From 0e5d54e48ca100519470651eaa9a929bf7188bed Mon Sep 17 00:00:00 2001 From: David Phillips Date: Mon, 13 Nov 2023 01:55:27 -0800 Subject: [PATCH 233/587] Remove redundant ACID condition in OrcFileWriter --- .../trino/plugin/hive/orc/OrcFileWriter.java | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcFileWriter.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcFileWriter.java index 30dfac5b77ef..e0d6776cb821 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcFileWriter.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcFileWriter.java @@ -182,7 +182,7 @@ public Closeable commit() { try { if (transaction.isAcidTransactionRunning() && useAcidSchema) { - updateUserMetadata(); + updateAcidUserMetadata(); } orcWriter.close(); } @@ -213,28 +213,26 @@ public Closeable commit() return rollbackAction; } - private void updateUserMetadata() + private void updateAcidUserMetadata() { int bucketValue = computeBucketValue(bucketNumber.orElse(0), 0); long writeId = maxWriteId.isPresent() ? maxWriteId.getAsLong() : transaction.getWriteId(); - if (transaction.isAcidTransactionRunning()) { - int stripeRowCount = orcWriter.getStripeRowCount(); - Map userMetadata = new HashMap<>(); - switch (writerKind) { - case INSERT: - userMetadata.put("hive.acid.stats", format("%s,0,0", stripeRowCount)); - break; - case DELETE: - userMetadata.put("hive.acid.stats", format("0,0,%s", stripeRowCount)); - break; - default: - throw new IllegalStateException("In updateUserMetadata, unknown writerKind " + writerKind); - } - userMetadata.put("hive.acid.key.index", format("%s,%s,%s;", writeId, bucketValue, stripeRowCount - 1)); - userMetadata.put("hive.acid.version", "2"); - - orcWriter.updateUserMetadata(userMetadata); + int stripeRowCount = orcWriter.getStripeRowCount(); + Map userMetadata = new HashMap<>(); + switch (writerKind) { + case INSERT: + userMetadata.put("hive.acid.stats", format("%s,0,0", stripeRowCount)); + break; + case DELETE: + userMetadata.put("hive.acid.stats", format("0,0,%s", stripeRowCount)); + break; + default: + throw new IllegalStateException("In updateUserMetadata, unknown writerKind " + writerKind); } + userMetadata.put("hive.acid.key.index", format("%s,%s,%s;", writeId, bucketValue, stripeRowCount - 1)); + userMetadata.put("hive.acid.version", "2"); + + orcWriter.updateUserMetadata(userMetadata); } @Override From e71bbdc257b9e287eda2c1bf460d988603acde68 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Tue, 7 Nov 2023 22:17:52 -0800 Subject: [PATCH 234/587] Load HDFS file system in separate class loader --- client/trino-jdbc/pom.xml | 6 + core/trino-server-rpm/pom.xml | 2 +- core/trino-server/src/main/provisio/trino.xml | 12 ++ lib/trino-filesystem-manager/pom.xml | 11 +- .../filesystem/manager/FileSystemModule.java | 76 ++++---- .../filesystem/manager/HdfsClassLoader.java | 123 ++++++++++++ .../manager/HdfsFileSystemLoader.java | 177 ++++++++++++++++++ lib/trino-hdfs/pom.xml | 20 ++ .../hdfs/HdfsFileSystemManager.java | 110 +++++++++++ .../java/io/trino/hdfs/HdfsEnvironment.java | 25 ++- lib/trino-hdfs/src/main/provisio/hdfs.xml | 4 + .../hdfs/TestHdfsFileSystemManager.java | 61 ++++++ lib/trino-orc/pom.xml | 12 +- plugin/trino-delta-lake/pom.xml | 28 +-- .../InternalDeltaLakeConnectorFactory.java | 2 +- .../glue/TestDeltaLakeGlueMetastore.java | 4 +- plugin/trino-geospatial/pom.xml | 6 + plugin/trino-hive-hadoop2/pom.xml | 24 +-- .../io/trino/plugin/hive/TestHivePlugin.java | 4 +- plugin/trino-hive/pom.xml | 24 +-- .../hive/InternalHiveConnectorFactory.java | 2 +- plugin/trino-hudi/pom.xml | 28 +-- .../hudi/InternalHudiConnectorFactory.java | 2 +- plugin/trino-iceberg/pom.xml | 24 +-- .../InternalIcebergConnectorFactory.java | 2 +- .../trino-session-property-managers/pom.xml | 6 + testing/trino-faulttolerant-tests/pom.xml | 6 + 27 files changed, 667 insertions(+), 134 deletions(-) create mode 100644 lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/HdfsClassLoader.java create mode 100644 lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/HdfsFileSystemLoader.java create mode 100644 lib/trino-hdfs/src/main/java/io/trino/filesystem/hdfs/HdfsFileSystemManager.java create mode 100644 lib/trino-hdfs/src/main/provisio/hdfs.xml create mode 100644 lib/trino-hdfs/src/test/java/io/trino/filesystem/hdfs/TestHdfsFileSystemManager.java diff --git a/client/trino-jdbc/pom.xml b/client/trino-jdbc/pom.xml index e1a8bb5a67d6..1fb6ab0bc8c5 100644 --- a/client/trino-jdbc/pom.xml +++ b/client/trino-jdbc/pom.xml @@ -186,6 +186,12 @@ test + + io.trino + trino-hdfs + test + + io.trino trino-hive-hadoop2 diff --git a/core/trino-server-rpm/pom.xml b/core/trino-server-rpm/pom.xml index 76a59fe85be2..5871b37d5e6f 100644 --- a/core/trino-server-rpm/pom.xml +++ b/core/trino-server-rpm/pom.xml @@ -252,7 +252,7 @@ /usr/lib/trino/plugin ${server.tar.package}/plugin - */* + */** diff --git a/core/trino-server/src/main/provisio/trino.xml b/core/trino-server/src/main/provisio/trino.xml index acc6fd6005db..519af7a4e530 100644 --- a/core/trino-server/src/main/provisio/trino.xml +++ b/core/trino-server/src/main/provisio/trino.xml @@ -66,6 +66,9 @@ + + + @@ -114,6 +117,9 @@ + + + @@ -126,12 +132,18 @@ + + + + + + diff --git a/lib/trino-filesystem-manager/pom.xml b/lib/trino-filesystem-manager/pom.xml index 8b86115c9acf..b82ba37f2891 100644 --- a/lib/trino-filesystem-manager/pom.xml +++ b/lib/trino-filesystem-manager/pom.xml @@ -27,6 +27,11 @@ guice + + io.airlift + bootstrap + + io.airlift configuration @@ -59,12 +64,12 @@ io.trino - trino-hdfs + trino-spi - io.trino - trino-spi + jakarta.annotation + jakarta.annotation-api diff --git a/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/FileSystemModule.java b/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/FileSystemModule.java index 535067fb74f3..57cd749bb05e 100644 --- a/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/FileSystemModule.java +++ b/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/FileSystemModule.java @@ -14,54 +14,62 @@ package io.trino.filesystem.manager; import com.google.inject.Binder; -import com.google.inject.Inject; import com.google.inject.Provides; import com.google.inject.Singleton; +import io.airlift.bootstrap.LifeCycleManager; import io.airlift.configuration.AbstractConfigurationAwareModule; +import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.trace.Tracer; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.filesystem.azure.AzureFileSystemFactory; import io.trino.filesystem.azure.AzureFileSystemModule; import io.trino.filesystem.gcs.GcsFileSystemFactory; import io.trino.filesystem.gcs.GcsFileSystemModule; -import io.trino.filesystem.hdfs.HdfsFileSystemFactory; -import io.trino.filesystem.hdfs.HdfsFileSystemModule; import io.trino.filesystem.s3.S3FileSystemFactory; import io.trino.filesystem.s3.S3FileSystemModule; import io.trino.filesystem.tracing.TracingFileSystemFactory; -import io.trino.hdfs.HdfsModule; -import io.trino.hdfs.authentication.HdfsAuthenticationModule; -import io.trino.hdfs.azure.HiveAzureModule; -import io.trino.hdfs.cos.HiveCosModule; -import io.trino.hdfs.gcs.HiveGcsModule; -import io.trino.hdfs.rubix.RubixEnabledConfig; -import io.trino.hdfs.rubix.RubixModule; -import io.trino.hdfs.s3.HiveS3Module; +import io.trino.spi.NodeManager; import java.util.Map; import java.util.Optional; -import static com.google.inject.Scopes.SINGLETON; import static com.google.inject.multibindings.MapBinder.newMapBinder; -import static io.airlift.configuration.ConditionalModule.conditionalModule; +import static com.google.inject.multibindings.OptionalBinder.newOptionalBinder; +import static java.util.Objects.requireNonNull; public class FileSystemModule extends AbstractConfigurationAwareModule { + private final String catalogName; + private final NodeManager nodeManager; + private final OpenTelemetry openTelemetry; + + public FileSystemModule(String catalogName, NodeManager nodeManager, OpenTelemetry openTelemetry) + { + this.catalogName = requireNonNull(catalogName, "catalogName is null"); + this.nodeManager = requireNonNull(nodeManager, "nodeManager is null"); + this.openTelemetry = requireNonNull(openTelemetry, "openTelemetry is null"); + } + @Override protected void setup(Binder binder) { FileSystemConfig config = buildConfigObject(FileSystemConfig.class); - binder.bind(HdfsFileSystemFactoryHolder.class).in(SINGLETON); + newOptionalBinder(binder, HdfsFileSystemLoader.class); if (config.isHadoopEnabled()) { - install(new HdfsFileSystemModule()); - install(new HdfsModule()); - install(new HdfsAuthenticationModule()); - install(conditionalModule(RubixEnabledConfig.class, RubixEnabledConfig::isCacheEnabled, new RubixModule())); - install(new HiveCosModule()); - install(new HiveGcsModule()); + HdfsFileSystemLoader loader = new HdfsFileSystemLoader( + getProperties(), + !config.isNativeAzureEnabled(), + !config.isNativeGcsEnabled(), + !config.isNativeS3Enabled(), + catalogName, + nodeManager, + openTelemetry); + + loader.configure().forEach(this::consumeProperty); + binder.bind(HdfsFileSystemLoader.class).toInstance(loader); } var factories = newMapBinder(binder, String.class, TrinoFileSystemFactory.class); @@ -71,9 +79,6 @@ protected void setup(Binder binder) factories.addBinding("abfs").to(AzureFileSystemFactory.class); factories.addBinding("abfss").to(AzureFileSystemFactory.class); } - else if (config.isHadoopEnabled()) { - install(new HiveAzureModule()); - } if (config.isNativeS3Enabled()) { install(new S3FileSystemModule()); @@ -81,38 +86,25 @@ else if (config.isHadoopEnabled()) { factories.addBinding("s3a").to(S3FileSystemFactory.class); factories.addBinding("s3n").to(S3FileSystemFactory.class); } - else if (config.isHadoopEnabled()) { - install(new HiveS3Module()); - } if (config.isNativeGcsEnabled()) { install(new GcsFileSystemModule()); factories.addBinding("gs").to(GcsFileSystemFactory.class); } - else { - install(new HiveGcsModule()); - } } @Provides @Singleton public TrinoFileSystemFactory createFileSystemFactory( - HdfsFileSystemFactoryHolder hdfsFileSystemFactory, + Optional hdfsFileSystemLoader, + LifeCycleManager lifeCycleManager, Map factories, Tracer tracer) { - TrinoFileSystemFactory delegate = new SwitchingFileSystemFactory(hdfsFileSystemFactory.value(), factories); - return new TracingFileSystemFactory(tracer, delegate); - } + Optional hdfsFactory = hdfsFileSystemLoader.map(HdfsFileSystemLoader::create); + hdfsFactory.ifPresent(lifeCycleManager::addInstance); - public static class HdfsFileSystemFactoryHolder - { - @Inject(optional = true) - private HdfsFileSystemFactory hdfsFileSystemFactory; - - public Optional value() - { - return Optional.ofNullable(hdfsFileSystemFactory); - } + TrinoFileSystemFactory delegate = new SwitchingFileSystemFactory(hdfsFactory, factories); + return new TracingFileSystemFactory(tracer, delegate); } } diff --git a/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/HdfsClassLoader.java b/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/HdfsClassLoader.java new file mode 100644 index 000000000000..172e916ebc15 --- /dev/null +++ b/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/HdfsClassLoader.java @@ -0,0 +1,123 @@ +/* + * 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 io.trino.filesystem.manager; + +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Enumeration; +import java.util.List; + +import static com.google.common.base.Preconditions.checkArgument; + +// based on io.trino.server.PluginClassLoader +final class HdfsClassLoader + extends URLClassLoader +{ + public HdfsClassLoader(List urls) + { + // This class loader should not have access to the system (application) class loader + super(urls.toArray(URL[]::new), getPlatformClassLoader()); + } + + @Override + protected Class loadClass(String name, boolean resolve) + throws ClassNotFoundException + { + synchronized (getClassLoadingLock(name)) { + // Check if class is in the loaded classes cache + Class cachedClass = findLoadedClass(name); + if (cachedClass != null) { + return resolveClass(cachedClass, resolve); + } + + // If this is an override class, only check override class loader + if (isOverrideClass(name)) { + return resolveClass(overrideClassLoader().loadClass(name), resolve); + } + + // Look for class locally + return super.loadClass(name, resolve); + } + } + + private Class resolveClass(Class clazz, boolean resolve) + { + if (resolve) { + resolveClass(clazz); + } + return clazz; + } + + @Override + public URL getResource(String name) + { + // If this is an override resource, only check override class loader + if (isOverrideResource(name)) { + return overrideClassLoader().getResource(name); + } + + // Look for resource locally + return super.getResource(name); + } + + @Override + public Enumeration getResources(String name) + throws IOException + { + // If this is an override resource, use override resources + if (isOverrideResource(name)) { + return overrideClassLoader().getResources(name); + } + + // Use local resources + return super.getResources(name); + } + + private ClassLoader overrideClassLoader() + { + return getClass().getClassLoader(); + } + + private static boolean isOverrideResource(String name) + { + return isOverrideClass(name.replace('.', '/')); + } + + private static boolean isOverrideClass(String name) + { + // SPI packages from io.trino.server.PluginManager and dependencies of trino-filesystem + return hasPackage(name, "io.trino.spi.") || + hasPackage(name, "com.fasterxml.jackson.annotation.") || + hasPackage(name, "io.airlift.slice.") || + hasPackage(name, "org.openjdk.jol.") || + hasPackage(name, "io.opentelemetry.api.") || + hasPackage(name, "io.opentelemetry.context.") || + hasPackage(name, "com.google.common.") || + hasExactPackage(name, "io.trino.memory.context.") || + hasExactPackage(name, "io.trino.filesystem."); + } + + private static boolean hasPackage(String name, String packageName) + { + checkArgument(!packageName.isEmpty() && packageName.charAt(packageName.length() - 1) == '.'); + return name.startsWith(packageName); + } + + private static boolean hasExactPackage(String name, String packageName) + { + checkArgument(!packageName.isEmpty() && packageName.charAt(packageName.length() - 1) == '.'); + return name.startsWith(packageName) && (name.lastIndexOf('.') == (packageName.length() - 1)); + } +} diff --git a/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/HdfsFileSystemLoader.java b/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/HdfsFileSystemLoader.java new file mode 100644 index 000000000000..c3e0785ac0f4 --- /dev/null +++ b/lib/trino-filesystem-manager/src/main/java/io/trino/filesystem/manager/HdfsFileSystemLoader.java @@ -0,0 +1,177 @@ +/* + * 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 io.trino.filesystem.manager; + +import io.opentelemetry.api.OpenTelemetry; +import io.trino.filesystem.TrinoFileSystemFactory; +import io.trino.spi.NodeManager; +import io.trino.spi.Plugin; +import io.trino.spi.classloader.ThreadContextClassLoader; +import jakarta.annotation.PreDestroy; + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.DirectoryStream; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.google.common.base.Verify.verify; +import static com.google.common.collect.Streams.stream; +import static java.nio.file.Files.newDirectoryStream; + +final class HdfsFileSystemLoader +{ + private final HdfsClassLoader classLoader; + private final Object manager; + + public HdfsFileSystemLoader( + Map config, + boolean azureEnabled, + boolean gcsEnabled, + boolean s3Enabled, + String catalogName, + NodeManager nodeManager, + OpenTelemetry openTelemetry) + { + Class clazz = tryLoadExistingHdfsManager(); + + // check if we are running inside a plugin class loader (full server mode) + if (!getClass().getClassLoader().equals(Plugin.class.getClassLoader())) { + verify(clazz == null, "HDFS should not be on the plugin classpath"); + File sourceFile = getCurrentClassLocation(); + File directory; + if (sourceFile.isDirectory()) { + // running DevelopmentServer in the IDE + verify(sourceFile.getPath().endsWith("/target/classes"), "Source file not in 'target' directory: %s", sourceFile); + directory = new File(sourceFile.getParentFile().getParentFile().getParentFile(), "trino-hdfs/target/hdfs"); + } + else { + // normal server mode where HDFS JARs are in a subdirectory of the plugin + directory = new File(sourceFile.getParentFile(), "hdfs"); + } + verify(directory.isDirectory(), "HDFS directory is missing: %s", directory); + classLoader = createClassLoader(directory); + clazz = loadHdfsManager(classLoader); + } + else { + verify(clazz != null, "HDFS should be on the classpath for tests"); + classLoader = null; + } + + try (var ignored = new ThreadContextClassLoader(classLoader)) { + manager = clazz.getConstructor(Map.class, boolean.class, boolean.class, boolean.class, String.class, NodeManager.class, OpenTelemetry.class) + .newInstance(config, azureEnabled, gcsEnabled, s3Enabled, catalogName, nodeManager, openTelemetry); + } + catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + @SuppressWarnings("unchecked") + public Set configure() + { + try (var ignored = new ThreadContextClassLoader(classLoader)) { + return (Set) manager.getClass().getMethod("configure").invoke(manager); + } + catch (ReflectiveOperationException e) { + throw new RuntimeException("Failed to configure HDFS:\n%s\n%s\n%s".formatted("<".repeat(70), e.getCause(), ">".repeat(70)), e); + } + } + + public TrinoFileSystemFactory create() + { + try (var ignored = new ThreadContextClassLoader(classLoader)) { + return (TrinoFileSystemFactory) manager.getClass().getMethod("create").invoke(manager); + } + catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + @PreDestroy + public void stop() + throws IOException, ReflectiveOperationException + { + try (classLoader; var ignored = new ThreadContextClassLoader(classLoader)) { + manager.getClass().getMethod("stop").invoke(manager); + } + } + + private File getCurrentClassLocation() + { + try { + return new File(getClass().getProtectionDomain().getCodeSource().getLocation().toURI()); + } + catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + + private Class tryLoadExistingHdfsManager() + { + try { + return loadHdfsManager(getClass().getClassLoader()); + } + catch (RuntimeException e) { + return null; + } + } + + private static Class loadHdfsManager(ClassLoader classLoader) + { + try { + return classLoader.loadClass("io.trino.filesystem.hdfs.HdfsFileSystemManager"); + } + catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + private static HdfsClassLoader createClassLoader(File path) + { + List urls = buildClassPath(path); + verify(!urls.isEmpty(), "HDFS directory is empty: %s", path); + return new HdfsClassLoader(urls); + } + + private static List buildClassPath(File path) + { + try (DirectoryStream directoryStream = newDirectoryStream(path.toPath())) { + return stream(directoryStream) + .map(Path::toFile) + .sorted().toList().stream() + .map(HdfsFileSystemLoader::fileToUrl) + .toList(); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static URL fileToUrl(File file) + { + try { + return file.toURI().toURL(); + } + catch (MalformedURLException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/lib/trino-hdfs/pom.xml b/lib/trino-hdfs/pom.xml index e2efb802453c..813b9c6f44f2 100644 --- a/lib/trino-hdfs/pom.xml +++ b/lib/trino-hdfs/pom.xml @@ -76,6 +76,11 @@ failsafe + + io.airlift + bootstrap + + io.airlift concurrent @@ -283,6 +288,21 @@ + + + ca.vanzyl.provisio.maven.plugins + provisio-maven-plugin + + + + provision + + + ${project.build.directory}/hdfs + + + + diff --git a/lib/trino-hdfs/src/main/java/io/trino/filesystem/hdfs/HdfsFileSystemManager.java b/lib/trino-hdfs/src/main/java/io/trino/filesystem/hdfs/HdfsFileSystemManager.java new file mode 100644 index 000000000000..19c89f815213 --- /dev/null +++ b/lib/trino-hdfs/src/main/java/io/trino/filesystem/hdfs/HdfsFileSystemManager.java @@ -0,0 +1,110 @@ +/* + * 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 io.trino.filesystem.hdfs; + +import com.google.inject.Injector; +import com.google.inject.Module; +import io.airlift.bootstrap.Bootstrap; +import io.airlift.bootstrap.LifeCycleManager; +import io.opentelemetry.api.OpenTelemetry; +import io.trino.filesystem.TrinoFileSystemFactory; +import io.trino.hdfs.HdfsModule; +import io.trino.hdfs.authentication.HdfsAuthenticationModule; +import io.trino.hdfs.azure.HiveAzureModule; +import io.trino.hdfs.cos.HiveCosModule; +import io.trino.hdfs.gcs.HiveGcsModule; +import io.trino.hdfs.rubix.RubixEnabledConfig; +import io.trino.hdfs.rubix.RubixModule; +import io.trino.hdfs.s3.HiveS3Module; +import io.trino.plugin.base.CatalogName; +import io.trino.plugin.base.jmx.ConnectorObjectNameGeneratorModule; +import io.trino.plugin.base.jmx.MBeanServerModule; +import io.trino.spi.NodeManager; +import org.weakref.jmx.guice.MBeanModule; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static io.airlift.configuration.ConditionalModule.conditionalModule; + +public final class HdfsFileSystemManager +{ + private final Bootstrap bootstrap; + private LifeCycleManager lifecycleManager; + + public HdfsFileSystemManager( + Map config, + boolean azureEnabled, + boolean gcsEnabled, + boolean s3Enabled, + String catalogName, + NodeManager nodeManager, + OpenTelemetry openTelemetry) + { + List modules = new ArrayList<>(); + + modules.add(new MBeanModule()); + modules.add(new MBeanServerModule()); + modules.add(new ConnectorObjectNameGeneratorModule("", "")); + + modules.add(new HdfsFileSystemModule()); + modules.add(new HdfsModule()); + modules.add(new HdfsAuthenticationModule()); + modules.add(new HiveCosModule()); + + modules.add(conditionalModule(RubixEnabledConfig.class, RubixEnabledConfig::isCacheEnabled, new RubixModule())); + + modules.add(binder -> { + binder.bind(NodeManager.class).toInstance(nodeManager); + binder.bind(OpenTelemetry.class).toInstance(openTelemetry); + binder.bind(CatalogName.class).toInstance(new CatalogName(catalogName)); + }); + + if (azureEnabled) { + modules.add(new HiveAzureModule()); + } + if (gcsEnabled) { + modules.add(new HiveGcsModule()); + } + if (s3Enabled) { + modules.add(new HiveS3Module()); + } + + bootstrap = new Bootstrap(modules) + .doNotInitializeLogging() + .setRequiredConfigurationProperties(Map.of()) + .setOptionalConfigurationProperties(config); + } + + public Set configure() + { + return bootstrap.configure(); + } + + public TrinoFileSystemFactory create() + { + Injector injector = bootstrap.initialize(); + lifecycleManager = injector.getInstance(LifeCycleManager.class); + return injector.getInstance(HdfsFileSystemFactory.class); + } + + public void stop() + { + if (lifecycleManager != null) { + lifecycleManager.stop(); + } + } +} diff --git a/lib/trino-hdfs/src/main/java/io/trino/hdfs/HdfsEnvironment.java b/lib/trino-hdfs/src/main/java/io/trino/hdfs/HdfsEnvironment.java index 7608e1f73e3f..7950a9b02fda 100644 --- a/lib/trino-hdfs/src/main/java/io/trino/hdfs/HdfsEnvironment.java +++ b/lib/trino-hdfs/src/main/java/io/trino/hdfs/HdfsEnvironment.java @@ -23,6 +23,7 @@ import io.trino.hdfs.authentication.HdfsAuthentication; import io.trino.hdfs.gcs.GcsStorageFactory; import io.trino.spi.Plugin; +import io.trino.spi.classloader.ThreadContextClassLoader; import io.trino.spi.security.ConnectorIdentity; import jakarta.annotation.PreDestroy; import org.apache.hadoop.conf.Configuration; @@ -82,7 +83,7 @@ public HdfsEnvironment( public void shutdown() throws IOException { - // shut down if running in a plugin classloader + // shut down if running in an isolated classloader if (!getClass().getClassLoader().equals(Plugin.class.getClassLoader())) { FileSystemFinalizerService.shutdown(); stopFileSystemStatsThread(); @@ -104,14 +105,16 @@ public FileSystem getFileSystem(HdfsContext context, Path path) public FileSystem getFileSystem(ConnectorIdentity identity, Path path, Configuration configuration) throws IOException { - return hdfsAuthentication.doAs(identity, () -> { - FileSystem fileSystem = path.getFileSystem(configuration); - fileSystem.setVerifyChecksum(verifyChecksum); - if (getRawFileSystem(fileSystem) instanceof OpenTelemetryAwareFileSystem fs) { - fs.setOpenTelemetry(openTelemetry); - } - return fileSystem; - }); + try (var ignored = new ThreadContextClassLoader(getClass().getClassLoader())) { + return hdfsAuthentication.doAs(identity, () -> { + FileSystem fileSystem = path.getFileSystem(configuration); + fileSystem.setVerifyChecksum(verifyChecksum); + if (getRawFileSystem(fileSystem) instanceof OpenTelemetryAwareFileSystem fs) { + fs.setOpenTelemetry(openTelemetry); + } + return fileSystem; + }); + } } public Optional getNewDirectoryPermissions() @@ -127,7 +130,9 @@ public boolean isNewFileInheritOwnership() public R doAs(ConnectorIdentity identity, GenericExceptionAction action) throws E { - return hdfsAuthentication.doAs(identity, action); + try (var ignored = new ThreadContextClassLoader(getClass().getClassLoader())) { + return hdfsAuthentication.doAs(identity, action); + } } public Storage createGcsStorage(HdfsContext context, Path path) diff --git a/lib/trino-hdfs/src/main/provisio/hdfs.xml b/lib/trino-hdfs/src/main/provisio/hdfs.xml new file mode 100644 index 000000000000..887dcfeacf6c --- /dev/null +++ b/lib/trino-hdfs/src/main/provisio/hdfs.xml @@ -0,0 +1,4 @@ + + + + diff --git a/lib/trino-hdfs/src/test/java/io/trino/filesystem/hdfs/TestHdfsFileSystemManager.java b/lib/trino-hdfs/src/test/java/io/trino/filesystem/hdfs/TestHdfsFileSystemManager.java new file mode 100644 index 000000000000..83dde99c7bed --- /dev/null +++ b/lib/trino-hdfs/src/test/java/io/trino/filesystem/hdfs/TestHdfsFileSystemManager.java @@ -0,0 +1,61 @@ +/* + * 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 io.trino.filesystem.hdfs; + +import com.google.common.collect.ImmutableMap; +import io.opentelemetry.api.OpenTelemetry; +import io.trino.filesystem.Location; +import io.trino.filesystem.TrinoFileSystem; +import io.trino.filesystem.TrinoFileSystemFactory; +import io.trino.spi.security.ConnectorIdentity; +import io.trino.testing.TestingNodeManager; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Set; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +class TestHdfsFileSystemManager +{ + @Test + void testManager() + throws IOException + { + HdfsFileSystemManager manager = new HdfsFileSystemManager( + ImmutableMap.builder() + .put("unused-property", "ignored") + .put("hive.dfs.verify-checksum", "false") + .put("hive.s3.region", "us-west-1") + .buildOrThrow(), + true, + true, + true, + "test", + new TestingNodeManager(), + OpenTelemetry.noop()); + + Set used = manager.configure(); + assertThat(used).containsExactly("hive.dfs.verify-checksum", "hive.s3.region"); + + TrinoFileSystemFactory factory = manager.create(); + TrinoFileSystem fileSystem = factory.create(ConnectorIdentity.ofUser("test")); + + Location location = Location.of("/tmp/" + UUID.randomUUID()); + assertThat(fileSystem.newInputFile(location).exists()).isFalse(); + + manager.stop(); + } +} diff --git a/lib/trino-orc/pom.xml b/lib/trino-orc/pom.xml index 6208e2e87e84..04f9293efcbf 100644 --- a/lib/trino-orc/pom.xml +++ b/lib/trino-orc/pom.xml @@ -114,12 +114,6 @@ runtime - - io.trino.hadoop - hadoop-apache - runtime - - io.airlift junit-extensions @@ -150,6 +144,12 @@ test + + io.trino.hadoop + hadoop-apache + test + + io.trino.hive hive-apache diff --git a/plugin/trino-delta-lake/pom.xml b/plugin/trino-delta-lake/pom.xml index 19ba1084696a..932f6a96b39e 100644 --- a/plugin/trino-delta-lake/pom.xml +++ b/plugin/trino-delta-lake/pom.xml @@ -258,12 +258,6 @@ runtime - - io.trino - trino-hdfs - runtime - - io.trino trino-memory-context @@ -271,8 +265,8 @@ - io.trino.hadoop - hadoop-apache + org.jetbrains + annotations runtime @@ -314,6 +308,12 @@ test + + io.trino + trino-hdfs + test + + io.trino trino-hive @@ -396,20 +396,20 @@ - io.trino.tpch - tpch + io.trino.hadoop + hadoop-apache test - org.assertj - assertj-core + io.trino.tpch + tpch test - org.jetbrains - annotations + org.assertj + assertj-core test diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/InternalDeltaLakeConnectorFactory.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/InternalDeltaLakeConnectorFactory.java index a9379152401b..2f39dbbd64ab 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/InternalDeltaLakeConnectorFactory.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/InternalDeltaLakeConnectorFactory.java @@ -91,7 +91,7 @@ public static Connector createConnector( new DeltaLakeSynchronizerModule(), fileSystemFactory .map(factory -> (Module) binder -> binder.bind(TrinoFileSystemFactory.class).toInstance(factory)) - .orElseGet(FileSystemModule::new), + .orElseGet(() -> new FileSystemModule(catalogName, context.getNodeManager(), context.getOpenTelemetry())), binder -> { binder.bind(OpenTelemetry.class).toInstance(context.getOpenTelemetry()); binder.bind(Tracer.class).toInstance(context.getTracer()); diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeGlueMetastore.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeGlueMetastore.java index c06f4a1cb959..54fa8be4597b 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeGlueMetastore.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeGlueMetastore.java @@ -113,11 +113,11 @@ public void setUp() .put("delta.hide-non-delta-lake-tables", "true") .buildOrThrow(); + ConnectorContext context = new TestingConnectorContext(); Bootstrap app = new Bootstrap( // connector dependencies new JsonModule(), binder -> { - ConnectorContext context = new TestingConnectorContext(); binder.bind(CatalogName.class).toInstance(new CatalogName("test")); binder.bind(TypeManager.class).toInstance(context.getTypeManager()); binder.bind(NodeManager.class).toInstance(context.getNodeManager()); @@ -130,7 +130,7 @@ public void setUp() new DeltaLakeMetastoreModule(), new DeltaLakeModule(), // test setup - new FileSystemModule()); + new FileSystemModule("test", context.getNodeManager(), context.getOpenTelemetry())); Injector injector = app .doNotInitializeLogging() diff --git a/plugin/trino-geospatial/pom.xml b/plugin/trino-geospatial/pom.xml index 1d0d11382103..61fa6c3bdf2d 100644 --- a/plugin/trino-geospatial/pom.xml +++ b/plugin/trino-geospatial/pom.xml @@ -114,6 +114,12 @@ test + + io.trino + trino-hdfs + test + + io.trino trino-hive diff --git a/plugin/trino-hive-hadoop2/pom.xml b/plugin/trino-hive-hadoop2/pom.xml index e18ca769a954..02a3f5437a36 100644 --- a/plugin/trino-hive-hadoop2/pom.xml +++ b/plugin/trino-hive-hadoop2/pom.xml @@ -112,24 +112,12 @@ runtime - - io.trino - trino-hdfs - runtime - - io.trino trino-plugin-toolkit runtime - - io.trino.hadoop - hadoop-apache - runtime - - org.alluxio alluxio-shaded-client @@ -148,6 +136,12 @@ test + + io.trino + trino-hdfs + test + + io.trino trino-hive @@ -186,6 +180,12 @@ test + + io.trino.hadoop + hadoop-apache + test + + io.trino.hive hive-apache diff --git a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHivePlugin.java b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHivePlugin.java index 6957c6d2a309..b1dc799428d0 100644 --- a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHivePlugin.java +++ b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHivePlugin.java @@ -307,7 +307,7 @@ public void testRubixCacheWithNonExistingCacheDirectory() .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext())) - .hasRootCauseMessage("None of the cache parent directories exists"); + .hasMessageContaining("None of the cache parent directories exists"); assertThatThrownBy(() -> connectorFactory.create( "test", @@ -318,7 +318,7 @@ public void testRubixCacheWithNonExistingCacheDirectory() .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext())) - .hasRootCauseMessage("caching directories were not provided"); + .hasMessageContaining("caching directories were not provided"); // cache directories should not be required when cache is not explicitly started on coordinator connectorFactory.create( diff --git a/plugin/trino-hive/pom.xml b/plugin/trino-hive/pom.xml index c91a4f9ab119..d2bd5b9582ee 100644 --- a/plugin/trino-hive/pom.xml +++ b/plugin/trino-hive/pom.xml @@ -294,18 +294,6 @@ runtime - - io.trino - trino-hdfs - runtime - - - - io.trino.hadoop - hadoop-apache - runtime - - jakarta.xml.bind jakarta.xml.bind-api @@ -385,6 +373,12 @@ test + + io.trino + trino-hdfs + test + + io.trino trino-main @@ -441,6 +435,12 @@ test + + io.trino.hadoop + hadoop-apache + test + + io.trino.hive hive-apache diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/InternalHiveConnectorFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/InternalHiveConnectorFactory.java index 4f5c0c2c656a..69a5fe1089d1 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/InternalHiveConnectorFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/InternalHiveConnectorFactory.java @@ -110,7 +110,7 @@ public static Connector createConnector( new HiveSecurityModule(), fileSystemFactory .map(factory -> (Module) binder -> binder.bind(TrinoFileSystemFactory.class).toInstance(factory)) - .orElseGet(FileSystemModule::new), + .orElseGet(() -> new FileSystemModule(catalogName, context.getNodeManager(), openTelemetry.orElse(context.getOpenTelemetry()))), new HiveProcedureModule(), new MBeanServerModule(), binder -> { diff --git a/plugin/trino-hudi/pom.xml b/plugin/trino-hudi/pom.xml index 8b7b8545a182..81f18cd509dd 100644 --- a/plugin/trino-hudi/pom.xml +++ b/plugin/trino-hudi/pom.xml @@ -183,14 +183,8 @@ - io.trino - trino-hdfs - runtime - - - - io.trino.hadoop - hadoop-apache + org.jetbrains + annotations runtime @@ -206,6 +200,12 @@ test + + io.trino + trino-hdfs + test + + io.trino trino-hive @@ -263,6 +263,12 @@ test + + io.trino.hadoop + hadoop-apache + test + + io.trino.tpch tpch @@ -320,12 +326,6 @@ test - - org.jetbrains - annotations - test - - org.junit.jupiter junit-jupiter-api diff --git a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/InternalHudiConnectorFactory.java b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/InternalHudiConnectorFactory.java index a65fda489440..9a09752ce57f 100644 --- a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/InternalHudiConnectorFactory.java +++ b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/InternalHudiConnectorFactory.java @@ -70,7 +70,7 @@ public static Connector createConnector( new HiveMetastoreModule(metastore), fileSystemFactory .map(factory -> (Module) binder -> binder.bind(TrinoFileSystemFactory.class).toInstance(factory)) - .orElseGet(FileSystemModule::new), + .orElseGet(() -> new FileSystemModule(catalogName, context.getNodeManager(), context.getOpenTelemetry())), new MBeanServerModule(), binder -> { binder.bind(OpenTelemetry.class).toInstance(context.getOpenTelemetry()); diff --git a/plugin/trino-iceberg/pom.xml b/plugin/trino-iceberg/pom.xml index 1c29509ef12d..cb388f332011 100644 --- a/plugin/trino-iceberg/pom.xml +++ b/plugin/trino-iceberg/pom.xml @@ -339,18 +339,6 @@ runtime - - io.trino - trino-hdfs - runtime - - - - io.trino.hadoop - hadoop-apache - runtime - - org.apache.httpcomponents.client5 httpclient5 @@ -462,6 +450,12 @@ test + + io.trino + trino-hdfs + test + + io.trino trino-hive @@ -519,6 +513,12 @@ test + + io.trino.hadoop + hadoop-apache + test + + io.trino.hive hive-apache diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/InternalIcebergConnectorFactory.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/InternalIcebergConnectorFactory.java index 6ea876634e07..0ebc1bd4378b 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/InternalIcebergConnectorFactory.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/InternalIcebergConnectorFactory.java @@ -89,7 +89,7 @@ public static Connector createConnector( new MBeanServerModule(), fileSystemFactory .map(factory -> (Module) binder -> binder.bind(TrinoFileSystemFactory.class).toInstance(factory)) - .orElseGet(FileSystemModule::new), + .orElseGet(() -> new FileSystemModule(catalogName, context.getNodeManager(), context.getOpenTelemetry())), binder -> { binder.bind(OpenTelemetry.class).toInstance(context.getOpenTelemetry()); binder.bind(Tracer.class).toInstance(context.getTracer()); diff --git a/plugin/trino-session-property-managers/pom.xml b/plugin/trino-session-property-managers/pom.xml index 804800748261..74aabce98b64 100644 --- a/plugin/trino-session-property-managers/pom.xml +++ b/plugin/trino-session-property-managers/pom.xml @@ -156,6 +156,12 @@ test + + io.trino + trino-hdfs + test + + io.trino trino-hive diff --git a/testing/trino-faulttolerant-tests/pom.xml b/testing/trino-faulttolerant-tests/pom.xml index 4df8c4a8066d..511f248352a2 100644 --- a/testing/trino-faulttolerant-tests/pom.xml +++ b/testing/trino-faulttolerant-tests/pom.xml @@ -185,6 +185,12 @@ test + + io.trino + trino-hdfs + test + + io.trino trino-hive From b408b076889e9cc46244707f13c3a69c746fc93f Mon Sep 17 00:00:00 2001 From: David Phillips Date: Tue, 7 Nov 2023 22:33:07 -0800 Subject: [PATCH 235/587] Deprecate and remove usages of duplicatePluginClassLoader --- .../trino/spi/connector/ConnectorContext.java | 1 + .../deltalake/DeltaLakeConnectorFactory.java | 31 ++++++---------- .../plugin/deltalake/DeltaLakePlugin.java | 9 +---- .../plugin/hive/HiveConnectorFactory.java | 36 ++++--------------- .../plugin/hudi/HudiConnectorFactory.java | 22 ++---------- .../iceberg/IcebergConnectorFactory.java | 36 ++++--------------- 6 files changed, 26 insertions(+), 109 deletions(-) diff --git a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorContext.java b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorContext.java index 07f8a7f4742d..db8c9d653b98 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorContext.java +++ b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorContext.java @@ -79,6 +79,7 @@ default PageIndexerFactory getPageIndexerFactory() throw new UnsupportedOperationException(); } + @Deprecated(forRemoval = true) default ClassLoader duplicatePluginClassLoader() { throw new UnsupportedOperationException(); diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeConnectorFactory.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeConnectorFactory.java index 01140d080070..df3cc06f5b77 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeConnectorFactory.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeConnectorFactory.java @@ -18,12 +18,12 @@ import io.trino.spi.connector.ConnectorContext; import io.trino.spi.connector.ConnectorFactory; -import java.lang.reflect.InvocationTargetException; import java.util.Map; import java.util.Optional; -import static com.google.common.base.Throwables.throwIfUnchecked; +import static com.google.inject.util.Modules.EMPTY_MODULE; import static io.trino.plugin.base.Versions.checkStrictSpiVersionMatch; +import static io.trino.plugin.deltalake.InternalDeltaLakeConnectorFactory.createConnector; import static java.util.Objects.requireNonNull; public class DeltaLakeConnectorFactory @@ -31,9 +31,14 @@ public class DeltaLakeConnectorFactory { public static final String CONNECTOR_NAME = "delta_lake"; - private final Class module; + private final Module module; - public DeltaLakeConnectorFactory(Class module) + public DeltaLakeConnectorFactory() + { + this(EMPTY_MODULE); + } + + public DeltaLakeConnectorFactory(Module module) { this.module = requireNonNull(module, "module is null"); } @@ -48,22 +53,6 @@ public String getName() public Connector create(String catalogName, Map config, ConnectorContext context) { checkStrictSpiVersionMatch(context, this); - - ClassLoader classLoader = context.duplicatePluginClassLoader(); - try { - Class moduleClass = classLoader.loadClass(Module.class.getName()); - Object moduleInstance = classLoader.loadClass(module.getName()).getConstructor().newInstance(); - return (Connector) classLoader.loadClass(InternalDeltaLakeConnectorFactory.class.getName()) - .getMethod("createConnector", String.class, Map.class, ConnectorContext.class, Optional.class, Optional.class, moduleClass) - .invoke(null, catalogName, config, context, Optional.empty(), Optional.empty(), moduleInstance); - } - catch (InvocationTargetException e) { - Throwable targetException = e.getTargetException(); - throwIfUnchecked(targetException); - throw new RuntimeException(targetException); - } - catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } + return createConnector(catalogName, config, context, Optional.empty(), Optional.empty(), module); } } diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakePlugin.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakePlugin.java index f4263e59dd80..f4b44d0e95de 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakePlugin.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakePlugin.java @@ -14,8 +14,6 @@ package io.trino.plugin.deltalake; import com.google.common.collect.ImmutableList; -import com.google.inject.Module; -import io.trino.plugin.hive.HiveConnectorFactory.EmptyModule; import io.trino.spi.Plugin; import io.trino.spi.connector.ConnectorFactory; @@ -25,11 +23,6 @@ public class DeltaLakePlugin @Override public Iterable getConnectorFactories() { - return ImmutableList.of(getConnectorFactory(EmptyModule.class)); - } - - public ConnectorFactory getConnectorFactory(Class module) - { - return new DeltaLakeConnectorFactory(module); + return ImmutableList.of(new DeltaLakeConnectorFactory()); } } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveConnectorFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveConnectorFactory.java index f7e50db00269..d2a861c28e04 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveConnectorFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveConnectorFactory.java @@ -13,30 +13,29 @@ */ package io.trino.plugin.hive; -import com.google.inject.Binder; import com.google.inject.Module; import io.trino.spi.connector.Connector; import io.trino.spi.connector.ConnectorContext; import io.trino.spi.connector.ConnectorFactory; -import java.lang.reflect.InvocationTargetException; import java.util.Map; -import static com.google.common.base.Throwables.throwIfUnchecked; +import static com.google.inject.util.Modules.EMPTY_MODULE; import static io.trino.plugin.base.Versions.checkStrictSpiVersionMatch; +import static io.trino.plugin.hive.InternalHiveConnectorFactory.createConnector; import static java.util.Objects.requireNonNull; public class HiveConnectorFactory implements ConnectorFactory { - private final Class module; + private final Module module; public HiveConnectorFactory() { - this(EmptyModule.class); + this(EMPTY_MODULE); } - public HiveConnectorFactory(Class module) + public HiveConnectorFactory(Module module) { this.module = requireNonNull(module, "module is null"); } @@ -51,29 +50,6 @@ public String getName() public Connector create(String catalogName, Map config, ConnectorContext context) { checkStrictSpiVersionMatch(context, this); - - ClassLoader classLoader = context.duplicatePluginClassLoader(); - try { - Object moduleInstance = classLoader.loadClass(module.getName()).getConstructor().newInstance(); - Class moduleClass = classLoader.loadClass(Module.class.getName()); - return (Connector) classLoader.loadClass(InternalHiveConnectorFactory.class.getName()) - .getMethod("createConnector", String.class, Map.class, ConnectorContext.class, moduleClass) - .invoke(null, catalogName, config, context, moduleInstance); - } - catch (InvocationTargetException e) { - Throwable targetException = e.getTargetException(); - throwIfUnchecked(targetException); - throw new RuntimeException(targetException); - } - catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } - } - - public static class EmptyModule - implements Module - { - @Override - public void configure(Binder binder) {} + return createConnector(catalogName, config, context, module); } } diff --git a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiConnectorFactory.java b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiConnectorFactory.java index 98d1a49cc3e2..21c7a6c03e3e 100644 --- a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiConnectorFactory.java +++ b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiConnectorFactory.java @@ -17,19 +17,15 @@ import io.trino.spi.connector.ConnectorContext; import io.trino.spi.connector.ConnectorFactory; -import java.lang.reflect.InvocationTargetException; import java.util.Map; import java.util.Optional; -import static com.google.common.base.Throwables.throwIfUnchecked; import static io.trino.plugin.base.Versions.checkStrictSpiVersionMatch; +import static io.trino.plugin.hudi.InternalHudiConnectorFactory.createConnector; public class HudiConnectorFactory implements ConnectorFactory { - public HudiConnectorFactory() - {} - @Override public String getName() { @@ -40,20 +36,6 @@ public String getName() public Connector create(String catalogName, Map config, ConnectorContext context) { checkStrictSpiVersionMatch(context, this); - - ClassLoader classLoader = context.duplicatePluginClassLoader(); - try { - return (Connector) classLoader.loadClass(InternalHudiConnectorFactory.class.getName()) - .getMethod("createConnector", String.class, Map.class, ConnectorContext.class, Optional.class, Optional.class) - .invoke(null, catalogName, config, context, Optional.empty(), Optional.empty()); - } - catch (InvocationTargetException e) { - Throwable targetException = e.getTargetException(); - throwIfUnchecked(targetException); - throw new RuntimeException(targetException); - } - catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } + return createConnector(catalogName, config, context, Optional.empty(), Optional.empty()); } } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergConnectorFactory.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergConnectorFactory.java index 39e2c9e52617..22aa76c52b52 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergConnectorFactory.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergConnectorFactory.java @@ -13,31 +13,30 @@ */ package io.trino.plugin.iceberg; -import com.google.inject.Binder; import com.google.inject.Module; import io.trino.spi.connector.Connector; import io.trino.spi.connector.ConnectorContext; import io.trino.spi.connector.ConnectorFactory; -import java.lang.reflect.InvocationTargetException; import java.util.Map; import java.util.Optional; -import static com.google.common.base.Throwables.throwIfUnchecked; +import static com.google.inject.util.Modules.EMPTY_MODULE; import static io.trino.plugin.base.Versions.checkStrictSpiVersionMatch; +import static io.trino.plugin.iceberg.InternalIcebergConnectorFactory.createConnector; import static java.util.Objects.requireNonNull; public class IcebergConnectorFactory implements ConnectorFactory { - private final Class module; + private final Module module; public IcebergConnectorFactory() { - this(EmptyModule.class); + this(EMPTY_MODULE); } - public IcebergConnectorFactory(Class module) + public IcebergConnectorFactory(Module module) { this.module = requireNonNull(module, "module is null"); } @@ -52,29 +51,6 @@ public String getName() public Connector create(String catalogName, Map config, ConnectorContext context) { checkStrictSpiVersionMatch(context, this); - - ClassLoader classLoader = context.duplicatePluginClassLoader(); - try { - Object moduleInstance = classLoader.loadClass(module.getName()).getConstructor().newInstance(); - Class moduleClass = classLoader.loadClass(Module.class.getName()); - return (Connector) classLoader.loadClass(InternalIcebergConnectorFactory.class.getName()) - .getMethod("createConnector", String.class, Map.class, ConnectorContext.class, moduleClass, Optional.class, Optional.class) - .invoke(null, catalogName, config, context, moduleInstance, Optional.empty(), Optional.empty()); - } - catch (InvocationTargetException e) { - Throwable targetException = e.getTargetException(); - throwIfUnchecked(targetException); - throw new RuntimeException(targetException); - } - catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } - } - - public static class EmptyModule - implements Module - { - @Override - public void configure(Binder binder) {} + return createConnector(catalogName, config, context, module, Optional.empty(), Optional.empty()); } } From 0a321a46d2fb40302514000318cc1bcd57162f7f Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Tue, 14 Nov 2023 16:45:38 +0900 Subject: [PATCH 236/587] Use SslUtils.createSSLContext in Hive connector --- plugin/trino-hive/pom.xml | 5 - .../DefaultThriftMetastoreClientFactory.java | 110 +----------------- 2 files changed, 4 insertions(+), 111 deletions(-) diff --git a/plugin/trino-hive/pom.xml b/plugin/trino-hive/pom.xml index d2bd5b9582ee..144933e8198b 100644 --- a/plugin/trino-hive/pom.xml +++ b/plugin/trino-hive/pom.xml @@ -111,11 +111,6 @@ log - - io.airlift - security - - io.airlift stats diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/DefaultThriftMetastoreClientFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/DefaultThriftMetastoreClientFactory.java index cb2319390b3e..c9a586dd7475 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/DefaultThriftMetastoreClientFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/DefaultThriftMetastoreClientFactory.java @@ -15,38 +15,22 @@ import com.google.common.net.HostAndPort; import com.google.inject.Inject; -import io.airlift.security.pem.PemReader; import io.airlift.units.Duration; import io.trino.plugin.hive.metastore.thrift.ThriftHiveMetastoreClient.TransportSupplier; import io.trino.spi.NodeManager; import org.apache.thrift.transport.TTransport; import org.apache.thrift.transport.TTransportException; -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; -import javax.security.auth.x500.X500Principal; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.security.cert.Certificate; -import java.security.cert.CertificateExpiredException; -import java.security.cert.CertificateNotYetValidException; -import java.security.cert.X509Certificate; -import java.util.Arrays; -import java.util.List; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; +import static io.trino.plugin.base.ssl.SslUtils.createSSLContext; import static java.lang.Math.toIntExact; -import static java.util.Collections.list; import static java.util.Objects.requireNonNull; public class DefaultThriftMetastoreClientFactory @@ -95,7 +79,7 @@ public DefaultThriftMetastoreClientFactory( config.isTlsEnabled(), Optional.ofNullable(config.getKeystorePath()), Optional.ofNullable(config.getKeystorePassword()), - config.getTruststorePath(), + Optional.ofNullable(config.getTruststorePath()), Optional.ofNullable(config.getTruststorePassword())), Optional.ofNullable(config.getSocksProxy()), config.getConnectTimeout(), @@ -137,7 +121,7 @@ private static Optional buildSslContext( boolean tlsEnabled, Optional keyStorePath, Optional keyStorePassword, - File trustStorePath, + Optional trustStorePath, Optional trustStorePassword) { if (!tlsEnabled) { @@ -145,96 +129,10 @@ private static Optional buildSslContext( } try { - // load KeyStore if configured and get KeyManagers - KeyManager[] keyManagers = null; - char[] keyManagerPassword = new char[0]; - if (keyStorePath.isPresent()) { - KeyStore keyStore; - try { - keyStore = PemReader.loadKeyStore(keyStorePath.get(), keyStorePath.get(), keyStorePassword); - } - catch (IOException | GeneralSecurityException e) { - keyManagerPassword = keyStorePassword.map(String::toCharArray).orElse(null); - keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - try (InputStream in = new FileInputStream(keyStorePath.get())) { - keyStore.load(in, keyManagerPassword); - } - } - validateCertificates(keyStore); - KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - keyManagerFactory.init(keyStore, keyManagerPassword); - keyManagers = keyManagerFactory.getKeyManagers(); - } - - // load TrustStore - KeyStore trustStore = loadTrustStore(trustStorePath, trustStorePassword); - - // create TrustManagerFactory - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(trustStore); - - // get X509TrustManager - TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); - if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { - throw new RuntimeException("Unexpected default trust managers:" + Arrays.toString(trustManagers)); - } - - // create SSLContext - SSLContext sslContext = SSLContext.getInstance("SSL"); - sslContext.init(keyManagers, trustManagers, null); - return Optional.of(sslContext); + return Optional.of(createSSLContext(keyStorePath, keyStorePassword, trustStorePath, trustStorePassword)); } catch (GeneralSecurityException | IOException e) { throw new RuntimeException(e); } } - - private static KeyStore loadTrustStore(File trustStorePath, Optional trustStorePassword) - throws IOException, GeneralSecurityException - { - KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); - try { - // attempt to read the trust store as a PEM file - List certificateChain = PemReader.readCertificateChain(trustStorePath); - if (!certificateChain.isEmpty()) { - trustStore.load(null, null); - for (X509Certificate certificate : certificateChain) { - X500Principal principal = certificate.getSubjectX500Principal(); - trustStore.setCertificateEntry(principal.getName(), certificate); - } - return trustStore; - } - } - catch (IOException | GeneralSecurityException e) { - } - - try (InputStream in = new FileInputStream(trustStorePath)) { - trustStore.load(in, trustStorePassword.map(String::toCharArray).orElse(null)); - } - return trustStore; - } - - private static void validateCertificates(KeyStore keyStore) - throws GeneralSecurityException - { - for (String alias : list(keyStore.aliases())) { - if (!keyStore.isKeyEntry(alias)) { - continue; - } - Certificate certificate = keyStore.getCertificate(alias); - if (!(certificate instanceof X509Certificate)) { - continue; - } - - try { - ((X509Certificate) certificate).checkValidity(); - } - catch (CertificateExpiredException e) { - throw new CertificateExpiredException("KeyStore certificate is expired: " + e.getMessage()); - } - catch (CertificateNotYetValidException e) { - throw new CertificateNotYetValidException("KeyStore certificate is not yet valid: " + e.getMessage()); - } - } - } } From e38ebbffe27cf9d568a27e78bd06b28fe39fe0e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Kokosi=C5=84ski?= Date: Mon, 13 Nov 2023 21:47:15 +0100 Subject: [PATCH 237/587] Remove outdated SPI revapi exclusions --- core/trino-spi/pom.xml | 848 ----------------------------------------- 1 file changed, 848 deletions(-) diff --git a/core/trino-spi/pom.xml b/core/trino-spi/pom.xml index 7a3175bfeb04..1c0bb8249690 100644 --- a/core/trino-spi/pom.xml +++ b/core/trino-spi/pom.xml @@ -212,854 +212,6 @@ - - java.class.nonPublicPartOfAPI - class io.trino.spi.block.SqlMap.HashTableSupplier - class io.trino.spi.block.SqlMap.HashTableSupplier - - - java.method.visibilityReduced - method void io.trino.spi.block.SqlMap::<init>(io.trino.spi.type.MapType, io.trino.spi.block.Block, io.trino.spi.block.Block, io.trino.spi.block.SqlMap.HashTableSupplier, int, int) - method void io.trino.spi.block.SqlMap::<init>(io.trino.spi.type.MapType, io.trino.spi.block.Block, io.trino.spi.block.Block, io.trino.spi.block.SqlMap.HashTableSupplier, int, int) - public - - - java.method.returnTypeChangedCovariantly - method <E extends java.lang.Throwable> io.trino.spi.block.Block io.trino.spi.block.BufferedMapValueBuilder::build(int, io.trino.spi.block.MapValueBuilder<E>) throws E - method <E extends java.lang.Throwable> io.trino.spi.block.SqlMap io.trino.spi.block.BufferedMapValueBuilder::build(int, io.trino.spi.block.MapValueBuilder<E>) throws E - - - java.method.returnTypeChangedCovariantly - method <E extends java.lang.Throwable> io.trino.spi.block.Block io.trino.spi.block.MapValueBuilder<E extends java.lang.Throwable>::buildMapValue(io.trino.spi.type.MapType, int, io.trino.spi.block.MapValueBuilder<E>) throws E - method <E extends java.lang.Throwable> io.trino.spi.block.SqlMap io.trino.spi.block.MapValueBuilder<E extends java.lang.Throwable>::buildMapValue(io.trino.spi.type.MapType, int, io.trino.spi.block.MapValueBuilder<E>) throws E - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.type.MapType::getObject(io.trino.spi.block.Block, int) - method io.trino.spi.block.SqlMap io.trino.spi.type.MapType::getObject(io.trino.spi.block.Block, int) - - - java.method.returnTypeChanged - method <E extends java.lang.Throwable> io.trino.spi.block.Block io.trino.spi.block.BufferedMapValueBuilder::build(int, io.trino.spi.block.MapValueBuilder<E>) throws E - method <E extends java.lang.Throwable> io.trino.spi.block.SqlMap io.trino.spi.block.BufferedMapValueBuilder::build(int, io.trino.spi.block.MapValueBuilder<E>) throws E - - - java.method.returnTypeChanged - method <E extends java.lang.Throwable> io.trino.spi.block.Block io.trino.spi.block.MapValueBuilder<E extends java.lang.Throwable>::buildMapValue(io.trino.spi.type.MapType, int, io.trino.spi.block.MapValueBuilder<E>) throws E - method <E extends java.lang.Throwable> io.trino.spi.block.SqlMap io.trino.spi.block.MapValueBuilder<E extends java.lang.Throwable>::buildMapValue(io.trino.spi.type.MapType, int, io.trino.spi.block.MapValueBuilder<E>) throws E - - - java.method.returnTypeChanged - method io.trino.spi.block.Block io.trino.spi.type.MapType::getObject(io.trino.spi.block.Block, int) - method io.trino.spi.block.SqlMap io.trino.spi.type.MapType::getObject(io.trino.spi.block.Block, int) - - - java.class.removed - class io.trino.spi.block.SingleRowBlock - - - java.class.removed - class io.trino.spi.block.SingleRowBlockEncoding - - - java.method.returnTypeChanged - method <E extends java.lang.Throwable> io.trino.spi.block.Block io.trino.spi.block.BufferedRowValueBuilder::build(io.trino.spi.block.RowValueBuilder<E>) throws E - method <E extends java.lang.Throwable> io.trino.spi.block.SqlRow io.trino.spi.block.BufferedRowValueBuilder::build(io.trino.spi.block.RowValueBuilder<E>) throws E - - - java.method.returnTypeChanged - method <E extends java.lang.Throwable> io.trino.spi.block.Block io.trino.spi.block.RowValueBuilder<E extends java.lang.Throwable>::buildRowValue(io.trino.spi.type.RowType, io.trino.spi.block.RowValueBuilder<E>) throws E - method <E extends java.lang.Throwable> io.trino.spi.block.SqlRow io.trino.spi.block.RowValueBuilder<E extends java.lang.Throwable>::buildRowValue(io.trino.spi.type.RowType, io.trino.spi.block.RowValueBuilder<E>) throws E - - - java.method.returnTypeChanged - method io.trino.spi.block.Block io.trino.spi.type.RowType::getObject(io.trino.spi.block.Block, int) - method io.trino.spi.block.SqlRow io.trino.spi.type.RowType::getObject(io.trino.spi.block.Block, int) - - - java.class.removed - class io.trino.spi.block.AbstractVariableWidthBlock - - - java.method.removed - method io.airlift.slice.Slice io.trino.spi.block.VariableWidthBlock::getRawSlice(int) - - - java.method.removed - method boolean io.trino.spi.block.VariableWidthBlock::isEntryNull(int) - - - java.class.noLongerInheritsFromClass - class io.trino.spi.block.VariableWidthBlock - class io.trino.spi.block.VariableWidthBlock - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.AbstractArrayBlock::copyPositions(int[], int, int) @ io.trino.spi.block.ArrayBlock - method io.trino.spi.block.ArrayBlock io.trino.spi.block.ArrayBlock::copyPositions(int[], int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.AbstractArrayBlock::copyRegion(int, int) @ io.trino.spi.block.ArrayBlock - method io.trino.spi.block.ArrayBlock io.trino.spi.block.ArrayBlock::copyRegion(int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.ArrayBlock::copyWithAppendedNull() - method io.trino.spi.block.ArrayBlock io.trino.spi.block.ArrayBlock::copyWithAppendedNull() - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.ArrayBlock::getLoadedBlock() - method io.trino.spi.block.ArrayBlock io.trino.spi.block.ArrayBlock::getLoadedBlock() - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.AbstractArrayBlock::getRegion(int, int) @ io.trino.spi.block.ArrayBlock - method io.trino.spi.block.ArrayBlock io.trino.spi.block.ArrayBlock::getRegion(int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.AbstractArrayBlock::getSingleValueBlock(int) @ io.trino.spi.block.ArrayBlock - method io.trino.spi.block.ArrayBlock io.trino.spi.block.ArrayBlock::getSingleValueBlock(int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.ByteArrayBlock::copyPositions(int[], int, int) - method io.trino.spi.block.ByteArrayBlock io.trino.spi.block.ByteArrayBlock::copyPositions(int[], int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.ByteArrayBlock::copyRegion(int, int) - method io.trino.spi.block.ByteArrayBlock io.trino.spi.block.ByteArrayBlock::copyRegion(int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.ByteArrayBlock::copyWithAppendedNull() - method io.trino.spi.block.ByteArrayBlock io.trino.spi.block.ByteArrayBlock::copyWithAppendedNull() - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.ByteArrayBlock::getRegion(int, int) - method io.trino.spi.block.ByteArrayBlock io.trino.spi.block.ByteArrayBlock::getRegion(int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.ByteArrayBlock::getSingleValueBlock(int) - method io.trino.spi.block.ByteArrayBlock io.trino.spi.block.ByteArrayBlock::getSingleValueBlock(int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.Fixed12Block::copyPositions(int[], int, int) - method io.trino.spi.block.Fixed12Block io.trino.spi.block.Fixed12Block::copyPositions(int[], int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.Fixed12Block::copyRegion(int, int) - method io.trino.spi.block.Fixed12Block io.trino.spi.block.Fixed12Block::copyRegion(int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.Fixed12Block::copyWithAppendedNull() - method io.trino.spi.block.Fixed12Block io.trino.spi.block.Fixed12Block::copyWithAppendedNull() - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.Fixed12Block::getRegion(int, int) - method io.trino.spi.block.Fixed12Block io.trino.spi.block.Fixed12Block::getRegion(int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.Fixed12Block::getSingleValueBlock(int) - method io.trino.spi.block.Fixed12Block io.trino.spi.block.Fixed12Block::getSingleValueBlock(int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.Int128ArrayBlock::copyPositions(int[], int, int) - method io.trino.spi.block.Int128ArrayBlock io.trino.spi.block.Int128ArrayBlock::copyPositions(int[], int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.Int128ArrayBlock::copyRegion(int, int) - method io.trino.spi.block.Int128ArrayBlock io.trino.spi.block.Int128ArrayBlock::copyRegion(int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.Int128ArrayBlock::copyWithAppendedNull() - method io.trino.spi.block.Int128ArrayBlock io.trino.spi.block.Int128ArrayBlock::copyWithAppendedNull() - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.Int128ArrayBlock::getRegion(int, int) - method io.trino.spi.block.Int128ArrayBlock io.trino.spi.block.Int128ArrayBlock::getRegion(int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.Int128ArrayBlock::getSingleValueBlock(int) - method io.trino.spi.block.Int128ArrayBlock io.trino.spi.block.Int128ArrayBlock::getSingleValueBlock(int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.IntArrayBlock::copyPositions(int[], int, int) - method io.trino.spi.block.IntArrayBlock io.trino.spi.block.IntArrayBlock::copyPositions(int[], int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.IntArrayBlock::copyRegion(int, int) - method io.trino.spi.block.IntArrayBlock io.trino.spi.block.IntArrayBlock::copyRegion(int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.IntArrayBlock::copyWithAppendedNull() - method io.trino.spi.block.IntArrayBlock io.trino.spi.block.IntArrayBlock::copyWithAppendedNull() - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.IntArrayBlock::getRegion(int, int) - method io.trino.spi.block.IntArrayBlock io.trino.spi.block.IntArrayBlock::getRegion(int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.IntArrayBlock::getSingleValueBlock(int) - method io.trino.spi.block.IntArrayBlock io.trino.spi.block.IntArrayBlock::getSingleValueBlock(int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.LongArrayBlock::copyPositions(int[], int, int) - method io.trino.spi.block.LongArrayBlock io.trino.spi.block.LongArrayBlock::copyPositions(int[], int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.LongArrayBlock::copyRegion(int, int) - method io.trino.spi.block.LongArrayBlock io.trino.spi.block.LongArrayBlock::copyRegion(int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.LongArrayBlock::copyWithAppendedNull() - method io.trino.spi.block.LongArrayBlock io.trino.spi.block.LongArrayBlock::copyWithAppendedNull() - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.LongArrayBlock::getRegion(int, int) - method io.trino.spi.block.LongArrayBlock io.trino.spi.block.LongArrayBlock::getRegion(int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.LongArrayBlock::getSingleValueBlock(int) - method io.trino.spi.block.LongArrayBlock io.trino.spi.block.LongArrayBlock::getSingleValueBlock(int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.AbstractMapBlock::copyPositions(int[], int, int) @ io.trino.spi.block.MapBlock - method io.trino.spi.block.MapBlock io.trino.spi.block.MapBlock::copyPositions(int[], int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.AbstractMapBlock::copyRegion(int, int) @ io.trino.spi.block.MapBlock - method io.trino.spi.block.MapBlock io.trino.spi.block.MapBlock::copyRegion(int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.MapBlock::copyWithAppendedNull() - method io.trino.spi.block.MapBlock io.trino.spi.block.MapBlock::copyWithAppendedNull() - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.AbstractMapBlock::getRegion(int, int) @ io.trino.spi.block.MapBlock - method io.trino.spi.block.MapBlock io.trino.spi.block.MapBlock::getRegion(int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.AbstractMapBlock::getSingleValueBlock(int) @ io.trino.spi.block.MapBlock - method io.trino.spi.block.MapBlock io.trino.spi.block.MapBlock::getSingleValueBlock(int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.AbstractRowBlock::copyPositions(int[], int, int) @ io.trino.spi.block.RowBlock - method io.trino.spi.block.RowBlock io.trino.spi.block.RowBlock::copyPositions(int[], int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.AbstractRowBlock::copyRegion(int, int) @ io.trino.spi.block.RowBlock - method io.trino.spi.block.RowBlock io.trino.spi.block.RowBlock::copyRegion(int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.RowBlock::copyWithAppendedNull() - method io.trino.spi.block.RowBlock io.trino.spi.block.RowBlock::copyWithAppendedNull() - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.AbstractRowBlock::getRegion(int, int) @ io.trino.spi.block.RowBlock - method io.trino.spi.block.RowBlock io.trino.spi.block.RowBlock::getRegion(int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.AbstractRowBlock::getSingleValueBlock(int) @ io.trino.spi.block.RowBlock - method io.trino.spi.block.RowBlock io.trino.spi.block.RowBlock::getSingleValueBlock(int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.ShortArrayBlock::copyPositions(int[], int, int) - method io.trino.spi.block.ShortArrayBlock io.trino.spi.block.ShortArrayBlock::copyPositions(int[], int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.ShortArrayBlock::copyRegion(int, int) - method io.trino.spi.block.ShortArrayBlock io.trino.spi.block.ShortArrayBlock::copyRegion(int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.ShortArrayBlock::copyWithAppendedNull() - method io.trino.spi.block.ShortArrayBlock io.trino.spi.block.ShortArrayBlock::copyWithAppendedNull() - ADD YOUR EXPLANATION FOR THE NECESSITY OF THIS CHANGE - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.ShortArrayBlock::getRegion(int, int) - method io.trino.spi.block.ShortArrayBlock io.trino.spi.block.ShortArrayBlock::getRegion(int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.ShortArrayBlock::getSingleValueBlock(int) - method io.trino.spi.block.ShortArrayBlock io.trino.spi.block.ShortArrayBlock::getSingleValueBlock(int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.VariableWidthBlock::copyPositions(int[], int, int) - method io.trino.spi.block.VariableWidthBlock io.trino.spi.block.VariableWidthBlock::copyPositions(int[], int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.VariableWidthBlock::copyRegion(int, int) - method io.trino.spi.block.VariableWidthBlock io.trino.spi.block.VariableWidthBlock::copyRegion(int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.VariableWidthBlock::copyWithAppendedNull() - method io.trino.spi.block.VariableWidthBlock io.trino.spi.block.VariableWidthBlock::copyWithAppendedNull() - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.VariableWidthBlock::getRegion(int, int) - method io.trino.spi.block.VariableWidthBlock io.trino.spi.block.VariableWidthBlock::getRegion(int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.AbstractVariableWidthBlock::getSingleValueBlock(int) @ io.trino.spi.block.VariableWidthBlock - method io.trino.spi.block.VariableWidthBlock io.trino.spi.block.VariableWidthBlock::getSingleValueBlock(int) - - - java.method.addedToInterface - method io.trino.spi.block.ValueBlock io.trino.spi.block.Block::getUnderlyingValueBlock() - - - java.method.addedToInterface - method int io.trino.spi.block.Block::getUnderlyingValuePosition(int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.RunLengthEncodedBlock::getSingleValueBlock(int) - method io.trino.spi.block.ValueBlock io.trino.spi.block.RunLengthEncodedBlock::getSingleValueBlock(int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.RunLengthEncodedBlock::getValue() - method io.trino.spi.block.ValueBlock io.trino.spi.block.RunLengthEncodedBlock::getValue() - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.Block::getSingleValueBlock(int) - method io.trino.spi.block.ValueBlock io.trino.spi.block.Block::getSingleValueBlock(int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.DictionaryBlock::getSingleValueBlock(int) - method io.trino.spi.block.ValueBlock io.trino.spi.block.DictionaryBlock::getSingleValueBlock(int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.LazyBlock::getSingleValueBlock(int) - method io.trino.spi.block.ValueBlock io.trino.spi.block.LazyBlock::getSingleValueBlock(int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.DictionaryBlock::getDictionary() - method io.trino.spi.block.ValueBlock io.trino.spi.block.DictionaryBlock::getDictionary() - - - java.method.addedToInterface - method io.trino.spi.block.ValueBlock io.trino.spi.block.BlockBuilder::buildValueBlock() - - - java.method.numberOfParametersChanged - method void io.trino.spi.type.AbstractType::<init>(io.trino.spi.type.TypeSignature, java.lang.Class<?>) - method void io.trino.spi.type.AbstractType::<init>(io.trino.spi.type.TypeSignature, java.lang.Class<?>, java.lang.Class<? extends io.trino.spi.block.ValueBlock>) - - - java.method.numberOfParametersChanged - method void io.trino.spi.type.TimeWithTimeZoneType::<init>(int, java.lang.Class<?>) - method void io.trino.spi.type.TimeWithTimeZoneType::<init>(int, java.lang.Class<?>, java.lang.Class<? extends io.trino.spi.block.ValueBlock>) - - - java.method.addedToInterface - method java.lang.Class<? extends io.trino.spi.block.ValueBlock> io.trino.spi.type.Type::getValueBlockType() - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.type.TypeUtils::writeNativeValue(io.trino.spi.type.Type, java.lang.Object) - method io.trino.spi.block.ValueBlock io.trino.spi.type.TypeUtils::writeNativeValue(io.trino.spi.type.Type, java.lang.Object) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.ArrayBlock::fromElementBlock(int, java.util.Optional<boolean[]>, int[], io.trino.spi.block.Block) - method io.trino.spi.block.ArrayBlock io.trino.spi.block.ArrayBlock::fromElementBlock(int, java.util.Optional<boolean[]>, int[], io.trino.spi.block.Block) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.type.MapType::createBlockFromKeyValue(java.util.Optional<boolean[]>, int[], io.trino.spi.block.Block, io.trino.spi.block.Block) - method io.trino.spi.block.MapBlock io.trino.spi.type.MapType::createBlockFromKeyValue(java.util.Optional<boolean[]>, int[], io.trino.spi.block.Block, io.trino.spi.block.Block) - - - java.method.visibilityIncreased - method int[] io.trino.spi.block.DictionaryBlock::getRawIds() - method int[] io.trino.spi.block.DictionaryBlock::getRawIds() - package - public - - - java.method.visibilityIncreased - method int io.trino.spi.block.DictionaryBlock::getRawIdsOffset() - method int io.trino.spi.block.DictionaryBlock::getRawIdsOffset() - package - public - - - java.method.removed - method int io.trino.spi.block.Block::bytesCompare(int, int, int, io.airlift.slice.Slice, int, int) - - - java.method.removed - method boolean io.trino.spi.block.Block::bytesEqual(int, int, io.airlift.slice.Slice, int, int) - - - java.method.removed - method int io.trino.spi.block.Block::compareTo(int, int, int, io.trino.spi.block.Block, int, int, int) - - - java.method.removed - method boolean io.trino.spi.block.Block::equals(int, int, io.trino.spi.block.Block, int, int, int) - - - java.method.removed - method long io.trino.spi.block.Block::hash(int, int, int) - - - java.method.removed - method int io.trino.spi.block.DictionaryBlock::bytesCompare(int, int, int, io.airlift.slice.Slice, int, int) - - - java.method.removed - method boolean io.trino.spi.block.DictionaryBlock::bytesEqual(int, int, io.airlift.slice.Slice, int, int) - - - java.method.removed - method int io.trino.spi.block.DictionaryBlock::compareTo(int, int, int, io.trino.spi.block.Block, int, int, int) - - - java.method.removed - method boolean io.trino.spi.block.DictionaryBlock::equals(int, int, io.trino.spi.block.Block, int, int, int) - - - java.method.removed - method long io.trino.spi.block.DictionaryBlock::hash(int, int, int) - - - java.method.removed - method int io.trino.spi.block.LazyBlock::bytesCompare(int, int, int, io.airlift.slice.Slice, int, int) - - - java.method.removed - method boolean io.trino.spi.block.LazyBlock::bytesEqual(int, int, io.airlift.slice.Slice, int, int) - - - java.method.removed - method int io.trino.spi.block.LazyBlock::compareTo(int, int, int, io.trino.spi.block.Block, int, int, int) - - - java.method.removed - method boolean io.trino.spi.block.LazyBlock::equals(int, int, io.trino.spi.block.Block, int, int, int) - - - java.method.removed - method long io.trino.spi.block.LazyBlock::hash(int, int, int) - - - java.method.removed - method int io.trino.spi.block.RunLengthEncodedBlock::bytesCompare(int, int, int, io.airlift.slice.Slice, int, int) - - - java.method.removed - method boolean io.trino.spi.block.RunLengthEncodedBlock::bytesEqual(int, int, io.airlift.slice.Slice, int, int) - ADD YOUR EXPLANATION FOR THE NECESSITY OF THIS CHANGE - - - java.method.removed - method int io.trino.spi.block.RunLengthEncodedBlock::compareTo(int, int, int, io.trino.spi.block.Block, int, int, int) - - - java.method.removed - method boolean io.trino.spi.block.RunLengthEncodedBlock::equals(int, int, io.trino.spi.block.Block, int, int, int) - - - java.method.removed - method long io.trino.spi.block.RunLengthEncodedBlock::hash(int, int, int) - - - java.method.removed - method void io.trino.spi.block.Block::writeSliceTo(int, int, int, io.airlift.slice.SliceOutput) - - - java.method.removed - method void io.trino.spi.block.DictionaryBlock::writeSliceTo(int, int, int, io.airlift.slice.SliceOutput) - - - java.method.removed - method void io.trino.spi.block.LazyBlock::writeSliceTo(int, int, int, io.airlift.slice.SliceOutput) - - - java.method.removed - method void io.trino.spi.block.RunLengthEncodedBlock::writeSliceTo(int, int, int, io.airlift.slice.SliceOutput) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.ArrayBlockEncoding::readBlock(io.trino.spi.block.BlockEncodingSerde, io.airlift.slice.SliceInput) - method io.trino.spi.block.ArrayBlock io.trino.spi.block.ArrayBlockEncoding::readBlock(io.trino.spi.block.BlockEncodingSerde, io.airlift.slice.SliceInput) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.ByteArrayBlockEncoding::readBlock(io.trino.spi.block.BlockEncodingSerde, io.airlift.slice.SliceInput) - method io.trino.spi.block.ByteArrayBlock io.trino.spi.block.ByteArrayBlockEncoding::readBlock(io.trino.spi.block.BlockEncodingSerde, io.airlift.slice.SliceInput) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.Fixed12BlockEncoding::readBlock(io.trino.spi.block.BlockEncodingSerde, io.airlift.slice.SliceInput) - method io.trino.spi.block.Fixed12Block io.trino.spi.block.Fixed12BlockEncoding::readBlock(io.trino.spi.block.BlockEncodingSerde, io.airlift.slice.SliceInput) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.Int128ArrayBlockEncoding::readBlock(io.trino.spi.block.BlockEncodingSerde, io.airlift.slice.SliceInput) - method io.trino.spi.block.Int128ArrayBlock io.trino.spi.block.Int128ArrayBlockEncoding::readBlock(io.trino.spi.block.BlockEncodingSerde, io.airlift.slice.SliceInput) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.IntArrayBlockEncoding::readBlock(io.trino.spi.block.BlockEncodingSerde, io.airlift.slice.SliceInput) - method io.trino.spi.block.IntArrayBlock io.trino.spi.block.IntArrayBlockEncoding::readBlock(io.trino.spi.block.BlockEncodingSerde, io.airlift.slice.SliceInput) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.LongArrayBlockEncoding::readBlock(io.trino.spi.block.BlockEncodingSerde, io.airlift.slice.SliceInput) - method io.trino.spi.block.LongArrayBlock io.trino.spi.block.LongArrayBlockEncoding::readBlock(io.trino.spi.block.BlockEncodingSerde, io.airlift.slice.SliceInput) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.ShortArrayBlockEncoding::readBlock(io.trino.spi.block.BlockEncodingSerde, io.airlift.slice.SliceInput) - method io.trino.spi.block.ShortArrayBlock io.trino.spi.block.ShortArrayBlockEncoding::readBlock(io.trino.spi.block.BlockEncodingSerde, io.airlift.slice.SliceInput) - - - java.method.nowStatic - method void io.trino.spi.type.AbstractIntType::checkValueValid(long) - method void io.trino.spi.type.AbstractIntType::checkValueValid(long) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.ArrayBlock::copyPositions(int[], int, int) - method io.trino.spi.block.ArrayBlock io.trino.spi.block.ArrayBlock::copyPositions(int[], int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.ArrayBlock::copyRegion(int, int) - method io.trino.spi.block.ArrayBlock io.trino.spi.block.ArrayBlock::copyRegion(int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.ArrayBlock::getRegion(int, int) - method io.trino.spi.block.ArrayBlock io.trino.spi.block.ArrayBlock::getRegion(int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.ArrayBlock::getSingleValueBlock(int) - method io.trino.spi.block.ArrayBlock io.trino.spi.block.ArrayBlock::getSingleValueBlock(int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.MapBlock::copyPositions(int[], int, int) - method io.trino.spi.block.MapBlock io.trino.spi.block.MapBlock::copyPositions(int[], int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.MapBlock::copyRegion(int, int) - method io.trino.spi.block.MapBlock io.trino.spi.block.MapBlock::copyRegion(int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.MapBlock::getRegion(int, int) - method io.trino.spi.block.MapBlock io.trino.spi.block.MapBlock::getRegion(int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.MapBlock::getSingleValueBlock(int) - method io.trino.spi.block.MapBlock io.trino.spi.block.MapBlock::getSingleValueBlock(int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.RowBlock::copyPositions(int[], int, int) - method io.trino.spi.block.RowBlock io.trino.spi.block.RowBlock::copyPositions(int[], int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.RowBlock::copyRegion(int, int) - method io.trino.spi.block.RowBlock io.trino.spi.block.RowBlock::copyRegion(int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.RowBlock::getRegion(int, int) - method io.trino.spi.block.RowBlock io.trino.spi.block.RowBlock::getRegion(int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.RowBlock::getSingleValueBlock(int) - method io.trino.spi.block.RowBlock io.trino.spi.block.RowBlock::getSingleValueBlock(int) - - - java.method.removed - method int io.trino.spi.block.VariableWidthBlock::bytesCompare(int, int, int, io.airlift.slice.Slice, int, int) - - - java.method.removed - method boolean io.trino.spi.block.VariableWidthBlock::bytesEqual(int, int, io.airlift.slice.Slice, int, int) - - - java.method.removed - method int io.trino.spi.block.VariableWidthBlock::compareTo(int, int, int, io.trino.spi.block.Block, int, int, int) - - - java.method.removed - method boolean io.trino.spi.block.VariableWidthBlock::equals(int, int, io.trino.spi.block.Block, int, int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.Block io.trino.spi.block.VariableWidthBlock::getSingleValueBlock(int) - method io.trino.spi.block.VariableWidthBlock io.trino.spi.block.VariableWidthBlock::getSingleValueBlock(int) - - - java.method.removed - method long io.trino.spi.block.VariableWidthBlock::hash(int, int, int) - - - java.method.removed - method void io.trino.spi.block.VariableWidthBlock::writeSliceTo(int, int, int, io.airlift.slice.SliceOutput) - - - java.class.nowFinal - class io.trino.spi.block.ArrayBlock - class io.trino.spi.block.ArrayBlock - - - java.class.nowFinal - class io.trino.spi.block.ByteArrayBlock - class io.trino.spi.block.ByteArrayBlock - - - java.class.nowFinal - class io.trino.spi.block.DictionaryBlock - class io.trino.spi.block.DictionaryBlock - - - java.class.nowFinal - class io.trino.spi.block.Fixed12Block - class io.trino.spi.block.Fixed12Block - - - java.class.nowFinal - class io.trino.spi.block.Int128ArrayBlock - class io.trino.spi.block.Int128ArrayBlock - - - java.class.nowFinal - class io.trino.spi.block.IntArrayBlock - class io.trino.spi.block.IntArrayBlock - - - java.class.nowFinal - class io.trino.spi.block.LazyBlock - class io.trino.spi.block.LazyBlock - - - java.class.nowFinal - class io.trino.spi.block.LongArrayBlock - class io.trino.spi.block.LongArrayBlock - - - java.class.nowFinal - class io.trino.spi.block.MapBlock - class io.trino.spi.block.MapBlock - - - java.class.nowFinal - class io.trino.spi.block.RowBlock - class io.trino.spi.block.RowBlock - - - java.class.nowFinal - class io.trino.spi.block.RunLengthEncodedBlock - class io.trino.spi.block.RunLengthEncodedBlock - - - java.class.nowFinal - class io.trino.spi.block.ShortArrayBlock - class io.trino.spi.block.ShortArrayBlock - - - java.class.nowFinal - class io.trino.spi.block.VariableWidthBlock - class io.trino.spi.block.VariableWidthBlock - - - java.method.visibilityReduced - method int io.trino.spi.block.ArrayBlock::getOffsetBase() - method int io.trino.spi.block.ArrayBlock::getOffsetBase() - protected - package - - - java.method.visibilityReduced - method int[] io.trino.spi.block.ArrayBlock::getOffsets() - method int[] io.trino.spi.block.ArrayBlock::getOffsets() - protected - package - - - java.method.visibilityReduced - method io.trino.spi.block.Block io.trino.spi.block.ArrayBlock::getRawElementBlock() - method io.trino.spi.block.Block io.trino.spi.block.ArrayBlock::getRawElementBlock() - protected - package - - - java.method.visibilityReduced - method void io.trino.spi.block.MapBlock::ensureHashTableLoaded() - method void io.trino.spi.block.MapBlock::ensureHashTableLoaded() - protected - package - - - java.method.visibilityReduced - method io.trino.spi.block.MapHashTables io.trino.spi.block.MapBlock::getHashTables() - method io.trino.spi.block.MapHashTables io.trino.spi.block.MapBlock::getHashTables() - protected - package - - - java.method.visibilityReduced - method io.trino.spi.type.MapType io.trino.spi.block.MapBlock::getMapType() - method io.trino.spi.type.MapType io.trino.spi.block.MapBlock::getMapType() - protected - package - - - java.method.visibilityReduced - method int io.trino.spi.block.MapBlock::getOffsetBase() - method int io.trino.spi.block.MapBlock::getOffsetBase() - protected - package - - - java.method.visibilityReduced - method int[] io.trino.spi.block.MapBlock::getOffsets() - method int[] io.trino.spi.block.MapBlock::getOffsets() - protected - package - - - java.method.visibilityReduced - method io.trino.spi.block.Block io.trino.spi.block.MapBlock::getRawKeyBlock() - method io.trino.spi.block.Block io.trino.spi.block.MapBlock::getRawKeyBlock() - protected - package - - - java.method.visibilityReduced - method io.trino.spi.block.Block io.trino.spi.block.MapBlock::getRawValueBlock() - method io.trino.spi.block.Block io.trino.spi.block.MapBlock::getRawValueBlock() - protected - package - - - java.method.visibilityReduced - method int[] io.trino.spi.block.RowBlock::getFieldBlockOffsets() - method int[] io.trino.spi.block.RowBlock::getFieldBlockOffsets() - protected - package - - - java.method.visibilityReduced - method int io.trino.spi.block.RowBlock::getOffsetBase() - method int io.trino.spi.block.RowBlock::getOffsetBase() - protected - package - - - java.method.visibilityReduced - method java.util.List<io.trino.spi.block.Block> io.trino.spi.block.RowBlock::getRawFieldBlocks() - method java.util.List<io.trino.spi.block.Block> io.trino.spi.block.RowBlock::getRawFieldBlocks() - protected - package - - - java.method.visibilityReduced - method int io.trino.spi.block.VariableWidthBlock::getPositionOffset(int) - method int io.trino.spi.block.VariableWidthBlock::getPositionOffset(int) - protected - package - - - java.class.removed - class io.trino.spi.block.ColumnarRow - - - java.method.numberOfParametersChanged - method io.trino.spi.block.RowBlock io.trino.spi.block.RowBlock::fromFieldBlocks(int, java.util.Optional<boolean[]>, io.trino.spi.block.Block[]) - method io.trino.spi.block.RowBlock io.trino.spi.block.RowBlock::fromFieldBlocks(int, io.trino.spi.block.Block[]) - - - java.method.removed - method int[] io.trino.spi.block.RowBlock::getFieldBlockOffsets() - - - java.method.removed - method int io.trino.spi.block.RowBlock::getOffsetBase() - - - java.method.removed - method int io.trino.spi.block.RowBlock::getFieldBlockOffset(int) - - - java.method.returnTypeChanged - method java.util.List<io.trino.spi.block.Block> io.trino.spi.block.RowBlock::getRawFieldBlocks() - method io.trino.spi.block.Block[] io.trino.spi.block.RowBlock::getRawFieldBlocks() - - - java.method.visibilityReduced - method java.util.List<io.trino.spi.block.Block> io.trino.spi.block.RowBlock::getRawFieldBlocks() - method io.trino.spi.block.Block[] io.trino.spi.block.RowBlock::getRawFieldBlocks() - protected - package - - - java.method.addedToInterface - method java.lang.String io.trino.spi.security.SystemAccessControlFactory.SystemAccessControlContext::getVersion() - From 2874776293c34d4545c200a29330843b13fa3bbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Osipiuk?= Date: Tue, 14 Nov 2023 13:37:27 +0100 Subject: [PATCH 238/587] Do not require all sinks to be finished in FileSystemExchange It is not enforced by engine that each created sink will be finished. Some tasks may be abandoned as not required for computing query result and siks for those tasks will not be finished. --- .../io/trino/plugin/exchange/filesystem/FileSystemExchange.java | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin/trino-exchange-filesystem/src/main/java/io/trino/plugin/exchange/filesystem/FileSystemExchange.java b/plugin/trino-exchange-filesystem/src/main/java/io/trino/plugin/exchange/filesystem/FileSystemExchange.java index 77eeeb251d64..f5c386fafab3 100644 --- a/plugin/trino-exchange-filesystem/src/main/java/io/trino/plugin/exchange/filesystem/FileSystemExchange.java +++ b/plugin/trino-exchange-filesystem/src/main/java/io/trino/plugin/exchange/filesystem/FileSystemExchange.java @@ -183,7 +183,6 @@ public void allRequiredSinksFinished() return; } verify(noMoreSinks, "noMoreSinks is expected to be set"); - verify(finishedSinks.keySet().containsAll(allSinks), "all sinks are expected to be finished"); // input is ready, create exchange source handles exchangeSourceHandlesCreationStarted = true; exchangeSourceHandlesCreationFuture = stats.getCreateExchangeSourceHandles().record(this::createExchangeSourceHandles); From 48c756a5dbc6463fb612ac9b0c374e534f095a39 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Sat, 11 Nov 2023 16:30:46 -0800 Subject: [PATCH 239/587] Remove unnecessary data provider --- .../trino/hdfs/TestFSDataInputStreamTail.java | 54 +- .../io/trino/hdfs/rubix/TestRubixCaching.java | 223 ++--- .../bigquery/BaseBigQueryConnectorTest.java | 67 +- .../TestBigQueryAvroConnectorTest.java | 42 +- .../TestClickHouseConnectorTest.java | 12 +- .../deltalake/TestDeltaLakeConnectorTest.java | 760 ++++++++++------- .../plugin/hive/BaseHiveConnectorTest.java | 456 ++++++----- .../iceberg/BaseIcebergConnectorTest.java | 768 +++++++++--------- .../ignite/TestIgniteConnectorTest.java | 6 +- .../plugin/kafka/TestKafkaConnectorTest.java | 169 ++-- .../plugin/kudu/TestKuduConnectorTest.java | 19 +- .../mariadb/TestMariaDbConnectorTest.java | 8 +- .../memory/TestMemoryConnectorTest.java | 262 +++--- .../mongodb/TestMongoConnectorTest.java | 345 ++++---- .../plugin/mysql/BaseMySqlConnectorTest.java | 27 +- .../mysql/TestMySqlLegacyConnectorTest.java | 10 +- .../phoenix5/TestPhoenixConnectorTest.java | 41 +- .../TestPostgreSqlConnectorTest.java | 133 +-- .../redshift/TestRedshiftConnectorTest.java | 142 ++-- .../sqlserver/BaseSqlServerConnectorTest.java | 22 +- .../sqlserver/TestSqlServerConnectorTest.java | 154 ++-- ...veFaultTolerantExecutionConnectorTest.java | 2 +- .../io/trino/testing/BaseConnectorTest.java | 364 +++++---- .../execution/TestEventListenerBasic.java | 28 +- .../io/trino/execution/TestRevokeOnTable.java | 104 ++- .../sql/planner/BaseCostBasedPlanTest.java | 22 +- ...TestHivePartitionedTpcdsCostBasedPlan.java | 6 +- .../TestHivePartitionedTpchCostBasedPlan.java | 6 +- .../planner/TestHiveTpcdsCostBasedPlan.java | 6 +- .../planner/TestHiveTpchCostBasedPlan.java | 6 +- ...ebergOrcPartitionedTpcdsCostBasedPlan.java | 6 +- ...cebergOrcPartitionedTpchCostBasedPlan.java | 6 +- .../TestIcebergOrcTpcdsCostBasedPlan.java | 6 +- .../TestIcebergOrcTpchCostBasedPlan.java | 6 +- ...gParquetPartitionedTpcdsCostBasedPlan.java | 6 +- ...rgParquetPartitionedTpchCostBasedPlan.java | 6 +- .../TestIcebergParquetTpcdsCostBasedPlan.java | 6 +- .../TestIcebergParquetTpchCostBasedPlan.java | 6 +- ...rgSmallFilesParquetTpcdsCostBasedPlan.java | 6 +- 39 files changed, 2326 insertions(+), 1992 deletions(-) diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFSDataInputStreamTail.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFSDataInputStreamTail.java index 2a509e778f8d..bb39a1189260 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFSDataInputStreamTail.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFSDataInputStreamTail.java @@ -24,7 +24,6 @@ import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.io.File; @@ -85,8 +84,22 @@ public void testEmptyFileReadTail() } } - @Test(dataProvider = "validFileSizeAndPaddedFileSize") - public void testReadTailForFileSize(int fileSize, int paddedFileSize) + @Test + public void testReadTailForFileSize() + throws Exception + { + testReadTailForFileSize(0, 0); + testReadTailForFileSize(0, 1); + testReadTailForFileSize(0, 15); + testReadTailForFileSize(0, FSDataInputStreamTail.MAX_SUPPORTED_PADDING_BYTES - 1); + testReadTailForFileSize(0, FSDataInputStreamTail.MAX_SUPPORTED_PADDING_BYTES); + testReadTailForFileSize(63, 63); + testReadTailForFileSize(63, 64); + testReadTailForFileSize(64, 74); + testReadTailForFileSize(65, 65 + FSDataInputStreamTail.MAX_SUPPORTED_PADDING_BYTES); + } + + private void testReadTailForFileSize(int fileSize, int paddedFileSize) throws Exception { // Cleanup between each input run @@ -105,10 +118,26 @@ public void testReadTailForFileSize(int fileSize, int paddedFileSize) } } - @Test(dataProvider = "validFileSizeAndPaddedFileSize") - public void testReadTailCompletely(int fileSize, int paddedFileSize) + @Test + public void testReadTailCompletely() throws Exception { + testReadTailCompletely(0, 0); + testReadTailCompletely(0, 1); + testReadTailCompletely(0, 15); + testReadTailCompletely(0, FSDataInputStreamTail.MAX_SUPPORTED_PADDING_BYTES - 1); + testReadTailCompletely(0, FSDataInputStreamTail.MAX_SUPPORTED_PADDING_BYTES); + testReadTailCompletely(63, 63); + testReadTailCompletely(63, 64); + testReadTailCompletely(64, 74); + testReadTailCompletely(65, 65 + FSDataInputStreamTail.MAX_SUPPORTED_PADDING_BYTES); + } + + private void testReadTailCompletely(int fileSize, int paddedFileSize) + throws Exception + { + fs.truncate(tempFile, 0); + byte[] contents = countingTestFileContentsWithLength(fileSize); if (contents.length > 0) { try (FSDataOutputStream os = fs.append(tempFile)) { @@ -184,21 +213,6 @@ public void testReadTailForFileSizeNoEndOfFileFound() } } - @DataProvider(name = "validFileSizeAndPaddedFileSize") - public static Object[][] validFileSizeAndPaddedFileSize() - { - return new Object[][] { - {0, 0}, - {0, 1}, - {0, 15}, - {0, FSDataInputStreamTail.MAX_SUPPORTED_PADDING_BYTES - 1}, - {0, FSDataInputStreamTail.MAX_SUPPORTED_PADDING_BYTES}, - {63, 63}, - {63, 64}, - {64, 74}, - {65, 65 + FSDataInputStreamTail.MAX_SUPPORTED_PADDING_BYTES}}; - } - private static void assertCountingTestFileContents(byte[] contents) { assertEquals(contents, countingTestFileContentsWithLength(contents.length)); diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/rubix/TestRubixCaching.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/rubix/TestRubixCaching.java index b6aff0df6f9a..870acf1adf5c 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/rubix/TestRubixCaching.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/rubix/TestRubixCaching.java @@ -53,7 +53,6 @@ import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import javax.management.MBeanServer; @@ -276,12 +275,6 @@ private static void closeFileSystem(FileSystem fileSystem) fileSystem.close(); } - @DataProvider - public static Object[][] readMode() - { - return new Object[][] {{ASYNC}, {READ_THROUGH}}; - } - @Test public void testCoordinatorNotJoining() { @@ -340,135 +333,153 @@ public void testGetBlockLocations() assertEquals(file2Locations[0].getHosts()[0], "127.0.0.2"); } - @Test(dataProvider = "readMode") - public void testCacheRead(ReadMode readMode) + @Test + public void testCacheRead() throws Exception { - RubixConfig rubixConfig = new RubixConfig().setReadMode(readMode); - initializeCachingFileSystem(rubixConfig); - byte[] randomData = new byte[toIntExact(SMALL_FILE_SIZE.toBytes())]; - new Random().nextBytes(randomData); + for (ReadMode readMode : ReadMode.values()) { + deinitializeRubix(); + + RubixConfig rubixConfig = new RubixConfig().setReadMode(readMode); + initializeCachingFileSystem(rubixConfig); + byte[] randomData = new byte[toIntExact(SMALL_FILE_SIZE.toBytes())]; + new Random().nextBytes(randomData); - Path file = getStoragePath("some_file"); - writeFile(nonCachingFileSystem.create(file), randomData); + Path file = getStoragePath("some_file"); + writeFile(nonCachingFileSystem.create(file), randomData); - long beforeRemoteReadsCount = getRemoteReadsCount(); - long beforeCachedReadsCount = getCachedReadsCount(); - long beforeAsyncDownloadedMb = getAsyncDownloadedMb(readMode); + long beforeRemoteReadsCount = getRemoteReadsCount(); + long beforeCachedReadsCount = getCachedReadsCount(); + long beforeAsyncDownloadedMb = getAsyncDownloadedMb(readMode); - assertFileContents(cachingFileSystem, file, randomData); + assertFileContents(cachingFileSystem, file, randomData); - if (readMode == ASYNC) { - // wait for async Rubix requests to complete + if (readMode == ASYNC) { + // wait for async Rubix requests to complete + assertEventually( + new Duration(10, SECONDS), + () -> assertEquals(getAsyncDownloadedMb(ASYNC), beforeAsyncDownloadedMb + 1)); + } + + // stats are propagated asynchronously assertEventually( new Duration(10, SECONDS), - () -> assertEquals(getAsyncDownloadedMb(ASYNC), beforeAsyncDownloadedMb + 1)); - } + () -> { + // data should be read from remote source only + assertGreaterThan(getRemoteReadsCount(), beforeRemoteReadsCount); + assertEquals(getCachedReadsCount(), beforeCachedReadsCount); + }); - // stats are propagated asynchronously - assertEventually( - new Duration(10, SECONDS), - () -> { - // data should be read from remote source only - assertGreaterThan(getRemoteReadsCount(), beforeRemoteReadsCount); - assertEquals(getCachedReadsCount(), beforeCachedReadsCount); - }); - - // ensure that subsequent read uses cache exclusively - assertEventually( - new Duration(10, SECONDS), - () -> { - long remoteReadsCount = getRemoteReadsCount(); - assertFileContents(cachingFileSystem, file, randomData); - assertGreaterThan(getCachedReadsCount(), beforeCachedReadsCount); - assertEquals(getRemoteReadsCount(), remoteReadsCount); - }); + // ensure that subsequent read uses cache exclusively + assertEventually( + new Duration(10, SECONDS), + () -> { + long remoteReadsCount = getRemoteReadsCount(); + assertFileContents(cachingFileSystem, file, randomData); + assertGreaterThan(getCachedReadsCount(), beforeCachedReadsCount); + assertEquals(getRemoteReadsCount(), remoteReadsCount); + }); + + closeRubix(); + } } - @Test(dataProvider = "readMode") - public void testCacheWrite(ReadMode readMode) + @Test + public void testCacheWrite() throws Exception { - initializeCachingFileSystem(new RubixConfig().setReadMode(readMode)); - Path file = getStoragePath("some_file_write"); + for (ReadMode readMode : ReadMode.values()) { + deinitializeRubix(); + + initializeCachingFileSystem(new RubixConfig().setReadMode(readMode)); + Path file = getStoragePath("some_file_write"); - byte[] data = "Hello world".getBytes(UTF_8); - writeFile(cachingFileSystem.create(file), data); - assertFileContents(cachingFileSystem, file, data); + byte[] data = "Hello world".getBytes(UTF_8); + writeFile(cachingFileSystem.create(file), data); + assertFileContents(cachingFileSystem, file, data); + + closeRubix(); + } } - @Test(dataProvider = "readMode") - public void testLargeFile(ReadMode readMode) + @Test + public void testLargeFile() throws Exception { - initializeCachingFileSystem(new RubixConfig().setReadMode(readMode)); - byte[] randomData = new byte[toIntExact(LARGE_FILE_SIZE.toBytes())]; - new Random().nextBytes(randomData); + for (ReadMode readMode : ReadMode.values()) { + deinitializeRubix(); + + initializeCachingFileSystem(new RubixConfig().setReadMode(readMode)); + byte[] randomData = new byte[toIntExact(LARGE_FILE_SIZE.toBytes())]; + new Random().nextBytes(randomData); - Path file = getStoragePath("large_file"); - writeFile(nonCachingFileSystem.create(file), randomData); + Path file = getStoragePath("large_file"); + writeFile(nonCachingFileSystem.create(file), randomData); - long beforeRemoteReadsCount = getRemoteReadsCount(); - long beforeCachedReadsCount = getCachedReadsCount(); - long beforeAsyncDownloadedMb = getAsyncDownloadedMb(readMode); + long beforeRemoteReadsCount = getRemoteReadsCount(); + long beforeCachedReadsCount = getCachedReadsCount(); + long beforeAsyncDownloadedMb = getAsyncDownloadedMb(readMode); - assertFileContents(cachingFileSystem, file, randomData); + assertFileContents(cachingFileSystem, file, randomData); - if (readMode == ASYNC) { - // wait for async Rubix requests to complete + if (readMode == ASYNC) { + // wait for async Rubix requests to complete + assertEventually( + new Duration(10, SECONDS), + () -> assertEquals(getAsyncDownloadedMb(ASYNC), beforeAsyncDownloadedMb + 100)); + } + + // stats are propagated asynchronously assertEventually( new Duration(10, SECONDS), - () -> assertEquals(getAsyncDownloadedMb(ASYNC), beforeAsyncDownloadedMb + 100)); - } + () -> { + // data should be fetched from remote source + assertGreaterThan(getRemoteReadsCount(), beforeRemoteReadsCount); + }); - // stats are propagated asynchronously - assertEventually( - new Duration(10, SECONDS), - () -> { - // data should be fetched from remote source - assertGreaterThan(getRemoteReadsCount(), beforeRemoteReadsCount); - }); - - // ensure that subsequent read uses cache exclusively - assertEventually( - new Duration(10, SECONDS), - () -> { - long remoteReadsCount = getRemoteReadsCount(); - assertFileContents(cachingFileSystem, file, randomData); - assertGreaterThan(getCachedReadsCount(), beforeCachedReadsCount); - assertEquals(getRemoteReadsCount(), remoteReadsCount); - }); - long secondCachedReadsCount = getCachedReadsCount(); - long secondRemoteReadsCount = getRemoteReadsCount(); - - // make sure parallel reading of large file works - ExecutorService executorService = newFixedThreadPool(3); - try { - List> reads = nCopies( - 3, + // ensure that subsequent read uses cache exclusively + assertEventually( + new Duration(10, SECONDS), () -> { + long remoteReadsCount = getRemoteReadsCount(); assertFileContents(cachingFileSystem, file, randomData); - return null; + assertGreaterThan(getCachedReadsCount(), beforeCachedReadsCount); + assertEquals(getRemoteReadsCount(), remoteReadsCount); }); - List> futures = reads.stream() - .map(executorService::submit) - .collect(toImmutableList()); - for (Future future : futures) { - future.get(); + long secondCachedReadsCount = getCachedReadsCount(); + long secondRemoteReadsCount = getRemoteReadsCount(); + + // make sure parallel reading of large file works + ExecutorService executorService = newFixedThreadPool(3); + try { + List> reads = nCopies( + 3, + () -> { + assertFileContents(cachingFileSystem, file, randomData); + return null; + }); + List> futures = reads.stream() + .map(executorService::submit) + .collect(toImmutableList()); + for (Future future : futures) { + future.get(); + } } - } - finally { - executorService.shutdownNow(); - } + finally { + executorService.shutdownNow(); + } + + // stats are propagated asynchronously + assertEventually( + new Duration(10, SECONDS), + () -> { + // data should be read from cache only + assertGreaterThan(getCachedReadsCount(), secondCachedReadsCount); + assertEquals(getRemoteReadsCount(), secondRemoteReadsCount); + }); - // stats are propagated asynchronously - assertEventually( - new Duration(10, SECONDS), - () -> { - // data should be read from cache only - assertGreaterThan(getCachedReadsCount(), secondCachedReadsCount); - assertEquals(getRemoteReadsCount(), secondRemoteReadsCount); - }); + closeRubix(); + } } @SuppressModernizer diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java index f6e3f9d0a224..57edeefb4d5d 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java @@ -24,7 +24,6 @@ import org.intellij.lang.annotations.Language; import org.testng.SkipException; import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; import org.testng.annotations.Parameters; import org.testng.annotations.Test; @@ -110,8 +109,29 @@ protected MaterializedResult getDescribeOrdersResult() .build(); } - @Test(dataProvider = "createTableSupportedTypes") - public void testCreateTableSupportedType(String createType, String expectedType) + @Test + public void testCreateTableSupportedType() + { + testCreateTableSupportedType("boolean", "boolean"); + testCreateTableSupportedType("tinyint", "bigint"); + testCreateTableSupportedType("smallint", "bigint"); + testCreateTableSupportedType("integer", "bigint"); + testCreateTableSupportedType("bigint", "bigint"); + testCreateTableSupportedType("double", "double"); + testCreateTableSupportedType("decimal", "decimal(38,9)"); + testCreateTableSupportedType("date", "date"); + testCreateTableSupportedType("time with time zone", "time(6)"); + testCreateTableSupportedType("timestamp(6)", "timestamp(6)"); + testCreateTableSupportedType("timestamp(6) with time zone", "timestamp(6) with time zone"); + testCreateTableSupportedType("varchar", "varchar"); + testCreateTableSupportedType("varchar(65535)", "varchar"); + testCreateTableSupportedType("varbinary", "varbinary"); + testCreateTableSupportedType("array(bigint)", "array(bigint)"); + testCreateTableSupportedType("row(x bigint, y double)", "row(x bigint, y double)"); + testCreateTableSupportedType("row(x array(bigint))", "row(x array(bigint))"); + } + + private void testCreateTableSupportedType(String createType, String expectedType) { try (TestTable table = new TestTable(getQueryRunner()::execute, "test_create_table_supported_type_" + createType.replaceAll("[^a-zA-Z0-9]", ""), format("(col1 %s)", createType))) { assertEquals( @@ -120,48 +140,21 @@ public void testCreateTableSupportedType(String createType, String expectedType) } } - @DataProvider - public Object[][] createTableSupportedTypes() - { - return new Object[][] { - {"boolean", "boolean"}, - {"tinyint", "bigint"}, - {"smallint", "bigint"}, - {"integer", "bigint"}, - {"bigint", "bigint"}, - {"double", "double"}, - {"decimal", "decimal(38,9)"}, - {"date", "date"}, - {"time with time zone", "time(6)"}, - {"timestamp(6)", "timestamp(6)"}, - {"timestamp(6) with time zone", "timestamp(6) with time zone"}, - {"varchar", "varchar"}, - {"varchar(65535)", "varchar"}, - {"varbinary", "varbinary"}, - {"array(bigint)", "array(bigint)"}, - {"row(x bigint, y double)", "row(x bigint, y double)"}, - {"row(x array(bigint))", "row(x array(bigint))"}, - }; + @Test + public void testCreateTableUnsupportedType() + { + testCreateTableUnsupportedType("json"); + testCreateTableUnsupportedType("uuid"); + testCreateTableUnsupportedType("ipaddress"); } - @Test(dataProvider = "createTableUnsupportedTypes") - public void testCreateTableUnsupportedType(String createType) + private void testCreateTableUnsupportedType(String createType) { String tableName = format("test_create_table_unsupported_type_%s_%s", createType.replaceAll("[^a-zA-Z0-9]", ""), randomNameSuffix()); assertQueryFails(format("CREATE TABLE %s (col1 %s)", tableName, createType), "Unsupported column type: " + createType); assertUpdate("DROP TABLE IF EXISTS " + tableName); } - @DataProvider - public Object[][] createTableUnsupportedTypes() - { - return new Object[][] { - {"json"}, - {"uuid"}, - {"ipaddress"}, - }; - } - @Test public void testCreateTableWithRowTypeWithoutField() { diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryAvroConnectorTest.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryAvroConnectorTest.java index bb4566271ab9..556be67bb39f 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryAvroConnectorTest.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryAvroConnectorTest.java @@ -16,13 +16,11 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import io.trino.testing.QueryRunner; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.util.Optional; import java.util.Set; -import static io.trino.testing.DataProviders.toDataProvider; import static io.trino.testing.TestingNames.randomNameSuffix; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -60,30 +58,26 @@ protected Optional filterColumnNameTestData(String columnName) } // TODO: Disable all operations for unsupported column names - @Test(dataProvider = "unsupportedColumnNameDataProvider") - public void testSelectFailsForColumnName(String columnName) + @Test + public void testSelectFailsForColumnName() { - String tableName = "test.test_unsupported_column_name" + randomNameSuffix(); + for (String columnName : UNSUPPORTED_COLUMN_NAMES) { + String tableName = "test.test_unsupported_column_name" + randomNameSuffix(); - assertUpdate("CREATE TABLE " + tableName + "(\"" + columnName + "\" varchar(50))"); - try { - assertUpdate("INSERT INTO " + tableName + " VALUES ('test value')", 1); - // The storage API can't read the table, but query based API can read it - assertThatThrownBy(() -> query("SELECT * FROM " + tableName)) - .cause() - .hasMessageMatching(".*(Illegal initial character|Invalid name).*"); - assertThat(bigQuerySqlExecutor.executeQuery("SELECT * FROM " + tableName).getValues()) - .extracting(field -> field.get(0).getStringValue()) - .containsExactly("test value"); + assertUpdate("CREATE TABLE " + tableName + "(\"" + columnName + "\" varchar(50))"); + try { + assertUpdate("INSERT INTO " + tableName + " VALUES ('test value')", 1); + // The storage API can't read the table, but query based API can read it + assertThatThrownBy(() -> query("SELECT * FROM " + tableName)) + .cause() + .hasMessageMatching(".*(Illegal initial character|Invalid name).*"); + assertThat(bigQuerySqlExecutor.executeQuery("SELECT * FROM " + tableName).getValues()) + .extracting(field -> field.get(0).getStringValue()) + .containsExactly("test value"); + } + finally { + assertUpdate("DROP TABLE " + tableName); + } } - finally { - assertUpdate("DROP TABLE " + tableName); - } - } - - @DataProvider - public Object[][] unsupportedColumnNameDataProvider() - { - return UNSUPPORTED_COLUMN_NAMES.stream().collect(toDataProvider()); } } diff --git a/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/TestClickHouseConnectorTest.java b/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/TestClickHouseConnectorTest.java index fe7d59daeda6..68fa84b6c2e3 100644 --- a/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/TestClickHouseConnectorTest.java +++ b/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/TestClickHouseConnectorTest.java @@ -154,18 +154,10 @@ protected String createTableSqlForAddingAndDroppingColumn(String tableName, Stri return format("CREATE TABLE %s(%s varchar(50), value varchar(50) NOT NULL) WITH (engine = 'MergeTree', order_by = ARRAY['value'])", tableName, columnNameInSql); } + @Test(enabled = false) @Override - public void testRenameColumnName(String columnName) + public void testRenameColumnName() { - // TODO: Enable this test - if (columnName.equals("a.dot")) { - assertThatThrownBy(() -> super.testRenameColumnName(columnName)) - .hasMessageContaining("Cannot rename column from nested struct to normal column"); - throw new SkipException("TODO"); - } - assertThatThrownBy(() -> super.testRenameColumnName(columnName)) - .hasMessageContaining("is not supported by storage Log"); - throw new SkipException("TODO"); } @Override diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConnectorTest.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConnectorTest.java index d07862db4413..1f5d7c5df5e7 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConnectorTest.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConnectorTest.java @@ -27,7 +27,6 @@ import io.trino.sql.planner.plan.TableFinishNode; import io.trino.sql.planner.plan.TableWriterNode; import io.trino.testing.BaseConnectorTest; -import io.trino.testing.DataProviders; import io.trino.testing.DistributedQueryRunner; import io.trino.testing.MaterializedResult; import io.trino.testing.MaterializedResultWithQueryId; @@ -41,12 +40,10 @@ import org.intellij.lang.annotations.Language; import org.testng.SkipException; import org.testng.annotations.AfterClass; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.nio.file.Path; import java.time.ZonedDateTime; -import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Optional; @@ -68,9 +65,6 @@ import static io.trino.plugin.tpch.TpchMetadata.TINY_SCHEMA_NAME; import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.sql.planner.optimizations.PlanNodeSearcher.searchFrom; -import static io.trino.testing.DataProviders.cartesianProduct; -import static io.trino.testing.DataProviders.toDataProvider; -import static io.trino.testing.DataProviders.trueFalse; import static io.trino.testing.MaterializedResult.resultBuilder; import static io.trino.testing.QueryAssertions.copyTpchTables; import static io.trino.testing.TestingAccessControlManager.TestingPrivilegeType.EXECUTE_FUNCTION; @@ -398,12 +392,14 @@ public void testDropColumn() } @Override - public void testAddAndDropColumnName(String columnName) + public void testAddAndDropColumnName() { - // Override because the connector doesn't support dropping columns with 'none' column mapping - // There are some tests in in io.trino.tests.product.deltalake.TestDeltaLakeColumnMappingMode - assertThatThrownBy(() -> super.testAddAndDropColumnName(columnName)) - .hasMessageContaining("Cannot drop column from table using column mapping mode NONE"); + for (String columnName : testColumnNameDataProvider()) { + // Override because the connector doesn't support dropping columns with 'none' column mapping + // There are some tests in in io.trino.tests.product.deltalake.TestDeltaLakeColumnMappingMode + assertThatThrownBy(() -> testAddAndDropColumnName(columnName, requiresDelimiting(columnName))) + .hasMessageContaining("Cannot drop column from table using column mapping mode NONE"); + } } @Override @@ -415,13 +411,15 @@ public void testDropAndAddColumnWithSameName() .hasMessageContaining("Cannot drop column from table using column mapping mode NONE"); } - @Test(dataProvider = "columnMappingModeDataProvider") - public void testDropPartitionColumn(ColumnMappingMode mode) + @Test + public void testDropPartitionColumn() { - if (mode == ColumnMappingMode.NONE) { - throw new SkipException("Tested in testDropColumn"); - } + testDropPartitionColumn(ColumnMappingMode.ID); + testDropPartitionColumn(ColumnMappingMode.NAME); + } + public void testDropPartitionColumn(ColumnMappingMode mode) + { String tableName = "test_drop_partition_column_" + randomNameSuffix(); assertUpdate("CREATE TABLE " + tableName + "(data int, part int) WITH (partitioned_by = ARRAY['part'], column_mapping_mode = '" + mode + "')"); @@ -459,13 +457,15 @@ public void testRenameColumnWithComment() .hasMessageContaining("Cannot rename column in table using column mapping mode NONE"); } - @Test(dataProvider = "columnMappingModeDataProvider") - public void testDeltaRenameColumnWithComment(ColumnMappingMode mode) + @Test + public void testDeltaRenameColumnWithComment() { - if (mode == ColumnMappingMode.NONE) { - throw new SkipException("The connector doesn't support renaming columns with 'none' column mapping"); - } + testDeltaRenameColumnWithComment(ColumnMappingMode.ID); + testDeltaRenameColumnWithComment(ColumnMappingMode.NAME); + } + private void testDeltaRenameColumnWithComment(ColumnMappingMode mode) + { String tableName = "test_rename_column_" + randomNameSuffix(); assertUpdate("" + "CREATE TABLE " + tableName + @@ -494,12 +494,14 @@ public void testAlterTableRenameColumnToLongName() } @Override - public void testRenameColumnName(String columnName) + public void testRenameColumnName() { - // Override because the connector doesn't support renaming columns with 'none' column mapping - // There are some tests in in io.trino.tests.product.deltalake.TestDeltaLakeColumnMappingMode - assertThatThrownBy(() -> super.testRenameColumnName(columnName)) - .hasMessageContaining("Cannot rename column in table using column mapping mode NONE"); + for (String columnName : testColumnNameDataProvider()) { + // Override because the connector doesn't support renaming columns with 'none' column mapping + // There are some tests in in io.trino.tests.product.deltalake.TestDeltaLakeColumnMappingMode + assertThatThrownBy(() -> testRenameColumnName(columnName, requiresDelimiting(columnName))) + .hasMessageContaining("Cannot rename column in table using column mapping mode NONE"); + } } @Override @@ -510,8 +512,18 @@ public void testCharVarcharComparison() .hasStackTraceContaining("Unsupported type: char(3)"); } - @Test(dataProvider = "timestampValues") - public void testTimestampPredicatePushdown(String value) + @Test + public void testTimestampPredicatePushdown() + { + testTimestampPredicatePushdown("1965-10-31 01:00:08.123 UTC"); + testTimestampPredicatePushdown("1965-10-31 01:00:08.999 UTC"); + testTimestampPredicatePushdown("1970-01-01 01:13:42.000 America/Bahia_Banderas"); // There is a gap in JVM zone + testTimestampPredicatePushdown("1970-01-01 00:00:00.000 Asia/Kathmandu"); + testTimestampPredicatePushdown("2018-10-28 01:33:17.456 Europe/Vilnius"); + testTimestampPredicatePushdown("9999-12-31 23:59:59.999 UTC"); + } + + private void testTimestampPredicatePushdown(String value) { String tableName = "test_parquet_timestamp_predicate_pushdown_" + randomNameSuffix(); @@ -617,18 +629,6 @@ public void testTimestampWithTimeZonePartition() assertUpdate("DROP TABLE " + tableName); } - @DataProvider - public Object[][] timestampValues() - { - return new Object[][] { - {"1965-10-31 01:00:08.123 UTC"}, - {"1965-10-31 01:00:08.999 UTC"}, - {"1970-01-01 01:13:42.000 America/Bahia_Banderas"}, // There is a gap in JVM zone - {"1970-01-01 00:00:00.000 Asia/Kathmandu"}, - {"2018-10-28 01:33:17.456 Europe/Vilnius"}, - {"9999-12-31 23:59:59.999 UTC"}}; - } - @Test public void testAddColumnToPartitionedTable() { @@ -838,8 +838,15 @@ public void testMergeSimpleSelectPartitioned() assertUpdate("DROP TABLE " + targetTable); } - @Test(dataProvider = "partitionedProvider") - public void testMergeUpdateWithVariousLayouts(String partitionPhase) + @Test + public void testMergeUpdateWithVariousLayouts() + { + testMergeUpdateWithVariousLayouts(""); + testMergeUpdateWithVariousLayouts(", partitioned_by = ARRAY['customer']"); + testMergeUpdateWithVariousLayouts(", partitioned_by = ARRAY['purchase']"); + } + + private void testMergeUpdateWithVariousLayouts(String partitionPhase) { String targetTable = "merge_formats_target_" + randomNameSuffix(); String sourceTable = "merge_formats_source_" + randomNameSuffix(); @@ -864,18 +871,15 @@ public void testMergeUpdateWithVariousLayouts(String partitionPhase) assertUpdate("DROP TABLE " + targetTable); } - @DataProvider - public Object[][] partitionedProvider() + @Test + public void testMergeMultipleOperations() { - return new Object[][] { - {""}, - {", partitioned_by = ARRAY['customer']"}, - {", partitioned_by = ARRAY['purchase']"} - }; + testMergeMultipleOperations(""); + testMergeMultipleOperations(", partitioned_by = ARRAY['customer']"); + testMergeMultipleOperations(", partitioned_by = ARRAY['purchase']"); } - @Test(dataProvider = "partitionedProvider") - public void testMergeMultipleOperations(String partitioning) + private void testMergeMultipleOperations(String partitioning) { int targetCustomerCount = 32; String targetTable = "merge_multiple_" + randomNameSuffix(); @@ -959,8 +963,17 @@ public void testMergeSimpleQueryPartitioned() assertUpdate("DROP TABLE " + targetTable); } - @Test(dataProvider = "targetWithDifferentPartitioning") - public void testMergeMultipleRowsMatchFails(String createTableSql) + @Test + public void testMergeMultipleRowsMatchFails() + { + testMergeMultipleRowsMatchFails("CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s')"); + testMergeMultipleRowsMatchFails("CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['customer'])"); + testMergeMultipleRowsMatchFails("CREATE TABLE %s (customer VARCHAR, address VARCHAR, purchases INT) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address'])"); + testMergeMultipleRowsMatchFails("CREATE TABLE %s (purchases INT, customer VARCHAR, address VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address', 'customer'])"); + testMergeMultipleRowsMatchFails("CREATE TABLE %s (purchases INT, address VARCHAR, customer VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address', 'customer'])"); + } + + private void testMergeMultipleRowsMatchFails(String createTableSql) { String targetTable = "merge_multiple_target_" + randomNameSuffix(); String sourceTable = "merge_multiple_source_" + randomNameSuffix(); @@ -984,20 +997,40 @@ public void testMergeMultipleRowsMatchFails(String createTableSql) assertUpdate("DROP TABLE " + targetTable); } - @DataProvider - public Object[][] targetWithDifferentPartitioning() - { - return new Object[][] { - {"CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s')"}, - {"CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['customer'])"}, - {"CREATE TABLE %s (customer VARCHAR, address VARCHAR, purchases INT) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address'])"}, - {"CREATE TABLE %s (purchases INT, customer VARCHAR, address VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address', 'customer'])"}, - {"CREATE TABLE %s (purchases INT, address VARCHAR, customer VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address', 'customer'])"} - }; - } - - @Test(dataProvider = "targetAndSourceWithDifferentPartitioning") - public void testMergeWithDifferentPartitioning(String testDescription, String createTargetTableSql, String createSourceTableSql) + @Test + public void testMergeWithDifferentPartitioning() + { + testMergeWithDifferentPartitioning( + "target_partitioned_source_and_target_partitioned", + "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address', 'customer'])", + "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address'])"); + testMergeWithDifferentPartitioning( + "target_partitioned_source_and_target_partitioned", + "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['customer', 'address'])", + "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address'])"); + testMergeWithDifferentPartitioning( + "target_flat_source_partitioned_by_customer", + "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s')", + "CREATE TABLE %s (purchases INT, address VARCHAR, customer VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['customer'])"); + testMergeWithDifferentPartitioning( + "target_partitioned_by_customer_source_flat", + "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address'])", + "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s')"); + testMergeWithDifferentPartitioning( + "target_bucketed_by_customer_source_flat", + "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['customer', 'address'])", + "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s')"); + testMergeWithDifferentPartitioning( + "target_partitioned_source_partitioned", + "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['customer'])", + "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address'])"); + testMergeWithDifferentPartitioning( + "target_partitioned_target_partitioned", + "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address'])", + "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['customer'])"); + } + + private void testMergeWithDifferentPartitioning(String testDescription, String createTargetTableSql, String createSourceTableSql) { String targetTable = format("%s_target_%s", testDescription, randomNameSuffix()); String sourceTable = format("%s_source_%s", testDescription, randomNameSuffix()); @@ -1022,50 +1055,15 @@ public void testMergeWithDifferentPartitioning(String testDescription, String cr assertUpdate("DROP TABLE " + targetTable); } - @DataProvider - public Object[][] targetAndSourceWithDifferentPartitioning() - { - return new Object[][] { - { - "target_partitioned_source_and_target_partitioned", - "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address', 'customer'])", - "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address'])", - }, - { - "target_partitioned_source_and_target_partitioned", - "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['customer', 'address'])", - "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address'])", - }, - { - "target_flat_source_partitioned_by_customer", - "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s')", - "CREATE TABLE %s (purchases INT, address VARCHAR, customer VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['customer'])" - }, - { - "target_partitioned_by_customer_source_flat", - "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address'])", - "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s')", - }, - { - "target_bucketed_by_customer_source_flat", - "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['customer', 'address'])", - "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s')", - }, - { - "target_partitioned_source_partitioned", - "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['customer'])", - "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address'])", - }, - { - "target_partitioned_target_partitioned", - "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address'])", - "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['customer'])", - } - }; + @Test + public void testTableWithNonNullableColumns() + { + testTableWithNonNullableColumns(ColumnMappingMode.ID); + testTableWithNonNullableColumns(ColumnMappingMode.NAME); + testTableWithNonNullableColumns(ColumnMappingMode.NONE); } - @Test(dataProvider = "columnMappingModeDataProvider") - public void testTableWithNonNullableColumns(ColumnMappingMode mode) + private void testTableWithNonNullableColumns(ColumnMappingMode mode) { String tableName = "test_table_with_non_nullable_columns_" + randomNameSuffix(); assertUpdate("CREATE TABLE " + tableName + "(col1 INTEGER NOT NULL, col2 INTEGER, col3 INTEGER) WITH (column_mapping_mode='" + mode + "')"); @@ -1084,76 +1082,80 @@ public void testTableWithNonNullableColumns(ColumnMappingMode mode) assertQuery("SELECT * FROM " + tableName, "VALUES(1, 10, 100), (2, 20, 200)"); } - @Test(dataProvider = "changeDataFeedColumnNamesDataProvider") - public void testCreateTableWithChangeDataFeedColumnName(String columnName) + @Test + public void testCreateTableWithChangeDataFeedColumnName() { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_create_table_cdf", "(" + columnName + " int)")) { - assertTableColumnNames(table.getName(), columnName); - } + for (String columnName : CHANGE_DATA_FEED_COLUMN_NAMES) { + try (TestTable table = new TestTable(getQueryRunner()::execute, "test_create_table_cdf", "(" + columnName + " int)")) { + assertTableColumnNames(table.getName(), columnName); + } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_create_table_cdf", "AS SELECT 1 AS " + columnName)) { - assertTableColumnNames(table.getName(), columnName); + try (TestTable table = new TestTable(getQueryRunner()::execute, "test_create_table_cdf", "AS SELECT 1 AS " + columnName)) { + assertTableColumnNames(table.getName(), columnName); + } } } - @Test(dataProvider = "changeDataFeedColumnNamesDataProvider") - public void testUnsupportedCreateTableWithChangeDataFeed(String columnName) + @Test + public void testUnsupportedCreateTableWithChangeDataFeed() { - String tableName = "test_unsupported_create_table_cdf" + randomNameSuffix(); - - assertQueryFails( - "CREATE TABLE " + tableName + "(" + columnName + " int) WITH (change_data_feed_enabled = true)", - "\\QUnable to use [%s] when change data feed is enabled\\E".formatted(columnName)); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); - - assertQueryFails( - "CREATE TABLE " + tableName + " WITH (change_data_feed_enabled = true) AS SELECT 1 AS " + columnName, - "\\QUnable to use [%s] when change data feed is enabled\\E".formatted(columnName)); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); - } + for (String columnName : CHANGE_DATA_FEED_COLUMN_NAMES) { + String tableName = "test_unsupported_create_table_cdf" + randomNameSuffix(); - @Test(dataProvider = "changeDataFeedColumnNamesDataProvider") - public void testUnsupportedAddColumnWithChangeDataFeed(String columnName) - { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_add_column", "(col int) WITH (change_data_feed_enabled = true)")) { assertQueryFails( - "ALTER TABLE " + table.getName() + " ADD COLUMN " + columnName + " int", - "\\QColumn name %s is forbidden when change data feed is enabled\\E".formatted(columnName)); - assertTableColumnNames(table.getName(), "col"); + "CREATE TABLE " + tableName + "(" + columnName + " int) WITH (change_data_feed_enabled = true)", + "\\QUnable to use [%s] when change data feed is enabled\\E".formatted(columnName)); + assertFalse(getQueryRunner().tableExists(getSession(), tableName)); - assertUpdate("ALTER TABLE " + table.getName() + " SET PROPERTIES change_data_feed_enabled = false"); - assertUpdate("ALTER TABLE " + table.getName() + " ADD COLUMN " + columnName + " int"); - assertTableColumnNames(table.getName(), "col", columnName); + assertQueryFails( + "CREATE TABLE " + tableName + " WITH (change_data_feed_enabled = true) AS SELECT 1 AS " + columnName, + "\\QUnable to use [%s] when change data feed is enabled\\E".formatted(columnName)); + assertFalse(getQueryRunner().tableExists(getSession(), tableName)); } } - @Test(dataProvider = "changeDataFeedColumnNamesDataProvider") - public void testUnsupportedRenameColumnWithChangeDataFeed(String columnName) - { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_rename_column", "(col int) WITH (change_data_feed_enabled = true)")) { - assertQueryFails( - "ALTER TABLE " + table.getName() + " RENAME COLUMN col TO " + columnName, - "Cannot rename column when change data feed is enabled"); - assertTableColumnNames(table.getName(), "col"); + @Test + public void testUnsupportedAddColumnWithChangeDataFeed() + { + for (String columnName : CHANGE_DATA_FEED_COLUMN_NAMES) { + try (TestTable table = new TestTable(getQueryRunner()::execute, "test_add_column", "(col int) WITH (change_data_feed_enabled = true)")) { + assertQueryFails( + "ALTER TABLE " + table.getName() + " ADD COLUMN " + columnName + " int", + "\\QColumn name %s is forbidden when change data feed is enabled\\E".formatted(columnName)); + assertTableColumnNames(table.getName(), "col"); + + assertUpdate("ALTER TABLE " + table.getName() + " SET PROPERTIES change_data_feed_enabled = false"); + assertUpdate("ALTER TABLE " + table.getName() + " ADD COLUMN " + columnName + " int"); + assertTableColumnNames(table.getName(), "col", columnName); + } } } - @Test(dataProvider = "changeDataFeedColumnNamesDataProvider") - public void testUnsupportedSetTablePropertyWithChangeDataFeed(String columnName) - { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_set_properties", "(" + columnName + " int)")) { - assertQueryFails( - "ALTER TABLE " + table.getName() + " SET PROPERTIES change_data_feed_enabled = true", - "\\QUnable to enable change data feed because table contains [%s] columns\\E".formatted(columnName)); - assertThat((String) computeScalar("SHOW CREATE TABLE " + table.getName())) - .doesNotContain("change_data_feed_enabled = true"); + @Test + public void testUnsupportedRenameColumnWithChangeDataFeed() + { + for (String columnName : CHANGE_DATA_FEED_COLUMN_NAMES) { + try (TestTable table = new TestTable(getQueryRunner()::execute, "test_rename_column", "(col int) WITH (change_data_feed_enabled = true)")) { + assertQueryFails( + "ALTER TABLE " + table.getName() + " RENAME COLUMN col TO " + columnName, + "Cannot rename column when change data feed is enabled"); + assertTableColumnNames(table.getName(), "col"); + } } } - @DataProvider - public Object[][] changeDataFeedColumnNamesDataProvider() - { - return CHANGE_DATA_FEED_COLUMN_NAMES.stream().collect(toDataProvider()); + @Test + public void testUnsupportedSetTablePropertyWithChangeDataFeed() + { + for (String columnName : CHANGE_DATA_FEED_COLUMN_NAMES) { + try (TestTable table = new TestTable(getQueryRunner()::execute, "test_set_properties", "(" + columnName + " int)")) { + assertQueryFails( + "ALTER TABLE " + table.getName() + " SET PROPERTIES change_data_feed_enabled = true", + "\\QUnable to enable change data feed because table contains [%s] columns\\E".formatted(columnName)); + assertThat((String) computeScalar("SHOW CREATE TABLE " + table.getName())) + .doesNotContain("change_data_feed_enabled = true"); + } + } } @Test @@ -1167,7 +1169,14 @@ public void testThatEnableCdfTablePropertyIsShownForCtasTables() .contains("change_data_feed_enabled = true"); } - @Test(dataProvider = "columnMappingModeDataProvider") + @Test + public void testCreateTableWithColumnMappingMode() + { + testCreateTableWithColumnMappingMode(ColumnMappingMode.ID); + testCreateTableWithColumnMappingMode(ColumnMappingMode.NAME); + testCreateTableWithColumnMappingMode(ColumnMappingMode.NONE); + } + public void testCreateTableWithColumnMappingMode(ColumnMappingMode mode) { testCreateTableColumnMappingMode(mode, tableName -> { @@ -1176,16 +1185,30 @@ public void testCreateTableWithColumnMappingMode(ColumnMappingMode mode) }); } - @Test(dataProvider = "columnMappingModeDataProvider") - public void testCreateTableAsSelectWithColumnMappingMode(ColumnMappingMode mode) + @Test + public void testCreateTableAsSelectWithColumnMappingMode() + { + testCreateTableAsSelectWithColumnMappingMode(ColumnMappingMode.ID); + testCreateTableAsSelectWithColumnMappingMode(ColumnMappingMode.NAME); + testCreateTableAsSelectWithColumnMappingMode(ColumnMappingMode.NONE); + } + + private void testCreateTableAsSelectWithColumnMappingMode(ColumnMappingMode mode) { testCreateTableColumnMappingMode(mode, tableName -> assertUpdate("CREATE TABLE " + tableName + " WITH (column_mapping_mode='" + mode + "')" + " AS SELECT 1 AS a_int, CAST(row(11) AS row(x integer)) AS a_row", 1)); } - @Test(dataProvider = "columnMappingModeDataProvider") - public void testCreatePartitionTableAsSelectWithColumnMappingMode(ColumnMappingMode mode) + @Test + public void testCreatePartitionTableAsSelectWithColumnMappingMode() + { + testCreatePartitionTableAsSelectWithColumnMappingMode(ColumnMappingMode.ID); + testCreatePartitionTableAsSelectWithColumnMappingMode(ColumnMappingMode.NAME); + testCreatePartitionTableAsSelectWithColumnMappingMode(ColumnMappingMode.NONE); + } + + private void testCreatePartitionTableAsSelectWithColumnMappingMode(ColumnMappingMode mode) { testCreateTableColumnMappingMode(mode, tableName -> assertUpdate("CREATE TABLE " + tableName + " WITH (column_mapping_mode='" + mode + "', partitioned_by=ARRAY['a_int'])" + @@ -1211,13 +1234,15 @@ private void testCreateTableColumnMappingMode(ColumnMappingMode mode, Consumer mode != ColumnMappingMode.UNKNOWN) - .collect(toDataProvider()); - } - @Test public void testCreateTableUnsupportedColumnMappingMode() { @@ -1882,8 +1949,15 @@ public void testProjectionPushdownNonPrimitiveTypeExplain() "_row#child := _row#child:bigint:REGULAR"); } - @Test(dataProvider = "columnMappingModeDataProvider") - public void testReadCdfChanges(ColumnMappingMode mode) + @Test + public void testReadCdfChanges() + { + testReadCdfChanges(ColumnMappingMode.ID); + testReadCdfChanges(ColumnMappingMode.NAME); + testReadCdfChanges(ColumnMappingMode.NONE); + } + + private void testReadCdfChanges(ColumnMappingMode mode) { String tableName = "test_basic_operations_on_table_with_cdf_enabled_" + randomNameSuffix(); assertUpdate("CREATE TABLE " + tableName + " (page_url VARCHAR, domain VARCHAR, views INTEGER) " + @@ -1933,8 +2007,15 @@ public void testReadCdfChanges(ColumnMappingMode mode) """); } - @Test(dataProvider = "columnMappingModeDataProvider") - public void testReadCdfChangesOnPartitionedTable(ColumnMappingMode mode) + @Test + public void testReadCdfChangesOnPartitionedTable() + { + testReadCdfChangesOnPartitionedTable(ColumnMappingMode.ID); + testReadCdfChangesOnPartitionedTable(ColumnMappingMode.NAME); + testReadCdfChangesOnPartitionedTable(ColumnMappingMode.NONE); + } + + private void testReadCdfChangesOnPartitionedTable(ColumnMappingMode mode) { String tableName = "test_basic_operations_on_table_with_cdf_enabled_" + randomNameSuffix(); assertUpdate("CREATE TABLE " + tableName + " (page_url VARCHAR, domain VARCHAR, views INTEGER) " + @@ -2024,8 +2105,15 @@ private void testCdfWithMappingModeOnTableWithColumnDropped(ColumnMappingMode mo """); } - @Test(dataProvider = "columnMappingModeDataProvider") - public void testReadMergeChanges(ColumnMappingMode mode) + @Test + public void testReadMergeChanges() + { + testReadMergeChanges(ColumnMappingMode.ID); + testReadMergeChanges(ColumnMappingMode.NAME); + testReadMergeChanges(ColumnMappingMode.NONE); + } + + private void testReadMergeChanges(ColumnMappingMode mode) { String tableName1 = "test_basic_operations_on_table_with_cdf_enabled_merge_into_" + randomNameSuffix(); assertUpdate("CREATE TABLE " + tableName1 + " (page_url VARCHAR, domain VARCHAR, views INTEGER) " + @@ -2070,8 +2158,15 @@ public void testReadMergeChanges(ColumnMappingMode mode) """); } - @Test(dataProvider = "columnMappingModeDataProvider") - public void testReadMergeChangesOnPartitionedTable(ColumnMappingMode mode) + @Test + public void testReadMergeChangesOnPartitionedTable() + { + testReadMergeChangesOnPartitionedTable(ColumnMappingMode.ID); + testReadMergeChangesOnPartitionedTable(ColumnMappingMode.NAME); + testReadMergeChangesOnPartitionedTable(ColumnMappingMode.NONE); + } + + private void testReadMergeChangesOnPartitionedTable(ColumnMappingMode mode) { String targetTable = "test_basic_operations_on_partitioned_table_with_cdf_enabled_target_" + randomNameSuffix(); assertUpdate("CREATE TABLE " + targetTable + " (page_url VARCHAR, domain VARCHAR, views INTEGER) " + @@ -2148,8 +2243,15 @@ public void testReadMergeChangesOnPartitionedTable(ColumnMappingMode mode) """); } - @Test(dataProvider = "columnMappingModeDataProvider") - public void testCdfCommitTimestamp(ColumnMappingMode mode) + @Test + public void testCdfCommitTimestamp() + { + testCdfCommitTimestamp(ColumnMappingMode.ID); + testCdfCommitTimestamp(ColumnMappingMode.NAME); + testCdfCommitTimestamp(ColumnMappingMode.NONE); + } + + private void testCdfCommitTimestamp(ColumnMappingMode mode) { String tableName = "test_cdf_commit_timestamp_" + randomNameSuffix(); assertUpdate("CREATE TABLE " + tableName + " (page_url VARCHAR, domain VARCHAR, views INTEGER) " + @@ -2160,8 +2262,15 @@ public void testCdfCommitTimestamp(ColumnMappingMode mode) assertThat(historyCommitTimestamp).isEqualTo(tableChangesCommitTimestamp); } - @Test(dataProvider = "columnMappingModeDataProvider") - public void testReadDifferentChangeRanges(ColumnMappingMode mode) + @Test + public void testReadDifferentChangeRanges() + { + testReadDifferentChangeRanges(ColumnMappingMode.ID); + testReadDifferentChangeRanges(ColumnMappingMode.NAME); + testReadDifferentChangeRanges(ColumnMappingMode.NONE); + } + + private void testReadDifferentChangeRanges(ColumnMappingMode mode) { String tableName = "test_reading_ranges_of_changes_on_table_with_cdf_enabled_" + randomNameSuffix(); assertUpdate("CREATE TABLE " + tableName + " (page_url VARCHAR, domain VARCHAR, views INTEGER) " + @@ -2226,8 +2335,15 @@ public void testReadDifferentChangeRanges(ColumnMappingMode mode) assertQueryFails("SELECT * FROM TABLE(system.table_changes('test_schema', '" + tableName + "', 10))", "since_version: 10 is higher then current table version: 6"); } - @Test(dataProvider = "columnMappingModeDataProvider") - public void testReadChangesOnTableWithColumnAdded(ColumnMappingMode mode) + @Test + public void testReadChangesOnTableWithColumnAdded() + { + testReadChangesOnTableWithColumnAdded(ColumnMappingMode.ID); + testReadChangesOnTableWithColumnAdded(ColumnMappingMode.NAME); + testReadChangesOnTableWithColumnAdded(ColumnMappingMode.NONE); + } + + private void testReadChangesOnTableWithColumnAdded(ColumnMappingMode mode) { String tableName = "test_reading_changes_on_table_with_columns_added_" + randomNameSuffix(); assertUpdate("CREATE TABLE " + tableName + " (page_url VARCHAR, domain VARCHAR, views INTEGER) " + @@ -2244,8 +2360,15 @@ public void testReadChangesOnTableWithColumnAdded(ColumnMappingMode mode) """); } - @Test(dataProvider = "columnMappingModeDataProvider") - public void testReadChangesOnTableWithRowColumn(ColumnMappingMode mode) + @Test + public void testReadChangesOnTableWithRowColumn() + { + testReadChangesOnTableWithRowColumn(ColumnMappingMode.ID); + testReadChangesOnTableWithRowColumn(ColumnMappingMode.NAME); + testReadChangesOnTableWithRowColumn(ColumnMappingMode.NONE); + } + + private void testReadChangesOnTableWithRowColumn(ColumnMappingMode mode) { String tableName = "test_reading_changes_on_table_with_columns_added_" + randomNameSuffix(); assertUpdate("CREATE TABLE " + tableName + " (page_url VARCHAR, costs ROW(month VARCHAR, amount BIGINT)) " + @@ -2273,8 +2396,15 @@ public void testReadChangesOnTableWithRowColumn(ColumnMappingMode mode) """); } - @Test(dataProvider = "columnMappingModeDataProvider") - public void testCdfOnTableWhichDoesntHaveItEnabledInitially(ColumnMappingMode mode) + @Test + public void testCdfOnTableWhichDoesntHaveItEnabledInitially() + { + testCdfOnTableWhichDoesntHaveItEnabledInitially(ColumnMappingMode.ID); + testCdfOnTableWhichDoesntHaveItEnabledInitially(ColumnMappingMode.NAME); + testCdfOnTableWhichDoesntHaveItEnabledInitially(ColumnMappingMode.NONE); + } + + private void testCdfOnTableWhichDoesntHaveItEnabledInitially(ColumnMappingMode mode) { String tableName = "test_cdf_on_table_without_it_initially_" + randomNameSuffix(); assertUpdate("CREATE TABLE " + tableName + " (page_url VARCHAR, domain VARCHAR, views INTEGER) " + @@ -2315,8 +2445,15 @@ public void testCdfOnTableWhichDoesntHaveItEnabledInitially(ColumnMappingMode mo """); } - @Test(dataProvider = "columnMappingModeDataProvider") - public void testReadChangesFromCtasTable(ColumnMappingMode mode) + @Test + public void testReadChangesFromCtasTable() + { + testReadChangesFromCtasTable(ColumnMappingMode.ID); + testReadChangesFromCtasTable(ColumnMappingMode.NAME); + testReadChangesFromCtasTable(ColumnMappingMode.NONE); + } + + private void testReadChangesFromCtasTable(ColumnMappingMode mode) { String tableName = "test_basic_operations_on_table_with_cdf_enabled_" + randomNameSuffix(); assertUpdate("CREATE TABLE " + tableName + " WITH (change_data_feed_enabled = true, column_mapping_mode = '" + mode + "') " + @@ -2333,8 +2470,16 @@ public void testReadChangesFromCtasTable(ColumnMappingMode mode) """); } - @Test(dataProvider = "columnMappingModeDataProvider") - public void testVacuumDeletesCdfFiles(ColumnMappingMode mode) + @Test + public void testVacuumDeletesCdfFiles() + throws InterruptedException + { + testVacuumDeletesCdfFiles(ColumnMappingMode.ID); + testVacuumDeletesCdfFiles(ColumnMappingMode.NAME); + testVacuumDeletesCdfFiles(ColumnMappingMode.NONE); + } + + private void testVacuumDeletesCdfFiles(ColumnMappingMode mode) throws InterruptedException { String tableName = "test_vacuum_correctly_deletes_cdf_files_" + randomNameSuffix(); @@ -2364,8 +2509,15 @@ public void testVacuumDeletesCdfFiles(ColumnMappingMode mode) """); } - @Test(dataProvider = "columnMappingModeDataProvider") - public void testCdfWithOptimize(ColumnMappingMode mode) + @Test + public void testCdfWithOptimize() + { + testCdfWithOptimize(ColumnMappingMode.ID); + testCdfWithOptimize(ColumnMappingMode.NAME); + testCdfWithOptimize(ColumnMappingMode.NONE); + } + + private void testCdfWithOptimize(ColumnMappingMode mode) { String tableName = "test_cdf_with_optimize_" + randomNameSuffix(); assertUpdate("CREATE TABLE " + tableName + " (page_url VARCHAR, domain VARCHAR, views INTEGER) " + @@ -2410,7 +2562,13 @@ public void testTableChangesAccessControl() assertUpdate("DROP TABLE " + tableName); } - @Test(dataProviderClass = DataProviders.class, dataProvider = "trueFalse") + @Test + public void testTableWithTrailingSlashLocation() + { + testTableWithTrailingSlashLocation(true); + testTableWithTrailingSlashLocation(false); + } + public void testTableWithTrailingSlashLocation(boolean partitioned) { String tableName = "test_table_with_trailing_slash_location_" + randomNameSuffix(); @@ -2435,8 +2593,56 @@ public void testTableWithTrailingSlashLocation(boolean partitioned) assertUpdate("DROP TABLE " + tableName); } - @Test(dataProvider = "deleteFiltersForTable") - public void testDeleteWithFilter(String createTableSql, String deleteFilter, boolean pushDownDelete) + @Test + public void testDeleteWithFilter() + { + testDeleteWithFilter( + "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s')", + "address = 'Antioch'", + false); + testDeleteWithFilter( + // delete filter applied on function over non-partitioned field + "CREATE TABLE %s (customer VARCHAR, address VARCHAR, purchases INT) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address'])", + "starts_with(address, 'Antioch')", + false); + testDeleteWithFilter( + // delete filter applied on partitioned field + "CREATE TABLE %s (customer VARCHAR, address VARCHAR, purchases INT) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address'])", + "address = 'Antioch'", + true); + testDeleteWithFilter( + // delete filter applied on partitioned field and on synthesized field + "CREATE TABLE %s (customer VARCHAR, address VARCHAR, purchases INT) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address'])", + "address = 'Antioch' AND \"$file_size\" > 0", + false); + testDeleteWithFilter( + // delete filter applied on function over partitioned field + "CREATE TABLE %s (customer VARCHAR, address VARCHAR, purchases INT) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address'])", + "starts_with(address, 'Antioch')", + false); + testDeleteWithFilter( + // delete filter applied on non-partitioned field + "CREATE TABLE %s (customer VARCHAR, address VARCHAR, purchases INT) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['customer'])", + "address = 'Antioch'", + false); + testDeleteWithFilter( + // delete filter fully applied on composed partition + "CREATE TABLE %s (purchases INT, customer VARCHAR, address VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address', 'customer'])", + "address = 'Antioch' AND (customer = 'Aaron' OR customer = 'Bill')", + true); + testDeleteWithFilter( + // delete filter applied only partly on first partitioned field + "CREATE TABLE %s (purchases INT, address VARCHAR, customer VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address', 'customer'])", + "address = 'Antioch'", + true); + testDeleteWithFilter( + // delete filter applied only partly on second partitioned field + "CREATE TABLE %s (purchases INT, address VARCHAR, customer VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['customer', 'address'])", + "address = 'Antioch'", + true); + } + + private void testDeleteWithFilter(String createTableSql, String deleteFilter, boolean pushDownDelete) { String table = "delete_with_filter_" + randomNameSuffix(); assertUpdate(format(createTableSql, table, bucketName, table)); @@ -2463,66 +2669,6 @@ public void testDeleteWithFilter(String createTableSql, String deleteFilter, boo assertUpdate("DROP TABLE " + table); } - @DataProvider - public Object[][] deleteFiltersForTable() - { - return new Object[][]{ - { - "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s')", - "address = 'Antioch'", - false - }, - { - // delete filter applied on function over non-partitioned field - "CREATE TABLE %s (customer VARCHAR, address VARCHAR, purchases INT) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address'])", - "starts_with(address, 'Antioch')", - false - }, - { - // delete filter applied on partitioned field - "CREATE TABLE %s (customer VARCHAR, address VARCHAR, purchases INT) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address'])", - "address = 'Antioch'", - true - }, - { - // delete filter applied on partitioned field and on synthesized field - "CREATE TABLE %s (customer VARCHAR, address VARCHAR, purchases INT) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address'])", - "address = 'Antioch' AND \"$file_size\" > 0", - false - }, - { - // delete filter applied on function over partitioned field - "CREATE TABLE %s (customer VARCHAR, address VARCHAR, purchases INT) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address'])", - "starts_with(address, 'Antioch')", - false - }, - { - // delete filter applied on non-partitioned field - "CREATE TABLE %s (customer VARCHAR, address VARCHAR, purchases INT) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['customer'])", - "address = 'Antioch'", - false - }, - { - // delete filter fully applied on composed partition - "CREATE TABLE %s (purchases INT, customer VARCHAR, address VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address', 'customer'])", - "address = 'Antioch' AND (customer = 'Aaron' OR customer = 'Bill')", - true - }, - { - // delete filter applied only partly on first partitioned field - "CREATE TABLE %s (purchases INT, address VARCHAR, customer VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['address', 'customer'])", - "address = 'Antioch'", - true - }, - { - // delete filter applied only partly on second partitioned field - "CREATE TABLE %s (purchases INT, address VARCHAR, customer VARCHAR) WITH (location = 's3://%s/%s', partitioned_by = ARRAY['customer', 'address'])", - "address = 'Antioch'", - true - }, - }; - } - @Override protected void verifyAddNotNullColumnToNonEmptyTableFailurePermissible(Throwable e) { diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java index 0612e6f01c9f..8334515d2e8f 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java @@ -69,7 +69,6 @@ import org.assertj.core.api.AbstractLongAssert; import org.intellij.lang.annotations.Language; import org.testng.SkipException; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.io.File; @@ -165,7 +164,6 @@ import static io.trino.sql.planner.planprinter.IoPlanPrinter.FormattedMarker.Bound.EXACTLY; import static io.trino.sql.planner.planprinter.PlanPrinter.textLogicalPlan; import static io.trino.sql.tree.ExplainType.Type.DISTRIBUTED; -import static io.trino.testing.DataProviders.toDataProvider; import static io.trino.testing.MaterializedResult.resultBuilder; import static io.trino.testing.QueryAssertions.assertEqualsIgnoreOrder; import static io.trino.testing.TestingAccessControlManager.TestingPrivilegeType.DELETE_TABLE; @@ -398,8 +396,14 @@ public void testRowLevelDelete() .hasStackTraceContaining(MODIFYING_NON_TRANSACTIONAL_TABLE_MESSAGE); } - @Test(dataProvider = "queryPartitionFilterRequiredSchemasDataProvider") - public void testRequiredPartitionFilter(String queryPartitionFilterRequiredSchemas) + @Test + public void testRequiredPartitionFilter() + { + testRequiredPartitionFilter("[]"); + testRequiredPartitionFilter("[\"tpch\"]"); + } + + private void testRequiredPartitionFilter(String queryPartitionFilterRequiredSchemas) { Session session = Session.builder(getSession()) .setIdentity(Identity.forUser("hive") @@ -445,8 +449,14 @@ public void testRequiredPartitionFilter(String queryPartitionFilterRequiredSchem assertUpdate(session, "DROP TABLE test_required_partition_filter"); } - @Test(dataProvider = "queryPartitionFilterRequiredSchemasDataProvider") - public void testRequiredPartitionFilterInferred(String queryPartitionFilterRequiredSchemas) + @Test + public void testRequiredPartitionFilterInferred() + { + testRequiredPartitionFilterInferred("[]"); + testRequiredPartitionFilterInferred("[\"tpch\"]"); + } + + private void testRequiredPartitionFilterInferred(String queryPartitionFilterRequiredSchemas) { Session session = Session.builder(getSession()) .setIdentity(Identity.forUser("hive") @@ -478,15 +488,6 @@ public void testRequiredPartitionFilterInferred(String queryPartitionFilterRequi assertUpdate(session, "DROP TABLE test_partition_filter_inferred_right"); } - @DataProvider - public Object[][] queryPartitionFilterRequiredSchemasDataProvider() - { - return new Object[][] { - {"[]"}, - {"[\"tpch\"]"} - }; - } - @Test public void testRequiredPartitionFilterAppliedOnDifferentSchema() { @@ -2259,8 +2260,100 @@ private void testBucketedTable(Session session, HiveStorageFormat storageFormat, assertFalse(getQueryRunner().tableExists(session, tableName)); } - @Test(dataProvider = "bucketFilteringDataTypesSetupProvider") - public void testFilterOnBucketedTable(BucketedFilterTestSetup testSetup) + @Test + public void testFilterOnBucketedTable() + { + testFilterOnBucketedValue( + "BOOLEAN", + Stream.concat(IntStream.range(0, 100).mapToObj(i -> "true"), IntStream.range(0, 100).mapToObj(i -> "false")) + .collect(toImmutableList()), + "true", + 100, + 100); + + testFilterOnBucketedValue( + "TINYINT", + IntStream.range(0, 127).mapToObj(String::valueOf).collect(toImmutableList()), + "126", + 26, + 1); + + testFilterOnBucketedValue( + "SMALLINT", + IntStream.range(0, 1000).map(i -> i + 22767).mapToObj(String::valueOf).collect(toImmutableList()), + "22767", + 200, + 1); + + testFilterOnBucketedValue( + "INTEGER", + IntStream.range(0, 1000).map(i -> i + 1274942432).mapToObj(String::valueOf).collect(toImmutableList()), + "1274942432", + 200, + 1); + + testFilterOnBucketedValue( + "BIGINT", + IntStream.range(0, 1000).mapToLong(i -> i + 312739231274942432L).mapToObj(String::valueOf).collect(toImmutableList()), + "312739231274942432", + 200, + 1); + + testFilterOnBucketedValue( + "REAL", + IntStream.range(0, 1000).mapToDouble(i -> i + 567.123).mapToObj(val -> "REAL '" + val + "'").collect(toImmutableList()), + "567.123", + 201, + 1); + + testFilterOnBucketedValue( + "DOUBLE", + IntStream.range(0, 1000).mapToDouble(i -> i + 1234567890123.123).mapToObj(val -> "DOUBLE '" + val + "'").collect(toImmutableList()), + "1234567890123.123", + 201, + 1); + + testFilterOnBucketedValue( + "VARCHAR", + IntStream.range(0, 1000).mapToObj(i -> "'test value " + i + "'").collect(toImmutableList()), + "'test value 5'", + 200, + 1); + + testFilterOnBucketedValue( + "VARCHAR(20)", + IntStream.range(0, 1000).mapToObj(i -> "'test value " + i + "'").collect(toImmutableList()), + "'test value 5'", + 200, + 1); + + testFilterOnBucketedValue( + "DATE", + IntStream.range(0, 1000).mapToObj(i -> "DATE '2020-02-12' + interval '" + i + "' day").collect(toImmutableList()), + "DATE '2020-02-15'", + 200, + 1); + + testFilterOnBucketedValue( + "ARRAY", + IntStream.range(0, 1000) + .mapToObj(i -> format("ARRAY[%s, %s, %s, %s]", i + 22767, i + 22768, i + 22769, i + 22770)) + .collect(toImmutableList()), + "ARRAY[22767, 22768, 22769, 22770]", + 200, + 1); + + testFilterOnBucketedValue( + "MAP", + IntStream.range(0, 1000) + .mapToObj(i -> format("MAP(ARRAY[%s, %s], ARRAY[%s, %s])", i + 567.123, i + 568.456, i + 22769, i + 22770)) + .collect(toImmutableList()), + "MAP(ARRAY[567.123, 568.456], ARRAY[22769, 22770])", + 149, + 1); + } + + private void testFilterOnBucketedValue(String typeName, List valueList, String filterValue, long expectedPhysicalInputRows, long expectedResult) { String tableName = "test_filter_on_bucketed_table_" + randomNameSuffix(); assertUpdate( @@ -2270,12 +2363,12 @@ public void testFilterOnBucketedTable(BucketedFilterTestSetup testSetup) format = 'TEXTFILE', bucketed_by = ARRAY[ 'bucket_key' ], bucket_count = 5) - """.formatted(tableName, testSetup.getTypeName())); + """.formatted(tableName, typeName)); - String values = testSetup.getValues().stream() + String values = valueList.stream() .map(value -> "(" + value + ", rand())") .collect(joining(", ")); - assertUpdate("INSERT INTO " + tableName + " VALUES " + values, testSetup.getValues().size()); + assertUpdate("INSERT INTO " + tableName + " VALUES " + values, valueList.size()); // It will only read data from a single bucket instead of all buckets, // so physicalInputPositions should be less than number of rows inserted (. @@ -2285,99 +2378,23 @@ public void testFilterOnBucketedTable(BucketedFilterTestSetup testSetup) SELECT count(*) FROM %s WHERE bucket_key = %s - """.formatted(tableName, testSetup.getFilterValue()), - queryStats -> assertThat(queryStats.getPhysicalInputPositions()).isEqualTo(testSetup.getExpectedPhysicalInputRows()), - result -> assertThat(result.getOnlyValue()).isEqualTo(testSetup.getExpectedResult())); + """.formatted(tableName, filterValue), + queryStats -> assertThat(queryStats.getPhysicalInputPositions()).isEqualTo(expectedPhysicalInputRows), + result -> assertThat(result.getOnlyValue()).isEqualTo(expectedResult)); assertUpdate("DROP TABLE " + tableName); } - @DataProvider - public final Object[][] bucketFilteringDataTypesSetupProvider() - { - List testSetups = ImmutableList.of( - new BucketedFilterTestSetup( - "BOOLEAN", - Stream.concat(IntStream.range(0, 100).mapToObj(i -> "true"), IntStream.range(0, 100).mapToObj(i -> "false")) - .collect(toImmutableList()), - "true", - 100, - 100), - new BucketedFilterTestSetup( - "TINYINT", - IntStream.range(0, 127).mapToObj(String::valueOf).collect(toImmutableList()), - "126", - 26, - 1), - new BucketedFilterTestSetup( - "SMALLINT", - IntStream.range(0, 1000).map(i -> i + 22767).mapToObj(String::valueOf).collect(toImmutableList()), - "22767", - 200, - 1), - new BucketedFilterTestSetup( - "INTEGER", - IntStream.range(0, 1000).map(i -> i + 1274942432).mapToObj(String::valueOf).collect(toImmutableList()), - "1274942432", - 200, - 1), - new BucketedFilterTestSetup( - "BIGINT", - IntStream.range(0, 1000).mapToLong(i -> i + 312739231274942432L).mapToObj(String::valueOf).collect(toImmutableList()), - "312739231274942432", - 200, - 1), - new BucketedFilterTestSetup( - "REAL", - IntStream.range(0, 1000).mapToDouble(i -> i + 567.123).mapToObj(val -> "REAL '" + val + "'").collect(toImmutableList()), - "567.123", - 201, - 1), - new BucketedFilterTestSetup( - "DOUBLE", - IntStream.range(0, 1000).mapToDouble(i -> i + 1234567890123.123).mapToObj(val -> "DOUBLE '" + val + "'").collect(toImmutableList()), - "1234567890123.123", - 201, - 1), - new BucketedFilterTestSetup( - "VARCHAR", - IntStream.range(0, 1000).mapToObj(i -> "'test value " + i + "'").collect(toImmutableList()), - "'test value 5'", - 200, - 1), - new BucketedFilterTestSetup( - "VARCHAR(20)", - IntStream.range(0, 1000).mapToObj(i -> "'test value " + i + "'").collect(toImmutableList()), - "'test value 5'", - 200, - 1), - new BucketedFilterTestSetup( - "DATE", - IntStream.range(0, 1000).mapToObj(i -> "DATE '2020-02-12' + interval '" + i + "' day").collect(toImmutableList()), - "DATE '2020-02-15'", - 200, - 1), - new BucketedFilterTestSetup( - "ARRAY", - IntStream.range(0, 1000) - .mapToObj(i -> format("ARRAY[%s, %s, %s, %s]", i + 22767, i + 22768, i + 22769, i + 22770)) - .collect(toImmutableList()), - "ARRAY[22767, 22768, 22769, 22770]", - 200, - 1), - new BucketedFilterTestSetup( - "MAP", - IntStream.range(0, 1000) - .mapToObj(i -> format("MAP(ARRAY[%s, %s], ARRAY[%s, %s])", i + 567.123, i + 568.456, i + 22769, i + 22770)) - .collect(toImmutableList()), - "MAP(ARRAY[567.123, 568.456], ARRAY[22769, 22770])", - 149, - 1)); - return testSetups.stream() - .collect(toDataProvider()); - } - - @Test(dataProvider = "bucketedUnsupportedTypes") - public void testBucketedTableUnsupportedTypes(String typeName) + @Test + public void testBucketedTableUnsupportedTypes() + { + testBucketedTableUnsupportedTypes("VARBINARY"); + testBucketedTableUnsupportedTypes("TIMESTAMP"); + testBucketedTableUnsupportedTypes("DECIMAL(10,3)"); + testBucketedTableUnsupportedTypes("CHAR"); + testBucketedTableUnsupportedTypes("ROW(id VARCHAR)"); + } + + private void testBucketedTableUnsupportedTypes(String typeName) { String tableName = "test_bucketed_table_for_unsupported_types_" + randomNameSuffix(); assertThatThrownBy(() -> assertUpdate( @@ -2390,12 +2407,6 @@ public void testBucketedTableUnsupportedTypes(String typeName) .hasMessage("Cannot create a table bucketed on an unsupported type"); } - @DataProvider - public final Object[][] bucketedUnsupportedTypes() - { - return new Object[][] {{"VARBINARY"}, {"TIMESTAMP"}, {"DECIMAL(10,3)"}, {"CHAR"}, {"ROW(id VARCHAR)"}}; - } - /** * Regression test for https://github.com/trinodb/trino/issues/5295 */ @@ -3880,8 +3891,15 @@ public void testArrays() assertQuery("SELECT col[1][2] FROM tmp_array13", "SELECT 2.345"); } - @Test(dataProvider = "timestampPrecision") - public void testTemporalArrays(HiveTimestampPrecision timestampPrecision) + @Test + public void testTemporalArrays() + { + testTemporalArrays(HiveTimestampPrecision.MILLISECONDS); + testTemporalArrays(HiveTimestampPrecision.MICROSECONDS); + testTemporalArrays(HiveTimestampPrecision.NANOSECONDS); + } + + private void testTemporalArrays(HiveTimestampPrecision timestampPrecision) { Session session = withTimestampPrecision(getSession(), timestampPrecision); assertUpdate("DROP TABLE IF EXISTS tmp_array11"); @@ -3892,7 +3910,14 @@ public void testTemporalArrays(HiveTimestampPrecision timestampPrecision) assertOneNotNullResult(session, "SELECT col[1] FROM tmp_array12"); } - @Test(dataProvider = "timestampPrecision") + @Test + public void testMaps() + { + testMaps(HiveTimestampPrecision.MILLISECONDS); + testMaps(HiveTimestampPrecision.MICROSECONDS); + testMaps(HiveTimestampPrecision.NANOSECONDS); + } + public void testMaps(HiveTimestampPrecision timestampPrecision) { Session session = withTimestampPrecision(getSession(), timestampPrecision); @@ -4180,16 +4205,12 @@ public void testMultipleWritersWhenTaskScaleWritersIsEnabledWithMemoryLimit() .isBetween(0L, workers); } - @DataProvider(name = "taskWritersLimitParams") - public Object[][] prepareScaledWritersOption() - { - return new Object[][] {{true, true, 2}, {false, true, 2}, {false, false, 3}}; - } - - @Test(dataProvider = "taskWritersLimitParams") - public void testWriterTasksCountLimitUnpartitioned(boolean scaleWriters, boolean redistributeWrites, int expectedFilesCount) + @Test + public void testWriterTasksCountLimitUnpartitioned() { - testLimitWriterTasks(2, expectedFilesCount, scaleWriters, redistributeWrites, false, DataSize.of(1, MEGABYTE)); + testLimitWriterTasks(2, 2, true, true, false, DataSize.of(1, MEGABYTE)); + testLimitWriterTasks(2, 2, false, true, false, DataSize.of(1, MEGABYTE)); + testLimitWriterTasks(2, 3, false, false, false, DataSize.of(1, MEGABYTE)); } @Test @@ -4857,8 +4878,15 @@ public void testFileSizeHiddenColumn() assertUpdate("DROP TABLE test_file_size"); } - @Test(dataProvider = "timestampPrecision") - public void testFileModifiedTimeHiddenColumn(HiveTimestampPrecision precision) + @Test + public void testFileModifiedTimeHiddenColumn() + { + testFileModifiedTimeHiddenColumn(HiveTimestampPrecision.MILLISECONDS); + testFileModifiedTimeHiddenColumn(HiveTimestampPrecision.MICROSECONDS); + testFileModifiedTimeHiddenColumn(HiveTimestampPrecision.NANOSECONDS); + } + + private void testFileModifiedTimeHiddenColumn(HiveTimestampPrecision precision) { long testStartTime = Instant.now().toEpochMilli(); @@ -5265,24 +5293,22 @@ public void testMismatchedBucketWithBucketPredicate() assertUpdate("DROP TABLE IF EXISTS test_mismatch_bucketing_with_bucket_predicate32"); } - @DataProvider - public Object[][] timestampPrecisionAndValues() + @Test + public void testParquetTimestampPredicatePushdown() { - return new Object[][] { - {HiveTimestampPrecision.MILLISECONDS, LocalDateTime.parse("2012-10-31T01:00:08.123")}, - {HiveTimestampPrecision.MICROSECONDS, LocalDateTime.parse("2012-10-31T01:00:08.123456")}, - {HiveTimestampPrecision.NANOSECONDS, LocalDateTime.parse("2012-10-31T01:00:08.123000000")}, - {HiveTimestampPrecision.NANOSECONDS, LocalDateTime.parse("2012-10-31T01:00:08.123000001")}, - {HiveTimestampPrecision.NANOSECONDS, LocalDateTime.parse("2012-10-31T01:00:08.123456789")}, - {HiveTimestampPrecision.MILLISECONDS, LocalDateTime.parse("1965-10-31T01:00:08.123")}, - {HiveTimestampPrecision.MICROSECONDS, LocalDateTime.parse("1965-10-31T01:00:08.123456")}, - {HiveTimestampPrecision.NANOSECONDS, LocalDateTime.parse("1965-10-31T01:00:08.123000000")}, - {HiveTimestampPrecision.NANOSECONDS, LocalDateTime.parse("1965-10-31T01:00:08.123000001")}, - {HiveTimestampPrecision.NANOSECONDS, LocalDateTime.parse("1965-10-31T01:00:08.123456789")}}; + testParquetTimestampPredicatePushdown(HiveTimestampPrecision.MILLISECONDS, LocalDateTime.parse("2012-10-31T01:00:08.123")); + testParquetTimestampPredicatePushdown(HiveTimestampPrecision.MICROSECONDS, LocalDateTime.parse("2012-10-31T01:00:08.123456")); + testParquetTimestampPredicatePushdown(HiveTimestampPrecision.NANOSECONDS, LocalDateTime.parse("2012-10-31T01:00:08.123000000")); + testParquetTimestampPredicatePushdown(HiveTimestampPrecision.NANOSECONDS, LocalDateTime.parse("2012-10-31T01:00:08.123000001")); + testParquetTimestampPredicatePushdown(HiveTimestampPrecision.NANOSECONDS, LocalDateTime.parse("2012-10-31T01:00:08.123456789")); + testParquetTimestampPredicatePushdown(HiveTimestampPrecision.MILLISECONDS, LocalDateTime.parse("1965-10-31T01:00:08.123")); + testParquetTimestampPredicatePushdown(HiveTimestampPrecision.MICROSECONDS, LocalDateTime.parse("1965-10-31T01:00:08.123456")); + testParquetTimestampPredicatePushdown(HiveTimestampPrecision.NANOSECONDS, LocalDateTime.parse("1965-10-31T01:00:08.123000000")); + testParquetTimestampPredicatePushdown(HiveTimestampPrecision.NANOSECONDS, LocalDateTime.parse("1965-10-31T01:00:08.123000001")); + testParquetTimestampPredicatePushdown(HiveTimestampPrecision.NANOSECONDS, LocalDateTime.parse("1965-10-31T01:00:08.123456789")); } - @Test(dataProvider = "timestampPrecisionAndValues") - public void testParquetTimestampPredicatePushdown(HiveTimestampPrecision timestampPrecision, LocalDateTime value) + private void testParquetTimestampPredicatePushdown(HiveTimestampPrecision timestampPrecision, LocalDateTime value) { Session session = withTimestampPrecision(getSession(), timestampPrecision); String tableName = "test_parquet_timestamp_predicate_pushdown_" + randomNameSuffix(); @@ -5311,8 +5337,22 @@ public void testParquetTimestampPredicatePushdown(HiveTimestampPrecision timesta results -> {}); } - @Test(dataProvider = "timestampPrecisionAndValues") - public void testOrcTimestampPredicatePushdown(HiveTimestampPrecision timestampPrecision, LocalDateTime value) + @Test + public void testOrcTimestampPredicatePushdown() + { + testOrcTimestampPredicatePushdown(HiveTimestampPrecision.MILLISECONDS, LocalDateTime.parse("2012-10-31T01:00:08.123")); + testOrcTimestampPredicatePushdown(HiveTimestampPrecision.MICROSECONDS, LocalDateTime.parse("2012-10-31T01:00:08.123456")); + testOrcTimestampPredicatePushdown(HiveTimestampPrecision.NANOSECONDS, LocalDateTime.parse("2012-10-31T01:00:08.123000000")); + testOrcTimestampPredicatePushdown(HiveTimestampPrecision.NANOSECONDS, LocalDateTime.parse("2012-10-31T01:00:08.123000001")); + testOrcTimestampPredicatePushdown(HiveTimestampPrecision.NANOSECONDS, LocalDateTime.parse("2012-10-31T01:00:08.123456789")); + testOrcTimestampPredicatePushdown(HiveTimestampPrecision.MILLISECONDS, LocalDateTime.parse("1965-10-31T01:00:08.123")); + testOrcTimestampPredicatePushdown(HiveTimestampPrecision.MICROSECONDS, LocalDateTime.parse("1965-10-31T01:00:08.123456")); + testOrcTimestampPredicatePushdown(HiveTimestampPrecision.NANOSECONDS, LocalDateTime.parse("1965-10-31T01:00:08.123000000")); + testOrcTimestampPredicatePushdown(HiveTimestampPrecision.NANOSECONDS, LocalDateTime.parse("1965-10-31T01:00:08.123000001")); + testOrcTimestampPredicatePushdown(HiveTimestampPrecision.NANOSECONDS, LocalDateTime.parse("1965-10-31T01:00:08.123456789")); + } + + private void testOrcTimestampPredicatePushdown(HiveTimestampPrecision timestampPrecision, LocalDateTime value) { Session session = withTimestampPrecision(getSession(), timestampPrecision); assertUpdate("DROP TABLE IF EXISTS test_orc_timestamp_predicate_pushdown"); @@ -7995,24 +8035,26 @@ public void testUseSortedProperties() assertUpdate("DROP TABLE " + tableName); } - @Test(dataProvider = "testCreateTableWithCompressionCodecDataProvider") - public void testCreateTableWithCompressionCodec(HiveCompressionCodec compressionCodec) + @Test + public void testCreateTableWithCompressionCodec() { - testWithAllStorageFormats((session, hiveStorageFormat) -> { - if (hiveStorageFormat == HiveStorageFormat.PARQUET && compressionCodec == HiveCompressionCodec.LZ4) { - // TODO (https://github.com/trinodb/trino/issues/9142) Support LZ4 compression with native Parquet writer - assertThatThrownBy(() -> testCreateTableWithCompressionCodec(session, hiveStorageFormat, compressionCodec)) - .hasMessage("Unsupported codec: LZ4"); - return; - } + for (HiveCompressionCodec compressionCodec : HiveCompressionCodec.values()) { + testWithAllStorageFormats((session, hiveStorageFormat) -> { + if (hiveStorageFormat == HiveStorageFormat.PARQUET && compressionCodec == HiveCompressionCodec.LZ4) { + // TODO (https://github.com/trinodb/trino/issues/9142) Support LZ4 compression with native Parquet writer + assertThatThrownBy(() -> testCreateTableWithCompressionCodec(session, hiveStorageFormat, compressionCodec)) + .hasMessage("Unsupported codec: LZ4"); + return; + } - if (!isSupportedCodec(hiveStorageFormat, compressionCodec)) { - assertThatThrownBy(() -> testCreateTableWithCompressionCodec(session, hiveStorageFormat, compressionCodec)) - .hasMessage("Compression codec " + compressionCodec + " not supported for " + hiveStorageFormat); - return; - } - testCreateTableWithCompressionCodec(session, hiveStorageFormat, compressionCodec); - }); + if (!isSupportedCodec(hiveStorageFormat, compressionCodec)) { + assertThatThrownBy(() -> testCreateTableWithCompressionCodec(session, hiveStorageFormat, compressionCodec)) + .hasMessage("Compression codec " + compressionCodec + " not supported for " + hiveStorageFormat); + return; + } + testCreateTableWithCompressionCodec(session, hiveStorageFormat, compressionCodec); + }); + } } private boolean isSupportedCodec(HiveStorageFormat storageFormat, HiveCompressionCodec codec) @@ -8023,13 +8065,6 @@ private boolean isSupportedCodec(HiveStorageFormat storageFormat, HiveCompressio return true; } - @DataProvider - public Object[][] testCreateTableWithCompressionCodecDataProvider() - { - return Stream.of(HiveCompressionCodec.values()) - .collect(toDataProvider()); - } - private void testCreateTableWithCompressionCodec(Session session, HiveStorageFormat storageFormat, HiveCompressionCodec compressionCodec) { session = Session.builder(session) @@ -8627,8 +8662,22 @@ public void testTimestampWithTimeZone() assertUpdate("DROP TABLE test_timestamptz"); } - @Test(dataProvider = "legalUseColumnNamesProvider") - public void testUseColumnNames(HiveStorageFormat format, boolean formatUseColumnNames) + @Test + public void testUseColumnNames() + { + testUseColumnNames(HiveStorageFormat.ORC, true); + testUseColumnNames(HiveStorageFormat.ORC, false); + testUseColumnNames(HiveStorageFormat.PARQUET, true); + testUseColumnNames(HiveStorageFormat.PARQUET, false); + testUseColumnNames(HiveStorageFormat.AVRO, false); + testUseColumnNames(HiveStorageFormat.JSON, false); + testUseColumnNames(HiveStorageFormat.RCBINARY, false); + testUseColumnNames(HiveStorageFormat.RCTEXT, false); + testUseColumnNames(HiveStorageFormat.SEQUENCEFILE, false); + testUseColumnNames(HiveStorageFormat.TEXTFILE, false); + } + + private void testUseColumnNames(HiveStorageFormat format, boolean formatUseColumnNames) { String lowerCaseFormat = format.name().toLowerCase(Locale.ROOT); Session.SessionBuilder builder = Session.builder(getSession()); @@ -8658,8 +8707,17 @@ public void testUseColumnNames(HiveStorageFormat format, boolean formatUseColumn assertUpdate("DROP TABLE " + tableName); } - @Test(dataProvider = "hiddenColumnNames") - public void testHiddenColumnNameConflict(String columnName) + @Test + public void testHiddenColumnNameConflict() + { + testHiddenColumnNameConflict("$path"); + testHiddenColumnNameConflict("$bucket"); + testHiddenColumnNameConflict("$file_size"); + testHiddenColumnNameConflict("$file_modified_time"); + testHiddenColumnNameConflict("$partition"); + } + + private void testHiddenColumnNameConflict(String columnName) { try (TestTable table = new TestTable( getQueryRunner()::execute, @@ -8670,20 +8728,22 @@ public void testHiddenColumnNameConflict(String columnName) } } - @DataProvider - public Object[][] hiddenColumnNames() + @Test + public void testUseColumnAddDrop() { - return new Object[][] { - {"$path"}, - {"$bucket"}, - {"$file_size"}, - {"$file_modified_time"}, - {"$partition"}, - }; + testUseColumnAddDrop(HiveStorageFormat.ORC, true); + testUseColumnAddDrop(HiveStorageFormat.ORC, false); + testUseColumnAddDrop(HiveStorageFormat.PARQUET, true); + testUseColumnAddDrop(HiveStorageFormat.PARQUET, false); + testUseColumnAddDrop(HiveStorageFormat.AVRO, false); + testUseColumnAddDrop(HiveStorageFormat.JSON, false); + testUseColumnAddDrop(HiveStorageFormat.RCBINARY, false); + testUseColumnAddDrop(HiveStorageFormat.RCTEXT, false); + testUseColumnAddDrop(HiveStorageFormat.SEQUENCEFILE, false); + testUseColumnAddDrop(HiveStorageFormat.TEXTFILE, false); } - @Test(dataProvider = "legalUseColumnNamesProvider") - public void testUseColumnAddDrop(HiveStorageFormat format, boolean formatUseColumnNames) + private void testUseColumnAddDrop(HiveStorageFormat format, boolean formatUseColumnNames) { String lowerCaseFormat = format.name().toLowerCase(Locale.ROOT); Session.SessionBuilder builder = Session.builder(getSession()); @@ -8927,23 +8987,6 @@ public void testSelectWithShortZoneId() private static final Set NAMED_COLUMN_ONLY_FORMATS = ImmutableSet.of(HiveStorageFormat.AVRO, HiveStorageFormat.JSON); - @DataProvider - public Object[][] legalUseColumnNamesProvider() - { - return new Object[][] { - {HiveStorageFormat.ORC, true}, - {HiveStorageFormat.ORC, false}, - {HiveStorageFormat.PARQUET, true}, - {HiveStorageFormat.PARQUET, false}, - {HiveStorageFormat.AVRO, false}, - {HiveStorageFormat.JSON, false}, - {HiveStorageFormat.RCBINARY, false}, - {HiveStorageFormat.RCTEXT, false}, - {HiveStorageFormat.SEQUENCEFILE, false}, - {HiveStorageFormat.TEXTFILE, false}, - }; - } - private Session getParallelWriteSession(Session baseSession) { return Session.builder(baseSession) @@ -9092,15 +9135,6 @@ public TypeAndEstimate(Type type, EstimatedStatsAndCost estimate) } } - @DataProvider - public Object[][] timestampPrecision() - { - return new Object[][] { - {HiveTimestampPrecision.MILLISECONDS}, - {HiveTimestampPrecision.MICROSECONDS}, - {HiveTimestampPrecision.NANOSECONDS}}; - } - @Override protected Optional filterDataMappingSmokeTestData(DataMappingTestSetup dataMappingTestSetup) { diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java index e139895358fd..5d8971c30cc2 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java @@ -43,7 +43,6 @@ import io.trino.sql.planner.plan.OutputNode; import io.trino.sql.planner.plan.ValuesNode; import io.trino.testing.BaseConnectorTest; -import io.trino.testing.DataProviders; import io.trino.testing.DistributedQueryRunner; import io.trino.testing.MaterializedResult; import io.trino.testing.MaterializedResultWithQueryId; @@ -64,7 +63,6 @@ import org.intellij.lang.annotations.Language; import org.testng.SkipException; import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.io.File; @@ -77,7 +75,6 @@ import java.time.Instant; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -129,7 +126,6 @@ import static io.trino.spi.type.TimeZoneKey.getTimeZoneKey; import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.sql.planner.assertions.PlanMatchPattern.node; -import static io.trino.testing.DataProviders.toDataProvider; import static io.trino.testing.MaterializedResult.resultBuilder; import static io.trino.testing.QueryAssertions.assertEqualsIgnoreOrder; import static io.trino.testing.TestingConnectorSession.SESSION; @@ -245,14 +241,16 @@ public void testAddRowFieldCaseInsensitivity() } @Override - public void testAddAndDropColumnName(String columnName) + public void testAddAndDropColumnName() { - if (columnName.equals("a.dot")) { - assertThatThrownBy(() -> super.testAddAndDropColumnName(columnName)) - .hasMessage("Failed to add column: Cannot add column with ambiguous name: a.dot, use addColumn(parent, name, type)"); - return; + for (String columnName : testColumnNameDataProvider()) { + if (columnName.equals("a.dot")) { + assertThatThrownBy(() -> testAddAndDropColumnName(columnName, requiresDelimiting(columnName))) + .hasMessage("Failed to add column: Cannot add column with ambiguous name: a.dot, use addColumn(parent, name, type)"); + return; + } + testAddAndDropColumnName(columnName, requiresDelimiting(columnName)); } - super.testAddAndDropColumnName(columnName); } @Override @@ -1085,31 +1083,28 @@ public void testCreatePartitionedTableAs() dropTable("test_create_partitioned_table_as"); } - @DataProvider(name = "partitionedTableWithQuotedIdentifierCasing") - public static Object[][] partitionedTableWithQuotedIdentifierCasing() - { - return new Object[][] { - {"x", "x", true}, - {"X", "x", true}, - {"\"x\"", "x", true}, - {"\"X\"", "x", true}, - {"x", "\"x\"", true}, - {"X", "\"x\"", true}, - {"\"x\"", "\"x\"", true}, - {"\"X\"", "\"x\"", true}, - {"x", "X", true}, - {"X", "X", true}, - {"\"x\"", "X", true}, - {"\"X\"", "X", true}, - {"x", "\"X\"", false}, - {"X", "\"X\"", false}, - {"\"x\"", "\"X\"", false}, - {"\"X\"", "\"X\"", false}, - }; + @Test + public void testCreatePartitionedTableWithQuotedIdentifierCasing() + { + testCreatePartitionedTableWithQuotedIdentifierCasing("x", "x", true); + testCreatePartitionedTableWithQuotedIdentifierCasing("X", "x", true); + testCreatePartitionedTableWithQuotedIdentifierCasing("\"x\"", "x", true); + testCreatePartitionedTableWithQuotedIdentifierCasing("\"X\"", "x", true); + testCreatePartitionedTableWithQuotedIdentifierCasing("x", "\"x\"", true); + testCreatePartitionedTableWithQuotedIdentifierCasing("X", "\"x\"", true); + testCreatePartitionedTableWithQuotedIdentifierCasing("\"x\"", "\"x\"", true); + testCreatePartitionedTableWithQuotedIdentifierCasing("\"X\"", "\"x\"", true); + testCreatePartitionedTableWithQuotedIdentifierCasing("x", "X", true); + testCreatePartitionedTableWithQuotedIdentifierCasing("X", "X", true); + testCreatePartitionedTableWithQuotedIdentifierCasing("\"x\"", "X", true); + testCreatePartitionedTableWithQuotedIdentifierCasing("\"X\"", "X", true); + testCreatePartitionedTableWithQuotedIdentifierCasing("x", "\"X\"", false); + testCreatePartitionedTableWithQuotedIdentifierCasing("X", "\"X\"", false); + testCreatePartitionedTableWithQuotedIdentifierCasing("\"x\"", "\"X\"", false); + testCreatePartitionedTableWithQuotedIdentifierCasing("\"X\"", "\"X\"", false); } - @Test(dataProvider = "partitionedTableWithQuotedIdentifierCasing") - public void testCreatePartitionedTableWithQuotedIdentifierCasing(String columnName, String partitioningField, boolean success) + private void testCreatePartitionedTableWithQuotedIdentifierCasing(String columnName, String partitioningField, boolean success) { String tableName = "partitioning_" + randomNameSuffix(); @Language("SQL") String sql = format("CREATE TABLE %s (%s bigint) WITH (partitioning = ARRAY['%s'])", tableName, columnName, partitioningField); @@ -1244,51 +1239,45 @@ public void testEmptySortedByList() dropTable(tableName); } - @Test(dataProvider = "sortedTableWithQuotedIdentifierCasing") - public void testCreateSortedTableWithQuotedIdentifierCasing(String columnName, String sortField) + @Test + public void testCreateSortedTableWithQuotedIdentifierCasing() + { + testCreateSortedTableWithQuotedIdentifierCasing("col", "col"); + testCreateSortedTableWithQuotedIdentifierCasing("COL", "col"); + testCreateSortedTableWithQuotedIdentifierCasing("\"col\"", "col"); + testCreateSortedTableWithQuotedIdentifierCasing("\"COL\"", "col"); + testCreateSortedTableWithQuotedIdentifierCasing("col", "\"col\""); + testCreateSortedTableWithQuotedIdentifierCasing("COL", "\"col\""); + testCreateSortedTableWithQuotedIdentifierCasing("\"col\"", "\"col\""); + testCreateSortedTableWithQuotedIdentifierCasing("\"COL\"", "\"col\""); + } + + private void testCreateSortedTableWithQuotedIdentifierCasing(String columnName, String sortField) { String tableName = "test_create_sorted_table_with_quotes_" + randomNameSuffix(); assertUpdate(format("CREATE TABLE %s (%s bigint) WITH (sorted_by = ARRAY['%s'])", tableName, columnName, sortField)); dropTable(tableName); } - @DataProvider(name = "sortedTableWithQuotedIdentifierCasing") - public static Object[][] sortedTableWithQuotedIdentifierCasing() + @Test + public void testCreateSortedTableWithSortTransform() { - return new Object[][] { - {"col", "col"}, - {"COL", "col"}, - {"\"col\"", "col"}, - {"\"COL\"", "col"}, - {"col", "\"col\""}, - {"COL", "\"col\""}, - {"\"col\"", "\"col\""}, - {"\"COL\"", "\"col\""}, - }; + testCreateSortedTableWithSortTransform("col", "bucket(col, 3)"); + testCreateSortedTableWithSortTransform("col", "bucket(\"col\", 3)"); + testCreateSortedTableWithSortTransform("col", "truncate(col, 3)"); + testCreateSortedTableWithSortTransform("col", "year(col)"); + testCreateSortedTableWithSortTransform("col", "month(col)"); + testCreateSortedTableWithSortTransform("col", "date(col)"); + testCreateSortedTableWithSortTransform("col", "hour(col)"); } - @Test(dataProvider = "sortedTableWithSortTransform") - public void testCreateSortedTableWithSortTransform(String columnName, String sortField) + private void testCreateSortedTableWithSortTransform(String columnName, String sortField) { String tableName = "test_sort_with_transform_" + randomNameSuffix(); assertThatThrownBy(() -> query(format("CREATE TABLE %s (%s TIMESTAMP(6)) WITH (sorted_by = ARRAY['%s'])", tableName, columnName, sortField))) .hasMessageContaining("Unable to parse sort field"); } - @DataProvider(name = "sortedTableWithSortTransform") - public static Object[][] sortedTableWithSortTransform() - { - return new Object[][] { - {"col", "bucket(col, 3)"}, - {"col", "bucket(\"col\", 3)"}, - {"col", "truncate(col, 3)"}, - {"col", "year(col)"}, - {"col", "month(col)"}, - {"col", "date(col)"}, - {"col", "hour(col)"}, - }; - } - @Test public void testSortOrderChange() { @@ -3042,7 +3031,13 @@ public void testTruncateTextTransform() dropTable("test_truncate_text_transform"); } - @Test(dataProvider = "truncateNumberTypesProvider") + @Test + public void testTruncateIntegerTransform() + { + testTruncateIntegerTransform("integer"); + testTruncateIntegerTransform("bigint"); + } + public void testTruncateIntegerTransform(String dataType) { String table = format("test_truncate_%s_transform", dataType); @@ -3136,15 +3131,6 @@ public void testTruncateIntegerTransform(String dataType) dropTable(table); } - @DataProvider - public Object[][] truncateNumberTypesProvider() - { - return new Object[][] { - {"integer"}, - {"bigint"}, - }; - } - @Test public void testTruncateDecimalTransform() { @@ -3830,8 +3816,14 @@ public void testPredicatesWithStructuralTypes() dropTable(tableName); } - @Test(dataProviderClass = DataProviders.class, dataProvider = "trueFalse") - public void testPartitionsTableWithColumnNameConflict(boolean partitioned) + @Test + public void testPartitionsTableWithColumnNameConflict() + { + testPartitionsTableWithColumnNameConflict(true); + testPartitionsTableWithColumnNameConflict(false); + } + + private void testPartitionsTableWithColumnNameConflict(boolean partitioned) { assertUpdate("DROP TABLE IF EXISTS test_partitions_with_conflict"); assertUpdate("CREATE TABLE test_partitions_with_conflict (" + @@ -4546,35 +4538,34 @@ public void testAllAvailableTypes() assertUpdate("DROP TABLE test_all_types"); } - @Test(dataProvider = "repartitioningDataProvider") - public void testRepartitionDataOnCtas(Session session, String partitioning, int expectedFiles) - { - testRepartitionData(session, "tpch.tiny.orders", true, partitioning, expectedFiles); - } - - @Test(dataProvider = "repartitioningDataProvider") - public void testRepartitionDataOnInsert(Session session, String partitioning, int expectedFiles) + @Test + public void testRepartitionDataOnCtas() { - testRepartitionData(session, "tpch.tiny.orders", false, partitioning, expectedFiles); + // identity partitioning column + testRepartitionData(getSession(), "tpch.tiny.orders", true, "'orderstatus'", 3); + // bucketing + testRepartitionData(getSession(), "tpch.tiny.orders", true, "'bucket(custkey, 13)'", 13); + // varchar-based + testRepartitionData(getSession(), "tpch.tiny.orders", true, "'truncate(comment, 1)'", 35); + // complex; would exceed 100 open writers limit in IcebergPageSink without write repartitioning + testRepartitionData(getSession(), "tpch.tiny.orders", true, "'bucket(custkey, 4)', 'truncate(comment, 1)'", 131); + // same column multiple times + testRepartitionData(getSession(), "tpch.tiny.orders", true, "'truncate(comment, 1)', 'orderstatus', 'bucket(comment, 2)'", 180); } - @DataProvider - public Object[][] repartitioningDataProvider() + @Test + public void testRepartitionDataOnInsert() { - Session defaultSession = getSession(); - - return new Object[][] { - // identity partitioning column - {defaultSession, "'orderstatus'", 3}, - // bucketing - {defaultSession, "'bucket(custkey, 13)'", 13}, - // varchar-based - {defaultSession, "'truncate(comment, 1)'", 35}, - // complex; would exceed 100 open writers limit in IcebergPageSink without write repartitioning - {defaultSession, "'bucket(custkey, 4)', 'truncate(comment, 1)'", 131}, - // same column multiple times - {defaultSession, "'truncate(comment, 1)', 'orderstatus', 'bucket(comment, 2)'", 180}, - }; + // identity partitioning column + testRepartitionData(getSession(), "tpch.tiny.orders", false, "'orderstatus'", 3); + // bucketing + testRepartitionData(getSession(), "tpch.tiny.orders", false, "'bucket(custkey, 13)'", 13); + // varchar-based + testRepartitionData(getSession(), "tpch.tiny.orders", false, "'truncate(comment, 1)'", 35); + // complex; would exceed 100 open writers limit in IcebergPageSink without write repartitioning + testRepartitionData(getSession(), "tpch.tiny.orders", false, "'bucket(custkey, 4)', 'truncate(comment, 1)'", 131); + // same column multiple times + testRepartitionData(getSession(), "tpch.tiny.orders", false, "'truncate(comment, 1)', 'orderstatus', 'bucket(comment, 2)'", 180); } @Test @@ -4664,32 +4655,34 @@ private void testRepartitionData(Session session, String sourceRelation, boolean assertUpdate(session, "DROP TABLE " + tableName); } - @Test(dataProvider = "testDataMappingSmokeTestDataProvider") - public void testSplitPruningForFilterOnNonPartitionColumn(DataMappingTestSetup testSetup) + @Test + public void testSplitPruningForFilterOnNonPartitionColumn() { - if (testSetup.isUnsupportedType()) { - return; - } - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_split_pruning_non_partitioned", "(row_id int, col " + testSetup.getTrinoTypeName() + ")")) { - String tableName = table.getName(); - String sampleValue = testSetup.getSampleValueLiteral(); - String highValue = testSetup.getHighValueLiteral(); - // Insert separately to ensure two files with one value each - assertUpdate("INSERT INTO " + tableName + " VALUES (1, " + sampleValue + ")", 1); - assertUpdate("INSERT INTO " + tableName + " VALUES (2, " + highValue + ")", 1); - assertQuery("select count(*) from \"" + tableName + "$files\"", "VALUES 2"); - - int expectedSplitCount = supportsIcebergFileStatistics(testSetup.getTrinoTypeName()) ? 1 : 2; - verifySplitCount("SELECT row_id FROM " + tableName, 2); - verifySplitCount("SELECT row_id FROM " + tableName + " WHERE col = " + sampleValue, expectedSplitCount); - verifySplitCount("SELECT row_id FROM " + tableName + " WHERE col = " + highValue, expectedSplitCount); - - // ORC max timestamp statistics are truncated to millisecond precision and then appended with 999 microseconds. - // Therefore, sampleValue and highValue are within the max timestamp & there will be 2 splits. - verifySplitCount("SELECT row_id FROM " + tableName + " WHERE col > " + sampleValue, - (format == ORC && testSetup.getTrinoTypeName().contains("timestamp") ? 2 : expectedSplitCount)); - verifySplitCount("SELECT row_id FROM " + tableName + " WHERE col < " + highValue, - (format == ORC && testSetup.getTrinoTypeName().contains("timestamp(6)") ? 2 : expectedSplitCount)); + for (DataMappingTestSetup testSetup : testDataMappingSmokeTestDataProvider()) { + if (testSetup.isUnsupportedType()) { + return; + } + try (TestTable table = new TestTable(getQueryRunner()::execute, "test_split_pruning_non_partitioned", "(row_id int, col " + testSetup.getTrinoTypeName() + ")")) { + String tableName = table.getName(); + String sampleValue = testSetup.getSampleValueLiteral(); + String highValue = testSetup.getHighValueLiteral(); + // Insert separately to ensure two files with one value each + assertUpdate("INSERT INTO " + tableName + " VALUES (1, " + sampleValue + ")", 1); + assertUpdate("INSERT INTO " + tableName + " VALUES (2, " + highValue + ")", 1); + assertQuery("select count(*) from \"" + tableName + "$files\"", "VALUES 2"); + + int expectedSplitCount = supportsIcebergFileStatistics(testSetup.getTrinoTypeName()) ? 1 : 2; + verifySplitCount("SELECT row_id FROM " + tableName, 2); + verifySplitCount("SELECT row_id FROM " + tableName + " WHERE col = " + sampleValue, expectedSplitCount); + verifySplitCount("SELECT row_id FROM " + tableName + " WHERE col = " + highValue, expectedSplitCount); + + // ORC max timestamp statistics are truncated to millisecond precision and then appended with 999 microseconds. + // Therefore, sampleValue and highValue are within the max timestamp & there will be 2 splits. + verifySplitCount("SELECT row_id FROM " + tableName + " WHERE col > " + sampleValue, + (format == ORC && testSetup.getTrinoTypeName().contains("timestamp") ? 2 : expectedSplitCount)); + verifySplitCount("SELECT row_id FROM " + tableName + " WHERE col < " + highValue, + (format == ORC && testSetup.getTrinoTypeName().contains("timestamp(6)") ? 2 : expectedSplitCount)); + } } } @@ -4712,28 +4705,30 @@ protected void verifyIcebergTableProperties(MaterializedResult actual) protected abstract boolean supportsIcebergFileStatistics(String typeName); - @Test(dataProvider = "testDataMappingSmokeTestDataProvider") - public void testSplitPruningFromDataFileStatistics(DataMappingTestSetup testSetup) + @Test + public void testSplitPruningFromDataFileStatistics() { - if (testSetup.isUnsupportedType()) { - return; - } - try (TestTable table = new TestTable( - getQueryRunner()::execute, - "test_split_pruning_data_file_statistics", - // Random double is needed to make sure rows are different. Otherwise compression may deduplicate rows, resulting in only one row group - "(col " + testSetup.getTrinoTypeName() + ", r double)")) { - String tableName = table.getName(); - String values = - Stream.concat( - nCopies(100, testSetup.getSampleValueLiteral()).stream(), - nCopies(100, testSetup.getHighValueLiteral()).stream()) - .map(value -> "(" + value + ", rand())") - .collect(joining(", ")); - assertUpdate(withSmallRowGroups(getSession()), "INSERT INTO " + tableName + " VALUES " + values, 200); - - String query = "SELECT * FROM " + tableName + " WHERE col = " + testSetup.getSampleValueLiteral(); - verifyPredicatePushdownDataRead(query, supportsRowGroupStatistics(testSetup.getTrinoTypeName())); + for (DataMappingTestSetup testSetup : testDataMappingSmokeTestDataProvider()) { + if (testSetup.isUnsupportedType()) { + return; + } + try (TestTable table = new TestTable( + getQueryRunner()::execute, + "test_split_pruning_data_file_statistics", + // Random double is needed to make sure rows are different. Otherwise compression may deduplicate rows, resulting in only one row group + "(col " + testSetup.getTrinoTypeName() + ", r double)")) { + String tableName = table.getName(); + String values = + Stream.concat( + nCopies(100, testSetup.getSampleValueLiteral()).stream(), + nCopies(100, testSetup.getHighValueLiteral()).stream()) + .map(value -> "(" + value + ", rand())") + .collect(joining(", ")); + assertUpdate(withSmallRowGroups(getSession()), "INSERT INTO " + tableName + " VALUES " + values, 200); + + String query = "SELECT * FROM " + tableName + " WHERE col = " + testSetup.getSampleValueLiteral(); + verifyPredicatePushdownDataRead(query, supportsRowGroupStatistics(testSetup.getTrinoTypeName())); + } } } @@ -4919,115 +4914,123 @@ public void testProjectionPushdownOnPartitionedTableWithComments() assertUpdate("DROP TABLE IF EXISTS test_projection_pushdown_comments"); } - @Test(dataProvider = "tableFormatVersion") - public void testOptimize(int formatVersion) + @Test + public void testOptimize() throws Exception { - String tableName = "test_optimize_" + randomNameSuffix(); - assertUpdate("CREATE TABLE " + tableName + " (key integer, value varchar) WITH (format_version = " + formatVersion + ")"); + for (int formatVersion = IcebergConfig.FORMAT_VERSION_SUPPORT_MIN; formatVersion < IcebergConfig.FORMAT_VERSION_SUPPORT_MAX; formatVersion++) { + String tableName = "test_optimize_" + randomNameSuffix(); + assertUpdate("CREATE TABLE " + tableName + " (key integer, value varchar) WITH (format_version = " + formatVersion + ")"); - // DistributedQueryRunner sets node-scheduler.include-coordinator by default, so include coordinator - int workerCount = getQueryRunner().getNodeCount(); + // DistributedQueryRunner sets node-scheduler.include-coordinator by default, so include coordinator + int workerCount = getQueryRunner().getNodeCount(); - // optimize an empty table - assertQuerySucceeds(withSingleWriterPerTask(getSession()), "ALTER TABLE " + tableName + " EXECUTE OPTIMIZE"); - assertThat(getActiveFiles(tableName)).isEmpty(); + // optimize an empty table + assertQuerySucceeds(withSingleWriterPerTask(getSession()), "ALTER TABLE " + tableName + " EXECUTE OPTIMIZE"); + assertThat(getActiveFiles(tableName)).isEmpty(); - assertUpdate("INSERT INTO " + tableName + " VALUES (11, 'eleven')", 1); - assertUpdate("INSERT INTO " + tableName + " VALUES (12, 'zwölf')", 1); - assertUpdate("INSERT INTO " + tableName + " VALUES (13, 'trzynaście')", 1); - assertUpdate("INSERT INTO " + tableName + " VALUES (14, 'quatorze')", 1); - assertUpdate("INSERT INTO " + tableName + " VALUES (15, 'пʼятнадцять')", 1); + assertUpdate("INSERT INTO " + tableName + " VALUES (11, 'eleven')", 1); + assertUpdate("INSERT INTO " + tableName + " VALUES (12, 'zwölf')", 1); + assertUpdate("INSERT INTO " + tableName + " VALUES (13, 'trzynaście')", 1); + assertUpdate("INSERT INTO " + tableName + " VALUES (14, 'quatorze')", 1); + assertUpdate("INSERT INTO " + tableName + " VALUES (15, 'пʼятнадцять')", 1); - List initialFiles = getActiveFiles(tableName); - assertThat(initialFiles) - .hasSize(5) - // Verify we have sufficiently many test rows with respect to worker count. - .hasSizeGreaterThan(workerCount); + List initialFiles = getActiveFiles(tableName); + assertThat(initialFiles) + .hasSize(5) + // Verify we have sufficiently many test rows with respect to worker count. + .hasSizeGreaterThan(workerCount); - // For optimize we need to set task_min_writer_count to 1, otherwise it will create more than one file. - computeActual(withSingleWriterPerTask(getSession()), "ALTER TABLE " + tableName + " EXECUTE OPTIMIZE"); - assertThat(query("SELECT sum(key), listagg(value, ' ') WITHIN GROUP (ORDER BY key) FROM " + tableName)) - .matches("VALUES (BIGINT '65', VARCHAR 'eleven zwölf trzynaście quatorze пʼятнадцять')"); - List updatedFiles = getActiveFiles(tableName); - assertThat(updatedFiles) - .hasSizeBetween(1, workerCount) - .doesNotContainAnyElementsOf(initialFiles); - // No files should be removed (this is expire_snapshots's job, when it exists) - assertThat(getAllDataFilesFromTableDirectory(tableName)) - .containsExactlyInAnyOrderElementsOf(concat(initialFiles, updatedFiles)); + // For optimize we need to set task_min_writer_count to 1, otherwise it will create more than one file. + computeActual(withSingleWriterPerTask(getSession()), "ALTER TABLE " + tableName + " EXECUTE OPTIMIZE"); + assertThat(query("SELECT sum(key), listagg(value, ' ') WITHIN GROUP (ORDER BY key) FROM " + tableName)) + .matches("VALUES (BIGINT '65', VARCHAR 'eleven zwölf trzynaście quatorze пʼятнадцять')"); + List updatedFiles = getActiveFiles(tableName); + assertThat(updatedFiles) + .hasSizeBetween(1, workerCount) + .doesNotContainAnyElementsOf(initialFiles); + // No files should be removed (this is expire_snapshots's job, when it exists) + assertThat(getAllDataFilesFromTableDirectory(tableName)) + .containsExactlyInAnyOrderElementsOf(concat(initialFiles, updatedFiles)); + + // optimize with low retention threshold, nothing should change + // For optimize we need to set task_min_writer_count to 1, otherwise it will create more than one file. + computeActual(withSingleWriterPerTask(getSession()), "ALTER TABLE " + tableName + " EXECUTE OPTIMIZE (file_size_threshold => '33B')"); + assertThat(query("SELECT sum(key), listagg(value, ' ') WITHIN GROUP (ORDER BY key) FROM " + tableName)) + .matches("VALUES (BIGINT '65', VARCHAR 'eleven zwölf trzynaście quatorze пʼятнадцять')"); + assertThat(getActiveFiles(tableName)).isEqualTo(updatedFiles); + assertThat(getAllDataFilesFromTableDirectory(tableName)) + .containsExactlyInAnyOrderElementsOf(concat(initialFiles, updatedFiles)); - // optimize with low retention threshold, nothing should change - // For optimize we need to set task_min_writer_count to 1, otherwise it will create more than one file. - computeActual(withSingleWriterPerTask(getSession()), "ALTER TABLE " + tableName + " EXECUTE OPTIMIZE (file_size_threshold => '33B')"); - assertThat(query("SELECT sum(key), listagg(value, ' ') WITHIN GROUP (ORDER BY key) FROM " + tableName)) - .matches("VALUES (BIGINT '65', VARCHAR 'eleven zwölf trzynaście quatorze пʼятнадцять')"); - assertThat(getActiveFiles(tableName)).isEqualTo(updatedFiles); - assertThat(getAllDataFilesFromTableDirectory(tableName)) - .containsExactlyInAnyOrderElementsOf(concat(initialFiles, updatedFiles)); - - // optimize with delimited procedure name - assertQueryFails("ALTER TABLE " + tableName + " EXECUTE \"optimize\"", "Table procedure not registered: optimize"); - assertUpdate("ALTER TABLE " + tableName + " EXECUTE \"OPTIMIZE\""); - // optimize with delimited parameter name (and procedure name) - assertUpdate("ALTER TABLE " + tableName + " EXECUTE \"OPTIMIZE\" (\"file_size_threshold\" => '33B')"); // TODO (https://github.com/trinodb/trino/issues/11326) this should fail - assertUpdate("ALTER TABLE " + tableName + " EXECUTE \"OPTIMIZE\" (\"FILE_SIZE_THRESHOLD\" => '33B')"); - assertUpdate("DROP TABLE " + tableName); + // optimize with delimited procedure name + assertQueryFails("ALTER TABLE " + tableName + " EXECUTE \"optimize\"", "Table procedure not registered: optimize"); + assertUpdate("ALTER TABLE " + tableName + " EXECUTE \"OPTIMIZE\""); + // optimize with delimited parameter name (and procedure name) + assertUpdate("ALTER TABLE " + tableName + " EXECUTE \"OPTIMIZE\" (\"file_size_threshold\" => '33B')"); // TODO (https://github.com/trinodb/trino/issues/11326) this should fail + assertUpdate("ALTER TABLE " + tableName + " EXECUTE \"OPTIMIZE\" (\"FILE_SIZE_THRESHOLD\" => '33B')"); + assertUpdate("DROP TABLE " + tableName); + } } - @Test(dataProvider = "tableFormatVersion") - public void testOptimizeForPartitionedTable(int formatVersion) + @Test + public void testOptimizeForPartitionedTable() throws IOException { - // This test will have its own session to make sure partitioning is indeed forced and is not a result - // of session configuration - Session session = testSessionBuilder() - .setCatalog(getQueryRunner().getDefaultSession().getCatalog()) - .setSchema(getQueryRunner().getDefaultSession().getSchema()) - .setSystemProperty("use_preferred_write_partitioning", "true") - .build(); - String tableName = "test_repartitiong_during_optimize_" + randomNameSuffix(); - assertUpdate(session, "CREATE TABLE " + tableName + " (key varchar, value integer) WITH (format_version = " + formatVersion + ", partitioning = ARRAY['key'])"); - // optimize an empty table - assertQuerySucceeds(withSingleWriterPerTask(session), "ALTER TABLE " + tableName + " EXECUTE OPTIMIZE"); - - assertUpdate(session, "INSERT INTO " + tableName + " VALUES ('one', 1)", 1); - assertUpdate(session, "INSERT INTO " + tableName + " VALUES ('one', 2)", 1); - assertUpdate(session, "INSERT INTO " + tableName + " VALUES ('one', 3)", 1); - assertUpdate(session, "INSERT INTO " + tableName + " VALUES ('one', 4)", 1); - assertUpdate(session, "INSERT INTO " + tableName + " VALUES ('one', 5)", 1); - assertUpdate(session, "INSERT INTO " + tableName + " VALUES ('one', 6)", 1); - assertUpdate(session, "INSERT INTO " + tableName + " VALUES ('one', 7)", 1); - assertUpdate(session, "INSERT INTO " + tableName + " VALUES ('two', 8)", 1); - assertUpdate(session, "INSERT INTO " + tableName + " VALUES ('two', 9)", 1); - assertUpdate(session, "INSERT INTO " + tableName + " VALUES ('three', 10)", 1); + for (int formatVersion = IcebergConfig.FORMAT_VERSION_SUPPORT_MIN; formatVersion < IcebergConfig.FORMAT_VERSION_SUPPORT_MAX; formatVersion++) { + // This test will have its own session to make sure partitioning is indeed forced and is not a result + // of session configuration + Session session = testSessionBuilder() + .setCatalog(getQueryRunner().getDefaultSession().getCatalog()) + .setSchema(getQueryRunner().getDefaultSession().getSchema()) + .setSystemProperty("use_preferred_write_partitioning", "true") + .build(); + String tableName = "test_repartitiong_during_optimize_" + randomNameSuffix(); + assertUpdate(session, "CREATE TABLE " + tableName + " (key varchar, value integer) WITH (format_version = " + formatVersion + ", partitioning = ARRAY['key'])"); + // optimize an empty table + assertQuerySucceeds(withSingleWriterPerTask(session), "ALTER TABLE " + tableName + " EXECUTE OPTIMIZE"); + + assertUpdate(session, "INSERT INTO " + tableName + " VALUES ('one', 1)", 1); + assertUpdate(session, "INSERT INTO " + tableName + " VALUES ('one', 2)", 1); + assertUpdate(session, "INSERT INTO " + tableName + " VALUES ('one', 3)", 1); + assertUpdate(session, "INSERT INTO " + tableName + " VALUES ('one', 4)", 1); + assertUpdate(session, "INSERT INTO " + tableName + " VALUES ('one', 5)", 1); + assertUpdate(session, "INSERT INTO " + tableName + " VALUES ('one', 6)", 1); + assertUpdate(session, "INSERT INTO " + tableName + " VALUES ('one', 7)", 1); + assertUpdate(session, "INSERT INTO " + tableName + " VALUES ('two', 8)", 1); + assertUpdate(session, "INSERT INTO " + tableName + " VALUES ('two', 9)", 1); + assertUpdate(session, "INSERT INTO " + tableName + " VALUES ('three', 10)", 1); + + List initialFiles = getActiveFiles(tableName); + assertThat(initialFiles).hasSize(10); - List initialFiles = getActiveFiles(tableName); - assertThat(initialFiles).hasSize(10); + // For optimize we need to set task_min_writer_count to 1, otherwise it will create more than one file. + computeActual(withSingleWriterPerTask(session), "ALTER TABLE " + tableName + " EXECUTE OPTIMIZE"); - // For optimize we need to set task_min_writer_count to 1, otherwise it will create more than one file. - computeActual(withSingleWriterPerTask(session), "ALTER TABLE " + tableName + " EXECUTE OPTIMIZE"); + assertThat(query(session, "SELECT sum(value), listagg(key, ' ') WITHIN GROUP (ORDER BY key) FROM " + tableName)) + .matches("VALUES (BIGINT '55', VARCHAR 'one one one one one one one three two two')"); - assertThat(query(session, "SELECT sum(value), listagg(key, ' ') WITHIN GROUP (ORDER BY key) FROM " + tableName)) - .matches("VALUES (BIGINT '55', VARCHAR 'one one one one one one one three two two')"); + List updatedFiles = getActiveFiles(tableName); + // as we force repartitioning there should be only 3 partitions + assertThat(updatedFiles).hasSize(3); + assertThat(getAllDataFilesFromTableDirectory(tableName)).containsExactlyInAnyOrderElementsOf(concat(initialFiles, updatedFiles)); - List updatedFiles = getActiveFiles(tableName); - // as we force repartitioning there should be only 3 partitions - assertThat(updatedFiles).hasSize(3); - assertThat(getAllDataFilesFromTableDirectory(tableName)).containsExactlyInAnyOrderElementsOf(concat(initialFiles, updatedFiles)); - - assertUpdate("DROP TABLE " + tableName); + assertUpdate("DROP TABLE " + tableName); + } } - @DataProvider - public Object[][] tableFormatVersion() + @Test() + public void testOptimizeTimePartitionedTable() { - return IntStream.rangeClosed(IcebergConfig.FORMAT_VERSION_SUPPORT_MIN, IcebergConfig.FORMAT_VERSION_SUPPORT_MAX).boxed() - .collect(DataProviders.toDataProvider()); + testOptimizeTimePartitionedTable("date", "%s", 15); + testOptimizeTimePartitionedTable("date", "day(%s)", 15); + testOptimizeTimePartitionedTable("date", "month(%s)", 3); + testOptimizeTimePartitionedTable("timestamp(6)", "day(%s)", 15); + testOptimizeTimePartitionedTable("timestamp(6)", "month(%s)", 3); + testOptimizeTimePartitionedTable("timestamp(6) with time zone", "day(%s)", 15); + testOptimizeTimePartitionedTable("timestamp(6) with time zone", "month(%s)", 3); } - @Test(dataProvider = "testOptimizeTimePartitionedTableDataProvider") - public void testOptimizeTimePartitionedTable(String dataType, String partitioningFormat, int expectedFilesAfterOptimize) + private void testOptimizeTimePartitionedTable(String dataType, String partitioningFormat, int expectedFilesAfterOptimize) { String tableName = "test_optimize_time_partitioned_" + (dataType + "_" + partitioningFormat).toLowerCase(Locale.ENGLISH).replaceAll("[^a-z0-9_]", ""); @@ -5098,20 +5101,6 @@ public void testOptimizeTimePartitionedTable(String dataType, String partitionin assertUpdate("DROP TABLE " + tableName); } - @DataProvider - public static Object[][] testOptimizeTimePartitionedTableDataProvider() - { - return new Object[][] { - {"date", "%s", 15}, - {"date", "day(%s)", 15}, - {"date", "month(%s)", 3}, - {"timestamp(6)", "day(%s)", 15}, - {"timestamp(6)", "month(%s)", 3}, - {"timestamp(6) with time zone", "day(%s)", 15}, - {"timestamp(6) with time zone", "month(%s)", 3}, - }; - } - @Test public void testOptimizeTableAfterDeleteWithFormatVersion2() { @@ -6398,8 +6387,22 @@ public void testMergeSimpleSelectPartitioned() assertUpdate("DROP TABLE " + targetTable); } - @Test(dataProvider = "partitionedAndBucketedProvider") - public void testMergeUpdateWithVariousLayouts(int writers, String partitioning) + @Test + public void testMergeUpdateWithVariousLayouts() + { + testMergeUpdateWithVariousLayouts(1, ""); + testMergeUpdateWithVariousLayouts(4, ""); + testMergeUpdateWithVariousLayouts(1, "WITH (partitioning = ARRAY['customer'])"); + testMergeUpdateWithVariousLayouts(4, "WITH (partitioning = ARRAY['customer'])"); + testMergeUpdateWithVariousLayouts(1, "WITH (partitioning = ARRAY['purchase'])"); + testMergeUpdateWithVariousLayouts(4, "WITH (partitioning = ARRAY['purchase'])"); + testMergeUpdateWithVariousLayouts(1, "WITH (partitioning = ARRAY['bucket(customer, 3)'])"); + testMergeUpdateWithVariousLayouts(4, "WITH (partitioning = ARRAY['bucket(customer, 3)'])"); + testMergeUpdateWithVariousLayouts(1, "WITH (partitioning = ARRAY['bucket(purchase, 4)'])"); + testMergeUpdateWithVariousLayouts(4, "WITH (partitioning = ARRAY['bucket(purchase, 4)'])"); + } + + private void testMergeUpdateWithVariousLayouts(int writers, String partitioning) { Session session = Session.builder(getSession()) .setSystemProperty(TASK_MIN_WRITER_COUNT, String.valueOf(writers)) @@ -6428,28 +6431,21 @@ public void testMergeUpdateWithVariousLayouts(int writers, String partitioning) assertUpdate("DROP TABLE " + targetTable); } - @DataProvider - public Object[][] partitionedAndBucketedProvider() + @Test + public void testMergeMultipleOperations() { - List writerCounts = ImmutableList.of(1, 4); - List partitioningTypes = ImmutableList.builder() - .add("") - .add("WITH (partitioning = ARRAY['customer'])") - .add("WITH (partitioning = ARRAY['purchase'])") - .add("WITH (partitioning = ARRAY['bucket(customer, 3)'])") - .add("WITH (partitioning = ARRAY['bucket(purchase, 4)'])") - .build(); - - List data = new ArrayList<>(); - for (int writers : writerCounts) { - for (String partitioning : partitioningTypes) { - data.add(new Object[] {writers, partitioning}); - } - } - return data.toArray(Object[][]::new); + testMergeMultipleOperations(1, ""); + testMergeMultipleOperations(4, ""); + testMergeMultipleOperations(1, "WITH (partitioning = ARRAY['customer'])"); + testMergeMultipleOperations(4, "WITH (partitioning = ARRAY['customer'])"); + testMergeMultipleOperations(1, "WITH (partitioning = ARRAY['purchase'])"); + testMergeMultipleOperations(4, "WITH (partitioning = ARRAY['purchase'])"); + testMergeMultipleOperations(1, "WITH (partitioning = ARRAY['bucket(customer, 3)'])"); + testMergeMultipleOperations(4, "WITH (partitioning = ARRAY['bucket(customer, 3)'])"); + testMergeMultipleOperations(1, "WITH (partitioning = ARRAY['bucket(purchase, 4)'])"); + testMergeMultipleOperations(4, "WITH (partitioning = ARRAY['bucket(purchase, 4)'])"); } - @Test(dataProvider = "partitionedAndBucketedProvider") public void testMergeMultipleOperations(int writers, String partitioning) { Session session = Session.builder(getSession()) @@ -6541,8 +6537,17 @@ public void testMergeSimpleQueryPartitioned() assertUpdate("DROP TABLE " + targetTable); } - @Test(dataProvider = "partitionedBucketedFailure") - public void testMergeMultipleRowsMatchFails(String createTableSql) + @Test + public void testMergeMultipleRowsMatchFails() + { + testMergeMultipleRowsMatchFails("CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR)"); + testMergeMultipleRowsMatchFails("CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (partitioning = ARRAY['bucket(customer, 3)'])"); + testMergeMultipleRowsMatchFails("CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (partitioning = ARRAY['customer'])"); + testMergeMultipleRowsMatchFails("CREATE TABLE %s (customer VARCHAR, address VARCHAR, purchases INT) WITH (partitioning = ARRAY['address'])"); + testMergeMultipleRowsMatchFails("CREATE TABLE %s (purchases INT, customer VARCHAR, address VARCHAR) WITH (partitioning = ARRAY['address', 'customer'])"); + } + + private void testMergeMultipleRowsMatchFails(String createTableSql) { String targetTable = "merge_multiple_target_" + randomNameSuffix(); String sourceTable = "merge_multiple_source_" + randomNameSuffix(); @@ -6566,20 +6571,36 @@ public void testMergeMultipleRowsMatchFails(String createTableSql) assertUpdate("DROP TABLE " + targetTable); } - @DataProvider - public Object[][] partitionedBucketedFailure() - { - return new Object[][] { - {"CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR)"}, - {"CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (partitioning = ARRAY['bucket(customer, 3)'])"}, - {"CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (partitioning = ARRAY['customer'])"}, - {"CREATE TABLE %s (customer VARCHAR, address VARCHAR, purchases INT) WITH (partitioning = ARRAY['address'])"}, - {"CREATE TABLE %s (purchases INT, customer VARCHAR, address VARCHAR) WITH (partitioning = ARRAY['address', 'customer'])"} - }; - } - - @Test(dataProvider = "targetAndSourceWithDifferentPartitioning") - public void testMergeWithDifferentPartitioning(String testDescription, String createTargetTableSql, String createSourceTableSql) + @Test + public void testMergeWithDifferentPartitioning() + { + testMergeWithDifferentPartitioning( + "target_partitioned_source_and_target_partitioned_and_bucketed", + "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (partitioning = ARRAY['address', 'bucket(customer, 3)'])", + "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (partitioning = ARRAY['address', 'bucket(customer, 3)'])"); + testMergeWithDifferentPartitioning( + "target_flat_source_partitioned_by_customer", + "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR)", + "CREATE TABLE %s (purchases INT, address VARCHAR, customer VARCHAR) WITH (partitioning = ARRAY['customer'])"); + testMergeWithDifferentPartitioning( + "target_partitioned_by_customer_source_flat", + "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (partitioning = ARRAY['customer'])", + "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR)"); + testMergeWithDifferentPartitioning( + "target_bucketed_by_customer_source_flat", + "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (partitioning = ARRAY['bucket(customer, 3)'])", + "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR)"); + testMergeWithDifferentPartitioning( + "target_partitioned_source_partitioned_and_bucketed", + "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (partitioning = ARRAY['customer'])", + "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (partitioning = ARRAY['address', 'bucket(customer, 3)'])"); + testMergeWithDifferentPartitioning( + "target_partitioned_target_partitioned_and_bucketed", + "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (partitioning = ARRAY['address', 'bucket(customer, 3)'])", + "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (partitioning = ARRAY['customer'])"); + } + + private void testMergeWithDifferentPartitioning(String testDescription, String createTargetTableSql, String createSourceTableSql) { String targetTable = format("%s_target_%s", testDescription, randomNameSuffix()); String sourceTable = format("%s_source_%s", testDescription, randomNameSuffix()); @@ -6604,43 +6625,6 @@ public void testMergeWithDifferentPartitioning(String testDescription, String cr assertUpdate("DROP TABLE " + targetTable); } - @DataProvider - public Object[][] targetAndSourceWithDifferentPartitioning() - { - return new Object[][] { - { - "target_partitioned_source_and_target_partitioned_and_bucketed", - "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (partitioning = ARRAY['address', 'bucket(customer, 3)'])", - "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (partitioning = ARRAY['address', 'bucket(customer, 3)'])", - }, - { - "target_flat_source_partitioned_by_customer", - "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR)", - "CREATE TABLE %s (purchases INT, address VARCHAR, customer VARCHAR) WITH (partitioning = ARRAY['customer'])" - }, - { - "target_partitioned_by_customer_source_flat", - "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (partitioning = ARRAY['customer'])", - "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR)", - }, - { - "target_bucketed_by_customer_source_flat", - "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (partitioning = ARRAY['bucket(customer, 3)'])", - "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR)", - }, - { - "target_partitioned_source_partitioned_and_bucketed", - "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (partitioning = ARRAY['customer'])", - "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (partitioning = ARRAY['address', 'bucket(customer, 3)'])", - }, - { - "target_partitioned_target_partitioned_and_bucketed", - "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (partitioning = ARRAY['address', 'bucket(customer, 3)'])", - "CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (partitioning = ARRAY['customer'])", - } - }; - } - @Override protected OptionalInt maxSchemaNameLength() { @@ -7299,38 +7283,41 @@ protected void verifyTableNameLengthFailurePermissible(Throwable e) assertThat(e).hasMessageMatching("Table name must be shorter than or equal to '128' characters but got .*"); } - @Test(dataProvider = "testTimestampPrecisionOnCreateTableAsSelect") - public void testTimestampPrecisionOnCreateTableAsSelect(TimestampPrecisionTestSetup setup) + @Test + public void testTimestampPrecisionOnCreateTableAsSelect() { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, - "test_coercion_show_create_table", - format("AS SELECT %s a", setup.sourceValueLiteral))) { - assertEquals(getColumnType(testTable.getName(), "a"), setup.newColumnType); - assertQuery( - format("SELECT * FROM %s", testTable.getName()), - format("VALUES (%s)", setup.newValueLiteral)); + for (TimestampPrecisionTestSetup setup : timestampPrecisionOnCreateTableAsSelectProvider()) { + try (TestTable testTable = new TestTable( + getQueryRunner()::execute, + "test_coercion_show_create_table", + format("AS SELECT %s a", setup.sourceValueLiteral))) { + assertEquals(getColumnType(testTable.getName(), "a"), setup.newColumnType); + assertQuery( + format("SELECT * FROM %s", testTable.getName()), + format("VALUES (%s)", setup.newValueLiteral)); + } } } - @Test(dataProvider = "testTimestampPrecisionOnCreateTableAsSelect") - public void testTimestampPrecisionOnCreateTableAsSelectWithNoData(TimestampPrecisionTestSetup setup) + @Test + public void testTimestampPrecisionOnCreateTableAsSelectWithNoData() { - try (TestTable testTable = new TestTable( - getQueryRunner()::execute, - "test_coercion_show_create_table", - format("AS SELECT %s a WITH NO DATA", setup.sourceValueLiteral))) { - assertEquals(getColumnType(testTable.getName(), "a"), setup.newColumnType); + for (TimestampPrecisionTestSetup setup : timestampPrecisionOnCreateTableAsSelectProvider()) { + try (TestTable testTable = new TestTable( + getQueryRunner()::execute, + "test_coercion_show_create_table", + format("AS SELECT %s a WITH NO DATA", setup.sourceValueLiteral))) { + assertEquals(getColumnType(testTable.getName(), "a"), setup.newColumnType); + } } } - @DataProvider(name = "testTimestampPrecisionOnCreateTableAsSelect") - public Object[][] timestampPrecisionOnCreateTableAsSelectProvider() + private List timestampPrecisionOnCreateTableAsSelectProvider() { return timestampPrecisionOnCreateTableAsSelectData().stream() .map(this::filterTimestampPrecisionOnCreateTableAsSelectProvider) .flatMap(Optional::stream) - .collect(toDataProvider()); + .collect(toList()); } protected Optional filterTimestampPrecisionOnCreateTableAsSelectProvider(TimestampPrecisionTestSetup setup) @@ -7384,8 +7371,37 @@ public TimestampPrecisionTestSetup withNewValueLiteral(String newValueLiteral) } } - @Test(dataProvider = "testTimePrecisionOnCreateTableAsSelect") - public void testTimePrecisionOnCreateTableAsSelect(String inputType, String tableType, String tableValue) + @Test + public void testTimePrecisionOnCreateTableAsSelect() + { + testTimePrecisionOnCreateTableAsSelect("TIME '00:00:00'", "time(6)", "TIME '00:00:00.000000'"); + testTimePrecisionOnCreateTableAsSelect("TIME '00:00:00.9'", "time(6)", "TIME '00:00:00.900000'"); + testTimePrecisionOnCreateTableAsSelect("TIME '00:00:00.56'", "time(6)", "TIME '00:00:00.560000'"); + testTimePrecisionOnCreateTableAsSelect("TIME '00:00:00.123'", "time(6)", "TIME '00:00:00.123000'"); + testTimePrecisionOnCreateTableAsSelect("TIME '00:00:00.4896'", "time(6)", "TIME '00:00:00.489600'"); + testTimePrecisionOnCreateTableAsSelect("TIME '00:00:00.89356'", "time(6)", "TIME '00:00:00.893560'"); + testTimePrecisionOnCreateTableAsSelect("TIME '00:00:00.123000'", "time(6)", "TIME '00:00:00.123000'"); + testTimePrecisionOnCreateTableAsSelect("TIME '00:00:00.999'", "time(6)", "TIME '00:00:00.999000'"); + testTimePrecisionOnCreateTableAsSelect("TIME '00:00:00.123456'", "time(6)", "TIME '00:00:00.123456'"); + testTimePrecisionOnCreateTableAsSelect("TIME '12:34:56.1'", "time(6)", "TIME '12:34:56.100000'"); + testTimePrecisionOnCreateTableAsSelect("TIME '12:34:56.9'", "time(6)", "TIME '12:34:56.900000'"); + testTimePrecisionOnCreateTableAsSelect("TIME '12:34:56.123'", "time(6)", "TIME '12:34:56.123000'"); + testTimePrecisionOnCreateTableAsSelect("TIME '12:34:56.123000'", "time(6)", "TIME '12:34:56.123000'"); + testTimePrecisionOnCreateTableAsSelect("TIME '12:34:56.999'", "time(6)", "TIME '12:34:56.999000'"); + testTimePrecisionOnCreateTableAsSelect("TIME '12:34:56.123456'", "time(6)", "TIME '12:34:56.123456'"); + testTimePrecisionOnCreateTableAsSelect("TIME '00:00:00.1234561'", "time(6)", "TIME '00:00:00.123456'"); + testTimePrecisionOnCreateTableAsSelect("TIME '00:00:00.123456499'", "time(6)", "TIME '00:00:00.123456'"); + testTimePrecisionOnCreateTableAsSelect("TIME '00:00:00.123456499999'", "time(6)", "TIME '00:00:00.123456'"); + testTimePrecisionOnCreateTableAsSelect("TIME '00:00:00.1234565'", "time(6)", "TIME '00:00:00.123457'"); + testTimePrecisionOnCreateTableAsSelect("TIME '00:00:00.111222333444'", "time(6)", "TIME '00:00:00.111222'"); + testTimePrecisionOnCreateTableAsSelect("TIME '00:00:00.9999995'", "time(6)", "TIME '00:00:01.000000'"); + testTimePrecisionOnCreateTableAsSelect("TIME '23:59:59.9999995'", "time(6)", "TIME '00:00:00.000000'"); + testTimePrecisionOnCreateTableAsSelect("TIME '23:59:59.9999995'", "time(6)", "TIME '00:00:00.000000'"); + testTimePrecisionOnCreateTableAsSelect("TIME '23:59:59.999999499999'", "time(6)", "TIME '23:59:59.999999'"); + testTimePrecisionOnCreateTableAsSelect("TIME '23:59:59.9999994'", "time(6)", "TIME '23:59:59.999999'"); + } + + private void testTimePrecisionOnCreateTableAsSelect(String inputType, String tableType, String tableValue) { try (TestTable testTable = new TestTable( getQueryRunner()::execute, @@ -7398,8 +7414,37 @@ public void testTimePrecisionOnCreateTableAsSelect(String inputType, String tabl } } - @Test(dataProvider = "testTimePrecisionOnCreateTableAsSelect") - public void testTimePrecisionOnCreateTableAsSelectWithNoData(String inputType, String tableType, String ignored) + @Test + public void testTimePrecisionOnCreateTableAsSelectWithNoData() + { + testTimePrecisionOnCreateTableAsSelectWithNoData("TIME '00:00:00'", "time(6)"); + testTimePrecisionOnCreateTableAsSelectWithNoData("TIME '00:00:00.9'", "time(6)"); + testTimePrecisionOnCreateTableAsSelectWithNoData("TIME '00:00:00.56'", "time(6)"); + testTimePrecisionOnCreateTableAsSelectWithNoData("TIME '00:00:00.123'", "time(6)"); + testTimePrecisionOnCreateTableAsSelectWithNoData("TIME '00:00:00.4896'", "time(6)"); + testTimePrecisionOnCreateTableAsSelectWithNoData("TIME '00:00:00.89356'", "time(6)"); + testTimePrecisionOnCreateTableAsSelectWithNoData("TIME '00:00:00.123000'", "time(6)"); + testTimePrecisionOnCreateTableAsSelectWithNoData("TIME '00:00:00.999'", "time(6)"); + testTimePrecisionOnCreateTableAsSelectWithNoData("TIME '00:00:00.123456'", "time(6)"); + testTimePrecisionOnCreateTableAsSelectWithNoData("TIME '12:34:56.1'", "time(6)"); + testTimePrecisionOnCreateTableAsSelectWithNoData("TIME '12:34:56.9'", "time(6)"); + testTimePrecisionOnCreateTableAsSelectWithNoData("TIME '12:34:56.123'", "time(6)"); + testTimePrecisionOnCreateTableAsSelectWithNoData("TIME '12:34:56.123000'", "time(6)"); + testTimePrecisionOnCreateTableAsSelectWithNoData("TIME '12:34:56.999'", "time(6)"); + testTimePrecisionOnCreateTableAsSelectWithNoData("TIME '12:34:56.123456'", "time(6)"); + testTimePrecisionOnCreateTableAsSelectWithNoData("TIME '00:00:00.1234561'", "time(6)"); + testTimePrecisionOnCreateTableAsSelectWithNoData("TIME '00:00:00.123456499'", "time(6)"); + testTimePrecisionOnCreateTableAsSelectWithNoData("TIME '00:00:00.123456499999'", "time(6)"); + testTimePrecisionOnCreateTableAsSelectWithNoData("TIME '00:00:00.1234565'", "time(6)"); + testTimePrecisionOnCreateTableAsSelectWithNoData("TIME '00:00:00.111222333444'", "time(6)"); + testTimePrecisionOnCreateTableAsSelectWithNoData("TIME '00:00:00.9999995'", "time(6)"); + testTimePrecisionOnCreateTableAsSelectWithNoData("TIME '23:59:59.9999995'", "time(6)"); + testTimePrecisionOnCreateTableAsSelectWithNoData("TIME '23:59:59.9999995'", "time(6)"); + testTimePrecisionOnCreateTableAsSelectWithNoData("TIME '23:59:59.999999499999'", "time(6)"); + testTimePrecisionOnCreateTableAsSelectWithNoData("TIME '23:59:59.9999994'", "time(6)"); + } + + private void testTimePrecisionOnCreateTableAsSelectWithNoData(String inputType, String tableType) { try (TestTable testTable = new TestTable( getQueryRunner()::execute, @@ -7409,37 +7454,6 @@ public void testTimePrecisionOnCreateTableAsSelectWithNoData(String inputType, S } } - @DataProvider(name = "testTimePrecisionOnCreateTableAsSelect") - public static Object[][] timePrecisionOnCreateTableAsSelectProvider() - { - return new Object[][] { - {"TIME '00:00:00'", "time(6)", "TIME '00:00:00.000000'"}, - {"TIME '00:00:00.9'", "time(6)", "TIME '00:00:00.900000'"}, - {"TIME '00:00:00.56'", "time(6)", "TIME '00:00:00.560000'"}, - {"TIME '00:00:00.123'", "time(6)", "TIME '00:00:00.123000'"}, - {"TIME '00:00:00.4896'", "time(6)", "TIME '00:00:00.489600'"}, - {"TIME '00:00:00.89356'", "time(6)", "TIME '00:00:00.893560'"}, - {"TIME '00:00:00.123000'", "time(6)", "TIME '00:00:00.123000'"}, - {"TIME '00:00:00.999'", "time(6)", "TIME '00:00:00.999000'"}, - {"TIME '00:00:00.123456'", "time(6)", "TIME '00:00:00.123456'"}, - {"TIME '12:34:56.1'", "time(6)", "TIME '12:34:56.100000'"}, - {"TIME '12:34:56.9'", "time(6)", "TIME '12:34:56.900000'"}, - {"TIME '12:34:56.123'", "time(6)", "TIME '12:34:56.123000'"}, - {"TIME '12:34:56.123000'", "time(6)", "TIME '12:34:56.123000'"}, - {"TIME '12:34:56.999'", "time(6)", "TIME '12:34:56.999000'"}, - {"TIME '12:34:56.123456'", "time(6)", "TIME '12:34:56.123456'"}, - {"TIME '00:00:00.1234561'", "time(6)", "TIME '00:00:00.123456'"}, - {"TIME '00:00:00.123456499'", "time(6)", "TIME '00:00:00.123456'"}, - {"TIME '00:00:00.123456499999'", "time(6)", "TIME '00:00:00.123456'"}, - {"TIME '00:00:00.1234565'", "time(6)", "TIME '00:00:00.123457'"}, - {"TIME '00:00:00.111222333444'", "time(6)", "TIME '00:00:00.111222'"}, - {"TIME '00:00:00.9999995'", "time(6)", "TIME '00:00:01.000000'"}, - {"TIME '23:59:59.9999995'", "time(6)", "TIME '00:00:00.000000'"}, - {"TIME '23:59:59.9999995'", "time(6)", "TIME '00:00:00.000000'"}, - {"TIME '23:59:59.999999499999'", "time(6)", "TIME '23:59:59.999999'"}, - {"TIME '23:59:59.9999994'", "time(6)", "TIME '23:59:59.999999'"}}; - } - @Override protected Optional filterSetColumnTypesDataProvider(SetColumnTypeSetup setup) { diff --git a/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteConnectorTest.java b/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteConnectorTest.java index 067781018014..19d1495fce98 100644 --- a/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteConnectorTest.java +++ b/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteConnectorTest.java @@ -374,12 +374,12 @@ public void testAlterTableAddLongColumnName() super.testAlterTableAddLongColumnName(); } - @Test(dataProvider = "testColumnNameDataProvider") + @Test @Override @Flaky(issue = SCHEMA_CHANGE_OPERATION_FAIL_ISSUE, match = SCHEMA_CHANGE_OPERATION_FAIL_MATCH) - public void testAddAndDropColumnName(String columnName) + public void testAddAndDropColumnName() { - super.testAddAndDropColumnName(columnName); + super.testAddAndDropColumnName(); } @Override diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaConnectorTest.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaConnectorTest.java index f687fcde1ccd..a82c24096871 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaConnectorTest.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaConnectorTest.java @@ -27,7 +27,6 @@ import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.common.serialization.ByteArraySerializer; import org.testng.SkipException; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.nio.ByteBuffer; @@ -37,7 +36,6 @@ import java.util.UUID; import java.util.stream.Stream; -import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableMap.toImmutableMap; import static io.trino.plugin.kafka.encoder.json.format.DateTimeFormat.CUSTOM_DATE_TIME; @@ -57,7 +55,6 @@ import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MILLIS; import static io.trino.spi.type.VarcharType.createVarcharType; -import static io.trino.testing.DataProviders.toDataProvider; import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_CREATE_TABLE_WITH_DATA; import static io.trino.testing.TestingNames.randomNameSuffix; import static java.lang.String.format; @@ -495,31 +492,26 @@ public void testKafkaHeaders() "VALUES ('bar'), (null), ('baz')"); } - @Test(dataProvider = "jsonDateTimeFormatsDataProvider") - public void testJsonDateTimeFormatsRoundTrip(JsonDateTimeTestCase testCase) + @Test + public void testJsonDateTimeFormatsRoundTrip() { - assertUpdate("INSERT into write_test." + testCase.getTopicName() + - " (" + testCase.getFieldNames() + ")" + - " VALUES " + testCase.getFieldValues(), 1); - for (JsonDateTimeTestCase.Field field : testCase.getFields()) { - Object actual = computeScalar("SELECT " + field.getFieldName() + " FROM write_test." + testCase.getTopicName()); - Object expected = computeScalar("SELECT " + field.getFieldValue()); - try { - assertEquals(actual, expected, "Equality assertion failed for field: " + field.getFieldName()); - } - catch (AssertionError e) { - throw new AssertionError(format("Equality assertion failed for field '%s'\n%s", field.getFieldName(), e.getMessage()), e); + for (JsonDateTimeTestCase testCase : jsonDateTimeFormatsData()) { + assertUpdate("INSERT into write_test." + testCase.getTopicName() + + " (" + testCase.getFieldNames() + ")" + + " VALUES " + testCase.getFieldValues(), 1); + for (JsonDateTimeTestCase.Field field : testCase.getFields()) { + Object actual = computeScalar("SELECT " + field.getFieldName() + " FROM write_test." + testCase.getTopicName()); + Object expected = computeScalar("SELECT " + field.getFieldValue()); + try { + assertEquals(actual, expected, "Equality assertion failed for field: " + field.getFieldName()); + } + catch (AssertionError e) { + throw new AssertionError(format("Equality assertion failed for field '%s'\n%s", field.getFieldName(), e.getMessage()), e); + } } } } - @DataProvider - public static Object[][] jsonDateTimeFormatsDataProvider() - { - return jsonDateTimeFormatsData().stream() - .collect(toDataProvider()); - } - private static List jsonDateTimeFormatsData() { return ImmutableList.builder() @@ -698,100 +690,53 @@ public String getFieldValue() } } - @Test(dataProvider = "roundTripAllFormatsDataProvider") - public void testRoundTripAllFormats(RoundTripTestCase testCase) - { - assertUpdate("INSERT into write_test." + testCase.getTableName() + - " (" + testCase.getFieldNames() + ")" + - " VALUES " + testCase.getRowValues(), testCase.getNumRows()); - assertQuery("SELECT " + testCase.getFieldNames() + " FROM write_test." + testCase.getTableName() + - " WHERE f_bigint > 1", - "VALUES " + testCase.getRowValues()); - } - - @DataProvider - public static Object[][] roundTripAllFormatsDataProvider() - { - return roundTripAllFormatsData().stream() - .collect(toDataProvider()); - } - - private static List roundTripAllFormatsData() + @Test + public void testRoundTripAllFormats() { - return ImmutableList.builder() - .add(new RoundTripTestCase( - "all_datatypes_avro", - ImmutableList.of("f_bigint", "f_float", "f_double", "f_boolean", "f_varchar"), - ImmutableList.of( - ImmutableList.of(100000, 999.999f, 1000.001, true, "'test'"), - ImmutableList.of(123456, -123.456f, 1234.123, false, "'abcd'")))) - .add(new RoundTripTestCase( - "all_datatypes_csv", - ImmutableList.of("f_bigint", "f_int", "f_smallint", "f_tinyint", "f_double", "f_boolean", "f_varchar"), - ImmutableList.of( - ImmutableList.of(100000, 1000, 100, 10, 1000.001, true, "'test'"), - ImmutableList.of(123456, 1234, 123, 12, 12345.123, false, "'abcd'")))) - .add(new RoundTripTestCase( - "all_datatypes_raw", - ImmutableList.of("kafka_key", "f_varchar", "f_bigint", "f_int", "f_smallint", "f_tinyint", "f_double", "f_boolean"), - ImmutableList.of( - ImmutableList.of(1, "'test'", 100000, 1000, 100, 10, 1000.001, true), - ImmutableList.of(1, "'abcd'", 123456, 1234, 123, 12, 12345.123, false)))) - .add(new RoundTripTestCase( - "all_datatypes_json", - ImmutableList.of("f_bigint", "f_int", "f_smallint", "f_tinyint", "f_double", "f_boolean", "f_varchar"), - ImmutableList.of( - ImmutableList.of(100000, 1000, 100, 10, 1000.001, true, "'test'"), - ImmutableList.of(123748, 1234, 123, 12, 12345.123, false, "'abcd'")))) - .build(); + testRoundTripAllFormats( + "all_datatypes_avro", + ImmutableList.of("f_bigint", "f_float", "f_double", "f_boolean", "f_varchar"), + ImmutableList.of( + ImmutableList.of(100000, 999.999f, 1000.001, true, "'test'"), + ImmutableList.of(123456, -123.456f, 1234.123, false, "'abcd'"))); + + testRoundTripAllFormats( + "all_datatypes_csv", + ImmutableList.of("f_bigint", "f_int", "f_smallint", "f_tinyint", "f_double", "f_boolean", "f_varchar"), + ImmutableList.of( + ImmutableList.of(100000, 1000, 100, 10, 1000.001, true, "'test'"), + ImmutableList.of(123456, 1234, 123, 12, 12345.123, false, "'abcd'"))); + + testRoundTripAllFormats( + "all_datatypes_raw", + ImmutableList.of("kafka_key", "f_varchar", "f_bigint", "f_int", "f_smallint", "f_tinyint", "f_double", "f_boolean"), + ImmutableList.of( + ImmutableList.of(1, "'test'", 100000, 1000, 100, 10, 1000.001, true), + ImmutableList.of(1, "'abcd'", 123456, 1234, 123, 12, 12345.123, false))); + + testRoundTripAllFormats( + "all_datatypes_json", + ImmutableList.of("f_bigint", "f_int", "f_smallint", "f_tinyint", "f_double", "f_boolean", "f_varchar"), + ImmutableList.of( + ImmutableList.of(100000, 1000, 100, 10, 1000.001, true, "'test'"), + ImmutableList.of(123748, 1234, 123, 12, 12345.123, false, "'abcd'"))); } - private static final class RoundTripTestCase + public void testRoundTripAllFormats(String tableName, List fieldNames, List> rowValues) { - private final String tableName; - private final List fieldNames; - private final List> rowValues; - private final int numRows; - - public RoundTripTestCase(String tableName, List fieldNames, List> rowValues) - { - for (List row : rowValues) { - checkArgument(fieldNames.size() == row.size(), "sizes of fieldNames and rowValues are not equal"); - } - this.tableName = requireNonNull(tableName, "tableName is null"); - this.fieldNames = ImmutableList.copyOf(fieldNames); - this.rowValues = ImmutableList.copyOf(rowValues); - this.numRows = this.rowValues.size(); - } - - public String getTableName() - { - return tableName; - } - - public String getFieldNames() - { - return String.join(", ", fieldNames); - } - - public String getRowValues() - { - String[] rows = new String[numRows]; - for (int i = 0; i < numRows; i++) { - rows[i] = rowValues.get(i).stream().map(Object::toString).collect(joining(", ", "(", ")")); - } - return String.join(", ", rows); - } + String rows = rowValues.stream() + .map(row -> row.stream() + .map(Object::toString) + .collect(joining(", ", "(", ")"))) + .collect(joining(", ")); + String fields = String.join(",", fieldNames); - public int getNumRows() - { - return numRows; - } + assertUpdate( + "INSERT into write_test." + tableName + " (" + fields + ") VALUES " + rows, + rowValues.size()); - @Override - public String toString() - { - return tableName; // for test case label in IDE - } + assertQuery( + "SELECT " + fields + " FROM write_test." + tableName + " WHERE f_bigint > 1", + "VALUES " + rows); } } diff --git a/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduConnectorTest.java b/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduConnectorTest.java index e0deac697850..067303b15309 100644 --- a/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduConnectorTest.java +++ b/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduConnectorTest.java @@ -20,6 +20,7 @@ import io.trino.testing.QueryRunner; import io.trino.testing.TestingConnectorBehavior; import io.trino.testing.sql.TestTable; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.testng.SkipException; @@ -155,22 +156,18 @@ public void testRenameTableToUnqualifiedPreservesSchema() .hasMessage("Creating schema in Kudu connector not allowed if schema emulation is disabled."); } + @Test + @Disabled @Override - public void testAddAndDropColumnName(String columnName) + public void testAddAndDropColumnName() { - // TODO: Enable this test - assertThatThrownBy(() -> super.testAddAndDropColumnName(columnName)) - .hasMessage("Table partitioning must be specified using setRangePartitionColumns or addHashPartitions"); - throw new SkipException("TODO"); } + @Test + @Disabled @Override - public void testRenameColumnName(String columnName) + public void testRenameColumnName() { - // TODO: Enable this test - assertThatThrownBy(() -> super.testRenameColumnName(columnName)) - .hasMessage("Table partitioning must be specified using setRangePartitionColumns or addHashPartitions"); - throw new SkipException("TODO"); } @Override @@ -1006,7 +1003,7 @@ public void testCreateTableWithTableComment() } @Override - public void testCreateTableWithTableCommentSpecialCharacter(String comment) + protected void testCreateTableWithTableCommentSpecialCharacter(String comment) { // TODO Remove this overriding test once kudu connector can create tables with default partitions try (TestTable table = new TestTable(getQueryRunner()::execute, diff --git a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbConnectorTest.java b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbConnectorTest.java index 394a325f6e1e..cc8e3d69db11 100644 --- a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbConnectorTest.java +++ b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbConnectorTest.java @@ -45,10 +45,12 @@ public void testRenameColumn() } @Override - public void testRenameColumnName(String columnName) + public void testRenameColumnName() { - assertThatThrownBy(() -> super.testRenameColumnName(columnName)) - .hasMessageContaining("Rename column not supported for the MariaDB server version"); + for (String columnName : testColumnNameDataProvider()) { + assertThatThrownBy(() -> testRenameColumnName(columnName, requiresDelimiting(columnName))) + .hasMessageContaining("Rename column not supported for the MariaDB server version"); + } } @Override diff --git a/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryConnectorTest.java b/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryConnectorTest.java index 45fd3e285161..0ce6f079554f 100644 --- a/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryConnectorTest.java +++ b/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryConnectorTest.java @@ -202,58 +202,64 @@ public void testPhysicalInputPositions() assertEquals(probeStats.getPhysicalInputPositions(), LINEITEM_COUNT); } - @Test(timeOut = 30_000, dataProvider = "joinDistributionTypes") - public void testJoinDynamicFilteringNone(JoinDistributionType joinDistributionType) + @Test(timeOut = 30_000) + public void testJoinDynamicFilteringNone() { - // Probe-side is not scanned at all, due to dynamic filtering: - assertDynamicFiltering( - "SELECT * FROM lineitem JOIN orders ON lineitem.orderkey = orders.orderkey AND orders.totalprice < 0", - noJoinReordering(joinDistributionType), - 0, - 0, ORDERS_COUNT); + for (JoinDistributionType joinDistributionType : JoinDistributionType.values()) { + // Probe-side is not scanned at all, due to dynamic filtering: + assertDynamicFiltering( + "SELECT * FROM lineitem JOIN orders ON lineitem.orderkey = orders.orderkey AND orders.totalprice < 0", + noJoinReordering(joinDistributionType), + 0, + 0, ORDERS_COUNT); + } } - @Test(timeOut = 30_000, dataProvider = "joinDistributionTypes") - public void testJoinLargeBuildSideDynamicFiltering(JoinDistributionType joinDistributionType) - { - @Language("SQL") String sql = "SELECT * FROM lineitem JOIN orders ON lineitem.orderkey = orders.orderkey and orders.custkey BETWEEN 300 AND 700"; - int expectedRowCount = 15793; - // Probe-side is fully scanned because the build-side is too large for dynamic filtering: - assertDynamicFiltering( - sql, - noJoinReordering(joinDistributionType), - expectedRowCount, - LINEITEM_COUNT, ORDERS_COUNT); - // Probe-side is partially scanned because we extract min/max from large build-side for dynamic filtering - assertDynamicFiltering( - sql, - withLargeDynamicFilters(joinDistributionType), - expectedRowCount, - 60139, ORDERS_COUNT); + @Test(timeOut = 30_000) + public void testJoinLargeBuildSideDynamicFiltering() + { + for (JoinDistributionType joinDistributionType : JoinDistributionType.values()) { + @Language("SQL") String sql = "SELECT * FROM lineitem JOIN orders ON lineitem.orderkey = orders.orderkey and orders.custkey BETWEEN 300 AND 700"; + int expectedRowCount = 15793; + // Probe-side is fully scanned because the build-side is too large for dynamic filtering: + assertDynamicFiltering( + sql, + noJoinReordering(joinDistributionType), + expectedRowCount, + LINEITEM_COUNT, ORDERS_COUNT); + // Probe-side is partially scanned because we extract min/max from large build-side for dynamic filtering + assertDynamicFiltering( + sql, + withLargeDynamicFilters(joinDistributionType), + expectedRowCount, + 60139, ORDERS_COUNT); + } } - @Test(timeOut = 30_000, dataProvider = "joinDistributionTypes") - public void testJoinDynamicFilteringSingleValue(JoinDistributionType joinDistributionType) + @Test(timeOut = 30_000) + public void testJoinDynamicFilteringSingleValue() { - assertThat(computeScalar("SELECT orderkey FROM orders WHERE comment = 'nstructions sleep furiously among '")).isEqualTo(1L); - assertThat(computeScalar("SELECT COUNT() FROM lineitem WHERE orderkey = 1")).isEqualTo(6L); + for (JoinDistributionType joinDistributionType : JoinDistributionType.values()) { + assertThat(computeScalar("SELECT orderkey FROM orders WHERE comment = 'nstructions sleep furiously among '")).isEqualTo(1L); + assertThat(computeScalar("SELECT COUNT() FROM lineitem WHERE orderkey = 1")).isEqualTo(6L); - assertThat(computeScalar("SELECT partkey FROM part WHERE comment = 'onic deposits'")).isEqualTo(1552L); - assertThat(computeScalar("SELECT COUNT() FROM lineitem WHERE partkey = 1552")).isEqualTo(39L); + assertThat(computeScalar("SELECT partkey FROM part WHERE comment = 'onic deposits'")).isEqualTo(1552L); + assertThat(computeScalar("SELECT COUNT() FROM lineitem WHERE partkey = 1552")).isEqualTo(39L); - // Join lineitem with a single row of orders - assertDynamicFiltering( - "SELECT * FROM lineitem JOIN orders ON lineitem.orderkey = orders.orderkey AND orders.comment = 'nstructions sleep furiously among '", - noJoinReordering(joinDistributionType), - 6, - 6, ORDERS_COUNT); + // Join lineitem with a single row of orders + assertDynamicFiltering( + "SELECT * FROM lineitem JOIN orders ON lineitem.orderkey = orders.orderkey AND orders.comment = 'nstructions sleep furiously among '", + noJoinReordering(joinDistributionType), + 6, + 6, ORDERS_COUNT); - // Join lineitem with a single row of part - assertDynamicFiltering( - "SELECT l.comment FROM lineitem l, part p WHERE p.partkey = l.partkey AND p.comment = 'onic deposits'", - noJoinReordering(joinDistributionType), - 39, - 39, PART_COUNT); + // Join lineitem with a single row of part + assertDynamicFiltering( + "SELECT l.comment FROM lineitem l, part p WHERE p.partkey = l.partkey AND p.comment = 'onic deposits'", + noJoinReordering(joinDistributionType), + 39, + 39, PART_COUNT); + } } @Test @@ -268,81 +274,91 @@ public void testJoinDynamicFilteringImplicitCoercion() 6, ORDERS_COUNT); } - @Test(timeOut = 30_000, dataProvider = "joinDistributionTypes") - public void testJoinDynamicFilteringBlockProbeSide(JoinDistributionType joinDistributionType) + @Test(timeOut = 30_000) + public void testJoinDynamicFilteringBlockProbeSide() { - // Wait for both build sides to finish before starting the scan of 'lineitem' table (should be very selective given the dynamic filters). - assertDynamicFiltering( - "SELECT l.comment" + - " FROM lineitem l, part p, orders o" + - " WHERE l.orderkey = o.orderkey AND o.comment = 'nstructions sleep furiously among '" + - " AND p.partkey = l.partkey AND p.comment = 'onic deposits'", - noJoinReordering(joinDistributionType), - 1, - 1, PART_COUNT, ORDERS_COUNT); + for (JoinDistributionType joinDistributionType : JoinDistributionType.values()) { + // Wait for both build sides to finish before starting the scan of 'lineitem' table (should be very selective given the dynamic filters). + assertDynamicFiltering( + "SELECT l.comment" + + " FROM lineitem l, part p, orders o" + + " WHERE l.orderkey = o.orderkey AND o.comment = 'nstructions sleep furiously among '" + + " AND p.partkey = l.partkey AND p.comment = 'onic deposits'", + noJoinReordering(joinDistributionType), + 1, + 1, PART_COUNT, ORDERS_COUNT); + } } - @Test(timeOut = 30_000, dataProvider = "joinDistributionTypes") - public void testSemiJoinDynamicFilteringNone(JoinDistributionType joinDistributionType) + @Test(timeOut = 30_000) + public void testSemiJoinDynamicFilteringNone() { - // Probe-side is not scanned at all, due to dynamic filtering: - assertDynamicFiltering( - "SELECT * FROM lineitem WHERE lineitem.orderkey IN (SELECT orders.orderkey FROM orders WHERE orders.totalprice < 0)", - noJoinReordering(joinDistributionType), - 0, - 0, ORDERS_COUNT); + for (JoinDistributionType joinDistributionType : JoinDistributionType.values()) { + // Probe-side is not scanned at all, due to dynamic filtering: + assertDynamicFiltering( + "SELECT * FROM lineitem WHERE lineitem.orderkey IN (SELECT orders.orderkey FROM orders WHERE orders.totalprice < 0)", + noJoinReordering(joinDistributionType), + 0, + 0, ORDERS_COUNT); + } } - @Test(timeOut = 30_000, dataProvider = "joinDistributionTypes") - public void testSemiJoinLargeBuildSideDynamicFiltering(JoinDistributionType joinDistributionType) - { - // Probe-side is fully scanned because the build-side is too large for dynamic filtering: - @Language("SQL") String sql = "SELECT * FROM lineitem WHERE lineitem.orderkey IN " + - "(SELECT orders.orderkey FROM orders WHERE orders.custkey BETWEEN 300 AND 700)"; - int expectedRowCount = 15793; - // Probe-side is fully scanned because the build-side is too large for dynamic filtering: - assertDynamicFiltering( - sql, - noJoinReordering(joinDistributionType), - expectedRowCount, - LINEITEM_COUNT, ORDERS_COUNT); - // Probe-side is partially scanned because we extract min/max from large build-side for dynamic filtering - assertDynamicFiltering( - sql, - withLargeDynamicFilters(joinDistributionType), - expectedRowCount, - 60139, ORDERS_COUNT); + @Test(timeOut = 30_000) + public void testSemiJoinLargeBuildSideDynamicFiltering() + { + for (JoinDistributionType joinDistributionType : JoinDistributionType.values()) { + // Probe-side is fully scanned because the build-side is too large for dynamic filtering: + @Language("SQL") String sql = "SELECT * FROM lineitem WHERE lineitem.orderkey IN " + + "(SELECT orders.orderkey FROM orders WHERE orders.custkey BETWEEN 300 AND 700)"; + int expectedRowCount = 15793; + // Probe-side is fully scanned because the build-side is too large for dynamic filtering: + assertDynamicFiltering( + sql, + noJoinReordering(joinDistributionType), + expectedRowCount, + LINEITEM_COUNT, ORDERS_COUNT); + // Probe-side is partially scanned because we extract min/max from large build-side for dynamic filtering + assertDynamicFiltering( + sql, + withLargeDynamicFilters(joinDistributionType), + expectedRowCount, + 60139, ORDERS_COUNT); + } } - @Test(timeOut = 30_000, dataProvider = "joinDistributionTypes") - public void testSemiJoinDynamicFilteringSingleValue(JoinDistributionType joinDistributionType) + @Test(timeOut = 30_000) + public void testSemiJoinDynamicFilteringSingleValue() { - // Join lineitem with a single row of orders - assertDynamicFiltering( - "SELECT * FROM lineitem WHERE lineitem.orderkey IN (SELECT orders.orderkey FROM orders WHERE orders.comment = 'nstructions sleep furiously among ')", - noJoinReordering(joinDistributionType), - 6, - 6, ORDERS_COUNT); + for (JoinDistributionType joinDistributionType : JoinDistributionType.values()) { + // Join lineitem with a single row of orders + assertDynamicFiltering( + "SELECT * FROM lineitem WHERE lineitem.orderkey IN (SELECT orders.orderkey FROM orders WHERE orders.comment = 'nstructions sleep furiously among ')", + noJoinReordering(joinDistributionType), + 6, + 6, ORDERS_COUNT); - // Join lineitem with a single row of part - assertDynamicFiltering( - "SELECT l.comment FROM lineitem l WHERE l.partkey IN (SELECT p.partkey FROM part p WHERE p.comment = 'onic deposits')", - noJoinReordering(joinDistributionType), - 39, - 39, PART_COUNT); + // Join lineitem with a single row of part + assertDynamicFiltering( + "SELECT l.comment FROM lineitem l WHERE l.partkey IN (SELECT p.partkey FROM part p WHERE p.comment = 'onic deposits')", + noJoinReordering(joinDistributionType), + 39, + 39, PART_COUNT); + } } - @Test(timeOut = 30_000, dataProvider = "joinDistributionTypes") - public void testSemiJoinDynamicFilteringBlockProbeSide(JoinDistributionType joinDistributionType) + @Test(timeOut = 30_000) + public void testSemiJoinDynamicFilteringBlockProbeSide() { - // Wait for both build sides to finish before starting the scan of 'lineitem' table (should be very selective given the dynamic filters). - assertDynamicFiltering( - "SELECT t.comment FROM " + - "(SELECT * FROM lineitem l WHERE l.orderkey IN (SELECT o.orderkey FROM orders o WHERE o.comment = 'nstructions sleep furiously among ')) t " + - "WHERE t.partkey IN (SELECT p.partkey FROM part p WHERE p.comment = 'onic deposits')", - noJoinReordering(joinDistributionType), - 1, - 1, ORDERS_COUNT, PART_COUNT); + for (JoinDistributionType joinDistributionType : JoinDistributionType.values()) { + // Wait for both build sides to finish before starting the scan of 'lineitem' table (should be very selective given the dynamic filters). + assertDynamicFiltering( + "SELECT t.comment FROM " + + "(SELECT * FROM lineitem l WHERE l.orderkey IN (SELECT o.orderkey FROM orders o WHERE o.comment = 'nstructions sleep furiously among ')) t " + + "WHERE t.partkey IN (SELECT p.partkey FROM part p WHERE p.comment = 'onic deposits')", + noJoinReordering(joinDistributionType), + 1, + 1, ORDERS_COUNT, PART_COUNT); + } } @Test @@ -422,23 +438,25 @@ public void testCrossJoinLargeBuildSideDynamicFiltering() ORDERS_COUNT, CUSTOMER_COUNT); } - @Test(timeOut = 30_000, dataProvider = "joinDistributionTypes") - public void testJoinDynamicFilteringMultiJoin(JoinDistributionType joinDistributionType) - { - assertUpdate("DROP TABLE IF EXISTS t0"); - assertUpdate("DROP TABLE IF EXISTS t1"); - assertUpdate("DROP TABLE IF EXISTS t2"); - assertUpdate("CREATE TABLE t0 (k0 integer, v0 real)"); - assertUpdate("CREATE TABLE t1 (k1 integer, v1 real)"); - assertUpdate("CREATE TABLE t2 (k2 integer, v2 real)"); - assertUpdate("INSERT INTO t0 VALUES (1, 1.0)", 1); - assertUpdate("INSERT INTO t1 VALUES (1, 2.0)", 1); - assertUpdate("INSERT INTO t2 VALUES (1, 3.0)", 1); - - assertQuery( - noJoinReordering(joinDistributionType), - "SELECT k0, k1, k2 FROM t0, t1, t2 WHERE (k0 = k1) AND (k0 = k2) AND (v0 + v1 = v2)", - "SELECT 1, 1, 1"); + @Test(timeOut = 30_000) + public void testJoinDynamicFilteringMultiJoin() + { + for (JoinDistributionType joinDistributionType : JoinDistributionType.values()) { + assertUpdate("DROP TABLE IF EXISTS t0"); + assertUpdate("DROP TABLE IF EXISTS t1"); + assertUpdate("DROP TABLE IF EXISTS t2"); + assertUpdate("CREATE TABLE t0 (k0 integer, v0 real)"); + assertUpdate("CREATE TABLE t1 (k1 integer, v1 real)"); + assertUpdate("CREATE TABLE t2 (k2 integer, v2 real)"); + assertUpdate("INSERT INTO t0 VALUES (1, 1.0)", 1); + assertUpdate("INSERT INTO t1 VALUES (1, 2.0)", 1); + assertUpdate("INSERT INTO t2 VALUES (1, 3.0)", 1); + + assertQuery( + noJoinReordering(joinDistributionType), + "SELECT k0, k1, k2 FROM t0, t1, t2 WHERE (k0 = k1) AND (k0 = k2) AND (v0 + v1 = v2)", + "SELECT 1, 1, 1"); + } } private void assertDynamicFiltering(@Language("SQL") String selectQuery, Session session, int expectedRowCount, int... expectedOperatorRowsRead) diff --git a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoConnectorTest.java b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoConnectorTest.java index a6dcbbfdf206..ac8b91a0bf87 100644 --- a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoConnectorTest.java +++ b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoConnectorTest.java @@ -38,7 +38,6 @@ import org.testng.SkipException; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.math.BigDecimal; @@ -120,18 +119,20 @@ protected TestTable createTableWithDefaultColumns() throw new SkipException("MongoDB connector does not support column default values"); } - @Test(dataProvider = "testColumnNameDataProvider") + @Test @Override - public void testColumnName(String columnName) + public void testColumnName() { - if (columnName.equals("a.dot")) { - assertThatThrownBy(() -> super.testColumnName(columnName)) - .isInstanceOf(RuntimeException.class) - .hasMessage("Column name must not contain '$' or '.' for INSERT: " + columnName); - throw new SkipException("Insert would fail"); - } + for (String columnName : testColumnNameDataProvider()) { + if (columnName.equals("a.dot")) { + assertThatThrownBy(() -> testColumnName(columnName, requiresDelimiting(columnName))) + .isInstanceOf(RuntimeException.class) + .hasMessage("Column name must not contain '$' or '.' for INSERT: " + columnName); + throw new SkipException("Insert would fail"); + } - super.testColumnName(columnName); + testColumnName(columnName, requiresDelimiting(columnName)); + } } @Test @@ -157,8 +158,24 @@ protected Optional filterDataMappingSmokeTestData(DataMapp return Optional.of(dataMappingTestSetup); } - @Test(dataProvider = "guessFieldTypesProvider") - public void testGuessFieldTypes(String mongoValue, String trinoValue) + @Test + public void testGuessFieldTypes() + { + testGuessFieldTypes("true", "true"); // boolean -> boolean + testGuessFieldTypes("2147483647", "bigint '2147483647'"); // int32 -> bigint + testGuessFieldTypes("{\"$numberLong\": \"9223372036854775807\"}", "9223372036854775807"); // int64 -> bigint + testGuessFieldTypes("1.23", "double '1.23'"); // double -> double + testGuessFieldTypes("{\"$date\": \"1970-01-01T00:00:00.000Z\"}", "timestamp '1970-01-01 00:00:00.000'"); // date -> timestamp(3) + testGuessFieldTypes("'String type'", "varchar 'String type'"); // string -> varchar + testGuessFieldTypes("{$binary: \"\",\"$type\": \"0\"}", "to_utf8('')"); // binary -> varbinary + testGuessFieldTypes("{\"$oid\": \"6216f0c6c432d45190f25e7c\"}", "ObjectId('6216f0c6c432d45190f25e7c')"); // objectid -> objectid + testGuessFieldTypes("[1]", "array[bigint '1']"); // array with single type -> array + testGuessFieldTypes("{\"field\": \"object\"}", "CAST(row('object') AS row(field varchar))"); // object -> row + testGuessFieldTypes("[9, \"test\"]", "CAST(row(9, 'test') AS row(_pos1 bigint, _pos2 varchar))"); // array with multiple types -> row + testGuessFieldTypes("{\"$ref\":\"test_ref\",\"$id\":ObjectId(\"4e3f33de6266b5845052c02c\"),\"$db\":\"test_db\"}", "CAST(row('test_db', 'test_ref', ObjectId('4e3f33de6266b5845052c02c')) AS row(databasename varchar, collectionname varchar, id ObjectId))"); // dbref -> row + } + + private void testGuessFieldTypes(String mongoValue, String trinoValue) { String tableName = "test_guess_field_type_" + randomNameSuffix(); Document document = Document.parse(format("{\"test\":%s}", mongoValue)); @@ -172,25 +189,6 @@ public void testGuessFieldTypes(String mongoValue, String trinoValue) assertUpdate("DROP TABLE test." + tableName); } - @DataProvider - public Object[][] guessFieldTypesProvider() - { - return new Object[][] { - {"true", "true"}, // boolean -> boolean - {"2147483647", "bigint '2147483647'"}, // int32 -> bigint - {"{\"$numberLong\": \"9223372036854775807\"}", "9223372036854775807"}, // int64 -> bigint - {"1.23", "double '1.23'"}, // double -> double - {"{\"$date\": \"1970-01-01T00:00:00.000Z\"}", "timestamp '1970-01-01 00:00:00.000'"}, // date -> timestamp(3) - {"'String type'", "varchar 'String type'"}, // string -> varchar - {"{$binary: \"\",\"$type\": \"0\"}", "to_utf8('')"}, // binary -> varbinary - {"{\"$oid\": \"6216f0c6c432d45190f25e7c\"}", "ObjectId('6216f0c6c432d45190f25e7c')"}, // objectid -> objectid - {"[1]", "array[bigint '1']"}, // array with single type -> array - {"{\"field\": \"object\"}", "CAST(row('object') AS row(field varchar))"}, // object -> row - {"[9, \"test\"]", "CAST(row(9, 'test') AS row(_pos1 bigint, _pos2 varchar))"}, // array with multiple types -> row - {"{\"$ref\":\"test_ref\",\"$id\":ObjectId(\"4e3f33de6266b5845052c02c\"),\"$db\":\"test_db\"}", "CAST(row('test_db', 'test_ref', ObjectId('4e3f33de6266b5845052c02c')) AS row(databasename varchar, collectionname varchar, id ObjectId))"}, // dbref -> row - }; - } - @Test public void createTableWithEveryType() { @@ -311,8 +309,26 @@ public void testExplainAnalyzeWithDeleteWithSubquery() .hasStackTraceContaining("TrinoException: " + MODIFYING_ROWS_MESSAGE); } - @Test(dataProvider = "predicatePushdownProvider") - public void testPredicatePushdown(String value) + @Test + public void testPredicatePushdown() + { + testPredicatePushdown("true"); + testPredicatePushdown("tinyint '1'"); + testPredicatePushdown("smallint '2'"); + testPredicatePushdown("integer '3'"); + testPredicatePushdown("bigint '4'"); + testPredicatePushdown("decimal '3.14'"); + testPredicatePushdown("decimal '1234567890.123456789'"); + testPredicatePushdown("'test'"); + testPredicatePushdown("char 'test'"); + testPredicatePushdown("objectid('6216f0c6c432d45190f25e7c')"); + testPredicatePushdown("date '1970-01-01'"); + testPredicatePushdown("time '00:00:00.000'"); + testPredicatePushdown("timestamp '1970-01-01 00:00:00.000'"); + testPredicatePushdown("timestamp '1970-01-01 00:00:00.000 UTC'"); + } + + private void testPredicatePushdown(String value) { try (TestTable table = new TestTable(getQueryRunner()::execute, "test_predicate_pushdown", "AS SELECT %s col".formatted(value))) { testPredicatePushdown(table.getName(), "col = " + value); @@ -324,27 +340,6 @@ public void testPredicatePushdown(String value) } } - @DataProvider - public Object[][] predicatePushdownProvider() - { - return new Object[][] { - {"true"}, - {"tinyint '1'"}, - {"smallint '2'"}, - {"integer '3'"}, - {"bigint '4'"}, - {"decimal '3.14'"}, - {"decimal '1234567890.123456789'"}, - {"'test'"}, - {"char 'test'"}, - {"objectid('6216f0c6c432d45190f25e7c')"}, - {"date '1970-01-01'"}, - {"time '00:00:00.000'"}, - {"timestamp '1970-01-01 00:00:00.000'"}, - {"timestamp '1970-01-01 00:00:00.000 UTC'"}, - }; - } - @Test public void testPredicatePushdownRealType() { @@ -608,8 +603,20 @@ public void testNegativeZeroDecimal() assertUpdate("DROP TABLE test." + tableName); } - @Test(dataProvider = "dbRefProvider") - public void testDBRef(Object objectId, String expectedValue, String expectedType) + @Test + public void testDBRef() + { + testDBRef("String type", "varchar 'String type'", "varchar"); + testDBRef("BinData".getBytes(UTF_8), "to_utf8('BinData')", "varbinary"); + testDBRef(1234567890, "bigint '1234567890'", "bigint"); + testDBRef(true, "true", "boolean"); + testDBRef(12.3f, "double '12.3'", "double"); + testDBRef(new Date(0), "timestamp '1970-01-01 00:00:00.000'", "timestamp(3)"); + testDBRef(ImmutableList.of(1), "array[bigint '1']", "array(bigint)"); + testDBRef(new ObjectId("5126bc054aed4daf9e2ab772"), "ObjectId('5126bc054aed4daf9e2ab772')", "ObjectId"); + } + + private void testDBRef(Object objectId, String expectedValue, String expectedType) { Document document = Document.parse("{\"_id\":ObjectId(\"5126bbf64aed4daf9e2ab771\"),\"col1\":\"foo\"}"); @@ -630,21 +637,6 @@ public void testDBRef(Object objectId, String expectedValue, String expectedType assertUpdate("DROP TABLE test." + tableName); } - @DataProvider - public Object[][] dbRefProvider() - { - return new Object[][] { - {"String type", "varchar 'String type'", "varchar"}, - {"BinData".getBytes(UTF_8), "to_utf8('BinData')", "varbinary"}, - {1234567890, "bigint '1234567890'", "bigint"}, - {true, "true", "boolean"}, - {12.3f, "double '12.3'", "double"}, - {new Date(0), "timestamp '1970-01-01 00:00:00.000'", "timestamp(3)"}, - {ImmutableList.of(1), "array[bigint '1']", "array(bigint)"}, - {new ObjectId("5126bc054aed4daf9e2ab772"), "ObjectId('5126bc054aed4daf9e2ab772')", "ObjectId"}, - }; - } - @Test public void testDbRefFieldOrder() { @@ -1313,8 +1305,20 @@ public void testProjectionPushdownMixedWithUnsupportedFieldName() assertUpdate("DROP TABLE test." + tableName); } - @Test(dataProvider = "nestedValuesProvider") - public void testFiltersOnDereferenceColumnReadsLessData(String expectedValue, String expectedType) + @Test + public void testFiltersOnDereferenceColumnReadsLessData() + { + testFiltersOnDereferenceColumnReadsLessData("varchar 'String type'", "varchar"); + testFiltersOnDereferenceColumnReadsLessData("to_utf8('BinData')", "varbinary"); + testFiltersOnDereferenceColumnReadsLessData("bigint '1234567890'", "bigint"); + testFiltersOnDereferenceColumnReadsLessData("true", "boolean"); + testFiltersOnDereferenceColumnReadsLessData("double '12.3'", "double"); + testFiltersOnDereferenceColumnReadsLessData("timestamp '1970-01-01 00:00:00.000'", "timestamp(3)"); + testFiltersOnDereferenceColumnReadsLessData("array[bigint '1']", "array(bigint)"); + testFiltersOnDereferenceColumnReadsLessData("ObjectId('5126bc054aed4daf9e2ab772')", "ObjectId"); + } + + private void testFiltersOnDereferenceColumnReadsLessData(String expectedValue, String expectedType) { if (!isPushdownSupportedType(getQueryRunner().getTypeManager().fromSqlType(expectedType))) { throw new SkipException("Type doesn't support filter pushdown"); @@ -1387,21 +1391,6 @@ public void testFiltersOnDereferenceColumnReadsLessData(String expectedValue, St } } - @DataProvider - public Object[][] nestedValuesProvider() - { - return new Object[][] { - {"varchar 'String type'", "varchar"}, - {"to_utf8('BinData')", "varbinary"}, - {"bigint '1234567890'", "bigint"}, - {"true", "boolean"}, - {"double '12.3'", "double"}, - {"timestamp '1970-01-01 00:00:00.000'", "timestamp(3)"}, - {"array[bigint '1']", "array(bigint)"}, - {"ObjectId('5126bc054aed4daf9e2ab772')", "ObjectId"}, - }; - } - @Test public void testFiltersOnDereferenceColumnReadsLessDataNativeQuery() { @@ -1479,8 +1468,20 @@ public void testProjectionPushdownWithColumnMissingInDocument() assertUpdate("DROP TABLE test." + tableName); } - @Test(dataProvider = "dbRefProvider") - public void testProjectionPushdownWithDBRef(Object objectId, String expectedValue, String expectedType) + @Test + public void testProjectionPushdownWithDBRef() + { + testProjectionPushdownWithDBRef("String type", "varchar 'String type'", "varchar"); + testProjectionPushdownWithDBRef("BinData".getBytes(UTF_8), "to_utf8('BinData')", "varbinary"); + testProjectionPushdownWithDBRef(1234567890, "bigint '1234567890'", "bigint"); + testProjectionPushdownWithDBRef(true, "true", "boolean"); + testProjectionPushdownWithDBRef(12.3f, "double '12.3'", "double"); + testProjectionPushdownWithDBRef(new Date(0), "timestamp '1970-01-01 00:00:00.000'", "timestamp(3)"); + testProjectionPushdownWithDBRef(ImmutableList.of(1), "array[bigint '1']", "array(bigint)"); + testProjectionPushdownWithDBRef(new ObjectId("5126bc054aed4daf9e2ab772"), "ObjectId('5126bc054aed4daf9e2ab772')", "ObjectId"); + } + + private void testProjectionPushdownWithDBRef(Object objectId, String expectedValue, String expectedType) { String tableName = "test_projection_pushdown_with_dbref_" + randomNameSuffix(); @@ -1503,8 +1504,20 @@ public void testProjectionPushdownWithDBRef(Object objectId, String expectedValu assertUpdate("DROP TABLE test." + tableName); } - @Test(dataProvider = "dbRefProvider") - public void testProjectionPushdownWithNestedDBRef(Object objectId, String expectedValue, String expectedType) + @Test + public void testProjectionPushdownWithNestedDBRef() + { + testProjectionPushdownWithNestedDBRef("String type", "varchar 'String type'", "varchar"); + testProjectionPushdownWithNestedDBRef("BinData".getBytes(UTF_8), "to_utf8('BinData')", "varbinary"); + testProjectionPushdownWithNestedDBRef(1234567890, "bigint '1234567890'", "bigint"); + testProjectionPushdownWithNestedDBRef(true, "true", "boolean"); + testProjectionPushdownWithNestedDBRef(12.3f, "double '12.3'", "double"); + testProjectionPushdownWithNestedDBRef(new Date(0), "timestamp '1970-01-01 00:00:00.000'", "timestamp(3)"); + testProjectionPushdownWithNestedDBRef(ImmutableList.of(1), "array[bigint '1']", "array(bigint)"); + testProjectionPushdownWithNestedDBRef(new ObjectId("5126bc054aed4daf9e2ab772"), "ObjectId('5126bc054aed4daf9e2ab772')", "ObjectId"); + } + + private void testProjectionPushdownWithNestedDBRef(Object objectId, String expectedValue, String expectedType) { String tableName = "test_projection_pushdown_with_dbref_" + randomNameSuffix(); @@ -1528,8 +1541,20 @@ public void testProjectionPushdownWithNestedDBRef(Object objectId, String expect assertUpdate("DROP TABLE test." + tableName); } - @Test(dataProvider = "dbRefProvider") - public void testProjectionPushdownWithPredefinedDBRefKeyword(Object objectId, String expectedValue, String expectedType) + @Test + public void testProjectionPushdownWithPredefinedDBRefKeyword() + { + testProjectionPushdownWithPredefinedDBRefKeyword("String type", "varchar 'String type'", "varchar"); + testProjectionPushdownWithPredefinedDBRefKeyword("BinData".getBytes(UTF_8), "to_utf8('BinData')", "varbinary"); + testProjectionPushdownWithPredefinedDBRefKeyword(1234567890, "bigint '1234567890'", "bigint"); + testProjectionPushdownWithPredefinedDBRefKeyword(true, "true", "boolean"); + testProjectionPushdownWithPredefinedDBRefKeyword(12.3f, "double '12.3'", "double"); + testProjectionPushdownWithPredefinedDBRefKeyword(new Date(0), "timestamp '1970-01-01 00:00:00.000'", "timestamp(3)"); + testProjectionPushdownWithPredefinedDBRefKeyword(ImmutableList.of(1), "array[bigint '1']", "array(bigint)"); + testProjectionPushdownWithPredefinedDBRefKeyword(new ObjectId("5126bc054aed4daf9e2ab772"), "ObjectId('5126bc054aed4daf9e2ab772')", "ObjectId"); + } + + private void testProjectionPushdownWithPredefinedDBRefKeyword(Object objectId, String expectedValue, String expectedType) { String tableName = "test_projection_pushdown_with_predefined_dbref_keyword_" + randomNameSuffix(); @@ -1552,8 +1577,52 @@ public void testProjectionPushdownWithPredefinedDBRefKeyword(Object objectId, St assertUpdate("DROP TABLE test." + tableName); } - @Test(dataProvider = "dbRefAndDocumentProvider") - public void testDBRefLikeDocument(Document document1, Document document2, String expectedValue) + @Test + public void testDBRefLikeDocument() + { + testDBRefLikeDocument("String type", "varchar 'String type'"); + testDBRefLikeDocument("BinData".getBytes(UTF_8), "to_utf8('BinData')"); + testDBRefLikeDocument(1234567890, "bigint '1234567890'"); + testDBRefLikeDocument(true, "true"); + testDBRefLikeDocument(12.3f, "double '12.3'"); + testDBRefLikeDocument(new Date(0), "timestamp '1970-01-01 00:00:00.000'"); + testDBRefLikeDocument(ImmutableList.of(1), "array[bigint '1']"); + testDBRefLikeDocument(new ObjectId("5126bc054aed4daf9e2ab772"), "ObjectId('5126bc054aed4daf9e2ab772')"); + + testDBRefLikeDocument(dbRefDocument("String type"), documentWithSameDbRefFieldOrder("String type"), "varchar 'String type'"); + testDBRefLikeDocument(dbRefDocument("String type"), getDocumentWithDifferentDbRefFieldOrder("String type"), "varchar 'String type'"); + testDBRefLikeDocument(documentWithSameDbRefFieldOrder("String type"), dbRefDocument("String type"), "varchar 'String type'"); + + testDBRefLikeDocument(dbRefDocument("BinData".getBytes(UTF_8)), documentWithSameDbRefFieldOrder("BinData".getBytes(UTF_8)), "to_utf8('BinData')"); + testDBRefLikeDocument(dbRefDocument("BinData".getBytes(UTF_8)), getDocumentWithDifferentDbRefFieldOrder("BinData".getBytes(UTF_8)), "to_utf8('BinData')"); + testDBRefLikeDocument(documentWithSameDbRefFieldOrder("BinData".getBytes(UTF_8)), dbRefDocument("BinData".getBytes(UTF_8)), "to_utf8('BinData')"); + + testDBRefLikeDocument(dbRefDocument(1234567890), documentWithSameDbRefFieldOrder(1234567890), "bigint '1234567890'"); + testDBRefLikeDocument(dbRefDocument(1234567890), getDocumentWithDifferentDbRefFieldOrder(1234567890), "bigint '1234567890'"); + testDBRefLikeDocument(documentWithSameDbRefFieldOrder(1234567890), dbRefDocument(1234567890), "bigint '1234567890'"); + + testDBRefLikeDocument(dbRefDocument(true), documentWithSameDbRefFieldOrder(true), "true"); + testDBRefLikeDocument(dbRefDocument(true), getDocumentWithDifferentDbRefFieldOrder(true), "true"); + testDBRefLikeDocument(documentWithSameDbRefFieldOrder(true), dbRefDocument(true), "true"); + + testDBRefLikeDocument(dbRefDocument(12.3f), documentWithSameDbRefFieldOrder(12.3f), "double '12.3'"); + testDBRefLikeDocument(dbRefDocument(12.3f), getDocumentWithDifferentDbRefFieldOrder(12.3f), "double '12.3'"); + testDBRefLikeDocument(documentWithSameDbRefFieldOrder(12.3f), dbRefDocument(12.3f), "double '12.3'"); + + testDBRefLikeDocument(dbRefDocument(new Date(0)), documentWithSameDbRefFieldOrder(new Date(0)), "timestamp '1970-01-01 00:00:00.000'"); + testDBRefLikeDocument(dbRefDocument(new Date(0)), getDocumentWithDifferentDbRefFieldOrder(new Date(0)), "timestamp '1970-01-01 00:00:00.000'"); + testDBRefLikeDocument(documentWithSameDbRefFieldOrder(new Date(0)), dbRefDocument(new Date(0)), "timestamp '1970-01-01 00:00:00.000'"); + + testDBRefLikeDocument(dbRefDocument(ImmutableList.of(1)), documentWithSameDbRefFieldOrder(ImmutableList.of(1)), "array[bigint '1']"); + testDBRefLikeDocument(dbRefDocument(ImmutableList.of(1)), getDocumentWithDifferentDbRefFieldOrder(ImmutableList.of(1)), "array[bigint '1']"); + testDBRefLikeDocument(documentWithSameDbRefFieldOrder(ImmutableList.of(1)), dbRefDocument(ImmutableList.of(1)), "array[bigint '1']"); + + testDBRefLikeDocument(dbRefDocument(new ObjectId("5126bc054aed4daf9e2ab772")), documentWithSameDbRefFieldOrder(new ObjectId("5126bc054aed4daf9e2ab772")), "ObjectId('5126bc054aed4daf9e2ab772')"); + testDBRefLikeDocument(dbRefDocument(new ObjectId("5126bc054aed4daf9e2ab772")), getDocumentWithDifferentDbRefFieldOrder(new ObjectId("5126bc054aed4daf9e2ab772")), "ObjectId('5126bc054aed4daf9e2ab772')"); + testDBRefLikeDocument(documentWithSameDbRefFieldOrder(new ObjectId("5126bc054aed4daf9e2ab772")), dbRefDocument(new ObjectId("5126bc054aed4daf9e2ab772")), "ObjectId('5126bc054aed4daf9e2ab772')"); + } + + private void testDBRefLikeDocument(Document document1, Document document2, String expectedValue) { String tableName = "test_dbref_like_document_" + randomNameSuffix(); @@ -1580,34 +1649,28 @@ public void testDBRefLikeDocument(Document document1, Document document2, String assertUpdate("DROP TABLE test." + tableName); } - @DataProvider - public Object[][] dbRefAndDocumentProvider() - { - Object[][] dbRefObjects = dbRefProvider(); - Object[][] objects = new Object[dbRefObjects.length * 3][]; - int i = 0; - for (Object[] dbRefObject : dbRefObjects) { - Object objectId = dbRefObject[0]; - Object expectedValue = dbRefObject[1]; - Document dbRefDocument = new Document() - .append("_id", new ObjectId("5126bbf64aed4daf9e2ab772")) - .append("creator", new DBRef("dbref_test", "dbref_creators", objectId)); - Document documentWithSameDbRefFieldOrder = new Document() - .append("_id", new ObjectId("5126bbf64aed4daf9e2ab771")) - .append("creator", new Document().append("databaseName", "doc_test").append("collectionName", "doc_creators").append("id", objectId)); - Document documentWithDifferentDbRefFieldOrder = new Document() - .append("_id", new ObjectId("5126bbf64aed4daf9e2ab771")) - .append("creator", new Document().append("collectionName", "doc_creators").append("id", objectId).append("databaseName", "doc_test")); - - objects[i++] = new Object[] {dbRefDocument, documentWithSameDbRefFieldOrder, expectedValue}; - objects[i++] = new Object[] {dbRefDocument, documentWithDifferentDbRefFieldOrder, expectedValue}; - objects[i++] = new Object[] {documentWithSameDbRefFieldOrder, dbRefDocument, expectedValue}; - } - return objects; + private static Document getDocumentWithDifferentDbRefFieldOrder(Object objectId) + { + return new Document() + .append("_id", new ObjectId("5126bbf64aed4daf9e2ab771")) + .append("creator", new Document().append("collectionName", "doc_creators").append("id", objectId).append("databaseName", "doc_test")); } - @Test(dataProvider = "dbRefProvider") - public void testDBRefLikeDocument(Object objectId, String expectedValue, String expectedType) + private static Document documentWithSameDbRefFieldOrder(Object objectId) + { + return new Document() + .append("_id", new ObjectId("5126bbf64aed4daf9e2ab771")) + .append("creator", new Document().append("databaseName", "doc_test").append("collectionName", "doc_creators").append("id", objectId)); + } + + private static Document dbRefDocument(Object objectId) + { + return new Document() + .append("_id", new ObjectId("5126bbf64aed4daf9e2ab772")) + .append("creator", new DBRef("dbref_test", "dbref_creators", objectId)); + } + + private void testDBRefLikeDocument(Object objectId, String expectedValue) { String tableName = "test_dbref_like_document_fails_" + randomNameSuffix(); @@ -1640,8 +1703,17 @@ public void testDBRefLikeDocument(Object objectId, String expectedValue, String assertUpdate("DROP TABLE test." + tableName); } - @Test(dataProvider = "dfRefPredicateProvider") - public void testPredicateOnDBRefField(Object objectId, String expectedValue) + @Test + public void testPredicateOnDBRefField() + { + testPredicateOnDBRefField(true, "true"); + testPredicateOnDBRefField(4, "bigint '4'"); + testPredicateOnDBRefField("test", "'test'"); + testPredicateOnDBRefField(new ObjectId("6216f0c6c432d45190f25e7c"), "ObjectId('6216f0c6c432d45190f25e7c')"); + testPredicateOnDBRefField(new Date(0), "timestamp '1970-01-01 00:00:00.000'"); + } + + private void testPredicateOnDBRefField(Object objectId, String expectedValue) { String tableName = "test_predicate_on_dbref_field_" + randomNameSuffix(); @@ -1664,8 +1736,17 @@ public void testPredicateOnDBRefField(Object objectId, String expectedValue) assertUpdate("DROP TABLE test." + tableName); } - @Test(dataProvider = "dfRefPredicateProvider") - public void testPredicateOnDBRefLikeDocument(Object objectId, String expectedValue) + @Test + public void testPredicateOnDBRefLikeDocument() + { + testPredicateOnDBRefLikeDocument(true, "true"); + testPredicateOnDBRefLikeDocument(4, "bigint '4'"); + testPredicateOnDBRefLikeDocument("test", "'test'"); + testPredicateOnDBRefLikeDocument(new ObjectId("6216f0c6c432d45190f25e7c"), "ObjectId('6216f0c6c432d45190f25e7c')"); + testPredicateOnDBRefLikeDocument(new Date(0), "timestamp '1970-01-01 00:00:00.000'"); + } + + private void testPredicateOnDBRefLikeDocument(Object objectId, String expectedValue) { String tableName = "test_predicate_on_dbref_like_document_" + randomNameSuffix(); @@ -1691,18 +1772,6 @@ public void testPredicateOnDBRefLikeDocument(Object objectId, String expectedVal assertUpdate("DROP TABLE test." + tableName); } - @DataProvider - public Object[][] dfRefPredicateProvider() - { - return new Object[][] { - {true, "true"}, - {4, "bigint '4'"}, - {"test", "'test'"}, - {new ObjectId("6216f0c6c432d45190f25e7c"), "ObjectId('6216f0c6c432d45190f25e7c')"}, - {new Date(0), "timestamp '1970-01-01 00:00:00.000'"}, - }; - } - @Override @Test public void testProjectionPushdownReadsLessData() diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/BaseMySqlConnectorTest.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/BaseMySqlConnectorTest.java index 679dd31e4c40..350e7706fc5f 100644 --- a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/BaseMySqlConnectorTest.java +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/BaseMySqlConnectorTest.java @@ -23,7 +23,6 @@ import io.trino.testing.TestingConnectorBehavior; import io.trino.testing.sql.SqlExecutor; import io.trino.testing.sql.TestTable; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.sql.Connection; @@ -379,16 +378,28 @@ public void testPredicatePushdown() .isFullyPushedDown(); } - @Test(dataProvider = "charsetAndCollation") - public void testPredicatePushdownWithCollationView(String charset, String collation) + @Test + public void testPredicatePushdownWithCollationView() + { + testPredicatePushdownWithCollationView("latin1", "latin1_general_cs"); + testPredicatePushdownWithCollationView("utf8", "utf8_bin"); + } + + private void testPredicatePushdownWithCollationView(String charset, String collation) { onRemoteDatabase().execute(format("CREATE OR REPLACE VIEW tpch.test_view_pushdown AS SELECT regionkey, nationkey, CONVERT(name USING %s) COLLATE %s AS name FROM tpch.nation;", charset, collation)); testNationCollationQueries("test_view_pushdown"); onRemoteDatabase().execute("DROP VIEW tpch.test_view_pushdown"); } - @Test(dataProvider = "charsetAndCollation") - public void testPredicatePushdownWithCollation(String charset, String collation) + @Test + public void testPredicatePushdownWithCollation() + { + testPredicatePushdownWithCollation("latin1", "latin1_general_cs"); + testPredicatePushdownWithCollation("utf8", "utf8_bin"); + } + + private void testPredicatePushdownWithCollation(String charset, String collation) { try (TestTable testTable = new TestTable( onRemoteDatabase(), @@ -474,12 +485,6 @@ private void testNationCollationQueries(String objectName) .joinIsNotFullyPushedDown(); } - @DataProvider - public static Object[][] charsetAndCollation() - { - return new Object[][] {{"latin1", "latin1_general_cs"}, {"utf8", "utf8_bin"}}; - } - /** * This test helps to tune TupleDomain simplification threshold. */ diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlLegacyConnectorTest.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlLegacyConnectorTest.java index eff06d44caef..1c951c17a6c5 100644 --- a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlLegacyConnectorTest.java +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlLegacyConnectorTest.java @@ -135,11 +135,13 @@ public void testRenameColumn() } @Override - public void testRenameColumnName(String columnName) + public void testRenameColumnName() { - assertThatThrownBy(() -> super.testRenameColumnName(columnName)) - .hasMessageContaining("You have an error in your SQL syntax") - .hasStackTraceContaining("RENAME COLUMN"); + for (String columnName : testColumnNameDataProvider()) { + assertThatThrownBy(() -> testRenameColumnName(columnName, requiresDelimiting(columnName))) + .hasMessageContaining("You have an error in your SQL syntax") + .hasStackTraceContaining("RENAME COLUMN"); + } } @Override diff --git a/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixConnectorTest.java b/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixConnectorTest.java index 2c5e1dcab91c..c1a29cbe9db5 100644 --- a/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixConnectorTest.java +++ b/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixConnectorTest.java @@ -232,35 +232,30 @@ public void testAlterTableRenameColumnToLongName() throw new SkipException("Rename column is not yet supported by Phoenix connector"); } + @Test(enabled = false) @Override - public void testRenameColumnName(String columnName) + public void testRenameColumnName() { - // The column name is rejected when creating a table - if (columnName.equals("a\"quote")) { - super.testRenameColumnName(columnName); - return; - } - assertThatThrownBy(() -> super.testRenameColumnName(columnName)) - // TODO (https://github.com/trinodb/trino/issues/7205) support column rename in Phoenix - .hasMessageContaining("Syntax error. Encountered \"RENAME\""); - throw new SkipException("Rename column is not yet supported by Phoenix connector"); } @Override - public void testAddAndDropColumnName(String columnName) - { - // TODO: Investigate why these two case fail - if (columnName.equals("an'apostrophe")) { - assertThatThrownBy(() -> super.testAddAndDropColumnName(columnName)) - .hasMessageContaining("Syntax error. Mismatched input"); - throw new SkipException("TODO"); - } - if (columnName.equals("a\\backslash`")) { - assertThatThrownBy(() -> super.testAddAndDropColumnName(columnName)) - .hasMessageContaining("Undefined column"); - throw new SkipException("TODO"); + public void testAddAndDropColumnName() + { + for (String columnName : testColumnNameDataProvider()) { + // TODO: Investigate why these two case fail + if (columnName.equals("an'apostrophe")) { + assertThatThrownBy(() -> testAddAndDropColumnName(columnName, requiresDelimiting(columnName))) + .hasMessageContaining("Syntax error. Mismatched input"); + throw new SkipException("TODO"); + } + if (columnName.equals("a\\backslash`")) { + assertThatThrownBy(() -> testAddAndDropColumnName(columnName, requiresDelimiting(columnName))) + .hasMessageContaining("Undefined column"); + throw new SkipException("TODO"); + } + + testAddAndDropColumnName(columnName, requiresDelimiting(columnName)); } - super.testAddAndDropColumnName(columnName); } @Override diff --git a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlConnectorTest.java b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlConnectorTest.java index 083ab976e1f5..a09f2055c004 100644 --- a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlConnectorTest.java +++ b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlConnectorTest.java @@ -38,7 +38,6 @@ import io.trino.testing.sql.TestTable; import io.trino.testing.sql.TestView; import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.sql.Connection; @@ -144,8 +143,25 @@ protected TestTable createTableWithUnsupportedColumn() "(one bigint, two decimal(50,0), three varchar(10))"); } - @Test(dataProvider = "testTimestampPrecisionOnCreateTable") - public void testTimestampPrecisionOnCreateTable(String inputType, String expectedType) + @Test + public void testTimestampPrecisionOnCreateTable() + { + testTimestampPrecisionOnCreateTable("timestamp(0)", "timestamp(0)"); + testTimestampPrecisionOnCreateTable("timestamp(1)", "timestamp(1)"); + testTimestampPrecisionOnCreateTable("timestamp(2)", "timestamp(2)"); + testTimestampPrecisionOnCreateTable("timestamp(3)", "timestamp(3)"); + testTimestampPrecisionOnCreateTable("timestamp(4)", "timestamp(4)"); + testTimestampPrecisionOnCreateTable("timestamp(5)", "timestamp(5)"); + testTimestampPrecisionOnCreateTable("timestamp(6)", "timestamp(6)"); + testTimestampPrecisionOnCreateTable("timestamp(7)", "timestamp(6)"); + testTimestampPrecisionOnCreateTable("timestamp(8)", "timestamp(6)"); + testTimestampPrecisionOnCreateTable("timestamp(9)", "timestamp(6)"); + testTimestampPrecisionOnCreateTable("timestamp(10)", "timestamp(6)"); + testTimestampPrecisionOnCreateTable("timestamp(11)", "timestamp(6)"); + testTimestampPrecisionOnCreateTable("timestamp(12)", "timestamp(6)"); + } + + private void testTimestampPrecisionOnCreateTable(String inputType, String expectedType) { try (TestTable testTable = new TestTable( getQueryRunner()::execute, @@ -155,28 +171,37 @@ public void testTimestampPrecisionOnCreateTable(String inputType, String expecte } } - @DataProvider(name = "testTimestampPrecisionOnCreateTable") - public static Object[][] timestampPrecisionOnCreateTableProvider() + @Test + public void testTimestampPrecisionOnCreateTableAsSelect() { - return new Object[][]{ - {"timestamp(0)", "timestamp(0)"}, - {"timestamp(1)", "timestamp(1)"}, - {"timestamp(2)", "timestamp(2)"}, - {"timestamp(3)", "timestamp(3)"}, - {"timestamp(4)", "timestamp(4)"}, - {"timestamp(5)", "timestamp(5)"}, - {"timestamp(6)", "timestamp(6)"}, - {"timestamp(7)", "timestamp(6)"}, - {"timestamp(8)", "timestamp(6)"}, - {"timestamp(9)", "timestamp(6)"}, - {"timestamp(10)", "timestamp(6)"}, - {"timestamp(11)", "timestamp(6)"}, - {"timestamp(12)", "timestamp(6)"} - }; + testTimestampPrecisionOnCreateTableAsSelect("TIMESTAMP '1970-01-01 00:00:00'", "timestamp(0)", "TIMESTAMP '1970-01-01 00:00:00'"); + testTimestampPrecisionOnCreateTableAsSelect("TIMESTAMP '1970-01-01 00:00:00.9'", "timestamp(1)", "TIMESTAMP '1970-01-01 00:00:00.9'"); + testTimestampPrecisionOnCreateTableAsSelect("TIMESTAMP '1970-01-01 00:00:00.56'", "timestamp(2)", "TIMESTAMP '1970-01-01 00:00:00.56'"); + testTimestampPrecisionOnCreateTableAsSelect("TIMESTAMP '1970-01-01 00:00:00.123'", "timestamp(3)", "TIMESTAMP '1970-01-01 00:00:00.123'"); + testTimestampPrecisionOnCreateTableAsSelect("TIMESTAMP '1970-01-01 00:00:00.4896'", "timestamp(4)", "TIMESTAMP '1970-01-01 00:00:00.4896'"); + testTimestampPrecisionOnCreateTableAsSelect("TIMESTAMP '1970-01-01 00:00:00.89356'", "timestamp(5)", "TIMESTAMP '1970-01-01 00:00:00.89356'"); + testTimestampPrecisionOnCreateTableAsSelect("TIMESTAMP '1970-01-01 00:00:00.123000'", "timestamp(6)", "TIMESTAMP '1970-01-01 00:00:00.123000'"); + testTimestampPrecisionOnCreateTableAsSelect("TIMESTAMP '1970-01-01 00:00:00.999'", "timestamp(3)", "TIMESTAMP '1970-01-01 00:00:00.999'"); + testTimestampPrecisionOnCreateTableAsSelect("TIMESTAMP '1970-01-01 00:00:00.123456'", "timestamp(6)", "TIMESTAMP '1970-01-01 00:00:00.123456'"); + testTimestampPrecisionOnCreateTableAsSelect("TIMESTAMP '2020-09-27 12:34:56.1'", "timestamp(1)", "TIMESTAMP '2020-09-27 12:34:56.1'"); + testTimestampPrecisionOnCreateTableAsSelect("TIMESTAMP '2020-09-27 12:34:56.9'", "timestamp(1)", "TIMESTAMP '2020-09-27 12:34:56.9'"); + testTimestampPrecisionOnCreateTableAsSelect("TIMESTAMP '2020-09-27 12:34:56.123'", "timestamp(3)", "TIMESTAMP '2020-09-27 12:34:56.123'"); + testTimestampPrecisionOnCreateTableAsSelect("TIMESTAMP '2020-09-27 12:34:56.123000'", "timestamp(6)", "TIMESTAMP '2020-09-27 12:34:56.123000'"); + testTimestampPrecisionOnCreateTableAsSelect("TIMESTAMP '2020-09-27 12:34:56.999'", "timestamp(3)", "TIMESTAMP '2020-09-27 12:34:56.999'"); + testTimestampPrecisionOnCreateTableAsSelect("TIMESTAMP '2020-09-27 12:34:56.123456'", "timestamp(6)", "TIMESTAMP '2020-09-27 12:34:56.123456'"); + testTimestampPrecisionOnCreateTableAsSelect("TIMESTAMP '1970-01-01 00:00:00.1234561'", "timestamp(6)", "TIMESTAMP '1970-01-01 00:00:00.123456'"); + testTimestampPrecisionOnCreateTableAsSelect("TIMESTAMP '1970-01-01 00:00:00.123456499'", "timestamp(6)", "TIMESTAMP '1970-01-01 00:00:00.123456'"); + testTimestampPrecisionOnCreateTableAsSelect("TIMESTAMP '1970-01-01 00:00:00.123456499999'", "timestamp(6)", "TIMESTAMP '1970-01-01 00:00:00.123456'"); + testTimestampPrecisionOnCreateTableAsSelect("TIMESTAMP '1970-01-01 00:00:00.1234565'", "timestamp(6)", "TIMESTAMP '1970-01-01 00:00:00.123457'"); + testTimestampPrecisionOnCreateTableAsSelect("TIMESTAMP '1970-01-01 00:00:00.111222333444'", "timestamp(6)", "TIMESTAMP '1970-01-01 00:00:00.111222'"); + testTimestampPrecisionOnCreateTableAsSelect("TIMESTAMP '1970-01-01 00:00:00.9999995'", "timestamp(6)", "TIMESTAMP '1970-01-01 00:00:01.000000'"); + testTimestampPrecisionOnCreateTableAsSelect("TIMESTAMP '1970-01-01 23:59:59.9999995'", "timestamp(6)", "TIMESTAMP '1970-01-02 00:00:00.000000'"); + testTimestampPrecisionOnCreateTableAsSelect("TIMESTAMP '1969-12-31 23:59:59.9999995'", "timestamp(6)", "TIMESTAMP '1970-01-01 00:00:00.000000'"); + testTimestampPrecisionOnCreateTableAsSelect("TIMESTAMP '1969-12-31 23:59:59.999999499999'", "timestamp(6)", "TIMESTAMP '1969-12-31 23:59:59.999999'"); + testTimestampPrecisionOnCreateTableAsSelect("TIMESTAMP '1969-12-31 23:59:59.9999994'", "timestamp(6)", "TIMESTAMP '1969-12-31 23:59:59.999999'"); } - @Test(dataProvider = "testTimestampPrecisionOnCreateTableAsSelect") - public void testTimestampPrecisionOnCreateTableAsSelect(String inputType, String tableType, String tableValue) + private void testTimestampPrecisionOnCreateTableAsSelect(String inputType, String tableType, String tableValue) { try (TestTable testTable = new TestTable( getQueryRunner()::execute, @@ -189,8 +214,37 @@ public void testTimestampPrecisionOnCreateTableAsSelect(String inputType, String } } - @Test(dataProvider = "testTimestampPrecisionOnCreateTableAsSelect") - public void testTimestampPrecisionOnCreateTableAsSelectWithNoData(String inputType, String tableType, String ignored) + @Test + public void testTimestampPrecisionOnCreateTableAsSelectWithNoData() + { + testTimestampPrecisionOnCreateTableAsSelectWithNoData("TIMESTAMP '1970-01-01 00:00:00'", "timestamp(0)"); + testTimestampPrecisionOnCreateTableAsSelectWithNoData("TIMESTAMP '1970-01-01 00:00:00.9'", "timestamp(1)"); + testTimestampPrecisionOnCreateTableAsSelectWithNoData("TIMESTAMP '1970-01-01 00:00:00.56'", "timestamp(2)"); + testTimestampPrecisionOnCreateTableAsSelectWithNoData("TIMESTAMP '1970-01-01 00:00:00.123'", "timestamp(3)"); + testTimestampPrecisionOnCreateTableAsSelectWithNoData("TIMESTAMP '1970-01-01 00:00:00.4896'", "timestamp(4)"); + testTimestampPrecisionOnCreateTableAsSelectWithNoData("TIMESTAMP '1970-01-01 00:00:00.89356'", "timestamp(5)"); + testTimestampPrecisionOnCreateTableAsSelectWithNoData("TIMESTAMP '1970-01-01 00:00:00.123000'", "timestamp(6)"); + testTimestampPrecisionOnCreateTableAsSelectWithNoData("TIMESTAMP '1970-01-01 00:00:00.999'", "timestamp(3)"); + testTimestampPrecisionOnCreateTableAsSelectWithNoData("TIMESTAMP '1970-01-01 00:00:00.123456'", "timestamp(6)"); + testTimestampPrecisionOnCreateTableAsSelectWithNoData("TIMESTAMP '2020-09-27 12:34:56.1'", "timestamp(1)"); + testTimestampPrecisionOnCreateTableAsSelectWithNoData("TIMESTAMP '2020-09-27 12:34:56.9'", "timestamp(1)"); + testTimestampPrecisionOnCreateTableAsSelectWithNoData("TIMESTAMP '2020-09-27 12:34:56.123'", "timestamp(3)"); + testTimestampPrecisionOnCreateTableAsSelectWithNoData("TIMESTAMP '2020-09-27 12:34:56.123000'", "timestamp(6)"); + testTimestampPrecisionOnCreateTableAsSelectWithNoData("TIMESTAMP '2020-09-27 12:34:56.999'", "timestamp(3)"); + testTimestampPrecisionOnCreateTableAsSelectWithNoData("TIMESTAMP '2020-09-27 12:34:56.123456'", "timestamp(6)"); + testTimestampPrecisionOnCreateTableAsSelectWithNoData("TIMESTAMP '1970-01-01 00:00:00.1234561'", "timestamp(6)"); + testTimestampPrecisionOnCreateTableAsSelectWithNoData("TIMESTAMP '1970-01-01 00:00:00.123456499'", "timestamp(6)"); + testTimestampPrecisionOnCreateTableAsSelectWithNoData("TIMESTAMP '1970-01-01 00:00:00.123456499999'", "timestamp(6)"); + testTimestampPrecisionOnCreateTableAsSelectWithNoData("TIMESTAMP '1970-01-01 00:00:00.1234565'", "timestamp(6)"); + testTimestampPrecisionOnCreateTableAsSelectWithNoData("TIMESTAMP '1970-01-01 00:00:00.111222333444'", "timestamp(6)"); + testTimestampPrecisionOnCreateTableAsSelectWithNoData("TIMESTAMP '1970-01-01 00:00:00.9999995'", "timestamp(6)"); + testTimestampPrecisionOnCreateTableAsSelectWithNoData("TIMESTAMP '1970-01-01 23:59:59.9999995'", "timestamp(6)"); + testTimestampPrecisionOnCreateTableAsSelectWithNoData("TIMESTAMP '1969-12-31 23:59:59.9999995'", "timestamp(6)"); + testTimestampPrecisionOnCreateTableAsSelectWithNoData("TIMESTAMP '1969-12-31 23:59:59.999999499999'", "timestamp(6)"); + testTimestampPrecisionOnCreateTableAsSelectWithNoData("TIMESTAMP '1969-12-31 23:59:59.9999994'", "timestamp(6)"); + } + + private void testTimestampPrecisionOnCreateTableAsSelectWithNoData(String inputType, String tableType) { try (TestTable testTable = new TestTable( getQueryRunner()::execute, @@ -200,37 +254,6 @@ public void testTimestampPrecisionOnCreateTableAsSelectWithNoData(String inputTy } } - @DataProvider(name = "testTimestampPrecisionOnCreateTableAsSelect") - public static Object[][] timestampPrecisionOnCreateTableAsSelectProvider() - { - return new Object[][] { - {"TIMESTAMP '1970-01-01 00:00:00'", "timestamp(0)", "TIMESTAMP '1970-01-01 00:00:00'"}, - {"TIMESTAMP '1970-01-01 00:00:00.9'", "timestamp(1)", "TIMESTAMP '1970-01-01 00:00:00.9'"}, - {"TIMESTAMP '1970-01-01 00:00:00.56'", "timestamp(2)", "TIMESTAMP '1970-01-01 00:00:00.56'"}, - {"TIMESTAMP '1970-01-01 00:00:00.123'", "timestamp(3)", "TIMESTAMP '1970-01-01 00:00:00.123'"}, - {"TIMESTAMP '1970-01-01 00:00:00.4896'", "timestamp(4)", "TIMESTAMP '1970-01-01 00:00:00.4896'"}, - {"TIMESTAMP '1970-01-01 00:00:00.89356'", "timestamp(5)", "TIMESTAMP '1970-01-01 00:00:00.89356'"}, - {"TIMESTAMP '1970-01-01 00:00:00.123000'", "timestamp(6)", "TIMESTAMP '1970-01-01 00:00:00.123000'"}, - {"TIMESTAMP '1970-01-01 00:00:00.999'", "timestamp(3)", "TIMESTAMP '1970-01-01 00:00:00.999'"}, - {"TIMESTAMP '1970-01-01 00:00:00.123456'", "timestamp(6)", "TIMESTAMP '1970-01-01 00:00:00.123456'"}, - {"TIMESTAMP '2020-09-27 12:34:56.1'", "timestamp(1)", "TIMESTAMP '2020-09-27 12:34:56.1'"}, - {"TIMESTAMP '2020-09-27 12:34:56.9'", "timestamp(1)", "TIMESTAMP '2020-09-27 12:34:56.9'"}, - {"TIMESTAMP '2020-09-27 12:34:56.123'", "timestamp(3)", "TIMESTAMP '2020-09-27 12:34:56.123'"}, - {"TIMESTAMP '2020-09-27 12:34:56.123000'", "timestamp(6)", "TIMESTAMP '2020-09-27 12:34:56.123000'"}, - {"TIMESTAMP '2020-09-27 12:34:56.999'", "timestamp(3)", "TIMESTAMP '2020-09-27 12:34:56.999'"}, - {"TIMESTAMP '2020-09-27 12:34:56.123456'", "timestamp(6)", "TIMESTAMP '2020-09-27 12:34:56.123456'"}, - {"TIMESTAMP '1970-01-01 00:00:00.1234561'", "timestamp(6)", "TIMESTAMP '1970-01-01 00:00:00.123456'"}, - {"TIMESTAMP '1970-01-01 00:00:00.123456499'", "timestamp(6)", "TIMESTAMP '1970-01-01 00:00:00.123456'"}, - {"TIMESTAMP '1970-01-01 00:00:00.123456499999'", "timestamp(6)", "TIMESTAMP '1970-01-01 00:00:00.123456'"}, - {"TIMESTAMP '1970-01-01 00:00:00.1234565'", "timestamp(6)", "TIMESTAMP '1970-01-01 00:00:00.123457'"}, - {"TIMESTAMP '1970-01-01 00:00:00.111222333444'", "timestamp(6)", "TIMESTAMP '1970-01-01 00:00:00.111222'"}, - {"TIMESTAMP '1970-01-01 00:00:00.9999995'", "timestamp(6)", "TIMESTAMP '1970-01-01 00:00:01.000000'"}, - {"TIMESTAMP '1970-01-01 23:59:59.9999995'", "timestamp(6)", "TIMESTAMP '1970-01-02 00:00:00.000000'"}, - {"TIMESTAMP '1969-12-31 23:59:59.9999995'", "timestamp(6)", "TIMESTAMP '1970-01-01 00:00:00.000000'"}, - {"TIMESTAMP '1969-12-31 23:59:59.999999499999'", "timestamp(6)", "TIMESTAMP '1969-12-31 23:59:59.999999'"}, - {"TIMESTAMP '1969-12-31 23:59:59.9999994'", "timestamp(6)", "TIMESTAMP '1969-12-31 23:59:59.999999'"}}; - } - @Override protected void verifyAddNotNullColumnToNonEmptyTableFailurePermissible(Throwable e) { diff --git a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftConnectorTest.java b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftConnectorTest.java index 0b458b229d15..cbae36a4dd4a 100644 --- a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftConnectorTest.java +++ b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftConnectorTest.java @@ -23,7 +23,6 @@ import io.trino.testing.sql.TestTable; import io.trino.tpch.TpchTable; import org.testng.SkipException; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.util.List; @@ -40,8 +39,6 @@ import static io.trino.plugin.redshift.RedshiftQueryRunner.createRedshiftQueryRunner; import static io.trino.plugin.redshift.RedshiftQueryRunner.executeInRedshift; import static io.trino.plugin.redshift.RedshiftQueryRunner.executeWithRedshift; -import static io.trino.testing.DataProviders.cartesianProduct; -import static io.trino.testing.DataProviders.trueFalse; import static io.trino.testing.TestingNames.randomNameSuffix; import static java.lang.String.format; import static java.util.Locale.ENGLISH; @@ -160,8 +157,26 @@ public void testCreateTableAsSelectWithUnicode() "SELECT 1"); } - @Test(dataProvider = "redshiftTypeToTrinoTypes") - public void testReadFromLateBindingView(String redshiftType, String trinoType) + @Test + public void testReadFromLateBindingView() + { + testReadFromLateBindingView("SMALLINT", "smallint"); + testReadFromLateBindingView("INTEGER", "integer"); + testReadFromLateBindingView("BIGINT", "bigint"); + testReadFromLateBindingView("DECIMAL", "decimal(18,0)"); + testReadFromLateBindingView("REAL", "real"); + testReadFromLateBindingView("DOUBLE PRECISION", "double"); + testReadFromLateBindingView("BOOLEAN", "boolean"); + testReadFromLateBindingView("CHAR(1)", "char(1)"); + testReadFromLateBindingView("VARCHAR(1)", "varchar(1)"); + // consider to extract "CHARACTER VARYING" type from here as it requires exact length, 0 - is for the empty string + testReadFromLateBindingView("CHARACTER VARYING", "varchar(0)"); + testReadFromLateBindingView("TIME", "time(6)"); + testReadFromLateBindingView("TIMESTAMP", "timestamp(6)"); + testReadFromLateBindingView("TIMESTAMPTZ", "timestamp(6) with time zone"); + } + + private void testReadFromLateBindingView(String redshiftType, String trinoType) { try (TestView view = new TestView(onRemoteDatabase(), TEST_SCHEMA + ".late_schema_binding", "SELECT CAST(NULL AS %s) AS value WITH NO SCHEMA BINDING".formatted(redshiftType))) { assertThat(query("SELECT true FROM %s WHERE value IS NULL".formatted(view.getName()))) @@ -173,8 +188,39 @@ public void testReadFromLateBindingView(String redshiftType, String trinoType) } } - @Test(dataProvider = "testReadNullFromViewDataProvider") - public void testReadNullFromView(String redshiftType, String trinoType, boolean lateBindingView) + @Test + public void testReadNullFromView() + { + testReadNullFromView("SMALLINT", "smallint", true); + testReadNullFromView("SMALLINT", "smallint", false); + testReadNullFromView("INTEGER", "integer", true); + testReadNullFromView("INTEGER", "integer", false); + testReadNullFromView("BIGINT", "bigint", true); + testReadNullFromView("BIGINT", "bigint", false); + testReadNullFromView("DECIMAL", "decimal(18,0)", true); + testReadNullFromView("DECIMAL", "decimal(18,0)", false); + testReadNullFromView("REAL", "real", true); + testReadNullFromView("REAL", "real", false); + testReadNullFromView("DOUBLE PRECISION", "double", true); + testReadNullFromView("DOUBLE PRECISION", "double", false); + testReadNullFromView("BOOLEAN", "boolean", true); + testReadNullFromView("BOOLEAN", "boolean", false); + testReadNullFromView("CHAR(1)", "char(1)", true); + testReadNullFromView("CHAR(1)", "char(1)", false); + testReadNullFromView("VARCHAR(1)", "varchar(1)", true); + testReadNullFromView("VARCHAR(1)", "varchar(1)", false); + // consider to extract "CHARACTER VARYING" type from here as it requires exact length, 0 - is for the empty string + testReadNullFromView("CHARACTER VARYING", "varchar(0)", true); + testReadNullFromView("CHARACTER VARYING", "varchar(0)", false); + testReadNullFromView("TIME", "time(6)", true); + testReadNullFromView("TIME", "time(6)", false); + testReadNullFromView("TIMESTAMP", "timestamp(6)", true); + testReadNullFromView("TIMESTAMP", "timestamp(6)", false); + testReadNullFromView("TIMESTAMPTZ", "timestamp(6) with time zone", true); + testReadNullFromView("TIMESTAMPTZ", "timestamp(6) with time zone", false); + } + + private void testReadNullFromView(String redshiftType, String trinoType, boolean lateBindingView) { try (TestView view = new TestView( onRemoteDatabase(), @@ -190,32 +236,6 @@ public void testReadNullFromView(String redshiftType, String trinoType, boolean } } - @DataProvider - public Object[][] testReadNullFromViewDataProvider() - { - return cartesianProduct(redshiftTypeToTrinoTypes(), trueFalse()); - } - - @DataProvider - public Object[][] redshiftTypeToTrinoTypes() - { - return new Object[][] { - {"SMALLINT", "smallint"}, - {"INTEGER", "integer"}, - {"BIGINT", "bigint"}, - {"DECIMAL", "decimal(18,0)"}, - {"REAL", "real"}, - {"DOUBLE PRECISION", "double"}, - {"BOOLEAN", "boolean"}, - {"CHAR(1)", "char(1)"}, - {"VARCHAR(1)", "varchar(1)"}, - // consider to extract "CHARACTER VARYING" type from here as it requires exact length, 0 - is for the empty string - {"CHARACTER VARYING", "varchar(0)"}, - {"TIME", "time(6)"}, - {"TIMESTAMP", "timestamp(6)"}, - {"TIMESTAMPTZ", "timestamp(6) with time zone"}}; - } - @Test public void testRedshiftAddNotNullColumn() { @@ -252,8 +272,18 @@ public void testDelete() } } - @Test(dataProvider = "testCaseColumnNamesDataProvider") - public void testCaseColumnNames(String tableName) + @Test + public void testCaseColumnNames() + { + testCaseColumnNames("TEST_STATS_MIXED_UNQUOTED_UPPER_" + randomNameSuffix()); + testCaseColumnNames("test_stats_mixed_unquoted_lower_" + randomNameSuffix()); + testCaseColumnNames("test_stats_mixed_uNQuoTeD_miXED_" + randomNameSuffix()); + testCaseColumnNames("\"TEST_STATS_MIXED_QUOTED_UPPER_" + randomNameSuffix() + "\""); + testCaseColumnNames("\"test_stats_mixed_quoted_lower_" + randomNameSuffix() + "\""); + testCaseColumnNames("\"test_stats_mixed_QuoTeD_miXED_" + randomNameSuffix() + "\""); + } + + private void testCaseColumnNames(String tableName) { try { assertUpdate( @@ -339,19 +369,6 @@ private static void gatherStats(String tableName) }); } - @DataProvider - public Object[][] testCaseColumnNamesDataProvider() - { - return new Object[][] { - {"TEST_STATS_MIXED_UNQUOTED_UPPER_" + randomNameSuffix()}, - {"test_stats_mixed_unquoted_lower_" + randomNameSuffix()}, - {"test_stats_mixed_uNQuoTeD_miXED_" + randomNameSuffix()}, - {"\"TEST_STATS_MIXED_QUOTED_UPPER_" + randomNameSuffix() + "\""}, - {"\"test_stats_mixed_quoted_lower_" + randomNameSuffix() + "\""}, - {"\"test_stats_mixed_QuoTeD_miXED_" + randomNameSuffix() + "\""} - }; - } - @Override public void testCountDistinctWithStringTypes() { @@ -380,14 +397,17 @@ public void testCountDistinctWithStringTypes() } } + @Test @Override public void testAggregationPushdown() { - throw new SkipException("tested in testAggregationPushdown(String)"); + testAggregationPushdown("EVEN"); + testAggregationPushdown("KEY"); + testAggregationPushdown("ALL"); + testAggregationPushdown("AUTO"); } - @Test(dataProvider = "testAggregationPushdownDistStylesDataProvider") - public void testAggregationPushdown(String distStyle) + private void testAggregationPushdown(String distStyle) { String nation = format("%s.nation_%s_%s", TEST_SCHEMA, distStyle, randomNameSuffix()); String customer = format("%s.customer_%s_%s", TEST_SCHEMA, distStyle, randomNameSuffix()); @@ -461,14 +481,17 @@ public void testAggregationPushdown(String distStyle) } } + @Test @Override public void testNumericAggregationPushdown() { - throw new SkipException("tested in testNumericAggregationPushdown(String)"); + testNumericAggregationPushdown("EVEN"); + testNumericAggregationPushdown("KEY"); + testNumericAggregationPushdown("ALL"); + testNumericAggregationPushdown("AUTO"); } - @Test(dataProvider = "testAggregationPushdownDistStylesDataProvider") - public void testNumericAggregationPushdown(String distStyle) + private void testNumericAggregationPushdown(String distStyle) { String schemaName = getSession().getSchema().orElseThrow(); // empty table @@ -542,17 +565,6 @@ private static void copyWithDistStyle(String sourceTableName, String destTableNa } } - @DataProvider - public Object[][] testAggregationPushdownDistStylesDataProvider() - { - return new Object[][] { - {"EVEN"}, - {"KEY"}, - {"ALL"}, - {"AUTO"}, - }; - } - @Test public void testDecimalAvgPushdownForMaximumDecimalScale() { diff --git a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerConnectorTest.java b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerConnectorTest.java index aedbd809daef..cdb4cfc3dc39 100644 --- a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerConnectorTest.java +++ b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerConnectorTest.java @@ -26,7 +26,6 @@ import io.trino.testing.sql.TestTable; import io.trino.testng.services.Flaky; import org.testng.SkipException; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.util.List; @@ -468,8 +467,15 @@ public void testShowCreateTable() ")"); } - @Test(dataProvider = "dataCompression") - public void testCreateWithDataCompression(DataCompression dataCompression) + @Test + public void testCreateWithDataCompression() + { + testCreateWithDataCompression(NONE); + testCreateWithDataCompression(ROW); + testCreateWithDataCompression(PAGE); + } + + private void testCreateWithDataCompression(DataCompression dataCompression) { String tableName = "test_create_with_compression_" + randomNameSuffix(); String createQuery = format("CREATE TABLE sqlserver.dbo.%s (\n" + @@ -488,16 +494,6 @@ public void testCreateWithDataCompression(DataCompression dataCompression) assertUpdate("DROP TABLE " + tableName); } - @DataProvider - public Object[][] dataCompression() - { - return new Object[][] { - {NONE}, - {ROW}, - {PAGE} - }; - } - @Test public void testShowCreateForPartitionedTablesWithDataCompression() { diff --git a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerConnectorTest.java b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerConnectorTest.java index fb536abb1e89..04d26ec7e8e4 100644 --- a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerConnectorTest.java +++ b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerConnectorTest.java @@ -22,7 +22,6 @@ import io.trino.testng.services.Flaky; import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.Jdbi; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.sql.Connection; @@ -34,9 +33,6 @@ import static io.trino.plugin.sqlserver.SqlServerQueryRunner.createSqlServerQueryRunner; import static io.trino.plugin.sqlserver.SqlServerSessionProperties.BULK_COPY_FOR_WRITE; import static io.trino.plugin.sqlserver.SqlServerSessionProperties.BULK_COPY_FOR_WRITE_LOCK_DESTINATION_TABLE; -import static io.trino.testing.DataProviders.cartesianProduct; -import static io.trino.testing.DataProviders.toDataProvider; -import static io.trino.testing.DataProviders.trueFalse; import static io.trino.testing.TestingNames.randomNameSuffix; import static java.lang.String.format; import static java.util.Locale.ENGLISH; @@ -69,8 +65,17 @@ protected SqlExecutor onRemoteDatabase() } @Flaky(issue = "fn_dblog() returns information only about the active portion of the transaction log, therefore it is flaky", match = ".*") - @Test(dataProvider = "doubleTrueFalse") - public void testCreateTableAsSelectWriteBulkiness(boolean bulkCopyForWrite, boolean bulkCopyLock) + @Test + public void testCreateTableAsSelectWriteBulkiness() + throws SQLException + { + testCreateTableAsSelectWriteBulkiness(true, true); + testCreateTableAsSelectWriteBulkiness(true, false); + testCreateTableAsSelectWriteBulkiness(false, true); + testCreateTableAsSelectWriteBulkiness(false, false); + } + + private void testCreateTableAsSelectWriteBulkiness(boolean bulkCopyForWrite, boolean bulkCopyLock) throws SQLException { String table = "bulk_copy_ctas_" + randomNameSuffix(); @@ -98,8 +103,21 @@ public void testCreateTableAsSelectWriteBulkiness(boolean bulkCopyForWrite, bool } @Flaky(issue = "fn_dblog() returns information only about the active portion of the transaction log, therefore it is flaky", match = ".*") - @Test(dataProvider = "tripleTrueFalse") - public void testInsertWriteBulkiness(boolean nonTransactionalInsert, boolean bulkCopyForWrite, boolean bulkCopyForWriteLockDestinationTable) + @Test + public void testInsertWriteBulkiness() + throws SQLException + { + testInsertWriteBulkiness(true, true, true); + testInsertWriteBulkiness(true, true, false); + testInsertWriteBulkiness(true, false, true); + testInsertWriteBulkiness(true, false, false); + testInsertWriteBulkiness(false, true, true); + testInsertWriteBulkiness(false, true, false); + testInsertWriteBulkiness(false, false, true); + testInsertWriteBulkiness(false, false, false); + } + + private void testInsertWriteBulkiness(boolean nonTransactionalInsert, boolean bulkCopyForWrite, boolean bulkCopyForWriteLockDestinationTable) throws SQLException { String table = "bulk_copy_insert_" + randomNameSuffix(); @@ -128,8 +146,17 @@ public void testInsertWriteBulkiness(boolean nonTransactionalInsert, boolean bul assertUpdate("DROP TABLE " + table); } - @Test(dataProvider = "timestampTypes") - public void testInsertWriteBulkinessWithTimestamps(String timestampType) + @Test + public void testInsertWriteBulkinessWithTimestamps() + { + testInsertWriteBulkinessWithTimestamps("timestamp"); + testInsertWriteBulkinessWithTimestamps("timestamp(3)"); + testInsertWriteBulkinessWithTimestamps("timestamp(6)"); + testInsertWriteBulkinessWithTimestamps("timestamp(9)"); + testInsertWriteBulkinessWithTimestamps("timestamp(12)"); + } + + private void testInsertWriteBulkinessWithTimestamps(String timestampType) { Session session = Session.builder(getSession()) .setCatalogSessionProperty(CATALOG, BULK_COPY_FOR_WRITE, "true") @@ -162,51 +189,57 @@ public void testInsertWriteBulkinessWithTimestamps(String timestampType) } // TODO move test to BaseConnectorTest https://github.com/trinodb/trino/issues/14517 - @Test(dataProvider = "testTableNameDataProvider") - public void testCreateAndDropTableWithSpecialCharacterName(String tableName) + @Test + public void testCreateAndDropTableWithSpecialCharacterName() { - String tableNameInSql = "\"" + tableName.replace("\"", "\"\"") + "\""; - // Until https://github.com/trinodb/trino/issues/17 the table name is effectively lowercase - tableName = tableName.toLowerCase(ENGLISH); - assertUpdate("CREATE TABLE " + tableNameInSql + " (a bigint, b double, c varchar(50))"); - assertTrue(getQueryRunner().tableExists(getSession(), tableName)); - assertTableColumnNames(tableNameInSql, "a", "b", "c"); - - assertUpdate("DROP TABLE " + tableNameInSql); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + for (String tableName : testTableNameTestData()) { + String tableNameInSql = "\"" + tableName.replace("\"", "\"\"") + "\""; + // Until https://github.com/trinodb/trino/issues/17 the table name is effectively lowercase + tableName = tableName.toLowerCase(ENGLISH); + assertUpdate("CREATE TABLE " + tableNameInSql + " (a bigint, b double, c varchar(50))"); + assertTrue(getQueryRunner().tableExists(getSession(), tableName)); + assertTableColumnNames(tableNameInSql, "a", "b", "c"); + + assertUpdate("DROP TABLE " + tableNameInSql); + assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + } } // TODO remove this test after https://github.com/trinodb/trino/issues/14517 - @Test(dataProvider = "testTableNameDataProvider") - public void testRenameColumnNameAdditionalTests(String columnName) + @Test + public void testRenameColumnNameAdditionalTests() { - String nameInSql = "\"" + columnName.replace("\"", "\"\"") + "\""; - String tableName = "tcn_" + nameInSql.replaceAll("[^a-z0-9]", "") + randomNameSuffix(); - // Use complex identifier to test a source column name when renaming columns - String sourceColumnName = "a;b$c"; + for (String columnName : testTableNameTestData()) { + String nameInSql = "\"" + columnName.replace("\"", "\"\"") + "\""; + String tableName = "tcn_" + nameInSql.replaceAll("[^a-z0-9]", "") + randomNameSuffix(); + // Use complex identifier to test a source column name when renaming columns + String sourceColumnName = "a;b$c"; - assertUpdate("CREATE TABLE " + tableName + "(\"" + sourceColumnName + "\" varchar(50))"); - assertTableColumnNames(tableName, sourceColumnName); + assertUpdate("CREATE TABLE " + tableName + "(\"" + sourceColumnName + "\" varchar(50))"); + assertTableColumnNames(tableName, sourceColumnName); - assertUpdate("ALTER TABLE " + tableName + " RENAME COLUMN \"" + sourceColumnName + "\" TO " + nameInSql); - assertTableColumnNames(tableName, columnName.toLowerCase(ENGLISH)); + assertUpdate("ALTER TABLE " + tableName + " RENAME COLUMN \"" + sourceColumnName + "\" TO " + nameInSql); + assertTableColumnNames(tableName, columnName.toLowerCase(ENGLISH)); - assertUpdate("DROP TABLE " + tableName); + assertUpdate("DROP TABLE " + tableName); + } } // TODO move this test to BaseConnectorTest https://github.com/trinodb/trino/issues/14517 - @Test(dataProvider = "testTableNameDataProvider") - public void testRenameFromToTableWithSpecialCharacterName(String tableName) + @Test + public void testRenameFromToTableWithSpecialCharacterName() { - String tableNameInSql = "\"" + tableName.replace("\"", "\"\"") + "\""; - String sourceTableName = "test_rename_source_" + randomNameSuffix(); - assertUpdate("CREATE TABLE " + sourceTableName + " AS SELECT 123 x", 1); - - assertUpdate("ALTER TABLE " + sourceTableName + " RENAME TO " + tableNameInSql); - assertQuery("SELECT x FROM " + tableNameInSql, "VALUES 123"); - // test rename back is working properly - assertUpdate("ALTER TABLE " + tableNameInSql + " RENAME TO " + sourceTableName); - assertUpdate("DROP TABLE " + sourceTableName); + for (String tableName : testTableNameTestData()) { + String tableNameInSql = "\"" + tableName.replace("\"", "\"\"") + "\""; + String sourceTableName = "test_rename_source_" + randomNameSuffix(); + assertUpdate("CREATE TABLE " + sourceTableName + " AS SELECT 123 x", 1); + + assertUpdate("ALTER TABLE " + sourceTableName + " RENAME TO " + tableNameInSql); + assertQuery("SELECT x FROM " + tableNameInSql, "VALUES 123"); + // test rename back is working properly + assertUpdate("ALTER TABLE " + tableNameInSql + " RENAME TO " + sourceTableName); + assertUpdate("DROP TABLE " + sourceTableName); + } } private int getTableOperationsCount(String operation, String table) @@ -229,41 +262,6 @@ private int getTableOperationsCount(String operation, String table) } } - @DataProvider - public static Object[][] doubleTrueFalse() - { - return cartesianProduct(trueFalse(), trueFalse()); - } - - @DataProvider - public static Object[][] tripleTrueFalse() - { - return cartesianProduct(trueFalse(), trueFalse(), trueFalse()); - } - - @DataProvider - public static Object[][] timestampTypes() - { - // Timestamp with timezone is not supported by the SqlServer connector - return new Object[][] { - {"timestamp"}, - {"timestamp(3)"}, - {"timestamp(6)"}, - {"timestamp(9)"}, - {"timestamp(12)"} - }; - } - - // TODO replace TableNameDataProvider and ColumnNameDataProvider with ObjectNameDataProvider - // to one big single list of all special character cases, current list has additional special bracket cases, - // please don't forget to use this list as base - @DataProvider - public Object[][] testTableNameDataProvider() - { - return testTableNameTestData().stream() - .collect(toDataProvider()); - } - private List testTableNameTestData() { return ImmutableList.builder() diff --git a/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/hive/TestHiveFaultTolerantExecutionConnectorTest.java b/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/hive/TestHiveFaultTolerantExecutionConnectorTest.java index b79ba9ff5b71..5503833738e2 100644 --- a/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/hive/TestHiveFaultTolerantExecutionConnectorTest.java +++ b/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/hive/TestHiveFaultTolerantExecutionConnectorTest.java @@ -73,7 +73,7 @@ public void testTaskWritersDoesNotScaleWithLargeMinWriterSize() } @Override - public void testWriterTasksCountLimitUnpartitioned(boolean scaleWriters, boolean redistributeWrites, int expectedFilesCount) + public void testWriterTasksCountLimitUnpartitioned() { // Not applicable for fault-tolerant mode. } diff --git a/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java b/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java index b7695f780e1e..32d01a7be321 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java @@ -50,7 +50,6 @@ import org.junit.jupiter.api.parallel.Isolated; import org.testng.SkipException; import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.time.Instant; @@ -104,7 +103,6 @@ import static io.trino.sql.planner.assertions.PlanMatchPattern.tableScan; import static io.trino.sql.planner.optimizations.PlanNodeSearcher.searchFrom; import static io.trino.sql.planner.planprinter.PlanPrinter.textLogicalPlan; -import static io.trino.testing.DataProviders.toDataProvider; import static io.trino.testing.MaterializedResult.resultBuilder; import static io.trino.testing.QueryAssertions.assertContains; import static io.trino.testing.QueryAssertions.assertEqualsIgnoreOrder; @@ -743,23 +741,18 @@ protected void verifyVersionedQueryFailurePermissible(Exception e) /** * Test interactions between optimizer (including CBO), scheduling and connector metadata APIs. */ - @Test(dataProvider = "joinDistributionTypes") - public void testJoinWithEmptySides(JoinDistributionType joinDistributionType) - { - Session session = noJoinReordering(joinDistributionType); - // empty build side - assertQuery(session, "SELECT count(*) FROM nation JOIN region ON nation.regionkey = region.regionkey AND region.name = ''", "VALUES 0"); - assertQuery(session, "SELECT count(*) FROM nation JOIN region ON nation.regionkey = region.regionkey AND region.regionkey < 0", "VALUES 0"); - // empty probe side - assertQuery(session, "SELECT count(*) FROM region JOIN nation ON nation.regionkey = region.regionkey AND region.name = ''", "VALUES 0"); - assertQuery(session, "SELECT count(*) FROM nation JOIN region ON nation.regionkey = region.regionkey AND region.regionkey < 0", "VALUES 0"); - } - - @DataProvider - public Object[][] joinDistributionTypes() + @Test + public void testJoinWithEmptySides() { - return Stream.of(JoinDistributionType.values()) - .collect(toDataProvider()); + for (JoinDistributionType joinDistributionType : JoinDistributionType.values()) { + Session session = noJoinReordering(joinDistributionType); + // empty build side + assertQuery(session, "SELECT count(*) FROM nation JOIN region ON nation.regionkey = region.regionkey AND region.name = ''", "VALUES 0"); + assertQuery(session, "SELECT count(*) FROM nation JOIN region ON nation.regionkey = region.regionkey AND region.regionkey < 0", "VALUES 0"); + // empty probe side + assertQuery(session, "SELECT count(*) FROM region JOIN nation ON nation.regionkey = region.regionkey AND region.name = ''", "VALUES 0"); + assertQuery(session, "SELECT count(*) FROM nation JOIN region ON nation.regionkey = region.regionkey AND region.regionkey < 0", "VALUES 0"); + } } /** @@ -1562,8 +1555,14 @@ public void testFederatedMaterializedViewWithGracePeriod() }); } - @Test(dataProviderClass = DataProviders.class, dataProvider = "trueFalse") - public void testMaterializedViewBaseTableGone(boolean initialized) + @Test + public void testMaterializedViewBaseTableGone() + { + testMaterializedViewBaseTableGone(true); + testMaterializedViewBaseTableGone(false); + } + + private void testMaterializedViewBaseTableGone(boolean initialized) { skipTestUnless(hasBehavior(SUPPORTS_CREATE_MATERIALIZED_VIEW)); @@ -1689,8 +1688,15 @@ public void testCompatibleTypeChangeForView2() assertUpdate("DROP TABLE " + tableName); } - @Test(dataProvider = "testViewMetadataDataProvider") - public void testViewMetadata(String securityClauseInCreate, String securityClauseInShowCreate) + @Test + public void testViewMetadata() + { + testViewMetadata("", "DEFINER"); + testViewMetadata(" SECURITY DEFINER", "DEFINER"); + testViewMetadata(" SECURITY INVOKER", "INVOKER"); + } + + private void testViewMetadata(String securityClauseInCreate, String securityClauseInShowCreate) { skipTestUnless(hasBehavior(SUPPORTS_CREATE_VIEW)); @@ -1763,16 +1769,6 @@ public void testViewMetadata(String securityClauseInCreate, String securityClaus assertUpdate("DROP VIEW " + viewName); } - @DataProvider - public static Object[][] testViewMetadataDataProvider() - { - return new Object[][] { - {"", "DEFINER"}, - {" SECURITY DEFINER", "DEFINER"}, - {" SECURITY INVOKER", "INVOKER"}, - }; - } - @Test public void testShowCreateView() { @@ -2934,42 +2930,43 @@ public void testSetColumnType() } } - @Test(dataProvider = "setColumnTypesDataProvider") - public void testSetColumnTypes(SetColumnTypeSetup setup) + @Test + public void testSetColumnTypes() { skipTestUnless(hasBehavior(SUPPORTS_SET_COLUMN_TYPE) && hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA)); - TestTable table; - try { - table = new TestTable(getQueryRunner()::execute, "test_set_column_type_", " AS SELECT CAST(" + setup.sourceValueLiteral + " AS " + setup.sourceColumnType + ") AS col"); - } - catch (Exception e) { - verifyUnsupportedTypeException(e, setup.sourceColumnType); - throw new SkipException("Unsupported column type: " + setup.sourceColumnType); - } - try (table) { - Runnable setColumnType = () -> assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col SET DATA TYPE " + setup.newColumnType); - if (setup.unsupportedType) { - assertThatThrownBy(setColumnType::run) - .satisfies(this::verifySetColumnTypeFailurePermissible); - return; + for (SetColumnTypeSetup setup : setColumnTypesDataProvider()) { + TestTable table; + try { + table = new TestTable(getQueryRunner()::execute, "test_set_column_type_", " AS SELECT CAST(" + setup.sourceValueLiteral + " AS " + setup.sourceColumnType + ") AS col"); } - setColumnType.run(); + catch (Exception e) { + verifyUnsupportedTypeException(e, setup.sourceColumnType); + throw new SkipException("Unsupported column type: " + setup.sourceColumnType); + } + try (table) { + Runnable setColumnType = () -> assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col SET DATA TYPE " + setup.newColumnType); + if (setup.unsupportedType) { + assertThatThrownBy(setColumnType::run) + .satisfies(this::verifySetColumnTypeFailurePermissible); + return; + } + setColumnType.run(); - assertEquals(getColumnType(table.getName(), "col"), setup.newColumnType); - assertThat(query("SELECT * FROM " + table.getName())) - .skippingTypesCheck() - .matches("SELECT " + setup.newValueLiteral); + assertEquals(getColumnType(table.getName(), "col"), setup.newColumnType); + assertThat(query("SELECT * FROM " + table.getName())) + .skippingTypesCheck() + .matches("SELECT " + setup.newValueLiteral); + } } } - @DataProvider - public Object[][] setColumnTypesDataProvider() + private List setColumnTypesDataProvider() { return setColumnTypeSetupData().stream() .map(this::filterSetColumnTypesDataProvider) .flatMap(Optional::stream) - .collect(toDataProvider()); + .collect(toList()); } protected Optional filterSetColumnTypesDataProvider(SetColumnTypeSetup setup) @@ -3139,45 +3136,46 @@ public void testSetFieldType() } } - @Test(dataProvider = "setFieldTypesDataProvider") - public void testSetFieldTypes(SetColumnTypeSetup setup) + @Test + public void testSetFieldTypes() { skipTestUnless(hasBehavior(SUPPORTS_SET_FIELD_TYPE) && hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA)); - TestTable table; - try { - table = new TestTable( - getQueryRunner()::execute, - "test_set_field_type_", - " AS SELECT CAST(row(" + setup.sourceValueLiteral + ") AS row(field " + setup.sourceColumnType + ")) AS col"); - } - catch (Exception e) { - verifyUnsupportedTypeException(e, setup.sourceColumnType); - throw new SkipException("Unsupported column type: " + setup.sourceColumnType); - } - try (table) { - Runnable setFieldType = () -> assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col.field SET DATA TYPE " + setup.newColumnType); - if (setup.unsupportedType) { - assertThatThrownBy(setFieldType::run) - .satisfies(this::verifySetFieldTypeFailurePermissible); - return; + for (SetColumnTypeSetup setup : setFieldTypesDataProvider()) { + TestTable table; + try { + table = new TestTable( + getQueryRunner()::execute, + "test_set_field_type_", + " AS SELECT CAST(row(" + setup.sourceValueLiteral + ") AS row(field " + setup.sourceColumnType + ")) AS col"); + } + catch (Exception e) { + verifyUnsupportedTypeException(e, setup.sourceColumnType); + throw new SkipException("Unsupported column type: " + setup.sourceColumnType); } - setFieldType.run(); + try (table) { + Runnable setFieldType = () -> assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col.field SET DATA TYPE " + setup.newColumnType); + if (setup.unsupportedType) { + assertThatThrownBy(setFieldType::run) + .satisfies(this::verifySetFieldTypeFailurePermissible); + return; + } + setFieldType.run(); - assertEquals(getColumnType(table.getName(), "col"), "row(field " + setup.newColumnType + ")"); - assertThat(query("SELECT * FROM " + table.getName())) - .skippingTypesCheck() - .matches("SELECT row(" + setup.newValueLiteral + ")"); + assertEquals(getColumnType(table.getName(), "col"), "row(field " + setup.newColumnType + ")"); + assertThat(query("SELECT * FROM " + table.getName())) + .skippingTypesCheck() + .matches("SELECT row(" + setup.newValueLiteral + ")"); + } } } - @DataProvider - public Object[][] setFieldTypesDataProvider() + public List setFieldTypesDataProvider() { return setColumnTypeSetupData().stream() .map(this::filterSetFieldTypesDataProvider) .flatMap(Optional::stream) - .collect(toDataProvider()); + .collect(toList()); } protected Optional filterSetFieldTypesDataProvider(SetColumnTypeSetup setup) @@ -4162,15 +4160,14 @@ public void testCommentColumn() } } - @Test(dataProvider = "testColumnNameDataProvider") - public void testCommentColumnName(String columnName) + @Test + public void testCommentColumnName() { skipTestUnless(hasBehavior(SUPPORTS_COMMENT_ON_COLUMN)); - if (!requiresDelimiting(columnName)) { - testCommentColumnName(columnName, false); + for (String columnName : testColumnNameDataProvider()) { + testCommentColumnName(columnName, requiresDelimiting(columnName)); } - testCommentColumnName(columnName, true); } protected void testCommentColumnName(String columnName, boolean delimited) @@ -5370,15 +5367,14 @@ public void testNoDataSystemTable() assertQueryFails("TABLE \"nation$data\"", "line 1:1: Table '\\w+.\\w+.\"nation\\$data\"' does not exist"); } - @Test(dataProvider = "testColumnNameDataProvider") - public void testColumnName(String columnName) + @Test + public void testColumnName() { skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE)); - if (!requiresDelimiting(columnName)) { - testColumnName(columnName, false); + for (String columnName : testColumnNameDataProvider()) { + testColumnName(columnName, requiresDelimiting(columnName)); } - testColumnName(columnName, true); } protected void testColumnName(String columnName, boolean delimited) @@ -5415,15 +5411,14 @@ protected void testColumnName(String columnName, boolean delimited) } } - @Test(dataProvider = "testColumnNameDataProvider") - public void testAddAndDropColumnName(String columnName) + @Test + public void testAddAndDropColumnName() { skipTestUnless(hasBehavior(SUPPORTS_ADD_COLUMN) && hasBehavior(SUPPORTS_DROP_COLUMN)); - if (!requiresDelimiting(columnName)) { - testAddAndDropColumnName(columnName, false); + for (String columnName : testColumnNameDataProvider()) { + testAddAndDropColumnName(columnName, requiresDelimiting(columnName)); } - testAddAndDropColumnName(columnName, true); } protected void testAddAndDropColumnName(String columnName, boolean delimited) @@ -5460,15 +5455,14 @@ protected String createTableSqlForAddingAndDroppingColumn(String tableName, Stri return "CREATE TABLE " + tableName + "(" + columnNameInSql + " varchar(50), value varchar(50))"; } - @Test(dataProvider = "testColumnNameDataProvider") - public void testRenameColumnName(String columnName) + @Test + public void testRenameColumnName() { skipTestUnless(hasBehavior(SUPPORTS_RENAME_COLUMN)); - if (!requiresDelimiting(columnName)) { - testRenameColumnName(columnName, false); + for (String columnName : testColumnNameDataProvider()) { + testRenameColumnName(columnName, requiresDelimiting(columnName)); } - testRenameColumnName(columnName, true); } protected void testRenameColumnName(String columnName, boolean delimited) @@ -5515,14 +5509,13 @@ protected static boolean requiresDelimiting(String identifierName) return !identifierName.matches("[a-zA-Z][a-zA-Z0-9_]*"); } - @DataProvider - public Object[][] testColumnNameDataProvider() + public List testColumnNameDataProvider() { return testColumnNameTestData().stream() .map(this::filterColumnNameTestData) .filter(Optional::isPresent) .map(Optional::get) - .collect(toDataProvider()); + .collect(toList()); } private List testColumnNameTestData() @@ -5561,8 +5554,21 @@ protected String dataMappingTableName(String trinoTypeName) return "test_data_mapping_smoke_" + trinoTypeName.replaceAll("[^a-zA-Z0-9]", "_") + randomNameSuffix(); } - @Test(dataProvider = "testCommentDataProvider") - public void testCreateTableWithTableCommentSpecialCharacter(String comment) + @Test + public void testCreateTableWithTableCommentSpecialCharacter() + { + testCreateTableWithTableCommentSpecialCharacter("a;semicolon"); + testCreateTableWithTableCommentSpecialCharacter("an@at"); + testCreateTableWithTableCommentSpecialCharacter("a\"quote"); + testCreateTableWithTableCommentSpecialCharacter("an'apostrophe"); + testCreateTableWithTableCommentSpecialCharacter("a`backtick`"); + testCreateTableWithTableCommentSpecialCharacter("a/slash"); + testCreateTableWithTableCommentSpecialCharacter("a\\backslash"); + testCreateTableWithTableCommentSpecialCharacter("a?question"); + testCreateTableWithTableCommentSpecialCharacter("[square bracket]"); + } + + protected void testCreateTableWithTableCommentSpecialCharacter(String comment) { skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE_WITH_TABLE_COMMENT)); @@ -5571,8 +5577,21 @@ public void testCreateTableWithTableCommentSpecialCharacter(String comment) } } - @Test(dataProvider = "testCommentDataProvider") - public void testCreateTableAsSelectWithTableCommentSpecialCharacter(String comment) + @Test + public void testCreateTableAsSelectWithTableCommentSpecialCharacter() + { + testCreateTableAsSelectWithTableCommentSpecialCharacter("a;semicolon"); + testCreateTableAsSelectWithTableCommentSpecialCharacter("an@at"); + testCreateTableAsSelectWithTableCommentSpecialCharacter("a\"quote"); + testCreateTableAsSelectWithTableCommentSpecialCharacter("an'apostrophe"); + testCreateTableAsSelectWithTableCommentSpecialCharacter("a`backtick`"); + testCreateTableAsSelectWithTableCommentSpecialCharacter("a/slash"); + testCreateTableAsSelectWithTableCommentSpecialCharacter("a\\backslash"); + testCreateTableAsSelectWithTableCommentSpecialCharacter("a?question"); + testCreateTableAsSelectWithTableCommentSpecialCharacter("[square bracket]"); + } + + private void testCreateTableAsSelectWithTableCommentSpecialCharacter(String comment) { skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA) && hasBehavior(SUPPORTS_CREATE_TABLE_WITH_TABLE_COMMENT)); @@ -5581,8 +5600,21 @@ public void testCreateTableAsSelectWithTableCommentSpecialCharacter(String comme } } - @Test(dataProvider = "testCommentDataProvider") - public void testCreateTableWithColumnCommentSpecialCharacter(String comment) + @Test + public void testCreateTableWithColumnCommentSpecialCharacter() + { + testCreateTableWithColumnCommentSpecialCharacter("a;semicolon"); + testCreateTableWithColumnCommentSpecialCharacter("an@at"); + testCreateTableWithColumnCommentSpecialCharacter("a\"quote"); + testCreateTableWithColumnCommentSpecialCharacter("an'apostrophe"); + testCreateTableWithColumnCommentSpecialCharacter("a`backtick`"); + testCreateTableWithColumnCommentSpecialCharacter("a/slash"); + testCreateTableWithColumnCommentSpecialCharacter("a\\backslash"); + testCreateTableWithColumnCommentSpecialCharacter("a?question"); + testCreateTableWithColumnCommentSpecialCharacter("[square bracket]"); + } + + private void testCreateTableWithColumnCommentSpecialCharacter(String comment) { skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE_WITH_COLUMN_COMMENT)); @@ -5591,8 +5623,21 @@ public void testCreateTableWithColumnCommentSpecialCharacter(String comment) } } - @Test(dataProvider = "testCommentDataProvider") - public void testAddColumnWithCommentSpecialCharacter(String comment) + @Test + public void testAddColumnWithCommentSpecialCharacter() + { + testAddColumnWithCommentSpecialCharacter("a;semicolon"); + testAddColumnWithCommentSpecialCharacter("an@at"); + testAddColumnWithCommentSpecialCharacter("a\"quote"); + testAddColumnWithCommentSpecialCharacter("an'apostrophe"); + testAddColumnWithCommentSpecialCharacter("a`backtick`"); + testAddColumnWithCommentSpecialCharacter("a/slash"); + testAddColumnWithCommentSpecialCharacter("a\\backslash"); + testAddColumnWithCommentSpecialCharacter("a?question"); + testAddColumnWithCommentSpecialCharacter("[square bracket]"); + } + + protected void testAddColumnWithCommentSpecialCharacter(String comment) { skipTestUnless(hasBehavior(SUPPORTS_ADD_COLUMN_WITH_COMMENT)); @@ -5602,8 +5647,21 @@ public void testAddColumnWithCommentSpecialCharacter(String comment) } } - @Test(dataProvider = "testCommentDataProvider") - public void testCommentTableSpecialCharacter(String comment) + @Test + public void testCommentTableSpecialCharacter() + { + testCommentTableSpecialCharacter("a;semicolon"); + testCommentTableSpecialCharacter("an@at"); + testCommentTableSpecialCharacter("a\"quote"); + testCommentTableSpecialCharacter("an'apostrophe"); + testCommentTableSpecialCharacter("a`backtick`"); + testCommentTableSpecialCharacter("a/slash"); + testCommentTableSpecialCharacter("a\\backslash"); + testCommentTableSpecialCharacter("a?question"); + testCommentTableSpecialCharacter("[square bracket]"); + } + + private void testCommentTableSpecialCharacter(String comment) { skipTestUnless(hasBehavior(SUPPORTS_COMMENT_ON_TABLE)); @@ -5613,8 +5671,21 @@ public void testCommentTableSpecialCharacter(String comment) } } - @Test(dataProvider = "testCommentDataProvider") - public void testCommentColumnSpecialCharacter(String comment) + @Test + public void testCommentColumnSpecialCharacter() + { + testCommentColumnSpecialCharacter("a;semicolon"); + testCommentColumnSpecialCharacter("an@at"); + testCommentColumnSpecialCharacter("a\"quote"); + testCommentColumnSpecialCharacter("an'apostrophe"); + testCommentColumnSpecialCharacter("a`backtick`"); + testCommentColumnSpecialCharacter("a/slash"); + testCommentColumnSpecialCharacter("a\\backslash"); + testCommentColumnSpecialCharacter("a?question"); + testCommentColumnSpecialCharacter("[square bracket]"); + } + + private void testCommentColumnSpecialCharacter(String comment) { skipTestUnless(hasBehavior(SUPPORTS_COMMENT_ON_COLUMN)); @@ -5624,38 +5695,24 @@ public void testCommentColumnSpecialCharacter(String comment) } } - @DataProvider - public Object[][] testCommentDataProvider() - { - return new Object[][] { - {"a;semicolon"}, - {"an@at"}, - {"a\"quote"}, - {"an'apostrophe"}, - {"a`backtick`"}, - {"a/slash"}, - {"a\\backslash"}, - {"a?question"}, - {"[square bracket]"}, - }; - } - protected static String varcharLiteral(String value) { requireNonNull(value, "value is null"); return "'" + value.replace("'", "''") + "'"; } - @Test(dataProvider = "testDataMappingSmokeTestDataProvider") - public void testDataMappingSmokeTest(DataMappingTestSetup dataMappingTestSetup) + @Test + public void testDataMappingSmokeTest() { - testDataMapping(dataMappingTestSetup); + skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE)); + + for (DataMappingTestSetup dataMappingTestSetup : testDataMappingSmokeTestDataProvider()) { + testDataMapping(dataMappingTestSetup); + } } private void testDataMapping(DataMappingTestSetup dataMappingTestSetup) { - skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE)); - String trinoTypeName = dataMappingTestSetup.getTrinoTypeName(); String sampleValueLiteral = dataMappingTestSetup.getSampleValueLiteral(); String highValueLiteral = dataMappingTestSetup.getHighValueLiteral(); @@ -5707,13 +5764,12 @@ private void testDataMapping(DataMappingTestSetup dataMappingTestSetup) assertUpdate("DROP TABLE " + tableName); } - @DataProvider - public final Object[][] testDataMappingSmokeTestDataProvider() + public final List testDataMappingSmokeTestDataProvider() { return testDataMappingSmokeTestData().stream() .map(this::filterDataMappingSmokeTestData) .flatMap(Optional::stream) - .collect(toDataProvider()); + .collect(toList()); } private List testDataMappingSmokeTestData() @@ -5756,19 +5812,22 @@ protected Optional filterDataMappingSmokeTestData(DataMapp return Optional.of(dataMappingTestSetup); } - @Test(dataProvider = "testCaseSensitiveDataMappingProvider") - public void testCaseSensitiveDataMapping(DataMappingTestSetup dataMappingTestSetup) + @Test + public void testCaseSensitiveDataMapping() { - testDataMapping(dataMappingTestSetup); + skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE)); + + for (DataMappingTestSetup dataMappingTestSetup : testCaseSensitiveDataMappingProvider()) { + testDataMapping(dataMappingTestSetup); + } } - @DataProvider - public final Object[][] testCaseSensitiveDataMappingProvider() + private List testCaseSensitiveDataMappingProvider() { return testCaseSensitiveDataMappingData().stream() .map(this::filterCaseSensitiveDataMappingTestData) .flatMap(Optional::stream) - .collect(toDataProvider()); + .collect(toList()); } protected Optional filterCaseSensitiveDataMappingTestData(DataMappingTestSetup dataMappingTestSetup) @@ -6444,15 +6503,14 @@ private void verifyUnsupportedTypeException(Throwable exception, String trinoTyp .satisfies(e -> assertThat(getTrinoExceptionCause(e)).hasMessageFindingMatch(expectedMessagePart)); } - @Test(dataProvider = "testColumnNameDataProvider") - public void testMaterializedViewColumnName(String columnName) + @Test + public void testMaterializedViewColumnName() { skipTestUnless(hasBehavior(SUPPORTS_CREATE_MATERIALIZED_VIEW)); - if (!requiresDelimiting(columnName)) { - testMaterializedViewColumnName(columnName, false); + for (String columnName : testColumnNameDataProvider()) { + testMaterializedViewColumnName(columnName, requiresDelimiting(columnName)); } - testMaterializedViewColumnName(columnName, true); } private void testMaterializedViewColumnName(String columnName, boolean delimited) diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java b/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java index e1a083b7c8ec..200f0ddf307f 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java @@ -50,7 +50,6 @@ import io.trino.testing.DistributedQueryRunner; import io.trino.testing.QueryRunner; import org.intellij.lang.annotations.Language; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.io.File; @@ -1190,8 +1189,19 @@ public void testConnectorMetrics() assertThat(connectorMetrics).containsExactly(TEST_METRICS); } - @Test(dataProvider = "setOperator") - public void testOutputColumnsForSetOperations(String setOperator) + @Test + public void testOutputColumnsForSetOperations() + throws Exception + { + testOutputColumnsForSetOperations("UNION"); + testOutputColumnsForSetOperations("UNION ALL"); + testOutputColumnsForSetOperations("INTERSECT"); + testOutputColumnsForSetOperations("INTERSECT ALL"); + testOutputColumnsForSetOperations("EXCEPT"); + testOutputColumnsForSetOperations("EXCEPT ALL"); + } + + private void testOutputColumnsForSetOperations(String setOperator) throws Exception { assertLineage( @@ -1211,18 +1221,6 @@ public void testOutputColumnsForSetOperations(String setOperator) new ColumnDetail("tpch", "sf1", "orders", "custkey")))); } - @DataProvider - public Object[][] setOperator() - { - return new Object[][]{ - {"UNION"}, - {"UNION ALL"}, - {"INTERSECT"}, - {"INTERSECT ALL"}, - {"EXCEPT"}, - {"EXCEPT ALL"}}; - } - @Test public void testAnonymizedJsonPlan() throws Exception diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestRevokeOnTable.java b/testing/trino-tests/src/test/java/io/trino/execution/TestRevokeOnTable.java index 404c0867aa6c..50b1324004f8 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestRevokeOnTable.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestRevokeOnTable.java @@ -29,7 +29,6 @@ import io.trino.testing.DistributedQueryRunner; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.util.EnumSet; @@ -87,8 +86,18 @@ public void teardown() queryRunner = null; // closed by assertions.close } - @Test(dataProvider = "privilegesAndUsers") - public void testRevokeOnSchema(String privilege, Session user) + @Test + public void testRevokeOnSchema() + { + testRevokeOnSchema("CREATE", userWithCreate); + testRevokeOnSchema("SELECT", userWithSelect); + testRevokeOnSchema("INSERT", userWithInsert); + testRevokeOnSchema("UPDATE", userWithUpdate); + testRevokeOnSchema("DELETE", userWithDelete); + testRevokeOnSchema("ALL PRIVILEGES", userWithAllPrivileges); + } + + private void testRevokeOnSchema(String privilege, Session user) { assertThat(assertions.query(user, "SHOW TABLES FROM default")).matches("VALUES (VARCHAR 'table_one')"); @@ -97,60 +106,77 @@ public void testRevokeOnSchema(String privilege, Session user) assertThat(assertions.query(user, "SHOW TABLES FROM default")).returnsEmptyResult(); } - @Test(dataProvider = "privilegesAndUsers") - public void testRevokeOnNonExistingCatalog(String privilege, Session user) + @Test + public void testRevokeOnNonExistingCatalog() + { + testRevokeOnNonExistingCatalog("CREATE", userWithCreate); + testRevokeOnNonExistingCatalog("SELECT", userWithSelect); + testRevokeOnNonExistingCatalog("INSERT", userWithInsert); + testRevokeOnNonExistingCatalog("UPDATE", userWithUpdate); + testRevokeOnNonExistingCatalog("DELETE", userWithDelete); + testRevokeOnNonExistingCatalog("ALL PRIVILEGES", userWithAllPrivileges); + } + + private void testRevokeOnNonExistingCatalog(String privilege, Session user) { assertThatThrownBy(() -> queryRunner.execute(admin, format("REVOKE %s ON TABLE missing_catalog.missing_schema.missing_table FROM %s", privilege, user.getUser()))) .hasMessageContaining("Table 'missing_catalog.missing_schema.missing_table' does not exist"); } - @Test(dataProvider = "privilegesAndUsers") - public void testRevokeOnNonExistingSchema(String privilege, Session user) + @Test + public void testRevokeOnNonExistingSchema() { - assertThatThrownBy(() -> queryRunner.execute(admin, format("REVOKE %s ON TABLE missing_schema.missing_table FROM %s", privilege, user.getUser()))) - .hasMessageContaining("Table 'local.missing_schema.missing_table' does not exist"); + testRevokeOnNonExistingSchema("CREATE", userWithCreate); + testRevokeOnNonExistingSchema("SELECT", userWithSelect); + testRevokeOnNonExistingSchema("INSERT", userWithInsert); + testRevokeOnNonExistingSchema("UPDATE", userWithUpdate); + testRevokeOnNonExistingSchema("DELETE", userWithDelete); + testRevokeOnNonExistingSchema("ALL PRIVILEGES", userWithAllPrivileges); } - @Test(dataProvider = "privilegesAndUsers") - public void testRevokeOnNonExistingTable(String privilege, Session user) + private void testRevokeOnNonExistingSchema(String privilege, Session user) { - assertThatThrownBy(() -> queryRunner.execute(admin, format("REVOKE %s ON TABLE default.missing_table FROM %s", privilege, user.getUser()))) - .hasMessageContaining("Table 'local.default.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("REVOKE %s ON TABLE missing_schema.missing_table FROM %s", privilege, user.getUser()))) + .hasMessageContaining("Table 'local.missing_schema.missing_table' does not exist"); } - @Test(dataProvider = "privileges") - public void testAccessDenied(String privilege) + @Test + public void testRevokeOnNonExistingTable() { - assertThatThrownBy(() -> queryRunner.execute(sessionOf(randomUsername()), format("REVOKE %s ON TABLE table_one FROM %s", privilege, randomUsername()))) - .hasMessageContaining( - "Access Denied: Cannot revoke privilege %s on table default.table_one", - privilege.equals("ALL PRIVILEGES") ? "CREATE" : privilege); + testRevokeOnNonExistingTable("CREATE", userWithCreate); + testRevokeOnNonExistingTable("SELECT", userWithSelect); + testRevokeOnNonExistingTable("INSERT", userWithInsert); + testRevokeOnNonExistingTable("UPDATE", userWithUpdate); + testRevokeOnNonExistingTable("DELETE", userWithDelete); + testRevokeOnNonExistingTable("ALL PRIVILEGES", userWithAllPrivileges); } - @DataProvider(name = "privilegesAndUsers") - public static Object[][] privilegesAndUsers() + private void testRevokeOnNonExistingTable(String privilege, Session user) { - return new Object[][] { - {"CREATE", userWithCreate}, - {"SELECT", userWithSelect}, - {"INSERT", userWithInsert}, - {"UPDATE", userWithUpdate}, - {"DELETE", userWithDelete}, - {"ALL PRIVILEGES", userWithAllPrivileges} - }; + assertThatThrownBy(() -> queryRunner.execute(admin, format("REVOKE %s ON TABLE default.missing_table FROM %s", privilege, user.getUser()))) + .hasMessageContaining("Table 'local.default.missing_table' does not exist"); } - @DataProvider(name = "privileges") - public static Object[][] privileges() + @Test + public void testAccessDenied() { - return new Object[][] { - {"CREATE"}, - {"SELECT"}, - {"INSERT"}, - {"UPDATE"}, - {"DELETE"}, - {"ALL PRIVILEGES"} - }; + assertThatThrownBy(() -> queryRunner.execute(sessionOf(randomUsername()), format("REVOKE CREATE ON TABLE table_one FROM %s", randomUsername()))) + .hasMessageContaining("Access Denied: Cannot revoke privilege CREATE on table default.table_one"); + + assertThatThrownBy(() -> queryRunner.execute(sessionOf(randomUsername()), format("REVOKE SELECT ON TABLE table_one FROM %s", randomUsername()))) + .hasMessageContaining("Access Denied: Cannot revoke privilege SELECT on table default.table_one"); + + assertThatThrownBy(() -> queryRunner.execute(sessionOf(randomUsername()), format("REVOKE INSERT ON TABLE table_one FROM %s", randomUsername()))) + .hasMessageContaining("Access Denied: Cannot revoke privilege INSERT on table default.table_one"); + + assertThatThrownBy(() -> queryRunner.execute(sessionOf(randomUsername()), format("REVOKE UPDATE ON TABLE table_one FROM %s", randomUsername()))) + .hasMessageContaining("Access Denied: Cannot revoke privilege UPDATE on table default.table_one"); + + assertThatThrownBy(() -> queryRunner.execute(sessionOf(randomUsername()), format("REVOKE DELETE ON TABLE table_one FROM %s", randomUsername()))) + .hasMessageContaining("Access Denied: Cannot revoke privilege DELETE on table default.table_one"); + + assertThatThrownBy(() -> queryRunner.execute(sessionOf(randomUsername()), format("REVOKE ALL PRIVILEGES ON TABLE table_one FROM %s", randomUsername()))) + .hasMessageContaining("Access Denied: Cannot revoke privilege CREATE on table default.table_one"); } private static Session sessionOf(String username) diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/BaseCostBasedPlanTest.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/BaseCostBasedPlanTest.java index 4a85713efb7d..cbbb425cfc3e 100644 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/BaseCostBasedPlanTest.java +++ b/testing/trino-tests/src/test/java/io/trino/sql/planner/BaseCostBasedPlanTest.java @@ -34,7 +34,6 @@ import io.trino.sql.planner.plan.ValuesNode; import io.trino.testing.LocalQueryRunner; import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.io.IOException; @@ -44,7 +43,6 @@ import java.util.List; import java.util.Optional; import java.util.stream.IntStream; -import java.util.stream.Stream; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Verify.verify; @@ -61,7 +59,6 @@ import static io.trino.sql.planner.LogicalPlanner.Stage.OPTIMIZED_AND_VALIDATED; import static io.trino.sql.planner.plan.JoinNode.DistributionType.REPLICATED; import static io.trino.sql.planner.plan.JoinNode.Type.INNER; -import static io.trino.testing.DataProviders.toDataProvider; import static io.trino.testing.TestingSession.testSessionBuilder; import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; @@ -133,19 +130,14 @@ protected LocalQueryRunner createLocalQueryRunner() public abstract void prepareTables() throws Exception; - protected abstract Stream getQueryResourcePaths(); + protected abstract List getQueryResourcePaths(); - @DataProvider - public Object[][] getQueriesDataProvider() + @Test + public void test() { - return getQueryResourcePaths() - .collect(toDataProvider()); - } - - @Test(dataProvider = "getQueriesDataProvider") - public void test(String queryResourcePath) - { - assertEquals(generateQueryPlan(readQuery(queryResourcePath)), read(getQueryPlanResourcePath(queryResourcePath))); + for (String queryResourcePath : getQueryResourcePaths()) { + assertEquals(generateQueryPlan(readQuery(queryResourcePath)), read(getQueryPlanResourcePath(queryResourcePath))); + } } private String getQueryPlanResourcePath(String queryResourcePath) @@ -167,7 +159,7 @@ protected void generate() initPlanTest(); try { prepareTables(); - getQueryResourcePaths() + getQueryResourcePaths().stream() .parallel() .forEach(queryResourcePath -> { try { diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestHivePartitionedTpcdsCostBasedPlan.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestHivePartitionedTpcdsCostBasedPlan.java index 56013dd1f471..3d8c8b17131a 100644 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestHivePartitionedTpcdsCostBasedPlan.java +++ b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestHivePartitionedTpcdsCostBasedPlan.java @@ -13,7 +13,7 @@ */ package io.trino.sql.planner; -import java.util.stream.Stream; +import java.util.List; /** * This class tests cost-based optimization rules related to joins. It contains unmodified TPC-DS queries. @@ -37,9 +37,9 @@ public TestHivePartitionedTpcdsCostBasedPlan() } @Override - protected Stream getQueryResourcePaths() + protected List getQueryResourcePaths() { - return TPCDS_SQL_FILES.stream(); + return TPCDS_SQL_FILES; } public static void main(String[] args) diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestHivePartitionedTpchCostBasedPlan.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestHivePartitionedTpchCostBasedPlan.java index 2ab22158c19e..71bdba29f709 100644 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestHivePartitionedTpchCostBasedPlan.java +++ b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestHivePartitionedTpchCostBasedPlan.java @@ -13,7 +13,7 @@ */ package io.trino.sql.planner; -import java.util.stream.Stream; +import java.util.List; /** * This class tests cost-based optimization rules related to joins. It contains unmodified TPC-H queries. @@ -37,9 +37,9 @@ public TestHivePartitionedTpchCostBasedPlan() } @Override - protected Stream getQueryResourcePaths() + protected List getQueryResourcePaths() { - return TPCH_SQL_FILES.stream(); + return TPCH_SQL_FILES; } public static void main(String[] args) diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestHiveTpcdsCostBasedPlan.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestHiveTpcdsCostBasedPlan.java index b9e9192915b8..1ac2e7b7d40c 100644 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestHiveTpcdsCostBasedPlan.java +++ b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestHiveTpcdsCostBasedPlan.java @@ -14,7 +14,7 @@ package io.trino.sql.planner; -import java.util.stream.Stream; +import java.util.List; /** * This class tests cost-based optimization rules related to joins. It contains unmodified TPC-DS queries. @@ -38,9 +38,9 @@ public TestHiveTpcdsCostBasedPlan() } @Override - protected Stream getQueryResourcePaths() + protected List getQueryResourcePaths() { - return TPCDS_SQL_FILES.stream(); + return TPCDS_SQL_FILES; } public static void main(String[] args) diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestHiveTpchCostBasedPlan.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestHiveTpchCostBasedPlan.java index dd34e104b47f..a9c152ecbedb 100644 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestHiveTpchCostBasedPlan.java +++ b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestHiveTpchCostBasedPlan.java @@ -14,7 +14,7 @@ package io.trino.sql.planner; -import java.util.stream.Stream; +import java.util.List; /** * This class tests cost-based optimization rules related to joins. It contains unmodified TPC-H queries. @@ -38,9 +38,9 @@ public TestHiveTpchCostBasedPlan() } @Override - protected Stream getQueryResourcePaths() + protected List getQueryResourcePaths() { - return TPCH_SQL_FILES.stream(); + return TPCH_SQL_FILES; } public static void main(String[] args) diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcPartitionedTpcdsCostBasedPlan.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcPartitionedTpcdsCostBasedPlan.java index d6810ea240e7..f2b90868ae6c 100644 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcPartitionedTpcdsCostBasedPlan.java +++ b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcPartitionedTpcdsCostBasedPlan.java @@ -14,7 +14,7 @@ package io.trino.sql.planner; -import java.util.stream.Stream; +import java.util.List; /** * This class tests cost-based optimization rules related to joins. It contains unmodified TPC-DS queries. @@ -43,9 +43,9 @@ protected void doPrepareTables() } @Override - protected Stream getQueryResourcePaths() + protected List getQueryResourcePaths() { - return TPCDS_SQL_FILES.stream(); + return TPCDS_SQL_FILES; } public static void main(String[] args) diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcPartitionedTpchCostBasedPlan.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcPartitionedTpchCostBasedPlan.java index d6861e9563cc..2375352bab2d 100644 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcPartitionedTpchCostBasedPlan.java +++ b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcPartitionedTpchCostBasedPlan.java @@ -16,7 +16,7 @@ import io.trino.tpch.TpchTable; -import java.util.stream.Stream; +import java.util.List; /** * This class tests cost-based optimization rules related to joins. It contains unmodified TPC-H queries. @@ -42,9 +42,9 @@ protected void doPrepareTables() } @Override - protected Stream getQueryResourcePaths() + protected List getQueryResourcePaths() { - return TPCH_SQL_FILES.stream(); + return TPCH_SQL_FILES; } public static void main(String[] args) diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcTpcdsCostBasedPlan.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcTpcdsCostBasedPlan.java index 91667b0f105c..a4fc1847d0db 100644 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcTpcdsCostBasedPlan.java +++ b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcTpcdsCostBasedPlan.java @@ -14,7 +14,7 @@ package io.trino.sql.planner; -import java.util.stream.Stream; +import java.util.List; /** * This class tests cost-based optimization rules related to joins. It contains unmodified TPC-DS queries. @@ -43,9 +43,9 @@ protected void doPrepareTables() } @Override - protected Stream getQueryResourcePaths() + protected List getQueryResourcePaths() { - return TPCDS_SQL_FILES.stream(); + return TPCDS_SQL_FILES; } public static void main(String[] args) diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcTpchCostBasedPlan.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcTpchCostBasedPlan.java index 4cefe3289df5..bee3ce48b1f9 100644 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcTpchCostBasedPlan.java +++ b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcTpchCostBasedPlan.java @@ -16,7 +16,7 @@ import io.trino.tpch.TpchTable; -import java.util.stream.Stream; +import java.util.List; /** * This class tests cost-based optimization rules related to joins. It contains unmodified TPC-H queries. @@ -42,9 +42,9 @@ protected void doPrepareTables() } @Override - protected Stream getQueryResourcePaths() + protected List getQueryResourcePaths() { - return TPCH_SQL_FILES.stream(); + return TPCH_SQL_FILES; } public static void main(String[] args) diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetPartitionedTpcdsCostBasedPlan.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetPartitionedTpcdsCostBasedPlan.java index f8aac2df7945..3b5d48f5f623 100644 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetPartitionedTpcdsCostBasedPlan.java +++ b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetPartitionedTpcdsCostBasedPlan.java @@ -14,7 +14,7 @@ package io.trino.sql.planner; -import java.util.stream.Stream; +import java.util.List; /** * This class tests cost-based optimization rules related to joins. It contains unmodified TPC-DS queries. @@ -43,9 +43,9 @@ protected void doPrepareTables() } @Override - protected Stream getQueryResourcePaths() + protected List getQueryResourcePaths() { - return TPCDS_SQL_FILES.stream(); + return TPCDS_SQL_FILES; } public static void main(String[] args) diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetPartitionedTpchCostBasedPlan.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetPartitionedTpchCostBasedPlan.java index 8b05fddf57cf..994571d11ea6 100644 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetPartitionedTpchCostBasedPlan.java +++ b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetPartitionedTpchCostBasedPlan.java @@ -16,7 +16,7 @@ import io.trino.tpch.TpchTable; -import java.util.stream.Stream; +import java.util.List; /** * This class tests cost-based optimization rules related to joins. It contains unmodified TPC-H queries. @@ -42,9 +42,9 @@ protected void doPrepareTables() } @Override - protected Stream getQueryResourcePaths() + protected List getQueryResourcePaths() { - return TPCH_SQL_FILES.stream(); + return TPCH_SQL_FILES; } public static void main(String[] args) diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetTpcdsCostBasedPlan.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetTpcdsCostBasedPlan.java index 10836dbfb887..d64295fc7e47 100644 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetTpcdsCostBasedPlan.java +++ b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetTpcdsCostBasedPlan.java @@ -14,7 +14,7 @@ package io.trino.sql.planner; -import java.util.stream.Stream; +import java.util.List; /** * This class tests cost-based optimization rules related to joins. It contains unmodified TPC-DS queries. @@ -43,9 +43,9 @@ protected void doPrepareTables() } @Override - protected Stream getQueryResourcePaths() + protected List getQueryResourcePaths() { - return TPCDS_SQL_FILES.stream(); + return TPCDS_SQL_FILES; } public static void main(String[] args) diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetTpchCostBasedPlan.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetTpchCostBasedPlan.java index d7258641717c..a8de1f470d7e 100644 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetTpchCostBasedPlan.java +++ b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetTpchCostBasedPlan.java @@ -16,7 +16,7 @@ import io.trino.tpch.TpchTable; -import java.util.stream.Stream; +import java.util.List; /** * This class tests cost-based optimization rules related to joins. It contains unmodified TPC-H queries. @@ -42,9 +42,9 @@ protected void doPrepareTables() } @Override - protected Stream getQueryResourcePaths() + protected List getQueryResourcePaths() { - return TPCH_SQL_FILES.stream(); + return TPCH_SQL_FILES; } public static void main(String[] args) diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergSmallFilesParquetTpcdsCostBasedPlan.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergSmallFilesParquetTpcdsCostBasedPlan.java index 233924d144a0..3e9633fff937 100644 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergSmallFilesParquetTpcdsCostBasedPlan.java +++ b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergSmallFilesParquetTpcdsCostBasedPlan.java @@ -14,7 +14,7 @@ package io.trino.sql.planner; -import java.util.stream.Stream; +import java.util.List; /** * This class tests cost-based optimization rules related to joins. It contains unmodified TPC-DS queries. @@ -43,9 +43,9 @@ protected void doPrepareTables() } @Override - protected Stream getQueryResourcePaths() + protected List getQueryResourcePaths() { - return TPCDS_SQL_FILES.stream(); + return TPCDS_SQL_FILES; } public static void main(String[] args) From baf3e309fdd8506b5ede56bf4d27ecc0d0e245e0 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Sat, 11 Nov 2023 16:31:02 -0800 Subject: [PATCH 240/587] Remove unnecessary method --- .../java/io/trino/hdfs/TestFSDataInputStreamTail.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFSDataInputStreamTail.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFSDataInputStreamTail.java index bb39a1189260..d1bb545d5058 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFSDataInputStreamTail.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFSDataInputStreamTail.java @@ -152,7 +152,8 @@ private void testReadTailCompletely(int fileSize, int paddedFileSize) assertEquals(tail.getFileSize(), fileSize); Slice tailSlice = tail.getTailSlice(); assertEquals(tailSlice.length(), fileSize); - assertCountingTestFileContents(tailSlice.getBytes()); + byte[] tailContents = tailSlice.getBytes(); + assertEquals(tailContents, countingTestFileContentsWithLength(tailContents.length)); } } @@ -213,11 +214,6 @@ public void testReadTailForFileSizeNoEndOfFileFound() } } - private static void assertCountingTestFileContents(byte[] contents) - { - assertEquals(contents, countingTestFileContentsWithLength(contents.length)); - } - private static byte[] countingTestFileContentsWithLength(int length) { byte[] contents = new byte[length]; From 0fe68bb2e3bbf8ce4684368bf8b0a6e68810cab9 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Mon, 13 Nov 2023 15:07:19 -0800 Subject: [PATCH 241/587] Migrate tests to JUnit --- .../accumulo/TestAccumuloConnectorTest.java | 8 ++- .../plugin/jdbc/BaseJdbcConnectorTest.java | 49 +++++++++++------ .../plugin/jdbc/TestJdbcConnectorTest.java | 21 +++++--- .../bigquery/BaseBigQueryConnectorTest.java | 30 ++++++----- .../TestBigQueryAvroConnectorTest.java | 2 +- .../cassandra/TestCassandraConnectorTest.java | 24 ++++++--- .../TestClickHouseConnectorTest.java | 32 +++++++++--- .../deltalake/TestDeltaLakeConnectorTest.java | 25 +++++---- .../plugin/druid/TestDruidConnectorTest.java | 27 ++++++---- .../BaseElasticsearchConnectorTest.java | 16 ++++-- .../TestElasticsearch6ConnectorTest.java | 2 +- .../plugin/hive/BaseHiveConnectorTest.java | 34 +++++++++--- .../plugin/hive/TestHiveConnectorTest.java | 2 +- .../plugin/hudi/TestHudiConnectorTest.java | 2 +- .../iceberg/BaseIcebergConnectorTest.java | 24 ++++++--- .../iceberg/TestIcebergAvroConnectorTest.java | 8 +-- .../TestIcebergMinioOrcConnectorTest.java | 3 +- .../TestIcebergParquetConnectorTest.java | 3 +- .../ignite/TestIgniteConnectorTest.java | 12 +++-- .../plugin/kafka/TestKafkaConnectorTest.java | 11 ++-- .../plugin/kudu/TestKuduConnectorTest.java | 36 +++++++++---- .../mariadb/BaseMariaDbConnectorTest.java | 7 ++- .../mariadb/TestMariaDbConnectorTest.java | 4 ++ .../memory/TestMemoryConnectorTest.java | 37 ++++++++----- .../mongodb/TestMongoConnectorTest.java | 37 ++++++++----- .../plugin/mysql/BaseMySqlConnectorTest.java | 9 +++- .../mysql/TestMySqlLegacyConnectorTest.java | 3 ++ .../oracle/BaseOracleConnectorTest.java | 14 +++-- .../oracle/TestOracleConnectorTest.java | 9 ++-- .../phoenix5/TestPhoenixConnectorTest.java | 52 +++++++++++++------ .../TestPostgreSqlConnectorTest.java | 9 ++-- .../legacy/BaseRaptorConnectorTest.java | 16 +++--- .../TestRaptorBucketedConnectorTest.java | 2 +- .../legacy/TestRaptorMySqlConnectorTest.java | 7 ++- .../redshift/TestRedshiftConnectorTest.java | 15 ++++-- .../TestSingleStoreConnectorTest.java | 23 +++++--- .../sqlserver/BaseSqlServerConnectorTest.java | 7 +-- ...BaseSqlServerTransactionIsolationTest.java | 2 +- .../sqlserver/TestSqlServerConnectorTest.java | 2 +- ...veFaultTolerantExecutionConnectorTest.java | 16 ++++-- ...etFaultTolerantExecutionConnectorTest.java | 19 ++++--- .../io/trino/testing/BaseConnectorTest.java | 48 ++++++++++------- .../tests/tpch/TestTpchConnectorTest.java | 4 +- 43 files changed, 485 insertions(+), 228 deletions(-) diff --git a/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/TestAccumuloConnectorTest.java b/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/TestAccumuloConnectorTest.java index dd5b96d8c4d1..1f89baca5457 100644 --- a/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/TestAccumuloConnectorTest.java +++ b/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/TestAccumuloConnectorTest.java @@ -21,7 +21,6 @@ import io.trino.testing.sql.TestTable; import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.Test; -import org.testng.SkipException; import java.util.Optional; @@ -29,6 +28,7 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.testing.MaterializedResult.resultBuilder; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.abort; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @@ -78,9 +78,10 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) @Override protected TestTable createTableWithDefaultColumns() { - throw new SkipException("Accumulo connector does not support column default values"); + return abort("Accumulo connector does not support column default values"); } + @Test @Override public void testCreateTableAsSelect() { @@ -115,6 +116,7 @@ public void testCreateTableAsSelect() "SELECT 0"); } + @Test @Override public void testInsert() { @@ -151,6 +153,7 @@ public void testInsert() assertUpdate("DROP TABLE test_insert"); } + @Test @Override // Overridden because we currently do not support arrays with null elements public void testInsertArray() { @@ -297,6 +300,7 @@ protected Optional filterCaseSensitiveDataMappingTestData( return Optional.of(dataMappingTestSetup); } + @Test @Override public void testCharVarcharComparison() { diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/BaseJdbcConnectorTest.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/BaseJdbcConnectorTest.java index 01ccd6290212..4c1f14eacdb0 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/BaseJdbcConnectorTest.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/BaseJdbcConnectorTest.java @@ -42,9 +42,10 @@ import io.trino.testing.sql.TestTable; import io.trino.testing.sql.TestView; import org.intellij.lang.annotations.Language; -import org.testng.SkipException; -import org.testng.annotations.AfterClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.Timeout; import java.util.ArrayList; import java.util.List; @@ -116,7 +117,10 @@ import static java.util.concurrent.TimeUnit.MINUTES; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.abort; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +@TestInstance(PER_CLASS) public abstract class BaseJdbcConnectorTest extends BaseConnectorTest { @@ -124,7 +128,7 @@ public abstract class BaseJdbcConnectorTest protected abstract SqlExecutor onRemoteDatabase(); - @AfterClass(alwaysRun = true) + @AfterAll public void afterClass() { executor.shutdownNow(); @@ -167,7 +171,7 @@ public void testInsertInPresenceOfNotSupportedColumn() protected TestTable createTableWithUnsupportedColumn() { // TODO throw new UnsupportedOperationException(); - throw new SkipException("Not implemented"); + return abort("Not implemented"); } // TODO move common tests from connector-specific classes here @@ -577,7 +581,7 @@ public void testNumericAggregationPushdown() public void testCountDistinctWithStringTypes() { if (!(hasBehavior(SUPPORTS_CREATE_TABLE) && hasBehavior(SUPPORTS_INSERT))) { - throw new SkipException("Unable to CREATE TABLE to test count distinct"); + abort("Unable to CREATE TABLE to test count distinct"); } List rows = Stream.of("a", "b", "A", "B", " a ", "a", "b", " b ", "ą") @@ -643,7 +647,7 @@ public void testStddevAggregationPushdown() String schemaName = getSession().getSchema().orElseThrow(); if (!hasBehavior(SUPPORTS_AGGREGATION_PUSHDOWN_STDDEV)) { if (!hasBehavior(SUPPORTS_CREATE_TABLE)) { - throw new SkipException("Unable to CREATE TABLE to test aggregation pushdown"); + abort("Unable to CREATE TABLE to test aggregation pushdown"); } try (TestTable testTable = createTableWithDoubleAndRealColumns(schemaName + ".test_stddev_pushdown", ImmutableList.of())) { @@ -694,7 +698,7 @@ public void testVarianceAggregationPushdown() String schemaName = getSession().getSchema().orElseThrow(); if (!hasBehavior(SUPPORTS_AGGREGATION_PUSHDOWN_VARIANCE)) { if (!hasBehavior(SUPPORTS_CREATE_TABLE)) { - throw new SkipException("Unable to CREATE TABLE to test aggregation pushdown"); + abort("Unable to CREATE TABLE to test aggregation pushdown"); } try (TestTable testTable = createTableWithDoubleAndRealColumns(schemaName + ".test_var_pushdown", ImmutableList.of())) { @@ -738,7 +742,7 @@ public void testCovarianceAggregationPushdown() String schemaName = getSession().getSchema().orElseThrow(); if (!hasBehavior(SUPPORTS_AGGREGATION_PUSHDOWN_COVARIANCE)) { if (!hasBehavior(SUPPORTS_CREATE_TABLE)) { - throw new SkipException("Unable to CREATE TABLE to test aggregation pushdown"); + abort("Unable to CREATE TABLE to test aggregation pushdown"); } try (TestTable testTable = createTableWithDoubleAndRealColumns(schemaName + ".test_covar_pushdown", ImmutableList.of())) { @@ -775,7 +779,7 @@ public void testCorrAggregationPushdown() String schemaName = getSession().getSchema().orElseThrow(); if (!hasBehavior(SUPPORTS_AGGREGATION_PUSHDOWN_CORRELATION)) { if (!hasBehavior(SUPPORTS_CREATE_TABLE)) { - throw new SkipException("Unable to CREATE TABLE to test aggregation pushdown"); + abort("Unable to CREATE TABLE to test aggregation pushdown"); } try (TestTable testTable = createTableWithDoubleAndRealColumns(schemaName + ".test_corr_pushdown", ImmutableList.of())) { @@ -808,7 +812,7 @@ public void testRegrAggregationPushdown() String schemaName = getSession().getSchema().orElseThrow(); if (!hasBehavior(SUPPORTS_AGGREGATION_PUSHDOWN_REGRESSION)) { if (!hasBehavior(SUPPORTS_CREATE_TABLE)) { - throw new SkipException("Unable to CREATE TABLE to test aggregation pushdown"); + abort("Unable to CREATE TABLE to test aggregation pushdown"); } try (TestTable testTable = createTableWithDoubleAndRealColumns(schemaName + ".test_regr_pushdown", ImmutableList.of())) { @@ -1455,12 +1459,13 @@ protected Session joinPushdownEnabled(Session session) .build(); } - @Test(timeOut = 60_000) + @Test + @Timeout(60) public void testCancellation() throws Exception { if (!hasBehavior(SUPPORTS_CANCELLATION)) { - throw new SkipException("Cancellation is not supported by given connector"); + abort("Cancellation is not supported by given connector"); } try (TestView sleepingView = createSleepingView(new Duration(1, MINUTES))) { @@ -1512,6 +1517,7 @@ protected TestView createSleepingView(Duration minimalSleepDuration) throw new UnsupportedOperationException(); } + @Test @Override public void testUpdateNotNullColumn() { @@ -1538,6 +1544,7 @@ public void testUpdateNotNullColumn() } } + @Test @Override public void testUpdateRowType() { @@ -1555,6 +1562,7 @@ public void testUpdateRowType() } } + @Test @Override public void testUpdateRowConcurrently() throws Exception @@ -1571,6 +1579,7 @@ public void testUpdateRowConcurrently() } } + @Test @Override public void testUpdateAllValues() { @@ -1586,6 +1595,7 @@ public void testUpdateAllValues() } } + @Test @Override public void testUpdateWithPredicates() { @@ -1744,6 +1754,7 @@ public void testDeleteWithVarcharGreaterAndLowerPredicate() } } + @Test @Override public void testDeleteWithComplexPredicate() { @@ -1756,6 +1767,7 @@ public void testDeleteWithComplexPredicate() .hasStackTraceContaining("TrinoException: " + MODIFYING_ROWS_MESSAGE); } + @Test @Override public void testDeleteWithSubquery() { @@ -1768,6 +1780,7 @@ public void testDeleteWithSubquery() .hasStackTraceContaining("TrinoException: " + MODIFYING_ROWS_MESSAGE); } + @Test @Override public void testExplainAnalyzeWithDeleteWithSubquery() { @@ -1780,6 +1793,7 @@ public void testExplainAnalyzeWithDeleteWithSubquery() .hasStackTraceContaining("TrinoException: " + MODIFYING_ROWS_MESSAGE); } + @Test @Override public void testDeleteWithSemiJoin() { @@ -1792,17 +1806,18 @@ public void testDeleteWithSemiJoin() .hasStackTraceContaining("TrinoException: " + MODIFYING_ROWS_MESSAGE); } + @Test @Override public void testDeleteWithVarcharPredicate() { - throw new SkipException("This is implemented by testDeleteWithVarcharEqualityPredicate"); + abort("This is implemented by testDeleteWithVarcharEqualityPredicate"); } @Test public void testInsertWithoutTemporaryTable() { if (!hasBehavior(SUPPORTS_CREATE_TABLE)) { - throw new SkipException("CREATE TABLE is required for testing non-transactional write support"); + abort("CREATE TABLE is required for testing non-transactional write support"); } Session session = Session.builder(getSession()) .setCatalogSessionProperty(getSession().getCatalog().orElseThrow(), "non_transactional_insert", "false") @@ -1832,7 +1847,7 @@ public void testWriteBatchSizeSessionProperty() private void testWriteBatchSizeSessionProperty(int batchSize, int numberOfRows) { if (!hasBehavior(SUPPORTS_CREATE_TABLE)) { - throw new SkipException("CREATE TABLE is required for write_batch_size test but is not supported"); + abort("CREATE TABLE is required for write_batch_size test but is not supported"); } Session session = Session.builder(getSession()) .setCatalogSessionProperty(getSession().getCatalog().orElseThrow(), "write_batch_size", Integer.toString(batchSize)) @@ -1861,7 +1876,7 @@ public void testWriteTaskParallelismSessionProperty() private void testWriteTaskParallelismSessionProperty(int parallelism, int numberOfRows) { if (!hasBehavior(SUPPORTS_CREATE_TABLE)) { - throw new SkipException("CREATE TABLE is required for write_parallelism test but is not supported"); + abort("CREATE TABLE is required for write_parallelism test but is not supported"); } Session session = Session.builder(getSession()) diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestJdbcConnectorTest.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestJdbcConnectorTest.java index ce5961f0ab33..a5e2fcbad224 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestJdbcConnectorTest.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestJdbcConnectorTest.java @@ -20,8 +20,10 @@ import io.trino.testing.TestingConnectorBehavior; import io.trino.testing.sql.JdbcSqlExecutor; import io.trino.testing.sql.TestTable; -import org.testng.SkipException; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; import java.util.Map; import java.util.Optional; @@ -37,10 +39,13 @@ import static java.util.Locale.ENGLISH; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.abort; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; // Single-threaded because H2 DDL operations can sometimes take a global lock, leading to apparent deadlocks // like in https://github.com/trinodb/trino/issues/7209. -@Test(singleThreaded = true) +@TestInstance(PER_CLASS) +@Execution(ExecutionMode.SAME_THREAD) public class TestJdbcConnectorTest extends BaseJdbcConnectorTest { @@ -75,8 +80,8 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) }; } + @Test @Override - @org.junit.jupiter.api.Test public void testLargeIn() { // This test should pass with H2, but takes too long (currently over a mninute) and is not that important @@ -121,6 +126,7 @@ protected Optional filterDataMappingSmokeTestData(DataMapp return Optional.of(dataMappingTestSetup); } + @Test @Override public void testDeleteWithLike() { @@ -128,12 +134,13 @@ public void testDeleteWithLike() .hasStackTraceContaining("TrinoException: " + MODIFYING_ROWS_MESSAGE); } + @Test @Override public void testReadMetadataWithRelationsConcurrentModifications() { // Under concurrently, H2 sometimes returns null table name in DatabaseMetaData.getTables's ResultSet // See https://github.com/trinodb/trino/issues/16658 for more information - throw new SkipException("Skipped due to H2 problems"); + abort("Skipped due to H2 problems"); } @Test @@ -252,6 +259,7 @@ public void testTableWithOnlyUnsupportedColumns() } } + @Test @Override public void testNativeQueryColumnAlias() { @@ -271,11 +279,12 @@ protected void verifyAddNotNullColumnToNonEmptyTableFailurePermissible(Throwable assertThat(e).hasMessageContaining("NULL not allowed for column"); } + @Test @Override public void testAddColumnConcurrently() { // TODO: Difficult to determine whether the exception is concurrent issue or not from the error message - throw new SkipException("TODO: Enable this test after finding the failure cause"); + abort("TODO: Enable this test after finding the failure cause"); } @Override diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java index 57edeefb4d5d..b1fe80f57813 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java @@ -22,10 +22,9 @@ import io.trino.testing.TestingConnectorBehavior; import io.trino.testing.sql.TestTable; import org.intellij.lang.annotations.Language; -import org.testng.SkipException; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Parameters; -import org.testng.annotations.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import java.util.List; import java.util.Optional; @@ -44,22 +43,24 @@ import static java.util.concurrent.TimeUnit.MINUTES; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.abort; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; +@TestInstance(PER_CLASS) public abstract class BaseBigQueryConnectorTest extends BaseConnectorTest { protected BigQuerySqlExecutor bigQuerySqlExecutor; private String gcpStorageBucket; - @BeforeClass(alwaysRun = true) - @Parameters("testing.gcp-storage-bucket") - public void initBigQueryExecutor(String gcpStorageBucket) + @BeforeAll + public void initBigQueryExecutor() { this.bigQuerySqlExecutor = new BigQuerySqlExecutor(); // Prerequisite: upload region.csv in resources directory to gs://{testing.gcp-storage-bucket}/tpch/tiny/region.csv - this.gcpStorageBucket = gcpStorageBucket; + this.gcpStorageBucket = System.getProperty("testing.gcp-storage-bucket"); } @Override @@ -86,7 +87,7 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) } @Override - @org.junit.jupiter.api.Test + @Test public void testShowColumns() { assertThat(query("SHOW COLUMNS FROM orders")).matches(getDescribeOrdersResult()); @@ -277,7 +278,7 @@ public void testNoDataSystemTable() "to match regex:\n" + " \"line 1:1: Table '\\w+.\\w+.\"nation\\$data\"' does not exist\"\n" + "but did not."); - throw new SkipException("TODO"); + abort("TODO"); } @Override @@ -418,7 +419,7 @@ public void testSelectFromYearlyPartitionedTable() } } - @Test(description = "regression test for https://github.com/trinodb/trino/issues/7784") + @Test // regression test for https://github.com/trinodb/trino/issues/7784" public void testSelectWithSingleQuoteInWhereClause() { try (TestTable table = new TestTable( @@ -430,7 +431,7 @@ public void testSelectWithSingleQuoteInWhereClause() } } - @Test(description = "regression test for https://github.com/trinodb/trino/issues/5618") + @Test // "regression test for https://github.com/trinodb/trino/issues/5618" public void testPredicatePushdownPrunnedColumns() { try (TestTable table = new TestTable( @@ -530,11 +531,12 @@ public void testShowCreateTable() ")"); } + @Test @Override public void testReadMetadataWithRelationsConcurrentModifications() { // TODO: Enable this test after fixing "Task did not completed before timeout" (https://github.com/trinodb/trino/issues/14230) - throw new SkipException("Test fails with a timeout sometimes and is flaky"); + abort("Test fails with a timeout sometimes and is flaky"); } @Test @@ -945,7 +947,7 @@ public void testInsertArray() public void testInsertRowConcurrently() { // TODO https://github.com/trinodb/trino/issues/15158 Enable this test after switching to storage write API - throw new SkipException("Test fails with a timeout sometimes and is flaky"); + abort("Test fails with a timeout sometimes and is flaky"); } @Override diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryAvroConnectorTest.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryAvroConnectorTest.java index 556be67bb39f..99d74666488d 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryAvroConnectorTest.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryAvroConnectorTest.java @@ -16,7 +16,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import io.trino.testing.QueryRunner; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Optional; import java.util.Set; diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnectorTest.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnectorTest.java index be2e072870be..aea76d462d85 100644 --- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnectorTest.java +++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnectorTest.java @@ -27,9 +27,9 @@ import io.trino.testing.TestingConnectorBehavior; import io.trino.testing.sql.TestTable; import org.intellij.lang.annotations.Language; -import org.testng.SkipException; -import org.testng.annotations.AfterClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import java.math.BigDecimal; import java.math.BigInteger; @@ -72,9 +72,12 @@ import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.abort; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; +@TestInstance(PER_CLASS) public class TestCassandraConnectorTest extends BaseConnectorTest { @@ -121,7 +124,7 @@ protected QueryRunner createQueryRunner() return createCassandraQueryRunner(server, ImmutableMap.of(), ImmutableMap.of(), REQUIRED_TPCH_TABLES); } - @AfterClass(alwaysRun = true) + @AfterAll public void cleanUp() { session.close(); @@ -131,7 +134,7 @@ public void cleanUp() @Override protected TestTable createTableWithDefaultColumns() { - throw new SkipException("Cassandra connector does not support column default values"); + return abort("Cassandra connector does not support column default values"); } @Override @@ -171,7 +174,7 @@ protected String dataMappingTableName(String trinoTypeName) return "tmp_trino_" + System.nanoTime(); } - @org.junit.jupiter.api.Test + @Test @Override public void testShowColumns() { @@ -212,6 +215,7 @@ public void testShowCreateTable() ")"); } + @Test @Override public void testCharVarcharComparison() { @@ -1297,6 +1301,7 @@ public void testDelete() } } + @Test @Override public void testDeleteWithLike() { @@ -1304,6 +1309,7 @@ public void testDeleteWithLike() .hasStackTraceContaining("Delete without primary key or partition key is not supported"); } + @Test @Override public void testDeleteWithComplexPredicate() { @@ -1311,6 +1317,7 @@ public void testDeleteWithComplexPredicate() .hasStackTraceContaining("Delete without primary key or partition key is not supported"); } + @Test @Override public void testDeleteWithSemiJoin() { @@ -1318,6 +1325,7 @@ public void testDeleteWithSemiJoin() .hasStackTraceContaining("Delete without primary key or partition key is not supported"); } + @Test @Override public void testDeleteWithSubquery() { @@ -1325,6 +1333,7 @@ public void testDeleteWithSubquery() .hasStackTraceContaining("Delete without primary key or partition key is not supported"); } + @Test @Override public void testExplainAnalyzeWithDeleteWithSubquery() { @@ -1332,6 +1341,7 @@ public void testExplainAnalyzeWithDeleteWithSubquery() .hasStackTraceContaining("Delete without primary key or partition key is not supported"); } + @Test @Override public void testDeleteWithVarcharPredicate() { @@ -1339,6 +1349,7 @@ public void testDeleteWithVarcharPredicate() .hasStackTraceContaining("Delete without primary key or partition key is not supported"); } + @Test @Override public void testDeleteAllDataFromTable() { @@ -1346,6 +1357,7 @@ public void testDeleteAllDataFromTable() .hasStackTraceContaining("Deleting without partition key is not supported"); } + @Test @Override public void testRowLevelDelete() { diff --git a/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/TestClickHouseConnectorTest.java b/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/TestClickHouseConnectorTest.java index 68fa84b6c2e3..3a9f9b6ac94a 100644 --- a/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/TestClickHouseConnectorTest.java +++ b/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/TestClickHouseConnectorTest.java @@ -22,8 +22,8 @@ import io.trino.testing.TestingConnectorBehavior; import io.trino.testing.sql.SqlExecutor; import io.trino.testing.sql.TestTable; -import org.testng.SkipException; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.DriverManager; @@ -49,6 +49,7 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.abort; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNull; @@ -105,13 +106,15 @@ public void testSampleBySqlInjection() assertUpdate("ALTER TABLE test SET PROPERTIES sample_by = 'p2'"); } + @Test @Override public void testRenameColumn() { // ClickHouse need resets all data in a column for specified column which to be renamed - throw new SkipException("TODO: test not implemented yet"); + abort("TODO: test not implemented yet"); } + @Test @Override public void testRenameColumnWithComment() { @@ -136,6 +139,7 @@ public void testAddColumnWithCommentSpecialCharacter(String comment) } } + @Test @Override public void testDropAndAddColumnWithSameName() { @@ -154,7 +158,8 @@ protected String createTableSqlForAddingAndDroppingColumn(String tableName, Stri return format("CREATE TABLE %s(%s varchar(50), value varchar(50) NOT NULL) WITH (engine = 'MergeTree', order_by = ARRAY['value'])", tableName, columnNameInSql); } - @Test(enabled = false) + @Test + @Disabled @Override public void testRenameColumnName() { @@ -170,6 +175,7 @@ protected Optional filterColumnNameTestData(String columnName) return Optional.of(columnName); } + @Test @Override public void testDropColumn() { @@ -207,6 +213,7 @@ protected String tableDefinitionForAddColumn() return "(x VARCHAR NOT NULL) WITH (engine = 'MergeTree', order_by = ARRAY['x'])"; } + @Test @Override // Overridden because the default storage type doesn't support adding columns public void testAddNotNullColumnToEmptyTable() { @@ -222,6 +229,7 @@ public void testAddNotNullColumnToEmptyTable() } } + @Test @Override // Overridden because (a) the default storage type doesn't support adding columns and (b) ClickHouse has implicit default value for new NON NULL column public void testAddNotNullColumn() { @@ -256,18 +264,20 @@ public void testAddColumnWithComment() } } + @Test @Override public void testAlterTableAddLongColumnName() { // TODO: Find the maximum column name length in ClickHouse and enable this test. - throw new SkipException("TODO"); + abort("TODO"); } + @Test @Override public void testAlterTableRenameColumnToLongName() { // TODO: Find the maximum column name length in ClickHouse and enable this test. - throw new SkipException("TODO"); + abort("TODO"); } @Test @@ -320,6 +330,7 @@ protected TestTable createTableWithDefaultColumns() "col_required2 Int64) ENGINE=Log"); } + @Test @Override public void testCharVarcharComparison() { @@ -329,7 +340,7 @@ public void testCharVarcharComparison() .hasMessageContaining("Expected rows"); // TODO run the test with clickhouse.map-string-as-varchar - throw new SkipException(""); + abort(""); } @Test @@ -594,6 +605,7 @@ protected Optional filterDataMappingSmokeTestData(DataMapp } // TODO: Remove override once decimal predicate pushdown is implemented (https://github.com/trinodb/trino/issues/7100) + @Test @Override public void testNumericAggregationPushdown() { @@ -676,12 +688,13 @@ protected String errorMessageForDateYearOfEraPredicate(String date) return "Date must be between 1970-01-01 and 2149-06-06 in ClickHouse: " + date; } + @Test @Override public void testCharTrailingSpace() { assertThatThrownBy(super::testCharTrailingSpace) .hasMessageStartingWith("Failed to execute statement: CREATE TABLE tpch.char_trailing_space"); - throw new SkipException("Implement test for ClickHouse"); + abort("Implement test for ClickHouse"); } @Override @@ -691,6 +704,7 @@ protected TestTable simpleTable() return new TestTable(onRemoteDatabase(), "tpch.simple_table", "(col BIGINT) Engine=Log", ImmutableList.of("1", "2")); } + @Test @Override public void testCreateTableWithLongTableName() { @@ -710,6 +724,7 @@ public void testCreateTableWithLongTableName() assertTrue(getQueryRunner().tableExists(getSession(), validTableName)); } + @Test @Override public void testRenameSchemaToLongName() { @@ -747,6 +762,7 @@ protected void verifySchemaNameLengthFailurePermissible(Throwable e) assertThat(e).hasMessageContaining("File name too long"); } + @Test @Override public void testRenameTableToLongTableName() { diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConnectorTest.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConnectorTest.java index 1f5d7c5df5e7..c11a54b0fec6 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConnectorTest.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConnectorTest.java @@ -38,9 +38,7 @@ import io.trino.testing.sql.TestTable; import io.trino.testing.sql.TrinoSqlExecutor; import org.intellij.lang.annotations.Language; -import org.testng.SkipException; -import org.testng.annotations.AfterClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.nio.file.Path; import java.time.ZonedDateTime; @@ -80,6 +78,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.abort; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @@ -138,12 +137,6 @@ protected QueryRunner createQueryRunner() return queryRunner; } - @AfterClass(alwaysRun = true) - public void tearDown() - { - minioClient = null; // closed by closeAfterClass - } - @Override protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) { @@ -233,7 +226,7 @@ protected Optional filterDataMappingSmokeTestData(DataMapp @Override protected TestTable createTableWithDefaultColumns() { - throw new SkipException("Delta Lake does not support columns with a default value"); + return abort("Delta Lake does not support columns with a default value"); } @Override @@ -356,6 +349,7 @@ public void testCreateTableAsSelectWithUnsupportedPartitionType() "Using array, map or row type on partitioned columns is unsupported"); } + @Test @Override public void testShowCreateSchema() { @@ -367,6 +361,7 @@ public void testShowCreateSchema() ")", getSession().getCatalog().orElseThrow(), schemaName, bucketName)); } + @Test @Override public void testDropNonEmptySchemaWithTable() { @@ -382,6 +377,7 @@ public void testDropNonEmptySchemaWithTable() assertUpdate("DROP SCHEMA " + schemaName); } + @Test @Override public void testDropColumn() { @@ -391,6 +387,7 @@ public void testDropColumn() .hasMessageContaining("Cannot drop column from table using column mapping mode NONE"); } + @Test @Override public void testAddAndDropColumnName() { @@ -402,6 +399,7 @@ public void testAddAndDropColumnName() } } + @Test @Override public void testDropAndAddColumnWithSameName() { @@ -439,6 +437,7 @@ public void testDropLastNonPartitionColumn() assertUpdate("DROP TABLE " + tableName); } + @Test @Override public void testRenameColumn() { @@ -448,6 +447,7 @@ public void testRenameColumn() .hasMessageContaining("Cannot rename column in table using column mapping mode NONE"); } + @Test @Override public void testRenameColumnWithComment() { @@ -484,6 +484,7 @@ private void testDeltaRenameColumnWithComment(ColumnMappingMode mode) assertUpdate("DROP TABLE " + tableName); } + @Test @Override public void testAlterTableRenameColumnToLongName() { @@ -493,6 +494,7 @@ public void testAlterTableRenameColumnToLongName() .hasMessageContaining("Cannot rename column in table using column mapping mode NONE"); } + @Test @Override public void testRenameColumnName() { @@ -504,6 +506,7 @@ public void testRenameColumnName() } } + @Test @Override public void testCharVarcharComparison() { @@ -872,6 +875,7 @@ private void testMergeUpdateWithVariousLayouts(String partitionPhase) } @Test + @Override public void testMergeMultipleOperations() { testMergeMultipleOperations(""); @@ -964,6 +968,7 @@ public void testMergeSimpleQueryPartitioned() } @Test + @Override public void testMergeMultipleRowsMatchFails() { testMergeMultipleRowsMatchFails("CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR) WITH (location = 's3://%s/%s')"); diff --git a/plugin/trino-druid/src/test/java/io/trino/plugin/druid/TestDruidConnectorTest.java b/plugin/trino-druid/src/test/java/io/trino/plugin/druid/TestDruidConnectorTest.java index c4c2ae91868b..94fce7df9394 100644 --- a/plugin/trino-druid/src/test/java/io/trino/plugin/druid/TestDruidConnectorTest.java +++ b/plugin/trino-druid/src/test/java/io/trino/plugin/druid/TestDruidConnectorTest.java @@ -31,9 +31,9 @@ import io.trino.testing.TestingConnectorBehavior; import io.trino.testing.sql.SqlExecutor; import org.intellij.lang.annotations.Language; -import org.testng.SkipException; -import org.testng.annotations.AfterClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import java.sql.DatabaseMetaData; import java.sql.SQLException; @@ -56,8 +56,11 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.abort; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.testng.Assert.assertFalse; +@TestInstance(PER_CLASS) public class TestDruidConnectorTest extends BaseJdbcConnectorTest { @@ -75,7 +78,7 @@ protected QueryRunner createQueryRunner() ImmutableList.of(ORDERS, LINE_ITEM, NATION, REGION, PART, CUSTOMER)); } - @AfterClass(alwaysRun = true) + @AfterAll public void destroy() { druidServer = null; // closed by closeAfterClass @@ -126,7 +129,7 @@ protected MaterializedResult getDescribeOrdersResult() .build(); } - @org.junit.jupiter.api.Test + @Test @Override public void testShowColumns() { @@ -302,30 +305,33 @@ public void testLimitPushDown() @Override public void testInsertNegativeDate() { - throw new SkipException("Druid connector does not map 'orderdate' column to date type and INSERT statement"); + abort("Druid connector does not map 'orderdate' column to date type and INSERT statement"); } @Test @Override public void testDateYearOfEraPredicate() { - throw new SkipException("Druid connector does not map 'orderdate' column to date type"); + abort("Druid connector does not map 'orderdate' column to date type"); } + @Test @Override public void testCharTrailingSpace() { assertThatThrownBy(super::testCharTrailingSpace) .hasMessageContaining("Error while executing SQL \"CREATE TABLE druid.char_trailing_space"); - throw new SkipException("Implement test for Druid"); + abort("Implement test for Druid"); } + @Test @Override public void testNativeQuerySelectFromTestTable() { - throw new SkipException("cannot create test table for Druid"); + abort("cannot create test table for Druid"); } + @Test @Override public void testNativeQueryCreateStatement() { @@ -336,10 +342,11 @@ public void testNativeQueryCreateStatement() assertFalse(getQueryRunner().tableExists(getSession(), "numbers")); } + @Test @Override public void testNativeQueryInsertStatementTableExists() { - throw new SkipException("cannot create test table for Druid"); + abort("cannot create test table for Druid"); } @Test diff --git a/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/BaseElasticsearchConnectorTest.java b/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/BaseElasticsearchConnectorTest.java index 5b53be176e71..521c4e04fb8a 100644 --- a/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/BaseElasticsearchConnectorTest.java +++ b/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/BaseElasticsearchConnectorTest.java @@ -32,8 +32,10 @@ import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestHighLevelClient; import org.intellij.lang.annotations.Language; -import org.testng.annotations.AfterClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import java.io.IOException; import java.time.LocalDateTime; @@ -49,10 +51,12 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; +@TestInstance(PER_CLASS) public abstract class BaseElasticsearchConnectorTest extends BaseConnectorTest { @@ -85,7 +89,7 @@ protected QueryRunner createQueryRunner() catalogName); } - @AfterClass(alwaysRun = true) + @AfterAll public final void destroy() throws IOException { @@ -195,6 +199,7 @@ public void testSortItemsReflectedInExplain() "TopNPartial\\[count = 5, orderBy = \\[nationkey DESC"); } + @Test @Override public void testShowCreateTable() { @@ -212,7 +217,7 @@ public void testShowCreateTable() ")"); } - @org.junit.jupiter.api.Test + @Test @Override public void testShowColumns() { @@ -1771,7 +1776,8 @@ public void testSelectInformationSchemaForMultiIndexAlias() testSelectInformationSchemaColumns(); } - @Test(enabled = false) // TODO (https://github.com/trinodb/trino/issues/2428) + @Test // TODO (https://github.com/trinodb/trino/issues/2428) + @Disabled public void testMultiIndexAlias() throws IOException { diff --git a/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/TestElasticsearch6ConnectorTest.java b/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/TestElasticsearch6ConnectorTest.java index c5a293bd8eb6..06b2c7499200 100644 --- a/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/TestElasticsearch6ConnectorTest.java +++ b/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/TestElasticsearch6ConnectorTest.java @@ -17,7 +17,7 @@ import org.apache.http.entity.ContentType; import org.apache.http.nio.entity.NStringEntity; import org.intellij.lang.annotations.Language; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java index 8334515d2e8f..113f1d3cff9f 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java @@ -68,8 +68,7 @@ import io.trino.type.TypeDeserializer; import org.assertj.core.api.AbstractLongAssert; import org.intellij.lang.annotations.Language; -import org.testng.SkipException; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.File; import java.io.IOException; @@ -190,6 +189,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.data.Offset.offset; +import static org.junit.jupiter.api.Assumptions.abort; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; @@ -265,6 +265,7 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) }; } + @Test @Override public void verifySupportsUpdateDeclaration() { @@ -273,6 +274,7 @@ public void verifySupportsUpdateDeclaration() } } + @Test @Override public void verifySupportsRowLevelUpdateDeclaration() { @@ -294,6 +296,7 @@ protected void verifySelectAfterInsertFailurePermissible(Throwable e) .containsPattern("io.trino.spi.TrinoException: Cannot read from a table tpch.test_insert_select_\\w+ that was modified within transaction, you need to commit the transaction first"); } + @Test @Override public void testDelete() { @@ -301,6 +304,7 @@ public void testDelete() .hasStackTraceContaining(MODIFYING_NON_TRANSACTIONAL_TABLE_MESSAGE); } + @Test @Override public void testDeleteWithLike() { @@ -308,6 +312,7 @@ public void testDeleteWithLike() .hasStackTraceContaining(MODIFYING_NON_TRANSACTIONAL_TABLE_MESSAGE); } + @Test @Override public void testDeleteWithComplexPredicate() { @@ -315,6 +320,7 @@ public void testDeleteWithComplexPredicate() .hasStackTraceContaining(MODIFYING_NON_TRANSACTIONAL_TABLE_MESSAGE); } + @Test @Override public void testDeleteWithSemiJoin() { @@ -322,6 +328,7 @@ public void testDeleteWithSemiJoin() .hasStackTraceContaining(MODIFYING_NON_TRANSACTIONAL_TABLE_MESSAGE); } + @Test @Override public void testDeleteWithSubquery() { @@ -329,6 +336,7 @@ public void testDeleteWithSubquery() .hasStackTraceContaining(MODIFYING_NON_TRANSACTIONAL_TABLE_MESSAGE); } + @Test @Override public void testUpdate() { @@ -336,6 +344,7 @@ public void testUpdate() .hasMessage(MODIFYING_NON_TRANSACTIONAL_TABLE_MESSAGE); } + @Test @Override public void testRowLevelUpdate() { @@ -343,6 +352,7 @@ public void testRowLevelUpdate() .hasMessage(MODIFYING_NON_TRANSACTIONAL_TABLE_MESSAGE); } + @Test @Override public void testUpdateRowConcurrently() throws Exception @@ -354,6 +364,7 @@ public void testUpdateRowConcurrently() .hasMessage(MODIFYING_NON_TRANSACTIONAL_TABLE_MESSAGE); } + @Test @Override public void testUpdateWithPredicates() { @@ -361,6 +372,7 @@ public void testUpdateWithPredicates() .hasMessage(MODIFYING_NON_TRANSACTIONAL_TABLE_MESSAGE); } + @Test @Override public void testUpdateRowType() { @@ -368,6 +380,7 @@ public void testUpdateRowType() .hasMessage(MODIFYING_NON_TRANSACTIONAL_TABLE_MESSAGE); } + @Test @Override public void testUpdateAllValues() { @@ -375,6 +388,7 @@ public void testUpdateAllValues() .hasMessage(MODIFYING_NON_TRANSACTIONAL_TABLE_MESSAGE); } + @Test @Override public void testExplainAnalyzeWithDeleteWithSubquery() { @@ -382,6 +396,7 @@ public void testExplainAnalyzeWithDeleteWithSubquery() .hasStackTraceContaining(MODIFYING_NON_TRANSACTIONAL_TABLE_MESSAGE); } + @Test @Override public void testDeleteWithVarcharPredicate() { @@ -389,6 +404,7 @@ public void testDeleteWithVarcharPredicate() .hasStackTraceContaining(MODIFYING_NON_TRANSACTIONAL_TABLE_MESSAGE); } + @Test @Override public void testRowLevelDelete() { @@ -733,6 +749,7 @@ public void testSchemaAuthorizationForRole() assertUpdate(admin, "DROP ROLE authorized_users IN hive"); } + @Test @Override public void testCreateSchemaWithNonLowercaseOwnerName() { @@ -2141,10 +2158,14 @@ public void testCreateTableUnsupportedPartitionTypeAs() .hasMessageMatching("Unsupported type .* for partition: a"); } - @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "Unsupported Hive type: varchar\\(65536\\)\\. Supported VARCHAR types: VARCHAR\\(<=65535\\), VARCHAR\\.") + @Test public void testCreateTableNonSupportedVarcharColumn() { - assertUpdate("CREATE TABLE test_create_table_non_supported_varchar_column (apple varchar(65536))"); + assertThatThrownBy(() -> { + assertUpdate("CREATE TABLE test_create_table_non_supported_varchar_column (apple varchar(65536))"); + }) + .isInstanceOf(RuntimeException.class) + .hasMessageMatching("Unsupported Hive type: varchar\\(65536\\)\\. Supported VARCHAR types: VARCHAR\\(<=65535\\), VARCHAR\\."); } @Test @@ -3437,7 +3458,7 @@ public void testNullPartitionValues() @Override public void testInsertHighestUnicodeCharacter() { - throw new SkipException("Covered by testInsertUnicode"); + abort("Covered by testInsertUnicode"); } @Test @@ -5149,6 +5170,7 @@ public void testDropColumn() assertUpdate("DROP TABLE test_drop_column"); } + @Test @Override public void testDropAndAddColumnWithSameName() { @@ -9156,7 +9178,7 @@ protected Optional filterDataMappingSmokeTestData(DataMapp @Override protected TestTable createTableWithDefaultColumns() { - throw new SkipException("Hive connector does not support column default values"); + return abort("Hive connector does not support column default values"); } @Override diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveConnectorTest.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveConnectorTest.java index 1d8c94367020..a39fdc84ce78 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveConnectorTest.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveConnectorTest.java @@ -14,7 +14,7 @@ package io.trino.plugin.hive; import io.trino.testing.QueryRunner; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.testing.TestingNames.randomNameSuffix; diff --git a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiConnectorTest.java b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiConnectorTest.java index d877e0194e1f..66527f5a2e52 100644 --- a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiConnectorTest.java +++ b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiConnectorTest.java @@ -18,7 +18,7 @@ import io.trino.testing.BaseConnectorTest; import io.trino.testing.QueryRunner; import io.trino.testing.TestingConnectorBehavior; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.plugin.hudi.HudiQueryRunner.createHudiQueryRunner; import static io.trino.plugin.hudi.testing.HudiTestUtils.COLUMNS_TO_HIDE; diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java index 5d8971c30cc2..92bfa3778f10 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java @@ -61,9 +61,10 @@ import org.apache.iceberg.io.FileIO; import org.apache.iceberg.util.JsonUtil; import org.intellij.lang.annotations.Language; -import org.testng.SkipException; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.Timeout; import java.io.File; import java.io.IOException; @@ -149,12 +150,15 @@ import static java.util.stream.IntStream.range; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.abort; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotEquals; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; +@TestInstance(PER_CLASS) public abstract class BaseIcebergConnectorTest extends BaseConnectorTest { @@ -189,13 +193,13 @@ protected IcebergQueryRunner.Builder createQueryRunnerBuilder() .setInitialTables(REQUIRED_TPCH_TABLES); } - @BeforeClass + @BeforeAll public void initFileSystem() { fileSystem = getFileSystemFactory(getDistributedQueryRunner()).create(SESSION); } - @BeforeClass + @BeforeAll public void initStorageTimePrecision() { try (TestTable table = new TestTable(getQueryRunner()::execute, "inspect_storage_precision", "(i int)")) { @@ -240,6 +244,7 @@ public void testAddRowFieldCaseInsensitivity() } } + @Test @Override public void testAddAndDropColumnName() { @@ -288,6 +293,7 @@ public void testDeleteOnV1Table() } } + @Test @Override public void testCharVarcharComparison() { @@ -1509,6 +1515,7 @@ public void testSchemaEvolution() dropTable("test_schema_evolution_drop_middle"); } + @Test @Override public void testDropRowFieldWhenDuplicates() { @@ -4802,7 +4809,7 @@ private OperatorStats getOperatorStats(QueryId queryId) @Override protected TestTable createTableWithDefaultColumns() { - throw new SkipException("Iceberg connector does not support column default values"); + return abort("Iceberg connector does not support column default values"); } @Override @@ -6432,6 +6439,7 @@ private void testMergeUpdateWithVariousLayouts(int writers, String partitioning) } @Test + @Override public void testMergeMultipleOperations() { testMergeMultipleOperations(1, ""); @@ -6538,6 +6546,7 @@ public void testMergeSimpleQueryPartitioned() } @Test + @Override public void testMergeMultipleRowsMatchFails() { testMergeMultipleRowsMatchFails("CREATE TABLE %s (customer VARCHAR, purchases INT, address VARCHAR)"); @@ -6964,7 +6973,8 @@ public void testDropCorruptedTableWithHiveRedirection() assertFalse(fileSystem.listFiles(tableLocation).hasNext(), "Table location should not exist"); } - @Test(timeOut = 10_000) + @Test + @Timeout(10) public void testNoRetryWhenMetadataFileInvalid() throws Exception { diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergAvroConnectorTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergAvroConnectorTest.java index ff0fa9b71fa9..d85a8fe78043 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergAvroConnectorTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergAvroConnectorTest.java @@ -13,9 +13,10 @@ */ package io.trino.plugin.iceberg; -import org.testng.SkipException; +import org.junit.jupiter.api.Test; import static io.trino.plugin.iceberg.IcebergFileFormat.AVRO; +import static org.junit.jupiter.api.Assumptions.abort; public class TestIcebergAvroConnectorTest extends BaseIcebergConnectorTest @@ -37,16 +38,17 @@ protected boolean supportsRowGroupStatistics(String typeName) return false; } + @Test @Override public void testIncorrectIcebergFileSizes() { - throw new SkipException("Avro does not do tail reads"); + abort("Avro does not do tail reads"); } @Override protected boolean isFileSorted(String path, String sortColumnName) { - throw new SkipException("Unimplemented"); + return abort("Unimplemented"); } @Override diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMinioOrcConnectorTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMinioOrcConnectorTest.java index 0c08d0c6a8c6..5df3dbad90ce 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMinioOrcConnectorTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMinioOrcConnectorTest.java @@ -19,7 +19,7 @@ import io.trino.testing.QueryRunner; import io.trino.testing.containers.Minio; import io.trino.testing.sql.TestTable; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.File; import java.io.OutputStream; @@ -163,6 +163,7 @@ public void testTimeType() } } + @Test @Override public void testDropAmbiguousRowFieldCaseSensitivity() { diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergParquetConnectorTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergParquetConnectorTest.java index e05324d2fb89..7f41b139e425 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergParquetConnectorTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergParquetConnectorTest.java @@ -15,7 +15,7 @@ import io.trino.testing.MaterializedResult; import io.trino.testing.sql.TestTable; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Optional; import java.util.stream.Collectors; @@ -80,6 +80,7 @@ protected Optional filterSetColumnTypesDataProvider(SetColum return super.filterSetColumnTypesDataProvider(setup); } + @Test @Override public void testDropAmbiguousRowFieldCaseSensitivity() { diff --git a/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteConnectorTest.java b/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteConnectorTest.java index 19d1495fce98..f932948ccc2f 100644 --- a/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteConnectorTest.java +++ b/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteConnectorTest.java @@ -24,8 +24,7 @@ import io.trino.testing.sql.TestTable; import io.trino.testng.services.Flaky; import org.intellij.lang.annotations.Language; -import org.testng.SkipException; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.Locale; @@ -38,6 +37,7 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.abort; public class TestIgniteConnectorTest extends BaseJdbcConnectorTest @@ -245,6 +245,7 @@ protected TestTable createTableWithDefaultColumns() "dummy_id varchar NOT NULL primary key)"); } + @Test @Override public void testShowCreateTable() { @@ -388,11 +389,12 @@ protected TestTable simpleTable() return new TestTable(onRemoteDatabase(), format("%s.simple_table", getSession().getSchema().orElseThrow()), "(col BIGINT, id bigint primary key)", ImmutableList.of("1, 1", "2, 2")); } + @Test @Override public void testCharVarcharComparison() { // Ignite will map char to varchar, skip - throw new SkipException("Ignite map char to varchar, skip test"); + abort("Ignite map char to varchar, skip test"); } @Override @@ -401,10 +403,11 @@ protected String errorMessageForInsertIntoNotNullColumn(String columnName) return format("Failed to insert data: Null value is not allowed for column '%s'", columnName.toUpperCase(Locale.ENGLISH)); } + @Test @Override public void testCharTrailingSpace() { - throw new SkipException("Ignite not support char trailing space"); + abort("Ignite not support char trailing space"); } @Override @@ -430,6 +433,7 @@ protected Optional filterDataMappingSmokeTestData(DataMapp return Optional.of(dataMappingTestSetup); } + @Test @Override public void testDateYearOfEraPredicate() { diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaConnectorTest.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaConnectorTest.java index a82c24096871..15374d28cf09 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaConnectorTest.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaConnectorTest.java @@ -26,8 +26,7 @@ import io.trino.tpch.TpchTable; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.common.serialization.ByteArraySerializer; -import org.testng.SkipException; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.nio.ByteBuffer; import java.util.List; @@ -67,6 +66,7 @@ import static org.apache.kafka.clients.producer.ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.abort; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; @@ -196,7 +196,7 @@ public void testInternalFieldPrefix() @Override protected TestTable createTableWithDefaultColumns() { - throw new SkipException("Kafka connector does not support column default values"); + return abort("Kafka connector does not support column default values"); } @Test @@ -432,7 +432,7 @@ public void testInsertArray() // Override because the base test uses CREATE TABLE statement that is unsupported in Kafka connector assertThatThrownBy(() -> query("INSERT INTO " + TABLE_INSERT_ARRAY + " (a) VALUES (ARRAY[null])")) .hasMessage("Unsupported column type 'array(double)' for column 'a'"); - throw new SkipException("not supported"); + abort("not supported"); } @Test @@ -469,10 +469,11 @@ public void testInsertHighestUnicodeCharacter() .containsExactlyInAnyOrder("Hello", "hello测试􏿿world编码"); } + @Test @Override public void testInsertRowConcurrently() { - throw new SkipException("TODO Prepare a topic in Kafka and enable this test"); + abort("TODO Prepare a topic in Kafka and enable this test"); } @Test diff --git a/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduConnectorTest.java b/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduConnectorTest.java index 067303b15309..29677ab90a77 100644 --- a/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduConnectorTest.java +++ b/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduConnectorTest.java @@ -22,7 +22,6 @@ import io.trino.testing.sql.TestTable; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.testng.SkipException; import java.util.Optional; import java.util.OptionalInt; @@ -38,6 +37,7 @@ import static java.util.Locale.ENGLISH; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.abort; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNull; @@ -303,13 +303,14 @@ protected void testColumnName(String columnName, boolean delimited) } } + @Test @Override public void testAddNotNullColumnToEmptyTable() { // TODO: Enable this test assertThatThrownBy(super::testAddNotNullColumnToEmptyTable) .hasMessage("Table partitioning must be specified using setRangePartitionColumns or addHashPartitions"); - throw new SkipException("TODO"); + abort("TODO"); } @Test @@ -417,6 +418,7 @@ public void testCreateTable() //assertFalse(getQueryRunner().tableExists(getSession(), tableNameLike)); } + @Test @Override public void testCreateTableWithLongTableName() { @@ -440,6 +442,7 @@ public void testCreateTableWithLongTableName() assertFalse(getQueryRunner().tableExists(getSession(), validTableName)); } + @Test @Override public void testCreateTableWithLongColumnName() { @@ -466,6 +469,7 @@ public void testCreateTableWithLongColumnName() assertFalse(getQueryRunner().tableExists(getSession(), tableName)); } + @Test @Override public void testCreateTableWithColumnComment() { @@ -482,6 +486,7 @@ public void testCreateTableWithColumnComment() assertUpdate("DROP TABLE IF EXISTS " + tableName); } + @Test @Override public void testDropTable() { @@ -597,6 +602,7 @@ public void testInsertHighestUnicodeCharacter() } } + @Test @Override public void testInsertNegativeDate() { @@ -687,7 +693,8 @@ protected TestTable createTableWithOneIntegerColumn(String namePrefix) * This test fails intermittently because Kudu doesn't have strong enough * semantics to support writing from multiple threads. */ - @org.testng.annotations.Test(enabled = false) + @Test + @Disabled @Override public void testUpdateWithPredicates() { @@ -709,6 +716,7 @@ public void testUpdateWithPredicates() * This test fails intermittently because Kudu doesn't have strong enough * semantics to support writing from multiple threads. */ + @Test @Override public void testUpdateAllValues() { @@ -720,12 +728,14 @@ public void testUpdateAllValues() }); } + @Test @Override public void testWrittenStats() { // TODO Kudu connector supports CTAS and inserts, but the test would fail } + @Test @Override public void testReadMetadataWithRelationsConcurrentModifications() { @@ -737,7 +747,7 @@ public void testReadMetadataWithRelationsConcurrentModifications() // TODO (https://github.com/trinodb/trino/issues/12974): shouldn't fail assertThat(expected) .hasMessageMatching(".* table .* was deleted: Table deleted at .* UTC"); - throw new SkipException("to be fixed"); + abort("to be fixed"); } } @@ -774,15 +784,17 @@ public void testDateYearOfEraPredicate() .hasStackTraceContaining("Cannot apply operator: varchar = date"); } + @Test @Override public void testVarcharCastToDateInPredicate() { assertThatThrownBy(super::testVarcharCastToDateInPredicate) .hasStackTraceContaining("Table partitioning must be specified using setRangePartitionColumns or addHashPartitions"); - throw new SkipException("TODO: implement the test for Kudu"); + abort("TODO: implement the test for Kudu"); } + @Test @Override public void testCharVarcharComparison() { @@ -792,7 +804,7 @@ public void testCharVarcharComparison() .hasMessageContaining("Actual rows") .hasMessageContaining("Expected rows"); - throw new SkipException("TODO"); + abort("TODO"); } @Test @@ -944,7 +956,8 @@ public void testRowLevelDelete() * This test fails intermittently because Kudu doesn't have strong enough * semantics to support writing from multiple threads. */ - @org.testng.annotations.Test(enabled = false) + @Test + @Disabled @Override public void testUpdate() { @@ -960,7 +973,8 @@ public void testUpdate() * This test fails intermittently because Kudu doesn't have strong enough * semantics to support writing from multiple threads. */ - @org.testng.annotations.Test(enabled = false) + @Test + @Disabled @Override public void testRowLevelUpdate() { @@ -981,11 +995,12 @@ public void testRowLevelUpdate() }); } + @Test @Override public void testUpdateRowConcurrently() throws Exception { - throw new SkipException("Kudu doesn't support concurrent update of different columns in a row"); + abort("Kudu doesn't support concurrent update of different columns in a row"); } @Test @@ -1036,10 +1051,11 @@ protected Optional filterDataMappingSmokeTestData(DataMapp return Optional.of(dataMappingTestSetup); } + @Test @Override protected TestTable createTableWithDefaultColumns() { - throw new SkipException("Kudu connector does not support column default values"); + return abort("Kudu connector does not support column default values"); } @Override diff --git a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/BaseMariaDbConnectorTest.java b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/BaseMariaDbConnectorTest.java index d2ed5a68ed43..347ebe78610e 100644 --- a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/BaseMariaDbConnectorTest.java +++ b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/BaseMariaDbConnectorTest.java @@ -18,7 +18,7 @@ import io.trino.testing.MaterializedResult; import io.trino.testing.TestingConnectorBehavior; import io.trino.testing.sql.TestTable; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Optional; import java.util.OptionalInt; @@ -84,7 +84,7 @@ protected TestTable createTableWithUnsupportedColumn() "(one bigint, two decimal(50,0), three varchar(10))"); } - @org.junit.jupiter.api.Test + @Test @Override public void testShowColumns() { @@ -181,6 +181,7 @@ public void testColumnComment() assertUpdate("DROP TABLE test_column_comment"); } + @Test @Override public void testAddNotNullColumn() { @@ -288,6 +289,7 @@ public void testInsertIntoNotNullColumn() } } + @Test @Override public void testNativeQueryCreateStatement() { @@ -298,6 +300,7 @@ public void testNativeQueryCreateStatement() assertFalse(getQueryRunner().tableExists(getSession(), "numbers")); } + @Test @Override public void testNativeQueryInsertStatementTableExists() { diff --git a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbConnectorTest.java b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbConnectorTest.java index cc8e3d69db11..42a7bf7432e8 100644 --- a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbConnectorTest.java +++ b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbConnectorTest.java @@ -16,6 +16,7 @@ import com.google.common.collect.ImmutableMap; import io.trino.testing.QueryRunner; import io.trino.testing.sql.SqlExecutor; +import org.junit.jupiter.api.Test; import static io.trino.plugin.mariadb.MariaDbQueryRunner.createMariaDbQueryRunner; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -37,6 +38,7 @@ protected SqlExecutor onRemoteDatabase() return server::execute; } + @Test @Override public void testRenameColumn() { @@ -44,6 +46,7 @@ public void testRenameColumn() .hasMessageContaining("Rename column not supported for the MariaDB server version"); } + @Test @Override public void testRenameColumnName() { @@ -53,6 +56,7 @@ public void testRenameColumnName() } } + @Test @Override public void testAlterTableRenameColumnToLongName() { diff --git a/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryConnectorTest.java b/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryConnectorTest.java index 0ce6f079554f..50fb43f610b2 100644 --- a/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryConnectorTest.java +++ b/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryConnectorTest.java @@ -32,8 +32,8 @@ import io.trino.testng.services.Flaky; import io.trino.tpch.TpchTable; import org.intellij.lang.annotations.Language; -import org.testng.SkipException; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; import java.util.List; @@ -44,6 +44,7 @@ import static io.trino.sql.planner.OptimizerConfig.JoinDistributionType.BROADCAST; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.abort; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; @@ -107,7 +108,7 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) @Override protected TestTable createTableWithDefaultColumns() { - throw new SkipException("Memory connector does not support column default values"); + return abort("Memory connector does not support column default values"); } @Test @@ -187,7 +188,8 @@ private Metrics collectCustomMetrics(String sql) .reduce(Metrics.EMPTY, Metrics::mergeWith); } - @Test(timeOut = 30_000) + @Test + @Timeout(30) public void testPhysicalInputPositions() { MaterializedResultWithQueryId result = getDistributedQueryRunner().executeWithQueryId( @@ -202,7 +204,8 @@ public void testPhysicalInputPositions() assertEquals(probeStats.getPhysicalInputPositions(), LINEITEM_COUNT); } - @Test(timeOut = 30_000) + @Test + @Timeout(30) public void testJoinDynamicFilteringNone() { for (JoinDistributionType joinDistributionType : JoinDistributionType.values()) { @@ -215,7 +218,8 @@ public void testJoinDynamicFilteringNone() } } - @Test(timeOut = 30_000) + @Test + @Timeout(30) public void testJoinLargeBuildSideDynamicFiltering() { for (JoinDistributionType joinDistributionType : JoinDistributionType.values()) { @@ -236,7 +240,8 @@ public void testJoinLargeBuildSideDynamicFiltering() } } - @Test(timeOut = 30_000) + @Test + @Timeout(30) public void testJoinDynamicFilteringSingleValue() { for (JoinDistributionType joinDistributionType : JoinDistributionType.values()) { @@ -274,7 +279,8 @@ public void testJoinDynamicFilteringImplicitCoercion() 6, ORDERS_COUNT); } - @Test(timeOut = 30_000) + @Test + @Timeout(30) public void testJoinDynamicFilteringBlockProbeSide() { for (JoinDistributionType joinDistributionType : JoinDistributionType.values()) { @@ -290,7 +296,8 @@ public void testJoinDynamicFilteringBlockProbeSide() } } - @Test(timeOut = 30_000) + @Test + @Timeout(30) public void testSemiJoinDynamicFilteringNone() { for (JoinDistributionType joinDistributionType : JoinDistributionType.values()) { @@ -303,7 +310,8 @@ public void testSemiJoinDynamicFilteringNone() } } - @Test(timeOut = 30_000) + @Test + @Timeout(30) public void testSemiJoinLargeBuildSideDynamicFiltering() { for (JoinDistributionType joinDistributionType : JoinDistributionType.values()) { @@ -326,7 +334,8 @@ public void testSemiJoinLargeBuildSideDynamicFiltering() } } - @Test(timeOut = 30_000) + @Test + @Timeout(30) public void testSemiJoinDynamicFilteringSingleValue() { for (JoinDistributionType joinDistributionType : JoinDistributionType.values()) { @@ -346,7 +355,8 @@ public void testSemiJoinDynamicFilteringSingleValue() } } - @Test(timeOut = 30_000) + @Test + @Timeout(30) public void testSemiJoinDynamicFilteringBlockProbeSide() { for (JoinDistributionType joinDistributionType : JoinDistributionType.values()) { @@ -438,7 +448,8 @@ public void testCrossJoinLargeBuildSideDynamicFiltering() ORDERS_COUNT, CUSTOMER_COUNT); } - @Test(timeOut = 30_000) + @Test + @Timeout(30) public void testJoinDynamicFilteringMultiJoin() { for (JoinDistributionType joinDistributionType : JoinDistributionType.values()) { diff --git a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoConnectorTest.java b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoConnectorTest.java index ac8b91a0bf87..675f3d5eedde 100644 --- a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoConnectorTest.java +++ b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoConnectorTest.java @@ -35,10 +35,10 @@ import org.bson.Document; import org.bson.types.Decimal128; import org.bson.types.ObjectId; -import org.testng.SkipException; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import java.math.BigDecimal; import java.time.LocalDate; @@ -60,10 +60,13 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.abort; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; +@TestInstance(PER_CLASS) public class TestMongoConnectorTest extends BaseConnectorTest { @@ -79,13 +82,13 @@ protected QueryRunner createQueryRunner() return createMongoQueryRunner(server, ImmutableMap.of(), REQUIRED_TPCH_TABLES); } - @BeforeClass + @BeforeAll public void initTestSchema() { assertUpdate("CREATE SCHEMA IF NOT EXISTS test"); } - @AfterClass(alwaysRun = true) + @AfterAll public final void destroy() { server.close(); @@ -116,7 +119,7 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) @Override protected TestTable createTableWithDefaultColumns() { - throw new SkipException("MongoDB connector does not support column default values"); + return abort("MongoDB connector does not support column default values"); } @Test @@ -128,7 +131,7 @@ public void testColumnName() assertThatThrownBy(() -> testColumnName(columnName, requiresDelimiting(columnName))) .isInstanceOf(RuntimeException.class) .hasMessage("Column name must not contain '$' or '.' for INSERT: " + columnName); - throw new SkipException("Insert would fail"); + abort("Insert would fail"); } testColumnName(columnName, requiresDelimiting(columnName)); @@ -274,6 +277,7 @@ public void testInsertWithEveryType() assertFalse(getQueryRunner().tableExists(getSession(), tableName)); } + @Test @Override public void testDeleteWithComplexPredicate() { @@ -281,6 +285,7 @@ public void testDeleteWithComplexPredicate() .hasStackTraceContaining("TrinoException: " + MODIFYING_ROWS_MESSAGE); } + @Test @Override public void testDeleteWithLike() { @@ -288,6 +293,7 @@ public void testDeleteWithLike() .hasStackTraceContaining("TrinoException: " + MODIFYING_ROWS_MESSAGE); } + @Test @Override public void testDeleteWithSemiJoin() { @@ -295,6 +301,7 @@ public void testDeleteWithSemiJoin() .hasStackTraceContaining("TrinoException: " + MODIFYING_ROWS_MESSAGE); } + @Test @Override public void testDeleteWithSubquery() { @@ -302,6 +309,7 @@ public void testDeleteWithSubquery() .hasStackTraceContaining("TrinoException: " + MODIFYING_ROWS_MESSAGE); } + @Test @Override public void testExplainAnalyzeWithDeleteWithSubquery() { @@ -948,11 +956,12 @@ public void testCollationNumericOrdering() assertUpdate("DROP TABLE test." + tableName); } + @Test @Override public void testAddColumnConcurrently() { // TODO: Enable after supporting multi-document transaction https://www.mongodb.com/docs/manual/core/transactions/ - throw new SkipException("TODO"); + abort("TODO"); } @Test @@ -1321,7 +1330,7 @@ public void testFiltersOnDereferenceColumnReadsLessData() private void testFiltersOnDereferenceColumnReadsLessData(String expectedValue, String expectedType) { if (!isPushdownSupportedType(getQueryRunner().getTypeManager().fromSqlType(expectedType))) { - throw new SkipException("Type doesn't support filter pushdown"); + abort("Type doesn't support filter pushdown"); } Session sessionWithoutPushdown = Session.builder(getSession()) @@ -1772,20 +1781,20 @@ private void testPredicateOnDBRefLikeDocument(Object objectId, String expectedVa assertUpdate("DROP TABLE test." + tableName); } - @Override @Test + @Override public void testProjectionPushdownReadsLessData() { // TODO https://github.com/trinodb/trino/issues/17713 - throw new SkipException("MongoDB connector does not calculate physical data input size"); + abort("MongoDB connector does not calculate physical data input size"); } - @Override @Test + @Override public void testProjectionPushdownPhysicalInputSize() { // TODO https://github.com/trinodb/trino/issues/17713 - throw new SkipException("MongoDB connector does not calculate physical data input size"); + abort("MongoDB connector does not calculate physical data input size"); } @Override diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/BaseMySqlConnectorTest.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/BaseMySqlConnectorTest.java index 350e7706fc5f..cc0322f29c5a 100644 --- a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/BaseMySqlConnectorTest.java +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/BaseMySqlConnectorTest.java @@ -23,7 +23,7 @@ import io.trino.testing.TestingConnectorBehavior; import io.trino.testing.sql.SqlExecutor; import io.trino.testing.sql.TestTable; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.PreparedStatement; @@ -102,7 +102,7 @@ protected TestTable createTableWithUnsupportedColumn() "(one bigint, two decimal(50,0), three varchar(10))"); } - @org.junit.jupiter.api.Test + @Test @Override public void testShowColumns() { @@ -164,6 +164,7 @@ protected MaterializedResult getDescribeOrdersResult() .build(); } + @Test @Override public void testShowCreateTable() { @@ -181,6 +182,7 @@ public void testShowCreateTable() ")"); } + @Test @Override public void testDeleteWithLike() { @@ -261,6 +263,7 @@ public void testColumnComment() assertUpdate("DROP TABLE test_column_comment"); } + @Test @Override public void testAddNotNullColumn() { @@ -509,6 +512,7 @@ public void testNativeMultipleInClauses() onRemoteDatabase().execute("SELECT count(*) FROM tpch.orders WHERE " + longInClauses); } + @Test @Override public void testNativeQueryInsertStatementTableDoesNotExist() { @@ -518,6 +522,7 @@ public void testNativeQueryInsertStatementTableDoesNotExist() .hasMessageContaining("Query not supported: ResultSetMetaData not available for query: INSERT INTO non_existent_table VALUES (1)"); } + @Test @Override public void testNativeQueryIncorrectSyntax() { diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlLegacyConnectorTest.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlLegacyConnectorTest.java index 1c951c17a6c5..f8cc52e93a36 100644 --- a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlLegacyConnectorTest.java +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlLegacyConnectorTest.java @@ -134,6 +134,7 @@ public void testRenameColumn() .hasStackTraceContaining("RENAME COLUMN x TO before_y"); } + @Test @Override public void testRenameColumnName() { @@ -144,6 +145,7 @@ public void testRenameColumnName() } } + @Test @Override public void testAlterTableRenameColumnToLongName() { @@ -169,6 +171,7 @@ protected void verifyColumnNameLengthFailurePermissible(Throwable e) assertThat(e).hasMessageMatching("Identifier name .* is too long"); } + @Test @Override public void testNativeQueryWithClause() { diff --git a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/BaseOracleConnectorTest.java b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/BaseOracleConnectorTest.java index 0d9834820a8d..644959e2d8e1 100644 --- a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/BaseOracleConnectorTest.java +++ b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/BaseOracleConnectorTest.java @@ -23,7 +23,7 @@ import io.trino.testing.TestingConnectorBehavior; import io.trino.testing.sql.TestTable; import io.trino.testing.sql.TestView; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.Optional; @@ -115,14 +115,14 @@ protected TestTable createTableWithUnsupportedColumn() "(one NUMBER(19), two NUMBER, three VARCHAR2(10 CHAR))"); } - @org.junit.jupiter.api.Test + @Test @Override public void testShowColumns() { assertThat(query("SHOW COLUMNS FROM orders")).matches(getDescribeOrdersResult()); } - @org.junit.jupiter.api.Test + @Test @Override public void testInformationSchemaFiltering() { @@ -191,6 +191,7 @@ public void testTimestampOutOfPrecisionRounded() assertUpdate("DROP TABLE " + tableName); } + @Test @Override public void testCharVarcharComparison() { @@ -214,6 +215,7 @@ public void testCharVarcharComparison() } } + @Test @Override public void testVarcharCharComparison() { @@ -244,6 +246,7 @@ public void testVarcharCharComparison() } } + @Test @Override public void testAggregationWithUnsupportedResultType() { @@ -266,6 +269,7 @@ protected TestTable createAggregationTestTable(String name, List rows) return new TestTable(onRemoteDatabase(), name, "(short_decimal number(9, 3), long_decimal number(30, 10), a_bigint number(19), t_double binary_double)", rows); } + @Test @Override public void testDeleteWithLike() { @@ -372,6 +376,7 @@ public void testTooLargeDomainCompactionThreshold() "SELECT * from nation", "Domain compaction threshold \\(10000\\) cannot exceed 1000"); } + @Test @Override public void testNativeQuerySimple() { @@ -379,6 +384,7 @@ public void testNativeQuerySimple() assertQuery("SELECT * FROM TABLE(system.query(query => 'SELECT CAST(1 AS number(2, 1)) FROM DUAL'))", ("VALUES 1")); } + @Test @Override public void testNativeQueryParameters() { @@ -391,6 +397,7 @@ public void testNativeQueryParameters() assertQuery(session, "EXECUTE my_query USING 'a', '(SELECT CAST(2 AS number(2, 1)) a FROM DUAL) t'", "VALUES 2"); } + @Test @Override public void testNativeQueryInsertStatementTableDoesNotExist() { @@ -400,6 +407,7 @@ public void testNativeQueryInsertStatementTableDoesNotExist() .hasMessageContaining("Query not supported: ResultSetMetaData not available for query: INSERT INTO non_existent_table VALUES (1)"); } + @Test @Override public void testNativeQueryIncorrectSyntax() { diff --git a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleConnectorTest.java b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleConnectorTest.java index af499f537977..a372c5171e48 100644 --- a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleConnectorTest.java +++ b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleConnectorTest.java @@ -18,8 +18,9 @@ import io.trino.testing.QueryRunner; import io.trino.testing.sql.SqlExecutor; import io.trino.testing.sql.TestTable; -import org.testng.annotations.AfterClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import static io.trino.plugin.jdbc.DefaultJdbcMetadata.DEFAULT_COLUMN_ALIAS_LENGTH; import static io.trino.plugin.oracle.TestingOracleServer.TEST_PASS; @@ -29,7 +30,9 @@ import static java.util.stream.Collectors.joining; import static java.util.stream.IntStream.range; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +@TestInstance(PER_CLASS) public class TestOracleConnectorTest extends BaseOracleConnectorTest { @@ -55,7 +58,7 @@ protected QueryRunner createQueryRunner() REQUIRED_TPCH_TABLES); } - @AfterClass(alwaysRun = true) + @AfterAll public final void destroy() throws Exception { diff --git a/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixConnectorTest.java b/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixConnectorTest.java index c1a29cbe9db5..02a206a5297e 100644 --- a/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixConnectorTest.java +++ b/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixConnectorTest.java @@ -25,8 +25,8 @@ import io.trino.testing.sql.SqlExecutor; import io.trino.testing.sql.TestTable; import org.intellij.lang.annotations.Language; -import org.testng.SkipException; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.DriverManager; @@ -64,6 +64,7 @@ import static java.util.Locale.ENGLISH; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.abort; import static org.testng.Assert.assertTrue; public class TestPhoenixConnectorTest @@ -108,6 +109,7 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) } // TODO: wait https://github.com/trinodb/trino/pull/14939 done and then remove this test + @Test @Override public void testArithmeticPredicatePushdown() { @@ -204,40 +206,44 @@ private void verifyDivisionByZeroFailure(Throwable e) @Override protected TestTable createTableWithDefaultColumns() { - throw new SkipException("Phoenix connector does not support column default values"); + return abort("Phoenix connector does not support column default values"); } @Override protected TestTable createTableWithUnsupportedColumn() { // Apparently all Phoenix types are supported in the Phoenix connector. - throw new SkipException("Cannot find an unsupported data type"); + return abort("Cannot find an unsupported data type"); } + @Test @Override public void testRenameColumn() { assertThatThrownBy(super::testRenameColumn) // TODO (https://github.com/trinodb/trino/issues/7205) support column rename in Phoenix .hasMessageContaining("Syntax error. Encountered \"RENAME\""); - throw new SkipException("Rename column is not yet supported by Phoenix connector"); + abort("Rename column is not yet supported by Phoenix connector"); } + @Test @Override public void testAlterTableRenameColumnToLongName() { assertThatThrownBy(super::testAlterTableRenameColumnToLongName) // TODO (https://github.com/trinodb/trino/issues/7205) support column rename in Phoenix .hasMessageContaining("Syntax error. Encountered \"RENAME\""); - throw new SkipException("Rename column is not yet supported by Phoenix connector"); + abort("Rename column is not yet supported by Phoenix connector"); } - @Test(enabled = false) + @Test + @Disabled @Override public void testRenameColumnName() { } + @Test @Override public void testAddAndDropColumnName() { @@ -246,18 +252,19 @@ public void testAddAndDropColumnName() if (columnName.equals("an'apostrophe")) { assertThatThrownBy(() -> testAddAndDropColumnName(columnName, requiresDelimiting(columnName))) .hasMessageContaining("Syntax error. Mismatched input"); - throw new SkipException("TODO"); + abort("TODO"); } if (columnName.equals("a\\backslash`")) { assertThatThrownBy(() -> testAddAndDropColumnName(columnName, requiresDelimiting(columnName))) .hasMessageContaining("Undefined column"); - throw new SkipException("TODO"); + abort("TODO"); } testAddAndDropColumnName(columnName, requiresDelimiting(columnName)); } } + @Test @Override public void testInsertArray() { @@ -266,10 +273,11 @@ public void testInsertArray() .hasMessage("Phoenix JDBC driver replaced 'null' with '0.0' at index 1 in [0.0]"); } + @Test @Override public void testCreateSchema() { - throw new SkipException("test disabled until issue fixed"); // TODO https://github.com/trinodb/trino/issues/2348 + abort("test disabled until issue fixed"); // TODO https://github.com/trinodb/trino/issues/2348 } @Override @@ -304,6 +312,7 @@ protected Optional filterDataMappingSmokeTestData(DataMapp return Optional.of(dataMappingTestSetup); } + @Test @Override public void testShowCreateTable() { @@ -327,6 +336,7 @@ public void testShowCreateTable() ")"); } + @Test @Override public void testCharVarcharComparison() { @@ -350,6 +360,7 @@ public void testCharVarcharComparison() } } + @Test @Override public void testVarcharCharComparison() { @@ -380,6 +391,7 @@ public void testVarcharCharComparison() } } + @Test @Override public void testCharTrailingSpace() { @@ -394,6 +406,7 @@ public void testCharTrailingSpace() } // Overridden because Phoenix requires a ROWID column + @Test @Override public void testCountDistinctWithStringTypes() { @@ -410,6 +423,7 @@ public void testCountDistinctWithStringTypes() } } + @Test @Override public void testMergeLarge() { @@ -518,10 +532,11 @@ private void testMergeWithSpecifiedRowkeys(String rowkeyDefinition) assertUpdate("DROP TABLE " + targetTable); } + @Test @Override public void testUpdateRowConcurrently() { - throw new SkipException("Phoenix doesn't support concurrent update of different columns in a row"); + abort("Phoenix doesn't support concurrent update of different columns in a row"); } @Test @@ -643,10 +658,11 @@ public void testMissingColumnsOnInsert() assertQuery("SELECT * FROM test_col_insert", "SELECT 1, 'val1', 'val2'"); } + @Test @Override public void testTopNPushdown() { - throw new SkipException("Phoenix does not support topN push down, but instead replaces partial topN with partial Limit."); + abort("Phoenix does not support topN push down, but instead replaces partial topN with partial Limit."); } @Test @@ -775,34 +791,38 @@ protected void verifyConcurrentAddColumnFailurePermissible(Exception e) .hasMessageContaining("Concurrent modification to table"); } + @Test @Override public void testCreateSchemaWithLongName() { // TODO: Find the maximum table schema length in Phoenix and enable this test. - throw new SkipException("TODO"); + abort("TODO"); } + @Test @Override public void testCreateTableWithLongTableName() { // TODO: Find the maximum table name length in Phoenix and enable this test. // Table name length with 65536 chars throws "startRow's length must be less than or equal to 32767 to meet the criteria for a row key." // 32767 chars still causes the same error and shorter names (e.g. 10000) causes timeout. - throw new SkipException("TODO"); + abort("TODO"); } + @Test @Override public void testCreateTableWithLongColumnName() { // TODO: Find the maximum column name length in Phoenix and enable this test. - throw new SkipException("TODO"); + abort("TODO"); } + @Test @Override public void testAlterTableAddLongColumnName() { // TODO: Find the maximum column name length in Phoenix and enable this test. - throw new SkipException("TODO"); + abort("TODO"); } @Test diff --git a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlConnectorTest.java b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlConnectorTest.java index a09f2055c004..0e1ebe8faec1 100644 --- a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlConnectorTest.java +++ b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlConnectorTest.java @@ -37,8 +37,9 @@ import io.trino.testing.sql.SqlExecutor; import io.trino.testing.sql.TestTable; import io.trino.testing.sql.TestView; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import java.sql.Connection; import java.sql.DriverManager; @@ -73,9 +74,11 @@ import static java.util.stream.Collectors.joining; import static java.util.stream.IntStream.range; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; +@TestInstance(PER_CLASS) public class TestPostgreSqlConnectorTest extends BaseJdbcConnectorTest { @@ -89,7 +92,7 @@ protected QueryRunner createQueryRunner() return createPostgreSqlQueryRunner(postgreSqlServer, Map.of(), Map.of(), REQUIRED_TPCH_TABLES); } - @BeforeClass + @BeforeAll public void setExtensions() { onRemoteDatabase().execute("CREATE EXTENSION IF NOT EXISTS file_fdw"); diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/BaseRaptorConnectorTest.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/BaseRaptorConnectorTest.java index c8fdfa351a5a..fab62710dcc6 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/BaseRaptorConnectorTest.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/BaseRaptorConnectorTest.java @@ -25,8 +25,7 @@ import io.trino.testing.sql.TestTable; import io.trino.testng.services.Flaky; import org.intellij.lang.annotations.Language; -import org.testng.SkipException; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.time.LocalDate; import java.time.LocalDateTime; @@ -59,6 +58,7 @@ import static java.util.stream.Collectors.toSet; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.abort; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotEquals; @@ -90,7 +90,7 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) @Override protected TestTable createTableWithDefaultColumns() { - throw new SkipException("Raptor connector does not support column default values"); + return abort("Raptor connector does not support column default values"); } @Override @@ -256,11 +256,15 @@ public void testBucketNumberHiddenColumn() assertEquals(actual, IntStream.range(0, 50).boxed().collect(toSet())); } - @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = ".*Column '\\$bucket_number' cannot be resolved") + @Test public void testNoBucketNumberHiddenColumn() { - assertUpdate("CREATE TABLE test_no_bucket_number (test bigint)"); - computeActual("SELECT DISTINCT \"$bucket_number\" FROM test_no_bucket_number"); + assertThatThrownBy(() -> { + assertUpdate("CREATE TABLE test_no_bucket_number (test bigint)"); + computeActual("SELECT DISTINCT \"$bucket_number\" FROM test_no_bucket_number"); + }) + .isInstanceOf(RuntimeException.class) + .hasMessageMatching(".*Column '\\$bucket_number' cannot be resolved"); } @Test diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorBucketedConnectorTest.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorBucketedConnectorTest.java index 960bddfc3284..4ac6ac60687a 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorBucketedConnectorTest.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorBucketedConnectorTest.java @@ -15,7 +15,7 @@ import com.google.common.collect.ImmutableMap; import io.trino.testing.QueryRunner; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.plugin.raptor.legacy.RaptorQueryRunner.createRaptorQueryRunner; import static org.assertj.core.api.Assertions.assertThat; diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorMySqlConnectorTest.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorMySqlConnectorTest.java index 6e357c989ae7..b67827072044 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorMySqlConnectorTest.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorMySqlConnectorTest.java @@ -17,8 +17,9 @@ import io.trino.plugin.tpch.TpchPlugin; import io.trino.testing.DistributedQueryRunner; import io.trino.testing.QueryRunner; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.TestInstance; import org.testcontainers.containers.MySQLContainer; -import org.testng.annotations.AfterClass; import java.io.File; import java.util.Map; @@ -26,7 +27,9 @@ import static io.trino.plugin.raptor.legacy.RaptorQueryRunner.copyTables; import static io.trino.plugin.raptor.legacy.RaptorQueryRunner.createSession; import static java.lang.String.format; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +@TestInstance(PER_CLASS) public class TestRaptorMySqlConnectorTest extends BaseRaptorConnectorTest { @@ -41,7 +44,7 @@ protected QueryRunner createQueryRunner() return createRaptorMySqlQueryRunner(getJdbcUrl(mysqlContainer)); } - @AfterClass(alwaysRun = true) + @AfterAll public final void destroy() { mysqlContainer.close(); diff --git a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftConnectorTest.java b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftConnectorTest.java index cbae36a4dd4a..3c51094ab3eb 100644 --- a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftConnectorTest.java +++ b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftConnectorTest.java @@ -22,8 +22,7 @@ import io.trino.testing.sql.SqlExecutor; import io.trino.testing.sql.TestTable; import io.trino.tpch.TpchTable; -import org.testng.SkipException; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.Optional; @@ -44,6 +43,7 @@ import static java.util.Locale.ENGLISH; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.abort; public class TestRedshiftConnectorTest extends BaseJdbcConnectorTest @@ -146,6 +146,7 @@ protected Optional filterDataMappingSmokeTestData(DataMapp /** * Overridden due to Redshift not supporting non-ASCII characters in CHAR. */ + @Test @Override public void testCreateTableAsSelectWithUnicode() { @@ -245,6 +246,7 @@ public void testRedshiftAddNotNullColumn() } } + @Test @Override public void testDelete() { @@ -369,6 +371,7 @@ private static void gatherStats(String tableName) }); } + @Test @Override public void testCountDistinctWithStringTypes() { @@ -599,17 +602,18 @@ public void testDecimalAvgPushdownFoShortDecimalScale() } } - @Override @Test + @Override public void testReadMetadataWithRelationsConcurrentModifications() { - throw new SkipException("Test fails with a timeout sometimes and is flaky"); + abort("Test fails with a timeout sometimes and is flaky"); } + @Test @Override public void testInsertRowConcurrently() { - throw new SkipException("Test fails with a timeout sometimes and is flaky"); + abort("Test fails with a timeout sometimes and is flaky"); } @Override @@ -669,6 +673,7 @@ protected SqlExecutor onRemoteDatabase() return RedshiftQueryRunner::executeInRedshift; } + @Test @Override public void testDeleteWithLike() { diff --git a/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreConnectorTest.java b/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreConnectorTest.java index 7c9243212187..92f8e672279f 100644 --- a/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreConnectorTest.java +++ b/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreConnectorTest.java @@ -23,9 +23,9 @@ import io.trino.testing.TestingConnectorBehavior; import io.trino.testing.sql.SqlExecutor; import io.trino.testing.sql.TestTable; -import org.testng.SkipException; -import org.testng.annotations.AfterClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import java.util.Optional; import java.util.OptionalInt; @@ -40,10 +40,13 @@ import static java.util.stream.IntStream.range; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.abort; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; +@TestInstance(PER_CLASS) public class TestSingleStoreConnectorTest extends BaseJdbcConnectorTest { @@ -57,7 +60,7 @@ protected QueryRunner createQueryRunner() return createSingleStoreQueryRunner(singleStoreServer, ImmutableMap.of(), ImmutableMap.of(), REQUIRED_TPCH_TABLES); } - @AfterClass(alwaysRun = true) + @AfterAll public final void destroy() { singleStoreServer.close(); @@ -144,20 +147,23 @@ protected Optional filterDataMappingSmokeTestData(DataMapp return Optional.of(dataMappingTestSetup); } + @Test @Override public void testInsertUnicode() { // SingleStore's utf8 encoding is 3 bytes and truncates strings upon encountering a 4 byte sequence - throw new SkipException("SingleStore doesn't support utf8mb4"); + abort("SingleStore doesn't support utf8mb4"); } + @Test @Override public void testInsertHighestUnicodeCharacter() { // SingleStore's utf8 encoding is 3 bytes and truncates strings upon encountering a 4 byte sequence - throw new SkipException("SingleStore doesn't support utf8mb4"); + abort("SingleStore doesn't support utf8mb4"); } + @Test @Override public void testDeleteWithLike() { @@ -209,6 +215,7 @@ public void testSingleStoreTinyint() } // Overridden because the method from BaseConnectorTest fails on one of the assertions, see TODO below + @Test @Override public void testInsertIntoNotNullColumn() { @@ -253,6 +260,7 @@ public void testColumnComment() assertUpdate("DROP TABLE test_column_comment"); } + @Test @Override public void testAddNotNullColumn() { @@ -316,6 +324,7 @@ public void testPredicatePushdown() .isNotFullyPushedDown(AggregationNode.class); } + @Test @Override public void testCreateTableAsSelectNegativeDate() { @@ -333,6 +342,7 @@ public void testInsertNegativeDate() .hasStackTraceContaining("TrinoException: Driver returned null LocalDate for a non-null value"); } + @Test @Override public void testNativeQueryCreateStatement() { @@ -346,6 +356,7 @@ public void testNativeQueryCreateStatement() assertFalse(getQueryRunner().tableExists(getSession(), "numbers")); } + @Test @Override public void testNativeQueryInsertStatementTableExists() { diff --git a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerConnectorTest.java b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerConnectorTest.java index cdb4cfc3dc39..bc4d2e96a04f 100644 --- a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerConnectorTest.java +++ b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerConnectorTest.java @@ -25,8 +25,7 @@ import io.trino.testing.TestingConnectorBehavior; import io.trino.testing.sql.TestTable; import io.trino.testng.services.Flaky; -import org.testng.SkipException; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.Optional; @@ -42,6 +41,7 @@ import static java.util.stream.Collectors.joining; import static java.util.stream.IntStream.range; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assumptions.abort; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; @@ -152,7 +152,7 @@ public void testReadMetadataWithRelationsConcurrentModifications() "Lock request time out period exceeded|" + // E.g. system.metadata.table_comments can return empty results, when underlying metadata list tables call fails "Expecting actual not to be empty).*"); - throw new SkipException("to be fixed"); + abort("to be fixed"); } } @@ -583,6 +583,7 @@ public void testDateYearOfEraPredicate() ".*\\QConversion failed when converting date and/or time from character string.\\E"); } + @Test @Override public void testNativeQuerySimple() { diff --git a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerTransactionIsolationTest.java b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerTransactionIsolationTest.java index 859371798c1d..8d7c7a43307a 100644 --- a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerTransactionIsolationTest.java +++ b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerTransactionIsolationTest.java @@ -17,7 +17,7 @@ import io.trino.testing.MaterializedResult; import io.trino.testing.QueryRunner; import io.trino.testing.sql.SqlExecutor; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.Map; diff --git a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerConnectorTest.java b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerConnectorTest.java index 04d26ec7e8e4..434da9cc1f69 100644 --- a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerConnectorTest.java +++ b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerConnectorTest.java @@ -22,7 +22,7 @@ import io.trino.testng.services.Flaky; import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.Jdbi; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.SQLException; diff --git a/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/hive/TestHiveFaultTolerantExecutionConnectorTest.java b/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/hive/TestHiveFaultTolerantExecutionConnectorTest.java index 5503833738e2..4874d865742f 100644 --- a/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/hive/TestHiveFaultTolerantExecutionConnectorTest.java +++ b/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/hive/TestHiveFaultTolerantExecutionConnectorTest.java @@ -20,8 +20,9 @@ import io.trino.plugin.hive.BaseHiveConnectorTest; import io.trino.plugin.hive.HiveQueryRunner; import io.trino.testing.QueryRunner; -import org.testng.annotations.AfterClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import static io.airlift.units.DataSize.Unit.GIGABYTE; import static io.trino.SystemSessionProperties.FAULT_TOLERANT_EXECUTION_MAX_PARTITION_COUNT; @@ -29,7 +30,9 @@ import static io.trino.plugin.exchange.filesystem.containers.MinioStorage.getExchangeManagerProperties; import static io.trino.testing.FaultTolerantExecutionConnectorTestHelper.getExtraProperties; import static io.trino.testing.TestingNames.randomNameSuffix; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +@TestInstance(PER_CLASS) public class TestHiveFaultTolerantExecutionConnectorTest extends BaseHiveConnectorTest { @@ -50,12 +53,14 @@ protected QueryRunner createQueryRunner() })); } + @Test @Override public void testMultipleWriters() { // Not applicable for fault-tolerant mode. } + @Test @Override public void testMultipleWritersWithSkewedData() { @@ -65,6 +70,7 @@ public void testMultipleWritersWithSkewedData() // We need to override this method because in the case of pipeline execution, // the default number of writers are equal to worker count. Whereas, in the // fault-tolerant execution, it starts with 1. + @Test @Override public void testTaskWritersDoesNotScaleWithLargeMinWriterSize() { @@ -72,24 +78,28 @@ public void testTaskWritersDoesNotScaleWithLargeMinWriterSize() .isEqualTo(1); } + @Test @Override public void testWriterTasksCountLimitUnpartitioned() { // Not applicable for fault-tolerant mode. } + @Test @Override public void testWriterTasksCountLimitPartitionedScaleWritersDisabled() { // Not applicable for fault-tolerant mode. } + @Test @Override public void testWriterTasksCountLimitPartitionedScaleWritersEnabled() { // Not applicable for fault-tolerant mode. } + @Test @Override public void testWritersAcrossMultipleWorkersWhenScaleWritersIsEnabled() { @@ -106,7 +116,7 @@ public void testMaxOutputPartitionCountCheck() assertQueryFails(session, "SELECT nationkey, count(*) FROM nation GROUP BY nationkey", "Max number of output partitions exceeded for exchange.*"); } - @AfterClass(alwaysRun = true) + @AfterAll public void destroy() throws Exception { diff --git a/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/iceberg/TestIcebergParquetFaultTolerantExecutionConnectorTest.java b/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/iceberg/TestIcebergParquetFaultTolerantExecutionConnectorTest.java index 6bb86779e0ae..32d6c5d0a2db 100644 --- a/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/iceberg/TestIcebergParquetFaultTolerantExecutionConnectorTest.java +++ b/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/iceberg/TestIcebergParquetFaultTolerantExecutionConnectorTest.java @@ -17,15 +17,19 @@ import io.trino.plugin.exchange.filesystem.containers.MinioStorage; import io.trino.plugin.iceberg.IcebergQueryRunner; import io.trino.plugin.iceberg.TestIcebergParquetConnectorTest; -import org.testng.SkipException; -import org.testng.annotations.AfterClass; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import static io.trino.plugin.exchange.filesystem.containers.MinioStorage.getExchangeManagerProperties; import static io.trino.plugin.iceberg.IcebergTestUtils.checkParquetFileSorting; import static io.trino.testing.FaultTolerantExecutionConnectorTestHelper.getExtraProperties; import static io.trino.testing.TestingNames.randomNameSuffix; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.abort; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +@TestInstance(PER_CLASS) public class TestIcebergParquetFaultTolerantExecutionConnectorTest extends TestIcebergParquetConnectorTest { @@ -45,27 +49,30 @@ protected IcebergQueryRunner.Builder createQueryRunnerBuilder() }); } + @Test @Override public void testSplitPruningForFilterOnPartitionColumn() { // TODO: figure out why assertThatThrownBy(super::testSplitPruningForFilterOnPartitionColumn) .hasMessageContaining("Couldn't find operator summary, probably due to query statistic collection error"); - throw new SkipException("fails currently on FTE"); + abort("fails currently on FTE"); } + @Test @Override public void testStatsBasedRepartitionDataOnCtas() { // TODO: figure out why - throw new SkipException("We always get 3 partitions with FTE"); + abort("We always get 3 partitions with FTE"); } + @Test @Override public void testStatsBasedRepartitionDataOnInsert() { // TODO: figure out why - throw new SkipException("We always get 3 partitions with FTE"); + abort("We always get 3 partitions with FTE"); } @Override @@ -74,7 +81,7 @@ protected boolean isFileSorted(String path, String sortColumnName) return checkParquetFileSorting(path, sortColumnName); } - @AfterClass(alwaysRun = true) + @AfterAll public void destroy() throws Exception { diff --git a/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java b/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java index 32d01a7be321..02ae0aaeeac4 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java @@ -47,10 +47,12 @@ import io.trino.testing.sql.TestView; import io.trino.tpch.TpchTable; import org.intellij.lang.annotations.Language; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.Timeout; import org.junit.jupiter.api.parallel.Isolated; -import org.testng.SkipException; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; import java.time.Instant; import java.time.ZonedDateTime; @@ -174,6 +176,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.InstanceOfAssertFactories.ZONED_DATE_TIME; +import static org.junit.jupiter.api.Assumptions.abort; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNull; @@ -184,6 +188,7 @@ * Generic test for connectors. */ @Isolated +@TestInstance(PER_CLASS) public abstract class BaseConnectorTest extends AbstractTestQueries { @@ -196,7 +201,7 @@ public abstract class BaseConnectorTest private final ConcurrentMap>> mockTableListings = new ConcurrentHashMap<>(); - @BeforeClass + @BeforeAll public void initMockCatalog() { QueryRunner queryRunner = getQueryRunner(); @@ -1969,12 +1974,13 @@ public void testViewAndMaterializedViewTogether() * Test that reading table, column metadata, like {@code SHOW TABLES} or reading from {@code information_schema.views} * does not fail when relations are concurrently created or dropped. */ - @Test(timeOut = 180_000) + @Test + @Timeout(180) public void testReadMetadataWithRelationsConcurrentModifications() throws Exception { if (!hasBehavior(SUPPORTS_CREATE_TABLE) && !hasBehavior(SUPPORTS_CREATE_VIEW) && !hasBehavior(SUPPORTS_CREATE_MATERIALIZED_VIEW)) { - throw new SkipException("Cannot test"); + abort("Cannot test"); } int readIterations = 5; @@ -2394,7 +2400,7 @@ public void testRenameSchema() } if (!hasBehavior(SUPPORTS_CREATE_SCHEMA)) { - throw new SkipException("Skipping as connector does not support CREATE SCHEMA"); + abort("Skipping as connector does not support CREATE SCHEMA"); } String schemaName = "test_rename_schema_" + randomNameSuffix(); @@ -2942,7 +2948,8 @@ public void testSetColumnTypes() } catch (Exception e) { verifyUnsupportedTypeException(e, setup.sourceColumnType); - throw new SkipException("Unsupported column type: " + setup.sourceColumnType); + abort("Unsupported column type: " + setup.sourceColumnType); + return; } try (table) { Runnable setColumnType = () -> assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col SET DATA TYPE " + setup.newColumnType); @@ -3151,7 +3158,8 @@ public void testSetFieldTypes() } catch (Exception e) { verifyUnsupportedTypeException(e, setup.sourceColumnType); - throw new SkipException("Unsupported column type: " + setup.sourceColumnType); + abort("Unsupported column type: " + setup.sourceColumnType); + return; } try (table) { Runnable setFieldType = () -> assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col.field SET DATA TYPE " + setup.newColumnType); @@ -3966,7 +3974,7 @@ public void testRenameTableAcrossSchema() { if (!hasBehavior(SUPPORTS_RENAME_TABLE_ACROSS_SCHEMAS)) { if (!hasBehavior(SUPPORTS_RENAME_TABLE)) { - throw new SkipException("Skipping since rename table is not supported at all"); + abort("Skipping since rename table is not supported at all"); } assertQueryFails("ALTER TABLE nation RENAME TO other_schema.yyyy", "This connector does not support renaming tables across schemas"); return; @@ -4097,7 +4105,7 @@ public void testCommentView() } return; } - throw new SkipException("Skipping as connector does not support CREATE VIEW"); + abort("Skipping as connector does not support CREATE VIEW"); } String catalogName = getSession().getCatalog().orElseThrow(); @@ -4198,7 +4206,7 @@ public void testCommentViewColumn() } return; } - throw new SkipException("Skipping as connector does not support CREATE VIEW"); + abort("Skipping as connector does not support CREATE VIEW"); } String viewColumnName = "regionkey"; @@ -4359,7 +4367,7 @@ public void testInsertArray() assertThatThrownBy(() -> query("CREATE TABLE " + tableName + " (a array(bigint))")) // TODO Unify failure message across connectors .hasMessageMatching("[Uu]nsupported (column )?type: \\Qarray(bigint)"); - throw new SkipException("not supported"); + abort("not supported"); } try (TestTable table = new TestTable(getQueryRunner()::execute, "test_insert_array_", "(a ARRAY, b ARRAY)")) { @@ -4828,7 +4836,8 @@ public void testRowLevelUpdate() } // Repeat test with invocationCount for better test coverage, since the tested aspect is inherently non-deterministic. - @Test(timeOut = 60_000, invocationCount = 4) + @RepeatedTest(4) + @Timeout(60) public void testUpdateRowConcurrently() throws Exception { @@ -4894,7 +4903,8 @@ protected void verifyConcurrentUpdateFailurePermissible(Exception e) } // Repeat test with invocationCount for better test coverage, since the tested aspect is inherently non-deterministic. - @Test(timeOut = 60_000, invocationCount = 4) + @RepeatedTest(4) + @Timeout(60) public void testInsertRowConcurrently() throws Exception { @@ -4962,7 +4972,8 @@ protected void verifyConcurrentInsertFailurePermissible(Exception e) } // Repeat test with invocationCount for better test coverage, since the tested aspect is inherently non-deterministic. - @Test(timeOut = 60_000, invocationCount = 4) + @RepeatedTest(4) + @Timeout(60) public void testAddColumnConcurrently() throws Exception { @@ -5027,7 +5038,8 @@ protected void verifyConcurrentAddColumnFailurePermissible(Exception e) } // Repeat test with invocationCount for better test coverage, since the tested aspect is inherently non-deterministic. - @Test(timeOut = 60_000, invocationCount = 4) + @RepeatedTest(4) + @Timeout(60) public void testCreateOrReplaceTableConcurrently() throws Exception { @@ -6874,7 +6886,7 @@ public void testProjectionPushdownPhysicalInputSize() protected static void skipTestUnless(boolean requirement) { if (!requirement) { - throw new SkipException("requirement not met"); + abort("requirement not met"); } } diff --git a/testing/trino-tests/src/test/java/io/trino/tests/tpch/TestTpchConnectorTest.java b/testing/trino-tests/src/test/java/io/trino/tests/tpch/TestTpchConnectorTest.java index 0fac01156129..c843f28cb96f 100644 --- a/testing/trino-tests/src/test/java/io/trino/tests/tpch/TestTpchConnectorTest.java +++ b/testing/trino-tests/src/test/java/io/trino/tests/tpch/TestTpchConnectorTest.java @@ -28,7 +28,7 @@ import io.trino.testing.QueryRunner; import io.trino.testing.TestingConnectorBehavior; import io.trino.type.TypeDeserializer; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Optional; @@ -162,6 +162,7 @@ public void testShowTables() assertQueryFails("SHOW TABLES FROM sf0", "line 1:1: Schema 'sf0' does not exist"); } + @Test @Override public void testShowCreateTable() { @@ -179,6 +180,7 @@ public void testShowCreateTable() ")"); } + @Test @Override public void testPredicateReflectedInExplain() { From 0db1f997885d3f9759d7cc8e15415381983b8d31 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Mon, 13 Nov 2023 10:42:14 +0100 Subject: [PATCH 242/587] Prevent global var override in bash script --- core/docker/build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/docker/build.sh b/core/docker/build.sh index 51e93f66b835..8d1e1f236516 100755 --- a/core/docker/build.sh +++ b/core/docker/build.sh @@ -39,8 +39,8 @@ shift $((OPTIND - 1)) SOURCE_DIR="../.." function temurin_jdk_link() { - JDK_VERSION="${1}" - ARCH="${2}" + local JDK_VERSION="${1}" + local ARCH="${2}" versionsUrl="https://api.adoptium.net/v3/info/release_names?heap_size=normal&image_type=jdk<s=true&os=linux&page=0&page_size=20&project=jdk&release_type=ga&semver=false&sort_method=DEFAULT&sort_order=ASC&vendor=eclipse&version=%28${JDK_VERSION}%2C%5D" if ! result=$(curl -fLs "$versionsUrl" -H 'accept: application/json'); then From 93dce3cb688332accdb06d944a37b158ae8db5fe Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Mon, 13 Nov 2023 11:13:42 +0100 Subject: [PATCH 243/587] Be less specific about required Java version `.java-version` is also for local consumption for tools such as `jenv`. When `21.0.2` is out, it should be allowed to be picked up by such tools. --- .java-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.java-version b/.java-version index a8f5438c0a58..5f39e9144690 100644 --- a/.java-version +++ b/.java-version @@ -1 +1 @@ -21.0.1 +21.0 From cf0b229a445d47b8fd206ae36ceb1b0534186fe4 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Tue, 14 Nov 2023 10:17:16 -0800 Subject: [PATCH 244/587] Add missing Test annotations --- .../io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java index b1fe80f57813..ed92ab29ead3 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java @@ -267,6 +267,7 @@ protected Optional filterCaseSensitiveDataMappingTestData( return Optional.of(dataMappingTestSetup); } + @Test @Override public void testNoDataSystemTable() { @@ -287,6 +288,7 @@ protected boolean isColumnNameRejected(Exception exception, String columnName, b return nullToEmpty(exception.getMessage()).matches(".*Invalid field name \"%s\". Fields must contain the allowed characters, and be at most 300 characters long..*".formatted(columnName.replace("\\", "\\\\"))); } + @Test @Override // Override because the base test exceeds rate limits per a table public void testCommentColumn() { @@ -514,6 +516,7 @@ public void testViewDefinitionSystemTable() onBigQuery(format("DROP VIEW %s.%s", schemaName, viewName)); } + @Test @Override public void testShowCreateTable() { @@ -933,6 +936,7 @@ public void testNativeQueryIncorrectSyntax() .hasMessageContaining("Failed to get schema for query"); } + @Test @Override public void testInsertArray() { @@ -943,6 +947,7 @@ public void testInsertArray() } } + @Test @Override public void testInsertRowConcurrently() { @@ -975,6 +980,7 @@ protected TestTable createTableWithDefaultColumns() "col_required2 INT64 NOT NULL)"); } + @Test @Override public void testCharVarcharComparison() { From 848a2dd64832c4ec4d20a24fe435332000b0a663 Mon Sep 17 00:00:00 2001 From: Slawomir Pajak Date: Tue, 14 Nov 2023 10:52:48 +0100 Subject: [PATCH 245/587] Remove storing TableSnapshot during metadata queries --- .../java/io/trino/plugin/deltalake/DeltaLakeMetadata.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java index fc337cdbeb1c..a0b96a2de5c1 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java @@ -760,7 +760,7 @@ public Iterator streamTableColumns(ConnectorSession sessio return Stream.of(); } String tableLocation = metastoreTable.get().location(); - TableSnapshot snapshot = getSnapshot(session, table, tableLocation, Optional.empty()); + TableSnapshot snapshot = transactionLogAccess.loadSnapshot(session, table, tableLocation); MetadataEntry metadata = transactionLogAccess.getMetadataEntry(snapshot, session); ProtocolEntry protocol = transactionLogAccess.getProtocolEntry(session, snapshot); Map columnComments = getColumnComments(metadata); @@ -771,7 +771,7 @@ public Iterator streamTableColumns(ConnectorSession sessio .collect(toImmutableList()); return Stream.of(TableColumnsMetadata.forTable(table, columnMetadata)); } - catch (NotADeltaLakeTableException e) { + catch (NotADeltaLakeTableException | IOException e) { return Stream.empty(); } catch (RuntimeException e) { From 7b83908cc325b504aef901cc6cec114f3fa760d4 Mon Sep 17 00:00:00 2001 From: James Petty Date: Mon, 13 Nov 2023 13:28:50 -0500 Subject: [PATCH 246/587] Short circuit on zero-length hash length --- .../io/trino/operator/FlatHashStrategyCompiler.java | 11 +++++++++-- .../java/io/trino/operator/TestFlatHashStrategy.java | 12 ++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/operator/FlatHashStrategyCompiler.java b/core/trino-main/src/main/java/io/trino/operator/FlatHashStrategyCompiler.java index 1919c222ba91..97541befb65a 100644 --- a/core/trino-main/src/main/java/io/trino/operator/FlatHashStrategyCompiler.java +++ b/core/trino-main/src/main/java/io/trino/operator/FlatHashStrategyCompiler.java @@ -51,6 +51,7 @@ import static io.airlift.bytecode.expression.BytecodeExpressions.constantInt; import static io.airlift.bytecode.expression.BytecodeExpressions.constantLong; import static io.airlift.bytecode.expression.BytecodeExpressions.constantTrue; +import static io.airlift.bytecode.expression.BytecodeExpressions.equal; import static io.airlift.bytecode.expression.BytecodeExpressions.invokeDynamic; import static io.airlift.bytecode.expression.BytecodeExpressions.invokeStatic; import static io.airlift.bytecode.expression.BytecodeExpressions.lessThan; @@ -378,6 +379,8 @@ private static void generateHashBlocksBatched(ClassDefinition definition, List typeMethods = new HashMap<>(); for (KeyField keyField : keyFields) { MethodDefinition method; @@ -393,9 +396,13 @@ private static void generateHashBlocksBatched(ClassDefinition definition, List types = createTestingTypes(); + FlatHashStrategy flatHashStrategy = JOIN_COMPILER.getFlatHashStrategy(types); + + int positionCount = 10; + // Attempting to touch any of the blocks would result in a NullPointerException + assertDoesNotThrow(() -> flatHashStrategy.hashBlocksBatched(new Block[types.size()], new long[positionCount], 0, 0)); + } + @Test public void testBatchedRawHashesMatchSinglePositionHashes() { From 0c9f9d819ec0f4deb4c9521087adad56bc693e19 Mon Sep 17 00:00:00 2001 From: James Petty Date: Mon, 13 Nov 2023 13:58:47 -0500 Subject: [PATCH 247/587] Special case handling of RLE Blocks for batched hashing --- .../operator/FlatHashStrategyCompiler.java | 68 +++++++++++++------ .../trino/operator/TestFlatHashStrategy.java | 24 +++++-- 2 files changed, 68 insertions(+), 24 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/operator/FlatHashStrategyCompiler.java b/core/trino-main/src/main/java/io/trino/operator/FlatHashStrategyCompiler.java index 97541befb65a..499ef7125004 100644 --- a/core/trino-main/src/main/java/io/trino/operator/FlatHashStrategyCompiler.java +++ b/core/trino-main/src/main/java/io/trino/operator/FlatHashStrategyCompiler.java @@ -23,15 +23,18 @@ import io.airlift.bytecode.Variable; import io.airlift.bytecode.control.ForLoop; import io.airlift.bytecode.control.IfStatement; +import io.airlift.bytecode.expression.BytecodeExpression; import io.trino.operator.scalar.CombineHashFunction; import io.trino.spi.block.Block; import io.trino.spi.block.BlockBuilder; +import io.trino.spi.block.RunLengthEncodedBlock; import io.trino.spi.type.Type; import io.trino.spi.type.TypeOperators; import io.trino.sql.gen.CallSiteBinder; import java.lang.invoke.MethodHandle; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -429,35 +432,62 @@ private static MethodDefinition generateHashBlockVectorized(ClassDefinition defi Variable mayHaveNull = scope.declareVariable(boolean.class, "mayHaveNull"); Variable hash = scope.declareVariable(long.class, "hash"); - body.append(mayHaveNull.set(block.invoke("mayHaveNull", boolean.class))); body.append(position.set(invokeStatic(Objects.class, "checkFromToIndex", int.class, offset, add(offset, length), block.invoke("getPositionCount", int.class)))); body.append(invokeStatic(Objects.class, "checkFromIndexSize", int.class, constantInt(0), length, hashes.length()).pop()); - BytecodeBlock loopBody = new BytecodeBlock().append(new IfStatement("if (mayHaveNull && block.isNull(position))") - .condition(and(mayHaveNull, block.invoke("isNull", boolean.class, position))) - .ifTrue(hash.set(constantLong(NULL_HASH_CODE))) - .ifFalse(hash.set(invokeDynamic( - BOOTSTRAP_METHOD, - ImmutableList.of(callSiteBinder.bind(field.hashBlockMethod()).getBindingId()), - "hash", - long.class, - block, - position)))); + BytecodeExpression computeHashNonNull = invokeDynamic( + BOOTSTRAP_METHOD, + ImmutableList.of(callSiteBinder.bind(field.hashBlockMethod()).getBindingId()), + "hash", + long.class, + block, + position); + + BytecodeExpression setHashExpression; if (field.index() == 0) { // hashes[index] = hash; - loopBody.append(hashes.setElement(index, hash)); + setHashExpression = hashes.setElement(index, hash); } else { // hashes[index] = CombineHashFunction.getHash(hashes[index], hash); - loopBody.append(hashes.setElement(index, invokeStatic(CombineHashFunction.class, "getHash", long.class, hashes.getElement(index), hash))); + setHashExpression = hashes.setElement(index, invokeStatic(CombineHashFunction.class, "getHash", long.class, hashes.getElement(index), hash)); + } + + BytecodeBlock rleHandling = new BytecodeBlock() + .append(new IfStatement("hash = block.isNull(position) ? NULL_HASH_CODE : hash(block, position)") + .condition(block.invoke("isNull", boolean.class, position)) + .ifTrue(hash.set(constantLong(NULL_HASH_CODE))) + .ifFalse(hash.set(computeHashNonNull))); + if (field.index() == 0) { + // Arrays.fill(hashes, 0, length, hash) + rleHandling.append(invokeStatic(Arrays.class, "fill", void.class, hashes, constantInt(0), length, hash)); + } + else { + rleHandling.append(new ForLoop("for (int index = 0; index < length; index++) { hashes[index] = CombineHashFunction.getHash(hashes[index], hash); }") + .initialize(index.set(constantInt(0))) + .condition(lessThan(index, length)) + .update(index.increment()) + .body(setHashExpression)); } - loopBody.append(position.increment()); - body.append(new ForLoop("for (index = 0; index < length; index++)") - .initialize(index.set(constantInt(0))) - .condition(lessThan(index, length)) - .update(index.increment()) - .body(loopBody)) + BytecodeBlock computeHashLoop = new BytecodeBlock() + .append(mayHaveNull.set(block.invoke("mayHaveNull", boolean.class))) + .append(new ForLoop("for (int index = 0; index < length; index++)") + .initialize(index.set(constantInt(0))) + .condition(lessThan(index, length)) + .update(index.increment()) + .body(new BytecodeBlock() + .append(new IfStatement("if (mayHaveNull && block.isNull(position))") + .condition(and(mayHaveNull, block.invoke("isNull", boolean.class, position))) + .ifTrue(hash.set(constantLong(NULL_HASH_CODE))) + .ifFalse(hash.set(computeHashNonNull))) + .append(setHashExpression) + .append(position.increment()))); + + body.append(new IfStatement("if (block instanceof RunLengthEncodedBlock)") + .condition(block.instanceOf(RunLengthEncodedBlock.class)) + .ifTrue(rleHandling) + .ifFalse(computeHashLoop)) .ret(); return methodDefinition; diff --git a/core/trino-main/src/test/java/io/trino/operator/TestFlatHashStrategy.java b/core/trino-main/src/test/java/io/trino/operator/TestFlatHashStrategy.java index d9c7716d2f66..1eafd9a2f094 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestFlatHashStrategy.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestFlatHashStrategy.java @@ -15,6 +15,7 @@ import com.google.common.collect.ImmutableList; import io.trino.spi.block.Block; +import io.trino.spi.block.RunLengthEncodedBlock; import io.trino.spi.connector.ConnectorSession; import io.trino.spi.type.ArrayType; import io.trino.spi.type.MapType; @@ -78,16 +79,29 @@ public void testBatchedRawHashesMatchSinglePositionHashes() long[] hashes = new long[positionCount]; flatHashStrategy.hashBlocksBatched(blocks, hashes, 0, positionCount); - for (int position = 0; position < hashes.length; position++) { - long singleRowHash = flatHashStrategy.hash(blocks, position); - if (hashes[position] != singleRowHash) { - fail("Hash mismatch: %s <> %s at position %s - Values: %s".formatted(hashes[position], singleRowHash, position, singleRowTypesAndValues(types, blocks, position))); - } + assertHashesEqual(types, blocks, hashes, flatHashStrategy); + + // Convert all blocks to RunLengthEncoded and re-check results match + for (int i = 0; i < blocks.length; i++) { + blocks[i] = RunLengthEncodedBlock.create(blocks[i].getSingleValueBlock(0), positionCount); } + flatHashStrategy.hashBlocksBatched(blocks, hashes, 0, positionCount); + assertHashesEqual(types, blocks, hashes, flatHashStrategy); + // Ensure the formatting logic produces a real string and doesn't blow up since otherwise this code wouldn't be exercised assertNotNull(singleRowTypesAndValues(types, blocks, 0)); } + private static void assertHashesEqual(List types, Block[] blocks, long[] batchedHashes, FlatHashStrategy flatHashStrategy) + { + for (int position = 0; position < batchedHashes.length; position++) { + long singleRowHash = flatHashStrategy.hash(blocks, position); + if (batchedHashes[position] != singleRowHash) { + fail("Hash mismatch: %s <> %s at position %s - Values: %s".formatted(batchedHashes[position], singleRowHash, position, singleRowTypesAndValues(types, blocks, position))); + } + } + } + private static List createTestingTypes() { List baseTypes = List.of( From a24475e3011c22cc107db0878d6b308cededb823 Mon Sep 17 00:00:00 2001 From: James Petty Date: Mon, 13 Nov 2023 14:07:18 -0500 Subject: [PATCH 248/587] Avoid retaining generated classes in TestFlatHashStrategy --- .../io/trino/operator/TestFlatHashStrategy.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/core/trino-main/src/test/java/io/trino/operator/TestFlatHashStrategy.java b/core/trino-main/src/test/java/io/trino/operator/TestFlatHashStrategy.java index 1eafd9a2f094..1948f04ea03f 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestFlatHashStrategy.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestFlatHashStrategy.java @@ -51,14 +51,14 @@ public class TestFlatHashStrategy { - private static final TypeOperators TYPE_OPERATORS = new TypeOperators(); - private static final JoinCompiler JOIN_COMPILER = new JoinCompiler(TYPE_OPERATORS); + private final TypeOperators typeOperators = new TypeOperators(); + private final JoinCompiler joinCompiler = new JoinCompiler(typeOperators); @Test public void testBatchedRawHashesZeroLength() { - List types = createTestingTypes(); - FlatHashStrategy flatHashStrategy = JOIN_COMPILER.getFlatHashStrategy(types); + List types = createTestingTypes(typeOperators); + FlatHashStrategy flatHashStrategy = joinCompiler.getFlatHashStrategy(types); int positionCount = 10; // Attempting to touch any of the blocks would result in a NullPointerException @@ -68,8 +68,8 @@ public void testBatchedRawHashesZeroLength() @Test public void testBatchedRawHashesMatchSinglePositionHashes() { - List types = createTestingTypes(); - FlatHashStrategy flatHashStrategy = JOIN_COMPILER.getFlatHashStrategy(types); + List types = createTestingTypes(typeOperators); + FlatHashStrategy flatHashStrategy = joinCompiler.getFlatHashStrategy(types); int positionCount = 1024; Block[] blocks = new Block[types.size()]; @@ -102,7 +102,7 @@ private static void assertHashesEqual(List types, Block[] blocks, long[] b } } - private static List createTestingTypes() + private static List createTestingTypes(TypeOperators typeOperators) { List baseTypes = List.of( BIGINT, @@ -128,7 +128,7 @@ private static List createTestingTypes() builder.add(RowType.anonymous(baseTypes)); for (Type baseType : baseTypes) { builder.add(new ArrayType(baseType)); - builder.add(new MapType(baseType, baseType, TYPE_OPERATORS)); + builder.add(new MapType(baseType, baseType, typeOperators)); } return builder.build(); } From 606a95ad6b67af1818b9bcf9f894bafa8da91cc0 Mon Sep 17 00:00:00 2001 From: chenjian2664 Date: Wed, 15 Nov 2023 10:57:47 +0800 Subject: [PATCH 249/587] Remove unnecessary override method in PhoenixMetadata --- .../java/io/trino/plugin/phoenix5/PhoenixMetadata.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/plugin/trino-phoenix5/src/main/java/io/trino/plugin/phoenix5/PhoenixMetadata.java b/plugin/trino-phoenix5/src/main/java/io/trino/plugin/phoenix5/PhoenixMetadata.java index ad1a46be5970..703151ad6975 100644 --- a/plugin/trino-phoenix5/src/main/java/io/trino/plugin/phoenix5/PhoenixMetadata.java +++ b/plugin/trino-phoenix5/src/main/java/io/trino/plugin/phoenix5/PhoenixMetadata.java @@ -41,7 +41,6 @@ import io.trino.spi.connector.ConnectorTableSchema; import io.trino.spi.connector.LocalProperty; import io.trino.spi.connector.RetryMode; -import io.trino.spi.connector.RowChangeParadigm; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.connector.SortingProperty; import io.trino.spi.expression.Constant; @@ -68,7 +67,6 @@ import static io.trino.plugin.phoenix5.PhoenixErrorCode.PHOENIX_METADATA_ERROR; import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; import static io.trino.spi.connector.RetryMode.NO_RETRIES; -import static io.trino.spi.connector.RowChangeParadigm.CHANGE_ONLY_UPDATED_COLUMNS; import static java.lang.String.format; import static java.util.Objects.requireNonNull; import static org.apache.phoenix.util.SchemaUtil.getEscapedArgument; @@ -287,12 +285,6 @@ public void dropTable(ConnectorSession session, ConnectorTableHandle tableHandle phoenixClient.dropTable(session, (JdbcTableHandle) tableHandle); } - @Override - public RowChangeParadigm getRowChangeParadigm(ConnectorSession session, ConnectorTableHandle tableHandle) - { - return CHANGE_ONLY_UPDATED_COLUMNS; - } - @Override public JdbcColumnHandle getMergeRowIdColumnHandle(ConnectorSession session, ConnectorTableHandle tableHandle) { From f4927c5d5d92cc507407125884d631c0a67a9644 Mon Sep 17 00:00:00 2001 From: Marius Grama Date: Tue, 14 Nov 2023 15:00:00 +0100 Subject: [PATCH 250/587] Skip archiving in Glue an old table version during a table update operation Skip archiving old table version when committing a table in Iceberg in order to avoid potentially reaching Glue table version quotas. --- docs/src/main/sphinx/connector/metastores.md | 2 +- .../iceberg/catalog/glue/IcebergGlueCatalogConfig.java | 2 +- .../iceberg/catalog/glue/TestIcebergGlueCatalogConfig.java | 6 +++--- .../catalog/glue/TestIcebergGlueCatalogSkipArchive.java | 1 - 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/src/main/sphinx/connector/metastores.md b/docs/src/main/sphinx/connector/metastores.md index 739cb5c8fafa..950e2248055c 100644 --- a/docs/src/main/sphinx/connector/metastores.md +++ b/docs/src/main/sphinx/connector/metastores.md @@ -295,7 +295,7 @@ described with the following additional property: - Skip archiving an old table version when creating a new version in a commit. See [AWS Glue Skip Archive](https://iceberg.apache.org/docs/latest/aws/#skip-archive). - - `false` + - `true` ::: ## Iceberg-specific metastores diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/IcebergGlueCatalogConfig.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/IcebergGlueCatalogConfig.java index 1f8ba21a56c3..156f48acebc0 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/IcebergGlueCatalogConfig.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/IcebergGlueCatalogConfig.java @@ -19,7 +19,7 @@ public class IcebergGlueCatalogConfig { private boolean cacheTableMetadata = true; - private boolean skipArchive; + private boolean skipArchive = true; public boolean isCacheTableMetadata() { diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogConfig.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogConfig.java index 3c21ffb69592..c6b329340154 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogConfig.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogConfig.java @@ -29,7 +29,7 @@ public void testDefaults() { assertRecordedDefaults(recordDefaults(IcebergGlueCatalogConfig.class) .setCacheTableMetadata(true) - .setSkipArchive(false)); + .setSkipArchive(true)); } @Test @@ -37,12 +37,12 @@ public void testExplicitPropertyMapping() { Map properties = ImmutableMap.builder() .put("iceberg.glue.cache-table-metadata", "false") - .put("iceberg.glue.skip-archive", "true") + .put("iceberg.glue.skip-archive", "false") .buildOrThrow(); IcebergGlueCatalogConfig expected = new IcebergGlueCatalogConfig() .setCacheTableMetadata(false) - .setSkipArchive(true); + .setSkipArchive(false); assertFullMapping(properties, expected); } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogSkipArchive.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogSkipArchive.java index 36638fbb28ce..629e97e1a847 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogSkipArchive.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogSkipArchive.java @@ -82,7 +82,6 @@ protected QueryRunner createQueryRunner() .setIcebergProperties( ImmutableMap.builder() .put("iceberg.catalog.type", "glue") - .put("iceberg.glue.skip-archive", "true") .put("hive.metastore.glue.default-warehouse-dir", schemaDirectory.getAbsolutePath()) .buildOrThrow()) .setSchemaInitializer( From e2e1e218f0c16324a715e2e92dfcb0fcfcfbffa8 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Tue, 14 Nov 2023 12:06:44 +0100 Subject: [PATCH 251/587] Fix typo --- .../trino/plugin/hive/metastore/glue/GlueMetastoreModule.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueMetastoreModule.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueMetastoreModule.java index 77fcf7043200..75b0e59a1687 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueMetastoreModule.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueMetastoreModule.java @@ -85,11 +85,11 @@ protected void setup(Binder binder) getGlueStatisticsModule(DisabledGlueColumnStatisticsProviderFactory.class))); } - private Module getGlueStatisticsModule(Class statisticsPrividerFactoryClass) + private Module getGlueStatisticsModule(Class statisticsProviderFactoryClass) { return internalBinder -> newOptionalBinder(internalBinder, GlueColumnStatisticsProviderFactory.class) .setDefault() - .to(statisticsPrividerFactoryClass) + .to(statisticsProviderFactoryClass) .in(Scopes.SINGLETON); } From c5ae5df17b79fdaa0895247ab8366e77d5f17193 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Tue, 14 Nov 2023 12:31:15 +0100 Subject: [PATCH 252/587] Simplify MetastoreHiveStatisticsProvider code flow Before the change `MetastoreHiveStatisticsProvider` was both abstract and concrete class in one: one constructor allowed to plug implementation of the most important method, while the other provided the implementation in a form of a lambda. This made harder to follow the code flow (e.g. finding call sites). The commit splits the class into abstract class (shared logic) and a concrete implementation class. --- .../AbstractHiveStatisticsProvider.java | 891 ++++++++++++++++++ .../MetastoreHiveStatisticsProvider.java | 880 +---------------- .../TestMetastoreHiveStatisticsProvider.java | 103 +- 3 files changed, 971 insertions(+), 903 deletions(-) create mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/statistics/AbstractHiveStatisticsProvider.java diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/statistics/AbstractHiveStatisticsProvider.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/statistics/AbstractHiveStatisticsProvider.java new file mode 100644 index 000000000000..bfaa9129f620 --- /dev/null +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/statistics/AbstractHiveStatisticsProvider.java @@ -0,0 +1,891 @@ +/* + * 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 io.trino.plugin.hive.statistics; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.VerifyException; +import com.google.common.hash.HashFunction; +import com.google.common.primitives.Ints; +import com.google.common.primitives.Shorts; +import com.google.common.primitives.SignedBytes; +import io.airlift.log.Logger; +import io.airlift.slice.Slice; +import io.trino.plugin.hive.HiveBasicStatistics; +import io.trino.plugin.hive.HiveColumnHandle; +import io.trino.plugin.hive.HivePartition; +import io.trino.plugin.hive.PartitionStatistics; +import io.trino.plugin.hive.metastore.DateStatistics; +import io.trino.plugin.hive.metastore.DecimalStatistics; +import io.trino.plugin.hive.metastore.DoubleStatistics; +import io.trino.plugin.hive.metastore.HiveColumnStatistics; +import io.trino.plugin.hive.metastore.IntegerStatistics; +import io.trino.spi.TrinoException; +import io.trino.spi.connector.ColumnHandle; +import io.trino.spi.connector.ConnectorSession; +import io.trino.spi.connector.SchemaTableName; +import io.trino.spi.predicate.NullableValue; +import io.trino.spi.statistics.ColumnStatistics; +import io.trino.spi.statistics.DoubleRange; +import io.trino.spi.statistics.Estimate; +import io.trino.spi.statistics.TableStatistics; +import io.trino.spi.type.CharType; +import io.trino.spi.type.DecimalType; +import io.trino.spi.type.Type; +import io.trino.spi.type.VarcharType; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalLong; +import java.util.Set; +import java.util.stream.DoubleStream; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Verify.verify; +import static com.google.common.base.Verify.verifyNotNull; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.Maps.immutableEntry; +import static com.google.common.hash.Hashing.murmur3_128; +import static io.trino.plugin.hive.HiveErrorCode.HIVE_CORRUPTED_COLUMN_STATISTICS; +import static io.trino.plugin.hive.HiveSessionProperties.getPartitionStatisticsSampleSize; +import static io.trino.plugin.hive.HiveSessionProperties.isIgnoreCorruptedStatistics; +import static io.trino.plugin.hive.HiveSessionProperties.isStatisticsEnabled; +import static io.trino.spi.statistics.StatsUtil.toStatsRepresentation; +import static io.trino.spi.type.BigintType.BIGINT; +import static io.trino.spi.type.DateType.DATE; +import static io.trino.spi.type.DoubleType.DOUBLE; +import static io.trino.spi.type.IntegerType.INTEGER; +import static io.trino.spi.type.RealType.REAL; +import static io.trino.spi.type.SmallintType.SMALLINT; +import static io.trino.spi.type.TinyintType.TINYINT; +import static java.lang.Double.isFinite; +import static java.lang.Double.isNaN; +import static java.lang.String.format; +import static java.util.Collections.unmodifiableList; + +public abstract class AbstractHiveStatisticsProvider + implements HiveStatisticsProvider +{ + private static final Logger log = Logger.get(AbstractHiveStatisticsProvider.class); + + @Override + public TableStatistics getTableStatistics( + ConnectorSession session, + SchemaTableName table, + Map columns, + Map columnTypes, + List partitions) + { + if (!isStatisticsEnabled(session)) { + return TableStatistics.empty(); + } + if (partitions.isEmpty()) { + return createZeroStatistics(columns, columnTypes); + } + int sampleSize = getPartitionStatisticsSampleSize(session); + List partitionsSample = getPartitionsSample(partitions, sampleSize); + try { + Map statisticsSample = getPartitionsStatistics(session, table, partitionsSample, columns.keySet()); + validatePartitionStatistics(table, statisticsSample); + return getTableStatistics(columns, columnTypes, partitions, statisticsSample); + } + catch (TrinoException e) { + if (e.getErrorCode().equals(HIVE_CORRUPTED_COLUMN_STATISTICS.toErrorCode()) && isIgnoreCorruptedStatistics(session)) { + log.error(e); + return TableStatistics.empty(); + } + throw e; + } + } + + protected abstract Map getPartitionsStatistics(ConnectorSession session, SchemaTableName table, List hivePartitions, Set columns); + + private TableStatistics createZeroStatistics(Map columns, Map columnTypes) + { + TableStatistics.Builder result = TableStatistics.builder(); + result.setRowCount(Estimate.of(0)); + columns.forEach((columnName, columnHandle) -> { + Type columnType = columnTypes.get(columnName); + verifyNotNull(columnType, "columnType is missing for column: %s", columnName); + ColumnStatistics.Builder columnStatistics = ColumnStatistics.builder(); + columnStatistics.setNullsFraction(Estimate.of(0)); + columnStatistics.setDistinctValuesCount(Estimate.of(0)); + if (hasDataSize(columnType)) { + columnStatistics.setDataSize(Estimate.of(0)); + } + result.setColumnStatistics(columnHandle, columnStatistics.build()); + }); + return result.build(); + } + + @VisibleForTesting + static List getPartitionsSample(List partitions, int sampleSize) + { + checkArgument(sampleSize > 0, "sampleSize is expected to be greater than zero"); + + if (partitions.size() <= sampleSize) { + return partitions; + } + + List result = new ArrayList<>(); + + int samplesLeft = sampleSize; + + HivePartition min = partitions.get(0); + HivePartition max = partitions.get(0); + for (HivePartition partition : partitions) { + if (partition.getPartitionId().compareTo(min.getPartitionId()) < 0) { + min = partition; + } + else if (partition.getPartitionId().compareTo(max.getPartitionId()) > 0) { + max = partition; + } + } + + result.add(min); + samplesLeft--; + if (samplesLeft > 0) { + result.add(max); + samplesLeft--; + } + + if (samplesLeft > 0) { + HashFunction hashFunction = murmur3_128(); + Comparator> hashComparator = Comparator + ., Long>comparing(Map.Entry::getValue) + .thenComparing(entry -> entry.getKey().getPartitionId()); + partitions.stream() + .filter(partition -> !result.contains(partition)) + .map(partition -> immutableEntry(partition, hashFunction.hashUnencodedChars(partition.getPartitionId()).asLong())) + .sorted(hashComparator) + .limit(samplesLeft) + .forEachOrdered(entry -> result.add(entry.getKey())); + } + + return unmodifiableList(result); + } + + @VisibleForTesting + static void validatePartitionStatistics(SchemaTableName table, Map partitionStatistics) + { + partitionStatistics.forEach((partition, statistics) -> { + HiveBasicStatistics basicStatistics = statistics.getBasicStatistics(); + OptionalLong rowCount = basicStatistics.getRowCount(); + rowCount.ifPresent(count -> checkStatistics(count >= 0, table, partition, "rowCount must be greater than or equal to zero: %s", count)); + basicStatistics.getFileCount().ifPresent(count -> checkStatistics(count >= 0, table, partition, "fileCount must be greater than or equal to zero: %s", count)); + basicStatistics.getInMemoryDataSizeInBytes().ifPresent(size -> checkStatistics(size >= 0, table, partition, "inMemoryDataSizeInBytes must be greater than or equal to zero: %s", size)); + basicStatistics.getOnDiskDataSizeInBytes().ifPresent(size -> checkStatistics(size >= 0, table, partition, "onDiskDataSizeInBytes must be greater than or equal to zero: %s", size)); + statistics.getColumnStatistics().forEach((column, columnStatistics) -> validateColumnStatistics(table, partition, column, rowCount, columnStatistics)); + }); + } + + private static void validateColumnStatistics(SchemaTableName table, String partition, String column, OptionalLong rowCount, HiveColumnStatistics columnStatistics) + { + columnStatistics.getMaxValueSizeInBytes().ifPresent(maxValueSizeInBytes -> + checkStatistics(maxValueSizeInBytes >= 0, table, partition, column, "maxValueSizeInBytes must be greater than or equal to zero: %s", maxValueSizeInBytes)); + columnStatistics.getTotalSizeInBytes().ifPresent(totalSizeInBytes -> + checkStatistics(totalSizeInBytes >= 0, table, partition, column, "totalSizeInBytes must be greater than or equal to zero: %s", totalSizeInBytes)); + columnStatistics.getNullsCount().ifPresent(nullsCount -> { + checkStatistics(nullsCount >= 0, table, partition, column, "nullsCount must be greater than or equal to zero: %s", nullsCount); + if (rowCount.isPresent()) { + checkStatistics( + nullsCount <= rowCount.getAsLong(), + table, + partition, + column, + "nullsCount must be less than or equal to rowCount. nullsCount: %s. rowCount: %s.", + nullsCount, + rowCount.getAsLong()); + } + }); + columnStatistics.getDistinctValuesCount().ifPresent(distinctValuesCount -> { + checkStatistics(distinctValuesCount >= 0, table, partition, column, "distinctValuesCount must be greater than or equal to zero: %s", distinctValuesCount); + if (rowCount.isPresent()) { + checkStatistics( + distinctValuesCount <= rowCount.getAsLong(), + table, + partition, + column, + "distinctValuesCount must be less than or equal to rowCount. distinctValuesCount: %s. rowCount: %s.", + distinctValuesCount, + rowCount.getAsLong()); + } + if (rowCount.isPresent() && columnStatistics.getNullsCount().isPresent()) { + long nonNullsCount = rowCount.getAsLong() - columnStatistics.getNullsCount().getAsLong(); + checkStatistics( + distinctValuesCount <= nonNullsCount, + table, + partition, + column, + "distinctValuesCount must be less than or equal to nonNullsCount. distinctValuesCount: %s. nonNullsCount: %s.", + distinctValuesCount, + nonNullsCount); + } + }); + + columnStatistics.getIntegerStatistics().ifPresent(integerStatistics -> { + OptionalLong min = integerStatistics.getMin(); + OptionalLong max = integerStatistics.getMax(); + if (min.isPresent() && max.isPresent()) { + checkStatistics( + min.getAsLong() <= max.getAsLong(), + table, + partition, + column, + "integerStatistics.min must be less than or equal to integerStatistics.max. integerStatistics.min: %s. integerStatistics.max: %s.", + min.getAsLong(), + max.getAsLong()); + } + }); + columnStatistics.getDoubleStatistics().ifPresent(doubleStatistics -> { + OptionalDouble min = doubleStatistics.getMin(); + OptionalDouble max = doubleStatistics.getMax(); + if (min.isPresent() && max.isPresent() && !isNaN(min.getAsDouble()) && !isNaN(max.getAsDouble())) { + checkStatistics( + min.getAsDouble() <= max.getAsDouble(), + table, + partition, + column, + "doubleStatistics.min must be less than or equal to doubleStatistics.max. doubleStatistics.min: %s. doubleStatistics.max: %s.", + min.getAsDouble(), + max.getAsDouble()); + } + }); + columnStatistics.getDecimalStatistics().ifPresent(decimalStatistics -> { + Optional min = decimalStatistics.getMin(); + Optional max = decimalStatistics.getMax(); + if (min.isPresent() && max.isPresent()) { + checkStatistics( + min.get().compareTo(max.get()) <= 0, + table, + partition, + column, + "decimalStatistics.min must be less than or equal to decimalStatistics.max. decimalStatistics.min: %s. decimalStatistics.max: %s.", + min.get(), + max.get()); + } + }); + columnStatistics.getDateStatistics().ifPresent(dateStatistics -> { + Optional min = dateStatistics.getMin(); + Optional max = dateStatistics.getMax(); + if (min.isPresent() && max.isPresent()) { + checkStatistics( + min.get().compareTo(max.get()) <= 0, + table, + partition, + column, + "dateStatistics.min must be less than or equal to dateStatistics.max. dateStatistics.min: %s. dateStatistics.max: %s.", + min.get(), + max.get()); + } + }); + columnStatistics.getBooleanStatistics().ifPresent(booleanStatistics -> { + OptionalLong falseCount = booleanStatistics.getFalseCount(); + OptionalLong trueCount = booleanStatistics.getTrueCount(); + falseCount.ifPresent(count -> + checkStatistics(count >= 0, table, partition, column, "falseCount must be greater than or equal to zero: %s", count)); + trueCount.ifPresent(count -> + checkStatistics(count >= 0, table, partition, column, "trueCount must be greater than or equal to zero: %s", count)); + if (rowCount.isPresent() && falseCount.isPresent()) { + checkStatistics( + falseCount.getAsLong() <= rowCount.getAsLong(), + table, + partition, + column, + "booleanStatistics.falseCount must be less than or equal to rowCount. booleanStatistics.falseCount: %s. rowCount: %s.", + falseCount.getAsLong(), + rowCount.getAsLong()); + } + if (rowCount.isPresent() && trueCount.isPresent()) { + checkStatistics( + trueCount.getAsLong() <= rowCount.getAsLong(), + table, + partition, + column, + "booleanStatistics.trueCount must be less than or equal to rowCount. booleanStatistics.trueCount: %s. rowCount: %s.", + trueCount.getAsLong(), + rowCount.getAsLong()); + } + }); + } + + private static void checkStatistics(boolean expression, SchemaTableName table, String partition, String column, String message, Object... args) + { + if (!expression) { + throw new TrinoException( + HIVE_CORRUPTED_COLUMN_STATISTICS, + format("Corrupted partition statistics (Table: %s Partition: [%s] Column: %s): %s", table, partition, column, format(message, args))); + } + } + + private static void checkStatistics(boolean expression, SchemaTableName table, String partition, String message, Object... args) + { + if (!expression) { + throw new TrinoException( + HIVE_CORRUPTED_COLUMN_STATISTICS, + format("Corrupted partition statistics (Table: %s Partition: [%s]): %s", table, partition, format(message, args))); + } + } + + private static TableStatistics getTableStatistics( + Map columns, + Map columnTypes, + List partitions, + Map statistics) + { + if (statistics.isEmpty()) { + return createEmptyTableStatisticsWithPartitionColumnStatistics(columns, columnTypes, partitions); + } + + checkArgument(!partitions.isEmpty(), "partitions is empty"); + + Optional optionalRowCount = calculatePartitionsRowCount(statistics.values(), partitions.size()); + if (optionalRowCount.isEmpty()) { + return createEmptyTableStatisticsWithPartitionColumnStatistics(columns, columnTypes, partitions); + } + double rowCount = optionalRowCount.get().getRowCount(); + + TableStatistics.Builder result = TableStatistics.builder(); + result.setRowCount(Estimate.of(rowCount)); + for (Map.Entry column : columns.entrySet()) { + String columnName = column.getKey(); + HiveColumnHandle columnHandle = (HiveColumnHandle) column.getValue(); + Type columnType = columnTypes.get(columnName); + ColumnStatistics columnStatistics; + if (columnHandle.isPartitionKey()) { + double averageRowsPerPartition = optionalRowCount.get().getAverageRowsPerPartition(); + columnStatistics = createPartitionColumnStatistics(columnHandle, columnType, partitions, statistics, averageRowsPerPartition, rowCount); + } + else { + columnStatistics = createDataColumnStatistics(columnName, columnType, rowCount, statistics.values()); + } + result.setColumnStatistics(columnHandle, columnStatistics); + } + return result.build(); + } + + private static TableStatistics createEmptyTableStatisticsWithPartitionColumnStatistics( + Map columns, + Map columnTypes, + List partitions) + { + TableStatistics.Builder result = TableStatistics.builder(); + // Estimate stats for partitioned columns even when row count is unavailable. This will help us use + // ndv stats in rules like "ApplyPreferredTableWriterPartitioning". + for (Map.Entry column : columns.entrySet()) { + HiveColumnHandle columnHandle = (HiveColumnHandle) column.getValue(); + if (columnHandle.isPartitionKey()) { + result.setColumnStatistics( + columnHandle, + createPartitionColumnStatisticsWithoutRowCount(columnHandle, columnTypes.get(column.getKey()), partitions)); + } + } + return result.build(); + } + + @VisibleForTesting + static Optional calculatePartitionsRowCount(Collection statistics, int queriedPartitionsCount) + { + long[] rowCounts = statistics.stream() + .map(PartitionStatistics::getBasicStatistics) + .map(HiveBasicStatistics::getRowCount) + .filter(OptionalLong::isPresent) + .mapToLong(OptionalLong::getAsLong) + .peek(count -> verify(count >= 0, "count must be greater than or equal to zero")) + .toArray(); + int sampleSize = statistics.size(); + // Sample contains all the queried partitions, estimate avg normally + if (rowCounts.length <= 2 || queriedPartitionsCount == sampleSize) { + OptionalDouble averageRowsPerPartitionOptional = Arrays.stream(rowCounts).average(); + if (averageRowsPerPartitionOptional.isEmpty()) { + return Optional.empty(); + } + double averageRowsPerPartition = averageRowsPerPartitionOptional.getAsDouble(); + return Optional.of(new PartitionsRowCount(averageRowsPerPartition, averageRowsPerPartition * queriedPartitionsCount)); + } + + // Some partitions (e.g. __HIVE_DEFAULT_PARTITION__) may be outliers in terms of row count. + // Excluding the min and max rowCount values from averageRowsPerPartition calculation helps to reduce the + // possibility of errors in the extrapolated rowCount due to a couple of outliers. + int minIndex = 0; + int maxIndex = 0; + long rowCountSum = rowCounts[0]; + for (int index = 1; index < rowCounts.length; index++) { + if (rowCounts[index] < rowCounts[minIndex]) { + minIndex = index; + } + else if (rowCounts[index] > rowCounts[maxIndex]) { + maxIndex = index; + } + rowCountSum += rowCounts[index]; + } + double averageWithoutOutliers = ((double) (rowCountSum - rowCounts[minIndex] - rowCounts[maxIndex])) / (rowCounts.length - 2); + double rowCount = (averageWithoutOutliers * (queriedPartitionsCount - 2)) + rowCounts[minIndex] + rowCounts[maxIndex]; + return Optional.of(new PartitionsRowCount(averageWithoutOutliers, rowCount)); + } + + @VisibleForTesting + static class PartitionsRowCount + { + private final double averageRowsPerPartition; + private final double rowCount; + + PartitionsRowCount(double averageRowsPerPartition, double rowCount) + { + verify(averageRowsPerPartition >= 0, "averageRowsPerPartition must be greater than or equal to zero"); + verify(rowCount >= 0, "rowCount must be greater than or equal to zero"); + this.averageRowsPerPartition = averageRowsPerPartition; + this.rowCount = rowCount; + } + + private double getAverageRowsPerPartition() + { + return averageRowsPerPartition; + } + + private double getRowCount() + { + return rowCount; + } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PartitionsRowCount that = (PartitionsRowCount) o; + return Double.compare(that.averageRowsPerPartition, averageRowsPerPartition) == 0 + && Double.compare(that.rowCount, rowCount) == 0; + } + + @Override + public int hashCode() + { + return Objects.hash(averageRowsPerPartition, rowCount); + } + + @Override + public String toString() + { + return toStringHelper(this) + .add("averageRowsPerPartition", averageRowsPerPartition) + .add("rowCount", rowCount) + .toString(); + } + } + + private static ColumnStatistics createPartitionColumnStatistics( + HiveColumnHandle column, + Type type, + List partitions, + Map statistics, + double averageRowsPerPartition, + double rowCount) + { + List nonEmptyPartitions = partitions.stream() + .filter(partition -> getPartitionRowCount(partition.getPartitionId(), statistics).orElse(averageRowsPerPartition) != 0) + .collect(toImmutableList()); + + return ColumnStatistics.builder() + .setDistinctValuesCount(Estimate.of(calculateDistinctPartitionKeys(column, nonEmptyPartitions))) + .setNullsFraction(Estimate.of(calculateNullsFractionForPartitioningKey(column, partitions, statistics, averageRowsPerPartition, rowCount))) + .setRange(calculateRangeForPartitioningKey(column, type, nonEmptyPartitions)) + .setDataSize(calculateDataSizeForPartitioningKey(column, type, partitions, statistics, averageRowsPerPartition)) + .build(); + } + + private static ColumnStatistics createPartitionColumnStatisticsWithoutRowCount(HiveColumnHandle column, Type type, List partitions) + { + if (partitions.isEmpty()) { + return ColumnStatistics.empty(); + } + + // Since we don't know the row count for each partition, we are taking an assumption here that all partitions + // are non-empty and contains exactly same amount of data. This will help us estimate ndv stats for partitioned + // columns which can be useful for certain optimizer rules. + double estimatedNullsCount = partitions.stream() + .filter(partition -> partition.getKeys().get(column).isNull()) + .count(); + + return ColumnStatistics.builder() + .setDistinctValuesCount(Estimate.of(calculateDistinctPartitionKeys(column, partitions))) + .setNullsFraction(Estimate.of(normalizeFraction(estimatedNullsCount / partitions.size()))) + .setRange(calculateRangeForPartitioningKey(column, type, partitions)) + .build(); + } + + @VisibleForTesting + static long calculateDistinctPartitionKeys( + HiveColumnHandle column, + List partitions) + { + return partitions.stream() + .map(partition -> partition.getKeys().get(column)) + .filter(value -> !value.isNull()) + .distinct() + .count(); + } + + @VisibleForTesting + static double calculateNullsFractionForPartitioningKey( + HiveColumnHandle column, + List partitions, + Map statistics, + double averageRowsPerPartition, + double rowCount) + { + if (rowCount == 0) { + return 0; + } + double estimatedNullsCount = partitions.stream() + .filter(partition -> partition.getKeys().get(column).isNull()) + .map(HivePartition::getPartitionId) + .mapToDouble(partitionName -> getPartitionRowCount(partitionName, statistics).orElse(averageRowsPerPartition)) + .sum(); + return normalizeFraction(estimatedNullsCount / rowCount); + } + + private static double normalizeFraction(double fraction) + { + checkArgument(!isNaN(fraction), "fraction is NaN"); + checkArgument(isFinite(fraction), "fraction must be finite"); + if (fraction < 0) { + return 0; + } + if (fraction > 1) { + return 1; + } + return fraction; + } + + @VisibleForTesting + static Estimate calculateDataSizeForPartitioningKey( + HiveColumnHandle column, + Type type, + List partitions, + Map statistics, + double averageRowsPerPartition) + { + if (!hasDataSize(type)) { + return Estimate.unknown(); + } + double dataSize = 0; + for (HivePartition partition : partitions) { + int length = getSize(partition.getKeys().get(column)); + double rowCount = getPartitionRowCount(partition.getPartitionId(), statistics).orElse(averageRowsPerPartition); + dataSize += length * rowCount; + } + return Estimate.of(dataSize); + } + + private static boolean hasDataSize(Type type) + { + return type instanceof VarcharType || type instanceof CharType; + } + + private static int getSize(NullableValue nullableValue) + { + if (nullableValue.isNull()) { + return 0; + } + Object value = nullableValue.getValue(); + checkArgument(value instanceof Slice, "value is expected to be of Slice type"); + return ((Slice) value).length(); + } + + private static OptionalDouble getPartitionRowCount(String partitionName, Map statistics) + { + PartitionStatistics partitionStatistics = statistics.get(partitionName); + if (partitionStatistics == null) { + return OptionalDouble.empty(); + } + OptionalLong rowCount = partitionStatistics.getBasicStatistics().getRowCount(); + if (rowCount.isPresent()) { + verify(rowCount.getAsLong() >= 0, "rowCount must be greater than or equal to zero"); + return OptionalDouble.of(rowCount.getAsLong()); + } + return OptionalDouble.empty(); + } + + @VisibleForTesting + static Optional calculateRangeForPartitioningKey(HiveColumnHandle column, Type type, List partitions) + { + List convertedValues = partitions.stream() + .map(HivePartition::getKeys) + .map(keys -> keys.get(column)) + .filter(value -> !value.isNull()) + .map(NullableValue::getValue) + .map(value -> convertPartitionValueToDouble(type, value)) + .collect(toImmutableList()); + + if (convertedValues.stream().noneMatch(OptionalDouble::isPresent)) { + return Optional.empty(); + } + double[] values = convertedValues.stream() + .peek(convertedValue -> checkState(convertedValue.isPresent(), "convertedValue is missing")) + .mapToDouble(OptionalDouble::getAsDouble) + .toArray(); + verify(values.length != 0, "No values"); + + if (DoubleStream.of(values).anyMatch(Double::isNaN)) { + return Optional.empty(); + } + + double min = DoubleStream.of(values).min().orElseThrow(); + double max = DoubleStream.of(values).max().orElseThrow(); + return Optional.of(new DoubleRange(min, max)); + } + + @VisibleForTesting + static OptionalDouble convertPartitionValueToDouble(Type type, Object value) + { + return toStatsRepresentation(type, value); + } + + @VisibleForTesting + static ColumnStatistics createDataColumnStatistics(String column, Type type, double rowsCount, Collection partitionStatistics) + { + List columnStatistics = partitionStatistics.stream() + .map(PartitionStatistics::getColumnStatistics) + .map(statistics -> statistics.get(column)) + .filter(Objects::nonNull) + .collect(toImmutableList()); + + if (columnStatistics.isEmpty()) { + return ColumnStatistics.empty(); + } + + return ColumnStatistics.builder() + .setDistinctValuesCount(calculateDistinctValuesCount(columnStatistics)) + .setNullsFraction(calculateNullsFraction(column, partitionStatistics)) + .setDataSize(calculateDataSize(column, partitionStatistics, rowsCount)) + .setRange(calculateRange(type, columnStatistics)) + .build(); + } + + @VisibleForTesting + static Estimate calculateDistinctValuesCount(List columnStatistics) + { + return columnStatistics.stream() + .map(AbstractHiveStatisticsProvider::getDistinctValuesCount) + .filter(OptionalLong::isPresent) + .map(OptionalLong::getAsLong) + .peek(distinctValuesCount -> verify(distinctValuesCount >= 0, "distinctValuesCount must be greater than or equal to zero")) + .max(Long::compare) + .map(Estimate::of) + .orElse(Estimate.unknown()); + } + + private static OptionalLong getDistinctValuesCount(HiveColumnStatistics statistics) + { + if (statistics.getBooleanStatistics().isPresent() && + statistics.getBooleanStatistics().get().getFalseCount().isPresent() && + statistics.getBooleanStatistics().get().getTrueCount().isPresent()) { + long falseCount = statistics.getBooleanStatistics().get().getFalseCount().getAsLong(); + long trueCount = statistics.getBooleanStatistics().get().getTrueCount().getAsLong(); + return OptionalLong.of((falseCount > 0 ? 1 : 0) + (trueCount > 0 ? 1 : 0)); + } + if (statistics.getDistinctValuesCount().isPresent()) { + return statistics.getDistinctValuesCount(); + } + return OptionalLong.empty(); + } + + @VisibleForTesting + static Estimate calculateNullsFraction(String column, Collection partitionStatistics) + { + List statisticsWithKnownRowCountAndNullsCount = partitionStatistics.stream() + .filter(statistics -> { + if (statistics.getBasicStatistics().getRowCount().isEmpty()) { + return false; + } + HiveColumnStatistics columnStatistics = statistics.getColumnStatistics().get(column); + if (columnStatistics == null) { + return false; + } + return columnStatistics.getNullsCount().isPresent(); + }) + .collect(toImmutableList()); + + if (statisticsWithKnownRowCountAndNullsCount.isEmpty()) { + return Estimate.unknown(); + } + + long totalNullsCount = 0; + long totalRowCount = 0; + for (PartitionStatistics statistics : statisticsWithKnownRowCountAndNullsCount) { + long rowCount = statistics.getBasicStatistics().getRowCount().orElseThrow(() -> new VerifyException("rowCount is not present")); + verify(rowCount >= 0, "rowCount must be greater than or equal to zero"); + HiveColumnStatistics columnStatistics = statistics.getColumnStatistics().get(column); + verifyNotNull(columnStatistics, "columnStatistics is null"); + long nullsCount = columnStatistics.getNullsCount().orElseThrow(() -> new VerifyException("nullsCount is not present")); + verify(nullsCount >= 0, "nullsCount must be greater than or equal to zero"); + verify(nullsCount <= rowCount, "nullsCount must be less than or equal to rowCount. nullsCount: %s. rowCount: %s.", nullsCount, rowCount); + totalNullsCount += nullsCount; + totalRowCount += rowCount; + } + + if (totalRowCount == 0) { + return Estimate.zero(); + } + + verify( + totalNullsCount <= totalRowCount, + "totalNullsCount must be less than or equal to totalRowCount. totalNullsCount: %s. totalRowCount: %s.", + totalNullsCount, + totalRowCount); + return Estimate.of(((double) totalNullsCount) / totalRowCount); + } + + @VisibleForTesting + static Estimate calculateDataSize(String column, Collection partitionStatistics, double totalRowCount) + { + List statisticsWithKnownRowCountAndDataSize = partitionStatistics.stream() + .filter(statistics -> { + if (statistics.getBasicStatistics().getRowCount().isEmpty()) { + return false; + } + HiveColumnStatistics columnStatistics = statistics.getColumnStatistics().get(column); + if (columnStatistics == null) { + return false; + } + return columnStatistics.getTotalSizeInBytes().isPresent(); + }) + .collect(toImmutableList()); + + if (statisticsWithKnownRowCountAndDataSize.isEmpty()) { + return Estimate.unknown(); + } + + long knownRowCount = 0; + long knownDataSize = 0; + for (PartitionStatistics statistics : statisticsWithKnownRowCountAndDataSize) { + long rowCount = statistics.getBasicStatistics().getRowCount().orElseThrow(() -> new VerifyException("rowCount is not present")); + verify(rowCount >= 0, "rowCount must be greater than or equal to zero"); + HiveColumnStatistics columnStatistics = statistics.getColumnStatistics().get(column); + verifyNotNull(columnStatistics, "columnStatistics is null"); + long dataSize = columnStatistics.getTotalSizeInBytes().orElseThrow(() -> new VerifyException("totalSizeInBytes is not present")); + verify(dataSize >= 0, "dataSize must be greater than or equal to zero"); + knownRowCount += rowCount; + knownDataSize += dataSize; + } + + if (totalRowCount == 0) { + return Estimate.zero(); + } + + if (knownRowCount == 0) { + return Estimate.unknown(); + } + + double averageValueDataSizeInBytes = ((double) knownDataSize) / knownRowCount; + return Estimate.of(averageValueDataSizeInBytes * totalRowCount); + } + + @VisibleForTesting + static Optional calculateRange(Type type, List columnStatistics) + { + return columnStatistics.stream() + .map(statistics -> createRange(type, statistics)) + .filter(Optional::isPresent) + .map(Optional::get) + .reduce(DoubleRange::union); + } + + private static Optional createRange(Type type, HiveColumnStatistics statistics) + { + if (type.equals(BIGINT) || type.equals(INTEGER) || type.equals(SMALLINT) || type.equals(TINYINT)) { + return statistics.getIntegerStatistics().flatMap(integerStatistics -> createIntegerRange(type, integerStatistics)); + } + if (type.equals(DOUBLE) || type.equals(REAL)) { + return statistics.getDoubleStatistics().flatMap(AbstractHiveStatisticsProvider::createDoubleRange); + } + if (type.equals(DATE)) { + return statistics.getDateStatistics().flatMap(AbstractHiveStatisticsProvider::createDateRange); + } + if (type instanceof DecimalType) { + return statistics.getDecimalStatistics().flatMap(AbstractHiveStatisticsProvider::createDecimalRange); + } + return Optional.empty(); + } + + private static Optional createIntegerRange(Type type, IntegerStatistics statistics) + { + if (statistics.getMin().isPresent() && statistics.getMax().isPresent()) { + return Optional.of(createIntegerRange(type, statistics.getMin().getAsLong(), statistics.getMax().getAsLong())); + } + return Optional.empty(); + } + + private static DoubleRange createIntegerRange(Type type, long min, long max) + { + return new DoubleRange(normalizeIntegerValue(type, min), normalizeIntegerValue(type, max)); + } + + private static long normalizeIntegerValue(Type type, long value) + { + if (type.equals(BIGINT)) { + return value; + } + if (type.equals(INTEGER)) { + return Ints.saturatedCast(value); + } + if (type.equals(SMALLINT)) { + return Shorts.saturatedCast(value); + } + if (type.equals(TINYINT)) { + return SignedBytes.saturatedCast(value); + } + throw new IllegalArgumentException("Unexpected type: " + type); + } + + private static Optional createDoubleRange(DoubleStatistics statistics) + { + if (statistics.getMin().isPresent() && statistics.getMax().isPresent() && !isNaN(statistics.getMin().getAsDouble()) && !isNaN(statistics.getMax().getAsDouble())) { + return Optional.of(new DoubleRange(statistics.getMin().getAsDouble(), statistics.getMax().getAsDouble())); + } + return Optional.empty(); + } + + private static Optional createDateRange(DateStatistics statistics) + { + if (statistics.getMin().isPresent() && statistics.getMax().isPresent()) { + return Optional.of(new DoubleRange(statistics.getMin().get().toEpochDay(), statistics.getMax().get().toEpochDay())); + } + return Optional.empty(); + } + + private static Optional createDecimalRange(DecimalStatistics statistics) + { + if (statistics.getMin().isPresent() && statistics.getMax().isPresent()) { + return Optional.of(new DoubleRange(statistics.getMin().get().doubleValue(), statistics.getMax().get().doubleValue())); + } + return Optional.empty(); + } +} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/statistics/MetastoreHiveStatisticsProvider.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/statistics/MetastoreHiveStatisticsProvider.java index ded2ad1a766c..860eb4162c8d 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/statistics/MetastoreHiveStatisticsProvider.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/statistics/MetastoreHiveStatisticsProvider.java @@ -14,102 +14,35 @@ package io.trino.plugin.hive.statistics; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.VerifyException; import com.google.common.collect.ImmutableMap; -import com.google.common.hash.HashFunction; -import com.google.common.primitives.Ints; -import com.google.common.primitives.Shorts; -import com.google.common.primitives.SignedBytes; -import io.airlift.log.Logger; -import io.airlift.slice.Slice; -import io.trino.plugin.hive.HiveBasicStatistics; -import io.trino.plugin.hive.HiveColumnHandle; import io.trino.plugin.hive.HivePartition; import io.trino.plugin.hive.PartitionStatistics; -import io.trino.plugin.hive.metastore.DateStatistics; -import io.trino.plugin.hive.metastore.DecimalStatistics; -import io.trino.plugin.hive.metastore.DoubleStatistics; -import io.trino.plugin.hive.metastore.HiveColumnStatistics; -import io.trino.plugin.hive.metastore.IntegerStatistics; import io.trino.plugin.hive.metastore.SemiTransactionalHiveMetastore; -import io.trino.spi.TrinoException; -import io.trino.spi.connector.ColumnHandle; import io.trino.spi.connector.ConnectorSession; import io.trino.spi.connector.SchemaTableName; -import io.trino.spi.predicate.NullableValue; -import io.trino.spi.statistics.ColumnStatistics; -import io.trino.spi.statistics.DoubleRange; -import io.trino.spi.statistics.Estimate; -import io.trino.spi.statistics.TableStatistics; -import io.trino.spi.type.CharType; -import io.trino.spi.type.DecimalType; -import io.trino.spi.type.Type; -import io.trino.spi.type.VarcharType; -import java.math.BigDecimal; -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Comparator; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; -import java.util.OptionalDouble; -import java.util.OptionalLong; import java.util.Set; -import java.util.stream.DoubleStream; -import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.base.Verify.verify; -import static com.google.common.base.Verify.verifyNotNull; -import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; -import static com.google.common.collect.Maps.immutableEntry; -import static com.google.common.hash.Hashing.murmur3_128; -import static io.trino.plugin.hive.HiveErrorCode.HIVE_CORRUPTED_COLUMN_STATISTICS; import static io.trino.plugin.hive.HivePartition.UNPARTITIONED_ID; -import static io.trino.plugin.hive.HiveSessionProperties.getPartitionStatisticsSampleSize; -import static io.trino.plugin.hive.HiveSessionProperties.isIgnoreCorruptedStatistics; -import static io.trino.plugin.hive.HiveSessionProperties.isStatisticsEnabled; -import static io.trino.spi.statistics.StatsUtil.toStatsRepresentation; -import static io.trino.spi.type.BigintType.BIGINT; -import static io.trino.spi.type.DateType.DATE; -import static io.trino.spi.type.DoubleType.DOUBLE; -import static io.trino.spi.type.IntegerType.INTEGER; -import static io.trino.spi.type.RealType.REAL; -import static io.trino.spi.type.SmallintType.SMALLINT; -import static io.trino.spi.type.TinyintType.TINYINT; -import static java.lang.Double.isFinite; -import static java.lang.Double.isNaN; -import static java.lang.String.format; -import static java.util.Collections.unmodifiableList; import static java.util.Objects.requireNonNull; public class MetastoreHiveStatisticsProvider - implements HiveStatisticsProvider + extends AbstractHiveStatisticsProvider { - private static final Logger log = Logger.get(MetastoreHiveStatisticsProvider.class); - - private final PartitionsStatisticsProvider statisticsProvider; + private final SemiTransactionalHiveMetastore metastore; public MetastoreHiveStatisticsProvider(SemiTransactionalHiveMetastore metastore) { - requireNonNull(metastore, "metastore is null"); - this.statisticsProvider = (session, table, hivePartitions, columns) -> getPartitionsStatistics(metastore, table, columns, hivePartitions); - } - - @VisibleForTesting - MetastoreHiveStatisticsProvider(PartitionsStatisticsProvider statisticsProvider) - { - this.statisticsProvider = requireNonNull(statisticsProvider, "statisticsProvider is null"); + this.metastore = requireNonNull(metastore, "metastore is null"); } - private static Map getPartitionsStatistics(SemiTransactionalHiveMetastore metastore, SchemaTableName table, Set columns, List hivePartitions) + @Override + protected Map getPartitionsStatistics(ConnectorSession session, SchemaTableName table, List hivePartitions, Set columns) { if (hivePartitions.isEmpty()) { return ImmutableMap.of(); @@ -124,807 +57,4 @@ private static Map getPartitionsStatistics(SemiTran .collect(toImmutableSet()); return metastore.getPartitionStatistics(table.getSchemaName(), table.getTableName(), columns, partitionNames); } - - @Override - public TableStatistics getTableStatistics( - ConnectorSession session, - SchemaTableName table, - Map columns, - Map columnTypes, - List partitions) - { - if (!isStatisticsEnabled(session)) { - return TableStatistics.empty(); - } - if (partitions.isEmpty()) { - return createZeroStatistics(columns, columnTypes); - } - int sampleSize = getPartitionStatisticsSampleSize(session); - List partitionsSample = getPartitionsSample(partitions, sampleSize); - try { - Map statisticsSample = statisticsProvider.getPartitionsStatistics(session, table, partitionsSample, columns.keySet()); - validatePartitionStatistics(table, statisticsSample); - return getTableStatistics(columns, columnTypes, partitions, statisticsSample); - } - catch (TrinoException e) { - if (e.getErrorCode().equals(HIVE_CORRUPTED_COLUMN_STATISTICS.toErrorCode()) && isIgnoreCorruptedStatistics(session)) { - log.error(e); - return TableStatistics.empty(); - } - throw e; - } - } - - private TableStatistics createZeroStatistics(Map columns, Map columnTypes) - { - TableStatistics.Builder result = TableStatistics.builder(); - result.setRowCount(Estimate.of(0)); - columns.forEach((columnName, columnHandle) -> { - Type columnType = columnTypes.get(columnName); - verifyNotNull(columnType, "columnType is missing for column: %s", columnName); - ColumnStatistics.Builder columnStatistics = ColumnStatistics.builder(); - columnStatistics.setNullsFraction(Estimate.of(0)); - columnStatistics.setDistinctValuesCount(Estimate.of(0)); - if (hasDataSize(columnType)) { - columnStatistics.setDataSize(Estimate.of(0)); - } - result.setColumnStatistics(columnHandle, columnStatistics.build()); - }); - return result.build(); - } - - @VisibleForTesting - static List getPartitionsSample(List partitions, int sampleSize) - { - checkArgument(sampleSize > 0, "sampleSize is expected to be greater than zero"); - - if (partitions.size() <= sampleSize) { - return partitions; - } - - List result = new ArrayList<>(); - - int samplesLeft = sampleSize; - - HivePartition min = partitions.get(0); - HivePartition max = partitions.get(0); - for (HivePartition partition : partitions) { - if (partition.getPartitionId().compareTo(min.getPartitionId()) < 0) { - min = partition; - } - else if (partition.getPartitionId().compareTo(max.getPartitionId()) > 0) { - max = partition; - } - } - - result.add(min); - samplesLeft--; - if (samplesLeft > 0) { - result.add(max); - samplesLeft--; - } - - if (samplesLeft > 0) { - HashFunction hashFunction = murmur3_128(); - Comparator> hashComparator = Comparator - ., Long>comparing(Map.Entry::getValue) - .thenComparing(entry -> entry.getKey().getPartitionId()); - partitions.stream() - .filter(partition -> !result.contains(partition)) - .map(partition -> immutableEntry(partition, hashFunction.hashUnencodedChars(partition.getPartitionId()).asLong())) - .sorted(hashComparator) - .limit(samplesLeft) - .forEachOrdered(entry -> result.add(entry.getKey())); - } - - return unmodifiableList(result); - } - - @VisibleForTesting - static void validatePartitionStatistics(SchemaTableName table, Map partitionStatistics) - { - partitionStatistics.forEach((partition, statistics) -> { - HiveBasicStatistics basicStatistics = statistics.getBasicStatistics(); - OptionalLong rowCount = basicStatistics.getRowCount(); - rowCount.ifPresent(count -> checkStatistics(count >= 0, table, partition, "rowCount must be greater than or equal to zero: %s", count)); - basicStatistics.getFileCount().ifPresent(count -> checkStatistics(count >= 0, table, partition, "fileCount must be greater than or equal to zero: %s", count)); - basicStatistics.getInMemoryDataSizeInBytes().ifPresent(size -> checkStatistics(size >= 0, table, partition, "inMemoryDataSizeInBytes must be greater than or equal to zero: %s", size)); - basicStatistics.getOnDiskDataSizeInBytes().ifPresent(size -> checkStatistics(size >= 0, table, partition, "onDiskDataSizeInBytes must be greater than or equal to zero: %s", size)); - statistics.getColumnStatistics().forEach((column, columnStatistics) -> validateColumnStatistics(table, partition, column, rowCount, columnStatistics)); - }); - } - - private static void validateColumnStatistics(SchemaTableName table, String partition, String column, OptionalLong rowCount, HiveColumnStatistics columnStatistics) - { - columnStatistics.getMaxValueSizeInBytes().ifPresent(maxValueSizeInBytes -> - checkStatistics(maxValueSizeInBytes >= 0, table, partition, column, "maxValueSizeInBytes must be greater than or equal to zero: %s", maxValueSizeInBytes)); - columnStatistics.getTotalSizeInBytes().ifPresent(totalSizeInBytes -> - checkStatistics(totalSizeInBytes >= 0, table, partition, column, "totalSizeInBytes must be greater than or equal to zero: %s", totalSizeInBytes)); - columnStatistics.getNullsCount().ifPresent(nullsCount -> { - checkStatistics(nullsCount >= 0, table, partition, column, "nullsCount must be greater than or equal to zero: %s", nullsCount); - if (rowCount.isPresent()) { - checkStatistics( - nullsCount <= rowCount.getAsLong(), - table, - partition, - column, - "nullsCount must be less than or equal to rowCount. nullsCount: %s. rowCount: %s.", - nullsCount, - rowCount.getAsLong()); - } - }); - columnStatistics.getDistinctValuesCount().ifPresent(distinctValuesCount -> { - checkStatistics(distinctValuesCount >= 0, table, partition, column, "distinctValuesCount must be greater than or equal to zero: %s", distinctValuesCount); - if (rowCount.isPresent()) { - checkStatistics( - distinctValuesCount <= rowCount.getAsLong(), - table, - partition, - column, - "distinctValuesCount must be less than or equal to rowCount. distinctValuesCount: %s. rowCount: %s.", - distinctValuesCount, - rowCount.getAsLong()); - } - if (rowCount.isPresent() && columnStatistics.getNullsCount().isPresent()) { - long nonNullsCount = rowCount.getAsLong() - columnStatistics.getNullsCount().getAsLong(); - checkStatistics( - distinctValuesCount <= nonNullsCount, - table, - partition, - column, - "distinctValuesCount must be less than or equal to nonNullsCount. distinctValuesCount: %s. nonNullsCount: %s.", - distinctValuesCount, - nonNullsCount); - } - }); - - columnStatistics.getIntegerStatistics().ifPresent(integerStatistics -> { - OptionalLong min = integerStatistics.getMin(); - OptionalLong max = integerStatistics.getMax(); - if (min.isPresent() && max.isPresent()) { - checkStatistics( - min.getAsLong() <= max.getAsLong(), - table, - partition, - column, - "integerStatistics.min must be less than or equal to integerStatistics.max. integerStatistics.min: %s. integerStatistics.max: %s.", - min.getAsLong(), - max.getAsLong()); - } - }); - columnStatistics.getDoubleStatistics().ifPresent(doubleStatistics -> { - OptionalDouble min = doubleStatistics.getMin(); - OptionalDouble max = doubleStatistics.getMax(); - if (min.isPresent() && max.isPresent() && !isNaN(min.getAsDouble()) && !isNaN(max.getAsDouble())) { - checkStatistics( - min.getAsDouble() <= max.getAsDouble(), - table, - partition, - column, - "doubleStatistics.min must be less than or equal to doubleStatistics.max. doubleStatistics.min: %s. doubleStatistics.max: %s.", - min.getAsDouble(), - max.getAsDouble()); - } - }); - columnStatistics.getDecimalStatistics().ifPresent(decimalStatistics -> { - Optional min = decimalStatistics.getMin(); - Optional max = decimalStatistics.getMax(); - if (min.isPresent() && max.isPresent()) { - checkStatistics( - min.get().compareTo(max.get()) <= 0, - table, - partition, - column, - "decimalStatistics.min must be less than or equal to decimalStatistics.max. decimalStatistics.min: %s. decimalStatistics.max: %s.", - min.get(), - max.get()); - } - }); - columnStatistics.getDateStatistics().ifPresent(dateStatistics -> { - Optional min = dateStatistics.getMin(); - Optional max = dateStatistics.getMax(); - if (min.isPresent() && max.isPresent()) { - checkStatistics( - min.get().compareTo(max.get()) <= 0, - table, - partition, - column, - "dateStatistics.min must be less than or equal to dateStatistics.max. dateStatistics.min: %s. dateStatistics.max: %s.", - min.get(), - max.get()); - } - }); - columnStatistics.getBooleanStatistics().ifPresent(booleanStatistics -> { - OptionalLong falseCount = booleanStatistics.getFalseCount(); - OptionalLong trueCount = booleanStatistics.getTrueCount(); - falseCount.ifPresent(count -> - checkStatistics(count >= 0, table, partition, column, "falseCount must be greater than or equal to zero: %s", count)); - trueCount.ifPresent(count -> - checkStatistics(count >= 0, table, partition, column, "trueCount must be greater than or equal to zero: %s", count)); - if (rowCount.isPresent() && falseCount.isPresent()) { - checkStatistics( - falseCount.getAsLong() <= rowCount.getAsLong(), - table, - partition, - column, - "booleanStatistics.falseCount must be less than or equal to rowCount. booleanStatistics.falseCount: %s. rowCount: %s.", - falseCount.getAsLong(), - rowCount.getAsLong()); - } - if (rowCount.isPresent() && trueCount.isPresent()) { - checkStatistics( - trueCount.getAsLong() <= rowCount.getAsLong(), - table, - partition, - column, - "booleanStatistics.trueCount must be less than or equal to rowCount. booleanStatistics.trueCount: %s. rowCount: %s.", - trueCount.getAsLong(), - rowCount.getAsLong()); - } - }); - } - - private static void checkStatistics(boolean expression, SchemaTableName table, String partition, String column, String message, Object... args) - { - if (!expression) { - throw new TrinoException( - HIVE_CORRUPTED_COLUMN_STATISTICS, - format("Corrupted partition statistics (Table: %s Partition: [%s] Column: %s): %s", table, partition, column, format(message, args))); - } - } - - private static void checkStatistics(boolean expression, SchemaTableName table, String partition, String message, Object... args) - { - if (!expression) { - throw new TrinoException( - HIVE_CORRUPTED_COLUMN_STATISTICS, - format("Corrupted partition statistics (Table: %s Partition: [%s]): %s", table, partition, format(message, args))); - } - } - - private static TableStatistics getTableStatistics( - Map columns, - Map columnTypes, - List partitions, - Map statistics) - { - if (statistics.isEmpty()) { - return createEmptyTableStatisticsWithPartitionColumnStatistics(columns, columnTypes, partitions); - } - - checkArgument(!partitions.isEmpty(), "partitions is empty"); - - Optional optionalRowCount = calculatePartitionsRowCount(statistics.values(), partitions.size()); - if (optionalRowCount.isEmpty()) { - return createEmptyTableStatisticsWithPartitionColumnStatistics(columns, columnTypes, partitions); - } - double rowCount = optionalRowCount.get().getRowCount(); - - TableStatistics.Builder result = TableStatistics.builder(); - result.setRowCount(Estimate.of(rowCount)); - for (Map.Entry column : columns.entrySet()) { - String columnName = column.getKey(); - HiveColumnHandle columnHandle = (HiveColumnHandle) column.getValue(); - Type columnType = columnTypes.get(columnName); - ColumnStatistics columnStatistics; - if (columnHandle.isPartitionKey()) { - double averageRowsPerPartition = optionalRowCount.get().getAverageRowsPerPartition(); - columnStatistics = createPartitionColumnStatistics(columnHandle, columnType, partitions, statistics, averageRowsPerPartition, rowCount); - } - else { - columnStatistics = createDataColumnStatistics(columnName, columnType, rowCount, statistics.values()); - } - result.setColumnStatistics(columnHandle, columnStatistics); - } - return result.build(); - } - - private static TableStatistics createEmptyTableStatisticsWithPartitionColumnStatistics( - Map columns, - Map columnTypes, - List partitions) - { - TableStatistics.Builder result = TableStatistics.builder(); - // Estimate stats for partitioned columns even when row count is unavailable. This will help us use - // ndv stats in rules like "ApplyPreferredTableWriterPartitioning". - for (Map.Entry column : columns.entrySet()) { - HiveColumnHandle columnHandle = (HiveColumnHandle) column.getValue(); - if (columnHandle.isPartitionKey()) { - result.setColumnStatistics( - columnHandle, - createPartitionColumnStatisticsWithoutRowCount(columnHandle, columnTypes.get(column.getKey()), partitions)); - } - } - return result.build(); - } - - @VisibleForTesting - static Optional calculatePartitionsRowCount(Collection statistics, int queriedPartitionsCount) - { - long[] rowCounts = statistics.stream() - .map(PartitionStatistics::getBasicStatistics) - .map(HiveBasicStatistics::getRowCount) - .filter(OptionalLong::isPresent) - .mapToLong(OptionalLong::getAsLong) - .peek(count -> verify(count >= 0, "count must be greater than or equal to zero")) - .toArray(); - int sampleSize = statistics.size(); - // Sample contains all the queried partitions, estimate avg normally - if (rowCounts.length <= 2 || queriedPartitionsCount == sampleSize) { - OptionalDouble averageRowsPerPartitionOptional = Arrays.stream(rowCounts).average(); - if (averageRowsPerPartitionOptional.isEmpty()) { - return Optional.empty(); - } - double averageRowsPerPartition = averageRowsPerPartitionOptional.getAsDouble(); - return Optional.of(new PartitionsRowCount(averageRowsPerPartition, averageRowsPerPartition * queriedPartitionsCount)); - } - - // Some partitions (e.g. __HIVE_DEFAULT_PARTITION__) may be outliers in terms of row count. - // Excluding the min and max rowCount values from averageRowsPerPartition calculation helps to reduce the - // possibility of errors in the extrapolated rowCount due to a couple of outliers. - int minIndex = 0; - int maxIndex = 0; - long rowCountSum = rowCounts[0]; - for (int index = 1; index < rowCounts.length; index++) { - if (rowCounts[index] < rowCounts[minIndex]) { - minIndex = index; - } - else if (rowCounts[index] > rowCounts[maxIndex]) { - maxIndex = index; - } - rowCountSum += rowCounts[index]; - } - double averageWithoutOutliers = ((double) (rowCountSum - rowCounts[minIndex] - rowCounts[maxIndex])) / (rowCounts.length - 2); - double rowCount = (averageWithoutOutliers * (queriedPartitionsCount - 2)) + rowCounts[minIndex] + rowCounts[maxIndex]; - return Optional.of(new PartitionsRowCount(averageWithoutOutliers, rowCount)); - } - - @VisibleForTesting - static class PartitionsRowCount - { - private final double averageRowsPerPartition; - private final double rowCount; - - PartitionsRowCount(double averageRowsPerPartition, double rowCount) - { - verify(averageRowsPerPartition >= 0, "averageRowsPerPartition must be greater than or equal to zero"); - verify(rowCount >= 0, "rowCount must be greater than or equal to zero"); - this.averageRowsPerPartition = averageRowsPerPartition; - this.rowCount = rowCount; - } - - private double getAverageRowsPerPartition() - { - return averageRowsPerPartition; - } - - private double getRowCount() - { - return rowCount; - } - - @Override - public boolean equals(Object o) - { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - PartitionsRowCount that = (PartitionsRowCount) o; - return Double.compare(that.averageRowsPerPartition, averageRowsPerPartition) == 0 - && Double.compare(that.rowCount, rowCount) == 0; - } - - @Override - public int hashCode() - { - return Objects.hash(averageRowsPerPartition, rowCount); - } - - @Override - public String toString() - { - return toStringHelper(this) - .add("averageRowsPerPartition", averageRowsPerPartition) - .add("rowCount", rowCount) - .toString(); - } - } - - private static ColumnStatistics createPartitionColumnStatistics( - HiveColumnHandle column, - Type type, - List partitions, - Map statistics, - double averageRowsPerPartition, - double rowCount) - { - List nonEmptyPartitions = partitions.stream() - .filter(partition -> getPartitionRowCount(partition.getPartitionId(), statistics).orElse(averageRowsPerPartition) != 0) - .collect(toImmutableList()); - - return ColumnStatistics.builder() - .setDistinctValuesCount(Estimate.of(calculateDistinctPartitionKeys(column, nonEmptyPartitions))) - .setNullsFraction(Estimate.of(calculateNullsFractionForPartitioningKey(column, partitions, statistics, averageRowsPerPartition, rowCount))) - .setRange(calculateRangeForPartitioningKey(column, type, nonEmptyPartitions)) - .setDataSize(calculateDataSizeForPartitioningKey(column, type, partitions, statistics, averageRowsPerPartition)) - .build(); - } - - private static ColumnStatistics createPartitionColumnStatisticsWithoutRowCount(HiveColumnHandle column, Type type, List partitions) - { - if (partitions.isEmpty()) { - return ColumnStatistics.empty(); - } - - // Since we don't know the row count for each partition, we are taking an assumption here that all partitions - // are non-empty and contains exactly same amount of data. This will help us estimate ndv stats for partitioned - // columns which can be useful for certain optimizer rules. - double estimatedNullsCount = partitions.stream() - .filter(partition -> partition.getKeys().get(column).isNull()) - .count(); - - return ColumnStatistics.builder() - .setDistinctValuesCount(Estimate.of(calculateDistinctPartitionKeys(column, partitions))) - .setNullsFraction(Estimate.of(normalizeFraction(estimatedNullsCount / partitions.size()))) - .setRange(calculateRangeForPartitioningKey(column, type, partitions)) - .build(); - } - - @VisibleForTesting - static long calculateDistinctPartitionKeys( - HiveColumnHandle column, - List partitions) - { - return partitions.stream() - .map(partition -> partition.getKeys().get(column)) - .filter(value -> !value.isNull()) - .distinct() - .count(); - } - - @VisibleForTesting - static double calculateNullsFractionForPartitioningKey( - HiveColumnHandle column, - List partitions, - Map statistics, - double averageRowsPerPartition, - double rowCount) - { - if (rowCount == 0) { - return 0; - } - double estimatedNullsCount = partitions.stream() - .filter(partition -> partition.getKeys().get(column).isNull()) - .map(HivePartition::getPartitionId) - .mapToDouble(partitionName -> getPartitionRowCount(partitionName, statistics).orElse(averageRowsPerPartition)) - .sum(); - return normalizeFraction(estimatedNullsCount / rowCount); - } - - private static double normalizeFraction(double fraction) - { - checkArgument(!isNaN(fraction), "fraction is NaN"); - checkArgument(isFinite(fraction), "fraction must be finite"); - if (fraction < 0) { - return 0; - } - if (fraction > 1) { - return 1; - } - return fraction; - } - - @VisibleForTesting - static Estimate calculateDataSizeForPartitioningKey( - HiveColumnHandle column, - Type type, - List partitions, - Map statistics, - double averageRowsPerPartition) - { - if (!hasDataSize(type)) { - return Estimate.unknown(); - } - double dataSize = 0; - for (HivePartition partition : partitions) { - int length = getSize(partition.getKeys().get(column)); - double rowCount = getPartitionRowCount(partition.getPartitionId(), statistics).orElse(averageRowsPerPartition); - dataSize += length * rowCount; - } - return Estimate.of(dataSize); - } - - private static boolean hasDataSize(Type type) - { - return type instanceof VarcharType || type instanceof CharType; - } - - private static int getSize(NullableValue nullableValue) - { - if (nullableValue.isNull()) { - return 0; - } - Object value = nullableValue.getValue(); - checkArgument(value instanceof Slice, "value is expected to be of Slice type"); - return ((Slice) value).length(); - } - - private static OptionalDouble getPartitionRowCount(String partitionName, Map statistics) - { - PartitionStatistics partitionStatistics = statistics.get(partitionName); - if (partitionStatistics == null) { - return OptionalDouble.empty(); - } - OptionalLong rowCount = partitionStatistics.getBasicStatistics().getRowCount(); - if (rowCount.isPresent()) { - verify(rowCount.getAsLong() >= 0, "rowCount must be greater than or equal to zero"); - return OptionalDouble.of(rowCount.getAsLong()); - } - return OptionalDouble.empty(); - } - - @VisibleForTesting - static Optional calculateRangeForPartitioningKey(HiveColumnHandle column, Type type, List partitions) - { - List convertedValues = partitions.stream() - .map(HivePartition::getKeys) - .map(keys -> keys.get(column)) - .filter(value -> !value.isNull()) - .map(NullableValue::getValue) - .map(value -> convertPartitionValueToDouble(type, value)) - .collect(toImmutableList()); - - if (convertedValues.stream().noneMatch(OptionalDouble::isPresent)) { - return Optional.empty(); - } - double[] values = convertedValues.stream() - .peek(convertedValue -> checkState(convertedValue.isPresent(), "convertedValue is missing")) - .mapToDouble(OptionalDouble::getAsDouble) - .toArray(); - verify(values.length != 0, "No values"); - - if (DoubleStream.of(values).anyMatch(Double::isNaN)) { - return Optional.empty(); - } - - double min = DoubleStream.of(values).min().orElseThrow(); - double max = DoubleStream.of(values).max().orElseThrow(); - return Optional.of(new DoubleRange(min, max)); - } - - @VisibleForTesting - static OptionalDouble convertPartitionValueToDouble(Type type, Object value) - { - return toStatsRepresentation(type, value); - } - - @VisibleForTesting - static ColumnStatistics createDataColumnStatistics(String column, Type type, double rowsCount, Collection partitionStatistics) - { - List columnStatistics = partitionStatistics.stream() - .map(PartitionStatistics::getColumnStatistics) - .map(statistics -> statistics.get(column)) - .filter(Objects::nonNull) - .collect(toImmutableList()); - - if (columnStatistics.isEmpty()) { - return ColumnStatistics.empty(); - } - - return ColumnStatistics.builder() - .setDistinctValuesCount(calculateDistinctValuesCount(columnStatistics)) - .setNullsFraction(calculateNullsFraction(column, partitionStatistics)) - .setDataSize(calculateDataSize(column, partitionStatistics, rowsCount)) - .setRange(calculateRange(type, columnStatistics)) - .build(); - } - - @VisibleForTesting - static Estimate calculateDistinctValuesCount(List columnStatistics) - { - return columnStatistics.stream() - .map(MetastoreHiveStatisticsProvider::getDistinctValuesCount) - .filter(OptionalLong::isPresent) - .map(OptionalLong::getAsLong) - .peek(distinctValuesCount -> verify(distinctValuesCount >= 0, "distinctValuesCount must be greater than or equal to zero")) - .max(Long::compare) - .map(Estimate::of) - .orElse(Estimate.unknown()); - } - - private static OptionalLong getDistinctValuesCount(HiveColumnStatistics statistics) - { - if (statistics.getBooleanStatistics().isPresent() && - statistics.getBooleanStatistics().get().getFalseCount().isPresent() && - statistics.getBooleanStatistics().get().getTrueCount().isPresent()) { - long falseCount = statistics.getBooleanStatistics().get().getFalseCount().getAsLong(); - long trueCount = statistics.getBooleanStatistics().get().getTrueCount().getAsLong(); - return OptionalLong.of((falseCount > 0 ? 1 : 0) + (trueCount > 0 ? 1 : 0)); - } - if (statistics.getDistinctValuesCount().isPresent()) { - return statistics.getDistinctValuesCount(); - } - return OptionalLong.empty(); - } - - @VisibleForTesting - static Estimate calculateNullsFraction(String column, Collection partitionStatistics) - { - List statisticsWithKnownRowCountAndNullsCount = partitionStatistics.stream() - .filter(statistics -> { - if (statistics.getBasicStatistics().getRowCount().isEmpty()) { - return false; - } - HiveColumnStatistics columnStatistics = statistics.getColumnStatistics().get(column); - if (columnStatistics == null) { - return false; - } - return columnStatistics.getNullsCount().isPresent(); - }) - .collect(toImmutableList()); - - if (statisticsWithKnownRowCountAndNullsCount.isEmpty()) { - return Estimate.unknown(); - } - - long totalNullsCount = 0; - long totalRowCount = 0; - for (PartitionStatistics statistics : statisticsWithKnownRowCountAndNullsCount) { - long rowCount = statistics.getBasicStatistics().getRowCount().orElseThrow(() -> new VerifyException("rowCount is not present")); - verify(rowCount >= 0, "rowCount must be greater than or equal to zero"); - HiveColumnStatistics columnStatistics = statistics.getColumnStatistics().get(column); - verifyNotNull(columnStatistics, "columnStatistics is null"); - long nullsCount = columnStatistics.getNullsCount().orElseThrow(() -> new VerifyException("nullsCount is not present")); - verify(nullsCount >= 0, "nullsCount must be greater than or equal to zero"); - verify(nullsCount <= rowCount, "nullsCount must be less than or equal to rowCount. nullsCount: %s. rowCount: %s.", nullsCount, rowCount); - totalNullsCount += nullsCount; - totalRowCount += rowCount; - } - - if (totalRowCount == 0) { - return Estimate.zero(); - } - - verify( - totalNullsCount <= totalRowCount, - "totalNullsCount must be less than or equal to totalRowCount. totalNullsCount: %s. totalRowCount: %s.", - totalNullsCount, - totalRowCount); - return Estimate.of(((double) totalNullsCount) / totalRowCount); - } - - @VisibleForTesting - static Estimate calculateDataSize(String column, Collection partitionStatistics, double totalRowCount) - { - List statisticsWithKnownRowCountAndDataSize = partitionStatistics.stream() - .filter(statistics -> { - if (statistics.getBasicStatistics().getRowCount().isEmpty()) { - return false; - } - HiveColumnStatistics columnStatistics = statistics.getColumnStatistics().get(column); - if (columnStatistics == null) { - return false; - } - return columnStatistics.getTotalSizeInBytes().isPresent(); - }) - .collect(toImmutableList()); - - if (statisticsWithKnownRowCountAndDataSize.isEmpty()) { - return Estimate.unknown(); - } - - long knownRowCount = 0; - long knownDataSize = 0; - for (PartitionStatistics statistics : statisticsWithKnownRowCountAndDataSize) { - long rowCount = statistics.getBasicStatistics().getRowCount().orElseThrow(() -> new VerifyException("rowCount is not present")); - verify(rowCount >= 0, "rowCount must be greater than or equal to zero"); - HiveColumnStatistics columnStatistics = statistics.getColumnStatistics().get(column); - verifyNotNull(columnStatistics, "columnStatistics is null"); - long dataSize = columnStatistics.getTotalSizeInBytes().orElseThrow(() -> new VerifyException("totalSizeInBytes is not present")); - verify(dataSize >= 0, "dataSize must be greater than or equal to zero"); - knownRowCount += rowCount; - knownDataSize += dataSize; - } - - if (totalRowCount == 0) { - return Estimate.zero(); - } - - if (knownRowCount == 0) { - return Estimate.unknown(); - } - - double averageValueDataSizeInBytes = ((double) knownDataSize) / knownRowCount; - return Estimate.of(averageValueDataSizeInBytes * totalRowCount); - } - - @VisibleForTesting - static Optional calculateRange(Type type, List columnStatistics) - { - return columnStatistics.stream() - .map(statistics -> createRange(type, statistics)) - .filter(Optional::isPresent) - .map(Optional::get) - .reduce(DoubleRange::union); - } - - private static Optional createRange(Type type, HiveColumnStatistics statistics) - { - if (type.equals(BIGINT) || type.equals(INTEGER) || type.equals(SMALLINT) || type.equals(TINYINT)) { - return statistics.getIntegerStatistics().flatMap(integerStatistics -> createIntegerRange(type, integerStatistics)); - } - if (type.equals(DOUBLE) || type.equals(REAL)) { - return statistics.getDoubleStatistics().flatMap(MetastoreHiveStatisticsProvider::createDoubleRange); - } - if (type.equals(DATE)) { - return statistics.getDateStatistics().flatMap(MetastoreHiveStatisticsProvider::createDateRange); - } - if (type instanceof DecimalType) { - return statistics.getDecimalStatistics().flatMap(MetastoreHiveStatisticsProvider::createDecimalRange); - } - return Optional.empty(); - } - - private static Optional createIntegerRange(Type type, IntegerStatistics statistics) - { - if (statistics.getMin().isPresent() && statistics.getMax().isPresent()) { - return Optional.of(createIntegerRange(type, statistics.getMin().getAsLong(), statistics.getMax().getAsLong())); - } - return Optional.empty(); - } - - private static DoubleRange createIntegerRange(Type type, long min, long max) - { - return new DoubleRange(normalizeIntegerValue(type, min), normalizeIntegerValue(type, max)); - } - - private static long normalizeIntegerValue(Type type, long value) - { - if (type.equals(BIGINT)) { - return value; - } - if (type.equals(INTEGER)) { - return Ints.saturatedCast(value); - } - if (type.equals(SMALLINT)) { - return Shorts.saturatedCast(value); - } - if (type.equals(TINYINT)) { - return SignedBytes.saturatedCast(value); - } - throw new IllegalArgumentException("Unexpected type: " + type); - } - - private static Optional createDoubleRange(DoubleStatistics statistics) - { - if (statistics.getMin().isPresent() && statistics.getMax().isPresent() && !isNaN(statistics.getMin().getAsDouble()) && !isNaN(statistics.getMax().getAsDouble())) { - return Optional.of(new DoubleRange(statistics.getMin().getAsDouble(), statistics.getMax().getAsDouble())); - } - return Optional.empty(); - } - - private static Optional createDateRange(DateStatistics statistics) - { - if (statistics.getMin().isPresent() && statistics.getMax().isPresent()) { - return Optional.of(new DoubleRange(statistics.getMin().get().toEpochDay(), statistics.getMax().get().toEpochDay())); - } - return Optional.empty(); - } - - private static Optional createDecimalRange(DecimalStatistics statistics) - { - if (statistics.getMin().isPresent() && statistics.getMax().isPresent()) { - return Optional.of(new DoubleRange(statistics.getMin().get().doubleValue(), statistics.getMax().get().doubleValue())); - } - return Optional.empty(); - } - - @VisibleForTesting - interface PartitionsStatisticsProvider - { - Map getPartitionsStatistics(ConnectorSession session, SchemaTableName table, List hivePartitions, Set columns); - } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/statistics/TestMetastoreHiveStatisticsProvider.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/statistics/TestMetastoreHiveStatisticsProvider.java index 521ba7c5eed9..5896b6e7d61d 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/statistics/TestMetastoreHiveStatisticsProvider.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/statistics/TestMetastoreHiveStatisticsProvider.java @@ -38,9 +38,12 @@ import java.math.BigDecimal; import java.time.LocalDate; +import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.OptionalDouble; import java.util.OptionalLong; +import java.util.Set; import static io.trino.plugin.hive.HiveColumnHandle.ColumnType.PARTITION_KEY; import static io.trino.plugin.hive.HiveColumnHandle.ColumnType.REGULAR; @@ -58,20 +61,20 @@ import static io.trino.plugin.hive.metastore.HiveColumnStatistics.createDecimalColumnStatistics; import static io.trino.plugin.hive.metastore.HiveColumnStatistics.createDoubleColumnStatistics; import static io.trino.plugin.hive.metastore.HiveColumnStatistics.createIntegerColumnStatistics; -import static io.trino.plugin.hive.statistics.MetastoreHiveStatisticsProvider.PartitionsRowCount; -import static io.trino.plugin.hive.statistics.MetastoreHiveStatisticsProvider.calculateDataSize; -import static io.trino.plugin.hive.statistics.MetastoreHiveStatisticsProvider.calculateDataSizeForPartitioningKey; -import static io.trino.plugin.hive.statistics.MetastoreHiveStatisticsProvider.calculateDistinctPartitionKeys; -import static io.trino.plugin.hive.statistics.MetastoreHiveStatisticsProvider.calculateDistinctValuesCount; -import static io.trino.plugin.hive.statistics.MetastoreHiveStatisticsProvider.calculateNullsFraction; -import static io.trino.plugin.hive.statistics.MetastoreHiveStatisticsProvider.calculateNullsFractionForPartitioningKey; -import static io.trino.plugin.hive.statistics.MetastoreHiveStatisticsProvider.calculatePartitionsRowCount; -import static io.trino.plugin.hive.statistics.MetastoreHiveStatisticsProvider.calculateRange; -import static io.trino.plugin.hive.statistics.MetastoreHiveStatisticsProvider.calculateRangeForPartitioningKey; -import static io.trino.plugin.hive.statistics.MetastoreHiveStatisticsProvider.convertPartitionValueToDouble; -import static io.trino.plugin.hive.statistics.MetastoreHiveStatisticsProvider.createDataColumnStatistics; -import static io.trino.plugin.hive.statistics.MetastoreHiveStatisticsProvider.getPartitionsSample; -import static io.trino.plugin.hive.statistics.MetastoreHiveStatisticsProvider.validatePartitionStatistics; +import static io.trino.plugin.hive.statistics.AbstractHiveStatisticsProvider.PartitionsRowCount; +import static io.trino.plugin.hive.statistics.AbstractHiveStatisticsProvider.calculateDataSize; +import static io.trino.plugin.hive.statistics.AbstractHiveStatisticsProvider.calculateDataSizeForPartitioningKey; +import static io.trino.plugin.hive.statistics.AbstractHiveStatisticsProvider.calculateDistinctPartitionKeys; +import static io.trino.plugin.hive.statistics.AbstractHiveStatisticsProvider.calculateDistinctValuesCount; +import static io.trino.plugin.hive.statistics.AbstractHiveStatisticsProvider.calculateNullsFraction; +import static io.trino.plugin.hive.statistics.AbstractHiveStatisticsProvider.calculateNullsFractionForPartitioningKey; +import static io.trino.plugin.hive.statistics.AbstractHiveStatisticsProvider.calculatePartitionsRowCount; +import static io.trino.plugin.hive.statistics.AbstractHiveStatisticsProvider.calculateRange; +import static io.trino.plugin.hive.statistics.AbstractHiveStatisticsProvider.calculateRangeForPartitioningKey; +import static io.trino.plugin.hive.statistics.AbstractHiveStatisticsProvider.convertPartitionValueToDouble; +import static io.trino.plugin.hive.statistics.AbstractHiveStatisticsProvider.createDataColumnStatistics; +import static io.trino.plugin.hive.statistics.AbstractHiveStatisticsProvider.getPartitionsSample; +import static io.trino.plugin.hive.statistics.AbstractHiveStatisticsProvider.validatePartitionStatistics; import static io.trino.plugin.hive.util.HiveUtil.parsePartitionValue; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.DateType.DATE; @@ -630,7 +633,14 @@ public void testGetTableStatistics() .setBasicStatistics(new HiveBasicStatistics(OptionalLong.empty(), OptionalLong.of(1000), OptionalLong.empty(), OptionalLong.empty())) .setColumnStatistics(ImmutableMap.of(COLUMN, createIntegerColumnStatistics(OptionalLong.of(-100), OptionalLong.of(100), OptionalLong.of(500), OptionalLong.of(300)))) .build(); - MetastoreHiveStatisticsProvider statisticsProvider = new MetastoreHiveStatisticsProvider((session, table, hivePartitions, columns) -> ImmutableMap.of(partitionName, statistics)); + HiveStatisticsProvider statisticsProvider = new AbstractHiveStatisticsProvider() + { + @Override + protected Map getPartitionsStatistics(ConnectorSession session, SchemaTableName table, List hivePartitions, Set columns) + { + return ImmutableMap.of(partitionName, statistics); + } + }; HiveColumnHandle columnHandle = createBaseColumn(COLUMN, 2, HIVE_LONG, BIGINT, REGULAR, Optional.empty()); TableStatistics expected = TableStatistics.builder() .setRowCount(Estimate.of(1000)) @@ -679,7 +689,14 @@ public void testGetTableStatisticsUnpartitioned() .setBasicStatistics(new HiveBasicStatistics(OptionalLong.empty(), OptionalLong.of(1000), OptionalLong.empty(), OptionalLong.empty())) .setColumnStatistics(ImmutableMap.of(COLUMN, createIntegerColumnStatistics(OptionalLong.of(-100), OptionalLong.of(100), OptionalLong.of(500), OptionalLong.of(300)))) .build(); - MetastoreHiveStatisticsProvider statisticsProvider = new MetastoreHiveStatisticsProvider((session, table, hivePartitions, columns) -> ImmutableMap.of(UNPARTITIONED_ID, statistics)); + HiveStatisticsProvider statisticsProvider = new AbstractHiveStatisticsProvider() + { + @Override + protected Map getPartitionsStatistics(ConnectorSession session, SchemaTableName table, List hivePartitions, Set columns) + { + return ImmutableMap.of(UNPARTITIONED_ID, statistics); + } + }; HiveColumnHandle columnHandle = createBaseColumn(COLUMN, 2, HIVE_LONG, BIGINT, REGULAR, Optional.empty()); @@ -707,7 +724,14 @@ public void testGetTableStatisticsUnpartitioned() public void testGetTableStatisticsEmpty() { String partitionName = "p1=string1/p2=1234"; - MetastoreHiveStatisticsProvider statisticsProvider = new MetastoreHiveStatisticsProvider((session, table, hivePartitions, columns) -> ImmutableMap.of(partitionName, PartitionStatistics.empty())); + HiveStatisticsProvider statisticsProvider = new AbstractHiveStatisticsProvider() + { + @Override + protected Map getPartitionsStatistics(ConnectorSession session, SchemaTableName table, List hivePartitions, Set columns) + { + return ImmutableMap.of(partitionName, PartitionStatistics.empty()); + } + }; assertEquals( statisticsProvider.getTableStatistics( SESSION, @@ -721,11 +745,15 @@ public void testGetTableStatisticsEmpty() @Test public void testGetTableStatisticsSampling() { - MetastoreHiveStatisticsProvider statisticsProvider = new MetastoreHiveStatisticsProvider((session, table, hivePartitions, columns) -> { - assertEquals(table, TABLE); - assertEquals(hivePartitions.size(), 1); - return ImmutableMap.of(); - }); + HiveStatisticsProvider statisticsProvider = new AbstractHiveStatisticsProvider() { + @Override + protected Map getPartitionsStatistics(ConnectorSession session, SchemaTableName table, List hivePartitions, Set columns) + { + assertEquals(table, TABLE); + assertEquals(hivePartitions.size(), 1); + return ImmutableMap.of(); + } + }; ConnectorSession session = getHiveSession(new HiveConfig() .setPartitionStatisticsSampleSize(1)); statisticsProvider.getTableStatistics( @@ -743,7 +771,14 @@ public void testGetTableStatisticsValidationFailure() .setBasicStatistics(new HiveBasicStatistics(-1, 0, 0, 0)) .build(); String partitionName = "p1=string1/p2=1234"; - MetastoreHiveStatisticsProvider statisticsProvider = new MetastoreHiveStatisticsProvider((session, table, hivePartitions, columns) -> ImmutableMap.of(partitionName, corruptedStatistics)); + HiveStatisticsProvider statisticsProvider = new AbstractHiveStatisticsProvider() + { + @Override + protected Map getPartitionsStatistics(ConnectorSession session, SchemaTableName table, List hivePartitions, Set columns) + { + return ImmutableMap.of(partitionName, corruptedStatistics); + } + }; assertThatThrownBy(() -> statisticsProvider.getTableStatistics( getHiveSession(new HiveConfig().setIgnoreCorruptedStatistics(false)), TABLE, @@ -765,20 +800,32 @@ public void testGetTableStatisticsValidationFailure() @Test public void testEmptyTableStatisticsForPartitionColumnsWhenStatsAreEmpty() { - MetastoreHiveStatisticsProvider statisticsProvider = new MetastoreHiveStatisticsProvider( - (session, table, hivePartitions, columns) -> ImmutableMap.of("p1=string1/p2=1234", PartitionStatistics.empty())); + HiveStatisticsProvider statisticsProvider = new AbstractHiveStatisticsProvider() + { + @Override + protected Map getPartitionsStatistics(ConnectorSession session, SchemaTableName table, List hivePartitions, Set columns) + { + return ImmutableMap.of("p1=string1/p2=1234", PartitionStatistics.empty()); + } + }; testEmptyTableStatisticsForPartitionColumns(statisticsProvider); } @Test public void testEmptyTableStatisticsForPartitionColumnsWhenStatsAreMissing() { - MetastoreHiveStatisticsProvider statisticsProvider = new MetastoreHiveStatisticsProvider( - (session, table, hivePartitions, columns) -> ImmutableMap.of()); + HiveStatisticsProvider statisticsProvider = new AbstractHiveStatisticsProvider() + { + @Override + protected Map getPartitionsStatistics(ConnectorSession session, SchemaTableName table, List hivePartitions, Set columns) + { + return ImmutableMap.of(); + } + }; testEmptyTableStatisticsForPartitionColumns(statisticsProvider); } - private void testEmptyTableStatisticsForPartitionColumns(MetastoreHiveStatisticsProvider statisticsProvider) + private void testEmptyTableStatisticsForPartitionColumns(HiveStatisticsProvider statisticsProvider) { String partitionName1 = "p1=string1/p2=1234"; String partitionName2 = "p1=string2/p2=1235"; From d30ecc3113f023c337e0d5fd4f38648970a6f5bc Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Tue, 14 Nov 2023 12:28:09 +0100 Subject: [PATCH 253/587] Respect Hive statistics_enabled session in Glue metastore Before the change, the Hive Glue metastore wiring would respect the `hive.table-statistics-enabled` configuration property, but not the `statistics_enabled` session property. After the change, handling of stats for Hive Glue metastore is same as for other metastores. That means in particular that some of the `DefaultGlueColumnStatisticsProvider` code will execute implicitly, as not all stats-related operations are guarded by `statistics_enabled` toggle today (see e.g. `SemiTransactionalHiveMetastore` and implicit stats updates after table creation or alteration). This is considered not a blocker. --- .../io/trino/plugin/hive/HiveMetadata.java | 5 ++ .../DisabledGlueColumnStatisticsProvider.java | 69 ------------------- ...edGlueColumnStatisticsProviderFactory.java | 26 ------- .../metastore/glue/GlueMetastoreModule.java | 17 +---- .../MetastoreHiveStatisticsProvider.java | 4 ++ 5 files changed, 11 insertions(+), 110 deletions(-) delete mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/DisabledGlueColumnStatisticsProvider.java delete mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/DisabledGlueColumnStatisticsProviderFactory.java diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java index eaa423967fbb..db6cb8640eb4 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java @@ -1585,6 +1585,8 @@ public ConnectorTableHandle beginStatisticsCollection(ConnectorSession session, @Override public void finishStatisticsCollection(ConnectorSession session, ConnectorTableHandle tableHandle, Collection computedStatistics) { + verify(isStatisticsEnabled(session), "statistics not enabled"); + HiveTableHandle handle = (HiveTableHandle) tableHandle; SchemaTableName tableName = handle.getSchemaTableName(); Table table = metastore.getTable(tableName.getSchemaName(), tableName.getTableName()) @@ -3453,6 +3455,9 @@ public TableStatisticsMetadata getStatisticsCollectionMetadataForWrite(Connector if (!isCollectColumnStatisticsOnWrite(session)) { return TableStatisticsMetadata.empty(); } + if (!isStatisticsEnabled(session)) { + throw new TrinoException(NOT_SUPPORTED, "Table statistics must be enabled when column statistics collection on write is enabled"); + } if (isTransactional(tableMetadata.getProperties()).orElse(false)) { // TODO(https://github.com/trinodb/trino/issues/1956) updating table statistics for transactional not supported right now. return TableStatisticsMetadata.empty(); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/DisabledGlueColumnStatisticsProvider.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/DisabledGlueColumnStatisticsProvider.java deleted file mode 100644 index a0b4024fcb3a..000000000000 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/DisabledGlueColumnStatisticsProvider.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 io.trino.plugin.hive.metastore.glue; - -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import io.trino.plugin.hive.HiveColumnStatisticType; -import io.trino.plugin.hive.metastore.HiveColumnStatistics; -import io.trino.plugin.hive.metastore.Partition; -import io.trino.plugin.hive.metastore.Table; -import io.trino.spi.TrinoException; -import io.trino.spi.type.Type; - -import java.util.Collection; -import java.util.Map; -import java.util.Set; - -import static com.google.common.collect.ImmutableMap.toImmutableMap; -import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; -import static java.util.function.UnaryOperator.identity; - -public class DisabledGlueColumnStatisticsProvider - implements GlueColumnStatisticsProvider -{ - @Override - public Set getSupportedColumnStatistics(Type type) - { - return ImmutableSet.of(); - } - - @Override - public Map getTableColumnStatistics(Table table) - { - return ImmutableMap.of(); - } - - @Override - public Map> getPartitionColumnStatistics(Collection partitions) - { - return partitions.stream().collect(toImmutableMap(identity(), partition -> ImmutableMap.of())); - } - - @Override - public void updateTableColumnStatistics(Table table, Map columnStatistics) - { - if (!columnStatistics.isEmpty()) { - throw new TrinoException(NOT_SUPPORTED, "Glue metastore column level statistics are disabled"); - } - } - - @Override - public void updatePartitionStatistics(Set partitionStatisticsUpdates) - { - if (partitionStatisticsUpdates.stream().anyMatch(update -> !update.getColumnStatistics().isEmpty())) { - throw new TrinoException(NOT_SUPPORTED, "Glue metastore column level statistics are disabled"); - } - } -} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/DisabledGlueColumnStatisticsProviderFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/DisabledGlueColumnStatisticsProviderFactory.java deleted file mode 100644 index 6a06aa5bc33f..000000000000 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/DisabledGlueColumnStatisticsProviderFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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 io.trino.plugin.hive.metastore.glue; - -import com.amazonaws.services.glue.AWSGlueAsync; - -public class DisabledGlueColumnStatisticsProviderFactory - implements GlueColumnStatisticsProviderFactory -{ - @Override - public GlueColumnStatisticsProvider createGlueColumnStatisticsProvider(AWSGlueAsync glueClient, GlueMetastoreStats stats) - { - return new DisabledGlueColumnStatisticsProvider(); - } -} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueMetastoreModule.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueMetastoreModule.java index 75b0e59a1687..751973e8f0a2 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueMetastoreModule.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueMetastoreModule.java @@ -19,7 +19,6 @@ import com.amazonaws.services.glue.model.Table; import com.google.inject.Binder; import com.google.inject.Key; -import com.google.inject.Module; import com.google.inject.Provides; import com.google.inject.Scopes; import com.google.inject.Singleton; @@ -42,7 +41,6 @@ import static com.google.inject.multibindings.Multibinder.newSetBinder; import static com.google.inject.multibindings.OptionalBinder.newOptionalBinder; import static io.airlift.concurrent.Threads.daemonThreadsNamed; -import static io.airlift.configuration.ConditionalModule.conditionalModule; import static io.airlift.configuration.ConfigBinder.configBinder; import static java.util.concurrent.Executors.newCachedThreadPool; import static org.weakref.jmx.guice.ExportBinder.newExporter; @@ -78,19 +76,8 @@ protected void setup(Binder binder) binder.bind(Key.get(boolean.class, AllowHiveTableRename.class)).toInstance(false); - install(conditionalModule( - HiveConfig.class, - HiveConfig::isTableStatisticsEnabled, - getGlueStatisticsModule(DefaultGlueColumnStatisticsProviderFactory.class), - getGlueStatisticsModule(DisabledGlueColumnStatisticsProviderFactory.class))); - } - - private Module getGlueStatisticsModule(Class statisticsProviderFactoryClass) - { - return internalBinder -> newOptionalBinder(internalBinder, GlueColumnStatisticsProviderFactory.class) - .setDefault() - .to(statisticsProviderFactoryClass) - .in(Scopes.SINGLETON); + newOptionalBinder(binder, GlueColumnStatisticsProviderFactory.class) + .setDefault().to(DefaultGlueColumnStatisticsProviderFactory.class).in(Scopes.SINGLETON); } @ProvidesIntoSet diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/statistics/MetastoreHiveStatisticsProvider.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/statistics/MetastoreHiveStatisticsProvider.java index 860eb4162c8d..3d79eed2a4d8 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/statistics/MetastoreHiveStatisticsProvider.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/statistics/MetastoreHiveStatisticsProvider.java @@ -29,6 +29,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static io.trino.plugin.hive.HivePartition.UNPARTITIONED_ID; +import static io.trino.plugin.hive.HiveSessionProperties.isStatisticsEnabled; import static java.util.Objects.requireNonNull; public class MetastoreHiveStatisticsProvider @@ -44,6 +45,9 @@ public MetastoreHiveStatisticsProvider(SemiTransactionalHiveMetastore metastore) @Override protected Map getPartitionsStatistics(ConnectorSession session, SchemaTableName table, List hivePartitions, Set columns) { + if (!isStatisticsEnabled(session)) { + return ImmutableMap.of(); + } if (hivePartitions.isEmpty()) { return ImmutableMap.of(); } From 67f04aae4a018721ae14d4ebf94864a5e5dd38ef Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Tue, 14 Nov 2023 12:08:45 +0100 Subject: [PATCH 254/587] Remove HiveConfig binding from Delta and Iceberg with Glue Delta Lake and Iceberg connectors do not use configuration from `HiveConfig` and so the `HiveConfig`-provided configuration should not be accepted when configuring the connector. --- .../plugin/deltalake/InternalDeltaLakeConnectorFactory.java | 4 ++++ .../trino/plugin/hive/metastore/glue/GlueMetastoreModule.java | 3 --- .../trino/plugin/iceberg/InternalIcebergConnectorFactory.java | 4 ++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/InternalDeltaLakeConnectorFactory.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/InternalDeltaLakeConnectorFactory.java index 2f39dbbd64ab..254cd158bc73 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/InternalDeltaLakeConnectorFactory.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/InternalDeltaLakeConnectorFactory.java @@ -38,6 +38,7 @@ import io.trino.plugin.base.jmx.MBeanServerModule; import io.trino.plugin.base.session.SessionPropertiesProvider; import io.trino.plugin.deltalake.metastore.DeltaLakeMetastoreModule; +import io.trino.plugin.hive.HiveConfig; import io.trino.plugin.hive.NodeVersion; import io.trino.spi.NodeManager; import io.trino.spi.PageIndexerFactory; @@ -61,6 +62,7 @@ import java.util.Optional; import java.util.Set; +import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.inject.multibindings.Multibinder.newSetBinder; @@ -135,6 +137,8 @@ public static Connector createConnector( Set connectorTableFunctions = injector.getInstance(Key.get(new TypeLiteral>() {})); FunctionProvider functionProvider = injector.getInstance(FunctionProvider.class); + checkState(!injector.getBindings().containsKey(Key.get(HiveConfig.class)), "HiveConfig should not be bound"); + return new DeltaLakeConnector( injector, lifeCycleManager, diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueMetastoreModule.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueMetastoreModule.java index 751973e8f0a2..ea4e4cb1e6da 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueMetastoreModule.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueMetastoreModule.java @@ -30,7 +30,6 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.awssdk.v1_11.AwsSdkTelemetry; import io.trino.plugin.hive.AllowHiveTableRename; -import io.trino.plugin.hive.HiveConfig; import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.plugin.hive.metastore.RawHiveMetastoreFactory; @@ -41,7 +40,6 @@ import static com.google.inject.multibindings.Multibinder.newSetBinder; import static com.google.inject.multibindings.OptionalBinder.newOptionalBinder; import static io.airlift.concurrent.Threads.daemonThreadsNamed; -import static io.airlift.configuration.ConfigBinder.configBinder; import static java.util.concurrent.Executors.newCachedThreadPool; import static org.weakref.jmx.guice.ExportBinder.newExporter; @@ -56,7 +54,6 @@ protected void setup(Binder binder) glueConfig.getCatalogId().ifPresent(catalogId -> requestHandlers.addBinding().toInstance(new GlueCatalogIdRequestHandler(catalogId))); glueConfig.getGlueProxyApiId().ifPresent(glueProxyApiId -> requestHandlers.addBinding() .toInstance(new ProxyApiRequestHandler(glueProxyApiId))); - configBinder(binder).bindConfig(HiveConfig.class); binder.bind(AWSCredentialsProvider.class).toProvider(GlueCredentialsProvider.class).in(Scopes.SINGLETON); newOptionalBinder(binder, Key.get(new TypeLiteral>() {}, ForGlueHiveMetastore.class)) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/InternalIcebergConnectorFactory.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/InternalIcebergConnectorFactory.java index 0ebc1bd4378b..6e05341bc096 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/InternalIcebergConnectorFactory.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/InternalIcebergConnectorFactory.java @@ -33,6 +33,7 @@ import io.trino.plugin.base.jmx.ConnectorObjectNameGeneratorModule; import io.trino.plugin.base.jmx.MBeanServerModule; import io.trino.plugin.base.session.SessionPropertiesProvider; +import io.trino.plugin.hive.HiveConfig; import io.trino.plugin.hive.NodeVersion; import io.trino.plugin.iceberg.catalog.IcebergCatalogModule; import io.trino.spi.NodeManager; @@ -62,6 +63,7 @@ import java.util.Set; import java.util.stream.Stream; +import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableList.toImmutableList; public final class InternalIcebergConnectorFactory @@ -128,6 +130,8 @@ public static Connector createConnector( .flatMap(Collection::stream) .collect(toImmutableList()); + checkState(!injector.getBindings().containsKey(Key.get(HiveConfig.class)), "HiveConfig should not be bound"); + return new IcebergConnector( injector, lifeCycleManager, From c52f31c131128a1745d6b3d6b5f2259c40b50ae1 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 15 Nov 2023 09:54:09 +0100 Subject: [PATCH 255/587] Deprecate old ConstraintApplicationResult constructor The new `ConnectorExpression`-based API was introduced long ago and is stable. --- .../InformationSchemaMetadata.java | 2 +- .../connector/system/SystemTablesMetadata.java | 2 +- .../TestTableScanRedirectionWithPushdown.java | 1 + .../rule/TestPushPredicateIntoTableScan.java | 4 ++-- .../optimizations/TestRemoveEmptyUnionBranches.java | 2 ++ .../spi/connector/ConstraintApplicationResult.java | 3 +++ .../io/trino/plugin/accumulo/AccumuloMetadata.java | 2 +- .../java/io/trino/plugin/atop/AtopMetadata.java | 2 +- .../io/trino/plugin/jdbc/DefaultJdbcMetadata.java | 13 +++++-------- .../io/trino/plugin/bigquery/BigQueryMetadata.java | 2 +- .../trino/plugin/cassandra/CassandraMetadata.java | 1 + .../trino/plugin/deltalake/DeltaLakeMetadata.java | 1 + .../java/io/trino/plugin/hive/HiveMetadata.java | 2 +- .../java/io/trino/plugin/hudi/HudiMetadata.java | 1 + .../main/java/io/trino/plugin/jmx/JmxMetadata.java | 2 +- .../java/io/trino/plugin/kafka/KafkaMetadata.java | 2 +- .../java/io/trino/plugin/kudu/KuduMetadata.java | 2 +- .../trino/plugin/localfile/LocalFileMetadata.java | 2 +- .../java/io/trino/plugin/mongodb/MongoMetadata.java | 2 +- .../java/io/trino/plugin/pinot/PinotMetadata.java | 2 +- .../trino/plugin/prometheus/PrometheusMetadata.java | 2 +- .../trino/plugin/raptor/legacy/RaptorMetadata.java | 1 + .../java/io/trino/plugin/redis/RedisMetadata.java | 2 +- .../java/io/trino/plugin/thrift/ThriftMetadata.java | 2 +- .../java/io/trino/plugin/tpch/TpchMetadata.java | 1 + 25 files changed, 33 insertions(+), 25 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/connector/informationschema/InformationSchemaMetadata.java b/core/trino-main/src/main/java/io/trino/connector/informationschema/InformationSchemaMetadata.java index 24e4ad501976..14c8bea286c7 100644 --- a/core/trino-main/src/main/java/io/trino/connector/informationschema/InformationSchemaMetadata.java +++ b/core/trino-main/src/main/java/io/trino/connector/informationschema/InformationSchemaMetadata.java @@ -196,7 +196,7 @@ public Optional> applyFilter(C } table = new InformationSchemaTableHandle(table.getCatalogName(), table.getTable(), prefixes, table.getLimit()); - return Optional.of(new ConstraintApplicationResult<>(table, constraint.getSummary(), false)); + return Optional.of(new ConstraintApplicationResult<>(table, constraint.getSummary(), constraint.getExpression(), false)); } public static Set defaultPrefixes(String catalogName) diff --git a/core/trino-main/src/main/java/io/trino/connector/system/SystemTablesMetadata.java b/core/trino-main/src/main/java/io/trino/connector/system/SystemTablesMetadata.java index 98228ac46f32..82da42b69658 100644 --- a/core/trino-main/src/main/java/io/trino/connector/system/SystemTablesMetadata.java +++ b/core/trino-main/src/main/java/io/trino/connector/system/SystemTablesMetadata.java @@ -163,7 +163,7 @@ public Optional> applyFilter(C // TODO (https://github.com/trinodb/trino/issues/3647) indicate the table scan is empty } table = new SystemTableHandle(table.getSchemaName(), table.getTableName(), newDomain); - return Optional.of(new ConstraintApplicationResult<>(table, constraint.getSummary(), false)); + return Optional.of(new ConstraintApplicationResult<>(table, constraint.getSummary(), constraint.getExpression(), false)); } private Constraint effectiveConstraint(TupleDomain oldDomain, Constraint newConstraint, TupleDomain effectiveDomain) diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestTableScanRedirectionWithPushdown.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestTableScanRedirectionWithPushdown.java index ca4f7dbdb818..e407895854af 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestTableScanRedirectionWithPushdown.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestTableScanRedirectionWithPushdown.java @@ -442,6 +442,7 @@ private ApplyFilter getMockApplyFilter(Set pushdownColumns) new MockConnectorTableHandle(handle.getTableName(), newDomain, Optional.empty()), constraint.getSummary() .filter((columnHandle, domain) -> !pushdownColumns.contains(columnHandle)), + constraint.getExpression(), false)); }; } diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestPushPredicateIntoTableScan.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestPushPredicateIntoTableScan.java index d2c98927016f..b5713a02e2d9 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestPushPredicateIntoTableScan.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestPushPredicateIntoTableScan.java @@ -412,10 +412,10 @@ public static MockConnectorFactory createMockFactory() builder .withApplyFilter((session, tableHandle, constraint) -> { if (tableHandle.equals(CONNECTOR_PARTITIONED_TABLE_HANDLE_TO_UNPARTITIONED)) { - return Optional.of(new ConstraintApplicationResult<>(CONNECTOR_UNPARTITIONED_TABLE_HANDLE, TupleDomain.all(), false)); + return Optional.of(new ConstraintApplicationResult<>(CONNECTOR_UNPARTITIONED_TABLE_HANDLE, TupleDomain.all(), constraint.getExpression(), false)); } if (tableHandle.equals(CONNECTOR_PARTITIONED_TABLE_HANDLE)) { - return Optional.of(new ConstraintApplicationResult<>(CONNECTOR_PARTITIONED_TABLE_HANDLE, TupleDomain.all(), false)); + return Optional.of(new ConstraintApplicationResult<>(CONNECTOR_PARTITIONED_TABLE_HANDLE, TupleDomain.all(), constraint.getExpression(), false)); } return Optional.empty(); }) diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestRemoveEmptyUnionBranches.java b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestRemoveEmptyUnionBranches.java index 5d0db81f6c6f..8168ea6bb09e 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestRemoveEmptyUnionBranches.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestRemoveEmptyUnionBranches.java @@ -148,6 +148,7 @@ private MockConnectorFactory.ApplyFilter applyFilter() new MockConnectorTableHandle(handle.getTableName(), TupleDomain.none(), Optional.empty()), constraint.getSummary() .filter((ch, domain) -> !shouldPushdown.test(ch)), + constraint.getExpression(), false)); } @@ -160,6 +161,7 @@ private MockConnectorFactory.ApplyFilter applyFilter() new MockConnectorTableHandle(handle.getTableName(), newDomain, Optional.empty()), constraint.getSummary() .filter((columnHandle, domain) -> !shouldPushdown.test(columnHandle)), + constraint.getExpression(), false)); } } diff --git a/core/trino-spi/src/main/java/io/trino/spi/connector/ConstraintApplicationResult.java b/core/trino-spi/src/main/java/io/trino/spi/connector/ConstraintApplicationResult.java index 04dd3122d5c1..c09b39f1c3e9 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/connector/ConstraintApplicationResult.java +++ b/core/trino-spi/src/main/java/io/trino/spi/connector/ConstraintApplicationResult.java @@ -31,7 +31,10 @@ public class ConstraintApplicationResult /** * @param precalculateStatistics Indicates whether engine should consider calculating statistics based on the plan before pushdown, * as the connector may be unable to provide good table statistics for {@code handle}. + * + * @deprecated Use {@link #ConstraintApplicationResult(Object, TupleDomain, ConnectorExpression, boolean)}. */ + @Deprecated public ConstraintApplicationResult(T handle, TupleDomain remainingFilter, boolean precalculateStatistics) { this(handle, remainingFilter, Optional.empty(), precalculateStatistics); diff --git a/plugin/trino-accumulo/src/main/java/io/trino/plugin/accumulo/AccumuloMetadata.java b/plugin/trino-accumulo/src/main/java/io/trino/plugin/accumulo/AccumuloMetadata.java index 14902550a85d..5920b4effb97 100644 --- a/plugin/trino-accumulo/src/main/java/io/trino/plugin/accumulo/AccumuloMetadata.java +++ b/plugin/trino-accumulo/src/main/java/io/trino/plugin/accumulo/AccumuloMetadata.java @@ -387,7 +387,7 @@ public Optional> applyFilter(C handle.getSerializerClassName(), handle.getScanAuthorizations()); - return Optional.of(new ConstraintApplicationResult<>(handle, constraint.getSummary(), false)); + return Optional.of(new ConstraintApplicationResult<>(handle, constraint.getSummary(), constraint.getExpression(), false)); } private void checkNoRollback() diff --git a/plugin/trino-atop/src/main/java/io/trino/plugin/atop/AtopMetadata.java b/plugin/trino-atop/src/main/java/io/trino/plugin/atop/AtopMetadata.java index b5065f82ce7a..368ae3bcd23c 100644 --- a/plugin/trino-atop/src/main/java/io/trino/plugin/atop/AtopMetadata.java +++ b/plugin/trino-atop/src/main/java/io/trino/plugin/atop/AtopMetadata.java @@ -176,6 +176,6 @@ public Optional> applyFilter(C newStartTimeDomain, newEndTimeDomain); - return Optional.of(new ConstraintApplicationResult<>(handle, constraint.getSummary(), false)); + return Optional.of(new ConstraintApplicationResult<>(handle, constraint.getSummary(), constraint.getExpression(), false)); } } diff --git a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadata.java b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadata.java index 47762fc13c5e..13c2e7f75fd8 100644 --- a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadata.java +++ b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadata.java @@ -179,11 +179,11 @@ public Optional> applyFilter(C TupleDomain newDomain = oldDomain.intersect(constraint.getSummary()); List newConstraintExpressions; TupleDomain remainingFilter; - Optional remainingExpression; + ConnectorExpression remainingExpression; if (newDomain.isNone()) { newConstraintExpressions = ImmutableList.of(); remainingFilter = TupleDomain.all(); - remainingExpression = Optional.of(Constant.TRUE); + remainingExpression = Constant.TRUE; } else { Map domains = newDomain.getDomains().orElseThrow(); @@ -225,11 +225,11 @@ public Optional> applyFilter(C .addAll(handle.getConstraintExpressions()) .addAll(newExpressions) .build().asList(); - remainingExpression = Optional.of(and(remainingExpressions)); + remainingExpression = and(remainingExpressions); } else { newConstraintExpressions = ImmutableList.of(); - remainingExpression = Optional.empty(); + remainingExpression = constraint.getExpression(); } } @@ -250,10 +250,7 @@ public Optional> applyFilter(C handle.getAuthorization(), handle.getUpdateAssignments()); - return Optional.of( - remainingExpression.isPresent() - ? new ConstraintApplicationResult<>(handle, remainingFilter, remainingExpression.get(), precalculateStatisticsForPushdown) - : new ConstraintApplicationResult<>(handle, remainingFilter, precalculateStatisticsForPushdown)); + return Optional.of(new ConstraintApplicationResult<>(handle, remainingFilter, remainingExpression, precalculateStatisticsForPushdown)); } private JdbcTableHandle flushAttributesAsQuery(ConnectorSession session, JdbcTableHandle handle) diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryMetadata.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryMetadata.java index 14a0657e9d74..467693bb2051 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryMetadata.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryMetadata.java @@ -788,7 +788,7 @@ public Optional> applyFilter( BigQueryTableHandle updatedHandle = bigQueryTableHandle.withConstraint(newDomain); - return Optional.of(new ConstraintApplicationResult<>(updatedHandle, constraint.getSummary(), false)); + return Optional.of(new ConstraintApplicationResult<>(updatedHandle, constraint.getSummary(), constraint.getExpression(), false)); } @Override diff --git a/plugin/trino-cassandra/src/main/java/io/trino/plugin/cassandra/CassandraMetadata.java b/plugin/trino-cassandra/src/main/java/io/trino/plugin/cassandra/CassandraMetadata.java index de4b76875ae7..0241cc9880f6 100644 --- a/plugin/trino-cassandra/src/main/java/io/trino/plugin/cassandra/CassandraMetadata.java +++ b/plugin/trino-cassandra/src/main/java/io/trino/plugin/cassandra/CassandraMetadata.java @@ -273,6 +273,7 @@ public Optional> applyFilter(C // TODO this should probably be AND-ed with handle.getClusteringKeyPredicates() clusteringKeyPredicates)), unenforcedConstraint, + constraint.getExpression(), false)); } diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java index a0b96a2de5c1..344d81a6d868 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java @@ -2753,6 +2753,7 @@ public Optional> applyFilter(C return Optional.of(new ConstraintApplicationResult<>( newHandle, newUnenforcedConstraint.transformKeys(ColumnHandle.class::cast), + constraint.getExpression(), false)); } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java index db6cb8640eb4..112f41e4f63f 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java @@ -2997,7 +2997,7 @@ public Optional> applyFilter(C unenforcedConstraint = partitionResult.getEffectivePredicate().filter((column, domain) -> !partitionColumns.contains(column)); } - return Optional.of(new ConstraintApplicationResult<>(newHandle, unenforcedConstraint, false)); + return Optional.of(new ConstraintApplicationResult<>(newHandle, unenforcedConstraint, constraint.getExpression(), false)); } @Override diff --git a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiMetadata.java b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiMetadata.java index 709e7439862c..fbbc5e09cb4e 100644 --- a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiMetadata.java +++ b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiMetadata.java @@ -174,6 +174,7 @@ public Optional> applyFilter(C return Optional.of(new ConstraintApplicationResult<>( newHudiTableHandle, newHudiTableHandle.getRegularPredicates().transformKeys(ColumnHandle.class::cast), + constraint.getExpression(), false)); } diff --git a/plugin/trino-jmx/src/main/java/io/trino/plugin/jmx/JmxMetadata.java b/plugin/trino-jmx/src/main/java/io/trino/plugin/jmx/JmxMetadata.java index 43efd522e81d..6f6990b37e29 100644 --- a/plugin/trino-jmx/src/main/java/io/trino/plugin/jmx/JmxMetadata.java +++ b/plugin/trino-jmx/src/main/java/io/trino/plugin/jmx/JmxMetadata.java @@ -287,7 +287,7 @@ public Optional> applyFilter(C JmxTableHandle newTableHandle = new JmxTableHandle(tableHandle.getTableName(), tableHandle.getObjectNames(), tableHandle.getColumnHandles(), tableHandle.isLiveData(), newDomain); - return Optional.of(new ConstraintApplicationResult<>(newTableHandle, TupleDomain.withColumnDomains(otherDomains), false)); + return Optional.of(new ConstraintApplicationResult<>(newTableHandle, TupleDomain.withColumnDomains(otherDomains), constraint.getExpression(), false)); } private static Type getColumnType(MBeanAttributeInfo attribute) diff --git a/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/KafkaMetadata.java b/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/KafkaMetadata.java index aa7af3535170..c7f1a02492fb 100644 --- a/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/KafkaMetadata.java +++ b/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/KafkaMetadata.java @@ -252,7 +252,7 @@ public Optional> applyFilter(C handle.getColumns(), newDomain); - return Optional.of(new ConstraintApplicationResult<>(handle, constraint.getSummary(), false)); + return Optional.of(new ConstraintApplicationResult<>(handle, constraint.getSummary(), constraint.getExpression(), false)); } private KafkaTopicDescription getRequiredTopicDescription(ConnectorSession session, SchemaTableName schemaTableName) diff --git a/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/KuduMetadata.java b/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/KuduMetadata.java index e8ac4922ebe0..4e16c1441e31 100755 --- a/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/KuduMetadata.java +++ b/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/KuduMetadata.java @@ -462,7 +462,7 @@ public Optional> applyFilter(C handle.getBucketCount(), handle.getLimit()); - return Optional.of(new ConstraintApplicationResult<>(handle, constraint.getSummary(), false)); + return Optional.of(new ConstraintApplicationResult<>(handle, constraint.getSummary(), constraint.getExpression(), false)); } /** diff --git a/plugin/trino-local-file/src/main/java/io/trino/plugin/localfile/LocalFileMetadata.java b/plugin/trino-local-file/src/main/java/io/trino/plugin/localfile/LocalFileMetadata.java index 44eac2de4e5b..9d0a03f9f8ef 100644 --- a/plugin/trino-local-file/src/main/java/io/trino/plugin/localfile/LocalFileMetadata.java +++ b/plugin/trino-local-file/src/main/java/io/trino/plugin/localfile/LocalFileMetadata.java @@ -148,6 +148,6 @@ public Optional> applyFilter(C handle.getServerAddressColumn(), newDomain); - return Optional.of(new ConstraintApplicationResult<>(handle, constraint.getSummary(), false)); + return Optional.of(new ConstraintApplicationResult<>(handle, constraint.getSummary(), constraint.getExpression(), false)); } } diff --git a/plugin/trino-mongodb/src/main/java/io/trino/plugin/mongodb/MongoMetadata.java b/plugin/trino-mongodb/src/main/java/io/trino/plugin/mongodb/MongoMetadata.java index 37ca08535c28..f5b10627271e 100644 --- a/plugin/trino-mongodb/src/main/java/io/trino/plugin/mongodb/MongoMetadata.java +++ b/plugin/trino-mongodb/src/main/java/io/trino/plugin/mongodb/MongoMetadata.java @@ -631,7 +631,7 @@ public Optional> applyFilter(C handle.getProjectedColumns(), handle.getLimit()); - return Optional.of(new ConstraintApplicationResult<>(handle, remainingFilter, false)); + return Optional.of(new ConstraintApplicationResult<>(handle, remainingFilter, constraint.getExpression(), false)); } @Override diff --git a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/PinotMetadata.java b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/PinotMetadata.java index 78a6b48a48dc..f0f3eaef8267 100755 --- a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/PinotMetadata.java +++ b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/PinotMetadata.java @@ -317,7 +317,7 @@ else if (isFilterPushdownUnsupported(entry.getValue())) { newDomain, handle.getLimit(), handle.getQuery()); - return Optional.of(new ConstraintApplicationResult<>(handle, remainingFilter, false)); + return Optional.of(new ConstraintApplicationResult<>(handle, remainingFilter, constraint.getExpression(), false)); } // IS NULL and IS NOT NULL are handled differently in Pinot, pushing down would lead to inconsistent results. diff --git a/plugin/trino-prometheus/src/main/java/io/trino/plugin/prometheus/PrometheusMetadata.java b/plugin/trino-prometheus/src/main/java/io/trino/plugin/prometheus/PrometheusMetadata.java index b1b5e828f218..3adcbc2097cd 100644 --- a/plugin/trino-prometheus/src/main/java/io/trino/plugin/prometheus/PrometheusMetadata.java +++ b/plugin/trino-prometheus/src/main/java/io/trino/plugin/prometheus/PrometheusMetadata.java @@ -158,6 +158,6 @@ public Optional> applyFilter(C { PrometheusTableHandle tableHandle = ((PrometheusTableHandle) handle) .withPredicate(constraint.getSummary()); - return Optional.of(new ConstraintApplicationResult<>(tableHandle, constraint.getSummary(), false)); + return Optional.of(new ConstraintApplicationResult<>(tableHandle, constraint.getSummary(), constraint.getExpression(), false)); } } diff --git a/plugin/trino-raptor-legacy/src/main/java/io/trino/plugin/raptor/legacy/RaptorMetadata.java b/plugin/trino-raptor-legacy/src/main/java/io/trino/plugin/raptor/legacy/RaptorMetadata.java index 4b9268970c17..ac9012779830 100644 --- a/plugin/trino-raptor-legacy/src/main/java/io/trino/plugin/raptor/legacy/RaptorMetadata.java +++ b/plugin/trino-raptor-legacy/src/main/java/io/trino/plugin/raptor/legacy/RaptorMetadata.java @@ -327,6 +327,7 @@ public Optional> applyFilter(C newDomain.intersect(table.getConstraint()), table.getBucketAssignments()), constraint.getSummary(), + constraint.getExpression(), false)); } diff --git a/plugin/trino-redis/src/main/java/io/trino/plugin/redis/RedisMetadata.java b/plugin/trino-redis/src/main/java/io/trino/plugin/redis/RedisMetadata.java index ead3c6e30fa1..3fa639caece2 100644 --- a/plugin/trino-redis/src/main/java/io/trino/plugin/redis/RedisMetadata.java +++ b/plugin/trino-redis/src/main/java/io/trino/plugin/redis/RedisMetadata.java @@ -240,7 +240,7 @@ else if (getUserDefinedKeySize(session, handle) > 1) { handle.getKeyName(), newDomain); - return Optional.of(new ConstraintApplicationResult<>(handle, remainingFilter, false)); + return Optional.of(new ConstraintApplicationResult<>(handle, remainingFilter, constraint.getExpression(), false)); } @Override diff --git a/plugin/trino-thrift/src/main/java/io/trino/plugin/thrift/ThriftMetadata.java b/plugin/trino-thrift/src/main/java/io/trino/plugin/thrift/ThriftMetadata.java index edaf3290ff33..286d9354354e 100644 --- a/plugin/trino-thrift/src/main/java/io/trino/plugin/thrift/ThriftMetadata.java +++ b/plugin/trino-thrift/src/main/java/io/trino/plugin/thrift/ThriftMetadata.java @@ -179,7 +179,7 @@ public Optional> applyFilter(C newDomain, handle.getDesiredColumns()); - return Optional.of(new ConstraintApplicationResult<>(handle, constraint.getSummary(), false)); + return Optional.of(new ConstraintApplicationResult<>(handle, constraint.getSummary(), constraint.getExpression(), false)); } @Override diff --git a/plugin/trino-tpch/src/main/java/io/trino/plugin/tpch/TpchMetadata.java b/plugin/trino-tpch/src/main/java/io/trino/plugin/tpch/TpchMetadata.java index f529842f0c0e..f1aa34153273 100644 --- a/plugin/trino-tpch/src/main/java/io/trino/plugin/tpch/TpchMetadata.java +++ b/plugin/trino-tpch/src/main/java/io/trino/plugin/tpch/TpchMetadata.java @@ -505,6 +505,7 @@ else if (predicatePushdownEnabled && handle.getTableName().equals(TpchTable.PART handle.getScaleFactor(), oldDomain.intersect(predicate)), unenforcedConstraint, + constraint.getExpression(), false)); } From 062e30bd344377146229cadf1708d21d26dceed8 Mon Sep 17 00:00:00 2001 From: James Petty Date: Tue, 14 Nov 2023 13:21:41 -0500 Subject: [PATCH 256/587] Shutdown executor after TestBufferingSplitSource --- .../trino/split/TestBufferingSplitSource.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/core/trino-main/src/test/java/io/trino/split/TestBufferingSplitSource.java b/core/trino-main/src/test/java/io/trino/split/TestBufferingSplitSource.java index 9356c5d4447e..8497ffb14adf 100644 --- a/core/trino-main/src/test/java/io/trino/split/TestBufferingSplitSource.java +++ b/core/trino-main/src/test/java/io/trino/split/TestBufferingSplitSource.java @@ -17,9 +17,14 @@ import com.google.common.util.concurrent.ListenableFuture; import io.airlift.concurrent.BoundedExecutor; import io.trino.split.SplitSource.SplitBatch; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static io.airlift.concurrent.MoreFutures.getFutureValue; @@ -29,13 +34,22 @@ import static java.util.Objects.requireNonNull; import static java.util.concurrent.Executors.newCachedThreadPool; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@Execution(ExecutionMode.CONCURRENT) public class TestBufferingSplitSource { - private static final Executor executor = new BoundedExecutor(newCachedThreadPool(daemonThreadsNamed(TestBufferingSplitSource.class.getSimpleName() + "-%s")), 10); + private final ExecutorService executorService = newCachedThreadPool(daemonThreadsNamed(TestBufferingSplitSource.class.getSimpleName() + "-%s")); + private final Executor executor = new BoundedExecutor(executorService, 10); + + @AfterAll + public void cleanup() + { + executorService.shutdown(); + } @Test public void testSlowSource() From 14b33c7579d070316b49e0ba18001e8206004a04 Mon Sep 17 00:00:00 2001 From: James Petty Date: Tue, 14 Nov 2023 13:43:30 -0500 Subject: [PATCH 257/587] Shutdown SplitManager executor during cleanup --- .../src/main/java/io/trino/split/SplitManager.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/core/trino-main/src/main/java/io/trino/split/SplitManager.java b/core/trino-main/src/main/java/io/trino/split/SplitManager.java index 70b388235b2c..4ec43a8670a7 100644 --- a/core/trino-main/src/main/java/io/trino/split/SplitManager.java +++ b/core/trino-main/src/main/java/io/trino/split/SplitManager.java @@ -30,9 +30,11 @@ import io.trino.spi.connector.Constraint; import io.trino.spi.connector.DynamicFilter; import io.trino.tracing.TrinoAttributes; +import jakarta.annotation.PreDestroy; import java.util.Optional; import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; import static io.airlift.concurrent.Threads.daemonThreadsNamed; import static io.trino.SystemSessionProperties.isAllowPushdownIntoConnectors; @@ -44,6 +46,7 @@ public class SplitManager private final CatalogServiceProvider splitManagerProvider; private final Tracer tracer; private final int minScheduleSplitBatchSize; + private final ExecutorService executorService; private final Executor executor; @Inject @@ -52,7 +55,14 @@ public SplitManager(CatalogServiceProvider splitManagerPr this.splitManagerProvider = requireNonNull(splitManagerProvider, "splitManagerProvider is null"); this.tracer = requireNonNull(tracer, "tracer is null"); this.minScheduleSplitBatchSize = config.getMinScheduleSplitBatchSize(); - this.executor = new BoundedExecutor(newCachedThreadPool(daemonThreadsNamed("splits-manager-callback-%s")), config.getMaxSplitManagerCallbackThreads()); + this.executorService = newCachedThreadPool(daemonThreadsNamed("splits-manager-callback-%s")); + this.executor = new BoundedExecutor(executorService, config.getMaxSplitManagerCallbackThreads()); + } + + @PreDestroy + public void shutdown() + { + executorService.shutdown(); } public SplitSource getSplits( From acb6fd1015bf022f0fbd33ceec7554b45d039afc Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Tue, 14 Nov 2023 13:30:18 -0800 Subject: [PATCH 258/587] Remove unnecessary data provider --- .../io/trino/execution/TestNodeScheduler.java | 35 +++-- .../hive/coercions/TestDecimalCoercers.java | 66 +++++----- ...TestTrinoS3FileSystemAccessOperations.java | 120 +++++++++--------- .../BaseIcebergMaterializedViewTest.java | 90 ++++++------- .../plugin/ignite/TestIgniteTypeMapping.java | 42 +++--- .../plugin/kafka/TestKafkaSecurityConfig.java | 32 ++--- ...TestMillisecondsJsonDateTimeFormatter.java | 56 ++++---- .../TestSecondsJsonDateTimeFormatter.java | 58 ++++----- .../plugin/kinesis/TestRecordAccess.java | 22 ++-- .../mariadb/TestMariaDbTypeMapping.java | 58 +++++---- 10 files changed, 268 insertions(+), 311 deletions(-) diff --git a/core/trino-main/src/test/java/io/trino/execution/TestNodeScheduler.java b/core/trino-main/src/test/java/io/trino/execution/TestNodeScheduler.java index 4d35bf58773d..e6a1caa32fd4 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestNodeScheduler.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestNodeScheduler.java @@ -49,7 +49,6 @@ import io.trino.util.FinalizerService; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.net.InetAddress; @@ -652,27 +651,27 @@ public void testEquateDistribution() assertEquals(assignment.get(node4).size(), 4); } - @DataProvider - public static Object[][] equateDistributionTestParameters() + @Test + public void testEquateDistributionConsistentHashing() { - return new Object[][] { - {5, 10, 0.00}, - {5, 20, 0.055}, - {10, 50, 0.00}, - {10, 100, 0.045}, - {10, 200, 0.090}, - {50, 550, 0.045}, - {50, 600, 0.047}, - {50, 700, 0.045}, - {100, 550, 0.036}, - {100, 600, 0.054}, - {100, 1000, 0.039}, - {100, 1500, 0.045}}; + testEquateDistributionConsistentHashing(5, 10, 0.00); + testEquateDistributionConsistentHashing(5, 20, 0.055); + testEquateDistributionConsistentHashing(10, 50, 0.00); + testEquateDistributionConsistentHashing(10, 100, 0.045); + testEquateDistributionConsistentHashing(10, 200, 0.090); + testEquateDistributionConsistentHashing(50, 550, 0.045); + testEquateDistributionConsistentHashing(50, 600, 0.047); + testEquateDistributionConsistentHashing(50, 700, 0.045); + testEquateDistributionConsistentHashing(100, 550, 0.036); + testEquateDistributionConsistentHashing(100, 600, 0.054); + testEquateDistributionConsistentHashing(100, 1000, 0.039); + testEquateDistributionConsistentHashing(100, 1500, 0.045); } - @Test(dataProvider = "equateDistributionTestParameters") - public void testEquateDistributionConsistentHashing(int numberOfNodes, int numberOfSplits, double misassignedSplitsRatio) + private void testEquateDistributionConsistentHashing(int numberOfNodes, int numberOfSplits, double misassignedSplitsRatio) { + setUp(); + ImmutableList.Builder nodesBuilder = ImmutableList.builder(); for (int i = 0; i < numberOfNodes; ++i) { InternalNode node = new InternalNode("node" + i, URI.create("http://10.0.0.1:" + (i + 10)), NodeVersion.UNKNOWN, false); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestDecimalCoercers.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestDecimalCoercers.java index dbd7b87cb697..4e08b96884f2 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestDecimalCoercers.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestDecimalCoercers.java @@ -17,7 +17,6 @@ import io.trino.spi.type.DecimalParseResult; import io.trino.spi.type.Decimals; import io.trino.spi.type.Type; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import static io.trino.plugin.hive.HiveTimestampPrecision.NANOSECONDS; @@ -34,8 +33,37 @@ public class TestDecimalCoercers { - @Test(dataProvider = "dataProvider") - public void testDecimalToIntCoercion(String decimalString, Type coercedType, Object expectedValue) + @Test + public void testDecimalToIntCoercion() + { + testDecimalToIntCoercion("12.120000000000000000", TINYINT, 12L); + testDecimalToIntCoercion("-12.120000000000000000", TINYINT, -12L); + testDecimalToIntCoercion("12.120", TINYINT, 12L); + testDecimalToIntCoercion("-12.120", TINYINT, -12L); + testDecimalToIntCoercion("141.120000000000000000", TINYINT, null); + testDecimalToIntCoercion("-141.120", TINYINT, null); + testDecimalToIntCoercion("130.120000000000000000", SMALLINT, 130L); + testDecimalToIntCoercion("-130.120000000000000000", SMALLINT, -130L); + testDecimalToIntCoercion("130.120", SMALLINT, 130L); + testDecimalToIntCoercion("-130.120", SMALLINT, -130L); + testDecimalToIntCoercion("66000.30120000000000000", SMALLINT, null); + testDecimalToIntCoercion("-66000.120", SMALLINT, null); + testDecimalToIntCoercion("33000.12000000000000000", INTEGER, 33000L); + testDecimalToIntCoercion("-33000.12000000000000000", INTEGER, -33000L); + testDecimalToIntCoercion("33000.120", INTEGER, 33000L); + testDecimalToIntCoercion("-33000.120", INTEGER, -33000L); + testDecimalToIntCoercion("3300000000.1200000000000", INTEGER, null); + testDecimalToIntCoercion("3300000000.120", INTEGER, null); + testDecimalToIntCoercion("3300000000.1200000000000", BIGINT, 3300000000L); + testDecimalToIntCoercion("-3300000000.120000000000", BIGINT, -3300000000L); + testDecimalToIntCoercion("3300000000.12", BIGINT, 3300000000L); + testDecimalToIntCoercion("-3300000000.12", BIGINT, -3300000000L); + testDecimalToIntCoercion("330000000000000000000.12000000000", BIGINT, null); + testDecimalToIntCoercion("-330000000000000000000.12000000000", BIGINT, null); + testDecimalToIntCoercion("3300000", INTEGER, 3300000L); + } + + private void testDecimalToIntCoercion(String decimalString, Type coercedType, Object expectedValue) { DecimalParseResult parseResult = Decimals.parse(decimalString); @@ -48,38 +76,6 @@ public void testDecimalToIntCoercion(String decimalString, Type coercedType, Obj assertDecimalToIntCoercion(parseResult.getType(), parseResult.getObject(), coercedType, expectedValue); } - @DataProvider - public static Object[][] dataProvider() - { - return new Object[][] { - {"12.120000000000000000", TINYINT, 12L}, - {"-12.120000000000000000", TINYINT, -12L}, - {"12.120", TINYINT, 12L}, - {"-12.120", TINYINT, -12L}, - {"141.120000000000000000", TINYINT, null}, - {"-141.120", TINYINT, null}, - {"130.120000000000000000", SMALLINT, 130L}, - {"-130.120000000000000000", SMALLINT, -130L}, - {"130.120", SMALLINT, 130L}, - {"-130.120", SMALLINT, -130L}, - {"66000.30120000000000000", SMALLINT, null}, - {"-66000.120", SMALLINT, null}, - {"33000.12000000000000000", INTEGER, 33000L}, - {"-33000.12000000000000000", INTEGER, -33000L}, - {"33000.120", INTEGER, 33000L}, - {"-33000.120", INTEGER, -33000L}, - {"3300000000.1200000000000", INTEGER, null}, - {"3300000000.120", INTEGER, null}, - {"3300000000.1200000000000", BIGINT, 3300000000L}, - {"-3300000000.120000000000", BIGINT, -3300000000L}, - {"3300000000.12", BIGINT, 3300000000L}, - {"-3300000000.12", BIGINT, -3300000000L}, - {"330000000000000000000.12000000000", BIGINT, null}, - {"-330000000000000000000.12000000000", BIGINT, null}, - {"3300000", INTEGER, 3300000L}, - }; - } - private void assertDecimalToIntCoercion(Type fromType, Object valueToBeCoerced, Type toType, Object expectedValue) { Block coercedValue = createCoercer(TESTING_TYPE_MANAGER, toHiveType(fromType), toHiveType(toType), new CoercionUtils.CoercionContext(NANOSECONDS, false)).orElseThrow() diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestTrinoS3FileSystemAccessOperations.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestTrinoS3FileSystemAccessOperations.java index e094ef14ea7b..960f6b54d324 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestTrinoS3FileSystemAccessOperations.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestTrinoS3FileSystemAccessOperations.java @@ -36,16 +36,13 @@ import io.trino.testing.containers.Minio; import org.intellij.lang.annotations.Language; import org.testng.annotations.AfterClass; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.io.File; -import java.util.Arrays; import static com.google.common.base.Preconditions.checkArgument; import static io.trino.plugin.hive.HiveQueryRunner.TPCH_SCHEMA; import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; -import static io.trino.testing.DataProviders.toDataProvider; import static io.trino.testing.MultisetAssertions.assertMultisetsEqual; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.testing.containers.Minio.MINIO_ACCESS_KEY; @@ -111,63 +108,67 @@ public void tearDown() minio = null; } - @Test(dataProvider = "storageFormats") - public void testSelectWithFilter(StorageFormat format) + @Test + public void testSelectWithFilter() { - assertUpdate("DROP TABLE IF EXISTS test_select_from_where"); - String tableLocation = randomTableLocation("test_select_from_where"); - - assertUpdate("CREATE TABLE test_select_from_where WITH (format = '" + format + "', external_location = '" + tableLocation + "') AS SELECT 2 AS age", 1); - - assertFileSystemAccesses( - withSmallFileThreshold(getSession(), DataSize.valueOf("1MB")), // large enough threshold for single request of small file - "SELECT * FROM test_select_from_where WHERE age = 2", - ImmutableMultiset.builder() - .add("S3.GetObject") - .add("S3.ListObjectsV2") - .build()); - - assertFileSystemAccesses( - withSmallFileThreshold(getSession(), DataSize.valueOf("10B")), // disables single request for small file - "SELECT * FROM test_select_from_where WHERE age = 2", - ImmutableMultiset.builder() - .addCopies("S3.GetObject", occurrences(format, 3, 2)) - .add("S3.ListObjectsV2") - .build()); - - assertUpdate("DROP TABLE test_select_from_where"); + for (StorageFormat format : StorageFormat.values()) { + assertUpdate("DROP TABLE IF EXISTS test_select_from_where"); + String tableLocation = randomTableLocation("test_select_from_where"); + + assertUpdate("CREATE TABLE test_select_from_where WITH (format = '" + format + "', external_location = '" + tableLocation + "') AS SELECT 2 AS age", 1); + + assertFileSystemAccesses( + withSmallFileThreshold(getSession(), DataSize.valueOf("1MB")), // large enough threshold for single request of small file + "SELECT * FROM test_select_from_where WHERE age = 2", + ImmutableMultiset.builder() + .add("S3.GetObject") + .add("S3.ListObjectsV2") + .build()); + + assertFileSystemAccesses( + withSmallFileThreshold(getSession(), DataSize.valueOf("10B")), // disables single request for small file + "SELECT * FROM test_select_from_where WHERE age = 2", + ImmutableMultiset.builder() + .addCopies("S3.GetObject", occurrences(format, 3, 2)) + .add("S3.ListObjectsV2") + .build()); + + assertUpdate("DROP TABLE test_select_from_where"); + } } - @Test(dataProvider = "storageFormats") - public void testSelectPartitionTable(StorageFormat format) + @Test + public void testSelectPartitionTable() { - assertUpdate("DROP TABLE IF EXISTS test_select_from_partition"); - String tableLocation = randomTableLocation("test_select_from_partition"); - - assertUpdate("CREATE TABLE test_select_from_partition (data int, key varchar)" + - "WITH (partitioned_by = ARRAY['key'], format = '" + format + "', external_location = '" + tableLocation + "')"); - assertUpdate("INSERT INTO test_select_from_partition VALUES (1, 'part1'), (2, 'part2')", 2); - - assertFileSystemAccesses("SELECT * FROM test_select_from_partition", - ImmutableMultiset.builder() - .addCopies("S3.GetObject", 2) - .addCopies("S3.ListObjectsV2", 2) - .build()); - - assertFileSystemAccesses("SELECT * FROM test_select_from_partition WHERE key = 'part1'", - ImmutableMultiset.builder() - .add("S3.GetObject") - .add("S3.ListObjectsV2") - .build()); - - assertUpdate("INSERT INTO test_select_from_partition VALUES (11, 'part1')", 1); - assertFileSystemAccesses("SELECT * FROM test_select_from_partition WHERE key = 'part1'", - ImmutableMultiset.builder() - .addCopies("S3.GetObject", 2) - .addCopies("S3.ListObjectsV2", 1) - .build()); - - assertUpdate("DROP TABLE test_select_from_partition"); + for (StorageFormat format : StorageFormat.values()) { + assertUpdate("DROP TABLE IF EXISTS test_select_from_partition"); + String tableLocation = randomTableLocation("test_select_from_partition"); + + assertUpdate("CREATE TABLE test_select_from_partition (data int, key varchar)" + + "WITH (partitioned_by = ARRAY['key'], format = '" + format + "', external_location = '" + tableLocation + "')"); + assertUpdate("INSERT INTO test_select_from_partition VALUES (1, 'part1'), (2, 'part2')", 2); + + assertFileSystemAccesses("SELECT * FROM test_select_from_partition", + ImmutableMultiset.builder() + .addCopies("S3.GetObject", 2) + .addCopies("S3.ListObjectsV2", 2) + .build()); + + assertFileSystemAccesses("SELECT * FROM test_select_from_partition WHERE key = 'part1'", + ImmutableMultiset.builder() + .add("S3.GetObject") + .add("S3.ListObjectsV2") + .build()); + + assertUpdate("INSERT INTO test_select_from_partition VALUES (11, 'part1')", 1); + assertFileSystemAccesses("SELECT * FROM test_select_from_partition WHERE key = 'part1'", + ImmutableMultiset.builder() + .addCopies("S3.GetObject", 2) + .addCopies("S3.ListObjectsV2", 1) + .build()); + + assertUpdate("DROP TABLE test_select_from_partition"); + } } private static String randomTableLocation(String tableName) @@ -195,13 +196,6 @@ private Multiset getOperations() .collect(toCollection(HashMultiset::create)); } - @DataProvider - public static Object[][] storageFormats() - { - return Arrays.stream(StorageFormat.values()) - .collect(toDataProvider()); - } - private static int occurrences(StorageFormat tableType, int orcValue, int parquetValue) { checkArgument(!(orcValue == parquetValue), "No need to use Occurrences when ORC and Parquet"); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergMaterializedViewTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergMaterializedViewTest.java index 7c0d00556143..c4f0e04e68ee 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergMaterializedViewTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergMaterializedViewTest.java @@ -27,7 +27,6 @@ import org.apache.iceberg.TableMetadataParser; import org.assertj.core.api.Condition; import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.nio.file.Path; @@ -598,8 +597,23 @@ public void testNestedMaterializedViews() assertUpdate("DROP MATERIALIZED VIEW materialized_view_level2"); } - @Test(dataProvider = "testBucketPartitioningDataProvider") - public void testBucketPartitioning(String dataType, String exampleValue) + @Test + public void testBucketPartitioning() + { + testBucketPartitioning("integer", "20050909"); + testBucketPartitioning("bigint", "200509091331001234"); + testBucketPartitioning("decimal(8,5)", "DECIMAL '876.54321'"); + testBucketPartitioning("decimal(28,21)", "DECIMAL '1234567.890123456789012345678'"); + testBucketPartitioning("date", "DATE '2005-09-09'"); + testBucketPartitioning("time(6)", "TIME '13:31:00.123456'"); + testBucketPartitioning("timestamp(6)", "TIMESTAMP '2005-09-10 13:31:00.123456'"); + testBucketPartitioning("timestamp(6) with time zone", "TIMESTAMP '2005-09-10 13:00:00.123456 Europe/Warsaw'"); + testBucketPartitioning("varchar", "VARCHAR 'Greetings from Warsaw!'"); + testBucketPartitioning("uuid", "UUID '406caec7-68b9-4778-81b2-a12ece70c8b1'"); + testBucketPartitioning("varbinary", "X'66696E6465706920726F636B7321'"); + } + + private void testBucketPartitioning(String dataType, String exampleValue) { // validate the example value type assertThat(query("SELECT " + exampleValue)) @@ -622,27 +636,17 @@ public void testBucketPartitioning(String dataType, String exampleValue) } } - @DataProvider - public Object[][] testBucketPartitioningDataProvider() + @Test + public void testTruncatePartitioning() { - // Iceberg supports bucket partitioning on int, long, decimal, date, time, timestamp, timestamptz, string, uuid, fixed, binary - return new Object[][] { - {"integer", "20050909"}, - {"bigint", "200509091331001234"}, - {"decimal(8,5)", "DECIMAL '876.54321'"}, - {"decimal(28,21)", "DECIMAL '1234567.890123456789012345678'"}, - {"date", "DATE '2005-09-09'"}, - {"time(6)", "TIME '13:31:00.123456'"}, - {"timestamp(6)", "TIMESTAMP '2005-09-10 13:31:00.123456'"}, - {"timestamp(6) with time zone", "TIMESTAMP '2005-09-10 13:00:00.123456 Europe/Warsaw'"}, - {"varchar", "VARCHAR 'Greetings from Warsaw!'"}, - {"uuid", "UUID '406caec7-68b9-4778-81b2-a12ece70c8b1'"}, - {"varbinary", "X'66696E6465706920726F636B7321'"}, - }; + testTruncatePartitioning("integer", "20050909"); + testTruncatePartitioning("bigint", "200509091331001234"); + testTruncatePartitioning("decimal(8,5)", "DECIMAL '876.54321'"); + testTruncatePartitioning("decimal(28,21)", "DECIMAL '1234567.890123456789012345678'"); + testTruncatePartitioning("varchar", "VARCHAR 'Greetings from Warsaw!'"); } - @Test(dataProvider = "testTruncatePartitioningDataProvider") - public void testTruncatePartitioning(String dataType, String exampleValue) + private void testTruncatePartitioning(String dataType, String exampleValue) { // validate the example value type assertThat(query("SELECT " + exampleValue)) @@ -665,21 +669,23 @@ public void testTruncatePartitioning(String dataType, String exampleValue) } } - @DataProvider - public Object[][] testTruncatePartitioningDataProvider() + @Test + public void testTemporalPartitioning() { - // Iceberg supports truncate partitioning on int, long, decimal, string - return new Object[][] { - {"integer", "20050909"}, - {"bigint", "200509091331001234"}, - {"decimal(8,5)", "DECIMAL '876.54321'"}, - {"decimal(28,21)", "DECIMAL '1234567.890123456789012345678'"}, - {"varchar", "VARCHAR 'Greetings from Warsaw!'"}, - }; + testTemporalPartitioning("year", "date", "DATE '2005-09-09'"); + testTemporalPartitioning("year", "timestamp(6)", "TIMESTAMP '2005-09-10 13:31:00.123456'"); + testTemporalPartitioning("year", "timestamp(6) with time zone", "TIMESTAMP '2005-09-10 13:00:00.123456 Europe/Warsaw'"); + testTemporalPartitioning("month", "date", "DATE '2005-09-09'"); + testTemporalPartitioning("month", "timestamp(6)", "TIMESTAMP '2005-09-10 13:31:00.123456'"); + testTemporalPartitioning("month", "timestamp(6) with time zone", "TIMESTAMP '2005-09-10 13:00:00.123456 Europe/Warsaw'"); + testTemporalPartitioning("day", "date", "DATE '2005-09-09'"); + testTemporalPartitioning("day", "timestamp(6)", "TIMESTAMP '2005-09-10 13:31:00.123456'"); + testTemporalPartitioning("day", "timestamp(6) with time zone", "TIMESTAMP '2005-09-10 13:00:00.123456 Europe/Warsaw'"); + testTemporalPartitioning("hour", "timestamp(6)", "TIMESTAMP '2005-09-10 13:31:00.123456'"); + testTemporalPartitioning("hour", "timestamp(6) with time zone", "TIMESTAMP '2005-09-10 13:00:00.123456 Europe/Warsaw'"); } - @Test(dataProvider = "testTemporalPartitioningDataProvider") - public void testTemporalPartitioning(String partitioning, String dataType, String exampleValue) + private void testTemporalPartitioning(String partitioning, String dataType, String exampleValue) { // validate the example value type assertThat(query("SELECT " + exampleValue)) @@ -702,24 +708,6 @@ public void testTemporalPartitioning(String partitioning, String dataType, Strin } } - @DataProvider - public Object[][] testTemporalPartitioningDataProvider() - { - return new Object[][] { - {"year", "date", "DATE '2005-09-09'"}, - {"year", "timestamp(6)", "TIMESTAMP '2005-09-10 13:31:00.123456'"}, - {"year", "timestamp(6) with time zone", "TIMESTAMP '2005-09-10 13:00:00.123456 Europe/Warsaw'"}, - {"month", "date", "DATE '2005-09-09'"}, - {"month", "timestamp(6)", "TIMESTAMP '2005-09-10 13:31:00.123456'"}, - {"month", "timestamp(6) with time zone", "TIMESTAMP '2005-09-10 13:00:00.123456 Europe/Warsaw'"}, - {"day", "date", "DATE '2005-09-09'"}, - {"day", "timestamp(6)", "TIMESTAMP '2005-09-10 13:31:00.123456'"}, - {"day", "timestamp(6) with time zone", "TIMESTAMP '2005-09-10 13:00:00.123456 Europe/Warsaw'"}, - {"hour", "timestamp(6)", "TIMESTAMP '2005-09-10 13:31:00.123456'"}, - {"hour", "timestamp(6) with time zone", "TIMESTAMP '2005-09-10 13:00:00.123456 Europe/Warsaw'"}, - }; - } - @Test public void testMaterializedViewSnapshotSummariesHaveTrinoQueryId() { diff --git a/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteTypeMapping.java b/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteTypeMapping.java index 37db793f4d5d..09ac97347e42 100644 --- a/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteTypeMapping.java +++ b/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteTypeMapping.java @@ -29,7 +29,6 @@ import io.trino.testing.sql.TrinoSqlExecutor; import org.intellij.lang.annotations.Language; import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.time.LocalDate; @@ -289,8 +288,18 @@ private static SqlDataTypeTest binaryTest(String inputType) .addRoundTrip(inputType, "X'000000000000'", VARBINARY, "X'000000000000'"); } - @Test(dataProvider = "sessionZonesDataProvider") - public void testDate(ZoneId sessionZone) + @Test + public void testDate() + { + testDate(UTC); + testDate(jvmZone); + // using two non-JVM zones so that we don't need to worry what Ignite system zone is + testDate(vilnius); + testDate(kathmandu); + testDate(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testDate(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) @@ -314,8 +323,18 @@ public void testDate(ZoneId sessionZone) .execute(getQueryRunner(), session, igniteCreateAndInsert("test_date")); } - @Test(dataProvider = "sessionZonesDataProvider") - public void testUnsupportedDateRange(ZoneId sessionZone) + @Test + public void testUnsupportedDateRange() + { + testUnsupportedDateRange(UTC); + testUnsupportedDateRange(jvmZone); + // using two non-JVM zones so that we don't need to worry what Ignite system zone is + testUnsupportedDateRange(vilnius); + testUnsupportedDateRange(kathmandu); + testUnsupportedDateRange(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testUnsupportedDateRange(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) @@ -404,19 +423,6 @@ public void testUnboundedVarchar() .execute(getQueryRunner(), trinoCreateAndInsert("test_unbounded_varchar")); } - @DataProvider - public Object[][] sessionZonesDataProvider() - { - return new Object[][] { - {UTC}, - {jvmZone}, - // using two non-JVM zones so that we don't need to worry what Ignite system zone is - {vilnius}, - {kathmandu}, - {TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()}, - }; - } - private DataSetup trinoCreateAndInsert(String tableNamePrefix) { return trinoCreateAndInsert(createSession(), tableNamePrefix); diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaSecurityConfig.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaSecurityConfig.java index aec686861aee..391f3cf5d95f 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaSecurityConfig.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaSecurityConfig.java @@ -14,8 +14,6 @@ package io.trino.plugin.kafka; import com.google.common.collect.ImmutableMap; -import org.apache.kafka.common.security.auth.SecurityProtocol; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.util.Map; @@ -49,33 +47,31 @@ public void testExplicitPropertyMappings() assertFullMapping(properties, expected); } - @Test(dataProvider = "validSecurityProtocols") - public void testValidSecurityProtocols(SecurityProtocol securityProtocol) + @Test + public void testValidSecurityProtocols() { new KafkaSecurityConfig() - .setSecurityProtocol(securityProtocol) + .setSecurityProtocol(PLAINTEXT) .validate(); - } - @DataProvider(name = "validSecurityProtocols") - public Object[][] validSecurityProtocols() - { - return new Object[][] {{PLAINTEXT}, {SSL}}; + new KafkaSecurityConfig() + .setSecurityProtocol(SSL) + .validate(); } - @Test(dataProvider = "invalidSecurityProtocols") - public void testInvalidSecurityProtocol(SecurityProtocol securityProtocol) + @Test + public void testInvalidSecurityProtocol() { assertThatThrownBy(() -> new KafkaSecurityConfig() - .setSecurityProtocol(securityProtocol) + .setSecurityProtocol(SASL_PLAINTEXT) .validate()) .isInstanceOf(IllegalStateException.class) .hasMessage("Only PLAINTEXT and SSL security protocols are supported. See 'kafka.config.resources' if other security protocols are needed"); - } - @DataProvider(name = "invalidSecurityProtocols") - public Object[][] invalidSecurityProtocols() - { - return new Object[][] {{SASL_PLAINTEXT}, {SASL_SSL}}; + assertThatThrownBy(() -> new KafkaSecurityConfig() + .setSecurityProtocol(SASL_SSL) + .validate()) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Only PLAINTEXT and SSL security protocols are supported. See 'kafka.config.resources' if other security protocols are needed"); } } diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestMillisecondsJsonDateTimeFormatter.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestMillisecondsJsonDateTimeFormatter.java index b19ab1021ffa..bc6613ae6cdc 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestMillisecondsJsonDateTimeFormatter.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestMillisecondsJsonDateTimeFormatter.java @@ -16,7 +16,6 @@ import io.trino.plugin.kafka.encoder.json.format.JsonDateTimeFormatter; import io.trino.spi.type.SqlTimestampWithTimeZone; import io.trino.spi.type.TimeZoneKey; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.time.LocalDateTime; @@ -42,56 +41,47 @@ private static JsonDateTimeFormatter getFormatter() return MILLISECONDS_SINCE_EPOCH.getFormatter(Optional.empty()); } - @Test(dataProvider = "testTimeProvider") - public void testTime(LocalTime time) + @Test + public void testTime() + { + testTime(LocalTime.of(15, 36, 25, 123000000)); + testTime(LocalTime.of(15, 36, 25, 0)); + } + + private void testTime(LocalTime time) { String formatted = getFormatter().formatTime(sqlTimeOf(3, time), 3); assertEquals(Long.parseLong(formatted), time.getLong(MILLI_OF_DAY)); } - @DataProvider - public Object[][] testTimeProvider() + @Test + public void testTimestamp() { - return new Object[][] { - {LocalTime.of(15, 36, 25, 123000000)}, - {LocalTime.of(15, 36, 25, 0)}, - }; + testTimestamp(LocalDateTime.of(2020, 8, 18, 12, 38, 29, 123000000)); + testTimestamp(LocalDateTime.of(1970, 1, 1, 0, 0, 0, 0)); + testTimestamp(LocalDateTime.of(1800, 8, 18, 12, 38, 29, 123000000)); } - @Test(dataProvider = "testTimestampProvider") - public void testTimestamp(LocalDateTime dateTime) + private void testTimestamp(LocalDateTime dateTime) { String formattedStr = getFormatter().formatTimestamp(sqlTimestampOf(3, dateTime)); assertEquals(Long.parseLong(formattedStr), DAYS.toMillis(dateTime.getLong(EPOCH_DAY)) + scaleNanosToMillis(dateTime.getLong(NANO_OF_DAY))); } - @DataProvider - public Object[][] testTimestampProvider() + @Test + public void testTimestampWithTimeZone() { - return new Object[][] { - {LocalDateTime.of(2020, 8, 18, 12, 38, 29, 123000000)}, - {LocalDateTime.of(1970, 1, 1, 0, 0, 0, 0)}, - {LocalDateTime.of(1800, 8, 18, 12, 38, 29, 123000000)}, - }; + testTimestampWithTimeZone(ZonedDateTime.of(2020, 8, 18, 12, 38, 29, 123000000, UTC_KEY.getZoneId())); + testTimestampWithTimeZone(ZonedDateTime.of(2020, 8, 18, 12, 38, 29, 123000000, TimeZoneKey.getTimeZoneKey("America/New_York").getZoneId())); + testTimestampWithTimeZone(ZonedDateTime.of(1800, 8, 18, 12, 38, 29, 123000000, UTC_KEY.getZoneId())); + testTimestampWithTimeZone(ZonedDateTime.of(2020, 8, 18, 12, 38, 29, 123000000, TimeZoneKey.getTimeZoneKey("Asia/Hong_Kong").getZoneId())); + testTimestampWithTimeZone(ZonedDateTime.of(2020, 8, 18, 12, 38, 29, 123000000, TimeZoneKey.getTimeZoneKey("Africa/Mogadishu").getZoneId())); + testTimestampWithTimeZone(ZonedDateTime.of(1970, 1, 1, 0, 0, 0, 0, UTC_KEY.getZoneId())); } - @Test(dataProvider = "testTimestampWithTimeZoneProvider") - public void testTimestampWithTimeZone(ZonedDateTime zonedDateTime) + private void testTimestampWithTimeZone(ZonedDateTime zonedDateTime) { String formattedStr = getFormatter().formatTimestampWithZone(SqlTimestampWithTimeZone.fromInstant(3, zonedDateTime.toInstant(), zonedDateTime.getZone())); assertEquals(Long.parseLong(formattedStr), zonedDateTime.toInstant().toEpochMilli()); } - - @DataProvider - public Object[][] testTimestampWithTimeZoneProvider() - { - return new Object[][] { - {ZonedDateTime.of(2020, 8, 18, 12, 38, 29, 123000000, UTC_KEY.getZoneId())}, - {ZonedDateTime.of(2020, 8, 18, 12, 38, 29, 123000000, TimeZoneKey.getTimeZoneKey("America/New_York").getZoneId())}, - {ZonedDateTime.of(1800, 8, 18, 12, 38, 29, 123000000, UTC_KEY.getZoneId())}, - {ZonedDateTime.of(2020, 8, 18, 12, 38, 29, 123000000, TimeZoneKey.getTimeZoneKey("Asia/Hong_Kong").getZoneId())}, - {ZonedDateTime.of(2020, 8, 18, 12, 38, 29, 123000000, TimeZoneKey.getTimeZoneKey("Africa/Mogadishu").getZoneId())}, - {ZonedDateTime.of(1970, 1, 1, 0, 0, 0, 0, UTC_KEY.getZoneId())}, - }; - } } diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestSecondsJsonDateTimeFormatter.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestSecondsJsonDateTimeFormatter.java index 4ec50a540041..954e593c22e8 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestSecondsJsonDateTimeFormatter.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestSecondsJsonDateTimeFormatter.java @@ -16,7 +16,6 @@ import io.trino.plugin.kafka.encoder.json.format.JsonDateTimeFormatter; import io.trino.spi.type.SqlTimestampWithTimeZone; import io.trino.spi.type.TimeZoneKey; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.time.LocalDateTime; @@ -38,57 +37,48 @@ private static JsonDateTimeFormatter getFormatter() return SECONDS_SINCE_EPOCH.getFormatter(Optional.empty()); } - @Test(dataProvider = "testTimeProvider") - public void testTime(LocalTime time) + @Test + public void testTime() + { + testTime(LocalTime.of(15, 36, 25, 0)); + testTime(LocalTime.of(0, 0, 0, 0)); + testTime(LocalTime.of(23, 59, 59, 0)); + } + + private void testTime(LocalTime time) { String formatted = getFormatter().formatTime(sqlTimeOf(3, time), 3); assertEquals(Long.parseLong(formatted), time.toSecondOfDay()); } - @DataProvider - public Object[][] testTimeProvider() + @Test + public void testTimestamp() { - return new Object[][] { - {LocalTime.of(15, 36, 25, 0)}, - {LocalTime.of(0, 0, 0, 0)}, - {LocalTime.of(23, 59, 59, 0)}, - }; + testTimestamp(LocalDateTime.of(2020, 8, 18, 12, 38, 29, 0)); + testTimestamp(LocalDateTime.of(1970, 1, 1, 0, 0, 0, 0)); + testTimestamp(LocalDateTime.of(1800, 8, 18, 12, 38, 29, 0)); } - @Test(dataProvider = "testTimestampProvider") - public void testTimestamp(LocalDateTime dateTime) + private void testTimestamp(LocalDateTime dateTime) { String formatted = getFormatter().formatTimestamp(sqlTimestampOf(3, dateTime)); assertEquals(Long.parseLong(formatted), dateTime.toEpochSecond(UTC)); } - @DataProvider - public Object[][] testTimestampProvider() + @Test + public void testTimestampWithTimeZone() { - return new Object[][] { - {LocalDateTime.of(2020, 8, 18, 12, 38, 29, 0)}, - {LocalDateTime.of(1970, 1, 1, 0, 0, 0, 0)}, - {LocalDateTime.of(1800, 8, 18, 12, 38, 29, 0)}, - }; + testTimestampWithTimeZone(ZonedDateTime.of(2020, 8, 18, 12, 38, 29, 123000000, UTC_KEY.getZoneId())); + testTimestampWithTimeZone(ZonedDateTime.of(2020, 8, 18, 12, 38, 29, 123000000, TimeZoneKey.getTimeZoneKey("America/New_York").getZoneId())); + testTimestampWithTimeZone(ZonedDateTime.of(1800, 8, 18, 12, 38, 29, 123000000, UTC_KEY.getZoneId())); + testTimestampWithTimeZone(ZonedDateTime.of(2020, 8, 19, 12, 23, 41, 123000000, TimeZoneKey.getTimeZoneKey("Asia/Hong_Kong").getZoneId())); + testTimestampWithTimeZone(ZonedDateTime.of(2020, 8, 19, 12, 23, 41, 123000000, TimeZoneKey.getTimeZoneKey("Africa/Mogadishu").getZoneId())); + testTimestampWithTimeZone(ZonedDateTime.of(1970, 1, 1, 0, 0, 0, 0, UTC_KEY.getZoneId())); } - @Test(dataProvider = "testTimestampWithTimeZoneProvider") - public void testTimestampWithTimeZone(ZonedDateTime zonedDateTime) + private void testTimestampWithTimeZone(ZonedDateTime zonedDateTime) { String formattedStr = getFormatter().formatTimestampWithZone(SqlTimestampWithTimeZone.fromInstant(3, zonedDateTime.toInstant(), zonedDateTime.getZone())); assertEquals(Long.parseLong(formattedStr), zonedDateTime.toEpochSecond()); } - - @DataProvider - public Object[][] testTimestampWithTimeZoneProvider() - { - return new Object[][] { - {ZonedDateTime.of(2020, 8, 18, 12, 38, 29, 123000000, UTC_KEY.getZoneId())}, - {ZonedDateTime.of(2020, 8, 18, 12, 38, 29, 123000000, TimeZoneKey.getTimeZoneKey("America/New_York").getZoneId())}, - {ZonedDateTime.of(1800, 8, 18, 12, 38, 29, 123000000, UTC_KEY.getZoneId())}, - {ZonedDateTime.of(2020, 8, 19, 12, 23, 41, 123000000, TimeZoneKey.getTimeZoneKey("Asia/Hong_Kong").getZoneId())}, - {ZonedDateTime.of(2020, 8, 19, 12, 23, 41, 123000000, TimeZoneKey.getTimeZoneKey("Africa/Mogadishu").getZoneId())}, - {ZonedDateTime.of(1970, 1, 1, 0, 0, 0, 0, UTC_KEY.getZoneId())}, - }; - } } diff --git a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestRecordAccess.java b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestRecordAccess.java index 2eadc67aa8f0..767bf5c03c05 100644 --- a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestRecordAccess.java +++ b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestRecordAccess.java @@ -29,7 +29,6 @@ import io.trino.testing.StandaloneQueryRunner; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.io.ByteArrayOutputStream; @@ -183,8 +182,15 @@ public void testStreamHasData() log.info("Completed second test (select counts)"); } - @Test(dataProvider = "testJsonStreamProvider") - public void testJsonStream(int uncompressedMessages, int compressedMessages, String streamName) + @Test + public void testJsonStream() + { + testJsonStream(4, 0, jsonStreamName); + testJsonStream(0, 4, jsonGzipCompressStreamName); + testJsonStream(2, 2, jsonAutomaticCompressStreamName); + } + + private void testJsonStream(int uncompressedMessages, int compressedMessages, String streamName) { // Simple case: add a few specific items, query object and internal fields: if (uncompressedMessages > 0) { @@ -210,14 +216,4 @@ public void testJsonStream(int uncompressedMessages, int compressedMessages, Str log.info("ROW: %s", row); } } - - @DataProvider - public Object[][] testJsonStreamProvider() - { - return new Object[][] { - {4, 0, jsonStreamName}, - {0, 4, jsonGzipCompressStreamName}, - {2, 2, jsonAutomaticCompressStreamName}, - }; - } } diff --git a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbTypeMapping.java b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbTypeMapping.java index 53a20a263773..4c6e4d1f5641 100644 --- a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbTypeMapping.java +++ b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbTypeMapping.java @@ -29,7 +29,6 @@ import io.trino.testing.sql.TestTable; import io.trino.testing.sql.TrinoSqlExecutor; import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.math.RoundingMode; @@ -350,8 +349,14 @@ public void testDecimalExceedingPrecisionMaxWithNonExceedingIntegerValues() } } - @Test(dataProvider = "testDecimalExceedingPrecisionMaxProvider") - public void testDecimalExceedingPrecisionMaxWithSupportedValues(int typePrecision, int typeScale) + @Test + public void testDecimalExceedingPrecisionMaxWithSupportedValues() + { + testDecimalExceedingPrecisionMaxWithSupportedValues(40, 8); + testDecimalExceedingPrecisionMaxWithSupportedValues(50, 10); + } + + private void testDecimalExceedingPrecisionMaxWithSupportedValues(int typePrecision, int typeScale) { try (TestTable testTable = new TestTable( server::execute, @@ -401,15 +406,6 @@ public void testDecimalExceedingPrecisionMaxWithSupportedValues(int typePrecisio } } - @DataProvider - public Object[][] testDecimalExceedingPrecisionMaxProvider() - { - return new Object[][] { - {40, 8}, - {50, 10}, - }; - } - private Session sessionWithDecimalMappingAllowOverflow(RoundingMode roundingMode, int scale) { return Session.builder(getSession()) @@ -588,8 +584,17 @@ public void testBinary() .execute(getQueryRunner(), mariaDbCreateAndInsert("tpch.test_binary")); } - @Test(dataProvider = "sessionZonesDataProvider") - public void testDate(ZoneId sessionZone) + @Test + public void testDate() + { + testDate(UTC); + testDate(jvmZone); + testDate(vilnius); + testDate(kathmandu); + testDate(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testDate(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) @@ -736,8 +741,17 @@ private void testUnsupportedDataType(String databaseDataType) /** * Read {@code TIMESTAMP}s inserted by MariaDb as Trino {@code TIMESTAMP}s */ - @Test(dataProvider = "sessionZonesDataProvider") - public void testTimestamp(ZoneId sessionZone) + @Test + public void testTimestamp() + { + testTimestamp(UTC); + testTimestamp(jvmZone); + testTimestamp(vilnius); + testTimestamp(kathmandu); + testTimestamp(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testTimestamp(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) @@ -856,18 +870,6 @@ public void testTimestampCoercion() .execute(getQueryRunner(), trinoCreateAndInsert("test_timestamp_coercion")); } - @DataProvider - public Object[][] sessionZonesDataProvider() - { - return new Object[][] { - {UTC}, - {jvmZone}, - {vilnius}, - {kathmandu}, - {TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()}, - }; - } - private DataSetup trinoCreateAsSelect(String tableNamePrefix) { return trinoCreateAsSelect(getSession(), tableNamePrefix); From 4700e8bad9e777dbfa4f382a04edabe841ea520f Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Tue, 14 Nov 2023 13:31:37 -0800 Subject: [PATCH 259/587] Migrate tests to JUnit --- .../io/trino/execution/TestNodeScheduler.java | 21 ++++--- .../hive/coercions/TestDecimalCoercers.java | 2 +- .../BaseIcebergMaterializedViewTest.java | 12 +++- .../iceberg/TestIcebergMaterializedView.java | 2 +- ...estIcebergGlueCatalogMaterializedView.java | 10 +++- plugin/trino-ignite/pom.xml | 28 ---------- .../TestIgniteCaseInsensitiveMapping.java | 20 +++---- .../trino/plugin/ignite/TestIgniteClient.java | 10 ++-- .../plugin/ignite/TestIgniteTypeMapping.java | 6 +- plugin/trino-kafka/pom.xml | 23 -------- .../plugin/kafka/TestKafkaConnectorTest.java | 8 +-- .../plugin/kafka/TestKafkaFilterManager.java | 27 +++++---- .../kafka/TestKafkaIntegrationPushDown.java | 6 +- .../trino/plugin/kafka/TestKafkaPlugin.java | 8 +-- .../plugin/kafka/TestKafkaSecurityConfig.java | 2 +- .../kafka/TestMinimalFunctionality.java | 4 +- .../json/TestCustomJsonDateTimeFormatter.java | 12 ++-- .../TestISO8601JsonDateTimeFormatter.java | 12 ++-- ...TestMillisecondsJsonDateTimeFormatter.java | 10 ++-- .../TestRFC2822JsonDateTimeFormatter.java | 6 +- .../TestSecondsJsonDateTimeFormatter.java | 10 ++-- .../encoder/raw/TestRawEncoderMapping.java | 4 +- ...ithSchemaRegistryMinimalFunctionality.java | 5 +- .../kafka/protobuf/TestProtobufEncoder.java | 10 ++-- ...estAvroConfluentContentSchemaProvider.java | 5 +- .../TestAvroConfluentRowDecoder.java | 16 +++--- .../confluent/TestAvroSchemaConverter.java | 16 +++--- ...chemaRegistryTableDescriptionSupplier.java | 9 ++- ...entSchemaRegistryMinimalFunctionality.java | 7 +-- .../kafka/utils/TestPropertiesUtils.java | 4 +- .../plugin/kinesis/TestRecordAccess.java | 17 ++++-- plugin/trino-mariadb/pom.xml | 28 ---------- .../mariadb/BaseMariaDbConnectorTest.java | 5 +- .../BaseMariaDbTableStatisticsTest.java | 55 ++++++++++++------- .../plugin/mariadb/TestMariaDbClient.java | 10 ++-- .../plugin/mariadb/TestMariaDbJdbcConfig.java | 17 +++--- .../mariadb/TestMariaDbTypeMapping.java | 12 +++- 37 files changed, 206 insertions(+), 253 deletions(-) diff --git a/core/trino-main/src/test/java/io/trino/execution/TestNodeScheduler.java b/core/trino-main/src/test/java/io/trino/execution/TestNodeScheduler.java index e6a1caa32fd4..e95f4e21e67b 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestNodeScheduler.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestNodeScheduler.java @@ -47,9 +47,12 @@ import io.trino.sql.planner.plan.PlanNodeId; import io.trino.testing.TestingSession; import io.trino.util.FinalizerService; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.parallel.Execution; import java.net.InetAddress; import java.net.URI; @@ -84,11 +87,14 @@ import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; -@Test(singleThreaded = true) +@TestInstance(PER_METHOD) +@Execution(SAME_THREAD) public class TestNodeScheduler { private FinalizerService finalizerService; @@ -102,7 +108,7 @@ public class TestNodeScheduler private ScheduledExecutorService remoteTaskScheduledExecutor; private Session session; - @BeforeMethod + @BeforeEach public void setUp() { session = TestingSession.testSessionBuilder().build(); @@ -134,7 +140,7 @@ private void setUpNodes() new InternalNode("other3", URI.create("http://10.0.0.1:13"), NodeVersion.UNKNOWN, false)); } - @AfterMethod(alwaysRun = true) + @AfterEach public void tearDown() { remoteTaskExecutor.shutdown(); @@ -172,7 +178,8 @@ public void testScheduleLocal() assertEquals(assignment.getValue(), split); } - @Test(timeOut = 60 * 1000) + @Test + @Timeout(60) public void testTopologyAwareScheduling() { NodeTaskMap nodeTaskMap = new NodeTaskMap(finalizerService); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestDecimalCoercers.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestDecimalCoercers.java index 4e08b96884f2..dadfea248de9 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestDecimalCoercers.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/coercions/TestDecimalCoercers.java @@ -17,7 +17,7 @@ import io.trino.spi.type.DecimalParseResult; import io.trino.spi.type.Decimals; import io.trino.spi.type.Type; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.plugin.hive.HiveTimestampPrecision.NANOSECONDS; import static io.trino.plugin.hive.HiveType.toHiveType; diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergMaterializedViewTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergMaterializedViewTest.java index c4f0e04e68ee..45b0e5ed2246 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergMaterializedViewTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergMaterializedViewTest.java @@ -26,8 +26,10 @@ import org.apache.iceberg.TableMetadata; import org.apache.iceberg.TableMetadataParser; import org.assertj.core.api.Condition; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.nio.file.Path; import java.util.Optional; @@ -46,8 +48,12 @@ import static org.assertj.core.api.Assertions.anyOf; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public abstract class BaseIcebergMaterializedViewTest extends AbstractTestQueryFramework { @@ -55,7 +61,7 @@ public abstract class BaseIcebergMaterializedViewTest protected abstract String getStorageMetadataLocation(String materializedViewName); - @BeforeClass + @BeforeAll public void setUp() { assertUpdate("CREATE TABLE base_table1(_bigint BIGINT, _date DATE) WITH (partitioning = ARRAY['_date'])"); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMaterializedView.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMaterializedView.java index c694902ccd80..7b8a2aa6dd71 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMaterializedView.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMaterializedView.java @@ -18,7 +18,7 @@ import io.trino.plugin.hive.metastore.Table; import io.trino.sql.tree.ExplainType; import io.trino.testing.DistributedQueryRunner; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.File; import java.nio.file.Files; diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogMaterializedView.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogMaterializedView.java index f3d54ce2deed..5bb341e92173 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogMaterializedView.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogMaterializedView.java @@ -28,7 +28,9 @@ import io.trino.plugin.iceberg.IcebergQueryRunner; import io.trino.plugin.iceberg.SchemaInitializer; import io.trino.testing.QueryRunner; -import org.testng.annotations.AfterClass; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.nio.file.Files; @@ -40,7 +42,11 @@ import static io.trino.plugin.hive.metastore.glue.converter.GlueToTrinoConverter.getTableParameters; import static io.trino.testing.TestingNames.randomNameSuffix; import static org.apache.iceberg.BaseMetastoreTableOperations.METADATA_LOCATION_PROP; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestIcebergGlueCatalogMaterializedView extends BaseIcebergMaterializedViewTest { @@ -85,7 +91,7 @@ protected String getStorageMetadataLocation(String materializedViewName) return getTableParameters(table).get(METADATA_LOCATION_PROP); } - @AfterClass(alwaysRun = true) + @AfterAll public void cleanup() { cleanUpSchema(schemaName); diff --git a/plugin/trino-ignite/pom.xml b/plugin/trino-ignite/pom.xml index fdd28fc8cfad..e38de38ee251 100644 --- a/plugin/trino-ignite/pom.xml +++ b/plugin/trino-ignite/pom.xml @@ -231,36 +231,8 @@ testcontainers test - - - org.testng - testng - test - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - io.trino trino-maven-plugin diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaConnectorTest.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaConnectorTest.java index 15374d28cf09..f4451fc375c8 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaConnectorTest.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaConnectorTest.java @@ -67,8 +67,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assumptions.abort; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; public class TestKafkaConnectorTest extends BaseConnectorTest @@ -380,7 +378,7 @@ public void testReadAllDataTypes() public void testInsert() { // Override because the base test uses CREATE TABLE AS SELECT statement that is unsupported in Kafka connector - assertFalse(hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA)); + assertThat(hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA)).isFalse(); String query = "SELECT phone, custkey, acctbal FROM customer"; @@ -504,7 +502,9 @@ public void testJsonDateTimeFormatsRoundTrip() Object actual = computeScalar("SELECT " + field.getFieldName() + " FROM write_test." + testCase.getTopicName()); Object expected = computeScalar("SELECT " + field.getFieldValue()); try { - assertEquals(actual, expected, "Equality assertion failed for field: " + field.getFieldName()); + assertThat(actual) + .describedAs("Equality assertion failed for field: " + field.getFieldName()) + .isEqualTo(expected); } catch (AssertionError e) { throw new AssertionError(format("Equality assertion failed for field '%s'\n%s", field.getFieldName(), e.getMessage()), e); diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaFilterManager.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaFilterManager.java index a54ba4be6622..3808c5074736 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaFilterManager.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaFilterManager.java @@ -23,8 +23,7 @@ import static io.trino.spi.predicate.Domain.multipleValues; import static io.trino.spi.type.BigintType.BIGINT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestKafkaFilterManager { @@ -34,39 +33,39 @@ public void testFilterValuesByDomain() Set source = Set.of(1L, 2L, 3L, 4L, 5L, 6L); Domain testDomain = Domain.singleValue(BIGINT, 1L); - assertEquals(KafkaFilterManager.filterValuesByDomain(testDomain, source), Set.of(1L)); + assertThat(KafkaFilterManager.filterValuesByDomain(testDomain, source)).isEqualTo(Set.of(1L)); testDomain = multipleValues(BIGINT, ImmutableList.of(3L, 8L)); - assertEquals(KafkaFilterManager.filterValuesByDomain(testDomain, source), Set.of(3L)); + assertThat(KafkaFilterManager.filterValuesByDomain(testDomain, source)).isEqualTo(Set.of(3L)); testDomain = Domain.create(SortedRangeSet.copyOf(BIGINT, ImmutableList.of( Range.range(BIGINT, 2L, true, 4L, true))), false); - assertEquals(KafkaFilterManager.filterValuesByDomain(testDomain, source), Set.of(2L, 3L, 4L)); + assertThat(KafkaFilterManager.filterValuesByDomain(testDomain, source)).isEqualTo(Set.of(2L, 3L, 4L)); } @Test public void testFilterRangeByDomain() { Domain testDomain = Domain.singleValue(BIGINT, 1L); - assertTrue(KafkaFilterManager.filterRangeByDomain(testDomain).isPresent()); - assertEquals(KafkaFilterManager.filterRangeByDomain(testDomain).get().getBegin(), 1L); - assertEquals(KafkaFilterManager.filterRangeByDomain(testDomain).get().getEnd(), 2L); + assertThat(KafkaFilterManager.filterRangeByDomain(testDomain).isPresent()).isTrue(); + assertThat(KafkaFilterManager.filterRangeByDomain(testDomain).get().getBegin()).isEqualTo(1L); + assertThat(KafkaFilterManager.filterRangeByDomain(testDomain).get().getEnd()).isEqualTo(2L); testDomain = multipleValues(BIGINT, ImmutableList.of(3L, 8L)); - assertTrue(KafkaFilterManager.filterRangeByDomain(testDomain).isPresent()); - assertEquals(KafkaFilterManager.filterRangeByDomain(testDomain).get().getBegin(), 3L); - assertEquals(KafkaFilterManager.filterRangeByDomain(testDomain).get().getEnd(), 9L); + assertThat(KafkaFilterManager.filterRangeByDomain(testDomain).isPresent()).isTrue(); + assertThat(KafkaFilterManager.filterRangeByDomain(testDomain).get().getBegin()).isEqualTo(3L); + assertThat(KafkaFilterManager.filterRangeByDomain(testDomain).get().getEnd()).isEqualTo(9L); testDomain = Domain.create(SortedRangeSet.copyOf(BIGINT, ImmutableList.of( Range.range(BIGINT, 2L, true, 4L, true))), false); - assertTrue(KafkaFilterManager.filterRangeByDomain(testDomain).isPresent()); - assertEquals(KafkaFilterManager.filterRangeByDomain(testDomain).get().getBegin(), 2L); - assertEquals(KafkaFilterManager.filterRangeByDomain(testDomain).get().getEnd(), 5L); + assertThat(KafkaFilterManager.filterRangeByDomain(testDomain).isPresent()).isTrue(); + assertThat(KafkaFilterManager.filterRangeByDomain(testDomain).get().getBegin()).isEqualTo(2L); + assertThat(KafkaFilterManager.filterRangeByDomain(testDomain).get().getEnd()).isEqualTo(5L); } } diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaIntegrationPushDown.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaIntegrationPushDown.java index 8bb8504af691..e07510367b53 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaIntegrationPushDown.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaIntegrationPushDown.java @@ -40,8 +40,8 @@ import static io.trino.testing.assertions.Assert.assertEventually; import static java.lang.String.format; import static java.util.Objects.requireNonNull; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; @Execution(SAME_THREAD) public class TestKafkaIntegrationPushDown @@ -92,7 +92,7 @@ public void testPartitionPushDown() assertEventually(() -> { MaterializedResultWithQueryId queryResult = getDistributedQueryRunner().executeWithQueryId(getSession(), sql); - assertEquals(getQueryInfo(getDistributedQueryRunner(), queryResult).getQueryStats().getProcessedInputPositions(), MESSAGE_NUM / 2); + assertThat(getQueryInfo(getDistributedQueryRunner(), queryResult).getQueryStats().getProcessedInputPositions()).isEqualTo(MESSAGE_NUM / 2); }); } @@ -155,7 +155,7 @@ private void assertProcessedInputPositions(String sql, long expectedProcessedInp DistributedQueryRunner queryRunner = getDistributedQueryRunner(); assertEventually(() -> { MaterializedResultWithQueryId queryResult = queryRunner.executeWithQueryId(session, sql); - assertEquals(getQueryInfo(queryRunner, queryResult).getQueryStats().getProcessedInputPositions(), expectedProcessedInputPositions); + assertThat(getQueryInfo(queryRunner, queryResult).getQueryStats().getProcessedInputPositions()).isEqualTo(expectedProcessedInputPositions); }); } diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaPlugin.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaPlugin.java index cdd40c58607a..5564583e159b 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaPlugin.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaPlugin.java @@ -28,8 +28,8 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static io.airlift.testing.Assertions.assertInstanceOf; import static org.apache.kafka.common.security.auth.SecurityProtocol.SSL; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertNotNull; public class TestKafkaPlugin { @@ -52,7 +52,7 @@ public void testSpinup() .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext()); - assertNotNull(connector); + assertThat(connector).isNotNull(); connector.shutdown(); } @@ -89,7 +89,7 @@ public void testSslSpinup() .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext()); - assertNotNull(connector); + assertThat(connector).isNotNull(); connector.shutdown(); } @@ -198,7 +198,7 @@ public void testConfigResourceSpinup() .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext()); - assertNotNull(connector); + assertThat(connector).isNotNull(); connector.shutdown(); } diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaSecurityConfig.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaSecurityConfig.java index 391f3cf5d95f..381d03e96963 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaSecurityConfig.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaSecurityConfig.java @@ -14,7 +14,7 @@ package io.trino.plugin.kafka; import com.google.common.collect.ImmutableMap; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Map; diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestMinimalFunctionality.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestMinimalFunctionality.java index d8208e3edcee..33f8dacbc65c 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestMinimalFunctionality.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestMinimalFunctionality.java @@ -27,8 +27,8 @@ import static io.trino.plugin.kafka.util.TestUtils.createEmptyTopicDescription; import static java.util.UUID.randomUUID; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertTrue; @Execution(SAME_THREAD) public class TestMinimalFunctionality @@ -54,7 +54,7 @@ protected QueryRunner createQueryRunner() @Test public void testTopicExists() { - assertTrue(getQueryRunner().listTables(getSession(), "kafka", "default").contains(QualifiedObjectName.valueOf("kafka.default." + topicName))); + assertThat(getQueryRunner().listTables(getSession(), "kafka", "default").contains(QualifiedObjectName.valueOf("kafka.default." + topicName))).isTrue(); } @Test diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestCustomJsonDateTimeFormatter.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestCustomJsonDateTimeFormatter.java index 66abeb2f2088..67593dd0d5a3 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestCustomJsonDateTimeFormatter.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestCustomJsonDateTimeFormatter.java @@ -31,7 +31,7 @@ import static io.trino.testing.DateTimeTestingUtils.sqlTimeWithTimeZoneOf; import static io.trino.testing.DateTimeTestingUtils.sqlTimestampOf; import static io.trino.testing.DateTimeTestingUtils.sqlTimestampWithTimeZoneOf; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestCustomJsonDateTimeFormatter { @@ -43,31 +43,31 @@ private static JsonDateTimeFormatter getFormatter(String formatHint) private static void testDate(SqlDate value, String formatHint, String expectedLiteral) { String actualLiteral = getFormatter(formatHint).formatDate(value); - assertEquals(actualLiteral, expectedLiteral); + assertThat(actualLiteral).isEqualTo(expectedLiteral); } private static void testTime(SqlTime value, String formatHint, int precision, String expectedLiteral) { String actualLiteral = getFormatter(formatHint).formatTime(value, precision); - assertEquals(actualLiteral, expectedLiteral); + assertThat(actualLiteral).isEqualTo(expectedLiteral); } private static void testTimeWithTZ(SqlTimeWithTimeZone value, String formatHint, String expectedLiteral) { String actualLiteral = getFormatter(formatHint).formatTimeWithZone(value); - assertEquals(actualLiteral, expectedLiteral); + assertThat(actualLiteral).isEqualTo(expectedLiteral); } private static void testTimestamp(SqlTimestamp value, String formatHint, String expectedLiteral) { String actualLiteral = getFormatter(formatHint).formatTimestamp(value); - assertEquals(actualLiteral, expectedLiteral); + assertThat(actualLiteral).isEqualTo(expectedLiteral); } private static void testTimestampWithTZ(SqlTimestampWithTimeZone value, String formatHint, String expectedLiteral) { String actualLiteral = getFormatter(formatHint).formatTimestampWithZone(value); - assertEquals(actualLiteral, expectedLiteral); + assertThat(actualLiteral).isEqualTo(expectedLiteral); } @Test diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestISO8601JsonDateTimeFormatter.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestISO8601JsonDateTimeFormatter.java index 5718547fa4b4..44eef07302e5 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestISO8601JsonDateTimeFormatter.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestISO8601JsonDateTimeFormatter.java @@ -31,7 +31,7 @@ import static io.trino.testing.DateTimeTestingUtils.sqlTimeWithTimeZoneOf; import static io.trino.testing.DateTimeTestingUtils.sqlTimestampOf; import static io.trino.testing.DateTimeTestingUtils.sqlTimestampWithTimeZoneOf; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestISO8601JsonDateTimeFormatter { @@ -43,31 +43,31 @@ private static JsonDateTimeFormatter getFormatter() private static void testDate(SqlDate value, String expectedLiteral) { String actualLiteral = getFormatter().formatDate(value); - assertEquals(actualLiteral, expectedLiteral); + assertThat(actualLiteral).isEqualTo(expectedLiteral); } private static void testTime(SqlTime value, int precision, String expectedLiteral) { String actualLiteral = getFormatter().formatTime(value, precision); - assertEquals(actualLiteral, expectedLiteral); + assertThat(actualLiteral).isEqualTo(expectedLiteral); } private static void testTimeWithTZ(SqlTimeWithTimeZone value, String expectedLiteral) { String actualLiteral = getFormatter().formatTimeWithZone(value); - assertEquals(actualLiteral, expectedLiteral); + assertThat(actualLiteral).isEqualTo(expectedLiteral); } private static void testTimestamp(SqlTimestamp value, String expectedLiteral) { String actualLiteral = getFormatter().formatTimestamp(value); - assertEquals(actualLiteral, expectedLiteral); + assertThat(actualLiteral).isEqualTo(expectedLiteral); } private static void testTimestampWithTZ(SqlTimestampWithTimeZone value, String expectedLiteral) { String actualLiteral = getFormatter().formatTimestampWithZone(value); - assertEquals(actualLiteral, expectedLiteral); + assertThat(actualLiteral).isEqualTo(expectedLiteral); } @Test diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestMillisecondsJsonDateTimeFormatter.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestMillisecondsJsonDateTimeFormatter.java index bc6613ae6cdc..889782e82047 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestMillisecondsJsonDateTimeFormatter.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestMillisecondsJsonDateTimeFormatter.java @@ -16,7 +16,7 @@ import io.trino.plugin.kafka.encoder.json.format.JsonDateTimeFormatter; import io.trino.spi.type.SqlTimestampWithTimeZone; import io.trino.spi.type.TimeZoneKey; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.time.LocalDateTime; import java.time.LocalTime; @@ -32,7 +32,7 @@ import static java.time.temporal.ChronoField.MILLI_OF_DAY; import static java.time.temporal.ChronoField.NANO_OF_DAY; import static java.util.concurrent.TimeUnit.DAYS; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestMillisecondsJsonDateTimeFormatter { @@ -51,7 +51,7 @@ public void testTime() private void testTime(LocalTime time) { String formatted = getFormatter().formatTime(sqlTimeOf(3, time), 3); - assertEquals(Long.parseLong(formatted), time.getLong(MILLI_OF_DAY)); + assertThat(Long.parseLong(formatted)).isEqualTo(time.getLong(MILLI_OF_DAY)); } @Test @@ -65,7 +65,7 @@ public void testTimestamp() private void testTimestamp(LocalDateTime dateTime) { String formattedStr = getFormatter().formatTimestamp(sqlTimestampOf(3, dateTime)); - assertEquals(Long.parseLong(formattedStr), DAYS.toMillis(dateTime.getLong(EPOCH_DAY)) + scaleNanosToMillis(dateTime.getLong(NANO_OF_DAY))); + assertThat(Long.parseLong(formattedStr)).isEqualTo(DAYS.toMillis(dateTime.getLong(EPOCH_DAY)) + scaleNanosToMillis(dateTime.getLong(NANO_OF_DAY))); } @Test @@ -82,6 +82,6 @@ public void testTimestampWithTimeZone() private void testTimestampWithTimeZone(ZonedDateTime zonedDateTime) { String formattedStr = getFormatter().formatTimestampWithZone(SqlTimestampWithTimeZone.fromInstant(3, zonedDateTime.toInstant(), zonedDateTime.getZone())); - assertEquals(Long.parseLong(formattedStr), zonedDateTime.toInstant().toEpochMilli()); + assertThat(Long.parseLong(formattedStr)).isEqualTo(zonedDateTime.toInstant().toEpochMilli()); } } diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestRFC2822JsonDateTimeFormatter.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestRFC2822JsonDateTimeFormatter.java index a185cd838ea0..3b206c81f185 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestRFC2822JsonDateTimeFormatter.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestRFC2822JsonDateTimeFormatter.java @@ -25,7 +25,7 @@ import static io.trino.spi.type.TimeZoneKey.UTC_KEY; import static io.trino.testing.DateTimeTestingUtils.sqlTimestampOf; import static io.trino.testing.DateTimeTestingUtils.sqlTimestampWithTimeZoneOf; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestRFC2822JsonDateTimeFormatter { @@ -37,13 +37,13 @@ private static JsonDateTimeFormatter getFormatter() private static void testTimestamp(SqlTimestamp value, String expectedLiteral) { String actualLiteral = getFormatter().formatTimestamp(value); - assertEquals(actualLiteral, expectedLiteral); + assertThat(actualLiteral).isEqualTo(expectedLiteral); } private static void testTimestampWithTZ(SqlTimestampWithTimeZone value, String expectedLiteral) { String actualLiteral = getFormatter().formatTimestampWithZone(value); - assertEquals(actualLiteral, expectedLiteral); + assertThat(actualLiteral).isEqualTo(expectedLiteral); } @Test diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestSecondsJsonDateTimeFormatter.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestSecondsJsonDateTimeFormatter.java index 954e593c22e8..922966e89377 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestSecondsJsonDateTimeFormatter.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/json/TestSecondsJsonDateTimeFormatter.java @@ -16,7 +16,7 @@ import io.trino.plugin.kafka.encoder.json.format.JsonDateTimeFormatter; import io.trino.spi.type.SqlTimestampWithTimeZone; import io.trino.spi.type.TimeZoneKey; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.time.LocalDateTime; import java.time.LocalTime; @@ -28,7 +28,7 @@ import static io.trino.testing.DateTimeTestingUtils.sqlTimeOf; import static io.trino.testing.DateTimeTestingUtils.sqlTimestampOf; import static java.time.ZoneOffset.UTC; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestSecondsJsonDateTimeFormatter { @@ -48,7 +48,7 @@ public void testTime() private void testTime(LocalTime time) { String formatted = getFormatter().formatTime(sqlTimeOf(3, time), 3); - assertEquals(Long.parseLong(formatted), time.toSecondOfDay()); + assertThat(Long.parseLong(formatted)).isEqualTo(time.toSecondOfDay()); } @Test @@ -62,7 +62,7 @@ public void testTimestamp() private void testTimestamp(LocalDateTime dateTime) { String formatted = getFormatter().formatTimestamp(sqlTimestampOf(3, dateTime)); - assertEquals(Long.parseLong(formatted), dateTime.toEpochSecond(UTC)); + assertThat(Long.parseLong(formatted)).isEqualTo(dateTime.toEpochSecond(UTC)); } @Test @@ -79,6 +79,6 @@ public void testTimestampWithTimeZone() private void testTimestampWithTimeZone(ZonedDateTime zonedDateTime) { String formattedStr = getFormatter().formatTimestampWithZone(SqlTimestampWithTimeZone.fromInstant(3, zonedDateTime.toInstant(), zonedDateTime.getZone())); - assertEquals(Long.parseLong(formattedStr), zonedDateTime.toEpochSecond()); + assertThat(Long.parseLong(formattedStr)).isEqualTo(zonedDateTime.toEpochSecond()); } } diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/raw/TestRawEncoderMapping.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/raw/TestRawEncoderMapping.java index 2ad35a35dfe5..19751718c3c6 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/raw/TestRawEncoderMapping.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/encoder/raw/TestRawEncoderMapping.java @@ -33,7 +33,7 @@ import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; import static io.trino.spi.type.VarcharType.createVarcharType; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestRawEncoderMapping { @@ -78,6 +78,6 @@ public void testMapping() rowEncoder.appendColumnValue(varArrayBlock, 0); rowEncoder.appendColumnValue(varArrayBlock, 0); - assertEquals(buf.array(), rowEncoder.toByteArray()); + assertThat(buf.array()).isEqualTo(rowEncoder.toByteArray()); } } diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/protobuf/TestKafkaProtobufWithSchemaRegistryMinimalFunctionality.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/protobuf/TestKafkaProtobufWithSchemaRegistryMinimalFunctionality.java index 5501062c9c23..4e8fa7acafd3 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/protobuf/TestKafkaProtobufWithSchemaRegistryMinimalFunctionality.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/protobuf/TestKafkaProtobufWithSchemaRegistryMinimalFunctionality.java @@ -66,7 +66,6 @@ import static org.apache.kafka.clients.producer.ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertTrue; @Execution(SAME_THREAD) public class TestKafkaProtobufWithSchemaRegistryMinimalFunctionality @@ -433,13 +432,13 @@ private void waitUntilTableExists(String tableName) .withMaxAttempts(10) .withDelay(Duration.ofMillis(100)) .build()) - .run(() -> assertTrue(schemaExists())); + .run(() -> assertThat(schemaExists()).isTrue()); Failsafe.with( RetryPolicy.builder() .withMaxAttempts(10) .withDelay(Duration.ofMillis(100)) .build()) - .run(() -> assertTrue(tableExists(tableName))); + .run(() -> assertThat(tableExists(tableName)).isTrue()); } private boolean schemaExists() diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/protobuf/TestProtobufEncoder.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/protobuf/TestProtobufEncoder.java index 8e4c38d5d4c6..41dc2fae1c8e 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/protobuf/TestProtobufEncoder.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/protobuf/TestProtobufEncoder.java @@ -72,7 +72,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.stream.Collectors.joining; import static java.util.stream.IntStream.range; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestProtobufEncoder { @@ -158,7 +158,7 @@ private void testAllDataTypes(String stringData, Integer integerData, Long longD rowEncoder.appendColumnValue(nativeValueToBlock(createTimestampType(6), sqlTimestamp.getEpochMicros()), 0); rowEncoder.appendColumnValue(nativeValueToBlock(VARBINARY, wrappedBuffer(bytesData)), 0); - assertEquals(messageBuilder.build().toByteArray(), rowEncoder.toByteArray()); + assertThat(messageBuilder.build().toByteArray()).isEqualTo(rowEncoder.toByteArray()); } @Test @@ -278,7 +278,7 @@ private void testStructuralDataTypes(String stringData, Integer integerData, Lon }); rowEncoder.appendColumnValue(rowBlockBuilder.build(), 0); - assertEquals(messageBuilder.build().toByteArray(), rowEncoder.toByteArray()); + assertThat(messageBuilder.build().toByteArray()).isEqualTo(rowEncoder.toByteArray()); } @Test @@ -419,7 +419,7 @@ private void testNestedStructuralDataTypes(String stringData, Integer integerDat rowEncoder.appendColumnValue(nestedBlockBuilder.build(), 0); - assertEquals(messageBuilder.build().toByteArray(), rowEncoder.toByteArray()); + assertThat(messageBuilder.build().toByteArray()).isEqualTo(rowEncoder.toByteArray()); } @Test @@ -506,7 +506,7 @@ private void testRowFlattening(String stringData, Integer integerData, Long long rowEncoder.appendColumnValue(nativeValueToBlock(createTimestampType(6), sqlTimestamp.getEpochMicros()), 0); rowEncoder.appendColumnValue(nativeValueToBlock(VARBINARY, wrappedBuffer(bytesData)), 0); - assertEquals(messageBuilder.build().toByteArray(), rowEncoder.toByteArray()); + assertThat(messageBuilder.build().toByteArray()).isEqualTo(rowEncoder.toByteArray()); } private Timestamp getTimestamp(SqlTimestamp sqlTimestamp) diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestAvroConfluentContentSchemaProvider.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestAvroConfluentContentSchemaProvider.java index 0888c7831d04..625b2bfea582 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestAvroConfluentContentSchemaProvider.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestAvroConfluentContentSchemaProvider.java @@ -32,7 +32,6 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestAvroConfluentContentSchemaProvider { @@ -48,8 +47,8 @@ public void testAvroConfluentSchemaProvider() mockSchemaRegistryClient.register(SUBJECT_NAME, schema); AvroConfluentContentSchemaProvider avroConfluentSchemaProvider = new AvroConfluentContentSchemaProvider(mockSchemaRegistryClient); KafkaTableHandle tableHandle = new KafkaTableHandle("default", TOPIC, TOPIC, AvroRowDecoderFactory.NAME, AvroRowDecoderFactory.NAME, Optional.empty(), Optional.empty(), Optional.empty(), Optional.of(SUBJECT_NAME), ImmutableList.of(), TupleDomain.all()); - assertEquals(avroConfluentSchemaProvider.getMessage(tableHandle), Optional.of(schema).map(Schema::toString)); - assertEquals(avroConfluentSchemaProvider.getKey(tableHandle), Optional.empty()); + assertThat(avroConfluentSchemaProvider.getMessage(tableHandle)).isEqualTo(Optional.of(schema).map(Schema::toString)); + assertThat(avroConfluentSchemaProvider.getKey(tableHandle)).isEqualTo(Optional.empty()); KafkaTableHandle invalidTableHandle = new KafkaTableHandle("default", TOPIC, TOPIC, AvroRowDecoderFactory.NAME, AvroRowDecoderFactory.NAME, Optional.empty(), Optional.empty(), Optional.empty(), Optional.of("another-schema"), ImmutableList.of(), TupleDomain.all()); assertThatThrownBy(() -> avroConfluentSchemaProvider.getMessage(invalidTableHandle)) .isInstanceOf(TrinoException.class) diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestAvroConfluentRowDecoder.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestAvroConfluentRowDecoder.java index 87d69e9ffc3b..5a7861eb0f28 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestAvroConfluentRowDecoder.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestAvroConfluentRowDecoder.java @@ -53,9 +53,7 @@ import static io.trino.spi.type.VarbinaryType.VARBINARY; import static io.trino.spi.type.VarcharType.VARCHAR; import static java.lang.String.format; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestAvroConfluentRowDecoder { @@ -171,7 +169,7 @@ private static void assertRowsAreEqual(Optional nonNullSchema = schema.getTypes().stream() .filter(type -> type.getType() != Schema.Type.NULL) .findFirst(); - assertTrue(nonNullSchema.isPresent()); + assertThat(nonNullSchema.isPresent()).isTrue(); if (expected == null) { expected = getOnlyElement(schema.getFields()).defaultVal(); diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestAvroSchemaConverter.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestAvroSchemaConverter.java index 600eeb4ff5c3..6371105a3a79 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestAvroSchemaConverter.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestAvroSchemaConverter.java @@ -42,8 +42,8 @@ import static io.trino.spi.type.RealType.REAL; import static io.trino.spi.type.VarbinaryType.VARBINARY; import static io.trino.spi.type.VarcharType.VARCHAR; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestAvroSchemaConverter { @@ -101,7 +101,7 @@ public void testConvertSchema() .add(new RowType.Field(Optional.of("nested_map"), createType(new ArrayType(VARCHAR)))) .build())) .build(); - assertEquals(types, expected); + assertThat(types).isEqualTo(expected); } @Test @@ -174,7 +174,7 @@ public void testTypesWithDefaults() .add(new RowType.Field(Optional.of("nested_map"), createType(new ArrayType(VARCHAR)))) .build())) .build(); - assertEquals(types, expected); + assertThat(types).isEqualTo(expected); } @Test @@ -227,7 +227,7 @@ public void testNullableColumns() .add(new RowType.Field(Optional.of("nested_map"), createType(new ArrayType(VARCHAR)))) .build())) .build(); - assertEquals(types, expected); + assertThat(types).isEqualTo(expected); } @Test @@ -261,7 +261,7 @@ public void testSimpleSchema() Schema schema = Schema.create(Schema.Type.LONG); AvroSchemaConverter avroSchemaConverter = new AvroSchemaConverter(new TestingTypeManager(), IGNORE); List types = avroSchemaConverter.convertAvroSchema(schema); - assertEquals(getOnlyElement(types), BIGINT); + assertThat(getOnlyElement(types)).isEqualTo(BIGINT); } @Test @@ -280,7 +280,7 @@ public void testEmptyFieldStrategy() List typesForIgnoreStrategy = ImmutableList.of(INTEGER); - assertEquals(new AvroSchemaConverter(new TestingTypeManager(), IGNORE).convertAvroSchema(schema), typesForIgnoreStrategy); + assertThat(new AvroSchemaConverter(new TestingTypeManager(), IGNORE).convertAvroSchema(schema)).isEqualTo(typesForIgnoreStrategy); assertThatThrownBy(() -> new AvroSchemaConverter(new TestingTypeManager(), FAIL).convertAvroSchema(schema)) .isInstanceOf(IllegalStateException.class) @@ -293,7 +293,7 @@ public void testEmptyFieldStrategy() .add(createType(RowType.from(ImmutableList.of(new RowType.Field(Optional.of(DUMMY_FIELD_NAME), BOOLEAN))))) .build(); - assertEquals(new AvroSchemaConverter(new TestingTypeManager(), MARK).convertAvroSchema(schema), typesForAddDummyStrategy); + assertThat(new AvroSchemaConverter(new TestingTypeManager(), MARK).convertAvroSchema(schema)).isEqualTo(typesForAddDummyStrategy); } @Test @@ -323,7 +323,7 @@ public void testEmptyFieldStrategyForEmptySchema() .add(createType(RowType.from(ImmutableList.of(new RowType.Field(Optional.of(DUMMY_FIELD_NAME), BOOLEAN))))) .build(); - assertEquals(new AvroSchemaConverter(new TestingTypeManager(), MARK).convertAvroSchema(schema), typesForAddDummyStrategy); + assertThat(new AvroSchemaConverter(new TestingTypeManager(), MARK).convertAvroSchema(schema)).isEqualTo(typesForAddDummyStrategy); } private static Type createType(Type valueType) diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestConfluentSchemaRegistryTableDescriptionSupplier.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestConfluentSchemaRegistryTableDescriptionSupplier.java index ebb91aab8ff5..ed8cccb9c7be 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestConfluentSchemaRegistryTableDescriptionSupplier.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestConfluentSchemaRegistryTableDescriptionSupplier.java @@ -41,7 +41,6 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertTrue; public class TestConfluentSchemaRegistryTableDescriptionSupplier { @@ -58,7 +57,7 @@ public void testTopicDescription() String subject = topicName + "-value"; SCHEMA_REGISTRY_CLIENT.register(subject, getAvroSchema(schemaTableName.getTableName(), "")); - assertTrue(tableDescriptionSupplier.listTables().contains(schemaTableName)); + assertThat(tableDescriptionSupplier.listTables().contains(schemaTableName)).isTrue(); assertThat(getKafkaTopicDescription(tableDescriptionSupplier, schemaTableName)) .isEqualTo( @@ -83,7 +82,7 @@ public void testCaseInsensitiveSubjectMapping() SCHEMA_REGISTRY_CLIENT.register(subject, getAvroSchema(schemaTableName.getTableName(), "")); - assertTrue(tableDescriptionSupplier.listTables().contains(schemaTableName)); + assertThat(tableDescriptionSupplier.listTables().contains(schemaTableName)).isTrue(); assertThat(getKafkaTopicDescription(tableDescriptionSupplier, schemaTableName)) .isEqualTo( @@ -108,7 +107,7 @@ public void testAmbiguousSubject() SCHEMA_REGISTRY_CLIENT.register(topicName + "-key", getAvroSchema(schemaTableName.getTableName(), "")); SCHEMA_REGISTRY_CLIENT.register(topicName.toUpperCase(ENGLISH) + "-key", getAvroSchema(schemaTableName.getTableName(), "")); - assertTrue(tableDescriptionSupplier.listTables().contains(schemaTableName)); + assertThat(tableDescriptionSupplier.listTables().contains(schemaTableName)).isTrue(); assertThatThrownBy(() -> tableDescriptionSupplier.getTopicDescription( @@ -132,7 +131,7 @@ public void testOverriddenSubject() String overriddenSubject = "overriddenSubject"; SCHEMA_REGISTRY_CLIENT.register(overriddenSubject, getAvroSchema(schemaTableName.getTableName(), "overridden_")); - assertTrue(tableDescriptionSupplier.listTables().contains(schemaTableName)); + assertThat(tableDescriptionSupplier.listTables().contains(schemaTableName)).isTrue(); assertThat(getKafkaTopicDescription(tableDescriptionSupplier, schemaTableName)) .isEqualTo( diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestKafkaWithConfluentSchemaRegistryMinimalFunctionality.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestKafkaWithConfluentSchemaRegistryMinimalFunctionality.java index 728814b0da97..6d2be2698508 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestKafkaWithConfluentSchemaRegistryMinimalFunctionality.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestKafkaWithConfluentSchemaRegistryMinimalFunctionality.java @@ -55,7 +55,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertTrue; @Execution(SAME_THREAD) public class TestKafkaWithConfluentSchemaRegistryMinimalFunctionality @@ -244,7 +243,7 @@ public void testUnsupportedFormat() .put(VALUE_SERIALIZER_CLASS_CONFIG, KafkaJsonSchemaSerializer.class.getName()) .buildOrThrow()); - assertTrue(tableExists(topicName)); + assertThat(tableExists(topicName)).isTrue(); String errorMessage = "Not supported schema: JSON"; assertThatThrownBy(() -> getQueryRunner().execute("SHOW COLUMNS FROM " + toDoubleQuoted(topicName))) @@ -368,13 +367,13 @@ private void waitUntilTableExists(String tableName) .withMaxAttempts(10) .withDelay(Duration.ofMillis(100)) .build()) - .run(() -> assertTrue(schemaExists())); + .run(() -> assertThat(schemaExists()).isTrue()); Failsafe.with( RetryPolicy.builder() .withMaxAttempts(10) .withDelay(Duration.ofMillis(100)) .build()) - .run(() -> assertTrue(tableExists(tableName))); + .run(() -> assertThat(tableExists(tableName)).isTrue()); } private boolean schemaExists() diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/utils/TestPropertiesUtils.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/utils/TestPropertiesUtils.java index e9328d52dc95..5744251a2306 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/utils/TestPropertiesUtils.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/utils/TestPropertiesUtils.java @@ -27,7 +27,7 @@ import java.util.Properties; import static io.trino.plugin.kafka.utils.PropertiesUtils.readProperties; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestPropertiesUtils { @@ -57,7 +57,7 @@ public void testReadPropertiesOverwritten() Map result = readProperties(Arrays.asList(firstFile, secondFile)); - assertEquals(result, expected); + assertThat(result).isEqualTo(expected); } private File writePropertiesToFile(Properties properties) diff --git a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestRecordAccess.java b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestRecordAccess.java index 767bf5c03c05..b00f2d35bee2 100644 --- a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestRecordAccess.java +++ b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestRecordAccess.java @@ -27,9 +27,11 @@ import io.trino.testing.MaterializedResult; import io.trino.testing.MaterializedRow; import io.trino.testing.StandaloneQueryRunner; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -44,6 +46,8 @@ import static io.trino.transaction.TransactionBuilder.transaction; import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; @@ -54,7 +58,8 @@ * the plug in without requiring an actual Kinesis connection. It uses the mock * kinesis client so no AWS activity will occur. */ -@Test(singleThreaded = true) +@TestInstance(PER_CLASS) +@Execution(SAME_THREAD) public class TestRecordAccess { private static final Logger log = Logger.get(TestRecordAccess.class); @@ -71,7 +76,7 @@ public class TestRecordAccess private StandaloneQueryRunner queryRunner; private MockKinesisClient mockClient; - @BeforeClass + @BeforeAll public void start() { dummyStreamName = "test123"; @@ -82,7 +87,7 @@ public void start() mockClient = TestUtils.installKinesisPlugin(queryRunner); } - @AfterClass(alwaysRun = true) + @AfterAll public void stop() { queryRunner.close(); diff --git a/plugin/trino-mariadb/pom.xml b/plugin/trino-mariadb/pom.xml index 35f782cf91fd..8f8b8ce15e84 100644 --- a/plugin/trino-mariadb/pom.xml +++ b/plugin/trino-mariadb/pom.xml @@ -203,33 +203,5 @@ testcontainers test - - - org.testng - testng - test - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - - diff --git a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/BaseMariaDbConnectorTest.java b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/BaseMariaDbConnectorTest.java index 347ebe78610e..cb09c5dbec89 100644 --- a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/BaseMariaDbConnectorTest.java +++ b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/BaseMariaDbConnectorTest.java @@ -30,7 +30,6 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertFalse; public abstract class BaseMariaDbConnectorTest extends BaseJdbcConnectorTest @@ -294,10 +293,10 @@ public void testInsertIntoNotNullColumn() public void testNativeQueryCreateStatement() { // Override because MariaDB returns a ResultSet metadata with no columns for CREATE statement. - assertFalse(getQueryRunner().tableExists(getSession(), "numbers")); + assertThat(getQueryRunner().tableExists(getSession(), "numbers")).isFalse(); assertThatThrownBy(() -> query("SELECT * FROM TABLE(system.query(query => 'CREATE TABLE tpch.numbers(n INTEGER)'))")) .hasMessageContaining("descriptor has no fields"); - assertFalse(getQueryRunner().tableExists(getSession(), "numbers")); + assertThat(getQueryRunner().tableExists(getSession(), "numbers")).isFalse(); } @Test diff --git a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/BaseMariaDbTableStatisticsTest.java b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/BaseMariaDbTableStatisticsTest.java index c764453dfb06..a4c726df17b1 100644 --- a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/BaseMariaDbTableStatisticsTest.java +++ b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/BaseMariaDbTableStatisticsTest.java @@ -42,9 +42,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.withinPercentage; import static org.junit.jupiter.api.Assumptions.abort; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; public abstract class BaseMariaDbTableStatisticsTest extends BaseJdbcTableStatisticsTest @@ -161,17 +158,29 @@ public void testAllNulls() } if ((columnName.equals("orderpriority") || columnName.equals("comment")) && varcharNdvToExpected.apply(2) == null) { - assertNull(row.getField(2), "NDV for " + columnName); - assertNull(row.getField(3), "null fraction for " + columnName); + assertThat(row.getField(2)) + .describedAs("NDV for " + columnName) + .isNull(); + assertThat(row.getField(3)) + .describedAs("null fraction for " + columnName) + .isNull(); } else { - assertNotNull(row.getField(2), "NDV for " + columnName); + assertThat(row.getField(2)) + .describedAs("NDV for " + columnName) + .isNotNull(); assertThat((Double) row.getField(2)).as("NDV for " + columnName).isBetween(0.0, 2.0); - assertEquals(row.getField(3), nullFractionToExpected.apply(1.0), "null fraction for " + columnName); + assertThat(row.getField(3)) + .describedAs("null fraction for " + columnName) + .isEqualTo(nullFractionToExpected.apply(1.0)); } - assertNull(row.getField(4), "min"); - assertNull(row.getField(5), "max"); + assertThat(row.getField(4)) + .describedAs("min") + .isNull(); + assertThat(row.getField(5)) + .describedAs("max") + .isNull(); } double cardinality = getTableCardinalityFromStats(statsResult); if (cardinality != 15.0) { @@ -359,7 +368,7 @@ protected void assertColumnStats(MaterializedResult statsResult, Map columnNdvs, Map columnNullFractions) { - assertEquals(columnNdvs.keySet(), columnNullFractions.keySet()); + assertThat(columnNdvs.keySet()).isEqualTo(columnNullFractions.keySet()); List reportedColumns = stream(statsResult) .map(row -> row.getField(0)) // column name .filter(Objects::nonNull) @@ -390,7 +399,9 @@ protected void assertColumnStats(MaterializedResult statsResult, Map ndvAssertion = assertThat(distinctCount).as("NDV for " + columnName); if (expectedNdv == null) { ndvAssertion.isNull(); - assertNull(nullsFraction, "null fraction for " + columnName); + assertThat(nullsFraction) + .describedAs("null fraction for " + columnName) + .isNull(); } else { ndvAssertion.isBetween(expectedNdv * 0.5, min(expectedNdv * 4.0, tableCardinality)); // [-50%, +300%] but no more than row count @@ -405,21 +416,25 @@ protected void assertColumnStats(MaterializedResult statsResult, Map columnMapping = JDBC_CLIENT.toColumnMapping(SESSION, null, result.get().getJdbcTypeHandle()); - assertTrue(columnMapping.isPresent(), "No mapping for: " + result.get().getJdbcTypeHandle()); - assertEquals(columnMapping.get().getType(), aggregateFunction.getOutputType()); + assertThat(columnMapping.isPresent()) + .describedAs("No mapping for: " + result.get().getJdbcTypeHandle()) + .isTrue(); + assertThat(columnMapping.get().getType()).isEqualTo(aggregateFunction.getOutputType()); } } } diff --git a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbJdbcConfig.java b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbJdbcConfig.java index ae18de64de00..cab9fe1f9250 100644 --- a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbJdbcConfig.java +++ b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbJdbcConfig.java @@ -15,18 +15,17 @@ import org.junit.jupiter.api.Test; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestMariaDbJdbcConfig { @Test public void testIsUrlValid() { - assertTrue(isUrlValid("jdbc:mariadb://example.net:3306")); - assertTrue(isUrlValid("jdbc:mariadb://example.net:3306/")); - assertFalse(isUrlValid("jdbc:notmariadb://example.net:3306")); - assertFalse(isUrlValid("jdbc:notmariadb://example.net:3306/")); + assertThat(isUrlValid("jdbc:mariadb://example.net:3306")).isTrue(); + assertThat(isUrlValid("jdbc:mariadb://example.net:3306/")).isTrue(); + assertThat(isUrlValid("jdbc:notmariadb://example.net:3306")).isFalse(); + assertThat(isUrlValid("jdbc:notmariadb://example.net:3306/")).isFalse(); } private static boolean isUrlValid(String url) @@ -39,9 +38,9 @@ private static boolean isUrlValid(String url) @Test public void testIsUrlWithoutDatabase() { - assertTrue(isUrlWithoutDatabase("jdbc:mariadb://example.net:3306")); - assertTrue(isUrlWithoutDatabase("jdbc:mariadb://example.net:3306/")); - assertFalse(isUrlWithoutDatabase("jdbc:mariadb://example.net:3306/somedatabase")); + assertThat(isUrlWithoutDatabase("jdbc:mariadb://example.net:3306")).isTrue(); + assertThat(isUrlWithoutDatabase("jdbc:mariadb://example.net:3306/")).isTrue(); + assertThat(isUrlWithoutDatabase("jdbc:mariadb://example.net:3306/somedatabase")).isFalse(); } private static boolean isUrlWithoutDatabase(String url) diff --git a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbTypeMapping.java b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbTypeMapping.java index 4c6e4d1f5641..ad153999d3c9 100644 --- a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbTypeMapping.java +++ b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbTypeMapping.java @@ -28,8 +28,10 @@ import io.trino.testing.sql.SqlExecutor; import io.trino.testing.sql.TestTable; import io.trino.testing.sql.TrinoSqlExecutor; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.math.RoundingMode; import java.time.LocalDate; @@ -64,10 +66,14 @@ import static java.math.RoundingMode.UNNECESSARY; import static java.time.ZoneOffset.UTC; import static java.util.Arrays.asList; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; /** * @see MariaDB data types */ +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestMariaDbTypeMapping extends AbstractTestQueryFramework { @@ -79,7 +85,7 @@ public class TestMariaDbTypeMapping // minutes offset change since 1970-01-01, no DST private final ZoneId kathmandu = ZoneId.of("Asia/Kathmandu"); - @BeforeClass + @BeforeAll public void setUp() { checkState(jvmZone.getId().equals("America/Bahia_Banderas"), "This test assumes certain JVM time zone"); From 84874e2d4ad2253672843e3b97b9c116481f6343 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Tue, 14 Nov 2023 14:14:22 -0800 Subject: [PATCH 260/587] Remove unused method --- .../mariadb/TestMariaDbCaseInsensitiveMapping.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbCaseInsensitiveMapping.java b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbCaseInsensitiveMapping.java index b8a9241c7a0a..698227f8e23c 100644 --- a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbCaseInsensitiveMapping.java +++ b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbCaseInsensitiveMapping.java @@ -18,7 +18,6 @@ import io.trino.plugin.jdbc.BaseCaseInsensitiveMappingTest; import io.trino.testing.QueryRunner; import io.trino.testing.sql.SqlExecutor; -import org.junit.jupiter.api.Test; import java.nio.file.Path; @@ -70,12 +69,4 @@ protected String quoted(String name) name = name.replace(identifierQuote, identifierQuote + identifierQuote); return identifierQuote + name + identifierQuote; } - - @Test - public void forceTestNgToRespectSingleThreaded() - { - // TODO: Remove after updating TestNG to 7.4.0+ (https://github.com/trinodb/trino/issues/8571) - // TestNG doesn't enforce @Test(singleThreaded = true) when tests are defined in base class. According to - // https://github.com/cbeust/testng/issues/2361#issuecomment-688393166 a workaround it to add a dummy test to the leaf test class. - } } From 8514bd9745ed4f2ec73b1a94ec4e97edcff50a90 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 15 Nov 2023 17:11:08 +0100 Subject: [PATCH 261/587] Migrate TestHivePlugin to JUnit This is apparently the last `Test*Plugin` class to migrate. --- .../io/trino/plugin/hive/TestHivePlugin.java | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHivePlugin.java b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHivePlugin.java index b1dc799428d0..ce7d26914f9a 100644 --- a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHivePlugin.java +++ b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHivePlugin.java @@ -19,11 +19,13 @@ import io.trino.spi.connector.Connector; import io.trino.spi.connector.ConnectorFactory; import io.trino.testing.TestingConnectorContext; -import org.testng.annotations.AfterClass; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.io.IOException; @@ -41,28 +43,31 @@ import static java.nio.file.Files.createTempDirectory; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -@Test(singleThreaded = true) // see @BeforeMethod +@TestInstance(PER_CLASS) +@Execution(SAME_THREAD) // see @BeforeEach public class TestHivePlugin { private Path tempDirectory; - @BeforeClass + @BeforeAll public void setup() throws IOException { tempDirectory = createTempDirectory(getClass().getSimpleName()); } - @AfterClass(alwaysRun = true) + @AfterAll public void tearDown() throws IOException { deleteRecursively(tempDirectory, ALLOW_INSECURE); } - @AfterMethod(alwaysRun = true) - @BeforeMethod + @AfterEach + @BeforeEach public void deinitializeRubix() { // revert static rubix initialization done by other tests From 999a4ab22e4f4fb95a718c065cf06e7f2d093689 Mon Sep 17 00:00:00 2001 From: Marius Grama Date: Mon, 13 Nov 2023 13:36:28 +0100 Subject: [PATCH 262/587] Expand docs for Hive schema evolution type conversions --- docs/src/main/sphinx/connector/hive.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/src/main/sphinx/connector/hive.md b/docs/src/main/sphinx/connector/hive.md index cc715695b1f2..32bc0490df4d 100644 --- a/docs/src/main/sphinx/connector/hive.md +++ b/docs/src/main/sphinx/connector/hive.md @@ -636,19 +636,21 @@ type conversions. * - Data type - Converted to +* - `BOOLEAN` + - `VARCHAR` * - `VARCHAR` - - `TINYINT`, `SMALLINT`, `INTEGER`, `BIGINT`, `TIMESTAMP`, `DATE`, as well as + - `TINYINT`, `SMALLINT`, `INTEGER`, `BIGINT`, `DOUBLE`, `TIMESTAMP`, `DATE`, as well as narrowing conversions for `VARCHAR` * - `CHAR` - narrowing conversions for `CHAR` * - `TINYINT` - - `VARCHAR`, `SMALLINT`, `INTEGER`, `BIGINT` + - `VARCHAR`, `SMALLINT`, `INTEGER`, `BIGINT`, `DOUBLE` * - `SMALLINT` - - `VARCHAR`, `INTEGER`, `BIGINT` + - `VARCHAR`, `INTEGER`, `BIGINT`, `DOUBLE` * - `INTEGER` - - `VARCHAR`, `BIGINT` + - `VARCHAR`, `BIGINT`, `DOUBLE` * - `BIGINT` - - `VARCHAR` + - `VARCHAR`, `DOUBLE` * - `REAL` - `DOUBLE`, `DECIMAL` * - `DOUBLE` @@ -657,7 +659,7 @@ type conversions. - `DOUBLE`, `REAL`, `VARCHAR`, `TINYINT`, `SMALLINT`, `INTEGER`, `BIGINT`, as well as narrowing and widening conversions for `DECIMAL` * - `TIMESTAMP` - - `VARCHAR` + - `VARCHAR`, `DATE` ::: Any conversion failure results in null, which is the same behavior From ef63a5a825936fdae9cf8d12fd2e96421752a26a Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Sat, 11 Nov 2023 18:13:48 -0800 Subject: [PATCH 263/587] Add TracingHiveMetastore --- .../deltalake/TestDeltaLakeMetadata.java | 2 + plugin/trino-hive/pom.xml | 16 +- .../DecoratedHiveMetastoreModule.java | 4 +- .../metastore/HiveMetastoreDecorator.java | 3 +- .../tracing/MetastoreAttributes.java | 35 + .../hive/metastore/tracing/Tracing.java | 62 ++ .../tracing/TracingHiveMetastore.java | 734 ++++++++++++++++++ .../TracingHiveMetastoreDecorator.java | 45 ++ 8 files changed, 893 insertions(+), 8 deletions(-) create mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/tracing/MetastoreAttributes.java create mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/tracing/Tracing.java create mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/tracing/TracingHiveMetastore.java create mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/tracing/TracingHiveMetastoreDecorator.java diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeMetadata.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeMetadata.java index 1ab69d381f78..8b006a851dc3 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeMetadata.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeMetadata.java @@ -22,6 +22,7 @@ import com.google.inject.Scopes; import io.airlift.bootstrap.Bootstrap; import io.airlift.json.JsonModule; +import io.opentelemetry.api.trace.Tracer; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.filesystem.hdfs.HdfsFileSystemFactory; import io.trino.hdfs.HdfsEnvironment; @@ -194,6 +195,7 @@ public void setUp() binder.bind(TypeManager.class).toInstance(context.getTypeManager()); binder.bind(NodeManager.class).toInstance(context.getNodeManager()); binder.bind(PageIndexerFactory.class).toInstance(context.getPageIndexerFactory()); + binder.bind(Tracer.class).toInstance(context.getTracer()); }, // connector modules new DeltaLakeMetastoreModule(), diff --git a/plugin/trino-hive/pom.xml b/plugin/trino-hive/pom.xml index 144933e8198b..f844ae68cb26 100644 --- a/plugin/trino-hive/pom.xml +++ b/plugin/trino-hive/pom.xml @@ -126,11 +126,21 @@ opentelemetry-api + + io.opentelemetry + opentelemetry-context + + io.opentelemetry.instrumentation opentelemetry-aws-sdk-1.11 + + io.opentelemetry.semconv + opentelemetry-semconv + + io.trino trino-cache @@ -283,12 +293,6 @@ runtime - - io.opentelemetry - opentelemetry-context - runtime - - jakarta.xml.bind jakarta.xml.bind-api diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/DecoratedHiveMetastoreModule.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/DecoratedHiveMetastoreModule.java index 262086727b39..9427bfeebd19 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/DecoratedHiveMetastoreModule.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/DecoratedHiveMetastoreModule.java @@ -25,6 +25,7 @@ import io.trino.plugin.hive.metastore.cache.SharedHiveMetastoreCache.CachingHiveMetastoreFactory; import io.trino.plugin.hive.metastore.procedure.FlushMetadataCacheProcedure; import io.trino.plugin.hive.metastore.recording.RecordingHiveMetastoreDecoratorModule; +import io.trino.plugin.hive.metastore.tracing.TracingHiveMetastoreDecorator; import io.trino.spi.procedure.Procedure; import io.trino.spi.security.ConnectorIdentity; @@ -52,7 +53,8 @@ public DecoratedHiveMetastoreModule(boolean installFlushMetadataCacheProcedure) @Override protected void setup(Binder binder) { - newSetBinder(binder, HiveMetastoreDecorator.class); + newSetBinder(binder, HiveMetastoreDecorator.class) + .addBinding().to(TracingHiveMetastoreDecorator.class).in(Scopes.SINGLETON); install(new RecordingHiveMetastoreDecoratorModule()); configBinder(binder).bindConfig(CachingHiveMetastoreConfig.class); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastoreDecorator.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastoreDecorator.java index 1ed3df5d3325..689bc695dbb0 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastoreDecorator.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastoreDecorator.java @@ -17,7 +17,8 @@ public interface HiveMetastoreDecorator { int PRIORITY_PARTITION_PROJECTION = 50; - int PRIORITY_RECORDING = 100; + int PRIORITY_TRACING = 100; + int PRIORITY_RECORDING = 200; int getPriority(); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/tracing/MetastoreAttributes.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/tracing/MetastoreAttributes.java new file mode 100644 index 000000000000..237d0662e1d1 --- /dev/null +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/tracing/MetastoreAttributes.java @@ -0,0 +1,35 @@ +/* + * 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 io.trino.plugin.hive.metastore.tracing; + +import io.opentelemetry.api.common.AttributeKey; + +import static io.opentelemetry.api.common.AttributeKey.longKey; +import static io.opentelemetry.api.common.AttributeKey.stringKey; + +final class MetastoreAttributes +{ + private MetastoreAttributes() {} + + public static final AttributeKey SCHEMA = stringKey("trino.schema"); + public static final AttributeKey SCHEMA_RESPONSE_COUNT = longKey("trino.hive.response.schema_count"); + public static final AttributeKey TABLE = stringKey("trino.table"); + public static final AttributeKey TABLE_RESPONSE_COUNT = longKey("trino.hive.response.table_count"); + public static final AttributeKey PARTITION = stringKey("trino.partition"); + public static final AttributeKey FUNCTION = stringKey("trino.function"); + public static final AttributeKey FUNCTION_RESPONSE_COUNT = longKey("trino.hive.response.function_count"); + public static final AttributeKey ACID_TRANSACTION = stringKey("trino.hive.acid_transaction"); + public static final AttributeKey PARTITION_REQUEST_COUNT = longKey("trino.hive.request.partition_count"); + public static final AttributeKey PARTITION_RESPONSE_COUNT = longKey("trino.hive.response.partition_count"); +} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/tracing/Tracing.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/tracing/Tracing.java new file mode 100644 index 000000000000..5c1148e9fd01 --- /dev/null +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/tracing/Tracing.java @@ -0,0 +1,62 @@ +/* + * 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 io.trino.plugin.hive.metastore.tracing; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.Span; + +import static io.opentelemetry.api.trace.StatusCode.ERROR; +import static io.opentelemetry.semconv.SemanticAttributes.EXCEPTION_ESCAPED; + +final class Tracing +{ + private Tracing() {} + + public static void withTracing(Span span, CheckedRunnable runnable) + throws E + { + withTracing(span, () -> { + runnable.run(); + return null; + }); + } + + public static T withTracing(Span span, CheckedSupplier supplier) + throws E + { + try (var ignored = span.makeCurrent()) { + return supplier.get(); + } + catch (Throwable t) { + span.setStatus(ERROR, t.getMessage()); + span.recordException(t, Attributes.of(EXCEPTION_ESCAPED, true)); + throw t; + } + finally { + span.end(); + } + } + + public interface CheckedRunnable + { + void run() + throws E; + } + + public interface CheckedSupplier + { + T get() + throws E; + } +} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/tracing/TracingHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/tracing/TracingHiveMetastore.java new file mode 100644 index 000000000000..dea02fb2ffd1 --- /dev/null +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/tracing/TracingHiveMetastore.java @@ -0,0 +1,734 @@ +/* + * 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 io.trino.plugin.hive.metastore.tracing; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.Tracer; +import io.trino.hive.thrift.metastore.DataOperationType; +import io.trino.plugin.hive.HiveColumnStatisticType; +import io.trino.plugin.hive.HivePartition; +import io.trino.plugin.hive.HiveType; +import io.trino.plugin.hive.PartitionStatistics; +import io.trino.plugin.hive.acid.AcidOperation; +import io.trino.plugin.hive.acid.AcidTransaction; +import io.trino.plugin.hive.metastore.AcidTransactionOwner; +import io.trino.plugin.hive.metastore.Database; +import io.trino.plugin.hive.metastore.HiveMetastore; +import io.trino.plugin.hive.metastore.HivePrincipal; +import io.trino.plugin.hive.metastore.HivePrivilegeInfo; +import io.trino.plugin.hive.metastore.Partition; +import io.trino.plugin.hive.metastore.PartitionWithStatistics; +import io.trino.plugin.hive.metastore.PrincipalPrivileges; +import io.trino.plugin.hive.metastore.Table; +import io.trino.spi.connector.SchemaTableName; +import io.trino.spi.function.LanguageFunction; +import io.trino.spi.predicate.TupleDomain; +import io.trino.spi.security.RoleGrant; +import io.trino.spi.type.Type; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.OptionalLong; +import java.util.Set; +import java.util.function.Function; + +import static io.trino.plugin.hive.metastore.tracing.MetastoreAttributes.ACID_TRANSACTION; +import static io.trino.plugin.hive.metastore.tracing.MetastoreAttributes.FUNCTION; +import static io.trino.plugin.hive.metastore.tracing.MetastoreAttributes.FUNCTION_RESPONSE_COUNT; +import static io.trino.plugin.hive.metastore.tracing.MetastoreAttributes.PARTITION; +import static io.trino.plugin.hive.metastore.tracing.MetastoreAttributes.PARTITION_REQUEST_COUNT; +import static io.trino.plugin.hive.metastore.tracing.MetastoreAttributes.PARTITION_RESPONSE_COUNT; +import static io.trino.plugin.hive.metastore.tracing.MetastoreAttributes.SCHEMA; +import static io.trino.plugin.hive.metastore.tracing.MetastoreAttributes.SCHEMA_RESPONSE_COUNT; +import static io.trino.plugin.hive.metastore.tracing.MetastoreAttributes.TABLE; +import static io.trino.plugin.hive.metastore.tracing.MetastoreAttributes.TABLE_RESPONSE_COUNT; +import static io.trino.plugin.hive.metastore.tracing.Tracing.withTracing; +import static java.util.Objects.requireNonNull; + +public class TracingHiveMetastore + implements HiveMetastore +{ + private final Tracer tracer; + private final HiveMetastore delegate; + + public TracingHiveMetastore(Tracer tracer, HiveMetastore delegate) + { + this.tracer = requireNonNull(tracer, "tracer is null"); + this.delegate = requireNonNull(delegate, "delegate is null"); + } + + @Override + public Optional getDatabase(String databaseName) + { + Span span = tracer.spanBuilder("HiveMetastore.getDatabase") + .setAttribute(SCHEMA, databaseName) + .startSpan(); + return withTracing(span, () -> delegate.getDatabase(databaseName)); + } + + @Override + public List getAllDatabases() + { + Span span = tracer.spanBuilder("HiveMetastore.getAllDatabases") + .startSpan(); + return withTracing(span, () -> { + List databases = delegate.getAllDatabases(); + span.setAttribute(SCHEMA_RESPONSE_COUNT, databases.size()); + return databases; + }); + } + + @Override + public Optional
getTable(String databaseName, String tableName) + { + Span span = tracer.spanBuilder("HiveMetastore.getTable") + .setAttribute(SCHEMA, databaseName) + .setAttribute(TABLE, tableName) + .startSpan(); + return withTracing(span, () -> delegate.getTable(databaseName, tableName)); + } + + @Override + public Set getSupportedColumnStatistics(Type type) + { + // Tracing is not necessary + return delegate.getSupportedColumnStatistics(type); + } + + @Override + public PartitionStatistics getTableStatistics(Table table) + { + Span span = tracer.spanBuilder("HiveMetastore.getTableStatistics") + .setAttribute(SCHEMA, table.getDatabaseName()) + .setAttribute(TABLE, table.getTableName()) + .startSpan(); + return withTracing(span, () -> delegate.getTableStatistics(table)); + } + + @Override + public Map getPartitionStatistics(Table table, List partitions) + { + Span span = tracer.spanBuilder("HiveMetastore.getPartitionStatistics") + .setAttribute(SCHEMA, table.getDatabaseName()) + .setAttribute(TABLE, table.getTableName()) + .setAttribute(PARTITION_REQUEST_COUNT, (long) partitions.size()) + .startSpan(); + return withTracing(span, () -> { + Map partitionStatistics = delegate.getPartitionStatistics(table, partitions); + span.setAttribute(PARTITION_RESPONSE_COUNT, partitionStatistics.size()); + return partitionStatistics; + }); + } + + @Override + public void updateTableStatistics(String databaseName, String tableName, AcidTransaction transaction, Function update) + { + Span span = tracer.spanBuilder("HiveMetastore.updateTableStatistics") + .setAttribute(SCHEMA, databaseName) + .setAttribute(TABLE, tableName) + .startSpan(); + if (transaction.isAcidTransactionRunning()) { + span.setAttribute(ACID_TRANSACTION, String.valueOf(transaction.getAcidTransactionId())); + } + + withTracing(span, () -> delegate.updateTableStatistics(databaseName, tableName, transaction, update)); + } + + @Override + public void updatePartitionStatistics(Table table, String partitionName, Function update) + { + Span span = tracer.spanBuilder("HiveMetastore.updatePartitionStatistics") + .setAttribute(SCHEMA, table.getDatabaseName()) + .setAttribute(TABLE, table.getTableName()) + .setAttribute(PARTITION, partitionName) + .startSpan(); + withTracing(span, () -> delegate.updatePartitionStatistics(table, partitionName, update)); + } + + @Override + public void updatePartitionStatistics(Table table, Map> updates) + { + Span span = tracer.spanBuilder("HiveMetastore.updatePartitionStatistics") + .setAttribute(SCHEMA, table.getDatabaseName()) + .setAttribute(TABLE, table.getTableName()) + .startSpan(); + withTracing(span, () -> delegate.updatePartitionStatistics(table, updates)); + } + + @Override + public List getAllTables(String databaseName) + { + Span span = tracer.spanBuilder("HiveMetastore.getAllTables") + .setAttribute(SCHEMA, databaseName) + .startSpan(); + return withTracing(span, () -> { + List tables = delegate.getAllTables(databaseName); + span.setAttribute(TABLE_RESPONSE_COUNT, tables.size()); + return tables; + }); + } + + @Override + public Optional> getAllTables() + { + Span span = tracer.spanBuilder("HiveMetastore.getAllTables") + .startSpan(); + return withTracing(span, () -> { + Optional> tables = delegate.getAllTables(); + tables.ifPresent(list -> span.setAttribute(TABLE_RESPONSE_COUNT, list.size())); + return tables; + }); + } + + @Override + public List getTablesWithParameter(String databaseName, String parameterKey, String parameterValue) + { + Span span = tracer.spanBuilder("HiveMetastore.getTablesWithParameter") + .setAttribute(SCHEMA, databaseName) + .setAttribute(TABLE, parameterKey) + .startSpan(); + return withTracing(span, () -> { + List tables = delegate.getTablesWithParameter(databaseName, parameterKey, parameterValue); + span.setAttribute(TABLE_RESPONSE_COUNT, tables.size()); + return tables; + }); + } + + @Override + public List getAllViews(String databaseName) + { + Span span = tracer.spanBuilder("HiveMetastore.getAllViews") + .setAttribute(SCHEMA, databaseName) + .startSpan(); + return withTracing(span, () -> { + List views = delegate.getAllViews(databaseName); + span.setAttribute(TABLE_RESPONSE_COUNT, views.size()); + return views; + }); + } + + @Override + public Optional> getAllViews() + { + Span span = tracer.spanBuilder("HiveMetastore.getAllViews") + .startSpan(); + return withTracing(span, () -> { + Optional> views = delegate.getAllViews(); + views.ifPresent(list -> span.setAttribute(TABLE_RESPONSE_COUNT, list.size())); + return views; + }); + } + + @Override + public void createDatabase(Database database) + { + Span span = tracer.spanBuilder("HiveMetastore.createDatabase") + .setAttribute(SCHEMA, database.getDatabaseName()) + .startSpan(); + withTracing(span, () -> delegate.createDatabase(database)); + } + + @Override + public void dropDatabase(String databaseName, boolean deleteData) + { + Span span = tracer.spanBuilder("HiveMetastore.dropDatabase") + .setAttribute(SCHEMA, databaseName) + .startSpan(); + withTracing(span, () -> delegate.dropDatabase(databaseName, deleteData)); + } + + @Override + public void renameDatabase(String databaseName, String newDatabaseName) + { + Span span = tracer.spanBuilder("HiveMetastore.renameDatabase") + .setAttribute(SCHEMA, databaseName) + .startSpan(); + withTracing(span, () -> delegate.renameDatabase(databaseName, newDatabaseName)); + } + + @Override + public void setDatabaseOwner(String databaseName, HivePrincipal principal) + { + Span span = tracer.spanBuilder("HiveMetastore.setDatabaseOwner") + .setAttribute(SCHEMA, databaseName) + .startSpan(); + withTracing(span, () -> delegate.setDatabaseOwner(databaseName, principal)); + } + + @Override + public void createTable(Table table, PrincipalPrivileges principalPrivileges) + { + Span span = tracer.spanBuilder("HiveMetastore.createTable") + .setAttribute(SCHEMA, table.getDatabaseName()) + .setAttribute(TABLE, table.getTableName()) + .startSpan(); + withTracing(span, () -> delegate.createTable(table, principalPrivileges)); + } + + @Override + public void dropTable(String databaseName, String tableName, boolean deleteData) + { + Span span = tracer.spanBuilder("HiveMetastore.dropTable") + .setAttribute(SCHEMA, databaseName) + .setAttribute(TABLE, tableName) + .startSpan(); + withTracing(span, () -> delegate.dropTable(databaseName, tableName, deleteData)); + } + + @Override + public void replaceTable(String databaseName, String tableName, Table newTable, PrincipalPrivileges principalPrivileges) + { + Span span = tracer.spanBuilder("HiveMetastore.replaceTable") + .setAttribute(SCHEMA, databaseName) + .setAttribute(TABLE, tableName) + .startSpan(); + withTracing(span, () -> delegate.replaceTable(databaseName, tableName, newTable, principalPrivileges)); + } + + @Override + public void renameTable(String databaseName, String tableName, String newDatabaseName, String newTableName) + { + Span span = tracer.spanBuilder("HiveMetastore.renameTable") + .setAttribute(SCHEMA, databaseName) + .setAttribute(TABLE, tableName) + .startSpan(); + withTracing(span, () -> delegate.renameTable(databaseName, tableName, newDatabaseName, newTableName)); + } + + @Override + public void commentTable(String databaseName, String tableName, Optional comment) + { + Span span = tracer.spanBuilder("HiveMetastore.commentTable") + .setAttribute(SCHEMA, databaseName) + .setAttribute(TABLE, tableName) + .startSpan(); + withTracing(span, () -> delegate.commentTable(databaseName, tableName, comment)); + } + + @Override + public void setTableOwner(String databaseName, String tableName, HivePrincipal principal) + { + Span span = tracer.spanBuilder("HiveMetastore.setTableOwner") + .setAttribute(SCHEMA, databaseName) + .setAttribute(TABLE, tableName) + .startSpan(); + withTracing(span, () -> delegate.setTableOwner(databaseName, tableName, principal)); + } + + @Override + public void commentColumn(String databaseName, String tableName, String columnName, Optional comment) + { + Span span = tracer.spanBuilder("HiveMetastore.commentColumn") + .setAttribute(SCHEMA, databaseName) + .setAttribute(TABLE, tableName) + .startSpan(); + withTracing(span, () -> delegate.commentColumn(databaseName, tableName, columnName, comment)); + } + + @Override + public void addColumn(String databaseName, String tableName, String columnName, HiveType columnType, String columnComment) + { + Span span = tracer.spanBuilder("HiveMetastore.addColumn") + .setAttribute(SCHEMA, databaseName) + .setAttribute(TABLE, tableName) + .startSpan(); + withTracing(span, () -> delegate.addColumn(databaseName, tableName, columnName, columnType, columnComment)); + } + + @Override + public void renameColumn(String databaseName, String tableName, String oldColumnName, String newColumnName) + { + Span span = tracer.spanBuilder("HiveMetastore.renameColumn") + .setAttribute(SCHEMA, databaseName) + .setAttribute(TABLE, tableName) + .startSpan(); + withTracing(span, () -> delegate.renameColumn(databaseName, tableName, oldColumnName, newColumnName)); + } + + @Override + public void dropColumn(String databaseName, String tableName, String columnName) + { + Span span = tracer.spanBuilder("HiveMetastore.dropColumn") + .setAttribute(SCHEMA, databaseName) + .setAttribute(TABLE, tableName) + .startSpan(); + withTracing(span, () -> delegate.dropColumn(databaseName, tableName, columnName)); + } + + @Override + public Optional getPartition(Table table, List partitionValues) + { + Span span = tracer.spanBuilder("HiveMetastore.getPartition") + .setAttribute(SCHEMA, table.getDatabaseName()) + .setAttribute(TABLE, table.getTableName()) + .startSpan(); + return withTracing(span, () -> delegate.getPartition(table, partitionValues)); + } + + @Override + public Optional> getPartitionNamesByFilter(String databaseName, String tableName, List columnNames, TupleDomain partitionKeysFilter) + { + Span span = tracer.spanBuilder("HiveMetastore.getPartitionNamesByFilter") + .setAttribute(SCHEMA, databaseName) + .setAttribute(TABLE, tableName) + .startSpan(); + return withTracing(span, () -> { + Optional> partitionNames = delegate.getPartitionNamesByFilter(databaseName, tableName, columnNames, partitionKeysFilter); + partitionNames.ifPresent(partitions -> span.setAttribute(PARTITION_RESPONSE_COUNT, partitions.size())); + return partitionNames; + }); + } + + @Override + public Map> getPartitionsByNames(Table table, List partitionNames) + { + Span span = tracer.spanBuilder("HiveMetastore.getPartitionsByNames") + .setAttribute(SCHEMA, table.getDatabaseName()) + .setAttribute(TABLE, table.getTableName()) + .setAttribute(PARTITION_REQUEST_COUNT, (long) partitionNames.size()) + .startSpan(); + return withTracing(span, () -> { + Map> partitions = delegate.getPartitionsByNames(table, partitionNames); + span.setAttribute(PARTITION_RESPONSE_COUNT, partitions.size()); + return partitions; + }); + } + + @Override + public void addPartitions(String databaseName, String tableName, List partitions) + { + Span span = tracer.spanBuilder("HiveMetastore.addPartitions") + .setAttribute(SCHEMA, databaseName) + .setAttribute(TABLE, tableName) + .setAttribute(PARTITION_REQUEST_COUNT, (long) partitions.size()) + .startSpan(); + withTracing(span, () -> delegate.addPartitions(databaseName, tableName, partitions)); + } + + @Override + public void dropPartition(String databaseName, String tableName, List parts, boolean deleteData) + { + Span span = tracer.spanBuilder("HiveMetastore.dropPartition") + .setAttribute(SCHEMA, databaseName) + .setAttribute(TABLE, tableName) + .startSpan(); + withTracing(span, () -> delegate.dropPartition(databaseName, tableName, parts, deleteData)); + } + + @Override + public void alterPartition(String databaseName, String tableName, PartitionWithStatistics partition) + { + Span span = tracer.spanBuilder("HiveMetastore.alterPartition") + .setAttribute(SCHEMA, databaseName) + .setAttribute(TABLE, tableName) + .setAttribute(PARTITION, partition.getPartitionName()) + .startSpan(); + withTracing(span, () -> delegate.alterPartition(databaseName, tableName, partition)); + } + + @Override + public void createRole(String role, String grantor) + { + Span span = tracer.spanBuilder("HiveMetastore.createRole") + .startSpan(); + withTracing(span, () -> delegate.createRole(role, grantor)); + } + + @Override + public void dropRole(String role) + { + Span span = tracer.spanBuilder("HiveMetastore.dropRole") + .startSpan(); + withTracing(span, () -> delegate.dropRole(role)); + } + + @Override + public Set listRoles() + { + Span span = tracer.spanBuilder("HiveMetastore.listRoles") + .startSpan(); + return withTracing(span, delegate::listRoles); + } + + @Override + public void grantRoles(Set roles, Set grantees, boolean adminOption, HivePrincipal grantor) + { + Span span = tracer.spanBuilder("HiveMetastore.grantRoles") + .startSpan(); + withTracing(span, () -> delegate.grantRoles(roles, grantees, adminOption, grantor)); + } + + @Override + public void revokeRoles(Set roles, Set grantees, boolean adminOption, HivePrincipal grantor) + { + Span span = tracer.spanBuilder("HiveMetastore.revokeRoles") + .startSpan(); + withTracing(span, () -> delegate.revokeRoles(roles, grantees, adminOption, grantor)); + } + + @Override + public Set listRoleGrants(HivePrincipal principal) + { + Span span = tracer.spanBuilder("HiveMetastore.listRoleGrants") + .startSpan(); + return withTracing(span, () -> delegate.listRoleGrants(principal)); + } + + @Override + public void grantTablePrivileges( + String databaseName, + String tableName, + String tableOwner, + HivePrincipal grantee, + HivePrincipal grantor, + Set privileges, + boolean grantOption) + { + Span span = tracer.spanBuilder("HiveMetastore.grantTablePrivileges") + .setAttribute(SCHEMA, databaseName) + .setAttribute(TABLE, tableName) + .startSpan(); + withTracing(span, () -> delegate.grantTablePrivileges(databaseName, tableName, tableOwner, grantee, grantor, privileges, grantOption)); + } + + @Override + public void revokeTablePrivileges( + String databaseName, + String tableName, + String tableOwner, + HivePrincipal grantee, + HivePrincipal grantor, + Set privileges, + boolean grantOption) + { + Span span = tracer.spanBuilder("HiveMetastore.revokeTablePrivileges") + .setAttribute(SCHEMA, databaseName) + .setAttribute(TABLE, tableName) + .startSpan(); + withTracing(span, () -> delegate.revokeTablePrivileges(databaseName, tableName, tableOwner, grantee, grantor, privileges, grantOption)); + } + + @Override + public Set listTablePrivileges(String databaseName, String tableName, Optional tableOwner, Optional principal) + { + Span span = tracer.spanBuilder("HiveMetastore.listTablePrivileges") + .setAttribute(SCHEMA, databaseName) + .setAttribute(TABLE, tableName) + .startSpan(); + return withTracing(span, () -> delegate.listTablePrivileges(databaseName, tableName, tableOwner, principal)); + } + + @Override + public void checkSupportsTransactions() + { + // Tracing is not necessary + delegate.checkSupportsTransactions(); + } + + @Override + public long openTransaction(AcidTransactionOwner transactionOwner) + { + Span span = tracer.spanBuilder("HiveMetastore.openTransaction") + .startSpan(); + return withTracing(span, () -> { + long transactionId = delegate.openTransaction(transactionOwner); + span.setAttribute(ACID_TRANSACTION, String.valueOf(transactionId)); + return transactionId; + }); + } + + @Override + public void commitTransaction(long transactionId) + { + Span span = tracer.spanBuilder("HiveMetastore.commitTransaction") + .setAttribute(ACID_TRANSACTION, String.valueOf(transactionId)) + .startSpan(); + withTracing(span, () -> delegate.commitTransaction(transactionId)); + } + + @Override + public void abortTransaction(long transactionId) + { + Span span = tracer.spanBuilder("HiveMetastore.abortTransaction") + .setAttribute(ACID_TRANSACTION, String.valueOf(transactionId)) + .startSpan(); + withTracing(span, () -> delegate.abortTransaction(transactionId)); + } + + @Override + public void sendTransactionHeartbeat(long transactionId) + { + Span span = tracer.spanBuilder("HiveMetastore.sendTransactionHeartbeat") + .setAttribute(ACID_TRANSACTION, String.valueOf(transactionId)) + .startSpan(); + withTracing(span, () -> delegate.sendTransactionHeartbeat(transactionId)); + } + + @Override + public void acquireSharedReadLock( + AcidTransactionOwner transactionOwner, + String queryId, + long transactionId, + List fullTables, + List partitions) + { + Span span = tracer.spanBuilder("HiveMetastore.acquireSharedReadLock") + .setAttribute(ACID_TRANSACTION, String.valueOf(transactionId)) + .startSpan(); + withTracing(span, () -> delegate.acquireSharedReadLock(transactionOwner, queryId, transactionId, fullTables, partitions)); + } + + @Override + public String getValidWriteIds(List tables, long currentTransactionId) + { + Span span = tracer.spanBuilder("HiveMetastore.getValidWriteIds") + .setAttribute(ACID_TRANSACTION, String.valueOf(currentTransactionId)) + .startSpan(); + return withTracing(span, () -> delegate.getValidWriteIds(tables, currentTransactionId)); + } + + @Override + public Optional getConfigValue(String name) + { + Span span = tracer.spanBuilder("HiveMetastore.getConfigValue") + .startSpan(); + return withTracing(span, () -> delegate.getConfigValue(name)); + } + + @Override + public long allocateWriteId(String dbName, String tableName, long transactionId) + { + Span span = tracer.spanBuilder("HiveMetastore.allocateWriteId") + .setAttribute(SCHEMA, dbName) + .setAttribute(TABLE, tableName) + .setAttribute(ACID_TRANSACTION, String.valueOf(transactionId)) + .startSpan(); + return withTracing(span, () -> delegate.allocateWriteId(dbName, tableName, transactionId)); + } + + @Override + public void acquireTableWriteLock(AcidTransactionOwner transactionOwner, String queryId, long transactionId, String dbName, String tableName, DataOperationType operation, + boolean isDynamicPartitionWrite) + { + Span span = tracer.spanBuilder("HiveMetastore.acquireTableWriteLock") + .setAttribute(SCHEMA, dbName) + .setAttribute(TABLE, tableName) + .setAttribute(ACID_TRANSACTION, String.valueOf(transactionId)) + .startSpan(); + withTracing(span, () -> delegate.acquireTableWriteLock(transactionOwner, queryId, transactionId, dbName, tableName, operation, isDynamicPartitionWrite)); + } + + @Override + public void updateTableWriteId(String dbName, String tableName, long transactionId, long writeId, OptionalLong rowCountChange) + { + Span span = tracer.spanBuilder("HiveMetastore.updateTableWriteId") + .setAttribute(SCHEMA, dbName) + .setAttribute(TABLE, tableName) + .setAttribute(ACID_TRANSACTION, String.valueOf(transactionId)) + .startSpan(); + withTracing(span, () -> delegate.updateTableWriteId(dbName, tableName, transactionId, writeId, rowCountChange)); + } + + @Override + public void addDynamicPartitions(String dbName, String tableName, List partitionNames, long transactionId, long writeId, AcidOperation operation) + { + Span span = tracer.spanBuilder("HiveMetastore.addDynamicPartitions") + .setAttribute(SCHEMA, dbName) + .setAttribute(TABLE, tableName) + .setAttribute(ACID_TRANSACTION, String.valueOf(transactionId)) + .setAttribute(PARTITION_REQUEST_COUNT, (long) partitionNames.size()) + .startSpan(); + withTracing(span, () -> delegate.addDynamicPartitions(dbName, tableName, partitionNames, transactionId, writeId, operation)); + } + + @Override + public void alterTransactionalTable(Table table, long transactionId, long writeId, PrincipalPrivileges principalPrivileges) + { + Span span = tracer.spanBuilder("HiveMetastore.alterTransactionalTable") + .setAttribute(SCHEMA, table.getDatabaseName()) + .setAttribute(TABLE, table.getTableName()) + .setAttribute(ACID_TRANSACTION, String.valueOf(transactionId)) + .startSpan(); + withTracing(span, () -> delegate.alterTransactionalTable(table, transactionId, writeId, principalPrivileges)); + } + + @Override + public boolean functionExists(String databaseName, String functionName, String signatureToken) + { + Span span = tracer.spanBuilder("HiveMetastore.functionExists") + .setAttribute(SCHEMA, databaseName) + .setAttribute(FUNCTION, functionName) + .startSpan(); + return withTracing(span, () -> delegate.functionExists(databaseName, functionName, signatureToken)); + } + + @Override + public Collection getFunctions(String databaseName) + { + Span span = tracer.spanBuilder("HiveMetastore.getFunctions") + .setAttribute(SCHEMA, databaseName) + .startSpan(); + return withTracing(span, () -> { + Collection functions = delegate.getFunctions(databaseName); + span.setAttribute(FUNCTION_RESPONSE_COUNT, functions.size()); + return functions; + }); + } + + @Override + public Collection getFunctions(String databaseName, String functionName) + { + Span span = tracer.spanBuilder("HiveMetastore.getFunctions") + .setAttribute(SCHEMA, databaseName) + .setAttribute(FUNCTION, functionName) + .startSpan(); + return withTracing(span, () -> { + Collection functions = delegate.getFunctions(databaseName, functionName); + span.setAttribute(FUNCTION_RESPONSE_COUNT, functions.size()); + return functions; + }); + } + + @Override + public void createFunction(String databaseName, String functionName, LanguageFunction function) + { + Span span = tracer.spanBuilder("HiveMetastore.createFunction") + .setAttribute(SCHEMA, databaseName) + .setAttribute(FUNCTION, functionName) + .startSpan(); + withTracing(span, () -> delegate.createFunction(databaseName, functionName, function)); + } + + @Override + public void replaceFunction(String databaseName, String functionName, LanguageFunction function) + { + Span span = tracer.spanBuilder("HiveMetastore.replaceFunction") + .setAttribute(SCHEMA, databaseName) + .setAttribute(FUNCTION, functionName) + .startSpan(); + withTracing(span, () -> delegate.replaceFunction(databaseName, functionName, function)); + } + + @Override + public void dropFunction(String databaseName, String functionName, String signatureToken) + { + Span span = tracer.spanBuilder("HiveMetastore.dropFunction") + .setAttribute(SCHEMA, databaseName) + .setAttribute(FUNCTION, functionName) + .startSpan(); + withTracing(span, () -> delegate.dropFunction(databaseName, functionName, signatureToken)); + } +} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/tracing/TracingHiveMetastoreDecorator.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/tracing/TracingHiveMetastoreDecorator.java new file mode 100644 index 000000000000..54aa31bdfe2f --- /dev/null +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/tracing/TracingHiveMetastoreDecorator.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 io.trino.plugin.hive.metastore.tracing; + +import com.google.inject.Inject; +import io.opentelemetry.api.trace.Tracer; +import io.trino.plugin.hive.metastore.HiveMetastore; +import io.trino.plugin.hive.metastore.HiveMetastoreDecorator; + +import static java.util.Objects.requireNonNull; + +public class TracingHiveMetastoreDecorator + implements HiveMetastoreDecorator +{ + private final Tracer tracer; + + @Inject + public TracingHiveMetastoreDecorator(Tracer tracer) + { + this.tracer = requireNonNull(tracer, "tracer is null"); + } + + @Override + public int getPriority() + { + return PRIORITY_TRACING; + } + + @Override + public HiveMetastore decorate(HiveMetastore hiveMetastore) + { + return new TracingHiveMetastore(tracer, hiveMetastore); + } +} From 9452b4fed3a41ddbcc0a54c7703ae375724b37af Mon Sep 17 00:00:00 2001 From: Star Poon Date: Wed, 15 Nov 2023 21:09:38 +0900 Subject: [PATCH 264/587] Fix broken link in doc --- docs/src/main/sphinx/admin/fault-tolerant-execution.md | 2 +- docs/src/main/sphinx/connector/delta-lake.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/main/sphinx/admin/fault-tolerant-execution.md b/docs/src/main/sphinx/admin/fault-tolerant-execution.md index 54185a71f3b8..864b8e442545 100644 --- a/docs/src/main/sphinx/admin/fault-tolerant-execution.md +++ b/docs/src/main/sphinx/admin/fault-tolerant-execution.md @@ -182,7 +182,7 @@ queries/tasks are no longer retried in the event of repeated failures: - `10s` - `QUERY` and `TASK` * - `retry-max-delay` - - Maximum :ref:`time ` that a failed query or task must + - Maximum [time](prop-type-duration) that a failed query or task must wait before it is retried. Wait time is increased on each subsequent failure. May be overridden with the ``retry_max_delay`` [session property](session-properties-definition). diff --git a/docs/src/main/sphinx/connector/delta-lake.md b/docs/src/main/sphinx/connector/delta-lake.md index 1bbd1bb95e9c..4025bbb94b8b 100644 --- a/docs/src/main/sphinx/connector/delta-lake.md +++ b/docs/src/main/sphinx/connector/delta-lake.md @@ -1055,13 +1055,13 @@ keep a backup of the original values if you change them. starts issuing reads of the `max-split-size` size. - `200` * - `delta.max-initial-split-size` - - Sets the initial :ref:`prop-type-data-size` for a single read section + - Sets the initial [](prop-type-data-size) for a single read section assigned to a worker until `max-initial-splits` have been processed. You can also use the corresponding catalog session property `.max_initial_split_size`. - `32MB` * - `delta.max-split-size` - - Sets the largest :ref:`prop-type-data-size` for a single read section + - Sets the largest [](prop-type-data-size) for a single read section assigned to a worker after `max-initial-splits` have been processed. You can also use the corresponding catalog session property `.max_split_size`. From b77f74c7873b58b318873686751763efe925459f Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Wed, 15 Nov 2023 14:05:39 -0800 Subject: [PATCH 265/587] Remove unused methods --- .../plugin/mysql/TestMySqlCaseInsensitiveMapping.java | 9 --------- .../plugin/oracle/TestOracleCaseInsensitiveMapping.java | 9 --------- .../postgresql/TestPostgreSqlCaseInsensitiveMapping.java | 9 --------- .../TestSingleStoreCaseInsensitiveMapping.java | 9 --------- 4 files changed, 36 deletions(-) diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlCaseInsensitiveMapping.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlCaseInsensitiveMapping.java index 841112ebf187..e85357a48a87 100644 --- a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlCaseInsensitiveMapping.java +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlCaseInsensitiveMapping.java @@ -18,7 +18,6 @@ import io.trino.plugin.jdbc.BaseCaseInsensitiveMappingTest; import io.trino.testing.QueryRunner; import io.trino.testing.sql.SqlExecutor; -import org.junit.jupiter.api.Test; import java.nio.file.Path; @@ -71,12 +70,4 @@ protected String quoted(String name) name = name.replace(identifierQuote, identifierQuote + identifierQuote); return identifierQuote + name + identifierQuote; } - - @Test - public void forceTestNgToRespectSingleThreaded() - { - // TODO: Remove after updating TestNG to 7.4.0+ (https://github.com/trinodb/trino/issues/8571) - // TestNG doesn't enforce @Test(singleThreaded = true) when tests are defined in base class. According to - // https://github.com/cbeust/testng/issues/2361#issuecomment-688393166 a workaround it to add a dummy test to the leaf test class. - } } diff --git a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleCaseInsensitiveMapping.java b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleCaseInsensitiveMapping.java index d5b3b2fe0dfa..ec6674c5fa90 100644 --- a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleCaseInsensitiveMapping.java +++ b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleCaseInsensitiveMapping.java @@ -18,7 +18,6 @@ import io.trino.plugin.jdbc.BaseCaseInsensitiveMappingTest; import io.trino.testing.QueryRunner; import io.trino.testing.sql.SqlExecutor; -import org.junit.jupiter.api.Test; import java.nio.file.Path; import java.util.Optional; @@ -94,12 +93,4 @@ protected SqlExecutor onRemoteDatabase() { return oracleServer::execute; } - - @Test - public void forceTestNgToRespectSingleThreaded() - { - // TODO: Remove after updating TestNG to 7.4.0+ (https://github.com/trinodb/trino/issues/8571) - // TestNG doesn't enforce @Test(singleThreaded = true) when tests are defined in base class. According to - // https://github.com/cbeust/testng/issues/2361#issuecomment-688393166 a workaround it to add a dummy test to the leaf test class. - } } diff --git a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlCaseInsensitiveMapping.java b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlCaseInsensitiveMapping.java index 9e115ab6dbf6..b670d0e903d0 100644 --- a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlCaseInsensitiveMapping.java +++ b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlCaseInsensitiveMapping.java @@ -18,7 +18,6 @@ import io.trino.plugin.jdbc.BaseCaseInsensitiveMappingTest; import io.trino.testing.QueryRunner; import io.trino.testing.sql.SqlExecutor; -import org.junit.jupiter.api.Test; import java.nio.file.Path; @@ -62,12 +61,4 @@ protected SqlExecutor onRemoteDatabase() { return postgreSqlServer::execute; } - - @Test - public void forceTestNgToRespectSingleThreaded() - { - // TODO: Remove after updating TestNG to 7.4.0+ (https://github.com/trinodb/trino/issues/8571) - // TestNG doesn't enforce @Test(singleThreaded = true) when tests are defined in base class. According to - // https://github.com/cbeust/testng/issues/2361#issuecomment-688393166 a workaround it to add a dummy test to the leaf test class. - } } diff --git a/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreCaseInsensitiveMapping.java b/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreCaseInsensitiveMapping.java index 698ad64bb05e..cfa0439dc71b 100644 --- a/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreCaseInsensitiveMapping.java +++ b/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreCaseInsensitiveMapping.java @@ -18,7 +18,6 @@ import io.trino.plugin.jdbc.BaseCaseInsensitiveMappingTest; import io.trino.testing.QueryRunner; import io.trino.testing.sql.SqlExecutor; -import org.junit.jupiter.api.Test; import java.nio.file.Path; @@ -70,12 +69,4 @@ protected String quoted(String name) name = name.replace(identifierQuote, identifierQuote + identifierQuote); return identifierQuote + name + identifierQuote; } - - @Test - public void forceTestNgToRespectSingleThreaded() - { - // TODO: Remove after updating TestNG to 7.4.0+ (https://github.com/trinodb/trino/issues/8571) - // TestNG doesn't enforce @Test(singleThreaded = true) when tests are defined in base class. According to - // https://github.com/cbeust/testng/issues/2361#issuecomment-688393166 a workaround it to add a dummy test to the leaf test class. - } } From b561a3399aef3b9445da31812d2dcda75189bbf6 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Wed, 15 Nov 2023 13:33:25 -0800 Subject: [PATCH 266/587] Remove unnecessary data provider --- .../plugin/mongodb/TestMongoPrivileges.java | 32 ++-- .../plugin/mongodb/TestMongoTypeMapping.java | 60 ++++--- ...stMySqlTimeMappingsWithServerTimeZone.java | 105 ++++++++--- .../plugin/mysql/TestMySqlTypeMapping.java | 124 +++++++++---- .../oracle/AbstractTestOracleTypeMapping.java | 70 +++++--- .../trino/plugin/oracle/TestOracleClient.java | 55 +++--- .../phoenix5/TestPhoenixTypeMapping.java | 42 +++-- .../postgresql/TestPostgreSqlClient.java | 107 +++++------- .../postgresql/TestPostgreSqlTypeMapping.java | 110 ++++++++---- .../TestSingleStoreTypeMapping.java | 165 +++++++++++------- .../sqlserver/BaseSqlServerTypeMapping.java | 64 ++++--- .../io/trino/execution/TestDenyOnSchema.java | 60 ++++--- .../io/trino/execution/TestDenyOnTable.java | 76 +++++--- .../io/trino/execution/TestGrantOnSchema.java | 78 +++++---- .../io/trino/execution/TestGrantOnTable.java | 101 +++++++---- .../trino/execution/TestRevokeOnSchema.java | 60 +++---- 16 files changed, 815 insertions(+), 494 deletions(-) diff --git a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoPrivileges.java b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoPrivileges.java index e7b920c47ab3..55623dae3614 100644 --- a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoPrivileges.java +++ b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoPrivileges.java @@ -22,7 +22,6 @@ import io.trino.testing.DistributedQueryRunner; import io.trino.testing.QueryRunner; import org.bson.Document; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.util.List; @@ -34,7 +33,6 @@ import static io.trino.plugin.mongodb.AuthenticatedMongoServer.privilege; import static io.trino.plugin.mongodb.AuthenticatedMongoServer.resource; import static io.trino.plugin.mongodb.AuthenticatedMongoServer.role; -import static io.trino.testing.DataProviders.toDataProvider; import static io.trino.testing.TestingSession.testSessionBuilder; import static java.util.Locale.ENGLISH; import static org.assertj.core.api.Assertions.assertThat; @@ -71,22 +69,28 @@ protected QueryRunner createQueryRunner() } } - @Test(dataProvider = "databases") - public void testSchemasVisibility(String database) + @Test + public void testSchemasVisibility() { - assertQuery("SHOW SCHEMAS FROM " + getCatalogName(database), "VALUES 'information_schema','%s'".formatted(database.toLowerCase(ENGLISH))); + for (String database : DATABASES) { + assertQuery("SHOW SCHEMAS FROM " + getCatalogName(database), "VALUES 'information_schema','%s'".formatted(database.toLowerCase(ENGLISH))); + } } - @Test(dataProvider = "databases") - public void testTablesVisibility(String database) + @Test + public void testTablesVisibility() { - assertQuery("SHOW TABLES FROM %s.%s".formatted(getCatalogName(database), database), "VALUES '%s'".formatted(TEST_COLLECTION.toLowerCase(ENGLISH))); + for (String database : DATABASES) { + assertQuery("SHOW TABLES FROM %s.%s".formatted(getCatalogName(database), database), "VALUES '%s'".formatted(TEST_COLLECTION.toLowerCase(ENGLISH))); + } } - @Test(dataProvider = "databases") - public void testSelectFromTable(String database) + @Test + public void testSelectFromTable() { - assertQuery("SELECT * from %s.%s.%s".formatted(getCatalogName(database), database, TEST_COLLECTION), "VALUES ('abc', 1)"); + for (String database : DATABASES) { + assertQuery("SELECT * from %s.%s.%s".formatted(getCatalogName(database), database, TEST_COLLECTION), "VALUES ('abc', 1)"); + } } private static AuthenticatedMongoServer setupMongoServer() @@ -157,10 +161,4 @@ private static String getPassword(String database) { return database + "pass"; } - - @DataProvider - public static Object[][] databases() - { - return DATABASES.stream().collect(toDataProvider()); - } } diff --git a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoTypeMapping.java b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoTypeMapping.java index 5cbe1a3d893b..854468e5d0f9 100644 --- a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoTypeMapping.java +++ b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoTypeMapping.java @@ -28,7 +28,6 @@ import io.trino.testing.datatype.SqlDataTypeTest; import io.trino.testing.sql.TestTable; import io.trino.testing.sql.TrinoSqlExecutor; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.time.ZoneId; @@ -266,8 +265,19 @@ public void testTime() .execute(getQueryRunner(), trinoCreateAndInsert("test_time")); } - @Test(dataProvider = "sessionZonesDataProvider") - public void testDate(ZoneId sessionZone) + @Test + public void testDate() + { + testDate(UTC); + testDate(ZoneId.systemDefault()); + // no DST in 1970, but has DST in later years (e.g. 2018) + testDate(ZoneId.of("Europe/Vilnius")); + // minutes offset change since 1970-01-01, no DST + testDate(ZoneId.of("Asia/Kathmandu")); + testDate(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testDate(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) @@ -297,8 +307,19 @@ public void testDate(ZoneId sessionZone) .execute(getQueryRunner(), session, trinoCreateAndInsert("test_date")); } - @Test(dataProvider = "sessionZonesDataProvider") - public void testTimestamp(ZoneId sessionZone) + @Test + public void testTimestamp() + { + testTimestamp(UTC); + testTimestamp(ZoneId.systemDefault()); + // no DST in 1970, but has DST in later years (e.g. 2018) + testTimestamp(ZoneId.of("Europe/Vilnius")); + // minutes offset change since 1970-01-01, no DST + testTimestamp(ZoneId.of("Asia/Kathmandu")); + testTimestamp(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testTimestamp(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) @@ -322,8 +343,19 @@ public void testTimestamp(ZoneId sessionZone) .execute(getQueryRunner(), session, trinoCreateAndInsert("test_timestamp")); } - @Test(dataProvider = "sessionZonesDataProvider") - public void testTimestampWithTimeZoneMapping(ZoneId sessionZone) + @Test + public void testTimestampWithTimeZoneMapping() + { + testTimestampWithTimeZoneMapping(UTC); + testTimestampWithTimeZoneMapping(ZoneId.systemDefault()); + // no DST in 1970, but has DST in later years (e.g. 2018) + testTimestampWithTimeZoneMapping(ZoneId.of("Europe/Vilnius")); + // minutes offset change since 1970-01-01, no DST + testTimestampWithTimeZoneMapping(ZoneId.of("Asia/Kathmandu")); + testTimestampWithTimeZoneMapping(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testTimestampWithTimeZoneMapping(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) @@ -385,20 +417,6 @@ public void testJson() .execute(getQueryRunner(), trinoCreateAndInsert("test_json")); } - @DataProvider - public Object[][] sessionZonesDataProvider() - { - return new Object[][] { - {UTC}, - {ZoneId.systemDefault()}, - // no DST in 1970, but has DST in later years (e.g. 2018) - {ZoneId.of("Europe/Vilnius")}, - // minutes offset change since 1970-01-01, no DST - {ZoneId.of("Asia/Kathmandu")}, - {TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()}, - }; - } - private DataSetup trinoCreateAsSelect(String tableNamePrefix) { return trinoCreateAsSelect(getSession(), tableNamePrefix); diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlTimeMappingsWithServerTimeZone.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlTimeMappingsWithServerTimeZone.java index af7844f723ff..203985ad27a1 100644 --- a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlTimeMappingsWithServerTimeZone.java +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlTimeMappingsWithServerTimeZone.java @@ -28,7 +28,6 @@ import io.trino.testing.sql.TestTable; import io.trino.testing.sql.TrinoSqlExecutor; import org.intellij.lang.annotations.Language; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.sql.Connection; @@ -62,8 +61,19 @@ protected QueryRunner createQueryRunner() return createMySqlQueryRunner(mySqlServer, ImmutableMap.of(), ImmutableMap.of(), ImmutableList.of()); } - @Test(dataProvider = "sessionZonesDataProvider") - public void testDate(ZoneId sessionZone) + @Test + public void testDate() + { + testDate(UTC); + testDate(ZoneId.systemDefault()); + // no DST in 1970, but has DST in later years (e.g. 2018) + testDate(ZoneId.of("Europe/Vilnius")); + // minutes offset change since 1970-01-01, no DST + testDate(ZoneId.of("Asia/Kathmandu")); + testDate(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testDate(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) @@ -89,8 +99,19 @@ public void testDate(ZoneId sessionZone) .execute(getQueryRunner(), session, trinoCreateAndInsert("test_date")); } - @Test(dataProvider = "sessionZonesDataProvider") - public void testTimeFromMySql(ZoneId sessionZone) + @Test + public void testTimeFromMySql() + { + testTimeFromMySql(UTC); + testTimeFromMySql(ZoneId.systemDefault()); + // no DST in 1970, but has DST in later years (e.g. 2018) + testTimeFromMySql(ZoneId.of("Europe/Vilnius")); + // minutes offset change since 1970-01-01, no DST + testTimeFromMySql(ZoneId.of("Asia/Kathmandu")); + testTimeFromMySql(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testTimeFromMySql(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) @@ -114,8 +135,19 @@ public void testTimeFromMySql(ZoneId sessionZone) .execute(getQueryRunner(), session, mysqlCreateAndInsert("tpch.test_time")); } - @Test(dataProvider = "sessionZonesDataProvider") - public void testTimeFromTrino(ZoneId sessionZone) + @Test + public void testTimeFromTrino() + { + testTimeFromTrino(UTC); + testTimeFromTrino(ZoneId.systemDefault()); + // no DST in 1970, but has DST in later years (e.g. 2018) + testTimeFromTrino(ZoneId.of("Europe/Vilnius")); + // minutes offset change since 1970-01-01, no DST + testTimeFromTrino(ZoneId.of("Asia/Kathmandu")); + testTimeFromTrino(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testTimeFromTrino(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) @@ -209,8 +241,19 @@ public void testTimeFromTrino(ZoneId sessionZone) /** * Read {@code DATETIME}s inserted by MySQL as Trino {@code TIMESTAMP}s */ - @Test(dataProvider = "sessionZonesDataProvider") - public void testMySqlDatetimeType(ZoneId sessionZone) + @Test + public void testMySqlDatetimeType() + { + testMySqlDatetimeType(UTC); + testMySqlDatetimeType(ZoneId.systemDefault()); + // no DST in 1970, but has DST in later years (e.g. 2018) + testMySqlDatetimeType(ZoneId.of("Europe/Vilnius")); + // minutes offset change since 1970-01-01, no DST + testMySqlDatetimeType(ZoneId.of("Asia/Kathmandu")); + testMySqlDatetimeType(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testMySqlDatetimeType(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) @@ -274,8 +317,19 @@ public void testMySqlDatetimeType(ZoneId sessionZone) /** * Read {@code TIMESTAMP}s inserted by MySQL as Trino {@code TIMESTAMP WITH TIME ZONE}s */ - @Test(dataProvider = "sessionZonesDataProvider") - public void testTimestampFromMySql(ZoneId sessionZone) + @Test + public void testTimestampFromMySql() + { + testTimestampFromMySql(UTC); + testTimestampFromMySql(ZoneId.systemDefault()); + // no DST in 1970, but has DST in later years (e.g. 2018) + testTimestampFromMySql(ZoneId.of("Europe/Vilnius")); + // minutes offset change since 1970-01-01, no DST + testTimestampFromMySql(ZoneId.of("Asia/Kathmandu")); + testTimestampFromMySql(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testTimestampFromMySql(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) @@ -324,8 +378,19 @@ public void testTimestampFromMySql(ZoneId sessionZone) .execute(getQueryRunner(), session, mysqlCreateAndInsert("tpch.test_timestamp")); } - @Test(dataProvider = "sessionZonesDataProvider") - public void testTimestampFromTrino(ZoneId sessionZone) + @Test + public void testTimestampFromTrino() + { + testTimestampFromTrino(UTC); + testTimestampFromTrino(ZoneId.systemDefault()); + // no DST in 1970, but has DST in later years (e.g. 2018) + testTimestampFromTrino(ZoneId.of("Europe/Vilnius")); + // minutes offset change since 1970-01-01, no DST + testTimestampFromTrino(ZoneId.of("Asia/Kathmandu")); + testTimestampFromTrino(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testTimestampFromTrino(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) @@ -612,20 +677,6 @@ public void testTimestampWithTimeZoneCoercion() .execute(getQueryRunner(), trinoCreateAndInsert("test_timestamp_with_time_zone_coercion")); } - @DataProvider - public Object[][] sessionZonesDataProvider() - { - return new Object[][] { - {UTC}, - {ZoneId.systemDefault()}, - // no DST in 1970, but has DST in later years (e.g. 2018) - {ZoneId.of("Europe/Vilnius")}, - // minutes offset change since 1970-01-01, no DST - {ZoneId.of("Asia/Kathmandu")}, - {TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()}, - }; - } - @Test public void testZeroTimestamp() throws Exception diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlTypeMapping.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlTypeMapping.java index 0102a40dc64c..70a34ac275d8 100644 --- a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlTypeMapping.java +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlTypeMapping.java @@ -31,7 +31,6 @@ import io.trino.testing.sql.TrinoSqlExecutor; import org.intellij.lang.annotations.Language; import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.math.RoundingMode; @@ -431,8 +430,14 @@ public void testDecimalExceedingPrecisionMaxWithNonExceedingIntegerValues() } } - @Test(dataProvider = "testDecimalExceedingPrecisionMaxProvider") - public void testDecimalExceedingPrecisionMaxWithSupportedValues(int typePrecision, int typeScale) + @Test + public void testDecimalExceedingPrecisionMaxWithSupportedValues() + { + testDecimalExceedingPrecisionMaxWithSupportedValues(40, 8); + testDecimalExceedingPrecisionMaxWithSupportedValues(50, 10); + } + + private void testDecimalExceedingPrecisionMaxWithSupportedValues(int typePrecision, int typeScale) { try (TestTable testTable = new TestTable( mySqlServer::execute, @@ -482,15 +487,6 @@ public void testDecimalExceedingPrecisionMaxWithSupportedValues(int typePrecisio } } - @DataProvider - public Object[][] testDecimalExceedingPrecisionMaxProvider() - { - return new Object[][] { - {40, 8}, - {50, 10}, - }; - } - private Session sessionWithDecimalMappingAllowOverflow(RoundingMode roundingMode, int scale) { return Session.builder(getSession()) @@ -556,8 +552,19 @@ public void testBinary() .execute(getQueryRunner(), mysqlCreateAndInsert("tpch.test_binary")); } - @Test(dataProvider = "sessionZonesDataProvider") - public void testDate(ZoneId sessionZone) + @Test + public void testDate() + { + testDate(UTC); + testDate(ZoneId.systemDefault()); + // no DST in 1970, but has DST in later years (e.g. 2018) + testDate(ZoneId.of("Europe/Vilnius")); + // minutes offset change since 1970-01-01, no DST + testDate(ZoneId.of("Asia/Kathmandu")); + testDate(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testDate(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) @@ -583,8 +590,19 @@ public void testDate(ZoneId sessionZone) .execute(getQueryRunner(), session, trinoCreateAndInsert("test_date")); } - @Test(dataProvider = "sessionZonesDataProvider") - public void testTimeFromMySql(ZoneId sessionZone) + @Test + public void testTimeFromMySql() + { + testTimestampFromMySql(UTC); + testTimestampFromMySql(ZoneId.systemDefault()); + // no DST in 1970, but has DST in later years (e.g. 2018) + testTimestampFromMySql(ZoneId.of("Europe/Vilnius")); + // minutes offset change since 1970-01-01, no DST + testTimestampFromMySql(ZoneId.of("Asia/Kathmandu")); + testTimestampFromMySql(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testTimeFromMySql(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) @@ -608,8 +626,19 @@ public void testTimeFromMySql(ZoneId sessionZone) .execute(getQueryRunner(), session, mysqlCreateAndInsert("tpch.test_time")); } - @Test(dataProvider = "sessionZonesDataProvider") - public void testTimeFromTrino(ZoneId sessionZone) + @Test + public void testTimeFromTrino() + { + testTimeFromTrino(UTC); + testTimeFromTrino(ZoneId.systemDefault()); + // no DST in 1970, but has DST in later years (e.g. 2018) + testTimeFromTrino(ZoneId.of("Europe/Vilnius")); + // minutes offset change since 1970-01-01, no DST + testTimeFromTrino(ZoneId.of("Asia/Kathmandu")); + testTimeFromTrino(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testTimeFromTrino(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) @@ -703,8 +732,19 @@ public void testTimeFromTrino(ZoneId sessionZone) /** * Read {@code DATETIME}s inserted by MySQL as Trino {@code TIMESTAMP}s */ - @Test(dataProvider = "sessionZonesDataProvider") - public void testMySqlDatetimeType(ZoneId sessionZone) + @Test + public void testMySqlDatetimeType() + { + testMySqlDatetimeType(UTC); + testMySqlDatetimeType(ZoneId.systemDefault()); + // no DST in 1970, but has DST in later years (e.g. 2018) + testMySqlDatetimeType(ZoneId.of("Europe/Vilnius")); + // minutes offset change since 1970-01-01, no DST + testMySqlDatetimeType(ZoneId.of("Asia/Kathmandu")); + testMySqlDatetimeType(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testMySqlDatetimeType(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) @@ -768,8 +808,19 @@ public void testMySqlDatetimeType(ZoneId sessionZone) /** * Read {@code TIMESTAMP}s inserted by MySQL as Trino {@code TIMESTAMP WITH TIME ZONE}s */ - @Test(dataProvider = "sessionZonesDataProvider") - public void testTimestampFromMySql(ZoneId sessionZone) + @Test + public void testTimestampFromMySql() + { + testTimestampFromMySql(UTC); + testTimestampFromMySql(ZoneId.systemDefault()); + // no DST in 1970, but has DST in later years (e.g. 2018) + testTimestampFromMySql(ZoneId.of("Europe/Vilnius")); + // minutes offset change since 1970-01-01, no DST + testTimestampFromMySql(ZoneId.of("Asia/Kathmandu")); + testTimestampFromMySql(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testTimestampFromMySql(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) @@ -817,8 +868,19 @@ public void testTimestampFromMySql(ZoneId sessionZone) .execute(getQueryRunner(), session, mysqlCreateAndInsert("tpch.test_timestamp")); } - @Test(dataProvider = "sessionZonesDataProvider") - public void testTimestampFromTrino(ZoneId sessionZone) + @Test + public void testTimestampFromTrino() + { + testTimestampFromTrino(UTC); + testTimestampFromTrino(ZoneId.systemDefault()); + // no DST in 1970, but has DST in later years (e.g. 2018) + testTimestampFromTrino(ZoneId.of("Europe/Vilnius")); + // minutes offset change since 1970-01-01, no DST + testTimestampFromTrino(ZoneId.of("Asia/Kathmandu")); + testTimestampFromTrino(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testTimestampFromTrino(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) @@ -1105,20 +1167,6 @@ public void testTimestampWithTimeZoneCoercion() .execute(getQueryRunner(), trinoCreateAndInsert("test_timestamp_with_time_zone_coercion")); } - @DataProvider - public Object[][] sessionZonesDataProvider() - { - return new Object[][] { - {UTC}, - {ZoneId.systemDefault()}, - // no DST in 1970, but has DST in later years (e.g. 2018) - {ZoneId.of("Europe/Vilnius")}, - // minutes offset change since 1970-01-01, no DST - {ZoneId.of("Asia/Kathmandu")}, - {TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()}, - }; - } - @Test public void testZeroTimestamp() throws Exception diff --git a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/AbstractTestOracleTypeMapping.java b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/AbstractTestOracleTypeMapping.java index 55ddb20ab1e5..20aaf45b95a0 100644 --- a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/AbstractTestOracleTypeMapping.java +++ b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/AbstractTestOracleTypeMapping.java @@ -29,7 +29,6 @@ import io.trino.testing.sql.TestTable; import io.trino.testing.sql.TrinoSqlExecutor; import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.math.RoundingMode; @@ -636,8 +635,18 @@ public void testVarbinary() .execute(getQueryRunner(), oracleCreateAndInsert("test_blob")); } - @Test(dataProvider = "sessionZonesDataProvider") - public void testDate(ZoneId sessionZone) + @Test + public void testDate() + { + testDate(UTC); + testDate(jvmZone); + // using two non-JVM zones so that we don't need to worry what Oracle system zone is + testDate(vilnius); + testDate(kathmandu); + testDate(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testDate(ZoneId sessionZone) { // Note: these test cases are duplicates of those for PostgreSQL and MySQL. @@ -700,8 +709,18 @@ public void testUnsupportedDate() } } - @Test(dataProvider = "sessionZonesDataProvider") - public void testTimestamp(ZoneId sessionZone) + @Test + public void testTimestamp() + { + testTimestamp(UTC); + testTimestamp(jvmZone); + // using two non-JVM zones so that we don't need to worry what Oracle system zone is + testTimestamp(vilnius); + testTimestamp(kathmandu); + testTimestamp(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testTimestamp(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(getTimeZoneKey(sessionZone.getId())) @@ -734,8 +753,18 @@ public void testTimestamp(ZoneId sessionZone) .execute(getQueryRunner(), session, trinoCreateAndInsert(session, "test_timestamp")); } - @Test(dataProvider = "sessionZonesDataProvider") - public void testTimestampNanos(ZoneId sessionZone) + @Test + public void testTimestampNanos() + { + testTimestampNanos(UTC); + testTimestampNanos(jvmZone); + // using two non-JVM zones so that we don't need to worry what Oracle system zone is + testTimestampNanos(vilnius); + testTimestampNanos(kathmandu); + testTimestampNanos(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testTimestampNanos(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(getTimeZoneKey(sessionZone.getId())) @@ -769,8 +798,18 @@ public void testTimestampNanos(ZoneId sessionZone) .execute(getQueryRunner(), session, trinoCreateAndInsert(session, "test_timestamp_nano")); } - @Test(dataProvider = "sessionZonesDataProvider") - public void testTimestampAllPrecisions(ZoneId sessionZone) + @Test + public void testTimestampAllPrecisions() + { + testTimestampAllPrecisions(UTC); + testTimestampAllPrecisions(jvmZone); + // using two non-JVM zones so that we don't need to worry what Oracle system zone is + testTimestampAllPrecisions(vilnius); + testTimestampAllPrecisions(kathmandu); + testTimestampAllPrecisions(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testTimestampAllPrecisions(ZoneId sessionZone) { SqlDataTypeTest tests = SqlDataTypeTest.create() // before epoch @@ -925,19 +964,6 @@ public void testUnsupportedTimestamp() } } - @DataProvider - public Object[][] sessionZonesDataProvider() - { - return new Object[][] { - {UTC}, - {jvmZone}, - // using two non-JVM zones so that we don't need to worry what Oracle system zone is - {vilnius}, - {kathmandu}, - {TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()}, - }; - } - @Test public void testTimestampWithTimeZoneFromTrino() { diff --git a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleClient.java b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleClient.java index 9685fa756e2d..03615dfaed3c 100644 --- a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleClient.java +++ b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleClient.java @@ -24,7 +24,6 @@ import io.trino.spi.type.Type; import io.trino.testing.TestingConnectorSession; import oracle.jdbc.OracleTypes; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.sql.PreparedStatement; @@ -66,8 +65,32 @@ public class TestOracleClient private static final ConnectorSession SESSION = TestingConnectorSession.SESSION; - @Test(dataProvider = "writeMappingsProvider") - public void testTypedNullWriteMapping(Type type, String bindExpression, int nullJdbcType) + @Test + public void testTypedNullWriteMapping() + throws SQLException + { + testTypedNullWriteMapping(BOOLEAN, "?", Types.TINYINT); + testTypedNullWriteMapping(TINYINT, "?", Types.TINYINT); + testTypedNullWriteMapping(SMALLINT, "?", Types.SMALLINT); + testTypedNullWriteMapping(INTEGER, "?", Types.INTEGER); + testTypedNullWriteMapping(BIGINT, "?", Types.BIGINT); + testTypedNullWriteMapping(REAL, "?", Types.REAL); + testTypedNullWriteMapping(DOUBLE, "?", Types.DOUBLE); + testTypedNullWriteMapping(VARBINARY, "?", Types.VARBINARY); + testTypedNullWriteMapping(createCharType(25), "?", Types.NCHAR); + testTypedNullWriteMapping(createDecimalType(16, 6), "?", Types.DECIMAL); + testTypedNullWriteMapping(createDecimalType(36, 12), "?", Types.DECIMAL); + testTypedNullWriteMapping(createUnboundedVarcharType(), "?", Types.VARCHAR); + testTypedNullWriteMapping(createVarcharType(123), "?", Types.VARCHAR); + testTypedNullWriteMapping(TIMESTAMP_SECONDS, "TO_DATE(?, 'SYYYY-MM-DD HH24:MI:SS')", Types.VARCHAR); + testTypedNullWriteMapping(TIMESTAMP_MILLIS, "TO_TIMESTAMP(?, 'SYYYY-MM-DD HH24:MI:SS.FF3')", Types.VARCHAR); + testTypedNullWriteMapping(TIMESTAMP_MICROS, "TO_TIMESTAMP(?, 'SYYYY-MM-DD HH24:MI:SS.FF6')", Types.VARCHAR); + testTypedNullWriteMapping(TIMESTAMP_NANOS, "TO_TIMESTAMP(?, 'SYYYY-MM-DD HH24:MI:SS.FF9')", Types.VARCHAR); + testTypedNullWriteMapping(TIMESTAMP_TZ_MILLIS, "?", OracleTypes.TIMESTAMPTZ); + testTypedNullWriteMapping(DATE, "TO_DATE(?, 'SYYYY-MM-DD')", Types.VARCHAR); + } + + private void testTypedNullWriteMapping(Type type, String bindExpression, int nullJdbcType) throws SQLException { WriteMapping writeMapping = CLIENT.toWriteMapping(SESSION, type); @@ -95,30 +118,4 @@ public void testTypedNullWriteMapping(Type type, String bindExpression, int null writeFunction.setNull(statementProxy, 1325); } - - @DataProvider - public Object[][] writeMappingsProvider() - { - return new Object[][]{ - {BOOLEAN, "?", Types.TINYINT}, - {TINYINT, "?", Types.TINYINT}, - {SMALLINT, "?", Types.SMALLINT}, - {INTEGER, "?", Types.INTEGER}, - {BIGINT, "?", Types.BIGINT}, - {REAL, "?", Types.REAL}, - {DOUBLE, "?", Types.DOUBLE}, - {VARBINARY, "?", Types.VARBINARY}, - {createCharType(25), "?", Types.NCHAR}, - {createDecimalType(16, 6), "?", Types.DECIMAL}, - {createDecimalType(36, 12), "?", Types.DECIMAL}, - {createUnboundedVarcharType(), "?", Types.VARCHAR}, - {createVarcharType(123), "?", Types.VARCHAR}, - {TIMESTAMP_SECONDS, "TO_DATE(?, 'SYYYY-MM-DD HH24:MI:SS')", Types.VARCHAR}, - {TIMESTAMP_MILLIS, "TO_TIMESTAMP(?, 'SYYYY-MM-DD HH24:MI:SS.FF3')", Types.VARCHAR}, - {TIMESTAMP_MICROS, "TO_TIMESTAMP(?, 'SYYYY-MM-DD HH24:MI:SS.FF6')", Types.VARCHAR}, - {TIMESTAMP_NANOS, "TO_TIMESTAMP(?, 'SYYYY-MM-DD HH24:MI:SS.FF9')", Types.VARCHAR}, - {TIMESTAMP_TZ_MILLIS, "?", OracleTypes.TIMESTAMPTZ}, - {DATE, "TO_DATE(?, 'SYYYY-MM-DD')", Types.VARCHAR}, - }; - } } diff --git a/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixTypeMapping.java b/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixTypeMapping.java index a61f8e260073..7d552e893501 100644 --- a/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixTypeMapping.java +++ b/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixTypeMapping.java @@ -30,7 +30,6 @@ import io.trino.testing.sql.TrinoSqlExecutor; import org.intellij.lang.annotations.Language; import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.math.RoundingMode; @@ -519,8 +518,18 @@ public void testDecimalUnspecifiedPrecision() } } - @Test(dataProvider = "sessionZonesDataProvider") - public void testDate(ZoneId sessionZone) + @Test + public void testDate() + { + testDate(UTC); + testDate(jvmZone); + // using two non-JVM zones so that we don't need to worry what Phoenix system zone is + testDate(vilnius); + testDate(kathmandu); + testDate(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testDate(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(getTimeZoneKey(sessionZone.getId())) @@ -575,8 +584,18 @@ public void testDate(ZoneId sessionZone) .execute(getQueryRunner(), session, phoenixCreateAndInsert("tpch.test_date")); } - @Test(dataProvider = "sessionZonesDataProvider") - public void testUnsignedDate(ZoneId sessionZone) + @Test + public void testUnsignedDate() + { + testUnsignedDate(UTC); + testUnsignedDate(jvmZone); + // using two non-JVM zones so that we don't need to worry what Phoenix system zone is + testUnsignedDate(vilnius); + testUnsignedDate(kathmandu); + testUnsignedDate(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testUnsignedDate(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(getTimeZoneKey(sessionZone.getId())) @@ -698,19 +717,6 @@ public void testArrayNulls() } } - @DataProvider - public Object[][] sessionZonesDataProvider() - { - return new Object[][] { - {UTC}, - {jvmZone}, - // using two non-JVM zones so that we don't need to worry what Phoenix system zone is - {vilnius}, - {kathmandu}, - {TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()}, - }; - } - private static void checkIsGap(ZoneId zone, LocalDateTime dateTime) { verify(isGap(zone, dateTime), "Expected %s to be a gap in %s", dateTime, zone); diff --git a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlClient.java b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlClient.java index 45cfc6bcafeb..7e18922d18dd 100644 --- a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlClient.java +++ b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlClient.java @@ -53,7 +53,6 @@ import io.trino.sql.tree.StringLiteral; import io.trino.sql.tree.SymbolReference; import io.trino.testing.TestingConnectorSession; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.sql.Types; @@ -61,7 +60,6 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Optional; -import java.util.stream.Stream; import static com.google.common.collect.ImmutableMap.toImmutableMap; import static io.airlift.slice.Slices.utf8Slice; @@ -72,7 +70,6 @@ import static io.trino.spi.type.VarcharType.createVarcharType; import static io.trino.sql.planner.TestingPlannerContext.PLANNER_CONTEXT; import static io.trino.sql.planner.TypeAnalyzer.createTestingTypeAnalyzer; -import static io.trino.testing.DataProviders.toDataProvider; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; @@ -292,68 +289,58 @@ public void testConvertOrWithAnd() new QueryParameter(BIGINT, Optional.of(44L)))); } - @Test(dataProvider = "testConvertComparisonDataProvider") - public void testConvertComparison(ComparisonExpression.Operator operator) + @Test + public void testConvertComparison() { - Optional converted = JDBC_CLIENT.convertPredicate( - SESSION, - translateToConnectorExpression( - new ComparisonExpression( - operator, - new SymbolReference("c_bigint_symbol"), - LITERAL_ENCODER.toExpression(42L, BIGINT)), - Map.of("c_bigint_symbol", BIGINT)), - Map.of("c_bigint_symbol", BIGINT_COLUMN)); - - switch (operator) { - case EQUAL: - case NOT_EQUAL: - assertThat(converted).isPresent(); - assertThat(converted.get().expression()).isEqualTo(format("(\"c_bigint\") %s (?)", operator.getValue())); - assertThat(converted.get().parameters()).isEqualTo(List.of(new QueryParameter(BIGINT, Optional.of(42L)))); - return; - case LESS_THAN: - case LESS_THAN_OR_EQUAL: - case GREATER_THAN: - case GREATER_THAN_OR_EQUAL: - case IS_DISTINCT_FROM: - // Not supported yet, even for bigint - assertThat(converted).isEmpty(); - return; + for (ComparisonExpression.Operator operator : ComparisonExpression.Operator.values()) { + Optional converted = JDBC_CLIENT.convertPredicate( + SESSION, + translateToConnectorExpression( + new ComparisonExpression( + operator, + new SymbolReference("c_bigint_symbol"), + LITERAL_ENCODER.toExpression(42L, BIGINT)), + Map.of("c_bigint_symbol", BIGINT)), + Map.of("c_bigint_symbol", BIGINT_COLUMN)); + + switch (operator) { + case EQUAL: + case NOT_EQUAL: + assertThat(converted).isPresent(); + assertThat(converted.get().expression()).isEqualTo(format("(\"c_bigint\") %s (?)", operator.getValue())); + assertThat(converted.get().parameters()).isEqualTo(List.of(new QueryParameter(BIGINT, Optional.of(42L)))); + return; + case LESS_THAN: + case LESS_THAN_OR_EQUAL: + case GREATER_THAN: + case GREATER_THAN_OR_EQUAL: + case IS_DISTINCT_FROM: + // Not supported yet, even for bigint + assertThat(converted).isEmpty(); + return; + } + throw new UnsupportedOperationException("Unsupported operator: " + operator); } - throw new UnsupportedOperationException("Unsupported operator: " + operator); - } - - @DataProvider - public static Object[][] testConvertComparisonDataProvider() - { - return Stream.of(ComparisonExpression.Operator.values()) - .collect(toDataProvider()); - } - - @Test(dataProvider = "testConvertArithmeticBinaryDataProvider") - public void testConvertArithmeticBinary(ArithmeticBinaryExpression.Operator operator) - { - ParameterizedExpression converted = JDBC_CLIENT.convertPredicate( - SESSION, - translateToConnectorExpression( - new ArithmeticBinaryExpression( - operator, - new SymbolReference("c_bigint_symbol"), - LITERAL_ENCODER.toExpression(42L, BIGINT)), - Map.of("c_bigint_symbol", BIGINT)), - Map.of("c_bigint_symbol", BIGINT_COLUMN)) - .orElseThrow(); - - assertThat(converted.expression()).isEqualTo(format("(\"c_bigint\") %s (?)", operator.getValue())); - assertThat(converted.parameters()).isEqualTo(List.of(new QueryParameter(BIGINT, Optional.of(42L)))); } - @DataProvider - public static Object[][] testConvertArithmeticBinaryDataProvider() + @Test + public void testConvertArithmeticBinary() { - return Stream.of(ArithmeticBinaryExpression.Operator.values()) - .collect(toDataProvider()); + for (ArithmeticBinaryExpression.Operator operator : ArithmeticBinaryExpression.Operator.values()) { + ParameterizedExpression converted = JDBC_CLIENT.convertPredicate( + SESSION, + translateToConnectorExpression( + new ArithmeticBinaryExpression( + operator, + new SymbolReference("c_bigint_symbol"), + LITERAL_ENCODER.toExpression(42L, BIGINT)), + Map.of("c_bigint_symbol", BIGINT)), + Map.of("c_bigint_symbol", BIGINT_COLUMN)) + .orElseThrow(); + + assertThat(converted.expression()).isEqualTo(format("(\"c_bigint\") %s (?)", operator.getValue())); + assertThat(converted.parameters()).isEqualTo(List.of(new QueryParameter(BIGINT, Optional.of(42L)))); + } } @Test diff --git a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlTypeMapping.java b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlTypeMapping.java index d14f146cba2a..a0bab311b393 100644 --- a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlTypeMapping.java +++ b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlTypeMapping.java @@ -25,7 +25,6 @@ import io.trino.sql.planner.plan.FilterNode; import io.trino.sql.planner.plan.TopNNode; import io.trino.testing.AbstractTestQueryFramework; -import io.trino.testing.DataProviders; import io.trino.testing.QueryRunner; import io.trino.testing.TestingSession; import io.trino.testing.datatype.CreateAndInsertDataSetup; @@ -40,7 +39,6 @@ import io.trino.testing.sql.TrinoSqlExecutor; import org.intellij.lang.annotations.Language; import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.math.BigDecimal; @@ -626,8 +624,14 @@ public void testDecimalExceedingPrecisionMaxWithNonExceedingIntegerValues() } } - @Test(dataProvider = "testDecimalExceedingPrecisionMaxProvider") - public void testDecimalExceedingPrecisionMaxWithSupportedValues(int typePrecision, int typeScale) + @Test + public void testDecimalExceedingPrecisionMaxWithSupportedValues() + { + testDecimalExceedingPrecisionMaxWithSupportedValues(40, 8); + testDecimalExceedingPrecisionMaxWithSupportedValues(50, 10); + } + + private void testDecimalExceedingPrecisionMaxWithSupportedValues(int typePrecision, int typeScale) { JdbcSqlExecutor jdbcSqlExecutor = new JdbcSqlExecutor(postgreSqlServer.getJdbcUrl(), postgreSqlServer.getProperties()); @@ -679,15 +683,6 @@ public void testDecimalExceedingPrecisionMaxWithSupportedValues(int typePrecisio } } - @DataProvider - public Object[][] testDecimalExceedingPrecisionMaxProvider() - { - return new Object[][] { - {40, 8}, - {50, 10}, - }; - } - @Test public void testDecimalUnspecifiedPrecisionWithSupportedValues() { @@ -1070,8 +1065,18 @@ private static DataType> arrayDataType(DataType elementType, Stri valuesList -> valuesList == null ? null : valuesList.stream().map(elementType::toTrinoQueryResult).collect(toList())); } - @Test(dataProvider = "sessionZonesDataProvider") - public void testDate(ZoneId sessionZone) + @Test + public void testDate() + { + testDate(UTC); + testDate(jvmZone); + // using two non-JVM zones so that we don't need to worry what Postgres system zone is + testDate(vilnius); + testDate(kathmandu); + testDate(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testDate(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) @@ -1152,8 +1157,18 @@ public void testEnum() /** * @see #testTimeCoercion */ - @Test(dataProvider = "sessionZonesDataProvider") - public void testTime(ZoneId sessionZone) + @Test + public void testTime() + { + testTime(UTC); + testTime(jvmZone); + // using two non-JVM zones so that we don't need to worry what Postgres system zone is + testTime(vilnius); + testTime(kathmandu); + testTime(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testTime(ZoneId sessionZone) { LocalTime timeGapInJvmZone = LocalTime.of(0, 12, 34, 567_000_000); checkIsGap(jvmZone, timeGapInJvmZone.atDate(EPOCH_DAY)); @@ -1324,8 +1339,18 @@ public void testTime24() /** * @see #testTimestampCoercion */ - @Test(dataProvider = "sessionZonesDataProvider") - public void testTimestamp(ZoneId sessionZone) + @Test + public void testTimestamp() + { + testTimestamp(UTC); + testTimestamp(jvmZone); + // using two non-JVM zones so that we don't need to worry what Postgres system zone is + testTimestamp(vilnius); + testTimestamp(kathmandu); + testTimestamp(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testTimestamp(ZoneId sessionZone) { // no need to test gap for multiple precisions as both Trino and PostgreSql JDBC // uses same representation for all precisions 1-6 @@ -1425,8 +1450,18 @@ public void testTimestampCoercion() .execute(getQueryRunner(), trinoCreateAndInsert("test_timestamp_coercion")); } - @Test(dataProvider = "sessionZonesDataProvider") - public void testArrayTimestamp(ZoneId sessionZone) + @Test + public void testArrayTimestamp() + { + testArrayTimestamp(UTC); + testArrayTimestamp(jvmZone); + // using two non-JVM zones so that we don't need to worry what Postgres system zone is + testArrayTimestamp(vilnius); + testArrayTimestamp(kathmandu); + testArrayTimestamp(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testArrayTimestamp(ZoneId sessionZone) { // no need to test gap for multiple precisions as both Trino and PostgreSql JDBC // uses same representation for all precisions 1-6 @@ -1475,24 +1510,17 @@ public void testArrayTimestamp(ZoneId sessionZone) .execute(getQueryRunner(), session, postgresCreateAndInsert("test_array_timestamp")); } - @DataProvider - public Object[][] sessionZonesDataProvider() - { - return new Object[][] { - {UTC}, - {jvmZone}, - // using two non-JVM zones so that we don't need to worry what Postgres system zone is - {vilnius}, - {kathmandu}, - {TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()}, - }; - } - /** * @see #testTimestampWithTimeZoneCoercion */ - @Test(dataProvider = "trueFalse", dataProviderClass = DataProviders.class) - public void testTimestampWithTimeZone(boolean insertWithTrino) + @Test + public void testTimestampWithTimeZone() + { + testTimestampWithTimeZone(true); + testTimestampWithTimeZone(false); + } + + private void testTimestampWithTimeZone(boolean insertWithTrino) { DataTypeTest tests = DataTypeTest.create(true); for (int precision : List.of(3, 6)) { @@ -1604,8 +1632,14 @@ public void testTimestampWithTimeZoneCoercion() .execute(getQueryRunner(), trinoCreateAndInsert("test_timestamp_tz_coercion")); } - @Test(dataProvider = "trueFalse", dataProviderClass = DataProviders.class) - public void testArrayTimestampWithTimeZone(boolean insertWithTrino) + @Test + public void testArrayTimestampWithTimeZone() + { + testArrayTimestampWithTimeZone(true); + testArrayTimestampWithTimeZone(false); + } + + private void testArrayTimestampWithTimeZone(boolean insertWithTrino) { DataTypeTest tests = DataTypeTest.create(); for (int precision : List.of(3, 6)) { diff --git a/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreTypeMapping.java b/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreTypeMapping.java index 8535626273b7..6309f16fc90b 100644 --- a/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreTypeMapping.java +++ b/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreTypeMapping.java @@ -30,7 +30,6 @@ import io.trino.testing.sql.TestTable; import io.trino.testing.sql.TrinoSqlExecutor; import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.math.RoundingMode; @@ -414,8 +413,14 @@ public void testDecimalExceedingPrecisionMaxWithNonExceedingIntegerValues() } } - @Test(dataProvider = "testDecimalExceedingPrecisionMaxProvider") - public void testDecimalExceedingPrecisionMaxWithSupportedValues(int typePrecision, int typeScale) + @Test + public void testDecimalExceedingPrecisionMaxWithSupportedValues() + { + testDecimalExceedingPrecisionMaxWithSupportedValues(40, 8); + testDecimalExceedingPrecisionMaxWithSupportedValues(50, 10); + } + + private void testDecimalExceedingPrecisionMaxWithSupportedValues(int typePrecision, int typeScale) { try (TestTable testTable = new TestTable( singleStoreServer::execute, @@ -465,15 +470,6 @@ public void testDecimalExceedingPrecisionMaxWithSupportedValues(int typePrecisio } } - @DataProvider - public Object[][] testDecimalExceedingPrecisionMaxProvider() - { - return new Object[][] { - {40, 8}, - {50, 10}, - }; - } - private Session sessionWithDecimalMappingAllowOverflow(RoundingMode roundingMode, int scale) { return Session.builder(getSession()) @@ -628,8 +624,19 @@ public void testBinary() .execute(getQueryRunner(), singleStoreCreateAndInsert("tpch.test_binary")); } - @Test(dataProvider = "sessionZonesDataProvider") - public void testDate(ZoneId sessionZone) + @Test + public void testDate() + { + testDate(UTC); + testDate(ZoneId.systemDefault()); + // no DST in 1970, but has DST in later years (e.g. 2018) + testDate(ZoneId.of("Europe/Vilnius")); + // minutes offset change since 1970-01-01, no DST + testDate(ZoneId.of("Asia/Kathmandu")); + testDate(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testDate(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) @@ -658,8 +665,19 @@ public void testDate(ZoneId sessionZone) .execute(getQueryRunner(), session, trinoCreateAndInsert("test_date")); } - @Test(dataProvider = "sessionZonesDataProvider") - public void testTime(ZoneId sessionZone) + @Test + public void testTime() + { + testTime(UTC); + testTime(ZoneId.systemDefault()); + // no DST in 1970, but has DST in later years (e.g. 2018) + testTime(ZoneId.of("Europe/Vilnius")); + // minutes offset change since 1970-01-01, no DST + testTime(ZoneId.of("Asia/Kathmandu")); + testTime(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testTime(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(getTimeZoneKey(sessionZone.getId())) @@ -712,8 +730,16 @@ public void testTime(ZoneId sessionZone) .execute(getQueryRunner(), session, singleStoreCreateAndInsert("tpch.test_time")); } - @Test(dataProvider = "unsupportedTimeDataProvider") - public void testUnsupportedTime(String unsupportedTime) + @Test + public void testUnsupportedTime() + { + testUnsupportedTime("-838:59:59"); // min value in SingleStore + testUnsupportedTime("-00:00:01"); + testUnsupportedTime("24:00:00"); + testUnsupportedTime("838:59:59"); // max value in SingleStore + } + + private void testUnsupportedTime(String unsupportedTime) { // SingleStore stores incorrect results when the values are out of supported range. This test should be fixed when SingleStore changes the behavior try (TestTable table = new TestTable(singleStoreServer::execute, "tpch.test_unsupported_time", "(col time)", ImmutableList.of(format("'%s'", unsupportedTime)))) { @@ -729,18 +755,19 @@ public void testUnsupportedTime(String unsupportedTime) } } - @DataProvider - public Object[][] unsupportedTimeDataProvider() + @Test + public void testUnsupportedTimePrecision() { - return new Object[][] { - {"-838:59:59"}, // min value in SingleStore - {"-00:00:01"}, - {"24:00:00"}, - {"838:59:59"}, // max value in SingleStore - }; + testUnsupportedTimePrecision(1); + testUnsupportedTimePrecision(2); + testUnsupportedTimePrecision(3); + testUnsupportedTimePrecision(4); + testUnsupportedTimePrecision(5); + testUnsupportedTimePrecision(7); + testUnsupportedTimePrecision(8); + testUnsupportedTimePrecision(9); } - @Test(dataProvider = "unsupportedDateTimePrecisions") public void testUnsupportedTimePrecision(int precision) { // This test should be fixed if future SingleStore supports those precisions @@ -748,8 +775,19 @@ public void testUnsupportedTimePrecision(int precision) .hasMessageContaining("Feature 'TIME type with precision other than 0 or 6' is not supported by MemSQL."); } - @Test(dataProvider = "sessionZonesDataProvider") - public void testDatetime(ZoneId sessionZone) + @Test + public void testDatetime() + { + testDatetime(UTC); + testDatetime(ZoneId.systemDefault()); + // no DST in 1970, but has DST in later years (e.g. 2018) + testDatetime(ZoneId.of("Europe/Vilnius")); + // minutes offset change since 1970-01-01, no DST + testDatetime(ZoneId.of("Asia/Kathmandu")); + testDatetime(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testDatetime(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(getTimeZoneKey(sessionZone.getId())) @@ -805,8 +843,19 @@ public void testDatetime(ZoneId sessionZone) .execute(getQueryRunner(), session, singleStoreCreateAndInsert("tpch.test_datetime")); } - @Test(dataProvider = "sessionZonesDataProvider") - public void testTimestamp(ZoneId sessionZone) + @Test + public void testTimestamp() + { + testTimestamp(UTC); + testTimestamp(ZoneId.systemDefault()); + // no DST in 1970, but has DST in later years (e.g. 2018) + testTimestamp(ZoneId.of("Europe/Vilnius")); + // minutes offset change since 1970-01-01, no DST + testTimestamp(ZoneId.of("Asia/Kathmandu")); + testTimestamp(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testTimestamp(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(getTimeZoneKey(sessionZone.getId())) @@ -852,8 +901,19 @@ public void testTimestamp(ZoneId sessionZone) .execute(getQueryRunner(), session, singleStoreCreateAndInsert("tpch.test_timestamp")); } - @Test(dataProvider = "sessionZonesDataProvider") - public void testTimestampWrite(ZoneId sessionZone) + @Test + public void testTimestampWrite() + { + testTimestampWrite(UTC); + testTimestampWrite(ZoneId.systemDefault()); + // no DST in 1970, but has DST in later years (e.g. 2018) + testTimestampWrite(ZoneId.of("Europe/Vilnius")); + // minutes offset change since 1970-01-01, no DST + testTimestampWrite(ZoneId.of("Asia/Kathmandu")); + testTimestampWrite(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testTimestampWrite(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(getTimeZoneKey(sessionZone.getId())) @@ -914,22 +974,20 @@ public void testTimestampWrite(ZoneId sessionZone) .execute(getQueryRunner(), session, trinoCreateAndInsert("test_datetime")); } - @DataProvider - public Object[][] sessionZonesDataProvider() + @Test + public void testUnsupportedDateTimePrecision() { - return new Object[][] { - {UTC}, - {ZoneId.systemDefault()}, - // no DST in 1970, but has DST in later years (e.g. 2018) - {ZoneId.of("Europe/Vilnius")}, - // minutes offset change since 1970-01-01, no DST - {ZoneId.of("Asia/Kathmandu")}, - {TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()}, - }; + testUnsupportedDateTimePrecision(1); + testUnsupportedDateTimePrecision(2); + testUnsupportedDateTimePrecision(3); + testUnsupportedDateTimePrecision(4); + testUnsupportedDateTimePrecision(5); + testUnsupportedDateTimePrecision(7); + testUnsupportedDateTimePrecision(8); + testUnsupportedDateTimePrecision(9); } - @Test(dataProvider = "unsupportedDateTimePrecisions") - public void testUnsupportedDateTimePrecision(int precision) + private void testUnsupportedDateTimePrecision(int precision) { // This test should be fixed if future SingleStore supports those precisions assertThatThrownBy(() -> singleStoreServer.execute(format("CREATE TABLE test_unsupported_timestamp_precision (col1 TIMESTAMP(%s))", precision))) @@ -939,21 +997,6 @@ public void testUnsupportedDateTimePrecision(int precision) .hasMessageContaining("Feature 'DATETIME type with precision other than 0 or 6' is not supported by MemSQL."); } - @DataProvider - public Object[][] unsupportedDateTimePrecisions() - { - return new Object[][] { - {1}, - {2}, - {3}, - {4}, - {5}, - {7}, - {8}, - {9}, - }; - } - @Test public void testJson() { diff --git a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerTypeMapping.java b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerTypeMapping.java index efa58425c123..a73215f5b524 100644 --- a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerTypeMapping.java +++ b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerTypeMapping.java @@ -28,7 +28,6 @@ import io.trino.testing.sql.TrinoSqlExecutor; import org.intellij.lang.annotations.Language; import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.time.LocalDate; @@ -437,8 +436,20 @@ public void testVarbinary() .execute(getQueryRunner(), sqlServerCreateAndInsert("test_varbinary")); } - @Test(dataProvider = "sessionZonesDataProvider") - public void testDate(ZoneId sessionZone) + @Test + public void testDate() + { + testDate(UTC); + testDate(ZoneId.systemDefault()); + // using two non-JVM zones so that we don't need to worry what SQL Server system zone is + // no DST in 1970, but has DST in later years (e.g. 2018) + testDate(ZoneId.of("Europe/Vilnius")); + // minutes offset change since 1970-01-01, no DST + testDate(ZoneId.of("Asia/Kathmandu")); + testDate(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testDate(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) @@ -578,8 +589,20 @@ public void testTime() .execute(getQueryRunner(), trinoCreateAsSelect("test_time")); } - @Test(dataProvider = "sessionZonesDataProvider") - public void testTimestamp(ZoneId sessionZone) + @Test + public void testTimestamp() + { + testTimestamp(UTC); + testTimestamp(ZoneId.systemDefault()); + // using two non-JVM zones so that we don't need to worry what SQL Server system zone is + // no DST in 1970, but has DST in later years (e.g. 2018) + testTimestamp(ZoneId.of("Europe/Vilnius")); + // minutes offset change since 1970-01-01, no DST + testTimestamp(ZoneId.of("Asia/Kathmandu")); + testTimestamp(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testTimestamp(ZoneId sessionZone) { SqlDataTypeTest tests = SqlDataTypeTest.create() @@ -719,8 +742,20 @@ public void testSqlServerDatetime2() .execute(getQueryRunner(), sqlServerCreateAndInsert("test_sqlserver_timestamp")); } - @Test(dataProvider = "sessionZonesDataProvider") - public void testSqlServerDatetimeOffset(ZoneId sessionZone) + @Test + public void testSqlServerDatetimeOffset() + { + testSqlServerDatetimeOffset(UTC); + testSqlServerDatetimeOffset(ZoneId.systemDefault()); + // using two non-JVM zones so that we don't need to worry what SQL Server system zone is + // no DST in 1970, but has DST in later years (e.g. 2018) + testSqlServerDatetimeOffset(ZoneId.of("Europe/Vilnius")); + // minutes offset change since 1970-01-01, no DST + testSqlServerDatetimeOffset(ZoneId.of("Asia/Kathmandu")); + testSqlServerDatetimeOffset(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testSqlServerDatetimeOffset(ZoneId sessionZone) { Session session = Session.builder(getSession()) .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) @@ -804,21 +839,6 @@ public void testSqlServerDatetimeOffset(ZoneId sessionZone) .execute(getQueryRunner(), session, sqlServerCreateAndInsert("test_sqlserver_datetimeoffset")); } - @DataProvider - public Object[][] sessionZonesDataProvider() - { - return new Object[][] { - {UTC}, - {ZoneId.systemDefault()}, - // using two non-JVM zones so that we don't need to worry what SQL Server system zone is - // no DST in 1970, but has DST in later years (e.g. 2018) - {ZoneId.of("Europe/Vilnius")}, - // minutes offset change since 1970-01-01, no DST - {ZoneId.of("Asia/Kathmandu")}, - {TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()}, - }; - } - protected DataSetup trinoCreateAsSelect(String tableNamePrefix) { return trinoCreateAsSelect(getSession(), tableNamePrefix); diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestDenyOnSchema.java b/testing/trino-tests/src/test/java/io/trino/execution/TestDenyOnSchema.java index 8f972c4e8341..6f793478fcb2 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestDenyOnSchema.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestDenyOnSchema.java @@ -32,7 +32,6 @@ import io.trino.testing.DistributedQueryRunner; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.util.EnumSet; @@ -105,8 +104,18 @@ public void teardown() queryRunner = null; // closed by assertions.close } - @Test(dataProvider = "privileges") - public void testValidDenySchema(String privilege) + @Test + public void testValidDenySchema() + { + testValidDenySchema("CREATE"); + testValidDenySchema("SELECT"); + testValidDenySchema("INSERT"); + testValidDenySchema("UPDATE"); + testValidDenySchema("DELETE"); + testValidDenySchema("ALL PRIVILEGES"); + } + + private void testValidDenySchema(String privilege) { String username = randomUsername(); @@ -124,31 +133,38 @@ public void testValidDenySchema(String privilege) assertThat(denyCalled).isTrue(); } - @Test(dataProvider = "privileges") - public void testDenyOnNonExistingCatalog(String privilege) + @Test + public void testDenyOnNonExistingCatalog() { - assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY %s ON SCHEMA missing_catalog.missing_schema TO %s", privilege, randomUsername()))) + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY CREATE ON SCHEMA missing_catalog.missing_schema TO %s", randomUsername()))) + .hasMessageContaining("Schema 'missing_catalog.missing_schema' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY SELECT ON SCHEMA missing_catalog.missing_schema TO %s", randomUsername()))) + .hasMessageContaining("Schema 'missing_catalog.missing_schema' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY INSERT ON SCHEMA missing_catalog.missing_schema TO %s", randomUsername()))) + .hasMessageContaining("Schema 'missing_catalog.missing_schema' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY UPDATE ON SCHEMA missing_catalog.missing_schema TO %s", randomUsername()))) + .hasMessageContaining("Schema 'missing_catalog.missing_schema' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY DELETE ON SCHEMA missing_catalog.missing_schema TO %s", randomUsername()))) + .hasMessageContaining("Schema 'missing_catalog.missing_schema' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY ALL PRIVILEGES ON SCHEMA missing_catalog.missing_schema TO %s", randomUsername()))) .hasMessageContaining("Schema 'missing_catalog.missing_schema' does not exist"); } - @Test(dataProvider = "privileges") - public void testDenyOnNonExistingSchema(String privilege) + @Test + public void testDenyOnNonExistingSchema() { - assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY %s ON SCHEMA missing_schema TO %s", privilege, randomUsername()))) + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY CREATE ON SCHEMA missing_schema TO %s", randomUsername()))) + .hasMessageContaining("Schema 'local.missing_schema' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY SELECT ON SCHEMA missing_schema TO %s", randomUsername()))) + .hasMessageContaining("Schema 'local.missing_schema' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY INSERT ON SCHEMA missing_schema TO %s", randomUsername()))) + .hasMessageContaining("Schema 'local.missing_schema' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY UPDATE ON SCHEMA missing_schema TO %s", randomUsername()))) + .hasMessageContaining("Schema 'local.missing_schema' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY DELETE ON SCHEMA missing_schema TO %s", randomUsername()))) + .hasMessageContaining("Schema 'local.missing_schema' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY ALL PRIVILEGES ON SCHEMA missing_schema TO %s", randomUsername()))) .hasMessageContaining("Schema 'local.missing_schema' does not exist"); - } - - @DataProvider(name = "privileges") - public static Object[][] privileges() - { - return new Object[][] { - {"CREATE"}, - {"SELECT"}, - {"INSERT"}, - {"UPDATE"}, - {"DELETE"}, - {"ALL PRIVILEGES"} - }; } private static Session sessionOf(String username) diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestDenyOnTable.java b/testing/trino-tests/src/test/java/io/trino/execution/TestDenyOnTable.java index ecadc95a1f15..c27053e2da8c 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestDenyOnTable.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestDenyOnTable.java @@ -32,7 +32,6 @@ import io.trino.testing.DistributedQueryRunner; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.util.EnumSet; @@ -102,8 +101,18 @@ public void teardown() queryRunner = null; // closed by assertions.close } - @Test(dataProvider = "privileges") - public void testValidDenyTable(String privilege) + @Test + public void testValidDenyTable() + { + testValidDenyTable("CREATE"); + testValidDenyTable("SELECT"); + testValidDenyTable("INSERT"); + testValidDenyTable("UPDATE"); + testValidDenyTable("DELETE"); + testValidDenyTable("ALL PRIVILEGES"); + } + + private void testValidDenyTable(String privilege) { String username = randomUsername(); @@ -121,38 +130,55 @@ public void testValidDenyTable(String privilege) assertThat(denyCalled).isTrue(); } - @Test(dataProvider = "privileges") - public void testDenyOnNonExistingCatalog(String privilege) + @Test + public void testDenyOnNonExistingCatalog() { - assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY %s ON TABLE missing_catalog.missing_schema.missing_table TO %s", privilege, randomUsername()))) + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY CREATE ON TABLE missing_catalog.missing_schema.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'missing_catalog.missing_schema.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY SELECT ON TABLE missing_catalog.missing_schema.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'missing_catalog.missing_schema.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY INSERT ON TABLE missing_catalog.missing_schema.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'missing_catalog.missing_schema.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY UPDATE ON TABLE missing_catalog.missing_schema.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'missing_catalog.missing_schema.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY DELETE ON TABLE missing_catalog.missing_schema.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'missing_catalog.missing_schema.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY ALL PRIVILEGES ON TABLE missing_catalog.missing_schema.missing_table TO %s", randomUsername()))) .hasMessageContaining("Table 'missing_catalog.missing_schema.missing_table' does not exist"); } - @Test(dataProvider = "privileges") - public void testDenyOnNonExistingSchema(String privilege) + @Test + public void testDenyOnNonExistingSchema() { - assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY %s ON TABLE missing_schema.missing_table TO %s", privilege, randomUsername()))) + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY CREATE ON TABLE missing_schema.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'local.missing_schema.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY SELECT ON TABLE missing_schema.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'local.missing_schema.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY INSERT ON TABLE missing_schema.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'local.missing_schema.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY UPDATE ON TABLE missing_schema.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'local.missing_schema.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY DELETE ON TABLE missing_schema.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'local.missing_schema.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY ALL PRIVILEGES ON TABLE missing_schema.missing_table TO %s", randomUsername()))) .hasMessageContaining("Table 'local.missing_schema.missing_table' does not exist"); } - @Test(dataProvider = "privileges") - public void testDenyOnNonExistingTable(String privilege) + @Test + public void testDenyOnNonExistingTable() { - assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY %s ON TABLE default.missing_table TO %s", privilege, randomUsername()))) + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY CREATE ON TABLE default.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'local.default.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY SELECT ON TABLE default.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'local.default.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY INSERT ON TABLE default.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'local.default.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY UPDATE ON TABLE default.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'local.default.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY DELETE ON TABLE default.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'local.default.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("DENY ALL PRIVILEGES ON TABLE default.missing_table TO %s", randomUsername()))) .hasMessageContaining("Table 'local.default.missing_table' does not exist"); - } - - @DataProvider(name = "privileges") - public static Object[][] privileges() - { - return new Object[][] { - {"CREATE"}, - {"SELECT"}, - {"INSERT"}, - {"UPDATE"}, - {"DELETE"}, - {"ALL PRIVILEGES"} - }; } private static Session sessionOf(String username) diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestGrantOnSchema.java b/testing/trino-tests/src/test/java/io/trino/execution/TestGrantOnSchema.java index d551aa0903fc..7406ca2c3061 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestGrantOnSchema.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestGrantOnSchema.java @@ -23,11 +23,9 @@ import io.trino.spi.security.Privilege; import io.trino.spi.security.TrinoPrincipal; import io.trino.sql.query.QueryAssertions; -import io.trino.testing.DataProviders; import io.trino.testing.DistributedQueryRunner; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.util.EnumSet; @@ -71,8 +69,14 @@ public void teardown() queryRunner = null; // closed by assertions.close } - @Test(dataProviderClass = DataProviders.class, dataProvider = "trueFalse") - public void testExistingGrants(boolean grantOption) + @Test + public void testExistingGrants() + { + testExistingGrants(true); + testExistingGrants(false); + } + + private void testExistingGrants(boolean grantOption) { String username = randomUsername(); Session user = sessionOf(username); @@ -84,8 +88,15 @@ public void testExistingGrants(boolean grantOption) assertThat(assertions.query(user, "SHOW TABLES FROM default")).matches("VALUES (VARCHAR 'table_one')"); } - @Test(dataProvider = "privileges") - public void testValidGrant(String privilege) + @Test + public void testValidGrant() + { + testValidGrant("SELECT"); + testValidGrant("CREATE"); + testValidGrant("ALL PRIVILEGES"); + } + + private void testValidGrant(String privilege) { String username = randomUsername(); Session user = sessionOf(username); @@ -97,8 +108,15 @@ public void testValidGrant(String privilege) assertThat(assertions.query(user, "SHOW TABLES FROM default")).matches("VALUES (VARCHAR 'table_one')"); } - @Test(dataProvider = "privileges") - public void testValidGrantWithGrantOption(String privilege) + @Test + public void testValidGrantWithGrantOption() + { + testValidGrantWithGrantOption("SELECT"); + testValidGrantWithGrantOption("CREATE"); + testValidGrantWithGrantOption("ALL PRIVILEGES"); + } + + private void testValidGrantWithGrantOption(String privilege) { String username = randomUsername(); Session user = sessionOf(username); @@ -110,37 +128,37 @@ public void testValidGrantWithGrantOption(String privilege) assertions.query(user, format("GRANT %s ON SCHEMA default TO %s WITH GRANT OPTION", privilege, randomUsername())); } - @Test(dataProvider = "privileges") - public void testGrantOnNonExistingCatalog(String privilege) + @Test + public void testGrantOnNonExistingCatalog() { - assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT %s ON SCHEMA missing_catalog.missing_schema TO %s", privilege, randomUsername()))) + assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT SELECT ON SCHEMA missing_catalog.missing_schema TO %s", randomUsername()))) + .hasMessageContaining("Schema 'missing_catalog.missing_schema' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT CREATE ON SCHEMA missing_catalog.missing_schema TO %s", randomUsername()))) + .hasMessageContaining("Schema 'missing_catalog.missing_schema' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT ALL PRIVILEGES ON SCHEMA missing_catalog.missing_schema TO %s", randomUsername()))) .hasMessageContaining("Schema 'missing_catalog.missing_schema' does not exist"); } - @Test(dataProvider = "privileges") - public void testGrantOnNonExistingSchema(String privilege) + @Test + public void testGrantOnNonExistingSchema() { - assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT %s ON SCHEMA missing_schema TO %s", privilege, randomUsername()))) + assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT SELECT ON SCHEMA missing_schema TO %s", randomUsername()))) + .hasMessageContaining("Schema 'local.missing_schema' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT CREATE ON SCHEMA missing_schema TO %s", randomUsername()))) + .hasMessageContaining("Schema 'local.missing_schema' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT ALL PRIVILEGES ON SCHEMA missing_schema TO %s", randomUsername()))) .hasMessageContaining("Schema 'local.missing_schema' does not exist"); } - @Test(dataProvider = "privileges") - public void testAccessDenied(String privilege) - { - assertThatThrownBy(() -> queryRunner.execute(sessionOf(randomUsername()), format("GRANT %s ON SCHEMA default TO %s", privilege, randomUsername()))) - .hasMessageContaining( - "Access Denied: Cannot grant privilege %s on schema default", - privilege.equals("ALL PRIVILEGES") ? "CREATE" : privilege); - } - - @DataProvider(name = "privileges") - public static Object[][] privileges() + @Test + public void testAccessDenied() { - return new Object[][] { - {"SELECT"}, - {"CREATE"}, - {"ALL PRIVILEGES"} - }; + assertThatThrownBy(() -> queryRunner.execute(sessionOf(randomUsername()), format("GRANT SELECT ON SCHEMA default TO %s", randomUsername()))) + .hasMessageContaining("Access Denied: Cannot grant privilege SELECT on schema default"); + assertThatThrownBy(() -> queryRunner.execute(sessionOf(randomUsername()), format("GRANT CREATE ON SCHEMA default TO %s", randomUsername()))) + .hasMessageContaining("Access Denied: Cannot grant privilege CREATE on schema default"); + assertThatThrownBy(() -> queryRunner.execute(sessionOf(randomUsername()), format("GRANT ALL PRIVILEGES ON SCHEMA default TO %s", randomUsername()))) + .hasMessageContaining("Access Denied: Cannot grant privilege CREATE on schema default"); } private static Session sessionOf(String username) diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestGrantOnTable.java b/testing/trino-tests/src/test/java/io/trino/execution/TestGrantOnTable.java index b4bc745dfc82..81f218ba4ea5 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestGrantOnTable.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestGrantOnTable.java @@ -25,11 +25,9 @@ import io.trino.spi.security.Privilege; import io.trino.spi.security.TrinoPrincipal; import io.trino.sql.query.QueryAssertions; -import io.trino.testing.DataProviders; import io.trino.testing.DistributedQueryRunner; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.util.EnumSet; @@ -78,8 +76,14 @@ public void teardown() queryRunner = null; // closed by assertions.close } - @Test(dataProviderClass = DataProviders.class, dataProvider = "trueFalse") - public void testExistingGrants(boolean grantOption) + @Test + public void testExistingGrants() + { + testExistingGrants(true); + testExistingGrants(false); + } + + private void testExistingGrants(boolean grantOption) { String username = randomUsername(); Session user = sessionOf(username); @@ -89,8 +93,18 @@ public void testExistingGrants(boolean grantOption) assertThat(assertions.query(user, "SHOW TABLES FROM local.default")).matches("VALUES (VARCHAR 'table_one')"); } - @Test(dataProvider = "privileges") - public void testValidGrant(String privilege) + @Test + public void testValidGrant() + { + testValidGrant("CREATE"); + testValidGrant("SELECT"); + testValidGrant("INSERT"); + testValidGrant("UPDATE"); + testValidGrant("DELETE"); + testValidGrant("ALL PRIVILEGES"); + } + + private void testValidGrant(String privilege) { String username = randomUsername(); Session user = sessionOf(username); @@ -100,8 +114,18 @@ public void testValidGrant(String privilege) assertThat(assertions.query(user, "SHOW TABLES FROM default")).matches("VALUES (VARCHAR 'table_one')"); } - @Test(dataProvider = "privileges") - public void testValidGrantWithGrantOption(String privilege) + @Test + public void testValidGrantWithGrantOption() + { + testValidGrantWithGrantOption("CREATE"); + testValidGrantWithGrantOption("SELECT"); + testValidGrantWithGrantOption("INSERT"); + testValidGrantWithGrantOption("UPDATE"); + testValidGrantWithGrantOption("DELETE"); + testValidGrantWithGrantOption("ALL PRIVILEGES"); + } + + private void testValidGrantWithGrantOption(String privilege) { String username = randomUsername(); Session user = sessionOf(username); @@ -113,38 +137,55 @@ public void testValidGrantWithGrantOption(String privilege) assertUpdate(queryRunner, user, format("GRANT %s ON TABLE table_one TO %s WITH GRANT OPTION", privilege, randomUsername()), OptionalLong.empty(), Optional.empty()); } - @Test(dataProvider = "privileges") - public void testGrantOnNonExistingCatalog(String privilege) + @Test + public void testGrantOnNonExistingCatalog() { - assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT %s ON TABLE missing_catalog.missing_schema.missing_table TO %s", privilege, randomUsername()))) + assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT CREATE ON TABLE missing_catalog.missing_schema.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'missing_catalog.missing_schema.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT SELECT ON TABLE missing_catalog.missing_schema.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'missing_catalog.missing_schema.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT INSERT ON TABLE missing_catalog.missing_schema.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'missing_catalog.missing_schema.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT UPDATE ON TABLE missing_catalog.missing_schema.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'missing_catalog.missing_schema.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT DELETE ON TABLE missing_catalog.missing_schema.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'missing_catalog.missing_schema.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT ALL PRIVILEGES ON TABLE missing_catalog.missing_schema.missing_table TO %s", randomUsername()))) .hasMessageContaining("Table 'missing_catalog.missing_schema.missing_table' does not exist"); } - @Test(dataProvider = "privileges") - public void testGrantOnNonExistingSchema(String privilege) + @Test + public void testGrantOnNonExistingSchema() { - assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT %s ON TABLE missing_schema.missing_table TO %s", privilege, randomUsername()))) + assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT CREATE ON TABLE missing_schema.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'local.missing_schema.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT SELECT ON TABLE missing_schema.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'local.missing_schema.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT INSERT ON TABLE missing_schema.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'local.missing_schema.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT UPDATE ON TABLE missing_schema.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'local.missing_schema.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT DELETE ON TABLE missing_schema.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'local.missing_schema.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT ALL PRIVILEGES ON TABLE missing_schema.missing_table TO %s", randomUsername()))) .hasMessageContaining("Table 'local.missing_schema.missing_table' does not exist"); } - @Test(dataProvider = "privileges") - public void testGrantOnNonExistingTable(String privilege) + @Test + public void testGrantOnNonExistingTable() { - assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT %s ON TABLE default.missing_table TO %s", privilege, randomUsername()))) + assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT CREATE ON TABLE default.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'local.default.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT SELECT ON TABLE default.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'local.default.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT INSERT ON TABLE default.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'local.default.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT UPDATE ON TABLE default.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'local.default.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT DELETE ON TABLE default.missing_table TO %s", randomUsername()))) + .hasMessageContaining("Table 'local.default.missing_table' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("GRANT ALL PRIVILEGES ON TABLE default.missing_table TO %s", randomUsername()))) .hasMessageContaining("Table 'local.default.missing_table' does not exist"); - } - - @DataProvider(name = "privileges") - public static Object[][] privileges() - { - return new Object[][] { - {"CREATE"}, - {"SELECT"}, - {"INSERT"}, - {"UPDATE"}, - {"DELETE"}, - {"ALL PRIVILEGES"} - }; } private static Session sessionOf(String username) diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestRevokeOnSchema.java b/testing/trino-tests/src/test/java/io/trino/execution/TestRevokeOnSchema.java index 2ab6172d5fcc..62467be21e8f 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestRevokeOnSchema.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestRevokeOnSchema.java @@ -27,7 +27,6 @@ import io.trino.testing.DistributedQueryRunner; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.util.EnumSet; @@ -73,8 +72,14 @@ public void teardown() queryRunner = null; // closed by assertions.close } - @Test(dataProvider = "privilegesAndUsers") - public void testRevokeOnSchema(String privilege, Session user) + @Test + public void testRevokeOnSchema() + { + testRevokeOnSchema("SELECT", userWithSelect); + testRevokeOnSchema("ALL PRIVILEGES", userWithAllPrivileges); + } + + private void testRevokeOnSchema(String privilege, Session user) { assertThat(assertions.query(user, "SHOW SCHEMAS FROM local")).matches("VALUES (VARCHAR 'information_schema'), (VARCHAR 'default')"); @@ -83,46 +88,33 @@ public void testRevokeOnSchema(String privilege, Session user) assertThat(assertions.query(user, "SHOW SCHEMAS FROM local")).matches("VALUES (VARCHAR 'information_schema')"); } - @Test(dataProvider = "privilegesAndUsers") - public void testRevokeOnNonExistingCatalog(String privilege, Session user) + @Test + public void testRevokeOnNonExistingCatalog() { - assertThatThrownBy(() -> queryRunner.execute(admin, format("REVOKE %s ON SCHEMA missing_catalog.missing_schema FROM %s", privilege, user.getUser()))) + assertThatThrownBy(() -> queryRunner.execute(admin, format("REVOKE SELECT ON SCHEMA missing_catalog.missing_schema FROM %s", userWithSelect.getUser()))) + .hasMessageContaining("Schema 'missing_catalog.missing_schema' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("REVOKE ALL PRIVILEGES ON SCHEMA missing_catalog.missing_schema FROM %s", userWithAllPrivileges.getUser()))) .hasMessageContaining("Schema 'missing_catalog.missing_schema' does not exist"); } - @Test(dataProvider = "privilegesAndUsers") - public void testRevokeOnNonExistingSchema(String privilege, Session user) + @Test + public void testRevokeOnNonExistingSchema() { - assertThatThrownBy(() -> queryRunner.execute(admin, format("REVOKE %s ON SCHEMA missing_schema FROM %s", privilege, user.getUser()))) + assertThatThrownBy(() -> queryRunner.execute(admin, format("REVOKE SELECT ON SCHEMA missing_schema FROM %s", userWithSelect.getUser()))) + .hasMessageContaining("Schema 'local.missing_schema' does not exist"); + assertThatThrownBy(() -> queryRunner.execute(admin, format("REVOKE ALL PRIVILEGES ON SCHEMA missing_schema FROM %s", userWithAllPrivileges.getUser()))) .hasMessageContaining("Schema 'local.missing_schema' does not exist"); } - @Test(dataProvider = "privileges") - public void testAccessDenied(String privilege) - { - assertThatThrownBy(() -> queryRunner.execute(sessionOf(randomUsername()), format("REVOKE %s ON SCHEMA default FROM %s", privilege, randomUsername()))) - .hasMessageContaining( - "Access Denied: Cannot revoke privilege %s on schema default", - privilege.equals("ALL PRIVILEGES") ? "CREATE" : privilege); - } - - @DataProvider(name = "privilegesAndUsers") - public static Object[][] privilegesAndUsers() - { - return new Object[][] { - {"SELECT", userWithSelect}, - {"ALL PRIVILEGES", userWithAllPrivileges} - }; - } - - @DataProvider(name = "privileges") - public static Object[][] privileges() + @Test + public void testAccessDenied() { - return new Object[][] { - {"CREATE"}, - {"SELECT"}, - {"ALL PRIVILEGES"} - }; + assertThatThrownBy(() -> queryRunner.execute(sessionOf(randomUsername()), format("REVOKE CREATE ON SCHEMA default FROM %s", randomUsername()))) + .hasMessageContaining("Access Denied: Cannot revoke privilege CREATE on schema default"); + assertThatThrownBy(() -> queryRunner.execute(sessionOf(randomUsername()), format("REVOKE SELECT ON SCHEMA default FROM %s", randomUsername()))) + .hasMessageContaining("Access Denied: Cannot revoke privilege SELECT on schema default"); + assertThatThrownBy(() -> queryRunner.execute(sessionOf(randomUsername()), format("REVOKE ALL PRIVILEGES ON SCHEMA default FROM %s", randomUsername()))) + .hasMessageContaining("Access Denied: Cannot revoke privilege CREATE on schema default"); } private static Session sessionOf(String username) From 8edaa86c6b43e5f5fe03bb9427d3f26439a60d15 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Wed, 15 Nov 2023 14:05:57 -0800 Subject: [PATCH 267/587] Migrate test to JUnit --- plugin/trino-mongodb/pom.xml | 28 ----- .../mongodb/TestMongoConnectorTest.java | 114 +++++++++++------- .../trino/plugin/mongodb/TestMongoPlugin.java | 4 +- .../plugin/mongodb/TestMongoPrivileges.java | 2 +- .../plugin/mongodb/TestMongoSession.java | 28 +++-- .../trino/plugin/mongodb/TestMongoSplit.java | 5 +- .../plugin/mongodb/TestMongoTableHandle.java | 17 ++- .../plugin/mongodb/TestMongoTypeMapping.java | 2 +- plugin/trino-mysql/pom.xml | 28 ----- .../plugin/mysql/BaseMySqlConnectorTest.java | 14 +-- .../BaseTestMySqlTableStatisticsTest.java | 59 +++++---- .../trino/plugin/mysql/TestMySqlClient.java | 12 +- .../plugin/mysql/TestMySqlJdbcConfig.java | 9 +- ...stMySqlTimeMappingsWithServerTimeZone.java | 2 +- .../plugin/mysql/TestMySqlTypeMapping.java | 12 +- plugin/trino-oracle/pom.xml | 28 ----- .../oracle/AbstractTestOracleTypeMapping.java | 12 +- .../oracle/BaseOracleConnectorTest.java | 7 +- .../trino/plugin/oracle/TestOracleClient.java | 2 +- plugin/trino-phoenix5/pom.xml | 23 ---- .../phoenix5/TestPhoenixConnectorTest.java | 5 +- .../plugin/phoenix5/TestPhoenixSplit.java | 9 +- .../phoenix5/TestPhoenixTypeMapping.java | 12 +- plugin/trino-postgresql/pom.xml | 28 ----- .../postgresql/TestPostgreSqlClient.java | 12 +- .../TestPostgreSqlConnectorTest.java | 18 ++- .../postgresql/TestPostgreSqlTypeMapping.java | 12 +- plugin/trino-singlestore/pom.xml | 28 ----- .../TestSingleStoreConnectorTest.java | 22 ++-- .../TestSingleStoreJdbcConfig.java | 11 +- .../TestSingleStoreTypeMapping.java | 12 +- plugin/trino-sqlserver/pom.xml | 28 ----- .../sqlserver/BaseSqlServerConnectorTest.java | 7 +- .../sqlserver/BaseSqlServerTypeMapping.java | 12 +- .../plugin/sqlserver/TestSqlServerClient.java | 12 +- .../sqlserver/TestSqlServerConnectorTest.java | 6 +- .../io/trino/execution/TestDenyOnSchema.java | 17 ++- .../io/trino/execution/TestDenyOnTable.java | 17 ++- .../io/trino/execution/TestGrantOnSchema.java | 16 ++- .../io/trino/execution/TestGrantOnTable.java | 16 ++- .../trino/execution/TestRevokeOnSchema.java | 16 ++- 41 files changed, 326 insertions(+), 398 deletions(-) diff --git a/plugin/trino-mongodb/pom.xml b/plugin/trino-mongodb/pom.xml index 92966117d0cd..3a2abec52f53 100644 --- a/plugin/trino-mongodb/pom.xml +++ b/plugin/trino-mongodb/pom.xml @@ -237,33 +237,5 @@ testcontainers test - - - org.testng - testng - test - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - - diff --git a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoConnectorTest.java b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoConnectorTest.java index 675f3d5eedde..89581bdd381b 100644 --- a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoConnectorTest.java +++ b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoConnectorTest.java @@ -62,9 +62,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assumptions.abort; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; @TestInstance(PER_CLASS) public class TestMongoConnectorTest @@ -213,20 +210,30 @@ public void createTableWithEveryType() assertUpdate(query, 1); MaterializedResult results = getQueryRunner().execute(getSession(), "SELECT * FROM " + tableName).toTestTypes(); - assertEquals(results.getRowCount(), 1); + assertThat(results.getRowCount()) + .isEqualTo(1); MaterializedRow row = results.getMaterializedRows().get(0); - assertEquals(row.getField(0), "foo"); - assertEquals(row.getField(1), "bar".getBytes(UTF_8)); - assertEquals(row.getField(2), 1L); - assertEquals(row.getField(3), 3.14); - assertEquals(row.getField(4), true); - assertEquals(row.getField(5), LocalDate.of(1980, 5, 7)); - assertEquals(row.getField(6), LocalDateTime.of(1980, 5, 7, 11, 22, 33, 456_000_000)); - assertEquals(row.getField(8), "{\"name\":\"alice\"}"); - assertEquals(row.getField(9), new BigDecimal("12.30000")); + assertThat(row.getField(0)) + .isEqualTo("foo"); + assertThat(row.getField(1)) + .isEqualTo("bar".getBytes(UTF_8)); + assertThat(row.getField(2)) + .isEqualTo(1L); + assertThat(row.getField(3)) + .isEqualTo(3.14); + assertThat(row.getField(4)) + .isEqualTo(true); + assertThat(row.getField(5)) + .isEqualTo(LocalDate.of(1980, 5, 7)); + assertThat(row.getField(6)) + .isEqualTo(LocalDateTime.of(1980, 5, 7, 11, 22, 33, 456_000_000)); + assertThat(row.getField(8)) + .isEqualTo("{\"name\":\"alice\"}"); + assertThat(row.getField(9)) + .isEqualTo(new BigDecimal("12.30000")); assertUpdate("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); } @Test @@ -263,18 +270,27 @@ public void testInsertWithEveryType() getQueryRunner().execute(getSession(), insertSql); MaterializedResult results = getQueryRunner().execute(getSession(), "SELECT * FROM " + tableName).toTestTypes(); - assertEquals(results.getRowCount(), 1); + assertThat(results.getRowCount()) + .isEqualTo(1); MaterializedRow row = results.getMaterializedRows().get(0); - assertEquals(row.getField(0), "foo"); - assertEquals(row.getField(1), "bar".getBytes(UTF_8)); - assertEquals(row.getField(2), 1L); - assertEquals(row.getField(3), 3.14); - assertEquals(row.getField(4), true); - assertEquals(row.getField(5), LocalDate.of(1980, 5, 7)); - assertEquals(row.getField(6), LocalDateTime.of(1980, 5, 7, 11, 22, 33, 456_000_000)); - assertEquals(row.getField(8), "{\"name\":\"alice\"}"); + assertThat(row.getField(0)) + .isEqualTo("foo"); + assertThat(row.getField(1)) + .isEqualTo("bar".getBytes(UTF_8)); + assertThat(row.getField(2)) + .isEqualTo(1L); + assertThat(row.getField(3)) + .isEqualTo(3.14); + assertThat(row.getField(4)) + .isEqualTo(true); + assertThat(row.getField(5)) + .isEqualTo(LocalDate.of(1980, 5, 7)); + assertThat(row.getField(6)) + .isEqualTo(LocalDateTime.of(1980, 5, 7, 11, 22, 33, 456_000_000)); + assertThat(row.getField(8)) + .isEqualTo("{\"name\":\"alice\"}"); assertUpdate("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); } @Test @@ -1356,13 +1372,17 @@ private void testFiltersOnDereferenceColumnReadsLessData(String expectedValue, S sessionWithoutPushdown, format("SELECT 1 FROM %s WHERE col_0.col_1 = %s", table.getName(), expectedValue), statsWithoutPushdown -> { - assertEquals(statsWithoutPushdown.getProcessedInputPositions(), 3); - assertEquals(processedInputPositionWithPushdown, 2); + assertThat(statsWithoutPushdown.getProcessedInputPositions()) + .isEqualTo(3); + assertThat(processedInputPositionWithPushdown) + .isEqualTo(2); assertThat(statsWithoutPushdown.getProcessedInputPositions()).isGreaterThan(processedInputPositionWithPushdown); }, - results -> assertEquals(results.getOnlyColumnAsSet(), expected)); + results -> assertThat(results.getOnlyColumnAsSet()) + .isEqualTo(expected)); }, - results -> assertEquals(results.getOnlyColumnAsSet(), expected)); + results -> assertThat(results.getOnlyColumnAsSet()) + .isEqualTo(expected)); assertQueryStats( getSession(), @@ -1373,13 +1393,17 @@ private void testFiltersOnDereferenceColumnReadsLessData(String expectedValue, S sessionWithoutPushdown, format("SELECT 1 FROM %s WHERE col_0.col_2.col_3 = %s", table.getName(), expectedValue), statsWithoutPushdown -> { - assertEquals(statsWithoutPushdown.getProcessedInputPositions(), 3); - assertEquals(processedInputPositionWithPushdown, 1); + assertThat(statsWithoutPushdown.getProcessedInputPositions()) + .isEqualTo(3); + assertThat(processedInputPositionWithPushdown) + .isEqualTo(1); assertThat(statsWithoutPushdown.getProcessedInputPositions()).isGreaterThan(processedInputPositionWithPushdown); }, - results -> assertEquals(results.getOnlyColumnAsSet(), expected)); + results -> assertThat(results.getOnlyColumnAsSet()) + .isEqualTo(expected)); }, - results -> assertEquals(results.getOnlyColumnAsSet(), expected)); + results -> assertThat(results.getOnlyColumnAsSet()) + .isEqualTo(expected)); assertQueryStats( getSession(), @@ -1390,13 +1414,17 @@ private void testFiltersOnDereferenceColumnReadsLessData(String expectedValue, S sessionWithoutPushdown, format("SELECT 1 FROM %s WHERE col_0.col_2.col_4.col_5 = %s", table.getName(), expectedValue), statsWithoutPushdown -> { - assertEquals(statsWithoutPushdown.getProcessedInputPositions(), 3); - assertEquals(processedInputPositionWithPushdown, 2); + assertThat(statsWithoutPushdown.getProcessedInputPositions()) + .isEqualTo(3); + assertThat(processedInputPositionWithPushdown) + .isEqualTo(2); assertThat(statsWithoutPushdown.getProcessedInputPositions()).isGreaterThan(processedInputPositionWithPushdown); }, - results -> assertEquals(results.getOnlyColumnAsSet(), expected)); + results -> assertThat(results.getOnlyColumnAsSet()) + .isEqualTo(expected)); }, - results -> assertEquals(results.getOnlyColumnAsSet(), expected)); + results -> assertThat(results.getOnlyColumnAsSet()) + .isEqualTo(expected)); } } @@ -1413,8 +1441,10 @@ public void testFiltersOnDereferenceColumnReadsLessDataNativeQuery() assertQueryStats( getSession(), "SELECT row_field.first.second FROM TABLE(mongodb.system.query(database => 'test', collection => '" + tableName + "', filter => '{ \"row_field.first.second\": 1 }'))", - stats -> assertEquals(stats.getProcessedInputPositions(), 1L), - results -> assertEquals(results.getOnlyColumnAsSet(), ImmutableSet.of(1L))); + stats -> assertThat(stats.getProcessedInputPositions()) + .isEqualTo(1L), + results -> assertThat(results.getOnlyColumnAsSet()) + .isEqualTo(ImmutableSet.of(1L))); assertUpdate("DROP TABLE test." + tableName); } @@ -1847,8 +1877,10 @@ protected Optional filterSetColumnTypesDataProvider(SetColum private void assertOneNotNullResult(String query) { MaterializedResult results = getQueryRunner().execute(getSession(), query).toTestTypes(); - assertEquals(results.getRowCount(), 1); - assertEquals(results.getMaterializedRows().get(0).getFieldCount(), 1); - assertNotNull(results.getMaterializedRows().get(0).getField(0)); + assertThat(results.getRowCount()) + .isEqualTo(1); + assertThat(results.getMaterializedRows().get(0).getFieldCount()) + .isEqualTo(1); + assertThat(results.getMaterializedRows().get(0).getField(0)).isNotNull(); } } diff --git a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoPlugin.java b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoPlugin.java index 4da82167001a..f71fae5fcc34 100644 --- a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoPlugin.java +++ b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoPlugin.java @@ -22,7 +22,7 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static io.trino.plugin.mongodb.ObjectIdType.OBJECT_ID; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestMongoPlugin { @@ -40,7 +40,7 @@ public void testCreateConnector() new TestingConnectorContext()); Type type = getOnlyElement(plugin.getTypes()); - assertEquals(type, OBJECT_ID); + assertThat(type).isEqualTo(OBJECT_ID); connector.shutdown(); } diff --git a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoPrivileges.java b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoPrivileges.java index 55623dae3614..5f70dcd9a373 100644 --- a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoPrivileges.java +++ b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoPrivileges.java @@ -22,7 +22,7 @@ import io.trino.testing.DistributedQueryRunner; import io.trino.testing.QueryRunner; import org.bson.Document; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.Optional; diff --git a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoSession.java b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoSession.java index c5fb6146481c..546937809a42 100644 --- a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoSession.java +++ b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoSession.java @@ -38,7 +38,6 @@ import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestMongoSession { @@ -61,7 +60,8 @@ public void testBuildProjectionWithoutId() .append(COL1.getBaseName(), 1) .append(COL2.getBaseName(), 1) .append(ID_COL.getBaseName(), 0); - assertEquals(output, expected); + assertThat(output) + .isEqualTo(expected); } @Test @@ -74,7 +74,8 @@ public void testBuildProjectionWithId() .append(COL1.getBaseName(), 1) .append(COL2.getBaseName(), 1) .append(ID_COL.getBaseName(), 1); - assertEquals(output, expected); + assertThat(output) + .isEqualTo(expected); } @Test @@ -89,7 +90,8 @@ public void testBuildQuery() .append("$and", ImmutableList.of( new Document(COL1.getBaseName(), new Document().append("$gt", 100L).append("$lte", 200L)), new Document(COL2.getBaseName(), new Document("$eq", "a value")))); - assertEquals(query, expected); + assertThat(query) + .isEqualTo(expected); } @Test @@ -104,7 +106,8 @@ public void testBuildQueryStringType() .append("$and", ImmutableList.of( new Document(COL3.getBaseName(), new Document().append("$gt", "hello").append("$lte", "world")), new Document(COL2.getBaseName(), new Document("$gte", "a value")))); - assertEquals(query, expected); + assertThat(query) + .isEqualTo(expected); } @Test @@ -115,7 +118,8 @@ public void testBuildQueryIn() Document query = MongoSession.buildQuery(tupleDomain); Document expected = new Document(COL2.getBaseName(), new Document("$in", ImmutableList.of("hello", "world"))); - assertEquals(query, expected); + assertThat(query) + .isEqualTo(expected); } @Test @@ -128,7 +132,8 @@ public void testBuildQueryOr() Document expected = new Document("$or", asList( new Document(COL1.getBaseName(), new Document("$lt", 100L)), new Document(COL1.getBaseName(), new Document("$gt", 200L)))); - assertEquals(query, expected); + assertThat(query) + .isEqualTo(expected); } @Test @@ -141,7 +146,8 @@ public void testBuildQueryNull() Document expected = new Document("$or", asList( new Document(COL1.getBaseName(), new Document("$gt", 200L)), new Document(COL1.getBaseName(), new Document("$eq", null)))); - assertEquals(query, expected); + assertThat(query) + .isEqualTo(expected); } @Test @@ -151,7 +157,8 @@ public void testBooleanPredicatePushdown() Document query = MongoSession.buildQuery(tupleDomain); Document expected = new Document().append(COL4.getBaseName(), new Document("$eq", true)); - assertEquals(query, expected); + assertThat(query) + .isEqualTo(expected); } @Test @@ -168,7 +175,8 @@ public void testBuildQueryNestedField() new Document(COL5.getQualifiedName(), new Document("$gt", 200L)), new Document(COL5.getQualifiedName(), new Document("$eq", null)))), new Document(COL6.getQualifiedName(), new Document("$eq", "a value")))); - assertEquals(query, expected); + assertThat(query) + .isEqualTo(expected); } @Test diff --git a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoSplit.java b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoSplit.java index d5ecf9c2db81..a2d4fc76e8a1 100644 --- a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoSplit.java +++ b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoSplit.java @@ -17,7 +17,7 @@ import io.airlift.json.JsonCodec; import org.junit.jupiter.api.Test; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestMongoSplit { @@ -31,6 +31,7 @@ public void testJsonRoundTrip() String json = codec.toJson(expected); MongoSplit actual = codec.fromJson(json); - assertEquals(actual.getAddresses(), ImmutableList.of()); + assertThat(actual.getAddresses()) + .isEqualTo(ImmutableList.of()); } } diff --git a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoTableHandle.java b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoTableHandle.java index e3777733f21c..bdeab116b8ad 100644 --- a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoTableHandle.java +++ b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoTableHandle.java @@ -33,7 +33,7 @@ import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestMongoTableHandle { @@ -56,7 +56,8 @@ public void testRoundTripWithoutQuery() String json = codec.toJson(expected); MongoTableHandle actual = codec.fromJson(json); - assertEquals(actual.getSchemaTableName(), expected.getSchemaTableName()); + assertThat(actual.getSchemaTableName()) + .isEqualTo(expected.getSchemaTableName()); } @Test @@ -69,7 +70,8 @@ public void testRoundTripNonLowercaseWithoutQuery() String json = codec.toJson(expected); MongoTableHandle actual = codec.fromJson(json); - assertEquals(actual.getSchemaTableName(), expected.getSchemaTableName()); + assertThat(actual.getSchemaTableName()) + .isEqualTo(expected.getSchemaTableName()); } @Test @@ -82,7 +84,8 @@ public void testRoundTripWithQuery() String json = codec.toJson(expected); MongoTableHandle actual = codec.fromJson(json); - assertEquals(actual.getSchemaTableName(), expected.getSchemaTableName()); + assertThat(actual.getSchemaTableName()) + .isEqualTo(expected.getSchemaTableName()); } @Test @@ -95,7 +98,8 @@ public void testRoundTripWithQueryHavingHelperFunction() String json = codec.toJson(expected); MongoTableHandle actual = codec.fromJson(json); - assertEquals(actual.getSchemaTableName(), expected.getSchemaTableName()); + assertThat(actual.getSchemaTableName()) + .isEqualTo(expected.getSchemaTableName()); } @Test @@ -126,6 +130,7 @@ public void testRoundTripWithProjectedColumns() String json = codec.toJson(expected); MongoTableHandle actual = codec.fromJson(json); - assertEquals(actual, expected); + assertThat(actual) + .isEqualTo(expected); } } diff --git a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoTypeMapping.java b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoTypeMapping.java index 854468e5d0f9..92fcf5fea78f 100644 --- a/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoTypeMapping.java +++ b/plugin/trino-mongodb/src/test/java/io/trino/plugin/mongodb/TestMongoTypeMapping.java @@ -28,7 +28,7 @@ import io.trino.testing.datatype.SqlDataTypeTest; import io.trino.testing.sql.TestTable; import io.trino.testing.sql.TrinoSqlExecutor; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.time.ZoneId; diff --git a/plugin/trino-mysql/pom.xml b/plugin/trino-mysql/pom.xml index 12c3a606c23f..e446e34f5fec 100644 --- a/plugin/trino-mysql/pom.xml +++ b/plugin/trino-mysql/pom.xml @@ -232,33 +232,5 @@ testcontainers test - - - org.testng - testng - test - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - - diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/BaseMySqlConnectorTest.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/BaseMySqlConnectorTest.java index cc0322f29c5a..8a93907a4298 100644 --- a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/BaseMySqlConnectorTest.java +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/BaseMySqlConnectorTest.java @@ -45,9 +45,6 @@ import static java.util.stream.IntStream.range; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public abstract class BaseMySqlConnectorTest extends BaseJdbcConnectorTest @@ -206,15 +203,15 @@ public void testNameEscaping() .setSchema(getSession().getSchema()) .build(); - assertFalse(getQueryRunner().tableExists(session, "test_table")); + assertThat(getQueryRunner().tableExists(session, "test_table")).isFalse(); assertUpdate(session, "CREATE TABLE test_table AS SELECT 123 x", 1); - assertTrue(getQueryRunner().tableExists(session, "test_table")); + assertThat(getQueryRunner().tableExists(session, "test_table")).isTrue(); assertQuery(session, "SELECT * FROM test_table", "SELECT 123"); assertUpdate(session, "DROP TABLE test_table"); - assertFalse(getQueryRunner().tableExists(session, "test_table")); + assertThat(getQueryRunner().tableExists(session, "test_table")).isFalse(); } @Test @@ -226,7 +223,8 @@ public void testMySqlTinyint() onRemoteDatabase().execute("INSERT INTO tpch.mysql_test_tinyint1 VALUES (127), (-128)"); MaterializedResult materializedRows = computeActual("SELECT * FROM tpch.mysql_test_tinyint1 WHERE c_tinyint = 127"); - assertEquals(materializedRows.getOnlyValue(), (byte) 127); + assertThat(materializedRows.getOnlyValue()) + .isEqualTo((byte) 127); assertUpdate("DROP TABLE mysql_test_tinyint1"); } @@ -517,7 +515,7 @@ public void testNativeMultipleInClauses() public void testNativeQueryInsertStatementTableDoesNotExist() { // override because MySQL succeeds in preparing query, and then fails because of no metadata available - assertFalse(getQueryRunner().tableExists(getSession(), "non_existent_table")); + assertThat(getQueryRunner().tableExists(getSession(), "non_existent_table")).isFalse(); assertThatThrownBy(() -> query("SELECT * FROM TABLE(system.query(query => 'INSERT INTO non_existent_table VALUES (1)'))")) .hasMessageContaining("Query not supported: ResultSetMetaData not available for query: INSERT INTO non_existent_table VALUES (1)"); } diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/BaseTestMySqlTableStatisticsTest.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/BaseTestMySqlTableStatisticsTest.java index 16c38585aa9f..adf256e67f79 100644 --- a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/BaseTestMySqlTableStatisticsTest.java +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/BaseTestMySqlTableStatisticsTest.java @@ -42,9 +42,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.withinPercentage; import static org.junit.jupiter.api.Assumptions.abort; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; public abstract class BaseTestMySqlTableStatisticsTest extends BaseJdbcTableStatisticsTest @@ -163,17 +160,29 @@ public void testAllNulls() } if ((columnName.equals("orderpriority") || columnName.equals("comment")) && varcharNdvToExpected.apply(2) == null) { - assertNull(row.getField(2), "NDV for " + columnName); - assertNull(row.getField(3), "null fraction for " + columnName); + assertThat(row.getField(2)) + .describedAs("NDV for " + columnName) + .isNull(); + assertThat(row.getField(3)) + .describedAs("null fraction for " + columnName) + .isNull(); } else { - assertNotNull(row.getField(2), "NDV for " + columnName); + assertThat(row.getField(2)) + .describedAs("NDV for " + columnName) + .isNotNull(); assertThat((Double) row.getField(2)).as("NDV for " + columnName).isBetween(0.0, 2.0); - assertEquals(row.getField(3), nullFractionToExpected.apply(1.0), "null fraction for " + columnName); + assertThat(row.getField(3)) + .describedAs("null fraction for " + columnName) + .isEqualTo(nullFractionToExpected.apply(1.0)); } - assertNull(row.getField(4), "min"); - assertNull(row.getField(5), "max"); + assertThat(row.getField(4)) + .describedAs("min") + .isNull(); + assertThat(row.getField(5)) + .describedAs("max") + .isNull(); } double cardinality = getTableCardinalityFromStats(statsResult); if (cardinality != 15.0) { @@ -362,7 +371,8 @@ protected void assertColumnStats(MaterializedResult statsResult, Map columnNdvs, Map columnNullFractions) { - assertEquals(columnNdvs.keySet(), columnNullFractions.keySet()); + assertThat(columnNdvs.keySet()) + .isEqualTo(columnNullFractions.keySet()); List reportedColumns = stream(statsResult) .map(row -> row.getField(0)) // column name .filter(Objects::nonNull) @@ -393,7 +403,9 @@ protected void assertColumnStats(MaterializedResult statsResult, Map ndvAssertion = assertThat(distinctCount).as("NDV for " + columnName); if (expectedNdv == null) { ndvAssertion.isNull(); - assertNull(nullsFraction, "null fraction for " + columnName); + assertThat(nullsFraction) + .describedAs("null fraction for " + columnName) + .isNull(); } else { ndvAssertion.isBetween(expectedNdv * 0.5, min(expectedNdv * 4.0, tableCardinality)); // [-50%, +300%] but no more than row count @@ -408,22 +420,27 @@ protected void assertColumnStats(MaterializedResult statsResult, Map columnMapping = JDBC_CLIENT.toColumnMapping(SESSION, null, result.get().getJdbcTypeHandle()); - assertTrue(columnMapping.isPresent(), "No mapping for: " + result.get().getJdbcTypeHandle()); - assertEquals(columnMapping.get().getType(), aggregateFunction.getOutputType()); + assertThat(columnMapping.isPresent()) + .describedAs("No mapping for: " + result.get().getJdbcTypeHandle()) + .isTrue(); + assertThat(columnMapping.get().getType()) + .isEqualTo(aggregateFunction.getOutputType()); } } } diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlJdbcConfig.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlJdbcConfig.java index 99afceb7294a..c0823df797d4 100644 --- a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlJdbcConfig.java +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlJdbcConfig.java @@ -15,17 +15,16 @@ import org.junit.jupiter.api.Test; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestMySqlJdbcConfig { @Test public void testIsUrlWithoutDatabase() { - assertTrue(isUrlWithoutDatabase("jdbc:mysql://example.net:3306")); - assertTrue(isUrlWithoutDatabase("jdbc:mysql://example.net:3306/")); - assertFalse(isUrlWithoutDatabase("jdbc:mysql://example.net:3306/somedatabase")); + assertThat(isUrlWithoutDatabase("jdbc:mysql://example.net:3306")).isTrue(); + assertThat(isUrlWithoutDatabase("jdbc:mysql://example.net:3306/")).isTrue(); + assertThat(isUrlWithoutDatabase("jdbc:mysql://example.net:3306/somedatabase")).isFalse(); } private static boolean isUrlWithoutDatabase(String url) diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlTimeMappingsWithServerTimeZone.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlTimeMappingsWithServerTimeZone.java index 203985ad27a1..471991196c64 100644 --- a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlTimeMappingsWithServerTimeZone.java +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlTimeMappingsWithServerTimeZone.java @@ -28,7 +28,7 @@ import io.trino.testing.sql.TestTable; import io.trino.testing.sql.TrinoSqlExecutor; import org.intellij.lang.annotations.Language; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.DriverManager; diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlTypeMapping.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlTypeMapping.java index 70a34ac275d8..a650a0c742cc 100644 --- a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlTypeMapping.java +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlTypeMapping.java @@ -30,8 +30,10 @@ import io.trino.testing.sql.TestTable; import io.trino.testing.sql.TrinoSqlExecutor; import org.intellij.lang.annotations.Language; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.math.RoundingMode; import java.sql.Connection; @@ -75,7 +77,11 @@ import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestMySqlTypeMapping extends AbstractTestQueryFramework { @@ -85,7 +91,7 @@ public class TestMySqlTypeMapping // no DST in 1970, but has DST in later years (e.g. 2018) private final ZoneId vilnius = ZoneId.of("Europe/Vilnius"); - @BeforeClass + @BeforeAll public void setUp() { checkState(jvmZone.getId().equals("America/Bahia_Banderas"), "This test assumes certain JVM time zone"); diff --git a/plugin/trino-oracle/pom.xml b/plugin/trino-oracle/pom.xml index eaf458272ee3..b985032e558e 100644 --- a/plugin/trino-oracle/pom.xml +++ b/plugin/trino-oracle/pom.xml @@ -242,33 +242,5 @@ testcontainers test - - - org.testng - testng - test - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - - diff --git a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/AbstractTestOracleTypeMapping.java b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/AbstractTestOracleTypeMapping.java index 20aaf45b95a0..45b3740cd38b 100644 --- a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/AbstractTestOracleTypeMapping.java +++ b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/AbstractTestOracleTypeMapping.java @@ -28,8 +28,10 @@ import io.trino.testing.sql.SqlExecutor; import io.trino.testing.sql.TestTable; import io.trino.testing.sql.TrinoSqlExecutor; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.math.RoundingMode; import java.time.LocalDate; @@ -65,7 +67,11 @@ import static java.math.RoundingMode.HALF_UP; import static java.math.RoundingMode.UNNECESSARY; import static java.time.ZoneOffset.UTC; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public abstract class AbstractTestOracleTypeMapping extends AbstractTestQueryFramework { @@ -94,7 +100,7 @@ public abstract class AbstractTestOracleTypeMapping private final ZoneId kathmandu = ZoneId.of("Asia/Kathmandu"); private final LocalDateTime timeGapInKathmandu = LocalDateTime.of(1986, 1, 1, 0, 13, 7); - @BeforeClass + @BeforeAll public void setUp() { checkState(jvmZone.getId().equals("America/Bahia_Banderas"), "This test assumes certain JVM time zone"); diff --git a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/BaseOracleConnectorTest.java b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/BaseOracleConnectorTest.java index 644959e2d8e1..183f451aa0d6 100644 --- a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/BaseOracleConnectorTest.java +++ b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/BaseOracleConnectorTest.java @@ -38,8 +38,6 @@ import static java.util.Locale.ENGLISH; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; public abstract class BaseOracleConnectorTest extends BaseJdbcConnectorTest @@ -186,7 +184,8 @@ public void testTimestampOutOfPrecisionRounded() assertUpdate("CREATE TABLE " + tableName + " (t timestamp(12))"); - assertEquals(getColumnType(tableName, "t"), "timestamp(9)"); + assertThat(getColumnType(tableName, "t")) + .isEqualTo("timestamp(9)"); assertUpdate("DROP TABLE " + tableName); } @@ -402,7 +401,7 @@ public void testNativeQueryParameters() public void testNativeQueryInsertStatementTableDoesNotExist() { // override because Oracle succeeds in preparing query, and then fails because of no metadata available - assertFalse(getQueryRunner().tableExists(getSession(), "non_existent_table")); + assertThat(getQueryRunner().tableExists(getSession(), "non_existent_table")).isFalse(); assertThatThrownBy(() -> query("SELECT * FROM TABLE(system.query(query => 'INSERT INTO non_existent_table VALUES (1)'))")) .hasMessageContaining("Query not supported: ResultSetMetaData not available for query: INSERT INTO non_existent_table VALUES (1)"); } diff --git a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleClient.java b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleClient.java index 03615dfaed3c..28706d97facc 100644 --- a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleClient.java +++ b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleClient.java @@ -24,7 +24,7 @@ import io.trino.spi.type.Type; import io.trino.testing.TestingConnectorSession; import oracle.jdbc.OracleTypes; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.sql.PreparedStatement; import java.sql.SQLException; diff --git a/plugin/trino-phoenix5/pom.xml b/plugin/trino-phoenix5/pom.xml index d85fc8732d7f..c2af43e4b25d 100644 --- a/plugin/trino-phoenix5/pom.xml +++ b/plugin/trino-phoenix5/pom.xml @@ -378,33 +378,10 @@ junit-jupiter-engine test - - - org.testng - testng - test - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - org.basepom.maven duplicate-finder-maven-plugin diff --git a/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixConnectorTest.java b/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixConnectorTest.java index 02a206a5297e..50a3d2335e14 100644 --- a/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixConnectorTest.java +++ b/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixConnectorTest.java @@ -65,7 +65,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assumptions.abort; -import static org.testng.Assert.assertTrue; public class TestPhoenixConnectorTest extends BaseJdbcConnectorTest @@ -600,7 +599,7 @@ private Session withUnsupportedType(UnsupportedTypeHandling unsupportedTypeHandl public void testCreateTableWithProperties() { assertUpdate("CREATE TABLE test_create_table_with_properties (created_date date, a bigint, b double, c varchar(10), d varchar(10)) WITH(rowkeys = 'created_date row_timestamp, a,b,c', salt_buckets=10)"); - assertTrue(getQueryRunner().tableExists(getSession(), "test_create_table_with_properties")); + assertThat(getQueryRunner().tableExists(getSession(), "test_create_table_with_properties")).isTrue(); assertTableColumnNames("test_create_table_with_properties", "created_date", "a", "b", "c", "d"); assertThat(computeActual("SHOW CREATE TABLE test_create_table_with_properties").getOnlyValue()) .isEqualTo("CREATE TABLE phoenix.tpch.test_create_table_with_properties (\n" + @@ -624,7 +623,7 @@ public void testCreateTableWithProperties() public void testCreateTableWithPresplits() { assertUpdate("CREATE TABLE test_create_table_with_presplits (rid varchar(10), val1 varchar(10)) with(rowkeys = 'rid', SPLIT_ON='\"1\",\"2\",\"3\"')"); - assertTrue(getQueryRunner().tableExists(getSession(), "test_create_table_with_presplits")); + assertThat(getQueryRunner().tableExists(getSession(), "test_create_table_with_presplits")).isTrue(); assertTableColumnNames("test_create_table_with_presplits", "rid", "val1"); assertUpdate("DROP TABLE test_create_table_with_presplits"); } diff --git a/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixSplit.java b/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixSplit.java index f9311bc48047..dea8625592ef 100644 --- a/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixSplit.java +++ b/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixSplit.java @@ -24,8 +24,7 @@ import java.util.List; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestPhoenixSplit { @@ -42,11 +41,11 @@ public void testPhoenixSplitJsonRoundtrip() addresses, SerializedPhoenixInputSplit.serialize(phoenixInputSplit)); - assertTrue(objectMapper.canSerialize(PhoenixSplit.class)); + assertThat(objectMapper.canSerialize(PhoenixSplit.class)).isTrue(); String json = objectMapper.writeValueAsString(expected); PhoenixSplit actual = objectMapper.readValue(json, PhoenixSplit.class); - assertEquals(actual.getPhoenixInputSplit(), expected.getPhoenixInputSplit()); - assertEquals(actual.getAddresses(), expected.getAddresses()); + assertThat(actual.getPhoenixInputSplit()).isEqualTo(expected.getPhoenixInputSplit()); + assertThat(actual.getAddresses()).isEqualTo(expected.getAddresses()); } } diff --git a/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixTypeMapping.java b/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixTypeMapping.java index 7d552e893501..672b4e75fea4 100644 --- a/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixTypeMapping.java +++ b/plugin/trino-phoenix5/src/test/java/io/trino/plugin/phoenix5/TestPhoenixTypeMapping.java @@ -29,8 +29,10 @@ import io.trino.testing.sql.TestTable; import io.trino.testing.sql.TrinoSqlExecutor; import org.intellij.lang.annotations.Language; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.math.RoundingMode; import java.time.LocalDate; @@ -68,10 +70,14 @@ import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; /** * @see Phoenix data types */ +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestPhoenixTypeMapping extends AbstractTestQueryFramework { @@ -83,7 +89,7 @@ public class TestPhoenixTypeMapping // minutes offset change since 1970-01-01, no DST private final ZoneId kathmandu = ZoneId.of("Asia/Kathmandu"); - @BeforeClass + @BeforeAll public void setUp() { checkState(jvmZone.getId().equals("America/Bahia_Banderas"), "This test assumes certain JVM time zone"); diff --git a/plugin/trino-postgresql/pom.xml b/plugin/trino-postgresql/pom.xml index fb6b2fec4ef5..ab6716e0d5ac 100644 --- a/plugin/trino-postgresql/pom.xml +++ b/plugin/trino-postgresql/pom.xml @@ -257,33 +257,5 @@ testcontainers test - - - org.testng - testng - test - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - - diff --git a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlClient.java b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlClient.java index 7e18922d18dd..fd47c631f77d 100644 --- a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlClient.java +++ b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlClient.java @@ -53,7 +53,7 @@ import io.trino.sql.tree.StringLiteral; import io.trino.sql.tree.SymbolReference; import io.trino.testing.TestingConnectorSession; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.sql.Types; import java.util.List; @@ -73,8 +73,6 @@ import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; public class TestPostgreSqlClient { @@ -214,10 +212,12 @@ private static void testImplementAggregation(AggregateFunction aggregateFunction } else { assertThat(result).isPresent(); - assertEquals(result.get().getExpression(), expectedExpression.get()); + assertThat(result.get().getExpression()).isEqualTo(expectedExpression.get()); Optional columnMapping = JDBC_CLIENT.toColumnMapping(SESSION, null, result.get().getJdbcTypeHandle()); - assertTrue(columnMapping.isPresent(), "No mapping for: " + result.get().getJdbcTypeHandle()); - assertEquals(columnMapping.get().getType(), aggregateFunction.getOutputType()); + assertThat(columnMapping.isPresent()) + .describedAs("No mapping for: " + result.get().getJdbcTypeHandle()) + .isTrue(); + assertThat(columnMapping.get().getType()).isEqualTo(aggregateFunction.getOutputType()); } } diff --git a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlConnectorTest.java b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlConnectorTest.java index 0e1ebe8faec1..8170cdcff5f2 100644 --- a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlConnectorTest.java +++ b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlConnectorTest.java @@ -75,8 +75,6 @@ import static java.util.stream.IntStream.range; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) public class TestPostgreSqlConnectorTest @@ -170,7 +168,7 @@ private void testTimestampPrecisionOnCreateTable(String inputType, String expect getQueryRunner()::execute, "test_coercion_show_create_table", format("(a %s)", inputType))) { - assertEquals(getColumnType(testTable.getName(), "a"), expectedType); + assertThat(getColumnType(testTable.getName(), "a")).isEqualTo(expectedType); } } @@ -210,7 +208,7 @@ private void testTimestampPrecisionOnCreateTableAsSelect(String inputType, Strin getQueryRunner()::execute, "test_coercion_show_create_table", format("AS SELECT %s a", inputType))) { - assertEquals(getColumnType(testTable.getName(), "a"), tableType); + assertThat(getColumnType(testTable.getName(), "a")).isEqualTo(tableType); assertQuery( format("SELECT * FROM %s", testTable.getName()), format("VALUES (%s)", tableValue)); @@ -253,7 +251,7 @@ private void testTimestampPrecisionOnCreateTableAsSelectWithNoData(String inputT getQueryRunner()::execute, "test_coercion_show_create_table", format("AS SELECT %s a WITH NO DATA", inputType))) { - assertEquals(getColumnType(testTable.getName(), "a"), tableType); + assertThat(getColumnType(testTable.getName(), "a")).isEqualTo(tableType); } } @@ -267,7 +265,7 @@ protected void verifyAddNotNullColumnToNonEmptyTableFailurePermissible(Throwable public void testViews() { onRemoteDatabase().execute("CREATE OR REPLACE VIEW test_view AS SELECT * FROM orders"); - assertTrue(getQueryRunner().tableExists(getSession(), "test_view")); + assertThat(getQueryRunner().tableExists(getSession(), "test_view")).isTrue(); assertQuery("SELECT orderkey FROM test_view", "SELECT orderkey FROM orders"); onRemoteDatabase().execute("DROP VIEW IF EXISTS test_view"); } @@ -276,7 +274,7 @@ public void testViews() public void testPostgreSqlMaterializedView() { onRemoteDatabase().execute("CREATE MATERIALIZED VIEW test_mv as SELECT * FROM orders"); - assertTrue(getQueryRunner().tableExists(getSession(), "test_mv")); + assertThat(getQueryRunner().tableExists(getSession(), "test_mv")).isTrue(); assertQuery("SELECT orderkey FROM test_mv", "SELECT orderkey FROM orders"); onRemoteDatabase().execute("DROP MATERIALIZED VIEW test_mv"); } @@ -286,7 +284,7 @@ public void testForeignTable() { onRemoteDatabase().execute("CREATE SERVER devnull FOREIGN DATA WRAPPER file_fdw"); onRemoteDatabase().execute("CREATE FOREIGN TABLE test_ft (x bigint) SERVER devnull OPTIONS (filename '/dev/null')"); - assertTrue(getQueryRunner().tableExists(getSession(), "test_ft")); + assertThat(getQueryRunner().tableExists(getSession(), "test_ft")).isTrue(); computeActual("SELECT * FROM test_ft"); onRemoteDatabase().execute("DROP FOREIGN TABLE test_ft"); onRemoteDatabase().execute("DROP SERVER devnull"); @@ -296,13 +294,13 @@ public void testForeignTable() public void testErrorDuringInsert() { onRemoteDatabase().execute("CREATE TABLE test_with_constraint (x bigint primary key)"); - assertTrue(getQueryRunner().tableExists(getSession(), "test_with_constraint")); + assertThat(getQueryRunner().tableExists(getSession(), "test_with_constraint")).isTrue(); Session nonTransactional = Session.builder(getSession()) .setCatalogSessionProperty("postgresql", "non_transactional_insert", "true") .build(); assertUpdate(nonTransactional, "INSERT INTO test_with_constraint VALUES (1)", 1); assertQueryFails(nonTransactional, "INSERT INTO test_with_constraint VALUES (1)", "[\\s\\S]*ERROR: duplicate key value[\\s\\S]*"); - assertTrue(getQueryRunner().tableExists(getSession(), "test_with_constraint")); + assertThat(getQueryRunner().tableExists(getSession(), "test_with_constraint")).isTrue(); onRemoteDatabase().execute("DROP TABLE test_with_constraint"); } diff --git a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlTypeMapping.java b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlTypeMapping.java index a0bab311b393..49db947ecb56 100644 --- a/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlTypeMapping.java +++ b/plugin/trino-postgresql/src/test/java/io/trino/plugin/postgresql/TestPostgreSqlTypeMapping.java @@ -38,8 +38,10 @@ import io.trino.testing.sql.TestTable; import io.trino.testing.sql.TrinoSqlExecutor; import org.intellij.lang.annotations.Language; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.math.BigDecimal; import java.math.RoundingMode; @@ -105,7 +107,11 @@ import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestPostgreSqlTypeMapping extends AbstractTestQueryFramework { @@ -146,7 +152,7 @@ protected QueryRunner createQueryRunner() ImmutableList.of()); } - @BeforeClass + @BeforeAll public void setUp() { checkState(jvmZone.getId().equals("America/Bahia_Banderas"), "This test assumes certain JVM time zone"); diff --git a/plugin/trino-singlestore/pom.xml b/plugin/trino-singlestore/pom.xml index 5038f4b47417..0ef29ecc78b4 100644 --- a/plugin/trino-singlestore/pom.xml +++ b/plugin/trino-singlestore/pom.xml @@ -211,33 +211,5 @@ testcontainers test - - - org.testng - testng - test - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - - diff --git a/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreConnectorTest.java b/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreConnectorTest.java index 92f8e672279f..ebfbed69a8e3 100644 --- a/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreConnectorTest.java +++ b/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreConnectorTest.java @@ -42,9 +42,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assumptions.abort; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) public class TestSingleStoreConnectorTest @@ -182,15 +179,15 @@ public void testReadFromView() @Test public void testNameEscaping() { - assertFalse(getQueryRunner().tableExists(getSession(), "test_table")); + assertThat(getQueryRunner().tableExists(getSession(), "test_table")).isFalse(); assertUpdate(getSession(), "CREATE TABLE test_table AS SELECT 123 x", 1); - assertTrue(getQueryRunner().tableExists(getSession(), "test_table")); + assertThat(getQueryRunner().tableExists(getSession(), "test_table")).isTrue(); assertQuery(getSession(), "SELECT * FROM test_table", "SELECT 123"); assertUpdate(getSession(), "DROP TABLE test_table"); - assertFalse(getQueryRunner().tableExists(getSession(), "test_table")); + assertThat(getQueryRunner().tableExists(getSession(), "test_table")).isFalse(); } @Test @@ -205,11 +202,14 @@ public void testSingleStoreTinyint() onRemoteDatabase().execute("INSERT INTO tpch.mysql_test_tinyint1 VALUES (127), (-128)"); MaterializedResult materializedRows = computeActual("SELECT * FROM tpch.mysql_test_tinyint1 WHERE c_tinyint = 127"); - assertEquals(materializedRows.getRowCount(), 1); + assertThat(materializedRows.getRowCount()) + .isEqualTo(1); MaterializedRow row = getOnlyElement(materializedRows); - assertEquals(row.getFields().size(), 1); - assertEquals(row.getField(0), (byte) 127); + assertThat(row.getFields().size()) + .isEqualTo(1); + assertThat(row.getField(0)) + .isEqualTo((byte) 127); assertUpdate("DROP TABLE mysql_test_tinyint1"); } @@ -350,10 +350,10 @@ public void testNativeQueryCreateStatement() // This is unusual, because other connectors don't produce a ResultSet metadata for CREATE TABLE at all. // The query fails because there are no columns, but even if columns were not required, the query would fail // to execute in SingleStore because the connector wraps it in additional syntax, which causes syntax error. - assertFalse(getQueryRunner().tableExists(getSession(), "numbers")); + assertThat(getQueryRunner().tableExists(getSession(), "numbers")).isFalse(); assertThatThrownBy(() -> query("SELECT * FROM TABLE(system.query(query => 'CREATE TABLE numbers(n INTEGER)'))")) .hasMessageContaining("descriptor has no fields"); - assertFalse(getQueryRunner().tableExists(getSession(), "numbers")); + assertThat(getQueryRunner().tableExists(getSession(), "numbers")).isFalse(); } @Test diff --git a/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreJdbcConfig.java b/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreJdbcConfig.java index 8f5717189a01..b00272937150 100644 --- a/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreJdbcConfig.java +++ b/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreJdbcConfig.java @@ -23,9 +23,8 @@ import static com.google.common.collect.MoreCollectors.toOptional; import static com.google.common.collect.Streams.stream; import static io.trino.plugin.singlestore.SingleStoreJdbcConfig.DRIVER_PROTOCOL_ERROR; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestSingleStoreJdbcConfig { @@ -49,10 +48,10 @@ public void testCreateConnectorLegacyUrl() @Test public void testLegacyConnectionUrl() { - assertTrue(isLegacyDriverSubprotocol("jdbc:mariadb:")); - assertTrue(isLegacyDriverSubprotocol("JDBC:MARIADB:")); - assertTrue(isLegacyDriverSubprotocol("jdbc:mariadb:test")); - assertFalse(isLegacyDriverSubprotocol("jdbc:singlestore:test")); + assertThat(isLegacyDriverSubprotocol("jdbc:mariadb:")).isTrue(); + assertThat(isLegacyDriverSubprotocol("JDBC:MARIADB:")).isTrue(); + assertThat(isLegacyDriverSubprotocol("jdbc:mariadb:test")).isTrue(); + assertThat(isLegacyDriverSubprotocol("jdbc:singlestore:test")).isFalse(); } private static boolean isLegacyDriverSubprotocol(String url) diff --git a/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreTypeMapping.java b/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreTypeMapping.java index 6309f16fc90b..9c89484014d8 100644 --- a/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreTypeMapping.java +++ b/plugin/trino-singlestore/src/test/java/io/trino/plugin/singlestore/TestSingleStoreTypeMapping.java @@ -29,8 +29,10 @@ import io.trino.testing.sql.SqlExecutor; import io.trino.testing.sql.TestTable; import io.trino.testing.sql.TrinoSqlExecutor; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.math.RoundingMode; import java.time.LocalDate; @@ -72,10 +74,14 @@ import static java.time.ZoneOffset.UTC; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; /** * @see SingleStore (MemSQL) data types */ +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestSingleStoreTypeMapping extends AbstractTestQueryFramework { @@ -99,7 +105,7 @@ protected QueryRunner createQueryRunner() return createSingleStoreQueryRunner(singleStoreServer, ImmutableMap.of(), ImmutableMap.of(), ImmutableList.of()); } - @BeforeClass + @BeforeAll public void setUp() { checkState(jvmZone.getId().equals("America/Bahia_Banderas"), "This test assumes certain JVM time zone"); diff --git a/plugin/trino-sqlserver/pom.xml b/plugin/trino-sqlserver/pom.xml index a9781ff81f7a..0787eceaaa62 100644 --- a/plugin/trino-sqlserver/pom.xml +++ b/plugin/trino-sqlserver/pom.xml @@ -253,33 +253,5 @@ testcontainers test - - - org.testng - testng - test - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - - diff --git a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerConnectorTest.java b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerConnectorTest.java index bc4d2e96a04f..237dd55659cf 100644 --- a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerConnectorTest.java +++ b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerConnectorTest.java @@ -42,8 +42,6 @@ import static java.util.stream.IntStream.range; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assumptions.abort; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; public abstract class BaseSqlServerConnectorTest extends BaseJdbcConnectorTest @@ -122,7 +120,7 @@ protected Optional filterDataMappingSmokeTestData(DataMapp public void testReadFromView() { onRemoteDatabase().execute("CREATE VIEW test_view AS SELECT * FROM orders"); - assertTrue(getQueryRunner().tableExists(getSession(), "test_view")); + assertThat(getQueryRunner().tableExists(getSession(), "test_view")).isTrue(); assertQuery("SELECT orderkey FROM test_view", "SELECT orderkey FROM orders"); onRemoteDatabase().execute("DROP VIEW IF EXISTS test_view"); } @@ -489,7 +487,8 @@ private void testCreateWithDataCompression(DataCompression dataCompression) dataCompression); assertUpdate(createQuery); - assertEquals(getQueryRunner().execute("SHOW CREATE TABLE " + tableName).getOnlyValue(), createQuery); + assertThat(getQueryRunner().execute("SHOW CREATE TABLE " + tableName).getOnlyValue()) + .isEqualTo(createQuery); assertUpdate("DROP TABLE " + tableName); } diff --git a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerTypeMapping.java b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerTypeMapping.java index a73215f5b524..c762e783337d 100644 --- a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerTypeMapping.java +++ b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerTypeMapping.java @@ -27,8 +27,10 @@ import io.trino.testing.sql.TestTable; import io.trino.testing.sql.TrinoSqlExecutor; import org.intellij.lang.annotations.Language; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.time.LocalDate; import java.time.LocalDateTime; @@ -56,7 +58,11 @@ import static java.lang.String.format; import static java.time.ZoneOffset.UTC; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public abstract class BaseSqlServerTypeMapping extends AbstractTestQueryFramework { @@ -76,7 +82,7 @@ public abstract class BaseSqlServerTypeMapping protected TestingSqlServer sqlServer; - @BeforeClass + @BeforeAll public void setUp() { checkState(jvmZone.getId().equals("America/Bahia_Banderas"), "This test assumes certain JVM time zone"); diff --git a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerClient.java b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerClient.java index 7b050b5bac86..50958eed81d9 100644 --- a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerClient.java +++ b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerClient.java @@ -39,8 +39,6 @@ import static io.trino.spi.type.DoubleType.DOUBLE; import static io.trino.testing.TestingConnectorSession.SESSION; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; public class TestSqlServerClient { @@ -159,10 +157,14 @@ private static void testImplementAggregation(AggregateFunction aggregateFunction } else { assertThat(result).isPresent(); - assertEquals(result.get().getExpression(), expectedExpression.get()); + assertThat(result.get().getExpression()) + .isEqualTo(expectedExpression.get()); Optional columnMapping = JDBC_CLIENT.toColumnMapping(SESSION, null, result.get().getJdbcTypeHandle()); - assertTrue(columnMapping.isPresent(), "No mapping for: " + result.get().getJdbcTypeHandle()); - assertEquals(columnMapping.get().getType(), aggregateFunction.getOutputType()); + assertThat(columnMapping.isPresent()) + .describedAs("No mapping for: " + result.get().getJdbcTypeHandle()) + .isTrue(); + assertThat(columnMapping.get().getType()) + .isEqualTo(aggregateFunction.getOutputType()); } } } diff --git a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerConnectorTest.java b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerConnectorTest.java index 434da9cc1f69..93a16229f5a9 100644 --- a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerConnectorTest.java +++ b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerConnectorTest.java @@ -38,8 +38,6 @@ import static java.util.Locale.ENGLISH; import static java.util.stream.Collectors.joining; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestSqlServerConnectorTest extends BaseSqlServerConnectorTest @@ -197,11 +195,11 @@ public void testCreateAndDropTableWithSpecialCharacterName() // Until https://github.com/trinodb/trino/issues/17 the table name is effectively lowercase tableName = tableName.toLowerCase(ENGLISH); assertUpdate("CREATE TABLE " + tableNameInSql + " (a bigint, b double, c varchar(50))"); - assertTrue(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isTrue(); assertTableColumnNames(tableNameInSql, "a", "b", "c"); assertUpdate("DROP TABLE " + tableNameInSql); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); } } diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestDenyOnSchema.java b/testing/trino-tests/src/test/java/io/trino/execution/TestDenyOnSchema.java index 6f793478fcb2..02f3d24b6b39 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestDenyOnSchema.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestDenyOnSchema.java @@ -30,9 +30,11 @@ import io.trino.spi.security.TrinoPrincipal; import io.trino.sql.query.QueryAssertions; import io.trino.testing.DistributedQueryRunner; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.EnumSet; import java.util.Set; @@ -45,8 +47,11 @@ import static java.util.Locale.ROOT; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -@Test(singleThreaded = true) +@TestInstance(PER_CLASS) +@Execution(SAME_THREAD) public class TestDenyOnSchema { private final SchemaTableName table = new SchemaTableName("default", "table_one"); @@ -60,7 +65,7 @@ public class TestDenyOnSchema private TrinoPrincipal expectedGrantee; private boolean denyCalled; - @BeforeClass + @BeforeAll public void initClass() throws Exception { @@ -96,7 +101,7 @@ public void denySchemaPrivileges(Session session, tableGrants.grant(new TrinoPrincipal(USER, "admin"), table, EnumSet.allOf(Privilege.class), true); } - @AfterClass(alwaysRun = true) + @AfterAll public void teardown() { assertions.close(); diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestDenyOnTable.java b/testing/trino-tests/src/test/java/io/trino/execution/TestDenyOnTable.java index c27053e2da8c..84aa8d0ba354 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestDenyOnTable.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestDenyOnTable.java @@ -30,9 +30,11 @@ import io.trino.spi.security.TrinoPrincipal; import io.trino.sql.query.QueryAssertions; import io.trino.testing.DistributedQueryRunner; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.EnumSet; import java.util.Set; @@ -45,8 +47,11 @@ import static java.util.Locale.ROOT; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -@Test(singleThreaded = true) +@TestInstance(PER_CLASS) +@Execution(SAME_THREAD) public class TestDenyOnTable { private final SchemaTableName table = new SchemaTableName("default", "table_one"); @@ -60,7 +65,7 @@ public class TestDenyOnTable private TrinoPrincipal expectedGrantee; private boolean denyCalled; - @BeforeClass + @BeforeAll public void initClass() throws Exception { @@ -93,7 +98,7 @@ public void denyTablePrivileges(Session session, QualifiedObjectName tableName, tableGrants.grant(new TrinoPrincipal(USER, "admin"), table, EnumSet.allOf(Privilege.class), true); } - @AfterClass(alwaysRun = true) + @AfterAll public void teardown() { assertions.close(); diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestGrantOnSchema.java b/testing/trino-tests/src/test/java/io/trino/execution/TestGrantOnSchema.java index 7406ca2c3061..9014df44aac7 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestGrantOnSchema.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestGrantOnSchema.java @@ -24,9 +24,11 @@ import io.trino.spi.security.TrinoPrincipal; import io.trino.sql.query.QueryAssertions; import io.trino.testing.DistributedQueryRunner; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.EnumSet; @@ -36,7 +38,11 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestGrantOnSchema { private final Session admin = sessionOf("admin"); @@ -44,7 +50,7 @@ public class TestGrantOnSchema private DistributedQueryRunner queryRunner; private QueryAssertions assertions; - @BeforeClass + @BeforeAll public void initClass() throws Exception { @@ -61,7 +67,7 @@ public void initClass() schemaGrants.grant(new TrinoPrincipal(USER, admin.getUser()), "default", EnumSet.allOf(Privilege.class), true); } - @AfterClass(alwaysRun = true) + @AfterAll public void teardown() { assertions.close(); diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestGrantOnTable.java b/testing/trino-tests/src/test/java/io/trino/execution/TestGrantOnTable.java index 81f218ba4ea5..07865f93a020 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestGrantOnTable.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestGrantOnTable.java @@ -26,9 +26,11 @@ import io.trino.spi.security.TrinoPrincipal; import io.trino.sql.query.QueryAssertions; import io.trino.testing.DistributedQueryRunner; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.EnumSet; import java.util.Optional; @@ -41,7 +43,11 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestGrantOnTable { private final SchemaTableName table = new SchemaTableName("default", "table_one"); @@ -50,7 +56,7 @@ public class TestGrantOnTable private DistributedQueryRunner queryRunner; private QueryAssertions assertions; - @BeforeClass + @BeforeAll public void initClass() throws Exception { @@ -68,7 +74,7 @@ public void initClass() tableGrants.grant(new TrinoPrincipal(USER, "admin"), table, EnumSet.allOf(Privilege.class), true); } - @AfterClass(alwaysRun = true) + @AfterAll public void teardown() { assertions.close(); diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestRevokeOnSchema.java b/testing/trino-tests/src/test/java/io/trino/execution/TestRevokeOnSchema.java index 62467be21e8f..8a02451ae34d 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestRevokeOnSchema.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestRevokeOnSchema.java @@ -25,9 +25,11 @@ import io.trino.spi.security.TrinoPrincipal; import io.trino.sql.query.QueryAssertions; import io.trino.testing.DistributedQueryRunner; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.EnumSet; @@ -37,7 +39,11 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestRevokeOnSchema { private static final Session admin = sessionOf("admin"); @@ -46,7 +52,7 @@ public class TestRevokeOnSchema private DistributedQueryRunner queryRunner; private QueryAssertions assertions; - @BeforeClass + @BeforeAll public void initClass() throws Exception { @@ -64,7 +70,7 @@ public void initClass() assertions = new QueryAssertions(queryRunner); } - @AfterClass(alwaysRun = true) + @AfterAll public void teardown() { assertions.close(); From cf70272a20666312f8ba59f4fbcd7821b677d6b2 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Wed, 15 Nov 2023 14:05:39 -0800 Subject: [PATCH 268/587] Remove unused method --- .../src/test/java/io/trino/plugin/hive/TestHive.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHive.java b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHive.java index c4568db9be42..2ae7c145603d 100644 --- a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHive.java +++ b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHive.java @@ -48,14 +48,6 @@ public void initialize() setup(HostAndPort.fromString(metastore), database); } - @Test - public void forceTestNgToRespectSingleThreaded() - { - // TODO: Remove after updating TestNG to 7.4.0+ (https://github.com/trinodb/trino/issues/8571) - // TestNG doesn't enforce @Test(singleThreaded = true) when tests are defined in base class. According to - // https://github.com/cbeust/testng/issues/2361#issuecomment-688393166 a workaround it to add a dummy test to the leaf test class. - } - @Test @Override public void testHideDeltaLakeTables() From 8025f2e9f12226b146a66e6ec3c21a12a6c2d254 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Wed, 15 Nov 2023 14:05:57 -0800 Subject: [PATCH 269/587] Migrate tests to JUnit --- client/trino-cli/pom.xml | 18 --- .../io/trino/hdfs/rubix/TestRubixCaching.java | 27 ++-- lib/trino-matching/pom.xml | 22 --- lib/trino-memory-context/pom.xml | 28 ---- .../memory/context/TestMemoryContexts.java | 80 +++++----- lib/trino-plugin-toolkit/pom.xml | 19 --- lib/trino-record-decoder/pom.xml | 18 --- plugin/trino-accumulo/pom.xml | 18 --- plugin/trino-base-jdbc/pom.xml | 18 --- plugin/trino-bigquery/pom.xml | 17 --- plugin/trino-cassandra/pom.xml | 17 --- plugin/trino-clickhouse/pom.xml | 22 --- .../TestClickHouseConnectorTest.java | 34 ++--- plugin/trino-delta-lake/pom.xml | 32 ---- .../BaseDeltaLakeConnectorSmokeTest.java | 23 ++- ...seDeltaLakeRegisterTableProcedureTest.java | 3 +- ...redMetastoreWithTableRedirectionsTest.java | 9 +- .../BaseDeltaLakeTableWithCustomLocation.java | 29 ++-- .../deltalake/DeltaLakeQueryRunner.java | 2 +- .../plugin/deltalake/TestDeltaLakeBasic.java | 3 +- .../deltalake/TestDeltaLakeConnectorTest.java | 25 ++-- .../TestDeltaLakeCreateTableStatistics.java | 125 ++++++++-------- .../TestDeltaLakeDynamicFiltering.java | 7 +- .../deltalake/TestDeltaLakePageSink.java | 21 ++- .../TestDeltaLakeParquetSchemas.java | 6 +- .../deltalake/TestDeltaLakeSplitManager.java | 8 +- .../deltalake/TestDeltaLakeTableName.java | 35 ++--- .../plugin/deltalake/TestDeltaLakeWriter.java | 50 +++---- .../deltalake/TestPredicatePushdown.java | 57 +++---- .../deltalake/TestReadJsonTransactionLog.java | 78 +++++----- .../plugin/deltalake/TestSplitPruning.java | 3 +- .../deltalake/TestTransactionLogAccess.java | 133 ++++++++--------- .../expression/TestSparkExpressionParser.java | 8 +- .../expression/TestSparkExpressions.java | 4 +- ...keConcurrentModificationGlueMetastore.java | 4 +- ...aLakeFileBasedTableStatisticsProvider.java | 139 +++++++++--------- .../TestDeltaLakeParquetStatisticsUtils.java | 58 ++------ .../TestDeltaLakeSchemaSupport.java | 35 ++--- .../transactionlog/TestProtocolEntry.java | 38 ++--- .../transactionlog/TestTableSnapshot.java | 3 +- .../TestTransactionLogParser.java | 8 +- .../checkpoint/TestCheckpointBuilder.java | 4 +- .../checkpoint/TestCheckpointWriter.java | 25 ++-- .../checkpoint/TestTransactionLogTail.java | 9 +- .../TestDeltaLakeFileStatistics.java | 73 +++------ plugin/trino-druid/pom.xml | 28 ---- .../plugin/druid/TestDruidConnectorTest.java | 5 +- plugin/trino-elasticsearch/pom.xml | 26 ---- .../BaseElasticsearchConnectorTest.java | 29 ++-- .../TestElasticsearchBackpressure.java | 4 - .../TestElasticsearchMetadata.java | 28 ++-- .../TestElasticsearchQueryBuilder.java | 4 +- .../client/TestExtractAddress.java | 20 +-- plugin/trino-hive-hadoop2/pom.xml | 22 --- plugin/trino-hudi/pom.xml | 23 --- .../plugin/hudi/TestHudiPartitionManager.java | 4 +- .../hudi/TestHudiSessionProperties.java | 9 +- .../trino/plugin/hudi/TestHudiSmokeTest.java | 6 +- plugin/trino-kudu/pom.xml | 19 --- plugin/trino-memory/pom.xml | 22 --- plugin/trino-password-authenticators/pom.xml | 22 --- plugin/trino-pinot/pom.xml | 17 --- plugin/trino-prometheus/pom.xml | 22 --- plugin/trino-raptor-legacy/pom.xml | 28 +--- .../backup/AbstractTestBackupStore.java | 2 +- .../legacy/backup/TestFileBackupStore.java | 13 +- .../legacy/backup/TestHttpBackupStore.java | 15 +- .../security/TestRaptorReadOnlySecurity.java | 2 +- plugin/trino-redis/pom.xml | 23 --- .../redis/TestMinimalFunctionality.java | 3 +- .../trino/plugin/redis/TestRedisPlugin.java | 4 +- plugin/trino-redshift/pom.xml | 28 ---- .../TestRedshiftTableStatisticsReader.java | 6 +- plugin/trino-resource-group-managers/pom.xml | 22 --- .../trino-session-property-managers/pom.xml | 28 ---- .../AbstractTestSessionPropertyManager.java | 2 +- ...stSessionPropertyManagerInTransaction.java | 2 +- .../db/TestDbSessionPropertyManager.java | 75 +++++----- .../TestDbSessionPropertyManagerConfig.java | 2 +- ...stDbSessionPropertyManagerIntegration.java | 35 +++-- .../file/TestFileSessionPropertyManager.java | 6 +- plugin/trino-thrift-api/pom.xml | 18 --- plugin/trino-thrift-testing-server/pom.xml | 28 ---- .../thrift/server/TestListBasedRecordSet.java | 34 ++--- plugin/trino-thrift/pom.xml | 19 --- 85 files changed, 681 insertions(+), 1419 deletions(-) diff --git a/client/trino-cli/pom.xml b/client/trino-cli/pom.xml index 992bd6d8b3a4..7d9505ff0ed2 100644 --- a/client/trino-cli/pom.xml +++ b/client/trino-cli/pom.xml @@ -157,24 +157,6 @@ - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - org.apache.maven.plugins maven-shade-plugin diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/rubix/TestRubixCaching.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/rubix/TestRubixCaching.java index 870acf1adf5c..8acfa153871c 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/rubix/TestRubixCaching.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/rubix/TestRubixCaching.java @@ -49,11 +49,13 @@ import org.apache.hadoop.fs.FilterFileSystem; import org.apache.hadoop.fs.Path; import org.gaul.modernizer_maven_annotations.SuppressModernizer; -import org.testng.annotations.AfterClass; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import javax.management.MBeanServer; import javax.management.ObjectName; @@ -91,9 +93,12 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; -@Test(singleThreaded = true) +@TestInstance(PER_CLASS) +@Execution(SAME_THREAD) public class TestRubixCaching { private static final DataSize SMALL_FILE_SIZE = DataSize.of(1, MEGABYTE); @@ -109,7 +114,7 @@ public class TestRubixCaching private FileSystem nonCachingFileSystem; private FileSystem cachingFileSystem; - @BeforeClass + @BeforeAll public void setup() throws IOException { @@ -121,8 +126,8 @@ public void setup() nonCachingFileSystem = getNonCachingFileSystem(); } - @AfterMethod(alwaysRun = true) - @BeforeMethod + @AfterEach + @BeforeEach public void deinitializeRubix() { // revert static rubix initialization done by other tests @@ -223,7 +228,7 @@ private FileSystem getCachingFileSystem(HdfsContext context, Path path) return environment.getFileSystem(context, path); } - @AfterClass(alwaysRun = true) + @AfterAll public void tearDown() throws IOException { @@ -232,7 +237,7 @@ public void tearDown() mBeanServer = null; } - @AfterMethod(alwaysRun = true) + @AfterEach public void closeRubix() throws IOException { diff --git a/lib/trino-matching/pom.xml b/lib/trino-matching/pom.xml index b72955e2a4db..1eb145ae2dce 100644 --- a/lib/trino-matching/pom.xml +++ b/lib/trino-matching/pom.xml @@ -46,26 +46,4 @@ test - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - - diff --git a/lib/trino-memory-context/pom.xml b/lib/trino-memory-context/pom.xml index 96dd54a2f224..699c27c1dbfe 100644 --- a/lib/trino-memory-context/pom.xml +++ b/lib/trino-memory-context/pom.xml @@ -63,33 +63,5 @@ junit-jupiter-engine test - - - org.testng - testng - test - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - - diff --git a/lib/trino-memory-context/src/test/java/io/trino/memory/context/TestMemoryContexts.java b/lib/trino-memory-context/src/test/java/io/trino/memory/context/TestMemoryContexts.java index df080eecbe5e..97ae0f0af20f 100644 --- a/lib/trino-memory-context/src/test/java/io/trino/memory/context/TestMemoryContexts.java +++ b/lib/trino-memory-context/src/test/java/io/trino/memory/context/TestMemoryContexts.java @@ -22,11 +22,8 @@ import static io.airlift.units.DataSize.Unit.MEGABYTE; import static io.trino.memory.context.AggregatedMemoryContext.newRootAggregatedMemoryContext; import static io.trino.memory.context.AggregatedMemoryContext.newSimpleAggregatedMemoryContext; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotEquals; -import static org.testng.Assert.assertTrue; public class TestMemoryContexts { @@ -41,14 +38,14 @@ public void testLocalMemoryContextClose() LocalMemoryContext localContext = aggregateContext.newLocalMemoryContext("test"); localContext.setBytes(100); - assertEquals(localContext.getBytes(), 100); - assertEquals(aggregateContext.getBytes(), 100); - assertEquals(reservationHandler.getReservation(), 100); + assertThat(localContext.getBytes()).isEqualTo(100); + assertThat(aggregateContext.getBytes()).isEqualTo(100); + assertThat(reservationHandler.getReservation()).isEqualTo(100); localContext.close(); - assertEquals(localContext.getBytes(), 0); - assertEquals(aggregateContext.getBytes(), 0); - assertEquals(reservationHandler.getReservation(), 0); + assertThat(localContext.getBytes()).isEqualTo(0); + assertThat(aggregateContext.getBytes()).isEqualTo(0); + assertThat(reservationHandler.getReservation()).isEqualTo(0); } @Test @@ -58,22 +55,22 @@ public void testMemoryContexts() AggregatedMemoryContext aggregateContext = newRootAggregatedMemoryContext(reservationHandler, GUARANTEED_MEMORY); LocalMemoryContext localContext = aggregateContext.newLocalMemoryContext("test"); - assertEquals(localContext.setBytes(10), NOT_BLOCKED); - assertEquals(localContext.getBytes(), 10); - assertEquals(aggregateContext.getBytes(), 10); - assertEquals(reservationHandler.getReservation(), aggregateContext.getBytes()); + assertThat(localContext.setBytes(10)).isEqualTo(NOT_BLOCKED); + assertThat(localContext.getBytes()).isEqualTo(10); + assertThat(aggregateContext.getBytes()).isEqualTo(10); + assertThat(reservationHandler.getReservation()).isEqualTo(aggregateContext.getBytes()); LocalMemoryContext secondLocalContext = aggregateContext.newLocalMemoryContext("test"); - assertEquals(secondLocalContext.setBytes(20), NOT_BLOCKED); - assertEquals(secondLocalContext.getBytes(), 20); - assertEquals(aggregateContext.getBytes(), 30); - assertEquals(localContext.getBytes(), 10); - assertEquals(reservationHandler.getReservation(), aggregateContext.getBytes()); + assertThat(secondLocalContext.setBytes(20)).isEqualTo(NOT_BLOCKED); + assertThat(secondLocalContext.getBytes()).isEqualTo(20); + assertThat(aggregateContext.getBytes()).isEqualTo(30); + assertThat(localContext.getBytes()).isEqualTo(10); + assertThat(reservationHandler.getReservation()).isEqualTo(aggregateContext.getBytes()); aggregateContext.close(); - assertEquals(aggregateContext.getBytes(), 0); - assertEquals(reservationHandler.getReservation(), 0); + assertThat(aggregateContext.getBytes()).isEqualTo(0); + assertThat(reservationHandler.getReservation()).isEqualTo(0); } @Test @@ -85,19 +82,19 @@ public void testTryReserve() AggregatedMemoryContext aggregateContext2 = parentContext.newAggregatedMemoryContext(); LocalMemoryContext childContext1 = aggregateContext1.newLocalMemoryContext("test"); - assertTrue(childContext1.trySetBytes(500)); - assertTrue(childContext1.trySetBytes(1_000)); - assertFalse(childContext1.trySetBytes(1_001)); - assertEquals(reservationHandler.getReservation(), parentContext.getBytes()); + assertThat(childContext1.trySetBytes(500)).isTrue(); + assertThat(childContext1.trySetBytes(1_000)).isTrue(); + assertThat(childContext1.trySetBytes(1_001)).isFalse(); + assertThat(reservationHandler.getReservation()).isEqualTo(parentContext.getBytes()); aggregateContext1.close(); aggregateContext2.close(); parentContext.close(); - assertEquals(aggregateContext1.getBytes(), 0); - assertEquals(aggregateContext2.getBytes(), 0); - assertEquals(parentContext.getBytes(), 0); - assertEquals(reservationHandler.getReservation(), 0); + assertThat(aggregateContext1.getBytes()).isEqualTo(0); + assertThat(aggregateContext2.getBytes()).isEqualTo(0); + assertThat(parentContext.getBytes()).isEqualTo(0); + assertThat(reservationHandler.getReservation()).isEqualTo(0); } @Test @@ -110,13 +107,13 @@ public void testHierarchicalMemoryContexts() LocalMemoryContext childContext1 = aggregateContext1.newLocalMemoryContext("test"); LocalMemoryContext childContext2 = aggregateContext2.newLocalMemoryContext("test"); - assertEquals(childContext1.setBytes(1), NOT_BLOCKED); - assertEquals(childContext2.setBytes(1), NOT_BLOCKED); + assertThat(childContext1.setBytes(1)).isEqualTo(NOT_BLOCKED); + assertThat(childContext2.setBytes(1)).isEqualTo(NOT_BLOCKED); - assertEquals(aggregateContext1.getBytes(), 1); - assertEquals(aggregateContext2.getBytes(), 1); - assertEquals(parentContext.getBytes(), aggregateContext1.getBytes() + aggregateContext2.getBytes()); - assertEquals(reservationHandler.getReservation(), parentContext.getBytes()); + assertThat(aggregateContext1.getBytes()).isEqualTo(1); + assertThat(aggregateContext2.getBytes()).isEqualTo(1); + assertThat(parentContext.getBytes()).isEqualTo(aggregateContext1.getBytes() + aggregateContext2.getBytes()); + assertThat(reservationHandler.getReservation()).isEqualTo(parentContext.getBytes()); } @Test @@ -129,22 +126,23 @@ public void testGuaranteedMemoryDoesntBlock() // exhaust the max memory available reservationHandler.exhaustMemory(); - assertEquals(reservationHandler.getReservation(), maxMemory); + assertThat(reservationHandler.getReservation()).isEqualTo(maxMemory); // even if the pool is exhausted we never block queries using a trivial amount of memory - assertEquals(childContext.setBytes(1_000), NOT_BLOCKED); - assertNotEquals(childContext.setBytes(GUARANTEED_MEMORY + 1), NOT_BLOCKED); + assertThat(childContext.setBytes(1_000)).isEqualTo(NOT_BLOCKED); + assertThat(childContext.setBytes(GUARANTEED_MEMORY + 1)) + .isNotEqualTo(NOT_BLOCKED); // at this point the memory contexts have reserved GUARANTEED_MEMORY + 1 bytes childContext.close(); parentContext.close(); - assertEquals(childContext.getBytes(), 0); - assertEquals(parentContext.getBytes(), 0); + assertThat(childContext.getBytes()).isEqualTo(0); + assertThat(parentContext.getBytes()).isEqualTo(0); // since we have exhausted the memory above after the memory contexts are closed // the pool must still be exhausted - assertEquals(reservationHandler.getReservation(), maxMemory); + assertThat(reservationHandler.getReservation()).isEqualTo(maxMemory); } @Test diff --git a/lib/trino-plugin-toolkit/pom.xml b/lib/trino-plugin-toolkit/pom.xml index 67ab71419396..cbdc83dd8351 100644 --- a/lib/trino-plugin-toolkit/pom.xml +++ b/lib/trino-plugin-toolkit/pom.xml @@ -213,24 +213,5 @@ - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - diff --git a/lib/trino-record-decoder/pom.xml b/lib/trino-record-decoder/pom.xml index 73e0fd62a3f8..96d06eb4caa6 100644 --- a/lib/trino-record-decoder/pom.xml +++ b/lib/trino-record-decoder/pom.xml @@ -195,24 +195,6 @@ - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - diff --git a/plugin/trino-accumulo/pom.xml b/plugin/trino-accumulo/pom.xml index 1d96733b1626..8d4b56b7952c 100644 --- a/plugin/trino-accumulo/pom.xml +++ b/plugin/trino-accumulo/pom.xml @@ -324,24 +324,6 @@ - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - org.apache.maven.plugins maven-dependency-plugin diff --git a/plugin/trino-base-jdbc/pom.xml b/plugin/trino-base-jdbc/pom.xml index 724676bc88c9..6210dac719e1 100644 --- a/plugin/trino-base-jdbc/pom.xml +++ b/plugin/trino-base-jdbc/pom.xml @@ -285,24 +285,6 @@ - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - org.antlr antlr4-maven-plugin diff --git a/plugin/trino-bigquery/pom.xml b/plugin/trino-bigquery/pom.xml index a8f6083349a8..c0495fdf8d51 100644 --- a/plugin/trino-bigquery/pom.xml +++ b/plugin/trino-bigquery/pom.xml @@ -489,23 +489,6 @@ - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - org.basepom.maven duplicate-finder-maven-plugin diff --git a/plugin/trino-cassandra/pom.xml b/plugin/trino-cassandra/pom.xml index 22c7d426e5f3..e125d36ecf2b 100644 --- a/plugin/trino-cassandra/pom.xml +++ b/plugin/trino-cassandra/pom.xml @@ -255,23 +255,6 @@ - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - org.basepom.maven duplicate-finder-maven-plugin diff --git a/plugin/trino-clickhouse/pom.xml b/plugin/trino-clickhouse/pom.xml index 562f2bfb0cdd..d2b332a954fc 100644 --- a/plugin/trino-clickhouse/pom.xml +++ b/plugin/trino-clickhouse/pom.xml @@ -206,26 +206,4 @@ test - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - - diff --git a/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/TestClickHouseConnectorTest.java b/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/TestClickHouseConnectorTest.java index 3a9f9b6ac94a..56f3ddde2ecf 100644 --- a/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/TestClickHouseConnectorTest.java +++ b/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/TestClickHouseConnectorTest.java @@ -50,10 +50,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assumptions.abort; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; public class TestClickHouseConnectorTest extends BaseJdbcConnectorTest @@ -135,7 +131,7 @@ public void testAddColumnWithCommentSpecialCharacter(String comment) // Override because default storage engine doesn't support renaming columns try (TestTable table = new TestTable(getQueryRunner()::execute, "test_add_column_", "(a_varchar varchar NOT NULL) WITH (engine = 'mergetree', order_by = ARRAY['a_varchar'])")) { assertUpdate("ALTER TABLE " + table.getName() + " ADD COLUMN b_varchar varchar COMMENT " + varcharLiteral(comment)); - assertEquals(getColumnComment(table.getName(), "b_varchar"), comment); + assertThat(getColumnComment(table.getName(), "b_varchar")).isEqualTo(comment); } } @@ -195,10 +191,10 @@ public void testDropColumn() assertUpdate("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); assertUpdate("ALTER TABLE IF EXISTS " + tableName + " DROP COLUMN notExistColumn"); assertUpdate("ALTER TABLE IF EXISTS " + tableName + " DROP COLUMN IF EXISTS notExistColumn"); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); } @Override @@ -221,7 +217,7 @@ public void testAddNotNullColumnToEmptyTable() String tableName = table.getName(); assertUpdate("ALTER TABLE " + tableName + " ADD COLUMN b_varchar varchar NOT NULL"); - assertFalse(columnIsNullable(tableName, "b_varchar")); + assertThat(columnIsNullable(tableName, "b_varchar")).isFalse(); assertUpdate("INSERT INTO " + tableName + " VALUES ('a', 'b')", 1); assertThat(query("TABLE " + tableName)) .skippingTypesCheck() @@ -237,13 +233,13 @@ public void testAddNotNullColumn() String tableName = table.getName(); assertUpdate("ALTER TABLE " + tableName + " ADD COLUMN b_varchar varchar NOT NULL"); - assertFalse(columnIsNullable(tableName, "b_varchar")); + assertThat(columnIsNullable(tableName, "b_varchar")).isFalse(); assertUpdate("INSERT INTO " + tableName + " VALUES ('a', 'b')", 1); // ClickHouse set an empty character as the default value assertUpdate("ALTER TABLE " + tableName + " ADD COLUMN c_varchar varchar NOT NULL"); - assertFalse(columnIsNullable(tableName, "c_varchar")); + assertThat(columnIsNullable(tableName, "c_varchar")).isFalse(); assertQuery("SELECT c_varchar FROM " + tableName, "VALUES ''"); } } @@ -260,7 +256,7 @@ public void testAddColumnWithComment() assertThat(getColumnComment(tableName, "b_varchar")).isEqualTo("test new column comment"); assertUpdate("ALTER TABLE " + tableName + " ADD COLUMN empty_comment varchar COMMENT ''"); - assertNull(getColumnComment(tableName, "empty_comment")); + assertThat(getColumnComment(tableName, "empty_comment")).isNull(); } } @@ -349,10 +345,10 @@ public void testDifferentEngine() String tableName = "test_add_column_" + randomNameSuffix(); // MergeTree assertUpdate("CREATE TABLE " + tableName + " (id int NOT NULL, x VARCHAR) WITH (engine = 'MergeTree', order_by = ARRAY['id'])"); - assertTrue(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isTrue(); assertUpdate("DROP TABLE " + tableName); assertUpdate("CREATE TABLE " + tableName + " (id int NOT NULL, x VARCHAR) WITH (engine = 'mergetree', order_by = ARRAY['id'])"); - assertTrue(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isTrue(); assertUpdate("DROP TABLE " + tableName); // MergeTree without order by assertQueryFails("CREATE TABLE " + tableName + " (id int NOT NULL, x VARCHAR) WITH (engine = 'MergeTree')", "The property of order_by is required for table engine MergeTree\\(\\)"); @@ -360,7 +356,7 @@ public void testDifferentEngine() // MergeTree with optional assertUpdate("CREATE TABLE " + tableName + " (id int NOT NULL, x VARCHAR, logdate DATE NOT NULL) WITH " + "(engine = 'MergeTree', order_by = ARRAY['id'], partition_by = ARRAY['logdate'])"); - assertTrue(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isTrue(); assertUpdate("DROP TABLE " + tableName); //Log families @@ -382,7 +378,7 @@ public void testTableProperty() String tableName = "test_add_column_" + randomNameSuffix(); // no table property, it should create a table with default Log engine table assertUpdate("CREATE TABLE " + tableName + " (id int NOT NULL, x VARCHAR)"); - assertTrue(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isTrue(); assertUpdate("DROP TABLE " + tableName); // one required property @@ -713,7 +709,7 @@ public void testCreateTableWithLongTableName() String validTableName = baseTableName + "z".repeat(maxTableNameLength().orElseThrow() - baseTableName.length()); assertUpdate("CREATE TABLE " + validTableName + " (a bigint)"); - assertTrue(getQueryRunner().tableExists(getSession(), validTableName)); + assertThat(getQueryRunner().tableExists(getSession(), validTableName)).isTrue(); assertThatThrownBy(() -> assertUpdate("DROP TABLE " + validTableName)) .hasMessageMatching("(?s).*(Bad path syntax|File name too long).*"); @@ -721,7 +717,7 @@ public void testCreateTableWithLongTableName() assertThatThrownBy(() -> query("CREATE TABLE " + invalidTableName + " (a bigint)")) .hasMessageMatching("(?s).*(Cannot open file|File name too long).*"); // ClickHouse lefts a table even if the above statement failed - assertTrue(getQueryRunner().tableExists(getSession(), validTableName)); + assertThat(getQueryRunner().tableExists(getSession(), validTableName)).isTrue(); } @Test @@ -775,7 +771,7 @@ public void testRenameTableToLongTableName() String validTargetTableName = baseTableName + "z".repeat(255 - ".sql".length() - baseTableName.length()); assertUpdate("ALTER TABLE " + sourceTableName + " RENAME TO " + validTargetTableName); - assertTrue(getQueryRunner().tableExists(getSession(), validTargetTableName)); + assertThat(getQueryRunner().tableExists(getSession(), validTargetTableName)).isTrue(); assertQuery("SELECT x FROM " + validTargetTableName, "VALUES 123"); assertThatThrownBy(() -> assertUpdate("DROP TABLE " + validTargetTableName)) .hasMessageMatching("(?s).*(Bad path syntax|File name too long).*"); @@ -784,7 +780,7 @@ public void testRenameTableToLongTableName() String invalidTargetTableName = validTargetTableName + "z"; assertThatThrownBy(() -> assertUpdate("ALTER TABLE " + sourceTableName + " RENAME TO " + invalidTargetTableName)) .hasMessageMatching("(?s).*(Cannot rename|File name too long).*"); - assertFalse(getQueryRunner().tableExists(getSession(), invalidTargetTableName)); + assertThat(getQueryRunner().tableExists(getSession(), invalidTargetTableName)).isFalse(); } @Test diff --git a/plugin/trino-delta-lake/pom.xml b/plugin/trino-delta-lake/pom.xml index 932f6a96b39e..3e6d398acbdb 100644 --- a/plugin/trino-delta-lake/pom.xml +++ b/plugin/trino-delta-lake/pom.xml @@ -15,15 +15,6 @@ ${project.parent.basedir} - - - instances @@ -449,33 +440,10 @@ testcontainers test - - - org.testng - testng - test - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - org.apache.maven.plugins maven-dependency-plugin diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeConnectorSmokeTest.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeConnectorSmokeTest.java index ff7601c9b5a5..07a0eb065be3 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeConnectorSmokeTest.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeConnectorSmokeTest.java @@ -43,7 +43,6 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; -import org.testng.SkipException; import java.util.List; import java.util.Map; @@ -84,10 +83,8 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.abort; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) public abstract class BaseDeltaLakeConnectorSmokeTest @@ -457,9 +454,9 @@ public void testInputDataSize() getLocationForTable(bucketName, "foo"))); MaterializedResultWithQueryId deltaResult = queryRunner.executeWithQueryId(broadcastJoinDistribution(true), "SELECT * FROM foo"); - assertEquals(deltaResult.getResult().getRowCount(), 2); + assertThat(deltaResult.getResult().getRowCount()).isEqualTo(2); MaterializedResultWithQueryId hiveResult = queryRunner.executeWithQueryId(broadcastJoinDistribution(true), format("SELECT * FROM %s.%s.%s", "hive", SCHEMA, hiveTableName)); - assertEquals(hiveResult.getResult().getRowCount(), 2); + assertThat(hiveResult.getResult().getRowCount()).isEqualTo(2); QueryManager queryManager = queryRunner.getCoordinator().getQueryManager(); assertThat(queryManager.getFullQueryInfo(deltaResult.getQueryId()).getQueryStats().getProcessedInputDataSize()).as("delta processed input data size") @@ -485,7 +482,7 @@ public void testHiveViewsCannotBeAccessed() String viewName = "dummy_view"; hiveHadoop.runOnHive("CREATE DATABASE " + schemaName); hiveHadoop.runOnHive(format("CREATE VIEW %s.%s AS SELECT * FROM %s.customer", schemaName, viewName, SCHEMA)); - assertEquals(computeScalar(format("SHOW TABLES FROM %s LIKE '%s'", schemaName, viewName)), viewName); + assertThat(computeScalar(format("SHOW TABLES FROM %s LIKE '%s'", schemaName, viewName))).isEqualTo(viewName); assertThatThrownBy(() -> computeActual("DESCRIBE " + schemaName + "." + viewName)).hasMessageContaining(format("%s.%s is not a Delta Lake table", schemaName, viewName)); hiveHadoop.runOnHive("DROP DATABASE " + schemaName + " CASCADE"); } @@ -497,7 +494,7 @@ public void testNonDeltaTablesCannotBeAccessed() String tableName = "hive_table"; hiveHadoop.runOnHive("CREATE DATABASE " + schemaName); hiveHadoop.runOnHive(format("CREATE TABLE %s.%s (id BIGINT)", schemaName, tableName)); - assertEquals(computeScalar(format("SHOW TABLES FROM %s LIKE '%s'", schemaName, tableName)), tableName); + assertThat(computeScalar(format("SHOW TABLES FROM %s LIKE '%s'", schemaName, tableName))).isEqualTo(tableName); assertThatThrownBy(() -> computeActual("DESCRIBE " + schemaName + "." + tableName)).hasMessageContaining(tableName + " is not a Delta Lake table"); hiveHadoop.runOnHive("DROP DATABASE " + schemaName + " CASCADE"); } @@ -521,9 +518,9 @@ public void testDropOssDataLakeTable() private void testDropTable(String tableName, String resourcePath) { registerTableFromResources(tableName, resourcePath, getQueryRunner()); - assertTrue(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isTrue(); assertUpdate("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); assertThat(getTableFiles(tableName)).hasSizeGreaterThan(1); // the data should not be deleted } @@ -856,7 +853,7 @@ public void testExternalTableFilesRetainedOnDrop() "SELECT count(*) FROM nation"); int fileCount = getTableFiles(tableName).size(); assertUpdate(format("DROP TABLE %s.%s", schemaName, tableName)); - assertEquals(getTableFiles(tableName).size(), fileCount); + assertThat(getTableFiles(tableName).size()).isEqualTo(fileCount); } @Test @@ -1919,7 +1916,7 @@ private void invalidateMetadataCache(String tableName) private void testCountQuery(@Language("SQL") String sql, long expectedRowCount, long expectedSplitCount) { MaterializedResultWithQueryId result = getDistributedQueryRunner().executeWithQueryId(getSession(), sql); - assertEquals(result.getResult().getOnlyColumnAsSet(), ImmutableSet.of(expectedRowCount)); + assertThat(result.getResult().getOnlyColumnAsSet()).isEqualTo(ImmutableSet.of(expectedRowCount)); verifySplitCount(result.getQueryId(), expectedSplitCount); } @@ -1945,7 +1942,7 @@ private OperatorStats getOperatorStats(QueryId queryId) public void testDelete() { if (!hasBehavior(SUPPORTS_DELETE)) { - throw new SkipException("testDelete requires DELETE support"); + abort("testDelete requires DELETE support"); } String tableName = "test_delete_" + randomNameSuffix(); diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeRegisterTableProcedureTest.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeRegisterTableProcedureTest.java index 94cbf68ea9cd..8de5cbe0da43 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeRegisterTableProcedureTest.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeRegisterTableProcedureTest.java @@ -46,7 +46,6 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertFalse; @TestInstance(PER_CLASS) public abstract class BaseDeltaLakeRegisterTableProcedureTest @@ -110,7 +109,7 @@ public void testRegisterTable() // Drop table from metastore and use the table content to register a table metastore.dropTable(SCHEMA, tableName, false); // Verify that dropTableFromMetastore actually works - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); assertQuerySucceeds(format("CALL system.register_table('%s', '%s', '%s')", SCHEMA, tableName, tableLocation)); String showCreateTableNew = (String) computeScalar("SHOW CREATE TABLE " + tableName); diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeSharedMetastoreWithTableRedirectionsTest.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeSharedMetastoreWithTableRedirectionsTest.java index 69545aa2a536..1bc9f0cddcc5 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeSharedMetastoreWithTableRedirectionsTest.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeSharedMetastoreWithTableRedirectionsTest.java @@ -20,7 +20,6 @@ import static io.trino.testing.TestingNames.randomNameSuffix; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) public abstract class BaseDeltaLakeSharedMetastoreWithTableRedirectionsTest @@ -85,13 +84,9 @@ public void testShowSchemas() .containsAll("VALUES '" + schema + "'"); String showCreateHiveWithRedirectionsSchema = (String) computeActual("SHOW CREATE SCHEMA hive_with_redirections." + schema).getOnlyValue(); - assertEquals( - showCreateHiveWithRedirectionsSchema, - getExpectedHiveCreateSchema("hive_with_redirections")); + assertThat(showCreateHiveWithRedirectionsSchema).isEqualTo(getExpectedHiveCreateSchema("hive_with_redirections")); String showCreateDeltaLakeWithRedirectionsSchema = (String) computeActual("SHOW CREATE SCHEMA delta_with_redirections." + schema).getOnlyValue(); - assertEquals( - showCreateDeltaLakeWithRedirectionsSchema, - getExpectedDeltaLakeCreateSchema("delta_with_redirections")); + assertThat(showCreateDeltaLakeWithRedirectionsSchema).isEqualTo(getExpectedDeltaLakeCreateSchema("delta_with_redirections")); } @Test diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeTableWithCustomLocation.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeTableWithCustomLocation.java index 025decd4a7bd..8c5f0130ad56 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeTableWithCustomLocation.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeTableWithCustomLocation.java @@ -31,9 +31,6 @@ import static io.trino.testing.TestingNames.randomNameSuffix; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public abstract class BaseDeltaLakeTableWithCustomLocation extends AbstractTestQueryFramework @@ -49,7 +46,9 @@ public void testTableHasUuidSuffixInLocation() String tableName = "table_with_uuid" + randomNameSuffix(); assertQuerySucceeds(format("CREATE TABLE %s AS SELECT 1 as val", tableName)); Optional
table = metastore.getTable(SCHEMA, tableName); - assertTrue(table.isPresent(), "Table should exists"); + assertThat(table.isPresent()) + .describedAs("Table should exists") + .isTrue(); String location = table.get().getStorage().getLocation(); assertThat(location).matches(format(".*%s-[0-9a-f]{32}", tableName)); } @@ -65,14 +64,24 @@ public void testCreateAndDrop() Location tableLocation = Location.of(table.getStorage().getLocation()); TrinoFileSystem fileSystem = HDFS_FILE_SYSTEM_FACTORY.create(getSession().toConnectorSession()); - assertTrue(fileSystem.listFiles(tableLocation).hasNext(), "The directory corresponding to the table storage location should exist"); + assertThat(fileSystem.listFiles(tableLocation).hasNext()) + .describedAs("The directory corresponding to the table storage location should exist") + .isTrue(); List materializedRows = computeActual("SELECT \"$path\" FROM " + tableName).getMaterializedRows(); - assertEquals(materializedRows.size(), 1); + assertThat(materializedRows.size()).isEqualTo(1); Location filePath = Location.of((String) materializedRows.get(0).getField(0)); - assertTrue(fileSystem.listFiles(filePath).hasNext(), "The data file should exist"); + assertThat(fileSystem.listFiles(filePath).hasNext()) + .describedAs("The data file should exist") + .isTrue(); assertQuerySucceeds(format("DROP TABLE %s", tableName)); - assertFalse(metastore.getTable(SCHEMA, tableName).isPresent(), "Table should be dropped"); - assertFalse(fileSystem.listFiles(filePath).hasNext(), "The data file should have been removed"); - assertFalse(fileSystem.listFiles(tableLocation).hasNext(), "The directory corresponding to the dropped Delta Lake table should be removed"); + assertThat(metastore.getTable(SCHEMA, tableName).isPresent()) + .describedAs("Table should be dropped") + .isFalse(); + assertThat(fileSystem.listFiles(filePath).hasNext()) + .describedAs("The data file should have been removed") + .isFalse(); + assertThat(fileSystem.listFiles(tableLocation).hasNext()) + .describedAs("The directory corresponding to the dropped Delta Lake table should be removed") + .isFalse(); } } diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/DeltaLakeQueryRunner.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/DeltaLakeQueryRunner.java index bcc59f61ac1f..552b30658d62 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/DeltaLakeQueryRunner.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/DeltaLakeQueryRunner.java @@ -44,7 +44,7 @@ import static io.trino.testing.containers.Minio.MINIO_SECRET_KEY; import static java.lang.String.format; import static java.util.Objects.requireNonNull; -import static org.testng.util.Strings.isNullOrEmpty; +import static org.assertj.core.util.Strings.isNullOrEmpty; public final class DeltaLakeQueryRunner { diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeBasic.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeBasic.java index 6ce5295fd046..283721d845b4 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeBasic.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeBasic.java @@ -82,7 +82,6 @@ import static org.assertj.core.api.Assertions.entry; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertFalse; @TestInstance(PER_CLASS) @Execution(SAME_THREAD) @@ -980,7 +979,7 @@ private void testCorruptedTableLocation(String tableName, Path tableLocation, bo // DROP TABLE should succeed so that users can remove their corrupted table getQueryRunner().execute("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); if (isManaged) { assertThat(tableLocation.toFile()).doesNotExist().as("Table location should not exist"); } diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConnectorTest.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConnectorTest.java index c11a54b0fec6..5a18909846b4 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConnectorTest.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConnectorTest.java @@ -79,9 +79,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assumptions.abort; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestDeltaLakeConnectorTest extends BaseConnectorTest @@ -476,10 +473,10 @@ private void testDeltaRenameColumnWithComment(ColumnMappingMode mode) "column_mapping_mode = '" + mode + "')"); assertUpdate("ALTER TABLE " + tableName + " RENAME COLUMN col TO new_col"); - assertEquals(getColumnComment(tableName, "new_col"), "test column comment"); + assertThat(getColumnComment(tableName, "new_col")).isEqualTo("test column comment"); assertUpdate("ALTER TABLE " + tableName + " RENAME COLUMN part TO new_part"); - assertEquals(getColumnComment(tableName, "new_part"), "test partition comment"); + assertThat(getColumnComment(tableName, "new_part")).isEqualTo("test partition comment"); assertUpdate("DROP TABLE " + tableName); } @@ -538,12 +535,12 @@ private void testTimestampPredicatePushdown(String value) MaterializedResultWithQueryId queryResult = queryRunner.executeWithQueryId( getSession(), "SELECT * FROM " + tableName + " WHERE t < TIMESTAMP '" + value + "'"); - assertEquals(getQueryInfo(queryRunner, queryResult).getQueryStats().getProcessedInputDataSize().toBytes(), 0); + assertThat(getQueryInfo(queryRunner, queryResult).getQueryStats().getProcessedInputDataSize().toBytes()).isEqualTo(0); queryResult = queryRunner.executeWithQueryId( getSession(), "SELECT * FROM " + tableName + " WHERE t > TIMESTAMP '" + value + "'"); - assertEquals(getQueryInfo(queryRunner, queryResult).getQueryStats().getProcessedInputDataSize().toBytes(), 0); + assertThat(getQueryInfo(queryRunner, queryResult).getQueryStats().getProcessedInputDataSize().toBytes()).isEqualTo(0); assertQueryStats( getSession(), @@ -1110,12 +1107,12 @@ public void testUnsupportedCreateTableWithChangeDataFeed() assertQueryFails( "CREATE TABLE " + tableName + "(" + columnName + " int) WITH (change_data_feed_enabled = true)", "\\QUnable to use [%s] when change data feed is enabled\\E".formatted(columnName)); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); assertQueryFails( "CREATE TABLE " + tableName + " WITH (change_data_feed_enabled = true) AS SELECT 1 AS " + columnName, "\\QUnable to use [%s] when change data feed is enabled\\E".formatted(columnName)); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); } } @@ -1826,7 +1823,7 @@ public void testCreateTableUnsupportedColumnMappingMode() assertQueryFails("CREATE TABLE " + tableName + " WITH (column_mapping_mode = 'unknown') AS SELECT 1 a", ".* \\QInvalid value [unknown]. Valid values: [ID, NAME, NONE]"); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); } @Test @@ -2661,13 +2658,17 @@ private void testDeleteWithFilter(String createTableSql, String deleteFilter, bo plan -> { if (pushDownDelete) { boolean tableDelete = searchFrom(plan.getRoot()).where(node -> node instanceof TableDeleteNode).matches(); - assertTrue(tableDelete, "A TableDeleteNode should be present"); + assertThat(tableDelete) + .describedAs("A TableDeleteNode should be present") + .isTrue(); } else { TableFinishNode finishNode = searchFrom(plan.getRoot()) .where(TableFinishNode.class::isInstance) .findOnlyElement(); - assertTrue(finishNode.getTarget() instanceof TableWriterNode.MergeTarget, "Delete operation should be performed through MERGE mechanism"); + assertThat(finishNode.getTarget() instanceof TableWriterNode.MergeTarget) + .describedAs("Delete operation should be performed through MERGE mechanism") + .isTrue(); } }); assertQuery("SELECT customer, purchases, address FROM " + table, "VALUES ('Mary', 10, 'Adelphi'), ('Aaron', 3, 'Dallas')"); diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeCreateTableStatistics.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeCreateTableStatistics.java index d9d705864826..53f7d815609d 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeCreateTableStatistics.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeCreateTableStatistics.java @@ -58,7 +58,6 @@ import static java.lang.Double.POSITIVE_INFINITY; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; @Isolated public class TestDeltaLakeCreateTableStatistics @@ -101,10 +100,10 @@ public void testComplexDataTypes() DeltaLakeFileStatistics fileStatistics = entry.getStats().get(); DeltaLakeColumnHandle columnHandle = new DeltaLakeColumnHandle("d", createUnboundedVarcharType(), OptionalInt.empty(), "d", createUnboundedVarcharType(), REGULAR, Optional.empty()); - assertEquals(fileStatistics.getNumRecords(), Optional.of(2L)); - assertEquals(fileStatistics.getMinColumnValue(columnHandle), Optional.of(utf8Slice("foo"))); - assertEquals(fileStatistics.getMaxColumnValue(columnHandle), Optional.of(utf8Slice("moo"))); - assertEquals(fileStatistics.getNullCount("d"), Optional.of(0L)); + assertThat(fileStatistics.getNumRecords()).isEqualTo(Optional.of(2L)); + assertThat(fileStatistics.getMinColumnValue(columnHandle)).isEqualTo(Optional.of(utf8Slice("foo"))); + assertThat(fileStatistics.getMaxColumnValue(columnHandle)).isEqualTo(Optional.of(utf8Slice("moo"))); + assertThat(fileStatistics.getNullCount("d")).isEqualTo(Optional.of(0L)); for (String complexColumn : ImmutableList.of("a", "b", "c")) { columnHandle = new DeltaLakeColumnHandle(complexColumn, createUnboundedVarcharType(), OptionalInt.empty(), complexColumn, createUnboundedVarcharType(), REGULAR, Optional.empty()); @@ -128,10 +127,10 @@ public void testDoubleTypesNaN() assertThat(entry.getStats()).isPresent(); DeltaLakeFileStatistics fileStatistics = entry.getStats().get(); - assertEquals(fileStatistics.getNumRecords(), Optional.of(2L)); - assertEquals(fileStatistics.getMinColumnValue(columnHandle), Optional.empty()); - assertEquals(fileStatistics.getMaxColumnValue(columnHandle), Optional.empty()); - assertEquals(fileStatistics.getNullCount(columnName), Optional.empty()); + assertThat(fileStatistics.getNumRecords()).isEqualTo(Optional.of(2L)); + assertThat(fileStatistics.getMinColumnValue(columnHandle)).isEqualTo(Optional.empty()); + assertThat(fileStatistics.getMaxColumnValue(columnHandle)).isEqualTo(Optional.empty()); + assertThat(fileStatistics.getNullCount(columnName)).isEqualTo(Optional.empty()); } } } @@ -152,10 +151,10 @@ public void testDoubleTypesInf() assertThat(entry.getStats()).isPresent(); DeltaLakeFileStatistics fileStatistics = entry.getStats().get(); - assertEquals(fileStatistics.getNumRecords(), Optional.of(3L)); - assertEquals(fileStatistics.getMinColumnValue(columnHandle), Optional.of(NEGATIVE_INFINITY)); - assertEquals(fileStatistics.getMaxColumnValue(columnHandle), Optional.of(POSITIVE_INFINITY)); - assertEquals(fileStatistics.getNullCount(columnName), Optional.of(0L)); + assertThat(fileStatistics.getNumRecords()).isEqualTo(Optional.of(3L)); + assertThat(fileStatistics.getMinColumnValue(columnHandle)).isEqualTo(Optional.of(NEGATIVE_INFINITY)); + assertThat(fileStatistics.getMaxColumnValue(columnHandle)).isEqualTo(Optional.of(POSITIVE_INFINITY)); + assertThat(fileStatistics.getNullCount(columnName)).isEqualTo(Optional.of(0L)); } } } @@ -176,10 +175,10 @@ public void testDoubleTypesInfAndNaN() assertThat(entry.getStats()).isPresent(); DeltaLakeFileStatistics fileStatistics = entry.getStats().get(); - assertEquals(fileStatistics.getNumRecords(), Optional.of(4L)); - assertEquals(fileStatistics.getMinColumnValue(columnHandle), Optional.empty()); - assertEquals(fileStatistics.getMaxColumnValue(columnHandle), Optional.empty()); - assertEquals(fileStatistics.getNullCount(columnName), Optional.empty()); + assertThat(fileStatistics.getNumRecords()).isEqualTo(Optional.of(4L)); + assertThat(fileStatistics.getMinColumnValue(columnHandle)).isEqualTo(Optional.empty()); + assertThat(fileStatistics.getMaxColumnValue(columnHandle)).isEqualTo(Optional.empty()); + assertThat(fileStatistics.getNullCount(columnName)).isEqualTo(Optional.empty()); } } } @@ -200,10 +199,10 @@ public void testDoubleTypesNaNPositive() assertThat(entry.getStats()).isPresent(); DeltaLakeFileStatistics fileStatistics = entry.getStats().get(); - assertEquals(fileStatistics.getNumRecords(), Optional.of(4L)); - assertEquals(fileStatistics.getMinColumnValue(columnHandle), Optional.empty()); - assertEquals(fileStatistics.getMaxColumnValue(columnHandle), Optional.empty()); - assertEquals(fileStatistics.getNullCount(columnName), Optional.empty()); + assertThat(fileStatistics.getNumRecords()).isEqualTo(Optional.of(4L)); + assertThat(fileStatistics.getMinColumnValue(columnHandle)).isEqualTo(Optional.empty()); + assertThat(fileStatistics.getMaxColumnValue(columnHandle)).isEqualTo(Optional.empty()); + assertThat(fileStatistics.getNullCount(columnName)).isEqualTo(Optional.empty()); } } } @@ -224,10 +223,10 @@ public void testDoubleTypesNaNNegative() assertThat(entry.getStats()).isPresent(); DeltaLakeFileStatistics fileStatistics = entry.getStats().get(); - assertEquals(fileStatistics.getNumRecords(), Optional.of(4L)); - assertEquals(fileStatistics.getMinColumnValue(columnHandle), Optional.empty()); - assertEquals(fileStatistics.getMaxColumnValue(columnHandle), Optional.empty()); - assertEquals(fileStatistics.getNullCount(columnName), Optional.empty()); + assertThat(fileStatistics.getNumRecords()).isEqualTo(Optional.of(4L)); + assertThat(fileStatistics.getMinColumnValue(columnHandle)).isEqualTo(Optional.empty()); + assertThat(fileStatistics.getMaxColumnValue(columnHandle)).isEqualTo(Optional.empty()); + assertThat(fileStatistics.getNullCount(columnName)).isEqualTo(Optional.empty()); } } } @@ -271,7 +270,7 @@ private void testDecimal(int precision, int scale) assertThat(entry.getStats()).isPresent(); DeltaLakeFileStatistics fileStatistics = entry.getStats().get(); - assertEquals(fileStatistics.getNumRecords(), Optional.of(3L)); + assertThat(fileStatistics.getNumRecords()).isEqualTo(Optional.of(3L)); Optional expectedMin; Optional expectedMax; if (precision <= MAX_SHORT_PRECISION) { @@ -282,9 +281,9 @@ private void testDecimal(int precision, int scale) expectedMin = Optional.of(encodeScaledValue(new BigDecimal(negative), scale)); expectedMax = Optional.of(encodeScaledValue(new BigDecimal(high), scale)); } - assertEquals(fileStatistics.getMinColumnValue(columnHandle), expectedMin); - assertEquals(fileStatistics.getMaxColumnValue(columnHandle), expectedMax); - assertEquals(fileStatistics.getNullCount(columnName), Optional.of(0L)); + assertThat(fileStatistics.getMinColumnValue(columnHandle)).isEqualTo(expectedMin); + assertThat(fileStatistics.getMaxColumnValue(columnHandle)).isEqualTo(expectedMax); + assertThat(fileStatistics.getNullCount(columnName)).isEqualTo(Optional.of(0L)); } } @@ -300,10 +299,10 @@ public void testNullRecords() assertThat(entry.getStats()).isPresent(); DeltaLakeFileStatistics fileStatistics = entry.getStats().get(); - assertEquals(fileStatistics.getNumRecords(), Optional.of(4L)); - assertEquals(fileStatistics.getMinColumnValue(columnHandle), Optional.of(0.0)); - assertEquals(fileStatistics.getMaxColumnValue(columnHandle), Optional.of(1.0)); - assertEquals(fileStatistics.getNullCount(columnName), Optional.of(2L)); + assertThat(fileStatistics.getNumRecords()).isEqualTo(Optional.of(4L)); + assertThat(fileStatistics.getMinColumnValue(columnHandle)).isEqualTo(Optional.of(0.0)); + assertThat(fileStatistics.getMaxColumnValue(columnHandle)).isEqualTo(Optional.of(1.0)); + assertThat(fileStatistics.getNullCount(columnName)).isEqualTo(Optional.of(2L)); } } @@ -322,10 +321,10 @@ public void testOnlyNullRecords() assertThat(entry.getStats()).isPresent(); DeltaLakeFileStatistics fileStatistics = entry.getStats().get(); - assertEquals(fileStatistics.getNumRecords(), Optional.of(4L)); - assertEquals(fileStatistics.getMinColumnValue(columnHandle), Optional.empty()); - assertEquals(fileStatistics.getMaxColumnValue(columnHandle), Optional.empty()); - assertEquals(fileStatistics.getNullCount(columnName), Optional.of(4L)); + assertThat(fileStatistics.getNumRecords()).isEqualTo(Optional.of(4L)); + assertThat(fileStatistics.getMinColumnValue(columnHandle)).isEqualTo(Optional.empty()); + assertThat(fileStatistics.getMaxColumnValue(columnHandle)).isEqualTo(Optional.empty()); + assertThat(fileStatistics.getNullCount(columnName)).isEqualTo(Optional.of(4L)); } } @@ -344,10 +343,10 @@ public void testDateRecords() assertThat(entry.getStats()).isPresent(); DeltaLakeFileStatistics fileStatistics = entry.getStats().get(); - assertEquals(fileStatistics.getNumRecords(), Optional.of(4L)); - assertEquals(fileStatistics.getMinColumnValue(columnHandle), Optional.of(LocalDate.parse("2011-08-08").toEpochDay())); - assertEquals(fileStatistics.getMaxColumnValue(columnHandle), Optional.of(LocalDate.parse("2013-08-09").toEpochDay())); - assertEquals(fileStatistics.getNullCount(columnName), Optional.of(0L)); + assertThat(fileStatistics.getNumRecords()).isEqualTo(Optional.of(4L)); + assertThat(fileStatistics.getMinColumnValue(columnHandle)).isEqualTo(Optional.of(LocalDate.parse("2011-08-08").toEpochDay())); + assertThat(fileStatistics.getMaxColumnValue(columnHandle)).isEqualTo(Optional.of(LocalDate.parse("2013-08-09").toEpochDay())); + assertThat(fileStatistics.getNullCount(columnName)).isEqualTo(Optional.of(0L)); } } @@ -366,14 +365,10 @@ public void testTimestampMilliRecords() assertThat(entry.getStats()).isPresent(); DeltaLakeFileStatistics fileStatistics = entry.getStats().get(); - assertEquals(fileStatistics.getNumRecords(), Optional.of(3L)); - assertEquals( - fileStatistics.getMinColumnValue(columnHandle), - Optional.of(packDateTimeWithZone(ZonedDateTime.parse("2012-10-31T01:00:00.123Z").toInstant().toEpochMilli(), UTC_KEY))); - assertEquals( - fileStatistics.getMaxColumnValue(columnHandle), - Optional.of(packDateTimeWithZone(ZonedDateTime.parse("2012-10-31T08:00:00.123Z").toInstant().toEpochMilli(), UTC_KEY))); - assertEquals(fileStatistics.getNullCount(columnName), Optional.of(0L)); + assertThat(fileStatistics.getNumRecords()).isEqualTo(Optional.of(3L)); + assertThat(fileStatistics.getMinColumnValue(columnHandle)).isEqualTo(Optional.of(packDateTimeWithZone(ZonedDateTime.parse("2012-10-31T01:00:00.123Z").toInstant().toEpochMilli(), UTC_KEY))); + assertThat(fileStatistics.getMaxColumnValue(columnHandle)).isEqualTo(Optional.of(packDateTimeWithZone(ZonedDateTime.parse("2012-10-31T08:00:00.123Z").toInstant().toEpochMilli(), UTC_KEY))); + assertThat(fileStatistics.getNullCount(columnName)).isEqualTo(Optional.of(0L)); } } @@ -389,10 +384,10 @@ public void testUnicodeValues() assertThat(entry.getStats()).isPresent(); DeltaLakeFileStatistics fileStatistics = entry.getStats().get(); - assertEquals(fileStatistics.getNumRecords(), Optional.of(2L)); - assertEquals(fileStatistics.getMinColumnValue(columnHandle), Optional.of(utf8Slice("ab\uFAD8"))); - assertEquals(fileStatistics.getMaxColumnValue(columnHandle), Optional.of(utf8Slice("ab\uD83D\uDD74"))); - assertEquals(fileStatistics.getNullCount(columnName), Optional.of(0L)); + assertThat(fileStatistics.getNumRecords()).isEqualTo(Optional.of(2L)); + assertThat(fileStatistics.getMinColumnValue(columnHandle)).isEqualTo(Optional.of(utf8Slice("ab\uFAD8"))); + assertThat(fileStatistics.getMaxColumnValue(columnHandle)).isEqualTo(Optional.of(utf8Slice("ab\uD83D\uDD74"))); + assertThat(fileStatistics.getNullCount(columnName)).isEqualTo(Optional.of(0L)); } } @@ -410,22 +405,22 @@ public void testPartitionedTable() ImmutableList.of(partitionColumn), "VALUES ('a', 1), ('b', 1), ('c', 1), ('c', 2), ('d', 2), ('e', 2), (null, 1)")) { List addFileEntries = getAddFileEntries(table.getName()); - assertEquals(addFileEntries.size(), 2); + assertThat(addFileEntries.size()).isEqualTo(2); for (AddFileEntry addFileEntry : addFileEntries) { assertThat(addFileEntry.getStats()).isPresent(); DeltaLakeFileStatistics fileStatistics = addFileEntry.getStats().get(); if (addFileEntry.getPartitionValues().get(partitionColumn).equals("1")) { - assertEquals(fileStatistics.getMinColumnValue(columnHandle), Optional.of(utf8Slice("a"))); - assertEquals(fileStatistics.getMaxColumnValue(columnHandle), Optional.of(utf8Slice("c"))); - assertEquals(fileStatistics.getNumRecords(), Optional.of(4L)); - assertEquals(fileStatistics.getNullCount(columnName), Optional.of(1L)); + assertThat(fileStatistics.getMinColumnValue(columnHandle)).isEqualTo(Optional.of(utf8Slice("a"))); + assertThat(fileStatistics.getMaxColumnValue(columnHandle)).isEqualTo(Optional.of(utf8Slice("c"))); + assertThat(fileStatistics.getNumRecords()).isEqualTo(Optional.of(4L)); + assertThat(fileStatistics.getNullCount(columnName)).isEqualTo(Optional.of(1L)); } else if (addFileEntry.getPartitionValues().get(partitionColumn).equals("2")) { - assertEquals(fileStatistics.getMinColumnValue(columnHandle), Optional.of(utf8Slice("c"))); - assertEquals(fileStatistics.getMaxColumnValue(columnHandle), Optional.of(utf8Slice("e"))); - assertEquals(fileStatistics.getNumRecords(), Optional.of(3L)); - assertEquals(fileStatistics.getNullCount(columnName), Optional.of(0L)); + assertThat(fileStatistics.getMinColumnValue(columnHandle)).isEqualTo(Optional.of(utf8Slice("c"))); + assertThat(fileStatistics.getMaxColumnValue(columnHandle)).isEqualTo(Optional.of(utf8Slice("e"))); + assertThat(fileStatistics.getNumRecords()).isEqualTo(Optional.of(3L)); + assertThat(fileStatistics.getNullCount(columnName)).isEqualTo(Optional.of(0L)); } } } @@ -448,10 +443,8 @@ public void testMultiFileTableWithNaNValue() List statistics = addFileEntries.stream().map(entry -> entry.getStats().get()).collect(toImmutableList()); - assertEquals(statistics.stream().filter(stat -> stat.getMinColumnValue(columnHandle).isEmpty() && stat.getMaxColumnValue(columnHandle).isEmpty()).count(), 1); - assertEquals( - statistics.stream().filter(stat -> stat.getMinColumnValue(columnHandle).isPresent() && stat.getMaxColumnValue(columnHandle).isPresent()).count(), - statistics.size() - 1); + assertThat(statistics.stream().filter(stat -> stat.getMinColumnValue(columnHandle).isEmpty() && stat.getMaxColumnValue(columnHandle).isEmpty()).count()).isEqualTo(1); + assertThat(statistics.stream().filter(stat -> stat.getMinColumnValue(columnHandle).isPresent() && stat.getMaxColumnValue(columnHandle).isPresent()).count()).isEqualTo(statistics.size() - 1); } } diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeDynamicFiltering.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeDynamicFiltering.java index 9665cdb4cea4..d9a922743b60 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeDynamicFiltering.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeDynamicFiltering.java @@ -60,8 +60,7 @@ import static io.trino.tpch.TpchTable.LINE_ITEM; import static io.trino.tpch.TpchTable.ORDERS; import static java.lang.String.format; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; @Isolated public class TestDeltaLakeDynamicFiltering @@ -127,7 +126,7 @@ public void testIncompleteDynamicFilterTimeout() .beginTransactionId(transactionId, transactionManager, new AllowAllAccessControl()); QualifiedObjectName tableName = new QualifiedObjectName(DELTA_CATALOG, "default", "orders"); Optional tableHandle = runner.getMetadata().getTableHandle(session, tableName); - assertTrue(tableHandle.isPresent()); + assertThat(tableHandle.isPresent()).isTrue(); SplitSource splitSource = runner.getSplitManager() .getSplits(session, Span.getInvalid(), tableHandle.get(), new IncompleteDynamicFilter(), alwaysTrue()); List splits = new ArrayList<>(); @@ -135,7 +134,7 @@ public void testIncompleteDynamicFilterTimeout() splits.addAll(splitSource.getNextBatch(1000).get().getSplits()); } splitSource.close(); - assertFalse(splits.isEmpty()); + assertThat(splits.isEmpty()).isFalse(); } private Session sessionWithDynamicFiltering(boolean enabled, JoinDistributionType joinDistributionType) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakePageSink.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakePageSink.java index 1494aeb6552c..3a1b1b0cbf4b 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakePageSink.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakePageSink.java @@ -72,8 +72,7 @@ import static java.lang.Math.round; import static java.time.temporal.ChronoUnit.MINUTES; import static java.util.stream.Collectors.toList; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestDeltaLakePageSink { @@ -121,23 +120,23 @@ public void testPageSinkStats() .map(dataFileInfoCodec::fromJson) .collect(toImmutableList()); - assertEquals(dataFileInfos.size(), 1); + assertThat(dataFileInfos.size()).isEqualTo(1); DataFileInfo dataFileInfo = dataFileInfos.get(0); List files = ImmutableList.copyOf(new File(tablePath).listFiles((dir, name) -> !name.endsWith(".crc"))); - assertEquals(files.size(), 1); + assertThat(files.size()).isEqualTo(1); File outputFile = files.get(0); - assertEquals(round(stats.getInputPageSizeInBytes().getAllTime().getMax()), page.getRetainedSizeInBytes()); + assertThat(round(stats.getInputPageSizeInBytes().getAllTime().getMax())).isEqualTo(page.getRetainedSizeInBytes()); - assertEquals(dataFileInfo.getStatistics().getNumRecords(), Optional.of(rows)); - assertEquals(dataFileInfo.getPartitionValues(), ImmutableList.of()); - assertEquals(dataFileInfo.getSize(), outputFile.length()); - assertEquals(dataFileInfo.getPath(), outputFile.getName()); + assertThat(dataFileInfo.getStatistics().getNumRecords()).isEqualTo(Optional.of(rows)); + assertThat(dataFileInfo.getPartitionValues()).isEqualTo(ImmutableList.of()); + assertThat(dataFileInfo.getSize()).isEqualTo(outputFile.length()); + assertThat(dataFileInfo.getPath()).isEqualTo(outputFile.getName()); Instant now = Instant.now(); - assertTrue(dataFileInfo.getCreationTime() < now.toEpochMilli()); - assertTrue(dataFileInfo.getCreationTime() > now.minus(1, MINUTES).toEpochMilli()); + assertThat(dataFileInfo.getCreationTime() < now.toEpochMilli()).isTrue(); + assertThat(dataFileInfo.getCreationTime() > now.minus(1, MINUTES).toEpochMilli()).isTrue(); } finally { deleteRecursively(tempDir.toPath(), ALLOW_INSECURE); diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeParquetSchemas.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeParquetSchemas.java index bf671b7fdc2f..8e9fe1c031c5 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeParquetSchemas.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeParquetSchemas.java @@ -32,7 +32,7 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static org.apache.parquet.schema.Type.Repetition.OPTIONAL; import static org.apache.parquet.schema.Type.Repetition.REQUIRED; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestDeltaLakeParquetSchemas { @@ -350,7 +350,7 @@ private void assertParquetSchemaMappingCreationAccuracy( Map, Type> expectedPrimitiveTypes) { DeltaLakeParquetSchemaMapping parquetSchemaMapping = createParquetSchemaMapping(jsonSchema, typeManager, columnMappingMode, partitionColumnNames); - assertEquals(parquetSchemaMapping.messageType(), expectedMessageType); - assertEquals(parquetSchemaMapping.primitiveTypes(), expectedPrimitiveTypes); + assertThat(parquetSchemaMapping.messageType()).isEqualTo(expectedMessageType); + assertThat(parquetSchemaMapping.primitiveTypes()).isEqualTo(expectedPrimitiveTypes); } } diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSplitManager.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSplitManager.java index ff5ea4be8ac6..dd165811ee88 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSplitManager.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSplitManager.java @@ -62,7 +62,7 @@ import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_STATS; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestDeltaLakeSplitManager { @@ -115,7 +115,7 @@ public void testInitialSplits() makeSplit(10_000, 5_000, fileSize, minimumAssignedSplitWeight), makeSplit(15_000, 5_000, fileSize, minimumAssignedSplitWeight)); - assertEquals(splits, expected); + assertThat(splits).isEqualTo(expected); } @Test @@ -142,7 +142,7 @@ public void testNonInitialSplits() makeSplit(25_000, 20_000, fileSize, minimumAssignedSplitWeight), makeSplit(45_000, 5_000, fileSize, minimumAssignedSplitWeight)); - assertEquals(splits, expected); + assertThat(splits).isEqualTo(expected); } @Test @@ -167,7 +167,7 @@ public void testSplitsFromMultipleFiles() makeSplit(2_000, 2_000, secondFileSize, minimumAssignedSplitWeight), makeSplit(4_000, 10_000, secondFileSize, minimumAssignedSplitWeight), makeSplit(14_000, 6_000, secondFileSize, minimumAssignedSplitWeight)); - assertEquals(splits, expected); + assertThat(splits).isEqualTo(expected); } private DeltaLakeSplitManager setupSplitManager(List addFileEntries, DeltaLakeConfig deltaLakeConfig) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeTableName.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeTableName.java index 9177046aedc6..2df36fb8a49a 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeTableName.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeTableName.java @@ -23,9 +23,6 @@ import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestDeltaLakeTableName { @@ -47,32 +44,32 @@ public void testParse() @Test public void testIsDataTable() { - assertTrue(DeltaLakeTableName.isDataTable("abc")); + assertThat(DeltaLakeTableName.isDataTable("abc")).isTrue(); - assertFalse(DeltaLakeTableName.isDataTable("abc$data")); // it's invalid - assertFalse(DeltaLakeTableName.isDataTable("abc$history")); - assertFalse(DeltaLakeTableName.isDataTable("abc$invalid")); + assertThat(DeltaLakeTableName.isDataTable("abc$data")).isFalse(); // it's invalid + assertThat(DeltaLakeTableName.isDataTable("abc$history")).isFalse(); + assertThat(DeltaLakeTableName.isDataTable("abc$invalid")).isFalse(); } @Test public void testTableNameFrom() { - assertEquals(DeltaLakeTableName.tableNameFrom("abc"), "abc"); - assertEquals(DeltaLakeTableName.tableNameFrom("abc$data"), "abc"); - assertEquals(DeltaLakeTableName.tableNameFrom("abc$history"), "abc"); - assertEquals(DeltaLakeTableName.tableNameFrom("abc$properties"), "abc"); - assertEquals(DeltaLakeTableName.tableNameFrom("abc$invalid"), "abc"); + assertThat(DeltaLakeTableName.tableNameFrom("abc")).isEqualTo("abc"); + assertThat(DeltaLakeTableName.tableNameFrom("abc$data")).isEqualTo("abc"); + assertThat(DeltaLakeTableName.tableNameFrom("abc$history")).isEqualTo("abc"); + assertThat(DeltaLakeTableName.tableNameFrom("abc$properties")).isEqualTo("abc"); + assertThat(DeltaLakeTableName.tableNameFrom("abc$invalid")).isEqualTo("abc"); } @Test public void testTableTypeFrom() { - assertEquals(DeltaLakeTableName.tableTypeFrom("abc"), Optional.of(DATA)); - assertEquals(DeltaLakeTableName.tableTypeFrom("abc$data"), Optional.empty()); // it's invalid - assertEquals(DeltaLakeTableName.tableTypeFrom("abc$history"), Optional.of(HISTORY)); - assertEquals(DeltaLakeTableName.tableTypeFrom("abc$properties"), Optional.of(PROPERTIES)); + assertThat(DeltaLakeTableName.tableTypeFrom("abc")).isEqualTo(Optional.of(DATA)); + assertThat(DeltaLakeTableName.tableTypeFrom("abc$data")).isEqualTo(Optional.empty()); // it's invalid + assertThat(DeltaLakeTableName.tableTypeFrom("abc$history")).isEqualTo(Optional.of(HISTORY)); + assertThat(DeltaLakeTableName.tableTypeFrom("abc$properties")).isEqualTo(Optional.of(PROPERTIES)); - assertEquals(DeltaLakeTableName.tableTypeFrom("abc$invalid"), Optional.empty()); + assertThat(DeltaLakeTableName.tableTypeFrom("abc$invalid")).isEqualTo(Optional.empty()); } private static void assertNoValidTableType(String inputName) @@ -90,7 +87,7 @@ private static void assertInvalid(String inputName, String message) private static void assertParseNameAndType(String inputName, String tableName, DeltaLakeTableType tableType) { - assertEquals(DeltaLakeTableName.tableNameFrom(inputName), tableName); - assertEquals(DeltaLakeTableName.tableTypeFrom(inputName), Optional.of(tableType)); + assertThat(DeltaLakeTableName.tableNameFrom(inputName)).isEqualTo(tableName); + assertThat(DeltaLakeTableName.tableTypeFrom(inputName)).isEqualTo(Optional.of(tableType)); } } diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeWriter.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeWriter.java index 1410ed80ad48..7665b2ec9287 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeWriter.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeWriter.java @@ -44,7 +44,7 @@ import static java.lang.Float.floatToRawIntBits; import static java.nio.ByteOrder.LITTLE_ENDIAN; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestDeltaLakeWriter { @@ -61,10 +61,10 @@ public void testMergeIntStatistics() DeltaLakeColumnHandle intColumn = new DeltaLakeColumnHandle(columnName, INTEGER, OptionalInt.empty(), columnName, INTEGER, REGULAR, Optional.empty()); DeltaLakeFileStatistics fileStats = mergeStats(buildMultimap(columnName, metadata), ImmutableMap.of(columnName, INTEGER), 20); - assertEquals(fileStats.getNumRecords(), Optional.of(20L)); - assertEquals(fileStats.getMinColumnValue(intColumn), Optional.of(-200L)); - assertEquals(fileStats.getMaxColumnValue(intColumn), Optional.of(250L)); - assertEquals(fileStats.getNullCount(columnName), Optional.of(13L)); + assertThat(fileStats.getNumRecords()).isEqualTo(Optional.of(20L)); + assertThat(fileStats.getMinColumnValue(intColumn)).isEqualTo(Optional.of(-200L)); + assertThat(fileStats.getMaxColumnValue(intColumn)).isEqualTo(Optional.of(250L)); + assertThat(fileStats.getNullCount(columnName)).isEqualTo(Optional.of(13L)); } @Test @@ -80,10 +80,10 @@ public void testMergeFloatStatistics() DeltaLakeColumnHandle floatColumn = new DeltaLakeColumnHandle(columnName, REAL, OptionalInt.empty(), columnName, REAL, REGULAR, Optional.empty()); DeltaLakeFileStatistics fileStats = mergeStats(buildMultimap(columnName, metadata), ImmutableMap.of(columnName, REAL), 20); - assertEquals(fileStats.getNumRecords(), Optional.of(20L)); - assertEquals(fileStats.getMinColumnValue(floatColumn), Optional.of((long) floatToRawIntBits(-2.001f))); - assertEquals(fileStats.getMaxColumnValue(floatColumn), Optional.of((long) floatToRawIntBits(1.0f))); - assertEquals(fileStats.getNullCount(columnName), Optional.of(13L)); + assertThat(fileStats.getNumRecords()).isEqualTo(Optional.of(20L)); + assertThat(fileStats.getMinColumnValue(floatColumn)).isEqualTo(Optional.of((long) floatToRawIntBits(-2.001f))); + assertThat(fileStats.getMaxColumnValue(floatColumn)).isEqualTo(Optional.of((long) floatToRawIntBits(1.0f))); + assertThat(fileStats.getNullCount(columnName)).isEqualTo(Optional.of(13L)); } @Test @@ -101,10 +101,10 @@ public void testMergeFloatNaNStatistics() DeltaLakeColumnHandle floatColumn = new DeltaLakeColumnHandle(columnName, REAL, OptionalInt.empty(), columnName, REAL, REGULAR, Optional.empty()); DeltaLakeFileStatistics fileStats = mergeStats(buildMultimap(columnName, metadata), ImmutableMap.of(columnName, REAL), 20); - assertEquals(fileStats.getNumRecords(), Optional.of(20L)); - assertEquals(fileStats.getMinColumnValue(floatColumn), Optional.empty()); - assertEquals(fileStats.getMaxColumnValue(floatColumn), Optional.empty()); - assertEquals(fileStats.getNullCount(columnName), Optional.empty()); + assertThat(fileStats.getNumRecords()).isEqualTo(Optional.of(20L)); + assertThat(fileStats.getMinColumnValue(floatColumn)).isEqualTo(Optional.empty()); + assertThat(fileStats.getMaxColumnValue(floatColumn)).isEqualTo(Optional.empty()); + assertThat(fileStats.getNullCount(columnName)).isEqualTo(Optional.empty()); } @Test @@ -122,10 +122,10 @@ public void testMergeDoubleNaNStatistics() DeltaLakeColumnHandle doubleColumn = new DeltaLakeColumnHandle(columnName, DOUBLE, OptionalInt.empty(), columnName, DOUBLE, REGULAR, Optional.empty()); DeltaLakeFileStatistics fileStats = mergeStats(buildMultimap(columnName, metadata), ImmutableMap.of(columnName, DOUBLE), 20); - assertEquals(fileStats.getNumRecords(), Optional.of(20L)); - assertEquals(fileStats.getMinColumnValue(doubleColumn), Optional.empty()); - assertEquals(fileStats.getMaxColumnValue(doubleColumn), Optional.empty()); - assertEquals(fileStats.getNullCount(columnName), Optional.empty()); + assertThat(fileStats.getNumRecords()).isEqualTo(Optional.of(20L)); + assertThat(fileStats.getMinColumnValue(doubleColumn)).isEqualTo(Optional.empty()); + assertThat(fileStats.getMaxColumnValue(doubleColumn)).isEqualTo(Optional.empty()); + assertThat(fileStats.getNullCount(columnName)).isEqualTo(Optional.empty()); } @Test @@ -141,10 +141,10 @@ public void testMergeStringStatistics() DeltaLakeColumnHandle varcharColumn = new DeltaLakeColumnHandle(columnName, VarcharType.createUnboundedVarcharType(), OptionalInt.empty(), columnName, VarcharType.createUnboundedVarcharType(), REGULAR, Optional.empty()); DeltaLakeFileStatistics fileStats = mergeStats(buildMultimap(columnName, metadata), ImmutableMap.of(columnName, createUnboundedVarcharType()), 20); - assertEquals(fileStats.getNumRecords(), Optional.of(20L)); - assertEquals(fileStats.getMinColumnValue(varcharColumn), Optional.of(utf8Slice("aba"))); - assertEquals(fileStats.getMaxColumnValue(varcharColumn), Optional.of(utf8Slice("ab⌘"))); - assertEquals(fileStats.getNullCount(columnName), Optional.of(12L)); + assertThat(fileStats.getNumRecords()).isEqualTo(Optional.of(20L)); + assertThat(fileStats.getMinColumnValue(varcharColumn)).isEqualTo(Optional.of(utf8Slice("aba"))); + assertThat(fileStats.getMaxColumnValue(varcharColumn)).isEqualTo(Optional.of(utf8Slice("ab⌘"))); + assertThat(fileStats.getNullCount(columnName)).isEqualTo(Optional.of(12L)); } @Test @@ -160,10 +160,10 @@ public void testMergeStringUnicodeStatistics() DeltaLakeColumnHandle varcharColumn = new DeltaLakeColumnHandle(columnName, VarcharType.createUnboundedVarcharType(), OptionalInt.empty(), columnName, VarcharType.createUnboundedVarcharType(), REGULAR, Optional.empty()); DeltaLakeFileStatistics fileStats = mergeStats(buildMultimap(columnName, metadata), ImmutableMap.of(columnName, createUnboundedVarcharType()), 20); - assertEquals(fileStats.getNumRecords(), Optional.of(20L)); - assertEquals(fileStats.getMinColumnValue(varcharColumn), Optional.of(utf8Slice("aba"))); - assertEquals(fileStats.getMaxColumnValue(varcharColumn), Optional.of(utf8Slice("ab\uD83D\uDD74"))); - assertEquals(fileStats.getNullCount(columnName), Optional.of(12L)); + assertThat(fileStats.getNumRecords()).isEqualTo(Optional.of(20L)); + assertThat(fileStats.getMinColumnValue(varcharColumn)).isEqualTo(Optional.of(utf8Slice("aba"))); + assertThat(fileStats.getMaxColumnValue(varcharColumn)).isEqualTo(Optional.of(utf8Slice("ab\uD83D\uDD74"))); + assertThat(fileStats.getNullCount(columnName)).isEqualTo(Optional.of(12L)); } private ColumnChunkMetaData createMetaData(String columnName, PrimitiveType columnType, long valueCount, Statistics statistics) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestPredicatePushdown.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestPredicatePushdown.java index 2ca8bcde9c93..ec95becbea82 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestPredicatePushdown.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestPredicatePushdown.java @@ -15,7 +15,6 @@ import com.google.common.collect.ContiguousSet; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Sets; import io.trino.plugin.hive.containers.HiveMinioDataLake; import io.trino.spi.QueryId; import io.trino.testing.AbstractTestQueryFramework; @@ -24,7 +23,6 @@ import io.trino.testing.MaterializedRow; import io.trino.testing.QueryRunner; import org.junit.jupiter.api.Test; -import org.testng.asserts.SoftAssert; import java.nio.file.Path; import java.util.OptionalLong; @@ -34,7 +32,7 @@ import static io.trino.plugin.deltalake.DeltaLakeQueryRunner.createS3DeltaLakeQueryRunner; import static io.trino.testing.TestingNames.randomNameSuffix; import static java.lang.String.format; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestPredicatePushdown extends AbstractTestQueryFramework @@ -97,9 +95,7 @@ public void testDeletePushdown() 200, 500); // Check that the correct data was deleted - assertEquals( - execute(format("SELECT custkey FROM %s", table)).getOnlyColumnAsSet(), - ContiguousSet.closed(1L, 1300L)); + assertThat(execute(format("SELECT custkey FROM %s", table)).getOnlyColumnAsSet()).isEqualTo(ContiguousSet.closed(1L, 1300L)); table = testTable.register("delete_pushdown_disjoint"); // 11 groups have data outside of (500, 1100] @@ -107,9 +103,7 @@ public void testDeletePushdown() format("DELETE FROM %s WHERE custkey <= 500 OR custkey > 1100", table), 900, 1100); - assertEquals( - execute(format("SELECT custkey FROM %s", table)).getOnlyColumnAsSet(), - ContiguousSet.closed(501L, 1100L)); + assertThat(execute(format("SELECT custkey FROM %s", table)).getOnlyColumnAsSet()).isEqualTo(ContiguousSet.closed(501L, 1100L)); } @Test @@ -150,24 +144,14 @@ private void assertPushdown(String actual, String expected, long countProcessed) Set expectedRows = Set.copyOf( computeExpected(expected, result.getResult().getTypes()).getMaterializedRows()); - SoftAssert softly = new SoftAssert(); - softly.assertTrue( - result.getResult().getUpdateType().isEmpty(), - "Query should not have update type"); - softly.assertEqualsNoOrder( - actualRows.toArray(), - expectedRows.toArray(), - format( - "Wrong query results:\n" - + "\t\tmissing rows: %s\n" - + "\t\textra rows: %s", - Sets.difference(expectedRows, actualRows), - Sets.difference(actualRows, expectedRows))); - softly.assertEquals( - getProcessedPositions(result.getQueryId()), - countProcessed, - "Wrong number of rows processed after pushdown to Parquet"); - softly.assertAll(); + assertThat(result.getResult().getUpdateType()) + .describedAs("Query should not have update type") + .isEmpty(); + + assertThat(actualRows).isEqualTo(expectedRows); + assertThat(getProcessedPositions(result.getQueryId())) + .describedAs("Wrong number of rows processed after pushdown to Parquet") + .isEqualTo(countProcessed); } /** @@ -182,14 +166,17 @@ private void assertPushdownUpdate(String sql, long count, long countProcessed) MaterializedResultWithQueryId result = executeWithQueryId(sql); OptionalLong actualCount = result.getResult().getUpdateCount(); - SoftAssert softly = new SoftAssert(); - softly.assertTrue(actualCount.isPresent(), "Missing update count"); - softly.assertEquals(actualCount.getAsLong(), count, "Wrong number of rows updated"); - softly.assertEquals( - getProcessedPositions(result.getQueryId()), - countProcessed, - "Wrong amount of data filtered by pushdown to Parquet"); - softly.assertAll(); + assertThat(actualCount) + .describedAs("Missing update count") + .isPresent(); + + assertThat(actualCount.getAsLong()) + .describedAs("Wrong number of rows updated") + .isEqualTo(count); + + assertThat(getProcessedPositions(result.getQueryId())) + .describedAs("Wrong amount of data filtered by pushdown to Parquet") + .isEqualTo(countProcessed); } private MaterializedResultWithQueryId executeWithQueryId(String sql) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestReadJsonTransactionLog.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestReadJsonTransactionLog.java index 3d87f04b4904..45f546e075eb 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestReadJsonTransactionLog.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestReadJsonTransactionLog.java @@ -35,7 +35,7 @@ import static com.google.common.base.Verify.verify; import static com.google.common.collect.ImmutableList.toImmutableList; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestReadJsonTransactionLog { @@ -44,49 +44,41 @@ public class TestReadJsonTransactionLog @Test public void testAdd() { - assertEquals( - readJsonTransactionLogs("databricks73/person/_delta_log") - .map(this::deserialize) - .map(DeltaLakeTransactionLogEntry::getAdd) - .filter(Objects::nonNull) - .map(AddFileEntry::getPath) - .filter(Objects::nonNull) - .count(), - 18); + assertThat(readJsonTransactionLogs("databricks73/person/_delta_log") + .map(this::deserialize) + .map(DeltaLakeTransactionLogEntry::getAdd) + .filter(Objects::nonNull) + .map(AddFileEntry::getPath) + .filter(Objects::nonNull) + .count()).isEqualTo(18); - assertEquals( - readJsonTransactionLogs("deltalake/person/_delta_log") - .map(this::deserialize) - .map(DeltaLakeTransactionLogEntry::getAdd) - .filter(Objects::nonNull) - .map(AddFileEntry::getPath) - .filter(Objects::nonNull) - .count(), - 18); + assertThat(readJsonTransactionLogs("deltalake/person/_delta_log") + .map(this::deserialize) + .map(DeltaLakeTransactionLogEntry::getAdd) + .filter(Objects::nonNull) + .map(AddFileEntry::getPath) + .filter(Objects::nonNull) + .count()).isEqualTo(18); } @Test public void testRemove() { - assertEquals( - readJsonTransactionLogs("databricks73/person/_delta_log") - .map(this::deserialize) - .map(DeltaLakeTransactionLogEntry::getRemove) - .filter(Objects::nonNull) - .map(RemoveFileEntry::getPath) - .filter(Objects::nonNull) - .count(), - 6); + assertThat(readJsonTransactionLogs("databricks73/person/_delta_log") + .map(this::deserialize) + .map(DeltaLakeTransactionLogEntry::getRemove) + .filter(Objects::nonNull) + .map(RemoveFileEntry::getPath) + .filter(Objects::nonNull) + .count()).isEqualTo(6); - assertEquals( - readJsonTransactionLogs("deltalake/person/_delta_log") - .map(this::deserialize) - .map(DeltaLakeTransactionLogEntry::getRemove) - .filter(Objects::nonNull) - .map(RemoveFileEntry::getPath) - .filter(Objects::nonNull) - .count(), - 6); + assertThat(readJsonTransactionLogs("deltalake/person/_delta_log") + .map(this::deserialize) + .map(DeltaLakeTransactionLogEntry::getRemove) + .filter(Objects::nonNull) + .map(RemoveFileEntry::getPath) + .filter(Objects::nonNull) + .count()).isEqualTo(6); } @Test @@ -94,9 +86,9 @@ public void testReadLastCheckpointFile() throws JsonProcessingException { LastCheckpoint lastCheckpoint = objectMapper.readValue("{\"version\":10,\"size\":17}", LastCheckpoint.class); - assertEquals(lastCheckpoint.getVersion(), 10L); - assertEquals(lastCheckpoint.getSize(), BigInteger.valueOf(17L)); - assertEquals(lastCheckpoint.getParts(), Optional.empty()); + assertThat(lastCheckpoint.getVersion()).isEqualTo(10L); + assertThat(lastCheckpoint.getSize()).isEqualTo(BigInteger.valueOf(17L)); + assertThat(lastCheckpoint.getParts()).isEqualTo(Optional.empty()); } @Test @@ -104,9 +96,9 @@ public void testReadLastCheckpointFileForMultipart() throws JsonProcessingException { LastCheckpoint lastCheckpoint = objectMapper.readValue("{\"version\":237580,\"size\":658573,\"parts\":2}", LastCheckpoint.class); - assertEquals(lastCheckpoint.getVersion(), 237580L); - assertEquals(lastCheckpoint.getSize(), BigInteger.valueOf(658573L)); - assertEquals(lastCheckpoint.getParts(), Optional.of(2)); + assertThat(lastCheckpoint.getVersion()).isEqualTo(237580L); + assertThat(lastCheckpoint.getSize()).isEqualTo(BigInteger.valueOf(658573L)); + assertThat(lastCheckpoint.getParts()).isEqualTo(Optional.of(2)); } private Stream readJsonTransactionLogs(String location) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestSplitPruning.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestSplitPruning.java index 5c23d74303f5..99ff36ebfa62 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestSplitPruning.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestSplitPruning.java @@ -37,7 +37,6 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) public class TestSplitPruning @@ -139,7 +138,7 @@ public void testStatsPruningNaN() MaterializedResult result = getDistributedQueryRunner().execute( getSession(), format("SELECT name FROM %s WHERE val IS NOT NULL", tableName)); - assertEquals(result.getOnlyColumnAsSet(), Set.of("a5", "b5", "a6", "b6")); + assertThat(result.getOnlyColumnAsSet()).isEqualTo(Set.of("a5", "b5", "a6", "b6")); } } diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestTransactionLogAccess.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestTransactionLogAccess.java index 281804f7bec3..4e2b46578e14 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestTransactionLogAccess.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestTransactionLogAccess.java @@ -82,9 +82,6 @@ import static java.util.stream.Collectors.toCollection; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @Execution(SAME_THREAD) // e.g. TrackingFileSystemFactory is shared mutable state public class TestTransactionLogAccess @@ -171,16 +168,16 @@ public void testGetMetadataEntry() MetadataEntry metadataEntry = transactionLogAccess.getMetadataEntry(tableSnapshot, SESSION); - assertEquals(metadataEntry.getCreatedTime(), 1579190100722L); - assertEquals(metadataEntry.getId(), "b6aeffad-da73-4dde-b68e-937e468b1fdf"); + assertThat(metadataEntry.getCreatedTime()).isEqualTo(1579190100722L); + assertThat(metadataEntry.getId()).isEqualTo("b6aeffad-da73-4dde-b68e-937e468b1fdf"); assertThat(metadataEntry.getOriginalPartitionColumns()).containsOnly("age"); assertThat(metadataEntry.getLowercasePartitionColumns()).containsOnly("age"); MetadataEntry.Format format = metadataEntry.getFormat(); - assertEquals(format.getOptions().keySet().size(), 0); - assertEquals(format.getProvider(), "parquet"); + assertThat(format.getOptions().keySet().size()).isEqualTo(0); + assertThat(format.getProvider()).isEqualTo("parquet"); - assertEquals(tableSnapshot.getCachedMetadata(), Optional.of(metadataEntry)); + assertThat(tableSnapshot.getCachedMetadata()).isEqualTo(Optional.of(metadataEntry)); } @Test @@ -191,7 +188,7 @@ public void testGetMetadataEntryUppercase() MetadataEntry metadataEntry = transactionLogAccess.getMetadataEntry(tableSnapshot, SESSION); assertThat(metadataEntry.getOriginalPartitionColumns()).containsOnly("ALA"); assertThat(metadataEntry.getLowercasePartitionColumns()).containsOnly("ala"); - assertEquals(tableSnapshot.getCachedMetadata(), Optional.of(metadataEntry)); + assertThat(tableSnapshot.getCachedMetadata()).isEqualTo(Optional.of(metadataEntry)); } @Test @@ -207,7 +204,7 @@ public void testGetActiveAddEntries() .stream() .map(AddFileEntry::getPath) .collect(Collectors.toSet()); - assertEquals(paths, EXPECTED_ADD_FILE_PATHS); + assertThat(paths).isEqualTo(EXPECTED_ADD_FILE_PATHS); AddFileEntry addFileEntry = addFileEntries .stream() @@ -221,9 +218,9 @@ public void testGetActiveAddEntries() .hasSize(1) .containsEntry("age", Optional.of("42")); - assertEquals(addFileEntry.getSize(), 2687); - assertEquals(addFileEntry.getModificationTime(), 1579190188000L); - assertFalse(addFileEntry.isDataChange()); + assertThat(addFileEntry.getSize()).isEqualTo(2687); + assertThat(addFileEntry.getModificationTime()).isEqualTo(1579190188000L); + assertThat(addFileEntry.isDataChange()).isFalse(); } @Test @@ -264,8 +261,8 @@ public void testAddEntryPruning() .map(AddFileEntry::getPath) .collect(Collectors.toSet()); - assertFalse(paths.contains("age=25/part-00001-aceaf062-1cd1-45cb-8f83-277ffebe995c.c000.snappy.parquet")); - assertFalse(paths.contains("age=29/part-00000-3794c463-cb0c-4beb-8d07-7cc1e3b5920f.c000.snappy.parquet")); + assertThat(paths.contains("age=25/part-00001-aceaf062-1cd1-45cb-8f83-277ffebe995c.c000.snappy.parquet")).isFalse(); + assertThat(paths.contains("age=29/part-00000-3794c463-cb0c-4beb-8d07-7cc1e3b5920f.c000.snappy.parquet")).isFalse(); } @Test @@ -286,8 +283,8 @@ public void testAddEntryOverrides() List activeEntries = addFileEntries.stream() .filter(addFileEntry -> addFileEntry.getPath().equals(path)) .toList(); - assertEquals(activeEntries.size(), 1); - assertEquals(activeEntries.get(0).getModificationTime(), 9999999L); + assertThat(activeEntries.size()).isEqualTo(1); + assertThat(activeEntries.get(0).getModificationTime()).isEqualTo(9999999L); } } @@ -305,8 +302,8 @@ public void testAddRemoveAdd() .filter(addFileEntry -> addFileEntry.getPath().equals("age=30/part-00002-5800be2e-2373-47d8-8b86-776a8ea9d69f.c000.snappy.parquet")) .toList(); - assertEquals(activeEntries.size(), 1); - assertEquals(activeEntries.get(0).getModificationTime(), 9999999L); + assertThat(activeEntries.size()).isEqualTo(1); + assertThat(activeEntries.get(0).getModificationTime()).isEqualTo(9999999L); } @Test @@ -317,7 +314,7 @@ public void testGetRemoveEntries() try (Stream removeEntries = transactionLogAccess.getRemoveEntries(tableSnapshot, SESSION)) { Set removedEntries = removeEntries.collect(Collectors.toSet()); - assertEquals(removedEntries, EXPECTED_REMOVE_ENTRIES); + assertThat(removedEntries).isEqualTo(EXPECTED_REMOVE_ENTRIES); } } @@ -328,18 +325,16 @@ public void testGetCommitInfoEntries() setupTransactionLogAccessFromResources("person", "databricks73/person"); try (Stream commitInfoEntries = transactionLogAccess.getCommitInfoEntries(tableSnapshot, SESSION)) { Set entrySet = commitInfoEntries.collect(Collectors.toSet()); - assertEquals( - entrySet, - ImmutableSet.of( - new CommitInfoEntry(11, 1579190200860L, "671960514434781", "michal.slizak@starburstdata.com", "WRITE", - ImmutableMap.of("mode", "Append", "partitionBy", "[\"age\"]"), null, new CommitInfoEntry.Notebook("3040849856940931"), - "0116-154224-guppy476", 10L, "WriteSerializable", Optional.of(true)), - new CommitInfoEntry(12, 1579190206644L, "671960514434781", "michal.slizak@starburstdata.com", "WRITE", - ImmutableMap.of("mode", "Append", "partitionBy", "[\"age\"]"), null, new CommitInfoEntry.Notebook("3040849856940931"), - "0116-154224-guppy476", 11L, "WriteSerializable", Optional.of(true)), - new CommitInfoEntry(13, 1579190210571L, "671960514434781", "michal.slizak@starburstdata.com", "WRITE", - ImmutableMap.of("mode", "Append", "partitionBy", "[\"age\"]"), null, new CommitInfoEntry.Notebook("3040849856940931"), - "0116-154224-guppy476", 12L, "WriteSerializable", Optional.of(true)))); + assertThat(entrySet).isEqualTo(ImmutableSet.of( + new CommitInfoEntry(11, 1579190200860L, "671960514434781", "michal.slizak@starburstdata.com", "WRITE", + ImmutableMap.of("mode", "Append", "partitionBy", "[\"age\"]"), null, new CommitInfoEntry.Notebook("3040849856940931"), + "0116-154224-guppy476", 10L, "WriteSerializable", Optional.of(true)), + new CommitInfoEntry(12, 1579190206644L, "671960514434781", "michal.slizak@starburstdata.com", "WRITE", + ImmutableMap.of("mode", "Append", "partitionBy", "[\"age\"]"), null, new CommitInfoEntry.Notebook("3040849856940931"), + "0116-154224-guppy476", 11L, "WriteSerializable", Optional.of(true)), + new CommitInfoEntry(13, 1579190210571L, "671960514434781", "michal.slizak@starburstdata.com", "WRITE", + ImmutableMap.of("mode", "Append", "partitionBy", "[\"age\"]"), null, new CommitInfoEntry.Notebook("3040849856940931"), + "0116-154224-guppy476", 12L, "WriteSerializable", Optional.of(true)))); } } @@ -364,8 +359,8 @@ private void testAllGetMetadataEntry(String tableName, String resourcePath) assertThat(metadataEntry.getOriginalPartitionColumns()).containsOnly("age"); MetadataEntry.Format format = metadataEntry.getFormat(); - assertEquals(format.getOptions().keySet().size(), 0); - assertEquals(format.getProvider(), "parquet"); + assertThat(format.getOptions().keySet().size()).isEqualTo(0); + assertThat(format.getProvider()).isEqualTo("parquet"); } @Test @@ -390,7 +385,7 @@ private void testAllGetActiveAddEntries(String tableName, String resourcePath) .map(AddFileEntry::getPath) .collect(Collectors.toSet()); - assertEquals(paths, EXPECTED_ADD_FILE_PATHS); + assertThat(paths).isEqualTo(EXPECTED_ADD_FILE_PATHS); } @Test @@ -412,7 +407,7 @@ private void testAllGetRemoveEntries(String tableName, String resourcePath) Set removedPaths = removeEntries.map(RemoveFileEntry::getPath).collect(Collectors.toSet()); Set expectedPaths = EXPECTED_REMOVE_ENTRIES.stream().map(RemoveFileEntry::getPath).collect(Collectors.toSet()); - assertEquals(removedPaths, expectedPaths); + assertThat(removedPaths).isEqualTo(expectedPaths); } } @@ -433,9 +428,9 @@ private void testAllGetProtocolEntries(String tableName, String resourcePath) try (Stream protocolEntryStream = transactionLogAccess.getProtocolEntries(tableSnapshot, SESSION)) { List protocolEntries = protocolEntryStream.toList(); - assertEquals(protocolEntries.size(), 1); - assertEquals(protocolEntries.get(0).getMinReaderVersion(), 1); - assertEquals(protocolEntries.get(0).getMinWriterVersion(), 2); + assertThat(protocolEntries.size()).isEqualTo(1); + assertThat(protocolEntries.get(0).getMinReaderVersion()).isEqualTo(1); + assertThat(protocolEntries.get(0).getMinWriterVersion()).isEqualTo(2); } } @@ -458,12 +453,12 @@ public void testMetadataCacheUpdates() Files.copy(resourceDir.resolve(LAST_CHECKPOINT_FILENAME), new File(transactionLogDir, LAST_CHECKPOINT_FILENAME).toPath()); setupTransactionLogAccess(tableName, tableDir.toURI().toString()); - assertEquals(tableSnapshot.getVersion(), 11L); + assertThat(tableSnapshot.getVersion()).isEqualTo(11L); String lastTransactionName = format("%020d.json", 12); Files.copy(resourceDir.resolve(lastTransactionName), new File(transactionLogDir, lastTransactionName).toPath()); TableSnapshot updatedSnapshot = transactionLogAccess.loadSnapshot(SESSION, new SchemaTableName("schema", tableName), tableDir.toURI().toString()); - assertEquals(updatedSnapshot.getVersion(), 12); + assertThat(updatedSnapshot.getVersion()).isEqualTo(12); } @Test @@ -646,32 +641,32 @@ public void testSnapshotsAreConsistent() List allDataFiles = transactionLogAccess.getActiveFiles(updatedTableSnapshot, metadataEntry, protocolEntry, SESSION); List dataFilesWithFixedVersion = transactionLogAccess.getActiveFiles(tableSnapshot, metadataEntry, protocolEntry, SESSION); for (String newFilePath : newDataFiles) { - assertTrue(allDataFiles.stream().anyMatch(entry -> entry.getPath().equals(newFilePath))); - assertTrue(dataFilesWithFixedVersion.stream().noneMatch(entry -> entry.getPath().equals(newFilePath))); + assertThat(allDataFiles.stream().anyMatch(entry -> entry.getPath().equals(newFilePath))).isTrue(); + assertThat(dataFilesWithFixedVersion.stream().noneMatch(entry -> entry.getPath().equals(newFilePath))).isTrue(); } - assertEquals(expectedDataFiles.size(), dataFilesWithFixedVersion.size()); + assertThat(expectedDataFiles.size()).isEqualTo(dataFilesWithFixedVersion.size()); List columns = extractColumnMetadata(transactionLogAccess.getMetadataEntry(tableSnapshot, SESSION), transactionLogAccess.getProtocolEntry(SESSION, tableSnapshot), TESTING_TYPE_MANAGER); for (int i = 0; i < expectedDataFiles.size(); i++) { AddFileEntry expected = expectedDataFiles.get(i); AddFileEntry actual = dataFilesWithFixedVersion.get(i); - assertEquals(expected.getPath(), actual.getPath()); - assertEquals(expected.getPartitionValues(), actual.getPartitionValues()); - assertEquals(expected.getSize(), actual.getSize()); - assertEquals(expected.getModificationTime(), actual.getModificationTime()); - assertEquals(expected.isDataChange(), actual.isDataChange()); - assertEquals(expected.getTags(), actual.getTags()); + assertThat(expected.getPath()).isEqualTo(actual.getPath()); + assertThat(expected.getPartitionValues()).isEqualTo(actual.getPartitionValues()); + assertThat(expected.getSize()).isEqualTo(actual.getSize()); + assertThat(expected.getModificationTime()).isEqualTo(actual.getModificationTime()); + assertThat(expected.isDataChange()).isEqualTo(actual.isDataChange()); + assertThat(expected.getTags()).isEqualTo(actual.getTags()); - assertTrue(expected.getStats().isPresent()); - assertTrue(actual.getStats().isPresent()); + assertThat(expected.getStats().isPresent()).isTrue(); + assertThat(actual.getStats().isPresent()).isTrue(); for (ColumnMetadata column : columns) { DeltaLakeColumnHandle columnHandle = new DeltaLakeColumnHandle(column.getName(), column.getType(), OptionalInt.empty(), column.getName(), column.getType(), REGULAR, Optional.empty()); - assertEquals(expected.getStats().get().getMinColumnValue(columnHandle), actual.getStats().get().getMinColumnValue(columnHandle)); - assertEquals(expected.getStats().get().getMaxColumnValue(columnHandle), actual.getStats().get().getMaxColumnValue(columnHandle)); - assertEquals(expected.getStats().get().getNullCount(columnHandle.getBaseColumnName()), actual.getStats().get().getNullCount(columnHandle.getBaseColumnName())); - assertEquals(expected.getStats().get().getNumRecords(), actual.getStats().get().getNumRecords()); + assertThat(expected.getStats().get().getMinColumnValue(columnHandle)).isEqualTo(actual.getStats().get().getMinColumnValue(columnHandle)); + assertThat(expected.getStats().get().getMaxColumnValue(columnHandle)).isEqualTo(actual.getStats().get().getMaxColumnValue(columnHandle)); + assertThat(expected.getStats().get().getNullCount(columnHandle.getBaseColumnName())).isEqualTo(actual.getStats().get().getNullCount(columnHandle.getBaseColumnName())); + assertThat(expected.getStats().get().getNumRecords()).isEqualTo(actual.getStats().get().getNumRecords()); } } } @@ -693,15 +688,15 @@ public void testAddNewTransactionLogs() copyTransactionLogEntry(0, 1, resourceDir, transactionLogDir); setupTransactionLogAccess(tableName, tableLocation); - assertEquals(tableSnapshot.getVersion(), 0L); + assertThat(tableSnapshot.getVersion()).isEqualTo(0L); copyTransactionLogEntry(1, 2, resourceDir, transactionLogDir); TableSnapshot firstUpdate = transactionLogAccess.loadSnapshot(SESSION, schemaTableName, tableLocation); - assertEquals(firstUpdate.getVersion(), 1L); + assertThat(firstUpdate.getVersion()).isEqualTo(1L); copyTransactionLogEntry(2, 3, resourceDir, transactionLogDir); TableSnapshot secondUpdate = transactionLogAccess.loadSnapshot(SESSION, schemaTableName, tableLocation); - assertEquals(secondUpdate.getVersion(), 2L); + assertThat(secondUpdate.getVersion()).isEqualTo(2L); } @Test @@ -741,13 +736,9 @@ public void testParquetStructStatistics() for (String columnName : statsValues.keySet()) { // Types would need to be specified properly if stats were being read from JSON but are not can be ignored when reading parsed stats from parquet, // so it is safe to use INTEGER as a placeholder - assertEquals( - fileStats.getMinColumnValue(new DeltaLakeColumnHandle(columnName, IntegerType.INTEGER, OptionalInt.empty(), columnName, IntegerType.INTEGER, REGULAR, Optional.empty())), - Optional.of(statsValues.get(columnName))); + assertThat(fileStats.getMinColumnValue(new DeltaLakeColumnHandle(columnName, IntegerType.INTEGER, OptionalInt.empty(), columnName, IntegerType.INTEGER, REGULAR, Optional.empty()))).isEqualTo(Optional.of(statsValues.get(columnName))); - assertEquals( - fileStats.getMaxColumnValue(new DeltaLakeColumnHandle(columnName, IntegerType.INTEGER, OptionalInt.empty(), columnName, IntegerType.INTEGER, REGULAR, Optional.empty())), - Optional.of(statsValues.get(columnName))); + assertThat(fileStats.getMaxColumnValue(new DeltaLakeColumnHandle(columnName, IntegerType.INTEGER, OptionalInt.empty(), columnName, IntegerType.INTEGER, REGULAR, Optional.empty()))).isEqualTo(Optional.of(statsValues.get(columnName))); } } @@ -803,7 +794,7 @@ public void testTableSnapshotsActiveDataFilesCache() () -> { setupTransactionLogAccess(tableName, tableDir, shortLivedActiveDataFilesCacheConfig); List addFileEntries = transactionLogAccess.getActiveFiles(tableSnapshot, metadataEntry, protocolEntry, SESSION); - assertEquals(addFileEntries.size(), 12); + assertThat(addFileEntries.size()).isEqualTo(12); }, ImmutableMultiset.builder() .add(new FileOperation("_last_checkpoint", INPUT_FILE_NEW_STREAM)) @@ -819,7 +810,7 @@ public void testTableSnapshotsActiveDataFilesCache() assertFileSystemAccesses( () -> { List addFileEntries = transactionLogAccess.getActiveFiles(tableSnapshot, metadataEntry, protocolEntry, SESSION); - assertEquals(addFileEntries.size(), 12); + assertThat(addFileEntries.size()).isEqualTo(12); }, ImmutableMultiset.of()); } @@ -841,7 +832,7 @@ public void testFlushSnapshotAndActiveFileCache() () -> { setupTransactionLogAccess(tableName, tableDir, shortLivedActiveDataFilesCacheConfig); List addFileEntries = transactionLogAccess.getActiveFiles(tableSnapshot, metadataEntry, protocolEntry, SESSION); - assertEquals(addFileEntries.size(), 12); + assertThat(addFileEntries.size()).isEqualTo(12); }, ImmutableMultiset.builder() .add(new FileOperation("_last_checkpoint", INPUT_FILE_NEW_STREAM)) @@ -859,7 +850,7 @@ public void testFlushSnapshotAndActiveFileCache() () -> { transactionLogAccess.loadSnapshot(SESSION, new SchemaTableName("schema", tableName), tableDir); List addFileEntries = transactionLogAccess.getActiveFiles(tableSnapshot, metadataEntry, protocolEntry, SESSION); - assertEquals(addFileEntries.size(), 12); + assertThat(addFileEntries.size()).isEqualTo(12); }, ImmutableMultiset.builder() .add(new FileOperation("_last_checkpoint", INPUT_FILE_NEW_STREAM)) @@ -889,7 +880,7 @@ public void testTableSnapshotsActiveDataFilesCacheDisabled() () -> { setupTransactionLogAccess(tableName, tableDir, shortLivedActiveDataFilesCacheConfig); List addFileEntries = transactionLogAccess.getActiveFiles(tableSnapshot, metadataEntry, protocolEntry, SESSION); - assertEquals(addFileEntries.size(), 12); + assertThat(addFileEntries.size()).isEqualTo(12); }, ImmutableMultiset.builder() .add(new FileOperation("_last_checkpoint", INPUT_FILE_NEW_STREAM)) @@ -906,7 +897,7 @@ public void testTableSnapshotsActiveDataFilesCacheDisabled() assertFileSystemAccesses( () -> { List addFileEntries = transactionLogAccess.getActiveFiles(tableSnapshot, metadataEntry, protocolEntry, SESSION); - assertEquals(addFileEntries.size(), 12); + assertThat(addFileEntries.size()).isEqualTo(12); }, ImmutableMultiset.builder() .addCopies(new FileOperation("00000000000000000010.checkpoint.parquet", INPUT_FILE_GET_LENGTH), 2) // TODO (https://github.com/trinodb/trino/issues/16775) why not e.g. once? diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/expression/TestSparkExpressionParser.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/expression/TestSparkExpressionParser.java index 7e4299e6ab9a..54bf271892c7 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/expression/TestSparkExpressionParser.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/expression/TestSparkExpressionParser.java @@ -18,8 +18,8 @@ import org.junit.jupiter.api.Test; import static io.trino.plugin.deltalake.expression.SparkExpressionParser.createExpression; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestSparkExpressionParser { @@ -55,12 +55,12 @@ public void testUnsupportedStringLiteral() @Test public void testArithmeticBinary() { - assertEquals(createExpression("a + b * c"), new ArithmeticBinaryExpression( + assertThat(createExpression("a + b * c")).isEqualTo(new ArithmeticBinaryExpression( Operator.ADD, new Identifier("a"), new ArithmeticBinaryExpression(Operator.MULTIPLY, new Identifier("b"), new Identifier("c")))); - assertEquals(createExpression("a * b + c"), new ArithmeticBinaryExpression( + assertThat(createExpression("a * b + c")).isEqualTo(new ArithmeticBinaryExpression( Operator.ADD, new ArithmeticBinaryExpression(Operator.MULTIPLY, new Identifier("a"), new Identifier("b")), new Identifier("c"))); @@ -69,7 +69,7 @@ public void testArithmeticBinary() private static void assertStringLiteral(String sparkExpression, String expected) { SparkExpression expression = createExpression(sparkExpression); - assertEquals(expression, new StringLiteral(expected)); + assertThat(expression).isEqualTo(new StringLiteral(expected)); } private static void assertParseFailure(String sparkExpression, String reason) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/expression/TestSparkExpressions.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/expression/TestSparkExpressions.java index acffab7a6512..7a5392f84f29 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/expression/TestSparkExpressions.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/expression/TestSparkExpressions.java @@ -16,8 +16,8 @@ import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestSparkExpressions { @@ -208,7 +208,7 @@ public void testUnsupportedCallFunction() private static void assertExpressionTranslates(@Language("SQL") String sparkExpression, @Language("SQL") String trinoExpression) { - assertEquals(toTrinoExpression(sparkExpression), trinoExpression); + assertThat(toTrinoExpression(sparkExpression)).isEqualTo(trinoExpression); } private static String toTrinoExpression(@Language("SQL") String sparkExpression) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeConcurrentModificationGlueMetastore.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeConcurrentModificationGlueMetastore.java index 197be9b65088..9fb30ddd5957 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeConcurrentModificationGlueMetastore.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeConcurrentModificationGlueMetastore.java @@ -46,8 +46,8 @@ import static io.trino.plugin.hive.metastore.glue.TestingGlueHiveMetastore.createTestingAsyncGlueClient; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.testing.TestingSession.testSessionBuilder; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertFalse; @TestInstance(PER_CLASS) public class TestDeltaLakeConcurrentModificationGlueMetastore @@ -115,7 +115,7 @@ public void testDropTableWithConcurrentModifications() failNextGlueDeleteTableCall.set(true); assertUpdate("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); } @AfterAll diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/statistics/TestDeltaLakeFileBasedTableStatisticsProvider.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/statistics/TestDeltaLakeFileBasedTableStatisticsProvider.java index 02aece4602b8..a2ca52ab4e67 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/statistics/TestDeltaLakeFileBasedTableStatisticsProvider.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/statistics/TestDeltaLakeFileBasedTableStatisticsProvider.java @@ -60,7 +60,6 @@ import static java.lang.Double.NEGATIVE_INFINITY; import static java.lang.Double.POSITIVE_INFINITY; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestDeltaLakeFileBasedTableStatisticsProvider { @@ -132,11 +131,11 @@ public void testStatisticsNaN() { DeltaLakeTableHandle tableHandle = registerTable("nan"); TableStatistics stats = getTableStatistics(SESSION, tableHandle); - assertEquals(stats.getRowCount(), Estimate.of(1)); - assertEquals(stats.getColumnStatistics().size(), 1); + assertThat(stats.getRowCount()).isEqualTo(Estimate.of(1)); + assertThat(stats.getColumnStatistics().size()).isEqualTo(1); ColumnStatistics columnStatistics = stats.getColumnStatistics().get(COLUMN_HANDLE); - assertEquals(columnStatistics.getRange(), Optional.empty()); + assertThat(columnStatistics.getRange()).isEqualTo(Optional.empty()); } @Test @@ -145,8 +144,8 @@ public void testStatisticsInf() DeltaLakeTableHandle tableHandle = registerTable("positive_infinity"); TableStatistics stats = getTableStatistics(SESSION, tableHandle); ColumnStatistics columnStatistics = stats.getColumnStatistics().get(COLUMN_HANDLE); - assertEquals(columnStatistics.getRange().get().getMin(), POSITIVE_INFINITY); - assertEquals(columnStatistics.getRange().get().getMax(), POSITIVE_INFINITY); + assertThat(columnStatistics.getRange().get().getMin()).isEqualTo(POSITIVE_INFINITY); + assertThat(columnStatistics.getRange().get().getMax()).isEqualTo(POSITIVE_INFINITY); } @Test @@ -155,8 +154,8 @@ public void testStatisticsNegInf() DeltaLakeTableHandle tableHandle = registerTable("negative_infinity"); TableStatistics stats = getTableStatistics(SESSION, tableHandle); ColumnStatistics columnStatistics = stats.getColumnStatistics().get(COLUMN_HANDLE); - assertEquals(columnStatistics.getRange().get().getMin(), NEGATIVE_INFINITY); - assertEquals(columnStatistics.getRange().get().getMax(), NEGATIVE_INFINITY); + assertThat(columnStatistics.getRange().get().getMin()).isEqualTo(NEGATIVE_INFINITY); + assertThat(columnStatistics.getRange().get().getMax()).isEqualTo(NEGATIVE_INFINITY); } @Test @@ -165,8 +164,8 @@ public void testStatisticsNegZero() DeltaLakeTableHandle tableHandle = registerTable("negative_zero"); TableStatistics stats = getTableStatistics(SESSION, tableHandle); ColumnStatistics columnStatistics = stats.getColumnStatistics().get(COLUMN_HANDLE); - assertEquals(columnStatistics.getRange().get().getMin(), -0.0d); - assertEquals(columnStatistics.getRange().get().getMax(), -0.0d); + assertThat(columnStatistics.getRange().get().getMin()).isEqualTo(-0.0d); + assertThat(columnStatistics.getRange().get().getMax()).isEqualTo(-0.0d); } @Test @@ -176,8 +175,8 @@ public void testStatisticsInfinityAndNaN() DeltaLakeTableHandle tableHandle = registerTable("infinity_nan"); TableStatistics stats = getTableStatistics(SESSION, tableHandle); ColumnStatistics columnStatistics = stats.getColumnStatistics().get(COLUMN_HANDLE); - assertEquals(columnStatistics.getRange().get().getMin(), POSITIVE_INFINITY); - assertEquals(columnStatistics.getRange().get().getMax(), POSITIVE_INFINITY); + assertThat(columnStatistics.getRange().get().getMin()).isEqualTo(POSITIVE_INFINITY); + assertThat(columnStatistics.getRange().get().getMax()).isEqualTo(POSITIVE_INFINITY); } @Test @@ -187,8 +186,8 @@ public void testStatisticsNegativeInfinityAndNaN() DeltaLakeTableHandle tableHandle = registerTable("negative_infinity_nan"); TableStatistics stats = getTableStatistics(SESSION, tableHandle); ColumnStatistics columnStatistics = stats.getColumnStatistics().get(COLUMN_HANDLE); - assertEquals(columnStatistics.getRange().get().getMin(), NEGATIVE_INFINITY); - assertEquals(columnStatistics.getRange().get().getMax(), POSITIVE_INFINITY); + assertThat(columnStatistics.getRange().get().getMin()).isEqualTo(NEGATIVE_INFINITY); + assertThat(columnStatistics.getRange().get().getMax()).isEqualTo(POSITIVE_INFINITY); } @Test @@ -198,8 +197,8 @@ public void testStatisticsZeroAndNaN() DeltaLakeTableHandle tableHandle = registerTable("zero_nan"); TableStatistics stats = getTableStatistics(SESSION, tableHandle); ColumnStatistics columnStatistics = stats.getColumnStatistics().get(COLUMN_HANDLE); - assertEquals(columnStatistics.getRange().get().getMin(), 0.0); - assertEquals(columnStatistics.getRange().get().getMax(), POSITIVE_INFINITY); + assertThat(columnStatistics.getRange().get().getMin()).isEqualTo(0.0); + assertThat(columnStatistics.getRange().get().getMax()).isEqualTo(POSITIVE_INFINITY); } @Test @@ -208,8 +207,8 @@ public void testStatisticsZeroAndInfinity() DeltaLakeTableHandle tableHandle = registerTable("zero_infinity"); TableStatistics stats = getTableStatistics(SESSION, tableHandle); ColumnStatistics columnStatistics = stats.getColumnStatistics().get(COLUMN_HANDLE); - assertEquals(columnStatistics.getRange().get().getMin(), 0.0); - assertEquals(columnStatistics.getRange().get().getMax(), POSITIVE_INFINITY); + assertThat(columnStatistics.getRange().get().getMin()).isEqualTo(0.0); + assertThat(columnStatistics.getRange().get().getMax()).isEqualTo(POSITIVE_INFINITY); } @Test @@ -218,8 +217,8 @@ public void testStatisticsZeroAndNegativeInfinity() DeltaLakeTableHandle tableHandle = registerTable("zero_negative_infinity"); TableStatistics stats = getTableStatistics(SESSION, tableHandle); ColumnStatistics columnStatistics = stats.getColumnStatistics().get(COLUMN_HANDLE); - assertEquals(columnStatistics.getRange().get().getMin(), NEGATIVE_INFINITY); - assertEquals(columnStatistics.getRange().get().getMax(), 0.0); + assertThat(columnStatistics.getRange().get().getMin()).isEqualTo(NEGATIVE_INFINITY); + assertThat(columnStatistics.getRange().get().getMax()).isEqualTo(0.0); } @Test @@ -229,7 +228,7 @@ public void testStatisticsNaNWithMultipleFiles() DeltaLakeTableHandle tableHandle = registerTable("nan_multi_file"); TableStatistics stats = getTableStatistics(SESSION, tableHandle); ColumnStatistics columnStatistics = stats.getColumnStatistics().get(COLUMN_HANDLE); - assertEquals(columnStatistics.getRange(), Optional.empty()); + assertThat(columnStatistics.getRange()).isEqualTo(Optional.empty()); } @Test @@ -238,8 +237,8 @@ public void testStatisticsMultipleFiles() DeltaLakeTableHandle tableHandle = registerTable("basic_multi_file"); TableStatistics stats = getTableStatistics(SESSION, tableHandle); ColumnStatistics columnStatistics = stats.getColumnStatistics().get(COLUMN_HANDLE); - assertEquals(columnStatistics.getRange().get().getMin(), -42.0); - assertEquals(columnStatistics.getRange().get().getMax(), 42.0); + assertThat(columnStatistics.getRange().get().getMin()).isEqualTo(-42.0); + assertThat(columnStatistics.getRange().get().getMax()).isEqualTo(42.0); DeltaLakeTableHandle tableHandleWithUnenforcedConstraint = new DeltaLakeTableHandle( tableHandle.getSchemaName(), @@ -258,8 +257,8 @@ public void testStatisticsMultipleFiles() 0); stats = getTableStatistics(SESSION, tableHandleWithUnenforcedConstraint); columnStatistics = stats.getColumnStatistics().get(COLUMN_HANDLE); - assertEquals(columnStatistics.getRange().get().getMin(), 0.0); - assertEquals(columnStatistics.getRange().get().getMax(), 42.0); + assertThat(columnStatistics.getRange().get().getMin()).isEqualTo(0.0); + assertThat(columnStatistics.getRange().get().getMax()).isEqualTo(42.0); } @Test @@ -303,10 +302,10 @@ public void testStatisticsNoRecords() private void assertEmptyStats(TableStatistics tableStatistics) { - assertEquals(tableStatistics.getRowCount(), Estimate.of(0)); + assertThat(tableStatistics.getRowCount()).isEqualTo(Estimate.of(0)); ColumnStatistics columnStatistics = tableStatistics.getColumnStatistics().get(COLUMN_HANDLE); - assertEquals(columnStatistics.getNullsFraction(), Estimate.of(0)); - assertEquals(columnStatistics.getDistinctValuesCount(), Estimate.of(0)); + assertThat(columnStatistics.getNullsFraction()).isEqualTo(Estimate.of(0)); + assertThat(columnStatistics.getDistinctValuesCount()).isEqualTo(Estimate.of(0)); } @Test @@ -315,53 +314,53 @@ public void testStatisticsParquetParsedStatistics() // The transaction log for this table was created so that the checkpoints only write struct statistics, not json statistics DeltaLakeTableHandle tableHandle = registerTable("parquet_struct_statistics"); TableStatistics stats = getTableStatistics(SESSION, tableHandle); - assertEquals(stats.getRowCount(), Estimate.of(9)); + assertThat(stats.getRowCount()).isEqualTo(Estimate.of(9)); Map statisticsMap = stats.getColumnStatistics(); ColumnStatistics columnStats = statisticsMap.get(new DeltaLakeColumnHandle("dec_short", DecimalType.createDecimalType(5, 1), OptionalInt.empty(), "dec_short", DecimalType.createDecimalType(5, 1), REGULAR, Optional.empty())); - assertEquals(columnStats.getNullsFraction(), Estimate.zero()); - assertEquals(columnStats.getRange().get().getMin(), -10.1); - assertEquals(columnStats.getRange().get().getMax(), 10.1); + assertThat(columnStats.getNullsFraction()).isEqualTo(Estimate.zero()); + assertThat(columnStats.getRange().get().getMin()).isEqualTo(-10.1); + assertThat(columnStats.getRange().get().getMax()).isEqualTo(10.1); columnStats = statisticsMap.get(new DeltaLakeColumnHandle("dec_long", DecimalType.createDecimalType(25, 3), OptionalInt.empty(), "dec_long", DecimalType.createDecimalType(25, 3), REGULAR, Optional.empty())); - assertEquals(columnStats.getNullsFraction(), Estimate.zero()); - assertEquals(columnStats.getRange().get().getMin(), -999999999999.123); - assertEquals(columnStats.getRange().get().getMax(), 999999999999.123); + assertThat(columnStats.getNullsFraction()).isEqualTo(Estimate.zero()); + assertThat(columnStats.getRange().get().getMin()).isEqualTo(-999999999999.123); + assertThat(columnStats.getRange().get().getMax()).isEqualTo(999999999999.123); columnStats = statisticsMap.get(new DeltaLakeColumnHandle("l", BIGINT, OptionalInt.empty(), "l", BIGINT, REGULAR, Optional.empty())); - assertEquals(columnStats.getNullsFraction(), Estimate.zero()); - assertEquals(columnStats.getRange().get().getMin(), -10000000.0); - assertEquals(columnStats.getRange().get().getMax(), 10000000.0); + assertThat(columnStats.getNullsFraction()).isEqualTo(Estimate.zero()); + assertThat(columnStats.getRange().get().getMin()).isEqualTo(-10000000.0); + assertThat(columnStats.getRange().get().getMax()).isEqualTo(10000000.0); columnStats = statisticsMap.get(new DeltaLakeColumnHandle("in", INTEGER, OptionalInt.empty(), "in", INTEGER, REGULAR, Optional.empty())); - assertEquals(columnStats.getNullsFraction(), Estimate.zero()); - assertEquals(columnStats.getRange().get().getMin(), -20000000.0); - assertEquals(columnStats.getRange().get().getMax(), 20000000.0); + assertThat(columnStats.getNullsFraction()).isEqualTo(Estimate.zero()); + assertThat(columnStats.getRange().get().getMin()).isEqualTo(-20000000.0); + assertThat(columnStats.getRange().get().getMax()).isEqualTo(20000000.0); columnStats = statisticsMap.get(new DeltaLakeColumnHandle("sh", SMALLINT, OptionalInt.empty(), "sh", SMALLINT, REGULAR, Optional.empty())); - assertEquals(columnStats.getNullsFraction(), Estimate.zero()); - assertEquals(columnStats.getRange().get().getMin(), -123.0); - assertEquals(columnStats.getRange().get().getMax(), 123.0); + assertThat(columnStats.getNullsFraction()).isEqualTo(Estimate.zero()); + assertThat(columnStats.getRange().get().getMin()).isEqualTo(-123.0); + assertThat(columnStats.getRange().get().getMax()).isEqualTo(123.0); columnStats = statisticsMap.get(new DeltaLakeColumnHandle("byt", TINYINT, OptionalInt.empty(), "byt", TINYINT, REGULAR, Optional.empty())); - assertEquals(columnStats.getNullsFraction(), Estimate.zero()); - assertEquals(columnStats.getRange().get().getMin(), -42.0); - assertEquals(columnStats.getRange().get().getMax(), 42.0); + assertThat(columnStats.getNullsFraction()).isEqualTo(Estimate.zero()); + assertThat(columnStats.getRange().get().getMin()).isEqualTo(-42.0); + assertThat(columnStats.getRange().get().getMax()).isEqualTo(42.0); columnStats = statisticsMap.get(new DeltaLakeColumnHandle("fl", REAL, OptionalInt.empty(), "fl", REAL, REGULAR, Optional.empty())); - assertEquals(columnStats.getNullsFraction(), Estimate.zero()); - assertEquals((float) columnStats.getRange().get().getMin(), -0.123f); - assertEquals((float) columnStats.getRange().get().getMax(), 0.123f); + assertThat(columnStats.getNullsFraction()).isEqualTo(Estimate.zero()); + assertThat((float) columnStats.getRange().get().getMin()).isEqualTo(-0.123f); + assertThat((float) columnStats.getRange().get().getMax()).isEqualTo(0.123f); columnStats = statisticsMap.get(new DeltaLakeColumnHandle("dou", DOUBLE, OptionalInt.empty(), "dou", DOUBLE, REGULAR, Optional.empty())); - assertEquals(columnStats.getNullsFraction(), Estimate.zero()); - assertEquals(columnStats.getRange().get().getMin(), -0.321); - assertEquals(columnStats.getRange().get().getMax(), 0.321); + assertThat(columnStats.getNullsFraction()).isEqualTo(Estimate.zero()); + assertThat(columnStats.getRange().get().getMin()).isEqualTo(-0.321); + assertThat(columnStats.getRange().get().getMax()).isEqualTo(0.321); columnStats = statisticsMap.get(new DeltaLakeColumnHandle("dat", DATE, OptionalInt.empty(), "dat", DATE, REGULAR, Optional.empty())); - assertEquals(columnStats.getNullsFraction(), Estimate.zero()); - assertEquals(columnStats.getRange().get().getMin(), (double) LocalDate.parse("1900-01-01").toEpochDay()); - assertEquals(columnStats.getRange().get().getMax(), (double) LocalDate.parse("5000-01-01").toEpochDay()); + assertThat(columnStats.getNullsFraction()).isEqualTo(Estimate.zero()); + assertThat(columnStats.getRange().get().getMin()).isEqualTo((double) LocalDate.parse("1900-01-01").toEpochDay()); + assertThat(columnStats.getRange().get().getMax()).isEqualTo((double) LocalDate.parse("5000-01-01").toEpochDay()); } @Test @@ -371,15 +370,15 @@ public void testStatisticsParquetParsedStatisticsNaNValues() // The table has a REAL and DOUBLE columns each with 9 values, one of them being NaN DeltaLakeTableHandle tableHandle = registerTable("parquet_struct_statistics_nan"); TableStatistics stats = getTableStatistics(SESSION, tableHandle); - assertEquals(stats.getRowCount(), Estimate.of(9)); + assertThat(stats.getRowCount()).isEqualTo(Estimate.of(9)); Map statisticsMap = stats.getColumnStatistics(); ColumnStatistics columnStats = statisticsMap.get(new DeltaLakeColumnHandle("fl", REAL, OptionalInt.empty(), "fl", REAL, REGULAR, Optional.empty())); - assertEquals(columnStats.getNullsFraction(), Estimate.zero()); + assertThat(columnStats.getNullsFraction()).isEqualTo(Estimate.zero()); assertThat(columnStats.getRange()).isEmpty(); columnStats = statisticsMap.get(new DeltaLakeColumnHandle("dou", DOUBLE, OptionalInt.empty(), "dou", DOUBLE, REGULAR, Optional.empty())); - assertEquals(columnStats.getNullsFraction(), Estimate.zero()); + assertThat(columnStats.getNullsFraction()).isEqualTo(Estimate.zero()); assertThat(columnStats.getRange()).isEmpty(); } @@ -390,11 +389,11 @@ public void testStatisticsParquetParsedStatisticsNullCount() // The table has one INTEGER column 'i' where 3 of the 9 values are null DeltaLakeTableHandle tableHandle = registerTable("parquet_struct_statistics_null_count"); TableStatistics stats = getTableStatistics(SESSION, tableHandle); - assertEquals(stats.getRowCount(), Estimate.of(9)); + assertThat(stats.getRowCount()).isEqualTo(Estimate.of(9)); Map statisticsMap = stats.getColumnStatistics(); ColumnStatistics columnStats = statisticsMap.get(new DeltaLakeColumnHandle("i", INTEGER, OptionalInt.empty(), "i", INTEGER, REGULAR, Optional.empty())); - assertEquals(columnStats.getNullsFraction(), Estimate.of(3.0 / 9.0)); + assertThat(columnStats.getNullsFraction()).isEqualTo(Estimate.of(3.0 / 9.0)); } @Test @@ -415,9 +414,9 @@ public void testExtendedStatisticsWithDataSize() assertThat(extendedStatistics).isNotEmpty(); Map columnStatistics = extendedStatistics.get().getColumnStatistics(); assertThat(columnStatistics).hasSize(3); - assertEquals(columnStatistics.get("regionkey").getTotalSizeInBytes(), OptionalLong.empty()); - assertEquals(columnStatistics.get("name").getTotalSizeInBytes(), OptionalLong.of(34)); - assertEquals(columnStatistics.get("comment").getTotalSizeInBytes(), OptionalLong.of(330)); + assertThat(columnStatistics.get("regionkey").getTotalSizeInBytes()).isEqualTo(OptionalLong.empty()); + assertThat(columnStatistics.get("name").getTotalSizeInBytes()).isEqualTo(OptionalLong.of(34)); + assertThat(columnStatistics.get("comment").getTotalSizeInBytes()).isEqualTo(OptionalLong.of(330)); } @Test @@ -433,16 +432,16 @@ public void testMergeExtendedStatisticsWithoutAndWithDataSize() Map columnStatisticsWithDataSize = statisticsWithDataSize.get().getColumnStatistics(); DeltaLakeColumnStatistics mergedRegionKey = columnStatisticsWithoutDataSize.get("regionkey").update(columnStatisticsWithDataSize.get("regionkey")); - assertEquals(mergedRegionKey.getTotalSizeInBytes(), OptionalLong.empty()); - assertEquals(mergedRegionKey.getNdvSummary().cardinality(), 5); + assertThat(mergedRegionKey.getTotalSizeInBytes()).isEqualTo(OptionalLong.empty()); + assertThat(mergedRegionKey.getNdvSummary().cardinality()).isEqualTo(5); DeltaLakeColumnStatistics mergedName = columnStatisticsWithoutDataSize.get("name").update(columnStatisticsWithDataSize.get("name")); - assertEquals(mergedName.getTotalSizeInBytes(), OptionalLong.empty()); - assertEquals(mergedName.getNdvSummary().cardinality(), 5); + assertThat(mergedName.getTotalSizeInBytes()).isEqualTo(OptionalLong.empty()); + assertThat(mergedName.getNdvSummary().cardinality()).isEqualTo(5); DeltaLakeColumnStatistics mergedComment = columnStatisticsWithoutDataSize.get("comment").update(columnStatisticsWithDataSize.get("comment")); - assertEquals(mergedComment.getTotalSizeInBytes(), OptionalLong.empty()); - assertEquals(mergedComment.getNdvSummary().cardinality(), 5); + assertThat(mergedComment.getTotalSizeInBytes()).isEqualTo(OptionalLong.empty()); + assertThat(mergedComment.getNdvSummary().cardinality()).isEqualTo(5); } private TableStatistics getTableStatistics(ConnectorSession session, DeltaLakeTableHandle tableHandle) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/TestDeltaLakeParquetStatisticsUtils.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/TestDeltaLakeParquetStatisticsUtils.java index e4fa3b469d45..b88d427a1e82 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/TestDeltaLakeParquetStatisticsUtils.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/TestDeltaLakeParquetStatisticsUtils.java @@ -37,7 +37,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static java.time.ZoneOffset.UTC; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestDeltaLakeParquetStatisticsUtils { @@ -53,12 +53,8 @@ public void testIntegerStatistics() .withNumNulls(10) .build(); - assertEquals( - DeltaLakeParquetStatisticsUtils.jsonEncodeMin(ImmutableMap.of(columnName, Optional.of(stats)), ImmutableMap.of(columnName, IntegerType.INTEGER)), - ImmutableMap.of(columnName, -100)); - assertEquals( - DeltaLakeParquetStatisticsUtils.jsonEncodeMax(ImmutableMap.of(columnName, Optional.of(stats)), ImmutableMap.of(columnName, IntegerType.INTEGER)), - ImmutableMap.of(columnName, 150)); + assertThat(DeltaLakeParquetStatisticsUtils.jsonEncodeMin(ImmutableMap.of(columnName, Optional.of(stats)), ImmutableMap.of(columnName, IntegerType.INTEGER))).isEqualTo(ImmutableMap.of(columnName, -100)); + assertThat(DeltaLakeParquetStatisticsUtils.jsonEncodeMax(ImmutableMap.of(columnName, Optional.of(stats)), ImmutableMap.of(columnName, IntegerType.INTEGER))).isEqualTo(ImmutableMap.of(columnName, 150)); } @Test @@ -72,12 +68,8 @@ public void testStringStatistics() .withNumNulls(1) .build(); - assertEquals( - DeltaLakeParquetStatisticsUtils.jsonEncodeMin(ImmutableMap.of(columnName, Optional.of(stats)), ImmutableMap.of(columnName, createUnboundedVarcharType())), - ImmutableMap.of(columnName, "abc")); - assertEquals( - DeltaLakeParquetStatisticsUtils.jsonEncodeMax(ImmutableMap.of(columnName, Optional.of(stats)), ImmutableMap.of(columnName, createUnboundedVarcharType())), - ImmutableMap.of(columnName, "bac")); + assertThat(DeltaLakeParquetStatisticsUtils.jsonEncodeMin(ImmutableMap.of(columnName, Optional.of(stats)), ImmutableMap.of(columnName, createUnboundedVarcharType()))).isEqualTo(ImmutableMap.of(columnName, "abc")); + assertThat(DeltaLakeParquetStatisticsUtils.jsonEncodeMax(ImmutableMap.of(columnName, Optional.of(stats)), ImmutableMap.of(columnName, createUnboundedVarcharType()))).isEqualTo(ImmutableMap.of(columnName, "bac")); } @Test @@ -91,12 +83,8 @@ public void testFloatStatistics() .withNumNulls(2) .build(); - assertEquals( - DeltaLakeParquetStatisticsUtils.jsonEncodeMin(ImmutableMap.of(columnName, Optional.of(stats)), ImmutableMap.of(columnName, REAL)), - ImmutableMap.of(columnName, 100.0f)); - assertEquals( - DeltaLakeParquetStatisticsUtils.jsonEncodeMax(ImmutableMap.of(columnName, Optional.of(stats)), ImmutableMap.of(columnName, REAL)), - ImmutableMap.of(columnName, 1000.001f)); + assertThat(DeltaLakeParquetStatisticsUtils.jsonEncodeMin(ImmutableMap.of(columnName, Optional.of(stats)), ImmutableMap.of(columnName, REAL))).isEqualTo(ImmutableMap.of(columnName, 100.0f)); + assertThat(DeltaLakeParquetStatisticsUtils.jsonEncodeMax(ImmutableMap.of(columnName, Optional.of(stats)), ImmutableMap.of(columnName, REAL))).isEqualTo(ImmutableMap.of(columnName, 1000.001f)); columnName = "t_double"; type = new PrimitiveType(Type.Repetition.REQUIRED, PrimitiveType.PrimitiveTypeName.DOUBLE, columnName); @@ -106,12 +94,8 @@ public void testFloatStatistics() .withNumNulls(2) .build(); - assertEquals( - DeltaLakeParquetStatisticsUtils.jsonEncodeMin(ImmutableMap.of(columnName, Optional.of(stats)), ImmutableMap.of(columnName, DoubleType.DOUBLE)), - ImmutableMap.of(columnName, 100.0)); - assertEquals( - DeltaLakeParquetStatisticsUtils.jsonEncodeMax(ImmutableMap.of(columnName, Optional.of(stats)), ImmutableMap.of(columnName, DoubleType.DOUBLE)), - ImmutableMap.of(columnName, 1000.001)); + assertThat(DeltaLakeParquetStatisticsUtils.jsonEncodeMin(ImmutableMap.of(columnName, Optional.of(stats)), ImmutableMap.of(columnName, DoubleType.DOUBLE))).isEqualTo(ImmutableMap.of(columnName, 100.0)); + assertThat(DeltaLakeParquetStatisticsUtils.jsonEncodeMax(ImmutableMap.of(columnName, Optional.of(stats)), ImmutableMap.of(columnName, DoubleType.DOUBLE))).isEqualTo(ImmutableMap.of(columnName, 1000.001)); } @Test @@ -125,12 +109,8 @@ public void testDateStatistics() .withNumNulls(2) .build(); - assertEquals( - DeltaLakeParquetStatisticsUtils.jsonEncodeMin(ImmutableMap.of(columnName, Optional.of(stats)), ImmutableMap.of(columnName, DATE)), - ImmutableMap.of(columnName, "2020-08-26")); - assertEquals( - DeltaLakeParquetStatisticsUtils.jsonEncodeMax(ImmutableMap.of(columnName, Optional.of(stats)), ImmutableMap.of(columnName, DATE)), - ImmutableMap.of(columnName, "2020-09-17")); + assertThat(DeltaLakeParquetStatisticsUtils.jsonEncodeMin(ImmutableMap.of(columnName, Optional.of(stats)), ImmutableMap.of(columnName, DATE))).isEqualTo(ImmutableMap.of(columnName, "2020-08-26")); + assertThat(DeltaLakeParquetStatisticsUtils.jsonEncodeMax(ImmutableMap.of(columnName, Optional.of(stats)), ImmutableMap.of(columnName, DATE))).isEqualTo(ImmutableMap.of(columnName, "2020-09-17")); } @Test @@ -144,12 +124,8 @@ public void testTimestampStatisticsHighPrecision() .withNumNulls(2) .build(); - assertEquals( - DeltaLakeParquetStatisticsUtils.jsonEncodeMin(ImmutableMap.of(columnName, Optional.of(stats)), ImmutableMap.of(columnName, TIMESTAMP_TZ_MILLIS)), - ImmutableMap.of(columnName, "2020-08-26T01:02:03.123Z")); - assertEquals( - DeltaLakeParquetStatisticsUtils.jsonEncodeMax(ImmutableMap.of(columnName, Optional.of(stats)), ImmutableMap.of(columnName, TIMESTAMP_TZ_MILLIS)), - ImmutableMap.of(columnName, "2020-08-26T01:02:03.124Z")); + assertThat(DeltaLakeParquetStatisticsUtils.jsonEncodeMin(ImmutableMap.of(columnName, Optional.of(stats)), ImmutableMap.of(columnName, TIMESTAMP_TZ_MILLIS))).isEqualTo(ImmutableMap.of(columnName, "2020-08-26T01:02:03.123Z")); + assertThat(DeltaLakeParquetStatisticsUtils.jsonEncodeMax(ImmutableMap.of(columnName, Optional.of(stats)), ImmutableMap.of(columnName, TIMESTAMP_TZ_MILLIS))).isEqualTo(ImmutableMap.of(columnName, "2020-08-26T01:02:03.124Z")); } @Test @@ -163,12 +139,8 @@ public void testTimestampStatisticsMillisPrecision() .withNumNulls(2) .build(); - assertEquals( - DeltaLakeParquetStatisticsUtils.jsonEncodeMin(ImmutableMap.of(columnName, Optional.of(stats)), ImmutableMap.of(columnName, TIMESTAMP_TZ_MILLIS)), - ImmutableMap.of(columnName, "2020-08-26T01:02:03.123Z")); - assertEquals( - DeltaLakeParquetStatisticsUtils.jsonEncodeMax(ImmutableMap.of(columnName, Optional.of(stats)), ImmutableMap.of(columnName, TIMESTAMP_TZ_MILLIS)), - ImmutableMap.of(columnName, "2020-08-26T01:02:03.123Z")); + assertThat(DeltaLakeParquetStatisticsUtils.jsonEncodeMin(ImmutableMap.of(columnName, Optional.of(stats)), ImmutableMap.of(columnName, TIMESTAMP_TZ_MILLIS))).isEqualTo(ImmutableMap.of(columnName, "2020-08-26T01:02:03.123Z")); + assertThat(DeltaLakeParquetStatisticsUtils.jsonEncodeMax(ImmutableMap.of(columnName, Optional.of(stats)), ImmutableMap.of(columnName, TIMESTAMP_TZ_MILLIS))).isEqualTo(ImmutableMap.of(columnName, "2020-08-26T01:02:03.123Z")); } private static byte[] toParquetEncoding(LocalDateTime time) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/TestDeltaLakeSchemaSupport.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/TestDeltaLakeSchemaSupport.java index f0924f859ed5..6c41a669b538 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/TestDeltaLakeSchemaSupport.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/TestDeltaLakeSchemaSupport.java @@ -68,7 +68,6 @@ import static io.trino.type.IntervalYearMonthType.INTERVAL_YEAR_MONTH; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; -import static org.testng.Assert.assertEquals; public class TestDeltaLakeSchemaSupport { @@ -114,8 +113,8 @@ private void testSinglePrimitiveFieldSchema(String json, ColumnMetadata metadata List schema = DeltaLakeSchemaSupport.getColumnMetadata(json, typeManager, ColumnMappingMode.NONE).stream() .map(DeltaLakeColumnMetadata::getColumnMetadata) .collect(toImmutableList()); - assertEquals(schema.size(), 1); - assertEquals(schema.get(0), metadata); + assertThat(schema.size()).isEqualTo(1); + assertThat(schema.get(0)).isEqualTo(metadata); } // |-- a: integer (nullable = false) @@ -144,26 +143,25 @@ public void testComplexSchema() List schema = DeltaLakeSchemaSupport.getColumnMetadata(json, typeManager, ColumnMappingMode.NONE).stream() .map(DeltaLakeColumnMetadata::getColumnMetadata) .collect(toImmutableList()); - assertEquals(schema.size(), 5); + assertThat(schema.size()).isEqualTo(5); // asserting on the string representations, since they're more readable - assertEquals(schema.get(0).toString(), "ColumnMetadata{name='a', type=integer, nullable}"); - assertEquals(schema.get(1).toString(), "ColumnMetadata{name='b', type=row(b1 integer, b2 row(b21 varchar, b22 boolean)), nullable}"); - assertEquals(schema.get(2).toString(), "ColumnMetadata{name='c', type=array(integer), nullable}"); - assertEquals(schema.get(3).toString(), "ColumnMetadata{name='d', type=array(row(d1 integer)), nullable}"); - assertEquals(schema.get(4).toString(), "ColumnMetadata{name='e', type=map(varchar, row(e1 date, e2 timestamp(3) with time zone)), nullable}"); + assertThat(schema.get(0).toString()).isEqualTo("ColumnMetadata{name='a', type=integer, nullable}"); + assertThat(schema.get(1).toString()).isEqualTo("ColumnMetadata{name='b', type=row(b1 integer, b2 row(b21 varchar, b22 boolean)), nullable}"); + assertThat(schema.get(2).toString()).isEqualTo("ColumnMetadata{name='c', type=array(integer), nullable}"); + assertThat(schema.get(3).toString()).isEqualTo("ColumnMetadata{name='d', type=array(row(d1 integer)), nullable}"); + assertThat(schema.get(4).toString()).isEqualTo("ColumnMetadata{name='e', type=map(varchar, row(e1 date, e2 timestamp(3) with time zone)), nullable}"); } @Test public void testSerializeStatisticsAsJson() throws JsonProcessingException { - assertEquals(serializeStatsAsJson( + assertThat(serializeStatsAsJson( new DeltaLakeJsonFileStatistics( Optional.of(100L), Optional.of(ImmutableMap.of("c", 42)), Optional.of(ImmutableMap.of("c", 51)), - Optional.of(ImmutableMap.of("c", 1L)))), - "{\"numRecords\":100,\"minValues\":{\"c\":42},\"maxValues\":{\"c\":51},\"nullCount\":{\"c\":1}}"); + Optional.of(ImmutableMap.of("c", 1L))))).isEqualTo("{\"numRecords\":100,\"minValues\":{\"c\":42},\"maxValues\":{\"c\":51},\"nullCount\":{\"c\":1}}"); } @Test @@ -179,13 +177,12 @@ public void testSerializeStatisticsWithNullValuesAsJson() minValues.put("c2", 10); maxValues.put("c2", 26); - assertEquals(serializeStatsAsJson( - new DeltaLakeJsonFileStatistics( - Optional.of(1L), - Optional.of(minValues), - Optional.of(maxValues), - Optional.of(ImmutableMap.of("c1", 1L, "c2", 0L)))), - "{\"numRecords\":1,\"minValues\":{\"c2\":10},\"maxValues\":{\"c2\":26},\"nullCount\":{\"c1\":1,\"c2\":0}}"); + assertThat(serializeStatsAsJson( + new DeltaLakeJsonFileStatistics( + Optional.of(1L), + Optional.of(minValues), + Optional.of(maxValues), + Optional.of(ImmutableMap.of("c1", 1L, "c2", 0L))))).isEqualTo("{\"numRecords\":1,\"minValues\":{\"c2\":10},\"maxValues\":{\"c2\":26},\"nullCount\":{\"c1\":1,\"c2\":0}}"); } @Test diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/TestProtocolEntry.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/TestProtocolEntry.java index b5f2c478ca3d..01a9d3788243 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/TestProtocolEntry.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/TestProtocolEntry.java @@ -20,8 +20,8 @@ import java.util.Optional; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestProtocolEntry { @@ -32,15 +32,11 @@ public void testProtocolEntryFromJson() { @Language("JSON") String json = "{\"minReaderVersion\":2,\"minWriterVersion\":5}"; - assertEquals( - codec.fromJson(json), - new ProtocolEntry(2, 5, Optional.empty(), Optional.empty())); + assertThat(codec.fromJson(json)).isEqualTo(new ProtocolEntry(2, 5, Optional.empty(), Optional.empty())); @Language("JSON") String jsonWithFeatures = "{\"minReaderVersion\":3,\"minWriterVersion\":7,\"readerFeatures\":[\"deletionVectors\"],\"writerFeatures\":[\"timestampNTZ\"]}"; - assertEquals( - codec.fromJson(jsonWithFeatures), - new ProtocolEntry(3, 7, Optional.of(ImmutableSet.of("deletionVectors")), Optional.of(ImmutableSet.of("timestampNTZ")))); + assertThat(codec.fromJson(jsonWithFeatures)).isEqualTo(new ProtocolEntry(3, 7, Optional.of(ImmutableSet.of("deletionVectors")), Optional.of(ImmutableSet.of("timestampNTZ")))); } @Test @@ -62,22 +58,18 @@ public void testInvalidProtocolEntryFromJson() @Test public void testProtocolEntryToJson() { - assertEquals( - codec.toJson(new ProtocolEntry(2, 5, Optional.empty(), Optional.empty())), - """ - { - "minReaderVersion" : 2, - "minWriterVersion" : 5 - }"""); + assertThat(codec.toJson(new ProtocolEntry(2, 5, Optional.empty(), Optional.empty()))).isEqualTo(""" + { + "minReaderVersion" : 2, + "minWriterVersion" : 5 + }"""); - assertEquals( - codec.toJson(new ProtocolEntry(3, 7, Optional.of(ImmutableSet.of("deletionVectors")), Optional.of(ImmutableSet.of("timestampNTZ")))), - """ - { - "minReaderVersion" : 3, - "minWriterVersion" : 7, - "readerFeatures" : [ "deletionVectors" ], - "writerFeatures" : [ "timestampNTZ" ] - }"""); + assertThat(codec.toJson(new ProtocolEntry(3, 7, Optional.of(ImmutableSet.of("deletionVectors")), Optional.of(ImmutableSet.of("timestampNTZ"))))).isEqualTo(""" + { + "minReaderVersion" : 3, + "minWriterVersion" : 7, + "readerFeatures" : [ "deletionVectors" ], + "writerFeatures" : [ "timestampNTZ" ] + }"""); } } diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/TestTableSnapshot.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/TestTableSnapshot.java index 3b6186fe7654..b7301d812581 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/TestTableSnapshot.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/TestTableSnapshot.java @@ -59,7 +59,6 @@ import static java.util.stream.Collectors.toCollection; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; -import static org.testng.Assert.assertEquals; @TestInstance(PER_METHOD) // e.g. TrackingFileSystemFactory is shared mutable state public class TestTableSnapshot @@ -240,7 +239,7 @@ public void testMaxTransactionId() parquetReaderOptions, true, domainCompactionThreshold); - assertEquals(tableSnapshot.getVersion(), 13L); + assertThat(tableSnapshot.getVersion()).isEqualTo(13L); } private void assertFileSystemAccesses(ThrowingRunnable callback, Multiset expectedAccesses) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/TestTransactionLogParser.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/TestTransactionLogParser.java index 0e35b212be84..aba6dae2167e 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/TestTransactionLogParser.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/TestTransactionLogParser.java @@ -23,7 +23,7 @@ import static io.trino.plugin.deltalake.transactionlog.TransactionLogParser.getMandatoryCurrentVersion; import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_STATS; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestTransactionLogParser { @@ -35,8 +35,8 @@ public void testGetCurrentVersion() String basePath = getClass().getClassLoader().getResource("databricks73").toURI().toString(); - assertEquals(getMandatoryCurrentVersion(fileSystem, appendPath(basePath, "simple_table_without_checkpoint")), 9); - assertEquals(getMandatoryCurrentVersion(fileSystem, appendPath(basePath, "simple_table_ending_on_checkpoint")), 10); - assertEquals(getMandatoryCurrentVersion(fileSystem, appendPath(basePath, "simple_table_past_checkpoint")), 11); + assertThat(getMandatoryCurrentVersion(fileSystem, appendPath(basePath, "simple_table_without_checkpoint"))).isEqualTo(9); + assertThat(getMandatoryCurrentVersion(fileSystem, appendPath(basePath, "simple_table_ending_on_checkpoint"))).isEqualTo(10); + assertThat(getMandatoryCurrentVersion(fileSystem, appendPath(basePath, "simple_table_past_checkpoint"))).isEqualTo(11); } } diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointBuilder.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointBuilder.java index 292b816be1a4..74bc61dcf8a6 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointBuilder.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointBuilder.java @@ -30,7 +30,7 @@ import static io.trino.plugin.deltalake.transactionlog.DeltaLakeTransactionLogEntry.protocolEntry; import static io.trino.plugin.deltalake.transactionlog.DeltaLakeTransactionLogEntry.removeFileEntry; import static io.trino.plugin.deltalake.transactionlog.DeltaLakeTransactionLogEntry.transactionEntry; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestCheckpointBuilder { @@ -77,6 +77,6 @@ public void testCheckpointBuilder() Set.of(app1TransactionV3, app2TransactionV5), Set.of(addA2), Set.of(removeB, removeC)); - assertEquals(expectedCheckpoint, builder.build()); + assertThat(expectedCheckpoint).isEqualTo(builder.build()); } } diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointWriter.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointWriter.java index c17f72519d4e..c78b09b42304 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointWriter.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointWriter.java @@ -67,7 +67,6 @@ import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static io.trino.util.DateTimeUtils.parseDate; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestCheckpointWriter { @@ -193,13 +192,11 @@ public void testCheckpointWriteReadJsonRoundtrip() writer.write(entries, createOutputFile(targetPath)); CheckpointEntries readEntries = readCheckpoint(targetPath, metadataEntry, protocolEntry, true); - assertEquals(readEntries.getTransactionEntries(), entries.getTransactionEntries()); - assertEquals(readEntries.getRemoveFileEntries(), entries.getRemoveFileEntries()); - assertEquals(readEntries.getMetadataEntry(), entries.getMetadataEntry()); - assertEquals(readEntries.getProtocolEntry(), entries.getProtocolEntry()); - assertEquals( - readEntries.getAddFileEntries().stream().map(this::makeComparable).collect(toImmutableSet()), - entries.getAddFileEntries().stream().map(this::makeComparable).collect(toImmutableSet())); + assertThat(readEntries.getTransactionEntries()).isEqualTo(entries.getTransactionEntries()); + assertThat(readEntries.getRemoveFileEntries()).isEqualTo(entries.getRemoveFileEntries()); + assertThat(readEntries.getMetadataEntry()).isEqualTo(entries.getMetadataEntry()); + assertThat(readEntries.getProtocolEntry()).isEqualTo(entries.getProtocolEntry()); + assertThat(readEntries.getAddFileEntries().stream().map(this::makeComparable).collect(toImmutableSet())).isEqualTo(entries.getAddFileEntries().stream().map(this::makeComparable).collect(toImmutableSet())); } @Test @@ -330,13 +327,11 @@ public void testCheckpointWriteReadParquetStatisticsRoundtrip() writer.write(entries, createOutputFile(targetPath)); CheckpointEntries readEntries = readCheckpoint(targetPath, metadataEntry, protocolEntry, true); - assertEquals(readEntries.getTransactionEntries(), entries.getTransactionEntries()); - assertEquals(readEntries.getRemoveFileEntries(), entries.getRemoveFileEntries()); - assertEquals(readEntries.getMetadataEntry(), entries.getMetadataEntry()); - assertEquals(readEntries.getProtocolEntry(), entries.getProtocolEntry()); - assertEquals( - readEntries.getAddFileEntries().stream().map(this::makeComparable).collect(toImmutableSet()), - entries.getAddFileEntries().stream().map(this::makeComparable).collect(toImmutableSet())); + assertThat(readEntries.getTransactionEntries()).isEqualTo(entries.getTransactionEntries()); + assertThat(readEntries.getRemoveFileEntries()).isEqualTo(entries.getRemoveFileEntries()); + assertThat(readEntries.getMetadataEntry()).isEqualTo(entries.getMetadataEntry()); + assertThat(readEntries.getProtocolEntry()).isEqualTo(entries.getProtocolEntry()); + assertThat(readEntries.getAddFileEntries().stream().map(this::makeComparable).collect(toImmutableSet())).isEqualTo(entries.getAddFileEntries().stream().map(this::makeComparable).collect(toImmutableSet())); } @Test diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestTransactionLogTail.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestTransactionLogTail.java index 7d005a9928b8..37407458abd2 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestTransactionLogTail.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestTransactionLogTail.java @@ -25,8 +25,7 @@ import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_STATS; import static java.lang.String.format; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestTransactionLogTail { @@ -42,8 +41,8 @@ private void testTail(String dataSource) throws Exception { String tableLocation = getClass().getClassLoader().getResource(format("%s/person", dataSource)).toURI().toString(); - assertEquals(readJsonTransactionLogTails(tableLocation).size(), 7); - assertEquals(updateJsonTransactionLogTails(tableLocation).size(), 7); + assertThat(readJsonTransactionLogTails(tableLocation).size()).isEqualTo(7); + assertThat(updateJsonTransactionLogTails(tableLocation).size()).isEqualTo(7); } private List updateJsonTransactionLogTails(String tableLocation) @@ -52,7 +51,7 @@ private List updateJsonTransactionLogTails(String TrinoFileSystem fileSystem = new HdfsFileSystemFactory(HDFS_ENVIRONMENT, HDFS_FILE_SYSTEM_STATS).create(SESSION); TransactionLogTail transactionLogTail = TransactionLogTail.loadNewTail(fileSystem, tableLocation, Optional.of(10L), Optional.of(12L)); Optional updatedLogTail = transactionLogTail.getUpdatedTail(fileSystem, tableLocation, Optional.empty()); - assertTrue(updatedLogTail.isPresent()); + assertThat(updatedLogTail.isPresent()).isTrue(); return updatedLogTail.get().getFileEntries(); } diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/statistics/TestDeltaLakeFileStatistics.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/statistics/TestDeltaLakeFileStatistics.java index 9145e2d112c9..7d38c1271e53 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/statistics/TestDeltaLakeFileStatistics.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/statistics/TestDeltaLakeFileStatistics.java @@ -67,9 +67,6 @@ import static java.lang.Float.floatToIntBits; import static java.time.ZoneOffset.UTC; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; public class TestDeltaLakeFileStatistics { @@ -141,66 +138,34 @@ public void testParseParquetStatistics() while (checkpointEntryIterator.hasNext()) { DeltaLakeTransactionLogEntry entry = checkpointEntryIterator.next(); if (entry.getAdd() != null && entry.getAdd().getPath().contains("part-00000-17951bea-0d04-43c1-979c-ea1fac19b382-c000.snappy.parquet")) { - assertNull(matchingAddFileEntry); + assertThat(matchingAddFileEntry).isNull(); matchingAddFileEntry = entry; } } - assertNotNull(matchingAddFileEntry); + assertThat(matchingAddFileEntry).isNotNull(); assertThat(matchingAddFileEntry.getAdd().getStats()).isPresent(); testStatisticsValues(matchingAddFileEntry.getAdd().getStats().get()); } private static void testStatisticsValues(DeltaLakeFileStatistics fileStatistics) { - assertEquals(fileStatistics.getNumRecords(), Optional.of(1L)); - assertEquals( - fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("byt", TINYINT, OptionalInt.empty(), "byt", TINYINT, REGULAR, Optional.empty())), - Optional.of(42L)); - assertEquals( - fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("dat", DATE, OptionalInt.empty(), "dat", DATE, REGULAR, Optional.empty())), - Optional.of(LocalDate.parse("5000-01-01").toEpochDay())); - assertEquals( - fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("dec_long", DecimalType.createDecimalType(25, 3), OptionalInt.empty(), "dec_long", DecimalType.createDecimalType(25, 3), REGULAR, Optional.empty())), - Optional.of(encodeScaledValue(new BigDecimal("999999999999.123"), 3))); - assertEquals( - fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("dec_short", DecimalType.createDecimalType(5, 1), OptionalInt.empty(), "dec_short", DecimalType.createDecimalType(5, 1), REGULAR, Optional.empty())), - Optional.of(new BigDecimal("10.1").unscaledValue().longValueExact())); - assertEquals( - fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("dou", DoubleType.DOUBLE, OptionalInt.empty(), "dou", DoubleType.DOUBLE, REGULAR, Optional.empty())), - Optional.of(0.321)); - assertEquals( - fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("fl", REAL, OptionalInt.empty(), "fl", REAL, REGULAR, Optional.empty())), - Optional.of((long) floatToIntBits(0.123f))); - assertEquals( - fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("in", INTEGER, OptionalInt.empty(), "in", INTEGER, REGULAR, Optional.empty())), - Optional.of(20000000L)); - assertEquals( - fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("l", BIGINT, OptionalInt.empty(), "l", BIGINT, REGULAR, Optional.empty())), - Optional.of(10000000L)); + assertThat(fileStatistics.getNumRecords()).isEqualTo(Optional.of(1L)); + assertThat(fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("byt", TINYINT, OptionalInt.empty(), "byt", TINYINT, REGULAR, Optional.empty()))).isEqualTo(Optional.of(42L)); + assertThat(fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("dat", DATE, OptionalInt.empty(), "dat", DATE, REGULAR, Optional.empty()))).isEqualTo(Optional.of(LocalDate.parse("5000-01-01").toEpochDay())); + assertThat(fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("dec_long", DecimalType.createDecimalType(25, 3), OptionalInt.empty(), "dec_long", DecimalType.createDecimalType(25, 3), REGULAR, Optional.empty()))).isEqualTo(Optional.of(encodeScaledValue(new BigDecimal("999999999999.123"), 3))); + assertThat(fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("dec_short", DecimalType.createDecimalType(5, 1), OptionalInt.empty(), "dec_short", DecimalType.createDecimalType(5, 1), REGULAR, Optional.empty()))).isEqualTo(Optional.of(new BigDecimal("10.1").unscaledValue().longValueExact())); + assertThat(fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("dou", DoubleType.DOUBLE, OptionalInt.empty(), "dou", DoubleType.DOUBLE, REGULAR, Optional.empty()))).isEqualTo(Optional.of(0.321)); + assertThat(fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("fl", REAL, OptionalInt.empty(), "fl", REAL, REGULAR, Optional.empty()))).isEqualTo(Optional.of((long) floatToIntBits(0.123f))); + assertThat(fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("in", INTEGER, OptionalInt.empty(), "in", INTEGER, REGULAR, Optional.empty()))).isEqualTo(Optional.of(20000000L)); + assertThat(fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("l", BIGINT, OptionalInt.empty(), "l", BIGINT, REGULAR, Optional.empty()))).isEqualTo(Optional.of(10000000L)); Type rowType = RowType.rowType(RowType.field("s1", INTEGER), RowType.field("s3", VarcharType.createUnboundedVarcharType())); - assertEquals( - fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("row", rowType, OptionalInt.empty(), "row", rowType, REGULAR, Optional.empty())), - Optional.empty()); - assertEquals( - fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("arr", new ArrayType(INTEGER), OptionalInt.empty(), "arr", new ArrayType(INTEGER), REGULAR, Optional.empty())), - Optional.empty()); - assertEquals( - fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("m", new MapType(INTEGER, VarcharType.createUnboundedVarcharType(), new TypeOperators()), OptionalInt.empty(), "m", new MapType(INTEGER, VarcharType.createUnboundedVarcharType(), new TypeOperators()), REGULAR, Optional.empty())), - Optional.empty()); - assertEquals( - fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("sh", SMALLINT, OptionalInt.empty(), "sh", SMALLINT, REGULAR, Optional.empty())), - Optional.of(123L)); - assertEquals( - fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("str", VarcharType.createUnboundedVarcharType(), OptionalInt.empty(), "str", VarcharType.createUnboundedVarcharType(), REGULAR, Optional.empty())), - Optional.of(utf8Slice("a"))); - assertEquals( - fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("ts", TIMESTAMP_TZ_MILLIS, OptionalInt.empty(), "ts", TIMESTAMP_TZ_MILLIS, REGULAR, Optional.empty())), - Optional.of(packDateTimeWithZone(LocalDateTime.parse("2960-10-31T01:00:00.000").toInstant(UTC).toEpochMilli(), UTC_KEY))); - assertEquals( - fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("bool", BOOLEAN, OptionalInt.empty(), "bool", BOOLEAN, REGULAR, Optional.empty())), - Optional.empty()); - assertEquals( - fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("bin", VARBINARY, OptionalInt.empty(), "bin", VARBINARY, REGULAR, Optional.empty())), - Optional.empty()); + assertThat(fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("row", rowType, OptionalInt.empty(), "row", rowType, REGULAR, Optional.empty()))).isEqualTo(Optional.empty()); + assertThat(fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("arr", new ArrayType(INTEGER), OptionalInt.empty(), "arr", new ArrayType(INTEGER), REGULAR, Optional.empty()))).isEqualTo(Optional.empty()); + assertThat(fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("m", new MapType(INTEGER, VarcharType.createUnboundedVarcharType(), new TypeOperators()), OptionalInt.empty(), "m", new MapType(INTEGER, VarcharType.createUnboundedVarcharType(), new TypeOperators()), REGULAR, Optional.empty()))).isEqualTo(Optional.empty()); + assertThat(fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("sh", SMALLINT, OptionalInt.empty(), "sh", SMALLINT, REGULAR, Optional.empty()))).isEqualTo(Optional.of(123L)); + assertThat(fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("str", VarcharType.createUnboundedVarcharType(), OptionalInt.empty(), "str", VarcharType.createUnboundedVarcharType(), REGULAR, Optional.empty()))).isEqualTo(Optional.of(utf8Slice("a"))); + assertThat(fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("ts", TIMESTAMP_TZ_MILLIS, OptionalInt.empty(), "ts", TIMESTAMP_TZ_MILLIS, REGULAR, Optional.empty()))).isEqualTo(Optional.of(packDateTimeWithZone(LocalDateTime.parse("2960-10-31T01:00:00.000").toInstant(UTC).toEpochMilli(), UTC_KEY))); + assertThat(fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("bool", BOOLEAN, OptionalInt.empty(), "bool", BOOLEAN, REGULAR, Optional.empty()))).isEqualTo(Optional.empty()); + assertThat(fileStatistics.getMinColumnValue(new DeltaLakeColumnHandle("bin", VARBINARY, OptionalInt.empty(), "bin", VARBINARY, REGULAR, Optional.empty()))).isEqualTo(Optional.empty()); } } diff --git a/plugin/trino-druid/pom.xml b/plugin/trino-druid/pom.xml index c85fe5d7d2e6..b9256f5ae8d1 100644 --- a/plugin/trino-druid/pom.xml +++ b/plugin/trino-druid/pom.xml @@ -229,33 +229,5 @@ - - - org.testng - testng - test - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - - diff --git a/plugin/trino-druid/src/test/java/io/trino/plugin/druid/TestDruidConnectorTest.java b/plugin/trino-druid/src/test/java/io/trino/plugin/druid/TestDruidConnectorTest.java index 94fce7df9394..8c9cfe4df1ee 100644 --- a/plugin/trino-druid/src/test/java/io/trino/plugin/druid/TestDruidConnectorTest.java +++ b/plugin/trino-druid/src/test/java/io/trino/plugin/druid/TestDruidConnectorTest.java @@ -58,7 +58,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assumptions.abort; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertFalse; @TestInstance(PER_CLASS) public class TestDruidConnectorTest @@ -336,10 +335,10 @@ public void testNativeQuerySelectFromTestTable() public void testNativeQueryCreateStatement() { // override because Druid fails to prepare statement, while other connectors succeed in preparing statement and then fail because of no metadata available - assertFalse(getQueryRunner().tableExists(getSession(), "numbers")); + assertThat(getQueryRunner().tableExists(getSession(), "numbers")).isFalse(); assertThatThrownBy(() -> query("SELECT * FROM TABLE(system.query(query => 'CREATE TABLE numbers(n INTEGER)'))")) .hasMessageContaining("Failed to get table handle for prepared query"); - assertFalse(getQueryRunner().tableExists(getSession(), "numbers")); + assertThat(getQueryRunner().tableExists(getSession(), "numbers")).isFalse(); } @Test diff --git a/plugin/trino-elasticsearch/pom.xml b/plugin/trino-elasticsearch/pom.xml index 7fb5d81fb87b..667fd0e799af 100644 --- a/plugin/trino-elasticsearch/pom.xml +++ b/plugin/trino-elasticsearch/pom.xml @@ -401,12 +401,6 @@ testcontainers test - - - org.testng - testng - test - @@ -439,25 +433,5 @@ - - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - diff --git a/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/BaseElasticsearchConnectorTest.java b/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/BaseElasticsearchConnectorTest.java index 521c4e04fb8a..a9700e9fa1cb 100644 --- a/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/BaseElasticsearchConnectorTest.java +++ b/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/BaseElasticsearchConnectorTest.java @@ -52,9 +52,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) public abstract class BaseElasticsearchConnectorTest @@ -638,7 +635,7 @@ public void testAsRawJson() .row(null, null, "\"Check out the bi-weekly Trino Community Broadcast https://trino.io/broadcast/\"", null, null, null, "5309") .build(); - assertEquals(rows.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(rows.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); MaterializedResult nestedRows = computeActual("" + "SELECT " + @@ -657,7 +654,7 @@ public void testAsRawJson() .row(null, null, "\"Join the Trino Slack: https://trino.io/slack.html\"", null, null, null, "867") .build(); - assertEquals(nestedRows.getMaterializedRows(), nestedExpected.getMaterializedRows()); + assertThat(nestedRows.getMaterializedRows()).isEqualTo(nestedExpected.getMaterializedRows()); MaterializedResult arrayRows = computeActual("" + "SELECT " + @@ -676,7 +673,7 @@ public void testAsRawJson() .row(null, null, "\"If you like Presto, you'll love Trino: https://trino.io/slack.html\"", null, null, null, "321") .build(); - assertEquals(arrayRows.getMaterializedRows(), arrayExpected.getMaterializedRows()); + assertThat(arrayRows.getMaterializedRows()).isEqualTo(arrayExpected.getMaterializedRows()); MaterializedResult rawRows = computeActual("" + "SELECT " + @@ -695,7 +692,7 @@ public void testAsRawJson() .row(null, null, "\"The founders and core contributors of Presto, and are now working on Trino: https://trino.io/blog/2020/12/27/announcing-trino.html\"", null, null, null, "654") .build(); - assertEquals(rawRows.getMaterializedRows(), rawRowsExpected.getMaterializedRows()); + assertThat(rawRows.getMaterializedRows()).isEqualTo(rawRowsExpected.getMaterializedRows()); } @Test @@ -799,7 +796,7 @@ public void testAsRawJsonForAllPrimitiveTypes() assertThat(rows.getTypes()) .hasOnlyElementsOfType(VarcharType.class); - assertEquals(rows.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(rows.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); deleteIndex(indexName); } @@ -858,7 +855,7 @@ public void testAsRawJsonCases() .row("\"dGVzdA==\"", "true", "123") .build(); - assertEquals(rows.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(rows.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); assertThat(rows.getTypes()) .hasOnlyElementsOfType(VarcharType.class); @@ -1208,7 +1205,7 @@ public void testDataTypes() 123456.78d) .build(); - assertEquals(rows.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(rows.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); } @Test @@ -1238,7 +1235,7 @@ public void testTableWithUnsupportedTypes() .row(1L) .build(); - assertEquals(rows.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(rows.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); } @Test @@ -1428,7 +1425,7 @@ public void testCoercions() .row(1.0f, 1.0d, 1, 1L) .build(); - assertEquals(rows.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(rows.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); } @Test @@ -1624,7 +1621,7 @@ public void testDataTypesNested() LocalDateTime.of(1970, 1, 1, 0, 0), "1.2.3.4", "2001:db8::1:0:0:1") .build(); - assertEquals(rows.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(rows.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); } @Test @@ -1694,7 +1691,7 @@ public void testNestedTypeDataTypesNested() LocalDateTime.of(1970, 1, 1, 0, 0), "1.2.3.4", "2001:db8::1:0:0:1") .build(); - assertEquals(rows.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(rows.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); } @Test @@ -1806,7 +1803,7 @@ public void testEmptyIndexWithMappings() createIndex(indexName, mappings); assertQuery(format("SELECT column_name FROM information_schema.columns WHERE table_name = '%s'", indexName), "VALUES ('dummy_column')"); - assertTrue(computeActual("SHOW TABLES").getOnlyColumnAsSet().contains(indexName)); + assertThat(computeActual("SHOW TABLES").getOnlyColumnAsSet().contains(indexName)).isTrue(); assertQueryReturnsEmptyResult("SELECT * FROM " + indexName); } @@ -1909,7 +1906,7 @@ public void testQueryTableFunction() protected void assertTableDoesNotExist(String name) { assertQueryReturnsEmptyResult(format("SELECT * FROM information_schema.columns WHERE table_name = '%s'", name)); - assertFalse(computeActual("SHOW TABLES").getOnlyColumnAsSet().contains(name)); + assertThat(computeActual("SHOW TABLES").getOnlyColumnAsSet().contains(name)).isFalse(); assertQueryFails("SELECT * FROM " + name, ".*Table '" + catalogName + ".tpch." + name + "' does not exist"); } diff --git a/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/TestElasticsearchBackpressure.java b/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/TestElasticsearchBackpressure.java index 9412bdebe66d..dbaf93176066 100644 --- a/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/TestElasticsearchBackpressure.java +++ b/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/TestElasticsearchBackpressure.java @@ -17,7 +17,6 @@ import com.google.common.collect.ImmutableMap; import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.QueryRunner; -import io.trino.testng.services.ManageTestResources.Suppress; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; @@ -35,11 +34,8 @@ public class TestElasticsearchBackpressure { private static final String image = "elasticsearch:7.0.0"; - @Suppress(because = "Not a TestNG test class") private Network network; - @Suppress(because = "Not a TestNG test class") private ElasticsearchServer elasticsearch; - @Suppress(because = "Not a TestNG test class") private ElasticsearchNginxProxy elasticsearchNginxProxy; @Override diff --git a/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/TestElasticsearchMetadata.java b/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/TestElasticsearchMetadata.java index 9ab47e1d93d6..f1f7c17cf9ac 100644 --- a/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/TestElasticsearchMetadata.java +++ b/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/TestElasticsearchMetadata.java @@ -18,26 +18,26 @@ import java.util.Optional; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestElasticsearchMetadata { @Test public void testLikeToRegexp() { - assertEquals(likeToRegexp("a_b_c", Optional.empty()), "a.b.c"); - assertEquals(likeToRegexp("a%b%c", Optional.empty()), "a.*b.*c"); - assertEquals(likeToRegexp("a%b_c", Optional.empty()), "a.*b.c"); - assertEquals(likeToRegexp("a[b", Optional.empty()), "a\\[b"); - assertEquals(likeToRegexp("a_\\_b", Optional.of("\\")), "a._b"); - assertEquals(likeToRegexp("a$_b", Optional.of("$")), "a_b"); - assertEquals(likeToRegexp("s_.m%ex\\t", Optional.of("$")), "s.\\.m.*ex\\\\t"); - assertEquals(likeToRegexp("\000%", Optional.empty()), "\000.*"); - assertEquals(likeToRegexp("\000%", Optional.of("\000")), "%"); - assertEquals(likeToRegexp("中文%", Optional.empty()), "中文.*"); - assertEquals(likeToRegexp("こんにちは%", Optional.empty()), "こんにちは.*"); - assertEquals(likeToRegexp("안녕하세요%", Optional.empty()), "안녕하세요.*"); - assertEquals(likeToRegexp("Привет%", Optional.empty()), "Привет.*"); + assertThat(likeToRegexp("a_b_c", Optional.empty())).isEqualTo("a.b.c"); + assertThat(likeToRegexp("a%b%c", Optional.empty())).isEqualTo("a.*b.*c"); + assertThat(likeToRegexp("a%b_c", Optional.empty())).isEqualTo("a.*b.c"); + assertThat(likeToRegexp("a[b", Optional.empty())).isEqualTo("a\\[b"); + assertThat(likeToRegexp("a_\\_b", Optional.of("\\"))).isEqualTo("a._b"); + assertThat(likeToRegexp("a$_b", Optional.of("$"))).isEqualTo("a_b"); + assertThat(likeToRegexp("s_.m%ex\\t", Optional.of("$"))).isEqualTo("s.\\.m.*ex\\\\t"); + assertThat(likeToRegexp("\000%", Optional.empty())).isEqualTo("\000.*"); + assertThat(likeToRegexp("\000%", Optional.of("\000"))).isEqualTo("%"); + assertThat(likeToRegexp("中文%", Optional.empty())).isEqualTo("中文.*"); + assertThat(likeToRegexp("こんにちは%", Optional.empty())).isEqualTo("こんにちは.*"); + assertThat(likeToRegexp("안녕하세요%", Optional.empty())).isEqualTo("안녕하세요.*"); + assertThat(likeToRegexp("Привет%", Optional.empty())).isEqualTo("Привет.*"); } private static String likeToRegexp(String pattern, Optional escapeChar) diff --git a/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/TestElasticsearchQueryBuilder.java b/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/TestElasticsearchQueryBuilder.java index a6d660033572..f040188b5f12 100644 --- a/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/TestElasticsearchQueryBuilder.java +++ b/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/TestElasticsearchQueryBuilder.java @@ -37,7 +37,7 @@ import static io.trino.spi.type.DoubleType.DOUBLE; import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.spi.type.VarcharType.VARCHAR; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestElasticsearchQueryBuilder { @@ -133,6 +133,6 @@ public void testMultiConstraint() private static void assertQueryBuilder(Map domains, QueryBuilder expected) { QueryBuilder actual = buildSearchQuery(TupleDomain.withColumnDomains(domains), Optional.empty(), Map.of()); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } } diff --git a/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/client/TestExtractAddress.java b/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/client/TestExtractAddress.java index fc8213bf8070..34a982f53f16 100644 --- a/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/client/TestExtractAddress.java +++ b/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/client/TestExtractAddress.java @@ -18,22 +18,22 @@ import java.util.Optional; import static io.trino.plugin.elasticsearch.client.ElasticsearchClient.extractAddress; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestExtractAddress { @Test public void test() { - assertEquals(extractAddress("node/1.2.3.4:9200"), Optional.of("node:9200")); - assertEquals(extractAddress("1.2.3.4:9200"), Optional.of("1.2.3.4:9200")); - assertEquals(extractAddress("node/1.2.3.4:9200"), Optional.of("node:9200")); - assertEquals(extractAddress("node/[fe80::1]:9200"), Optional.of("node:9200")); - assertEquals(extractAddress("[fe80::1]:9200"), Optional.of("[fe80::1]:9200")); + assertThat(extractAddress("node/1.2.3.4:9200")).isEqualTo(Optional.of("node:9200")); + assertThat(extractAddress("1.2.3.4:9200")).isEqualTo(Optional.of("1.2.3.4:9200")); + assertThat(extractAddress("node/1.2.3.4:9200")).isEqualTo(Optional.of("node:9200")); + assertThat(extractAddress("node/[fe80::1]:9200")).isEqualTo(Optional.of("node:9200")); + assertThat(extractAddress("[fe80::1]:9200")).isEqualTo(Optional.of("[fe80::1]:9200")); - assertEquals(extractAddress(""), Optional.empty()); - assertEquals(extractAddress("node/1.2.3.4"), Optional.empty()); - assertEquals(extractAddress("node/1.2.3.4:xxxx"), Optional.empty()); - assertEquals(extractAddress("1.2.3.4:xxxx"), Optional.empty()); + assertThat(extractAddress("")).isEqualTo(Optional.empty()); + assertThat(extractAddress("node/1.2.3.4")).isEqualTo(Optional.empty()); + assertThat(extractAddress("node/1.2.3.4:xxxx")).isEqualTo(Optional.empty()); + assertThat(extractAddress("1.2.3.4:xxxx")).isEqualTo(Optional.empty()); } } diff --git a/plugin/trino-hive-hadoop2/pom.xml b/plugin/trino-hive-hadoop2/pom.xml index 02a3f5437a36..1069cda0c04a 100644 --- a/plugin/trino-hive-hadoop2/pom.xml +++ b/plugin/trino-hive-hadoop2/pom.xml @@ -217,28 +217,6 @@ - - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - - - default diff --git a/plugin/trino-hudi/pom.xml b/plugin/trino-hudi/pom.xml index 81f18cd509dd..377ddb7edd5b 100644 --- a/plugin/trino-hudi/pom.xml +++ b/plugin/trino-hudi/pom.xml @@ -337,33 +337,10 @@ junit-jupiter-engine test - - - org.testng - testng - test - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - org.basepom.maven duplicate-finder-maven-plugin diff --git a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiPartitionManager.java b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiPartitionManager.java index a998d8a311b5..41a4c566d978 100644 --- a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiPartitionManager.java +++ b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiPartitionManager.java @@ -34,7 +34,7 @@ import static io.trino.plugin.hive.metastore.StorageFormat.fromHiveStorageFormat; import static io.trino.plugin.hive.util.HiveBucketing.BucketingVersion.BUCKETING_V1; import static io.trino.plugin.hudi.model.HudiTableType.COPY_ON_WRITE; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestHudiPartitionManager { @@ -82,6 +82,6 @@ public void testParseValuesAndFilterPartition() List actualPartitions = hudiPartitionManager.getEffectivePartitions( tableHandle, metastore); - assertEquals(actualPartitions, PARTITIONS); + assertThat(actualPartitions).isEqualTo(PARTITIONS); } } diff --git a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiSessionProperties.java b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiSessionProperties.java index 62d039c763e7..d36fb8085225 100644 --- a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiSessionProperties.java +++ b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiSessionProperties.java @@ -19,10 +19,8 @@ import io.trino.testing.TestingConnectorSession; import org.junit.jupiter.api.Test; -import java.util.List; - import static io.trino.plugin.hudi.HudiSessionProperties.getColumnsToHide; -import static org.testng.Assert.assertEqualsNoOrder; +import static org.assertj.core.api.Assertions.assertThat; public class TestHudiSessionProperties { @@ -35,8 +33,7 @@ public void testSessionPropertyColumnsToHide() ConnectorSession session = TestingConnectorSession.builder() .setPropertyMetadata(sessionProperties.getSessionProperties()) .build(); - List expectedColumnsToHide = ImmutableList.of("col1", "col2"); - List actualColumnsToHide = getColumnsToHide(session); - assertEqualsNoOrder(expectedColumnsToHide.toArray(), actualColumnsToHide.toArray()); + assertThat(getColumnsToHide(session)) + .containsExactlyInAnyOrderElementsOf(ImmutableList.of("col1", "col2")); } } diff --git a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiSmokeTest.java b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiSmokeTest.java index 37c53555268d..71a687134880 100644 --- a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiSmokeTest.java +++ b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiSmokeTest.java @@ -29,7 +29,6 @@ import static io.trino.plugin.hudi.testing.ResourceHudiTablesInitializer.TestingTable.STOCK_TICKS_COW; import static io.trino.plugin.hudi.testing.ResourceHudiTablesInitializer.TestingTable.STOCK_TICKS_MOR; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestHudiSmokeTest extends AbstractTestQueryFramework @@ -144,7 +143,7 @@ public void testFileSizeColumn() { String path = (String) computeScalar("SELECT \"$path\" FROM " + HUDI_COW_PT_TBL + " WHERE id = 1"); long fileSize = (long) computeScalar("SELECT \"$file_size\" FROM " + HUDI_COW_PT_TBL + " WHERE id = 1"); - assertEquals(fileSize, Files.size(toPath(path))); + assertThat(fileSize).isEqualTo(Files.size(toPath(path))); } @Test @@ -153,7 +152,8 @@ public void testFileModifiedColumn() { String path = (String) computeScalar("SELECT \"$path\" FROM " + HUDI_COW_PT_TBL + " WHERE id = 1"); ZonedDateTime fileModifiedTime = (ZonedDateTime) computeScalar("SELECT \"$file_modified_time\" FROM " + HUDI_COW_PT_TBL + " WHERE id = 1"); - assertEquals(fileModifiedTime.toInstant().toEpochMilli(), Files.getLastModifiedTime(toPath(path)).toInstant().toEpochMilli()); + assertThat(fileModifiedTime.toInstant().toEpochMilli()) + .isEqualTo(Files.getLastModifiedTime(toPath(path)).toInstant().toEpochMilli()); } @Test diff --git a/plugin/trino-kudu/pom.xml b/plugin/trino-kudu/pom.xml index b08e9e5c280f..b2f0c17510ed 100644 --- a/plugin/trino-kudu/pom.xml +++ b/plugin/trino-kudu/pom.xml @@ -255,24 +255,5 @@ - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - diff --git a/plugin/trino-memory/pom.xml b/plugin/trino-memory/pom.xml index d4229c226040..42c275e8d656 100644 --- a/plugin/trino-memory/pom.xml +++ b/plugin/trino-memory/pom.xml @@ -197,26 +197,4 @@ test - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - - diff --git a/plugin/trino-password-authenticators/pom.xml b/plugin/trino-password-authenticators/pom.xml index d6b102b822e7..5917227dc985 100644 --- a/plugin/trino-password-authenticators/pom.xml +++ b/plugin/trino-password-authenticators/pom.xml @@ -171,26 +171,4 @@ test - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - - diff --git a/plugin/trino-pinot/pom.xml b/plugin/trino-pinot/pom.xml index 59edd39af6e5..ec26b1e8977c 100755 --- a/plugin/trino-pinot/pom.xml +++ b/plugin/trino-pinot/pom.xml @@ -678,23 +678,6 @@ - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - org.apache.maven.plugins maven-enforcer-plugin diff --git a/plugin/trino-prometheus/pom.xml b/plugin/trino-prometheus/pom.xml index 09d1c5dcecfd..0b9722d07590 100644 --- a/plugin/trino-prometheus/pom.xml +++ b/plugin/trino-prometheus/pom.xml @@ -263,26 +263,4 @@ test - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - - diff --git a/plugin/trino-raptor-legacy/pom.xml b/plugin/trino-raptor-legacy/pom.xml index 646f7ed4caa7..47b013da9f6c 100644 --- a/plugin/trino-raptor-legacy/pom.xml +++ b/plugin/trino-raptor-legacy/pom.xml @@ -271,6 +271,12 @@ jakarta.ws.rs-api test + + org.aspectj + aspectjweaver + 1.9.9.1 + test + org.assertj @@ -320,26 +326,4 @@ test - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - - diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/AbstractTestBackupStore.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/AbstractTestBackupStore.java index 59b937340036..57f45c085991 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/AbstractTestBackupStore.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/AbstractTestBackupStore.java @@ -13,7 +13,7 @@ */ package io.trino.plugin.raptor.legacy.backup; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.File; import java.nio.file.Path; diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestFileBackupStore.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestFileBackupStore.java index b113f9be8dbe..66bd6d445abc 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestFileBackupStore.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestFileBackupStore.java @@ -13,9 +13,10 @@ */ package io.trino.plugin.raptor.legacy.backup; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import java.io.File; import java.io.IOException; @@ -25,12 +26,14 @@ import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; import static java.lang.String.format; import static java.nio.file.Files.createTempDirectory; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.testng.Assert.assertEquals; +@TestInstance(PER_CLASS) public class TestFileBackupStore extends AbstractTestBackupStore { - @BeforeClass + @BeforeAll public void setup() throws IOException { @@ -39,7 +42,7 @@ public void setup() store.start(); } - @AfterClass(alwaysRun = true) + @AfterAll public void tearDown() throws Exception { diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestHttpBackupStore.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestHttpBackupStore.java index e9f717a26ee4..975b86c3bf6d 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestHttpBackupStore.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestHttpBackupStore.java @@ -28,9 +28,11 @@ import io.airlift.node.testing.TestingNodeModule; import io.trino.spi.NodeManager; import io.trino.testing.TestingNodeManager; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; import java.io.IOException; import java.net.URI; @@ -43,13 +45,14 @@ import static io.airlift.jaxrs.JaxrsBinder.jaxrsBinder; import static java.nio.file.Files.createTempDirectory; -@Test(singleThreaded = true) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@Execution(ExecutionMode.SAME_THREAD) public class TestHttpBackupStore extends AbstractTestBackupStore { private LifeCycleManager lifeCycleManager; - @BeforeMethod + @BeforeEach public void setup() throws IOException { @@ -77,7 +80,7 @@ public void setup() store = injector.getInstance(BackupStore.class); } - @AfterMethod(alwaysRun = true) + @AfterEach public void teardown() throws IOException { diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/security/TestRaptorReadOnlySecurity.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/security/TestRaptorReadOnlySecurity.java index 5f17c2143a6b..fa67df2db1e5 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/security/TestRaptorReadOnlySecurity.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/security/TestRaptorReadOnlySecurity.java @@ -16,7 +16,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.trino.testing.QueryRunner; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.plugin.raptor.legacy.RaptorQueryRunner.createRaptorQueryRunner; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/plugin/trino-redis/pom.xml b/plugin/trino-redis/pom.xml index b7db43f7912d..4142d0de508b 100644 --- a/plugin/trino-redis/pom.xml +++ b/plugin/trino-redis/pom.xml @@ -216,33 +216,10 @@ testcontainers test - - - org.testng - testng - test - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - org.basepom.maven duplicate-finder-maven-plugin diff --git a/plugin/trino-redis/src/test/java/io/trino/plugin/redis/TestMinimalFunctionality.java b/plugin/trino-redis/src/test/java/io/trino/plugin/redis/TestMinimalFunctionality.java index a891275cdf4d..ac67e6d9d108 100644 --- a/plugin/trino-redis/src/test/java/io/trino/plugin/redis/TestMinimalFunctionality.java +++ b/plugin/trino-redis/src/test/java/io/trino/plugin/redis/TestMinimalFunctionality.java @@ -25,7 +25,6 @@ import static io.trino.transaction.TransactionBuilder.transaction; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertTrue; public class TestMinimalFunctionality extends AbstractTestMinimalFunctionality @@ -44,7 +43,7 @@ public void testTableExists() .singleStatement() .execute(SESSION, session -> { Optional handle = queryRunner.getServer().getMetadata().getTableHandle(session, name); - assertTrue(handle.isPresent()); + assertThat(handle).isPresent(); }); } diff --git a/plugin/trino-redis/src/test/java/io/trino/plugin/redis/TestRedisPlugin.java b/plugin/trino-redis/src/test/java/io/trino/plugin/redis/TestRedisPlugin.java index 9487abe4678d..ab4112829f57 100644 --- a/plugin/trino-redis/src/test/java/io/trino/plugin/redis/TestRedisPlugin.java +++ b/plugin/trino-redis/src/test/java/io/trino/plugin/redis/TestRedisPlugin.java @@ -21,7 +21,7 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static io.airlift.testing.Assertions.assertInstanceOf; -import static org.testng.Assert.assertNotNull; +import static org.assertj.core.api.Assertions.assertThat; public class TestRedisPlugin { @@ -41,7 +41,7 @@ public void testStartup() .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext()); - assertNotNull(connector); + assertThat(connector).isNotNull(); connector.shutdown(); } } diff --git a/plugin/trino-redshift/pom.xml b/plugin/trino-redshift/pom.xml index 7f47fcaf3700..2a14b45a060f 100644 --- a/plugin/trino-redshift/pom.xml +++ b/plugin/trino-redshift/pom.xml @@ -199,36 +199,8 @@ junit-jupiter-engine test - - - org.testng - testng - test - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - - - default diff --git a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftTableStatisticsReader.java b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftTableStatisticsReader.java index fe471829e926..762cadb465b5 100644 --- a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftTableStatisticsReader.java +++ b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftTableStatisticsReader.java @@ -58,8 +58,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.from; import static org.assertj.core.api.Assertions.withinPercentage; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; public class TestRedshiftTableStatisticsReader extends AbstractTestQueryFramework @@ -147,7 +145,7 @@ public void testNullsFraction() TableStatistics stats = collectStats( "SELECT CASE custkey % 3 WHEN 0 THEN NULL ELSE custkey END FROM " + TEST_SCHEMA + ".customer", ImmutableList.of(custkeyColumnHandle)); - assertEquals(stats.getRowCount(), Estimate.of(1500)); + assertThat(stats.getRowCount()).isEqualTo(Estimate.of(1500)); ColumnStatistics columnStatistics = stats.getColumnStatistics().get(custkeyColumnHandle); assertThat(columnStatistics.getNullsFraction().getValue()).isCloseTo(1.0 / 3, withinPercentage(1)); @@ -187,7 +185,7 @@ public void testAverageColumnLength() .hasEntrySatisfying(columns.get(3), statsCloseTo(1.0, 0.99, 14)) .hasEntrySatisfying(columns.get(4), statsCloseTo(1.0, 0.5, 700)) .hasEntrySatisfying(columns.get(5), statsCloseTo(51, 0.5, 800)) - .satisfies(stats -> assertNull(stats.get(columns.get(6)))); + .satisfies(stats -> assertThat(stats.get(columns.get(6))).isNull()); } @Test diff --git a/plugin/trino-resource-group-managers/pom.xml b/plugin/trino-resource-group-managers/pom.xml index b18c76ab044f..afb9ee34af36 100644 --- a/plugin/trino-resource-group-managers/pom.xml +++ b/plugin/trino-resource-group-managers/pom.xml @@ -265,26 +265,4 @@ test - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - - diff --git a/plugin/trino-session-property-managers/pom.xml b/plugin/trino-session-property-managers/pom.xml index 74aabce98b64..1deddf09d9b1 100644 --- a/plugin/trino-session-property-managers/pom.xml +++ b/plugin/trino-session-property-managers/pom.xml @@ -217,33 +217,5 @@ testcontainers test - - - org.testng - testng - test - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - - diff --git a/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/AbstractTestSessionPropertyManager.java b/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/AbstractTestSessionPropertyManager.java index 43cb801f95ef..7268289354ff 100644 --- a/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/AbstractTestSessionPropertyManager.java +++ b/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/AbstractTestSessionPropertyManager.java @@ -19,7 +19,7 @@ import io.trino.spi.resourcegroups.QueryType; import io.trino.spi.resourcegroups.ResourceGroupId; import io.trino.spi.session.SessionConfigurationContext; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Map; import java.util.Optional; diff --git a/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/TestSessionPropertyManagerInTransaction.java b/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/TestSessionPropertyManagerInTransaction.java index 60c68b9f57cc..d366e673407b 100644 --- a/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/TestSessionPropertyManagerInTransaction.java +++ b/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/TestSessionPropertyManagerInTransaction.java @@ -18,7 +18,7 @@ import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.DistributedQueryRunner; import io.trino.testing.QueryRunner; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.File; diff --git a/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/db/TestDbSessionPropertyManager.java b/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/db/TestDbSessionPropertyManager.java index c3e54d178e3f..6e21e9acfded 100644 --- a/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/db/TestDbSessionPropertyManager.java +++ b/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/db/TestDbSessionPropertyManager.java @@ -19,20 +19,24 @@ import io.trino.plugin.session.SessionMatchSpec; import io.trino.spi.resourcegroups.ResourceGroupId; import io.trino.spi.session.SessionConfigurationContext; -import org.testng.annotations.AfterClass; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Map; import java.util.Optional; import java.util.regex.Pattern; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -@Test(singleThreaded = true) +@TestInstance(PER_CLASS) +@Execution(SAME_THREAD) public class TestDbSessionPropertyManager extends AbstractTestSessionPropertyManager { @@ -45,7 +49,7 @@ public class TestDbSessionPropertyManager private static final ResourceGroupId TEST_RG = new ResourceGroupId("rg1"); - @BeforeClass + @BeforeAll public void setup() { mysqlContainer = new TestingMySqlContainer(); @@ -60,14 +64,14 @@ public void setup() dao = daoProvider.get(); } - @BeforeMethod + @BeforeEach public void setupTest() { specsProvider = new RefreshingDbSpecsProvider(config, dao); manager = new DbSessionPropertyManager(specsProvider); } - @AfterMethod(alwaysRun = true) + @AfterEach public void teardown() { dao.dropSessionPropertiesTable(); @@ -75,7 +79,7 @@ public void teardown() dao.dropSessionSpecsTable(); } - @AfterClass(alwaysRun = true) + @AfterAll public void destroy() { specsProvider.destroy(); @@ -90,9 +94,11 @@ protected void assertProperties(Map systemProperties, Map sessionProperties1 = manager.getSystemSessionProperties(context1); - assertEquals(sessionProperties1.get("prop_1"), "val_1"); - assertFalse(sessionProperties1.containsKey("prop_2")); + assertThat(sessionProperties1.get("prop_1")).isEqualTo("val_1"); + assertThat(sessionProperties1.containsKey("prop_2")).isFalse(); specsProvider.refresh(); SessionConfigurationContext context2 = new SessionConfigurationContext("bar123", Optional.of("bar123"), ImmutableSet.of(), Optional.empty(), TEST_RG); Map sessionProperties2 = manager.getSystemSessionProperties(context2); - assertEquals(sessionProperties2.get("prop_2"), "val_2"); - assertFalse(sessionProperties2.containsKey("prop_1")); + assertThat(sessionProperties2.get("prop_2")).isEqualTo("val_2"); + assertThat(sessionProperties2.containsKey("prop_1")).isFalse(); specsProvider.refresh(); SessionConfigurationContext context3 = new SessionConfigurationContext("foo123", Optional.of("bar123"), ImmutableSet.of(), Optional.empty(), TEST_RG); Map sessionProperties3 = manager.getSystemSessionProperties(context3); - assertEquals(sessionProperties3.get("prop_1"), "val_1"); - assertEquals(sessionProperties3.get("prop_2"), "val_2"); + assertThat(sessionProperties3.get("prop_1")).isEqualTo("val_1"); + assertThat(sessionProperties3.get("prop_2")).isEqualTo("val_2"); specsProvider.refresh(); SessionConfigurationContext context4 = new SessionConfigurationContext("abc", Optional.empty(), ImmutableSet.of(), Optional.empty(), TEST_RG); Map sessionProperties4 = manager.getSystemSessionProperties(context4); - assertFalse(sessionProperties4.containsKey("prop_1")); - assertFalse(sessionProperties4.containsKey("prop_2")); + assertThat(sessionProperties4.containsKey("prop_1")).isFalse(); + assertThat(sessionProperties4.containsKey("prop_2")).isFalse(); } /** @@ -181,10 +187,10 @@ public void testReloads() long failuresAfter = specsProvider.getDbLoadFailures().getTotalCount(); // Failed reloading, use cached configurations - assertEquals(failuresAfter - failuresBefore, 1); + assertThat(failuresAfter - failuresBefore).isEqualTo(1); Map sessionProperties1 = manager.getSystemSessionProperties(context1); - assertEquals(sessionProperties1.get("prop_1"), "val_1"); - assertEquals(sessionProperties1.get("prop_3"), null); + assertThat(sessionProperties1.get("prop_1")).isEqualTo("val_1"); + assertThat(sessionProperties1.get("prop_3")).isEqualTo(null); } /** @@ -209,10 +215,10 @@ public void testOrderingOfSpecs() SessionConfigurationContext context = new SessionConfigurationContext("foo", Optional.of("bar"), ImmutableSet.of(), Optional.empty(), TEST_RG); Map sessionProperties = manager.getSystemSessionProperties(context); - assertEquals(sessionProperties.get("prop_1"), "val_1_3"); - assertEquals(sessionProperties.get("prop_2"), "val_2_2"); - assertEquals(sessionProperties.get("prop_3"), "val_3_1"); - assertEquals(sessionProperties.size(), 3); + assertThat(sessionProperties.get("prop_1")).isEqualTo("val_1_3"); + assertThat(sessionProperties.get("prop_2")).isEqualTo("val_2_2"); + assertThat(sessionProperties.get("prop_3")).isEqualTo("val_3_1"); + assertThat(sessionProperties.size()).isEqualTo(3); } @Test @@ -228,9 +234,8 @@ public void testCatalogSessionProperties() specsProvider.refresh(); SessionConfigurationContext context1 = new SessionConfigurationContext("foo", Optional.empty(), ImmutableSet.of(), Optional.empty(), TEST_RG); - assertEquals(manager.getCatalogSessionProperties(context1), - ImmutableMap.of("catalog_1", - ImmutableMap.of("prop_1", "val_1_bis", "prop_2", "val_2", "prop_3", "val_3"))); + assertThat(manager.getCatalogSessionProperties(context1)).isEqualTo(ImmutableMap.of("catalog_1", + ImmutableMap.of("prop_1", "val_1_bis", "prop_2", "val_2", "prop_3", "val_3"))); } @Test @@ -238,7 +243,7 @@ public void testEmptyTables() { specsProvider.refresh(); SessionConfigurationContext context1 = new SessionConfigurationContext("foo", Optional.empty(), ImmutableSet.of(), Optional.empty(), TEST_RG); - assertEquals(manager.getSystemSessionProperties(context1), ImmutableMap.of()); - assertEquals(manager.getCatalogSessionProperties(context1), ImmutableMap.of()); + assertThat(manager.getSystemSessionProperties(context1)).isEqualTo(ImmutableMap.of()); + assertThat(manager.getCatalogSessionProperties(context1)).isEqualTo(ImmutableMap.of()); } } diff --git a/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/db/TestDbSessionPropertyManagerConfig.java b/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/db/TestDbSessionPropertyManagerConfig.java index c5c4a071524e..a07d94198e03 100644 --- a/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/db/TestDbSessionPropertyManagerConfig.java +++ b/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/db/TestDbSessionPropertyManagerConfig.java @@ -15,7 +15,7 @@ import com.google.common.collect.ImmutableMap; import io.airlift.units.Duration; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Map; import java.util.concurrent.TimeUnit; diff --git a/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/db/TestDbSessionPropertyManagerIntegration.java b/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/db/TestDbSessionPropertyManagerIntegration.java index fb9ec0de90e8..33751da94278 100644 --- a/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/db/TestDbSessionPropertyManagerIntegration.java +++ b/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/db/TestDbSessionPropertyManagerIntegration.java @@ -39,10 +39,12 @@ import io.trino.testing.MaterializedResult; import org.jdbi.v3.core.Jdbi; import org.jdbi.v3.sqlobject.SqlObjectPlugin; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.IOException; import java.util.Map; @@ -54,11 +56,13 @@ import static java.util.Collections.emptyMap; import static java.util.Locale.ENGLISH; import static java.util.concurrent.TimeUnit.DAYS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotEquals; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.weakref.jmx.guice.ExportBinder.newExporter; -@Test(singleThreaded = true) // see @BeforeMethod +@TestInstance(PER_CLASS) +@Execution(SAME_THREAD) public class TestDbSessionPropertyManagerIntegration { private DistributedQueryRunner queryRunner; @@ -74,18 +78,19 @@ private static DistributedQueryRunner createQueryRunner() throws Exception { Session session = testSessionBuilder().build(); - assertEquals(session.getSystemProperties(), emptyMap()); + assertThat(session.getSystemProperties()).isEqualTo(emptyMap()); Duration sessionValue = session.getSystemProperty(EXAMPLE_PROPERTY, Duration.class); - assertEquals(sessionValue, EXAMPLE_VALUE_DEFAULT); - assertNotEquals(EXAMPLE_VALUE_DEFAULT, EXAMPLE_VALUE_CONFIGURED); + assertThat(sessionValue).isEqualTo(EXAMPLE_VALUE_DEFAULT); + assertThat(EXAMPLE_VALUE_DEFAULT) + .isNotEqualTo(EXAMPLE_VALUE_CONFIGURED); DistributedQueryRunner queryRunner = DistributedQueryRunner.builder(session).build(); queryRunner.installPlugin(new TestingSessionPropertyConfigurationManagerPlugin()); return queryRunner; } - @BeforeClass + @BeforeAll public void setup() throws Exception { @@ -94,7 +99,7 @@ public void setup() queryRunner = createQueryRunner(); } - @AfterClass(alwaysRun = true) + @AfterAll public void destroy() throws IOException { @@ -106,7 +111,7 @@ public void destroy() mysqlContainer = null; } - @BeforeMethod + @BeforeEach public void setupTest() { queryRunner.getCoordinator().getSessionPropertyDefaults() @@ -125,7 +130,7 @@ public void setupTest() .onDemand(SessionPropertiesDao.class); } - @Test(description = "Test successful and unsuccessful reloading of SessionMatchSpecs from the database") + @Test // "Test successful and unsuccessful reloading of SessionMatchSpecs from the database" public void testOperation() { // Configure the session property for users with user regex user1.* @@ -166,7 +171,7 @@ private void assertSessionPropertyValue(String user, Duration expectedValue) .collect(onlyElement()) .getField(1); - assertEquals(Duration.valueOf(actualValueString), expectedValue); + assertThat(Duration.valueOf(actualValueString)).isEqualTo(expectedValue); } /** diff --git a/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/file/TestFileSessionPropertyManager.java b/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/file/TestFileSessionPropertyManager.java index e4f911644b00..311c2e8c5b31 100644 --- a/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/file/TestFileSessionPropertyManager.java +++ b/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/file/TestFileSessionPropertyManager.java @@ -26,7 +26,7 @@ import java.util.Map; import static io.trino.plugin.session.file.FileSessionPropertyManager.CODEC; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestFileSessionPropertyManager extends AbstractTestSessionPropertyManager @@ -39,8 +39,8 @@ protected void assertProperties(Map systemProperties, Map - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - diff --git a/plugin/trino-thrift-testing-server/pom.xml b/plugin/trino-thrift-testing-server/pom.xml index dddd285c164a..0fd32725cf0b 100644 --- a/plugin/trino-thrift-testing-server/pom.xml +++ b/plugin/trino-thrift-testing-server/pom.xml @@ -140,33 +140,5 @@ junit-jupiter-engine test - - - org.testng - testng - test - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - - diff --git a/plugin/trino-thrift-testing-server/src/test/java/io/trino/plugin/thrift/server/TestListBasedRecordSet.java b/plugin/trino-thrift-testing-server/src/test/java/io/trino/plugin/thrift/server/TestListBasedRecordSet.java index a6c28fd9fa3e..9dc1a726e83b 100644 --- a/plugin/trino-thrift-testing-server/src/test/java/io/trino/plugin/thrift/server/TestListBasedRecordSet.java +++ b/plugin/trino-thrift-testing-server/src/test/java/io/trino/plugin/thrift/server/TestListBasedRecordSet.java @@ -23,10 +23,8 @@ import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.spi.type.VarcharType.VARCHAR; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestListBasedRecordSet { @@ -34,9 +32,9 @@ public class TestListBasedRecordSet public void testEmptyCursor() { ListBasedRecordSet recordSet = new ListBasedRecordSet(ImmutableList.of(), ImmutableList.of(BIGINT, INTEGER)); - assertEquals(recordSet.getColumnTypes(), ImmutableList.of(BIGINT, INTEGER)); + assertThat(recordSet.getColumnTypes()).isEqualTo(ImmutableList.of(BIGINT, INTEGER)); RecordCursor cursor = recordSet.cursor(); - assertFalse(cursor.advanceNextPosition()); + assertThat(cursor.advanceNextPosition()).isFalse(); } @Test @@ -47,23 +45,23 @@ public void testCursor() Arrays.asList("1", null, "3"), Arrays.asList("ab", "c", null)), ImmutableList.of(BIGINT, VARCHAR)); - assertEquals(recordSet.getColumnTypes(), ImmutableList.of(BIGINT, VARCHAR)); + assertThat(recordSet.getColumnTypes()).isEqualTo(ImmutableList.of(BIGINT, VARCHAR)); RecordCursor cursor = recordSet.cursor(); - assertTrue(cursor.advanceNextPosition()); - assertEquals(cursor.getType(0), BIGINT); - assertEquals(cursor.getType(1), VARCHAR); + assertThat(cursor.advanceNextPosition()).isTrue(); + assertThat(cursor.getType(0)).isEqualTo(BIGINT); + assertThat(cursor.getType(1)).isEqualTo(VARCHAR); assertThatThrownBy(() -> cursor.getLong(2)) .isInstanceOf(IndexOutOfBoundsException.class) .hasMessage("Index 2 out of bounds for length 2"); - assertEquals(cursor.getLong(0), 1L); - assertEquals(cursor.getSlice(1), Slices.utf8Slice("ab")); - assertTrue(cursor.advanceNextPosition()); - assertTrue(cursor.isNull(0)); - assertEquals(cursor.getSlice(1), Slices.utf8Slice("c")); - assertTrue(cursor.advanceNextPosition()); - assertEquals(cursor.getLong(0), 3L); - assertTrue(cursor.isNull(1)); - assertFalse(cursor.advanceNextPosition()); + assertThat(cursor.getLong(0)).isEqualTo(1L); + assertThat(cursor.getSlice(1)).isEqualTo(Slices.utf8Slice("ab")); + assertThat(cursor.advanceNextPosition()).isTrue(); + assertThat(cursor.isNull(0)).isTrue(); + assertThat(cursor.getSlice(1)).isEqualTo(Slices.utf8Slice("c")); + assertThat(cursor.advanceNextPosition()).isTrue(); + assertThat(cursor.getLong(0)).isEqualTo(3L); + assertThat(cursor.isNull(1)).isTrue(); + assertThat(cursor.advanceNextPosition()).isFalse(); assertThatThrownBy(() -> cursor.getLong(0)) .isInstanceOf(IndexOutOfBoundsException.class) .hasMessage("Index 3 out of bounds for length 3"); diff --git a/plugin/trino-thrift/pom.xml b/plugin/trino-thrift/pom.xml index 480aab9517ce..6d1795b5c2cb 100644 --- a/plugin/trino-thrift/pom.xml +++ b/plugin/trino-thrift/pom.xml @@ -257,24 +257,5 @@ - - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - From 42a3c0235b2d44dd271375e523a73e2810660c31 Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Thu, 26 Oct 2023 11:00:50 +0900 Subject: [PATCH 270/587] Add support for partition pruning in Delta checkpoint iterator --- docs/src/main/sphinx/connector/delta-lake.md | 4 + .../plugin/deltalake/DeltaLakeConfig.java | 13 ++ .../plugin/deltalake/DeltaLakeMetadata.java | 2 +- .../deltalake/DeltaLakeSessionProperties.java | 11 + .../deltalake/DeltaLakeSplitManager.java | 2 +- .../transactionlog/TableSnapshot.java | 14 +- .../transactionlog/TransactionLogAccess.java | 33 ++- .../checkpoint/CheckpointEntryIterator.java | 51 ++++- .../checkpoint/CheckpointSchemaManager.java | 13 +- .../checkpoint/CheckpointWriter.java | 3 +- .../checkpoint/CheckpointWriterManager.java | 7 +- .../plugin/deltalake/TestDeltaLakeBasic.java | 105 +++++++++ .../plugin/deltalake/TestDeltaLakeConfig.java | 3 + .../TestDeltaLakeFileOperations.java | 31 +++ .../deltalake/TestDeltaLakeSplitManager.java | 2 +- .../deltalake/TestTransactionLogAccess.java | 2 +- .../deltalake/TestingDeltaLakeUtils.java | 2 +- .../transactionlog/TestTableSnapshot.java | 5 +- .../TestCheckpointEntryIterator.java | 210 ++++++++++++++++-- .../checkpoint/TestCheckpointWriter.java | 4 +- .../TestDeltaLakeFileStatistics.java | 10 +- .../partition_values_parsed/README.md | 21 ++ .../_delta_log/00000000000000000000.json | 3 + .../00000000000000000001.checkpoint.parquet | Bin 0 -> 16816 bytes .../_delta_log/00000000000000000001.json | 2 + .../00000000000000000002.checkpoint.parquet | Bin 0 -> 16791 bytes .../_delta_log/00000000000000000002.json | 2 + .../00000000000000000003.checkpoint.parquet | Bin 0 -> 16925 bytes .../_delta_log/00000000000000000003.json | 2 + .../_delta_log/_last_checkpoint | 1 + ...4e70-86ab-c21ae44c7f3f.c000.snappy.parquet | Bin 0 -> 452 bytes ...4ce1-b96c-32c5cf472476.c000.snappy.parquet | Bin 0 -> 452 bytes ...4fa6-a8bf-860da0131a5c.c000.snappy.parquet | Bin 0 -> 452 bytes .../README.md | 77 +++++++ .../_delta_log/00000000000000000000.json | 3 + .../_delta_log/00000000000000000001.json | 2 + .../_delta_log/00000000000000000002.json | 2 + .../00000000000000000003.checkpoint.parquet | Bin 0 -> 25294 bytes .../_delta_log/00000000000000000003.json | 2 + .../_delta_log/_last_checkpoint | 1 + ...4363-81b6-aeaea00fff6d.c000.snappy.parquet | Bin 0 -> 452 bytes ...4c22-ad51-909b9600e221.c000.snappy.parquet | Bin 0 -> 452 bytes ...4156-8560-bd819942f7fd.c000.snappy.parquet | Bin 0 -> 452 bytes .../partition_values_parsed/README.md | 17 ++ .../_delta_log/00000000000000000000.json | 3 + .../00000000000000000001.checkpoint.parquet | Bin 0 -> 7055 bytes .../_delta_log/00000000000000000001.json | 2 + .../00000000000000000002.checkpoint.parquet | Bin 0 -> 6920 bytes .../_delta_log/00000000000000000002.json | 2 + .../00000000000000000003.checkpoint.parquet | Bin 0 -> 7077 bytes .../_delta_log/00000000000000000003.json | 2 + .../_delta_log/_last_checkpoint | 1 + .../_trino_meta/extended_stats.json | 1 + ...9eakg_302b745a-59c0-4fce-8ca7-fed724196b93 | Bin 0 -> 199 bytes ...9eakg_e6448c08-9b43-4fa1-8288-823fd3d692b9 | Bin 0 -> 199 bytes ...9eakg_2a008dd8-da7f-496a-b404-ca455732578e | Bin 0 -> 199 bytes 56 files changed, 630 insertions(+), 43 deletions(-) create mode 100644 plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed/README.md create mode 100644 plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed/_delta_log/00000000000000000000.json create mode 100644 plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed/_delta_log/00000000000000000001.checkpoint.parquet create mode 100644 plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed/_delta_log/00000000000000000001.json create mode 100644 plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed/_delta_log/00000000000000000002.checkpoint.parquet create mode 100644 plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed/_delta_log/00000000000000000002.json create mode 100644 plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed/_delta_log/00000000000000000003.checkpoint.parquet create mode 100644 plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed/_delta_log/00000000000000000003.json create mode 100644 plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed/_delta_log/_last_checkpoint create mode 100644 plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed/int_part=10/string_part=part1/part-00000-383afb1a-87de-4e70-86ab-c21ae44c7f3f.c000.snappy.parquet create mode 100644 plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed/int_part=20/string_part=part2/part-00000-e0b4887e-95f6-4ce1-b96c-32c5cf472476.c000.snappy.parquet create mode 100644 plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed/int_part=__HIVE_DEFAULT_PARTITION__/string_part=__HIVE_DEFAULT_PARTITION__/part-00000-dcb29d13-eeca-4fa6-a8bf-860da0131a5c.c000.snappy.parquet create mode 100644 plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed_all_types/README.md create mode 100644 plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed_all_types/_delta_log/00000000000000000000.json create mode 100644 plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed_all_types/_delta_log/00000000000000000001.json create mode 100644 plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed_all_types/_delta_log/00000000000000000002.json create mode 100644 plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed_all_types/_delta_log/00000000000000000003.checkpoint.parquet create mode 100644 plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed_all_types/_delta_log/00000000000000000003.json create mode 100644 plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed_all_types/_delta_log/_last_checkpoint create mode 100644 plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed_all_types/part_boolean=__HIVE_DEFAULT_PARTITION__/part_tinyint=__HIVE_DEFAULT_PARTITION__/part_smallint=__HIVE_DEFAULT_PARTITION__/part_int=__HIVE_DEFAULT_PARTITION__/part_bigint=__HIVE_DEFAULT_PARTITION__/part_short_decimal=__HIVE_DEFAULT_PARTITION__/part_long_decimal=__HIVE_DEFAULT_PARTITION__/part_double=__HIVE_DEFAULT_PARTITION__/part_float=__HIVE_DEFAULT_PARTITION__/part_varchar=__HIVE_DEFAULT_PARTITION__/part_date=__HIVE_DEFAULT_PARTITION__/part_timestamp=__HIVE_DEFAULT_PARTITION__/part_timestamp_ntz=__HIVE_DEFAULT_PARTITION__/part-00000-194b12f4-b133-4363-81b6-aeaea00fff6d.c000.snappy.parquet create mode 100644 plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed_all_types/part_boolean=false/part_tinyint=2/part_smallint=20/part_int=200/part_bigint=2000/part_short_decimal=223.12/part_long_decimal=223456789012345678.123/part_double=10.2/part_float=30.4/part_varchar=b/part_date=2020-08-22/part_timestamp=2020-10-22 01%3A00%3A00.123/part_timestamp_ntz=2023-01-03 01%3A02%3A03.456/part-00000-b0b98900-4fe4-4c22-ad51-909b9600e221.c000.snappy.parquet create mode 100644 plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed_all_types/part_boolean=true/part_tinyint=1/part_smallint=10/part_int=100/part_bigint=1000/part_short_decimal=123.12/part_long_decimal=123456789012345678.123/part_double=1.2/part_float=3.4/part_varchar=a/part_date=2020-08-21/part_timestamp=2020-10-21 01%3A00%3A00.123/part_timestamp_ntz=2023-01-02 01%3A02%3A03.456/part-00000-0c74f71e-bb8b-4156-8560-bd819942f7fd.c000.snappy.parquet create mode 100644 plugin/trino-delta-lake/src/test/resources/trino432/partition_values_parsed/README.md create mode 100644 plugin/trino-delta-lake/src/test/resources/trino432/partition_values_parsed/_delta_log/00000000000000000000.json create mode 100644 plugin/trino-delta-lake/src/test/resources/trino432/partition_values_parsed/_delta_log/00000000000000000001.checkpoint.parquet create mode 100644 plugin/trino-delta-lake/src/test/resources/trino432/partition_values_parsed/_delta_log/00000000000000000001.json create mode 100644 plugin/trino-delta-lake/src/test/resources/trino432/partition_values_parsed/_delta_log/00000000000000000002.checkpoint.parquet create mode 100644 plugin/trino-delta-lake/src/test/resources/trino432/partition_values_parsed/_delta_log/00000000000000000002.json create mode 100644 plugin/trino-delta-lake/src/test/resources/trino432/partition_values_parsed/_delta_log/00000000000000000003.checkpoint.parquet create mode 100644 plugin/trino-delta-lake/src/test/resources/trino432/partition_values_parsed/_delta_log/00000000000000000003.json create mode 100644 plugin/trino-delta-lake/src/test/resources/trino432/partition_values_parsed/_delta_log/_last_checkpoint create mode 100644 plugin/trino-delta-lake/src/test/resources/trino432/partition_values_parsed/_delta_log/_trino_meta/extended_stats.json create mode 100644 plugin/trino-delta-lake/src/test/resources/trino432/partition_values_parsed/int_part=10/string_part=part1/20231109_020343_00032_9eakg_302b745a-59c0-4fce-8ca7-fed724196b93 create mode 100644 plugin/trino-delta-lake/src/test/resources/trino432/partition_values_parsed/int_part=20/string_part=part2/20231109_020344_00033_9eakg_e6448c08-9b43-4fa1-8288-823fd3d692b9 create mode 100644 plugin/trino-delta-lake/src/test/resources/trino432/partition_values_parsed/int_part=__HIVE_DEFAULT_PARTITION__/string_part=__HIVE_DEFAULT_PARTITION__/20231109_020350_00034_9eakg_2a008dd8-da7f-496a-b404-ca455732578e diff --git a/docs/src/main/sphinx/connector/delta-lake.md b/docs/src/main/sphinx/connector/delta-lake.md index 4025bbb94b8b..3661c98a75ca 100644 --- a/docs/src/main/sphinx/connector/delta-lake.md +++ b/docs/src/main/sphinx/connector/delta-lake.md @@ -124,6 +124,10 @@ values. Typical usage does not require you to configure them. * - `delta.checkpoint-row-statistics-writing.enabled` - Enable writing row statistics to checkpoint files. - `true` +* - ``delta.checkpoint-filtering.enabled`` + - Enable partition pruning when reading checkpoint files. + The equivalent catalog session property is ``checkpoint_filtering_enabled``. + - ``false`` * - `delta.dynamic-filtering.wait-timeout` - Duration to wait for completion of [dynamic filtering](/admin/dynamic-filtering) during split generation. The equivalent diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeConfig.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeConfig.java index 9dd03f686b18..f92a5b893b11 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeConfig.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeConfig.java @@ -62,6 +62,7 @@ public class DeltaLakeConfig private boolean unsafeWritesEnabled; private boolean checkpointRowStatisticsWritingEnabled = true; private long defaultCheckpointWritingInterval = 10; + private boolean checkpointFilteringEnabled; private Duration vacuumMinRetention = new Duration(7, DAYS); private Optional hiveCatalogName = Optional.empty(); private Duration dynamicFilteringWaitTimeout = new Duration(0, SECONDS); @@ -269,6 +270,18 @@ public long getDefaultCheckpointWritingInterval() return defaultCheckpointWritingInterval; } + public boolean isCheckpointPartitionFilterEnabled() + { + return checkpointFilteringEnabled; + } + + @Config("delta.checkpoint-filtering.enabled") + public DeltaLakeConfig setCheckpointPartitionFilterEnabled(boolean checkpointFilteringEnabled) + { + this.checkpointFilteringEnabled = checkpointFilteringEnabled; + return this; + } + @NotNull public Duration getVacuumMinRetention() { diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java index 344d81a6d868..b138e265b9ec 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java @@ -3519,7 +3519,7 @@ private OptionalLong executeDelete(ConnectorSession session, ConnectorTableHandl private List getAddFileEntriesMatchingEnforcedPartitionConstraint(ConnectorSession session, DeltaLakeTableHandle tableHandle) { TableSnapshot tableSnapshot = getSnapshot(session, tableHandle); - List validDataFiles = transactionLogAccess.getActiveFiles(tableSnapshot, tableHandle.getMetadataEntry(), tableHandle.getProtocolEntry(), session); + List validDataFiles = transactionLogAccess.getActiveFiles(tableSnapshot, tableHandle.getMetadataEntry(), tableHandle.getProtocolEntry(), tableHandle.getEnforcedPartitionConstraint(), session); TupleDomain enforcedPartitionConstraint = tableHandle.getEnforcedPartitionConstraint(); if (enforcedPartitionConstraint.isAll()) { return validDataFiles; diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeSessionProperties.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeSessionProperties.java index 0ca4012c16e6..1179ccec7cb7 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeSessionProperties.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeSessionProperties.java @@ -70,6 +70,7 @@ public final class DeltaLakeSessionProperties public static final String LEGACY_CREATE_TABLE_WITH_EXISTING_LOCATION_ENABLED = "legacy_create_table_with_existing_location_enabled"; private static final String PROJECTION_PUSHDOWN_ENABLED = "projection_pushdown_enabled"; private static final String QUERY_PARTITION_FILTER_REQUIRED = "query_partition_filter_required"; + private static final String CHECKPOINT_FILTERING_ENABLED = "checkpoint_filtering_enabled"; private final List> sessionProperties; @@ -202,6 +203,11 @@ public DeltaLakeSessionProperties( QUERY_PARTITION_FILTER_REQUIRED, "Require filter on partition column", deltaLakeConfig.isQueryPartitionFilterRequired(), + false), + booleanProperty( + CHECKPOINT_FILTERING_ENABLED, + "Use filter in checkpoint reader", + deltaLakeConfig.isCheckpointPartitionFilterEnabled(), false)); } @@ -306,4 +312,9 @@ public static boolean isQueryPartitionFilterRequired(ConnectorSession session) { return session.getProperty(QUERY_PARTITION_FILTER_REQUIRED, Boolean.class); } + + public static boolean isCheckpointFilteringEnabled(ConnectorSession session) + { + return session.getProperty(CHECKPOINT_FILTERING_ENABLED, Boolean.class); + } } diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeSplitManager.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeSplitManager.java index 84b5e71caf1f..606ab5d55ce1 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeSplitManager.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeSplitManager.java @@ -154,7 +154,7 @@ private Stream getSplits( { TableSnapshot tableSnapshot = deltaLakeTransactionManager.get(transaction, session.getIdentity()) .getSnapshot(session, tableHandle.getSchemaTableName(), tableHandle.getLocation(), tableHandle.getReadVersion()); - List validDataFiles = transactionLogAccess.getActiveFiles(tableSnapshot, tableHandle.getMetadataEntry(), tableHandle.getProtocolEntry(), session); + List validDataFiles = transactionLogAccess.getActiveFiles(tableSnapshot, tableHandle.getMetadataEntry(), tableHandle.getProtocolEntry(), tableHandle.getEnforcedPartitionConstraint(), session); TupleDomain enforcedPartitionConstraint = tableHandle.getEnforcedPartitionConstraint(); TupleDomain nonPartitionConstraint = tableHandle.getNonPartitionConstraint(); Domain pathDomain = getPathDomain(nonPartitionConstraint); diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/TableSnapshot.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/TableSnapshot.java index 5069517239a8..ba4fc8c37a40 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/TableSnapshot.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/TableSnapshot.java @@ -18,6 +18,7 @@ import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoInputFile; import io.trino.parquet.ParquetReaderOptions; +import io.trino.plugin.deltalake.DeltaLakeColumnHandle; import io.trino.plugin.deltalake.transactionlog.checkpoint.CheckpointEntryIterator; import io.trino.plugin.deltalake.transactionlog.checkpoint.CheckpointSchemaManager; import io.trino.plugin.deltalake.transactionlog.checkpoint.LastCheckpoint; @@ -26,6 +27,7 @@ import io.trino.spi.TrinoException; import io.trino.spi.connector.ConnectorSession; import io.trino.spi.connector.SchemaTableName; +import io.trino.spi.predicate.TupleDomain; import io.trino.spi.type.TypeManager; import java.io.FileNotFoundException; @@ -178,7 +180,8 @@ public Stream getCheckpointTransactionLogEntries( TypeManager typeManager, TrinoFileSystem fileSystem, FileFormatDataSourceStats stats, - Optional metadataAndProtocol) + Optional metadataAndProtocol, + TupleDomain partitionConstraint) throws IOException { if (lastCheckpoint.isEmpty()) { @@ -206,7 +209,8 @@ public Stream getCheckpointTransactionLogEntries( typeManager, stats, checkpoint, - checkpointFile))); + checkpointFile, + partitionConstraint))); } return resultStream; } @@ -225,7 +229,8 @@ private Iterator getCheckpointTransactionLogEntrie TypeManager typeManager, FileFormatDataSourceStats stats, LastCheckpoint checkpoint, - TrinoInputFile checkpointFile) + TrinoInputFile checkpointFile, + TupleDomain partitionConstraint) throws IOException { long fileSize; @@ -247,7 +252,8 @@ private Iterator getCheckpointTransactionLogEntrie stats, parquetReaderOptions, checkpointRowStatisticsWritingEnabled, - domainCompactionThreshold); + domainCompactionThreshold, + partitionConstraint); } public record MetadataAndProtocolEntry(MetadataEntry metadataEntry, ProtocolEntry protocolEntry) diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/TransactionLogAccess.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/TransactionLogAccess.java index 52ff743b8968..b503a27c28cb 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/TransactionLogAccess.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/TransactionLogAccess.java @@ -27,6 +27,7 @@ import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.filesystem.TrinoInputFile; import io.trino.parquet.ParquetReaderOptions; +import io.trino.plugin.deltalake.DeltaLakeColumnHandle; import io.trino.plugin.deltalake.DeltaLakeColumnMetadata; import io.trino.plugin.deltalake.DeltaLakeConfig; import io.trino.plugin.deltalake.transactionlog.TableSnapshot.MetadataAndProtocolEntry; @@ -39,6 +40,7 @@ import io.trino.spi.TrinoException; import io.trino.spi.connector.ConnectorSession; import io.trino.spi.connector.SchemaTableName; +import io.trino.spi.predicate.TupleDomain; import io.trino.spi.type.ArrayType; import io.trino.spi.type.BooleanType; import io.trino.spi.type.MapType; @@ -75,6 +77,8 @@ import static io.airlift.slice.SizeOf.instanceSize; import static io.trino.cache.CacheUtils.invalidateAllIf; import static io.trino.plugin.deltalake.DeltaLakeErrorCode.DELTA_LAKE_INVALID_SCHEMA; +import static io.trino.plugin.deltalake.DeltaLakeSessionProperties.isCheckpointFilteringEnabled; +import static io.trino.plugin.deltalake.DeltaLakeSplitManager.partitionMatchesPredicate; import static io.trino.plugin.deltalake.transactionlog.TransactionLogParser.readLastCheckpoint; import static io.trino.plugin.deltalake.transactionlog.TransactionLogUtil.getTransactionLogJsonEntryPath; import static io.trino.plugin.deltalake.transactionlog.checkpoint.CheckpointEntryIterator.EntryType.ADD; @@ -219,9 +223,20 @@ public MetadataEntry getMetadataEntry(TableSnapshot tableSnapshot, ConnectorSess .orElseThrow(() -> new TrinoException(DELTA_LAKE_INVALID_SCHEMA, "Metadata not found in transaction log for " + tableSnapshot.getTable())); } + @Deprecated public List getActiveFiles(TableSnapshot tableSnapshot, MetadataEntry metadataEntry, ProtocolEntry protocolEntry, ConnectorSession session) + { + return getActiveFiles(tableSnapshot, metadataEntry, protocolEntry, TupleDomain.all(), session); + } + + public List getActiveFiles(TableSnapshot tableSnapshot, MetadataEntry metadataEntry, ProtocolEntry protocolEntry, TupleDomain partitionConstraint, ConnectorSession session) { try { + if (isCheckpointFilteringEnabled(session)) { + return loadActiveFiles(tableSnapshot, metadataEntry, protocolEntry, partitionConstraint, session).stream() + .collect(toImmutableList()); + } + TableVersion tableVersion = new TableVersion(new TableLocation(tableSnapshot.getTable(), tableSnapshot.getTableLocation()), tableSnapshot.getVersion()); DeltaLakeDataFileCacheEntry cacheEntry = activeDataFileCache.get(tableVersion, () -> { @@ -249,7 +264,7 @@ public List getActiveFiles(TableSnapshot tableSnapshot, MetadataEn } } - List activeFiles = loadActiveFiles(tableSnapshot, metadataEntry, protocolEntry, session); + List activeFiles = loadActiveFiles(tableSnapshot, metadataEntry, protocolEntry, TupleDomain.all(), session); return new DeltaLakeDataFileCacheEntry(tableSnapshot.getVersion(), activeFiles); }); return cacheEntry.getActiveFiles(); @@ -259,7 +274,12 @@ public List getActiveFiles(TableSnapshot tableSnapshot, MetadataEn } } - private List loadActiveFiles(TableSnapshot tableSnapshot, MetadataEntry metadataEntry, ProtocolEntry protocolEntry, ConnectorSession session) + private List loadActiveFiles( + TableSnapshot tableSnapshot, + MetadataEntry metadataEntry, + ProtocolEntry protocolEntry, + TupleDomain partitionConstraint, + ConnectorSession session) { List transactions = tableSnapshot.getTransactions(); try (Stream checkpointEntries = tableSnapshot.getCheckpointTransactionLogEntries( @@ -269,8 +289,12 @@ private List loadActiveFiles(TableSnapshot tableSnapshot, Metadata typeManager, fileSystemFactory.create(session), fileFormatDataSourceStats, - Optional.of(new MetadataAndProtocolEntry(metadataEntry, protocolEntry)))) { + Optional.of(new MetadataAndProtocolEntry(metadataEntry, protocolEntry)), + partitionConstraint)) { return activeAddEntries(checkpointEntries, transactions) + .filter(partitionConstraint.isAll() + ? addAction -> true + : addAction -> partitionMatchesPredicate(addAction.getCanonicalPartitionValues(), partitionConstraint.getDomains().orElseThrow())) .collect(toImmutableList()); } catch (IOException e) { @@ -407,8 +431,9 @@ private Stream getEntries( { try { List transactions = tableSnapshot.getTransactions(); + // Passing TupleDomain.all() because this method is used for getting all entries Stream checkpointEntries = tableSnapshot.getCheckpointTransactionLogEntries( - session, entryTypes, checkpointSchemaManager, typeManager, fileSystem, stats, Optional.empty()); + session, entryTypes, checkpointSchemaManager, typeManager, fileSystem, stats, Optional.empty(), TupleDomain.all()); return entryMapper.apply( checkpointEntries, diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointEntryIterator.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointEntryIterator.java index 1892b44643dd..c2dca1f4e295 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointEntryIterator.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointEntryIterator.java @@ -22,6 +22,7 @@ import io.airlift.log.Logger; import io.trino.filesystem.TrinoInputFile; import io.trino.parquet.ParquetReaderOptions; +import io.trino.plugin.deltalake.DeltaHiveTypeTranslator; import io.trino.plugin.deltalake.DeltaLakeColumnHandle; import io.trino.plugin.deltalake.DeltaLakeColumnMetadata; import io.trino.plugin.deltalake.transactionlog.AddFileEntry; @@ -82,6 +83,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static io.trino.plugin.deltalake.DeltaLakeColumnType.REGULAR; import static io.trino.plugin.deltalake.DeltaLakeErrorCode.DELTA_LAKE_INVALID_SCHEMA; +import static io.trino.plugin.deltalake.DeltaLakeSplitManager.partitionMatchesPredicate; import static io.trino.plugin.deltalake.transactionlog.DeltaLakeSchemaSupport.extractSchema; import static io.trino.plugin.deltalake.transactionlog.DeltaLakeSchemaSupport.isDeletionVectorEnabled; import static io.trino.plugin.deltalake.transactionlog.TransactionLogAccess.columnsWithStats; @@ -140,6 +142,7 @@ public String getColumnName() private final Queue nextEntries; private final List extractors; private final boolean checkpointRowStatisticsWritingEnabled; + private final TupleDomain partitionConstraint; private MetadataEntry metadataEntry; private ProtocolEntry protocolEntry; private List schema; @@ -160,13 +163,15 @@ public CheckpointEntryIterator( FileFormatDataSourceStats stats, ParquetReaderOptions parquetReaderOptions, boolean checkpointRowStatisticsWritingEnabled, - int domainCompactionThreshold) + int domainCompactionThreshold, + TupleDomain partitionConstraint) { this.checkpointPath = checkpoint.location().toString(); this.session = requireNonNull(session, "session is null"); this.stringList = (ArrayType) typeManager.getType(TypeSignature.arrayType(VARCHAR.getTypeSignature())); this.stringMap = (MapType) typeManager.getType(TypeSignature.mapType(VARCHAR.getTypeSignature(), VARCHAR.getTypeSignature())); this.checkpointRowStatisticsWritingEnabled = checkpointRowStatisticsWritingEnabled; + this.partitionConstraint = requireNonNull(partitionConstraint, "partitionConstraint is null"); checkArgument(!fields.isEmpty(), "fields is empty"); Map extractors = ImmutableMap.builder() .put(TRANSACTION, this::buildTxnEntry) @@ -221,7 +226,7 @@ private DeltaLakeColumnHandle buildColumnHandle(EntryType entryType, CheckpointS { Type type = switch (entryType) { case TRANSACTION -> schemaManager.getTxnEntryType(); - case ADD -> schemaManager.getAddEntryType(metadataEntry, protocolEntry, true, true); + case ADD -> schemaManager.getAddEntryType(metadataEntry, protocolEntry, true, true, true); case REMOVE -> schemaManager.getRemoveEntryType(); case METADATA -> schemaManager.getMetadataEntryType(); case PROTOCOL -> schemaManager.getProtocolEntryType(true, true); @@ -272,7 +277,30 @@ private TupleDomain buildTupleDomainColumnHandle(EntryType ent type)), ColumnType.REGULAR, column.getComment()); - return TupleDomain.withColumnDomains(ImmutableMap.of(handle, Domain.notNull(handle.getType()))); + + ImmutableMap.Builder domains = ImmutableMap.builder() + .put(handle, Domain.notNull(handle.getType())); + if (entryType == ADD) { + partitionConstraint.getDomains().orElseThrow().forEach((key, value) -> domains.put(toPartitionValuesParsedField(column, key), value)); + } + + return TupleDomain.withColumnDomains(domains.buildOrThrow()); + } + + private static HiveColumnHandle toPartitionValuesParsedField(HiveColumnHandle addColumn, DeltaLakeColumnHandle partitionColumn) + { + return new HiveColumnHandle( + addColumn.getBaseColumnName(), + addColumn.getBaseHiveColumnIndex(), + addColumn.getBaseHiveType(), + addColumn.getBaseType(), + Optional.of(new HiveColumnProjectionInfo( + ImmutableList.of(0, 0), // hiveColumnIndex; we provide fake value because we always find columns by name + ImmutableList.of("partitionvalues_parsed", partitionColumn.getColumnName()), + DeltaHiveTypeTranslator.toHiveType(partitionColumn.getType()), + partitionColumn.getType())), + HiveColumnHandle.ColumnType.REGULAR, + addColumn.getComment()); } private DeltaLakeTransactionLogEntry buildCommitInfoEntry(ConnectorSession session, Block block, int pagePosition) @@ -431,13 +459,16 @@ private DeltaLakeTransactionLogEntry buildAddEntry(ConnectorSession session, Blo statsFieldIndex = 5; } - Optional parsedStats = Optional.ofNullable(getRowField(addEntryRow, statsFieldIndex + 1)).map(this::parseStatisticsFromParquet); + boolean partitionValuesParsedExists = addEntryRow.getUnderlyingFieldBlock(statsFieldIndex + 1) instanceof RowBlock && // partitionValues_parsed + addEntryRow.getUnderlyingFieldBlock(statsFieldIndex + 2) instanceof RowBlock; // stats_parsed + int parsedStatsIndex = partitionValuesParsedExists ? statsFieldIndex + 1 : statsFieldIndex; + Optional parsedStats = Optional.ofNullable(getRowField(addEntryRow, parsedStatsIndex + 1)).map(this::parseStatisticsFromParquet); Optional stats = Optional.empty(); if (parsedStats.isEmpty()) { stats = Optional.ofNullable(getStringField(addEntryRow, statsFieldIndex)); } - Map tags = getMapField(addEntryRow, statsFieldIndex + 2); + Map tags = getMapField(addEntryRow, parsedStatsIndex + 2); AddFileEntry result = new AddFileEntry( path, partitionValues, @@ -709,7 +740,15 @@ private void fillNextEntries() for (int i = 0; i < extractors.size(); ++i) { DeltaLakeTransactionLogEntry entry = extractors.get(i).getEntry(session, page.getBlock(i).getLoadedBlock(), pagePosition); if (entry != null) { - nextEntries.add(entry); + if (entry.getAdd() != null) { + if (partitionConstraint.isAll() || + partitionMatchesPredicate(entry.getAdd().getCanonicalPartitionValues(), partitionConstraint.getDomains().orElseThrow())) { + nextEntries.add(entry); + } + } + else { + nextEntries.add(entry); + } } } pagePosition++; diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointSchemaManager.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointSchemaManager.java index 290d66081fc0..3fb8df2d5b69 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointSchemaManager.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointSchemaManager.java @@ -15,6 +15,7 @@ import com.google.common.collect.ImmutableList; import com.google.inject.Inject; +import io.trino.plugin.deltalake.DeltaLakeColumnHandle; import io.trino.plugin.deltalake.DeltaLakeColumnMetadata; import io.trino.plugin.deltalake.transactionlog.MetadataEntry; import io.trino.plugin.deltalake.transactionlog.ProtocolEntry; @@ -30,6 +31,7 @@ import java.util.Optional; import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.trino.plugin.deltalake.transactionlog.DeltaLakeSchemaSupport.extractPartitionColumns; import static io.trino.plugin.deltalake.transactionlog.DeltaLakeSchemaSupport.extractSchema; import static io.trino.plugin.deltalake.transactionlog.DeltaLakeSchemaSupport.isDeletionVectorEnabled; import static io.trino.plugin.deltalake.transactionlog.TransactionLogAccess.columnsWithStats; @@ -112,7 +114,7 @@ public RowType getMetadataEntryType() return metadataEntryType; } - public RowType getAddEntryType(MetadataEntry metadataEntry, ProtocolEntry protocolEntry, boolean requireWriteStatsAsJson, boolean requireWriteStatsAsStruct) + public RowType getAddEntryType(MetadataEntry metadataEntry, ProtocolEntry protocolEntry, boolean requireWriteStatsAsJson, boolean requireWriteStatsAsStruct, boolean usePartitionValuesParsed) { List allColumns = extractSchema(metadataEntry, protocolEntry, typeManager); List minMaxColumns = columnsWithStats(metadataEntry, protocolEntry, typeManager); @@ -156,6 +158,15 @@ public RowType getAddEntryType(MetadataEntry metadataEntry, ProtocolEntry protoc if (requireWriteStatsAsJson) { addFields.add(RowType.field("stats", VARCHAR)); } + if (usePartitionValuesParsed) { + List partitionColumns = extractPartitionColumns(metadataEntry, protocolEntry, typeManager); + if (!partitionColumns.isEmpty()) { + List partitionValuesParsed = partitionColumns.stream() + .map(column -> RowType.field(column.getColumnName(), column.getType())) + .collect(toImmutableList()); + addFields.add(RowType.field("partitionValues_parsed", RowType.from(partitionValuesParsed))); + } + } if (requireWriteStatsAsStruct) { addFields.add(RowType.field("stats_parsed", RowType.from(statsColumns.build()))); } diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointWriter.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointWriter.java index 4f2c94a46abd..b8d0c1e38c74 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointWriter.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointWriter.java @@ -111,7 +111,8 @@ public void write(CheckpointEntries entries, TrinoOutputFile outputFile) RowType metadataEntryType = checkpointSchemaManager.getMetadataEntryType(); RowType protocolEntryType = checkpointSchemaManager.getProtocolEntryType(protocolEntry.getReaderFeatures().isPresent(), protocolEntry.getWriterFeatures().isPresent()); RowType txnEntryType = checkpointSchemaManager.getTxnEntryType(); - RowType addEntryType = checkpointSchemaManager.getAddEntryType(entries.getMetadataEntry(), entries.getProtocolEntry(), writeStatsAsJson, writeStatsAsStruct); + // TODO https://github.com/trinodb/trino/issues/19586 Add support for writing 'partitionValues_parsed' field + RowType addEntryType = checkpointSchemaManager.getAddEntryType(entries.getMetadataEntry(), entries.getProtocolEntry(), writeStatsAsJson, writeStatsAsStruct, false); RowType removeEntryType = checkpointSchemaManager.getRemoveEntryType(); List columnNames = ImmutableList.of( diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointWriterManager.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointWriterManager.java index 44ebc5d96aed..0c797dc87c22 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointWriterManager.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointWriterManager.java @@ -29,6 +29,7 @@ import io.trino.spi.TrinoException; import io.trino.spi.connector.ConnectorSession; import io.trino.spi.connector.SchemaTableName; +import io.trino.spi.predicate.TupleDomain; import io.trino.spi.type.TypeManager; import java.io.IOException; @@ -103,7 +104,8 @@ public void writeCheckpoint(ConnectorSession session, TableSnapshot snapshot) typeManager, fileSystem, fileFormatDataSourceStats, - Optional.empty()) + Optional.empty(), + TupleDomain.all()) .filter(entry -> entry.getMetaData() != null || entry.getProtocol() != null) .collect(toImmutableList()); @@ -135,7 +137,8 @@ public void writeCheckpoint(ConnectorSession session, TableSnapshot snapshot) typeManager, fileSystem, fileFormatDataSourceStats, - Optional.of(new MetadataAndProtocolEntry(metadataLogEntry.getMetaData(), protocolLogEntry.getProtocol()))) + Optional.of(new MetadataAndProtocolEntry(metadataLogEntry.getMetaData(), protocolLogEntry.getProtocol())), + TupleDomain.all()) .forEach(checkpointBuilder::addLogEntry); } diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeBasic.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeBasic.java index 283721d845b4..501c409d6679 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeBasic.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeBasic.java @@ -43,6 +43,7 @@ import org.apache.parquet.hadoop.metadata.FileMetaData; import org.apache.parquet.hadoop.metadata.ParquetMetadata; import org.apache.parquet.schema.PrimitiveType; +import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; @@ -1031,6 +1032,110 @@ public void testReadMultipartCheckpoint() assertThat(query("SELECT * FROM " + tableName)).matches("VALUES 1, 2, 3, 4, 5, 6, 7"); } + /** + * @see deltalake.partition_values_parsed + */ + @Test + public void testDeltaLakeWithPartitionValuesParsed() + throws Exception + { + testPartitionValuesParsed("deltalake/partition_values_parsed"); + } + + /** + * @see trino432.partition_values_parsed + */ + @Test + public void testTrinoWithoutPartitionValuesParsed() + throws Exception + { + testPartitionValuesParsed("trino432/partition_values_parsed"); + } + + private void testPartitionValuesParsed(String resourceName) + throws Exception + { + String tableName = "test_partition_values_parsed_checkpoint_" + randomNameSuffix(); + Path tableLocation = Files.createTempFile(tableName, null); + copyDirectoryContents(new File(Resources.getResource(resourceName).toURI()).toPath(), tableLocation); + assertUpdate("CALL system.register_table('%s', '%s', '%s')".formatted(getSession().getSchema().orElseThrow(), tableName, tableLocation.toUri())); + + Session session = Session.builder(getQueryRunner().getDefaultSession()) + .setCatalogSessionProperty("delta", "checkpoint_filtering_enabled", "true") + .build(); + + assertThat(query(session, "SELECT id FROM " + tableName + " WHERE int_part = 10 AND string_part = 'part1'")) + .matches("VALUES 1"); + assertThat(query(session, "SELECT id FROM " + tableName + " WHERE int_part != 10")) + .matches("VALUES 2"); + assertThat(query(session, "SELECT id FROM " + tableName + " WHERE int_part > 10")) + .matches("VALUES 2"); + assertThat(query(session, "SELECT id FROM " + tableName + " WHERE int_part >= 10")) + .matches("VALUES 1, 2"); + assertThat(query(session, "SELECT id FROM " + tableName + " WHERE int_part IN (10, 20)")) + .matches("VALUES 1, 2"); + assertThat(query("SELECT id FROM " + tableName + " WHERE int_part IS NULL AND string_part IS NULL")) + .matches("VALUES 3"); + assertThat(query("SELECT id FROM " + tableName + " WHERE int_part IS NOT NULL AND string_part IS NOT NULL")) + .matches("VALUES 1, 2"); + + assertThat(query("SELECT id FROM " + tableName + " WHERE int_part = 10 AND string_part = 'unmatched partition condition'")) + .returnsEmptyResult(); + assertThat(query("SELECT id FROM " + tableName + " WHERE int_part IS NULL AND string_part IS NOT NULL")) + .returnsEmptyResult(); + } + + /** + * @see deltalake.partition_values_parsed_all_types + */ + @Test + public void testDeltaLakeWithPartitionValuesParsedAllTypes() + throws Exception + { + String tableName = "test_partition_values_parsed_checkpoint_" + randomNameSuffix(); + Path tableLocation = Files.createTempFile(tableName, null); + copyDirectoryContents(new File(Resources.getResource("deltalake/partition_values_parsed_all_types").toURI()).toPath(), tableLocation); + assertUpdate("CALL system.register_table('%s', '%s', '%s')".formatted(getSession().getSchema().orElseThrow(), tableName, tableLocation.toUri())); + + assertPartitionValuesParsedCondition(tableName, 1, "part_boolean = true"); + assertPartitionValuesParsedCondition(tableName, 1, "part_tinyint = 1"); + assertPartitionValuesParsedCondition(tableName, 1, "part_smallint = 10"); + assertPartitionValuesParsedCondition(tableName, 1, "part_int = 100"); + assertPartitionValuesParsedCondition(tableName, 1, "part_bigint = 1000"); + assertPartitionValuesParsedCondition(tableName, 1, "part_short_decimal = CAST('123.12' AS DECIMAL(5,2))"); + assertPartitionValuesParsedCondition(tableName, 1, "part_long_decimal = CAST('123456789012345678.123' AS DECIMAL(21,3))"); + assertPartitionValuesParsedCondition(tableName, 1, "part_double = 1.2"); + assertPartitionValuesParsedCondition(tableName, 1, "part_float = 3.4"); + assertPartitionValuesParsedCondition(tableName, 1, "part_varchar = 'a'"); + assertPartitionValuesParsedCondition(tableName, 1, "part_date = DATE '2020-08-21'"); + assertPartitionValuesParsedCondition(tableName, 1, "part_timestamp = TIMESTAMP '2020-10-21 01:00:00.123 UTC'"); + assertPartitionValuesParsedCondition(tableName, 1, "part_timestamp_ntz = TIMESTAMP '2023-01-02 01:02:03.456'"); + + assertPartitionValuesParsedCondition(tableName, 3, "part_boolean IS NULL"); + assertPartitionValuesParsedCondition(tableName, 3, "part_tinyint IS NULL"); + assertPartitionValuesParsedCondition(tableName, 3, "part_smallint IS NULL"); + assertPartitionValuesParsedCondition(tableName, 3, "part_int IS NULL"); + assertPartitionValuesParsedCondition(tableName, 3, "part_bigint IS NULL"); + assertPartitionValuesParsedCondition(tableName, 3, "part_short_decimal IS NULL"); + assertPartitionValuesParsedCondition(tableName, 3, "part_long_decimal IS NULL"); + assertPartitionValuesParsedCondition(tableName, 3, "part_double IS NULL"); + assertPartitionValuesParsedCondition(tableName, 3, "part_float IS NULL"); + assertPartitionValuesParsedCondition(tableName, 3, "part_varchar IS NULL"); + assertPartitionValuesParsedCondition(tableName, 3, "part_date IS NULL"); + assertPartitionValuesParsedCondition(tableName, 3, "part_timestamp IS NULL"); + assertPartitionValuesParsedCondition(tableName, 3, "part_timestamp_ntz IS NULL"); + } + + private void assertPartitionValuesParsedCondition(String tableName, int id, @Language("SQL") String condition) + { + Session session = Session.builder(getQueryRunner().getDefaultSession()) + .setCatalogSessionProperty("delta", "checkpoint_filtering_enabled", "true") + .build(); + + assertThat(query(session, "SELECT id FROM " + tableName + " WHERE " + condition)) + .matches("VALUES " + id); + } + private static MetadataEntry loadMetadataEntry(long entryNumber, Path tableLocation) throws IOException { diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConfig.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConfig.java index f374ebb6c605..c2ddffcc048f 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConfig.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConfig.java @@ -53,6 +53,7 @@ public void testDefaults() .setMaxPartitionsPerWriter(100) .setUnsafeWritesEnabled(false) .setDefaultCheckpointWritingInterval(10) + .setCheckpointPartitionFilterEnabled(false) .setCheckpointRowStatisticsWritingEnabled(true) .setVacuumMinRetention(new Duration(7, DAYS)) .setHiveCatalogName(null) @@ -90,6 +91,7 @@ public void testExplicitPropertyMappings() .put("delta.max-partitions-per-writer", "200") .put("delta.enable-non-concurrent-writes", "true") .put("delta.default-checkpoint-writing-interval", "15") + .put("delta.checkpoint-filtering.enabled", "true") .put("delta.checkpoint-row-statistics-writing.enabled", "false") .put("delta.vacuum.min-retention", "13h") .put("delta.hive-catalog-name", "hive") @@ -125,6 +127,7 @@ public void testExplicitPropertyMappings() .setUnsafeWritesEnabled(true) .setDefaultCheckpointWritingInterval(15) .setCheckpointRowStatisticsWritingEnabled(false) + .setCheckpointPartitionFilterEnabled(true) .setVacuumMinRetention(new Duration(13, HOURS)) .setHiveCatalogName("hive") .setDynamicFilteringWaitTimeout(new Duration(30, MINUTES)) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeFileOperations.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeFileOperations.java index 0e4f043f94f0..6470b9978c2f 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeFileOperations.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeFileOperations.java @@ -204,6 +204,37 @@ public void testReadTableCheckpointInterval() assertUpdate("DROP TABLE test_read_checkpoint"); } + @Test + public void testReadPartitionTableWithCheckpointFiltering() + { + String catalog = getSession().getCatalog().orElseThrow(); + + assertUpdate("DROP TABLE IF EXISTS test_checkpoint_filtering"); + + assertUpdate("CREATE TABLE test_checkpoint_filtering(key varchar, data varchar) WITH (partitioned_by = ARRAY['key'], checkpoint_interval = 2)"); + assertUpdate("INSERT INTO test_checkpoint_filtering(key, data) VALUES ('p1', '1-abc'), ('p1', '1-def'), ('p2', '2-abc'), ('p2', '2-def')", 4); + assertUpdate("INSERT INTO test_checkpoint_filtering(key, data) VALUES ('p1', '1-baz'), ('p2', '2-baz')", 2); + + Session session = Session.builder(getSession()) + .setCatalogSessionProperty(catalog, "checkpoint_filtering_enabled", "true") + .build(); + + assertUpdate("CALL system.flush_metadata_cache(schema_name => CURRENT_SCHEMA, table_name => 'test_checkpoint_filtering')"); + assertFileSystemAccesses( + session, + "TABLE test_checkpoint_filtering", + ImmutableMultiset.builder() + .add(new FileOperation(LAST_CHECKPOINT, "_last_checkpoint", INPUT_FILE_NEW_STREAM)) + .addCopies(new FileOperation(CHECKPOINT, "00000000000000000002.checkpoint.parquet", INPUT_FILE_GET_LENGTH), 4) // TODO (https://github.com/trinodb/trino/issues/18916) should be checked once per query + .addCopies(new FileOperation(CHECKPOINT, "00000000000000000002.checkpoint.parquet", INPUT_FILE_NEW_STREAM), 2) // TODO (https://github.com/trinodb/trino/issues/18916) should be checked once per query + .add(new FileOperation(TRANSACTION_LOG_JSON, "00000000000000000003.json", INPUT_FILE_NEW_STREAM)) + .addCopies(new FileOperation(DATA, "key=p1/", INPUT_FILE_NEW_STREAM), 2) + .addCopies(new FileOperation(DATA, "key=p2/", INPUT_FILE_NEW_STREAM), 2) + .build()); + + assertUpdate("DROP TABLE test_checkpoint_filtering"); + } + @Test public void testReadWholePartition() { diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSplitManager.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSplitManager.java index dd165811ee88..5c9f3dd08da9 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSplitManager.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSplitManager.java @@ -185,7 +185,7 @@ private DeltaLakeSplitManager setupSplitManager(List addFileEntrie new ParquetReaderConfig()) { @Override - public List getActiveFiles(TableSnapshot tableSnapshot, MetadataEntry metadataEntry, ProtocolEntry protocolEntry, ConnectorSession session) + public List getActiveFiles(TableSnapshot tableSnapshot, MetadataEntry metadataEntry, ProtocolEntry protocolEntry, TupleDomain partitionConstraint, ConnectorSession session) { return addFileEntries; } diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestTransactionLogAccess.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestTransactionLogAccess.java index 4e2b46578e14..0a39614c6292 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestTransactionLogAccess.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestTransactionLogAccess.java @@ -66,6 +66,7 @@ import static io.trino.filesystem.TrackingFileSystemFactory.OperationType.INPUT_FILE_GET_LENGTH; import static io.trino.filesystem.TrackingFileSystemFactory.OperationType.INPUT_FILE_NEW_STREAM; import static io.trino.plugin.deltalake.DeltaLakeColumnType.REGULAR; +import static io.trino.plugin.deltalake.DeltaTestingConnectorSession.SESSION; import static io.trino.plugin.deltalake.transactionlog.DeltaLakeSchemaSupport.extractColumnMetadata; import static io.trino.plugin.deltalake.transactionlog.TransactionLogParser.LAST_CHECKPOINT_FILENAME; import static io.trino.plugin.deltalake.transactionlog.TransactionLogUtil.TRANSACTION_LOG_DIRECTORY; @@ -73,7 +74,6 @@ import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_STATS; import static io.trino.spi.type.TimeZoneKey.UTC_KEY; import static io.trino.testing.MultisetAssertions.assertMultisetsEqual; -import static io.trino.testing.TestingConnectorSession.SESSION; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.lang.String.format; import static java.time.ZoneOffset.UTC; diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestingDeltaLakeUtils.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestingDeltaLakeUtils.java index 618c953171eb..251985eb1afe 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestingDeltaLakeUtils.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestingDeltaLakeUtils.java @@ -28,7 +28,7 @@ import java.util.stream.Stream; import static io.trino.plugin.deltalake.DeltaLakeQueryRunner.DELTA_CATALOG; -import static io.trino.testing.TestingConnectorSession.SESSION; +import static io.trino.plugin.deltalake.DeltaTestingConnectorSession.SESSION; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; public final class TestingDeltaLakeUtils diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/TestTableSnapshot.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/TestTableSnapshot.java index b7301d812581..95c1869a3d37 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/TestTableSnapshot.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/TestTableSnapshot.java @@ -28,6 +28,7 @@ import io.trino.plugin.hive.FileFormatDataSourceStats; import io.trino.plugin.hive.parquet.ParquetReaderConfig; import io.trino.spi.connector.SchemaTableName; +import io.trino.spi.predicate.TupleDomain; import io.trino.spi.type.TypeManager; import io.trino.testing.TestingConnectorContext; import org.junit.jupiter.api.BeforeEach; @@ -141,7 +142,7 @@ public void readsCheckpointFile() ProtocolEntry protocolEntry = transactionLogAccess.getProtocolEntry(SESSION, tableSnapshot); tableSnapshot.setCachedMetadata(Optional.of(metadataEntry)); try (Stream stream = tableSnapshot.getCheckpointTransactionLogEntries( - SESSION, ImmutableSet.of(ADD), checkpointSchemaManager, TESTING_TYPE_MANAGER, trackingFileSystem, new FileFormatDataSourceStats(), Optional.of(new MetadataAndProtocolEntry(metadataEntry, protocolEntry)))) { + SESSION, ImmutableSet.of(ADD), checkpointSchemaManager, TESTING_TYPE_MANAGER, trackingFileSystem, new FileFormatDataSourceStats(), Optional.of(new MetadataAndProtocolEntry(metadataEntry, protocolEntry)), TupleDomain.all())) { List entries = stream.collect(toImmutableList()); assertThat(entries).hasSize(9); @@ -183,7 +184,7 @@ public void readsCheckpointFile() // lets read two entry types in one call; add and protocol try (Stream stream = tableSnapshot.getCheckpointTransactionLogEntries( - SESSION, ImmutableSet.of(ADD, PROTOCOL), checkpointSchemaManager, TESTING_TYPE_MANAGER, trackingFileSystem, new FileFormatDataSourceStats(), Optional.of(new MetadataAndProtocolEntry(metadataEntry, protocolEntry)))) { + SESSION, ImmutableSet.of(ADD, PROTOCOL), checkpointSchemaManager, TESTING_TYPE_MANAGER, trackingFileSystem, new FileFormatDataSourceStats(), Optional.of(new MetadataAndProtocolEntry(metadataEntry, protocolEntry)), TupleDomain.all())) { List entries = stream.collect(toImmutableList()); assertThat(entries).hasSize(10); diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointEntryIterator.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointEntryIterator.java index d31ca20fe438..245b602ad40b 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointEntryIterator.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointEntryIterator.java @@ -24,6 +24,7 @@ import io.trino.filesystem.TrinoOutputFile; import io.trino.filesystem.hdfs.HdfsFileSystemFactory; import io.trino.parquet.writer.ParquetWriterOptions; +import io.trino.plugin.deltalake.DeltaLakeColumnHandle; import io.trino.plugin.deltalake.DeltaLakeConfig; import io.trino.plugin.deltalake.transactionlog.AddFileEntry; import io.trino.plugin.deltalake.transactionlog.DeltaLakeTransactionLogEntry; @@ -32,6 +33,9 @@ import io.trino.plugin.deltalake.transactionlog.RemoveFileEntry; import io.trino.plugin.hive.FileFormatDataSourceStats; import io.trino.plugin.hive.parquet.ParquetReaderConfig; +import io.trino.spi.predicate.TupleDomain; +import io.trino.spi.type.Int128; +import io.trino.spi.type.Type; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -41,16 +45,23 @@ import java.io.File; import java.io.IOException; import java.net.URI; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZonedDateTime; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.OptionalInt; import java.util.Set; import java.util.UUID; import java.util.stream.IntStream; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.io.Resources.getResource; +import static com.google.common.math.LongMath.divide; +import static io.airlift.slice.Slices.utf8Slice; +import static io.trino.plugin.deltalake.DeltaLakeColumnType.REGULAR; import static io.trino.plugin.deltalake.DeltaTestingConnectorSession.SESSION; import static io.trino.plugin.deltalake.transactionlog.checkpoint.CheckpointEntryIterator.EntryType.ADD; import static io.trino.plugin.deltalake.transactionlog.checkpoint.CheckpointEntryIterator.EntryType.COMMIT; @@ -60,7 +71,29 @@ import static io.trino.plugin.deltalake.transactionlog.checkpoint.CheckpointEntryIterator.EntryType.TRANSACTION; import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_STATS; +import static io.trino.spi.predicate.Domain.notNull; +import static io.trino.spi.predicate.Domain.onlyNull; +import static io.trino.spi.predicate.Domain.singleValue; +import static io.trino.spi.type.BigintType.BIGINT; +import static io.trino.spi.type.BooleanType.BOOLEAN; +import static io.trino.spi.type.DateTimeEncoding.packDateTimeWithZone; +import static io.trino.spi.type.DateType.DATE; +import static io.trino.spi.type.DecimalType.createDecimalType; +import static io.trino.spi.type.DoubleType.DOUBLE; +import static io.trino.spi.type.IntegerType.INTEGER; +import static io.trino.spi.type.RealType.REAL; +import static io.trino.spi.type.SmallintType.SMALLINT; +import static io.trino.spi.type.TimeZoneKey.UTC_KEY; +import static io.trino.spi.type.TimestampType.TIMESTAMP_MICROS; +import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MILLIS; +import static io.trino.spi.type.Timestamps.MICROSECONDS_PER_SECOND; +import static io.trino.spi.type.Timestamps.NANOSECONDS_PER_MICROSECOND; +import static io.trino.spi.type.TinyintType.TINYINT; +import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; +import static java.lang.Float.floatToIntBits; +import static java.math.RoundingMode.UNNECESSARY; +import static java.time.ZoneOffset.UTC; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; @@ -91,7 +124,7 @@ public void testReadNoEntries() throws Exception { URI checkpointUri = getResource(TEST_CHECKPOINT).toURI(); - assertThatThrownBy(() -> createCheckpointEntryIterator(checkpointUri, ImmutableSet.of(), Optional.empty(), Optional.empty())) + assertThatThrownBy(() -> createCheckpointEntryIterator(checkpointUri, ImmutableSet.of(), Optional.empty(), Optional.empty(), TupleDomain.all())) .isInstanceOf(IllegalArgumentException.class) .hasMessage("fields is empty"); } @@ -135,7 +168,7 @@ public void testReadProtocolEntries() throws Exception { URI checkpointUri = getResource(TEST_CHECKPOINT).toURI(); - CheckpointEntryIterator checkpointEntryIterator = createCheckpointEntryIterator(checkpointUri, ImmutableSet.of(PROTOCOL), Optional.empty(), Optional.empty()); + CheckpointEntryIterator checkpointEntryIterator = createCheckpointEntryIterator(checkpointUri, ImmutableSet.of(PROTOCOL), Optional.empty(), Optional.empty(), TupleDomain.all()); List entries = ImmutableList.copyOf(checkpointEntryIterator); assertThat(entries).hasSize(1); @@ -153,7 +186,7 @@ public void testReadMetadataAndProtocolEntry() throws Exception { URI checkpointUri = getResource(TEST_CHECKPOINT).toURI(); - CheckpointEntryIterator checkpointEntryIterator = createCheckpointEntryIterator(checkpointUri, ImmutableSet.of(METADATA, PROTOCOL), Optional.empty(), Optional.empty()); + CheckpointEntryIterator checkpointEntryIterator = createCheckpointEntryIterator(checkpointUri, ImmutableSet.of(METADATA, PROTOCOL), Optional.empty(), Optional.empty(), TupleDomain.all()); List entries = ImmutableList.copyOf(checkpointEntryIterator); assertThat(entries).hasSize(2); @@ -196,7 +229,7 @@ public void testReadAddEntries() throws Exception { URI checkpointUri = getResource(TEST_CHECKPOINT).toURI(); - CheckpointEntryIterator checkpointEntryIterator = createCheckpointEntryIterator(checkpointUri, ImmutableSet.of(ADD), Optional.of(readMetadataEntry(checkpointUri)), Optional.of(readProtocolEntry(checkpointUri))); + CheckpointEntryIterator checkpointEntryIterator = createCheckpointEntryIterator(checkpointUri, ImmutableSet.of(ADD), Optional.of(readMetadataEntry(checkpointUri)), Optional.of(readProtocolEntry(checkpointUri)), TupleDomain.all()); List entries = ImmutableList.copyOf(checkpointEntryIterator); assertThat(entries).hasSize(9); @@ -236,6 +269,146 @@ public void testReadAddEntries() Optional.empty())); } + @Test + public void testReadAddEntriesPartitionPruning() + throws Exception + { + String checkpoint = "deltalake/partition_values_parsed/_delta_log/00000000000000000003.checkpoint.parquet"; + URI checkpointUri = getResource(checkpoint).toURI(); + + DeltaLakeColumnHandle stringPartField = new DeltaLakeColumnHandle( + "string_part", + VARCHAR, + OptionalInt.empty(), + "string_part", + VARCHAR, + REGULAR, + Optional.empty()); + + DeltaLakeColumnHandle intPartField = new DeltaLakeColumnHandle( + "int_part", + BIGINT, + OptionalInt.empty(), + "int_part", + BIGINT, + REGULAR, + Optional.empty()); + + // The domain specifies all partition columns + CheckpointEntryIterator partitionsEntryIterator = createCheckpointEntryIterator( + checkpointUri, + ImmutableSet.of(ADD), + Optional.of(readMetadataEntry(checkpointUri)), + Optional.of(readProtocolEntry(checkpointUri)), + TupleDomain.withColumnDomains(ImmutableMap.of(intPartField, singleValue(BIGINT, 10L), stringPartField, singleValue(VARCHAR, utf8Slice("part1"))))); + List partitionsEntries = ImmutableList.copyOf(partitionsEntryIterator); + + assertThat(partitionsEntryIterator.getCompletedPositions().orElseThrow()).isEqualTo(5); + assertThat(partitionsEntries) + .hasSize(1) + .extracting(entry -> entry.getAdd().getPath()) + .containsExactly("int_part=10/string_part=part1/part-00000-383afb1a-87de-4e70-86ab-c21ae44c7f3f.c000.snappy.parquet"); + + // The domain specifies a part of partition columns + CheckpointEntryIterator partitionEntryIterator = createCheckpointEntryIterator( + checkpointUri, + ImmutableSet.of(ADD), + Optional.of(readMetadataEntry(checkpointUri)), + Optional.of(readProtocolEntry(checkpointUri)), + TupleDomain.withColumnDomains(ImmutableMap.of(intPartField, singleValue(BIGINT, 10L)))); + List partitionEntries = ImmutableList.copyOf(partitionEntryIterator); + + assertThat(partitionEntryIterator.getCompletedPositions().orElseThrow()).isEqualTo(5); + assertThat(partitionEntries) + .hasSize(1) + .extracting(entry -> entry.getAdd().getPath()) + .containsExactly("int_part=10/string_part=part1/part-00000-383afb1a-87de-4e70-86ab-c21ae44c7f3f.c000.snappy.parquet"); + + // Verify empty iterator when the condition doesn't match + CheckpointEntryIterator emptyIterator = createCheckpointEntryIterator( + checkpointUri, + ImmutableSet.of(ADD), + Optional.of(readMetadataEntry(checkpointUri)), + Optional.of(readProtocolEntry(checkpointUri)), + TupleDomain.withColumnDomains(ImmutableMap.of( + intPartField, singleValue(BIGINT, 10L), + stringPartField, singleValue(VARCHAR, utf8Slice("unmatched partition condition"))))); + assertThat(ImmutableList.copyOf(emptyIterator)).isEmpty(); + + // Verify IS NULL condition + CheckpointEntryIterator isNullIterator = createCheckpointEntryIterator( + checkpointUri, + ImmutableSet.of(ADD), + Optional.of(readMetadataEntry(checkpointUri)), + Optional.of(readProtocolEntry(checkpointUri)), + TupleDomain.withColumnDomains(ImmutableMap.of( + intPartField, onlyNull(BIGINT), + stringPartField, onlyNull(VARCHAR)))); + assertThat(ImmutableList.copyOf(isNullIterator)) + .hasSize(1) + .extracting(entry -> entry.getAdd().getPath()) + .containsExactly("int_part=__HIVE_DEFAULT_PARTITION__/string_part=__HIVE_DEFAULT_PARTITION__/part-00000-dcb29d13-eeca-4fa6-a8bf-860da0131a5c.c000.snappy.parquet"); + + // Verify IS NOT NULL condition + CheckpointEntryIterator isNotNullIterator = createCheckpointEntryIterator( + checkpointUri, + ImmutableSet.of(ADD), + Optional.of(readMetadataEntry(checkpointUri)), + Optional.of(readProtocolEntry(checkpointUri)), + TupleDomain.withColumnDomains(ImmutableMap.of( + intPartField, notNull(BIGINT), + stringPartField, notNull(VARCHAR)))); + assertThat(ImmutableList.copyOf(isNotNullIterator)) + .hasSize(2) + .extracting(entry -> entry.getAdd().getPath()) + .containsExactly( + "int_part=10/string_part=part1/part-00000-383afb1a-87de-4e70-86ab-c21ae44c7f3f.c000.snappy.parquet", + "int_part=20/string_part=part2/part-00000-e0b4887e-95f6-4ce1-b96c-32c5cf472476.c000.snappy.parquet"); + } + + @Test + public void testReadAddEntriesPartitionPruningAllTypes() + throws Exception + { + String checkpoint = "deltalake/partition_values_parsed_all_types/_delta_log/00000000000000000003.checkpoint.parquet"; + URI checkpointUri = getResource(checkpoint).toURI(); + + assertPartitionValuesParsedCondition(checkpointUri, "part_boolean", BOOLEAN, true); + assertPartitionValuesParsedCondition(checkpointUri, "part_tinyint", TINYINT, 1L); + assertPartitionValuesParsedCondition(checkpointUri, "part_smallint", SMALLINT, 10L); + assertPartitionValuesParsedCondition(checkpointUri, "part_int", INTEGER, 100L); + assertPartitionValuesParsedCondition(checkpointUri, "part_bigint", BIGINT, 1000L); + assertPartitionValuesParsedCondition(checkpointUri, "part_short_decimal", createDecimalType(5, 2), 12312L); + assertPartitionValuesParsedCondition(checkpointUri, "part_long_decimal", createDecimalType(21, 3), Int128.valueOf("123456789012345678123")); + assertPartitionValuesParsedCondition(checkpointUri, "part_double", DOUBLE, 1.2); + assertPartitionValuesParsedCondition(checkpointUri, "part_float", REAL, (long) floatToIntBits(3.4f)); + assertPartitionValuesParsedCondition(checkpointUri, "part_varchar", VARCHAR, utf8Slice("a")); + assertPartitionValuesParsedCondition(checkpointUri, "part_date", DATE, LocalDate.parse("2020-08-21").toEpochDay()); + ZonedDateTime zonedDateTime = LocalDateTime.parse("2020-10-21T01:00:00.123").atZone(UTC); + long timestampValue = packDateTimeWithZone(zonedDateTime.toInstant().toEpochMilli(), UTC_KEY); + assertPartitionValuesParsedCondition(checkpointUri, "part_timestamp", TIMESTAMP_TZ_MILLIS, timestampValue); + LocalDateTime timestampNtz = LocalDateTime.parse("2023-01-02T01:02:03.456"); + long timestampNtzValue = timestampNtz.toEpochSecond(UTC) * MICROSECONDS_PER_SECOND + divide(timestampNtz.getNano(), NANOSECONDS_PER_MICROSECOND, UNNECESSARY); + assertPartitionValuesParsedCondition(checkpointUri, "part_timestamp_ntz", TIMESTAMP_MICROS, timestampNtzValue); + } + + private void assertPartitionValuesParsedCondition(URI checkpointUri, String columnName, Type type, Object value) + throws IOException + { + DeltaLakeColumnHandle intPartField = new DeltaLakeColumnHandle(columnName, type, OptionalInt.empty(), columnName, type, REGULAR, Optional.empty()); + + CheckpointEntryIterator partitionEntryIterator = createCheckpointEntryIterator( + checkpointUri, + ImmutableSet.of(ADD), + Optional.of(readMetadataEntry(checkpointUri)), + Optional.of(readProtocolEntry(checkpointUri)), + TupleDomain.withColumnDomains(ImmutableMap.of(intPartField, singleValue(type, value)))); + List partitionEntries = ImmutableList.copyOf(partitionEntryIterator); + + assertThat(partitionEntryIterator.getCompletedPositions().orElseThrow()).isEqualTo(5); + assertThat(partitionEntries).hasSize(1); + } + @Test public void testReadAllEntries() throws Exception @@ -246,7 +419,8 @@ public void testReadAllEntries() checkpointUri, ImmutableSet.of(METADATA, PROTOCOL, TRANSACTION, ADD, REMOVE, COMMIT), Optional.of(readMetadataEntry(checkpointUri)), - Optional.of(readProtocolEntry(checkpointUri))); + Optional.of(readProtocolEntry(checkpointUri)), + TupleDomain.all()); List entries = ImmutableList.copyOf(checkpointEntryIterator); assertThat(entries).hasSize(17); @@ -309,7 +483,8 @@ public void testSkipRemoveEntries() "metadataFormatProvider", ImmutableMap.of()), "{\"type\":\"struct\",\"fields\":" + - "[{\"name\":\"ts\",\"type\":\"timestamp\",\"nullable\":true,\"metadata\":{}}]}", + "[{\"name\":\"ts\",\"type\":\"timestamp\",\"nullable\":true,\"metadata\":{}}," + + "{\"name\":\"part_key\",\"type\":\"timestamp\",\"nullable\":true,\"metadata\":{}}]}", ImmutableList.of("part_key"), ImmutableMap.of(), 1000); @@ -367,16 +542,17 @@ public void testSkipRemoveEntries() writer.write(entries, createOutputFile(targetPath)); CheckpointEntryIterator metadataAndProtocolEntryIterator = - createCheckpointEntryIterator(URI.create(targetPath), ImmutableSet.of(METADATA, PROTOCOL), Optional.empty(), Optional.empty()); + createCheckpointEntryIterator(URI.create(targetPath), ImmutableSet.of(METADATA, PROTOCOL), Optional.empty(), Optional.empty(), TupleDomain.all()); CheckpointEntryIterator addEntryIterator = createCheckpointEntryIterator( URI.create(targetPath), ImmutableSet.of(ADD), Optional.of(metadataEntry), - Optional.of(protocolEntry)); + Optional.of(protocolEntry), + TupleDomain.all()); CheckpointEntryIterator removeEntryIterator = - createCheckpointEntryIterator(URI.create(targetPath), ImmutableSet.of(REMOVE), Optional.empty(), Optional.empty()); + createCheckpointEntryIterator(URI.create(targetPath), ImmutableSet.of(REMOVE), Optional.empty(), Optional.empty(), TupleDomain.all()); CheckpointEntryIterator txnEntryIterator = - createCheckpointEntryIterator(URI.create(targetPath), ImmutableSet.of(TRANSACTION), Optional.empty(), Optional.empty()); + createCheckpointEntryIterator(URI.create(targetPath), ImmutableSet.of(TRANSACTION), Optional.empty(), Optional.empty(), TupleDomain.all()); assertThat(Iterators.size(metadataAndProtocolEntryIterator)).isEqualTo(2); assertThat(Iterators.size(addEntryIterator)).isEqualTo(1); @@ -392,18 +568,23 @@ public void testSkipRemoveEntries() private MetadataEntry readMetadataEntry(URI checkpointUri) throws IOException { - CheckpointEntryIterator checkpointEntryIterator = createCheckpointEntryIterator(checkpointUri, ImmutableSet.of(METADATA), Optional.empty(), Optional.empty()); + CheckpointEntryIterator checkpointEntryIterator = createCheckpointEntryIterator(checkpointUri, ImmutableSet.of(METADATA), Optional.empty(), Optional.empty(), TupleDomain.all()); return Iterators.getOnlyElement(checkpointEntryIterator).getMetaData(); } private ProtocolEntry readProtocolEntry(URI checkpointUri) throws IOException { - CheckpointEntryIterator checkpointEntryIterator = createCheckpointEntryIterator(checkpointUri, ImmutableSet.of(PROTOCOL), Optional.empty(), Optional.empty()); + CheckpointEntryIterator checkpointEntryIterator = createCheckpointEntryIterator(checkpointUri, ImmutableSet.of(PROTOCOL), Optional.empty(), Optional.empty(), TupleDomain.all()); return Iterators.getOnlyElement(checkpointEntryIterator).getProtocol(); } - private CheckpointEntryIterator createCheckpointEntryIterator(URI checkpointUri, Set entryTypes, Optional metadataEntry, Optional protocolEntry) + private CheckpointEntryIterator createCheckpointEntryIterator( + URI checkpointUri, + Set entryTypes, + Optional metadataEntry, + Optional protocolEntry, + TupleDomain partitionConstraint) throws IOException { TrinoFileSystem fileSystem = new HdfsFileSystemFactory(HDFS_ENVIRONMENT, HDFS_FILE_SYSTEM_STATS).create(SESSION); @@ -421,7 +602,8 @@ private CheckpointEntryIterator createCheckpointEntryIterator(URI checkpointUri, new FileFormatDataSourceStats(), new ParquetReaderConfig().toParquetReaderOptions(), true, - new DeltaLakeConfig().getDomainCompactionThreshold()); + new DeltaLakeConfig().getDomainCompactionThreshold(), + partitionConstraint); } private static TrinoOutputFile createOutputFile(String path) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointWriter.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointWriter.java index c78b09b42304..31aec13a79c3 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointWriter.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointWriter.java @@ -36,6 +36,7 @@ import io.trino.plugin.hive.parquet.ParquetReaderConfig; import io.trino.spi.block.Block; import io.trino.spi.block.SqlRow; +import io.trino.spi.predicate.TupleDomain; import io.trino.spi.type.BigintType; import io.trino.spi.type.Int128; import io.trino.spi.type.IntegerType; @@ -479,7 +480,8 @@ private CheckpointEntries readCheckpoint(String checkpointPath, MetadataEntry me new FileFormatDataSourceStats(), new ParquetReaderConfig().toParquetReaderOptions(), rowStatisticsEnabled, - new DeltaLakeConfig().getDomainCompactionThreshold()); + new DeltaLakeConfig().getDomainCompactionThreshold(), + TupleDomain.all()); CheckpointBuilder checkpointBuilder = new CheckpointBuilder(); while (checkpointEntryIterator.hasNext()) { diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/statistics/TestDeltaLakeFileStatistics.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/statistics/TestDeltaLakeFileStatistics.java index 7d38c1271e53..5e785a542f4c 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/statistics/TestDeltaLakeFileStatistics.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/statistics/TestDeltaLakeFileStatistics.java @@ -27,6 +27,7 @@ import io.trino.plugin.deltalake.transactionlog.checkpoint.CheckpointSchemaManager; import io.trino.plugin.hive.FileFormatDataSourceStats; import io.trino.plugin.hive.parquet.ParquetReaderConfig; +import io.trino.spi.predicate.TupleDomain; import io.trino.spi.type.ArrayType; import io.trino.spi.type.DecimalType; import io.trino.spi.type.DoubleType; @@ -104,7 +105,8 @@ public void testParseParquetStatistics() new FileFormatDataSourceStats(), new ParquetReaderConfig().toParquetReaderOptions(), true, - new DeltaLakeConfig().getDomainCompactionThreshold()); + new DeltaLakeConfig().getDomainCompactionThreshold(), + TupleDomain.all()); MetadataEntry metadataEntry = getOnlyElement(metadataEntryIterator).getMetaData(); CheckpointEntryIterator protocolEntryIterator = new CheckpointEntryIterator( checkpointFile, @@ -118,7 +120,8 @@ public void testParseParquetStatistics() new FileFormatDataSourceStats(), new ParquetReaderConfig().toParquetReaderOptions(), true, - new DeltaLakeConfig().getDomainCompactionThreshold()); + new DeltaLakeConfig().getDomainCompactionThreshold(), + TupleDomain.all()); ProtocolEntry protocolEntry = getOnlyElement(protocolEntryIterator).getProtocol(); CheckpointEntryIterator checkpointEntryIterator = new CheckpointEntryIterator( @@ -133,7 +136,8 @@ public void testParseParquetStatistics() new FileFormatDataSourceStats(), new ParquetReaderConfig().toParquetReaderOptions(), true, - new DeltaLakeConfig().getDomainCompactionThreshold()); + new DeltaLakeConfig().getDomainCompactionThreshold(), + TupleDomain.all()); DeltaLakeTransactionLogEntry matchingAddFileEntry = null; while (checkpointEntryIterator.hasNext()) { DeltaLakeTransactionLogEntry entry = checkpointEntryIterator.next(); diff --git a/plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed/README.md b/plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed/README.md new file mode 100644 index 000000000000..a3ae6655153f --- /dev/null +++ b/plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed/README.md @@ -0,0 +1,21 @@ +Data generated using OSS Delta Lake 3.0.0: + +```sql +CREATE TABLE test_partition_values_parsed ( + id INT, + int_part INT, + string_part STRING +) +USING delta +PARTITIONED BY (int_part, string_part) +LOCATION 's3://test-bucket/test_partition_values_parsed' +TBLPROPERTIES ( + delta.checkpoint.writeStatsAsStruct = true, + delta.checkpoint.writeStatsAsJson = false, + delta.checkpointInterval = 1 +); + +INSERT INTO test_partition_values_parsed VALUES (1, 10, 'part1'); +INSERT INTO test_partition_values_parsed VALUES (2, 20, 'part2'); +INSERT INTO test_partition_values_parsed VALUES (3, NULL, NULL); +``` diff --git a/plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed/_delta_log/00000000000000000000.json b/plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed/_delta_log/00000000000000000000.json new file mode 100644 index 000000000000..9c3414fefca2 --- /dev/null +++ b/plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed/_delta_log/00000000000000000000.json @@ -0,0 +1,3 @@ +{"commitInfo":{"timestamp":1699426607713,"operation":"CREATE TABLE","operationParameters":{"isManaged":"false","description":null,"partitionBy":"[\"int_part\",\"string_part\"]","properties":"{\"delta.checkpoint.writeStatsAsStruct\":\"true\",\"delta.checkpoint.writeStatsAsJson\":\"false\",\"delta.checkpointInterval\":\"1\"}"},"isolationLevel":"Serializable","isBlindAppend":true,"operationMetrics":{},"engineInfo":"Apache-Spark/3.5.0 Delta-Lake/3.0.0","txnId":"8e5c07fa-7101-4a83-899b-6d124f3bb8d6"}} +{"metaData":{"id":"2f4075c9-1e3f-4fcc-b5b3-1f15d800a400","format":{"provider":"parquet","options":{}},"schemaString":"{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"int_part\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"string_part\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]}","partitionColumns":["int_part","string_part"],"configuration":{"delta.checkpoint.writeStatsAsStruct":"true","delta.checkpoint.writeStatsAsJson":"false","delta.checkpointInterval":"1"},"createdTime":1699426607271}} +{"protocol":{"minReaderVersion":1,"minWriterVersion":2}} diff --git a/plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed/_delta_log/00000000000000000001.checkpoint.parquet b/plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed/_delta_log/00000000000000000001.checkpoint.parquet new file mode 100644 index 0000000000000000000000000000000000000000..fa1bbfd0199f92012b6e883f9d91d9dffe9d7586 GIT binary patch literal 16816 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 b-RJ8T7r_Hgc%J`1{+sJ@bKE-kUo!j`hD4H9 literal 0 HcmV?d00001 diff --git a/plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed/_delta_log/00000000000000000002.json b/plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed/_delta_log/00000000000000000002.json new file mode 100644 index 000000000000..e6f9284d3789 --- /dev/null +++ b/plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed/_delta_log/00000000000000000002.json @@ -0,0 +1,2 @@ +{"commitInfo":{"timestamp":1699426626909,"operation":"WRITE","operationParameters":{"mode":"Append","partitionBy":"[]"},"readVersion":1,"isolationLevel":"Serializable","isBlindAppend":true,"operationMetrics":{"numFiles":"1","numOutputRows":"1","numOutputBytes":"452"},"engineInfo":"Apache-Spark/3.5.0 Delta-Lake/3.0.0","txnId":"64a2fdf1-ecd5-44b2-ad60-42d487414f5f"}} +{"add":{"path":"int_part=20/string_part=part2/part-00000-e0b4887e-95f6-4ce1-b96c-32c5cf472476.c000.snappy.parquet","partitionValues":{"int_part":"20","string_part":"part2"},"size":452,"modificationTime":1699426625000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"id\":2},\"maxValues\":{\"id\":2},\"nullCount\":{\"id\":0}}"}} diff --git a/plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed/_delta_log/00000000000000000003.checkpoint.parquet b/plugin/trino-delta-lake/src/test/resources/deltalake/partition_values_parsed/_delta_log/00000000000000000003.checkpoint.parquet new file mode 100644 index 0000000000000000000000000000000000000000..7215b844f02d48b854ff2c6cdeb3d951d773071b GIT binary patch literal 16925 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 literal 0 HcmV?d00001 diff --git a/plugin/trino-delta-lake/src/test/resources/trino432/partition_values_parsed/_delta_log/00000000000000000002.json b/plugin/trino-delta-lake/src/test/resources/trino432/partition_values_parsed/_delta_log/00000000000000000002.json new file mode 100644 index 000000000000..d3461be234eb --- /dev/null +++ b/plugin/trino-delta-lake/src/test/resources/trino432/partition_values_parsed/_delta_log/00000000000000000002.json @@ -0,0 +1,2 @@ +{"commitInfo":{"version":2,"timestamp":1699495426681,"userId":"yuya.ebihara","userName":"yuya.ebihara","operation":"WRITE","operationParameters":{"queryId":"20231109_020344_00033_9eakg"},"clusterId":"trino-testversion-312e43c0-2e3d-4039-8fd3-9fe5e100d186","readVersion":1,"isolationLevel":"WriteSerializable","isBlindAppend":true}} +{"add":{"path":"int_part=20/string_part=part2/20231109_020344_00033_9eakg_e6448c08-9b43-4fa1-8288-823fd3d692b9","partitionValues":{"int_part":"20","string_part":"part2"},"size":199,"modificationTime":1699495426664,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"id\":2},\"maxValues\":{\"id\":2},\"nullCount\":{\"id\":0}}","tags":{}}} diff --git a/plugin/trino-delta-lake/src/test/resources/trino432/partition_values_parsed/_delta_log/00000000000000000003.checkpoint.parquet b/plugin/trino-delta-lake/src/test/resources/trino432/partition_values_parsed/_delta_log/00000000000000000003.checkpoint.parquet new file mode 100644 index 0000000000000000000000000000000000000000..fb248e83da3ff566a55060fb9e37bc1555a18640 GIT binary patch literal 7077 zcmeHMO>7&-6`ox#DUy5L*UT9Gt)TipgDe#(YVkEaII4`;$LP4t>5L0`;j6>EckmOj#)Y!tzAAWHU*1!r8pLDDk4YL(q1~7P*GK1N^ z-tyctzjEV$xg4@Q<)1KN9`N^oou$OHx!mH?jW==^-?()C`toY-&GRd(ORGy)ujF#G zE#G8@PEnM2A~#30TN^o5QgNe+a5_A&-zc4_^s)UWn&>pWY{_1ROXe6P?Q^;y|s%q8T`w z@D*Rz<9cdN&Cg*0r||qT{D3u2ZSLt)}w*>!MN0h1>Ack7jX$Bx< zAE2JV5@lkV!9Q&=VG?v}l}$cQQLg(;oy0F&azk)AqA-VIQoA1TK)O#f-<_t|FETRl z!7tN`V>Iwqa}9`vWexjLl9#+RTwjE6jROdeo#rLJsGmm5lb{#&D$ua+92nI}qfjr_ zjAFH7jsuKmy;?7826#84^499rt)foq?G+<}5fnGQ{P4uNaViC<8kAyG3)K?cEDtLKaKu)K25P!N z>J6&T5GhDW4k2(^V_7t!c3~l2uCj=lobyBWw5z8~2wq#O{LpnsLpHnS^(#o~A z1U;%2{lUrLmmwKV;;~@T>p{V=9WY?Km>2!xE+^jQWN%iQ9wt2=Zk6Z_zd$q;oP=l! zhK7UHcPlK&vCSxALcY&sQ~m>KoN>@5eY_88#@3t}EbSo4$S;YPAw8{ zy673}mF_gy=4LksE9SQ2ERC;>-Rt57`D4ECMu&uIg=QOU*VPxS%V|2IH>3TxOZ+7- zf8xqYpK-(1A6A(H$7{GE#4cXUA_B@5v(ov>5RRkUMrjQmR1Cef^S`i1@Ynxa(snqg zmY>_ztlQ%d5Qp;C{!wPpjxs$t7t6Co%=`*Y_n2siTkx!A7!P96nVR*9 zW0b+)JXuK^ROJtz0Y`lfs^^v15iS?SW%*AdS&56mA4tJ|yK#l?`_`FPvdDQ(yeL0} zd)I3#cm)rufi*0L7>yRko>~^rN!nf*hLVTWtAL zxA;Ji?~Z0uLiiqMo#lf&FRZP#-?GDYWu!G*80gJ7BE(tNxtQ(1PsCZ*M>V@X!}M%= z&34)&1Q%(A(OpXP9O!`fJ|}*_$zL3ke|&5bG&d-0L?E9NXl@R@9Y^pru2+1sKn|L8azbgS_rd9BtD<-sj~-!-&1C0&#mwsSlK|9K@dc7xz`NgbwPW~X+N07m^c3AR@v^IO%pSo2}nKgd< zuAx`phbQ~+vx19B(Odkpty1zQ{m8ArVUl;p4oY|TR2N(E_pgZl)Xz_We@cOOSo>3usm>C&?q$rc5jt5UkQD$C#d~tF{YHp$^ z6Hri4I%kC`&CW&dkqKC`m0Yfw47`N;7j(6!P>F NH5ujsbq4^w004thBAEaH literal 0 HcmV?d00001 diff --git a/plugin/trino-delta-lake/src/test/resources/trino432/partition_values_parsed/int_part=20/string_part=part2/20231109_020344_00033_9eakg_e6448c08-9b43-4fa1-8288-823fd3d692b9 b/plugin/trino-delta-lake/src/test/resources/trino432/partition_values_parsed/int_part=20/string_part=part2/20231109_020344_00033_9eakg_e6448c08-9b43-4fa1-8288-823fd3d692b9 new file mode 100644 index 0000000000000000000000000000000000000000..c26b952c5e645d00c8da3e3dace9c344526bb110 GIT binary patch literal 199 zcmWG=3^EjD5ET)X&=F+3usm>EH&D3hd)2Tw^+W?p`LadJj#ZlWj? zP*9agf+;hFL5xXKMp8zNK?bPIl!J{y5+o|hB*r9WCT69^VI#o;G6O=2F=!z17$jwY z)&Wfd$^#WMnOQMt7!)KH6_%!!=;jvbf^AeNOD!tS%+FIONi8mcu{DxPGjmcD^7Iom M8Rh|X2LQbQ0EKKKo&W#< literal 0 HcmV?d00001 diff --git a/plugin/trino-delta-lake/src/test/resources/trino432/partition_values_parsed/int_part=__HIVE_DEFAULT_PARTITION__/string_part=__HIVE_DEFAULT_PARTITION__/20231109_020350_00034_9eakg_2a008dd8-da7f-496a-b404-ca455732578e b/plugin/trino-delta-lake/src/test/resources/trino432/partition_values_parsed/int_part=__HIVE_DEFAULT_PARTITION__/string_part=__HIVE_DEFAULT_PARTITION__/20231109_020350_00034_9eakg_2a008dd8-da7f-496a-b404-ca455732578e new file mode 100644 index 0000000000000000000000000000000000000000..979c12f8bf0acb6dd97e99b6fb60123736a0254f GIT binary patch literal 199 zcmWG=3^EjD5ET)X&=F+3usm>HRYq$rc5jt5UkQD$C#d~tF{YHp$^ z6Hri4I%kC`&CW&dkqKC`m0Yfw47`N;7j(6!P>F NH5ujsbq4^w004)EBBKBR literal 0 HcmV?d00001 From d2586fd57271e47042fac431237f5275a1ddc0bc Mon Sep 17 00:00:00 2001 From: Konrad Dziedzic Date: Wed, 15 Nov 2023 16:26:59 +0100 Subject: [PATCH 271/587] Add table functions to hive connector --- .../io/trino/plugin/hive/HiveConnector.java | 20 +++++++++++++++++++ .../java/io/trino/plugin/hive/HiveModule.java | 7 +++++++ .../hive/InternalHiveConnectorFactory.java | 4 ++++ 3 files changed, 31 insertions(+) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveConnector.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveConnector.java index 862c380f0a40..6771a8216ade 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveConnector.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveConnector.java @@ -30,6 +30,8 @@ import io.trino.spi.connector.ConnectorTransactionHandle; import io.trino.spi.connector.TableProcedureMetadata; import io.trino.spi.eventlistener.EventListener; +import io.trino.spi.function.FunctionProvider; +import io.trino.spi.function.table.ConnectorTableFunction; import io.trino.spi.procedure.Procedure; import io.trino.spi.session.PropertyMetadata; import io.trino.spi.transaction.IsolationLevel; @@ -67,6 +69,8 @@ public class HiveConnector private final ClassLoader classLoader; private final HiveTransactionManager transactionManager; + private final Set connectorTableFunctions; + private final Optional functionProvider; private final boolean singleStatementWritesOnly; public HiveConnector( @@ -87,6 +91,8 @@ public HiveConnector( List> analyzeProperties, List> materializedViewProperties, Optional accessControl, + Set connectorTableFunctions, + Optional functionProvider, boolean singleStatementWritesOnly, ClassLoader classLoader) { @@ -109,6 +115,8 @@ public HiveConnector( this.analyzeProperties = ImmutableList.copyOf(requireNonNull(analyzeProperties, "analyzeProperties is null")); this.materializedViewProperties = requireNonNull(materializedViewProperties, "materializedViewProperties is null"); this.accessControl = requireNonNull(accessControl, "accessControl is null"); + this.connectorTableFunctions = ImmutableSet.copyOf(requireNonNull(connectorTableFunctions, "connectorTableFunctions is null")); + this.functionProvider = requireNonNull(functionProvider, "functionProvider is null"); this.singleStatementWritesOnly = singleStatementWritesOnly; this.classLoader = requireNonNull(classLoader, "classLoader is null"); } @@ -232,6 +240,18 @@ public final void shutdown() lifeCycleManager.stop(); } + @Override + public Optional getFunctionProvider() + { + return functionProvider; + } + + @Override + public Set getTableFunctions() + { + return connectorTableFunctions; + } + @Override public Set getTableProcedures() { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveModule.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveModule.java index e44202c0acf1..2f0e6a6b97ea 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveModule.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveModule.java @@ -19,6 +19,7 @@ import com.google.inject.Provides; import com.google.inject.Scopes; import com.google.inject.Singleton; +import com.google.inject.TypeLiteral; import com.google.inject.multibindings.Multibinder; import io.airlift.event.client.EventClient; import io.trino.plugin.base.CatalogName; @@ -53,7 +54,10 @@ import io.trino.spi.connector.ConnectorPageSinkProvider; import io.trino.spi.connector.ConnectorPageSourceProvider; import io.trino.spi.connector.ConnectorSplitManager; +import io.trino.spi.function.FunctionProvider; +import io.trino.spi.function.table.ConnectorTableFunction; +import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledExecutorService; @@ -144,6 +148,9 @@ public void configure(Binder binder) configBinder(binder).bindConfig(ParquetReaderConfig.class); configBinder(binder).bindConfig(ParquetWriterConfig.class); fileWriterFactoryBinder.addBinding().to(ParquetFileWriterFactory.class).in(Scopes.SINGLETON); + + newOptionalBinder(binder, new TypeLiteral>(){}).setDefault().toInstance(Optional.empty()); + newSetBinder(binder, ConnectorTableFunction.class); } @Singleton diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/InternalHiveConnectorFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/InternalHiveConnectorFactory.java index 69a5fe1089d1..b740d5f42fb0 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/InternalHiveConnectorFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/InternalHiveConnectorFactory.java @@ -62,6 +62,8 @@ import io.trino.spi.connector.MetadataProvider; import io.trino.spi.connector.TableProcedureMetadata; import io.trino.spi.eventlistener.EventListener; +import io.trino.spi.function.FunctionProvider; +import io.trino.spi.function.table.ConnectorTableFunction; import io.trino.spi.procedure.Procedure; import org.weakref.jmx.guice.MBeanModule; @@ -173,6 +175,8 @@ public static Connector createConnector( hiveAnalyzeProperties.getAnalyzeProperties(), hiveMaterializedViewPropertiesProvider.getMaterializedViewProperties(), hiveAccessControl, + injector.getInstance(Key.get(new TypeLiteral>() {})), + injector.getInstance(Key.get(new TypeLiteral>() {})), injector.getInstance(HiveConfig.class).isSingleStatementWritesOnly(), classLoader); } From 5aba09388791dc1468739bd893f134bfe489aef2 Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Tue, 29 Nov 2022 22:06:04 +0900 Subject: [PATCH 272/587] Pushdown predicates fully in BigQuery connector --- .../trino/plugin/bigquery/BigQueryClient.java | 2 +- .../plugin/bigquery/BigQueryColumnHandle.java | 13 +++- .../bigquery/BigQueryFilterQueryBuilder.java | 30 +++----- .../plugin/bigquery/BigQueryMetadata.java | 28 +++++++- .../plugin/bigquery/BigQueryPseudoColumn.java | 1 + .../plugin/bigquery/BigQuerySplitManager.java | 24 ++++--- .../trino/plugin/bigquery/BigQueryType.java | 72 +++++++++---------- .../trino/plugin/bigquery/ColumnMapping.java | 26 +++++++ .../io/trino/plugin/bigquery/Conversions.java | 4 +- .../bigquery/BaseBigQueryConnectorTest.java | 42 +++++++++++ 10 files changed, 169 insertions(+), 73 deletions(-) create mode 100644 plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/ColumnMapping.java diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryClient.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryClient.java index 3d864a500d41..0c488460164d 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryClient.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryClient.java @@ -334,7 +334,7 @@ public static String selectSql(TableId table, List requiredColumns, Opti return selectSql(table, columns, filter); } - private static String selectSql(TableId table, String formattedColumns, Optional filter) + public static String selectSql(TableId table, String formattedColumns, Optional filter) { String tableName = fullTableName(table); String query = format("SELECT %s FROM `%s`", formattedColumns, tableName); diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryColumnHandle.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryColumnHandle.java index 0642fb1e15f9..7fef53a4cc8d 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryColumnHandle.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryColumnHandle.java @@ -40,6 +40,7 @@ public class BigQueryColumnHandle private final String name; private final Type trinoType; private final StandardSQLTypeName bigqueryType; + private final boolean isPushdownSupported; private final Field.Mode mode; private final Long precision; private final Long scale; @@ -52,6 +53,7 @@ public BigQueryColumnHandle( @JsonProperty("name") String name, @JsonProperty("trinoType") Type trinoType, @JsonProperty("bigqueryType") StandardSQLTypeName bigqueryType, + @JsonProperty("isPushdownSupported") boolean isPushdownSupported, @JsonProperty("mode") Field.Mode mode, @JsonProperty("precision") Long precision, @JsonProperty("scale") Long scale, @@ -62,6 +64,7 @@ public BigQueryColumnHandle( this.name = requireNonNull(name, "column name cannot be null"); this.trinoType = requireNonNull(trinoType, "trinoType is null"); this.bigqueryType = requireNonNull(bigqueryType, "bigqueryType is null"); + this.isPushdownSupported = isPushdownSupported; this.mode = requireNonNull(mode, "Field mode cannot be null"); this.precision = precision; this.scale = scale; @@ -88,6 +91,12 @@ public StandardSQLTypeName getBigqueryType() return bigqueryType; } + @JsonProperty + public boolean isPushdownSupported() + { + return isPushdownSupported; + } + @JsonProperty public Field.Mode getMode() { @@ -148,6 +157,7 @@ public boolean equals(Object o) return Objects.equals(name, that.name) && Objects.equals(trinoType, that.trinoType) && Objects.equals(bigqueryType, that.bigqueryType) && + Objects.equals(isPushdownSupported, that.isPushdownSupported) && Objects.equals(mode, that.mode) && Objects.equals(precision, that.precision) && Objects.equals(scale, that.scale) && @@ -158,7 +168,7 @@ public boolean equals(Object o) @Override public int hashCode() { - return Objects.hash(name, trinoType, bigqueryType, mode, precision, scale, subColumns, description); + return Objects.hash(name, trinoType, bigqueryType, isPushdownSupported, mode, precision, scale, subColumns, description); } @Override @@ -168,6 +178,7 @@ public String toString() .add("name", name) .add("trinoType", trinoType) .add("bigqueryType", bigqueryType) + .add("isPushdownSupported", isPushdownSupported) .add("mode", mode) .add("precision", precision) .add("scale", scale) diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryFilterQueryBuilder.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryFilterQueryBuilder.java index 184b53ca67ed..53426e0be820 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryFilterQueryBuilder.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryFilterQueryBuilder.java @@ -96,27 +96,18 @@ private Optional toPredicate(String columnName, Domain domain, BigQueryC for (Range range : domain.getValues().getRanges().getOrderedRanges()) { checkState(!range.isAll()); // Already checked if (range.isSingleValue()) { - Optional value = convertToString(column.getTrinoType(), column.getBigqueryType(), range.getSingleValue()); - if (value.isEmpty()) { - return Optional.empty(); - } - singleValues.add(value.get()); + String value = convertToString(column.getTrinoType(), column.getBigqueryType(), range.getSingleValue()); + singleValues.add(value); } else { List rangeConjuncts = new ArrayList<>(); if (!range.isLowUnbounded()) { - Optional predicate = toPredicate(columnName, range.isLowInclusive() ? ">=" : ">", range.getLowBoundedValue(), column); - if (predicate.isEmpty()) { - return Optional.empty(); - } - rangeConjuncts.add(predicate.get()); + String predicate = toPredicate(columnName, range.isLowInclusive() ? ">=" : ">", range.getLowBoundedValue(), column); + rangeConjuncts.add(predicate); } if (!range.isHighUnbounded()) { - Optional predicate = toPredicate(columnName, range.isHighInclusive() ? "<=" : "<", range.getHighBoundedValue(), column); - if (predicate.isEmpty()) { - return Optional.empty(); - } - rangeConjuncts.add(predicate.get()); + String predicate = toPredicate(columnName, range.isHighInclusive() ? "<=" : "<", range.getHighBoundedValue(), column); + rangeConjuncts.add(predicate); } // If rangeConjuncts is null, then the range was ALL, which should already have been checked for checkState(!rangeConjuncts.isEmpty()); @@ -143,12 +134,9 @@ else if (singleValues.size() > 1) { return Optional.of("(" + String.join(" OR ", disjuncts) + ")"); } - private Optional toPredicate(String columnName, String operator, Object value, BigQueryColumnHandle column) + private String toPredicate(String columnName, String operator, Object value, BigQueryColumnHandle column) { - Optional valueAsString = convertToString(column.getTrinoType(), column.getBigqueryType(), value); - if (valueAsString.isEmpty()) { - return Optional.empty(); - } - return Optional.of(quote(columnName) + " " + operator + " " + valueAsString.get()); + String valueAsString = convertToString(column.getTrinoType(), column.getBigqueryType(), value); + return quote(columnName) + " " + operator + " " + valueAsString; } } diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryMetadata.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryMetadata.java index 467693bb2051..c035a6677de2 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryMetadata.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryMetadata.java @@ -69,6 +69,7 @@ import io.trino.spi.connector.TableNotFoundException; import io.trino.spi.expression.ConnectorExpression; import io.trino.spi.function.table.ConnectorTableFunctionHandle; +import io.trino.spi.predicate.Domain; import io.trino.spi.predicate.TupleDomain; import io.trino.spi.security.TrinoPrincipal; import io.trino.spi.statistics.ComputedStatistics; @@ -79,6 +80,7 @@ import java.io.IOException; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -782,13 +784,37 @@ public Optional> applyFilter( TupleDomain oldDomain = bigQueryTableHandle.getConstraint(); TupleDomain newDomain = oldDomain.intersect(constraint.getSummary()); + TupleDomain remainingFilter; + if (newDomain.isNone()) { + remainingFilter = TupleDomain.all(); + } + else { + Map domains = newDomain.getDomains().orElseThrow(); + + Map supported = new HashMap<>(); + Map unsupported = new HashMap<>(); + + for (Map.Entry entry : domains.entrySet()) { + BigQueryColumnHandle columnHandle = (BigQueryColumnHandle) entry.getKey(); + Domain domain = entry.getValue(); + if (columnHandle.isPushdownSupported()) { + supported.put(entry.getKey(), entry.getValue()); + } + else { + unsupported.put(columnHandle, domain); + } + } + newDomain = TupleDomain.withColumnDomains(supported); + remainingFilter = TupleDomain.withColumnDomains(unsupported); + } + if (oldDomain.equals(newDomain)) { return Optional.empty(); } BigQueryTableHandle updatedHandle = bigQueryTableHandle.withConstraint(newDomain); - return Optional.of(new ConstraintApplicationResult<>(updatedHandle, constraint.getSummary(), constraint.getExpression(), false)); + return Optional.of(new ConstraintApplicationResult<>(updatedHandle, remainingFilter, constraint.getExpression(), false)); } @Override diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryPseudoColumn.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryPseudoColumn.java index d961d7618f28..81a78dbf0094 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryPseudoColumn.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryPseudoColumn.java @@ -56,6 +56,7 @@ public BigQueryColumnHandle getColumnHandle() trinoColumnName, trinoType, bigqueryType, + true, Field.Mode.REQUIRED, null, null, diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQuerySplitManager.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQuerySplitManager.java index 76c17430399f..be588816d59f 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQuerySplitManager.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQuerySplitManager.java @@ -48,6 +48,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableList.toImmutableList; import static io.trino.plugin.bigquery.BigQueryClient.TABLE_TYPES; +import static io.trino.plugin.bigquery.BigQueryClient.selectSql; import static io.trino.plugin.bigquery.BigQueryErrorCode.BIGQUERY_FAILED_TO_EXECUTE_QUERY; import static io.trino.plugin.bigquery.BigQuerySessionProperties.isSkipViewMaterialization; import static io.trino.plugin.bigquery.BigQueryUtil.isWildcardTable; @@ -110,7 +111,7 @@ public ConnectorSplitSource getSplits( TableId remoteTableId = bigQueryTableHandle.asPlainTable().getRemoteTableName().toTableId(); List splits = emptyProjectionIsRequired(bigQueryTableHandle.getProjectedColumns()) ? createEmptyProjection(session, remoteTableId, actualParallelism, filter) : - readFromBigQuery(session, TableDefinition.Type.valueOf(bigQueryTableHandle.asPlainTable().getType()), remoteTableId, bigQueryTableHandle.getProjectedColumns(), actualParallelism, filter); + readFromBigQuery(session, TableDefinition.Type.valueOf(bigQueryTableHandle.asPlainTable().getType()), remoteTableId, bigQueryTableHandle.getProjectedColumns(), actualParallelism, tableConstraint); return new FixedSplitSource(splits); } @@ -119,15 +120,15 @@ private static boolean emptyProjectionIsRequired(Optional readFromBigQuery(ConnectorSession session, TableDefinition.Type type, TableId remoteTableId, Optional> projectedColumns, int actualParallelism, Optional filter) + private List readFromBigQuery(ConnectorSession session, TableDefinition.Type type, TableId remoteTableId, Optional> projectedColumns, int actualParallelism, TupleDomain tableConstraint) { checkArgument(projectedColumns.isPresent() && projectedColumns.get().size() > 0, "Projected column is empty"); + Optional filter = BigQueryFilterQueryBuilder.buildFilter(tableConstraint); log.debug("readFromBigQuery(tableId=%s, projectedColumns=%s, actualParallelism=%s, filter=[%s])", remoteTableId, projectedColumns, actualParallelism, filter); List columns = projectedColumns.get(); - List projectedColumnsNames = columns.stream() - .map(BigQueryColumnHandle::getName) - .collect(toImmutableList()); + ImmutableList.Builder projectedColumnsNames = ImmutableList.builder(); + projectedColumnsNames.addAll(columns.stream().map(BigQueryColumnHandle::getName).toList()); if (isWildcardTable(type, remoteTableId.getTable())) { // Storage API doesn't support reading wildcard tables @@ -137,11 +138,16 @@ private List readFromBigQuery(ConnectorSession session, TableDefi // Storage API doesn't support reading materialized views and external tables return ImmutableList.of(BigQuerySplit.forViewStream(columns, filter)); } - if (isSkipViewMaterialization(session) && type == VIEW) { - return ImmutableList.of(BigQuerySplit.forViewStream(columns, filter)); + if (type == VIEW) { + if (isSkipViewMaterialization(session)) { + return ImmutableList.of(BigQuerySplit.forViewStream(columns, filter)); + } + tableConstraint.getDomains().ifPresent(domains -> domains.keySet().stream() + .map(column -> ((BigQueryColumnHandle) column).getName()) + .forEach(projectedColumnsNames::add)); } ReadSessionCreator readSessionCreator = new ReadSessionCreator(bigQueryClientFactory, bigQueryReadClientFactory, viewEnabled, arrowSerializationEnabled, viewExpiration, maxReadRowsRetries); - ReadSession readSession = readSessionCreator.create(session, remoteTableId, projectedColumnsNames, filter, actualParallelism); + ReadSession readSession = readSessionCreator.create(session, remoteTableId, projectedColumnsNames.build(), filter, actualParallelism); return readSession.getStreamsList().stream() .map(stream -> BigQuerySplit.forStream(stream.getName(), readSessionCreator.getSchemaAsString(readSession), columns, OptionalInt.of(stream.getSerializedSize()))) @@ -156,7 +162,7 @@ private List createEmptyProjection(ConnectorSession session, Tabl long numberOfRows; if (filter.isPresent()) { // count the rows based on the filter - String sql = client.selectSql(remoteTableId, "COUNT(*)"); + String sql = selectSql(remoteTableId, "COUNT(*)", filter); TableResult result = client.executeQuery(session, sql); numberOfRows = result.iterateAll().iterator().next().get(0).getLongValue(); } diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryType.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryType.java index ab4f26b021b4..791f9b696122 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryType.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryType.java @@ -97,8 +97,8 @@ private BigQueryType() {} private static RowType.Field toRawTypeField(String name, Field field) { - Type trinoType = convertToTrinoType(field).orElseThrow(() -> new IllegalArgumentException("Unsupported column " + field)); - return RowType.field(name, field.getMode() == REPEATED ? new ArrayType(trinoType) : trinoType); + ColumnMapping columnMapping = convertToTrinoType(field).orElseThrow(() -> new IllegalArgumentException("Unsupported column " + field)); + return RowType.field(name, field.getMode() == REPEATED ? new ArrayType(columnMapping.type()) : columnMapping.type()); } @VisibleForTesting @@ -274,96 +274,90 @@ private static StandardSQLTypeName toStandardSqlTypeName(Type type) throw new TrinoException(NOT_SUPPORTED, "Unsupported column type: " + type.getDisplayName()); } - public static Optional convertToString(Type type, StandardSQLTypeName bigqueryType, Object value) + public static String convertToString(Type type, StandardSQLTypeName bigqueryType, Object value) { - if (type instanceof ArrayType) { - return Optional.empty(); - } switch (bigqueryType) { case BOOL: - return Optional.of(simpleToStringConverter(value)); + return simpleToStringConverter(value); case BYTES: - return Optional.of(bytesToStringConverter(value)); + return bytesToStringConverter(value); case DATE: - return Optional.of(dateToStringConverter(value)); + return dateToStringConverter(value); case DATETIME: - return Optional.of(datetimeToStringConverter(value)).map("'%s'"::formatted); + return "'%s'".formatted(datetimeToStringConverter(value)); case FLOAT64: - return Optional.of(floatToStringConverter(value)); + return floatToStringConverter(value); case INT64: - return Optional.of(simpleToStringConverter(value)); + return simpleToStringConverter(value); case NUMERIC: case BIGNUMERIC: String bigqueryTypeName = bigqueryType.name(); DecimalType decimalType = (DecimalType) type; if (decimalType.isShort()) { - return Optional.of(format("%s '%s'", bigqueryTypeName, Decimals.toString((long) value, ((DecimalType) type).getScale()))); + return format("%s '%s'", bigqueryTypeName, Decimals.toString((long) value, ((DecimalType) type).getScale())); } - return Optional.of(format("%s '%s'", bigqueryTypeName, Decimals.toString((Int128) value, ((DecimalType) type).getScale()))); - case ARRAY: - case STRUCT: - case GEOGRAPHY: - // Or throw an exception? - return Optional.empty(); + return format("%s '%s'", bigqueryTypeName, Decimals.toString((Int128) value, ((DecimalType) type).getScale())); case STRING: - return Optional.of(stringToStringConverter(value)); + return stringToStringConverter(value); case TIME: - return Optional.of(timeToStringConverter(value)); + return timeToStringConverter(value); case TIMESTAMP: - return Optional.of(timestampToStringConverter(value)).map("'%s'"::formatted); + return "'%s'".formatted(timestampToStringConverter(value)); default: throw new IllegalArgumentException("Unsupported type: " + bigqueryType); } } - public static Optional toTrinoType(Field field) + public static Optional toTrinoType(Field field) { return convertToTrinoType(field) - .map(type -> field.getMode() == REPEATED ? new ArrayType(type) : type); + .map(columnMapping -> field.getMode() == REPEATED ? + new ColumnMapping(new ArrayType(columnMapping.type()), false) : + columnMapping); } - private static Optional convertToTrinoType(Field field) + private static Optional convertToTrinoType(Field field) { switch (field.getType().getStandardType()) { case BOOL: - return Optional.of(BooleanType.BOOLEAN); + return Optional.of(new ColumnMapping(BooleanType.BOOLEAN, true)); case INT64: - return Optional.of(BigintType.BIGINT); + return Optional.of(new ColumnMapping(BigintType.BIGINT, true)); case FLOAT64: - return Optional.of(DoubleType.DOUBLE); + return Optional.of(new ColumnMapping(DoubleType.DOUBLE, true)); case NUMERIC: case BIGNUMERIC: Long precision = field.getPrecision(); Long scale = field.getScale(); // Unsupported BIGNUMERIC types (precision > 38) are filtered in BigQueryClient.getColumns if (precision != null && scale != null) { - return Optional.of(createDecimalType(toIntExact(precision), toIntExact(scale))); + return Optional.of(new ColumnMapping(createDecimalType(toIntExact(precision), toIntExact(scale)), true)); } if (precision != null) { - return Optional.of(createDecimalType(toIntExact(precision))); + return Optional.of(new ColumnMapping(createDecimalType(toIntExact(precision)), true)); } - return Optional.of(createDecimalType(DEFAULT_NUMERIC_TYPE_PRECISION, DEFAULT_NUMERIC_TYPE_SCALE)); + return Optional.of(new ColumnMapping(createDecimalType(DEFAULT_NUMERIC_TYPE_PRECISION, DEFAULT_NUMERIC_TYPE_SCALE), true)); case STRING: - return Optional.of(createUnboundedVarcharType()); + return Optional.of(new ColumnMapping(createUnboundedVarcharType(), true)); case BYTES: - return Optional.of(VarbinaryType.VARBINARY); + return Optional.of(new ColumnMapping(VarbinaryType.VARBINARY, true)); case DATE: - return Optional.of(DateType.DATE); + return Optional.of(new ColumnMapping(DateType.DATE, true)); case DATETIME: - return Optional.of(TimestampType.TIMESTAMP_MICROS); + return Optional.of(new ColumnMapping(TimestampType.TIMESTAMP_MICROS, true)); case TIME: - return Optional.of(TimeType.TIME_MICROS); + return Optional.of(new ColumnMapping(TimeType.TIME_MICROS, true)); case TIMESTAMP: - return Optional.of(TimestampWithTimeZoneType.TIMESTAMP_TZ_MICROS); + return Optional.of(new ColumnMapping(TimestampWithTimeZoneType.TIMESTAMP_TZ_MICROS, true)); case GEOGRAPHY: - return Optional.of(VarcharType.VARCHAR); + return Optional.of(new ColumnMapping(VarcharType.VARCHAR, false)); case STRUCT: // create the row FieldList subTypes = field.getSubFields(); checkArgument(!subTypes.isEmpty(), "a record or struct must have sub-fields"); List fields = subTypes.stream().map(subField -> toRawTypeField(subField.getName(), subField)).collect(toList()); RowType rowType = RowType.from(fields); - return Optional.of(rowType); + return Optional.of(new ColumnMapping(rowType, false)); default: return Optional.empty(); } diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/ColumnMapping.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/ColumnMapping.java new file mode 100644 index 000000000000..0e3c560f4a48 --- /dev/null +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/ColumnMapping.java @@ -0,0 +1,26 @@ +/* + * 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 io.trino.plugin.bigquery; + +import io.trino.spi.type.Type; + +import static java.util.Objects.requireNonNull; + +public record ColumnMapping(Type type, boolean isPushdownSupported) +{ + public ColumnMapping + { + requireNonNull(type, "type is null"); + } +} diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/Conversions.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/Conversions.java index ad0b4d469012..1e53944396a1 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/Conversions.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/Conversions.java @@ -38,10 +38,12 @@ public static BigQueryColumnHandle toColumnHandle(Field field) .filter(Conversions::isSupportedType) .map(Conversions::toColumnHandle) .collect(Collectors.toList()); + ColumnMapping columnMapping = toTrinoType(field).orElseThrow(() -> new IllegalArgumentException("Unsupported type: " + field)); return new BigQueryColumnHandle( field.getName(), - toTrinoType(field).orElseThrow(() -> new IllegalArgumentException("Unsupported type: " + field)), + columnMapping.type(), field.getType().getStandardType(), + columnMapping.isPushdownSupported(), getMode(field), field.getPrecision(), field.getScale(), diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java index ed92ab29ead3..493ec15d9b66 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java @@ -16,6 +16,7 @@ import io.airlift.units.Duration; import io.trino.Session; import io.trino.spi.QueryId; +import io.trino.sql.planner.plan.FilterNode; import io.trino.testing.BaseConnectorTest; import io.trino.testing.MaterializedResult; import io.trino.testing.MaterializedResultWithQueryId; @@ -110,6 +111,47 @@ protected MaterializedResult getDescribeOrdersResult() .build(); } + @Test + @Override // Override because the regexp is different from the base test + public void testPredicateReflectedInExplain() + { + assertExplain( + "EXPLAIN SELECT name FROM nation WHERE nationkey = 42", + "nationkey", "bigint", "42"); + } + + @Test + public void testPredicatePushdown() + { + testPredicatePushdown("true", "true", true); + testPredicatePushdown("CAST(1 AS INT64)", "1", true); + testPredicatePushdown("CAST(0.1 AS FLOAT64)", "0.1", true); + testPredicatePushdown("NUMERIC '123'", "123", true); + testPredicatePushdown("'string'", "'string'", true); + testPredicatePushdown("b''", "x''", true); + testPredicatePushdown("DATE '2017-01-01'", "DATE '2017-01-01'", true); + testPredicatePushdown("TIME '12:34:56'", "TIME '12:34:56'", true); + testPredicatePushdown("TIMESTAMP '2018-04-01 02:13:55.123456 UTC'", "TIMESTAMP '2018-04-01 02:13:55.123456 UTC'", true); + testPredicatePushdown("DATETIME '2018-04-01 02:13:55.123'", "TIMESTAMP '2018-04-01 02:13:55.123'", true); + + testPredicatePushdown("ST_GeogPoint(0, 0)", "'POINT(0 0)'", false); + testPredicatePushdown("[true]", "ARRAY[true]", false); + testPredicatePushdown("STRUCT('nested' AS x)", "ROW('nested')", false); + } + + private void testPredicatePushdown(@Language("SQL") String inputLiteral, @Language("SQL") String predicateLiteral, boolean isPushdownSupported) + { + try (TestTable table = new TestTable(bigQuerySqlExecutor, "test.test_predicate_pushdown", "AS SELECT %s col".formatted(inputLiteral))) { + String query = "SELECT * FROM " + table.getName() + " WHERE col = " + predicateLiteral; + if (isPushdownSupported) { + assertThat(query(query)).isFullyPushedDown(); + } + else { + assertThat(query(query)).isNotFullyPushedDown(FilterNode.class); + } + } + } + @Test public void testCreateTableSupportedType() { From 535c7495b48f930d9ba93b6e4d0420d6a6b66b70 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 15 Nov 2023 17:37:41 +0100 Subject: [PATCH 273/587] Remove unused method --- .../plugin/iceberg/catalog/hms/TrinoHiveCatalog.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java index c3bcde1a1b73..baa6c0d1d058 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java @@ -86,7 +86,6 @@ import static io.trino.plugin.hive.TableType.EXTERNAL_TABLE; import static io.trino.plugin.hive.TableType.VIRTUAL_VIEW; import static io.trino.plugin.hive.ViewReaderUtil.ICEBERG_MATERIALIZED_VIEW_COMMENT; -import static io.trino.plugin.hive.ViewReaderUtil.encodeViewData; import static io.trino.plugin.hive.ViewReaderUtil.isSomeKindOfAView; import static io.trino.plugin.hive.ViewReaderUtil.isTrinoMaterializedView; import static io.trino.plugin.hive.metastore.MetastoreUtil.buildInitialPrivilegeSet; @@ -480,16 +479,6 @@ public void updateViewColumnComment(ConnectorSession session, SchemaTableName vi trinoViewHiveMetastore.updateViewColumnComment(session, viewName, columnName, comment); } - private void replaceView(ConnectorSession session, SchemaTableName viewName, io.trino.plugin.hive.metastore.Table view, ConnectorViewDefinition newDefinition) - { - io.trino.plugin.hive.metastore.Table.Builder viewBuilder = io.trino.plugin.hive.metastore.Table.builder(view) - .setViewOriginalText(Optional.of(encodeViewData(newDefinition))); - - PrincipalPrivileges principalPrivileges = isUsingSystemSecurity ? NO_PRIVILEGES : buildInitialPrivilegeSet(session.getUser()); - - metastore.replaceTable(viewName.getSchemaName(), viewName.getTableName(), viewBuilder.build(), principalPrivileges); - } - @Override public String defaultTableLocation(ConnectorSession session, SchemaTableName schemaTableName) { From 5a4e39eff77fda26096061ecb2820740de4c9595 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 15 Nov 2023 17:38:07 +0100 Subject: [PATCH 274/587] Fix REST Iceberg catalog for names with dots Let it differentiate between "a"."b.c" and "a.b"."c" tables. --- .../trino/plugin/iceberg/catalog/rest/TrinoRestCatalog.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/rest/TrinoRestCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/rest/TrinoRestCatalog.java index 74927abd75e9..d3a095281e93 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/rest/TrinoRestCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/rest/TrinoRestCatalog.java @@ -82,7 +82,7 @@ public class TrinoRestCatalog private final String trinoVersion; private final boolean useUniqueTableLocation; - private final Map tableCache = new ConcurrentHashMap<>(); + private final Map tableCache = new ConcurrentHashMap<>(); public TrinoRestCatalog( RESTSessionCatalog restSessionCatalog, @@ -300,7 +300,7 @@ public Table loadTable(ConnectorSession session, SchemaTableName schemaTableName { try { return tableCache.computeIfAbsent( - schemaTableName.toString(), + schemaTableName, key -> { BaseTable baseTable = (BaseTable) restSessionCatalog.loadTable(convert(session), toIdentifier(schemaTableName)); // Creating a new base table is necessary to adhere to Trino's expectations for quoted table names From 4f3d778dc3afb6f209a24d7679b8c4e51e8b7d12 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 15 Nov 2023 17:26:45 +0100 Subject: [PATCH 275/587] Evict session level cache when dropping Iceberg table --- .../plugin/iceberg/catalog/AbstractTrinoCatalog.java | 4 ++++ .../plugin/iceberg/catalog/glue/TrinoGlueCatalog.java | 10 ++++++++++ .../plugin/iceberg/catalog/hms/TrinoHiveCatalog.java | 10 ++++++++++ .../plugin/iceberg/catalog/jdbc/TrinoJdbcCatalog.java | 9 +++++++++ .../iceberg/catalog/nessie/TrinoNessieCatalog.java | 8 ++++++++ .../plugin/iceberg/catalog/rest/TrinoRestCatalog.java | 9 +++++++++ 6 files changed, 50 insertions(+) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractTrinoCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractTrinoCatalog.java index dc518c966dd3..922d5293f5f3 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractTrinoCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractTrinoCatalog.java @@ -149,6 +149,7 @@ public void updateTableComment(ConnectorSession session, SchemaTableName schemaT else { icebergTable.updateProperties().set(TABLE_COMMENT, comment.get()).commit(); } + invalidateTableCache(schemaTableName); } @Override @@ -156,6 +157,7 @@ public void updateColumnComment(ConnectorSession session, SchemaTableName schema { Table icebergTable = loadTable(session, schemaTableName); icebergTable.updateSchema().updateColumnDoc(columnIdentity.getName(), comment.orElse(null)).commit(); + invalidateTableCache(schemaTableName); } @Override @@ -471,6 +473,8 @@ protected Map createMaterializedViewProperties(ConnectorSession .buildOrThrow(); } + protected abstract void invalidateTableCache(SchemaTableName schemaTableName); + protected static class MaterializedViewMayBeBeingRemovedException extends RuntimeException { diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java index 995bdb127587..300a831f1ac9 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java @@ -677,6 +677,7 @@ public void dropTable(ConnectorSession session, SchemaTableName schemaTableName) LOG.warn(e, "Failed to delete table data referenced by metadata"); } deleteTableDirectory(fileSystemFactory.create(session), schemaTableName, table.location()); + invalidateTableCache(schemaTableName); } @Override @@ -689,6 +690,7 @@ public void dropCorruptedTable(ConnectorSession session, SchemaTableName schemaT } String tableLocation = metadataLocation.replaceFirst("/metadata/[^/]*$", ""); deleteTableDirectory(fileSystemFactory.create(session), schemaTableName, tableLocation); + invalidateTableCache(schemaTableName); } @Override @@ -752,6 +754,7 @@ public void registerTable(ConnectorSession session, SchemaTableName schemaTableN public void unregisterTable(ConnectorSession session, SchemaTableName schemaTableName) { dropTableFromMetastore(session, schemaTableName); + invalidateTableCache(schemaTableName); } private com.amazonaws.services.glue.model.Table dropTableFromMetastore(ConnectorSession session, SchemaTableName schemaTableName) @@ -796,6 +799,7 @@ public void renameTable(ConnectorSession session, SchemaTableName from, SchemaTa createTable(to.getSchemaName(), tableInput); newTableCreated = true; deleteTable(from.getSchemaName(), from.getTableName()); + invalidateTableCache(from); } catch (RuntimeException e) { if (newTableCreated) { @@ -1486,6 +1490,12 @@ public Optional redirectTable(ConnectorSession session, return Optional.empty(); } + @Override + protected void invalidateTableCache(SchemaTableName schemaTableName) + { + tableMetadataCache.remove(schemaTableName); + } + com.amazonaws.services.glue.model.Table getTable(SchemaTableName tableName, boolean invalidateCaches) { if (invalidateCaches) { diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java index baa6c0d1d058..fa227a3827f3 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java @@ -365,6 +365,7 @@ private static Optional getQueryId(io.trino.plugin.hive.metastore.Table public void unregisterTable(ConnectorSession session, SchemaTableName schemaTableName) { dropTableFromMetastore(schemaTableName); + invalidateTableCache(schemaTableName); } @Override @@ -421,6 +422,7 @@ public void dropTable(ConnectorSession session, SchemaTableName schemaTableName) log.warn(e, "Failed to delete table data referenced by metadata"); } deleteTableDirectory(fileSystemFactory.create(session), schemaTableName, metastoreTable.getStorage().getLocation()); + invalidateTableCache(schemaTableName); } @Override @@ -428,6 +430,7 @@ public void dropCorruptedTable(ConnectorSession session, SchemaTableName schemaT { io.trino.plugin.hive.metastore.Table table = dropTableFromMetastore(schemaTableName); deleteTableDirectory(fileSystemFactory.create(session), schemaTableName, table.getStorage().getLocation()); + invalidateTableCache(schemaTableName); } private io.trino.plugin.hive.metastore.Table dropTableFromMetastore(SchemaTableName schemaTableName) @@ -449,6 +452,7 @@ private io.trino.plugin.hive.metastore.Table dropTableFromMetastore(SchemaTableN public void renameTable(ConnectorSession session, SchemaTableName from, SchemaTableName to) { metastore.renameTable(from.getSchemaName(), from.getTableName(), to.getSchemaName(), to.getTableName()); + invalidateTableCache(from); } @Override @@ -887,4 +891,10 @@ public Optional redirectTable(ConnectorSession session, } return Optional.empty(); } + + @Override + protected void invalidateTableCache(SchemaTableName schemaTableName) + { + tableMetadataCache.remove(schemaTableName); + } } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/jdbc/TrinoJdbcCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/jdbc/TrinoJdbcCatalog.java index 52af89f0fbe0..6f4090954b84 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/jdbc/TrinoJdbcCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/jdbc/TrinoJdbcCatalog.java @@ -273,6 +273,7 @@ public void dropTable(ConnectorSession session, SchemaTableName schemaTableName) LOG.warn(e, "Failed to delete table data referenced by metadata"); } deleteTableDirectory(fileSystemFactory.create(session), schemaTableName, table.location()); + invalidateTableCache(schemaTableName); } @Override @@ -287,6 +288,7 @@ public void dropCorruptedTable(ConnectorSession session, SchemaTableName schemaT } String tableLocation = metadataLocation.get().replaceFirst("/metadata/[^/]*$", ""); deleteTableDirectory(fileSystemFactory.create(session), schemaTableName, tableLocation); + invalidateTableCache(schemaTableName); } @Override @@ -298,6 +300,7 @@ public void renameTable(ConnectorSession session, SchemaTableName from, SchemaTa catch (RuntimeException e) { throw new TrinoException(ICEBERG_CATALOG_ERROR, "Failed to rename table from %s to %s".formatted(from, to), e); } + invalidateTableCache(from); } @Override @@ -447,6 +450,12 @@ public Optional redirectTable(ConnectorSession session, return Optional.empty(); } + @Override + protected void invalidateTableCache(SchemaTableName schemaTableName) + { + tableMetadataCache.remove(schemaTableName); + } + private static TableIdentifier toIdentifier(SchemaTableName table) { return TableIdentifier.of(table.getSchemaName(), table.getTableName()); diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/TrinoNessieCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/TrinoNessieCatalog.java index 6d5badaa8380..1a3c3a2176b5 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/TrinoNessieCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/TrinoNessieCatalog.java @@ -218,6 +218,7 @@ public void dropTable(ConnectorSession session, SchemaTableName schemaTableName) validateTableCanBeDropped(table); nessieClient.dropTable(toIdentifier(schemaTableName), true); deleteTableDirectory(fileSystemFactory.create(session), schemaTableName, table.location()); + invalidateTableCache(schemaTableName); } @Override @@ -230,6 +231,7 @@ public void dropCorruptedTable(ConnectorSession session, SchemaTableName schemaT public void renameTable(ConnectorSession session, SchemaTableName from, SchemaTableName to) { nessieClient.renameTable(toIdentifier(from), toIdentifier(to)); + invalidateTableCache(from); } @Override @@ -419,4 +421,10 @@ public Optional redirectTable(ConnectorSession session, { return Optional.empty(); } + + @Override + protected void invalidateTableCache(SchemaTableName schemaTableName) + { + tableMetadataCache.remove(schemaTableName); + } } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/rest/TrinoRestCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/rest/TrinoRestCatalog.java index d3a095281e93..06d174f709d5 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/rest/TrinoRestCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/rest/TrinoRestCatalog.java @@ -266,6 +266,7 @@ public void unregisterTable(ConnectorSession session, SchemaTableName tableName) if (!restSessionCatalog.dropTable(convert(session), toIdentifier(tableName))) { throw new TableNotFoundException(tableName); } + invalidateTableCache(tableName); } @Override @@ -274,6 +275,7 @@ public void dropTable(ConnectorSession session, SchemaTableName schemaTableName) if (!restSessionCatalog.purgeTable(convert(session), toIdentifier(schemaTableName))) { throw new TrinoException(ICEBERG_CATALOG_ERROR, format("Failed to drop table: %s", schemaTableName)); } + invalidateTableCache(schemaTableName); } @Override @@ -293,6 +295,7 @@ public void renameTable(ConnectorSession session, SchemaTableName from, SchemaTa catch (RESTException e) { throw new TrinoException(ICEBERG_CATALOG_ERROR, format("Failed to rename table %s to %s", from, to), e); } + invalidateTableCache(from); } @Override @@ -331,6 +334,7 @@ public void updateTableComment(ConnectorSession session, SchemaTableName schemaT else { icebergTable.updateProperties().set(TABLE_COMMENT, comment.get()).commit(); } + invalidateTableCache(schemaTableName); } @Override @@ -508,6 +512,11 @@ private SessionCatalog.SessionContext convert(ConnectorSession session) }; } + private void invalidateTableCache(SchemaTableName schemaTableName) + { + tableCache.remove(schemaTableName); + } + private static TableIdentifier toIdentifier(SchemaTableName schemaTableName) { return TableIdentifier.of(schemaTableName.getSchemaName(), schemaTableName.getTableName()); From 8d4f26b10547aa52278976988e2ca9e96646e8a0 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 15 Nov 2023 17:25:04 +0100 Subject: [PATCH 276/587] Limit session state during metadata queries in Iceberg Metadata queries such as `information_schema.columns`, `system.jdbc.columns` or `system.metadata.table_comments` may end up loading arbitrary number of relations within single query (transaction). It is important to bound memory usage for such queries. In case of Iceberg Hive metastore based catalog, this is already done in `TrinoHiveCatalogFactory` bu means of configuring per-query `CachingHiveMetastore`. However, catalogs with explicit caching need something similar. --- .../main/java/io/trino/cache/CacheUtils.java | 7 ++ .../catalog/glue/TrinoGlueCatalog.java | 102 +++++++++++------- .../iceberg/catalog/hms/TrinoHiveCatalog.java | 29 +++-- .../catalog/jdbc/TrinoJdbcCatalog.java | 29 +++-- .../catalog/nessie/TrinoNessieCatalog.java | 47 +++++--- .../catalog/rest/TrinoRestCatalog.java | 28 +++-- 6 files changed, 164 insertions(+), 78 deletions(-) diff --git a/lib/trino-cache/src/main/java/io/trino/cache/CacheUtils.java b/lib/trino-cache/src/main/java/io/trino/cache/CacheUtils.java index 614ebaa210f2..313eca6ca3c1 100644 --- a/lib/trino-cache/src/main/java/io/trino/cache/CacheUtils.java +++ b/lib/trino-cache/src/main/java/io/trino/cache/CacheUtils.java @@ -14,6 +14,8 @@ package io.trino.cache; import com.google.common.cache.Cache; +import com.google.common.util.concurrent.ExecutionError; +import com.google.common.util.concurrent.UncheckedExecutionException; import java.util.List; import java.util.concurrent.ExecutionException; @@ -26,6 +28,11 @@ public final class CacheUtils { private CacheUtils() {} + /** + * @throws UncheckedExecutionException when {@code loader} throws an unchecked exception + * @throws ExecutionError when {@code loader} throws an {@link Error} + * @throws RuntimeException when{@code loader} throws a checked exception (which should not happen) + */ public static V uncheckedCacheGet(Cache cache, K key, Supplier loader) { try { diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java index 300a831f1ac9..6761c555c00c 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java @@ -95,7 +95,6 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.UnaryOperator; @@ -105,6 +104,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Throwables.throwIfInstanceOf; +import static com.google.common.base.Throwables.throwIfUnchecked; import static com.google.common.base.Verify.verify; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; @@ -180,9 +180,16 @@ public class TrinoGlueCatalog // Even though this is query-scoped, this still needs to be bounded. information_schema queries can access large number of tables. .maximumSize(Math.max(PER_QUERY_CACHE_SIZE, IcebergMetadata.GET_METADATA_BATCH_SIZE)) .build(); - private final Map tableMetadataCache = new ConcurrentHashMap<>(); - private final Map viewCache = new ConcurrentHashMap<>(); - private final Map materializedViewCache = new ConcurrentHashMap<>(); + + private final Cache tableMetadataCache = EvictableCacheBuilder.newBuilder() + .maximumSize(PER_QUERY_CACHE_SIZE) + .build(); + private final Cache viewCache = EvictableCacheBuilder.newBuilder() + .maximumSize(PER_QUERY_CACHE_SIZE) + .build(); + private final Cache materializedViewCache = EvictableCacheBuilder.newBuilder() + .maximumSize(PER_QUERY_CACHE_SIZE) + .build(); public TrinoGlueCatalog( CatalogName catalogName, @@ -558,22 +565,30 @@ private void getCommentsFromIcebergMetadata( @Override public Table loadTable(ConnectorSession session, SchemaTableName table) { - if (viewCache.containsKey(table) || materializedViewCache.containsKey(table)) { + if (viewCache.asMap().containsKey(table) || materializedViewCache.asMap().containsKey(table)) { throw new TableNotFoundException(table); } - TableMetadata metadata = tableMetadataCache.computeIfAbsent( - table, - ignore -> { - TableOperations operations = tableOperationsProvider.createTableOperations( - this, - session, - table.getSchemaName(), - table.getTableName(), - Optional.empty(), - Optional.empty()); - return new BaseTable(operations, quotedTableName(table), TRINO_METRICS_REPORTER).operations().current(); - }); + TableMetadata metadata; + try { + metadata = uncheckedCacheGet( + tableMetadataCache, + table, + () -> { + TableOperations operations = tableOperationsProvider.createTableOperations( + this, + session, + table.getSchemaName(), + table.getTableName(), + Optional.empty(), + Optional.empty()); + return new BaseTable(operations, quotedTableName(table), TRINO_METRICS_REPORTER).operations().current(); + }); + } + catch (UncheckedExecutionException e) { + throwIfUnchecked(e.getCause()); + throw e; + } return getIcebergTableWithMetadata( this, @@ -612,7 +627,7 @@ public Map> tryGetColumnMetadata(Connector private Optional> getCachedColumnMetadata(SchemaTableName tableName) { - if (!cacheTableMetadata || viewCache.containsKey(tableName) || materializedViewCache.containsKey(tableName)) { + if (!cacheTableMetadata || viewCache.asMap().containsKey(tableName) || materializedViewCache.asMap().containsKey(tableName)) { return Optional.empty(); } @@ -828,37 +843,41 @@ private Optional getTableAndCacheMetada String tableType = getTableType(table); Map parameters = getTableParameters(table); - if (isIcebergTable(parameters) && !tableMetadataCache.containsKey(schemaTableName)) { - if (viewCache.containsKey(schemaTableName) || materializedViewCache.containsKey(schemaTableName)) { + if (isIcebergTable(parameters) && !tableMetadataCache.asMap().containsKey(schemaTableName)) { + if (viewCache.asMap().containsKey(schemaTableName) || materializedViewCache.asMap().containsKey(schemaTableName)) { throw new TrinoException(GENERIC_INTERNAL_ERROR, "Glue table cache inconsistency. Table cannot also be a view/materialized view"); } String metadataLocation = parameters.get(METADATA_LOCATION_PROP); try { // Cache the TableMetadata while we have the Table retrieved anyway - tableMetadataCache.put(schemaTableName, TableMetadataParser.read(new ForwardingFileIo(fileSystemFactory.create(session)), metadataLocation)); + // Note: this is racy from cache invalidation perspective, but it should not matter here + uncheckedCacheGet(tableMetadataCache, schemaTableName, () -> TableMetadataParser.read(new ForwardingFileIo(fileSystemFactory.create(session)), metadataLocation)); } catch (RuntimeException e) { LOG.warn(e, "Failed to cache table metadata from table at %s", metadataLocation); } } else if (isTrinoMaterializedView(tableType, parameters)) { - if (viewCache.containsKey(schemaTableName) || tableMetadataCache.containsKey(schemaTableName)) { + if (viewCache.asMap().containsKey(schemaTableName) || tableMetadataCache.asMap().containsKey(schemaTableName)) { throw new TrinoException(GENERIC_INTERNAL_ERROR, "Glue table cache inconsistency. Materialized View cannot also be a table or view"); } try { - ConnectorMaterializedViewDefinition materializedView = createMaterializedViewDefinition(session, schemaTableName, table); - materializedViewCache.put(schemaTableName, new MaterializedViewData( - materializedView, - Optional.ofNullable(parameters.get(METADATA_LOCATION_PROP)))); + // Note: this is racy from cache invalidation perspective, but it should not matter here + uncheckedCacheGet(materializedViewCache, schemaTableName, () -> { + ConnectorMaterializedViewDefinition materializedView = createMaterializedViewDefinition(session, schemaTableName, table); + return new MaterializedViewData( + materializedView, + Optional.ofNullable(parameters.get(METADATA_LOCATION_PROP))); + }); } catch (RuntimeException e) { LOG.warn(e, "Failed to cache materialized view from %s", schemaTableName); } } - else if (isTrinoView(tableType, parameters) && !viewCache.containsKey(schemaTableName)) { - if (materializedViewCache.containsKey(schemaTableName) || tableMetadataCache.containsKey(schemaTableName)) { + else if (isTrinoView(tableType, parameters) && !viewCache.asMap().containsKey(schemaTableName)) { + if (materializedViewCache.asMap().containsKey(schemaTableName) || tableMetadataCache.asMap().containsKey(schemaTableName)) { throw new TrinoException(GENERIC_INTERNAL_ERROR, "Glue table cache inconsistency. View cannot also be a materialized view or table"); } @@ -868,7 +887,10 @@ else if (isTrinoView(tableType, parameters) && !viewCache.containsKey(schemaTabl tableType, parameters, Optional.ofNullable(table.getOwner())) - .ifPresent(viewDefinition -> viewCache.put(schemaTableName, viewDefinition)); + .ifPresent(viewDefinition -> { + // Note: this is racy from cache invalidation perspective, but it should not matter here + uncheckedCacheGet(viewCache, schemaTableName, () -> viewDefinition); + }); } catch (RuntimeException e) { LOG.warn(e, "Failed to cache view from %s", schemaTableName); @@ -957,7 +979,7 @@ public void renameView(ConnectorSession session, SchemaTableName source, SchemaT try { com.amazonaws.services.glue.model.Table existingView = getTableAndCacheMetadata(session, source) .orElseThrow(() -> new TableNotFoundException(source)); - viewCache.remove(source); + viewCache.invalidate(source); TableInput viewTableInput = getViewTableInput( target.getTableName(), existingView.getViewOriginalText(), @@ -996,7 +1018,7 @@ public void dropView(ConnectorSession session, SchemaTableName schemaViewName) } try { - viewCache.remove(schemaViewName); + viewCache.invalidate(schemaViewName); deleteTable(schemaViewName.getSchemaName(), schemaViewName.getTableName()); } catch (AmazonServiceException e) { @@ -1031,12 +1053,12 @@ public List listViews(ConnectorSession session, Optional getView(ConnectorSession session, SchemaTableName viewName) { - ConnectorViewDefinition cachedView = viewCache.get(viewName); + ConnectorViewDefinition cachedView = viewCache.getIfPresent(viewName); if (cachedView != null) { return Optional.of(cachedView); } - if (tableMetadataCache.containsKey(viewName) || materializedViewCache.containsKey(viewName)) { + if (tableMetadataCache.asMap().containsKey(viewName) || materializedViewCache.asMap().containsKey(viewName)) { // Entries in these caches are not views return Optional.empty(); } @@ -1257,7 +1279,7 @@ public void dropMaterializedView(ConnectorSession session, SchemaTableName viewN if (!isTrinoMaterializedView(getTableType(view), getTableParameters(view))) { throw new TrinoException(UNSUPPORTED_TABLE_TYPE, "Not a Materialized View: " + view.getDatabaseName() + "." + view.getName()); } - materializedViewCache.remove(viewName); + materializedViewCache.invalidate(viewName); dropStorageTable(session, view); deleteTable(view.getDatabaseName(), view.getName()); } @@ -1281,12 +1303,12 @@ private void dropStorageTable(ConnectorSession session, com.amazonaws.services.g @Override protected Optional doGetMaterializedView(ConnectorSession session, SchemaTableName viewName) { - MaterializedViewData materializedViewData = materializedViewCache.get(viewName); + MaterializedViewData materializedViewData = materializedViewCache.getIfPresent(viewName); if (materializedViewData != null) { return Optional.of(materializedViewData.connectorMaterializedViewDefinition); } - if (tableMetadataCache.containsKey(viewName) || viewCache.containsKey(viewName)) { + if (tableMetadataCache.asMap().containsKey(viewName) || viewCache.asMap().containsKey(viewName)) { // Entries in these caches are not materialized views. return Optional.empty(); } @@ -1380,7 +1402,7 @@ private ConnectorMaterializedViewDefinition createMaterializedViewDefinition( public Optional getMaterializedViewStorageTable(ConnectorSession session, SchemaTableName viewName) { String storageMetadataLocation; - MaterializedViewData materializedViewData = materializedViewCache.get(viewName); + MaterializedViewData materializedViewData = materializedViewCache.getIfPresent(viewName); if (materializedViewData == null) { Optional maybeTable = getTableAndCacheMetadata(session, viewName); if (maybeTable.isEmpty()) { @@ -1423,7 +1445,7 @@ private TableMetadata getMaterializedViewTableMetadata(ConnectorSession session, { requireNonNull(storageTableName, "storageTableName is null"); requireNonNull(storageMetadataLocation, "storageMetadataLocation is null"); - return tableMetadataCache.computeIfAbsent(storageTableName, ignored -> { + return uncheckedCacheGet(tableMetadataCache, storageTableName, () -> { TrinoFileSystem fileSystem = fileSystemFactory.create(session); return TableMetadataParser.read(new ForwardingFileIo(fileSystem), storageMetadataLocation); }); @@ -1436,7 +1458,7 @@ public void renameMaterializedView(ConnectorSession session, SchemaTableName sou try { com.amazonaws.services.glue.model.Table glueTable = getTableAndCacheMetadata(session, source) .orElseThrow(() -> new TableNotFoundException(source)); - materializedViewCache.remove(source); + materializedViewCache.invalidate(source); Map tableParameters = getTableParameters(glueTable); if (!isTrinoMaterializedView(getTableType(glueTable), tableParameters)) { throw new TrinoException(UNSUPPORTED_TABLE_TYPE, "Not a Materialized View: " + source); @@ -1493,7 +1515,7 @@ public Optional redirectTable(ConnectorSession session, @Override protected void invalidateTableCache(SchemaTableName schemaTableName) { - tableMetadataCache.remove(schemaTableName); + tableMetadataCache.invalidate(schemaTableName); } com.amazonaws.services.glue.model.Table getTable(SchemaTableName tableName, boolean invalidateCaches) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java index fa227a3827f3..39ef750ec7de 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java @@ -13,10 +13,13 @@ */ package io.trino.plugin.iceberg.catalog.hms; +import com.google.common.cache.Cache; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.UncheckedExecutionException; import io.airlift.log.Logger; +import io.trino.cache.EvictableCacheBuilder; import io.trino.filesystem.Location; import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoFileSystemFactory; @@ -69,14 +72,15 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; import java.util.function.UnaryOperator; import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Throwables.throwIfUnchecked; import static com.google.common.base.Verify.verify; import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.trino.cache.CacheUtils.uncheckedCacheGet; import static io.trino.filesystem.Locations.appendPath; import static io.trino.plugin.hive.HiveErrorCode.HIVE_DATABASE_LOCATION_ERROR; import static io.trino.plugin.hive.HiveErrorCode.HIVE_INVALID_METADATA; @@ -124,6 +128,7 @@ public class TrinoHiveCatalog extends AbstractTrinoCatalog { private static final Logger log = Logger.get(TrinoHiveCatalog.class); + private static final int PER_QUERY_CACHE_SIZE = 1000; public static final String DEPENDS_ON_TABLES = "dependsOnTables"; // Value should be ISO-8601 formatted time instant public static final String TRINO_QUERY_START_TIME = "trino-query-start-time"; @@ -135,7 +140,9 @@ public class TrinoHiveCatalog private final boolean deleteSchemaLocationsFallback; private final boolean hideMaterializedViewStorageTable; - private final Map tableMetadataCache = new ConcurrentHashMap<>(); + private final Cache tableMetadataCache = EvictableCacheBuilder.newBuilder() + .maximumSize(PER_QUERY_CACHE_SIZE) + .build(); public TrinoHiveCatalog( CatalogName catalogName, @@ -458,9 +465,17 @@ public void renameTable(ConnectorSession session, SchemaTableName from, SchemaTa @Override public Table loadTable(ConnectorSession session, SchemaTableName schemaTableName) { - TableMetadata metadata = tableMetadataCache.computeIfAbsent( - schemaTableName, - ignore -> ((BaseTable) loadIcebergTable(this, tableOperationsProvider, session, schemaTableName)).operations().current()); + TableMetadata metadata; + try { + metadata = uncheckedCacheGet( + tableMetadataCache, + schemaTableName, + () -> ((BaseTable) loadIcebergTable(this, tableOperationsProvider, session, schemaTableName)).operations().current()); + } + catch (UncheckedExecutionException e) { + throwIfUnchecked(e.getCause()); + throw e; + } return getIcebergTableWithMetadata(this, tableOperationsProvider, session, schemaTableName, metadata); } @@ -838,7 +853,7 @@ public Optional getMaterializedViewStorageTable(ConnectorSession sess private TableMetadata getMaterializedViewTableMetadata(ConnectorSession session, SchemaTableName storageTableName, io.trino.plugin.hive.metastore.Table materializedView) { - return tableMetadataCache.computeIfAbsent(storageTableName, ignored -> { + return uncheckedCacheGet(tableMetadataCache, storageTableName, () -> { String storageMetadataLocation = materializedView.getParameters().get(METADATA_LOCATION_PROP); checkState(storageMetadataLocation != null, "Storage location missing in definition of materialized view " + materializedView.getTableName()); TrinoFileSystem fileSystem = fileSystemFactory.create(session); @@ -895,6 +910,6 @@ public Optional redirectTable(ConnectorSession session, @Override protected void invalidateTableCache(SchemaTableName schemaTableName) { - tableMetadataCache.remove(schemaTableName); + tableMetadataCache.invalidate(schemaTableName); } } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/jdbc/TrinoJdbcCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/jdbc/TrinoJdbcCatalog.java index 6f4090954b84..fae4f5507cb1 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/jdbc/TrinoJdbcCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/jdbc/TrinoJdbcCatalog.java @@ -13,10 +13,13 @@ */ package io.trino.plugin.iceberg.catalog.jdbc; +import com.google.common.cache.Cache; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.UncheckedExecutionException; import io.airlift.log.Logger; +import io.trino.cache.EvictableCacheBuilder; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.plugin.base.CatalogName; import io.trino.plugin.iceberg.catalog.AbstractTrinoCatalog; @@ -50,13 +53,14 @@ import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; import java.util.function.UnaryOperator; +import static com.google.common.base.Throwables.throwIfUnchecked; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.collect.Maps.transformValues; +import static io.trino.cache.CacheUtils.uncheckedCacheGet; import static io.trino.filesystem.Locations.appendPath; import static io.trino.plugin.iceberg.IcebergErrorCode.ICEBERG_CATALOG_ERROR; import static io.trino.plugin.iceberg.IcebergErrorCode.ICEBERG_INVALID_METADATA; @@ -75,11 +79,16 @@ public class TrinoJdbcCatalog { private static final Logger LOG = Logger.get(TrinoJdbcCatalog.class); + private static final int PER_QUERY_CACHE_SIZE = 1000; + private final JdbcCatalog jdbcCatalog; private final IcebergJdbcClient jdbcClient; private final TrinoFileSystemFactory fileSystemFactory; private final String defaultWarehouseDir; - private final Map tableMetadataCache = new ConcurrentHashMap<>(); + + private final Cache tableMetadataCache = EvictableCacheBuilder.newBuilder() + .maximumSize(PER_QUERY_CACHE_SIZE) + .build(); public TrinoJdbcCatalog( CatalogName catalogName, @@ -306,9 +315,17 @@ public void renameTable(ConnectorSession session, SchemaTableName from, SchemaTa @Override public Table loadTable(ConnectorSession session, SchemaTableName schemaTableName) { - TableMetadata metadata = tableMetadataCache.computeIfAbsent( - schemaTableName, - ignore -> ((BaseTable) loadIcebergTable(this, tableOperationsProvider, session, schemaTableName)).operations().current()); + TableMetadata metadata; + try { + metadata = uncheckedCacheGet( + tableMetadataCache, + schemaTableName, + () -> ((BaseTable) loadIcebergTable(this, tableOperationsProvider, session, schemaTableName)).operations().current()); + } + catch (UncheckedExecutionException e) { + throwIfUnchecked(e.getCause()); + throw e; + } return getIcebergTableWithMetadata(this, tableOperationsProvider, session, schemaTableName, metadata); } @@ -453,7 +470,7 @@ public Optional redirectTable(ConnectorSession session, @Override protected void invalidateTableCache(SchemaTableName schemaTableName) { - tableMetadataCache.remove(schemaTableName); + tableMetadataCache.invalidate(schemaTableName); } private static TableIdentifier toIdentifier(SchemaTableName table) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/TrinoNessieCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/TrinoNessieCatalog.java index 1a3c3a2176b5..ab0a40a3ef9a 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/TrinoNessieCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/TrinoNessieCatalog.java @@ -13,9 +13,12 @@ */ package io.trino.plugin.iceberg.catalog.nessie; +import com.google.common.cache.Cache; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; +import com.google.common.util.concurrent.UncheckedExecutionException; +import io.trino.cache.EvictableCacheBuilder; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.plugin.base.CatalogName; import io.trino.plugin.iceberg.catalog.AbstractTrinoCatalog; @@ -49,11 +52,12 @@ import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; import java.util.function.UnaryOperator; +import static com.google.common.base.Throwables.throwIfUnchecked; import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.trino.cache.CacheUtils.uncheckedCacheGet; import static io.trino.filesystem.Locations.appendPath; import static io.trino.plugin.iceberg.IcebergSchemaProperties.LOCATION_PROPERTY; import static io.trino.plugin.iceberg.IcebergUtil.getIcebergTableWithMetadata; @@ -67,11 +71,16 @@ public class TrinoNessieCatalog extends AbstractTrinoCatalog { + private static final int PER_QUERY_CACHE_SIZE = 1000; + private final String warehouseLocation; private final NessieIcebergClient nessieClient; - private final Map tableMetadataCache = new ConcurrentHashMap<>(); private final TrinoFileSystemFactory fileSystemFactory; + private final Cache tableMetadataCache = EvictableCacheBuilder.newBuilder() + .maximumSize(PER_QUERY_CACHE_SIZE) + .build(); + public TrinoNessieCatalog( CatalogName catalogName, TypeManager typeManager, @@ -184,18 +193,26 @@ public Optional> streamRelationComments( @Override public Table loadTable(ConnectorSession session, SchemaTableName table) { - TableMetadata metadata = tableMetadataCache.computeIfAbsent( - table, - ignore -> { - TableOperations operations = tableOperationsProvider.createTableOperations( - this, - session, - table.getSchemaName(), - table.getTableName(), - Optional.empty(), - Optional.empty()); - return new BaseTable(operations, quotedTableName(table)).operations().current(); - }); + TableMetadata metadata; + try { + metadata = uncheckedCacheGet( + tableMetadataCache, + table, + () -> { + TableOperations operations = tableOperationsProvider.createTableOperations( + this, + session, + table.getSchemaName(), + table.getTableName(), + Optional.empty(), + Optional.empty()); + return new BaseTable(operations, quotedTableName(table)).operations().current(); + }); + } + catch (UncheckedExecutionException e) { + throwIfUnchecked(e.getCause()); + throw e; + } return getIcebergTableWithMetadata( this, @@ -425,6 +442,6 @@ public Optional redirectTable(ConnectorSession session, @Override protected void invalidateTableCache(SchemaTableName schemaTableName) { - tableMetadataCache.remove(schemaTableName); + tableMetadataCache.invalidate(schemaTableName); } } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/rest/TrinoRestCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/rest/TrinoRestCatalog.java index 06d174f709d5..9357a08c158a 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/rest/TrinoRestCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/rest/TrinoRestCatalog.java @@ -13,11 +13,14 @@ */ package io.trino.plugin.iceberg.catalog.rest; +import com.google.common.cache.Cache; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; +import com.google.common.util.concurrent.UncheckedExecutionException; import io.jsonwebtoken.impl.DefaultJwtBuilder; import io.jsonwebtoken.jackson.io.JacksonSerializer; +import io.trino.cache.EvictableCacheBuilder; import io.trino.plugin.base.CatalogName; import io.trino.plugin.iceberg.ColumnIdentity; import io.trino.plugin.iceberg.IcebergSchemaProperties; @@ -58,12 +61,12 @@ import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; import java.util.function.UnaryOperator; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.trino.cache.CacheUtils.uncheckedCacheGet; import static io.trino.filesystem.Locations.appendPath; import static io.trino.plugin.hive.HiveMetadata.TABLE_COMMENT; import static io.trino.plugin.iceberg.IcebergErrorCode.ICEBERG_CATALOG_ERROR; @@ -76,13 +79,17 @@ public class TrinoRestCatalog implements TrinoCatalog { + private static final int PER_QUERY_CACHE_SIZE = 1000; + private final RESTSessionCatalog restSessionCatalog; private final CatalogName catalogName; private final SessionType sessionType; private final String trinoVersion; private final boolean useUniqueTableLocation; - private final Map tableCache = new ConcurrentHashMap<>(); + private final Cache tableCache = EvictableCacheBuilder.newBuilder() + .maximumSize(PER_QUERY_CACHE_SIZE) + .build(); public TrinoRestCatalog( RESTSessionCatalog restSessionCatalog, @@ -302,19 +309,20 @@ public void renameTable(ConnectorSession session, SchemaTableName from, SchemaTa public Table loadTable(ConnectorSession session, SchemaTableName schemaTableName) { try { - return tableCache.computeIfAbsent( + return uncheckedCacheGet( + tableCache, schemaTableName, - key -> { + () -> { BaseTable baseTable = (BaseTable) restSessionCatalog.loadTable(convert(session), toIdentifier(schemaTableName)); // Creating a new base table is necessary to adhere to Trino's expectations for quoted table names return new BaseTable(baseTable.operations(), quotedTableName(schemaTableName)); }); } - catch (NoSuchTableException e) { - throw new TableNotFoundException(schemaTableName, e); - } - catch (RuntimeException e) { - throw new TrinoException(ICEBERG_CATALOG_ERROR, format("Failed to load table: %s", schemaTableName), e); + catch (UncheckedExecutionException e) { + if (e.getCause() instanceof NoSuchTableException) { + throw new TableNotFoundException(schemaTableName, e.getCause()); + } + throw new TrinoException(ICEBERG_CATALOG_ERROR, format("Failed to load table: %s", schemaTableName), e.getCause()); } } @@ -514,7 +522,7 @@ private SessionCatalog.SessionContext convert(ConnectorSession session) private void invalidateTableCache(SchemaTableName schemaTableName) { - tableCache.remove(schemaTableName); + tableCache.invalidate(schemaTableName); } private static TableIdentifier toIdentifier(SchemaTableName schemaTableName) From 253a7fbb909dcb148fd6847bdfa34f2d0505b456 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Thu, 16 Nov 2023 00:21:28 -0800 Subject: [PATCH 277/587] Fix announcement delay for TestingTrinoServer --- .../jdbc/TestJdbcExternalAuthentication.java | 2 - .../trino/jdbc/TestJdbcPreparedStatement.java | 2 - .../java/io/trino/jdbc/TestJdbcWarnings.java | 2 - .../trino/jdbc/TestTrinoDatabaseMetaData.java | 2 - .../java/io/trino/jdbc/TestTrinoDriver.java | 1 - .../io/trino/jdbc/TestTrinoDriverAuth.java | 2 - .../server/testing/TestingTrinoServer.java | 19 ++------- ...seOAuth2WebUiAuthenticationFilterTest.java | 1 - ...AuthenticationFilterWithRefreshTokens.java | 1 - .../trino/testing/DistributedQueryRunner.java | 39 ++++++------------- 10 files changed, 14 insertions(+), 57 deletions(-) diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcExternalAuthentication.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcExternalAuthentication.java index 4afc14f41dda..e8e61efa3812 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcExternalAuthentication.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcExternalAuthentication.java @@ -55,7 +55,6 @@ import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; -import java.time.Duration; import java.util.ConcurrentModificationException; import java.util.List; import java.util.Map; @@ -114,7 +113,6 @@ public void setup() .build(); server.installPlugin(new TpchPlugin()); server.createCatalog(TEST_CATALOG, "tpch"); - server.waitForNodeRefresh(Duration.ofSeconds(10)); } @AfterAll diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcPreparedStatement.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcPreparedStatement.java index c889011d54d9..7a5d9482eb03 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcPreparedStatement.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcPreparedStatement.java @@ -44,7 +44,6 @@ import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; -import java.time.Duration; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -101,7 +100,6 @@ public void setup() server.installPlugin(new MemoryPlugin()); server.createCatalog("blackhole", "blackhole"); server.createCatalog("memory", "memory"); - server.waitForNodeRefresh(Duration.ofSeconds(10)); try (Connection connection = createConnection(false); Statement statement = connection.createStatement()) { diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcWarnings.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcWarnings.java index d5c9b9ac71e9..2bbd00ac91b6 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcWarnings.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcWarnings.java @@ -36,7 +36,6 @@ import java.sql.SQLException; import java.sql.SQLWarning; import java.sql.Statement; -import java.time.Duration; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -80,7 +79,6 @@ public void setupServer() .build(); server.installPlugin(new BlackHolePlugin()); server.createCatalog("blackhole", "blackhole"); - server.waitForNodeRefresh(Duration.ofSeconds(10)); try (Connection connection = createConnection(); Statement statement = connection.createStatement()) { diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDatabaseMetaData.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDatabaseMetaData.java index b4c1b4f06a06..e53425f22da5 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDatabaseMetaData.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDatabaseMetaData.java @@ -55,7 +55,6 @@ import java.sql.SQLException; import java.sql.Statement; import java.sql.Types; -import java.time.Duration; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -128,7 +127,6 @@ public void setupServer() countingMockConnector = new CountingMockConnector(); server.installPlugin(countingMockConnector.getPlugin()); server.createCatalog(COUNTING_CATALOG, "mock", ImmutableMap.of()); - server.waitForNodeRefresh(Duration.ofSeconds(10)); try (Connection connection = createConnection(); Statement statement = connection.createStatement()) { diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriver.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriver.java index 548b7eacbf38..d6a65296573a 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriver.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriver.java @@ -112,7 +112,6 @@ public void setup() server.createCatalog(TEST_CATALOG, "tpch"); server.installPlugin(new BlackHolePlugin()); server.createCatalog("blackhole", "blackhole"); - server.waitForNodeRefresh(java.time.Duration.ofSeconds(10)); setupTestTables(); executorService = newCachedThreadPool(daemonThreadsNamed(getClass().getSimpleName() + "-%s")); } diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriverAuth.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriverAuth.java index 3f2a2730066e..a946ec51c89a 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriverAuth.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriverAuth.java @@ -36,7 +36,6 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; -import java.time.Duration; import java.util.Map; import java.util.Optional; import java.util.Properties; @@ -91,7 +90,6 @@ public void setup() .build(); server.installPlugin(new TpchPlugin()); server.createCatalog(TEST_CATALOG, "tpch"); - server.waitForNodeRefresh(Duration.ofSeconds(10)); } @AfterAll diff --git a/core/trino-main/src/main/java/io/trino/server/testing/TestingTrinoServer.java b/core/trino-main/src/main/java/io/trino/server/testing/TestingTrinoServer.java index c3539fef240c..b2da13f8aa15 100644 --- a/core/trino-main/src/main/java/io/trino/server/testing/TestingTrinoServer.java +++ b/core/trino-main/src/main/java/io/trino/server/testing/TestingTrinoServer.java @@ -118,20 +118,18 @@ import java.io.UncheckedIOException; import java.net.URI; import java.nio.file.Path; -import java.time.Duration; -import java.time.Instant; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeoutException; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.google.common.io.MoreFiles.deleteRecursively; import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; import static com.google.inject.util.Modules.EMPTY_MODULE; +import static io.airlift.concurrent.MoreFutures.getFutureValue; import static java.lang.Integer.parseInt; import static java.nio.file.Files.createTempDirectory; import static java.nio.file.Files.isDirectory; @@ -262,6 +260,7 @@ private TestingTrinoServer( if (coordinator) { serverProperties.put("catalog.store", "memory"); + serverProperties.put("failure-detector.enabled", "false"); // Reduce memory footprint in tests serverProperties.put("query.min-expire-age", "5s"); @@ -403,7 +402,7 @@ private TestingTrinoServer( EventListenerManager eventListenerManager = injector.getInstance(EventListenerManager.class); eventListeners.forEach(eventListenerManager::addEventListener); - injector.getInstance(Announcer.class).forceAnnounce(); + getFutureValue(injector.getInstance(Announcer.class).forceAnnounce()); refreshNodes(); } @@ -677,18 +676,6 @@ public final AllNodes refreshNodes() return nodeManager.getAllNodes(); } - public void waitForNodeRefresh(Duration timeout) - throws InterruptedException, TimeoutException - { - Instant start = Instant.now(); - while (refreshNodes().getActiveNodes().size() < 1) { - if (Duration.between(start, Instant.now()).compareTo(timeout) > 0) { - throw new TimeoutException("Timed out while waiting for the node to refresh"); - } - MILLISECONDS.sleep(10); - } - } - public T getInstance(Key key) { return injector.getInstance(key); diff --git a/core/trino-main/src/test/java/io/trino/server/security/oauth2/BaseOAuth2WebUiAuthenticationFilterTest.java b/core/trino-main/src/test/java/io/trino/server/security/oauth2/BaseOAuth2WebUiAuthenticationFilterTest.java index 6315d9c4481c..d2f8060c531b 100644 --- a/core/trino-main/src/test/java/io/trino/server/security/oauth2/BaseOAuth2WebUiAuthenticationFilterTest.java +++ b/core/trino-main/src/test/java/io/trino/server/security/oauth2/BaseOAuth2WebUiAuthenticationFilterTest.java @@ -120,7 +120,6 @@ public void setup() .setProperties(getOAuth2Config(idpUrl)) .build(); server.getInstance(Key.get(OAuth2Client.class)).load(); - server.waitForNodeRefresh(Duration.ofSeconds(10)); serverUri = server.getHttpsBaseUrl(); uiUri = serverUri.resolve("/ui/"); diff --git a/core/trino-main/src/test/java/io/trino/server/security/oauth2/TestOAuth2WebUiAuthenticationFilterWithRefreshTokens.java b/core/trino-main/src/test/java/io/trino/server/security/oauth2/TestOAuth2WebUiAuthenticationFilterWithRefreshTokens.java index 5fd67da71cca..badcd56931c2 100644 --- a/core/trino-main/src/test/java/io/trino/server/security/oauth2/TestOAuth2WebUiAuthenticationFilterWithRefreshTokens.java +++ b/core/trino-main/src/test/java/io/trino/server/security/oauth2/TestOAuth2WebUiAuthenticationFilterWithRefreshTokens.java @@ -110,7 +110,6 @@ public void setup() .buildOrThrow()) .build(); server.getInstance(Key.get(OAuth2Client.class)).load(); - server.waitForNodeRefresh(Duration.ofSeconds(10)); serverUri = server.getHttpsBaseUrl(); uiUri = serverUri.resolve("/ui/"); diff --git a/testing/trino-testing/src/main/java/io/trino/testing/DistributedQueryRunner.java b/testing/trino-testing/src/main/java/io/trino/testing/DistributedQueryRunner.java index b1dc66524a45..e77f27a55e36 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/DistributedQueryRunner.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/DistributedQueryRunner.java @@ -22,8 +22,6 @@ import io.airlift.discovery.server.testing.TestingDiscoveryServer; import io.airlift.log.Logger; import io.airlift.log.Logging; -import io.airlift.testing.Assertions; -import io.airlift.units.Duration; import io.trino.Session; import io.trino.Session.SessionBuilder; import io.trino.cost.StatsCalculator; @@ -78,6 +76,7 @@ import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Throwables.throwIfUnchecked; +import static com.google.common.base.Verify.verify; import static com.google.inject.util.Modules.EMPTY_MODULE; import static io.airlift.log.Level.DEBUG; import static io.airlift.log.Level.ERROR; @@ -88,8 +87,6 @@ import static java.lang.Boolean.parseBoolean; import static java.lang.System.getenv; import static java.util.Objects.requireNonNull; -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static java.util.concurrent.TimeUnit.SECONDS; public class DistributedQueryRunner implements QueryRunner @@ -136,14 +133,15 @@ private DistributedQueryRunner( { requireNonNull(defaultSession, "defaultSession is null"); + long start = System.nanoTime(); setupLogging(); try { - long start = System.nanoTime(); + long discoveryStart = System.nanoTime(); discoveryServer = new TestingDiscoveryServer(environment); closer.register(() -> closeUnchecked(discoveryServer)); closer.register(() -> extraCloseables.forEach(DistributedQueryRunner::closeUnchecked)); - log.info("Created TestingDiscoveryServer in %s", nanosSince(start).convertToMostSuccinctTimeUnit()); + log.info("Created TestingDiscoveryServer in %s", nanosSince(discoveryStart)); registerNewWorker = () -> createServer(false, extraProperties, environment, additionalModule, baseDataDir, Optional.empty(), Optional.of(ImmutableList.of()), ImmutableList.of()); @@ -196,7 +194,8 @@ private DistributedQueryRunner( defaultSession = defaultSession.toSessionRepresentation().toSession(coordinator.getSessionPropertyManager(), defaultSession.getIdentity().getExtraCredentials(), defaultSession.getExchangeEncryptionKey()); this.trinoClient = closer.register(testingTrinoClientFactory.create(coordinator, defaultSession)); - waitForAllNodesGloballyVisible(); + ensureNodesGloballyVisible(); + log.info("Created DistributedQueryRunner in %s", nanosSince(start)); } private TestingTrinoServer createServer( @@ -287,36 +286,20 @@ private static TestingTrinoServer createTestingTrinoServer( } public void addServers(int nodeCount) - throws Exception { for (int i = 0; i < nodeCount; i++) { registerNewWorker.run(); } - waitForAllNodesGloballyVisible(); - } - - private void waitForAllNodesGloballyVisible() - throws InterruptedException - { - long start = System.nanoTime(); - while (!allNodesGloballyVisible()) { - // TODO node announcement should be propagated faster when new node starts - Assertions.assertLessThan(nanosSince(start), new Duration(30, SECONDS)); - MILLISECONDS.sleep(10); - } - log.info("Announced servers in %s", nanosSince(start).convertToMostSuccinctTimeUnit()); + ensureNodesGloballyVisible(); } - private boolean allNodesGloballyVisible() + private void ensureNodesGloballyVisible() { for (TestingTrinoServer server : servers) { - AllNodes allNodes = server.refreshNodes(); - if (!allNodes.getInactiveNodes().isEmpty() || - (allNodes.getActiveNodes().size() != servers.size())) { - return false; - } + AllNodes nodes = server.refreshNodes(); + verify(nodes.getInactiveNodes().isEmpty(), "Node manager has inactive nodes"); + verify(nodes.getActiveNodes().size() == servers.size(), "Node manager has wrong active node count"); } - return true; } public TestingTrinoClient getClient() From efa0870b0460e77444dc7d04a8084adfd642bbe5 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Wed, 15 Nov 2023 20:47:10 -0800 Subject: [PATCH 278/587] Remove unnecessary data providers --- .../cache/TestCachingHiveMetastore.java | 12 +++++-- .../hive/parquet/TestTimestampMicros.java | 34 +++++++++++-------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastore.java index 0eb9115a6a7c..7ab32a611bcc 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastore.java @@ -48,7 +48,6 @@ import io.trino.spi.predicate.Range; import io.trino.spi.predicate.TupleDomain; import io.trino.spi.predicate.ValueSet; -import io.trino.testing.DataProviders; import org.apache.thrift.TException; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeMethod; @@ -928,8 +927,15 @@ public void testCachingHiveMetastoreCreationViaMemoize() assertEquals(metastore.getDatabaseNamesStats().getRequestCount(), 0); } - @Test(timeOut = 60_000, dataProviderClass = DataProviders.class, dataProvider = "trueFalse") - public void testLoadAfterInvalidate(boolean invalidateAll) + @Test(timeOut = 60_000) + public void testLoadAfterInvalidate() + throws Exception + { + testLoadAfterInvalidate(true); + testLoadAfterInvalidate(false); + } + + private void testLoadAfterInvalidate(boolean invalidateAll) throws Exception { // State diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestampMicros.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestampMicros.java index 937e4ddbc6b1..4deaa9e2684f 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestampMicros.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestampMicros.java @@ -20,7 +20,6 @@ import io.trino.spi.type.Type; import io.trino.testing.MaterializedResult; import io.trino.testing.MaterializedRow; -import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.io.File; @@ -41,8 +40,16 @@ public class TestTimestampMicros { - @Test(dataProvider = "testTimestampMicrosDataProvider") - public void testTimestampMicros(HiveTimestampPrecision timestampPrecision, LocalDateTime expected) + @Test + public void testTimestampMicros() + throws Exception + { + testTimestampMicros(HiveTimestampPrecision.MILLISECONDS, LocalDateTime.parse("2020-10-12T16:26:02.907")); + testTimestampMicros(HiveTimestampPrecision.MICROSECONDS, LocalDateTime.parse("2020-10-12T16:26:02.906668")); + testTimestampMicros(HiveTimestampPrecision.NANOSECONDS, LocalDateTime.parse("2020-10-12T16:26:02.906668")); + } + + private void testTimestampMicros(HiveTimestampPrecision timestampPrecision, LocalDateTime expected) throws Exception { File parquetFile = new File(Resources.getResource("issue-5483.parquet").toURI()); @@ -55,8 +62,16 @@ public void testTimestampMicros(HiveTimestampPrecision timestampPrecision, Local } } - @Test(dataProvider = "testTimestampMicrosDataProvider") - public void testTimestampMicrosAsTimestampWithTimeZone(HiveTimestampPrecision timestampPrecision, LocalDateTime expected) + @Test + public void testTimestampMicrosAsTimestampWithTimeZone() + throws Exception + { + testTimestampMicrosAsTimestampWithTimeZone(HiveTimestampPrecision.MILLISECONDS, LocalDateTime.parse("2020-10-12T16:26:02.907")); + testTimestampMicrosAsTimestampWithTimeZone(HiveTimestampPrecision.MICROSECONDS, LocalDateTime.parse("2020-10-12T16:26:02.906668")); + testTimestampMicrosAsTimestampWithTimeZone(HiveTimestampPrecision.NANOSECONDS, LocalDateTime.parse("2020-10-12T16:26:02.906668")); + } + + private void testTimestampMicrosAsTimestampWithTimeZone(HiveTimestampPrecision timestampPrecision, LocalDateTime expected) throws Exception { File parquetFile = new File(Resources.getResource("issue-5483.parquet").toURI()); @@ -68,13 +83,4 @@ public void testTimestampMicrosAsTimestampWithTimeZone(HiveTimestampPrecision ti .containsOnly(new MaterializedRow(List.of(expected.atZone(ZoneId.of("UTC"))))); } } - - @DataProvider - public static Object[][] testTimestampMicrosDataProvider() - { - return new Object[][] { - {HiveTimestampPrecision.MILLISECONDS, LocalDateTime.parse("2020-10-12T16:26:02.907")}, - {HiveTimestampPrecision.MICROSECONDS, LocalDateTime.parse("2020-10-12T16:26:02.906668")}, - {HiveTimestampPrecision.NANOSECONDS, LocalDateTime.parse("2020-10-12T16:26:02.906668")}}; - } } From 411184310080bf83a3f0abe10b022227c762c668 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Wed, 15 Nov 2023 14:05:57 -0800 Subject: [PATCH 279/587] Migrate tests to JUnit --- lib/trino-hdfs/pom.xml | 18 ----- .../trino/hdfs/TestFSDataInputStreamTail.java | 21 +++-- .../io/trino/hdfs/TestFileSystemCache.java | 17 ++-- .../cache/TestCachingHiveMetastore.java | 25 ++++-- .../hive/parquet/TestTimestampMicros.java | 2 +- plugin/trino-kinesis/pom.xml | 19 ----- .../plugin/kinesis/TestKinesisPlugin.java | 13 ++-- .../TestKinesisTableDescriptionSupplier.java | 50 ++++++------ .../kinesis/TestMinimalFunctionality.java | 46 +++++------ .../plugin/kinesis/TestRecordAccess.java | 23 +++--- .../s3config/TestS3TableConfigClient.java | 78 ++++++++++--------- .../trino/plugin/kinesis/util/TestUtils.java | 4 +- 12 files changed, 149 insertions(+), 167 deletions(-) diff --git a/lib/trino-hdfs/pom.xml b/lib/trino-hdfs/pom.xml index 813b9c6f44f2..220906b25c2e 100644 --- a/lib/trino-hdfs/pom.xml +++ b/lib/trino-hdfs/pom.xml @@ -271,24 +271,6 @@ - - org.apache.maven.plugins - maven-surefire-plugin - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - - - ca.vanzyl.provisio.maven.plugins provisio-maven-plugin diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFSDataInputStreamTail.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFSDataInputStreamTail.java index d1bb545d5058..73a7b8832ede 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFSDataInputStreamTail.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFSDataInputStreamTail.java @@ -21,10 +21,12 @@ import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.RawLocalFileSystem; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.io.IOException; @@ -33,18 +35,21 @@ import static io.airlift.testing.Closeables.closeAll; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertSame; import static org.testng.Assert.assertTrue; -@Test(singleThreaded = true) // e.g. test methods operate on shared, mutated tempFile +@TestInstance(PER_CLASS) +@Execution(SAME_THREAD) // e.g. test methods operate on shared, mutated tempFile public class TestFSDataInputStreamTail { private File tempRoot; private Path tempFile; private RawLocalFileSystem fs; - @BeforeClass + @BeforeAll public void setUp() throws Exception { @@ -54,7 +59,7 @@ public void setUp() tempFile = new Path(Files.createTempFile(tempRoot.toPath(), "tempfile", "txt").toUri()); } - @BeforeMethod + @BeforeEach public void truncateTempFile() throws Exception { @@ -62,7 +67,7 @@ public void truncateTempFile() fs.truncate(tempFile, 0); } - @AfterClass(alwaysRun = true) + @AfterAll public void tearDown() throws Exception { diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFileSystemCache.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFileSystemCache.java index 3abe7b2e72cd..83c4496735ce 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFileSystemCache.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFileSystemCache.java @@ -22,9 +22,11 @@ import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.gaul.modernizer_maven_annotations.SuppressModernizer; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.IOException; import java.util.ArrayList; @@ -38,15 +40,18 @@ import static io.trino.plugin.base.security.UserNameProvider.SIMPLE_USER_NAME_PROVIDER; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotSame; import static org.testng.Assert.assertSame; -@Test(singleThreaded = true) +@TestInstance(PER_CLASS) +@Execution(SAME_THREAD) public class TestFileSystemCache { - @BeforeMethod(alwaysRun = true) - @AfterClass(alwaysRun = true) + @BeforeEach + @AfterAll public void cleanup() throws IOException { diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastore.java index 7ab32a611bcc..a4d65e13e014 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastore.java @@ -49,9 +49,13 @@ import io.trino.spi.predicate.TupleDomain; import io.trino.spi.predicate.ValueSet; import org.apache.thrift.TException; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.parallel.Execution; import java.util.ArrayList; import java.util.HashMap; @@ -112,12 +116,15 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.function.Function.identity; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; -@Test(singleThreaded = true) +@TestInstance(PER_CLASS) +@Execution(SAME_THREAD) public class TestCachingHiveMetastore { private static final Logger log = Logger.get(TestCachingHiveMetastore.class); @@ -136,7 +143,7 @@ public class TestCachingHiveMetastore private CachingHiveMetastore statsOnlyCacheMetastore; private ThriftMetastoreStats stats; - @BeforeMethod + @BeforeEach public void setUp() { mockClient = new MockThriftMetastoreClient(); @@ -149,7 +156,7 @@ public void setUp() stats = ((ThriftHiveMetastore) thriftHiveMetastore).getStats(); } - @AfterClass(alwaysRun = true) + @AfterAll public void tearDown() { executor.shutdownNow(); @@ -357,7 +364,8 @@ public void testGetPartitionThenGetPartitions() * here simulated with an explicit invalidation. */ // Repeat test with invocationCount for better test coverage, since the tested aspect is inherently non-deterministic. - @Test(timeOut = 60_000, invocationCount = 20) + @RepeatedTest(20) + @Timeout(60) public void testGetPartitionThenGetPartitionsRacingWithInvalidation() throws Exception { @@ -927,7 +935,8 @@ public void testCachingHiveMetastoreCreationViaMemoize() assertEquals(metastore.getDatabaseNamesStats().getRequestCount(), 0); } - @Test(timeOut = 60_000) + @Test + @Timeout(60) public void testLoadAfterInvalidate() throws Exception { diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestampMicros.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestampMicros.java index 4deaa9e2684f..b3002c501de9 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestampMicros.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestampMicros.java @@ -20,7 +20,7 @@ import io.trino.spi.type.Type; import io.trino.testing.MaterializedResult; import io.trino.testing.MaterializedRow; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.io.File; import java.time.LocalDateTime; diff --git a/plugin/trino-kinesis/pom.xml b/plugin/trino-kinesis/pom.xml index 1d28d1dbbf63..df6234712f55 100644 --- a/plugin/trino-kinesis/pom.xml +++ b/plugin/trino-kinesis/pom.xml @@ -196,12 +196,6 @@ junit-jupiter-engine test - - - org.testng - testng - test - @@ -221,19 +215,6 @@ s3://S3-LOC - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - org.basepom.maven diff --git a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestKinesisPlugin.java b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestKinesisPlugin.java index 1ca455ff46c8..91ade692250a 100644 --- a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestKinesisPlugin.java +++ b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestKinesisPlugin.java @@ -25,8 +25,7 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static io.trino.spi.transaction.IsolationLevel.READ_COMMITTED; import static io.trino.testing.TestingConnectorSession.SESSION; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; /** * Test that the plug in API is satisfied and all of the required objects can be created. @@ -51,16 +50,16 @@ public void testCreateConnector() .put("kinesis.secret-key", TestUtils.noneToBlank(secretKey)) .put("bootstrap.quiet", "true") .buildOrThrow(), new TestingConnectorContext()); - assertNotNull(c); + assertThat(c).isNotNull(); // Verify that the key objects have been created on the connector - assertNotNull(c.getRecordSetProvider()); - assertNotNull(c.getSplitManager()); + assertThat(c.getRecordSetProvider()).isNotNull(); + assertThat(c.getSplitManager()).isNotNull(); ConnectorMetadata md = c.getMetadata(SESSION, KinesisTransactionHandle.INSTANCE); - assertNotNull(md); + assertThat(md).isNotNull(); ConnectorTransactionHandle handle = c.beginTransaction(READ_COMMITTED, true, true); - assertTrue(handle instanceof KinesisTransactionHandle); + assertThat(handle instanceof KinesisTransactionHandle).isTrue(); c.shutdown(); } diff --git a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestKinesisTableDescriptionSupplier.java b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestKinesisTableDescriptionSupplier.java index 27a28bfe4a42..590b16bc537c 100644 --- a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestKinesisTableDescriptionSupplier.java +++ b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestKinesisTableDescriptionSupplier.java @@ -31,11 +31,9 @@ import java.util.Map; import static io.trino.testing.TestingConnectorSession.SESSION; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -70,55 +68,55 @@ public void testTableDefinition() KinesisMetadata metadata = (KinesisMetadata) connector.getMetadata(SESSION, new ConnectorTransactionHandle() {}); SchemaTableName tblName = new SchemaTableName("prod", "test_table"); KinesisTableHandle tableHandle = metadata.getTableHandle(SESSION, tblName); - assertNotNull(metadata); + assertThat(metadata).isNotNull(); SchemaTableName tableSchemaName = tableHandle.toSchemaTableName(); - assertEquals(tableSchemaName.getSchemaName(), "prod"); - assertEquals(tableSchemaName.getTableName(), "test_table"); - assertEquals(tableHandle.getStreamName(), "test_kinesis_stream"); - assertEquals(tableHandle.getMessageDataFormat(), "json"); + assertThat(tableSchemaName.getSchemaName()).isEqualTo("prod"); + assertThat(tableSchemaName.getTableName()).isEqualTo("test_table"); + assertThat(tableHandle.getStreamName()).isEqualTo("test_kinesis_stream"); + assertThat(tableHandle.getMessageDataFormat()).isEqualTo("json"); Map columnHandles = metadata.getColumnHandles(SESSION, tableHandle); - assertEquals(columnHandles.size(), 14); - assertEquals(columnHandles.values().stream().filter(x -> ((KinesisColumnHandle) x).isInternal()).count(), 10); + assertThat(columnHandles.size()).isEqualTo(14); + assertThat(columnHandles.values().stream().filter(x -> ((KinesisColumnHandle) x).isInternal()).count()).isEqualTo(10); } @Test public void testRelatedObjects() { KinesisMetadata metadata = (KinesisMetadata) connector.getMetadata(SESSION, new ConnectorTransactionHandle() {}); - assertNotNull(metadata); + assertThat(metadata).isNotNull(); SchemaTableName tblName = new SchemaTableName("prod", "test_table"); List schemas = metadata.listSchemaNames(null); - assertEquals(schemas.size(), 1); - assertEquals(schemas.get(0), "prod"); + assertThat(schemas.size()).isEqualTo(1); + assertThat(schemas.get(0)).isEqualTo("prod"); KinesisTableHandle tblHandle = metadata.getTableHandle(null, tblName); - assertNotNull(tblHandle); - assertEquals(tblHandle.getSchemaName(), "prod"); - assertEquals(tblHandle.getTableName(), "test_table"); - assertEquals(tblHandle.getStreamName(), "test_kinesis_stream"); - assertEquals(tblHandle.getMessageDataFormat(), "json"); + assertThat(tblHandle).isNotNull(); + assertThat(tblHandle.getSchemaName()).isEqualTo("prod"); + assertThat(tblHandle.getTableName()).isEqualTo("test_table"); + assertThat(tblHandle.getStreamName()).isEqualTo("test_kinesis_stream"); + assertThat(tblHandle.getMessageDataFormat()).isEqualTo("json"); ConnectorTableMetadata tblMeta = metadata.getTableMetadata(null, tblHandle); - assertNotNull(tblMeta); - assertEquals(tblMeta.getTable().getSchemaName(), "prod"); - assertEquals(tblMeta.getTable().getTableName(), "test_table"); + assertThat(tblMeta).isNotNull(); + assertThat(tblMeta.getTable().getSchemaName()).isEqualTo("prod"); + assertThat(tblMeta.getTable().getTableName()).isEqualTo("test_table"); List columnList = tblMeta.getColumns(); - assertNotNull(columnList); + assertThat(columnList).isNotNull(); boolean foundServiceType = false; boolean foundPartitionKey = false; for (ColumnMetadata column : columnList) { if (column.getName().equals("service_type")) { foundServiceType = true; - assertEquals(column.getType().getDisplayName(), "varchar(20)"); + assertThat(column.getType().getDisplayName()).isEqualTo("varchar(20)"); } if (column.getName().equals("_partition_key")) { foundPartitionKey = true; - assertEquals(column.getType().getDisplayName(), "varchar"); + assertThat(column.getType().getDisplayName()).isEqualTo("varchar"); } } - assertTrue(foundServiceType); - assertTrue(foundPartitionKey); + assertThat(foundServiceType).isTrue(); + assertThat(foundPartitionKey).isTrue(); } } diff --git a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestMinimalFunctionality.java b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestMinimalFunctionality.java index 554c0aa842f8..18a3297bf2c7 100644 --- a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestMinimalFunctionality.java +++ b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestMinimalFunctionality.java @@ -26,12 +26,12 @@ import io.trino.spi.security.Identity; import io.trino.sql.query.QueryAssertions; import io.trino.testing.StandaloneQueryRunner; -import org.testng.annotations.AfterClass; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Parameters; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.nio.ByteBuffer; @@ -49,7 +49,8 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Locale.ENGLISH; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertTrue; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; /** * Note: this is an integration test that connects to AWS Kinesis. @@ -58,7 +59,8 @@ * You may incur AWS charges if you run this test. You probably want to setup an IAM * user for your CI server to use. */ -@Test(singleThreaded = true) +@TestInstance(PER_CLASS) +@Execution(SAME_THREAD) public class TestMinimalFunctionality { public static final Session SESSION = Session.builder(new SessionPropertyManager()) @@ -70,35 +72,29 @@ public class TestMinimalFunctionality .setLocale(ENGLISH) .setQueryId(new QueryId("dummy")) .build(); + private final String accessKey; + private final String secretKey; - private EmbeddedKinesisStream embeddedKinesisStream; + private final EmbeddedKinesisStream embeddedKinesisStream; private String streamName; private StandaloneQueryRunner queryRunner; private QueryAssertions assertions; - @Parameters({ - "kinesis.awsAccessKey", - "kinesis.awsSecretKey" - }) - @BeforeClass - public void start(String accessKey, String secretKey) + public TestMinimalFunctionality() { + accessKey = System.getProperty("kinesis.awsAccessKey"); + secretKey = System.getProperty("kinesis.awsSecretKey"); embeddedKinesisStream = new EmbeddedKinesisStream(TestUtils.noneToBlank(accessKey), TestUtils.noneToBlank(secretKey)); } - @AfterClass(alwaysRun = true) + @AfterAll public void stop() { embeddedKinesisStream.close(); - embeddedKinesisStream = null; } - @Parameters({ - "kinesis.awsAccessKey", - "kinesis.awsSecretKey" - }) - @BeforeMethod - public void spinUp(String accessKey, String secretKey) + @BeforeEach + public void spinUp() throws Exception { streamName = "test_" + UUID.randomUUID().toString().replaceAll("-", "_"); @@ -146,7 +142,7 @@ public void testStreamExists() .singleStatement() .execute(SESSION, session -> { Optional handle = queryRunner.getServer().getMetadata().getTableHandle(session, name); - assertTrue(handle.isPresent()); + assertThat(handle.isPresent()).isTrue(); }); } @@ -163,7 +159,7 @@ public void testStreamHasData() .matches("VALUES %s".formatted(count)); } - @AfterMethod(alwaysRun = true) + @AfterEach public void tearDown() { embeddedKinesisStream.deleteStream(streamName); diff --git a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestRecordAccess.java b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestRecordAccess.java index b00f2d35bee2..4ee7baffd22e 100644 --- a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestRecordAccess.java +++ b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/TestRecordAccess.java @@ -46,10 +46,9 @@ import static io.trino.transaction.TransactionBuilder.transaction; import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; /** * Test record access and querying along with all associated setup. @@ -159,7 +158,7 @@ public void testStreamExists() .singleStatement() .execute(SESSION, session -> { Optional handle = queryRunner.getServer().getMetadata().getTableHandle(session, name); - assertTrue(handle.isPresent()); + assertThat(handle.isPresent()).isTrue(); }); log.info("Completed first test (access table handle)"); } @@ -172,7 +171,7 @@ public void testStreamHasData() .row(0) .build(); - assertEquals(result.getRowCount(), expected.getRowCount()); + assertThat(result.getRowCount()).isEqualTo(expected.getRowCount()); int count = 500; createDummyMessages(dummyStreamName, count); @@ -183,7 +182,7 @@ public void testStreamHasData() .row(count) .build(); - assertEquals(result.getRowCount(), expected.getRowCount()); + assertThat(result.getRowCount()).isEqualTo(expected.getRowCount()); log.info("Completed second test (select counts)"); } @@ -205,19 +204,19 @@ private void testJsonStream(int uncompressedMessages, int compressedMessages, St createJsonMessages(streamName, compressedMessages, 100 + uncompressedMessages, true); } MaterializedResult result = queryRunner.execute("Select id, name, _shard_id, _message_length, _message from " + streamName + " where _message_length >= 1"); - assertEquals(result.getRowCount(), uncompressedMessages + compressedMessages); + assertThat(result.getRowCount()).isEqualTo(uncompressedMessages + compressedMessages); List types = result.getTypes(); - assertEquals(types.size(), 5); - assertEquals(types.get(0).toString(), "bigint"); - assertEquals(types.get(1).toString(), "varchar"); + assertThat(types.size()).isEqualTo(5); + assertThat(types.get(0).toString()).isEqualTo("bigint"); + assertThat(types.get(1).toString()).isEqualTo("varchar"); log.info("Types : %s", types); List rows = result.getMaterializedRows(); - assertEquals(rows.size(), uncompressedMessages + compressedMessages); + assertThat(rows.size()).isEqualTo(uncompressedMessages + compressedMessages); for (MaterializedRow row : rows) { - assertEquals(row.getFieldCount(), 5); - assertTrue((long) row.getFields().get(0) >= 100); + assertThat(row.getFieldCount()).isEqualTo(5); + assertThat((long) row.getFields().get(0) >= 100).isTrue(); log.info("ROW: %s", row); } } diff --git a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/s3config/TestS3TableConfigClient.java b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/s3config/TestS3TableConfigClient.java index 656d97238767..dd641207d86c 100644 --- a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/s3config/TestS3TableConfigClient.java +++ b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/s3config/TestS3TableConfigClient.java @@ -24,65 +24,73 @@ import io.trino.spi.connector.ColumnHandle; import io.trino.spi.connector.ConnectorTransactionHandle; import io.trino.spi.connector.SchemaTableName; -import org.testng.annotations.Parameters; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.Map; import static io.trino.testing.TestingConnectorSession.SESSION; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -@Test(singleThreaded = true) +@TestInstance(PER_CLASS) +@Execution(SAME_THREAD) public class TestS3TableConfigClient { private static final Logger log = Logger.get(TestS3TableConfigClient.class); + private final String tableDescriptionS3; + private final String accessKey; + private final String secretKey; + + public TestS3TableConfigClient() + { + tableDescriptionS3 = System.getProperty("kinesis.test-table-description-location"); + accessKey = System.getProperty("kinesis.awsAccessKey"); + secretKey = System.getProperty("kinesis.awsSecretKey"); + } + @Test public void testS3URIValues() { // Verify that S3URI values will work: AmazonS3URI uri1 = new AmazonS3URI("s3://our.data.warehouse/prod/client_actions"); - assertNotNull(uri1.getKey()); - assertNotNull(uri1.getBucket()); + assertThat(uri1.getKey()).isNotNull(); + assertThat(uri1.getBucket()).isNotNull(); - assertEquals(uri1.toString(), "s3://our.data.warehouse/prod/client_actions"); - assertEquals(uri1.getBucket(), "our.data.warehouse"); - assertEquals(uri1.getKey(), "prod/client_actions"); - assertTrue(uri1.getRegion() == null); + assertThat(uri1.toString()).isEqualTo("s3://our.data.warehouse/prod/client_actions"); + assertThat(uri1.getBucket()).isEqualTo("our.data.warehouse"); + assertThat(uri1.getKey()).isEqualTo("prod/client_actions"); + assertThat(uri1.getRegion() == null).isTrue(); // show info: log.info("Tested out URI1 : %s", uri1); AmazonS3URI uri2 = new AmazonS3URI("s3://some.big.bucket/long/complex/path"); - assertNotNull(uri2.getKey()); - assertNotNull(uri2.getBucket()); + assertThat(uri2.getKey()).isNotNull(); + assertThat(uri2.getBucket()).isNotNull(); - assertEquals(uri2.toString(), "s3://some.big.bucket/long/complex/path"); - assertEquals(uri2.getBucket(), "some.big.bucket"); - assertEquals(uri2.getKey(), "long/complex/path"); - assertTrue(uri2.getRegion() == null); + assertThat(uri2.toString()).isEqualTo("s3://some.big.bucket/long/complex/path"); + assertThat(uri2.getBucket()).isEqualTo("some.big.bucket"); + assertThat(uri2.getKey()).isEqualTo("long/complex/path"); + assertThat(uri2.getRegion() == null).isTrue(); // info: log.info("Tested out URI2 : %s", uri2); AmazonS3URI uri3 = new AmazonS3URI("s3://trino.kinesis.config/unit-test/trino-kinesis"); - assertNotNull(uri3.getKey()); - assertNotNull(uri3.getBucket()); + assertThat(uri3.getKey()).isNotNull(); + assertThat(uri3.getBucket()).isNotNull(); - assertEquals(uri3.toString(), "s3://trino.kinesis.config/unit-test/trino-kinesis"); - assertEquals(uri3.getBucket(), "trino.kinesis.config"); - assertEquals(uri3.getKey(), "unit-test/trino-kinesis"); + assertThat(uri3.toString()).isEqualTo("s3://trino.kinesis.config/unit-test/trino-kinesis"); + assertThat(uri3.getBucket()).isEqualTo("trino.kinesis.config"); + assertThat(uri3.getKey()).isEqualTo("unit-test/trino-kinesis"); } - @Parameters({ - "kinesis.test-table-description-location", - "kinesis.awsAccessKey", - "kinesis.awsSecretKey" - }) @Test - public void testTableReading(String tableDescriptionS3, String accessKey, String secretKey) + public void testTableReading() { // To run this test: setup an S3 bucket with a folder for unit testing, and put // MinimalTable.json in that folder. @@ -112,13 +120,13 @@ public void testTableReading(String tableDescriptionS3, String accessKey, String KinesisMetadata metadata = (KinesisMetadata) kinesisConnector.getMetadata(SESSION, new ConnectorTransactionHandle() {}); SchemaTableName tblName = new SchemaTableName("default", "test123"); KinesisTableHandle tableHandle = metadata.getTableHandle(SESSION, tblName); - assertNotNull(metadata); + assertThat(metadata).isNotNull(); SchemaTableName tableSchemaName = tableHandle.toSchemaTableName(); - assertEquals(tableSchemaName.getSchemaName(), "default"); - assertEquals(tableSchemaName.getTableName(), "test123"); - assertEquals(tableHandle.getStreamName(), "test123"); - assertEquals(tableHandle.getMessageDataFormat(), "json"); + assertThat(tableSchemaName.getSchemaName()).isEqualTo("default"); + assertThat(tableSchemaName.getTableName()).isEqualTo("test123"); + assertThat(tableHandle.getStreamName()).isEqualTo("test123"); + assertThat(tableHandle.getMessageDataFormat()).isEqualTo("json"); Map columnHandles = metadata.getColumnHandles(SESSION, tableHandle); - assertEquals(columnHandles.size(), 12); + assertThat(columnHandles.size()).isEqualTo(12); } } diff --git a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/util/TestUtils.java b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/util/TestUtils.java index 9786f76cdfd4..11398190803d 100644 --- a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/util/TestUtils.java +++ b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/util/TestUtils.java @@ -26,7 +26,7 @@ import java.util.Map; import static java.util.Objects.requireNonNull; -import static org.testng.Assert.assertNotNull; +import static org.assertj.core.api.Assertions.assertThat; public final class TestUtils { @@ -39,7 +39,7 @@ public static KinesisConnector createConnector(KinesisPlugin plugin, Map Date: Thu, 16 Nov 2023 20:45:46 +0900 Subject: [PATCH 280/587] Run BigQuery Arrow and FTE tests in a separated profile --- .github/workflows/ci.yml | 14 +++++++------- plugin/trino-bigquery/pom.xml | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b905c857b1d6..cd365660d38a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -578,7 +578,7 @@ jobs: - { modules: lib/trino-filesystem-gcs, profile: cloud-tests } - { modules: plugin/trino-accumulo } - { modules: plugin/trino-bigquery } - - { modules: plugin/trino-bigquery, profile: cloud-tests-arrow } + - { modules: plugin/trino-bigquery, profile: cloud-tests-arrow-and-fte } - { modules: plugin/trino-cassandra } - { modules: plugin/trino-clickhouse } - { modules: plugin/trino-delta-lake } @@ -655,7 +655,7 @@ jobs: matrix.modules != 'plugin/trino-singlestore' && ! (contains(matrix.modules, 'trino-delta-lake') && contains(matrix.profile, 'cloud-tests')) && ! (contains(matrix.modules, 'trino-iceberg') && contains(matrix.profile, 'cloud-tests')) - && ! (contains(matrix.modules, 'trino-bigquery') && contains(matrix.profile, 'cloud-tests-arrow')) + && ! (contains(matrix.modules, 'trino-bigquery') && contains(matrix.profile, 'cloud-tests-arrow-and-fte')) && ! (contains(matrix.modules, 'trino-redshift') && contains(matrix.profile, 'cloud-tests')) && ! (contains(matrix.modules, 'trino-redshift') && contains(matrix.profile, 'fte-tests')) && ! (contains(matrix.modules, 'trino-filesystem-s3') && contains(matrix.profile, 'cloud-tests')) @@ -758,25 +758,25 @@ jobs: env: BIGQUERY_CREDENTIALS_KEY: ${{ secrets.BIGQUERY_CREDENTIALS_KEY }} GCP_STORAGE_BUCKET: ${{ vars.GCP_STORAGE_BUCKET }} - if: matrix.modules == 'plugin/trino-bigquery' && !contains(matrix.profile, 'cloud-tests-arrow') && (env.CI_SKIP_SECRETS_PRESENCE_CHECKS != '' || env.BIGQUERY_CREDENTIALS_KEY != '') + if: matrix.modules == 'plugin/trino-bigquery' && !contains(matrix.profile, 'cloud-tests-arrow-and-fte') && (env.CI_SKIP_SECRETS_PRESENCE_CHECKS != '' || env.BIGQUERY_CREDENTIALS_KEY != '') run: | $MAVEN test ${MAVEN_TEST} -pl :trino-bigquery -Pcloud-tests \ -Dbigquery.credentials-key="${BIGQUERY_CREDENTIALS_KEY}" \ -Dtesting.gcp-storage-bucket="${GCP_STORAGE_BUCKET}" \ -Dtesting.alternate-bq-project-id=bigquery-cicd-alternate - - name: Cloud BigQuery Arrow Serialization Tests + - name: Cloud BigQuery Arrow and FTE Tests env: BIGQUERY_CREDENTIALS_KEY: ${{ secrets.BIGQUERY_CREDENTIALS_KEY }} GCP_STORAGE_BUCKET: ${{ vars.GCP_STORAGE_BUCKET }} - if: matrix.modules == 'plugin/trino-bigquery' && contains(matrix.profile, 'cloud-tests-arrow') && (env.CI_SKIP_SECRETS_PRESENCE_CHECKS != '' || env.BIGQUERY_CREDENTIALS_KEY != '') + if: matrix.modules == 'plugin/trino-bigquery' && contains(matrix.profile, 'cloud-tests-arrow-and-fte') && (env.CI_SKIP_SECRETS_PRESENCE_CHECKS != '' || env.BIGQUERY_CREDENTIALS_KEY != '') run: | - $MAVEN test ${MAVEN_TEST} -pl :trino-bigquery -Pcloud-tests-arrow \ + $MAVEN test ${MAVEN_TEST} -pl :trino-bigquery -Pcloud-tests-arrow-and-fte \ -Dbigquery.credentials-key="${BIGQUERY_CREDENTIALS_KEY}" \ -Dtesting.gcp-storage-bucket="${GCP_STORAGE_BUCKET}" - name: Cloud BigQuery Case Insensitive Mapping Tests env: BIGQUERY_CASE_INSENSITIVE_CREDENTIALS_KEY: ${{ secrets.BIGQUERY_CASE_INSENSITIVE_CREDENTIALS_KEY }} - if: matrix.modules == 'plugin/trino-bigquery' && !contains(matrix.profile, 'cloud-tests-arrow') && (env.CI_SKIP_SECRETS_PRESENCE_CHECKS != '' || env.BIGQUERY_CASE_INSENSITIVE_CREDENTIALS_KEY != '') + if: matrix.modules == 'plugin/trino-bigquery' && !contains(matrix.profile, 'cloud-tests-arrow-and-fte') && (env.CI_SKIP_SECRETS_PRESENCE_CHECKS != '' || env.BIGQUERY_CASE_INSENSITIVE_CREDENTIALS_KEY != '') run: | $MAVEN test ${MAVEN_TEST} -pl :trino-bigquery -Pcloud-tests-case-insensitive-mapping -Dbigquery.credentials-key="${BIGQUERY_CASE_INSENSITIVE_CREDENTIALS_KEY}" - name: Iceberg Cloud Tests diff --git a/plugin/trino-bigquery/pom.xml b/plugin/trino-bigquery/pom.xml index c0495fdf8d51..30f6094550c4 100644 --- a/plugin/trino-bigquery/pom.xml +++ b/plugin/trino-bigquery/pom.xml @@ -551,7 +551,6 @@ **/TestBigQueryAvroTypeMapping.java **/TestBigQueryMetadata.java **/TestBigQueryInstanceCleaner.java - **/TestBigQuery*FailureRecoveryTest.java **/TestBigQueryWithProxyConnectorSmokeTest.java @@ -585,7 +584,7 @@ - cloud-tests-arrow + cloud-tests-arrow-and-fte false @@ -601,6 +600,7 @@ **/TestBigQueryArrowConnectorSmokeTest.java **/TestBigQueryArrowTypeMapping.java + **/TestBigQuery*FailureRecoveryTest.java From d60b4fca3482723be6afbe4aa08632dbf66a68e3 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Wed, 15 Nov 2023 19:07:13 -0800 Subject: [PATCH 281/587] Remove BaseCostBasedPlanTest, subclasses, and resources --- testing/trino-tests/pom.xml | 97 - .../sql/planner/BaseCostBasedPlanTest.java | 361 -- .../planner/BaseHiveCostBasedPlanTest.java | 95 - .../planner/BaseIcebergCostBasedPlanTest.java | 223 -- .../sql/planner/HiveMetadataRecorder.java | 131 - ...TestHivePartitionedTpcdsCostBasedPlan.java | 49 - .../TestHivePartitionedTpchCostBasedPlan.java | 49 - .../planner/TestHiveTpcdsCostBasedPlan.java | 50 - .../planner/TestHiveTpchCostBasedPlan.java | 50 - ...ebergOrcPartitionedTpcdsCostBasedPlan.java | 55 - ...cebergOrcPartitionedTpchCostBasedPlan.java | 54 - .../TestIcebergOrcTpcdsCostBasedPlan.java | 55 - .../TestIcebergOrcTpchCostBasedPlan.java | 54 - ...gParquetPartitionedTpcdsCostBasedPlan.java | 55 - ...rgParquetPartitionedTpchCostBasedPlan.java | 54 - .../TestIcebergParquetTpcdsCostBasedPlan.java | 55 - .../TestIcebergParquetTpchCostBasedPlan.java | 54 - ...rgSmallFilesParquetTpcdsCostBasedPlan.java | 55 - .../sql/planner/UpdateExpectedPlans.java | 67 - .../test/resources/hive_metadata/README.md | 19 - .../tpcds_sf1000_orc_part.json.gz | Bin 343475 -> 0 bytes .../tpch_sf1000_orc_part.json.gz | Bin 213712 -> 0 bytes .../tpcds_sf1000_orc.json.gz | Bin 10673 -> 0 bytes .../tpch_sf1000_orc.json.gz | Bin 2228 -> 0 bytes ...-8a15-44b2-9495-13fa19b96b3d.metadata.json | 397 --- ...37e8c9-cfc9-4231-b46d-141e70e1c9bf-m0.avro | Bin 8109 -> 0 bytes ...-bc37e8c9-cfc9-4231-b46d-141e70e1c9bf.avro | Bin 3784 -> 0 bytes ...-db36-4b60-936e-b1af07bd5975.metadata.json | 166 - ...6eedf3-e609-4590-9b08-43815e502397-m0.avro | Bin 7017 -> 0 bytes ...-356eedf3-e609-4590-9b08-43815e502397.avro | Bin 3791 -> 0 bytes ...-b0df-499f-afb4-1e9e085343dc.metadata.json | 374 -- ...868c3d-341a-4ecb-afdd-19ae7cddc60a-m0.avro | Bin 569585 -> 0 bytes ...-ee868c3d-341a-4ecb-afdd-19ae7cddc60a.avro | Bin 3817 -> 0 bytes ...-8f43-4a73-b8f5-b7d9bac10357.metadata.json | 451 --- ...606fe7-b3d7-4f87-b73c-facdbf6465bc-m0.avro | 2149 ------------ ...-28606fe7-b3d7-4f87-b73c-facdbf6465bc.avro | Bin 3816 -> 0 bytes ...-bd6d-42a0-af6b-fda9f2cf6770.metadata.json | 265 -- ...3999f3-8e7c-4dac-a89b-0605a4d91eda-m0.avro | Bin 8066 -> 0 bytes ...-ca3999f3-8e7c-4dac-a89b-0605a4d91eda.avro | Bin 3788 -> 0 bytes ...-acf1-49bd-a385-7c8b25ac5acf.metadata.json | 210 -- ...27e5de-60c8-4068-914b-5927879fcf7a-m0.avro | Bin 7414 -> 0 bytes ...-d627e5de-60c8-4068-914b-5927879fcf7a.avro | Bin 3796 -> 0 bytes ...-1602-47f2-b054-d7244fa6b610.metadata.json | 166 - ...780e6c-ef23-4ea1-a89d-4160c571eb7f-m0.avro | Bin 6730 -> 0 bytes ...-c3780e6c-ef23-4ea1-a89d-4160c571eb7f.avro | Bin 3796 -> 0 bytes ...-36c5-4e84-b55e-c5c51a28b468.metadata.json | 375 -- ...63012b-2c05-49f9-8772-77377306deb3-m0.avro | Bin 8798 -> 0 bytes ...-bb63012b-2c05-49f9-8772-77377306deb3.avro | Bin 3789 -> 0 bytes ...-5994-4567-9305-7305e97a1165.metadata.json | 122 - ...e4d086-6a9a-4bfd-88b4-71bfaf544152-m0.avro | Bin 6380 -> 0 bytes ...-f1e4d086-6a9a-4bfd-88b4-71bfaf544152.avro | Bin 3790 -> 0 bytes ...-4d80-4918-9cb1-de3936f39de6.metadata.json | 100 - ...19af1d-dd90-4815-a241-1470565ff0b3-m0.avro | Bin 6154 -> 0 bytes ...-2e19af1d-dd90-4815-a241-1470565ff0b3.avro | Bin 3785 -> 0 bytes ...-c313-4d76-b3c9-e7bb2f504211.metadata.json | 121 - ...f2184b-1082-43b4-96a3-6dfc1534cbae-m0.avro | Bin 19358 -> 0 bytes ...-a1f2184b-1082-43b4-96a3-6dfc1534cbae.avro | Bin 3814 -> 0 bytes ...-50e0-4118-b53c-4fd16021435c.metadata.json | 309 -- ...19ea20-4bcd-49b1-9914-f1e2162dc4c3-m0.avro | Bin 8339 -> 0 bytes ...-dc19ea20-4bcd-49b1-9914-f1e2162dc4c3.avro | Bin 3785 -> 0 bytes ...-3540-40d3-a1ee-37efd8f1a0fe.metadata.json | 276 -- ...0ecda9-3f82-41b1-a3fa-ec24faa845b1-m0.avro | Bin 7667 -> 0 bytes ...-d80ecda9-3f82-41b1-a3fa-ec24faa845b1.avro | Bin 3785 -> 0 bytes ...-641b-4e5d-b07b-7ec5a00a5478.metadata.json | 100 - ...06760d-6ee0-46e9-8aaa-14c93a48bca1-m0.avro | Bin 5931 -> 0 bytes ...-7206760d-6ee0-46e9-8aaa-14c93a48bca1.avro | Bin 3782 -> 0 bytes ...-8976-4e78-8f05-b7a0b6c3b508.metadata.json | 133 - ...19546d-26d5-434b-aabb-d17437382de4-m0.avro | Bin 6176 -> 0 bytes ...-8219546d-26d5-434b-aabb-d17437382de4.avro | Bin 3782 -> 0 bytes ...-759c-4136-8fb4-957722126c7f.metadata.json | 386 --- ...fcd5d7-cef5-4a88-a153-88f349613b01-m0.avro | Bin 8498 -> 0 bytes ...-3dfcd5d7-cef5-4a88-a153-88f349613b01.avro | Bin 3783 -> 0 bytes ...-5484-4c5d-b4a0-ab8e80b0d970.metadata.json | 297 -- ...42fcd1-8e83-4bea-921f-429e6c20c65b-m0.avro | Bin 415099 -> 0 bytes ...-6742fcd1-8e83-4bea-921f-429e6c20c65b.avro | Bin 3813 -> 0 bytes ...-299d-4162-b289-3eb91f22a733.metadata.json | 330 -- ...b632fc-de03-4b66-bf86-ae708d762e14-m0.avro | Bin 441350 -> 0 bytes ...-fdb632fc-de03-4b66-bf86-ae708d762e14.avro | Bin 3816 -> 0 bytes ...-42f8-43ec-8380-e551bd1dac77.metadata.json | 177 - ...d083d4-1117-471c-b68c-46c17b21cad6-m0.avro | Bin 6817 -> 0 bytes ...-bfd083d4-1117-471c-b68c-46c17b21cad6.avro | Bin 3786 -> 0 bytes ...-ef7e-4f80-a36a-2487795f98b3.metadata.json | 221 -- ...15941f-2631-4f48-8c06-40256d90839d-m0.avro | Bin 6815 -> 0 bytes ...-0815941f-2631-4f48-8c06-40256d90839d.avro | Bin 3783 -> 0 bytes ...-bff6-4c45-8f93-55b7557c5cd6.metadata.json | 221 -- ...8675c3-c016-43d4-ae0d-971d4d141dd7-m0.avro | Bin 7304 -> 0 bytes ...-928675c3-c016-43d4-ae0d-971d4d141dd7.avro | Bin 3784 -> 0 bytes ...-6abc-4fac-af46-8702ccccebfa.metadata.json | 341 -- ...ef99c4-de5b-411e-b587-288a6aa1bd5f-m0.avro | Bin 565197 -> 0 bytes ...-9fef99c4-de5b-411e-b587-288a6aa1bd5f.avro | Bin 3817 -> 0 bytes ...-773c-44e3-8a26-19b42235f8cf.metadata.json | 451 --- ...069005-df27-4ca7-a174-f5b6d743aa4c-m0.avro | 1871 ---------- ...-6e069005-df27-4ca7-a174-f5b6d743aa4c.avro | Bin 3815 -> 0 bytes ...-5fc9-4adb-b604-a6449ac112bf.metadata.json | 353 -- ...902609-9edc-400d-8b7f-6e5ed1adcab8-m0.avro | Bin 7806 -> 0 bytes ...-fc902609-9edc-400d-8b7f-6e5ed1adcab8.avro | Bin 3782 -> 0 bytes ...-1a0b-44af-a643-d2e737ecb560.metadata.json | 397 --- ...b81736-4d75-4b23-a088-91f01e2d9b47-m0.avro | Bin 8104 -> 0 bytes ...-bbb81736-4d75-4b23-a088-91f01e2d9b47.avro | Bin 3785 -> 0 bytes ...-164a-482e-b4fc-e7c4660ff071.metadata.json | 166 - ...c343fe-ccc8-4f0f-9bf0-2926df207e9a-m0.avro | Bin 6982 -> 0 bytes ...-37c343fe-ccc8-4f0f-9bf0-2926df207e9a.avro | Bin 3787 -> 0 bytes ...-553c-424f-96e3-449a8a2b0545.metadata.json | 364 -- ...efdd40-4c7a-42e8-a446-b1af8a7deb69-m0.avro | Bin 10666 -> 0 bytes ...-b2efdd40-4c7a-42e8-a446-b1af8a7deb69.avro | Bin 3791 -> 0 bytes ...-6902-4596-b4ec-b6e767971497.metadata.json | 441 --- ...f357df-ac7d-4eff-a9ac-5e348421f178-m0.avro | Bin 29268 -> 0 bytes ...-81f357df-ac7d-4eff-a9ac-5e348421f178.avro | Bin 3792 -> 0 bytes ...-cce6-4a5f-8077-60cbd89a48de.metadata.json | 265 -- ...b4a206-6242-44f2-abf0-07a749715635-m0.avro | Bin 8042 -> 0 bytes ...-9ab4a206-6242-44f2-abf0-07a749715635.avro | Bin 3784 -> 0 bytes ...-05c3-457f-b01b-7021368ee7ca.metadata.json | 210 -- ...959e83-0739-49a9-b4d0-d8a4410d0de5-m0.avro | Bin 7423 -> 0 bytes ...-0c959e83-0739-49a9-b4d0-d8a4410d0de5.avro | Bin 3789 -> 0 bytes ...-6769-4372-84b3-a5de5fd7121c.metadata.json | 166 - ...f3da13-bbed-4ae5-9bd1-cc8d8a549852-m0.avro | Bin 6740 -> 0 bytes ...-4ff3da13-bbed-4ae5-9bd1-cc8d8a549852.avro | Bin 3794 -> 0 bytes ...-ba48-46d5-9ae5-f9989e7a9d57.metadata.json | 375 -- ...f907a4-67ef-40d4-98f2-e748e4a3d238-m0.avro | Bin 8786 -> 0 bytes ...-6cf907a4-67ef-40d4-98f2-e748e4a3d238.avro | Bin 3783 -> 0 bytes ...-fe2d-49bc-9014-6a86af1b9184.metadata.json | 122 - ...7a9eea-51da-4570-a291-9f702051709b-m0.avro | Bin 6376 -> 0 bytes ...-ab7a9eea-51da-4570-a291-9f702051709b.avro | Bin 3791 -> 0 bytes ...-9c95-48be-8af8-ff5f752a5a88.metadata.json | 100 - ...4c7883-5fd9-49c5-9c1a-467fe5c49bfc-m0.avro | Bin 6151 -> 0 bytes ...-8c4c7883-5fd9-49c5-9c1a-467fe5c49bfc.avro | Bin 3781 -> 0 bytes ...-c2ff-46f2-b893-d3755e29148c.metadata.json | 111 - ...9e3684-dfee-4c0c-a7b6-194b67b916d4-m0.avro | Bin 6286 -> 0 bytes ...-e79e3684-dfee-4c0c-a7b6-194b67b916d4.avro | Bin 3790 -> 0 bytes ...-3d76-44ed-b705-fcb2462c017a.metadata.json | 309 -- ...cca44a-2f36-4ee6-b15e-90545a9914e9-m0.avro | Bin 8427 -> 0 bytes ...-96cca44a-2f36-4ee6-b15e-90545a9914e9.avro | Bin 3781 -> 0 bytes ...-720f-4653-8388-40ba51e1d407.metadata.json | 276 -- ...174fa3-1fa4-4ed4-ac01-e3b763bd5107-m0.avro | Bin 7666 -> 0 bytes ...-c7174fa3-1fa4-4ed4-ac01-e3b763bd5107.avro | Bin 3783 -> 0 bytes ...-c8b9-4b35-837a-5728077c1415.metadata.json | 100 - ...248139-c43f-47dd-8495-a81175bf12e6-m0.avro | Bin 5928 -> 0 bytes ...-bf248139-c43f-47dd-8495-a81175bf12e6.avro | Bin 3778 -> 0 bytes ...-554a-44ff-9ece-6d0aa2125df8.metadata.json | 133 - ...64121d-c401-4590-9c64-80bad6030d6a-m0.avro | Bin 6173 -> 0 bytes ...-ec64121d-c401-4590-9c64-80bad6030d6a.avro | Bin 3781 -> 0 bytes ...-7455-4688-bf34-8b63ebd45185.metadata.json | 386 --- ...2700cc-52a0-4e98-b68d-2bf6c97afaf0-m0.avro | Bin 8494 -> 0 bytes ...-3d2700cc-52a0-4e98-b68d-2bf6c97afaf0.avro | Bin 3779 -> 0 bytes ...-71f7-4b79-9349-58f69f4fc435.metadata.json | 287 -- ...9a98c3-e090-46a5-b8c1-2b98b19afcfc-m0.avro | Bin 9531 -> 0 bytes ...-1f9a98c3-e090-46a5-b8c1-2b98b19afcfc.avro | Bin 3790 -> 0 bytes ...-73b8-494a-b361-ef0489640024.metadata.json | 320 -- ...cef31b-41d6-468c-bb42-93affe6951ca-m0.avro | Bin 26783 -> 0 bytes ...-99cef31b-41d6-468c-bb42-93affe6951ca.avro | Bin 3793 -> 0 bytes ...-cfcd-44c0-be9b-403d099a042a.metadata.json | 177 - ...b16a0b-7ae8-47ec-a431-3a1127f188dd-m0.avro | Bin 6833 -> 0 bytes ...-66b16a0b-7ae8-47ec-a431-3a1127f188dd.avro | Bin 3781 -> 0 bytes ...-0e56-4b52-9983-39d6882fb51e.metadata.json | 221 -- ...77e0c7-7e45-48be-86dc-25c25ca4caa9-m0.avro | Bin 6810 -> 0 bytes ...-6177e0c7-7e45-48be-86dc-25c25ca4caa9.avro | Bin 3776 -> 0 bytes ...-0cfb-4ef6-bbd0-320747fb3ad1.metadata.json | 221 -- ...0ff455-0714-4117-aa41-2dc31716ffc6-m0.avro | Bin 7298 -> 0 bytes ...-140ff455-0714-4117-aa41-2dc31716ffc6.avro | Bin 3779 -> 0 bytes ...-3062-4b50-984e-163027319097.metadata.json | 331 -- ...a9f918-c205-49b4-a160-2a8fdea6c408-m0.avro | Bin 8902 -> 0 bytes ...-eba9f918-c205-49b4-a160-2a8fdea6c408.avro | Bin 3787 -> 0 bytes ...-5546-4556-a86a-4154acc746e7.metadata.json | 441 --- ...c61da1-0e94-4028-aad0-571a221b14e7-m0.avro | Bin 17738 -> 0 bytes ...-96c61da1-0e94-4028-aad0-571a221b14e7.avro | Bin 3791 -> 0 bytes ...-bf34-4691-869f-04790fbcfea7.metadata.json | 353 -- ...8be12f-c97f-4a38-b468-967972f28eb5-m0.avro | Bin 7805 -> 0 bytes ...-ce8be12f-c97f-4a38-b468-967972f28eb5.avro | Bin 3780 -> 0 bytes ...-9b2d-47ff-b99d-6651df79a90f.metadata.json | 397 --- ...1023b4-6337-466c-8a49-ca737e4de1e6-m0.avro | Bin 8220 -> 0 bytes ...-e21023b4-6337-466c-8a49-ca737e4de1e6.avro | Bin 3789 -> 0 bytes ...-55e3-41b5-880a-5147a972c288.metadata.json | 166 - ...0f6b66-1991-4295-b758-8a103f131278-m0.avro | Bin 7264 -> 0 bytes ...-c80f6b66-1991-4295-b758-8a103f131278.avro | Bin 3792 -> 0 bytes ...-f58d-4caf-a091-9d9f69ccf38a.metadata.json | 374 -- ...607888-8425-44d1-a805-3fa09850a308-m0.avro | Bin 789355 -> 0 bytes ...-0e607888-8425-44d1-a805-3fa09850a308.avro | Bin 3816 -> 0 bytes ...-488f-47fa-b807-878548c039ed.metadata.json | 451 --- ...fa6a85-351c-4221-9149-e24f845e0aab-m0.avro | 3023 ---------------- ...-bffa6a85-351c-4221-9149-e24f845e0aab.avro | Bin 3817 -> 0 bytes ...-42d6-4481-b109-a6e77d653c8b.metadata.json | 265 -- ...4651b4-d41a-43fe-9b7b-b1ce97b12f81-m0.avro | Bin 8577 -> 0 bytes ...-384651b4-d41a-43fe-9b7b-b1ce97b12f81.avro | Bin 3789 -> 0 bytes ...-b792-4256-b355-f7a7bb59ff30.metadata.json | 210 -- ...e9211a-0cd9-4034-9266-c7722c1b7559-m0.avro | Bin 7793 -> 0 bytes ...-e4e9211a-0cd9-4034-9266-c7722c1b7559.avro | Bin 3796 -> 0 bytes ...-e0de-4f2b-bd05-5f9c3a482dcb.metadata.json | 166 - ...89f5c2-ecbf-4b3e-8734-19add5f223b2-m0.avro | Bin 6940 -> 0 bytes ...-da89f5c2-ecbf-4b3e-8734-19add5f223b2.avro | Bin 3800 -> 0 bytes ...-6c5f-4ec9-9bf1-1a17a5e7cc1c.metadata.json | 375 -- ...e086f1-09c3-4393-9b69-b50996314dd3-m0.avro | Bin 9267 -> 0 bytes ...-a5e086f1-09c3-4393-9b69-b50996314dd3.avro | Bin 3788 -> 0 bytes ...-7d9f-45e6-a8d4-0a6584ff3d85.metadata.json | 122 - ...730f87-2407-41d0-9fd5-85e7668336e3-m0.avro | Bin 6492 -> 0 bytes ...-4f730f87-2407-41d0-9fd5-85e7668336e3.avro | Bin 3797 -> 0 bytes ...-4baf-4515-83bc-00b6f63e40ed.metadata.json | 100 - ...3490b7-e1c2-467a-8a2a-0731a236ef94-m0.avro | Bin 6127 -> 0 bytes ...-9b3490b7-e1c2-467a-8a2a-0731a236ef94.avro | Bin 3789 -> 0 bytes ...-f403-42f3-b3d6-73ee96472da3.metadata.json | 121 - ...d94170-9def-424b-b8a6-ddc6c170d9b1-m0.avro | Bin 22474 -> 0 bytes ...-16d94170-9def-424b-b8a6-ddc6c170d9b1.avro | Bin 3807 -> 0 bytes ...-2247-4ec5-adf6-ae30605db15b.metadata.json | 309 -- ...83e1a0-079d-42fe-87f6-ff23a0840a5c-m0.avro | Bin 8930 -> 0 bytes ...-6683e1a0-079d-42fe-87f6-ff23a0840a5c.avro | Bin 3788 -> 0 bytes ...-53d6-4cd0-9639-089a17b5707f.metadata.json | 276 -- ...3005ce-7a3e-4260-872a-e609a7597081-m0.avro | Bin 7863 -> 0 bytes ...-a43005ce-7a3e-4260-872a-e609a7597081.avro | Bin 3789 -> 0 bytes ...-5734-4c79-b9e6-d0ec7b90e8c1.metadata.json | 100 - ...fee408-669e-41c7-a70c-719122d7fdc4-m0.avro | Bin 6043 -> 0 bytes ...-c0fee408-669e-41c7-a70c-719122d7fdc4.avro | Bin 3785 -> 0 bytes ...-7ac7-40c6-a00a-1dbe7b4dd5b5.metadata.json | 133 - ...0c7bc1-48e8-4f95-ba69-e3703029c025-m0.avro | Bin 6202 -> 0 bytes ...-070c7bc1-48e8-4f95-ba69-e3703029c025.avro | Bin 3785 -> 0 bytes ...-1f99-4c10-b931-ee450d7c8bc5.metadata.json | 386 --- ...de8a60-e04e-4495-b6ec-4846f70b4b6b-m0.avro | Bin 8119 -> 0 bytes ...-0dde8a60-e04e-4495-b6ec-4846f70b4b6b.avro | Bin 3782 -> 0 bytes ...-e9b0-4157-8aa4-2a3230d8ecd3.metadata.json | 297 -- ...ca9089-7256-4012-9082-858ddb6c2eae-m0.avro | Bin 574282 -> 0 bytes ...-fbca9089-7256-4012-9082-858ddb6c2eae.avro | Bin 3816 -> 0 bytes ...-0286-413e-9c52-986221cc8a7d.metadata.json | 330 -- ...99e356-57c6-45af-9c74-5b984ffcbb80-m0.avro | Bin 621793 -> 0 bytes ...-9299e356-57c6-45af-9c74-5b984ffcbb80.avro | Bin 3818 -> 0 bytes ...-7ef8-4956-b815-4d190e6caa27.metadata.json | 177 - ...b011b3-8b57-4dff-875c-4dd6db7ad6b4-m0.avro | Bin 7017 -> 0 bytes ...-c1b011b3-8b57-4dff-875c-4dd6db7ad6b4.avro | Bin 3786 -> 0 bytes ...-a351-4660-ab95-539b41950c4b.metadata.json | 221 -- ...ea3a99-100e-421d-bd48-d963bf217bbb-m0.avro | Bin 6868 -> 0 bytes ...-daea3a99-100e-421d-bd48-d963bf217bbb.avro | Bin 3784 -> 0 bytes ...-a417-43be-b61e-766768459e3f.metadata.json | 221 -- ...39905a-92c2-41a3-ae7b-25d23273dccf-m0.avro | Bin 7544 -> 0 bytes ...-1839905a-92c2-41a3-ae7b-25d23273dccf.avro | Bin 3787 -> 0 bytes ...-93ef-493e-a23c-2316f1ab787d.metadata.json | 341 -- ...6373bf-8105-40e7-af78-8519b9edebff-m0.avro | Bin 761962 -> 0 bytes ...-dc6373bf-8105-40e7-af78-8519b9edebff.avro | Bin 3815 -> 0 bytes ...-296d-4767-99d5-2dbc01e759a4.metadata.json | 451 --- ...8c5447-c661-442f-99c4-cd2203afcb9a-m0.avro | 3052 ----------------- ...-c08c5447-c661-442f-99c4-cd2203afcb9a.avro | Bin 3817 -> 0 bytes ...-4dae-4195-ba13-40e7023ffa58.metadata.json | 353 -- ...bad00b-8038-45ba-b97c-7c765af2c811-m0.avro | Bin 7907 -> 0 bytes ...-b1bad00b-8038-45ba-b97c-7c765af2c811.avro | Bin 3781 -> 0 bytes ...-a534-444a-968b-e642e48d1618.metadata.json | 397 --- ...cb8c91-e49d-4c3c-b678-7f9305c77c68-m0.avro | Bin 8214 -> 0 bytes ...-e1cb8c91-e49d-4c3c-b678-7f9305c77c68.avro | Bin 3783 -> 0 bytes ...-946f-4c61-936e-a87e5e77dbd3.metadata.json | 166 - ...08e506-df9d-4e48-bcd2-63d0d897b29e-m0.avro | Bin 7216 -> 0 bytes ...-4508e506-df9d-4e48-bcd2-63d0d897b29e.avro | Bin 3789 -> 0 bytes ...-e057-410c-b547-ada25a99ccb1.metadata.json | 364 -- ...4fa463-9aef-402a-8635-bd783cb9a133-m0.avro | Bin 12195 -> 0 bytes ...-fa4fa463-9aef-402a-8635-bd783cb9a133.avro | Bin 3798 -> 0 bytes ...-4e58-4a9e-8313-f9e75941c6f2.metadata.json | 441 --- ...7e3adf-c440-4b9d-bef9-615275412981-m0.avro | Bin 44414 -> 0 bytes ...-2e7e3adf-c440-4b9d-bef9-615275412981.avro | Bin 3797 -> 0 bytes ...-c833-4a10-ad35-24a5eecff81f.metadata.json | 265 -- ...ecdd7a-633b-4eaf-808e-e8a76f2afc93-m0.avro | Bin 8582 -> 0 bytes ...-45ecdd7a-633b-4eaf-808e-e8a76f2afc93.avro | Bin 3790 -> 0 bytes ...-4e58-4fca-8984-d07ac74beec9.metadata.json | 210 -- ...16d408-6c08-4422-92ba-d10aee4c038d-m0.avro | Bin 7804 -> 0 bytes ...-4516d408-6c08-4422-92ba-d10aee4c038d.avro | Bin 3793 -> 0 bytes ...-76cb-4b35-a637-ac13df61d7e5.metadata.json | 166 - ...fc14d2-fba9-4f14-b636-772729a87995-m0.avro | Bin 6941 -> 0 bytes ...-8afc14d2-fba9-4f14-b636-772729a87995.avro | Bin 3796 -> 0 bytes ...-8f6b-46ce-aaaf-ad0aed68ce59.metadata.json | 375 -- ...3fae69-41bb-487f-93eb-39739d52225a-m0.avro | Bin 9134 -> 0 bytes ...-833fae69-41bb-487f-93eb-39739d52225a.avro | Bin 3785 -> 0 bytes ...-c560-4dea-8ca5-745e3a3f7fc1.metadata.json | 122 - ...dce8d0-485c-4feb-83bc-df6a677886fe-m0.avro | Bin 6496 -> 0 bytes ...-c0dce8d0-485c-4feb-83bc-df6a677886fe.avro | Bin 3792 -> 0 bytes ...-28ce-4a93-9309-f95b81f494bb.metadata.json | 100 - ...15ac44-d82e-4a05-a038-65fde8ef6da3-m0.avro | Bin 6123 -> 0 bytes ...-1015ac44-d82e-4a05-a038-65fde8ef6da3.avro | Bin 3784 -> 0 bytes ...-1f4b-4419-bef6-9f77e0de188a.metadata.json | 111 - ...58c7c7-d25c-4356-8db9-b1c7296943db-m0.avro | Bin 6440 -> 0 bytes ...-5558c7c7-d25c-4356-8db9-b1c7296943db.avro | Bin 3792 -> 0 bytes ...-49f5-4161-abfe-9bdce15c0060.metadata.json | 309 -- ...fcc6a2-2e2f-4fdc-bc85-6932abb37aec-m0.avro | Bin 8955 -> 0 bytes ...-41fcc6a2-2e2f-4fdc-bc85-6932abb37aec.avro | Bin 3788 -> 0 bytes ...-1be6-4681-a37a-e645575b4d79.metadata.json | 276 -- ...427289-cf08-4939-a662-11b5a04a2a82-m0.avro | Bin 7851 -> 0 bytes ...-0c427289-cf08-4939-a662-11b5a04a2a82.avro | Bin 3786 -> 0 bytes ...-5936-4426-88ec-e7b613e34aec.metadata.json | 100 - ...a52cb7-2991-4880-90a8-07a6a56c89f5-m0.avro | Bin 6030 -> 0 bytes ...-75a52cb7-2991-4880-90a8-07a6a56c89f5.avro | Bin 3782 -> 0 bytes ...-2d0f-40b4-9cf9-58a0a294bf84.metadata.json | 133 - ...25905e-562c-4a35-9e6c-da1f5a57437c-m0.avro | Bin 6198 -> 0 bytes ...-0e25905e-562c-4a35-9e6c-da1f5a57437c.avro | Bin 3783 -> 0 bytes ...-7950-4449-baba-91f153c638c8.metadata.json | 386 --- ...e69e01-f4eb-41bb-87ef-df547e18e0d5-m0.avro | Bin 8490 -> 0 bytes ...-dbe69e01-f4eb-41bb-87ef-df547e18e0d5.avro | Bin 3783 -> 0 bytes ...-4685-4885-8135-377e67bb7045.metadata.json | 287 -- ...4d5031-eb6d-42a9-886c-92d65dd60c58-m0.avro | Bin 11317 -> 0 bytes ...-0f4d5031-eb6d-42a9-886c-92d65dd60c58.avro | Bin 3794 -> 0 bytes ...-586c-4989-8e91-5d55096c51ff.metadata.json | 320 -- ...7b55c8-1883-44c4-9c84-f0a475518c5d-m0.avro | Bin 40066 -> 0 bytes ...-037b55c8-1883-44c4-9c84-f0a475518c5d.avro | Bin 3792 -> 0 bytes ...-0f96-4d37-8f70-2d1fb5e236bf.metadata.json | 177 - ...c582ab-e4f8-4275-8d8c-b34457635479-m0.avro | Bin 7008 -> 0 bytes ...-3cc582ab-e4f8-4275-8d8c-b34457635479.avro | Bin 3787 -> 0 bytes ...-d122-4849-b30e-7eba5da700d9.metadata.json | 221 -- ...e0ec08-ee7e-47f9-881e-8713ab061965-m0.avro | Bin 6868 -> 0 bytes ...-4ce0ec08-ee7e-47f9-881e-8713ab061965.avro | Bin 3782 -> 0 bytes ...-255b-4f17-a411-8c76483037d4.metadata.json | 221 -- ...b13234-29d8-4a96-a78c-423abbd72428-m0.avro | Bin 7557 -> 0 bytes ...-a0b13234-29d8-4a96-a78c-423abbd72428.avro | Bin 3785 -> 0 bytes ...-ffb7-4294-9b18-d270678ac85d.metadata.json | 331 -- ...1134ab-6c97-4c8a-ad75-729685823145-m0.avro | Bin 9691 -> 0 bytes ...-0e1134ab-6c97-4c8a-ad75-729685823145.avro | Bin 3793 -> 0 bytes ...-bbfa-4f25-be27-1f9d8239301f.metadata.json | 441 --- ...148074-cbae-4bc4-b94e-62d9fa618ebb-m0.avro | Bin 24907 -> 0 bytes ...-86148074-cbae-4bc4-b94e-62d9fa618ebb.avro | Bin 3794 -> 0 bytes ...-0586-48f9-86e5-6782429b329f.metadata.json | 353 -- ...3481ac-9183-4c42-9afa-c8c18af931ba-m0.avro | Bin 7903 -> 0 bytes ...-8c3481ac-9183-4c42-9afa-c8c18af931ba.avro | Bin 3781 -> 0 bytes ...-9dc0-4241-8794-a7b4112eeefd.metadata.json | 155 - ...e1d166-8a50-495d-a9af-52d223afd979-m0.avro | Bin 7594 -> 0 bytes ...-aae1d166-8a50-495d-a9af-52d223afd979.avro | Bin 3793 -> 0 bytes ...-65a5-4a63-addb-5a40e3eb0302.metadata.json | 253 -- ...8a886c-55cf-47e9-ba6c-642d28b8282f-m0.avro | Bin 336897 -> 0 bytes ...-298a886c-55cf-47e9-ba6c-642d28b8282f.avro | Bin 3805 -> 0 bytes ...-107d-4125-b15d-8fcadf903b05.metadata.json | 111 - ...7d21c1-573d-462a-9e50-56c2d5a0eadf-m0.avro | Bin 6244 -> 0 bytes ...-ca7d21c1-573d-462a-9e50-56c2d5a0eadf.avro | Bin 3785 -> 0 bytes ...-1fc8-488b-b320-a017535c9c71.metadata.json | 176 - ...7513f4-29a3-4499-9f24-b7df6ed65323-m0.avro | Bin 226830 -> 0 bytes ...-367513f4-29a3-4499-9f24-b7df6ed65323.avro | Bin 3801 -> 0 bytes ...-0e95-4dc9-bfe5-8720b9ce9c9d.metadata.json | 166 - ...390b4f-4d94-4aa1-a2b7-d60a4f18ca59-m0.avro | Bin 6817 -> 0 bytes ...-41390b4f-4d94-4aa1-a2b7-d60a4f18ca59.avro | Bin 3786 -> 0 bytes ...-e98a-446d-a0fc-de2976040eef.metadata.json | 122 - ...c47d71-81d2-4d24-b332-198ce4da3f5e-m0.avro | Bin 7756 -> 0 bytes ...-2ec47d71-81d2-4d24-b332-198ce4da3f5e.avro | Bin 3790 -> 0 bytes ...-f262-4275-a11f-2fb9efd56ca5.metadata.json | 100 - ...9dbdb4-c413-443c-8597-690949ea4ce3-m0.avro | Bin 6043 -> 0 bytes ...-329dbdb4-c413-443c-8597-690949ea4ce3.avro | Bin 3786 -> 0 bytes ...-31b3-49d6-a1be-2d6457e10487.metadata.json | 144 - ...40356e-47da-42c3-93d5-49146db8940e-m0.avro | Bin 6829 -> 0 bytes ...-0940356e-47da-42c3-93d5-49146db8940e.avro | Bin 3791 -> 0 bytes ...-6d15-4bf9-93f6-ff779c34654c.metadata.json | 155 - ...c47218-453f-4b6b-9a31-22e5d0cf0ff3-m0.avro | Bin 7602 -> 0 bytes ...-adc47218-453f-4b6b-9a31-22e5d0cf0ff3.avro | Bin 3788 -> 0 bytes ...-6c41-42a6-a608-0139125beae3.metadata.json | 243 -- ...bed02d-660f-4274-bf90-81621ac8000d-m0.avro | Bin 21057 -> 0 bytes ...-27bed02d-660f-4274-bf90-81621ac8000d.avro | Bin 3788 -> 0 bytes ...-ba15-4cde-82da-d240aee66235.metadata.json | 111 - ...b3f9cf-2a25-4652-8db6-972b8f8a70b1-m0.avro | Bin 6239 -> 0 bytes ...-49b3f9cf-2a25-4652-8db6-972b8f8a70b1.avro | Bin 3781 -> 0 bytes ...-376b-4e80-b317-b3fcbee8a4a0.metadata.json | 166 - ...01c8f5-3b35-4089-97f9-e2711080f425-m0.avro | Bin 9464 -> 0 bytes ...-4001c8f5-3b35-4089-97f9-e2711080f425.avro | Bin 3787 -> 0 bytes ...-d6d0-49c4-993e-aac98024d18f.metadata.json | 166 - ...1d5ca2-a258-420b-84e6-b3f2e86fe27d-m0.avro | Bin 6806 -> 0 bytes ...-391d5ca2-a258-420b-84e6-b3f2e86fe27d.avro | Bin 3784 -> 0 bytes ...-ef71-4fb9-9fc1-a2daa5520423.metadata.json | 122 - ...4ba584-b742-4d1f-8008-ec8283bb5946-m0.avro | Bin 7681 -> 0 bytes ...-a04ba584-b742-4d1f-8008-ec8283bb5946.avro | Bin 3787 -> 0 bytes ...-2743-4941-87ca-07994e16e4fb.metadata.json | 100 - ...8dcd78-0714-4b2e-84c1-28b4c77b8c13-m0.avro | Bin 6036 -> 0 bytes ...-258dcd78-0714-4b2e-84c1-28b4c77b8c13.avro | Bin 3780 -> 0 bytes ...-5e02-43a2-85c1-874b7cf8e5d8.metadata.json | 144 - ...06a73a-bab9-4da0-a294-1522da0fb7dd-m0.avro | Bin 6817 -> 0 bytes ...-d506a73a-bab9-4da0-a294-1522da0fb7dd.avro | Bin 3785 -> 0 bytes ...-e048-4852-a4c7-714bb8baa437.metadata.json | 155 - ...e9cd1b-7781-4dde-9335-6822c5386bf3-m0.avro | Bin 8271 -> 0 bytes ...-33e9cd1b-7781-4dde-9335-6822c5386bf3.avro | Bin 3792 -> 0 bytes ...-b607-4375-b38a-59b0fdd06683.metadata.json | 253 -- ...714a3b-16d8-4ad5-a96d-06b3319586b4-m0.avro | Bin 484801 -> 0 bytes ...-62714a3b-16d8-4ad5-a96d-06b3319586b4.avro | Bin 3809 -> 0 bytes ...-c879-478a-8aa8-abd8a523df51.metadata.json | 111 - ...7f7ceb-3506-4df0-9aa0-3f572afcf0d3-m0.avro | Bin 6349 -> 0 bytes ...-057f7ceb-3506-4df0-9aa0-3f572afcf0d3.avro | Bin 3781 -> 0 bytes ...-eb63-480d-8d14-1476113fd04a.metadata.json | 176 - ...a26561-2404-458e-9175-b0e2959b280b-m0.avro | Bin 307126 -> 0 bytes ...-a8a26561-2404-458e-9175-b0e2959b280b.avro | Bin 3807 -> 0 bytes ...-bec5-4989-8e44-3aa5375b5231.metadata.json | 166 - ...2610ba-365d-468d-840a-166a97a61f0d-m0.avro | Bin 7139 -> 0 bytes ...-c92610ba-365d-468d-840a-166a97a61f0d.avro | Bin 3788 -> 0 bytes ...-da22-4f75-9846-576956ca20fe.metadata.json | 122 - ...61b2f6-a7af-4dab-be09-bd5bbdfa8faf-m0.avro | Bin 8693 -> 0 bytes ...-7e61b2f6-a7af-4dab-be09-bd5bbdfa8faf.avro | Bin 3792 -> 0 bytes ...-7197-4a19-aa6d-6a64a3873218.metadata.json | 100 - ...a03294-b36a-4455-a41b-ee395d1a5a49-m0.avro | Bin 6038 -> 0 bytes ...-12a03294-b36a-4455-a41b-ee395d1a5a49.avro | Bin 3782 -> 0 bytes ...-05fb-4aea-a4c9-66727e05c918.metadata.json | 144 - ...d699fe-e7c0-45d3-b2c5-f8cff28a432f-m0.avro | Bin 7167 -> 0 bytes ...-96d699fe-e7c0-45d3-b2c5-f8cff28a432f.avro | Bin 3791 -> 0 bytes ...-0ce9-4e5e-ba87-e86c8cc33af6.metadata.json | 155 - ...750c07-6e32-4867-a606-44111b035949-m0.avro | Bin 8252 -> 0 bytes ...-30750c07-6e32-4867-a606-44111b035949.avro | Bin 3791 -> 0 bytes ...-7f87-4bc3-9ad1-efb29f31b2ef.metadata.json | 243 -- ...b45a05-f72f-48d1-82f9-b6ea7baffaee-m0.avro | Bin 33502 -> 0 bytes ...-9eb45a05-f72f-48d1-82f9-b6ea7baffaee.avro | Bin 3789 -> 0 bytes ...-1beb-44f4-8e56-8eaa274bc9c7.metadata.json | 111 - ...a2705e-f37e-4708-9dd0-34090f475f02-m0.avro | Bin 6410 -> 0 bytes ...-e3a2705e-f37e-4708-9dd0-34090f475f02.avro | Bin 3781 -> 0 bytes ...-0e38-4dbb-8a45-b1102b4616b5.metadata.json | 166 - ...c9dd59-f137-4d62-bba4-cd37998c427d-m0.avro | Bin 11526 -> 0 bytes ...-a2c9dd59-f137-4d62-bba4-cd37998c427d.avro | Bin 3790 -> 0 bytes ...-a40c-4d6a-adc8-f0abd7376d1a.metadata.json | 166 - ...dd0121-9c67-4612-a698-952806d01c6d-m0.avro | Bin 7143 -> 0 bytes ...-5edd0121-9c67-4612-a698-952806d01c6d.avro | Bin 3785 -> 0 bytes ...-e3d3-4d3d-a331-78e604e9d362.metadata.json | 122 - ...c1c648-86af-4ecf-b9cb-85595ef91a48-m0.avro | Bin 8761 -> 0 bytes ...-00c1c648-86af-4ecf-b9cb-85595ef91a48.avro | Bin 3786 -> 0 bytes ...-992a-4c5e-ba08-fcff4733bf75.metadata.json | 100 - ...47ad2f-9095-47cd-8988-4809d94e799c-m0.avro | Bin 6111 -> 0 bytes ...-3f47ad2f-9095-47cd-8988-4809d94e799c.avro | Bin 3783 -> 0 bytes ...-1c61-4083-b182-0c99c6f89305.metadata.json | 144 - ...9263b5-5a1f-4f16-bea1-f0f748f0c0a8-m0.avro | Bin 7172 -> 0 bytes ...-359263b5-5a1f-4f16-bea1-f0f748f0c0a8.avro | Bin 3789 -> 0 bytes ...-4eed-4e77-ad02-4c936c7d8d6a.metadata.json | 212 -- ...bcd002-b25e-42d3-9a20-680286272ed5-m0.avro | 6 - ...-f8bcd002-b25e-42d3-9a20-680286272ed5.avro | Bin 4297 -> 0 bytes ...-5b3d-4c75-ae60-3dddfed4dacf.metadata.json | 107 - ...f08a55-904f-4581-aa97-f856752b6967-m0.avro | Bin 7385 -> 0 bytes ...-cdf08a55-904f-4581-aa97-f856752b6967.avro | Bin 4303 -> 0 bytes ...-7cf1-4e3d-9557-e434c41a2383.metadata.json | 197 -- ...002ed6-2a22-4402-8b13-0b9b85931824-m0.avro | 209 -- ...-1b002ed6-2a22-4402-8b13-0b9b85931824.avro | Bin 4312 -> 0 bytes ...-88d2-4aca-8724-500bf37495e8.metadata.json | 232 -- ...052cea-ed9d-49d0-8d6b-2ec6dd57e46e-m0.avro | 2495 -------------- ...-11052cea-ed9d-49d0-8d6b-2ec6dd57e46e.avro | Bin 4310 -> 0 bytes ...-fbfc-4a07-8fba-7e2291ed4e27.metadata.json | 152 - ...caaa8a-6674-4c6a-84e2-1025ea03170b-m0.avro | Bin 10951 -> 0 bytes ...-94caaa8a-6674-4c6a-84e2-1025ea03170b.avro | Bin 4304 -> 0 bytes ...-0787-4713-a480-2286504d543f.metadata.json | 127 - ...3a7184-53df-490f-a4b9-480b8e43acba-m0.avro | Bin 8157 -> 0 bytes ...-ac3a7184-53df-490f-a4b9-480b8e43acba.avro | Bin 4307 -> 0 bytes ...-0ce6-4a43-bf66-d25bf0af2dfa.metadata.json | 107 - ...47d08d-e19d-4f6b-8cd3-e07f95b6e1f5-m0.avro | Bin 7354 -> 0 bytes ...-8347d08d-e19d-4f6b-8cd3-e07f95b6e1f5.avro | Bin 4310 -> 0 bytes ...-1b80-43c8-91ff-f12d4920679c.metadata.json | 202 -- ...921b30-42c9-4690-86cd-a7d6f8180299-m0.avro | 5 - ...-94921b30-42c9-4690-86cd-a7d6f8180299.avro | Bin 4301 -> 0 bytes ...-3533-4945-acd5-14bbca4a5cd3.metadata.json | 87 - ...673543-f484-41ca-b5ea-77d5dc1a95cf-m0.avro | Bin 7009 -> 0 bytes ...-3f673543-f484-41ca-b5ea-77d5dc1a95cf.avro | Bin 4309 -> 0 bytes ...-0157-4d0e-a965-e7dd6068ec4e.metadata.json | 77 - ...7df258-effc-4253-a398-78634bfecfff-m0.avro | Bin 6833 -> 0 bytes ...-777df258-effc-4253-a398-78634bfecfff.avro | Bin 4298 -> 0 bytes ...-a5fe-43ea-9ea5-e15bec1e97f5.metadata.json | 82 - ...9da5f8-9688-41de-97b7-d7558d5793e3-m0.avro | Bin 10046 -> 0 bytes ...-b09da5f8-9688-41de-97b7-d7558d5793e3.avro | Bin 4307 -> 0 bytes ...-7442-45fd-bfb1-038936df7f5a.metadata.json | 172 - ...50169e-0264-44be-8503-dfa9dbed5fd4-m0.avro | Bin 8452 -> 0 bytes ...-7e50169e-0264-44be-8503-dfa9dbed5fd4.avro | Bin 4297 -> 0 bytes ...-14b3-44d4-9878-b7d36cc455ee.metadata.json | 157 - ...34c69a-e8ab-4a4b-866e-344ca294d9f0-m0.avro | Bin 8128 -> 0 bytes ...-1634c69a-e8ab-4a4b-866e-344ca294d9f0.avro | Bin 4300 -> 0 bytes ...-6305-4aaf-95b4-ca05171fc277.metadata.json | 77 - ...35d285-879e-414d-aa40-feb600797de4-m0.avro | Bin 6846 -> 0 bytes ...-b535d285-879e-414d-aa40-feb600797de4.avro | Bin 4296 -> 0 bytes ...-7e0c-4eea-9f90-c581e6edda1a.metadata.json | 92 - ...b2231a-ca5b-4ea0-b8ca-e712a3f15393-m0.avro | Bin 7096 -> 0 bytes ...-6db2231a-ca5b-4ea0-b8ca-e712a3f15393.avro | Bin 4295 -> 0 bytes ...-30f9-4547-a321-c09c13eabb7a.metadata.json | 207 -- ...4adc81-2b0b-41f4-84fd-9760561f7fa2-m0.avro | 4 - ...-f84adc81-2b0b-41f4-84fd-9760561f7fa2.avro | Bin 4299 -> 0 bytes ...-db8b-4537-b569-7d44104e8ef5.metadata.json | 162 - ...9bd945-ce7d-497e-9766-742f5edb0933-m0.avro | Bin 80183 -> 0 bytes ...-579bd945-ce7d-497e-9766-742f5edb0933.avro | Bin 4311 -> 0 bytes ...-01a1-4b1c-9760-fc4837d9ec1d.metadata.json | 177 - ...3ec034-868f-43ec-90a1-52e1c38c65f8-m0.avro | Bin 683772 -> 0 bytes ...-fc3ec034-868f-43ec-90a1-52e1c38c65f8.avro | Bin 4308 -> 0 bytes ...-1d98-4cbc-9356-7838397425ed.metadata.json | 112 - ...ba4857-ca91-4c07-96c3-f2549618d4ec-m0.avro | Bin 7343 -> 0 bytes ...-a0ba4857-ca91-4c07-96c3-f2549618d4ec.avro | Bin 4298 -> 0 bytes ...-aa29-4650-aa4f-31efa6057fc2.metadata.json | 132 - ...0b0ff9-aa47-44cd-a9a2-b59e12df5d70-m0.avro | Bin 7766 -> 0 bytes ...-c10b0ff9-aa47-44cd-a9a2-b59e12df5d70.avro | Bin 4300 -> 0 bytes ...-be4e-4b35-87ae-74d0e24ff027.metadata.json | 132 - ...683bd4-edf3-4581-a9fa-7644a32ae331-m0.avro | Bin 7752 -> 0 bytes ...-26683bd4-edf3-4581-a9fa-7644a32ae331.avro | Bin 4300 -> 0 bytes ...-5af3-486a-b9c2-b853c4792bda.metadata.json | 182 - ...80c5e3-b2f4-40a3-b1a3-67e016d116cd-m0.avro | 81 - ...-ab80c5e3-b2f4-40a3-b1a3-67e016d116cd.avro | Bin 4303 -> 0 bytes ...-e9a3-4105-b745-219c5c38da47.metadata.json | 232 -- ...6a3161-5ba9-4968-a394-a2eb6e3709ce-m0.avro | 1903 ---------- ...-416a3161-5ba9-4968-a394-a2eb6e3709ce.avro | Bin 4308 -> 0 bytes ...-e3e7-4e51-9edd-1fdbf0e97c30.metadata.json | 192 -- ...d8e2b4-78fd-47d7-b90c-1c52639f75b7-m0.avro | 8 - ...-fbd8e2b4-78fd-47d7-b90c-1c52639f75b7.avro | Bin 4293 -> 0 bytes .../trino/tpcds/hive/partitioned/q01.plan.txt | 43 - .../trino/tpcds/hive/partitioned/q02.plan.txt | 45 - .../trino/tpcds/hive/partitioned/q03.plan.txt | 16 - .../trino/tpcds/hive/partitioned/q04.plan.txt | 102 - .../trino/tpcds/hive/partitioned/q05.plan.txt | 64 - .../trino/tpcds/hive/partitioned/q06.plan.txt | 53 - .../trino/tpcds/hive/partitioned/q07.plan.txt | 25 - .../trino/tpcds/hive/partitioned/q08.plan.txt | 38 - .../trino/tpcds/hive/partitioned/q09.plan.txt | 122 - .../trino/tpcds/hive/partitioned/q10.plan.txt | 54 - .../trino/tpcds/hive/partitioned/q11.plan.txt | 69 - .../trino/tpcds/hive/partitioned/q12.plan.txt | 20 - .../trino/tpcds/hive/partitioned/q13.plan.txt | 28 - .../trino/tpcds/hive/partitioned/q14.plan.txt | 277 -- .../trino/tpcds/hive/partitioned/q15.plan.txt | 24 - .../trino/tpcds/hive/partitioned/q16.plan.txt | 37 - .../trino/tpcds/hive/partitioned/q17.plan.txt | 40 - .../trino/tpcds/hive/partitioned/q18.plan.txt | 39 - .../trino/tpcds/hive/partitioned/q19.plan.txt | 30 - .../trino/tpcds/hive/partitioned/q20.plan.txt | 19 - .../trino/tpcds/hive/partitioned/q21.plan.txt | 20 - .../trino/tpcds/hive/partitioned/q22.plan.txt | 18 - .../trino/tpcds/hive/partitioned/q23.plan.txt | 123 - .../trino/tpcds/hive/partitioned/q24.plan.txt | 78 - .../trino/tpcds/hive/partitioned/q25.plan.txt | 41 - .../trino/tpcds/hive/partitioned/q26.plan.txt | 25 - .../trino/tpcds/hive/partitioned/q27.plan.txt | 26 - .../trino/tpcds/hive/partitioned/q28.plan.txt | 47 - .../trino/tpcds/hive/partitioned/q29.plan.txt | 41 - .../trino/tpcds/hive/partitioned/q30.plan.txt | 54 - .../trino/tpcds/hive/partitioned/q31.plan.txt | 104 - .../trino/tpcds/hive/partitioned/q32.plan.txt | 32 - .../trino/tpcds/hive/partitioned/q33.plan.txt | 92 - .../trino/tpcds/hive/partitioned/q34.plan.txt | 26 - .../trino/tpcds/hive/partitioned/q35.plan.txt | 56 - .../trino/tpcds/hive/partitioned/q36.plan.txt | 23 - .../trino/tpcds/hive/partitioned/q37.plan.txt | 21 - .../trino/tpcds/hive/partitioned/q38.plan.txt | 56 - .../trino/tpcds/hive/partitioned/q39.plan.txt | 45 - .../trino/tpcds/hive/partitioned/q40.plan.txt | 26 - .../trino/tpcds/hive/partitioned/q41.plan.txt | 13 - .../trino/tpcds/hive/partitioned/q42.plan.txt | 16 - .../trino/tpcds/hive/partitioned/q43.plan.txt | 20 - .../trino/tpcds/hive/partitioned/q44.plan.txt | 48 - .../trino/tpcds/hive/partitioned/q45.plan.txt | 32 - .../trino/tpcds/hive/partitioned/q46.plan.txt | 36 - .../trino/tpcds/hive/partitioned/q47.plan.txt | 68 - .../trino/tpcds/hive/partitioned/q48.plan.txt | 24 - .../trino/tpcds/hive/partitioned/q49.plan.txt | 61 - .../trino/tpcds/hive/partitioned/q50.plan.txt | 25 - .../trino/tpcds/hive/partitioned/q51.plan.txt | 25 - .../trino/tpcds/hive/partitioned/q52.plan.txt | 16 - .../trino/tpcds/hive/partitioned/q53.plan.txt | 23 - .../trino/tpcds/hive/partitioned/q54.plan.txt | 69 - .../trino/tpcds/hive/partitioned/q55.plan.txt | 16 - .../trino/tpcds/hive/partitioned/q56.plan.txt | 91 - .../trino/tpcds/hive/partitioned/q57.plan.txt | 68 - .../trino/tpcds/hive/partitioned/q58.plan.txt | 98 - .../trino/tpcds/hive/partitioned/q59.plan.txt | 54 - .../trino/tpcds/hive/partitioned/q60.plan.txt | 91 - .../trino/tpcds/hive/partitioned/q61.plan.txt | 63 - .../trino/tpcds/hive/partitioned/q62.plan.txt | 24 - .../trino/tpcds/hive/partitioned/q63.plan.txt | 23 - .../trino/tpcds/hive/partitioned/q64.plan.txt | 201 -- .../trino/tpcds/hive/partitioned/q65.plan.txt | 37 - .../trino/tpcds/hive/partitioned/q66.plan.txt | 58 - .../trino/tpcds/hive/partitioned/q67.plan.txt | 24 - .../trino/tpcds/hive/partitioned/q68.plan.txt | 36 - .../trino/tpcds/hive/partitioned/q69.plan.txt | 56 - .../trino/tpcds/hive/partitioned/q70.plan.txt | 36 - .../trino/tpcds/hive/partitioned/q71.plan.txt | 35 - .../trino/tpcds/hive/partitioned/q72.plan.txt | 52 - .../trino/tpcds/hive/partitioned/q73.plan.txt | 26 - .../trino/tpcds/hive/partitioned/q74.plan.txt | 69 - .../trino/tpcds/hive/partitioned/q75.plan.txt | 120 - .../trino/tpcds/hive/partitioned/q76.plan.txt | 41 - .../trino/tpcds/hive/partitioned/q77.plan.txt | 88 - .../trino/tpcds/hive/partitioned/q78.plan.txt | 54 - .../trino/tpcds/hive/partitioned/q79.plan.txt | 24 - .../trino/tpcds/hive/partitioned/q80.plan.txt | 91 - .../trino/tpcds/hive/partitioned/q81.plan.txt | 55 - .../trino/tpcds/hive/partitioned/q82.plan.txt | 21 - .../trino/tpcds/hive/partitioned/q83.plan.txt | 105 - .../trino/tpcds/hive/partitioned/q84.plan.txt | 27 - .../trino/tpcds/hive/partitioned/q85.plan.txt | 42 - .../trino/tpcds/hive/partitioned/q86.plan.txt | 18 - .../trino/tpcds/hive/partitioned/q87.plan.txt | 56 - .../trino/tpcds/hive/partitioned/q88.plan.txt | 151 - .../trino/tpcds/hive/partitioned/q89.plan.txt | 22 - .../trino/tpcds/hive/partitioned/q90.plan.txt | 37 - .../trino/tpcds/hive/partitioned/q91.plan.txt | 36 - .../trino/tpcds/hive/partitioned/q92.plan.txt | 32 - .../trino/tpcds/hive/partitioned/q93.plan.txt | 18 - .../trino/tpcds/hive/partitioned/q94.plan.txt | 33 - .../trino/tpcds/hive/partitioned/q95.plan.txt | 50 - .../trino/tpcds/hive/partitioned/q96.plan.txt | 18 - .../trino/tpcds/hive/partitioned/q97.plan.txt | 25 - .../trino/tpcds/hive/partitioned/q98.plan.txt | 20 - .../trino/tpcds/hive/partitioned/q99.plan.txt | 24 - .../tpcds/hive/unpartitioned/q01.plan.txt | 44 - .../tpcds/hive/unpartitioned/q02.plan.txt | 45 - .../tpcds/hive/unpartitioned/q03.plan.txt | 16 - .../tpcds/hive/unpartitioned/q04.plan.txt | 102 - .../tpcds/hive/unpartitioned/q05.plan.txt | 64 - .../tpcds/hive/unpartitioned/q06.plan.txt | 51 - .../tpcds/hive/unpartitioned/q07.plan.txt | 24 - .../tpcds/hive/unpartitioned/q08.plan.txt | 38 - .../tpcds/hive/unpartitioned/q09.plan.txt | 122 - .../tpcds/hive/unpartitioned/q10.plan.txt | 55 - .../tpcds/hive/unpartitioned/q11.plan.txt | 68 - .../tpcds/hive/unpartitioned/q12.plan.txt | 18 - .../tpcds/hive/unpartitioned/q13.plan.txt | 26 - .../tpcds/hive/unpartitioned/q14.plan.txt | 277 -- .../tpcds/hive/unpartitioned/q15.plan.txt | 23 - .../tpcds/hive/unpartitioned/q16.plan.txt | 35 - .../tpcds/hive/unpartitioned/q17.plan.txt | 41 - .../tpcds/hive/unpartitioned/q18.plan.txt | 37 - .../tpcds/hive/unpartitioned/q19.plan.txt | 32 - .../tpcds/hive/unpartitioned/q20.plan.txt | 18 - .../tpcds/hive/unpartitioned/q21.plan.txt | 20 - .../tpcds/hive/unpartitioned/q22.plan.txt | 18 - .../tpcds/hive/unpartitioned/q23.plan.txt | 123 - .../tpcds/hive/unpartitioned/q24.plan.txt | 77 - .../tpcds/hive/unpartitioned/q25.plan.txt | 41 - .../tpcds/hive/unpartitioned/q26.plan.txt | 24 - .../tpcds/hive/unpartitioned/q27.plan.txt | 25 - .../tpcds/hive/unpartitioned/q28.plan.txt | 47 - .../tpcds/hive/unpartitioned/q29.plan.txt | 41 - .../tpcds/hive/unpartitioned/q30.plan.txt | 53 - .../tpcds/hive/unpartitioned/q31.plan.txt | 103 - .../tpcds/hive/unpartitioned/q32.plan.txt | 32 - .../tpcds/hive/unpartitioned/q33.plan.txt | 88 - .../tpcds/hive/unpartitioned/q34.plan.txt | 25 - .../tpcds/hive/unpartitioned/q35.plan.txt | 53 - .../tpcds/hive/unpartitioned/q36.plan.txt | 22 - .../tpcds/hive/unpartitioned/q37.plan.txt | 21 - .../tpcds/hive/unpartitioned/q38.plan.txt | 54 - .../tpcds/hive/unpartitioned/q39.plan.txt | 45 - .../tpcds/hive/unpartitioned/q40.plan.txt | 26 - .../tpcds/hive/unpartitioned/q41.plan.txt | 13 - .../tpcds/hive/unpartitioned/q42.plan.txt | 16 - .../tpcds/hive/unpartitioned/q43.plan.txt | 20 - .../tpcds/hive/unpartitioned/q44.plan.txt | 48 - .../tpcds/hive/unpartitioned/q45.plan.txt | 31 - .../tpcds/hive/unpartitioned/q46.plan.txt | 35 - .../tpcds/hive/unpartitioned/q47.plan.txt | 68 - .../tpcds/hive/unpartitioned/q48.plan.txt | 22 - .../tpcds/hive/unpartitioned/q49.plan.txt | 64 - .../tpcds/hive/unpartitioned/q50.plan.txt | 26 - .../tpcds/hive/unpartitioned/q51.plan.txt | 25 - .../tpcds/hive/unpartitioned/q52.plan.txt | 16 - .../tpcds/hive/unpartitioned/q53.plan.txt | 22 - .../tpcds/hive/unpartitioned/q54.plan.txt | 69 - .../tpcds/hive/unpartitioned/q55.plan.txt | 16 - .../tpcds/hive/unpartitioned/q56.plan.txt | 90 - .../tpcds/hive/unpartitioned/q57.plan.txt | 68 - .../tpcds/hive/unpartitioned/q58.plan.txt | 98 - .../tpcds/hive/unpartitioned/q59.plan.txt | 54 - .../tpcds/hive/unpartitioned/q60.plan.txt | 88 - .../tpcds/hive/unpartitioned/q61.plan.txt | 61 - .../tpcds/hive/unpartitioned/q62.plan.txt | 24 - .../tpcds/hive/unpartitioned/q63.plan.txt | 22 - .../tpcds/hive/unpartitioned/q64.plan.txt | 199 -- .../tpcds/hive/unpartitioned/q65.plan.txt | 37 - .../tpcds/hive/unpartitioned/q66.plan.txt | 58 - .../tpcds/hive/unpartitioned/q67.plan.txt | 24 - .../tpcds/hive/unpartitioned/q68.plan.txt | 36 - .../tpcds/hive/unpartitioned/q69.plan.txt | 55 - .../tpcds/hive/unpartitioned/q70.plan.txt | 36 - .../tpcds/hive/unpartitioned/q71.plan.txt | 34 - .../tpcds/hive/unpartitioned/q72.plan.txt | 51 - .../tpcds/hive/unpartitioned/q73.plan.txt | 25 - .../tpcds/hive/unpartitioned/q74.plan.txt | 68 - .../tpcds/hive/unpartitioned/q75.plan.txt | 120 - .../tpcds/hive/unpartitioned/q76.plan.txt | 43 - .../tpcds/hive/unpartitioned/q77.plan.txt | 88 - .../tpcds/hive/unpartitioned/q78.plan.txt | 51 - .../tpcds/hive/unpartitioned/q79.plan.txt | 24 - .../tpcds/hive/unpartitioned/q80.plan.txt | 91 - .../tpcds/hive/unpartitioned/q81.plan.txt | 53 - .../tpcds/hive/unpartitioned/q82.plan.txt | 21 - .../tpcds/hive/unpartitioned/q83.plan.txt | 105 - .../tpcds/hive/unpartitioned/q84.plan.txt | 27 - .../tpcds/hive/unpartitioned/q85.plan.txt | 42 - .../tpcds/hive/unpartitioned/q86.plan.txt | 18 - .../tpcds/hive/unpartitioned/q87.plan.txt | 54 - .../tpcds/hive/unpartitioned/q88.plan.txt | 151 - .../tpcds/hive/unpartitioned/q89.plan.txt | 22 - .../tpcds/hive/unpartitioned/q90.plan.txt | 37 - .../tpcds/hive/unpartitioned/q91.plan.txt | 36 - .../tpcds/hive/unpartitioned/q92.plan.txt | 32 - .../tpcds/hive/unpartitioned/q93.plan.txt | 18 - .../tpcds/hive/unpartitioned/q94.plan.txt | 35 - .../tpcds/hive/unpartitioned/q95.plan.txt | 49 - .../tpcds/hive/unpartitioned/q96.plan.txt | 18 - .../tpcds/hive/unpartitioned/q97.plan.txt | 25 - .../tpcds/hive/unpartitioned/q98.plan.txt | 19 - .../tpcds/hive/unpartitioned/q99.plan.txt | 24 - .../iceberg/orc/partitioned/q01.plan.txt | 44 - .../iceberg/orc/partitioned/q02.plan.txt | 45 - .../iceberg/orc/partitioned/q03.plan.txt | 16 - .../iceberg/orc/partitioned/q04.plan.txt | 102 - .../iceberg/orc/partitioned/q05.plan.txt | 64 - .../iceberg/orc/partitioned/q06.plan.txt | 51 - .../iceberg/orc/partitioned/q07.plan.txt | 24 - .../iceberg/orc/partitioned/q08.plan.txt | 38 - .../iceberg/orc/partitioned/q09.plan.txt | 122 - .../iceberg/orc/partitioned/q10.plan.txt | 55 - .../iceberg/orc/partitioned/q11.plan.txt | 68 - .../iceberg/orc/partitioned/q12.plan.txt | 19 - .../iceberg/orc/partitioned/q13.plan.txt | 28 - .../iceberg/orc/partitioned/q14.plan.txt | 277 -- .../iceberg/orc/partitioned/q15.plan.txt | 23 - .../iceberg/orc/partitioned/q16.plan.txt | 35 - .../iceberg/orc/partitioned/q17.plan.txt | 41 - .../iceberg/orc/partitioned/q18.plan.txt | 38 - .../iceberg/orc/partitioned/q19.plan.txt | 32 - .../iceberg/orc/partitioned/q20.plan.txt | 18 - .../iceberg/orc/partitioned/q21.plan.txt | 20 - .../iceberg/orc/partitioned/q22.plan.txt | 18 - .../iceberg/orc/partitioned/q23.plan.txt | 123 - .../iceberg/orc/partitioned/q24.plan.txt | 78 - .../iceberg/orc/partitioned/q25.plan.txt | 41 - .../iceberg/orc/partitioned/q26.plan.txt | 24 - .../iceberg/orc/partitioned/q27.plan.txt | 27 - .../iceberg/orc/partitioned/q28.plan.txt | 47 - .../iceberg/orc/partitioned/q29.plan.txt | 41 - .../iceberg/orc/partitioned/q30.plan.txt | 55 - .../iceberg/orc/partitioned/q31.plan.txt | 103 - .../iceberg/orc/partitioned/q32.plan.txt | 32 - .../iceberg/orc/partitioned/q33.plan.txt | 88 - .../iceberg/orc/partitioned/q34.plan.txt | 25 - .../iceberg/orc/partitioned/q35.plan.txt | 54 - .../iceberg/orc/partitioned/q36.plan.txt | 22 - .../iceberg/orc/partitioned/q37.plan.txt | 21 - .../iceberg/orc/partitioned/q38.plan.txt | 54 - .../iceberg/orc/partitioned/q39.plan.txt | 45 - .../iceberg/orc/partitioned/q40.plan.txt | 26 - .../iceberg/orc/partitioned/q41.plan.txt | 13 - .../iceberg/orc/partitioned/q42.plan.txt | 16 - .../iceberg/orc/partitioned/q43.plan.txt | 20 - .../iceberg/orc/partitioned/q44.plan.txt | 48 - .../iceberg/orc/partitioned/q45.plan.txt | 31 - .../iceberg/orc/partitioned/q46.plan.txt | 35 - .../iceberg/orc/partitioned/q47.plan.txt | 68 - .../iceberg/orc/partitioned/q48.plan.txt | 24 - .../iceberg/orc/partitioned/q49.plan.txt | 64 - .../iceberg/orc/partitioned/q50.plan.txt | 26 - .../iceberg/orc/partitioned/q51.plan.txt | 25 - .../iceberg/orc/partitioned/q52.plan.txt | 16 - .../iceberg/orc/partitioned/q53.plan.txt | 22 - .../iceberg/orc/partitioned/q54.plan.txt | 69 - .../iceberg/orc/partitioned/q55.plan.txt | 16 - .../iceberg/orc/partitioned/q56.plan.txt | 88 - .../iceberg/orc/partitioned/q57.plan.txt | 68 - .../iceberg/orc/partitioned/q58.plan.txt | 99 - .../iceberg/orc/partitioned/q59.plan.txt | 54 - .../iceberg/orc/partitioned/q60.plan.txt | 85 - .../iceberg/orc/partitioned/q61.plan.txt | 61 - .../iceberg/orc/partitioned/q62.plan.txt | 24 - .../iceberg/orc/partitioned/q63.plan.txt | 22 - .../iceberg/orc/partitioned/q64.plan.txt | 201 -- .../iceberg/orc/partitioned/q65.plan.txt | 37 - .../iceberg/orc/partitioned/q66.plan.txt | 58 - .../iceberg/orc/partitioned/q67.plan.txt | 24 - .../iceberg/orc/partitioned/q68.plan.txt | 36 - .../iceberg/orc/partitioned/q69.plan.txt | 53 - .../iceberg/orc/partitioned/q70.plan.txt | 36 - .../iceberg/orc/partitioned/q71.plan.txt | 34 - .../iceberg/orc/partitioned/q72.plan.txt | 51 - .../iceberg/orc/partitioned/q73.plan.txt | 25 - .../iceberg/orc/partitioned/q74.plan.txt | 68 - .../iceberg/orc/partitioned/q75.plan.txt | 120 - .../iceberg/orc/partitioned/q76.plan.txt | 44 - .../iceberg/orc/partitioned/q77.plan.txt | 88 - .../iceberg/orc/partitioned/q78.plan.txt | 51 - .../iceberg/orc/partitioned/q79.plan.txt | 24 - .../iceberg/orc/partitioned/q80.plan.txt | 91 - .../iceberg/orc/partitioned/q81.plan.txt | 53 - .../iceberg/orc/partitioned/q82.plan.txt | 21 - .../iceberg/orc/partitioned/q83.plan.txt | 106 - .../iceberg/orc/partitioned/q84.plan.txt | 27 - .../iceberg/orc/partitioned/q85.plan.txt | 42 - .../iceberg/orc/partitioned/q86.plan.txt | 18 - .../iceberg/orc/partitioned/q87.plan.txt | 54 - .../iceberg/orc/partitioned/q88.plan.txt | 151 - .../iceberg/orc/partitioned/q89.plan.txt | 22 - .../iceberg/orc/partitioned/q90.plan.txt | 37 - .../iceberg/orc/partitioned/q91.plan.txt | 37 - .../iceberg/orc/partitioned/q92.plan.txt | 32 - .../iceberg/orc/partitioned/q93.plan.txt | 18 - .../iceberg/orc/partitioned/q94.plan.txt | 35 - .../iceberg/orc/partitioned/q95.plan.txt | 49 - .../iceberg/orc/partitioned/q96.plan.txt | 18 - .../iceberg/orc/partitioned/q97.plan.txt | 25 - .../iceberg/orc/partitioned/q98.plan.txt | 19 - .../iceberg/orc/partitioned/q99.plan.txt | 24 - .../iceberg/orc/unpartitioned/q01.plan.txt | 44 - .../iceberg/orc/unpartitioned/q02.plan.txt | 45 - .../iceberg/orc/unpartitioned/q03.plan.txt | 16 - .../iceberg/orc/unpartitioned/q04.plan.txt | 102 - .../iceberg/orc/unpartitioned/q05.plan.txt | 64 - .../iceberg/orc/unpartitioned/q06.plan.txt | 51 - .../iceberg/orc/unpartitioned/q07.plan.txt | 24 - .../iceberg/orc/unpartitioned/q08.plan.txt | 38 - .../iceberg/orc/unpartitioned/q09.plan.txt | 122 - .../iceberg/orc/unpartitioned/q10.plan.txt | 55 - .../iceberg/orc/unpartitioned/q11.plan.txt | 68 - .../iceberg/orc/unpartitioned/q12.plan.txt | 19 - .../iceberg/orc/unpartitioned/q13.plan.txt | 28 - .../iceberg/orc/unpartitioned/q14.plan.txt | 277 -- .../iceberg/orc/unpartitioned/q15.plan.txt | 23 - .../iceberg/orc/unpartitioned/q16.plan.txt | 35 - .../iceberg/orc/unpartitioned/q17.plan.txt | 41 - .../iceberg/orc/unpartitioned/q18.plan.txt | 38 - .../iceberg/orc/unpartitioned/q19.plan.txt | 32 - .../iceberg/orc/unpartitioned/q20.plan.txt | 18 - .../iceberg/orc/unpartitioned/q21.plan.txt | 20 - .../iceberg/orc/unpartitioned/q22.plan.txt | 18 - .../iceberg/orc/unpartitioned/q23.plan.txt | 123 - .../iceberg/orc/unpartitioned/q24.plan.txt | 78 - .../iceberg/orc/unpartitioned/q25.plan.txt | 41 - .../iceberg/orc/unpartitioned/q26.plan.txt | 24 - .../iceberg/orc/unpartitioned/q27.plan.txt | 27 - .../iceberg/orc/unpartitioned/q28.plan.txt | 47 - .../iceberg/orc/unpartitioned/q29.plan.txt | 41 - .../iceberg/orc/unpartitioned/q30.plan.txt | 55 - .../iceberg/orc/unpartitioned/q31.plan.txt | 103 - .../iceberg/orc/unpartitioned/q32.plan.txt | 32 - .../iceberg/orc/unpartitioned/q33.plan.txt | 88 - .../iceberg/orc/unpartitioned/q34.plan.txt | 25 - .../iceberg/orc/unpartitioned/q35.plan.txt | 54 - .../iceberg/orc/unpartitioned/q36.plan.txt | 22 - .../iceberg/orc/unpartitioned/q37.plan.txt | 21 - .../iceberg/orc/unpartitioned/q38.plan.txt | 54 - .../iceberg/orc/unpartitioned/q39.plan.txt | 45 - .../iceberg/orc/unpartitioned/q40.plan.txt | 26 - .../iceberg/orc/unpartitioned/q41.plan.txt | 13 - .../iceberg/orc/unpartitioned/q42.plan.txt | 16 - .../iceberg/orc/unpartitioned/q43.plan.txt | 20 - .../iceberg/orc/unpartitioned/q44.plan.txt | 48 - .../iceberg/orc/unpartitioned/q45.plan.txt | 31 - .../iceberg/orc/unpartitioned/q46.plan.txt | 35 - .../iceberg/orc/unpartitioned/q47.plan.txt | 68 - .../iceberg/orc/unpartitioned/q48.plan.txt | 24 - .../iceberg/orc/unpartitioned/q49.plan.txt | 64 - .../iceberg/orc/unpartitioned/q50.plan.txt | 26 - .../iceberg/orc/unpartitioned/q51.plan.txt | 25 - .../iceberg/orc/unpartitioned/q52.plan.txt | 16 - .../iceberg/orc/unpartitioned/q53.plan.txt | 22 - .../iceberg/orc/unpartitioned/q54.plan.txt | 69 - .../iceberg/orc/unpartitioned/q55.plan.txt | 16 - .../iceberg/orc/unpartitioned/q56.plan.txt | 88 - .../iceberg/orc/unpartitioned/q57.plan.txt | 68 - .../iceberg/orc/unpartitioned/q58.plan.txt | 99 - .../iceberg/orc/unpartitioned/q59.plan.txt | 54 - .../iceberg/orc/unpartitioned/q60.plan.txt | 85 - .../iceberg/orc/unpartitioned/q61.plan.txt | 61 - .../iceberg/orc/unpartitioned/q62.plan.txt | 24 - .../iceberg/orc/unpartitioned/q63.plan.txt | 22 - .../iceberg/orc/unpartitioned/q64.plan.txt | 201 -- .../iceberg/orc/unpartitioned/q65.plan.txt | 37 - .../iceberg/orc/unpartitioned/q66.plan.txt | 58 - .../iceberg/orc/unpartitioned/q67.plan.txt | 24 - .../iceberg/orc/unpartitioned/q68.plan.txt | 36 - .../iceberg/orc/unpartitioned/q69.plan.txt | 53 - .../iceberg/orc/unpartitioned/q70.plan.txt | 36 - .../iceberg/orc/unpartitioned/q71.plan.txt | 34 - .../iceberg/orc/unpartitioned/q72.plan.txt | 51 - .../iceberg/orc/unpartitioned/q73.plan.txt | 25 - .../iceberg/orc/unpartitioned/q74.plan.txt | 68 - .../iceberg/orc/unpartitioned/q75.plan.txt | 120 - .../iceberg/orc/unpartitioned/q76.plan.txt | 44 - .../iceberg/orc/unpartitioned/q77.plan.txt | 88 - .../iceberg/orc/unpartitioned/q78.plan.txt | 51 - .../iceberg/orc/unpartitioned/q79.plan.txt | 24 - .../iceberg/orc/unpartitioned/q80.plan.txt | 91 - .../iceberg/orc/unpartitioned/q81.plan.txt | 53 - .../iceberg/orc/unpartitioned/q82.plan.txt | 21 - .../iceberg/orc/unpartitioned/q83.plan.txt | 106 - .../iceberg/orc/unpartitioned/q84.plan.txt | 27 - .../iceberg/orc/unpartitioned/q85.plan.txt | 42 - .../iceberg/orc/unpartitioned/q86.plan.txt | 18 - .../iceberg/orc/unpartitioned/q87.plan.txt | 54 - .../iceberg/orc/unpartitioned/q88.plan.txt | 151 - .../iceberg/orc/unpartitioned/q89.plan.txt | 22 - .../iceberg/orc/unpartitioned/q90.plan.txt | 37 - .../iceberg/orc/unpartitioned/q91.plan.txt | 37 - .../iceberg/orc/unpartitioned/q92.plan.txt | 32 - .../iceberg/orc/unpartitioned/q93.plan.txt | 18 - .../iceberg/orc/unpartitioned/q94.plan.txt | 35 - .../iceberg/orc/unpartitioned/q95.plan.txt | 49 - .../iceberg/orc/unpartitioned/q96.plan.txt | 18 - .../iceberg/orc/unpartitioned/q97.plan.txt | 25 - .../iceberg/orc/unpartitioned/q98.plan.txt | 19 - .../iceberg/orc/unpartitioned/q99.plan.txt | 24 - .../iceberg/parquet/partitioned/q01.plan.txt | 44 - .../iceberg/parquet/partitioned/q02.plan.txt | 45 - .../iceberg/parquet/partitioned/q03.plan.txt | 16 - .../iceberg/parquet/partitioned/q04.plan.txt | 102 - .../iceberg/parquet/partitioned/q05.plan.txt | 64 - .../iceberg/parquet/partitioned/q06.plan.txt | 51 - .../iceberg/parquet/partitioned/q07.plan.txt | 24 - .../iceberg/parquet/partitioned/q08.plan.txt | 38 - .../iceberg/parquet/partitioned/q09.plan.txt | 122 - .../iceberg/parquet/partitioned/q10.plan.txt | 55 - .../iceberg/parquet/partitioned/q11.plan.txt | 68 - .../iceberg/parquet/partitioned/q12.plan.txt | 18 - .../iceberg/parquet/partitioned/q13.plan.txt | 26 - .../iceberg/parquet/partitioned/q14.plan.txt | 277 -- .../iceberg/parquet/partitioned/q15.plan.txt | 23 - .../iceberg/parquet/partitioned/q16.plan.txt | 35 - .../iceberg/parquet/partitioned/q17.plan.txt | 41 - .../iceberg/parquet/partitioned/q18.plan.txt | 37 - .../iceberg/parquet/partitioned/q19.plan.txt | 32 - .../iceberg/parquet/partitioned/q20.plan.txt | 18 - .../iceberg/parquet/partitioned/q21.plan.txt | 20 - .../iceberg/parquet/partitioned/q22.plan.txt | 18 - .../iceberg/parquet/partitioned/q23.plan.txt | 123 - .../iceberg/parquet/partitioned/q24.plan.txt | 77 - .../iceberg/parquet/partitioned/q25.plan.txt | 41 - .../iceberg/parquet/partitioned/q26.plan.txt | 24 - .../iceberg/parquet/partitioned/q27.plan.txt | 25 - .../iceberg/parquet/partitioned/q28.plan.txt | 47 - .../iceberg/parquet/partitioned/q29.plan.txt | 41 - .../iceberg/parquet/partitioned/q30.plan.txt | 53 - .../iceberg/parquet/partitioned/q31.plan.txt | 103 - .../iceberg/parquet/partitioned/q32.plan.txt | 32 - .../iceberg/parquet/partitioned/q33.plan.txt | 88 - .../iceberg/parquet/partitioned/q34.plan.txt | 25 - .../iceberg/parquet/partitioned/q35.plan.txt | 53 - .../iceberg/parquet/partitioned/q36.plan.txt | 22 - .../iceberg/parquet/partitioned/q37.plan.txt | 21 - .../iceberg/parquet/partitioned/q38.plan.txt | 54 - .../iceberg/parquet/partitioned/q39.plan.txt | 45 - .../iceberg/parquet/partitioned/q40.plan.txt | 26 - .../iceberg/parquet/partitioned/q41.plan.txt | 13 - .../iceberg/parquet/partitioned/q42.plan.txt | 16 - .../iceberg/parquet/partitioned/q43.plan.txt | 20 - .../iceberg/parquet/partitioned/q44.plan.txt | 48 - .../iceberg/parquet/partitioned/q45.plan.txt | 31 - .../iceberg/parquet/partitioned/q46.plan.txt | 35 - .../iceberg/parquet/partitioned/q47.plan.txt | 68 - .../iceberg/parquet/partitioned/q48.plan.txt | 22 - .../iceberg/parquet/partitioned/q49.plan.txt | 64 - .../iceberg/parquet/partitioned/q50.plan.txt | 26 - .../iceberg/parquet/partitioned/q51.plan.txt | 25 - .../iceberg/parquet/partitioned/q52.plan.txt | 16 - .../iceberg/parquet/partitioned/q53.plan.txt | 22 - .../iceberg/parquet/partitioned/q54.plan.txt | 69 - .../iceberg/parquet/partitioned/q55.plan.txt | 16 - .../iceberg/parquet/partitioned/q56.plan.txt | 90 - .../iceberg/parquet/partitioned/q57.plan.txt | 68 - .../iceberg/parquet/partitioned/q58.plan.txt | 97 - .../iceberg/parquet/partitioned/q59.plan.txt | 54 - .../iceberg/parquet/partitioned/q60.plan.txt | 88 - .../iceberg/parquet/partitioned/q61.plan.txt | 61 - .../iceberg/parquet/partitioned/q62.plan.txt | 24 - .../iceberg/parquet/partitioned/q63.plan.txt | 22 - .../iceberg/parquet/partitioned/q64.plan.txt | 199 -- .../iceberg/parquet/partitioned/q65.plan.txt | 37 - .../iceberg/parquet/partitioned/q66.plan.txt | 58 - .../iceberg/parquet/partitioned/q67.plan.txt | 24 - .../iceberg/parquet/partitioned/q68.plan.txt | 35 - .../iceberg/parquet/partitioned/q69.plan.txt | 55 - .../iceberg/parquet/partitioned/q70.plan.txt | 36 - .../iceberg/parquet/partitioned/q71.plan.txt | 34 - .../iceberg/parquet/partitioned/q72.plan.txt | 51 - .../iceberg/parquet/partitioned/q73.plan.txt | 25 - .../iceberg/parquet/partitioned/q74.plan.txt | 68 - .../iceberg/parquet/partitioned/q75.plan.txt | 120 - .../iceberg/parquet/partitioned/q76.plan.txt | 42 - .../iceberg/parquet/partitioned/q77.plan.txt | 88 - .../iceberg/parquet/partitioned/q78.plan.txt | 51 - .../iceberg/parquet/partitioned/q79.plan.txt | 24 - .../iceberg/parquet/partitioned/q80.plan.txt | 91 - .../iceberg/parquet/partitioned/q81.plan.txt | 53 - .../iceberg/parquet/partitioned/q82.plan.txt | 21 - .../iceberg/parquet/partitioned/q83.plan.txt | 105 - .../iceberg/parquet/partitioned/q84.plan.txt | 27 - .../iceberg/parquet/partitioned/q85.plan.txt | 41 - .../iceberg/parquet/partitioned/q86.plan.txt | 18 - .../iceberg/parquet/partitioned/q87.plan.txt | 54 - .../iceberg/parquet/partitioned/q88.plan.txt | 151 - .../iceberg/parquet/partitioned/q89.plan.txt | 22 - .../iceberg/parquet/partitioned/q90.plan.txt | 37 - .../iceberg/parquet/partitioned/q91.plan.txt | 36 - .../iceberg/parquet/partitioned/q92.plan.txt | 32 - .../iceberg/parquet/partitioned/q93.plan.txt | 18 - .../iceberg/parquet/partitioned/q94.plan.txt | 35 - .../iceberg/parquet/partitioned/q95.plan.txt | 49 - .../iceberg/parquet/partitioned/q96.plan.txt | 18 - .../iceberg/parquet/partitioned/q97.plan.txt | 25 - .../iceberg/parquet/partitioned/q98.plan.txt | 19 - .../iceberg/parquet/partitioned/q99.plan.txt | 24 - .../parquet/unpartitioned/q01.plan.txt | 44 - .../parquet/unpartitioned/q02.plan.txt | 45 - .../parquet/unpartitioned/q03.plan.txt | 16 - .../parquet/unpartitioned/q04.plan.txt | 102 - .../parquet/unpartitioned/q05.plan.txt | 64 - .../parquet/unpartitioned/q06.plan.txt | 51 - .../parquet/unpartitioned/q07.plan.txt | 24 - .../parquet/unpartitioned/q08.plan.txt | 38 - .../parquet/unpartitioned/q09.plan.txt | 122 - .../parquet/unpartitioned/q10.plan.txt | 55 - .../parquet/unpartitioned/q11.plan.txt | 68 - .../parquet/unpartitioned/q12.plan.txt | 18 - .../parquet/unpartitioned/q13.plan.txt | 26 - .../parquet/unpartitioned/q14.plan.txt | 277 -- .../parquet/unpartitioned/q15.plan.txt | 23 - .../parquet/unpartitioned/q16.plan.txt | 35 - .../parquet/unpartitioned/q17.plan.txt | 41 - .../parquet/unpartitioned/q18.plan.txt | 37 - .../parquet/unpartitioned/q19.plan.txt | 32 - .../parquet/unpartitioned/q20.plan.txt | 18 - .../parquet/unpartitioned/q21.plan.txt | 20 - .../parquet/unpartitioned/q22.plan.txt | 18 - .../parquet/unpartitioned/q23.plan.txt | 123 - .../parquet/unpartitioned/q24.plan.txt | 77 - .../parquet/unpartitioned/q25.plan.txt | 41 - .../parquet/unpartitioned/q26.plan.txt | 24 - .../parquet/unpartitioned/q27.plan.txt | 25 - .../parquet/unpartitioned/q28.plan.txt | 47 - .../parquet/unpartitioned/q29.plan.txt | 41 - .../parquet/unpartitioned/q30.plan.txt | 53 - .../parquet/unpartitioned/q31.plan.txt | 103 - .../parquet/unpartitioned/q32.plan.txt | 32 - .../parquet/unpartitioned/q33.plan.txt | 88 - .../parquet/unpartitioned/q34.plan.txt | 25 - .../parquet/unpartitioned/q35.plan.txt | 53 - .../parquet/unpartitioned/q36.plan.txt | 22 - .../parquet/unpartitioned/q37.plan.txt | 21 - .../parquet/unpartitioned/q38.plan.txt | 54 - .../parquet/unpartitioned/q39.plan.txt | 45 - .../parquet/unpartitioned/q40.plan.txt | 26 - .../parquet/unpartitioned/q41.plan.txt | 13 - .../parquet/unpartitioned/q42.plan.txt | 16 - .../parquet/unpartitioned/q43.plan.txt | 20 - .../parquet/unpartitioned/q44.plan.txt | 48 - .../parquet/unpartitioned/q45.plan.txt | 31 - .../parquet/unpartitioned/q46.plan.txt | 35 - .../parquet/unpartitioned/q47.plan.txt | 68 - .../parquet/unpartitioned/q48.plan.txt | 22 - .../parquet/unpartitioned/q49.plan.txt | 64 - .../parquet/unpartitioned/q50.plan.txt | 26 - .../parquet/unpartitioned/q51.plan.txt | 25 - .../parquet/unpartitioned/q52.plan.txt | 16 - .../parquet/unpartitioned/q53.plan.txt | 22 - .../parquet/unpartitioned/q54.plan.txt | 69 - .../parquet/unpartitioned/q55.plan.txt | 16 - .../parquet/unpartitioned/q56.plan.txt | 90 - .../parquet/unpartitioned/q57.plan.txt | 68 - .../parquet/unpartitioned/q58.plan.txt | 97 - .../parquet/unpartitioned/q59.plan.txt | 54 - .../parquet/unpartitioned/q60.plan.txt | 88 - .../parquet/unpartitioned/q61.plan.txt | 61 - .../parquet/unpartitioned/q62.plan.txt | 24 - .../parquet/unpartitioned/q63.plan.txt | 22 - .../parquet/unpartitioned/q64.plan.txt | 199 -- .../parquet/unpartitioned/q65.plan.txt | 37 - .../parquet/unpartitioned/q66.plan.txt | 58 - .../parquet/unpartitioned/q67.plan.txt | 24 - .../parquet/unpartitioned/q68.plan.txt | 35 - .../parquet/unpartitioned/q69.plan.txt | 55 - .../parquet/unpartitioned/q70.plan.txt | 36 - .../parquet/unpartitioned/q71.plan.txt | 34 - .../parquet/unpartitioned/q72.plan.txt | 51 - .../parquet/unpartitioned/q73.plan.txt | 25 - .../parquet/unpartitioned/q74.plan.txt | 68 - .../parquet/unpartitioned/q75.plan.txt | 120 - .../parquet/unpartitioned/q76.plan.txt | 42 - .../parquet/unpartitioned/q77.plan.txt | 88 - .../parquet/unpartitioned/q78.plan.txt | 51 - .../parquet/unpartitioned/q79.plan.txt | 24 - .../parquet/unpartitioned/q80.plan.txt | 91 - .../parquet/unpartitioned/q81.plan.txt | 53 - .../parquet/unpartitioned/q82.plan.txt | 21 - .../parquet/unpartitioned/q83.plan.txt | 105 - .../parquet/unpartitioned/q84.plan.txt | 27 - .../parquet/unpartitioned/q85.plan.txt | 41 - .../parquet/unpartitioned/q86.plan.txt | 18 - .../parquet/unpartitioned/q87.plan.txt | 54 - .../parquet/unpartitioned/q88.plan.txt | 151 - .../parquet/unpartitioned/q89.plan.txt | 22 - .../parquet/unpartitioned/q90.plan.txt | 37 - .../parquet/unpartitioned/q91.plan.txt | 36 - .../parquet/unpartitioned/q92.plan.txt | 32 - .../parquet/unpartitioned/q93.plan.txt | 18 - .../parquet/unpartitioned/q94.plan.txt | 35 - .../parquet/unpartitioned/q95.plan.txt | 49 - .../parquet/unpartitioned/q96.plan.txt | 18 - .../parquet/unpartitioned/q97.plan.txt | 25 - .../parquet/unpartitioned/q98.plan.txt | 19 - .../parquet/unpartitioned/q99.plan.txt | 24 - .../parquet/unpartitioned/q01.plan.txt | 42 - .../parquet/unpartitioned/q02.plan.txt | 51 - .../parquet/unpartitioned/q03.plan.txt | 16 - .../parquet/unpartitioned/q04.plan.txt | 113 - .../parquet/unpartitioned/q05.plan.txt | 64 - .../parquet/unpartitioned/q06.plan.txt | 51 - .../parquet/unpartitioned/q07.plan.txt | 24 - .../parquet/unpartitioned/q08.plan.txt | 38 - .../parquet/unpartitioned/q09.plan.txt | 122 - .../parquet/unpartitioned/q10.plan.txt | 51 - .../parquet/unpartitioned/q11.plan.txt | 75 - .../parquet/unpartitioned/q12.plan.txt | 18 - .../parquet/unpartitioned/q13.plan.txt | 27 - .../parquet/unpartitioned/q14.plan.txt | 286 -- .../parquet/unpartitioned/q15.plan.txt | 23 - .../parquet/unpartitioned/q16.plan.txt | 32 - .../parquet/unpartitioned/q17.plan.txt | 40 - .../parquet/unpartitioned/q18.plan.txt | 35 - .../parquet/unpartitioned/q19.plan.txt | 31 - .../parquet/unpartitioned/q20.plan.txt | 18 - .../parquet/unpartitioned/q21.plan.txt | 20 - .../parquet/unpartitioned/q22.plan.txt | 16 - .../parquet/unpartitioned/q23.plan.txt | 125 - .../parquet/unpartitioned/q24.plan.txt | 78 - .../parquet/unpartitioned/q25.plan.txt | 40 - .../parquet/unpartitioned/q26.plan.txt | 24 - .../parquet/unpartitioned/q27.plan.txt | 24 - .../parquet/unpartitioned/q28.plan.txt | 47 - .../parquet/unpartitioned/q29.plan.txt | 40 - .../parquet/unpartitioned/q30.plan.txt | 51 - .../parquet/unpartitioned/q31.plan.txt | 111 - .../parquet/unpartitioned/q32.plan.txt | 31 - .../parquet/unpartitioned/q33.plan.txt | 91 - .../parquet/unpartitioned/q34.plan.txt | 26 - .../parquet/unpartitioned/q35.plan.txt | 51 - .../parquet/unpartitioned/q36.plan.txt | 22 - .../parquet/unpartitioned/q37.plan.txt | 22 - .../parquet/unpartitioned/q38.plan.txt | 54 - .../parquet/unpartitioned/q39.plan.txt | 45 - .../parquet/unpartitioned/q40.plan.txt | 26 - .../parquet/unpartitioned/q41.plan.txt | 16 - .../parquet/unpartitioned/q42.plan.txt | 16 - .../parquet/unpartitioned/q43.plan.txt | 20 - .../parquet/unpartitioned/q44.plan.txt | 48 - .../parquet/unpartitioned/q45.plan.txt | 31 - .../parquet/unpartitioned/q46.plan.txt | 34 - .../parquet/unpartitioned/q47.plan.txt | 68 - .../parquet/unpartitioned/q48.plan.txt | 23 - .../parquet/unpartitioned/q49.plan.txt | 61 - .../parquet/unpartitioned/q50.plan.txt | 26 - .../parquet/unpartitioned/q51.plan.txt | 29 - .../parquet/unpartitioned/q52.plan.txt | 16 - .../parquet/unpartitioned/q53.plan.txt | 22 - .../parquet/unpartitioned/q54.plan.txt | 71 - .../parquet/unpartitioned/q55.plan.txt | 16 - .../parquet/unpartitioned/q56.plan.txt | 91 - .../parquet/unpartitioned/q57.plan.txt | 68 - .../parquet/unpartitioned/q58.plan.txt | 93 - .../parquet/unpartitioned/q59.plan.txt | 54 - .../parquet/unpartitioned/q60.plan.txt | 91 - .../parquet/unpartitioned/q61.plan.txt | 63 - .../parquet/unpartitioned/q62.plan.txt | 24 - .../parquet/unpartitioned/q63.plan.txt | 22 - .../parquet/unpartitioned/q64.plan.txt | 193 -- .../parquet/unpartitioned/q65.plan.txt | 38 - .../parquet/unpartitioned/q66.plan.txt | 58 - .../parquet/unpartitioned/q67.plan.txt | 22 - .../parquet/unpartitioned/q68.plan.txt | 34 - .../parquet/unpartitioned/q69.plan.txt | 51 - .../parquet/unpartitioned/q70.plan.txt | 36 - .../parquet/unpartitioned/q71.plan.txt | 34 - .../parquet/unpartitioned/q72.plan.txt | 51 - .../parquet/unpartitioned/q73.plan.txt | 26 - .../parquet/unpartitioned/q74.plan.txt | 75 - .../parquet/unpartitioned/q75.plan.txt | 114 - .../parquet/unpartitioned/q76.plan.txt | 41 - .../parquet/unpartitioned/q77.plan.txt | 88 - .../parquet/unpartitioned/q78.plan.txt | 53 - .../parquet/unpartitioned/q79.plan.txt | 25 - .../parquet/unpartitioned/q80.plan.txt | 91 - .../parquet/unpartitioned/q81.plan.txt | 52 - .../parquet/unpartitioned/q82.plan.txt | 22 - .../parquet/unpartitioned/q83.plan.txt | 99 - .../parquet/unpartitioned/q84.plan.txt | 27 - .../parquet/unpartitioned/q85.plan.txt | 40 - .../parquet/unpartitioned/q86.plan.txt | 18 - .../parquet/unpartitioned/q87.plan.txt | 54 - .../parquet/unpartitioned/q88.plan.txt | 151 - .../parquet/unpartitioned/q89.plan.txt | 22 - .../parquet/unpartitioned/q90.plan.txt | 37 - .../parquet/unpartitioned/q91.plan.txt | 36 - .../parquet/unpartitioned/q92.plan.txt | 31 - .../parquet/unpartitioned/q93.plan.txt | 18 - .../parquet/unpartitioned/q94.plan.txt | 32 - .../parquet/unpartitioned/q95.plan.txt | 48 - .../parquet/unpartitioned/q96.plan.txt | 18 - .../parquet/unpartitioned/q97.plan.txt | 25 - .../parquet/unpartitioned/q98.plan.txt | 19 - .../parquet/unpartitioned/q99.plan.txt | 24 - .../trino/tpch/hive/partitioned/q01.plan.txt | 8 - .../trino/tpch/hive/partitioned/q02.plan.txt | 52 - .../trino/tpch/hive/partitioned/q03.plan.txt | 18 - .../trino/tpch/hive/partitioned/q04.plan.txt | 17 - .../trino/tpch/hive/partitioned/q05.plan.txt | 34 - .../trino/tpch/hive/partitioned/q06.plan.txt | 5 - .../trino/tpch/hive/partitioned/q07.plan.txt | 34 - .../trino/tpch/hive/partitioned/q08.plan.txt | 44 - .../trino/tpch/hive/partitioned/q09.plan.txt | 34 - .../trino/tpch/hive/partitioned/q10.plan.txt | 23 - .../trino/tpch/hive/partitioned/q11.plan.txt | 36 - .../trino/tpch/hive/partitioned/q12.plan.txt | 14 - .../trino/tpch/hive/partitioned/q13.plan.txt | 17 - .../trino/tpch/hive/partitioned/q14.plan.txt | 11 - .../trino/tpch/hive/partitioned/q15.plan.txt | 25 - .../trino/tpch/hive/partitioned/q16.plan.txt | 22 - .../trino/tpch/hive/partitioned/q17.plan.txt | 23 - .../trino/tpch/hive/partitioned/q18.plan.txt | 27 - .../trino/tpch/hive/partitioned/q19.plan.txt | 11 - .../trino/tpch/hive/partitioned/q20.plan.txt | 39 - .../trino/tpch/hive/partitioned/q21.plan.txt | 35 - .../trino/tpch/hive/partitioned/q22.plan.txt | 24 - .../tpch/hive/unpartitioned/q01.plan.txt | 8 - .../tpch/hive/unpartitioned/q02.plan.txt | 52 - .../tpch/hive/unpartitioned/q03.plan.txt | 18 - .../tpch/hive/unpartitioned/q04.plan.txt | 17 - .../tpch/hive/unpartitioned/q05.plan.txt | 34 - .../tpch/hive/unpartitioned/q06.plan.txt | 5 - .../tpch/hive/unpartitioned/q07.plan.txt | 34 - .../tpch/hive/unpartitioned/q08.plan.txt | 44 - .../tpch/hive/unpartitioned/q09.plan.txt | 34 - .../tpch/hive/unpartitioned/q10.plan.txt | 22 - .../tpch/hive/unpartitioned/q11.plan.txt | 36 - .../tpch/hive/unpartitioned/q12.plan.txt | 14 - .../tpch/hive/unpartitioned/q13.plan.txt | 17 - .../tpch/hive/unpartitioned/q14.plan.txt | 11 - .../tpch/hive/unpartitioned/q15.plan.txt | 24 - .../tpch/hive/unpartitioned/q16.plan.txt | 22 - .../tpch/hive/unpartitioned/q17.plan.txt | 24 - .../tpch/hive/unpartitioned/q18.plan.txt | 26 - .../tpch/hive/unpartitioned/q19.plan.txt | 11 - .../tpch/hive/unpartitioned/q20.plan.txt | 39 - .../tpch/hive/unpartitioned/q21.plan.txt | 37 - .../tpch/hive/unpartitioned/q22.plan.txt | 24 - .../tpch/iceberg/orc/partitioned/q01.plan.txt | 8 - .../tpch/iceberg/orc/partitioned/q02.plan.txt | 53 - .../tpch/iceberg/orc/partitioned/q03.plan.txt | 18 - .../tpch/iceberg/orc/partitioned/q04.plan.txt | 17 - .../tpch/iceberg/orc/partitioned/q05.plan.txt | 35 - .../tpch/iceberg/orc/partitioned/q06.plan.txt | 5 - .../tpch/iceberg/orc/partitioned/q07.plan.txt | 34 - .../tpch/iceberg/orc/partitioned/q08.plan.txt | 44 - .../tpch/iceberg/orc/partitioned/q09.plan.txt | 34 - .../tpch/iceberg/orc/partitioned/q10.plan.txt | 22 - .../tpch/iceberg/orc/partitioned/q11.plan.txt | 36 - .../tpch/iceberg/orc/partitioned/q12.plan.txt | 14 - .../tpch/iceberg/orc/partitioned/q13.plan.txt | 17 - .../tpch/iceberg/orc/partitioned/q14.plan.txt | 11 - .../tpch/iceberg/orc/partitioned/q15.plan.txt | 24 - .../tpch/iceberg/orc/partitioned/q16.plan.txt | 22 - .../tpch/iceberg/orc/partitioned/q17.plan.txt | 24 - .../tpch/iceberg/orc/partitioned/q18.plan.txt | 26 - .../tpch/iceberg/orc/partitioned/q19.plan.txt | 11 - .../tpch/iceberg/orc/partitioned/q20.plan.txt | 39 - .../tpch/iceberg/orc/partitioned/q21.plan.txt | 35 - .../tpch/iceberg/orc/partitioned/q22.plan.txt | 24 - .../iceberg/orc/unpartitioned/q01.plan.txt | 8 - .../iceberg/orc/unpartitioned/q02.plan.txt | 53 - .../iceberg/orc/unpartitioned/q03.plan.txt | 18 - .../iceberg/orc/unpartitioned/q04.plan.txt | 17 - .../iceberg/orc/unpartitioned/q05.plan.txt | 35 - .../iceberg/orc/unpartitioned/q06.plan.txt | 5 - .../iceberg/orc/unpartitioned/q07.plan.txt | 34 - .../iceberg/orc/unpartitioned/q08.plan.txt | 44 - .../iceberg/orc/unpartitioned/q09.plan.txt | 34 - .../iceberg/orc/unpartitioned/q10.plan.txt | 22 - .../iceberg/orc/unpartitioned/q11.plan.txt | 36 - .../iceberg/orc/unpartitioned/q12.plan.txt | 14 - .../iceberg/orc/unpartitioned/q13.plan.txt | 17 - .../iceberg/orc/unpartitioned/q14.plan.txt | 11 - .../iceberg/orc/unpartitioned/q15.plan.txt | 24 - .../iceberg/orc/unpartitioned/q16.plan.txt | 22 - .../iceberg/orc/unpartitioned/q17.plan.txt | 24 - .../iceberg/orc/unpartitioned/q18.plan.txt | 26 - .../iceberg/orc/unpartitioned/q19.plan.txt | 11 - .../iceberg/orc/unpartitioned/q20.plan.txt | 39 - .../iceberg/orc/unpartitioned/q21.plan.txt | 36 - .../iceberg/orc/unpartitioned/q22.plan.txt | 24 - .../iceberg/parquet/partitioned/q01.plan.txt | 8 - .../iceberg/parquet/partitioned/q02.plan.txt | 52 - .../iceberg/parquet/partitioned/q03.plan.txt | 18 - .../iceberg/parquet/partitioned/q04.plan.txt | 17 - .../iceberg/parquet/partitioned/q05.plan.txt | 35 - .../iceberg/parquet/partitioned/q06.plan.txt | 5 - .../iceberg/parquet/partitioned/q07.plan.txt | 34 - .../iceberg/parquet/partitioned/q08.plan.txt | 44 - .../iceberg/parquet/partitioned/q09.plan.txt | 34 - .../iceberg/parquet/partitioned/q10.plan.txt | 22 - .../iceberg/parquet/partitioned/q11.plan.txt | 36 - .../iceberg/parquet/partitioned/q12.plan.txt | 14 - .../iceberg/parquet/partitioned/q13.plan.txt | 17 - .../iceberg/parquet/partitioned/q14.plan.txt | 11 - .../iceberg/parquet/partitioned/q15.plan.txt | 24 - .../iceberg/parquet/partitioned/q16.plan.txt | 22 - .../iceberg/parquet/partitioned/q17.plan.txt | 24 - .../iceberg/parquet/partitioned/q18.plan.txt | 26 - .../iceberg/parquet/partitioned/q19.plan.txt | 11 - .../iceberg/parquet/partitioned/q20.plan.txt | 39 - .../iceberg/parquet/partitioned/q21.plan.txt | 37 - .../iceberg/parquet/partitioned/q22.plan.txt | 24 - .../parquet/unpartitioned/q01.plan.txt | 8 - .../parquet/unpartitioned/q02.plan.txt | 52 - .../parquet/unpartitioned/q03.plan.txt | 18 - .../parquet/unpartitioned/q04.plan.txt | 17 - .../parquet/unpartitioned/q05.plan.txt | 35 - .../parquet/unpartitioned/q06.plan.txt | 5 - .../parquet/unpartitioned/q07.plan.txt | 34 - .../parquet/unpartitioned/q08.plan.txt | 44 - .../parquet/unpartitioned/q09.plan.txt | 34 - .../parquet/unpartitioned/q10.plan.txt | 22 - .../parquet/unpartitioned/q11.plan.txt | 36 - .../parquet/unpartitioned/q12.plan.txt | 14 - .../parquet/unpartitioned/q13.plan.txt | 17 - .../parquet/unpartitioned/q14.plan.txt | 11 - .../parquet/unpartitioned/q15.plan.txt | 24 - .../parquet/unpartitioned/q16.plan.txt | 22 - .../parquet/unpartitioned/q17.plan.txt | 24 - .../parquet/unpartitioned/q18.plan.txt | 26 - .../parquet/unpartitioned/q19.plan.txt | 11 - .../parquet/unpartitioned/q20.plan.txt | 39 - .../parquet/unpartitioned/q21.plan.txt | 37 - .../parquet/unpartitioned/q22.plan.txt | 24 - 1305 files changed, 87855 deletions(-) delete mode 100644 testing/trino-tests/src/test/java/io/trino/sql/planner/BaseCostBasedPlanTest.java delete mode 100644 testing/trino-tests/src/test/java/io/trino/sql/planner/BaseHiveCostBasedPlanTest.java delete mode 100644 testing/trino-tests/src/test/java/io/trino/sql/planner/BaseIcebergCostBasedPlanTest.java delete mode 100644 testing/trino-tests/src/test/java/io/trino/sql/planner/HiveMetadataRecorder.java delete mode 100644 testing/trino-tests/src/test/java/io/trino/sql/planner/TestHivePartitionedTpcdsCostBasedPlan.java delete mode 100644 testing/trino-tests/src/test/java/io/trino/sql/planner/TestHivePartitionedTpchCostBasedPlan.java delete mode 100644 testing/trino-tests/src/test/java/io/trino/sql/planner/TestHiveTpcdsCostBasedPlan.java delete mode 100644 testing/trino-tests/src/test/java/io/trino/sql/planner/TestHiveTpchCostBasedPlan.java delete mode 100644 testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcPartitionedTpcdsCostBasedPlan.java delete mode 100644 testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcPartitionedTpchCostBasedPlan.java delete mode 100644 testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcTpcdsCostBasedPlan.java delete mode 100644 testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcTpchCostBasedPlan.java delete mode 100644 testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetPartitionedTpcdsCostBasedPlan.java delete mode 100644 testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetPartitionedTpchCostBasedPlan.java delete mode 100644 testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetTpcdsCostBasedPlan.java delete mode 100644 testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetTpchCostBasedPlan.java delete mode 100644 testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergSmallFilesParquetTpcdsCostBasedPlan.java delete mode 100644 testing/trino-tests/src/test/java/io/trino/sql/planner/UpdateExpectedPlans.java delete mode 100644 testing/trino-tests/src/test/resources/hive_metadata/README.md delete mode 100644 testing/trino-tests/src/test/resources/hive_metadata/partitioned_tpcds/tpcds_sf1000_orc_part.json.gz delete mode 100644 testing/trino-tests/src/test/resources/hive_metadata/partitioned_tpch/tpch_sf1000_orc_part.json.gz delete mode 100644 testing/trino-tests/src/test/resources/hive_metadata/unpartitioned_tpcds/tpcds_sf1000_orc.json.gz delete mode 100644 testing/trino-tests/src/test/resources/hive_metadata/unpartitioned_tpch/tpch_sf1000_orc.json.gz delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/call_center/metadata/00001-113d146c-8a15-44b2-9495-13fa19b96b3d.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/call_center/metadata/bc37e8c9-cfc9-4231-b46d-141e70e1c9bf-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/call_center/metadata/snap-651404939180126625-1-bc37e8c9-cfc9-4231-b46d-141e70e1c9bf.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/catalog_page/metadata/00001-a42f2ff8-db36-4b60-936e-b1af07bd5975.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/catalog_page/metadata/356eedf3-e609-4590-9b08-43815e502397-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/catalog_page/metadata/snap-6831945364967653617-1-356eedf3-e609-4590-9b08-43815e502397.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/catalog_returns/metadata/00001-a8d3b628-b0df-499f-afb4-1e9e085343dc.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/catalog_returns/metadata/ee868c3d-341a-4ecb-afdd-19ae7cddc60a-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/catalog_returns/metadata/snap-8404586660025874872-1-ee868c3d-341a-4ecb-afdd-19ae7cddc60a.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/catalog_sales/metadata/00001-931d0f79-8f43-4a73-b8f5-b7d9bac10357.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/catalog_sales/metadata/28606fe7-b3d7-4f87-b73c-facdbf6465bc-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/catalog_sales/metadata/snap-1121206068119349348-1-28606fe7-b3d7-4f87-b73c-facdbf6465bc.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/customer/metadata/00001-b24666ad-bd6d-42a0-af6b-fda9f2cf6770.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/customer/metadata/ca3999f3-8e7c-4dac-a89b-0605a4d91eda-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/customer/metadata/snap-7710055988899077289-1-ca3999f3-8e7c-4dac-a89b-0605a4d91eda.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/customer_address/metadata/00001-c0286f17-acf1-49bd-a385-7c8b25ac5acf.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/customer_address/metadata/d627e5de-60c8-4068-914b-5927879fcf7a-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/customer_address/metadata/snap-3058273466508965471-1-d627e5de-60c8-4068-914b-5927879fcf7a.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/customer_demographics/metadata/00001-1d11929e-1602-47f2-b054-d7244fa6b610.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/customer_demographics/metadata/c3780e6c-ef23-4ea1-a89d-4160c571eb7f-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/customer_demographics/metadata/snap-3205611945061422968-1-c3780e6c-ef23-4ea1-a89d-4160c571eb7f.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/date_dim/metadata/00001-76f3926d-36c5-4e84-b55e-c5c51a28b468.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/date_dim/metadata/bb63012b-2c05-49f9-8772-77377306deb3-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/date_dim/metadata/snap-6005541327418396024-1-bb63012b-2c05-49f9-8772-77377306deb3.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/household_demographics/metadata/00001-4078a0db-5994-4567-9305-7305e97a1165.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/household_demographics/metadata/f1e4d086-6a9a-4bfd-88b4-71bfaf544152-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/household_demographics/metadata/snap-109596220792630279-1-f1e4d086-6a9a-4bfd-88b4-71bfaf544152.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/income_band/metadata/00001-d5362f19-4d80-4918-9cb1-de3936f39de6.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/income_band/metadata/2e19af1d-dd90-4815-a241-1470565ff0b3-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/income_band/metadata/snap-8798826066288547642-1-2e19af1d-dd90-4815-a241-1470565ff0b3.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/inventory/metadata/00001-96bc8a29-c313-4d76-b3c9-e7bb2f504211.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/inventory/metadata/a1f2184b-1082-43b4-96a3-6dfc1534cbae-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/inventory/metadata/snap-6013065583635837403-1-a1f2184b-1082-43b4-96a3-6dfc1534cbae.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/item/metadata/00001-5f26e65e-50e0-4118-b53c-4fd16021435c.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/item/metadata/dc19ea20-4bcd-49b1-9914-f1e2162dc4c3-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/item/metadata/snap-8987387595833449359-1-dc19ea20-4bcd-49b1-9914-f1e2162dc4c3.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/promotion/metadata/00001-6b0621f1-3540-40d3-a1ee-37efd8f1a0fe.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/promotion/metadata/d80ecda9-3f82-41b1-a3fa-ec24faa845b1-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/promotion/metadata/snap-7512054532736550261-1-d80ecda9-3f82-41b1-a3fa-ec24faa845b1.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/reason/metadata/00001-0d9431bb-641b-4e5d-b07b-7ec5a00a5478.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/reason/metadata/7206760d-6ee0-46e9-8aaa-14c93a48bca1-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/reason/metadata/snap-371425011160356895-1-7206760d-6ee0-46e9-8aaa-14c93a48bca1.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/ship_mode/metadata/00001-4b657509-8976-4e78-8f05-b7a0b6c3b508.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/ship_mode/metadata/8219546d-26d5-434b-aabb-d17437382de4-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/ship_mode/metadata/snap-1674108510544737757-1-8219546d-26d5-434b-aabb-d17437382de4.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/store/metadata/00001-fa221042-759c-4136-8fb4-957722126c7f.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/store/metadata/3dfcd5d7-cef5-4a88-a153-88f349613b01-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/store/metadata/snap-8302422540923679471-1-3dfcd5d7-cef5-4a88-a153-88f349613b01.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/store_returns/metadata/00001-e58581d2-5484-4c5d-b4a0-ab8e80b0d970.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/store_returns/metadata/6742fcd1-8e83-4bea-921f-429e6c20c65b-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/store_returns/metadata/snap-229083097532627639-1-6742fcd1-8e83-4bea-921f-429e6c20c65b.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/store_sales/metadata/00001-bdcf1ddb-299d-4162-b289-3eb91f22a733.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/store_sales/metadata/fdb632fc-de03-4b66-bf86-ae708d762e14-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/store_sales/metadata/snap-5218874958641855037-1-fdb632fc-de03-4b66-bf86-ae708d762e14.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/time_dim/metadata/00001-c7cb0bb2-42f8-43ec-8380-e551bd1dac77.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/time_dim/metadata/bfd083d4-1117-471c-b68c-46c17b21cad6-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/time_dim/metadata/snap-8917187097339504486-1-bfd083d4-1117-471c-b68c-46c17b21cad6.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/warehouse/metadata/00001-df837cca-ef7e-4f80-a36a-2487795f98b3.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/warehouse/metadata/0815941f-2631-4f48-8c06-40256d90839d-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/warehouse/metadata/snap-464364136890993501-1-0815941f-2631-4f48-8c06-40256d90839d.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_page/metadata/00001-045bba9b-bff6-4c45-8f93-55b7557c5cd6.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_page/metadata/928675c3-c016-43d4-ae0d-971d4d141dd7-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_page/metadata/snap-2015819917735805161-1-928675c3-c016-43d4-ae0d-971d4d141dd7.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_returns/metadata/00001-8882a15e-6abc-4fac-af46-8702ccccebfa.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_returns/metadata/9fef99c4-de5b-411e-b587-288a6aa1bd5f-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_returns/metadata/snap-8685060902938679641-1-9fef99c4-de5b-411e-b587-288a6aa1bd5f.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_sales/metadata/00001-eda4bb5d-773c-44e3-8a26-19b42235f8cf.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_sales/metadata/6e069005-df27-4ca7-a174-f5b6d743aa4c-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_sales/metadata/snap-2885374810939635374-1-6e069005-df27-4ca7-a174-f5b6d743aa4c.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_site/metadata/00001-45722a5b-5fc9-4adb-b604-a6449ac112bf.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_site/metadata/fc902609-9edc-400d-8b7f-6e5ed1adcab8-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_site/metadata/snap-3940511604881380271-1-fc902609-9edc-400d-8b7f-6e5ed1adcab8.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/call_center/metadata/00001-abb63900-1a0b-44af-a643-d2e737ecb560.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/call_center/metadata/bbb81736-4d75-4b23-a088-91f01e2d9b47-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/call_center/metadata/snap-2890857111948924486-1-bbb81736-4d75-4b23-a088-91f01e2d9b47.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/catalog_page/metadata/00001-f59a4aad-164a-482e-b4fc-e7c4660ff071.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/catalog_page/metadata/37c343fe-ccc8-4f0f-9bf0-2926df207e9a-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/catalog_page/metadata/snap-6418044730637608616-1-37c343fe-ccc8-4f0f-9bf0-2926df207e9a.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/catalog_returns/metadata/00001-d836c438-553c-424f-96e3-449a8a2b0545.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/catalog_returns/metadata/b2efdd40-4c7a-42e8-a446-b1af8a7deb69-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/catalog_returns/metadata/snap-5289158630857119083-1-b2efdd40-4c7a-42e8-a446-b1af8a7deb69.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/catalog_sales/metadata/00001-f422fab3-6902-4596-b4ec-b6e767971497.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/catalog_sales/metadata/81f357df-ac7d-4eff-a9ac-5e348421f178-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/catalog_sales/metadata/snap-575568110763032177-1-81f357df-ac7d-4eff-a9ac-5e348421f178.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/customer/metadata/00001-911da009-cce6-4a5f-8077-60cbd89a48de.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/customer/metadata/9ab4a206-6242-44f2-abf0-07a749715635-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/customer/metadata/snap-8464312436443445418-1-9ab4a206-6242-44f2-abf0-07a749715635.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/customer_address/metadata/00001-07194215-05c3-457f-b01b-7021368ee7ca.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/customer_address/metadata/0c959e83-0739-49a9-b4d0-d8a4410d0de5-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/customer_address/metadata/snap-1456431036578754086-1-0c959e83-0739-49a9-b4d0-d8a4410d0de5.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/customer_demographics/metadata/00001-9d1005b9-6769-4372-84b3-a5de5fd7121c.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/customer_demographics/metadata/4ff3da13-bbed-4ae5-9bd1-cc8d8a549852-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/customer_demographics/metadata/snap-5691095370418586959-1-4ff3da13-bbed-4ae5-9bd1-cc8d8a549852.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/date_dim/metadata/00001-b7a307af-ba48-46d5-9ae5-f9989e7a9d57.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/date_dim/metadata/6cf907a4-67ef-40d4-98f2-e748e4a3d238-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/date_dim/metadata/snap-1832313220659256293-1-6cf907a4-67ef-40d4-98f2-e748e4a3d238.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/household_demographics/metadata/00001-758a199f-fe2d-49bc-9014-6a86af1b9184.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/household_demographics/metadata/ab7a9eea-51da-4570-a291-9f702051709b-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/household_demographics/metadata/snap-2849001755671445746-1-ab7a9eea-51da-4570-a291-9f702051709b.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/income_band/metadata/00001-cb9a6976-9c95-48be-8af8-ff5f752a5a88.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/income_band/metadata/8c4c7883-5fd9-49c5-9c1a-467fe5c49bfc-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/income_band/metadata/snap-1688263605598351106-1-8c4c7883-5fd9-49c5-9c1a-467fe5c49bfc.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/inventory/metadata/00001-d639aea2-c2ff-46f2-b893-d3755e29148c.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/inventory/metadata/e79e3684-dfee-4c0c-a7b6-194b67b916d4-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/inventory/metadata/snap-8748002454324012504-1-e79e3684-dfee-4c0c-a7b6-194b67b916d4.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/item/metadata/00001-2fa7f855-3d76-44ed-b705-fcb2462c017a.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/item/metadata/96cca44a-2f36-4ee6-b15e-90545a9914e9-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/item/metadata/snap-2465049471077187546-1-96cca44a-2f36-4ee6-b15e-90545a9914e9.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/promotion/metadata/00001-7d829a9f-720f-4653-8388-40ba51e1d407.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/promotion/metadata/c7174fa3-1fa4-4ed4-ac01-e3b763bd5107-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/promotion/metadata/snap-1093755410757306738-1-c7174fa3-1fa4-4ed4-ac01-e3b763bd5107.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/reason/metadata/00001-76e43a49-c8b9-4b35-837a-5728077c1415.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/reason/metadata/bf248139-c43f-47dd-8495-a81175bf12e6-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/reason/metadata/snap-4175047431543444715-1-bf248139-c43f-47dd-8495-a81175bf12e6.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/ship_mode/metadata/00001-a1c038d7-554a-44ff-9ece-6d0aa2125df8.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/ship_mode/metadata/ec64121d-c401-4590-9c64-80bad6030d6a-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/ship_mode/metadata/snap-8067287936697281152-1-ec64121d-c401-4590-9c64-80bad6030d6a.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/store/metadata/00001-1a770d6e-7455-4688-bf34-8b63ebd45185.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/store/metadata/3d2700cc-52a0-4e98-b68d-2bf6c97afaf0-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/store/metadata/snap-4385758742104041796-1-3d2700cc-52a0-4e98-b68d-2bf6c97afaf0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/store_returns/metadata/00001-da8175e6-71f7-4b79-9349-58f69f4fc435.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/store_returns/metadata/1f9a98c3-e090-46a5-b8c1-2b98b19afcfc-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/store_returns/metadata/snap-6678993594316835328-1-1f9a98c3-e090-46a5-b8c1-2b98b19afcfc.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/store_sales/metadata/00001-04ccf4be-73b8-494a-b361-ef0489640024.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/store_sales/metadata/99cef31b-41d6-468c-bb42-93affe6951ca-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/store_sales/metadata/snap-6981524120413809067-1-99cef31b-41d6-468c-bb42-93affe6951ca.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/time_dim/metadata/00001-9cb3d4d0-cfcd-44c0-be9b-403d099a042a.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/time_dim/metadata/66b16a0b-7ae8-47ec-a431-3a1127f188dd-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/time_dim/metadata/snap-1704649328896084613-1-66b16a0b-7ae8-47ec-a431-3a1127f188dd.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/warehouse/metadata/00001-8ec5d192-0e56-4b52-9983-39d6882fb51e.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/warehouse/metadata/6177e0c7-7e45-48be-86dc-25c25ca4caa9-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/warehouse/metadata/snap-3707620510388704025-1-6177e0c7-7e45-48be-86dc-25c25ca4caa9.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/web_page/metadata/00001-55c15b1b-0cfb-4ef6-bbd0-320747fb3ad1.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/web_page/metadata/140ff455-0714-4117-aa41-2dc31716ffc6-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/web_page/metadata/snap-400010007199662463-1-140ff455-0714-4117-aa41-2dc31716ffc6.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/web_returns/metadata/00001-4367409d-3062-4b50-984e-163027319097.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/web_returns/metadata/eba9f918-c205-49b4-a160-2a8fdea6c408-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/web_returns/metadata/snap-2369429414529147459-1-eba9f918-c205-49b4-a160-2a8fdea6c408.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/web_sales/metadata/00001-200a73b0-5546-4556-a86a-4154acc746e7.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/web_sales/metadata/96c61da1-0e94-4028-aad0-571a221b14e7-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/web_sales/metadata/snap-8759178883976857949-1-96c61da1-0e94-4028-aad0-571a221b14e7.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/web_site/metadata/00001-606a5274-bf34-4691-869f-04790fbcfea7.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/web_site/metadata/ce8be12f-c97f-4a38-b468-967972f28eb5-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/web_site/metadata/snap-8190493912996571211-1-ce8be12f-c97f-4a38-b468-967972f28eb5.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/call_center/metadata/00001-e89d97b2-9b2d-47ff-b99d-6651df79a90f.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/call_center/metadata/e21023b4-6337-466c-8a49-ca737e4de1e6-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/call_center/metadata/snap-5654429153846711549-1-e21023b4-6337-466c-8a49-ca737e4de1e6.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/catalog_page/metadata/00001-f71cfe6e-55e3-41b5-880a-5147a972c288.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/catalog_page/metadata/c80f6b66-1991-4295-b758-8a103f131278-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/catalog_page/metadata/snap-8481719299089367901-1-c80f6b66-1991-4295-b758-8a103f131278.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/catalog_returns/metadata/00001-535dd9c7-f58d-4caf-a091-9d9f69ccf38a.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/catalog_returns/metadata/0e607888-8425-44d1-a805-3fa09850a308-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/catalog_returns/metadata/snap-3711210276322022202-1-0e607888-8425-44d1-a805-3fa09850a308.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/catalog_sales/metadata/00001-614bb4de-488f-47fa-b807-878548c039ed.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/catalog_sales/metadata/bffa6a85-351c-4221-9149-e24f845e0aab-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/catalog_sales/metadata/snap-1524015146186969396-1-bffa6a85-351c-4221-9149-e24f845e0aab.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer/metadata/00001-bc8cb648-42d6-4481-b109-a6e77d653c8b.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer/metadata/384651b4-d41a-43fe-9b7b-b1ce97b12f81-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer/metadata/snap-2201162249508106077-1-384651b4-d41a-43fe-9b7b-b1ce97b12f81.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer_address/metadata/00001-b07d6011-b792-4256-b355-f7a7bb59ff30.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer_address/metadata/e4e9211a-0cd9-4034-9266-c7722c1b7559-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer_address/metadata/snap-3308787911813009439-1-e4e9211a-0cd9-4034-9266-c7722c1b7559.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer_demographics/metadata/00001-5c1100bd-e0de-4f2b-bd05-5f9c3a482dcb.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer_demographics/metadata/da89f5c2-ecbf-4b3e-8734-19add5f223b2-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer_demographics/metadata/snap-7513288259375812719-1-da89f5c2-ecbf-4b3e-8734-19add5f223b2.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/date_dim/metadata/00001-17d74180-6c5f-4ec9-9bf1-1a17a5e7cc1c.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/date_dim/metadata/a5e086f1-09c3-4393-9b69-b50996314dd3-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/date_dim/metadata/snap-879409309086531902-1-a5e086f1-09c3-4393-9b69-b50996314dd3.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/household_demographics/metadata/00001-25b249a8-7d9f-45e6-a8d4-0a6584ff3d85.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/household_demographics/metadata/4f730f87-2407-41d0-9fd5-85e7668336e3-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/household_demographics/metadata/snap-6765154030047008235-1-4f730f87-2407-41d0-9fd5-85e7668336e3.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/income_band/metadata/00001-99568020-4baf-4515-83bc-00b6f63e40ed.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/income_band/metadata/9b3490b7-e1c2-467a-8a2a-0731a236ef94-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/income_band/metadata/snap-2915181097642837852-1-9b3490b7-e1c2-467a-8a2a-0731a236ef94.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/inventory/metadata/00001-69b008e4-f403-42f3-b3d6-73ee96472da3.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/inventory/metadata/16d94170-9def-424b-b8a6-ddc6c170d9b1-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/inventory/metadata/snap-5575001584338726042-1-16d94170-9def-424b-b8a6-ddc6c170d9b1.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/item/metadata/00001-fd875019-2247-4ec5-adf6-ae30605db15b.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/item/metadata/6683e1a0-079d-42fe-87f6-ff23a0840a5c-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/item/metadata/snap-6799978105221820475-1-6683e1a0-079d-42fe-87f6-ff23a0840a5c.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/promotion/metadata/00001-a0a4a9ff-53d6-4cd0-9639-089a17b5707f.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/promotion/metadata/a43005ce-7a3e-4260-872a-e609a7597081-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/promotion/metadata/snap-6100987187671647433-1-a43005ce-7a3e-4260-872a-e609a7597081.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/reason/metadata/00001-db60d8bb-5734-4c79-b9e6-d0ec7b90e8c1.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/reason/metadata/c0fee408-669e-41c7-a70c-719122d7fdc4-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/reason/metadata/snap-501198784787015747-1-c0fee408-669e-41c7-a70c-719122d7fdc4.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/ship_mode/metadata/00001-b7cbf038-7ac7-40c6-a00a-1dbe7b4dd5b5.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/ship_mode/metadata/070c7bc1-48e8-4f95-ba69-e3703029c025-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/ship_mode/metadata/snap-8517577181179121697-1-070c7bc1-48e8-4f95-ba69-e3703029c025.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/store/metadata/00001-7f61b83f-1f99-4c10-b931-ee450d7c8bc5.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/store/metadata/0dde8a60-e04e-4495-b6ec-4846f70b4b6b-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/store/metadata/snap-5538226458318650132-1-0dde8a60-e04e-4495-b6ec-4846f70b4b6b.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/store_returns/metadata/00001-49bd4963-e9b0-4157-8aa4-2a3230d8ecd3.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/store_returns/metadata/fbca9089-7256-4012-9082-858ddb6c2eae-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/store_returns/metadata/snap-4803221953065828720-1-fbca9089-7256-4012-9082-858ddb6c2eae.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/store_sales/metadata/00001-5f263491-0286-413e-9c52-986221cc8a7d.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/store_sales/metadata/9299e356-57c6-45af-9c74-5b984ffcbb80-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/store_sales/metadata/snap-5829085943537843612-1-9299e356-57c6-45af-9c74-5b984ffcbb80.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/time_dim/metadata/00001-67c70332-7ef8-4956-b815-4d190e6caa27.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/time_dim/metadata/c1b011b3-8b57-4dff-875c-4dd6db7ad6b4-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/time_dim/metadata/snap-4765156773063077910-1-c1b011b3-8b57-4dff-875c-4dd6db7ad6b4.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/warehouse/metadata/00001-79b8fa02-a351-4660-ab95-539b41950c4b.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/warehouse/metadata/daea3a99-100e-421d-bd48-d963bf217bbb-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/warehouse/metadata/snap-6500222206325327167-1-daea3a99-100e-421d-bd48-d963bf217bbb.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_page/metadata/00001-cbf8131d-a417-43be-b61e-766768459e3f.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_page/metadata/1839905a-92c2-41a3-ae7b-25d23273dccf-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_page/metadata/snap-8492339180313289358-1-1839905a-92c2-41a3-ae7b-25d23273dccf.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_returns/metadata/00001-48e3f75b-93ef-493e-a23c-2316f1ab787d.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_returns/metadata/dc6373bf-8105-40e7-af78-8519b9edebff-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_returns/metadata/snap-4600928459638848296-1-dc6373bf-8105-40e7-af78-8519b9edebff.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_sales/metadata/00001-c5b98ee0-296d-4767-99d5-2dbc01e759a4.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_sales/metadata/c08c5447-c661-442f-99c4-cd2203afcb9a-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_sales/metadata/snap-6086383195474739821-1-c08c5447-c661-442f-99c4-cd2203afcb9a.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_site/metadata/00001-48e6cb18-4dae-4195-ba13-40e7023ffa58.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_site/metadata/b1bad00b-8038-45ba-b97c-7c765af2c811-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_site/metadata/snap-5291438207141919817-1-b1bad00b-8038-45ba-b97c-7c765af2c811.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/call_center/metadata/00001-13d0f186-a534-444a-968b-e642e48d1618.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/call_center/metadata/e1cb8c91-e49d-4c3c-b678-7f9305c77c68-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/call_center/metadata/snap-2775996447369347456-1-e1cb8c91-e49d-4c3c-b678-7f9305c77c68.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_page/metadata/00001-1f5b49f2-946f-4c61-936e-a87e5e77dbd3.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_page/metadata/4508e506-df9d-4e48-bcd2-63d0d897b29e-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_page/metadata/snap-2884094035664543174-1-4508e506-df9d-4e48-bcd2-63d0d897b29e.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_returns/metadata/00001-001bf11b-e057-410c-b547-ada25a99ccb1.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_returns/metadata/fa4fa463-9aef-402a-8635-bd783cb9a133-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_returns/metadata/snap-8816593287668123241-1-fa4fa463-9aef-402a-8635-bd783cb9a133.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_sales/metadata/00001-0cd42299-4e58-4a9e-8313-f9e75941c6f2.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_sales/metadata/2e7e3adf-c440-4b9d-bef9-615275412981-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_sales/metadata/snap-1828287521862639994-1-2e7e3adf-c440-4b9d-bef9-615275412981.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/customer/metadata/00001-1497473d-c833-4a10-ad35-24a5eecff81f.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/customer/metadata/45ecdd7a-633b-4eaf-808e-e8a76f2afc93-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/customer/metadata/snap-8107938679113897524-1-45ecdd7a-633b-4eaf-808e-e8a76f2afc93.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/customer_address/metadata/00001-c47bb31a-4e58-4fca-8984-d07ac74beec9.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/customer_address/metadata/4516d408-6c08-4422-92ba-d10aee4c038d-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/customer_address/metadata/snap-7742401198892667307-1-4516d408-6c08-4422-92ba-d10aee4c038d.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/customer_demographics/metadata/00001-5ff789a3-76cb-4b35-a637-ac13df61d7e5.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/customer_demographics/metadata/8afc14d2-fba9-4f14-b636-772729a87995-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/customer_demographics/metadata/snap-2101692149723994036-1-8afc14d2-fba9-4f14-b636-772729a87995.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/date_dim/metadata/00001-cad2b3f6-8f6b-46ce-aaaf-ad0aed68ce59.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/date_dim/metadata/833fae69-41bb-487f-93eb-39739d52225a-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/date_dim/metadata/snap-4095010124650684994-1-833fae69-41bb-487f-93eb-39739d52225a.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/household_demographics/metadata/00001-3159effb-c560-4dea-8ca5-745e3a3f7fc1.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/household_demographics/metadata/c0dce8d0-485c-4feb-83bc-df6a677886fe-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/household_demographics/metadata/snap-1609980499255550327-1-c0dce8d0-485c-4feb-83bc-df6a677886fe.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/income_band/metadata/00001-7a78e721-28ce-4a93-9309-f95b81f494bb.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/income_band/metadata/1015ac44-d82e-4a05-a038-65fde8ef6da3-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/income_band/metadata/snap-9031488788150469094-1-1015ac44-d82e-4a05-a038-65fde8ef6da3.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/inventory/metadata/00001-a96b6ff3-1f4b-4419-bef6-9f77e0de188a.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/inventory/metadata/5558c7c7-d25c-4356-8db9-b1c7296943db-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/inventory/metadata/snap-304879748299377848-1-5558c7c7-d25c-4356-8db9-b1c7296943db.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/item/metadata/00001-f3923263-49f5-4161-abfe-9bdce15c0060.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/item/metadata/41fcc6a2-2e2f-4fdc-bc85-6932abb37aec-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/item/metadata/snap-6329032515952286791-1-41fcc6a2-2e2f-4fdc-bc85-6932abb37aec.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/promotion/metadata/00001-b5b347cc-1be6-4681-a37a-e645575b4d79.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/promotion/metadata/0c427289-cf08-4939-a662-11b5a04a2a82-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/promotion/metadata/snap-629877963447712280-1-0c427289-cf08-4939-a662-11b5a04a2a82.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/reason/metadata/00001-db49590f-5936-4426-88ec-e7b613e34aec.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/reason/metadata/75a52cb7-2991-4880-90a8-07a6a56c89f5-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/reason/metadata/snap-204521873767892073-1-75a52cb7-2991-4880-90a8-07a6a56c89f5.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/ship_mode/metadata/00001-b3791be6-2d0f-40b4-9cf9-58a0a294bf84.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/ship_mode/metadata/0e25905e-562c-4a35-9e6c-da1f5a57437c-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/ship_mode/metadata/snap-3829200591547055835-1-0e25905e-562c-4a35-9e6c-da1f5a57437c.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/store/metadata/00001-c279dae6-7950-4449-baba-91f153c638c8.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/store/metadata/dbe69e01-f4eb-41bb-87ef-df547e18e0d5-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/store/metadata/snap-8205194758611994023-1-dbe69e01-f4eb-41bb-87ef-df547e18e0d5.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/store_returns/metadata/00001-c467e79d-4685-4885-8135-377e67bb7045.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/store_returns/metadata/0f4d5031-eb6d-42a9-886c-92d65dd60c58-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/store_returns/metadata/snap-939104734745829810-1-0f4d5031-eb6d-42a9-886c-92d65dd60c58.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/store_sales/metadata/00001-96646f0d-586c-4989-8e91-5d55096c51ff.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/store_sales/metadata/037b55c8-1883-44c4-9c84-f0a475518c5d-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/store_sales/metadata/snap-891179441395529189-1-037b55c8-1883-44c4-9c84-f0a475518c5d.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/time_dim/metadata/00001-49904184-0f96-4d37-8f70-2d1fb5e236bf.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/time_dim/metadata/3cc582ab-e4f8-4275-8d8c-b34457635479-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/time_dim/metadata/snap-3991191175779644547-1-3cc582ab-e4f8-4275-8d8c-b34457635479.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/warehouse/metadata/00001-a11a1bfc-d122-4849-b30e-7eba5da700d9.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/warehouse/metadata/4ce0ec08-ee7e-47f9-881e-8713ab061965-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/warehouse/metadata/snap-5768182382411552162-1-4ce0ec08-ee7e-47f9-881e-8713ab061965.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/web_page/metadata/00001-43448ab4-255b-4f17-a411-8c76483037d4.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/web_page/metadata/a0b13234-29d8-4a96-a78c-423abbd72428-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/web_page/metadata/snap-5566490230012484218-1-a0b13234-29d8-4a96-a78c-423abbd72428.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/web_returns/metadata/00001-1b87e923-ffb7-4294-9b18-d270678ac85d.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/web_returns/metadata/0e1134ab-6c97-4c8a-ad75-729685823145-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/web_returns/metadata/snap-5194348898560540975-1-0e1134ab-6c97-4c8a-ad75-729685823145.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/web_sales/metadata/00001-c52b34fd-bbfa-4f25-be27-1f9d8239301f.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/web_sales/metadata/86148074-cbae-4bc4-b94e-62d9fa618ebb-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/web_sales/metadata/snap-965884838915906198-1-86148074-cbae-4bc4-b94e-62d9fa618ebb.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/web_site/metadata/00001-8398c88f-0586-48f9-86e5-6782429b329f.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/web_site/metadata/8c3481ac-9183-4c42-9afa-c8c18af931ba-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/web_site/metadata/snap-2353247371984755424-1-8c3481ac-9183-4c42-9afa-c8c18af931ba.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/customer/metadata/00001-0d4ad0a4-9dc0-4241-8794-a7b4112eeefd.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/customer/metadata/aae1d166-8a50-495d-a9af-52d223afd979-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/customer/metadata/snap-8024485471681810521-1-aae1d166-8a50-495d-a9af-52d223afd979.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/lineitem/metadata/00001-27c12418-65a5-4a63-addb-5a40e3eb0302.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/lineitem/metadata/298a886c-55cf-47e9-ba6c-642d28b8282f-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/lineitem/metadata/snap-4572939840786045920-1-298a886c-55cf-47e9-ba6c-642d28b8282f.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/nation/metadata/00001-6b4caf01-107d-4125-b15d-8fcadf903b05.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/nation/metadata/ca7d21c1-573d-462a-9e50-56c2d5a0eadf-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/nation/metadata/snap-6744173985551436807-1-ca7d21c1-573d-462a-9e50-56c2d5a0eadf.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/orders/metadata/00001-843c1139-1fc8-488b-b320-a017535c9c71.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/orders/metadata/367513f4-29a3-4499-9f24-b7df6ed65323-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/orders/metadata/snap-8400893575492285-1-367513f4-29a3-4499-9f24-b7df6ed65323.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/part/metadata/00001-9dead5a0-0e95-4dc9-bfe5-8720b9ce9c9d.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/part/metadata/41390b4f-4d94-4aa1-a2b7-d60a4f18ca59-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/part/metadata/snap-7380758221979511099-1-41390b4f-4d94-4aa1-a2b7-d60a4f18ca59.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/partsupp/metadata/00001-eeb6d75b-e98a-446d-a0fc-de2976040eef.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/partsupp/metadata/2ec47d71-81d2-4d24-b332-198ce4da3f5e-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/partsupp/metadata/snap-3761094200008885694-1-2ec47d71-81d2-4d24-b332-198ce4da3f5e.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/region/metadata/00001-c07c760d-f262-4275-a11f-2fb9efd56ca5.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/region/metadata/329dbdb4-c413-443c-8597-690949ea4ce3-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/region/metadata/snap-3099168623670929377-1-329dbdb4-c413-443c-8597-690949ea4ce3.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/supplier/metadata/00001-042f5da7-31b3-49d6-a1be-2d6457e10487.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/supplier/metadata/0940356e-47da-42c3-93d5-49146db8940e-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/supplier/metadata/snap-6442399194812117023-1-0940356e-47da-42c3-93d5-49146db8940e.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/customer/metadata/00001-b8261ed3-6d15-4bf9-93f6-ff779c34654c.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/customer/metadata/adc47218-453f-4b6b-9a31-22e5d0cf0ff3-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/customer/metadata/snap-9117423429073808947-1-adc47218-453f-4b6b-9a31-22e5d0cf0ff3.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/lineitem/metadata/00001-ee5944af-6c41-42a6-a608-0139125beae3.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/lineitem/metadata/27bed02d-660f-4274-bf90-81621ac8000d-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/lineitem/metadata/snap-4352555810371227857-1-27bed02d-660f-4274-bf90-81621ac8000d.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/nation/metadata/00001-ca2e4058-ba15-4cde-82da-d240aee66235.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/nation/metadata/49b3f9cf-2a25-4652-8db6-972b8f8a70b1-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/nation/metadata/snap-1267796143814779184-1-49b3f9cf-2a25-4652-8db6-972b8f8a70b1.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/orders/metadata/00001-de39ef43-376b-4e80-b317-b3fcbee8a4a0.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/orders/metadata/4001c8f5-3b35-4089-97f9-e2711080f425-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/orders/metadata/snap-2019712160243546218-1-4001c8f5-3b35-4089-97f9-e2711080f425.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/part/metadata/00001-a549dec1-d6d0-49c4-993e-aac98024d18f.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/part/metadata/391d5ca2-a258-420b-84e6-b3f2e86fe27d-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/part/metadata/snap-7291036132088373259-1-391d5ca2-a258-420b-84e6-b3f2e86fe27d.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/partsupp/metadata/00001-10b4483e-ef71-4fb9-9fc1-a2daa5520423.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/partsupp/metadata/a04ba584-b742-4d1f-8008-ec8283bb5946-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/partsupp/metadata/snap-2966252276871620522-1-a04ba584-b742-4d1f-8008-ec8283bb5946.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/region/metadata/00001-ea3004db-2743-4941-87ca-07994e16e4fb.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/region/metadata/258dcd78-0714-4b2e-84c1-28b4c77b8c13-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/region/metadata/snap-505527619873344341-1-258dcd78-0714-4b2e-84c1-28b4c77b8c13.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/supplier/metadata/00001-c145c3c9-5e02-43a2-85c1-874b7cf8e5d8.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/supplier/metadata/d506a73a-bab9-4da0-a294-1522da0fb7dd-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/supplier/metadata/snap-4470411975392631507-1-d506a73a-bab9-4da0-a294-1522da0fb7dd.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/customer/metadata/00001-489a0dd5-e048-4852-a4c7-714bb8baa437.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/customer/metadata/33e9cd1b-7781-4dde-9335-6822c5386bf3-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/customer/metadata/snap-4666520520301191779-1-33e9cd1b-7781-4dde-9335-6822c5386bf3.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/lineitem/metadata/00001-0d17e724-b607-4375-b38a-59b0fdd06683.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/lineitem/metadata/62714a3b-16d8-4ad5-a96d-06b3319586b4-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/lineitem/metadata/snap-4619553423372716097-1-62714a3b-16d8-4ad5-a96d-06b3319586b4.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/nation/metadata/00001-ad66a370-c879-478a-8aa8-abd8a523df51.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/nation/metadata/057f7ceb-3506-4df0-9aa0-3f572afcf0d3-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/nation/metadata/snap-2884749000519741880-1-057f7ceb-3506-4df0-9aa0-3f572afcf0d3.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/orders/metadata/00001-2aea44dd-eb63-480d-8d14-1476113fd04a.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/orders/metadata/a8a26561-2404-458e-9175-b0e2959b280b-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/orders/metadata/snap-5808443786783721793-1-a8a26561-2404-458e-9175-b0e2959b280b.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/part/metadata/00001-704f5147-bec5-4989-8e44-3aa5375b5231.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/part/metadata/c92610ba-365d-468d-840a-166a97a61f0d-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/part/metadata/snap-8171683153397007568-1-c92610ba-365d-468d-840a-166a97a61f0d.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/partsupp/metadata/00001-3d26a388-da22-4f75-9846-576956ca20fe.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/partsupp/metadata/7e61b2f6-a7af-4dab-be09-bd5bbdfa8faf-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/partsupp/metadata/snap-8775012740330327682-1-7e61b2f6-a7af-4dab-be09-bd5bbdfa8faf.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/region/metadata/00001-76036286-7197-4a19-aa6d-6a64a3873218.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/region/metadata/12a03294-b36a-4455-a41b-ee395d1a5a49-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/region/metadata/snap-6009178438198803734-1-12a03294-b36a-4455-a41b-ee395d1a5a49.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/supplier/metadata/00001-806c6418-05fb-4aea-a4c9-66727e05c918.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/supplier/metadata/96d699fe-e7c0-45d3-b2c5-f8cff28a432f-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/supplier/metadata/snap-6296080196019161829-1-96d699fe-e7c0-45d3-b2c5-f8cff28a432f.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/customer/metadata/00001-93ab73d2-0ce9-4e5e-ba87-e86c8cc33af6.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/customer/metadata/30750c07-6e32-4867-a606-44111b035949-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/customer/metadata/snap-4370200594185215759-1-30750c07-6e32-4867-a606-44111b035949.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/lineitem/metadata/00001-f6deacdd-7f87-4bc3-9ad1-efb29f31b2ef.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/lineitem/metadata/9eb45a05-f72f-48d1-82f9-b6ea7baffaee-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/lineitem/metadata/snap-2010185327452057859-1-9eb45a05-f72f-48d1-82f9-b6ea7baffaee.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/nation/metadata/00001-9c53243a-1beb-44f4-8e56-8eaa274bc9c7.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/nation/metadata/e3a2705e-f37e-4708-9dd0-34090f475f02-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/nation/metadata/snap-5987065169814573221-1-e3a2705e-f37e-4708-9dd0-34090f475f02.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/orders/metadata/00001-e500bd00-0e38-4dbb-8a45-b1102b4616b5.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/orders/metadata/a2c9dd59-f137-4d62-bba4-cd37998c427d-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/orders/metadata/snap-2054734473589120268-1-a2c9dd59-f137-4d62-bba4-cd37998c427d.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/part/metadata/00001-b61d1dc1-a40c-4d6a-adc8-f0abd7376d1a.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/part/metadata/5edd0121-9c67-4612-a698-952806d01c6d-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/part/metadata/snap-2451437136998665463-1-5edd0121-9c67-4612-a698-952806d01c6d.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/partsupp/metadata/00001-0ca88c5c-e3d3-4d3d-a331-78e604e9d362.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/partsupp/metadata/00c1c648-86af-4ecf-b9cb-85595ef91a48-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/partsupp/metadata/snap-4939042327833546636-1-00c1c648-86af-4ecf-b9cb-85595ef91a48.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/region/metadata/00001-93c9215e-992a-4c5e-ba08-fcff4733bf75.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/region/metadata/3f47ad2f-9095-47cd-8988-4809d94e799c-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/region/metadata/snap-6839986430402395594-1-3f47ad2f-9095-47cd-8988-4809d94e799c.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/supplier/metadata/00001-e7a9638d-1c61-4083-b182-0c99c6f89305.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/supplier/metadata/359263b5-5a1f-4f16-bea1-f0f748f0c0a8-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/supplier/metadata/snap-3174586439174883544-1-359263b5-5a1f-4f16-bea1-f0f748f0c0a8.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/call_center/metadata/00000-dbd37a20-4eed-4e77-ad02-4c936c7d8d6a.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/call_center/metadata/f8bcd002-b25e-42d3-9a20-680286272ed5-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/call_center/metadata/snap-180463549666864477-1-f8bcd002-b25e-42d3-9a20-680286272ed5.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_page/metadata/00000-2ef466a3-5b3d-4c75-ae60-3dddfed4dacf.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_page/metadata/cdf08a55-904f-4581-aa97-f856752b6967-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_page/metadata/snap-82908533283371708-1-cdf08a55-904f-4581-aa97-f856752b6967.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_returns/metadata/00000-78945112-7cf1-4e3d-9557-e434c41a2383.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_returns/metadata/1b002ed6-2a22-4402-8b13-0b9b85931824-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_returns/metadata/snap-8292219806794635592-1-1b002ed6-2a22-4402-8b13-0b9b85931824.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_sales/metadata/00000-f914f08c-88d2-4aca-8724-500bf37495e8.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_sales/metadata/11052cea-ed9d-49d0-8d6b-2ec6dd57e46e-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_sales/metadata/snap-1612602357847496502-1-11052cea-ed9d-49d0-8d6b-2ec6dd57e46e.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/customer/metadata/00000-1761ead5-fbfc-4a07-8fba-7e2291ed4e27.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/customer/metadata/94caaa8a-6674-4c6a-84e2-1025ea03170b-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/customer/metadata/snap-497222046221833446-1-94caaa8a-6674-4c6a-84e2-1025ea03170b.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/customer_address/metadata/00000-2cb66f64-0787-4713-a480-2286504d543f.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/customer_address/metadata/ac3a7184-53df-490f-a4b9-480b8e43acba-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/customer_address/metadata/snap-5497986337991527298-1-ac3a7184-53df-490f-a4b9-480b8e43acba.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/customer_demographics/metadata/00000-b75465de-0ce6-4a43-bf66-d25bf0af2dfa.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/customer_demographics/metadata/8347d08d-e19d-4f6b-8cd3-e07f95b6e1f5-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/customer_demographics/metadata/snap-3805658040916858242-1-8347d08d-e19d-4f6b-8cd3-e07f95b6e1f5.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/date_dim/metadata/00000-87f53898-1b80-43c8-91ff-f12d4920679c.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/date_dim/metadata/94921b30-42c9-4690-86cd-a7d6f8180299-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/date_dim/metadata/snap-1540898220745315894-1-94921b30-42c9-4690-86cd-a7d6f8180299.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/household_demographics/metadata/00000-90853a69-3533-4945-acd5-14bbca4a5cd3.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/household_demographics/metadata/3f673543-f484-41ca-b5ea-77d5dc1a95cf-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/household_demographics/metadata/snap-4875603543705301354-1-3f673543-f484-41ca-b5ea-77d5dc1a95cf.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/income_band/metadata/00000-3a51efde-0157-4d0e-a965-e7dd6068ec4e.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/income_band/metadata/777df258-effc-4253-a398-78634bfecfff-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/income_band/metadata/snap-6121948948604053537-1-777df258-effc-4253-a398-78634bfecfff.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/inventory/metadata/00000-aca46963-a5fe-43ea-9ea5-e15bec1e97f5.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/inventory/metadata/b09da5f8-9688-41de-97b7-d7558d5793e3-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/inventory/metadata/snap-2468336565001458778-1-b09da5f8-9688-41de-97b7-d7558d5793e3.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/item/metadata/00000-509022c2-7442-45fd-bfb1-038936df7f5a.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/item/metadata/7e50169e-0264-44be-8503-dfa9dbed5fd4-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/item/metadata/snap-4551617743668210082-1-7e50169e-0264-44be-8503-dfa9dbed5fd4.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/promotion/metadata/00000-f67ad0c1-14b3-44d4-9878-b7d36cc455ee.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/promotion/metadata/1634c69a-e8ab-4a4b-866e-344ca294d9f0-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/promotion/metadata/snap-5366699751584273815-1-1634c69a-e8ab-4a4b-866e-344ca294d9f0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/reason/metadata/00000-d391c19b-6305-4aaf-95b4-ca05171fc277.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/reason/metadata/b535d285-879e-414d-aa40-feb600797de4-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/reason/metadata/snap-7858783715254064031-1-b535d285-879e-414d-aa40-feb600797de4.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/ship_mode/metadata/00000-a8324c13-7e0c-4eea-9f90-c581e6edda1a.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/ship_mode/metadata/6db2231a-ca5b-4ea0-b8ca-e712a3f15393-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/ship_mode/metadata/snap-93583429254238746-1-6db2231a-ca5b-4ea0-b8ca-e712a3f15393.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store/metadata/00000-5be497c9-30f9-4547-a321-c09c13eabb7a.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store/metadata/f84adc81-2b0b-41f4-84fd-9760561f7fa2-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store/metadata/snap-1274028951089363265-1-f84adc81-2b0b-41f4-84fd-9760561f7fa2.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store_returns/metadata/00000-19e865be-db8b-4537-b569-7d44104e8ef5.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store_returns/metadata/579bd945-ce7d-497e-9766-742f5edb0933-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store_returns/metadata/snap-2430355542025878006-1-579bd945-ce7d-497e-9766-742f5edb0933.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store_sales/metadata/00000-52958805-01a1-4b1c-9760-fc4837d9ec1d.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store_sales/metadata/fc3ec034-868f-43ec-90a1-52e1c38c65f8-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store_sales/metadata/snap-6936726980875249890-1-fc3ec034-868f-43ec-90a1-52e1c38c65f8.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/time_dim/metadata/00000-65cb7b5d-1d98-4cbc-9356-7838397425ed.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/time_dim/metadata/a0ba4857-ca91-4c07-96c3-f2549618d4ec-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/time_dim/metadata/snap-2337519488650886122-1-a0ba4857-ca91-4c07-96c3-f2549618d4ec.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/warehouse/metadata/00000-2c5534dd-aa29-4650-aa4f-31efa6057fc2.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/warehouse/metadata/c10b0ff9-aa47-44cd-a9a2-b59e12df5d70-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/warehouse/metadata/snap-6972294945978784584-1-c10b0ff9-aa47-44cd-a9a2-b59e12df5d70.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/web_page/metadata/00000-f2d453bd-be4e-4b35-87ae-74d0e24ff027.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/web_page/metadata/26683bd4-edf3-4581-a9fa-7644a32ae331-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/web_page/metadata/snap-7514041884394119007-1-26683bd4-edf3-4581-a9fa-7644a32ae331.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/web_returns/metadata/00000-75d415fc-5af3-486a-b9c2-b853c4792bda.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/web_returns/metadata/ab80c5e3-b2f4-40a3-b1a3-67e016d116cd-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/web_returns/metadata/snap-4364078469342007082-1-ab80c5e3-b2f4-40a3-b1a3-67e016d116cd.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/web_sales/metadata/00000-9cc093d6-e9a3-4105-b745-219c5c38da47.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/web_sales/metadata/416a3161-5ba9-4968-a394-a2eb6e3709ce-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/web_sales/metadata/snap-7836568010808044048-1-416a3161-5ba9-4968-a394-a2eb6e3709ce.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/web_site/metadata/00000-4a750e24-e3e7-4e51-9edd-1fdbf0e97c30.metadata.json delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/web_site/metadata/fbd8e2b4-78fd-47d7-b90c-1c52639f75b7-m0.avro delete mode 100644 testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/web_site/metadata/snap-3658900633663111316-1-fbd8e2b4-78fd-47d7-b90c-1c52639f75b7.avro delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q01.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q02.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q03.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q04.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q05.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q06.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q07.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q08.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q09.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q10.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q11.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q12.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q13.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q14.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q15.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q16.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q17.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q18.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q19.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q20.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q21.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q22.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q23.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q24.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q25.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q26.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q27.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q28.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q29.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q30.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q31.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q32.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q33.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q34.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q35.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q36.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q37.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q38.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q39.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q40.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q41.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q42.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q43.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q44.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q45.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q46.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q47.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q48.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q49.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q50.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q51.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q52.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q53.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q54.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q55.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q56.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q57.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q58.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q59.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q60.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q61.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q62.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q63.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q64.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q65.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q66.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q67.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q68.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q69.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q70.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q71.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q72.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q73.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q74.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q75.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q76.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q77.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q78.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q79.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q80.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q81.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q82.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q83.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q84.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q85.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q86.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q87.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q88.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q89.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q90.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q91.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q92.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q93.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q94.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q95.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q96.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q97.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q98.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/partitioned/q99.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q01.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q02.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q03.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q04.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q05.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q06.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q07.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q08.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q09.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q10.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q11.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q12.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q13.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q14.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q15.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q16.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q17.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q18.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q19.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q20.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q21.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q22.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q23.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q24.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q25.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q26.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q27.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q28.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q29.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q30.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q31.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q32.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q33.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q34.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q35.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q36.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q37.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q38.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q39.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q40.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q41.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q42.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q43.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q44.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q45.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q46.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q47.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q48.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q49.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q50.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q51.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q52.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q53.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q54.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q55.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q56.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q57.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q58.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q59.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q60.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q61.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q62.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q63.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q64.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q65.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q66.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q67.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q68.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q69.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q70.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q71.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q72.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q73.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q74.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q75.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q76.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q77.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q78.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q79.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q80.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q81.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q82.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q83.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q84.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q85.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q86.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q87.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q88.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q89.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q90.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q91.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q92.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q93.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q94.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q95.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q96.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q97.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q98.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/hive/unpartitioned/q99.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q01.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q02.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q03.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q04.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q05.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q06.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q07.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q08.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q09.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q10.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q11.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q12.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q13.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q14.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q15.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q16.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q17.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q18.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q19.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q20.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q21.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q22.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q23.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q24.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q25.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q26.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q27.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q28.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q29.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q30.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q31.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q32.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q33.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q34.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q35.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q36.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q37.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q38.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q39.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q40.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q41.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q42.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q43.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q44.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q45.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q46.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q47.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q48.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q49.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q50.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q51.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q52.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q53.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q54.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q55.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q56.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q57.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q58.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q59.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q60.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q61.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q62.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q63.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q64.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q65.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q66.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q67.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q68.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q69.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q70.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q71.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q72.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q73.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q74.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q75.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q76.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q77.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q78.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q79.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q80.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q81.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q82.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q83.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q84.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q85.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q86.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q87.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q88.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q89.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q90.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q91.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q92.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q93.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q94.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q95.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q96.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q97.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q98.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/partitioned/q99.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q01.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q02.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q03.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q04.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q05.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q06.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q07.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q08.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q09.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q10.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q11.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q12.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q13.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q14.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q15.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q16.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q17.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q18.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q19.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q20.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q21.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q22.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q23.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q24.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q25.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q26.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q27.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q28.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q29.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q30.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q31.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q32.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q33.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q34.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q35.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q36.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q37.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q38.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q39.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q40.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q41.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q42.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q43.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q44.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q45.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q46.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q47.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q48.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q49.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q50.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q51.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q52.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q53.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q54.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q55.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q56.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q57.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q58.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q59.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q60.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q61.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q62.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q63.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q64.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q65.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q66.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q67.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q68.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q69.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q70.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q71.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q72.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q73.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q74.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q75.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q76.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q77.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q78.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q79.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q80.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q81.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q82.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q83.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q84.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q85.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q86.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q87.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q88.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q89.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q90.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q91.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q92.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q93.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q94.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q95.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q96.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q97.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q98.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/orc/unpartitioned/q99.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q01.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q02.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q03.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q04.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q05.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q06.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q07.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q08.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q09.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q10.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q11.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q12.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q13.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q14.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q15.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q16.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q17.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q18.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q19.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q20.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q21.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q22.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q23.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q24.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q25.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q26.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q27.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q28.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q29.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q30.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q31.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q32.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q33.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q34.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q35.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q36.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q37.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q38.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q39.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q40.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q41.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q42.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q43.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q44.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q45.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q46.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q47.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q48.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q49.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q50.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q51.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q52.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q53.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q54.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q55.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q56.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q57.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q58.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q59.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q60.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q61.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q62.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q63.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q64.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q65.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q66.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q67.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q68.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q69.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q70.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q71.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q72.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q73.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q74.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q75.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q76.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q77.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q78.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q79.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q80.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q81.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q82.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q83.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q84.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q85.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q86.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q87.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q88.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q89.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q90.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q91.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q92.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q93.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q94.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q95.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q96.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q97.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q98.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/partitioned/q99.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q01.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q02.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q03.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q04.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q05.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q06.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q07.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q08.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q09.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q10.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q11.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q12.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q13.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q14.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q15.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q16.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q17.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q18.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q19.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q20.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q21.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q22.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q23.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q24.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q25.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q26.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q27.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q28.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q29.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q30.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q31.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q32.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q33.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q34.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q35.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q36.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q37.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q38.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q39.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q40.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q41.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q42.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q43.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q44.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q45.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q46.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q47.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q48.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q49.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q50.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q51.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q52.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q53.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q54.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q55.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q56.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q57.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q58.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q59.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q60.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q61.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q62.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q63.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q64.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q65.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q66.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q67.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q68.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q69.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q70.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q71.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q72.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q73.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q74.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q75.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q76.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q77.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q78.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q79.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q80.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q81.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q82.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q83.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q84.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q85.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q86.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q87.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q88.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q89.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q90.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q91.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q92.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q93.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q94.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q95.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q96.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q97.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q98.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg/parquet/unpartitioned/q99.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q01.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q02.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q03.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q04.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q05.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q06.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q07.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q08.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q09.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q10.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q11.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q12.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q13.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q14.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q15.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q16.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q17.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q18.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q19.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q20.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q21.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q22.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q23.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q24.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q25.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q26.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q27.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q28.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q29.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q30.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q31.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q32.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q33.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q34.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q35.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q36.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q37.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q38.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q39.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q40.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q41.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q42.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q43.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q44.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q45.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q46.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q47.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q48.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q49.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q50.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q51.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q52.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q53.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q54.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q55.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q56.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q57.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q58.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q59.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q60.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q61.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q62.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q63.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q64.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q65.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q66.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q67.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q68.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q69.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q70.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q71.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q72.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q73.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q74.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q75.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q76.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q77.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q78.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q79.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q80.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q81.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q82.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q83.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q84.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q85.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q86.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q87.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q88.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q89.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q90.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q91.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q92.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q93.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q94.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q95.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q96.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q97.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q98.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpcds/iceberg_small_files/parquet/unpartitioned/q99.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q01.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q02.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q03.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q04.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q05.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q06.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q07.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q08.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q09.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q10.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q11.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q12.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q13.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q14.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q15.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q16.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q17.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q18.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q19.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q20.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q21.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/partitioned/q22.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q01.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q02.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q03.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q04.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q05.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q06.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q07.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q08.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q09.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q10.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q11.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q12.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q13.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q14.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q15.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q16.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q17.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q18.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q19.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q20.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q21.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/hive/unpartitioned/q22.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q01.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q02.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q03.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q04.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q05.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q06.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q07.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q08.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q09.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q10.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q11.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q12.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q13.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q14.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q15.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q16.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q17.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q18.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q19.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q20.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q21.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/partitioned/q22.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q01.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q02.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q03.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q04.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q05.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q06.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q07.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q08.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q09.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q10.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q11.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q12.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q13.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q14.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q15.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q16.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q17.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q18.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q19.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q20.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q21.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/orc/unpartitioned/q22.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q01.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q02.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q03.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q04.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q05.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q06.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q07.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q08.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q09.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q10.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q11.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q12.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q13.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q14.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q15.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q16.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q17.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q18.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q19.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q20.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q21.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/partitioned/q22.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q01.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q02.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q03.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q04.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q05.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q06.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q07.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q08.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q09.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q10.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q11.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q12.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q13.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q14.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q15.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q16.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q17.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q18.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q19.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q20.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q21.plan.txt delete mode 100644 testing/trino-tests/src/test/resources/sql/trino/tpch/iceberg/parquet/unpartitioned/q22.plan.txt diff --git a/testing/trino-tests/pom.xml b/testing/trino-tests/pom.xml index 3e7eeb39666c..43f9532505a8 100644 --- a/testing/trino-tests/pom.xml +++ b/testing/trino-tests/pom.xml @@ -177,12 +177,6 @@ test - - io.trino - trino-benchmark-queries - test - - io.trino trino-blackhole @@ -195,56 +189,6 @@ test - - io.trino - trino-exchange-filesystem - test - - - - io.trino - trino-filesystem - test - - - - io.trino - trino-filesystem-s3 - test - - - - io.trino - trino-hdfs - test - - - - io.trino - trino-hive - test - - - - io.trino - trino-hive - test-jar - test - - - - io.trino - trino-iceberg - test - - - - io.trino - trino-iceberg - test-jar - test - - io.trino trino-main @@ -271,19 +215,6 @@ test - - io.trino - trino-parser - test - - - - io.trino - trino-parser - test-jar - test - - io.trino trino-plugin-toolkit @@ -322,12 +253,6 @@ test - - io.trino - trino-testing-containers - test - - io.trino trino-testing-services @@ -346,12 +271,6 @@ test - - io.trino.hive - hive-apache - test - - io.trino.tpcds tpcds @@ -370,12 +289,6 @@ test - - org.apache.iceberg - iceberg-core - test - - org.assertj assertj-core @@ -410,16 +323,6 @@ - - org.basepom.maven - duplicate-finder-maven-plugin - - - iceberg-build.properties - mozilla/public-suffix-list.txt - - - org.apache.maven.plugins maven-surefire-plugin diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/BaseCostBasedPlanTest.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/BaseCostBasedPlanTest.java deleted file mode 100644 index cbbb425cfc3e..000000000000 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/BaseCostBasedPlanTest.java +++ /dev/null @@ -1,361 +0,0 @@ -/* - * 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 io.trino.sql.planner; - -import com.google.common.base.VerifyException; -import com.google.common.collect.ImmutableMap; -import com.google.common.io.Resources; -import io.airlift.log.Logger; -import io.trino.Session; -import io.trino.spi.connector.CatalogSchemaTableName; -import io.trino.spi.connector.ConnectorFactory; -import io.trino.sql.DynamicFilters; -import io.trino.sql.planner.OptimizerConfig.JoinDistributionType; -import io.trino.sql.planner.OptimizerConfig.JoinReorderingStrategy; -import io.trino.sql.planner.assertions.BasePlanTest; -import io.trino.sql.planner.plan.AggregationNode; -import io.trino.sql.planner.plan.ExchangeNode; -import io.trino.sql.planner.plan.FilterNode; -import io.trino.sql.planner.plan.JoinNode; -import io.trino.sql.planner.plan.SemiJoinNode; -import io.trino.sql.planner.plan.TableScanNode; -import io.trino.sql.planner.plan.ValuesNode; -import io.trino.testing.LocalQueryRunner; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.Optional; -import java.util.stream.IntStream; - -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.base.Verify.verify; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.io.Files.createParentDirs; -import static com.google.common.io.Files.write; -import static com.google.common.io.Resources.getResource; -import static io.trino.Session.SessionBuilder; -import static io.trino.SystemSessionProperties.JOIN_DISTRIBUTION_TYPE; -import static io.trino.SystemSessionProperties.JOIN_REORDERING_STRATEGY; -import static io.trino.execution.querystats.PlanOptimizersStatsCollector.createPlanOptimizersStatsCollector; -import static io.trino.execution.warnings.WarningCollector.NOOP; -import static io.trino.sql.DynamicFilters.extractDynamicFilters; -import static io.trino.sql.planner.LogicalPlanner.Stage.OPTIMIZED_AND_VALIDATED; -import static io.trino.sql.planner.plan.JoinNode.DistributionType.REPLICATED; -import static io.trino.sql.planner.plan.JoinNode.Type.INNER; -import static io.trino.testing.TestingSession.testSessionBuilder; -import static java.lang.String.format; -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.nio.file.Files.isDirectory; -import static java.util.Locale.ENGLISH; -import static java.util.Objects.requireNonNull; -import static java.util.stream.Collectors.joining; -import static org.testng.Assert.assertEquals; - -public abstract class BaseCostBasedPlanTest - extends BasePlanTest -{ - private static final Logger log = Logger.get(BaseCostBasedPlanTest.class); - - public static final List TPCH_SQL_FILES = IntStream.rangeClosed(1, 22) - .mapToObj(i -> format("q%02d", i)) - .map(queryId -> format("/sql/trino/tpch/%s.sql", queryId)) - .collect(toImmutableList()); - - public static final List TPCDS_SQL_FILES = IntStream.range(1, 100) - .mapToObj(i -> format("q%02d", i)) - .map(queryId -> format("/sql/trino/tpcds/%s.sql", queryId)) - .collect(toImmutableList()); - - private static final String CATALOG_NAME = "local"; - - private final String schemaName; - private final Optional fileFormatName; - private final boolean partitioned; - protected boolean smallFiles; - - public BaseCostBasedPlanTest(String schemaName, Optional fileFormatName, boolean partitioned) - { - this(schemaName, fileFormatName, partitioned, false); - } - - public BaseCostBasedPlanTest(String schemaName, Optional fileFormatName, boolean partitioned, boolean smallFiles) - { - this.schemaName = requireNonNull(schemaName, "schemaName is null"); - this.fileFormatName = requireNonNull(fileFormatName, "fileFormatName is null"); - this.partitioned = partitioned; - this.smallFiles = smallFiles; - } - - @Override - protected LocalQueryRunner createLocalQueryRunner() - { - SessionBuilder sessionBuilder = testSessionBuilder() - .setCatalog(CATALOG_NAME) - .setSchema(schemaName) - // Reducing ARM and x86 floating point arithmetic differences, mostly visible at PlanNodeStatsEstimateMath::estimateCorrelatedConjunctionRowCount - .setSystemProperty("filter_conjunction_independence_factor", "0.750000001") - .setSystemProperty("task_concurrency", "1") // these tests don't handle exchanges from local parallel - .setSystemProperty(JOIN_REORDERING_STRATEGY, JoinReorderingStrategy.AUTOMATIC.name()) - .setSystemProperty(JOIN_DISTRIBUTION_TYPE, JoinDistributionType.AUTOMATIC.name()); - LocalQueryRunner queryRunner = LocalQueryRunner.builder(sessionBuilder.build()) - .withNodeCountForStats(8) - .build(); - queryRunner.createCatalog( - CATALOG_NAME, - createConnectorFactory(), - ImmutableMap.of()); - return queryRunner; - } - - protected abstract ConnectorFactory createConnectorFactory(); - - @BeforeClass - public abstract void prepareTables() - throws Exception; - - protected abstract List getQueryResourcePaths(); - - @Test - public void test() - { - for (String queryResourcePath : getQueryResourcePaths()) { - assertEquals(generateQueryPlan(readQuery(queryResourcePath)), read(getQueryPlanResourcePath(queryResourcePath))); - } - } - - private String getQueryPlanResourcePath(String queryResourcePath) - { - Path queryPath = Paths.get(queryResourcePath); - String connectorName = getQueryRunner().getCatalogManager().getCatalog(CATALOG_NAME).orElseThrow().getConnectorName().toString(); - Path directory = queryPath.getParent(); - directory = directory.resolve(connectorName + (smallFiles ? "_small_files" : "")); - if (fileFormatName.isPresent()) { - directory = directory.resolve(fileFormatName.get()); - } - directory = directory.resolve(partitioned ? "partitioned" : "unpartitioned"); - String planResourceName = queryPath.getFileName().toString().replaceAll("\\.sql$", ".plan.txt"); - return directory.resolve(planResourceName).toString(); - } - - protected void generate() - { - initPlanTest(); - try { - prepareTables(); - getQueryResourcePaths().stream() - .parallel() - .forEach(queryResourcePath -> { - try { - Path queryPlanWritePath = Paths.get( - getSourcePath().toString(), - "src/test/resources", - getQueryPlanResourcePath(queryResourcePath)); - createParentDirs(queryPlanWritePath.toFile()); - write(generateQueryPlan(readQuery(queryResourcePath)).getBytes(UTF_8), queryPlanWritePath.toFile()); - log.info("Generated expected plan for query: %s", queryResourcePath); - } - catch (IOException e) { - throw new UncheckedIOException(e); - } - }); - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new RuntimeException("Interrupted", e); - } - catch (Exception e) { - throw new RuntimeException(e); - } - finally { - destroyPlanTest(); - } - } - - public static String readQuery(String resource) - { - return read(resource).replaceAll("\\s+;\\s+$", "") - .replace("${database}.${schema}.", "") - .replace("\"${database}\".\"${schema}\".\"${prefix}", "\"") - .replace("${scale}", "1"); - } - - private static String read(String resource) - { - try { - return Resources.toString(getResource(BaseCostBasedPlanTest.class, resource), UTF_8); - } - catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - private String generateQueryPlan(String query) - { - try { - return getQueryRunner().inTransaction(transactionSession -> { - LocalQueryRunner localQueryRunner = getQueryRunner(); - Plan plan = localQueryRunner.createPlan(transactionSession, query, localQueryRunner.getPlanOptimizers(false), OPTIMIZED_AND_VALIDATED, NOOP, createPlanOptimizersStatsCollector()); - JoinOrderPrinter joinOrderPrinter = new JoinOrderPrinter(transactionSession); - plan.getRoot().accept(joinOrderPrinter, 0); - return joinOrderPrinter.result(); - }); - } - catch (RuntimeException e) { - throw new AssertionError("Planning failed for SQL: " + query, e); - } - } - - protected Path getSourcePath() - { - Path workingDir = Paths.get(System.getProperty("user.dir")); - verify(isDirectory(workingDir), "Working directory is not a directory"); - if (isDirectory(workingDir.resolve(".git"))) { - // Top-level of the repo - return workingDir.resolve("testing/trino-tests"); - } - if (workingDir.getFileName().toString().equals("trino-tests")) { - return workingDir; - } - throw new IllegalStateException("This class must be executed from trino-tests or Trino source directory"); - } - - private class JoinOrderPrinter - extends SimplePlanVisitor - { - private final Session session; - private final StringBuilder result = new StringBuilder(); - - public JoinOrderPrinter(Session session) - { - this.session = requireNonNull(session, "session is null"); - } - - public String result() - { - return result.toString(); - } - - @Override - public Void visitJoin(JoinNode node, Integer indent) - { - JoinNode.DistributionType distributionType = node.getDistributionType() - .orElseThrow(() -> new VerifyException("Expected distribution type to be set")); - if (node.isCrossJoin()) { - checkState(node.getType() == INNER && distributionType == REPLICATED, "Expected CROSS JOIN to be INNER REPLICATED"); - if (node.isMaySkipOutputDuplicates()) { - output(indent, "cross join (can skip output duplicates):"); - } - else { - output(indent, "cross join:"); - } - } - else { - if (node.isMaySkipOutputDuplicates()) { - output(indent, "join (%s, %s, can skip output duplicates):", node.getType(), distributionType); - } - else { - output(indent, "join (%s, %s):", node.getType(), distributionType); - } - } - - return visitPlan(node, indent + 1); - } - - @Override - public Void visitExchange(ExchangeNode node, Integer indent) - { - Partitioning partitioning = node.getPartitioningScheme().getPartitioning(); - output( - indent, - "%s exchange (%s, %s, %s)", - node.getScope().name().toLowerCase(ENGLISH), - node.getType(), - partitioning.getHandle(), - partitioning.getArguments().stream() - .map(Object::toString) - .sorted() // Currently, order of hash columns is not deterministic - .collect(joining(", ", "[", "]"))); - - return visitPlan(node, indent + 1); - } - - @Override - public Void visitAggregation(AggregationNode node, Integer indent) - { - output( - indent, - "%s aggregation over (%s)", - node.getStep().name().toLowerCase(ENGLISH), - node.getGroupingKeys().stream() - .map(Object::toString) - .sorted() - .collect(joining(", "))); - - return visitPlan(node, indent + 1); - } - - @Override - public Void visitFilter(FilterNode node, Integer indent) - { - DynamicFilters.ExtractResult filters = extractDynamicFilters(node.getPredicate()); - String inputs = filters.getDynamicConjuncts().stream() - .map(descriptor -> descriptor.getInput().toString()) - .sorted() - .collect(joining(", ")); - - if (!inputs.isEmpty()) { - output(indent, "dynamic filter ([%s])", inputs); - indent = indent + 1; - } - return visitPlan(node, indent); - } - - @Override - public Void visitTableScan(TableScanNode node, Integer indent) - { - CatalogSchemaTableName tableName = getQueryRunner().getMetadata().getTableName(session, node.getTable()); - output(indent, "scan %s", tableName.getSchemaTableName().getTableName()); - - return null; - } - - @Override - public Void visitSemiJoin(SemiJoinNode node, Integer indent) - { - output(indent, "semijoin (%s):", node.getDistributionType().get()); - - return visitPlan(node, indent + 1); - } - - @Override - public Void visitValues(ValuesNode node, Integer indent) - { - output(indent, "values (%s rows)", node.getRowCount()); - - return null; - } - - private void output(int indent, String message, Object... args) - { - String formattedMessage = format(message, args); - result.append(format("%s%s\n", " ".repeat(indent), formattedMessage)); - } - } -} diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/BaseHiveCostBasedPlanTest.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/BaseHiveCostBasedPlanTest.java deleted file mode 100644 index c627ac63fe83..000000000000 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/BaseHiveCostBasedPlanTest.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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 io.trino.sql.planner; - -import io.trino.plugin.hive.RecordingMetastoreConfig; -import io.trino.plugin.hive.TestingHiveConnectorFactory; -import io.trino.plugin.hive.metastore.UnimplementedHiveMetastore; -import io.trino.plugin.hive.metastore.recording.HiveMetastoreRecording; -import io.trino.plugin.hive.metastore.recording.RecordingHiveMetastore; -import io.trino.spi.connector.ConnectorFactory; -import org.testng.annotations.BeforeClass; - -import java.io.File; -import java.net.URL; -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.Optional; - -import static com.google.common.collect.MoreCollectors.onlyElement; -import static io.trino.plugin.hive.metastore.recording.TestRecordingHiveMetastore.createJsonCodec; -import static java.util.Objects.requireNonNull; - -public abstract class BaseHiveCostBasedPlanTest - extends BaseCostBasedPlanTest -{ - private final String metadataDir; - - protected BaseHiveCostBasedPlanTest(String metadataDir, boolean partitioned) - { - super( - getSchema(metadataDir), - // In case of Hive connector, query plans do not currently depend on file format - Optional.empty(), - partitioned); - this.metadataDir = requireNonNull(metadataDir, "metadataDir is null"); - } - - @Override - protected ConnectorFactory createConnectorFactory() - { - RecordingMetastoreConfig recordingConfig = new RecordingMetastoreConfig() - .setRecordingPath(getRecordingPath(metadataDir)) - .setReplay(true); - // The RecordingHiveMetastore loads the metadata files generated through HiveMetadataRecorder - // which essentially helps to generate the optimal query plans for validation purposes. These files - // contain all the metadata including statistics. - RecordingHiveMetastore metastore = new RecordingHiveMetastore( - new UnimplementedHiveMetastore(), - new HiveMetastoreRecording(recordingConfig, createJsonCodec())); - return new TestingHiveConnectorFactory(metastore); - } - - private static String getSchema(String metadataDir) - { - String fileName = Paths.get(getRecordingPath(metadataDir)).getFileName().toString(); - return fileName.split("\\.")[0]; - } - - @Override - @BeforeClass - public void prepareTables() - { - // Nothing to do. Tables are populated using the recording. - } - - private static String getRecordingPath(String metadataDir) - { - URL resource = BaseHiveCostBasedPlanTest.class.getResource(metadataDir); - if (resource == null) { - throw new RuntimeException("Hive metadata directory doesn't exist: " + metadataDir); - } - - File[] files = new File(resource.getPath()).listFiles(); - if (files == null) { - throw new RuntimeException("Hive metadata recording file doesn't exist in directory: " + metadataDir); - } - - return Arrays.stream(files) - .filter(f -> !f.isDirectory()) - .collect(onlyElement()) - .getPath(); - } -} diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/BaseIcebergCostBasedPlanTest.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/BaseIcebergCostBasedPlanTest.java deleted file mode 100644 index c45d687dd611..000000000000 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/BaseIcebergCostBasedPlanTest.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * 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 io.trino.sql.planner; - -import com.google.common.collect.ImmutableMap; -import com.google.errorprone.annotations.concurrent.GuardedBy; -import io.airlift.log.Logger; -import io.trino.plugin.hive.metastore.Database; -import io.trino.plugin.hive.metastore.HiveMetastore; -import io.trino.plugin.hive.metastore.HiveMetastoreFactory; -import io.trino.plugin.hive.metastore.Table; -import io.trino.plugin.iceberg.IcebergConnector; -import io.trino.plugin.iceberg.IcebergConnectorFactory; -import io.trino.spi.connector.Connector; -import io.trino.spi.connector.ConnectorContext; -import io.trino.spi.connector.ConnectorFactory; -import io.trino.testing.containers.Minio; -import io.trino.testing.minio.MinioClient; -import io.trino.testng.services.ManageTestResources; -import org.testng.annotations.AfterClass; -import org.testng.annotations.AfterSuite; -import org.testng.annotations.BeforeClass; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Path; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Verify.verify; -import static com.google.common.collect.MoreCollectors.onlyElement; -import static com.google.common.io.MoreFiles.deleteRecursively; -import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; -import static io.trino.plugin.hive.TableType.EXTERNAL_TABLE; -import static io.trino.plugin.hive.metastore.PrincipalPrivileges.NO_PRIVILEGES; -import static io.trino.plugin.iceberg.CatalogType.TESTING_FILE_METASTORE; -import static io.trino.plugin.iceberg.IcebergConfig.EXTENDED_STATISTICS_CONFIG; -import static io.trino.plugin.iceberg.IcebergUtil.METADATA_FILE_EXTENSION; -import static io.trino.plugin.iceberg.catalog.AbstractIcebergTableOperations.ICEBERG_METASTORE_STORAGE_FORMAT; -import static io.trino.testing.containers.Minio.MINIO_ACCESS_KEY; -import static io.trino.testing.containers.Minio.MINIO_REGION; -import static io.trino.testing.containers.Minio.MINIO_SECRET_KEY; -import static java.nio.file.Files.createTempDirectory; -import static java.util.Locale.ENGLISH; -import static org.apache.iceberg.BaseMetastoreTableOperations.ICEBERG_TABLE_TYPE_VALUE; -import static org.apache.iceberg.BaseMetastoreTableOperations.METADATA_LOCATION_PROP; -import static org.apache.iceberg.BaseMetastoreTableOperations.TABLE_TYPE_PROP; - -public abstract class BaseIcebergCostBasedPlanTest - extends BaseCostBasedPlanTest -{ - private static final Logger log = Logger.get(BaseIcebergCostBasedPlanTest.class); - - // Iceberg metadata files are linked using absolute paths, so the bucket name must match where the metadata was exported from. - // See more at https://github.com/apache/iceberg/issues/1617 - private static final String BUCKET_NAME = "starburst-benchmarks-data"; - - // The container needs to be shared, since bucket name cannot be reused between tests. - // The bucket name is used as a key in TrinoFileSystemCache which is managed in static manner. - @GuardedBy("sharedMinioLock") - @ManageTestResources.Suppress(because = "This resource is leaked, but consciously -- there is no known way to avoid that") - private static Minio sharedMinio; - private static final Object sharedMinioLock = new Object(); - - protected Minio minio; - private Path temporaryMetastoreDirectory; - private HiveMetastore hiveMetastore; - private Map connectorConfiguration; - - protected BaseIcebergCostBasedPlanTest(String schemaName, String fileFormatName, boolean partitioned) - { - super(schemaName, Optional.of(fileFormatName), partitioned); - } - - protected BaseIcebergCostBasedPlanTest(String schemaName, String fileFormatName, boolean partitioned, boolean smallFiles) - { - super(schemaName, Optional.of(fileFormatName), partitioned, smallFiles); - } - - @Override - protected ConnectorFactory createConnectorFactory() - { - synchronized (sharedMinioLock) { - if (sharedMinio == null) { - Minio minio = Minio.builder().build(); - minio.start(); - minio.createBucket(BUCKET_NAME); - sharedMinio = minio; - } - minio = sharedMinio; - } - - try { - temporaryMetastoreDirectory = createTempDirectory("file-metastore"); - } - catch (IOException e) { - throw new UncheckedIOException(e); - } - - connectorConfiguration = ImmutableMap.builder() - .put("iceberg.catalog.type", TESTING_FILE_METASTORE.name()) - .put("hive.metastore.catalog.dir", temporaryMetastoreDirectory.toString()) - .put("fs.native-s3.enabled", "true") - .put("s3.aws-access-key", MINIO_ACCESS_KEY) - .put("s3.aws-secret-key", MINIO_SECRET_KEY) - .put("s3.region", MINIO_REGION) - .put("s3.endpoint", minio.getMinioAddress()) - .put("s3.path-style-access", "true") - .put(EXTENDED_STATISTICS_CONFIG, "true") - .put("bootstrap.quiet", "true") - .buildOrThrow(); - - return new IcebergConnectorFactory() - { - @Override - public Connector create(String catalogName, Map config, ConnectorContext context) - { - checkArgument(config.isEmpty(), "Unexpected configuration %s", config); - Connector connector = super.create(catalogName, connectorConfiguration, context); - hiveMetastore = ((IcebergConnector) connector).getInjector() - .getInstance(HiveMetastoreFactory.class) - .createMetastore(Optional.empty()); - return connector; - } - }; - } - - @Override - @BeforeClass - public void prepareTables() - { - String schema = getQueryRunner().getDefaultSession().getSchema().orElseThrow(); - hiveMetastore.createDatabase( - Database.builder() - .setDatabaseName(schema) - .setOwnerName(Optional.empty()) - .setOwnerType(Optional.empty()) - .build()); - doPrepareTables(); - } - - protected abstract void doPrepareTables(); - - // Iceberg metadata files are linked using absolute paths, so the path within the bucket name must match where the metadata was exported from. - protected void populateTableFromResource(String tableName, String resourcePath, String targetPath) - { - String schema = getQueryRunner().getDefaultSession().getSchema().orElseThrow(); - - log.info("Copying resources for %s unpartitioned table from %s to %s in the container", tableName, resourcePath, targetPath); - minio.copyResources(resourcePath, BUCKET_NAME, targetPath); - - String tableLocation = "s3://%s/%s".formatted(BUCKET_NAME, targetPath); - String metadataLocation; - try (MinioClient minioClient = minio.createMinioClient()) { - String metadataPath = minioClient.listObjects(BUCKET_NAME, targetPath + "/metadata/").stream() - .filter(path -> path.endsWith(METADATA_FILE_EXTENSION)) - .collect(onlyElement()); - metadataLocation = "s3://%s/%s".formatted(BUCKET_NAME, metadataPath); - } - - log.info("Registering table %s using metadata location %s", tableName, metadataLocation); - hiveMetastore.createTable( - Table.builder() - .setDatabaseName(schema) - .setTableName(tableName) - .setOwner(Optional.empty()) - .setTableType(EXTERNAL_TABLE.name()) - .setDataColumns(List.of()) - .withStorage(storage -> storage.setLocation(tableLocation)) - .withStorage(storage -> storage.setStorageFormat(ICEBERG_METASTORE_STORAGE_FORMAT)) - // This is a must-have property for the EXTERNAL_TABLE table type - .setParameter("EXTERNAL", "TRUE") - .setParameter(TABLE_TYPE_PROP, ICEBERG_TABLE_TYPE_VALUE.toUpperCase(ENGLISH)) - .setParameter(METADATA_LOCATION_PROP, metadataLocation) - .build(), - NO_PRIVILEGES); - } - - @AfterClass(alwaysRun = true) - public void cleanUp() - throws Exception - { - if (minio != null) { - // Don't stop container, as it's shared - synchronized (sharedMinioLock) { - verify(minio == sharedMinio); - } - minio = null; - } - - if (temporaryMetastoreDirectory != null) { - deleteRecursively(temporaryMetastoreDirectory, ALLOW_INSECURE); - } - - hiveMetastore = null; - connectorConfiguration = null; - } - - @AfterSuite(alwaysRun = true) - public static void disposeSharedResources() - { - synchronized (sharedMinioLock) { - if (sharedMinio != null) { - sharedMinio.stop(); - sharedMinio = null; - } - } - } -} diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/HiveMetadataRecorder.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/HiveMetadataRecorder.java deleted file mode 100644 index 5791e87af4dd..000000000000 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/HiveMetadataRecorder.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * 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 io.trino.sql.planner; - -import com.google.common.collect.ImmutableMap; -import io.trino.Session; -import io.trino.plugin.hive.TestingHivePlugin; -import io.trino.testing.DistributedQueryRunner; - -import java.nio.file.Path; -import java.util.List; -import java.util.Map; - -import static com.google.common.collect.ImmutableList.toImmutableList; -import static io.airlift.configuration.ConfigurationLoader.loadPropertiesFrom; -import static io.airlift.testing.Closeables.closeAllRuntimeException; -import static io.trino.sql.planner.TestHivePartitionedTpcdsCostBasedPlan.PARTITIONED_TPCDS_METADATA_DIR; -import static io.trino.sql.planner.TestHivePartitionedTpchCostBasedPlan.PARTITIONED_TPCH_METADATA_DIR; -import static io.trino.sql.planner.TestHiveTpcdsCostBasedPlan.TPCDS_METADATA_DIR; -import static io.trino.sql.planner.TestHiveTpcdsCostBasedPlan.TPCDS_SQL_FILES; -import static io.trino.sql.planner.TestHiveTpchCostBasedPlan.TPCH_METADATA_DIR; -import static io.trino.sql.planner.TestHiveTpchCostBasedPlan.TPCH_SQL_FILES; -import static io.trino.testing.TestingSession.testSessionBuilder; -import static java.lang.String.format; -import static java.util.Objects.requireNonNull; - -/** - * This class helps to generate the gzip metadata files for TPCH/TPCDS partitioned and unpartitioned data. - * These metadata files contains statistics which is being used by cost based plan tests for plan generation. - * To generate these gzip metadata files, this class uses RecordingHiveMetastore which records the hive metastore - * requests that can be used later, instead of live metastore. - */ -public class HiveMetadataRecorder - implements AutoCloseable -{ - private DistributedQueryRunner queryRunner; - - private HiveMetadataRecorder(String schema, String configPath, String recordingDir) - throws Exception - { - requireNonNull(schema, "schema is null"); - requireNonNull(configPath, "configPath is null"); - requireNonNull(recordingDir, "recordingDir is null"); - - queryRunner = createQueryRunner(schema, configPath, recordingDir); - } - - private void record(List queries) - { - queries.forEach(query -> queryRunner.execute("EXPLAIN " + query)); - // Save the recording cache - queryRunner.execute("CALL system.write_hive_metastore_recording()"); - } - - private static DistributedQueryRunner createQueryRunner(String schema, String configPath, String recordingDir) - throws Exception - { - ImmutableMap.Builder configBuilder = ImmutableMap.builder(); - Session session = testSessionBuilder() - .setCatalog("hive") - .setSchema(schema) - .build(); - DistributedQueryRunner queryRunner = DistributedQueryRunner.builder(session) - .build(); - - String recordingPath = getResourcePath(format("%s/%s.json.gz", recordingDir, schema)); - Path.of(recordingPath).toFile().getParentFile().mkdirs(); - - queryRunner.installPlugin(new TestingHivePlugin()); - queryRunner.createCatalog("hive", "hive", configBuilder - .putAll(loadPropertiesFrom(configPath)) - .put("hive.metastore-recording-path", recordingPath) - .put("hive.metastore-recording-duration", "2h") - .buildOrThrow()); - return queryRunner; - } - - private static String getResourcePath(String relativePath) - { - return Path.of("testing/trino-tests/src/test/resources", relativePath).toString(); - } - - @Override - public void close() - { - closeAllRuntimeException(queryRunner); - queryRunner = null; - } - - public static void main(String[] args) - throws Exception - { - recordMetadata( - ImmutableMap.of( - TPCH_METADATA_DIR, "tpch_sf1000_orc", - PARTITIONED_TPCH_METADATA_DIR, "tpch_sf1000_orc_part"), - TPCH_SQL_FILES.stream() - .map(BaseHiveCostBasedPlanTest::readQuery) - .collect(toImmutableList())); - recordMetadata( - ImmutableMap.of( - TPCDS_METADATA_DIR, "tpcds_sf1000_orc", - PARTITIONED_TPCDS_METADATA_DIR, "tpcds_sf1000_orc_part"), - TPCDS_SQL_FILES.stream() - .map(BaseHiveCostBasedPlanTest::readQuery) - .collect(toImmutableList())); - } - - private static void recordMetadata(Map recordingDirToSchema, List queries) - throws Exception - { - String configPath = getResourcePath("hive.properties"); - for (String recordingDir : recordingDirToSchema.keySet()) { - String schema = recordingDirToSchema.get(recordingDir); - try (HiveMetadataRecorder recorder = new HiveMetadataRecorder(schema, configPath, recordingDir)) { - recorder.record(queries); - } - } - } -} diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestHivePartitionedTpcdsCostBasedPlan.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestHivePartitionedTpcdsCostBasedPlan.java deleted file mode 100644 index 3d8c8b17131a..000000000000 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestHivePartitionedTpcdsCostBasedPlan.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 io.trino.sql.planner; - -import java.util.List; - -/** - * This class tests cost-based optimization rules related to joins. It contains unmodified TPC-DS queries. - * This class is using Hive connector with mocked in memory thrift metastore with partitioned TPC-DS tables. - */ -public class TestHivePartitionedTpcdsCostBasedPlan - extends BaseHiveCostBasedPlanTest -{ - /* - * CAUTION: The expected plans here are not necessarily optimal yet. Their role is to prevent - * inadvertent regressions. A conscious improvement to the planner may require changing some - * of the expected plans, but any such change should be verified on an actual cluster with - * large amount of data. - */ - - public static final String PARTITIONED_TPCDS_METADATA_DIR = "/hive_metadata/partitioned_tpcds"; - - public TestHivePartitionedTpcdsCostBasedPlan() - { - super(PARTITIONED_TPCDS_METADATA_DIR, true); - } - - @Override - protected List getQueryResourcePaths() - { - return TPCDS_SQL_FILES; - } - - public static void main(String[] args) - { - new TestHivePartitionedTpcdsCostBasedPlan().generate(); - } -} diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestHivePartitionedTpchCostBasedPlan.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestHivePartitionedTpchCostBasedPlan.java deleted file mode 100644 index 71bdba29f709..000000000000 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestHivePartitionedTpchCostBasedPlan.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 io.trino.sql.planner; - -import java.util.List; - -/** - * This class tests cost-based optimization rules related to joins. It contains unmodified TPC-H queries. - * This class is using Hive connector with mocked in memory thrift metastore with partitioned TPC-H tables. - */ -public class TestHivePartitionedTpchCostBasedPlan - extends BaseHiveCostBasedPlanTest -{ - /* - * CAUTION: The expected plans here are not necessarily optimal yet. Their role is to prevent - * inadvertent regressions. A conscious improvement to the planner may require changing some - * of the expected plans, but any such change should be verified on an actual cluster with - * large amount of data. - */ - - public static final String PARTITIONED_TPCH_METADATA_DIR = "/hive_metadata/partitioned_tpch"; - - public TestHivePartitionedTpchCostBasedPlan() - { - super(PARTITIONED_TPCH_METADATA_DIR, true); - } - - @Override - protected List getQueryResourcePaths() - { - return TPCH_SQL_FILES; - } - - public static void main(String[] args) - { - new TestHivePartitionedTpchCostBasedPlan().generate(); - } -} diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestHiveTpcdsCostBasedPlan.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestHiveTpcdsCostBasedPlan.java deleted file mode 100644 index 1ac2e7b7d40c..000000000000 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestHiveTpcdsCostBasedPlan.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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 io.trino.sql.planner; - -import java.util.List; - -/** - * This class tests cost-based optimization rules related to joins. It contains unmodified TPC-DS queries. - * This class is using Hive connector with mocked in memory thrift metastore with unpartitioned TPC-DS tables. - */ -public class TestHiveTpcdsCostBasedPlan - extends BaseHiveCostBasedPlanTest -{ - /* - * CAUTION: The expected plans here are not necessarily optimal yet. Their role is to prevent - * inadvertent regressions. A conscious improvement to the planner may require changing some - * of the expected plans, but any such change should be verified on an actual cluster with - * large amount of data. - */ - - public static final String TPCDS_METADATA_DIR = "/hive_metadata/unpartitioned_tpcds"; - - public TestHiveTpcdsCostBasedPlan() - { - super(TPCDS_METADATA_DIR, false); - } - - @Override - protected List getQueryResourcePaths() - { - return TPCDS_SQL_FILES; - } - - public static void main(String[] args) - { - new TestHiveTpcdsCostBasedPlan().generate(); - } -} diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestHiveTpchCostBasedPlan.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestHiveTpchCostBasedPlan.java deleted file mode 100644 index a9c152ecbedb..000000000000 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestHiveTpchCostBasedPlan.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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 io.trino.sql.planner; - -import java.util.List; - -/** - * This class tests cost-based optimization rules related to joins. It contains unmodified TPC-H queries. - * This class is using Hive connector with mocked in memory thrift metastore with unpartitioned TPC-H tables. - */ -public class TestHiveTpchCostBasedPlan - extends BaseHiveCostBasedPlanTest -{ - /* - * CAUTION: The expected plans here are not necessarily optimal yet. Their role is to prevent - * inadvertent regressions. A conscious improvement to the planner may require changing some - * of the expected plans, but any such change should be verified on an actual cluster with - * large amount of data. - */ - - public static final String TPCH_METADATA_DIR = "/hive_metadata/unpartitioned_tpch"; - - public TestHiveTpchCostBasedPlan() - { - super(TPCH_METADATA_DIR, false); - } - - @Override - protected List getQueryResourcePaths() - { - return TPCH_SQL_FILES; - } - - public static void main(String[] args) - { - new TestHiveTpchCostBasedPlan().generate(); - } -} diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcPartitionedTpcdsCostBasedPlan.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcPartitionedTpcdsCostBasedPlan.java deleted file mode 100644 index f2b90868ae6c..000000000000 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcPartitionedTpcdsCostBasedPlan.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 io.trino.sql.planner; - -import java.util.List; - -/** - * This class tests cost-based optimization rules related to joins. It contains unmodified TPC-DS queries. - * This class is using Iceberg connector un-partitioned TPC-DS tables. - */ -public class TestIcebergOrcPartitionedTpcdsCostBasedPlan - extends BaseIcebergCostBasedPlanTest -{ - public TestIcebergOrcPartitionedTpcdsCostBasedPlan() - { - super("tpcds_sf1000_orc_part", "orc", true); - } - - @Override - protected void doPrepareTables() - { - io.trino.tpcds.Table.getBaseTables().forEach(table -> { - if (table == io.trino.tpcds.Table.DBGEN_VERSION) { - return; - } - populateTableFromResource( - table.getName(), - "iceberg/tpcds/sf1000/orc/partitioned/" + table.getName(), - "iceberg-tpcds-sf1000-orc-part/" + table.getName()); - }); - } - - @Override - protected List getQueryResourcePaths() - { - return TPCDS_SQL_FILES; - } - - public static void main(String[] args) - { - new TestIcebergOrcPartitionedTpcdsCostBasedPlan().generate(); - } -} diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcPartitionedTpchCostBasedPlan.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcPartitionedTpchCostBasedPlan.java deleted file mode 100644 index 2375352bab2d..000000000000 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcPartitionedTpchCostBasedPlan.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 io.trino.sql.planner; - -import io.trino.tpch.TpchTable; - -import java.util.List; - -/** - * This class tests cost-based optimization rules related to joins. It contains unmodified TPC-H queries. - * This class is using Iceberg connector un-partitioned TPC-H tables. - */ -public class TestIcebergOrcPartitionedTpchCostBasedPlan - extends BaseIcebergCostBasedPlanTest -{ - public TestIcebergOrcPartitionedTpchCostBasedPlan() - { - super("tpch_sf1000_orc_part", "orc", true); - } - - @Override - protected void doPrepareTables() - { - TpchTable.getTables().forEach(table -> { - populateTableFromResource( - table.getTableName(), - "iceberg/tpch/sf1000/orc/partitioned/" + table.getTableName(), - "iceberg-tpch-sf1000-ORC-part/" + table.getTableName()); - }); - } - - @Override - protected List getQueryResourcePaths() - { - return TPCH_SQL_FILES; - } - - public static void main(String[] args) - { - new TestIcebergOrcPartitionedTpchCostBasedPlan().generate(); - } -} diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcTpcdsCostBasedPlan.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcTpcdsCostBasedPlan.java deleted file mode 100644 index a4fc1847d0db..000000000000 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcTpcdsCostBasedPlan.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 io.trino.sql.planner; - -import java.util.List; - -/** - * This class tests cost-based optimization rules related to joins. It contains unmodified TPC-DS queries. - * This class is using Iceberg connector unpartitioned TPC-DS tables. - */ -public class TestIcebergOrcTpcdsCostBasedPlan - extends BaseIcebergCostBasedPlanTest -{ - public TestIcebergOrcTpcdsCostBasedPlan() - { - super("tpcds_sf1000_orc", "orc", false); - } - - @Override - protected void doPrepareTables() - { - io.trino.tpcds.Table.getBaseTables().forEach(table -> { - if (table == io.trino.tpcds.Table.DBGEN_VERSION) { - return; - } - populateTableFromResource( - table.getName(), - "iceberg/tpcds/sf1000/orc/unpartitioned/" + table.getName(), - "iceberg-tpcds-sf1000-orc/" + table.getName()); - }); - } - - @Override - protected List getQueryResourcePaths() - { - return TPCDS_SQL_FILES; - } - - public static void main(String[] args) - { - new TestIcebergOrcTpcdsCostBasedPlan().generate(); - } -} diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcTpchCostBasedPlan.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcTpchCostBasedPlan.java deleted file mode 100644 index bee3ce48b1f9..000000000000 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergOrcTpchCostBasedPlan.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 io.trino.sql.planner; - -import io.trino.tpch.TpchTable; - -import java.util.List; - -/** - * This class tests cost-based optimization rules related to joins. It contains unmodified TPC-H queries. - * This class is using Iceberg connector unpartitioned TPC-H tables. - */ -public class TestIcebergOrcTpchCostBasedPlan - extends BaseIcebergCostBasedPlanTest -{ - public TestIcebergOrcTpchCostBasedPlan() - { - super("tpch_sf1000_orc", "orc", false); - } - - @Override - protected void doPrepareTables() - { - TpchTable.getTables().forEach(table -> { - populateTableFromResource( - table.getTableName(), - "iceberg/tpch/sf1000/orc/unpartitioned/" + table.getTableName(), - "iceberg-tpch-sf1000-ORC/" + table.getTableName()); - }); - } - - @Override - protected List getQueryResourcePaths() - { - return TPCH_SQL_FILES; - } - - public static void main(String[] args) - { - new TestIcebergOrcTpchCostBasedPlan().generate(); - } -} diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetPartitionedTpcdsCostBasedPlan.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetPartitionedTpcdsCostBasedPlan.java deleted file mode 100644 index 3b5d48f5f623..000000000000 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetPartitionedTpcdsCostBasedPlan.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 io.trino.sql.planner; - -import java.util.List; - -/** - * This class tests cost-based optimization rules related to joins. It contains unmodified TPC-DS queries. - * This class is using Iceberg connector un-partitioned TPC-DS tables. - */ -public class TestIcebergParquetPartitionedTpcdsCostBasedPlan - extends BaseIcebergCostBasedPlanTest -{ - protected TestIcebergParquetPartitionedTpcdsCostBasedPlan() - { - super("tpcds_sf1000_parquet_part", "parquet", true); - } - - @Override - protected void doPrepareTables() - { - io.trino.tpcds.Table.getBaseTables().forEach(table -> { - if (table == io.trino.tpcds.Table.DBGEN_VERSION) { - return; - } - populateTableFromResource( - table.getName(), - "iceberg/tpcds/sf1000/parquet/partitioned/" + table.getName(), - "iceberg-tpcds-sf1000-parquet-part/" + table.getName()); - }); - } - - @Override - protected List getQueryResourcePaths() - { - return TPCDS_SQL_FILES; - } - - public static void main(String[] args) - { - new TestIcebergParquetPartitionedTpcdsCostBasedPlan().generate(); - } -} diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetPartitionedTpchCostBasedPlan.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetPartitionedTpchCostBasedPlan.java deleted file mode 100644 index 994571d11ea6..000000000000 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetPartitionedTpchCostBasedPlan.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 io.trino.sql.planner; - -import io.trino.tpch.TpchTable; - -import java.util.List; - -/** - * This class tests cost-based optimization rules related to joins. It contains unmodified TPC-H queries. - * This class is using Iceberg connector un-partitioned TPC-H tables. - */ -public class TestIcebergParquetPartitionedTpchCostBasedPlan - extends BaseIcebergCostBasedPlanTest -{ - public TestIcebergParquetPartitionedTpchCostBasedPlan() - { - super("tpch_sf1000_parquet_part", "parquet", true); - } - - @Override - protected void doPrepareTables() - { - TpchTable.getTables().forEach(table -> { - populateTableFromResource( - table.getTableName(), - "iceberg/tpch/sf1000/parquet/partitioned/" + table.getTableName(), - "iceberg-tpch-sf1000-parquet-part/" + table.getTableName()); - }); - } - - @Override - protected List getQueryResourcePaths() - { - return TPCH_SQL_FILES; - } - - public static void main(String[] args) - { - new TestIcebergParquetPartitionedTpchCostBasedPlan().generate(); - } -} diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetTpcdsCostBasedPlan.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetTpcdsCostBasedPlan.java deleted file mode 100644 index d64295fc7e47..000000000000 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetTpcdsCostBasedPlan.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 io.trino.sql.planner; - -import java.util.List; - -/** - * This class tests cost-based optimization rules related to joins. It contains unmodified TPC-DS queries. - * This class is using Iceberg connector unpartitioned TPC-DS tables. - */ -public class TestIcebergParquetTpcdsCostBasedPlan - extends BaseIcebergCostBasedPlanTest -{ - public TestIcebergParquetTpcdsCostBasedPlan() - { - super("tpcds_sf1000_parquet", "parquet", false); - } - - @Override - protected void doPrepareTables() - { - io.trino.tpcds.Table.getBaseTables().forEach(table -> { - if (table == io.trino.tpcds.Table.DBGEN_VERSION) { - return; - } - populateTableFromResource( - table.getName(), - "iceberg/tpcds/sf1000/parquet/unpartitioned/" + table.getName(), - "iceberg-tpcds-sf1000-parquet/" + table.getName()); - }); - } - - @Override - protected List getQueryResourcePaths() - { - return TPCDS_SQL_FILES; - } - - public static void main(String[] args) - { - new TestIcebergParquetTpcdsCostBasedPlan().generate(); - } -} diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetTpchCostBasedPlan.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetTpchCostBasedPlan.java deleted file mode 100644 index a8de1f470d7e..000000000000 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergParquetTpchCostBasedPlan.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 io.trino.sql.planner; - -import io.trino.tpch.TpchTable; - -import java.util.List; - -/** - * This class tests cost-based optimization rules related to joins. It contains unmodified TPC-H queries. - * This class is using Iceberg connector unpartitioned TPC-H tables. - */ -public class TestIcebergParquetTpchCostBasedPlan - extends BaseIcebergCostBasedPlanTest -{ - public TestIcebergParquetTpchCostBasedPlan() - { - super("tpch_sf1000_parquet", "parquet", false); - } - - @Override - protected void doPrepareTables() - { - TpchTable.getTables().forEach(table -> { - populateTableFromResource( - table.getTableName(), - "iceberg/tpch/sf1000/parquet/unpartitioned/" + table.getTableName(), - "iceberg-tpch-sf1000-parquet/" + table.getTableName()); - }); - } - - @Override - protected List getQueryResourcePaths() - { - return TPCH_SQL_FILES; - } - - public static void main(String[] args) - { - new TestIcebergParquetTpchCostBasedPlan().generate(); - } -} diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergSmallFilesParquetTpcdsCostBasedPlan.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergSmallFilesParquetTpcdsCostBasedPlan.java deleted file mode 100644 index 3e9633fff937..000000000000 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/TestIcebergSmallFilesParquetTpcdsCostBasedPlan.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 io.trino.sql.planner; - -import java.util.List; - -/** - * This class tests cost-based optimization rules related to joins. It contains unmodified TPC-DS queries. - * This class is using Iceberg connector unpartitioned TPC-DS tables. - */ -public class TestIcebergSmallFilesParquetTpcdsCostBasedPlan - extends BaseIcebergCostBasedPlanTest -{ - public TestIcebergSmallFilesParquetTpcdsCostBasedPlan() - { - super("tpcds_50mb_files_sf1000_parquet", "parquet", false, true); - } - - @Override - protected void doPrepareTables() - { - io.trino.tpcds.Table.getBaseTables().forEach(table -> { - if (table == io.trino.tpcds.Table.DBGEN_VERSION) { - return; - } - populateTableFromResource( - table.getName(), - "iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/" + table.getName(), - "iceberg-50MB-files-tpcds-sf1000-PARQUET/" + table.getName()); - }); - } - - @Override - protected List getQueryResourcePaths() - { - return TPCDS_SQL_FILES; - } - - public static void main(String[] args) - { - new TestIcebergSmallFilesParquetTpcdsCostBasedPlan().generate(); - } -} diff --git a/testing/trino-tests/src/test/java/io/trino/sql/planner/UpdateExpectedPlans.java b/testing/trino-tests/src/test/java/io/trino/sql/planner/UpdateExpectedPlans.java deleted file mode 100644 index 7df289b6b675..000000000000 --- a/testing/trino-tests/src/test/java/io/trino/sql/planner/UpdateExpectedPlans.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 io.trino.sql.planner; - -import com.google.common.collect.ImmutableList; - -import java.lang.reflect.Method; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.Future; - -import static io.airlift.concurrent.MoreFutures.getDone; - -public final class UpdateExpectedPlans -{ - private UpdateExpectedPlans() {} - - public static void main(String[] args) - throws Exception - { - String[] noArgs = new String[0]; - - List> futures = ForkJoinPool.commonPool().invokeAll( - ImmutableList.>builder() - // in alphabetical order - .add(runMain(TestHivePartitionedTpcdsCostBasedPlan.class, noArgs)) - .add(runMain(TestHivePartitionedTpchCostBasedPlan.class, noArgs)) - .add(runMain(TestHiveTpcdsCostBasedPlan.class, noArgs)) - .add(runMain(TestHiveTpchCostBasedPlan.class, noArgs)) - .add(runMain(TestIcebergOrcPartitionedTpcdsCostBasedPlan.class, noArgs)) - .add(runMain(TestIcebergOrcPartitionedTpchCostBasedPlan.class, noArgs)) - .add(runMain(TestIcebergOrcTpcdsCostBasedPlan.class, noArgs)) - .add(runMain(TestIcebergOrcTpchCostBasedPlan.class, noArgs)) - .add(runMain(TestIcebergParquetPartitionedTpcdsCostBasedPlan.class, noArgs)) - .add(runMain(TestIcebergParquetPartitionedTpchCostBasedPlan.class, noArgs)) - .add(runMain(TestIcebergParquetTpcdsCostBasedPlan.class, noArgs)) - .add(runMain(TestIcebergParquetTpchCostBasedPlan.class, noArgs)) - .add(runMain(TestIcebergSmallFilesParquetTpcdsCostBasedPlan.class, noArgs)) - .build()); - - for (Future future : futures) { - getDone(future); - } - } - - private static Callable runMain(Class clazz, String[] args) - throws NoSuchMethodException - { - Method main = clazz.getMethod("main", String[].class); - return () -> { - main.invoke(null, new Object[] {args}); - return null; - }; - } -} diff --git a/testing/trino-tests/src/test/resources/hive_metadata/README.md b/testing/trino-tests/src/test/resources/hive_metadata/README.md deleted file mode 100644 index 917e50e873d3..000000000000 --- a/testing/trino-tests/src/test/resources/hive_metadata/README.md +++ /dev/null @@ -1,19 +0,0 @@ -These metadata files were generated from the hive connector from TPCDS/TPCH schema with sf1000. -So, there are no min/max statistics for char-based columns as hive connector doesn't support them yet. - -## How to generate these metadata files? - -### Prerequisites -1. You should have a running trino cluster with configure hive (or glue) connector. -2. You should have tpch/tpcds unpartitioned/partitioned data in your hive catalog. - - The tpch tables for unpartitioned/partitioned data should be under `tpch_sf1000_orc` and `tpch_sf1000_orc_part` respective schemas. - - The tpcds tables for unpartitioned/partitioned data should be under `tpcds_sf1000_orc` and `tpcds_sf1000_orc_part` respective schemas. -3. To create tpch/tpcds data, you could use builtin TPCH/TPCDS connectors using `CREATE TABLE AS SELECT` (CTAS) command. -4. If you want to use some other schemas with different scale factor, you can configure it in - `HiveMetadataRecorder.main()`. - -### Generating files -1. If you are using glue metastore, then you need to be logged into your AWS account. For example if you use - MFA authentication, you can use`gimme-aws-creds --mfa-code `. -2. Otherwise, for normal hive metastore, you can set the appropriate config in `test/resources/hive.properties`. -2. Run `HiveMetadataRecorder.main()` diff --git a/testing/trino-tests/src/test/resources/hive_metadata/partitioned_tpcds/tpcds_sf1000_orc_part.json.gz b/testing/trino-tests/src/test/resources/hive_metadata/partitioned_tpcds/tpcds_sf1000_orc_part.json.gz deleted file mode 100644 index 7857aa79c46977ec0acde31b4973652f407c325f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 343475 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�<>51~;^XR8 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

u)oqf;B*MeIB#R1eLk3L6=Q@P5Y8vk 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

JXvRM;v{51- z8v@C{f937YdJ)q)pUbf+hPqeeyXHUxIZMuo1Ic4oiO+d@%4$S;Hqi=xdm0id2K0{> zb4H6ek!dEyHqWP@sO52}b;>)gzRu?n)H>#irtYXPEmS0YZ#tVxA&H{YI5Jh7#OJuG zVA4pf0Hq)4kN^{#nk5GK3~4jUO($iRX$ynDz%(pZWltDwj+Os3pe$W&IwvDrAfvEu zl!(V-U2ds80oobaMCH$QbKRldkUZ!Z2tdZjo+$`^Mx6+6YKI;&wW~3W>if~iRDi@! zOvP3`g9=?Bd&nj1TJ29`(k~HWwf!y7$&V%&x>7l&h7Kj8ohtT?Y!6KeI$aWNntU11 z9*I{%LqVsTPM<$2pY1QEAc9vmdSJv10}fjqDNV_q`_cwuh$}GsgDJ*jU*V!Yf^1a3HngC@ zFT#L-G|w2yo*Djbrz!r)J_Cg){Kkm$bLB6LH&7hIsDnJT%{cw=n*p5nKI*#9gGe6=G2{xRyou~l-Ik;y+3`KmJJ_<)hv2y=YE zNNj{TKEMM)!aFXn8;?_1rj`>=AkiL*b+-gT-k8t4NmypaEEom z&(~%>*g1LecZo;WS*|TnX{JRv%y=C1$;AC&?JWmk(EPpbK56bTx2}^nk3qD|9*-7@ zDC?Z>A~P}Tz&sCr$7RsB86J^EX12K!KqH{sBO1ssg9b{Jqvt>wb0CJGsZ0X21F7z&j5#D9VMl>FO95V`T?*)u>Y`PEDQ6A?h9u~fd^B-bDmPTi*m^Auia_ANh^i)` zr3U(TA2bdaV_1YHX$5Ej6mUSbH0Y8b^jwH0YNcHW3UC~5)Fz+|S{#%kr%z5bYTbaYwj9Vni5Y|u&qD`|^~=2ZKUfs_GD-;MBIf04hqyYUp}>yf{^;c;2Bmhkt9 z#=9bRGw(N8T)>Qoj`H`3$9o~3G4D596k_g1#AVMK5m7P6`o@n%$e3fI@naDN=9qZ= z&jc4Eez|CG2t=twDps4$7U4z;Mjnx4(H?SG0R4o!xqTBaY@b3GgqnqqO6J( zsg9e*|H5&w-VL8`1(TG!{LAbeE8$(Yv@(Z~_r>Mh4XdMk4#xfC3USvB?-M@GRR21$ zN8{?E&CFQP;gJyPA56x~R1y)!|xtHUhnH|-`R>#4AW!V;-vdzoC z(PKuzu8n_9_7Xy}ewXLQrj%_F{yTkUyuR!od6M2P*!7i{nV5C(2hW3U1>FVl)NN

LA*XHS4hT?d;>Ni*B09tEPhomb^t! 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

+}u3oNx2c-3Sn!H};nGY>I)_3!L>%aO5ykNo& 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 z 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%0A1YqYlTJLQXqy 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

mG`yU4Kp?N z@=ss*#qH(a<5g4cU36dY!1Gr@agvby5H07|62ap9QW_|^rusPoYz17J@)_q`fvZ=+yA{R e^lkU=xBqqaPyavv|NZ^H|Ia)W_E{~;fdK&6MfQ9E diff --git a/testing/trino-tests/src/test/resources/hive_metadata/partitioned_tpch/tpch_sf1000_orc_part.json.gz b/testing/trino-tests/src/test/resources/hive_metadata/partitioned_tpch/tpch_sf1000_orc_part.json.gz deleted file mode 100644 index 2fc9c63f0131fd397a7ade5aa0d1b0f5c3de285c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 213712 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

|)9|<+HRawCZo2;;@jmVo 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�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 z%0Ai|s`7cXrn66`*1-9f7yOWYK_DLUcGd-(`3jN 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%0DJdY*CEeXpl4FE0Hp)Okx_fk&h~yY39l}61 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

5{MT0y5b_R; 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

cDK*w+dB=xz?;F9{CfhUq0! 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

{%j_LlOA$5Ai8Ep{y-`@)4OKAm@}$-v3RU_ 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

ba7K?@cM++OBl8V+oM-v-OK7l+_DW#H<6&)c9 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

{w*ZV4|ZfkQCv2?fTMry*AY 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 diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/time_dim/metadata/snap-2337519488650886122-1-a0ba4857-ca91-4c07-96c3-f2549618d4ec.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/time_dim/metadata/snap-2337519488650886122-1-a0ba4857-ca91-4c07-96c3-f2549618d4ec.avro deleted file mode 100644 index 628c537a1866c59f42e64b354a726dae68716a73..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4298 zcmbVP+iM(E98Lp5Ei^`{wP0)yQ>_Y#%B_&7I7Bj=$0$RKH*YqA(kNW&C>*#4w_%0#81+V18Y*?2KK1Q@~Qd_qE`W#CvG~F zXI+6m(cx(n_{2ceMNQA7t@#$Du)6C|622dIK@N5>`qM{l4beJ5No&hU-EUxuhmp45 zAQoxMT$_*}d~zxvj!vphV9ygza-dP6`w?v5&A}5LS6O)Tm3^n1p4U z(4VgGj=D`)5|kWHU||w+r7ck75>%K<)pu~iue&rgkaS`32{sj4$26*;1BzN~Lt)Jh z_t~%NZjd*w1FSg+rs*xAt5|H^r858hv@PcqNOmp3_cyMP^Zk> z`|i2TwqB yNm<%j$(D-vQo>4TJJFO9;n!K+DEm57=-{_%u~QTP@L-$FIlLm0|tq|PSO0a(_HpwZX_aQIg5z}oWM z@~{H8%?6T)HKw%83#$aSje$E@DFo|v(aw(bs1p!&M<~v^YY=?^O1Cg2I}>gMq)6EYr&G@xG_GIauyu@I~NjXivmL97Fv z$zJ3K!eZ2v*vH!3?h9ho_dq6057eX#p$K2`9$Wz1p9n>3N8$RPgOehwTt6Tn3LJu8 zEXfJvfXHH=?F=}8Ho*aeC+H$A@`s)NziQ?Gw5|?$S4K zP_@WGvm8S=kd-bRguokZ!OIZ%98h^%+P#E85X0ljxj7Inr?O`eIAUChl5P$rk=INV zOBPUO;eT12Xoe36Agh}?)kPf>T`6gDynS?)x1w3GoG`VxVf zs+k*<^E{7kh-FOlKCgdt@KY9ks_aP~#A>y)mfdz?@ z?%+4z^?PCAm`#yf#8zhF`c~{;FWzOpICbEMxxR(qzv=#~{r9XsHn2t8)%V@Ik2cq@ z>gDI3oEg4v>oe>B6yLfqGkfEcpTF9^a_fio%IkXve>!`;xZOPa(ct&1pWSx*+dsbd z&cV;Oo!69)znNLP`|H2!eb?sR=sR_ZzHt2hH~n3IU3z)F@#V#!cHy%A?exFLe!1h& zYj>W%a%Ad(r>0*0V8`!|?|c8@2d}ByhTOF;+&$+8kKDYu`rG=a&z*kdrM1g{?)~GI H)@1(&HE+Cv diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/warehouse/metadata/00000-2c5534dd-aa29-4650-aa4f-31efa6057fc2.metadata.json b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/warehouse/metadata/00000-2c5534dd-aa29-4650-aa4f-31efa6057fc2.metadata.json deleted file mode 100644 index 9f945fc966a2..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/warehouse/metadata/00000-2c5534dd-aa29-4650-aa4f-31efa6057fc2.metadata.json +++ /dev/null @@ -1,132 +0,0 @@ -{ - "format-version" : 2, - "table-uuid" : "bb8e4082-500f-4f17-acb7-131c79c2bf68", - "location" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/warehouse", - "last-sequence-number" : 1, - "last-updated-ms" : 1678866192142, - "last-column-id" : 14, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "w_warehouse_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "w_warehouse_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "w_warehouse_name", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "w_warehouse_sq_ft", - "required" : false, - "type" : "int" - }, { - "id" : 5, - "name" : "w_street_number", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "w_street_name", - "required" : false, - "type" : "string" - }, { - "id" : 7, - "name" : "w_street_type", - "required" : false, - "type" : "string" - }, { - "id" : 8, - "name" : "w_suite_number", - "required" : false, - "type" : "string" - }, { - "id" : 9, - "name" : "w_city", - "required" : false, - "type" : "string" - }, { - "id" : 10, - "name" : "w_county", - "required" : false, - "type" : "string" - }, { - "id" : 11, - "name" : "w_state", - "required" : false, - "type" : "string" - }, { - "id" : 12, - "name" : "w_zip", - "required" : false, - "type" : "string" - }, { - "id" : 13, - "name" : "w_country", - "required" : false, - "type" : "string" - }, { - "id" : 14, - "name" : "w_gmt_offset", - "required" : false, - "type" : "decimal(5, 2)" - } ] - } ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "write.format.default" : "PARQUET" - }, - "current-snapshot-id" : 6972294945978784584, - "refs" : { - "main" : { - "snapshot-id" : 6972294945978784584, - "type" : "branch" - } - }, - "snapshots" : [ { - "sequence-number" : 1, - "snapshot-id" : 6972294945978784584, - "timestamp-ms" : 1678866192142, - "summary" : { - "operation" : "append", - "trino_query_id" : "20230315_074311_00065_9f4mz", - "added-data-files" : "1", - "added-records" : "20", - "added-files-size" : "3819", - "changed-partition-count" : "1", - "total-records" : "20", - "total-files-size" : "3819", - "total-data-files" : "1", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/warehouse/metadata/snap-6972294945978784584-1-c10b0ff9-aa47-44cd-a9a2-b59e12df5d70.avro", - "schema-id" : 0 - } ], - "statistics" : [ ], - "snapshot-log" : [ { - "timestamp-ms" : 1678866192142, - "snapshot-id" : 6972294945978784584 - } ], - "metadata-log" : [ ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/warehouse/metadata/c10b0ff9-aa47-44cd-a9a2-b59e12df5d70-m0.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/warehouse/metadata/c10b0ff9-aa47-44cd-a9a2-b59e12df5d70-m0.avro deleted file mode 100644 index ea78c114ac0a946b049dca83daf32de4865cf439..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7766 zcmb_gac~q@8HW&u>I5nX4OU9GOLgpUf!p2W$faT%3F%}+zyzu(!p`2_yWGm&?qzo` zAxY_g(-sj_tTZ?(GB9mns+~?-M2FE9Y8|HFP@yvxXf3rB+mX&N6oaio^?Pq`_r2TP z9(R|_Wb((}d%y4de((Ff_xs-R#J4AYy3SV9D5IR7I>b8zS;V*SwqxZ~hmY{?FAZ9f zZ;3|ul!o-A&9|%`;_(+D!W&8kukV-p6$_=!oQ-6A9qhAEZ%(riEJ`W5jUpbfZW=xO zhKRdgEV>_%!}o(@8iODeMIcUwb&2Y*i94n{+)HM>bO_8QqZnEW*^Z11hj_@;aDEBqEN8=(eDUMd&eytI__$uZwT6@haGHF&Rn#7=`G#xt6m?0Y($fV*Z zGRF0p2_@MQJ;L1Pm?uj$4F_oV_wz2ZNmGTEx!C>D=FAl5q!EWLwQ$iz*ldIo7G+Ia zb2QW7l1N7mvf?~3P(Thy< zZgt6|-hJ2Hy!WgwBb^_gs=L_cG}j1bE&x3`gS9~1vaVl7;@~(fu_gi@A0qn&{rI8+?--0 zxn;^Suv&sU)=9ieCWktX1d0MCu%N{K+o*J|iEKx?Zmx-9RRj{I2sq>*2$zy<32Y}d z6l2`UlA3`mDPBdkNu!FORJKX+D#^mC7}qQ06-QX}+oM~-CFzsc=AoPY$dVJlIZPy8 z!x=+Q6Jt2f7FI!>7z;1dBAr=WSQU39UXH|Dk|eQ-@g|qejxR~Ln@QDlGm&i+fnqE| zl2lzgBk`6)5}WPNMdS=}6s!_PWfJc?=ifI=4AmOQ65Ry*IBy@9(9J~2bCK9?WM@If zkv$AhI+Y?qhPla~*Bh@r_Mm+q8f!D+#$=Z0^8L_y^PlYe8hfTChv^Plx^rkmJNcuM&y0H*nssSoI_1?wx%NJZ9Vyk?ASbCK7lwc z<1K|w!2UQ!pB?~#KB3@fafuhcz}N+Y8Qogq`GrofqX(z50O$})rh+O_=m^eET+{(J z_|c@2(_@~bBK|pGpm8)B=6=AQt?QtUMd-WsYdn z9z7;M`=PpuTPtx)<1n64pJ)aL2D9RBdHvI!e@hQ))Cf%|H_HPfS~;ddM>cAOKEDNq zHHu$Q%09(-9E$L87rEDg01ts!7PsWHn_RW|pTLHD^R{?QxW7^pxYl6catSJPw4yQ4 z2S9>LhX87zychr|BnA-*YkXoTJ@fcMF;A1l#+wQw6QOFuB7*`OYhtiz%TQ3VB8)9~!8U>sAh{ z->Lhw>;qDrB9~P%VtC0&OvzO5KC7&K_a%A>0e7s34soCN9dKJrNi|K)(7PunoI~vR z-BH(UJdzg*RJbf;&V7_|yjH9pAtKGw>dw6E|@VvjSKR)fwAM{S@>O5AvfBVWMk&|b> z{MxH{SZbv3%(0j_39opSZB=ha=Y>cyDuSV;Rh)f$TKxp57#!vzw0#p{j=hZlhdT{cdU-peKb6)@t??>>6l#a-VT{L-`k>iGJJ3ol-ovoo?syL3(+Ja;m- U`C9X#w=dlz@80~@g5WX#A4Vxrvj6}9 diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/warehouse/metadata/snap-6972294945978784584-1-c10b0ff9-aa47-44cd-a9a2-b59e12df5d70.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/warehouse/metadata/snap-6972294945978784584-1-c10b0ff9-aa47-44cd-a9a2-b59e12df5d70.avro deleted file mode 100644 index 822cf35ffcbaa729692d8cf83cc6a54194ad5731..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4300 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 U=2x%0y6?r;-W_^*Vf0Y{4^s|LvH$=8 diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/web_page/metadata/snap-7514041884394119007-1-26683bd4-edf3-4581-a9fa-7644a32ae331.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/web_page/metadata/snap-7514041884394119007-1-26683bd4-edf3-4581-a9fa-7644a32ae331.avro deleted file mode 100644 index ebc0d1d90a16eeec5216607c5d1bc51a2fd3a8c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4300 zcmbW4UuYaf9LH+}u^ViMx`MprI)@K$~m_?ms{Q2z3k5B znudso#a0Bd`ksA`0YD*R)sQx8djzXWr2U`-0##4a^iJ{7M?^a_x9 z;-*b`)@Aq;9hpVGM+`(A)N)PQp6`SdR(EVlg7>2;$ieOj|MZYkL$pp%T-p*+4;q-_ zF{B+dh($Uw*9sECBdb2KbyBhYMwPfC??g$;(JBRt}*v^BdhE>ug zC04_P`t-z`cUo{rP`o(CLMP;%wxOmZRFF!=vvJd_J2cUd8_)AnN%n>FU2J%O^?EHuk;rB-e`H3=yDsvLt*q7d^R{j z-7@d2yX&+%YLO^=SPZr-trnVm2Y4;E4a&=q8CULIT!n4J#s$cXDtC?x0aJ_c(k?5u zZ&?bcP;`m0>*96z8}5Dsq>n#u*GhL%EyhPYA(2cLuxWd6XIbDlZq;!tf^B#ZYUGmZ zgLu^tw%0>7tFt=_cgeC4&l9a-%Wv19Y3VEqSP(k7CnaW{*ba*A6sAFH*te)sVmT#s zqbQ|dff~4#D%>rht8rD8P?|aaXspC2cnbw@p_=0&4BKX+&N|ZtEYAzS+1vp*c&mS~ zws~T6Oo7|h0Fj7hOmUNMtn4=$7_@_Jgbo!|Oc$m}2|^Km#QSgo?EHijt&_s%e+DNZR+)Z4 zASO71Uo6Q9WI*I$p6(1dKr3*7@B}@C?Z+bs*d&L=Js0jE1DYsUxevD>s8gJp?h|?; z_vjlqRLyZ{nq%k%S?<9>2wuL8monfppz>+y^b&#~n#U*S&LDi8%AQ4VM7!cix-(2X zUQ;QWxL9F-64N@PQi-$J;WYRTG|_urslLPWB1!H*5Jb1*8ubl`%aayg6{^5xhfOz8 zmm0P_uAW?;EpV5xtZmfr2a+Y>LN0x$g`zsm53(=_p&JqOgmlMFVLPLc=1xRKiz(Q^ zmk4TN&)lGFhfVP(f(o-M`hHDXkxZy*!>DF+Irv!Z#ghn_7*|RA(KjsHha{Yah|Br~ z79>o%i{F6P?|=ixY>MO}u4g80SL@D^eY<|zc>MDhmaZ-*c_s7XCWj96kTT z@|Bs(Q+a7xR(C@FWKE40&vHedxc-_f&KKSFT_SN@`8y|mYKeO^= N?&{#9pMKPy?*C?Y ڶHwPҢEZ>R5l(k)Ugzu捚;_ucoKR_ ]i)cKZ3m{6OzfoMimEx_|!`@1)9P%P5-=0#03poѱ sQx9 ?B$D^JdJ|(ʈr_J"t)ѦEnʉ!3"TWF̥b(%\9(A%xEҋH寒-%_W~U"x+d+N:T+_ZHG_/2v#}L_d/biw -O;CygTO/ޫojj՟޿nuW^XyR|{]'?o -yޡc61Ax\6}s3f2ڌYFq~wU 4Knpⷻ7t.'\z'\{ɅpB 8kpun0 Პp. Z[֫?۠ oK;2Qb7ͅpM ida܅.&ª -N8|% R") HZ!\(:JrsMmF) }Q 1t@2bZ s[k6\yG^N"G8W8E \pGxNJpGxpUy wnLϘf'o\e_2$wVp~~ ,NlӒ -.;ٖrpcn~_+®3CGHK=z8+,F߹S޴өZY:5;.Mަ ޤ]0uc,XKܴW_=9̈́I@@@@@@@ -XE擔[3{)QIJ%-I--Պ[^T+n ٭$֏b q:[qW+nMuy~Ѻٷu 60&[datN:c y= --^[JfsR=u[3{0XC [ha~ M "4,g -S ۔3P^:QXB!Gц>8ӴiQB:¬zM?r2 I@@@@@@@ -XB¬ ( zCta6%2.%:jD_ND1FfabߛzŸY&p -0;U=*xÌz|ĜX@_NfWSse]4/e=swŇ\sGWpΐ :c>w{氰!`^z rpըpu>p n h=EH(_Bl7-HopU{׫}A?F!\%X6;Vjz!\Sn%e{!  r"nl fYQ7b{8~򧠤tǛl x~O[{!VRvE!,pя!\  \܂dx%e B \]zug4Mop h}5\!\N$ػlArUreV"ʈ8+J$Q"-"*"od%QUqE,᪈ w VCC \p9$\p_]4aV +3%&3y7N-(8&8Wu wK7@\{kp>c)3)\^Vp(])d% duz-~\~Ovp ]BطKya -ϣZJ(QNJ<$}eow %I;~/CeQ t-7in>JC?PFSIAFY DB$ b R rJjZz`F`f`,ޢ8o!\-JJsil6-=KjshyfXF+'*ڌ\Z7C iCVض65l͑l:M[鳒#7ٮ\̛lWv 9nyVѴqh׽ ђؚeђmϧf/je7JIVSpAⴥ,^aq8)gO lh?5a͡*Yxl?Ծ %jyAR8JI7d oQ?(kFaAUgn7~IQL:̠Pڧ3PG^{)k׉')6駨{%ZرYU'hQ_@DKNҙ4l&<&P MYm|P$з i\ԙ&0w[#,gm\ZҊkPKuMLb}qmmKYqmLumϯNw= \3?Pe&Ff -}wC5rpUPfd3w:nٮ ΅ځo J¤u#jj]jEH8BxBmlV#űFZ]wT4zTݠB-ܡڋPk͘E"z7S֎(vm-D[ Bm-D[ Bm-D[ Bm-D[ Bm-D[ Bm-D[ B1fB-ueLZr vle5Du+ʢU PFnu[h}ôa_6mDЙisVںf<Jp-Mq⭏+%p>2";r vzzTƲhw uo BЮ~a8\؃r8,>=`} evm][<vs;C]k~5M }ZCڵ0>ckO]k~Z kϡ]dfl1z ZCh׊sЮ/QƱwѮV4[7qڵVqZk TqUV<ڵj")# kDUrF~2#8!1T.¦z(Iw}DȻƿ>[Xgx8Uo<=YEzƼ `f3?Do{ll)>[yo}.W?p;j?8al~k%<kppMn4Mq[ntp ieslZ.ڠYvvZ "c7uuL=uߎivapB$ b R rJjZz`F`f`,bK9JK('LʈI{D6&*%r:[ m}{ 1ca919X„,IlR^ᾰ"ܥ}WgS^֯)pyRq -j9vѶ=$#$GWpdˎrc)fxC>6q^Y`z+%w|[XLzv*ih8ER]BIS?Iɪr(yk-Jy*kI=|Fmϣ5E~ 2 8qeN_DYS)6q?s0 HȀ(hX (J pd/*ޭx dT[iykhD%9V*lZ[Mlڐ6FOV Yur+gxFxv9#IgZ-Y9omu枾t. Ivw+]#=ptG 2oBuYC11fF;1feTcCWp,~tz,)6 67"< `G8Z>FWB$ۦ>#.;-jQISD@u;ж^ݓQSTV OP>Ȁp;iSo| Dvkݺkɻ6Q"Zl5q;mDVZI{d+reUmn-Eb("jbͭh: pubwmȊp o%IVcιԘp}"UgI>Ys>Krg|}w1=sW|^;3lh /#IܖY ۑ zC?p=M$m]}!\{ WJ \n$\_͵nu.p֫7oMYA}㔾k^@떝 { ~.ជpÏ^a8&n͔P5.uKy8BUUDy o TmF9F~ B HEn<fѺemN%û pKL8Gv]5Z<$Zɖo.܍xgC/.n01@I%Z\v%h;,ΠoxC)&΀q᭽[yJN_ 2(fJ*CwQܠ%UJ:ZCRuQAECRj}2Y=5.mjJpOwQ̻#=)S{-9Kem'*cnxo!`@1)9P%P5-=0#036Fi1{E{Ghq(-}Ҳ<Zwн亷whm(?HFiC( 0Oִwh* W5jE59vyRbqYr4jQ8]GyUo:]5!{w1&떋+L͌Su9]\EݛHq&;-l ps,3d>`lkC(&m\1l[|}m[sejɍDe@ &6jS֏!ۊFm&{kn%toT( ]zɻnxqµDe;p'>y91ᾔ]V+]q$/:CE-]e$\-fڔ{ G87p8pZ>$KxݬǙux%Cɩ.;\ŭOC޲"MtjitopLWoԌӯswʸ, \d ? v\?L7"Dԇ i&)pcѓWo:*('+h[*no5CECnG+i Lۛ՛yp龞{gIy0 ov7OsZѽ=srIZ!O{E+xe7ΦSPRx^%Y $)\{jr9|pʹ>v DYnxJ5&v>&5A@) 5;6< -ẟphC[nJ!:qcԉïrubtz{5CڌÈ־JԉE 2?AlE+P's+!}M'D|!`@1)9P%P5-=0#03Įh`[CDԴUB$EDVJd%DXLR+Cc.TJ Ĕ:Xwvo8Nl ι1<\VlWS7FGx1Y<=n%g"Ua+sͽF(7崸>y-~GrLv{N_|g -}–NјpYI DiGtߓ5@ʙ=ORHƝ/Ѻ bmh~}?ĊghҾ~ڼTmz>ÊO  H"  2  -*:`&`VϯhgƖ3Q'1+,'NNyS(O.=Ąˋ`9Ah91tEqˉyX ;Tmi#1r_byX_JyzN_77͌#rc^JQbe ubu 9g1[wp먆p5pm4PxQ, - w~>f++BĩQ"IEGI9RADRJT;9ᎅvr'Ew}n01e QR.vwSRpͪs9I- !Q;me!~Fn,JʠwyPJ0lqlmz%/Frc_B5@.q/w|u ́p};> 7Jc4!$7H7 rG#=O>Mz!tKkp 7H?Bi$\U)/IW NT3J";Lbj ˈn XF abǍBIWNv ǘnML/'PR~{4qn;g^\}Ƙ-۟ cz^].)\_U_2mp*HRlϏs!,!wreKmg H80mнœ7)HAZT<E(IL--|D˧#i Z}hw\SԷ ޤtCŔ>m-м72.l?@R)kx!`@1)9P%P5-=0#03N`,.trH;}>d>Ey>beejen>6@w_3:N%uK(8O7M|W^?AEW&j8cka MBm3\Эyfg7W\{̆xwκ6ȳ|ze$-f(7io0 HȀ(hX bNW `P4Q#Jw ]*tPQKl%TBZvJj1s4Kh^ZU ٺ:aB]{T"W^9G\d=5]Rg7nb̨B VF4*څKqi3Y<).=-9g-nK2$f54 D\ 7s< 7&p׷sgotmC2('֫lD z>Pm{Bw>݁ntp5'7 I@@@@@@@ -XqSAȅWq^HF4Ȑv2X@7TBTma͌(-L Ub\ -`*)LpI<vsp|ۅS&ܩ]89''OFycyďF$W\)6w緤ϑl:#ف2 g/+f;q5GߜE@\f"CYȧ3(wߢҩꩩ/k߿T;WuGY@ E7kxK#97Y3}%:Ү$ PP 00+`Y(aw 4J4gR4JFɦR)&JQJ)(Uw* TJD(b*eD{ajD(kWm9R#qԺbEouqUsuj%U#U#ūuOF0W'FbP<Y忚Xoݺ@P&rOpXemgdٖw!dk˫3 &F͌+swѭ1/3#R+ vLFv lg(i;a1>p9n(枲<$\oY8IkfTUo@kVQvmߒ9-N/`.zq0w)1gUk^'*7XNNNlġCi"NncG&Q'FP'YSgs}ubsm;6jC nA3h̲NlE[:1#ub`=m,lC1;:1#Չ="jq9LG%Ld=eDSAZ^ATY;GDPJt6Z;ѲjZ[Fѕ:qsD]f9&2QؾW[s)#xOO֗pmo[>u_?DԈp!wjYQ/ig}i8emYh5ܿp⃽ӿ,%y~=cJQ7Kf(y(_R Q:kmohk)]y~PeJ#ݔhr2w )!0 HȀ(hX b#}¦jH[@Y!O$i5if@vR -cev@PhU tD0W-0ajkfM$t_۔IVd6e^ٔa*ȩcs t3\~]gnxW5rƋ͌WVF]? -~A!#cqyb@6yQ)n ݜvُoD>q:Vzpo0Oܔ4͢}z$w5n,_;ʜZUzrWIv l;zcLK/5[jz+lx3!`@1)9P%P5-=0#03./{hiHYY(gP=fݧ+}@?p~c}{>?l8sOY捬Sb=}'C϶r>]|nzm5eؐrd(j_bf(Que\Ek=HvY6LNN.={&}g1665b]U :-- ʹ'F,}~zMXP?jĹTԈ;sOӦbԈM5{hkC1w*ՈQ"̏/ r" n]Ȋ#DVIKD9&XXA{D7\Ab01ՇQ#Vkva+ۈ& +ol]+?Mvڧq;ΨXezxe#fFʰ2W͗ChcJJp{-vgr]HvĽ,s`26="{MW7]ݍl <:?H暲LnY?;MǧAm;{ҷsg8"hҞpx[Bcݲ2")#24e"?!R-#Ht)ΗbɓeQBLVBń=RvϤ~Zu2 #'c_:Cw~ $jjb+\{Vku:n XBhŜ~Kv'[#epMlHv -Af$wqnLM-q=E+GIUiY59A푷 .ַG \.7aK wYfc 0ՆH*4Ty_(zˉ2;Lnhn!BH2~Z9 7&l<1&ʜDmsx1;5ad0 HȀ(hX b{a{c4R@\@4%.N -dyQ@zX?ʭ<;,Ў'ޣ^m;,0  ش#&?8ޗ_p_}ƻ;}g >z߂z-t-fFgޏCT^E_ 8 -~dpq] 'c7v\$7mCesׯ jpdh umz6o%?wt|&dGo殧=rq\*ʴ (J".rDFEt -QQT@ Q6UU}l$hD7k9yxwsA Tv\'yL.mղ ܖ_wFE-R i&p-%Vq?YEׯS|))ސb g>am![ -7kLfv;x)}}9olNoJ:V.v5r3>NQQrA ׫kh i;i\o SƉghSzs?mNj܌A I@@@@@@@ -ؙAgPQfZZW!2").'b"@E!R.#XE!ZK ናC""ĴTNG'Bرgm,>=H/q^sr$"ݼ5}z}/R3^kee YMZ6y-˂`wmnxg6|oXeBgp^M?-na쏑p}~FvA.#c/WOmv#fpÇUpŹn6,.{N}{-\Ʉ'$ PP @kf`,b/6$n6B$YDڍ-B͕D1VM*"mFA2}f51 Vc+ph2+W<ބ))]wUb'G?'p+()=#zxm˛ JkURJJk,sjzoOf-P2fuX|ɞ%ّw;l~Eʞ%i;%q_B8Vfu%u'<篒DIZWV#mJ%eNjzzoYSmoU7uj۔]m6ۭl KTD@ $@ -d@@ T@ 4@ t@ L /,f5E~q_{t/'PD;Tn$O{nʧ> Gl?;ˢx !W,Z[hE_cu=ؗCOnfSKx߭Xt1E9}Uo~gbsUfFu9kef4fdX8\v<30!wѧx٠ad - -*{;bꩤ-h/LF2JI?jd5S򾣿QFJ_;)CO482RJ^sMQ֩Bʣɖ̃̃̃̃̃̃̃̃̃̃̃̃̃̃̃̃̃̃̃̃̃̃̃,vX-l{{a{;-jœdyZZ<-z5vZy{as_ -6=OPך^lo;ЕK/9 - \ywU?8=kyIVd61_jjW0 -ZE ٸ,dgR^wʙMt~֫1;N7QL7gߣDYSR⢅{$=JV8.%^GqIJ^;▯f)}+ڹ)@J_?e8_ipq?e{W=CYͭ1B$ b R rJjZz`F`f`,= Qpj޸7O *!H\'O ( y̶8\+OǷ Ƕ y;pܟ+N#;>_oܖx%q+/u'y.:Ƨxw.~zvn=c~/˱nsk׹cť,s[E|)\|YAI3.s|k &>Lz=KvnXd -Xz(Pn)tc 9EZ=-Xr׫7hFpBvzއэ̠^8Yvﭧٖ-lC-7Bn,&ʈXzHDVPLe=أˉ;BteDXN 8ZqNc&ln,'pxm -/xx.^Ʈ-Xsk!3su[nU4 ϛA/=!{ڳ,?Ͼ/L bf^.SCUQJS5lqgb_c-ȮFߵ"4nΗdQ扐l0+ci.2/BYs(v9dn$ PP 00+`Z[Ցi;qFKr4DS2V-Vru@9muǢ~.L+ic6Um]lC}V|RqH)jo%v[ěmF=ěmxwԎIz/Uy\s53W~+_QGeQ ڒn[kaCni,gqR܈ɶcV̍2NqErm5Jqd[;$d[1w5E!mȶc}ȶ[>;9.>!nhnwKFK-B!к^=BsP;: \'WLr!Anv8 y};cU":REUD2^IDZyrXJD٩V k&JbW#T4ur!+BUW&e+)x2^~-J,?&ܩEüza^O NY(&ejŤWqH_Zl>nZOelv׍ލMƋ>-R5 SS)Xn4SI*(qTMIG*)YwK%wEVV5,Cfuݨ-,z}24 cUke]x2/x.=IƊI0 HȀ(hX bsËhP WL6Ѣp -ҒZIiyO*%jn'WLN;ic>6tt6#E4YD'wE/<8xU2=ށw;xU{Ms9"Tɟ\L_\_wW9`8,֍cY});:k-1Xx6&mٖ=t~44_ȶȶ#O ̧ c`g׫6h'B}ȶ=ȶtdܧȶ(Pҿ6=c`V|dH ȁ( -h  6{cZ(%Ed[j2muaN*3v0;B UDWXA9UWEJbʈϏNN6';8|ϩ9u뜦>9~iL?Cmes9q+weqKn<^([r]&}t%]۩LwH([!B{~AnO 4\V;F!OfG}N_n6Dt+v8m#vy^W&k*Jra[6pTuɚznBNspU kًtgskY7ހn1oݍ-Uŏ/Ck.K x 4 R rJjZz`F`f`V`"H}FX"w+B$#!"f눼 BȔ}Q'n}!oJ,L [;BL/IJ[b8ڦU>i@ء[o/C4ɷKoX:~/o9]6!2 -[Fno!wEa'ϑ紉Yіͱy.bX:ɎƓAF#Bp )eDV9(eC2*u NsdC>b8DL}"5:p?Ŋfr,@i@@@@@@D89CX%tD6.?+QHG$`:J !_jI7u[ӭ=t|BKdhb+F+"}/#L2M6)S`s_/Uj7+*:_UWW\Qѕ*z =삊| ? vsi~zpG.(+zm57E?ʤaxEx}n]v+y.=L3B߾=lZ[+ mUFY\+?Uf3UF5Q`4cjGb~؏7⳯3oXSDciD>c3s}$~.$~[ x 4 R rJjZz`F`f`V`"Ht|{ ]:K[y^V[:xEU,U=tux=POh'o>xkh{jeHS O,!-K^"iOYyX3I׵>վ`[m -XFӲ_cd#-sftur]oNQO4"N,6-N9+mBX>[ iH׳/TR=Bf6^ ѓ2+jW' - N vЃ[wVTCĀĊ0^ĉ0QrW-Qt=Ѵ$fD C=1NSg'ab%XFHZ9,>j)AӒ&fb"2%/~躮xq(w+jf8g[x(v&jyYn+h.[+;E/w1Oiuz4W ކlizYazw:;Vj?"ND t֙]\k}e—&^DҹH{DVg!z$fuD!-D>4D"f1' `= 8%G5.Ń ,e.eȭ# ]Wx i2 -?: " -wNlٹ^m~9lYP]/}? 빗 -n7h!VY-csŌ |~**z;ː> -V{c<~|1L#xd1Nsp8c ]\2i=$tFZ>KY#?(^Q~KU99so#*ԗ1ƊX+c -xoe,5{{ɡfF= @i@@@@@@DhZTkrҳR>']dU9eI!]iZ+(_P>g(Lk8sjRYY'VsN|s5қ"~ e`mlXr$Ekk{! i_.iA;v'_Ev{2py[nڟ8.ۙbmdtzb` 6a!')[wi -9yWS4~mn9^!XwR*~@kC,9]ǚZW˘Hުv#<9Fs%\% x 4 R rJjZz`F`f`V`"?1B#dH'(!" eo*CD}[Y$!-ЅX2XrwD%%>$o8e(nEג -.R;Ims"UsۜȟuOms E44r,_vyn!f.ndxӪ%ZW&v'z"T-OG(DÍE&p:a&J01ł Yzk>$TmjnअKFi]YzVM!y 00}{pw[;ZnC((/r9 זͰesَooz)q*@P%gBHwF֒nr;WҰS4CpTp=`}JmņUq {nwRof^"|<_E$Z8\ UyD -ez %| Ofb"旈yjWL/rE R&wO:!O:\!AQ{ȁͳ}_!庿 w!ehELG \.[82\{ 7IAp!==}7nhX:t_fSmUTpp.M U7MppɄU#+v!07`5Q;TDs$7$ab@h Sr?L 7&R )٤glE -’~F䫂8d!ൂp: !=E=pWvۭ)XF/N%cPٶLᲹq9~w^VWw\Kn`llVyf9܁F -ϱ/ w\3Ǫjw0'OjGMW~ѧδ18[5b̥k_e-C5pl+k/hbն_V| HȀ(hX؁(y*Hig@(ceD5Mċ&?Z8J6s rX'6\)[K#M|R:#;Jy5;J<&|`y/ ?="Bo킦!w?YFW5r}.}F-Ǖ緉 _Wtf-:6_hEUʆ V7i\ \ kc%pStCdEJX*]Kӓ1dx1?;I&󯱖):jSA7pӥ7≝$[dopp3yn(+D?S4E 'WO1  k?r"Ss/";%(w-H;ItIs9}oA%|hXqbRE0H=freDx=Md9lY97/3;s"-;caޕ6mIVR‘݌qG͋,B5[MuWt'tc4:cVYKt[+koF+Yq2?e'ҀH ȁ( -h X Q49D9%'gxi^^>+KN85ghecWyC)8M]yTykǼ}/)p{RJ~bY֭9ܤ&ٰ_ -nRl餂euMwG ___(fX9]DA9ew!G-(m͛ TSg.d<5-]H9DQ~y6R>MG_߶ /@  -*h X DEo9&A>PN@YWNTK;ZFr$r(#rO0o#2b*F=RWI-ab.!|_φ8TNudlf>Y'+bOt']HiOz黯2ȣ_3 &:L'XhN <`D/W'~]6tf-y^fn޸SKilmy'^޴2]ĭnf~`=Z٘CڙNd42)W9\1LMRN_}>7pe닣i7%+8Mphrw`m" F>FsJ!ⵉڎS M70fCF(7 _km: \CQTwvbϺMJ"k -(weG%Q|#D~;ьl 冣MD# -7./ gjK ۄSFc(*\U7epBܛ5:.o -W6[ ᪣5Zヨn$d,>A -!hKz \z>7]O  P`@@x`"bx5G+#R-"rvnc;JkFt-oDw'KJl(#h*=\NĦNp$ǔD>UIW\_b76.iz^vӷ6.6֔=k~|sKYս3{ϧ~\阵drJ,hX@QJ6r[ G(Ņ.JyRtRlQ]\=EʎPp2n>B.#]%0Eٮ.>y'S3nm-Ark"[nmb&Wɭ!ɭ-] ^pn+[m<1{قKs-d!B#9 zNǑczB˪݈YktvUl,:=kچA,jTYa3ǖYEĬulx-bve˵K=4n,׮dm@zK1+x(s*m ),:6֗Fc1gZ1= *D̕b۸1[7fDnQܘMFܘMHc+I I8c ~WЭӣ1{XiKocsg3^ sG8W0LNq' N(B) &'R=ɩ) Nn;5״.%)ڙ8sУ_) ;V4%[ڶwT/j7-Lfs[%`͛rN/kE{{-np[,b X-np[,b X-np[,b -/M.YW)+#˸ꖥeڶeSRcrS2喚e֒ޥɥeb,Jab"y ,Z,Y$aq-"ZɢSE$='YtHo蹸l=ga037dc6Fm3 1>*C#a!z}N S0n!v:\~eњ޷w'6 [?lM|߀5XVtӣ-ϲkh0urUvo]m04ֹ|d5 :nF2+iF½ᄊl]&hEkQIT5 ADPM4&H4[9b&bC2+m5!v|_lq#[fk~MoH͏ɵ˖syIl}%ܗqw3$ᾌ˘W8a[ka~ -kŌp ؘ^1hpR)>pn_W!aikNNozdE \A~ؤ8~sTUt[]ۀ9` E=ᦎCV+7g{,e S!~)E+.ڶY.cH(3ya ׎9hCDPݷH64c?/ >ʞuFxc\'TXg uEODCQ.kԾijrtV|d{OUؔ뼞------------⢷_)[V*W)VbVp]Y+5+WVEر4|ne,d ]_]i_Zl^9 VaU,bcXd;?%>uw ^s)[wǰZz ֘Yl #QʓG 9ىl>dʛVټ!;Q[mJ76Zu10!dJmthsXՌ iuHQƷȺ;炿L>[EDYRETS *zhn' -믾˩"*b.)'ۉ؊j_ 1r#ig)\bw_ x<|'GqWbqR}u.x2QnNڃ;I}cFcWp%/qiδPrJZ :ohz8:'~QFɚQLkݔ*͞:Dsݔ&騠tQ9CЦ<_C[ZR֢Jh[IV͗L7bY29P%Pp@ 4@ t@ L , -lv ݕS2ٹdхLyLU2"cNɸ2uLwJ=-]8#fdƶ2\rJfYe2>L8%nclhd>';9r'[sqOv:X';~W8_nY=sKoy}cg[l^ǝLr{ӢGn!p.1qn/;yhS?) w9UK5.kʡ!U4ذ2y"RVY\KsRy4qo`( 8:`&``6;~ZBCZ9Vmlk<@'Zhp B.P~Le?mB -~^{зbq'D?ǝeqU QwBT7lPR;^oܼCPͰm[ acύ DFs̄?w!G- \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/web_returns/metadata/snap-4364078469342007082-1-ab80c5e3-b2f4-40a3-b1a3-67e016d116cd.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/web_returns/metadata/snap-4364078469342007082-1-ab80c5e3-b2f4-40a3-b1a3-67e016d116cd.avro deleted file mode 100644 index 7666abc19b54b99f551efe3e60a4ede8f55024a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4303 zcmbW4U1%It6vs7!1`4qcr54iE%d}RMq|PL}$!1G1)ikl#qPVRVVp(ox?q;XV$2xN- z>oyG{3KbObso)2#g^C|gsvtg;q|hfHN^L<9&68*!RK$n+Wc9&w=i}ZxyE~iRJnrnB z^E>yPd;jM@&n=w1cVIhS^4xvCUL`g@w`*CUjT%vkibr(UGn8S)!8ZGCW5=uzpO!18 zMc{G8B$nYT#rfry6*Ww&up$oRQ_pl3;kV)H5a+O~V`{n%I`+aLR5xiA&6ZF|=TS#Y zLn-D}b!E6UwndypIlARav5&eGTZkoyeDf4Rrh^t5l=w-yabQgfT*DqUSw3aIM)Wd} zdE%x+dDbQP6CIjCflmxXT~zl>+MMsG6jpZ~O2YT!D#*c}j{fwKTS2r+P*U0=Ql|_| z@i5Y+3}TU%%(V##;geSa;^?I81oi^)MBb^Al;Tw=T7~LW;-sLcj(vm;gRq^4O^vFg z`ZwuevnVko3ml6|5_?ifOcm4it5) zg~Ey*o@c+TyFpgF4zS`N=%&{|$ug->YEFtvUNwCR$GzNL=!5a{4$EjvZ`MjLuNIOco{~r=3)pmgxU(#95_iFMErK0*5NYI5 zF97kXAnY_EHLJ2a3U|q}5YH1WVJm3Xph@X03RoCAy(cAMp4bSAoiqbFJ!2&h#$_sF}gsvu4RU&ET{O1!TM&Vm1d<#`v4`J9g6Lr>^4q$m+1hv{Gz~NiH zgSC|tE5i!hHXB4Do-u_LzOhnZ+ZeQiZG~XHD$3b$Kk5X8-4+gK-8G0l09&^(CEF8i z1k&Ez*mzEzDCEW`$8w{pI-wRucLK-8bYzKmKFqbg(Orock3=(|Pupqg1V+YUJoSHg zn4e@A)`8B(UgQVD&8VqiAJ67iUl31y55|P)!89pCD8i3;4=#Y6pNOKhQuzGO;H1PV z(+>#5C`a&%B{_i%h&;^OI|B~TCOANNf-b`L;t>Qa&tXZ=g*(WArV3W-!7T{tIH$Jv z3Ehyp^bH)Uj&W!^$IuP3)P;i(yj&Bn&4AB<%BQ8(O9+B!9-o|>gYa=GdltbF?Mf!; z<}k^4ZA;O_#ftiqn${VWN}a_Pr@?oiiQe-{^&FlTNpc5*AiAB@sAoV@o_6t7p$c4f z*mM(hsbI_J>dEEV0(S|^nnn$OAXySFg3lb&W!EeCpd*Hw^nbT+*WMb!j~zIE{lC+nTzF)x^vkwW{e$)2v?~woKlA0SA6HjbZy$So%iKr% zpSbk+rN2M^?AE_8ufK8M?zP+R?>)HmfcD*^?^Wi!pKq=gYM)Qf9DnDf>o=?$moI*E z_O!X>TmR0TC!fvF43+x7I6u4ff_3EUXYPMf(Vp6~KK<@g111!c&!N4Msg!X+X!""CDDDRHC ip6YݫQ.o{>sΡg_x)7d-Mq}7ƛnv_zo7&GZ׮MMOIjG+*rS>+J{WFW&WfW154+K{V*Fj"TCE\2GET/"TD2=͆~V~].[iu]i/P<;z6gڤG/O;~6rO~Z* ɱ6MilߟknI_cahO]F+,e[1G=阞=zxT/,0G'oL ٔ.́*98R\.%Ǧ_2g7Sw"sPQKcHhUϨ-h'EѺaA_Y%3ze+s ɼ_"X! oȜ _PRmoQ\ڗ_N[y+,SETrC5@C$D5h@ :Ѓ`3Ā`;8 >C2_' MP%f'! i&vb8LGM٘ Nb"j$L-q5p""o;_ ɒB&5D&5DҐh!2!2!Xi9b!2!eސیgߐ/BԟW)WD/dT*+JALRA\)H~ +VtN ).Nz m{oX -ݛ/}ɴ!Ӧ |, -;RR+5:xw1Lz,w|Ad =K;O>)E=IEd[28S> W#X-!m%$3LBޖr?1!v$Y)!gIʟw* u'&o=g-Y+ , )i듍H> ~>zU~hK'!l;5:3>6őz|BJ{Ǭ8ogwb ([Tufd_xѠݪxCHc6dGi#ɜCHKi_Z<0ݍ!d. !sCWд}1cCHE04bk#A"L)q vp}#N"j ٰfBl VJRB0 #!߬۴VBYw! d#fB*O,!sbdCHPԺlEA/|gCk1v$`I6aRӦ \jeSnGcaXp͈KvQvÏS8"P+"wn*rSjw?n4E)mQeҷiȚ#Ʈbj+!W7Կ.dɭ՟e;F{AΉLK].=Jl 4N+ޢ΍ōҝU3@C$D5h@ :Ѓ`3Ā`;8 >C2[ RҨUVUQiRު)Zѻ3jJPymDJI=L>VMvbLd% ?V$BvbnN= Q1'soӫD-sE!xQZO8s% -oZ,L?(|?vo|I_j@,ED! [<&)(z-?nBjc66Qp1#e4¦biw;zIB~L^ E]Yƨ=oSaJٲRԣPaJ[M -A8P~ܘ5|y-1-n~Kfɛ[[J{lgT@ʕmctF*ȣ*_Űʿ*yx96< DBQ" -AD!( DBQ" -AD!( DBQ" -AD!( DBQ" -AD!(`( - - -PLjFU4zAcԙ f]( -:֮w93ͪQ(L6HQ8g% -JQ(W׬DaDA.EaS•WBDբp9E'R6KQxSܕI:ZGڮf% -RRVS@G(8~%C_)Q"X~Kic<' xpG??N($ťĥ#5vn`2݁X7RB{Y3GĵF}{9XktEGʼѪ7ljѺo`1XߛdM2`1ulkW~k65ƟZc^]xZcgnk/k:5 -:55@C$D5h@ :Ѓ`3Ā`;8 >C2_eg }X\k .֘"]rю+&iĵFZ,ę+gzc;J$,N֐xCZk\憧GZkfa^Z%koWP7xy⸂i͵]ZH\ZkW -0ح~fctBk 5ס Emܥ.AK;/^їt i_% -b*=m -NMܱMOq9xqx#=>>=IgNJ EM;C%+)zQPeYѪu^x9Q{s'w~a@,یLe& }[,#P(QQ$6s/ -2)jix5ޏAAvp X}dbG&VX}dbG&VX}dbG&VX}dbG&VX}dbG&VX}dbG&VX}dbG&VX}dbG&VX} -RM䅄 (ډ2HT5D=@4 D[Nt{C] X󈭞ˉ0{݄+%^#B5 -$>LAZ+[kb: -r-uBA2n-׏+ȭkdfZHT0 a?v'mY4$rxq9⡵* ~(ގ(ȟ^CA:} -䑑`i>6sĦ >uN\d65sBA*Ndn*j}"Degέ{MdlV} m3v#ɴx8\49VM{/tS #S1#;f{f;)~[Ӎ(흯SEXA(H A$Q -DA(H A$Q -DA(H A$Q -DA(H A$Q -DA(H0T%"#XQ!I!&l軈ag8[' ar9;LaO-ډw;09C* b SuA䷫cY /K3WZ k W  7uҶP+6 9Pk e -(C~KoAA6+Nd$q D ĹS@\;ai;u#q-,۹FAz0DeNjCA=_La#Z7j ?Rv_Gb2uŲ`لAFQ1܅dA͛1_0`|#;0Tp߱_S4DB(@ *P=&0C X -6p ?$UʉbA(E#Q!n"V o$:bl!bb#Xnb/$&TW aWCf)'B֭V3,H4lSRA)dCXQ,HKa -kTa -K KWL S  -_s݊U m{s ?DA^߆Ug 㕂) -SU_秓ߛLWƹ8fd$8.ݑDZ.?W#A,9b[Q(\@AQ۶vD{uC]>w  -c}i/ɜ -2>dFAPF)|/ RYUL2g V1G;Qb&+h(PT hAz0L`l`'08x~H% B%I,zhm\W W =xyiyd؆=W% YAFHwqXŸ87`8GB+֑rX'0 Ϣ Q9 S*&4TN=-;PU$w3H{(Hgf|M9 Q%@ Ђ`# 1`+N`,p W'QB/!1̓zhzvFFqyB,[GWa,ax'Nm5-F g)vvR֘A}֚A~-. 0f9xo~Y(*E 3 }o-A*V-fࣷnnӾmJ NiST7fXq9`]>.ݑHR鱜넂4;GٟGFvMQdVR -$vJ7]FK ]ơ b 3c (˞3om{# A5S=fY|Iw]䟿cSruզcni9 Q%@ Ђ`# 1`+N`,p WɵQ(z^mjQ7XFlBtVMe8eb՜zkY吘VJBn$̕$$ +IxGJ;LXmkXyDj?ZPKx<iE-Trkniה  -P -Ԡ-@0  \?'%riS65=--#wl,Hp;bq) 2@fLngźb%w -7ѪW.KFEѺb]ҿt'~ v%=KP߃uR;%b]R%MXL9H {.ɴ0%ĺ$:Kźu h(PT hAz0L`l`'08x~Hy9h!j!)'&D {JQ,He$݄'Dh!S>+v6 -SHqJQ)H: -<ͫd>KVTsJTsw6K椷|wCF\i˳() - -2I%3bA~uSg@wxˤ),1\ ˜p}̚B;sE(ȒxN\JT0Qy87Z-M[=([(B6boi$s(HxN -w$(l4K {EAF{@AP -dNxyiÙr!@JP4F0bV X!Jq cDq(j)t}/t}v rc1Msi$>l Ä&"'ggOzT;Q2 ^GAjuW+Ho֗:g~$ RATa -uw6s0bA4(}y(O_[DAnzU?EA0\/( -1z\͈ pn.ss)6!6c,$0lƹ/HCo -ʒ-t7:2 -2"(%*VeݱU7(hF_Kv]U: -Z8$PyB,^ i{i "(SGAFQQ{>=o K(H(Ȯz G_S4DB(@ *P=&0C X -6p ?$wH{^ 2*,QMѴΈ 1 <|ue؊) q~x0OVX oyF^ ni6LAQ?s;]V=#/uuB*,LA~" 0: -b**?VV1g GAAj:=`OBAi9y.G\Zˤ0.G - qUJƦ2ԴFG/Wc͑ Jzj9<rdOojucZS]GVmq#:c>i]c.S~,Kp V[ߴ,~(~05>6d]ힿ/x'SMBo0ޜk|<r!@JP4F0bV X!įR+"zQ%Sʔ2UL]&Ӕ˴2]L_)3ʌ%2S\,,2^Df/9+eLU&ce\/y*dB[$[ً^J@8C2Cɋ%D@)*R,tK>1(s8rd4e59(f=(n '❢Nq.!K#kn^"0 q#!|w[2ݫ>gKZ̆9v;["uї ?slCB̡tgK? N̡kO_rQIH 6%fcit,X*eRS@j 3Gベ-tOqEˉʞ uDmAW:.{jf%TJyZr޴XVaR߅U#X4q-X̉w~j.ªiZϥ/{kʁH(AjЀtf Xvp.`< |d=ț(&4,z*6m-Rb bb"n1!ZIlоGaBJ Hbi"ڪ5b_aR%%5)!IH:Rp~|tݿj -ҪEfyU93OB;_{(ah{+ZW?\Ӗ:; -Ҟ,z)j2`Ͳd:oOA@B\*rp eGBZF#--aT'턝ӎ'3[_KعY> -R}!QY3fPyE]~Ns~GXltx.}.)A1FhUMVMfhA0 }nmIs)LJw; G 5#Ց2<2B˘ٻ7ˋm$덨EDemVjCHc=QxԠXϋ f2;<:jԿ ! !琐$" oy˘2Hgڧ}dž|M9 Q%@ Ђ`# 1`+N`,pEAڼ+_9yk//y1AA.ޭXiOAl|:H PXGZ|l+5-p,]-f6z1$mdGCT3DMQ Ă\aL2- $GA\d/`}GA#>0]0(H(H ( -ԶS!dCcQdcה  -P -Ԡ-@0  \U (HjMTfVM ME˱ /G|LM\aO2K2WiX*։wQdCAQPS2hvOgA@AQ~d -2?| r!@JP4F0bV X!Bt%bABWcjjRc&B/%*bKLE\z[&JW)a E!Լ5=\'cW)H4h"zPԷ6߬zKm~]*Ȧ0I -ui+0Ѭ 7|% -ҊC4R8 -Y:OC[2$zifۥ>d?t1 wM>[)\zj %ݑNINq9|,7iqi' U G,=jG+Q[PM%*grU{۪?$hIvuV}8 -}6c@iOyO -2wŒ?EA:[QPċ1U(H(Hf{*Gލ,4 {Q^rXA5@C$D5h@ :Ѓ`3Ā`;8 >Cr얋o[i i'fh;nۈC  2Kk0r8 ?GLAJQeړ -Bÿbv8r6LAj1k>$ukP[PŘ?]4c'zi>۶O_yfF ,b .Wf48܁T9g' -=6pfd=E^+QiT/uMѴ/sG? u̘rRxWiܵVr61K\å0\rT{P;[C_խIPs-"! -AZЁ `!,` x^A*F6 &QiV:Qk:Ѩ3C/fY6a.3kԼc^ݗxn%%${~:gHIxb xu +8)~w\aԄroHGzEWrz)1Kcćn_7)ɴ7N&f鱎@,%)8r;YwB 8֖쬫;_z^<%;O^.|@9$[1íl|U .ۭ=_Q*f .3xM=^ށYLeυ篴6h9Kŝzʕi#ŎGqo¢LOc=%t=TGy۞|;Oyh(PT hAz0L`l`'08x~HΖNMPt8UdZQ~J;Er)Ǯ凗QʖE9I1+C\Y#)=zEZ|pYJB0SW1Uc8K׸2~qy%KXH Ɗ0cE: -0O/5buҶcIzۥ3HHXXǂZaz,L2ԙ܌MRW*KIu3X$9hژz|yJ+$4ۇ"!UKnu'p}Mw"!K~؃Լ5#!GҀ}, )CB&"!EsHHνHHH㙞r$da$y$$H3_ 9NPEAJ1C)R)J=Jiv}.1 G{ńLS9VL%Y\"4<ÔxVKHԚ s"z?Β&$HO蓝jRL? - _<{p #aaҞжKV -PQ7F~ ziǷoїskX>)@HhsTLz -x&Ý0..'=R-.nܾPQM;Ekʩh>Aݗ/hʆ;uuG}l0k}(ɴ$''X,uk2[!_S/كuqߢivO˔+>{pĭ,J'=)"! -AZЁ `!,` x^aQBcOk&n ?& {:Cثs0Aj ~]3 q !ߐ -ra# uDvɞhڗۼ֋(Vi)=@'?Iu/ -ru Bjek?U(Ⱦz?by5 zig( +XSçq)T݈## 8/~1T" CO'yJ˘ nE (<_UV5qڲwc){ƎW&0Ob)]0[Zb^ {3kA}3AeKA{q}3HXƴNa)ZA5@C$D5h@ :Ѓ`3Ā`;8 >C2_ek<, 9J<^GDWO+=ĔEUbAʈ5*8{ S@\- T/moᩝzgn - i'D u7LO RO zFAF9Z-H#?X r ( wWfM cjMrÂ)V1_i?)+>=-ͦ94v@|#T&]q !C]ݶ% -;G(H.N9^rxŭ=io;_W~;EA&ck>FAJ͖+K  ?CAj{\m(H( -2=";S9CAw>E*_EAꞧ[b_(|M9 Q%@ Ђ`# 1`+N`,p GQ|B.oTe)Quy DWEyb'j 5ĺD3H8 SD\E=%x$SC饦0QHɚOPW Gu~*6 GD+ =RAu+?S'm{yɸ~e1w WsƬ>;U/ sƸ4w|J+9؄x~r2X&c W9*&kI'ܱG<^ o&*;>1 ;y/QACX ݻ͸p04֛d֏Z,e#x̴G|inVbzixGɭiEytf=ULlULU h(PT hAz0L`l`'08x~H;."p,#B'=D[Lts~2c-15s([gi9cG)po%н.=.i8LA$J9/LA6 -Q'J-Z-!z|$-ci#0 ^GBM- J,!:$o!!ߪ@BS !:͇o !>'cΧ;2\)BnGBz\Hgxn2G R$v$}P&'ZUm43ڱѺ$d+':syHHA2/nWHnǛ)a !HHOvD-2ڍT|HHp'kʁH(AjЀtf Xvp.`< |dQ] &(ZD]:>Lt-+k$]ډꈽ8BՅ^%FJi&B;]{0 I89LB.$g4'pTHIecOB_PQ<^)H/=:nRoRFjܤnߤ/ܤuoTpƽ{5ﭏRVk}Zo;ۯaۮq5;+N'{ῄ򱫽{Ư7}%7L6ter!@JP4F0bV X!įR(R1Aer2Kʕ eRF+ser@fʑ ʬdrd's,W" dRP!/#L/z G hQȱ[[nXs0m^kcם+x_:Ncz =9_}]=1/JAK;r-<LGϓ Εr.qH8Lgٴt7&OxBkn,Km9c>Q՞5c(Qjvڠk@xLf)fV-cIvCKBڱe-;Mާ)n5V-y`2@glduR|~V-wFķ<|Z|M9 Q%@ Ђ`# 1`+N`,p ##=0r4u;ь(}1c?1sB6m$ΞЛ {p}"n"owN]{#&!1r5*$W}: -5G7Cf8G9^=ŒY(ȫ -LcL֖_GA.jK1rtT /KӑSMOu0ާt\x,U(9&!ħ5<9yhߣc -2܃T -]DO u-Qە˚C3>fl?djCAP;7hi{ 9qߣlx{>,Z kŧ#`ҹowy,ZAAjZ_OG]~:h(PT hAz0L`l`'08x~Hnxx9'd(z0Q-,8zSt$Onϑ7e] 7u^;CRwԮ$|uT-k(Utnk//s-09[*HOGq=OG]v˒ŧͣ ?ĢbAKM}aS2}nl iۑ=S32 |g$0\]9q`^,ཛwnwk| 9zPPLTV ߷U4(hsEQq4o6cŁ$SUOyiX7,79cwO; -R?>>H3EӑM]On侎L77' dAXA5@C$D5h@ :Ѓ`3Ā`;8 >C2_+"f!S=ꉩGXVMYOu~xHU3)걂kʁH(AjЀtf Xvp.`< |dJsݓ='B7um -d&q:6b$R">_0]J.4kUk;RAa -THĴ<,?IAjA.RJ a -- _ ߿rD椷I|&ym:<9cXXѣM(HA -sEۉʲ#H] *&;w$>d8ɔ?dnjFAj_X&? X>CA|g32䵢 > (H]u'%tWm~|K(Y=>h(PT hAz0L`l`'08x~H'6~qɳuc9cMD@PI N-O*l&PA3J\{CgP1Okm=&0Tҥ/(ǟq3CI* 0J1J^H\3{¯bQÏt~d/~*bL#> /MK<ӗLxs{ms\#s;؄XGOp9RY>JA`^9d訍[_Y:dVPt$* Vu}$M߾DmPxo|*f!4/\-u.GA ʜ(_]W)ndv~ {1,|{gQר )"5wam|"1 i!DcdIIn, P" ~}MY%AYF|$H^HCܲgwE B j1b^وϕÃLq9m4Fd.˖xPf0+p-ZŴΑv%uqٱ?8/m xEYi,7A-A=}*^s߹u#Z}]tΤ}2d-_]ZYM{u} ڼÏ /W{h8&^G1Bă %@ Ђ`#$ `+. V?lZvor2uHRR)|-r /R6>'9FdF2W9 {8. `wV(iuj4>5Q3O^~O_N7̽n)Ek2 aBO^脗zvWR>rOhFQf*08~caT{mV܄.4v~LcD%zԋkm n 1K8_pZ!Nm"|$BMgh9$껑㏑17'c_g]}RnqТ.!W/aCˈ($+vv}oV/~Dș܏(_}}o-7hVNodO -rzTAe7QɹsRBY\΢?l*D\e׳Iq juJP 4u&A;"dWF}tCtc=z)0JGaDH?">r2 RvR9}N[17uRمiFtw6g2Bă %@ Ђ`#$ `+. GIH4 EIG# ^BEr6Ng6h/,,ZtK6MD(B&~VBrOOVLpWJvǖЧN-3eexN;^ RLGyv$.?-ood%iμDA{oYwcwpM{k&wFp]?Bo"A'~1?)> d (AjЀt! L` Xpn )[%L-UYD^)QJUD]!I%XI$]c9%1XVHEGU%>zoD*UInKb<H\G v^}zl\t^<3/B^☉Ż11Sȋ_ٻUv'w|ꨶ!5<]SsZX+yq y&]׹rrNWv+ۖe9dޖI%99O֢mMggi1,,Xѧf ,c,<*DĶ 泂l*M==QSSA[5@n }p9}?n2 ;0*>ze}L5lBuoEQNOk:ض}g2Bă %@ Ђ`#$ `+. R)9+J"h1ذ{2I?cѲ~Bq {Gr<,Z$"}/n]A#<)Y6 ߚBf{uL&OHKfH7}X#-_+_:gm4-<ڼ+OKt|[?03vZ$I*8p_XZǻ%֞c{AB$j/K3sCG% Ri!oAoh:[W;3`!ArPT hAz0fp  ϋ;54B*q DQC3RU>Q r"C7N󈹎XqI;U>xpoV>J1MymrR*YcyڞzmU˿F"XˈU ;6 aZ}S?ClYs6D}F?ջGǻ+&BRYⳗTII]+r'g.9mڿ!,V!%kgaO>*8^AVZDEվ 1A۟uEݠ|UɣS' GPL͕o -9tUȥ#B_^^E_r%uBޛ%#BZ?@RD/O/mB.?|ٿ d (AjЀt! L` Xpn )i"l+*"EbN*'&}c|nbi#xi wiauȋ!ɑU -Y#1/b̄zx-ANl" 6:2vNm%џ:X^A۞&ld[@ER _;&W7mY9t\Ӗ*\t${Bɞ+3zjSićssftŽDE4]I5ó:Eavu8vAn㸠ñN7 O7vs)Lէ.z|ۆ E"OۿbS 2<Ͻ纐 a$H\?ۉ) LXxAZЁ `$0,`;8/? ꩅu Md1A:zhU01Lc;MV {7qLWp WNۈPK|ᛛ u( ʘ\el?}KK6N^-cS" I[$ȏQ$<3 Z j_GțA"A+j_g}'q0+rS6w&qٜƧ&gBGR]9-ؽP ^2# 9˨AJD@ݶ 0Yb~,9"_udZnjܕszgp)-gظPV{B96eS3E5ȅ[vd1%)s9j-m6ղy韉djUnj0ڃOܮrxtGnKx3W6$X4ֶ3j{{\u\mSuZxogV(]k.0.,Q|+,A<@ -P -Ԡ-@0B S,5!,,2ܬp)-*:Ic׺u`5FZen;orgT$lD£H(D­HDWK/D;HxsGBHxZ$0vs̪ޥhqڥ#mQW"[B(<špɛۓ 5OT:ػ ?A(%|P³w*@EYwl V;r1$:!ơȰ8C6*Lv^ 1;#LDYQmq3dgMT\ Lz0Q+hw޷Q_{2ݰtcs={&屠mnm%'hG(+Mo읝@YK;z֣D;zw?sg2Bă %@ Ђ`#$ `+. H; t\dv?By ?f!AZf Cx&Xj3`!ArPT hAz0fp g6GG ;Ayraa#^&t LjCR{s /qFII$AN[FhX r׿9EVbW" xb( b\ֆ+%gB] "J+&R,L>d}ڒc_z l,L&'&? T#D\ƹ<6ޙ92sr̠3սhLxcj3Z$ydCn%2OUYZMєG[}D{^@03^SF4s'/H,=Skc A uu$~.u)+7y+5H Rعfi)"oz q25h@ :Ѓ&0`7|dئY<^E:㕓N;"$ړDEdVbRA,AMG\+%f7o *oͽˊyrk$AVGj5Qd2]K%b%A~I X e <;{ R1Uq(Kxƞ>v { D:WW -8l@ p y<;yhS5Hk'׳]C r W-[.vA:MD&<-h@ 3 U=ƺb b2 x(jjy?j?>!@ R_x_W<A+Pld8AJP4FHV\x>C2@ܧe7r9zAL*d=D/v$9 IEb/ehWai=/8nʙ%L# " (W DIe S-À ;Qw. ߡWՏYӮ$/8?`:\y`q)3ra=qxxXrһc4Y1I wc ٟsq[s9?fon+"""2D2DDDDDDDDDR"+X;sJ@-_wW/cw¥>~)jg3Ͽ~ETTT+uM 6tuZwJW>toq ]/w3pgw׸Cj5k2bqm?oS~uM_m,1qby6WK_%fpL.nMIwsL;1NiϤ !..W1Z߷YPAj £Nzo -@.*:eQB3=QWOW'}d66$^0<Ҳ7ڴ7Z^n/9_R8GOWxj^E1U/غ)n /= -nLsݔo(Y mG(|9|M%РhP=&0`0<|A*}D9Av&i!V$bIN]FMb1@uĕ&GvD#"6_/GQQX~nv}[JXs֞+Vtw^{3Q_?geeeM6+Js*Ie(PQ,Q(RQ"壢TT -3K?k~ֿH~yrg}M?ռ{/ſN5/u3jיͧ/U/~?KiϪAQG{~uڿAڼ9ߍ$KIO@APDZ >^`RO,HQc Q҇רrѝOS>N5k]/[g<@Lmh}]-ikヴmMLm Wf8Y_gr=dXa ?s_TQwwpY(o?'мs@ -A Ђ`#  bp <  DJB,X_iy8c739Cp]B$dIPI?m) )'aJNKI0EHB0vbWQU+4C MM3PM܄7,5K754+/kµ.5Yڙ=֣ z{h/nh۞v/m^'JhwOz{jEL8Kp pPC }Rb/#Zm o&Vb%.bn$:b!V #E]F^r"AzӆSg/Ywǧ& <λSSXOaxĂ̌jq+{Mwӫ8/zu5yCuc{P:yyh*kmf>qNi:v6;+6r)LTna\\n~x׹B[7ͥ;ǷuϺ@ -A Ђ`#  bp <  D^ڏXpꀆr:QwLhel>wx 2cwE_! /M$P˓pPN3떒P$l\CԶZc/yy&|v MH!^(7@/,5 |{;..^rNLGz\H QV^Z:W>^p S2􅗖`/Iv2M9 % 7ǹ3wZlML.;Y祣tݽ񺨮NҌ|ffo5w$C[&cIVSYwc/ٝl+T^⍐5L1Ud`8҈IL隸2LR]{vLvS%*TQRG;5@ -A Ђ`#  bp <  De%k{ /!,J94[4UGCx/)& -Tpmo"B36Nn/佄0U%'-y/#$d >C%G;RAWPV*P.OgE*;Xa//eSşJPTvKu=QN>TwQ;^JψKuBjKOprXNBLJj\: sS߮td)roНbUE/;?S]35Gތ֖7kxL4ԎDs4ƛ;Lϲ}ؘj{8_}Կy -t>XoYOnF }xdo% >֕?RRJAѠ hAz0L` X1`8`x "U -rqzBs6WYfcT0),y - ? -G™p义Wy -D(|7W!f+|7"D#@-E"j)ˑ6B$_C$Վa(Z\ZG//UBaH]D|b P'=+q!+0gT}9cP qi=y(}=[ I#^H 6.ͥŻClNRx!6cӖMoh~D=>ݣ芃ݵNcjtpuƑ z=ڱ{iQuT7;*7Kyx [ؼ=++4}`s_J 4 Ԡ-@0 `+ ' xA!Al >ěyxAt;X ~"WhBR&ܳn p<8$&U,5k D'6ՄFn[u'4I/@ ~R䠈09LyM{py>t)&`M.M]&\h/ꭢO hhԙPlKKs'p3=2l\Z" -!߉M(zx{AQi(ܠlsEa{ GOnݝȬܪ;v'h6jʞm==^KWbhyn9fvcG>;OqE`dZ! \ﭾݯP6C=꺱{ O=5@ -A Ђ`#  bp <  D e/kݣgJ*'AbX #t;XωTG3qvW#Tfk'|FzrE? "5?WG>VP]i< Zu(w:TKy( 1BA.YMȻЮm73zRAΐvJN9v!(}V[w iyLAx.#>>q3qn.?iOy.5!1]v`K/̺۠k x -r%+w%i27iGD]~v(ɐ٘td!N$QC!Xt99F(׼sH!d?qwŗ)~CCAj -QيT *5h@ :Ѓ`3X -6;8 .,p^~@ϪO*HQM[$7ωS 1wKMZN =.H t^ D*W*bՂ'F%*_CAP,+ s +d+\EA bPh D(5 -"y3Yӟ ? -@ -2Y$1RADS}=@_X m9'B;6-{I˻P:5%5N`2hϽ-҉˿qCwwޚ.oo(ޜJ,鮧|o4PGw?Gsw -_S 4 Ԡ-@0 `+ ' xA!A$tTL^4D[NtD?KvʒI6D 8) 9H1A,xS۱RA4dRAl -U.*j㟆5dpyAҾ՞WPW 鼥hj3_PD>Twt _ V>^N> /t(]LAҘ8wB\\cw2qˮ̼| 9O,~ACA 9 꼏y]5/%GyCWFckW$l13/l7](ف}ңpMFAzQWQ#M7| -Rܽ4 -2Rt C-T`TPJAѠ hAz0L` X1`8`x "U2mRAʈhj芉>1X򈵘 -8*ꉧ0e-"NxxU _rA~ * rAΌP5D^jgP\RA'U r޶4Q+ vQ3ȿ߁dc -k[2ID9x>sAC;1..͝eAwloiies' -tK]!4lҖ~&vbH2j6͉'dgRBd[A};hS # -OdlSbs[Luޙ#]8l1S{TKʧQOT *5h@ :Ѓ`3X -6;8 .,p^~@ QzR*QwѶ4$1S51$~ CU6#s]D!B"v_JW-Wb"D)\ſJ}ƗYqy\jA$d*9K-v ?X r *0<7TkGA>xUcOQENs)qLzFt5btw||jz|\,*~*d ڒ-t= -8H坢`fMږQW,I_naM]s7 -md|_-RELV>?ч伄?_)|hŴVIACA -n{>P7y Q(A*ph;~_S 4 Ԡ-@0 `+ ' xA!A,Q.I - -D;gWs4I#q Lak B e#_ЪGR rAt -ܝ-+Ăy QrAfL F(Hf^0|]> -ϣK/y(N{l1g8;ŸCxXG?#|.;k󉥃XljQ*Hqtg=q .GAZbFPGO|}+dݪRăP|[.PP==UX6g_*izle_WGnOPk&r6j[]m #"gvs_|8b=>d϶f"6{^ǥ΁] -WW)yb'x}{jw7PbS?],h9*sq|M%РhP=&0`0<|aln%Tu]e*bl'&b ͭ!^ EQBUC?ISY;kjmIDTf[ړ3ɶWte/q - džC=3Cbi Rs 20fQ %osǏk*D4F0,`Āx8A/? q(wz@* QwC)1SgZzb  Ѵt66|6pMk;3|-zŗo1f[!BAQ,$F  -2/9zJy冯U傜a(Ug?ay!#BACAQ7cqv/F?g] Jh׵/YS6%:W%$ iq]B?NxwJ,ƧeW~tz},[XO}j!w*JS:m1n`VϙUXgUkLWՖhDMͳc&G{?8^:3>ד{8)ebw9p)y IJ]?}7.8ą2LT jЀtflvp\X  H_Ui^O.1LkMDX=c/.^N ӪIj) {R<%b*O> l]- sʨti'4܄qREh‡9:.Y~[_eǍ.|vSflv]D{_4.QGo9O{[waN:MHMIIT$Bt~6JLHgy6%nٕ'vr⑍S -UVf/=)g4[u3ݢ$CfcSVSji/fiIli*UČ 3!GLs*f+<(izŎ>2o~0af[CA7o -:"U>1BBΗXZx⊨/o|F+Y^x? -+|{O_xZ{7:e -rlb̈́ؐOKt ǺCB&&&,A -f,]}lezvwB5{K c=A7_o{7 o4,$ -o/S3볶^WĔ<ٞ쥎cO+E3= -Wm+ -Oti ;v -E ;PcWzw6Pb=?w -̽JK?T jЀtflvp\X  H_%7|v%|~[hk[L 1sk;/m?1|X9aO%.#`x$fӿUߌP\gs"ĺ2ti}5rA>0\/ >Xt -F=׏u|^o@A^h\6 -B-u3eRA^;'@'BAvw:Wxxs n.݉ql*Z¦)qq)q˟J9+S(+fthp::JݛGi^Uh'tENʐXuej*]o*"^Z*eQ-8R=HUs5egG*‘ގb j_I_4- qJ%РhP=&0`0<|!ORMh5juZmTfEeڢчh֩vi 瞘_$AU)ʕCQ8;BZߖ`Rr*5Qx'뮍XO\Ўng^|zDMV/}fwESB?p5@ },H^8uNħƧb9wFthFHp|;..x>6`OB-ۻ9:=Ttm.It|nުQOO2t3ۏZ,&YZ^Hζ'?_ݞr1tʵIG -OpCK}5RǤh؁dIIWoKwOk*D4F0,`Āx8A/? e^Ɗ<0T+yJBt ~4I̅JKZJcE1OH4(tV*cD(Rzb#<6ZcQ+BaƊ+n۰/.NJs"5,&c/؃+bBϻL6(}dA?-bC; ->x܂ťr\;6ssTwb^`3X!#! ,[Lfd?#-&%ub󉴘d%[򚅣&tWO~j7Tdn4%s%gb2Sf{{˥2i1@ZL^bR--&XLF`19r'j^[w,&b19( a1u|1T *5h@ :Ѓ`3X -6;8 .,p^~@2D5 -J(Ѷ]+C16_X .8j.=#\sJ$+;˾R#rA5%m\RA.; 7h0jWq<"WkXLq -|Co3IZ*ע /PESu)gi_C~jh#H(M]ї/%rb,ZJlRAr8+?VDh%ޢѮ -Z 9#BAD傜 5Kf?p_/ -r\  -2%0=.K)$Ϳ7S>EA -z/ -bx?HoYƩr>=!FzJc3Xw'ӷ,bA^Z~q#ǜx! -^ͯ lܲU7#gF 9Ywn5K/ZjS5rFAzP -dJz5B -rCl13 ] {`,^RT O/hPA4AZЁ `VA Np`>C }r,!tbAh+{a8b.% 2 9|! ´/_zq;sjAK<4 -myD(Ȇ5dV㗜vL+\$EAr#jyyKHl&BA G«~-fz#K'h]\|(ȷ ES}LWe{P]yϦq\;O@AΈyN`S݂rrCya w _f&0&ؾ7LP|)%РhP=&0`0<|A*W5ZV &d4,f@2ztq^/F3pprn@$4jpgW#tpxIIh#%ݝθ1 eCHۇO8hr-twNB_U{4CwmҶo{ E;^BAffNNlTS>7픞.=r$(H'(HythC -zoc -2 -ҘVHgGA=^|M%РhP=&0`0<|a( -(f>=DDPF]BZk$V [HJ—x+G|'W[("&D/ -rO>,cg蟖 2 ppQNv, 7K3MA&(ȣOQ>DAcys3%LbzzHusU"'B7O( ?xle#Wz.ǃOP&'nOдnby3A_s{薍ƞ1m-a)峎oloiQ -H:A@1] [^"5v?~hPA4AZЁ `VA Np`>C`WOwQ=>~QK1us_ŗD6 qv1D~7_DĮU~[hڣ}8[>5$jI_%޵__s H>2/d>XCAvϟAA]B/ }1^҆~.Jh!by(ק pi\*RBnKHu'Ǧ8t$ ` o?RA۠lk ] ~DzQ˝zl1#߼\=cv<=&+ {O,ʤ :) l+$#ag`G(s5R&%drgq冟/XHbOD\|y'w$u9!"$29!3p:O|⑐>׺R.ȑ\6y_ҳD>z)Y[: C7*]5ݧx1*Q?W`BYctwjz* -KM;!Hoji|5|Xʶt^$W/5Lښ=0<`b>k_϶sP*Hf{:z7~BA y/ қZjQc -vJ2diZu_HԥDhK+OIL}^c R 5|Bw0Ӕt~)z*'b.SrAz!5 !1_VW kU/B7v k(# t R&1I.O}iI,2MLHM q4 P,˺SSPJj*.,A~3xem[1Xc$훴M%[u5ȻI;6kޖ.4mdPmQ1U=Ci1#XcF;`;b',=iP}7#1Xc_=5jkL5_S 4 Ԡ-@0 `+ ' xA!AX|dⓀFzhzz8 ႌk.u]5Is(0cq掭"f'Yz%FQ7( -pnv/gaLzSK- -2" -R8B}zDf7%ރ侌̼ e T jЀtflvp\X  ,>%]˵M+і] OC1ii'.V.-|zqO=a:Y -Ol #PEeׯ Oz6+ wV(o1"5\3&9C;' 7\TL{GƽY7yeb2 m:`3Hw)ST!96.8 "Ԍ4.qsCx:m!D;@tUJ"abi#b -2:|ܑ +BF[L#{VyP.'BA^ s /OM-.Y ]䂼/ |5(w{]O +POQf}A5` 6-plHLK -{m۫G׵7ݷAyc']v~*>"$ݛnՍ!ꏍ$r6[vn5UK׵ƌٯ Qy鍵 oPlQtQ3Ў5 R7 ={{@k*D4F0,`Āx8A/? [b *UJ3OCҘ44 gO q >i'vn0CD'+H\o<"d -kXmI_}_Θ;¹? bP߭ ]BA[.5C]zae6qv_Ҏoxr;[:ÎO#h`kX8+otr.ŸXcx^ཬ(`JN^|"'ʥ$8$ܬX7א;$\_AɯOH}CN놥$! IȑHӳ~F_qP> I| }N˓^̙zQG:?I肴N$'`^bͤCcl(<.;=cP1\A kQ:)٤.٪~@O'6ʷf0Tu8zd[XKj⩐t`c -CD JYKXK`86~S a-ZskI֒ҙk hPA4AZЁ `VA Np`>CxKCD]hï%]+c>$ đCK!l#ᆗ.To bU_Cty8_.Ȇ >k,Ӽ?11MC&F1M2!<$ig]uy;uΎ:)He(""` "2PRDVJkEd(m~Z=299=9=(=rew$T|U,#uQˏ*T(TQWUzAaTܢl*RJSxʧ]B ;DDPn]P¯DBEGd[lA #"J0tJ=Jx8~In[jGFHA+mrA -ܷxqFd) RHasؽ(2QvcA6m=2C=eԿM>BfXs.Yoxu &ÉJL;1Ӟ$MEFu˖uVwuҸgxym_j^U#sTMEn͜Gصɰc$X0f-ͧ?uS(h@0 p'p>W孲SDE!b1"6+f[/8N9L\G { M o+wHݗ!Vlˈ~*,+7|ɨ/hĊHPĊo,#VE~`l~#b.ܸ[X++8b7}'_ ⳕ7 Id{8!633\0I6hlOokeLK 2Ї¤/B -<8AP:L. {}ߞ-'b{"Q$kYAe)ɗUGQoWba"-0ȝ0ȫ'cےP<]G>/vP_}?gz0`I:iX9 pVJ -YWeOqsA333Yw q>\QJ;/4,| Ibiy6tzeM*QqkErr}>~)"Ix,9-kYve>pI\3y>nd׍RA|Qo* MC_ƾX> Ļr,d8'lwH3dݮ'-POsh]N֑m@i}Uh'/;LںQuWLՌ굓n }@[P=f>foD{lZIn.]_ -o#lLNJ>Ch82H82H[Υ*) q@@ T@ 4@ t@ L X 8 ܀^HS 2&Ay)Q4e%Qu1 srڄ REeĶ8ƤaGqx#G b1ȶA 1rXǽs%o\}qᏢ7F ވA^^mϖa< 0H "?9@vG;Q|6 -YaUΪwAv J -;Cdg 8!gb&Bq&d-~kar)CA&hkS+'oT{Oj/굳u%}/n28f0͔?64s?IlMd W꬚A A?C3 odm 2Ai<5 omĀ8  -*:`& \n/A) D6ICۉbQ$NaGx".bl%&j]R.],%6wy&\^+ZE kl"y{q^Zrkxr ۢ~y_Q,D o9kW PA )բyr y]*&U?sF`N+o4 ).{F'n; =Jd,W8i뷦N %;ZE ` 򦃩xÇS5G6uЮMپ4cd2zZ`6 h)} E0AZa\$W0Hɫ%܁'tIz^4v"IX_:gb@PP 0Hf`V`.7 oK !B4GQ?I }#@.a- 2OZ>K>!|o!Ϯ1O#$N ?]A~_u%Xr'A"Hתq%`l̆o[{91 =-0HO1A NٹUL7س?U1ti^=z U3m{=QMҌsiVzRWT,-]4ot{mpFo7AI -ych?G7Ƒ7 -v"o{y#yRgb@PP 0Hf`V`.7 ҳ+ީ Q"'D9JTD=/4MI'^b$Y7eX'8k2w7z &#yC&" -_m˙~Cߏ2=}ѩi.FƴAF2rv޿W by4AnOW]e~;/Apt8Ù){Ad{Г&fxdAB WNVK9 .Nړ΍WfSR-LToku dAdE$E2}yѷY'c}lh ?k3öq9VOA_ tf+ YDO(˝axP!s0Hb'd\V{ziSdcy*T1@S[OUtlTwTMNav诼8:M4ciWPd͕'QT1]GPŴP]b*nT1uNKwN*\ymZ RE ]Ağɾ/?f$]yb_, Us:A[5_cn;@疲!}È*A^.X1 'pe:øhOY)!WVr09#+eAz붜}OxZ:Yt`0H[/HU=ߨ?«jJ uts 9ӌL /cA1:upI ~Ok:`I 7| o=ٶOksK a ϔĀ8  -*:`& \n/A^:Ad3|ZO.af]d:LlC1EM 9RaQ~1"bxHa\fUA gn| "vBD r>Rwԓԟ/c(g-rf } j =<ddo62~*Vrfy==)9LgӔr{XOrV8s/b:UKdbv;cCMFYjUvjhcnz}Cݍ9qu M7?rh=pm;fX3{,qqۛzwvdA;=NHU2@@@@X؀8 8<?t@?!w RޣڤfvfGL,m!\ܷ!;JH(ybP/"%lQžeSۮ*Dzd JHq%TLkpQ󑇵_92BK`wBYfJmG9=1͠,PL  #+F PV& -ٳBn -d{(KlJNBH\t*!5av367_U*.ޠUՖƪ'V Wbu;n4L}g(46¹7YN:Rtu펲Ygv55-q`zg$&*XhrB;[8d1 ȁ( -h $3+p`p x~~ -//}o+yNPBoJ-[~&Y~-ED ")a+^D FR³dA (AQBZۢ-׶!:6(3n{I YP")Gd? -8zJظJHˆJ|B n3w47RB!(.v6ؔ`d^fwN1o~i?_:cꌑn^^v0U1:pjd?JTm[7NșаHHy2&s3*:#{)Zg|q:cy;E} osR -uFazR1E׼'/߷" q@@ T@ 4@ t@ L X 8 ܀^HϾ歟vIEc$sk%& ^l哊hf s ;NEvF91"17#!*ZF;}/yym/D 'bIHqouWcA8myvm'0IclogWp)3ra=qxv̽ X[s,ƘŘD1M!N4F`g޺gs%{s˪(EDJbe!"""""eH)RDJRDkEDD ,u>Nn}w7MWT?s*iӦVXWtƔ[nf{JoSߍɞw/SSo=foԴ7cXmW+dllLXW~TYqg vLĥ}>ZJ|LJ;ߍ%fʿ?`{k:^*,k,T*"Jґݢ`"+/J1/rbN6sV Ք>n+8y ԙUJEwLͥ>KyKebJ98H;pzFi~mg_Ųnns>>L'O;Bo5e ȁ( -h D3+`ppx$d JuDl#*'*#vK-DL}D-4B7%X8: [I]$| -O7%oʌ6IȔMa;W,BIY+ɕZN|TF|R^Zywit?333Vrמ!N6zқtG7 &7I7yM^zS")9y$:Y+oX.YuYV>kzV>QYxZ\9MJ_^|𳪃U_Z)CN}4.WZeDd7 :kW7w;y1 :rKy2*3ӥΟ5K:m}-/Ӑ{c<1UOE/7^jkҙLYz(x@ 0M4~~od'hŞGi`g-{NN;~ ?DH  -*:`& N D^*DD3ŒA򉼚(he&QZ3Gkkn֗C!1f2 d,m=Aۊ9Osل/&,.b-(A „0E0HN3H6HZ q 0A u?7A̖+Yf -$ r p\.A3Zz^檿 '7ARӜ.oS\,gOacl/ޙʥ\ RUE)mmaf@ۏ 2({7AQ,gWTϽGF軆$С$SQ 2He|# 3AGmAjF)ه| y;=GA*Bi,?H{O> R]!Mve!OFyxӪB"~\Q,s3|Gz=I!'2Wy'6B.WLy;A!>bXϧ}^!KwV){,tCx!${+&t.U5^3N[KWz?B*_gyLMG/57r!$+ e+A|!M{:QBwvwABHɓ!/!|!dwN 0 D9P%P5-=0#0h``6,p@n D We)GDK3Bh3.ghC>1SLb)#yڶD; [K9!|w>'c-V!UޠB@*PP.L!k͏>g[b B$5h!G0 Ċ6 Ҡf I Z`/A|~:e<)q!07A\󥥦]| 1N'|q4_ieLc귗2Ҟ)!q|Q>z,AGrOu (mέ! $^I2ǯF4Y'B:FBF)C<rFߥw{BNd='D{!l?BHΛB^S"(h@40 p8x 7xA"HҫTYa%4y#QTe QBʘ&+!:bh Jb* RK,ĚAlD2Hq.{ODl# B$6hBAP a: r -"TBs -kX$BgVT!Bp+C|+Bo]Uz(c(JOQ\ -y!>2}QlKavǤ ->/&iuL>rͲ0-B!MC!M]<$Aq JYg倨ΘH Di?uePx6Cv$SNug='P @JߡhoRvSÝ#1KYL@!PHs+uL)R1cJQǔ)ES:uL)R1cJQǔ)ES:uL)R1cJQǔ)ES:uL)R1cJQǔ)ES:uL)ҀBN0af$'yX$a#D%]?яCx%,[1Ly!\'B̝-'Z -Bv-5!Y|{ -3߯e 4Hg ! 4͛W 2"r{!; r2[ԇWRZa,7~(=Es(cA\Hfٽ 1H!=Mp9MCnRx!wĝBz>ٿJ=.oݹ9\ȂA^IPRv=H&+7J[)ږ`Og9dM2M fs>K^֋Sd( RKBY%Şw`=Ҽ#tޒA5e ȁ( -h D3+`ppx$d JLKn^ V'F'@7Ɖ&%4K?[q48 O$w; 2A65WQ0MA|%h C?a …ta-u eԠA QƘe`0Bd: R{CĄ2v2f)f@yS rec^goAebxǸNƴۗvWS/DʪgL*nϑ/hZEKyʂ7CZZM֮oju_1/64艉˗[lN}[ރ9޹b}P{( ;ȺSb,3w_6孿+J\X̥Q.L]"(h@40 p8x 7xA"HҫR>`FJ^A):)e3MVSJ_CZ)c'ej`NCY)[;nMq-O {)OuV$h:hF? -a:1a =n@Rf:v o^UGMѼ{U#Au!ǮܳW/# rVVZc>P_Ps3aQzyqwʗv/6N&B== uij>/g)130յ?ʗͲwLMʗŇDyVspAʮm dJ519]P1hϙͽo, 'PM!|GGR ->2 |eT̝*_2 @@@@X؀ 8< A2^SRE6LQM (C-1aX8ZD W]D!i[+|0AdPA\"| - -* -k֡_3ܼA&^A6|<4!D A|)D:@f?]c*Bﯖ/_ca# LS`0]Y^ӉLoi0}oKwٹT= -vrdkH),+_2 OEQʂQ,$hJҖN`g{˽IEjtCٜ9tAz;a 0H4y?/s;E-/bnS^=t u{O) @@ T@ 4@ t@ L X 8 < $Ul0+ %6"f%N#Jb/}AZe4` h'4q.nD$y"ZÆ0A0hul[AWs_e~2Ȧ]HQ }Bda "Y-_JagEyl? B)0vc/5O ?Al%w9;ϹqToi)1BZKNط}q!Ѕ)} W S0l(_x)AQ^9*|ATJМT|_UL 55#4I2 A:)eAady)z rhzn Aa7N) @@ T@ 4@ t@ L X 8 < $Ulfed;(jfhn60 -2H#t\2$Lk aOl`ĝ)gFA;l -d_ ! mڵ(_h$7]TȭAB(ĸd$򚃹!s?Z !衐&}n¾~Qza|Ke:w -9R aKu8ӄT{jZZR}x..M';S+hBݛlcޡdofʛEGENd嵯]lR*[7/QgDݬۨwcr!hѦƦmO{8Խf=~_pYO'r)g[ 7 }4=s4ZGyj;nk xA`@rJjZz`F` , -lX@ HK:V@)hjRSvS!MOP6i PVxtrJq?D 9{G($uVJ*cyD9!q:q^?kcߘD c&sCAw<|b da̜!; 6ٍ!~<,\I٪;*pGecM/E驇ˈdY7;XK8;ϧ).'q:}>n/(̔/wmeZ?NґY| KQ=x A^$Ga$cI"~,b6HYR?J{i6^ڙGc4?8QVyҏѴq0\xN^zT) @@ T@ 4@ t@ L X 8 < $UK R&&$@ ďbh#fb]D%ֺ@($luC&"$"ZC ? -Y$ 0sg fP(oK Ӈ"DHYG Bt# 22Y-`nρAbPJq(=0W/}9@Dw#gӜH\ Isv_og|1,珉 &n\$-0矠#ǟqɋ'xEG:eG.UmK=v}fͺtwQ'1y}SaiK_k麳6-PҗPnٹ<ߺ[Zhq26~NyN~s$H ,@hJvi7L 1Sdjb#fbk%zfgj?F%bE1K cԪA\! b 4HD\焝eOۣ v7̏94ȗC֑A S`l{Ƹj ^(0G~GzG-^D& m6L\l叵 ll,4jwŧY/)-,k{"}fnA\I^1N9Q ^pkFtENd^cA`k-OS֚0Hً0H~%dd.AN{1Ad)_n[&`僧 הDH  -*:`& N D^A!% j+QUQ%Rm#*o&Fb,&. #M6La;r[ HJn&tݎD  䒠A4! : Ž9Wk]olpU aW@zaw6X}N7/*A9"k@S}K 3U;'^ŎT1JSd#tb!:ybzo 0 D9P%P5-=0#0h``6,p@n D\ouO(;v&%~j10͒ASdVO=Sk dv.%b'4A [ż4"h/o|ٙ22H AΡj习ACeU_]Umנ2h0Ȣy+JOQ\eA<&391Hz35%͞ꏍs1N/SSxLOc>oKߗ0eKVft*btG3y[U5/c ]Qڹ̭m$Bg);b6,O `;s=4A;x抇Gi~4io>M{zhoN$trA)5e ȁ( -h D3+`ppx$d2ɞ#cF$8QLQMN!.;d&SA [dA?L,{#aN  z A  ?^A~C B6 !χP -9\J] u݆ ePxsVɽWA!:NQ|o4^2FK'8bgsҎ\^ǧN9W3ΕyTlBf6”ݍR!d~he?ݞ(e=[Uzp.A٪ ,fI2L2B!B!#>ˤ4 <ɽ_NA!#w I[Ϊ6B!;Bˡ|N)) @@ T@ 4@ t@ L X 8 < $WԮL&S!o!Ebl 1I!y2v0qv8NN"'"Oٍ8B!RT׃!B!C!eG4 c-L6~^! A. -QƜr!R[~1 C0HM(ctn…2Qz -;2Dήd&D6f>NR4_:BHZ4M&ő?\:vBHsK_C*c,+]2 etd]Hb6#JYXJP'h&>kzD](}ӡmד'* %Kj,uͿ!5c -{i^YreL R !nZhG3 T[3ej5e ȁ( -h D3+`ppx$d J;u[2wRj]`uh{#30:$XdfN6L݁9yyP $>A>iI t(s,ȏ"ϰA,Ba1A -;M\b;0H>2z1/ soA.FA=eU _SW?@Ft4Y)c8SpjK S?=)>3G,ŒKeL/@s<Eʘ%Q>~$AS.gAjA:IO;ڗ|FdIʘ\dE1%= o{oݵO!T# C3+tb(cV7k"@$PP 0f`V`'"/H * '$yQL7#@NhԬ̣vKG`l4pP;qvD n8Azs)l) fMa1wm^t7Bȍ;Zd[V # -at^Qza|eܚ\|U5΍:]iv.6_|N{|,sT6ǺbNҿg@j2+;b #r~YG#)(kҨr5ֿi4'2ڒhtM? ҬƩ]MG+YD[j2:lus﹍mʻYO=?r:S6w^E.%Δ)mօ99羿Jk"@$PP 0f`V`'"/H p!%i7SBJYCj(u)QZXF -|RKYRʑIU)(^JfK/62NWSYBG cOe{O\ #OK`i}%yltɋ]]겧xMK:mgKW_Fʯ1xLÃTt=R}G3B|e=@9 -MtH ߺs6b}U=@{s|NyN|}MѲP-DAT%DG4yD;GrFimZ9lf[.qd68G$홦ϪleZR*bA-ee<5A^ʭO$hF&qQ7qDZ:mfhM2%No;B46a10;+m3s ٳad`A rSw0[FjjA{e ##$([ רo܎ƙc)q1i&Jll]pٝ)1?wŧO K^D6PY6ɗ 2ʟ/^KP}[USGDu ][u y eIҗLJ͗!gD>1~Osu0H JA>AAZZaa[*"@$PP 0f`V`'"/Ha 3D>H{} 3D;EtD?J ؿr0ko`#m' }g|~ 04HN0Cpٕt9לcjk1haT~o9{B_5] ck˒N"F`.Fy QzaeנDe0MޕSX;jb)1.˦rb'Lgw|fY}椴f\ 3w(oOP4D)UǫEuL(;n>goaq"X3d2H恏}$=G)۔f3zigFO`')!nZ<@{˽Ď n0D6@! SC@8Q'Dz͗ѕ).b"q8Na \_ L4ôooI 0ȗB4_B͐}i3Idg y3:Hj~mU!d _Uf 8$0љM0HZ")OT.u'e򝂮OtԎ_yZf:( ) JsG9v^y%lw/J;aj^O{A>>^1.^T{J3VHu9} T7o~4pDf좕NCEs(:ΎRU٢a*AӚ,۪NCfK2Ώ%*[A2Y2A2>F3*2NdAdwAyںwJAJAj_Ci߅ ҿ*&Tk"@$PP 0f`V`'"/H@z"k!Ld:HE` -H:ik;co$bmM&\/H9;09! rm aor saѷew_ZhÝ -bss-LtaaAiGnAwaoST1Ϸ '2i 1b9>ŧ NW# -N!]HMNÁuͲ-L~*n9$&wE)EUՓzMמ(ܬ<M}PsvRi5'l|gNdNdy=H A?DsR3sw[;= ҁd]tb] ׼rÃe ȁ( -h D3+`ppx$dU&C2H1E3QUQgM5V]m$R RAVCYm!RD ".ў  r~.hG,ŭ ";Ci-| L0L}+Ja3 O!la ^ s qwD驇ɽF^9m# \zz/ޞ6X.]T>sr-zr{ぽtyes[駤qgaQjbMT%Aړ[usҝ/l34dN2u4 %f #dyx'2H0 2! <ÝU 2U R2 i ݏ6 q הDH  -*:`& N D^*Dv0A&VҍUq@H$TKS`/q nl&\᫉8xD`c a[!aPaosUia R4ȅ!2{nL 0ȟa#d7lρA~Uzr4ෑA+`\]e#QDݼq?ƙo;}jH+W.j{ 3 t Ef,Aѻ;JwHTUՃ'h.vy>{qT1s Ot{\5- s 0+0HtG0t 1K[C ] sGaʏ`0P)5e ȁ( -h D3+`ppx$dn#tF)>A4DHtD?F KLA*q,Pw]D$^;(hu|%ıʡ uOsϰV/潯4|yo=A:ц0H: R5{1[Yʱ UڍD2ȲUw&JOA~ x\ r 8\*q})))vAa7`w`OOU1xM`@rJjZz`F` , -lX@ VRe^LL>@SD9ET2ɴ3SD`7n Y&6b!E"+5J2ntD̐yϮsk _aĠA [Ŭ`#ww 湆\tABbnuْuܟ,0n\@S"/#2t ҚqA\t`O;ᒺ~.Ť:O;V98V3yɴ>-#@sMF@]Mj{@\/EC_1vim[tEޗuzKs}s6V䘞)>Sgq\Sc뿄KŽ?3oI_bk{JTRx)`@rJjZz`F` , -lX@ Hrb|J~veʥܬͤtQ,ʘE2X)k!eˤ g6R|%x':^]!$ՠ$ 1v:B?$g/ GdײY9UK|b0pTKTu:P\ -9{NX[16qXQ7a9%VHSc†ǧ -ORӤTߞl+=bj0o.[3@Ψ/ʛQʎ.T*4wRiu#Rθ!Pq$э5c>K45V֏Y߈18rr&rFC!g}x9nSw?r'j6X3iԏ1$ʂvN#> |c71IJ k76vgS>"4Ng6ܖ!Ts.?>0>ǚg|嶰9ްncB] 1=w^E2)]pqc7 e\1ϋScjQ\O7T|>=>-gK|1it!M2x‚.0'󩈁'yyﻼ3bewjR/M̋2J7 /y'_Ove@Rae8t;@ = i޿&F)a1u䂏yvMTFyV:.A~%0њ܌ }Ny/ThHd#ݡ]t ]0HtX({jr}idET1K=0)5e ȁ( -h D3+`ppx$d q"#q c4QX2CK&ec-dX5[fkL?a -GA\4)Aa73|Z_rew-G8A0? =I(A6f C} MzG02qdfd鹍sǦIOMs~eOa=gi)i7H>}272f-r% < 2.Ǐ$(yQ5#JۘU 6`+IG`{o,c KRiA2H ҟ 2dH)xY؋ 2&ulasxM`@rJjZz`F` , -lX@ B*:2X\;Nt 2&бm'Nbm%>%l-qNn-D3 aϰ22¸T1Ǥ4 2ȄbL\i >Y{Z\'i uͲ+S R){^NPt镵*'KTg$h>ku] =GLu0t<2{AN S 2$ C ;] -v m301tqern 0 D9P%P5-=0#0h``6,p@n D Vi ^ e9Qun9AK*q6uJ"Fb-#lkzqWz1UgA Ε Bzz1U;drּHh|`y)D5ǃ -9"\ {(DV2<C!7' -̥{PƼk!BIKPƜt)cbB:ƹx;vAH킓Kg/MBb3mVe'?T3$F=MEfum,lVT,߬iݬij&6m5U]_{R(ccuDbtJsVcmm+6PsΧج|V@qS~-tn{9%6ݵ6SޮB*^*9=x?`@rJjZz`F` , -lX@ HEɚ)fRi RRvJCi(m=a2SFi -tRB9z(9pn/SBnӷ(ȗº#%sM_ mp3b;%-;v@Î!ܡn\a|]:0tGKD_ kD)GmICH7wq.NT$鳧HwjǦ ii>봑-ۗ#ٛeYsw#~,M z4Gz/A^Gsfhx#ɸX)]a6"e(e+~c9#v!ŏ9ďPH٭nZlChcxaďG̲SCxM`@rJjZz`F` , -lX@ lFd 3(戲&K 2M.=I4AB2$ G0N3D,y׊U9 qiP! -QC! m5ON@B!Q\L-ACĶؚlt҇wD2j s% _}_UJBED:!yQe1QDSDA X;Ey]C<Մ"ކ+ x,`y$1A`@r܇.fN\Vo7^ . w~\yYu6T?fwEs[`c/3a}%2}wnF1iσ`?bI0%2` WДZMA&&;z_j}9ݴSњ؊/5^+K+䲞\|쵊Jr0׫ZsL +eEb]Mɲ e?4c.pk#OgGN4RǏ{37M>|Ow\Q  PP Ђxz`F``vn HJXOi>%͔PJ^M)*)e ԍ'唾keS*B)OŔPꋝ.*O"v^0b"ݙ5ѭ ~ѡ' JW-w~Ǯ(1q> mV1arP1\°zi˵jT-˖j"s,/`Åŝ2ɟ`M7Y4{#fw^p1X <{q>D9̈&ƒC9qyÌl$yyP*e[9^\ܕ)ܕxcN~߯FmChŽB(z#^: ch4.\ym?+o@(,B8UʎL!A "  2  -*@ , - > R)a"C6s (tHl^QA>C 1 Lk~8g{xs/9[D9$] 8=~k x`w6>``c͑n3Ӧ_jy>{cx -yaE^W&޽Y5h nHt%趰L.ɖp ,5bm3=sr29&+m8B4K%]!rRV8B>yxbu2\=Uޛԧ_Ti{O&ǟ+7#7E~sS`Cezl-{-gM8k:P"*Lf*L-OhbH ȁ( -hA<=0#0 ;p7x$Tuq飅MQzJEɚ)y=褔ܽm馴ulh2QZIYF[{ێ(Q[ǯ"~^[x0:n[ȥ=.%/z1uY wM^8BsiF#Q˦?Wa⦿iY֎{xɳx҇ՙwM@dw:& qi i?u5G6pƚV7G؇m#}2$Mq/i.F~0I8Jٳ|juM{)ڼ2)n_Ͷ_^e#37,OQ=ZO0G'H/!}԰O׋dvΦ|  PP Ђxz`F``vn H -;Lk븝mDVFDI&np);l/ZI-BiV],}|͇ -!B2D{xl -ɨTELSx,`Gx>llO]7D>xf֏>l{?A ~|`hS^oC2j߿7-͑0v3wflkm g.^U< h aow 0r&K1[nG2 - U0H0 0HSAB@ D@ $@ -d@@ T@ 4@ X؁0 | Szs;KxHʸKܭK)m\n71 c/wRDNĽA{l\pԗߢϭYAȃK.qrс;5Հ[uu큨-$N[:۷gw,{f U0ȡ0Ȼ% :vp^23L"$.ɮ4k ͙hJKp:L)14pق`qA.97Нl\U?ۀΈz&9H:Y$ q)Fx inu))֗[:] ~8 R|do]-{v m `Oa=`af]v\ 2=ĀX b R rJjZt@  <^ u`}g& R[g~a Yr!#TJdD@TDM4%D[9ڈK%X󉽁8;x SD#i'Bځ !2Zsמ#}G}7Ҩ- d}Mdps~A_7 iS0qQhiz+ #` 2.62L#dKZ\ TrA 9c` -a? t[Լ`DĥqU.F6Ne)fV)w#*XەMю# tlЉRBH -!$ʘ !9{&BRSb~4̅BH!$B !BB!!BH!$B !BB!!BH!$B !BB!!BH!$B !BB!!BH !GId#AZdțb D@f皹 {q;X˸mMi%+u#d9oOh=0"_ ]~>l`5. -EP |nvv-2ă0ȉu |Ls3t-g?fMN+71-H Ġ%dUw -ay]N eL*ؖUF62$?O|2|=J>h'7_:Q~? R^ dԶ 330H 0q]}rݡ}||mcmAf ĀX b R rJjZt@  <^ =;D~n%aH&IdDI]ކ^nڃDF%Lab#.n~'++cQ7͗1G0 0c/|G_ 'JAȗoA*&7UA>}~/6G 0GZh2%}5 p 6^#Bz;*fNdD `]4ɎaJL Mk= ) 7ORA(azŶm6 -{(IiS@I /C)ʢV5u{~ve:o;FgJb U.5lܹ[di-~({ysȝӜ#b:|uDɃ_RKNO - b@,1)9P%P5-:`f`V`N/d -O PbD.UTJTiTTzAneUc8nG˨x%"%WJּ~+W,+!tռJWBݵW_R %/څ [Ha]RPR⥰fټ~NJy)͝²R9//c_jnܕP>j }憬n8?xhYwW/mLG&ӞeTZys:Ȱ)ђn;\n;!bJ[@#D(LjzϱbDJ"iĊ~&=ǒčyqUnF6t I^\a*e1{2rnuKoIFv7Z¤"?XQ_8I֫k=B߇XQX1UXQX.{i&lj0&D< k#0ԶO8@=xƒsCGw2J,!뇈&-WVlc)BpB&WӝpIrxbee -+#T&,/byʄZ@xm-#P{ql抓ݴ}Z1쯨LfQ+zxNGNt!a&WnMO8m *5;Gå+ t+{ -ncD3}Id#hfd}I/TSrOxpjuMξmi|N_5 tBP^(d:/,OnCe2 -Գ ϳ, 잆PHPHՁY31 HȀ(h03+'p`@2H 0PH/#QniPq(q&ƸCD?A Ę'4eLjs'}Pw*Q7ʥ -yL#( POn#G.vk<2 PW&V '#T&XWbQP0 MkzG> hT&wqA+#rLt!dٳ P9wQU0=hgOGw0Ii$3#3*fFYrh^>7ES7E>? Ň8X? :Ϟg 0H?zj aFvKޛ0HE饚[ ]V" <"%i;(& -j =AJY}-1c;1˔jOan!2oM+ rugbF E0_jOzhl o] g b[GnQg"Ԗ#Asdd`nDBZx k5h9NJӔL vd''j6ldohK7_/,mP1cM0HK;*%mS.i]NyߔʜrJ՘u}Wӳ׫,K_I7~&2L~GGS4 99o -;ڃG){v6 =M_o ϶ R?g - b@,1)9P%P5-:`f`V`N/d -|97kH눬ȳ([;hYpM= Bb/ n>aaʉ\ь "`y沐6Wp:zWLh[m|h:"ڄ`? eU7o< jw7v@PK w ۄ01-$ZM  i@FB. kF'cϭ=>.{C/dDC'|H?j%L33mjû?Jєh{{A*t{ɂ(c2H~_`mA=$pS֋yzL]o4}Ag RfG31 HȀ(h03+'p`@2HܜnX#k":DDRMyD=U?HDUCmDS9wL܄ -bf$wq^͵Fy7ȭ "ay]jEêj[y7]|!/ ߏ<џ!|p? tx xG"2x2H aE}j}2n q<9k)3ra=qxvֈ̽\[ߓOvD1f1Mcd1KxNc ;:]mOY9ݥH[D"v )RDaEՊqېj#'|>^|d^}ckoSVjݚ[o ޒ~-HϺ)oͭmu/IЯ~u㶛32ouJ`q^7f _,q%2 I?g_3H',}W=uO׬L,.[唭bd\LVv[VU>*lU@*>8.~.~?^C-|WCŅQw5zHO+> Oj>k񻺥晴FYW5ST6]~[Nz]ο3ot\aiǹn|}K[;/<WWi-:-]Fß -0 -96HH`OJr˓38D<-xy'[--b&}ңhV6|W{ MO[[>Վxyea;}n9/В)[leo~c'yAv4\Gdl}-CqO{A>T/myA/eYfU6.הĀXJjZz`F`f, -l.7 Ҁ*2>*A[ei]6C 1S61`C2kQ30dwra_ gJɉ`ԢA)ZhO% e0'  z sT4 U rx͢A^uA.YA.gqm mA~Qu;G_; O}4 B7 q1d& 9X;ɑNv$6=+9!#qh6;"d5{ѵ! H)ʃ9qv^=y)8] ^klM5M5#Կiރ Һd -d=2`2H食 n } 2 dYdҿ t<51 (h '` 8<?4 JW:W 2Eh>&v$>b8 3S11WXVFYF*'lqW.xs R|  ?2ș rdb ty2ABlRnV-eS rdV)"de -N6H< m/FAګbxᩏv8?}Ï!]b LdBDWБJ8XW0OrەxAtA:\#ٺv? 9eߦ8Uz')ʞ8P:㶝pO?R/hzhAArJad{nT> uzd2$Ar@ @ T@ 4@ t@ L X ؁0X@^e; 'd(Fzhm;э}1Tc1us`Ybm Nb%N4WaGp]N(U̪U͒A b 'PoM1bn_ ;rO0Q `%4IY e򹂈UAs Y:]0-A0/X|NlES)^3Wj|_!> ye|UNoxdK[T>YZr@ @ T@ 4@ t@ L X ؁0X@^E01#V A%a" 8CLrs$bACb]Ao :A\AVK6A gWF K%DYFTmDB4uDLtD_O ab"--dbm$Rb/;EUIb.%\96WAdQ + K!h] \Ĺ w/eo% HyC2w"D P r2 EApv$h༟t>@2 r+ ,8 lu';3$723ؓb3=j9>+xt1[3vtjbje -Uf՚ڑn4OI5̼o2To*;7?>K!j[_Ur(8r5>KG6S bo(xlΡGᅬJ+;O_ b@,P%P5-=0#03`6`N`p x~i@,(yE7R0GhRwRZJE(vPG)S3en`vZGن({ 줘FN(oP:`^/#999?c9q25ëO4G#90`#],cD2Ǿeq]~IW Bw٣(4QuьDN ۈqFN:IlIÄ&:w%7T ){"]2U^~ .#{mNARٯ8 wEA  d$!{ܽ tYzk)=UQ - ;9/Snc9z9AބAzw u_4؃ -yUXm 7 0}0HndEN,( QM7]'7fb#~ ClC%v¶w z-Q:T5KE0ȗA%#ĸ \? ݿVk0Ab#RߒAAis`L^b/TXԼG_З/Gyݷr@l'!B5)+HOrII 7Jr R>%j|JOdz︕[Ы𨳿rkOۏu:K:@fd/ ͵*nNJE-'0ńhT1D3BH8ZKL\)XۈGQ4H3a{p;IkZ gDb$"$V2R"$e4FbX2'ߠu$uwLb U>]0HgQv.T1_h ~ G aCpVNs5\b lJ9HNb!Ǖ&ddT4=!yN0HYyG:0 )ʑTͅՇ -xMɁ8]^6(LuȞTSTأ$~eh4hA -v AF`¿ d`R,6mAL d / ӝ הĀXJjZz`F`f, -l.7 Ҁ*CDB D1G-D5@fh臈aT <+X[mا]\ a* 7wz}Y ɕ rd#$iySe[2ȡoA6HA>0:*3$U0]FE!bh5>y/AOιGo/A)N>sh́d&ɺ *(FM4H'ՊNjBdZb&0c5,N0zO HY H)35+_uR -g̷Jw򄤐r2%Ҏ2G BPȖy( ʘ($Y O}4cti: -JY9 -a< LۑH`BIb -0^Bzk=GJBrCfR- -G5!=_to#~rG!(cC [,BB&2 !$!_vT#42ffbc!$BH!=!u8{הĀXJjZz`F`f, -l.7 ҀK5 - YQ!D=F4D;BtD)bj&ޅ2&_n#^l"L;q[Q2(FOO!B -lI! -9g -9젢-ݵA\qA a $A~+Ib4 ekl#ʘ`/b`=w-10G%/}^x2O/e!$y~1n -e8nx sxCLb2d^g_#ں!50H h> -ǩ֫R8]c¦ܞg5nO5 u [,0H rt R$d%͇AzFHs3 2޺ћwd0 rR(c^A:`m+#ni E2fR\έ&>i#iqSb>&Xmۉ3,6BF &q~k;0jV*-$&A rD2,A\AdZ0e 6 R$ey r"g ai%C.7?Az>hKu0;iG;fPxaǽiaWbLF(!=#!=`]lJdILwF&: Un@S 2(Aa/RsyqxMg)$ ؽ+Քӓj})XA߀AZA yk=2h> R)4Bf7zGAF`ʭ0HvdQAr@ @ T@ 4@ t@ L X ؁0X@^ %d@֟/ʘQ$A;LVj;1u`Qb#bo 9sF [#!iqC̒!Q3uT4pAAb"}qu -,|ݷZ)R }A Ҳ }cnrҶ 9RAt;GaLG_\a/@$d'+ !OÕ) HdBNb7m 2TF>Z b=E9An5Coh>CZ[5M55N5OԵZ,F֒7aiXiA  aC#0H0H0Hn- 2U-R[2H92H92H92H92H92H92H92H92H92H92H92H92H92H92H92H92H92H92H92H92H92H92H92H92H92H92H92H92H92H92H92H92H92Hh*"o%t`h '0 &qQl S=1o H@H-qN\!_\)&AΒ h*z+5J#NPQ%) -gr2ʘEa=3͋ -y -|2u־4 O}*?9@_ٞ`YɎDeL02p \;aNT /}.ϔ~{qZe -U^V=ߠՔMmU_ \b+;ߘ[TV;|?:1`+s/O(jd[ƷS8[{Ҷ?v|.^Shb( -h A<+;p ܀^H^؆+gYHD1'6u Tm8C XFL Xkm ga*Ľp$;:jU)uGCmXV[u07~AcB!t%I9'As yI#? -< GB8 -$?|G0hYi>b $ OB(2.G:r \R KK6)+[#ߴ>$2Y.^12l[nz4 hӍ߻^uˁk%=`KxuK7ꖾ6-0H[Qej 2чޚn-!M0HepPꖲ[r@ @ T@ 4@ t@ L X ؁0X@^ȳQU h{'lji'V_f #u&v>P>de%<"D !_4}'Z%+R[-K ByUɉ2 m7 *$;{ />AƯAVlA?ErC?>-#/tqgNIp;X.O.Ld=I LDL΅AgW' -e=6lƭY wyceav -L4g.-;֚/_lPvRVa3]{UauȦw(.{d|zA/E(jewk b@,P%P5-=0#03`6`N`p x~i@xyuAŸxH8&}?1c15sAzطvq3].aȊ|zo ra%$D e$m.9:t÷d#) II_A]D^}< -6>j.A|G_Dɼ _/ ЁU2ȧ?Y9qY+ Đ#9p$er&)bN_SQ Uk䵅>a _(ZGRq:~Ajo YP 4AFa']4H]6d8/<@/l\1JL䠌d֕!m85 ˄ȊzewcapFoGc/)5}ߒ: +Ar@ @ T@ 4@ t@ L X ؁0X@^{4lPÄ:Yı1J޻RQ>JO)M't픾E\L- PJIٛ)gSa 7@y)QU$HQYeHeq'JW',ٖ,e~u%Us*qq([5|C `aUcm -6+G_#w\vreiZ]YLOAXKt$nGRr'y\։(0wL܂yݼnz -#T.RT.3yq򽼺K=qQo_kL5ͼj. rGÛA{\GP47#weuT.5 wtȋ!e_rɯCہʥUT.C+ PP 00x`V`v p>Wɓ rA b:O)k]\@ )b.A! b'ls0Ga(8'o>> -ZRI$B~ Fe]'dcӗa>^ܘ%&OoAV!w D= h/&Om>ѡ>hxk37z$:I!%&;IfdOf|/w -h.b^۵գ?VUyܚ/mkuu{<2P^|qzT3+k/t\o/llٛvQwRѩ6&$t2u{--imdC/9GZeiwOr@ @ T@ 4@ t@ L X ؁0X@^ȷZ0HxPQUu(+bh7sBض{=m/s(#&q n3_>EMAVG0H2 O+_i)|v6YI -YA!W/#<sPI^W/(iPHً(]yᩏvB| ->ϊQHbȝ<.rpL'bҳ22ғBHuWs@]#?mUr!d{ݟʠ*^5)ڱ{t[닄Co^gܕjjߙj0Rh}0}0! <,S.V 5onsv L!c!!xM9A  -*:`&` ,px| , zp6>'0QM8R#PLTN̍B:b *QFV \El?=$K -.C!k٨ -Ɋ2[-D{`V[p룝.09 V<De}jkw/0ȋG9AVyᩏft~?7@~t5 e $wHpI#b'9Y!m_ kU6p']w92Ї)dzlL6ӠY3ȇwzSۄ/MY,m{A Rڎ R$܋Y W #mS Yd IBm 6 b@,P%P5-=0#03`6`N`p x~ik Ҵw\G#D]Q[܂A`"n RM N(袟f#1gE0Hd rn(a>A|(lbTmaC{a{`  3 w51 (h '` 8<?4 J ]0H;Qex DK({#1sBiȶgx H=a{qKc@ʈoh)56 ZQe>UMKuN2y]Z1I9_f&vŘ-b ׵/˄Vj7GAz}tʞ>MLZA`BJ 1b$G2 -22IUQK ;Uœ|<ӹtfwLKXomjsVw|_϶L?>>#g[0on/{OZ|=}sCji9wEQ PP 00x`V`v p>ϕSt|[M))e픦VPZJL”25P&avelrSL -Sl#i -ʷ}[Ea:5G$#\0p) %Da}r]:,ʍ]Fse#|"G}A ̫!J9‹!rLvSm煽/PwCܴrl:̐HOv!r Ggx`(# %N\|jT?.Y#/]G#rnALQV3fɫk^5h@ش^__wuƉTSTsg"ǖezc~D9TeEK1D- qS 7 rTF@>9r@ @ T@ 4@ t@ L X ؁0X@+ēV$DWF N.ZOM^k`Aܳ&ڷT䈉9&-j 2qx٧:KEҾم;NAh{_R9޿|+bBOŶ0Hq G[- sw#r">G vAG\=&uR9+A~ Qb.`, {GԷ Dm{\"_m=}\rOQLarAf1$ Ȼ.tuAs22=E>xL8Eh]Y$1"}Afa~ O}sUC342,0l:p%8$K鸌, -&q'A>MULmjybQ4@SG[YVu#9f+G_*JcNg^h9|uvE63*f[Q Fq*w(n޹0#)َ*&Y P br@ @ T@ 4@ t@ L X ؁0X@gوЅ_l#F:"Zb*[XzO?$z,#LqkW.ΠvFn^E/dPbL ,eOe KdG d#KAD0ycKĥnpҶ)?,.w0us i0)?E8?Y;dҕI\Pc.˰ dp3̄LI'opCxЖ'Ȼ"̾#+fNQfoSܳ^kFNnu tT\klN5y#<+tRXFy E [A= 2Hǣ2wp]wz&Dg_[ R הĀXJjZz`F`f, -l.7 Ҁ*D޼p~r+Qe7Qmv*maX)V1cJV-%@NjԟH AJD /? K}eV'&|_x[Iϵ mwrM#dkyᩏf> P!{q"IN䒂A3lH8?HOJH.d]OI# /jySV^dAd|[y`^h <->{|^9o2H3Zr[ -I IBۃ ܇ ]e׊a{2ȸImyd4eY #d}3^Shb( -h A<+;p ܀^HbAE kqQWM1і`A$C2$X+طgxHaKHF'\͍djU*p LR tT-Y|7| 2HdK*敫#"wR:i;׈ rAҢA2H -U3Ay)2i~Abϯ\bBVF:H΂7rpLG?Ä86Doh)3yQ:yd7w!KQLQjxWKTLh tMmSdkS }˱c 0@֋ Rw+2vbx{[7VBG 9;xM9A  -*:`&` ,px| GyBi'I̓ #=D###OÔx1/tR"p] a&kIyzD,ԋ%LD=,qҳNQ,9@6}NR`HA"${ ?}0Al .d.96 a/+n/E^S]ou#g+=[Yѳ5cgk+8[?E#oc:9梏/ ҳWkV*gr;`޼l[gsO\+>x.@cUiH, b@,P%P5-=0#03`6`N`p x~i@x?oYd*WLu.o7ngn0l3_~b93_ndopgrwJFUB䋡;E%@t%\Wk]n?CU5lHQ DzopV'n .%m3pO^[\]ywN.>(Nㅧ>>1_]O$3t$3p6a(CIINٞ F!#:yA}Ylس)ʖ ^=i[uƲ.%B8Zg eBooB]"|zdO*&?B80;Rű****ߒuUOxM9A  -*:`&` ,px| RF&*ND5"I9ٕP-v6VWjU[8s &4[_R"6B.R.BȗRRFPȦe?>:Tٿ9 ~7?LߍPpH]b}vU l]Liagk8{ۑ*ο5WK y`t6Bd6919yɡ I 'Hن=sǓk [% o(ĩ׫[-qnk3{RMSͽeuD\;w5 #Ϣ.: -w˵ <;yO!aNorU)c w51 (h '` 8<?4 J\>=0Q&j&]LL kk=%i#q  qOnx?C|GN7 rn D.g#|yBH+{;`ue,H|w4l-1S8?qwt]rk'Rt.W\Uܸ\]U,v5k]ccvcKgMT~Ӓbu([3oδ_.{=cy -v.j7H(AJP4FV` x Vi:vKdyDC̗$\&G -$|\b,:vdKyKZ o0G-87K\\J<˺1ʋŪP^|Z&//bZk.ۅ󟞗E|8&/\vB狨8VlXmv*dY*+<ݓѱGZxQmٖG'IMv`i,kc]6ϴ%:\i6g&=#.r0eg#ݑR:>&n*L/+O):zxeSGI'ϵ,]}Cȇ)c)lg^v -sv e;yHb/)馘nQ6K QO%\vR|w(]: >{-[-vh5M bǎKjɞ%c nJ;{a9YIZ&a);^_I< cYiZDxsZw\qIwFB fE(AB0"*'hQqT?8Hfw𫇾1[~G*sK&A bnNp%2xG?>#!⏞ f4w^+nZ $Q廉.Y>uF8>R_uwgNj5OKtx c+jי֛w*4 %:aBU=,ecb)gﱍ3ӻ)peʓX=;B<}n) d (AjЀt!L` X 8'p<il9!]DL9=:[xǫ+"!AʉXJ{楄 v`8oSzFd](A;vՋH[/ұCzEB/eG !n\DlT !x!VG]sXLRiZJUݩ'g*cLYƽKK5Q7wQLmЩt>¼-ܧo#B;)Oxf=G:fR-[3@CD %@ Ђ`#Ă `+؁T>%_*">,"K*4)LjfhqزXxp?H'ӄ 1eKOC(B D#P"![tED\l/as(A*CG/a1k kpbCFWpb>AS!Ahn_=}J$e(BHuLSB&ic ظx[dmq֑s>o<8u5=>M5*l&ƒc:EʪA^Up"I]Q}NַNݚbߒbl&mChv AJ n'$A:û 5/!AJpnތފi{ 8xmBL8gJ(AJP4FV` x $iBgI cD6C╰):LԳD3MCD?K sc|XFus89Mc=N>♼wD|rK(AJad"',cU%LԱWlEbˎRi'J/k#O$ $ ɋH;~)A2"4ykbaDJD"Vc󦝶)C?4_f>ѫ -~yU8-CwM>t$HjLse\|V1lq#g؄zcϻbґԋHӵBt$ؑ$yNwWNRLəãHμuC}t M >iHH G Ǒ ݵ̧=H&$7>A@t K #Hs ϔ Q 25h@ :Ѓ &0`8pR%H+@&^ ۆ[ڈhw[$6}z[|r0&H&WDj%I+ 뛑$3V_cW2mI -E/"v9\i̟!aP "dChlRxR -Rt71yW,c DH*+!֥?=#X[\b<71`gqq0wP{7N?wBY 6IT݉ ë*{5gu^;""d^O1Lb_-zd)*EMHMǃ3z$H9&^z=H9{_p-[6 OaSf?@ڏRw}rn#) d (AjЀt!L` X 8'p<dw?n%t!۸qd7,hڈZ\}NbI썄i"Y7w"UD[BE/L\3{vER繑>]WKB B_w?~˃H8~c>A}H:fM+1_xׅ9Rڥs A. 0M@OLg:Xӑ~AW ^ =zLUt{1Q9geeQZQLٴeeQZSL{jР~dZf(̘s &!Jleč&o94ϵո;S~M ]q+@CD %@ Ђ`#Ă `+؁T>eNz-^:U55tuw ֚}ۭ;k y. 0@"0: 9/"nz9W<\2'_Q}Lw^ga6/uApiK7!2J+޷edf8L2!` q622 ᦲn{ }ITQn*% :}ZEKKy:ߥts=.r[yz zC[3z0%_^SO1e)FTTƳHw6S@U)hz 6.ũ,l"+#1%+.LꈶYA s/[ę -@\&^%|?]^?7C Y!!^W]D<؍TX-p^lXV[ia2hL"" G -hߎ -PV Dt} zP꥽i);PCccMDp#:2ҙ4w]p#bcc¤qJi*|ne䅟YwyU@:Ns߼&%E))3eXLa&ݽX̼tHbobNE9S -4DA4@ -P -Ԡ-@0B, p N HS2!Ay֕DQ)^4E2J4DDtb۟Mbӎjqwt'avGa[p󃝊/6{B RJ&AE$b~PtE}f_%'%?F:j ʧy  mmA6)PlW;I1/mY^&G{9q=})3ra=qxv X[GYbӘYiC8inuG=ܺyXRDDDd""FDH)EHRV[JbE9߆T微wݯ]Lv;C5MrKoPk֬io]vM7oLƛnvf.W OZFjƍsCMΛ~X^)/4王yb"!_AwpfS3lBיҝ o\z*IKu>xnɸY߳mc.:zp"L}q)5Һ)([NTUܻN]A3mHӗn0̿RC{?TJy;R?5&{j*z;)x0娫|r55{hO^-|]}u{vXsJ+az hP -Ԡ-@0 ` ,pC į2L 3CD7eQU~i >k&1t1 0 5Ib Ah'vn"!I.$5IkR֬~`uVֈR֜uE?үKRɟ KҨ+tӫZAcFW&WfWW1jg_Y?ft^׸5:kxz^#k&^LH?k ?ku>Zexvgl\Yv/.>¥ߧ^}u9?+'Zg?Y?&'~tgU\+n;h\Yn[aM -s1d{6#..>ĝ\G6< dh-{= -RTe}i g햁D]Q_P< jco em(H{ -R~C<ͅ}4;Xt-=̌#!iԠ'֙eSLwF,?9!gV|ԼǞ9_j1[{ '.rj ^M1N;.ɾ@?~o2N\mZ7-Sc]lmyz[ⳔfrduFBv5h&IsE6B(wퟯmeBtRx RM' -_S DA4(AjЀtf@ Xvp 8!IWJ5RHDUKԍDSJ;!iS17K@ xip[_L|ED'BELyrB !ar*؏ 7T“ -rK!V.>LA! Bl(\=a rwP -7.$o(s(H:oQ?EA`6h,73 Aw*LbbAX֙X &p'$M5R\Vq0w=S|;O !UUF4 -ڲ}YsCB:1w&s-| !mV-Ԋ! !;0݋!$ !X4g0 nm}CHN;n:0q7lq!_S DA4(AjЀtf@ Xvp 8!IW&Y´i&i"Ai&j&~bh'~bj&ib Cl>L;k镖1턯$I"JQD,Ȉ\l gV\Y15'%a2*'䠜sl1rBaU$dK^!$ -똑_`õ!!W !K!GHH"1Oː_ >6%`zll|LMw\;>3M !6u~ &d޵ΣvP!2܌u̖AـbTMqNԵ/; }HT͵=dK˽HH{՚?uEBZ+!="!ŵ═'HH13SߙWކTc3/$d!$dHWB0bBjzhfb!Vbt " b"^%9F ~a[:f9!$LBd9!T4"!VFEB2Y -r(3,c>BZa -r\CrA YEA˘P1t3Ƌ!67g\I(2CqN.=  z|LMfcݙq4ޱX;*ٙcۨBƟ݉)A3W-h[w&JPCc -26|dso[f~E|i9>DMtӞϣ 5B;P֞; -qDA_R7T>: (|M0ѠAZЁ `1`.pX$A -,d0=KCQMu40 1I!nb Gl>A#Jܳ3. !o"F"J!  L!Qr^P(ȥ\ ,} -9hN*#\ -cܹv Lc5B; n˱f: rXAf^DA 1E(Xf01y>>6>dSS=Naq|ZFl;&\,Ķm_W -](*].PTT 4EG #]^J?W[>1?e2=et?bm9Lǖtp}(CSlGR Ov>J *c<@%-RR@DT hAz0L` Āl` `| ) ~vJI1K))U4]m2Sq<@Y:)cMPv1J:)w>1O Z!_IGutHJtE>*hZ:~ZqꈹXjᣉj8^C:•~BDA+ \-a -r\0I b/0v٦Zi\y8FMa -\#._rAspz\a|vxO(ȃkP|)1A) 0Wv$1Ɂ*dgLA،L6֛s{c,e8xxKM2=S/۾iḸ|9t\3;ĭ }{#U%FuͬYXLnmOuk -X4f]kjz#\fjLkVkTж8a,_Jv=,^94ht[4z+E mUiWЁt t|M0ѠAZЁ `1`.pX$A -,]Cm%LX^ Qw8$#Ab!^b%1 3VGMA\}=B<S7@^obwa.ah܅Yq+FJ\@rA _ -SWQ t -2Y˚-/_nQ:f ~ϟ eH -_1Ix/OEO3Osrq ,q._6$|X Àdx3,}S|Ϡnj?ⴹp}a`fWzW1]l{z[{fcݔ~/f}3Hba* 2;;_A:+0L R:ev}\|Mb6bXl*f#V1وUFb6bXl*f#V1وUFb6bXl*f#V1وUFb6bXl*f#V1وUFb6._ -NDU)]$J+!z.L15s+ -CUDuS'%Mro\ 0*F!$A.ȅa -U-Lו -N*_"rA Sr||O؛0bWfO]\Q3%`W ~g g3Ǥ08s -z=q4'7'0]gڗlؗlf֜mci{OF0t3T;ff6'vL9fn ycA0͝A5@DT hAz0L` Āl` `| ) ~DQJ ETz躈]AzU0%!b'Qi'H3 p o#"   S\\sK(_}dY+%OJ"n&[I !ߐT9!$LB0]!Hȏ ٹg 17 HHɞښ$H~T'M2&!s&ngZzl:/ӓ99!s՛:[=jgZ㆐zAۜjȨ)/%ꪏ[ -ך*^K6W'[jVA[{HHHHHH 2@BJZc!!=X !ew!!!qGۍl=|M0ѠAZЁ `1`.pX$A -,݅i"L6!D@4DEt2눩KBĄ4[+wq 1RBV -rvĄ\&'>9!_ *j*҅HeL\]!d\ڈqWQw.cn:\7)?i}g.1C:qF - -]B>20l=s¤ MKs;YsiL.wʎg75o2qvM+5s-crA~$D ^EA2>M(yͬy//QD>/ f: 0e9q=xg&"#\ɳqԴ >.#HJ)QLA2r:hʈBA XELE\B,ybAj ! "'a WAz'BU o(|%LAX ^ 0aVQmG]qk&bB/'$:LBrB 4~3Q$nA iaH/COg %1ۮHa]3~n\jjFFЍisKprq1SLgn;q-6$d~ ~֫zS~|4qڂ9]}q> 96y\t4U^lxzg){$ ix ݉D9Hŗ?_k$$ =|M0ѠAZЁ `1`.pX$A -_Mz&HLHQ.Ҫr.#JRIL\E,bBrKx Hs9&2"J 9+q;ꧏ}]N VKye-+dngiA:">$C.W7]f;ޡycrAε }a3~5S{ ԛA,cژ3g`qA'FAR3'jb%jb& -qh.HQxj @_1*݄yN.ȗ/d\ tk#C.Y. ]EA10{Ԏ˻QBaGSāz~ǵmgMiwObtt$96h+ -6 -0M-}bhP -Ԡ-@0 ` ,pC eNf"̀XIi7jt0Y'Ѷ]t&H91Sz Al!i'H?quw tLf7b"Fmb%GU \.LArVQ٨%J:n|ZoD.ȥk#]XkEw1opylwn癯m[(HP(7Pm~|%lLĿ<=A3˧fzROA=6$$6dB>>#(&j[yrf찠M5w?xa鵦W ɖ`cVkf`۸ 2< -R -Rq/ -( -ݎf 3AQqqiGA,gxhP -Ԡ-@0 ` ,pC įO]ٱTa%i#Hѵ}/1Isna - qqaBv;$bq00\l 0ѯ&L4WPRA^:'d&Vspx+<%҆|+e -F ]UXx -0WDc{?f[93HfA3{Cm-w jr,Dyo}b[{(n襄6ʿxO;XܷJRH@@DT hAz0L` Āl` `| ) ~Z1O1E4@))U.Q5(4a2ЦH'@hz~꥽-I,KQQ:UZ~H:;0珽 -0傰rAHLc'%O0KK\XKW;Qk78$$%̏e@: @VI Ϥ@b=^6f"!^Ιŧy28wz)Wm*..n_hk=3~2ҾKP %_cfvIԵUgl0,4_kۓlnIGBrZdžWŽ컑j$d)$d=2^-,@BL"!wnt?Lqhr3ZNtw\(`U$>$m7y'sƥ#g03-LmpYvpN7tĠ85TQƖ\ij{~bkN2)񰠒WwS6@{ÂJw\s[XdonolC- t`餉-tJיwXPQ}Qj. NjsaAMKcE' .hʆn† uW%=rˣទ|{fD+R0cWѐD^=}"6j|Xay -SU,L{ ^هB7./L*o@AJ]Q/Vaa`7&1~5gLA⃱.ޛd3L9 g͈urqqޠ=P˦9.**:n[ό$wJ|NiAx$Q՝N]zX$h'JY?x@0de2_c*K6ln5u:e\5@DT hAz0L` Āl` `| )IK :i^ C:CZTc1s4%Jb\~Dx UzK+ѳ)$&LAΕ Fī 9,J\,䬈LD|D(ߺ;|AGQv- |-baR+CgC޴xӓΧca4. S?}|X*g[QAUAٰ?Qv: ɝdh>'oa. YS;ɱdKԘ֦c9((H( -dCAGA*<}ioyF,$ -s; -Rt|N(Z*H !$P -BAB(H !$P -BAB(H !$P -BAB(H !$P -BAB(H -yBj Dٿk-bCYACE,#bAF{V"l A|[CKV*W#, g)-!oH -SUD}%ȽT6~G.&LA^gryC)eY 1YA(,1<P՘ ~0g'1W_BA3/iiL6ueĊH< S`ǝ9u3̦UfgQQЋwo*4gTݶA3Oewgm0T##ךAZtwYGN>zlrt Y;FA -J){>djx -RTxiDA5@DT hAz0L` Āl` `| ) V16.!l ZhtT8:OL+<-&h f#R.[zKJ|\ +B Qa -l:Ky>y_~ICSrA ^ܰy wy@|kTrAn7 {Pvc3.bc}'lwci5asؙs65qN˳N㝩i δ`&d&x>頏+lL*j):NW|Vt9}^C]z6gznC,ܖm[))G[nʕF|kيv\oQ|3 0'T/^Yy::A5@DT hAz0L` Āl` `| ) ~jiǴXFsDM4yDMtUD6L|b* ҥ[GqK{<ʥ - _J|D|L>.ᮃD:+h\H* -r^&Śsq@B0 !2]Na[EB.[YX$Bjs#HȯFB!?vA8p;1?Ib.|Cy%el3#ACˉRS%҃|ZBzک/jGǚL>+&#&{KM2v s#{Mw`| ec߉ii8ϚZhype?N?%w܎ a.^Žٟ{x\0<ύv<~؃? -~q&1Іk?=dr No|4->Ù`0xqun;"m1(Z(=3(ٍ̱\Aq Q:u4vp.QWu~`f5dsޫK[LCVpv C9$hێL1񵷳X?g7.ZEgc0ǚOTQ JP4F0,V7xx~@Uba"t\rAB7F=%b 桥ކ7gOH[L{x0~(![o.LB6 ,'d]]WG 7T/9 N aULKM(,Or01 ~g.| -RRlsƛgzc L\3 TH`cOYVХ7_?T1rV*Ǩ,mTeȨ)5j^1jUFC /1o7֛-ES:8MnGvVDﮤrR|?0ѠAZЁ `1`.pX$A -HWO)EŌǐ4}4R1J3Fi(]/ !8@)sK+)[eÔrwP!阠fJ+M*9)r:~$#:L:ܫHǿyDsg?-r;k# O]KG*\u -/܍ WO/*C:=3 ik׽܈#1 !uՙs#Rcc=TG<'9#ÙHuySSݩS~fЉ;*n[t7cOZ/~QPNڒN} 7pnK:}ւ`6δ(ٜVei cVkLVcy0|zz_ٮQ ǧ(hqowa Ʀĭ[N\=TQ JP4F0,V7xx~@dz'!TAD5Lԭ#rD/]=A-1A#KwKohwp -S\y -S* -rN̚\='GF'6˗I 0*W~kiq)> - k;3/J?qav+LbPbrSòl0wha^3d 1># -_ؔOzWµ- v]<䴲"(ǻU F#fX`wQ|aԌ`8${ZSxdK(ȱ֮cA[(ȱ;ޤc(pq.-= eQ<,_jlWΠ u )(hP -Ԡ-@0 ` ,pC 05bArhfvZ[zTzgVbm^Z"\tϷ-yч)& ߕ „)?VQ?|tV,>׻#nN wp^EA^`j _Z72AT-u{c3Yw;jKaA8ן`$F| -ݓ4#=yyg*:32b`,;嘠-?Z/޵V1 2 ݂f(QQhuًa}b5彞l.LG6Z &:1LbS+U8ob*fI wcz3H3}ӻha~ SAc/|K+7)(%@ Ђ`# +p<<@? R`ªƺrh*J*H91Kjh.J8X:촃U&|033D r\0* -"݅ *sdP.2 k "= S0) _mZ, y%(ȁBY/xzoaݝA ]u$63獋uzyb9g03>ˌ2SnݿcbA](XτP;Q}пU9Swo4&X/(`h܅_k܃t'[Qwֱ}A[}' -RGKK[]dωo2 -J -R~1 -Rȉk*(%@ Ђ`# +p<<@? R@*DL =*G:S*HDzJ?K!bzG'qJ/nV1*_:cvU rA[.)LAYEA?rĂʉ?9φH(Y 03H*fn*V1(Hf7,  5R{Ia@DT hAz0L` Āl` `| ) mV4LVӪZ]Lkim+M!6VЦZH[*iqmec+ʧUfh[h"% -o WHG%z-]E=:v^z@ ->O8f ]9wALa\E> '7DZ<|e?L<+}ȥ kd]g΍;˨qX¤rN6fXOzک5lO2[{PQ[>EGUSf_ ts^Cgx+M9{e~iغz-f>&nBye7vOt4О)msE;ig-moJwg@BN{N}E|M0ѠAZЁ `1`.pX$AJM!錏&DJԽD3&mco%Qbh"Eb!zbqb&&#^錏FFL"J1!vy_ "!~7bk=}'%@vta -Wy+a4.f{݌ޖ f$}gvӹ@O108l@.?cLOrTgg'33#x +[2^h*S<{#or9zz~ƫ>zs/g{ =sR-/_mjxon鷌"!Ƕ]lm~-e ܎?t=u!!mܼx8g vw'RՃ>HXzm%aS<,$yZ]J4EӸD_I (]^zaC9- +WtED|Yb/>Fpwb䄬 O_&!#HHt՗NU5L< ~'RA:#nU䢫W86EC{V u<\w- -~ ~gߞ+N<ߟAk6>LzyיgƦr,zrA6ULKMUgJsKm+_v ʅ.q7HMKDq!gaKvWABl?{ٹj^t`hNkN))?q_S DA4(AjЀtf@ Xvp 8!IW!݉n<&vO4-D;&qb!' M{-qWqKvKJVExK -& Osum{;h! UUH"]kU]3Mͤ󾳚#LV=ct&cڄ88!1%ƄB(!ʸq(߹7kBM?th-sٻI6I61A -`[3h?V3q 2 ߕ r4†\AUҡ.!nZ=YZ|e%$a94 r;00H`aMs}|×w󹮈9G!V̿DI>?ĕVBĎrfH|ͬi?`>j֍fCQ?jjL17bYVJw::*>_?Im~wsGa5a}K_?x ;M2bݓMLq0ZheB#_bkڥ3@@HJjZz`F`f`) -lR" l@J9d%ڋ3V]jJXdHk5K u-0"e<0q cnHV wh9Uqw?wkHKJq%s6CH;?ruͿi^Ϙӌ<_zݏ:\w#Kw|5} nt;$Ei@}CTNd+ս5'DL]c͆H=U2eY=CgGh"}4#}AXFh:Qj *>+eől`)683QsnJT$D*:`&` 8 <@~ E zWVVHcNfhGO Һ>iz,vd8k\&4C}['_nqMRM= - <tʃgjKՍ2SG'Ο ~tdy,K.wVkpv)8\: cO ;QtA16\Q/`9I:kt*@"PP 00 HV`v -܀ ?AAdSꉢseP=Q'F>H4DCD_H uX. J#ƕviz%qW~dHĖ8@> LE-5}A߸Y7\ ?~ 6^5v wt ֐A ȿ!9 rrjROO@bƇA4'ү0HAns}!+OHn8?w9">z9zJGtWlm}Af/|#)  RrKsRKwIz{mUtrG':z[F39̺g4'ǐAP4t66g@{7\F*nT1 svKZ軥nQb'26#~⮓.rP/瑥0E>yF XAzϯwnloe\!FWF^1PȷנǿʶM"lB=ڂ -!>2?BRB6F߮]?eLNz7Ǘ}+br2"כ."k ?\`"!ū)B {B]:O*c6 ]U&wP{QزE59މrk)BHvn6gZ?+<ϕv;PDם综wz7#Cg^s[߲ub$ŹMV(q -9T%/bQ[znd}Kf(dj=,h0>-_BF\7[dث6f -Rwbvhzh> @!\y׏H@ T@ 4@ t@ L , X A*ppx@!]$(V΍H˳ktAtMD(<)o"vNb-M#17I/bj{I`l5|6}nܩv[> B l[Bfzg?'ǿ]Hg8K55d!KWOBA>^BbHҥsRYU Sg2r=9(^ܼOs~) -!=׋ -' :tSr Iѵl.͵0HrrLUdu#5vڒLd}͆n:ذ{LY惧,taɈ:vvr:IRAaxuMocoGB -Aa.;+!?S8hX@ -T.< B Wwg#wI3DNқ:b&viwvAbvggq  ljg=qʽ_61 % r˟y2o-\|(69}1I[Vk%0׌c){}l5g/^?:N3.fe03 #iu3;3w2ݏ22&{Į+8 ( -h +;HNn Qt1\hg݌Q0nF1FSc`,u2f&q3&-2vF`-uo0h&lԡ5v(YS}<ѩ#u<|Ç9FS<d` 7c@PC¼zz<;.E8]"7GwC\ Kyݨ×Owa!q\z=>=͗ Xt΃)ظ!g>QvzT s^MقGG7`-y Ce41a{vkږ>ji/H ݰ@7ltYOc8GC5ug]/HQ'\)-_ʈ"KW;tM9%Vo#Zb,!jb%Ɇ NzJ\Ľ_Gci7Y ſqIa"wk^CPm6K=Bnh8'cQVwנvywk 1ߗWzAR߽ -+pw1"D - rw1(Qx|5^C]aO.9):;!m9+IѲl+{N͢rXcyQ j^ԕ.}:xtoM%2e)xI)xj='O8[NçXwW;z!j/z~< a6{^ -@@@R؀'p7DA`e@(fBf*Sj'& -1㼴 wXVZsTC -O'#$Ps>B2em{kP䗯7}2`k˭qR"p1  rۿ!&vU {I J kAm" -|GS І_*I.Näe8}V} ҫ4ߦy42U]7w ]^GF}{`hڍ.@\zR:Bۭ#\B5BH!]o",.# @n@)}_߆وR~!!6ϭ(BH!$E"DB!Q(BH!$E"DB!Q(BH!$E"DB!Q(BH!$E"DB!Q(BHtzjUHۤG DF-RKQ"'0~T/UO\IJ G,LMDw,HlyP6sA ̆xQ=̝guq}r  2 ky؏BW`  R}2ŏPlK s b;  ገ+7,82dWm6|W/)3ra=qxv\[|e1i Y1fic`w:ͭusRDD bE)RH"""bEJ)"bO -mC==zMu:_|NrK%6mjź.tۭ[ݲV?7_m|.)5͟vˍ8J-Ӷnmj`m[z 0/}?.ݞa_#s:bnonHHg}cR&q N_qi69l~MKbn3:J:6K}wJ"N,PݼlDT^b:IYW徙7J[騖]I6,t's*D/Ḷ/tI,G$ƒr^)/(ܿbg>Kp/nw5=_mPŢA[TJ#&|9|M) 9(@ *P=`3X -v`,8x!2M3%"'lȏE;Q.,Q] 1' qDo2 f8Dӄ;A\#?A܃ijɻ)eӦHx/+NVܱ"?JpWrS_*{VJXZ}UҊL'J/ (*Z}WᏙ -XMvjCmrP6q&6MjڔBύ-ʛ~ֿ\#YY^Yj[Y?ZY U'?S)]}jiߧU4ugu?JYU:zjANg^Ho[^f-2 m9Aai[M&zE{>u*CzڊRho"_Gzg:P?g8ı_ 8 luǥX_YZ>* #,+wE)HRu񪩣ꪢ(MՃIھ6Z}CwxOOX̍LٯAEA -U̽*&i݇UQbcvWc3fvb -) 3'Xwrה  25h@ :Ѓ &0`8pnR@*DZJ& =D6FCD@mD@DSODWFAC'bm$ntGaˈpĵݷ^A6-ȕbA Qł rq6PgA+H[ZŌQX(X,HTr|Ia0ИA(c- ַQ='8x^4fv§A.ބU_x+f2Κ4k &x|i6G3-֟yjA.D(Rf؎-@ -* -2LF)r2(HDu f:/I -y}`ad(TLom( -ߊL :9 cށNaqG$LC#GCN85%&$|%G$'$$)MKvIAJP4FV` 7x ) |lJ:#s(#I乔bI̢T:QLJC$"ʘE!2/JSE9(vQ̢|ʵ((w&1(WHGx -[%_>3.8 +NI-!1&c˗c1HO|:C._na(# HGCxS,_~w,_;kሉ0͟9l,dlTƖβ T<m+_KA /afyG)>╣ݼ?QR2ߏK}ɆWe>vML'F}! }>𑏿"Ռc.  -@?ݕ; /X7y_׈})" d (AjЀt!L` X 8'p/UʉQCd DYLTD@4D[@tEᣚᣊ˗:bo%L#q8+WK\#3! -rX(Vq[,=DAa%g(/+ȱ?X[łC ǜXCl`x>ł˹vښۊc%B) -t/PW>uv{(5@AP'Ξ募[ ݉pi1g\a v [jN)/, - D4̣ oDyQ$Dj;3J3m:dEA% -%˗&>s -: -, -2'dH(H(HM- -dwP9F(H -RPO_S -4D@$@ -P -Ԡ-@0B4 p N<^Hᫌo -Y v"o&ekhۈ[yH31ˮ`A;M!ZW  DA łD(ȷ6Pn¤H7T фHH=bB"B$ !w}3dBn)rGr_搐g~{VyS$IX|%$b6g +q1i~[ 4gk#ܘR}{fz|IDTdKyb9SWī+3.4p2n+܆CncB$vh^, }:+{O33] }9$ݔs.\(rw@y^~hxJYz?QR!"ArPT hAz0fXp. B -oۅ8WE7QU QM#ю]701 &b)#b#Lqvq_ -HW@Lj-! 7qkY {R_B~#J,Hk !9O ·aeL$ -X >2&}' -rK'T -}o\gq$[:H>ӖťǤ:b}Llം*޺lp/Yuf§z%CHo9E,c -wF)J-j|(Q;Iލ!Z}dC+ŝB*vL}]BV0̕ qv<"}kŖ2J9KpE]CBF0ށ!$B2]{rה  25h@ :Ѓ &0`8pnR@*uDGV DC}D1BD5IԝD3BCD@@BAjP+GE$nuW'ỈaH.c!! -rXoCWBG(Ǔ3%iB˘a°bA֬x{gk&i1u]۟0Gb,c§Ab:i -,c|)t5gMAb~#cl L -OHLBϑKO 0dI߿+Ȼ_?  -[XM! -O&/Wo W<~_NoarUUӟբ /FVG_ b?zh>X&b15IIOMw1>$&`6Kbqi/2nlyya}it~ ]s 蔍M;Sk+ʑO#NVSQN /@?646 c)h4vy>Yb] R3~ {0t,c~3H9;WI-f1L<Dw- N R!"ArPT hAz0fXp. B -_H %$Ȋ(Pʊ A4DOtDEH(H11K.P,dG aKpUBi]^A<̛ n 3 M%) =lBSL.DBr浄~lu}!w[Om%,c&\wg$d}^2g)C'N !!5 IKOOgmqlMIK`qoI85!立}ZHH#HtKHt)RdS>vTWv<tgj5ً楇%v nB]zi͓3X5"! !SX8;(go! 7-EB2E$d驓 ה  25h@ :Ѓ &0`8pnR@*%DXT!DEDYHTUD]@43vҕ} 1>R@ X2rg9ኂ7^(wz MHB$;bBnf 9'5tzy  -Qm2f6u -ua\eLT+C7 !T+6]AA M\}˘",c5=7^i;Yqظt !/-a.V>SaYc ,1+cSw/k;(ʍRLʦ"^?+J:-]2kɆƱF,cM9t˘,cZaSp˘i2f 1eYXL{o7ŏ? -+˘'ה  25h@ :Ѓ &0`8pnR*O>B -Hd]D>JDy[6誈FR(1K V{?agg$s 9ĂE,*DA:7Pon. -u bBRA~*=Կl { mX1ju~ЧXu§A^x˘җ_ R{M bXƤ9bllH'bSNAF3ȉ.7=A2ޯ"vgł =^udnBM1=`*B?0mlƟ]Xwizfܷ1Tbiz3H狘AG1}z ˘ǰi˘G1LcY+ -'g|M) 9(@ *P=`3X -v`,8x!2LMn -RBdD>|hʉr<8T i >MM^J l[m%q92&TA"I " Qvy -,\A ->Džnݾ$+! -k %bAT!f !V1|r  ~񗞫9_; -^cͷK_v̊>QHxB;q~x{" CLuK>s{$$q RLݽ 3Hf0towb)ߍ~3Hc딷d*e )" d (AjЀt!L` X 8'p/jA2P "[$8BD5ND3@R(C8#d%Cj#!dJRv8 7M\˄%/5Y, χ -#=*fݧ`d} -k! -"#d% r -?b- JC[ZA.@A -yUS mA -z11gHRcbm~gZebl~KuǦbKmM[24WlvlGA&AA_BA^OJREA$r4s$iw}ULAdCXjtf4:3/|ULXŌ cS, ͘9 7[5r -2ׄǀǀu R!"ArPT hAz0fXp. B - ҺcDVNDIDUNԹDS܀UL71. \D, !z)'jw{ -" Tł/$>DA@AZg؀zz9$B7c׆>o(HϏY\yЖVGQ` ){>u'(?Ҟ5fIY},ŤۘxR&jMw;bbm@q)7KW(H~(/.R $)yU{I(G ]=lhN66֢ #&ybiDA{ -2>rL>̾t 3HǾP̽(HW; -2?4]5sO˷,mֆMuLc]cekj-5N8/z>aRJKIAJP4FV` 7x ) |ߧ@,]+OQJ*E\Jֻ 7^>3W8ܬיy\W)+IWLBs$\/>rS]]b~" {75';<&lt~ru&$Fk/߄"iB7|m6LxE -~ iK C'Ե{DZ=SE ƦKXKmq/xme[ NX,bxSEߐ0UnLG)HR67սFiHҎk }Ɇdd1G}1UTQԌb L1Ut -P0ux £'P?txSL#t1 qiGoQ\YZzɺ'{vF)N+Uc(^^{B(߮շK6O6=f\b:3/GA{Q\|f (H4!p1dvWad~rg uϡ @AN_S -4D@$@ -P -Ԡ-@0B4 p N<^H);yG{|r DVCDQ|:˂5]7C=1v^ ޟmGGkE\5 /zj $aOY9ɿ^ /^%ofl Qn |/ Aƒq:._+ȭbGGߤzxC':Ox餻O/lk=HNH/Ʀ9m/6s$pӮfm-C mBW P=k8Q`b$^^5| QsO=INɆ=Q.ifgڍ,  ]%(H_1 -R!%H:؄ig--&36KBN>$eͭbU㝉u9^QT\~&FO6[}Bd:i}sXb衘Q?zTv._ a}/fK~;{V1,돍liS -ဠ4[\<Kljjv5{i3fzExXqTSV4唷*zW.|TTϽT}-/ -W }nc| ]dxzxvĒ#8@1 #cbgJvSΊ%\CVWYK7=^O[:(̧-h+R7~=,WNC -+z&ʦk*: en >n[C5#~6gGE/UL×ۧ~XS\ nT6;#\A:uF ( r_ 1y1[UQ۾ŰeX)*E/d/.§(H꤇NyK܇U /={V11i1-U/>em466ƥ&O[Dc5 .-tǣ«F]ib0T')z0JS6=&V1_L6 &;F77j+'*EA^K1#眖b) 24>+f:P'ݔ{I 9 Rނ!*e卓A5@CD %@ Ђ`#D `+؁\<X}] BKQ*f6xQLJ ,1JAeXB)žދ%qgKݝ-%D ? r}9Ƹ3݋YuG{БцX|O,~qJܾܘzO{1? r[Cozam(+w`a*\:>EA4(/9nvx5a\BjLphP#yu&8ԂVlP6KBg G~<#ͭe}XRhoz&3J3zG~\ٗlO6Odj y GNJn - -R+BA*;g$ʎ]GPwPRd(H(H|M) 9(@ *P=`3X -v`,8x!=#?J(j3T}ip >mHMAO&~q9̑gҰL ?bA6(Ȏ bۦ3EA ne0fֳJj4,`0y\Z3,Ӟ*P 0n -PNiۃl~,Q^LRvtc4$iG^EA2glK6V M>^m (`9 -RS=!*}[y -Rc]M=(.ӁP'PRРה  25h@ :Ѓ &0`8pnR@* ANyQt%)s:GmP,5N $ bɗZ}0RLUQ+WE~~ł\ bA~"( 3 [ o,~cN ;*wbA {/f#WR/0 i*ZA+P9:J -R*nRbneg Υ3ݑA%mz .s;Pȋ' -6K(H(L?/k8(_ $e;&ݕ^={3H0<"l.dgnEA5T5 o1y{]o C *27x R!"ArPT hAz0fXp. B -ͭ^-HyQ4_WYGԥDSAD;%2X&:8ڃ{) -at "! -rXT ! -(H3 ^949fV1? 9 2)I](:h-)W '~AW C lEAď~ WxMG73B멳X_\z#k,#* $N[Ŕo9ܓZYlJ? -re'[P;0DuF -) -q}s_d~QAaGYP8YGi.FAJ lw ;Ʀ %ŽfaGY;Y|M) 9(@ *P=`3X -v`,8x!䎲"'PI"e$*&a ^i!>b -2Az7'lwica?/lA({V,HJاm(7EuW1 =mٳr !f376rp߯s7bqiJƞ? ] bi3Sx]eK -}ay?-&>pr66Mccm񱌏31ӞkncO腳pQHsfR#DfdE&y+Mf"ߤk:_SkY7obhbJ^ט/3gH,/+ȗJ·%OHزPG|*WV~<=xdP♜xZ Q -4D@$@ -P -Ԡ-@0B4 p N<^HAWV6SJH)(e jԭRRJ_F*(cprr#en,ՔWRL娠6ʙOq%)w0O0G-1{n 7U˨-an{5D>O8vտv cܱfA7"R׋umzlx/|ꦭxh,aN"yvZUJ| .ƦX9>_3sƳm'hړ{d)wrBA1H $)ڎʜUM/'+iymS]Ŏkco%N6N A_3fV|2a3Ȩd] fz)Gaa;(g^Q 76H7w -H딷铛A5@CD %@ Ђ`#D `+؁\ݴPok ډ"H 48KC fK{dbAT! -gAĂ m Lr/?cZ\,}˒Xo(H -R N[:>f-[+ O9LϹhM)fO^Љ~K'-=xϞ \^O0-!᜾&-.DA>^*,-B r;{QD OurDTGm^!d`}Naf_qQdg.nV ooăyAx\f2dYsxy)Z@Az. i{wHW_z;Jd D|mO 0Lc걧#2 H% >V7fkłhCR aC ⯇07b5cO^Fm;}C* /mฏ;C?9\V|3G_A2Oݴ4K_ >{ K;1y' T[pM$=㸀s6Tm >VWf0f"$'e%&ʇ %)GxUL(Mn^c? -5ɾdCv}d:rg]AAGAJAA:fw}$-`jy AAjBA&PPO_S -4D@$@ -P -Ԡ-@0B4 p N<^H>hJՇb&QeoN 96@0M]e%'8qLN!qu~ܭĂ! -# fDn _um -:u3|G`B5! -rf聝W1EA^dh GA^jKn.fi^z˒> -rgOAb|-&ƙg9R4& }m+1wJ͗Dt@E=/k)ϝ*29bS`ҩV3Qe]v˕nCאX{,_Syzfel+PJ3[vS]cѭ5ZO?n<ӻ˻^R -O5_S -4D@$@ -P -Ԡ-@0B4 p N<^Hi.OWRs.2.'yJx07 D( Jx xha[t"&Q[ڳ+,lAΣ½eX" /swЭ׾9  r *&M,(] gfb1\d6* 2 b9vo9dId Ĥmt638G\-..prixiߚݸY:V|3Hnv/9U˺?HG蓔}ӼDuكQ$1 +%'LY|Aa 26fCfgً਄ڽݕMB`) R3aqt[@CD %@ Ђ`#D `+؁\<$.zlxuL0HD7ECP 5 ɒGeXO{ɗ: [ADpK{Km>B^,! -r -r[Zx/t+rJ7C XڰA6mXJ(H(͉ks3 -2_q) -])[sv)HBjLz` Ԙ& =%[UL(Ȍ^ىULsll$Q>NQ3IYīGՍK:'-VߛlM6vRZMXż RsV  R*k -u1 W~h)6> ' )" d (AjЀt!L` X 8'p/ܐ:<uK{ 2GT# D|0NceKIJ2< # }a - DAłX+ -Q 6P]ιMc?XݦS -ws6; 2 XFbAZx%5IKϹy΋U /|ꦙzC{SOڎ=Ǟb8F8ؐa\Zt;N;j֖ wnc?$z,y5'ʇu!^9oIT/irxh- -25 ơDwtL]>c(HCuasbP&drNUJ+P -qOy{N.<_S -4D@$@ -P -Ԡ-@0B4 p N<^HϮ%i"&vI^ -#E$!u4xdX}0=1IQ"\q~+:BAn ' W(ȏ6P~ncݷX~d/˘!1 yTO7p)Y~2F !3 z !>Rg XoRslq8q_Twlii4K;mO{'U6LFH)Q6btGUZ䫊*e*?Tْj^Qit-^?r{.0f9TcO2uֶWoehht=̍mWёU=[Os6o{Rv&W -_J -4D@$@ -P -Ԡ-@0B4 p N<^H4ID`Kd5yDQ"QVKTuD іKt}P!1IR9_bX%f qH*X•I\D.sB1&G QX帰gld䈽p=n -c/tٵb/*StؾsBJhNymoFы--vI~^2C_K'_as&xOgNbl1,sr/qkt,YE;+3h)(Ϻ+JћWM̍Lݝ#G'gF ͇]MXd=i2C 7ocߏEK"-bҳb DZhX92W(w$-KXv@)" d (AjЀt!L` X 8'p/iB9VooeOpѲH4DI0X},8r9xq~[8r(Bd8rP,H\|<3]8]o __Yzp !lXzl)DyƊN˫^OTgFi:v&i -?rOK6LK6 ?LoOsv} }0f1bin,I%K6!ucngl]纻ywE!cl)ÚF""E*EJ)RDADJ[_JaCz*|~~?c޾%D+=) O"A:Nn6sGѴ{ }FYZgʀH9(@ *P=&H3X -6pNpx>C۪dmbI =ƩzRZ1L5\'M4"u&|/S!&qdW4A6Hh$E#A֬"A֒?scut4A41hEĨAWѳ|Ke=~jϞ]N_!A R_},n<],> nIv832=ܐwvsyrSsݡ8/d5 li&qty_A^Ta }mzmgk)x# ekd&G:eub+8xFa#CHH6eFq5H9$'N0w`Υ) %@ Ђ`# `+8n •CW=PPŸ4ltvY>@Mb4S˘40Lҋs/ kN;ܢRqEDw.r|)F|{ [s+ u ;Mbt1Ud?{٨~"5qِ8'?'̳wą9_(X{qOۦC7N eR' eCINI8s!Ѻ5 l!ŹQ suwUnA]7+Yt˛ f3LI3}!KU>d8 r4$0a5kH _vRSۂ7o?C 2;) ĕ-2`!A -P -Ԡ-@0  \  HOxSY 2IsTq*Ji)Aq.#+Y:uFcr3C]*] SH+bS)Kp r}ט&g:AY4B4Q܅Z#w!B#ڿ}ғVq*go;zW?{Wjm |V*$cۍ!iN'J)vӕc8'MZrn(9목/n TU^UT!;P -tE'zewFcڱ7PAT!BJ͑!eAb?*q(M':P E[ z*PUHٸBX*d[Rϔ rPT hAz0Lfl7|4 #dABi aE -xfey1B&Pfihܼ4Oh\cƩw -}q޽ԛZ/!x[Ek ]WM/}h S^Km@cǦo A~γIǾ'9g`ڃq8Yi.+wc,{(Y3Ci9ΜR'sG"domg-l(B&[tw|V\)g._zfd 8'aI{XLWm_ߨ*难zmeA(~se$8s&T҉dl YĵԽ"PaR ]A/Y81gNo4A _RaZA2`!A -P -Ԡ-@0  \ Zj4Zj. + TD{#ti*jTSkt,B &qA8Pٸ RAR㮄l 1U$O''Vsu$d]4B1"6:PE/W!bWx&#BOq6BDH"Ϛ!s ?}DoZ?D]S7NteyY9vWfN=NԐ3+3t =]=es5lٿOYA^˷5zUU^]^}Am@&qhUZzn>G~n…kwg{"uȯ#d#gf|  ?4 ' %@ Ђ`# `+8n )}DA؈xEQUQ0'>epGѿ|+݈6y䦸yO2jOMNςpPnVI NO+0vOUP,$@"AJP4F0AV8p "Y.ّ$QU#Q=KtD_1 eXm8qNW`E#"0_eܯjk4;#;dxv{Q{cJqυoǣX#<~ Fccn!< -8vC?_~Z<.m{|k.6u{(iu銂NAtM͜^F]z_80t*ødl ZNyeR'v$кteiq9.z@,Z!.D?ضԺ3eB$AZЁ `$,`8' CC˃P;(;)T~*J4RqNKtK++okU*?(DҋtcTk)^sW4B(?naW1Jy!dm׵Fs}GMU${tC<~9v8,~GC$?_9 {JuK=iJIM=\j2KcXQFV=\"60SLbOONWLS.oTM@6Unԕua xWi[1$yDRx} Le - L^̹W"A5[^g ݌h'Un3eB$AZЁ `$,`8' S,$@"AJP4F0AV8p S,BB1AҫTGUTjZv/IPR.1A*Zkia uURw嫨@N&-1h<wy ׸ 7A#1jG.f&F*v`~n:Ŭr.w1"7y֒y5M} 5Kq 3#'+|JV …켇϶{rd7s91v`6͵K -Y;ks$Th%LbnVeߞUS&ܛ&M[ uvLgOpa,ygI*}FcO>ؚz&OJWf{{c/[҅70; -?*"Nmt_ك$ %@ Ђ`# `+8n )aEJF^(qf1m502Pw0q1=qa9K\e(~cBW|M AMqG4=n*E5:r8wsIw1Ο^W<&Sws솏/Q"2(NB(Y'=\138;;✩vw'+S:q^ϟF^M_*% 41y }kUqh]IwCaiRtxdl;8Ml uoG h3z~q1D_z'3B}oH Tֵ2KO2`!A -P -Ԡ-@0  \  2*+j&6Bu}TSSXPK>VR[5uTQD`×R.ir{{WT-*Ŀx /q ?c y]!hs4A^^YK)]? -Hr :U6d!t0򳾃 HO=Ȯa)A?)3ra=qxvT[~11ƘiE1M%\nnmݜ}7 sZܾ"bEDD ;DĊ""")RDDĊXauS~mHUvί;yޟ{?s)/UVR7%y?Bt͎P!7927mzf(soGe7q\+.^yЯo -#Ÿ_3dΕkV>מ0[aDŽ2aG: 9X.1LNORF1ncſVRil)-?u*]3!nGӓٟV6mSm|zvjfjmGInjVCWƞL_f>rBBqμ>`l3yϻ3C_*|6r.E?mV4в"_Gm\(WKɁ8(AjЀtfH Xvp.` 4JH.nU_R^zVt_o3NXezZM&׭ϸa! -J[FsVV:ZTſ:+ſ>U,R^FeZT+>*mWz____%{%NkV*FNrRj`嫲8jORjZVUi9a]Vvt|-9VwɺdXaO+>ߧW5r~XÎî?g+9f?\M~b&*]iB7K;d8ڞ?Sn )c?Oזſ#&'y<ͱnJ .ܞ0wL~Hzsz,yu,e}u+r?UykUxunaNzZ^ra{E>e<=T Khqe[ezdWwɜ6PLEO9DK^do=@ Sg(;@T+vh=ohxPT hAz0L`l`'08 Hy BQEYDYFTۉhva2VS1Tc[1o#|,PR3%_%Y&!'I -JBV-%SV-%${RB( 9e {NH'!Tx|? R|_*c /$M*I[+q&JAv(P0 ?Р iQQnZ^ۭ~w]v̧ц_} #93)f#8BI+p!1iߘq`#L^*~l&EQj2uμ ,AZd6#nTsmw,cg!h,)U>2z)^U RAVG)Y*İܹ1JA'mx?CjV ? -rC -Z\dm ~ O  vY'LA2Y6r3LA#3gb$ ri{xcKɼݽF}z:$T|s'E9v' -n~Nдh{骾 yӗj*ޝj~ -RV?%3 'vS}w ߢ(ȞґN7ي<1H -G"Bkʁ8(AjЀtfH Xvp.` 4wK,HQ Q~i"1% b$b*'A HlC! }[CNX T̂(dKdT祂 Nw~|[ /K1F)H4*dMZ*>ACA& (i**fU ~87a7} (ɿ8a -$y G(Jt\3#ݕ]|"eSUi~, -+V1C}XŌ>V rCzUAzSNS>iuur -~g*f Gζ pNb}`3ݏULgV13X c3ULSuX>UL3XT~BZ>*`[Ŵy/{łE;QQMѶ]C'1S)1׋'ab!*#L#qvp5o'a"wAfGRA~+D ' +HwQsU*&JA~. JS\du8*P U{bAnAA&L S##peL8)#b\XU8d?FrzzA 5Aʟ]),b =k0E[:]C}A_n,H5Mjn+ 9bLcڃdp;fg0b RVd% nV# eAʚ1>#3hxPT hAz0L`l`'08 HyłtE5QU'Q 0ѷC8AL3(>b#,9@\3m%\ᇈ7\AV,壟E)L*yXWP)H[.Wkԯg,d_+RA) kWP]w[yb6Q% -[<}S}~/FAEA wgd\;t.ӓpzs'cV1M_ D>{|J `3Hg[Qŭf&q+:2v5v 3A{0 c`wc;d[>f=A X 3Hm;@{AF{ r!AJP4F0V X/?  RLUL#QU7QM1v]яC#16S1'Vb+%Zl L3q5v;_OULw[\"(I -ZAQ - fu3&cs!OU{RA( -V1:A`_*]Ӗ-(He)V1g7a~3^s/&9d&1 6 :B|:x‰G5E3Ȗ5txkfGŶegUØ.xBДBi#6J2k] ,]*.JA~$IAQ -2)|M9%@ Ђ`# `+N`,pW'R9=.&d!r)u?Q ,HOt~qf[L2G05IquGne1v\(%D%!j)!I.JB+HQ-Όou-)!(SR %du)+XΏL! y}Zj ym>xiSL!%LM@/$)t$$J$#ٝ.^K ;1!T@ZX~n/ѷD2LL]ļ_LH[?7 a# \K$"Ÿ;BNJHQr -qXR3c܍yc߷J$~(Cȅ+(>nk9iۗ>䕫snEA>Ns>})z^?}>d)HzH  &2pr -' !+66NF -WF|zMZ]eCTUm#6A:] +-{RM(X -RŒ5f %}(Hc- -ROqyFAQ6sQQRdK -2P'!/)hxPT hAz0L`l`'08 H!ӄ 2O-DOT]DO4D;It~( łtk qFv &%ʅ!Y BRAΏR WƼD鱇o#䖘 -RTm rF+(wc!7 R3} 瞇w X\4V d]fĹɅ]Ad˄>3)HfIcwWҝ3 q?RR:N5R]^Ԍ?ӕU(%Z gQr TلS,cemx$ѶYH&I>F ~9}Z=rw2nG(p*m!֑rgIgNBC9#y9R *:?OQDzuÂ-Klߓ܊KA?b* -c+F Gz-ocцn 5**P1!n>bċahyj~7"qؽ*PPb|(hxPT hAz0L`l`'08 Hy7;ġ(:*5I:c15sPElE SG\u\!v"d_rCE\̂ P. +(HS,K\Ashy{oP"-KƢ\-5N~R.CF%DAz Qghc*VO݆e9'LAA,I*=Hvp WRAx(=YA{ɳUVzYB=?׭h?)s֪:յ58mWz]Wn}m2CkEƺ.g*%LUmxjkge=2{^.sb(WM[U={z(a۝o̴PT#7h5@Că5h@ :Ѓ`3$`;8^~@D&"/%tXmDUI+DWI%ĔO9bAr"{$r{ GbWo#=򳘷WVK4dSb^[JQSKH9#JA6Ii -6ʲ_ xeɫ(a,K~uҲ—PAQOw>qs\Щn˒+9a -L ܙ GFKr3#ar<]7V٨F>" `k({o[LP~^$kSA_4hƾ=Txj2; 3xacFA  A Ryxw2VokA'0tc - c5@Că5h@ :Ѓ`3$`;8^~@ZDH]#$ ]C =LL#3H/aJ"՛ ٨>H1 nU Z D)%+Yrq6-;%fAc\+\.lLEAdo&^/?wilTYӖL ~U~zúvc);qHc+ŃeOr0$S9OyZqŔRҲ.qEϛyʝ3*̚fmɾu_c-!Cvkڶ\sçHLKwǚͶ2Bm2g2f]%R[m/GTyL(nze_,  쒉W q -P -Ԡ-@0 ̐ \"%fllR!I'&!b:Nl#6vG b7LSA.RAd1֬fX~q+ jRA?,Q*}̥+?|mF]\nahS5K -K`ΘEA*.O1O}֥kOn6 -;9XƓ&$,atTnwno -lb?EٚN5,s>ItYn%jߝjI5{bZa/bw$V0%5X +;LG2~f|pV0U#Xi{+Rp hxPT hAz0L`l`'08 H:?loͶ0QU{d0NC1VG6!b%a"q G6g~x-؛RAQ -b -M*(1 )ǹi_dT V 0]E]AAn/~kIDAOZ*- -3׊L%#h?t%f)gx\ᠸpD!>d >N:>dF6w ?F^Rnx|PPL{jvXPn4hs~murc`i`4!me߻AwQ$DA - $ -RT!nfo>d3dqWa" -Rkʁ8(AjЀtfH Xvp.` 4X,H]ܤ>yPn,24.=?D cɑ>u&$qf#;L"@"z 2!(᥂.D +(q -r벏 rT(3* "2l\AA*AACۭy3dkA -⧘Aa7?F۱ęA]İ#91Iw8\I;8aᤜ7g?ʃ˶iǕv,b"6:ᵚ±ڂ..~3c33m{W0閱&le}@G)Tʛ+9ՏȸM|aΛ_T#JkF.=,}_,|?9%@ Ђ`# `+N`,pWɻetL+S˔}2ՀL'ӌ˴2ݸL/3 ˌm2Ө-C:&e[f9dL5,cd܈yeB+~rw|%u3_A:zVM ײ ˥w!f:gl)R:TglÁ@6vҶ+av^rQ}˗>E:O{{ːIGғì#3)=`D' -:9w(N?o,R>zgXOO?.^@R$((Smpz~A/ar}IA0|х?PR~Z/ b81| `ēQw,_>Q|.7߈U >XYF9%@ Ђ`# `+N`,p7`=!ݑ>';9kۈoLFo'ξIA]m#\Odozb\} wP>Jb>cCWPgs *,6c2ݘ>'LvI7Ǔrs`Lcc{k_GאR(g2o۠ZHd h?3 JqڽT -qVe?/cHȖ!$$O\tFBpː:$ q$X>)#BBr;7$_S4A<(@ *P=&0CX -6p |UF2%&d(eQuѴ0D15sAl]+ ]Y\#P a"r 99fB|RBDIYJQs _:.e7iV_$ !tx|O*YQ -YAA2 !Up -!O{>_q+L3.La9G8aWa1YB -Y#k=i\@AjG)ʁuڷA6[7*CHƩTSpyipRa: -! -߇Loy -28t;ٻxTP)>4Ɵ1 - -2C U."tXHc .Wog S.̰Tӣti -rr - -rKqW/{e`JRJQIJiFIHp y{xJcsACޓ -x%$CHsViA 䧅,c~pcيd$e0p0I8X&f38f -e%{},yq{@W94_[qp'T:0Vrk{9܇z]و[ߐ_.3"ni[Ϝ#6VlKM}w̹9@j>m}{-(zʗC~*7Je ̎) AZЁ `!,` x>CҺ6M͋fGVU!Qo!!<"rb,'ZbRLI#a*#5ĻmXdĒsC)!⊒Vi_BfBB> -."H 9< 3\܆=V>=ӒZrM,}s?/cں^luۺe\cw' s|9~Ih)K+r#P4A<(@ *P=&0CX -6p |Ad#Px#fF(e*RPeE2;QPVFٷP -r)(~B pH⡈5R<ҥxxóxܮL*gzlVzp1{ɿ&7qe?Ga6K&>tGUϢLLljs7$rIɎ`f"rG{8BL(%{yBbcKk_#6ggN8ZrA5A9<5ڰif -TSTP10կX,o0Q3>GaX|I|w`չ;=%LXˆZ.> -yd ) AZЁ `!,` x>CҚ?:ĴQuFJLļ]?zغ#ȣ0E&Z"L}󒣝FJ/$RBH DIȟWW/?E.WvQ9;˨k?9h[QL)/ -dS]\ms^˓`3l0î +l&9"Hͧsgt8nKXֻp֪>s ݚ}v^oߖLwLӻ|n`J4[Vϊ㾇L X b,V0cXTwP2~Fo{V09XT=L;-T+玬`5@Că5h@ :Ѓ`3$`;8,pr.&r#6&܈$b;A>q.CILO#f0;2Lw0Ȅ\. iyY{*y:{d eKgf,s4emQ1o:Cey1wiwg[zBgYoڲ+Kf^}{0=(cGeCؾ}2n2_ޖ my{eeOFdQYZ߁"Q4A<(@ *P=&0CX -6p |A͕<(E,T%LSCi )]/ u12RrqR^M9s(&rSl=I)wMV)#1J?C cSDǏWЏOS|yFZDjwFkF sGNǘ@lb>X>qJ|kb =?k!ӓFYp |$%s`8$==1+ ]6Y宑ݱ> tZ85L?\C2oDPO/h^Lt -Qa:_Xڟjz5\)t:k Z5LkXL4b S kn~-|k~aj[7{[&Ca[Av` 33~d ) AZЁ `!,` x>C r6 /a0eQumCd%vbFL-|a#[A#|!|+Gnb-M aiQ(WP,8/ }Q;g֨RAE)W+x 7܅Qq?ei)Wӫwu]N;^׈ol~q9@plh}w4l3zd>nxwU>MKWp[2oz*0'rz "/"tXJ(󥄈#2;L鋉ˈx͕eZLly^9[ -DsPL|Y߭ J9#JA. -rV̭ +yadgbFҾ۳ /$.JA6 } m70Ez{v[> -Y {~UL֨K4>ȇA AՉU'OdpAwRb#\e$:lvy]ᰇ M靭bAdĵ}i(CA귢 hBAlCAZQQ- MG -) AZЁ `!,` x>C9kLDNTD=Z9*&?  RE\mlh f"T80.杘ۥU,JA -rT(Yzw)Hysr|ZZ<} m^ƗYF-MNØA2] ydh^?_z mc~DbY;  :B.wFFȕ3޾qatFr8w|>k==?,np?񢠨=EٲxaAuYA_V1SMc/>}3>etgX^>"W}6,sVC1P={:rɺVoox# =T`p5*7"Bkʁ8(AjЀtfH Xvp.` 4J->?B3tt䑘ȆqbOČ4g;aAN0Ljwg<0v+(I_ z,;ݷz$y(ȟoNYAA(~'m˺y,=??sQ%h%D(HOdlrrxva= -Lr&Y;99d]F&]#)YOOng (;ZԝefmU:̨z\fh{qd,TvC 2w(hm& Y_ ^\;쏦e|foAE7&gvQo~1HQiS'dD>x0Q pd\Ilh% b"Jb]A#N\}Ovo/omh/y0A*qosr9|*D T5Q -<|2 -P(Kb~ۍO[~"zV1=i_2@.߃s$Ĺ8s3L ap:r{1cdݷ ,[O*V~KPO(שJnߠE{)uyA؍% -TSmwyJ<,b) -Z:1 R -2^bKGbdci }9q%>s3H(HS> -R{1r!AJP4F0V X/? ]|C9Q4Q EV1D@ #XygTX~nbr(r%u"rrub=[ * w8=c - -r/ox:x=D5HDsh#GN&%wfɭÑGb0'G 79[R%dG!b)!;bmx -QIqYSo}!ƘCo>5JAl%D'm?]BbRU, -,cCAV7 -B~7\zYCHE !$=$%:H -9xr$=LǓs;|CAܷF>zH%>ۃT̹}j wzA3nv]^ًlƎTS+~фܽKAk -܃ ?L?d= ϡ [7 oo΋Ήڛ\ EA -oCA檎_S4A<(@ *P=&0CX -6p |A:ЕbA|-uUAjC1vb^7e)g'fUOzGNj#n3cRA4Q - XgvǾc? }23$)!RBN7J}$dC1 ϋ˘CBWy3u ~꣝J$$CO.2f9 yaGff`i Q3 $KM JD}&<{1Ǟ^joф}&ʡo+NYҡ_FY`e_D+δ7MP>V._yO|Қ; +.秅фDMvlj>ǰ`fF#1V;3ّ2|>ӆ/³7{<>K>J)=MwV}*C:ɭՓz]۔[?wg̐bcnx<@0T{hֺl`aҵKl@1/6P)qGK+Fd|ޒ'(a7P*0F+ʑ=r!AJP4F0V X/? įROn\:ZIDU@e}}e:G̅¤X뉭s.i"/-'g%!gI 1I h/XXxwh=toۮ}+>i8moS(XN,MbN>y S?EAj~o9 &' AGFˑ$I=Mg KKpa{XƳ-OXdvt+)g1f}N[qX˟u{_83M(H3g[j (HQw sy(fK]AAF~4ف ?w r!AJP4F0V X/? įRF䵋G6'rymCEd6b!&b&: ZgZC %r^P"/- B |}H!Q -r^iQ -ǻC - uhg(ͯ۱̥Ϝ)KkiXcg&O'EA ->/tGtK=n|09rC!.G0 rec=x}N1u<FwVzMŷ>)(R9 UߜMюܽN?4R#3ذ78jD<8ݠ#2\˻)f{7j+oQg2~uO<8y>kN6HڞҶvhxPT hAz0L`l`'08 H9^"?@{B-Ǧ"{Q}HW6F|ec{g]Gl I;w /q^̿O}*+V1e/h_3ȕ[N$],|8=Ñz`(9fG֍ّt笧݉CYR}T[6u wRl7h'~jؙj*N5)X m(HC(h={pa߸6sMH9144 1Xi<yvtsZ|v>skn&ÊH!EX*r(EFiKi XO!=džT/ u=}_yiv[)g3d`#dz~f` bA -25h@ :Ѓ&0l`>i!LMZ"hSQI#DS)h6jGlcB &t1Մ+&.a{C%Um< 5"BܼO<ď~ : 7${^G=^XB&}otfP|1O +Z^5HaXFo27SBd0/Kp}YI$'緲DT#D|gs~ٲ6\L>n&N޺ V &$$M_Bm$~xyi5)BH #jӵyUѺbſSǢ :?T!AVS/!A޻(rr)D3ZPmw^y(_zo -6쎿|fGau%f9@&wmǮ'h Lf6D6 R}4J<'JU+ggSTEqR5!A*Z芏NW!ALiBW A: -}Hb - cH 3tS~e Ӆ )b  9(@ *P= `3Xvp N YiK*V*CCRcz扉ZJn̈́cO>:t7Wor}<}ޜuiB2 -wmI*_@qw?O/I#04#0ԗOӒs9Xn $Z$Xٮ,.9)9k{&4-m+%O2(9So?>)?j>ժ5tT5t}~_a*|h(CQJV(9v?cn{?)O?ViiߎpJ:+93%@ Ăd (AjЀtăL` ` x! 2|"K"6""z -O[f&acv31%G 15( -J+ccpKc%pi#9!F%DYv;a9E[}YFTiاKf" ]*:m}c7D!-"dzt-GʽM51WUe0r>l">_b5ͤ9s:Xk3!s>i9tFH+%3g/Aqa^:LMɫsSݼr;EU1NyT B[y=]|-0\ɭB3!Ӗ -ztr2b"I6ۺ!53AyDL1"$4?S @,HArPT hAz0@<f x z´DKD%tm¶Ĕ#1pL wXX9A<!p"D)!1Q_f 3>f|_y)אyά~—; C"Aot-տ26!IkP\loGkHY4AV_VkM -س2;GӎwϬ fvnELL7xiSdEqܷͩxeU~~f[d.]t_-BN{ 2Hӧ-?짛۳HR$䦓"n/cG CH(Bƪ ;"AZ%~X %@ Ђ`x0 `,87/AПR.?/5DEDDT=DJ4CD; L[o$N pz$ - - Krz0p"$K(B / ~9p裮/ onm\hlyg*nE4"AEtmFL!AӖas -5H F$HV~dgToQV @,HArPT hAz0@<f x g~Zry^BTD]O4 aie.eʉ}8Ɨ:·|/$%!A N/qˢ,hYᗺҾ2Zd#dhPKSl+!?"D\yỈԈ_tk ?>Ƥۆ]>bw:2 ./YDk3ffIYIY  -PgךEOrIggʧW;[]NU3Eڑ5K=zz uc׍5?0MQd EE3 b{&1kYaד==b~=[i-;Z[G$@ Ăd (AjЀtăL` ` x! 2@xG$E -sqU5b,'aie0.翄̈M-\F,b:[pbV%FȇQWt#䊨kqU.e=BV!#DئKҺ(O{!"DOO_^|cnx8veR^x3U%%B:,Y3VG ёKNJ_8tbmCDw+%MeB -j)zT!}" l;.SOjB]{_>ԗn,C2o4LucjOף -݃*eUHsKȋc;7;ER;*d}L1j*?S @,HArPT hAz0@<f x c޹DpHs$t\'Q SzhJ _=G萏9b9Cls| %|S$/'#Dr]!KqEo/rd*—;#SFHI"{o٘vor萏!AN޶%U1 Zܞ3Knh>]X34#A"Ar c3A$ȩ ?#AES너 ~]9"m13 c6KL 0 AJP4`6Xpn^H 6 uHf%in -}M!bcmۉljsp]M(_B*' GH[ q8A%$ߪc!(+˿ Xݷp̆ۘ"| ٷӘJ$HG|. ?F9&> nE  \)16hکJ -$Y3l+eIf5Ȳgv R;(5,lkbj4ԋc'qI8eg5`!p)ChcrWK91ܦџj_D3uc ?056fmX1ژݧ=Uhc:)+[ -{mƔFsY1}ϔ1 RAZЁ F0,`;8'p<4Iajh$ -"!jagCH*%aK}1#J bK.6"i!\m6)QpȢ w ҿ'mE EWV~{쯄?\!A +\\9ɻqsݷ1Ɛ { -y^M -OalwyO $INY3l dk2sYI/'d]8t5kDȇꘂ7c/˺\!/ع\Qre{rU?PU/Դ4\;7&g>զ_3h)-ڇ$vy\kݡ |XgN,vfN8*H)@ -P -Ԡ-@# 8pBd)wHfnAX-CmŒ[whnޮ^a;̷[2l߱`3kozX$GF5R _B$}kbh?ZVH?B$p$l%ׇ#G"aNJHx8BQӗ aYFs:{bp/(*|"U^{B$|HLv%%\ae}.k=oge|輠عim蓏h[?23mŴك3u5E>_ᕣRT;?ժ>5-t}eGaڒFk>S%5hK+OeOҧ-GЖC[9) -6 і4G[R?d`mShKsϵ%` bA -25h@ :Ѓ&0l`;7-|AG.O)%9$Hf:B} enz\x_ rs1=Lb2Y>MY3ϡ!qfX;Ax^;JIY33LM?Vmᥣ)g%o񊼧yPcsהyϬUI7Lm/hl{gK;"A HP^=N?"AnpC4~[8%zk%@ Ăd (AjЀtăL` ` x! 2]h"T?#6$<"v 6k$bQ/Po"^a gᚉ{iEMh6/aaEPKAv_y{O-:%/5{3ꗍ hZB(~ۖ]6j!'{"$Ջyc=~?Ooz+m|zK'2-u' ~{k^=+\U߳RҶ}53*"OZ݈"DSTCeqwxM)DH{]t}Ha QhgFTL"B :.֌i;D_Lc5b~pA2>x5L 0 AJP4`6Xpn^H ?eH sFȈPy3Qt堰aR؈]Et{3>zUg:E'cRx_"\qL- Gprma.!_B~/yė:]Fx8AE:B,!A#!7 AR$HQBCT5q [܌jyOaʗ =`_)3ra=qxvX[|#bLc41f4!p1ss{\d9nݜsH"Ub2DDJRH""RD9xېX y?|PTI #y%CUVSƹ.M[M6ƍiwn6.}Z??!Vۦou8\k.kot8q:YgKko;X_͗O1qN͗iLB\kw$_7,ܸT*UVHz]RZ*ӟݼr+KR ڹ2n))Ĵ?m2u7̳H,Iy{mc6Y3OǎH]ӏJRmdm>H VkJ(AJP4FV 7x | -"&8Z]EduDAMDATDG4DGtDC 8@bVX{#H= ڈm=O(|M) 9(@ *P=1`3X -vp`x!Amn&"k":h%~j%EUDWLPNd$Fq"&%=i"l7ډsݰd!C 1,C9B&!LU'&$%$~\A:O(?PA~v 0ۗ -rTTw8 LqJod;o=8YX'u(}QOݴMڝz &Hu|iSX4"e1Y9lL:tcb RmccbAj\+ZOg Xt6䝟ʬ^|+INS=Y" -)ޗSEǰ6?b2tϡ O [Q,pv@ArGPa Ynv?> (< -2Up R! -ArPT hAz0bf'0. B*_eH; =/dȺ(łtuL&!d{eXC,a K$0qw-W#do 0J -wRAa -B$߼ 3 -M6/W -3&0u PAS :agMsAE9h. ?AA8/<(ƋxS s( -2?ԣ ;:&;Q:)0M OmvGkTs §X<# S(6>{$#9XGA2m\"ac3ҝ6K? 8gBA]+޲.(BAQ$»Ip - -2 -Rκ-AFEA {7POQ2~ -)yxA5@CD %@ Ђ`#Ā `+N`\!QBZFBZ4 )| i]RȐA$]*6>COKl~1,c|->;wbBz+715JۊӃUHH$20/k0IsbdeQ)FB -ixmHH\_PqRɴcg .#!cHHλHHk-y ipq| \y˘א"!;#!s) d (AjЀt!L` Xp,p/UR$(zr&h!d /kwǜx i#\8 GXP1_ލ0 ^AB6n)nc'_&Cч)t  -B)H - -3Bȭ0P -rE7 -2MeBԹI)1fv;җtmEAJMA6=%la8g[bȈs.t7կ(]#.3I> `ZE\rUw?:M$hn+2fr}~am9')|;anʬXZ&$ֶ}{Ur—)鰄|f׶*/xro}LHy߮R5@CD %@ Ђ`#Ā `+N`\1e%qQFń)"l6Ᲊk7(oVPAa -rW !_AAiRꟖ]ԟPD,PAY̅ -* _pRAnGA^苮*AA5 i۳QՙAdNmĄXƖ 6"s8 ;b3bOZ >̋衵Z cA]Xlgb W ]{f~l~3[)Ɩ ;MS>sfY SݘA> 2f0T{GmkV~3a  Aj0hhAZЁ `0,`;8 xp -5qXPl"+'* -'z#b+$"bh \ -L,EĺKVRCbW/ˉ݌Y+Y*wD*ա gv^J0۹_C9? ]Ju k;+(Ȫko d(f^t3[(Hy:G.܄ͼ𩛶>UG*/b9|3&}qqiXzjl4AKbqii XלXG7y_0Lc $Q /TQedG -)ԏ.8vm߼_>f!L 4:ラ4 MO]~}^H/=4N/}moEA5 -5eW ѧMA4Ksp@ktǒ&spδ G:8 7wAJ -JG,#ji?U% 32Xëfړ;u^VD+w>Cfw1x$f)9p'6 AS -my„+yyg@x,frOy_/P]kJ(AJP4FV 7x ROdv,#Fq"VNL51cPfb^ bm7gazī i& -rV'("^G+2 - -rT\A>-Zrݡ -rVlZAA[c)A[ݰTd( ZR${>uӶU̝bGO Lťm~b/9X e ٝV{ |)eEI"W6ɫ:>i:?ⵕ¦OҏuQQ7M|PʷQ¦vd -2P(܉FAJ6lvu}LmG+jQ(H>xA5@CD %@ Ђ`#`8h3X -vp`x!5liCY+˄ـX -"NL6]+ѷC#1^ܒD,nr ǷZ;1DrTI;g?W|D29!!RJ' !YƸWy;ݓj͟s_ZZ\q.VV+)mK{He*{WMB,t[llBIw8'|lbzɦ2,fԽkE9{۩l?IޖNMVνīLR/ܳNӚ]AB:'ԏN1{ꐐLO|yfnp37RR7t"!sHHk W}>kFB{RgHH7s5@CD %@ Ђ`#Ā `+N`\<ԁ疆nB,j"Yf$Qu;m- }L3!>Y+>NC9 !k!\) =YJJaB !m>ծ.WC_k;HVbnd$s1ׯdyoI]9 -步!doQ.a -F^ѝc(5ϓu4LIGFDcӘ?>'82kM3  `)eMIOuGx -^5eLy !?@Aʞߠ/:bI16=tdxg -. -R" -|Sx%I / #IͮLd>dE -ՂԽw|ה Q 25h@ :Ѓ&0`88pnRA*DN! cDCD'4@mD3JD7)d~b'Yb"A#~4vpĕ-;{}6T )Tг0 - -c&sWi du*HOMVP4i;'Ѧ?FAJxS,cvzKQP7>Og~gB͙1g'ml,%Ħ;NƂY k;^O? O޶$yQdexU11:ΜdmE7 -SAZac.$Xo2ͼ3wu %QO 3u( (H=(H¦m*?eLG“u(HK(*xA5@CD %@ Ђ`#Ā `+N`\~f1zKy0߄PB b 9)rN)Rr`)!ގ qŸsABlnbEo@*Rxݍe;mqa b3?;i3\1!yN~aKswnEBvy:Edf^5ڗ҂ _R_tug -.iR,<;Ԋl-EBtV !yN1!!BF^GB<$ (FB*OhhAZЁ `0,`;8 xp -⊊H $Z(Ą kJHHh]x7QLȸxx᪉//<#LB.%d40 XѧH -9 #! D #.c+(H_1ix'4 §nzflyi-QIN02>Ȱ%Ʀ;l 4:bٓG6N>%ލ)z` ]zU,/+۟$޶NQ)˫vaiک(H )x@ԁ -2.q$Bx -i) %/ 2xA5@CD %@ Ђ`#Ā `+N`\<ԲCH5  -U(f(e!Qu.єBr>OaF,ĺ؋cr.PN. -'ok t T SWP>EA}'_kWK|~ gߍi8#^37K{  2Y^3m밌)-gw ''n#YTIjwIjܷ^ -4DA4@ -P -Ԡ-@0B X<^HqDZ*$n@"( -%b*(QK4mD J$" Q'1WH,Ek^&qK;%L#j%J˝* S *T zaq ]Cj;Ős ŸT8WdZxgnQۏC~::=<*<#؄㯮1xTݵr`lnc) d (AjЀt!L` Xp,p/VԎm DKCM&j&rLM}#1c0xst$Jh&l z+SpPoK"mfO %  -b>ӛ)_kqfPAzC9ǫ+؊AY' -W M=kb*3Y*jIRߵNOdlCác)~ktT|giH# -Ru?"l$CAZ 'QΉͮ/PP(H(¨p( ) d (AjЀt!L` Xp,p/v?_<M("!t{čd%bA -ĭ5X d7Tɚr{e+7wDq~  ;rv":k -*LAPA DfaY,sBz;mtOK.~D '3rwf]u<ՎɠsRi_{Or~B|\͑ O g #.x0۞٘XG0ZgPve/[@A&+gyUe_z -3H]BA27J1)}gAA>FA -   Ow`Q'?p ov|kv Q!i>^|M) 9(@ *P=1`3X -vp`x!w4Y(H/uQLQuux:jx:xfP($14V{ qtxB:k3 rC $o S_dxv] U.ۿՏFJHN(!Sa?`c<2֋I,chpry8 9$l !rw1<牺 066A܋%p<36-O9OLȞZ]Y_hinm(jk?zjYë5F)Z]ZUTZ=0jmΫu#Zzac+4ui\Xvw$[dc*gM5LG)\nފw~|)) 9(@ *P=1`3X -vp`x!M oeoP\~hnYw'ۍP~±y:ϮߺL1 - (䅉BYBC( -N‹+yF~mkGr‡i'DQЄ -&̵ :P 3W `s](JopAh҉O" -YqpѪ§Uy'1>os[6=q8鶴 1lgsߖ: ~/I;-M޺9'*cV8+*OP$Ū[j[};$MnCc8ޓ'i֊_;j%cm֞}g)Gg)gSx;pG cWRmUQ){ה Q 25h@ :Ѓ&0`88pnRaNB抠x[6S*L:* ŹJ>H eX2)'zJ2 G;; _M5g|OÝsW7+Ǝٵn^*+0vc_h^cµ Ɗ^qX<+\۸>u2 -2pwbyHΗg_gc}>ߗa'3NϦt[ݿU؟ZXzC>aO2ԯSnHVUY:zfpoZ~GmC!?>sp[]xoCV&{)Gc3cLZX?5_8uL4RޤR{kOה Q 25h@ :Ѓ&0`88pnRa\)=-djŷReV.i%Qqo -$ N/#1>qؘĄ 8?`KO2Io}d̖sJXO ;Dž{yIw&+x$u~ -2kDAߠJ1bvLm}Ad;(H\K~ʱm?  ز&d7 -2"g -2~3}HCshhAZЁ `0,`;8 xp:{|cG5V(Hy/Q&n"F+DcݥⵍK , ]cWy $c+YAA>~-\aۻ_ ɡ"ҥXmW~oqrN[=0tiܛs*d>*'^wBA~suct##aspeG #~*fpq1=ekw+Q -Rjy)򓕵`!ofgI76fk>1:|~A` -R+T3ķܾT\.!Q_{|vEGi1]ABn  !"!rpiɷ߸s§B{tH9-(KOg6gp^a|ϖs|>\Im(81'&d*$_HHކٷyَ佹/ʑ\^1hv iܠBBvuM&S>sɣ ix  ݏdBBF1tmv"! BB -^BBPQ -4DA4@ -P -Ԡ-@0B X<^H᫴q&8Scmt𠜰IqfnZ#xL͓Dͼ |0A.+/9UT){{T5GX is8ܧKt5^oqʚ]/IbiNӹCmX>}rWQΒnҭ%\uͮ]yU(OS(O֛uz*'kJ(AJP4FV 7x ^+!O,DQBDDUD@iӂZ;/k)g&1qX7{P'pY^PA+LA+(HA~oF>uPA3xBZT/}Pepوdu7f=KYZ苮-GAzOQ,YxeA+( d (AjЀt!L` Xp,p/EO5$bnK 8پN?k˅>AߕbәbV(ɔt !BBU FB -=tOHos5BB ( _|_S -4DA4@ -P -Ԡ-@0B X<^Hmzd鴠b)](2G*[ a Q];p+nT/1BBFëx4qdK#)R.W* SE;\BBa%$.$dh rݪȯ~wNrSuӡ)Df ->-1<)IpC׵3H!SK𩐐aL!WzKGABZ>}1qt`Q[\Z|ĴsBB*Z+۹M$YXuf^wWJRc -iߙn޳A?ۙb)L!#!L;2Խ)}HȘp;f0SH="!;Mnl{ )nnj,\K]) d (AjЀt!L` Xp,p/;p +L!▐&qKH%܎i%|/%]*Xڈu+!*qSYxn219]NHMeIX$$*wSOVO%_Qߢfij>bB9+⦲3d(Q% - -DA6 GQ}(𩛾x瀇K_$ !;m -&&%WV:Q,f΀Ǧ1;遹ƮRY+ڵx4L&wݳN}a6I$ug:MCQၹG6*{SEAd!i>$WGnFACAAAڛQƻHٗ6# KQ7PQ$əX,H A$Q -DA(H A$Q -DA(H A$Q -DA(H A$Q -DA(H )J"'fh "i6}x-sq[j4k/WG?qVG6:+! JHUh&LA. ~I1q+(/nr 9PL&!B\ - -reP]ey$P:Y%$$M~`О(/ >TBfH^\mtƲ\Zsss)K-.X+۾*<17p)C^Vn {"7?Y9KR7޵Nӕ|r/0ޓblBB*_7* u !GBQ&g^$pXGfWi60ىO !co!!ٳ) d (AjЀt!L` Xp,p/SUֳxnr'(A|yC]C XZǐQ̈́!\&oܗ7tarA(!? ! - -SY쥐쯕">K(!rN)d -E;=cTppr:F|ݳӘB~_889똳]Bʮr19}ӰIOqN&rufd4LX)N!="!HHA-&{ӼrO}5I=edm{_R_)똑DŽ&SϰϜ'-lLhS[;c"!۞GB*_DBovOa/<,<&2ՁdNOhhAZЁ `0,`;8 xp -[ۇAJd♅DYIT#♅D[/vcjb&b {x,Tp^|{Cr VĄdE<#-:&+HuS]M}`ts_&dsh - -w(!_2oorM! !Cs楧cn˻/zE$/|)${ŘB~*<sh!ll Ɉ|LZ-iqq~w/wiwȥu/kkfDOd] |E(>0\{hcl70sq`䚘]%:^d--=yc -p60yo$aKlp)f sWĽOt;^IjӍcR! -ArPT hAz0bf'0. B*_%ns٤D!QK՜D I ݼD?+1JSc99RJʞI9r(g>ŌK\(~X➗|3?~Ox$08g~M˵c2ktza$6=j|v\vwqЎmy]?ZS܂viK Q]ǽB;.-1-19DKl`JRWFZWckI˞N J a3ZE\rUG:{^gIt5Uk܆vq5fT/=/I9/˚(*Yn eL[$΍*ΫUXLQٞz*A5@CD %@ Ђ`#Ā `+N`\ZȿNGrɺ;9Ԡq6}QG C>^䥽[~4z\Bz|\\ 'kKdcll||B?tp:^n֬tP8pq,`Zx$yc_ʑ6^U~(I]mf<+Y;pj)ٮ6aS!,`G'J N.N.&lLfWɅXL?̎ױihf0R! -ArPT hAz0bf'0. BMD:B) _:_|wT+QNňɓ'CGK2N)' q}- - -S_Y(+(n2RA[ɺW#⻣~sɻiwA ޴4E84O8$>EAvKEAPs&OKl,3 XΙ!yua"IU.>.<2:mT1鄄fW"Vy퓘*T5wQmkCz[?^=9=@#D9$N['1S=A_|7Z. jmd.uD/%lYcE$uE.0_Ա-R|%j)A6wԔEAsp6]'gѹO$kx$ DU$\ב ?tN1n+˓D/wt7˞vm3ݓğe朓4AzMrHvQ (gU>=6U!AjPКjk)1My;dyN;@;#AJ ANDl)&7 2(7?޷]'ׅ@RDBʽ0e:p (AjЀt` Xp | 4_Y3RߍFdn"0ѷC16SM6NGqWqwwa=_si r}{.N)A IJz6޵PV.6PR|_J|{{ɛ.D߅)D r7}7YYSt'?RӸ[$o3ٍXLdzp !/'d&z]o߰nq O=Lv ZSͧʉA5 kh?k7t!A}Pq08z0T5lgR4 Rqqέ$HYk-:5SZ5SHmlp(:6s?S@,AJP4F0A<V7x! BKāDUFԍDS+b -~1tc'1oR/!bCYI\z·_|]>\JH+mY)AK b ?\DpB-8;俤 ^J+Bz0ו$=jeH$l=wL"AwãeAƦ#A\:ם!w3iOgL -dO&zCY,>sוubģ+dCWs^ u 0WV}"rugkf|T)x-l0ޛjO5=g5dgB߃ ASAz&]Wpq5ȉQ伱׽?s8dv5H; U1;W1eA ĂAZЁ `ă,`8 .px~@Ҁ>J75e3Q u8z:i۔&&;<S  ;QGHY)}jQ-"A> tx,#6侨I 2BZԽPQ|, ?۵vSjG k ;R*J?=% kݮ {+=NwCD{rzFVVfR3PUOXmt),Ey(EQM;< hsg]tǡ ƜTS6-Ofs[!Q }tzݳH:$HY-.nN/ʝF T6jP A6{* ӇRDVOA b4Q'QG&Hx$q#a#I 5Hޤʥ#$GJGE߰| MycZ۞dyy_zOmdb}l{"򼏧}Ud){H#A|AbFFq94no- 0 d/&g!T#d1/&H[I=rYrn3[ߌS4tQj8uq}Whwt9gJnט%~UeqZ -3SV^hn8f\a;6e#SoSWƟn&P,=Zu[)6dA ĂAZЁ `ă,`8 .px~@@\):nb݌aTC:hzm1.S7mPg,Ռuu39ȸzwaQ7}cT|$E:):  nٰPvEn;jR5;/vJ5ٓ(> -Q}}dʇdbGخ|.ES2]A5X&^JT}V| rP}tn0RqTSO2㯙C'P}7ѭ0S69نc+>ֻ;}SFTS@qQ6_5)b %@ Ђ`# `+NpC{m[7B "$$0QMnĭy2&\'H8c؆;5+v >WFGF7v URl:c11X`q ۨhHՇVX?DN'g}{B;A+~ LQoHS`qIXg&ҽvwȝiONH{f9aλb[n̤h洛˥iz o8e^PW5hrgږqA vT#2/d>#A&h/Yۋ8LӁ p~ 2ѹޗs 痒h/Y1/U_Jjؖ1 rPT hAz0Lfl' ^!AH(y2Yp=4A(IJ;ab:AXڈz8^";.i reԑI %,r\r.nqóh_a>a{ GA^D4IEH{fI~U"EDO?C ;!}jQteS\*<&~b 1XċbC{>b3$7-!1Q#Y%j!R,!w-"B.܇J7.*A~+!䍨Od:8۝;Kk66v8P? E? ?p41kf%] '%ݼ==11Û&&wl纭JYti؛o1nηU1M'[_zߘڦs@ˌsׁZ=wӳ1|g՞8cPL>0}(b.=>R;N?[8ɐңFz 2-(@nԕ#=闘`|b؍f8RmFz\Ӆ0!=t0zOv{uܝ\wbR(9}m,[])އډ\-@Z_Qyԟ[wQ겙M8dD OfoO5ֵ*G3,c) 5(@F| cd3]2;D@69QEO@^+@2 bA -P -Ԡ-@0   <@? is_hR'.~$ !q`r }hRE,-KlQ(nmߣJK{/m}eT\=  BQ[-(G}_H:]yɟ;mE$ȫ.wp뜜muǹ#́|$H ꏬb$7&W.pCyKmdvIv&x\ބ'ɝewP'JL!3#ӕºp+dm٫j:/yC$HaXOKQ pybjK?E'?N6#F;zRM-t^9c!K٧FQFlc$ $H{>dwzpN++t=GGEGi'Y{T< 8X5h@ :Ѓ`x0`8n>Bψp Q{BEm%wmxIh4KX8]..&A"Fy lR!AyIJE"$ _`[ -%5ȝK^YD|coSA!A̿: #ANuH?<= 8KrK)3ra=qxv։̽X[OΝ11˲,!fcLxNؾ>tߞ}= 6ggARDZRDF"""""""EDRDX!bEO -ӆt]ׯ^{uv'Wzsn*z~s)hns_~wo ywi{mݖoRi\峥mwܚzXal?knoI v?_eq;O.[9R]X&m SR\iIc9pg߿uSTC{&XYq7U -/|+Q^N)UÉOu6ig^>F_ОdmO2Ɣg276vJ, O K9oJ]')fxQA{rVHʻܵ{^.S7My3|oP Sx9 DPP 0`f`V` p8܀> W)$JBȈr"%|!!)''Zb$la!b)&Jbo&Lqvq_B<H~`EF%?R8g{ -/e -S=+_Q+_şQ\VNJ/~e2_(㼓_"T n"TCE8TIEpT"|TD2=~u߼YzdjY|7(oW֏DuhMŧiiF0d8~(8Yxd Sc0 0H0$$):lqϝA",>L -TA -O)4Q@@@@@ XN7x$Ut9Azlț!A*hf~I B,:M=#E¶g;FSAa 3! 7 W ! b_Az. rZ9yAk( ! G 3! r: CߨbB < q mㅧg0_ ?$ӺMA8W$QtHՐD%Jm]D?-1J-ȣ_bX$nO KvRpwx%kȃ -+wDy|5-!l cl. ->\AD(C+aD{hq' -o>~^M%0ȱ79ڸS}y7wᐏNA:9g T겱&H@=btj=,0u%醿)oAS?HE+ -ʬN^~$Zs6 }IƑDŽ%!my-0m(`Q )=bG9wy0K(`QL~L'  DPP 0`f`V` p8܀> H(zV0H-  QTeQu+ѴnC 1 i&b".b#L qt84H; e5Hh AXAIzfZ -|Q<#*d!DؾOEB({PȽ\NoܰtU!ȀBra^x&q! -9|($ʦRmƦ)(`i6'gؔtש -iܽ.3mt׶tU<8(~9Q>N+;gUNT4uvd -|:}v_xoq4d*>7C!=!af -~RX C!9Hg}w]PHO=2RBR@Hd@@ T@ 4@ t@ b X0Xpx@2^e8TH=eJDND5HDKD7m_Q01pp 0đ/esp-=F9H\ -IrB!r:2Hԩ -G!fkd,_XE A aBJD|3Az(c,0&y+e.74 xᩇ_z*Ѿ ;K LIH9!ll`js:@,` i;3 BO agxYD:E"xWw'[uUB)+V?z?,ôe EafhilAr^AF #, U!3>4=' u=0HA*aJA*aJA*aJA*aJA*aJA*aJA*aJA*aJA*aʠA:+ =}& hʈZK01KV{aU9LAd-aCuA.ahB YA_VafZŗ BhDLXyldѠXmANd :d")G|/MlA }wA;w2H&)ixᴱNkRm@BZOp-dmȘŸcY-ͨ?<~qFU/<_^3RQ;tr|UꅜHEo޹8fH)_mrK{^8f8Z?!۷M:wQ|xg~j`5?KI "A9P%P5-=0#&``v`p x^@x[eE[oշj~U#0BhfM̟[?y '/:!+ʪ s~EVNxiNsMSiDZR;RfU -"VxE)|tCHa~KR@a )<З+KYUsi WSm}K'w|^s2} sF - 4.lx"o Pp ipŧ+2Ky:Z̷Yϙ$ -}EmC!wF[EZy/:/&!  31V [G{ t{^;m(Ch,[_}7G_ҕT𴥷C|G_qC:bŕYAqtΖ'ؔ6?!Hwf9I}psc Ba ҆y ݘ$?IlDy=ъY^\ǫ -'>i:fxms -W/O2dv'K@ab2es;Pt0.FaX¤ N-PQnY&=(LJ3QL<,sGa)4Q@@@@@ XN7x$UuA-D1Blja21 -Y"1w4ljVGϣV rIJV h I`56^: w0ȨhOE| El r,A!/An2De)/pyl 4bG{S` s$UH?!Ֆ:lloKpbm))q\Jlj;m Hwt 3PQ#]hA^9]īfSGkrxm}KtO\K2ddlA7&?^XX{߁Az(fX~rNMu[ ]׵ -EoPCTrOc.xM)A$2  -*:`1,p< | C+"z"#z,&|n"f-%j/&b RD̹Rܡ"Lq"lgp G4HC51VЖG}2xP%xP+AV<&]b  r^ut]u`\: 8zL !2E {v=06T1q7)zq Cy0ȃBG 桊AےhI9cb\6W#Sl8͑H9*flsفAvmlH< ExYT?/ZѸ}1^uDhMF&Ф>Z}iaUn$gd 0HS0Hs 2~dZ8(ɌvudS0HQ R qAR@Hd@@ T@ 4@ t@ b X0Xpx@2^H;]/dȦ(Z[)'!k!b$A 4H&C"lWǣ M<ka = Tc._㱼 򗙯a{ε r,KU4Ȇ'!a4gA0w05*:!|: jALCC_x{|Y0;x[+>NKSqԴx9Ӻ*hϱ.ʢnl=,TV'T2kRUإUW_ws߱ڦlƉnY`)/gK,#OXK?(3{JG츄XzOV.v\^x] =wD):*dNJ|=O#I.=lS;J "A9P%P5-=0#&``v`p x^@x:JG͔dJ:(y#]RWPfJ[K(}/e襌J(K%eMI9(r6R\nSKQ@ :1Y>CZXs]!!\(a؈ - b xѦBx0#KfL鹳b)X|紱i4!ۜ))qiq#iAeD }eK zd $ %;h'DUDuWvfM [/&j&v -bc&Sި\<2؋+g@v5 /R10;G).EkE:fW5 -Ȁ(h0 ;` 8</䌓H=)"HQAꈺhbDC \L,}<%FmE0˨X#ԧE&: 2!ܼAN1ȖJ 0H@rDhBu$! m0#vz?D?X k`%;G_uC3I(aIIW$k1#6.5N2Oa$⹴+--?taI ⬍H R e$ -=3W $vDk:27it~$CN_0T\8 m'A*andikA~ ``ZJ8?4^S -h ȁ( -hA 03+8 nH^8٦\F @d9DGDYm"R .VC!1 -)%.b) Qk[AU+ zOÙm a}Al! M ˨ZAUgʕFdEu&Q!P5BWA$u_m!o3eO+ - -5?q%C^>᣽EYc].'RE`cl @Ù0i)ۧg ܿ>$]T>^қ(XFUÃꎼhM?ymy-2{:}}+Қd,$fd*}oήBA!>ث')fX7hAmn!_χB:K~UA%fo^S -h ȁ( -hA 03+8 nH« WAZtYKp(j{ffJuUVbRO IK^qvn ?E/]-cR`x6oʍNSdc26ԇ ]Ng8%&l~W|->>`Xi M{COlnA&顢yYbhEMʾ}Dњ֬MھOak]I#IƜVa!a)ooRqXbA?A|Y|eL1d(~ R RP8 הD( rJjZz`FL , -'Ji$`|lb(';1DDD86DWreX0G!l >Wɐ^WD\ F J  2͗˝ YR 2"$ 珆Hw0 -~ y 7ӕ Y,8gKa8đk;66őαI:wڴ .V}s = W 2 /8(Xo29^;.A2>嵹0k=II#UL<ޅ rEd&SHv| 2)|&.`/2H۫BG=0@ R q"ǝvn@&32|ȮWE57\ k@^HE_ʱ T9Gjj_@[F߾naڥKcf)`no))XJx 3M;fo`Ƒ$n"wIӏꈱQ0H[Bb vWg{%]I]aƈ?p\6kXxa궈3LC{-zROa )Dp+bsCW1yy#2ȟV D 馿ٴzh[bUЯAn9wN%0qq*ait6666\Fk6}.dG6H7=O ׹e|&?Z\,U&kA&G7J2J2U#l4pd| -dt2H1d)dP@əD)=UE< "Fɞ?AR@Hd@@ T@ 4@ t@ b X0Xpx@2^eH[ )Ȗgmۂ]DB41bD C+A!rb ^2DYa&|4~*FndLnaׇ#Ш 񥪘G~qsbQ00tϹd_=k{pZc~G'C[;t)= -lw;+NW`lq t[35=bUL[Au0LFzPX[9Cji\imv Y^M2yX4*QŌAFrQ=%Bb3`0HH=[USbN*&^\TtU1DZO蚕ysD^MDFTD]rjLqt!YABVIވp>nB(j 0H: 2Kϴ{-zڠI2愸A72.>cm !=m -Iㅧ~͓(cEtn] -ĥ.#N:P#8KmS] Jy"=`SطSufU1寠rNθP1)uj&ef#v4D=CiX_3Uqg%1G3֍2feL(cfgQtnG9[F$p=2eL[ʘƌ(_ʘ' DPP 0`f`V` p8܀> W"rB -EdK(ʈ -W2[[b/y} Gdzvx sf.?E~$3{ޓx*^|_/_HշxV?D( rJjZz`FL , -'*5lRVJY}TFfB^x8 }gهϝuT6!MacmƉ&ΟbKHelqqi+O=izbsuԱ -tȉClDyъnRUYGEk:vlf;"ddCvk0O @FG@@F-] B FpzN'6K "A9P%P5-=0#&``v`p x^@x)"] J70(QQljn;=wB Kxpmb/~xzRߋv_07aîRV?2_8EL.̊Kd <)w#07j"H0ql /m#JS *|tIsJta)fc"95)#=-: 3s ҥ7ғ­rB?L;/9fQxh2WUXۤz9v>$CJJqd'G`&\Un!dZe^8ݍı{`aedI5 -Ȁ(h0 ;` 8</䙓TɂgmǃtE#QVՉ"&ɕ-\G,[ZEKujwg hME$^ALgmq_ ,0'B,$ ;,:25AurM^, uKC_K'oў9d/Ν.8BMwb\@:baظ:Rs -7gwb6H7 ] d5L&VܤUhC ӓ{$:AAMy R ,A -K:>HG([%v e(OɉnQ L^WI5 -Ȁ(h0 ;` 8</{) +$S*, vUT', ]?A K+'2G'dđ%eGeH݃znzheػ~!! b]A^ "2{A[hmG$\]fT1ӧ(O#wfʗp7vɍ DPP 0`f`V` p8܀> W"Ҭ%JO)n<Գ MtcS1G0H.1K=f{>a#Ph^p#Y>l ҳ/"ZX) `mfh2 rtkdy) b oO/188wwB1BO{5/8 N18+ZAPŔ4mV+ue zqF'k7'k)'~vdAd!dQtAzƑAufT1 C !LO xM)A$2  -*:`1,p< | -_9SMd5D^ODBT}D+87芉.8I0H17KqpbPyp#/8ppu rv1Eq=k+YAX=-Οb߅ OCT1U! ^IAI Cۮ_'K6bYQy3^xꡙxidZܙY&rMsLo L|J|\B덻y _f^D6r*-ITdEfa˔fq&2m'w膎pEu1L'Gs%I'^(؛w0$7$l‡g \v{~mg`1m=oaY{D( rJjZz`FL , -'Ac].lĴSzJC)(e*Ô>F(Ce6b)s/e顬M=3LÔz(wS~7X#_ 7v=Fԇ1"j*Ϡe}5lBTعYxwR}Ĝ,a,יQOXfH7H --ᅧm^zAQ3s56=l4%q~郉KK;RNzڴ{ࡺa@?MH. }2QܜP{f w`a$cЎ=b2AyjR tHAގ2qp[u!t<;@r\2S{ DPP 0`f`V` p8܀>trX_pX"'2j+QwM/6ada 1c"8T 8CC K~ǣa/nhgث_3yKδWcFh% : 2^c2nuaC9Vӿ0Hw~yg bW;G-Ll\p$eKs;b\i RYAڔmA -Yط#ZѾkcW}~0ZS&m΋0'%_N2vnA -M~sK3 R , =0H 2<3B9GqA[0HE a7}YQO)4Q@@@@@ XN7x$_* ]!"x,(ʉhjGU}Sz M<lho nAs (aϮa{ɾXi;70Ȅ].k,LR9!D",dN0HR|+fh?, 'mC_~C>s̚LG7;w78qi 9miLHbmVmn|c n.}lK^AxYmъM^5Fsf!gMc}^_7*/8d2Uͥo - c50H0H0Hp$1d Wڳ]08 RYiI5 -Ȁ(h0 ;` 8</ʓGb[Azl"8pQAzfhGn'aL\p+w'3F͔:g 7Kܳ k估1=Tt)r҈{RȻ_TݣBBQ! -ya  }.N%B C[s[f8ڴg("Ja1 $6C{P#)sqof Mfi ]zڽDyɼNQWٱDNSkv6q.k I$005ndj;yEb\BA!C!]P¤p ]b)xB8URS DPP 0`f`V` p8܀>prite*BDUEԵ*,x.w26 -"lbi$]ɹ_JAr{;sn|+BBĄ@{ -])p -9v=*n|5D -q -Y :Rȱ8UWe-GUPȓUD'xTJ/ W ~A!cDl#i # -`Kj{ -J0}9N!HKp1-p\,*$1c+%a-ucSBCօ0 AnІ[ t͡ r{BmjCYpyCC,G_`p&&":륿;OQǼTvsrL266c:&98sZjFO+!B37]5ST^^6z(Q>ۤ,U'{FkؤVBy~x5X]q8h27Azk;ɹ.-Aai?.zn{MF>1! MϬ/,4Q@@@@@ XN7x$gF>8w DB3j_)iBLc 4MBx oc ܭj9g{qڭXG""EX"qMr!("Cd-vX)bys%k?8_~sD'VM19GAbV~(5ï ^A N>%d` ! A]'wJC+)$  -M(^eڷ]:f"]2 -a5! !6g%- 69aʰ''؜vBWg_߈UWjbzhs(L9əiF3)F50ͩD;=mkʿ[+O coe'++FAR}(^ĊÈ\SAĊz b!Ċ3͈=GߩWgA )9P%P5-=`&`V`vNx߈FH'N}(?9覉<_'!l'Q#\ qw\ĆnA! B0Pȝ($3ǣ+.zcׄ[+!V&Ċ -:* UoX&t诈iYIBPHe*~[2AeIIV|4ZVtf7?*B31iet_TLp6ERt;ɛ)_KQO=©A)ߢ:{+U8t`󊏈L닲UTEB!3;NF9Ήg~4{%=7O}}=ʛ ǣL1`@4 2  -*:q -l Xp4겇0%>jPĠ.mcL򈾌*$@[-?cTN\-$WvHW7?"(DO={/P|C\x+!rDH!a#PȖVceLO\MTKǣ;Ӽ,am Lh -)d标eyn۾ ҹ{켉 tC -?IzYm&y.N1z(EYcMN-کT]Tt.1a0Ծ3#T28RRR2;jchp)kK4!L9)d,)d|q+Tq0  PP Ѓ8`F`f`6`,pxArI':b!n mu-> !~+T6{:8 [O>mcQf~\#($QPHݶ"Pf㥦H !dN0Q'9IXF6kbbeF5&u:6wRucSis` Ga`^<0T@ixuL}j:2u.!d1Cv>5}=`|0  PP Ѓ8`F`f`6`,pxAeF´.oh'1~zQ4eQut,=DH ^Iv.b'^%lq*LK:"d`7Ƌ.15+> p5! - ?ww$?2?t1`3>Bs˸Më%L g$Z3 YIL-˶lȳ=O9A{dH ˆg9X7NQs׫ -?= hRu{S{`CIqudݎ=]'`>V}_vU>2H ;iYD R rJjZz L .7/HYx^(3s A")KŲb ۶([*H'!v`9_%zb]#|K Yy;v a0 aoYsvm+n9qYsyq B 6atryi;'u0Hf*&C cF~&H #3oq%HLee8}s9H TN\ s:5HiN޳St+Zj.q'UWؓ CߠXLAʋ` -.F9g{c_N@)xN -A* -`g D R rJjZz L .7/HG' ӽX}?%S|~)Q0ѴU 2L GH :? n>WZL. n ! r`B01Zy핞뾬٧. rZ0ȵ! H޷B>}~o1M.5IQo0a BXc_Vb9kOt%*}gAR52?by6kp#_:Ͻt{}elÞ?󋴞Aǿ. 1@@@@@@ X ؁8 \n^A"q)A-"IHZ-u"EHY/R5"MH*DQ+2VL"sZ'5u"G(bkE:W+r׊VG?Vx !aqca\]iޯ^B$%3lhN¸Vw (Ze_B>feL/iPh阱F΃Ɵfh0ܣ^f1tֺj4d&,%ޑa8m%ٙo%:΄xgRRŷ9:>#3ӽ?*ff8IR$鹼eCIIIʡU's"hunl(౯&elk+2"V 6h^ -?lr99xSN>jik -DK^n?kN10f ) 1@@@@@@ X ؁8 \n^"^ln!2]i5wߣ?Ie| -/]\:JL<į*"Jh Σ#tPaqאK$TxYDȡ r\g]+)dpe5 -U˯+d !RB^ܣPȌg)s>ŭhя9+27߂Ez;23}l,dⳡ~2Ą$c—@Uvi Yu9T-Utwvi3N2u,EFV{| 8lը?twvohWSŶm>c!:7E5ӪQU˔:z; ݝ]2}IBՂD R rJjZz L .7/H ,ؖq1a|i(ZQZ~eT✱.bCLuĜ/| {8f#Q^rB!ɨp+~BRdS2Ⱦ _v ' -хZ�ȟ~d1[Vf= ʨ/) 1@@@@@@ X ؁8 \n^60oԿBڈ(:r~wv )%F (Bx4c>*p|eN+E:l Y!$06\w)8(eݽ*D V! m w_S:<2&1M]2d,c;U7cixoA.2&#+; <ѷYX[" CI_ے'o~jwsaFdeNZt#A^MQY:~S ަ~5UWJ}`;3 atP^0G ArNETvX' 2Hmq-0HӂAbh$@ -d@@ T@ 4@ t@ ؀88iy D?0.sd -P<#hC(d~rBƘ]zBv -){et\r'G׷f \a[F!$;YK`Fb-)>a٬=/}iX'>s#S]z|'EZ^6RI޲S~WoR'dǟڢJQq5O tmT -kuQ($>!+}!d2!#o>,ABބBFhizgA!L1`@4 2  -*:q -l Xp慵QBZ/_tijQ5C|+8P==6^!!M~~yv*Q˗ ۣ0lXBVw rLneA0HP!9A=1! ׷AF8+ʘ={aW1 WYjRVFrBߒA7ߦ'%X|lfŞcYɉe!Ycs['>z4_L"3!TpWRGg49u ![RuO: >c0}~ 駳ƆZhStG30H5Vظk4 R.BHK GgA`|0  PP Ѓ8`F`f`6`,pxA7q^"iB( -'zo_<wg7=e޸4 > uB,A1 Y Q5{%.q ףF8 -(bTW!dߟwc^Blc6KuL> !u c8_7w{oꘇN#$,v)3ra=qxvX[c4M4R1M!NsvnOo_zoHRDd""RDH[JR"CX)bED}_p]ׯǮ^?|9s>˪^8ҫ9/IX%{ᅷ9鶛oՙzcu -[)[wrjR:c_i}C-[oJNyKZ4˲_W]15񅡿ͺcؘ߳X{BҸ?$.F8.dc-ͦzƍަwbN7Q׿O* Љr -` "A *P=&0 -6\8A/? RG]9L"VDeQMѴm+ѵ(11u!b9@VX[爣& {[ 7J "$."_H\"S[!G&+QsE,RįRɟkNJ֝V=+Պ2($,"*J;W,M_ѷ -^W -O+Z~˩?O ?)N}'~VekOfzߜYd~gҵ'~օ?OğgP7ϪV}kg( gU+nc[f]ew~qRQ+0/2O}̅~?0ә]-AIBLOuh':ؔTgrv S -RA_]ϴg ]tdYWlY"M~?^WT^d | -SŮKҨNuLj{p(B "_bX 7Q)M.Ya -e5\SN"}t8)!w-aBBF.=5!$'de&;dC&Pܺ ABKGB~0ĵ !#s~et)R?sq# 2pͥ%i1N!՝临8gRt -l 's''񋵇GW1{˷u0sD _ZuZY|hպ3[ՆniDc+X&޼ j֙-chs5>x5{l]~ǏPsy]'4oj'8[RR -` "A *P=&0 -6\8A/? rnv&eCGi)o7iL1s) M;w8pͤwr77 wx*K)%˰[ e S\2a`ZFbݓp)}"as0ÄaD_-n;0ð=lq2f0a8ߡs _)Y2aK fO}.?#*^/Nd #w1aĥ$%%';cS<V''axXpAw\)<|~^*2rMwG>:x߅ο_\n_,PW*}[>k>teG4ʛyKQQ޹ָwwo\;sh+vw4\냏|kmй=i~wdӾshQfظkV JP4F0,VxA! }1Z Ve6QYZ3Ek3.m&A4Ee4YжQڞIӴ+{  Q7E/ݒƵ:&1$`gL?ҢbS1NwjZqg')ΙvՍt{cg)w˔jSd+{Vs+5Y{WjW׎4ϊ*9)5xkk:[?t\8:|kfml9=懿F,Z-)޺W2u3z]`@DT hAz0L` Dl`7p^~@"JQI1 QO)+(U%n4-6S26RzAY(Քr5Pl+Jq5D k(_bKFF`t`\&r0 -c]``]sr0=i)X1*N*% L.qyhS.c)R0ňB16,` HB1zL`3oES.F1Ư}"vMS`lZ;6IR\;InEƤITObfl TXTDtd1ʢOݪꣳ1]8C+RƱr:eiTCڵ*[[OS;(Lm *yfg{vϽ8xOGVbkҾhЁCO҉MYOT JP4F0,VxA! }YZAjFQѪIZ=Bk6i}:1Ci68Ҷi^DS+S;p#4Kl-"[l֒3䙃 -3Ǫ0 yv Cʥf޸XA^> .y1% ]a -;y]zu3ݷ) -!5Qr;FAeb0c -k0(]̈3 X..5.ƙǠ1θ`\AR4Ν{rA&7t!~{[9fg_*"#EeTnZu[f@fM%ӟ=W`NtPQVkA[}({k[7){hvn]?KsƬ#0wNZkMCO&}NV_(|M0AZЁ `Q`.`  DW%*´Hi$jj'F$;@PHG̝R)Zwq;L*"o3GVAn rA RP2 -ƻr d餂ܱ 2#_e¬ZR|j 0C<|"]]F Z`E (}do -2sq1$Xv$:5SqXO*V-n6c ;RbcN; mi X*E?l<*bmtC仼jSzãNY~K)e8vihCA*=DG& DMTmD]J4DNtED_K =8LL\M,M' -EB k%|ZzdY B8\J - -S(%JR3H=v r\/ Uu 6LA^FA(? -r* r{s%W{݅uRA1nq_p*. -r_S4bR_I< -&ĥ$!.N)c8NQ}>Sc3;^MGUUhP75oj_dg Ŕq&tE.hLEXZd6P};)Dիki彧iwqf5̾H7bwdg fW}7ЁֱIJ`HP -Ԡ-@0 `( x "HE%a+DYJTDG4hmѕ}51[!)D?sn. rIHLlPHKuo99.&t&9cK+B:rW+L_V󜨬:isQk޴V_hhut\n~5<ؑ`)2fmh_vy'RQ˘=;^)jw~-utҾh~:P~::5@DT hAz0L` Dl`7p^~@"rfҥ!$(*#4D苈a6S1K4$m/%L*$l5qO\)'hxh1rKdBM-1!N9!arr1',~!o;\|,_1)ȕavu|7|bW w ބ LM(k׉ҧB\懿x>Y{{ᙳ1,#,V0Ab )iθ`2v<):6 -Rjš̡dIB ֪~qLfm^(0 -yJSپֽ 6o&PkBA i~IJhBAf %{hE -Q;@'>lHP -Ԡ-@0 `( x "H ! -BzhC˘Ѕjb&.b% uVF-QL\Y&nµ}{ WXA"\Ɯ#/cq8tf:Y3BKN$.S!&)ՓFz҅UؘyTD}6:rȣjר=z5{ CGn)eigNtQQַ^ckj3]cX]O{iwKv|o(:H E7yѾf:~ 8 ! -` "A *P=&0 -6\8A/? G -JԙDBDWIp,4T}|H{Uhkiape fJ"/9Z [Zz\(e0+RMTY rAa -K2 b-g(?XTM/ wa 3/ҧX\U̹4 ->sV1`LvƺӜ' Ѭ39-.-U=1'a/$}jEv&y cӘA26cy7^u s例4_kumk\dh sCC ѡKv ˭m b6Mbk Rt3b:_ݴs?fØA&0twf HP -Ԡ-@0 `( xP "H4E7aO\Jhv}%14c#1 sKMB셡1- ]AAK.VſL \,y!d2 -2A^lnIٰdA -傜 ya/x -,\yBA~x+ -2v+V1 )V1Q6dL{TOqrnw -$grSRO)CiGofgW)1e0ёM=xkCz4m{59wQX K>sK>L=fu'Nٷ f0l 2Hsc|i?fnvmgi߱, 1Vc| " 5h@ :Ѓ`3X -`;8,>CA*5DQ@j uDYITD]H4D;K򉾂HPK3A*Hm J[H4OХj"_ BASurAΓ 09wLj XbCH7.~ 'de''1-c<ې/$$]>16,cN{0L3&y"c>c&ytsn'Lr'N7*$qp. 9GW+33aiUkB7ukCB4 &%XFUvYA[A !cBa)ݽC !yB0>!XFo]84I&RCH !_T JP4F0,VxA! }D1DХTrhF)#zq:CvI 'b"A!r BA|7!d]-'f~nbYFB3_s? -kF˘O.SB.};BP`QuHB(Ȧ|҃\+ !) -B-2ŀ!dS!Mqɥ`t -4qAOr,䂔?s Vtn^TJO鿊4dc/ǫ&YQ3&jKzuGMDAf'4K0w S/X#A[[͘>UzJ?jEA&EA+P'Q6zAA~?du M/ /HP -Ԡ-@0 `( x "H,?4Q LTEAVG~d}әmlLXH|91lZ2O.HAUYSW]E[kD3TdeqCռK}ܩiߡ-ʝ޿.6Mǘc,oUk|VD'[kgb'{(w(nl/yJoY׆?_:PVNx:+oHP -Ԡ-@0 `( x "H ӊqi~ZuVњ>Z{֍Lb8L3 8mtӶa>J;hW NҽQ1;K}||Or>aC>.О杘hK-r>^d9r>4aq2q0WA8,+0@گ@>:DS/L㻠Hs'Ɲť`*qrU\LJ3):99K ^n2Z1%Lo11 *ī -5gEڲuwDpt/+L%w'Xƥ{GߵZ󆃶70dJrՌ[w~B yMRٯa~H#@J1Vci<ъQ+zm-b("b*# -bi05DPVKhWUh7H Er-$bm%_[pHc1rABAV)(K=Y7Q.Ț0Ky a;1w. @t1#]@* -r(syyQǸ'KɽI9c -¦9Mri$'qlRS$&%Gܧ6 򺡌5t0_JKP|g_5ոv-&^Yٷr6i_ꞃpxsMy/@'-tb?_GT JP4F0,VxA!vE#a: :DSDNDI0H͡ R~bS{S A؝}p?Nn"b uc1rAa -)$YA0Q. ?T=hu_(WKwW r #edūa5K#+0<0$: yet6Q3ȥ~&߹(MCg&qɂ_H=3sNV:! ƺOA6lWzՊgrwb#/*ƫ]#j:o2;D}OQƚ#I0&Xf^ 25e~m}`e-15pI7o7N3Ԧa!%muIr{wӾiE~:0t3k*%@ Ђ`# +p n DJQd RBTD]L4DMt%D@ ĘMLD:B*H&iGj qUvk豘§6t/7ZN2j -b .$"LAV- c^ܓ -r3װԎm9: ˨wjA(H -2>YAKV1bO1EcoDf 5gΞdwt;əa\rjS8`*NMrzђ {BVt~igJBA`3%*Uvt -ɟ ]*n -sBi3l65UbZGA?%U8-UUL[V1G6zK`s`N~㫘br*&br*&br*&br*&br*&br*&br*&br*&brB('LT'%C㒷vL*t%J8*BA򉻔p}o 'b6-V,G/#u<()nQ&XF~I mɄ]~,Br2#,6qLHޭHHː2f/Hȥψҧ>.u2p,cRRR1tPl3dS|ybNy6wyCF=2&Պ33m҃uX=&*\.'j -?7KDCt>HW&[G,Y2fuӠ~,c2]:,cj7c61B28B u}XƔ6cSt>H1E9XƔ=y|" 5h@ :Ѓ`3X -`;8,>CgOㄙED5C}DSЎ!! -1L1IC +{Y - &CAzBAjo&Cȏ$sGM틞0'%1q %/c~z떱rˢY_ \Dr[c+D1JQ?1tas)H\Нx11Ť81AΙpIAS '?Okx!X̓cً!dCHSG9pW+Xьnhs-kY B>Լg.xg]g=܂!TꖶJڅ!t#،!A ![6x3R! CH\3l{R*HP -Ԡ-@0 `( x "HH H ]Jm#mI0TKj mI9)_ac1͘(]_S,#]J}K.0/02f*K;0\^Ҁ(9T)J =Z?sc\g w)Hjl\LL LKvyqɱrSNtmdfL_*E36 -R9%0TQfQUeQ7 y4m缮1s~MaBA{|枽>K[(HV:kr2 -;ԏ ͘gQ1_-ziGAP",cZDAt -` "A *P=&0 -6\8A/? M]4/dVӪ\ RJt !t>H1OǗ1sm 5DK\:h%b ѧUQ)a( w3&m=ܯ~b(~7\ ^NK>ۼ!dU"Y؅!ԁXFV`P>2T?t," Ϝw6$ccbXgJttd `q -)N;eiZ7bi1CHܦz:rȣUר=l6o  !CsRyxB"!Ϭft\c~ ڄK xO.SU#!#UHHϱM>)!#!yǐ֣" 5h@ :Ѓ`3X -`;8,>C!>t%$#(sCOк&#b'"b$/Z"V,q{ʂrB4a" yZꐲ/#!Kξ1)і K8j\g催 -S+1$wcB߲\(OgscIcy J>1Σ۱Qsw$2kYיr`jR,LT'9<3͓fc8O;% df -l EdYSSʬnJUQFP}6w}}[ƌiS嗔,[?TDdP֜h[~U=gێr>-:]<|Wɗ%oYYbeYꮳG -dϜ)2" 5h@ :Ѓ`3X -`;8,>C!?nD+JJViTZNW4FIiVZTȃaW;.t3 -^))B"|K.S"R,\Ѷ"CP-aHN_<(0Ul] GWrR.BrMxXg MX#7?[hBz ߗx& uMnE=]uU')h…~G"|1*eϥҧ>f?;ylS"&8w\bbTN` ǧ 1ѩ4h'Bwh۶V ޻ɓ~EG4xUky/zQ{+^7ibF4LNrSsK'Ҩfu`Ж'esrUIjCs{hw=4w |1I [7zǻvbx'-C1UTHL_=~_S D@$(AjЀtf@Xvp Xp<|$B .ѧ#z tC!^hTR"ҥ< ݡm$Njb"lbn>qX>MđC\m$FUy=װXAY Q)H\u"LAC~Av<򝯻iRzRB~dB֭^jarRov a~w(0e BBQ`l#!mO02~"$䟩X|3 $CHtuM8$.9%&%%-%;58tXB -W+LٛP9;0twyQ3*jUܿV?sE1Tא`>ڐ`=Uen31&?Hh6v}CsU |~7z?I}oBBtmӁ7CFk*%@ Ђ`# +p n D>:}LJH'Qv#~'^"ݡm{˨ʅ '?JATHWNoɑ0 ɓ"!ae !_''F[vRAn^ Ca -#_. !qq4Ebֿ;!; 0},0Q{P;DS%g~PqgIܘ?KQ[nnQQ/ -brxwdO*l:15Q*mMC{h^,[|+PFoy.Z}axTl<~_S D@$(AjЀtf@Xvp Xp<|$U:b1cա>B%$+&b mMmMTOl))u[B{:ڈUK<ܢ\r`e'rB"e|Pa^kt,zFЎo{|6Z^g?)lX͕;1s,㼢eaR[ [jP_dҧ>B V-tg'2& iN.$$ON9 #kk<[/Y(*LQy5?JӪRG+T:y!B?Ɗ{ϫͣ jKuyQce-^ek,p;?:?\-\v^>pTU|&pӵݗ5]?+'/" 5h@ :Ѓ`3X -`;8,>CA*7+anB,~[nm&eJ1XC_]ew[zuݾ"IN;WKᕟV|l6L.[ƕuS5{qO. ӄrᅫ ӄ&SūauHy7 SźϥuhBS}:AQ8s/3%D*TƲ`'ٙuH0(ʝ{ԴSPK+y=&Uؘ<*mtd3e *Ƞhym.g ?41dR⽿0UwYwQQuؚ祋vQݔ+f[iw4ו -OfާP/"] G@NѮ@DT hAz0L` Dl`7p^~@"H_wQ6e]|i - C4Bv4K퉩"s#[Eܝ+#|CMr%&9EAFAZ: -&3&`3.::t'G|,wɃ#}&KJ]_KJvb]RCTWШn|f=όODCAK30MK0g%XKw[M  a]R_zK=%s/c]R%[zinC&)!w`7]zdCKVIt`]R,%;_k*%@ Ђ`# +p n đg6 a椂5/ ݐm$^$b"rbj6T.b!q ¶Hf"4owQ(0}?/m|/LA(H +*)D \. pHCA~J[kޚ< -MKa-~Od־yݙ -ؔ6% Hgwxh> SSNl03zC˖ՊL~# -25tԋʪ7P9Q j;uӛ+ 3a^n:`JJ`(ʲZ zQc ս((H>ݱK: -2,dlj7}K'-<5KIݿm:HP -Ԡ-@0 `( x "H,hX80'ٹpvig?@ XGLE,RAm34 -zd6C-'buC_5OS rN|oY_QXU催 S2 -2Ȗ7 -eꡅ+L u -̺?<(=RU̳ҫ(7\g`yGA^69W6Im $9`RbcS)?alWhxO/܁}l=^5Qu5sF碡-d+L{yAz1DejaɑfAgQ&  (HagFo[A>N?DAP (?A D@$(AjЀtf@Xvp Xp<|$?8]%LT$ @-c;z6 H9zv3q4Wa'+}_؎V"K7)H\ ?6ug-67Z <*BaY5ᇐ -$e_ٽ{tq%0VU=DmWv. C/LCHy+4gtlLcSRt5$5&6ƞcs7v}?.ՊB"{RQ97^52iTDmIѰ $'_H85Z]zC6 *ׅ-lkhLze׻{?I !y ]u:0~:d- -` "A *P=&0 -6\8A/? N+m'aDYFTU@mUN$Jb;75RJHap/'BvfwQ.+:t,9,g͖E/"_(WC;rA>S;䂼-!ݗ{O@Ȗu.aQ |WzT漃7 ҧBFQ093YĻҜaNOEPܞ8L!B*|#;lH[:;)*"go:RJđխԽRJ]tC_d=4ף7- QGϱtS{vUW&fogKfowLG;3qWbY.6)%@ Ђ`# +p n DRRLRtP"JC)}3eh2PiKGek͔rQN]EqJ M-M-_'|]``$~``gqfŊ''Oa1,hM`?Q[(ƞ0Ř>buob|35.~e^/6{Xjlwҧ9f,[&F|i(Ys̹̦yRl:8OSzin&%BȭPїZ>kgIJ ˖C+^W1&EڹxcF}f+Le 槰lofN`|3GY,;X KNl؅eKSRB[ޡI,[u`aoeb/-C3Ǘ- -` "A *P=&0 -6\8A/? @]-DY:+hڈv qĖ,[} z¶w+ZCte'{噃IȘKݿ}z Y[:ic³,o9a -b 2-eܾM^ȖW0s48*.Im(HU`CAO}̅T[ߟȬ9Ic6q:83)&% SR٠UKTᏞncchQ/Ly>svR֖GEծVpx;er\OLy=.Hs|!Jo;>z<(ݾoЉǪO>" 5h@ :Ѓ`3X -`;8,>CA*eUK扃~ZB)#2%눱JX[%W!lq7.%DH':"6/q^)yK> m +0a -eDMhWX0|0 p ٟ[lBBrmϰlyR|O/>0󦑐w1 yw\ZlrЙ,bA!5֙ǥ !M8!kB;@Zfc#!-He<ʼwuMS^A_rcȓ^Sk>sk>KHHu֬klYM}z ^P iMNsGK^C)! 2$dTzB3|ߋtc$dfxB5@DT hAz0L` Dl`7p^~@bz%'l& -=WOt FBm!ZbBʈvv(څБǹ:4=9!0 8V,uV||ݡ+HMˠKzvǒn\ƅc]ܚb_z+ˬ-,cO <%0븟 [ESA2$y>1n,9s<8O3-9Ns˥qa^4k+<>-4Mc6C6fc$%pI.1 nvkqS]{,"8"0A, )"2eAR "}/w;!v?f{9}yh3Z2fu)92fi';S4(cj/-4zm˜˥';g8 agg<[kd7aGvAbyWl\#I=dAGbl<2˕ e/!k\!$q"ֳHzI2[予14/$9AD,cVAj<.3BYU]<qT}!<2Rdo 1_~Iɽ w *愗ӘXsb= ~ιMLqq LW\.GZBf23CE} M_Ud)ZGa^A*Ai]?$*AV߅AzKX ~rIs0H $w Lp -D9P%P5-=0#03+;px@~ lK(OJV=,M6=2̗hBHGrPHPHsO1\{GT6^&;7F;;t -i~)Ra.hmPSPHPHy=R -YAAomC]B -J{i(u]!Lp -D9P%P5-=0#03+;px@~ u~}{0a71 -Y| -J4}WäfvemĞ: ?B\'0Slq"6rS#޽ ={]2&ܿA^^r.ҐB bsmd9;`~2<{ścF_~y\97uʘ#_9C>33\,:#==ё.$\RMtS٩\0HC}Ld()(EUOnib+z?ϰ:b|#< RŒND˘> 4>59}d?BHa!Ey0jӫS#d=`) >@@ T@ 4@ t@ L , -l. /@2HC>F76vy'QeQJ!6"b$j>b . 3([GĖ] a rC("&mQv!+Aȫ?׹L(\ Sa 6s+|]g`a̓AH˓W# _G_38%A; A^L>!2;DG ed&fx ~e)#=9_r~?Ԥ,=wEQUۣ^Zj.ܣ_L4~Ls~\XLM?}Lf>vD\yiZw=?-sսw^^=3JL(5:%Wߕ%/=7/Kood@4PP 00 '  D Ra63qF('U5f4vU3A{S=RXG[cceܣgo+#2&Ajɦ>F<) CFm#y%>%]Ž/> ~.LAH}HÑ3elraZ3zǦ>bPCsFc(_@ 3%tr7<9G DޑFJDpQ㇬LWVYY(;nl\9j1J7DyOQ^ٓkk>Mmv6$]-ajgP俖b_5םXQ0U.)0=(ajVP  \jq;ׇM0˅ 锱2vgQ ȁ( -h X ؁"H %aW 7K4>r爦決q)c.k(amاsH%>iNAF8LW~IniKo&FnP GC)Al 9J!ګt @a2a )d!)e -)|(| -PcgQ?/stiC֛;'`|#ѓ) r#t%fiYR[~a2h7;^.*$ -ig}Ur$Tni&rb&&Kv<( -Xס^( ->Bj?Bi;HI724twt - ӏ 'r(e]!Lp -D9P%P5-=0#03+;px@~ lto<Ɲʆrzhf膤qb'q~PqP_đBwo ”1 jdf"}YwBnid;"g+oA$wAnG]9bAO0=+9|B2,wlf\e eOZ2cU]O˘V%Q0STϋNQ3T<AYAJO3tkSL-٠5 p{-d dÿ/뺑Aߘ E>È%2WA# Rxq2,@rJjZz`F`f`V`vQO#%>MvvPbXo;bl8b*_5/ s0Ha670ls)c d̓ށ .0 Y7>@@ T@ 4@ t@ L , -l. /@2H!]ƭgaf#˺Nia4RNn42Jc X$qwH{ƈĿpm71{BU̧$&dc On#ܮG[N y9b7'C.A}ӏ򨓳Ģl(Уԗ - ULRHsӛ Fwγ`ZzfVb ":Y DHOHtemnQ큾K Q t(YI()ZJcǓT=`ާ8mY.< 2>pq)޵X -n9D R5>A -O\ ֿAj:a7`>dA,@rJjZz`F`f`V`vedK@҇NHp -D9P%P5-=0#03+;px@~ Hgven #bQM2~F3h;"f q1I:{a5͸# 1qFd5 51b ø+vx-k $Ү,?F s_Hl@ZV6OAMY~ {#_%{dNާ];ՕOLugde!3 LW =+Q|Gݠ^׶-^GdQ/ 4,RQeIHRWt^*Ous}gSM)#2б[zZg@i?j6H Q)$B$ AbC1nWzVe U71 A -$)vVe/| s0xrDAF a. [}; i|V\'Js4:=6ޑ{./TXs`}5r7ې*E彈 ()&Q%&^#>MMNvO˭%d *Cobnx*]WxeU䩐Aa Pd:L62H0ޣAFZnf'p3δ`DsbL G\\q ro)3ra=qxv̽\[O';"˲cҐe,MbNc l_v{w@n׭usw3"EYE 1DDDRJ)EDXi~}<~x<2O5z>CQ5/Ly57EQQQmDוWnw{oÒ[[;|W<#3=[,I뼖onc[e{ fY[?rGf϶mD[bBl$ƞ3Q6g/ﲰI,DKLO [rF|;K lk9eҨXqMtYq(d?seN0"Sl.w|S (sTY'9ufTRQ|ӽWrG|ƪ.{Dd.{M5mo)AU|bFLرv7ǿ3P3Hyꇨ* -/D R rJjZz L xUzHtTL uDKD5Nyb ϒ(C/1N1(v!҇S?aauk}?>SȴKN)9]sjY˗?,#|Xa‡/ĊV|$dQmM3c6z\գrI7E+O==oҩ7њE$-6_Rőa X~KzRBBfb-I+$Px=R} -$)ҁ879Ex8NUwoRiwg{ -i| -iB!A!=SC!آߺ -)d>} -} -z -?zF!xM1A4 2  -*: -l,pxA_[=+H戴F(FrLm?}˒B& -"b&v%(a{kp}=|a - -I\"(QB?.\!7JVUHk%L]sAAލ]6"An -v #/ s bAN /_, 3 ,mhb - ]=-?K  [F:aq#3oIf, yL?+D>E$o!E2w4EZ8')KQz$NAF`#ClhGyi~ 9Q^AZ r RX ,V@)!dQiCȫg D R rJjZz L xUЃAJdH[({hyc%1  G=ajO\"ޕ Bj8 TH0HB _YC)/܂B^-Mn졅f1+?}Dk׮)rwi<99/R6*?F5ŀ HȀ(h00+;p8ir".'t1J"YDBDQJmDUGEDSODKeBrX0ń"BvzUl-+DA! -+(A!]1ԝ+,nUt,A )e\2FJ`~x=0H yfa9W^WHrW LFӞ vI8d$Lߖ8 s y4lOC0H0H'$8#]عQ6S^:(anTr']>5A -zafcu7, 2)A*ay^>+ =kAv Ga3k A )9P%P5-=`&`V`v ` p < *!"n#t3oR"i%6"$feQu9єm+U}ozb,!6b b \h L/apM]ADOB8AAlx 9UC }%? wjAD0ȷ ``[R#d S(c~Z6/`Xvh$AzX9 ucGFRr[ 1Ld? &tAzKNueLS⼬M(crPƄ8IX8٩"?E++N5S@SFmǁTT}!~4`(yg,=2&؉2eLk-ʘQvrxeL׾iW+(crP ~e̱P<{k A )9P%P5-=`&`V`v ` p < *DƗd|uCMt}# R9̗ )8[;l?NUfG0Hum0 2~x`h>3 =0, ! 2 2: U̖#!x>AN g D R rJjZz L xpAE RA$fLE QUcx)A~o:b Tk {U u$Dܭ+KPC.d` 7 \;BRoW !EP}j!_ZB?e ?PY!_φBްB!1?sS1n)oI.KnqdĀ%!w &D6~֙$fd旮=c1/++Y+x+XܡXeK_Q.ĪO=|frmÌFP}ge46`gb~m>x}O9/؅v׉2W0'rWȊYw|WKew<?\;<+ [Is_h2A~\u -Bx BV@_!}Ҧzm 6]u<3KDsS{cu~\?k6'OL̴dYD$,Ivgbf}&rQ&ғMobʚ_!ͻG!+} -y֋W(N -enBU2{m.F{zܬlfNg_ml>-25O>$2Ml"{Gc"G[Dl7f><.zf<>{[myQZYDXxG1A4 2  -*: -l,pxA_Rt9}TSJDZ(yh픪RWPVJ[B*)}-}PTK)k eUbJ(rR\3.VЇxam}\-D=Q5^}m8[e8K1A~AD} G}xא< rb-_ћw/'6H4!y\䁧 ǣ-^y)_ }KHLBٙeA|D/qhytkyG~Q8/g=/Ux0Ex(E'y}hMQ/jT͜zu/Eܨ>??8`0:3ο⥂_8Ve (^:-|\-PMJQE2Qe4txk A )9P%P5-=`&`V`v ` p <:ۚrf =/GK Q,Q $ _  1us$GaNv7B  Z :~|vB@B/H/$6BbjPF]@nz(b/rdO 8yv c0z -` -/.U ]Gc -|=:ӓ|/ZYx2V<].z{Q$}Rs0HiNLJQN}QnVgA&޻IۛM s; qٵ) })3 2dk itV  /1^S h bH ȁ( -hA<0#03` \n^zQKP'H;m DEM! ?:7H71S1k7{5qd6 -`W2rUY0Ht\*ۂA 5ꌋW3-궕 Dm\m!d_Wk$} $;r +黷 e\}#0>pїmnj襽 gS$'$&{ra_ng$$'It 2VbI#z9 3SR8HղlV~>"gQV?O4#CӎuyQ䓗B7ZD}"7R#T -4?pDĖ?L#=@F/RޒmTZ;gb@h$@ -d@@ T@ 4@ t@ ؀8Xp3D\K& DRm"j%Qu#Tmw7Hkۈغ - lqUnr\ykVT]h\ ׭H5l/˩ `n!l]DW`"0ȝ Y^6H$ 28K{ٵU_=j rgقIp&>->;d22mgdtkqI>tNJwmۊAE 'iO='䵅beo~jLlQ;z U7 r? Rro$3H O"AfA R ܷuMd9>< o$kLk A )9P%P5-=`&`V`v ` p < fd""n"t%o*"%&"+oe Q53H;ц軖  "p)WS8]A "`a   < ?_ ? ŬA `/ ܾqjam;l˨OPlOݴ%_她]0ȕ8$T1dÑ`aZILN̴gs@[/ dR'=zE5T̰E`dEgS]:fQO#tWYY(>KkqvDdjFE26*(GS{XĆ~j3zrw꥓~pIA9fg_RF%)39p2%=ɞnqmD3D8} *0҃+jb8%iZ6S^TdUNh1Cܺn (2FrPdrhA6"3 EF/2&([+؅"#E,~{EF YGy^"#fdHGfbxS(DxnG'zX8S#& ?"—"Dp ƵtiwZ+Ea A!"L(2 -Pd|g:6?_*1 Lk^nJQW_Do[?33`3UhIt'ɞs>XuBgya|Dv-8)5INu})ʹI*gSݨ="?U["cmahg<~YyS(2F)GnS"vfjEFGPdES(2r:b@h$@ -d@@ T@ 4@ t@ ؀8Xp4l%⺥!"% -"k$ah!V:AԕDOD7HͼAډXG}^Jy U ]  b  XA 2Z 'K BƪUXA,I&Hgvp|NQ4}0N5pfu}O4Ko;N 3?Wd4ý}8 rzS -=Mq=QS0HCS7g AB0H !$` AB0H !$` AB0H !$` AB0H !$` ABa4q[x IKlax(ý-DSKuDJݼAJ.)ZHVM싔0-% ;I0HL &a gO: W5H`#k  yv  {ݽ)ނAuhA^-滽,m0 r㟺ibvA$ _78X3A_b °I d[9d׳[gyo;郟 vsc)d6ˇ_ (S iz2H7hKJu ^a(g o$xY8 2׼ #0Hy~tX 27 2.2H}g D R rJjZz L x/:K|lXZ.A^ J:X[#0 Oa -lթD6Ǻt3\ ~wzM+Z]CFsB*2><V- qjd6o_?6K#nʴ23 W~AZ0^x(V\=T ٜd,E5 (SUvfu72Z2`n`~r7 m0u0H6$>X˷N 50Hױc0H{|.yF|i ^S h bH ȁ( -hA<0#03` \n^W)#jBi$pW%Wl b r Z nh$*b$6(%L5a$}a+w -qE0ȕA.]uյLΕ}^U!;!B6A!_wlE1B?B,m#(d -S}.!i~׿~:}ɎdKRRbődGI9,lF:8gr[wRS+=/mR9IHwNwq\NQuca(d_ RC!Wy->R߯w -ik_[:|  PP Ѓx`F`f`6` !ӼBF]'l4<:l(j8<4K"^^!8JL?6E OpW#{ - -Qzc -[_:OaG(c6 xCZA}L䊴e<ʟ[QK3?86w!~!JjkIItKƗ1~K2Ről|­e;xĊO*{LM?'9ɋ9E] :ͩ_AJ?A۶/U׹7U_oƄPC0Ha3?:G U!-'F)*dYAn}@lS`,s5lQ=QǪOi^N C>/2 ̽'V6R'){qO38vbڳء[]{)n~{F:؊2J>D R rJjZz L xo]$'<)yQe-Q"9JHi[titX)1 uWx;5Ȕ/ЮRJ9q' -N8ϺDn _g]rpgC Z /ok~}6l⇁ 8!y/*nx:%U她; -C맏<9##$YX?3#= d8d{ HOH:'U 7˟3ңH*tJzO3cl?ʇ ;{6êO}T{ږWݺWƗ**柽٘3TQ{bb%g* U4}TREԯ\3H]TTU,PY\b.%EVJ%kr@ZË+ -}REfTq* BA!5ʿn;q4~Y8vITRŷT1!U(װSn+m/u U*j/f埸h?u֤xh^ bc4ݖn2GFÂda$l2٠nZUn-O͙KE}NFt6'˹g<)(>ը? 5"M{ډT[h|pjg N}>"2~Sdl>Oz)G;G(fC׷õXMq (wg4:9ŷik A )9P%P5-=`&`V`v ` p < *D@2 DGDND1DDUE4Z~`i"GNIX[m;c fJ\5& 'xuI0HڪakhO%d/T̮>C0HΪ k0C"iқ3 -uJ r] _ j~񬑣M74.iyhmzz6tfL%8~ dL_ 9ɰ9*L>OKFz2ȱ^dN⻌t񾍲SNgbfQv-UXlNH|:>ϭ? 2Zvśe# -2H^'2HUdId?B1կ {dAj>EC9s&5ŀ HȀ(h00+;p8i`i6)wWB᫝:$1 ED[1Ԩ@LAb&b%&$L a m]A.] A 1Al` ˫b}łA r`^5llK,|֥&1KC-? Gb(AG9)2H4 rK_u!dg|΀M|ȴ-|gşt#6nn _0Pxr&1dS TAN29"ΏMnMQ?jݬ= 2 R5+9D8h03VwLuC !tul==lN=2HS2HW^rg"/"L]k A )9P%P5-=`&`V`v ` p < 쉥rD2C'<'G,9ڈf~y*Xo3&b{Dx*Q!DbT1b5a6ӃAEx$I|쮬$vg_ tD]_z-G76:nVhvUnU~l4;b'DcYNl'EOm}QĖ?|rQĕ=|Its7Қ&  PP Ѓx`F`f`6` ,%T~3%iSAJYG((iҏcI)c?ẽuR^O1B:(rϬt^_>}$R+oFЇb 1ND|8B[GTW5L 's?Q8߸9i?E Ƃv˼cOUq ?1!`q3-XcgFFfR?!\ZYxOIx}=7#sƆiŸF6ɋ)jRG5ӓ0ХFT]T}S!6h0L דكOC~ O SOpA1H}-H+Km*41@@@@@@ X ؁0.7/H >|-K5s&tiUyQL@ChN -.w%b %qV v׻J *ȼ"(dw\A!d -qMy@|kYjV12pD0mk0-B,0g,*H -iE+pS侻=4B'oFTLgr#bg먉tYXg='« Om*t5=щgln~Mr+N\LjaջDiun -D^ni8_O[q<*Z#e+hS5SW(䨈mk*(󍔧#EvVƙU  PP Ѓx`F`f`6` @)~i'&Hg(Y E!Q.RE_%Z+X VM !|Хr0|a,U !<*'`h U?4ǬjJ9Uˊ^AT4~(w-UNunijj{ݺ> RWn6f ;=c0H{0Hq ESz#"6`+AʟAA0HhKTډO)41@@@@@@ X ؁0.7/HTq2HʈtWE)QvUQMV],bLe2cO)Bu'J-hdsEOwF8éIZ.4Fa0'PŴ9"$vꯨbQŌULû;\ڹr<*{?mhtif  PP Ѓx`F`f`6` a -"n$t-AUL+y-QUu єm1}o&bl&!bn'bk&h_O[\rq.UQp|VYê) P /` rwԿoCi\6H67>*F}?E}Z/62XL ߉ɴDK/#)g9 9[sj> 2+.D?"bFvsRhd opNq kAzZoֽ%^1驄AjE֞Oz([le8H9F)f贈]ܿÕ 2D7n9Cyf_N;f D R rJjZz L xU\ږ RMD>Hp7H. KyTc15sS0ܥ6H{x'R.RUAzkYIz|7+d/4ĪCǮk0UL< r? rM63o-; )o) r6anT1͍drOF6 'i)iS"ܠ=:*P1 q2H e b_E2S*蓨b=!b_ -| $WP̷#bb@h$@ -d@@ T@ 4@ t@ ؀8Xp4JC]H$f" Quu],x01[w„"fw󅝔J:A~''A ?} rJ} Wj ]Ad2Hp /cay0Pż޻l4 RhA8) rKNYAĨb>~ d&'H-,Z2{Ovdk{ܢXq?:v }Hsd%pT;7ky4ޤmI;A: 3Kw-T#*aIT1,]bvpt 0H}0Ȯ'Ae0ȩ3k A )9P%P5-=`&`V`v ` p < *SDOA }DLD6i(j2Ov4l%1b ,»[&[kp}A. rSL -{ձ7 ^Z=B)BMaۍrBc -FXoڧɧ_秎=x22yi⯙]G7$; $Ob,ɎId&sI\] {|E\r -?ٳߚ9fELG=j]-y(r<ȳp;OEi} {b@h$@ -d@@ T@ 4@ t@ ؀8Xp4 %J~'PNJJ(0TݔtPZJG8J)s?emlrtPL/P%WZ UCkFyXB5J"E\{A߿u~+mOaxic㟺iWyhzi7d^I 䰤'f>,lB&kq}NazDTqx 0"dR1$UOHid%Stԧ( 478u?zFm۩wR{ƞ[9EH '{)#s5LQpbvU(b3;\-7th.[@yuTZ^úGKdeDRc+wLvUQMMx/iiha1VWADx'8gbp-wa hO`d v4 y_=:8*BnBs E&B~'$?W9Jo_yҿ@ hwC1OQČ{xK_ (dkYHu;L|@B3ҝL:G*7*`(*f}FZ61W;Ge'jS}L{>[?}ѝ-"ĩyM4R͔W("_Z)o(՝yF›B)I=VYGl!.'J"z/L%BuZFlUeʰBp]sa łBT{sՖԵ\SL] /3mX6.An R!D8tG6H;Bx f r Rq'r=?E \y{i =b $ {XXFq063eXq'Ə=}Lk'g8SHQ6}Q~̩}p/Uw?US/m037Hz2S{BJP KN]GR\?tx7BHC 孭_Zw7H Ǟ%6" ID9^H=F4DIt!oXZ{Aዮ{0Cm#&{B2:A ) ʵ4bP_  /D 5:AA̎m9ep -d"K (c~z5 64:λ~ʘ G/!3t[Bi~as4?uXqӼ~xyb${) -_KQf}Qu=ǩǞ~|)KՏGF Q18AsO 90H%oَi]$( 5wam=ܘŘ4f)1$!p1;7mv ]w?F[lE RDbEDD(F"R"`{!]?4|?>Bazue})A` -A -25h@ :Ѓb&0l\<4>%(n  @}D6M3Dq(;~&NbhCN,ĶGZ8 &H ᦉk/2DAtaJi׬j\dC.b]&BJБTu -jwx,|cufO -!A1& -] u^]̐p r+g!$;;#=31_2kMPgdd3 6ssM%t17Hvjb -9.i)[D_LRԽp -4O4Ebj߮#܆:ZLkbC3.f]LŐp-ftp]xwbf^kW vC3|]Lѥ.)  9(@ *P= `3X,N<^H_bd05^!*q3(;J<NmⵘsʉI|Z܊ .qiX/sz(A0 |7wɹKza ~t$]+Hs5ȗH=, sh *xW an{i$HWL$223ӭ>GBՑY96=%I/H`GVI}ANHafZyeTſ%H ^n -e =e -^A< jC261LWLge'$ۭ>.nu'9IՑs:22lƅbr?%^G R`bd- Nich<3[p*?rQ7sB vz܆d06^SV }Av -5H A {E 9 ]P\]j MQVR>\A` -A -25h@ :Ѓb&0l\<HF %$NY]x}.fD|I{:]/71qa;qϿtt" I %HCQIt1{$rHڸ\܋B 'y mo8qvt@2;IqyrU7놅 Pr!խK !L 0 AJP4`6`p. B'sڻZ<@-3MDPVAzb"_jmu≲6q)K|OmEȗ#FWB&BRC*B$a"$fEH3}T,;6c"I{ Lq^sY!u01A<¯hcì2ރ6KO^9m z$_EHkrىV֙d N殛BjoԚ (BM$7+MRmLRMoQ4c%(Bu{݆> ]c@)KpC!=D칏B(Lj/zak -En!zE,-ǖ|h %@ Ђ`X0 ``8x! +:DGsX6Y wE8gR*gMI vb ab'i'wI w06LB *BadJ&_ 4 7B 0 kSR^ؘ̆Fe|OYC ޽4ZȡǞ@{?r'(Ϟy;@M9#I7J()@ -P -Ԡ-@#  vp8pn@lhI -GR;hi/-V~ZOk+i] ‘ڴ6WҖ:ږOݴvYk];Z*@ a#&CnL -cq{宣Γ_Hjth  -TU+?n# >eó[ #>z5¯(@y[2A|@|bͰ;|ɉVGOXG'[ӓ|V.۞ee]x$uO:ja*IדqLe+@Gהٶ?W)5},Z;uGS Z}|yXXG-QGtB SC;:G´A 惮4_^6+ZE0e'”m[ZGgJ()@ -P -Ԡ-@#  vp8pn,NIaڄdHGlP<:CUQOM?N(K B/1M¶PF%W)눻򆤮%uaJI O A -FN$:c4d*;ھPEȪ0 -"$E˿]3!\"rAc4!do2OӘ+g+&#&2V#> 331c|پ fδޑwW?*$虁Qe&lg]qfeyYzu*N32񰶩/E7vWf [^\cl f|2쨠,唭'bg7NQMg(GSsp |4{8̛~G0m~/Nᴼ+*}xB|,{Gr/ EF5)Iv/6p&d'%%g'sh߾Yal*8fqi*jvnj,=SV1, E%:,){Ctq~=pli*8|g2 R\0vl^zwю a>x5W4iw߻ḩio{tڦ-K0L 0 AJP4`6`p. BRN$;Ґ6"-avE)QH+4m=ѕ6b(:7mz zqDj9+΂D*$=9J&L]KEzj*Ab%?񩩓U 0 $ȃïl0M0{ >1_a$K;yW${<aüt嬂$dpI!< YV=FfZ|\#9+`#wvaIgJ&u-Tt*U=VStrUƑk՛*4`jK^|v|a76_nxl~֣閂>bq{7R(gp]= .gR|cW>yQDyg?-N}T,> 0 AJP4`6`p. B2@I(JK)yRQ }fSAJK(KcReCW7x?/IdocM(:~&:\+E[? -Cx/d߈9]v -e%,caGGLc¯n1/xڛFӘ^9/>gzR932l\6ˌOL`P+[rUi3S L;HG -Vˋ* -+'STM;jZN(.X0U?L54Q%FȤ~Zx#%(>lQoЎ#dG]-~?6.8H{?飽tZRϔQ RAZЁ  F0,`'p/)=D2GhP|(r%Z:EMDI# я)">2)bk%g@٨9~W|̇ڗ$ȵ1Q/d ؅%/$o"'B[0ׇI -%d(Ab¬~d A) ߾L A6~W?N J,[ kо4t3о\4'ڗ+煇D/39>;jOOFn$dsa9+pf%;/|)nhP>ǔ )="ڟ"+ǛxTr))״L ٟK~쯩z$˄09:9`^x,lU!A#A>FG {)  9(@ *P= `3X,N<^HS$xn &@"+%6 HRnŷDB m&6s%T(a[=8 5@˞ -B"/OW.%=a_V  _.Aw._B F(Aǒ}+sG3 h_Dr}p$ r2=+j/0I}kڗ]9 $c3\/'s_LgW*CY%i -r^GKRd~$(-m)iznt!A:J=jC  $H HW $!R݊ 6<*<|.F7~ ݼ L 0 AJP4`6`p. BZ҅3D'̴ lԠR4QUK[$&n'1 &N6$1b$l8WhB>(C!˫A %HrSB b -%2L+H8͗/Բُ\uI;0E/ªH&Vp $hSd֫%}Sd,~$$ -/ì25HWN &'ƣIpX9iͲX.>;K/ *I߶8f~ 3)ҖYccj"EY/E4U񚊣HһtTl_a'jzyg9>)@=a /ڏx \kd 2P da$b)A` -A -25h@ :Ѓb&0l\<4>Hj UDZDdmP|%qPV/][B-#bb.//'>|ޟ*jvM~9òuֵQy|W]u[S)H6GZUIMOI&GonNq-i9n ź\.ǏUS~u[r{\ظ_q;W\ܯCewj;`cYq;R^Ż9w - ySo+,vJr^T^Js[dQyJoK}<}nMXm{=px[d1&K]3o=%e Uo/\5;K)wnG9ŌV_e|e/F鳎]YAr泦 UMM~.Nn9m׹$ڡ-bi mї> O?Mt`ߓiz_Ir.ÝJw0\ -x.֓b]'lhc' ^/Hʢ -'S5[yETU[&u~&ix?A[ݛjd7H2H2zTSbi}=hٺzGe˜3)WlŔ?v95# -lO_c?icGſPxM9A -*:`&`1 \  A2^eȇ=B`)'%*'fh{n臈81O#G3ĹD\M!.F*I -cV "c $ZAyS 71m aׯ &9Aa a~A \7oU}w 7 ⣵5 Oa:!!3ݐL_/ĥ=#6v;ؠ#˲"8WM m3_P0H= W ;yEeUǼzOЎ߽A0돾cc$SI-0DR0AAfa{`0HSN ݓ}c0H{ 8KOJ%n{5Q (h 'p7` 8<H@x"o$t`n!Zj# RDUA&;k {7qvW3q5%nwjG Aa '$ `GN~l+,-AJ rI\#Aa \C Ab`Wfnt;V 3rG_5 ᅧ~u/2pI2A<7Ĺn&5it4'.lfq_7TTw%uw*ZoQ6R5~h/u!'dơddcTLvˬmۧs$WKK.aڞ'\I}9w%K$6T]TwLh( -h A +;pp>?D WQ -P(Q(RF!ڨ0pB -g+ڭd(6)h*x_I^I dE\ hV4pW44p Dl fWoB@+UI5/`EFI[VDP?+"$g`;my|t ld _ZOo=5&qO)hDLMaR=t6=b\ȑ{S\qs3Z32Ƿ\?˳HW(sQ"HHoɠޤ.kj$h>5r6yJfh-0djO2w[crg-ғAkq^-e)D>u>=HI#"J4}kHCTbݖ%{]+הDhJjZz`F`f, -l܀, $q9ȏ%b(QOM\{&~Fbd(1Fm6%#CE#ɓ;N|%c䂖3R(0Q"IR&J|m QŇ֝%##G?8+Jw(1-#G~pnQR -%(]%\v(Fm?]Sw!JTW@iC -u/iB1ҧh bSشtzL\z:D\,~X ƆRR1HB㖍s,+L𸕞_ zY*zƫe}zU^us^MaV;>ȫ/*N{^o) >reֱ2[黽2{C>rHǛ)Nm(kn  -ԞhOQy;ĿPxM9A -*:`&`1 \  A2^% ]-(*2hZ貉>ʈZ0H[Wg>q<pWML߾A.hF 0qJd1{ y6ayDAn\;R1nH9(dz y8-\a;`{7~o~)?<,GfA鍖 ?,@1 #706h  -6ޑ¦:֊ ҿ??r/> ݈ 4 22W⳶ t4"4GvSݯYk @4P%P5-=0#03`6`Nnpx*=D^D^ DNCDGMD&.g(*6b%Vb 2AN9&.g%YDv $Aiw!WXrnmjA!BT/BHBH'BCB[6#dW!Җ86BHהDhJjZz`F`f, -l܀, $UڈBڈ(ˈh[kk+5{)qvWq-"&7ȅ1d+ -rH -RR9BDk#1!j,%byV2$Q -!qҮk!_}Iۮ 1KQ0^ ʘ0ȗy) ґ2(cJ_AۋgO%5^6n&`t7ŝvAjZ2..׬޿.jCGEmPm/ܤkF&h+ 6Fۤ]Bzqb҅G^A952b|!dn!IBF:הDhJjZz`F`f, -l܀, $UНAH ! DN4D;AtD?C 8IL](Xˉmڈ0$\'V3h -)[AtAa SYs1ْA SP2ȕRQ1H rKB vm |}G\慧0[ `Me2gOb)iB9&ݑJgqAfh)H(cY/oX;`c(crxEWGm*5] Sݩ.^5d댓I΃IRdqRFz51= 0H0HO rT{)8|\eD0CA_AZ`3e ^Sh( -h  'p7` 8<H` ̈́Z6H>QUX4э}#1dc?1m'6 b!2"^n%L+aB|o' ەdw$(%D1YAJ_WG2M sA>c_IdMb zYΰ۫aK} ayd+zaO9/<Ώ] rp$†Xw*C&rwZn&֛L;3冊wrbޯs)jvdQ]CQRPvkU;='횣мVC2C)ǔ\^rm%2[ާdkdO\'Oܙ輦;9SƕwWd6ŗ-}"w,ʷC%Pǎw9A -*:`&`1 \  A2^ȋ ]'cRe!Quєm}91)TJ̥?28OQ2"BjD R%|!?aMevs,)@F $K5DߜFO !1$̧s7~G_z^xꧯz;E]!F l0ƲiA+r0nŲXo\\Z͝#̊bkiazy_F:D -Dكi$A9Aռu74]2莶7AN8R#D>1G֙aD K Bߍw7"ܣ /kBddV M {WR+2Q (h 'p7` 8<H@wy(ꈲQ\%bh&q/EPH6[׈d EA5_1{eQ!1>kN(ar3&^L(B\i7w)A. r cְqaInAS+o0;8S O^.@y _I߁AVpŧĆbw<`8/tDԐ+yй[Z2:[X*Y/oHw }TcXU+D1Yʕ -AކAƄ;}%=_), Y  [__!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!Aʈ݂A('"hvEts`fb&Zb"jCa O|m! ͽFx˿KBa r KM~4u5ehR$An A. c!\uնEAS\c]ӊA~.,j8[܎"i /<Ӯ_{$З}1\05..02`ZbY.~oF䬐Ar;gmǏȢKE/+CTꜗyMm }:KIю$s'b_X2 U0H҈?KEi9H1-(t|roQdJdW{{RK r@( @ T@ 4@ t@ L bX ؁0A$d 4O`#Iq7wh80GӢA -'|mۉ3G&ivp37My8VL- b cXA޺=bQtA6GUFlNd%5g᫘.'m?\H yf2:aSd a )!65=8B  ±O\|l\'5 -mꒌOyz U ;QdeGUӛc ڱ ƽ~QXH=u{LIm Sp#t e"֠}T8 7 mA?WWb`dx2B?H͙*)4@@@@ X؀8 X@"H«LPB"V"z&l6 %ĔQVHyVOK\9ĝEr6 !K?C. B1F yȭe!A^~ AN9 AvH11f }#kbLs己 muA:PL w/S?us_%ҁ^*7\gqm'pWJfoTć[$W2Hb |3|ݖUCH?B>SBF!B0 -j !4C!2PHz' -*G_t#S(CPț3PHdwn. 8G7-p98W(xMMYJ)ȩZBCl' B!$m^Q=,X26#,Ԡ|b~WhJxl/ɼo|b) Z#RBȩ -ra7BHvBH{!KOo !O:*'΄ -DPP 00`V`v. `|~ LO -'֊]DOb[{;1 -tkU =5A=i!l7ᚈQ(~.b(6n5($p]yBW3Ug䎈yE2ȩ0 !dRWgk0I G\zVʘ W `oS'|06Gx=4 \u0O#>59..s)9?'>^,y](lLP/Te#^tcnqח?)3Tf]o*PdiD3Qo=3xXfn93Q(~zbA[|R}0 X16D%7?"k @4P%P5-=0#03`6`Nnpx*D~c !CDKTDG4D;LtD?N BrO1b$%b_$c5ESbOƉo|h%a %䧒A$v ᯝț1HY&Y28ok0ȱO_JkA*[oA榟hm -% ɫh ¥]!6N``qx#rsAW\|wA3fj7ܳ./AjF(A1s8A9wUK:7׌Nfn u# E_I2U'Kw [,G!a;AڞFlC)^BYxR8T_$2HQ6jAFn9Ar@( @ T@ 4@ t@ L bX ؁0A$d J'Q0H'Q]-D n4]#w$1VS1EYK\NnVn9իhKqF}B%a r Ү;vn2RȬu*)XRykڡ[Qc Pir=O;.@~\H_=Lo/P+-0¶ <򠌉Oqpnol -ˆɼi~`Xe*+}j@1,U9r֫n~ƫiesz]λ^}e2Cm~SY~@3nmGX ٛFՀ넕=>;XF,nST|+F\f%)4@@@@ X؀8 X@"H(EO -()J9I8G))J%Q|B)tneFY;J(j -$)DF!H -yVR(${ -g 浟E4mA( 9k_roa_r6 OJKBHARii4HsX1))`Pz\ЕrΨ֒nq7p!VXx!,W4 '(Qdϫ5 ډ]'~Ox|_'ܻ![5BȂ3-,,@!$S؍tb[?@F)GB -3!)4@@@@ X؀8 X@"Hn=REnBH9R[S6VJDWM!\!ZLlJmZj8lWWXKN#< Rȵ~ioy$w=?ۆ )2Hְ3 2r$Q WGNdާAAJ^?Ay{9d#d#d#d#d#d#d#d#d#d#d#d#d#d#d#d#d#d#d#d#d#d#d#d#d#d#d#d#d#d#d#d#d#dHR,((a\aQM!?E1dpn%{ؕ:C D Br/R#4~0G޴]rCwfׇ>WOYde -2ܩkrW1_tWnqѶc+`dC!/<)+gE֋g?וq \A$LEM3?D W"D@DND+%2x8!dxhLx8wP܌i7cJAq^BHtù6 I6A`oc}7fA$\ ? -d>Lq y4ힰ -U_AŠA2K+&x)'P,YQ4գyb6=%;us yc9iy>{G^vzy6q R&9,+'(ۗ 7`i= ƓLǏ$`e.h-^AF  K0=}wtn}N1& ҙydA8{)4@@@@ X؀8 X@"H!eˇs[Kk&!Aq3S4 1s`ab=Jl5^)O vpT|%A  rhyf Q|!%N /K(AnYCs+ S~Eo|1y 0s}]CyawߗH_}0ltœARCT7p 3L:pbBq RYQ4.fHd -t_2+kz}9A[A7I|? RuI$sٺ.n$hmRu2JJ-ArGʁAJA}g߂9 2ɭB.Ar;Ar@( @ T@ 4@ t@2 X؀8 X@"H,Yٌ&v (jR7M]WC 1֊Cts9-D8JJJ'l݅2I -c, \ġ5:A~AΞsx{DI20 !ɒAG:[w ?oJ8Gysd wV Uw .~A!PHTj.04O6`Bii`(.rnwl\06ŝs2f鞛ƻZu-VYT+RѳY^ũwYeinN}bYz%e2TεƊi|oyCJ}l)|Ţ?g>sdyF^#^xx'4_q Of^i?*$E*r:^Sh( -h A +;pp>?D DD0H8D<[7D4D[DtD)eS1W-_P#-$z'jpėOr⯸!_22A2%%$s i,< 7fq SҘ}!ek0Hۜ׹h[ĊA5s0Hi痝zUAArO^q1H,:RR(ch彘1"T#oF,RT|v.\_OɕT 29Oj9OT1o rd7T1_YAaC RA0HFp-i]ɼOۧ~ r8 -I/bB)` q!T1(et7%=K{AvkL>\l_Ar m^EV94S{5^cuo{= RǾg87 3`]WXnAjNa$/ ҼL1ɘ1|-B?Hh+ 2~+'vWGA*DUQMA -Ja$*'L&a G|%ɘ [QJdc|_2ȍA\GYאQ6UzJ maQ/ Ky^˺\՝ũOdj nT1`wg,ٺ箰tG0H`0Hpn5t9 R1 |C0H]% d0H ۉ|:ӔBۉ)ux~dB̧b-!b_}[!P!V1f.F"dVR"K)2,ArnI?j 3a b bx5_^] A8ݯ^zG9TST1U}߼Lo]c㙠#&T1t9X+Ωbz_z4G-ٶ^^AT^QzbjKټT6C| -u|xia<ܲ|bi<vr^٧'a2ֺ>ʝ3.\-Z2n)/Ζ?ѳ眆A[jF%8הDhJjZz`F`f, -l܀, $uU rRex0BIAj9oyZ<[N#6pķ@_raQJz|[2C2Ua r M)['?su}*dG|S2N}ùk=dXᢿsB0G_USW0H~dAt1HZjl0qT2qP<˹jʨ|!!T ӭ"p MٝP ӼK:}:c2H+IJ5ԛAkQ6 ұ>L !=W y$dܪ 9A -*:`&`1 \  A2y'vex8xMm&^bl։U⽗mb.vLB!|9^2/҄D4H |SV=ߡ\^n rW|_2HGēu7 ߋIۃ-b~Pb0H m[hOS?nK^-EU~wqDAULznɧ.iǟ1*v%軏W }ش54h·ƴһW־Nd^{H|wjyg.QȸS~'+_Bsp85G%NtSUCg,4@@@@ X؀8 X@"H«y; -!絣 2#dV\ !b\cē1ݢB&īs"yС\R7B% K -Qv -+uG -!_ -ch m/6 ;{m;_1ȏ ٝ Oa]1ߵ [.+=MsBa=䏸x+ -) :lCoZx~B# -5w?=ZR?R>S^hviZui[vkK IǽSƏMOWly^f|Il~r>@ JG*(eWy%S0"fbhۗܲA4i5= ̻7FlҗBӿzKIIӏz{Ed%,1\e1mG3ҽٷw -1o)=*7uJ>u -DPP 00`V`v. `|~ ̍1MD޴裵 O޺DaHuLk`J -~Ma3&áIKqƦP1g+dI qT6~9 US^dzypu+BK}TLGr\?n)5uGtKC~d덹o'&aa>3Zc62I$>5_ 86'?j1a/fkhxXkTrU白 -DPP 00`V`v. `|~ |vѺNAhĶNbhƔsg۹ waZ ;Bk%|_'0qHM -!a  ue?v6e\d ; Tį {W `A-7d{LѺT6/A9^x2f䚭_I7A.Iw{un`{k,bὲ=TtC3hã\^~Qwh__keM'jc*4Y`z^YoX'o.G;dgv˜/UR;b)vAW7(~hG?\%8R-WVG%g~̙ԃA9A -*:`&`1 \  A2^%KR0H5Qe&QSJS@Mb.SR Ѻ!~RK;0C$B[{󅵵HK0HdSw][͗BܯQ$H !Gœ'ZeN\R\s>G_w -yPHyxn2}yQ+c4GlH<[bB.0 ~NC\u̓b_:y6: }!M`0AY`Pxm { !$oƺ$SݫI*lݸRv:<-反w! \=)wc!"tf޳;!LJO5 20qf!)4@@@@ X؀8 X@"H« R E8KZ{ӹ1ℐbBWR .j -RDL"5($󜭓{䟾2qDHSB&h֠+o)ٕ!)4 | C!'9B!%BWj̞;21-c墎9v - -Yzk @4P%P5-=0#03`6`Nnpx*Bԉ)zhʈQ)+ ,[8Y\ &_sf}>CbŘ4 cL !yFnz%@Lխݡ""v!VD!E)RĔ!RD.VCl)YCjNjc99=|>8A"TO=Ǽd]!|'0_2sKd)⚲'CAHO()A,0ȪY  K֮߻}iAȻ]-ew=WN%$ޞ#{Ļu N[]%&%uY3'ē1[ -VKKesk0䛂,7INQ3)(kՙ$uɌN dtW{Rn]a#yޭ;vio!m10i%q ײ]9 ט J kLE -DPP 0`f` ؀pxdGE.Wy(ĻuuDI4b-uWC!1!+BʉNNbaN%|+qAH䑐Ala ro c r\{G<WtD\22k_m_C [$[stѝ0S~ ɬS /VxÕO<=9}V`uy]|:~$^ZK-hjc#A!iA -$YIY""bsI$u䌠 Af -~oL50طM^s)A RBctEȻ0H 2Ҏc;1 }Gaٳ0H 4g>הDh rJjZz`FL , 8@ R@Xh ٱ`6(zK\2O4D;DtVbli'fqMYxMm[B:Ħ~龼A r_$ j 01/#weNE;9YGH!Y!&2/ ={OBByM5i̯)l_B-C zcӖ ʧ'f$&$ddz/Ichj9 NAȱA6v?Br 46A!!AZ @!tLl|=vAHF(dx}y -!R( d@@ T@ 4@ t@ b Ă8`vA -Ho#RVQY8'C!DS$n:l%Fq>rxn#!Wۖ{PA7rR%Bn )RH\K}rA_no#1! L 1ٶ\y-!݂4&O<ѱXKk10ȭaO)Ҙ řHcf'`֕Sqe&fqt+pOtY3|vXg\raOMe#B:'-l[ d93:'UPt$40ڼ^?H5J5-CL3G|:\7& 2, 2{iu3CcV=>t!;`;m0H^żA5Q Ȁ(h0 q7$6" C2BtA/ s68* FDF !|f<7\[j2 2r1Ȓwᄁ#B1k#eб3m0HE9b 5j))Ƽ2 h)u<޾YLi욯%82~o;N+gOZ]v[8;Gi\D-O 3Smݯ! 8e}ya-ݩjwoԔ}{N;3};C;c͟ -%1e27|fN3ڣ̉{ -bc=$w¤Tv2i#-kJ @49P%P5-=0#&` n H) Dڼp;ȆQ,41H TYL&nj&Nb5-yuk 2HI8ݺp17a.q+*2Bn )+0AH2^z`{{B!pi2kwHcB>MG)8bS;,wB>ڍ[^1)۸K-m|bx~{ -1z~&zz!9OlQp*K9Ug&5E=Nm1=]G}Kǰ=S?l4k{ܰYbiLA(?9PG΋PXN -i~ -)= lyI韁BO, -DPP 0`f` ؀pxdҀd/12F,s{e{n#B*E-*exn#q!@< W4RF!7j we({|3B qE/gCMe0A^i6e!UsxPYϩʜfUG+T~+x՘wsSÉ;5yKe3bI\amazc 740!qٲ]U2.F I<'%-c$ig%0|G)`A2  -*:`1bA;  UFinFw3Fu/f}2Kscfbۘ ckcm 8:~q2c)UGP(+>P]lSKkJnXDU"A>XF@@btk<- -8m览@ngnD(=Ss}Hr@\ k/3헬nj;ZZ}vο$?$/ظFNY0j -&tB #GS é7$1=A){kyGbi0$f>Lblaie;8N?K׵bvG@&~&\|M)`A2  -*:`1bA;  UOƴiS,yQ#NvlnblX$nCfq_\/F WQŊ!.B )$0 -ym --e5W^[#-:-#[5 -΃B mJk*s5qcPk!AB@!]+vsz3LG5u.QH߯(X6>$ -[(;eK*JRlYYBU5Ca(*d}i6nBC!PHin7cf죇<{j{V7цq(d8 -;{n)Hs թ ]GT:Hіu10FMT!s />&&qGH=[B<W(I($;ѐB]$P?$1YK!  / B~2H6RS7avm){, Iw!b#Vgoɪs SkL~G;?rR3N-~2}<6+gxݞyɊɲbSjԹe - IBr)&&>HR_iNn^AvחR UTc.d;{:%3A`-BZH_sBdi$ж. 2KWA^2)9 /ϿkJ @49P%P5-=0#&` n H) )~B"E9RF2AtbSj[L$X$\;q׷UW7\R=ڐAXiOY2 Rk"F9|&/ 0kcc=Qh]0HLb7)Ҙai=0WNSjJ̰rLټ ?wӖ軤ں=wdGjlf: -:,{bl:e~j<]iںN;ޭ{jTCYgqvd -zm0H= rl 2 q!w I C_AvAO;`GהDh rJjZz`FL , 8@ R@_XH-KķK[ Dsh~b8CS kn'ib?C1D1>)1WJ"9d_ĦTrcd%}Eo1/ orH!Sas㖡EKjGkQ!b%u42tgouIサ%ɬp4gHc.WB3D| ~([ V+ҝ| Uw3}ct"*1Ioz^6pmg״hkߠ.q+6 {rmƺ_֚ƚ4&y^[<$nrdBb/5)8^%K?'?x*?$vJR:zZJ* -DPP 0`f` ؀pxdҀHVRY+#aMr?a͌і0jF`XV&. -{G/÷2.Fha/"(뉦h-ed]1Wo$v{KO.bCYHo˹ߟ̲%֝;<18{]( D9t_>9$6]'[@-go.@) }NϞJco[9 e^әiKz켕s;͟n|b?6|VlIݹZ8ga^B3L7ld}IbuIIY+=jL.io^s ݔzIL{4:e6Z w 񂃌gCLr!)I+dem -DPP 0`f` ؀pxdͩ a'|3E#D1!NJՓD3Ngng![jl[95D,H#\8=Mipu_D  01- s_LSo2XKAAjB9xo2ȣo;aޝEbAss֒2+y6ka)r |Z -{{l"b')3ra=qxv̽\[ߜOvd,,Ƙb&!1{mW{lVmRDDJ"EDD"ED""VR}oYxύGOѓuCQ5{joIlFv_u[7'z[l)O7f-_%$|-ϖߦ䝶Q Rk -|qD3Q1Ÿbob+66Ցp[l3&%:l$ic\.&&c[SnZT5Fz^zH6w -iee{?)+Uũ4Gl7HtS^)PoFd7uK,XNHM{)trAO~$>{b} Rޚޠ|eGߤEm5a Ȁ(hD0 ;` 8</ Jzm #:"o!&bn'6-"*"Nbl'[Lwu~g@g~Y =?[=kOSO*ZYgUU%~ֱgM>JEz*;DCoL}c,mÀ&i9.uӗ O=UVK_}G~~U;2"AbW -blT'36$sɱ,pDd򹴹2ŸczV =R" +2/D(2+zyU8uWVXv2 R=v~HaHD\k 22ĞG1y(k[9 2r{'OyLJ(_0w8הp rJjZz`F L , -'*D:E GlW(GG(]яC/1v -i tXG01B^$\?qw~x AA rhZ 1%0HwC$n   riI4HD|9Q \> rR6}r7zh7v /. ? z$Ip.b9qLsƲ%.6t?R,dѓ1$ 8˺5't.^U1YiF`IKxc[ Ro27wL  24qb_Aڊa`ón6w$2H^6  ziy) @5a Ȁ(hD0 ;` 8</ J \0%+""!&'|#r$bh J \I,5ZK'V2Hqxj72HxH\  QD*A0O.< AR72Hiߐd~AA g1h1 \4ȕ0vzyC[Ä r; \dGG照&K{d>[UuARcYWj*1.Mm~l-9IaR9 -wA+K?psɥE5K7H«!(zQY7TYFr5SFme_u>+ YWGN9~iz/gۻR.uhsKβvce I9qI<ϴKmH|mJ&I} a Ȁ(hD0 ;` 8</ J%-Q N$BJQA))ՂDOi%tJ*OI qQyI̳Keؗ$L1/a%r[)>l KBUQ 0D._y y|cMhyH<=fܧBv1<3=,nGA*ZUy=\X {=U^xꡭq&Ϳ@_MqڜlfXWR-199)ŞiBX,*F d7 Cq=+3yUxM]vm0у񆡃ƹ"䏕+M9o##t>"GO+G= ͯ!{j#T0!T,G}ׄBҠ4($ -IBҠ4($ -IBҠ4($ -IBҠ4($ -IBҠ4($ -IBҠ4($ -IBҠ4($ -IBҠ4($ -IBcH=(~"k"DKDED3J1eX}0G&%qx6TH\T:B^UT#BmB!-gSmQ!rDdB. n"%( 0W+_@!%(aUS^k;t MBؘr$B!)QN["cK\4`\3KӦsN)$ggB uP]lq6N^|LX'T]oƩ+N4tA!^7@!A!&Sye -BgA!(dN}027 <t? 4=LR^S -h ȁ( -hA$03+8 nH«i/'A(hvjזQ{Xڈ;G͐:7XF%" jɺBA'*fQ! -M(g_reԛ72ȡ/U(Miq^ !?< & jٮٹN[~3 [7]pB&ⅧνWo ~ycD$&GR0+%LTu8(8 Տ?.,nUl`0H!g -" OoW6ϫ?SGhkۄECK01i]y0  R=T 0HS0 K0|- Ұ)A8e PP 0H`f`V` p8܀>W"NBwi "%z B'2j:C" RF,MZOE,BH9q7xFmFIeKE\..ĺ cYAn fCdX4A rB4Hr2MdpΙ#\ Ro0H2.dkl0 -^x - 􎿴h0;o pQ%,1ɐ -آM~&OM:)Mo -,ll c0H^vە}(cԫwEh& -k{ -˨g =ַMQ Ҷ} ɯAت0Idζ 0H)}n)ES2eL)ʘR1(cJQƔ)ES2eL)ʘR1(cJQƔ)ES2eL)ʘR1(cJQƔ)ES2eL)ʘR1(cJQƔ)ES0I0 (Q eQu#Ѵ5(1`b&FaF#[glĴOF u ? bM}2J / r8dXŜ r?y+4 rrV y OP\< O=U;|Ѓ0[AR)11TͲ66*9&T/6grTj"%$n촆OTndlO<$ [e]Cq=ch :ɍ,]{a#; /3WD L~sn= 2X H셟 #S0HumJAr^良AZߤOjkJ @89P%P5-=0#&``v`p x^ @x "]"tw`#&`1&v'I"#eb& yum+)4@@@@@ XN7x$U }Al3'I&#qo'6bZb2@; afch$8,[3[4 be z W1c0HCa r _9q%  Wj'kfWRw~*& my޼A⮁AA꫑ALJ߯֜{c>zXf}YbcD3:9%9PD]-u$'&'s)TYH\zo [5jMtْJ*xuflU>U[551ںBn;#q8c]ْȞoj\3k}*[E9*{k(rN>kw_Y\Iy&/Q멄g?PxM)A2  -*:`,p< | A. MdDN %DSLUDWGuİLsLQ:b/!L6q68 B{(>x62֐aCfh?10H_*Xy?A %A~!nBbA "D1 - vcY7O`{my^x -1b^=dWKdC:Ù)W\NUNuUetNL~c[ȓD5_y0XZտ_bybz)DZ)W(gS 7hJ(Of[#mOQ  r2U -h ȁ( -hA$03+8 nHk n[m!%j"fi k JjZCj=SKM&w=ˉbEdkHD Qn 7~tS3]}SGs8yqeX9N5^[.47j~D+~w' އ~nkAb>/f׼&w>B1#ϵP - |?Lp<ŷ})jN*!;הp rJjZz`F L , -'BH7E'QUQvBH1̬i!vb&֎BʉN2n#lBdU8DXEsβs?\ODd)?D|E4 Ms<2m W ۹яav)/<}>I#$IOKߑJAdc㜜˖%&qL+)ttէ-Rwo,YgB&AVdBW:EGve8UHzdEsi!Ӈ EBB2MYca)u!d!d~!dn!$EhC~ !dݓwP)O©22ɩהp rJjZz`F L , -'*D.=Օ8Ww(F(Q-u m# }=1c`Qb!Qbm'>,%YBdhYl ұ%TKٽ0~~yþT AU4ȓA䆐"ܝ K#/6Sh)K+ӯAsu /;; 7 gmIѬ-115lJ+M~3 %[  EB_l'/+׬EYە8uzz&?s c(c_7 -}](cBS8 R , }BSj! 2T -dBӽ2fR(caSkJ @89P%P5-=0#&``v`p x^ uB6cf8E;QQ%9MfXjh"E :K죄%f&{yn !O$A4SA.b0~?EQ - BRD|&*DD!M(dMB愾guhzg9PHJS挊Izʜ~($MqI1Q66Lvhݘ#,=ڶRH[taGߐ?o03.V􏙔u&lI]ŚLڎ놦bjH<1/0>!<}0w^1|H¬:$q.Jޕ8 Iڦr'%|~ְS!#_Eے ;J @89P%P5-=0#&``v`p x^ `h1 GPJYMV$2"6Jb)ԔT8l~ccYdϞٓ~St3ҡZ37' ˚x -o8U㔷.i.e}Kqԩ>L})zcyc,z3OzjXJKCSqrh؂WrDUv]Ԉhye@&fۨçbR@0d@@ T& 0H`f`V` p8܀>Wi JB/ -YdŁ󔲃2h*",!TŔk7ګ IY"JO#HF&*$w)şs2aa775dl 2y.Afw iaavd9 M])1UDVO%DQGe(b(&RDG)C=1 9JG)K1f{aEęNF%B?ȆA4!C\4 I u.&n3E 1/C}٢AC.hC޴ ݴ;[a eǬ/20HE& 5 Sdvd>3=)O %RL -jda7q~[T+J;m9TdK-fZAz_vZ`ٌEwrBdi֩NLS~']ɁA}_ ҏ R-C!Y/ {1e['-(^ -/=N^8+'^UCq꙼-_#~da@1# -1&CtK,mk~ ɨpbgOQΕ>p 9oPwbC!ڋkJ @89P%P5-=0#&``v`p x^ 0H3tTS&UHCER"QHRMT!JĐ%5 -eRP`*f8JlY.;_/ORMTHXPBb$bPe!M qA !_M1&1W}7^w2&& T{i0e0HKcX2Xa!N'2&ev0)1)0!i YUȇAڟAeqcʎ2N*A -g -``n`o^AF_AKaY)5qdBͽA: -`j)!iR@0d@@ T@ 4@ t@ " X0Xpx@ڈ%q Y tu}1ܮNb'Lq}pnKdvso yB4Cb11ȟ7a rSo>M! m)e6Ԕz_=PN1M[AVߵ!7fM=q[;|t*(3hKr%%:RmT!qN[Tre)g :iH+80c!k!9(x -ie䙟KV^,4˼mQh)Cv84o,|^1tɔ77B(d!22꘢1(:f1PHw(d6Tהp rJjZz`F L , -'*D:CIA!D6AD1ERQOMT;GtLj~qk"9b%!Œ/gTX Y! H! -WB.dP?̌_x)eƼYR!WB 'cf~}iK -f| bx 9+0[!B"<޻}taΟQѱa@YFs-1:).tf?-]?Ǫg.|^% /~ -_%/HyXR5>Ιh9y`ٱXc]y OH,oXZ$''AK)jQ_]3XB<@yzyU@SQkJ @89P%P5-=0#&``v`p x^ `tn5gIJCD1K)(a?w,SYJ؍ % -(K.f d, 2gOF AX 1Hh: tb3?,s7\KR䵐h!͍LJA2CArxᩇJޡxGn (1Q-)cccRblɮ$[L\#97_*LIꦞ;^uɦeb^NK3T,k5+2n}Ӏ71[#˫0MSs}-KΝ$%FY:H9~b[^5%bc;Wk(OF; ouJY8הp rJjZz`F L , -'* RBdDGEDY4DLteDA Ę!dE@,H콄)#J -&FMe_ i֐1}1lY4EA Mo^*R /AE| R*s;Bo` 7#/$EW<9 ~ҷy?G{>A<:B\1ɩ1I$.)&Iڸ$LLt,8`lmZKI`أ[芧PԍeqEYv뼪s,N=+Bqv4w7oCSdߜ=*(.T1C#b Q*WXJ-6wgsQŌ*&}U쩅 PP 0H`f`V` p8܀>W#L).,JӹD1Ms1D3OSDCS)e A&yXƈu%:ADsE%.Rȹ-d*d+;IwA Ґc&Et m }os ZnpWp,Cھ Ryzᢣb!occSm~a3&Gq:X/I:ViaRjA0)L2#ye*8NqW&^),N\7Ծo,X֞>7, - V O ]RjAvN•z>e ӵ0H0H[)5a Ȁ(hD0 ;` 8</ Jm`)L DCi]D5DԵ !B` YiMi'~*5zn!ℐ!'JCv܄Au -_*Dh rqܰ`/܄A.y4?xN[ %G¬ OG뢋y) r{_B" ~hcl)Qbbcmdg39c3&㉴'+[YnkT yYq]ʕ=j8uzfU^[> xn0P7o{R2~}nA`&-ddd ~3aZ{>2^dpeL? )5a Ȁ(hD0 ;` 8</SW֭f- -"n"_&E̗}IG10Ȕ`L0p5S6C \"\ q&LcA. bgD\, yfY&FRd 䀐 h?v ;q]nd.!9 O=4=zE:b2)NJMbbm)6։$FٜQl"4J % bRQ)J}+nQy8$PJ0eY1B ➠3}n N QA 2$䣐=6aճڸ!d4C0*V1 ! ij <^̧0T Cs!D`9:Ru;2HżC[´551HLlrq -e!đeMrgY,IL)+dmt ^B-r#C2E^UN+7B3pvmPŬ\/@3ol(BL~spÊpCbZΩ9T1-w }$jwy MT1AAZ+Aהp rJjZz`F L , -'OU1RΩi$Qn"@36k(Zu!Pf-g t jD#dOo Ig6]7RR-eAA hCZs37(ڪ3.*d{A&^xA|0ȍ!\;f-sQ.&UX?Me-ݤhÖ茊JN93MJ{`v>*5վ'Aʳ+AގSO).ٮ%}xCD%dvdoμ{2~Xb=6 G1Y}#oX־t {nw=CE{QT_I -?A*O5a Ȁ(hD0 ;` 8</ JTZ!,YTY -T13D]&,2eJpNb-3L1DY̒r],叆s* BE\r7{0SgB%odN9 AUbsa& AV rjd׾Ж]ko[ -_|7/<ж_?/7$ЗzSŰQW-%Yh -aRcme.&匽wğjcO;;leIx#6YU>?V۔M6UلU]8֬i%O|m6dwdVT=sh2ԓk{ı.-ۻrv6K-Ȥ$}'%Ù4J}1?p rJjZz`F L , -'*tfQqJ1D)Q~J=Hi)m2 5e,wQL'嘡!9Nq];) Kg?!wbۄ?TlÝO_j#[C 9bЖgs73_]Nm{f${?lW, ąǿ?=+H uǟVΟnب(3p:RvW-ŤIJIQHOipB7ӗJk3yWE%k)u"g\R /:ciz382kM{ u=L|kLjEb;+ο_b_ybVYǚ)vW(cn?[[)A>HyDŽ¬t,z`#ub(B 3N4c!G~qXaVyXuا{͖:G Lܳs;Yk 1Hh-vb^m; Ǥg)XUâA1qya;`?5 _Z?X? wq> ̍ EW})ALo 7rl-66ٟbKNbQ$ĺI.&;+*mC Sf~5guxeW3jy6N]N3k3ӷ -C&S~sn%}`TbC vb;@93P?-a/;MGQl~5!7X&[/+Ҥa Ȁ(hD0 ;` 8</ J7zL0`XLQ '^%ak'vbXr8@&$vgs`Pk`'fVA5d?A -Ao >ۄ Ǥ~ r[ $l3HhsoMb`-ު[7Hi:%GA,= ev5̑W|?A?$vE'm -dIID+Jv$%"Dn⬛_k2J7BGqa.SUs)ss\ZH-/2d`r3u} 1ڭ~eb ܼ*f8d$0hyܺAc\%/AA r& qkp'&K׆b;1AA7aO7-;`daY*>iDT1M$ y$j62-C" uӑ, :bTFb)1)~j{-/U-)vVoeŒa{x[qE^Y?ΊL㵋w=)M>xC;.$iܻ{auv -G1sQ#0H 2wd};S%dMוsJ[kJ @89P%P5-=0#&``v`p x^ @x6"\3P(r-0p:+A֯ \8I읁)m!Vuw;n \Ìdý1K]^n؏BM#dkDy zگbRAn܆rA.照:mdC•u@ycDWLbLt"23r6l0,FrN$93yDjmtO w8yVA:cr^xzwE(2OΧxtwlEɜ͂Aʗc/L¹~HHm> >&FAND - }I:Re ]5 הp rJjZz`F L , -' -O]85ANY d#h+n$`Ab^[#b'9]nq.JheP4L4&A~ x=u o~?KMEBfP |C촵Y߶A|A8ʕ aC_us^z?ގQn3 -uiabQŸĘTǸ\)(eRc$5Y؋i~RiQ^̓=,>7rɇv_/+T Nufe6vRnhW=W=ƞ 27^66'0B1:īGh&Jk[Dɿ:}`ad9Two|d 2ȨR-E.,x$82H#w{&Az?@,zڅԅdS)4@@@@@ XN7x$ 2CN];bf5AFh%0&uVg- {ac=D([ .XQ4H$TOŢA~( Փڼ r,:nR -yFT ۹[!n.c9B!rKP!…Pș(ckp佯}2kئBH]0(C o}l!E Ke̎ 7儗NW@2B~VI`oFv8NC˘vxgF-Jb8#qԑgJ k0]ZW<2#+wwUgʅv]zv^ hKB>~ygX5ܼxȃsl|r˧ )M"25 4]l*gJ "@$9P%P5-=0#&``v.7 $)D:Bvn""SD1!&^ !bHT!>qPqWᦉ{#;!.캺::&:B - -0XǘB(+P -1-TK5w,ugׇ; Y˷loAGa%gM5!o>m~VG+@9R3<.G\:qdOL#=6N R|_t`ȗv1<]ŅG5QW^U$<.>A3:4uL&cI91t= ڋ:fM13oN/tKPtE{uLKc -Ė5HX e;Q5u8jT\@Yj5H+1K{qWڈۉao $I"h_ .A -W`^eLs+l#I4d2IH - ;A)ʘujdORf ~.zc6w`iK:SN[L,qqdT0,xb.o*TEZ4> 9dEr\9'Uxu(MC] ݮݟh~=XmK=a2O6w}2yږz i^辁 RedS4lο2{2+ 3AgA&/1L)`A2  -*:`8<$$ "vaȎyA&C\phħ10H=1]F3H< AîiLq rM!Ca.tzaGa0A ٞ<̌F;k}OväBTB:eh -Fy/~~~wX\?'lt.ƥb2lNp.OyֺCOfXujiw'${kȁnYW[^xT x ܪN£Xs;7zm4V<'9n4 ܜw!ZlvY1,z&ƸK HMޚeo~ LLh(| DPP 0h`f`V`px|@:4!|"o 6,&$z!B/wNBr( -*!\qWxsPK|WUv}9BLAB!([Bk/7_O?K!|фPHge!=yH!BYxOtey;Di蘡K%&sBFLC}WB̽H!_I[FNB![B^Coo4"4u0\RgS>S -X" ȁ( -hA403+p@> ODF5Vy]9Tm;51e8BrD\M~D|;H~>$.|N![躂 -*ksJmeǴ` -9(:Е@!QV>2ݿTȤ? -dP"_o@!fB!ߴݹ(r[j -3Lvi6>s:=)鎌+hI+ 7; ][nd(EΞAxuQ7jǩBr>Mx<ݙh̥[2͍PHaRFRL -Ϣ1$|-޾}PHi(da(dn~ -xnQ!L)`A2  -*:`8<$$@?eHG(dE+Q6U:WuXDPMCT!"2b -i$bQ!nF!s*}ݫAC(DT/x -@!ַW8L]vw+_1IeABAv aȾ1u6>۞z [^V]o ل l;dLzg=)i<xm|p%8S/[WS).N ۹Nzj^xn %mW5[-r{WoώҴ.0ıSY`c&S% 2Q\)z,A2'am0ȩs {]hYPɢ!d..),@@@@@ X؁܀^ TSƿ(:7D+nj RI d:ˉeR|_FCAv0xgpꪧD " u*1a +0V׮ Æ/՘4\ < vgړBkR-#g:%nv AK=!7NٛL`O)Xgj, jKNSmc;6ݕqYcj~馼Θ=Mر'`ǝLdn k<8#(kTE#(k6}DC֫R:-vd - % 1uL^' R{9TpO awA -i9toAO.),@@@@@ X؁܀^ 5D@9j DV,OE'QN Q/Ν&)ϑ1APH-n1 "1YRoTȕ.\]i $" ݗ:ܺk`{3k_`Zgy1h fGч0 +07>15UWou~ؒAn!a)g7GQTA[EsA3˸]|3xs3HDX45!  P 1 ~Jt^D~N!UBP!B(߃!䑰cVvʟB!2;BF/D#f@Ek@.C!;Wэn\2܎TG&gqdOL -p/ !G7x[N{gBHy.AV}*^ޟڵQ9"ƦB6j #]?}$0ޞhB6N&Bf#!}Ot11o2CD9BTD=K4bWY5OCr X2qLHqMBŃir - kuB -QPȏV02|#]c٦)au -rgP!!Bn_Bn>z{crKy_x6zϽ:BH!)?H!1c6WB6۸8wšIq;=1zB.d7Ot+_X+59'%3 xd}tCzErN=vzq#D=#}qglOt4_gSb?J' g3^nws{a:D="0FؘϔDH rJjZz`F L , -\n/AHSꈴ1D(v15b@DWGۘFBĞJb-!J&w;᫉Ua& a $Y꺠Aֆ= Q z'˻omO: 1 &AF'!G2+0H% [y3KlAko(c֠YK{ )3ra=qxv̽\[ߜOvf14M܈1I pr޹uu߻-p?uun"""RDDDD!"FH)RJ)H)EJ2DĊk9!㇏dz'>|ޟ*_9yVZB;7H;7:R~͝[SRa#ߦx[jNǝ.q~rӵR~yrǸbcIJ_[_ƥ3Fn&=Ʊ4GltJ;=ړgRʅ~fۭ/کdj` -yɣM"_"Kz8E3Uq79Ms:Fד:[L[CŅǢ,C:"ͿOfo{W,+i\͔?&j[n|)_gP ;S{;]*^Sh"( -h A+;pp^H $^BU}DA-DUL}D@DWC0HTJ$jev[g/qw/a [EN IªUɫVXv*J?꾅ߋ~/RB[J)=U4 _U΅4ZJ+}N>{+I,}jR_Y"߳RS*M/,VyUk>ɰYӷ|޳}څJߧO -^YYjϪ>7?KʯVw;Kfm~MSbC;$+oⅧ>u{"}]ɏiÿ EoppOKu֓R; vϱ1 -y_ȗEx)(Nb]zUA &?ם+Zg`$sv\oܧ飘#[d\廛ӻ(_s{B䦑ao^Sh"( -h A+;pp^H IՄ - -i l#$m# яC101 -$b L\]E: WKbi[J! -B!Bn -2l4(2j?G!B!cB% -K)$CRȇ?:_!e?/Br` -fŢBBs8髼 OB gtBmPVBRPĦ9bq8֕NuǤss2WQ;?'(dru`>2ɑkU Yե{xMxm{ =I$s6(dR7䷖wA!g^BFBNB!A!s(&9-z_mmBZB!SC!BvlBz: DPP 00(`V`v. `>A2^qA!DCD5ID3IYrq"Qb٢B!e(Z -yh)t՟{*dlB. uB$|B!,#W!rrj`:q(q!WWfaPW7C3mCPH6['fˠK~kR -BδB!mA(0)sR&>2TL<sV!xM9A -*:`&`Q \ x| $d J/O[PH'Qe7Qm}+1(1sBzډ83L aW-.R -*jQ! -URߥri\ Q(d[2HOWcjqIdgB!|D!<Ϲh\iA^W2B ?މ2`"}<g1HJ+&%5BRbXΑqq-wKw+RH+%XQǼQ gszUNzУ}6~׵7 %7GLͽ>P,+-Em7[3l㧻e7eڷ(W~q2t?:Ŗ=sXƝ|ޢ(gG= J(8L%fh7O] -k "@$P%P5-=0#03`6`Nnp x $Uvy3LQ%:&l$R%Rb'rb. RDv M\U]KJ%\_2Q0L2q y!  r6HRil -Az%(CN AVX ٴ -7 9i{)G`G`і?  O}\X )M}q0U_1ǥp qƦ:i4w}^^B/\-?c T ?+ZވWNT;fyuK3^;AB)=hW)O PT[K'1BS)1ۑA*v ES : վۼ dnP4#lCi>Ar@ @ T@ 4@ t@ L X ؁0@Hŋ|a%Y DE5bju}1c+1[0H [57Xƴmk˘fk\ kA H b a2 r–1i 2w?P|A$! rd !  x9A*~bʫ3H4 R -|JT1v )Az dzQ+ qLlL4*$%ab kꌒg?Z>Pz >+r╥kU' ׫xmvZc0] IIR: 2: -Լ - BbNad\߾Mه(>7)_v# Lu #g הDHJjZz`F`f, -l܀,@"H I0H Qh)%>k'~bh Zbj"J #Bl5~\\KER[ 7N̈́o" *AJ doJ0Oa3H .e_i)q ?: > EB$i)^J 0ȷW- u}ծ? <|mYDAgVAS9?f OK#%I Ÿe`` -t|*s=* R~WWgU<^k -kGJϞA&`,y;T4|?5R=dA>AF`Μ5q_> ad 0ARE%7>k A0H A$a A0H A$a A0H A$a A0H A$a  G] ROD9. MP ]D?M ;M܍ . GYx _B|g[KC䷒AVIKy7wAnaۖ2ȱss A6IY" wJ {ƭ y Uک*_/ T %ZݼXliAY1Ib΅&&%U'A#n&KIucS[I >Q~Z EmC%B)FyW+s֪:׫kyMSw3[0HL% D:2 Ҿ{}l'2d@|$쿣9 redAVRr@ @ T@ 4@ t@ L X ؁0@H@xn"_ D1LDGԣD,014ha Az8'͘Iŭmd B0H2 _\`%KdA b ardA!2 ǡ;B60^ O% qndFP(Lټb ĸ]wa`Ԙ4Mc4W̹i,8`'W۷' Džuxxes*^]9y$^;tƠܼ^ l0Vt'Nv's -`K[~kۛ0Hn  RQ)AG1G(v>oӫ0HAd0 )Arsk)4@@@@ -X؀8 X/$D W'B -#zB[)_o6*!L UJ%wr@ @ T@ 4@ t@ L X ؁0@H@xAJA1N))e?)Mmt2SjJ)cNSnO9{)W&QK񝔯;1r)|+?emUK 9C"[j9B wHJYB ?[@9r"aI&A m~GȖ{x*P87&SD:E-#t?5Y$)lj LjL :D_{I~[ -D>!"%baV9w ڣ;jrxce2C7L5>s?~4JKk7[2IM(drU5P&/~bȸ2 t=xJI%M -}+ȋ]'DrDAg(Mі]їC+1S1g -Xg)[#*& -LRl1 -J-b TI!nnP &tt _Fw"I2A r2 _m ^wiZ {Ro K_LO6Ϯ2lrĥǢaS4p{eR;ݒ1RZ}J'o#(b(b&yxeUnkFkﺒyLs2ccIIQĜ8jt}V>[fk9,"=ʽ}S<b_qC7yk(7)OPČz65 (hD 'p7` 8<f"'`^h&1*#&q+fZ~vv&b%Nb Bj+aŭyeK! rdÒAL! XA_ AKdFmj(Xe2 <|A/AZos26}0Cs% ߓiC+ iXߑ*,~:R.ǟbeo~U0H Wg*acdabo~N=ՃRZ rbD ƞ>hx9•o -#WAz A+aQ]m{ rz)ڎ*Sf%[ (hD 'p7` 8<*D^Gf e=D]A4]fn 7C16S 17  AbOvIڈR4l H a$ {62 5m\ 1ȝa3ɰl) a- 9p*f)$pUz [奵k~ O}0z`{`:VV xbqÓ`Rݎ5\RRq9\F݀P[ZޛzU~T1EyEpᵪ7xھ -s;NT ubNL2u %k-K~t611,*5d{ULb*Z>qSm bfQ B35:@%?הDHJjZz`F`f, -l܀,@"H« y l'~n']wCXŴB{;qWqbKjPֿAa3/%XCD+䚰KXA&q)RE<b(dV2ȷB0ȏwo /Es ҄ -knюWH~/4e'X$.(`b866MMOc݁s BǙ:HSgltKO`yExe}kUͧx^3o#sao`,;d*9diAoX,M9AN 0HG0H~qhA ln~GAFa0H;0 xM9A -*:`&`Q \ x| $d J+X1JDYDTDC4ED{ BL bLu&4f@\i%aڇ%! b Zl0ȶ~B1KNٝBÞTRȻBf~n\'BP3P1yQ!wB! r(X6/<h߄'5!T6G'Ħ~6Wl8W!~.v=Z^=g$"_Bz:y[Shz.^Sr ^;0o[/˞`,Di|+\2co~ -)B*PHRY T@!PH&o!%yPPa(dsRהDHJjZz`F`f, -l܀,@"H?~R\ɑC!sDqFI!gɵ8liKrxIb?Aĕ'wfr$Ex_%YN9q,ae7}G%Bddvcs=w(DS&w_ߢA,: ㅧ>`;0șdH1t&Ԁq$2&:5:Keڹi(g 5?Z>.N uoʓkU[׫[yMQl]+}x RUcfix_yXF`~$ zRk`/fd\M0]/ZjoA^A)4@@@@ -X؀8 X/$D  2Ll Dq(OQ  +)b̖ !D\#~b! $Ala'HS& /2  A.\ $ ?ce jW2q kB,C sdBxqL"X܌ Ţ照(cz u?OI_d=ʕq!鬃8<\jjlt &5:2&wsNqr՝._TPL [^-ގןڣ Q E3U(cvPɅ׈r@ @ T@ 4@ t@ L X ؁0@H@M!̈́!1DQB'(U QM ]9g)X@L)sq;8Kņœc ŗ_R>(LJ~ԠW3fy}Gb^`;djN25ybə[>ES8*bZwu cb^@emo7)_PŜA9*&KxM9A -*:`&`Q \ x| $d y ;Tgs뉪W܌'v! 1 ! -emHl޽L^6K܌ 3$"A%dl )!Є?,y6<#e6|Y*Yb3.7jT1w~r r Zm_8`AhdG1æظ`\2H4cl "䶉-eTZf!؃ RQ*Ы> =wYmo]>K gn0N#e/]i9zu^ %Bه *dǑANȸ{k+e o - A*A^qd !nƔhn rb$ -bNk887W q+%J—]VQ#)$ e & upA6|y%K{1eC7n|I A'Pe sG;A*\A30ƺ]G;%b%6vqL*-~ -yk;L YE,oj))dc30I~n3 2ƚ=>SD? Wo c*>A 5 "2n:;p0HO; rj%)l蠔cjRP1JOt=~2LRQʔIANP A')g?DJ)a&)oQ] >iɓ S2%_^w_h/e=A4+A) 0H2 HAIw` 5 pUw /q} u07iس0H+ b])`b8GHF{ny-ebjyQ::؀*o+>1z>ՙxMxm]qb8E(Z[,~ǨbN͢BA3AT1„]5iBڏ*cT1s3bV1xM9A -*:`&`Q \ x| $d ʸ.dq"^j{h$g"vb La7X*aqa*x*f&  rUؖ%P! ^A"~qii=G!w-cZ%|B!6I!UBQܽAyf5hOO8oB1G_+ -~!ݬzgw";.N\91q6&օ% -IKq9,q@j;O!k3NRuyy(Ґ+&++^*xdz` -߲Vw`b - -9cϡ$[Ina"%=5(tμ+\K~ -zK){ -iBggd؉Ms)v(dM2ҾO2sv;)4@@@@ -X؀8 X/$D WK -)#1Mvݤx1NcŞQ!1}$'lBH}a+ -׮))䚯y@ȭW^l͘Ο}A]aG Ap !?ZA.á˘םGU(߿-^Z/pQNq#a}K WN7 !AF cwc7Aѯ -w×'cRd}c9MuӹKjJMְm27v0HoCmAS`s R4^W{ych!{/AzƸc#4.pYؔʤ:;ğc٣3 `ƈȲcvEo]Ԫ#vMvmZ]6+Z31ǘvn*_c)_2[#o?#s6=rJ~~sރ[;(N5[Q&ş>/n=%K~ܟE%”ԛ (hD 'p7` 8<xJQt3O))(}S  q2̓Nel'({\ݔ8cb)nNP)7I]TO>!qrޞgunܥvX菒5̕?®,DS?rvv5aXrx_~ tKk9a>U7-6= !וsCzLZz?"eWZ\ ĺ=1g{FIC 3zF>C^++\zͽڹ-kux±6Ou%jѨ1eZ[8j0%a&{sAug7hx2M`&jH a f)4@@@@ -X؀8 X/$D ]!qT@QֈgsˈhDDbCY315s…SⱘM$Lx6xZ. )}l +7&.4%/ ɭ9XWqL@y;.-ΑNuX|'sg2/l[GU `Q^QQ2!^?^{򩵺#0|c$SɁ$sۓ0ȩW[ -CG Ӎ0H' %^8UG1e G(;G )yvPkA5^Sh"( -h A+;pp^H yŠADC.Nlj#bqNjP܉gk^BUH-)g5a̽] -!$A> 5lK*YAڗl(9c؝w$D0A>_`oAWXL9 }' |IVAނAn諓` OQtޕ@_Wr"hy iZA qsD -M nA)nƑHe~wJlyZ3j^? rF~MEʞ/ yu˼& n oAzm0t&:;Y0H1o|vb  - ~d2HM.ᲇNgdtp]^g הDHJjZz`F`f, -l܀,@"H«y+{;1D5KD3BbSb8! kU3ډĽ~µo2pqSIQ0HP2: Y j(A&1ȟf{c1bҤ*F&e|ͫ wGl=gNAy1<:4mhݧ"6+rLJc#AX.Ց8\L;B&77f<;/VOg /!WY]5%әkuA7zL#{N-C~^ a0` - -;1] ye0l&o8 R8*\2HW- ҵiߵp4Ch"( -h A+;pp^H 7Oy…SⱘNq'R63HsRث* C⥗Mv+n+%B - JFD- ؽܷ[~OKpGs; 2*}br6ȕ #0uU{Jbh>c5OnT MJ<(L}9X2)LHu ^F܎:6INDRyԳ-NLUz+|j -=t`ܗLvuJ8i$O5 5Q̜0iy&_؉);j2HU=o{I%h݉k "@$P%P5-=0#03`6`Nnp x $mj/*!Rq[<_'$p 5bKj8)⌲JW3HdP^C!Ae jߴA>tU̗ 2"(e)}Y]AUL& '0Hصm\*FQ O}:~K br W*V*.b e0H&/ St_@_wۉt†_yb圫IMv!yDǤ uӅv4wەp :DF.1?Zްm0&UW+KתA5'ޏN7 [jܗd*ޛdF|XjFTLdH0H[A 2oAoA R i~Ix)۹pr@ @ T@ 4@ t@ L X ؁0@HϞ;Rz'&hb1}TI̝ Y}1b!©i!|/ _\I) qCo.#һ+ aL AT! R~ r3-#.꽴ֿdݍ>z݇w"Jq1,:4\#.0twlNO3$K{n|n"vO]bGyu3x薵ޢd?1$N2O sG-ֆwlYe# m=/r죘ڏ`'ߗqo>E;^n{n| 8 RxM9A -*:`&`Q \ x| $dA %dA:j[h cR9ᶇbA0E$\xo.2Hz,dM!4P5w| ݹdsWZI;`V HU̇!bn]Fy%FAE/sq0yJ0 OAޜ@'<*lDi]vs6=vpќГKO;R\rq1L=1ؓZZuF /#l>{'^A5rz'&T1M S~b Az-:ȎLڽ$KXi2H^QV 92H_p_$2HϑMކAdqd0p2Hpⅳ)4@@@@ -X؀8 X/$D4l>CToE($luٳ:[[j*]8>f`Gu\ު\DŚ`!q CrCAyDh"( -h A+;pp^H %͐QaVT^ct&`5M~0YͶ({䲸͌5qNLK(zI P\TB^%U,*,*vYcǯ:W /%W,q !9auUN(D8%ux 9po!Rmgt;k NpV8^Zs,hۣ~+9ɴ_V04qL+֑v;iTOjl?'^LV˳XGmE(vWޏWL+֪fOꉗyMx浺H=?1u%]IHsApīJpZ"s*2*^A޹[bng*v#U5MxM9A -*:`&`Q \ x| $d ʬxZl.l"Xm+ +:DlL#~qzq-7xq+ nbе!NP_s]+rKN>?G!.ֆ -?Xѷ:aYFazeK Z*\9bCa[9RW{9/<ю'N&՘H'ćXQQr6⢣#M86:bccnWJywHUlOuI]#4 ʦ wyu_yMڡI^Mn0nBvdɁB,~k -iW -B(~ 6B6yvA!mST|\(sQ)4@@@{|s cne1MC)Ƙ&!C8g!ϝu홛|lsnC=-VZU*""EXRRaecQ{R[1c||A :Ѓ`3`;8CW"[hŌtnQGh[p;i[6GO[#gaFM:,wp3[/-!7oʼn:!Xdlj -"枤,Lnw6 [<.ܵ$ ;x +q}DЋm=]5q}9#-d}#L~B 231cXG.9ܙlnf^'ĺMGM VJ>w]M|Z"dيײ#RU3]@|̠F#xvcQg3P$!9$HAqlKH߉7!A BtE =qbi$HxY$HcVLڽ ǔ ɐ -P -Ԡ-@0 ̐ \8>}DAj1Aډ(gF)Ah (1 1#枅#R/E:n[MMeGEHnIN8tX ${eGZ<k3ɋ%;8a ֭7GyTῴxX{}&$+/꣝9J[ŭ5H]9  楧{\tw`Y'w{ґ%%Mb{ַZʠ4;YL{(z9'؟".bf'&桅`fmH@B]M^#|u_=*бP,A~G]~KvG]>] iIبy/#N6+$HQ*;QqlWQk<^U[:V1oԫ;!J8\\e8y \FȕϦf.ikzufW=n"AFJJ^Qz&[ٿ9U[5!?PvcrLeKO,Ak2[x[2{Ib -'&Y$޶ G(_? SS -@ǔ ɐ -P -Ԡ-@0 ̐ \8>> xerˎ;]ۚd\E=@*xb8Hޖq{wHl_MPQ<hHPT hAz0L`4l`'0x~u "B/@ LTDF4Ct ]+17 $!GZŴvR[L=7A>p}$ 0ZA~g.eA;$Aqi%tULh5OGi*a;j*V$V$v 5đ'Q*"V1_F ]9h ?`ú2B|. - dG ︹Z̐Z"K.;"n(w׭Vc f.[M%eM_57l:Kdik,U'koDސهKd΁)PMPl );2nϼ3g_D RHJ^P¹QZTthHPT hAz0L`4l`'0x~(D^E芅%DY'%N;\:nLw^8,!*0-RXԨxk1X BId8VI AB]>H{Im%t}/d:-f%~Q*e)^u,[YP;ʫwek(Jm{Wbn7ֵ;g Z[ŔDlvbfʎRQl5wB<,7شw A}I }wK?(IcʁdH(AjЀtfH Xvp.` x@ɣm ݾfZM#DK#A6ni1S1GV144*K JǖK RpGn,A8 $/?t}e|#zI D F~D'Ai]QN$ȭ[~ݳ R.؟5w{iift_hߡmXd -ЩWN "~M*&)@\ :Yy,B$+_5JJwVE GxEH2`x(?FoZ?5 ?}q;4y,<dR5LG"AZ {#A:G RӲ 3ǐ F"cH Ň)!AZЁ `! ,` 7p|  >Jz aAKTj)k$B XGL]&&)(a"@Z[L\qѧ-&9N -d ?\.AN2407%t:%&N|g Nzm&59/7>نAz>F rʩA2O~6 -er'we%5Hゎ] /\+oNjeEEnͫUs꒗=Qijݙ9Q%|g*( >Ʋ֝M22{#2gۑ9@:ġA]=-Ƣǟ|%РРwĝ;xL9А )%@ Ђ`# i`+N`,/_SIo1- ;ՋÓf' fo&b%ƯĦNixr+a{ԔH{y;k^KX($H -g/֔^.B.)1&5NcF5q"ļy+_򛍈ō5F7n2&Ӷ?ghuIA!+&Lwt幥vB cSKB*׷<,5t^%-"+|W )۲N5dzsޏy}85e6c9*!;QG-!=O9ԠY,cSz[NAsEHxbo=*!M 1@C25h@ :Ѓ`3`;8Cn~jа4]k$>Aq -=<!xL9А )%@ Ђ`# i`+N`,/QFm  -&R[b>#FH(KUq vم! -[|6Nxbrkl!& B9XsٮFH6Qt})4oE7 Uȍ m߼q1B!sk!}ь*$g"@ʹ2G 9,.(+e? 2An]TqToe))LժSk*u?-3D\"f"KXƲ}ʰ2!)^1\ؒ{pEELh8(7Fe2c  tXH )aAaAaAaAaaa!-,X‚5,‚=,8\a oXÂ/,aaA%ɓ$"II$mS$c),^;dM%ٓIL+Mr/^/k'RbwpP7}}M#}V/ލ}/>|j?6/r7Y,;{W8g'VEw9^罱ƿ,i~_{\5凗߿x.ͷ }oYb8Z}Vɋh[ ~:iz+++ qXe0P,#+MwdXW(.jU?'/6M.PRۡժzGavK;ec> Sioy8zoZeW#[eji9[\SD^ȸf>vN>4'6Q]PgI/R4$C -(@ *P=&0CX -6p n <@?@tGRTQZJM6SJBvSJEY([-eur7QvQ|U2U ^G%]#[Lkb·w@(etF>ѷ dɂX`t^g~ﺾׁ5F k5 -6@o_}}OkwW~8 )3ra=qx^̝T[wY1i,,I !$76m6g.Ʀnu )""""EDD!""""")EJ)e֊X{ wϯ ,9zΫ[uޟ~)^h˖-͉/Mt-`Ưneْ%p27]oI}o ƌ-:-f͡fΕ\~nmD[bBu6WӞp(t;, 1AőmDIJOOb tׁ~ی[k]ufq+JUmwnLCŎ&K%K;dYȇɊʊT5/"MھP -Q|P;0vLE'C٦[P!1_~r'bzvz_b(R7:BRS[rxM1A  2  -*: -l ,TWzlm$N""b"&& (Q,6]+O-Zbl#,bn vb&b'^O aۈn`YVƗpe[NGn[hbe$2WRLfYJ.{k+ҵŸSk__itWzx'2pgMTLmR[l;rP:- Cma-^j⧶ҋ֚?_}V/?Tx~fϪӬoϺKRs*?^mVYgUuZu^rsUI[Y|q+aۿg^9-NL2W8ᡕYzr/m=SMH537_Ai@:nIK -[A{ nB!w-xAڞXPU<^} b YI8Y>V>x8~$YٝZz41fm)} 5`X<06"2uyAlemɿmr?~eg>|V =(]S(  PP Ѓx`F`f`6` xA*^e HtF|(Ɖr&zh! 8@Lʋ.ө0nai iiI=q'dXێ48=: Yd8gGvoGAr 2 ʻ>MV,ȉS<˪!-^-N }}CDX4e^d:x+2b2G(G] w> 2; R^!W v xM1A  2  -*: -l ,TT d%D:@dG|(rh@+'941M1b'Qb_%V$AAA^; j7 ?Ba<+Qo"7Z?A$bޯ[ &K01T1_D*:l?GO{3PŤ;I 9AJ].p%ڜ [T Vꏖp-D.4Cwb>$_ᒜtJQzF\ré\WZ]"M[6xu^}5xGƜí"SK }03a6FsrE9 Zg~z1L#5Q֚0|ON>@5ŀ1 HȀ(h00+;p'` |R*MDH =DRADNDQCDUG5DJ-DEAZh FuEA„m$ r^TJnIU0]06A | Pn#; ;#`2 & "d&2kD6DeM]y$T 29 2q)qJղ\NjUvK]q727#^]ˀW_2HAsA -#,qddh/2 c% dzB$AAfA xM1A  2  -*: -l ,ԬH4ʉ(V)e#Qum/ѕ -i b*WRxG5q6xGD 2ȅ ($C0"G7%zoH!/ -gYBGPuBFPO7B~*.roP(c>75B!M2($0ࡕųS|(?}G9&KL8ܡDKm H -zSHU%#}|?0>S:xL-\qɗ\ԪO]ꥻ׾E,>cmZN!\)k8Len( @PJ=RǕ1wA!C!E9dq. 9IV1| *'bm%R 'RGL \HEVEQL̈́iCH5Vl -BA! -+$6Bo",^p6\J:  ͂Ad !$.f/7a-wF!u!`u !OB,K[f1W ϝTgb+BH05! 9 ƕ< Og?oTuxr[ʘP$K oU {XEʪ8ձVݿR/_OѵJW>W!_70V}ڋѸȼ2zPdh+^C{_3)gGE;;=};̫vf v0J/8ĀX R rJjZz L /?Hܫdq9I !D+h|m!ˈK80uSF* ` r}\!Z!#$f* G !o& -gOP"($a7&B܄B*o­*{Q#[ Q@!WgSڐR={ -|trwK53 -q]N&AnvcIH4[ZyJHYs19[͟ݢTl d|o4C1{x'ޟ@*<ƪGB~WB}EScE21(dS}oPH~%r+{S*n3ŀ1 HȀ(h00+;p'` |R*D\VdI+!]Dίu+t -#NN!ĘCL1UBc*)1QRQ^HE;6R (QS˂B>+A!Bbrmc^Ҧa^_LgP ? -iIcPHG?BBf},f\W͒ -2=# PB ̩ -)ϺcDB9[pJk&kϺ\;&8R7c13;5ruLGc -:ێ)~uGc:fèc3=Sϣ)CЃ:fuL11}S'  PP Ѓx`F`f`6` xAj:8%._[L-%'Fh%IJB -_ -R@+RAV/')SH)GQʪt -A!՛PH_ldwr%!2 !D!dl 7toEymIY7Wav+ꘃ_ArOee1w- qANZBI [%ns[B!-)-љvZKH]}fsq!rxrDuL^3ꘂVrt:Yz[Vޔ*ʦ9qnV=&BHW5څHuLl&BHx -!duLkc: -uL];꘥gvzr'BrPkmO:l|->S hbH ȁ( -hA<0#03px  p&21DKdED&,'%Z ^ 2L4E1A݄) AʈVBBA & b;ߖnd!gZ ZB4 ] OGm*& 2|q1Jms0H" 2_yC+rO_4+~T:G8 Gbr:ҐHP HLsܮ2HσC+A>6S0`}dP!/N6^C޼ede= RZC=< -|pvhO.gox7 2w`{G2f/ T m\Sٝ0Hu {"); ,);AF֚b@ $@ -d@@ T@ 4@ t@ ؀80X>{*"1| n"/'0cjhډvu}ZKH1us58 Sŷ60ȅQ `|I0yAt M.zkYDO d5}<`,[AFA]G9Jٺ1ˋ;=+\S02[R"HV "HGĀX R rJjZz L /?H-?Gij"zWj-E._tu8f1sN1MJjag]|v!|!A+ 0ܟod=1OQ g [#$ yYH `7@Dߺ=rs Ag 1~1Πod0G_!5 wŖX AK $9 `i}o-qɯ*^N~[S4O.a% (bvɊsw{Xde g`k/_Jd7CמqI0a`){N/ vpr ==LMS)*)Ab@ $@ -d@@ T@ 4@ t@ ؀80X>um:Hk Aj~3H;R1vR[zi$[H -۹E0uQso܄Ag)8 }8  i rA|}{"[9n0W 02HW< 9{G{x#jo>9wsn&X 6Þ`lK-HLpgK2_ 2yVq^v ] #dlot5+N4ʋoA& Y /N /|W՗+z5E"db`(_AꞁA¥0H}0 U0H0HaӂiAf:aed.n R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R R>CN"m3H/R6NT|i'6[k)"Jb*#Ab%~77O+a+w`#B!|1A\&`M+?9|,LuقAEb!-qn 9rѾ rzwb73Ga>-~Ndي 3LbOH䪘 ,;Nt'V,i6sDUlbʞbg\N?Բy|֥:TVWuGiV xqu3"ӱE款WEW)[K͔lrTD9s3=Gi{¨bZ)_X;R'zNn5ŀ1 HȀ(h00+;p'` |RZN詵ɘ0NY/SKc !DG#D7N}k[1|(N[OG7qxٺ3H= uA΋`?m /}hAyU)"D!I0Ȫ`Yf7skA:64{A~ _7 =U,K[/FAJo;M$g%!`wtlΤ$vz9ZY*As 5%\S YIlt}lb{."!0Q& 2(QD9Kij@yv 2o"A~$$3A |m$!޲k2FRۄ 1}QA~ ݠJB% -yCo>,zic>zU}=;0slŸ]PuK -qwZtd8 i;TTegq}ngmsB&rdP77N6 -+UL&+sTz| - \LNr}G q4S(reCCCP;PPHNBer}@!DzP(dd_;^S hbH ȁ( -hA<0#03px  pRK_ͭ%A" b#I~2h:~- S 1*8ډ! m!ޱ -n6A! -DPg -yLve*N1nN !h2g6c"$vn0"BU`?na'a0 r<eL}t~ޡ ϝt0 v1!6K0JUrN7v]c1w-RKLG?ͩHR1xti|Yދm-)[>_?t`0AӖҶX7pb}= u=cסdbWk~uggΩ'T>zglO-e-/IKI#GN hbH ȁ( -hA<0#03px  pgqq' -$E"iHV!"T7E?ioeE"VcT!2犬"-["g'GG1%,,ZhʅQͩ7U8E?IA7*}r׺&^A}ڏ7ĥVL}ЄA_v2tE&VrOc_BИ.KKmNXFq=*A%Ȱ$%fI DFpkrϚ[ק:/7BQOQ.NidS{]b#rmqnRɫJ[8Օy G -E}u^֘U&2-޾GdxUd{3e/oЫ_< B!/\k\(B? @!s/;V0_y -iy -i_ZJE(dvBb@ $@ -d@@ T@ 4@ t@ ؀80X>oKצ_V(i=UyQt0QUu?@Kt D)$)^ W+R*9t$-1Ir;@ =Ⱕ9,7w²͞hs6)OB5 %gR* _[rJn&˾R4;KYTC.0HVxu0H+wPVRCǵƕ`a.pqG J9zA`fYXm G`' ĀX R rJjZz L /?Hk ]^CBꈼ(ʈ2z~/ R^A|or,#L.i@䒨G~  "_^혻3}lx۟mi F0H`j IDIhs@L7~y -9D aKoo޸`r$rd$,iv&`q$q-In#))uLiCCd6~m4*>vDWUbGs@%%KW5gYrbb_riEyUWN֋4}W/E?B?m04=` E=mu`e}`r>qr'bO7Ri]CF(q(:塕q1 ,ފ |" s Lzq۹$4әrBi!~ռȵ},~ATO.-jD[K Fb 0M 3_0zhG0ʒ7k/UvgAB7T } m r߅0HK[~#O=e]tW¤,`ܖ`BbbbtNͺwڸ*h%cF(T1.INiqlnFTTN:U]RT1E\յ[u/54xNT1 bfGPŴeUD!9 -GQ q׾ǭLfxb@ $@ -d@@ T@ 4@ t@ ؀80X>Y'A> eKDH-DUَ3 k/CILcMRj9q(A OCg׼˨w -`3H#KAݴA^_AAF6P dk6a#*@= >|0HT1UL I0#ܛ -{}:s7H[ -d!Dpt}+kgOVӨf[`c NKѭ G* Cdݐ;ݐ$ y!3ܑG! ABq = , :O)4@@@@@@ X ؁8<^~:r:* n6yD*h - RE59zʘjXRF<Մ- ޜ[J(jG# sRKa!dåԽ?L[!?>,A0G0HO> j\7_Rzz(c6}72t=0Ew$3\cqpW[!fKHϰĤS R[YC EMJ! 2 ~'KV5¢ìeL'Ucwc)}=0HzA)C)A^g  2W Mܡk5ŀ1 HȀ(h00+;p'` |RZӺvjY+yQ ea?HtUD_VS#߼^Jl>Ec9L6xgW<&D !$ n 7v3ܰy@gj)K_>} ϛ0wocZ /0U̗tf̋ Q~rOQ\aA+;Np^tL9`0nIe8- G:Rdq1I fVL=:dt?Lt0HG J+Y*+dy'+ ];ͯ \]؛[Boo4rAF^AV`Fs0H0H>2HE- 2))XAQ -:a ' ĀX R rJjZz L /?HD\G艵͘z"*$l V.C-o ď -UEI]AJ`"d`S*8ao 2u; -QC& oőR rY WzCYT16n)u.{*溻`?F9X{Jp&&-L(1dq8vKd2&dKdl5f?􃩩/\J5oYTlKU"+D5ax֪({Ǣ}qܪ>qWHS+ I]Ioy0P?|JDȼq]-]=;(GRκDL[?VI#Fy{uO -ԙ{'  PP Ѓx`F`f`6` xA*^eOQt/P6Iq-˔lQAJ9Hf( nOpgvS ʴJ[(("죘,2Ϊ/G?7sU" lnj*zW{ÿϭ? x%B)QWes(h| 1m 5-1`S,K[ߐhob5j*@r#t&]gR `q8'j3$%%Ct={-x[%|Dv  JzYٱaV>pXx8Yپ_cǑ@F;9Oяwp3Cр@#82>wT!$'P,3bFYHx d2.^S hbH ȁ( -hA<0#03px  b@21 D$v9jq$%&ns7lHX 6sغDlsCbg r1[(.*?oTE0N ߉M$/i֍ 2^Zz5]@H Mn 5q; u=64"[7/` 7=E) #~;ϝI -&\.K;A,`ZpH&[veL 2TU.AA sdI˾diςF6kW'&+4;i&ۯVNfR%1/8$2x=@٪G߆AFN sYf0jf5cd埸u*DI5ŀ1 HȀ(h00+;p'` |R*DԞ]s`P0E &2ȶ-g8pC|;a,vbnڄA/\ܼ[!u\ W1Aa{ꥭ~'7qB I{0aq,LmZRk3'_A:ʶ{]!݋ 2Jgd9;5wdJv*+z0wa5]P85Tqudfdd] #ds Nω7GYAFYh宕zi%6S hbH ȁ( -hA<0#03px  U1 G$`]눢(j? uh[kW[S'1L{a(;1r"D ? VRc#W0sotЖS rk rGԎD  "A pMAn\_i R+@3`^<هVza;ӥl.gȒA e,iۖJVń3ƯofokAVaXdqܻvs{XT28*+NusAA`y}z/  ? A[`܂>=ȫ 2|VńQńQńQńQńQńQńQńQńQńQńQńQńQńQńQńQńQńQńQńQńQńQńQńQńQńQńQńQńQńQńQńQńQń/k#tgf""F";RܓG„ H|?Y;_4{UGvxKϮyV0 A>/d*A~ dz,fS d^n#a/Fu,?6a٫`m*fcA&Gs#:Sv_麈yXgbz;Fgn  A B"MAvsaG3L%D _ %A~ tdn~`9=|%2Hk 6{-K#9~7ϝ*&dcwIX'A-===)=8m*2v?S 2Hvz;f% ݅qZV1i"NuU7A:\ٛݗ/t0r't{`Y mcrsus qymW*(R ,  I5ŀ1 HȀ(h00+;p'` |RO =֑yQ"Qu;4󻹝kǃz3DYCt]ڨ/0Gim6Yny]'Qݦjv_}Hl'˧ -}G /Ę!2 <^ug)[})Gac?ln1~YbNRٺg))KTj?PxM1A  2  -*: -l ,Tq>TsiWR;-jf~ $f 04K|Az e*&0Y[Qϕˣ? b6bިLŪAf#Hl ?Bs& }_ d \^*`{\ rm=K_/hf_sǃ|ܙKt!h-鉮4$m Ui΄Ù2 ^ټ߇ pkq)4@@@@@@ X ؁8<^~p^D@Q cDRGC1SRLjj% a cĸ@L1sĶ@3\:>LoM\u/ {Vl 9Cxng:ͨgި-eWmB!Z6W(>65 -{PH[rl7B,K[/~G'OBD9w Bnnqu@%}XngliS2~of`bVqiv(>($ 'Kd#vȏudeÝq(dk})}\S C;'7 -+B+9.`r~OĔ>TOK"(sz\  PP Ѓx`F`f`6` xA*XwBsRkȻS[oYX[ b(kOjI%L'L !gs$jKYBbGPȥPȅotP|vgF[Ku )$_btѷ1ۡڠN0t?]LSg4't뺮6k:JmáNKMbmzk}$ےlB8ǀ 1źnjcըiH!цՊZѸ@, qs.Qnsӄx<bSD -QiDHa!kfٝl.s=XTT VVOќ3{VvȖHUS((\W+ӿ.TG,+jB6!tfm)֙ot-9L9RlΫN9S)AJஏRCW(ojUw&U;sZ[ʀ<9(@ *P=&(3X -6p 8/ E sBl;M "$4$6EtIOCOSBl'^b%qtvpq |)NOxOG>=G~ǺȖ |X>wJC-f([[hrA|r++y6R}ć?EIqd[ -D_?E}7絘,,؋Ҁ)Ku|]B8auֶjHHî+Fj+T uQL1^.ϔϖi2˴]FP^kH*Sx3}l*-C@nè@&@A2yXDF)!'ֳ@ mXFQ9,ڏ) %@ Ђ`#`+,pp?TS2=ڣGz!F҅0m:"V Ҁ.(&IaIt$MAVA&0_m@X<̊/.0寏gy^)3ra=qx \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/web_sales/metadata/snap-7836568010808044048-1-416a3161-5ba9-4968-a394-a2eb6e3709ce.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/web_sales/metadata/snap-7836568010808044048-1-416a3161-5ba9-4968-a394-a2eb6e3709ce.avro deleted file mode 100644 index b4827102a8fe9debd45848f77b93e142c2f56fb8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4308 zcmbW4&u<$=6vt`Ap+adTq*6gz%4pR@YGfz7iJiC>Js?_y(>O)ANTE?>CcEQ!(R$bH zkGP?VP!T62L{CK+i33vM58%g%L&XnKezmlM^vnUMk^>wOz3^s#%*=Xs9XpB?$?Mto zdGp@P_kDeC_0@d`594*)nsU@8Y2go#U6$O9Hj&DbO;pR)q?}~p7X949rrscqTW{zF zfyWJ<7@8xMS1u=3w6WWS6=5L9wRLk1K5Lc=aTYBVySinfmtL%(j_x+mb2a4CS=79) zN#&v}Z|0J*4Pvf|(G5$8ecp1hfoOur(O)M>H__^bOPna(Ft8#7Ze!ckX+CwQP1HJ& zS>n3svaDR<<9O(S&Yep7=g zDMf+R(xE;B@fNKP91;{QjMpwb(T%D??^nv3F4wb`2X9ATz4iIVJ>DErLtC zBAK3HNT5RDCC0XmH{ffy`z??@_Pkds?V?)%Q6Ty;X$C0 z=WP$ftAVh&5vW;{-ch(qhJjd~XdN3~uLhkI&LW5Tq2qg!BId>2px8`dYNUZZ!EZBtRFo#_J>=Y`j9?*Z(; zH9T0^yttW@;I_3uB*Ga}+GHE6d94-(?VwvBSZ(rh_T7&>4`C1a!&$X7q8@~;8`vd> zBW(oI#C&OXt~ghc6}bd|g@Rlt9R*GcyZLouJ22NqW{yQ-oDXKeh;md{35<-jaO!V8 z#ZEE|Yfq(PFYp8YX5`qg4`*}I7lc#ahB2YrFipx3im)R-gbSeOC!i=v3Y-5KoS0Z; z`T>rZVF-4y1SgOI5r=uYGhhH^o&mTg7$EF09)ZJ(3>NjAyMqj9tYEbv+ybZ0F>1O` z7=%2aZ(vY)mO;}T!yw4o01krl3O&4(0iOXCPfOBEZ~|{0o1A-tuyHDS7S7@AiYDpa zFwuBTr6~Mj1^tOl>x@dp&SJu8upKD8_pDMwhi63++yN)>Zbvm58W5EyExycEfyoY? zZoDoHY&c9knLHcdE@4^Es9_HTOTvU)_?;Gr>PdExxj_ir2&c!SJ8}xU8HF@=!Yf)z z!TP?0Q)7E(24y#Fia+60m|fxT*SHnQgz6@YYC4zwAFG3S5)R|zDsDf9hGqMZnA6~K zX}`dN1WEVt8}RyZIB?Xa2rgnjHE|E^fBMX$`wo>p{rj!;tN%W@>WthR*};=@U);Sn zJyppqOy(!2?o=0kK6~qCHTT2RR<)9={CazFsk)F~xK}Lxu<|ePq--aA*2l_3i1UhZlDK`uN)MH$U$_-W?x1(tTpPJ28G?ySv2&tCcV*4l%2*Ps0I`%CY=OgeC4arMB4uYNP@dl diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/web_site/metadata/00000-4a750e24-e3e7-4e51-9edd-1fdbf0e97c30.metadata.json b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/web_site/metadata/00000-4a750e24-e3e7-4e51-9edd-1fdbf0e97c30.metadata.json deleted file mode 100644 index a0c8415b15f8..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/web_site/metadata/00000-4a750e24-e3e7-4e51-9edd-1fdbf0e97c30.metadata.json +++ /dev/null @@ -1,192 +0,0 @@ -{ - "format-version" : 2, - "table-uuid" : "0a9f7276-47b8-4877-bd54-714589f33a91", - "location" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/web_site", - "last-sequence-number" : 1, - "last-updated-ms" : 1678866199751, - "last-column-id" : 26, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "web_site_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "web_site_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "web_rec_start_date", - "required" : false, - "type" : "date" - }, { - "id" : 4, - "name" : "web_rec_end_date", - "required" : false, - "type" : "date" - }, { - "id" : 5, - "name" : "web_name", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "web_open_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "web_close_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 8, - "name" : "web_class", - "required" : false, - "type" : "string" - }, { - "id" : 9, - "name" : "web_manager", - "required" : false, - "type" : "string" - }, { - "id" : 10, - "name" : "web_mkt_id", - "required" : false, - "type" : "int" - }, { - "id" : 11, - "name" : "web_mkt_class", - "required" : false, - "type" : "string" - }, { - "id" : 12, - "name" : "web_mkt_desc", - "required" : false, - "type" : "string" - }, { - "id" : 13, - "name" : "web_market_manager", - "required" : false, - "type" : "string" - }, { - "id" : 14, - "name" : "web_company_id", - "required" : false, - "type" : "int" - }, { - "id" : 15, - "name" : "web_company_name", - "required" : false, - "type" : "string" - }, { - "id" : 16, - "name" : "web_street_number", - "required" : false, - "type" : "string" - }, { - "id" : 17, - "name" : "web_street_name", - "required" : false, - "type" : "string" - }, { - "id" : 18, - "name" : "web_street_type", - "required" : false, - "type" : "string" - }, { - "id" : 19, - "name" : "web_suite_number", - "required" : false, - "type" : "string" - }, { - "id" : 20, - "name" : "web_city", - "required" : false, - "type" : "string" - }, { - "id" : 21, - "name" : "web_county", - "required" : false, - "type" : "string" - }, { - "id" : 22, - "name" : "web_state", - "required" : false, - "type" : "string" - }, { - "id" : 23, - "name" : "web_zip", - "required" : false, - "type" : "string" - }, { - "id" : 24, - "name" : "web_country", - "required" : false, - "type" : "string" - }, { - "id" : 25, - "name" : "web_gmt_offset", - "required" : false, - "type" : "decimal(5, 2)" - }, { - "id" : 26, - "name" : "web_tax_percentage", - "required" : false, - "type" : "decimal(5, 2)" - } ] - } ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "write.format.default" : "PARQUET" - }, - "current-snapshot-id" : 3658900633663111316, - "refs" : { - "main" : { - "snapshot-id" : 3658900633663111316, - "type" : "branch" - } - }, - "snapshots" : [ { - "sequence-number" : 1, - "snapshot-id" : 3658900633663111316, - "timestamp-ms" : 1678866199751, - "summary" : { - "operation" : "append", - "trino_query_id" : "20230315_074318_00069_9f4mz", - "added-data-files" : "1", - "added-records" : "54", - "added-files-size" : "10855", - "changed-partition-count" : "1", - "total-records" : "54", - "total-files-size" : "10855", - "total-data-files" : "1", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/web_site/metadata/snap-3658900633663111316-1-fbd8e2b4-78fd-47d7-b90c-1c52639f75b7.avro", - "schema-id" : 0 - } ], - "statistics" : [ ], - "snapshot-log" : [ { - "timestamp-ms" : 1678866199751, - "snapshot-id" : 3658900633663111316 - } ], - "metadata-log" : [ ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/web_site/metadata/fbd8e2b4-78fd-47d7-b90c-1c52639f75b7-m0.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/web_site/metadata/fbd8e2b4-78fd-47d7-b90c-1c52639f75b7-m0.avro deleted file mode 100644 index 844ba461225b..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/web_site/metadata/fbd8e2b4-78fd-47d7-b90c-1c52639f75b7-m0.avro +++ /dev/null @@ -1,8 +0,0 @@ -Obj schema{"type":"struct","schema-id":0,"fields":[{"id":1,"name":"web_site_sk","required":false,"type":"long"},{"id":2,"name":"web_site_id","required":false,"type":"string"},{"id":3,"name":"web_rec_start_date","required":false,"type":"date"},{"id":4,"name":"web_rec_end_date","required":false,"type":"date"},{"id":5,"name":"web_name","required":false,"type":"string"},{"id":6,"name":"web_open_date_sk","required":false,"type":"long"},{"id":7,"name":"web_close_date_sk","required":false,"type":"long"},{"id":8,"name":"web_class","required":false,"type":"string"},{"id":9,"name":"web_manager","required":false,"type":"string"},{"id":10,"name":"web_mkt_id","required":false,"type":"int"},{"id":11,"name":"web_mkt_class","required":false,"type":"string"},{"id":12,"name":"web_mkt_desc","required":false,"type":"string"},{"id":13,"name":"web_market_manager","required":false,"type":"string"},{"id":14,"name":"web_company_id","required":false,"type":"int"},{"id":15,"name":"web_company_name","required":false,"type":"string"},{"id":16,"name":"web_street_number","required":false,"type":"string"},{"id":17,"name":"web_street_name","required":false,"type":"string"},{"id":18,"name":"web_street_type","required":false,"type":"string"},{"id":19,"name":"web_suite_number","required":false,"type":"string"},{"id":20,"name":"web_city","required":false,"type":"string"},{"id":21,"name":"web_county","required":false,"type":"string"},{"id":22,"name":"web_state","required":false,"type":"string"},{"id":23,"name":"web_zip","required":false,"type":"string"},{"id":24,"name":"web_country","required":false,"type":"string"},{"id":25,"name":"web_gmt_offset","required":false,"type":"decimal(5, 2)"},{"id":26,"name":"web_tax_percentage","required":false,"type":"decimal(5, 2)"}]}avro.schema5{"type":"record","name":"manifest_entry","fields":[{"name":"status","type":"int","field-id":0},{"name":"snapshot_id","type":["null","long"],"default":null,"field-id":1},{"name":"sequence_number","type":["null","long"],"default":null,"field-id":3},{"name":"file_sequence_number","type":["null","long"],"default":null,"field-id":4},{"name":"data_file","type":{"type":"record","name":"r2","fields":[{"name":"content","type":"int","doc":"Contents of the file: 0=data, 1=position deletes, 2=equality deletes","field-id":134},{"name":"file_path","type":"string","doc":"Location URI with FS scheme","field-id":100},{"name":"file_format","type":"string","doc":"File format name: avro, orc, or parquet","field-id":101},{"name":"partition","type":{"type":"record","name":"r102","fields":[]},"doc":"Partition data tuple, schema based on the partition spec","field-id":102},{"name":"record_count","type":"long","doc":"Number of records in the file","field-id":103},{"name":"file_size_in_bytes","type":"long","doc":"Total file size in bytes","field-id":104},{"name":"column_sizes","type":["null",{"type":"array","items":{"type":"record","name":"k117_v118","fields":[{"name":"key","type":"int","field-id":117},{"name":"value","type":"long","field-id":118}]},"logicalType":"map"}],"doc":"Map of column id to total size on disk","default":null,"field-id":108},{"name":"value_counts","type":["null",{"type":"array","items":{"type":"record","name":"k119_v120","fields":[{"name":"key","type":"int","field-id":119},{"name":"value","type":"long","field-id":120}]},"logicalType":"map"}],"doc":"Map of column id to total count, including null and NaN","default":null,"field-id":109},{"name":"null_value_counts","type":["null",{"type":"array","items":{"type":"record","name":"k121_v122","fields":[{"name":"key","type":"int","field-id":121},{"name":"value","type":"long","field-id":122}]},"logicalType":"map"}],"doc":"Map of column id to null value count","default":null,"field-id":110},{"name":"nan_value_counts","type":["null",{"type":"array","items":{"type":"record","name":"k138_v139","fields":[{"name":"key","type":"int","field-id":138},{"name":"value","type":"long","field-id":139}]},"logicalType":"map"}],"doc":"Map of column id to number of NaN values in the column","default":null,"field-id":137},{"name":"lower_bounds","type":["null",{"type":"array","items":{"type":"record","name":"k126_v127","fields":[{"name":"key","type":"int","field-id":126},{"name":"value","type":"bytes","field-id":127}]},"logicalType":"map"}],"doc":"Map of column id to lower bound","default":null,"field-id":125},{"name":"upper_bounds","type":["null",{"type":"array","items":{"type":"record","name":"k129_v130","fields":[{"name":"key","type":"int","field-id":129},{"name":"value","type":"bytes","field-id":130}]},"logicalType":"map"}],"doc":"Map of column id to upper bound","default":null,"field-id":128},{"name":"key_metadata","type":["null","bytes"],"doc":"Encryption key metadata blob","default":null,"field-id":131},{"name":"split_offsets","type":["null",{"type":"array","items":"long","element-id":133}],"doc":"Splittable offsets","default":null,"field-id":132},{"name":"equality_ids","type":["null",{"type":"array","items":"int","element-id":136}],"doc":"Equality comparison field IDs","default":null,"field-id":135},{"name":"sort_order_id","type":["null","int"],"doc":"Sort order ID","default":null,"field-id":140}]},"field-id":2}]}avro.codecdeflateformat-version2"partition-spec-id0iceberg.schema+{"type":"struct","schema-id":0,"fields":[{"id":0,"name":"status","required":true,"type":"int"},{"id":1,"name":"snapshot_id","required":false,"type":"long"},{"id":3,"name":"sequence_number","required":false,"type":"long"},{"id":4,"name":"file_sequence_number","required":false,"type":"long"},{"id":2,"name":"data_file","required":true,"type":{"type":"struct","fields":[{"id":134,"name":"content","required":true,"type":"int","doc":"Contents of the file: 0=data, 1=position deletes, 2=equality deletes"},{"id":100,"name":"file_path","required":true,"type":"string","doc":"Location URI with FS scheme"},{"id":101,"name":"file_format","required":true,"type":"string","doc":"File format name: avro, orc, or parquet"},{"id":102,"name":"partition","required":true,"type":{"type":"struct","fields":[]},"doc":"Partition data tuple, schema based on the partition spec"},{"id":103,"name":"record_count","required":true,"type":"long","doc":"Number of records in the file"},{"id":104,"name":"file_size_in_bytes","required":true,"type":"long","doc":"Total file size in bytes"},{"id":108,"name":"column_sizes","required":false,"type":{"type":"map","key-id":117,"key":"int","value-id":118,"value":"long","value-required":true},"doc":"Map of column id to total size on disk"},{"id":109,"name":"value_counts","required":false,"type":{"type":"map","key-id":119,"key":"int","value-id":120,"value":"long","value-required":true},"doc":"Map of column id to total count, including null and NaN"},{"id":110,"name":"null_value_counts","required":false,"type":{"type":"map","key-id":121,"key":"int","value-id":122,"value":"long","value-required":true},"doc":"Map of column id to null value count"},{"id":137,"name":"nan_value_counts","required":false,"type":{"type":"map","key-id":138,"key":"int","value-id":139,"value":"long","value-required":true},"doc":"Map of column id to number of NaN values in the column"},{"id":125,"name":"lower_bounds","required":false,"type":{"type":"map","key-id":126,"key":"int","value-id":127,"value":"binary","value-required":true},"doc":"Map of column id to lower bound"},{"id":128,"name":"upper_bounds","required":false,"type":{"type":"map","key-id":129,"key":"int","value-id":130,"value":"binary","value-required":true},"doc":"Map of column id to upper bound"},{"id":131,"name":"key_metadata","required":false,"type":"binary","doc":"Encryption key metadata blob"},{"id":132,"name":"split_offsets","required":false,"type":{"type":"list","element-id":133,"element":"long","element-required":true},"doc":"Splittable offsets"},{"id":135,"name":"equality_ids","required":false,"type":{"type":"list","element-id":136,"element":"int","element-required":true},"doc":"Equality comparison field IDs"},{"id":140,"name":"sort_order_id","required":false,"type":"int","doc":"Sort order ID"}]}}]}partition-spec[]contentdatajlb+ X8 -eo@ט -V -) -!N4f"؊(-%*KlQ##ZXX11!*ğvB;з 8hn4͉I]kk6BXԤHn+4ñer0TEW{k6>lL96fl)-=PD}rWoيbe}QZɺuePӢb6?0o<|y` c021|D>d\˽.\c -0(])&/Btvg=*P)1%q|9WK&1RuUu`L!-m25>Y*5&QY+swq-na#|b ztg)ncCuo^=)eM8}Vyz$XTxE^2!g?aX&l3ah&gCE@#V735&gM1T6oZ6InAl$5rD)I$cQ zcpPbm3}Tb6%(WQ_;gjWnI6A30L9wbv5&A}5VrHMsZo`* zX^GXepgw)^=G-+ZSju3DtZ9uhV0 z3qZUY2s^8hnwjj5!d&e<4v~6uyPRw@}0N5Qc3tQD>d$0hZ@Q&}wZ19KJO; zSX(=_Hm<;JYl29`Gp4-8H&zXrO$^$>wnDIOigNbck2(Wk_k_b)cMYNs!`5v~$w;D& zKzg!Rnl8_(YN=Q(m5Q3C6}8eBaGID-tPszKxmK9mn~3p5Gy@9Sn57dK8H@4M|K$mO zl3`c}IvabD9|$+2riOhyo4b8MJoP;o6P5?lqynJ`KjH(p0Cs*Niq=ix^FN1^602N4 zAP|!r!7rBN1actqFwb@d9H7l`fbax;gdM~q2-q}-B|R7JAP1T%SakrmAgCoy&GrfX zko)uv9I6&MG|MscgRJ)9AOvr!gO@SjbD;8R>Gl$WAezS~=k_3coXVa>a74S3NxD5u zGF~$&nz&d|e^S#rr&6i2*yS|%4m8nwUa5h@^CC&^KoCT?lNt>SNXnBHUlpprWrs~S zQI`g`eXgEdo^5cKu&iU$@CTA5;X*Ebr$wSV%MY?J2$35R^ptcbPGKvfkmXK9Mavo3 z(3c2mYR}xDY=zD6CxQyIEB<~>TajF-<-n+Bb25@S`!O&q--o1}hKS4h z1r{Vqx`*F@*Z0GLV>U%{5kt(x?O1<1xN%~AXzA!nCtmwddG3M2#lqu1UL8KH?b-4B zt-E7){;l3TRzHufZ``=xZNB=^#LCFmXMb*dws3Xi^E2PKH}~DX|Dg~5zVgq$!p5~r zqjzrZy!6h?C*OMa-ov|QN8kKpY1f18YxfNuJU9Q{m8ajozFu73|H|0|@0m~C`j47l zov#mHygmNM4+p-TAANT2+{)#Rj|-1XUH)|UnY-V7@!00Q Date: Wed, 15 Nov 2023 19:19:21 -0800 Subject: [PATCH 282/587] Remove RecordingHiveMetastore --- .../io/trino/plugin/hive/TestHivePlugin.java | 26 - .../plugin/hive/RecordingMetastoreConfig.java | 64 -- .../DecoratedHiveMetastoreModule.java | 3 - .../metastore/HiveMetastoreDecorator.java | 1 - .../recording/HiveMetastoreRecording.java | 557 ------------------ .../recording/RecordingHiveMetastore.java | 421 ------------- .../RecordingHiveMetastoreDecorator.java | 44 -- ...RecordingHiveMetastoreDecoratorModule.java | 52 -- .../WriteHiveMetastoreRecordingProcedure.java | 72 --- .../hive/TestRecordingMetastoreConfig.java | 54 -- .../recording/TestRecordingHiveMetastore.java | 354 ----------- .../plugin/iceberg/TestIcebergPlugin.java | 39 -- 12 files changed, 1687 deletions(-) delete mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/RecordingMetastoreConfig.java delete mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/recording/HiveMetastoreRecording.java delete mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/recording/RecordingHiveMetastore.java delete mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/recording/RecordingHiveMetastoreDecorator.java delete mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/recording/RecordingHiveMetastoreDecoratorModule.java delete mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/recording/WriteHiveMetastoreRecordingProcedure.java delete mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestRecordingMetastoreConfig.java delete mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/recording/TestRecordingHiveMetastore.java diff --git a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHivePlugin.java b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHivePlugin.java index ce7d26914f9a..b3bb9c96a669 100644 --- a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHivePlugin.java +++ b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHivePlugin.java @@ -141,32 +141,6 @@ public void testGlueMetastore() .hasMessageContaining("Error: Configuration property 'hive.metastore.uri' was not used"); } - @Test - public void testRecordingMetastore() - { - ConnectorFactory factory = getHiveConnectorFactory(); - - factory.create( - "test", - ImmutableMap.of( - "hive.metastore", "thrift", - "hive.metastore.uri", "thrift://foo:1234", - "hive.metastore-recording-path", "/tmp", - "bootstrap.quiet", "true"), - new TestingConnectorContext()) - .shutdown(); - - factory.create( - "test", - ImmutableMap.of( - "hive.metastore", "glue", - "hive.metastore.glue.region", "us-east-2", - "hive.metastore-recording-path", "/tmp", - "bootstrap.quiet", "true"), - new TestingConnectorContext()) - .shutdown(); - } - @Test public void testS3SecurityMappingAndHiveCachingMutuallyExclusive() throws IOException diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/RecordingMetastoreConfig.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/RecordingMetastoreConfig.java deleted file mode 100644 index 14e0cc8dcd05..000000000000 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/RecordingMetastoreConfig.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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 io.trino.plugin.hive; - -import io.airlift.configuration.Config; -import io.airlift.units.Duration; -import jakarta.validation.constraints.NotNull; - -import static java.util.concurrent.TimeUnit.MINUTES; - -public class RecordingMetastoreConfig -{ - private String recordingPath; - private boolean replay; - private Duration recordingDuration = new Duration(10, MINUTES); - - @Config("hive.metastore-recording-path") - public RecordingMetastoreConfig setRecordingPath(String recordingPath) - { - this.recordingPath = recordingPath; - return this; - } - - public String getRecordingPath() - { - return recordingPath; - } - - @Config("hive.replay-metastore-recording") - public RecordingMetastoreConfig setReplay(boolean replay) - { - this.replay = replay; - return this; - } - - public boolean isReplay() - { - return replay; - } - - @Config("hive.metastore-recording-duration") - public RecordingMetastoreConfig setRecordingDuration(Duration recordingDuration) - { - this.recordingDuration = recordingDuration; - return this; - } - - @NotNull - public Duration getRecordingDuration() - { - return recordingDuration; - } -} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/DecoratedHiveMetastoreModule.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/DecoratedHiveMetastoreModule.java index 9427bfeebd19..d7b5cd995868 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/DecoratedHiveMetastoreModule.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/DecoratedHiveMetastoreModule.java @@ -24,7 +24,6 @@ import io.trino.plugin.hive.metastore.cache.SharedHiveMetastoreCache; import io.trino.plugin.hive.metastore.cache.SharedHiveMetastoreCache.CachingHiveMetastoreFactory; import io.trino.plugin.hive.metastore.procedure.FlushMetadataCacheProcedure; -import io.trino.plugin.hive.metastore.recording.RecordingHiveMetastoreDecoratorModule; import io.trino.plugin.hive.metastore.tracing.TracingHiveMetastoreDecorator; import io.trino.spi.procedure.Procedure; import io.trino.spi.security.ConnectorIdentity; @@ -55,7 +54,6 @@ protected void setup(Binder binder) { newSetBinder(binder, HiveMetastoreDecorator.class) .addBinding().to(TracingHiveMetastoreDecorator.class).in(Scopes.SINGLETON); - install(new RecordingHiveMetastoreDecoratorModule()); configBinder(binder).bindConfig(CachingHiveMetastoreConfig.class); // TODO this should only be bound when impersonation is actually enabled @@ -77,7 +75,6 @@ public static HiveMetastoreFactory createHiveMetastore( Set decorators, SharedHiveMetastoreCache sharedHiveMetastoreCache) { - // wrap the raw metastore with decorators like the RecordingHiveMetastore metastoreFactory = new DecoratingHiveMetastoreFactory(metastoreFactory, decorators); // cross TX metastore cache is enabled wrapper with caching metastore diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastoreDecorator.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastoreDecorator.java index 689bc695dbb0..50d0dd005e32 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastoreDecorator.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastoreDecorator.java @@ -18,7 +18,6 @@ public interface HiveMetastoreDecorator { int PRIORITY_PARTITION_PROJECTION = 50; int PRIORITY_TRACING = 100; - int PRIORITY_RECORDING = 200; int getPriority(); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/recording/HiveMetastoreRecording.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/recording/HiveMetastoreRecording.java deleted file mode 100644 index 2fd569416b6d..000000000000 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/recording/HiveMetastoreRecording.java +++ /dev/null @@ -1,557 +0,0 @@ -/* - * 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 io.trino.plugin.hive.metastore.recording; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.errorprone.annotations.Immutable; -import com.google.inject.Inject; -import io.airlift.json.JsonCodec; -import io.airlift.units.Duration; -import io.trino.cache.NonEvictableCache; -import io.trino.plugin.hive.PartitionStatistics; -import io.trino.plugin.hive.RecordingMetastoreConfig; -import io.trino.plugin.hive.metastore.Database; -import io.trino.plugin.hive.metastore.DatabaseFunctionKey; -import io.trino.plugin.hive.metastore.DatabaseFunctionSignatureKey; -import io.trino.plugin.hive.metastore.HivePartitionName; -import io.trino.plugin.hive.metastore.HivePrincipal; -import io.trino.plugin.hive.metastore.HivePrivilegeInfo; -import io.trino.plugin.hive.metastore.HiveTableName; -import io.trino.plugin.hive.metastore.Partition; -import io.trino.plugin.hive.metastore.PartitionFilter; -import io.trino.plugin.hive.metastore.Table; -import io.trino.plugin.hive.metastore.TablesWithParameterCacheKey; -import io.trino.plugin.hive.metastore.UserTableKey; -import io.trino.spi.TrinoException; -import io.trino.spi.connector.SchemaTableName; -import io.trino.spi.function.LanguageFunction; -import io.trino.spi.security.RoleGrant; -import org.weakref.jmx.Managed; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.Supplier; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; - -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.collect.ImmutableMap.toImmutableMap; -import static io.trino.cache.SafeCaches.buildNonEvictableCache; -import static io.trino.spi.StandardErrorCode.NOT_FOUND; -import static java.util.Objects.requireNonNull; -import static java.util.Objects.requireNonNullElse; -import static java.util.concurrent.TimeUnit.MILLISECONDS; - -public class HiveMetastoreRecording -{ - private final JsonCodec recordingCodec; - private final Path recordingPath; - private final boolean replay; - - private volatile Optional> allDatabases = Optional.empty(); - private volatile Optional> allRoles = Optional.empty(); - private final NonEvictableCache> databaseCache; - private final NonEvictableCache> tableCache; - private final NonEvictableCache tableStatisticsCache; - private final NonEvictableCache partitionStatisticsCache; - private final NonEvictableCache> tableNamesCache; - private final NonEvictableCache>> allTableNamesCache; - private final NonEvictableCache> tablesWithParameterCache; - private final NonEvictableCache> viewNamesCache; - private final NonEvictableCache>> allViewNamesCache; - private final NonEvictableCache> partitionCache; - private final NonEvictableCache>> partitionNamesCache; - private final NonEvictableCache>> partitionNamesByPartsCache; - private final NonEvictableCache> partitionsByNamesCache; - private final NonEvictableCache> tablePrivilegesCache; - private final NonEvictableCache> roleGrantsCache; - private final NonEvictableCache functionExistsCache; - private final NonEvictableCache> functionsByDatabaseCache; - private final NonEvictableCache> functionsByNameCache; - - @Inject - public HiveMetastoreRecording(RecordingMetastoreConfig config, JsonCodec recordingCodec) - { - this.recordingCodec = recordingCodec; - this.recordingPath = Paths.get(requireNonNull(config.getRecordingPath(), "recordingPath is null")); - this.replay = config.isReplay(); - - Duration recordingDuration = config.getRecordingDuration(); - databaseCache = createCache(replay, recordingDuration); - tableCache = createCache(replay, recordingDuration); - tableStatisticsCache = createCache(replay, recordingDuration); - partitionStatisticsCache = createCache(replay, recordingDuration); - tableNamesCache = createCache(replay, recordingDuration); - allTableNamesCache = createCache(replay, recordingDuration); - tablesWithParameterCache = createCache(replay, recordingDuration); - viewNamesCache = createCache(replay, recordingDuration); - allViewNamesCache = createCache(replay, recordingDuration); - partitionCache = createCache(replay, recordingDuration); - partitionNamesCache = createCache(replay, recordingDuration); - partitionNamesByPartsCache = createCache(replay, recordingDuration); - partitionsByNamesCache = createCache(replay, recordingDuration); - tablePrivilegesCache = createCache(replay, recordingDuration); - roleGrantsCache = createCache(replay, recordingDuration); - functionExistsCache = createCache(replay, recordingDuration); - functionsByDatabaseCache = createCache(replay, recordingDuration); - functionsByNameCache = createCache(replay, recordingDuration); - - if (replay) { - loadRecording(); - } - } - - @VisibleForTesting - void loadRecording() - { - Recording recording; - try (GZIPInputStream inputStream = new GZIPInputStream(Files.newInputStream(recordingPath))) { - recording = recordingCodec.fromJson(inputStream.readAllBytes()); - } - catch (RuntimeException | IOException e) { - throw new RuntimeException("Failed to load recording from: " + recordingPath, e); - } - - allDatabases = recording.getAllDatabases(); - allRoles = recording.getAllRoles(); - databaseCache.putAll(toMap(recording.getDatabases())); - tableCache.putAll(toMap(recording.getTables())); - tableStatisticsCache.putAll(toMap(recording.getTableStatistics())); - partitionStatisticsCache.putAll(toMap(recording.getPartitionStatistics())); - tableNamesCache.putAll(toMap(recording.getAllTables())); - tablesWithParameterCache.putAll(toMap(recording.getTablesWithParameter())); - viewNamesCache.putAll(toMap(recording.getAllViews())); - partitionCache.putAll(toMap(recording.getPartitions())); - partitionNamesCache.putAll(toMap(recording.getPartitionNames())); - partitionNamesByPartsCache.putAll(toMap(recording.getPartitionNamesByParts())); - partitionsByNamesCache.putAll(toMap(recording.getPartitionsByNames())); - tablePrivilegesCache.putAll(toMap(recording.getTablePrivileges())); - roleGrantsCache.putAll(toMap(recording.getRoleGrants())); - functionExistsCache.putAll(toMap(recording.getFunctionExists())); - functionsByDatabaseCache.putAll(toMap(recording.getFunctionsByDatabase())); - functionsByNameCache.putAll(toMap(recording.getFunctionsByName())); - } - - public boolean isReplay() - { - return replay; - } - - public Optional getDatabase(String databaseName, Supplier> valueSupplier) - { - return loadValue(databaseCache, databaseName, valueSupplier); - } - - public List getAllDatabases(Supplier> valueSupplier) - { - if (replay) { - return allDatabases.orElseThrow(() -> new TrinoException(NOT_FOUND, "Missing entry for all databases")); - } - - List result = valueSupplier.get(); - allDatabases = Optional.of(result); - return result; - } - - public Optional
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%0AA^-{7yY@jLNL+s#O9Yy-ZUkCZfZsf{3|E7K< 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

%e6zH^BV!Pi@FLo!%5{t*|pKpEcvI z&g4>}j&RL^b1^zV69-Zdg1$IL)EJ)TPpj$q%6Uq!`Pb&1L^S_NIAdZJL5RQ?S54n4 zFDaUpj6BO9kNd+TdT}rQ5s}e3|xJ~mFet}XZYjU1-y`^t??CgY+n!iOfM)3I*xIvxZ}7vR#?Eo??)gE^{A$rAJ!_<6i0AS#ea(DTy zYhamS136l1_+;&@iUMJQUDbb<%Zg!pO^Oo{FU!IopQ;FA(;=mc$n1JD&*mE6plC@7 z>v}f-U?*M+Qk`6F!oxF7}5$e$8_|aKy&HnkWV4m+1`knNG(MB z>;SNYyTD9}z4oB}TlM$NHq8}E#u;Kr`=P}s`r?F9?{vK$5OBP}DL#k|RFrpb1H>Ed znusq8-{TdS*9PSZ?QK?PGaU3(F`2Z4mgfOY!E8p}^cq~HtrfpP5~t0kx+VX9tfVJp z>$pYs#6aq7%3dF2_!V09Y`S6nS_&oc%P|w70qzC72G1?#dWNlq10lSc2S;O3QLgVU1|sjcn2lB~C4`$XCM&IA{O@?OHUJ4c^}CY$v0 z%{E1H?A535Lbi@8>>!tUOz7E+_^WuUg*9~yLWuBQ72uV1(u~e--O8eA{a?;WglKLr zes_8NWa0a2RNCEwgbNvVIwCp9%opuBM~lSE>1sbsY!uaBp*_&FFZ#+aXA>D^Iv{vn z4e&)H@SjljXl428Vr)d9f1~I!vMV@!2pz=(qy`C`U@!#ZE4nJqPNUu6?a%(+GVZiodPnMSd=p* zA0t581|>?Zau5qo$GoiBR(B?K#6OeIftjXZtwXV1nZKJJ)OIh#tf~f?2Hc>$*foID z+D79sWSmhIm}7XED*jL^81}F)w_d@`9@D)PJi^=N4|oEmio7pZ;69=C7^B|Bg1|n6 zO#SE@H+U@~W%w^&d51Jf(*{o|ikFlBe1udmgNuJ*mNbz^8yMwQv!2kjM6;AF@(N0n zZcxip%)We8ezBXR8st-;l8axl?l)6r9sRb;Fht(V07fainuaY*gZ>@`Rw{ies8$8} zJr&&qKQam`fIB|A1UGMX>)LY5Q!}VpK0%hYGHm4b|A;+u8L7} zbC1om3~y@1uEd=Wv{50QK3{Ks*J4T4a?g2zVp4yQ5bxs1;6d^vcY4z8WOWkl@Yfsr z!Ohbu51;BDo0qr1IT2v0#W2u7rtPC_#qgNr`JzWqS*=j>=M81?7ck`ku_#Wkw&kX$ z96K-6QIq*;m^bXGvVp{$G6tX9p`~sjrkKM<@92m$+XFnsM=%aL+tMbG0GdRD_}nd! zI+CbIHzV*WyoZ#c$IB*;oB*i>jL{tP{V9O+rWSx;OZ5cFL}kkJRw@16twa(b#*adD zI}S#wR`VJ{cOp0};0_OU4Qr}g{pp&|&M)DC^F5bm<#xpXj%bVS-f2@|rG~k3GfC!; zRY>OxMa9@gm$4ey?YPo*P&17hBm=?{9)|HrwP<@R4j?7!1EfR={AKIk8_=y=sX74Q zTT4aP_{pvtYc!v%LXg~3C|oo;T+nTFQ9;nV2)zDVaSkO^<%N}5OsCbGdU%)Gxec1U zrc$5AFtac;K_H@PczS1Y>!dL0Io|<52)=RcWYWm|whYjnJf#c{Zi3JFD2SGjm_pjy z%hmRxG^&L~ddy|{=T$1=aHS(b_~iSqFhI93cl7IMJ_Ry%Oqc!F*7f4gsIOfIF#-#n za{1XZpQ*V7S?#MIznrigoBIM4E(N z!Npj~M{UBMszBTUIM3iAB$LzRw{&QuLI*a{DXhpq%TZf-JPi^N1j&20 zZC=+qYR}3-^(M8xopbtwbhT{0F@rYF{Qs!qKB_iBi+niWD~H~&=VOR6nczzOI!wqa z7RVNN8&6>Mg`*$zka+6Hd*0AaeJ(h%nk}4zchtOw1acOs-vJ`&lkmaLBh1hHR%(T~6 zX@043cm+&_(Zm0ldr({)iN|>yf%MRms`(>J(!{&FneMni?Di&F>4U6%9QtHWN@7L?%80F`S%XQWHcdN6OHNk zw|(f%sPmbDhMY)6O=GRh9rVkUSLjjWfx6=Ki5*@Bs>p$e&lrQ3ubh|x?w~Fbit^y! zqS?9KJypEW`x1ws07iMCr%5mfhC}vwvLDZuijNb0Ke7m3JJZgA)f-s%^%j_6D*WI? z22R;+t)*_Oz1|0X-(~Q)>pPJ#K!b1E6o|DW=L8Xh@^?=)S8c`b$#|;eoKoQf8|Dhe z6a+G3y4*WtL3$EVAI^(}gmQK*ev8nYtzJHHci=6G!`0Nl%j1C8xx&BpQXX0*(h~VX`%#eSa|;8zXJ01Qduo&Y zdF{&zOwp5GZKViP>QV8~=e4Ms1%vTkK+1W{eVq79S&1&seK#j5YUUh*vw$6AIjnc?B`SPcd(e}+` zLSOW80D)KSt$ZR~P~wH`NVeL8F*BAl+3j9AGpvm4w=Oc8Gm#E6W+S@VskX*w?2n;p zPYZP+pPzr1_a(U}#YaEoSm0_-Z7Ya!GLNUhQ&O4KrkN1cc$EyaN+nG!>yjQ=n;KZh z)T~ev0cu_Qo3VW?06-GceUNlYs^QS^2v6%T<(MxdsgIk&~$TMblJtk+Y3U+e-R8N`Fe3uw#l;6 zLGz_`%|iQcSx@U*zRVG*uFCHPkwL8`4bAE{)vUKrf;})WpnDe>FDF_u!6#VAd>e?> z|NoHo)=^Qn-TUYxD&eRI$j~4mAYFnqN=ugv-67o}CE}=bDKMmTcef1EF!azM(lx}8 zQs*_mH4W=U zAGQHD5j4DI2>^a=YkGdndMB+XC-MT%Wl-%;&lT(2#FcpjFw01%#})Swwzp?E>Al!KnK{B;O>_ zsa@sU{o9Kk6T;B(GuukVl%eKr0Ruw?q8ZdibT}9L6tMUisF=uAQ)E-$S6{m;U{Yzd znNo%8P+BmK>C}62{E;H#JAUoYY#I{#q+Ucza3%IK*e)RoYpTjGcELp#uXd{R{xOw) z1#=@*} z9s+1ZnR!%fVcy!oj<@DHL|8O^NmhK4I5-pQ`C7~Rnf5Jxux8#bA+YxaDMTy7gj?~+ z&YD@+F51{f!7U#VQ>Q<>fHeBx^Twnva%B7mb2JylCwLRsmR=;gU9XkWDd*upBz?m!G8q z$j~A-(PXPF|FaYE@v#1qUsW?{++^d{m>OwP8epbL`pq=hZN?`R%8CN(1D$pkA(uc_sgyW4v^X^INZ<;3B|-(UbnQoNWrOZp#YtY zVt1K9Xs0I0TSg7q^+~t+w8-jv4l<;e2?uV)&^`qtuz|A_qU@y6 z811oROgU!SP=rmBhlSg3ds+3t)@*S2|B+ZK?7xyV^mH&98t=(%h_d{pP`0NGkf+M_ zu6iHhWk4-d1S;ulCTrT@M1wqCFe{j=B#CP=6sp#Up$d;k`_J_!k*Gxbnx?KNNCOm_ zfYZuCD!a@_`-+up7g>AXN}zCu`Hd;dGt_f%D)4`EW%5*xO-E|$^F$v86nXdkqxfA= zT1)Q*O~M2V?;ncIwJUsC*RSdzo@VAcu9C%ITX`Z<=1yS3%}X*G9KOSS%h ziRjX!Ymwr@S>QyVRoB>mx4p~>AWeF9!21;XEAVu@3cBsuaS4+e6U@8?TvH{$^TU2C zO_og>4Fn7|Brs2R9WT2pDzr2`v7(5fuWcpE#c6*~eamues$EUkdV*8NuZ8#E7LOX8 z7W_wY7fKJGXH1+mgkHpyaRHMuBOZv?=!19PXMUs33Onse9GpGB+OHIKm2zLuKBdH6 z1$YQ@@q6!yu8PE!eIT|Pw6-&lEe&=Bqmpove>^nXW|!!N;NCORH6c=(UE^Qnz>(3t zlVXZRYBsor0!wP>*qZ8trXoDK&Zp>H6<1+sfKO6QQUW2}eRv^^f5p0+1Mq>32vJoY zci2UGk|>j|nS72T>ikANSZ;m3maI!~jpmZOnB+PC{Ly|_4RRXm4LObQFrC zjoZua%#U>T3hQBao!f*8de&I~vp6Gl^K%zKIV3Sz+)3%L!vY(sZ6K;W4auS*p=|mb zT<0x>u3^NbvlmX_IYCWJZ<;x29pltd=tPS8m^HVRdCNNGU1v`e^bE*&uJx+dCw?)7 zQ#GRi6{rEA0(~BWFCld*_ADT$A0xlP5v46C!(*Bzc}xNrO4-T^rO|yg20SN4?`>W3 z-8n+z_f@5oWZU@D^3C}qG$*| zrP5>dDXE>Jy74O4hC1WjUpv()qaC;ZZ-h}*77|F{z5j<~uUZ2w8yZFrURMjecYe@6 znP@e#*=)Y3flCOeFy=nX5+iue=K(#yC}U^{9O6-~>*v55=uuBhX-VnV-V5ecu5#Fu-a$2TreK^SPdYaua{v z2TbaQwoZUR0aDtSiYyPgTvR8)Pn{W>F(yTc2ke!R9`j=UqN?V{%YEU=9ZZ!EI!Pb#v*C6!kk2T5KZ_G7!@K|U~(hgx1w+4Bdr+4PO$Qp&ho%613=3WH0B!^@{qn@a(fTDh$6$f~7Q z-4>%7xW&*HG(En3%>$4q?01`-$S)q^e~q*3VNN312%Lk_}vBvLSjQN zDwyGmQm+OW>B*iZXJ)dh2Yx1(phK+kJa34quvtvqtuCkHOtXk@4XAI(O8pmout!zU zmv(C(!B(fYTTJKmi{Bp!9N%}z_xlt#=18#|S1mc)aCzH7*fVOT_QbVo?~$j&=2FQb zFpE9r0)~`Rql2Q2_%4p6rxL}HxLu(OjEHU}rc|r@HAf%(MmUq$r;`ciCpZkNyw}*~ zn$9^ggd7_b6*dJFOY`&eD}R-W$K>BYUJXX33(4x29UuR~2!&=DjNkL-=ZPoXE5r++ z%lbehf9q}lUx6i0v=bZgv8;x>Eem$QWT(fqg2G^i`XA|p-m1Y4Cbb$WD(vr&c^kmU z)Y7x}U&MJ zvtKkf2Y^z5{&W}Ma1Gr90p2k!hI}S^mF^Pe8z#_5r%fhp9g8>_YeiVZ0A|%`eOI5)rG+a#A;KdTA8$ zdiZ|cw(;V?jF_qY3c{;Nj8csBHD@NJ{#e?TRgu}?!n20R)NQd=EL?DUVG97N#FICGZIfrA=03@^G!KB(pb+GF(r8OdQO5sHwkEv(K?cl)BUoR#l14Szsr*RJCs zR?iEgFJRZyCo{HQLP-@H7{MYw_C+|gPDaF=J7L%`)aC zk`hkO{L#f-;Dx=Efa5tD7^2euN%pKJdiI*%(UEC%caXRJjrlTBRZd1lV18|ojz@Zu zz+{gdMO5@Kt=rud7<${5B4Ii@nrZ$1HdcXCsy9h!SM-8bwbPFRPq|VDp~>TQK);q6 zCVVxUfQ}x7eIg=mD z&w7$mqnh!kk&s+byNuhNkuA*sVI9((`7i`e{w9A|JR4g!ZINK04$kIra$u1O4a%_L zp$;z0m*c0n|9w>OeQ3c)&-A;FmmTk#v(sLQhROZ|>kwZPthLN~QnyBC^nt+{pgiXQ zpoJS}?7^4e8Z>jef-ub83trrA1v$UWQtX7l6Y3w8POVdJA1Rp<}y%5s%9MbXRX}!swaTSNX0@dm-3b&j6vRU*%lh*C1$sE2W8-ivKhm$sArI%Ko2A8sR5Ls4|F|otHc1 z^ihk3g}D=qnEp-ob{)@X@P73se6-?fof|x3~F_}h8?gL8^=Gh+E%D_`T<*OlCDL~q-JSdVA354 z!q!~e0;}jvjwuL#Df!I}07$>VrS0&5^?V>e9ZiA|enE}L#LoeF%Y9iFn`a4l1^dQ4 zMIU&sIx{pRdE?>8!aUX`;r?dZ z4w5}lc9Gm6rXqy6{hNh>{=(#64Swq7Q>q;_??YL>lkE8&uTHJKaA>*m7J7$9y|xvX zSD0^ks*-~Z5k9g4r2fE6@bp^^O;1TFBQ;`r~39FmX`&0y;&&r?>9AT%J)oKztG?YVn<3_s~}G z&+GEHPeX4wY|Rhu%vBlYo%GJd%wX4_|D?qbLoMF=G~m1J^6zUYr#*~-H_Kb^T;(8B z1xeJY59AS+vQ8)x1k_j8dWRF?fvrG>Z29ua)nopsL9LFM1OxR0&KyFmw`{Mwa)20# zY-Kx;2+PWPYn~X@plHpg3Rg%tlZMIDRh16Oa&x)|kw^rmK8~%C{`uxb#{!F1?%DdE z^LdK=5^~Ovl;a2N?Yft}1jUON&x@VyAgnu7*{@r8C3O|H)Qa6_%8+q1j-W{l&xk;L@woFU=b_3J#sWxWNEA2H$FPkzO`!~p_b-8E3 zsoy0`8KUJB87v7w!C$`1m@+v>J(JgGa3F+9(e6ii$@?;Vk+jF@nP$R&efIt9s{m&S zTZq4$?Y3y%m*cz9)l4QkDLB>0Xi43fR7u_Q9NJ>Djju>=4~Z>Z;UaexYUFPM)7Tq; z%u6r%!j=Ns1q!Z>wzCo16LJ0JxK5jPxMCTYRU?J)8n2CKZ*hn#Xr`I#u!?J+?wFrQ zxl^|B_O}>gT2f^Q_5+5g;Wex}w8bs30?ip8-pdP=AV(q?M$C8KCQ3cQXS{6F=h4&q z)TOkLIL>A>(XL#^0ipbpt! zs%X0Q$7ZcDXhG$y1HX=9MLgBmu|#ZbOwt5Z?&RrBz=q3I$Uz>Doo;Sqi*FywGt z#abo7_8nOesPt&v6HAE%=(6^OKOA}{+slqGyi4^i&*rYgk)6>!^fF&jvuywBc z>$9jV-bE>{FX8z;cT>PoFTam}UN91*=1BVV;T_o_Qz0@{f1$I}$H+uaXi#%v_1_RB z4w@x&dx7MxC(BWv|08=%>`+DHX@PF18K;H+OPI@?hHapu=~yh;X{dNWch^j zkn_O>m-#*>_A9QVNQd+#RKO6^?|gH7b@J6l)(#VFkgv&p!H_;r^nlF9*pypXNl3`A zaWA$gwai5+Hk9n5Ojf?G54w}T#>&30p8dQq$oCJrGYy3msbYhwv(liA`1k}`Bh${M z+l=9wmQ5;g2^JPil^jpFXAbBpBR?l(HhxPc3F&_dvvRn20=&NYP8P$RHCFF6*msEx zCr_vrA1%;`XgA(Y0t{jjsopyS1`6}oOJf17ReLKGRxoy7T=3PeE|Wz^K+az#pu82~ z`ybtK>$QfP`C1I-VFC&05R>C~nzpHO1!DKn_bnPu?@H^%6<3H?ki$d&F;llw z$78ES@xfmmD&fd{S7{KZo)di-1)7r6Ta11mU!|f}Y<9tBgKv?h^l7k0j1jJ6- zZSd0ZFU>0%ZUkKGLKJryW1|^HRyDaO)g_0`6Hv)xV_Y#Psc>*a7G3t{;7z6C?M%_q znq(Gtf^V64dlG+P%pV@TyQ7MSk(x_jDgVH7_;CZ!wqO>nKNPaDu*%r8X5y_~GhU>u ztXWA&;QWjTH_y%7qlVP+c4aX_>bSe~mvmNJf|>8O_mOHGTP4&AcKH(hYJ_^vNejzQ zzzgf3jD%-^3zAhz+kVXIPY07Z5U6;71eL}V?L`1a`QHVvE~Es*In|D=e5L*q)yhuU z9u8~Yyo*|flT<5#%rnF6+jR2F-GSC`f7wcpK}5*91ipg9t=pXF)I4rA5~+E0Y5I-E zxedd;_-;_+{+7;5sor4u4Tj2n!_)tEPecN6)WV|KyZP(r9&`>KeV6D%MSt=KXc7UG zA}?Nn_D-#Ng8d&|}Be*iV);4wZ3#R(@2-eNs`Q_nVB?X=@No|g1@UaVreN5Ke}7i=h7EI3iy!oR0~dAU|$R8w)d z^NUE5N*c5@e9P<6Ul`Y<2hVTk-NML_9^GJax*@Rl7vPo!0xTHUC-9ZE11w=qx4{Tc z9tHf2y*+M5)Dy_5T*OqTa6+>{7iF^?!&OF700DH@?NM|$w~8bV5Lp=Aoh2sUjiM50 zY(uD%_=axXn|w7o?iqL&)5T9#cWet;+mK+X2}-roGKV9pRtsv-HQj!VqIkw$O*9!u z{JFs`F@F`zxivMR*B#6Djsq)d^*>8LEAIVDAVZg!(N3Sfez;@d{1kk*BY(ieosG2k z-q@@7U*r+Hju!C_6Dt0O4SIx;jNfkF`0Y7|%Odf(>;~Wrh4*`DCJrFIHvDn(N?{aC z&9V${7w4f}Xzy=~;O&zjsifXCHC0G2UDPD0yqq1_ZuEiOB+!28sZvXyxNeGSp6hq5 z``AOO;o(MqM}wniL9Nj3y20u`NIche(KC)L<$a=opeG)ORDy1uK)hp+1Z^0 zU}*@k&jw-it|y7(DZqYr{PhYGUzA+lz_lMji_@m|?N@&eO56@$ruitJ(WbhNydYmG zzg+62#?78-YaVhHX}}dcd`E2=enxSm-O55$-TBWSo6F*h{k4yB3^jBlVhJX3Iee87 z^mkOiZ|eEzNUj(Mwl%2wxl>I>G(QMBdH|=#!m5x-kFte%?JX~NZ-yoH>&9#s| zs5{leIsv&Z-szaW`-VSyj{z$f_WgzFV*+^=IhOw;Jdyej0bJy_*%d$lKb60uM+cyvh!sIIe1cQzrc|54Qhywy~30p$II9Lnyr3*N-VlmA<~&P zENZc;lJeg-63Lgl<&w+-t=TB=bSmi43MA=GX)Yc4yctY=f;t$k41Ui<@PACbvf-SbBHSVf`jhgCK0gEZfJ zwgSyY$8qL60XP2N2Abao|2EJ(8Ps2W8TmSqdX(?p#_O>9rTv6~U~hjXgJS%~1_!-H zF`v@HflT&d-I)Td#;s4Ey6GxDJtQEAcA;-~sl;U)fhn#AKFM|?UG@H8B1A=?BbC(+ z_-9^;Eey%4_v=SbT)w%KG1`qxb{&SHyK;@hCwR@`mBr>f_hGx2meQk-eE1Gl1)4X8 zyGA0-#jd+EBR60dRuv}!LYLh^k(>30W*Y*cQ3F;7k-jejwioSCYL~+=m`U$$rui`x ziYNOob6>auKVJ99j^eM-_-UM#ivL@`F$3k9 zIWsw4*Ne2f*cyvNOs!F;Kiffvnha!$ITg=E$&{oHt@$Y?MyC{I@QDc*?HFhxv*A;J z`DgF@?FKOE9cY3Y`S#RD^DhTms_W~|5Z-L6{`~*7_oIvb9|4vWEk+1|vG@BTMUVLo zuow>Y@B!_W$WjS6NA-Qz(PMuHL!MstpdkHuvEE{|ov=*(@0e(T$3*66b2+%eO`&bX6U(C z5r?r9Tb{+mUz_JQ#j*xe^u`-qWh!NePwF7rT$K4$OwjsvsYxp$+oOj3Di&xy{=u%* zvv8}Kmp9G~@SCnS#btnHdK8I}LZpbA=?8u>ponlj$u^q} z68F&#b0WSjR8n$TSm8z)c~zP2K;4V{%sQrP$I6dw-y;pJUYqX>8k+4K8{9Zc*R^de zji_c?9fX`m2qA0k6sPzqQQ0=8eDKi^8LpT7P~Cd!3_0Hc7>WBg%Z`5;<&Aw+3mQC~ zt`TUPUJz16e{nV5F=lyOn~hs*wtd&%9RZ`c?+QHdKIKd`BP+-4bii+}q3uc1iJ8g58kWp|d} zV@shKME{yX|HwkyvPH^Wu;FCScJogE@N`G==;IuHYpaqkk5|RU5h9Ht{XRJ<`Chr~ zDASg4`~HA}px^>D6AQQoSzd-@s3==fDRecoTMVVS*B$u605I!e6M^I-YLVr8g#f@k zt8~QfdtwkW9PM`(d=>lx>`w{yw|xLMJhuNr&IcG_7#O{Wl91T|r0@b)K1yQUXQW{` zuH{~@@U!pS7EM&nIx^3Tnjl(yA(HSZXPb627e`TsXls~MpK|^_2yrEcAlSu zx{%K~lpULt=N+IMcfWBsrZQ{;x@p_L`mpCd{Z2a5wpmx#a zrV!F!5isPN7YnUEe;{%PFJ7&FZ&Gp3>jzuWWp?W;%-x5aJ5qnF-krf5xyg=P&}MSq zcbAP9bOl`+XYZ+%%-i4ad|_E{lM^BtkP0d{=hrMhUO8g7(_4FI(eC$UGwct}+h|@M zaE<$(#eNQZT^0)iY6Xjj<7dY8LTS5}$4hhl!_8@eoG4j$lt~GdXun@sl6QnB_ij3e z<{EX5{f1@BQ_+5LgJQ%H+cp9A-Nqq!iFY_w=Q~@r3tvwO4y5jBvpd_YMtNCw1hn}D zXu->s3e;%4I0*PJ*~_?JZ3*WK0q%UePT$}Zt}IWj0VmaK8zQy~Dcb>zZEVmYpod9U zr6p$lKJrF{LKzq?-+99m!|~NI0n|m`h85#pE#!FR4{E$X-=@I9VFetB{YPiG7$TeS0o>hWof6={vD;u1nUr;j~WXXe{3vNML@fjMNL)`ke^0!x+Cp$fZx zUb)kJyO^gv@qO%N{4wmX#x00fmg%Au>P!Qa^d5Yb>KDgyVR0+$z(HHs+x2&3?D9 zSY*1GB5r3wGQj~Ep4KSmqQ3XHPan_UCb_rk-coqICCQVxiP;n&A%0~pNZ+0}!ePFw z)ZHyw-0xEMrH9CVg=MyLyDutw0t~CH?f&d8y{bElm|ZFce1T036O1~9?H_<;JdQ~^ z!2Yt?cV@OA5JFwAtDOnqcjW=n5?2s_I9412HPXtxgK^YMG87Gkrb!U{U*wjR?SFuU zU8F|B;UJ?@b$~bq!%G`JGNSnay}p`w3J6=~n#|c4sgwJ1GNN&3Df(n8a0%szdKCH6 zKbq!S^T*0}EDig1z?%!BqXWu}ft?4$K!@|U%fh3ZtiKxoCe^o~xR&IFKWn9R6u#IshLQBQ&zxn@sy!?U*I_-WBv6|LRr$p6G|gx0Ao{WQgioTpAPdilJHMg zbH9=Xa@R!pF;;rDwyq`Fd)M~qYkz`q?~Q#j+kn@%i<(!HU*5wd4JZdDp6CGR=0$CN zB8D#CrS6aTiQMRjWZa2#kvRJ47dKeJY94sKu`$7-jk}`CvDF|Ff}HK=x1y;0VCy# zo8$Fv)k2(`<|U%Iq<)WA>)&qr{&nN#Zq+zdzbE01Of5>TE9GanwXduf{r#JRoS06q zmn}{NNM+~s%lFQN$OHjU%B%Sap)i}Btc)e_78MhN0)B{=Iho|Yyhjcgh9LpCL3~ad z_&Ryi3FHA6AQ00Fs6KP9?47oE)ZyS5V2v2wW-x@7I9NJpO0G#LqRGY&q}OQkd|?F< z0^PU%+rc_2ka+Sf#xRWe+wIwJz#5O+7)ib`Z1{V;i=^&TYgA5Upc|;0mk34c`2K zIIx{uYf-kOe2*9Pw&_sg9@d9KQxokJ7CXQ~B+4%?I;5|=yOr!Cv~_j_HFjO5Gu%xjfRMjzO;IG zrr!a-Owxm(1rTNI0ngCEWPs}Ie>oY${wrj@gYmn?I2q?LkYgGi!beZ8lh9$eYpzRQ@K3K!nmY2hPW54abX_&fwm@w1%}d{U2QZpHnDcwV zJ%1JG$C0U^!)vxvL+vKDwd-U1zKenHJr49!Y z4I_p8#;_BIdbi~gR4PX|#V5_qq_4HycvEluk20(HI6p70XVZ5@TeTDd8n$+6MwPfi z^c~GLQ{gM6Cp>Gg@??(mVe4zV` zt!6Lx%I!hPTIP*2?K5mLZ=Vi0dJA$2dI4gs(P8riZb7_}l>5~@6)*UzTN>iEp{->z zln1CKQ(I-+4b_T(5Xsu)KIOjP*;i#y*b2VW<7ODSy5uZM6d`?R&HS7-R2`~WFnuZ9 z+yJF&-ILh&)$0JzvVZt6CF)(zTr2jn`5D=t@QmiuVac#oy4CV}OGdN#Hg#{4OefrY z-Yz)X_BoZ#Rfz=VBRMnfyqt|o>XmC?XV#*i!h5aj{OuIDOXWgfe>Lkx{AS&RXb|-= z%$E0H;r6mCz6|R;EJkyDW{a)q)nHctub5Xi4}LIn z(V6rna>92dSrIDrooKvXik4DwWpPTM@CH5Az$VO%qqIwM(Mu>!QWcTK0-is*1oM|4 zdn%0%`fu6uQs-FxFMT@Cf2TVSG3M(SeY%=FA0bU}8iRy7^?6bbL0Ctuz7SWhQZ7@S zw@b2KVx)_=v^R7%(Aj}A+_@`|042iR_66aLBBxwIA-Pd7>a}^lC7?#MpI|;=r(C-l zCxTl@b%s>02q8W06#FTHb%n2U=UQXvA6@Ut*mAvLMb-e1uQn7Id5yA-dq2kNY*f!f zd?Z0Y&P#+%cG3)gw?^h`o`B)^tTU!sl6O}Q##tLFS7eD(9(n7+QzBbA{4O=*R{I-T z())nzhTB!eI6+u7E-6Kto)c>3BkMR>ll@H%#loPW-$0sQe{YEbR(j`%sQKNw zvBcI8kXO6Pic1=cOB&xxPb!*Scw@;F$JODvJ8H+g1VQKu2%mANq;^B@=lo20C3UjP z(-7bz1YPR$VFkAoM?f8kIOrSd7kM&D_01M}+umOIW^PgzLMC5z!Q;Z}(~G*(h1Yfx~sXe)lshxi8l>*p9D+NnKEPnXIR6 zT<78m0Ru_y5KhsnHAJrN2)ilcSRVa;O`ARYfe$=;H z3^~_wFR~|mwf3%A?PPcQ2lpFC31xxJtKuTajAqyL38)aguT$iABBZ%ii=&#N&18i% zz9^8>u&Wx_CS3)qTwZ-ajY_{f)ej9aRKkUsdyt21dlk6Qm2N#22ES zm?|^Lg=;c}B0YIlq$pf>w#`w7nrr(Ut8;4REc34wreThthJg5n_ftCgMQu)Fz&4X-6*L$C!ab&;(@uU1 z9puEkygdl#{U7@LWCI^S8=q6zz7JjYS;V~IAw{g``|>=5i)(kw6!d*krWM{5^(e`& zKWb$iho#>8uZd0ff6XM+>l08Cn>4oE*zA_xCQMql0yM1O{QzmoqA}4wrn#2=cy;>Irf>Rpk#^}-phN2vws&Si z11s#W@blllm#@cXH=1NNg_<=)DG zh3f@eQh`VWaVaaRO=jBA;Z z`thMciRhpLYG15p)K%)h!OKv6U%7p7pB?q(q7NUGtDN|LYKpmv-?q*LrE5!bWh^m2 z*F7B%)nM|(D@1?0&?!LEcC62?1XeS#_*}OrvaYqDxxbJb?|k$R{=vPAU*AX@O4tyTGww(jPQ?}8Byi%#O6O7~n((w$K- zncBTWW(;1)5BFo0t-Icia+-=}NhbC!2G&IUn6TX$(UiDql$nDs2uu51fA5RHc1-en zDb1}==3!&kIlVh~!}j`==J!QMijjY;t{lZQc*qFZ4qJXWOrQ2)d*ncewSlWx0+JWX zAib@~k~sK&WZ%o(Qy#6N3qhaZyvZrlVEM;l1Ly)oSF+DWr5RJUjdSJ+d1*Mg`&`^W zDsl0v`0)vsYz@#;A4JNIqTpDYeqTs`_@|%sWB%^1#pYjI%_KMqzw5g1T)Q_P0wr?) z?G>`%Cz+XjhCcx#W*heaU6({aYe8KpqkvFjKvmpp*8P6l(j+!y(m!=&Bza#-lD`=+sVD@vSjxdV>Zk? z;GqGN@HY)$$_w{^KPahyD5X~{WV?J4#)7tefBwZ=8t_3!dE3e*zp3O*pQQ4{lea4o z*cq_JtEjWku5X+6+yiBt>Uth+e9TL{TeWl!b6oIWT{sGJnyt_0sn|yefm4|%J*k`1 z)ja$U$wBx5B9B@thbuCJL!gfJD5}YLE4_(}=YU|ys4nc~L5p^g?bT`>rtvuqD;WSd zbk{zTKn_y!01w__bQ$(~M$*4gZ%0~AN0@x%>`4*Bkr*eehE^D#<}+LTeYpdKVJ~6;=ka-iVbAaz2^WXmFP~Wl z6-u%PU!qcjVyxP4^!PMr17E4|QPHySRnc;n57585XA238mwUvP3GMo7Wxn12@f0a` zxcIiIP|ZV(RzzS4@FQ!qN>VR>(mAtmqEdFeK~)O)Lg^I#E+P?Wu5`vCZ;UWW_w!3R z{gl{6W46RFt0OOF;uoffoFh#-@VMD9L_|~0G6p7BEp$RJTzauGm9u(%rSn+$U$j7_ zE2J6QP4}j}_qiQ}1mOeC?X&9Ss|SgAy$F)7`pZL7&xc+0u7{-B$Kg&BtC_O~ls{A3 zbsRBcNw&|$ z+kL&h>Gsm{Br8$O&4K`qSF@ha8wqSHMrbpVAJTcj|44Ue!8G-6bG zb#|ijv#5Q)JKkLD)17|a>E5Qb84Qki;IS$U0vT~&&W;$=S{MOsWv<$@a(wv81Ze3d zO7doiqa5utp$hP}`U$+Qltza{iyr?7;!lq~javAsEm5Qc}Nne{JT4w}R9M3XqPes0*YMW9SN;KKY>W#g@) z6IF+L@vJw3{4k^^OXf6fR z9dS;fxfVbOJjZH~l(ba9NsRKw5{*M(HE%QT_}S$^+3~arW)yO>sQDByalBl!1@3PV zRVY7ca$gtyO6)^dc{dPmPgZKg_n&W`QTO7YH@u3X3Q;ok7n$Ii-IKM%xScae_gS*R z|K;{*{@=w*%)fot0Jq1C7;6SGLUH-Gz=9q)N%!=Unf^4_fYPYOtnVgit=-3Jl6a=3 z37VzjV6$0gH<@Q+)JTziC?(M{!t5n-c+G`svIg5OEJ=)~HsK?$X1sw%Y2+&vP%3+Q4sJ99^$-U$3jD!!$~xhd_!@6d8FhHu0t`YuC71+ati+{DmvetA&TVv(L)qsq4;?>)BE8SMe8LNp(;n%Bn_$MtS4@7kAFIYlxasS+RoUP65qp914Z+u_gh3`OrG#9?Q`_)L+%X^7x&Z#mU{;d;^O&hYcW56vg zn!^3q5h}ejY%K!A-)h4Z@*Acs&I8?!7K7J238f*v^E@S*4^pWqQ$K%KJg9}g3jask zzxPJw?4=$q=p;TNA0I5^UsELI;lQyg^L%2krL3D$PbdMcFRZ~qC!EQ;4!AO;ZJHZ0 z*aImwwz$7GC;V)}{Z3=RxM-kbBZ_K@_ zlsw}5p<d(9#~G(jA4r5d!lC_|*xykCgw9)A0Wn2EKA5rUHPw&ual~ zyEYClz9&~7zmVJ4P&VJ}?rqVXer|{>(BN| zgRbA#-4TCPUX~vLsI2heF!I~MI9uC7Y82s|t&n^;a!c$CEl_zEKIf;;E#!Ai0v0<+ zB?HNx8!zI-6#}~>?CM6Xn#|DQdGMs&|DaBvo<1XFjP372ub)X*=(t_KAQ18u39q8W zUyI4NW}|qi9VB&~1FrGxc!ypx_!FFe<>o*iJt?am%#A?HOSE9`nBNlc2st=7hDqXZ zd``IeblRI*%gdJ4HN2MlB;(cUbN6G8chG(1BpG6qU*ef|aABEB0<^=HtLEt}>@r(h z_w?~q+}n#z8QIXmQpl-GiYssG1KrcWv&z!8gzGW>TfvcjzKoy+%?tx3GGJh{w9`I- zOGpFz0Ie0kz)eNA4nqT>?=ySM`O`+)7x~^viBSxXwz+9WekUw{mZBK{QhUN!(YF=K zF78zFx?`6^tHJxv=h3}59$zBC;%8X#9LYa&meyB`-vsLME4!SHJ#z$u=!ZiZSGAk5rHZAHKtLzzJ7f{FlytkKI3;@#XjTvf; z>-ZL^aS`_}|4jdyio2#7+r9xjxF11pgb3KAc=OGf3GywM)il>e5^ryvXczu77@=y| ztchCPu`?U#QMM%KkGtkuYevdgcX`n1 z4w((B-W?W<>wjNJ=G_MXEpgGjMPujHDz84U?ENvbpf_{+W)**Ct4d5xgj|lnp#17a zP>cndR^IT;_e?Hw%5En zlZMBvm~J$j20)2BupmBL5x}q}c7NrQSC2nKN<@xp^gHUiOlEv7$wAd>xs_8+TvR>p zd51NhdfmOwgWls5nsD=8j{oxG`M=4WhqrD=R-5TJ@O3H?5zRK)hUwi`bn-Shd)8sm zRp-~GhI%I1^(4aC0B>_GORIUQw|0n4IXPD8rQMk1L1Xz8{Or#60L z$3Lf&n@lOZYfoup_ei&mEt|^OW(SwsT=pQN1KUi%W?L;B*9Z@;6+0$ZDu8Neaw~F` zuvh#Y&ird4T57mBL(_2TH%`!??Nt#l(HNF5c>BX}%PcS+-ZBhM5=dk|EqW#p|PJ+;we|uub&ANi**iU<2mT?_pa=$xDOOYxSWs3qagmDg+qz-1p|SF z^~|1Wu;tY1uDV*+cZFWL;q=0esmoVl`xTipP|p-nz^m!g{+mkl_lCC@C>hpG1Re=g zjG^i{dMb{}UMs9NvbE1bv&DTktok~nq`}uW{N=r}V*&hhy-9&IkIY#e-~tnhn5JU` z&8K2l1%W6btNEcdTik>O^YR-pqi$*XGrNihJ(@*+SZ$f!J@2S|W(s|<2t@u~hiv}$ zx`jKxdCr`wf!LT;^8?yJT^%lab+d@9(4Brh15?W^lfD(YSnV&BuH`9HgSEU}Waeu- zJBrWwJ;Ld{4(WtXXocxbfcO@7&p7D4wjY+-`sbLMw7W03S8OAx$9#-O{LrdQ8`TyQEOv>GVrWDhfypz=zDNjK> zMo-y^6_LQpfto#QonL|c10t7xUzH~)!$36Df#h9R%%#<28>==8MZupF06rI@AKi{b z(8p9&tMH_dMfYn+#$0;l66!7RgFYJ&7?$Jbs2`_-W@W_Is&FNNo5`U5JupBA!%vtoTE zMD*{vZ297ck0SDc`9HL#vE*JMrW1SDl~J|eh2Ekr0TO7rhm=SKLBw}Bcf$`Jg2?xW z);t6gvFnVRi<(tYgR5r4)&=Au;Pz=Bf4>jKr&9u~u*5%ZJpn9#{_22WqFKBsH+1vE~#jn8^x6ZDq0}>?o7tZ?F(nEnf4! zGqbf&{bobxYIEK+r!TqIIn8h6uiWE>el^jCDkF7ZyxAgKu_uC7BbU%blu8~-H{~mq z%%NF$d;@Z>I_NmH5urp3tQq*@@%Zi@^-!pb|5sBDpdgi=|w3afPysXz4xkA=^Yg!(gmb9 z0qIhthY}=o!2lv151 z5@|n4UxMX|-^XsOU0L_L_d)ZzZJ8P;OSORR3-eiofJVnp?-ySCD?I-T<&G;s$M5AS zb(%rjS9Md)=764uO-$(vyYZfWI|$M_6y!TIR|yP%BwY15eW_}cWXnRVNBWo(<`*0d z+}ix)wb|nm^MDxT@eK+NesZpUx}SHJOws)_N)dF_VcKoM&l}n81oB^?w_g|q zjpm3>!1KhcLe6sjb{8M-4tRMxMt1-u*{9ZYryAA!S+Q@~pl@;u@t%dZt|fU(r2MEG zeR?m0MCqc+X8c{w+E{~uk|N0sT4Vkdw|-1`RlUphgWpxn#MGvE-1XhIrs!amm_*H- zxyyXtZz<;W+www={g9k@Q(UN;$ zJFTl+SQo2$3g@JSk))*@k5f9)x-@=7BcWOq__%k1M?6HcK zBQ?L%T~GK_13vuTcm>yH({`F+s-1(zrve?u0viAG^1orJ9G`&v2o76v0bW!UABw0F ztE4Y{XUT%AP^NkCEe*vzI~l_fc7PO+)c;Y8%UQtQbP^#($!Fx{@<}J>UpYGfzM1un z3d0D_@Gi>AK>CQ_y@~4fd_@CVf)rF|-m z{~?cJ5V}Gur(^zfmv4djTwxxe08pa=_LCRA9d&efwF77tAI9y{3LEGzLf>N(9dv*Nzel^~**7b9X6e7fQvx{2MY< zgc+?E511Be;$y<>Laob@s(sC;-z5ChM)U^n8g5TCP>d74-97xs6GRjVyTChDws_-T zp6}kmh)wx{5+2ee=$@BjC~(dR@7EOOgB%NcijrxZ&*AY6=f=UG2riS~aPr&!R5`~k z>47~t55D7cfu&Bjl1<2{`gwMgR9}E$*DcCB6IXyShDte|R-6h=@Rh`zvC$8?{fEiUg3&lewNqK9 zlkqu^>_4JD$nZ1E!}ICLm5Hpo=h-7OE7S3*l2@HOd>AGuWG#$eD`os?^(+7R*spA4 zCgB$_)h1~9VpGeRZzuS3;GuS6EaYnga`Y1#efU5grlE}tt$R5NY{Q)F`p)7P;C1N! z*NNrv;~W2adS7`~JrF#_{kHm1AmGrVXSd=?%yO#0>CVT>FKTXg(6Bl5rMf#iXp|qK z{j}-5!>!p2$MT$jpl515QsiNMO(n?$x)@E`MHjbOBS z3E1$E7G4|=JvVW}Y&MY|&>~A1E8!ddJwMKgX8wZn;{+8}zLW0Ei{)K~zgg&dshh9I z@p>{)OqB|gXhmDiCd_=j^=87SaDr1axK{X27f_jvUEc`Zb@92irb$QtFDc+hh=iZd ztp~OE848EYRjZXVF^B2#&ZV5?#5f(Uy2DNcz#pWNG?|`5n$22cpz}j@EinO_enK?3 zhv{fP0PHhtB+*ndF(jei0n=_#nH-%*8~&Okn%cc6tQP7-v%5Gjn~$5=7MG}8W5MkzbbMFCEY~9(h$x1 z)Gom3lD-4QjPiziDV+^lJT-Br#F#>9Wget&=bmrr)@(-sMiblmxnRNq=r3dW{W~;Z z&th$O(I~s|`Du8eO;Vy9ClhFc;E~G5q`o@f&sh;!yQ2nwuikF+;2g%1&dcXLiSgVHbT{m`R zGXW9zvKT;oK<+1k_73VQv{~XDy=!LVgwdZ+e6r99=3R&XTwN)zi@!q}cAUGB!}cX> z!)PkBAxr!mUMB!Hw{-}H1tWWHu|PGp3dm^j&pLKiOl6IL`oxR4%KxSSE5*v~L_6i- zk<3p{Jj~;wJ4QNvtZZve_p-iEy40_Sg7l9Ns_J80H+b0T|9hDECPfUeBuWxI*M>;v z>KEw6Ei;<~y~fMwo?o+F7Iu>r>SS(>f9Z%9uFB3{5NPva1@}sK33}jO(_IABc#48<4)e#F>e2OIpcP2(d*R%nCe5k#c+ zr46UmhsyTf&KE=@R$+E4HJPY&m#~W_Z&U*#leq;kn{juzkoS9mYjLomJhBGgIozo z4TfZ1$E!kqe9S9Innkl|$szL)_PG$>h#+LV7jEZe6##pIzl9W8Z&^MPs6W`?oe2Hz zLy2V0m;@Rpde~rWB%|^1=j~v}$dMUbi-M4ZAEmlW$0VdKXqngBex>*F-}5>yqb_dp zsP&>GyMZm(xl#9KM~;VjXoFyNuTp7|5Rpq^FCQp&tAr7YPqeBB`*{9Q;{WZ5RaIw9 zV=gnw`p-3rN>rfB&CgdP=lP&n`8`tzFrFfNF4Fw*q)5>tSeO8BT zoxGkL0vx3&^p6!7uaE?-(&!)8RsPe`Wj(w0S^8}BBB;7qhu=RNcq+qC=)CHMxmoVZ zLo+El;4L@oSexk0nMek`t?zvJ+0FVmw9(4rx7B^!e`nV(_gieyy8X7&v5d+0DI?Wd)~vAisK5r!r~w$fQ1pxVXdJ)XIJ>z>|}u?j`q7z zb8!C7gu*{5YzvyV6H&Qwv5^|;FK$9=rkro80!O7eSHqBCB{(jp#?E%b$+4!OoneufABo>P?*LXznSrun*A0{r9Wz2zfTCx zlTv7!_qbC$P7$`&a(FXzN>Y^uPE3lMB_UNZ9P%a|oIIm;(q4tKd=x_%q0h_4O5A(_$3er^^VSixX@Tg*CZga?><#n$LX8Hq3SS#%4tC>tk6U z@1Z&r^Q{7C4bjkA7~7qxRw7coSSznZIRPq&r%sr~UPX4sNtD^6ZJ>KnsHop;-dsg+ zl_Nc0t~?wvf#&Q^nkm|!MjOtV?T^@BT*Ft;u(v$HV@R(HTIu$k)NvU9@EJC+usP99sO9>Ih{L{Hv)(*$dFYXvLq*=kq;hzpl1 z*+&Q$vB!-4y@jNjXeiI;keZ7ewE%oScUhG)`JTz8a>oV2dlvFBjV(2{LTvJmw+usc zq|T$XYS$o;zW?BK@#1-7;g9cy|DA-4>|ZA32#oi5zIaodnQ39AXLZG#e!&y_IA8~R zsR?&yDSlC@8NYDJFupL$&ojMMlgw+l=gAiT0nikBcrxu5`2JXv0@NCyh}nr*kOx&g zgoN(Q+=|V&dVJAvTQ6SK?YNWRQP2lF+!nCb6OOh@M z$&%mVMwzUdj)*TV1_?yEB+?AeI7aZQoWJ(ugqQ?5esl)=EYBqY2=rLgb?mv3`AO8j z9n?!7G@s09jt2~fa*~hT5-CLnB_;~8v%B+I#=$~m>jXD#;sF(+cJ7)%Xw_*% zrXyHX910Ap4^@`sUP{xFzpaBAapXSG!##!_h0``Jnv!Y=JkaO+ck$f^QO|%+3uG+; zsw3Y`Cpw3uz!~=PT*;arlz19_g^sVUucV~cmBc*glS+3^r`VNKv_J*y2RpWnrTPk? z!QOa*ca*!)b$}ENXxDb|zCBk~iZb)MK)zEO%gtj!LKk%dKW)WUS&~Fzx8x*N zT4Iy`kwvhZG(Y&^Z8d1Qv(Q8(5LP2;v*}q&ybhfMZ_SDBth0;iX@G;>Vvo;OD{I_Ifv0r+)0 z21vq#N8dD*#Qc|r^xh&R?Eu1b^t;r2kD4j)Mfo7VCQ(B0G@_=2S$|@bl+=C0?pyhf zadVDU`am&YM*Ef(&^sWSNw22UZ!VFSn4+bMr$YlAps-V9P&oHmk4 z9bA{3qSKeSOp29%Ae~66m^7^>k{GA&XH{)9hR`^7>Kt&Fwfqn2K`nvZ<<3TC;@ioa zLqDVW=8SDpd+u{;esIrjF|#{>-d~Xz8OWB3=|=!eClIZLntpR!fM)7@=xqALEHZcl z@ZXOP?`B8HyP_Y|*HmL7_8MX&iDYQr7s<`<_v@3Qb-!#!f%T6_5j-c(eSF48BIO>@ zNN0)=yJC>6MvSC*u;o966uYA}M)R!sVhd~d@8vd-+OrK<2{BBHc5;w6E`tIyQ+;m8 zmpK)@?+LRV-|wFd`+-0<3P=c<0b|YnMIrgR&QOpkm&XvZ#n$3gBZLSKaAvF(;%h6* zh6Y(#FPZki#8$FjI6f2Sn>*P}`jeutqVP& z#wbm}mMI~2Ssll&v13N^MrJA$QTi%~Rnmmkh~lZpFTxfcxHb z+e5u#f_FSQ#OWf8;gQe@8L`>h!4)>%RnyVs?WbHR4Yb}XbLCt>wOM*u|Jc<%e+#XA z(fEt{xbQn05hEv({Udzfiuq!4%vzb8K$TqT2ivU5M5%Ip7j zsxNCbdIY4p)4UC=bf{I4&O|B} zeDM(bfCMN(e1Eh_XFZOJiYKbN#z|cMdok^q#9E{_FO4W#kPv5nTr49x#7`I53^9TC zIxf>UrS&!!N_6KWcbR3dEL8Eb&$T;HX9Ru-@{7M9R*V(SMM5?$J=G*$OY<@9cmM;p zr{8XTsFqHzz;j)O)oG7!-4f`e%Oh@?kv!flm_3vRIim@=hC^o|hk%{|P^)hf(^+pG zO6y;sBSg_hA5P`2Nh65W(?977d5h(Yh(hg%6OOpvjA|9!tn#Ikx&=YXwp!sHhu!!8 z_xyct@wnxI|L;u)9c-wB33_0Nu_a^KpDy`$kZLOEQ1p12WrbV1E4#HN{f)sSLZBP( zwle)CFCa_m0@d1~0Oa{SC(f7gk|1Rd__e2mv}Ldoh8&RbKc zYoxe8o#DKBf7E@54k%gvRTD~JTi6vTFfT=O@C{7H` zqf0apm|#xYCnbVeAMYlw|3f>3 zK(G5DaWd}ZbA~$sbRwP`q;s|F*9F}3KE(##xkF$Q*+mr2!R(WGu@*~J=6%h_-2gW@ zYYBz2y|4<|)!y33_;q%5!L=Ol0LhA&2#tM|Nz-=lp&&f#%5F7-J~lPF0c&~4HcTmb z?eJQ$`xC{pUrRvD(Fh`(^%Uv=g9p*GS~=$-L^<~ik5jyCHsAZxseqX%BSp47n_t~# z{@x8LMRvBa#8SW6@xP}{JkC_#KsCX_99H%m^CLgH;eTMs6b1yGdOlBA1(b8o@xHgAR z*PE-%{BXvJ(D@!9C&-#cScQ{Dv}21=MojurDL;}i{!bG)*$1oya=+c*9e>j5 zquEd+f4;9X_G{#SbpG*WWp~yj^ISV3768UGSj`UQAO*PQGl9=Khl&E{-%5Pu)z)c%bmM^?)I!-tvt zE?&_sP#mqvaqsMP{aIhZc~*bl-kH2?B_gj7xr+q%rZRluvU6C-1rR&nVjb>gEtCY< zW84923uM2_UsF!^7Apf(K>lDFlIvdi>n8C~M$@SbebT@Ky;kV|qX~W1v49k8tgMJ% z{GPGN%x5dm>4W<`xNP&ef_G!a)1@!|k;TDR4!vKOZ@k&5Aymut@>txIXV5YDR9fC5 z5UW+tsAl}6=tXV$n^6U0TPclqa%Yc7jW^^B^GiY6#KuIoGVk&Rhxx^9)-L%6eCxvU z&Ij=kASd2?(%3U34tv?+?p0Q;SAZu6(dCamig7_-JGQpKX?m`;$Y{(e?%}jcV zmWA2=2MI58SDbX2g+T1hg=Wz+W3bQs0eb5sf!#9lkp{Ba19uXPX~pjs8f5c*`4~T zoDUOCe;YJ+?)ZE-)mum|af#@{_VVs)gJ1tIYvaWoPHol69a&`TlnJl=z7rx_9Z(lR zT>m3+B6{c;$CfM@D~jfn^Y{NyMrx}T)YN^Fa+{PoMy*e3k=33SqVKSh)C^fTbgtNT z^drVZLhnJoLMuElT~HY6H>qmf_ar%Yk|38DS(lE@`F7>a{7H+a#H<0kaGBRiu3&Qd zhp+UHszv0E2*|g_TZ24IG}VadU`S?+&r;cIXKIurO+$RDe4@V?t+T3a9({EXc2SVB z7Sqb;awaQV8A9{PkufSfBKaQ=37&DWgt{26P{q0m+}F-^}MfWw)z-Nu^~@O1MH_G6J9c9&ZGn``L%emg;wcN3irhC zZ`q>kOoQ_IbNj{Wzs`Xl_5ZQ-U*G$lG6PsOFnU~~$Oc?D9$%@vFy6Q!^YIek21I$~ zG0_d;D^wA$eYRZ2Ew`9$Po6;~kNYy5+Yxi7_D_FtR3fQ598obeobKvEXiw+^)kQVt za-m9a=gBe&?P`?}GcI--M5>V>j4z7z@CjXhReEcOvd1-mLQIq%J>SVrmsN?Z{we40 z8X1fYN8A)}-TZNszx$+OP*c|1ch;)(dU-tV&&Tm1&lMp9tR&C9Gb=~nklsg2@`%xI zj_S2Y&nCVZd#6xys#+pfrYA!6jLT-gh%-~OTW5b0%?H`lfmQrjB$yB{j^1E;b@jsU z^ZjGq?Ms61UsG~l*o%Z;8hL&B&DU$zR_6SC?pUc|-P2_euTIWp%>?1KW_uw{o}rja zX__C>t92Zn(cFu*bV^gq2=k1+Zoy;=M%}65?wrSiK$a*oeQ zbB7k;bJQ6iYU6TK4P87Z0hp3x_E!4XYVPg+;WKp~Zr0XX^M&P?e_ox2M{8Uqw&tlX z3jOvREF%+KW1V_D_Rh#f=q7y4tm-{!r%HF}+iV}wn*!`KD!OTdb}fZx`|e9acPq|b zgqw#Q=QVuN3T#Z20Q|R;FI;dEl}-LS^GDuyyn?Ef=8TC9kT|@T_FLu{iDw@$k$9 zPU(q#Dv>7grI)db5X&vpkON}mj129(tl5%rI-%tL^z&~?@vb^kr=Tz3>ZS^c{nves za;;b|F!@)jlixu?^c`DW>%lKS0!!3+#e3gvFnLI6Y|UTko?d^k&YsGBmS-hS z;_1p z>w+c^i}g%m^Db8WVD86mF+X3K{VV$39jO}yC*x6_Eg8u<~o!&qNke~<%i8~1v^a&6Au`%{5Sv(^4 z@S&e|_lrAIkHnwxUGi=dy`2I>gOf2!}-WM!=Qc#dZ_NGWXOnw;S_QTfon@X5l*$dz|)2W~cP!GjDrW z&)UQnG2MlUcBT9YIhGLZcW=QnwVU~J0?C7p1z#GU7Fdk0{uI11{pU*+RUOs-Mpa*n z%h$^i9#;V4F_QW#fcMy)qgPh|OAT4#OU(3liPdi2n22+&@FNj(JE3U9dnUpft3<@x z>g%0=KR`ejx1!HU*(sH9H=^|x;+#ihleUOC#n>w31MncmelYSV=pG7M=%xH|SsQoT zFUEt$;k(X3N1eQNRo8d;*?gHjvJzKwdR!|M(XodTpph{wQ+5@XJIY{nrCSl{h`t#L z)>L@WO3v_@I4$%F?gV}I)>w!5obvy){gn9V){`CLt&0um+vIBbmQ&8%6kX0EE-s!K zALv!G&J!3mt&-`o`6D|ATD;llt(vM=O^#%|JmX;8sFS9ZmiR#@GqJlULg<^2EmPX9 zD~)`EZ?uTs35Rxxbr*fmc_u3NV4vIe_>2th%S>2b%9F$f!*@bhOasr3wxv3bA%-@; z7s}?P7u`c~hP)fcQw9C3Xy4g_o;7HOprBd3(cn^Ezs#deCR={Yu6?R(T@HyXI;92%~#L1T9OGzEra+qh4SLPuSxI^a&DT`{@Ae1}2;LT9VY z7$-hz^$2&;e>^_}B&g#eZ1W?-JM|>?wheDvnvZ;hdS-B2h!rXPm@cIyr&%!JOec9x z(7Km|{Du1w<#fsY`PmRwW?-Vy*_y~w+OT6G&N9SiAn86(t$ginh(f4!>Kbu>rWOOe zMrdF@dgwHBKLtg}6?^FVbGK20pwBRc+q&1dSIspky^6tj_>*`+)@43kC5eq~Zbws} z4^%A)9tYsQmcs11m+4*&6fYMBtTlKjU+(riLC!z?77$&S?bj!)(V5*4rp(er^>8*rekaECno*@?}}d{-;$C)MGk@o$VwJj=&#yRq zy-Et$;obb}xYhiMnB(qMzzkau$V@Lt;;$@1msxH=`&#M4ij$24d525_lBZ2t(?^V~ zwfvo-lEc7s<~0yif{)1apo{^!*7P86L45WDQCy?6T4%b+nhhM)K7=;3P6mY)Mk829 zX;vz@gDdh8=voGz?8BP3cR=Syk(V*zV>D;6RohZ62=pWkZ8>+gsNRv>aQiq7iY=pO zrK{FBkN{JGo;#6&!U9U@0QQ}NTQ)jg zjLBKiL-QsPi2Ez9ydu|aU-%^wwp-8O0uFjm+z1UVu;|u&gv?e52peXJ>2u(QtWXf2 z?-eoVAAPRcCfBDv`;?D3-~6lto!9b@vh$tJgCZ8cCxc|oiJ**GG?}ju(BqFD)?Cyw z^HQ!UUilt;Zl66KGO87i5!zC}-T&wzmi1F7U(JG40R*#R`w-jfsjKh5r~iKzY^DC7Jx=P!Awv%nCjRd;1oJX!BLXh_U1&YYG&a)Z581Ih0H_kE!~ik z{OwGNVk@*&Bo>uAG?Ln!~pHR{mSuu6``q;X}Lrda;88sX9={?=%2M}rt5LB*cTl?t273#ezXUZL|R#+ZZ^I$u5 zJiM&|C*;n^`U^V2!t#QW=~qrkV8GA@ z44dK62JYUK1l`I@r4Rh zO=y#&Dn*`3a-RSUksNVN(`7Smzz{svPsl?zEw1;aC7o(_y%T5FcoN3!i7FN}tyb_p z#;)MGIc9f||MvpsaNRI(eaug2^?%sh%EJ2aFE;NM8I>dkk?h$%^0JPz=wZM<9QfGv zgI6vfk@k)AT=;sE%ba0pY3DL>Uc5g*EY1Ukm(PA9yx)BZJL?&Nr(_nM>oodU!LO%% zY$D%)HP@~1voI>!@o&&71iMd;r8?4w~_p;MO|2m+IQxeJ}qb+~|ojT;& ziv$3|n>e=%9m82nR)`9ye4gldDY5b7sQs6FbK=FC|h zims|gS`{7Z zgPgir61ToN57=>3-`|DW-Aj$7V}q96u*Fkqz3kv3Dl_Q(Qw9_B>gGiw%2*rC^ zQxBM^H9&Mw<$K!w)NUY3XqsHU)`MW`(CAA&-5uFc>yY5s#YuRVu|dMi3G=ROS3EtJ z8Tc8{=@7!Y$b5qxoJCLpp#G0-VlGSnNUv(CZvhMmo~UqkV3&PvJRiNOk9FZLv%l-Z_F=#=d8e!} zIc~#r>ha67sBaTx-(d^E|Naz716F-gokw(9IfuZwL|~foreJ|z?$k8Bv!ljdch$4R zsLK1Cada7wk=H0#a=@RN3_n#aU7_Ntbt`x-#R?j2JtY;^7^-7!N(0Rk(mN95e@m{o zbvjP?-EbMxI#z+V)GI?8m|IsxBUH&~D5C53kQUr0f97kA8|P%oY7TF4pEOwVJbaBJ ztZd5E1s}5hSeX+YGmxYM5$X0SaZr^Pi{9Z+9qw*$0y9khfXbPv_SV9@0ecO)THSWM z;vTY{5zq?#nIQGk0IN-Mf<&)tTIbj3OmSZWNkPLADwu+s4>&*>2z5;UwhlKn4>SRx zdF$gxKj|F`ee6~tQ`iB$qp{>SW9>3m`92BE3AeuEr@DQ5tcmXjDkU`L)0VJ?g%|=R z-qscHOg{T?NvH&PoW%MGBeutbh1rQS!tG#7Q-#gu{29uLaWFVTR(`W<`WYIV8&tWM#-3nxuiDBhP^Fg`R%5-gY+qyaJfm%OqkT zm8!uI4LOPPm}>e8Dsy@#v`OQLtoH<7vuA0THMCa?!jlHdNJc|j z*}xE6;=nzZLPlJzC;K(G9O*tUQC0&aVwOOD1v3`IZb@@xm=A~0g2ICK^`Y)(k`Lzg zKFCpdd&`Xz(4$h8lMPnDBm4=>6?>SoIPK5I6x~8}e0z3tQ)IHhp6BkG1+u*$4Zr8>g;I@_1m0lWY&Onc5X-tH`a5UCr=j}zs}y6L z53AnhLdL6_dIO)W(TciFQP#aiA&99D>RN-KC27OC?~a{M=sRCA_%ZM<@u>f56xt=# z!@38SRC;Q}>)Cu^c!AaJv|=m9h~11+5=;A$w4-b^xFR>|Eki@GQmZS)7-Z>cO^0%# z4-H+{yP3vmbpry7l&QIV`Jbub?;c+f`D>+pz4Wh@Hm5~s0a!eh9vzDcP?f5K$D&|m z-4VJ}M6*DzS#qY+taNw= z&-tj&BBiJ^AN#Ei`3vm5ej*>8_~ z&3FmnoJGm;A+$S&W7caDaQVXLTxxR#vn4y z>p&897S8j+(!KPcWBfBRL2ufx>SsAJ&8K0R=f$wNUhBMA%*_1(KJdZgP4pumwd$}W zY3LX)8M*fJY*VAue6IkECJX|Xm$V_-dJ!eb!1qan;PjNVr`DQ3Q*JR)Wx-r0%9~AT zMrq-?`6edJ)%vQee>I#im$2U(UZqa$0Ln$kSr^r46I1(wh@i;`g?l>6)FRLbSmc`TUX_64=#SNFU$#RFo8+ zI`-+wMDdg)%ogSZ&WOL!U?%Y4M*&8{)%Rlw^7`U~*r$v6X3B;^-!o|&_vrA4k8nR2Da|rp z-P!(n1sHvB4H%L1>zcsbDGh$2ov{f7-9+9Q%rf%`(0PoyS=~-Kz>berjQgiW&5$^qO4Wc5(4uw&t)b>`H%; z%d5|HzzA;Jm#>qUBaYlu)q2bNuoEMt*@?Nn=+SH)B}e7a+GUgxe|ew!tY>Uh{LL+iY~ttn!5o=GMFrD#PA?V%PKsy^%>Gz}xVvYetVjOy z1?DUdll7rjzsl4RoL*-}zw~+jG@CpGs1gG61I0Z zw=opiT9^l<9e^Pld_N>R%;jrMS99-ne%uDxyv4u^SI98muEq+)@2}-RR!~^XhVwI;0M5WOwp~tuC9l zpzCpfqTjvG+5S!!TGL!^k;%r}RDSu8!sPdc^`eKx6f4G-HmDe80NZG32`7AW9qK6V zs)6K9(wsTbKxG|Dw>7^vWG-zS@3V8Hbe>gG`5x9F>jap-n>;r{4ff8)k4U z#c6)rHCNZ*#U~f}@?#%?bB6H%G=Sw~GeVsq^@(adi)D6NqzXTp&q<4tOu&)gobTO; ze2<4|Lk{6B2-E!SqeROu@GYu86F1DWxQD1AP$=S=b>U6!-_#T!E@+J0r}x};AaQ6U zLN#Bjlv)NPtMJOk#@UwD)zwAzD<0>IPu+cj0*Ah8+MF6D4Wb;G=$&>$!B+TDKF?(Z z4JdYXHlA2m_@?{eHddt5D7I0>SKEMedEhcieCGSHxHqGl>j?}qR?zz$@BFAq3?%w< z3(k9Y&T>Qk^)Y$E^*k}XV{ji|T;I#m5P7b0d|#Sox%7_mJpMaKz4YPbhOhuY`#9hS z6hs1YLi|q=dlx3(U$1VQYkV-W)S)(Zf z|68#eg#Nf53=iK?m^;s(FfmmfGMM=uvD+=rxFMBJl`{3n#BSlo?PTBj#WTwB08&61 z%7%|m1%i{0Z6aZ!QS{tkaXq-BYP+w3`{Y$VBGRq2Xj>Mk)!cUX?!$@o9MQL?^w;H~ zUUf{QXFX#{gyMuVhH=;?K?PckrhwA9>0JaR?%(HZ)Nst7i+ifHzR>H)HJ~h(PUZkI zsnY7#@8gEoYi`zhY-9XvUk88ZDy-F4R|` zIfy5iPafLusBc5nFvZ(i{tu`@DF`J@39;5lD_lZiC((5eNp~zcICB|k!m#85STZAK zqEIlubT1aEZ!is3O!s0}cD+w}vZ{Rc@d7b+jd7{1$Qpn{1AX7)_bEKiM0?vsMNj%0 z(ORxm4nDaAh)$8fU0@V#LgDX;abH92pv3%{y!1`ABEa_+ol}aWoZAax9A+%tX~t=! zaE?fw;|$k~ws|iu>o{zp*GoqC9kz~yKe4cvote_P%wMe7er(!|FYo(Xk8yaS6@5uh5bX#;h(D5)OQp^pqt*(bf&xw|8hy0WZJ68I8f}t>} z{OuAkb<^eSU^}Tykx-uc_n^0h(08f*oC`O>bQ3>D?c@eANI?@~2d@mkUH4$6f#2mc z`t7(BkM8jAYA2z~qQ72W2i(Mofr*j8Y{kddZohwhF^mLgNL~yh5&F_;#s(es6%J*j zzA=$2;9xr16H!E!SK4CafV>Qz-m+OZ6W}x#5Dt)s!d`AZU$;S#=csi~b#s+#$YFMp zFuM7aYx+iI1>4TR>p2{v1*Jr@+=Lr$bgnSBAR^2E!mwEp#^u{|qgEaj+E95?PFWma z8K(>nNcbCkJL!MtJ07s6H-3|poYr6n&M6P`HX~grcsXOG;k;%gly6Tut;fL_ z-OJWlw%G9n$P^pVLzaH^pZ^o&SQHg0isBbJ1Hh*c1&4M$yT{OzGTOq#y=>>|G6R>`fTx8b?W0E`LC||0ByOO6MEcf|qnCC1?C25ec8n;> z3q^MmIWZFvG_u`l85^`TJnXCJnPBHD9dt#%xm(i!L$n$rnv1T!DCxA0=4bP_zZU{0 zRva`rWf|X_Oi;d8zPy-5YPORlsXj)1YgTGMBY@3}(<$W*0&6X$+ZdyfgCN&La{G$Romf5))8_T9Dp??FPtbTv<>-hX2 z8G-%i!A^}D9r#@FY{<7IoCM3IQ9kS~jHRcxAb~dJgXA3p?1{Vz*8>}UL~ue3B(Y^1 zBg!9Bj}848#BMY0^8_2xK;?m>$`(9QEx67~No-l&pu3}MZmmiKrFyGB<&aSXa(_Rx pV7u3I$K5?UcxnN~!hOMm@5L`Lk7SOXt*YB&v#4+3k?GqnrFkhS9P!$_MBwo2Qi_&R z2(Cg5f(Yaz0`bYyWKaeA7M2P$Msx>JQR5wS17aB5mZ#~C9~2W_2}^-i$TFCrDFb9< zw+Z6)d+>4s53QOWCYP-Bi98YQOHWkys9*0BASEp07d= zKtMMEmM2(&mfzl~lR`xt7KBdLB*jeDCB2}~?!xTdKC&vLU^xxsYGu-+Tz#xZLjg1X ze5_Xx>;l0qP;p!UN!e8T^k((|i&Mp~*Y^PqcJ&XITZ^p`i8No0Xe<(~+G2&3{8|kX z>CsXU)T{hB_pB$I7Y;Ixv}HQn5?S#+@zvTIjDAph^W95BuRVduwp75Xg)RG{VY<4Tl~#&vjqQ`d1Sfhr2Td?gEUtZWVx@J*tWwi@7@K#PuHe_0C7mwvsGD)JBdeTmN zLQR|44fU4bF_FesIgAgFq=ohk>+Pipx8!l@eGwZClitHKd9Hj4b+JbtIC&+B8PQ>r zJUE{~OsxrvN)?|8Rq!f~;4>%IY5#;+q9S0ib&M#IzD44(NgU~3`g*);=jx`qs-8Xg z;Nbd=%)%ePW`4;$^5%y_12czCG3r=$IqWVQuyZU&j*|T49}jrbY=G9@%Lv=9vJxk_d&3`s{L7fa_*i3+h44W w{_F`-$sVsAIWI8D;lE#@ShqppfbCIqu1Xd9twz%h-7V5<~M7wg!pv=cBceFBfRcxLL)a zdw?wnIS*10t)RnR6s@eni!P!tKbKg_D-I&NfKVZWQB@?=3Qa~MmYM3;E=o=o;((eP z_nZx46WSUArjt&MZx2Asm3THKPh!kSxI6c|9ORT z0vhShgBzyYWSY)Ym`tgDh6He~oP$1ms0KF*u*A};U>-uFd8`}mlU5{|EoV#(&PgN3$Uv0_prVY! zfhsLHErhF&OxS)?#(RKD?F9p1p;hqMKob*iED@~BM$BX9fv6F%!;^$ojJl~K2q0yG z00wloK5HX$jlze?UAab4!vau6N_7r6s7;jcXqoUK>SA3Z4i=pXz@pQJ!8ZDiCWyo~ zI&GL>`R6L&6``W0k5;XNBa)5Ahle08x~E!g%5s@+dZ-%!D&VhXPQy?pa^Ql5FpXR8)s_0(SU@r9ZV6%$tf(< zTX!&t)RNQBn=F@ATINSm@73Nma##zUJ>;zWfk~O86Qo%_wP<*3Iq^|x_WH)C8wT?0-^HvV%vNE5h|8|lt zQGlwA;$n%e61t#Aml)syT`KWw6S|Jd0n#3sMs*>@)jTrAquyDK3LADYHlhktsp*sU zq1_Ed>cau1UcHT|S~Y?$@}rB99i`|;@z9N3-*7;v(9t3QRP04A(LFv$>WB8~?7%v}l7Yx`R4qe=Zy6eE znc=68t2RA-8l6M{J6xcJNuSgc&_+x|rG~A#VY@=$p+N27y8iNH6e9)(=`$S^Z#9@qMxGQ;(Dv)eU}B zv^?SA4Daf=cSnEzc}C4=Gn2b7=T6=KaMzD#&wSxud)n*P=22h%`jvC%KWGvE-7qO$ zZ2I$3Thrze{>mQ;ADWNuy)WKy zZuvuF+K*;FZ*GiV*|49?u*9!zU$yi4cN4pN2AUpYZrw`zqC{!xqvP)Xr1@B;HE&0T z^Xf)t;?FwcyT@gvv|PS+?H})~tE?=!X+7yq?W7+4;^Kq@$IiDG^e)-fd}_`3m&Mxc zCr#Z=7q)&}{#EDdm-}wdY`py@NyhcZoSm?)VVup*l`t}m&NyHUD(-?of1rw-q$TfXUm^zo%D?$2o5 j8hZDUd@}4|yS^svlL=Gt+C&-sVyD z{l2g2`+xs>v3cO$2PfbH!H@f98@cfIrb8O-c92%p2r@CTv?XmPN^EbQeYdd5;_SgDq&D`z%P-A=MVq$4vo#RXMbz!K zrB#c%-kTbX?I3SnjqYG2_C8FZ19*bKw_iuV_CT{sk)Na+2R4+z9Z0Cn^J(}UWHuOC zByM|DWL;x_g1Ma_@R0>528+a|{rOHSVWDLqtKoaF|CMA<2i2;CWN%g>xun4(>HPo+;&CiIVG z0R(P58t`A?iw9z^y0@sC1 zdb|__&9*$w73tp6qn6_{J-l;z>EMyouAxQFw{b#3XnO;;|S zIDhHYIr8VPW1o#}`EkeE)B3unfB9zj@-}mN_T-s!XRRX#KE80|-Q!QZ{a5{mx4^-P z@89#z{Ie1N`&4(^#^<+w-o9b1dF8vmgQMTRaCz6R;M%bd9{cc(`r-3mEKS}%tM6UE zwSIl~Z})FF_4Ca?macwD$Jd=0uRZ+cvB~k8v9$31p5up)&eM1Pb3fYf)44m#)la{^ Tbn26d-!CjbdF#%7{Tbc`Y)2QL diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/warehouse/metadata/00001-df837cca-ef7e-4f80-a36a-2487795f98b3.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/warehouse/metadata/00001-df837cca-ef7e-4f80-a36a-2487795f98b3.metadata.json deleted file mode 100644 index 8ada478e38e7..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/warehouse/metadata/00001-df837cca-ef7e-4f80-a36a-2487795f98b3.metadata.json +++ /dev/null @@ -1,221 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "395ab586-8db1-4e23-a5f2-09baa77292e4", - "location" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-orc-part/warehouse", - "last-updated-ms" : 1663708904266, - "last-column-id" : 14, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "w_warehouse_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "w_warehouse_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "w_warehouse_name", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "w_warehouse_sq_ft", - "required" : false, - "type" : "int" - }, { - "id" : 5, - "name" : "w_street_number", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "w_street_name", - "required" : false, - "type" : "string" - }, { - "id" : 7, - "name" : "w_street_type", - "required" : false, - "type" : "string" - }, { - "id" : 8, - "name" : "w_suite_number", - "required" : false, - "type" : "string" - }, { - "id" : 9, - "name" : "w_city", - "required" : false, - "type" : "string" - }, { - "id" : 10, - "name" : "w_county", - "required" : false, - "type" : "string" - }, { - "id" : 11, - "name" : "w_state", - "required" : false, - "type" : "string" - }, { - "id" : 12, - "name" : "w_zip", - "required" : false, - "type" : "string" - }, { - "id" : 13, - "name" : "w_country", - "required" : false, - "type" : "string" - }, { - "id" : 14, - "name" : "w_gmt_offset", - "required" : false, - "type" : "decimal(5, 2)" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "w_warehouse_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "w_warehouse_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "w_warehouse_name", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "w_warehouse_sq_ft", - "required" : false, - "type" : "int" - }, { - "id" : 5, - "name" : "w_street_number", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "w_street_name", - "required" : false, - "type" : "string" - }, { - "id" : 7, - "name" : "w_street_type", - "required" : false, - "type" : "string" - }, { - "id" : 8, - "name" : "w_suite_number", - "required" : false, - "type" : "string" - }, { - "id" : 9, - "name" : "w_city", - "required" : false, - "type" : "string" - }, { - "id" : 10, - "name" : "w_county", - "required" : false, - "type" : "string" - }, { - "id" : 11, - "name" : "w_state", - "required" : false, - "type" : "string" - }, { - "id" : 12, - "name" : "w_zip", - "required" : false, - "type" : "string" - }, { - "id" : 13, - "name" : "w_country", - "required" : false, - "type" : "string" - }, { - "id" : 14, - "name" : "w_gmt_offset", - "required" : false, - "type" : "decimal(5, 2)" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.7.ndv" : "13", - "trino.stats.ndv.2.ndv" : "20", - "trino.stats.ndv.4.ndv" : "19", - "trino.stats.ndv.5.ndv" : "18", - "trino.stats.ndv.9.ndv" : "12", - "trino.stats.ndv.14.ndv" : "3", - "trino.stats.ndv.12.ndv" : "18", - "trino.stats.ndv.10.ndv" : "14", - "trino.stats.ndv.8.ndv" : "17", - "trino.stats.ndv.1.ndv" : "20", - "trino.stats.ndv.3.ndv" : "19", - "trino.stats.ndv.6.ndv" : "19", - "write.format.default" : "ORC", - "trino.stats.ndv.13.ndv" : "1", - "trino.stats.ndv.11.ndv" : "13" - }, - "current-snapshot-id" : 464364136890993501, - "refs" : { - "main" : { - "snapshot-id" : 464364136890993501, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 464364136890993501, - "timestamp-ms" : 1648044153184, - "summary" : { - "operation" : "append", - "added-data-files" : "1", - "added-records" : "20", - "added-files-size" : "3323", - "changed-partition-count" : "1", - "total-records" : "20", - "total-files-size" : "3323", - "total-data-files" : "1", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-orc-part/warehouse/metadata/snap-464364136890993501-1-0815941f-2631-4f48-8c06-40256d90839d.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1648044153184, - "snapshot-id" : 464364136890993501 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1648044153184, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-orc-part/warehouse/metadata/00000-336a387d-27d5-4f9b-b277-5c4a1cd22f06.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/warehouse/metadata/0815941f-2631-4f48-8c06-40256d90839d-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/warehouse/metadata/0815941f-2631-4f48-8c06-40256d90839d-m0.avro deleted file mode 100644 index a35edd4ce9045b4f3fae62af58e1219a2f37a52e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6815 zcmb_gYitx%6o!g~hdctxD;0);@GuMR&a}`rDo~&b0!AJoSjsp%JKGMOompmPX-a7z z5s4vKB98`v)cAv{h=7O)KTwE9A_@jVxp#Kv-repjyGxU%KW6Xw z&N<&b=iKk!*MbY%cYn&1s^o}xx_cdK)kbBugf%TACRwbHwSO6n1+yCL40%yZF=SX27FARBIl!u}RkHOy+n!5l56Is3 z5ST(C@F@{+P#I$sq{J{631LKlS0ajLxp@>)2oRtwTY?sg1Z5+QeNrN0Qa~VTy#q)B z7~2{E?`6gSMKnN6vE;0CD5el3#j4Fb_yE+FTS<~0)3P$A%h0q$D+|^FbO|m|qFIDY zmmUt;$R=UB=qe+YpohbzoUYzNvZO>rbwr_$;YPCc74?0@#fCoG?$5^2Zs!`Zq#LM% z;(SI#O$p1UCCHkEBcysbz65a=Gr=U@{HV0!y>2%?>Q`s2CPvLF-4bw@+6LvU7E@I) z!OdOaV?%OSjHwn|g1*y80SYN3TA~nERJ2<*j%N-A=fq>ro_H+CScr;Nm5YGp78(x_ zf-b>Wnx;#lrRW+nXXX^9MzN}xvRMqK40**&;0yzPKNT6q4eLfkL_EYvNkT3I-;BM6 zLE)4zXghq2Zb;}K6BUg`F}$I)7dn4fVJRp$*N1oh0e=Dm74?b1b5!v5{tLJwU1WP_ z99CxF*~I8!#$IePipE$~kdM6^bi_Objd?MZC9LyPTcV257_=P0 zJIiV80t!2*>Qc3k9-OuTLVujfhd`ZimU~VifI@ zogt%y7)ysg#4aWlDm$d$^VYlN{6)zz*~QT|R22hNoIe+;ij%`4yvB=(qKflB235Eh zd{Bjw8VkXAV9;1%L@mV35NCkbG~CfaA}axJY8w*B1rUJ=-R;iW^tHy(!{m{*#&J0j zh?&CRP=jzRr)4WZ4^d6j+MF!D2*~1#bEr1HRTHGEHoiDVvXJIO{R(x(VAkBxYIks2 z`gnSHsCtcT2tnW+^tLl_9%H6)V^E!q&4D^@jCZ4^nCjfvoVdgJGC1F~BysfMk42+x zOA?x9d@j?BqX$J&Go~Pk&uyIHd@~`5u6CFrLPWMiq_Yp989|}JUxxfsH!-UHuLIrPDojmBOq37VaD_1Fr0;8Einkn%a+Rs+k1HPv@9}p z10pg)P%o*JE0Vk}3A11=XgFBQOqrC{MS;DK-pKP5=8)SE7LS%-}Xo?oh6vCzn@1AeV6Z?n*9C zW`eXQrWsw}X*EwwX|Jubq0|+AP4JTpu+)r8`_ypRNPRTGY!4(`?7XvWnYNWOMl{oTu)Pq{!CnXUIK_0@@@k2C58M? z=Z2t=hl`|(05MM}VV7qlwS6qiIeXLu;0Xmut`gS10OK(p{|S89Q@zCD)BcsXK&1|1 z-ljQ2*-mmGPbV0c2u$K2vm5}(BL@z;t8H>fuczIls7IYt(mBh_IA~q3%%IC2H?vkk z*G+>bFKa0@ZI)T^P)HtG@UV4{pT#2;sGAo@x-Us=W|Uc#7`Dz1TA3~n44Fd{UQ{YbUmA_ijtxu8wlUgoWqE_j`NzTRlrV4PNap z-RJ-D%(TJ*^A>HHbF*OHhP@|8jy%8dy@H05XV<>p|4g^@|FnCtH1ELpy5&vH_jm06 zVaL{~C#-c})!zQ*@cnU}mMy(+#P0oj?aM!(*>=UhYW-ENdh3ayy<-b@?0fL>D~Af# zY?~h{ZR&mCSl7uX+2-|Y%dUowZf)2*f7Nbf!#jT*SW zhbtO3?fs>1y>yq|bvf|rCI8a$n)7)_3r>}GTm9?Lx0nYn_uTp4{{BskrK=_nd2(UP ze!2c(OWwKskA@r`_i5v{PmbR)S_V}1?U8?L$+7L*8>e6IabYfdx%S=%m;aLA9JKn( Si2CpD9KQbb#M^z7x9WfIEe)*z diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/warehouse/metadata/snap-464364136890993501-1-0815941f-2631-4f48-8c06-40256d90839d.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/warehouse/metadata/snap-464364136890993501-1-0815941f-2631-4f48-8c06-40256d90839d.avro deleted file mode 100644 index 61b4f2e35715b22cd2a72a20cc537cc837bd8ede..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3783 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 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_page/metadata/snap-2015819917735805161-1-928675c3-c016-43d4-ae0d-971d4d141dd7.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_page/metadata/snap-2015819917735805161-1-928675c3-c016-43d4-ae0d-971d4d141dd7.avro deleted file mode 100644 index 871d7564951e039d0a244cd0489d0ed4db24488f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3784 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�`Jz+;v~~Ls;MP@GwBwuG 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

cI{p|Wur#~ zIS}Jm4r;ottlDhQ9I4Uw=EXO+%1nKg*wR3uN*DCH<3qC=PEl1rX#N-^DAF3W1N(6X zHhc0yhY;>oZbg2;h*l^km<43tMDm-d$DX8kTb#|)US7o#tIkjzTJEWA~!VW zt|U|Ocul{c>MrX*?f!*G))<4bElZiXFR@I_Q8EO1Ac9EQ_;111k4KG>5TY{GeGz=P z#oyK-BXBD-4|n-B;Ux-)DbQhG5N4LZgVwL3Zie5Kz~_@C0e%75`hZU7<>gS|*$p{t zXwmiSV~>RrTiiwS;#aj2)2Y{erOkR23@aewvuNaIX2$^-v3zJa>cj!Td-%53Bi8$3B6zn!4i|Z;<--))>C#s+IUx~-K zEc8pOwUORhY^h2;rK)P8x(KjF!4;=61M$=LU3xR97je@z&Ytxw$a?IU)-C4Z$ zPOz)N&<&LHKRNa}f)vM&5cVcj?KHdfP_<4y^@_@I!Qes3V2IT(KZYf6Pwh8ak;X@< zwsc)y(V!+FrxLv;Mof6IJ3OX#EE41vXJx-2@5W4-3l@3>s81Z1cW}Q5gEpbTW zLOxcp?Mo~}nQBl`L`Ke5&3M>wJsjvi)G|Z2JWa=bG}_}-u{-8CqoS#h|%_PBc{7Ndl#|_snRm5MFb98$bgEA6)jjK#YF%a0S0i6do zB*J46fFCD@030oBYVxo+9iFkz?9d+mY4%$f2LLC-%2I>UUAlhF6I-Aq^bsXRyp^kA zcl&IALCzskV+W7WLW=>O#}xo^!|8Yj2pO@1l)$g)*3Wi z=JfxP(fmUPO)Y^`5`fb@=iog4_-0Dzg+{7m>tct{D0N0a#vruoK3>!8c{!0|dEmf$ zCBC*V>Ai2;WO&B({yV=mJq3X#8)W&o@j>`t4baG~GKfG3+Ga(_j@%`fSf5xrRJSax zvE5QmlagWi*cp*rIFQXu@vI}lyC-w0mQEI~h{br_4_Nv+@0vt7*u5@&te;}Cy6n%q zQM>H*2)lpQK#XBpSRHsU)LlO9?E+S%5zM<5w^TdjTAQk=GKkXRG7(Chhc-^*QsUPTFq}!%{D?;ZVR8~aX&DVv;2I*Mw--IA6U$Vnc z`a8)kc?GKf>RL99DL+Zs&c)dqy3+OZV2*{1P>EDO>x|t4P%3LVMu5CR618PitD%uB}zo$a;CJLu-rEl z+j}#1IGSEY;=n}QD9`9ukp7PzomNUg7i18!aZ;<19>Q4^ESomMQ zlf&DeRq+2WiS{3g$b2BcW7xk7BSZ(C>w&ht@z0nQ)Y=s~=FX^Vc23)#oNThZkw>Y_ zqtaq~gO!eBSIEI*_+p0SUr6h6*kM6u&Q9<=2$&dy+J29knMlBe=)~E&xZ&_954?lt zbSYxdu2s0C6I_jaR>cS;o>yiU%PGu(fIChBPS1*8yj}vPXy;Ce?Gy`{#0aMwAa~X6 za1cqhJ}<%UzLI?s;*wV*a18!!+?8{V@>S{+Er5Z*mJlH@f5yOAwau3t)6tDeET07a zuQ=PY!;N7=CF#GorQ#AFxawFRw|z_z3Qy5~ayO>e`f>K!G7>Ei5On-8lp=29N1?Js}QlUfZxHFo++?G-FX&)|u+kSOS1IX*U<1Xu(R&4MMGQuV#_l`<-bvDI2 zv?yw_>{}KNO@dvAQway2Gr10?(>b;lSC9m%wtn<8A4drBow@(3S>GtJ@!@+Ovn_2u3t{cNz6vI)LV zDq$nfqgDcijT4hi=g|4!j_36yYx`E?wCkLwd;s9iL%L1u+^>Jf`<)!w64=YPiI}hA(w_AU8 z$&7qZ!gq-GLc#Pvg&Nd;sdVR5H|CVMD-^wXJ;#;94>a)PlbHUdOJ@B%hDaLW3eEz8 zD#vDffL7c`*z!5fDdi*AbseiEVRO%Ed@NKnU(~d(7`|Cm>MEMX$1S$yEtFpcNm7Wr z1N^zQ!HlRGwfaXaZSJlzBj}_$h4)M%&OB`tK2$k=&XZDuhEyD>go8LYxy0Y2J%SzazqkP5^TzG997!~(X*A$t$H zq|v|w%R|7_y$V0n z6Y@iR&f{~!XkMY&YhtOG5nV>s?=5b^w_c33Z=oNiTh;j4?^v1xQSe!MXWU@V(zbF6 znfR?`!F#*<4=H9`BE4vH0Bq~q!^`1`cdO~T*CQHro*!S$lZZHe({~X#ToSFsFnwWS)#qk^tiI!o%Y)Zh{n|*NoVu|n(&&V7f#~LkepzA|#94w&# zPWX79TaE;7@0Hl}ul=%rNEP2}l;+Bp!K+S%NHLVN*62OdsZEZX@xjs3!%%}es}2xD z8x+|{dDVpve~krk;RmOwP`N3-a?$~(ZyzphPg>cl}UYmRe`<+gS@QoGkejqx%L3HdiX@M5X329ot`g7`zfUCZjEhCTVK z>#U)M97ab9)Ep^$*8i!JaSS%R`LSjbk=_;5@pSQBnDIWz7GFu$8qVZ(N9pYMoQEgb zG}B60L9cnl_wQA()I(jFf0{!i4rh@Un>Uz#JCepMMSM`9q$3+I=sJ7b)qq%G_9n9C zsD{7dnBBhW5zyDhyn;;VV~MJtDBSaNjbg4-41TNL(62n>HRc@utO#e>*awW2ba$j# zN7M_T5iBms@-^BBSMl%!&4vxxhkn)`_PF&)%zt??>R+{E-Sj$lU)K5g~r!pRS^}RXp2>*%$QPD*4zf2Ks4zjxAXKU8k;E zk^=V?((bP;hT+OLyr|-*QoLe+01Vv&dLCMpvwpM?M$e41E6jBV@<`wGvC# zX+)Q&QmXvidYN4wsNoZhgW5D}$KIoXB?Ak(-to>AxMHG`UmxL2jWbAuCxy&|OwDJQ zBS$Gh3jc)5C5=!CYCr^WsB!OqAfkLK_ww~}><%3tX7m5ZdM2K$!B{?hcX7>VisZ}9 zvc?K6HZz^66x=pbp2G&T?x2}s5|Q&2{*Q^0xsKOMC>ZzJKz@9 zrSo1&Z0((;D_nlNJ&S$~(Jk9u2NJ6`-c)fg>{DVO%S5?6aWN1`Z^#PNOLR_&6d+1C zFbI|;dB02ZuFDkcJ`C(~k49j|e1@9y7bx}!(*L3(_ZpOI3XiE`3anlF4EYu**ZyEg znBw2qT*DuJ+ep1)_IdB)U_pQlBzBwN{zm)r7aeH7+PeEh( z8HC>jJ1HUF=>yKR^xIliLn=6-ClCDrIRC+e8=mEGFM>k8%kz<04+FX*#(glI6S90P z0k@e!e26jb$Cri1+q!iFSDZ52JWt(-OI_UC+hNF4T=!}5<)azqiG$*~;ynzuv?RcM z8LJga4{bF{k`H~>sN89rq*!gBZ{Upo0`ii#_hp*&rh^S}J)@t)Kn;8$RC2M_%NNF= zPL7f7^s+nRlK9HpP%Y1$16{zNI0z?r%AOerN;!98Hfeaa}IDp0IJG_LDLz={F;_bFKpw5>m+Kx znVG9h77ltLymeQHHF>EEG=x(v-u;pld}TyZW+wjTaRHCqwbcuonxn6eWCjnjhD8`^ zWBSifnw~-}R@~T)420~#(7yT4f&o>QlUVp=Qrgmw-~3=+Ew41IP;`OY7M0G|v1*0y zU3ToMoXZUA21BOtU)li96MhZO4-;#(cNo2NnPl6RDp0h5_DDvrLb&Do|9|E%Bdl)^ zKz@Ly%47YuC1TSUcq;oI8TujR4GWjupuDslSIkWm%Avg-%&CT1Gg6sTpS-QYv~I+_ zhP$ULDB~`BOs4M?lfT_!8(8VG3KajWK4W=vt2IRE{$~df8RpHG3oi_+tIUV=dZoYl z-$`{bYjCk*RX7G!=VGc1kfVzMLa5I&_=@Ks3TTDZQ@~%=!wvhTAF~$W=|thDTDS~K znj=Lj*3D=%OWIdLO|m@WT~<+^)PXZwZm(_T$OxE21J|18Hma~b1nMoNc<}QQ#VwVj zyMHNORitO9jHK^Yd`rTqc;U9V#;dx$MRT?#e>1V}Qz#0T?rb*EtL=h)q`-P-MVTM_ zGrf*xTMC65(_ZrnwHMCcSxF0PpYM04&7c304{PB5L=Ag6h3U1TX0?m4{RF@{2*o4@ za^rQwBNA)7-u^N=zS%XR&eriv6VH*+r*Eg)nIYH@T%*R`%TvYIqqb8*R8;V>!hJb+ zI$U&~GElJVJNVPfxa9A?dnpVmBTP~+$G+a|8{0U8yzNo#261HLj%Cy%lgYN zJ0-mjsc4QU3)dYb{2UGP-E{x~$GOlWg}^WYw0^6~^66Jt2=_zN(`vSww#|#DJo^qN z_&Dvg(FbC0&C?W+)L5}R6a4y%&45w7D;qn)Cu3+>1Sr~UaS)IEzP5hGM~5qn;{)1D zv$F=ZtzY4;8DOHz!K2GJA3uHMqnC0<`=jxj0`JeI zi(xjm60bjwqMsEefnG1{?fKbuY9G{F+ z6+qK9Oo4-hK)@JPynVBU*eDlTI7khje$5{}LqA@+XN&I`rdH^)QMx9Si#x0Cqo~g? zh|uN%!dyU7y{w2Zz=ILpLFAmooPtwz3arp*#h${;q-9VDXowW*sG=LKeO*X&?8feL z;SciWLXHhn-f9q9^pR)1mY(nif6&s0au2%gtY=)iHVi*1BmOUjY19cL~W#g}RFJBQiJKJSfP&!+h?!AeOU5CA82hMB<2JYTr zhz%u}8LZ97mw1cWLP`5i^^5vo^HbhL8M9>xpI3?2gON%#1~cwWtzq^`9hDgq+$Uf_ zaSmh>Me65rXB&DoEDZ!C&GND;^bb~Pta9dCDW&?U zSgSRraW-WI9!lgIwKw08sR`Y@1rh98t_dymuvhJL*4W*P3243nq(q%i4$O(-6uY;T zdG`go^y3yDF`O#$P{Z(p8n{acM$6>=rO2omy!+U+Z%5^e3~L}?Pg5@VKdYo3IPGvR zdt5u^-WODASRVy2a}O5D7~EP{<1mA4jF-)MH*S00!WNPvnsxR5DN!%=ys}8#KT9s!xE5dHZj0) zp8~~H!G47;>92Qxv~U6uBvJ+&H#A=mevQsO3QFP%FW=OA{aOGeL)g!~a?q>L=0A7o zX2O~F#>TQt*v^L}PP^I?ucdzN_G52d`Fx>U@oFXzQ?biS zLsMG8rOpK);f!}!?}|lU$sDzCR%+M}L)%l!5r0vulb4qiem+tpxptBA4dolQi-ysP zcN{eyQQtC9e0_`lYmU9F|I;wx6*|M#SJC@4Zf{7)X+mD1-Kbfb+;K0cEfrV8ROEH3 zFLBQG@>fOAYd4m=0WPCX+OzXlnwy)&AUC4Pt3hy zx@tOR=ki94^R6AUnuY%6xcQR!;M;^NosO_bi61@o&7PCFh4Z~hn6%dudaO7m?SJxA z*8~uh29a3%_UAV5l1}%O%0tbI9Nz&N-DoA#H+AwIV{CVl0}-33AgI>>#_O)Pml&Eu#awZUrRZkT*wm-*b%$HaYgTZqTH%zV|$TNAfb-BJ24P0UwX z&DZT;+=2DNS**RbOg|1b1K)dR@bOW@wv;);f4w?SA)4VKVfD$i(?8Uj?Dk80BY-#CiUM(>^7{h7W!q->6i{KA~xYi6MsTcot{Y5WUBWOsn3 z2Cky`Jxb0YRzj?^Lw&_L?0MC|aE@I8i1h(zRmm*hgLqD>P|Gwoe@xjITU3`(`JY~G z43)2<6YC4FbDkXbZZ%eO>7oYG#vm3IJPwwZ1W+G;T_6qr_A25kHObwpY*&>@iYQ+{ z=u3LZ$y*R{<=*{*tiU#SfB}$hCBLhfpGu!*>%ldF!q4Q4jCWTY{NwjMnbrT^ zZNy}+JXxiof-taL%8S`&R#3yAEF@uMB#S|b2?-KtAHH{IA!)Jgs4Td3FfyMwVlsLj ziL_)R_OdWaWfr5fUprkYo9aE&2QGIwuAy5&f>QNO_JS#c(lR%j&-`~Mxm-d+0`SSZ z++F|o)bPTvp}`S1R$}68RA)0P+qRA6c4_>?rNJ`N<9CH@B8&2pYWUnO)MnX2Ir53U zw~Fywl_tx{bUl4$%25Kf*3%eRj-SL#D<}STI%d$^%%?_s)fScKdjIoT3fYZ{VCc5W zYv6}&g&{yFL_1!%p?1U06d7U-2+i`706y2mWh4DlhO?SjV-V))(%5BuojiV>uN53* z?kj<47{!UH8(A&LAl!(>fp+LvNbhsvv*E#`Bg}?Wtuwiu@aAx9-o4dLrJCfL(p>c! z=rjjPM(D@>cW-JKlKual9D)(}CN|XTWI}Ye7wEgZW@x*vh4AIKCEtpgzk-{8CWRUI zeQBQMzPBvcZ|xD|@EDyX=>#n~(wGM~PqvL?t>aTLXVpY)R!tmL`hTtV*{pM>^@rfUjq z)^$LL_vaOTanJNK^2*&$+JaI~)$y?ZFOP5K1#sWR?xmZlx2Ccb!ZWw-Wmo>FTu>#_ z_)ercO{Aw))}M69Y;(guW`9&`h{<*>syf=P#3b{M;o^j8A~c?JzPX!48S#co;MI%s z-$;1g++~Q-yUM0PX|BXgqOL>lJaKl0rRC4#@f(D%iN>Lkp0g6@mGlo&-cIiHF=Za z3xWs3j0n#CJRg9gu9NaX{fK>XzmO z&|B*sO60at7&Yc7W=ex#uOeUtN~<*zfVB)oYv{ucKEDezt3u0pJ2d~&whJ>6n^ z-TuG{-X^!yX>lztMkmQ7PbxyL(MaetK8*FO`}=3aF%k4ncEZrL!9Mpjp+4d+DL712?X>>7m%lX#RZ$^ao-)=3}IxE(ZvsGcAGYv3h8z z#UrX#XP`~LWzqU{jRwnzmo=+LXK4-a&CnoDUz_MwZ9;uQMX*JRhI6|w_e zFmq;CfjL*L-}q8iq>v<}Jf*nC%&mcAr-vftJSaq7&}tgL>LD3|kXWuyTf`HaLMdT;Qf@(kSty`KY=*UHEGV2T_BPt3D5 znOZq1wAVym{`nTU$Ke)gLHWn)z_?IcrC>DfOWSQV-Cb##Ai}g0pV)fcO-7o~AJ=mk zU^`OWG=V!D#5sb;pixJ%S`frM#<)3ifGN_V^l6DvQ?jAPMzUHBc)*)Rcx z6dGEeHF5qX?^otlKW{Nvd}c$R&bgBEusWnMKYPZ{WZPBYkt3NnLHEL$DL@iUH{J*R z+6R;Q@oX(9st=*eLEM2i;k6>@zWn4K0292!0dyk&01Zk$bV|^t-`fKmR(Y;@1o`p9Aoqe_)>qCxTx)J#5 zG7HJw|E=0Zd5Jgg`+`^G+)thYRkN9~UDW}Xdq8S z?QQX|pGnAeAWsI^QG@3Dn(0de=r;3}+v(MgrBlxeGHwbvD(?zVBZk$a8qnURbD!X? za~M5SIeE2(ksnb{8hY7KhwQ`&?6vX-hZ4jCcWyS2$Lza$YvUS}1H z=8G275BA^B(UuANhlO9cLUxfFX#7)%cJB2>m@ehX<$9wey{oT~o!c zrENQz`Dn`{_u9T?Wb=j?%>Cmls@PD`Ef%a*rNP%oUK!QfhGT7j?jB&lZ2gX>&c--UpewrvgTc3E` zap$i@P~wuzC6EFtC^AO4V?`$|V_G(<4|b{?pEY|T;ft19b`f>R2ez(UQR72b=)mRB z6=3T%vOf8fV_!+@&-W#>q`cGYi6p#Sv1z^uYT?eCm1VnCmNwXR!quoM&@!W;x~VHpMYsrD4N-QS1oCQ?Tj$Otm*J znz<1Yw9JM|gyJ1mg?D1_qTKh5Gh^j{>A%ixKi1>KgC^+PKi#8#Qp)x&$_|O_@ujc4ndNNU^bFwvd?;KjmvxPv{%O>BiaoN=HJW;h;*77&|ROm zP_*U9v~;tbP@)+f>lQx9(UnCeVTv37`(f+g3}@I0SdlJmTnDWiU}p`cLPOxw4RwmJ zV>De#}g)sm8oI7PMqO~!Ex5d6#nS(WtXr05Q_>Cw!_D{S-(yK43K9B z=c|U-#vBaw6DcJu9$Je7nq=p}#ud*}tF@NjuT}PF)g;$ZM+V0u-gUELnqBZSM0dcLUU-i3s#kz6&O@;4nR{NNMzzSf>l5Cp zEf;|(WK{6Mj6tz#@;j8+ZEFrf6N554VI_nL%bdu8-gUf>wlu5K!4wXD?Ki_~yTF0G zUZSrSqyFb6YhGLct8iHkWW=gf7z;6MIZC>%Y4@ap^f$%k9K zp}zoj^NmP;pb`#qw@k77e;9kus3y0q?|bj9qKJZmf`Y)NNG}QmrABEY(nN~XC>=sk zI#RM#5F#K%K#D*p(tDSdi1gkGO@blxn$Qvm$#b#KInTK7cRcU*e9Fog;lssR*IIM_ z=YRfI*mZJngvBBSU#$$`8}5XSWRzZiz$ZD}IpDDjZLiayE=V%MDV4xO$Dikgc{-Jq zIwciKWptQi#eye7JF(NYQdGbo3+!cU(Ba9QT~M1^exeY2U2?qa@Pf*)&_dxSL&k!z^^dS>I+GC*7+e(($u^=on=MEJ zM|WX~kutdVdiO{5eOVu3^xa6yk*Zi)A+Q8;;Cdsv)KW&^P7x3R?6=O%0=C;>=BL_8 zwwF9nR(^^H`=PtF55EKQJM9pyNjZ}>)J0o9p$J>sV0IKkMRIb7W?_Y&Ey0P?&6ZcT62EH;?r)p%2-yX6YQHT1q>uao${#)Z7v zD9K?8Y7{zn->rMwM?cd2(%5mPkJ>0E+@5V?UOlW8e9wfvj1dk2ut%r7vVUKTAzdv2 z1&`lqNDdPp#w3zBZrI}kfJLCdPtB4cG|3QRIhkpXF|AviQl?pv)9+h-UDR>zckg~3 zp5vosNwMewMTlz2W$hO@Obn^@-8+tJ`K=BEu_OKP_)+bC-Yi3!H7)HtJ`L z-w22R*}f=AxMTKZN4F!CPI9;va$VUso?@Hq2mfGEdI8y>6)XiruJRtoLwu@>&M4qL z1}};5QwKWeOu8TkLe7**EGF^W26`~5yfRi`-T^%G;uK$Pa_{kV!wmg`t1Jtdi0&y* zP|_84cF`o+B9HG{4#fT07{IXwZYaoK>*x#k(5U&VH@W#6>QVq`(tbQ3#4fsw0$dRq za#N|O3VCt2yt&WCF-u9Q?sZg&!-9B`M|hO$^Z78-z26FppYI(P$%X0a9sd3PI%w35 zk)|DS(pfXk6xR|sR^oyr!PorDBIQDXvdNB2YO0F|P@>nNjClD}bOH7H5;%H`$`eFU zdSshJX7Iea_z`RV_7T)fBfmFKl6g=G-7nAP{YlF{M|p&-b!tt=}dphQSf% zJJJeSkoWkZ!vY%%;HpczHY=T%Ghk|q-sw(;jG0^*VTVPxSm=ZUX=YVBcK()$=UXVw zon6W!>bP4a3OSy2>j^XD+WLG)AS)bgG?rK{`EIad;OmraI~S|~h`8NuCmpTZO2i8- zoNr@iKk#bST&k4ggB0O!$LJVo)!Ck>oz26LeE3mNot{We(f=a!%pBmVli-{64%rZ( zXz{x6*@`1Ryno%q|Mjns4^wc+N_0OjQ6saLw_JxGh0HUD*F-Gugx7scF`r)xq7=&z zY=Bo(PliwntmBOs63jz~a@_Jc`XKVv6z?=UAMH$7#@c`*J{qznYq}%NPM*i0FkQ*e zcM{hnrdWuzItGao1F*oH&PRQxUHc?Ufefn$a>>7vDT>l_(puH2qnGAz+w^j|?nQ0A z%+GoWq7SnYHG#hzb7?EEjfmHJuR-+!5{zugn%$c5T)}^iw2QJGas&@15fIfz>Em_! z@3n`ab|E)z4tp@)Rev*boME|d+vfuFf{rI_e|S zD8EGoR;uEaWQg492zQ_G#tsniNtm2cQdMU}6da9xzPw5g=uMZyTG6^EGSi93LMUwz zpsw<8WJplPkujv3mglo9wm5Ipf7sMO&^Dtq18E5{Bz-VDnMMh2xfnUIYunt^Y4W7b z`nG8g2i$`l{(u@~U1JO?3KTFAW*e~+HG!B_o`qxgB0d%Cm1M&^vd>=I{)0jB+`W7M zw-#=z|0mr`{rjglwadOy-}@*K&cbUMT%(Zg2MjyMwvq_e!&B0f=C}MZEzzx8{6mw$ zb)SVg&ek++@~j4;$Mtq?S{1ceB=0JEHHGPpYx`(Yc4P<#gI&p8z(F+&BA{Kl>=87P zcfnBz5PcYQKC4twegYHtytrGt z#7M^8pu;bf-eUXaE^wE2_Q|?#So3c$X;YImEvr3V=();gsgn?CFkZmkW_ukeB5>Zg zX0}VRWy1TgW^sPQe0k#PKflOtUUyjRPk5hM+OV5Ea|8DA>W2#aEm2pSQ%93?!Lp<7 z(UuJp%gV-XOL3=MTm(6=0|h}vJ~3mM@NUqNJP}HB5%+>4boF@esjY zQ52gNj)qqnJ*;GW>e32SV6b@&1L$QB%oYUyGsK9{%6Xm;V;#2c#nXm;LhH5(?u@#a zMrvNSc73{uO?z~mxdSDjNkH|BFOXf}bD;gwHz745ZC;r!wuPr0{B67Uy>z~7eqvd% z1;rNJWZ4ViX$q&zvVDdmnPMqn|9n^h!gOxOpTw|szv$@acOritb)0+jwBoP${V4ig z>$lPqgHj5_%86wI<4x`?ATCyr5m5eY6dg5WTz1Gju6TQKCQN^?SRd+yr}gdWzpa)b z()QDsKzWT}A~16sJoo`0I$9%`v6yHCNQn0eDL2d(3b*`s<^7i#x4_c=mx;v~Q8D(WNKeTI*V%kGA zFB(z%rZ1CC2Y~CwI>iMa)!mUzQoG7G_7Ty0RZ2tNynx+5K-6%ItBEyYQY%v*yIp^j zWTcadbu^u@LT0x9^J^?WASSq0mMf&7wxxxz1gz`oKNIiOt2qhKYqPy&XdPq+gp*ds zS|RsK@gQrL*ViC?1;v@;w=a=>2s0-F3GAJQl}C8sYhyAXmD~*LWV0xwVD9=RBVv>w8uWmt6l5*iXpH2mF}+0$KrMobzV|Q zMPCYJ2mydIry+YS=@9p;#F35_H?5A++vU16sTzV6JAYPvSoO;j>t}NG{X7{gJ~-d^ zLw#yie+?C0GgrZRhSwX9>P8$|4Oz^UVXKv2aXw&?l;@|4$`IxqaX|bDYXcFmOpKMK zE0vu{;BZP;6{Yq1)q+}ThdaU2v#x#H#U<*&n=woJL$D@x1Z2DByEc_9`c_m(^-i9) zkXK!Mq$}FMp&dJOTAtcjF^YM{p)gMJM&Y>Q% zkHJMwm(9o*(Qa0|V5O?~Uu@RT=H^ZZ*k~#&S@x7q%b}uK#yxEXMO}aX@wsl!MWSj( zxo+_m$HpSQF2y4ys@juqCPhAV#MtA|qF?b_nu5A%H06f;^BpHllfLJ2NM9~?$12aA z(bK!HiM4K=+_mPGOuD5*F=vDyjI;uZFV;h|5ep)4s!k;@C+8warhtSrlq4NX#EmWE z`Op`kypr2aCi0o6+2P5 zgbLz7^hnd?0~lpu7y!U?m)GERabNo|lq6Pi-kKa2EJt1WFl~U~H>j{KSPFJ2A{@w7 zfsVerIq0KQbD{g8Kl;-HJG|=`x0O_Z(Xpf^MLW_mmvV4K2c?A3OziN{YWnj_=QJtO z-1nns)GsIVs-e85>?3X`o$t}E#c{1(Aj(TPN&5WFNNbjOgPMba#MVwAm{i6y-YaMNUPop%|Jn&z(j%IH>x-VOBR^t$gSyQV_W)iRID`3UEMdH`Fet)3*sqgdyOnc^yi{tE$PoQQo^)fit7oXIl;EWO4Oqx3 zv|Sc8D!KOLB>L3V;C`XYkl1Ec{t>8HxO8cnoAwCZ)RN;%s>VP*y@#vRps?O7?IoXS zxJl~R;d7m@yT6^XdiMJ4@8>ix{q<+apBFBNzqxQ8aHfekroW|Uy`bn{%>;F3YxF#` z|JOI}J^Iu?w3@pv_uxyB@0kF!&)H^BPe4sge@SG*235* zqNHtgt*E27fmzwmr1)kxm#_II}V*|JzE1>InWv5ho zbiIEb|MAQf^7|%CL$Zqb6Ky+v{F8W><9jH%eV0CsfmsM=sU-XCGU1<}cYV8t?V>x; zU+-wbTRB*gd5aGkIP9eSqLWQ=fnlS92{bVm;YR2R2;4ps*aU|DhJjx z!`S_9d?0*n^{!bwmLVj3?H@mRF9!kuH9+qvp6>}K6TXni@?fZ%?s8a-fHst(TtEIN zZg4!r(gC{3>G2}!aO1CDnP_s!cZ-VS?PpQwvPX6jRjf|EBv;sI(o4+o4G>BRphofe z8a}}(GIS4!EXhEy8}uHEMaxc0R`$@uG$nfCmaM2wjM$$!C~w)5hXA0 zo5h49P5^?Y_w^>QL1#eM!S45QN%`kJ324Ut&WJ6pdnP;EM?+(g0my#s8LAL-sr|{S zw#WZKHhdBjH5Iz@NP`Dbr9WQ?%8Gl$3gE-y9*vzhzKJXt=-j%GJDN6IlyJ@cRVe*z z4#s!NvhN$+mK-lk<naIKG+`OBn*x5myU{91@t;Fo|pSd|)iEuY#mdVYDTU4`0K!Dgy9j z{x!g8Q1^LU`_#8A-q$QfeZC?<9oBiPPE}u-SdxREm#r5TS@*X7hUtDBg=a{4DacPkfWl=%=KvKky+ZCQj3I3WJ#hGvAQ#JX_71?f4)4>; znHjj=&-M{+A(sGhHaZ#{Gn2Bp(j8xb=(K;lAl+t>uMe>pAp?ekN5(ZNj>lX&zpA^P zih!=SZt=_}g(%%5iiVek#>{8RB%EfAeTKf4>{bgh*YpxDKWOlYnRv;(C9wKOrZ}&U z`4e!heVi)7Jp(RzmtXll3s!{%0_@oRAcAOEoix8Y0-LfmlWba5EVnWUR2W&n5j7w| z&Z%TzCog3(6@dM$Z-^mzpN6v~TugJiwQnp#tku;p-&w?XwZqm;1Xw1#0DMm5)2P~+ z0hzLo)n3K=T^tMeZ)ELLg8Dfu8a0u|D1&O|(7>i*Q#Yh}<#Z*bZDr3B>Jy;&k2S_q z(RJ$?Yd+1>B4wo{Qjqe`FVVZ?FTs#>opb&Bp+3{97*mxhqZpH;I8M~WeMqCFen5~J zQV)4EQFM6_w7Gp@l~vw!-d9smj|Ik%T0~IbPxHkd-|4LeWsRHT<(JCW))+;Fjx3l2 z3ro}2J(W^=fz~`ukJAh4Lo$3`A#ie`Svrc9?fKnydS~q^>3rx=JDz&6>$;8^SB3htK&xw z?;guQ`?Dk7N%}ZKgVmWVi{;g*f-*#OW>|?aCohc!(@9%;aYwbULK7d@(}mI~q#WGf zMaSyu2S*u`x8&4R#R=swrnnAw<`8skvL2q?dh9F8c?Y zoXjsic9SKp%cZ}Ybv>wL#jsyfka&gpsW5IR~iVoQ;_`(IUY z+(E>dkuEM^W6LU0r#7fYQ1U^V6xyp>K0!f0p5eAo7IrEPW*~c9ln~Jd8>4N%-Ub68 zeu&TMUR%(DNej@EvL%67BZ_!J|MfP*8s|7D*`U~Qfw?;Hm}hhys67NAT}}7n4?mdv zvdH9EZNe@lIP3O(pE>yU<*A`m=9Q3ZckjIkd+1pBqpOr}Xhcn;(*I2>Yb;46bN@n$ zZVfyUx^jAfKbb@?L%?RHQtV1nDYcG*Te~$qAyAwHqvspq;|N9BF+%UyEAz^z8c=Xp z`I?dpVcOAr9yVZu+2oPD@R*Cj#S9M0{ocx`&7D$AUP|oNo>F2obQ<@DhE_OB}8SLaxk;4GD z@E>hx&EHIk>q#awSL?|pF>or&hjQENqSY^7yU9-Uyz`j`1#@#Kb#*h`wPWB=Xl{=# zTV*vi|F-hL)Ke*7bE29$u}2+p4NMPE9U-Z<>U&XV(D$ieKOkKyRAegIdQ6C4N;u1Y0AuUi<-%(Yxi<;#^lch% z-1gnm{Y41<<~6NgT{oC<=|@Ysglw{t>so#=RQK|t>snrJ zn{DB9>)bAw0(PN3Z<|heOlK(vQFp#8Y>AJE{`^Ll`UA)wtxAs?y0s&XE|CWs!*ziM zri@tHP9l&p)QP~-u6Ns(8x|P>cWIia;$xFOKJK&=**E`Go)j3S;Ba>MFcQ=v2j2; z^QTQ+Sqgp27ghFWI`mUo-eMSl?di*ZKB0I$Cg1J)30@eg<)wq>@pdwVZa^c=*U`mq zY9i%dHaEqsscyhz86!f2qyqI-Ns-d6@?bQ1mL zg0>P0D87G|z{N6I7Xr}?xfqfnu>X1RQ|z2F^J=*{If(z&grqOCT@JV=zBTt~<7S*R z<<;5S(L=_{b1y!2+;PHl1Epa>Alv{_qHt9_c!iyec~_UVP8T7jh@AvgIw20^_=Eq@ zp}3gggBan+xv6fLL#NqWVYXUcf-kz@7F$A5bMu<}I!4eUO&xcD&6dtkr#&wf{0vwe z`FuYBLq}^1yQVj{IskUod2ko(CxVru# zVTg=A%e-~q#&NfV{w?N7hztL)6!Tq`H){_a>rzR3{PoCAh46~kt+E#s-04n4_(G)~@@Q%z|1r@8vyiKT8r~*lTO&cH8nNDVN z#C6E?qo%!+8s%-@NTD(~;qDDg@GTDT#JHmGb$PNsY$#Fx{%{fj3_F)f$#FPwJuo9X zkq&5RzCmvs@tsi@Ceo`oS(X^@^<7gyI=(1=z_|Ebu|7wd>6jPZb>LkB^2@Iy=i;vY z)#M+iB>j|bZtti)uhblm7}nq!6reM2)BoHwI+uRQQlh&@%`X4iv_vcB{p!Q}K;QJ) zGdpo}J_XGKPJ*ydRb1|HD=o!`2!PJOV<1A9VvCvct?g^8i#ttod-@X_UvsvGJGsv{ zaAI~XF`Y~AQ1q^;$D8OdaPcj+m7l`suHo0g1q@O+8tj^Z9Ctsx?S%-h8`tYUBgG=O^Z1Eik$?f<`ceeOH z`aIseKDD+{P4_@;O9HXMOn!1Iw`)LPIPmu1O*+VB)rO7EgNPxY$D3E;{rPKjBTHGv zlpjy?tsbfEB>HV81@7shQUQA!Tcd6k!aY}gcI8azhy@=l>ZcEJz!V?R&(IApDvRme z5MULIJ_^(JMMV^0-?7 z3#K@qOBbKSJJ%Sy0kLmG#%DTFC;~v?AsW`Jz-B_ zxwrq>%u|i)Rfvk7MgXDn)euWnk}j{dpe;ATC;YW5|HMxwPL6ea0<%ANc`dC;WgVlU z!|m6+R^Q&IAK7wc!I9lR_fEzbQNO8E zVc?Ua=#Gy!vIMZ6))|f|t0^OEJ4)IbgX~R*@0~&pEJ#fn+%!U#n1;1(O-~IU^;gOO zJs|&->yM+PQz@Cv3HTd-x)dU}4j3XRwj2oiqf={?<{#8SCj~+nWk?p?7$p?MKfV&p zt$t?}<8vwK1J>dv*w-OklZ9p-JDpFLFw}~kzyqt>t^_XF5@^?Xij}-iHpYnMf`TJe zaSe{l?P-Pabk|rCD=VfaN^QHEUMg}T}qT{>U%<|2|KNalNEN*uLwhr`WlJeGX zsp-Gao`Dh)*KDxexh0?#IrnbxDM2yZ!?TjE_)`p|IcH_B!L)RX&Ifn4iBeXktsRGT zBnvCcKB#LWD)ElP_7aM9^$#Fw0`^;2&-3+H$P-hkPM}jFh$-ATE z@5{Ac+k}<43&SyrL`pXse&RI);B{>^g zb~|~VQ5PoM|4SKalB1s4`(ttCniPkZXvo&vIX{uYOUo9FbGCX+Pe1RR!vp(#IDM0APu5 zb>$7}T=hpCbFvnXYaCQKz+0!>+WmajHMq4vLg`|U_kLm^`R&dcvzug{kLuy`itfCkVzb9IFgcga`BtyKI&q7GiJ-ZnPx zUuiJ)yd6ccWhcLsA>OG=bG`-FpbB7~T>SJ^17(+}PR(MyK0Pr$v;N|%4;x@j92{4C zz=Bn08Ms>o8mJSXN~%I|$J=ZOQd_BTZ)m^(E4kF|NzyXG-6>K={)_bMrL+eK>6U2HB(P_8b_gTMU~ujb}($NdQbFjc5K96dV8faN3N^w z-bzYcbcsxi6YewMCd+~YkyBb0933ezyAOKij z!&qhnpe5cqjZ(I*Ixdvl7{8r7YnlwHJ7$2}IEhf_pGN>s0oeDpLLH66+36FL66hpJ zKpAKodW5DR9`UbtB{v4;y*84Pf~(ww8g(zfT5X`|X0{&;?CzEXX3dWE?)v?sRi2Kq zs1{1mnr(;&KJM$@?L)X(nf0HQB{bIsI_6l;A9xOt8yizhrbrXa%E9L?FXk5i+?#Lo zPf<81ho%Tn`F42>r+9#L5KcQ6bS@sKeb-vB40yBvjb^-HR0{!6t>P&PG6b||9sww$ zpaGAbLKr&?4aITasQAl+SnbP87|=Dm=KrIjxfQZ+&L7-O7e()2zuNn?6Da==oc~*X zuI;as{G$Kxi!=PaUFnzBL6C_$ZfBwKv4}~rOF>+bQTN^*c5ch^f_#TZJwvDVRv!Ly z_FK5rJDltm5Koau*LDnW?d%*Ke-{mw!b*x0&dQ(dpErMw?7(bBmu&ZJ1ZNw)-{2vr z276aRrZ+Hpmpo%>GbK4z^Zv#!DeC9@*96xhy4ved zNA7>5rrlUN+(grttO<%Pt@~DE^sEli=d%Kr`kLvTX8VwUeXXVGkP06*nw?4 z5p%={ON9iuwZ!8BGmX9q?)%w2rdf&Iagi>(MqTlh3paT6SkQ|{%g$-;P5~9vbLW3m z-|sWt4+&%=q+Oa!<%L;Xk-gYh9V|>Q#@D>UKkBU4>3(x4NG(k-u8-fxFmm9})!#FV zc@B-OOFWfc6VJ=@NGkB7q6Mh`9s#j)YD-ECx|bJO*FuQ(7;g2gM-6?& zfC#l0{SUC}dYkug?zFGpExcY}pWz&4@}*);LJ%aT5;c=ba&8{sEEk|_TwZhTuk8QR zNXw3eY~8Lr^BV|dtjR&vo|39q3Cy+-ds-J*oJI;-cK#r-(;JwH^!cS~#XmdyGwA(6 z4SC|Gaxjx*3cgDWIl9T6xFKs=$D$5Fd)?z_DLLxWe}^V2YP_+Ua(B4#T{O+`peF@iF+my!dI>h~`w4JooLQoU56Q_Y4^s_E&5%V0Kv`O40GT{A2-wP|b9M%c@1|k;Y%9zH}g2LwMwqD`bMr!PTlF2`3lUnhVS>xx58vffx6{a(;LOoqciF-tUbn9_ylTkoc3H5dj+k z5nC+g_x{J9+E;_3lpZD(u@>1Xf39Y?3KF2r8>%@l~L9dTdb1xx|u|Xbv#t<*G-sXX@5^6#M(x{`a5HzKW zL>b=FBh7refYm-qM#`(9k~FCHQy9u953vvE(YwmMKb)-IZ-Y@{2F;+r2iObKa^Jc6mGCXx1>G$|%0ce1O%^{$); z?!DUvzcy6<+{K-E2>Y|?&2IqGwfm-!hhV z=1Ug)yT+LdLcneF)sNj$0~cwwH3ANZF5n?&53PYKQUvAsl^-q%CXBFyhj<7NG2|{R zV-mtd)}<`^ItudKTCz#$>z?~C(A@|l1xH;!QZROCS6Y)!dPzHkpOm>v$Id@2 zH0ZXyKv}D#qboX-^|UZM(mLNl*r+=+kmm`+OqvA_u1UA+AHn7$@71NZ{bQ%zt!?i3 zALG6Jv;HBL+N74uGR~+*Ay?K@Yjm@e+GZ<0bfQ}Ad!pClNxpW*2tx2$o~;;vTuHXT_xth^w&oSzvOh#pEwQ!P|lb?LP_>yj@wHfb;iS^r3Ub1B(M{_KDu} z<0!!=iV~&D)zJNDy;d;L?!r`Zi^)Y+|NHt$+Y3OkYs)HI`sYKo%vjo7^+~v)J{$B# z3ek^$Pu8B){1oA19$R=l)No9y$aXAv>}BRttx>EwnhOCwRO#8Jm9YJyKW%L}2}Jzi zgTOv&RG<$q!yl<1HSH;>$&1j{pWUbSRYy>mnU#3DkJbD`TXiG$XVE^H8$5^3);_VG z7`<^m+Hc}Sqz918-UE31vpW;stJ@z4W!w$A9$XF7MpUrJ8*>EmDFiInT->SW1+?#U zih4-Qda=B3^w0k8_~6q80aR?;#NMf#c5Tw6w>-?T#Q^nRh;P{-2F zK7#J%Q8|?NIX;%vIr#SHQUNJAa4#6_+!zg9n7nx0H$v-G{4ZJ%6T$j;^(|dQswlBi z{W(%|N_{2n(p}!u0YHh4kzpwI&5Vc$L+jJpXWaMeOh;wkAHHl>l=#Y7e+W$`#i-%{ zBLgj2KpAMtYbk*bT=2YYI>y`K?yG$w^dGgjNm2`eDh*1%}1az^v*~mVZFA& zxSwCyyfd!Ixz4z}d#@&k0~EZ_{wWB&{9i~_jyF6O%hy!Cm+(_Gc#F1U=hfuz{h^=< z0KjCY05mk*ji0EJx&CLlD?jQ(-lO9w!fQ-AfIZ->f8~UODRyt%MMa-99Y;T?CJh%XrBs zCRQ@h?25l|`t6LKS)j^`iUo2Ue!zl*m-j!IOtz@e?{?Tx=ld+0Ui@%LEGwpnHPW^u zuITD{xbsa@Nup~wC1tB>{d0N9f6<3qp2fl6|fBq?Nxl_rrb^nRLkH}8!9xE@btM4shD=k!)dbB?@H3fOR zK%0G!TJ$_|&;e;~Kw7QJf;9X*HR*BQr}NABCug$a#PE?W#fhRPlL9vtmggM%hq-O@sv2-KK;Hzqsc^2pA%2LX◊G4sH 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 z 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[=>dskZtc9vOhZ1NHMJpyl6+Ei^j& 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%0Dq9 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 z%0A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%0ADE>A17By=mq(`^A4uZ<<*wuHce6tM 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≫|t_RN;2#6s*Jj85~cB@s-$>_9# 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 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_returns/metadata/snap-8685060902938679641-1-9fef99c4-de5b-411e-b587-288a6aa1bd5f.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_returns/metadata/snap-8685060902938679641-1-9fef99c4-de5b-411e-b587-288a6aa1bd5f.avro deleted file mode 100644 index 17b7dd871824d3e12ee6fd2de57ee84204a12548..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3817 zcmbW3O^6&t6vuS}5>aG{Aq35#XvEV-W_EXGXBI)kCfmfo7~n(f-{>7H~~ z?>dG-#JgmNcC$OPn6PRTSrQL93gX8pCs7c=9K?ec6+DW9Sk)g@)$_6T>}@`(-tWDt z_kaI-rLq3tqi5izV?X0+25P~-pS-G&P8%spii0%U(Un=nf-UyF1ufh}E>WAh(LVuEE&{buY{+-D&!eF&r$}+B2EXy z>yO|qNIdigb_u;?bxP!m(Z2N5yhPQJi3mOADWMm_ZRwc>iOJiA^;GgSxpJW6zz+SJ zc1Vi~g#SLQC?}sS(q*GU(rk-BY`JRGDUpyGwrwJ4u{H>lRB=4o;!OapPT2DXbO8ix z6X1D*O=x<F@-CSKh1MA6@@#zy#pmSt#uc9)#>JkNCr`9k>*;Z^%+t~q z==GcqtWdLcq&-Gk$bn97!%a0rLN@D**Ex5O#iiSE1huKogp-IIct)X?#}z7lwdi ztY;Ecu4~k41tQN8Tw}+iaKXu*Ec8tdgDxDB$iUsp}@qdV&5Ue&MEDwhuie*gOZ_Mqyo91eQb-X}Nq_WOR# zzg;h1+bdk@bAJ(r`D+lWP{nM&nE7$zj4{r==y^}vQ_ICPByk{N^dh7iU zHyXz7rvrT*-rmsRwz0Eo?D~!Vt-;34>($r%=YIR*krUtF`grl(yMGkF`*(XA_f9>2 kVrDLvdHC&LE0-RcSv~dhy>~u4ySwx3=f7minUMni1Md1H0RR91 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_sales/metadata/00001-eda4bb5d-773c-44e3-8a26-19b42235f8cf.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_sales/metadata/00001-eda4bb5d-773c-44e3-8a26-19b42235f8cf.metadata.json deleted file mode 100644 index 66bdf6a3c9af..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_sales/metadata/00001-eda4bb5d-773c-44e3-8a26-19b42235f8cf.metadata.json +++ /dev/null @@ -1,451 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "e1d6e056-20e0-4423-af71-65495f15ca6b", - "location" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-orc-part/web_sales", - "last-updated-ms" : 1663708932370, - "last-column-id" : 34, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "ws_sold_time_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "ws_ship_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "ws_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "ws_bill_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "ws_bill_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "ws_bill_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "ws_bill_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 8, - "name" : "ws_ship_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 9, - "name" : "ws_ship_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 10, - "name" : "ws_ship_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 11, - "name" : "ws_ship_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 12, - "name" : "ws_web_page_sk", - "required" : false, - "type" : "long" - }, { - "id" : 13, - "name" : "ws_web_site_sk", - "required" : false, - "type" : "long" - }, { - "id" : 14, - "name" : "ws_ship_mode_sk", - "required" : false, - "type" : "long" - }, { - "id" : 15, - "name" : "ws_warehouse_sk", - "required" : false, - "type" : "long" - }, { - "id" : 16, - "name" : "ws_promo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 17, - "name" : "ws_order_number", - "required" : false, - "type" : "long" - }, { - "id" : 18, - "name" : "ws_quantity", - "required" : false, - "type" : "int" - }, { - "id" : 19, - "name" : "ws_wholesale_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 20, - "name" : "ws_list_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 21, - "name" : "ws_sales_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 22, - "name" : "ws_ext_discount_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 23, - "name" : "ws_ext_sales_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 24, - "name" : "ws_ext_wholesale_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 25, - "name" : "ws_ext_list_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 26, - "name" : "ws_ext_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 27, - "name" : "ws_coupon_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 28, - "name" : "ws_ext_ship_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 29, - "name" : "ws_net_paid", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 30, - "name" : "ws_net_paid_inc_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 31, - "name" : "ws_net_paid_inc_ship", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 32, - "name" : "ws_net_paid_inc_ship_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 33, - "name" : "ws_net_profit", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 34, - "name" : "ws_sold_date_sk", - "required" : false, - "type" : "long" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "ws_sold_time_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "ws_ship_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "ws_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "ws_bill_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "ws_bill_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "ws_bill_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "ws_bill_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 8, - "name" : "ws_ship_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 9, - "name" : "ws_ship_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 10, - "name" : "ws_ship_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 11, - "name" : "ws_ship_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 12, - "name" : "ws_web_page_sk", - "required" : false, - "type" : "long" - }, { - "id" : 13, - "name" : "ws_web_site_sk", - "required" : false, - "type" : "long" - }, { - "id" : 14, - "name" : "ws_ship_mode_sk", - "required" : false, - "type" : "long" - }, { - "id" : 15, - "name" : "ws_warehouse_sk", - "required" : false, - "type" : "long" - }, { - "id" : 16, - "name" : "ws_promo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 17, - "name" : "ws_order_number", - "required" : false, - "type" : "long" - }, { - "id" : 18, - "name" : "ws_quantity", - "required" : false, - "type" : "int" - }, { - "id" : 19, - "name" : "ws_wholesale_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 20, - "name" : "ws_list_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 21, - "name" : "ws_sales_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 22, - "name" : "ws_ext_discount_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 23, - "name" : "ws_ext_sales_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 24, - "name" : "ws_ext_wholesale_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 25, - "name" : "ws_ext_list_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 26, - "name" : "ws_ext_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 27, - "name" : "ws_coupon_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 28, - "name" : "ws_ext_ship_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 29, - "name" : "ws_net_paid", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 30, - "name" : "ws_net_paid_inc_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 31, - "name" : "ws_net_paid_inc_ship", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 32, - "name" : "ws_net_paid_inc_ship_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 33, - "name" : "ws_net_profit", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 34, - "name" : "ws_sold_date_sk", - "required" : false, - "type" : "long" - } ] - } ], - "partition-spec" : [ { - "name" : "ws_sold_date_sk", - "transform" : "identity", - "source-id" : 34, - "field-id" : 1000 - } ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ { - "name" : "ws_sold_date_sk", - "transform" : "identity", - "source-id" : 34, - "field-id" : 1000 - } ] - } ], - "last-partition-id" : 1000, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.22.ndv" : "1059688", - "trino.stats.ndv.7.ndv" : "5947530", - "trino.stats.ndv.2.ndv" : "1962", - "trino.stats.ndv.27.ndv" : "1509736", - "trino.stats.ndv.30.ndv" : "2360578", - "trino.stats.ndv.19.ndv" : "10091", - "trino.stats.ndv.18.ndv" : "100", - "trino.stats.ndv.14.ndv" : "20", - "trino.stats.ndv.23.ndv" : "1059915", - "trino.stats.ndv.34.ndv" : "1820", - "trino.stats.ndv.21.ndv" : "29691", - "trino.stats.ndv.8.ndv" : "12132428", - "trino.stats.ndv.3.ndv" : "297612", - "write.format.default" : "ORC", - "trino.stats.ndv.17.ndv" : "59619264", - "trino.stats.ndv.31.ndv" : "2436710", - "trino.stats.ndv.13.ndv" : "54", - "trino.stats.ndv.26.ndv" : "211496", - "trino.stats.ndv.16.ndv" : "1483", - "trino.stats.ndv.33.ndv" : "1994307", - "trino.stats.ndv.4.ndv" : "12188957", - "trino.stats.ndv.5.ndv" : "1890006", - "trino.stats.ndv.9.ndv" : "1890006", - "trino.stats.ndv.29.ndv" : "1750832", - "trino.stats.ndv.12.ndv" : "3006", - "trino.stats.ndv.25.ndv" : "1115360", - "trino.stats.ndv.10.ndv" : "7082", - "trino.stats.ndv.28.ndv" : "552520", - "trino.stats.ndv.1.ndv" : "89157", - "trino.stats.ndv.15.ndv" : "20", - "trino.stats.ndv.6.ndv" : "7082", - "trino.stats.ndv.32.ndv" : "3163161", - "trino.stats.ndv.20.ndv" : "29733", - "trino.stats.ndv.24.ndv" : "388752", - "trino.stats.ndv.11.ndv" : "5947530" - }, - "current-snapshot-id" : 2885374810939635374, - "refs" : { - "main" : { - "snapshot-id" : 2885374810939635374, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 2885374810939635374, - "timestamp-ms" : 1648048918746, - "summary" : { - "operation" : "append", - "added-data-files" : "1824", - "added-records" : "720000376", - "added-files-size" : "36034160613", - "changed-partition-count" : "1824", - "total-records" : "720000376", - "total-files-size" : "36034160613", - "total-data-files" : "1824", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-orc-part/web_sales/metadata/snap-2885374810939635374-1-6e069005-df27-4ca7-a174-f5b6d743aa4c.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1648048918746, - "snapshot-id" : 2885374810939635374 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1648048918746, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-orc-part/web_sales/metadata/00000-71940b59-79ff-434c-b199-72f9f79fd033.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_sales/metadata/6e069005-df27-4ca7-a174-f5b6d743aa4c-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_sales/metadata/6e069005-df27-4ca7-a174-f5b6d743aa4c-m0.avro deleted file mode 100644 index 350d21fc73e2..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_sales/metadata/6e069005-df27-4ca7-a174-f5b6d743aa4c-m0.avro +++ /dev/null @@ -1,1871 +0,0 @@ -Obj schema%{"type":"struct","schema-id":0,"fields":[{"id":1,"name":"ws_sold_time_sk","required":false,"type":"long"},{"id":2,"name":"ws_ship_date_sk","required":false,"type":"long"},{"id":3,"name":"ws_item_sk","required":false,"type":"long"},{"id":4,"name":"ws_bill_customer_sk","required":false,"type":"long"},{"id":5,"name":"ws_bill_cdemo_sk","required":false,"type":"long"},{"id":6,"name":"ws_bill_hdemo_sk","required":false,"type":"long"},{"id":7,"name":"ws_bill_addr_sk","required":false,"type":"long"},{"id":8,"name":"ws_ship_customer_sk","required":false,"type":"long"},{"id":9,"name":"ws_ship_cdemo_sk","required":false,"type":"long"},{"id":10,"name":"ws_ship_hdemo_sk","required":false,"type":"long"},{"id":11,"name":"ws_ship_addr_sk","required":false,"type":"long"},{"id":12,"name":"ws_web_page_sk","required":false,"type":"long"},{"id":13,"name":"ws_web_site_sk","required":false,"type":"long"},{"id":14,"name":"ws_ship_mode_sk","required":false,"type":"long"},{"id":15,"name":"ws_warehouse_sk","required":false,"type":"long"},{"id":16,"name":"ws_promo_sk","required":false,"type":"long"},{"id":17,"name":"ws_order_number","required":false,"type":"long"},{"id":18,"name":"ws_quantity","required":false,"type":"int"},{"id":19,"name":"ws_wholesale_cost","required":false,"type":"decimal(7, 2)"},{"id":20,"name":"ws_list_price","required":false,"type":"decimal(7, 2)"},{"id":21,"name":"ws_sales_price","required":false,"type":"decimal(7, 2)"},{"id":22,"name":"ws_ext_discount_amt","required":false,"type":"decimal(7, 2)"},{"id":23,"name":"ws_ext_sales_price","required":false,"type":"decimal(7, 2)"},{"id":24,"name":"ws_ext_wholesale_cost","required":false,"type":"decimal(7, 2)"},{"id":25,"name":"ws_ext_list_price","required":false,"type":"decimal(7, 2)"},{"id":26,"name":"ws_ext_tax","required":false,"type":"decimal(7, 2)"},{"id":27,"name":"ws_coupon_amt","required":false,"type":"decimal(7, 2)"},{"id":28,"name":"ws_ext_ship_cost","required":false,"type":"decimal(7, 2)"},{"id":29,"name":"ws_net_paid","required":false,"type":"decimal(7, 2)"},{"id":30,"name":"ws_net_paid_inc_tax","required":false,"type":"decimal(7, 2)"},{"id":31,"name":"ws_net_paid_inc_ship","required":false,"type":"decimal(7, 2)"},{"id":32,"name":"ws_net_paid_inc_ship_tax","required":false,"type":"decimal(7, 2)"},{"id":33,"name":"ws_net_profit","required":false,"type":"decimal(7, 2)"},{"id":34,"name":"ws_sold_date_sk","required":false,"type":"long"}]}avro.schema/{"type":"record","name":"manifest_entry","fields":[{"name":"status","type":"int","field-id":0},{"name":"snapshot_id","type":["null","long"],"default":null,"field-id":1},{"name":"data_file","type":{"type":"record","name":"r2","fields":[{"name":"file_path","type":"string","doc":"Location URI with FS scheme","field-id":100},{"name":"file_format","type":"string","doc":"File format name: avro, orc, or parquet","field-id":101},{"name":"partition","type":{"type":"record","name":"r102","fields":[{"name":"ws_sold_date_sk","type":["null","long"],"default":null,"field-id":1000}]},"field-id":102},{"name":"record_count","type":"long","doc":"Number of records in the file","field-id":103},{"name":"file_size_in_bytes","type":"long","doc":"Total file size in bytes","field-id":104},{"name":"block_size_in_bytes","type":"long","field-id":105},{"name":"column_sizes","type":["null",{"type":"array","items":{"type":"record","name":"k117_v118","fields":[{"name":"key","type":"int","field-id":117},{"name":"value","type":"long","field-id":118}]},"logicalType":"map"}],"doc":"Map of column id to total size on disk","default":null,"field-id":108},{"name":"value_counts","type":["null",{"type":"array","items":{"type":"record","name":"k119_v120","fields":[{"name":"key","type":"int","field-id":119},{"name":"value","type":"long","field-id":120}]},"logicalType":"map"}],"doc":"Map of column id to total count, including null and NaN","default":null,"field-id":109},{"name":"null_value_counts","type":["null",{"type":"array","items":{"type":"record","name":"k121_v122","fields":[{"name":"key","type":"int","field-id":121},{"name":"value","type":"long","field-id":122}]},"logicalType":"map"}],"doc":"Map of column id to null value count","default":null,"field-id":110},{"name":"nan_value_counts","type":["null",{"type":"array","items":{"type":"record","name":"k138_v139","fields":[{"name":"key","type":"int","field-id":138},{"name":"value","type":"long","field-id":139}]},"logicalType":"map"}],"doc":"Map of column id to number of NaN values in the column","default":null,"field-id":137},{"name":"lower_bounds","type":["null",{"type":"array","items":{"type":"record","name":"k126_v127","fields":[{"name":"key","type":"int","field-id":126},{"name":"value","type":"bytes","field-id":127}]},"logicalType":"map"}],"doc":"Map of column id to lower bound","default":null,"field-id":125},{"name":"upper_bounds","type":["null",{"type":"array","items":{"type":"record","name":"k129_v130","fields":[{"name":"key","type":"int","field-id":129},{"name":"value","type":"bytes","field-id":130}]},"logicalType":"map"}],"doc":"Map of column id to upper bound","default":null,"field-id":128},{"name":"key_metadata","type":["null","bytes"],"doc":"Encryption key metadata blob","default":null,"field-id":131},{"name":"split_offsets","type":["null",{"type":"array","items":"long","element-id":133}],"doc":"Splittable offsets","default":null,"field-id":132},{"name":"sort_order_id","type":["null","int"],"doc":"Sort order ID","default":null,"field-id":140}]},"field-id":2}]}avro.codecdeflateformat-version1"partition-spec-id0iceberg.schema%{"type":"struct","schema-id":0,"fields":[{"id":0,"name":"status","required":true,"type":"int"},{"id":1,"name":"snapshot_id","required":false,"type":"long"},{"id":2,"name":"data_file","required":true,"type":{"type":"struct","fields":[{"id":100,"name":"file_path","required":true,"type":"string","doc":"Location URI with FS scheme"},{"id":101,"name":"file_format","required":true,"type":"string","doc":"File format name: avro, orc, or parquet"},{"id":102,"name":"partition","required":true,"type":{"type":"struct","fields":[{"id":1000,"name":"ws_sold_date_sk","required":false,"type":"long"}]}},{"id":103,"name":"record_count","required":true,"type":"long","doc":"Number of records in the file"},{"id":104,"name":"file_size_in_bytes","required":true,"type":"long","doc":"Total file size in bytes"},{"id":105,"name":"block_size_in_bytes","required":true,"type":"long"},{"id":108,"name":"column_sizes","required":false,"type":{"type":"map","key-id":117,"key":"int","value-id":118,"value":"long","value-required":true},"doc":"Map of column id to total size on disk"},{"id":109,"name":"value_counts","required":false,"type":{"type":"map","key-id":119,"key":"int","value-id":120,"value":"long","value-required":true},"doc":"Map of column id to total count, including null and NaN"},{"id":110,"name":"null_value_counts","required":false,"type":{"type":"map","key-id":121,"key":"int","value-id":122,"value":"long","value-required":true},"doc":"Map of column id to null value count"},{"id":137,"name":"nan_value_counts","required":false,"type":{"type":"map","key-id":138,"key":"int","value-id":139,"value":"long","value-required":true},"doc":"Map of column id to number of NaN values in the column"},{"id":125,"name":"lower_bounds","required":false,"type":{"type":"map","key-id":126,"key":"int","value-id":127,"value":"binary","value-required":true},"doc":"Map of column id to lower bound"},{"id":128,"name":"upper_bounds","required":false,"type":{"type":"map","key-id":129,"key":"int","value-id":130,"value":"binary","value-required":true},"doc":"Map of column id to upper bound"},{"id":131,"name":"key_metadata","required":false,"type":"binary","doc":"Encryption key metadata blob"},{"id":132,"name":"split_offsets","required":false,"type":{"type":"list","element-id":133,"element":"long","element-required":true},"doc":"Splittable offsets"},{"id":140,"name":"sort_order_id","required":false,"type":"int","doc":"Sort order ID"}]}}]}partition-spec[{"name":"ws_sold_date_sk","transform":"identity","source-id":34,"field-id":1000}]j-qڿaNPdco7Su?ksw90\Hff WB+!" _B"4""#"""$B{w#""TD_fFי~z@=js>}nϹrwĤqEJ7O-8aDR.^tCZNN_L',7N(/Z98:_eȉ7Eu15J"EV9$G&R 9 .&o?Z,f^ME,jJEib)]ʔV_}K;%v=!ǴirNpn|!a@b$A4<*M:#\/O~%j!)QB,IdA股r#ב(ttX9Ys~''{NOs59q?ܐ>pp::k"dDf*zYUj51YI颚bF3b)ɔRQ)&rֺ՝oki"qi'7x >C0D -1Ƞ -` YA -`}a692[\U|0rD"3㪐LPd5eu0MDJ M;&&r4M<ҟ#O j#yCC"+cvHI4_eD~Z$_C0D -1Ƞ -` YA -`}MC$UGΩD2Ѽo_G$ՐHQ%k,a5YW TA4;'h$ H^I#9HmHֲHV:6IHNBgD>{E2D>tV"H䨯<(m&^g[K&HFF"L{6|װ>5 sW9OD77܄Ȥ2Z$SբXSHdRѺZb&r%ٳkgi"qi'7x >C0D -1Ƞ -` YA -`}f"Crj.7ܪJ>ͯD0d^P$C)iF=餉#DKi"D?1D~;*ZD*{y;MFm[W1#m4M94z55&m'||x_*%""ڥ?p֧&'E<#H?zٷnk ?tD4-V*F5mkZLjJV4#HyL;?vp\<!AA"A dP@ t0, [™svjd>V3:,vyP P3D4f$2E1"M9<+\G%ڐ*;ENc$oɼ?G "yߙ7Hv1}'#qDrŇL _I|j"4#:EriX0{D}k[ 0&HjjHr$WEDSjQDB,kdJM$JQ܏H5iĥ 8/B( H *h&d!y(Up\:Vœ~# PԣFD$Uy`$ 3dɛh$_sGLF$#9"9!ɯ7IN?kh$gD>o$Oё:?U]%mގHF$3Ig\o$)O|^U%g4?C$/UR -T6܄P%Ui"QTRH2e,tMN&TC0D -1Ƞ -` YA -[Im")o5]*!73lP ӭfL)BUEuÐ#Hɘn'W3"yݶH/#vdQͪ䙋 -C#91|Ɍ*9FҤC6Vɛڨ ɝ?FzUpO$>DI؟u{g S+f9и>׉*p#:F$jRJbN&ĢWjZR)M/d ~es$.mx~@BD!q@PA 0! 9C2& c>qN#yFS|I`|pBh -9D'ŦTa:yQfu:n' c5DG^FBEsfGB^s'Y֐9͊SW7NJQ4th?׷s jhVf^$7I\|緬ɞ}Hv#9qY԰>EBrs\~($t܍ 7'Tk=ZUҚbUXkz&P@HS3p7M(.mx~@BD!q@PA 0! 9Cr$i\f\ZP_iIYmNsy[>=j,s"跌9?_cu(ui=uz:v&;7NNC=u!Қ! EZָYξia3=9"ׯl ?2bVSN%uʺX*f*b:dRUy`"h6ҺyՐ^iť 8/B( H *h&d!y(Ufb+c&q.%K gQ=H^\NBXa]FIl!/ |"M#Te!Q!/guAQFVgҬY-:f\FV?FVg15E4YX;2za]E߃~e,FX?o("Cf*oº՟AX8ð>EX<ºpX$R5%Q)VViM9夞RR))ҰnuCh~qi'7x >C0D -1Ƞ -` YA -`} qXh$W;7펍O&KӉ<(T}1֜F=J*F*42mxOakmz1:4K+otc*o)#h9q{+MWh =Hmr$+AqrQL!g"_иswRͷ SrJ=f^sg+5܄J-Le%YK$DZ,VRI\ʥJ2Jr՝,_ߟJ\ڀ;8 .  B 2(:`Br琉mUB{c#qFw/!ޕķf\AB;n"$[ȏm{;H08bF\eRGI]vNj֗; -"v贮5ghZiH;4!o+q<OV =ӻoXޛD }mx˝}~i[M8im%I+L%-jdQT3)1K-%ʉRԪ2aҺhȵ6p xp? !C8 -WKl{n =Ĺzqo%xw+;Ht+m)"uQ!:?B]|i]FZm4FZ4#Ho:ZOk3 ƸNqu3k.׷|)~YS9n~k<ַO@\иZs SQSܐOnq_n ǿ3ZQJT"FWײ.kX( =QZOg9{fW\ڀ;8 .  B 2(:`Br[XKk9wnLo= ⺹.!GH|En&"m&Rb&4qh5jW?#6:䖽pBC/n*_fόVxMsVt#HV8%NzDW٤ݳ5!i5KY9f+L*pz_II1iF/%E)EUEX-ZZ/jo#kե4vp\<!AA"A dP@ t0, &bbuqzq]E;g.#=ĿzIp XiBIl$B6y#Q6u)=:vFZi$#aV.90ʵZ,./Si}ֳiZaV3[>q -YnZПk"<룷 a}jr>:5ֿj ?rZ* QIVH*J5QӪD40VGF>SV\ڀ;8 .  B 2(:`BrX_e#k!gkW[H|[ Xi]KIlo$6"ӺK>VM띌 -t5 FZy֡iͶF94Y\߷q=%FqGj4~F\mްqݿQ30r?foXQ%n5]ryUpV#~@rZӺ"%EdZA#N^|;5j$\ڀ;8 .  B 2(:`BrX_EHNɥ[H^K9(W#eJL*ɲUI꒡#59׈dPی -:8pNdRH%,]UVĄ.fjhx 𑑛C+(.mx~@BD!q@PA 0! 9Cɤ' -8ݭ)׈w&-$vYt;>CbOz""V"P>O-DE*I4fThb4ΈymG:4sbp_X@oiu-@ݱ>xPsq~s(SH2>d=>]|7[49ᡗq?5%upU["+ %eQOQ@zE,&VdˉRw*MW^:<-vp\<!AA"A dP@ t0, xjdg%; {j)^W$3Q@%0LSk~?nHDnyO줉F">/4_}Ԇlc&FKMW?Ny6}/}Hgvw#)^E"qE<[*ҕtɢ*U|LJ)VȺ $r%Ϗ|~zZ?qi'7x >C0D -1Ƞ -` YA -`}Ķ٪Ek/B=ķwnLBt''ҎØc}-1s'iAaFZ~̨6 9n/|ɨiZɨM\Z??Nj#&EFB t{]]wt{ S~w_tdvM8EUJ9LYHJ)STՁG폌V\ڀ;8 .  B 2(:`BrX_ b`uq"Νĵۈ{ $'o'/%}$ ҺD{Hl+$DM=D9@-DM=JҬfiL:庤xiv㚍M]?2jkb4{Zuri bDZ'LCui}XAm]']ɰ>591- N8i7܄fZ?)j&Ir -nYO*dxtyrct .mx~@BD!q@PA 0! 9CqmN:9MC0D -1Ƞ -` YA -te}A -ZO0ˈw {{HhUU7֗n&5DL%DDD]G^/$F/16i]JZQ:HkwiVF׷nub[hZm4[hZe6zmfU'??!.׸xakr q-9QUWpz1䴮+%Q4ª&pY.UaXi]|}Npn|!a@b$A4<x:9{:C3e<R/ 믵$Bj2ztcy 3hB; }&t=H2>zwG_i - -MXsJt9Y vFBg2jHvR,BB; GB)^,w~?,nmM8 EKzUCXrRTj -j:)RjF+2=kWw|],/.mx~@BD!q@PA 0! 9CTpNͥ[H^ŧ{Q!Ƴ&V6'2H1+Wc$r&]VIF\rGN{ E -}Ӽci$eDhe<&}n0V6F-nM E&֋V1,媨%--Z(V* ==0hӆQ\ڀ;8 .  B 2(:`BrX_eʹ2:8fE%=ճZe:ϯ[i$i!geQm>c11g5}Cf[tvPh;Oa>)um:_/ƠhPg2zYV^K*Р6&o'phߌ{_1]2Gν?a}AP zc 7رVr&LFT+XAҪt2% nOVw{k/J[\ڀ;8 .  B 2(:`BrX_EoJf8 [dO#o[oeD+ji4qo5?6F"?F0#k#9r̭;4'? #y>3eݗNd!}V.X("y޷)p,Dr(y֧&waa[3qso 'v^RRjJ/z"U3iMI%2Zqι3aZ;qi'7x >C0D -1Ƞ -` YA -sgo2ͪszIy3X]2K IpRhfߎGs 4",O'\ίoxt/1seE2НzhXt0j2šj#Ni9kٽYXw?_K:Q?K/2/Q?jCVTu?G?Ը~C|=bp49 ~wɚ'\pc~(!+5U$dMTTJtMҥJ"Dx⟮Wa1=nYOS)覡C=SyQ?HZ]D +<:1cՎm˭/Ux s2"9F$F$/|k/C6MNlYY|[Y--\k9o-:cOY9.?f<"PM8-zODf}VlEXL:#iV.fr"1pbK՝vx $.mx~@BD!q@PA 0! 9Ckidw]~>yި/"H͖xTCƴ33HB#>W#9JFh 163ZV:4+/l+KyM'CqCf!Z$1!b_i$@" #mvO pLa}jr§Y<qCKOD"Y,V2)UH:"SCGJ2EW_ݹQsnpi'7x >C0D -1Ƞ -` YA -bn}8_ }{/쮿0w !VKK{=Uv[KDI=4sw.N{ڋiHӺsN:fO;Y\(wX0jSt}>FO{zWC1Ճ6U ]wiojX==Y~2^ko ǿ=ƛj%zhPKJB'Ւ.B -8k`z\ڀ;8 .  B 2(:`BrzZ͖#T\*oO+Kr|jQ#6<2L55E׌)?2Cv;i3"toHFO[[%M2 ]?zS7[N] F"Ƕ77eQ&o"V!7/# e959μHd䢆pfiO.֒ZU3)QTb1+kE/duۻ<rH.k#nZ̟7}̹OgzdL7v2?LjmDr&]8!+Ӵ[q5_D})=qDg zS4܄Iu$u5%2唨k -zZ,)5#j媚J&r)5pRsf|{э&H>HO2>%Hmɳ&! v\wͷk^ո~nX"_؜p"cdVʤ@E5ʈ%TF@$JdBK^\vuk/wK\ڀ;8 .  B 2(:`Brk-lV|ܒG%cJ*!H5&$)Ԙ&H&r`3y"9?yNȻiJ6dG#_nxӞD<<ȿx\\'pQ#_?:4!/F.eXȻP#&~ލ 7'[ՔZ&3D5NńRkIMѬlwFn>ҵށ1qi'7x >C0D -1Ƞ -` YA -`}B}rDTжn52[_kFjiHdU1Ԝˁn`$RFfQ#_59W\HB"/gM8%[!HV媬dbBDKUԋ隘IUUKUTj Ӭꇺ6PP 8/B( H *h&d!y({s;ֻ#C[ļOGR X\@2.ˊʚkjOGh"+4˥@:3Vӭ[(?%-v&HiV$Zh$jt:)44{HΙdL?ŞH~Zwz; SɯßF$'?JM8Tӭ)]ZB,E}k,1ee*J-(?^֝Cy};]փKp`8<^!QA@T@LBPLͳV,%%Ĺ_~~<[NH` N'֪%$Z",]0(KhS fo}t4Ϸc6Ƙ/:GM}vWG3\G,c=tV ޡ4ǰnmxϗU~ؿZ~~YGW!]a1|,]Rq|2ꆛpêzUeBObڝVӴT-5;N ]Z?qi'7x >C0D -1Ƞ -` YA -`}l$P1ȴ.^\)@m>ƆŇ լV c9ٻ9FGuZ?1"FcDSmDhJMs 1=`1JwoDh"(Oh;P>!qª笎v>9)tEu\]uV+\c湳>p"1)kVDUTe+JRSb|3#Q>{:k} 6p xp? !C8 -5^S}v#q!3!޽;!$ w"=B?e?QwGtn%#;MkP'#[HX{7}wJ?2 O?fH'i]y=&]O~nytmf)֧'J[P? 7֏MT4bYaJ*!+ɒX*t-UɁ#~.ykKp`8<^!QA@T@LBP}4\f}0oΗˁA>#`yu=l(OUv[% ,k9oyeg&wo9\,o/09c52 -3_dw7׵Q@ !_%\/#=Kƒ D]ζ,y e 7G2M&ezMbQVĄLXػ`;"y[6p xp? !C8 -WYOlk8VOwO6Տ^AB! Ji7eQmWۚya-G c^F\4JczMQ@/MO^{{2Hʁ|ry SË܏vk~M8iU[O*Z\dbBV*ZIsRTQjr(:0]ld{iZqi'7x >C0D -1Ƞ -` YA -`}ĶJXOk)a i!$/KIt ԏ^I"$"ny~@錴iZr4aeFZl;+77_,nt#4<#6Mv/AZ;>v[]~i}R;ora}ް, ޵? qpRM8iMhxeՌՒjrBL˪5h34 N/rCOй]\ڀ;8 .  B 2(:`BrX_ezducsO_.F|S]Xscѱѱ9$~p4V)3:NEITsc :h2 29.rJM_̗uF2WdH:mz?F&zݟW#K] _>:yy7OMN?dΐJ!sn1VԖr+3QGWRVLze6A2w8w@ť 8/B( H *h&d!y(U62.! ĹV~qB|}}$ͪkHt$MD!"/|~zb,"Ʀ[FHB- f֓i=:zvG˴NhL.ofH]tutFZ?FZgѤ:azfТuH+_1O֯" -Һ^kڧn 6jI"J+tRVKQӊXLT&jrBNU5t_;.mx~@BD!q@PA 0! 9CH?v1KѼϨoF@WSeCɪq-FTe0-9*c_D[3cOgh"Oy7# h|Ϗ1|TD].8[.Xf, 4:5ٺgo;G5܄d6ZUMheSRZTjY2i1]R1DރD~eߣ6p xp? !C8 -WYCl-,'΅5{,&7r\FBf -CDZ@ye 󉱂k>P?Oiu0ơ]~;.Z?OcOOsaGˇJcZ'Ӵ7nw#9Z?_d84F|&Y q=P?7Zk} t祺a}x38Vk 7<$mARr&XKgIYO66 מpi'7x >C0D -1Ƞ -` YA -`}{H} :8ou-;xlb2%)cԿYD"KDhS$B7G]SӔiJДuQL7S`?LjJcrJcFQͶ69ӻ_8 X7t S,7dgs%~(ϝS&fZǴjYOz]FJJ*4r%J <՝3=0t6p xp? !C8 -0󽧣F5O8ݺGJ>ٯ-h6A2dM1#gF=nQ87EvF$w6ꢗۈds9iep9b\^D lcVAq.O(֋#8F"ƝHd /<6Gr\W63nOd4Q&Ԋ^ˊ/mE)[et5V&rܷC0D -1Ƞ -` YA -kI αI [ݾqB^'L&5e:AO2jfXa`T&h_{+#TISy#綑wcFwDPoWi,/`>zq[n=miBZAeP3yFȦr -pxIQ@#{MIXpOcv6٪i7 $:D^ry -M40ky"l3Hd]49ukiƼi7d寑5܄ʭWͧdUWji~܊M֪rI+碭]bƻĥ 8/B( H *h&d!y(Uن3g+zHP -e:\Rn^ݬF#L28}r4#nusgA"Қ?1WH?"y2#Hs?/3Zڇ(?m7 \1 H~k6"{F6HfXb)Ŏ9np43zn dbM%1SIE5)"ɔŸjL&7 HzoF6p xp? !C8 -WQlRHN%1\@S)ַrLgY -˒qM%C2f;}m 2"y*-C(8j<3Đ!fbvr9czG)ZS\]7Z*d:jۍ:Pq ͩ) -sm׉{a -†Tl'q{‘y繇Dm]1܏`Ztw8}Oҷ&UrҷIbV*W(Uҁ*9:υu?֞ kףJ4NE$A$:*i%\H~`Qf4eQ)YSJFMCŊ3m#A2Gvp8p<!AhVA"C AP! @vB0edc5] Z?c %$s =˄11bU4Ì2ΨcK 雔* i;%sH'{S먛58!0Uz(=KfR2H'L:+Uk(OFFS맷&mx2zO~E'`M/ܣZ dîp2*̨ dsYɌʦEY*݅l446E)$ -)ǿ-,4\x~@ZBD@THC4_Yzk'_ ܃a[-/ 3-|E넰_ai #UV<s %Kj;BJ`Hg_gƪP!C] Ryj_sI =NjX|)o,p]?|BB@ƙ H"$'2 ܾx6"hjwY'T4;#!&/)ux W`k'2&ug%1-Pfⓒ(R>O. -;p,4\x~@ZBD@THC4ym*=㐜tѽ/ai]O]ԠUIeEW͸(yH.6:f #6z*|Dc0r62'I,f$2;v\VF}7緣ݸZ@9Df~ºOUWHs5d 3*mx WPړ񄔐b.*۬ShJ QYI ![~}x M`8^h!<@$AҐ t0WScp 1>X{g71cHe_hq'R{1\&^klYPǘj{?䲍F-dhy7Y$)Qry5xͷgR j~d]d'?Rr9]Uѻ|{vˉ]gX˖KƟf}35\F/*,[KJb"B*'J - -R"%d<-W'WS>6` 'p7x >C0D 2(B2]G.u{|ӕqѻ}#ΐ޵@4DkHbPE!J"">ELk.?wGe"3$\\)GI"9ɃdGa:H"o$D;B7Ifb듵ֲ̆|Q)6͏-MTʇvR=+^j-qڿaNPdco7 ս?a<1NcN18L&q1)Zb"ሔ")"Rjk} zEZJ-hQq;a'Wsܵ>3Us]޳gέ??S6uNI;SgJXL*s\-;T)Vz{'S)ո,E=%qrK+űS -+SKrX\TN`&x*}"+PcɸrSSN Vx"NZMj\D]bAr/ BSOso }}}َμuip.p? !C$A42*s:ut'yG qt_Oi(h > 95ӑu;:}ݱl G~xϺAvd]栟:oc~rGM?m׏^g>?O!~o,YvVbBg!wNPlP;;0;;2Ύ y]UwȮ_̶뗽]wA]]e9|/[{>yXeu_M٭6h/[~Y1|v,8aޙ74oU8~+5nEo蜧ca}jrFga7/<&6ttӨ&Q-&j"WŲZPDU)t;IW!feoP{MٔeTʺ*j*D6բ.QUKq9*&ٜwz6qip.p? !C$A42*ME6Cvs抺5U|_ AEEɒS M50F0H\BꑜA#${Fl#G$odYxIfc$ϧ݄=#C<u?Grɯ3"}唕7trшE$Ϲ<;r:~/Hň)!S&Hʭ#Y-49 *j2,TE }4.\RoHt\x~@BD!H *h&d 9ȃU6yI-yDR =( cI -~=-Kn_F2J+D#y3")HVmHȨH޹G$hWc#YNZ%3:ίG;IvGHG=#)"C\|E_9>r."ynVt9"H䆞5 |(H#ɠp}$SZHz<)˕*X*b!(%&h$Jv/K#Kpvpxp<!AA" -, WٲdΞqq@$'F3S#1Sc1RV֕:R1f중Q%?GHB GHz;H="Ym$Df_lM[*M(MTTBTT=50\s7:I\ڀ. /B(@ dP@ t0 d!yb!=0*qeи^ݧGN fI!2*!eMɫ-H3Hi$/bTwi$%F$/KѸۘ߳Jm,ysHnIxBES>]˭_^f39Vt#!/׸PBTG֧H31\T-Grz4܄}_9JDꉄ(tVXT1^LVʥVHWe5˗w>W^6 '7x >C0D -1@PA 0!YA2Ys:XP+ kH[sͫ=̜\d$:Į#"H<mgcO2hL_d!#ӘAdNdtYΖqJb$[cz9陌 }#g¹6 !K5/G!0膍Fd bLNwM1@JI+(Yt1-1rAWz<Ә~~=w‰Kpvpxp<!AA" -, WlB%^snã{59@4"brrV1UEucN٘{&2E(D~wP=F"npjo>1!HNeLM{ٿ3"y"f'ڨ^̈}{Ɯ_ˊXc{쑈w_@$7sQ9}py$te@",*BZT&j\,SbJ.W w"?>oћOM#lANn|!a@b 2(:`B0!Ň^O<6܄}Z<+JYK+U1^J&EU-Ƒ֤,-^.' E+Ѵނ>{?yJ \ڀ. /B(@ dP@ t0 d!y"bebE7\#}\GsOVw!̌Bb7aK@ļq@>Ez#ӬMz8#7YŘtj:xqٝF-F{29t6r -Ƙ:&NAeSTVoW.:Yb;zu[8#?&*reV5]MUM.%=%]u-Q*J,<|Y]pO-/Hʈ6"yjG5CțyD^B$=SOO:ȿ0m$r F"H!p@c#q>Bĵ7}!HY\mM$D7F"9aZ? k #cHsnmI300\@ZWqGs1:}a}'e֎ro8Ӻ0JUՋz,J*)ShbZ.UxOVz}ݢ5]`8^!QȠ -` B`}ɖUY+;zui==%fcZmfWRT5Bw=&0F,љ]H-'2Hd1<u5Ǝ^*4/3 -(OǟOЙTX0LD?/-mzށH^zƅXHfo1O)",w|PD5 7a??[JB-W/#j<-DQbAV=oY:ĥ 8;8 <  B @T@L@rL 3e~=A$gH9:1#̐&Z>ͨffdr,F&ϥUr}5e6#vYԗbgf~ yeZ&ϥ0Q&C2ٍ?D&sSL?j1; fa}2ʡn9Y.ApbW -.' ֊bA-RIOij0m?I\ڀ. /B(@ dP@ t0 d!yl#]vHN7\[h^L-%dRhQ3 -ɦ`)*ʤHi$sE8#gH>\/L~F4/emŬiCP&C7[0άw"ƭWSH>,u֧ >kz3M8sQ&qfε"J1Or!2(ieQK鲦[+h$Z̍ݫ}F6 '7x >C0D -1@PA 0!YAD2k:tg\#ݚ'5|_ -hA#XLHy9(MHj$|1O'cs@A#HX6:DDf|bk_8YɡJ]:GњH껯X&4Q%]HU2E#y!Ǔ}{?nи^X%HÈI#c4*Ʀ<6ݸ|!p$n'z|XѸHYhXJ3qYnGQ%l ˄JE-Uu -V k*RVۧ.at\x~@BD!H *h&d 9w͞wIӕqg<)~UR5zFJ&nfIț#I?dW5N:<e4oH~ۈmM68a? -]~#o"yJv2#Hd& \lJ%?H䂰ƅN9a}jrP$rH 7a'RQ[&T.RjZ&ŢH)(%4"_5M\ڀ. /B(@ dP@ t0 d!yJj7Q %DHd֯`4":VY%ַ(]1ݟXJfLC|&W4S|ٝ7{yb븡Y$y1gHǘq}FF$|FAF|uK_ NC|iԮy,"EQ${H@ u ߸Erŝ9n¾J*d)*%-/jZDRIjj2UJ*4b(y˶綽E$.mANn|!a@b 2(:`B< c#Y{gWf`t0HɣHP$ FU-ٝ΁h$h^m %uYߺ}Y+{yɌ*mɣCpM#y0J~6?$dwnc7/F;Wu#*skv2\YP%7psֻ$ʪJ bkRRbQSb%(Ŋ֊zVJ>QE#Kpvpxp<!AA" -, ;gw*)UTvr`xe_6c֪UbGYU4C i8#Uqɯ3dyoN.m#[v9X,[<h'H^?3/J~Ii4U6?-`mϵ >!סJ^J^6OMnpW Y.WL8n¾o\#YJ%/Z{ԫb,Z(r)^)LnG$׽սՁv]`8^!QȠ -` B`}ݦ&\.P% kU@bV -GkLϠJj_$O#`Dr*]*#7H%Ƙp{Y"+I4E -lfTki$/w*6ƒjT.k˕uOOG#=%;f8Dt%|3\:s_3 4n¾{ x"+UkWtBWDPVH^oE{"ُH#d?"ُH#d?"ُH#d?"ُH#d?"ُH#d?"ُH#d?"ُH#d?"ُH#d?"ُHj\XRvǒ")xcUE҈c:rNɠJjM,w G#`Dz$ #;eb46"9mկ|e㿛%}o\ɈV4#Kh$G#9=ǒ, ;ji}[ɟ\um7OMNpF$o<5w h\{n >ɽ=Jkq$#館 )1Ai%YЫUa/ %?ﶿHt\x~@BD!H *h&d 9 WuJݙK**)\k\PcDYPUӢldhw/e%#q=IB#&0F$߇[%+ӛW>4%k[ٳj\h1rɆ } }k+1 $/pJU&T4R{,&ttZbqW }z6 '7x >C0D -1@PA 0!YA2gGcs"?z}3;:+auTT'D a4IQ[pMc y #c+hq0l\?^_p^F=9v_2h8png *_l#%r (#? r¹dƅ(|aX gJkpn aK2J*VJ&JX*$ZJ<޽sWy}Kpvpxp<!AA" -, ;wצ^UG {R>?zº:X-:)eꪮ(C}a!3X{>HzS1J 40"L|PjWyGSKOC$aD5^A#9mDr,~z)]z}~>"y`q "yRְ>E|q۲ܰ+Tl agk] BPHŒZbQ-Ŕj~΍8oK/pt\x~@BD!H *h&d 9îYs+?N+s% mĽx6ķ7]/pm&IIieQ>&jb>I-{QA - pF\o#Z]Q:g6Ky7*2{h}֡AI0ۡmL-d| 7Ov+h;y9ҺgIg19+pxSme-UJhI*rRM(Ji`9&7Wӽx6 '7x >C0D -1@PA 0!YAvP6TSx3}?0jTQ)-QUuC} -h`O&H&G4SF"ukf.ߋL;3_9^Z@g$OA#OFO{r fD򘘭0ˇΛQp+#o D,ey3LKgL)e^[pCO9&PEMMaY,(.bI׊bEebwߠĥ 8;8 <  B @T@L@r Q;D2y^ 9E$XTBXR䈢1MFh>Er&cVv -]sw#')#h$eD6V[9&jy2[bDrK*9qHƳˡ ,iB$n=<"y=#+݆)z'ex #m mNeZTIUU5]6bZQBZxP[gc\ny]`8^!QȠ -` BP &Gvo"57כ,oJF@"V[C$!Fn!Z?N爹4;d%0]4]mˮ>My6Й42cv$-ܧ}fͫp'zPz֭j\. Sk@?)A)t風'˚*Duu`wӾMxP\ڀ. /B(@ dP@ t0 d!yع)AO Y["xjig(d -ia(fO9)IW8 D+ nf$6bQNZlΚ&xw.+E%^Ib9"zB,TҊԵWc͕Kw>T^ĥ 8;8 <  B @T@L@rګ]Cr|eeDj kEcllֆM;j0sW$ ?1̴jӢHNl|O`N,j%UD"F+dϜ0E]*ZTO䆣Gzq5԰>59WP$EhiyM-mb/k -2ꥄcx)ղsåcyO {ˋ&.mANn|!a@b 2(:`BH{=AF"I'~rt$_;ukfCToKc$/ι?a}H~rl/H.6܄з^SiRLV㪨1PEM-7.6 '7x >C0D -1@PA 0!YAɈbWjWIjͮlm,"4C`(4[Fߺ2"I}r/#ژܙ}Q#zHG#=FPuz$53"yyU&/[i >_dp4D SDr guE6}Dn~XCz%bbJjYL$u:P%߿syO5]`8^!QȠ -` B`}`}`U ^%{3l"ޭ&Gv!xD{ȯqn%o%fb>`U.kh>7K:,C6HkQ@EZ@t.6*g#vbӸȒӞ7:3ܰC\`z)+T}XI'tQW*X-$SEu୯HǮgM+.mANn|!a@b 2(:`BNIA"Tɼ&8̋ɼdzJk۟6/ϟ9&C8ͳH˚<Լ@;\onA4op򃹆IO#Ѯ_dTI&h[fO'RrDLj*7^,I-UKŪZJ]$| -&.mANn|!a@b 2(:`B]磅s9pFi:{kʡ%cgחε67x`;?[K/4OMN?؝rӹ/K%=^1%eQ -b*j1Znnĥ 8;8 <  B @T@L@rߺpIlY鼉د'ۉso,xfMw;DKHpE$r=.!;p#:#uD[Dyĸ+v4׌ou>B|r(cîۈ]{k6: aNFM?iBk-Shs J? H3oZSHAkЯӸG}la}.ZZjveoFZk al4[Re9^DL/dE B0:k_)}ЂKpvpxp<!AA" -, ;X/kC'*ZobAf&)KJF54]ZJ4v*ڋh{{3#qȷz@tյ}m@蠮v\6ijd[ 7aߧUvAU-tK^֒'rYSfug!<4|mť 8;8 <  B @T@L@rgcDV#c: k!q=󈷏&' ȝ$:WOJ5W4I_@悦Ӹo`ijSEmse^:5+y'2MjΧ3~QC@>O! i?Ya]Ve~Lڰ>5̓2q+n~x)e/+xNxZڹmdQӓtp;w}m{ߡĥ 8;8 <  B @T@L@r - -b[lUӹ Ĺl⾷6n! V5D"[D r Qm>g󉹌4[0i4M{3hZiN;yGh?#cbz- F1uj|nw_-Q9iܑذ>59y;w Y|$7&a-(J.ib5a-h_%ESbJQ*Ft@1ť 8;8 <  B @T@L@rLm]S=Iq2:1dyRT'h3oyM(F"H~^Hy2#y]GǠ۵c?H By-DtM"pFFϮwf|E[,x ho^I1&De3BUIjܚ(JjIj9)JʺئdH_]հ_mANn|!a@b 2(:`B<M΁W:; s HڨZUMgl {a D~z"c$pЮ>H;Fy&gFC{$rَ'HԸ6$2vJj;0|Vi==T*$ɢ5=NʨqCϢ.&=^kzgZ޹y&6 '7x >C0D -1@PA 0!YA~6^qF+=W p PEhJQ9(jT}+&F^AVHaFΡ3'2oFN8p/ot^,{h"K)ty8k@kFHdF|1SRHhg/ƕG֖Y.oc\hfCB_N}9|ՆjbH.먓e]LkUTS(Tbj\xs->O#\ڀ. /B(@ dP@ t0 d!yeMlk!cqn!ĵF}6fNo@z5n$爰Hy Q6זޮ%.akM-4QFZCmuJ^j븢Y\׭gH}@f_Ҹ>MckۈEV>Tnq};.p؆.wC 7a?zkbT*eQ*EQMTPЭgZR:0 7];o6 '7x >C0D -1@PA 0!YA26 ah/;zɮ^Dd8L4P5GScEaSHh3c1lnv -#%R9rMʫYvۺk3|5t>w5t0 |Pnd~vz($Br_z N3O{^p3E(pú +ŊS2*g*ukU|:(UrV/F ]ױ|+ZCqip.p? !C$A42䡶Đ[5tSn8q=Y۰vg @O|֪kId n DJ kmX蛈q/ lSFZixbc6뺾iݚ<$QB\:&} FNiɓJKN֟X=PsH7kXbLznՋk?&WE%^%ʊ*X(b5-JU &ucW_M+.mANn|!a@b 2(:`BOR4H뛾V;g-ϖwVru `{Ci=z7;u^OO{~?EmӪ%ZowW*Rkx8WOJX=mXmΥt?t?]`8^!QȠ -` B`}Xm/[ko)Krg+XV֤* MUJ$vw;^e\ڀ. /B(@ dP@ t0 d!yعtsHHh>HJWG$V=Gs1M$S*L]1tj$4G0de#h$ϢE(Fhkh„V:2/F\ψZ%]z$ >}60"9UZMDmAYK#V{J^$ wܩ+?ra4܄}_%5u/K*Hx*jPu-Qj".ՁwUryۻl]`8^!QȠ -` B`}u>}y+6FQ9jTSu#H/kutsz";<.GaF$=mDs3 ˛b\˘YEe`Dr:}mڷK#F _ޣdHTɈHc$9f3"9 .)" SN$/z#ɿUn¾{Ai%kb(JzA,R)_q"Gyq\͸foY2sø][{-XTwiH4k*mHˀ-6jm55*QsĖ*i#RK߳I=s{,?^ {0}~xYO =~u~O Šaq$ (a&,pƒ!j&NzwsV՝Ky Ja5Fug$i'We$Q̷nUr"?|NV{H^xC5 MUrJFbE$ʡՊH>'#yPV[U>[HLи%J^hUpɟfVzDrWh\/9%O{/+xeh=][NUJJ.zWYOmx<Ơaq$ (a&,pƒ!j&Nmm=5~}*93#3#:doGqg猽icd9ެo -f-ݳݞ;Z{wU`nV^czsϢ"RWL`^ӮѾbNҬ5{QyB3n?%.h>Ԭw/i <O/y ~' $qeyt7M+U,[bJ6v7|HЮ'׽BlW> #$tFYGE c%0a|QC 4qr5Qq;t+ W#-W3iX- lm ͏Gr<"[Gd$("yoVW8$OrqFɊ"wH_E }}ضYj ן;w[ZB@HjºԵ׃/y ga -wy-19Y)yb? pcN{ݚSRɿ3wWBA H )A9Q@A LXBPGMDF,쬄ψR"g\#oSQ 3]aD)\djgMv~X/u\YY%+!{;x#]3{oFrdI1 \H/#9/'yQ(ǼܭHfEK3ߤ}5;'W(_OZr<~J~妚lkߴW~iK8 ]oycCmq_BS3Qo{L1"9wG/ xA H )A9Q@A LXBPGMD҈7T=*ȅyHμ03T Wx \@FwIw=}HNH+Q:Wy|H&h(o4UrDFrqA6H*ZN$RtT/.±T{@$ wD:U7(j-qڿaNPdco7ܿ չ?B1c1f103N&dȱj[m -""RH-"UkRpUEKPZxԻTEk-g;DX>?}C|y>֬xn|쑏15e}i) L&+N9wX)N+}NZN'N\L֒,O)SjʼnթGO={+gzsUtROVdESJd:)e9O j:]kb9Jj\KJEʲR*LF!wҩ_u<>ұhy ?̙3'(ؗN\!AA"QA 2(:`9C29p=%,|ONM/")) ':+6=>-1 4]LQm>Әf.$VO'Sޞq9Ǔ˗w⿻'7_''/?rO>)@.H -џ??E{9*qG%=GvS税ѣ;z G=9GO?n/U]Bv럮v]_]#'w}g5kp?owMGW]_ܫöwuǝ&inW[2_>pRaߦqGrd̰?5?\Wo =ݙL:1rIU\TEDZfZh2_ұn~H&.A/  >C0D8$@ dP@ t0 r_%#Kuy7F ia%R@*X..$4AFʚS%-4TSb"5y##QG#F1y%ՖDW=FN\!AA"QA 2(:`9C쯒sdeU+^͗ˁB'DD҈q+adɆbf誡#駑<{|ҽh$oEr?F$H#ҰF$ˆ."y^DǬv.goId&&IHD',|w#G&SD޲uCHyDH=#DQ=MNu7nq![nOd61JT+jX+eɌ&F6S*UTZ.j4W._շj$N\!AA"QA 2(:`9Cb8%$pi5|_#CzD13'L$YR$$D"MD<5H|c* %)ޚ4ȵ7y #b̮.H䁨ж߂9%;[жe$w͗i)j9,\qֈYH [nOdFJg5%c%+ZRb*gDEil6&eP#?|oD  ? !C! -1C@T@L y(U,HʹsQh[> m5"9$ؘD^Ic䜒Gy4ж/&I}F|xX# F"F^W#Ȯm>$rV $7 -DΤ|Q#_>H7c *M݁F"uZgf3@*Kɩ@"{D$rH&6 $k+Z8 wH–wLdF5E+T:)f+ *[-7kGH׆yZqzn^!!H *h&X<`@RG"t9O#}_ P#z,Č]ѵi9܇&y:H aF"3y]\صN@v|pY99!#gЁ*FL,lDr"y(..bD0SKUîjH!1%DRTYSUT@Rb$4|[<w#0eþ|$;lH 伶s; -y䛌HF$fTI$oUF.vYH370\̎*)6 s"XOsܱ׼H~햛#v*`QŢa$YSj\Id1Th$ ӷ첿H  ? !C! -1C@T@L y(ULHJ[B$eHP4F DRq9Q)!*)|1h$G-uHZt$9}i$FGsuQ%$+*mMI4J}<ӑ䡌DNo}]9sCIErݺF$ܥ|i.2OMNNu9.?4WZnnO&wIfkbUjP$ -V=#jMKFi[tp߆i"qzn^!!H *h&X<*SE" I Fz@ - a|̊`Ila$9Ra<㢉 HEt1,[g W3y\HrdN/K[#9Fx [⮑-܈dc("WQ$B>Ev$gH~[E$]qeAފ"[7qfh ?@*RjOJVt9piX*L1'UՈ䪻X2$.A/  >C0D8$@ dP@ t0 r_%L +y>Qn5xH ޷DT#d}kv[#7"#h$C4cE$ItnuJnz_ɋh$HΣ1J4>F$EkvGL8 -kWo} =#P%G,5OMN0cqYg=hGo܄ݿL`TΖSJ&f*Y\P%3b-S˪3 +V:S[?ĥ8Kh$淚;HNcҝ7UqaGrg'\1OHN݊HXrƿJ^l?E$y⌷9%Ur{-7aWɬ9٤^˚=SU5KJ(JQ˨rezg`?XCN\!AA"QA 2(:`9C yc5:5 Л4_"5.ajr-9 o\rv@#K-k59]մ0$(bUaf݊v4t7x/B~B $A4,A -`Ĺ2"Gy^II`Bp M !vLg$1KKD(ED_I悝DcelC11A40bzm1 s]sM[sz靌Ρ퇌N 4 sn>v){U4'r3+'#D!u<>7ǰ?ENw, CN\rFk`K=ShXTʪXE1JgKJZ-\7O.zn^!!H *h&X<*p3:oPfhTxd}Rn`I9Bokj 7G'|1H<ш䝌Hh$\#)F$"H{[{vh?5HĈ$OK #Z:?{H,}wYdNZD޸G?"u)"ycwH.<?aMI2ҵd5b5ؑVb*˥dYS:X+ߥĥ8܄=l)[$e1jvJFORbI#y"M6gpzn^!!H *h&X<m_:t˭x `vJ.]q)!ɚb-Kn|2#sK6nuoF$N7ul\(-/&r MDЙ4g$o2b|9jbENXЃD?9ˇ 9[;υ[nnO+Ȗbfꆡ}i$?dl~݈yəJ~NSf7dzOyMGQ%4Z~G$(ӹk؟J~33s8sP%x[M[-;/+YY+`,%XRel6[J}$.A/  >C0D8$@ dP@ t0 r_EsURG@j`U҈H}]PSJJj먒;?DFFĎшAHHg|HNM")L(wuyexuѸaC0D8$@ dP@ t0 r_Y|~bqCO?N/# X4X$x#>Q?!5}$'m|QL'NFZi --!FZS]:]Z_>n}`F 1:ue(!Bd3zɔFZOZ>Ņ0&'bzo HkrMMDCVr2UR*-zEԵlVNLsH=7:ť8'hZ? F"bEﺁzq{gsS2.YH4cfFؾ /<{>~ZD/hjBdIE?ش wG&vZ#a$.j<{' -Nk$E#Ybߢ-u~4g2"y|6"~|t控(!Dr.O靈 S>i}1")Ao܄IS\(rY,+fUMDt(әlEJFr."y?4t7x/B~B $A4,A -1"pɟ=W85y#cV(SeU)'kcE3d?ERF$KZHHH]y5i"DH"%D~DV?Xj0s*!kd&1ȼ>Cv'u!Q$H<ٰ?EWѶ?\߆-7a'Ue=1Tu* T*z --3w!>[t! -.A/  >C0D8$@ dP@ t0 rleԟk& d֥1-D?D1Q$c끕y30Z$g$pZ$7"9;(G3D";,*ύgˆt$y0-fc9o6)@$ %n sEsב\q߲ajWp:"suP$.i {/JfL].KJQTJe6ց[^zٷo@:u}[зo@:u}[зo@:u}[зo@:u}[зoѷ;Vh\s@zV\Siq}.OUbjl=aEMF<ɛ\LWi$2os#iKUf7Z#9F5ӥ3"ym*ɘH*WfUrs{_[:xɰ?E$3,.kgHrvNsTd*fJbU2c'f뙒(jJ^󕎻sM\:^p<|!a@?D!qHȠ -`  j\o?\O\2YG{&$p' >JBkI5%GHq@o':L D뉹~Xu M+hWt<.*C>.]QOd|\3LG=ߠ̟_ \?y on/Q@[h\i]Jg Sq;0|֋W܄ݟVY~ t1+e{IgRX3Hղie~.}<^>'p .px (  @PA 0䡰Kp'<2y-_o'~o*Q+A$9~=Qϡ43y)]#ўIH]$R{.*=njXfxjo#{/7_(.Hz~vw,l0OMN'=H܈aBM\@%VK+d .)QuSV*T.Y^_ѾxhW뇰z$TB~F"'aC e#/\ȈZ$8B'~eDr."q|No5H0sxF$C0D8$@ dP@ t0 r_e3q{v⾑x#j⽏6 -W -^O".o'[pK(khZiCb ?#7b ty9C./,m>~ViOgFZIGҞ㋅{.kbׇP@.iN~GZFZ?İ?E}mNr͖FԲd+ZLմX*ޒн$'-i+/_KӊK'p .px (  @PA 0W8(q&ijY@H` #$jO}7iZqzn^!!H *h&X<*k>;kCj{!$x #GIF;z$$­#D]G&;MӪ3bOuhZ9FZ/"#P@i |=8%#;GPhIv3Hsٵu9'\Nx~Hb{o<w.<ٰ?59i'wxiv8j4&NxMkjTB<%KՌY-U5ZɨHzhZqzn^!!H *h&X<~[GDxH뉼(OVU?go=1)e\oдFhZG0:Z=_>'H#Ӵ^L<]ym! %NP~DZiM,3OQ[oZ܈sqsdF܄ݿ,=%-Y\ZIڧzNUK{vV֚ii㽟t -.A/  >C0D8$@ dP@ t0 r_es%ĵ/!+oby$894#<-u$t K|=Qu)76'"n{g¥EFFLK%/#.bq әbP? iL/d4AW1Lj]t+?1bzH1͵;w;3PTlkQ_Ҹ鷧}7-nq_-!=zM[A;bH늬2E,jŲ*jR+GQT|מU\:^p<|!a@?D!qHȠ -`  k;'[A7~+G|oVFЋ$l -B/f"HW(ۈh} 1sNiд(:>2=Uo+HC~+-1FFZ43E4\z6i}EuiGj矊mpG}_&'`$3Cg&CcvVǬ/ӽomMldJ\1}&sF2Gv>2C𢡄*d>RɌ_{a-qǮ 7܄ݞ̔9MժU m -T5MKTRr T2z3nXB? pzn^!!H *h&X<*$͕sW Vᘽ̌ACR/S5IO\N2y۰N &,m$r,#{w?lD[ -|5h"_k"'i:h[ID5nO"ˎF"XH S4Cn&C" !m ?JRZZQETRLMԫ(gk%UVQogsJӯyWsI .A/  >C0D8$@ dP@ t0 r_ed}2w$y5rK*(RN+*ԏWvNdsF"u|+t6ME<qIy<ܞ |Fr"H}MS~H[A#"`>DryD$Oh_fqsq#xD-7a#MRdrVL#͔. -zJiLEr35?hK'p .px (  @PA 0䡰f $BOsg o$eķ#k]R^B"$~4"?`>؜e, mz,MH둴~:ޗx K -;㘧`v$Ҏv֓xdvpC6DXC0D8$@ dP@ t0 r_ezYjNUBÓ"cJlf(LJdez6Q`L1Ky80yM~>ǛOc <&H |ndB ܄ݟH󪿲V+ST)kz^gJT5RO]<❿4W&  ? !C! -1C@T@L y(U霂D\g'xˏNMO LBS"SIىIi~'l,7cr\[۱X=\sGij)լ6dJ1]*Z2Y)kͅ텸[4t7x/B~B $A4,A -`و\!97?%0dggG&"ng<, tm>ݘdhvF2sXF<&B(#E2P'=/sqe>=Q,3b2m_v|{2Q,G ~ rm O5W}=h_O_j؟XUr(&d*U3\*Zd5)JUt])f/4W,zn^!!H *h&X<~-qηǚk)qB<+߼%$x+ -!|ϖZ"Yy?QzBw}l`5a=뷺B2Duk7Y뾵l~m41ZۏiZ::]c.jvu"϶g ]77/Qy%b );QG{}ĖӚ$S̨ՊRKl~#Y> -VRdU/7-Cx׈/{N\!AA"QA 2(:`9CsV}9\w3}Y$8!4-|%,sBC9DOD9Kg3mp(ciFgd4E3:f.2zs3ڷ~{3pgDѿwm'5N qGH3xLK$czrqq;7[n}v~C~ǵjOѦ*}*jRT]9xՈU|VM\:^p<|!a@?D!qHȠ -`  =R⺒b]D|$$ᙑv弞Į#%$ OS.#"/ -Cw|{V? |Fאg]..̵{sag}:w֛gv" Ȣ=1 =~SQ?g]WNŀT]ygBh H/\rv{X!ZVNb.jdVSt\U-|β[Go9^p<|!a@?D!qHȠ -`  ~DZv=Ss޼O F#9:62>*1R)H%浂nL -ƨo HFrd_|+:Fo}ngH'c=i]#<ufh$/gD.ZګfyD?~ni'<|"yuws"nj7O177U9ny.ԝ-7a!ꕤQk)p꒨V1JqNZހӳ: N\!AA"QA 2(:`9Cvs]?o'{%E$7ٷt]ODHˉ(뉺hsi#4LGwܘ=u?F\.:ja+QtQ@ns:Gb @Gu6Щ(Grczu;}|-zҊOMN=g}A՟h ?Le2U+隢jV:,TT--ic5]\:^p<|!a@?D!qHȠ -`  :AadX$l{t$Q@6+6g$f 4~2EMקR|1`n<<#Nl2 |F0B؊~v߳&Hh2݌dCYZa$s\9)pGd1{'!1\d؟"'` +|&JZdJbF2YTjɲZ6C鈵&N\!AA"QA 2(:`9C2{5۽x~wVbb^gЅQ$7i!g*KhK r׵=Ǩ - i%=E}s7~:{.k;?P -FPP\.A}E z(E =]>'elcM25z9Jz詿X f܄?uZ+Jr?5bFV%Y/&3c+}wܥK'p .px (  @PA 0䡰|1KsOkZ!=lDF|ddI>&WjZ6_¨H&Z`LᫌH*ݜI{ ]$?)h$a zՈW4hNEtԿ` ɍ[hDɳ'k\r{73OMN8'gq j#9Q~)ZT+U1گ~ȔuXB8r\KiY6p_acs}j>V8/~_Jxa4>a:Md0c7Ϩ0׵tF07Z92`;@W ؇TѾ+Fכ gd~d~pidGѾ1ɼ3$ u S8{VwZoC39[Rb\ʈj~n:SLVU49-7k[w=ӷN\:^p<|!a@?D!qHȠ -`  TL%N-#j0J[%ATYSd5Yb$4뭴V8;hoZy #xsY m_q>]F2E ܋Fz}V${)w}~FF$s=V%I&9n2D2u8 Nz);"MTIyajr`Y܈/hMLʝWrY;j_3oe[s]auoe[mv}vfiL 8"݋4M83̌A"J - !: -zBV(=s{p =<z%<㽇}sё!Aa QA!Yȁ - -:`Alp__wi}Ai/28vs|7;8)hؗ}87)C>uGOļ|yBXB_4簢ItWY;Ѕ͕3iG]ql/6('X@c MTz]+[F;ԉ666E v7Gƺn}fKgM`AN5ayR bjTh4s/V3!Aa QA!Yȁ - -:`Alp@$IHpY"zԎ)q'kJFj9EVehcmbRSܕ͕b_0 NkDO$rY sgLqV'&֚E;N$Èӌ$ r9;>*kF|#ՇƆ'fa7[ۡ =0^,gc2[0|6-/:,k[L״bЪy]g}jESb_EY$ϣ=KϹdģ!"AD!qH@R d!2((`eI9`,Ұ)UI+AWzҌurD&Yʚ}Rd49#c,8Em#'"y#;hcϘrQM\wEc{d[H -lq޿ɞl58:L8oUN[Y-*F$/Bzw}/"~Ȼeaaw,ޑp$izkI+JjwǑuo?gX#D\TiCc=>E~ "!IHA2Ƞ - \o1 ;q$$&HF$m,)/U:*"IJ.l8yH",T:iw煟]Y~$grU/#p"4A6sӸ. K7NDB$GOq}V^]dܷ -ۦq4bz"Xlzr1Sﴼk_%)#tyּjCMj]/Պ6Gh\9yI*ɞ'\Ÿ=ũWHel,ys̿~0i\@$/^j\oH]in*rq"}SиcI*J~[E|Ooy ^$j-qڿaNPdco7޾ չ?cLc̎1nc afL&8cH)RkmT"EDDU[R-ETRTZk^+ǜ5k̟Ã,:æ(M+wb62Ҵ3ҌaWjڴS+zZeiqjiڌΫO^:6_|S}f\N~M+r0EV9dNIgrV;E87wbM)Z&l6't+U׵ _bu eܹss/? !CAC$A4,Ep2>Q+~3 ͐.D3vHSE!%Yiz0M -}ž>s}i]~1?s? пKyw!ga].F73nd>OT\}?T>3>}{W{ak=`E?_O<`muA~Ⓠػl*a v? ~۶C뻩kG' z%.q} *)w]/ܾfp &7)w/ TΚvpwj$\H*l/Ōˈ,R5WKV"Z>M#y˵_$2w=ĥ 8p B ! HB -@T@L*\ -"YXބO Ġ#vTI3$TA+ՂVԋ8l$4'2"kFK4Ff$G֌JF$C$};DrR[$J|}EU?2 42Mf"tOg$2rMn8Ȃ8C"_Dnxpg}pʶ׺&N2|9eQj9ZI̕$JȵH%Z$q/? !CAC$A4,Ep2HS*/6"脘DZ Er`JRƩfa>Md(MH˴H^K䞬D65HS$=^Omi$bDrN\[oF%F|"?`DRL"y*IHN@|t+"O7(bqGLj;'݄Ik$itQ͔jb9SR)-H^w|g?:I\7x >B(`␀$@ dP@ t0 l(@ q=BsK|7~ AFZOIdKb/$fXGH1"l$D^CD}hYb[rafudzZ-zE| -{]D#KpẍٴqMѹHC$ԡq}WO?afVɽ#;swH^t|a&ד?1~ U_o ;=|̏\WJ=k-_/uHI+rV-ҺV4K_1P3tn|!Q !IHȠ -` P"8_ek3\D y4K0#?N$FX5Z?Ab补׵~֔:uAGP{VӬcVѬ4юc 2gZ&ס|<&4giY 2{0Oіaq1FNl ;|YhU9]s,Z.|Y͊Z)[rLs#+>L\7x >B(`␀$@ dP@ t0 l(@OQ>%yï`2b SJ$B*%(,JBMhI]0dSl"5Ƚ\I!D^A1<۵|VQ>T>cOyo{$GHΧ?_h$>}Ah\(-ϤK*7|u_KpxŘfNl&i&ddf&L.*LCK+8\ةLteО*xFB(`␀$@ dP@ t0 l(@tdr5}?ޯ`1d3̈́TREad㔂kam03@##HȈi$HzwqysHqdGr*"(s ]_cρ4Q~HNa.o3 \|297j ӘanEKйFZ!&6q=F\%OXec 9Ōʈ7݇^ -q]uroo>6δ8u6wԁB\o ;U/):JfI̔9Ct]sZj:Ϣ4t.]<!a@b0qH@R 2(:`6W1+4O,^[#Jc0v(l*jj]3 Sk)][3_fDW5#㮏/˽躤4-EX_>+)D..grt#ޔkDD P'#\qDFΧ(?kq͙gIM4%ҨVrYT*ƈ39-(\MЅ#ZKpx5-.\;^Z›݄Hzjcz,|I,2XVժDn@"h`]iY\7x >B(`␀$@ dP@ t0 l(@"5Iz%T BHd1yo }1I+gT?^Fеr֕IiLgt>Ќ2&|>+>v}h -eSGs!=鉏ܘ"m7a/uek|ʢGTjF̧s˗l^O+֪]ԛN\7x >B(`␀$@ dP@ t0 l(@Nkx -FtVDU?Zp񩉂%ٲeehݾp*41y']'2 Md&e{XkczJ{$ϥ|1᳁/ߥ@GW/c"eOEsN[F};s -n[HZɽB$wܡOkqOۜqsM37©"jEUh^媨Vig.srYeZC]B/;^vz!CeB/;^vz!CeB/;^vz!CeB/;^vz!CejD* )RHje d#&") -)Yd ~473"UZ$HJ4: 3=!#:(ٛf /v`Fr]W0Q%O%3>LE$ݼ3{isϗt4_3{)"^nlߨsvv~tɗNUsYDRDVĒSddJC+Vop`'H^~@BD!$!H *h&X`C|KkLn -ZD1qScA+h$c4.tMF$K#GcDW=_޷:T.4&E@!W1,h\O:;FZa|lM%쭫~qCMѷj8qe##3_vvO|JLN3bITQTb9]z g0l{RU#wSI\7x >B(`␀$@ dP@ t0 l(@5J4_XA+F$s -䄔S-h\H/Wo YŨ 3Ɏ|ђV<#Ҹ6ɿu<"HB#y J޵ű'*y KUK>$աq]#p"f>Dr~͈Χ&'=<;WJNP \'[n.8:tjMZNT кV/egպj<A\<!a@b0qH@R 2(:`6$Fc,x ~_@4C&ױQ9&!且%ؒ&S&֎L3"h$WӱňKbHHX6표L#Q%_U"F$c4"o1"i9Hr،u3JsAD/""Ͽ S;Xen.޸Vrj&_/ZZET4f\YuR/e[UE2x. xC0D -18$ )@PA 0 -Pm8[*9;7<.E&D'k& -@ձh\ FXr-#i$9i>4i$aDrBU򥾮'Hrf=HU2uz>6WF?qq>L!׸ka8p;"q.`]M穦rNl<,R>+Vt/϶>A${r5jC 8p B ! HB -@T@LaX>`iW`1q50yl(XK*JUrlJ>42"njHNUAק\C<aƵNDQԊӘFDLERCh" O{y5P"g+Φ.ȻA?F"\j8b(yͧgзyn tOJM͊igQŲVLWjΙjso<շ^~@BD!$!H *h&X`C =QyRk>U &0#JI5!TdU4]1۟ٳ7M44wН#v_gH=}#.z4fgoL8Gg g2fw=xF$Mݍd2Hٷ? \"yɇ6OQ$gbq=y)Ǵ݄]p>HHz9-gJYQQf /rVref3ޚY,5Kpxzfоkf;FZ1}|t-ovxAaG;hil.8in-[ Sҗ1ʼLk m7a2}Jj]E隨|@Kjt%Sk2^y_lo^~@BD!$!H *h&X`C0zI2/Lp,'&d*%GP@e]Gw*0 -M0M]wf"c$D]7;ו:"?~i"-Om&2Hty8MdG{ya>i -\jV$ƥ>B1$rjK|d޸ G"߹+.p9¦LR/#rUT3Y$2S΋eMuU+`݁}p/? !CAC$A4,eBJɣxM$KVcGk.1so_sa%JVKZZ~Ϯt^te"QǴGk4w3" --H.YЌ4F$GrE9hiw; i.kB\Ӹt^0OMnG0ٜy{NM/UI1saU4rZq]jz[KYh7Zoƥ 8p B ! HB -@T@La/:kl~4BĈ-4^;{|b\Q8 (`tɽh$ÈUm=Q$P$w]΅}up߃=ʼѷIeN}>tc"-.:g/To$nc2Ee]0Oѷ>8Wlз~cM鑔*\j-WZ.fW55[+7du73F.\7x >B(`␀$@ dP@ t0 l(@mGMq- gz;/ Ʀ g&ii$ie:I/ ƔOoug0ƒѥv1F(_ɰ.F(NG87S|"?}09EUѴ.sZ5ZDpG.P^~@BD!$!H *h&X`CxzJ\wW. Eķ :E9+2DϜy)y$asle:h󉾐sv8uݵzhܛhL=v^̈i5ҙgv{Њh-,`Ι=ҽhJF3Rt7qv'3)%['i\/}|7pvrmF\wǵ݄R|Y+L^*rITͲZrRVrkV)Ͽ ť 8p B ! HB -@T@LsxADS%$!gId=%n I7"?L!>F DBܴ]Zwi}y]'hi8ʽ{Hk_'.oz#ׇJz-1 PTWu8roNc" nЎrK ǢZΧ&'8k6g6uQ&N7=zZ?MgO"GYra[e,e݄_C3#V*Z͚SCXֲˢj\EεVP>vnXGk(.]<!a@b0qH@R 2(:`6GD| #Hא$ uj"Iq"XFiAq#ObJ S}V -c c $n.xcI'*rUjUy='jNGrOZ|MZ\7x >B(`␀$@ dP@ t0 l(@rk&9xf{ %YhjߚXLSR3oHB(`␀$@ dP@ t0 l(@o%[-s% 'llL"7*qxyI${we QWm c 1 ?F\/q 1tk?z9`qu&lw+{ٯRC>ʈ>t[rWPA/ay ͗ s#?0ڼq-^ -:k%zd.@o."nΏkzsy, -z[gYY]Tzknh5/>u\7x >B(`␀$@ dP@ t0 l(@ "{sCFDC$ד$:DbAkIr=IҘDsCk,c 1$VʈkHF\'ҸDe5Jߝ)XQg^b)Z\f*д0Һgi}f6{mߌ/ _ W=H_sgΧ&''gXos.pܓl hwFXQ+r$3jYrVi{iSV\7x >B(`␀$@ dP@ t0 l(@K'>F|v$ =H›OCn"M*%Fq}hEbִ,\ZgtMg^VxEW>Fp/? !CAC$A4,Epl\'ˈgw M O̊Nn'ɥ$s4U^BD,-mL5N H ZKÌt>GyM^t~tnvy+؞lT#ZLc=sc*uh}F59PL+&쟡]poMG[_EV^rVvDPU WsukN[{W__Gtn|!Q !IHȠ -` P"l;9 !ѽOS~! NF̨3גJ)%  -aOڻˆd HHG<ɣYMv"m"?-*|VAI-ZOLBBq̫EKpx: bN-kTN6zee wn8p>59ᨭhxwl>&삽h#RVFLՒ*KzUje׬x)?{ .]<!a@b0qH@R 2(:`6Wy:tneB|U^!IYH"[IDqx$%W1/e Q7m-_%Ƴ|c-HM]ݞBOk]w=ߥq=D'q=vK3:=ĵ6A(E\5;oC\xƍz Χ&'O>n\vvv\ah59SR|W|,L*5S7=|u \. xC0D -18$ )@PA 0 -Ppw6op1bș8ڒ%j }PZ%t9-/Ȉ"Y wKHqH{$QAVvیǏ\މ6*`0[\8{<"0"p>E}gM9 5'݄I%;#|l$#ETsj]ju\3W(w^~@BD!$!H *h&X`C|H3yo2G -Q;7^otbrB \PLDB$w8U%_X%<=ݛ|{x~]8S$WUk7i$kH~JGUr_Ԯ/;q-Gtxsô=nC$gNr>N$WJnU?B|{"m7a>GxJMQuS=FSKWRRk骖U5U8离-u%wY. xC0D -18$ )@PA 0 -P\G\KvgRXDKI >#%d "\AR5q$DjW=yE̟hh 匨BVO{gu_[v?"?|+~63iTiCzFzxrEDv# ^i6RD3Q="n}lq]1=VnίZ--sYRsVKΉEZyDZ-j\Q]W{6tn|!Q !IHȠ -` P"8_EmllEЃcC… TRFGbc5ߔQ!I7twɉ471"ymhH{xPRnObE~ww -E2ٜ3a݄H=}K^igKV2V|]-ٜV3Ռnru/#>w)b^~@BD!$!H *h&X`C H8U|"9,4^{daY&JP$ǫ6^gXq<1DAy2#+{[mnkU<&Dhn#kCoH=[.fwO.y[C"/q >H aqu0 mԶkٌR/keMʕ*zVW劘Ζ2\t5{_xoVZ#q/? !CAC$A4,E6H=c{lx!P@"''Dq HdmXLV&m8żi"g$/`kZm+MF"/Ogwy[~|k=&ri)}f,Mt y5c yn5DN*=Lȿ5.VDyp>59-.|;HktM骘mj,rWZm먑/ ,"2\7x >B(`␀$@ dP@ t0 l(@ZG YUm0dDM) dUԔQ6;m[Ck& W3o2)V<Ig;.-ۋ41/H>A4Go_m9N2H? wvVٌrw0OMN=oڜ5Dr`ME2}TJk%FjEOHfb^s^I1LW355|6̿5ĥ 8p B ! HB -@T@L*IW+qs%8_C"/RQ$_b *$rn.h['Rɨ9MɉzɌ%YZUk5ɶZߌgFi"q/? !CAC$A4,EpʸƫLx;7V -C"9S c%C.* F)K4ӶF"Ҷ~ڶH=N8ErE[}i$0"Y}HA+mEo}E({tv;t,8=o8Hh(K. m7aG2}Cu-WKZcSWUW|%^WBY(9H. xC0D -18$ )@PA 0 -P|p[HǏA;41<>bEOHNHXH~C61LLH(O1"z4aF$w}HsgvJ{'k޿z -M<>Z$(?by7އe6tӸ/et.ê|jrbz_>v~Am7a<V*Y͔rEωk2jkA2ɇ_ز4tn|!Q !IHȠ -` P"lK8O ->mk2DžFn̶H.$Y0TѶlZ1s-s3- -]%0HE}#S$~^$k1"9Sh$LJ7D2ͲǶ,ölް׶m4;;;73nN׽,9f*H^epvFWhBH)$ymދ{y1,3{i-z^̅ƭ{*KNk(k^_gwKr"DrnEb%~COirVS$KZM7%IY1ǭ3|5X!!QAxH@z!iȀ"Ƞ -`( %pWƸU (A#$sz8QJH =4.%j*"")H:Hn&K')O$"R"vr~t/[na&y2nI$Q(Y-$ +$Eֺf"M/aW@fsY-|%UgIѧ]_zj%G? B8C8$B -ҐDA4 -PJɝD -bqkHZ?XpBcr![VF}/.R$S'H$RB3[[:xJSƭF+H.";I+qJ- -DvQ|Jc[׍x[P$F$-WDr.G#(iz ?npVj|E2JWKl֐*n|֬L~;ЂCWI#:B7 ! B C )HCAT@,(@l(޿`&kl~ևw0 -ﶅ-Lb=9-&3++u}cdMoD^\JϾIVFJTw^jXy#EvT߫3KQYIIR@E@E"]""EZKARh[KRʵ>z}{w#|5;w?|u߯~z NO>}F~ZM!ʗ'^6yXȟ~i\(OD1X.NeYV8raӫ_14'SԤYQ䄒O6^Ӹj$QI!%rBTj^XIe E¿{Z׮Xߺao]vΙ3'Қ/A8n^:!QȠ -` d 9JΑZ ge/s/s1lGKh66J!dSQZN׌ѦedZ-ii՟8Y\S7?'vO??O!S?SggRXk"ȭmEkm[[jmɴdܛU=+]D=7;5ǜˆ7\˶.{^| 7^>_JY/es/lGeK/<us\ t35}*wĝ~ڢsǟȰ?59!qYL~ҳY=]spZZa$r<0DE'X.㢪'bAMDMVyɅ_ߺ`Wߢĥ8h' C0t@'D -1@PA 0 d!W"9+tȀ̆Hf#z43U2qK4U=cȦHI#9Ig_w\F#yI@$gDrUԏ; HND$WՉ;'?ϪdFK#āHfD2h  DɱMDr+c$qC#nD2Q8{$"y.Dr+ S8c{Μ8]s}$THe(IQ-1].jbHk|4 +mmv@$q .p  B @T@L YȁULdrs)q-&.~.,$%7ӿfR^HΙe$f Kˉ6[e,&|MSHQpHiNZ8gOiaJHzr6iJc40R&MiJ??^Hc -HRz,so]q)N,n8Y_>&i9޸pդ,ꢜLD5t>)R+Z<^)]=};nyoC/ۇ^lz>}eC/ۇ^lz>}eC/ۇ^lz>}eC/ۇ^lz>}eC/W-CFHG5:/{ P8P&l!Vd -)䤑rFTMt0#H>_>0gHɁC#ՌHDgXKɅ -rM$4_fD@$~Hh/;HɑtpP8/*q1D2|t"oj\ƙ .v3 - 3nB$Sp "7d>/WD4J"UEt" -m{ڷ]I\:6p ? !CtB$A4,@r`X5!g̥Dl/A- I51lLPC`xi>#h$+mr@$*|Hv#yAUq|jxzYG9~tm&M6F"y?Hp ?>q fMɣc$cH>./nOt!K7i)D[3<䆎iq?pN"~&D&'|ʕ\TBQC+['1hT4MCHcܜ?V 7/B(@ dP@ t0 2_ec1A$OE廛JOBIxKIKX@j'hˈ>͘m.$慌N,:4ChsOg==ʇHZsCl<+,T\xl!Q {6QTF6|Whĥ8h' C0t@'D -1@PA 0 d!(XjUĹ&G<ˈ[F H` .$j\C Hl%i~u"h Q./!*bٯ4`PwH 4Щ#ih"4lrKJza}~m2B)a_iXObO^ƪ"è_kG%g%ѨgnAb؟b)}miG v[M83xYH zX5]UP? hw夝d:HZ9Núj֝snO\:6p ? !CtB$A4,@r`Z?#NeM^(^'ai͐6O-E͘ RD0T4UWvĨWӖRF"8b DH&볷lkXIC0t@'D -1@PA 0 d!W1Z~<ݭb0{ &5R]T%hRLI4A5dDEH/Z?OV&&rylصNTZH_9~]m"/`$%ȟ3k%H䷻*8][UHcW#-H_i؟F.x͋nجwGΨ >Z5̤(,&zALWSJ% -K" mq4tmx~@BD!H *h&X,`_"jתk͠k*V+ !#A"c ItjH>7gH:ܶm"KF$H7'2\fɟ ~ F=!jj2-IM%Q҉b1S -zu;<7I\:6p ? !CtB$A4,@ron[2x2^gH"EDRdII겑1G|:4s;LFHE3"9Gr`mk"Ds;&R@H^zH&vҗ2dE7J\7@,!b$˯&7oX.Y.p)57'2x D$}( ~$6ՃѢRLuQb,SJSD>wOZ$q .p  B @T@L Y`"24ò̧ԑH{ÈQVE 3G&rHCy<#ߣ/;9=M$ѣLBY/o-37Lj$A}>4h`LM:K \G%N<~I #"9m)dGg3ONzšp6.RIqI5Jz%!T2j -:##з[ 8h' C0t@'D -1@PA 0 d!WT`$ >H>2tkG4A!U2zT>ci$INӭ:#.nM1"#>{$9gýW(]R\4m{H~ȟйw[D.jHf|sLVߪM9$x -(>cq_.Dzh['570,I6$ғD)]D9/\iE.r"*|^\\"}#&A"{$A"{$A"{$A"{$A"{$A"{$A"{$A"{$i߷D"N mg=W(Ѷ3B􈎶HҔ5e!5O'2@F[%r&Mtnnb$[/$:/M,F\Mg[Si"5r#_mb:qMD} s;6"^и)coE S;,j zg!jdfYM8ԉTT\*QMW4HDy)=_H*> |yd5*V m~) fB@2XVHJWumn Fm;OO@"Ș#ze$&և,5ۑ95C#2Fro 3F!:|NIM4HΙ[瑭؏l#2ER2D2wD Sɟ!! ܄CxT%*)1%ʢZ.|uU.&) (!ַYh$q .p  B @T@L YȁUκt(04[ε^}d^ #Zg l|`|.dydD&䖣r@1OGkM4DQ$g"--7|DͨS$!wF"_~ 4D<.7)'җ3{(57'2x')|Q 큤Ѩ*b9E]Er[;M$.A8n^:!QȠ -` d 9R}r+#=P$% $\u'1жfQ$- $ϩeO'Dȿ0ym[2y1IGkX{[(sEr{9]H@avZ$'H</@:nB"g֡H< Eq S&ERb$reMA|PқoFFFE?0"t?"yʵ#>d7t12k "yua[ZJSGd})tv#˚&pQ \HW%_jSJv18<;|MTɝ%cɑHܐM-S)ѰONf&Aϗ^X_y9 F;257ЇUi>RRZ,%Q-I]Let*7EXm7hXq .p  B @T@L YȁU<;ˉsq!`B[NH` .#$Nj;;+Vw諉H.eFTWNOxC'NdDuSE nuiadf ~R,؛.6q WQ {i]>ral13\Wjoam|2A_'D"J\˕LcӅ6꾎VwoD.A8n^:!QȠ -` d 9H\9wgG ^w Vy1Qu>іWR_B6L:NZ@m27sd;Bm-9)wFV~DúL=&&pCk%nxX,^Vwư?EX=-nxxº~º䥚pg"}w_e5IRZ&>W͗BJϧhX7~.}> 7/B(@ dP@ t0 2 #b83.ݝ5|YyA% C:U5ѲXMӳ>OŨHֻH\M`$&G7|D t[]P[?HeD2C[G?}~Ĩ71lfΰ0|5. #r]?qHs S-NpfaNk粚p5IyhKO0bAIV|R/ϡ~>&tmx~@BD!H *h&X,*sc]?9uO<3W`Z+TOTC"HlV_F;(9Dc1>chDca* &昖ͺ犼(-Wфuh2VZ4[E ML}1/ :GSƠs't*mɉ?cqFp7 a v^r$I>2QELbJ.%t5;e( Vwݲť8h' C0t@'D -1@PA 0 d!WQ5g%eMES+= I2XTBDꬾD"bhsE~X_&"3c"2yn|SQ47[;{HEƠ:ɳ<(ÚEa9oE8W v;DrLA?˰?5!̵8%n-՚pA*I5EID2A'ZXTʥy_]!-tmx~@BD!H *h&X,z4qt.&95Amk. .%v\`9&$z-$<"MuDYMԥܗ\ߐAUFѩ\G 4It^Y򙇜ϞŒFTos##hTyrQWzWs3wI?GT?m؟bɋg,,wR܄CUqT+TBCqMT,"*񊪖 1;/]6*.A8n^:!QȠ -` d :хptuoxkg՛ H榆Ai СgcH洣rz91G͇Ѽơh4*z\l}E=m =Csw`yT\9cD|&BwUKɤXI͢zL%*t*5UoCcpG{oM8h' C0t@'D -1@PA 0 d!Wѫ' /5}?PZH_Mr,&h*ˊVclPdc6BqHj -LjHѰX^H^쇪F/&ml7v{efک;~yx{'oZ*K7&'⬣~2>&H&cB*ĵOp )QOb*u.'mַnKc?ˇKpNpx|!aN@b 2(:`B5j$MYH~̆\rČ걌`HnQaD_郼1( -9/(4d/*=H䦵Hgp .p  B @T@L YmwsQ.=Y-`,ﮕ#cͩ#5DǘNG^F"ϠNw.&Ś(|n{PP4\oeQ#EWLަmQDb2cF.Honz*$jiӏ9Ű?59Omhjc'"{jnaX1Ii1 )r\LWP(U]M2N vcDʽ_otmx~@BD!H *h&X,䠺w8fWˉr|ϴKYH|K익SHhVx=]l?2H ܭL`\EMG*1_%KM%Z2]MlA=oΤ!d~BFǐxz#i"ƕcS|O>D@!@|gaqeDžw܄CP-xKP2%jB-'DVtjRHh>x #o]p}УA8n^:!QȠ -` d 9#=;8${7C=IϓDBV;ϐ}Ķa^#&l!:=ENRo1e5#)V_ãF{M}mB^{L뻴}kg1N(Ϝp:xsLCuYuniw o 57ЗS*zOU4Eo 񼨗XB!Ry=oݼaC=M+.A8n^:!QȠ -` d 9~xN#"q=G~C0t@'D -1@PA 0 d!7/?se9^$-BJXB<)BdɺUU-FK.gLZ s]4kmGx6z|N]Id*'4Z`\뾉ᱝ9~:{_TQzh\guVtz3]u]q5uGӵ}])W85NG\7gjn¡J:x,$IJZHVdZW^NʃsXߺvGKpNpx|!aN@b 2(:`B쯲8q}8wV㺍OA|;3 ^)tK]l~4s&ab&Xt07rA^4x,ǛղH #ߤdFFsFay;q .p  B @T@L YȁU;=F x6J[,[H![Id{ucD!6" D}h[[s%#ӴaUiFM&[e;{r0<1켥YSi!zћȾDZ?u 7_sOۅtzEpoʷJנi]y}M8 kP -鼦E1 ;e1mo^ϗdJ|׷{3K ).A8n^:!QȠ -` d 9t&:8V+w}G(L N ϱS:D3գfӴ%Vܶ>zyiDq .p  B @T@L YȁU8& %DD4~gw? ЄHwtRl0E$OR&SzŘ&3ɣ= iFH~{%tƇ~ ⼁HΙjwq蜯}朧P5&H9x(((rAOjE-ZKEO YK Vu][$$.A8n^:!QȠ -` d 9.eĹwxt~%$J?>:C>vUp#ڟf7z^@3zlYv5q:ظֆ *^3_+hFoddRZ6(ЌzCqh~~F_FmdMv|.th\MDF2Ұ?59alq_2?ѯϪ QƳE?}I".JQW䲘WRi-.jA{쳩oĥ8h' C0t@'D -1@PA 0 d!W)Ug8/wul<3S}%T}=4{zhfl%%Ts -c1gC4I-٧h*Iٳlk?51>?3(*]wr^m, aSBS4W=Tbtϗ sk^W~ew_:|˂  7/B(@ dP@ t0 2?߲ΞK[CUg6{!$ Gn!M$)"1ok;p1Lj%4qטسN;} o82q [K못1|= &w\eR|;[`yȚp^exYNVRQBYLWZJkrbp toֽD(.A8n^:!QȠ -` d 9/q̵㺀8W{<]W ,!b^͝ml>4t1 NeW&W CDLoj$;6">z;>OG^ע1ѣO~ƈךŽb>ӆ Џ>w*^4[~EUݒDUrxj a l&z2_I)Mϣ*|EQYKyP"ڄ?> 7/B(@ dP@ t0 2_!xĎ5G[VGo?Hϡm"{쬮#$a#Nl$܇;Hƈ {4#W5ژ]TkZTiFz=:1{4sho ۆ|@j^yyaaWZܰ7GgL~ZZsþ+z SL$eQC*lt,'7#h35tmx~@BD!H *h&X,I촮! ĵ 눽>o1 %5vXW]եeDK쵗DYHԕ~orb,#抺-pѰ𡗡kaDXRpXu#_f<FiDX̭sڇN:ȁxnƝ2/˰?C;opVukna8.5j%uS)QWeE,& -T"{nR\:6p ? !CtB$A4,@roefuѲL]@s8TGB3]mDW;9DCe):D_T}O(o4|-BCz #w0OC90u" J:L}|fyWҥ`z6njEiws S/EE=nQ+bkn¡?3~7nj>s*c|JDXz: -5u˃mz.A8n^:!QȠ -` d 9!r^>/FPzH.&JFza -N&r+c4F"+]KW_>HiMk;ۡ{JwdK_F2ȈtHL$c/L y|*uo s %uxuS nw2ͱ3Ny) c8˵/]Qs}$Ӎ2xP,Q2&TED51*E-^*+ ;q~M\:6p ? !CtB$A4,@roY{Xsk=m4xS}B]IHeW;KR6_TJB}E/?yߛA3\:6p ? !CtB$A4,@r`{@NesѼY_o2A+dsWhQ96Z$Y*ji*jfMyV_:z`"/m"WuR݃' \R9&Ro8lb֚Hbu=7΁3v ^ϰw{a!ۯ1!}P*?Ys}"^Ij~"yMwD:*D:98||׷kA8wmqZci$\zG(Rk^셯&ڤS7qnCQaÍNbnL| q XJ7287Pۘ?u> 5E8(}y~O -AA"!! AL -.ܸY^W4нZ Nġ{5VH?馌לckByr';42'kbbS3ڃ:>F]MȽC["@7&7bż_@G -;myO145oۦ1rxKF䧮9ByFztŞY.A_k{.`omcK#XBBD!qH@;t@'$!iȂy, ޿_9"OYLIET\OkfYy+1FV->F;V̓+eck7BJ^yU7*9*,>vd"dO4V"u|nq`wz6l0!ջpem;(O%ɓҶZC?qvyLQ'ѶFLYKR٨nٽ&hh0D -1Cڡ:! )HC4ȃ` \)ihrUiRZ*(i%8fJTXq%>W:/*I%uDIP)deQ8ӊucem~[|dsMs5&C jl=+'*ki/Cz~V mi4MZ:}͹MIf>ZR@wb˧ )Щl=o׼_@{5{Qɕ̌u -Yr[ҺHMZ|Xɟdg` 4C -AA"!! AL -.ܸaE%d2mu0脜@DZ||0Q@Oӥ ᚎeϿ.i>tR>!,ȑ5r=t2cVm]-Gפ>=rAby῕tF8_Ӟy%__J[S U(>]nϬ䋥b-=#޽LwVQ6 ^Rh;r5e -R e -R e -R e -R e -R e -R e -R e -R e -R e -R e -R e -R e -R e -R e -R e -R e -R(i7(Yhv[[u ڡzZ'jW}ʀ!'i7i3k O"GɈTW"9!@";d{៻WR糮VR}z޺SL1sTrg)vW;xxtFTPr}$z(=ˌ(fbfa[Kj-qڿaNPdco7 չ?q<c̉183N&drQk[UE"")EZkZkE*EJ)H_jZk-UUw;O_bMf/OȺn5O>yʗ'M"U~ijm4QEtRUX4&/O>_>ɗ^R;&_|l8YUTUɪ2S./8^dn$`drKZN Ȕ+ܙ>ѻKzm;k֬bO^z>C0D -1C4 -` E(WQ=*ӗ>/Z@!-Q3&DORMiG(%V U4]4,ӲTSqzz۳kpɿ͍<0]J^,q܌ӻOYT5SK$^6^5Ll -Y- ۖ-o|kM$.=Ax~!AA"!IHAAT@L"p(:iy5y#6H#5$ROҪ ,+itD&i"Ho(#RP"JdH|DwtD6Z9Uho~ Z#y%$wЏ}Fr{d(Hi$C0D -1C4 -` E(W1=JoQ%@)tTRTq4S-)*)ach$Y%{i$sJx"}F$";uDvv|vOhMh"]g"o>r(o2#kC| ]Ɂk۴HB".o5H]+0\s yKt?E"',F"RoC"]r}"ΉtȨTV0~UsRAgJeV|uC@Kp^x@BD!qH@RDA4,%p`xU( >ݜoBČ&cB\ٵ" iђܒFۚ2i"`$t:%#'Je44WtQ$yR?qi"1\P$yY4sh$1"yhEAv$@$}NNơ%O#~Ex lAzfN5gs?HrHF_MTBH*|C+>P$ˊTd i\%Kz7` E? B ␀$  *h&X`CJUf4drwM#EBIxDfFg Dsfjbz0S 2U5i s5mpp1~pHe$ 牴\.© ZYS{/nW.mrǷdt͋h6BNnb<:|s4ϚxM -I"I5m\;-3eYd?EdP.|s-7a?d;fS+JJU4KFղ5\-lC\vkt;s? B ␀$  *h&X`CJU l 7x3"ɨNsH є1 ,YZY\; -#i"ZF""gy(_mGZ.Ϡ|Q.H(ch$Ncʟt=IcʑɜXk-DP`g~jqio,r/+qGxWMRU;)jYd*R&U - LP2zF}ϟG9~(}!a@b$!i@PA 0Pܯ,' -}6J/#HqB[I^HbH|ABד$[[џ"Vbn&{\{i\(N #Cqm1Z"?:ɬvq}p<ʨ :+cXi\cZ6q}FN4|(LA\0OQAz!EQA&:ǵ^S3ղjHZ!iL^*䲪T糵zhi\W.]c=;i\q >B(  HB - 2(:`6_AӍCĻ݄_KINZC+Id9'$ץ$u/I/'z"'JwȈ؎; 4?G0jqU0z벣 FA\#;N_ ,{>C0D -1C4 -` E(]'\}_c% - B,>H *iURj̄%IFr2#Kh$aD2@#y]Ȉ)ɕeCvӹG~Rk$Fet~1e4V;E?>]9fmED;"ٳ%= ~ -z\;Z;ok[6܄W/1bW4I竒+dBZFȕ\w3o^ĥ8/< !C8$ )H"Ƞ -` P8~c7:6"gGF iK(Eyr:Fa1ݫ7 a#i²o{")/3vQ%:y^^ޮHiM9419{9-iO{4c.fNCϤ" 2'~jirZ'?Mb?w aAH~]$_[j%4+<3"y -cC3."yH#sE.e.Ƃ H^}]ep6O-$͙FE(;܄}B"Y.|J5MWѼf4зf5MյrL#}kw}c$.=Ax~!AA"!IHAAT@L"ErLsc8Drl@$G"Q'6jY']D$UVlC7]W H0"Y}냌Hn1odD]|!}1wk$gH3wJIhQ% ]3f~ELY(r'{eė}40":soLSTɟ܄5 tk <\AJCz^s%T+RMQ 5WE Gr%ogoxm݆Kp^x@BD!qH@RDA4,%p*3<ɉމ*?-018=T W"S،DrBjjzgy2U4s5cLzh&X<9Ƀ<;h02yjWLT;*:#2A*9ܵv=q̝t¢];a(y铈 mnd"Wx\&*3ZkzeJZhHF^jEUfѸ}O=N|!QAAdP@ t0 l(B صvr;xm.u.%fB+Hh ?D"HtUsd宥$}#VqWe=Q" -?IۉXH EZ|^ -uX|B?vUF\q0&cOq]Ha.:̓ӓ{PA݊q".E\/mZé6wj"7ig^v&tnjsrȢ -5TVslٌZΔ uvrᵓ}!a@b$!i@PA 0Pܯxq]Ge_O$ -D6V[Cܸ>CRkIz"*"o&΄lj<1"N;z;Nˆ'i\4 -#tgz;65wBcnqxw`Ui= -ͤgSHvؘp HO/JkuyXs'|k3զ)$P\-7a?.d*%SIݝeR!B=sYѴ~տ|ť8/< !C8$ )H"Ƞ -` P8 ϔi)<nDfF/ T$t:Zީ"g(:Cw9ͺpXDc XnvvǞE,yZ?֞w2e˾H4iO\v5;4X%?X+/w'7޲ȝ6)qƴ܄}|XuMW.U.iU,Z%#5 LNKo{Ϳĥ8/< !C8$ )H"Ƞ -` P8\! ,\_/ $8-4%4^F:_"_ѱun8xI:oxOFB(  HB - 2(:`6쪚w.N& $0#83t tF4sI%Dĝ/KD]@[>Ÿ5mHW2Bz= iӐ~LFHEH?7ʅvM5ShHOft!NC0 )~ŮײS&FeNzh t %MSS6WxlE^^btM!5:MR Eu[B= !KL&6lex 5tM# ).=Ax~!AA"!IHAAT@L"Lo>P:7?SӃ3BHxfdbtRlbs6w|s!QƣND8'O049F4gh.Ѵ|hnrWAons[e1dGۃ\H9@3Y"G)V!y?J!C2d͝Iߍ9"tNfYSFR]3j.nThHu6K}[|!QAAdP@ t0 l(B d*^ۧeA9bzl=5mAӲZݰM룛4o)GyMd-4Gz1N uA'@i$elEi$XdH8T$?U;Cо~n~"$O#G[&gP:oj3Q/H\ΐ4CJe*Fr9Dދ"y ya%]G8/< !C8$ )H"Ƞ -` P8~;B+n⿏7=בZ~D$׻SAIj5I!†#ce+Qm=їc519I(:q©ϋvQ@/3(ۥ3aA i9r깈O?Mt1#]K{zjdhF(ԋEKŊa~~9;3\Mhir}:iZ5 lڪ*B]cgj2܊6l|!QAAdP@ t0 l(B 5a;M|wyq0OSB$S:?Y/ʕ,bQIRZ) 㲚^Qk{n9? B ␀$  *h&X`CJ༺= Jˍz_ʯjH '"BTuJuAuYU4;zMJɃi$?M#9F .%{Fr$:1"Qk#ۅnk/8Z#y.# EFISKZUw̖B>6Sg7Wpnk_>? B ␀$  *h&X`CJUFyJ m\2V́瘴- rQ)ghcFѴGP2{\Чh$c4CW=l{{$Ȩ:r|o#*yazsWftȝxʕ2'\vȳ'Q%ocaJE׿?-7a?,t 2j\\ХQC/[ȡ-sR^֍[1g/Yһ7g|!QAAdP@ t0 l(B utdrM_BZHI-$6E4 DX@9D^@%hcg͵ymiDfDtg(sC'҈bDn|@ F:-lmMtqh]F쟿qB1:>hɜ8wPB-DWu.զ)FnS]XGE[n¾c iٲV5 m a -R^WQmԳ=(o7hĥ8/< !C8$ )H"Ƞ -` P8DIQ(6"NBk!)1m XK]5,ӱ|MЈD>N9m]$Uq~rztFV/Y5h<.׹Sv>X:͘ f0Ɗff.Z>`+ ̓b/D}h/~>(v] ; N=+Y܄}_>[i ZFeQ5=Wz=,퍿n=\z>C0D -1C4 -` E(;߉k6;Z΁¥cz\Q):rITKu0M/4i$?ɈLF$I#9FF$]t Gt8[n~dyD5j%bd0t7ՖBC*T2r(r~x" zF? B ␀$  *h&X`CJvݧ#B@TXQ#6rJMA;9+!eg17iHH>xoOS_ny'DKn[ƞ ?E4FC{FE:\>o"}ψHt[L$~t?E<*$x;ɟrr/TlMHYY˕z='ՍL5bY- /r7D|!QAAdP@ t0 l(B p|DZ>_❀4CzB4cZŔVC,ʦRRM-DڦD4W3ɓD~&sD~bOܽĻ]|UqYi^FzYBgbf[cN+ŖHxNNї> [s Q*3cq∣lx}kyGM%wz.W(R#:Ƙ{8m>p_[Pχ^1xp=? B ␀$  *h&X`CJU5 0~Vs\$ѩ9l"\0E݃}1ݜK>^є>HѤY^єK˦)v8P0YckJє]>BSz(#-H1]XO ]^ r'=KNYRWu.<)FVN{/r8=V.l arsUuɣ5tj\ݿihSw,Zܦl? B ␀$  *h&X`CJUYͫ$C++h"&4 Jӂ(*rZTA)#eʖ{$40"y-%F$C0D -1C4 -` E(;~wi/Ɔ?inHƉ䳕s3i1tpzYt/ޡD.:?猼~hdFN3]~"*~;>wM)E mH^|"y䴛LS>u)"9"wGbt9꒖"yOCtCpR52hT) -RԍBglB$[-;q >B(  HB - 2(:`6_j>TbyMx=`Bv҈F$M4eU1UKtPIݪȅHE:.=tS t -j_Er=Poq.h4aDF׌*9~vw/b?T2 .C\ko-y]Fzϙ 'n9㳕"Wx,qGfDM/ܷsj^UjRfץBJz%_j._ o{y/ttKp^x@BD!qH@RDA4,%p`)͇1xo Ø=kAu3#Sg1Isl9\4C>3uˏ}u{sgi+ӈeD] -^gw86Xv0yЋ;R],mI }c̝桄.و޿X玿$4 ʼnOvwS<^1)JRVjM*FF-紺 h7iĥ8/< !C8$ )H"Ƞ -` P8vHk>ez5WT! EUcNsNҶ`hGY[iH&rF">v9#4')ٱ#H}{=vsH(i鶟3>Tr6B(  HB - 2(:`6\TIkDvvJ6!?5Hɿ*9Wi] -?/MbD2*yȉȨg9"_ԹB(  HB - 2(:`6_{cgxgTL,Cb;#5#=SEA"OWf>/$BbNh;NtDy"#GuιO᪫ g;-쑌QKpM7@S.bWq;e"Z{J2'nؒ"3uha#/mT},io+qGO܄}LA鼫@J jA*‰em4r\\޽w|!QAAdP@ t0 l(B t>Nۈ滇6|^$ $yffe׫¶DBĭD~()m%zbḼ{{hhϴ)hZ/#^FZ"y?)oWu*Mkv](]8zTRŀϙL c֗})lKX/7O-N+}>iݰ&Zq7Hj!ie '9I3Z#SoԌOhIk/޻Kp^x@BD!qH@RDA4,%p`)2:klƛ%hQ-VlkQTM+a?w=GOF"sFiG>IKX t޻v`ÿrsYc$.g$.Axs4$9gcH=nֹhnt?>8"g}(D&Yij%R/ -\MR -zS:u}ƛ;fE7M$.=Ax~!AA"!IHAAT@L"}ENP b-Ŝ";ue*kzmmٻ'o D^Dy,3j[]l]8޼ͥ̒OnmiO?F$ ?G09C|BɄ.dxϽECS@sE6йQ${Du͝7 ErxJVճR%[0$MdcЙWU#F9?|͈yF? B ␀$  *h&X`CJ΅~/`1d"ZttḫtQDEvY5G7 {w:#H4ch$fDrq_[vrۯ~Lk"?A:)aѶqI?~Dyz>AףH^Rxxr$r|]={{Ngdd>} 2_agYMmk~5tKz쾽BTjJCV3ZfU жzn:ĥ8/< !C8$ )H"Ƞ -` P8~;^@\ӫ"waS"6Nqr%#wҳ~(Gtt-F$o颀mF+0:2wc/ wt::t?tis\wI;@Sr}$fjCq%*J!) U+ -]E2|%.=Ax~!AA"!IHAAT@L"8͍wDrt-[씑1(y\3E}1`hy8cS4ю*2AH>[4J#9|y>My&]T3i"y}uQ$!ψ܉d.}H ȉt.yҮLS$򉯢HX^X"g>r2^rQBAՆTVPhM+^>NoÏ6H$.=Ax~!AA"!IHAAT@L"ΞDC/ccP$Q13n"cRcu6F0ƙ֘饉vQ}?e$rM\ڶ]Il `o=F"y7[cDr-%Z$ʈ]DϷ)auûk2 |b?!{jѷNx -ixE-7aߏ2<}gzEQkD2H\HVٺP2JFr>F|{? B ␀$  *h&X`CJ7Ly O L N %i$:n#!!4y23æ֤vk_eNk[i<봇M0⹾xtP޷q>hxrtuF<EpAt ۜ5xV.s'pjs?1zo^x8ֿ ݯ r}uTɺy3d*|"UF\-j2p27hĥ8/< !C8$ )H"Ƞ -` P8~'aUt La3"Dc1>5LG(ʢPEMmZ#/zIB#PĦ(0E$wt;q]7:DN`̗3`$,~?4Q.FTF"c(Gt(gkqսtw!ܯs~vwLSa=D>zOMyZ6ӨԪj@l+k4zCꕂf[?:-Kp^x@BD!qH@RDA4,%p9ݵ n$MĿI`) >DBI!YM+Ilݮiٍ͗Q"}D\CrQfG\ihZa+hv:$EZ"{9]wz֟q1G2#h{˘yqm|. m䟣~Vʜ8fP}`yk&wҡ,Nqft~ -7a?{[󙌤fJF*j,U y55On[o,A'.=Ax~!AA"!IHAAT@L"<͍A;tnx\ٱ/4O$*an 4[e5͏T<1q\B9#/vkSg 2Sh"?dtu-=C.].)=P䄷?@GIz G"2AR.):osv;z\Qk >λt5W(d ̚9TQ1zjs9:~.>\z>C0D -1C4 -` E(;i6J7y5"VCn"͔y׳Ն&ECخgfG{: &M0yV5r>z"і5&:1j!4GaHDNc@;e)"hCzy"yS:w?\oZIBׇyB$m ~_^J}| j5nzW ZPm@50|!QAAdP@ t0 l(B yJ1tVH -F5O;SҶ%hYWkN2jKt_So+eO"_$ǘig"y&䭌"iH~=#'ڶ~@ge(.^+ %[U=M6ZǦ֦[KKSJNa2&l/rDk6x#$qэ/ApYq%H6HDy1"*YU 1ӊ5{h&;s;A$ˋP$gnOYWhHHfXډ"9e=H|CJu -W5H+sN.eA=au<3-Z^ ~@BD!q$!i -01mVB$SՋxV 12tTj$LW'XjߑFv,^,HMI#g@ _؉$cYP%Gk >G1] n}1XE$V;W7J9.BOJŢ̉\+9+gI.Z~D}Bn`<!a@bH@R Ƞ -` -Ij`n@ؼժG4)eV\ɐ5Dfol"ɶw&PM$W6/q&N-wC\@i8& -u0-"f'6̺I#DOIl˹$9DRC';q^ uXMkMZW:ޠJӺ6ikgWTo+\ؘiZl]Ҷws-F\p,i?!äԾdH%f*ϋk5W:ctj0W>}!\J΍ٞ_U -\9_(sbPl6ϕʕT.JBލi]szkiO70 -XC0D -1C$ )HCx@ dP@ t0JZo$ B#$+|DNWloJJ!$s]I""(Gr'HMZ/{BvWi4+@W5Q[;]S|pyd_ogۤ9zItAV&yZֿٱZme:ZBZi7^SHmw̵ ¯v )viJAڂw\9Ca-ɽ~w7X]t/? !C8t@4dD@T@L3XݯU/ӴZwEƺJrajVxSEMeIT߉젉 :!D.{B,Hr@ ͌;~7Y4n:9qONɗm"F/C0D8 -`ArX\WO̩w=#O9 a3gFs1#>Z0%M]U5M7 TlW+eGtd8g=|g.pOB 2(:`YA쯒sB"-ʹ ~>͟XA3dHL$RR,5Gy3DII'r'$F"4Dg$rl|ж(+ZDÓ"=%HG2#yUHD2?#?!p'"J;;N,,F$Ѹ/  < 9cnd"TFRI'bFb""fjELjdEJHމ"쇽+$.A8n^!}A dP@ t0 <_Es4\[ի\ J( -"DX< -ʒbMㆾo_EUG#i"!#? ~)佭_B 2(:`YA쯲8r qF\/#;w%$H`# Ix ABW2[MN"A눱7E㺖WOw=GiOq=Rq=m\ˈe<^Lz#?(F=VkKZ`UPM_n|`o\EF\x!k6j؟y*!MO6݄_A TIkZ5!z"?0ԋդXIgzKjukVP\:p ? !C -1Ƞ -` d!y"yg%u(* -5XVA+*c3 l$0wḧt#y+aH$~$݃67;盛si$`DrQH:,.G_=iS˜t'3"y*Ũ܈1腳idd4"y]cu#8 Szw-Έr'MsqM8TId*QQv$(tA,ȉ"JPM YoC0D8 -`ArLv@$'yyg`h> _XN"O<"e\ϦsMTFJy*#t鼯t~9Y龤U<^sM,mo2&YXg0^z;f qnS9QN,#k&h\sf؟q,g]n!^tx8ӃUN%)Q.V)TM fӜs EN\:p ? !C -1Ƞ -` d!yL=ZHٞ{o93xAh. Ϸљi.g(s:C[Hls>ݬ4F8]4nF8gph8݌p YLrC0D8 -`Ar\pNuNpͱ9V8|ؓIDOi\&󈱀?h2ѼfH=NF4 mݬ([Eq槌hNyc;Z7hj?  g-qM-~B4%;~=SцLNZr~'z;%M7Gs AtUVJQիXf4THBFMjƴmʧƭi=¥8' C0D8 -`Ar@iĹ6x&mķ&Iyz?Sz/n#wII"+4{'H+:z"Nʝkq'o؁B iM75OkA/z*+Պ Zu^W JXMF!gowBKpNpx|!a@D!q@PA 0, }K6]h=mT,lC HhQ,sI|0Hv;[-jir?Ch6/fdmol<;CU6ߘg+.F% 4o5cKtZ5ٽMܳ/~u>Fe؟a;ǡ~4kcM7g3f&YъUr9) -L"B1hDczh y/~f7/B>B 2(:`YA@/@4g;]&wg'~ \G0 Ub@'+s:#,V39@Iټf8F6;K]~v7js6gl>3tal~H9f)F;y&+}{{ddcu+͋@6;qbz,7+_tP"6bXTb\P7e2D!SLWIMԻj -.A8n^!}A dP@ t0 <ُ !SwIn<~=0"mzTqS0$Y6Iz>Md#/v4"#j&5%rj5%06:)Sh$/鎾m9|F2(C;(+-"9xC+;VO`{5o(_а?E2*EtUe!dFS;HVSG -:v'*H>H_q'"9q?E2N~xcG$<&H"*c*U(S UыdRK+U7~`NEZ\:p ? !C -1Ƞ -` d!yJ֑G$NՕu-,BٰHZQ5%Vu#g>ȣh"|&r DVH=yN'UrHaxTɟsw̟2"*ɧhD#F$}b&qǴf14 O?H^gbvy'#d9s\j a5e'**W{kEUQbZP+zj"SLHFSخq]M8' C0D8 -`ArNM*qm#x^ o'NϒՔ7Y}^%DH]&D׭M-nzVSKdgh\E (v-SZ^W]@݌Քtiϥtcm R422Nzz=c,u# -7OMN7`z,Cqj ?z -LZb"jF/˙ZLjrQ.3(/ܻS*t=x~@BAb$A4,B`ӡ!Sws[M8L5]fJ:^.OTŢ\Έ f -FKM7 GL\:p ? !C -1Ƞ -` d!y\Xl%Ĺ%ysZƹBx^GKIl̹Hˉ<)s jۆ|@ FӤHj&tBFRAR6!=UR/dv>Hꆶ;~A; ۮvA{I?;Ŀ߽P[~s":aQ;/l؟]|qi遦p:ȱ%¿ QOh)OE dJ+7~U/߾xO7/B>B 2(:`YA$49՝Krs;'̀(SZ㣅d9]2,3jE ' -F"9ٴHwlwOnvZy~%FHFHOI$!'}RN<>2=KӸ/|n؟\-6#eϿspAv٦ZT1YOՌ)Uu\VDJ/$V|οs7'.A8n^!}A dP@ t0 <_e9qf[s5q!MvFZ[G+I` Q;#!~A$kt7e] ۈk"F.|I㺁Ƶc;AVi}e Ws=0bV#iӴHYK| N?MVʷ *dq+MN{'㦛pӪTj&Je3VW)T1eI+r!)UwW^fƍ'.A8n^!}A dP@ t0 <왽Uj[ Rےf}#14Y;"=5% =!C> ֡DNb$}3ߵ훏ԫ/#s#5.m}n }[=-˝|S")t| ϐ*e5Sղ6R3%tHVB* -莛WuxܿM#KpNpx|!a@D!q@PA 0, WQD2ꌺ8^'#)(aGH\ )&ǔhA=hx$F$H447>2"YoH~g"Z%-HAT~8I?*9*9m:#/#Q6}9\%HN؀soJ~!Y9DRt|LD*rBL(ꕪXT*i'2tQ%7}1t=x~@BAb$A4,BWw*unac.129a۞PuP8Osrk^Jy~{qFJϤaFJ^h㽓%WHisG,S6ևBJe0\9ߊ S3s-nX,7|ェM7 K2?[)E*V4U߽P/Q33L2-7˷;ʧh/KpNpx|!a@D!q@PA 0, {NzjbQnj ߔ7Nňo,8Q%oqNx/iwOj~uz:Q:r,MN]E$rɕD.D }k S$E3g9iH5݄)ArZJ -bPLbQ3b&Vj.j?IGt=x~@BAb$A4,B`i H4L]}3CSkʙIIIBU/Pfem>Ř]mc}km">ߦH?JmZz& -b$zZ#f$TvoCd/=;wms$_]\\m/ymeCYM7HށthrU+TQT0bB֓zTT˙| -{ -޹vKpNpx|!a@D!q@PA 0, ݇{o #P#-w{FxGj`6d@"GFGF-a(yujنdl"{y5?19$I'F"?Fڶv ;ue"vF$7}#Hv "<-p'\qĝ8c"]#G"\Y&7t7[p1M7 WMN mk%zP˲ًٛRH>uyϣ"t=x~@BAb$A4,BXԜt˼QOTއ1ո,$]A55A y߽1_d$tU$ňi$ߥ<#:I:t] [wN{;w'M[U'H~ʈ]L=:aTWݨsv&w>E$E$ODp%E;&&Hj$3^MF$EM3r$VʚV)E5n[H[M[8' C0D8 -`Ar=wEQxyMWFPar@$eIcbV7l$4 *9oо*!_Jt K -jʯ#ɝ/3B?eTi|ηw:95áJHP%?y^%mG$gܣs?ވ*9}TorN36݄2ȦW zTB$Մ.rP.V;Uݫ~qd.A8n^!}A dP@ t0 9{(FF4ƫUR=5bԽyH"KJTP%#i$C4[Ur5H /"y."9U$:%<6jvhF$]4k|F$Dre-6YN"\_γ^0grXɟ\}+zWt|L?RX,i`71ԓe$U+&Jc NTg_ݹq4.A8n^!}A dP@ t0 ݢd&2Egw5A".b?}$2~U[N${ -5:EAajrOGZ(`(M7 B 2(:`YAV)նQb@4i!)DSM-Mi"?a$j9Pr1[ӌD`(gС fw##Y<1vg;D%Ak"QC[Y,w -goC\h HfJt`XLدRJDQbIo~=|?6¥8' C0D8 -`Ar=O;j]u}_Ae6ta-;PRW$$ %5Sn5|Fɏ60c(Er>dDRaCr;ߘ\0oQ%Nv~F0A|QɡΧxd~_w*j̰?Pr6 %YE䏏n ~Nz#eD0e]~W:]B[(׎ -ԂR,~UݏһxKcv7/B>B 2(:`YAD~Ŵ4k}ѽ&"iUƌ^ۆ(i.־C+k KGrQ(GÈ*i:"g %7G<[wN?]:݆Ggw>bTIOO<(pC߻kF!P%5O _b(y 4eM8k 퇒RXNWSd9)T%!L!QTc d Ƈ{+u=x~@BAb$A4,BuI%ځO%hTk\*JWCɸaZ=/1,m\EF$q۞  vv}Aߐ ;<r6Hi|WO4o #XGw'p'ĝxI>LwW1^߰?E\V3*SnAxV!ҒUQMjB)zU-%CwXս5O}xG7/B>B 2(:`YAl9&RC:ƣzsH͐U;GG:">Bз -Do"C4w19J~h"Í%F"A"/?|%"?ON)qGJϱ4OСJ(;J~z {7ݴwoz=CH0}Ѱ?E|=v4ߟA<󽦛pV}$u5rAL' B2#*E2x׎嫺7m;gъ)=t=x~@BAb$A4,BP=y'q-'_KX$rjD~)L׏jٌcb1\ϰG߿V_T-B"WFuH- S @ס~: [xypZM&ja ,dQKZ.f2؃w7?]qSc:.A8n^!}A dP@ t0 <9b}mjmÏ|#V B#jQ3fAtYyn3FAN3&H396e{vD~Е5h"}DΡ< Ч2yv&\N7#'_fD&_ӸHy:;'l؟83)mgGW0,iTc\(ZAW4گœ|{pz .p ( H *h&Xa$mi)wwA>:"5HKP#M-+h"d$rADCk2SF?J]U$__`YhcUɏ!w36L a ݣQ$]kI; ' nDajr_.Ggaɣ_m aVlO"ԋbTE\VbR^&ҩjQi.xEr=8K7/B>B 2(:`YA<˕s{_5ŸP$sՊZh[G!yYU,5zްg#y(H^Ĉ#"y(G:(״>8;ُ<ˌ"8=^ci"/E3iB"!u-ȟg[_d؟N7ņW}~@M8A6&)JDXPQ$ɤd1(eRr%S)4}~ۇz+g\:p ? !C -1Ƞ -` d!ydr{gOԚO\OsH`jpRZcO,$YډZ3YDKD7}8rn۩GdOlr"M蝌v4T$tDŽWVJУ/sm@B_$R !pmedl~Q3nAhco-*D$EZAB+*Ɣ&&e=PSrSBC_{߼qV3.A8n^!}A dP@ t0 [3T2:m1jPJ!3lj*˨f隑MF<,b$2:mI+|2Yo|9hNE %G#RaZFY@nF|4Hn&'tgqƓYn.q {&?W6SLJ+ 1-5uzXH#d!)VJnB$Իh$qz .p ( H *h&Xa\Z;rrj?#I-G`d#"i#HHɓfϳFHi$SH̾>tVT7G"ɳUUrQ=tE-6#m'|h;fIۛ f S-9'˝D'nA~m&2ŴKfE1 \Jӑ1\|w\<!AA"QA@T@L 9ȃUd\{åQĽ/WJ0kUR -1!. 1TIMTE7VGȈC -cd{NbDGN|o43"yDzE֩]mq"kzߺ&DIENEd/)RQǠj:PrVu_=t=x~@BAb$A4,BP*q=3y3qp/"TK|g\C‹ٞr p<(z6s1HJjwV-4)8#;H݃B򅳇.Og.Ogy} '忿'6=)Fus9R:;~ţ:5o؟=s!f9S=7݄?%ۓIWd)wQU%łNa)J$7zhJqz .p ( H *h&X*Sg#\eO;7@Վ,!̔fȓ16iY PF(ÍI|Pi[:+m~|y_1jr1vܙmaO`yԧ]'p ߭?~Ȓ]'Q6O騝dm˝YM7d9rUKEUQh դ%^M;v4|^ĥ8' C0D8 -`Ar,&v ٮ%ΰIV@p! #v\JsIl% i< 6olF}Q85G3?~, Rr~㛿_|9QeoLӈ}la;} 6S3>ԸE!/~ɰ?5N~,gq -pdZVr岠cYRbRr2Pr!]6Yu~ 8{_4伦Of0̭\Ԉg %gV7 մ>,cy7mVoD:=(vD&^)U$m\sjeT+KZC#[ [32bu訚r3`WnH{/H7OMNNC$w{vM7wŖө2"گbLI5gKF$o\սq>YA*8' C0D8 -`Ar@OU]L\Yo''w}@/$6vn!чIl?F0&N>N-D>#O'뙬WzqI4W&7ZE`AW7W򩒮<K1 -,:|CZ#g#<7 w$o#>8 -/9-89پw0RvbL}5Ǵ:mLvh=|FH(:jC+q'JyD[EzJZ${LW-z1HN?}w @*IS S$rGف,7|9.Ĭp'ye\-UE *jERV*"ٌ\5QnC0D8 -`ArAko C\s2@B;fjuDX@5;^GG ٟhLwoweӯҘm?A/;ٶy{32ٴr۾i:yFN0υ翼f{kgGNO9Raf+,ΪQ9/n ~y^2 b!a@D̖B$ɪVNA[0t=x~@BAb$A4,B`#Hp.k)~5 !), }QMdC&ѷIUt6h#K莠eȈDrmg~*|=|qlAH3c-\zf}g ܉7ݏҹ0\ z>hC]Hh؟Î8;ӗzgM8Sde3.0UUeMuOBJG+F\nkǂiĥ8' C0D8 -`ArTSj+iL$_@5* S$LU$ߘbV[m{Q%/È䕴J>CaHH19"-{pG\$D(O0AHAf(`KZ,fBj^G$+5ouHkǙ8f Mhf>EҒn/>Ȫf%$H}t\3U9-+ɦ2Es!:?KpNpx|!a@D!q@PA 0, ˆY{~u⼇Ļ H` n}D7ۈnK׉XS˜ 3Fv~v$]N2#S: 8;9u#.1, 3OOo浘Tniϴ׸yް?EZ<- wfSΙ&2T)dJhaEE=%gj*Y̨*''A/DZ8giť8' C0D8 -`ArAn ĵ ijx7Ŀ6jZ77{ﭝkv+ѷabIZeFZH4' _kܦGH֫im=kiM^m;^˃[ -=< ٰ?59aɋgb?%6ՏnM&۟YJdYrETn?ZrA)VPw1mtMpz .p ( H *h&Xa[쌻4w=}Wsi$^@ݗHnb,pXgi(___GR)8\t-,{˶n۱ :;̎Ӹ z%G?A;z!&$jeG֑u¡fDt" R!RQ}{n=k#, |=3{ѻkOܽ?|Pb{mӢ"0 GR*WJ%ɦ*v9KDg1U3)Ůr ۔g8{E]A^!QA'ā$A$A4}Mtcl RK],V4X)GeXJ˪S^FvGKZ 8h7x >C0D -18$ 2(: -`zByTK#̇HՒTBB.DTEnnH3&"9!Y$SZ֜Chvnj3{cdU"+ UAC\W˶:wm/p.S RF0v[T?Zbr -ܬ0 S9U.ZjzR%Urvi[.fm{r"yN<e<!AA"tBxH@APA ȃ AG{xN /K$xFHxDHC"a%tD9JA [!4dpQvI6Zq=29*{1t;:~*%MTБz>E]nT'QAۂދ - -oOck5oj-qڿaNPdco7սKc415gd2c1]"Zkmk}DJRG)UhZQH)PJRSDJ-j@F 4=pv }޹Ng維\E&4Y.cMom 73!Z3]ɪ~WuQMBZt&JZ,@ֵ~;ZFuـ;8 .  (@ dP@ t0 d!yjSLu5}_ (A#$#BĈ1]Хܣɺ`Hh$\o$GɅT!Hψd瑼ղT^H.lɏW\lv龑GiF쥑FVLm#h2StE{>""yɻ$;Y䄻V"mG$ Nn VZCZ<TLZ2!+TR)eFnhZKp`8<^tC" -, WT\?=3G|ccZ99t1R96'&Xr:VO0oFfheS#a'0hVȖDUC3KT1Je-1r*:WRym'.哶z|i%ryDʨ<};_!f;Mg6ump|.ee./h_J,UӲ*Z/Z4\Cg.ĥ 8/B! B @T@L@rL'1z2u"~'~A ItN&p'Zl4YLRme9|Q7gк3⹜&xx.=d?Ssy~}cOcFy,2-]6yk?lNx~!8 SCs; S 1͝a 1?(uS<ȱD~&GH-mj~"-y~tcݼFr;cpz7#9F2he~?qHُH8 -!+DK#Sи&ya}9g8S{ eC#9GaDdDrS|$9ɕM'|Fpc$Ϩi,1"y=ĈdѥoHN؄Hށ*^TɿlZSKg9{Q%Ki-"t_*yQ_M8𑌷~NPjCIQWи&R)1]ŸdU/ >' IE#Kp`8<^tC" -, Wa"9/vG:;җq̈́!fDC* Jڣ Ӈ̑q4.F<&8iJAh\[NTZM|Hki$odDcZ -4[.ju$KtZ.MSEt<^z"j\\ U]+>xF6p xp? !0D -1@PA 0!YAE2PP%u4*"i2~#0 UR=DJHl*:R3uȚ?I'K^H9*}XQ%m4S-ǒޯE|1ߢ#O9FrOH>O#y6"5ɣv%Oyj)"ycGLDrD򙹈Cab2iߺ-˝{9D&H, E"*d%qk))WUUSh$g![zӗh$qi'7x >CnC$A42*M3㪸^r=7B j`bIYU$[StL᳑ Hʨo\MWhF$GHVΉ&\ܥS;MCNhzHH'6y,}%q<^$_և;ѷ'֧&'=opCf c6܄v z9OR&eYԫICb?Kb)hTBIVD~rs.zv6p xp? !0D -1@PA 0!YAHNu$5{&ީI9ZɈ4jm$LV!ʜڙ^kAl^+`8VSK#j=_dD6݂t<){Z3Fѯg"%ٞfD$>D"GZ_=5rkX|x};ӳc}LM8)֧TUVRb2U="t*:8۳iw-iM\ڀ;8 .  (@ dP@ t0 d!y=3 :z=|ޝ&X}lΌc&جܣ W3Z^1F}6 -M}<#7D9HΖCJz$4!F$ߣ\Θ)HoF6&`}5YCO{ "9V;Wɳ֧Z>}"ջZ.Xpx$Ze%^*$+jILeQVlBLj(ոU/\Q; Kp`8<^tC" -, WYEl%w+cq"LjZ_KIp =cm\N"+Ht=FeD@䭵]k'Bb.%a[@h+Ɉz}],<*s}=k4o0zMkVA6`&,Y6_ -uWQxai}Kqziza}?"?M 7JZx9+j\T5=nEIZ!uPӃtz:e=..mx~@B a@b 2(:`BE&Cf;tt|;drɺp+hu&rOjQESe1]V5HS,S>F}OKZ\ڀ;8 .  (@ dP@ t0 d!Mmyge;v8L %FD1M njZN7 ԛQ)JEN& -F"*!nH8zy< fB*ErѯrBV=05 0ga34i`$o4L7#?O"]pf-m<kc"/5rѭ|]$vvx=0|FQf?ZG(fop';o(s7k#8pS# T-5ʢ,fETihioŋ^vp\<!AA7!QȠ -` B`}rm2bdgs({kgnM^He|N꣌&(NFj9;W19DLZnlC ;.Ý(F:>dgM852zt 9-*jI)]UE) -%TL ]?t-?hĥ 8/B! B @T@L@rԦbé6е~):Y%GX^Ikkd{O5Q#DȾ䫈-Sɍk\x(?hX"PoH~p^JqZSH*U1kQ*U)*/ӮM3wHNpn|!D!H *h&d 9{t6I9̺s=>ş {B=%?DGƾ&Hf 4Ca{/]H~\wό"9H^ˆ$FCnC$A42*rCq*.UU|_ A-$i[*Ȓ"G(Vkǥ(M:RVO&rKF"j#_qguԇTc$K43f:aGй#HsmAGH~IoosuU_H?G$2O\Dwжj H*g[^J$eQKċ*W*b:.kb.%j[#]]o.$wa$ #]IHrF0܅.$wa$ #]IHrF0܅.$wa$ #]IHrF0܅.$wa$ #]IHrF0܅.$wyˁT;VHsnfQ$kj L@VMQj#Ɍ!}o3VEbF$/RZ$L#I#yr|kyTHrC*yCnC$A42aL-3xmx[ur0[{AߚY9HjVafOwtU[zNZ%I7AqײJ^HhrgF$OCJ~VKh`<PװND$+jH.C"uIwpdӮ،Hz.h a(IҥLdzE-뚞?O) E661׮I$.mx~@B a@b 2(:`B<^gS[%`2dw/[Ɇ#hOvzONɫY1bP1"yo :m\?h\ic1Y-XmV%#0/#F$wGA/"lM8Nn\ie1mˬ& -N&.'_Kv=l;V׬Gwpi'7x >CnC$A42a+CsfP%uD2"rUt,t|78鿥n$0Z%RBF$TlG Z' ! i"ߴHMCRɵ"m'I\t;_?X}kB$2|a}"W[P$Ww_p¦ Zb,Z/%)=#$7ָ⧐]Dc>XKyƘD"/&wY`\.X˵=Hd\/i\QjQM%k}?еⱏ9D H9D H9D H9D H9D H9D H9D H9D H9DYf7pdte;1k'kE=jD3'p9hjN=qOЮZơ :#u:m1ۡ/I7lNpn|!D!H *h&d 9ȃUڦ"c:quӈ{6M3ou@L'>kƧ~i$6S)TSz?1&kpbaTb H4+h{#;X5d?S=_s8=ˆ5G}U'#6'[nm=iNY(?}]npmIKGf4e_9=̓n/֯*e=-RbT@[QQGj-ђbIMNEծ7cqi'7x >CnC$A42a5] -/cdj>#KcGUQ4Q4EQꂡ<*F\B#h$b2"yH7fn,4?rD>@ }H׳\t'HdI߇Dn]NXF"L\rCx EsR&gGD*&4rX-d!%jbBQb8Ƨ9.\ڀ;8 .  (@ dP@ t0 d!y؝HkW#̸Lkn3;gdZ;CnC$A42a#pʵyt|zH*WE҈)" rLUUP$ 4|"F 9ղH<ﰖ}kŲ7#y9+ -jH~̪mLt0"9/1ۄNp̗yix)"w"ǕG>2OM.6p1_2&ZRTZHjO[ˢ^Nb1URN܆H.]׆_ĥ 8/B! B @T@L@rE-\jsp;7 W^DzZ Y#ofmɈVɍHHi,0"yg9dOwV?D7"$INflCKdj6+G|E_Jk?AB"ؠq9~Lr֧&'7E/[&wTXRR)kZ,jR%ja[ѷ.g_Kp`8<^tC" -, 赅Yd=>4jQQ$D*U6LP$}6!H[jz"c'"ɣ<"sWˑu "E$ wNB$k ~UO$Z -֫׭?*XՒXR4ETu:g\Kp`8<^tC" -, 30"9v&wd-ռ<.7仌GF.-3z$<73lq;ӭD^HVӭDKobR;'aɵH$;K)3}ׯmy; S$A\"yl6܄o*jRVDX*j:QBQd\^-NDlזV\ڀ;8 .  (@ dP@ t0 d!y<0+D #0^ȕA"ڋeHRtICٳ{r'@#WҶu"#_QO$Ų9>|g3y~rv}+>CwRH֡mr)dLE򑵣-Kp`8<^tC" -, WyؖZE#Ĺ~q#"q@FkC$[d V1"?@D}[dEmF,4E*2q%W=F\h9{9;fy4#S1J[OkcM΀) -h1 2GОHQ@a}vԒ wzsYnǯs 7DZ-$ӊLTŤXkqQQjYn˕([zwNpn|!D!H *h&d 9PyUw3ܫhisA=TYWY~8r$Mz>]~\'t&)F"/l#rd^۬ZCsM䭌DK$ʘy Ly}=DH6Db^ZHz\9K[oZjX"OyQ?u +i6܄_?%+qe]M XH$4Qr)tjkZ಻Cmx~@B a@b 2(:`B*8}4s;cWghTKݝkzq)"*K#(2ʍEռo2jh <~^b)UQb(u҉ac2 @3Kp`8<^tC" -, W#~')I63;7OBgoXt"_  O'Xsrqz% #iGK߶/^[U:'6GĺJJy][W0h#w7:ٰ>59a3\fJۆ!p|VT*j QeQ-Z[Jr酒FuE>Npn|!D!H *h&d 9ȃU+cfs3q=CĽx^&ޝķ7$vX NyDא؋DF(/uޯ,uhZ0zdGs 41i FﶖGs!W6K;i=LsMѭHϛr^a 'w?~i?q'F!g°>5S>cr٣qǚ4܄ZBr:m-H@eM Ԕ^,)zQ>Ce}sy/ʊKp`8<^tC" -, >6JDGqN#1L⾙xn&IY+ CLAwzZYOIz]#ќ91rzXUE嵷4 G*C A}Օ6&A͠^9btB_5OQV_|2ÝvӿgEP0&z/uO4J9QEPS 1(TJ)(KTI||6_2_вKp`8<^tC" -, Bln&Ĺv`F⻟F\GB뭰n%$Ķa-E zm'rb#憦Mq>-NF\qJ'|/7uUΓ7Va՟Řk>i=QVoicUi &ZV^ ε!?&zG 7049r\ m >jMR.U9.JiMVtTEAiLg#u 6vp\<!AA7!QȠ -` B`}mĶJLkw2YSkWXKsVZ7c$t3hkkMrbmuJvXইy<ƴ~u(#b46jVz"_MvH{4?0OMNŕN?upaw% B"R.ښ Z<%E9-RQW>.kԆ@KIl3i!ge%Q/!bb.$^V?m[ND*1:~uLW.И4_b=L0zMkem}ں&yUGm-/od"l@m-71O1d,7W}FM8S[V5QH⿹RÌd"-RNgfO!+.mx~@B a@b 2(:`BvQ& Y#q5zVe?9VNo܂~GVYjpR~UU:꧘TK*eZ1Q,I`eE/56p xp? !0D -1@PA 0!YAvi|$uyamS0υ^7a ̅FԎNKYB<|<"c\0?|O(F"=m$#8p\DnYU_Uݦs#gRNFH3z \l㨞G.Oa= wO/B"O}İ>3\fg9˝WoMh Qj+RbdQQ׵x1JZBϡz4ޠ6p xp? !0D -1@PA 0!YA,s쵖"u'gXo7FIJDF'Y&Hj}DEHa*]'i8yZ.c6y~yoV̷u|*F8=9o4v\wJ{^L-7|$3.@lG8!g;_#/m >kJ@M -! xHdN^C!Pc|"/$QcH,M {e6pm_ϋfoKѤc(ֽұo_WUJQ7LpsR^H$3RXfɨI4$GG$qCfda - -o_p3H  -&|G Y8C6#D"1/"Wo'ow\9rn'7x >C.C  2(iС\˧3G9F(grQ(SZc彡ThĨ;'wSq)QF{F)~;%lqJC){jnnHTb|fZWn"KukoNoSGW/ìs,D\hu}{b؂"  C F<0#UH韰 b)U>l07NrXy>eRXLPuLKT>>nvp\@<!AAtB! Āx@PA4÷'qa(wr-U}q Ca5f -'rEX$r9|zE"Oۨ/ [SH䪥2XD{T%O#]}"qI\PHE$d|\l嫴Hā_l6ivp\@<!AAtB! Āx@PA40h?"nl?{LyA7 R>skqW ǶRjnIw଼NY_լKf>՛̎ҠbZެU2[J:g>O4Mm3=8۳M8oոa=&vuhhq.\8 'd -_F>HOzUzF@j-qڿaNPdco7 %kq1li! 3d2LqstETmVͫTqADED"EDZ߰jVi[ժVZ-עZVo'g#x`r)) 6Sf>R+OgͨTg3Y3+ଃϮOY^yp/=ęgLډ3O;TQsr!Ȋ"g쉙\Fi'rgϘ&L^-gZFjPKz>W沙&ῐ=}5{z,|!QA$A4,_EHlOTY9J̈kĔxE0%MU54U׍Z=vOq=gyj5~b9f?;aO>{OdOn֟ !?EO1S -csY=Bo#R{{1{{Ⱦ q/]5fz|rgx𞭿)o]wmֿ$/qq[׻wo*]w u.501[~תzriYg?oKt -+ctvGu/Wt5f"ۿrpzzw`"|D2BjUՂV RVʚQZD5/<>aĥX/ !C8$ )HH *h&X`Cpbɹޥo2Мb&:'63H& iQV0\m10ʑַL3{P K${PzcvlЗw3-. j5 -kt#KB 謏+vG#½n@(o8:|x(#*dc@9^ Qdk*bVD5SzY׳#%G|è,|!QA$A4,_E舧5|&rD -13.!ZJd P 4ҦޮdR2OL$$Cc܍$zJ"]$v<&:ܺ֒yITJ"&|I"/i&rP3̳ΣL D2CHxak"ӑS7j| w~n>{cZ~{-7aL^jM䔼*f3u]TLUD 5sUT$hMO=H\z>A0D -1C4 2(:`6g<%A[72f& Ą^`KLY&V7:&}?ÿ2Qb_2zF163FiCWJ4{Q~4ӪSzF zKk i,lW@sW[ \o}\4K)]Ԍ餀|?(q$D\6uO:Tte0iN`;Bc\KVV94rUzTfĜRV|"GƜ]LX7IK^8@BD!qH@R@T@L"8~yyr;ϷfN .fBR&͋" Լ|~ǘkkWJ/]Σ(B(  HB -Ƞ -` PܯzTDR_Ԁ"6"ɧ˂񦭨j\C0v|27L{$<)R."S5Dr]$?pZH$OD_4#/J$ UrtR"9>URY%/A$|\c*9߆)̷X -4"y˴#wU\ѷ*M-Z9_*+J^/ 8_@/7I\z>A0D -1C4 2(:`6 x D}pGp1GH)lɑ'JN }1iHI$JG30GrdL.lSjY.Tn<}Py-%$ߡDrTg)=bJN`ǽĦO U2h\ǟ S_qt כȦO[nŽwd浊XeQ-*bP&j9_eň_B(  HB -Ƞ -` P-S׉A4#jTqR6"4DWMuCv q (% %kz܋ɃEhK'Kd`mQUJ"D~HINV?)/Pw1DgMHC[/<`HEo-HQ$oB"Hݱ 7{ -7a'RW2JRĬHg+oJ!'j$r57]ݿI"q >B(  HB -Ƞ -` Pܯ214}SH#`İE$D:"? 8);DUl}1`~! I"{I"")Q0D[3R.)(?h; :ܢHO"ymHBfwD>2s[(ܣd6;D$>j]w"+5K~Hκp?PrUk=fmR %rv|$dIɔrj]RURKLIyjI$G$]{D`?p B ␀$ < -` Ep`˄떡2g N a'D쎞2IY$ CI7ʶHH$DH(9?=:FhplMXHR$ -5oJ"/l& `HDA"IHA"#7 ߼E! S>"9n:9-љ܄<)w^D[jXQUb!")R\U+Z"#U%\z>A0D -1C4 2(:`6%ͫxD[b0ѣzLjl5SZZuArQTEt͐I$e$iDH" 2<Ǻ(h۵:<$DAI"c$@_d'yhg%{"FlЭA $ܺHcsk)9 =6_eM52۹mW9N| kUSkj\.CyjKo=>I$.=Bx~ AA"!IHAx@PA 0U 2j8+"fԈJcQL[H#5@RmbJ\L9HIwI"$$vHѶ^ԮH~ᖷ>J$'|R$8(o3S"uxf3vOW #.G<]Z=):?H#mG~vFi[Ւ5TZfrF/%9|Y{Q$7?%ĥX/ !C8$ )HH *h&X`CI5|2FV`rP M #j&MDRFIC2dU1TKaBH@)f$uB[(EE$ֶݖ~Z9$(DOI䫤Hi&KI$e)!=(KqsG"I{D2p?5YmtZ'Wd#-7a'Rv&PVk՚sP-ebPU3r]ώ܀DpK-ٖ~K,|!QA$A4,_%Il&\*BT424K,"mqCvud$HIl%D$ϐD2D^E"wmu ߮o /p>eדH~o3WP"};ME7DdH@$*7[ɟw>DOѷl=䙋m-D$W>rvHRM@UJY/ -ҨtQ^(H>5. ]q >B(  HB -Ƞ -` Pܯ,s/eߠ9-`2& /f ،"wWR&I/gE0_Z󔥌:{˖m63$PrzӃ(9MGhbU`fi8ɭ9$9 tJ`_5ӕdY_t~B(  HB -Ƞ -` P>r91]xfs2&8Ą29-+݈.fRK~:+=+} c.#\LIr2܍ЯJ%?ꢒjdJwҽWo W(]K-ߧ!^M hybdu;>vksaw2_>CN1OMVstr=cГ-7aTz-WQ*j9!g)b,Ar",bM's= (.=Bx~ AA"!IHAx@PA 0B-_?}$z0 -f "&://t#IN12FXHUy%,c!F[W2ls6)ݍ!)*%99I"vu:ܲ֘KbR1HL$])+'u1OYYI?i:识;էCС:.EL[ Sw[~ԗk ;a{AtUb\QGXV+eQ/+zU - -Ukz?xg7I!\z>A0D -1C4 2(:`6H!5PJN#euT-Ԕ3LHH.٥P"$|lIӞ"d!J -!nFP -pn$M8\q~D.֣?C"7܄O3%W)2u14Q-#ERRWJzϳ}׾CK^8@BD!qH@R@T@L"8e1H ޣ|'sz`rpbhJxjd*@|rcWd&G)jQ+6/&R$|Ҷ E2C;$2@fd#DE"q(ڍ$?!6~:dַCC$ǒGGvD.VN9!x`$+9rD2~: Ƨ&+O@$c'٬9qՖdRIbϋjEɈr='fr5[R><ͅ"?yD`?p B ␀$ < -` Ep`ˮXs-E@$a'a$i6Fɔ" cI#ⶋH~Fɝ}($&E4J$v=&zj"VoHKqdr=J<C[g~S{JԩJ^lFXDr͜~*zb͏n =D$l ;Ԟ %-_Ur9hYQ/ke%ّ,bM&b&.=Bx~ AA"!IHAx@PA 09B;;?t1S{fGW1yԜ ?WXHӕ뗧;Jdjd$I>d]$[2;.\p+Z9{k`EOxBZwѾnaUh_a@cq[q0ܨV[d~D-7aϺsU.U -y (*22T1WY5_-#Z{9`?p B ␀$ < -` Ep*F.E8+`!;\L1䳕GK6#5Q+vկNOx)ǔDņ]rEHpWgH)r30%}$H\CvѾnx)UQn㨕!N3ú> -MG~&Zrz\ԚQj"J29=SKՖо>ʄeEj%.=Bx~ AA"!IHAx@PA 0UV3z=y%s 1ELx~daԢ1a!tQ02F[CnrdϹy$u7R;SzJƞNm>w99=JP S?~8*2μ'i2..hS;BPWQ;ϼ>rԱnDc>I οa-7aOdQZ-e+XTQ-WuQ/G+5MճΟ.Zd}`?p B ␀$ < -` Ep`d8oQ4N%G褘xHIi1δeYTEtӗG/D~$;v~LI.j>rFa^p$'HDv>Krc#.Ygֻgvv%V(H(޳P;o"oa l1f&(;OhzI动 -U3ՌX.*Ji]W\Th._ӻe$,.=Bx~ AA"!IHAx@PA 0'KMO -8A=T IQ54GĴʛBQvv@=VS?y HMfcOc'l_P"HۨK[ r؛(E_d>&$#y6UDRMJ -Ӷ>)NsgcO/"o&+L[ƒ6[4󉖛+t[jj6WF;[\6'zUdR"xs K> ĥX/ !C8$ )HH *h&X`C2,6,m0mNSM O D'&7 S& \sa#HJHa%7*y %w%{v<Ν3''}4R%#_BH*yTU2}>%$>vgc+>䎭#L HUgk4O1|k4f)a^~|M _rzN)hU+\ET*ժ*W3_#4ߏH>*o/ +.=Bx~ AA"!IHAx@PA 0vIU;ܔCz؎LZ1@RLiTI5ehnζ'PK",I</HEoQ""7t7;Z%?ؐPOdA]$s>|}ĎN"9&+>  ܄Z(ozVٜJYR=drB^Sԑ}=a(qQ'ĥX/ !C8$ )HH *h&X`CpJG"޴'9!C|Ln.dBe^J- %_峤o= -%vHyl"鞭u{Hnp/%M"y$%?!|2[B"2 t^KO?9k{&[Ʌ4w{]>0OQ$_8E9ɗ~rv#ѝ,sJYT/ b^X(˚ZRre%3rU(.aDrF$aDrF$aDrF$aDrF$aDrF$aDrF$aDrF$aDrF$aDrIc5SP$5N hACI%"DS[#)4'YeAUYt0J̷E[Է^Hd(9j|kQv_|m8|bV;̷F(\Au$Q9"y"P$'oB"k#IHw./>h[ZkHU=jڬy7nBͥyZֲfD5ŒVj?I"7H~ z׋#dž,|!QA$A4,lylѶNM93`BZx 2)mU5JS60ndž|$CJ<$MIdedt$.y{(_==׶tkS#HH"I{y]ݜ\~YG$ǬGzK gC$=HG$6 S=88b'|g6[|iVg-7a'y+2V1HfbT+Rt};DWzt] }!a@b$!iA dP@ t0 l([v4&wvRFobVh,X3aVjjڶmH$?D29JDro ;F=\kEH:q5gHE/*ycc!;wR"y[Cɝ3i[byoilꅱ[ SCɡsmvB} -7a'̷vJfkj^.EQ%z]٪5M-dlEOoG}w=Bx~ AA"!IHAx@PA 0W'MN A9d'!VxR.YdV'j>p]#LS֗;ǓH:R%_"_ t\tL}:HɟPyJBfҸw3OR@th>?B$# ^>H9zI/eUOdJT)]=1$TJ'䴁*"vDKJa,6`*9BM U&LNzM̕*QTbk=+H$w_UfGNťX/ !C8$ )HH *h&X`C^<1#7N0V2JUbGŭF4SxcIQTѸ*>ۼ'xJc[ H$gҌDJ$?bQ#U*8Z%H^C䉤qR" %ceUrnh}֮9%wkl4G=a"%ٓlnE$߰[nN؊yV.|& -yURԬ͖E+y}׎X/ !C8$ )HH *h&X`Crt٫TjIJTٍ7P%SHUR2xSb$=$P:>I$G䵔H*]Dr7B$]$8d}=Dr?J$_&"H*yQJ$E$WΥ>HTI%®?kSDrW E<%g۽jִ͈qMU2 J%˕|YrvVt]ȢWl=WWՑWJ1o#3 }!a@b$!iA dP@ t0 l(W;SMXv"6(\lQ1Q@(y )h\Iʟd ۣJ~DcINIgpZ`b-2L;!-$BFn,Ki\ǒH^ڥɱdmJ$"S߻3m)!b5~YDJ$Dž;y:W*y%K)6ӳuߧ"Ojadp!"yw03܃s#+y חI|[Xm|]p_ H~=w O MLklWb;xZ@ܳBDϪkjYrlc}="|d.=Bx~ AA"!IHAx@PA 0|JNl<<;ŧ'qj,;jƬVɍ;G5UyTΎ9ݦ)6?=B"9өOS#/Bϛ$ˤ.hS<=(gA?[p>Z$c0! E?^ylb#bW܄TUύeiˀJXҳUPVjr]-%w}FX/ !C8$ )HH *h&X`CزNFN%Y3zYggƬI+Z&b7I$KIWI"HK/I626ŋ7z:; vpH$K${QdSI?/emy$_{NX~Ju_sg"Oܣ.3Owad#9r%WJVQ@uwtYA$j-*Yy>dkzz#5,|!QA$A4,U{]}_vS D 1F$##JQχ|#y#" ϮB(  HB -Ƞ -` Pܯ23_ĸ<%47<;2+:;6/>^sŌ4C*4m>XȘCLcyZ)v^JgٟR+.9s=1МPX*Mz5je~p?5YYHXIu/_זQve/R.[/*n)Yds#\`bo9$~^8@BD!qH@R@T@L"8~Go$Q+ytHtT qVN˼h_%Ž7r:KDaҾK"%ݼ^T(萞F9țIZʛtR~nb@!;b@Y9X η56yƣ(ip?Ez{'܄Hu-eUJ5[ʣzVJb)_%˖#FG^$K^8@BD!qH@R@T@L"8j (o'cH"|XX,5iO -$)%K`m#XO^ <6#H"$"?z*O3fsk:DhJ$ݧkID:snE( c@XeΑ,劒hW5QˈQeVVZFgj#R^HQ#p >B(  HB -Ƞ -` PlsIyӾ88'"h*ӈd"%FRdr`3)?L*yZD۸*y[x+6{Bqk ;q|H^ z.z)Z)4%$T*YJZD:$.=Bx~ AA"!IHAx@PA 0C@! Ob&WŭINJ.XƏh\'ڶLHLPOC@$H~uIM!6RC)_';Ry9sY#ED^Prh>}!dEJm$";ɯ%_rIp?EbͯwĐȏZnŽOV+ƎRݑl.fz^.uo[ӻ膡!t3.=Bx~ AA"!IHAx@PA 0^,(>ӯs;)QjLm&iԴTE54MWmH"HT(]OBVe!$s[u(;ғwiG)yJ$ HA/H#rn$}]Meu[zGb{6gw:D[7H.1O3H3f5Sl돷܄2ڱ<\M-ײbY+_3ɋ\(rP-쵻cMʋ'zEK^8@BD!qH@R@T@L"8~Y;׷q_ ,cKЕLx9Y׳/w,cRKU .gU`O5{W46۷3O>v6ƗG> -Jm ;aAK)"V -,lI)Ǣ|T/ RyS4mGq >B(  HB -Ƞ -` PS>O BH"J7RVZ)«|u(1D$W$wDDI䯺HS톗4J"U2 {%ro.$.sAf%^u -'|N.DHWU$rf~jb'\f[nNjźȒ*jYxJ* -B"詿D,|!QA$A4,lyRxH=c4S: ɚbfam;9D@)lIdȥ]4+:騞vgZ#y,%;@HNt:俻dg֟>?K)w~c} 6hf_E+Wڬjrv.lVz.V R&ՂTKl=SW*-K?[ӻ O,|!QA$A4,lݥݥ>x1Y!o qw1L|1/oo1#W36G_Ks.S2@LMt'ϧI;=D9JTCz~DB(  HB -Ƞ -` Pܯ,w+t,4nV`08ZƄGB&>.,b94ep}c 1<ߔld3D -RG#LP9l9㜑ۅK %wpwH3b岹m;YkQG5)oAxݣ Y[Z 6{)QR&I#J5E.*e1t{TQjQ2sokz>ٿ7Ȥ.=Bx~ AA"!IHAx@PA 0κӳ0J9jnjƤr&o rc{{xito(F I${(+! y)\E$t~1)Jvў9DD^H: \JY% }W?6ONr}6k߰&`jΛ3yf44*J%V|&f*yVjy;ɒ;Iw_wA`,|!QA$A4,_eycx_g|O2!&| c/0{&&8t>¤b0dWmF}ў`'ccMg "J\d::'qt]ѱL ݪ[oևu:6Q'fGc9+̼?^wA"=n<:Yb>8F&2:LMKd \ȩ]ΔZsOޱ 'ܰ. >B(  HB -Ƞ -` Pܯ2xi]x3U wX2Lx0Nnr%%IgJH$^w=%_"o3{BPROm&@H.BzdTj?DH_Xv(rv|H gqkRJSQ(Xd -beke-E'm%RRq >B(  HB -Ƞ -` Pg1_!x_a|o2M x nd0:tk YeYz.vbڋo[]2qnΡ2S_Rʜed Pj:j'2G-c': aBț<ΒOɷ@ -Z*"*H#uYKdȜ5ZK5#YGXZ:jVнcrΈ+s=ru rr6uڡ* WBǾg#!בw5c)D6uhʹ;C\bіDwz#]jg/rGX]=X^E9~|劗 Z Vh DAT@LHB -ҐW%'vS ,9'I,71rɳ@J-:L#EI!e4WȘ$U'K\7\o x'5uza -w:Vxp/JUN麞r^%uX9̿~6\wtc;r͕0]E1wkpRHO _@Suk|q+b1+v'Əܩz֑#,Wt\n|B!a -2(:`BR ؿJcvEj|KC3j=]Vb)X?T%!Gx-9ދph_;8eh&h'p? !QA44d|@rLص)GCZ˗r i?K HrS$Pt)p'@T'rj]j]ͩu .QGkzj?.rfkENZWK?pj9V3Xz{=:f&wZcX=}ºmݯu*'XD[ZDVX+=xiD'.ϬVt\n|B!a -2(:`BR ؿ饋ig9Gb\Yj-fvLi -ϒɓ\$@d{XOpj}ŽϠM=!pwoSUoA:;9`W7gcw f뽜Mx_f_ cv؄<[ڷ >ߊMak -]c~0Mx؄/S!ص j-qڿaNPdco7?K1c !c 3d2L14RʃT}|O#Hq>hZj-U("RjRJ)*Uʣ[TVT-Okލr{zv~ܹ}w{bˁyh0:kZWxm.XRpUs4\*ueRt^!vM-ӪqYū;KBgYU&WUM7q6EM*t,EV9$œq94.:Qi=.R"%(.VTI)%r_OZѾfеOjζKpvpxp<!AA" -, WQlYCt(,qncz `6#jTe9%ftӐM-Ӗm˵ڬ䶃&^C?3O>)Ϗj?==??OAS;SH{9~eMhoޥimz{fe۲m9j ɡwO١ߕ|eء5]{\Wvw{C˯?wꦿ]?sߵl3@ ;Hܙ3*ť*75.|:4OMNH Ys[=o܆#HMU&RӕdXe$PT"Ӛ&˩/USNy 9w7tȹmANn|!a@b 2(:`BDb((ˈh ˈ|:4 M붶c^֟!FKkiQZ߹3?.7Ҵ]дO뱌~ןV?XkW%no!FZGwe:w⛒a}jrҺ.;;g8O]wxZrb)ɪ"r\Tӕ'TЋr"Qi]zE9Oiť 8;8 <  B @T@L@rLMEV.2 Dt@WxLΌu -USLTm>]fә<ͤIB312Li&Oadra \:,LVI2SIf[ -:VPi4i&?f2y`#C݇ -,dr2}ƅt4:*k܈'C9nsn‘r -Zʲ.+b)EU.Ī x d#+WGhť 8;8 <  B @T@L@rZ1'V]Er⼏ˉkq$eĻZXI -D֐b[MDZ@5 -}-1!T!43Oi}5Jz#ZHiQZ_Zg֧u*M뻓!:}wOtZ i]~#2:U/ڋ沨HsиH63PA 7lTgt݄#_Ah ɢRj$TJL'IJASt~u/Gz/t\x~@BD!H *h&d 9ȃU#eeEre ދ|"E/ 22u6V̘`Nhȋ&Rd$Tȅ~^H -$2~X"+(ϗI;F"ϢH1+of$T$21 UǴڽHOh\d c^bw 3݄#_?i"DA+$*/4Qժk.RxP+j&r):U`8^!QȠ -` B`}mU?%q8%~zw}^OY]yD DxHQvBx{?S? M띌Ni1ҚihZyFZj! 6ڝ(M :@7дcՓU%%51'dQ-X-hEUE=(V⩪O*h.6'7x >C0D -1@PA 0!YA4d1gW\[(ވ/W`"JTBTaEV-KF|:m49DoO<Й!Z:cD^s̿ȟ 63dkNڹEB6HΤ-#4*K9H>=ɨ۳7K#y]"NBN~ S;[ш`<7(6݄#_;(\V%PFTU,eEDPzIOTrFr!zΧډKpvpxp<!AA" -, W!.dg:3Yު{H`Ȍ fv,XNm>Ә)i4G3eGDF4b c{[Vڿ(]go:bLhZ/.F: ~Ynل5#/kX|xMYnğw ^w|ש5_Or*QAZX("PJP)&U+?O[\ڀ /B(@ dP@ t0 d!yfSU.8 .ÝސOC)ӌHp4IJ #|:N[]ȿ2yM;6K-$ri9?+i"Z?f$M8FN jv"NŎاXH5.Mts&'{!Ír縡u7'R7Od'zYxbZUbP@ܷbE#v} t\x~@BD!H *h&d 9ȃUf:sc\chCڙZ3#sβiyC.wcMuxx4x;#!m5-D[lpo _C/hVhɈNFs'#C۟h&Fsxog)Q,˪ĉr"Ds Ńhra}B4_| 層 GX+jBO%dMVe|]B\kd(,E\w袇J%.mANn|!a@b 2(:`Bh 5o|ӆjֶsr}$gHf<4yH7GQ̈7z ܙXH -;!ոH!jX"+>pJ37p#ԛFR(Rq"唘b9UNKq]I"9Er+C?xuHt\x~@BD!H *h&d 9DRAG);%>1D2拢HJA#J@$[ScFT?eDr(-0"y;-_#0#"yŧgd'4*}|o}"'D~HOi|ȳ>#*#Odvj$riX݆D#XX1CN٫H .z~KbnKvK݄#H x & qtJEu=^E-] %EV.dEKpvpxp<!AA" -, WT$2gWsnӓ|c#J0DdȌFʚS3JDd$EHLDjk-ɋ_Y0.UIiF}$40"-:#HŌH>$ODžNr"?<͍H>:GǗ9 Sx"ÝY.rwnQXӼHBT.eE"Y,zj* @' \ОI#Kpvpxp<!AA" -, W1mڸUu2Tt~-%D2IEP%USU1.X'_mnՌ"Ih$HHB$`FEr?_\14 {BN,ӹtn绌D^B"AH|/qgV"gHB"~qA"/Cލ"ȝ1l=~SM8 -E2|nGӕt9L IjE=bES>=w:>t}$IC'هNd:>t}$IC'هNd:>t}$IC'هNd:>t}$IC'هNd:>t};I͡HJ$5|@ԐH 0l$S6: [DbɅtyF"wDCy9<c񙨴OlT#yI̅tkԗDQ#uk"?'ƣ[y4y=5 7|Z 2+nmD"ƝqT$K S2Íp,r-A\W GA|!APX QM5 [QVJR"uBrB\ڀ /B(@ dP@ t0 d!yi BG}+ql!Rۈdl#-ķב&th!fHbH(/u6}1vs3idH4NFZiy~[hlՍyNg#.^AGNFZ³4XkuTUgb+^nyX<߷֧Hr;gĘ,d$Қ&N7l,T++EaX ZTU<|U+{6'7x >C0D -1@PA 0!YA2ږGV#h2G~D=1i)E 2̜gΈ}v#ci"W342̧h"鴏[H䱶AFkdi;/?\ h$`<Е6ڞWh$F㱇CJ >ƼOׁkdcc݈{R9 Ck=+ҮCuf3ng9s;>Qw|$쭧(IY`H[)U1%+)%]JT ܅!}L#Kpvpxp<!AA" -, W/Ŷudk=YoƗ=1k+s^[P(C5OGR̈䷚5rt=HB$ulL;-ObPt2&i&fb7i0M$r\$Hdv ̽KȮƝX~b"yh2ӞG캛p4$Y((%9 k.b\L$b)m`ڧZ@ХX@Ppvpxp<!AA" -, Wl:iuKs ER6RLBU5MU4rak#"?wD~H-$[p7'YDOmt9F";ZX@H/Li3!_޺. }W~ya}DD.8ˍy s^w{$rJ&eE,˪IYL/WjZ)z^Ѿ}'WKKpvpxp<!AA" -, f"##c9~Ku=׬|ptmaHȌQjcFDi"73y#}6H䥴FV;#׎oZ#M㓘fZ5v+ OB"?w3{k%$<УH䪹/܅1]HH䪍h$Z]wA}*Z\QqP# )]LXԓ\@"^F=}D׿t\x~@BD!H *h&d 9jۉ!N~k{gwov`jp>-GwR9 mi;|v2jkIf `B6h`b.Z?|r*cmY]3YiaL{SewX<_.>pNr03ڤYpNrr&?^^w|8SeI5QN!j,%ł5''Y/Wrn[+-C~o`gPpvpxp<!AA" -, hdӴhQnczeL W[3 --ˢ)y=o5FrF*F$oF݌$ggy&}lX._|\|䡉ߴ\v,coa'7Hw/v=ELقD`Eplߓ?8~bM8 -+ U骘J bWr%Sb%h"xEmH\ڀ /B(@ dP@ t0 d!y88ɓ9R=zsy|pL3:WNh 1~:M%D>Fy#hKcCvȓ[X~&6dMHܑ#j,'=r/HΥ=i$O`Z4Xۃ%d~}/lɭ(Ȉ7"y|;c3\Yr\&"5IS"UDE+r<*&ժRxIҏK\ڀ /B(@ dP@ t0 d!yĶ&5ı8&vzĻfXK۬6yDNP@DC-+}1vs}ΧqqI XWcLKZ(3Xy}nw?csH}Ɛv5}JfCM߀5o7ߜ7O?B7Һ'pa}jrwbHw -owMM8 -ime2%Qޅㅒ(n\j ![9+ 8;8 <  B @T@L@rڿ8mľ8*?D\rYEj' +XB"[JPmDhˈk4za40zM>i=iZ+J yvѴ~ Kz]>֟֋o`?AV$NNﯭw.и[memXY]~5zrܰ1nOk<5nqU/QVH+ZbI/[g+Ձ^F*¥ 8;8 <  B @T@L@r#+,<✁Mܳ=wov -XA%$:#K|"we:Y0flgLD:( FPh~s ElmOdEFBO멟KhB? }ѐ^S8#fB c3m> Z xӧ!=>FtG=p ;s5TTQeQM$u11JXN''*hHw>:tՖWhCKpvpxp<!AA" -, OTt{ơ:ډ~F|rmG֐Q ,iTd}~vA#gu4 |F2M#)1"Yj!Agm75*}!/ [h>hHsD!BCzj'c!?bε~#4^sk!S5ι^mX{R(WfL9npfmy(tR]+TELWˊXI$jEy͚ 7b,w>4nQwIո'b<-WDUˢ*ĸt5z\ڸ{B'.mANn|!a@b 2(:`Bz_.D87fnQͷb"]Rb"mW b(R)U-d%Qwj鎿ۺAZKqi:p.p? !C$A42*%}ĹWqM< jK>"=$UK_!H=GDK7o-# |QLH0ցfu{ i|ewѢ^xMFZEKRjCFZ׵ #pbV:H"lDDZ\R\ iu7?xY-tEz: -%^WWŴ*r\L,ݿg-}R\ڀ /B(@ dP@ t0 d!y8TJRe/"=xfN\xpX0HPKMF-CYNZ(}g4W1)ЖN8fw?mm4O:8F:5h:F}77X=A3ځt.@N}׆I_tf߿TM8 -+uR9N+I2ʨL)bAMEBZ,u4,hMjKpvpxp<!AA" -, ?C#k+uUTyeU|y^ǖ#u‚IQL5z}u41uGyg1];/0"c =5_8y6SY⤉ -M*FL0{dRi6ŞyKۭ#7_B0sμr 3ougssq/݄P/;rS%(S岨*E1Uhj:Yu4{4m#=K\ڀ /B(@ dP@ t0 d!yʄ!bs~<o7@7 9x_1:+v;in\ֺu|tsv_E3@GCq4I/3:.%MV}Ny"31j/7}߬hHOZN-ǡf~Q~ߘYHўSk7֧3ہpk6giM8 -5B3a-S$ŔV+z9].< ]~w'P\ڀ /B(@ dP@ t0 d!yL1s55Kn⺝gzXЙy(#kvn"tY/Ωui|рЉtT{,#_n!vwnm7BsΡ 9#YhaTM f栆{5t+$'?Ӹsİ>EQsa[yu7(,orYOi]Rqzڂ3J\Ձ]\Ѿ{+\Ggpi:p.p? !C$A42ހ^FZE 4)_WӺZHl@eG=D=}]O5 .@=] S;ҟf˼H{#BM8idb EEL㲨UXJĄȪUua{am4`8^!QȠ -` BP{Cln$ı8V2#]6W{5m$DWZ"#ڳj/C0D -1@PA 0!YAjkmf^(Z <ߜ! "LXyo'Lsn` 7I8#mțkZԍ5O$tysF~{Mh/҄e$jP#OРzُ\:݊԰u5Ծ0=۰>wiNru7'Tф\Ѫe**Z+Bu` -iÏV/?t&uqi:p.p? !C$A42rܘCw*RݪG>o䠊`3*!*)4LSt$OvC_P4ɋZ(Q _*N}>Dq|i"ߠ3ڭ~"?a ؕF"O-qguEձ8\-$`5!{Htv/f]J/X: l^J{+!dl~l !|{ӗDJFe?]esX@Z6b~ ]@>FF-z/{f3*p ZB~E/2:Df@p#Od/qøu7(l9~&'t2YAQĢOQʪl<G=]㞼u1¥ 8;8 <  B @T@L@rڢCb{ -cľ8%G Z{e;o%݇qHd'$DxHyQ%j66-Eie1Қ%H뵴r4FZj!eI?'2ϽH|V6֩4^BZm7598K?u-5.|}J Hs0 wν˲\q+g݄02]DEŲȪjA IMKrQNR* r |4`8^!QȠ -` B`}#'Gko&ķ76-zJYCOi(ۈFbl vhcLښ-i}bi[HklFi}<*H0Eǿ/2j tkz2#nn.Z7VWOֵ5.[1B)hIoF[wWdKt -f-LXYWQjKRRZ9QH+}g蜧ߡ-).mANn|!a@b 2(:`B<EqHN-=W ~! Èg4 - HnIT5Ϟ4pbF=}Gt(9DD^أ3JK1Ȧ ݖS_@'H~nW'+#i$OwG;nw XB`?5E⢫.%mWv{w%Ɏ]gr/pN&]/[H&ɪ,b<.Œ,崵CJ(㥒>T~u=e{= 8;8 <  B @T@L@rȵy۰]phNvѽO=E$##$AtYUR;^5F_d,ƽH(i$۞$Y/lao7?lrm0O }] ո_G򪱇;q`:wy_d=MۉgQ%CD:ۏhܩ[{?4OMN,ޒ q"_QiG Ga5nHVD2.'V*dR,tVpWVaсv?>ٿ@gpi:p.p? !C$A42*E[o:8eW=3;="Leez6ClL64/2d7P7[x|㰊~e3_/Ld^(74&;BKX!2?п`iOC\fyIW2wߗr?U݄#psɪZL"?BU,iQj)]IK^[fgq+.mANn|!a@b 2(:`B<|=ܔy [^'ت`H,)*k#u0LNDsC0D -1@PA 0!YAmȚYdm8"K=K Hn\YBzsמC>3a+O*2šdLBA_nk_$(azɌ=|1Oo|~r3{޵'#^пcևj\xE댧 SK/g]] GgMTRR/ .9)V -% iK %3րvLjث6'7x >C0D -1@PA 0!YA2PX%ıf>⾛x@;gH9n3n‘Oelw&SŊ*|$_5\NYi"_A"VY\ڀ /B(@ dP@ t0 d!y8.jtLw'= ?%0v5h566CۥT}Q57ZF$a+aciD,xHc,@asOa_i$aWh$o5;"u Tٗ; ?-H~y:Lk(g8s_1TwUomxbHMg}z/#OI>hi^'ƭ3{={4.|P%Bҟ[HdWwM8L5_.q]QTRJ5JbAXܹڿ}mĥ 8;8 <  B @T@L@rֶRS3.-}:ƭ` ?1neMIbH=+'-S|[fB$#aW4Y~{y~Ha$䦏1HHF"/o!uaܚ[HK‚̥(ik7n @"eXbܺLE"g/G"{F&wK)xĢjUb%ETBAQU00yZ$qi:p.p? !C$A42|iHdn:BN4yUIJԨSTU5MP)?s"&^F| DOȩ>_haغrHӵ>נHѨy+bO`[:㍰7h|0nM^mWɭO"g^H^@%s[ SC+fٗhMn G>RJY'VXLB<-Gug4lE$`ļW`pvpxp<!AA" -, WZF#8d."CKC2B멽Ƚz5l.sQ3d5tq:8Q@g׳k:k 8Q}e|_~ψk\ȵ-p $n1m.qgF\&'-eUrĥmu7(rJUN#izDT jUOTJz\s>ZAqi:p.p? !C$A42*M9Huj -*|?P2(H,&HRLF)ʟh$oaD<Km&~0YG6d7wNn^xM#05tP{[.F$_qwFЅb11ƬƐޝ8Q6;AӼq`'YZm?j}A s eSPiUEI'vrؽk=v漦>&i$fD2ݾf ݈'Gs$gf}ڴڧrI}Z.aPb+jƵ)sˎ>|8$@ \x~@BD!=$A dP@ (`  #Z}RŠVgHL> #cPʺ2jE2k<ªd݇E~6{ղF"֎VU^}EK8'Xa܁Hq]?H n|!a@bqH@@TЀ`A`)#]Qaߨ,0 -"V}I\VKl)è7!}n5p]ʼnd3̰*)r"E 1ݭ+ r"8*9h~I'wZ %|H~"waz_IW=HV,b&䡦p"~GKLEE@ ba}b*ʴ8y F/Y$]7x >C0D -18$ H *h@AL yXFJRⱼoȯJB&F}6֊K =iT䜢Q#WgziN**TaO"j ~N$w\nS-aU{NJ2L$]5DC>y9q3SX9ۿDxaY4pNeՒNWDT.j -[*ZPi:ݸs~|, n|!a@bqH@@TЀ`APؠ-ރ~~v} GtϙM;aӰSз:-'arR -g~7:93Aᬶo5{g?/??q`GCN4.Z8Pt9GP7Jyѯ[4?|KuWH]n~k DHoz%U!-R*gʕ_81di^4V1!1R<:@  B z  HȠ -P,A?e~J͵]37 -L7&k#kUsc|"ɑ&CYNh5Z'ڿSf|KN#[9 w/oۈڦק+#AN*Ct^IV:_b#m[Zt}F]B&6ȕ7ۿs(#;Q:E*_%ةj-qڿaNPdco7ܽ ս?=Kcq1uMc'd2!SʥWZbaED"EDJZj"EEZR-"R^KՋOϏτ'jk}{wΫ~9g:;<;1;:''SN[Tr^XPwYa%rpRXt;T,N&dY/T'&rU8vrSjc'_~iy,.*c'_2TQS,'NQdEJrl"SXYbJjHEQ-bRMrAOijI$%?N?˝|-1cFӲ/\!AA" =A dP@ t0, _%琹2ܲ'+^çCjX=FTqUФ,+Y,]3u;߇wv}I8G?S?Gaς)LП#Shg9YwvRgYUZgatvΎ\gG{K-}U헝 e t }l$Te7u }ly:˦/룿{G]le_c+wL|X|uů)ѹU -'TnТhx[ /3OMN<,w5sA+\)4܆d&H"ZZTSUETL$+>Dr_~B#KpI蕮ge:I T|h*<ʩz*TFMJeurJo,~3;&#_ C&#heO?8Cڨfu"B_\WF(sPA(-G(X޲a:Pv-qٗP;& |jݹ*&c0&2#UJQ3JB)(jRLP.|kko .px B 2(:`Br b:LREHo7o\H#&2iE:9BdֹJlȦDi"b$&H N^D@NdDz}-dur~:ٱluL#9z$3"9HȈdWq;da$"iBi]'w#0|i؟\|R &_2sܰb5uXp>HfZ&V*& -bPJ!鲘U?e-YH 5䞭]~?4tNp<|!a@7@b$A4<"b!Q%<"uejhY}$FTi$cDAHaDr[Lɓ[?h--kh$3b=d4'F:U2L#hJ -Ƶ)DI/Qb;ki"yGAua*Y/@h""М0I92E=ԋEE UTP8 T-fe\T -Vtӵ7/H8n^!QA@T@LB`UZuݦG53*d,dqI4TLU 4#y*֔+dDr r}Fr #6"9~A9"qݸ~ʨUfMH~xZ%sژYpy䉈vϹGcge؟?иX ƒg&|s閑,'*JB(rQ,RT*JTﵮI#Kp9A=FPnJYiP=0:ArYiy;=̾Hsz tKKY1yVsз9 9fٳ=9Najr]Ynun?O4܄ofS>)5UK銨dhfjY,䒘)RBY) hfصꧯ҉Kpflx<.X9Q:U=wO* LK0F"Dt^HW(-LD鼩2ŏmdFOY-tM<c|iRn2f9A4ot#9FF}ɇF$F$۲tNmV$?cOoHE4Hd$,{pZ$D^F"?d|9E2Ɨn\Dn"9^~%1OW_ӕ^<%oi H]nTʊ*#E9#)(fTQTt$Z)U\}׊η3lƿ,.\!AA" =A dP@ t0, _eb$r.qgϥ60-8fȄZ;f$Bi<(v.7fs6l:9\0Vah:rF:o#w2wk-Ƴ{֥_FWhtq6♘a'E`BVG<q=C<7 Ss,r|˹Ek _0HIUhJR,dT5夨W,3b]Wt}tNp<|!a@7@b$A4/H=4#HD#cF$l#7v23Er'o6i$odL싌H?[2F$oc&[Ez'dy{qzsI@ۙD$ư?59asGlD[ 7sa[+KZIKՒXQP,DI "Y(' rPQT$.ahw8n^!QA@T@LB`^o7ܕðr7|hzX<3 cpufbX9?]*tXy#h$o=4F$kc犣Z+˭ vXS㙫ҿF#ɕtqOIVt Lτ~$T cX aNfQ%G -9iqLT;_4OQ%|[KQf/ƕGM*)^INUL:-LOh$(bF/))%B: Ur~0ĥ8p ? !C8 -DB,qm&ijw'm$$7&@"k%It-'DXO5DHD}hkˈlq,ZAF\Ӹ^@+hIZVP{yV+75VoѸ8cF@R-F\'lr3G$Nxrgi>zo9q̣zQA* {yƙW7܄[o")T\E̠fq\Sr$(-_{E3.d6W\:'  >C0Dz -1Ƞ -` YA,VXiuqm# ij&N;H>FB;Hx5,㺉DwbDD@]DYM5D[I70qqh\rmĎO+xY v{2#hZgׇi맓Hi G1rv{zUHA)zş7נ߽𑆛9$D%]PŢf/ -b1]L*rnH+vvwM\:'  >C0Dz -1Ƞ -` YA,d -.mxr~A=Z;?$#EɪzmvP&rOG=~F"'rAG~6Cl}-~׳i"_f$R|Q?N -hwkc׊2滨hww/ȹi\ګtO&'>ye;u[HdoHd*p>r5AkzZɈrnw IX$%9U+:8#P\:'  >C0Dz -1Ƞ -` YA,ؿpV%ʛ^' ڃx-!*# K+inҁ'Me틦# #ez&Hk .AK٤ɲş43"yEG;(e"كHzEsH"ٻ\ Eؠs4O1}`}9n?BK~& |KniUP3%PMLJ̨jBLUKRYKJ-BsgM\:'  >C0Dz -1Ƞ -` YA,_w}g*?[]?;058-tAxBd"YƮOz$WMOfh:Μh\hz,-.F(ocvPf%yL00CF<4}=> 3|kkqa}V/5.6 g S4]冦Eܻe75܄dZo$/S.RBT44ȡX( b:W -$g%gǾ| f8n^!QA@T@LB`Amu qDZߜp>ӚDQ0Lc2ۂ.fPX3DQ+rl? .px B 2(:`Br ";af'+hjePOX 3Q)&d%JZ\W؁O:qҎVEڅU"/EhjiV@;?蒊/MyHn h;3?љ{O#WO6yy C$_iWMG$EWލG .6t8a9Ό: m c3?墮VJi]Lj 3jIjUI+޿WpϞ .px B 2(:`Br 쯲86iEF¯!eķw -ZB{Hd]@w"[E/aumvK6m':b#j촼 :ƕgbZ@˴k*^߬xwSNˈ둴NF{v;kT+Euz{q=quA{R?59ƠҨc7mn CANSV*k:V2djG=+:uk -Kp$.\!AA" =A dP@ t0, NZ$1Əkg[!)`iDX>n 4vYdHE##H̨WHC #n#?ww-lfZ㙻¾؃ dQ%?n=|H2"y^U2 "/^Hb/rA"ytT?AS,o1}͈ƛ93+Lz"KMTKlӪ&L+Hb]{ѿ .px B 2(:`Br 3G yTɬD$`>4U2Wc)dlJZv6L1|60"ywfD6m޸yp?q43"9W*6gB8ʈ)mT1W_ >4 DryLE$/1OMNC$[&͠w#Vܷ@x[g80~$ b[?r#N=!HٓZpıԞrYHy{FI\MBÀڢمӀ b,!*-"b%&c4E qL:|kCnIizHjc+x׭Է-)nQzzvV;;^m/J|ٰ?59kO^B\p6܄_|pw`ԥtADFShNu+:9voNp7x/Bn( H *h&d!y*O3v\F~x%v]O|OFx6E";l"ۉ눲k軈1W6]~VCqҸYk7#wC=;6w?O>p0+lZ-G'2zBi՛|9;70xL9OhI{z䤟YnwZHkzTM -ͤJe*b*)r"S.\tǟwu8p ? !C8 -W!m AK#e 1fW -MciZ'Ji}h=:(6K]xONhփλhm]Kd6zLS)~ .ߟUoctzvNlEm7v6܄o8=]J9)jzIE(ŲRzR3JJ>lEڶ,w\:'  >C0Dz -1Ƞ -` YA,?_Sk`tרS6jSRTʒ [q1EAF"ϤD8MDDi9[iqLF-x^g/H.cN'2"J7g'zwO;_Btہ(HnH>ڝ.D/~YMzL'T1wt:%2IN 2U`t:su@GtNp<|!a@7@b$A4ӥ9~uѦ1s(![n匴DJGm*ژDu!'~Ҭr>'ret}fTNt0[}n;/&ª*Ss9$|r^ -ێQ!3 2tX1}>\d2ړtZբ ^j&S)s+chKpC0Dz -1Ƞ -` YA,}7'-J[o#m)HEiu-n'$QVu#v}=1z,M"FZN?ua-KSL-$8n!%$2ˮHt~md<"l{h:sQ94z --DiTa7oBTܛ炟l д&\LŲ\(j"&3XɄ\Fu w~9..\!AA" =A dP@ t0, ־ߎuq$7&G0In'6ͩ^C~"JKKHHqu0#E+ጸ^F\j=4~ϷǦ]i\NqѸq-םj]'cĉfxq(ٽc?A\ڍf/ w@I co܆9C0Dz -1Ƞ -` YA,yormͮ吂DƑH%*!]IVu<IV*ўAF"ft/}"y%d[20"y2:~aF$f {%.!ۀHΔ4.2HDr .6@$睟GO;0=mnRTSXoot,7_I5!'rw|٭YGtNp<|!a@7@b$A4D*ɯydEr}wUZ5seW7[3"U״o%kGv}of79d YKܐI?.ww"%f^y |r^t ]W#pJ**(2afݮʅJEOTCO,]ڇ]k>ه] .px B 2(:`BrY٢o57Sj@!)lTP%PZV7 7;;s9JN<׌F4Gosȥ7=;s3l0#+$[H;ɇٙFzlcW瞃HF=h\p[qM,Uq -"YJ!?b74܄Jʭ_vr~nP(b9e'Ixw}i\:'  >C0Dz -1Ƞ -` YA,ڱxf3qʵf2BchwLM1%ƵN֔>.ɏ\H#974<~x='1"iѸܒqzwh$'3&cUo?~urMc+:5*o+"5g%kk S%O"Ggk 7sK>f*L&%jATt~ -Z}4䋈?i\:'  >C0Dz -1Ƞ -` YA,UKʮV{ǑϢJjaV%←L9Q-- 5[/F$T1"F\1F$7Q%vEJu2gc"D^H@WMf$?hڷH6lC؆H䨵ws~X^G"|ajrr8]DuGMHh}lI5# ؒUQ-HՒ,rB*z6 %ýu\F.\!AA" =A dP@ t0, o_fƸIӫHUIvFUj;d *k&Vý40 z"?a$ 4hꠉ줉?S>kCɥVmſ8D#y?i|{ty^ɯѷXwK珨ɏeDrIE?)/_匣9μd _p~v'ѺH%X(%JJYSE]&U襒(HkE;Z8n^!QA@T@LB`UtW{vmE|J`ZpRh6 Ob?CbH|0H3|+Q& 6]ָYKK_uF@Ҁ>BZYidNpb{UJhm<`\c\Ysפ|sy~6K.^>sd ?xJ~la}H^qC%n֋tB)k%J d%3JU2޿s=a+"'.\!AA" =A dP@ t0, _e>q,;1W_Q0lЂ[6"H|~ĄD^@}q+iֱ;>*? YIVa]rkZyAcXаfua}s׺h#6ٙ2 -:Vޙ4i(wcpFXݙNwNˎ;3umy& $傚)rQL(*'TͯBúvيέ[^8n^!QA@T@LB`cڻM@A);]fcB\R^6*+g02M DΦ#E#i"݌Dnmc(|\9;\c,>^w_t};ri0kڈf!WM@$ŗK|&݂!n@$Ϝj؟\y祈t 7sX$UR JK>Ghĥ8p ? !C8 -.%EijZE Hp! -"}s#EDHK(z+& jg4]>bKt(/26ێCmkz$«W{OgZ=HrGhjm[%@}9VjܗNYz]>qC! -/"p>RDAQ41Y*TEUKbY+t1QVR:Zpז Yť8p ? !C8 -α:{BYĻ$0{{#sAb3gJD󈶐cK'n`du-gdu+_hVmmduλp9?ƼvZW?`ձ4cou6ir4nґջߩ>MGayKjnPUroOvY0YE[Kj1U{ީTUQMbAK(N+U[ĸɱWt;.\!AA" =A dP@ t0, _emaCkq"gj%F|; `n%=$Dm[.""z_ۄv46bnizߨq^ZpMt/0Piv::nh߬c4Ӹb^FiG㺆UOL#!o?|z=poܥq=N91mGgCrj:υn0:.RB-BJ,T޿s]+:,i^\:'  >C0Dz -1Ƞ -` YA,ʼ6x>qAܳg:?64iy$ 16_ۈ;(=qo GroP8s?E##\.eOϽ-b܌ijYU=.ZFj󗻗V3oH8p ? !C8 -2"iL7R$HF̌.UEU5M%#oj޴cTIegv~'/Y8*ΖKmǷe>h6Vɡ472z$MF$=zw;G\ͨǡJ p'7pK}֯"W0O1\,g|qr;uK= 7a#~E`JRZL'VX(keQNQbWhDwð?D .px B 2(:`Br j cڳD{zgD\FPo߻Egf&KSu6QgL45;@s4#Wddh2Dc2F\x!ؗZc_g?lycdI-=ѿkc}ߑu<2(C0Dz -1Ƞ -` YA,ξi7{OV_O[jm֎CHl-o$†+eWmv+ѷc%1w6=lF\=znXF\F\w :Bȿ\@8ckQ@'v -1ZImLӞ<47֋dKf Wz1hm0Oڞrq_P@Վ0i[LDK {TAZժVzI& jkʒ{Ѵ8n^!QA@T@LB`Am-qlӺv~fYT{fX{אZ^I"i]\{$Oi5 *'DvXfd>VNi]F4]H﹖OU-&x}ŧOU~ƈ42k-l#& &C sׇ6j\t >qrfjݧ^voMh⢴^PU= "k3+RjZɬe+:~u~ .px B 2(:`Brk}#:zr}gG 7ggKgʣj6f au<_z4O='3xۈd2w,hc:z$dDr0:9t:w4=9{#6 -:t"yWN̿Hv~wVsqƫs4܄agHԔ ZAՔOT!,fJGrkvv2$.\!AA" =A dP@ t0, ^{bGxd^FkG#(A)75A$ܒjj.Ɓ[43dVI#; -FHj9H,F,*ydڃsMe}FH>ƬЙM;v/%N8z,H~coB$;Ӱ?59)( =nT^1zaq}RqJV{XNmL%4d wi=% }s޿pv9=ip^@o܌2z:)z{v %[z4܄Ok$+iĊ"(-IY(*$lОgSp7x/Bn( H *h&d!y`3ռ+6= -ozGt`.dl%Kv:\iG6GDVӞG9OЗi|C-ut,+Hut9]p=#e2 -i_>dwn}BlGrDɂƝt;v]c؟= =s9bFꢆ0LuOfZT.RjA,+e*VTZE1k-}ڵ] .pW8QqR'ڔ)nҔq:ӱ41zՃ!d)fuê1$`X 1ADF<w*dL"8H"4Џ ALXEJ(z 1*+D?*8I;mAӊzѰMoރ+"@,^LI"(KOBɅ]sޮH=W+P,&9%)~KY ?)It1UWmːvH"׻}iGFHe^$ D4OydQRgw-. -< UcV/4/UaVZ||k @A!!H!>#,rPAl8pQʨ)j'ha/Z15^NI-e=\V4K -vULH`DH6'1lLYEr9{MA_UD7웚}.Icd)1Q#'%5rq5򨤵j`ưunv5?2="0""8H"4Џ ALXEJ(27{8]ٗBvؠF$JI+Q#uDZ*AuC35hykKQbuD&{I}FtQ#<1c&W(;2JFUF.;;g?]E"y9ya35r/C"OJ߉ȟ]O)*)7Iu-ίf"YF ;oT -րcce=3:b"e -zDaDE q$D -id - : `Á"#|2whw[yQb%W;g @A!!H!>#,rPAl8pQʨ)n MEfEmO=f楦=vb'ƄjO8n}^qVK2|=uO߈b9!9(3]+ϷK߻+%l%<&V]W';{bk_n#ΐ̟Jvvby ׿˄CR~m閇'_j-qڿaNPdco7 U7CBa!Ĝ!t&d2 sBg b)Z˱ZKrV+"P ނbPK|"|'mvB.j?VCsf_ٳo玧nM7#m|Ug IʔϖfT- NR+צOU3(+Ҵ/Q6?|Kf\qy|]23Z&g'*i5}I*R2%Ç٫]&)r^*RUҒSR)ץr(ejY!O[}7 . xC0D -18$  *h&X`C -PG]&7myTyojD*1m`LJɤ l)jj)X=&?08f?.~Ӕxya={^?ox~瞿翱{/L?&& }cľ1roX1Ƙ}c1vߘ|ߘZE=U㗽,=/W]{~ee1{~c#_\?c/=\a_6Ceyp:D>*.9a[2'U#c*'^ҸEv0iʼnWN-ܓk7]\1}5)k$rJV꒪\:媡ifK4n_HůH 8p B   H"Ƞ -` ؐQ.Lzt3?HNDIIUdYQTAtHHHʈzɏ3" H.:#C$}"Yo䥈Nw푼F2@#F򈣛 |\7s{9F$5g"7o!LlFY?ӹxqu4asvD;]I]JRJTժ-դrNפRFѪjVUh$oFz7E#. xC0D -18$  *h&X`C -PGQ\ -"$Ix01DRKIQĄ,*6""qS4?WV`\ -EDҌYubD$ V -ƵhئeMxo$OQ%_w7#y˃C,oŲ%:^ 4M:Oi,f,\{,G_GpxrɦVL$>7x >B(`␀$ -` y(@Iz gn'Iϒ:|fyDn'I "l!F"o"v>AGX{:lFZдii #ϐfU)#YiMS>KӺAFZJ'iZaKz4gZ\Hvȉw#sbj.|UAZ7|RO>GH+؜NS~T"maurUk&iEҴ*4"iePYәLu Һ|O|tn|!Q!I@PA 0P"^M"Ш&TCZ؈bLA<'IYEC6hniX{yM䥌DC~&C[ډDC"O~3hiHVi$oJ'\s}3|[K[gYH@$䗷?Ι2w"kD$WtYJ$;孻=H/zE-mZI\jպXf%եV*K%OZU+uMm/}WkWF]<!a@b0qH@AT@L<Ώb4Lˣ#hiHʱ$"i'&DYΣU5Sf-HE#y?F2FLF$U%HdDR!5vni_;"}Dd&-<#hMևMП׌ "7wI6LH9:7κk:ZU #ϝcP$w""tMdRj.ePҖ뒡kT֪Jrwnk%]<!a@b0qH@AT@L<xuH- 65$RhQqDLI4bfc,yKtr1HVh"=DF -zG ]t[Q,׷G#41"Ljwi$}vF$eu)(^tՋYt.~G1eZǘη26g30e&"maGR޷ZE(Rfh()E*:+鲡Jh$ѷ%nS&>7x >B(`␀$ -` y(@e'qt ijx'7c)sCcJ+I!g|$6DD{Gl!ƔXkIƈEz#]rusr\t|)woE>׹4cZqd2]X:1sF\%EǼ -*D\tQA=i:ߢ~f͝j\~PA?7"V~CRzVJcTRNZYSvTЅ-VP|tn|!Q!I@PA 0P"8?i51f*D@Z!1DԨ3KeK=XM7&4y1ks1)H~N"w{" MD747D -,_;ÍDFArop'6z$r-96g'sx<cE87N'R/Ke%rVBa-ٺdu{{߸u]<!a@b0qH@AT@L<ΏT$RH7I:zBDZDʉdRvuyvuT:˼"197"y -#Kz(>)]#7$i7x >B(`␀$ -` y(@vyDRXdWB@kl/p1'HdE6TDr1p -#-}#)H~FrdL߾c{S$ȗ#YX}'0]41s+c++?\De.yKs썽mϣJ>v|17\nR[^f]IZ-eZNJFRPlMir9W[\y GpxzyH=xn}o+{f/"Go Cio(yMErbo=hDzMdaΝxNDw|HYmsDrֶpvt=l Ufd$Y3ٶmo}%l]GpxQSvbgDZ}"ΉQIZ9%e32ddzVgrɛH?/. xC0D -18$  *h&X`C -PKvZ^  A-m-(2LS(jj.Zv) ҃i"_Hx;f"elb?D] 3e~ç9t id/lFkF Fzi!DDM:{*b| Dr)sWQ@GM[ziCZƴ] Y%AfsuIsR*HjjJ\i$E$]+_Gpx`$MqHNcɓ<ɴH(_!?3!71)G~ψ-t-v+tst37x >B(`␀$ -` y(@Nk.";~/$$f<GbNK$f%p#o MDYHDG!c)1k^ 4oVPU7 `dzuh+BvjhNb23 .3hCڝCC;qըȝt'eN-쓝_sz>VFT.@T',SQ=Q}貶:ʊP(ղ\ճ45[ACk$Wt]m} -#ls#?G0~`9s#?G0~`9s#?G0~`9s#?G0~`9sHc|6y -&$& $DבV'5MDHDL'6}1Vs NE2*H+k#K;IF󻩱v'% NEH;p>xs+Qu6^bl$h0jFZk=ю%L70Zi="Mb֣*i#;֫95:~wHL[SN_dsgN/]0vOkNSCSQQKhz5-e+eJu~ͨ7?hEGpxәRQOޛ#i>|^4o|s#wO7ʶE|~+]XC}޿0prC5tN>jz#e2oo݀!5TC5]k=QhZFdiI3T)Iz:Uh>oy续G^~@BD!$AdP@ t0 lC(=^٧A3d5D[^UEP5Mf>E=& D~#zKxA -4_ɿV̾C{l*zrD7M_dD2Cɼ`>|G#LƦ&5#9v"%:7/I'm0v93B ߬]_2THk)]W9)WO$HFUIR9ic'7J&>7x >B(`␀$ -` y(@9LLN0 !!*t[k?vd)W]\i#[)+{jJMJي TɕeMѪ;)]<!a@b0qH@AT@L<Ώrq=|߽_N+HaJYOkHlԛHb%I D|ȫr/Q&!JbM;;.r?ICXi5/1zr5QܡNi}"?>zh#ShQROi4͓{(pp ЩIt;n5i:e ;]·-N)4f%L.r3Kj(+<͡tgT^ժr)RAz_ ->7x >B(`␀$ -` y(@L$du;8rI ѳc!$' 33Ղ6Ass:Uf2u[M79ȗ{5p^,x"]`Dr=d#o] o17| ?B3WHÉ. xC0D -18$  *h&X`C -PGG\g"S=so3r 0Ύ !SS0˹ :K^ 鳌Ĝw4[dzםh4Kψ{F٧йp3塏3y4~mclP8_y -:o1y~7sKy͛m`ys.Q*%5e9)դZCO[ω>l>mph6^~@BD!$AdP@ t0 lC(KQYS?7 Hp /#쨟XBpq2:WYDōsSAk^3L:a4bhP=z]rf-/(/sz>#˸~FN?Kטּi #!:44˜xZu|^}%_y櫦-r+̾(tE;m[tZ]dʥ0s2VTU+4+]7>ڢ. xC0D -18$  *h&X`C -P=˸{q{s;ŝ. NBttĹTea/%}l)F: 4QF:[ƌK׼"${v"#ptziMt1~{:d"r'Hs/6Y|8ٗ0tESlξɷjFr.`hF2(;{-FzCpM߆HW:/v^j=ퟝsK`~xRaD!3Z ᄚSdCUASt͸%vZZÈJ "9ɷp/yw7S"H]$2v"\v9NErvk/}L)5a;iцyնREܸtE {j31g\cI]Ž#JzVRFcO|&R9/UFRmr-k_Gpx@Oכ3oQ@?}͝uyngP@op -͍#E+ge)r٬銔I(6st"od@㊏. xC0D -18$  *h&X`C -PG8{N j&[CBY R!m>dL3Xʷ< -MAM`RY'{s[ld45t<ʩ= G hLsuW9JNxًP~<9bsCyΞ |ѶpkG=6]SԪ2ՍjR岢Ln=W7Asp-Z|Ġ9As A 41hbĠ9As A 41hbĠ9As A 41hbĠ9As A 41hbĠ{@YШˉoyc*XA-&(t7I. \qi.!MD[BB7k^%aZ+a#oҰ#AXMf)FXMz$6a -=M 7FЧ67!ss'~m Z&-:y#Ef,l`PguC/glEYCefrFQr Aܿ. xC0D -18$  *h&X`C -PݫBb'A ЂzcUȎ<)$dAŢ,(t4 ӰNϏ}4G2y#MH1NzH샻&rj-'r -Mdfu{i`S(f"Df\,V3sM[CO{ƪ+h$O7PLEr-1:C{Xz vS3{iDqepABDrZ;ܩ0f"ypdH2՚J*)g뻦JR$険fErC#. xC0D -18$  *h&X`C -PGIbw%^% DcVl3a1)'qId- ٔ{#O#uF$v~OӕSi$[/X{ek̪NH_PݯWbT| :r}^ 8"'C= oзpj(o k^skJoJժsxIQ%RE-WʕT.WmE[Wmu*. xC0D -18$  *h&X`C -PUB$'5|ޟIBXX`qJA-hu˘h{#)H^Ĉ #9B#9Z%yF[' vyy['sGW"#/x&r6]o=DN]D}ͷ9Q_"5[@"-m.q \dE8UgFyJYS2i])s@_$ ='RZ.UJ^oF}M]?/? !CC 2(:`6EbdcMD^oI=darLC" ( @j) 5U5TIBBF"ڷ1Nz>E41&t;~>u|DNVtJ俵"%v.#0jNCϨmg3E\$rg9ԻwGu⫘$O*ڜnEⶋpvt?૬kR.$W\\=5\6M*7>&M$>7x >B(`␀$ -` y(@vPG"MH6"6VUr3I*h[E`LkIh2)5Bh"_aȳz%W׶uwuDr|_מOD*tF"k;*$NQŨニD>ۼ_)}X$ o!",1y%3 $S.Od#`jYOi٘Y 5Rа&e*JFVlImmA:wH|tn|!Q!I@PA 0P"Nd,z4t&?ޯk-#j7MLLH-F 5]u|NgiC>hMsۘf.TZsΡ8Z4/gD"Ծ|=WфXyhU_szHh|C~f$4Y4Hu./!?t8߰StnQ-$?ohr*iHl&YJZZF%yE"wlX3/? !CC 2(:`6E}`ݸEiyM[,61Xb& -ɼ`V͝D;T~& D:B>a$:Q49푼1F|Fr-h΢k=ߦ|ci$K=Dgx}Qo#?Z|$">;\'Q4|* -[ǡէ.}*+426tg Υ*Y[}2L/i$^~@BD!$AdP@ t0 lCed)" B &CrXoܣ=YIU01Y2aQn`Lw2ɈմHCQrN}l*_a\?1ZIQ^ÈOh$]f$0=,|"o}Y9bϭ ֹ1o-N9\ "v;ʁ?*RES0_frνG[ke祝En}uApxz(̖ӝ=^ܴwj;?AKhdi6wjeE;I-W|"vݫfVʵTN*LI*SJW륶ɇvۂ,>7x >B(`␀$ -` y(@vߴAYůu6#jԌٍàhd ѐ-PUM5C35K!=hdEʈuHt4{䣇vmd'w9'/zVxvF"-S?=l#kW8Yg|&Cȳ*:wO!}ע,+yT]v[lTNg5)^+9i7[jJ c_>]<!a@b0qH@AT@L<Ώ2~y9G"sǺw-&42q.r==l) r'r Y*G$I&1]&wb:iq!0"y.o](l(3uIJեJKUTr^͵Y?MXv|tn|!Q!I@PA 0P"8?KĵYyx^&Gk`ϒIx'l"$yrI$7a Dy[/L$v|š'dh\/q=Lq=g3S}"_YA2[͈kV|?~LB\eNf,.ԹY Ӧŝ`΁\~`;fPE8mzwճzy$%yP.NK\m[_vWi 8p B   H"Ƞ -` ؐQf7O,hoL $2Dg3Nu1dN;2a3=][C6̓G9goJh"3t;(wPn!`a} w/? !CC 2(:`6Ep~?דN6$w;m%Z%$ȋ$W{ܩkIb-I""n'Dyx5X:LyQITbBZI5փi=5WvJ룯̴(wҴVW!k; -ڄBcPH}AZ׮(:>n|B:qPHc^Vnq4]ij6-UTS%ln_UEѵZo]dGldG 8p B   H"Ƞ -` ؐQd@L+x/F$3؂ _EV{ BQfNȟDxdaGu B5iVh$ňaf$HI Q!7egN - -/(bɍnl.7b>Glif('fZZ/LcWW\K՜OTeOF]<!a@b0qH@AT@L<Ώ2qsgwo7x >B(`␀$ -` y(@qa)|#3xoH>o%'% jAtqHὉD3YHDshj|d!NmP0MMD>ptO_JV^Y5r3֕H䟆{.o>nrJ;P#>jϫ2$;{F~ej]H}DTYUhf(TӊTɩ5H;'̶G\G:J]<!a@b0qH@AT@L<t .؁b …c°K,ʶV5e<&?f'D^8Jum뒎E2[ 푴i$3"gZ$@4"YEBF:u8TD2|!v{$TuRΨ\N'3JCJLյT=mKnYww. xC0D -18$  *h&X`C -Pݧ;75 O+&ETBF؊ERMɼ`l9p2xK~o$Ϡy}¼7NV$Нkcz);o{ό 䔍Gxݑ"\|W뒇L[4,Y6Fɯv@$TRVK9of%#G5[jR\nUՑUKn| 8p B   H"Ƞ -` ؐaw$/@$/@軘??Uи3I;QLNƋU -DAŝ||SKиJn0$NJ|׭S[e/z?K iTKZ)[Z`Q{*9DMdƵZ+h^r]h\B0z}1kp< nz /|# B#H   -ThaEPF'6kI#`#U2܍gKbV TKTI0LŲG5qvA$ܵuHZn ]4_D;6k;ErщɊÂH>`*{:}`˂HtUuӏD2M$C{.?ޑ9G`5׉d!˹ZEﱖV9Z%VUmxI{ɳIƢHA/!b#$RH#,d(PAlQB.?p՚Û5>HVvHqUbŸPU 򠢩VM֒&U4ӖIA5Jns#JE$ oH6:Wc$۷n"[R} }k,-tD*_Q$3?M)s˹JS3:y0# -)B^PMF5s>P7fkOwOGG =>@!A q$D -id *40`‚"J(}Zs)xJP a9eXJ%-g)I5c͔Mڹ`)HʹErK+};XJnf֏.g:7ϦoU`Q-۷ܥ]DruDRݯHOL ɑ2}Bݖs"Ie%焃׷݄9x+9ˤXBWW96\e)WժnևUW[CޡHi=pz /|# B#H   -ThaEPF%屚\5NzJDoURuM͒l9D3wb;n$^=U$t}Ҭ[wt5H>FyA<,k*;*vTP%ǻ[;'K+YԶowGҒ[sՖJO}RgnDz5gW|ZriV:B$'EMkT졨z /|# B#H   -ThaEPF3*Yf)JF`fsV3l8)W5LIзqA$[s!Lيdu"g:}3i|v -M%A"k]g8h[$rEZuykqJCZLw\O)#2}멯RlŶ$?j-qڿaNPdco7 738sb1fd20)^lmk)RJ-U(jQS"Tz[ZO8ى2]gZsfϳٻl[8aS~Ӧϛ:mP]xViӄjiz3*rm)4aZ=#IpԊ04uʧN+MM;/vs&WOEig\&S-K,e쩙\FʩJR/2Ig -J\WYPe%ɕz%HJI?=KXx9sX}s/? !C8$ )H"H *h&XP4-{8՟A#bx_BITZutYQҪ:o(fWo\X{nq7đ?\C?ȟ r)D^!?EO1Sqn%}|'WeWߥJߧ}Fgٷhs/2Cy7t ?>䞡_v H\͡_g}`ȟ r_͡_B~eݲO+e/:KK]SMMȎ\`,Uao1*V;׽m4b͗"nxMmߓk$\S -PU+"eUUʨyM+3rDڧ"9H" "9H" "9H" "9H" "9H" "9H" "9H" "9H" "9H6"$DRh^'#P$ňHʈd4oiID$ 5b?Iwlh߮f$H$GP"D%"Y/g6>-gD^KId LgN5-$<Ȼ(|nѧOŻ&rHxvȎui!{)$wUuS!O,m7a'2d&Rrj.(JFLAU먞J6$ Q$o~U9H\7x >B(  HB -Ƞ -` ƸdGmw㻓g2;Z&8Docb2L>&Iq #m`aF]hw3:xdZ>B)꣤[4z(%P@j[Zk(Wt떛m wH\GQK%_'7;q׸^x٧k{*],c ?Ke4|nxd͓&[臿Xd͏o {! h6'L&JuMPREЪUȕ gUJq࿧>W\7x >B(  HB -Ƞ -` \͸wzYxe|n㿟 e1#Ld-]8qI^Ϥ60~#e|7ͨ2Ռ~7c\1uᔸ"qC\Cvl]ֱ$1JZHSz8-#GUdӗOp:i=>i}9`KH,K^dv|Z J״ֲ,$*YAkPUZ)WFҺ i}_wnot n|!QA$A4,( WIj{E_ɀCIEF+%NB+IUjHL$C%vW$ȏH$اO?\$ޖI}؞SH"Ry=IaD޶+H"/P݇ȯ"k6!܄D^|!o|?Y~GC"qM㉔lv\BU2Y$( Z `\E.DB(  HB -Ƞ -` Lq@"'{&{E wRȔ DrbITyr:SO4&2HΠD,R$DߌWH$HCdQ 웂Um7aI4m%2JFP5B,h|^ʥ4WV$Bԛ|iiq/? !C8$ )H"H *h&XPqtZkύnƷ3LhbUNK{-\ΤV1卖jF4ZŌv-/gvji%i]KI\oQ:j~.vwM3S}ms|ף)4D*(K$KɄ{ -: -n~e똓0v(5w ;Fe:{ԝO }GgOXXd#'[m7a5WZu5'T,*hTJՒ*eTSUg*rF+;uoo%qť Xp B ␀$ < -`Alp6{{fz&0Z>D0;g1'f70FF(u=/1SL'$J\{{wqbsOb/PҺ|B@pJq⚞!K^D]au1S@O\;,P\ts>p_M iUʅLYT0 U*PjBJZjMBZ o"iť Xp B ␀$ < -`Alp&ƵIf4c}L`#\ÄfȓLoLlֵLA&Iq3#=ȏ1FF}M4:3%אJIaa8V7%KzHi:7l򯠝Z@zHVx.oPjj;݀ Q[j!+OT#; -k֘쨧UGm4&ѩ*ճ% -M+ J9ʒZZVgKTΒ^VKG dtKx!AA"!IHAxAPA 0"|թ}SNIJLO'SJ@kM~R2a_P! t&#cYo /TğAg`|jGc脇1(JwKMm!KA-R>#T녚Ȋ|EՔjHᑟV?GKx!AA"!IHAxAPA 0"|kEgw!6/fs||&2asB&=qR:fS1c1i5nCHNٮB>~nav;\=g>Cy;t]2</نiP56ňNL)2b*}?b|VUt_9bm3^|JM 1zI|!aY@B빜PTs9SghsF_tkKx!AA"!IHAxAPA 0"|kNL1Ōwp & .gB˙ebrI.bRsR_Ȉ%Q3e̪du%#ɺ8%WkfBRSC~F=oU?SM6wi{V!YF)WQ!w(3C|="ԷPR׌fVz*{D9gNȍ_lY-vMفs݄=fYUيԄ3UPrY!L5Wj+0 -ӮSd.` 8C0D -1C4 2(:`E*&'k%Ʒf[O]63O]^c1眬>$_i1O5>h9XSe=!JZ_#HJZ'U'iRzzO]:NiݸlOLV{He P78LzH/iCZX>j7 ?:i-Gw>5X>5.8*瘶+}ΨS -T JPJJ^d\)hDҺ _~5gy,^~@BD!qH@RD@T@L68_wuW%_ -A1$b")&I4I ܏$DJ"E*%DD$5vMDNfs''k$PzHRy1Id8LK\鋌^wj\0y:݇D.zݕ9@w>E">_IU׿v,n9#)J>/`Z\*rE*UI4)d4ȧQ?{?\j&.` 8C0D -1C4 2(:`E*o0Nlg+}2k 3ᗘ6&10LcWK1򇌲_] қ. ieh/. \5nAZ~F)i;vWy~~m>5Qg1.ܬ Y^Χ+B(  HB -Ƞ -` ,c\SgpW0KB&,`W0R&9;59=*ox 8G: 0JP!-=S{R;=ӾfNCLȣQJU?9]5>a;}Jص(p9=*{ĸ((E>b|;JGٶr+jVIJ锲hj *i kYڗh?]t,^~@BD!qH@RD@T@L6ؗh,R:cyTN1qa B4o4V -J}NӋ+g?IdLٔDCڗ5{H\}NgtJ˿E I"]DqP3]2t$r|S@:5",@Lm6G5' !O1ϲ&{CTʶYu7 -|TՄj./ >\SBPU[kE3K\6I$.]<p!a@b$!iA dP@ t0 `ÎDHѓ&}i|P -dDFc $OJd:$%TfvG(D)5BR#o$r?E (5Rlf75wyǧ܁E$?D.2 !% X -L!~ QD$uET -+;f"H>J-w$ҶDBJ$H>.S>omhgH);%H.$t)Hô%%<;<(ygRx" C$[H&Χ+d>%5ηQnž}ʧVL\ϫ/oUdU(9QK|[$Eߺ݁,^~@BD!qH@RD@T@L6ds['qIHNGQ1A$$R"(2*ZJ|ՔH#U"J$WH"UXJ$W۵oufau_G2K"yP;;޸Ei\C$*<"꟣q=ohA$u&ߚS)K]WYh\nžo\jsJ]TM(dATBeg%-ko\B\[+.]<p!a@b$!iA dP@ t0 `U,xDRPuk3h̰c:"i7xS*Ety(z J$MR%]@>HBw _xo54u|@3UJ"!:l:Ϧo yǘ$$򬯠Ht d^V D""w>vB}VRL=SԂV3TAΔRFZ[EF$r"/?~D,^~@BD!qH@RD@T@L6(z#cŀYM`6V)+]F"%EWO4s+ %?!٧Q?I䔾noU𐮉tv3lu(E,` o}DJ;= %ߧOΝH~{+"f$7oA$dTm-tSsiAY"9#޷f|5W+a(D? eE -R&[j!Wɵ} |#R$q/? !C8$ )H"H *h&XPb E}Dža;Gظ}'D]:Er:Q$;>\JdDr_J$J"iw{[]#YvzR9f]IoݏQ7.h&&R$! dّ݄6_f"s7!+{ƣH~2Q/B"𶾶ݼ`z5_ӄZH`LYׅdZ^jLH[p/? !C8$ )H"H *h&XPKH3+l A-$!6VRI)(8#IS1TC"؏(\@mhsHKzC"~XJ̀9z}؞ȋZ[RS@NJ y&HDwHH{B3v@PyuSMVzOtT_j {a]ͣ eYdEP3JMP$HURT_nXlO .` 8C0D -1C4 2(:`E*VSI_!fTp(H!&k^c% v€#PRY:JI(A״jV7c@y~*i_q/? !C8$ )H"H *h&XPZBW3F - Pj1ԓjHk (U (UsZҾn$JH$w}KHnܵ}=c!T%߹~D_D42㦌( H,x{/0:gSH>fhֳ^'aDYr=`iO;C]3nžn)g5VATξ5ghq\W3rT)[cDG:dp,^~@BD!qH@RD@T@L;8>z@!#lE,TI DF$dQI&9ɇ))5u[H˶.GE*jdc(S*?i\'+xH=,GoJ} l숏"D}'!e◨#;d6Zqά"~?v|$#)srVЮ*2B)Sl.WJ=ϵ#zU7I\7x >B(  HB -Ƞ -` &D(1qcŠZb6"yBn!"9Mlsy쑗*yX`G罖4m7a/JPօBݫRW ~V.zTm|sv#d.` 8C0D -1C4 2(:`E*W0U"s;q,^yW0yLj&hELr1^q#g%Iq\ӴO?w( dg%i }^q}v+{B67>z^wp&ITYʴ$ET_ssC˥hh/m)~bwnž?")R VzB]PJY03rcEC{wo~TO\7x >B(  HB -Ƞ -` HTuG>JcV*35Il4a$Rgm+H"tەgwN|umi_& -%ku H$~380< kN$Y٬$M*{<"ɹtSQ=߂H[v|u_SɖjZY)9; \M(JRΕj6[JH Zt n|!QA$A4,(ʎg$W3Cg-dKU 7od";46W,gi#(ItJRu.ILp:"v:;EyY%U+*j.rnv vH> Oj畔DD.vdP_gGR"DR"yaI#I<;O@$_% ]P٣fHDgS3.DDr]E6|m7a/v*RW>M4uPJRY+LQhgW|8pˍ5xt n|!QA$A4,( MD}iNˍ?Z(QHhgM^D;+iEWӚH4? CdP 늟V$5H]7r"NU'mucS+(UR"#7_-|<E$gk.,\EqɎJ,::9m7a/<>JBVr! Q 傐ZJk-=O/H,^~@BD!qH@RD@T@L6X5dzL00´"vxd%iaJXԭGL%%ތEx6_ם(l ɣٝ||Dh& J"?OMR$S +azHۓ0DN>Ce2;`=\FM Gt_WfTK Ey\%Ad5\+Rkc`^m7Kt n|!QA$A4,( CS_ 00eL`)ě˄2ELtFơl|Dr3}R:Js;=;D6)A錞H/@d t^0Ɨv{3+Wk΁|%/KUABA_lBknvnXu $.]<p!a@b$!iA dP@ t0 `UƸx8ÍfplhL؎3TEQ0M@$wZlp@]CCw*Yקd/Bwyۋ( ]$E2F)Gț;r7 J /o?u3V#t `ycYxb nžOn|^F+ՄJFP$33*Z^JgO'ZkO'\<p!a@b$!iA dP@ t0 `du8Y~#Ր>q5Hv|Uq4FUEm5AkҷAf|S ҷIUeGû/N|bGN"9R$PlI$HR$/!P"Ϧ>D$oH!z9[E+uv[f{Ŏڎ<&셓Oִ\5RE8KRYU2%EkRz/[~ɫ$t n|!QA$A4,( y|"'R*1]*ŧM^qe^ѬzH~2H~R$d %$ %?׿k$_!uݺɟt[\/G| y8I2I,R"i2mo}зY&~"yo; Χ77cn\H1'݄P%wsԁ*3zY3h^&h"HyYʕLVH$?k7Z<;/? !C8$ )H"H *h&XPF$*,тzH#7ʍ,^i>"56u}羵(Cɭdv&J$O$<2zٝAw;>nIN N)%|""eR$N[᱈"}+x$W1&:>toF/c'݄ed,)hMByRQY$gVk\9W+5ooˏ~LKx!AA"!IHAxAPA 0"ذcݘƲ9@1ƇEn!zHq)8/w}Ű:-S2.DI$HHDv}Ry&7ڜ^$D"y%H~R$? \E&wΡzox}ɩ[/w5*{H~םO_H^E57>Hk {>}kRYzECԒPB5䔬TK9ulvI\7x >B(  HB -Ƞ -` 竌o5bx&xu %UDr\[(VILuPr2rΑlu8%I%Yx`Y~ %zXB(  HB -Ƞ -` p"bգz>Sf@ !%,EĨXKPa]l"|2tg )ER@O`k.>je~$|2ܷu1 DC"Yyyٸ$yďd/o@|vo8݄{ו|E*ieuY(9r%Uʴo:p{ť Xp B ␀$ < -`Alqȸ((L4Cz q|SR.I2F<$6zS7$R'5l3;Dnwv=l׻nGHJ$v~Hz`3JP$6y~DDse^l՝O1ʼw?5gD4ntVmm {aSԫUֲ& -F[UA2tjLITj~/Hmƿ@"Kx!AA"!IHAxAPA 0"| -yxO֘9!<&2>zBづRŴďE$5Vlu#SmH@~J$89(om5ze>ܿH-{&%# (} ɂXr.׾'RNa7i[wE6͉k~UvDl_TCԝOѷzm]H"vBvsATjP$H.lAjZuU3U=xr{x\7x >B(  HB -Ƞ -` ް8HB[ؠ2zDG^V6&2ZVDFE.DVJzаnsH&}YH^CwXבEҎW^ds7SOІd!J$M2L5CϣDoNj\#?!Ukɉ"mA$_:6]P%e,/q#VzjR RQh kE.` 8C0D -1C4 2(:`EaP -A+E4F$ -F㪩ọomfJNd}Z\đӻ'zn{"'Dn%஫zZNGIC=$sA"oHM9b(i8֧m}낉&k9 EGзcn^x:}zE3BV(kIPJE(T*g5TQ Vwotk-\7x >B(  HB -Ƞ -` CILyxq9[ߩa-bD4^tXʍE2"k;m{Id28)Syy9GL>s.NasϷL"rF)"'CJ> 'NLa"W@$'oWG8>;HF"߰ѣ'݄#ՌƔrPgBVΔZ)Sʷ#o5l{\7x >B(  HB -Ƞ -` q8ٍR !)oѝCd(EYQL ]7 8D2M-f$7tEoN)P$;[s;E26)TI$MdT"[o C\zI#',mAz`Y%{\UAܧG7#k:{}QMjз>n^xҽJU)Sk9An^YkBYEֳjY:5Tz$.]<p!a@b$!iA dP@ t0 `/|c,q7RP a5B7Ii󶈄<;o:*nJPo= %_r.l?t.E*)P"9T%?$EwǧWGJzw47cJ9<Uy'T6>C})ſ7Ykܷ,ֺ>5m7a/Lʻ6ت2q4RE(lմ& -]7D.` 8C0D -1C4 2(:`E*r1^çpEЂcBr،zLml0!5.}dȺc4Ur|RfwoH'LR7{vp=\q6ȳ/HF$K -|DJ$?͢WI笑ɟ'o6i\GnDrD%ϛH2dGw"и~0&*޸j9'3ת \rRZNZȳm`+m;lp n|!QA$A4,(ZsW#UI54W%,Gx*'͔VQ%RZ敔jj ]$7Q8234$ǓHuxc>y*y{wmn}%ysf$={"?.of7u>Ϧ0"Ҝ 9'rȟ565U59m7a/}yNS2(b,YHH\jZJe˵֜w^za`/j€ Xp B ␀$ < -`AlQ%Ƅ-6Nkw*)5&\Eq<ƒVgY7 S$]#EnO6ɺTɋ(~\|nt=)_x憵WID"J$C-:k2|R%oqЧw1%ƒ#9$wt[ `G^|U{M";0|NM kJ\\MPlY(W-jeMT|k# B(  HB -Ƞ -` ͚֫G>3Z@ Z䘈H*C0Ki7(HNqMP"pf$ÔH."U<2SDrQaum\ǒ;%}A3맔]@ER%O"#w6P"@^ù%4lɓϩ"ͨg_7Χld+;*qm7a/<^%լePJ$hyTɼTת΋YZ߁D.` 8C0D -1C4 2(:`E*Jc͓J>SC0ѢJtg9ɧtq̏%cJNDr6qi\ߤD2C$}U.VFe/&My$JA>J$Ϛ#W1?~TyKu<=Ըg9dw&C$/;Vw>5XعK^9c;_9&}wRzMJEg+QBYΖKeo|3[+p/? !C8$ )H"H *h&XPv7I͝mh[UepYixr/'''77/YcL [7mJYW:'el:ED(@`J9/Cڜ$~.C_q$ekEQ@ii%:3%J -+) |+yGm%`Wɗ-\:ѺKOp5PiA<˕ -"Prs_iK(ӽoK}JugBco%;=!Dɳ#璝ҙr-1jVTVa9WUk$͕Y+yQrnթ\I< ~  @I t`lPڹ%D6J14fuw,`Q0tےJ~ʷw2sp]dLp[2҅:.\kt#팤#Ϸ7v>~yU0 oyo~.0r #OGl=:Ty,7Hȃ`qɱ k]v|̒*Jim$etJF&]QuUY5j-UYeL%8 y84( ^}dAaQ`A( R@P ` [ גc~]#A'G"VF,] ME DGXeO%|>an"0ea.Yݴ@Nڨ파o1rI4H‡(Syn7g;čCm[JIbdw1)y3d$ac1r$d$6HrLrj,k̘Nz#?#?5$ |  B " -b` 8H$A -h -ЁLPA83k ^9;0vDvl-IĊ{r:[1d70O#F~)0c|( 02ߨx[#ÎE%n07r9%/ ȼ02#0ȱmfX)) 1rq1gv܍UiGV`t$[ްmxȕFn۵\tF-GTШyWSF}\W n仧^:P 舑ގнI5JL/NW%t#|L#YZlz#cv: -V#F #1.GFg7wa6wɋ#~aE]iqΐڦ1 ?2cKCۇG\#j-qڿaNPdco7 ?}tiNc15afL&8FǃxmWrH""*HH)Zh)REJ"EK)Uh3aO9/o_;yO2ϴ|E싶Og5~Ba\q'Hʘ҈oƍ/  -g}T)V]+M[*W㊢HWM8Jѕg kpQfsT-jF,UQU%&'JRFQҸaHPKzRQM$MъRRMKIX0JJ2܅|}{iӦe8p ? !C:DA4,@rks.h|+40F(#VG[b3fn2mٶ\[÷u>{N~g/{ntᗵ_K9?^O)LП:?S{r"mr{~h[mF{fe۲m9n{I\:'  >C0D:! -1@PA 0 d!y,Cۉkq!Eso"$8f| $6s8ȓD]LX@)4ɨ~F,FG`Dux[OTD\FT'>1rDTGEOj͗y>iRc$#Гv[OR{z#>}RHHIIew Ӿs?1srSz;3nQOL6MV+U-iTI@,Jr:Nw'4s$ .px NB AT@L YA"ԊgƥEk(_ !3lQ!ˆS O&Awԑ&r7#ߡ<&xF"P7Hn#yҷTǸȜ mTD%:wƙ)")ޚ_Vw~$>g1ktE*-UVn^L%RDM-H[}п0F .px NB AT@L YA쯢; Dpxs_G?{~HI= -1'Xj^tșu'#飑|dԑ_z"i##2~=B$4eDrVH5L#)3F ,^qrO$gD"ӱH~̋09P' 7=so U;\,8a gneK;;nяnIEղ^e]lHJEJzEM %2*C0D:! -1@PA 0 d!y8I \;!}1, `Z^W U2hb/vFI#:$HH@O5473"yO$_UTgD'D s-HC$ r/Q%glB$ݩrҠ6Dkt+Ǘb 7#NCwD$GEOm <ݷD~Ō5Mn:Cɯ0\0ݷ/rK!Hdv(iE"٪s[OdڟZ=ѷ^X;|$޺p FDˉJ2$%TɊU#Yєjj4;0+c4ڷ8n^! Q"Ƞ -` d 9ȃU6Gߪ{-_o"f(1\4 Y9eu"9 MDڷHd&\O"<ߧOsHdy7[#9FJ>}bO$1"(^:롑CDYM5D[GXA̧尹~4=HF\JJHG-I4(7*/=oY@{)ُ>& -菮@Z* -3,NxF}溛pWMbxXSN- .&4;܌Q{m|Ot .px NB AT@L YAr;q̵:L⹝k&ӈ , $4o'9vZo'$K8r7Qgm&ѧcXHh'#WҰ&a]Lzz%vk۝~DX˟6sW:Gnf# 뗚s-ua= r|Ju>mS碫oAXiڟZY-pq;\Ai}eqM8nJ룴j^)K)-Q h-o9YRӥb5QHѰ@;8n^! Q"Ƞ -` d 9CSq'罃}?Ƞ̈́v\,/|aVQ'NF"ד<~d"}-)G&O.ej?ov'Dc4KNI1I6u2&iB"OȀUn/=,F"+ѹ< ZH+ Zenmݫ&嫐E|5O1D&>hZ2WRYQ IKRZ-HQW*z!]!{CEhq7x/B(@dP@ t0 2*Zת5O׼D4zH kjb͊o2yZǞГ5u-$ vŨOۓ>NB"D^\bgup6+Ùr<\~M8>DuLxBJRR4MwxZIUE>#Ke^.\!AA" -`A<_ec*95=xf~v[H3H`RpNm<۽KvM'.yDGٵٞļX Әꌘ.44=ҘLj[Φ:noM4?gLQv(#ӜIRV3zZ]>(+ F27v/}F9} 29fs?#Dž݄cyNŴTՂUMX*vƍT` iw 94tNp<|!a@tBb 2(:`B`) 8h8~AFDŽƄ,;HtL(L'ʓjU+cIhk'c顱''2'G td#m}Uφ֧[4?erNƜO;a,^B*_c-Q=?ʲ`~]Νۄy#?~TXVw~?7+[zPդTY$ `R1YNU$ڃ挧oZa8n^! Q"Ƞ -` d 9ȃUrݖ'Ûg#ra6T0 1#JNhy=kDMݒcg&ݾNh"9(Md;Mc-$r״P~]mQ"?S~YɘU,_ukty6ݗwQ'B?7urcHu{J6\a3#Hg>&D&1K(SJV٪!)tVj}naVӘtNp<|!a@tBb 2(:`Bp聒C9"FѐDZ"XFl) =oHDH=4Si"1j[x5r~SD'rM9Dv/l_;-$rs= L 'gen;ȽSțԹ3^|۴?E"i0?#̟} . G?4tTj -tJRw*u"x.H\:'  >C0D:! -1@PA 0 d!y8H][(Hz@ !5޼.FXLPDQVQ4EW T,Ѿ3fa'eF"5:|\O"HǧO \ӕʯc0F #yH1"Fh[@#L>}yﶰyLbG79 aEP$s"|O:+"Yy/m?i؉(?t/U} 5MSBH邽y@MR%5&b\{7{xy:/ .px NB AT@L YA쯲8;}3Ľx~ >E|; l#Hh' @"[]Kbۉ(/u+V &b Ma 2')Z׋h4ΈBK{S[_˘j6/ؗ}݌:2?-mQ@l^`{G&sτDZ΢rLS;N 2dr)ku7O$MSjR-IeIM'EXJjPBӺYk-.\!AA" -`A<_ec2:5]LGxၩ)᱑)a8S 4ѧc e/b@C`IkJ:!OF({xL8SG C9IF }S_@g~BPk!?0Zg.@B( -%TYj3(nmY.qw݄tXͨ&*Dm(i^ ՞M&t-4U?]OKpzzKi]u:`?ƴ?E p;i_A f&6ꅂT%IMŤ{ {WOӊKpEb/y5Qvuў#fbl!Vb=E=MFZ%3i Ҵ4NFZ/oa~}m([1C^e4nLk'ج}뷰$L8u e=V_svijV; YnPqju7O{S P*R2nVUKvm-㋨jZMhFwx5:V\:'  >C0D:! -1@PA 0 d!ymX"vZԆ/F OZ@^G"n#GIl!sDEjG01vsǑ>DMJӪ3:>kk'g.ٌ-ֽ4o2j:of5~jVA$yW;sg~CZ?ijqrb O}ѻnѯZi2jUMKJ@ZIEdXi:c ).\!AA" -`A9Xs iX3z k֧iX/a 0o!ǵ5l(O_ϫ43J4dz40šn!ZFx|)nBX7_.;MS^YPZ=wãu7/R)TƧT)WIKKR\IKtIJu\:'  >C0D:! -1@PA 0 d!y8tXY;IȬ`[k#(:Ù^BT^[լ&aG9{ov-)'*xIJZݮbW0$tIHNC׼藴ť8p ? !C:DA4,@r<@IXB k$,%eķ2\@B+Ix,}ŘK(h+XFqQ@иG&͎t5m}Moht?O76t˚w:vinl!` 16}k -{t -vZg 7woڇ=wCM8ݮj/)ZʨHjUAZ>XJ4-T˩gV=hKp:%>H>͈U4gDr--L~z<"]lO?:6t쫈dmD!i_TwtQ5aKZ4MIJU4]4DATE({ .px NB AT@L YAb.D]Ļ& S֓вZ2#ۈQ)%Z]O\F\ik.kh\/d5B\5]4R<{7q8roiZOiiZOfL[X?9qn[t'Snijqs2\61'vFr)' Ǡm>NŊIt,ixZJ'+))UT*j*{d9klZEӊKpCζey7_ '=QL7t HJ8F%^Tݻ"ko{ .px NB AT@L YA(ql㺎׎R*rVvZOO v\ג -FjeQ_"fo#nb>sd\{9Lfբq=_Ѹ4#׫|/ڑo8:l-FZ4д>HoG<=a뉦 5'яwNҹ#^4O-ho\VP\?@M8Os7P-$ ՊTޕ*$SR(+jP{{}C8Nq7x/B(@dP@ t0 2*c&9zLN3ISbӈ0Y)OV&#YDaL_6 -gi8%F8BlkvZw1uhnt-t|Χi:c'tZ_tٸY/s z{|32O-_pH_WpUwA:%TDP%H&miU.t͠]{w7\!AA" -`A<:kFeҹM|#SSCS" щYv2y:QS5gc9XS?vNΏ몌'pΗΛ?|·4h8pFZq鸿܀;~ҩӕѓ6 nй+S/3O_qm޺p V"YI]B1-Ij!UEuO«te .px NB AT@L YA쯢95U^'ba%b"cjʲ&1C1uKd$|2oz Hfh$z9j 6hfvٷF1*Ti"Dy={M DhfǶ0ʘםp@pO\NCvM4m\|{co'E4(?yUwSjŴQURJQVR %Lzg<}ӏ/-z8n^! Q"Ƞ -` d 9o:~)qm=F*¯/XD ( j[֞R[E̹ZM7nF-7"itgQ.m>!OO>l^6_#_i]KiBѦ'0R3׽>u7Oֻp2&e/< i&OiIzI** mIB*+ U'TSnw_-.\!AA" -`A<_er퀢vs;3S|3rmmt\xdd:.:96E ڻΊlL#F0B2n0'4 Z,ϟZ4?`4w`g4`[( f6رpID0o3't?w;eyi`xM/"݄c<ǎMI-.H5IqE/~uY=|Ghť8p ? !C:DA4,@r,ı.;S{{J|QNϐKXuBb\#f bDW-g#i21Қk!z` g/,~Hưf^Mi=2jpl4 ?k)`qPFO9Ǵ?h0ܹR{wZM8eh~XHr)V̐*d\JhղRN+ #{؎/2M+.\!AA" -`AYAU_&E$:Du. }ˈ(ŵy wOdTI՘BFHO?޷{g눺C\omTAu0*`4ZAˌ^wd  &r2'ΛcȦ_[Ѧ)Fwy#Ӊn1uWP\k)^j"Sr@}FB蔦ƒ0`_ rSnsG- 3.w̴?h;2\_9evu7r|W+ktj$iFِ ڳGj<}H}l ;¥8p ? !C:DA4,@rphkx.k+qo I+o=IWw;<}磵DHDykl!)Gٻh"#/дiMѴCl! x)-_H42ozWz?S bI֋i= .[sg؂3`ڟ"9Ýs@Z/{&74?7%Qrri )^ʅtRK{ev]~лS\:'  >C0D:! -1@PA 0 d!y8`jܽp=s4 8 M OL>NY$6YMYDLQmjZ6ZwFSΛi:]MO NF:B:o6`9~F:NӹuXUGTUӮo79H;"yP\KMN#޽gCn1/jNCQQRDd,"d%Yjq#ٻ՟/ooKp7x/B(@dP@ t0 2%uś^gkT88,a(YTU-#covi"tbz⮛s[H>h(!Ϗloud$.bDJzPʏOj| sƔ;tEDr27=E 0ҟ'^fK>Z{А%KˣإE._ V-"+f6: O9i$gbF$0g.ٞ'b&r#W"1maw3Zo0.?WP$bwOHet4>sAɝQ$ (oD"- G?LTf/.$x5%'*u/F"4tNp<|!a@tBb 2(:`B`XuKs^*_ ĂP8F|#1]EA)i1=j(f/[}OCD;KKHq-:ߨF&Sy5M{D2<1-$򿦳5mjkٞW~\{q:w|eMS8@G,w۸ ݄cж63_N'^R5n&fHr2.Kj0 -J!ٻ -OKpE Cj1SDS,R-獡HMQ͞T&][@ُu| \ |k~^}$<1sɧiB#Iޟ9a <^uJzU9uie/a$7\f4$ϻ&*zT*$TI%QZ")%JUUBߺsF$.\!AA" -`ACO_+];wHmuHP7_[X^PEC64]6 SbDĨ_uD`Dr=;{"ykO s;/=omH`54o3kcZ%#JȈdyæ71+詒wF$gֹ7"rUr#h\c(iuBjCM8lJF%YTŐb"dE**J!njJ@$F .px NB AT@L YAb: -VJ"UVU)a -+R5 -B<]-%7c,`EH#Kpl\</(/_+@FO;l(O-T-ױٍs#7]&svCv̏")*Wgs!3w݄?L55ԴRVeIk)d艢T)Ih$R}vɭE.\!AA" -`A<ڌnpg<" ЃFHgk>J(Ӕ+L4<|9F*9F^HB#9uxA UnG5yw;Q4Ib_#UTjwi_SCU-;ssHbڟZ"Q%]fknQdg-KqkՊq~> BJtR{,._޾`f˽ĥ8p ? !C:DA4,@rL:5=x&S^ L#$4'׎jY0 8NL9SXkzÇ+_el(vF>/ -Y ϟ}1yo#_hbE+`NX#ZO0Ku@0,Ğa7/Ř_?k?\yYd w΄Ѩ/qEsn1s6**TR֥D~²TLKx2%FTђrw^vƜpޝwNp<|!a@tBb 2(:`B`5XMQ1;BphbA⥩lDM&b^GgF"chI`\?¼#WJ^M/93룘\ɘC3*thP[[7:6TpXAz"!~s⟡\ q; wKxH$RJJZZi4%RɔO+_A$wN8n^! Q"Ƞ -` d 9C%s qm ij6 6}$&$D@N\CD|țj@[џ!Fb>L OTq=~~ܶf?B\Mݶo[ ~(i=Q@M:n -萮@Dn,D]Ikju3o.i3O-N,r׿Xw,SbEZUX*$ԔdSTIWqu(`ȹ~W%.\!AA" -`A<:/~+c974fmzCNE"ƐP3 \H$4/DHfs쵟limx^<g}"GD~nΡfZC{`$s-L}"7Z.~#[zFwlG"usF;Zʼn@"RtGqn&x}HPH3YHj˒.%#d*]*tB}"вD!pQ@'1q7x/B(@dP@ t0 2Y{t3Q-& -y1#+JVU5Q Y=u!#2M:+ 3|֞Z(/F1vQ@i{#>-Q - 쇣'6t8"Y}ZN+m@$/nڟMP-HF0߶&q^j$ ~JTLQEQ T(s; -/*x5b̲bb IMnnnCf] -cš62uNOKW똸n "]R1Qc(1ծRd αO? Ҟ&E=d9P A"4B󢤙Ӛ'4FNby2N$3FrNs\RVp+ܟ)-iղ֩u*>zԅTFq؞bq:g1c*q_B\}%ܧ_+r_^ݺ9ťvgnh wbܺkʍb^ -Bu-yy^D\fIɐT/P tzp{a8sМhATtfl`@-8nx"HD!ii=@ڔS>:;M]n [dߥ4"v׫nwߓj!iW"]ij7m(ySzZϵ*\UF1[aS_eGF'㆟5եrR?wQLW99vC O!5$;t?.qx[X+f`[3HglaK\-g}@A|W?&ZM*$nMtoSJkGbKkJZ*aaiXFZ9 -7]j*mF=G*qŠY%s,kZxt Jz\uy c߾^uoR1'o(]ߦQLpb{(cq !33^P(R%)9-b -$ -__7ol}?G pP ZЁ `VP> A"4BF,/}3$ rd̓id"8.=CY9x>MH HDJڏ]iz9W7ܯ~J\o<ݦr9e5F玡xf_מ/ELq lrH܄ZU%qj-qڿaNPdco7 չ7q<4Ƙc103N&dCLs)ZjzG<"RH)"h-ZX^RHREKZjϵVO asfٳgOgp3ޟ}I 3'M.L,^5qdX4K'™cKbe% $qR5.˲xĒ80qWW'W&YWO=P=ҳ5ULEV9$FǓq94>LI]=a/i-jINŴKtBDYQ% |e;[4t=zy 8;8 <  B @T@L@rGYJl5$/%EĽx"Kw\@By$DgB",$,"'DGD_H;d:|G?6qd?G;['?tߟx~?xnߟy??蟂O!1?,G;C;JC;;#ّq{^|:ϯK~׫J¥[m~|]O~נN~'=w?}kyk=/15IW7]wydw]2#ewGmWMJ pxZeN JxtƝ6Dۈ w'd׽kmEet4hp.p? !C$A42Q&YY#G ?ftM|SӂS$2DHl6fr:(jѦ}ٌA3#FFgFFUhFFF62|66J3ad'33z=?|ncd[ʨMEbwHx˜zFO>uƝ>Zdo?39ycor9/!Ϩh]PZQWR$VF UML+Ū,(j:)J!N3z;|ǟ|zFt\x~@BD!H *h&d 9ȃET+[eD]FZN6KΌ\^4b 0T˨ʅT!Lb -RuYWdI*jbxQ]ºqAZXt\x~@BD!H *h&d 9ȃ$VT7vF~#qm!ijx7J[H+Id'Dba 6ZaBD뉹si\z3-^NjWOq}~@\/UY?RfZobV?^I oiZOam^n'=6U^Xv k喢7ַU 3ܐf9sY97\C_XUeZd:ObB-DU+Ă^JE54/EUwŠ6 '7x >C0D -1@PA 0!YAe jeup}AUZOrBFA-TDUMV?RT)J@ z6^|whAGpvpxp<!AA" -, &VA}6%M@\Fyx7!"Ht-m"v" DJlj6n% -4z F\i\mqBqRU鼴YA]וf.ghZ҂d6Yi=i}s;zvҚ*#Ub'mBA=E,J/IU]>vE8iɖiTMU+*2_Y)R ҕRZǿM/WiZWܿsϽcmANn|!a@b 2(:`Bڀ. /B(@ dP@ t0 d!y~^ ~#y[3 ?ڽY7g|gȴt[Pt2X4Z;Z_tѤHH:IMꭴvf$6jLgˤjuo׎ʌdi?>HQm$MZ~E1 1>6Ө!eAO~"o=jtRqRHDUQقRS\IZ)Ѥnoy}oviGpvpxp<!AA" -, ֏v8Y4&Ud3ѣ&RN6P"+w(wtַgl >_.¡_ JːJʥt7iQ-VbZ-j9%REUMe;w<2t'>ڀ. /B(@ dP@ t0 d!y~>bLN2q]Hjq!IO℻>FR]FR}ƅ^mn Uh. >rg}އv wzmFT*VSbXIȢ.SHr% thp.p? !C$A42Q4[9MaQn#xM_?<thM!' 35MEG5Fr5Hl䧑|z$H~Hq-x^tNnDi"0ZΘHkڀ. /B(@ dP@ t0 d!y~ݦ#]qNϹ4xHHH.&e伢膡gD*DN DC{Z$O'&rO{"yyu=D^Iy?#DrUg5cF"F"/`A -j'|XOﻑKkDַ&'ጻr拋6\Ð78KzPJc -HdBIERDNz1D>Dnֽ}nZ#t\x~@BD!H *h&d 9ȃ HDZLb.ݭy SZ@ C"bYAbPe銡3&Rb$,Z#Hh"nojLyAnb:,/"{!'}Fm6H"bDF۪Mb﷝l8̣H^_ZH{4.D,=+39̝N`Yp,JHV)+bBdZ*V4ͬTuղ坏?]4hp.p? !C$A42QH ?ܕqH?'pn0[d&: E2+eM1QZVѶf-N[z$c_-HK#jH2"FlԳumַ7FFE'i$=J9p!HBdb&zɈ5.rNsߵ̰E|_[}y9dɆp諤zcdZ -HVb3id)Y( $exFmANn|!a@b 2(:`B<[sI24 sj}7jmEEss\-#9ⳑHU#<#u#$]`Dڈd"93fUr}G/4FZEz$aD2Nd`F$OkJÈ;1Ѹل*y}v_lm,ONYH.ַ%'_Hy/q=Aoaqu$բ^J)%j`E5]EMM墒 UEo%D$?|{ͯߥGpvpxp<!AA" -, {w(9A$4GJ_ HA"JTBLeSTU0JʦHvHNaDT:JD4qF$o#גe͟|׉2^՘ȯDHڷH$_POD~F|I (~^sGʼΑ7!(QSѷj4\CȴrzH? rYԋEYL)b,k@ڏD|{Ӛ7i"t\x~@BD!H *h&d 9Qrؾ"uw w[FaQrX$D/eQz0QM&:F"'7ɁM䏎'rxupg{cΫ%lmLi"gLy7k[7}KV_D#yϸ'$Q$z$?;E2F$37М9moݷ#w|= M \z#}Ɲl#JwX\y%(P2m=B"|z!rRzZLETdALոX'RZwd.^߹SZ2t\x~@BD!H *h&d 9ȃj&s+UkY+3dž tSihb>ȓi"yF"@_Ohd﶑ͭWz.Eɜ߬=syH~Fیr&댒顑C0D -1@PA 0!YAe-6!Ĺn{Zf#; wId~mچDAD]Tq94w3YQr#JY~0hV=w5FrՓm}VTy0EӚBw[]VʎX.IN}{a}.k3grCI7pCwz\W"J?aଖt=SDj>\t+>lY+U|]`8^!QȠ -` B`(= G9`g0xT, L S"3Ht2^)JګMыk'6;iF0Q`~ѿ`'ms -,ZzwB9i # gwcg`` -#7QC_~5/o`^\15tzu*; 7l 5`E8 O^Si5^U$3t*ztGD5y1=5sͿGpvpxp<!AA" -, rNlЙ18~kA1CZ@3*:XS۶Hs<(skG!ĘIAa9#*4ӎ76zCE"T뛅y1niX`̠аKhx5A'FXv.]'19*:ȁ8ܰ51~3Ut/n~6պi'E-QjBNբDYE[X#z?3(>ڀ. /B(@ dP@ t0 d!n2j ${ƑqGLѽ่g>E*dfX"kO,wy$w^E8TZ?J 9X5~MT-kB"^yΏww}$>ڀ. /B(@ dP@ t0 d!y~,MGT\[^ӗgڞ<#j{LT 5ipCY8ɳ<>`DZ$t:Y6:Zv{- C0D -1@PA 0!YAemwı8'vZGܛg;D|o$7[c:V;-o#i7emXucWɈkVHG-3jX[V:{_<~,3hO{ -}ПӴ3&d$o =i}^3/zie =jַ&'M+ -h_tG("M$* M Q)H&VRD"i7n~{t\x~@BD!H *h&d 9ȃ P=Z;Gsg<ü:zaa1lԈz2\5idM*h"|&R ?_f$2'iZԬ=zW4h$_dDg4 =LVZkịj43?Ř'h@RӃ%TУ.$uu -oARַ(kpO'gܹkv зA,Uz"iy)d j<W޴&Z%1-hp.p? !C$A42aiym.;rNW]:OV<!jDAzHS2bdg#y&䇌vՂПKsKHxnYAnq:~,yv?{xFg?O#F,1}>w{kď1}[@!k@#Oc[Fto-7oD]&CF"{Лz"(GtUMa(pѷ:žS/C{psa}krpƤuHЛ>p}"Zkx2a Ք rE,*鴮%M҇"9sUOnxzU 8;8 <  B @T@L@rDs\"st_"9w!^;jdXdx4;ՊUOZaɣhmL;* ޻#6 '7x >C0D -1@PA 0!YANbYK.íHJ$s/)JQYU$Ur]'I&Q$H.ci"mkѶiHhVMo'qg ia$2O'W=gZ&k;n@:_rWD,!_^bXߢm]~6ɯOr жnp}"d1QTxZ,jZAZLib1(RZ Et=tۀ. /B(@ dP@ t0 d!yػ:jDC"$pG$3H8.!G"UYR ͒.a4${`"4iڀ. /B(@ dP@ t0 d!yIPsnvWf0S۳E͘&ȒIRR5ͯ놲 ;qUkm\z̳Q$stIrLwF{Dȧ\'ZF"t9t{ϩ6$dNa-ɻǎEqٍ"7oMnE2ܐr͛Ȯ 0 9\$Q% &JIEǪR\%]S07HnS mANn|!a@b 2(:`BC0D -1@PA 0!YAE@ˎpFaRKddH%| -WURk[f{`AoжHo0V[hFvAZo5<640"iIr:HHcdxdIY7W(Oڀ. /B(@ dP@ t0 d!yͻĶ˪;}+ql$ H\!yxWN֑+VM"Ht F"FDE;R=3FZm.2+iڀ. /B(@ dP@ t0 d!y~~7/!y93NIwZ]ϡ.|~&4:.rt}OX镌D:7hHz}9wurӑ/t`M-͗g8,w֢H= 0lm@Trzf`zPDUj*z4Ntt?VM|]`8^!QȠ -` B0pЈaWs8sincx 9]|Ch&$2W;y\$ zz Fn52H4?5r:#_l#q6+o9c;xfi$Ĉ6NN@$zh:vEc`J0TIUUзưX=ѷrO#3"64/C$W7-wc4XȈɴq}w3")MF$@$oKѸ^Qjo"OT_FzV$qg9_ и>"uTA)E.jYL%QVUHֻbi`v=;mo˴hp.p? !C$A42Q'ֺ>Ĺ^Io{cIcHGib~^m -"qO}猱H;qg#}֫7A]24 #~:"3JJN$K*m"*iXՊ%γ܌nxOѴ 8;8 <  B @T@L@r1amľ8v mDL W=|]`8^!QȠ -` B`(lSթÏsw=Sc|=)q{O<6ECrYUkS4sB['eF& |F&Gцw*dɋ2r'29N<_boKx;f0fᖙQA3T 2t89ÝgB&tƅWxQA/а;A{WVE8 'B~/%d "2ڧT'9Њj|o7 8;8 <  B @T@L@rEk;$xͥ5T&VUcYWd]3LSl"yM$'Z%;z"u m 74 -/w#y!XF,HhjB#ꭷ?hLߥMN@p[o"vG.NBcoC$ַ(2ܐy2"yIꆋp#J^MTr)XRVӺ/V*JË4_s{I|]`8^!QȠ -` B`(Ӊm& ))TW=3cE*EhO(G#4GO3&㚽yh#i01C0D -1@PA 0!YAjo\!ǭ\- -#% ;N⽇!$0ﰚ$r7#6y Q#=@ĸ+H7m z’C͕ci]FZmm~F쥡HɌtZE_oylmTѳ[Hc){wM'$6VYDKYͭ8~17ݽ'3Ѻ:ԁJz)#w~zY:}f!yL2 -넖3lՅ}M@bSoxo]YE~dX"$(_ްp憋p裪~AT(UڴNd\+ 5YIVҩt:IQn}wmANn|!a@b 2(:`B̽ǹk;L{1yw\ #f|ddtTl0Nܑpm.#Ͷ9u9FFucX%fcwgmЧ~H|z]SF${ [3\Ffq_OE8 4Y1J'|RAR55Tԁ+ody?}whGpvpxp<!AA" -, fVft>E0N LɑhMz) ڊ- -t,W39G2y6MgUk;t^y(-|yuN;-%c힥ꅛ6ҹ&m$N(ί"[وPPa,;EPE=QUJjE&nlGv@%>ڀ. /B(@ dP@ t0 d!;^;8g׬ڸ7l[\7ܣ#ڸ9s٤vӤoyts-#h7!N_qDeԦZֱt:0ty%ȥݶ@+$Q@ A$Hn!%.,lDc!`!NC <&}Y&|y͌NC]֍M@Ӛ{.ntۓsژ^ևu h,c;DhZG3ig&i0=9{-F/ku5iׁi- nI&gi7{3t/&K KNYnB:]LH/KyEzmPWr˴Np<!AA" Q!q@ dP@ t0,zXF]I˜UoxjL}GKW>#DXKĕ*&˿ٴ]B34#1}:0b*Xŭ ZNmRӽ+U~ZU{0rA%\C0DC Ƞ -` YcD1wNcF*xe_/_QD4d:#UAu,pH>yk}O/o tYIFr>#Z-%AƔ6J%HHvO X\4S.X5Ŀ)LIgqG5t%b$zEV$Z&|*崒窱D$wط -$m^!bD@T@LBhՏ4ʹ }_ -A=6W"Ō.D jzs[;V~$gCsFɫmD,iqY$C집Ϩۨ+^gWɥ"酈'D$DU`|X~E$g2Ic7!WXV4{pȔ'Jr"JX.2IDT/ܞd[}9h\p`8n|!a@'DA$A4O.X! q"#{i?U]:NB>b- 0+D"q"}I䟉2@ԣD5=*1@oG3.{iF\f]/_EA#[ЏhmfAS1`L;6c"HsӺG!ZirLi^jx VZj-qڿaNPdco7ܿ չ7q1ic̉1c 8L&aҘRJUk}GDDDH-UkZBZZZk-~VkU_նVd' u2gV>޳g_ozgOKG=m4|iR62tviYӤjitZ6tixJ:MVO)"3"M)M>ZyҤڴi3::F2)CWVUUjzb*R2DEbz)gJ\=ʖ3RkQkRNI,L*SrJFƿ҉\?'.GΙ3'_t.]<!a@b0qH@AT@L<WYC\^G|_ E~_^pzl}ǵw{CwHw=ʯ]vsA.߽w5@]ݧ~gߵ8+EV9-^Q·݄=U=ctͪelVjt -QYkdT3J*.UR[V/ 4Kpxh,s|Hj%M% -mX8s"PDNo&`F"D41F"o䉼]y껹S$?<ɋh$ 4HwH3 -#tҌ~t>E$CKmKyP>~&HfS#j錦+RE%M˒QR^+ -`H.?y=I\7x >B(`␀$ -` y(@"!j>v|XQ#&#S,DLHjs$ܗQ$/Er-#H31"Gr?.jdMN)\{Dfd?3yۢf"O iDC F"\n76,,$P$ _G"vU67ZyE;G9|&"e&R/tj JE2*KjΕUZm5޼_ \Ի4tn|!Q !I@PA 0P"8_pH1/ϛ~=͐.DLI,$Ĥ)(ie뇺 1?E짉Lf"ϧEAF"WP$79"N"{F$")"c^8"AJ_ geRf~Z&LFL0|۵sEc&2y䓌LΠ_5?er:h\^39S&%uЇh\[kc,;b2/Χ'EI!K&5}ǨU2Z.+իe%S*Kj.)ZMIL~u?$.]<!a@b0qH@AT@L<B\ y%ijxՄ_DHǑ^CbΥ$1$a83D]HED_J9);t1ڴ=M3t)RWה:N1}"o|ӵ~1CKBcz OƗٳP:ߜYĴ1]:bzr7Va|9jt -}嶛\kL5=YCYIS4׊KVY\7x >B(`␀$ -` y(@"ltG>͊A=dfXTJR4іut1-iƔϻt狌H~05pE#O#?=D/)"ȿ΢R+ճT&r WDKo&. xC0D -18$  *h&X`C -P] -){to' 6"r4SF IYTI3f;'&F"|DN>e$m뛇c '\%D.|d`Hk䋌zH䋬e>q5U9Q#ٶ~$1\C"?t>8y3J9S <&D|rR6Wr:֊QrU)Zf+Jȷ؏?}?tn|!Q !I@PA 0P"8_eDLNvƖ3sHdIE$6NN&S3lmXa6kj&#G(wXDG3byp+e׍Z(:ʗ/XΦ #8 X.`3Q(u]1RDN|~9;k:w1`4˟[6}Bkh {a]ֵjJKL,ijZʕT4j&]22i[߲O#o]*tn|!Q !I@PA 0P"8_n,,(xޢoFF -#:FfcaA!Y -񲥌Wb4iYN62,\F"AGi\H$C\pa^ܩPFkG3IH䶅D -By^r3P(gu^9ruHd$=kg6g-E"ы -ܡEnž/tDV˥jV$Q3S6l6,^tUi &o`ykӴP^~@BD!$AdP@ t0 lC}]`rÁH5:DNJLN 2Iԇ9՚s"]4'2jtu#Dz`+D!g.K;Mڋy41t~g_hC\G!F${s3;< M9f$%Թ?U)yn̫"k {wUojl]J5]bH,+eRՔ^S2Vl}4tn|!Q !I@PA 0P"8_e1q-r,$H <^H"KEdNA:YLu,%%Ěs@M3Z hCiz刮=v/j{>/||h>_w[^C>ϵ"7rj4:c?@ 0!-Go:blyQ6g?xb+dF;oYMMH^2R%%FFGɬԝגTэz\TtkWϽi`Dp/? !CAC 2(:`6EpXO`x,Sx_FHk#jLM,Zr^UMtLwNd&vF{Md+i"XOJ5SP2.?jbO<˴=>{y3v'z )]9]lŐI)NUUU[͈Տܵ I\7x >B(`␀$ -` y(@RpK|^7/ j2y+BRHS4dC)ƕ1޴;GC#CF$I7bD5H{lS"k{"4F F"iDXkc~D~Br(͑HC"\#P$OvW7\~\~$򪟴݄=*Tl)Sץh%#].Ie-UOKtN/D߲G^eH\7x >B(`␀$ -` y(@td#xE̫~=P",DK"J"Q$eA! ]C?K] 3=)F""9zD31iP$R!"DΤHf"CuĕV`̽~91:$U_u;v>V߀u($LS1mntB(`␀$ -` y(@r[gn!l!HaB"[H{9`II#OAt 2O1ȼR;ƭ1+E>~&dL:U);iTU(z.ի^O`U{mZ?q/? !CAC 2(:`6۷`w9kNO#p\ '6)8%qJ$(/_73O;'2My6~n4#7&!F"O!_t]~?S"3OjO4[vhGwMStI+4O2v`ᘂY>#0\*rq̍>Hg کɿœv\&SjIbk9T^+FVRʩ:M<$7&n~Z?q/? !CAC 2(:`6Ep:ݩϽĻ =; GHh# #$6;a@p'yQV=E%XK̻cд˨U#2cбeowώK6)"2i c+ .:I+?y~&rRʜH3gt\љ])0J< ܡ3o {> * 4u 3SHZNA)He2՚Z-[ e. xC0D -18$  *h&X`C -P竌uGVmY[b0.FFuD1i œ䱊ښ5ls5~D@9,}9Hv]\=N?x*Mیy!B;sKюvwtֹHA7݀-N̷ܕ3E$2|/nžRw)MJi%yiE2zZR2IǚWߺëkutn|!Q !I@PA 0PWwM4+_A5dM$rc+!$uA-y<i^@G"xƃo&2ѷ"[hG{ DzH v}lRU]}wt[Tc41%t-w]gZ$fD<DZ"\ÿDK! 2\gDmhiHf֘ΧhiM@$gs*?dn^MK[*״^*FF~ʕjT2^\ [V~ >A#KpxSYHBO1sxDh2Hf&hF2NȞx\EU?O0y!ݖLkzZ+aL^C2=>Ug5'dO݈d^rKKo7O1Q6W8gymV(݄=?uOf*z&ղspl,-z*WɶyU_j_q/? !CAC 2(:`6EhmQ+ M9T ":jSE[6 h_mJô>^+4W0&d#4#ߦ42#wѾ)oXOz6QHCeF)FBg:"2Sh_+bDy1HG$gH)"B$GFbe?e˽S(|&e5ej:5reJrl&]-uʾ5H^~@BD!$AdP@ t0 lC|g9<73xb`B:k!оѾkA?țEkΑLH(]GHΥEFz6HCݎ(vQHɍ_4ɹHz~u40"yqUҾ=3dYiFrs+uu=cMS(?{_9{D &&셅xݏD,KΡe`Hl=-UV(F9[jUr4tn|!Q !I@PA 0P"l -#Gj>WZ@ETj$H:g9 ! -(?74/i=YQzi$gD:*y<#P%y`׾۲RW 1<șo}ȕ~Ȯ[., ;l#r3(g|,{B(`␀$ -` y(@5N )b$iơm!#lEh!o0N,Hǡm-tu}7h"Di"F~i=$2ӷt*r=9 -cgh$_eDr6m[oEF$'uxba$TD54FtCE$/zt>HF:V鶛#Bz*]жNJ -jf%Vj^5JSnG$8CH^~@BD!$AdP@ t0 lC|e5Ns^ArK~i+=g!h\P|H~hD$WjDo {HSsk:-˪s]:%匲.R锢esj9ݪ7 ?3Ɩ֚:\7x >B(`␀$ -` y(@cX.`ƻ aUr{jqh\ǢJNh;O0;P*F$zN#kꒌH=Tɍ: -/nIQ%T &aTD;2Q%/<.T׽*MS%|͙oo@z;K&셱dminRh\KjZ4J9IF9*6݁붶ƒtn|!Q !I@PA 0P"l5^g_ E$|Ĉ*:WE0ES[յ.b,|k][dDr4W1"!ݜ껦S|/+χlijx!; 8EkI>;IdmcD֑"l"c}"7uV^bl$b=B:ŵȈtgF\q qZq|҃bWn~ز3 u}9t%V>ptoQ@;/s&ƚTG<*P:Ey.9_DM DP-]ʤR%UTFce5M*FkW4kk . xC0D -18$  *h&X`C -P,*xt2_L=fRTPe\K5MG7v>_Hs&*Z@g0f~@ND~&rcȫ4-}Yhi&~ZBG"ou!s[w$R˩HctME"b:"\~N$>^vGtF3tŐrrUUM9OKRti՜Z0F+?X4tn|!Q !I@PA 0P"l(x2Nl<94><>2.zbl\#'%Ǣ ~S7ƚ>~Ok -F"WnK_G%5Ҡ5`,ᆬ])+0䡌<[/ t{]ׄV}IQҞS57yH^Ht>8a?0EV\;!;y֕&|uhT^sڥ\KjfjmxUwWQ&.]<!a@b0qH@AT@L<WYK\Qryx'-B@BkIfDw&g$6]Dk(kz_cy;~lѸaT˺.U4i -z#{n[Ni}"o1{1 }A?cy=Mn F}aFp]|r9t+(o\g:"c#wފqrMiteӵTfӒќU6g[=f2=?l. xC0D -18$  *h&X`C -P竈xc){(Q#b4dBM&EEP5M`&Ή DNF"D~ș=$rۈ8+o*>f֒/-2"at4`ysHU$?uޢw: ħ_c|#1k {<r]Vk9CRˊS@+TJW(iLRUr8WoZտ1kxĥ 8p B ! H"Ƞ -` ؐa۝7 {&7sr !9j}JtFc hg s9K6fSI;GFw]:ё{/=]gt{YSEәdf+iB`$s4`3z8gx~Q(WG\ks/;@B^]4OWm.n1}f{(EnžOnZ\%SiqJ*ƗV)krRkuF .$wl4tn|!Q !I@PA 0P"4ĵiqg{{|K ?q^=k.!ɛ0S\H^xAlc>1kQCҗ*ѐ}ݖ[lx`A?s:s{ɉbYvcxLQFJnu^F{u35]g6 -e#\{ go {>SCjY+)TVi.lեJ-[T4@e~zoۋ^]<!a@b0qH@AT@L<W9q`gwo -??9%62(s -('ӵI1ݜ%{FzM ʔ:TEIZ-WrJ4d)Δ2}/]ؘ N$.]<!a@b0qH@AT@L< u.&Yپ9_漒v> . Ix1\CHl.&$yqs(3%DK[bӤH6ҤH=$Kd7')Eә:`[s#~Z;'AV֣Y\ȋk27 wU4A}rSGv~f|~6E~ve n^M.%)iTF5Jt-S7TiP7`UZ;q/? !CAC 2(:`6EؾZ@:o,1zxblrPΫJ#N,3u F"4h&DC"PDv^i{"_jgH>ͨ0#'!tO1N"E; ٫E;n {>n^\5ҹsҺMTRJيL&SMuF$?mGtn|!Q !I@PA 0P"4# N@<w=8x>HkHh= K"$vS>%$/.YKDhQb|AMn{\rHjZsmSR%%[9-UN)G17/|tn|!Q !I@PA 0P"x} N\gx'hu'b⿙!$4+DBOwآѽg*uqNKe\J?4att݌KF.cu.>W?8>aFN_9uw}Msff&>"',ӱ4gn3Sk^ѹ;fn)rW( -(И˪J6ct5#tZK9=\3R5$}ymN|thYť 8p B ! H"Ƞ -` ؐ*V'SĻ>FY}#$EbO9e!D@goQ?C;0%(ߠi 0z8-Gqi{HxIyO \5vY]i(Su%CYKNFYuE׫fY}l%{]璡5Y7O-N>LJ00=6uvMjf*)ˊdhٲR]-KJk碛W_7Dtn|!Q !I@PA 0P"l?Yk8YOkr`FBM*&誡 ne ;Gr_ӿ,#i$eDrY{yFqiC퉜A!c\zIjLy7zX+ިr"' 7-<%sG UoYtd|޼7&F71K.3wi˒+iJiRJ.W2ڟA{=L]\7x >B(`␀$ -` y(@25ǩS=swo &9$0;8;4G&E!IڇwIc e1QjĘayG.o0ۯЂνC)1Yᴒ}] ݎ 45F\J -N->Q_`籏/x~?\s{0+:78byEb5ݓn^X!}m)fz9YgToSYT)rMST5m>忟?-.]<!a@b0qH@AT@L<؇⻋1#{Hx=CN$lwqech!Qm=o'jbK:y_}-Ƶvsqn' uEby#[隅il}=1houNAZeGNZRӹHw iPL' -ވ^r]MiM+9926:.vO4Wiq8ciZvNd&r2KF"m>D~H$̶DS$4O#F Z@wq.z[soMM >AT5U3l%KZdHFIz&WKti潁ov}^~@BD!$AdP@ t0 lC|bcӊѽoxJ͈MƮOq4!q -XFZΑEzځӾyζ燜Ff7CQ3_ 9ST.Uk%UY\{Ӯ;>Ftn|!Q !I@PA 0P"8_efsEBgȹ$~^cIph3IWOMLO. ¢Vfa}XiWvĦICҒy.gQ2n'i/"h{>ϧ.-ђQ2ߥc>1Ǟu6|2'ζ%S;ѹ5|jqG\aso9{=/nžg\V2V1tMdv^HZ 0É[6?Lǜtn|!Q !I@PA 0P"4fv9i,U&RGHpKc l&ѧvX'q&lj#~Cc:bC{HE F\.ߵÍӸ -yN+;]dw## -MtcYphpܨ1~i]a\2T71gV"1m7a/,]WJ6#UrI2iUM)֔\`Uoi5ť 8p B ! H"Ƞ -` ؐV8ixn%ew;W5$p3 #$Dnm-1CtI^M[ˈrQ!/'u -v!^fWhZ\tMkY,|YFZS41jwim=FZ{H;4{CF@N= i -SNCZo5v8&셴v^TSYݐT Q--tEԜTKց/#[^W߱. xC0D -18$  *h&X`C -Pܲ}3s~1/$ZCb. |",$|"/ ~ 5tzʻΣљ4AVF{h7nY92ڦ"N{F'ьNbdEHFFF3ӛ?'wzKy귚\s^6=|(O7O-Ny~A,݄DQG^fU眓dTJ+J-Sm⾁>iFq/? !CAC 2(:`6EpGs3@]JzA#ҙw'YuڿDK. zݘn^My3bo:NdӋÖ!/q];EәDa{tE}~F';[1{˝f0L4r8/r;!yqE; ֶ?lQ5-RTB) C2g\Hjz+0Lոtn|!Q !I@PA 0Pn 5NN7:}mQa$qDIl-$q/I>8z3DGa&bn$Sd SnGTҴΠEu?FZ+=4+]N"#7ʈH:NY׃P*=K;w~T 6z=ͪzqOontV2 Χ܄qjn=ƩaN&gW2(4)UrΫY(jT֪m/ |UsF>qť 8p B ! H"Ƞ -` ؐa| ~3`#ﹶ.EK.*i^4 -fF4g1 -Wҿ9dH~F&wzu])O_lUF%z_wy3aQF$z'™*6N^v7<{QVF͙ fsfv|$ӻ96lt*:*ơz-o]TKzVgK{k - . xC0D -18$  *h&X`C -P$o]#yYp"&g%Sl.Y<;!M.9QF=c;}.|vx$ݼ0P3RT%-դRGJ5Sf[ްW$.]<!a@b0qH@AT@L<"G*>OBrX$r,H*dLPI]Ir̈́|E׭s㓌Hn3iFi<屮Y]gR?&F<&7!r٠AзrD'9dY9Wln^2:$ѷL|jqoDtFMEr7/'TsQI)- )ʺKZ:rV.iUܹe:KpxӨ-3نD4h`Dsa ќ0ɛ -_2c뙧t4=˳|b mx}me `V7׌3OPx9`N_U.tJ<Rwf-SͿҹGrhBOiNESHANRo'1l({~yіU-)Wolі-$$kQ$r -mdD>yOL)ZDS_%4NSFtnݻ0B9@$E[8m=-3p;hI&$߬LW -!F$Zh/_dKHCKǩ|"hI~PܧtlX"Ըag -Dr&-|Xd4S;ED`z/ө|<. Z4ǮvLB#Kpvpxp<!!1@ Ƞ -VsxeВY_-itDR j!=L"z-#UJ%{ӵQ61"9M1Zwk5Ѝe"}N/͖f> HHc \%ߥL3"vു$} ʝW|˄ Z7nGK.;H~;-y4Z2Drי`Fj-qڿaNPdco7 U?KBB:&d2 ! kQZȣPRJe)RjZKP( -T÷CC(  H @PA 0( WQ|2ӧx-$S!jx:&ՁT2gA YUUd}0LRx=|gۿ|Sid/l)@? /m/S׷.({1S?%Om?ezOz{Gm[1{{)q[߻jDҩÿ3mec&ÿ.bwleυ韋=Yaˆ/e_m/[~Yߡ|7巂o2eNg(%Gw3ujp _917J\ӻ+#)#|(BNS$^,KŪʊViz]oi$YwM'/|u!Ñĥ8? < B ␀$ @ -ҐDA4,}-wA(%$p3 n$$o#u$֑$$$Q@2a=&:l$D7c1k%h\{h\ji\=qg83⺴qK볳]FKZ/iUO1ci;ˆz;M_19;\IM~S䄧EZWU:%.s?"B*YxҚrvyZs1z-E+HfH|Z9ҴޏzP?P\>CC(  H @PA 0( W|6jR -;RԌYq=a$MSNG0E]]54S7 4-㣉܇&'Rg$rHc"';$D.\~p"f'&0y{ԏg$3o -}H1Q?A"zPyQ4}$j -"YKOϜ.LOOUfWITk FF(p(/d'4i`弞eNjʿNqߵ\1B"-37 .ZU&S(B(Cg"xE/ӹ:dMeW-C(7rv}TrCYԫFj:~RVP|GY;>-}!QAHA2 2(:`68P2" -A2iD̨p2i݌!,ʠjinMݲ?#i"Oe$ '2H4wNDD$4wjDzOiM4f4cÉ LnÉ|ovȯHdBTF{lK*$2fꧣL~ +>8kиVv#ntaMeuLdbU2FeQF!eZ٨UݨD^lE7-}!QAHA2 2(:`68PkQY$0RO"sH,CsHr7"DE9D(3YDG"$kʦtSw tt'#'Аz:txIB:ae㣣]>tjKHgҐcͽ4 eK!쓇Kأ)).3h)9 _e MSO>!=jFVK\|–NB UZFH5U2r")u\U4[0).}A!A!a@b$aR  -` Ⱦ42*lAWCjXQ#儐M=-gdME6*j.:ʦnVF:md&:&1(k%mg˯l-h"ӌD^KȦi<ȧH }nA"g n/R}$e .LS.ߺ\ -UH˦ytYr5ېj*iZXgrUZ&r1c?-}!QAHA2 2(:`68Pxus"sHJE"sIt7|+Iz 5<"/"|^ID_@눹XS?VB`OJ$h3g>Z秹2{܋a=r᰾J --2u:V΋%_8+[ֳ(g=l:z=-7aUnjFh RǨ3d0jPPkEܨȽh"e^:19w8?}80G'Os;:-vsw˧[Q42FDc$r?:|nDc$]LΞ9(ky`6Hߔ$˜ó>_q>-Hk3ȳLSS@*&6Ok ~֧yMA1PղZ)JZsy+y*MЏW~p_JKp~@xA"!I! *h&X`%p*Kc  Fqq'169d7џ1VөU:`4wџr;Y8v=$]֕xShF>VK 4+2cw1ĠU^{AfM ;8U߼gzbhy@]Շyl&\5UC)^ fVUr]W\Xez#Lѹ׮}rSu׿B&.}A!A!a@b$aR  -` 䀹|H>?2VFD4Ǧf,Xё-ܒfj}|ǠQ5oܗKi"O 1yGvHc'K֪9&F" '2xz܏F"ݏ5|c[{;TM_ԛ~Un0˧߳y6g<;)q)Lv VhhR]3ɞ&UlRUBIFGHNCy"C'"]ɃӦ,lO!zE MSE6wđp1'"rv}$ -|^yk 1,k^FadAJD;F> 0D -1C0)HCAT@LJ>m+! ڼQ$h)fViFΔEdG1ToɄۜHN#e{i2#3 YΈ1"yfK$OG$߮J>>kdF{Hi$W3"y&?Uٞ ]<H+&_{""y>8Go> 0D -1C0)HCAT@LJBsy -j_GHpאJD'U$v5#$yW@髉xWj'ڵDy'-2ib>E4)F\O"㾓;]Z -ofL}GӺ@`7]u6K{n^>\@CZorT n>vy -i& -^r> zǴrMUEIU ^-ץJ1W4PTՑIG }A!A!a@b$aR  -` 﫤}Vs b:NTLfmnVWu5#q8Kh"472 -H#i"N0]$򪾝%rZ9~6h"pIFNX'.\0=e2A'xsB'"y'H-}/~rG"| ؖ)<8\? 5T+FMRkJ^dh"jrFZHK o9> 0D -1C0)HCAT@LJnݶn.7HKLhaFNTĽ&h-+ʠjk%Y? M|F"J9C4dJ6HU]$Rl'5I涯5CF"Ggi? bL ' -#=oW1 QDZ=Hn@"羌ve/ɟjzFN|掸e97&| -۾: ( ꪤsd4J)U%yOy2a#2}!QAHA2 2(:`68P/fiu(5kذqnNFZi=3VŒ*UGu0LnJF"odHD&,m{H䤝uoŹ[#9FF$G:m%9W"y#nErIo"(3#GAs{ :wi!R>8ŀYDD򭡖2b>6\U-zA4^6$njdzVKQ$y - \>CC(  H @PA 0( WYE|˽A Ŀa/!Ix)\MIl1/"+gfVa!y>QuV5ǘKyˎcM-1f>3G;ڧhZ㌴ֺHkp hOAZп\~E ;478K+}_2.wƼ)0 -(Һhis .b|i}s~.Zw'܄]?Kt>ֲX *izMҲ_VjՂ-պSjeTYWP\>CC(  H @PA 0( q#@*ZZ7n%cN7=.cGUҎ7ƛOH2 -tWmiHdDc;<#K.ϛ򤵀~F|F$ϣL2"䃴ZHE$3؋&H$پd @>:t/>ч\hsʼnI;|k%.v鴖{Zu';BVWRIQC5)Wr|>Wk4FN5+z׽?HBD!qH@ iȀ"Ƞ -` @ \[ (s0!a'GMDRKH:i5 4w~ٚQXSbh$g2"92{|VI(sL<3;=]"͸wZyM?ҾKu<9S&rOʜxыAyHZӐg6 GFg(QXFME^*TP$˹dV -Y|)w6݋Kp~@xA"!I! *h&X`%p*js/7LE^iT$一QDf0L#)YUlɴiSƯ'~S,DJyF".ٳ{Q)ji.bk"/HF5Jgbmk]$rXF"9P#y5r᭘_{62jjzbGmξ=p`^n 5bx ɨ(YH.ce׳J9[/Vh"F"=ߤ5> 0D -1C0)HCAT@LJUq?_ABFXJLGԚ[1t5R9dԤ&#XrDNf9ȣF"έ<;/a}9S#'/$HWEh$da`H~}5F$ֶgE{1HtIm%䱮ÍIނH~䖛)XZ3\Q\[0[.KQF.2٨G !C l\>CC(  H @PA 0( W}d/AOR@t ERd(i%ˆhVCC(  H @PA 0(;wHrۑ=jŽ!⤭-!g{:r8#/>F#?]A4#?T{:NxUvt)3^0A4FiǗ (4g3"Y0"K#ұXωVEmK˟~㨭Ŵq}{h$Kg\`4vF%ſ=,s^^72pt.=旨_7OQ%7 -c1I),鼝(wOHƒFQ4Tedƥ+zb?ABD!qH@ iȀ"Ƞ -` @ \ʕ7[3'&fȤj{i>ob,904ṗwz8v.X&%s3?h{0XȧE~^H$cDW4KnÑ|Ñ<ɽvoz25i"bTu:ԧ@W +a4]$rzc\{xxV.DV2ℷis;b~wMdm^l˺`xjQjЪ#D۶-wqG]O+/@XEuͅIZU͕$$n 5^\Kw̵D(hW})1'Zv㌴Ficu-+:WG䞎i۵M.{!w[^LJh|Q?,{Ys"&byǑiM.[!kO AC ۜ7xYMi5:T5+jmyzC*6S E櫕J~d H-y?}!QAHA2 2(:`68P3(3r -a3BBI -V2FL h>D~&r+Md/M4ޮqDH|g%+ -˿}:υ;|;]:\x̎|H5ͳ}"Dϩ"9:7SMS 9O -q!7nm >jH0Tjz^rU y >Wb-i_ǐɅ~*Z@q!C8$ 4d@dP@ t0 lp.4ZInWpK7$$H&$s&"BDhˈY@ޏ=i\`ĵJj4;a"Nm -~k3,eF} 3 -Z@7v|.zIWiv/OxiWKWa) -msG(i͹-7Shw;OʵTr6j RYFZh=WhL-C [GӊKp~@xA"!I! *h&X`%pa{ZoҺדf$GI6֑Z$md$:"m.r/Q67~IļX+?д>Ha4uFZOǎiMwqmW\gָNqˈ]ݏki\E#btzU~w0:2'fޫv:73Eq0izzs]lsVD}W;@q[n®kYjP)ײQ4ՊTg9E-H\7#_~k~GKp~@xA"!I! *h&X`%p*k';I/'{Ix"IN$^\lrGhˉ绾uF\x@tn -+'>F\]Z~\+;UFZߏurKz+ctzBipvyuϻdN9nmӼ|p[7O-NI9MwԐԖӺiVԬh誔TUI4raJRWwPߍ/4}!QAHA2 2(:`68P5^Z$I)HGHh ?L"Ht m Ib&Hf^"ͣ/7uў$Fbi- UbV*r)Vӥ# WZ6akh\q!C8$ 4d@dP@ t0 lp.4 -_OKH`MXUc1בrn!Ifۋ~d$j"$*\לgc}XHcNU4Vx_F\bq잎[?k0u.di5'⺕V,^qxik^Hu^+|r8ti]授n^UMu[n.Ou^[kŲR{ob@+\@]-suZJ._/4F0vU+zosb^>CC(  H @PA 0( WO| ."$0Ds?-4)<{$6Do[4gLa8{:CDEb!ּp!&3B:@ZSw?^Էmߞ˭!AC:6:?1̢K{Ґ1^6'&4Fn$óKCtPRhz":Wrq~&| -!\RYjTˆT*(ŪbN*\bcd!}{_Ղ}!QAHA2 2(:`68PRo"2:?-0~> "$2-:16,"i"'te:h"lb vˌA*#3hD@#lq;];]!.rkFҌ>VȜRS!wQH+'dNmQj4ۙh{7edRedh{; -ׇZn§(^,BQ -FjԧJ^W*żQ+xxmV> 0D -1C0)HCAT@LJ¶ ruz@ j|:5br<0+Y˂%*l(#/ʈwZ4r4#Gv};]veWs7ًNa;ɿ -#o|kHE\1~9?E|2w>;)D?@#HhzZܨ>ksS/KW25|!YdeJF]i}| ZCG}!QAHA2 2(:`68PF9}6W<NHs$X] -#щqq4EF\'v׫{v߮ =;֫i 0FMk4]/dt;y-.8j]&1MLS>1g;ܘwC}j U:ЬBUjU &rF5}dڻKV>h y.}A!A!a@b$aR  -` }|.5g _ XA7CQ@'i% hȲVuoaqOi19kзi"HOHv99t8;3zwi//э0Ɲo~Go8|zczdn[C{x= -oN0L.wvWٜPQ@~nrv}$V|YArﭝdrT F![UI}Wbܹ?KiyC}A!A!a@b$aR  -` m'E$"vdžƢ7JEQKceKTycٞA#9tHO#y}Q]L׮t,g KE|k$D#y#oUc$\"'jN*r-zڗN^{oiS_G<8GO!z -7aWGR)vtEkTsZސ -E׼Yը+_4}!QAHA2 2(:`68PUII- ^ a!DHn{E|!ZA$MLdcw[8IHuOI'>.سW$m\%F<ߠ3*yL̟a?B U2̍ ?BO+Ѹ)Tgާ'- pSK\䖛뫤UUzPjQTUZUZh-]c#}!QAHA2 2(:`68P2@$gY ٶ{6?31$#gO,&tq{$W2eRfѼ xHW^4nͧ\3t2ƔG14~F<1儋Lj﫶5-n\-_Dd=_h>|Yp±f뫥y}U55_Z=/QTZl5ABJ|0-E4aD -eRH9(:%|4&Q^t47HH#ygwvHyKoDdcr1j96$I6,F$Cq:QDrjɳnҹݼ$ WKs/^6Ɣ3W܄]N؂^zV)J9'ךT*6h`ֿO> 0D -1C0)HCAT@LJUf$U0CBXn>'"Ojh`U1-']4]7Yl^كQ%G:*y1=YdFS31S9wDIVoHȈ."im{ lU-y=G$Yz cY*y,H/G$|Mf^; jF=WhƔދ8몆\UM-gymd|dLKp~@xA"!I! *h&X`%p^Ⱦ+Ry;䄵*cL9y؉V3`Ru4EכGHa)OcJc$OhN?^s4qLYW{OmW$gNF Ӊק4Ktg6cHYϷYC;Dk2'LsxgW$Ut?o2Hx}jqҖ͙s8ѷ^ZM&^;k[)F4^\>+&덺RUuU+z7躑BD!qH@ iȀ"Ƞ -` @ \BYWѷʼULL yԴN7"JJ4M<"ME1ڷHC|D,dv H͈qo}ɕoH>.=z[H6e{G$zQRzEkQ$"i5"7C$g?rv}ߪu~CC(  H @PA 0( "w50E{[lf&,N$-džd#1*#w" -<#v//d\Cɳh$cD40"h#gvqa?ȍD$O^?xw3%wu.A Hz}Hb -<92c$4:rcYH]D˾Uқݹ]q٭ 0D -1C0)HCAT@LJU\\3.`]~| +ŭl#UrP4SwиH&h$wcTɳh</HN#1&\"'v<˫?o{+.~2҃33"ٝUNOqEuuS"<2ڢ -"9ѸNBzE>Ewq .@$/[n§P%w}J^ MbUE*5"5zV -\<׿G}BD!qH@ iȀ"Ƞ -` @ \M0] Q*le+fTAEYQt5et0͌ek\2p$q;׷|KE$"/}g_Z$/H~η|lDNcw1ߺзy__?`ﰠֹ{K7O-nѷxA"^rvNQR/RVVDfkT+RUz17KW1WnD BD!qH@ iȀ"Ƞ -` @ \B⛎HNO6\NgFE'm&D$Δez%Ѧ3+9Xs? =Hy)^~Nʌp,zʃ"+;;vr?v1' A8?ާ(wjsGrÕ&7&6y5VU+BY*dUI+6ԼRVf< y+z7/GNť8? < B ␀$ @ -ҐDA4,y+5y@)8[!'lEƢD;6n_:( 5ٝyIV|?J=/ e m\.kS_zFBD!qH@ iȀ"Ƞ -` @ \JͷY9?|R"i%;6dNM,!ɹޮ$}%,$B"!;X1L25yf{4IFN96Ww牝H7gw[ٳiL c뭴,k+Nsts9m$r6W~hy7[-S&7 ?pllv8io >;FȫW -Z[M0T%Z׵l#_[%jEK<4}!QAHA2 2(:`68ProS333cCH%:-ħc9`9$3E8K쳀v>՘bNf;1d"#i%h6o+awcdnlg%hNlח٭E XXlF MZn®jhj]ՋņdzAYM*ղklV7l~>jq!C8$ 4d@dP@ t0 lp֦JJ~+06h󥐎֌R\Mh\P`ʢb1rwՆ@dDJLj.^4kR^oy]13=-=l9 4~"9ᙖ)˝0|^VBU*jՐJVS5#9iv3lO\BD!qH@ iȀ"Ƞ -` @ \"E{\4|󛙝 ٍ,wYP,yNP_ kZvݫ~t<5`ay{}EηisXsDׄE+#ϘbX[Z入e֎oy `iG(Jg%eVR5T)i*Zΐ|ӽ'rG`8n|!aD!q!2(2Ehg!q.'9A#fG)b?k$4NdS@lć$jΕD%ZS&Hvmmf_D{<`0_T_j!2,v t_1U ,M9}Iْ#_!~M!fTW t FUQ}c%*֟SJZZO*%{I-iVrIFj~O=<|E6p /B B BdP@ (d 9C`uمAq&F9 )6>/6ֈƈ2I-D&t-#m>Ih%t)'WYBװ$A1"CF)~1=)XB$t z{8C0tC8PA -BPb}v_{mnj9;QP(!h<b$#JDMh"db?jeC0tC8PA -BP"c'a;1F5wπwH]@c^t͵ds(꠶-79?7`ܜ'65bkq{ݥ+)v/V=œ/+WKKĞqxq|-)˲xywYW9j1'U/Ÿ^<~uM?YEN)餜.,.&d7JLMJj2bMk^̔3V%rgszkw֢=SZ8n^!]!H *h&<*:tity`!08h5cS2!Z^ff!au; -w|OO?oO.{r{9H=JCeK9_5!b 3>GDY?NM$ћȡ\o"yx<YmHXqwH~5D{43/C| SD7ow+C5܄ɤ$[FRWt:[EtUQ͖eQKeQQ]OerE\p^wh$q7x/B.B $A4r`A -`CC$ !{ _ E$GDH aI`҇CdFr8Ri$ObDr-:{#1"ٯH;rH^ -"9P2q~?JN-Jh$ߢk$.\!AA"QA 2(:`B,C"9∤2"Jj DDRq% -$J&TE7Gr [z#i1"y29U9x$/JɛELc$|HCaF}To$}*y/{dD2pƵ?6v*H^žA$&'*E;y_&H-#YNjjQ.VĬ)JVbINbYeRrfHq3;Oi$q7x/B.B $A4r`A -`1]xk"z 4CzxuH< dy4]!6FF/Z%o&GlڸvHÈO;x$lٸv#6k\S(76i$/gD9Z%1d6pFƄ4IH\HD4.zu?laHߕ㌗;U!?i ?cɬ\UKEEjMT*TZU4J|O8n^!]!H *h&<*#Hʮ;7S]DPia>Ix".$IN(]d؃E4]*)HȨ~xXo$ob<F~l\ol= - U*;|HzUY4HO#ar#'&CɤGNzHZ+ɩvmh\{Urg?E$_NS<↛p#~Rt:͊jZ/RM5C+E5e74P%g<əH8n^!]!H *h&<*c0"i4B},i?ɇp>#Flp\MK-ɩCۿquHͨ:z#)1"9VɝJFU2F$kY%͟J~X$'D^H&i"DHg|| -;D>w co9w{t7{a{r)>@<5 %/:&DrDf0Tt45.^.RZEM(sћ4tNp<|!a@D!qHȠ -` 9 oȼkQ!ޜ/ "rq }kA,Y4CW 4>MFAAF"%C$#6P.5Jm,ch$2" ɣE򣙽Igw^c4F$ _$r=:ErLjdϛ73Q$3DQ$nq[y 瓆p#gKwVQˢ%SbIղb5cUw~05kSC .px (  @PA 0!Wgw$з^ŧb(oMH -D\OĄdqEQ%M5C6fE"HXCh[˧ɭ-Yͪ[-k42#Ӿ[g ?U2Z%ˌHF$ HsDoo֞s?E"o`#q|xg>s&':ǙoCz=Ip)~"k崨5Qb)Y+tR)ɺZѫ}@"?}}}+.\!AA"QA 2(:`B,C>1Ȅ6<*/HZV{N>2UH2WO誮 n&@<##k{H~ȑm̷MX$=a]$ch$'0"ɛxr:kk?D^zDrV}ՇHy 4fh\BJ$#w,DY(&HJT1SCTK% %SZ)d)U*5{oz#KpW&;/oȍ -9&*F"*yD2)zHC5vF:gr 0<8/j ?9OW-ŴѭW*bZU*%=Uj2$gtNp<|!a@D!qHȠ -` 9 l͹V_MguǼOH{5VzBB"["ѤE,oŨEmȟѶ>ٲFBzKH~q1HɈid_ %#aG mD$3nOz7bZTd 9L)"Je9S['(ׄ|ҷ .px (  @PA 0!gO@:!tws?m878";,Ćsi)#TC+1}>MK"9&rF"S4E6+wDtueHɿH^Cd~o\(+g}HU$"Kt |%;N\xu ":Erw6r[G,n om1+)rIEM RճiUjbZQr*H.B$oH8n^!]!H *h&<*VHꮼ[V߽oUBFXa`$i/9W%C@t]6}׷J#ُFG# -#kiߺI#}H_9eGǼfwq4sc*Wz#fTfF+Vg|TۨgJH: nF}Ur$dP%W4OM.gr;߳\n! (Vd#,r5ihbXrUMW24e/h4tNp<|!a@D!qHȠ -` 9 eL .T*)j8Q[X"&"dLt]ӟF9FN#h$5#Q%uobΏ -Iy#Ҿ(IN;7ax"h{[Gg#[q'_h؟I? 7G쇒G4܄THRVUMH}k%J(&e5[*ZD>Ι?o2.\!AA"QA 2(:`B,Ca@w/F,`$i," iB}"Wb$FlDM1ݺ^ݯ772yaErK@7G(7F4|I7#:<%1"N8ug31Ii$tF C:*+3Oѷ=i}DrᒆpM&)ZQG$KY,'lZIE>ɏ{4tNp<|!a@D!qHȠ -` 9 J!ein}}9oa(e q!aq E˒K40"%ڷfEr]s}2p$GZ%j:Ho]:%J#g6"9g#ovHIܽ@NHl/ݹH4NZe⵸SBp2ֻXXF&1L#3LlRHH~L#KpC0D 8$@ dP@ t0X|V%?URF*4иZh\ H4SW cTeTߒHHt(a%-M)PF<>p\Aǒh$*/h\F3Q%ϥ<;ָ?#޵)"`,izn?[n_z*zdUL,VK-LURI{΅K=y .px (  @PA 0!W9H^3[ oFk"Y%jRE)(R|c9%SkUH3-w/F$KZ.ݹDi_l|*y5M&};4DzVVנoJ>wy3K4dy)at.E"n H*He3TJ,*E2UjVT) #l1՗y(|򾧒tNp<|!a@D!qHȠ -` 9 l5Vv<z㾸?PV#]P<UɪPJ[0Uo=kP\z\YDP4S=@NgDrIѷ>頏@n`g.d%?E$} -0D w­ajr;1LGz&.ZE3lF2(zPŊVR},D$ՆtNp<|!a@D!qHȠ -` 9 lQ[ % -D@~6}V"[!з e4dDr;[Ht@7(gy[Frl#>>^lLe4#I<WC!m$r1lI_ձ[+p}E\;0#ɕSQ$ˑpVHR*I:"RM3"(rÊ+wKo>AV\:'  >C0D 8$@ dP@ t0X_ec.A$g4M@;Q:43kIAD==/gfs}a и%K.^BT3Du~ C6܄d\lIVkJZTK-gSZM/j\IӍys5*.\!AA"QA 2(:`B,C2n;cC݁i$894D!_HFD"Zm'tb-]Zz - ^L* h'#׵-'jNk:QИ }Lѕ1>Mhc :>&'.EB]b:'^bﱼh>(ctg8cK o򖆛pjXV+j(*RKŪ5UMkZiپǙHS7Zm4tNp<|!a@D!qHȠ -` 9 ʄjĵz-O| &1$c\@ZRH ?9TɩV5cؾ;6}jH3ty^>R[H>-ϓ0}qhl߱4OHtl 4|?\<|[.N5\fi܀LHtT\)ǝɍgHt 7GRn=]T*YUfr5)-L:+j[;w\:'  >C0D 8$@ dP@ t0X|6[:=3;ؗ!Cɡs Wk~7|$9#ߣqH3ɴH͈}mDr-r^| 7fUg4 4uG+&`m,<Hjx`! wldeq̚cqE_s 7'R:,fZ$jzVeT*'jMp@ .px (  @PA 0!'{E2RݪG5 -z}WRBrn -˾Dx2~#Qڶɿ09~8Wk*_` -c6l0_/&Ovh[ϰ_umώhD\e؟Iw=\Vj jY_@D5m/JX+i.2rñZ!0tߧKpGݍcT(}iDd?4&4%F'Vѷ^˿G<%e؟q1 :5kj VZRʚZNU5HjZ lZtJ =坏2hقgtNp<|!a@D!qHȠ -` 9 Xb!}3'|]J|Kza!y$4;+tTWh\ΈkƵƵĈ6FrsbwV~2 -FMӺIFZhcU@i~ i}igП5.?to}](=ܑnp i9[RJELVɪTђZ.lQ͔ʚz]+w>{ӠLӊKpBi}wazkvv)cv#Wnj ?xAX3jQL_-EдzVeTbε뮏s[ӇKp]I|> ,!E$Wj."$$"-#j# -&2b#n0*Ӯˈ]3q=:kת9YZF;z 26zÌwM+:nt\˞( Nm!q_H$l59iwZNpֆpʴ~RͪdD=TE9*Dt.}`y7J@tNp<|!a@D!qHȠ -` 9 {幮s= y9aq+1D*Pe:T+C>[kmoe<[)iu:?h)#?k#,mYwF`4ߥGvd Dhc/fc};-7=@mƝj>1O1jq\w 7t㚢bWP@%QWJ)zJ+&SZ;6 yhoKp CJNiJ0JiK6 -m_qfk{ SD7"Ygmo" 7G2z)IMΈrUEUbY&Q*MYUi$G$~ՏО .px (  @PA 0!È}^%ijۈw"=$ DBvՉFbI|IkCҸtNp<|!a@D!qHȠ -` 9 Jnɣ2zzZ!(׏ UP%IC`h~ӾJ'N`$N -h"H6)uQf|%U{5&#i"fϯDqDF\;zg"{.K{OZ$7#g1Os97[ay.K 72֋dWd+M,3%1]M+boMO}¥8p ? !C -1C@T@Lȁy(U_e4ܵ@>h+rq3!TMK5~Yi5w1y:}r.#oHdwO;we?h6/j4~F$OP3"ɣzE2|-ZɇvQK!zE$n- y/4܄IMm=gbV3*fD=Ί5]%Tj(so]wӖ .px (  @PA 0!Մ>N\Oij&JHIFBHx촧i&_GH[牺h,1s iOnFKucq9miOUB(g-zۣ?`bIM~moײ{ڞ/ \ⓥi{7ʹi'=E\}NZ J `zٶp<xLTh^E\ڻRJbRSYZtߏwݓ4tNp<|!a@D!qHȠ -` 9 &v\vL kpzW/bX+]R::+j2+״kg#nm4tNp<|!a@D!qHȠ -` 9 JXduq'sW՗-&E$ -K"'%$$a!y^!D{苈i\Y冷и~Ƶ#m4o O ƴд~H-Z4[hrQiM^dzw&$BFd2XToߊ&^igp3^f5\GM8iUZOZ:UsOuMOiYVKb6& ?w Ҵ8n^!]!H *h&<>lSıNvZKkQo&oA~P)^E"i]KbfXODJDDljc1V7M\FZc4.FZi=H=mӘǛG>ˌ^xu˷CV՚뚟z{1tݲwB#_=>}a؟"÷SN=>/0&1Zfʲ]U5]SĬ*VKlIԊ}IZ9svwͲ\!AA"QA 2(:`B,C_8f#%e ?>h! KHh /"YvVcsHbfiFpDKD_P?dJcu jKhPϣM肿8fsLQ.}GsLѠ>ƴoI<n+?A]] mُMָk_o؟"/nqk,^m ALt)VA͢UMb-Y %aqъfLJ\!AA"QA 2(:`B,C/ {tEܳM%i?.0'&'G&!thA4H=rǜN+Ft#4ߣrFDFDˇ|\R:I0#7i,q6JiM!(z5o HY =ړ=; <&fՒfE{ZIͦE=]TS)A~LE\!AA"QA 2(:`B,Ck,c"cs}2TVr3 "$4'D&"cH|^)(y2|4O瑌|*t1O3<|~@K4# 1i>g0*+V--\N><8#p¸=Q?ف|uuނV7zajrЕjˏHbd3&}.jE>~pZȴrԪ)UM-<\u砛Kp$& =y6Qfu&{fFF5t#_[K3z5#1e;2=tŁsͨсoQYhk>mޣj^b 7g;"8ǝwn `nG3I$\2bETEUE=+WE-Uir*ɩ^ Gwۋ~8p ? !C -1C@T@Lȁy(UftR/{g2Z9 L3IhJx\Gbool;=[~6ѧc~Ѿg0:V~@#o06z'9qEcZM foI[H ZEc$,Хtc$qfI2bC><=e j܀&wO1~hvth iLGkj^jӘrQ,1ыbVմ-isW.|ム:Kpz v)]IbH%m>WeA}bo&*b>to,FZzZmk4yFZo#u6B=|+ -iM+aLI_fIҴ>@3Һ$K>AZX;&]`^nbLzVudcw>3ck&!wnk m=%+ZRWkbLjE5[Rr}t%z_4zKp2~%tȖ2XYc9F{Ϥ=F"wDL7w?Ze. |q F#*#ӗ-Lj6 -{ ].S͕SW}oƝpOuwVSN>洆pI.4"ղ6yLZb;pW~|8p ? !C -1C@T@Lȁy(e^@wKIۈoo"G;I7NlН$$L)"m#z}h/՗ ֦a ;Z-h\}:mwV0t~YZ^Uol,WдZiD2z9-[aiAg,%NN濋 q;P@e؟;sܠaCC0D 8$@ dP@ t0X_eHtp ozMJ0Bez,W Il(iך@(OD^HXU=4H䤯L-Fo[ -̓dF/vƗ{#y#iVuct (JniW4 -MQ@Ǭ@ - ,spB[}0)5T¸膫LF͔ι^ 3¥8p ? !C -1C@T@Lȁy('3:&o#iASonL)Y$KoH0g:ZrAIOah:*b/-z?;"{;N65MqUz_=J@P v7O(]vGFPo݅NwY|n|&[>jQ/Mfr--$ZV2r߫߿sο[1tNp<|!a@D!qHȠ -` 9 VwcW~O O)"jL Y0j\ɨGDf$_f/V l#ϵ>YkMό?iuZ;4?]mDI9?F;{KT!{P;?^׸6k&!;7v>&E^'gEUUbr+ZXVvnM[O\:'  >C0D 8$@ dP@ t0X}fs5Ľxv^H|]}WZMkId=\|>@Oa37#$:bl$掦ɌjtzҏM41:'v^ ]]-MG _ZVi=U@{ CZ9wvj+ʢ DZ/(Q@A@-i `B멢T6RZVL:dF*V˵Z*eK/M댕;rm[hZq7x/B.B $A4r`A -`]i{7D28{㱬봭۶Ǻ47;;8s6ro:$+ygʉaQzuRXV\`t^bEGJc -j)wٽb7~33Ϻg]o+&\qWd;ie%LıʻWi.ʪ7ҭ{oKeuMmwc5BZGᣢ;G`#:v0>4si޻A{i. ި>*#h\bpތ\55jV -uC&܆GEӚϩdݮTZkb -`F3e9ѣmuD]t/? !C.C Ƞ -,Ȃ ΟdUE RP a!FR9IJ*vW%E4FemK ]z#ю,ۖM8owD.e$' 1)NHw <:6="rK#T݃aO'y`#Yzs!ݹD>78 ypP|AKlXRjQIHyt׾>;k(~v|!QA!I@ dP@ ` d)<56PoP;L"bTYDjICՒ,*El]9-0y-ho?K:=Mulf悄lgiKI#h7HZ3;K[3 6{ Ns/?>9+5}\:u+Yq&Z⺛pdw%SNt--1 |OtL#9th:\O7pxC0D -18$  - 0,)zegΤGjH#(TdTi" Bj -QuMF O'3"yteD2B曫m$_؏H.CBF"yfu7_npH*;VHW5nw.ҝ'VuΩgvg{zn­/,JHҩR+I3s$ID#Inj-qڿaNPdco7Se?8<99Ӝss<;;;;;s |Ԕ ĮшЌPLML{~&}/^׫v}>skӏ7kr8SJ_4yX]ZҤ jiJ+rmE┉dqr=-˲x٤84iWN.]R||/_1ɗ]R 'O8QQȊ"g̅lZjrSsWL/sL&+Wl=z*|Yz&]|ZCNvݵbk|h``㲝K7p xC0D -18$ )@PA 0Ls׹;7H`Fp|,Ebg$%gl""<"Wu69X=bCa=\rLOO^3'?y>?c )@^)D0S)JK:^UM)W\=.W]=Gwcz -"6Wߺ&omʟqoE77[~ӏͿo]}Pjo ߺ}x5a֪[IW{wn _^ s -'^P}"KYs Sn"5ؓg\`Mp>EJg켻Ml >ys$|\AiTy=rZΤsVi$ }I\^|!QA?!IHȠ -` 68_EsK1OzH ˈI%!Hj$)IfhHi$(7<i$3=P$k>~$G$kE2+i2lZ%ʨ[5#mZ%oaD]]͈)'T8DrٹIߊH^v[`8J^hYi~NJ-7aGR:F2Sd3|M,Vf+UTKL5URCgk }Fn<!a@bqH@R 2(:`( W:"{2"iJc")' ˂) PM飑H^Ĉ#t$y+3ґ/\H.fDRb$y{Dr\K$/G$gk\_|5WH>;aH&PPɗh<=#u?'&pǝ VI"^c S]*p#("j >\HRb> %rZ,Ws5QVsR+ m{Fn<!a@bqH@R 2(:`( W9խ6 ~CI+ 5*d!a7!YrT4Cɂi|4ILZ%'wHdHn:6mǒ5C#7F$aDR#{')sgw՗ 1h\g,9aI ɻΧw*E"wno >ΓZMWP -QeQUʲVՊi$qZ[wA#K7p xC0D -18$ )@PA 0Xl%uU|D$uDR I;Q@D4DB$m]7樏F`ɹHi|x32fwuѸ.G$k-H~]$5h$?`D2Bǒo0W%0W?=w%|;u;LҸ؂ ΧigjTɣno ьSHNbz'>H _Z9IKIDfH~6O1|yP%QrÌks*46ƒXNgKb&_Vkz%'f\B\wĥ8xC$׺ STIR˿Z38ܾ&eU鼖͊t%+&Wʣͧ"$.A/x >B(Ġ␀$@ dP@ t0 -P>qIcxuTI4:W-EXBRX2))WPwpɅ*9V4'0"wQ%vwl\f[~њsh"`$rM_EZ$f"_avMD1\(p%w N/A" .^1OMn޶*8sr$&DVdRUM͢o-RԫWkM{wi"qz^~@BD!$!H *h&XP"|ko}:oBP Hd1ZlJJ(I%$4]B"&&|F"DHwi:KF"oH8Zз|)iFD# {r#_;kFyF$/oU.nOK܈")D$qܾɆ} - Z %Wk AB;Hx;E;Hu>KAwweQ_$Dvڹh\#t}|F\WҸuiYt,5uY>wuMM>GzM+iwMk3*KoK GJo;?4.}g|vD`B;#܄}ֽlIJVQE,grRLVVtKvP3t/? !C~C$A4,(@lpYэQfL;0*X aҚvT-RQ3բfaI9ȱÚ<\Di=ȇ(°;(7/VgZ#|1{g&;(PX!]_)D=VUf=׳qH^2p>E$rŝw\Q@#vדs|Z+y5/ٺlŬRZ+i$O-t/? !C~C$A4,(@lp -~) J| %U$#$DבZ.!$":"'*# D[Bx[IyCq-2UZ@#P?#wQ@L:ЋҺmst+Mk1't6--?<#|o o1zIʹ~eu) ΧhwZ6uf_k >y1^V\M̔5QUe],Xg^Uznhz#ν4t/? !C~C$A4,(@lpICijx݄_M+HI\JB$DW*'+H!DRH(ˈz?V.b$抶i<#GҴi5i=w­u~'Z;i"Lk#hm]IH??nS['NAZyr3_t5Gތk8A/Z>iu&TZ"U.圪RV tݕq{tn<!a@bqH@R 2(:`( WI_^ ->OD0JH"*gBIFS1ARrJ)-'h"C4g2o:_Dޥ.H<搽%vk*gDn;^Q4 h"sM.X^=i%.$ą -'|$r}wgt*o܈C{EY!Q-7a^TP<*fipL\Ϊ~|fĥ8xB^\^|!QA?!IHȠ -` 64Ľ֩g+L|[%-$6yD6'Hb#I%W&(KhO}#16 -h{ӸqV#qwQKtkUq}]Zm>zHFKጴFiZL, fy~N_xi=v7V>kF!Gh a g>zɢ^gpiՌZ+Ц5Hw]yC4t/? !C~C$A4,(@lpz~bpMQL|o#Hp $ $D$NZ7VPבږ>ImDHbmz#׻;m"MV1]R\oAZG[zM֧͌΢i]uD5sh|m<ƨ u ʹi}иc~w< Suc -IHm^MSJgzIRETeJʵl6^u.|Ѵ  B ! HB -@T@LE*k{5ijQ[' -FDBOFZWCNZ#-ԍDxH[QVIOxtTMkqHLwӴe.ֺjy?][HE4FmxZ[3zui= HD S ֧n_i}Ҹ5\ SO>8P[<&*{y,-jZ˲^(9kYܵ 7vV\^|!QA?!IHȠ -` 6 wxq^Kko%$%e$D64:-NZW7:mlu=Q6 %Lmm'g \?IYKu섗ѴFZ#]uRt—TzϬ_auu8#[Z[1.m$Z^(q6;Nxnk8jq#o_tQ[/m >j5SSUULޫ`ܚO Ht+jsnx.Z[qz^~@BD!$!H *h&XP"xx;i]GI-Aб]@7IsʈfH>K5n}* -ΧHZW8Җ -hUO1[+@b 5S(VTV&sQ@7>ܡSqz^~@BD!$!H *h&XP"|qs3x?* .FSM(Xj>0M94b$%ȗ<|4E2P:|伫e^۳T6o?0 -NOC'vv|]@w`۝4#K>9^q^/Dr ɥzŝE.alM瑔WT-jzNՔXRXizRL~^uȎLJNǥ8xC+HEO{kRszl5wm;t.xl5YξE 8t\*L䬪k*ֲzKee{En<!a@bqH@R 2(:`(}H6W<BF؈B8l!abYꪦdhnK_m8&h";)DE" w,B"qW[ )4t]UY(S;#"Yfny;Q@$cW5{ލH.H<-0OMnD3/,8uf܄} A,V*9$3zM̫RZ⮵t.A/x >B(Ġ␀$@ dP@ t0 -P2=ȏ&jpFhjx&ѱM3Ss0@<ك0M_bL4V+4ieM7"_켛ᱳ?ymO?e#&َ"/n3[{q#w"Op>E0_[s, 6܄}L~/yKRETC$+j%b-w>沝[OK7p xC0D -18$ )@PA 0ƫ{IFs6<"$p+ 'y$<52Do&Nc$$5ȋ2S%|bA̅mOգhM2z8*GQ"oceم6#:m廊f5NWVbѫs/=8a`yȮgՑ"Bsl籗n8Y Ƕ܄}ʝWVjY-/眕g*51_hNgr\ϷSbw͗h_K7p xC0D -18$ )@PA 0߅&7^kyS>f@ !-,7BcLCj>' {NFF$O|I>2@NF$kIiՓ_]$k||;O e|ŚΈRV͖h$E_K7p xC0D -18$ )@PA 0竌sOC$˞K|s @}p=25:.6 *!8y2V=_EFTi*2RTNb**MA3(? {;JaJPlak(k4G14+=mɻÝ^.fk9s+3g ?|Ϋ]i\ʩGΧ&'f;u|pKi194F"yv' -:ŭ&H4"#ܼf"_2y hB_YZ3H.j|EѨ@7]~+Pйl}/blYrm籥`bEpE]SbZ)u<4\Ȼ&n<!a@bqH@R 2(:`( WO3" )_H @o#,,ghI"$53҅DN&Y/j{FSDSeBS:>b3Rz])o^Nm[Fw>l hHdtߌ2#:;ot14eVNhs2z{ͅeK_h/:\eZR+G=﹖ΛZV)5QVQb^͠2JkCmYv`N6t/? !C~C$A4,(@lp.4vk^ç𚿈 )7o 'ƶb~$DHrWl?#.vkbDa [Ye8.b$Ky'-c.W9L4K.)ʀsTw^${\KLn ׍-pƬ%E~rVu/T2jbIVsbZ|Wu?ZڹoD  B ! HB -@T@LEír۪WOxH - !7P3Ce]I")]0T3ޮ-1ty#)h"e$2Psfzsڍ56?N$)CvQ$kٯRC㮊5;Ys"9z\|jrYWX(pVdǮk aJv/Ǔ՚KbFER-R%t\WЊ6ɅO\pz^~@BD!$!H *h&XP"|y=*% 47OӝW=LtN=>o좇=sXǑEkrI_69Q1U:<O4?I{U]T̟zX?̈́ɺd3;[io\q 4?'Χ6bZ`;gi >r r }Uӝ791_t]zZҲC+&!~ҊK7p xC0D -18$ )@PA 0n,x -^WG4"Ũ"YLRLu6Zc1 [Ht5_H&ό4޼G<:سڜxzحE\5}qi;G5p>5Ԯ-NA?> -3Zn¾aʕtu*|=-ٴ.U -RZ*۶i"\ߪ;zX\^|!QA?!IHȠ -` 68_EskHQ1Tϛha="E11 -F$M57 4m #i<ȇi"7GΨ ;#`lS3Za螺<I1{.MW~z؋dDrGUA{Ո䖟Χ(0aXE.ؖ"xJ,Wjb9Vi%` -&Wй@i$qz^~@BD!$!H *h&XP"|ш[͏BP Q+6<=a'Ǥ,Li\PNosѦH -42"顑<w5#9.fD.֓щ+ڞj?^%#/ a$\țh"(tQ$OeO|/pǽ)YLdm$raJeU~D1F?h[K o -7a'2ݹmM5D/jKj$f\ͩy]SgGA#t/? !C~C$A4,(@?|(Sh)Hyb^'HjER -%$2% $%YUMiRcgWfJ2s”Gi"1)"vEm>ɯH.gD2O#g3!F8#?b$yLDs]z^Drɔf$uH. "YXb88C οH`JMs)L6Q+\9#I5'2e6S4MlE?^*$.A/x >B(Ġ␀$@ dP@ t0 -Pb1 ~%P !;lDSI+'ԩBqxV4FR2"OZ$A4#y"=VF$jKm5v bv'[<:SE|9rv$0֟|q,[H˪|fYa -h$y?7y*<=B4ڿ^Oe1ň٫xd '⼱W9[ͷo7OMSnB/p'߱i X*{y3/W+动dj9X˚O*zVˤ6Կu}o=>Կ  B ! HB -@T@LE~0xO8ibph<$RSbYAP,^,&yLBBy=#4:4Ŵ߱vC3 }Vajin3wu<}EeMSǭHZkLD4gN_Χ&'m{N)p4 )<-7aGr/ rq>J^T3ZE,sU13٬V*۾Fn<!a@bqH@R 2(:`( W9m7}cSv !5EF!&"9*a'OMiHhizeZL#y -HΧy1" HHX$ux?]cسfFK#+C4cDy|t .ymy&8;%.u=䉂\"*่dU0OQ%_:WJGi a OHZ-_jba,,lֵlMg^Urǫ}0K7p xC0D -18$ )@PA 0ƦBXo -A54:\Q+Ee%Dr HcD'EBFE#U|evx3EF$s]\v@H^Wyn<}ԄQ%<1<1JnJ>צquLg'7Dc|lE$_:\3OMnĝcKn,p' l qmkR?4d+sbIG$3]+%UU[6 |o#ĥ8x=rzƙO:W8[sSQ%Knyr#?;1Yt,9%݌H*:6:,.E@F$iHə]D2Xic@',O^ڜqܴDq -UKѸ!HNJJD$]r}$#Wk>5=W՜s -A-l-l^:"޷.  B ! HB -@T@LE*܋m g.&ل_D7N - -"w3b3E$qsyʉS<(3DCĸw[9!FLO+}%]TS:lZ ٻҌZr3@9FELom~)&bZ+qlԳ1oQ9GL=,#%+rG~햛N[/]Y5kZ<EkrEStF-e4ob|cWjfw݅fv]hfw݅fv]hfw݅fv]hfw݅fv]hfw݅fv]hfw݅fv]hfw݅fv]hfw݅fv]hfw504*x E 6#RTj~ZRN).)ɺf?S9וHj 3"y\?1 4H}vB>+ st6H4DHHYDH܈Qk& Ό[bs&aW Sz-n,rru"l{J|EY颞ĴϤ+j^Wf_ĥ8x0'HBŊv9߼%h"Zi" #HXo$9SRhI$$ϠFF$2~|jrw=heT ߸&w~BDՕLZĺRӛ5Q/Uu1]KuU-3[/Fow  B ! HB -@T@LE*kj%5})^򫍃'SaO9;5DT$KN*EU]6wOЁZ5T5L`7ͯ -#t,@#.oIE2W?i$`.o"ߣsG1"yI+_:Q 7 Lr8:܆H^sβ3|"-u};q(&"ܶl6b=zU̧+t.W͕K.wy^ĥ8xg:Jzxh:ߌ7GoJ>6/s߈5I;9X1@$ovofɟlwF\vg.8놷P%_}&쇾#"ZR+ٺlc$Y\-P|}kI\^|!QA?!IHȠ -` 6 Ϝh,H:[ -IqgVD]HGNzi$fё|ƲY .J^E:1`oyeHL繜Q%ߣm\70`ObBIǦܗF$YHltYqgҹG3Oɫ&[\~R w\9;Zn>v55TI]wXV%1d.kz}h(9宁#zwmqtVccb5f-vBQ*TpХMeXE -:TD -C!5t" lc ;Stn~/[iG|{, @5XvNp<H(Ƞ -4$ (+IJڱĞ͓@S'O<Ү< ="RWVvyQ:ۉE]@r hWN+6Or+M.b8}FW^j32km@i%8'e{UUly2|x'Y=#RMTzTѧ)C/^ʅ9sIK)_ryssBSrrDͣdD]ɓh$$ߢE?i$<4#\G26c^.o(ݒq4W03DD~&O뉼j#'{EN8,s.Yȫ8$rwuHdHQ[rxkn =FRH^dh[l5+i*!٬nF=-M]6oC 4Kp.1QCı8eQ'iƀo |2GLV^O329f=L$"Kzd6|IEܚ4;Gɿt^&f2¨gwQ%]Sиqh\oغ7WC:~LlڟZեNs-p;ZnTLSl)Z%(%C–kj0RzZ뛫v,tNp<|!a@b -`AP"_pȤѸ*-.FCQI#%TA[ke~2> q=L D4y;i&F"]$GuW"lkZ]@:[_8oD^~c3 4y/c(9zο_0G:kCsl$r5:H䴉)W+J~oU[nOwLd^M&,jO%))唞MUDBIKpE=V$n9k0A$!_I _!7hpFh\7\k@k qU;7rMGl+Ri)Y\Ar:elupuo+.\!AA"! *h&X<WY@ B9B⹍|j` N -"$2 3I|:I$L"N',$Lҧc9X':JFP `j/ ihFP{ѱtr"?5hNw3ٳ,댜OK:Z:>E3;:FNtk̾3ϑ{k:w@4Ӵ?S^OENsm t:7FZRɦ*e)[dQRF&Rz5Kssf6-tNp<|!a@b -`AP"_Et:gv7ִ^GbNi4MiZa.z v;"Һj7;֭1,whw AZ{\M8n"MT٬&){PUդr -[JKJӺv3mwq7x/B(  @PA 0ESCV-</z >o¹xjq9a -:]$ɺjMse@GDHW|1.OuU{iW?=F՚ -M䥌DmM4vw3M䓌Dbhh?{hGH 7C0D -1CAT@L y(@21aK٘F̪5]2<2e$E2K/kP xO;'`4;-bu{n` Q1z69D":7F"l'CD@ DNDXg:z}~Uiu1Қ"!zZk_RoMiZEF6Mی7FҴ(v׷)W!/2*/lu.ud{v=Y{!J6Zt9a;^WBpԌr=jvLIqhMJVT5(RjI(Эe .px B DA4,A -PwLEVLYJ;{ BE"\K0S9DYHԹD!,b!ִ}R)Hi3~t(Mq~zHmM}os.'0j5tQ) єͨ_"gYRzzM'/'p'!g+޹5ijqqH4Ni& -݌h*2tI꒡Vbj)YG+PS_ū_5 .px B DA4,A -Pl&evM]M\{Cwm!H` >LB2b{ct3Qa&XN5ZOZ9{i;.: sO"+mE~h+1FM=T?##hZߣi=otg1۳\AU{͚jGZ;ANnH+atK(p}?jM8i\SӕdFSY)C2tHתr%mT MSH뺝#VM+.\!AA"! *h&X<WYC[n ;~⹏[CfHYfu&*"'CkiwBM -FZ}4yFZJ:c"g4^I (?nM7iZOftջD"HBmL)k֛܉'_njfH?9t~򵨭g=rxsZQWSF]*eJ%IӴUP[r5LV4]M Uusn .px B DA4,A -P"n$loW&ĿvN›HdH|7IGQ[}31^ kv-Mz#+zii]EZw|pjP)eC0zWe]Lf0^9eRnu:A{-O` 9sy\Wo iRՕRVRitY.ZJ*USԓUZ:l3/*.\!AA"! *h&X<Ww"{{gVc:ƈu: MO̱:$a!'SyDE,b/Li7Z# 0\ZQF3cdG]d3=KەTljE֐N!}1\q -4GKO%5EI dԯ{1\]9 ޾!Y^碡# Οkڟ"qm?tZn/Jmj^ԤL u pUYCjjz 隙>iHq7x/B(  @PA 0E4ot&qMr/&ޘVZJsH`> #$C0D -1CAT@L y(@˹<oxBa#rNb bQ)y{>?L+ny4א#yki$dDrA -wry"㨖D^M9 i"`kh"׌ uW?:INy|$y\!_E>`ڟZ8sخFwʞ׺d6"O8ǝMĘK̥ĚO++^bT]n/pGczfDžJCYSNt1}u[}fZ;񾟡L1O^C+0=bt .px B DA4,A -Pλıӎzwn?I[o'?E/4&v6mO$;Pc6?Ii軌WJiEZvt歷-ˋ;W&deI)Ru{_경ྒr&5T)岑|:m'{?6 .px B DA4,A -P=o.٭xD>U}?HEbg<&B\1%F5E)XvI c4!F"WD~ǐD8kOMk璋|Ok$'HNbǏlFr+#݌-(WvZ;EtOGpdَHPչap1MSwns]>ϝ~Z1p#t~VRJRTWgR)[+KT*F&=~YuI\:'  >C0D -1CAT@L y(@7S][|ȫB~5CbX]TeEɩ -F3}G[ySaD2M'FHr"yyQH^Y"QuFW29L1N3c;0/0hiD%8"Er臉fD|yDEr+g܂-yn"Wk ?ίngәz7QQ$jV2jI2Rz%eYliwHzoˣĥ8p ? !C8$@dP@ t0 r*Gɟ9缺/`.d[iF))WtӰH]m}e$2I*ڶҳ[iٶH+bkH.gDCFF|2w|I#(/`Q2!+G8c9DSs2OQ$'n@$/o]logmj =z*s2RnY2T)[J{.L*2೔X}^+$.\!AA"! *h&X<{VImx4^E*@,(oF:")H&KoMdE!4w)t#y{H-?kSi$%,yɕ4hz #cYn[o={*xSB$_osɗ-p}[?n nz׵TO)IRu)%5IU %2U G|/}yF .px B DA4,A -P=OLF$s ^>U -~NaxrxbRz8['#YJBdIF@aG'w.o3y'{rg{F"kUtruF"c]$كP$̝a3Lqx$Mr)Yr8kW)ZD^܄CзvतeP(j50LUr9YtkѲ$r};}& .px B DA4,A -P=Sf^K}p7x/B(  @PA 0EʼK}b׳/'ީV&I` .%HxLFt%[x8n^!QA 2(:`9C`X#x+tQ<7 Q1&']Euo?|Q<=7yx toN䷇pٶxȿZC0D -1CAT@L y(@쯢8"=4ƶBcVăJcIXc$B\&4]W HFi$3"tVgDyد0"BC+Z"9$oG1Ǩ#yg]H>Li?{JJff%='ިdM4 ~pH6C뾀H>& .px B DA4,A -P\5{-EOy9B88Zmk+M-C75nF"e$HF"gDw4i0)tz Dieϡ1\O#y,#=4i 2 -]+&%Ken薷c>cGrԃ:w0L.AO{]HX &HYJ֕tJJ)I˖zRRtAW.﮷@#Kp2u'_ ~TCbXnߙ KDqYfc0dkq}3F$4?嚑=#Az>+Vc|u26A#ѴaDЙtz1.zeETENgAќ}Ǟ_:C$77yf3[ni8I˵T12YIiHדn9]ꙺZ|=Co-\:'  >C0D -1CAT@L y(@S73?xӛz@ j!=7"ŵ"(bAC5P%7NO? HF't2vc]<;:W 3E2ȏhH>Ø:rc.[.W۝B|2 s\?H.o4ϮG$UѴ?E$8.s)HF-7$J=gRիYU]S+dzpQTɝ/=utNp<|!a@b -`AP"왌U=jJ~! PURo4z"!h>"X.~218J>J3"UӦqU䎍k9[XObow+ec;-S2-z<ѷ~دMmEѷ -#)HuD%opaڟZ|EH $[Zn!*FFUJRädTԫd7X$he{7{g\:'  >C0D -1CAT@L y(@mrZBK&7m$N/!.o {üD[F1\BmwFV+4AFVY=NYEV_#!W#/WnadsO3 - tf]Tϓ1o c̛if$'KAN~y{LSdUA;Z+ 7h 'idPk)M*'iEO ӕL]/iFjpg):akU\:'  >C0D -1CAT@L y(@Zlguq-jpFVokH$rB",nl(K:Yfka.`du**ȔhVyX)纙=mwyV)_6u4uu c{0:EVm9Ki}37odN{t{ՋI'ܒm >G,F-7g輌U*Ux*kj)S2ʵLf"d7?HKpƟW{ꅗi*q7x/B(  @PA 0E\Bwx~wo F",8h&y&Q6b,hYO/錜@s)FNyQFEN>@sLvm؃1- #h-f J^~#aq?Qz3?1~M碕5(_nb{]N!/)(wEZn!(e+FZJYʖ+٬Qg [7hLq7x/B(  @PA 0EdG!*GzQޱIzp\h|x\c(Tκ|9.W3г&흟ժVICzJ2dkC/v>[돠D9'  >C0D -1CAT@L y(@.;t?۫k#a3b!VJ&jXp ,,HVUi"1v6i?|"}f]/ȳ>L2F,V֤߼x&G=-$/n6@"{n4O-n7<7lHߪpnѦidRnm1 HRM+Y5?(WoGK8n^!QA 2(:`9Cڻ*E$2^ѧCFXh-D\EE]U5YW)[BSrb$r,]5F"Wn'rjj ךȯDxy*?g$` #WTkеڙ%@دiֹǟD|iD~&gsZwvM8LuNd&JժTY+>d^R˥L(2$r#_,]CKp=eh4ż89/ʜ{C{1^|Ge^=—ۦ閛pֵwd2.UҥT)IKRI:ܚR%TY2gwǯL#Kpy?8n^!QA 2(:`9CgǮKvk׼Oh\6EZ""bBVYhU'Ur\O)4I:s#.ח4ǡ"?,['#/ ߡkѱ$Ϩ=]Dw-Wи&];T>ؓo})]-nsи?C$?;dddM8T;+U(&Zcɺ&e+5CUURepGv$[?p\:'  >C0D -1CAT@L y(@TI{kټKE$M~@>6ƒv,66ͳDyfd4d݌HN:m-k:L4Ǝ."y֩#i7H0#W*Y`Tt,Ûtǒ7i\o ە͈i\K2O-N#ǝcI _r~$ӝ#iyPIՒT12ɤThu)Zʨ֍j˫&1}=I\:'  >C0D -1CAT@L y(@m.qѸބOlexGZ ]EMP%-b,H>ƈht0"'|2:fT]4uKOG&b$_31!JnCIQ$Pr J|k|E:w?H~<}{0͈EHErᱝ"iuQ$v_Bk(rDڌ"yoZPH-(%Xjd$43|'1"\hGd#wtu ĎmT2dH`DRHh<w1@Hey5"9ΟEDgt.zkADrTߩ9.}];[׼rγ;ɤVJXdb?L66R+fhev .px B DA4,A -Pc1QW}1ɱ1B8E+x}1n]ŘpiHn<<:%.VL>ĈpO3~뻌d*y,ׁKDnɧf6W -1x-^G$aڟb(yW6 ;"䤋 -\[n!Xy(YUJc#q*͖zF#`=b碍ta.\!AA"! *h&X<WYN=wm}!]Ļ{Ih ݻg5E|Q"žUĸkb=Ӹhju^F\!43ob{GN~kgȟ>.aZq0z>wh\# -zE {MkkmNƦ*u&ם>8$ǝ6#4rԁvdZd*%InH")U+]g.z'?M-.\!AA"! *h&X<{5֪G>o5C91k6N01bžs¾ fDXZA?F$wQA7]E$vE&v$`3狼HH#M -zbMmm,#' ?C$'@N:\ Dt3ƙwߋ -6|9.u_0}-7GR;{%UWd%H%RnHL2S3uEm9vٲ[0sp\:'  >C0D -1CAT@L y(@21.kq!N|J` N%{^KSS%DNDiY39Xn+{4#sh弌iLMFLELEr=7<^3$Wr1>Ƥ^attWT΍Dߜ^{,buLS=N;hƞ=FM81;+ʤZU%U>hHwtht zݳ4tNp<|!a@b -`AP"4&d >gi?>0%84o=YFIJ(D㍉Dk&iwȻŽ6/t~G`:"+폯[?Ů3 ΘdNۭ~\M#Y`D2Dَrvׄ}=Լ]&gccdqCF&,L(.2L_+[`>asfhL4 u- -SP0<>6&C/}ǫ[n!bseTɰɬgR9U.f2M}~Cߴvӡ&.\!AA"! *h&X<)8αw˶˺.m۲48\!eFF(haR($Bxi\ -uHXbEoBwֻgW}vZ?> sp@mlVPt5#fM? ˅ԈT$j]M-C H`,q*6,?Hq"y*`jSˤmcD>TN*W5,W}}6\IKȇX"c-480]GtHՉfZ3ogű;3[$Sᬘ,YM>}HdTPRw~%5^(:Ӭ5MCfh" \x~@BD!2( tLB7)ͨ.9OR'̪^$]=a֌djL T"B͌qg"k$r+X"X"{X" -s^ ?pco0{%U-$rP#E"{NE"||e$cE"^D҇.$2~#XNnTӴ\d\+q{I*iRLk4Ok7m;:kد,x`8A^!Q -@@(d 9u Re/ģnœ>o>KDRkF=ϳD$r--]#'z>\f[")<<3IrX;kV$Dla%͍YYyFe1ѕ2H=[?A${2[Cl|_i>4[TҺKTL[+x'EL$˵e?Ɂ-7NN Dh;8   B dP@@!n}5agD2]ۨIşgIl5cLEyZ]6!DrE4]Wa;Hr"j!k=xrDNȱ,LYG,Hx7fg K _$1Emby/]a*يHμ)#b(ۇ>}@S/h`]K[k(t%kj9/ȑ=m5+x`8A^!Q -@@(d 9~VAHAZMdGC"cHDF]UA.6_dbN"g!| ؚmM }\f6;*ȉsI8C;gZH^"#y<<9D= ܺk-oF.ݕHՏ>½H$#ҕ$'KRSx H-OT#yiwۍ~x- Dh;8   B dP@@!nȁS |N A:. sS{^}&~["#Bdb_ yA+hÂ>(SB`mJpd>iNZe>Or: [ysO Ğhds\pҺ-n*:]?)✣tb 2:^9XG]QS㟬x-鶺`?j-qڿaNPdco7?Kc1Ƹgd2tDj_JaEDH-OjH"?b'w#܄߯ygp٩;ܶ Yl6N9e|_q\qR}cX)O\-UǍƖ+,⸾S._SJ/_3W\%qfR5}"+Vҗ2)9]"9\L^3vʤtRkz"rJJUSR*rTJr__8u|G\.p B z  HȠ -` 68_e q"\G -^N<_B|K $a4YcDFiĜج=QF XF4o|*Kڈ~긡Y6_@Sl,h6ogdL1L푌6:[+'%]}nds}'^>ټKN߇ڷ S:gg湡; ܱi U2ǡT,1W5Q-Jb.+%YQՁ2: .Y8. /? !CC 2(:`y( WH\/:Fܫg%" -[FkI`5 .'$D֒F!WIb)I!J"%vJ DˉƵ_J5F\ 4_pq]ŘA̻j꫌7@bD1<ጨFT/'Dug\76(fT/d P_sFet_Q=eT/k#tFT՟c*;t§)Dk5.6c -#y,Oc6Vsi$ߣ+4??dDs411ʹ1g슈oP==s=sF=!?f8/F;hsQ='nTHj*E1#窢Z4eTjR.k1Kv<6t]x!AA"@$A4,ClpRz)YGk VGKIh- #$©HqXMH(u##vb,۷0:VCqVh=mTc6ӛuSO`7w3%=&i\ -#ۨ`uܻwۻWLND\ugi\b S4W=mqz:4܄-Z颒a0EݬKb)r*-e3J -}?qhKpnx!QA!I@PA 0<竌v!3{L#XD $2%z2:3'Me:Q'hSqF9wl41Ci6 F6glJy<#|E%v"N8tYl m=d˵՜E%-b Xճ^QdT܁纷~mt<|!a@bqH@@T@L *KG4-wcxu>jCF7bD ҈#IIy"9pll"ϨH&r:"&2F"o:Lhf|>X,Dm雷#g':{XFo{5왡nvI\# D~Oz>br4 -Kv3^s 7'kDfٲIUl\$ʕXkZr@"pQ׻Wݿ&. /? !CC 2(:`y( W\y$Rsk?w_ n|tMIY0WL5ٺm&;5r"MDvD^GyMi9Q#Y4긥cX$h$gm{H6?9?0"y-yH`مHV^EW· fi Sg#p>uWN_TD-n >֓\AtMT,bM,jJ{>HNE. /? !CC 2(:`y( W)LDRuz^P$vdXTC$BJZ)Y<5 -z1T?#i$bDKED#yIg$g3"6~#9Y|}ɋi$d?Ĉڷkj$߽$" )'#i !5KNYrEˈ$]VD$E9*gLe1?oXfnpnx!QA!I@PA 0<55yg4Bѣiƭ14qKjRSu]?HQ?黎HDAy#6yQv2w79qcL#y#i$g 2 iKd<)19=ȼSУ5K~[$/_sΧ(_y|a]=چp hsKSRT}mjF\YKYYOk-:g5t6疸t]x!AA"@$A4,Clp$ ~N's;qL'?Yd0&o'$:թ3H|q&,"M%4 %\bL0'72m:33Dg$uDI-vv6{^6 {FPϥcOvi\Eb6jӚ pn=?Կ\A[i܉g\|jr{S\F=nA{g5-2rNZNT\ZԕlJV՚.VR!ƞ3wt?evt<|!a@bqH@@T@L aw픐ӄ[Ľ2/~)j!%,DQ;%!)rBQP;%]5DD9!4&&33HD ofv4r5&rMM[hDH$f}aDmɩ؋fzX,|^NG&/H䰙H䍫.4![(lqP:WYYp|T[eiMK몘Tg{uQ,RMERIr4t~dΕiĥ 87x <B(Ġ␀$ -`A -`Uƹ&#ܓ59qSg幡FǘvuM:wޕ!Kzӷ1dΧOgš~c葷7܄999+ՌΕΣR)E MS5U|aݹv[ߤať 87x <B(Ġ␀$ -`A -`C}/q"#;cPuϨu. &;HauvINiDZ@ϮuzӅo1afբa}6c --'FX_i} -gҦeFXis{1Y0 ?_>WzEXe8gq~#܄_m ~*~Rɕj5SsI-aZ9%P\kZu ?ÊKpnx!QA!I@PA 0<竜<۾ VH >kĭD!YF%fato|^Hd?!F"D9>c$2F"?bCߵ1&rc*a3Sn@i\H䋳DZDۃDvΖ8AZ"Ot^0YډD9p>E-7^Z=̆pk=SVlQZEJb\dffp.ܵ{щ!\.p B z  HȠ -` 68_EO nՓ&yӧTCr؈$ĵSmT bhD|WI9NF"E *4?O~lh+#MIQ$qt$fD2M#\$w1"6:Z&۩8c/o蟫ʙӸ=R?p>EG /S%~nhSSriXUE- ҄\eRMfCGχ>ť 87x <B(Ġ␀$ -`A -`Uv|vLwvgTʸt%4YC9DZ29Nh1&}󔭌9M/А^@GG1BZll>rPU鼲Y|Q={%FFO -uty!md22Jhd$[Q6OY;>S;LgyN䤙<3/34et5܄_6*ZUĚSD]ÿ#l9R͏)-t]x!AA"@$A4,ClpJ•EPG>PP!Ddjq#Gٔ%UM54] uc2HmA?SHhmOb.J3\huUtl<(F$Q6#GG$A܋KZF3/F$Ɲtӻ0|كrtřs/l=&[Vj*WQDYZJWRJKz1Wb*[KHοoQ§xx`5.]A^~@BD!=$A dP@ t0 P25Ǐ_'})cc -u6c{WCi&gE3y#CRYL9vvj6]_q{.e>HΨ+C\I#bEw^cDNF$_oJ~~2{z[.%_%/UGGjH3 STɛfqf@o$KZz`m\/fD2:ًiWf& -dJncMHZt,y/0"Ym!d% ɇh\~?C9Χc,xnj;O/.l URSTM֜e{lNKd-SN遃6އușlۧq >C0D -18$ H *h&Xи}z#q!Ļ_N 7HhC -ϓ6gVHۧ@ D^EUD}h[눹mwи&l4׵g^JsJ>2Z{w/c2 :_FZ'Ӵ~N06 -w6>} sr i4\ |jrty™ynOi7܄_@ӭKrʤX:+}ՒXd2)j5]ի,g>%]NӊKpnx!QA!I@PA 0<竬ړ-|' ""e$W~~f'IЏù~Jn&JHܼWZ4kjatZe4~ m#p;vvTG'klsЩP Y躼ci Mzh;_`i4kCpr3LN&F1,pl >ZڪkZZC1-"rjRUմ]?h/q]x!AA"@$A4,ClpĵIe8V( ,'e$7 -]ue$q"o" -uiلvFZ?Ok뱌B:FZFZ;o{u@q.\t>#/N8Hq4C.S[)p8o]?CZG;0:ቯΧH냪YsMcQ[;piAHJF֔ղYQͪEhX%9Lm`hE[~S\.p B z  HȠ -` 68_EEux>ݯbpa9G{ˁD2)cpU%J uC<Ę#Mdi"ϦKc$Gm$rwD̺8w?~g4G0":'m쯞3s7nU8:ոO|vw7w F$h >nAtvh%Y$9]ˊ%Ujj-U ,lEGt<|!a@bqH@@T@L *Է I[MG)ԧp!.[JjizQ0fkf'jjy$+i$_jy~W;::S+ۚUI}_kWFFڃOλiO6ȃ/Q%̎UJB'u8W"9G# S;?[i7N{ey~bUM8OUZ/j5*:YUWU$ufkZe "~ѽĥ 87x <B(Ġ␀$ -`A -`^I_"E!$[izUЬo2#_#7o$\GGG2"9HGZ=p^Ez5h\O繡a 7 ljݸV2-]UKbF.i(Ϋ*MS^șjnLby]6. /? !CC 2(:`y( O!HNq_N#|WO N# K#cv[sR|bbI^!c> + ve>ev1Jh.F.o#ξf|V}cv0]wF,x#OV6*fG]Ccz\׏%A"g]XN{p>E,/,<6Tм7&Xs0m6gӲ3SRq^SĴ}M+h,92toO2t.p B z  HȠ -` 6~)_:QSxk9p<9HXII0%RQk3$Sk>[rg0MM+ՌDzH?~mּjHv2Ɠ[:4?6bDKmTʥ}#`<94?I<"}H2OR2Gм\pBlpVrE%SUb:SՌZR+Z9WJirY=thĥ 87x <B(Ġ␀$ -`A -`ÞC~L%;/^H0O%Ip6 %9C8UQ8!]DD]P?bNMeVKNi=O-m5pu~bg4F?%Z?Ѱϥ=q Q?v=a|lf:g SϯlqCH7G[7܄P?[tEUkJM:ETbEVRJ.g;?7]A^~@BD!=$A dP@ t0 Pݣzmgc) V+ -~겡jLSt(>H&GEk4e$%BG鼑;"M{0Fi$71&Ns4+qC.\HfAi4 kɧLS,H=mb&G$2%-fjUgIIVt1*j5OV!wlo t]x!AA"@$A4,Cl0y'|=zLF9ad 3"$ˆZ$|"yVeb>v ǎdQ%GH^higHO#y4I+q.ןJ.t3o(ΧNUrJ&5^_U+jUԲY2..lZ)*r6ptuN{t»_--.]A^~@BD!=$A dP@ t0 P25ϠWM#ɡ+3Hd&NF.gt"9QM>ys<YdҹCN|h6钂m%h8f^M[7[n2vnaƢ=$p-/MxO)¹s0ƛCbha;lDM8omH18O- #*rSJ*hCh@\{Ʀt<|!a@bqH@@T@L aFMӒ[^ !+^ZWj:GBApK~1l_Υh"//'1?h";i"F"Ov"yB6\CҢ/1iI׻˨F$^3>vϏH>k;Db-<$ˡ \p"\Qh\Sb-)aT\TkdzJj [H3*w.p B z  HȠ -` 6~U;mx4k -I;^@$u!__R`y6f/+,M#YaD<:|Y(ۈZWkuT{-HJ*Љ[U6Zod -4R}9p- » S*9ʇӲO#]p| mIUɕgT{.LZ)RQXRE[Ћt,.]A^~@BD!=$A dP@ t0 P_:=;"uIu95=휝 7Um=eUC0r)ղZKJ6匮Ӳ3U]r׺giZq >C0D -18$ H *h&XPo1L<Jo"%ĿV ?V߂I$$6J",'*"o$r>BD_EU\tQ>JG7+Mz#f6Һt~&鼪Ym=+Ӵ7^MkEmM!XHʱHC[54D~ur[0]#Ч@Z7܄V4[UEr1AKT)]IS@m}E+|{:V\.p B z  HȠ -` 6R'k{-&U'= '$|}:]g؃$8I"F"%ˉ1bn+]4KiL:0MLkFZFZ#0vYںi1WҴgӴ^Dkk֞6z&E&Z=kYq'sܙSn} wNk ᬽ3ZjrI,jk&.ʩ\TΓφi,\iËyt<|!a@bqH@@T@L *R}ɐNzzXA% +{ Iq=%%iTESuPLى&cpGt2h"D>H6G#+18Y=fͽV!/(vpL m,MfXߏ^E$o/S!a7&$;i -e7܄Tv7[+*U~\]guF^ʔӥ#K!i$q >C0D -18$ H *h&X00Yw}_>%a&mA#jf+ -aG"u -=J:uWo$#Cw߃;UHVH7jp#_ATߺ}=kw<,z])W,yΗQ%p|$ӭ߃[QrkY{:zlYӹ+QTبukx p >C0D -18$ H *h&Xذ`/g Jmy{{sQ%Ј=~^obD4 Pi7{oA8$ƨ_dF Z%'F6"ygOU,Yl7Fr$}Fr#m\od, R{eL -}MѸ+#I^U6䩗Χ =l;-q."onAֺqUjl5tVjMϊZ&#r"WkzUXX=:׼ѽ㶁*Kpnx!QA!I@PA 0<^FZw-y,4-Ya9bDz꼁 IuG6uِ~O#ߌ*O6S2mDk]-#yi>WF1aj -Z$_'όDn?Oo6UQ$Gΐ>l -{ E:gvHHdΧ&\72ϙ-.pasn[ɕlJ+T.RSzR3\:=ջu[Ƕt<|!a@bqH@@T@L a>قD" s6poČB^ % P2i0I<&=YeNӗ@gJO"[ %6<#RF$g+#"9H&[ߟ`iQ$KNB$i܉a 86O1 8gkS_"9y[M8M鲒uJQ$U"rNeZKi&Dr+ݳW ť 87x <B(Ġ␀$ -`A -`F#)y [>ݯԠHZ~Ob(He}z/lvHJY"Kѷ|kr2stӧ#mӑ1D;Ѷi"mS4DɝOcʶKސ!gqt$[b"CH||jr' #?>熞ഭ 7 Hu,eKz9KU*+9Q1tv+eJóL$W^4:ĥ 87x <B(Ġ␀$ -`A -`ՙQܲ'Dh["i=5닁LA4TtL23Dh"3Fg jm&0lm|[/̷ i$'_v HYFK8-Fp#H" }+E|^߳H,ZJbߞs;=)䋏Z\W9c.|v&ɝۤXJմ*J9(ZEh%ִR8v="w-t]x!AA"@$A4,ClؽD.{T d!ՐUBR -)늊Hꨓg"yXIh:ɧhvhcQ%wȸf;:hLd&IF"JFy"[d$rVE݉M 0z]n$t3FFm˗m 7 aoLKJtYzyqmYNWjjkxu% 殤p >C0D -18$ H *h&Xذg:($G ~*XI$Duv H1XIˈ`}UDy8c%1f>o0}NhkZ?|FZƼ--}F)5Ku6]kH듴~~Hô~QH -VG{<#'|#E=1˿ўuNyBZ39u憛pRTӪAG[.%]E%*f2eUKU 21q 4q >C0D -18$ H *h&X؏x\=ýx~l!hH!jejB H& 0f -y~j3u_b$h#onr 27{ i$g0VlfF$ϡSҭ[}uvW݄8Z?>azDw5$pg=`8Naq:-ϝ-nA8ǠB2ƒlY+)睶%%-*jXk%]nXsעk79t]x!AA"@$A4,Cl}t~NpO"8t1}IJKԒㄱ4"Qu6Q`1k͖?(,F04#iL09`xX ي9ՒkQ8#\>juZ\2~9aI}rs 9rK*s^8[Q* 72eVUZE,:[\l9-fZzYj_,t<|!a@bqH@@T@L a;Lxad+ AIh o'5$il&^hl7i wEn'r%\O8;FZ}=4&}|r#6J͙5sL>5:YSVg=6?H6nkD^?1trCv4{|jr~eq;Qc_~&ZӀ2VJ93"szکjRӔZfw.5t<|!a@bqH@@T@L *c]Y}vF:G[^,. -\KLIN'bыZM5l-F*`r]f屬cژ:LhMkhfпMo鱳G3uOSۖۺoc& #m__CZgnVΧ&'$F\Bo҆pSm۪^+e]լN95UZ)SIgzȇmnk9M%.]A^~@BD!=$A dP@ t0 Pkb9g|OM &$T LNwNI\CĜִfHg'0ҹY40kfĵGnmϗ+6?Ϗ-!F<CgHgxnjKNq yw;Wk\lQ(GΧ6CWR/_xμ&xtZWj%#*tfR5V,*ZUOh<6Q>7WQqOYc11cwz^3Y@ſSP!iCpݴt[c/ ucV-PjZ(j;+_l}'6b .p? !C:! -1CDA4,@rZ 2p+ 0ِ֪ 15.' ѐJV4C ԗO 29C0D8$@PA 0 d!y}"m:.3|YHӊef5#Ia"585D>OKZHnJo㗶\{OQN3olEg)8ss F<ĊvnOgɳGvSh;wRdԝ#q7JYK dn^~_;!NFwQ5=,ײvr2i'PQj1W_XF9cmg+/r*2+GDAz`u5MGn҅+J[Q":x}u AT_Ϩp0eCVeqd RN+JQg~Vͨ .p? !C:! -1CDA4,@rW"G 9_Ψ%OՊ:AQ -V(4NqH')>\7K0)թmȜ"58kNZWI:z9'PQwZac@{%vN\o 6qyag}Mnocp='z> ?d -f,Az{#J#(BCj-qڿaNPdco7ο ?5kH15fd20SzZ*⊸HcoRjcZ֥j)UVE9|'NxӋ1d.ws}oYmgcW-^;O(Vf_=oP.=T)VzfʳՔ$I%aV؋+ųfz*g5Y5e%dXYe)-J:%I9\^? pG$,D&?EɟGpo/'+6Mlor{ަimz{fYmyg߳i#?[9Ou!?ۧ6[ߺ{C?xo{;[׎ -~[n7[[p~;kd5{g'l_d[bVo0C$Iє#i]Mz%ZH{LkQZ?lnk}Z琴=áѴ~FIkѴn9t4JZBZ )i="{gyt'u -WQE7"k4xdO8bڑHڕu7aoUʥ2MӚ)HZQSB6UJ -j,dbZ)f2Re~au:O\p B  H"H *h&XwŐՄ['4G(xJq=KJ"RRY=iO$MIh"(Luj]"Ȯ;E6Z.I$K)h$D/O=B Htɳ'~e2"91zAs(Χ;LָqZ<7݄\*kJUj)% "g\*URYEVEѹH,t<|!a@ qH@xAPA 0<|˥!{GCHꈤHjI)XY$C|$$RZ\|R$cF#y~;Z䫾"9.%q͉EL⡣|ȗ&dҺ)ErU_<7ͧ'rJ=ׯ1o| 0P_xfd~t29'ޓ/b#QFc|,#,]MJ,O rnh,b״RQ)(6ܥ凔\s&'yR)/iR4e-";a[\ӥ_K)ry"4mnjզ,j9Er*e%'iR*UӄLTS%2kuӫ%CM\p B  H"H *h&Xzj>Ÿb??ݼ4t x!AA".AD@T@L 68_ekS?,,n ]Ƅ"B&>=ћ,ĹRYWe6_7v)h^@4R,Kh"?,PY-ۚUF|es3G%s$Ӕd~+I=]c! %ij W3B6KZ)5H-?H2q: >C0D -]8$ < -`AlpJՇ`>3[ʾ1P N^GD5y#-͐*E=F0MIWI@IM%@IB"pzv,}?.v9%%]l$H_ GKZy jQ+#+v ."L/םOHI&k|<>WwR˥w9sRXTl%]*i7n{X,t<|!a@ qH@xAPA 0<|. Nlw -N -M )Q SITqdɦbfFyI䳔gI"$q|ȏ t`yKD|6 W)ɉHn%<Ҿ\ߣHn@,2˿\|"h_>Ww>E$#`ŚI ER&H*͋d#HtTE ,SҒPʪ)YͨE/=ׯn_yEG#Kn8!Q!IA dP@ t0 `UlWI[~B'=Ü0'd˓SMҧ#iH3$sHD-ɛiZ$G$E2GRIɣH<2F#yHZ.5MFG?#Wѷ>|"6|k~UrSwkC̒Z-,Tt}" *R*Ų\-˻iwtOHĥ X7x B(tA ␀$ 2(:`y޹@tWӓ*@"RD*$%dEE2Q%EUd]uD,+HΡD?L2s({$i!]{~mQ"cA;o.I($yJ#Y賙<ҷз}z[@"W?D,i݃"7 IrW;,MV_~q4yƳu7a$l%U-#HRIP*eXTj_޴buM\2ĥ X7x B(tA ␀$ 2(:`y\Vtf_{=^s]|%{" _#STjz1DpIH>h^\s*ey3| ܣ3'z'QOf HTQ$'Pw[Ys9e<;DžNz`$b#SU6vu2 Mu~4doZ󽺛aYH+TUN̠p*frrQ+faVZ[Kn8!Q!IA dP@ t0 `U=W,11̌EhBNgY1̿.>v"RTw>5c'l3㾧!}g;ܼ-`:+؅R%-`)2JZRX\H,t<|!a@ qH@xAPA 0<ذ/BQ[xlЪ|DZ7fRMQlTiz~wJTC0D -]8$ < -`AlpՋH2w0LЬί\8oz͓e2_dEo,a~T[)50ETOx<݄}ͪE(%g&hi6[PsYlDu[Kn8!Q!IA dP@ t0 `ULnѣz:^R+͈UkS@RBL|SJR'N"%J$sH)|Df^>B$M#,3X(s6穏$ (J4Bc$mىjmDӇ0ܲfݲ< VXߋh^iڏ東a6_nPҥJ"TT˂&HVB)SȎau{o2z.`/p? !C1CȠ -` .k;HD@Z!=D %:ƚ(.RBY5Y 㔱gKH+[HdMǚZQ$PD$5#ED9n>xǫy[(-T"LEUYAQr5)4elg%"9ne$ ,.]B^~@BD b$$A4,ȃ ;W+dmxdoSj0‘k/(xREDRKMjZQ"yd;%dDǔHNi!3K%r6\}"EMIWH*HHf"9R$kH6 o(wlW'^H Hݽ7mˏĐ2wFMkORH90[ -Ey+%$SbRٟ+V\3qHKn8!Q!IA dP@ t0 `νC09\<90+Z{#s:ļd/?O`yrz^I]{WF<һ2X"J,Ola\y=ljLm?/_2eP*äRn N00H_Ǣy+^֦zh^ߨ3X>d'XZ\݄?#5_SIҥL*#TT19U٪RL -ղ\[+흋t x!AA".AD@T@L 68_%Yk^57Q&W2TBp<=(qҒϋzmJ C|$$)ڂݣQ")P)״R썒Ke?)Ry/deqA`l3&vAZ}-cwF@xt䥟 _J1M\3`'(y]bмD `rJPVSe JV(8'"Z۠`'ĥ X7x B(tA ␀$ 2(:`yawΚXm;|W;4%<)rrm<99.&$[&ɺb6eCJ$Pd70J^E"ybh$J$Ok!"PQ_$O' $G5O.}(Erv - /AuRi^DeC"_hpe+XH6VγWu7aO|sV!=֋hcsŌPJeɖ]7;Wul\p B  H"H *h&Xv&Ҫt{Hg+N-T;6h -9%y2?ɼl*STS;yxa"I)$C2%i!"y[H~tMv]إo=R$!NM"wJ$;[K<ف[ -Dѧ!tS1YNM캛#(傢j$dٲd ,A}L66܀HxsI$q: >C0D -]8$ < -`AlعJ=HJn#{N%J@ DXȵaq)'c(ʒ$kl)Q""y@ OpdsMHuW^"%G.Md}CD^H}Nw $D4$< '܋uQƗtSM HP$?x&U$L1%USjJPJP*iZ\f亷.B/; I$.]B^~@BD b$$A4,ȃ W\rmɝ}_(A94)lFڜf5Q4P&fa4z2Hۛ`DDIU dεȇ[HijFB۹R#̹z(IOjrJ"hFu>} <{Av~ȟ/T#jh[\DG"oM %u7a|U࿡YqWeZZNI}U+˕ް΅ƶ¥ X7x B(tA ␀$ 2(:`yai:-wjG.!=<%2֩q;1-@R&Snu֭wS?I"ߢHHȃI"'BJ";ZxKۺ\6&m=H~L)=c5S"Ym!Ձuuɓ]L P$g G>;"+oGuv M#ER͔bU -T L%Aj&0yzVʅKn8!Q!IA dP@ t0 `ΕuκIh[YC0D -]8$ < -`AlpT;Ӓw7E|_Dߪ̰kgZ|BLF0LJ T5QG|$s$Pޮ{U$E yUvRצ"y\=䵍_^ E"iS"\|pI{hŮ ֟c(y"y5|縍oyz~D2C`ŮDrh&ɝ̦RU -W rFU0cڟEYW?%ĥ X7x B(tA ␀$ 2(:`y74;,#} #o {?Ym*s5- -*B&LR%έ -Kn8!Q!IA dP@ t0 `UlyD4/*56,P/a,p^ k4KJ*m:(L׿5݃ tvgV]:%UJ$'2u*y0*,%S[p}{%uX 3wօs*=$Rg2 Ն*kv ݽ&uͧwJ)*BMJ҂VjYrjZ*cG\u;:\p B  H"H *h&Xvl֖hɫq5hk@ZTɤN~P%]:DiJ$$<٢2n%P%Ffoj?v6 H%)ezg YCw^]omGKA17H 밳SEяH^|H~j۟n U@Md&WQ$)+K*(E-'s%YRTIWrXi\;?ͱu0a4h\ѸqF:u0a4h\ѸqF:u0a4h\ѸqF:u0a4h\ѸqF:uBݲ\URXKJa="EMD2WP%5%%)*K&pwR"m $LYMHlgw'%@{_e6wE}"D~B d(%g"mM"Z llp,NPoՐnu]H'!_ xd@چ"9κGdӪRȪY!)R*˂V,TII+r\hcl|y+?۞ X7x B(tA ␀$ 2(:`y*Jo14зj>ٯbUf1L&I& -RitV)< %LfʞmMh66~H)WǒR"SR$/%ɯ0?Uɟ/[s也 [/B$CbCwfd9HN {Ҽo-l$+BUt(BjNJwnθt x!AA".AD@T@L #CI6# ->ͯl %ED2_[1R\VIUkJ"ɒK]$[Q"+td(y %}Hִo<臭_Jgh$R"y=)ɉ ;}?E)|*9G$~Xecz#"m䫓1XIgn>X)g N~$Q$3vΧHQ#{A$rڎ4I*V2jI3(t dsBEn[7- ئt x!AA".AD@T@L 6\M204j -9(rm$'Ԥ̫bR˺"y$]7DFIxҷ~vI)B"i^S~msGDNOLjd -.-$ϝHWOsFb]E&k.`'pT 34_q)HZ)%Uroͦ-. )%S ZUHc/r._;e~;3wdǧ(g>s'~-.---[[[{c~gn㶏#v@[{~ @#eu&(!>e\}nÅg~r!#mvR+]et^Hlg'}e(; &eچbzTo&hlp|j@6Z?)ѽ봺S͇rP,9A& Z eՊU+cǍl{u-w6F.`/p? !C1CȠ -` Y-{ Y>ӯT4z؎>j\Nt'uMɔV{REI"=oR$2K9YFB}J"'=n ɶ[l"y>dɯ5Dr䉔"1y?K\~r$H"y&׋F{Gl$:WgC嗢 {?Zg.BJ=$=UUd [+JV+eDr=_x? ĥ X7x B(tA ␀$ 2(:`y_xsdj4vկĠVbRdR%gaƞ*iID2mq"0D]{D~- JWۃ6>2Od%yI$ߣDr/̣3[F$1P9y9$]םO V[qVQ%O]Xw~/#*)eY(2A)UT =E!\[ONtI/Kn8!Q!IA dP@ t0 `Cmfj@wgfL`) _D0Y30~~Y>_i3~czpf(T큔p'`Je%}-,]}@Ӈ'`hzMwd>d'dbM`{[(7zم6}_"ĥ X7x B(tA ␀$ 2(:`y*3]3Yy^n9_P 3!&;r yqNz9ƜF;D ߛn9itZxq{XjQ:8hsz`m rd˫|t^vW$E<:d ?OF:uSзQ9g~t;nOmblYЊ$ȅsrR2-c˥N:&9t!CH9t!CH9t!CH9t!CH9t!CH9t!CH9t!CH9t!CH9t9A-n?-0#874#|Z;ӑ3㋘D/}RY 0Lmc|*DRMI$PRY M$D%EXJTvdvOBi"g4;y+VdD$J$76J$J"9DEP$ҟ^]ijǜ:edQ0FDT65tS<4-V8H{&HaVHBJS44E -)P*USSezO!. -osl,.]B^~@BD b$$A4,ȃ ;_tF$'{uNI~=0)8)dH -WZ$dy2EIm"9p/H$|IlTY8;R$/ha6E\({E\qũ7J|h3,B|w?瀼Gan[aӻlI-i)%-%%%(TE(dRPY+f -؅[|ߟ%cK\p B  H"H *h&Xjۘ0 -ƽ,dnoR{\274/,c=#,k /.czFY8X.fchxS2%鑔&H|t%jt.g.._J"_P*R9StC 1}twxE=ŧGeF0\9a4Ld4oGLw݄}ȠS2\\AuX(KYEK?n;&.`/p? !C1CȠ -` XjSE*gү /p>b訜bRD$U+fmoJc9$$Yw)Gc~i\DF[G>*ɮ[7\=s)TC)}fJm!/kp*g{"i`3+{d=z` -"yJ?";{S9"yʺat)) i( DR˕ۦB6Q 4V9@3{u;]B^~@BD b$$A4,ȃ ;٩4ɞI^;Ɂ|pJ -ˑiGSFd>/N4EuGm>l?)1R$HB{M9yH"~J܏TN--ޔɳM/G܁H~8=cHΧ;S_ x]Mͬ|#lAKW+F9BY0 -Ud=(yU'؃\p B  H"H *h&Xq#3Eg7 gF3yyd?+ΒvOW{>XK:0J|˱##>WPr)[Ms浧`l.CݟrH|˱X>@%´Od c̞;F?byJ˛ -gfb=a1M {?{8렐Q+UrXr6- ) Rdb:{Yr5+~CbKn8!Q!IA dP@ t0 `e8C̅{g1d~"&0 0U? _$2W2Y3W]\^.avK(K$4Lz8mJJ*xJBl!oMu??in{/P63K"JDf@r(sNn!`EkQ9GD:x4w9g\TN"E`ţA33fvDDt&=l![-KBYЊ"v-J$TjQ)ϱE+ZݾumwLY\p B  H"H *h&Xv@m^{ }y/A;j:[q3&5^E)fTUUl4C˻f|Drf{xZ\fG;:џ_vP"LD$olfl!h|H~Usd4;gH.PeH~V՝OvUmbgC; {=ԼjBIVAdUA +Zhe:`uO:j4t x!AA".AD@T@L 68_yuS6d9{=70LY& D`;`ģLqĈi3#(u 1SLe탔 -Z$ |v|n!C̞jS~~O)i"PҺ٠58a=u";!e眰wOQ@_uμ<[w>E]d9֝gÇQwlP*|6HSՊ,UgEPP(:+j6)c=G(=Hť X7x B(tA ␀$ 2(:`yaeUP3joI3'E^OhIż˦&>F(t-"J"Њ(_Gi$J"4D~$7+x"g =gO6?@"B",Dn;b_~^YĨ'G݄Ȭ|)3B 2Ίb6J,uͥJؾy;ƨs?Kn8!Q!IA dP@ t0 `oٹ߳JZژ'sOǑH1$5D"q,/ƤS5%ĮЎ2?$rv7m$_$rq +F-m}|>b f餥@ZH[hiA$3<{[olnt MQ#Oc;!"yV=v/ZW݄}Ζ JZ*jg)윻Rةwܻ}?'>D.]B^~@BD b$$A4,ȃ WY8`q/a<2e w?[W1;L&,g8-mL|YmFh /eěi#dzk㖆ӶNokn#-A #rc͝Joi7P -ʦ}ЕݟZH0^A=]ztOq"E;eYi[moٖcif}vvi\e!D0tܙ^vuԩGe\E e%!vQ`/7IQDvB=s#peAdy{AZG-,iG]Úmr%-s?zz'-_t=_@5jI\(MZVcz+tl؃;m.gک[I_ 0D@(  HB -Ґ!Ȃ9PAL ` pTvH W"Tl̉ӄ$ STC3CQ(.o" O)A 'mA"䓾wbD=stB⾚|rTyDt`M; ˏiwo\.세e5"ȬiOlo=9HxcI%nvcZӬ,lj4|Kr6KVYD(ksn7/t AA" Cb$!idA& -PPN/h%d -3vG=i5٨/}\kN/hA$wii>i_Cܲo,g.{&VՂH>~*9Gcf{V'~Ś~H/7H6liִK:[W2G*kI+?Hz=^+yդ 'e* zM}Բ)fgD_M}{liH!2D!qH@R Aȁ -` l  -5bR atړtSL^IjQDs*W6Gr_LHRu3=tJߣ >M hM#GՑA$~_c/HNl)"y|g淙/PDrj'!gP%OH+~s=:=H!܀yb6F@mkUhcJEXlh˲ߣJm}-MxtO<K7q+>mLZ脳}|ADݸ`şA0=9qlGuTMNv<`z$]'ɨ&q\G|gvO/v\R3|]pطt-ieiO]\ZҾj-qڿaNPdco7ս?=K41i1fd2c1r-EW[,HWD\)*UA.RRJ)ȥRRDhm ?p=@cfy~>̙sGkvOL_j$l'YMQ8񿖨ܠ?Ѹ%:yva}jr☟f8Y.9SnnC[Lk25Mk!&R+Eմ^u^)UJ*Mwl{ҊKpvpxp<!AA" -, WAlSKSs ?nsOK7f`hWlnf[Nf}1ݍ♦DY$x>gs{ TNg#nϿy>h>Wvb>,F>47 ˧p F>eIYt#75ː,| wrGq߿PwN~>D|VR"Ŕ.xIrZx\*jss纛--`8^!QȠ -` B`}Ķժ}'ql#΍B\/:@o%!M$JN;Hl7$"& Fo'zb=r40k4vF\ ZM:qV q1P\ۿ Gڞ_O]4guM+NLFzۗFZfu>^p9Қ"7oCZw]nI#4OMN 7Yu7yբ^r*RR,(&jtZ)蚮uHޛ_xi3M+.mANn|!a@b 2(:`B?#I}u-z߅O!lѸyΝsfkQ[ Zi*z_yzM8iMӚV˅DExJ Ţ(WŸR(%IӺ6;6'7x >C0D -1@PA 0!YA2ٶ}{cq"BC3g&8 %VXtIl0H<(DC9D6z9I=KiRc$u0MF3I=Q'&5ꖕy>P)4 Q=H1ZQ]N`D -=2F]碰vo!yQ}D)zgNda7dz&jriTdZ蕪)֒\ӅTZLT+r\OThTGa]rŠKpvpxp<!AA" -, WIl-ı8~3q$ijxwZ_K/k -C5DZAUDYK=IB- iCb-h\yF\B\W4dkm_UCM+k :ju0Zn!*0ڟ髬6s Sc胧èupnɯtӸxR.TSbPL\LT5.RJJIMиq5Mmqť 8;8 <  B @T@L@rEq=8.]za'n@qwaM8iMRM*WDEr%-iQSJbhruՊey[t\x~@BD!H *h&d 9ȃUCrxMOk8cQrIe%ƴ}4D.wb"+}#i%R$'&rP DފQ$Xu4g3"ɾHaL$]F#yS}"#?˻:Pg[ERxH> _@$f֧&'y4,8߻[nɏCS-Y*ZAwU*]ԫb\Ubc.k}F6'7x >C0D -1@PA 0!YA"٢d3+1/FPB$H(B T"DRh$H^ψ/OOH^}Z_$OgD}HNFHlW#yq'FKQ%?IVɿ0"ߦɮ#1a騒oC$ ߽{ѝ:U3^DO{y&HCd)VU-!&DATSXuEhRJKdH#eo9z -¥ 8;8 <  B @T@L@r%VOt^mhq-$)^%ĿzIp%$Kba!y6Qu1}61f縖A3Zmi3ҺΨ}Ⲇ#OzQ?' -E?BXvkiЁأq|s)0 re!W݄VyzVb1Q(j\PT))bYUɴVԒ+e CVM{!6'7x >C0D -1@PA 0!YAڴZKUys͛|@*g01IP$$JZX ٌ}4 Vȳ<&/1DE'$DV%9&2H+th"wDM F"l1z'f>mCH\xƝ=(gʰ>EGqnɟMě&$\DEKiB' QNzA. +V-k_Û:%Kpvpxp<!AA" -, ǖ>equh\#:lX.$]wlM8n ܔ*բQMb:NhzIK(xgtsť 8;8 <  B @T@L@r{LdU_09~k;nw/Fuu•yrF4G|44kn/g3飳Etw#緐7HYi`ayvB#y1#I/ki$w2x n$F$t|/,"MW#7OMnfK/rhwo)-jn(-U+hw+bBLE]Q@ 袕ֻ=:[Kpvpxp<!AA" -, օL.$Yֺ^/"EM$Y&H!ByQu6ӌYĜ{\<ɨ+i݌1RZuiZ7OgTk!g>(/g!tJw1G)pt'MeM K)U7g9+ S]nj/ƤCr'OM8OYSjP q&bQ*bAUJPT4Q72݇iĥ 8;8 <  B @T@L@rdm2G)+<7SQ7l#zԨ=pK)a㌺ -%zMD-='r)ߴn~a+{e8f+;Fw4͌8ɿ?a[he'w2!hqKK*HN:]B=EM SLK߅yɁWsonWM8TKJbI#I}D%%ɝ.k~?ĥ 8;8 <  B @T@L@rkV6b:TgtDR*>ůĭQ*"i"U5MGFuSh$5ɱH^OHC"G|aD-q#YQ'5Jo[9&esw]}H|h'{#9_rD~5jHM Sr&񢓽`IM8OU+I-j%QKi2%bVLjTTSAɾvgt\x~@BD!H *h&d 9ȃUf k*h.w;mgWL]D|󉿧svEf45хDC^&Mc>1{ٞfoSe1Z;UAn 3Εnv!Ag@Kh7#/_mo ;Ի?nvC-)_2\1;ٽ%tf&ҩgⅲ,'$KjXђD(rP]!Kϴtt\x~@BD!H *h&d 9ȃUd[1%Tx%ëtЂQ!& 1Iy-WfՏ&|3LKgt{tHb$y}('22ϮUI#Wa2d_$Ĉ];;vD$<,Èj\_/?bX"K3%1tnG$u7Ny$UMNQ,.tUDUKh8r4+Q;{ID@ݵ}۞[ƅ^֧&'}bG֢p~F݄Sy@)TtM("XZ!iIg/5t\x~@BD!H *h&d 9V/KF/>ɯM%EXVФn^V5&2Gyח#g^M*#A?&v_oT3tm*.<]tf6ƨH~H5sP 3GzٿwюLdƝͨg S _rƘ٨n '?jy\LUTVĂ(B\+U%H/Z/I\ڀ /B(@ dP@ t0 d!yʕ<"kJ~+yF{_ flԈKJ13;|siHBO\7HniɃ+HN3#*#6:jpWRoJɳDrT3U<"k*yH8ݰ>E g,EDr\wN~$ͫdZj( zRTu]e;'(/n^6v%F6'7x >C0D -1@PA 0!YA]H#k]#ݣTt:^JUdTɒ"J֪uV9̧J#Kpvpxp<!AA" -, Gǒ#I>ґw燻MOUrdjnF9ESFYsm75{d*#khXrF$w#mָίdF+Xc;z"Y$onJϽIg \LIq}zM_:mDK~Ud֧&'̐3\f܇Y.и^P `Ƶy,TtQNr2jb*QЪx#e7F6'7x >C0D -1@PA 0!YAb2HURs꾈?jeI=Q)UU%cFƵ=0"ٷ*N߄䋴Jci$i$ -TonbDs$NN&(ٝ3}['P$0doзP$5E2{~_"}O"\q{o=a}"{l3},˙+H>쩻 `uzWHʅBVW媨ʪ.EHkJ4Q$umANn|!a@b 2(:`BKasQ<ǵ,uݣ=YH %3Qlm|ttJat|QGj7N_HM"NPo @?c,l9'{:_DqcKaˬ7 #f˜s7݄I%ą*jZDRTz ߡI$+Z"',7-_־jo?m 6'7x >C0D -1@PA 0!YAv[+yA[qm#ijx7vvk=AFI6%!v$rbpcP7mi}41jeY2P gP}Zi4o%t*M뼦 XVjPѭ_2t!?L 8;8 <  B @T@L@rK͘CwKwkw/ZY#E1M%S)z#vP)Q.jT(I1JitER틐'9d3`8^!QȠ -` B`}@Gk*(.ޞ+Hm_hDcAAbrL faC0F(F"_7a$Ҡ<&NF"D[[Or<U}"Db$,Z#VtF"?v\v\;:6nb$cS`-˓ S%[z(~_?RwN~+jƭЩɄ -ZDQL'eQW.j  <:0OӁ'.mANn|!a@b 2(:`BC0D -1@PA 0!YA>Mľ86v]]}v# gj U$J&YR&HDP+z3Q7mWc;14&1ҪӴDӪjgBZG֯4Joheo32Mk7iB:ϵ}?Z7M#+ۦ!shXD>?!e\pM8Z優녴HJ2Q^.&luol4`8^!QȠ -` Bplg]VZG0HٜoAS;~/ &у${ȻvDD}\O=KhZYGghZϥi3z{ i6_n'W;hZ1ҺMzMkgӴZHkoVN_~f:&5@mS):npϣ^ctpWT%qE+,z)8^Lb:YJ?ټlY{O..mANn|!a@b 2(:`B<#n>8f =xfo[[NbL&LӉRV'k9ĘlhdL~Ni@f[`&Z6*'ze,4`lzUXG/k!#/r* tU:֧&'_ڞ?.PwNS-ūTU N WSbC0D -1@PA 0!YAb4IÝd9_ޟ ɲlԌi䕲HZ -#h$eD2HثUVZ%wWndD2>WĎ;n]E#O#yc/i:wh,GU^^z 3A$78?bUKY. skXJJ3\ծ,wa9\㱺p|DQzJZUPԵ&&t=$j"^ʪ{zo>ػN -t\x~@BD!H *h&d 9CeSbf}q#jm!ijx_GHkD6n#ªJ5~ۈ[I[|5[Bq6g3zq ZBUߵ~iI2cV%DH}szt: -5/})z,,rOC}nTM8BGr9KW/EJҪr%/<}m9qi:p.p? !C$A42*n>Z7ٍ!x֡͞$0!8hwdltJl0EIiuz>٘NN$cnlLAB6x@A Wܥy溛p -RTR\Tt1F"U:DLKb1.+r97Z#cqZIqi:p.p? !C$A42*[mJ8^"m_E\{lD[jnRVI" z""D]GuDf~qiMj4#;iZ/dBZ_!o<^}Z'дHk抸#7R"?$*i=fD?Lȇtf ]AnF"kaߘOH-}pߕD~&HFFoMeDngD Tk p\ky#[t&=% ڷ&7OdS+z\U9^ E]Kj"/*t}#љ2}mK۸`8^!QȠ -` BptrH-7Ss.mzdo֧"Fmv*\ۑT05t!M䥌Z#oe$&rj{M2]-'W}k]"'* w6y;ϛj|o=q\D[vFF C#g68 v~EVҞ -"7"Yz:R,lXbڗ2ܥ|8Ex&}dղWK#rZL'XVJ%(WS8 _s`Ǔť 8;8 <  B @T@L@r,?v*bR[߷69Ϛ @wZK"ϐ $oe-QmMm}_/15JM bzBsҲN}ⴁZ/7J믖y=9n-홌MfQ[hi]7f֞؃ . Sߘ.4]rܹO/ 'M6O?PM Q.ʢZ,tBKZ"J5@?DK;a쥓C`8^!QȠ -` B`}yK!XLkkfo)fR+IXo B/y5\ED_@|LF+7ak4KLx d}4 -ςyG )FXnUtNj!O!~>FXw~V.S 5OMN rŸM '=C0D -1@PA 0!YAن׎V:]Õs=E:]%bt9[;z9~K5?H^Ȼ?!H˧y-< -^ơ^n=1ٔI#9"yftB"ii= 69ӿp%{f8ߒnvgt5.Ukװd1. b-+Nw-k6kؖ#?6'7x >C0D -1@PA 0!YA26ڻss<_tM+ZN'i^jHSDV{uؗĬ~4gtN?Mg#e!F:?Bs[mܕ|yȨKw$ cvNe-<} N53UQ/yyuhPE%QCe" u74_^)kJ5b%θ,RBjAt7ܾ̺m?pvpxp<!AA" -, GWNvv]ݝn-n`'r952%iD,)m wYPr4ϏmvP~y:A4q(;:7a{ġ5c#NJ6+Ti(7PB:ڋPދPN+6:'009"Yf&*qt!VoƝD6_YlX"/ Y.*:캛pǝSAd<.bA/TQ/Ƌ\R&=˗}ߘ/OYpi:p.p? !C$A42*mUEAK~?q#}ijxĿN+o>Gb;H|({h;k{#hh)tż'^z[H룎:q>ik^M}>e93[H LGZ/ԷX+|0i]vD.5ϰ>@t_6Ù{"K~&8˳zZe B -AKTb!݀.1~u M+.mANn|!a@b 2(:`B<0<֣{&=O^"g[k;!$Jָk i7eQ_"fo%bnimiZO>VZ0(82zQ=7x -Pn(Q[[<iQ}u"i-]ᆩS\6s.j݄S"R%\,Ǔb+tVriI/!79m1M+.mANn|!a@b 2(:`B"l'A"%aFmDKp*0Һi'i*-ö(/mC:;^Hkim3zC {t/pC𢶾89k dnBZ֧&'wzn_WwN(Ui>ɫ%TAr5D 2:a]j\d!+1J]!6å 8;8 <  B @T@L@r뫄madk:bNs4 O!c! ER}ZD^3D|16)](#h"Pi"6j!s]mrNaclHE'AWC[yJ%I"y _׸ky~h2 } vrCM `6LVbTUZ(j,UDUow{]־3Ht\x~@BD!H *h&d 9ȃU]qdy~t{J.=vƲpɔUrjV#{:"y}t4#oѩ%mH~ScɇU=<8ҡ_wvc.w*eDVNnU~žH~FDk5.CmVi_)]rӃY- |u7WI}$dQ=&e1ij*HJՕUSfpvpxp<!AA" -, y6 O,Kkg>N|xy IdQCI'|b= ]@U\԰(hK{# M+Ї[Hlm4{7m>(:i6QJiG # vݜ؋~)slvWKOο=7HogK_ea8 ',Td(V5OzeMKJՄNy_?tpvpxp<!AA" -, GN}#q)ZFTЂکF4K(ЯkǞxyt6F$9Z@_KʌH~rۏ+;ED\}$o|qt-N#y.-3"4g{Ε釶&k~;D< -u SD_faiY.;z -g `}Vb%nm[ZTD1-Vu _fg [hg߿-gYƱ-C?1sj\h+Y r_C,Fn)7y&BQSjR,IUTjZLKK.U*UD~9j 23\ڀ /B(@ dP@ t0 d!GnSᵷ\8~lݮ纽^z8N&g[CUZ\1gC>6HiXj @ASPj{r?^s |KSSŔޒxvknk~pv*e亗^bHD"15$!*dNܙLE$)/3?єO-L$K$7iD]I| ~%o󕍱@$|aK35NVUQn1+i n3j6pvlx?D$y CA"8@@ -id0!d - y0a|QB^hF7a0ǛH|P=ۆ ӲsM$#y$3̗:~rVDl?Vi|}t];:c-~E7K$}n[L.E$"IGHNo!&`&VuyW2wInz⪒20z ?v3BJUt>lVuНtMA{au.D.DzE q!$1`C"0`‚ .<(2 77M&F̈{I7inJK[M3<i5^J5/sIiI"D*a%E?x"Z_;c],ǫ$o}Ӆ7Dnf%$.mj)>| ONg+IGIy ~e*yǯL%?ܪpVa87ܨ [z j1]ktX eZD>J"=D"y CA"8@@ -id0!d - y0a|QBR O6k~dl.4J{BnUG`Z6',+]"SD$=68Y$b"y5ڮH7U,_$jb%OJ:Oo+."ЮЃIrJ̛&o>yUn8)ڡ?.ܪpd>JbnDjR$U0 #VMDD}cnu@1 "CIc) E*4a\xQD e,ϐЁϏ [ K4#f՜Z6o1l]ts㏯[/< ^DC}H ~ 42A< `Á >(^cs}]uFs=ks뾐:͇3ƌ9kCP_PHH$%1 !'%W+3sx$ʿ=B<;WOs}z)S0o۹%\xv>c51îܶF#zZspͲVC擋xzE q!$1`C"0`‚ .<(2:CᣡfC]@(39 -KI^>s{v6[=<2g}Y_gDYI@+! 5"?]pK -hQy]@Nl( =- H/.>H|zj\OOl+Чw_dj 1z/_\j-qڿaNPdco7Կ ՝?{.q4Ƙbq2L&81K)V[mk -""ERHZPVŶZE,RRR[ 7'N4vº~;g9ۻ ;5eri)˧L&8R8xʄrTr4mr2UZK*"]:,M.Nv҅STRWLp꥗T.E©jiUI'OQUURjd: %+&O􌡪^dF(FMfjICjٚV; u7Wz=gΜ\Oot}x!AA"A 2(:`6 pҸ;1y~+`">v DK֕rniYvO'Sq9|ϞpL<Ǔ?㥡C?y^/3O~衟B0)Bҟ=??VGQzKuܤ=Focؽ=ޞ< CUd}5N=<~_6nա_g>6^ e_O4~o痭8$>x55%r'*;WlD./? !C01CAT@LWYF\B9=3x~- E$8+4#\,'Eٜ_B7y2S]DeDGĜG 4/2BiO1B:o6Bf.B:ɵOH/j iE./}Xؚ4_xߌhF -kͨ~v.#kʛ"7b}2'#P9ivfDFo7Oh;7kþr|FucF]+WRjERII)ddETԊ^QKA3]~ȏ_J3Kpnx!Q! *h&X`CPyĽxILD\FDYHD[LĘaJIFm$dFRI}`X#ER OR-IS%u͎5hRsN -4&*ԛm$#F50*ފjHCR?I}BR ^|jqR|;בG܄Ts[*岖lVҴ,~fkZ2RӤ.we%6t}x!AA"A 2(:`6 p![(^O>`s!3lFhTV![ʚˆ` -Dh"3'DFtD^O29DFI-K_ 5MG`#0YN&2BY"GoHvt̝N"g iH/A"Χ7"ujtΑ@"uAM8TjDVRzF(U2 -C& t&r+{^ĥ 87x <B( @ DA4,!y(UtDG0ã"g f|tB -b^TSu#N}2MHH144 i"C; }YD"]=sEq^l4w0"F8ɯH^{L#AF;ε~$Ȉ$"SE$GbyHNHe"G(Ʌ8k䥈׵p#IwrjY#$r:4#YJ"#LHngeﮝnyF./? !C01CAT@LW1]"5ygTBF؎hQzB 1'YE-HڟdFF$"yٷHB#澑K+%r)DVh"Hc*H5yȋmG D~*F"Os%m ImH?y(XxxE9Ξ -햛p:'RͤKz*F`f$ )ETd&di"@\f[kAKpnx!Q! *h&X`CPXg^Vs "zTo}qH"yYTQѶʪjXbiVh<Ѷ^HeWpDH~"yh"9ErsHy~ϴF*ɟ1"92"5Z$EE5FrnDGv>"МxՈdEħ䜿Χ(6gjS7s3w܄P$;˖2eHftXѷ*FY*֪EI˨d-Sf"{nI\>p   @PA 0r8_rIm"o4CcÅH.: -&[srNQTK EII)`?{{ZnAxv~^RKjŴ-Kz%g\A[%*B*~ӿsiKpnx!Q! *h&X`CPT<&\g倎.W3* jJ؂&d:Ϝ['yMHF"bή\Ky#u`|c ~r7.7ʈN5G7"`D2?gE҃H>Hɍ_>H^B;kwm1ۆgv2"y#˝4&H&l⩖tR,VJTKe3R6$5]Y> w]%?pb#t}x!AA"A 2(:`6 pNz)kEF/-B$NJ깉7z" F"?Ihybmݫ='tXF=ǛE}g?vq}b^_5%3h\U<ѻ4qL+Jub2N{qE]76*袷ׇ_չ-_F{{ŝ2=;S]ynyp+hJ|)jJI*J%('+RYӊrUW 뇨o<.]A^~@BDab -` 9Cp{D>o -!1D|jq; hɊ"qM5CFMݎdTХ4ɈE|Nɱ]T[z;?/A$K -ZAϠ|ɻ:.98>t3*h7K4I;"w777?ҹ -"Yg:Zp6w g*hmTM84v'%I?-J$CӋUTRs㕽o;I#Kpnx!Q! *h&X`CPܣ<6oFz$M4DrG$ 6*ÚLFװF$9F$oUR3B3"Hz]oV:5Ͼ!Vk7ǙHH#*mjUTə);Q%!s'+hH.+W4OgY*#gGZnJvgfJ^dȠD$Ke)[lH)'*[]ٻ}q uyt}x!AA"A 2(:`6 ph(D2=c<ƙ4Bz8"9&%tAhDw4KUa4ǙqxH#^F$qݱ# u롌;h$73/$Fڸˆ.~Li0P+dn[o7p:jDP%,"u3/>ǝC0D -8$@dP@ t0 lA -[R]r}呑H;S!+lDV_*$r&jHo0ͽDL-=&F"5F"Ӿsgwo=~t] -|5H``_:JN}뛌"JO2~ɘr ֝?ybW}atv~U (yt>E$7"v?υ|&dsL&UC+%)Y+kNߚ&LٷPw}{./? !C01CAT@LW;$"HA-dH>DRTQM4f C6dFfRbD2C#yzd 8SS{'K[ZGSi"3.ѶB][Dh[omIMF+v9<ɓU$-)|N(EZ&VsKe5]LZҒŚR5UT1SM%;ܳwM$.]A^~@BDab -` 9C"֋Vb@ Bvq5 ( EWѿaF*+F~D:5V:ݚ`$.wĽ|H$o2 F$OAÈdT -:-VSMSD|m7>j Hvd:Z(dJJU]*FZRbd3"/F$q >C0D -8$@dP@ t0 lA -'9?VG' F ǠmՂf9C5GYf' 3d`D::FhF$]#8^<{*ƪ+i 0v$>o"'v1sQ):|$ry(C"21?/ΧH|VSWȷk Hgj8-QsA*%Ӫ*KIUוdiޅDw tU.]A^~@BDab -` 9CqmuVOe -[T -jQ@_q](e>E]56GA\ozڏ~8b'ޮsqA,NF)Fje&_7djtX.9lQfEJ"Uh, -sO{Z\>p   @PA 0r9(OR"o&FnۉeWɉ~l>F\"MNiW@_9?NHkO^Si1'tE-iװeN i>4PBtLhsvZ0kҾ}*o >Kj-USҒ^ꒆ*eahZ-+zFyȝ+{%miHpnx!Q! *h&X`CPMSօC,g{o&fo $xIh! ψ,"C;2%Y4q)g)7uf賌Ĝe-+43^ - 02"wy:6z9mOQh 4oN7chIQRϧ!]oetgu1qJ/c) ܈O7!%ƚΧi"hs-s\nftW-7w9]ѝI*JjqgZQ%#U-JUUX4$^Mt}x!AA"A 2(:`6 s\wj+`ܩc*!!lFdbyԟnZFL8OѲ9Ey_kDRcD."9mM./YݭKi"2)41Mٴl1qf5grƝPW~U9FC0D -8$@dP@ t0 lA -8t~YO{-@!hՏRfBt; Ū_|ZzOw&hND c&.yc"'H^nW_h-H͈t "yw%@ft颓6O7ǣH>w,%Hb"?sǟ7E5OCVڜYHF'{pyhjg3REMjVqbRUkd^Qٚ^ޕK߲/4t}x!AA"A 2(:`6 S$ʼSV^_gĕ,آ)+Q39ty"FLYO#eӧUF|Hq3DDrQH*y0f0"9FZ%`D]Tah*s 5" :mF$t>8I6gy/D[n\gg^-)e)UMV$L\JՒZPj摲w}yٸ?Mp >C0D -8$@dP@ t0 lA -|Ig*h=q?@<9G| @+h=wbv=tN=?i=2дg2/ꢀzwmAa`CBZ#}zu7LSuDF^0}z<7|`XM8iU;LiUJg٪fKYM֯\ȣ#%:Kpnx!Q! *h&X`CPzt8lm&|33KHhFx.#{8$ 8^GΩ\3bδ}2Hh1%tMBiI/~11N7!#ci:rl'.ǫoJPKa84$٘אΛΧho|Nà_s~|wM8Lv~Me)E--)UjF+zYv3y#-M'.]A^~@BDab -` 9C2nwҹ{ %?;}Cҡׂ-$$18ˉsvw}C7s f2ҬN`d5D'l FV7{~㬳F;{PҮ_'KKbEW'kIx|vKmLK'tv])cd.29m}Jk&gLngt,+.8Ӊ\ûg9r緆˜xi+tč\ 3t>Snߵ+ǝIn3܄RMJlE))XIf% #OԚ"U5RNfjwÏW_Ϸ/ >C0D -8$@dP@ t0 lA -g1ӣ{MY~!`Ga3"glx!a -hȆWm-w{DDCEӞi"3C_;|K.iȷXjM43ƛEF"P:F"bvmUkpݏs8GZs{P%t>8ᣇl /q +P%<&D5U5C*멤.>]2*&Iƀ3)7W.zÛg(t}x!AA"A 2(:`6 .v伣OZP a-F.j;a Ȫb<ijLyDNΨ3h\Da$. efH*m|N4"Er=#vQ$sڼqm( N!Oйm<č6gG$s 䈓ZnA8ؠs$RV+d咤5Mʦ -ؒZ,^jv;;4t}x!AA"A 2(:`6 s pk#"9EҨ%"") =CQ>#9&HFHF$H~FHF$WwѶ=\HضJ Ur&d/*NoMHc+e%E\5ɣI !c,`;H>փH/Ӝ|7J㬇u]r|$*̦I:LY&UjFRJP%}kĥ 87x <B( @ DA4,!y(='V=Nߪov@ !޷I1>PSHT]3g{W0JI2kN}kˆoY2۷3򖵛=vQgo:q"Qh&]ieDrs<6kq}Dԛɝ+չhlT9F|k"i/B$NC$ZnAX9je)]JdKiYIbЋRڲg5;l\q >C0D -8$@dP@ t0 lA -:#/_S>=(H:+ FJZHjU -H`Tit։\t쒞NHޙ7?Ȟ(i"{G'wE*Fzt}Vք+D^#r¸з>Q$ODйB"ΟHFSsM$r\&D_W6*LMEi5bV*(R1K6(oĥ 87x <B( @ DA4,!y("C"ˏ%ka=Z?cxCiPri"{3 n=14M4d$/]$C;N:~.>֯3"YwڙYE.#lv|3[o|D Ÿ_"O8#݃H^ߗFGo&Hv^M*zVJZ$*eERj9U-E-\15mĥ 87x <B( @ DA4,!y(UGoUQ$CcH>cG'lahHZFL6qF$oCeu.$O߀p&#tɍ=^. -ا\Hd>8m}M4EEq"״"[/m}aEcrDN_ ? \E2F*t㺖pJf:OjtVQDTbLYL%]4&ѻVyjp >C0D -8$@dP@ t0 lA -P߶=Ľx6a#K$$mC3ws$뇍"fn!F?G\CMmWhi?O pFK{e~гigi޳ڥ/[+c-֯Ѵ}ߣdŨY27+ez/u.udUmH#ٯry|itP?kmVI^5R6QߏGv./? !C01CAT@L{g{GzB0_c-m> hcҚe9LBF" t*9D -DKWM}m;qZ4,A}zH^_nsj@K{kq^(sZE$l.vNHZr¼OVKelF/Jz*IZKd"ը~VJ"ZD'،$.]A^~@BDab -` 9CrFQg "i&Qs؈7I~Ȩk1lNּ!ah|F$ˈ=]DjHN@$nc>,nU4_fT#JFh$O,Ӯb*m֬ğxDp   @PA 0r8_e!qDZ ijxV[FH`Vp^ 7 NVo&񙉛7y Q-#bDĚKڽd:#ӨÈjsfaѽ]DuK=-rf1DI1]$vע0 ?'ۘ,sz39qʼnsn9W`ypt޺唎iY F14OgZV)2jEmN={7=M*.]A^~@BDab -` 9C25ǩK{!%[L H` .#Ixvd!.ldDYJ -%\b% fj/#%ԅ<곇v NkޚX~oIfhMMj1*HvG`~I)"XnIct>E 3 -3;s\nm-܄LQ$V5ZKR1[HY5iJwXٻ蚑67<|!a@ qH"Ƞ -` ؐ<ID\'3E0UGO>i j_hdTJiv%)]*^vƦk]4t}x!AA"A 2(:`6 ڱYOTYN4dsHLf }1)K>\{i6'0y-Wl8,(ż@t|ׅm8c? -͏h4ψ#ff6 js>89F+:w<Cwx x;ut@4&S/;?\12jВ%5DYʖZ&UQ}U4t}x!AA"A 2(:`6 ֣9M+ ,%i$~|{K89`a} :賍%ї{HԫAJ}i#Ӥuܑr;.yfFP?M,DjE.6'_:OAܮ8L:w25_ΧӗڜjWAOk RZJRTIs6e3LN7wm~v%Ь=έ@7˙0CC"tZ|3]DyD^L:h8,%ֲѠA=V3A}VԣhPYI5չVmWA: ÌCwFEH]A#x3|&#!*ϧ 7^ Dիg\{GΧ'}5gs9T=^z|PӝTɨ.rFT(e+颔J)JҒ5\a wj7./? !C01CAT@L{V:n3{or9ha+-G'F 8FMk9]3TSF2GD`$r&-1MK9ctN^ĠbC3"M4H^J#>~m_`DrܕI!ztFDCt.vaYMl\#ǝD˫[n~ލIQJ5E/KZ-[LURԊVNՌViFr."Sy &.]A^~@BDab -` 9Cbw9Fx٧CF؈i0JLȦ2J5і ^F$O+S4s\mɻf {Kd] {Fs^;7E2Ai"H.^r]zhʔqHu_"yj:"D)D޽.&D:?,I\Hբ-))]RECM˙y!l_< H\>p   @PA 0rYI͓ f۫!E]( z0Mkcͧ'aoioHҶF<&Kc".r0gGzF\Gswh$ߣ3A`D.e-.Hʜ8+Ias2LG|w|H+cs33Ygp#g^Lk]+kSRI-&tjEC)6!w}+.]A^~@BDab -` 9C=lEQrX B -"Է(T* ٺjM?$H^M#)1"C0D -8$@dP@ t0 lA -|MĵΙD/V]Own$Hp; =O/ȎC7:E'[QL特XھFFZoxhoѴ*fH\q./gKΣ?2Zq;.6`7ĭ#:8P@_vy9.32#Ӹ.#Гq=GlJKnnM4V?#ߥi3i?EZ7\ycbO~ws'#Q\t|vٜ 8i슖p:oaerZvxO#RI*&kU+r*&?/<|!a@ qH"Ƞ -` ؐ<`KR dUvk:F@#ZMbZ\N-&dl|qDӴQF"Mw]%)"S߹%2=>+v7B~^@DV$E">}k"w-H;NV/B"Nҹw&LSWm4+ǝԛWr|"3[TDza44)RZ+5IX<&h"q >C0D -8$@dP@ t0 lA -|ЛL$r?ڗg9 3$1ZN/93湴G19F"9`$rQ2Ӈt\Q봴۵o.|k$H.`Dr1EO[ڗ#t2_ϣH̝ʋEn?"n@;Z%(#Лbz+ǍC+jI]ZM٪i"bIԊzV,kW>BΝ9!\>p   @PA 0r#yg7I30:h԰BO2]4/WlC7eM#ƿH"^+t9RO*e?'2"VUrQ֋K -_3\Ȉc:6;|k國HSwy{ɏH>rVE:9 -o:b̞fVKEC+ۼket./? !C01CAT@LWJ\/:?{="Մ_S?Nh]}zzȓ$'IbvՏZMUDXqB뉵<"FS͈DW<c[;~Umih13c5M:&i{7?+<iQF\yGcg}ď*Ή[Q@t>8y i( -1ӑ{-73OUW*j=mVJ"R5Z1*)yOe~?]A^~@BDab -` 9C25 Y_US燾 rIt"Y%Qϑ'*%M'K'饉,#Ki"D@42yS+f/B(٣5;H#]6g$rf66ކDd]Q?.G"|s@57wڜwSH`e:/sTO&iUwbΪR-l&r'Fm(./? !C01CAT@LО@c?;9 S#Q%Ɉ4xt ~oZ+١n)yN;٭9Q%NuѦ3SkVezSZ+tJv8#V!Kkw¨OdHU4'Ӆ|jm.{MeDNot>Q+9a*O:wjg ܩ04 |-7󥦓J9鬔Mѽj -HJ2[6rId.GJDt}x!AA"A 2(:`6 pQ]Rp ëV(ia+bO;+OEd[>(qWùڍss]ӳܳgϞ=:&@(!m*:db!f$(D8LN;<ȡ vD74!U.4$GKؾ(-AfHQi@?+D*&Ik+3I"}-O}xVŀrgkAϖ<;0 2(BbHWO1wKB9A\9n'ij8ͫxsENHDF-D}F*=sA}ٵ;h͠f(S1u U߄m~i]F:`F ƿq6OU?^/3\3-Mk>E\_k=2|m*b6g:QAE-Lp:u b5ZJk/ M>L1D9$#ez[Ov7o?W| Ԁ,`Np n@=4@#xD@TB  qWڈe o~Amę#Ulqo"lHuķ[F;X!evD}*h; ?҈"0TEDgN|r:=a cȌJh{g yDž.(Dt߈hr}u^UUPcm":!&W<ΈT5헅"*F֌OEHR`~ͭhr7\=xx:0Pfl`8,pCAP! -1C $@+CBElM )xz1y% "'Ee"[kK#9bnk "id{q,1qy/a;pmޜ=-D.6qU9Vk_ff]G"({D ;.3Η<۞PSI&##)%S >DMjRZ&\\ty']\2!:ʼn&``+p Xp > 2(BbH@:b:WgO~+ 8">$a=ߊGAŸ$D#b/y( 8i} ?wAZY*P8e#k˾=\i]IZ|U}f^iVhZo]qs8`%y UgbDk, Ңp$TPQ2 ?pGdE4,JJXLtqDU?onV|55` Xvp\1:l9 t/B! B 8@ dP@ tȀYȁU2vsKg5ѽOgrP e;b$$.* e匢Lёut{Xq?v3?1:ٱ`I?u?eggy?OS?upg[)jlCuk${Ǐd[bPmCudl#|`{3[UsFٛ/'?>=_6k|tܓ쒯>/;e<&/롿tlegQqLl"2g If9 ;竌 g:sv 6`2m2r«Ŵ'rQ,URɕmG?zH t/B! B 8@ dP@ tȀYȁU${4SrEX!_DJ5BD*1 H1#cq4sp$gZ$ѵH4E$'_<ʀ"9!c"y}LC$Hm'"yE$NEHl#Kz,"ٯ)")>-0g}FDXH>E$ͭ䊷5SؚaΙs?2]lh =DHKq#)jx5]N𩒤inFr)?4'7x >CnCAPA2`@rp "8T'HʨW~=3")E$O8AeI8l$4_:ZUɫi d"jUre'ٮmȣn,h"g[$%uD~PKzo- ?8|D^1"1ɧ9欟OE,eHܣ&WeNf~3SK3LIH+OHNp"։,DE.D^,o$>J|WJ2)'2Mn$M_-'7x >CnCAPA2`@r`~qiHT84vdo7'55kbS ," ]*e:UA Q4qXGciKX e!%Tۛr_c\Ls"hE.\^2dQ)?l#uE.Ř}؆Ye\*{=3ܡ*&\be.өl*X1,'8$j9.$z.}h3Kz~GsK;0 p Xp? !0D -1@$A4!d!Wj\%4l)yf !CBc̙$2)ZMfa8H)LNҦ?.K鿭6>ĺ-UWE3aͧۈ莃YnfYMkV1Ocݘ̛h2_Hb/:8ߢHyE\F2̰N &#gHoS׽;dvs2C$[dpؓ[$1rB+bOg$|R,m̵-ҵOv`x~@B a@b"H *hC B̯2>̠2'7ÂZHC*asPAs%JNsz+,E9"DJINaCAH;5^8E#ʘ#+7Zˏi,"HoI /aXa +dadj";Da`fJC3?E$F$Ki^NyyER/H.G\A -'7x >CnCAPA2`@r`~Eľ 3cqC\s |E|X@l. y$n>qyQuf}i^b>D{[E\G&rc׵m5?e-IZz M, -?ik{Etaڞ$}4im8L`iu,t"XҺS3?ՙ_,d}`{&e;F5܄_@$P!ϊ2_R*Hk͂,"e9O+I1i݆瓮P\ځNp\<!AA7!Q -0 98TW hΩx9`608hTe%ll7X*rrV洌>쳉<&jPE"DNY$"Of?(?3 dqرc,GoXAoQ@zHN(;x$N3ou@B$eD_/#`ykk槈GsQ@KY攳o G`DnɊOMŒTJZ-D@#doGE${^DE${^DE${^DE${^DE${^DE${^DE${^DE${^Diͥ!άKc5pHCBFu2SaBVTU5I鳑rB-G*y*x$6"ADᘳ;61o(ièh槈"q!}"ƛpd*2\J)ROeTɒǥ^*ĥ8,^tC"p Ƞ -URD$u] -k3URL T"FT"(, %մGFrE$tCAТJC#}k"zy -W/ѷi#r썍,DݢHn;UZ"_,(#[$L$$&5x9A"7{$r)F s. z[e~iNM8ERl|bϋV6!JRJI|YJGKVk[KK;0 p Xp? !0D -1@$A4!d!C"s3[{ `.44"Cѷ渌C}kNյ&OyE"ߥErE"D}k98 Hd߀$e6Y"Hr9&89&TDt$9$OIoc$9Z9dw~DҨ%HYHh[oTg7ek0HpOe!ēROE^N|:.dQĕtGѶ5+ĥ8,^tC"p Ƞ -vZ#3Nݥ[G|$R p $Ȇb`$)gy*M.IDfI-w D5xrˑUM-\"??ȏ-VMdWK"6Nkҵ^1]f6\*+_F܌OudQ#D޴&Ujl)rQ*xITeTO )>Ȫ$U -Z=H\ځNp\<!AA7!Q -0 9n#̙-qn!}7Ļk$& n!Ll/I/ FDD(ho}fbiZLVâ~~Ѷ\8FZLA&:s9ƴ~iV>'[WﬥIb,Z?д?d8&v1?6{~DQ1304s=!1pL%ZU,c$YP |RMyYRQd**J:OiZ?^>7ާ&'7x >CnCAPA2`@r`~bmu>qLs%]]2K3Hh -rC"ItMSYDLRg=)4ՒcZ͞L3z9>qNmѝrWYf z]-=ft-Yd66o`023d/~eA!3K ->;\3?Ũ 3W2)*m\M8[O\LTDn '|>NxETd={]=.8,^tC"p Ƞ -yCt.O4?P@1 QC*ڭ o77h%rE"Gjy ǵD,Z&jj4dj;jL܅$mLo"<")!Qv>3a?0},"5Sͅף}Ieo "(z"(UJ)TI ۀ<ޒEF%+lo ,yckĥ8,^tC"p Ƞ -"aةTVq+PRHFR -$D]d5Y?4[4#hXDr--֧f\$OkU$ol~)Oɱ5&{4-9IE#;ɷi(U#ہa3pTN417f9st|if~3a;Z{M8ZɓI1.e^N(()ǴLbjMUE?W\^׮g?Ev`x~@B a@b"H *hC B̯_,DGj>ɯ2[ -̩Y#q IM?M+YeH],"{i"]ROS%W5dnLd& DH")ȻO%$]mM>ވٯ[=b._@"0qQ#c'  F"o52,y 7'Rnݶq ' EK5!yIQxPI\.m>$_tnH\ځNp\<!AA7!Q -0 90\Q m5/eѼ!_/Ġ˗RT)&ȸ%, ԬH?mFHh"Hgx9ZoWu&RzWKHy/YtڨK,jZE;_`SKV -nYaBW,aݗ̹ݑeN͆pf1,Z嵆pRZoiO4>/bBImWeTRԤHWt<VfbhZqi:Np n|!D! 2(:d,*q3Q\SM^C< )PM$68YrQe-oF¥8,^tC"p Ƞ -[$8g%[(ޡ>\s#B&r#Y4A4;Q4:mOh"[$F"_tLTpHבc5FzaE#9F"gHډCj͗P@'h4|ȎHu4S4Oq"Yp"zVJVJVD^MEDSR:]߉Y>P\ځNp\<!AA7!Q -0 90[O=j@ !"bI\LIUV]hrHn(HvX \GcEۈYFսA{7h SfKhuB%f"i37ewu "):]$[ch:Z=6r'ߘUzRV,zZI, -4Ӹ~j׋i̴^t:pWr1wz|]3?~ܟA48- 7guɉ|夢 /HҸG\߾cԲ忧qť8,^tC"p Ƞ -O͸'t}M<^G=$6 3.,Eػ{m"KD.4_(k"+4?/vP#)jiMvhV]'/FZy[= "mC]#f-EZo!x/~5#BHP\w7܄#p^뇪dAEKqW"JBዅZ,+X?d9;mwqi:Np n|!D! 2(:d,*PũdVtk+A:{N81$ rHv$EKR\J՟y?}WO\ځNp\<!AA7!Q -0 90zbnı8f®#ijxF;Ip m1F;Ilva7wi_#VnoZ? Xo;"6z;5+o?cy)܇, -%KvOVƂ&Owcc?.z#mJ2q]Ou 9&o0FX2\vM8q8*IIRSNU>-VD^UUIUbY)֧p_o퍟Zx7 K;0 p Xp? !0D -1@$A4!d!Ɓ33 cq.#]Fܫg&.#ĿLgu9$љ$6pK0TGeq D_4{G]V}4EbױmUl+ތfqؑc>rh}'>jq}bt{Ksk1{gsB\ (LLEf~zp@\>_M85~QIM"UWUQJR,%QǨqE+.@'8 .`  (ĀD@T@ _)bbƵ8zZK^~x&[U]]GHQ3Hq]IbWG":,!J'*lnƢ@)qHz#>l#ujwBn\빤'ZW33/I (6FiIȯOv3"+ -s֖8Ycb=:ß)} nN_i ?jTN0:>N0:>N0:>N0:>N0:>N0:>N0:>N0:>NG\Sp)J^'3)(s1S>**j1]l"Df`Sl DJh,hO9vi&-N3(w-;- -xTᘳFp݆HN|#? k(W[1#KQ@yOM8RͲYR)^.(2FZV r\нں% t/B! B 8@ dP@ tȀYȁUS=5I<ӉwMO')i]$29z \"L'Ti:1OCԩ}Z" O飠E@hd8&MOӦ{tc>^dQ2}tcQ2Kd6[m&tA:Jq{kfG s0_m4ެ"G+fв' &y>&|[Oa)V[Rq5n%rQ((VMxtA NK;0 p Xp? !0D -1@$A4!d!Wlc\sg<tNF:Hͬc;U&MT9X;Y @Of-4_sE^%5Ü3A|,sqnXmi|KO7:8$ ;&  $:96%Li9 N'OW<ϣ-G[ECnCAPA2`@rpm3tNvE6wU9;ɜ}m3j;nn34EMj?Mfe f:̵MCnUUtoړ[Tstkbyn4YQ;7j@8Oz`R3 9` p~7܄CLOITDe|ȗE)(B&VW -]i:Np n|!D! 2(:d,*}q>N\PݪNw=o$$VYF˫Ͻl&ꙸ눼(눺h}eӝE1h\-ϔyڈCpCRP=&vi) XZJ_:F`isCN(dT,7 - i]{v`x~@B a@b"H *hC B̯RAV 五-xV|%RhȨeqeBE#],QF1z٣]V4Z@rQ-ț%rpyG9vgt1_=-4Y -ㅙӉ27hs>ʈEBVoFYQbﵑ~"ƵhL4- ft/[>*zÝ_|8tga9ἊOs%b8U$ɊfLJՁDOWγHHo7}=MrU->HZ$ZD2Nخmc󆛭c"O H>OaΘ4'l3)"yyɚd/ldwcM8E3BJEU -gZ*|!Qy5/JYKb}9Es׬AKM\ځNp\<!AA7!Q -0 9%vY58ƚ&{ )sh"{Ht:M1_S}$e&Qgm{ꍭDiesEHm=eH~kXO }ϢlC,2:5ڕpu[Mkf4&0|LjQ 7 A\y~TgސG\esr?i G &)Id£$x5YtQNiIɗK޷¶{à՛ߠť8,^tC"p Ƞ -[XR5o>k6wMuv́xN6S&tr`v\QƛsMX鳨hji4VͧHYJ1;qcFsE4EG}Ń"o~yX.@ػjg=E4>0ݷoA<֡ {*oƜe>AM8Ӈf!MN@2BZ*j{̖ K7 wh4qi:Np n|!D! 2(:d,ns1՟ A%$T7+Q!&r$ɲ"1MbHASf\O2mj+dۈv-CW11_hIGZ$rS˪H:ȯsL(9kgW0/*sf~/0r(HcJM8LH*|%_t>HQ$- Qxԟ܍Dk֌Oiĥ8,^tC"p Ƞ -HdZ$ C -DH#2QD$@gy&M/,Z$ch<"|DMjVOcnL$ȨEm1\Om$֯cǜskmke -s)$ǚ ~"g /h[/&DxSv%.42Q䢒 t帚 mH]~M$.@'8 .`  (ĀD@T@ 2)#+ƪ+1_/Fʡ()GЩ -zW$sh"H佴m=" :-a#IqosE?ɱO6FB#yE$rb-ZL|HhEbH:ۈwYo:pľ;m+];LC| ߸O3?A#wLf(f7܄#p@!^PURPD>.濣h[SHt7ꭺoM>yN t/B! B 8@ dP@ tȀYAuPْڕĵk{)'%֒"\IB ̉$6KH+(+'EZpҞfכyfhizp\2{س#9}s0ah$/:[ocM !;jo-{ݬ*hehjGS;$ 3>j M/W*R/( -/'I^ͧ.&*RB*L4kz'7x >CnCAPA2`@r`~v:ԝKc3wOGS;43<mjϥUSmTI6뙟 "g@SڶGԟJ&OF$oOu?8a0]4-4܄#0zKPPI.夈qfT_\JRR -mC$ڲy$.@'8 .`  (ĀD@T@ _eHن#4\sg=!Hja. GJLV@S_0O*|r}+{WkH t/B! B 8@ dP@ tȀYȁUr\5s+jճ0=G5T`M4-G.D0h@-|6"9nU%__c/h4cѸϛ KJMϮ}%46"k,"F$C$GUr>0ZOa?D$ǙUr~~ X2Urn#Y):R/HYEOR:-xɷP%~8i\ځNp\<!AA7!Q -0 90R}ݮ;5JncIgTGHFQ%a) -Zm\Ń󪯏.!ލE/| {췐Nm$&"s3Qު~w6u[IV"MQ-~?bC{ݖc_X>pU^ZYOHkhyWoɛ+ě9s2|dc -+Oupy9o>aL(5܄#0}zEEBZz -zD9ɧBP,ruߢvg?uqi:Np n|!D! 2(:d,"6^}pgCnCAPA2`@r`~}HSSwHMPD^Hkdª *%,6jl-goJE7\1*3=mibAS[Nj9DL!0Ϗ枻zɛC y7"b9{G.tFv`x~@B a@b"H *hC B̯r/3G3cs.qM1 C1΋HИ}9HEZ|i]rv☁cYS=uKBa,aݴ`2?5_pQ˫RJEM|}OK*$t2-D}sw6z t/B! B 8@ dP@ tȀYȁUwݑs.܃=b3GQ!6 j1ά"\dh"YGh"}|>6&Dsa#nyԁ\V>cX@GH~⬃ŶX/Qr-CzN#x;eCt]HNHf_E$oOQ@/[>7` ϛ;5܄IгVR)GI3岨TJ>Ͻ+l>ZCI\>2n.,RKJӔ)z^^$hHhv$&P#1 j"21 ( -1nCQy(/N^=tKh|Hg\< `+p /  Ƞ -萂40IITCw\[h^'ӅrH 8WJR$ jHH$;($C_݄ =S"xErq{U&þ^ =*9Ldy|UP%VE$wJȣLyq DTcgӧJ w-y W6h6d3x$#JH. h>QC}su'_ `,`Np<!APARѭJU<~DunãzW8K -!.,r2 b 呜J"Dr({]J~Z=ᾰrvaϖFr!% w?ɚ%T]eb^5ų3ߔG>XHvl`/lߪZg_q]35=%\uhr"&&H"c^2O4GR,[2ϊ9~4ֽ?kZl`8n|CBx@ dP@ tHA 9 eوƕɔ_F$7URuIe4Š'YnDre,"%I(J$H$3αHIwu-sBH^<. d:v9%eI(gSByOj37}U:~HBtG)%t~*wBƙ&y6bxl63lPZPޡO5PVMӏ 3j-qڿaNPdco7 ?}8Ә1k a&N&dSJ-⥶G[/*UDRDqHRQъZj)ڕRjuAVgI'?~<ٹcw~{zszB?iM )I]6],{'}X.]"N-+qI+ũi']].\<=?<|_1t1.OYIq$Ye)!'.'RRXҸzbAKʲ/Œx\ BR+$EUR ܙWw׮EKq̝;Y6p xp? !C @ $A4 X_e:9G9Υhޱ?<+dtwHa=bFE%U):N -avd;r?u%'Tx2gvӡ?sC?xq'qcxЧ>g~S?O|н?ح? w"D;;ΎXgYmC;;ΎLgّ|L:[Ud}؆~_GeC/9nb=c~}7}s}/_CYewRe2b/[~Yۉ|ֻ9jN.*̎q$Ȭ)sbWNFr=eqG߳LF\W2\x Ye&6ttHie$%Y҅$Պ,*JZ B\,TN+JF+] ~Dr@$Dr@$Dr@$Dr@$Dr@$Dr@$Dr@$Dr@$DrIA$e\cy-!zP -)d$Q]ܒV%Mҵ}#9FR'"y/$E#E[Gw"~4DHI$9.ɉ4/F/Z$fDkyL-1"H#yM~5b zɳ ިrmҭO . 3qOL/Y'6܄ɸnɢɅt!Q-%d(IԴT\LHn^'jĥ 8/B( dP@ t0 &d!W#Ov ,&RYH05H)Tu!&,T/;hH4ǓZ:2Ytޱ,5$whNg.Y4pDù3x캩Z88Fy#b{ĸhZ8^E|8swy=?WLD8?|r*px8JzЊ*5QSiQI b:J))-)rRNù՝vp\<!AA7@"b -d,*hf3ʠDg -bb"TSDe%a43h hʈF4o_4'}X:Y4^4V94F;#hEGV}~4ψ˭hekV܁jٻќղ%D֧p,[hD۷6܄?L[F3VʪĂTD%t!RJŤ$iFs1Fw>ݵiĥ 8/B( dP@ t0 &d!W_va8%ܺ@&e:j$'eTji48F ;kȣѿQ,1.¡y(;?#׭Oѿ><9kr,esM82l=,*r1_JbJQK%ZɣZP,?;O(ߢi1 -Щt.VѦGfi|wy5Mk֯ߤ_汌yNi}j#'~8 Wks'Hy*w•hw)+ڞi}?I 7τ2RJ-eV ?Yi-Ur%/jE9MڿruW|wZ?qi'7x >Cn0D -@PA Ȁ YȁUrѦ9e[=M=0.NGCn0D -@PA Ȁ Y)YZ$:5x>6=⛀AXDҚ5c#㢲I9T ufSHh[sHň둤$2#?/6DDg"Ғƾ41"9F(3I#YB-!F\FˮW>HNĸjS'HcT^8+;uSw;ٵ |:D4܄P%[GTQr!xETR񤨕PUIDZ*52WJ>׮7__%k 8/B( dP@ t0 &d!_Oy:sݚg7 BYDrl@$29dTSg 중t3}#FE"YdD6bZF"aW*y- ]*)Сt(J~ Cnv;+kCˈ *mT#h\| ?4SNq-h >JFE),jdITPtJhlIV+JF9Dޝ]/TKp`8<^tC!Q Ƞ -`@LBVcQ%5QϬ.\5"zTԘ"JiHdFR`D4|VɋPr$#oѸ&F\wC+h$̈4&#Z$W.XyHڦ!J\KlݏH^%JdklT :P%k JK-Jr*E-o -Sr%]1i$D$ vz:u A4h\ѸqD:u A4h\ѸqD:u A4h\ѸqD:u A4h\ѸqD:uIZ%u9=y?+pNP *9.rNt h\/ҍ}WGMǒ'3"wH-75͈6"yژ ޻69HX{HZ$9Fd%gDm%`l}^N|9H~U%ﴶʍܶU3G$/ڜ0 |pX2zRK "'EUT+VTĂZL&r5ۍgF6p xp? !C @ $A4 X_%fSIvJ.7܊ǫT$E;V4p,$E"ӣFH~K ?~:9Imm,Xvd%r?0+;ԟUFFR33ƒh#cA{h\`,y)dCɅUQ% >B$.;5"yp~hDʗDJ+r:J/:!g\_[&t6p xp? !C @ $A4 X_emV,V ̈́'M DIeke'I9"#Dze=Q!Vo%#%?c'@:62zjnrV1iZj}$:|3݌^0s잶'`WGji'!m 1UaN:D 3by,w 7Um=̌bbAFZKb(K&Ҵ>‹w| M+.mx~@B =DAH *h0! 9ĶJ6^MF¯#ijxWz/ l!gHh~J"H "l$Lj8'&n #ZlVh#'ѴViҴ~HvV3;)w3}։ZZ,4~FZgm2)#Я]dmDbd?Ӿxc>1ÐMȦckvh&=U{񨤿+֧'r9.dW77܄?;$5% -b2]F+œb>PIJ R,)Cy>PC~ E1PC~ E1PC~ E1PC~ E1PC~ E1PC~ E1PC~ E1PC~ E1Phv8Tgĥ11OKLp\HB&3'M4Ae$EkOfzh"Ƙ&wv]Ch"UF"iaND~!ir(ǟ8;t6F$AFs;Ft1;4 'w\y;ۅb^P+N;" *7y腺)[nA;yO&rdVqJ"!g:*&i %hn?y#6vp\<!AA7@"b -d,(m 涟v%~x⻗ ~AB;6n# $ؖjs(u}C٧<ףtT0:EF\l2bG5msu]AMF\wұ1 -z,:ڈ ػE`,ڒt޼Hzn}Gw`,uɍ"4܄WdnVJiUL$EQQ1,$pY*rPҊg57xM/ѸNpn|!a@2(:l#MV\'ս>Ng n'Ŀ6Wo⺁WǢۉ6i#7V㺎h뉾OWLjh\UZ] #6zɈa",0oaOqk5H㺋Q]ۈkF׳P]+#[q: MBkD >Au}`PM8qR7 %E9$RB֦1,-'bBJT唜TEni̺6(.mx~@B =DAH *h0! 9Jmº8 -Ω)y/M@tZH%z096HS^e:MTlL|*@SʾVE.i*1{װ3D3s,P~Z?h(?` ~mB^Чuj-= -+hy\5;~ʯ4܄z;ƠjRӒb:,JQ*iTO1^,Kr%ʩ /Kp`8<^tC!Q Ƞ -`@LBihecF\S~x 5%~ZkIQHhnl"*=J ##ShZ%V#gQC/C e5+ ;Nk [PZ%.4*$ -q1/+b)Lj|)YKHώy=Npn|!a@2(:ꃦ6a\F\>b4BH9]AsIdVt%Li!+b."r <+!N:;^FHsoU4֘iJ]o9;cZtn)]^)2R:Z5ltCFJG.@[#Í6鳑nAht[ȥ .X;Ri/TT*rI;!o2єNpn|!a@2(: ĶԪK2¯$x$ 'HhUSBYLˈĖi)e^"¦;1*vٴj-w[H7&Ea_Hz+LjWۈMzwER:_}NPTA\Ϝ?B\ܘev7nzR"ϧiYT%5/D)ДeXݹsſ6p xp? !C @ $A4 ν ܛ>\J\S3;wfZ`tJxvdJt0#6K$Qsc~%ҫ&y4;[v3F ^,Vv3iytkǴF2|={誨j1 -ar'u3x^3})Gڭ{gM8Kr ^%ǓiMTBETTREhjQNt՝Zчt6p xp? !C @ $A4 X_EU3RyݭUi@& :A#i"e,^GX"}&/Xb$6SZ&ҚfuOW\m1z۫}Z@NH?eL$&'~MHNqGngj䏶#+Cm{n}H~kg;Է0B/" 7K|Q% -DEQ)'TQK,e)YǓ r喝c6=Z,qi'7x >Cn0D -@PA Ȁ YȁU +XЕO܋g)_/ $8[esA]J󈰄H3Dc9F>IQ;EG'2j4I-Sl-zyM9[s +ڹe<Y:Udg·Vuv.AW{u4߬6:A}*wJ@>EWN?drw 7 lS[d,2TY&Jb.qD ۋ t]c>Wjqi'7x >Cn0D -@PA Ȁ YȁUfzә>YD}SSBp /&y$G^i)dm'7$42l&h241yXɼuخ˻s^z%TC3]wi2dNm \~e$> -MZ2CׯT[Gu֧wҍxKM.;[Y[nAosWeEZ -N9 KrJCA-S A }̪w!c'7x >Cn0D -@PA Ȁ YȁUf S TLo?'0;f=vvdjt>fŦJܫ,$j%,0y2 桌`9:q{#J<;ٙ-6}iɮ{5f>H扴f~& m4&5s̭W#x$3m$/ʅd1uS$Sؑsi #&4܄?ޔZ4]REIV -yQ)!Z*Qd"YFA -M\Ǻv-c]6p xp? !C @ $A4 =$Rݒ+9eKW~9 3CM="Dea\̐tYStU23h$ cY{{PqHе?HN#I#6"9ۗ!+FrS︄LF>=>S@2ƛz\Zx:ƛ;jEr "iƛu:պZ2߻/^oV[PJ&bRJ`YL,x9ԤJH޿|u睃]쪟0=e'7x >Cn0D -@PA Ȁ Y98ty|Sr|}'5}czЄ.y)"GMA)X9S3gM#eTɬɟ0"U6zg*6z{D]x@?LMj5 XY;`9|I$r\@yɝNznEk >֓rR)Q.>*Z<_˅D\KdZ$rUcXvp\<!AA7@"b -d,*-S=+]^q$I-TO5|c%&ISLUb_]A3y"-`L3i/( -R7*O+JEU+rÆ~(}i:ĥ 8/B( dP@ t0 &d!{wZ#ʹıo%l mķ{3g4y*jdl>5v [6NTr.2zŅŒk3)?4M|s:NWM8MΨ%(d -,~J'2 -I 5G+Ƽ(.mx~@B =DAH *h0! 9b[e-'+<[AsI` "%C;X3$XeQkvv1V4 @gf=VF٫A͵3=;;Zz^cRӤ>HFܣI=~}Ȩ'1䌱fܨ?B5ڮW#K\㿲^"G=2fr~#TpWNRWN4E$IILKiRe5) tRg~sٮW.-Kp`8<^tC!Q Ƞ -`@LB8J&⸟8!G z-QXKkH+YI"=@u civ-WSau>iF\=rUΞ`ޛȘ Nz,#h]3ۨ;76ՔIѓO܉7܎|Y>E֋`Kno D֛m|#N֖i&STB$%R?]>:AKp`8<^tC!Q Ƞ -`@LBV9?z:|Ϻz4OdFHzaÄ8A%YezffDNG1 -_HD^`D7BA+jd*4gaUmbDt"Y Y}zhE8ފI_9SFt"z.?1S> 7 g -P rKE%WB2tBIU֣}cL[]\ڀ;8 .  z 1@T@2`Br`}vt9%eijGh^H6Z6.%C~w>(u󈾈 H}3ҺcCOiAY4 FZn#_ w 즻Eaa}vqaO:Vn^`~ºq{߫B*Y߿["n2.dc;ps#h"^G%:S4HJ&HF"F">)V\,-"eKӹ -# qoc:wWy78ZaSLmB$ -kBvYhiZa)4܄0U9*T*qQQ)VhnT*'ʥֽ-hi07(.mx~@B =DAH *h0! 9z&{;<;<vyz6Uجkw3$X?%Vl!ꉸce;hq=5Dz8#8Aa'rbbq_^Ez=iS!֣i}NZ.pv&!7lrhPѭOQ@.0S^BZw?p|f,E-]VvE-%rz׮ĈL;<׵ -vp\<!AA7@"b -d,*#řsEQ@5~!GKa%EUA ʚ3@)3nF"WD^Dy7#3(\jTgPQ? :%t(cJ4UKD6ZZahiE"ҋD"Rۡ:m*wۇ~n}jpf8]gOpǟ-mR֔ZJ1Xj.%P.ZQcv=\ڀ;8 .  z 1@T@2`Br`}l꫐γ\,׽|cZ@0c4qx}|"4Km4 Z#b$~ݡHZ2/i1D~&1F"/|Q#,_`em$2} ^9ȥ::$r&s.F"{Mϑ&ɏrS'6܄0#&B"b"%D bA.#Z!Y*&O[DaONpn|!a@2(:̰MۻGQWmD=\G(#ᙑiɠ9bJp= -И^(wҘˈW/6Zٵ>t-Έh+1#Ҙ/&'!.䇈7 S\n/ -KF11ĥ 8/B( dP@ t0 &d!{I7vzJ^'̀ԫӳY.cѰD2Hƴ F7h$ƈDϱt7i56"/,k=voV|VM˯CURn,`/{G$F??菗#򪵈~4]gf8#4K?G3k 7O&ZCPMDZCTd$ 2)%i5/$V|:y:=Kp`8<^tC!Q Ƞ -`@LB+v5=q<@O?#lj3 gדӏa =N{o&FO'1д]i -z# -v̌v sJ|fi}v\{2(As(FZkfi}h==%Sga"_Nf\{;f&v#3)IJօ|Yk(J鼔,ņ6#Z@qi'7x >Cn0D -@PA Ȁ YݵwP-Z :C(<_ tP #p4"ӳJT قӌOf$\Z@N-A9Qc-'͇&T(wg:itk(_ \d`m@$rE$n}jpOe8}& -܆pVLZ_J:^(rhM0WSRO b՝{4vp\<!AA7@"b -d,`Kx%D21Vn`#J4+rjI;(Vyi$/)wPY)%/Ǖt>̼ Uwh$qi'7x >Cn0D -@PA Ȁ Y}a$z9kqeIɣ"?<%b":)kղHcDzW?J#y.}3ۈC9)*;y>MF"|LW1a$Ņ~"?Ğ7$R3nD~lG*u֧HC2^$p p'~eXΧKb2DAr\L9_ZP5{vw=ԗGvwـ;8 .  z 1@T@2`BrV"Hy}XoE20Pѣ`LIL-fCo['18ڷmB',lַv*9o5h$oaDiU"Ftо%ڷ6a6ClDr?V,ȵWW}&w^3E"z"H6܄{u⩲*TT(Dt % I-P +޾׻>XF6p xp? !C @ $A4 ԋ0Kn#y _̯R}+9=21CBdTE3tPE#F.<>%[-#/1̰6NdSgcb$2C["ɓht3m$Rէ(/F}sg y*蓬>KI1 |⨆p"i}`W:Q瓕XL`ԴX(h`D;VwngV\ڀ;8 .  z 1@T@2`Brda$\E=:zu<(g 9BrEbA2K54KP_˫?WŒzz8-'06WeZ;hnyK/ϝ}]4r -7|-H5yu5נV_d^|ʝM@tn}dx\nAvmD/'#yjYziNF<罵Hh_m,N~&w@ZG$_F$_rx#|(O49gφp#IJ:))XK*2ׄ"&J>O&jɁ7vm|F6p xp? !C @ $A4 dm9x>yt̀VɅ YDRu٬HΈ4,Z:iuд^G[ڳi ֓:]$^'sw=njk`I;tw' -E!#{zw}#T\xyw֧'ޖN`ɍqpIazZP*5U@1?nI$xYOl_uOum[Npn|!a@2(:?rUK!>ïC="ZGb4NVP@M҅}_1s:ha/)={ۘ{0C:߬\o.3+a4=tJv-#?\4l%hc Q:ى6[-H޳R  3G0LE1̼kZr]% 7 AB\Χɼ,5%%b˅bQn8^T]Ƭ+Npn|!a@2(:&6\@k)gYG. շ- ١V\JHdFt!HluR%- ^R]魌yQ9Z9ߠ':|1{9~'c_i#ј mTۮgKrv 3W@@w3"Ft夛=mD>2ӗ7KZ?}S^<F"=/ic26U'yu1.zg/#Ho֧H$aKP8?|&=xLbTd!!*V -h֋tJJ$㕲\ zzO{Y\ڀ;8 .  z 1@T@2`Brw2h/Oh^'`&d"J،QUБȜ!fʾ{Χ|.~dF"D{Ki/S?wH6V_]f.{<#o|U߸Gz!Ym މHɡ=ZDDʅx")OխO n3eg9DM8ZI9^N**ۇIZYİR+?M+"v /ҵ/긴vp\<!AA7@"b -d,`2Vk8kn-6"i-X;~(ֶX,,(֖u]dɍHmG1"Ih|Hjm,piΦ/OUF$?7o`X}k9 cĸ'^^hʝ8 /OV"Ǽދ*}"on0R.KbQVTQD!D:)I凨wu_U߄Kp`8<^tC!Q Ƞ -`@LBn޾Uz']2oyyjj~Ȫ -*i~_3";~mCUzr\rx&ڄrH^D#0"V6V7یHF$ /Jn\)Ck&T;"yյ*3H.X[Jq׿^CpW1'a5RR\, JYԊP2ŕ+5'P%~ڵ|ϧ4vp\<!AA7@"b -d,*JuVI<ӯtTIq5I5j*V hɝ]H>Fki$ÌH^PmYsi$W#y1-(h\/;oٝ~F$״1 ;&˘Nvὲ"?8MF!Wh\ݎH>[T)*9ﱆpgw`I +h\ㅲKe1jy1.TRD^%_cu_:ť 8/B( dP@ t0 &d!WFK]ld4 _J`Rp "S" a.Ji,u6Sii`:c14^FQ'(_"nYibr'sHŴPHSyc8J&*uQ(oUK۬Wg>ޕ_ߥ[ g9#(o aiu(*cގPFTҪX,rTYkV0G_ĥ 8/B( dP@ t0 &d!Sk6##,#xWT7;pip9x3ői]$G%Ճf}Dj;3y0#3hBil+8d69~9{WXqәط9yVgۘa^S1(`yڴϢ/~r'{rvbGNMn̚\ 7 $y+TɒX.YU*Z.dJ+UI*hi;Q=1WNpn|!a@2(:G/9x1U%hVFČfO elc}P&˕o2c_zKMm$ -tfbokЦg6F4(odqZkZrY4KXB q缹?M2)n9eeWTʜ{ StCd*n}ZX^|^{<Ȫ/)4l=.Em>WށH.A$(kJ91iOglC<6wCo%13SgZ^-95ݕ5EUswT'W+yƘ[]LƘx~@BB"!IHA2x@ PA 0I~fH)͐Za3E1T+eլHjH򊮖4Q ϕn/57{d!oy -CgߢHn)92fvm%܂wwC;+kr渺W$\Y/%+[һ]oMG0!,!QA!YD@4`A l_H3Y%<"i FbvK靲>j];\@rqЧH(IƗ[DڔD>EpWDzf_GɡHXDEҷ u3F&f"eN`hwK "9[5_MfAUde',fZ^/z$@U+hTHJ)hrNjSis|_?o$x~@BB"!IHA2x@ PA 0+}Z[ W?G(Q5HJWOxQH$"&Ϻ>ʔ[d"~zƿ?,: HruD.\c}g%3d$SۺIgEryxH;$rnd] -('n_}D!qH@R dAT@L(%O^4bpWVq!'J%sVK>Zhמ"M)F"S9E@I.H"ҳ]"#lvWk\N$rY.mdaJTu#%AY_M_H%mEJ$Џ ]`E7"y"gл]W6 7C$OMHZkS?~%xj-qڿaNPdco7ot^oc2܍91gi{:/(˶v>($"D1D"` A4?< _Wxp&\J~Z?˟;Γ|{iOZ \i\[knnm6սCۖT^jkɑ^}][G.\=^Ɲƛh^jEZ.FTtT( OPZˣ+GKT6 :*k%͕fInxz5+FA[w_ϿЉυ6mTjgAA" @b$!i0d!yA(` p -[ PP%<Q;ZYq-49\UhIUBLnYLpW韀ŠcA!^ycav6"ݕ FWQaeX]~W v,V)JUӫls,ǻyyN9(%t3x9lC~>YNua{_7;aO]w;a3ްnI{{]]`wCzjl6Ɔ==7wNv $n+$rskBG)zc*42:ɾy $s"l|]ܺwW4IJ/d0^kU/Z\kސ=+4nj,}H_>~%m!aQA!ÐA :`6T*tnH=DAv|YbI.M; IɏLYJL$9nVYS>I%YIr@/$$'7$f${$MU$ױ$$y#K/$_KrKOggOY1INW#HrwHҹIC'f{fEF֮N"dDCIz$r]/4EX+7eӛr]uuT+hn6W,ɷvzu7,Il@ !CB ␀$ , "H4,Ts+J@Dj(FL<ГT>HRQl%GU}PLjыY{||%IXIG;g$%s$}oUx?qd'I%y'ɝŒ\gLr $"^[!?ǐSA#HЇ:^ I>7;g-"nIVrl!)=+_6IMjԫdoZ5:-5bYsR(&Kk$yЩgY@B!IHA20 YAD@ -*h&X`CBV$ ӈ%bL[ 5)̴I#I'HRDVaai'9Ȓo?؇^/$9o`I;#f_M_{"W"E"_gE3|$^3vQYx.".}އ"w _PYs߭shM,Bj-qڿaNPdco7 \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_sales/metadata/snap-2885374810939635374-1-6e069005-df27-4ca7-a174-f5b6d743aa4c.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_sales/metadata/snap-2885374810939635374-1-6e069005-df27-4ca7-a174-f5b6d743aa4c.avro deleted file mode 100644 index 80a8d8a13c7e14344c13d4f60479b946b6a7ec40..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3815 zcmbW3?~5Bn7{~Pvq(YTqOJCFzhDC(ZYsp?Ncew)*p^61TxuhB{JvNiw+1qJ1yK#5+ z&a?#2H@;J94A*)|Z8<22#Pf|W^re4;Absac@tv;}>KkYF$INUtIXCw%KW3iK^UOTo z=R5B;Z$5kaJlyfzE52@_7JT~h9ffq-NLg1rq`RJ>EGiCcvF|PDU=#U7GqH{6&n8B; z;VbL)JDC-2NG!S{4&)OLJKOZT;p#NbyRHrib{%ki;~Ln-!~$@>u5(GZJjR1B)vqO-dq#FlTrNC|I5zO+@{5H}xO6G}Uhw!W` z^iOc@DhPaJ0OEpO50mkHm!z=JFp#174s83DOXRS1#R+Vi1`addN{*!xFJ%y7K?1@E z^dkbv$+KjT2?Lub>nuim2XRrw4BdbPhPUNwx*LSWL|4L7U={KV-ef5Q% zmL(qg82f}?vNj{~t$1I4YDuCR$VP;o@{G`{(YE}|6^Y5)h4obWG`Vu1=fMvBn|4Tx z3WWbYt|+fmDAQ$GA?dC|Aa;B$?vz+aP1m&%bXXgNN~(GRZE+JorxW$O1$_Vk+XQ%? zU?!jGS5p}rq^>W zv_joAkp3cVAsZ5OKAGY4#$H=0mrKRf<+55>E3T~++2;j%UoA*3?;y{oE;6SsU>!A) zw;em`j~BT9sSyS`vy{kd!xku{^dI|P)^lS=_0UHF_7HVP8VV%?NSVQ5S+hhGHA~^% zI)#(ZYXxG7BltLyTy_fN|6NxAiX0GiesWil-wJ>UZC@N$s*F6oD}c*mzzNnfjVjlb zwOWBFaRk@c2`OA~vZo8s3I`?PIw2=f+GNZs$M7a#6MD)qak1F+MM0O1jZ6N6WUE|O zl95I&zQ)nQbw$rIl>`=R5 z-V#10^7yKNiQ$p9(3xS=y)@;PA}+fxYNJuo$9Sg5RZO7?_9z0UuOu}iGBzoKiwVTl znzE=|@wreXuM!9`a}u4-PDmsw1Gc)aQbn@2NHR95Bb{Qe$7e1c{j>M^_xr8Nm4%=G zy7>DWb5G{}{iSBxkAKi^^lJU;X7$G5pnq_0_~oG1-x>{iwcfW6zaAd+H~UBRjl08* zQGIAyjmD#a)*sdH)~bivHwWjm;r*{RM-K;^y;Hw6_HQ;!+-Q6^=$XdRt=`Z&8d#Qd zI4}>4{if3#-nV+A!L3L4Y9I7J+5Y0}{I3t{AO7*+^}ioq+WT|w^x63{OX}S7pMF-o a{@j_yOSS$yR14joZsS)jz4JmQ{r>>t(izhL diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_site/metadata/00001-45722a5b-5fc9-4adb-b604-a6449ac112bf.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_site/metadata/00001-45722a5b-5fc9-4adb-b604-a6449ac112bf.metadata.json deleted file mode 100644 index 2e06141d2e81..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_site/metadata/00001-45722a5b-5fc9-4adb-b604-a6449ac112bf.metadata.json +++ /dev/null @@ -1,353 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "c378c256-a694-4173-9f2a-de9b30519ee3", - "location" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-orc-part/web_site", - "last-updated-ms" : 1663708935930, - "last-column-id" : 26, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "web_site_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "web_site_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "web_rec_start_date", - "required" : false, - "type" : "date" - }, { - "id" : 4, - "name" : "web_rec_end_date", - "required" : false, - "type" : "date" - }, { - "id" : 5, - "name" : "web_name", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "web_open_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "web_close_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 8, - "name" : "web_class", - "required" : false, - "type" : "string" - }, { - "id" : 9, - "name" : "web_manager", - "required" : false, - "type" : "string" - }, { - "id" : 10, - "name" : "web_mkt_id", - "required" : false, - "type" : "int" - }, { - "id" : 11, - "name" : "web_mkt_class", - "required" : false, - "type" : "string" - }, { - "id" : 12, - "name" : "web_mkt_desc", - "required" : false, - "type" : "string" - }, { - "id" : 13, - "name" : "web_market_manager", - "required" : false, - "type" : "string" - }, { - "id" : 14, - "name" : "web_company_id", - "required" : false, - "type" : "int" - }, { - "id" : 15, - "name" : "web_company_name", - "required" : false, - "type" : "string" - }, { - "id" : 16, - "name" : "web_street_number", - "required" : false, - "type" : "string" - }, { - "id" : 17, - "name" : "web_street_name", - "required" : false, - "type" : "string" - }, { - "id" : 18, - "name" : "web_street_type", - "required" : false, - "type" : "string" - }, { - "id" : 19, - "name" : "web_suite_number", - "required" : false, - "type" : "string" - }, { - "id" : 20, - "name" : "web_city", - "required" : false, - "type" : "string" - }, { - "id" : 21, - "name" : "web_county", - "required" : false, - "type" : "string" - }, { - "id" : 22, - "name" : "web_state", - "required" : false, - "type" : "string" - }, { - "id" : 23, - "name" : "web_zip", - "required" : false, - "type" : "string" - }, { - "id" : 24, - "name" : "web_country", - "required" : false, - "type" : "string" - }, { - "id" : 25, - "name" : "web_gmt_offset", - "required" : false, - "type" : "decimal(5, 2)" - }, { - "id" : 26, - "name" : "web_tax_percentage", - "required" : false, - "type" : "decimal(5, 2)" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "web_site_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "web_site_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "web_rec_start_date", - "required" : false, - "type" : "date" - }, { - "id" : 4, - "name" : "web_rec_end_date", - "required" : false, - "type" : "date" - }, { - "id" : 5, - "name" : "web_name", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "web_open_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "web_close_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 8, - "name" : "web_class", - "required" : false, - "type" : "string" - }, { - "id" : 9, - "name" : "web_manager", - "required" : false, - "type" : "string" - }, { - "id" : 10, - "name" : "web_mkt_id", - "required" : false, - "type" : "int" - }, { - "id" : 11, - "name" : "web_mkt_class", - "required" : false, - "type" : "string" - }, { - "id" : 12, - "name" : "web_mkt_desc", - "required" : false, - "type" : "string" - }, { - "id" : 13, - "name" : "web_market_manager", - "required" : false, - "type" : "string" - }, { - "id" : 14, - "name" : "web_company_id", - "required" : false, - "type" : "int" - }, { - "id" : 15, - "name" : "web_company_name", - "required" : false, - "type" : "string" - }, { - "id" : 16, - "name" : "web_street_number", - "required" : false, - "type" : "string" - }, { - "id" : 17, - "name" : "web_street_name", - "required" : false, - "type" : "string" - }, { - "id" : 18, - "name" : "web_street_type", - "required" : false, - "type" : "string" - }, { - "id" : 19, - "name" : "web_suite_number", - "required" : false, - "type" : "string" - }, { - "id" : 20, - "name" : "web_city", - "required" : false, - "type" : "string" - }, { - "id" : 21, - "name" : "web_county", - "required" : false, - "type" : "string" - }, { - "id" : 22, - "name" : "web_state", - "required" : false, - "type" : "string" - }, { - "id" : 23, - "name" : "web_zip", - "required" : false, - "type" : "string" - }, { - "id" : 24, - "name" : "web_country", - "required" : false, - "type" : "string" - }, { - "id" : 25, - "name" : "web_gmt_offset", - "required" : false, - "type" : "decimal(5, 2)" - }, { - "id" : 26, - "name" : "web_tax_percentage", - "required" : false, - "type" : "decimal(5, 2)" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.22.ndv" : "18", - "trino.stats.ndv.7.ndv" : "18", - "trino.stats.ndv.2.ndv" : "27", - "trino.stats.ndv.19.ndv" : "38", - "trino.stats.ndv.18.ndv" : "20", - "trino.stats.ndv.14.ndv" : "6", - "trino.stats.ndv.23.ndv" : "32", - "trino.stats.ndv.8.ndv" : "1", - "trino.stats.ndv.21.ndv" : "24", - "trino.stats.ndv.3.ndv" : "4", - "write.format.default" : "ORC", - "trino.stats.ndv.17.ndv" : "52", - "trino.stats.ndv.13.ndv" : "45", - "trino.stats.ndv.26.ndv" : "13", - "trino.stats.ndv.16.ndv" : "35", - "trino.stats.ndv.4.ndv" : "3", - "trino.stats.ndv.5.ndv" : "9", - "trino.stats.ndv.9.ndv" : "39", - "trino.stats.ndv.12.ndv" : "38", - "trino.stats.ndv.25.ndv" : "4", - "trino.stats.ndv.10.ndv" : "6", - "trino.stats.ndv.1.ndv" : "54", - "trino.stats.ndv.15.ndv" : "6", - "trino.stats.ndv.6.ndv" : "27", - "trino.stats.ndv.20.ndv" : "30", - "trino.stats.ndv.24.ndv" : "1", - "trino.stats.ndv.11.ndv" : "39" - }, - "current-snapshot-id" : 3940511604881380271, - "refs" : { - "main" : { - "snapshot-id" : 3940511604881380271, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 3940511604881380271, - "timestamp-ms" : 1648044157641, - "summary" : { - "operation" : "append", - "added-data-files" : "1", - "added-records" : "54", - "added-files-size" : "9387", - "changed-partition-count" : "1", - "total-records" : "54", - "total-files-size" : "9387", - "total-data-files" : "1", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-orc-part/web_site/metadata/snap-3940511604881380271-1-fc902609-9edc-400d-8b7f-6e5ed1adcab8.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1648044157641, - "snapshot-id" : 3940511604881380271 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1648044157641, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-orc-part/web_site/metadata/00000-b21269e3-86f6-4b42-9a46-e3698a5b2b65.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_site/metadata/fc902609-9edc-400d-8b7f-6e5ed1adcab8-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_site/metadata/fc902609-9edc-400d-8b7f-6e5ed1adcab8-m0.avro deleted file mode 100644 index bf7656c1e6830bbd27459845b6591f351c53ac18..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7806 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 JcZb9h?Emo)cpLx# diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_site/metadata/snap-3940511604881380271-1-fc902609-9edc-400d-8b7f-6e5ed1adcab8.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/partitioned/web_site/metadata/snap-3940511604881380271-1-fc902609-9edc-400d-8b7f-6e5ed1adcab8.avro deleted file mode 100644 index 1f0c54134f97a81f30dd428504347393d5ca89ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3782 zcmbW3O^6&t6vub>5LZdo&*TtSC>zNkZkFzyncdkXfrx8xBw*Z0E(uF-O?S;sOLy0# zx_UR0Fb9Pk^dPIE1Qfqe#DfG9#ZwSWh~Ni-9CLELhaf76=3-TUR8`MRuRVL4kE-{3 zuj>8Zzg}*$l*Q6&^a{1e9H}IVAFhWM6S=MWNcrMU>}v%Hi(pToP?~!n*jQqxaV6i01)s^ zK;#M5pd0pf>bz2sghipVHEAhRbV(i*`U9Bx?PFAx6kNQHh}mF0D%B@?G#0SnA4~NL zqFo@`1)7)u$jau@=QpzjSe+_iyS)u?v}<^<(OvFNX{`A?rm;k{)vhS49(o>R(&MEd zXtm^VZds2$ryOKFbqHS1ReW~n#FFRh3n<=D=bKP`ahMQ$QJy-{Qmq%qr7|x{E3@l) zBCNk6Z{+SVm7PpkxYoiv>Gx|?_uitYMMzIzF=- z?k&+{qKL0bm>eEi3mqDk@1+^HlyUieF&mAO-oi6wu5tOcNzkkAi z&ue_KiC@23J3le`<4Eb+__t>H*dHHSHDlxA)0-FGy4k&PzVrR`clZDP=f&$68m+r` IALz~QKeX5h6#xJL diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/call_center/metadata/00001-abb63900-1a0b-44af-a643-d2e737ecb560.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/call_center/metadata/00001-abb63900-1a0b-44af-a643-d2e737ecb560.metadata.json deleted file mode 100644 index 6e1e1739137d..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/call_center/metadata/00001-abb63900-1a0b-44af-a643-d2e737ecb560.metadata.json +++ /dev/null @@ -1,397 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "d4f23172-7e60-4ae3-90e1-4fd8725738e1", - "location" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-orc/call_center", - "last-updated-ms" : 1663708523414, - "last-column-id" : 30, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "cc_call_center_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "cc_call_center_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "cc_rec_start_date", - "required" : false, - "type" : "date" - }, { - "id" : 4, - "name" : "cc_rec_end_date", - "required" : false, - "type" : "date" - }, { - "id" : 5, - "name" : "cc_closed_date_sk", - "required" : false, - "type" : "int" - }, { - "id" : 6, - "name" : "cc_open_date_sk", - "required" : false, - "type" : "int" - }, { - "id" : 7, - "name" : "cc_name", - "required" : false, - "type" : "string" - }, { - "id" : 8, - "name" : "cc_class", - "required" : false, - "type" : "string" - }, { - "id" : 9, - "name" : "cc_employees", - "required" : false, - "type" : "int" - }, { - "id" : 10, - "name" : "cc_sq_ft", - "required" : false, - "type" : "int" - }, { - "id" : 11, - "name" : "cc_hours", - "required" : false, - "type" : "string" - }, { - "id" : 12, - "name" : "cc_manager", - "required" : false, - "type" : "string" - }, { - "id" : 13, - "name" : "cc_mkt_id", - "required" : false, - "type" : "int" - }, { - "id" : 14, - "name" : "cc_mkt_class", - "required" : false, - "type" : "string" - }, { - "id" : 15, - "name" : "cc_mkt_desc", - "required" : false, - "type" : "string" - }, { - "id" : 16, - "name" : "cc_market_manager", - "required" : false, - "type" : "string" - }, { - "id" : 17, - "name" : "cc_division", - "required" : false, - "type" : "int" - }, { - "id" : 18, - "name" : "cc_division_name", - "required" : false, - "type" : "string" - }, { - "id" : 19, - "name" : "cc_company_name", - "required" : false, - "type" : "string" - }, { - "id" : 20, - "name" : "cc_street_number", - "required" : false, - "type" : "string" - }, { - "id" : 21, - "name" : "cc_street_name", - "required" : false, - "type" : "string" - }, { - "id" : 22, - "name" : "cc_street_type", - "required" : false, - "type" : "string" - }, { - "id" : 23, - "name" : "cc_suite_number", - "required" : false, - "type" : "string" - }, { - "id" : 24, - "name" : "cc_city", - "required" : false, - "type" : "string" - }, { - "id" : 25, - "name" : "cc_county", - "required" : false, - "type" : "string" - }, { - "id" : 26, - "name" : "cc_state", - "required" : false, - "type" : "string" - }, { - "id" : 27, - "name" : "cc_zip", - "required" : false, - "type" : "string" - }, { - "id" : 28, - "name" : "cc_country", - "required" : false, - "type" : "string" - }, { - "id" : 29, - "name" : "cc_gmt_offset", - "required" : false, - "type" : "decimal(5, 2)" - }, { - "id" : 30, - "name" : "cc_tax_percentage", - "required" : false, - "type" : "decimal(5, 2)" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "cc_call_center_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "cc_call_center_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "cc_rec_start_date", - "required" : false, - "type" : "date" - }, { - "id" : 4, - "name" : "cc_rec_end_date", - "required" : false, - "type" : "date" - }, { - "id" : 5, - "name" : "cc_closed_date_sk", - "required" : false, - "type" : "int" - }, { - "id" : 6, - "name" : "cc_open_date_sk", - "required" : false, - "type" : "int" - }, { - "id" : 7, - "name" : "cc_name", - "required" : false, - "type" : "string" - }, { - "id" : 8, - "name" : "cc_class", - "required" : false, - "type" : "string" - }, { - "id" : 9, - "name" : "cc_employees", - "required" : false, - "type" : "int" - }, { - "id" : 10, - "name" : "cc_sq_ft", - "required" : false, - "type" : "int" - }, { - "id" : 11, - "name" : "cc_hours", - "required" : false, - "type" : "string" - }, { - "id" : 12, - "name" : "cc_manager", - "required" : false, - "type" : "string" - }, { - "id" : 13, - "name" : "cc_mkt_id", - "required" : false, - "type" : "int" - }, { - "id" : 14, - "name" : "cc_mkt_class", - "required" : false, - "type" : "string" - }, { - "id" : 15, - "name" : "cc_mkt_desc", - "required" : false, - "type" : "string" - }, { - "id" : 16, - "name" : "cc_market_manager", - "required" : false, - "type" : "string" - }, { - "id" : 17, - "name" : "cc_division", - "required" : false, - "type" : "int" - }, { - "id" : 18, - "name" : "cc_division_name", - "required" : false, - "type" : "string" - }, { - "id" : 19, - "name" : "cc_company_name", - "required" : false, - "type" : "string" - }, { - "id" : 20, - "name" : "cc_street_number", - "required" : false, - "type" : "string" - }, { - "id" : 21, - "name" : "cc_street_name", - "required" : false, - "type" : "string" - }, { - "id" : 22, - "name" : "cc_street_type", - "required" : false, - "type" : "string" - }, { - "id" : 23, - "name" : "cc_suite_number", - "required" : false, - "type" : "string" - }, { - "id" : 24, - "name" : "cc_city", - "required" : false, - "type" : "string" - }, { - "id" : 25, - "name" : "cc_county", - "required" : false, - "type" : "string" - }, { - "id" : 26, - "name" : "cc_state", - "required" : false, - "type" : "string" - }, { - "id" : 27, - "name" : "cc_zip", - "required" : false, - "type" : "string" - }, { - "id" : 28, - "name" : "cc_country", - "required" : false, - "type" : "string" - }, { - "id" : 29, - "name" : "cc_gmt_offset", - "required" : false, - "type" : "decimal(5, 2)" - }, { - "id" : 30, - "name" : "cc_tax_percentage", - "required" : false, - "type" : "decimal(5, 2)" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.22.ndv" : "11", - "trino.stats.ndv.7.ndv" : "21", - "trino.stats.ndv.2.ndv" : "21", - "trino.stats.ndv.27.ndv" : "19", - "trino.stats.ndv.19.ndv" : "6", - "trino.stats.ndv.30.ndv" : "12", - "trino.stats.ndv.18.ndv" : "6", - "trino.stats.ndv.14.ndv" : "36", - "trino.stats.ndv.23.ndv" : "18", - "trino.stats.ndv.8.ndv" : "3", - "trino.stats.ndv.21.ndv" : "21", - "trino.stats.ndv.3.ndv" : "4", - "write.format.default" : "ORC", - "trino.stats.ndv.17.ndv" : "6", - "trino.stats.ndv.13.ndv" : "6", - "trino.stats.ndv.26.ndv" : "14", - "trino.stats.ndv.16.ndv" : "35", - "trino.stats.ndv.4.ndv" : "3", - "trino.stats.ndv.5.ndv" : "0", - "trino.stats.ndv.29.ndv" : "4", - "trino.stats.ndv.9.ndv" : "30", - "trino.stats.ndv.12.ndv" : "28", - "trino.stats.ndv.25.ndv" : "16", - "trino.stats.ndv.10.ndv" : "31", - "trino.stats.ndv.1.ndv" : "42", - "trino.stats.ndv.28.ndv" : "1", - "trino.stats.ndv.15.ndv" : "33", - "trino.stats.ndv.6.ndv" : "21", - "trino.stats.ndv.20.ndv" : "21", - "trino.stats.ndv.24.ndv" : "17", - "trino.stats.ndv.11.ndv" : "3" - }, - "current-snapshot-id" : 2890857111948924486, - "refs" : { - "main" : { - "snapshot-id" : 2890857111948924486, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 2890857111948924486, - "timestamp-ms" : 1648026727072, - "summary" : { - "operation" : "append", - "added-data-files" : "1", - "added-records" : "42", - "added-files-size" : "8498", - "changed-partition-count" : "1", - "total-records" : "42", - "total-files-size" : "8498", - "total-data-files" : "1", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-orc/call_center/metadata/snap-2890857111948924486-1-bbb81736-4d75-4b23-a088-91f01e2d9b47.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1648026727072, - "snapshot-id" : 2890857111948924486 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1648026727072, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-orc/call_center/metadata/00000-4fddf443-a1fc-4805-83e2-779331ab7307.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/call_center/metadata/bbb81736-4d75-4b23-a088-91f01e2d9b47-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/call_center/metadata/bbb81736-4d75-4b23-a088-91f01e2d9b47-m0.avro deleted file mode 100644 index fa8c821c5c41c5e5c8026c90faaedc5438a8d01f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8104 zcmb_ge{dAl9fwp&k&?lNvCvZ3EQ|qq;N4z+UW-k@BxK}=Bv6YOm%Y8c+>*WB%kEwv zI2l5W42To0lL7@33Pg%Vksk_5V5VqHAM66Jv_@m;Fu>%Yu)PcVD_HOs>l3VVQ zFd6>Xd*5H5_j%v%=e>pA6~j}0rmz7n$ka}Hl2ofh9O)qyRSvT%=^(XVX7E1J<8+XI zo)dfu=~?z93BR};q{sx}dX}YGMi6M06ID*8l?q@gbCqFU=D<2XBPg6B>RXV+a;j}RD5O-< zen`4l%rRM-$QO{pvRzC|&Mp(}f{e(NbFxiftGS7A!3veOWveM|6Vae)f^fS`M1u}| zoWdrMyNPB&My}vgI&lO{Wb^S=yuwSOO%^5$OmvGH(}_{mW#Szx1w)KjouCiO%sy4- z02X36=uLzSl!=IdDaZ+Ii?NAp;S*(|W5fmO#PPc@QBn!>DwlXACT_C4TAe@%CR(ym zSWL*;L`u-TZ5y{0JrfsK^PvQ?HZc)pEhkd6iG$@qm6rT|g|lm6K91#sj4(CRK~U55 z*6%hEQDs)qAx?&~6!ayVYAnvmYVKpIWND^0vp3Dur#Tpjk__i*q}77s&igquA6VL>H5 z@H_6wg?R!anW6o>0E^cA#H>N4qWNfgB$5j>rbCPx&^k*Ql&=xBM zFA{11)T8YNp0OKqjc`%;)f~->w6|JClRp9v+WKgK#gfVh$QuEdL-*0;xbFxgsEjN#@VtX=83Z)azgrJXh0B#qSGim{U@eMh;#>s> zeME}39u#1SrB%fUVT0V!Ex?L!<M2=0?sA;J3y zRRVtyT?8y(wvR^wJob@u=2&ym$T2if;vtAfNhCjL?5w$SpcHiV239Ot!T8VLkJ+{f&iLMx87^*xklm5`TMazXZQIQdc94vPZ0Lz^_0JgbrI|O!YbLS2aET|^oc?G;85NdjB)lRT2d~UpX z2+~S$xAp?e!5uu>a0b!Ss4U2{>;XVWWyL1Gt(C@hDuACTMwYiFq5ctFD04!Q^(JXAlz>bL8 z5nw!fZTH2EcOxYv@KJpnKYODSk`uTf5UZLnbGwaUSPI>$j29>`O?yM%v75IWmqm&M z9*KZ327E#HcWr`44-^R}xeh`pSPL{9SW6T?WE&zw+ec$i!7e)%33FBgiaiBnCx8pi z1WhTqTMjz5Pk&1S^A2 zZ?P%^r);p5-6nMPGTg?KQ$LzE!7AK%p`Yd8I!I~)WpckVi z3Sbwl$;*ojdVoS7W*9;4cye%UXryl(dy5P-JjZopsMtpaKGh9AeO$Ha>AQ_d1hB&e zT8#8b9Y3wb*eW$6j4^<$qAW%ny*moL0#wV>-v8;EPmXU~fAHn2?t9@c*Q)Z@diU@BY;WJJjzhcIE92NXJq@Q1Z!Z7aDe1`RFVz0{*1z8Z|3;P26YSkzT$?@i`R}V-q2Y5^x~1^ zwwA)LTk^NI6r8;)wP{@Yl>5H=(}TsGX?Igo=PycGdLn<~J&)A?ym3d;lDvH*GxyB- z$=2?=tmWOuhow3$=1&^d(%g8iwsmgWm!7JJ$@?C{`{|Yep*}5IQbvfmhO9dX6rQd+k-be*(GJIF9#Mq``Gv| zU%Y$NnNumhc%o}e$I|rm$GV%o`l6|I#8aVr+kZ1TEPXhyV$+7huU>vPFOVJ>*R!SI zSnq$Ox{rT3`^t#=r*4hudu3#O_KKtnpDoVIOYXh8yLsdHb0(hNU^p>}frqSPa^hcDZC{f%cD+V`Z-?HXH@ z^p3Y-`sCBsW_gO*8jD75XgP5I_R80rThX@3l7E j9ACif8*#u{va85{_Rv1&{3n;vdp1)iANxCP<>&talzHM$ diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/call_center/metadata/snap-2890857111948924486-1-bbb81736-4d75-4b23-a088-91f01e2d9b47.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/orc/unpartitioned/call_center/metadata/snap-2890857111948924486-1-bbb81736-4d75-4b23-a088-91f01e2d9b47.avro deleted file mode 100644 index fd9b36b20e0a2af3bcc5beb85e5d7ad6a96508aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3785 zcmbW3U1(fI6vs8iYDz5egGg)(!$td)mc8k2ayJ#RHW3y@gBw9;<1%~iYKGG-2OHvh1A4fId{(Jp7xABK z7`cvb%$z#cwW0}Gov(-k`HW(3iGO#9&EvdAY{)S2z*}#YzzSw{@JbnkbQ$#sc8r-q zF4vyuj_o3ENssOlE%pLo&;=qv;N$lYz#gc!81j>JDLL zKp264L?AhNkqoNAz-7jah!NjGT-32Hx&s*uZ_BUPBnXR%u0*CFDwG+V5Ge!XG1>yj z`W<+Ajfc)+pYcns42YbI_hqLRG^&GK#P}%>2%U|#WoJ%lOxZ4?r_!e>l>;e-E&ey} zkOmis{Jm3Aw2-s-vTh;S#A6V9ekJadSV&buTm(JQ2BDG`Xuw;%2B6o9dcF>Q0D;&9 zWS(Fdxp0vwZosPmJ%iu_gqYS8u7aiz-0;=2l%?*R6&o_

_U*kF^LeXYl9Cy{8=#> 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�FfGcQv4 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 z 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
~ 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

6~0eYREOVh)*7J4v@UD=HV(+zsG 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%0Dh;;D`B zT3HI**+bwoMP`6R_|K-54G?{AOspyXaX~{mi$JaIi?vc(2SBkz<;m60w*Qwh`u~$@ z$0GX4#=nud1&Ij!NEBv&r9LLiF8r=ak9L9UXZc`-*r&Ubw7o!qZCOR%;P-`+9e&{_ zQL*3LK(`CkVt1YuZX}I1lmn;V!TodN5U|l>zgOFZeM1X0((w+E7?MKcoJM1%&(-E3 zo{ny2oG|Uu>x1cFc#_9M=Y`*K2!DObrXHEJ3&zsK;Xii}ClbH67MDszs(t z{V@MX5)8i)%zL5_m}-Pa+{EDI{y*ajLJ6v32|E`_4e;>=_FxmDM3$QuppSbah$z<#X(f! z{8BFr(&rI@!J+&Rd~$ae(s3PWKmx7ezg5A^zISSwQHOfzw3%2^|Q1e@QWV$0viPXD#VI_vM{JJ*@dJbfUb?X|;TRPBma>gvu$7N;Re2jzZ zo=nOA$zx^Qm;T)GW|!pfG$byxEsK%8N6JaJqlskeDkLsBc%HmUul$#Zl1x_;?EC%C z<-aWV$6Y@_1guO8!>be0>t>`NvD`7k#taW&DxR5OKUmX=zC2f&3g9wJd0Jz`81zc} za(R5Y`tjG>WXoS~vH!aDfTs2K!^79NSbuz_ocYSw+e(n$`rzrW+YioLyL0Q+B(yh&-7`#r(9k|oIf>WV@`G02~_pyn`!eUjl?I6S*u zZu9~r6#x8+qWFeZsd9zc2M51{q4%ZjmR_dQy9nI9gP45%g zniTy}VOvmM^!n$oaE&MUsmpG)2aom|L@59Wh7Nd5O5lolYUkvpU18m`Dc>E18Yd`S zeG*wW-Z$qj8;oPP!GnKz@nl~7IgD3hpD%hm57^OxZMS8+MMTp|K=A%G0iEDiB8KH} zw-lZc@Kg{;{rE;G{*C^{Ya+VCZ||Yhs};fGAdYB*2sV<{f=Y2;j%dReE2Y)Hq^?)* z(<`2G4tL2zDO~Gn#r=%a7if^lk?x6I4R2lh)zT&D@}qjTvE94aRbMD#5Sq9DL0?HHFS4<&br1tH0fYj?2FY~*FSJTxZW z8IF+~S=>nbGOc;$Gz>^>dVDN%`kA)&dR+55QQ(!^RFn#{kGi~$nTfWx`*J>=LmjkY z`|wQEJf_IzAQ?E4zh3aoq+IoZU8?E;n#gq4%)d4FlY>u#?Bp8!$PtdwRgbW@PTYdl z5#&Wq zPy8M^mWOn`m^RmBsHVQX-I$JE+CyMDdg$;VxqLbo%aI$TR0@gS!7jJ{{@Bsl2{5nR&Ob{Ua{7^j-d`i)dV(!E5N zd|JUfodmg6YZY|dk|hZ;wxmeXtf*U0EpCzAc}75F*LsV}t^YBRRT@b&qe*Bt6Siy{s|@vsfR93`jA(q;y_AG#;>5uk~&g- zzpbwe)?WshT&S2`kjz@|N*(g`2I_aP@3#MYTr-0&IB3^-wz1GMWCY#%fVn)Bnf%%j zfQ1YxSvwBnXSSdnzEDD6Xtq35YzHfwk4&@sfeLRsk!d^NN0ficcgD{Xu}INHEwJZ= z^vf{}d+{Nn@>W8g#)DV%DL1%b<-hxYH=o8=_FbW}it`eP$iZhNS$crP)P$9>?+q1^ zXEuH&;i(&^>( z_WauwU*SfT$xr_X?!I#Nyav$JyF{);Ftr8tiK;RDeIzg`nrj4rFanKfs%GcSs5UmM z0-}G4>0|_z8!Zx1m#B25mek?15FJpV>;JswQ-+n59?dKnTU9@EwkHA;%IaSRZSN^a ziX{x!=aa5&uX;U;gx!MgAy zvEC%1UJR#l(*UNP)?93#15pf~IHDKbxYP;Qma4acW{sdTrqQ_%j&TCu3#O@?-U?^prm1HN|O}3 zszNU*?Rj@s&h;)cID;h--n%}FV9X9@l2)MR7gW=HZ-X?pY`9PsmYZPUIjQRoA7U}t zvHMiomHW38rd!1f^MfrQcqBIQX|0N;*?y}M+O*!CdC`+IHC2GIf2}I`vi_OMQMogT zuhWp!$LvqIqHkY&e1v0dZ>jP5#|W(?j;VZk=xH`SBAl6ekg$-M` zjm@K7_seNp#bVT(FKuAn=^+exH_UUD6COM9(yTQmVH^y_Z5V| zt>c#(4HD)T2D|GGB^5VHLrYAO5;yw3V5@5w0M5=@r&u?zgu_TZAHuM=9=UQp36rJm z!*~ec>l8ta-%@l!$liIUeef3O_>p1#r~E{zJr7grjtLC2B@z|2(d}=)Nso%6bSSp$ zTJm$XKN%E271l%KE$rX+npH%pgF4?o_6S-;q zBIY|OY2`JVkt78;u8{D)DOcou#DdVFGVZm^qF$GbSWEAkwQ$wEt5T`xL{UQ=fgiYu zN3Hon1LdLdnZWOvMH3MfrbtWi$ZTpoki4QT=Dar0*0V4MCc+ZbrS zHQp2_CDTH~Ig#hr`7?8%Ml0pv9Z=cAUZh&q&!n}!5x4pc<2!zc(ZX|5y>f-)Qr~mI zF*&QZs*=_v+of;4F)e)xU5p`p4G&X{AiIwibrS|7bs21I8!a0SF6|(j9wmZqlKOiz zt+`M!gWX=4zonmbtEMv$0)V7ksl+#2!>LyynEpx;>!OoibiPA*2@B5^^H5Lmmea{l z9YJuu#f3)ONyhhWDWY+&`~MH#@P&S{Hz*IK&U>uLI?MZEh;=ckdoau~ptm;sD)2># zLFZ)D_WZLs4n;q`)CD%l?R%E;jo>80?J(4^v3oR5PS#wk{FwM`ri{M;u&I# zX9j;JZP9#D4~jU`9_^-%<~RWvpKgC%ay_~_kM=VUk%xM{Y#K;{9$8K#cA+DtTTj3j zIu)1)dBFkJAp<4?JQ~yZh1dZrq#XSAnx{l4Sz>SOy=r zr?U()w#Prp3l`)H#J(~3^Y&qmYvwOyVo_s}x7Bxg(0^WslKttKH<03Uf7q|wKpUiQ z7&Q`L92$NHlT+~Cg*q$pj%|4O9cq93`U~iLms0#yhkuKxWYQF8ZoO~($VM!e;!@#xXp z!sgtQDfy5-{Abc`0Qj?KqUylFal>uso(VuBI(wb?b_#9V>!rHYZYD>e+RW%IZ^*Nk zf-za~u_+CNTcAr8N5|1Y&UsZ z;(hhe#K_sI)HZ3=vAWv+Yz5%sb41W5bV@zt)2!kV^D3<0E=7IcSoYUxdZZ&@<(~-K zSLhODWi?f^KD)kx+u|F_u#@=S!HB)kptQ%>C)x&2y6%zcHnGnYjqM3%OS4H18;cTe zepjS@G-;^K(@&f!fG^MQLj_(DGm zYTSlBplTM_qKFerP8Pm^oO%_;hAGpksr4;;=%hC{L4Xl{_5<5_g!R*H-@On@pC%xS zoY$g4DVnQ`XBy{FJZ`1)vzO0&(@B|78F-?dsiC1Vp+L9zV_5f3 z_4pHjz>(@ttyq34N0Z`G<@$_-y^ZbTPpLT?Zl3u~w$C@-q|R~M%OsUerjj|=R&3+m z(Vpy%qT;Kzaj9e%QBB{8P^St@WmL^_huSBnNfO4=zmWqp>zDqgL?}4^f&_ptw7lN* zUJc8PmmL>VM5tO=5J>J?#^7)&7@f;Z7eoY{d&KLlbgU=$u~rk3x1WP22gN$h`O0G`Bj{bXJ=i`w zad$fXo*v($@Y+jC+Iik17>S6b8(m!F<;J}dtwk>q&Y8G>y85@3)-$7n)!v@sS&pJw zjSnh$TtNGczecc!Z?B?N?=C3ina90VEba#|$)fo2Ud#6dXIHiR!|&Q!{+bt77vxeo zS>Hrc`#GF~DDYUYSr!5uL#w~)O=|oODlfyy;pq{D?9O8IK)Q1{o)O{6>%1>?{C{_2 zE@Eur{!4@av*FB7)HlMIj-7^cXIdF=#Hb6DQnUS}Wk*#I+?1)WJMlJ&@NQ~Yd;I|GIOa-` zWUM0jo2cwgEAfpR4P3xBOsA)uH9|w8puMZZ<1&NS9eTxGhtjMx=`&RZwP6iDZfO1h z+BfUs@}KVZ^Ew+B*3T6K?licJ?bu26Nu*`PFYv%s`{yD&zZ4FY@u}yBpo8G=ZrvCB zN_hLn*V`f%w@AfX2?;`9-`2Mv^n6B0;&NL|>+5}yWx`kx{;PZwX*OdG9`9`vDSDe7 z!JcPqsXG2wC9GVNVpw^#wfkgWxq2vnmU?`>QKZKs)t!AvCN{4|b;l^R&eQ&s!RkkO zHZslP2e2d)b$JSs2mpt-GmWR!6$kCM?grk(EIZd{P z+k@>P3NW&3Mcj^WyF)!ozd z+IrrlDLt1~v%?a_1D~C*YbR;x=!Jmo19a)O3UGj>w(;VJDEiZ{*Lxy zjX?IDcJz+a1P{wXXo|gcx;d5N!oE3>%%0ACba2zoqgRScIrC%OTYb+9SkOeD3UuFR z6zC3*^6$>wnR>dr{LHynGQ&B`nQ}cLVmo>z_bKuiHNKErp3lVwlPVA;XPVGo3NzXJ z_OH8omv828C?KMT~V$>{9g_uP`@Ys@2k} z$>YUTLFT5^H zX<#t%dVFpEYd%zu-jR@cJJ8pEgf%7jvL+Ew!qBxb?N((9CG)q@&-=LnKLsML5~pgD z`x{3NVw-Y0aM9A~;Whd%w`Po9);0f(q{atlu*Foa_rnc}z^_s=(^-+p$l@8uLb`HE zgm3l@523Nu^@swKhgkiSUr|VIHtbZohT?rDn~~!+-YK8RE+v6S3UG-;M_5*Fcy#s2 z4AJ*(MyuIel|WKPyz1)r=r@{4Vec;u+n1I*Sm+b0G{@Zi{fIC(CFcEtUG|LH78^`2 zsZH*(SG@0zSp1Z}%Kt%yPI^(&9!Z@XrV?HvsH5}L)XpZ6s}4R`ssk!Dd?6a~iXL00 zRcBP)vUp_%wXqdrAj~C8o^+@dZFGmqH3IJtIp`9kOj@{|kFOv%J4!GnCr%9$8 zmtUBJoQ5%SWO*naUnn0jUX=wxLQc0M=+qF?_!55Y?{E7VT8_JW3{J)eM%w$Fn?+E= z4&DXDGrxgS&|~?TgD25?(Eos4er9b3%0oB6P-1D&BASP18i|{4p_-MLl?7;>+BuA5 z{kF0FNcul~w878q<}OGN~)F<8GVDq`ZH+V8s#rsk<{oZtZ?fHtqyvo~f@R9O6SqvyV$9HaTPo}#O z4yaNYXQrw3iX2V6|Ka~YH<|1+Fnnnis}|20S55Y+Z3Ij<-cu3OXJY))JdKx(JwmGSy}TAwIa-RDwpp##cyh87cZpPZsc(TWfB;r16hST-qH+e10LEE_5hEd*5f zYHYdgJD@KOnNJ-vSAr0#<6~ zGS}M1&y*mGbA00nYvJ)(q9L>OOb2fJ5#977OYr4|!TN&UX);Bj6DB91T)Yx`nQ^{A z$klm1&hK{pJs<6)6cyUqm zGKfU6;ZQLxXBn$&s}whB%237KvRE~D`lF=w;X9_Wf5sh`B{M-H%^z5Y^WyZjCnUo7 zRc!UyMcG{go|wn-$LB@J(8t_U|1-NYw$HZhX5E;>yV&roK{J;pXxpLsPeu}1o`9vx zK{y`0A;^YJX-&`hn!ba8Ckr*jAi0B_F;NuQ|C1ni`UJ!k0D}EO&coIUkplGV^A!Uj z9^=84rDEI&Z|^l|R1!W*Wz;+f1I=LI&143DkjECu72Ea0_}iE2o&M8cPr(3MDcB0f zbkZ6ru`~prHLx^uNnCAm-K_QiFRU7D_ zIq75eS-A1g&;E#^7zM^MGLPZH@sgE1)E-cQ!R>@hn#ycoB$&T+dLK#KRCrU79pipx z0&R9Q;4}X7&k)RbwN|YBbe6B)l#$oxZXSx!i9WyVECdanB*#wm-tW&a&>LO%5^y$= zXvD`o?4K?H@OpRiue=TGJ0bwH*AE(NOyqbog%fxI`WhOy^d~bHzV_ht*Fp-uyzGSJ z?}-t4F3{7M{AU@9B9eL?7FJ#_y~n$}vp;`jQR=5Hs{a04Q=m)OHp_Zubk>gl=2{$T zD{7SN&1Yod&HgTxHm06muWQC(So

AghFdx_yM?BakWWe)-B4=P~wY)OBwY*9$~ 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 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/catalog_returns/metadata/snap-3711210276322022202-1-0e607888-8425-44d1-a805-3fa09850a308.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/catalog_returns/metadata/snap-3711210276322022202-1-0e607888-8425-44d1-a805-3fa09850a308.avro deleted file mode 100644 index 7861dff6163a9ae351fbda7f1e663fe9edbf52d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3816 zcmbW3UuYaf9LHm-P_fjugwnKyPFGvOB(i(ST`pHatEmvJ)+Y5qVp!*P=W<*3Uv_7c zp5?HFN_|kIsfc2+mg=igECCUvR1|GNABvc&kBan7P*e(nGy7*|_U<l3*OpsT*3_(NfpUMn&auxpkzafezu@()R2$WnqeaH zbVb7A!x}%Xe2OSM!!?D3LlM`UUz;*E06bR`o>X4yJ zm7**+2is$t$etCWn~o6sh=ZXCXoA2uUPQpKLA8OApQIZHR)oMV^sqtmQGFL_Dj~DP z4I8tpr^t_BVjKiM(gAkBf@k2?d<#NYsOv~qeH*%d-N9nmX~_;ulLQVkpGl5oDqcz< zxDGK0BanX)NKT$6gK97^u~eZk;yZ|o8g8TO5X11cd{uLTu$bsdSPHa4mcf3SGC(%= z8X#G}3U5T7cfRYxYm z@-yiWiwJ~$->N9DD3{5yb|GnwjiF)tYTPNYkgASjB52b#2$gii3rLIC0JIxX&)1<3 zAfTH7%M;8)GidG9F`*(33qz-Ck`gBCk}N2+J211`M=1*_n9c(7)GFywu0GbIk$@Th z=|ry}+y%m2pyqf0lCr7v>CLPH7N<(!x@!Q3ySfJ}&6(z)M4Hbc8jD3+ZnDCr0?UF# zdbAV-wK_k}RqK&Qg@cTyj^`|7Dn31QBFWSBITWvm^NlG!Ka7bzFHf9kiPrPu5}D_v zjgaf<3$0Ldbfj%0Eo4HBwk9*2+}PgXQAH^za$&SITqwu|`nQwZmj&_AJo0?vBE8B^ zLqk>M&Bl&;X*<(DF~UHjmJ)ew*aC7&|FQ42dv5Hg9-0db4-t1Hp-?h_q%ItmHcLcN z+9}LiGdTIYmLm!b!N!r`vKf&7cU=w`W`L;kle>!imIG?g^!ah6%E;rp95~Vf>|i}v zRI#phtL2CyLoki4k-`KgdO8P{7$_0f8aav5I%7r|hBX15&>6?X#A1>c1zFZIF4-4? ztuk2&MjEyF7(;WDi{4(Go)S{xc5881cCbph12*(lPE9vtUTg=C%A#_`XF?UciX-^UNp#vhA(5yEm}%Zi6iMGA$=D>0w4S~md+rY2IlpUu+kJQL zeYte@;d9%2f9&1!#J-JJ$;J0CJigbv>e!1T?Ze?R!_t(n8G%}FPFHoyAF`o)!XTedBp+w|0dSAMy0 zV#BFR*T4K@*vU}U`?vϼ*{/鿿D/x98okpSơ@=cJg@E?%R#WX(%>J3(7,{ gP) aIOOO\|zb]bj罩q\FFZzB)PJ`KJt=Q(͟t]`- -D*?3'=kZ -ʬ6)"K;X+ {=H˵2 -wֳ -nޮY(eMDq^oGR(唂[T -h L , D+6p7@" A$d@neVc:єzZW{h6Li6іfRS -m+a!()eioiZ}TU(e쑿f%$PeJB)(Ər¥trjJ.K?\)7JYg@V:R: -u,}_}R;oB)|r Pʷ''P6R*Q|?@m'Q#kK8cJcc*Ot'6J) !S4ʔReeCǭKJ)sPǠ:N}gvSN[9(Cqהԟ#s;|kD~X++R ^X5~GRgk|7 J8RH)"2RJވRp*@5-=` DH`XN@> ?$ ȭ2:fE&2] i|jCmcL5)NFVİq룽E)NF*a R^rk,]Xe4JхP5R>N)C(p#˽uE9d+W.sUS6C8grLC8g!bJ/SQpJ?c-nK)wUοj+bY*Q?J"D lxT%9xǔԴ 2"x]q^{RcO pJ.R[?R{6NdEi'p^;P>֚f.2j:5Ө,b~$*>c;vwLg_RNi )s7 -?ĚW]pJ+pJpd-ܢ -P@ 4@ t@`F`f` XA48H $r+GhUM+բ״iZ7AD~ݴfhl4wN^8J5o#|57vJr()A8%7S] ;~9 1rR)9hє!rʥMlu5Nyo 9%S\ S{Ll^)e~ʯ(@]6Zr3/)Bq^oOpi.!==!#KMMdpJZ{fߒSOTzC=Q<6N?4e+?Қ?65) OIbs -I8pJk2tܢ -P@ 4@ t@`F`f` XA48H $DZhjVOКVZ;Lh}'цq6nڲ> HumfQ9NnV|-&WH̪N锝);!S,9%Rv/B8es;ENI_ VSJd}GhDJiRPerLY^r\vZ(W!hnjcI)i} PyPʝ-PUr?;ಗp[-#ݕSR9! -C)%.)e˥(R^Թ8}QINycjr E'9cTGVK;Zj o<|v ;>{OnJR*+S&P$yK>RS'W~(-Y)Eh0"@$h`,p'px @HViU C53QZSBk;h];oe mlMŌygĔ6Lqf1Re aZWH_U9Re/+J򾬔 C(EXrbB3@. }˝);!;SC8R)G2)&O-swƜcgS.Rï)Yo yA}*bJ}~J(i,;hK8cNMuJǥr+.KS-7=C$x=ؕ)u{on)G_t:PCq9Mcb{ᘲ(CcZS]?2Z-p[uDEzlO:2))pʮb8exMBǒ8W+N}{?.i}ށS:>[T -h L , D+6p7@" A$d@neV 5Ӵ6U04OGhJAbJ mm4)Gsi!s<-~) -)N D9(TKNфpJĔX?S.JSS&ӓ-wJd|) S.a4h}SЪW8%9!K"޳S)oN-8%w>r9套ԅ \rJκe/9%./48qpb+V -i)ibJ -d);|a)'^SPYG8uM_te)SrG!vq״TqE^kH2W:s^t[ǖ|u#LJ|G+zNmSzQ() ) UpBμERi<[T -h L , D+6p7@" A$d@n%\8C3s5vѕ?x.rccjg%\8e80lv9˸*o#×+ 18kɵpUeN)*QT'Te/9%.{RDD?Owz=AWF|zF' &l}ᔮ/9e.S#h.ytݜ~3Ӹ+vLM^d=eY<*btfovWGExlۃlޑk;2)pJ?pJK7 - Jb3pJmpJ3pJ}pJA^#;P X@V ln/DHɀJRSNkm+g4Dhc1ceN!Nm @;zGhOd[0B7-2FSԲS>Ҵ~) 5">AS)_dt~Y ՔdJd<)+eU^?KUz:N]Tt휾c拢 qƖI?G\e|#Sb->qV;ȎOA)ζ^(ĔOB)e7 -H;PJPJPJ^3,J-@@`&`"D`8p x  @2X\ϨhVW3Y -)Rnw-m$ ж4;I;hg Χ^ڻ@PIGi_#ad<%+SJ9_)wʝB)築R6_3\SZ2tsӲS>ᔿN9-;;!;b4ٕɡV'񂿹/bs N)@7+Ĕ*QĔ. kh=\\L z4Osex28/h}<`7}SF\lʘ_Gy&~gK5Cq&,J{vӏ=1eQcø4E[lkD}!Y:DwO-xGβgᔹptr>E7 bqJWR2> ;P X@V ln/DHɀjNZ]hiNZLgLm6-e)d4eU J;iq뤽4?F e՘^v,VvdʭϷC8) ?Nαp>)Ym4%=YU!cg=$+eO-?$+eOeo{|ꐣ)ZbJzRL1C)mPʥP!L0^SJlfs+b]|'ݕƻ\BBl|r`S{ٷRJT5Q&5xhB}<onWݮ7ܛ`z6oW`~!--^ɓ -kGϴxGR8N4(sznstURuB,PD)U~J/u}ISCys(Aܯ -P@ 4@ t@`F uX@V ln/DH`q~ǨsYMOv:F7?2 af152԰KkaXGss}XX17#.T&~m_ᗟ_ Zuϡd7/Z#eC+e~"\/%9~Ad~Qݔڥ~ _N :r*Q./rɻ -^™5)T!>%&ĹDOsebSR -[:ܩKTœT%;Җ* -& -'6r>__b}qKDG'2<L={EtއiKٳgLެ+)_:n+XW~R}~y~i 4/d~UjZz00 -  8<|@~I ,ƨY(d5vm]0;톱ct5w/,d_X[[ ;eC1 -_ԣOqQ#3n-Ѯ@Yf9cߤQ#VֈU?ŚpTܾ [q絎Ι9EKTS<|Pt{]W~ -QJ$ZiDY3nQ(: 0#03   ܀^$rJ9`S(^™¯OMH!xN]b|s%'pcW(nT59~S@)Di;<8f ʐ1Қ*sQϢ"mTD_dyl>Kc^qbjgU>_{QwZ֍[8ҠO>*QO?/T+fGYp*@5-=` DH`XN@> ?$ ȭeTRGu=e՗t0 u\$c#Acde 3(h;ƸX\2YUGY -dDVB)Ju\&J|WGnd}r1aߖVpQvNݚ,ZͳCnU6){.S]f~0FLy#(R߉pʝm~jSᔛS6%-{ g)||m8>#ĺXNp - .1(f=q\ -N[m#qA6N#cfqobFg|Ռ{#+7_3ZrʦM!&Ny{g͢[Wsg-Yd&?lh}~c)7N^gs;<[pkNA78Z4<8rY8%*w %NɕZee/ ~B8ޛyOp Ԕ O0N"C+ݜupih%KTCTVv1O9f'͆GҚ ǷX1`uO*WD!3K}?_Tk2t;)S]ᤜ7W"K]8r؟*>!*Q_2e/ 6 s B^ʧRB+#^brKA=g䅶 Gx78u}}iN7;; Eܤs"hy5s(.ou.* [5wg:w5"" m;oGfBfi<}02],,u,,-_AU胪UBT> -}P*AU胪UBT> -}P*AU胪UBT> -}P*AU胪UBT> -}P*AU胪9̨ZQITɐs%imYܶ\w3bM,Umf+e4Wʐ,PΈ팯vR,!Qɋb-2-Em5tU!A~ֿJ9;R̲R3-)J1lsV(ePʤr:"o~{POR~S#WY~*e1@%ƸY*k3wלŴT4!mPJ%z\\|IHW֖6 lU }_8/;OQP_ai:Raydemuo"=CimMLV󚒭>t??tflQw~ڢjiWy?*]/*yL[p*R(RԤhHђ#EO -C#)&R̤XH %+)ѤHaIq$M -G))~R$L -aj$MT%jcD ]&ќhiO$^ɤ֕Ȗ!';-HBO؝;(%B[YCChhl~+kh} mZI5gQf\,c򳦄Bt[~S sB.vR.ܔsFxXG&/ESHo<#WCH6~g^p4ąPl\Nw%œdRc!4o7ULDTP_Mx*.nVvmӖnVjf)f/;MCCyc,}mVE67CCy}442 ys@7 - 0\ޫOBCPv+44xK4DnZE -E )ZRtIaH1b$D )Db%%),)RIH“""#E"OJ$RI!7Ou^J^m&u~o~ӄ|"rhhh26JαDn?לHKD+LzVMW[ -!o:Z_s§U5&og,k(2Ҿ\+DPZ |7:!䑸[;`7 D.5X?i4N5,i -P~Ч ^™ -Ǧ' izF\KLr4Š7Y&4Xqs. L^ΤڮPSh:7kr%P0/m6Tܮ0Ul0?0wb<%Cowңwɴ}{F9ttRrJoe׎w);GJ߱iTU|Jow@狷O~X^U -h L , D+6p7@" A$d@nîjf~Vj:ccO0 vC.k, -Rg'ژveXG+lbYn:#gHc, /~~s 4)m6P{7fz!gXQ SSܕկe{!z8d?4 %)`N1``C0B0`p*@5-=` DH`XN@> ?$ ^UT >jYm]W'Yf6d7VnZn7VM1*9rYvoϳ¨]c}ŬT8;ȂL,F)` -D05#īmM>6tu2g> 1} 9L>MFkS:e0/h"rO?/G"ܛ6M_.0//{ g(𧻤yE> Oz$ĺĄt+>KRC=7t.rv>䡦u&0J;8 04s'{dj)Q7OkKiTt۱AZLg.Y|w# -nWO;\?QPxw8y^Ȼ(+jEwqERm;ܢ -P@ 4@ t@`F`f` XA48H $/M.Z@kO]<11^Աmzqc+Y|Hfivv Σio#/ɶIHK=[Dw=C(,yǍ!r֚ͬ>Y>+JRiRY-RmqۦOyI'DžܔC?Y2+]{U#WSdhze/E903 K"K<'ו.\lZFj06eΧ(>"KۗJeR+U! msBW"KID= s -hD[,["NԚ*EVKE5c/"K3"KQ{lA1ZGLg!92g e nDnv$d!^,? # "P-@@`&`"D`8p x  @2XFFUĐŴfvѺ*Z6ƣgq7і|Y*Ȥv!cгS2Ջ[ib=Wedb ӲR++B)d!>_kŽ 3gIGVYOJ NJy4Dd *uϮ\Ǡ z%iDYR%(}ψ, -*Q߿1,$D/[ΜRf%ʉ4.(>6ΕJMxA,+Rv7W}yw.Uͽ NS=J;0醡&iɋ2{U肎'oZF-PJwQzl{l5LgTwcPJaMBÌ$'vt ]Arnn[PJ Y)Eh0"@$h`,p'px @HV\jY<77кjZ?'l6EiS/mn-9D)?4۳x*C.>&,聾X@Zii^,?_PRVY)? -(Czq+CGϣnVY)~ k?uj+RT[  EΟJGJwr"aK8jrR?馜ȲSP쑟/o br]k zmK8<{= \ |F -%X'(o^,.U[8MVi,ϣG9f(C眱֔E(K騈'\lc Gqtޞ{eDGuN(THbtAC,]<2Jλl+# nQ(: 0#03   ܀^$?2KSьFFWA1d3]i6OӖ*[-Ҏv\3fQZhB7MK3>$nB)W%0R\7~u:QkWS=Bw}Y)f▕>RfGΞP0"uJm^&B)[YiRn3 l\'Q#\@dnݲp"'4W]\ Wzwwܥbm7"˱?QJÈ,CO:6#Gܯ -P@ 4@ t@`F`f` XA48H $̮ʵS=vuCdR;XrKZHd)$e `vwfv>.b7J.fsj#}]/kY?ulzUu/[A/|-劐m=I9~p܍Psr Tݳ,?M{eBwY/ۯ`fد08My'湡m7EUCiz6G=TudK?tO)5TS-*`41'_/L}ܥsv| M*E|*;ңJVB&EC)zRR I1b&BJ)XI&F -K')nR8RHH %_l8I5:&'7i%&] I+J棒%ٝ lUZqDrיD=Q<'IҪhT,5\YC aw0&zЯfidٍV%u5V%u\W_vN^$qĜRѐr5nhӇ|ӴOqw ؒDm0-{ g.ć{O+!(sZoBCHHOHWO7=G`*_fh|1ubNm#"FsjksB̩~1guĜssfw!PqCsjN#z1g -fbNbNE bNIb1Ĝ/wHVB&EC)zRR I1b&BJ)XI&F -K')nR8RHH %dRT'$M]vvnAҏlb - z4*$KD~D[I"{LrT%:#W(yGD7Q|7Ij/$("BhY =?2>'kvsږq7ߚBoMrR5!<򛥘s Ћ7 ;so}Dqq /~d3sXM>‰+=)r+5ΛxOJFjdz򼪞=Oo>6бsT~&URSPSַY߇3^]ϮBl!PULy$bQGb9/GfZGsƻs!̾!b9%ޥsE&ƛs Tsp*@5-=` DH`XN@> ?$ ȭIPwhv΁iV_ogXCXn7ͣeX9Pheyu,Wd^oe٥dw/~1~9G*_kkŜ0gl6$m>fZ/OpC史_ nuqMFs.?tm9Mm8sv`U94/wUĜu!x(KTѲp/^L缮TŧŹR W{2R2V挣9ުcNlj1YmFĜj{ -69lĜmmG%1~Ĝsjsƞ!9sJ! kQТDi{1gfza݈93wܣU FB̩jE̩}C9UPIѐ%EG)FRLIAJ$)VRI’ I/)<))")>R$RHI"%rjV%u_s0QKԷIL0 $z-)23Dv`m&GuDrJ_}/`7_]ap,Ih>O{NyUGsߐKA>Y3NZIP_#&1/BƜmĜsfsmK1 PvGЇdM~ԅ~؃s-% _s^::+%n+sISSq)'4tYhh *w -Mm!h} -}i~Sw4T AΣy B54tqh |44: 9 < ڏjѻPqhh#YCUPIѐ%EG)FRLIAJ$)VRI’ I/)<))")>R$RHI"%r}T7QՒzhCNJI_)1咡e%TGshht[EL"-Jr-v[;%k/I*ڴjr4lGR&xN+V=+Y>+g5J5#kBC׬ЃʿuR m\dn2N:"RƍPǧ~j'# mK8sq*{22RO%e$>sǥޔشs%:ni۽mM~pjLa?c{+4GmMC&l6lXS ;o|qK["[me+oG~iUscf*V>B{\=VZzGuOG趚gݷn_p*@5-=` DH`XN@> ?$ Ϥ`Uҡe׌ٵ3nwLP0aɠr~YeuV#ᜱ\[CjwnW./ߒsߗbÑ_ G~s$dfS Ɯd-_d:-E/2vokr^ArRs+ܔc|ЌPȟ_Ov(fG2.0 ˞A|K8V-H)W)|\ \J!WP@)햂Iw/Q}I7/)4 {7kn_f_/ U+cL/9[,/o/SO*G>ȴ5O`߀_&_:ɏM:Jw/ ;K=KcR|W[ʲ1y4P ݢz00 -  8<|@~I ,*Ne;Ӭvu-~e -㬱n: 9hNɱ&Xvgvw{X.4\o.2(e!bR+Cž6ks -0GwK֧^O|P~XJwCBY0,̅k2keٛsO"7A/ -`]#W![ ?'/{ gp8'X쒒J2cEi+`vxmGu9M㏢*`nl[ܳêCz+Gu͢}T?Gux@n:|@dRyB5WRJj~}TnU`/ꑛVB&EC)zRR I1b&BJ)XI&F -K')nR8RHH %dRJTU&R=~BۯJsShN$}԰d[JȬ飦mu~v,ёlGDo/ ՉNFWְUjZe%+{*bfq΍V]|bkꑟ!m IN҆PQѪzBCk?G7hp@,ιG97)ߡr=>bK8s} y3oqV}CN,7;,Ƙ2hzI<9I1߇zwǼMٽsCH("EDd-RDJ)CDҊ-a]EH)bw_6Ϗ pP(ˑALJJMtg%g_>4oHO[2R|B65o{_xnV9[:w*&?|'O<^OV45ϐ/h8`&` X@؀8D> 47mqTGfgY/iXⶆqN_Xc>g:ǚɶ/A),OrC ]9ⶆ.NV:JQPm gd((뫲((;#L]pJi5GiZm k 6 r~(eG^r(N<_4@hS$%\ߏIJ&=T;5G!8Rd1ەd3ޘGRO>Tۼ@A3HOtxǂ^n(a|mn']MS[l'~b/}JJ)9JM.n/GXg'tb5.u׾ޅoPL%ĝE*Mᛵ OS9*{)"vq.kMdwySj*;a7RY=G1 -,#vڶ=Bkd;31Rΐg"[פuNRjZ Ă8` -x`vx @: r4 C <x}o卅7٘Cd6|Qm7&:v^;yݙ;Ky+/!(7/NK<u2 -zvJD9Bo3~nң -$BdB7K -rՌJT2yy{^O ߄_{LDޖW_$2~pg{?c'fvFrrde8Y3!FdWzvoL}sA/V;ᗎgnvWnS/u/]7ϗ/ K&sͱM5,G>l.>OO/KιgK\pdKQ*okKcGIk}X&y)@5-p@ L bA`<;p < |@i GyLT%jlD@"SMCDoɛ{_ -ɱ|c"ى;+ޣ+nb Nr~QUKE?Wԝk;:(%V/),Qݶ*7)OfmsYɢK_Ͻ 9>D;|e`$H_Z;1Sr9Bf=r8E39!+3B9YuoL`K[Uv*u4#^hCŴPqC;#TCvc*SCw؅oPq-XΖmU%zQe-}T ?Rt!ƶstrVvR 0nZWyg&S(i;OzJ9sbi<4E -M )ZRt0pI1b$DXRH@)vRI%GDJ )i0@ufI}"hu}ϵeCFcddHꑬ;7'- -~zXp=g: EXi$kj }GMQ5չ=,}>VI))R=+׃Q5I5IS'{7i~$, k<BCwZ|Shٓzc5bNZ:xxKD)rl -YɎPfg |ՌaT'^X9|L0jf6?rh -èGIH>\m)u댳 0sSLbNFu|0tz$Q9SU90jy èCo`u\nI=b90-bN! Oauf9 %8ݵ3}40Z7/1 -~/~3qwŜ1?Y/?\<]7~r _(\*[~)_~vu_iot/,/>)r $QKuic^•7̯+˕"2$L#9l -%g''_~ ?:T)4Di&ĝGvb6 攽#tާb@zگA̙:f7V?EvbkCnXĜm1UB9ӈ9ӈ9e9ՇRs)t_s?N*|y)N*pTcTPϧ=0iIO;Lzavä&=0iIO;Lzavä&=0iIO;Lzavä&=0iIO;Lzavä&=0iӮ 'qISNu/qCo~ b6iOH~]lx}})/b.izI+k8vU4q͊Wt?5]E$Ι]Um}cÄN{nĜ,o4V\KAClh-1gOpPgt!$WEe:BlGJCBsURTRjYUgus,^wIU,0 mwx'alZk,ܓn8\2{~M- օ3闷?|V[% J,hR3<*y|c=ۭ -LT=r -Hh0z`F`f $+ ؁@n^ H`P,]XqFY}k`Mi5_b&rJkfV #Tb%d= Q}ߕ2R++RjWҙgF_2Jg}6wPW+(囟%oZA)Vtݵk@ A)w6_KH6{6!G>RtG ?Rvݹ%\AFW!$8r2wRR##)xDWpT_#R\ʉ=]KJ)8&wӝA3IzڳK`xм`R&ME%ss%|*.o&PM|By:Um6lA)#PJ*qvJ{[2TUIB)5ǠWQY)xD -@ 4@ t00X, Xl@"p@AHQYYu ic]eXn28棤]MNd-,5-ށaì4c=/DiCeܨaY)Y)7*(ت(N)*GQsKg\)JyFߋR'PPWE)wc>)qǩ )Pʭ]RR2] Cv0r B#%z3D!'rdLޘ});w.)m5_[!ASP9"pÕCC#Ribf=RY2X>L:7ZbyzYY&J)]^OV:9o(GPI(RG @@=0#03q  7/ @tXk@g<4\1og q5sbn(6p| k;[XYV(ĝ{Ts9WImoTnmlo 0Iqd%)7#5q4PrV -]>)Sw/9}A-/ x+=.0$^LiMxsc&0ixe#|,~[SUpJpY| -GL*yO.2S=.;Hh0z`F`f $+ ؁@n^ H`QXϲ&NSik8]eJ9ӷr)5wl=<,rN<͹ 8 ; j^"g6$;Vp?NB+8yUrϡF_FbҙBϘ]JQPʛj}RR)oһoUj]l?bJNŌ(MPJp4b>:1Y\ 5F޳%\Ou7[p\1)U xTGJjWLʆR3/?D#֔ރj##C4kG?tMfJ@Lw5vm4YZb/"lȧ@|vubw#{6ۻOB)C)) y)LA@)SfgW(Ҙ<"h: A,6`N xH (sPjnVSiX]7˴/"4kcdBri|8%9 -Ӌ֖dyWI;Z)mg)RMKAW0X%\1{Dr1J!R755ՁOBJH.?xioL3)5R&]t<'h+u3'p]aJiY-Kl.(MU u-Y|ԮcPJa(cPJ҇ (e(4rJiyJG @@=0#03q  7/ @t@ CUe5yյ0L2:p1Yoz֙ -O-GJabGf^9|EA)_QV -/RVtD:SK\QSҨ۩uUIxlpJgYK)C8)sS)G|S_Np~e/93.vy\)"YaAdef82s %gC( -+:ᔊ.UIS -|UyQELS=Ҕ<@}CŔ=թJ{sd]Xy؅OQqMXƟW%4oK߸c&=8W:<O$Yak~:@96wz&MB&EC) ))zR I1b&%8R,$b%'F'))")nR?`Z<<* -*l{FgA@h U~we}R_iC_K=>!Nw_l{۳ȷ3Rl=]zy'd i4sYCwhnW-;mQhmKnr:ֽ5P;>D_QFʃt~vKrJBMgCdfWHr2]d;Nu nWpx-K<~@n=:G]t|^t<-w L+|(^?2LLj=Mx%#4W '=Y[l-푷mΓI}6b4轞 ; Ѧ0+{fMr#RjZ Ă8` -x`vx @z'uYjZN3ÒL+e q5u23L keIc]3J8uf;f'uJIfBS6J^yU5vG͎zOgҔR-;dDFKR" -ΒH6v)}|JV@FKcKg;_;TҘD;70PC^%\9|$wV'3#8<+)ّq7+r&xCA~wRifvӍd3dӼf}**n+Z$#vP={wsc;_G9d2y*8YTWEɛC)A:dF龈d3܁d3UOى*o%r'H6lR]ǨN9ِHIQ!EK=)RH1KJ)RH“b#N7)RHH $%SЯi h; ?z6 ~cd{Ȣ~rf?YTw q.n{:>^"+p?E݂5YCklnر6ށ;$ޥxo]3E_v9ټ'khB}UM+M=4tc%Y3$/@C{ o޽~6gAwACu+loee_;FIMųd:2Ō$WRjRJH6'R^'mjsP_54%)'pm[])ŀbjmۉd3PR?>a*?[f}C9T3Ezf$o4Mm#M;H6-H6H6dG @@=0#03q  7/ @t@eZ8SsqV9FX})geɤM>g>D{Hi,9 +V=z"wͲR9bƻ.NA)-R|rkiReUͬ-S]b]+)]6r]/J5R.J*l{R[`m/lr;3Ȯ5ڒ9 4T ߺ=H J}YR<ѕe{rRsԌLr8BM :.wj;u`(MtlKɦ`jwfz2WЇdSLi ɦo!W?7dwn&2k?׿f'_0ۆd~$>r?H6tRι Pވdܭ*x*UH6" ٜ}zeJ'MB&EC) ))zR I1b&%8R,$b%'F'))")nRA:^=kym";ü74j4͙kMy˦: _5ȋTR7/O"767%mJ޴IEKXj?AgİӪ55jiYuX6짼==׌'ʟ"?E*އ?C&ͪJT6&jSIṰGIRmUM>զjSP)ysKf>{SgƫzH׊/?v^ ߼FW콮~U++36^O_zz^ʽ\p^ɽ1@vnt2.q3fsJ\9ScsY/$<3J3) GjJHtb#Ĺ]R9J?+{*y:%&(R?w44tYOPQ_zfn|=T.;"_#,yDz:P8oWg/#=/Ӟl*si@coƧkj|n2w'}fqgjG}PA*Y+}BϖE~.{ -ҰO.y'>Mb櫛V59V5=VZ ~or.z_\Ck4Vf v6fhV^OV5BCɊV*[dECʽ^4j E_|ӹm~hz'sPR8*PլXs2cu^q| M-=Ęo\Οj/zrČwx2ĸtGKJK̐3D#Nhh'U h441J; uACQ|vGۍOBCK44K4*4TY BCE| 44L44~]^NmCCoBCuǠbhR4D.&PАВБ`I$$ $$L$$"HD&a%!pIIxHH$d^>AI$I-Ӎ~&/iz|~ݤ~}P0VLg5 -NP~gu@}4~\][ }Uij3a5 2_ n= =єF-~r [ NЫ-~!Jr@CۙKd'c1jwJ44O̖/G&yOC a5wICH4GJJz#>-=5军Ihh⑄?6X3~7$k5UwFi{>uOl"?R:-Z<9?Tdm2KtGZ{0öPݞh*jkPR|kO7Ke4XyVQ`U+ Ш`i5-@ L "@$h`;pH@^I KiRսNp['9}%gh猃3WsPJ}08a9Ή͜{aN^+RTRYWJQJW\i# `eU7lBfrE)]WQ_R\LlDSuO׶t'|l,o '8lֺ˄ -g9K|ϝJ\}su5t՟&jUCPj(fC* Oh]OC/ (zn*EC\ ^i+W7n:V5Yh,Mbbf`K>|/FeFoC8ÏEc923S2wH܎Dw+=!-#!EN=WClZV6̠l5K{]DS+#{\ɜ-e%"kM^6KɨwmӅ>ݞ?f !{mQi\6ʦIT6Y% T6'Hhz`F`f"D+ ؁ <@2 Hɀ\J9OwL#.4׍sl-ϷiN=)=-Ip;Rܙ#!=΃n)UH=[SrZԜrSp [,-˥}yoe(}Wk,b̷ ,)Mwo=[v>.޶԰^>E&G8%i#Ni -t4 8Hhz`F`f"D+ ؁ <@2 Hɀ\JOL'5vlϏpPKxS7g^!RqJ=o=㔓w.pb=4w |[bHb[pAqJdÆ8Ȼ9%ӭn=&JYQ>^`\UE!ʔUUC)-=5yJA];c{'cKnU)9cPJ!P\K>1NKƷPn)rtKC?]tV)i)rKNLqdxd7"'BA$I+NeOPJ|;ë=ۃniJ2wԼK;QJɾc߶K_t2̍؍3wG -wFwEdJ-M=.CǙq= e^u/tmC{g]@~TE^{K-fECVyEC Ê(L ސn*\Vq= &z~h^_W*uJ qCƁ=Cjhߝoj_Fe@DCC#z-}Ͽ!7 x<8+!'B> ّp2 qҹ6}Z>뗶3z~Ǣ*0y${s=gs̟lNaq4`h x`&` X@4؀8$ /$ ȥ,bXuў B _(+D5-KbeXÂ>%8c\}X -`'xb|1Nw|7_~a +_a”9Fcғ7 9t/_ -*~Qn23 9%~q-by~y)1[./~ٝ`.Y [ !2sJ2T#3% eN'~HLYL2-sQo W˜],ʜgQ v4E涷RPlʜ ̩yn؃2]ٗvE4M'eNTtiʜP<2g8ʜ(sFo%(sjESVfifʜwi9QF3*ʜ72\4M!&!%#IIHIHID$a!MJB a#a'$!pH$$|$$$H$-s:-}~݄~3vL>s:f2賵>O'+>\`n˨4# Q!4D)T4%:~Bw[;{}<쪛_+z*쪛_wi546'suAjhh12ySh3_ 0[^ 2Ę'}ky,sw[ 9txG'3ޑ($|xP2edtۙ<9u(sgjsQɧ'PU?F.2DSm晞mՇP4}0eY&ӯQ)vY9(sߊ299(sZQA{ eNm(sNoߺ982g2Khz`F`f"D+ ؁ <@2 Hɀ\JW,4XMmCеŲS?5ڌXsa,!PkO Xq*="xFlRa͋Ʈ;49_ !_:B?6/'9-hI'Y˛_~^˛_~~Cʜ9M;6QWu//^ !p^)'iRzwlC8s3x+mTfHKJ$:ݙ)Um?dܪ_ ->}s;S,w/lf_@5p~)Tb -+|~rC"j.;/Wۨh_^~XF~:#_KA ~!7+_ČDWJbLFÓr&tN?/Rv& tN@u?[Q)ju^v+wg YsZc_7 ',K{m,у O '\o9ݾow5ή" Qc7KwG(ox-]R}YuT҉ (\" : A @6`N 7 | $38zc8u 9i9]r|6=\3sy2G&Xk)'dq9+8qs7rzN=ZQO  +J٫( -JJ2aV_7e甧 6dOreʆχpw)B |7S^i23 5{3pw>嚬U|cN78ej1/NyoSImӚp C3+35^&C3q)Y\ x+-1=.!39N)SJȮm[uJvw\Tw6(m٩D4J3/ٕ+L#snKD*D6ʟ'e`v{3)gBl4Swor더wN)8HJ®Im\" : A @6`N 7 | $r)1'Yf^N_90g̕))efY[g՜X̹8O>G&OrNq|IqUϬ]qJ\^cV[l6|NfSsn\O)K 액˔Z^W*R^*D!Jy핯,SnH3ELձ:a4n9t3ʔ|/;> b"Lesqkf e&$f$dfJLw& 1Αt$d%Ȟ13>IC[zYm߸n{mh -_@TڠQA/ nB4V h&k.26wmw ZF4@ftvkh0r'ڠ{UEx*w뾂͞Ų;TR<ۄ6hUo l*ٜzJip4`h x`&` X@4؀8$ /$ &031XM h /V+c`LڠjZ '[`˂X!OG#gz_lu/^Y,_Awސ~ޔ`V̊r7]=e.^1| -f,2j+VYˁ`T5 A/5ӅAݨYyO0'eObZq9\ n 5K"Hs%Ƨg32RϭY&![ӽ*w+3]ʳ[wA0 dݪ_ \dl&=}"ʎ@0cD0Ev? C0@0uLi3rQ B0F=LA2{*`Z`:zi5-@ L "@$h`;pH@^I y -sfYP}`(%Fr#f:t#Γ$ߐv恒Cvjlvuc&R:&R7UQttuUŗ~KU}2l7}0G_e.{~WD t;,Edz:ʚJnxOekSٛJ;{{m4ϖmw?KhoہfQ:_,\yN~\&M`HIhHhIH$xzF&f$"IXHDHHI8I$$<$$2 /  $$άS~ܯ h&ڲ*NQao:22]Ga1`k ث8'Ke2-?;EC4V4 ! iBhȸ1{N\Iׯ}NWNPrNop®vCs!wА44hY}^hy/2GL> -yOCIIt]iU#+>.]̐rB1*[*TVe^9C/lvFSʜ29[(s[I͜ʶP@Yr'2{vX)!o"e+C<ʜٗPOyeBTЁ2wp eD*ҋ2gd{UyBhz`F`f"D+ ؁ <@2 Hɀ\ 3eNty/_'Coq_i \Cʜ"R4 ։L5)8q(]ɎقXeŮ[)eN@gCEPP~6f9Wbo>1!4׆ϴU˗B97(~]224L2gvv~ږա=ב$ߒU-^)@ل6KmC8e'<1-#..-ÑHs:R=(xĔsR2 2gen}eN 9&zyD++<2f/ʜ]PtۍQ]]oHG6izeN 9f!|ۼ6{*{191Zl)ڽ2~RQ9iԁh_V:zOl|/tR!4i 5 -  OBO@HDL"D$ hV  ; ' DB&%# $D"}C~Ͱ_ ??ӏ 3c772lb  [ypI{"-oʿ`d6WiR4l -Ҵ!zwdVt^_m}4BnR蕒c9Nʜ^-sw@C,̩54/v4t(s砡w\OCЃ>R4+g(Z^5ېjƱB*?nkjTC͹7 HOmuRd6}h5Mx2=R=#;=rjv*fĈt".l5UFi]Dvq.җv* 0|eXj8BEe+tӿTwWL)O;7I#>`#x? - -rpVA-l T_Li5-@ L "@$h`;pH@^I Kg`㬦ӖpngYVYk*̽,r ,,Z 3m/AV\a8y v; )JцP -(eHQ3R Y\U ގ[^V)PrKJeciIl׶=?R~r"|.(J߯7(K^%7Qٜ2[F$Ƽ|C8J? eP$\d^#4)>1#ʦ JIhQL:=.zXT7<+jr-pz^'E(=.Cߒxb -P\aTDNR_dsYHrvfm7!J1ĒPJPJ7I3yyErMQ+RjUPJcRp4`h x`&` X@4؀8$ /$ ή嘬/OpZ^9sy}gEi35dmMg]Op3xO/r?}#g;\υPʀwT-LzmJ͏]gm*WR{m o(Ji olښX.]tA)T(޳c6dJW~Uʵ/̖ JiHZ'(œ':R<H33nO2=(e*薶gVDŽ;s3m{(uc~zv޽nbМG}[Ua"<٩<\ҿ-͑% ;U ۭ#{ɘ)ʖe9r6#*`SfO[*6Jn:yR]ny__Tz,gsRʘ0 P ` D V 'xd>A1aZu)#p #U~%P,S`. [ ɘp`[`=X& AA*V;%>>&/oN,F,K0a=:m}/ ;1~{ǻwAu/;O,w/`Go_%瓃W/<'r%Փ JiɎT>8I3btam/c.v)'RW//E'jf)|]_fGW_/2/o31_D`懦p'^}=`O+x9NU:̒F&=#_tڽS0;T|3ݶ!?O8r<"Is9$DRb&LOIsqi)WO&sn)~S#^u5]7yLQ - -aY7Jy{h/e8Q mm'`ZN,z|jj| Yi`As -!?SrW; -0Edv{L2=Khz`F`f"D+ ؁ <@2 H@ Ln4mka c2A,c`*MD0D0Yjblu:9C5aA+Fb֝V~s."a|Cs:c.j_>kFT*>&-`xE0B0$Z\DA oB0ݱ*[ &1 _O!O!IEpC$C3=2!Ν"kL?/萎7&<:۟ꚿ.]ǫ"+E h%i_Ŝu%bX͒uW'\tC))r&/&O.iEWQ{dPePHhz`F`f"D+ ؁ <@2 Hɀ\JGrLn4ӍqlwsvP'8g^"U䥔}:8[7g?9WXspNZ)Λß{ -G }UaWhw0OP~Rޠ= }dIQʨχ(Y6'Bol3nq2=.W _Fɒ^6K>%%˗̖]Jb~p>ad*"Ƌ##Nu&'8D)-A1nzjGW2IO-Ȭ.5KQڅ|nnLdE~&J߲JY;Ʈ0e#lvۮZ3\pS"u(SS0)ILᔢc^򩏹d;d)y N=sTΧ4Y!SD0@ 4@ t<00 , Xl@n@AHRygZxg yӷ}175 RӜ0x{-yx0/Wί;r3R\)?VC)׆pʏ7d0edpApݐwMQ^(Ny/h7imֹu -98;K~wduRT9i}ǫSRS> {&\~ͮ$ƜvϚp~m [GBi}RR)S*e=s+T#tIyo)U[7k^ܽ9)QҀjZ,DH` -`v"p  @2 1L4QЍŲK_wf:hL0X8?sVL5KblMp b<1R  -AWt_>x,_WY!I| E˭ 5ЂXTC(Z =32\smwvI4BWFiv1L[G#?&H?d޸!?H79q)̈sItși.q%RxY1yLQ-}Ǖ3zzg;3J08U[~?YSy3V\^>e(nXS6"[ș OdFڻt -L< , U*DAfOi*'w &?/{sS1Khz`F`f"D+ ؁ <@2 HE<+Y{cuYѮXu1lK ?-C`L1)"z2cm[`?C,YM1}1RV&xb|/h9拊`"CƥƮ&2`\sH~™7痑d+tPy/LX+~aBŲ!~@ -ϬN9᥃(`~5*OQ4NP<8)8)=3>͑J!ݥ;R ٝ(ĹC6u)w .fGT5tE"7_:2CkM6g[9ٖ^2v͒=[.ݙ.LOܵ>pr>zgsȋfOS7K|rdh&?Hr)ES4ܮi5-@ L "@$h`;pH@^I yM O70K!8qJ^?Z9coEd4E=uy4g3XŻy' }otR.TbV^4*3̎JlCZ*~ᧃ~Rw/^K^_ -{R^ -{o8AMMd B) nl%22'16'F| -< 06m!k<%K|3\OF'9 ّ.R3d1]L8W)PJᄮB_$FyO)IGNL.YrxRܩT۝/ʢ(L8(cAb*E\*qFE"_pweX|Okl4b.%Jٽ2?]ﲖ ͯs}(t+R0s4OR*!(r( E)D0@ 4@ t<00 , Xl@n@AHRy:gjxu9ncy>'A]7<[ԑ*es,ggy(^.J|gV(E*JGQB)7mRny&埇\g]E)ްtRfRB)J)}Z|+ B @dls7Rm&RewK~* o)ky܉~+-3.35!I)x)C22}B!U]xau`{(“yeb{)C{(vfp7ug/ehnؚUI^fn~i[Do?@YRAV>|ej9f*L+~Ac})/}u ;QUdY9e;Tt.%w?Nx/J6ހ,ե(Y&NހЀjZ,DH` -`v"p  @?-ձ$iIg}Ia$k[rc˜`WbCb<킴/V }[]%"_/!bݐyO׬痙d%Jr◈o◈ok[>rDHb~˛QzɧĿ/;̖N5%5>E̐SRG^|DgKpILȔLuj[۩VO<yo@i msYm;CG)]-Fv ^9J_L'v1Z{y)_eYMEί9T cs*[*{QڹQZyvW6D{.,GɃށGi_l M{|N*|t`$Kr4 K''a a$a"a&A"D4 + HMCB"!H"LWiڂ-~vϯ]~C_3'ȁC@u25`+ۋΕx x}҄_nyg}BIT4 !VP` !4>ޝ,y_C|IQnqu?zo@quC7`6j^x&6Y M?'Y62'3 ]%BCOCC?B vvC8#3bܻ=;=ALud$ƥ8>-?}eէu"2 Ca""L:"PDS";"Rdy_Zٞsx&qs=y%|RB;b:[Cue-AgtK^Hn/-y@v яFHoپl_eyus>xzUa;΍9Ωl?r3;M#K*v[vAW#8&K9-G4i9tN-蜚^BTցΩ1b^oJxtNt x:^<0@@0#03XlN H  A -HQ<3x<ݦ0u+:ٻHw;:y4U{ⅉx_Y>TzB,6 -PJtoϕqŻSIsZS˷e5_-+o(sJs˼id$-YfEܡOÌ(Yr>tEpnx @%×NVA2 i)y7/&/%~)/Er3>R1TOݨ_T CTƮ#;M/-S_/bq._t_f_[_[PI'ᗁ&sX? Uh`&` ؁ \~  @D@2H`-aWbviwivj#k }lyq̜Iݼck]U3 Lf-j*>Ե+d5LyK:SC 6~[UqUXU^ljg2ns与Ϡ%DK-@Z{Ŧaip-Qhj.%CKT@*-Q ZzX@,6`'p$a N%#4v*80ak8J8cgjX m:9 9g1" p*ω#DjBe\0ehOd\'+%dDٖŤwI[g-џ>ީtJo,NY)&{#BJ(P_~R<)2_lćad&|q(e /2GmIӤ41-Hb"= Aopgg$z^!y}Ji9ԝ[}T cv!na^׾& \/%X53^onfɷּ tmjWL#9W2(a蹽bait@5C)@)tn#ݥ1 + -=` L , @0dR&BRCtD-\ei&B,cT)3t}8:88qҝ$%:"&R rmf1Q WmR}!ZDOC?U&Bkl ꍯN eʬ-۲@S,Sn{ -mw%8%Ͱh[w) OÌ͟E/j$I=bp%/ee%f{ݢIi2 YbZʂc }.g-Փ{!A}BV?a͕xo >˫jJ:Sכ/X-7VX#^G}8q?F)c>M,[=$tSW) epSj pbA;p8 x|D H) C 25}+a+9VXcg"2AgnSډ#DUypfm(υp_;d\NyV)7NSnޖsUԙS~UbMeW=p ҢtɾͭҢtɾ혺Y~)}tWM/>մN<ꔲZ֧>„rrQ1|{^e̫Ģ4 24z(-yL3d{}WZjУ{͎ι]yӂCXuGk_jm|Ng={yyj; ҽtj 8V3=~?L)KSpJկh2vXvXjkS>|\{:N)+S<|q5р0@@0#03XlN H  A -HkB]i4ì MZYS:qPLK9uJm}ڈ#ĹH\+S ] #4 ͑p_>C5);R,;5);R-No_e2eTr9P|V'+eUL춄CV>v1+[so7$`~2W"u{2_7Lu^eM>[2w0]R,wzwZ$l -ʳ)g*/<.h4Zu77{}tkn 6˩q[۶kUy-9gZr\=oC)Mlh}?O?W]KC)(SWENC)GiY)xD5`h 8`F`f` ؀8<@>"@A$ -v s3i -92G9Z8)8͙f8s5gYJJKùD\g99}W&ʔƓ\<0@@0#03XlN H  A -HQyu,^;+ݥ?{A*xC..Q\[ -0]Z;NTywu]$Ƌgy`W/l}NZ\) fgԚmY_.rk㊵mū5ˊ_~#,+ -~ٻ-lj* -Pq}LJ6HX_: < _4!϶f\wI˟$g\ܞ%\5KH%IteDO}b<][ ={sftO=e{M mPW?ӏ69*r/Yi7WYFn[Ә՞Wu=O/q}*ux5Kj,6zX/IBr, jF:jw -=` L , @0dR_b8fhrZbrZjyXtm+Y l%Z ^"'!/%

u7eq=:Mgkyzȩ:W/OjW{e:R<0@@0#03XlN H  A -HQz0'8GgV9}Z41 pJ4Ḋ9KmZfV8G/ qY",3D*By\xfWUAA)*Eq[ڠ\bf%g.胮T7*GRfԴʧd)+Ee[v*ϬFr֫,RmtAMRJ)h0_>_|֑fglՆp/eiV; &%R o-%mBPJ@I3n/<)h;miUW߫o/ \qP56.Lgr,SSPݶcV{pU&_¡Wu2R*nq>I@AsZ߄Rr@)%G>Y)xD5`h 8`F`f` ؀8<@>"@A$ER_;JtDƱE76Y[F?snJhNY# o;8P!ᶬRY%EY)IQ}f[nt{+L]Q*w{x;nR"W)*%R'?7ƟBg<\ -mx O)T3` tnIU҂ ?}SRP?e -MIT)uV]yWtF`[_ -a^cTs BRR6>S7/q6R{ɇ}et_-΍7PJ$2M{J)<0@@0#03XlN H  A -HkqrWivz8 ǮU);cTʙ[9K U2UJ(i\%nS W9&HhZ)Y)R>^VJ@VʧoR\[ϥ̍ǥ?ب,Y)'~xR/|IIJaekx8G0]4MP=߃RJ[Pʓy!iB @)4w7x0nڴDџ &hhF;- 2R?!3=(m,mz:TK`rsJRMqA[[fx R:nJ]t<(e[l(%t%(v~9A)A)-P"ݺW<Rڠq|zUY)xD5`h 8`F`f` ؀8<@>"@A$zJY"y06]p0M4FhK9Aaӄ/"9'.W'8K*&ŭRWFf'պRnVEfY~ӳ?J)RB w<*(_*yUP\i5s1^Fտ,KӞZ/s2E[㒙>c݆p=%+' ~͒E;MNtEIHL2(ׁ䴧7GL&eA{J؁.kBcҞ֔2CL yRRƉcs489iл-,?xq2j˳r\춬4ٿn}E*RޖIJ9\4U!L+>w\h2}R/gW陟0sM/DR̤|/ /5>%Ҟ2D!M2 (%-3Fl'e&&67>h|:O5w^dD4a^iV*O:̓E6ˉ}؆ET)UvF;Lۜ9)(,ݥRMh £woPrR@J)x_V -Q ZzX@,6`'p$a 9VAB紥D7 [@b]UKe^$|5uxNa s m|p1r9Myު!+VՌmi|%LVYLeuR~"+ͨ*UQ)߳-s)-xcʟDM_b_Pkm"ckRZCߠHsLSONnx 1F{7;+4RHnIJ܂Oi1O>3v,0>'+nu!4>fSVyzv;ՃƧjh-{>ԙN\VbGwۆڭ^Gs|{mΒ9zL;uJm[,YK4Y#˅Rq(Rj pbA;p8 x|D H) uQ%&a#l'9zQ b'֒KR:RfYIY ͤ$F.5ket*(/7>+ו]Uۡ7ODWʖn*ѭU7e<.+ŢeXbݖ#1w\!T)slJ2Ė,#*͡l;z$穰/K|aIXH -BIႭs(= CT'{>!n[r(K?Dԋs*n Q7|E.m>+ےCQ̡%{ꠡ"`s? -O-e4$4˔f^W_AxBmt̽\[g19ǘeYdpI< ݭ޹mw? 0N77J)!"b"""RX+CXEYHV>l{ذsfޟ|J5{xGmӪmɾ/vCۯv#3Yߌθ!벲3ㆭYmm9.Q[3py~W?鶌_eo(kO*SEO2Renݞ+]?~/p{DY$7;xE f9nv귿ݫj8:r;Ra Uo9.Ȁ.xX_uωT7U k}馪=d%G-ɉ2鮰P6ն\k/~j?<~J쩧ܓ~MR~Wx_-ox}?koBS-RiEb6aP" : A,@6`N 7  B 2V.^=k)^dz'xbsű"eS4omᅮmC}w^,.syMf>))mSM*7ac6h_|O f=gҰٟIg+gP'gfX8' &fUUMɮTmUaü0*x^9͋˜LrR/9q\(EVfvjE)L|[QJGQ|qR?jܬ(L֧\*JY=VJɍU?B"B)WRJ8RJJX/{)G>EJR&rBL9m}nS~㓚%#Ǖ":2rRYu%9uRz۽4(erA/'2E؀RFGDvfH#u"atAklI7Mo^-[ W4u ,mm_ fk)ZJ<ҁRT2 >nGJ})PlndE)" : A,@6`N 7  B 2)昣TqN7ȱ?9CgLS9BRQg= Ζ9'6r13Iݜ<8ǕB)J5VI)k|>RRoY`­ZJyglj\(wʵ(Jl]QJKK>mJy=R.ǹ)ņr#r$#J-R? J'3OR^'Ȥ~WbXOcxW݄^JHጬ!nvd%KNN]n񞣔]TRw:eu'E>cQC΃ V=z⏍n׏p2=o7.;G^n;=vkt[oQYjI%t4F(āWTdIzNe?A;[c3Oգibテ?F+_>HPӭt_'D2h5)RHaIIѓb H3)đb!%+))6R8IIq!E"E&G )!RHI' tuսWhـD -U}~004hht*i JS~%+ -hEC\ՌU4Eh(VEC\ iOo]4Vm[KC_uCZ4KqQ4;ECU(]d&0rIJ,~f}yaɦ!4K!?f?+/" : A,@6`N 7  B 2q^f.4=v|7r@M/I6X[;go͜xsqAN*N7'5VmME)[(E)RFQʟ6$͘7K?fTSKg+J8_m(N 閞<@-}vϮtKdH6$-')i+ҁ !ʼvKtןfe95ő"N3Y GvN;3'#'KSqJӽ߻Xپy=zG.j:.U&]"Z_sƪtSy~%vN߹Œt|׭\pJgN)^B4? $M!8%RL?GtS:S>ӽKhz`F`f $+ ؁ <@2?H exW1Nng;xחF8›Nsq2Gyk o;xg%/v6SKU|\?n)5V(NU28Q!N~<\07wt>Z,FZQe(0WGQJ*L -Ĕx(%JLC)$jz[f.,)A!&yJqް& R,)pgC)َ0$Y9rֹRfǓ+J)>b^5»Nc"{1x}ϲhAL)5Sn%vzJ)b=0r:w_XjkݟkoR L߉rPJ7I| $C)@)RZ^W!ҀjZ,Ă8` -`v"p | @: CAYS&rF);ʑj9g$[sBgkȜ 'q I˜-jVkk]DvM חrNju ARJ>RJٵЄRJysko (e(J Sg/M!RjơCPJK'Rs'''''''''''''''''''''''''''''''''VYc -8u`ncK9r!θȚ āyJ) pucf=zFX('OJ_q|FQ^Jq+JT({QJSJ[뵔L:ɔ+JLQEi|fR,J3%W|Z])H)<*Rn>G>3L꿄B(ﯺ M)<Yk$g{p#5uH^w%f&{]&wU7ug7y R.=zt`7lݬmCZJPl)f}OsƃRƒ[̽m[bGb:,˕ CֹRݧ([ue)XpZdcJ:c7% 6Ϋ'THwƤ|;&! b4`h x`&` X@؀8$  4PFF+˓4ev"QW-'B ؑhI2 KK ’`[L JA5goT#ȽVߟfm/Jd)]PYb{2Yze蟯ş^}6\%JdY^/Y%JdY^fT}Q'k_7!tb//Oz%&nD_X|S?㬹!b|M/V݄f_RrL-:<ə+'Ǒ\r8+纨{W֌)t}R44uOl4wPvy4o׷ vW(SUvsSct\Lʲ]T]:PV~AٖAO^=C_H9{1?~A:O?OMAӤ0Iѐ%EG -K -O)FRLI%% ) XIHb'IH))2)>RI FJ:)gT5Amo@7`|~@_04CSYм臆ɚPZz! Q> ހ6?Xsn hHhS4!hh{5tZNgKnJEC_3hECWE9}O˾u8ݿ5I1n|yAJ×9'tU7<.]f,'xGVGvxّaٍ)+v; E>{{⧠)u¡ںR( wn/wBCmٍPvsÑݯqշ,ͧ;Olͩ쑩5 gGiOѳHs=h7蠃yU=>H<,]AӤ0Iѐ%EG -K -O)FRLI%% ) XIHb'IH))2)>RI FJ:)dA*t{ޠ3WHp"hZit]$Pvm)`8bU=Ҡ<|:oTꕯhHECS4$*D6DC{5t.zZV!S PЉu5 YFYX44 qu+8_QCCAʘf!jڝd-CzЪp5p8˝)Y;;5Iud3)˕-; (zHh(]!EC_!܏5eKJДnZЯyP~hha| - HЯBLhYuΟ\J+38<+G;倛)YެTY5 r}{(׋߬mCˋ(bPy{)s[b|{LĮf>Py2Z% Ξl/.P}*giJ@>$SxJjMɭU.RcBK1iU;L`4`h x`&` X@؀8$  4Pz\`͂&"h]c"[w -$ü`lI25 }dgL$Yǒ$[e}Vp -bͽ xzAMU IkNJPQ+~ (~E !~ 麵]/ǜ^ZGM٤eԴnMQi2-lwWpCl.n+ UBC1i8rVoCLt[?p˓PH.'-9<9YԬd+3+,3bv9*=l{AWr1Ou2测|x}m0Bk\ ʗͧߏm(Wxei>>}ڼ+,T֒ko?ݿǞn<5o^ˏPcj)Tp -M?H )" : A,@6`N 7  B 3tTVNSi8]2pRΘϙrJg,p&NlEsay*9iY,_FaWT(Jf(JP(J)ؐM}O<׹rw&mq(NP(N/)&)ڐo{"3P7u񬓱ڳ@9RcQ8E;c+3CA -o/M4/&?*'9UrَwXXf8prV$LdG8ehAb&_-~WХN>" ].;Zcqޕby{-8e-\ֱa!oa>^= 5SdWΛ]A5rpJ 8e8tNyMq -Hhz`F`f $+ ؁ <@2?H e 9Sq~VYvNgYS'gn%Ni%Ng0J9{ #,R5'r?qh8E)Fqʗ8(N8e}u癆JG;?ꖴQbSҩ(EE) QJL{ԍ )A)JyJa-z|LR1(tKseJyU7xOX"]0C&C)9#;)UJY=wsfe6fǷ\tti{(E۹HLnGvS}-︗2 ݷ|vK쾊,$wT'U҇TCy1>ڂw3 : A,@6`N 7  B 2|Ldf!fɪJ&ӉQA)Jc{X0/%/d6fjM -D{9`DDy"7K\b/>G/> ij',:ݴ_+٢1ǔkwݵ(~W˗Xɯ/}D/^d}+m}!%>qz4ȤCL:ui|M8~o$1%zY̜d'rz\9w%#ĸni_cU+1UOStŽos[۵Sb:(v}SPvcen[4FǵL,cOF#00U~Aڹt,:5=E;*)y2Fn}]Mthʢ4M -C )ZRtI1b$DXRH@)vRI""#OJ)iB$N~x@SЎuEv<7 Al 23Lfc:,: /NUgz7]WƜo(Fѐ!?';Ƽ\j菊V4ļ_tJu{ bBTފnTb?wȧxĜӈ9spcNGdee{#x#Í#RpFNN[633/RwĄzvƤu[s0^0@ 4@ t<00X, Xl@nA@HgLDuiH$aI$"ƆDSAĜ$ejK[{Zp$c7QDD[O|._nT/7/?Uտ3ܪke.ݳ/Ur{i|=랡w݆l;}9wZN=/;ᗇDu՗Fu]c.{_#bCLHuks?ZudIaYtS%)bҬ>Ĝo^uΟ_JfÎ+;l'; vgxEyv+rǟ|FK?2TfsKhY2x~kbq;RKڨ#My%Z,2 ~o_/=hfᗾ!QSo7FU=__f/w*~xi5-@ L bA`;pH@>Ai  -9A}Z $i݀$IDCWP0d^ik{Pds  ÂC] raͽ?Rspú(?cwo6tE=sEQ((~Ն[FQ2M--:2M#ȧ~yA櫯ą~W݄)TgHɎ9v.ϹmtUiwtI=r}BT5euӽ"*DC;Zc3bȭgn,'e?,4}նИk$tOS(7Iݧl hwHZ~A*mck0D0@ 4@ t<00X, Xl@nA@Hg7")Uk:xmq<0#FN:LY欕ۦ94GN:͹#>W}np۵+_3Y(JY(JVgJh;Gi&{tUJAQNQJrƄ>RF21(%ir`ۯY왹_̃k2_O} b ޗ|qx˪p")#S$GJ; 9Ԕ)93FtRP5R4޻̾$#Pʉe.DvEn/ okW:s{ [z -\]aaV[\b=rw~]3HyƊn'a(0هW4xv'2ը(C@@X=0#03q D A8cJ8u );s`gvr|= 3̽dLQ$gmJًyg1'pgj8E8/LQbU"{lMylͧWKw Irk.jM9]o)4I)SN[rn(Ne)?H O)o9߯A߹N>Sb }S8eׂ}!&XU8Wnylw+;Sq9ee!d$)pr>NASɕ6hrzy,)G|~79ҹY[=+ڠ :ѼYʰTwqjmC[̑-h_&mPuvB竹-=6.Aϡ :^H\F׋6z7ڠYAӻgU~AMw lGT?6Ai0^0@ 4@ t<00X, Xl@nA@HgX"$5mI}Il%È`;(0%GLGMR2'A_//IM3ʵ^I߽ڿې63۠ zڠ_]|mQf.\8n_~v RƘoYuΟ_>L^'/Õ3w$rDNvٞs2Uc/5܇L-dKqf\W1k~9n}f~2>xqa>ʴsȖ؁dfr'T.ZQ񝋔r{Q\zrQ%r>YM%wϼw.-1' bSxi5-@ L bA`;pH@>Ai  $usXP/ $m]`;$}]a@0%sde>ژ$DlRn#ӸNڒqWr:p5zV'i5-@ L bA`;pH@>Ai y[8fU7p3=Q.q|3_ 'Xcgjs'Kz^Z mmq~NfIO4J͜Z8#fdUzuYEyAiC2OXZqٙ-эRfBe-JKR e-JK䊢?RMzxx Jq^y=(#8/DKtɴ| -.3b(rV݄󧔔M).wJǑ:<԰#Ýve)/Ss7A)z(ejAGnu1/vQSZ].t_6?R*pkS2γ ,uA)y[,E'}c[mMGf((enWT?};2>7G4AE)" : A,@6`N 7  B 2.1NifY3r q3}[=QJ!oQζpN\gNpr1kk{ }ܮ(e1HE)lR?xetk.6/%0QRqRj(e-1\Kr19( JR]GR#B) 㫼2\J9vdM8Jl''ّ dJɎT)'ˑEOV}R:oUtk{NTO=&j]ť{Zdc\yW?7w!,~JjKe%.|OXX~)喳)0y-J)((e֛r2V -,B)CPJI)b4`h x`&` X@؀8$  4P8rx ȫ8]3F8wq*X̙j8R3Dwv;Y[W{@5;Ĕ:]D.k5E"nWd[hH|[| :۶X"NlO?rY8eYi;Rr/W4=S -)/(Ni5-@ L bA`;pH@>Ai }#4nNiy].p|/ q3 pFSSZx(',pmG9w39I=|ck/0U:S,0_ސfN>Ϛ1/όrJcS~(exW->Rb>R`Zy ;TJke2%Z||^Icpӊr&lv:R]r#';5ǝ8i(BR栔DfjPTPԜ.t"W׽+;L7Uo.|/>蓈)W[JZD,a񭶉>;WK9ORb3䅐PJ՛'rUr(~ o*4<ՀddE)" : A,@6`N 7  B 3K>,2:ӔrFN7˲,|.2̲J̚;Rf9/3' 9eus1νyt>e=?{5BLB,)1 -e2 Q!1ezJA󋵔tJRN^{ߺ(J ߺ(J֍W &꒲J9"8,RPJ(|e(R -Rʵ&t>J{٪p^$Ypj'vf9)%5'CLNMtѭTvĄz}ʖ P `  HV 'xd~!9$YP $|V`&#Z0, z&Ȓr7YRmPh'#gNm傧Qy)w,_I 4Jd Y䭄k%)~}.o/#:|W-N.JE'cO"~/OJL \U ~ 9`!i̥ qG]T_$ʎW8+#ŝqT%x']cm.qHT=.j+YJ\"[wHӻ\yfL-"Kl -JXO8\沶 %m5^@E͖tAU' gI+ -ȝc,肪YFᏎ{Khz`F`f $+ ؁ <@2?H%9czLr2N7ϱ'X~p|X™8s7, :Y95qBιp휧:8E,)JR5>(E)R6)otDQJD^z3+,7 n0UQcv)"u>\#l~ U7Eq%loj8;u^rfKFfؑH -g䤄ҹ3/@)=ȳ gft"S󜨎4rtAϊlGB)\Yr*ěNǶΣ ZՖDkbPп}P彈.hJP$J(eCtAc ߭(C@@X=0#03q D Atpfޘ{9WdbeUlWӜa3񦅳J'J歵Pҫ(e{RR(J1lRگ:nDo*Z)9}?wD=嗊S"Qr:k|\\b_FL~dr+3+[ߕ+7#<)̕Brp WO`DW2ǕA3$K IʈTrt^q#zexmkf=^߹ Z:N^e*a162-}E].kMqX|}m\t#G4-7J~KdJ1a^A#jSB Hhz`F`f $+ ؁ <@2?H 9&Wp2^N/s%09g.N)ᔣuNpdwpb>{kGt?Dq;SԊS>)lHh)#^K)>> uz|C64KQ]6>E(E*Y9s* O) >)Fdb^uJI™Iv Gtrvؓd;u;Uw@U5|m+ei\bJ=2Kfmss1k)(᧋(yl2?zsC[bڐYʛY -Pd -ee;e~gA<||A%!,xv!%7>25R$y1wc҆Wxi5-@ L bA`;pH@>Ai 4 -t4bX0IW'I$C`K2 r,0d[{g$IMtW<A.Nk6(߹{|~2޳+6慌Oz5"|VbsQsY|.`nڐ2S`ZdR辛u3G>3=dߝ!sϦ1V݄'kC#K9lOj#z=-g8ܲK=ᰘrLC#O|AO,/QSxN%]xX+/ ]sZcyLEnKlqcT\UKr&,LmMߓkCf";XSwY/( -/# 4"tEYn1D0@ 4@ t<0?q\[7pfgYs~HILrOB89"f,۽xtP,s:E)2()RD"2+bE>!bmv"b|S&+2?0 `;xB@*Dk85U^24ٵӢ)_9RE?X6(#E(٪s -ekmd=ڠai扗nMn6U8te3wJo\雝pb%_P!WC6 9LxP^ޚIiϫ -Ԡ-z0L`H+,8~A!" -QXYVU\_rh+X]9K: hi5;XNۑ/ k-ÊmZ_'ȆJ׆w<{_9WܥD)_\qn]6_犐Q:M z2uٶR4lU邦{rr.]]\-Ykr /$~|IP¿ԕߘ\%\ijA -㓒Ao}[7ÿ!y54>eNrŪy4G rVNӽߢM r )T(i-;M:!S>143bt17 Œew1]ÍѾ1PɄfp% &)R)HF&RH* K_)SFdY/e=q_7_/e=q_7_*g4uyX)7#S"Y:WVn~xg )OcI -um`|+eSՒ/".$+%Ȕ@bnOf&6hmP;޺X >=UёP4uj,PT,P?ӟ,@|H8(@psZK$New6sfi}5>QRzNyo5*Ʒ¡(CN+GߟQYq[< -(PF0 `;xB@*D <'5ΪGX͌CO;Iap|GY;0ͬ=gW.9r]N_?B#47:V A0L0_{UjS޳RW"|)_6J2# Rt[EDY|S,!Zv#5"PBȗ@OT"_׊r| fsg$䋰!=9N $܆LWC]Mݧ|q!˜j/jѶyu]/qtU=Gj~ownƮihܿŚ0X"4k^ŖSO?c>vmt~5>,-SYwlBΘrL"<jRB'eoI5 QAZ `# VXp <!C -D  >JQu0T9F3HDWIr"N]i$Zb,$<.,Ucog6".O)3$~"4$.^^f9a)=L_Ujx_c^!tn;๛hOԕ{/7WP+W|P8㡜g7^Ex6UH4L]cVd4D?R޸KX#%39J^DJ wnAH37X>ȩb#P=zh1RǙx>N=ij -ښ3n mȡxЀHi]SvԚ"xmIw>c{~>-g>Hy rIM|kc#j)bFoiX< -(PF0 `;xB@*DaaeeaA$J`tC"O2x1 3澅[1RsYD\3p qm 2WnU)WgIUtE&Rl⏝VF RO&-+e -Ԡ-z0L`H+,8~A!" -QQQM+mft 3 gi'Hi<{6aK32v!pC0ĄpYiHy^&Rn"ƸnXg?}Guc+e;(ݰO)S(i1%zE;L^x۪t>WnG*)[ϾrY\-CSsdCȔ?2PÒ/-&?oߐ -n_VR 7p|I\FX>ufr`o`zgmjxjxS=i-ڦӜK=̑BgPk\ٚY"qlsb+.ګbرlg}Ȕ)g*|➕GB8{|2UdJȔZdJL#5h@ : `! -6 Np8x aHB⍈rhrMJB0rb!^b"9,fJ-bRʸp7@+ D#LhBLQIuS)S{R-)ׯ즬o8\J3l>J_!//6_j}fdh,eJ~Z}p[<)j-vqP>IdʾJ|b-20+JyusmK)<7ycX;C@wcnor03@f:f>eϓS/f[5X8u8dEۆ֧b[82Rjx ZcuwMuX̓XD2 ,ч؂lgNdJⅆ[Q Li۾/ϻȔ=ȔgpmȔn)S*@ Ђh fHD \|AR Q&UKFÈ'[!c(!pEbԈ)D|\DGW=c\+]8 M}9StR@j.VS222i5!)AL1(.4S&o$>ې)+)lϒ[ Ͻݮ[gW/5<&u5O S6^}HDxjƐ)U!S)Ro~o2B)’/">9b3uـ@Q$ynoR09%dfWh{L 6I/}&)ۼѷ8u2av"׫[9Rע|k5YEȔd4kgVk*Cg;wnt@"XmȔāYdʡdJN8jD9$e -QAZ `# VXp <!C -D 0Q3QFhm7k`*}}hccbb )]fe##o3-gB5LnOH qjLyASu׬JsWEP޻R(mӛR)^sp1Rn6k}S')Eo+[e)ȃ)"ϡIFHEZ^%\'9 d,93+S2%2$e'|'Ėއ֧2Zb ͸UsT^#鯶hs9#{,OƉ-9KBQ>3뭻Z-}.'Ɩvv޿upxR|<*>m3wM3{)3!R+Ů 5h@ : `! -6 Np8x aHBGU 5ǨM…@M ]ʐKR[(c*&1R/N-L)c\3Lsmo1FhbB=2rqJfSRⓉ)SRⓉϪ>oϳ]xW˾ɕ~acIR2l|nʯE9oQ̵-n &!R穫~ڄHi~UJ<&N0"T)nY%\;R&2t Y>T)\f[Hߐb`oYܻOY?pr ٞT -u{٭ -]tۢ 3/٪0_io~Tad`Zxmɚ?'?ʴU~r ۹0ՙ{JQr敾 Z[:*'?Pf?X~uFR^ΚHkRߖyU@4@@0 ̐`؁'<0@R! -r԰CЌ)V8y%NSkU[rpֳ~SzYߘ?fã+ٹ#Rɢɗ|T|_&_n\| /Ҹ(Zqkb|m^<tO]}Y ڐ)J-)TO!,o[| -_|e~C7bhYy4ާl{Cڮ?^ۜyNSYi6yu軶x ZcQSӜ%aJV}bd`_v柹յ%@J.dKƂM|PXh~%K'(YFŒxRyxnd#5h@ : `! -6 Np8x aHBGfT!^"^1}=c($b=]UUx;Qdw/L'g XwYا<| ?,TQ᥎S~4hk{uost{̢/,Nis}QSN\՚0Y"4Zb o jblLsnF^DJZ~R\|0"jE/W)#RϽT#5h@ : `! -6 Np8x aHB3lˉPED=hڈv 2C 10YbkGH$i-$ĕCe;7MݠږMMwmU۟*Phf{iOP8v@Asw* 4nRFOMK|tMbo~٭ΧlYOgY%Wҷ3&])fWBO?Q^U&RyޚԚ*@ Ђh fHD \|AR 1R 7VqbIC_08ìag7Mf7M;ε,N!T8ě;WJzT3vZvy!Ռ1,,2f :x&:cWpooabFCLxחn=Rl")\"RH|U t2>xs&mQ[eOJe,m2%˟)#޼)KwA;=9ws1.]Zz}=ݹ.FJ'nUW:é;[8M.v3N:G7rdʢ/5LLi"R>'>$ _ob+ڏ؁=]kAT"0"'6tAU ]#*wA=Oݴ' -Ԡ-z0L`H+,8~A!" -Q8eb]P) 2aFW5»4E.(ثOs+ +'0#|%#3f|k*ү+&)R{4/2Ҵ*XHJJnY~ag=&H9!U)LrL^~rߔR~!~%֯_.h>F_ "Ri. -;BKOW_AxBmtĽ\[߄,9,f1EX8$prCnnn9 ;GvSyRJ)"""""""CkX+"֊)ED{}rGx<ߢG9'ߑˏux1x%l17lўkKῶC)7\rmZzj [7nK mo;9oK7xI[ޞ,KiۯޞW˝ ^%.]9_8{ÇB ͻvRKK]ꗿ{SP+ <*K^\ KT -( -ht@ L D3+ ~AZAS3ZeSZM͡lZDsiÀX˘MQySLm9J9Uю:+]3ZwhNWLতMɛ6=oӗSh`+JLJJy+}Ztں_f2_į"ůį; ,$"U&o!bS[o|M~|SP)L־vo_Nᕛ雍7eZY=}YO+~#~N>oՉ7o]7;򭕛H͆*bbDžV lTܩ'88TĿEP|S?uII.l9!,~ "xxɧu:nOH{S< δ׻)O]+Nخsqhu眦9N[8LtfԆ.7~m89}Yu`JxUtLg%{6m{~wO]p*2WCpʁ7o+'pʑ)SӏȂ ,)Dh0@"A0`,p@>I K4( U?^5a]6 q6 0pJ7mYBz1qǸ:iw+1;HskW~~#:%V)׊NX \+{N}*E5l]唭.2|MtR,E޹[A)8'&yɦN9ʩJ;#s'8f"Z77Ϊ cfMKH6 @Js14c?Ʀdϐl -;lEyf~/݋d3pf&8H6V .Q(* =0#0H X .<AAHRE5C0*Qg2fF[0mn҆)N(tKEa mde47Ehw$ffno{|E)A lE)A ;+&d?S; W'mR3(eZ"\'* V lb&gl~6DC), 9>E%r$2)G%hrJ 3r42u=!37KNp>C)) T7J񸜩)ɦ^Ylw%Tʕ)ƑljK˔2`֟UH6ʴY=-clN!!uԊFw)&^6j 64XGoo1w,@C-6^hux)Co)砡~4Xhj6c6wY4Xe|4s—׎+ Va7I9sV4D.ZA -E)jR4hIaHё'@)Db&% ),)VRl8HHq&'E G)ARHI&\_/@մ_]Ќ&jLvn8Q_hI4$:e _f=5@$M%?8O$jH)D %RJhgEC} 4a= }MFJk&^?5YBC >5YBC gEC{[!Uq㈍ _;W4tߠxeiHOS4t x|V=s 5 yBvO]p!3>KXhpѭ]*_**>ˠZ̓)ΕFPw+dGz6WƔ|xnbL_Ѻِ2-s'oƴ̰dŹ2kVZ7/[Ϗp/-:rOY'#Mq -@%P5-` D(`1X`6p7| @2 iVU^ӴaɏUXkꊃ_4kɎcG⬥qXG˕8wBu_Bfgp~%be˯Ί_A\_F~!O7IKa#d n_b1\\v ~)j?OZm5?4 -t )G٫9qNz3fOOH7CIK ^t]t4T<3 |fW8iN5`z өEpgS?s\mXx4x9fYTm[̹cs[6 bC2GCɸޏ^j>o %d*@CZ$% 4SSDh0@"A0`,p@>I ,`3Ij`3Q29FM3݌nІaX˜a25X*icelmyj*q|9#}Sb)8C,V AT1X%2wstN74WElRTVB)?R)FZB)?;+SS}?DB)(k *lm0"A~G>Ed &QƷ3W=s pJy/ -'!!e!g؛LIY;&4z&k_QJ]礊Fg9UeQqSS;ik䘩;uN}٬ڐ!($ّ$-(e i36:^a~ -J){JiF*R nB1(eJyJ)hR6B)mRp -@%P5-` D(`1X`6p7| @2 rVtT+U 9@kKh(mMd Ӗj-a]mvŞ_ {~4=H)5fQ) 'ٽN*]Ny9NhuuS-昣EFp?Sr.7@) 3J۹,B)sPJ6뱻2l=A)-PJN((e*~SfJyJkRRjR -.Q(* =0#0H X .<AAHRh My(Q16FH3-W0Fx6/aVւpkvZhc|팿}({R4J7Q)PʿEy nx`Z&/]T]?$N3$RJCb㣓H)geM5y<2))EL&o({>)f>@yonR6j媇pR -tmRd۞q!8x{3%͞ݜ;zJi>|gE)y*R^K-hazSN3'k?G8}٧jCơ;ͦ»̑CC)u[ f~%=,LmfJY$kPJGy:Q|BR&N@)A) ⚼g= -@%P5-` D(`1X`6p7| @2 RE+2*QvӪ>Z]Akfim!Ô2"Z@iccj&J!mZVJc WŸ&hw/B1㛣a*+D\$5"[QR.KU ˙*O6 q WEtʜx?p02)^&SgS<%9gtcNlߝ^3ᔜQ8E1JN)?A*֔DmN^ΙS8ƃ\<‚y=vJKw9.)=ϯ R=AMu);>̕P4-ikn#cz?B73'l(~--FD \}1dX+c*NʬMK'drG'\?#peB}-ßŷEvF$ ]*@@:`&  `؀p܀? Hɀ\J^b㬪/N=jEVѪkϳ)Ú`X,ƱqJ֖8se]`aVha}6n̒.cE,`1veul`ZY’ψ 6($14!$2KL3DzMRAw(,ɠ/OTk>ur* C5+W=s97nbtoKjsvs!]YrJيV2Ԕ]qIu)8;N;$Gs9dS.X9~d۷cf BmŪ [K,c,% t͢ j-p`m, {eMzSlp -@%P5-` D(`1X`6p7| @2XYa% 5N+U!.g4#az]? E6#˫K 0O;jrU#ᔦOB{p(8yNi~N|N?(:PP @$f,V`p $hjaTzddok` -6FIBXrW1v1LsEkvjF8Lndg+xp3SVO+~jp_?S)bC)e[kSnhZݚ1%ySxK̹]=Kunכ{k@|jy+mPgW)ڎgPGT26g6h\Syh7'H3"j AK'1?ʰ- *xmPs2;厪r. hO~{r/mSr_i '偺@yvD҂zJjZ0Q b -l8n@$d@.e2VKuIVU>ɋc8]` }m~Y$kVjXKYg^nzcv5º[Y~b}3`6g.Ml#D\-Aߑuge͊qWtu.YV62q嗢_.f/~@b?$|~O.ߠ ~{+mPF~yq'0rɕ~QaJ3'H/Uz &lZ)] *ܮhqRy#S˩~V<1EѺ۝}_ 64G-ԧ4vcrZFɷYkfklP9"2WY/"K},CY*ZYYJYjkȂKT -( -ht@ L D3+ ~AG\R(hjVN3NZhiu 140TM"K Y>!NѶ p 㮥BF8ׇnITyhC]*ۿ,bJ6m #+Fn/*M~',YEBK(%ITJS%IB)~k#H(D7Iܾ. &(O XCT{u/PJӯUN0 twz<]+N B3ŞN=i'uR練77{zS_߶+Kԁ~NYũ2V/;5}c:5q O Cݗsͦ65 T A)㏠ jJy>k/,} -dA)ǡޗ~(kTT -.Q(* =0#0H X .<AAH)VZru?駵hj6^HRA[fh.жQIsh(--Y9~qḨR RXQLB)2r˻i㕵[K) dmꁕJ2+*.(,*eVT J J*e _:(3W&.nRʛxӛ/ȧde]Н?2䪇po`NrP=vw:S\hR</NwhmT.ݿgv+(|=E5tA}SWD4FtACGЕFTfþ6tAOn1 l,.ZF֘ KS肊E4 ]'ғrnE䁽9?x$G&WЬ_;3'4~[DjgD.׫PP @$f,V`p $-VEWU<jS78m+˔,Y5ǙjdPqgcg⬹-?Αr㬫u+Jq뎲 ]EZ//%9+]PѺ ŦgAaD\$Y$ -F& -"6O1T0o6%3 h~l{WڠL;!OXO]2.^Jj$Q_߷!愍YRS|=7 ) ί{9]W!D0\n{) z{ -]q״>SS ["U)3)x/26 C0ys2[mrǾy9WrrWgf~{xtLXzdJ?!w J!=I0 W(* =0#0H X .<AAH$YŠ(,X+VnazcuXCuq*T T#`,k-e:bB{w -լ.֟n^BL -9FB0qgey84i#}Eۥg_j/&SoD,J̳2l$5f=QÁS \'[ DO俧5H%IIp l<Ju\-!kwsaMsvO3MOKX;^Q//-\鉆f黝TE#)J8 寞@fXW=s蔍ϋJIOq{¼=ⲻ].piv!-55O |ړ0OE+N):fWtqR-9eShuVvrL#Ѻ/8}ާjˍYM=#Krɬ-c8-wؙm֥;2ld!pJ#Y_:u-|qpr,/! +B}Sp -@%P5-` D(`1X`6p7| @ hEMy-9 3tFuU~^k襉SYlm)acu1d9{yKBk+] :eSĘr* k7rF{&k*9r\כE$ڠ ĘcQ)&6φR^VZ)ۡWy J #TRO}SG}S?u(C)Iԅ¹; 3^,B\vv. |ژ}_iC6ȾZwɨG[dʥ2U?/,=)v1/ n#E4ƎRD=Wny7GLjyu9;:1#|ަpdlSp%w)\Ç~m@&4ܥ <բަT="XJ"imr -R(RHQ!EK -C=)RH$%3)1XHaIb#A -G7)<))>RH DJ2)bD#,TUW5~mc"p.;Q_7 ^jlyP9/oiJd;/%'af':&a8=t)_}~j5-v6,jFýJtkÎN mdaku7QC&QC/:_BC5􂨡%4۳r+zLRC薞H!L=řFSx!N)B+NJ;3oyјW)f&3/Ȕ]rewPiF^#+c&]}&Okl~--5wFDO=&3O Ò;ix$7/ƜIo_R2;9W_>PgǶTU-P{}S墤 RAU{W=sN׻"g;> jwr!7ﴧ;4gוX̶eL 0A&it7J@B)/1MR.P 99.7,~~m?J*)^#˔` * Q)S1R.8SO% me},6([>(ew-O~ֆr_G>SGdde(%wC8gJI7^xSS)^{y0…UGMh0@"A0`,p@>I ,&F1PE++W //g|/;PKSP ma,%k#cg Ÿrw|BQ_uAi6<1mxlo`8ZtAHuoKDrRΓmLw@!4R^)GZ9V >SC)ONR>Ǡ^`7=--vv/Oޞ SgOOqyI K]N)rjՓ&o:us~6d3䠖 -RKRB[Jhvm chW3`Z}EEJ9%6?PJHTOE$:+=7#P)MŔO_H7Řr蔛E.DLCw1g?]ب{%bʬaeߛp_OSh PӎrM¹[x &C)ncwS)N'Ck.K4w8eCI rb81u>i"?8ێ 4j4GL)l1GǔT8-!%]%8cW+8Lb?!sWf' -Cw>!M)ղ@"Oj%*@@:`&  `؀p܀? H`yV4TcThuVMkhֵڐЦVd^;Eh$m;H;i.vd+-T2#_wS^[E\`ֳ6ǹoXOޝ}v=џR~`J/~L9)bP;2J<N1%2( G p7)|)vNéaC)!<)kcJQE pب]SqNNU\.é){ӶqLVMЩ]Psl,3̑B) %|ZcZJGBl6둇3lo:b:(s-D!g1% rYz -C_u>Dh0@"A0`,p@>I ,₩4U(rQFIk;fU2Q0BE(|(ecaƚؚ;FǸau0w>odQ)$dQ)$Y)Y?5YGz5{>.'w\B)E9 -%2 Ke,R~ie|v[)u&(e,G=~;+ (e{êpS~ ;v!㴻]|ݛJ&y!uD J)ZzPGQJi 4VJ%*@@:`&  `؀p܀? H>a ZUnRihC;mMD)d|&i>֦u\ :Hi> -i8?ۮYEjJyRbe,t0Ej܍"6PFx|$YH.{Ri{YĔϿK -2LSp`JUq㷠MPMʔmPJo(tr}rda|_! xRӄ=C6qǓSN{Cin'~RʐR=Mo< -')s}GsN;RS?O -?6-40G@Jyh'Cb֎ [ rd -J{lJ,E-|sc};CH)=h|:'cd0%Kl|p -@%P5-` D(`1X`6p7| @2X~Mvl\F]him[\gƧ4> fl"ƱHsՌq2-1%Ĭ7VT -+VB)ʳR~"*4}2Ήصr_u>6NdbcOMsͥ{9kW

-SB=sWLspL=_C'=Mo6}̘ӷ4Ȃ;#-<&3TcF?ɰ};)l厩r/䮊|򊲦;|NnPW^~B{۠o]/#˻'g帧/:>KA+mгKO"Lfȧ PX -q~Y]7 {©iv -N!J|(!y5~i.{& ;AN9ہ6(7Z,dFv9Sh%G0̑=-51ʝmCŦؘ݇Z$:姢S~/A_l4)~:e*O>\AA)r]S:Ĕ;ᔇ)?bʿW=sSἧxoGStAa %Upy TSU{2_lr̮(uR/sʞ'9UEQff;u#s85ؗi6[lEe/]i9d}oo6ܞa;(2Q͇̑7Bq>XR<S&izG.Q(* =0#0H X .<AAHRhŁe*pJ>n5egykҷ8NSpJmӲմӎcTiwO.!wS YxK)S*ă]NY<+Nq먍FknLsV/lzyEJ^)Ht>SaJ kl(UAگ٠x 1e1JP1rZR b¹Svqߠgz؋r={^os.e*˛zVݹuL1}NFվ*S엩nz22“O˘/vf?/W ͟ʌ0e}#ET^9oj,`ғ3$gkwʭMٟm%?pyZ>pݼdB' -_MS -B"PPݧ6>"iAqk3.ZA -E)jR4hIaHё'@)Db&% ),)VRl8HHq&'E G)ARHI&ey,ȯ8&Y:7QLdÉ*X7\JNX [D3ڔhk;/c~?2}s~$qP _/j<1\(2{s#6YnJ_&뫢$ͶAs)hk&JtPdsVhTV[lr<= CCK6_!xB||gaWׂ?˕ iƥ}ٱzPyqh/v)̃۝qN[UFvMCYG-ݽ|蘹NKOI͚fɦf9$cEH6GlF ogN% - :WEkAY .Q(* =0#0H X .<AAHRrhŴVӚ孇W,/M9$ٔfڒM4y_EPlF>gt^bQSB)vEN ܁0gnirKjv7$MYTɦ][3UJѽ p@)yubCʜѩbrZT叠Y| -ұ 4K{6zp޾u9!.rvENK -{J^>s3bq&>G;+SU3u<{gDc2s3,%'ߕoYu#yrG߿Cߖ'*&OGNeE#f9#\P@ T@ 4@ z`F` -A X 8\ x $<ٕ%"+ʏUVXu?ְ̱X]3KYS3j'sFMe!폵ֲXG_,̺zYT, -X9>Y&%J/j/DDIE}V"ޑoq] J1S_6nvgY3zO%ߔ_6 Gn⩸^_OmˮG -jFdQܲ!X6^ˇé)TCz]vo|ovz!)zyսv3 e_6-3gL{dڜ2v -]Û m92cS-uED55/cȰT/;щL/-//O/o/u//Ymr~2an~i{ ~^({~_r߳\P@ T@ 4@ z`F` -A  Sjp $A"V1Rs(NU>jⴳVf4N7 VcAGRgYc;⬥q8xʋsw|f立Z]Gw;/_p_D'uY9\Mshv7o"rx6: TZ~3_S/53[#)uT01!MtvǥSin;C0=LrtO(akWyr>NTEɤ6cZʣuK; Cơv9=̞-]; 5۬edYDyp5} <zʇu '{=h;z^ZQ -( -ht@ L D3+ ~AwQw>ii61Kliބps!RmӺv{v< $>O3nNm ""CdGDd38N)eH"E:E"EeʑK\9I&x?>UZ?j\UwQڛnOS@ pgiئt)AZl(Q^ &x&_J쑝rS&M)7+8eb[feےfgs)sQ>WAdd,ed,JNLw(J -Ny{NRS~0M!)BKu.vE/)u ?sJzr@A~QAvYy /!3DOp씩1M4eVmIO7yVhIU_]i_kj.Yᔣv*m>\gY}߭vy98e%8eɔ@ˑwwᔣ/)ȹy8 +(;P z`F`f`i   @D@6×&Q uєeKL %z 彇0MK|DY[gY?ON-nsדxi[Y\lS$kKYW)sw -!Iymo?N|aN-[H; "{wn O+"\N䔧 -jφBN)g -O07N2b\.(f79Ԗʟ=~f֕\S -^BѯhC!TkB-(~}BmW/hiihy]ܙ6 -?؏1[B/oGQU=BhP(B}bi -QյxHPnC!( ! -p@ 4@ t pp/$a D{ҫTuWs-Nu՚:vީ_ vʜji*s[h9 -!v -UNbSZvgo OɂYL`>-Ѕޏ21cmo+2KrV -|;|E e<˹zod ~*rv;g/'PR\ewmQE1?#=x22$' 3ϯoQwLޝjM&JQ/%h]4\n+_8B*o׏?\aymySɟ~h}qUZ+I8*GSVJuտ?6vL%,֜TΪm};3/H-UϪl:4N*W5{i{pf {hC<{Pг=ab3{X#=lag{8n~{#fPD՟UuYᰮ)ˢ#a5ΰT=< --ꡰ-9vsx{xp8p1,Y-|S^ -$>-Lzn;Hz&|uDU?1gDU?(Ĝ>>xUn}?!u -qĜVĜ4}{g#ܵ]r pWz ~W:`X, -=x#@ otj~<8/4UiA}9wȪ-FeO {ފ*޳+ -mP:ﲝ9nMb=w'EtNh^Sc)A6WqNʥ^Q8: -Ҁ ؁8 @0lJнD=@4cDNUS<-'>bh$Nb͵t:J-j6Wg=M$PM3ksEH -xUVʍ -J|GFt[JH6wn?/FriZujK -R՘/+TN-SFy5nN FַZYoDv%(bB)lrM -JNG||{D32 -\'=/_` '#?ϔL6 V-ƬQ&)4VBnE>*ЊV}񻂡yAk,_T~|f J,e[J}s{\3RF٩}PJl$|XRR -_RF٩gHeUjZ<@ L , ؀8x D 9 p*"\)Q6e* ?n'b,'H%vwwgZCVyХJHh;>|E-+?]|Y)FRbTP --y =_MM~#wIX{DRʤB*]_ۼ+mR#˹ul}$ -)ϞOÜ7…SƲȕY9G||B2ARJ~0E!y%C|#򵣑Ej-.O vnK%Ja~Ik;5M-H)ov-[c1g=(eI(eC)_B)b]YRqiTRVeJ9T?%+P z`F`f`i   @D@6KWJ)5FիD3@]]8NZCrj'jcJcFS1G5Dݵ;K _G3DlaFdˍ扊 -H)T^#=S^NDscesrS1W|v*8Ŷ-,o ݜ+]֙/Ny1K䜽C)?=>SZ.ˈ_O)77\rI~!3ד<1# -_?P fb}犛ǎw; =}QPIGA8-)ը|V[ZcSQSɊ|jj=umj?8,zY 8e(sё8ppJ{dW/;P z`F`f`i   @D@6Q^ۥTA5T[Huo'a[852,ʧ8*(ۈS8CjFeL)Jd8BeNq(8;뫳;6ںy%)kSfb9cʈBswo(k^ s}hJdqO@)! n󪚠殥Wp'~w1\)Bn, -< -|'/b1D%'4ڦ2U=! |j+|eq٨i~fhBӶmV{[qHٻǵn[8*EO:+.WfIe%O~RALYzJ)WTh=0#034`vNnB " (`RAUk,fjn36P}75Q"55R;2bJuRg eԻSh`ǩ4OC$5YJ,Rγr weI-lCP]PH!\&+P z`F`f`i   @D@6Q~1&."|7T:qVt=G1e,OH`kKixl9'_Y)W)(jY))RVPJ:5n7NE;|FM|R6u杯+| JRa=oDJk JCRʏB)4}CJٛ `cAn.(JX<#3 zq1OF> -)r7+%p$u%8:'Dlxգ**qV(֪]ǧ U ^zSO\v2"VV|K;wvA)ǘRJXsPJC⁈[(Jd[>OB)]PeUjZ<@ L , ؀8x D 9 :qi bUSnfy\?)qbhRjZ$Rږ0q'6¦uSfJB :LÓ[S֕Y\%+ePRےR~uGnۢ|ƔWVʼ]&+Ţ)gR, -Jy|;>)ea9i}Yg"yM/3RވGH׳k^Rj~8i(!!&KR>\F]iOt ԃH)w BJy9j*+ϕ,vُ>s>sxg+^8J9wpOϓ{dz3A)ٌ՗١!vfP z`F`f`i   @D@6ѺKO1U nk& UP2X&%b\"P.rSq#U"v -kqhX)rL^u$^fCVbߖ](TSFN>kKSvNY\m9_P١8dy>uN޲{f%roºWZ\WS -o=&rEdžU> X~٨<J$)Kot$HpEygKDYg -~_>*j=Іq2jffL)e+IX)3{\gjw_d+LN {ŚѰ0\lS\xu|NIWTh=0#034`vNnB " hեmb*!Q70!'tg ?9e8ygqPO"/&[W>ʎdm>^#Y[On7w&)[9yϿ}pPM%;E/[)?Og͂`v}d -NyU^),;Üi'r;S,#wOz~O = z\!,7;Hjc3ɃA c9Q{nw_QЃ6d2bpb}(?s*-nNۜ"!t5+eIN 5JmN =]V֚Ӷ_5WTh=0#034`vNnB " (xY$ܹ͓D{?Bh Ϭ&Yri}B(aJ*a˖ -xH+$t'1\Xd)+"+((mQ yRnIҽr;Q7@k++ʯ%K)RV߫|>ɔ,ݜF(E^WJJM9Eװ4rg"\(座#\H~6] LOZÚlgߓ ̸$n\q(8&W>#U 愠9Qmծs?@[>0*כޅ=| 2^=qq<\9P]RJukWƗ!T!ߗ%M#RRL~GN)xEh(00 H6`.^ ?H  AmZDkz_MhWRC1S+1ϳY,LGqW q_\XG$p046ylyRWS -JQJWS -JQmRK)[=)ESR-gו)(/+"+JR= -+PTʿ?\罜?(RH=m|?lt#\ƹ3|/#d=(&|2H̩`oN+e:%N -.AӹϪ}WЭO@ w }o7Rjlŝ)iwF{l|sVWnw]%RJkRJbR|^)ϑR研ǑRJGJR\R*5-P`&`l \ @~"@Adk;ڄLY^K)D[6 -?A k(K>_iz⪡;A2ꯧsT"R !Ae}䔢QPʬ|9h2-@Kj+Pʭ[1!k"iJMVJ"Rn{`;7o+D|)@)k)A)'c"wՏXӕƆJy/]=\V~1RJ؆2O^FFn` -=/#H/mn䴄§9xغRjR|+[=fNE]hmU? /i'޹rRRl.[ä~瘮9&RٞcH)B3ҹ|80%%zZʽPRRRS -^Q8: -Ҁ ؁8 @0l}Mk^;O+DWHzJ۩~ -qy>ꜥE!a"RZA*͓PBsFʅiY)4ӲRR4ےRL~'SJ\'7䔲ʳɔդ=W-ߎ2u_o~)A(%Ӹ^|Ji"h?}}X2x -JirpÏp|ď(|2r㱂tO#HOnЗIA!37?x$`a<;k>u1Asª(N -jB(||EqZS}\Tft-]fho9VvOB)}O#@JYj+؟%  g˳oC)=dY(tRV -^Q8: -Ҁ ؁8 @0lϬjS -';Cy¦  1lڑRѹ6 -;ԟ Z9>WJUɑ +(%SVJR2e˧8c<ّ~ՎGroK[_P~ߔ2$; -1 ->[>[>FĔcpy ߃S "w-)OtاpC)jT>8ӾG`W_AxBmtĽ\[ߓfgYsY 21'!C1:{7u{w[UwNGREElC"""2DD+"""E tr'!ߣGx(r[7.80>;]9B}gVb4n>!!eC *0ԆGh4%U>S~+cqUQ?ѬE)J)ZtgX 5*ī jn6 j_AVm6URT~wWQ~W[?w|_Os99#+[:r:|]C仪^}`Wp個?"xh58Hc-~rU] SoV=_ x~Ie}N7~Igs -RVq_S-6yOߊ_rRNw~i~ -bҶw~ahe>w~YzvQm9%m-oM1FՓK K^%2x~; V/ewIIKo52u~9Z~ɿ{_pj@ p@4X`<px@~  -ȭ87N3=cӵYilü ɛgl8+Vsq -Qǻ:Eio-d7S(f -o24Si1,e.}F7F|U]^Gem~!]x(eki~Yw<5̪gծf0@37߷Q颤'Q 5冏N9>VS^RѐMIIѐ%EG)FRLIEJ4)VRbI“b'A)^RDR$RȤH BJ*)f=) hKuǓ9f!U&:7geS`t244 %e3^pTʮda1Jv&R~?#˅ih.~Z7hȣh(C7hrAbN/i(PzzMe:j(ZQ!s mW4h _۩1gA;_\e?|!_*':DhO_f?;mA:`kL-naCxDnBRz&~ݙ 9BZIOfhhl1UT+Up~EC=S3OBC4oZ⦏)hf_ct%n}s98cX:6.Ldj)+Fն=FEwܳ:RLlq6v8_a_8wrՔZ|GY}D_S)CǨɿvE5h8`F`f`Q XA,؁< D ?AA -HoSW).wrL-YָȚYs#kYbaJfm?'Y4eӜg6b+|[wGRSQW(QSQW( vnSi&W++{ZQ -F)o(MQ -F)+[HJ~z6mot+J4Xq(@Oʴ5ǂށRW=7ETJmUZ e4ve))==)!$Qc_]iɾJ2W@if'/^o.P ~ -Pn!el~d歃y魖Fw߽1?R֩-n~ xejٻTOΩUq]*~>jU;?TůS -gwm Np&bܯ@@0#03( \@" r+2>k*yr9N?sSqq7 ř,d/~)m9qouqӼ0{mql/lTMePD //0~tAp7m엛˓ ̥׆휶+#%lQ"G_6˿,WF}kbGdv3Yna/W/2D{K_Ct?.@oI5_Lªp"9'YYB};MStg/ݓ\95wL5oީR:T;Mm{ F7מ}-WRc)tgu~h^^|5{˕'bbvm}!;uy]GYZ!=By[x8> eNDvD,,%Jd- 4@ t@00  -b ^   R@* +Yz31FWfjCkcLy0PJ,mKX(a]3|V\fr?-產R2;ԊRQEQs_4#Gmͣu, JdyO,FTVrߨ-Rc?;!D_B)+JA6"nFdݏ*"5; z!\Ȓ y#*Er'Brg Dg/)<ބĵJ8}7~ˊRO9{t=%h- -~jJtAGtL?4wX-֨ l6O6RB|{=-PJ (]P(2U~8=%KC)y^(e((MQ -nQ hZzX@V lv. HdAR|N}YM%gt󬾗e85qi3ä j!Jfm9| -Ӭg=͊3GXy]w`YlYI)Q)_>#Gq[bʻ72M~8SbʨS a*1)i/딫~zy88%A_]Ӿ23 N_!6݃pJO)?t))v!\\<?GЁ.:BoU9 V<iNILO@NSBRӗCvv 9e7WSJfnz-A3*hWŷCj*yGVkTO;Ro5籘ؒ2.ė^vG2.R -djXm=+g Sx)q5RLRK&X31 --gֲb#+0N91 (NI -')]o[?_rJ&S<<)76:Y-SV>([-{/DNnUjs6e=NiO%pʭ]~rN܆ -_هާ_.SO -ڒn}ٸFN)x0PzX00OnajN)j]z5&{6Va*SSrxJN-{T6zz8pSSpj@ p@4X`<px@~ *N=й)Spa #q3jRr )SYc]Mz8o!' /aSaSa2pArk8qٮz=9eZHe -S~f\}a̅&eNpύN8e,Ͽ'Or? OcN~O) u zߞSU9%QJ<)C> 9%3| 4wbY"x3$s5N):?P}FniAs1AS+ӭqbAg:ݙje}5Z2qVkɣ1#n[eq?þgc8pJ_PpJ?ᔺpJןCpJ!8ܢ@@0#03( \@" r+},OYb58m%N_1ȑiE4͚9aS&S8"Gz&1ĺY!p1Va~_s+UvmNJ)_ 0Ne{{Is)TSəS^Wl┯q+Nٯ>_V|٨HNy=SN%")g@Nʼi -N9+\?*ӎS~/IA3sJ~KJLKHYI>2i>w3+=!S22Ҳ~𘪩I+pwP(Ma_ mQwfJ_AhȞ?E+_qV7Z߼1aut3{Tֲ۩-*~lʑڕja՞W]6颤ޅ{O呩fujO:ކEդФhHђ#EO -C -G#)&R̤XH"%+)HI E@/)"))~RdRII!%r3s~Mfmwnj@fd@64n6.ʦ͖lm6|>QwlF6{j7{ 7eE?OK7롫];WCay(hi-[OCaM]'͑3 M#M?g67Ehɏo ;UWWCCN),i[i~rU/o.'44>4t_쪇p5]"#YR)3^!IrJLgV;ݝ!C0ePR+TݶSbn8  MޤlǠ{a2f8LBCЫ7F:z4 444T=  M@CM՞|hhbi^/% /U;_kTzUQ4d+"7&&EC)zRR8R I1b&BJ)ѤXI%F -O).RR?ܺ1>E)FVri FR!T|w*sjܪ8}˪D)A%D)0NNnoX߱Xw) -4OeZr\KśN'2B>g(L.y}3]N1)K -KIQk5S@s)yG2ު@nZM -M-):R0pH1b"L(RIK;)R\xH""'E&%@JRRI9?!^H5m+ 軓.kbH6˦ʀ62INcd۸w'';f]BW3=R^9 8uuĭ1V<ĆP9!HInկ=a4k߶gঽ۞.ȡ0y vk]Oc+#0YlFO[JÅ [rdz/3܉!Lrg[o3×BFCo˭{netUUFwS|J[ -(}4Z0P@6R}jinusb[&fy_:G٫)ϩ\ *a~*OG{6zs Ub}TW9~2zϩdt܍)iܯ@@0#03( \@" յt]%^߮㙎xnh9);<[Z*r]Am*BQ^WV}G0~}Q0~]^C_pݺ#̥2}ŮD◞1? g>x$h sF:u)[ },'WchtJO:Ұ!\/'1ALp -i"Z7˙IJBkGxˏZ*|+SN|.zUTv -1n35Utᩀ1QS Tѿ\im2&(mkv[s;AԴʓ9dd=AJn?D b'^e4-=` L , -D+6;px$2 H~V=m&m+Y9C9gL TјKΖìspJNa=wk9iWs['$]('I*i2»k~hmҕ/; 3W9Fj/Ⱥѯ.,;seN%h&:-4kOu:saNyBTZ܏R{ꏡukxZehrD;ilZh։ܴ )ZRtIaHH1b$D )QDb%%)<)vRHH%E$E"OLJ ))r~A@=L/mLU]C@Φq[$ژK`W ٵa X̑1}:ԩ.'O^'E_Vo_6hmfOJ1Uӗf)/@o3H7}ઇpZ'wiBZbb׉"zI>tRD13͗rЈ֩['tNC_.>A}S;Wڎu;bzuꟼ0^)&S h[Z[FM>HNzSӳYnuoFS֩mSϩ%eNKE*qNJߋ֩ Nuhi7)׶xiryoM&L{x(o8|c& -gyrae>ɦt%8=Z")Z/t]<-"qE0\r"o\ /!?*ӎgzj8ڒWzq IIYbG,yII,oF7+!QHkR1'O K:JU(MۏSڼ#7Μ]M3~qk&~ɘÔW{2Oh雺1*dX{zZTG)['/*{'*tjW%ig+j]oꢤҲRZ{Y8^Э?:N٭DnZM -M-):R0pH1b"L(RIK;)R\xH""'E&%@JRRI93Y=5d]@?dozF&iV mC~"` 8 -ɞL@l"5m|Ij |].k~Ad'zzuG29 Sk[ĵ2;}@2;/Ȁ)D?V - u̸˖-+>h *z;sAh[.ހ''M3!!"o.IpI4!C2ڽn~qw}vzkf7-~ -]{(}cD~N'6&;RzS蝖iĶآw -ϡwB4$zw.\(BG*O'jT!Nc~^B4) wcJJUh0`&`8 @@Tp5N:7.5ռl"{xS1oēx% dw%^T^x'/׮7vqqwATC_+ -7koV5&/_/mbwӝO?j beW6~liˋdBi:kH-Ӷ!\1r_OLDgFb& -ΤLQHBڝKm󟤦MGnCdI8bsnT4S;NQXa ?pe1'Ui -1g-oUY3$bNTܳ9%$LD3|1gbsjOaĜ6/?KIɼBZ,b{Ϫ Wbi5)4)RHѓ‘b H3)RH&JJ,)6RxR8Hq"!KHD)ARRHI%弆6Gdz2Y!k$d\23%sɆdleK9=dIplJ Pv-B)Mfi@7&׍9cJ2ha4hJ0 ȃ|kTf4_Qno%{U4ԫh;a401gIvreWD~1?]#fey^hj6&*4t"=d˪pvgCeeee%:$WJp;C>3-yp!3+戚Z>z|eg`̩sg^4]1һWCfb Encه:嘨*z+gcbpJ Blvb!5z%c^&Kv"VMROwMهk`QTJc#~ħ4-=` L , -D+6;px$2 H=4aVsaV0,&ckgel!#8Y[+O261ŸZXjVc3?)JPdP"Qʭ$9Rn ._mnlC9z*J٪s0N(gR:k\ҧ?w1$#H6 "h"''H6v?m~Wl")U"*%rfnFʐ)%%{Y4oA)K|3SL{=I~XДC1~%n.qiNg*?Cs'1(c+ïVvZE^(eifq"'Yj|J{J)R& fR[Th0`&`8 @@ԥdpj;YVWY&YC7kdMˬyLA!2ΑSěY{hb]ˬy:YrC +ͲNV_&{IQJSXQJS('W?"k#@GbfؔrڈsRƄk#ιB(]!>mOiRf_Y@)BJ9~͎2frςMR!\oRQRv7J].u}nl\Дn}[S'{(ع3-j+F==RZ1\涕WR^$cPjSx-'RPJI(yJ"JRFGՀ:  Dh`x`/@) [ǩK9qLr z9s;g9n>`U -K~N8[W))J9(%6RN*JOQJl J5ȍo=ww*nc~(iE)(JJ)V RpARʥSUove_i|(Sc/Rz*Rʮ4` BgZW=w̃3|)i33)-ٙ>33J |9k/;j~7Oy/S=)QЎJJ2?p)%TRՒ_jj}Jɫj+.tZC|=;;SÔP {.?rX,u>A߁R?Lꗠ]G\ܢ@@0#03( \@" r+sY8*YC kaMYki!Ji$)eu k/ٜkr9O1`fVf9*JfC' R ޟ$\5Ͼ<)|Uq72(NSf8)v՗u?ŮuʷS^DLykE&Wbʁ p_>h~rUS6H|^z1Dҳ {Ҝ^QHsJљJ'x>i1#) d:nO]+sk:fAr:ᔩ:?4.X̝:Z[Uwr;$a*t"̽? KkSᔎ&Ĕ)[Th0`&`8 @@TiY$Ýa ͬq55AE:i2˒~id]ͬzsޚOeמFٻN>P+N^/SN< L*G|WuE)01埕Ra:{/Hs6 -1V?RʯAm~Oe?R`Cx _phff(K%e93ȑ ^3-3JB;fco=?LC_{u;nJr>m>s$y7Mq/\aX2o2 enmjz|kTѮ7F#emxWڞ{k%Ss*WSTgɾ=.S@IwLͩCT]SoߘR2 Uh0`&`8 @@Tp~MNxM xf)8? o#_vNU -<ޞ;x8O11_'WL+s"Fe55 KN K#K6gn,$Y5}s 52KXIs+uW+jE) -o(JY|nRqA"ˣoC#yR]Y|#RR"7( -ޝYfWuKjA/'WeutA6@)&Shl᪇pOY -s\kTD[زn[O"tw" "KϽyӲT,"L5",͍,]Jd- 4@ t@00  -b ^   R@j_#ɪQVjX]!c9/vTs-M# am ,p KN:zgXqY0˟)^%\t$Qʯ\Y'Qʯ.HdMmHKe @g3_g|%S)N98+awυ8tlhSnD'hÓS~T#ӎ|Xb NYvCx% ^Ҳt1Kv;D/M qjyd:f^X,\p;nJ.2{WېYZ졘Y'04PƶLMgNZfU 2KA2*nGfY~,= !TޏR7SYƟBfi@f)| 2KJf 4@ t@00  -b ^   R@*8nV^=x$oMŸ M1'fƘ#9t'&v6i wU9^33#uxe(" m)"v("Hk `""ke[R˱ZkvOϻy<%L+QW8guyDBH>1c1Us1ѯS44{ኈX"Wo%BHȚd>(%[#R[)J>GRJ"V D:I)5Q;4D+r竔wrr;bm2l5J4.Hώˠj(_S+n z pVgIBPeffŬ;1!##!ݟ*$]M *˗)|ӭ*+2džyMEIK^W7{C); #_ƦZЋe=hz*-ցx[N}<CT2 J)A-@)A)EPJaXJ)}zJieKPJ!I)xD`h`F`f` X <>A@$_X=H)V=jXٯˠQT{Rʉ0<:z UJ`Vgle}`/dr&qj׭K}q5?}ѿ -wI)ߕVnǣΠ;3/% ?dS -;H)J ҿ JxU`l<&2xo 1[~*h?%3'QMV 9NS}bbB;Ht~w**(+@U(e*XRuTΫ'RC~ OTrsYi㰹w12[i"izkgC-+^U:VgWw&Kiʣس -_~zBc6<2ur"1&\Ji:3Υ* P  L , +;@"d"`U ̂^ݼJI8b0Y~|Tӕ1h?k_`㬳u/Ϡkd}󬿌Yq V;򾴲QM2GW,Z-c-|NCr)Ǿ&9_'W{}]VVs7Lg,r+1e Nq1 Nw[ޅS6V!ԗ\}K|̍^…%Fw'fywbbS $>-5+#58Lʽʮ|ceeHōe3c -ꙗ 6kg)t -} -|Ha,׶X;N>HL\̑n|m|WqJfN}S6pJ,*}kzw)u%)eQrPj`@@00 q -l8.<?  H3Umfѡ>5hԡs0g(s{Fa++te>qgUL81o? 9q s%;)eI0flZ ihovK?vI0rZnc -7ɄW.>_Q{+ctP|f"ĸ -'P bW t{Ft% -n_]'/A>w@IgڊʂW㻤FCnL'?x;6mIOI iZkּ256oZfpw9tfz)reYE\kd}C -XDlhvt?*KN2Zh7lt{{CG:"z6Kҏ͎z)Qπ"_}|wʶͻ+= |ru�~ һ!ƣ=̄r_ih[.\h7yQLMbR@tDܼ_Hgg9eʶ?_VBKug3seE -uKڒz}- } m-0Fh9ѶŒ۾%{$&nBK݇f퓇ihف2׎p%u-soT -<ej;BR3BKK'Z*5-= bA8 x| 0H) [XYSTraNr\Y Mct39,=44=8㜫8|?jb?j_{p}ߜW --ߕ͒`Rh`n^sHXs_"=3.*[%\,Y~.֨~_/^$Y.JQ -uFSEQtɣ,L3Tp2K _2>F7ɝ#R4wjF">o(bwFFfB /gbjpCwIV2N|(^WO?՚1nͳV>B; X y{[ fހS)մ-nrК{XGg$Nwᔁ3xD`h`F`f` X <>A@$_bV=j&Ym#"E=f qIo:ƚ+YuJTrXW5̳|;+f%K`9*'f>1KNq8E)9_rC)u9@h3*rDcDowJNǦI9E-$|&9mhtNLϭ?2:N8%tB`8][XwS6=ldOrRˋ:W 8}wP)f,SE:2*BZFzzvNizr!ݨjfJjPu>\T|YanHB?V[ uc y?0^au[,olm;@[Bxml{W?ADK敞J~K8?]JuPwߴ2XIe8꠪1OŤ, < -0@ 4@ t@0#03XN B A -vNվiG8MC8GsÜi'g,Գx[^q FWFOo|c8 68B \f$\m*\#"QQa9uvH˩CC!O3f׸RU/OnϢR_w a&d&/R+,7z nֿ/􄌌,f"$JĤ@bB$R{rUWnW*ܑ+=;[_VJ_V[_V6L0N L5+̋w[?;v~Dw䵇=#t,;?=t;yP>ۋ_S7x"F XU -q*4=u@,U%׿Jn҇V C/jzЋ^tB/z1ҋ^bXz+N/8E/zG/~zE %L/B/zYXR-lbBMMڐ./ -c mWBM!K&:^g7q!g&&ϱM|Ux6˯ -5!3F=|1FP!bEC )΂#T-ֻnG]Q7A߳.>\zso^Pv24TFy YAz7x:a?# 0^…;{3Ĭ// 1'#-&nģ,B/-IJOsnbkwyuxr60k\ך:#=VK*b7VW)':+v|Si J?ts[:Ė!N嘣(Js[P:UJ'< -0@ 4@ t@0#03XN B A -(ND]G4󬶀*> ZbcMKgY0gqOsLl!w]+%RΜ-'Vt=٨.z|-M?fH?#iaOH7KNrXfjl<_\S-~'8)S+Ny| N9c SZF 1?y_dG7S>Y.rL S|7+n>GtfzywFZ*ROFvvD5J8УZeMWyxDWɉxC}8Ctyj)y;T"n`ux[N*:UvNSrL$^M8fH; ]Mcppp7< -0@ 4@ t@0#03XN B A -?=XaU-֞kV)YQR|V ! Q %"xSLALJKJM\WYWh޷I^yWk&/y]Q^bOSSiMEO^c"ޒ2R4w5նo?e:ξ/tu? -ܢgc{ӃV*PJ}!T)) P  L , +;@"d"usGsz^OcJ>!~zDTOi˃wҘC'X8K+xfYY+q  o*E')R3{ORQJR{E;C%Uʯ"5YqD_)}IJN9)}DcќrOb1Dp1pw:Wbʽ5p5 1e ]1jL뒙6S[窗pV`LHOR/g&%I$Atoһ{@M;V}3Uv^tv«1Q14QؼxKB|l;}X6%oO{:+gt)U_*GgOfCx-Y9,U)'S$;G20*5-= bA8 x| 0H) FT,SOԥDSCMD7@ &b,`'b&hLO죄&:A1=3)MXW9ʓVk|~zX/y״6<Օ^g CsiO|D5v慗qX'V>;˙}&۵"Kq?ʠz$]Yy|0UQDަZEhqDC(^U,xD`h`F`f` X <>A@$ˍ.,PjO,/'KX-i4ssbԍ&Oa \c塉*{[2Ɩ<b%tI5{y -s뗬z /">=HHMug&& -J}nOLp ~熪ʪ@4K)^S]]*e]<7.iM"B{NٹZo(j3[}]}PJ#2r#)(e9(C(eRJJJ#@@00 q -l8.<?  Hi̓WVƈh7蚈b!㬩;VARJ7.ꈧ7Mel55rX[yQ4pR*p7|'b,֞*/-̨JBZXɌ/dRw*%(e'ʅRv۠@Rv㋪P۠G](3(dA)KW 8%Š( -Y^wVZ*RJNG!ii |RZvH)s R)MU};L!^ӹ3^[zx}<o7|5 1Z-Zc{KXm^{c\[_g]nǥ)ec(a}ɐ82,ݖ8 -,RF|#@@00 q -l8.<?  \n)Xnd9n~y[(kbzS9kd-44х=Xg#jb=u,~й|H.fJTRKҿYR6(%ORJ 2J[r;Y bR. -?HdVRJDo7dzNqgr2W(eV -+RPB)cEsMg֖(K>/].஡胵3|~qډ~wjRϝ%xRyX%U<ȲΊʾ=fiοU5^&Wt񚦧3^]/iISZSɗט[-ۭ5PJ{k+"4:P:~J){J̼&4Ođwj(zJnR 9< -0@ 4@ t@0#03XN B A -"WOT,SB#Нν1%Q\M,94Дr, )_/B>6͔rgvoԵ3m*F]KQR\/}\ȧmҏ}k$/ŔrͺJY~ba܆T>?csEޅS -3) L\^4%W_AxBmt֥ĝXv\Όn(tuvYf"E۳W[׻X^3=B$ """"FJ`$HRDDDiDĈHIDɰ{{?{ߣ\WNWy^ҽ{=q콎~m^K++yvVU4ͩ6)۽tRvxM%P1*2?#)gs~OLtme~Wk/LSbz9DOzqu~Yz࠲ӻxa/9nP得I-Gnz2OuO/K[*K/m1ϴo (_߇_&2NdZ -<>_ހ_?_F{LBylF[_Z7ᗒ/^h X`&` X@؀8 HI KT}5©5svUqt9.qb p6< T/8H?OFx梅eN\|91܆~X-V[˭2~-\rG% -H0]=3`I0`2YZm>"v?/ehDknM6udYuYMҿ]o)DVJG 2zT{T~L^Wxfv'}/VV;UU$N?'EPAEr&.Q(:@ A86`N7 AHR -c㌦verV?:ckg5,2)Ja>8u7YF`E癯8%LrA,Z)8%[rVr%2Nɖq/9g䩔!k2s#dZ6.)ҽ^')%LF)7JJNRJRnQJͥ窔Rdmj'uPJMOoRn&P(}S?(?p*qI拿!\0{`0CSyGrx<)6qO7.(:,TTNb:e2[?QF[7*4۵snUZ̛GV̨ -vsQU\=JE`zה\JsJ{N1똊/)xNn*O٫nW%*U[U^U|˪?W%O_UPIѐ%EG -M -K)FRLI '% )QXIHb'I -O))")>RHI$%dR&Ԁ_=Ќ$h'jD[ EcW4`.LPw5;;` K wi7A(~_y? $ %Hs2'% e4:\M_P ^H5.i>^ m4SPh(窡ٻ6%BC:m]M69ЉlsZoގi3Ng< ! 7+ -#5%dt=AGz7>->- T: P}[3ɦmhjHm.]8Ou.CX)w)ru%((ssϚp'Vwqx1Utx Nw]bjlzLuJ(=lhynU)]UG|J9k:"']yg+FRddŜ^Jɩb~>2emk r{olϴW,B)g3P| -O#;qnS -02T\| L~ S\ -P@ 4@ t,00p, -XlnA$$ ȥ3yd5f60a~ZMc!cbD)4Xn0AD{dZl}yJJJP*QBRhj(΋R6~;o@)}=5];$(%"5KR߳v)z~oRlsUwDWJMu6(RRC)Rz )eC)|S?uA)(屏R.M\.XrN)B7%x.y)޴t!x^dXqRf* Zm -R_E4VجP/|fjb~BWR٬, u ayn@Trbo"rYzR hB&7,֢Y-Az=R[{&ab~4KE'U&4K^T%,Kf\5)RHIaIѓb H3)Db!%+))6R8IIq!E E$G)$L -؅\UPOt%hk]`CX0 'GH4Q?W3~gCp/=s BN@WO|YRH:*j^*%n4tl2'BQok4Hc6˒l>47 X&ټs|͒x4tdc_j@b~4KG>Sk#@%~ݘH]H6 YkK6|18w0()T GOux2bbЕNC,Mf-^M6#Oxj`Wc\y:%=e(Lk?4qyv.2|I7qȨ9:nwV[o퓓 -{ -hzzJb'G\4KEc+{'kI-suP   D V'xD~ $r)팪ju0p); [{hCcfL󴹔$1,502blq2"ngp%wM# $~t_p+C:C7Џ8or[4ZI2+%4{헢e!LJLFC?=/0__ldІJ:Qh?@C?1hHG>4}KmoGInY.\:Rt#E8;Uf{TʠG_]h))jR4hIёB’'@)fRI BJ)VR8RlIq“&C@H?)RII"%w.A3m -N0LU 4Kj%`K |K!S ,%.LpV5)!2!1KY"Jmi,~5C6 9봤oh&IC% }SFC7kwXh.WЏ. тH]:eOau]4yO"u_E5i(>J4QLiFs)i<4T9aN9Tc7yR;w銏ts<[H`AkɽԸbͷw:-w#sYk\뙭L{ -gϛ*G(UʊZ?CV\/F5z"1?)ޑp*@5-@ L A(`;p@>@2 2ĪYU0fV&zag}!kdŬi1 m*X(\tT/\`zJYG,e7찖2֏)[fX+f sVbJFJiJ*Rnr26#JTRIAQ>*!JR.S?ڬ,9h.RwȧH6}*@);k< 886tLw;\q. VZ#5C8qnpLG?pP| :^]]|<UB&u|:1ON{f$_+hC6 ]+@wux-撗WSD),OgDʴWpݓ=3JJw ,doJlx,gSR=q*@5-@ L A(`;p@>@2XQ -NircѺSW\uR1ed(rSv9@oLC$[?HY.ܠp\hzw;!A#%v x\ؔXO:t=rPwU̽PgyMGqv6˥+}";]HsgMB)[Z#J\ѥ4.L{Pr(kJmR?SPJ(OC){IJ%@@h=0#03p x @"Hɀ\J/a>V=hm1bEb!1v7('J&Jf,7 -X{3dA=x!x)( zb -'NR)p2Jd\rcpSV6J$IRdڠ_HJyORʐL I)1ҽ^̅R[2J&v*+Vtr;H)kT>r6,@%F6$R߻rGe~/uCp)EC]{BjЛH#SL`z##8nyӂuJFJhN}^R{T% -o5|(wG@)G> ZcCך-bKxEDWK΋QU.kͱ P|mLi(nJ~= -OّBpS -_H)k(c(%KZKT -hЀz`F`f"D+ ؁ <@"?D'XWȪUF{|p1VKBzֺpEYa{ +vՔrwn)D(rI)RJ(R>  $ Uyҽ~$wBߏd̶?RR- ɜ.YK(e.(ȧ~˲7ٳkCPJq{DW蔂#fyË.Gj%=Ixwx]*{> 4OM"=בRxH}oR޼tb.SSi[mSW+3 -~p 8z_tƧ:Ji|qEZEP   D V'xD~ $=ߌFu anKV#10~I)uca ¸O #`Y7|CRJͫfߗV5(%)@j|eQ[U)vQ-%ӭ7*E/)I)I)rKdU^)7*0Yl3A)kR8(%)ť'O@YD@)ȧh|/JHFJ.X.RBWFZZ0>nǛqib!BJOqSJO:WҰߩjxEN^3Wҍ9m<|o~^!cqkMSB%|)e-Ȩ2Pumpz))f"4>=vs~|/RvGJ9q)#)UjZ4``Q -8`v&dxJ`z#SWĈұgu;3R8*P߹Y#R_gȧ~^uO0'R~U!\8xyv|GHOMsdyTGJ0pE1V׾Y4>PJٟyMWivn yg'"%]Oܿ]c˱s-EPʉ{X"& -]\[m(eJɛDJl! JJ+RJU;2s JxYR -.Q(:@ A86`N7 AHnbt)j`rV]jX$;ҕ,[gCklgMX,QJ)e,Wf{lbIS -EX&X6>U*R0|9C<>/OVKbx{#dzqS~LErʘQ\&9eLrʨS.;/^^o!۩D8 -jsKsL"EҮȧ|߄ΧN^pJؚpS)`JȣBx%R`+cSᔼt:5jqU2|Gj]>nٲ{";]zĔ55g1X³N)nqY[rjʹ({k|pQĔBII8T2_5'Eb}n">i|P   D V'xD~ $qVURy԰1FW2S'hCklcL3&Okg<6+Yg3Ɠ -y8ZX $'9qJ$0dz]=o㳪}[L?uߖbZq 9(>2Nah}b=?r5N'VW;rk;8}S8%P 7'D}k5ІӼupr6]zF:T%_L5E>~g#yXZ^m%2K#Qy\[mOekI>2,'a"A@N)~N9y9]䔦OKT -hЀz`F`f"D+ ؁ <@"?Dj7VP ь3F73 ;q8˘XsjZbZrO3,esY1ʍWj_V)XuJ䔀\NQrWXS:VˏЦJ7pp)IN N ?/9sZv9Y䔘W'}S_)N8zT j(J|49\.m'MHKS  q]BN_S*kz.j >O\>.<]҂r_D85,_cbCs`2ާ&2mz"Mo=iG|Ni/SO|pP; V Zr`T!}JSHs_r -.Q(:@ A86`N7 AHRfUKNw`4 ѵ2t63#0)bPRLRakSJz'^2ҽ)Tx%5-ݑH{Yfa 2촴yءG^}J- >'pI~jP'oSry~%ie_.REJ9H)D3YPJE@qwU@)|꧜dwT%RoH6sk…S -zi -J:R27c#'fSICޑRSC5U䢖NgxMcE6/ǥkFx0RnahNk;t&b>>b9UW3oUg{^R:zǡϠ#w - ~1C(J<B)%A)%UjZ4``Q -8`v.Z)?7*$ȧpJimJx8,: t>59 =@+ӼToCp(zG^#-ŧxRܢڔvonɗ)8Txb+)--җx)=\oZ ZcL},"Kx)2\%e? -oes,|J -ObHN1R&2ZZt)9rwJ>Dh X`&` X@؀8 HI Q*X")`Kn!5dL$|HΒnٌ,κXOKSX?nQrJOw))Q2N1+·S/ X2-w>Eҽ&JNI)1*LsR>ѹ*em^$|^)qRW|YRo$l;ߟ PW;\1篞5)%N}T. - ǝvxx%7.#e<\gW;\Lj= -y -MkiKW^ڣS_;_anl -ȱ-?o tC -K72'^}[U7V&XP؛O+#%%P0G!Q+N+ ;7%VMޅ.4\ -P@ 4@ t,00p, -XlnA$$ VV5p|z0Zs&Z;h,F3pz4m#Tz8kqh{qsF=0-qj_\,o Y~$E<('bb -6,"-ǩ<)$d"KIYQD#2mPOyd/~u/? '4}b3G>Ed+H%7Dy BO<9۝x\ixbS2t!-uġJ-RC㢲Fx1^@ApgԷvr?sN]cY4-2*3[z#rvV~k-Ed9SF&O))%JF)R&CF< -u3p7>hI仠%l&YA6b;)(%c -Yl{Ja1 ['BeOH˟OY~+'^!\崡T^y1cyǺ^/񩮠̔2Rm)mMsqQٽ^3͇R^-/eX]}ך[E-Ow=RojtYO Z.6w,^}#R^DTM^XC8\ʞRZ +DG&NC){.P   D V'xD~ $|1.eӌՎ2)dy]q5.3) teƚRN22%FdłK|W5I)aүYKer+%\*΋R 7:L/t{%21){%21Le$)N-w,;eNQyVϳ Sv TtLb|ヒ6ěh^S.Y.\ }N Ƨgx45͑xw0Hs?A%mPa^\mfvߠ\R2Hd WV[qi}؎2C6hlp9%5AO ԑ5+G6P   D V'xD~ $ǩt2TSu0t2v0sXӉ/9+#mPcf&VKg6Kd<]Ȉof6,#RT-eR$N, ٠͗jn -$7ˎ%lmFVIJRΜ w.,_R^I|Fe; (Ky,{}SAY{TFdPX.\ds~Y\uff8<#>6%͑&y|~os$Y*qb2UceRM,e3{,%y,N_7_ale/[̽l [@d)Ոr䖨LkG+GdBdGdi~쌒?Ȓ[_s1D?߭KȒ݄Ȓ"KUĬFDjq*@5-@ L A(`;p@>@2 2*pN3i9GslA<0m,1M,E$sbMrFΙC͊Tp('.NoYe0+>~.3"Ȓ0pv×(%?EvKtI~_~"E$|C/?9/ RdxImd?~@Y ~5̈,o PJ'rZ#k…-yTC#HM X76=]?r;xPY]w&2Ɨ|Rv-Q|p~N0 Tl+س[)DGMdZ'ͼ6/='O+3%_\Rg'MP)t__eZ]׫P   D V'xD~ $VU /њhm>K8v:Z_c66s_r򟿄_y\NZ䠕Z:Esմl/bܓh8a3_=he%GEa/ 9˝(yh)2~2[SRU5]ވX. !tfҽ#.6.1HRStwjJjpS6+_K]!5 -i5#]Y.J)h^k*g1ﳄ?2UU@ZίJ2e pJ/9p'rN;Cڟ vwKFNS?KT -hЀz`F`f"D+ ؁ <@"?Dj -Y(f n{/1"%U%',3|'`=],Zb|`{%|7@!d֠lQ;7}pʵACP X5=[܈)Lܵ(ey]?}w|S?TL Q%URiMUPIѐ%EG -M -K)FRLI '% )QXIHb'I -O))")>RHI$%dRV '"rbѧdK6;mB#g E\ -P@ 4@ t,00p, -XlnA$$ V0*jc=&՞dt] İ%11|TʘsHTKVt1\[dl q62|%.^9r&[ǾlhE)dN"m]z:hΒSfGW}.Ry'j%7=sK>v\9٣xh_.mnAy HI3$,Ԯypst4!Oκ 9D;͑u iQR׿>ʪǼsҬtC5EgyDEvbK72OtlSe~nfVkry`%v Nbi{*2xzfo+<־/Y= ԑY+< S}5ASJS^|ThS -)ESp*@5-@ L A(`;p@>@r'),3YM=mau'e cai17v8ֲ\kgXw9`!Ff}f_$d䔫$dyqJEowtFJZTRJ-_ /#\9/ҿ};s.s՘RM -GROkO(9>{{ 5µK={`'"8<,AĔԔT~fڤt973g(ԍy -Muv= -z<[u} -\fě+Ly][y['o8uHa9nFۙVpd֨Ђ^ryJkJ=+ 962wEo8edR,2>H5kz86htS_>,wIeJ22+}@//S&YVrYDoma~Y:_nl[.~Ы^)8OH1(:RDv3xۛ~(YmmΨrpQMdԎ~x)-YZS-ڠ3hZ b){$2ekrٻn@2XbTjQ731F{э1t>f'?*iIdg'0A9s 1IFlb|}pY.bQsR."F΋RŝT3Ld]*)VU(fҠJHaGۮO&nv6i?6wB0vPbcJL(cLCBvJ!cB(!~w #]g,zbir*JyYΪ(E)/+]Y<"{dž9KR};z< R{R;!%R.1Ftg{~D"/19ެ|ͣW@~v޵ZzNR.dWsa?[xws5M]PՖ·'wtAU]PYX<)=zO5uA%MPd!RM]PB嫒G{s+4On~')]د(,03+;p8@"p   " dB3ԛ&]I_j2LW.0YkM3&crRdr֚&wyr&1Lx7Ie_D+JI:RtA'4ˮܝiS_YYPlۢNjiVʔTڠ_/Z.ޢpJNqO\8\`j ڠ˽XOKhVqk\G箳%9O9YӲov0I & |?;6hm^:_]3zvmPAA_}ETmPkZPڠcf;1ڠSe'W$<{WbzC 7<6hrm\l _6ڠce18?6hi(6_1WٜhX؀8@ 8|AH@2=ʌK3bO#ӟ mevY]ev9* js]*\~M)W XjO7To %_[<7kCg{RsvMz}2ZۛKsKѫk-m/ᗒ/}C%6~bLBeu~bζ?29~_w/+m}x1aV -LT_>VY-=0#0  $'x @" 2@&t0ks/ .<4r -'\i0.f!ƶhlIW]f.4_fk 6 v zS|ueGigU.VUY/_>Jm:zŪwҤަDK1;hܬ鍰`o,n_=QrVvnr0E&%CJ9mHyzls3XqKϦ fT.=vQ=8Ș;XzZ0SˌOѷ 4j:I8^]$70ΙS1\ۗcܵb<#m5jnM&xrCU0ψ546ݡw\ @CoXO@Cm[Nh;w -6? uJfJāנCCP04: MCCwBCso2LVM24t 4444ޮhZCRh)tz -LaR(8xD -'GP(xEB)BLȠȤ2x7ٚݲlŲelF${% _CGewn _ aFʡѰ.t~~u4r \_C/\=^֛iKWzm}2h_w3Jy1`GCՌ2U9ႸQ9ICSyeE9'PT>2(sZ2eNʜƏz!q9=%5(sji"۫9 -BKS(f - FapPQS$P$R8)8 -7GS(H(dEE&ŕPtKl[X;UÆp<-Ye[lJw4I,ӬްFbS+p`B -nK$J -5)&j߹^5𽫢94>:2g.I2w[wtr{kpo5'4u wSt[>Yf)Cf ʜU%?}'/-/f 7?'%$e,!Z BC?b=⢆O۪' ?h;]}d'44]`144v -*p]74񅿃v0N m~ - 5@C=+4gı4 ud)PZ -@a0SX(6 -;""""IQ)<> -"@(DD!SD(2(2)IԦ#ړ!] #ts}%ݺMQ!CCʹS(;\ޓI r\8XzeqHKe.R4CQUBQU+!1u47o4:6g*kj~"V/T5,t[VB‚:G娇>̦Wa>vPcɏp4~);-%N4P0 Eg3ģ5ԼP[О&:kٷg0b\jh[P)m:n睂WQ  Mv1 s}f˴*4Tz :[hpQFl뀆NoA52 Vl@5T_=R( -#BaQ)q N -MQ @!R($ -"BAIqx) sJu k macY\-Y&C֝a[{{QEPD$$gm% 4$ -`$Y#vCO\j =iޢhHDEC hhseOqz5]_U4dSPW T4T"}/솆f?A5uQC3}# ѧ~sM#\M$z)ɏpv_47({d!'/M f_ӸyoS^ߡihK K]ݛkCOlf {71O2͌6[Ө09]7?y]#9!=\w>EqʜDHU$/nI9MS,ћ)r?WU)~9Mz<;Z}KCR -Rutt?2p,m91tx]\C32.rG5ownRF7 \_F? |ɮaORST?#v/g7ft(~j @ , -l ăn> $ 9)UiF]lK[u-a3s2봶lNE3.g.7[]iW8 sR5ߜI*~SI*~sErO\/]29LcRJ֫E*~U~"|N~lV-<E/~LDY/}P)K~<@n~7%[Lx#޼'e׶Q)4=ߴF<]hF}¥R|Kchz1<Ș62|cbMW vۄ6j\18Y6o7ڨFUV:R7|COh= N>+l+Dq|&VަʇhmG5})k5,BG0P) -+N᠈HHpRpn -P) -"D!Q L|w>f_Kd [Hֱ5n^#96\m܍_Q-pHĦ5I]͹Oe ϭ* -(Us+S \`]iW}'a]*WO =h(^EC?]՜ YtQ#߇xQCН{Xg(s@x̝(saQ֯ɽG; LrgmNЛ* -i|075KǺ S{5tr{ *s`t]@CP[ӆˌdctA9hh)hh%LbmX44NCCj~hO -P-4Թ  T4D`): -=HaPX)lv -EE_vf|C/fѭL`Lps%aqt ev3h&ҺY&ct@9DGhX؀8@ 8|AH@2=JYSgfKYnI?f21L4Jkd7ێ3&G/jCfgk0gMn`Yn̡zyQZ)窿(eRQ5E)kT̊"?Nθ47-7zxƽKWrKSUV/).)Ny]eRJ"s\{<ѭ5ˆ34ܰX. Mh֔S|2/Fph]GqN FYDAHNKY~oPtwvϧsin;xnoߦ|8;^M+2ynku[VyÎnx7o~duMmc3p@iB.8Hݫ* ;sKw ESᔙ%8R<R|NS5Zz`F``6`AHN7ADAdLpeIsĎ&ݐId(1kL^ed|eM1@)vS5fn7yMq?j -ԚGL€I3LRiYܯ8f)QqʃSnV<"NI%Ne3T)P_;EXeNV;7R\wDS_SKJ~K5(%J)M؄f( !Tb}l{V:W7SEa)4ћg xST1;'?+?OMF)h6v׳mb#ldt=Gu@+[ki݄6lUsh~1|f]ڠf&aϭw6W 1\dMhfATՋ6,68vmChμ6hm'hwF :2ϫ,03+;p8@"p   " dzBN3bsv8dn,5}.xn|sq\ -Sƒ!g Ӝυ8:iYZA__Wo(~$|cEڠ>=4<v.ܵ"/Vz"`_N/,S_ -,w!~} -(Yνaӟ>߹G%KR|r0 xY,o(NKKrӒk2hWHŒ=F?=k+'V+syjszdZo+ʴd0!oZBǑՉs~gaI.W~>](YjP&EU02PT>%(YڶdWJ<@ t@ @%+]Żs+I?emOr~nOӕjOOԄ %:5~w~(o8~S?)R YXCPG eQB=n9JhN7ڊSL]kG +6C qLSKcy8_a|=!,OTcQXs]PFZHZH#tU?;D5]F'{ 1>ҽ`)>Jz }5=drckdʄr\%^4Aa(1Tj a1 F F$F#ÂbX1l Åc"Cb01R0b;$rn'Q.J.I=%iJ$mtŒ>cȕ˒_$wĴ{,YvQHaNJ)I̖Y-ywv$HٱC-"E7^~OR{?冀FV9'FN^k5Qk&O?2)F͢UæPb!(vnIëSHRoܽ{u{ƿ^z^L{{{Z{==*OU'߫Nױ:{ͺG5JjY2n%oc_YtP6K+G}5wMTDׄ@]~6Q~&ύJr|MG'S BC ΄3SSN;Hb'Bn:(V[ Һ|FGhc;cA) (e43 mmchW͏B#1Z?/JɺagvP%ĺRnJYF*zQ)Y}=E{ r!^_ -e$+EB)PJR~6)6JER^ -J,q(%{Lه@)SPʑy)wz@)?r6< WBXfg'rxh@@x('-=-տI)OQ|FL}tshΩi:7 '9 @>tN 9 :2u+oBo34=x:LȊ39i0YnΩMZބ)蜺##7sZ9E̓eB ':7sol!K#9: }^4Aa(1Tj a1 F F$F#ÂbX1l Åc"Cb01R0b<众(s~Iմ{^fУk=aqU2xAC^PDzakbmr^״o -= ˼R3RQG֐BCChȲ-cga;'rGVz`dovRE! -h:ru::NBT:(4` 0@`b V8p< "$ d 8w)qd0d=jU籚FV[Բ8}Aa9Xgbt(ϲV(k+sq k5/eNVlc+['RLe|%`e`B0O 'sn/=)on_~'[_/;d%5c[Z'Ӟ~*9+3uK#@~yF7~ -~Eo_y;  :IJseS`^֥LuS_ l>jao ަo>yfXCCSUG= }TΛ9D1裆^>E:CUQBrG|m^iPX/!VC5"QG5CUx9y -/Ġ0* 5C`0 # ## -Ìa`16 1 Í!ax1|)x2ۃ}ԄW5Qz4 Lyt%>k^SQ9W<*-X=< IXugy|ϖ#?M!u ])k.kHBCWnN3P{ꦭ4TF ^л)kHBCWʐ5 eg6h(ڨhX2u tZPI>Rڅeem?.ᩉ']LIi~%pĄMZ~ڨ#Ok^ -M4Εcn2SQBs(198+ Mj3T ՜45,fFQˣDLN *Eu–112u:}tUyg&r^ gH0]{e25*EC`h1 Àa0aDbDa1b0,,Æ0\2}D)߇{=+kHBCߓ54/kHBCۖK_my db]CāZ -ՠqmrKy=>@)h蹱 i(>)xNHO -$$>i]"oOJ$WjZ6@5za]Cofd{h e)&TA5^Dh(LdAC6C~hy$1"hiP-TCσơ{! 5 C5Q -ZaX!TC P u<P_ =$k/Ġ0* 5C`0 # ## -Ìa`16 1 Í!ax1|)x:QzTC٣hILߩZCz[BCtG -hhzy}r堡'F)/k)4e[);Mٗ7< !B(AH4;LIIvp AħoP_lQ,p4TZx 4Qվnjhf @CД,Vl~h^ό @C+Og1UO*AC+GACς߅} -RP+Дjh44MY䝠?V+4e~,pY\8gqY,. e,pY\8gqY,. e,pY\8gqY,. e,pY\8gqY,. e,p]C^C z+¹ɭBÌ{t}P16yL:PdY񰅒ũ<\'hyJ8$g$in*;. !!RХ!4_TC_WC[ihLѲ7P|K>fZ5I={i 9~Д@Codz3yfH?  -ı7< lsT_\h2vӕnH?YCQ=3vrIrʹ:N5/Z=i;9:ʍֵ-sa-ؚg63GfU HRd-MAZBW -F[G3enַӆZKpAWӖQ-O[iۼAs͌h{N+|vَJVJ3DVʵ!-+d\B);c;U/toR{)=)+67XPdnܲ-v^?qQf;T6JIR|qr_@E?#@e߅nTXHR_(KLoxN)Y|LKR&! V: "gS32*&nRJ;(e9_^ XrNR8J#'Z]i:;8mvdD:?M ӯǂfJ(%]18-UO؃wly2Jcp+M{'Gn{\ J(e4(el R6#+.(@ 5=` 3X6p 7 ^$)9MMfZdy֎L+gݴ60 JE4Җ2mf㴭v4횧|F|FK%̖KpRRJW\V -B)۲0cUJ"wlkSr N˧)NDvJgoSkS.~XH es we( wLi|MYW'Qv㥼Nox):y>MT&I W=Ob7961MLNrS<ǩi}Ӗ7sxEN?|Fm(_q9lk LwD0sxn[ CSFL?|PXOSS*ԷSZe%(4` 0@`b V8p< "$ d 87+N45O+hn5AZ[O3.\RGiS)Qf;ikNia9k@-1Z/f/(֝ -ᔫ2e{Z{r)檿[]pLіntͲS*_S;QH?NqE\uJ9(*)p]_h:?n˯y)C>…$g λ\~;f'D{(IN>)5G{#&]&:4Aw&R{}p2<MbPJ C`0tz Äaƈ`V paƐ0>d CY-Q2ߣ$M0݆xP283(Y$XvH,qh'+$~P HRfh -hef\4I2AڰDi"6=ۥjgmJ7N|-힤ϖ6Jy@ JaC(gRz.ٶ6wξRn|a7,\%+ɰږ&8Y)VP -tKy1+hlʵeϡ[ -4o(K -l^NOiCpJ -D!)g`cĄOo>rb#G+oZWJчvrpyS=ũ*KNM(eRHՆѦՏ# ?i$VVgz4:&xru|n[APàG WC+tK.ı&薊?RC(^PTD%Ԁ 0& @ `X -.@܀x 7\1ZYFu5f S*}cf[*>7}PfGhkcb Ÿ&ij1qRgUY)f]u}RֿzP׷-Jyž>gTVh)|};t_NYVFj[FuGxSnFu8o e(SэSA)[6< !&(1џ]v1#iy ўqbByq)M+Ov;wNV>⤊G9e jhDЩi/pLϣѺb~mIm(}#Xrl=`l2OOGtZ5B(SvF)C(S2q(S XS2cR>Hv -\" PPj@hz Df ,`l n@HR.aFZ9DFi)fU Si} cMi˔ltJ;im+`4Ǹ9-50[{/̢p+^2v%-fsoݪLPhmTnY)o')(Sln\\LoRm8X(SPG+=J)#nT R"%rwp`ŰJ $$8|=wSIT{3>ɞLJy;o(:X{DQF Y9[CP̈́cLG 3\k{(?n3܏3M}Ù'ɨ -.in!,#'l0K\Pئֲc$Wzw'k{ qb]v) ?BzkO2x$Pa14Z C0`1LQf aŰa808 !`n ËHHa#;U zE,JڵLGWS_"w ` -pfmeb'Aɶ,9F%n㚒fI)y|0Zy-!4d5t!"l2S񟣡P@VQC|+t!4t\<;eG{~iM_v(i*7)h&KQ~% i(1|e&x{F4@C~ўNK26gۈwkl 44yw jT JJ} 4frZDͰp44F|43rPPܗۣr^ _ 54T * u- `h8p4T M<+hhiQ^MbPJ C`0tz Äaƈ`V paƐ0>d \/9:<.ISѴ{Σ;.%\1xǁK=[ﱮJDZ,qk@$aZNHr%Si~SBC)k mЏ_yVV핡oKCx%{KCxȵ-K* |P6xάQ;C/~],A9u C~S{CL׆p;ӹT BJO]ӓ%.=>O䡡Ǐ(ʚsߴNcS.ryNM]?-☖uN҇jC53&#d9"HXɺtd4tX%a%:} *pLtXa5L\" PPj@hz Df ,`l n@HRs,jAZH[hM'rF_zn)^x-8<0mm㴣FhW!-VJZz77lm -cߦNڶtX oY|r]Ju7֕NAhZVFDo˲Wz F  M̓nwwR: ֚sCpJ\ #D1- :@=\ d$x.1DKj<=N,?*pjsSr_n\PzwMw#'}ŏEwZJlMݶ@)@(%g+V'xP@)CcVP.ūI$ -P*@ h-:@#`"(  px@D H@ -C#4D+hU.a4mvf2&ZHiT^2H˴5Ҏ6]+4_|nuo96f!R=JmޟE-a+ԗ&R{핫 wXM\B)_EO=]]J?lqϏOA)nT~襮[}>L]z3!\U3ụTg|F"+]O& !IH<\RDvz"tK+--M74ʮ*SKx(٧ 0n-%/ .ƶLS+m/ Qم-g1ՏC4tKyu- WBB:{[[ʁni7zOtbN}tK%3GI g8"wKx$Pa14Z C0`1LQf aŰa808 !`n ËHH8wDvx*r@RuxY͂G;a -<5I1JƉ'[XzlךuJ -wJ0=RO]_\|)+/P̶tKgf9ϔC{fd}3DtC -]-6)QCK<2-Pq?*)qՍJԕY^wG%qzx,Hhr2`߾Z$F<]]Q% ̇ -3:۲i-l=KXGY"l#'*/Up-c -#p*L0O1E{t c,Eb ᗛC/ꗙ~*T봧F]qꊯ̯/eqA:*QRhnQ_)ӷ7< ?;;>\i&ޞ$&$91><uNYsD{ÓuYszNASyP:LꜼʣ}d/e^jm3PLdΌx$5 uNm/9'~ꜞwYmHGS897sV;EGJ#N5squpNw mꜩ/ɫ'I -CPch0 Ca0b0"101 Êap`p. C1ÇqnݍPGU}Ь~6tytM^$SVӄHq$Y< Kv_lzif-J9S[>5^+밬YCò"Ch_eP񷹡˜='P49Xqʜk)hhihQ߾&(sJ7< 9OQ S>#ĥSB=?џy954^lE UσZ2 hhe -44Q;Q -44>i'3rb4 :^"4Ե *\ M&^Pϓ.PjY0Z /&ݹEϐR hPq7k{4pT^4Aa(1Tj a1 F F$F#ÂbX1l Åc"Cb01R0b$o'QH֝"f`vTbzt~U2t4I#o$KĎܶ|ɱZsJ$ܲ"n|Z͗VCVߪ~sF}~}9)Om|¾%SЩGfnKU T+Ր_T[,ha|4T^OR0m]5R@5t!\8 %<.ӓv>wE>#`Ow @ssU`kb-cvONjSv?ɩzJqN[4LWDJW9*c^ٴZho~~U1mە5uq0XZM -e uy{"3URWV&#dD%Ԁ 0& @ `X -.@܀x x) 9P rQ3ꂯim/t01F_c>cүRjKk!G7505cyFU_?R'Fdk|UVʈ!R-_5\BQ+Ja])/Hֿ>b #;gߩ Y1Rzes3J)@E .R~RO%&Dyo=룒iQlxL)~):3~1 '%+?ĄsSݏQ9eغRRI)k©V7858m3r8Z7woy_m~cUx9񧈨]蘕<(Vd-o)P&P%\5_]x#66@)k/&SU'@) RI -P@ 0H -01`+`p|@2&)jcy .(.d4 MкaZA1xLD M m)a9ںznSɹM -4?B XȸC0fRP V)ܔ-Rn/6*3䎬m2)7{\hB)EםM%4˔-S+m.Q]֏I S*L90%R_?(./彼G]ustK 9%1s"$8LJn)!>͞ -BjDo.S)3ՉeJ߼,=_┃-jhuIЩ%4OqDinةo86|8c6Ud#{)w2O9:&i)) Z[ׂ{))PLN+]z_^nt8RtrD%Ԁ 0& @ `X -.@܀x 7>0T|w5vfz]3qS.c*:W&Jΰu2v=Iahqv0R.Sv?3\0Snb˔pRO}1r\qׇ90jw.#TcW&}v:{LZ95}Z{˫?˯>kz)w?Qދί{$_LO "%= `IO9+}1<&Xvzfʼn2MәS'ɨ.<<92.ंY>}>Fr3k7ՔFT1鮾Rt?=& Cchx M 14A<& Cchx M 14A<& Cchx M 14A<& Cchx M 14A<&>!=T*}:2r醇p59ғ@O]  'm~ $hhd=.V=•Lo4Tctm׎&H!hhf842Y ?~h/3&pTeh5P~ *h K*".B,x>=DJSev|-›=drYy]^4Aa(1Tj a1 F F$F#ÂbX1l Åc"Cb01R0bp{VM

oCumC9}!)&vgj2$W<3Ql~Ae?=KOn'ݿ_O_^9 -~i{x?-Խw8~k0 ]wENׁ_ˆ3tb KŽ K~K]2K>K^`p!Nv_qV~~% -P*@ h-:@#`"(  px@D H@ -RɒY$N9ʪYige,S*Y}u!/kj]wa%;`38峮F_f8qudvQ'd~ž+ -b[B+n]ޫ){h// ;YKmNޖќ@g%ն+P^ 3e%Ql|~ࡍzܵ!\8$|A"/:D{G =)! REZ/d!-Q,}Ύu]K.&_,U_/y5>_&Ԏ^CF/uw:wE29 ~)oψ}k%]K eCK% q95C4uV휆%{|R_zI -P@ 0H -01`+`p|@2SK.d2U Ǫb54)ՍXJq5c2~i,lk :Fbvֵ~YbŊ8HThq_^.%uG헗o_2k7!ܻ8.EzX/ fT.` !s,Q1̯CF/*ݲU[ߗpy@9ς`ζ'{AJhQ)qSֆp -_pIP$ -(g=5>oOOMR3ͯ,=91ѳ4o%ȎdsznUyUksiLWW{ WBܗP{--^6*k6|ʵ(K^+z^߻W?}}.CCO;?E-/Ei~qa_O 2l˂`R> 3+mfzGu/./.6THJ7~*Q*NjOuFB_4_/{p/̊wܔk W~iqU7*n~-4RгPt +e/<(PU%XO/3^[wZ/;}Cp+Q.11Ùja \ݕ%ŻS}cGM& 7ɥTkS-<n,G9fh]]N}ޒw9MمȬF"]#1%;fjn[aѺxಎW ۅqOO#:i( =d}d_/k S4= kpK:Lx)-p$@J@ @`L@$\ > H91dCu9ZȨsM;g"Fhc`LrtJ c)gf؆q¸|1-9Z*)^gֱ#Set]5-N"~L~V -]7KAN%kB8Erc考"tTn67ŮVj;S=>sLJɔ 9Jbjz%Pd8y{R| Þ2\IWSjN;'~')'Ty5k9N; _gԆc\ N9swܵ˼\e wZ 7YWmkx8v?r2u0xFG)SpSU+v)p$@J@ @`L@$\ > H-9GSrVM>Z;H3n֗ц2XĘ7d,4d݌n[ΝWMGiNxgbJMط7=+;EM_)GNQq+,hׇZ/뾰K`Re &u[r/e -r 24n8q gjPʟOLY< %\[!\#+%& 2ǃ\DWR];x#sk(gڈS#9{ Z-=J(l!Td%4G \^ MWxW YgcLȶ2 -s[A"fL>{R6QXs<ұM?AZ_"B HwC3[Tu͵yo+I -CPch0 Ca0b0"101 Êap`p. C1k}OH7˲ܘe1BdIēp8bȲ\Q~{WW{un[JSX+"RD""RD+EF""EHbXƑүG{<8{?y|I $%>)J|dUU~ǔtdÀϸ7TOvU CWY:G>tW:|^缗/ >"ٻ*_7JZ)h(FҐVҐ,bg'NEF>䬥MvK룒La4tnIC0v]Vx ;4tE;;WV`^ȇ:44&O}>tK"@ w. -LnILJKK\9E>Ipq\zag$NyM)ʛ9eyQ*V4{_:$_p3*þKwMGN!لj.56nˉLv[薊Y\S]H6H6H6%#tlB8r TI(4P%P5@4f,v.D>)`yF+/ZŒFٮUiԕZ̈́9յjZy-I6$"ɦ]K5k=ZP+kFNZ|=ME:c+I)wSoKy$tźr')WG;%K_VHW8$nIƧuY|aR񑃶QhgpFK8寝^`9͆'mnCHͫ~Km8.ϋixgBV&MFۙwvZHN;p@־ސv]'oʡ{?E)zE);v\=WH;vQ9f]ᧅa㔱+T衧D̐g nȱL;Oo/Ss27Ӳ=Ϝ!+Ă+<|N={_T`Q1iw WhJj =0#0h X \ | RT`J2VYΪ,UjXS;ykTHvnj[-GlkbmkY<},_azXm&kfxb#$KF0u,4=dA)KE/K~)2<__F~ 'χ_+~FJOᗲOtuN쾻{ՏpJ'/Y g|PΥ%:ⳳ |zB||f[rm5遥/# -(UwSꚷq%4YWHjl2Tv/;ZRMm/F E#~͎q<Dz - 5/u2On(~~YAO˼ |*em{T0D WhJj =0#0h X \ | R,V>VEMʪBU32F.? glwcu\K -#V6}GK~"MBiץS4W{Y/%|;Uq%|?yU7۠6+[&2xY/7~y7if/~zs̪U?%vbt&qdVbS iǓ-; ;mX_N>:CW_ⓛUGڑ_gvQқu Kݳ é2 T /7ŕ Xހ_/'ҿ/!~i_ -;)q eee.% OZ, -*01 8< ,~dc,=*ڭjU46=ffM$XZ6~d/,7z|+4z5O?J~6man5wz`ۺO\/kވ;X%/#8tw$y -*01 8< %oQMìbUTmVUheZY]/f -VtN ~)g-5,ګXrgY+_ -UzGX_?LeI<%%O 5sZ~$ES/ w@R]uwH~@/Kav]f~uXl%b޻rx -rnGV`|y{QnڴGo#ϲӠ$'dxΙg "u'br)/,ssnNYP|Э.=iN1s5cgUٝ?1-0 }5Q1c?{m)d.nqL\ M5qAzajwwֽXESZ*0TiRZ# @ T@ 4:`& b `8 px xAA - 22V1<QWk5ZfA+aZC81%ksdMwRưcZ1vץTGzh=t Obuю?Vzim=-A/Gs6iw:2M7._I|̒g{;#Q[n)zғ$!;>wFO5?XxJfuʇ -aN)Oƪr걷9KHnp[߂̲p$صl3Gu=G楚'މ<ɎŞ`c%d,d{TzP#Y,xe#,o"Df9ZGwJ(4P%P5@4f,v.D>)>h2FQUiUՌzr=VWǵ6͘Bdn;,c ~Tk/c匫1#kɜaf!0ݲgUdHJyJR*R2==#t)o G> )e6LdXR"8R|T)e㱍(rW}NfV66Rg+WvrbN~Ą6ӟ.2 5t 4.lLr%;<2 GeܝΝ^,^+ϝˡGK -)E D77+È,3 ,6jl24N"M5uu#Yr*9"K("K5{KD‚V̓ -E Yrkfd޲|'YZEΏ -V|2{ -*01 8< ,/Z,*\V5oU׳1) -XPk5JRC^X,,bGպ<[jV,z笾_C殌xH%ME:iZrY=\g1hWg?Ir$& DDXu܂n N 2cu`5A0@[~-CfIBf>Yêeș%)3͍d|zR:LLHK,wbǐY&Z=ݱY:qt h?)UՕս8M1Yx4VϭoXPB)Ɔ"]{$\?wm)|&~Gd2Գ$:di{4Ff)YMGGYGYNK(4P%P5@4f,v.D>)`yIS͌jVjfLb˧,$Li-֞8BkJ3j-ߪc9 j,Yn0N[%0au,'??1Sn0!Esfuf%8*)4bf0Jy偍ڠxJn(媅Pd0/ =K>%U~:CU/PJjՏprh=IZzg NOIsY \#UA%|a;)^甥ŪuOr8XRuƱbMe*qyDlܡnKۮL}x}aWcGR$2ɓ1AU#N'կA) E交.APJYRU+)(4P%P5@4f,v.D>)`JF>!F(GFh0L0N^leLGRRKa1uu1\cZף]3ȾU')F)/PҬ:I)0JY)e#Ɣ2/E!n()/u\䥎߼ ~tcQR;Jm6ےPʃD)% zK-0맓oCJ1H_~U?%msfFzZ|t{,'/ 9rIYL;CBۆKM_'0>.Jڃ6h|ljl26mPf]_K!odhymPˑT˩ыSdKռc)}mP}ڠh*GKv.<{Ʉ; -)qo>ڠgegnۣMJmWhJj =0#0h X \ | R8Vj lVϪ:miƜeue~5LȬ\r@fg-=,i#7tWo +4>䘗|wiP(4twiH6)AHy! 9tvV3;CicZe ֪͔ft Z}3cXS4nAk9ejֵᖴFfrFboVqr*(,)XZ +R`%bwyHf_g/;aXqJkwVfGg= \#m'BpwtNym/y N')A8@5s6ɥѫ~K甋\6Yi$'->g)IbM)&%܅e84au[л(EnElڬjRH|PkuCM5qTDj􉦢|q%[,uQlqe/y8\j-\MW,<e{b0Իg~">)<(+y y6ĪX͠鱑)ìf, -q)Y!g-6v7%UdJXϼ?dmԶff9yj)ET \pS~7vf:gJ/SNii\2=(W^}A~Y?|_/3]pߔ}YLq_$z}wWz8rQEѓpo].'>Mʔ3\ )b˗*R2e"KblG_ɾfZ&YS}z^bn>JiJ(~}멣aebOkl2s?@Y[;G?5BGVhl/R=OK4B45ΦGia:I_/)Rr:0L+o&LN\wN8<݁$M%EG),)<)FRLIb%%HRlDb'E A7)")^R|HȤHQH "%dR'ҫ -әIԕ+vŐu -߿8˔KbU"vDNS#PMt+Do;HŻa% +I;wАlϿy' ~gZ6⣟t,OzVOXx?3:?Y??E?E?{'>G~֒?kMCé5^ͯ|fP4;!͎$fd:s{\vG?Gi]nG{{{F'Uu>n^{}6{ڽ{M'Jǰ堯~ɜ%n Yd2yvFb/<,3YS 2Jҝ!©_&1rpC8_Yѐ'aK t_\>9]q>#ǧf4TI4mxz_ss=L󫢶Ou/cx]dE~(ck477^h-YJlT{lSFE<.~c'YQXMO#5wW*/R֔0JNUWuaJ')qfNkg2zo)SҾ支qrS~e羷)k)N9N89%nqYGNqᔪ}ʌ)'@>UףAfWJqIj8ί{g)ҖNfx)I\NW*bG N)SZ=攩7\t-Q[˩_1,"e7xLsëAKWZkh)c;q**zǞ].ts{POC)ݓꀴ"=DS*NCZkSۏ9HhF`f`V" D;8 |@2A$d@.e8ft3qŜi5ZABR8as <}{8o!+.V)_P2:jLqʥS>K|~[R}ţtO9%ëMcJc2ۿ^)רJJԼs(eAUW¶(%޺Q)Qz_qچSޯR.ELJ9D0(3_y-:@>ELII =g1I)mR:%S=>WF$+4WGNp^Q{ŌxiRhC?R ?s2yDiW)]׋' -( ($&}uʔEY*Ox村w4R?[} J=ʱ8D95e7wpo5T}#%Oi(+w.k} M L?r\oKhF`f`V" D;8 |@2A$d@.e\GUV;t~Z0Ųc1`\L2,X[bXWc~Q"8+cb]J<%0~Tܣ~羮E/~/Y_4a0~YrW0Kemd+öAzJm/6}!_iLe&\dW6@fw>旬jK6˿EdYGd'AX۹!"-.g&TWvx+ErKI#;Zi:CQL]QJ[|(k{bG)C-;p(/7>Fڟqs_^,m Oh,z}@c8CEQђ4BY;Gs΂ƹtvF'i_;?RdmtGh%(>KfI#ϯƐIaHђ#EO#)&R̤XHAJ$)6RI" I/)>R$RdR(I DJ2)bWvѵ -S -}.CV{c^4ܳҠXhLc"4ԧ؛YqtrrwywZ.ezצWcNχ92U }>ٖјk9sI4h2[~4V5jHFCT UR UC;~~4f99_"2BCi{%|~1Ta 2c!&_ACW{g-Ry| />/&RSS|zKSS1#u4faE0Dm~k#JΩrDdD4X17~}z:=CGEwܗ.̽qHKF9֢s*w4s*kG:ϣsF=vND0@ t@ <00  l ؁ @^A( B $r)m}c88ScY3γ㬹tq>9ղP2kj9gYw6's -7JŜ<YeRRԯ^U)Ea򚪔R0Jym[:ciHfS'RSYU)3ۨgŪQ ?R~}Soɗ4KLyݠQ$;'֒MH6 4(sH6}{vo\9J4Odзڳ?"0MqAz*z$H6#H6y 4>d3(MMH6o=CD)9dSM~:iJM6iRRHѓb %'H3)RDIhR8Hq&E$K) -)ARB$L -YSs]ZŐ.'SIAt㻠LYQ9wm|T((QO3P5Ԩ&҇^UC>UC0ڻ-W{?%l3 gنS5d lj=UC0ےljvL 3xz4X:1@>U ~ ӫ!=Lbε{g/$l`*I[eJöO&l2M}dӷ l_5H6/1L"y[4\-Kg-bxlTtwǞS.ue9!"ٴ"_UlG)GD} &Y5i-=0L , -"@$h`p'pxH@ $ ȥr(,r!N7yNp|9oM ̘!JY%c󜽝f8G5GƄ9YNY.P+SPfMWjfB+'zLQ JiRfj)o. Y:&?^1, l"?^fcZ@o^^زcv]g84RTn^c;.ב_\zJmR:PʡHT!(eJRf2\" : XA6 @n / !qt1rNw 199!^Ӝo#S=\SV?9u}+ԯٟԔF)?)%*RmQ;b-_o:rM2{U)@M)RBjJ 3ןRB,%AبC) Rʵhw P ue;dx¸2 XyɺpzQ^f|&{\L̄4WZ'5ӗ*e266KG49:֔2ìmp~c8v"G[J=Kzu߲T##R*ؖ)W҅w:Vg9'Ra qN%(eRς"FfPJC?2 J%ҀZz`,XDH`N" @AI |휮OrmUθ8s%gaD)JNsrb5|'8RhU)5S0JZSJQϔk;n)d>qJd(S~v>arYt?Sȝ᧖fSێrMZ r$68廷ȧpŸ̅'CLp;=q녾^152 |'EgfJ H1vSONyEaDmǐ+/1̾.E~(`4\q[b14ܽ6JTj>ߒ.4^z,9<4)ePƆI7[ϓr -M\" : XA6 @n / !y"^[8}gYqJ oiy#N)ӜP;8g;*yf] 'p%NdSuS:%.SƔsUąq7%ܗ,=R^+Mf땒*%W? t>>Sc])7hwUO9fbWحPΓC)^t>w*-|.o 1^]S/n0iq qSluFJ:T+>>Γ!lPJ):MwO./ 6QZ/c6o2v.ԣY0*bJ)~td|8˙rt>CL)]q3pIRYWX((e睉)],YƉ%ͱ'#J `ʞ[LQJfM)?PSa{3dvLwܰh3n^f1o5Oԣ:ͯm5OSOrטp+>b~]NFSSSM<8Ta\L*$)=h7ɕ!RRR)Ni@L|_֥>}.z EHy~lٕD~OR޼֧YhS~**z6ta|yc.˙7 LSSF>Sr= UdQ89&8p NSON%ҀZz`,XDH`N" @AI K)q ׷N/+xS;o-)d4g_ӜwUN3oyԯEjLy0S՘rSZ8x[bm ff1ߗ~uR~**^_W;0Jnj>LLy *e_?cCL14C)ou>y@)7=)1罰Jȧ -rinQgu)%~QR2^WyR\ W'#]iJ!qU)+oS;&ju9Q<ȞxD{2]1uG-y6b-%(=[<҅w:r= B)d4e](Xv<0єUi-=0L , -"@$h`p'pxH@ $\є,}jV7;pl/R4˚XK k!1fpu̲]>J\X1QN#3RaϪRU)0JIt<1U,0BrJU|\*eXUarE)U)7V)?uJyr|'rTa?~"J\!EL4>L1\)q[(x24qRfz  ϣ)0/^t>1l1?}W1vib\Z|-ċPJ9R= V uou)yl- !(M(4>C)UPJKPLJ%ҀZz`,XDH`N" @AI y~m0ecFyO|FyCώ|;oMyrd0|^ϓ'wNEN:3SKm<8)勪RSF)JyQ| E)?Nnz5, 7'WUԪJÌƫJUB$RյPJSS4>}J1]_!=[e?ݛdR|.)!ML݊h|\S.tip&azHd˺D~mSɒ\beq  @2 2e<3iy]% <'xaޔ˛x8o]"s>MD)+=fxG7Ox.^*R>+-;9kGU -F)Ws>_լ) -eǖ>I쿙R4+7>WJz,[V[Ѷ4>;oWJ%r%P[PʅP/ISdR.OڂRwMGcL_w)7NӼҙZMh|2R\ j7N#OC)-TLryJc(F)2xL9h|z e,YO#Ld9]z>u$]81א~JRꠔ{H%ʕ H)OC'+SF1(exJU -. ` Dv p7d -H^lR^[8;(ž-)l53ɬoR~*HW:<0JkaROegSg*'v3TLQRr5h| H(ƈ2Z!٭R!t/MMHR2\rFZ+E}x\T3}?wmM)_pуEW.>!zz +="ߎƧ2ط"R/L`_o{OM{lU{{˲O@)#r3v,v> ռȽPʡ{B4C)MPJS\" : XA6 @n / !!^fNyiaX̛jyso+D)Cdgr*ټy0;K=\y 5kSF)T$n<:OYRSC>2jfPc1SSPc1SSn{'|LQS^SvN=r$1A8s:S.dv懘1㺇puLgz22tWBZyWjڟxM8ѿ)pJu_LYqMzQQ[WxId{=dK64R(c+6*:f}t{UY-N9D䗽CrDyrN9(8 SK@X#03+ D>  @2 RѝSp6V?YYm5Oֺ϶nNhf휳sWri{MR#'瞙FnC^uʍ8R) ar϶t>\@K8Ysdִ)TxiʖTA-|MĜO9wNS/)PbλJ89Xd.ur 亇pZw0r_]i>/UȢ+ALKfĥfotJG4y ?P|OR"rJk1 )s2yLSpJw޷,%mֺ[D_;uc?֧o{h}Gp\ r<|Dq,Mi# pJi}SNN%ҀZz`,XDH`N" @AI Kq)mW6-3ɕ"2YHN> %wݍ8{8_%OsqNiyS㜢Ys-S?Ub m)#:V/8ir0J٫*!^][n|R\[n\ʔA[G ԏgIs )#B)?0ҭ0ćLpWN(*>⺇pb^)3>%Οʐ$(E3] PǗ2rgcSLW?P$+S^RSzo/y "{Ji+ʸt.Zɖ6k9R ݹǖ=|>ߐ.\jrVڊ)( Ji,RNE$ͩ!"9K=lDy,i)=-.Օ/z!ߕ/g1-3SLx6K}G4 XwŚ_ -9hht1J~}C4T< NcQ^Ah(##nV'ow){_'N;u;4s;ӯCC] ¾V:8g:ԟ Ih-):RHaII1b"L+)Db#%;))RII#E"E&%@BJ)I$r&%ҹAI6+D}bMdk=؝hSeBvS M(g4Rsbq[ZiW(QiP6Q_T5 ]jHFCECxs[pf]2l5 52Euͪ-kɸ.qk~V MhΓŋ\sU1$rCKCPZ_J˗˛J!'w{< lji4sMG.bߺNDw'w(v|?޺۸e\sA]iyOtN)LWFb_%]9W]}{|U=Qpp"Բ 4ܢEҒ&8al;Jo\;\KhF`f`V" D;8 |@2A$dpf@7 -L͙u}`6/C0XJk@pB^+ Xq}"VảB /V6PU|=_j9/o6[ǜbNZvz]0vQr0a 㗇 Лu[Mz룣K]d>@>E n$b·pp)5*y$Wj\?=>MSt[K;6->时KSYLG Rp#+|~>1MH%vV1kukOD|~eYVe,_{_n_V_ˉ4'Z_j@X#03+ D>  @283K ̄-խˌ3.++c,+K˴`v1̏uWŊ1X_[#fba}F|5/;Uhrnܞw}ib4'cSs,zA^_W旿a~I_鰛 -AzN]\OΖڨ#ۡWS'AF1/u^YʐLYr Hnq='>ݛqxfk}z/e1+5/}C)Cp.bncd=ւ'D_߂_3_˲,_ᗅqe =Ueb ~i<|#%: /mKI%MeY/^0@ t@ <00  l ؁ @^A( B $3͜@ LCvj&T+Zbñ6AG~%Vo[Obl:LQsvlR0~h[]E?G丅 ~ݛ{mSW?Ua=areoľ~'u/ֻ /"|~A&/Gc/S )%>At$|W/-~W_n旑dzej~˕_rm_9|'eCG7l_F̘ˆ__Rh1䗟{go8n"eq˟)z]%Rh6&!LyffE7a㍢(us";Fg&՛G> ZZm7"f_{u&*Vp0^G>cPbEE $> UȭSMTaJ~*XL-~*i}ҸD0@ t@ <00  l ؁ @^A( B $}tOY[xg\yc?gYmdL6.0O9sqVN⼹/9 sJԯ;RQWU*eO۲f恘gY8?_ QӍRr<̘ˏTDN9܂_vg6)p2`q3Ip ,טOW ux璘W6{gqd)353.JȌ#c+UJ2| RfMl|aZG4R 4w\.Պ([|NN)N}/\" : XA6 @n / !g[(1󜶂IPóP+M/pQfר~)ONڹq>M"Ɣwj2;{V~|UUʴ0J KS~ \[wԹs_#ǹ\[0Α[%!&xҷ!OycJzZ|f|x\>^&%X2=)߷Ĕ:;f͡:9Ji~ҝxk~z(ehbx⛞o?J?4CYrouAm\*t[eo|0оq.̴h.ڛ;4L.)MK=с[he惣t3tDw?4sL}h-):RHaII1b"L+)Db#%;))RII#E"E&%@BJ)I$B...Y -j.ԕӉD6?ȗASK<4) ɫ`lD^(%:rAwvHJ]H`'Qn:g5ڤцHUCijh(r[3ih3;٧Vf-}jHcWDtе6[*Vb_#u*@>U @d5[G['ӐocS3`x@'dӽ.o/!3MJLIܠ~hh3%\t"SsJ(t0t -ҳ"PcjZқsRe^xx[[ԢhϞNN?Q>ka]4wH6G'U) H6wSd.9{>*4j-T5_lp4` x`&`` @48$ P@@HR -xM"9 N_8ycoj %NniW9wB=S'6r(l>>{ZUʯTar_J(mI6wl6۵dvaw-T,#DYuCuʱ0Fޖ3` 7:+pJ͜xmfN/1]p|0_>$CL($yC8{Nd?3T:/|OqS/+ޔ0'U)9gniu=y}:JN!oFd;{D0 -cQo˿RPh"Ɔ{oc8lcy ]˹Q`spR 9a}R}U\T`l1Xͩ C/[j}-i-=0L , -"@$h`p'pxH@ $ ȥp]Kx])o c9Vj#o?Y+II^ l>-͝Y2N>&9efsN9:)N9:)lS"i͝2)Y~ĪԜbSbT:&l攫^S -E􏾵vW6ڥ++ -}rʿO]9wC8^IK)^)MGNQuq87#Oݸw#cVڬpDzD{>[<[?\@f|S 5\s,95{S)-9P ek*Ytms=%D9N?.:q7_҈Kch;} 4|%?&0(gwR"=>1e}Pm)2-,sĜK@X#03+ D>  @2 t&>Nq)dYYyk,#$p3euα%7qR/wsRbT2"F)C;6nNtm˶Ȟέjwdv&Bd5,mF)T, ]0Jٵ-mP?=~迠 -7km˅P@)b\8 * +bFt7{gq7SNӼ) /yUQq27RcQ|~LQe~k^#gjlE7\OEQ^uh]8'hwSm8ᓴ0 #s\mtxQ ~?CIE𒋦IaHђ#EO#)&R̤XHAJ$)6RI" I/)>R$RdR(I DJ2)bD?Qۭ -:I';Ӽb.H -Zȶ~D"t':Ê_ -+ n?d%9}ky~y%VbK/} -<|o 5"ď{gqdfkeD8'Ż|)4tN).+RRTYq~v1ud&SY)~)yg26S|n -T|9~a5nODN 24gF>e_V(ҩw)gK;Kq ~}`lfL%/hcKDP9;Fzi-=0L , -"@$h`p'pxH@ $ ȥЅyf=oU%06 |X%b%e):@Ff*c!x1Aq48sw >7#UB`LPc6߹lf_$\^0ת9UDlyGE7C37$MQkNa੷$&f<i};0ϥ{g/H[Hf 夻23ȋ2|4ѕ /.eBM赭/ fKcYJr񃔡lH(`ml| e<| ip=5C0=L+m=Y:f-f&<2fF - Ȇ)4R34 -KhF`f`V" D;8 |@2A$dpUvU`b%nJ6_Gb[0 -D "~^*=g67ZK<|_cO_ CLpu,u~IHIIyǹ|i,ҥ4m,,F$b'Oy'TYwKcI^霉BIJr_x_~r܄Y)?zr~yփ.!?AiW QGVKpx>g ijӽ13ڃIuoV{~ym oAztBe~ixȋ:+SҋMmN)@: tKb]_~ !/E+/{ᗽ!~? >GN-/_"~9Y!/x^%` -h 8z`F`) X@؁< DAaQKڔ6Յ6MMgcynM7fٌͶZtڬ6~O4?j΋ͶriL}m3_ >1W Z7?,v|2"q|Bf2~9s#_Sȶ}57uOMRQ/2_O4GLsӛ_nnX._DƼ11;_To8$ 6)s*KL˂j闂zԬZ hU?h3C%d%CԞ͖;֞G=[5+\tT~AݽqPP"XSWKrgRD^JcJP p@R@*4`<px   )SCƈf.l'Lja1S3"PBKc:@KSM-D$$8ȺSb޾kfOJîGR=)ѭ+eJc޵^Rs@D},OIJ+dKJdn2!tJz([mRcr,ҕ˥1{?&2<,2JS88f"?aA(gs^¥S'Kv߃ Б9q:>ϙ.Mz3q &A}jYSmzQ`6 HYug,bKJ{;RgVmOl~qct{J)hRJH{ĺp(eJ?K2 7FޖGTh:`&R+8 @!Yg?8\mWʩr)hBSAb}T)T)%G}88W',y-1患$ŭ{񀤔L-2Jq_YF)6B)_U,e }RI)Q%JJLC/V))]Ȃݿ\ e.^ҹ&PewB)9dA\aǎEU/z_d̈g9>ӓMwz}g'z"8ҡ@,/cRk& Z/K.9ixUPA#̚wT`ì[xo]v&c9]sJco"u[-09| - -KO*<Rw&Xz -J} Ehn2>w@阩SDGTh:`&R+8 @!Y`9# 4UH#N$џ$~b,L%SbRk=sT)MR\`˹4_JUF)I)R<O.Tfj'QI) ;a,M2JyJ9P%lﳈRҟw1sT)Ek<d (%.d9anʏ2ƱȪpɔ}ٱ@z3ހ :\g?/=cvceI7n[Qʑ>rQ7S[A5+Af~_` -PY7[OS9FD)%-G|{-R_ARQJPJo=b<6R+D)EPL?>D)'%*, T`i -x`/AAADADUzhv~yJl;AsEKIBҰI+8/ [ˉN_BdwI(2J9I)(+MNp֜,鷳|(W^JF)=R1nWQʭ2JbDkkL:M@ZKlr A) Aiq]0sK$>eɫ^%,MWIngۓgM,^cD޽Kƾl+߮P)G:nќggwmW#\[-b~ yfSz$9uris27m_Wg3IhsIHNΔ$ f[ߜO --I -/9LuJ{x^%` -h 8z`F`) X@؁< DAaQaˎ{mL;xm565&a:o^@j[m)o탼cjygV[`FkYu1? %&,KߐڟKeA^/=YlPV\e j]RRpֵ!,B溋 8nH]7X?LZt$7}*H? 1w_Xa>eI窗p6V.P+srcNo^ dĽ\OF@ci) ZWqu]qhY_Fi.,d~b\/3a"SfdA流p C J|ngn? 1Z'z3w& Rfֵ+ކR>8,;6[:i%%nkYEK}lGcndAeuM>M5x:IEpG~TGTh:`&R+8 @!Y>J5QfUu!̱ڳe"i0F*e*eX;Y~W,'fz<,cRKJCROF)IJCROF)mRڏ]y{Վ zfR~$)|ԿSʂD)$,IYY(΍XeN)wO# z/ؿҊRB)i˹駈RuP=&\3^¥S/Y;&x37pz3qgv cy/k]D)[XQПʉGLco3fͶ@6>#p݅f]~Jczq~i202fK!sZM:8|{[T)g Y(e|=ⶃ1(e/acPJJ#*T@ 4@ X0HҀ^ ,"UQM6v5їÉ"T)T)CJ B7Vzgxw:h+ NUnI)'% -dr}R@ QQSoyn_O)_boJQ{2J!R̈́ujGeEuU#R|ܻRG"sSRB~b6=0sÏ*>R ^¥X $.rrXN O<LJy<8Et=^.D_V'>r3/ -&A]Vb֜zOЎتYe՜U5o2,ꠔm?RZol밤up[ J̷Tnq,tA)XIZ?g(ev=bYAf`F)TTώJJ#*T@ 4@ X0HҀ^ }ꏰ^”UQ/aV;HV$ V_F Xc 1Jk OZ"yVha=mˊ ,X)!/RbQ -/)_( ~CTfr⽔uo~7R]?0ɗORRl=-[reJZM|v 48vU 9>=%\w"X,e;o,Ëȝ Bnz 'ʔ9oxH*կw*ϗ JyYPC)KsvJ)Ru 75n6v=`1<`IjRT^Vg8_{:޸ocJYgR*ǗBW&(pJz J9=GTh:`&R+8 @!Y`7Q`M˷FXvJnrR2Ů쥜g3,?ڋCOf 6PE$t]W)*/֞('>!m^!)%)aWk_^ʺ'ǿ2w*ߤzE(kR.(|mCjQQJֺ](%r+r?@)PJ}$H? 1Q"ټ5DtD^%oKt!/qŊwzgW;B:~q;ӅOyr'L?#m{EP5 ͚6dA2 -9;-햔l9m|7߾ϽL n[^xxi,gA6BY*JkRȂGTh:`&R+8 @!YN>Ng SFT3Dij9mGnG^ O-_X\DʂUWJ-K\WD kSeb/INy@rʼL%d/~$GakxR30UN{< + SDK-xD%` -h 8z`F`) X@؁< DAaQ,´,Kwcm7a:5,V-A3ZXwG1qm%Y`mD<Hp' M^I4JJN)C7}#QhsB]9e, ?.fKг2O$dKг2O6$;kB+`lC*cz_wx?lYd ?N 2v|sF|NyU/_oW_AxBmtԤĽ\[O,|1121$pr4cqs^;w7ZuwEDJ)EX"""EDȰ" !"VDDD"^ˑ+!^_x~~w'w&.d);RoڱF{j i6eov)7\[7nO prgߞw7(󍗤׮߶ڝ)קdߔڝۮ_~lr|rq.p?qN;vvOZbjXINLfrvL ;=o?Gj<333 HljT%&9Ȥ9S.3 f6fc3w?.; s4< *V\#u26 sl2杖> 4=Z# ?,K.,T!7[T -( -ЀZz`F  `؀p܀? H! J#Q*(giUQ i&Z[Ctôrq[1s:KZc -kv|7i_@L0&9&#KhY̧P]E_z/*?T韝I#-#O:-dQ)V)NL_o(ğ_ì<*cNvɯwcxy c<&(Ii -]jY}g O~փW]pY}??C=qYN#V=Qz/^~VZY_ۯ8C_Y~Cq5,>"f7qVō(dZrEٵ~7V;OoIsAxOwu -YڕL/a3 L3RnyBZӞriBUOyC8֓W+JGQE{dѩ2X#/in#;_-c&:G;P}0cݳ[bkwtQ9{oF|k,LJ_=!N=<'; -GNȹkbdwt/-f侪wf@s߭'߻mCrӹBٔAܯP@ T@ 4 0X X .<AA2V6*X*srU% 4sљ,SK'{ q_X l)khJp45ca<יoe?g.9f/?ߣD%_T#ydKKkmcvz_,%_z$/w%,-J|7_v;_HCd٨7M:eT_^㩋@6ī~ _6x g/|3zdq >ݞ&m=)^p8ǭK"2O'#K?,'5xS?M<1MڪN]/"K; -LSɷdq\i>)V%w1v ٻY0pG@d9u ?<Dd=q_8"K,cY" nQ(*@hA,f,V`p x+EPruL:aѵP RB:JJb'#@b⚦U?B'G $<'E%|=R~+[VRA)˿Y+/R+(br`M~vef $SSbZ̲ V;%N );B,_S컆?Bf}xOC}<O%SOux g.:$yRDINz3NKM㜉ԌUN)Efz3ԲS&=NhS25;9M=gvs)~e,VsC(2ob|_봌AMJ&8#8eNMsp` ʠcbTS%Jj4`Ă8`X`6p7| A,%&$(QQOM 3JkˈGN2H, ;Bvv qMw!PH|ğy|Urפ?fKN"8eSnBEpʎu).I2:Ƶ}ʘ{I)[g͓R$("(ekYퟬVJ22(ztL]s%Rsr /Pڼ#M&H>y JZΜR9FL87T2hxqNh)AP26 *@@h-=0#q  -l8n@$o`b&(YU/^d5%:iON1V_2],1b Eѻ;ѕѽB#k/|̝+~$+~֥ :ʠK~ݚebD.Jgr~$J~9?_W/ƛE,n#Q]E Yl穄P}w'^E"b]@tw ʠ]gx g/\iOJ=s:v3%nwRJJZ*L ʪԝ"Ai'T&u"G8.Q/3LgCY(l1WΛ[:-9l֎{Y@dnGd)]-s !L#,# 5!4! ?/EܢP@ T@ 4 0X X .<AA2Vh0z֔0 JcY(eXhX qD8F|E y JKJIKdy7--䷬= -%JR"(R~%)ku,JZ;/R~'(嚾elR)sYW#TAPgC)W37Uq;é岻Þ4=v3HKL*<$-- N|S<ɩLLfjG9~vxSZjPr٘c9o1iowZ2 -nݻXJC)'oBkPL%R -R,*e JiܢP@ T@ 4 0X X .<AA2[!B2E`D3@Qm%>bg R**X [X8p 5Mܣ#B 0ƵrHRJc<$)%UR%RZLPFK)v)brѱJdHN 9E.9%6S")bJ:O)z8%eѫP=v8|OS;Sk xO]~-bMAjs.A^ΜS֦&%yC3^*$!i&taz6ZgVFĘ2)άL4a؆f+3GK>)IN/iY)SKZ`EpJ8%tٟwS1k9J쒜r䔧"0G) -)OEXa޿.N9KMd܇`.r)rQ -wy*~p|U7[ToN߁saK8+@BprJRݓݩz7͞%%frJ1JJOET+rTÛYN5R^4Nixؤԍ)z2Sl"Rpy S|N0ܿZ6X+)cSjn ]pJqrJGxN)/S[T -( -ЀZz`F  `؀p܀? H!4(('ji%Sz]|UOU,N{Ny@}[T -( -ЀZz`F  `؀p܀? H!pz#\re9Qu=^ZU%6#ib&aq#ܴ.Xj O}6IWC\=E|"ԡ!bf͍* S|RNiV}RN9.Nٸ;W;R}3?r~Y)DPʕRK.+e:͗GYaCܭOq:xrCPʻxOc_ߋ -q:/)%))1 75̻9OwgIݛf CJZ+7> oB)uPJK?Ni(@@h-=0#q  -l8n@$oe(f5I DUΨfu H%n Cc<,f3L;:Jlq2tJ9bU)$IJyZR-RS)OKJEPRcC\rZNy)DtHNyh))gKNIbrv~Y6DQ֓嘲z8y")8FxN:@mc8岬d]+^™+}ѧS)9S/B ﶧx2I)TJUNGL(?%MpJ,SrM9l+GrneO}Xt{CĔ+'3W,r0;Ի: q^8e}Ĕc)o $= _7S)Sp -@%P50@ t@ bA0x`,p@> ҊA$bL'㴦ЇifNҺz Y)yb3K[ZLĶ@;\+q,WNyWcRckRsK~~߬iC;+CReRLŔ]V-ػZ)}YqʎkS~}9bJ Jy{4m,5iK󜺞I  &ci9RcsPTMl޾VY? J~_2{bH+RP4@)uRp -@%P50@ t@ bA0x`,p@> TlJ?Q$Il%lF]E4c"6ٔ&bcJ^bi#[&\9qw+0K|y%HJyW)"(bʻRLA)̺Ĕ-$gXK)!z:dRJyIZa/寇RLJ9 _A)ώ/+Jٻ)rS>*R -P(`ۊpyMIywiw%r|҄qSap* z^{vY)E9eN -Ӝj^pSSǟ -v.Bѷ ={ƹ|slm*-G=6mMB)bWT>w="sn$/@)G*Y`z JRNRZV(JiRZV(JiRZV(JiRZV(JiRZV(JiRZV(JiRZV(JiRZV(JiRZ%tru.942JD\H!Nb}E3Y]\N;K ĸ򉻑݌0J|Gm.}˒RA)J),)A)?ߕySkMZJYg#\WI)%&R~+>WI)%&BJTҳHM)2y51'SQ$|)7H\OJ }'^EJF# -c4W37%vZW;=NJ \Rdp=͕ȹwb{RVO5eY)ůOq8Jԣp/rtU-2i3g8;CL̏L5#TO]i6-pءۭ}7&>9zNUʸ L<-їxR6§RJɇdYxܢP@ T@ 4 0X X .<AA2VEבU+QO#Te.W3l8$*%_Lf,E X[?q0aθZ?lR1|'yL5KJkޔ k)e$;DDK)咯/+R/ 벌gR΅RQP/R`R -uO.l:@}۱A2B)ZΠRMteӝ^)x"vFLm̼WA]1\攃r=&<qL>#N;ַO {|sl^㲸[;J0ۿw?wо/h꩖gFEx 1e1Jz1bDfݲRp -@%P50@ t@ bA0x`,p@> [ B Q-DSJ:L%b"<1)ruڈc檗| _HR+#wWK1Ғ5)rq|m]b7_3:|ZJ U+SvI)ҳnRYrRJ9+RnZ_YR6X;.RGǗg=O])|UK:R+m׭x gL)/@ ;]k Px,9a>1ѕ.x̖O>)xKڙ2'o]jԤn[4/stq+GJrZC[ƱESlGH)n1O5;s}|^nݷ6_6@)b"Lu ?_8RJqg\T~6G(*@hA,f,V`p x+}DQCcDYLTLj$%I;B BRDJ5NѶ:XBƕŸhqm++WW$.1;;꒏FRQ|"(JydN)ܦ*hֈk>n KNA8BLyReco-?|n~,Xp<ϛW%f`6x2Yn7WnD.s=p8iwR) ))3ՙSWv˹vY~tZ*%SOʨg)Lմfu[O4]2:S2&fmG26}!Mc)UE\rc3ke'_~ m} -1 \ -W]]~o2r`7_ܬ׿"7E"%EhLt7J8hāF8('fq8`88ā8ŁA|8!YBT-mVLmZMmx07ab;Mlf뱀!( p]# ~`PU׬.vo*Z4(i裫oq_VC'AolZmYCC݈6KP-]qOhqs+A*I.8׸%1 qFHJ {ӒIްNpW< :3m!y~aJCۨʔ2U+G4My2\3d'/9(3<Ԗر;7M\x"#6K_9!Δl ?!wt?BU,]9_;O&O tݺ!x uRܯP@ T@ 4 0X X .<AA2E=K%(Tu fVS,ӛhdxP(~ig-= lgLpTlf\9 V? M5J39T_<_"E'~TLM^/lY9ߒ_Fg}X tNZ4zX !\/OeGr Uj:l9e1( /wÈ9ߒLvx gn>˻gۓ܉vwR˞{IW/}HwSArT`Wt)s:z.fu>,>`,n,l6ǖ,gs)S|>hI]|glSy'Jc2WqNUŻM(Ķ+( -AT֌ҩit-*@@h-=0#q  -l8n@$o%Q0Q0RFM4 m+b8tQ] b[5%֖I^ |#40?vפ-h ՒR,1fekWF"&r!_K'7$#eϋ}Y-o"sx`SIȚSSZSN[T -( -ЀZz`F  `؀p܀? H!p<ۈz2taJ}=c:L)%ak9c$I0>LBF":ƟɬO)K9=MGSl#A)k9场},=C$#S΍RN9"9N!lerUB0&b6q%cpJ,O]\r ?u68e/H)-+^™[q}ADW8%bim==QsKsW,#V+ftR8e磜ؤné)|kBv}8OfcM9vD<|xdS|NKcY]FN)fl{$Oe3!~,N$4ԈGᔁ@pJgp -@%P50@ t@ bA0x`,p@> [)^R(N --M#]cľ+~YtJfƲ@6F<\8fWθ<73B=e̚FߓoXvErTrv)\)NkgLr1DoɈ8]mRr6I)J(R*e`Lr_R^r+'w@)]ߣy)'^S' P?.Ϳ)\\H{\$3ݝmIH-\;+<$(ۻkǯVTnȔ e'Piz -2+cy2]g #/m19z熸d, Gel'dʗdc5rǁr宲7_ʗȄ侒>OWdm6foHst-W(*@hA,f,V`p rP;AuU%hն%*X} kL0.ӵb<֒ɲ ց[gxWNp'%PVO~6Ci7v'_ ?Y./t^ ?uĩCҳ>cvqg$|#_6[.׿\yn~q/G?/w}e|UY~_\/% N"zng$ny{{)BbR.G<3,G~ -eP8em#38Mq7"K#LiҎd9ujĞo̱ o1v;-٬Sۭuul}, b[DEDڒ]L joCdAdy eG0&EܢP@ T@ 4 0X X .<AA2 $EqܰIJjIJyNRW#(%-BdyCe#W ?_|rs}udyffG_cC)ײ>#~V?@]ލAjӹ3+^™[rEYǧ/TΞ䲧I.W+eCO427hW:nN=`RO~iz:9c:LnrF/a,l(0~Jiovs|NKcam7nM+@o@)C)EܹUMB)( J)Ϯ: -zSVj(@@h-=0#q  -l8n@$oQ .ugTD]h]03D}n3Ib" -:X{X0 %\jeK3+W]ͤ.R4la]fVI7=rb巟K=h]~"M.MaOchOqH\T/uu -Ҋ+Po. ~(P:ë>*Rʛyy -zzQy:~,JzZbZRzz=)4{n<ΤJ):$o5INeN9-5?4_g9I{))%2C&cǦج|(ԕfS|NKMvnf;~PTʃPJ2Wc&(e.J?,O"5HJ-*@@h-=0#q  -l8n@$PMk u(+VM  SԂ$'6b,SJRb'lW.}ˣiwR N[uk771R$|K1Re?Vl2!ҳ&=k>>$탻,=O_a;nQ[ƽ_[N)fdE1c/Xv:O63\$;⋠KJ)_x] -q99:8eN7ߤ sTT"ٔ:u'^fh2n56'bn~_i(#ۭwn'1dÙ[)Pzɦpgr*G9lp -@%P50@ t@ bA0x`,p@> n K (U?ne4 =O"F{ƈalrfG[ (ea"v*Yj]j_T}KURyR.nI)IJ0RE)ٯD%l^)/@r%X!SX!S z(k^]6?S~OY~2h1ī~f P8?YΘSR;͕  K{bݛ$~,)%R=iC=.m]^>0b~zu=2x<&=2=2&g -pL7w blxaK+wn]xTfȈ=͒u;0rBfَ5/wg˹]7ge/ ՇւWN gmސ2#*@@h-=0#q  -l8n@$o,A1@%(T \VlyUc cIPRƲ Q֑hMZ\ |QPYgš[NHA:``^ J` JG[3pkD|O:V?v?_:<~|u̲ :Nt_*a~Q/_'^Efdn RΆ_vǭx gBIBJq71ՎS=$/Nt:]Qvrƒ&uUS3G7wsL~Hx>d1n7{\7)~:i. ۭcelUO#a𥳐ĝsw@2K,u⩡dY2 nQ(*@hA,f,V`p J?j&T sP5i!t.')ds݌t8;H,Ռccf% 7A/0||flg;[I)qR.$IJRn< U -3+Ll2vßī7"?rRr+^TJzSxޞt'oOMLs۽w&zΤTng=wJ3+u=NyN0d95ʧ8&mC-d43o5RF?\)"i#̎u6[RqLq*]|ۛc@]Zq@%Jj4`Ă8`X`6p7| AR]| #D40:?ITJNbZ4WGl%DJ\#KgۈM|߲˒:%HR9|[#/uHZ ,}\hHZ~,šQP,HGn~l_\SJ٭1LcX42fV8SJ;w2$'k??YKKJX,7ǶE;vM`5DJKsHOLJ)HOl]%|eژHoo<'7)幂#CR^.:ϯ}U>7^7gdx[ӊpjI)! ґ!ܯP@ T@ 4 0X X .<AA2#C,5*YUU:?A3FmF]FC3k,(vx d-,[Z6ZYGͱz5 B3+`\_@1v],Ec휻x]jUG;B+#%4W[/e2_R/e2_[_Խ1UP,aח =&O]x4 ~ }U?u?f "/UA%ݓ!2T2TwR%1cuCZOQ D#2S-}{> kuNTk> -&o3 ͱŨr򝖊avbbd6,,YÈ,w^ חq_7>!Պq>-*@@h-=0#q  -l8n@$Pog'5@Kq(ۖ>^v}#ˢsnX:{recQpUC}_2&&I)HU%R8I)Ȣ/sR=,rs T$\zR#(,IJY4cy|.▕/A)7#\2\}/ ᅧz[>})l{/Hmzs*:/̝B+El+$}8IڽNkwzĤ oJ+BӸC疕R*I)sQ:5Eoqtf 7i[nu?Tsaz8p9_-fS|A0[;k_?RPÏ%"KܲrO,s -Ji@l[j[T -( -ЀZz`F  `؀p܀? H!ŸQ40,QF]h3t#T1b~=OTʨ8XV0.1t -q+eqB&?WA+{RRʹQPr]RJb eoяGS~!9%7SBS~!9;bj}uJGcY~D/rL+8Aʠ:J{j<~FNTUU5SUQdBŠHHѠd&o6:n9gnicn{(cBB v(uXLB0UJ!,ƬJ1!qLe^ 0(sx]9~i#?0sYxʾ.y 7 ep^$gJւd9|;7;ś-/Y::j=i:N;/Qڒc#m׹oqk2 l n>soܭo/6PY_rMe\T8]#p\{׈uOPŮ'hV:0=NNᱦ^:}E<4M!&!%#'1111#OJ$ONAED !%" B8HHH=?Z 74 {CAd2Jmyێc6芬ƃh;CR[=( pL>Cu ->!SU)ko.{˦le)Cߪ\WV+ݩ V3S![z37jCVƝs0"[g1&\B\?'c-!&l}+̄}Ȗ䒗pB11Sud![qJL7T);G1]m}UMʕ-t!7SxNPw4Ce nШxA+ sEncqTk\PayttC7n%$6rNv8x劔Q6[toƧ]&p!-*\Joi5-= āx`x`/ B A 2 -YV[9B*.pY>ɖ -9۬Ý]X|8OKZ;XW=?*51RZXV%[a'TX)]HVe^b)b~FIXwm MXr6q"c;cɧH[b9+3vKy>+8=^7%Wpfz^O^vZϓSJV7|yI痸7uEOЎ>֕EW"pE n_)׾jʷZz?Ho'7X'" nݛd=Q}﷔g쏍wr6g, 7SFR?Lڡ ]jR1Hh L , +H6;px$a2 -V_9#φYm)Vk\2pZΓtiKӬX{c]gX+XJ +~MS"|VS6ʞ= -qU”%ЖhZ2w)}riƚ3W$Ck?S^Hz4,:ደ"sGGaԃ~)] -3_a)?Ynޥ/9L1)73L}>fJ޴+V5< Fѕ]\G;\BJ/]Ww%qui e`)y}C\5(k$n<3JRQ=IHfKZ >8N{~4fvvxb{RO4m׷h]QJ|j|2uzQ擧XFl@_ڭv._hlmgU篩Qc|v DhyO')yu6ۿF̏QXTCOz}i yhCDMDCDKDGDO#b b$b"b&b!G$H"@CKD$"   I'Ad(Dʐz& iB? Acq42 ZiL6Ըޖ[Bp=~v9ybGH -K+9OӲ q -6mӲ ilȵ:wWg+MÆTP-ј!h ݿXg(ozv9s-mߋaC"'9'?Dd12|WnŴAngFNcHLsfff.o -8ê}/+tS%.HQ}AWRЗH)ܳjer(27X+ܶC9_7mr2.2mb|@:ETAO9@4?,gNxD0@ 4@ t@8`F`f`q XA"؁< D ?tt6Vs]֛zYsk%SɜY[7w,dNSȜBuiL /aϜn- .G/+XnRrdK^t9muN]rz9u:`H=@F\z *F8vL߼Xo]һJ$_Gh3NO ujz(]]a& 5/zI~`(6FњR,>KOݨ$h)R'߯_?o ,ݍȖ֒.ɧȖ=ې-ؗWb}|Y9Y,Atzs2%gZ'ۙIJ Yy)-`o)R -J\tWnfO)x6A۽˭;yJG:.r(Pm<Mk0i/RfJ6XXV//lo"KϓE"smtuXJO,%ϰ6DeX諲i5-= āx`x`/ B Aũ܃,3̪G9MG]Nib ͬ5VRD.%cuays့%]\[R=ʏE"J{Y-el)J]K[cM~aJJ'Dޤ)C -rDOxNxt:%OU/40o}ayxJu^SɧH}n1{-K) {wi$S-WrzELe;,'%So:*~ӻOx;IύlbvQarsvT 뼰ҟ,EqC1], L(?l o+|53=KYGHd?g8N6<5C9OrMޜWyw/Su;)i?ڴ*0{D/ׄl_^ pҀjZz00  - ^  A@ pAӕM -HG)"#\^$G+UY"f[',xD0@ 4@ t@8`F`f`q XA"؁< D ?t2:j"6j8}a sq5/$iP@]v9cɼαbB(|GRy J([,Uѽ3WĶ+vfKTleKYp [ʏeKY%(,'KI.;}=Xc.~y,e]a)?GȂOܟ4 3_3ڿvKysi/Yyy>_S$+bNSq -![ȖSV]<yInt-LA={L?z_M #2FDF%ˉ7XgIHk)}ec,}LjFT(oMSt﨣BN>Qs1 :0#038 \@"@A:ȸxlqK1ǔˬ&jX]cp5rΥ4A'X{-d]0zX+Ny?V)gdK )XCn>%[JHRL[c[ȋ1MKciEl)dKyD!J [5R FWv)/'z$;(=ȂR KGG6f^X4%ܼ:!oG$`)L s=iYiJSRچQb}t61o:WMܣ -wM\Lq{ їU4pYPó,cmܽgM|m+}0gYPȂFRbdAGS,W*z%5_Y,hbtF/@TT,h8s1 ҀjZz00  - ^  A@ ɘDLWGyE^fyOq*0/'xs#o9C)vN6s<٧ؖ$OK[x -~Ŷ,/!U_l -r۪\0W|2j })U1_,1W@bQYRF_On|Ne\lO/o ?mDdFlK]G幥T1-ϙ+d9^SrN!-Ib;3۳_ƫ'۩:bګZ)uI8,ߪtlc)eVCq90\UC[,gU]@wVYk[wPӤΥ*~~1VvU\žz-طF/>AIGPE5^:=rBs!MahhpD DDLDD,DI$b#qqxxD$"~""!"a"D2!. iҐa/ĵ7!SE<: RC␫$$ yf JaO00*نȆj -a)?#25iAdN;xD6o͆02-y 7(5 -̼4oϝJf9Yi3KJԼ< YW.?wX5G; -73s%`S=BznJ?ZJ/cS2v53n?Hkmt^P&~yĽVytRX*W˳*ol?U; S;)itL -TΪB k]Xr P X@Vlv. H  d(yzg}f6I5$=w8I祒dXR2bpRRXO)iUBUGiJ!ɺ &FwYU阥n^}ںA&ΎŐer!c|`\Ej,U[9/\n^/viח# drxY9δL(-ǗI=>ѝ{]CלtQW~/h&+{Uw\aM8>5en-Z.["dܳ5qWn->\GR *F>83( %ڈdu/UIY4`hp@|MG_T)4}Q~U2>%0[!.ᒗ$ MȜͧDd\y!Ąg΅ͧ`C%jg))Y9iR"M33vf#O/3wê'}mWMs҃3uZP״ O%hpj -WQ`650&XKkg'n87'e}/oO=8A-*^K lJO?*F;~tti*"3T}J!G6xD0@ 4@ t@8`F`f`q XA"؁< D ?t>㘋 )m./r\?gh匳3p)ٔȦ|aw5gok`0NsN`M$S.̾u [S>IXjϟgT15з)]do{@CaɞbVydO1+x^jޫrdžz̝kNSf-ҏiL~Bh-6ɭ? 0E0{ΘW_AxBmtĽ\Yv73nvva3lV\s_kޟ -,]MV{Z$ ADH EDH RDDR1Hb$H#~_TF#}5 &:w7_I}tPuGj?[?8ڽ&o{= cդޫIwk?׉Ϭ5LUdxU7Fv7Oi 9D;ȴgB;tτ=V=sO|)Q5)fxRrG̔=OyRSpffn jҬ4X>P˂G}{Iеeܤ ..Gu>^H_am,u)fByuiHekj;LY~6wzM{kc>ogP%]ָ6,k4Ljohۋ(yyMXd&8|o~L(14 W hz`FX@,v.^ ?d -H\>^[ݸC?49q'Wƛ:!eaw:>'tt9ZnuzBu)%2_t_zw_L"WT|W)Bd,ZMȒt{,K/jdyE,LЪ_^Q# /tmWD˞qTK5/Kt xm|ОGK~ubE%+=I1Nd -'KG)RV7iMi^,^hcKw3,0c=7}8Y3.,5DCdk}žr1T揿U]YY#[@d&i"/! "teߐYpZ@0pbA?xspޒKKy-\`U>YhU)M9kfTFP%RvJEPJU+↸OFuJk~RNgd0S Rbr7*ܨJl}oR\U J|r -t8LPJ`J" WTYyAZɷkWikV纇p"?U))YrMOcq-IGJܤ J)Is-,5@P^A? S-pǛ -}F(oX(J:~+>a]CP]'v'PJPPlMRKA\wJi; -,"JyJ#]IU)D-8`f`V` Apx@A( B dr)vY]-/ Ìqe53e2X Y4~:Y(d~UƺYo +S8J\YeRJ١'5kJɈ;UdJɈ;#(R^?}&R›wA|k}JZ^vA_Ro]З"([RQ*)+.g;dtA㣁HJyRS(@JRN].R}rTYrvIMLR#礦zRa)%=?+yR4i^=6;vH#Eww?Nc(}(e}q|Z:ǎRajvvr`O̸6"}b^*arT~`ASo -o>5;o=#~Oޭ >Uj+ hCg iӧ6K.ZK -M=)R0pH1b!JXRH@)nRIIHI $%DJ:)ݦ=F*E_Jӌs -3p_1Y*bGw,)voV\ܕۼU0ciRcܠƷ)g}BC@ĪACU5d!4K{c @Co &xMfBI5"hhI5FO"h(竡/}[?f)KS֚[,P^t6ؿCC/ToBtzp`Cx&59|a1Q)~-x|a's1wlf2V{y>zAWd`.cR`M̓P75춆${vތO8uU>|d{vɦ$j$loIWa2[ҎdS6MI6d|lpZ@0pbA3hSB)5Mc6s6K6÷jElD{O>Dh*gm*= ym-n*W#Ti}9>a0,vr/BmHM7&)??M58Iӈ6ͣ6_F~Y6D-8`f`V` Apx@A( B d 4ujgYխjP+Xf%S禮gXk#kfdfup|ǹXo)' vNlf VrtfYUhaFTQ6A)#$|{R^E_KPҪ*EA)jiU;Mє2A)y*R~_fkk*c MɦS(? ҁwBҾnśN>,%OjRvfGN=KJMɔ6N--nyXkSG޿B[;PHw.o짘c754UD{j;}?673bo;BN&QwgP%=ްN>>c?h"J}n^8F<}F̯Ώ -ĤWS׸^-8`f`V` Apx@A( B ds6SW$ - '3F</<v:ʜ5t7;0:Q^Zq)2/O,~Y 2SwDo6ge0gwN7~u4:jd9ti{l=gNG윮w"Y522fú~NwnD8!\T1)_¢$ed,IFn˞̬G99ۤ9؟2V۵2G[.zYЕ uw/#<-p>sߜR_ kkYn=6"XN{mW|BUQ*nV?"|ǞIXV:Dӈ,QSF*ȂK0&``6 $ D  A Ri X2i0X$2a`7,V*}c?u<˽;ڠ5$-ʵQ8T=6|m %j t@ 0 q " @A K)a,Y kb%s2ZY8c+'N Nicm,?ZwfsN9J<*YtoR5|&S,S.Ucg"8rAڳ[-ɅSn)MLzDuʸz[_G)7NY!)_)O"}k!ɶ+*-r$9+SB)r{.H݋_Sf!\Y)\1)#d&),O"x|~_,ds2S68eI`JjNhK|낮؃>cK5<og]2Xϰn;۵Fŭܺ>qN8叻]'垩Sph3%IJK[8*XBҬ:4=0#`L , -l ;Hp/@2A pnhqt>eݬ5ΰLˍ*})%8e8eu4rd=L dPYqfY 'bP«ϏU S.Sc)?.Jµ'[k9͜ c}SԛWs&BNBuʸS4rRuF$!W|S3}iUpʵJ|r.H߅S.cN{o?T?,$zs b'57%œD˖7D7jԽԯV&KV~J7l!xeg2;@1cdlNJ(󾶭Ôo;cK;棔}ⶫk0pr|lrw}F-9z=/KfH5UE\8 4.jghZ=7&{pml׫4=0#`L , -l ;Hp/@2A p.?%jD]K?h8hlOdEԘh>h9[m#L3[Ot|u/$ -ʼn^qI'y$R7[W7W̗#f*{U|9`d:Hs-V~_f!nG{P>__>Tv̇"A -3ǑYK$韓i d"X"Bo ~bCh~IJ -9b",IYGޣp6Y"|3Kiu#޵R4V]S~xC^eT` -JYgX4Xj a=rV~}ʝZ*|֖0?Sr.w422pYR>hћwA-},y̲z5:` X Ă8` xnH@ tȥL.`uyk,mGXh5"oDX[&Sq81~mlż TIFd˾oDý, <2{/_Ǭ{o69s_eg&{2lGLdTO$&dQ2W4iJ;RYsJKp!=l}wf]7)S,w|PlMcSjiۄr#a'燐vS'R8&(M/SJG!'8zNYxl4=0#`L , -l ;Hp/@2Aҿ8Վ C+XC k`M9s k)g_[e,q#VV\e.Ve]RNS.TM)FpRbl-QhܶS`9T\q&l5uk+wMĜ[DOpʡSzN9Z| -hw; OjmA4(w -)҃[-UV>3eA-dTnB.GM$Oͣ*!sSxFdCq8^>hA胦(3胚kcBCd{ru׫4=0#`L , -l ;Hp/@2A pn *n9 F'3亝%yasZdlt4Ut9NO82^s@/S> -A01js1k@Er73}Ьz"Ʃ}k_:7.Bϝ_s /uzEo^2/ێ>h+9ۻ !}֟L{o>S2sÒ$zD>9ɓ)ٙRn8s_N7i -Nx^8v蓯 g}Y]>+p c,+OgX'm]"+O?s,wrt L#Gf9r3%$SWZ|%2KZȉ.GIfyR,D-8`f`V` Apx@A( B ds*YKbN?Ycˌ8ķX,nՓRHG/oe]#{NBdIVj8,SJM3KgJy}Jy=ltGZQ #3:%K,_YTd+2Kb3K<Udĵ)?R+d4}oN)4SjBtAffѺpʼn3KRrfrjgfz$OVvv'+9GΑSToS7i*nRڅS(,; PҦ"9VY_Efjj9Ht9;V?3v|eyQ^ 3g^,3,ӷ!tY@f[Ef9Xw;%"DfCfY֘PO92K:v0&``6 $ D  A8&Q;WDDfM=NDK[,o%-}<_rxo /T&(%&3k#a)6`d]ܿf53ߕ,_R)_/S~b|{[tke;vybW VD OYZ 1S !n~Cxc,>'=Y9,1rĤdY6I|_ػOe3mkΟt  +} {RƫM|%eyւmߏx;v_'T -d'vNĽ|'2K7ꃔqdK7J=o(r,3RBfYݏ2Z2pH,D-8`f`V` Apx@A( B dr)GXKN[eGX>82K,W͚X$kbd92Sk%KgX+-XaYvSVW_pF=%jF_yJ~O?fy~$)Q_I폺)U+rmNrD'הb,It០ ˇsB=?D+@dyuE))QOtQr -]ɹdewuDo/tSXKcN'#gU|Ak{jdD]ҨFؿr=Gte0_-_r{}F,Df" ?]+\.EK7oJ.I!!@>EK_鴟,"\{D庇p>e7+=Ir.Kș)ǔLY -'37[UѤilKȨ]m}>19/p7 n9`oygh >1?h>u98zʿ%VU$QYY -ϓ#ȂK0&``6 $ D  AF_dGX%5TƖse%Use:&:ú{X+ >lf)60*[2èJJUA)Ido|ȢE`^ϪE!ܢ*:rY.3CedK$\=(5肔5tj{t[v":prD[j22@"88nA޿ʋyW2o:; _Vt/_V/^rk?Ev7_KQ,!5G,}h98y+p -ᗡ;$: /_#\&"lL/me%/S@)5%#re%{T(&N-ߤiiL:ֵYʗ<ڼ={AW=o(g{^`ʟx}w [m#c ; -vKZaxfk|iS B')<&i DY!T2ߊ2َ2;5:` X Ă8` xnH@ tȥsUt 5䌃,Sqi3q!Zڎpk\ΰ_b]՜,= 8@=qBUJPU.R*T}m+5MO-`n\Ts݇jd+w0CvmeOX; ᔻD^Sv|>mP|}!ڠ+f鯼yźpaI>5Fb,˞0%f{d_J''357SLb&_; j'gt'[R1t=q2.c.;Jq {LEG)n[ةWqc؇R E WO-9 jSxS+,_Z?{9#Pr[ev4}T\xw@{H^zh-)4):RH1‘b"L+)6RbI#NJ)RxR\I"'E$E"E&%@BJ)dB.v M{2WtiaF1 2#A-hK#gAL YPUy1צܵAAEO*bvGGǃLp]]j;qY T]uI ^VU:]LgnvUC_T j ACo||5S,=7~ڢ> K4t"!@>UWm(d=u"NPG'33$Ov3žTO"dKY6ͣu:^zCkӱ+˻NN+0UDǚ:WHqc;LuZ}d Z?~gl w5ã v9MRh^@TG9_@m*S#[F5w5J -ZcB#.I#^-8`f`V` Apx@A( B dr)EvI/8u]N}oO4q2Nni*S;m%dwAZ'cɟpy9mNqIՕ9gJ_S> V/_i4?@_~`DRm^5G[ |LPWPAĜw/2e~zA  0W鴂chVkR"&'esr+${ĔŗSܬ\W7i&O٫n.m+nAog<4Vj731v[NNv=֩z8̗v7r/ctW_E4ـ֩G(q}w-䬹n:iZ1NR['\@03+X 8\ @~  !29atӬ5,3RV=9C}$YLGNEپd?FTפy*xms"s*8Jf_xҷcG^.kwi8: 7r1'v5tNu蜪9ET;tN=ݧ9MNѝE4Rӫs)=#s\Ztj iC%Ϣs|CEkIIё'@)fR,XIKJ)vRHq“"M?)"))2)RRHI'%rҴA#M7?4* 3Mey66:PmKsTH7 -40( ``E7cH6/ kN5PGn6OڛQ6 :=w˧lGNr= %#ge%rrY;#7i~znmm>rD<)G)|ƪ35Wě -|׭mZVcOFum{H|BYx[j-zɦA$)37Jo)V*0f f -ɦɦa5:` X Ă8` xnH@ tȥ1gus~%趰L;M2ACF_\ yэy1w-+44c/~욵AJI߾W [StXӦuMsLٟ=bɔTOOa9W oUsIsk^nR;rbnp"X~)&aWD n i{jglɫ1qSu g5ޮ1}%}ĩmg2#>ŠS./\\Ju^\\/ ;Oɼ~˿?/_n+_&@>E <ӭ =7\}91'!/}d6򏜵qHec-ݚ_wvhv}"ҍ~é")y ~a*z~|eOKNҳ;cX_f:afc~ ~_5ފᗡ22t~-S#N5}/Cm/gKȋR0&``6 $ D  A RktCo Env4ɛxKomdiKݲw'3aA5a⨘qfcKN/& RͿKt? -Oڝ <9cdCxf>eLKIL&SNdfFl9GN8;~I3]_Wj[&vs5L4̾7vj`!_*1_o` t-8iEfu47`j`:`B0SH'Bh~ LW! "sC0/A:v?3p0!U0^-8`f`V` Apx@A( B dAujKxzו67L-:MN oi歵%`QTNG!ϟt`xr+yyyq( -m/ۣUs*K^Γ5Ql36g0^?3/T`S74Y#~yNm/Ȫ/<0ygݴԀ-kGMƁDEG? *LomKKh_.^}LRRVibNV# Y'3)9G L}M۟&\^w/~;(]SOR //vPWTyk'e>۲t-OmoƝ{?4R5Ok\4_?}j_֊ -}ώ߭ M^;Щ ּ6t#䢵ФHѓb H -C -G3)RH%%;) 8HIq&K@) -)ARBAʹM -۵A4;Է A SLy*h Z Z8pvhh%ͱ*9(ĮPPN tG/EwAr?e{ӕ63#/LUo.86k.WD9b#;Z!hnʾ *?n u aCQWH,!WH?IRsSS|WtԾ]PR>m9 xKӄmaEMrڦ4mzllb#hV#n im1ч7P%~MnzQ5tҨLGס˜M5􏂗쵳k%s6r\is9@> -hhS!z5!\< W$ə?%#S=F|ٲ(l\7 }7UӼ񧐆&E] 3,R{qiQ<۲ec{wڸ#3E{xph*_^и}G/zG:BOk+/iҚb2mǵdQmpjd@j~w5 ֒B#EO#) ))&R̤XHb#%8R$ 'E/))~RDR$RdR(I NJ)bWi[CJ/l3,N&n3+BګnjAC-TዶmޥprD{Im<-Ш(6MC>zUC_:ꣿ jOЦg vO^Ǣ{ƃ"{A47e nh{R<٠kˠS4e8h0塗i[pI>++d$fzbNG@yxSכ)Fi4~ -TSK)/<@oBS|-8`f`V` Apx@A( B dsou8}7gs&8S)g.,u%[V5s/q%N89 ,sJ%/Ҧ6XUX#(VrXU5R rp Y(/_ g0\묪U)R"#{.+hE7 m^:@)oq(F/MR OүtරCtZW7{qM*9'33+'G -derIJ<>ٙ_i{ISw\jԎG8@Y{0elb%3;L3䬽ŶWSֆgv.#6hSb%9kouQ=sF|kALث82W hz`FX@,v.^ ?d -H\ ^ռ`7&P"WțZxse:JD(r:x>*MteD@=tljlwή~TS&. ,O6;jy“OE=~ʲRJV=}k}ece1bnzI'!bvnt޻9~~G__hKRH"hEDUdRCGx9><}??fpv=>ej)KcRUڛ:Ů%,]HYsHY#)e+@@h=0#03X`6`p'px@!) 1L' CU"ea46յ^?|r{arG,X{;53>ƺsY~fe+0qE;K# Jg42J^RVe* IYnp]@)}4SYk;eڽYF)+D~OYRN\PʵBw?=&P: L~ @@XɒKzԱfV,gCkdLY{nuyƗwPɈLy% IYIRU2Jd ROjӶ2ЃӔL)HNy*)/ɬ|G6bӔ=w M˂SWk# p uیiJWBT/7L]OM$Rtk OGqOz}Yi5Ni{^amihr`B^zwQgMʑ_h?~^[8ޢ /z^a}i;0-kz^KщTU2rXs^3z11MM:TCC*_1ΗG{U#`A~*4ueUaIyi  j4$hIБ@'@ f,$Ē`%F N$$xI  !HB0 )$DHXhT -z=AkyA]C. 7)kƲdy8Hn:9 r3 |uw:Wb[0\XOޗ%Y3lLѹE=44 $x&$B ߒb5N(֫6SV4t}54QH: )w}Sm Sm^ _&䓼dH-y3]iHr2=I|bbB_\;*nSL| U^5o(5sjBC@CoAC w|t;rok 5? M:ahCh]h7PqYEP  kzw@C5qhhOKHHP!AK= $H0`!!+ 6$p$8Hp&'K AB$IH!!Br/dUN 5)KfO!Bq42א4WMK=?=Agk! -!+9 -v& h / ^\OC'_C:埼}\ҡeCGMЃ+zpXl>h誅y - 5L]k_.}N A_.SkqJOW}K哏޷r|ϕ]$J22< !+UX[m ~Pj)U=HVp*@5-@ L , X  ܀^AAA -|(a}nitjy12vSFVp:$3ٰ<:;61qF(e&u7RJJ+fE) -rCzamu:>; I)^|ߐm|b*qS?tb_jsMH]qg8@. R/u}0ƑB}-U6&E|'vKx\?)-OJHX;sebrnUc\IYY8M 4"e|Fn,] e -Rj,ݤ1yY,%n1oAr`G:v[řl{kΨkD2 -RdAD,/=RF w_V:**#˶DŽƤI*@5-@ L , X  ܀^AAA -1h RNSOzDq1m8cq)޼4GLxwp|+N,t$#+$|IJY¥SJ"3 ~%d ._Vϕ2 oR*Ǟo[Q.U#|W骍?ѕy/RM~aAk91Y͋;2`pu8[\Vlg+D)H9=?Ơ(e=( 䟔WT -hЀz`F`f` -l8N< B R@WcTS 5ƪ+rԶF? -X0cg-D) g,:Z>}ᷳV78a-WRRO3MRNF)KJMJSt2JCʠgM )Y)Eh -}5Ҋwc-_v4188I!8%6q' A/)J?X.>z+TJR|.I)(23L>!ճoy;?TOyxuՋf(N[Y GCq릥jS::I^ܞՖ(=Pzcլ[ц^8ǘN֞r݌uv&o`ÌorX Bv]$KAߓn\'4)ߓҔd!A*=^6/ɒm/c_bqMS^Snq/^SN|1pJ&7hbenrlH6ryJ,0u5)ԕ}?¥[M\g ȣi_ĒJ:< Oi4O C9V$ }uji*.] -ڹyxQ_Ee?gt4--GbbȵʜmF(s -vs[;Ɨw\;:@oU)CI)*@5-@ L , X  ܀^AAA - I׎NqT4N7хl%/ i3ouos!QΛӛV.p s6l}mS/4l;r߆|Ţ %-9 fisIEfmʨ>[[9#SlȈwO}PM]S" F;}HK |S<5#\{GIt1# K$!d~1ӛ -zeqՒ4-jCMɫZyxiI -6-.<1tiNDLV=VK0k+q'= #G(J.$-UHZN< Hv"iI\!Q5N*!%-xEh X`&` n/  DrVUR- eэ2t%ˎ02O1F|$-u$iYb,78&g%gq[Xa|!GD}S8r#2N6DXHvvuׯ*B){II9%Yx_OZ[9?Pxu˼8mIG3ӹ/lcq=Zn0\eq(/w8B}ioW81Ul=NRK':&CC7*BU-PJsPJ -^Q(:@ +;8 |@" 0H#0E̺[@ҔRbd[I)g%(\+7empR^VޖQ -W[SM.|IGN) 4ty({,e(@qW QɑRnMLʫV}K=KIKHOLfҳ|2m4] t!5=kkW'gla,% >~Z-|)˱wbHY?eRj}i0]JY*@5-@ L , X  ܀^AAA -4!VPmd"g51YƼ\ng5"ggH2WMXjyfݕ]R )儌RR$';^|6E**^I)H ]Rg6tIYXBqJ(栔PJUО(%0<`RXr*y4HBTOT V}KwU'\T%3|B*RLJMK I|چ.*+JXtwzWQ5 yt<}x2N?1.jEn0/řsK߲D` S{_7 _mכu "i|˧bZZT)lvFKoVdSM( -Qռ{vO -QE42Jc{(l1/2ҋ2u60m 3y ePQ/tHʠ RM-R -UQze46ʠ|Ε'ޙ~>)=ץ\z{s&_\{g8应K~~/N qp<ܝr5ק_Gy%iջ~=)Wd{v]9K\ƻK1?1Ŕ,v3Y漰-wd}.Gv2Wɬgz㛄DI(2WjjeڇdI]O%Wo݂Ia -%P5p@ t@ b@,0 px@~AI ЩaZ8e=QuѴpD;BtD_G el$-b!Nbo'z*%|3sQ"TGI`$mI޲EN-GE?1?S_'+'5~Hcqtҟ}?3J?H?J?٘YN(Ya׵ϱ8sZs:&kU+蝊uKk^^Xz8{.̹߸8Kk7]jq?bCL31ϺP - Q T^t;ݩ^/LS>5.v{}^aRzJKZ|+kJyͩ-u3m/6^ճߤtkzzyi;~-Wn̐o6V0A)Gi~d)/q[ökǚv9F'q(eJi.xX(+~>R:cB)CoB)S#PJ I)0@ T@ 40 \@@Ht*b0D9Ϫh۽b jb',R -b-"!֞9X"OA{Dc$ylbX}Jϱ+k])9dSJG~fJ-ɬ%cR()ҽ9M)gF^RXsJsJi J=_u0b T)#P8V *2_)J) 1%WsغpJIO=bySR g/. >ѝ<WqX^{VR&_q*栔xei7,5GnqkFy3iu3.3tB)y(t-e.u!l;<ˑ*RҺJ+4ߝ(N t< 4dJY R5.)ST( -Zz`FbX؀8 @"HɀNSpL<Ω29uy[quvup"ΐ8(e*g+㜣sesGzN(RqS\RJ{YS"R~-)esJujOU):y{Ryl(wR\vmR %IQʇ #(eSU)oRLPʃP.f{V RJR\nOB)N 2EPJqpƔM4_zZz3ݝ* JI塔4gjSžQJoayA3;~Ssn)J^'c=)SN>&SU_~Rh d\k:_u;O S'Q.=@nλ7[fK+r[ }p~N(J*{)_»[eX_p EVEpWj~Q41EH'C%TtPACtAOtC,tJtpE:x A:C@'ۚ8Ȕ&*TBf6=%juA}uА4%BC9PO8h;h/Ht'*=;('eAd 0H&}5uR/IIɒ.LECKUPЮ"d+i$ QןJ0[O% ]!JT5/;uH`\#4]w;,ْ,)8분',ݿ!1 />%)NoZיKCħ%%aћACCg}5E6o:n&^$ˤvkZ³-&mndV/Gиl2d)Y3w9o&K'w%6n{'q݉ȦMy:y[ڎĬ't/ڎ YEd3P&\l0E`h 8:`1 X ؁<  ? $ TYEaZ9eQ%2FO:%!o!lbRlu&s!Lo%*!<?$F}]RJ8R~*)R?R޷R^-R2<-9d+#fK>+9)kNb͞m政EpJzF>@hsȂl#N )7p?~6gU8{ J6o]!1xNOBz NiNit9_27SWMܚxv玗sݺy Ȗ1 [<ӜɒS.4"[{y.G78efKyȖz+[:-.)#pJ_ᔁNiSZ8NiSZ8NiSZ8NiSZ8NiSZ8NiSZ8NiSZ8NiSZ8NiS>-ry”lTCmt 圱fKY)cqQĹZ8~p:N\`|l鿤lkN)v)-S짥!+`^jIf>[^R^_rN|I -S^nlK”j${ݗkZ SJa(~0~zRIXUyJut*%œ#PsfҝǙ;yi\z75cRf:,_wt5O:E˼aJ&>em@RivA)GWaCqv3c/74,CSz^mYJJ920eaJMBûJ=”Y)SB)KPJAe{| -%P5p@ t@ b@,0 px@~AI Щ%edrajDSű}ig{a˨RfiRY 9[G&Y S9 '#b-YWʹR>L_m”"(GR\RysZjcKe w*rI5ݽQ8I){%JTgj׽gW9RĮ-%C)\ۈROӫPJ| ^a 1p#rC8s5]Ot1>,=N3RD1ޠ`YRB7*\1ܚc<;s Pl[ 9ft9Qʱ|D),3+ö]?ӕRj߅Rog%sPÈRhB)CG%` -%P5p@ t@ b@,0 px@~AI ЩDPJ'4;HnN;901c|RfuqIqs->tqvN'Wr+rI)EP -')I)EP -wZ\g3Y[암W^"rI)J1+{~"Lts)sϯE)_@)EGJs{̥mC7뒒r=3>zMOSSiN'wxN/NtR07F)H| -}%JO_r73+'jyUcI8kFy4i+għ2C^8gJRJM{ֺ'¶ۮw(jYzJJͪ_S - PP `@ 3+;pxD!J=QTUU-Q'rfnh(O֘KLGYK]SZYxwYI`ݴ+kGI)ߌ?JJfxORf$Y^OHJA)JRFPʃ#|$9bgQeP%eJyuQ -̎CLpD)S=EQHIpd8R2ҝn7p|'=ߠ?4Ͻ,~P4;O/Smu;y2My2\4mL.߾Ofil[c?xDf_]֪ߐJG?:dr\֢oX)5B}y2wF}y`ay0gqPOq֤7s&g3AW*hA f`V`v. B $:a,۔ Uvu]3qvmvv}0o3.c6tlovGղ?3mf],o[oZXyX vI~a""e&_,_Ȣozd-%/_>Y&)dIQMBSmebAr0u[}k~~7O"d}E[!oZmgq4+)N;3pƅō[n~Fֺe檛eWdLeɓ2eSm2boCO4GfGomqwV;2]q0~ow(b': 7,?_'yk~srGSO)\ O) -\k -owo4>/+GVEmyEIe24 tPAM X:ptAG= t0!t0B+ltAx:xृ@~:CItHlW"' (U5g5=j;0hlгtz4ڗh[N':]sA~!SMAq"ѿd%n iE4dQICs#hӲ s[kWidWF6JWICaN@_% "9/Fs~{{O ٫[$ ]ꅆ4EhhQӫs.ă3гȜ[=3V=&$) g8#wz SL'5Om\-9,o?=9U|TT给xeu/*ߤ/tkz^٬?\AIv[7O4嘍 昚{9u4{d9) ۪m_kdNmOȜߨy+ -oɑ&S -<̩1dNOHLQ*hA f`V`v. B $:#DQNBNY©J8u7LvpE-bጭ4sjY`G mQ66 RN"b@y͑#R,򡤔I)Jw[,=-N 7JNn-)_s$%9rQ+Suʎ["6{GSp}c1NPӫmr{ 5$!8]պpB_/$t8%>!)ƹ_S=;#%C[ڌ֌z\7+ y|:״wb5ύ围9'x]Z_`kdSQ,XN&Knkɭa[n]pJ{22 pJɁ±bΟ\ZTvN*SOx)@@X-=0# , -lx^ AA$O[:b09DYDTD=j [rD[DtDI ĸȮM.֖OszjwX@E~u?"x"8%(9)r"֨Ŗ͜$f$|!]:WrP]޶qkKhNĉq68_Z\*rP8e NWũ3ABL௓ {g)q)pZSLIMpzS΄t)i 8('=nSJ*-HESNEA7sWȫJ*LܚyLڢ;ܺj}Ge\9fqJgNs&KN:~(l`]8^ڮ:OhT)}pJUmxEr - PP `@ 3+;pxD!2QsQVst\ьvr5NtD_COSLk7-8qLW+O $B3gm|mRU"!jW.9QSHN9-?FM^)Nf )M^ZVxwZbn/z'PrRzIK:i:(頢:t蠥z:`C b`V:`.:tK"t!HL:ىPPԕAMg7ڡ K h(V`wXk}4|Oozⱀ11Pi~۟+iHACI:WҐ,.:-|A4nIfȑMtJ"4cICJ"4yZ޻>bfO'O\^GzrW3 }RȖ Tbq>dKﵬ{gIД~]2+ IHOsytKKߘ-~ߔ44U~L64T -4 z hhXm:n6]hzءli?һ4BC%@CuGehh j̅FoCC /@CmePQh4T>  uת. > -x G:H~ G:H~ G:H~ G:H~ G:H~ G:H~ G:H~ G:H~ G: Af,FS v)Զ'K -VZ.*Zm-AH1 -#Ouە(ź2(nZPjQף.4J:'.8-{_ݛidk5tt% DH~!i踤 /"hSKέoࠡRhhH -_ }khK*44t!&tI-\z7/FjQa<3_,e2l=]ՃF)y+T|(oigpje??$Kj{Y*` -%P5p@ t@ b@,0 px@~AI ЩEǬQN+9 M81 ]hjΚV}s WOڛ YXk'+A)7HJA)7~|/h͔WY2&G0gGP6I)IÜA)N&֙bq0?b.HJ& dVdt.?`\l -2!$~SغpB10 BzO8SN7LH͈w8^|3{ǚ֔SxixW{MgBZj32ĸ4O߸-o -A "MT:T1 = M{|/49NE<3&,~WM>`R5<;s &mK[#hd]2 !NYܼR[lKq =vpJ1)tMрҀ8h,_8;>˵R)*@@X-=0# , -lx^ AA$dzB'LQvUn4[9 ,ΐRRTk;g Ih!*7r!%B.'%SSJ_I)gGp%얜rv|>})6=kW޸>FrQf_Э9e:BWrʛR3!N{Zz9o}bW\q|ەk)q8H\ -QW,NiUkC8SN㔌TQL 蒵KG))M} BZ|octX>tq#/S)n4ݑsx5TOYiޏ84aHy0Sq,-P9;re}1LQ*hA f`V`v. B $:\B>GթQϜ=ph͜F1jss[~Lи49wV#hM@frJR,ډt_9,ډt?Suʱܳ)Ep:d뵛%QOtKm8->vfoH攱Nr#o\/ -NY/n!/9BXOS -IqNEG>+kUԥ^z*]9n]ͲZ?n0i6 ┩E,99naѬ]; Ifgv"N}FKqJÈS -oER}rLQ*hA f`V`v. B $:%NQ1Dũ9uy&fN{<] 9zZOYmOpb%gaNy\)HN1FpJ< 9))Ko=;zv.7?Lf믎x #)%KRw%)ezQʼ"HUGkJ9W S&0C)"[ApIxb W'1sʺpƔoMoH9}q)SRRqNQqR9,/o"{M)H}p3YmWuiR-n~䚴snbZo0Sl64d4ו,w[ö{OrU\52ON2o7 Ad*YZ,MBdIM'fO*hA f`V`v. B $Ո( - O9DUEԽDSEeY=a'h-Vq5xʈwHM$Py9僫־fˣ]qcQJY*\2RR̾:쒔>z~Ѻ~A)ѩ*ew]ʈ'tLC) kJɷTrW^ 0\d<22D){gL)q_]{2ܴ(=nT񹝢ǛLOp q|zXtni8,/7On/WmR5+'xnQJ=~ķ #+D)#;ͣ,ERmﶏܺ1=QJ<,]tr8AW}*C)=RjhwQ]zTR0E`h 8:`1 X ؁<  ? $\iwQ Q4rQNyj 9Mj'(N]> E):ElY}8F8ˈgx;0J>_&+lI)JJ/YozI)A)oOS {pJؙ=qNqol^R|ϭ)%5^7S -l=«*o1{nqk_v$>m&m#R.;.37 1eb*wkL29ld.DZcPJ%=>.?WOKB2*2(۴ m(eq` -%P5p@ t@ b@,0 px@~AI Щ8O?QUup#D{|@;qroJ-vGQF\y*8o+A $M6=ik_*К"(A)J1e\i}D2U|"E)oHJ)YI)oHJ)FP~pJM2e$ Jq TE)[B( -? -Ĩ1!S2!(%>R|:Bӗu;q|ݛS3DwB\a@MzqѠS1WffyeC3+1yMQml⹒L,nZ֟ll,^ in2Y -{öŠ]^(rw8C - D)C)co"JiRWl)@@X-=0# , -lx^ AA$O#%!1$~'l-ꉶ膉&qV:X[m؛cur,To?ڈ8O5$0y%쓔$)&I)!ѳrؒٛl[?wʕS^nG>2g2m;U$"@+Ce"ݢ#3?Sr^ 0Afj)$u̅)B\RW1:T^TojZbJ#Y| 'sw*sl;\S0>lutMB -N)z N;$9ST( -Zz`FbX؀8 @"HYjiB,TD=#ܱW4}g" -Zbl^EU|WVwCGSo$(S.)?OSVF?rbkdwRHJ\X "(*\A)o01L\P"g)ZA/0@)?ӫ|a?:B̥Ͼ a\ΜRK> NwfS.3΄xgqJ!lPJva`tL:{~(v3/GxU&gxn6/ǭSYfcQ9f]U|%mA2۞U1vA)3%S*ކRV -5 RN{J9}csPJSLQ*hA f`V`v. B $~ts6_ũ8uRV0e2q.TbJvs<O=B1(@[h[R1EHߒG -Sd>{cO9edV~u7LٰT}-B!)a)Ld*FL}Z{ V -”ںo -̶wVfiAk”/7)/{g);}|oJ;=HlL^g:-ܦ'v!L{wց 73 )7ԓ<[85围i~ Ƽ1mMp=;e&`Hvk}rthkS?,>W/L[s{ڏᔮy))0@ T@ 40 \@@Ht*mȹݖ_Ωc9ӎ]. Y:%3r2,sr|=GrN#RnO6'KaO -S+{M㷸a[R]A?|R?#NݷWŽSrsV8n8e>uS0E`h 8:`1 X ؁<  ? $LJ n>^ܘ!N~W9Y1^9*@8g湾z弧_fl/2з%vݿ\UaVOm]c/zzh[ q`5┾֛S^Sey8ofJ>0@ T@ 40 \@@HէɭD9CTShBDWѣoq:eS8k#ehxwн)Kw -'9eR%6S!9,9N);ekhgM\ib2;^)%0MR.B9%YRR9E|Z”͑SRnRAV}#w*&v3=rh~)]grS - !8x” -ىbݻpJSM}S_ߔ)*@@X-=0# , -lx^ AA$dz7Q( ּZ&R/ZBKKRBVE1@\'7oXBy$PE6M}N.D;MUrqKӴ[OSDDdի"{-)\I)Q98WRJC_׾x8p1ams[r۽|;\^R^ 2BLu_!jJ\tRqbHsyҜ8Z qNy0Sf^u.<ӭ~gېgҶfu9j}ɆOLNZMARvNsC&\:q{V;v9R*Zs[ -]3QPyJxJzJ}JR - PP `@ 3+;pxD!JQtL\dUK,=LS V7X4k]{a],zJr= wNRDSlOz(EO=gEn~|#8h>v$}.):-{S&ϟ;G^z*oR?`.y 1;J1dC8ݢhS ǧ;43%vRTMl<-|7]-)ej©Tʹ'yU&u2'ynĤU&ad>Gv{jMu𮰭sjݻOB)Y4); 2!(er=B PJNgI)0@ T@ 40 \@@H D1D9 -9p4J'3qaZ-B -w"qs|!Y&)"Hh׬>.)^R!ӡ g}A3 'Dޖ7f9d٨Si{aw,ӅgakS~ ? -L>dv_G?{-?=L ŧ3^g xg837.۸\g׾Oږv̩htx:gwlYUo!d>w^fl,0,?T4W,G¶kOr.=pp=|+t~zt[jY0nS:$` -%P5p@ t@ b@,0 px@~AI ymbezS slqNW39 1Q c9G%:cK|8gIN1HN闾fh{hOu%LͫD[H#jW5$K^+)*e>!w;ؔӲ{53(EQ jm[Oos1u *%~T15&d޴ gJaJ:ϧ |(WsXY~WV<«%^3RΎLxg^)Z=m+_d;r1%VRI)c[)J!)e,R~{P -t2tk#|ky-q9BX<>g 0 |(\ZR{gN)Cy2RSBOKBŔ)d>Ս-Nhy >gyIۚ֕!)μЗk6vcA)G v۪LBu쾰Cs/B)/(J[DS|x'FL4C)y3|2n(%%)JJj,ĀX``6`</ @2Si$\Œe!QկqbJю.#vqC>=QJXwO-]BBRʗDۚeI)_./Ѷ#dko2x3=Vrʳr-SOS9i5KzlJڏ”hᔿW"LMЦ!}aߵ!1{WS<Owӛ0euk7IHb\B3FP8F߾Btn&9Ol+ȕ!O/cnϕq-O}[pLW|2Ɖ1CE[c3j;Zݼz2[]2' 2G5w|Q7S6;BP3{L_4<آ=g lzr_*KuA uM/_~V/*;/_'g5 .+ڮoO3g1Y f'Y? ᓐ&13{w}ﶻ{=Ku~8RJ)"CVlRdbD!R-""VDX;i!?9cdG_~Se?]sARuߢxLX$aNJ%iTg8Lr'8 oSm2E&\@z,eгtBRzD}YGRzBdgгwg2W( 1@@:  , -N</dQZXY1K c:X `U"VjkRV?CzgeMu,;ZYk?k]w=_c-fDrA0)`" @VD0UYyT~+ Oꟷn>,|CBrND0w9$4-"cU·K&A0 KMA0hZ\Em^򩏲ݓn]-Hm<@*GoMOMHpaq\Nt.$g͑'9=e|ꈴ3),NdhZ_yr1#ee{hBs"cbM#hZ*;LOMZJDIChZI\{Fv>#VY4-#ii~M˙w(J P ЃX L`v'px@A2(u%q/xQ=E@Q% a+ix1 P@b( -0@ 4@ t@bA0#0Xx^ g4d MkOͨiUUU>6Bfi<2FϫL4;"33*z?H{Jio%Px\wk78_CbNq|m+fa+n ϮÂSfEF)G=qj>.ߧU =: )fn(Z/GY/̣i5:}-=6pL2!$w&|G iZv<0奦Ͳд iڤ(|MܣU#hZ:6i|r!+Eٙ;|5qOK Ǝ,vR:+}zIj%)םԙw5")_I7;o%i`ljǚtnaW( 1@@:  , -N</d=>c,iZ:XeYU2VfvFxܹ4lfYu UngXOm7VnZn)&AD0zA0;$F*MK_W˩`He"[e?◵?ێ~%b[/{~RϒFg?S?嗵Yz~)`ѳT%SߚU?+8%΅&%'w,'%%<|{\|fǹ/ᗒ -\\fYl5~-ilؤ΅_ZJwKѺMH4X32jH/ґP46Xy1Դ(<2~~@(+F(*_H(_rSKK(K1Bae2@9 -*5-=qX`V`px@$X<ʦY,64fYUifXYjJ3RJf6g>~1\`q5 u% ~C ~=Ȅ;W/)k}J~i IPmY~nHm">߶*~~aq?_ρ_6`~{3B&ړ%/Nwԭf/9|Տp2QRMII4gBZKt=snΖv<#A B`eeU 5Iˇ^Z1B+iU./޷\h;]9o% 5_NL(d2M['h{5-^^}}+cX+Ą -SXֈ(e -ֈ(ed"F66zr_ 5?UTPgR -|֋(elUn:9<dl)!(e KJI A)|nNw(?в|ЊHtc$HwRns_#\Yq;32piCisr6D+3!ёv9 O=n{CMַA sOp1W\&*i̋WMr3ڶ= ClIR7Ysk,.2Tٲj/4g?#A)@)CPJY?yNP -Q( 1@@:  , -N</dQiMs^jfhubS Q a)ɆirygUD)ol+' v:vBQʖUQJnֆ}+EQHW)8eFdeFpʛSfDYf -Wc -KFu˟tN8AĠ^/Ny8ڔ}hS6"e]#\6}#sdf"qC6;%qAa;Ցv˝2i^%4dEj4'_xmJQb~C{S]MB;֍6%Ϡ54>T~0mS[- gYN)5]d>8%{mJq}|ISiSA2'yLEp -Q( 1@@:  , -N</db -+F^NЊiZD&ҚaZG9ٽd q mȄ*t۾x $}C+ɾOpJ"Ng)S{XS'-ݒ͹+9Ś7cX/}ֈ]*8s!S/\ q ϴSS.a0N)%(?T AjS&Sү}8Tޕ`K 'm.> nII\8#&a)u淅kJ.d%}JQW|)#j99?i -&ڙϾS =)~8clDzu/1,/)d K29gMr9>r9spq8< P@b( -0@ 4@ t@bA0#0Xx^ lFvS8'{Vg@ki]o"'aaL5 [N[Fik>coasvO=gho%fV<|h)qʝSjGcl+V~|#XOCS>Dt) }g"}Jzjq,2cI#S }d9L7^)OTdJoU?ut\¥ܩv$HmnG:HL_/.]/,4MdݻTy;'SjJ#©ЧdW/vh?QhZBl~؎#Zstvpp :K&.rXSZ/yd_YN魄SrɅ˝Wfer@ TjZz  8. H!@bi[\N9ȨjfV09Z[Ci>O9E3l>c=j5IM}\.)]&8%"8"NmU7~#rgT;';볂Rd"mA) -J)UiSάjKr{HtrJR4j[t-۠tDG_+V4Ϲ\Ia[̴uqRII&%D9G }1~Ŭ_yЯ*3u_6u >} CC~ӌ-[*׬/{F[}}L>VE4d4+h"2Jk#_VZ]HwҷE4‹l!-}[DCw(͚-_:4J~;p -:)Ci{ɧHK!-m.Fgs(4tO: WFZef;l4G-p<t\h8"-=o5eQG -$2;&Ece\9еI=}@~zC] )ؼ܇ĝzia'X1e͗$يYL%}KRyԃk\^-t옔z/wH}%kwIyJ -A P%PhA,`& ;< | yBB wc̊rUUXSޢcɘBL^D[Lfl)X.37bv7fY>b䚽ˍ\J/7 -~BU>vY+!խs/7e-_nUy]'~c$[˓z^d#ڜ]{Se;T0m%~:˻t/2e&7R{ŮR3Hw")?]_FWd#] H7< P@b( -0@ 4@ t@bA0#0Xx^ 4F VVel~"UγdՓՎȗG Mefv\f f9;:[YWz~p1 W`. ~a._PxQFixXO ~:s!F5ƥf!1 пV)<2oxj}q+'|eI?H%/o^#\?$E+JS?fKMuzl.wG7vdƏ^>{\;PZ͗ڟA9D~1튒? F5xy S׻]]܁u1ƓcˊFjvI3Q#"F55 F59$bTgS]B#2oQg8& uɂMZbyh))rRbHQ$EE -C )ZRtI%%)FRLXHb'#I)^R|H LJŎ/kS}% |rȯ ]>M_U7@C$FxM#>gY}WyZ#>)uȷb+OS"h"Z+hH"h"Z*Fw% TRm"zȱGdJDC44+9* ZDCj7_QQܚjThB2G95%̴Ls'$.5ݖ'tOZzXts?I.?Բ39Y 4:44| -8]17 zIJfݪ-<}Qٮϝ۔}VghhnN{d)[RyQ*<*j^9 -ekNJ<2o+|Oz̯kN -l0Ujep-`_q~gߵ~wkŸQ[_!8k\CC+^Aҽ?*fKM"]nWe5'x6q i[4CC'nGH[s{| Ro&S7WW_AxBmrğĽ XYn;uݮa.0-Ş6mˌJ5=̣5)ծ8E>Ԭv]LMڕN]S.Ԍp^mZ.L kSmV䟯( ?fܕrMƮNvrk]W=bGx.)NuJ3#Is;32)N1=-љ"&9S=x)#>]^oXUt@uT^cǏdU냻)::jՇ(̳YRZ)}(,;)LyyDe1:^Ut9TLSOeƋ*~`}y:D ]'-֎GH3/PrWM -сgƪa:yBgIN&PАВБГHHIHIXHD"M"ONAEB !!H$$|$$$H$ '[1%~uS@vu >};3tE>S<#DGoY}{aQw9/Ny\V}c-[~'\xOZO=ɟz?q ߙOf,ʟ"?E)nO1jwVU:ʰ8T[\-*ݣ"Ȫ-^jK@%ڒM\/N_MS\r'>֨k][ɵNS~gP~gەkڵk5(zkڵkM'J"0_I -zgeD %&2Qf1 fѥ SIe~nM8_P^CL㌋Kpw35Mr;R i)ghhkߟ&_?PǗyd&R/s |t==~[(nv+{(.05/KwXwD|{DT{A#]?#ei}p[,\/çTB %ͪ} |M'U9܈=ɹ Η P X@$ X\@  | $r*sfu%+5AƛyKtKqx{:bXO'/fJUv8뫎_(/APb /lh6/~/zܨL֗|%_+!/w?[}_CK_4^}5^r~d.|;Aປp/* - DAbjL )x QLg9P,TX׹旞` 2qg>Yڮ_Vj{)oa8ZsKgK;"3NL/C0=b73r > C0c䊏  L[ P@@`&` @4V;p@2 Hɀʜ`l.Vt6m9ifY` -mL-o-I~跹xtATnlޗFƪ1EHt&%(z)3%^HIS3)ge~ӉetQѵL+hRvm;K^e` RA4HCK׃*ﬢ'vX@d^{ UdČ\k-?%>1Ҹ4H9*O2UR#<|*Kh:W 54H8_0@ 4@ t@8`F`f` -D`<px$ /$ Nxfk)_jH[ o/u :^hE+vJ-}30T_!B)~ٮ/Ԧngڍj{>6_v+ڦe32\6/Lewo~ILlӻAvq?X_KK[xQ/K0,|C_&EaE91)f/qN93>ř) ٓ. /Zۚ_/?ɔ? -T/ Kc d@`*.0 -~aY6D1_PH`/'_:N<=K1/OAyaV}~Y}KK 22 Η P X@$ X\@  | $r*<3=۴0V_sa7a8B;kU^;flX4nrb6?/v+Tr^_Ur^'sy/_ mJW\/1_._*CXr`OP?xQ/C 6ݴ&5Hİ~ȉ陙qqN1EDxegjLLMw 5P-vPz{n:n݅Pduhh$ o: Ow@l H~y]m8Nshh("hEChH/>giF &^CR4ro٥hͰ42Ϥuʹ!ǁFLFw4Ps\xoڨl/9 - ~ʜYhu79Ry81]tδt!QSȈLQrgGnj[Z+sW/&v23y=udx+xeNm(so7P L=ϣ9ױ#reNGܞWvZs8j3ʜջPt9rT #+*q2L32ʜB9S *_=(s*ڨ |i5-= D( b"  @2 Lqz5V3M[bj#8ΛZc-ER2g!:O'yGA/= hT+zb}9?V`XYKPKDn_rœ-s2odmg +&_nTP672 <{80ͮ{E9߸Fey2I_>~K/As/hcIrM8{~$y Όt9qSK B[HHHtKg9hF s5-\F}o'SFRwQu=۵sE82vCa]ϒy)i]Z_^_z -QAU_6P%e[@]`ڨ)e:mTuYU@@`&` @4V;p@2 Hltm%v̦+6nfm[rmK#9j#Lw\=6!鵉4lKxo9xk3w◯J˅aɨ6/73 ??tS!rBMi_Qa`RmZOy$޳~K疾xEL&1nM8{mT\Bx qqqN93#4#˞3҆6jjFFUڨ)uf+K; ?8/DUАez msS˝Sײ,ٯfEe*oCn* mTd(ڨch:=DfKB`\Ex$BZEwX ih/=KFU둓I0$$4$$t$$8F&f$HD!a%ppHxH$$2 /  $$~?SW75s~m_7׷%$`F}檀e_ mT)-Z =~ϼ_,K]* j^ѬQ4DАEPM4Mзο9;ÍzIZ[.(B-hSڨÿ \8.jE,IILL -4^1ẻF}8ڨ뾻&5 Ź/ʨk2 nmTFb+Bb|I?CCPML(} 5O:{L糂zZܺU۲$&[SqZKhKё%SQ=U;˷ƴWmM|-t,{?jx~JhoLU7HC(o)TZ*X=Tq\AgS@@`&` @4V;p@2 Hɀ Gwq0g5q& 5qE2zUws|1ιXp('Mp$-|gr_]9(RO)š(P+_T)}s>g{_NꐕMrGGCL@](ewzw7@]E3bRMA/*&/RR Pl:9ŮǷ}osW݄11|e'9=dJS)OD'ǝ?ilZYeN-f=.tN%yjm7Lsjai8#rQtN os̈9~lUtN%ddSIk*ajvPJZ-Aނ7ցit,TڥtN8_0@ 4@ t@8`F`f` -D`<px$ /$ NS,3gU7AvЦy.7ETus%#3]d͸Zbm 6ϜM̶IK6y}G,_Uo\PrAEPrA7dq9Pߨd9|Y 9]\Jr^廊_VJB,ݔ)w!W.f/wLJ ܸ%GQ7wSUvrraM8 %.55=ՓN1-r'8DALR33K2xj5,L^F>,/E۵'ᗺ[(e%<_&:wD.GD̈Yi~~i~~? {~ix~ ~*'_Jޅ_/3CK[KW/ :0#03HA ؁<@@AHTx1t%6}^,7k3J4f%; L3K:oWbλlBͧWOݶ̩|)_D/j/_ -qsfEkfÖȔ0_\o_|#_kաZ]S˖C_62/37H5̄1Χ}o{|u7E%1%1.-Qp&xeȲ NfE -UEvR1CudE*{̼!~hOv1Z{g)~-k?KWN8`] C~{wψOIek h* ]BC>ECKBhȷ)zths4tF:d5ԥ\Ѱ)?]gs4FmԖ -jQGݸ62׀2'n &;LpF9nPb|8tKSLOw$IN!%#3!3>ޓ̙n?Lu=ypMC5נQ44Yڱ{ҿACGR\^c 4ACyY}dE.7BC/AC*nZ9Od/CC%44~Sihh)~hD%44Y u/ACwP틊I$jZ:z  #  3 HQ$IĐII8HH$<$D DDDdd;}t8+s\vP76Lm~2@Ls7lf?볏=W]@{b}@CWذRM*q!JBC1 a# 4'w Sl>y;:Chv97Au<蚆Ah4t(ٿgikZVJ -=P] u?0(gY_ϊ,e:j ꃆJMBb[ذrbӤ<|eb*bhgm.v]3>>i+O{ F.PCW*adžb ]vl(vs6W)޼TE>shS7VCj荰hWzU/[4䯠JTC?4KBC0IeAILO/[wΚ<9PZjq&hʄDgSSE1!C37WԣjI~mmq'UfVԵ͂枭ln_ -\wVC`Ӛr -ё3O?NE5n쌎ٿm]Kk>>NG)Wx>LE Msf(ooj~*05ZK^~JTq4`hp"A1 -x`H@^I Sc&eիzMgu󬾔jXXs7kgIB)#zk?7Z1ºrSŠT9wErͧ/Tk~UQ p5e3~Jrr3!S)ڤ(e t74)J1]J9uU 5~ ]研YVb#RfοK;ǡُfб&8 Է@)oB)C)PJθ" :0#03HA ؁<@@AH%NVjrYmi܊07֜Y:RR -Y˷ˬu\ӣXiX7n8f)rӓIIMܼ̐0?0R)Ns)zFP<(hjk]n8 p'P s>Кf]9іё{ᔖ˶tq[Nj KP< -4>T)cpJ7HK~)8 NYSfjΧi5-= D( b"  @2 2#,ĩWYcu~*8C'kgM9yU}e)K˗iѣwͲB?g}4e7rwcQ*SU}B8kSV =Y\?|޹2YzNQʀrJJIWʔ^E)_U2vUJuY9!;LRRP'eo^r8W`[*ʔ|M8{J ?+'{< )ӜnnT9=EHD -cPJw}Bҡ5xI7f;u#&hv|[tDw=,p7o5(K՚$s-}PJc1Y(\#zj)-e|̵ʊ(%J) JiR -o&^/C)MP -]v(eΧO=:zt>|ԣGSΧO=:zt>|ԣGSΧO=:zt>|ԣGSΧO=:zt>|ԣGSΧOitQbìնzXA˙X>R'J),8G/|3NJ͜wm\\!SNnS6)ގ3r.r8bP\uoj\X&)wW{Q)܅֧TVt;t g)3B+RZ${rFSLw&J([ԌLO[pS Jj\)~QXgWʫלgSBbʌqo䨏qΟ`LpkdM8uJ D15ÙI^y 8332Q}P4&r)So:LBSZov:c7Chw#Z -ꔆ\8%7:8xG[c% ڋv:<g}>}rr1>)}Z;S))N)ҀjZz00 Q +8 D x@$d@N[XfUpd֧ua GYka 88.ItyX8ֱvÉR|9S))+NrmS~XnH)oW.S( B>_Q򌢔 B>7eVᑐ#}GEyM)71/1.R%G/n Hb1zp"/ST2L';%89)qd -bBKge kJpunJi4E[(S.+d!fqin%μhK^^td_}E;K]fW\kwcJ$)P>RNSvC)yG!E)8E0@ 4@ t@8`F`f` -D`<px$ /$ ȩgqi`Mf6#'XC!J!#y?f}:Yk%ٜI]|*OǜU.Xwx@hpǀ9쎁M_CyrizKOgŲFBC*MRڰ!4t-*zտЫF`^YQnh(_|. ySbl7 ґ^?;TACSАtKw&&1+eSB"٩$yȨnǙq'SDQ -J16$w6mSNnM/۪]H"[[nUX85eyhKӞȦ{PTni3{:?_>wN -FiJ[)<3Ҧ!6M9ދfi6#JiS@@`&` @4V;p@2 HɀJ GqLi9m7[8=9cg*̃6[ZdKN?=Ñ-]y8ks4M!&!%#'0000$E"D + @CB$!IxIHHI$H&qzr)@W~%)ئ]W0S\߶>F}/9c._ 0Vrh_ gq&LLSbZJSO=bbsKŨlWʦm'=Qfj{pm唠;qD<$p[ Kq]+ -DG$KuvDOܵ5mͩKO]kk9f(* -??aJlO.1[$*Q% lT*" :0#03HA ؁<@@AH%^`^N]i8Ayk qeAQY{G斦9ab='e[ _fUZTV,PQC!Zfl(ڶZ[dkA(SRI6܈*, 2Anu7)s^%t($N9^Bp''9[]U' 辭A7M,pojv{給۶G-ё3/C)i\ᶖJ[o}NG݇PJ"Gp4Q;PtB)wVSPJ"" :0#03HA ؁<@@AHQ`VjZN/M,WJX2k:XK=QqJa}uMbγŠ嬔ͬ~V)_R=1c*JY^JuSr᪔[r6rD_B)){CدL+NAqʗBt>ӛJkCN-] eʃpʎ8&O7%&>Q 0= -2 F8nYqq*;4sIq;x/bZSK5(S::&eji߃)z:iO~mhtRv[b e<0u>e.;e}5+E:a*n*i2tE_**ǾSmC0d8I>"BZ8KcA{о@37ee4M!&!%#'0000$E"D + @CB$!IxIHHI$H&ANvO7~uE@SnU~n(`f\掀e SK%۬c~xj ~Oy@ -HmXڜ<~[:'JtNY7e毎oZAG~5|59vx59vnoS(mFPemL>nJ_ [LDisKcM_jM8{ ̸LYrf Аv -I͔݉ UBC⭏(/"[jwԽ:HV;Ń7~ } ;LuS,/dE QsCCչTL#PO34044tfhaNh((ǟ;W - ;H*GS("'M`HIhHhIHIp$ $$L$$,$"ID&CJ'a' "!IH$d^>AI$I~e1mOSЮt~}+ :/:Ƿ'爆F~W_h{|b_F1| 5*&%ƥ) BC ns4^#tSa]h詰6EC3ܯ|׵kУd/%ߞАn䡡+߄!O+.#ESRSWyNQrn7L @CSuPcU+ mwCCZ'R=֏@C=)}_a[gX郆 -^stsY"^~hhLG騥'pCC#J44TޝR{JɷvCCmP^:U u -qsDpIǟC+n@@`&` @4V;p@2 HɀJ^,=jcbc$Vxfx4oOԑOT.y}v^(tƊC+ywU奫֞o}禠a߹)l_>ĄyLf!ti/^տ)~!(~!7_V2dB9[Q_b?X+ssSbb/D%GᗾZ@M? g/BfffA-bLMHGՓ&%&gBٳuTӥۚ__9RT/۵3/PJ,GK ;,#_iz2c&vZWzG>_ᗙ%+JzQ!j/R*_K@ImND0pnDR=k~ҀjZz00 Q +8 D x@$dpe6,ZӦVm&fmnZ(+ÓOm6ǼU ټg*NRUzmƍ?rPOB%FOB%fS<&|_⪐mԧ-㓊_~'~EEs_~Hd92H\eWJLdT=^r\ ZdYKI﮻ gܙBSHȐL49!)Kw~OCo>日/r-31%~xn6疽_/E{(. )cs(s߱‘/%KG1Z_r({$/ Dm*i~ee~·_rZᗩne_p4`hp"A1 -x`H@^I K726uS$V_s˱j8̛X$_I2k1^wuq^[/s_V◇CŶ)~yu;=Y?{U車_F?U2Kl/~RbBjqT~)Wz'P~!ꗂ<j~I/_ˇ?^w΢_ŧBZDSLLsdʲ3QJMLMKR܈p~])k/ӗm;///l׶=ev~)?0IG_vX}6sʘwZ(wKȇfM?jP%?/٤?*>eGGɇKh L , Dhv. d>A9"3Z^ky]?ixSy/3/kx4mBSˋ+6h\{+x_%ڽ~9ܰ/wo_1Ä_hҹ -_La_m//}7/Y&PKe/ R%G1`؎d/nH@̌3b\JSNt x9QHii)gNG4M+k~9^p9]N&Eoז ~i~nh>쯿"zxsG"0[3cwZGA@<3űX(ux})M>7MCӤ~%~k=y{ ]e{rb/-*}%4)~1b#e(Л◿+tq_nTQr2k◫]uzSn =2/u7u;Q+/]`QQ#&/oYwnE7qR\ZBJ\S $xPɤ IR3=/]_J/۳eV (j/#۵3eK{͢?nz~BThȩᗆ}*3c>i=2Rr~=h Qi>G*i%: ?(AیQJ@@`&` @4V;p@2 H]*ͱוX7yS^!E2FRk-.}?趹*NM, =J _{LKŽf*~9cNރ';AόiV˝p`7e|ΐ~5`#س_ϐw~Ae~Y1mhLUc.[wΞ_>g+*Ndsro11\boB\ F~]=;;̮+[wt)ED""RH"ERD+"V"RHrBaOx}ë}$&FG@-NI'x&\$4//D-3K -T~S:Cf+Ֆf:uT֌ s{MƾlS1YH]~P<;nk59!c!7wJ?_+szZfgȔ+db˗d#2oCs/n~!J(Jj 0`L ,V`pD w@HI&c2iui0մN;FZQl=m=Mj4Iiw%͗dp!-FJJɕR#)EpgLΘ[[*q wƼ1e\1))%&;(E}rdRp-?M3ջ(ۻPJyD;%-Q|*RSKyYz+[$MiB7ۨGduꖗ_Nf,#bJ_{SK/eD5?e }GȉHƝ'q;9)D ⶨyi܀]^X&9E NY2ϩ^Ӝxc2jK-Ol=r)~Z[@iEId"e4lmFiGÕdg%WBA[ I)HJtJJFR/(sS7R,t\/[]rJ,C8e}poؼ eUlm.5S=SdjrmЛYvt͏pxobJjGyQ耒DS"e 8٨eIl'U1攳GBUYNuݟ9Miǜ՞xԩU3wz2LtSph\iv6\9w[ lvw {O.K.oBzU)h2ᔥF8Ujr@PP ``f`,p@ Xq ˒iy5M.`jN.YiiFW iCml"Ni!Ni[irVṭ4@ii ?I sO-o<lIha)K@8oSvKflSk=H_Y?N)Fq2)As6i;r }wB -s9H.rʍ^8e)[@QIHwӞ)=)mwqB3QH뗆 jlow;L^{FF)*ʔC%z;.S449/˘Ųe!Y!~=/ȔK,eAЙ [qrnkrpr?mdtcܳr1wYR%ֽnᘙPmISW4e4fcYWSӡ<%f}4[KpJc 42t~eH?EL*S+pJ18eEr -Q(Jj 0`L ,V`pD ăË+#;EEŌ 1L5-cty~62aS`3 ;J[[a3+q0|?#ўFb6Y̮.XJrS^TOЯ6)WUk-}׹.VwNR -|_&=mFLqߌCr2O-JFFFLIvOQ?:Z#\>D%'$=NS\v&ё6(%)jN.}I͏p9e~n|S=Χc:j'Ĕ\q"KbJtCӲPcK.K2қR_3! -Kȝm dGd2{qV/Q(Jj 0`L ,V`pD ăZeTyL1Lvmc!1nRR:KdsnksiwJ8[Ĕ}DalRYK߄SN)MHN#@ T@ 4Zz`F B  -l8nxbā6򙕻eeC\hiu󴾚6 SJSiv -6TЮ|]KE{iqlagL-MּƏS~g˒~)N@NNٻS~5Nw)7IN9)NQqIiICM')owJw%Yb}˭9&NixȧpD3ZF/;NQW5?s -xӕ!pv!'qTݙ*pѩȈ9eHPOGlՕҽ2ySi*y\{\lأ&L32fzxv2]gM_3+31\%!nŧe3E'/NR[/3sj>ҖCfwe]r,?ĵmk*w,@_)%56 qsA3OelVen*y -*-=0#!X`6p7<@ @btu ?VLԣuJ?,sB42z??5_C4֩ShƞE$NxD9( -0@ t@ A03X 8\ x A,8ȣ2vaHCfu>`fF;Eh mdhn!a+2meK4Wĸiw;w3a:fUHJG)?"-DQ7usc0 k/w=* xuQm̠Er7m̮OGPtN&tN;(kLR~r]g4ӏ1g2vl~)mZ?`x9!8\֐ᗢѻ Ci~? //=Kv}n?-ᗱaw)_O/G%y -*-=0#!X`6p7<@ @< RkYU4lV5˪4Catv(L }6luDeeìat1ra0B? -{70"Ŷ%!$ض:$pݦ%aw%2޵_HrGW8^MK/rUCRU(P{| -GK~婧q|ԥ&RSS+vIR}ĈT_;u_ZK[ϦQ9d|{T_&_A~)E~?K[v ~8r carONMu9dHs Y%m+{Aș3l_Q gT'TڼE;kQ-SXݻ8|Z,'O%ę݅egZgoOhx7w{b6 W 4@ t@00  -" ^  AA$D@.*xz>J-u=gy.+PmlMQhKKTض(ȶ([aۧk9JXt\^l⥺(lT 7:--a[mj7aT>?58'~4?icq _31ٻ>YkOa?Ywj~F?Ej_Syu]͡Roԩ>z&mAz[=;L%2G>>{2?{ 덷=yv~|332V ߫A>ױV5^SɽRQ O۩ڡw>v^z=t%6r?O !ߪMw~5υ_XqSyc=nS}IDoӝKI1n/Sm7}없;]TA73-h%E芋Sa)u33-w)j)V*ˬOY#{rݶC|2'kU*ݫyTy^Q,x+ U4mTgUUFݯJX~*z;.4-=` L , +6;px$!,UѧY-t ~eFX.3ɚ;YK#PaSbӒ=>љF2LIv$&Koz)p<ꔼ\^4  -ځAyY|T# S7K:6jM)} Kܶ#{Ye#Y8eN)QyޅSfn )pJkpN8e N{Nv -.4-=` L , +6;px$!*AqN;8 -˔s\g(ጹ3wq|bNlbUkN^;Ɗ͜4OqO;E';&;VpSlS~;)oV9?픴5NN))pJZ쓝))\*)S -NT!L>SkSv18%Nc418p4/r?4@~wҴ!+%9bҒSBl,8݂I<ɱ1:SJ}{S5og݅y*M*T=>}PKAU;MonÊmy}ɣΰ5>$FT" ֠j=>hXS>h8rACܖ=4X>(sQph8`F`f`a XA$؁< D ? ȥDQ}Q`Gy:Pe2G{,#<HF޶O wUBN'+;%EIQQ ܫWsZv=FZQ5Z{&At~y>ɹz_$eB+eBtWe~o_WK[}P}жt1w2ˢ'/+H?-D_|5A1%.v9)>g} -~Cfվ&AN*;MW4=*B7Onha1}ТĶkXы,v[Y#[ݶT~jj c,(BadWYnQZH/ t܃2"2qdYʞ3 .4-=` L , +6;px$!)fN3jY4ofa;YkjbK%dBYXǗq*1ȺXa|Nbf R^VO3SVN9|QA);$X -Fe62̒!;AfcdPpC)?ܒ>h)1BqlgImv;8 ]ՓU*鸊[g?t\elajWeg۾𲪃jkN-Ȭu*[cj]sN(:Fri.S(\(.v<:Lmm%mTp*5O%+8!C$MB!EK=) ))RH1b!%pRDb#'N))RHI $%DJ)-@!]&73!2ࡦxL<W5AWUɏĜxi,ொtCjCwPW<T<%GxLG}FzId65|Gd 4tm2+hW -eЏ/T64 +ϮF H܏j?f -x!:a N5iH| -)6qN.Ei$wrSR_r^qhU+j(sʭ -_|T^h}lh1[lUqgF̩O [8s -_S[saRh6h#ɂ۠g<DysX=Ыj(#TTq*X;KrfV5D."&EC)zRR8R I1b&BJ)XI$F -O).RRҔ=Ҟ|R]rJ_psiUqmo^j9NS׉#*xnKߓNwS'TYW9 [+*3*{ *G3jekvo?SWIy3jgn{ =#8^ -@@0#030 \@"@@HR&(ڎh]晑3CQ2޴țyK9UgmޑǻxGy(i2?J Gp*ߜAed|A/-~ʟ|L1C"Sm-#R˵_zdP -~vKfc)s9?AufbmK_mSĜmet?}C8w#8#8BJJ'jyҜ8$x)ޤ1-${ԭ:WGp*_vRenz|X?+hGt;^n?aqҙo~5UEu\mkjJE}%1?}:sJ8TSt*T bi|I~J˩Vg_Uj^_P$)@ p@8H`<px@~A Kere5%յzbPYkb-,u"3ˬmOBQsvgsVer?=MV>r:Y)6\%#8})%ݣFJyJJd^9='+R򊬔/*(%sO:]A)}N"3_,B)}H~W2eRB ,;t|({ o]杓GybŜ]L"/ɓ'Ťc_fcJi:㤲ns#PJS|v@)n%KVKY5l)(2{VkdOx(o~uPJ9 @)'Oԫ979PJa(,YR6Y)D -@@0#030 \@"@@HRf8j;8Mtsc83,B9K5&…X{\0y8o?'VqR܆)ْzU)0RV[fKR*rէm '2?]딽So6IvʢDSd,*LdV -g~ YsjLySnN nSF_"@;  - :65.ټ JObI^35ELqzc$JiB7YLǿs0qJCv -bnzA3t\жWDZ8M" ncɻ:Sosְi8ȱn[}|{?)uKpJœ+pJCpJdq3pJp>qJ4)MpJ4)MpJ4)MpJ4)MpJ4)MpJ4)MpJ4)MpJ4)MpJt)UѳtŜc83r^T5NbmGX,Z:8fpb+r.PmS~%ޒcOrXv[rLSoSj>1\tꗕbQ)BL)ߊ1d+% -J᠔S\)?(RFL}JS|Rsmuo1bJ J*_ΝR<3y}nw[HC$;nśNŦuJY=<']7 A&hGwN LcYahuGuK̹X-7#<m89xm-N^cϼPJ 1eq17(u䒹SOJ'kxO$+mKY)Jyo)]ϫU^)vjo"reR/B)wt_/ ~)"~ JYiXM-욇pk͗&Ʀ -hzt7 JI8SRܢ7 LJ[cPJO4f}95v&i9,䪴}ƫT*\v%sgҬcz)@ p@8H`<px@~A KQn֬Diy]>?30o(6Dyo_yTm _㼣wB%)彣X-UGG dЈrdtVؒꂆtC6/X-[YۚSm,m~$=k{cYDwYjYNڶRU!DjUDiDȂK 4@ t@00  -" ^  AA$k8dqBNW9f9C g\bM.tAӬ-9{)\Pywx880tde|CVT|Q+xw(([Y -g/H)_Wx|J@A)r {<ykudi6( :}qQJɨR;.h/肂~yBPJ{"D2bh?YΝRYĸ8RɮTgشtRc/s)z6_{AӪRJr]TAsK7Euv rCm{_g*ļ4a'"}I^ˬYl=L*+TJ:vTE45pX& YXB>~*2VJ[U -.4-=` L , +6;px$!^`rV3jGX]5d+e -gW&RJղEdìcu)zXqX RvJT -JJN)F9|YA)n3͕rFJ9|G9TGVJBtU쑕rB)vAtAwC),P3O4i(t?)e9H_ˠQoKRgnRf&: ())rXX%R:  p`x`/ @"8O9zqlVW‘yk ݬq5b2M2J.gqQϺXa,VVZf>/JP^&r0yG-Qʿ~ަ+Զ2zy"2Ov@]vʀSʛ.;e@)GkK)nz\y|.|ՑNyhϰNOiĔ)iYpʭ5nt[R7"8|INw;MJy1͡meO˛^rRt˂1e$BWz[=%p٥\y^gʻVҭְ|h֊#ݶ#;{U&4YF)+7 pJQ)pJ~26$;Hh0`&`D8 @H9eJ8,ړ.sL5sZx32Ϯtcup0)gRҧ"d 9D*8%FvʲS"%Ni=l0pFi*[S~+;|Ge+8%Nn}NWpJܖ֖T~i}Nu8 E0opJ%"m7S8(85sW)m@Z -2 ;Nə@W͡YE3{\"hZzX@V lv. H B $r)=,UMVLN27ϒ %4;XK6M#Nl9?YG*rγzO'eq)bfMCfK3NSn)_2%2k{)77gU (8嫟{8E)^/h:mǑ]n}ei'P6L+VK~kK9z!"۶R^[FNDNɯEN&TJSR<؀2N֩L99 )D -@@0#030 \@"@@HRXrZV&( \;kb#)3*SHVXw&5 -+IVg -iN!trS)StrS)[┉#_NdWd<碪9A"R+?֟.˭06,Lu&?u*PJeȺ-+/ocESL T tC)s7+J REΚ{(J)>5Rph8`F`f`a XA$؁< D ? ‘&NSiX9g'}9I'8K );Qζ}ss$Jc? ,nYD)(#S߶r)a+V/(eb]pM"]yN)Nw)S.(pO3.y]jL0!֘18&|]; _@!nC8)gN1N|H))8!.ݙ*D)EHOOY?2}[^w fvPG -LW^a(.qәzy"jɿS -LEDNmwJ3ӏ)s(B]6bY{GOί#W+2HΚ떝K 4@ t@00  -" ^  AA$D@.r8zsN79fj98gLy֧s8go9(Ys ]D|)g,>PpU)r )j)S+8NyA)Wyب0<+ǔb^7H[Y)SH:)ukކRR]}uu67$tԯR| -\RMd9mSӬ_a糹R=;6%9ơIJMw&%θ8IԴPJs/)PJ7WA!h'/lVDE!CVKm5,fUxnk뱈{ܶʖT- GPJ<28_[ DvPai,=X - t/J%R:  p`x`/ @" RQ]iXmYpv$kL՜am>gcL>9ZYW''qR;wv/GS e9h2&+e9hbؖt>O݀h5)wlSDSIv1VpJ<}9hJaV>;/to%'2$+ttYH\t(67k¹;%n]?q1">B3).6%شk&:TQPYY*j:۪Kྰ𹂃jkyޭu*tËjw?ϩsm+6J_z֏RʲbS3*Ô*Pq*8D`JX"MB!EK=) ))RH1b!%pRDb#'N))RHI $%DJ)]OexMv +'t<o(7ME-E(xx8hOۻuxD3p?,~((JP<~QP-9es> HMdZ'YCs9mS7[rK۹GAvH.{5˄*x84Z?4@ qvo.E[k93X!;XtKb3.U9}*zғG|D}SmNi'5M&O AY˾ۭ/;q_anqѦ;ѼpՒm k@Ts`5w5tVn=2 ["L;nk[*tVD) .4-=` L , +6;px$!{?ꖚ8jOvN;8}ǜ1ְWXr22k)!R 6s8{ jf's<+ur>hɲ[3sf ZJsf ZoI^ƫ5Qt箌%2h+)oE]zWa.mdT7:v< {Ym@ꔟ )ypJa'h;6ѻ=@_5k¹sJgSĦRɩtP1^g$ťK$ƭ?c'|+L 'ՋvsPT=*hEvKm7Yaml|WgZ*ToSz8Fu"7rʇ%2cksSD)]o6St.))+rˮ벻'<S£pehvDE㌓$qIGuSg|S˭{r@M?AWPi&UiWTWRHP1 *RCiq8Y|Denom9Ѹ;,kp{xWǃ*kxZ䩧3l+ugTwS9N8vpA-U.=7-+-Po娤ROW -FlOX|sC\/hZzX@V lv. H B $r)2O/^{|;3UG&);m!gìh,D۫Ѯ^=w%Z\ॡhrVgQotaoQU[Ej6[2_a` fDF -}YA0[r.Qs/,M]=;L|WmOtwYW 5܅S)1B!ӓD^'"*Rҝ>!q _Qԇ2u/CyBKCh.-'SA#6N37[-%7[f5\f]ls: S=C6da.BKinr-K\rdBy)@ p@8H`<px@~A K)g^a5Ì6rg5汦n\Z*oYRٖ~Wi5 -b+5"3{MvS)_&; -N?Cˆ۾,ymRn;d,(ACVʂB-,߽Y展>da22{W@)?ɧt82ˡzη!m%)=&.WIvJ)iθشX!)Ir2K[,˗Sc1d[YT7^r2K,N# ݿׁ2nKcC'{^I6V82Ky2ā^dϨGYz*YroSmUd*d9d7o"&o 4@ t@00  -" ^  AA$D@.q^ky]S~gynf2V*\[J9$,EzxyOeODIs|:|W~yR)eD,_t -~ٽ%^6?麍"s݉Lrdj/*߲_R,GJ))2vY,%G&T/jG)E =ё<ӈRY]d0J!)6-%&&%E$Ddɓ$#j+u92-}BT?!h+"tfM -# ,uL3goZa5w5ǎ.<9uޖk{Gg*g|,ȭ}@]xFL@yX寙@diZ_BdY9C6#~2΂K 4@ t@00  -" ^  AA$ĊYjYjY]fu֐9S7݌8N"kgn^:FYW/+T r3YErdʦo9_VWܒq}8KFNy>ٹvj)o$OKp3][2vY~Oq^ R5̲{ -8%cL#_S2~/=r— ypVii!IOwd8%yg\Ll7Et mP3ڠJ_\uJqӽCKЎG2mPm j@0Vљ:gJDi"wˬMEDߚ/9? ;> -t w?Rc[6z0WDr'o=Q:  p`x`/ @" R,=jr8IVO2L˕q:֘řYsg%cSN =cu5Y dq͟vJ1oNQ)8e7eoK2:3zS^+HdN\q+;MV(̛qK.C/8h{D/VN=SS(S"trJ"Śp+m~.xII1d"(,uSA3Y.M"hfu; -LUe0t6f35>h.jYɲu<dݶZ89ﶝ+/~k GpJf2OV_S:oK=ϕUppJO Y>wN퓝K 4@ t@00  -" ^  AA$Dơ%d5լv599ra56=eԓROzX4'V_4c}d/ das;әN %+ְڒցڈȞ;ݶRk ͈%og%o/XSQ,+^:E6#4)ͳp!8erh6.4-=` L , +6;px$!G>:4;w5f9ef~h[ ;Ci3sSƉS8[UCsZwu+;R -(xyFvJ<2 eDʫGr8%2A,k7RL̴Ro+CI -PM$\2ҘKk1_#PJaĔ2\RZ~ˡ^R]ΝRܛ% ^z|NoJ\SK9Ғ钘sǮSJ2[!y8VuV70)hN>+h"ten}ś37snO7ZL:Ҳ|u>Y|_op,T͈' sXnOz(oϪ@ÃPJmPt.R_9,> -@@0#030 \@"@@HRNpT͈P3q !gN%rCi}8G *9λȉtgs>nCm$JHi蓘"+"-QwoyxᔴvzQHdrS"Ĕ)Ay햴>QmV,WiWܧ~J?~3p\36Wˎ;L+s*|>}aMKK֢Toݯʹ csj{wԎڂM)/P7.v>٫Q@V*_P?VWHIѐ%EG)FRLIFJ8)VR"I“b'A)^RDR$RH "%DR"/|nhwiv3N22UuKN'[].{.GY5VD@,%u=6].{M`>yedv}F&)S~|.[9 Wn T gBCUưzhhbtKOmtͽ![?l\y(aIӷ8 sSGPq*8t/<,p ESФhHђ#EO -C -G#)&R̤XH #%+)HI E@/)"))~RI @J")ggTo<=kuYx&;;o x.K yslo+xGwk,^89תּGRGп,oȮZi~M΀]lg-=_gl6tºJyzY7= 7=DC-yiyN7=#tOL.`!sN0fL=g.1dY--YְGT9qJ'3ae*/Qy RE73/掐5v#Dkph8`F`f`a XA$؁< D ? xFj+YM+gtMe5|֔ϚK6#K݌ NX7="Vfjֿ O=՟rRΗl*DmrUo6Rʳ̩_Qodd\RlRT)6yrpPt+..jVǁD^KtD_z V4'b{+w }'`Zg$$ĉbRI]cjHlEU^!hJ;mU9%p" -ncy7h̴Z -?kzaUx-yEDvp& -R=΂ GK2SUy -7=PJPdn(%^V -.4-=` L , +6;px$!qT;Gr@N7s8f R3pӜe( pQogy##sKݜgsb#'es~.m%_ղR^bWP[2hsj7S>婥HyfHV -v /r8b̦e]J~飩%cJ1XU;[R解O] [.gԮΨevob\J:} y_Ձ3`YCݲ=踼lKh0`&`D8 @H\Jeټ6Z[뚣 QLS4WʢK7l7+~m|y}wdEjh(וֹs>_MgtAd\엋6=ݒ.o~i0;ʃ1"oeܮYe[+D dQgcΠ E_8;l u _ N,HNBmtA]Ι_|q@]l yיBzoKN)=.֛$Ĉ)i.>:I'}M mO~n}R`j>?5/3N^bʱZj j޿ۚs4"r4m)I[^Gd)p<؉Ȓ].c -ȍbK 2u}/"(jGd"i# .4-=` L , +6;px$!>ZR ,=jXm5`,ȅN8‘w̳ZYHJX~=G%/gay}T^~FU+RR,GVA)-~Pv#̄'2O\da9t :,Gvs~^Ją.@)՗:NhR"nCdi]a J)#rwY&&qᤓR{`]9B3/˻~+a~C0vLLg.1bb σRƳv[J""ܶT= ٷjjR>DJ6/ yJi < 46@)ٝRph8`F`f`a XA$؁< D ? ξǑfXzJagw,L;r8ʚ"J'Jgm?YGdΓys9N:6prc3S]5$!yk7[aGFVvuu;8{;ɸgݮ3u])rvuw; :`o9+?oNokUmDD$ )"MIDD HiD4H9)FDJ"x_IfpzeNZ ܧ:bAp#/,:=)oӋTN, ~)j Oᔺf)ILW ʔ~Ko2&KRWʔ))ޜ@g^8mPPfTv>EjÔ}vP=D.kiwONySҾֱWv$N=S׵PIul?C*.9qw?<,Vuо#Ow‘_Cb~Ij}UB{СSi;GGO|[:mui -  =  OHDLBJ"D" ;$.n> DB&"H#Nbu]LoDэa@acg17E,7~~A]Y3 -;î{:S|}0#`X MLD -f]Ct mV=]Ct m=yڋ,]-c@ q$HPCI?ڐ5pQ~֮~=)1g5D>U_-&< .ݚEJY+eA>V%+䌔L!+443)Di3Tg~-h+ J3P7 |U8457ϝR:-L+Xux=Qa'J٢mftMJ~2ɨijRG|L\|M/0y@-m4` x`&`` App$ P@DAH9zc:xm acx~#sx o㤴"/xxGݧ8>7 #|y -nYw7Qu褪K xTTbφtK߾ {Y:~Q-RPb+TXb(C)߼*eC)2ռ_JW{dBOW\l?e^?~K`"9LC٢7#SNS@09[qKS4}e2t4#hW]SP'Gfl]LoTA)Cy24ölW͏mT@) ThJ9R E#kF]Rdyv˃A0@ t@ <00  8 @A(  գQ,]1ו΅g/gYSgY9k:sTs|}duzj4+qR +rVfUʨ+Uq7^*E+7~y\SzY25ΖR5eWU>RK6b]㋹ M\7׆fIZRj%s%Cr!)s÷LdsY -$ǟ-ys4K͔sSLIٲxa2~㥟u=f[WۘRQnTSxbQ|щoSg1,5ZjnM-*۔XU+~쭭I}ctrO{dQ+~iQ#4曊7Hc=):r2z:MEGެO6Wi-=0L , -@"$.H@!4VO^rtr*L [=B=rq͛{8g=Z.'2p3jyr-C/V'Ƞ&?5c(:uPufc( ]M_䪗2MW_[ֹ/9 [Ul{ yZv/]/&ynSK^7Yȧ -{:}AyY8Gtmo?5+xf$xs)5%#;35DT|0oOSOy=~pHV i 'Qf+?қ+ -a-[NJ Cmh*nٓJõٮgy<ײQAm} -Ԣ ZWSJڠyr5As *?7HhF`f`V$p7@@2D@t@e8fiuk/Ȍ:^ݍʙ8g=K|k*ܵMs4ENjY.TcF#omаs 2ZuWc8Ÿ!m]7g׷Ƽҫ:ꔿYT|:c,Y6ʬMǚjt׾cW5ݞWk$aSu%D>EtEOQ`-EfDHEPdfg^Q -ȒWr`vNi9xHSaUs칵E)(oC+NݡoigR|{;픩zcx4ezc{xG?Љ5;5|*Hhԋ5#OWN-LЁFhr3Hɳ/Cb;|U:>@Fi -  =  OHDLBJ"D" ;$.n> DB&"H#NbsSyh{ú?lX -_6VFLsq2֒Fxsl4k dX\ -K-7о2^uf$Tĝjt͆KZRmCtFyNtEmNZfۆ,ug;b(vchzڥW3%Yq4O!)Jp'ZL..:GtRJgfedxbjWN~r:;#'ן%];pH<ә1~n}s](xJWs~pQP(6)iKhF`f`V$p7@@2D@t@儓p1 NSW79 uNv;.Ә\Nq:\9{yqӮSpJNyjq* uoNkUT1reO3s^O0^Hg$3}E5qO2 -暿$c׿`<sݏ/Gs-58*!vIzyEf0ȧ -|3X}hy?¥L0ތOIfILV@fdd{ _ ff8P'SWzmNf-Y_VULz - ]ƅ]ŔSטA0ouޚ0MCLKmNA0SB0+dKqfLwclӿ C0u43L!U0x^0@ t@ <00  8 @A(  (]ui睺eXv4ǜ95]pyGNǔUr8=y.YP s):C)uwߥs/`VsH|G~6]=g:.u=iB˖~)~R/;{ ~aZb]_*$o~f'D>E0{2;gX.ݻYbv DnSr%09A_fgHRj334 ;%׽tn?[A$:Jlb6JKwڌgSܜ K#vp=!Tb_ɭ}ڒ;*]Klw>3"9E&N$HN% -yf --?YK)ţ^23TxD0@ t@ <00  8 @A(  Utڽno1̯k,7mt2r$y7sw>{x2O2b)/sr敊|߫97U}7[U q%JR)_݌-lstH%s!vJxR2W^oJ9-1rQDDst[v'_@8"'sî(ޝXo[8GtJI_,f{eH?+(AAA[x˞:)n֨7f.B)ӏ ]^~L`;uؖ7~7ܴz CTbg[d1UܻS|J ,4[x΅ڷBrF;#gRzUi-=0L , -@"$.H@!4^R&V ^79,ǖ>śZys5oiMuo3;928}͜0f8'MsrjQn]Jꀣ]j9R7D)7L&SʿśJsYڮ:eTu'1rQ)p_uX -{+I)IupJ;h~N6Q?„%(9))oT/x٩(SR3 ojn -AP4 ͹^zϴ='h˟tc6Ype]]6i<~$r6n^%, ;b/%]lw? ̓tgParewNi #))S4` x`&`` App$ P@DAHoAc)V9)Pʱ-,?9kaдNbq8]=ɒt0Ww6*캝n)/N)weʋSpʝ?.l?4m ]ݑ>vRJy_=جv>=I˖)o5,^)ן^X9w T9$5895(0ފ0Q='ӘV_yޏp2EsɁoT)~(E9K3.TJ72_šRztY݂njM_7 !3(SJlBʔo,/۬gl Li{ 3l%˕7ݽӻ=ӻ*(PJQ(}}<,#')2J*R* #ҀZz`,X$D`I\ <" B@i G93i[y] g ;;]8s?gKL"eJ?8ȹ89Yh2Nj#\SjuwԪfeNG?*E{: Q͚x/S~l9WĮR*] S"=B6線sҮR -QI3}\hS -u֨fGd#qo|aC.w:|jםiTsђe;Up1_uJ.SlSnR1@fT-)ՄȧpP~p - ʔ[4nmVjN UN$ɩ^13C򦦦fxbnr +]4sz^>`#]j)ޙߑ?8LcX;e*yR~tcGBۣtbb*ir4ҸVf5̵/сco_l RgPôVNG/N[Ro%M`HhIHIH$xF&fV $II$pppHH$$2 DDDtazRanЖDteabxWaacKԫÖu\CNG7)Y5nF|M U:EW%4V׿QE})~Y<CgC։l폦ͱסu]u'e|ܹGQ%Y %.ݷ9/(m-]}k/u!)At3D>UWG2Wv ~ҕ6Hɹl_]*d -)A}fn tKcׂI~C{EvMMқKoD4bKhxM-wlI ҳYOԖF(mGiS=qnr,Jz6(mZQtU  ` $;Hn> d -(H(5Xï[z/ - jQC){s\ug^=T6 #\-OGg9)`2 jT2Y-K E97cl -F*W)rvJ8ʦVAf?Tyw^XK=<ʅf9e[wZܦV61mU)Mb |{C*ypfn:;5R$U)eR1=U)eqoC*7E#PbiObߨCe;BS?7e?Dpޏ@W_AxBmtУĽ\#τ8\icR&10L\6)jV{{=wV[[DdYYDdqE\Wd)"""⊈J銸""5t9 {xoљy]vonpnLr޸-#t,7qmi |[ iͼ႕P aoC¿ ^y<\y/;Wp !1=KpN>+3),ĬP)p?X{م?g]zxi"Eur>79ovJ޻bun}'C_Ʃii\^4Zj$6n^u[װÄ݇ n}g>qV!HڪD"8u1"y~":: -KThP:`&`18<| A -.Q10eԇhM+\Yu~6aɘbhKTmv8i>cQ<4:@FsɤM\V~F(Ft %.ә 8$3CN1) -I|)yVZx)_9\A uM; m3L>tEW^Y@k=5 aBiTwGԏ?AX|pu\VA8FK)\c\ߗwvF3RSOo'Ēfs -7nN\R~tJ.)w׫$P5-t@ L b+`8 px @$ ]Jer9gUˬkY$)YC9kaM̐ɋgkAֵh{x%^ub+!dpoFY0dXe( #kH!&_Lh`2=7Kou -f_,%_d/~]~I~YZlw_~~+o~ygEAQeW$J$/]>ыPt#ykG,!>=!Υ932Oi!O31!Mw w:گx;󫙥kʩ&8U!2KeN;3Q{瘦cu'8Qd-@2ؽظ2&,n{ dY#n>KS,!=Rqd19*` A 8`,px dK` Du%ic 5B3ic E4.Ӧ*)T0PJ cdyڞ8Ю+f< M8N0fV9{DVʿJQONKfNWr,[7Rʫ)TrFVʏeF)ߓcY)gQ(矪R޹[r.R T#:o54@)O -u`/"ٓ>S(PʞIӿ޺!ȒY)DѝȉNobF  Nѓde&YYCUjd)˿\v`+9ZX@+Y^ޢ{VY^ݢ+Adm<0ӊRߘjjy&|—YꧮCdy%evDZDl \S, )y8N)WHJڣ*)_B=~sfK":ep)%SL~oSꔉS(tp﹓NqF)]6<'2>Sd̒  Ny59EL$!JICfI:<$HOw{2D/)WTxGw:ed\S9UA+{NοQ3Rkq'4]dl_5-ƚ)\jv[)CpJub8NSڞS'ogb_#R)W+9%N>QC$N%* T@ 4@ (00`q -X`@> bAZ@ M=@SC4@J}1cXEr$ ^8i2Ӟ4?HKSU| 3Nv8E/;:)gq8į:%̬()K)krMrNq}g))[圲=JZltLݎE_P xxijE N rNI)WN < h!:HDt&$zBRr -9tg"MJHKBR:jw[W렆˕ݽ[= ;ⓨmutyAET1EЉ:h O54[^G4т:hr^j{Tvj;|pWJD4X:(xA4ӷS!NeD61͎ێ:}* T@ 4@ (00`q -X`@> HR`>Vͪ+XMC*VlOCiFz3lkbxn(W x@3~é0х0NKtV}_Ldy/;xmG/{{}J,0~RSrf1Kꗪ;_x ~u625Z]/o_.UP)~#폿:N]6ŻmC8s~.nOBKLt gR;iIBBf;z!oϮfBAuyTEOp#wj9mꠗ88Vq[?rXc"cXSry3dZjbp[gBl6YV!}ԍ̲x0k82KU)܇2\:9*` A 8`,px dKcy yVZ1"͌2fF60RTȬAuf2ڑǸzhn6FZG0.7OĪR0J#Q*(2_)\rFNRuʟd&/dLYneBvd:(,U~pUR "A( -zVdeOm2Kvd[S)k™uP(QpINOVV':ۙ.&q-,z*CmJQ0Mֿũ8=\A8_9Xn9GQMknN)9verl\~ۺt"@p:Gn8N9rNꠒ[bU2~uЁ:8eAN%* T@ 4@ (00`q -X`@> uFYϐsQ3zZFS ͮ,51QT*9e#ah8hf\4wT22-L07M[7[yCLSa+s䞖:(GH1ERS:B~n&;2)/s+ U OzP#~n%<4 d|SnrJUi 9(IIG!8M?I&ϝN[ΘSx>S+dKm++Eq++%\b>-,,drL1+uN,VWvJnĹ=-{XYnA:c@fq\-pԕ,M p'}'vtn!Ok̒!A|z;)1͓2PqiNT?KNx^sS?|%ngG4տ>y:G=pJ=! uPU 2tȎMbp,A;ᔞ"8>)D% -hz`F`f, X \ RiMѪ&Z=Ei(m)M˦a2NSÒShV!QOh|-(囧ӵrb)|s+E:㔿:huNNnH)Pυ/%+BV6RΖR!+EF)g=VnFLQ1]Z 5-:.-gS>92PJ)wTygL)"y';#11Qڱ)8y8O3Kt'YAPJt9}ynSr٢XMQ[[&bJ>?AL9|Di!1EsRGq-nkߎ[6{έ*(a("̿߀Rz#v@)sC)A)RpJ@P  -0@ X@؁<AA2H'c0MѪޕ%iZ;GS}4}msORʨCSW6#M׎8-,b}_URVJ婕Q/r<0Ji)W=:Bsu%OI:)#~$O(8E%; Umꔻs8:V8ĔKS=pJeDOS%?ͩ,b]\LI6!)KL2n>]tYngR&uK@eW>Ϳ].}>p*P p&NurS3VW){/2';,f/ؽqpJnJ[};p܃ppJe (-uN%* T@ 4@ (00`q -X`@> O;\eZǨgiP4Dzi}3m)W-Q(m?L;riОv_iJ})?"0NR$>0N?-1entRW:%Cvʗ]79 Kd ; KNKNys)9Ny28eONȀ8r8߱!ee;tm&ғD1{Lg: -8%223t>cSCُdTNqN,.(ՔֶQK))'8C[r-\yjrg96nNi/f/SJkCvi:e}8ȃ -~pCp$M=~Iv -.Q HjZ@ V;p@"? H)@FEjVњRZ;ESE SN0FhSS% [O;iV"OPɈ{i_SSg398e+圢ݧ)s?Moi[ -ubS ; Q:;aSVG[ 띧ꔜs/ ބSf*W"M9%E ㇣S>N)=@S'.<36}o(!-!3 e%PtgbJpy=Szᔡ{޴ꔜYv#9UNS:svjycfbu-˜>PEƶXXs(jCZzJ-q u vc}u#Uk*k{TR-S -o(S%K5DL\@@ =0#03v.D~A RSheMӪ:)J3ݴַӆXD$TJNiC[N'hG!꣹iCM)iJWr씿9S9)ߗS>s9aiquyn ;#M7 egkBqSs -)Kj1|NOɇS~9ER'} -F?Y$^BnYΔS$w$1L)Y3=vfibB(=!ӛazosr%7Y4̩z8uIUf9eH> #bcEԙo1KSyR-M"2v~U1~999a8 -7)q8e9%9kSpJ@P  -0@ X@؁<AA2H'sJCvҪrZ}2AZWC!cM}R3Mc[3Ö3m*eza-e|pT9—n'!wЏ"4CrJIkZY1V/*PaPRrkO.%_ 7׹H6lSBy@~{ER7{'}' }5 we2i|8jC8sy>%OOKJHwzB'I❢[b$!#1SoUYDN)}[ªSu*dEN}KThP:`&`18<| A -X9H+sٌ !ShcmjrJX+vҎJ5Osg,-VӾ6?՜oSN9b $)c$2H)Wy7lxrL/~_x:JP1aE*Ji^ΰntM=A(}5)E)t7KY\z9BnJKpsYݷ_Qmw~(\VrBUB>h&B;:ҔO0S[t )RM㭩1Oj=A:Fg'?pTSp-K -O[h~jBپ>X{bN]W=:XɳN^% -hz`F`f, X \ RrB;YbWlbF36]Mc7tٌ6Ӹ~i/5vkmqkm=v&!oۿ\7I0~el &_Ӳeߍ>+BoS;G{r"̎oˑeDˉ0; ne&Dy~ÏVw ~ ~i@tOOwsA2yoQ=5%kN&d&2,gB'eLZB w/ ˁډ`ZrHS-AP5^~ڊg g @ͺ'Z }0,ax&\M*c ' %C`'?S؇w* uJWIU837{weBMs%J_J_UQ[_U&72J/tJi A% jiHV(i`A' zi0HQL`iHC4X48% 4xADiI_diHbIUYݰIӺYf63tK&cuԼ *$-EoؑMMM7n7 쀯:_ܴa0a4 -s!iӛΊP:I#uPh[[e]ߨ2\t]Uۅ{s# #g1=JHZI;}ҧ~ҙP@ΩlZt 51Y$3C4((Mp'w.fP~~Er#rn7y ~SgWjKڹa:4Upfؙg1nBthWe=6"8͞]Q+5풖AqT* O}_t'M } MtDT{@.pJ@P  -0@ X@؁<AA2Hҥ|S!Ub4Ռ0vӰ43*N9a1Frs Wxi>"#v1q?B3ʥa+)ʥa2LfEE\V\RHYkMV߲R eF)OSˊ -j m!ju%JR -rm{>SN7H6i<3mIBJoLL7(x7zo?OOZ:U]\XJVVtj^Nkۢ[AhJc feBVazҁTSہT)h ,mq[;&SN]C(*tph~Oj$W) -1E0':877:y t* T@ 4@ (00`q -X`@> ,r췩:mlf]RC,Sovk\dM9lPZmgvG5nXϜ/ Evq櫰slnAA,0~W/3󟜚PT̲wYaW_uHr)M,_ÔNiaf{kT;v#<'5hX)J -$_,o-(.E Lvo_Μ_!\'&xYtpHIIN13B[ /_6&.{reV2o -~{4PW~ETU@hK -P7=O0[toJ>24v^I5OO>AXɊkj] V/Kבc -W9%yzg4?Bx~ɮ&Sۣ swD'w&׫$P5-t@ L b+`8 px @$ ]ʒMc#lAVoTڴ6eZX/d 6cT)MLRW9β]6,8ºfm\ӻҨ&V|sݰ&rna/0~ӓQ&f#L>Bݚ$i/_.o/dS-Z? 旟JH+[>S6As y\͚pFY\(z<'ctq& !.!-KL7~mGF{.SD~_-a䗒ȫ[tmeS?M{Ar#"|!⍼ZLLKigDQV3IYu~)ڳ_18{RK~Y\9/9Kק[43KR=-5Vߢ._[>THK628 -t͊̿ z -~zᗉ7ᗖ%8KU V/^_ހ_ez*` A 8`,px dN6n*V=ay,2Enwxckj2(.x{m o'}?ݰ>!oωH<^s"R NK~Q rF~y;.ڞA^_Xr~y;txZ_v"*8HKoWK_|!~Q?>_b,?@|rwݚpt~yoZ: -%JinxESV/#wNN9nMN(,t|i^9]h /6[L%w[EO1թظnks!w&\>[8?u&uS`︧!G#VtqJ@P  -0@ X@؁<AA2H+)eMVҪJaWIkG)ff)];o C2Iav}ub( ] 4 %b E?+KWKVrdy"R\eJgʙwv^EM"+!+EF)QʟSU=W~J鏆R^lu (^o}>μ Gd)Jy5̕DB(݉BVHw;;ә(8 ^iZz>\_1=w[Nًe ,S DG;GYfpݞ'YZ34I% ҎTsS,RIsU\"K鰴F*FEdCZq+D,Տ Ad)Ad~UD1DgȂUhP:`&`18<| A -X9mSΰ(b{YMMktX a 6c4&Ei7L5kmb־w5ٸESJS,6_?lW. ,vi)O1RIa7k.1*.^ B_K\Qa?-=qjn d&~1Hc5|?~y - OgHA2ZLwؚp_y77Óuf =B3=!SH e׬_rUu54Tt5oqNN=Zisk6?V7W1-^d,b˱;S-M|[m[YIrw"#[;63oF:*^,jBd)hBd.Q HjZ@ V;p@"? H)9KִZZWGi-4jWTֽ [؛hGY_YU0B/Onܷf%+%>RvJ/Y)a(]  ҹ-^}f~ad@)R~A?K&^vC8#gĄD>s& N-D&:3xKq~7Nz[qPUSYM>ũ?2hg&-FrX9ư糋SXRK-ƕ席wCoOnu>LI!<SnN'T! >4V)_6n%* T@ 4@ (00`q -X`@> Ou4Ϩjiucubi] y6oHrJ%ea~v8e,Q|)-tQb{6vY^yf0Ni/+Ϭ\)8ʯ9hׄ]z|&9K|$$ S>y:N1kn9|X-H}EKO!r!\kAy䚇pƜᄯqJR[p;S22>š0د MN qzN=;V3-:@ N_rTcl/wX ᔎ{R-5q9%nkWU9>ocIz2\wNi,U.$H^Z)4 KThP:`&`18<| A -.^?dE*gVZJhC5m,dL5S%2흌cve^Fҕ>ۥ_-~M"; -SSuJJDrܪjrmeZdStr09aҝ܊OD؜KˢV,pK{IE|Y 7]$sp%^r) % -iN;!'&t+:Y71=K+W w[q s+/WJJj>PfHSaAPR=? UzPy cOci)<[T3kf_j?HKţc5)ovϒ“}g4_S - -_=s -yExvt0#G z*` A 8`,px dK)VUcS[5U6KuۘrV|.mZ[YKX&b]l(ɳPɊ{lV_ˆ/I˂)DiYj" 6[y(|F,_oÈӲtξ!^[Ikq/Y,9Hڎ{>S?Lk 5 /4/rk™k`|LLΌ ޕ"qiiI IlO=_Qw'W3Ne ^N5'V3)]~k9$V7tӏ? kNĚ5KuPʼnK--溭P5mu Kj@f?:md޲[w4R|/2K[2},Rė;QhP:`&`18<| AJ?!ZDhU+&.9Ժĥ6^")1eմcriQZ:O6n u|8zYa Y) _?#5._P7{@ш;RF#==2轡+5v8Hs,탫S+f;w"AdO? \gk Y)IYBz;ҳ<%9wI \H2ePGZW2|Щf,{ JikRz -QUMB)sE9>RFRtm+\@@ =0#03v.D~A R -kVVd>^ɦu󔾗^:F$4KJK[iv׮Lז(%[GrhJ9$!"LV9DP0m j"|5뫠0UP\&+e*LYx:BD~W6 9~JѬ*Fj}YR޺ڣ~Y \ -r5ͬW q% dVҚnZ[BS{hf.PChS4R&)efk h{ \4I{i~- b[_UUVȓ gɝa yv{Jq:fVZ<焮¦JRR2+TjO)OX8(#(gA)^ˡ7" >S(R?. -j5 Y$%e$ e$C) t1-r\VBpSJ#߶Pн:RrVPUNآz]:'N؝O0oѕ 'FγeVhilTlvf,>ibe\8`sڸnsWۄ.?϶93^%O(#K<괬0?+̊77B]~>^_NsBw"iٴ/|'˗}_,\Xɻ~y@esOO:vȀoUߑ<3k6dBBZۙ$93O㜙\Z% -_u~Ev.A(^%ȲB5~fMIY+=A A9)~f]W[+wʋ>#3#7&޽<ֻʘ#TXzrZZ[ -6 -V69%{S9>K޿/ZX~v7RP2P+`ɝKr 颕@JJҠ4PHN`4, 1`8iJ+ vipHK8iH/ 44!( Ґ" VlVȡͪMMe?`Z͆€q"`Z܀ui{7:\&O{/ );ng9ˢ"m5tYD Ŝ?cΆoBѐrj}2܏"67wIs1絽a_&tx㠡W[9O; -"4+Irts$H&kPʦ$ޛ L NNHH: r*i=*7w*d3*1TN1ձYx>TNs^*ݩظR.ffVGATN{0pcRˁ|UͭB׻~B*Q9I/;ߖy6%WND% -hz`F`f, X \ "VNd+˨ghM#Gfi] /geiYʥ.]ab1Inһhq̆1+~*+%F)ѲR~"WN0J>-kҿ&.QDvwP*vܗ| (Y)-Rs(é*%%+o}"mgqMPʣ/5~ldO:./.-'Ƃ<3vHxbVbbrz6nh:+`xY(in>ng*~I<0"M'mwkRjP_n%* T@ 4@ (00`q -X`@> a+tiuKkjffV&c^8I%|Sj@[ ViZj -G 8@&hW'cLR&Y)0J1J1)F)rܓ6N)2BeM)b8%qJE?A|rmXA)1PJƵ{^>Rv<01P\2yk™{7. O[z< 9yo&LK %:=\b(JzK5{+r -<y2rVB PEb=п'O&?* M| aK55H5#KO7[l ma_|}p/(\3-(ǎ+ji|%6u5}ṭcҚt'2KIʠ[MBSo;,󻫉@tSikg\@@ =0#03v.D~A Re>C0QZݳrC|)f Ì1-HюfUp+,0NfSyf%!5_sVrޓİIf'd!2gNG9gwѹ9ӞqhK"""m)""""EDD-Rl+"T"EDDĊ 2L{]wp_k=ZKԔijh2v ەf|6-.jnH)+rmϳg4W%aI2\D>hOWrWNؖxnUZ|1{lʁk٪U>+vv6y"Җ8ߧ7"*)LνtLT 42M&)<(7gWMAM ɃZj)ASF_ؖX -Ȁ(Ѐ*:p -x`pH~@^1T#`YmS4ưsQW0rF;躈tCR8E9V0|$&9OC\3=6+sQ)p{ZkS>+ >`5My&?ݺ?/ܾDEԔr)Tr!4k.\r$B[~f̮v?wASFOAS6 çMzq^֒L]_.u\G} .%-NM8lTK)Zli BLkx@D]IKF}H}Cj:(QN6H%l#T̚羐h?ڪU̠4}ޓ9CZ|O\_0}>a>{t갔*:&t+uW><bR!w.o4ޥS#R(d$IP$&%AE Zt$DM F8L$I`'A7 $HH&Ob=fxAOWz<UoP/x4G7h -<СithcYUo0e7ye^x8wl67xmD!G. C5ѡG\r̰5F񷎉2tMt鷢 2tMm}2+;.Ӑ!T&':)`mb&O!C^tG%_d#\z HKHue8,G"ҥ4i ŻR]GO}a~R=fIf2C%cd&E<`͙ؤoȓZzLӉ])QDFf5Hgn6t~i1"?Rbj|sVb޻g.\_Z{HG뉂Ԏ$ѳRo~H_Y -Ȁ(Ѐ*:p -x`pH~@^9NZQ4N)j9qtU{SUp8MA=NWH1S⸆8S^8LS!NmqջE#rU}uq'xtU}ٵ&yDx}k5}Ow-9b%YCwE}IC^wXHߨUcO-\J~}y*'[ I7yꡬw/Gp>eN %!>rd4őD$ l +4 usem[J,Ҧ|5:ˎKJc#ڔ<:vQ?jSUh6j[vuv=A~ch-ksn1M>i>ԩ5NY: ET=άA0NAt=Ne-H: ׼2  -4` -hDh`03؁8\.dZK4Mּ0^FUw0:F[:I4MRlDsGS#cnbGH \]x6uZ'JJ()QR֋SRIÚJ?$?_yZ(ZBdN颤՘2wJ]:t2_TxӒ_ 2=$䩇2(wFo2ueUxKJt8%h.TKb| Ԡ+r%pɁJWGuE:FuV^Wʶ)st_;ϖQȲ /6j˷uyQ5M螬`MsXMT@yiHJͳɿ<-d9}#2 $uHJS%kYIx*] -Ȁ(Ѐ*:p -x`pH~8bv,ε<8c:[hm+#Hw1v-_8Θ+9o]\E #)()vyR !$],BR^ -ϯ]7zՋ.7i~yw()yo}F.eL:zF.e,- -[+ ,$wp)2ݽeIR2jx$hsS[ D+|Ԇ\ -#\SD>!,6[z.%fIt$ -@0&ѕwYH>ˤ -UFTvb%2$FlRlCU{d)殔Ȃ -z^/0gz_•-Ȃw# jYPY)dA/ uyaYP]YU.푾u]̂R@P@@@@40X@n^?u+ZXyQ{lqj2V]i2jcu)krcMMFsqu::FcͱXwQ\쪣+fgօ,Q_΄=AҬIPGojfn !`U"Gî9~?tlWDtcI_v[/U9LԿ,u^OA_-bG|]oЗ@%Mp%Z ŕJ xRZv }ɲ|`֗بyY(FMY6sl,K[M=<k - Q#uμc8^b3ԧsYw_4%²,${]U%,K,K>YL_8%ZP@@,P5-Ё( &`V;p'x@2w}7wN.^8XIVYM,wqb3b9,IJ԰yXBcȂB.s}$!)3gE7yꡬ^uHJ>,ߐx[| p%$ 6 -Z\31>=iO 8;o{@,-E:E&i, H7>IQw"eMӰ,,dbgX/`Y^le)ey,vXs_Fge>GFq? ˲p0X2 R2),KvX3eJd@@ hP @ 8`f`<px$?X<#9$'q)yNn4&(&]9p;kR4g⬵?g"]E:nՋt߹n/ΣQԗo9-{'#[Ugt/ЅdѲEB_*KhYt!%+Duj;QdYvǠ/w ,wz䩇P}xJ:h[.v]@zF0 -Z.)o$m&@j #~=" -}u.`-ƱL* )+Зӛ9З*udx=&U6$G=ҵ3Зc))QOB_J a4N~}i=}!&MC_:"죟A_*/5ЗЗƯf"<}ǡ/Yg/Y ;#O -Ȁ(Ѐ*:p -x`pH~$9Y'/S*9$S%&M{ 81ΔgnYqIQy=ϭ`hXCԗCˏD}Cԗoۉ߰]qՔn?ۉN^܄-2v7E}Q~Du y/?=5}yI뫡/)#HⲐm.ǧ:mT̰Zxr;퉮[`}FWsx0=`Q eHb6wxz)QcehMBS0מsQ*JїvzlƁӹƹ-g2=HrFJRo;R'q 4M4J܃Hj>CJ4܅5DC52  -4` -hDh`03؁8q]C 3V*eCWl3a)TqgP=FcLC1Ʊq1BgW]yf]Q-xׅղ&UW_hs$'X\QS wؼIԔmM!4qkWh>x) ?_ȉ2hH'eKև.@S^xKQ)QԲpNGiG nIgOiI/x5V*H -lT0/+:(wڔك<=ugQU)jac^Wj=$M5ltn-L|#GG톦4!3w; _)a,X,hd,}agU7.e5'8"eQRfc,*Ys;K4W2|Y˒NSXOĿfW/dr)BhJ֚h-NȨ4sO Ocѧ2'YR^` Qg'j/ڧdƥWKuO=4-NДӼ!wKg.i!S?K-a˽u躰^嚔c{+5Fl WmOp;Ekǰ;nSI9jڶ.E@^|IG9&O=C*tO\LDzpNOwOt9\|%AЧf$:,񂐑Oq"r>#5[Ye6j^ks4FkS.WxUn -MNڙQ'.- M9'e#SKHb|H4>e6K0T>LLӑ.9Դ`Ll9+4֦iI[k3p֦X[%T=*aU=֦⣭o#-Hm6oF)MG6y6}6 =YɅ9S,uOdY9kӹG6䥥$P$H AIMK5 $H"!= $p$H0`%'N'  n<$xIL^iH$'I6=eےT>uO3ӎ%鲉#fkrI*ykJ+ǓI$yNV6cD CĄ=iM 'G_m5:OB[V[[hAm=,&1~X,4)Sgam, CZS O â^ 1#\#ƌ@b ͞ndi_Lu%MHpiI%ubm"m}FuɃ|zoblM9OٞbTy67j.TmM6 -ҹO4oi+ K_?8*wlam>Nם~Jk)"U.2  -4` -hDh`03؁8 F:P#/c͌+YU0>4bt}~8r 5ggcV.}()b!EBRrDk)J=H֢jx)m]-X~Zt.QS~%p򙸠&|JM04%ULhSٰ6?d)o> fJ=6_#]>ͧi>jCd#.tnǤ|ܵ dtex--͵T]Lʟ=,Kc6m^VxO(*wڔ<=[ϳbT 96u -M_^vG^>jXhmfiL纐.i>t2=N*@S&!]?ph+hʎnF`ҥ鏠)]ДƅRⅻ)F-.;caTO21Fs2DS*.h 1YX~!a0-+Vq@EҬhS2DM H]Jx𝌿^ͦӅ2!\nf@)?:0L{+CJo7æl])IyKWNƶnRLٟ6䣒O|ݲp*0K9RvA-abK]N\)Yv 5#ڔO,ҮB5˪[xlYؔ<]zgOQ착,mԞM9S5Iٱ;E?PB&Z4 -Im!;_i)YARKlJM?Nv׽*J -^Q -( rJ@h@z`F0+8 < |  ^v4H{XvTO2FhSUe5e -,|x!ƜZXO3iY -󌻚4XŢf1-JX-ܼ&wx3舰6e;0}~ڰs()QRr()7†Z;:&"JF2W*.E+e~a{s?uz )g )7{_jR/|TR%+K}˗g&lx{%Tai%%XT+#+6;]xHv٨Ӽle^^/FQM{_gcTv=p)e~m]m>*/Mn׼ch˶+ҹ[Lgw @Rfس{ ),#TNf8$eHʋ2  -4` -hDh`03؁8,ⲣF6GRGv1QZh]q)3952bc>Wj@:QRn!)D7ݚZխ8%b()!7b)!7KvׅܟI r!1w_I9}'CO $ecߥݑԛR/uzFZ ׸oڕ}c8ߝq.I׮yͯnL.KVu"ՍM \x+9o7W7vg$zNLwބtwb|} B$3AEsXoϫ~rُeYYYaey*(R A6>G׿̱E5@8{,jʳ"c[g^hđ9睮(G[OGKc??3J?O1OOV܇_ɦ&fWF rKţ)3|[yAETn ([B-adꃯ.睾{}NW/:}{]8}ߣ C_e^ҽ{=z}e^ߋp^#^Uh%.uGW߹%@> Rv@0d5aOEUJb$CΝI!o;-/Hj/덬Sr!RJ>R*T٫h;Ǣ-՝zq}^Cӱ漻1 D)U;Mu۩#};k:v:C)yC)A)㥏+}YO)ïB)mkY(\ -P@ 4@ t,00 <>  r))F3jXqnfJV?c;kaD)O+%u#+\%fVe,6ǿ-R׬ݢ\UFF)I)ߣUJDF)E^\9WCC/='PקDpi{|.IL Q0~۟L]tgk'j(5-=MLF t7)-ݢKD_bB: Pwڟ]M6YSn^j/CmcWWf/{ԢFтVB5fɦzuuc2z`NWMIlG,Gn#OAMwI63H6sH6Y P   +;p'p@" 0H)\JfV=j -Xc d5a13S$elF{Ja]Í2*c)F`LkRe]#)% YI)HJ (evS~YVʟ6jJX#)^M*eDYʔ=ZZd(|ř*4KY}gJ8TdrRRBJ$\dDމdmAޏfɿkC8{Y'F2IDwZzB  r)yiu#ia݌nя0XɘsT0R1A;3:3Mso[(#v0b&E(%R2J7I)I)Nۦ4K _L)/7mR)rm'I)YRN&l-%)dR땲_J\RNM?R谇X^U?oAO{[+2Y[Rܵ!e/~NLu^1͋^wjB$||Zf1v*e[U rꂧ9tE[qWWG/<ͱXY^CI)T ,LB)=[m^=G?w9~JBs1(e=RPzJ9*$2$R\ -P@ 4@ t,00 <>  r)-Y0e;63F8;:|"[WPEm -AdɃ -]#m -zvUt>݃ -C8 -S?w;؎)S9 -[ّzou szrN8;xHVjPe[W^R/'[UvUG/寨Z%""EM-):RhRXRH1b"LJ )XIb'A)R8R|H "%LJ2))LRM%Q3I꒐,]NU$4_n 暐y2 53ɑٜLt$q!x8$tŤ@{Rp4advWIC_ѐZ2RhH2=m!$7L6צ߸vn4ttILIyH7dM2/WƜ~r1hf>^M6)И K3P~$w1$|'4܇f霚55K^!x.3ei>GHL}x4{t^iH# RkϒM~G<˱}Xy^$e,SKLH6K9H6e"tq,fp+M[3\Mߑl޻G8v*(vd~MkH6 H6O!TvJP   +;p'p@" 0H)ӟ%FUP 2F7Oӕ 'h1%JZҥKqҮ^Sp]o[aka:@A)IYRR~')eR,чtU[7rJY -}lmSBR dSޔrS])?VSbqQλއSϟqM)oN0H]\8VǩdT!=ѻLޛp hHB;-!33#2_9StƞR9uANSoѶ覧9ؽpJг5V\nZzj>5$kk6=lNWݡFY˚|\ϽGHzRZDQ[SZ=4.Q(:@ A V`vNDAa R6VRӌL3:Fе YcgMytK` -YGf\ˌ02<bNVflpݒFrʢ䔟KN9W)ߔs)3g~_2ǍR n釒RnPN} -1r_@^W Jy7+ /QygoLU)>_"E.fxݼ/H۟g\~M:1wݪ2/2ͩK_4eRWGrl}bJZc|73Mf*vXJRĔ'"띧jFLi~1)R#}S_FL)ΆR&1ĔwKT -hЀz`F`fb؀8px @@$@.%Q-T?.e55+D4˰~6404B;RRJ{5㘧y+1\1d -FX&ăc[A$#'% )<)0WqBQmٿSLO]G)?nDF)D'2޵#0Dh}܋)կ>_4S±iBԥa*4{]2u59%>?S iۗ dFܩ<3b~Q]6xs[ڛPW{izA 'V9`fv -* ŝ'汹1c؅ -[x¾\0t49tPrORyRq%/|Aʶ -uJUQU>UUr h))jR4hIёB’'@)fRbH%J;)RH‘#'E E$%@J)aRII!\xj$JRl׌'iC$zt;[]24%sJdX7}LV'^۟*x-FTwȑE B: 8m~n5LVJ9uu7).hr2<.Y٤[:5NM1ui5/ZcmvXn8ZNrtՌ!~f1DmmNťiD6DG~D#6EHP   +;p'p@" 0H)TĨjjVϳvF;jza>0GcnV1dzq3zƳDsc8#2N&Xh[>_fn)VF)oJ9q7uRRhQ~Lt$Q][:$)eRV XlC)Rz(Dx+R} n6$/-}Zx5)%}& $dR#;5w{|f$CJ9es%[ZحK r^NSi:gz>goLV7vX,m_Qxts# P*|]5(nJMKR.(e(J~ZR -.Q(:@ A V`vNDAa R FDS0Fu [CʠQOM2E)YϸO-Ӿo`jFܟBp -~^$eK$),,]*R1R~UMo(=:x)@YϹ J?@> RGkT'k<7돾?>]L w/Y3q-"IH}  uP>ZUJϤ[5Z^Թ4S[yusrt{gt+E7rr՜RZ 6{DZ]ΖGv_RR*RޅR Y!oPʑ(em(2 -dHJ%@@h=0#03 -l \8< B A - RƪQWfV[aa5@)y,c kg3<c&7n@⢺?zF'HJ1oort>x)GRUWGM+t y -*N8s~a0zq)LmwKvtܽ5z 5{Z[û;pT8GYPFN*=KO*OYUо;|߬2н4 N TypKvmMɡVڠOoU -hЀz`F`fb؀8px @@$87N=itq`/rrƥ8S<:!qb7Ytyq8>Nh#q#qf߹+%2IR +#ݦ\6ErFp -Gҽ~O92/ߓrLfF/3˃w..g^5L@mzm3u!*tx - ڻ!5p/x} Lo&G^Hĵ!HIxUYKױ[s}v/\;PPr~b1~:i[i/9=d//3˱ǗR -UK) 5K։e('~mܻ5ԩ;VU -hЀz`F`fb؀8px @@$@.e֡*F-mS94qnAoc"asfm6etv8mm\WG G`cCdKsI~9_/reMKw>%_:m)t|~YK_~/YK_~/gv~+'d'oJtZl5K͛_~4H]rw'KkdC8{~FKF/x#xD!CKK՞i֭ZR/qCԢ"\^g8v?55|b.s|E[֬,l&/XlʭCO4\hGe/",f%=o##d\zJv'%@@h=0#03 -l \8< B A -XY¨NT%KgYv13.Ɣ˘0K.fe݌qc\+=Q?`:AZ<`.F/KfoIJR.򖤔(MQJ*6%/NwJtW8r1ZfdrW))e ]YN E\mCpʁ Y~urEM|5՝*n/0cSjYnSy⇪wRMGfi@fB{E7Ty -:dSO^d>̒۷|SjWzkl-3;'+2p;2YYO#4YN!V'j=Q;,3 tJ=W(:@ A V`vNDAa R!5S84mknA9؂8}Ðg3ƙHOُ9Ikqpq _',9##P0%O\. '`*#7E0{D7oRLf{2˅2~ZKY.{9zS2ϟ`e_C)f;tEf=QKW|p=W-yg15Ua_3fvT;/KǫWh[_GO --DK-thЎO;ʹ-W/3/_eeD-rBGg=/Gj/^h X`&`1 X ؁8 x|AAA2HRaUGzzڡss(N?g3v;L~!c.KqLF#0c.[%/Ω%uUu`.n\w{E^/$"E/m2ɞ \z\ŝsVƦY"\6 "\CԷ_  r)0CU3iF3hg]K2l;b Cq1 3hO1øFW`|VdLpp?ݠkrRlRTR.QmS%kwoIt?gJ-2J/ϗQ6%h}K"< \`PU JC(˖uiru0f.߬ygq5e_fpn>͏ˌwL.]HNjߔyU*8>V-RopꞃfԢ-(&pt!m*ZO.3X [cFRV|`y.@NX# -T7^A~/'IYR:FQ*:PJK0 .Q(:@ A V`vNDAa Rʈ*a~.Yfet Ȱ~1t1>4ʘRske,Yx0gZg6&0 -|q5 #) -( I)AI)_Q;~ET?JU -}5]Ѐt?ӝy>%-YS B)A"e e j? R/R(,'S?gygM) kn} Twzċ.( 4 iB[esթțn]]\BQ -nkU?dY{P!/M?ׯ:l}Nq*ޫP=`^mS{^o AC9- -zq - 44;1Unh -[ѫP hd{C*Ou!0 6s˷ -{yhvUhr5 zfzϗ~8Kܪ2/3©+8PEVͿ9v¢kPk\*TjG^hub;V浗8>,xbk 4Xţh`}\W> u<~ VW`MH .Q(:@ A V`vNDAa R 3jQW!V[zaX}6k(cˬ0c!k{HkYQYϺ2d;<"#bX`cf /̘ /˦4XOs2ʘo|R>Jƕ{ߒR{`}$`LR7$i.ؔUJ-`V@:y8žv#$v.@> RKCTRXK 5X !/StHƂ| 4dS,??ZRܪ(+z,XS6 \ Y%'9CIj=JU"o = ,4O(JYF%zR?P   +;p'p@" 0H)On+raFSj'].K1 oa U y]-`Z0(cbex_9/3B+3l3>O)?~ͬұj2J$KJ)Q -)J>N w;1B_~̦NrLJqHJ4fsLJq(;S|Kz\T0\uy(n`yCz)T|fi;D%&/K* 55Ko D|n^t4K~!ezSy[nR޼fiґ#hں,B4Yrv#4Kmhr?:`Y_w -Rk.cqo6wOY~MUSh7UFz,%c6GT:fq4K=YZhVh.QF,T÷Y*KjEHHQ!EK=)RH1CJ,)VRlIq$E)<))")RH LJ -)+['TGԥvRbIb.]j( -T1Aȥ$@5䩼KMЛ$%JTj FqFq,>ؽ5_GIVVCHjko% Lf~)ҝa}D *1wR zk7@jCԷω Sɝ?C~֚p'/I2LtGids;_;F4>.!>1CH\"RBr40Ve?쥚^ԣs+׫N-ۢR4KV~kH6U6ZlY^L{Ppsz ɦt$lfߛ$JXz[M&HH6{ n\ -P@ 4@ t,00 <>  r)+;0FjXH,gi5ϑd3L caYqbg|,VN8,n{n&}:jEڜc=8wO%##N_4s_-`^~"̫2O6e{[w$0ΥqDRSFAtSTȧpJӕ>%/*]Wh3pM◼$iH9W-͝.$|bbb?>͛~:nSͭ)RKG8uS&̢Xtͯ[zc> %sZc+ﰚkƌW);=[l^de1r|ᝮο){9_}{*j!r_Ȣn8%믟W -hЀz`F`fb؀8px @@$r5g:Vݷru+g]`clfM8q0cb3%fwjerVal0ݤ|E0sIN*=2N~Nct E13ZS'],8)˒Se'9%N٘hNSvNQC.SFrzOOqSKi!*p>.|5oygqÆ{pzjz/D87/ݩϝEČH|OfHNqS^S]&Ԣ]ݫIG-\w^k;TsܛcˇSwXKZ-""hS*S_SS߃S*IbٻpJ-ٰwNY<{N~@@h=0#03 -l \8< B Ag6,2leK!g5f1L6ca1 l08gWefWPĐfdjXR)8EXuJ dMxk6ܵS޽/ɔ)9-\+9<)o8Mٰ![ڝ fr-Z=ue9.)S)9*RҊ筑5Y%\z$OpFޟq BۋO?M=_`'5\Pܩ,N\X /T+<;; -C)Lc/0=# uL[N{ -s -'OO*{?Qf>޷,P -Kޡ*g*'n/ٚ<W(:@ A V`vNDAa RFjA-:q8CGı0;L9s!yy`P]g?p9נS-o qBClw_N=uÆQ)|; 22_o݆a^n -=r kAc+˷؊|~k~JvYn~9Jzᗾ`R -}ЫW -Ƣs}ygo"Z>kJީ3x7N9IgfIrYw򀲠?Q#VM?G4ZKwzuc]ű U}^CZMV]֘.U;MXlx}GEΉY_@fɩ|\׿G(+I,)E dl=didϗ%@@h=0#03 -l \8< B AJgKײjgSְ,]f 5f͹d"zl֨u1rUzY-1 +b)`ODKJH혵21kMQm7G&ɍ}5)ʽ")E))DRF)J4I)Jtˬ=3c꽛r{L] @)_B)~"PY. OY.sJ~ %dG<7}mK*&@$i}GO@$pt?'5@diw?+MD/|VuC)h*,{ao2~,-)B(e5\Z|bR["ۯwiʟRރRr6^(eeQP C)-yPJ6RzBjp*@5-@ L b@,p dN4P: 1ڞ6ha',c(]]8c"J"mP>k_bMuM3"g|] _uj`õR"(%()%XU2J nD;/Y۲g#|?%RV\J)wMD\&ԜJ|܆7܂2H&c\ -A) @)/ -OůTxJ :<70XoO2x.(Aݩ>w ˔Y.w쀲ZR:gܪcPJu?RPJEOֶы\bz ZcR>h!RʳPx_,r}9(˹޹NשVA)Rǧd.2<4LCG'Y9))P   +;p'p@" 0H)E)e!fu>a+K_\ -sy"RX f+K3\kg"rSLpf`UU^FVRJ܋2rwG6rp%kGV~-9Nf_2Ej)$9Ϭ?󑕫~d|Sbu5)wTlUG[St>_6D]VB/yg)|tDSTO [twZjz;5K2"iY(쟝vȭ*ڇNi;ihOq8zc oZ!flkb"1 !m8h圸g04|>yT!gW,"L#g֏) )'%UjZ4`ĀX`6`.@! 4aj -`4NY9`Na ӌ1u0Y|F^:jd<+10+N2)?_td{R"X)'Ŕnʑix-Q)oSO2dWTrsS>Sz drʯrNy!=uS,NՍ)o4)޲:Z{q8%)cJ8as|܏;BTHw)U[Z#pѝKS3R3Qwz;՟q i^.=-";_thijF39Zc8vܢkY޺Թh1dSr箴[m8{鎮]Ξ~ɂSNU+>8eQ8!Ӡ8Y{aEp^8eϊp|"96)Dh X`&`1 X ؁8 x|AAA2Ha`V]jYenb%zY8k*9\~RHrJyY1:Y)ekXa ,9o%JdKrJ4@qʮMUξaak6\).TyWAI)K2JRRʠ% ŦL*ɭg/(e[mu J@YS Sh} -kA;}}ryᚇpbʗ). ^Νq)8!LMMs2RZ=,/564u=^GwƱY z PJ뻈)PJɒ%f.OlS>l]J)(DLi<t4C)U OB)@)S dʝ)Y)G%UjZ4`ĀX`6`.@! ta,ݥQW1dqZ7н CcXM%1e.+;^0,ĔiWm02<7>qZ)JJMs.TlJL)Oa'jLy)T.ɟy 9E-*>)jM9dOQtQK<L/#9Zw~-@>S(Ĕ_S2xkY<5HjZF*/y@A?.ͻ) Pv6KO[Q奎ʩ:9M~vOs;xE_s055޳ݔu|kLA9Ym!}#8^t;]dgr7V?-‘$5'FNiN'MUjZ4`ĀX`6`.@! KeT EwL1B ѕ]tcʤO5N?N[Y2ɸJX. -m l ]Q]t<<&?vr*+zYNsT,Ufw/7-Z֘, -鈣 zg98eiN9Fh.t{Bbm2W8e,)9dv)UjZ4`ĀX`6`.@! ɨ&Ul._` =5au)U)gMu 3+aF`ޕӊ6>S7үN$SPDH7)/oZ>{Oy6R JJ%@@h=0#03 -l \8< B A -XًUBhm;C@g2V_*Yc?c<呕cZWvfE,yJ9f{e&&0{IJZ'5() )|MF)7nJch#Q&?Ig2d]`h$'rn ^_ǻ>!Ariʉ-DrnU}^eG*-ڬMؚ?[y(MYc֘N2yWGUIsĘsbL9 ;1;!l6a7r=vΝ{Tgu=νrH)EDDJJ)05 " RDdz" "VDHNa˒Mz]]?vx([2v.q64f& zR櫖 ARF^K9q 2?I9=IIQ(: 0#03 N D $O|$.}ՒKɗ\2ޔo+{!u rxubrB~H!)p).ey H'$R&r/IJlxIɈ 18r'uǧ,qzZ -O$繸w15ݪb/}S7i -k1N..Y -x6sE5bi+7ou3ko8I`34I?Nn쇤 R|G"# )oCR&!)%dy]< -P@ 4@ t@`F`f` X  <><@ AH+8C2YZhh@;giC!#sK".O]s+݆!Zhdy8oKf$I {v$)'h[$TF.d.^~.IR>$eLٵ)x[dÉ}w@Rիb&!O'B !|B>!O'B !|B>!O'B !|B>!O'B !V$Q-T!nd449V̐)gô11b<{>DR{FUx&Gi>@-v~PYGBi-E'#).IR -6e-26CB$K-VnIR]?L !:#Rb7;ܮ'Ky zڲ|聤 I"m RsRn} -?\]xq|zLLOwTw -OqgĤ1)q|+ۊ͝^GؚrfBv6_:C;wfB7>ĥ} -@~y -G8;#}zk|a7}mr)˞[0`.^T8YTj_\RzzBKJ7P?x)%J*?WmK`sUr&طzϫP X@V`v'p"DʤrVǪXͤC{ڡg9N5NiavXzɐ12,ie"vx4s+,+L8bKo,RF_ Y7%l_0{gf%s򵋄J_v.:΅B$ -N{\kerU`'{]wwA`n( -O!0 R=^ݞ7bm&!x1 -NIqOscPE2ѱrw>ˢj)KO(4o (t9d|a<m3M@`zwZgLc6&Hp.^ !0OB`#"%SfEe8W͓ϫP X@V`v'p"DZ)c-Dvή֗LC58LE0K:XWa!w:h4:p>Z m|Q!s $y_6E`l f^'!ed`ޓїG$}yOZF_ٔ)g -&p=00Hrj&O}:@>[ ~.x ޹oKv b_Tgzc9rG!Be، _]?EHrP\ˋͫ(1jtԋ=&T-.Zr~)8e885U}4],]<ߢpur ΞΎi6;SJn>L=;2Q_uۅJd# -q(D&:󈔉*@5-=` DH`6`,p@ HI GUe 5ȨhM+=}T1rXƘs e&Z&gV:h{J5K{zne3J#ӁZF,d6_O:n"\ݒ<nwvwGkR.K/~,Iʰ$)22GkGk Nt)D*&Q\]'U҄!wgxb!0|;56y>]MǤzeSC\q[V䥆ԭ'9MWMv)gϷq(x8>5M/f.k42^1EjEp&%MMFSLXǽ熿U^cm7]f}NsQR?zcƏFFK {s7TM{|6]?}]k -#u CS?͉Ihirm 42ZM)4P X@V`v'p"D5F5FSVrF7LOLcg㴩6wAS97iq0!SpM 9׾rh bgߑ-e4%[)wKrdovoq)Ku>"IvW+H)Hʭ[ΣѮKўNk^Y=򣬰:捗ndF?_-6EnJ:qIegMO J1.\җ]}`SzL8Ыh,ӏAl)[/PFobr|}q< }ɊLnLnzzKvYWϓ,|@VW)<&LV+7(Y)Ġ}^jvSizaYZ?ٯrLkm -ĒԾx97jG *"'UO*r 2H7 5e+~䉠4˲\L‘N+e< -r,S -h L , D+;`pA"H_vZU4Uʨirݰ-JXD[ΑtXJ@Ak& s0ퟣQZXqRIRҷJʷd$%U՟K?VR68g(2cFV[d)3җ~ؾ)5Eom,M5%n&C~'A/r'9 M98S?7 BS#1)kKvAq;Xe8r6Cp J3 NiSG/ eu7߭{K s?p'tu'9}cG:pƩy,j14duՖ[ⵏ֤.qvrMДДOEazMYM>M9TjZz00 -lX.?@A@2 RUC0rFhg]+aݠɕS2m'1hx -c_bY͸ -Ӿb0;ڔ?-4%]p)A]׷ZGʍV%\\)oJ_v@Ҕe~$IS>YZѦmB&J'S&#)?ԌM)4įScS?EJ^ᜱGXҹ8\*O&cS4w8,+s|VjP[յK E-f$J;_5q8(C~4C{ǭdi=g{0V]ϕS{CeYJSASs)c$¯ 9Д ˵MDSJY)xDh0"@$8 x|x A$$IIZ5HS}zLJFWB[h6T, t!>~fig!=]47CFiOB7h0'%M -;gIIS;/-ҍ|xn^̔m]JO[)?ܔ9c7𹨛oçұlhJQh:hs-Ho*Oi+ڮ %\;rZ?ctc1>"1ǪUů(ycujIyBU:RP)S+i6~Y(!~X<2 6^Ps>zC/SEV)l  -)Jv:oA˼5qt,{M{qVf|@!uce/vUp`*qMU4B<5)RHѓb H3)R"H$J;),)NR\xHH''E %@HJDRHI&<켨N5AvmC"Ӻ0(ωq\.ZDrI1{"t6FkS,= J]a[>Y%rmdݔ%󎻷݊xw?Y|}w-d&-P$C7Mz¿u}C#Ve7[9<]t@ȧ"u6Hz'%D|˷%\;kNtJzF||ƻ14w?6&&>&#+cm+ۥ9snUaԥq(RW7>;NrbekӚܕkt ڴ=:SfUy=Ά,MVM`~k3FFU#.MlkW ks]s(Mz]6xDh0"@$8 x|x A$$\6ZJStgi 6qXo#eXrzʽZ"7@/d2Xżw~$Xd$,IEZFR̛bm42"J$RH)&blZZEOt8gx?(CCW|*R6H4J:LUy nU7>ɈMgn>tS3 9lt];UUgQuS7whK鹗)կ+3; # -cA6S -séS;#jk9zJ-|j}vysQ*YRzdeW -ȁ'Bkˬ2PR V}gr&HFx^h0"@$8 x|x A$$ V:Hv:,0԰lavXZfrhWsXO1 :|K+Yq&zv9!MacbYv<*lkUMexc{pM#tH/kB_~B})9ЗHNi| -}]KIXۡ//ǯy BQlU 1^o;65eĻS!71\*Ŭ?zcɩrB5y$R$W:Sk)3oS0_Fr - LHN]YF(ُ4G$s>Cr|m%t9{ɩy$$UgʷN)+Ϩ&ZUbq$SHNWUIOIɉ<5)RHѓb H3)R"H$J;),)NR\xHH''E %@HJDRHI&Ɨ @b|L -eáze qKgx+ݪN},=Mg_嘂Q/ZS~'ZOEي[َ8ǐ*Ir=@};0)Sɠ4Q(: 0#03 N D $wzhQI_YZwwL1cf1W34A{!CzvЮ^3Bs5!&7wt.IqJ)I]2ܔNʰ;?Yk$Ii$)zIyEr6Ӓe$kyazgC<碜CKT{%*2' 78ORDH}%Q)?Y W_AxBmtĽXYv3]+= : fY)}SmO{ڞN ,h[[RclH """1"RL0}ys]u}n1KufZ>|g?*աRm]|kҮNvkʸiWa3vMſLϼ -5[2[[܂ 8]mא^sq+~5̭/el쫂df%?-sQ< -=" -"R B\@@0#03H \@   A -HR vή6MqLi7gh3ƙxl!nSg8{N'vI]q +w R6nؠ"fQ3W^x+a+-C}񕎉+}}}ʟ_?(_E*_E)_YMWb4l ʲ68T\ *ӣސ+6H jOPmHVmHSꋻ:y7^N}qG앷7}}} ܬ5(7kPnv67;'_lܬ:9h/ڞ#|oC;3bD:&V苮O;5+#O-[W= 2%e:ELOOw{iB|(= kS_U wr{tѝf.fM ɷ Ɇ{gaBʸ L#D0=-;7Gt淳m3sL}L3:1sxoJZfJ.*`f -̉kyE0^5h8`F`f` -XA ؁<@A$ -ȥTƩV^3kt]q8:8c{i9dA0-qLW-yGݵ uqD0q/78AWC)7kgRÅY  !sSLŰ -+5չT枬U~V˧ʽnΊ_ZT◕c[헕[!2 -n}Qg~II_W·_&O}I^g2^_ftK(KNLw#^I8ӽiN`FF|p_Z[Oˉ1zM<-heѺuP`; sncG:늬"kdٓPQe{7[o-7[َ{(W=0uW9q^'-ޒ$O/6S=5p*b=1x?8J % 4@ t@00 Q -b " | T@.Sr9]  q3UrIrR9(0k^9f9,+<8I]/\bLPb AQ -B)P=UY\G\O)TUJ٪(eVx%L,R" "*DdY:Trok#K$ m/-sC+J6rqҺ#ig7>kO/IԤyj|C8sEN Iϐ3E8Ō 313-!DٛV)wWjپW"(ef@|V)ZwЭ{N`RvGwGtΝǠ[B)wnzӾJi -c<R&A)]٢(q를OrUÔo(e0 - NB)PJE)D5h8`F`f` -XA ؁<@A$ -ȥq .4Yg ,q!36y|,PaJ)l?YG7b&3‰P -'YR({1RܫZQ1RVR~>ZRPcE)e()RVQoRB) ⃵)\J肢+]PePʝ5'J9|S("tP?;R~7!9J3)LgbB/:eƋH_qtA{JoZr.Sw/gE肺oIi 6+ջ(ftAC BWs7[6GpKe{ELٶ[(~8qtA-\/=~=Z!@t ]Pk>R TF$7Et߶s  4@ t@00 Q -b " | T@.eW/C|^[ry}=\>osXXKA,/m!?kxAu4V NJt}u,/M_/^%|C,txOK$Rez~LeW_4_V"!_*~9Be- ڹ].} [ +JeC/lLcM>򩟾x6=L)Y`dI R3S ʒgzFZ-edӐZ5~Bd6?Y&?tGwwmh]}[?%0]WxO!]1i𣀹;jͳF}BE ~v1kLn ?l,.(e%tA Is#~y]|!"HzDZD懔ȂKTh0`&` 8 D @2H\J#.ViY/z+ MkZftA% dm'X~q^ֵXO'R;+/?~E)ZE)G,*JaB(嗊R0!}|{oSW+:E)(e.|OQʔ9\di,&ϮUJ,"}Q]PJy]ЉY}S?FtA!M$gBݙiδ,O3(g DOf0=^\7VO7]){B.E:r:(+ڐ[6V֙:RyVvkdwn3\Mvt@)PJER~uĔ_RRFV(ea(SQ -.Q hZzX@$Vlv. d~ r) =&}!`2PY$cb- ']P kc*:YW+2VI*|E)eyhB(NJRR4!JY_W_*dSʟSTUJo*<(%2RQrLQJdB)vU8PJӛ+J)N(RLA)E(nRFGJaG>Rr:4 Jy᪇pRG ?&ʰGVӓδĴ 'BJ͔,imJܯ-/PVǝnxRД?%hun}/2-m+qLO@)H)SyP(RPtL.m _{GEc J9RORһz<> - A)D)mPӊRpj@ p"A`<px$ H) K)g]iJ9m'`,r,XdMy tfo`{8)sXiX_)RtRW -+JQPJത19XzCzNKeܫkQKL-U<8e*`CyxNi=l^3X{1t>9hEǍȊN -N;oËpM|꧝ )&Ӂg.LUŔx)Sބ` ;iSLDљ3kR{;t>wQA'hkuEn}='ppn1N)s[RRpfkbֶ?^`cbN陂S)dܩ$oÔ98e2&26r\@@0#03H \@   A -HR:Y8Ks"V;X"rk8YI N9FYװeU - ';YVcoS4SʔS)N+NCOKLټzJyLWE)ʽUZhWQk! SΣNbƔhĔhĔ?\_Ĕ((_Itt7y<#i! @'BLJ/iC8s) BXdyeo %:3|43͋2<ĬD9}`J%yA)O54PtݱVJӗFi[?ܦ˟~MRL( >F‚#6KضȡQURY?ȥbN<@٦^xE7uQԮꇺԞʚi9Jy6[ݦ=NWT-N֜|#VB!EK=) ))RH1b!%(RĐb#'N))RDR$RdR|I LJ -)g7I~vlF$ I"oi-~hnmϟh_;6)Q*(mnOl" *DsChȦh(Uй!4d;-c۩寧ZOC/SclxECg薮V4Th!4NUC<-4tfcECu@Cue!>g~atKIK^L#Ϛ^XIȲ PZz3=-1q$(߯iV\I6Eo8՝nzfAP׿*0SSmȿm@-Ne9Of߾ٚ3{ۖ[ ؗd;J^Aɝ@iyɦ3tK5UKw$--#L"ٌd3e% 4@ t@00 Q -b " | Ԟgs=j88lJ8n5,i5Wrlɘn-gf^99+p)r_t9$X$™K6RfIHOLt鉒3({ܲ(J #AU?lFkF)*C}_}ɦɦ}$] -klz_t?:Lf~q[HH6y7bZEioF)ɦ.$n$6еɦQ$g*"H6}H63ӏ ;l檑lR h5)4)RHѓ‘b H3)R"I"JJ )6RxR8Hq"!E$E"E&G)ɤJ -!:O/o%i?S ~cFScyg)JI|qMrm!ؿQ*#I$d3hlBCzECh!4?=fs$DV5l;W4Dq` -N9 -lq~d'+o"6\"ѱ?|eO>>kO'H6^~C8cJO649MȊ:31+Srixw:LH`w~U>pJiwst볂>!+_fkm8`;30O|m9itd-TTKc19nO3-OKƁKlZ߼!4|rT_jG)\W?dSuMO WI6D5h8`F`f` -XA ؁<@A$ -ȥ p=]j=./1LrNƚ2DMI6COZZ`\zYfX(ܺ,_P9R~(!rN|d;ULLFQJGuzJLe^ft{gZQʷB(%GާV+qʽ~7C(ʷ"(%Rve}Q$JM%߶ģY§~|[dJQ?B[nhC8cJOĪ!ޙ ݵǙMOwGNJ3(]Un,{ae]} Yftg-٤+9ZHko+hfxx25_`Y2wm­QE=HYb>ȶLQ;({Q1|xJS'woQX2[%͵Lӝ*ޏTc@܈wE*pj@ p"A`<px$ H) K)ݱt3)325q\o5v|<2@&- vey]'1NB4㼯 I -IT iYXSxT:VfA/{V"D1)eX,iYѥK1vGW:% )quˁ9 z%gg|ML/ b * t:Ӥ̠3>(x !=+c_W5>xf% !4K -Šh]N~Q`f40^6,L R@* 2ϩ9\Msu eZ93kls- $t ,qE셜cs+Ls8I˜\x\vA.aE)?/F+Jy$rW߭ 7Qq= S_IQʗ4?vtACjSUE^)iA%'E -2 /DoF -)ޯϹ~ecϦ,4,{ޤ˻{ӊ2"Kk ,\`jDdliYуRv芘l۞ޢAD,Sr><zJYvEC{T|^!%$eiQ{eɍHi}KYUh0`&` 8 D @2HdKn^3ksy]SgrCa6Xc_ -Hdm]Z_/%I}SY]!GR؞ ('ђ=L z wPNfZTW?x{%TO: t鴠9%hju9nwfh@ C,dM@bkd "KgfltНn[AlG凈,YY&Hd^/! VR","Kg_NEd4-=` L , D+6;p@2?dR?,zs9MatC>cX524ÚY,yde-k`8#TrrNfVd}Y7pʯJ٬(%#R6GWojdWvs)Zw7TYY8CqBgeS~Suen YNޞSpJmxs|S?}ohR2ԻNAΪpfz?r{$3bVLO:%) =)yط_|WYc:/M,h{mg ǭ_"Aцncާ:SG>T~9O~z+1yn[_~b9QN9zRx/~%t4:(28b*/[_FN\.˦˞G>M -)}胊67zS[胺{6[Ang>DT㊘l[+HTƏt rv:>bs͔y/eAϣZx} AE*}W hZzX@$Vlv. d~ }UtS9V[yL"oKF<[H4O8[/8G~!4X457~R+T~3JhB槊`۹m,kFc=LEVWCʽ.)~QˍJEmFTT腌@f+~9B[R6+'/ᅤ_!|wתpvq1AYD@EA'1=#235~گ:;Q89A3*hF뚖}ǫs1mh6/LkFߊ>(蘥"m,bٛDfmCf)& \/ݜ$w!#kGY&ȪvdĝKTh0`&` 8 D @2H?_uͪYTNXfYAX˚ZXg5@'~LgS ~U ,Nw;RfS 7Yf{o{mt'ڠ'Ε9󹔺Sڮkumb{5?FO|0=|22ȚquT.PQJeQ9*{aq+Bajϡb|yu)Uw?jSw N)zjxe?h5)4)RHѓ‘b H3)R"I"JJ )6RxR8Hq"!E$E"E&G)ɤJ -ئKK~zٯN' -$& ƓKӁy82L] 5'&$$Hr$: K|KIuPpa_h ;2㺴kLGA*V7H&+F"HI6sJхH69e GBl;A;nFTh%K[%3 +Oktxl_ds™{5u҅,/Y\M~H6P3S - ެf޺֕dS|>@=Zp[_5$0]O hCE8sDgjy YDtb |5=:#m+9|u='/Zd3WC9|/A~_}ɦA@݇dӷf%Հ:  D(`1x`H@> R@ uI.4'Xm;s8X2cl`M}y3D) R.g+HbkOJYT> -T="y;"R_Wcpj@ p"A`<px$ H) KUDD%dyC1o5yb/{b&Vb/Mʝd0,nG8C! j%L!k"˶"KoS*@Vȹg\7Nf/o |Z^ %( sJ_؃ri_"x _#"s S 3™1"$HnɛDgbVzSLJR|FbF0qݭU E7Vʊ_\=Mw4E/ӯm--%K yA=8ϻbf+J<+cK2ZqNԻuiDžQuFR˕!rr/*~9+_E/g7ND.("dKġ%:~pӊ_~{%R;k}S䗫tDUo 0bDw%Wr3=KNJK=~sdPJ=@GG4#)m{εG(}ﻏQL6k~oa2.0M9LLJY^Yvꨲ]*yTt]e[h|E7sDe9r,]Kmj쳧ԞQ||oI/%?G*:Ͽ:rsfeWrjRhR4hIё'!#@)fR,DERlIq"E CHDL?)RII!%]!NtI$x7d$sKr&%~P_H$9\{Bc$=9/W/Мs~vd&BЏqZbΉMRa5ސLL*3zh&U9np!6 םGP/V]#xhsC7Cz (Q9')@_r~ CSCPGjԾ@3tkj|ަU C^jw(#դФhHђ#EO -C -G#)&R̤XH$%+)1HI E@)~R$BJ*)b ݓhtIz?S$ƾ$ y>`  غ|^ -Qg/%IIh5_\JD%O?8dF|͖NyTqeȍ}9R_ЁLR W=3[?a-zDw|3=ޛItLpy4gVP -f%dHbۯmVt,\pvザh-n}>%pцܛƾc:g?0Z󬑭=BEݽZx8:fV*~Ş/1=^4ĥۯ 6򻥾7uJ鸟J)zA"% 4@ t@00 Q -b " | _~Wrf8jNncr\gk,Cw y]9*U gK9s9_/0Rrŗ*NqpJ2D8)%e?)ᆈ S/T[yq)/x}iYc*䖾tM-.>3uZ&G:$:ϣ-#>B呓WR zC8Ćo DLOOHw3IsfʉY`W8UOm{TTkMa-;Z7TI(e>аm\XVˡ|(enfkHt`6Tb/,v=́R_R>NMCPJn(fR@)c~9^@@0#03H \@   A -HRYf55|i1RTɚ{X 9iu*l,4NX`=XI`_U7%\1;lB)%QoioK6_bNy^qʀS)ϟ9劐s(Ĕ?orvQSE $ȧ~.*@'n?Lj)zgnݒ~3MGʔ-VA.+ YIItl=,M |[A З\ՓLJ -m^v3'>!+v=,/N=ir| W26{-F_n.colҵ߶w/ c ]//\`jy~qos/'曮)g862eI%[5TY]!6?[%/ / ((P=7"y.\@@0#03H \@   A -H'xX,ɉ6t6όr dK^Qw yG9*yOG8+ ryoW.R =ܭ~9_݊_ O8KKNzƑʨBwrW) -! %\&*`O˞vij}]e5YdPJ+6l@uqv)z ك™ 0JLL)HN'ݙxI3ĵ s`jyߥl0S}b4O u_hCMXT6O-՟FG̶PQMG.Etme#[=۳%hIoKyjCS4pR$MQ'wS!9):6D5h8`F`f` -XA ؁<@A$Z68KrN[9,\-g(%7Zf8$YYٖN0P98<+r8ÒqalûGNqJ{ܫ -MDZy!NKfItCuKW"RSe*{-UJg,+:{E9or.㞯$=ѥPʅ4;nFOb -2˃#,wRlZil_?$%J49)fzEgb${ 7amf9NNc.}lS]t4'un}ը?%pUeцnrۂ4|$Zf,Y#DLֻmzx َZʵTRK cuJll^Ky-PT`~*Jir[W hZzX@$Vlv. d~ r)[8:ӴqjNWK9f9g,LTq\MaΖˬ#-B)yNwws.|R96%B)<ĔC(eisBz~< 1E)JQRKՄH)3Ezhl(D]!옫9)M 0r 4<#"|~C)9)VqC8s =xL& YYt(#d8er)eeܯy[ryΩnMW ^A[Axխ{U` $|3?j/ZmPJ֩蘾;ܶA>7#Lꡔ\($gH)]PqWRr_GJGj@ p"A`<px$ H) |,K]>}2'sCi3wr<d趙-p|!g9~LbG |]K!hfJa(J>R2t;;pmpzNDD^^}#!n_٥R="hC8SuJo>dՄpʅ+NqNə蘈E8k<w= t'7F%zg|ݞDɓ&f.ii49#Ù +xyZzt>ް4۵2Rxꉑl`h'i.ܤ;1PHsI15P76 ) LUwQG7[FYzIDT Ri̶z->F[wTwS -*Oݻ"ĂURK;)|qQ?{Ls{D) -W hZzX@$Vlv. d~ +x(OӔڎ8]+Ϗc0k̋#3z[c-qL%پX{i#w5B_.N.^}yqu3ϕ7}`nPQ055[{__g{W2~٢AJt<_wZ7t+}K_QY߇_}S?*t@bpڠ9>v'f&(g:E4E4!Qrz1CLI]h*UUn\rgA ݂hB[6"0}h ,LUUO-ǣ#{BE}tq5gmDbp?9ːY*oEf9N8j{`y:_n"p4ҽ 6%Հ:  D(`1x`H@> R@*89éGXp VW[8XC>g,L5yԒ/ɜc9z9W/#r -N\dqr˚< ʢ-ؠ`ɢ-@61YR[O)oIe ro( E)N hŎ_AdG#+PJ "{|S(%]ع!洀LY$gP -ff9eO&(fK4߹_սIjcereKȡ%7uRLUE-_k(F9L5G(6K"'wAt5PWT=o1zjHj.g58QV!U PrԾ;ԁɷS'xA2[L%&&EC)zRR8R I1b&BJ$)QXI!F -O).RRS9Rؕ{Kz8LPؼoL6:DyytθǑl^&ѱGA>)]H6iKH6fG˪p4 9#---^.9`9!1>>>!ÝQw@~YЌ>!hKuu I4KцCw%fkD#TTofBst̉=nA~PmpJRﯣ<}B)]/ޑ$,@)ykgi(J% 4@;㨶41fsbLYcz&n.1Eʢәz3G3Աc[iRJ)kEDZ"RD V,2e+mRJ~/-+93ss.ד<@@(00Xn/ $ -Leaj;Ŕ}1 b#Q1Y*$̺&h'f%\N_#OjeD)/JJz:VRJLձ ;Wn1Su/<$\ˆ)SpE)yO!S,mQn"#{̲ϕPVqMɝ⬻(% Q6ɀ48F{P(eYp rii~vy]Pȕ]KK iDPR.e[T9ukESt Aҿc(1iʷYcކR:JVY[l3WlƇye9'7X'>zz_]\ .O)#_IJ+*T@ 4@ t=0#03q -lXN @2H`*)sf̨J(% -j8KTD(%_AM-!\r Ro)u -$~f$,Q]R:I)db_lUNƑQ'l>:R~ܖJ5. >R]I^Hf./&f}k=J e3|+JJ\i|C!![ͭSOj|RV{w8uMEN[}⛉O[9C'w:6Z'6ZckKU։:cf;V;vmrB)mPJʣR -PPw -R8dݕ/*:@Ă8`6`,p'px@I onRe eƈj;UwSM# Q@PDTR:(TTtI:O7eSxo}D8M$4륟MRJRb#Q=W)?M-3ʂƣkRuߺ^*zzTKUF)dRUsr7r˞tyW(YnD)mB)UyDAI̊MfR_~9YY)HH4!Jef|x91OG֥BsK%9oLRnx4! 9eN8y}R?1riV9w56JڹPnm屏UىՎ,gן@)!JiRۖ bsJ)RzB)]PJ'RJP @ L bA8x $ R@*_ QPfZO uZL0A;[KB>⨡"=2k|3*hέpeo3?_Dϒwd䀹UiU wJtPvI)R>I|ҥ(eRRJL.~},&lcRME3f}kfx8s⨺+ѓx%x/Ɩ (KQJil>(SY4P`-sc]֋sKlͱE܉3?V*.s٢#_9Y &>bgD)EP`Z~?(t@)OQ;^RR6B) ŒRJP @ L bA8x $ R@*N|:[fhk:o -jZj2!*'JA݇)H-ԷeTȥFF)+餃gR ,~#^<(eYSl<()gRBQFR!),,Hr1R>;(Jq^Rv|(' > 2?$&D)[Rro+-S d"pq.AIHLi?^+H}l()FcGu3d},VxR̲NδR~W3ӠF 2.ߚ$&dfý|p͔g34d$@).OzF5R{-#.-O]N݇(DG[ 4qbE_cR%7Ycsč=PJ[M>=̞_ّ,JBҽm?\:l2.&>W+XJP @ L bA8x $ R@*K%%6#Fͦ"E1SsXO'>UYOՄޡCA" M!F)wHJWRdWWJQ9$>S7\e|CؙK픵SvJRR/XrRS/X)攏e rه ^0%(MxNy/@$dR*f7U]TSFDAi97ʹ{nzc%uQ|_DzR:/لqRRR)/HRA䆨]SuSƕ4hQrMD2N~8eA@[q 322?cflxN` )8ŕǷ2oͬpR>p8!#,=Dq0w'%$ᔰ/x|xnI>=/'IO.e6S:&|֢z£Sx =_j2Er9"8{*kɋ[u3̶#NE2.>8eF8ePSJ/s7' )pQ8tXS)xE%` -hP`&` X   ܀^<@AH) |9z0TG{f/N^JOtDLS :ex:N|*H&!(Loa,b Ο~fUS$^=b!)KUΔSFRJ}HJ SnFRʨL`!)U*0cSE SncG3JS(z;zd&R”gg}kX>c +|| -Lo's|ED;ߙQJ.ns]N5lhr^hSE|3 kmf5oErXD*k~tǞj>Y24DހRΔgBb>RB4vJ,]G(%DR"PJJ@)(%DR"PJJ@)(%DR"PJJ@)(%DR"PJJ@)(%DNMDzntgJ5c'1)2s8&lOqn\'.$|I`'")eDF)*cd"ՂhUQg*+wwʽW">'JHϫ2N]sbt(<l,oSg3 O$S~S>NyidGvN>ҟMgz] B&xㅄDon -N9Y/|eg.e&~S)O[4'6y#t9E_1lJc:}\7im<נ+km6cfv?l.S&ŕCJšS/B}R 0k NҚWThz`F`f ؀ 8>@$dRˢx|q"$8 cdzbSj}đC-+#{pǿ)%Gv{FrJzng装)V*vsA nI)mʠLц ,D6C/]\>}(6R 2z0l2|zN) ]ӹĴ+=,|>w} ')O^)?v)w0%휪*©h.q6NW #i U4ơ wr7[չ\Bۻcb;^[f Ϯv俐<Լ"L98 -WO\ -JzJRv@)G$*:@Ă8`6`,p'px@I :0D5@ԝ:Q)ZCM4@9=b5tbF=ڈJ C|tVSHJORm2Jɐrd Jy|B||8Ruʎ7HJm-E]lZ֔v{#nimp3~(yvJ 6!Q/'_<$GZìp RMHr -D/q >g&dC<ۚR(??U,e>UǠg=̮^NU7ړP G۷Z]=.D)[6Z۬هVY^zwՅٖ}g9@)SH7ޱ޲\(z5((%g8nJ+WThz`F`f ؀ 8>@$dR|Re'{k}+SC%GWQs@[H6GuOSo uQ|'?/')bQJFR]F)m 'nTE6o%>-V$X$DSz)Jy2R,RμvیOiJ٤`ħA51n{ս>5SJB|]򼐑GNLױz]a+1,26w|ݾ[_ɽʱ YLg[y -uafuB7v(OAW껞W*s'WVW6߱(n 2?P&1I̬8%/W)&&COe|w H>?)d]ڰ_~?Nӌ_r߫(əSv/]+5uKy )Q!D9"%eWc?QPk"PWq@| VY'I)^Q jZ A,V`vp7AA -H)r#UR|j SXNMvG~D8(厵.pNeoֵ^` lj@| -R,#LJYeG=+WJ \s\!.x2| s&5t){+$\/< -ᑽrd>Ƕv7vBRSD)G#iY{f}kWY>xî%K=Bȕƅ|~o b.|Lee>eY- -<ڲ|v`|BF[?ޕ0LYlyx*dضw,UL[gY?UU1wc1޾3[RÏ^UϜ IjKƟXRTY*T@ 4@ t=0#03q -lXN @2H@|ZٽTuUɥ1VW^g f5犕۾KR{RuT/u6/u\ɥ,?Dl/wK%Qo> eI,_lP/" J}H%L[+=I~9~O1mJҠ5d̷ps,x/ > 2;;̬SŚYAW_AxBmr̽\[ߓcse1F.0BLI8Iwknnww^nS͹!E)"""EDR(ERD +"RwWθ||>|6sϟB1̳ՖihW=vnKJm~xM;m)'^Ԥ~i䔝il$vkzۯH_y/oؙxs+wްs)7IaM'q W89v7p_8G {[bIMsROMJ|-9ڙ IN+xݏ{\}D)Sw_Gxj7l>{/ZJ]d.}z7AlKWiM]/y0l7R[C?C<׷40?CXf k3 -+ywxV~~0^Bl;]ڷ(woe_Q5!WKT4Z@`BP LVp7 ^ ^JW$c#}&ըIeD&63R[ SȖbs4j2,S&R}5F:s#H!,6D'MRNwoK-[7/t>E_ϔ?hOjs'{{{Z:O:5 Q)D)T)L9ğ"g&EYfQl*[8ES[^EPl[܊-bWŧOͽLM?\GV^K~p{ 5M-+V=mU+^~e^oyདྷL,moYkv]9)u<'Rx!ōJ='^ʽ8ڙLΤ$:T_9_*޽\Ae/RO&4]榫y+ -4eg!5/uKimv/%'/u/u_z\N -gŮYJ6!*5|ZLߝշ'kk|^%Ԁ:@#@`̀v > HLeD) TuHR$=bfG(݌Yk6EgIˤٴh6ך-fJ&5;K|YˣeQRs~!eXE~j\,%RWM˛QG>o䗱Gk/OZ%5g/U_Vo_)~tK?5N]ױRR.U%R_W%6<Ӄ>)@ƚ7EőH]hGLMmNaKq8yp/MC#ĩ'?/ݻԳ5B9JU3M=5p`KV"t/XCC󏓡+Q""+k Sn -s#ɼZJOj%sanyx1ὤs&}#dȄI%MbPJ CAcZ ÀaLj0a1,V ;1 Í!ax1| xqd(%UG=["ic3fy@Ch[2y=˜Ǻ$$Or6J|GX#-IYCeG󛲆5MYCd 6ECn8nHC}{ZmYC5粆Zs~@C?4d!47'@Cρ|/@ͩvPsnKu= -5VГ5oPtlHc1G;lhKJmiwq144P_Q],jӟʴsP}2SMnX49côY˜nxNYlFq4+<Dh_"*rSE)wn-am;8aon}2 gx)dexFQVIH?NxDBV.(@ 5h@83`' n@`Fèi8iag݌g1L3&Tʘi>+g9 8ĸiR.ՏY|P +L)7PʻۿR>|i)%)ܲ{#LhnZN*;e`Z)\)9Dr9R6V*j6NE=kouDYN^NyHw -Cl"bN0t<CF:UC~:uCŽ5o…P+X%Fs6Εl] $Ǒ*btRRJ: 9jϾ~{UбPޝCJGR5g9~֏+P]/ztjNPsކSۑ1qb){jNC'Ԝ9Psށ3Y3wjNw^BhX5gC9B~jN̮KT4Z@`BP LVp7 ^ ^ʌ\-yЬ^ 3;3aa.Ҙ5g[̦Hْe툴3?F"!:J -Wd@ydbBmS#MWuCF~/~ͦhE\VkNᓙԜ7|?ԜStuPsXsӵy@ngSAx&=dQ2Ԝ]DDԜ{9y0iϟ=LrO>O:G$Q9wC)*"ݳO$]IoÓ=cd|5/Ġ0* 5`1: =Èa0cX0v Éc"Cb010bK%\V}*ӣ>Yة8rnޣ3KY9Xs=&dZ -%dɹ(qBw8&Jg YCWj5ՠ"7EC^\Cn'9oz!} %khR֐>ڔ`m+u9hQUgACn|juR?쁚5界5o…9N81-U9ӢXdKNKIupNO]]ww֜גŧwPr 0TÇRW~Ќ@it\BXq~'z^ 5+^OX:TԜ9G~kS,ԜкM=BS|jt^x\szI -PX@ =`@ - 0 -p< "wy$x)-+jRΚU}Q(h$vFitQ(CX5gkN}4ei6FB ?mĶ(wY*ڰXϜ(Q /MK_ڲ/%k역rj/[Mic}ͱXȝ#՚Sft<$PÍ"D7*QeF} ~IC?^&\ocE!6Z%&TWMLNpʘOLNNII^?S{"}=5jwZePsJWwC͙l!{[ vtVBe> -5dqo!A8 -5g?{_W;̿?N}jǝI9]Ps3Y5'k&ZIo/9 O5/Ġ0* 5`1: =Èa0cX0v Éc"Cb010b[%]y^U^4]a%mkn*N_gXsp!|c3=QKqHB$Ĺ$XPCGAk岆\)U_¡r# tQ3+kKhvYC}. 7e!._YГFģ*x_q8##kބ h椸\<iKJKJId[l$G4Rij.?)'8q-9jnBYԜW ߀3ՖKU%ؒ9C)jNۍyG̗@ٜԈʷv6A?5<2U8+BYxs;=d#jXRHVΒiO J01V~ -ɓLT ;%8U+%J4q)?;d )hH/kh!E 7ECo/XÆ5}&AnYCs. - e - - 77S.eVАntu6gs\-НWUa#tk͛p'\16ƋIX(:]iI\btǦ+Z^=߯eY*{SVVsԓq~}cvs9~{pCun?;Q^0-6wj8Ú;8aϘ}۫ O|w -HXs{X%!USExWޭ!|SO s~?D%Ԁ:@#@`̀v > HM3dK 0eFUʨM?C2l]ftE~1%׉S.k^d,~ƚXnq3|+1bfsJ1JUFN7c 6rqS^~GN>)u4!)`YqY)ey߇?/RJg. -3vwjch6/7?![ދB"׼ p*)1p:m).PJq\.95-&_?ͦl;-oiE}l# Utu24C={oOkWZ +V}cN֏MlZ(y4#lJ_f30i{*GtI]E[,f:4ZIo4l hPb0 b0 #FF(F8F Ìab18 '!`n ËÈH-g%/)=I=zhOI>cf8lZ<:k:Uk:$#x~;+-l$CkA6gg7Pǟ sGYCcXeMl~,7Su\l6;ACl'6g4hHtl~略] :xC~/-I|oiۄy3ke.)+Qpe*Yy[BFXQ9pegޖŻ?8#O(4 z "`,x@D H@2f&#>'JUΏdi1k#ufD!ll6qٌˣ,-fkof#f>, DfwY4o?s/RKARn_N~v ש/)~Y=_sl~ 1xJY޿NYjVrTY"r7e*qg>ʷ#wJ׼ p'xISbI-Ql|RoKT[' -#&K^jN@35g> ^S5'jLW/VBͩh!誇ʋڶCoOfC͙J7?N샚SטEDL@YC)|jʋPszϴZInY9y'I~[nBK -5'?jNA9Aͩ'pI -CPch0h Cc0!& 3Êa0og=|)? -INpqJ-)91#II)NCŠG^4ABhh%]]V:~qb8]ۻ4#Ӎ䟂۠Sc!y`Aaɹ:yOioTJ{p0Ïh )-&E!}o# ^4Aa(1Tj bh1tz ####Äaư`X10D 7ac$`.Id*G=1<G苽. hc$KǺ"G$lP&u{Q=?^YCP!h(tS4҄/І> m myS44 MtA:[Џ6F[-_({KIGmm92{њ7ixT+I6>%%9[b#::֑L^ g+'ۉw^PC8sw Bʪ=LգP4 AjK׶Xesatc1hCІ23X؆ =m hC O`6T m>hCІ: ? -*64twު"hC#Іf:6MbPJ CAcZ ÀaLj0a1,V ;1 Í!ax1| xM%堤Zg43qtv+it%~m(Y8SʖLy:R+YW笕SqB$NIzIZp9+ChZLA'}Ma_N+pu9裠5,kH@C7oʤFlk/f;eV5PhxhV7 -mhkІS(:hC_3y.r -\Lۢy<+ZYhKcScI)O|zѕ{du9kY󐃚9)saıyal.o^y=P;غ;S~)N m=pQ+~QM9@MqxN׏d|@)_kGR> MQ_/R,R_Xu,P{O Tč"?*(`4~r7)E(!9DD> ͋dWt+6ϺOٚF.uPSSϭp]qcGô pi>/ 5jJƣxE8L%PS:>]ԔW?JiZRQcN7RNMcMuR)p$@J@ @,t0F  `8p|@<C.0TaUSdtJF XC+k}Yމ=7u;P|R̯;ۍxr׼ )|\I1iX[2>.-I;S,,AMUUFrPESA.:ıi39>|6qXHMDh὏Ed05f_Ųk2XN8,! ~"ˇ)#)|t>)p$@J@ @,t0F  `8p|@<ौ2dC0VFЧN32Fc6:%R͘s+c9X;{7͍2gtà]ƢCg0k{bMXD){Sb+akáO (%;>ӣG Ĺ@)RξC<`xf?( %(4 z "`,x@D H@p`,azFBfi<`aW3ƸJŚ2͘rskgc& ×3,-V22Fja6):ٔPKVŲROY`C){ 㦀[}GUNF|*opʭ[sퟞwʊNY[Vghkp+%)H"ˍJ-s78pJܔM`NqF)Ct&;mδ4G'S[tOJ]>uyډwjWKEM]tPfM{g AִlCn5>;4aL7 7Hůdn<²<νJڋ^m%}?L򙍥[O fy/y-+y55}/ʋx$Pa144a1 FPp ÂaŰcpN C1Çwm众**{mm/e;<ڌm&kXk`/xhcZ=k xs>+xijwWZ~CCؠ_]CKx(+zu. ΍4@_tSD^ߗGKˣ%U AP|?^0 @CǿxVV˵SVbruӯڜ !WACc/G.[}kބ !gxDWt*m1` wbT[l -,&iIu um'ZN<2 r5Pfhtuqԃl<@a6];JzW @K1#=<̩\ExN=DOѹyxCЙC=gM%o@C[ U5n2?11 kȏ!?jȏ!?jȏ!?jȏ!?jȏ!?jȏ!?jȏ!?jȏ!?jȏ]C^rCJSjVRxr;ztrT2{WC/=&Wyn@rH${2#I d ]@C^YCYCА}S4o67Iu7$k9f j5 ϗ6e2~چvT^#P. ƺUO~Gm+0Rs׼ NC1_І.P-%r>qIN'Iu?j'f/}e9hC/ vPQ KAxŪ mw$8p<="J NЩVkІ{nhC]ІV2fhCS 0(+~P#ІvCoe mhC-aP<(Ë&1( % -C1X -Ca0b`bcD`0 +pbƐ0>xsQx/UQH -ѣm֫-=BP1`B xLM^sǒz~/WmJBGs=[h -A4qMЃ[П{=tP&Р?6ECm@ l6GU z毠!4hmMY~9 -nRQ =/S~͛p4$j'NNLŤO9ΖpD' -Ws4s3*/amdeN,Sd94^;'L[)kV3sÍ y! OVE9LO>Pㄽnq_M8v=MmcS8#&a%:\WKޯȫuU;mFNȾ=)9Uf~{C -G؜´C]ɇj}ޒאn;48zckwispYZ?=Co8eox$8ej{xc ԔX;S3nUpJPS)SJ"U+\!D%Ԁ:@#@`̀v > HYdzV̨fL-aw3Z8N)G0ܫr tpctYj}|r9!M a9L%0jʓ;]oRބR4(/VP9* AMmÑGPSR2_d%D%Ԁ:@#@`̀v > HRFe9b4 =}U{n1Ⳑ,~7 FK>ce : ~VƽBKY? }c*%6R~-+%ZUJXnR ^SwlR^Hx2Bק'<t?tK M޷?P)R_-tQyo TWpeRW:|T],7kބ  !6%OLmDWO0q8lBjt /$ )Uu!V fBYwPM = rkcV}ݡӄatclzH믓Y{Y )efiä=o0Օ?O:IoK|tOx_A7!GԘG9U x^ʹ^^mGO{ tpM41gnTzu^,5^#[^ @C_džG}eSt'^)fd }u[9ݿl -wʋ~f5hh,}ru¯(ӵBɛueSRE Ʒ`)g͛p'`7mE7:ACx_b-͉?80l]#r) HRhe՝f6v1F_c16vl6ô)53Lg W8{M2C5s1Y)JXVʫR,r4; ~~޶٤JyX;,iNY)oXYlOYe yۀ\;5{Rw@yFU}/KK X&\V'1ɮ#-eӢmb#%NLa\h6%%GWԻY0O%JaB5vԽG BlF[=TJ7fΧtϽN.ffuMC - ٜ񩄙$nMxxPK|~ )eg&u=l:%9hPb0 b0 #FF(F8F Ìab18 '!`n ËÈH^jAR{UuG3g r_ܤcM6L]38@y%o3Mn*c x>Mi誇XY F)UT%z -`I7y!|%$;DWrB9RmIN^ɮh.:6%&6i~,Cw[>]ב[ɩ݄#PM{\BM}ٓPe+vc64/E,0>4A2;5sIao뒂+T)\BxmVH!/)93z_?c% -P*@ h`-0!@(D& X+`8 /!Dfj JYjR4E3f׬ԵD YQ^>J>Q8JXD'ij!\}ſ[.:M C7 0t>Glj&Rw) J?y-C'D%Ԁ:@#@`̀v > HRM -&>F=unRa3XѕJ0gqԌ݌1sg,;SsX-*Y)ȓڠJDJJy/+&п1#L`1aRy3@e}Y}>;p-(Rvh,C:TA)?u0t*Զ`OA)5o…:qϕpI.li_jZz`F" 6`N7`x>q zfijȪ(- eQME[g':@,9Bdi7-bkˈ,l X|w⫵l}7wcȃk!Ɛgv~c["K̩KeV~9}v_=1x[?m?2F-~y;~9ȲSe~ -OQ6?QGKSfn$bCMJG\n6ɑʦ;Taqhb㵇~/ٻY+l~#%Kky:rMHN]2}*y*ј#eL(**o7ud{~hu~i{~Yz~9__:- _ʦVe%Q~i -Ȁ(@ 4@ t@ A$03  , ăX/NSj*͢ l+]HԳD3LKDFcĐm/Y6!VA9L)֮ex nXGEhD|;F%o_^wlҟ5;g %%lRBheb򔝲et/ -t%gOi}^)J"够<tdžp[.ڨĻYʴ<:bXltjfd",,g/廤fP5IdpJᗬgI3o'hOee`~8hZr˖ mΠEd{Dd{߶uiPgl,Һ_ -#_ -OQ5 ?IoFS;9>'n+JqD%IIL#ɩi)Ifjer+wIgfP//&(]5>jjA~Jd/ ;dhz%RQggi sᗉ&%L@h._̱ _{~˃_O>jݻ#nvķ%^ -Ȁ(@ 4@ t@ A$03  , ă^e"P٨E^JeVeiݪm蚈~b(zpV01f-Z9ba®X>Wo7cr;7_b/1b[A-~7SIV3;n7p\\,/^/_ Am˭ǂ_nG}N-/eƁr+uiIojl -_:&[Q2zLkiЕ$ꥩ5ǫIuEJom~n4nXgYdKeI,=i:4Vk굓F}9}33Xv6+~o{4v2bi!kAkۢ4T򝪀xXfS܂lӯD 5DC۲ݠ:kʨ@HbΙGַM@Cg:_s礗=w3ʨs-c9}egGmJJv%;. .BIv%G9Rcb9OsFke=|_mz̙p~5mC̙_Ai=XiGɮF)-Fz5ASw1N]'9 '+ T@=f\0]D9(iPF!,5"d=2j0nyQ]9#硌*=2jʨ"Jd@@ h:` `v pp^~A`f[lǭI,VuZV]UD B5.if,+*l2VDeg7 [ƜĘsUKke_ -m_\=9+li~6Cw<*7;*mdO#gEIٺ`~ fI2՜Aι+pZ+&ݝp3M)$IKNeS7O]깰gu/%Ƞ^`IPA0y}M9NiHk!D̅ 㨣t sꨃLV9:sBJl uTO)7$|S>4[>:4ꨊs -: -+9P%hH`f@ ؁0 XxAJH "(D=c,Y3DIì)ZZb['|´wa wVŗE|E9Q0^!4oo`{>/A>M_r)IXH$m: ۹>nX~A-ʭ[IHKV_< #9P%hH`f@ ؁0 XxAYT,լ-V^RejΕlp+q~8`2Ȅ6]7Yr -3ZKchQ)?[J&RRZE\D)mY!rGc%zgA[?rm֯Bʕ`RߗU 2IP7oũOA)~"7SDGw%ߏW_uXDH3zK'Z~o &1b%2 SB܃p] z&sYs7~jc{oc{<%"˖߾|j1|" ~C'_;%u!xd97/+%s-(sy"˜eG_HScdԵ27|˸ }LJrJRp'l9hGR4p1i' pXqObzd)@dyEw0|ȥb^vyQ/Q??\i|Q>DbG]湆TғK[ecw,"PSĝ[-|>"O,VI,o#]xqP@@jZz`F" 6`N7`x>q *j骊PnP)׮RizVU2&!9j2j猊YR 7O|M-R~,*%C:)m ;;yw(6;嫟Y/j]/NY;6fq^ᩏbvDء@"㩝R݆p,7WG{8HD8ؤt&88YEfixFzD92\j*AX'Nݩ+ʬk_O4,N44 aDv>u scwIbzR3q)i4Bms*O—̆y;> -5X -g툫yqW -( rJ@5-=0#̀'`<8 @ZJ3TUoOZEVA+}[i˪ qb"}FyJVj:sLme Wa园1oq>y7r#% -&]2`& YBni@}rP1HfQA/ m,z -:O22KR'2LmGrᗦr%?xr/xEfG|r$[8ʓvQi4ť\Ii#T>~zfz!-qQ]1ƿ0C.8Ct3ģ.Ї -]#G%{##Ḷyݶ {advէ$վ: ;wt!%;_8C,@f)x[6^Q -( rJ@5-=0#̀'`<8ߖJK'hj\-oôriuJSFki]'RHeBfY49Hi,P1ô;O -͏fWroE\/*QQ)׋J:RvlKfG>*Įm{+'At)*> (m,?-~yUlc7> ҘrU,E{>; s>z -fIxXrzSlzú\RLr+&%*%ȱ=>ړ3zd(%Ϡ -Id+=dlCfҀ̒~)BMGŝ s M ń5$ɎdXݼ#ɝ:Y6G{޵x~Z̲>2Kg;2Kr>~ y/2K!,@f,FfgP@@jZz`F" 6`N7`x>q **逊e*Q)hzVҚ|v oRR-d~\"*[ʞv֫|^Ŏ]TNӾՖSj̢┛EԊE)%D|te둤WlL6ntoD#2S)eq *-D-D6flХDN4DLtD?E d}9K1`!!s09V(asH!-sI`dA# "wK mgy񖀘ľ- L, yTY4!_,7gk Nʾ>M=˧z'Y2(!Q׏=O|GێB/Ó7IIwlJ!wTǕpd䗃g3qAel>lh~)NP s}Ϡ(:O?_]_rFW M݉ƃv/=n7Ma? T//+K2m#Et!",+~)_e=/x_) ȁ( @@ D0ln |@<""$T5EQZ %-!_$Z/_FFb&%s4w;aGkw+%[{RK藫Bǭrz]rFkCx-{ \.n[I_oW^@M̪_s%K^)j&#[-C>F') 0ID| LINJMN䗼?'qgrHtQuo3F>[hzĥz_e#4͏so/F?0#Z"L#R[m- QdUK#!VgbD 5QӨFPUk"P@@jZz`F" 6`N7`x>q *Kh-]^ME}*͜JMTU0G_/Dh+ckT̰ʝb3b[PykTJՖJITw&*^Q) r[{&GhB= 5ޭUo~;`N y8_)4lwW)8~uKg+c̣ӻm{328%SX+YrzF8?H|BeDX;),|Bt -^Q -( rJ@5-=0#̀'`<8K}p uJ>RT*zX?Ve8" YWUFsPܝ*a268eq nx:3HL%:D]┦KY쳠ZY}NY 9|Z 9mY[>}gMNʚgYwrTg -OᔨH{Cy+"#OJT&7Hcα 7sD_T^>[r';2=A(Otf$C)'g%g |vftC)aoJ)tnGg2rJYy^!䜺AxUӤ@)w+-C)3GuUjZ,ĀX` -lx t@OeUffV]!aXnC1S?1OPpP*k%S9-3 -#D _)W|B)*Y)ӲRuǺRA))uܱoEP2R~A)![)tvjR~%+ErA f:=,gvGA1939I)+s#:mՙL#feJZIB!hRXPUc˩ -S[.s~w-=,8w[_Q&QZ+JױwQWzfsv -m(ņƛy)m;[wL+ؙq*5-p@ L b@,x`6`<@ -@:R -'0#6x&7Aۚ+O`\q?P`,Jk ˚ ~ 'Xl ǑW/N4؄qkKڤOT=IVC>^e5Ӭv.{qWO%V%85>YvJLV -'+JJe$X0Bvqeޙiy`IjL^Ӂ.ح+3ZK肖^.7Yma(e]"u*dc816H$X^ ,@)7 khJ)C*42 -0@ 4@ t00 , X ؁<@"?@44U ahjvXp%D?A 8ϚyvA T)u؎2!*/O.Z8ꉿ?R_Y)JQJ)S?Rt 򵞔A)WJ9)+e)RϪG/ϡ u1z+Rc(0 +H)@JɫpΝRӣܕ\׆zNyzvM8gdoTʑ\N =P7>}B)ݧm;еݪ`^iWp#m 'ҿ:S -B>sbN5]Pi<~PwW:yJ[E6k6'TFUB_;Ų{?ݦ7N?= -_UNW-6u&R>iEmt:_Ќm4t9#hZH^/8 Yjh=ǁnrEK)ʤw4tkpx?v,Or;,>ѝx^& u?gx˿Z=)B]\B3uk;`;(PpO\_-/T:j+L+û}bN߼3vFeSX;R؎(3o,+t(+=E;nQE7)|+3ϼڪWܝsLkj9>Uhz`F`fb+;p@>T=՚iNP56H'p6@a)X`\@xG셶)hl|ݓcfĪ:[ u'l);K//6/bۖOO­3PNn9Gxd") fH!`zgLy`މ>6>׹>?TI9i'(?7\@so wJ? ;v&{|!tfdILL&w\'{q.7ë{xM8b[׈iQ[9uZ;͠wjbn(Ĕi]{r[ //wZEKhTO{κ_IC -zf:ϥMN:V -0@ 4@ t00 , X ؁<@"?@44st8fÜfhnEMKabZ%V;2[5g'5%|=%B9'v_)sR#;Zv-S0D(/5JVVJ|OJDPwe<-+EA)?~V୘J xYR,ONWJM+JER\`.ya2t D&GL1Sp>/Z,O3913xD2J);m=ל273:ǫxM[evV]senڒ8Upb, @)v/+wlܷ1<;B)S+coJ|(݊P 2 -0@ 4@ t00 , X ؁<@"?@4U%u%ESPHCi+9(s Vb'bM\_yP@Zw8'αRِr])JyPY+|r򟣍أՖJIg6*Y)3>h)MW\iU)>SuJ@)6r)>oQ(nIq8SR۸&s)%C3CYYg;:32dgbRۗ *S&:-1+ -Uu -fVzhBO ndU~T5T\ aqE}gվ-,ZHhI%tZ>DU䤨.Jcn\bRd))>h1dJO8NIeHI¢$6F$YtFu<-9쌠d󭨣1ohϋ-g𶦳=Gc~#k(VС3xds( ެm恇6CC{}ơOZҡz|g?K>JKՌ 7%wv 1ə;t3A d4ɦ5}e=vgy#fOqڅn]i8}~aZ/7՜3}3Lg{eT\|u2/dkc;[dSd3= -OmKs&%_Ac?'TFgA4|,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,}8wpL5QMwv/.nw(k#6ּLj, -qO9X샦Y"|YRJxH #(5Y)}R.׶e"ߣMG歜l:{GMYvdGxx7oG7DZp\r1XwZU%薴;(EQMeR1&;$~L01$fzygRȝ /=`F'&m^Zmkm7z!z歛^zBWv>sR?:{Ha8rWEe^;cooTXdwm-mJa;N/+}(]c-+J/+=^*V7!*+~[)ݼ ݙ:Xδ{^ -0@ 4@ t00 , X ؁<@"?@4詄T6fnHдٴ6Zۚ ]Vl)#d`͚o'؋6p?Y 6,woJ [f;ʂJe`A0mo?er[e?UZe|%Bfɑ[b/g›ژ/NE_+a/ "& 3,T&{t -/6܄s7 x4L!D~de93AOV/mr^mrf9T*t3K/.^3tȭټynmhy[k[Tb:`)Dfiex4.>ZkVʑݎf:LxאYFY+n+% 2DYBDfCf)CfyD,8E`h 8`&`1 X@<pxD~ Hi ODM^&!&:tv] 1Ms)1:sW7g%8D|HjJQJAe߷ermPC:F\u|&ƜY#˿ɣ1"D+e]y%WJ)ov1^\a!*"/߇RJѯKʤ?&)Gc2wǗDBk<`7ۙ(dCn!I7) yݑםJ7ڠGyԝqNAC<[sw{ Pdbp{"v.t\|wZtݎ(/mUxxBhAE4.2@Q:SRreUjZ,ĀX` -lx t@Oz s뉦R r D_D sILļFW46hXˈcZ N Y))e%R-J85c研/7RH)RnF5>FTF7 n n;`&e;E)ɉ AI) ֻLs=c -WjJ C -qB!ᆵ=<خՒgN>tcῼ =^};QGɟ3+Cj~C_xzI)U ߙ؁i+BF -0@ 4@ t00 , X ؁<@"?@4$mȅifѦ-Lu%Cw(tf\BƁͶf [\56橶 v;e/'Hö-[cԑ۝rd1~/;e6TѻG[{/N^x^`h 8`&`1 X@<pxD~ Hi STé8,rA-SD9c#g*<ߜC)YNk gmsUq|.1"Dග:;_J^|_VdT|["8)]Ж^0ićAzW/\#wADX/ڎȢ D^mR]#Av.tiDPʫy~zTbw zҘ~{M8wϤŨJed`O B( fz7#Y>!3im.TNRL}הC)cݺ3lY8}Aau^k\<}6ySu ]t.KңnkeW~ǵƧv;*(\M5 -~hJ9S.hFqnY|HzJYBT.62Q :)@@X=0#03؀8   @*H逞JZ"L 4D*98 -1rƖ3à90r^9扫{9 z89'p[k#yEPJVʏsն(eߞ;DvTn唗zYFZvŞ/;勑N9_vbr}ܟYۛ0_Е#|s.8喟%spDSZ4pĩT&p)l )̬Lo&/ҽBOpf -2P28%QorLZvt3SWi[rݺ-Zӏrj#馎b8SRR݉Χ' JP1qΧ2|Ppl+dMFLezNO>/D!1C+sV+'JYp&~^ݎRx4N;~[7ij )qRaO_Nt>-1Pb9:֩=ݎWI(ASC)|*>@S俏g%:9(Rp*5-p@ L b@,x`6`<@ -@:RMTD3D},BaK8S;ɧu9{%Ep"p 5 n-\ -˲RA)_򲬔oGPʗ_[vܵSG//;)~)júSS2gqJ:Gs@heQ)E&@7rW>2ļq>GOo 0x?e'L!1% -^4<$gFFf333}Y^or&"tvϔg]nf1VitƳ5O|8n N^;[M,c,1Et]WW -Ґmpf}nG 8a.zRy~Nϔ pJg -0@ 4@ t00 , X ؁<@"?@4.6 f u "u -8}5g%fb%QSiL"*b[$~hsxfPʉDD*z4#>Dp ;`S.a[bl>%doNc:rLZEP"l Pl[bJOyh:_@LŮo1# mы'V{Pۧk;0t99c -Sc5틙~mLzRv"~:ȁ當ՕEzѓۦr5ܞ'Tƅ*!7Νb볽 -ߙ;oSVIGOOR_*ih}?=i- -jZ4hiѢ@-fZbhBK<-VZliq⢅C@H?--ZRiI%zc)IIQ/h&Sgp -[c~b-L@C)[n},1%RA3, ݒx*+ -[$%ehS*?;-G&8YChls i(+u:[ruЧk]5[YC_EЋϪ#jnH-~ -P\IcOJkퟡ&}*"tXەˡl -jSH6+t+dSThz`F`fb+;p@>Tҏ|8SCTcgwԖcDWD|[#ܳ0TB#4dDt=JsDS[L|?N%A;Ԗ騻ߖds>9VkOsygc#+He!|NVRދl>-;ID([]EߧRFE]5OB)z.H_2)>Ƙ~&DOQ$D| B3=>yn#R{LWNUջO`uߍd; T>^SSƆS¯N)@@X=0#03؀8   @*HunET9sS/6[qD_B }AL5y:kq9N|UH+d˭w_?d<-?{>?Sc[a&J$ۣv+LkWG\'=Z_팠dh;ͽsYOD|NsMo+ ^&2x%Jo0T&0w&·>cB76r -$g2> !ޓrͯ@Ll.wmZrKL'bJӣ6NەB)ŭxWuvm}?~Y5˕c"(BY)rLA8E)ӫTC셑7xwe+;?DwNSaSGS1Nypp5Q>iVb1< 7!S|;1=NN1/3-3=B,#) aؑV!kNUkixW?ki÷uxƎk*܆z8m6Tg_;AGS\e[?l%#{uv;tSh}J~ %7CN_C)gnSN)@@X=0#03؀8   @*H KTsi$bNJDwk>3rEb:Crs:Jlc^93SDW%|<tsQ8嵽pJpJw(r7֧T&!l DYhx<N![HtY^1QHffsJ}ǕM7[wJNZ¿Sm8X[7 1N֘s -rJ.ZvYfO L{gnG+m} -SӜ2y$<rJ)m} -r9t>Ĕ -0@ 4@ t00 , X ؁<@"?@4>aN'-aBm;[$lœ~:>{.})}9eu4oQ%)Bgqpr"oϞ(GvG))9 RlSJO_,w{mS͑⿒ȫ}q~N94z߁S.Q88eu>S^Ɇp)pzD)Ѝ2|Π7Cdѝy>K>RtwN簛)WWfݺ䔾gyn8}mn{ZܻFr,g,59Λ֕mmr}dA88!NQ=7GNSzSᔂ&)8E`h 8`&`1 X@<pxD~ Hi =pRh+9]Ǯ.W)q3pjyuP+rqb/-gmX9 ' m=uNIQvJN)mW~R޿?]'Zk}\Vʃߊ<.+oZϾ U?C)׸(8^ rSď|OJBuLʥT)_цpSɞdw31)r -xܙͭбfQy8e~ҩ Zx ܺqyVjS݆wƑ0:l1ZbNC)ӈ)/m8c_RQSJsSoߑ|kR )uA)/B)UjZ,ĀX` -lx t@Oe8f{ Ksl -9*1c/1r0g(eFnR̹Ɣ>"qb=Ɖm9r5mRvD"w(kўdEysHFǦ1\o p4S6IY3[ :$3Htzn>MJvglRJǕhԘSzzT95̳Oܑ8}nл54}b=l]R&vY&+s[Bg(yn2LA)-tӍb{oyJzо(oRp*5-p@ L b@,x`6`<@ -@NTuӮ]c>H?UלΧA-Ƿpb~vnJ;pBn揷ꟑGS/DP -Bd|!Rȶ(荧-8):[rcє_N N lSh9wv@q&1Hb{;?)'Sӣ$6~-\4Ƭ-pΝS> A']S|G7o4t?oc*bJO) ⴹtʳMnG)ҘRr$|{S!?N9F[9_WUjZ,ĀX` -lx tpQq1N=B4 slZ9(1 )K)aS9 - sVαL\MpED|ΐ-'rP~uGۃhdoS~1-d{6@F쓕ORwQc}ؖ$>y&>%RRJ|vTQt>iE)_G,y͆pΔə݈)aoO7๳渲w ynJS5Rfz#3qږgZxnZzk6<.X~JzJ=HhS |'iLu1>H<,+ P `  V`v.|$ 7[| quD[Dt"OC;1S111X+mG9:BnB(NJ9ėG Id$FzKr])JI$ŶLSvorl_||rJRFf9DPhJ9]GH)3J9 Ud Qt>w |{ϼcM8w)E"'3^ 9`R2vo^[{\jL+39+v -پ,g0ɝ y%Gxq2rg"wdH6-PZ5퇯׮)t+P5hn^Mah?03ͩ}Gt>G8M~U"[h3H6yH6uhrglB M4M^ɦ$$*$ldCOZE C -ZZtpi1bDZbiO-vZhi""ODKTZhI8ꓘuy)"mS)QP&.1U'[.k%d KGZ%)|IRHҙKc(kHAC񲆾uwmI67m=s2}4$k٨?ͳQǑ-ɦ9,jKYdf{`}'>Kf?=dyhh.UH66܄s8&,O 93lINKN =!_ICH6{{yN-nf%^=4iӖ֭=ϳO\u8N_rz妩MNl:<_^3d+c_s#oNʣo( fd^! q؟Aife$gldSThz`F`fb+;p@>T?| @j'L=Gww9FD7N -511~4줼Ubm"ab'jZ9jXH|_IC[Gxe|>R2]ΗJPlGQL4(˦w-+%MViy dsZ^Bdcy;v=yO˝H6˝PJ"cJGPʾQ4K_XHe.7Ӌn9l$P餼 Kf3)<ܼJ;|u4T-B)M\]qyIk\ `*̷,A)]4lm -ٺcnG[?2W@)PJٟnR|ӧT)SiPJSThz`F`fb+;p@>TIycJ9K8t՟ -s%mNAv.՜kCH -"!:O*E'+夬RA)rJ)+-ogGT;ltNAvŲS~5?cʯfǟyR^%ǷAsQ983hS*G#'I"#r8/ImSpŒ>7^: ; Oșg';3lo~tKu(ڛwKJ-5=۪P7V+4+ߧ-}B7ݪ`ܛCƆW-U3u).zQwAMy sl&4FlrH$! HT.+μJݍJtK3/+}3S薪rߥw-[یniRNtK[NuK䡕ФHQ!EK)FRLINJ)QXI’ I/)>RxRRHI DJ2)aK tej(A=L'ht"ӓ`L .@frfh - Yp.[+OEa8ߚYx@S!m;;-͒E閾GYgP%Y쑼w-4 NIC?s/.ƬR4ovޮnn}mCq'{ahHaDAtKy/EݒA5Ŧ|dCW\)\LP;E.e}Sվͩ/)h8]Sy90c컬15ψ6D=/DwGܼ"Hk6tUY΃m.A9Uh3kB_m -v!aB9xѦmJ@P ` XN DI Gc-52촮+rZoazVCI)gl8O1j+emK}GBIYsU{ꪂ<7+?TBq E`lU8a ’H׼y*:00  -X  8>A @2 rUΰ,*VGkjXm]72M!7̚FYk& 5Ŭmζ4r=whae g eMk^(eM;(U7{B~93w"Q//Rd1E,GdiKG+iLHO46:?襭nW%@`T/4o4h"ivICKd4dY jL:YW'!sHhh%t2Z(L(!r? - [ǜ~Iν@1g?l鄸 > y/i&-Ǻ/'rIlpq>_Ff4@C?loNC{s(eݛP!J5R_Yi et5QLjÉT%jKӻghhH.e l n+mPJ&hr4T w+{P:[Nw(ߒL6H"$&EE )ZRt0H1b"LpR"H"J)NRܤpxI“"'%@HJ$RI!;%* 2QՐ5]=.; ى hn-Dhh*66$ '~[+DQʼnGCW% I%eo s٢hoop`DB#ɺW5tE㒆PqICwhh\f3.Ѐ{yh(jNCd k#/wthEzYPNTy/iHO˓g|+>]Hs|lå& hn4TSR섆5PscVkQڢOPcN6w# 9M'ՖËɶC:hWNrI8W -燔O)-P_GJ-aT?4thhuh(R,ꡡwU7䡕ФHQ!EK)FRLINJ)QXI’ I/)>RxRRHI DJ2).؞KCM !Q3uE"#P`M0ՊKHvo4Ԛ`=sDt4T&E 61лJ{e4$74ȪE~r4\UUR i$IC̡N-5iw*U9iǦn>h)fMYO> &H'o.<2%: ń%5Cbi5L!XOBf| -K)=qܠ􅇔s)A @2 ,YUnc5vm%`ZKMhѶh0ڱ7nwDs"bh9/8Ffj_erlW7rd3~not%~Z/_}wmqv?5-SKe$ObĜO+[ON\2xon_lCƻ|)c2 >-35QM{!e,zt:R/, }TQ (rCRk(2 -KÊIe{ɲ/#W) 쭫 -hUW6l -+P=)s%Ȅ"P Âօ%ՕKx^% -h0AV`,p'px@~"$ n;]#vM̤PM#vzn'W6;[ew4ٝv˕/w؅sal~swܖirWmEcem-&~'..B/wJ~Y&E!㗔E ylIpwWm|g+B7OXy3E&Xrp…nb22R=+F|\*4.SHK񥤤\M-59̜{H~&.)@~_Z_)y)~O7!Lw/7T_ȧݕKM;I2=_ -"VeSY^R*R04 /K -&A -c~nUh L , D(`6p7 Hɀƹu6C.e_:ʩ -j9uHM㷜v81{_4}[kL7DZf >SOEN?Xexc~!pcOnsݔ|7ŵ|h|C٢PsW} -Mȹߠj~Φ=ݩ'z5zm^͐Ɯkfr+YgWofzQ,׻k\[uF0EIJ7DKJrRervlޥ yKʂK=-Ywt읳wIJ2`c=%?oV)CI -]N1swV_;}|=mDɧ},޼*Y^n]d>&͓pDXW|ZJ+6œ_` DKk/ζE+;YZ^AdYɻ&eDwz^AdBKԍRڼstEx5Yg3tLk@-DZYzzYj?@d92"KqDnD],Y:v#!-Lj,}ȂUh L , D(`6p7 HʢhUYVSj/h%ڸ:V$mkfYY.'[+bBv 4,Y~srI~yUR|Q"׭ eےu=vI fdV丹h7tc.W'X80X!?4̿[Sd}ٺuKuk̴ KŻtWl'DuqEG2Kg.e2Kg*{S!q9xx#Ұ~3|1W's!Ef)| jf[5'c۱>Bfyej2Kdr[}=ao#1 e'2KvrhjZ 0#03p  ܀^< $=4蕭z[*cԭzyvRajMoיNɥ]N=z[zg]p-zoW!_bR/e˒S ye)~ n>|BeAO,KխUFRʧRfL_H2m2JqܬRۯ,/:icnarεA;r騗lhO> .L"Hy*g罄[8N oLFf356adεMޚ,#)[ⷐYJY*k* -),Yj6 [jV+) -}e9f$;yrL*CL*OVx{|C -~웍PC7weGO%]:$My*:00  -X  8>A @2 R*+Xʱ/4zVncq5F2{FN׳ۦl+KY{q -v=Ppf,e_~*esEy?]D Ԟ,O/)e$sҴ(|81z_/GYeljLKOۻ O>EOJN\qNxR̘TW ڙ9=l;E_jᗂ5u%(xFJ7Ӱb6PחzwU'_r˴f٦fD'?hD{Mi"2P*_FO3D=Q'F;ϫ4P5-X@8Q -ln/@AۮYE;î괫kX͈]{ήkg%=Qla >;FXg-r|6+=2øa_~ϓ  ~"/? 嗌{ ɺ?mP$erP*/frL=mJ~\ ݗ> O[c=3?-}/3_:IK\p [\edf/>ĸbL>>#&3{j!Dq#ұy.:}NvSo-tzqFi|90tYc*~62h@ޮHk[ɺtvGM9Y W% --1D#kP6i6%:J.%øyEhʥ4P5-X@8Q -ln/@Ag't^UW5{JnZL%9zyGo\=6GԹG\;u:? LyZHI)&FRJR~(-Ǵۙ,%jWr;g -%JHv工N'(K\K_(:DǣYQLRJIR潄[!(K|ZLlz&J" @\޴OJL\̽)Jk T*t){8~N}$R3>i'8]~ijL9d>Bek~T+DZGSZ%|ϴ'w=y/9%.ls|fl*'ixOlƤr|ׯkSkRL@Lй'9UU39RsjfqLHCoqQc$DZJf"glsQfSOrtv8|'2br=p6Ob8N)GTh L , D(`6p7 HZb+Э0F7gV87匥ĔRO ڮZv_)EDĊ+X-"VDDĊ,"be+""e\2uE3R9v룉ǔ7'.["7el[Y7f^M̬u޲)3zl7M~9[.ďvm.C7_sFd]soR=_=>㽆=>w|L)N;93d/lF\ź]K~<91'''LWv;U[T˼הgV.mc}?xqKLm sgmc +%OX+p:FM=)<yV=2|0^Pu^+;u {}WuhX$^U/ཪS:{ԽFɽӜjz} ._?=j/wf"/)G6D$Ae>ÊYQ7%Rl?Dfe'qf32HۛJ)PJnogS5T:x~^3gí+j͹â =jGqa\_\"a8oIK-(75~tF(e J߹_i{Jt05{߂Rj+*c}P(eyI)Dh X`&` XA"؁<  4ȥ40&Zd}V1Jb=~keA\B)cD)(X]G*Vha]l`6~U)$|GRWF)7IJRn:m|CF)kZm-8N?))7-;vƻs4V; \vA,#*wW;bWp;.ʾ5 `C9 Nپ)@> R?.PJp?W<Q DGp;#^8şgKIq'{=^*;{73߸=;UݟTkz ^ӾǢ鼧xܢs:c#:?wY>@L -kk5q3޶m>k|i7:v)p1ĔpJbJxN1ṄSsc%Sp*@5-@ L AD`px@!i K)eU 5ŨM-du,]ƲÈ)a10>Gbq6Vr=}q3!a.g{c;܆S/1[-ş)c'ݒ)o4dFG]p -OZ޾ۅT!88en2"4[KCc<˜/Z*v X`\m4M{vjFELLpJY')\)KJd()zI)RHL,.RVJ1i)eFק%|.RiI)ȤhwqrJ(HJ{(R^RRSt>Ã!*p9L]@)׭xgM))~wL)'ER!qzy1x2#QwΧJ)9eY)o:U^vLcюuPQ}ghJk4jnIE ־b(6^wcJRvUWxO@)3;R (R~JiyJ~J9Ɨ)R!CH)R!BJ9r)R!CH)R!BJ9r)R!CH)R!BJ9r)R!CH)R!BJ9r)Rʡ%a9R C72lg Ռ1u0!RzOu>,7 YG)*egmg( hwHf R磖QFRJe9m(e24c4t>%k9EN߹)-9]f$Ĕۥ2$9G)t>IS|'|Ĕ[SnSLpJA۟)!@> R_7p9{4 KE fILz|hv/OUN{@&eLY橑>^kSu<= Zynج8jx1%hbJֲ㼭LM -W -1eV2BA:N|1ev8tOŻ|?Yv -.Q(:@ A5Z6({-}ldtIeJ&H~9f\ %3#veGVny~y.yu)+@%e~% Rơ:kD4*썬}1|b -O$NQ_39fJvŔU~w@S+& O8Uc;Ty,TXn;<]}g+wmwgƑO.1UZe9D;6X>$vm9EQM⍎ZAd(mPo"KSXPlEd}ڠYjAtAERKT -hЀz`F`f$8 @" 0H\J9*`iFjYaVWң 9Ƹ5c$_cgcKXO)K"#6|6~E)I)',1GVr%sd%Dk~8֤hrj=eJN;NQ8m UZqgdnE]KN:zB, )U)ҕB0vY׼!ND||Sdf9ۙ83Y',rJSe#7IAN7gyMNE;)ӅlSEp\k{#T%~"IEBw;hS -\rp>4Ϡ y N±;RF8`!({Vr -.Q(:@ A(,;LõrN9e}ʱcXSZG\&PK$Ri Q Nɸ?:/+ٛTv ̔HF$q,8%#:3 ޙߛ]ʢ|#N9S_^գyMKE[ <[E_o@kl,4qHfSY[5fƑúrXԱNF(ci:0'JGRNY/b)?rʥ2NY7<9 jesQZ1ĔHJJC+f<]l8yj:7:\~Y)[x(;ʺyĔ T>0ۚF۽uC8{C+1%GR23b匸(H'eJW[ՏSsjԞO+E -MfPmB -'(ت6{[cxduof *>\ %Bio6t ۤr-nR񻺟Wy&TyKJ;bP*PvIUiUC+ʜU#j"h))jR4hIёB’'@)fRII JJ")6R8R8Hq“!K@HJ )!R¤N`PHB궋5!mOHכJw(dȿ}7d>CD6C!G~s1?i{8$P`,58Z3rmmX \"EVFC32'o5z%J!VFCJzL+ke4t5TOW}Cv&dD+4Te -hgeȺtK S-]Ը!n)Lԝ${YYQtK~HLJ+fFB/?9iNU.7gWKF`rl"LoM ٴ?d$7H&蘔lp*@5-@ L AD`px@!i KigU M9vVW {a #1SkYL#k+gq־8 -X234i4Ìƙ`>"DARw& g[e(eZ0 ~V6KR)ҽNL6HJbQ-gYZwOdãYzA]p9(D0'͇n+gYG*f -֓]b_UʹR.>))2JirRRʷezFR7n5OvJ9w]:^vN^˜4{d_ - -\B)*wQIO-5A);@2  s@)RޏB)7VxgO)b͇|Y$}No?xY|fWzQZ$O)ݓ$$ \:)XfE=.DߩSK-p芇p2Kr^'zy_Bs("n>zmP_|']vܛNdj}W>kFhȁ = <[Vfяnu?/1)uۭ ֲ- wm#F28Q8e~a䓠:FFVᔩF8믒Sp*@5-@ L AD`px@!i K`U},ͪ+XM!`t- ϒ9s q1aͭdΨ29+fqU|3`Y!G@'SS R !*5eNIߋ>xC8{Nb/LwJ3-No3oF/G|)8ğZҫT6R-v( -{/߅>o?A\o}P]yƦm`zfC%TE4?އ>^QyRl;Wwy'w*TGwL*zfN*CVdrU -hЀz`F`f$8 @" 0H`i\ړ՜f~8IWDsl./ },ɴ3sd=$g+~4QjEγrBQ:|KL@{0zs%<s=gf6;5'tZJ{}O,ߋ9YwlAC܄̒vˇ'~ym3g/_:n_~[>(d Q].>rp _[>9Yvd9#>>Mtf2Δۛs>2SՍ}PsEQ-t3<;Qiї -ZcQΥR.e\ -P@ 4@ t,00x \AAAHKMY*bԓf1J?wyPiP@{PJCUбOE(ev{Rp*@5-@ L AD`px@!i KRXjUV[Xe{Y}>kb%5O.-#mжsl,WګXKzYoK&z&ِ̮kR̥sHK^f~" d-?Wd\⯼y z&g.QǥոߔI):)RtR8^F,7`Ҍ@.X#rztAPEJXQ&WڐR~~C8{WfE23=^>mO)%qZ2yGc{@{oe}TUMTh -ݺygXH)CZѐ;cooP$u Kbok -6ntԍV*\UT*Qxj RZ޿My1(Jv W)epQEjEZ{Fg\ -P@ 4@ t,00x \AAAHbC6:1QFSh]C73$/c qdRH2G&.mt.axK _{j)t\␔&Rgd5bO094YNo ҽ^sHru A&|;ݔPJJ-.*i@WPJ:| )G3!*u0R#:xg=XO -~/&gd8)fDNwr4#+h|j}C_6>h|vSxu3&av*׭=R-z(eX<idռk/ -;ݶ(W0'4=Ƨs(efPEuOH R7 Q8(.^X-[)NzPJV -ofdeRH{?t.^]4)*h˶u_:=Zlmu&O/1a5f)R: -6X[k-nM#/A)D)9ȩq{o12s4A)Ud9I)Dh X`&` XA"؁<  4ȥѪ"aKsaeF_04HRvRVpMgi,>LXR))RҤseuSʹ2JQ.5rGq-5K ORrKJ)ˌRF%K9Yx& i>dRn̮?]YKYZ&"e)JR~R'iyᬍYey4>قg$94#*rpy鄧םm0Rn+<Ӄh|R*܆ik륦ҝVѝ7X{$Nm\&{wF0ن> T5@)];߷E8HX~JyJJ} J9|\R -.Q(:@ A=/:S1/)ތ,t ! >[:QjOq/5 wYUn"4.ZcgZ){=7XmM)oOlt? >)^On+C.^IM6Χ0r%)Dh X`&` XA"؁<  4ȥ1=,5+M;mZ:azִTL3nn;hfi|2)l k?!ŔHN 8:)qug$-bZJ9t_)RL2%S%LJJLL:#'4zg'8r99/J8J9R@}G{]LjBTg0Kp7>69*d{ENޓf$GUJ:l|дqz^_fNuM<݂RE߶m(ELĔ|k`ҾpՒXuV35d.h8TA)rrӞ[t>uTPJ)PNERrKT -hЀz`F`f$8 @" 0H_tQgV]jFdk=>%կ -(!I)eQ)Wxo-|y(.I)2JXR.I)2Jl =H)']yCD)cP*(j(e}9PH)|=D]]Χ4)#^#>oތS^'eO49yPNl^ViJi~W=mvml~(edRrk|9ԩ4wmҒ+m;տɾx UNRJP4mcЙ*>,@)#䠸]%UjZ4`ă`8`</ B @: 2JVW491Ц -- 2Fc匣v|;MrF,bLp^S)vL#Mro -[V1O> nRp*efOs|VuϯTtoK 2b9tt>9Χ*Q)ᔈ9JAq!8ir`Of)&ˊp>;xldNoEv -YhkhGp:yzϞG,81r<6g?6ؠH( -kC%q3~2odќGNzQBbP̭Sz۫>̃S -?VHN%@@h=0#03 - px^ @@teQ2nFь2mzivnigÌk=ʉSk=2Zu9 -e'u>׬u Y3x.~͛n_yY_YrSm#Ŕ)|[f03xWǔD/:ȲĔ-Ly9(D J Ouat>/Jֱ!5x}3Ψ^_4N^wLSq@0ܢ(~NU꟨QP;+}O5+4oΗ5)tw>;+f`y08{bZalnyes|[w*Gy5 -[o(mU'GɡN6\mM*/MY<دN>'Nv({Uê`[OB[_VR>)D.ZE -E )ZRtФI1b$DxRHH;)R\xH"" %HJ0)ittfj"j,^ jTx*=N/ Ac_T4\D^XZ]k"3ٓmL*.Sǂ5 % 1u:WP'pPX{YzriNM ҽ\/Jf<2~Qf\{lW0NvLh[nK!4ԯFZs|.uV -;YΞ|_7OċY|vӓN"NIF2HfJdNrmBfb#7C>~B3} -ݶc;t -O( u<0M`._xO\C -kSى6z?zW5~RanU8N*]'| J)T -m -b탓@Œ2XIe;Ggƥ:&m@@h=0#03 - px^ @@t IujԍIѥf8cǒݜq37w%r丄$[Ǖqαȹ -γ? C` fw_K@/y-H@/Wxn\-7tyNv/\&= O?+"eI˝$n_>o_ȧA?CETɒF{pNܞ >%3dH@vFO3DWwNUPb1grVt1F:Iv`i. - bNӟ<0 0A6@HJ#L -:'|C8{S_3IqG}d -!H6p3FnG(g.wN#9W6R5 -u;k v*t%Z9 \/ :Osvpy9.1ōZtNc;9UM}psڝ)7 T -ew*W?RE34 .ąF4)4U -hЀz`F`f$8 @" 0H`)pYZmKMr*؉F0+LMTrdi gM8Q1UOs~ Ib3ckg VylZ/[ k^xft'Y_"8~ ?6ҕτOٹ~(Pn)ϮOm!*xKW<:.QI:Y>gėFn̈W[Wv@9u׹MzSV_-NDnnlپ}nI1?RmVsVkO* -wn&tۚʢ6'hx9vwWxjȡP[5oő~Dcd "S,cm,ŝ_JO(:@ AǐWɏ0ֳx;K@+b5I]'ft\R'ftᏛ}͡PNX0fn4I7%hdr7%hdr}Yڭ^J]PPңZmpʖz:7Y62H%, -S?),N:gdgg -gruzHgK]M(*kI[UXB}l='V -?|RAW6+'6+vIƁi+ymp@0ZSimz|"1FayC-VPV:<դrؤ'fWykUOۭ _WhY|R=*TrC>ZڥJk&EHHQ!EK=)RH1OJ)VRI‘b'A)^RRDRI &%tRvTSU!BPۓ ]!-U_2 SM &Txh6[RCTXs${0$̧=`o駒d<=C?< A7 -BiptIm>43L:=p878h̲N+͵Sipl/44 SF7xgos[CFQwި⌸#~g$L_TXD/ʊ5NUN7xM}m+<}g{s݆ZcGr7(`-??q!m+)rnݽQvѦ # -6[]d/ѦUDrDƧhKT -hЀz`F`f$8 @" 0H_,ћcT3 UϨ;Mdt ]Ȳ1,#椥hÐU#}q4,g=݌M"lCfsҗJirsRnrs(3mBw{cE쵷P{nzswVR8{{?MY'{ϡ]T҇q鵋mM"IH6S$C);9(Өsl_^ޛNR^>,F;>"D,qR>lOs/I]]`#5ֶC.._rmS~BW=[`_\_o3.>0;<׾!~{#=ىen卼&^<*}c'J~僝qރ/)Ф2P39 > 555.C@@h=0#03 - px^ @@t@.%I5QkfN$r9zc7pvϙ*9s.%1ۧ8G;^ ے1.0kLK*&-,ߤJ\zF"أ1knT*K,KeQZ/ɒ_%_e;trH7:({e+"5ۖ#nrK@v ~yo[|\$CdVDD -O"K-B^a98"K+,?Q 2g535t'ֳ< c.%+KN:Y{8ʺf 2|VcɱSLE#)"I)Rd(/)%Q,Z\L*UB]պ;rSHNWC1޹$%㔧 :eSnSuPc)";h :du?A ":.. So)/Ԭxgo H茈pGt0]<7zڠ}ZvJ[K3zW7k[y;ݺ~.iZ.ahZkĴSyh?6ڠ+MLnϋ앟(ZTZv)o j?]IP쨄S>ܫv<?O|VH_r -.Q(:@ AFhba:+>nDjo'4)<3r>Xmе1?# ~/9%(AoINRfqiV-)WT:.S'uJhg4l/xfSY)9@u_7fpC8k9%{; LyQR>ӗ")2W-9,x7-~>ǥ*M6^s9̭m)nCŜX>{s՜?FVFo.ą_N8>Rބ2Hj g4ݏ. p})o)dg$UjZ4`ă`8`</ B @:Xz dzb.ײJǘSRf&˷~㬘ٵVnqJS%evFZ;1g>J{neL,)Rs'd-ŔK%R2>19^>ɼ3"D}ΔhOIzo'BLi>'>cSGU 9frbfݘcʁaNHv~LH3ggr aJvVjGsj} ܤm"s/loҢF)kɎ;cq[ge32kA߬>Vη9zQzradނ3r䁙YymQŽyQ)Xڮ*@@h-=0#1px. /dRyN1Qm8LPLT'ЇqNۘkLП '9c ٮ=]`J*, #۵ Bn7y ~.RG](EXzWrTzt:1>-_rEh$r_./'>t$ ~i -~q߼S5O1t% ~Q /O/@ -u)㲏p5~G,+m96WmD/GVg&NTYWzᏣ}K~fElj 9*d:o9ze{~)da(z[i/ kӭǍg&>qCeyr:~)?35>^,_&_ZMc|`n{TJa8PP  @ f ; A -HE}9f*.# ~N-uribrq3'p ~g8&ِ*KZ9P:!Pq}ʽKػ_~&9Qw k5M5+b+̋5b+̵tK̃_vRr/rk+4Hm|oJ//<#\wLWt]D>Ӗ-8`,^Np|n3'dWWMqhzWUOƪ'^.oX9^797~~|qt!6rES5{ظ=:̳[C\fKHr -^w9Qç Gȉ:sU6#'M@"x)P@ T@ 4,0hL , -N? H) |S;ȐVy,+X$-cuX0ck6KɉzYs)e,}u78b]0xG>rmXKm$Q)JR֤OntՔt*wiVIlO+[aJlbMڞC^%B?-UM&꺊 ^L_~ubG|EqJq{BFv: eLC8B'̍gzN1M1RʅyTeacV؅bC~f6h(74E瓁;Lc/RC\͖ͩmOX zHwW=2gRk! UC)A)d` 2E0RPJJ C)a(% 0RPJJ C)a(% 0RPJJ C)a(% 0RPJJ C)a(% RvndU2C 9a`73qXLRHvnìkq^u:V]r״ӣM<\v;=ܓjk9}4i[w&S D) yz&{c[;RA)m]m#2}~р7Y2W~iYJ@e)'K.{Jj4`D`q 8`V`Q߆l,os93lސg>糽+) =Bl>lUR;4=t{ 9t3j} ҅Xc6Stz J.*=9JpRw}PJ(K$>c)2}Jyf(g}WZfWcs6;+ -'$=-躞DS{Nl]:iSyt+*/U-qh:xzd??-ЕW0k2v晢iWb%!fKqi9D)ۑ? ̝ x>R*#Px+i2s1CSħO:$>uH|!CSħO:$>uH|!CSħO:$>uH|!CSħO:$>uH|!Cuhe*FYgdR-d$ )e}$Jaw'Õ0Y*:M(gŽ&&P H>1J D'*eR2dkQZVn0Ri]Nź՜rT6m>OKo?nKi-AsbN)]+~8eML!1fS8% Ox8p -ug]>Y0%t8 -!2gdgYY>ǻ"i*sP#r^տ+VC}Ý<;YumXG+K3J橚Wyveh?)N9YL)GI~͔pJp\Y8ɣPP  @ f ; A -HSYE”IVȪYQ>i粆j8C”Fg,5,ٟeiYúXar6P0)g h)> |*FN'*^fEZ ɷnD<>/n\qH~qT \'C)i;eR\ )Yٵ P.G-Po/P~G"BcK26ەape;]ʷu뛗z'̟.S vʔ/T#v&*G_mK2]a~2Á#EȆwMeq}] 2srgwΗ7!QB3oʼE)#Bc"X3mb'K -PdQEE5Y4d’EKYd1HhĐD8‘B+Yd$,Yd%@ YɒBT,MR PIʡ$$L&D٦$Ѡn6?d -H 44drCKyu"hNIܠPmo7Vbh!j* ip&;6`V?FL؅[-yo͌xiḧ6Iu{u- N3um h'O?=L]9 - ݐ#\wgLom.ep݂/ 鎕+cI{PwȔǻ -dM -ewvbҰ{Mځw -e Jry'8؝=Үe౏e\{yyRr^3r^;G? ȅ2IlbZi?/N5*y7P@ T@ 4,0hL , -N? H) WMP4rTTGG8M#Gsl t8}9gግRx0qc#^u @jb6"ZK@L/5BQ[WT%+s7~0bo-_ -"fٲ&7Z,̽_>jCS.9NvJW 'O^hN//,/noNGHX,d6WmC -y#%dVe -`x =Mq:AWvɫĪuh׾>>V>N5->r)t&Sxlܱ޼0ɵ28"қȜmMȜO/463e02"dN[98>£PP 9D`q 8`V`g^]IAl1!ڿjc-g# ?81QG̪J銨%\#;E,w-Wʝkig"̢?Tz!rA - -Wř=ǣ$G=̩鸝{u)dYEk(e :Ae ] -^4ޝB]nG|wLO&+6!˹B6/IexNw#W(r|IO>Q)s_Pܫ2ɫfbxMUOwlnu2J9ַ4r硔bMB)}37fcڡ!n͖9UPʞɐ (()(#="r%((e՗J+*@@h-=0#1px. /dRb;FQPQb-uaFf icQJ7Q4mgI2Xinoaie09k(O<3I()*O@PsMz'luw|HraʿNlQ (0YtʸSaJ핗.H󅕲rdn7-9E$ݷ(P\~{iww%SoPۆ}˗9"A|+3J9|ɰ=6Ovf/PVgS=W^3$}fSD2l˫U5eoXvu]g/o0tMÍd1&k06[α{N qɥ.w,$ᔦ'kd!8er?2NSt -^Q(*@hA4&̀`v'px@$ZuwzZ1NS-rVfZFӵ4qJ 6NK'i8e6\mXki{O#Zio#oUÔ?6)J XDNQJ85q/4y;Cuy9Tf*}EX2"Q1N9$:e$0NDNf䒳 ϏS>^r6DiS>hH}rj98嚞e9OD#;ݍ%CB|''2=!oe+>cܫK[+ݬΡ*() dM4eM2z;ztBnOdii'Jb{^fȎ|/\2RY2#ۋ?/ ˝ /r=R,ZeyyykTrkۣRFV -@%P5@ t@ A 08`+8  T@^S rT?W&9x<]r Nw0A? 2G.%V$;9s|Ξ`5lq.j˿*s"-3(n\J6|)qF -S_I_J'#^zIKϳ~W^_ҾxaĐErike~3 F̂ȃ7<諿_n4-m,=i qO8̇⪋~gh˱GR2C -]}9mIރ($JjE}1Kӗb̂WT -( -ЀZz`F b 3X \@^ *ôŒrQ 3.F= V/ c=$*$yPc=3Jeeo;eS.=⡴]YR$r&P:7*q]Nڊu -RnF -F)_8Q)lG֢oLo t/ppʿ d٠JxbMCҔnI^#\ŗ y̲B|-N YlJtW6TrD<*vxvjLYBblR}e``nҖA2ޠ/#ӌ E_g1?1Ϝ@r], 1,m[;ۦ:\}!hR!}o`n^TJ1f*@@h-=0#1px. /dR=u"^O+[U1(g Ux@al C0Lg4sZٛ|lw M޶x{|5~՘1oNI\Y\YE"_!:~4[r j#^!U+DgK?U)\wOdRNt@]};'O)M6G}ר9fAvgmtd|D92O:/+oLRR>nSL9^*ꭈUphtKn+68t3j}GFH8onD\f$6nx\tk82Tc1ÈY!fE2~@9jHۿz/m(@@h-=0#1px. /dR7A}o;U g4Îڲ)D i_,5ja̝4WcoeaϸY1j&p^U)sD\+JC,?Z}v|RNnjAéldsqeHt$':E!:{NpKugX+-e1˟O,"2|.Nu< Pj*Hv !fy>W_AxBmtĽ X[Y̲xYe12Nbᐅs[{ރ{)OmnTkY9H))" RDDDDD1]!VH)a]B\Ww]kɫpC{67ޚrK[H}ڍK[)|iuKZVր8ǖ[nޖq[ݔyu[Sn(mu[3oJ%㺭MpX.nbu㤿pn?:<8bcEDGJ|!Ŕ w \k~)&+F',"IqNBYr O }Pݦ T3ӶML?4d7 D;fz{[xk2"ka(AOL'x|7%mڤGMbnl'?q' ?iQ{:{z"ݿ2$.!d:*~RIϢHg6&bSSy&AITl(6y|M~ŦDrE1r5-7_n7;s{=F)VٔެNY|c(O7{NlthUnlm_4đ%ݤOߓ"?zO?>2{O^Dt2iCX|x9ܮ4w#&>Up)nGJ+ՌI[i ML8y.uNuS,廴%8jc#uu.}I8iq92X+1l.[dTNhA:[˞L{ˣ~ \'{Z[̞j0<(X~"&L$P5-t@ L D3'@"/?HI@Z9G9jV06ZPe Ch$mȡ5Sr8e1lmkb쓴iw.0AZ= >)*)_a.b)A)ߣu8/S2f3_NQlH)sI7QRukJD) -Y)ߣUނJ٫;[|$L)g'7_$/O[UʵePJPJ"uzOM?yM)1!"@$#V#䤺t iXWjǥSJC-wϮFDҿ!ByѦyzfyB@O'TmU#hSh6ض7Zmm^@# Rz}ޥ͏?rhYCn9A4t96?OxhqFC^Dwh|9!mrBFۂD?Ds hnDU}_(\h>ߠEyŗnDyzK"yIyĚpC;&@qiii8GzlbCW 4FCC\SӖg9uoeK/{Ud9|)""<sT}.Kt-L66O~l0\ d?IxJ{mw#6#| G\@@ =0#0p -X ln $R&i8MӪ"ZOkimM-QAZ;i!8IhS-EB2J mϧ @i> -i1RfcCvʅArF)q$Vcc9e:6S}Lvʗ8`)Nr6_=[XHrkE'iOSSt^8%y@^z.8hGK^WUl59%6B ==!A,)i+ǯsJʥϭ:%CypTSz"RӘZ8fH]7riwM9R31\಴oefڛ.8s'OK.o\N)y RN;R$';$P5-t@ L D3'@"/?HI@iFYǐMjy\*g45N3sn3ƘØz$4INf, ؚq!rb\jJ˕ŭ+Fw1gh/(+2R/v"٬} ~']f]K_%}؋ɦyy膨LKol V2tB;_w%/wb{24~B+/伃f݁.1T_MsCecK]/Ӂ3-s;_N/K/R~i_&B~l_r%%GD%`K.9z*` `QX`vp@> HR*VfUX5ӬKULc3цx5E/\eX[kfC,7úY*b3iΜ_/5)E/_B~QWI~9uG~FeJ>!hr~ m_de[]r~I__#IGm)Y/-zO W4~Oᗲ5% !.=6#Et#Ā+222ĸ@Ŗݫ=eA̟T]z"RΌsT~_qK?y\c(*c.i1SDD朧#f]t-܆L{ ghz.xŁ' O]W5]80kyJ}ܥw .Q HjZ@@8f,6`N7D^~,FGiMͥS+}ZHJim!J- [HZi̡Nhaўy~f"Y)JJ R -ed\D)"P}$ꞵN/)K_>NYZ|u%r˒=$/mY&}i)?>g>21?yE*4iC8sBe/q)1|z#>#>RAt dR3\2~EӁw_uJ+`TCpJ=]G8ʏ5CSCG{̦{)#vYe7Z25slo M)sJsCOnJĹ68e_pʩWW(@$*m\u -.Q HjZ@@8f,6`N7D^~t)&(U5vZife0OhӨm2N[iUshw ͗B-.О\Mo87)%B9r)^"S.㖳uJa$,S6,fߩI~ͲUV_{OVbV)JY 1*l(D)omlu NҶjLs+G d6(=ҧ^1Ĕ߾'SnRpb:Ĥ82\Bps%RmAĔ渶rCف2:4nx0RSiHTK_1,g_el3MPJsq5r:hmL{۫))ePJb#gݿ],OGZOSrĔ~Ĕ>Ĕ9*` `QX`vp@> HeMV2FFhijfi])e K651PJ45TX2vڹHs{3B -Ӟ[lع}Tj9QOVrLI 9|jR1eF$ꑵ1&) -)_ R^uJ씯)}V6|ⳍ)XS.FC"4N:Z -sRpJH"|'G~z?.8g<85z xDWbyG+sHMxE~SJ+V[+$Ew걗7kF mt_̽Y774^bxmaI6z>9|xԺ|0/>i)86>wM=@؛N(]'\;K -w+ -a~'!QxJ1NUPF_>xgXbܺ* T@ 4@ (0  8<< |A.UdCUVMkX q5-L`-,[Y,Yw?[cbbsV4܍rfL,e&7ya5r,7oY&]gL=었 ~Y^/7|j;i? ePfkG(~y#}%A>қ2 5eSb2q!_S)cE*)3؃Ou6YP";pnN=\sig_i{(R]w]e7wBf)M6=PէY7ۚ2ͣ,E"d=R#V S EdSYj%r=GYfߑ3 .Q HjZ@@8f,6`N7D^~t)C&ҪCTڣ4U0Oц+) ʠ,RBs04_F cC{:herfb JV%R礵2jz9bFJy˒De,|JR,+yY)Aʠ(*clrMA$qiHK"vdOR^v(e{#"׵X֚pfݡgBlupbED1^U䎏!~6ΖщUM;ť.ruNU >pofN[0Q-SR]m5WwMK@)E&[",ŵlթ-SC҆Wj($ʠ=ۅY88"m(z Ji'r`J90 KThP:`&"D ` ؁p x x$wkseC.Ӫ2hѶ0TΊRQ0OO+eYRJ=cY~Ɩe{6ei]K0Oyg񎝩˓]R. -J钕rQ|L0=ঐ)e2hAWaAvJ|wN CrN)Ar(H/rhl)S)[-_ -s#يr'ʠ_E Gvu"y7<׭ŐNI)@F#-51EH 8R2ŧcnگ8׶m)Cʾ. *~Stri8cFK"u;\b8IN.sx>8twb.2*o:[BENi} NY 4L)E{oݛ 6)]ppJWr NKThP:`&"D ` ؁p x x$$Vd/:FiMBSѵ 0LѦ)W7l=m+chg e1rF=ôc/)V)J)ߣutkհ1EZɿg֊27F)m7Se쐝bR,vkÂVny9Z-}n^SwSN)JtG7D5kkBM||(: Rb&6\l &55sJ>8wV],BvvA5HV߬>O諞0tWa,M6 $7]Q@3ai!j- ٵHتgU+O/*G_^Tp(ea| -9[qEoPZu[󷜰GU+^% -hz`F` A؀8܀xA"Hҥ)ldM5cS4Vmʷ1%V]1a5[M6ZYl͖cٜeVnꮲ6&Wf q1@ZJz:dZv(\NU$9Vۥo91tuq.ih9|P&UGF,meڳ2WdbdYdlf,tP42KK,3 .Q HjZ@@8f,6`N7D^~t) &f4%*M&PșY:XjvM*9Gs-sffer!/dr!/(e\)ǯQ:HqFN>Z\UʑED)JYTAraHyJy_h5[}J/s"P"˻S(% Z= ښpʠYh9#YbcW#- \Jll:^{&yrU)%3eGN pVN7R }>1{QSIƼV"b"9Ki3[l]~oϝ&=(zk wPJmB bK!C^({_a=iêRpJ@P  -0@A0(`,;p DNA4YŨu3i_YC3Uѷц)MJcY2MigMղL sXH{roՙ0Y)#+KAJɑ JaIj6,Y-wl06ucupj3xY)<#w*(zUP? :t˰4,UAjRtn>z=rq(z GKOyRXF~C8VB"g83bU܎ Α܁4^aimm}\GrgC9ENũ:^OmwopTHn,ץ?RoGͦrdse_dL2U}gm$> RkFJ~ J6!W,RːRR -R_lmm}@@ =0#0p -X ln $:+zTtVh53,0GKf$4JJ- 4Fh I=@u+g$,Qq;EoY/>ZY) JNVVV=R;'O8sW6rۛ$/9㌄0)_ )_ 唌=bъ?*N*at#]('[$0!|=%%6OkqJ@P  -0@A0(`,;p DK49Yi)Z;CS% O"PiShlccݴs)i~1#42`Ĕ+?6șEr6R,p6앒=Bݿ}#L0Iu\VʛA&&ɠqY)o`8 `JMAC)6DNorDKe#_/R~#}%G&̥C)ߞH^rhC8oZ1zm .>%OwSyH}ڌ4#.&>% YW_79*6P"s9աrHM.U1EwNj IƊ=fS9P4|5U >Nel+lɴ/M *~eP[{g - P ʠ%A(&DT2'AD% -hz`F` A؀8܀xA"H+}cZIVA4K3˴./ІvXO-sR(0Os{KiaJq)RJ}>a?S59")||+*:x7Ħ_~$Ϯ*CEVʩG|iQ9OrLEYm}1'dM;Rl%"J6wTTlݓioRrRRHCn|bћvR(U(J)kR_`G─*` `QX`vp@> 4yheIq z4g&F:}1T3 -&m*ir1cd$ÏIqC1'wV!r9"R.='KktsUo&s{囍c!S~*;%:䚕q3u -Y:4bʡUpNZ@^1'&)bJ>2>g87⚇pw-Ή).QNvcbz Y%N\i*_ Sf6~<22ƶb?{8ejG-᮫SPW}Niz)=>Bû?[[[$P5-t@ L D3'@"/?HI`Hj4^4 M;Fц6eIN9nfi[mobtX-ESF{[ + L-A/;&ǔqJ9׏ڵ|[R&?$ ; Ŕ; / -s @Ѩ|b':I{2/SPoٻ)&GJ)>ł⁽%Z2@.ډi\\CL8GZ be*MUùw{yYuʁLcj1PmdEhEPE"ͺ|B%ƹd|rxwE0/z?ȴXx`OޱkQkQ T}pOY{B\BeFY,=NwB[x0',oFnz*` `QX`vp@> mUì*UwX5XfeEFfmtxSr4=*X{)a&=/G N6kv٠A_69On n'IWf^'d\/?#DssS޳ zUd/#aiY'#}2)EU'KC,^R41Ƒ!B|JZ#%FHspXŊ?V_1t\èðgޡ.sm*9N}"R3sK[GwrL]q.}ư*\4oz)"dsQ,K lmg{AdYG8ʠCtvaW&pǤ9qblY \M~qS{ʶr9::4n̏qi^稅fY΍U/q52m6-1K֊dG"zwYKg+sn?i}NSSʊz8e+f)pJ]n = -̽!9n-.Q HjZ@@8f,6`N7D^~"FZRͨ)d5 U0͌ѷ1JF:ieL*I2O9W2R;:̆Z<)ry?&AkS qiŻ\(2D>>b)C) Pq}j)N]'RSR -G>1EFꎞVv-}8l1TA)m;e;Q .Tq:[HIse\KzWx#MI_i!|Х~(MqP'i*aϒ2Ȩ.؞tv-=fY*)et(qviOXiP1iJYd~RjRjRjRjRjRjRjRjRjRjRjRjRjRjRjRjRjRjRjRjRjRjRjRjRjRjRjRjRjRjRjRjRjRjWRF+Ri9ѶT7T>6ѦC+9!B -R6v%ͪwLeDʕϝʷ=Ar<D)s2 9'ao8ۙD}}mEVmAN ;{֟ZlwɛR=[RORUv#@Fe),)RfR+۳IZʷ+4!.qI{9ˑӅx.į_"͹o{Qeuj_yzf ˻ Pdݬ;nB?%cSci_@rثwEd}8a!jLKGol'[ +gQ,^TpM~pYg)TC; qYM{+oHOv K,lemJ@P  -0@A0(`,;p DKVX^VnUg[5ͬvJ՗YaMRHZY;ZC+'YSV nghXϡhourdQ -V9(EsN0YY?Iz3? - Ӟ ~1_YSTAĤݱi/T;RgTA,/HzIGҷIMkKLe>sVb2+CtǻDG;%ddd||zܿ_#oe=[~e~9fMˋfNK Ud<:>P$cQK[,-OMlE6{O_fS/' -t, ᗆyE02wT>׫$P5-t@ L D3'@"/?HI`EV!+9KvJXV]U?k5$ MvZ-,ͳ>+_ --Va wBb &]sP]B,}e(Y dГ~, ~o/~8_)7MAٮt%~/ΆkJ*yy倗t~t2vO&5ǡ$R<3(.s#" Ǧ9Ɉ ċ2~~`b.br7W}M([:rB5< y2|FjhwBwn$$1hu|d&\1Y0 9Yrʰ`}m!|I^ޥ_k_z)sA0~>`n9#WPGMSk>BnALT;-#VzK|#^,Cq*9Qex` -v\$+ #ܬ`:PGUB0u娣=,`^xwXDㄹ솨<鍫CB0CW}tB,?r Cܢ ޸z`·0> W HjZ@@8f,6`N7D^~Jb,9-qheX]UJYtU&7:,;ږX{9re;E GřhOݸ]0 !X,Y0As`&?gjifIm{xl_BN-"_lrSG7=[Dyw~H: $IytƶtjE~9r~)_~U!Я "ɝj8(Jdr{fۯhR?{]dWjm{"5\^h昚H],Pc_.s9ᧈ]Ȩ|t_[lu :Fܳ]h>h![$P5-t@ L D3'@"/?HI>[V,kսf6iuݔ~2dQ).M-ISK픴uP,AsM{si!;iO6xy? XY)?UjĞ_~?lyMtMeR՛RבRKk[W ͥ928;R3i)i( -@s*+z:Wˇ*P"+@)-4RS)㨁*ӷKcs1l-07>l>>6u%^? , J9r~y0W\鎾̫!| 4RzNJ%* T@ 4@ (0  8<< |A8}NK1MTCzT3Yʣ,FwwІ\8OjeRhK!^yos%l>}him`6,|r_3\|%RU+agk?B_L*ײRF{1:T[)+GVW>srLPzPH)Jy8/ʤ K=ҧH)OGzfWD7C)ڝk+|Нv:W2i7ubgɸG{]ّO8k1~|Еwq~ToϽJ9Pln}72.K}}:;tjm1򆴛PJQ# R\v!n">3OB)K3H)CJ+'e*` `QX`vp@> HҶnV0 -ZSh;jf&hYyw@[ -^8nv|5-T0b7#)dl:9BT'>觳/|l -q\e(L$@3NV׃RI) -Y)e R{jkj^ >{{Қ'JHUkGJI!lID(Ҝ5<z;pǤ;2T \#5uP+UHKM_AqGlw([vȹg8~N3R䨞8&;/RWr/@)}>\(edsUydD^NMng{R&C)cסqOZ+2= - H@lKThP:`&"D ` ؁p x x$ӒK+hrden)45B3ٴ (|())4ޠDM6k6wsΜA RS"rJa(听x9?L7CڭT g"!:/Ny諡~ - :s3p_PgNURC)uHzIgIQ ]ӷ'R77) 2V5w˞^/󬳒srB]bB+`,99RpGMGϮ__)S-i=S])F쒝"S -N٥N]=a> ;SI3$R!)3%H&?:eh/`2;)gdi5-= Dh`6`G<S.G<{S|N9vJNS}SROXܣS= m 1I|NyT涯}kKmo#SїDK̉3}.)ӗ%9&}{_^sJňo0'-͹C1+q8czclךzf3 -k4Qֺ:~,ƶ~?x꧎˃;(wIU%, uB w`2^*ep#(w#ҀjZz00  -lxNH B 4@'Naɭ1(aWV$Yћg?qJ'^SX9PzYqZYj?>vܧYGWpJ=*e)唟cn8PȆ4}cShۧϾ=)}oޤ蔷LSJk מJLLg8a8=NrEJeS׽w_sb:(uqJ3nJds y3:MͿ@Լ;jmn{xie/QW.EUΩSiwqZ{E[zMZ,>Ej3Oс^StNYj:=vƟ<4MC Zt$IH0`$D Q$D`%F N$$xIIH !HBRIH#auvTNR$i*A]Ww|"ݳeN/\?Z`7a !yisV)R[d(Mt~lS6k~i=)o_N^\Q*_R';V -]n"9eL)g^&sz62L'O)'SBFg.1iqn5S8Mt3䯖`j90g"R-;7ڛ9s80'pR`>2%G2%;կNW_%B fR&WOet_@v{ -NyYvJrfuo_*s8纙m~m N)K?1)&SSnyNo)LR6i_{ 7.]JHGʖ\")?ѕ'/(l ]:o<&dE0pJ A3\M*wN1Ӛ&J6X-QO)e;ؚx]gw:HYrN鮃SW9Ni# Ni= ,-;Hh L , -D+;8 D ?  -Ҏ~|eXU/ugY]-o"0XS#kb-0S}X<ʺOBX=Ѳi^v -%-;d-;6)v,ce\sC#R -Ϸg$bU9xy| _c`h^dNξdd.[+Sڐ'9 -6\i t -¦T`]6tk(5YCe >c!o_^ }蒢045 ɂo_.Sh1W!Ҡºp,G.Ng]KIs<˗OH\tIد0(7۝tŝL~J=7W@iζRWSg -(dp!e,~nre^~mǷDW 282PK/ /GcKw-R2,_& x^0@ 4@ t@8`F`f`Q X  @^  AATV[X V9muyc{i6[:_j^ǷX)}&VzKxZy[l`nx#Vax1 esfBW?G]cC Ū4}fY'dp -- ,NA07oo._Y7tϭ GL{ȣ߇`tȧܩTfklp㶐,HެM\?'ە. x_(f'$W I5g„;aFuKfHlGW0%PC~qTt%dn/ZZHGvXߌyOdyrUt־^yIl!u=Ki)/JȘԚ -no ^o? !KcARGA)v)G.2-;!;Hh L , -D+;8 D ?  ->.rtnjpAVsz[G83K#wW'pOp699Vd89K]DN1uENɋT7SeJMĹ)&wG) rE->0@ 4@ t@8`F`f`Q X  @^  AATVeSγ9V?rgXC kL y2eC%?:¬su -:Nf\ 7>5J1DJ1((_rQ>YyS]eܬtY)R)lJoJ疙S+Y7e(eabc 2RPJ4?JY)R]n*tW\+#1ʈɐ<>oB&UUW< :aJ_xtQKUJYzE7t \_UJݢTYusQ%qkTq(fu[Y,觎c;?Rj.@)]PԯJi~@:f?y*<tA)C)Y)xD0@ 4@ t@8`F`f`Q X  @^  AAT>9kNcJ%R "BLk)X$KenkV{ 7'n񢔝J2W"i5'sTy~Zhw;Nߡ=yw??wahk!elhjʜ߿Rһ#-Q?My":H9]RAUWTˏmWl޺}k$M½{V"r- P X@V`vp7@~A) (ygjȠ^m<:y|i0\[HO!"q|3gb}Bm77N8mxӠO'j#AԦktGOp]4ɍ~W냲_ސ|6b/~'g2eK!~1/,O}-ȧXT mMl*|pKqAbdd$dg\Yd(t+= '99YU~)u`~{{StЫMX|OBLͣ)L;BCt޺p1WӡL)+%dD,s]t7WzjR W*Z4~EO?a -A3t6wGԩYn1L_=:f00&j6*j޺[Gr&_2QH9RsHN4hĆ+^ -g[@' OQrQNgʩ P X@V`vp7@~A) Ճ.c8u[mzY9n5vk`͵՘Z:uj#u✋άѝgsyso_9uؚ{AN?ckt\V]c놭鹻IHR&+VV16eky>w:7!sڍjOc(4 mwbeSV6Me,u/)E\C*>O3\ GJO l=|R}WPۚRrJx^Vh r=? N+z"0`95U,XrQA)Kߴ.Xm}bq.GMN̳P@#|JiR? 5}}PBQ-}J}JYzJt4 P X@V`vp7@~A) >*c=jNN)_"'aRIRIbeKzg7bIŊ- @g73kԚ!+e+5cSLE*ٹ_li-ʍ_]ҫ -J?^)}t3u,QJ?Mr2qnfZ[3"N|_A)?o O#!&Tg -wKqgsQ爾LKqɕ LIW(iRUӯc;i{R״P;'(`#~7qPځQů>%z s;53:~r,RΞT%07L1dȡ}@yP$]r(oKy@@`&`؀ "@@ -Hic^iSuhf㴕q8k3 9F25^xT]NY^Ku- ~|Cxpp薝wE2gqn혤wS(t͢XzD.S%tVx**|C⏏+QsRr B\@@=0#0h Xl@n AA2HR|+]B^jVYJu㼾7n46E>zC(oW`XzsqPʻ6UVOFiF>!!yCʆ -/a %S?u?I-O}V#c?(I)Z)Fd*N8E6bSAP\VITl(6H >bC@!ؐLvΤ0{OuJW7}vٽƘVu띗v+cqt /S_]W|:^V~vc_Y5DUcv/8iounqD:O^o R~ڑ}_~_JM/gz'~&"%(2Off"vLWB|ו*eK{}g rl(ғgaf~f?=T oՕSA#RƑ#LlSdR^ˡ:_,CRm}/)T:+_yR)RH LJ -)bsI&UKz&I$"+Mw%FƜ$?44 $YNl} ~_H$_ߟ m5WjYC0e hkECU/}QCU -W_0omX.㊆La4?V[ eͩj MMyN4԰ Co{O!43?fjtPAתp4$K5J܂$82xC@IKMw9B!5>Ó($؞7ޱssΞI(K5 #E̩ͣKuGG̙||T3bNYfSc;^/)|دeƕ>2-G9FgvW8K +etǮ(q -?s)yy|AhZʊ -6?c%z*  D`qx`vpx| A - 2QcW5%V \!+xC3olhj%1gv#2[Nl7fyFgUXټ8{x)+˭_KYw%%*bq~a%._t#1'3_^T0GX嗿~+zDF0~~IUCvn__n/X ~XD̹^Ĝy>)B]x׋˓?[Δ_E"\bZ|ÝpLQ4PbBFZb7^\㗢ҽ6C9}{VP6 -c5%mKsx5;(5G ̦;]OP1c Uqu,''o=H -{q#8r'$'͔jߒUCTJM.Q hjZ@4f,6`N 7H r)G80KpZN}s*~N9C7g,L2B)c(Ws#}u.B/"3I͜p_PJVJ~ʯY(E!+QVa}6lR2W)e[b=I)L^*\';eRe8)N1q5a T k)ohӎlSSp-W)~h}O#0 8d-gzg,xODǧ&ff8B$!!yD#Փ. ez8zb oϬ8%rE&umyf`Yv*0$VחϾ1td]be6M4G>߳ KaSU 4Ym4,a٬5,2)eY[5g?:Y!sbaW/8&;L;Jv=SΒ%;)arrN|RT䘲mݘUJY^2qZ\'+*Y)+[dX{FRʛa?Z6{촭:(*S#zhs჈)G>ELy,@ozh&H_bJV=37%JƇBB(=hLqd$W+15F)={ͷ9UD9|VjmP ڠK5E);) ڠW. @P]EG'79z8*2=sE\[-?"e.?NHlB=( -;(i}oEE1w{TOwA^% -h8z`F` A؀8@ dȥ[<=gUY՝V2;ceJܴUnw[ xo/Cd7ffo:+B7β˼gFl&_7,~9;__rvXO0%KY\/)La?~YuI=/LdR/煉,YO57 ?hnuv⚁_.Bdp"Kt=[Ȳ[27p?[Θ_#'&f2vT!5HӐ_RRR^E[@ޕ2o_=E|Rxn?.tfbj:({ͺ~qnșl3M-eʘE#۩z!?9]\׮ZQG=eQG_޻Jܛ/(u{(ۛ^]%$&EE )ZRR8RtI1b$DJ4)1I#B -O;)NRRܤxHH'%@JdRRH!lIT_3О3Iܒ_׷'j<m'yIr?_IΜp&IAtO!K֐6hYCW҆e4֋~1\к1 n2h߮\9&>L^s[ szG4t@FYqlچ99/}S?}!LWA:X^ Mo]hLbKF{EQ Pȝi"\Cq%Pa^E{w'W:y]7$=wSΩ6VW{K01XSP1h,1ǵnwYzv׷ +9->ı(+ T|]4ԍΩNtN/sBT\ΩYpJ@P p@A 08`<;p<@> @bs êu+cK pÌ՗ì5ME9HFc,0\^:,.g3HM0~U^jE)FT{RJјqe(S` -sVd*HY)+ lnrJ%1ƾlRJA)s/ȧ~ڱ$-A:S ءU *%rIx!͑Jt9DrCLOH%דF)Pѧy+Jv(+]lkSűYnbuG}0Vrlݲ *hft{l_]_t-gR!}rJ<ȭ)!(5T`iJRnoXQ -.Q hjZ@4f,6`N 7H r)rYe55e\)=9SS#"J9ZYd%*Xw+rFd}3]Y:[5LN)VVerJчQ -{Zx9uU$dJ"MJuSn.lS8e`)1%:SV)߯uJ,򦝶Y~m?]4NyG--yINݒrW=3-ŋu'&S+!$8NKLOܡ5NܫlN?┉7e.ij>݈) W=!>*v^b7wS6ke',nV<Ӭ:Yns<}씄0N9";>)ar$SSuJPZyҼSyG -9fGv&SsNфqoSNDPHWil}~Ny6/9|꧝?hENOS*)tzg)n!1S2C #$S^ɑ*xE;MJfċڜRW|wSj_t(]ts1ArOcA{yyLJc0wlezNiظ\GB|%RѴ~pJpJ; Tr*?I:L.SGòSpJ@P p@A 08`<;p<@> e_Nd]ʩXu9j{YX]oc Yq5bVֶX"+sN<4X޳_e|?S>rq)oLE)S" }E%.+e hʛrL]V@є7ĔݺHJ _Z^;R>̅RJVFSmqP7ȧ) Hd|E֪pƔ"Ή țp%"%8].GZ -%25Joy~eh|OmBLTԕ])ݗOi:)3޹T2OZ7C{z6]_zWTԣ-7ޢ-f2t\!tpWǛ { - -+㽷Gg_\E׫4P5-`t@ L 3'$~A R%^ykxU7jymYܪ/x4EΝ FӼe'y[oVFwMzx o}Ii]l -3) Me(p"Le痧R+3ÎvU/w  [Th.|lЛ nC8s~qEnA $xӤt)JLy\/mb9f9>eA b5wm/p]v[>:=cDZٷZsG|kV[u;YfFdy>@'nGYf=@Y -Yw!4-G\@@=0#0h Xl@n AA2HEF=9Z[qrh%ezNAE~5L\4Iڠv2ZzXu)9 -͜ +氤 c}٬E'+?_RY)0JRY)0JiQ_S" d Y)0 h|j)a2ee_AwuNNS -;pW8k=t_"Tȧ~|+2Kk8 #gPHf]CzxOs%&&YUW^|iߊS>q(>UK.V3rKT \]e^c!3/ƚ))E(4@) A)~#"hSj{EK>ԭzgN)_>IKO%7^)^$IRfBIa(e߱ʒ].5A#c5m.mkS)pS:wYfSG9zqJپRluYbnRZP!(eyOќ_j9HUM˞RS%OJ%* T@ 4@ 01  " @$@.Uγt-*gXMKrfVXc9kj J$1e |#g+aX<+VVf}Je<" Rb(R~,O*DŽQO:[E\S>Ia^v[W*N97L볰k)N97Lss|Suʉ-pqoELaI[VVt~8C|_>*BL$%ъU9E|hmfbzFHK Ib22C^qzy8igWrǡ_T 1яKS*p;buK=gwMEbl.SCm[)pʁȬr#ؽVOkEw㔯`,/ )'Ȭ2.Q hjZ@4f,6`N 7H r)]rYU4㬶c -8rp5fq\2ZCbJ1kbIڻYg)'T1V_!5 OtIfk^'luNu 4o|HLԴ5Nz`b;r@ފ8rȡ*w]/ -#M8Vݥ]@NyBbupQ!?"cfS_9*ܻ<UՇOfG)8h2NiՓW$"yN)SpJ@P p@A 08`<;p<@> e_3@݌Ua5%e:Pq)'gINd, {;`>]ϊ%ZX_>/bN9[vJkvvWN9[n}8ӲmՃQFhp+ZO)R߭^O{v^GSQJ<CV9ab"̶m+^ -7t)JFJc qOO hBL)JfeNd+Z\L_9#1Sb'!z2H# LOBBB(vʁUx;S^s(GpMo u.m?f9-4.1-2jv[߁Rl72V{J922Z wi9SjބRޏRxJ?14P5-`t@ L 3'$~A RNk[#)VȪ8Mɘ2rY]c 13eWbJkb*69g-',Nd=MÒ*]>W1qRcariMJw'o\O)G&suJIVJ|)0ϟє"*a:sO5w־U(/RU+)' _#"\/kAṶS~fѓTo<:LOCJO$OZb!Yʠǽ\jz^PUjJviK'f%Mk my -̦](%lyظ{\C|[moCPPJR%i/UeA)[9>m9*  D`qx`vpx| A - 2)9zU-\N3i[9f9o y3-qJlm}urd~ '.VNZf}GY/*E)+2ltQY)T3E vd7WM),QʀR^|F)pT^pRvM摳^I)>(χR$Z(H'? W=3wƓ%G͉x!IbCz t/9)Δ4WUK:(ЕoT(W.ՔOiwR\[^ɧ] 3}Mm/m+*&'2>E\[,ٓѪjW* VP&W!n&$&EE )ZRR8RtI1b$DJ4)1I#B -O;)NRRܤxHH'%@JdRRH99WD7UCIꃛ4 II̴L-y~à8TKv7&g&?uI1PwOQ1Iϲ|Q^CCjC;dQa<>-[GE"c= 3¼~+圳CPT ε1Qa40?>U }r"?! ?Ơh(: *|O;~tL6 (^+r %d].GFC!#18 ^wBfH׮;33I=w)nhhixR|M3 M u|vSaf:qh[t>hh~hh(Z|jzƖõPah({<ݍPߡ⧠|h h5h({Jh%)4)*RԤhHђ‘#EO#)&RI!LJ)RxRlIq"&E$CD?)R$B -M?ݳIոI=IS׶&1Vnɧd;MP&ho9MξM’]|$!/mZWCrBF5sPyqH4\Q - uN֐1Y/sY/:U sɍaMK Nڞ2ˠA+9G:K,MR@S׋™N(##=JpdD&yuRbZ={ {wJ|ʶ-thFG5;ufx.J;)ڭ7(Ku%=h:PVÛMՏl~(*G)sýWyv->@ɑs -ge(1n[14,|.W hjZ@4f,6`N 7H r)xݨۨ|+QWQ_n%Gh4#u+"h+PnulyFlOn _1|~B}ĘC$JO\ز_m=~]여oF:p'.M{ /jc߳6&}bi 11g}?'~1 p籅ȓN !JwċK!1GLuӑtぽy;[VFʉ|]/ZK܏ S&pSow;4lvsOP17O<W,⳧޵>AH|nm[<!c*  D`qx`vpx| A - rUvq$:[XܮeJ828C;kcMYD)D)%gX$kaMPΑ%'wu0'~>]y0F)SrJaedQiuyr}ڠWdN_YGc -m􂣿Fݳ!r3'm}w+N N|\h.{1l8>U wFFB$&82<(bZH̔: 1f*5}~arE/]P -깿jvi f[5 Cͦ;ѳ)39U]渼e8LoM޳>$R")%ӈ)RRN ,՝ȈW?$'Ó)dCiksr^ő׷SʖQ2M&& 4 c)mY[v?IqmluO SRƊl3 -lL3׳Ka^zj;Wb=e|~C9}'΅'B|=kJq(O% }zq_<Ү / zI4rJRhRTIѐ%!#EG)FRLDC8R,H$E MH)~RII&%gmR**:kٯ3~ϧߤodI2›㇇-՛&{m3/4ݵ>qpg'|yIGx_͛e1a׻Yʪ=.UA5.lLߍ6+pK5\C=2w l*6n.K{}ky9mD%yk=hߨEhzYj"em.Q hjZ@4f,6`N 7H tk֨U.ɭJ9ՎL-|N?Ycg%DXKל1t;wC - #3>)g$,x'n]Q H^T%킺"VS LϓWXv~ccDZObYR?4y:6nS=6Ş_ԣ3ATR}?j2.^(J \@@=0#0h Xl@n AA2H'7bB) ϪY9jX&ZX]'oe ''RRYcm'lgq̊fY Ym"_vmKfxڵ-QʏOˆ|Ka Ն]ЕR}~""u򺬔ä NGt>u.]褭+sFQEuOG>dh-v&]tѪp"F3OLwH.:Pz#-p{Č Wjz@) R>p(tH) b5s.mU5 Cyfb9z)vftl\yZ[j}(e9(e~w[<ogtRǠ.R"oKTh0:`& b ؁ D? H)rM,]ªX$crq+au%YS6#J)`tu' --(#d=M͒/u׶8_ҽ!QRםG/QzXJaWm|r{XVʣaRJHn|>h -ןRȲ|)B)&(?s{PʵH)Vȧ~:m].^ -JCLx3HH]L!^JkB}{]" }:0VS˥=u\aQ)xNc.Rd6(efo9uR+ >^1v4>s9}HvSgN7PƧjJiX"O*  D`qx`vpx| AJ+'^eGX]f buD:YS=I)D)ˬY4k}ĸY#cì7. -콈EV{rsN('uKvkRg4zm/?3rZQ[rC-RJO?&2C;|S(OR![ B佋4Dr;Qr܎4;u mUMc4[s*XeVJ6_տ`]o;g8#i4t^Ew="\з?߀)eHsAߕo~/ᛜVVz'.6 :>vgO< |M C8s#Kj|BFFjBC̄_22H' oZ׽/3UV{MCee.{CPUt XMCK?"0]X;D^60|k46b7"KG?47UpY:rCVt{C,?D{z-⩺=I$TGdx]AVW"=(wAD% -h8z`F` A؀8@ dNt!TӬ4sZ/Xm.gh⌅iDIrT7gc Vs9g'lNb=TYcBW)?_i9ڊ",YʭܰsW*V07_@0q;k7: w -NǮS'lBf'mgNSsdCfW>򩟾?h2GfͪpWF>-$҅tC=15%S"v%|=͝ϋ,OS>q;?\OCd5nI~J]fܮvJ[Ub:(n(f]ݐ!e\fj~o[t2dœ}hWF=e/V]*ǞT:}R)JwkJ(Ov TRZu4S+?mWj_R&Ͼ.%$&EE )ZRR8RtI1b$DJ4)1I#B -O;)NRRܤxHH'%@JdRRH!;lMTI$MES$H7.'kȱ/I &s/r& I$)Y HyI*|c_:yoDC߈8 <-sFTh̗~t1OJ6D<}We5{ -yl -ZVͽDC~h3<4tk;@_@MMM\YC<#%e8LQ"[R kF۫-i֩&'[趧: Թ/L7ujoBtBNcfPͦ3SwE߉֩"3-]hFly#h[:5ux -cuPSKhz:ee.uhu*+['\@@=0#0h Xl@n AA2H'8+;U5ȫ[2vgy.Ǫy8o,k;Htj)V[^dsNB};65Y~N˯#;_~59-~) |ɜN 0ׄK| _>sn 01Ӳod\_Ӌ|E%dI_v!\T!H'_~$yC8% - ^דINM BĐ'&IDUU=m}J^4Pvo -gusaf #%].̬0^~`4u9:g۰|ظj!`K58RK[Zn?$M=8zSe} h0ή˪^\@@=0#0h Xl@n AA2H'# ,aᓣ٬1d51~֐S6i:N7| Ìs:d+Ur]$_\Y)_l4d!h/uZF{y/{}zNE<fmSLa2%SlSLS}ZzIxp)wNy}S?m?ٳ:g~71rfL$/w<5 !ǛҼkUU,vR5Oɭ;(eh:mR/Msm48Mُ֩ya-*!8NsΎ2N0s0E/ݶnbIukbDk !DD FJ1bP1EĈPH(Hq#?y9ɕOy羇_7GeN=;PXG::}L?}1>5/@ԾJT.R*(;ح -OhV[#u*yY.8LxPNK+EE5]4t҅ G]t1H]@ ]tх."C/]DHt %H]tIK]4(KC̉MR4!TONu)ޠ3dJ1ui?MNlu!~2HSk&E؛iJ)R_J$] : -kxH.us)c::XkVu;JYCD֥U_Q[m}8Vi Sk7R:5vɩW jZ@q"5 ӼgV^%|_3y;0D/0Do]r^헎-i[ eRdsk&K+1fO}mU7eVuHO/(@bBYa/{W|+:IӯALHvC(I,Nnt8}I>GfiuHn_N*;{tnEfO`k^fv~Eo7-'G4]n1[vmUhffi̙C -=uz>xGHjmT暐:: *e]oȧNxE%` -h 8z`F` X\@"@AA*HUʈ0]DHUD3J!0Y}>1|ָY,( ?GQJ\SD8N<=ěOD*&QR er(J1JyGVJbe ݱ -ec:McEO&ؚSjbHKİD7_G -NV//,n7?li-!&Z|inGr$edrC/JKdwmdGDtsɑz;nr/22,O fa;)pefn}P^Xͳ-S-,֖"mi<(5Q=ItЋpJ))xE%` -h 8z`F` X\@"@AA*Hߍ8u m+aWKtsN,1혮t8q"B,oSseGOd^]1~'S.r>_iwV^GV_:%+OǺ8%+OQvx/F"9'UJ/2;iJRr5(RS(PʿC)_y -J)>#\!vE<)zKaR22YR'SL0eH\qEWX^le^l ЧAy^ -1{L6>g+>•lk>Ff%!_ wf'3Z=8`ڶfhKY}SP?'K4kʑ-f?bM/K5?4#[j?tˉumy$7Nl}pSyzT3Gʶo=fuD6'~fYD6T+*T@ 4@ X0 x`/ B RAX4EUTCD=K4D;@qB}g'nb꧑0+l _k9^u77.?hdQV1blr}\ژmٜXmb]+xi,.Y)D>*7>>VyV(k]Jjs;"g".+۷B)ewC)RT -ЧA rC$ڣ+>•lJd"9άH2-vfNL\<W]o,i\VJ9}VPJPW2k/ -Fmj_1MM's-7)O0[Om"|[E uPQ:Mv g6}g8qR0tRzA)RJP p@A+؁< D Tt9e+ Un4 K-88ػ3S;z*Y)ws3J 1Kٯ}e{ⶬ9~,(eʿ)9JiQSrE)?XY-/mTw ]][/2nEOYdyHʘX؍2W$nun:ZR$UuMߕh ?kD=':%N9S| N9;0 Ug> 2sSѶ/wlY\ LntIOv&gd>g''˙$d 'kS=7#p*GAU.{1k*ڙ{Ajun}۴-}8ē閪gJl{.kcjk} S -on}o߳8@"ԳN\bWTh:`&X ^  @@4@_S.r_0A p^S)CV}/y8(rYnsl >4< 1iQFKG3{ l%J,r@Crr JbS!9N9%N13.憭SږDƶMSε <}_-t8[W|+甤؛IHv >pJ{|I9h%yuC]8콟}i%oߝ73Rv-PFnӴP:󐂝zֶߦm/T觞0:¸Бn{1=>=ǞVX&ʿg:6֛ ->ԬQ5|)U3y)NBtdF8qoɸ@řKq?޿1SO,׶}*, ă`V`<px@! ,_D&7Q5Ikym&v769ֶkGDqo탼c7yOYK@ab_s;ʂ1FY0?c"?˵ggZ};'n:+DY~.,_H%{]_EZNZL/E赀GEݴ} -~ eLTK[`#%G2Srȃ$SJNtf<9&.-BTZ/~EoԖR.v3Un?lukO -Sw1nư؟f1>ESFT߆EAC*, ă`V`<px@! ~9!L/Qu!Rqb#Jb^QM.7""wqUsbI ׾Q}cVJyQkb]vX-9kw|S4iнR*mY)('R-+eCVuWj놸h!]YZڑL!˝'cPʃs48Rg 3v2uW(c& -n)3#)q{4 tJ9|BzHyɑ< 5~iTC }fMv -~n>׭XToo.Jٓn~b 5P͎?@)󧡔A(>譠TKK[ꡔI(%(KP|0@@=0#0x, -lv. H  u^. ysZ*eh{ ;Jf#T)͜8gjҷ[Aljq a;i!.Nl^ڭ!ӠE9JV\'+GQu8b˭P;쨻co|1R^@(u)myۚ-d;.CrVu=zUwA) :,ȂΜBbsWG;nmNϗL3^OLHug}_ʎuM\USYpf3SVRP͜F4mdAȂ*t; METp,x,=^w-wȂ! Z܃,YP1dAŧͷ! AC -i…@iȂ*# Dtq9 *T@ 4@ X0 x`/ B RA2I{3NMj^γf^K7fmƙM.ażmo"_;xM?Q^*Uk93:H۫cdn]©PĮY7OGo}zmjr1q7G Yz՟/7}aEL_TrM a7 27\P!d3_kcr9pK!KRTBLCy\>` k?lh+;NJD#N,͂XY)OH!+E)J.+E)FQJV'U[1BW`4H{,=S^?wnW2QBdߙ2)iQӠ" j/auՈY b}dOBM0j )\Špb:%'e -ّ,gfFf+gFv̒L_r%Yz=or* S1l[xF`nYw6߭~yqaŴ}%y8\MeR}>7M۫҂jpnNihxJ}b˥TRB+ ,V+{S>**, ă`V`<px@! -r{FNY1SOrt)s[$AbG9SuJ-Zlc9UD`^|H\ivUa73FPJP6kڪvYJuta&)3XEѢtKoցݶ[EtF߿J}a#o!LCS:him~3”0@@=0#0x, -lv. H  U].-^*Z銹=HťQE]Df1 ҝ)-K\MX.Dc$XQ|VVBVdQaiY3R(eO?T{'t4/SQxeg(eqO.21y`+nf+APal 2ͺKCÅCfetTQ-{-ֹ\?{A)B)cO(OO;”,ϙvs"^oO\=XN~_%)!?)7k -wk~'pJSY7íŸPh1u=d>0c_nJ~-͎3Kw?CZ߃S'NKpʹV)$2I6a~%` -h 8z`F` X\@"@AA*HUNKQM)#s#ѵ1tcgNi_BgX{qT/mn BfN ^X{Ƞ씠X8%,)) u?׊S iٕN씋1;UvE)3Q┯FqJ>S~ڨN*sώ,ש4 N L_8eӍ4ȸrN߽#̄ 8W|+攤up$-)!H9~w$$SG⊪e/.;oNe7zVP5G͚nmch*CSޅSfn6vo1.'J-O#wۆ #|{͎#tJk->9zhą)uS8I)xE%` -h 8z`F` X\@"@AA*H+|h(shZq\7&ab(%i:>=Kr^9U3zOC۴C;vӲSTSq[%;cN*FOֵJ0}ꮨ-Xrx{0XMYIO^5L :.&q0V)#IvS)ߪAr|su߿#пW_AxBmtĽ\[ߤ1ecnS1CLIC 1bvWu;6wlf7R!"B"""EDX)"""CD1DDDD)^*G qGx5%6..FKMu|k˫;"ClYu>##/O+J8Wx㼪.B]Դ<;_VǦ6TDW"†4³15G>-sk)ɗXsVVp2R2~nZiG?U7^xIwQoF/ɩ%4%Vm߆%*@@h-=0#8`6OgNS˔ %WlxgG?/JW+-.%.]IvwL '8gKLv9JRjc'<9'uxW>ǫVP@)Uotq*ЎeR\mȸd\$",'IYrnSskn76RnmRVRz&{ 9ɷJ7e(%q(%)(I)Dh X:`a X.xAHRdjU粚InfYV};kgD)5,Z&.Ƒ򃌫u3B.+g=%JIJy[Rʿו"Q RֿGKۨPJ5J OܶR>g$omTmRrJRmArSRD)GH6lRϔb߮Rjw6(ee|lvr2`C8oJ]!"Ns&c4ygOJ %:SRQJ{yUChǺRj{TI/oU,xv$B,w4@)wFˠݦ#{‡\kiymJiRR>Ro}<  <,vA)#/IJ%*@@h-=0#8`6%D(%)vgוR?`W;~^9ūP7wjfxzi-;B;0Ե. '9wrRj -W:QnMB)UA)+PJBWJ)nR栔^(e(q@R -.Q(*@hA&`+ D^~)V1R-rUFS],j+Y]e "(%yrY3.,ú&YIVXa|zM-%o[W"R~%)e{JՖ( DJRn3ϸt;D)RvJ)3R坫뾪R*eo.)iJY<*P OB)򩗺buvORԞ 5>1!IKch1^NJqI=SRsgF)+V -TjUe4>S<@vS7qZ<ӐwӔ2Pdܽ۔UawZp!?B)PJPB~W,'JɯRJ'N(J9Rp -@%P5@ t@ @803X 8\  ȥdM U*+U;e4+ =ΐ,VXc%)e~f*0Q=S8x&S -%)cg֕ߙR־J)Tp닍Oڬю%oTʯ$|_j|:%R)RZ(%#HJR?p\ \ \|J{])Fo(ΟB)=SR<JiΟR\gSgtl=),Xg(a:̺O)bJQJJyIRMR.RRJIJQ~KR=׿Ŷb]r$HNa\TrʀoS~)߯_ʠ18%Nl{?|)<2S|Vڽ*}k-:k6S gfOE;w$=wňq.'N9)epJF:iW:Q^Yy B .{#=N]bJNC˝&c3bJebJݦM枻kYYm82")ELiN/ zֿ<26RLw)CSp -@%P5@ t@ @803X 8\  ȥЊ,Zc嬪Ug]aLep -C:AX)m$t3Grʀ>i0%1SS N lSl)SRᔼ͜2tF^r -'9%\rb ){$|cKu -oEVQQANy{d)bR2g)]whʪK9E)_ 攮ׯ唀K͔r7H{/){RQR4-|Js>44#bJb|(߇~*BS!G8C*%-͕)hN%']!&:ELNIKu9TVR߰+rT2W\5<]ʳڢln}> &ʙq22vi6\viɽ;?R5~0^bJI>RԍZԗC)M>h}TFS֧O5Zj>h}TFS֧O5Zj>h}TFS֧O5Zj>h}TFS֧O5Zj>h}*eQ40qFǨfuibebџd y(paV8ZєnFRs}Q)%;I);(ho%WV JEWFbJfNOOmt$\'9ݺS4AK)ߣmt/)h}{ K? -ԥ:!Rډh})nQ?jBIφp&}B>)@\JJR] iX;${L $Ęs'}{b{(-M)~ǮXISUFKN!{gN~*?Tg44†S -w0봔=>cmx4FFh'SSgʔWSS:߄S&'ᔂ%Jj4`p`f`p@> yV1PErQJVpv*-eu5L3A 62ru1If]-;q3މ/>*)5Sr)RLɑZooIsoC#+ݱSܔH6e,i" NKNɒS.WH~3S\Ν S#kSrSvN9䔽7zȧh}QWO}D#xՆpSЭgOs&v72]t.{[NJMSbŴsROZv)w;?ʶ6^Upoc^3=ӹTqNSrW6Le-Law)Sw6-UG;rZVp.ޟn[yNh}jԢES֧O-ZZ>h}jԢES֧O-ZZ>h}jԢES֧O-ZZ>h}jԢES֧O-ZZ>h}>](3롔 -@]Q\򩗲~=DĔGlxM)1q!K&ź粻i@R;ΝbΝHn<"˾}RrO]XJ2^<$S͑:wre7;7K;ߟ+e8^c2SWVw>ҴWzZ’6,w<,;J]y/˅ƒ2nNHm8%ͭݱO9(A\P@ T@ 4,00L ,V`p $r)VE*Tӗ2+NDi(}Րsq< -~ɴYeʵEY+(Ǡ_rZ݇RoDy{_\K3wm(%\"E/ (|f~yKi_~+~^kb YnR#EcrQTW5AQFdS$hᗫ/Aֆ6H?uU?!͆pzV9)SSiZ-FI*NR]sv=~DzbE׺_FN_JKf.uՙC2cKF2ҳK[!~~y~imynwXד,2f.H,___/mӧ|ܪ5]Cyr~Y)_Kҁ /K~*@@h-=0#8`6-k_. ◝_ZrI ◶|Ut//-i*goulH|jU/)Q %n zmm${i)v15%`OwqZ)8zDxlC "-krW%*)pj^v=|vS7Z?34*@Kԝ۴|"\tW=ֺGmhr2h-J~aCX~-Qhrh_FKJj4`p`f`p@>  b- (U^b4 =O6u0,ƐGIKLZ|ƲzvPce _ʺ#3bif̦eoQʝR*$|#RNlIKt]q͗l!|>NRףR%QRʢY%1R|* RȦB-QOzK+JyY2rC>R+WSO%y,7&źIv1IR8{㶻c-,kCd,%CөdʌC2U盻ko%*DdBd٥-+OnܡXEdymqmpw⻈,+e_{-wOȸ%D?#,ף%j>iD%ZDY:BKTse&D# W(*@hA&`+ D^~ -NQ-2;JȩsHz(3.'J_e㌃$ 2Y2(k+gˎrr(ʉrsBamYn^,__=_[Y"4%_~_܉/OI:(D eorٿ%e߁{gY'r4\.Pᗗ=S/u1GW%: -ކpzK HIN$EqiY%ٞNMSѼ8/K[ue5:@:Uu-I2yr~Y~=4OҖ>+ӕ=C_4=Xl/~-QnOyDeiD_?Hf!-Q-ZDGe˅S2䞖Y&7%/Z"\P@ T@ 4,00L ,V`p $ HK4T9\8^JN"a3_"-w2Қ*98wm#LNmsvJº6uנ%i p+{uqDAK4P,̝~w+fO%*DU2_8YZ1%%*@@h-=0#8`6_} D''P;~ -snb816%kw9cRnQ=wBd͏6<Yo]ihgL" qq/bZI ĞǏ߹mHzdȼNQ9N- <#SuK҈2>""ޥͺKy| nc2V #ޓf{5хRGDR>B){ȧ^r1?'%ѯmxM)KH SdۅTW* 1|Y?^Sfgu2vjp1c2v;ǯdc~-aUo*k&s;e:eԐk|oAnxm1G|e?+ >]xG&67*<ǞTx9 <ޣ"hdP8<{ -.ZA -E)jR4ФhIё'@0RI1b&B -G)RxR\IHI%GRI!;US^q/^լ\IO]ɖk۽>05vzr/4z+^ۘQ뼮+ݣL8[~4,%*.͏dsa ]5'? -lgMySv)لI6% K&?HvlxP/YR펳G d ,ړRCŸSy1-H=`aɦ5kT:`1ۡh,sRH6cuNMM3<[\>͇jBN&ݦv$ݦsWs'mH6 ٬C=T_Ȋ>FEYCY+?zɦlVJ6Dh X:`a X.xAH]RÌU5 FЋ h]ٝ嬱$sl֖:fu5ݹɊgb%+%ۤdsu%)6)\D)-yPO\[6SDmQR>I) JHJ')D)-I6\pYU^e=F93~nw|w#w:d Ϭ'z$v$CdʲA$_;rdljl>Eagf|(3 b\%e Ed]rS[H6UH6cϼ&7(.6-mm -X.RGwJ 21>'$M[.=6):OlE+HHQ"EM-):RH1FJ8)&R̤XHHb#A -O7))")RH@J")grǓdSWƫG5^ku>}0olACK$,[㹆xkom)ޱ\xa+vz=^owӕwI?)A44䐒 DC-I6/WƼH6fפ{l~"iBҐ.~%f>xaP AC;c;ד=dLF,hԐ|s~h燐ފdK6q@p hhxw ,!גmObyr޲#o;ط>&ɵ3Tǹ2e92#M2MO!]Qۥ} Wz|a!֞a-n$J9tK[dLS2kK2[䎵NW♻geb_}zQ]!'!iL׫PP  @ 0 ؀ns7< |@" 2(F9e^?JiWQN;˴g KQ +Ǝ_*,c[mVGC?rOE +Qb[._hgnN!Ϧry6WK̉coiM7Jm!|yK˶̗~ɹ sdCD:'y}#~itN?(_zC>_߁_^ Sߎ9hsCoSSR+}i j+v\spLq{PϺ_?V1^:rLYu~ߥ;7/S/'visvO/m='v _ʗWSeK{KkY& ґ{F~~@.4//mɽY 2=o_p -@%P5@ t@ @803X 8\  ȥ,r9甅SrHcK8mӑ(2s$~9Y9srݜ5] Xy8oSԦ#3ߒ~K~ (ix6;F~MؗH>k^Bn<r3O/撂eo;F_s.(sԼHh|J裮+C2@ܳ!?Cov$^\2ͣdFL UӢs6CHzU4/䰆.uQ3KN2.mS9mr% eh~Y9k*G&~y~)</Ydgi5O.ɖŏ/WS[_p -@%P5@ t@ @803X 8\  X2HEGuF)9bzӌpt6rN4dsKilQ% #ݍdgdl3)Q%h /ABm_NxK&~Y~)N藛%II PI Pےex%G~È蛪uOF~_ ~MF~zCFr΁}ϫS ߸~y 5es1kwr9odџF-߀_ -‡.x12H${Bk K!(*L3g%zJj4`p`f`p@> KTsT{rSG9b$]DZˑHJ~6PKH~!;G"-Y"mc9\0'E5gvqNp_" Z-egȝ[3CG!vVcL|g^^o _~(v/ [_oGpQOdWV'_]OBT%PFqφp̄YIswc҄{J\S 1sg;"|,6%"Iu+GZxU,/<],fGh8uuj}; &cA)l'e]6M08-m{SfrXC7ykXahVc81$dUʼ|5ϓe݊ݟ[PP  @ 0 ؀@"/?H9*:U3V=hNt1u}1Τr%űkgeVla=Ìt53f$Ar$I)aAr,]ݧ YB-&~-1WHJyWRJXΓٕD),]*ex.(PJjK5Y&(^%fG)oգ, )E3)Nt\qvW\ryÈ˞\!9 -r4D͎cvENa8j)P7T m)V@) wz+PJy]k}M"(p+;ǺZnR:(e[zPxRHޭNޭ*\P@ T@ 4,00L ,V`p $r)KbfL;h2Y0Î06FWCgo#JY=(%u+u1B?#N2[ns`IRJQʸؐ+{Ƿ$첅?voYHt軟2 A)0D6-hαoX7[8彔6GnS1?$Gw࣮uJ8b 9z79::9%%M$n1mO$ؓ1+*3'bߖ^;nW=ऎw)ͼj:;B]kNkGy3;Bۼ<.SXctnɲsNKyM[Sfn[S_SSϐcv e3^qzyN|UE8e-)Dh X:`a X.xAH),yZldU,@ ;hK]7/g ĔERr+ 9fwq|ZdS0ˈŬn -IN!U Nw);_)-)ذ/:#8}_7:e+%|r㭒S> -)))젢J֝}#i}.SdjONN(ks -:b-w`wR@]Lr δhڊ}J9qHA))2XRC);K^ʋw%ٝdVH# X\$w .OMqi0RJkGlqY+fvRcH)JYvj&yDhugvrMSP7nSH)eNKVk֚t[(yt>E {őyǡ*oe J隇RFկ+PP  @ 0 ؀@"/?H\J)`nF9.F= ;s/B;,1J[eݴmqg ƕ˸gha'O%R&)~I)~m])QA2&)SRJTmR^뗤MvDoRRޓGJ)=ASiGJ)=AӼ΢`JΟ X?9 =Pu@)h|ZtvKٞ8ᣮO}PJo6<7%A ĸSSB3"&iv>Nb'|/KkV2SJJsɔ92P.uEqLB됌=~tv W+ڡ/]|Xf{yqaniN5}nY88!?%/lerGK_8vFl.ɗ Uӹ219Gro)/x|4]\P@ T@ 4Zz`a X.xAHR&9J$dQG"Όd"EY~1p3.5+dNy:RqֹHHGpHEh{,H0[:), -5ۣ%;o -:ToXa +얬Yzr56~^:hN/viD_${Ul†p"|_xA11h'%| iudMܙ:*MNv:e/K.uB]ޥ{~١|~|~{wA%d{o4wLOzSr)9_Ѫ;uvw=r2q`E);'lO~5qgnS -( -ЀZz`F p -lxn x$D@.#kVF#MHuHfrH}s^_2Ț β EZ#mӜs8גŝ)pbc7[9/K}$0Ar䗚/0tKrGCחŞD?&!{\/PqdKyuVD,sPF2~YlOc4/~Ivoxq;S3:&IIiigɮ kVF;"{$64p`W4f;!^9ʫP-{g#ykrYڰaZ[[ke6<a^;T pﱎe&AK4ZlZx^YUh>˟jy:DKW`.BK4tDh X:`a X.xAHts <Ψ3ÌGv泺B`d1֓<22ZF01+u#6e;lvNj/MqAuI)ߗ& J(Y$b$ҳwC%\D).i Jd.J9޳fRE(>7r5+?FdR|$#޸!zkh i)vSs߹8RpCL1B(h)O&߿EpLi~UF/<-YE1aئϼC[6ٺ\NfM}(v/mV8V3/t)\# -G%ۅNĦ"gvE-lS*NPf UP(IQ&EC -M -K)zR I #%)fR,pXI 'E)^R|II %2x^jի̈W^jrtf<뗯4{䜗xh3R2֬+m3l/uuz+Bוbotҏ&-%vICf)|=ohhWuAC ^OB&#i!n=[l54٭x^4sz  ͮŵyiψ>o ~~ee+7<8:٤3iNGyqEg)b\\ZtGU ГM=튚+2Wu<[5t|av +U rvVC'e 6D'7kc78MceGe|ؿd M -MJj4`p`f`p@> ո dȨM Cw1l-cuI&k9c[e _ʸ # }O@+e6UJlC'RRʝ!P%򐃽d˝I'҅͏%J7Ю;qJ6:%=.lop%6!ڔ#Srܳ;겒w=SDmF lx+Sdz$'ɱhW1&69YCM6J< SEFXB;}zMi]m26#Wڢ6mvAfycM݃hhC2sE5Mۣ6}%6E6O)|ɻRzmfB}Ѧa>6C= -6Jц\%)*RԤhHIaIђ#EO#)ab"L+)6RHq""!K?) $B.6ǫ(T5xKWj"/;yu^}05h3AͤגV^ikW;+{>;/ͅ!72^o/ hhGץ{8ySM}okM%A/&e+Gy˸g~ _2+?{ȧ6Yhl4jDpzvqn>A uº챱ih!&56T5W><>(\ZE{WɔT+}4ٽdt*9ar6g W[kءoήfmV_=|Qfjy=󏊫>׽{g&C(Lg 03,I.mOu[~XVZ^D$ HK`AJ$D"""A>/=paҞKW<=5z-e DVNsA+)z꭪N=i9 ?XVS;g>@[iөzÒV:9$癵͜P[8sz7wCFR$!l# '| -nS[_bߗF.m 8XrKM"/?Y-j:/Ĝ_Z_~y7?iapKz7Bq{b+;x#!9=q{ ʆ;ۘ?ԮƜӈ9!~1xĜ!Tsѵ_ge ۍ3}9m沗3# ɣ?̗EWfb -ssfgeOe/ʝ]ٍ9g"w_gU}M-/ŖBĜĜ3+Kz$,DI"QhHt$zDb&$"&!p$6;E!I/D$ IIHV1 ~E_Yw#^kC06Me[SAL>p\J?_ -ެ_<X^|)qK=gv eg> f$ĥ!l'!.laC?ِß'9'aCN9YbՆ ~W`-ß~iѧ"U`oj 7Dvsܚpm(|MKIv%wb=:)q9|#..ݛv'󗟭) /1_]僰PJ+ŁgY2O`COG3{6pn/i>9U8,*;j+92lhlVT[ o);.wF_{>G5¯#΢*GUu3t!NKpɎTdv%xS`ԼsfbNKQeNO"]|3Se 1' 1g|?bNXxf39 "tabO!LO!L"Tۆ2b"bN}Y9_^!4"L}uٿ%993Ͼ.Ojb=%Q(IT$j DOb 1H$$Q$$1$Fb'qH<$<@%$ I"I =l_gce_P5E~jM >Ӟur?샸0zߛ!fL` v-XƥU -*z`F`f @4'pDA=ʄVޭe&MҪ{v^oV.Gj͋_KK3Zʐa ߢڴoQ#׮?-oKj}|[PsؐB:$͑噒):nJvuL-؈;'>6 -Y#CF <~,Ug:3nMvװ6O=:WS :&_)KcSn%_OG򔛥jSOD8OId;$MF~6}Kr>=EN2d\W)~r_yY]}*c])'o2k^2<^DG\ - -gxS^>-ő7 S__z,퐏uU xJž-|u -[E?6,SN$ʑSfxԎ[bN?嶜FNidbrJ唚} xJQ/%<Li+O; -O<(,P%P500HA ؀8 x> DQ1-;jOZҪn\jF7h-Zn^k۴%kZZF-Ƶb_V=S!¿rݾ I"/ίy Wo~OOMOHwd&ޛHNKOvҒq -`AY/VWxog=ֺQ1ꏷgӟ31I}~ic9٘E;vg_tD܇ҋ1S;-M1\b9'NQ)pQz[uj8g)\2I,'2lY͹qlo w<ͱ8n_nTm/o.Y{#}Iڥ%ɕam)_m>Or_߯mt^`-( yP,)oSA6/#YFv)) iXG,IKvxSw\;#ΛI|7v ~ -oGٌ\Î(:he9g&_;ʨf^b43#!~n:&2ã'QCOʢsZ]𶌛 4+/?.w@\KrOёrͲ)>@7,{=&ȃC/ -K -/=%Q(IT$j DOb 1H$$Q$$1$Fb'qH<$<@%$ I"IÊR?[Pe~6u_׵ qH4uͭ]MiZEKmgD{-m|6W.n by!Ŝ!dCR¯> \}f8ĘCƤS{}!+QҔ￸ 5;o|~ՆOj޹M`}9Su>GY -VРnKjǜ8!#x]Nu$g=ٕp#erc5T/*:-@̩~ 1}1gGs0?ye>{1gvu%qb941g錘wvZ{s - "洶#G)}cIDY|1}d7t -1ĜVĜ7s~-<@@@t@ L "A18`v.< AXAUdU ZVբEiJ8ue~gsK"69Kl[KVյd [ccX}-V[w.b*Hh^53;WCOl_/KIeT_^5woH~HȋPF< 2P  ~&9-ZBb//썯e~Kz1s!!-5ّL1'>!1J$dxxo2iG̙9yB9$@$I$I"YY";Wүn5.;_ ~61g5-~5i4|_h{Ӣ%]b S\z<&mz|Clȭ -vU<͢]{^Ɇ{^ 9hc'sN^~F%mٌc5SН0>:Id7?\%\=oK4ב2ݩ8հ6fUF5}64}(lT[2gaC* U|iɌ,z V1S }6? M V † 5Gٿ /f߇ 5Æ:} CIXDE&ѐH$#LIEMCb!Hl$v'C“$^H $$Lb:fD@@S ~Ch, %J:Os(`ip~]~ON ~oYWֵ}\޿\†ѿ<Ա7?qZw\4O>6]În_;i`#l+߻nipt{lHAXyPG†~ʪAY^ {KOwyS|u$Rg-OZ⎋ NGAYYͣKK*۳=1}_ٮ*Q>j@)T[o6?UŘwwD+n`{csvY2ȾvsT[/Ȝ 2WG_ lΉH{S(h A$ Xl<AA"H+}խe+)8eUĩ筚Vnª/ Vc-g갚ڪjE+-*WpUyr9~8ok2"B˷BC6_~pVF53I-=,}kj0ׇ0;B/ܹ!;/8m50՜sW`0's=r0yOs &*mK9 -wjp'Fx2zɩ >6.>2)GΩocKVs*#TM(r^<(&3Uma4UGsr.f+ 9y9E2S99Es>ab!z9['s<&wNg!~9gizPO-E|99gy?rbr"jQCIXDE&ѐH$#LIEMCb!Hl$v'C“$^H $$˳ʯ,F(Q7*DCh,fZCM(第*^wѓWQnՋbKʒCKׄ< vV[φ$SW(}ưl1CRnk†fY963lh#YOaC6d y WE8|lJlB|Kp<TZ Ju%!* L>ڲ-iUyVŝWsOayoW>Ϩ뛐sgt]'C[fcZU~c(519HI]6$У@iS;Y웫ף}rS3Oi37k^;w |B\#%9"nGJlr -ݖKHqS/F_<(˯=Ӻz/|)7;Ksҥڢ*|ʭ>}ѥi:TnW Y76w>9PյgGt-15{ܖcҸv*v;:3Ζk)=F!ޚFWjF> jeG~$֝ -*z`F`f @4'pDAJ~-ZŠFYUukZ͔F7wh cT5~ tޯBkYpZ[~LlӺ:cZ~L+z{R KQJrt^i#R̪+mBX;׆m,^uO%i~C&})SAPJ2e -7 9C|7/?,Nvk)7F6ɥZ|>Y?fXFO <_]xY] )inCXz q|ÛuzR9tP6v8C^.KG]qxl[֥nsJ) ϸ s*4m>;:2=Զ%mɮN}6N{g<:ȺSԠ Nd#y ?W_AxBmtڢ̽\ǝٌv3]uG7˺.찙ݬt쵽Zֳ=$B""1A ""ADD1"F#2rfٜzY|uf~F9I K.3_Í_\/efVF?wxmf ]צ__o$vͶknߔynvMg/Y?WM/^/~^O{Rv W~J3})^+#q |$%yS]bJr:-?]u?|G^_cM)''' -^t ״Oʞ\Ce81s]IBR pVD[BRv(b~ZGN6gIARwx/=B%YlDrkE(y , --uVrٸK@@`&`$p7@A0 e,3ȪX duU -YCk,L# 6 =zko`czg:XN\76moؠ"# շN_}FO}VO:q3*fҿ/??(?)?+?Y_!?%ɟT*Pmp6UxUKo)6 j_AVm6TҘђ~gwuf}곛}Cwvg7oYf>=+/fW~ϠQPnvWn֠ܬAٱnv伕=n6Bnޤy}Nơ܌O?晿 -5y- Ƈ";sSql2C!|s땫ύS|1LSEWfFJKNOw'fS33D)+ۗ*8%W}Srѧj<+zN=A[UM \m ԻZS̋VKָ'o8!m'bKnsP5?5VKy?xYzx T);BTZKeÊSp4`hp@TsNSi9q33vq"ΜY:88SY~uYȹ8~N_t -86kWd)Ny\q+S)W<0E8E_k(彯8߷Z)WrL+JDQ(eVuQJү_=S)b(PR ˪1nO>R^01󡔊V=sOJLDx`3(S:]PJʔ+ַP>@ʔQ?2 )dž2Hh L , +H6`n/$2Ha@.eXS`5sՕr+ 3usFC2Eʔ%VgYG`?z8a[9WpSSkUjE)(JSWQ -E)s뢔%93 t)}t==.KNVkSNƑEn8l.k,Ƶ=Vnߗ!b/:ǩmαI8eU8%N)ޅ273o[Q< ,)cp878Hh|0#038 ؀8@" B 銉8oh8w3 qiT̙k8K/qJ)S9[1g?99gox8![8B+NIWf)LX0Q__tJ*ܫn-Q:Ji8& 3|(G(MvmGJ2FxPg9|c -JӅ;c-UѦU9evN{iґ0[Q.{^*#wrG`Oӡޖ~:-:<& I0$$4$$t$$8F&fq$IXI$pppIxI$D ? DDD0 rmfeJ֌~Y7-[\ljܢ8>f_`)qQRt)N┏6\eK>aҿ给I4Rux}<7_`8c՚Vk.8έ֎[Sy济;9[v8N#sE7>8e -N9<6)Gi5-= āx` x@Ai ȥ,t Ls꣬du$ s5՟"N)%0mvJ>:[ %ISۿ蔍S|)EqWS΋┯S$/:_pʮFyXivS&))Dqw=b|`vIF8B'io ZzlX4%;ošv:8tpNyO!M`HIhHhIHIp$ $$L$$,$HēH$a#a' $&H$$~2 4ab{ez4Te)YݬopAd0dB ->Qp'| qRA մ(WhQ棾EC?] =ߟEC59qXв h(.6[k4T!VFm~ #2I q?3h̖dӫҘ8Gݪp=15$fg]əh_$ÕO,oF틲lj\?ب ?KѳǠnhYh84tuTC=BCSP\vñ|hӴGhhޱqwACACGnhc j^K -heysQ\8 @CM#P+"M`HIhHhIHIp$ $$L$$,$HēH$a#a' $&H$$~2 4ab|eM@[uAH6i"h.XDC*NsAnj|W[n;Ғ ˁ5/R!s  q(25- ݴ^) /zor3.ٔoץ$s쟡ZJ5:_ ]p|*3}#3BLW1XΝ>|d+9YLD1]HEcdgjh@cTI+*(AChh&mPQPyM64e=N#Pv◡Tb h(gߐS4r4~_임砡'h4 4$41XF'w>BѡgQECi 5 -  = DxV$l$$$$$x^ DOB&$"F"L\"3Uum@tcҔ5Ȇ4`Z -KR2uhhN  _v܅[Nހ4&srT(ECGx]4t+,Zʽ~hQ4w>R4(uKVC7 卭TCGFP ̷~q-?TfܝmA&  U9\JI2I]R$dJ|JI?j<|H5+YT/ιrS5ȫyM |n9^s ˻<ƒEi#lϷZv[=om['$6zl#uTi텃o 7]Ky>hҢņdix6亩T --GYo%ҀjZz00  - ؁8   @K93ΩK8M%cumnθĚNrR (eaqcsp!]wZXXIVT?+_3C0RTR Q¬xs%RRSa?En~@WZQd53VPnxh=[N_?d.Nf.~ҕfkJ)DoRnOeyJ *wq=9S^ÇRd ג.,ї%fRS颏aZyM= [=~I0浦}yVH5Jj=ޛXXWG޻QTL2V RYlSB)eC)@)UG(R,3W/5Cz)V7׷\=kYS;knc-ed_QJV+XG޹w wB/b/+}\VVk6(E)E)(JuQJDo(\)ׅ- )kVn{Sf8LRDq Q/딈~LcNƱs8 -UIx{d|*3˟XC9β8+%;3=d&JW'-2%oi:tK9vRc+Rӻ(8FtKǎP⛴K֎VJTAqm7nUiqWǕ~0H5[/GDƎ_TW9fS9G[*gȠG'haNtKw[꛿Ju֧CCtZc-I0$$4$$t$$8F&fq$IXI$pppIxI$D ? DDD0 rAz84'-ڙdPq:` ~2hDƎ낶'X:ǂ? -Π\1*}=ԊDC_!tKo }-u&*z>2 4[n,.nuOY AC͘'1TɧPU(m"l]Ν|wqL_GtK|Fĕ"b/I>CCSPcƢҦiE0xu٣f:A;tG2/yj`X@Vk*޷żPbtn||Vkۉw{lEF%9Z\M2JgQL{&Y,y' U|׿f(mQڔ(q4`hp@553g > $$);ٕ-y,QNY*~:ԲR Rt(mw|G];>JOfX8Bqy /;45FivOH=Ҧ0JlzMIΐʾJQT(mL E9(mFEi_WF[(mNѡgǔ҆\4M!&!%#'0000#OJ"OKB !HI$$B$HI>,GL -hJdmG@ Gen>`hkٜt <-cVv_/{en8 Kp@Ej~K*x[6FgZ|g;fM|KXo^ܨR,DJm`um &>t2p3;WZӻz?Ucwɧ2~prya }#1Ը!;^yy1qfZS])gyC}O>RLw&nm5% Sm׷ܩ] Oxc7j{*?Zom[:ZkLH\8 9b:G6ggc j&'|ڦ{ i 6JRrm?jCQz*Ri5-= āx` x@Ai N_fU*N[85Sq5d ;=lڦY{ 5,z8a'䄚VnⰍZq'(N%eş>SXf#ڵ=_)q  Y>#&gxpJjcT+Mq6mr[)rA)ۮm{MΩ(dqMgPL=4C&>_yL.&PАВБГHHIHIXHđ'a%HFNAIM'%!IH$$dA!i$m ҅.n -jZ"@Fz7`:*,3dNWCAǔ쬕C'MA (|I :f5hHJY j$NbECQ(*V4)ɺ $\P5АoWOo!jO>w>4eє}sC8wpNdH˛L&W:<oJp>CTCrGBOMoczwS{kXH&FwSxrCO_!e,hTݔѭ'm|~z]onert/PΉ>T~?Qy?سQڱO%ۅBy䇪`;n uۘV4U1ǐq4`hp@_B~ɹzVYRn+~G2I^6,2mUWǧտ߸62:!Át[,)"Dz,!K,yb#YgUpjs.WwxG7u׏w\CyNk4he u=xuÄRmp^9scf3 .xaQYKy{f(ad⩽AdaQsɱ8MuTh>*_YK@@`&`$p7@A_i,S4sӝ93sz4ʙ;K\E}utq!񕜷8&9/'Wsk$_|vlu.qRQ"@򋿿,'m:ց%Kr%ˍR 1KõQK.'㸞v3I+7RDoRҮOeƹ15RrlszL)|YJ QB""(%3KRHF,Y;#+J>颋xN^w?-K?뚻y}[3ϕܞ`XG՚?yVr5A*jVk= {<#kݷos?,3DO$GffY T.E)D0@ 4@ t@8`F`f`q XA";p'pxD ?A@w -fNV=j9s\khd#3ϰ\rmAϺd+pb 'qNn|#-J2([Qң(eFQʷ('RR`bƫXnn:ăʽ^sՊR.(UtV))&jTd.5G_ÊRTd]c"HIOQ|"r?JylC8wJ9 YR=23\TAr Lѕ}OW8C)GrGOJ3rW鼕sr|1zN>}ywRRfi1_[E)i1_uQʕ߿yxڍO6ϼ:ʽN]k||E)S(ΨJnɸ~Ձ*Ͳ?8B)?Oeuio Nb.0Ƙ'V=sxcvl_/˕*L*d|/HgN34RutR|-vPt3Fǟ:Bi槶k'JP]W30ʘ;4OOrq{T־;ı)fj~}cǜ9n;kio Z87G(2ڿJ=ti:Tt?6L3I0$$4$$t$$8F&fq$IXI$pppIxI$D ? DDD0 rez:Յ@@{";-Y80M湀.H^tyAwM xsqn4mמfR) D!*+iKw,LkV6/OX(R|5dEC(WhH^-^64SqnrJe< (2bj.*㡡q ؀h!3 yRcoNf#W%"+՛J^D2SSgy萪fwe+ߧncvSJӾ -BJ7?.SfOR^d|?eǶ*^(GPwT?{I]zpp|Jx7Q?|_%7,/l U1]+㻸^0@ 4@ t@8`F`f`q XA";p'pxD ?A@~IsI^3I;h-v!0a7vKu/v[NLrSv6;IyIr:Iwykʿsc)7 ]z/m~1s\[XQcRTc'ytӗ /D߽>e{qfկ9i]d}@bίO -2!o_4?!;$K1">#%UJ&lj -)LGS{|Yg{>XH5Oea1#O/u˩W.ז=txrC3K{EgZjzػ1~}|:+sj~~CQԹ@9 -UE?]ʛh0PoEIyͨn@%Ͻ!C%yӪUp4`hp@zE+~9`^yesZ/oYu_/NyT"~̸33F_x}C8mT%##ٓ.g^@\RJL_W$S_ꏬQMdFUjI[56mQ7 -^F햓ol+|F5ڨ~Q=shB46Ghb#h>@5qeZk(N?-ƅ)wy1`${z]A-c(H -y[=Avx4(5'rmԧWy@_CECنv3朲mk\(mT71ܢ9w|-FWy'8ڨ+jk"m=Pٿɧ2sɛO{!&˯CC_zn,ME+exty@l'7vR5Uֱ2@\;梗o0u# ڎK>aN0 gwZ-ָl5'$xl {̵ۜ9GR}wvZY/ H͔edΩ>|}4`hp@Nވjh544U\iȓzWRDJLMqeD|.!OR+5%;3ӗeiOTmNe9 }xW ڣ<ηQֹwײ|mcaV,ce5ǁ?zE)QUQRRܵrgZN?ֿ˨N٪6\WnL8BN+΋]K+.58%!8eVdlC'%1 DSΪp.m2}/K"3]MvIY.H3ۥ^K }8eS}lYչݼf*AtG7 -y'=ƁOcnm4Sn4%$r*#귯uksq8n8ed%V<:%K)L+pỵpʩ^)D0@ 4@ t@8`F`f`q XA";p'pxD ?A@5GbVNϑwaq~N?r͜a5ZμY*lX{(䜕9oviN˭7)[u)NI┿Uҥ8%5Sv]RDoZJy練>ruԷt*(dE)_[+Ψ[Z2y2cR[="82.OeߣL6ĄElW=s]ge.(eM^f=Y$g@)uaE)D0@ 4@ t@8`F`f`q XA";p'pxD ?A@r)y2=%X8ĒWvױZ2SFlլu,v(pB1+1L*_+bnFQ_o:m]7 Y+[)ՆE՟`+Ny-ەW|3Sn/:)/mNy~o!G9eG-d(ZćxI*"5\d5!&Nc. V=s?yBM\RF'=ӕdE2D͂R\oWYH5o䏺]e^$Y8-yO\= BeJw̃9VK' qdsXcڍ2eZGm})sR^8d,)spL5IpNihPK@@`&`$p7@A08=gőFX2eՕsjk 3 㜥)Lfm}u4N]񅬷8q&2ٕSQOqʕJ;e4e+WV]픛eZrg50(u -eҽ{g~ -YyI%d+A͝"?,1'Qb z)kDOJ"2#)uz3xOTo*+ۘV;\/ :0#038 ؀8@" B v 4vm]׵Iim2 m2VMMvsRF֓5vmv[^lw4lrݽ$&azX$n"kk>|`E0*E0vE0_"պ櫥c-\cRXl>h2\WjPW5v_G_cw=ڧ,nq//}5V,_|*3?<}$9tUEڷ,h7t ^{]E"̭JMK;Yn*~~9v~\;3,+Z0ŭᗪZp~Y(ylK#KܣK*oܞB`J.*ٻUrM/r$r|yƴ~/^0@ 4@ t@8`F`f`q XA";p'pxD ?A@ |=4՝vMuv1I7sC3`CKYÛG4$ٚIꋝ{$oC(H$k\a/R=HS̷n^=Howe ϝ׫eI_Ώm_.G`ۺepw3`6//RRC䨻'Pg Q|=fļ! 1_t1+=#JFՒ iQHr'38r{Wz] nD&hkox}m5V%&xsZs1`O>=kBBboϴ7]8:ξ{z"DǞXto@==+BoQioկDD0@ 4@ t@8`F`f`q XA";p'pxD ?A@gXMt~zXC%gaMYsg)$=QzN&>:F9nR[ J>fgV2ڶۣV2ڶeedk0󿌺xJEǜ 2(J8la]f~¾t^a!87xR$ JIKdp Lc.4W=sX>!fg,WFFzHYBV2/Yd-aeWѓsۘ -(u(YN^}%LZwvQ\K>M4K m<5n%2Jѷ+fj?AJ,3h%jEKtDϠ%ʭEI(YNV%j@R/-ѽhƖKh L , +H6`n/$2Ha@. 3*Ȯ=kOoklsL mvK9iIKhMvGlw$voc0m7Iv]9Z̒e◯,YvKrYZ553s%E)YJ|eG_J|%Ѻ,O1QUP)SC5ˉ%AOQhFC̖dcqC8g%"ejrp%S\Jf/J%Y?283dcLÐM$aȦ1Y{mhWw]Z)),"")"""EX)"bVDDDbEe!9awL]E Aw/,Vo`vê*bIܙ^ҐVFChH}W s5걣\.̤G`3hmwՐ8٘s^ }d5@qjGmPAC7Rז`Gx1'!o8A -LgR&I(D!)!͓ǧ4Sw(ޢ=1g|[O(Tsʦ6jFE\Y<3NJw'9yOMin1 $Þ$~Duޑ~9&e-T<KK72+OA/n_r^o_N^O1/YUZ?b;/Kj_*_zQ#u(^_^__<=(P5- t@ L X px^ @$XSVR]|NivrXcbu]X%$Q( eu懚L /8ᢜf7ii(0wS?u-Y*}[J>6Lu[.`9gz3iNwz8'84'BMFZZB;-C0%#ne4ܤB "4-Uh;?D)>SzCo 0ON1#DO{Rajʹ6{ -h13={N((\gQTBgx:oQB+9 -q|gsQ낕ܷ.9W (jZ@D 8 @"? H!MSVR㱪RN]i8m=Ge'9l~1֐8S+L 0qc]_yZ8'4qbY2֟~ !܀S=Z/iᴤp8q` Jn a.`g+Nr*+pSxd~fl[^]nZc}1gTb [xKS, M1֬bPG{[Ӄ*\%[D/dff 3) -HL$7׆ |8>,4j:x(*ĬcNen=^5;i6v/эsg4k0RrK[6/n6nqtA)PJs32Jxr_lRZ{ڜjE`pJ"^>WT -hЀ:`&`8`</| A(^ -e4匶!Ս334c*$J9K/3"[Ռq3`0ʈYW~*mijI)$\#)% ӚVvNjߏei7'C[+qIJ iK2)e_H`^[M9pORNyʞ]k!࿗d6TA~#O+uMvi2Y.bJIO#ddx2tw3U&9=.RiXQk#sg 8&9Yܪ 7%%?IdyOj [I5DZY$RK)e"Y~)ELd1Iu|t~~)]&M2[nGtw2#OQ*tAr6Ewp8]t{4# %NwL8˻})GdoJnxV}Mynmٛ<]6ѵ}h Ykr-ly=D)/bݶ6rqvviD,e!t,+n>?@d)eSr}k,"!EP@@,=0#03`6;pxD~A B` -Y ƨu f ]°.O҆|TA$L263`#!fO ]Z}ۑ2I)}RdQJJY/%(%QkY&[/Y]M)}w+DI)$ɽJOr$˯'PW΂Rs}䩟rz"_-H3g+>E,hgdâ33ɝ&g/&8ÙǛ&&EyJ92位^,C6)ˋR /3G -‰RDn'z D٨Cd8pX|&Au- -Kx5xQ7_?R18r./ۿ噼u >DGZgg ;wZ uSMpYNiޓqV ;g!eD8Lwg9*\ _y:8o]|eqz?./6\)˕kY@?,2UGfj.42eOw: - Mw}yUdH鿧UD2B0V||Y0xgBb']n'7=-Οf){Ps/.gsNeCn^WUh -skOtMfr9vuƒB~]dKo[a9̍ўպ 2K2dPxY>;V*YId1dY)*4` A4+؁<  dU -Ye C 3lV}T6neC13n2͒GfnY2s۷#ueovGwMGYbD)N131=#-5)^'ϟYɆRj38EnhA(fvCόmYLEl IN7& ԰t'/ΥN9XbU"jtѩuS9CjO{}f|[;6ǻxv1eO!bʑ?3|ci1= ~e`Zm -Fmq,)O)ų)7˦GF5[7|N()CVr:]%[|\P^`KOn>77L۳9x 'I:A9ҜI o'5*ࡨļimrn$~WhJJ<= ->;Oi =;1rXm2q raۖ_Knq 2J(<_ Wmz>?EdlAT1"t"K,JP5- t@ L X px^ @$X*he=C?kbeKUPC3l+g4YaLe$̑mS0~1CȲH{Jo'# b?dMUлҞ2JYF)ygRgEVsYM~wePOڰqINyGN,kiecoZeP2ˡadrY8AT)~1]>2+>E,"Ϭ=^Ax!)NIKrNOFFBFIJ?)5(z^L9T?zU=/궂M.MnFSgj -ZL-殃͊ƒ)jumsm%OUpͪVEѺ'ޞg ~܇Y8e +]WT -hЀ:`&`8`</| A,"`,uQ50AFSjgeKX]/Zj]˜SN2h\5kof=k`4Q8ѫ3$9%(MSqcMb7GާzrtH#o=&Z&FR1 ebo֤ Ze@4~,ǔ G,A)O(;,R/ߡUpכNH :Ӓ2Er|3)3w wZbZ|BJ㇢vJwM9nlhqk{Swl1=n}9R:cV4bڗb9tm~5冇8ɚrm7b_IĔ}ۅ -(e)WC)c'RRZP@@,=0#03`6;pxD~A By +zW0F]hf Cw0,f!5aLD)dM(c+diƞ8r&kgO ^Zf|Yv3sI3+I2J,)ŵ.ҵCdS_rjUf/+_}[\풉) -)\kOe߱8ojĔS/S91_e>*n]8 P׼ -ROH_XW_AxBmtĽ\[ߓϲsβc pIvC{u@@鬽W-RJ)""""E)"V"2Ċ!"v VDZtȵG|k眻'.C'Zcdi!bnk ;9[2q8"emm -ٹ d gSκ)/(wSRtSw VQTSǨTRu(\" : A @6`N 7 | $r)gF8;x]#γ|&/ 7MrZ.l]xoY9Y8aAY΋ÜqR/Wq9Η%KZNE!,Gtٙ?1,E,wS {iI|U++3(gd3I;0Oʟ,} o^@&gUUMήZTUA:INVTuպDOԙ{'{o5{n^=.}e'~WWg`{--ݫ^WroK u$JG-Fz\\'Yd؇fkkVel%j}xfLOd.m+_?<(-zܫ*Et1bCq;<ّ,';1)1sRAUq3[.*t1oD½.]ψ>#-D >6ef[,aCPJ͖Ȫ<(?V[ao| J)xJ,RvRR͝~yj}J~?2 ~MQ -. P `  V 'xd>3JCӶqlAN8c.gD)e2Y9rf9ʉ'Y BNcR)JF=(EB)T]qMkb J .SJ[ە2]n2ܮ(JXE)?*eޒRb,}oR\)oJI02(xmC'P ܷ$&nRNO}~wUs_̆.{K).Y9UJO9ܱRS1)99%O| 8*kj|NAu5p.}6pJmEEWP֎UgTfڹL'%L勔<^ڻ# tсn:УIj_Dr4) )jR4hIё’“'@)fRH 'BJ$)VRRlIq"&CDL)~R$D -=e+4>mOWgH0N]a'z|P:j}l -gO}YBJ$x_ Qdsi^BCk !U5cdsJz,)EC.]ЄChjMWՐ/H6"BC7W uCC?)3^)M_;*D|س!d_UCn)-&.6Սl4It{\-=AOZ>GCǫ?$d)xR)/vERIy0|Zk<=SI\V6dSVْ9P6TS[l%fOd3*MUMMۥE\zfM $n$$a%i5-@ L @8H`;pH@^~ xzc&9]cKx~ӟ Y7x($vޚ gʻ$^n}ŊRbQ%\B)E))J8REI0Yz!QòpޒM|JIRRǑ*GrJ\CLRD$$l=jk{%Խ3t1U'DD.vߍf"ȟ|0B B)&Z}PJާPTf|w"pYǟ -e6d'>R*K3Z8vn|R甆CǡwiE)D0@ 4@ t<000, Xl@n@"HR9c8encY~sJ8řZ9s; !Z żwr\Nܙg9y|܊)c%D+)k!rAQB(5Q% ^JܼRJMbO-O)TE)i/)/+Jyܔ[MPJOD)RƋCPuP n13~y"\wSJCAQv$O#>txRxHKn1=6.=M:G)R5-)e3}:Ŵ)DMڮ{\q.CGZsI"MǠk,OGDd;rB[O~}(J9=^&/l}rC;RJn(eGR#P~(e\" : A @6`N 7 | $J91ӬzLrlNWʱӏ.Xș8s7')6r"k쳬s9ISI՜\y8^QʥϬ[I)B\(凊RJ|M26ERɩI%SʟlUu[KJ Ǖ=Vr<"sRD;c.R^XOPIe?DsyK>EJ$\(~J)^㐮kUħ%;c9VvB)TO0͓NO?G)dHg27k\̎NQ]ˏж~&ʟٖE~8B߸ vs?Ԛe1i +Rg&"}ZWF'fȰO@)MG{RށR&.5J߀RJ*a(d# |H80@ 4@ t<000, Xl@n@"Hg.'YSgqqV[(OcaglcMI)D) 29[=kRXquVfn[z)?%B)E))Rn -˚(Z!WR I+W2r:R2#Jsa'sZ f+˔F2mg.Wd.#)EfLyK>1fB)Q*1_ײ!?VKIIIbR1d)ݕ(t[ߓ@)e'c)G4>Ke ri((e:OP8i|gi5-@ L @8H`;pH@^~ M),ϩ;8Mt<;sOIR8QJo\޶+8'r9K̲_PR[I)JDQ[%rP%kL2 B)WRIRSuf~(e{g~w-R_tR(儝cg#&C#t$!\w1RЂ;ϯ/R~_)%5&ótR)qnGLz˝9G)PJwc\疔r5(Ŵ| '_5ltpQmpC-{J(5V4<2WN -}meاR߆Rzj<(%c4Y @tB(ߠ(J%ҀjZ,p` -`v"p  A8;\1:̱iV˱<[81x3-r擊R8k/'9Kyss9^i9m>FlgLyt>Yd|vVsB+ocgqyÓ/:E8eP5*C8v)Fzܾ&NK~q073ܺҔE؁Bv>yÒR. SN()2>{i|b0e_U)GC"״)SK @)JUWt>GyV 1% |JD =󦔸yUKq9ȼ#)ΕOIIK=G) UU=uq -KJzADuQBS"sӢatZk{]Tݛ-}""g\ֹPr-GQ -. P `  V 'xd>nbNk8 >;s2t>"g̲ENlC=wbR*c˕& Sn5jC۱RLY$NEqʭN)C!bʏQbPڭ!b.Wu,2D")W <֤)OIe؏+| ->'cNy2!?;%ƤrLTCSc\TwN9)!ҀjZ,p` -`v"p  A8/.ffuc"Z-mlC4?o >4&ˢA D[a^uE۳g VD= 4)ȵ޺hlTV~s_~t/7$\~{bLIU>k~2ҥ%<_YʾU/C)o~ Z7ǽshh:"Ϳ1NE?_̕Qzݼ!5~ JH&8 ۸ӐVeG|(z⥴s68yzҚgwPtGS 47QꢂJ6m[G(]^QVm'Pg(бmz?g2:CENRHI$%r~z$c څ|{Lol3w-k MIEhNhEh/Pghg<4x;|:CɭCC/O<"W4D.&!EM-):RXRxRH1b"LJ)XH$J@;)NRDRܤxHHI#OJDRH!KLMz֯it|l u>ߔ`%O|BvgM ,Ԝ &x|# +jQi&*MԪa&k3/X}9璒؆)uBP4Thj@ bzIC{K4z"N" =u| - sx -i}̆e]pu!c$'&HDLKIO ƇZU}P5d\[ӳK8}^35DhD]k"[gX4djw}4bͳ5g9 %o.Q3U_D|bզy86Yaw)"e?b̰J)}J{L]R&M%mPT5p(JyJ)~SQ -. P `  V 'xd>=1V洵nN_q TʛOYdΚ q:Nݣgz9y%G+\6*JQPSR6*JQPSk_~j)궕Rʻ$[)iiJњ|"18oK)'Pg,Jo2(E 0GKS]%%zrzlCNE1,q1V~j|Rt]xRͻh,ݦmFT(<,7, ؍džg(S3̙FO ,mhD4H6TigM)C]љi+%)B=tM5utсS/щU-5KiRRԤhHђ#%'EO#)&R̤NHRH$E$M/)>RHI$%r>z$)g}mO7c+>C}4v9 &0L$X|B}?>s>3&|rϻk(ҷ ]BCzECR4d !4K6R%;XIC$C j^I6VK6ׯIT`h ]`g[롡% ]= 5fKL\hS^)9ajA -lJ=󧡘奥I`;MOKvx$O -ɩɱ[;5 >Sި$;Hi}OT(jF<#/|Sև]ZcǜԸb>v%q$͖*K\5=6_lG=dSB.'3H6'B)xɦ0`Y%i5-@ L @8H`;pH@^~ ]3xu>9i8!&_syC+gLCv7pL6đfs6sb.=gf;,ʛ -VQʘ}e!(e2‡PJ$vs7<6) iګ(YE)X@#)JyVQ{!|&|!6D7fYץ٨YiPb l|d5g9+^$i:%6$[)4w#YNA)&=-NN9w;R۳+C?^`zwSꦇr(M&XQ.M95I?P.eh~bq}iaY;eq2=3:T6B)G=S*B)^rܹg|tlJ|_;mN<>Йs}]Kݸ^0@ 4@ t<000, Xl@n@"HRh+Z='hꣵm.?=ŏ=Xm̕d;?~u6aG`v6DhyD/vܥťD{lRR"=_6Id98N4V,cJbw.)~r=}rs҇,Y:]_/?8W9k'+L_\5~/Lj7Y0D& Kw=VZ0͑x,1`]#}䠪8-]w:9|2ԲIx7RS 4/m9 /e͍nY>||G=e)c3cP!r9rTVM'~n;,2=)X閬w/Khz`F`fD+ ؁ <@2HIž(z4ɏR 6A[*NG;AeLQQKmezlǣUQArTFIr-F8t3(~Q_ _T!5wCZ/-珎)_/ %|;_ 'ůrޭW/o݆({/:@rMr4]Dq4~9迿៖=,J)~)A2-9ӓdAOrj-SKKV,Ld5}5aJu\ܦ?ŴF_>M_;y2f7L5 ̙U M,YT5r_%ΨlGhhZvl4F%Vޓ%i&_E&|NnVFfEӤ0Iѐ%EG -K -O)FRLI #% )XIHb'IH))2)^R|I HJ)gٯk 0MW ~cao%d{j8A۪C~gSX{Ұ_{|#3OlE}:ECeA1_]C_%#3+n> ^; BhECL!4&1G#,jm߂g8w,i-4+s9^nx 9DfU-y9sNxwl@>1HMOwǧ$'X73>z{lRi't΋{(\/b+"攽b"I? 1#9M ͇pmԡF ?Ȋ2O*+B̩C5Q6mchv,C1{mTyڨIQc{\h߇6*sVip4`h x`&`a X@$؀8$ /?DȥL -ta3)[sUK`P (c`ʌ6s,ΌE SQ(PQw੏f(kZYͽv{vW_n,a%tt% =۩ki)~5w59+G#/ 5?N"\ٻY8T4a__F<=4 T5/e䫤]|2Rx?ڨᗎᗆʦ\/ : A @6`N 7 | $3s%0Ղz1J#ht%QL0/k(s@(|Z, Q2>..xF+JQΕۨU~s?[uoFK3K5˻oWX/o_n_{}GB及_Q~eCO!WCkQ.uKDݔ!3&S_LDp^}8>=&(%dLJΧKuH)X16YNwFWTejP:3uLT5Ght^ٜf/?B_hhPk8us|,wlX"wAald {ѓ)p~J=3PKy˶K&}rݛ(oEa%ke??V[M=X2K@@X=0#03" D xA$$@.e8WWrF^ȑ88?Jxc=oj͙dfi,9Y3syY|^3!f~vE{R=R6PKJRf=RG򠻒VM۹SN=^))NISn6N9)n)qyNq)C8Ez܋̲e>XNm^)3 ?EOts2_ΛSͤb\\||#EJp-K;ޕ#ǦĜʃB<;.fQ=󔨩)жet"|D|ng&6e-D{>} -\`Yd)E)SIUZ8铇)_pJ}pJn8ٸD0@ 4@ t<000, Xl@n@"Hgg μbm?sCǎs S:FX&tȑc9/s)SIE{ V=ieomy {ܦNQ[ {bjC{==|c٤o(JdT_ 1[c1ž!c^;t2>>{IL[{O}#7xG+SwC81%v&H˝9ٝĦǝ{wb\I\N5D8]/Ey55_m>aRX""[?B[[G7\S꺳v.F(wJt~($/"[Q -. P `  V 'xd>~9&׶>N36Tț繥91^891yjxig9Iײy+?P8B(U:Pb#R5|~s_2畔:fr̄XS,= :o-N9'd%W[`9M| ϷǼLe >ƾ0 JdLeJ)ԘXE'&Yr$' -i禔(" S9Af|AŜ|^TWֈ̼miQwSdc;#=iS'6a,")(.k{VPbͰWB)YB)%RR>M*NZcB) gRCH)EPJ#Rp4`h x`&`a X@$؀8$ /?DȥtYSWȫ9MtE<[74rLTHSD2Yk88ogλkyr*U/'s7[5G`!+JRrwkr')[u+ $Vz6(%j3njkq~rvCv+SޘZ:Bf -Niw@L9z|bMSF&'Ĕo=8ZS⒥`r#'9<)LW|#/=)sg9y_}Zzb(i;KO64eG7iGCfvS._4px9氩{Y'(W"33oSBS`e>r5R/.3Y)<_%忳Uކ*ߎݧT; -^ع>HҀjZ,p` -`v"p  AR?hfʪ4]Q(]~[G (ʔe'AdCV ¨`krv (BVvrw7O^$kNE0zNe2Id.=xDg7qR;͑q8RQi41s_N#~r{K~)ǵt &h7YEiIzb7~~iߤ/~;X; ecsXa!R> Tv% %-]%}eq ~9PEɭ_N//o/_j;zi5-@ L @8H`;pH@^~ K) -hݸFháh3GTEYdn`-[w}Jp. -b_UDK<-x}Š~7We}T*~Y/5ՇnW_ -X_٠b^_VUwP!rݚQyD-[螬٠kᗞdS~/8˯__6nC~wfC8gìB8+&.& -bRL#>>5ՓJ{=pPU{Z^ZT}b>4ӷ<ӿ-M:=*K]N*2P e=J<ʖʞ5L;giv}A3rdY"%7uK{k} aSΆ!M&EC),)<)zR I1b&%pR,Db%E F')")nR9]ҕGEڪ2 ƪ(ӈ\e1:% -}$ʙ%>3%Fɹ+ײM !NuuԆ5ǾdQ݊{zG/Ĝ'Zܓ~C-MXE/ݡO<6g9_F=j&b{Hb.ws:m^E~xSbcy\%owL]Rjr?E!)1O(v!oCGE͟]Kw g0c/y9&Mړ}mTY>TR nmyahɢw"_˰ּ /#߼&QWңhZܭNM5jmThʏ*}Tip4`h x`&`a X@$؀8$ /?DΨNg&+Z])&vK[EkC`,6GȢ:F*Ahl Q<^wU#Z,;'N +Vg(W=5z/1__\,d]ij+e4<}r'/YmןO-]0pҚA5J)*2[={)~y~}~2{ɧh~ӂHQ'/^0l$SR GLwȩHK4IvSK>R?t{tӒ_3B7/EK212R:RڻI_}UXBVOo6g! ? 4=pCd}Wu1.F~9"=K"KN/dK[=/Q%zi5-@ L @8H`;pH@^~ { -ウIt;.e~L0LEM534֜h>vL\8fou - -r-|u_ oUi\0mM烫^Q0F^ 0 !s`B078MBm;y#̿. &j xɧ>>sq 3/{oݗ-&VJKIuScYv;3k\brz;=5ܓ+Jeukzpm B:'j^ٮE8B}J4j%2-;-aYYOT>yeݍS)dm~^<ޕ6|e[scȞE)JIUOQ&RR -ԄvVWdLK/T:U_+1?$o?/87C) {K;=BՏ%$yWJ!Rbz|0I>7?87޺1Yb=]&"B;QҍhOFkq>{6e1촄m4vED绬c%A-3ǞRڠy1oTO~ JiRނR[Pi5-@ L @8H`;pH@^~ yS6GgL>i(ec93sR,m aZrKrVޙŋ{dR/r -7ǭx'R~(%)R.RbOPEkh{/]%wk!}ȸ䔋B8ϊSv)}E!5 l~j/bkYĔNXuڳ,o5檩S".^OVgbELNq9 ё,9\tILNMϝN9pPUZ+U#KIU;):b:ō-fmڎfJ7DO?C}om5 -oڍ3)gmayoY{TYTT59=5Vs>H3lwҞ]/e7,L}]]Gh]tЎn:3[N"M&EC),)<)zR I1b&%pR,Db%E F')")nRGC9T{=u^K׾Em0އ!ue Į,/=ݝ'$G H)idHEINu99Ƌ&8;:]T#;vt9=";Ϣwj2PkT:a8"*|k,^XϤ -;Jgo7DTEe#QN=o}rs.z?LK;}(8UM%d+. P `  V 'xd>8zc8uəy8].϶|3 #176~i8qB o'9g>/qżT˝y@+pL;USM!}[Z^^@~C9s8Y8v3alaCz&e_n_j^6ޅ$!HsH$FD#A1bJƔ "%4APԻ+kd?{v|^giW7&05CvmdyR:ci7%XhJotb5»'xA_10 6R?CB_C}OY>x9g|^/k>eZ=Twsm=1uh_}g/נ&Zrʲ'ˁ#"l R&*=̄o,0[_Er+^•{V?7S2EO %zj")3'#/1ş//w G -޾Q=yr 3rD|]GOD/} >ESE=2QRfKO/#]89ҽ~{{l}L =[*g=G_"oQyՀ:,  ؁8\ @~"@@D@:E=n.^s"A%xv'- \0?2IؕPI\}9[ '*`HXu%K]s_5jGx+lg(BK$jR/&8'J[ᗗ:ᗇ ђeh>_u_O$~y/ -n'.vrJ'1'!=Rn -BFfFJrf 1'(M~P+zǴ.hs{m6 -}нg!;\o٭ GUmQs,mu>݈h%Q(^JI'QVv*2cJՀ:,  ؁8\ @~"@@D@:SrH9gp0CK;}\<'-ݮB-P+D/JRR^ -ӹ&I?e-VÆwKoJ9nlr+eټ&'ʗhIt(%,+kPʋVq"ty~hwe\J{Kr,ɱw}~$S/$(*̗_J2/+şIRt7H=d\?l -1~f"F -͎/ - ^eܬ\#dddz1C=^rTAoߵ*U naN<_tЃn/_DT4*hh# fhEц*mu^Ctf-W8zQ=*hg,nqB4{KvqQTAt#%#nʂUh00 x`3Լ4 t7S BWJx:!7!4zYYidYi֤ -2Vm[e -?_w*hJ -ʐ -?ֽo |f*˷ށaytDtxRMAi -LWn0h\*tCuHdeLAdf$e~1Cu*^oWU/z`J=DinQienӵhU9bș .!%=nԴʼнRm֊_U7{o};TGTڗ߿joU{O=MǾ_YۯTTl]ث'ՑQ7?*AC zX 4h0`JC< v48iipK@ "  AB4iАNCT:M3 B4=D %i!Sm02 - c!g[ Ҽ]!a*Ϳ -!*- -'OX35ѐ/szHQqŘ}̶TilKla,hh;[!Y<448j2;H? 1/Ƽ/ -^?ɓ =-<+c{ǝ8ur:qΣ.1M4S킶A'ߧ 4*>T5_Y/ڬP9Mmmw9K-Cfu޹GT:.VwI}Ϣr+B|6~S#*  -=``&`` xn  Azc&8(VA!l !0Ns4™"bL1q>8z /O7` oR>#+\9uJ-Y)=O_9=\m^GZ壽d}JRQY)_TPJ4}g4 -Jyl|ÈioG3%(Pw i OC-̤<2RV+@$)l!ۗII|K6*\ML22sK=|8pXVؤG]Qcv S tv- mcєTpyhݲw:82{ͱ۟OmuUnJY1{jURvS M~JzJ>4= LŘӲRj `F`f`V.^ ?H B " DK>. SD EV7 [Ahvϙ8g)J9DcƉs9W%qp1NhZ񽓈4Ih'|VVqY)eDA)1' -k6_V՜OQCɷYvʵrtB-)ܓ^3/z՗:spn)/v MQ[hF)Ўo/=\!ms J#upJfϊp"ޓLI'%ez2e$'''+)3)si U'y+'hTM7Qӏ~qcxNpO;~*tJh{Ji*M{_OˡT"(oQU{*}eՀ:,  ؁8\ @~"@@D@:(zcf܀Q~C1&ab,GRi3H{ =[Nܭ :9qH$X@.=bʃRJU9M&+SPW$MO(X{F]gGdȕFA)7((ź*ֳuRt./ґ,ZR'ssLPE& J)#H? 1:f#03RƯ]R|+VND$Q$ْGJ?s3sNfxɹN(e,ylQ>ߒUO LkJɯJ}|d))}fY;:Ѩnfi|fyakC 7.~JROB)Cg!*nCO@)]T) J#4@ t@X@ XA<px D 0tKC U9ioi-U>8g-O'qr=y8:ݨIHQabjY<݈QPuR\R\&JQݿ^C)|[m^_NYlV)?,;UpJߚY/2<'1 4xsO~%\-4LNLΥJLR}Kۏgm>c{:Skכ6k8e~tvG-;ŷ=|NYSN5)EpʡTpg8e4N<4TNN#4@ t@X@ XA<px D 0tK)p -ahn?Orl!ӄ" E9Mi"|q: qHS$XABdUf&)S ;d -Nš8WX[Ϸ)WSΚ++"NyMa5_^!;%;עqyO"ndx) ,߈ SZ=AiA2a"Dss+^•;⋽#dHL!rGTF ;ʕ9I]B[s8.qyӧOus)gvtK`oRf3־3u_o.aT[Ulw>Hy6?pa|;#E*9R] ItjUrO>JGTh00 x`PJ9z#2[d/<O fHvg/ -LRrxlOr>*(|UrIyk=<zF4=)hGm{|>oHmжgҙ*߼ܶhLo!M)zw}d9lu5mq7UA)tdZHSt]l9z;ټ)RNB)5#W%4@ t@X@ XA<px D 0tK3f94q N;~e8R#3r|bJ)\:gsM{8 TP9gW Y)nTP7D) l^\?TګcY)eU -J(w|WaT>?GS~<1mOVV;Q\0DwtKO)9Oyl %+ٓ),R/YL>ςMP+xL g R֙&'7oTC)P;U;qt_}dq;P3iRo#R.*F(f)%PJdՀ:,  ؁8\ @~"@@D@:.V.#rL;\|9~rdti8Gϟ,MT)T)8g=h۹j[A ?RElyYrs\4\MM|Rn )>B'K{B+tTF}M|6 Juy1e"cˠWo Oa&&翡2V+x6bJbV@HyshK@=L1ןt`Z(vŪC*uCY8,=v-lUtL?yT6ۦ"l3?2wM*mm։w#Eq=U{Tg q#coŹ ;v"= nzqhK%,S7Uʎ]ȡ_TW.AC zX 4h0`JC< v48iipK@ "  AB4iАNC-UdR5کTz6/ȶTlИjZ T: 6MG7ە*BTt0O Vz>,4#1>OD4d]-ihX}Lz투!~%khD֐UACZCNu{nh82cuR~ - 3Ez-П{W+!=>7H`3tC:3O-٭gK6ۧl>ܓ|s]M[Sl*ߤjUU##i䞧TSB4m -:27 'g6xD5`h 0#03+vNp/$!>JQ4紽D@9Z,q}pbg)ksWpj"T% pb z=zf"WkݥMRPo6|ZT1{[HSY)/kSY)/keb9eH)S_/Ү^:_G'a8$NdS'ƻׅWL&"LFG:suԊpŜ=X$#;9');)ѓJR'%D/%YH3pܡ{[vȜGZc͉g>sO?+a|ϸyrr~Rݻq-9k)[];~6ChYTK)V+ЍpJS*eՀ:,  ؁8\ @~"@@D@:(-z0DSJED7T-RG x35pIGBX:u;*LrbX -"s$4~)e%̞YP>!SNSnMq1ojQ7JyYWJT&i7e *lET)LV)/};Nij4e7=t~wQ-]WB)\3(e=Upx WJ)B/Ō윬$OR)\_GNJ$)';^|vvңn(0Cm|Ħ;sO.H^0tϘ3 }{ffm[ϝ3ev^:CPJK ]D? e8nH?LL? -5{]tY)xD5`h 0#03+vNp/$!+tk`ʉ"pnP1.p\J,t빘)u9= 5_sb&p*9?rcPPJ@NS˕AA),Wu6QSB=B)wJI]AòRVRoDW+d)+d)VZ|)V>Ɵ}yO9 w@)wDž}4 B)}.̤ &y(Ԋ@/W_AxBmtҥĽ\[e9cLrc8$! q߭_}޻ݍ+ժn*E"RD-"")EDH+"VDDDDDD"ED{&g\ q~|5cɳQ(&;z>~6m7'ߔrMnvߐz%m䛓/mjzJM:nޚͱ-qckM7ޒ~7_]y%_ҕR_o˼>x7r]n.N7뛶G'Ӓ]4wŤ;x.ƑdڟgW((j<#c YYY "o¡"9U rOĥ稣3'R7˥\c>0o7n7xhN2z(̌=A$3WKThP:`&"X 8< $.Q0d fѶT+LӺZFj%Fp.R<>CJqn1&-=͕IYG=C-J) JEμ©[8#^(G 1F:\9aˍˑPJ+ǞdcY[mٹ"4W A)/A)ß&4}/C)H) 1쇑R"TV)$P5-t@ L D3+` ؁p x xAH ]J7\Z5KњYZ{Zhe56vӦF)J)l;m;Jۗ)g-=4_I Hyi_5 |WV󷫿fUA(+弰UAD)jtr>uJI_Mwo/ P߮qSrm)rLH$q:7WovSlpN^sNILSN){J #GS>SwI)nN$Mٻ<W\hxR\X#:Xw88ź32bb29e=SYuJɬCٖ"Tz -1N|jqC.Spʜ8+u> ? ,oO24DZ_ޛv6Ti~ -NN} Ni.]h'.w)p8ez/RNiFv -.Q HjZ@@8f`V'@"@,FLQКv:i<ư16ӦcSfRH[iv !Ms{haGio#MlkSƒ86)1%J9ʝyA:e>v>׿8KS -(wJM_YRȋ3F'|{r9qdC8[J@BǔtKOIqiRLMvx)\+.%.C)%"ƳSʩBmNͩ+#5PJ1Z>1ULi_wM]Y9IHN4V[L{sRPei0e(em̽~|J@)=5PJՋRpJ@P  -0@A0 ؀8܀$D`J6aFU¨{h2a,ftS1-u>y raVسgÕ2bo9Wa<;O|_VJN4s(#+e{JQOR]2V)wܰRN(OR{-2$$+\V|rSGR8?lSPa'm'i)x)DSW)eċR[ΞR% i1hr9xOJ#w9R!sdĮLyZWuҡ-pceN=gԥzJ91uOFw釠:l1B)$ފRR2C){i|vBO\RjRʥI)RJ%* T@ 4@ (0 , -X`vp@^~AHR('hrQe1RF3GkKif -$ q?c*r\J)Gik#6ӶEt>w -21ױqsL5S"R./E\rFf)%-DJ9yQ[?JɉSVʅARʭr)+ )e{krY*a)#eR_GJ_+}#;6%&tyg񉍍 !#MLw9R3R3|GI|;mR=ȭTu)oʡ" _Th|J,nwi_稶Vz,R1Tșe4>h|4goZzLcGweڗRfkh|? -ƋEP.(e J)(R&*Y)D% -hz`F` X ln |@"R&hqlU֔*YLYa6ӦR-)J"VI)8,J(.оty̒R)P)?>7{,,`O)mCJ];>gY):.++HJRe|7R2ÆRpgaASA('io5lɃR[e>@H)-#>dKEP)"ݚp֔!Nuy T)%.&!ƉR ~,e=wNS>ۯV.e݇wϭlkGL9MIEw?M4dZ^x`=E؎eS -gœ\Ӓ]Ȯ0~P!ȧ -DÜW?\=,!뱻#,CJ z*` ``,;p /?H Ke{Xr>J5Ϫ;XM~Yf$JW;XCk,fMcQҔ2 ,FYlk+F9KXn2]Q+.oE)eݛBuAeܽ)T =q~z["˝eꆌ?,e)_e,e)HtW.Fue ~y]%w%N)?t|.EdI>ET,LCyK_<q~qex\Ɏ41ݞtGJ+NŹ\'cr<KeCE.tr'8umnv.Q}8&/;RW9;1>rqi4 -$scJu4vm9ݙ*y4AW.,VŋYBx>Adix>I$T'JjW# .Q HjZ@@8f`V'@"`Rhe7M39ZOkhi40ZL&icm^"mbƖig9礪6ZEoE!+] J%RJI%RzH~~E(2yMzgm.+%VVӆUD)=JZ3]l_;=C(W#Kq RXȢRDtiC)A)7&yg-=#Kj,pҒ'# ]i)uJ)y䀢O/*e E˩&rHRK4Qgw.gK414?tl.6.):zlJnUtdgC)z_2u02/<Ծt>L~!՞D/+$P5-t@ L D3+` ؁p x xAH ]Jg^F5iM -cRft%~11"4$NN=ؚ{#c]eD㴷MS)?r<%BJQʯdR,A32]EڍRʉ/ P[.|˔R%kRRVJD)SAVz?]uSl`ei'mwnrK)eJ#O} ->EJϠ]DWk[rTJL܎X)qqd.%9bקG=;*%r!Eʩqݑlv#u_q5SDDҕH{\֊4꭭G#e(\ wZ_Mh+6 dA)=F+@)c@)#O+D% -hz`F` X ln |@"mFYÐU7f4 ve*iJ/ceٌiIJ)Rj~cl}qfu099Wa~[j9sGVr`6yJm+-(7lSuAk?ovYvAkSvʹS.2J鎬TtdBt>=\:X[t3ҢH|4^S8NRu)Syg)|Ldw dt>iGrsX!vSPܹ0JNʉR.BU^>yofj;up3O5m6,G zL5&};,"/0In1 .^2b] -}u|@W)l%T؏nU:)Ɨ`CaBGq9/ҟ{2aW{?WU?E+BJE%T4RJ -#TR1H(T¥!T,RJM*v8I-^*TDxⓊ_* RIJ@*+HV?ʏWwk.S~f:^W/@)G`n|B_a]ĜŎ$SGs&̭Z>̴}=v11u9)Ww7bG.ĜGwӈ99'sr>0׻[9^% -hz`F` X ln |@"',`%њaVR#,3ke-Qhkb(<:ɲݬ&嬎&XwE ˬzoE%e9T~1M__ ArKEovlsA]$ .yDdɂWGA OW0^^4lBιGYW <9?yJK?O&<.&[WqC8{};tbSc\XXFX9D'5-9CtL3_vgˎ7Ww{j+PgE31S }EW$Qc+cP)9f}T/3*{a@5Չ>mf}T^VBx<佊>j co/Q^% -hz`F` X ln |@"EuʊhUͱhM}v9eh]s~o5D*!he6ڻGY#}4ghqg}Q#ACNT% `_4O1]AK c'q0/A~/fC-m05;I>*YڡX#ֆXV)̿O~sLvC8{;(\1#6%9{ȈKNKℸhE93.rtSrHMz:u|}FcrcRai7•vx:6նGKZoϹ]'77"ibn'=/.Q HjZ@@8f`V'@"v̧U jzFWLGWЦziF% ;Lfh{%\%=N 0D㔷Us7LVJibY)QAVŲRiPTQߒYr6O"@]vSoA2KòS6,Egbb UOj;IWz<8^4ESvJk߸G'UhxTIS4!UuBhyRRbe8cR11nWí]|耢>μGW3K}W+{>$quMQ/2 2ԫ, [tջpfC{h'ƺ»ZYO)Z|:KG4E5o)*oAfYMQq4EUY,'5(,-,Uh*_CS49 4EoMW HjZ@@8f`V'@"`e a1V5ª{XM+-]֕FYC.k\bMRS4 ֞h'Vmr^;(q泾avUuwB!]L`!]LgdUyoYUwFw&@ݑ4%kUA vԒ$<f[>]&%y܎R+mW68mQ:+^oQr;ٗ|ߩ=aB}Bl_yIA?#ʄetcE+BJE%T4RJ -#TR1H(T¥!T,RJM*v8I-^*TDxⓊ_* RIJ@*+)'}di*ۯ.kZ⵭~+)5 3xSeh<4TogGm xgx4^̉o86sX4AH %3 DȜyQoYKA7lE }fODИsD0umZPbOH uA̹/6o.FiᚇpV≡=>5=CLb9BJKw;iTssi맠?r@Rl[m.SwL#rT!,MƆt0>ޔ΋z Z6NMh; $P5-t@ L D3+` ؁p x xAH ]J b6F5AiMEftC1L|4-NURX6ڹű[2V7dk'D)E{&?,,pC81wMr":8ˑ̥8ԔOL*SJgEgh}U)m3|lToOf䯜G7rLYi!˥_RrXo6U+B)%͖bu4v[mǫ2PJPJM9<')ۅ86,RB)}G򗡔aY)D% -hz`F` X ln |@"Rh2MV0iJ]Hk -mM58Y5=BsHJiS-h0m/4Wȸkh~Zht1y"5dJ"L)"#hL[r#|em)?7 N11c)Ar9/?/=tK~Yuʾz8(Ĕ -+}?g*2bʥ5 BOQ q $&FpX#.-Cp%xwv1Ұb4=T96I'T /-]v]544E7Ok7 ->Gk^I2-t'u}0w21i͙`?Eت^' -gɂ=qoA_S\<6,%FAz& Lc~uFf٠nV!,rU/r ʽ^'N'CK蚇p2K\ I"cX+=w|Ca2bRc%#ƬK{h<ٹrev,ۺP}tE3UOhIPMW_l{~{)4BR}KKC׵2ᗞJS -gۧ\]ή0~>И_ -W%4}wXbI\@@ =0#0p6`N7D> J{*Yr0J5Ū+5zX-XSvi:Ŏf>圏YR_ -X汾o_NS~1ӝ~7딡нe#6ho|dE_~*H~C wE`}٤[;ioE~ᏯN>= ,嗟?UZy/lۚpPܡgҸWCIBǧ;BJlJ(ǮKMM垨3! -ZhwEjʷlDwh {؉h$ z'?զ#j˴' =Qs:wr dNĩf1%EOh yY+vM)B)A)"-]֑4vh~*:5TB) ?BW>qt4HG~*/ 4B)ţRpJ@P  -0@A0 ؀8܀$DҥT1)̣U z4|*a,FLghcbL1Xa:MKm?m/c}4Wgh>}s.+Y) JirU(/-^)ǜ-=C SFd,fqNcb>#2}lS.)oZWwѻY])7 ŊLWmPO_$Ukٛ =̒*=^p!bpaX1֓)G=|V>2{SEVǩ& Ԍ)q̋S(vs*[Ƣ,w92>8e+Y5fKu V[IaNYq8u8mx@:8xN)SZe*` ``,;p /?H ա!,aTє0efF7OiqXKj2D\m٠ܕsFhw 7 -%ڗl(ydeҐ{?riԟ٠kP2έcut.B{=XC!sTwJEEnBLi(?Jy~b>tSTK d|Qt>5,*%& -Ƥԕu..H.֕λO0w@)mVNĔc\d~?j:ĩde3opT5:"u e)e^a)0 -UPJ_UyORМw->=t#t?Χvat'ΔzJ9K:4|Jϑz%* T@ 4@ (0 , -X`vp@^~AHҸV5#6 -hfqp6QAI)R@ "+s@ GiD%dck6XU2R~*+e{J(H6r3 P'^e\rJ5+' iG~z ]ow )8J\/AJ{RGRA"iL\dPܷːqx7S<18/8Γ{4_ ]o\Q^%/}nBY~,{PZ:w+U3׃v2 A,"8H޳zNƱwo1ͼsKxه(#v) 9e7lKImwVa+r@tг8ӱ9MP-wCjS?qis9/\~ϬP -c~T]`m:$sS]ކ46Ǎg3퍟sE[9}SۅxqS+!蜎i=tN` .Q HjZ@@8f`V'@"@)ZYȐvF&hWK5K]ҜQccYV؏ΕMj`ut9oP)7;RD)Rޑ J)=#e//Eswk#_9uꑕ~OMY)_}`8x}dR ezl:(%o@ZߌM^S(:ct p"'\MN&i^WcR=q17J!,~yd2rd֗,yI>٢)i"ːYz-&D7BdikO2w&/Gdi@d:Ka)LIit)D,S)D22""KY&Y G\@@ =0#0p6`N7D> 8"GTQ(M7mʣ(x~j2E*)g ,[چX2Vez|/˕O -p7g$p¦-M5@d];"![e3EbMۆYR(gtuҫ*D'ރ_yO}RU 䏾D隇p_zK -/Ʀĸb -'mjp{)# .Q HjZ@@8f`V'@"50yZ=Nkfhm=MU2nF_24. Kyufh[c8 vO|'#4uUmYz9d(ed(eڼ߲ {zNI_'+*+DVW:DVWcӝ.B","$moV+G"yOY󓾊&p O.=#9)8|+)4~}4)Hrne}.=NULlwi^稜f9^usNh z1?l1(uWy-e]PYV[Ù6(J%ۅXVC)PJҫ c/l'o'm)r$#NĔg^I}EWGھ0)iNpߘzN:#&1 WJCLKuR9O:&zb(ξW۠*OfjAc[4=;=6h;-~OfCGhL}I]h >n(Ӛ%ڠw6dmP_)9aSҁo jiFԝWы6KA9amW HjZ@@8f`V'@"@heg4Y*Σk.DSuLm.?ZZXc/kFn -Yk틶Fˣ\N;+Y(ڻ'W/' &S{W` *.g xmSȑ[{6]}͎ɇGr;ј|xԹveZzox)}Ѡ^ -AA /~;eJ"\ 背W'b<_tԐ;&--FZNu\H2bzF=]7_ -o;.rS >>H8jY)ɋh w_a5v kZzB"m9h5.im#-5'Z濸M =-O"t/mDhlwЂKThP:`&"X 8< $:P1Z5FkiM6-2)u96LP|B4eZ6Eٗ)4t;J ٴ@K/Ѭk>jweOZ3_)pJFN쭲S}O-qJBN7'qʝiMAC˽܅US<,8%3+} -dZT,5,$ R\'، /.GJL:d$䌔dU/(;jhʚ3靄j׷hf mt ]h٢['lh9dH -xe4BKVuZ&Z^Ah)kBhD^qH @h)Ɨ** qe9oenarh* T@ 4@ (0 , -X`vp@^~AfcQRGiZ,deztQFJ/#B0SmMeklY(rneh!?Z,eSQ _$tOB_g=rnF~> -2MQ1ԁ_ yz6E7]@S4՗yRց̢_^W~m@SdˎM{욇pnC7E)1dQupqqOH )nO;&[?7TEÞ/], o9Gs\jSוFjFYJqd#u=_q96`2Tp(\Wi9Y.Ul.`}@:Dw72K{,&L#T4">JGfمҙ+YpJ@P  -0@A0 ؀8܀$Dҥ 6UMzi45A3nrMq6Hut-@keyvV3\#yHo/ۗdQU$+D)r<)+yCMPmlY) J<tYTA=#c'Ԗmӈ,PʎP0#i9J)Rn}=?y (e9 JókunTwL4 3Jvs##{^ZUʞs,Tq#5M.m{?GUD4rg5ޒ+cf̽#h%KqY{o;iJ)RƎ ڿ)/vaOl } mPYAJ9zJY8!+$P5-t@ L D3+` ؁p x xAHb7F9BմQSNZISS4ShQڐi~ ʕXh-9DsgiI)󴷖5 "ed)E-uL)E-uL,뷍nNuژrAf cr씇,ay֟+v$LMZ²\ ﬑oJ)_Ĕ% N$_uyg)[pbHڠtW#5C@uN){@1Pi:n8Eֿé8uwUEL䘊"~sˍYfSt`E$ȡHK.5w[V[σփpJ_#R26O@L+<&m{SS[e*` ``,;p /?H KirVF3LK[Z@hCcMSyښCʹ-O8r7Y wk7<H5c1S&b vJզ:ZbF[P2)kAJY.8]OY r{uU׼v( Y+}#)]@iOkY|=mVRSqiq)>Fp9GFǓJψO1;h}xsJΫ5˙/vu'[4ͻmUv>A-T$F }3 KB7Lo&;,0u_k)2u|~U 37W -ڂ0hGBAtS܇* K_"׫$P5-t@ L D3+` ؁p x xAHߦl&[UK46mU4LEVm(m!i:R*Y+mpڜ6n*]llBMlE7>E~wdXdrFFVnU+B^Fjt_nI-ʌi>#ˌi>+8|QT6G;bF ~#%f)d~yResk;[^"&8'H#+ɩdw##x=ȒEgmD92ٴLvㅷj$ g`nѕ,$E Qn1M ^2t<7GXJG`T؊g%WT]ޠ['L|!(5MJL^|DJ2ytJRQIE-TRHE'T R1J$pDH,TRab]*NpRqK Q*_qgWydttqNGq:̙=0A:c9g MnV]AH!HbBH(bD "H)Pȡ}([n]? AHH'!D<߀7@UlW5UmE@!/0o74t:`mWs_ xN Bi@ ox^K<-iK%-ioʪt4~ot# .NE8Ibdbঌ'?OtҜ/CC{4Ge{4xˡ;7vBsh(ڱ%\b KM)֚pf4x$ʮIDfs򬋲ߞyQ|J)W?4@94jG5מ^pkIޤHRؙ)t-yApf$;9L)'C'}gy.USnbWk.MVu<տH Ɖi!b^ɷ}.O ZOmE5=gַgŏc -Χ=硔_UC)d@"JsҧP    8 @"4BC̠;ǪXUװF;FzaX}kbɼrּH2Ee-ZY{.`]5,_zf+Lq6,+e)(I)R,k6[5e5#d)?H)gX_R~O:L3!)KqIJ_l^K+U}e(184{r]nQ q?4@9'TjR2b+R{jKjJI~Τ;YΔ1 GgF̈^wDy3.pry_/,SUX⦲_չf./A[/ha>J{>NY3P 2)h5:~mk?/wUvLSϯB)!PJsЅNRN7RֹW3R*@5-@ L @<+؁<  ? H ȣ,0`ԣ!](e}%che#1WezCW3UCgfrF\`Lg,I=W$*R^MF)ervJwk,JbOO4*ݺppI\d/"GR<g;u -I|$O&.@)(&\뿔W -P[6f:>mK -]/U8;~VXw”3u{f⧺+-)u -[Y%W?Ai_W:jZ_V -_V+*OG;*oOo{T~ -tת -f?֣J{*vpBZEE Zt$$$I0`$D8I`%FG .x<$xIHI !HB $H ;TTL*j%H֠>'hI5McAs;jq2HvںvpѠ#{3ϥz:BoP -ǂ%IC !JFCߒ4IC)mK3Om[7%9"}ICs1O2.H'ϋ: ;2C _J\)K]'}cuҡ j 7U^=̦pp[7+ݎ7 ">F&a.#=f, v̦Km $yMN/2K# -@(:@ A`6;pxDi >)fU C1fі3c솩aԧaIfSNnbeO۶&70ڍO2 -RqYSJR%-z nJ3hF[SR$}w$L(&I)HJQMR,^şknJ9tނc\+Ҝb)kOl}4*Cu/tWBVJFDusH3fxII{.SJK eѳ? _TMz{jx{8nQ6I)RcSmnC;1.X+d,JߵQRJ-I)17|KF)*x^7E2JXڳPʡ JإBO?Q7uvB)?pwhG S|NޝN1rF=WLd /[kةJx7xMΑ`)A:óe '܆vd)uG-溽R[ n[=N@) GC)PJTqm@)~U,RvⳖnO(:@ A`6;pxDi y6VRe]`u,ϲMW,^Ii"a9u|d1jV,aME{ I)Z/Qw%)]#nJr̲"G 9e_2,-9)2N[)WzU'?]y]|+.~wKkm7SS)7Q<" N}ټu/9EkE3gr8wz},!:ݾHV8u#|Rn3'|J_Zs<7ϫk򐦜~ԭS_fT>3|ΝτL9s Ҕ}pNKkҔ<(5WvN%d>O 23P6k;b8pJ{Rr NAr -Q(:@ A`6;pxDi J?V8*c;w>9p@}F5bhSZɍ'rV)_kKzM]sy< !)HNo4 {e8 1\'c52s#0&Pq=RR2N {6ewpʝ1 -?^ߠWRJ] WRXvn:ѭrݔ{҇RfB)롔 -ԍ.RV'}F2G$ֽOs6eD()L d$9EOpF#H[3<+2|#ϴJ yjM 2> 7yz iJM>o7\k0ﳘ -,qbS,PTۖ{</OR+rwlJRA=$M))!za۟JNUJSf$tɤ)_ݿ)]2i}G.MdZ>HSvuQvKBv)g3 HSS>\#My-^;[CAHB)B |8'E/OSP5?eYggvN_zOU#g:%k:I7Xi/{P)>g vִSOnMo(Mm@@h=0#038`</  ͩFQ#zNM[8:ck8}1gff:Ι2e$rىE1U񍜧s6qn pkH?% 惘?ܔE[>. '\OC˒_Xe,K~a%!W>hrIeJ{L~(^/'"g)RjҨw/C{ W922Qd)d%$$p'7#Et(˚gN(k|eH9KSuq7:̫OђmK[7sðǏ$g -S7:gĵIR~0 9Ivm Y%J'b0rB,/5Y"gir< -P@ 4@ t,008, -lv. @:ҮdT] ʒ 嬶3t9Vd6|Aդ :~m{5f] ?x -X$C3l6PnxHAkԝR~5*wSr'no8&1D;eSz6Kne^,uVeRyM}]R~FˬeӉ2tݤ[{/PơFS,z,v(e`eu/MbA)BOzΔf0~NoDI>KOPx3Ҡ#Q 75j5Gn]x͎N/ eY_ X}99,Vk[TcEry,}/ ei9K;Zfb)w)KN/RHYjCRYP    8 @"4B,jnb+)dX簆Ϛ>$Yhi+]ݴκ~LEFXf\ֿ\~gAGK)˗d')y)eR~،s^6RʝՖm3dɮ}$}V :Є_Ԑ]yN9We".⇐§[Ny@m;8rϒ|eqr'F_9˯{ W)fJ3S>>% HXy"a1pJ1_i9T=榖^mW[Wӵ/{݆yqlȔm1Og[JSr ܶn -eݎ n98ʠjkeAawN&+) " -UuJr -Q(:@ A`6;pxDi *>YaU9 UϨ{iM>gt] ƐSCՌa12NƼ@RBR&n71 -5cg0B#11&Io]6ۨ`ʝkl*C,٩-[4嬤?(T:+QʅP;qD.끵SCMe5|zsP?R۷NqץS_u/uV>!>IEI3#'"3qI(y|'NR#fm!)'M"""AM- :hX$H0`"LB $XH`##N < $$$I$!tB$` -Sܠ:g"JMSAv4U_в84R;H7fvv8FT~66Tq"_ Ƃv`BJ!"1;0MY4c5C# Pߗ4w2_% P+?_Ȓ=Hƶ8 -]=9TK>|qCnJKOj:u[^[PFDf7vB3E|49,3ca ľG&*-J/ަh75>5ܢm|@xm'ޥ[= -CG# -Sߛ̝gvٕŨϻmCRϢqlIj,\R|4n*3_* L~ lMݚ>(ux*@5-@ L @<+؁<  ? H ȣ sB;5é5 ®JNPh,I4&H lWهC9Oaw淉z.mnICL&/jܶ)~ixqRyy_C|s䗋_ 2~qJ~(O_+_V?%tkHs|~yhm2K_Se # _[ W_AxBmt̥ĝ \#wB:M4i#HlL$ C6wWݵڇ]{,",,H)RDD\RDDDJYb{m_Wj:3-|ORt}:E]{]5{:gF֯2e5?L.efed]sݙk׆܂ 8w_s_(_Ů+~rm/h?yOOo~rSiks)q-4baGoi[ŕzSc~{O85"=;2T ޑa[`[궴mTP>+<0'=~^|SgPgT)6|Y{h'+S1(S=*ȫv6jKMP=_mjUmUmHoj^r{}Xm=|5Ʋq?ku{==#+똍{5(jPuV:{}{ {U3z`W_w\}@>5࡝D:Km7dxp|ϊ~:q)@N-Mas9w Q,L!J e%dH i*:nKC u-8wԓ4Vx[_Y~J:jb 7=:Sy-Gֺ os?o>.G̽+{):3sAJ\;W+Ǜ)oio ʮ/SʼnԮ7QiQvD5h8`F`f` XA؁<@@*HRJXu+K/1rV,ˬ2\ k㬩5O1J9B)Ŭm;X -f]PzjXq:Y9\cJ^kvUh\(EF)0J/ROcJڤGuVJy4MJ^Qʔr_P>R^)P{JY*e?QUʝڑ(@)/| Jژv< !_?%hW?XTG7=s19RNHO!YHp YN9;)$BiJiR -H+PkNj7]'v(>)imX`_ЙzKn,ZrPJ;MָŻܶ _pj{]e(E(#P v&NU/A)7E(%{EQ -.Q hZzX@4Vlv. d> -e({XM6]tEejYn5TslY0QJpy_aݜu )n>NN2SALiS @C.lr+t_Ma-D)E2tK}nMKiB҃H6y7^5>Or{ ɦ~ -ɦ f$gJK>g ބnU$Y$奏`ړԀ:  D`qx`H@^~ Kc,jJY+e,rEa5ְ\|4dSH"kg2^:XW+6Ve6VAa}CO6ZE)OgmTQq槊RpJNWORIllg+̴1OemRE))iL7R.80JuJm)%f_tюԇ0N2`oh޹K;LhPʟ=3>tjC8g#GI`FfӝLg,&;Lw$&ɡӔ|!թ椁?un(mz672,hj{buS+c %n;:SY}VKe5z>(eּcm h:wh"2Ps/pn+uݚ"ͣY!I,=<+J% 4@ t@00 1 - "  RA rUOt>9j;9] `c kq5M2M2Nr5s"kaìkYO+pR!+w׿?)E)dE)χQJYiޭ2ݝ]RQVo+%^Q -(F)a23UJ_ÏޏR12J}J/>RJT| -JH#?!3,y$9KHOOwf&e;RBB)J) A<}ACy.o?9vr?TRXO1%7째K M+wy~epgtٛD mW՝e,'){28uߊʕshE%t=P%(p;zB]Q*rrRKG9z5h8`F`f` XA؁<@@*Hcͼ;yMxf1 kt7--<2~mm<ۏ*ޕ =m8K&ޛ-9ej._^VR 㗗4t!*oGuV~JcPrr ◍m /ߌ>SQẠk_.<رU˾I݁J| ~:eOۿ \%ozn0FAIv'NȈ[r& -x\^s4^jy}e<3x@i7[lKtۆcU0j=6q1^Z[?m_VڄS%9SN] -%j_ݜ$DND3> -#3)QL$HYuHU}R;oD'neAӀX]>e?*pew Ʈwuײlkt-T ;3Mq]n[A]fD-hJnDK4r -Q-{$Z2DcChDKTZv%% 4@ t@00 1 - "  RA 29zԲV{GYzYk\`LehRX(×p"1˸Y!t +r m`}+̖JQڄ |1RE)cmV -sVZ+TFY"D1ᗹRmE),_W(0{geJgGwFa(y,eb,O}f-ю Y¹eqGʐIB)$KA(EHrf!gP,e$RiJ<ʻ7)/ȆRrGorK#0QJY'nXCnc)iHktmΣTLeNkNKl`6?ȟn PJw=2tzJi~wR}ro?\< VB)]PJÊRpj@ pA 8`<px$ /?TȥTpN>jBN8fJ 3beRe.ٜm8{!g]0z9#ݬw-|E(Q~͎()0J"#JJv6DYBdCdT" .Q hZzX@4Vlv. d> -/Ot+GVriN?1Uw3q6T̙[8K,M$T?Y9W'sqNZNG9_\w|M,R(kgeomVJHc<׼WQה+. O)P .R>fwg;2.;}9J)pЎ:\Ut둥@Rv$xɧ> -;t_*}s9,I#'Qʐ!KB39#'!#:}`搪晤Wz}Nu}n~Z,7 ڊ[bu- f eoLůgʬۭu@)@) q+mA~j{s5ZJR:J'RG(P-_8;@54n(4-=` L , +6;p@2Hi`}ZSt`n՗qUYXƙYk%+]P3ײͺ8as+eֻ ->ʟܴ(%SI)AE)Q5LJ9a;S|G#mIY _Ocn<)o(7S |(17 |>Sb]표2nR=/!S;hdt撑|K>E1%28%~1ղ!ɠȓ$1pD=NO$r'uJG!Uρr7ڠԅIAMhrG.M<6hSKڠP\AC߻2޿3ڠ=~el+ "mP? mE嚯XQ 5<+eQ_ U(mP۽'Tտ .ȉ -=6(Wl 4@ t@00 1 - "  RA 2x`M1o7Ls6sn$A0-6)owtĻxjXKK6*ۇ6ȶeT9FF0RFF0:+AV,[1U#+ʽY*~1=+AsAqv//*"vˆ_GFntB/[!2˳~Yj2ȞSiߵ!3 '題t)'ᗄ$gLp'gIIbPpwR \7^9 L]k{ ~)vRC{(cicKYNK_2e5+⎍Տ/O/KI -+~imTV~yi6?:S!t̐%_Ԋ_Vb u3{7l ? -=Qfi'艎w/?O_qz?Zdsba$D`bS&$;DD9)Av&'䄤Pb(s۪CRߎT/qA/h5pW^^[7PwXwRgsu2k.BOIYƭqEn[]A~dn}rOR籠[K*Jy>fu?z{)ST*u%e4-=` L , +6;p@2Hi\ nXfˬӷLM"͚&Ysg'=:X[1Ǐ z:89yfsrn;E)0JE)0JYlUFnQ]RӘ;6^(r/(J1QE)/(J1Qʎ2|~ -Y݉ȲE_80J)8)q?8w!"Ke,d}7w|,M܍&J#K,zBrӓHLDp gCr]BTw&M޺e=>6,h5wzov^puw8C3tyT*qօñqmT&?ٷ@)eEPJ} J^{'B)oC:Rrw[Հ:  D`qx`H@^~ Kc,=jmeSr!5|,2Pa"Vחϱ"Nc=+8~LK -|5l(J+rG6/ׯl|a_9_l~e%qi.vIHK.V2qIaO - Aos]P6r?C)>+3/R'm ;^RnMmu.CPJQ32 -d%q嫿|rG'2WRA)35H)Rpj@ pA 8`<px$ /?TY KϱV[Y˔r k`3@)$Tfm _;YG#Gv -VVl`Fcsy+dK(\J -R賢mTPo)SiYaRS3FY`) ?N)mM_9)&8So/)7#+_)DᔷxɧpwO))M%FY ܒ'pI;YpROsHАvSVv -Ȁh+S{L*5滍:S;~\2ΧbJUNk]ָB,ȏݾ˱)U| -_S -Jɋ)-dwNY!lՀ:  D`qx`H@^~ KdXzS-5pѮϢi0@~6:lzp~e !]L88qA_9 4 mqzIgZ|VKk571eNkn}l"m<ȯB)KPJ'L J!Ji{wRwv\[JihRZ۠nE)D5h8`F`f` XA؁<@@*H[P)zkYM7eu#kd5"fe(kgkZXϩ͜\~>1N`XYo*JXY0JYL4sSܤ+׆_f{OR3AJj JM㭢LcO!}'}WHAJ)tlzN)ɑ7%RR0=]pD(%$;3,GJH -s*R_Rvy&7Duw+BJY[L [LFz)ֲظ"t>t>݅դ9D3,2句R>((l'g >(4-=` L , +6;p@2Hi#WՌ3>F7'&Ck<̚sk#)e/bcqbYƈTʳ||3ZQJk*)F) RX1QJUQʃ>U5+lR^<"eR&f3ЄR&f3Dep;Lr`~_B)'⢷PB(-w)^z||=J8?MOY)DOHJBbL$8333SΐP;qRF(e6iD`(pwFnw,s)@ŧָv[h|nJz Ji92NV.߸WKNr'CV< g\@@0#03h \@  | X,V7O̩@5 *2BEVNpfŬ#g}0e}3+Upr!g}c[W|TER|JJqQJYEu*,r4. ]YuES0)E)J9F):+>s:oA)c.]E`RGJ>Py*JJp;$)&$&9Ӄ!ӝIE!N:}|␪m+w)G"]EM4UaR[d3s)|;:|8̳];-];:nCgqSld3I>ڰL9fO\'Uªځ(A4оW*%?zCT`xƨVzՀ:  D`qx`H@^~ W959l.߮_oVy(onxKvv4n]]{xqwp/{~WJdF\wYgu!ȕ`)?Ť1?]^gC,V/0]PGXw $Bv/肞l<HMwSEVi|҃ˁ\ Mj3BdE ,3ٝ!:$)SHJee&쐪W݈,mo;NJ+3VWU`VZXwIgY'Y-7Y Cd,i~$6m*-wܳˑ׋2HVЛ,ٷN*~'Jg,݈,Jd% 4@ t@00 1 - "  RAX_˒ˍfՖqZN?1=OM5WsAYITXp@G9O 'q+O16*eQF)eRE)QJY7EPn;¶AǕ)+a9% Gdυ,_)yㆰ+߮AoEzA8:8%% j;_|#2W6=sw& xP0 N1:IӝbV(fHw VR eAɝ)@B3]ʒĬ$9S 3Z>Iꉍs ʡc ]}W9v`n.nP\+{ GP!i㔹K=91PY[([A1w^^v\Yz\B:V{V|Y--J= %̕??v^~V0?qN?h5)4)RHѓ‘b H3)RI!JJ)6RxR8Hq"!E$E"E&K?)RRII#\lsOhz|ڊݐOc\Pb\~9(o[>߱ ])1EZKe)Ҕ-MBxQaOrw4-|)Vڥ=5)|hFCo|L5C??wKhh_2vThh1%]>ڥ_|hh9l"G9!=3tdrRr&EO;!ׅ4]Z~,iO9AnS]\۟4 UKnaY{ XC>4oj$;E/M7ܫuECTC%/J(D}xOG)M $L TB^GЏ% 4@ t@00 1 - "  RAX߇ȑ8*t5ΐ9S/k^,M$p,miI98W 'pVfJN#цQRten\ķ-q[μ]z_O+p-iLyV7-*Z`4#C/in&CسRv6sPJP ([W{ɧ>'5$>7a%$9!&NwF;(&Y'$'S7Ri$WlO9S{t=SlS_N=N[lfXJD?dg$A$w>--B|ɦ.a$WJlzloU&H6]!٬U h5)4)RHѓ‘b H3)RI!JJ)6RxR8Hq"!E$E"E&K?)RRII#\l_].Mќi}}J|j%ܛbdSb[}>{yc)U"L<)j4")|Qll@-Z@p.dթs b~_n#@vׅpYK;@dE@E9nhh&ȴQM.] hRP֏l.vO8{;#n)#LݒSJF%'Aa@X~HUx('7Ss4UvXZ[ dSkml;3վwy>jȵFϭP13vZ;5mA]$N"2\FKJ)B3M-Q]11$?~CIj@ pA 8`<px$ /?TȥLEsbFΰ8S%g.>X[NM#ke$Qʹ8aԲT:Neì򄅋|ZI)+J*R?+JپV)dyT4&yR2ܪ98G',,)J',\OR>}ds9hSP7j7|K{?ds߿zɧ>Q%?}b( J ,'?V;rfn9 AH8}@T롃fws)MT>-xRݩJ_~s>ŭzlel||SwS;-=;ni_$',e\klM3RI>2X>qRZ]>nzO?q J?ZQRݼޒWM[!*PT4AA hZzX@4Vlv. d> ->^]ůduuLsGy)8~l]m<oo]}н~4/eo[xou!=D]R"˅g?mmy`v1Mʽ/Pz8+',<&9]74ވ,?_"EKt@.I_AR1b(9#t)Jb3]KP[H"旒j/leruˍq~T -W+VeRC#dmL}Քybdeѣ/w4Q֪P\Mm U?B\Ϯ(fP%-;޿tDz/)'DzՀ:  D`qx`H@^~ Kݮ~^kxv@A.hW/УWRkX9%KYoZnF/"{kvZ+bqۖ%W'Xru%:y|ÃX^i5E^Z;k)ջhzGK4\cՀ:  D`qx`H@^~ cey)QV[̑S-+9fYC>ggMYΛl:*ls,X*,-~N#o㛎>(6l7ڡ(e7qV1Vw2R0B gWE)?\Q;R 3tjn镨p-\sDZ\~_7_ ȍ^)Yn}ow,nznkt'"z3Ĥd B(3>}CţTX`r(uoOE8BiC٣?B'o#׸0t2s -S摿t'r/ښ*ЍT pߋ*~Ee]~]-4WNŮ˟䙡[Vovgԁ~uuդФhHђ#EO -C -G#)&R̤XH&%+)qHI E@/)>RHI%%mͷ)tS̯fR)LK1y~ӄ߼hh%6—TL~/_][Nc[l4|_I6p̓UG>!n+ ˮ {er/F/*?n_ǜi92h-d㼴nhho)4l~>%sڭCkG9x7=sxK^!3)$8nHՕSŌ`f B쐪PR vz𠛞yA>*h  ++5qu4\5~$֙cJnv˃|nQ^ɦoT#%ݱW:?E^"~!۩GF%zq$e4-=` L , +6;p@2Hi ^c5+Ӎriap56l:lOhgɪ:U kge}+ϲFWF݌oXHo4p'r`z_ILc {_4CǦi'³q˶pG4\3n36BM)uJ3pXhM>DS¹D~@;(#9e)1)eg -N# $Pw!UY]̈\z4=pnL >x͏k:%ͷ)kߴ6uprnr.GpJCd(2ww>pJ]19NNiN)mRKTh0`&` b8 D xANj&8rKbfk 9e3ϰVMhk9\՜pqb/'s -|ܖG4|O@qJA')N@)tVx3޻Mh?1<{{WbJt\Ĕq% ?+ E۟Xmw*yH0wbK>ELq^৽ɩ/ĦpyiLfrLg(ӓ9[Nt< I r,I] 46&u=[tǐɅRUţ)pE:eKuVKM(qNkq-)UAgn]Χ%d1 SjJ>Y(I79Gه?}$5h8`F`f` XA؁<@@*H믎eՃj8m5d9fVYCgLyT2@bJ/k]?Bw%Xr+g}u[oNk(a(IQW(%:J:RL !ܼU R(ݯ83a>S8E>+Naܳ-Fk}">s:q;Yn09CwSn%b{tM)7pٛ¹4#o$p z2!˝vfCPr0YNJNH8e0ve\ya*k:NGcY۞薶1uL_?Hpw>|e;oX9[[T9:^z\ZDV{ -_VOWDIvCj폩}jw)H:Ieh5)4)RHѓ‘b H3)RI!JJ)6RxR8Hq"!E$E"E&K?)RRII#\O+|\ͧMA6ŸpgزDە*O_lKqԬo$3u뗦|7vZCVeTۊ>qۊ>nhV'VziLLʽEWhhVވ8-y'1R _6k]O#ڰk|JMO馇p oLKNX_Lҝ;+LJYrV0SL̔쑬8[CSs͋s'vчO4~RJ~\ȧѿ^j{25o2Wt?3z(3]_]%dm3'UB*OWρ(`J*~p%wK*_{IčQ)kcpj@ pA 8`<px$ /?Tȥr5sz}څuI[Oxo嗗s~M^x`fPi/O|L+?GY]uz7w/W.#<b^)bKRg*}Lܦp"}GNL3dSӓʐ{wǹ_YtDr833 {6̈́Rj{=o^9=W&FOc! !I$/""F1"F1"Ҙ)"a3^yqgw^G 8O2om\"]EC\u |aw'xxan^a>4/}6^ܦ!ctόx2f]Fw]nڮ~巾}TW+~lo嵆U͢ ko[KOU"?k3]L]iz,|!/؆v9u -C=>glmҭcPJWru/YC~]FH3PJ1(rAQ -^Q Xz`F`X A4.^ ?H d gH+ڨ?N U.rnbhJi$*"|qw3 m rq"uY}q;A/=RQQ(JGQ5*Juٔ. 6E:]ܮ(HgJQʢ-Rט_ $Q|,7}Yʭ6uu J#Dʬz6~k~uYJ#\ŻՑod2_Г+GM $@jW6h^`&{k -zm{)F701Ε<Ř_hgH{e|m4c+yz}{tym{G;LpY=\ssץwEA;VYxumCģHݻ҆OiÍg^Ҧ_^/AG FL44Xh`NC4 148hIO7 ^4hih !4iXP6OfStoQ6"6AC󛠡Nv F7Sp"?-Fe3EC)reʚ2d-boܕ{֥X_}/}}Rnה̦*E YSu[L?wpBC]/k(XΑX} - ʀNKec74i⊏pG D&J>)I$@`R'3333˟Hɕ{NG4E]ҖQ1v61s?̧ q՜e#4SsiSQ'K5IMh5rO4ᶽQӏDTYj t@ L3+;1   ttמO`'x݁D.Λ,o.Kt֋mŘjS,W?5.a_X ć.%֬s)~QY}_bTb^ͤjy&fҚ~iS}]L8G_z#Ѿ;pyD{l8L*Ek+5oE6~:9^ -ѧK a6祲riGz~ͤdn0ɔ2TJ /x|~)/ ǂuR*16oK]|VW<ʩgl~ sy+}Yg;R>sDW@)ӟosĕm[ݭj(cJGJwMs)RKU_1rc *J+j t@ L3+;1   tt>6 !0M]YYkg/Q' .#D"V"b9P)O{RE0u]C_SӮ!t2bLn7^W~uJªd)QRSV%KZw֟Vm ݃*g(E[RMt,"8"DұR.ݜʦ]e)ȋ1B093ID=I(r$0($eUJ9,b{9:um!{GM Kk/{SW@.<579]%o<8?sv5ܺ]>!#2Ï3ɦTRv B/a•A)PJޙxE-` ``6` 8@px D A4m1vq" lj3qdvi,gml^OOӼSs'LJy'8r9NBjynͅ,]E)jca+JyWQʏTrx]vkvGaךxI7]"*㑬*Nydx$S^(Y!9s4 ٛ缬OӔ}T)D;ASaˉb>O,4|hRMo$o'G/U8 LqH \{5pR (]*N98(N8֥Ew"/ɍp7z?kޱWTүҏ+*JK֣EC]ꓹA)Gn~?RvJJI(ѧHSì;פ7$UWo:dLJ򉞌,)YttF%PrNvNRFJ?ѧ*=*yV<)c &cG`l:tfl~=2r-ظC>Ps6?4ee{yJY- #Mn!H栔ޗٿB)@)3RZ0 , -lA p8w^eܾ1@)PJ=|ڨnw]֍Pʥ@>ErE5 |X^`-䬤dG<Ĭl$$OFH@b*LlЌ5+{{e>5AR@)O YʥSJmE{+}j-=Rg\u[}PJshoݩ;Y*xJކRzioݒV(e,^Q Xz`F`X A4.^ ?H d 5.ѥ=uf=~+Rv-Ժ=SLSH Z#[H߷^Mcvup'_")τS)Ϳ~I8k1+>s9Mg\ɓs=lOFF ɿ `ᰲGA![c 3E>c۰`<-Gbͣ{|yda*pD>/8G\Yp6_6juJ'b%7) 4sI5prKS"54ű!Rs:i]ҔC"9vf)tӋ+kE)P2))[*i&4eR6G}YOV?-wiʏ@n> ʇJѧ2} ̆r6)}-$}RJfCv{R0ӓ#St9Rfc `IeyNj}lq#PSciR>,Ga<}p#oR{ґظbRY6?7͵JYcU: vbEY::;} JYxJ< ?(,=0#0 Np/$2T>rv)MtD?NpNTBH5g!~:3mT)%;ݜۈ.pF.PGRBBsӔ;o*iJR(Jb{> GrʟM\Mg8z4N)SWISTixy-eWS* F)t֕l7,YNS[)8%C܂43:RJ rص#\v ̗2%( W d&_  f|&na3o-}ډ320ҋʟ3ϊzsc{KY 70s[][3zw:[ B:yq};;GwE>-dIMc&<53*_vE )GZ0 , -lA p8`JKO,/?ў{a+.?&?x|0L۹2 O3s팥On_ۙ7ۇG=6jqv7$v5 GHfZ. uk': C@Kļ72R iCmMtzZi6<ċ^mZ{JC]ZXt4i0`DL+ 64DC844hpAOC 2 aRiH!Ӳ6muu)%;SL2O17˖QVKPmsXϥe;"Lڔ,Nn -RB)є5;ս!VEC7*"mq}N'YKC/wiYه>qzڕ"NOYtZ0|G^S=w1Ҝ_xY?U.9(oY>26Sh0~5M)Jco+>[EއMƿ)9(I?717KI* u6hj -6~|ev^t deq5tS cm{|J6۪KѓNu[Cqg|΅lB/]OnuNtҩx !HcO2NC5Lxjn}It+j t@ L3+;1   7 -vc9]97$g'tQ%[}ZZ:g\ =yVx(G7zIhȧއޥ8bClE)?xwqۗ^Ѩ(^&j(XkWH7Toc/+-.,xT*Neê9Ыrᔨ9ژ//܇)5vj'ڸfC5clGkŭ+>ۈ#:%%R@H,ex$vnLΖT./<ܠ)h +vye>vuA+jc ce>c˰`:@&bͽ>KzjͶsL#phHOlܥ}Κlq|Vwi8ey8s?m=jCl?73)<ʄ{3)m/+N+j t@ L3+;1   Wx2ډ -1zbn#㜽.nj K8 'r$O:"ՑP ϐ5BM(L .)n&R׺6][r -Zi\)JNp}aӣJp}aӣ*O6lZl\ڤYa66zSh41+>U?)33i$&ybI9l_fRHH -58֠ii_.J`m/\}U?xя~ݐcEKol7=rM#Vvۣ;{Q.U>ri RWʥWP.u\Z|ྦྷWP. ri(qEF;reK'Q.8ri` ٝJKZXt4i0`DL+ 64DC844hpAOC 2 aRiH!~B -۟3Q6H1uȤ6<([jekl+I_/h˞MΖM&W.IMs#IJ2%T,5~_YhjP -oݣ]\N7ݪ>YV+ƈݣ:f( ޝBjst\rACo7T v2腆UKA.JܝNI\ϓ+V.%2=R/)#ןvQmeK_U_^^`Pˋ R9ꌗKߨzܪF/_U˭OO/~=RwrUw?2e;rL◯Do=_>K3_L_򁻿~i!m_~:ҜLM4^@OW_AxBmtĝX[,fY9g1fYdY8$pҐf wgݻ [nFRJ)""CDD"bEJ"e-""J"{<>cMs6^}\>'[cm3ꢅc)-vF3͞q;ߐ!q9/a b~hF8g -gR2?+Hii?1ڳ??3H?I?K?- fxyY"`oySAop7x >|C=Dgoj^zy3g콆Vu9{+cq4 /kb^5ҽj{=vu^rg,g5@bxj{z mh /vv'3A[/pC>>O'З~YG~q~bb55q:cɱ1iִ8QLcSbb%t|U3_>Ryvr\P vuEhڅYnOuоv,|rcdA4wpeQ-2qO̱tV,v1xwqo@{Geje YG+7)@PPp@@@80H`<0 8 < |@" QU)J9eT:cXTZV+9C7LPJ9gjΜY9[Ʒ:Q9k8sp[}I)$\tʯ%DQJR%g(:W\vKJ~RSۑׯ_%$IRtA◔Ҩ[Q.RAr\"KDF)V3uBG?Fn"Jk%U*(/xh>24{3?^.R1bH81Np$֤gMv'8#gwQJɖ^uq>MO{0^W >h UL|<2복 2hX=)}ICrICm?4DѐRЏ$ QA48/oR~ZuLds!d % }2DC\5dߺVC4tɦїa% K)hWD)+x ^=3=0䵅ѺhAP G/eѦh~7F[Fx[~;f'Ğ(t'fFs7],Ar䗋% ~ל_orz~^LdT7Ҥ{ ~J~ ~aꗸeᗟ[]0?oKODzs=Saq?)uz/ؐ~ŘgM1V'"59.)p11'bHkqs*J]/ -eEyjbAPtszJZ"4g*]Po1PiD?9||q6"1n,oxSVb:9-O(|SbI{ęGd޼ZtN9D4R$uND -@@Ԁ-=00 l@"pȥtT5KWYFɪ*XuԳ\/dX"og 'X(%tNYYs5kgl0:Yg6:']y/wNIJ[5 Y&)t JtJ)䗕r6ԭ㔏_"cStz)׬uuSSV)INn6,:SqJɵN0S~|kN.Y,=lSid)r}pJZgTuۿz.cB:%)֞tڭ=:-krJ~r1669--vSj/_8}̞d{FJW)F {*Ud֦2ev*MndKu,eo1dn 'dƲȉޑGYAfi{vd\}Q}}Fg~d,Y:hoY2ˉvlW߽1!M,^ -@@Ԁ-=00 l@"pho(ŜIYRD1QܐIs¤u=&}u1 -iiQ(b(rFQ(OY%KQKyCd`6%`d`6%% Uʿ$*>-y}$\!eDKXg/#_‚d?,F9D7Ƀv6іTd~~;e KOG6'ЗnZ.Ќ:cS1Nk۝HH&۝iVG 6%NHԵIW>'乕wɨJLQrX<1]5!zItpXOnL5ik:,S2} Ʒu+^#7Ng"^z]Ώ͞N-c)[㳇)uQQ&z`_"$s |gwQr)4) -RHQ‘!EK=)RH 'HJ$)&RxR̤XH" IH)^R|II %r㩌x7^,WīM&Mo&]W54z./4T5n+)jm+x^gUXuw{ZN4$4t\х-t!5tyxVuڶ;ȼ4$厇%Cj ZTⷴʂNH>ZW4Tt-4 -4C9{=SĜBC?ϣuz…k\BH W#gIsYiXkRSLJjl+&6.\GG] Nݧ$˺EAAUjTP7t L7\)mvϫto " 9Gu4uZ|jck12`Lӟf>2{Kއ2[tLȽh -^],2: uj܅֩djYBɚ H(0:DX @n^~EN)8U)n!e5Ҟ`ue>34IzIe̓erBdYs/RΛI/)6oZI)A2()VRJD ɞu,h ͟SʋD՝_$tKzQʟ$&D) -9]ğk91ʓ |/"vS $r)4) -RHQ‘!EK=)RH 'HJ$)&RxR̤XH" IH)^R|II %rTnWlR6lRoRzM&MW;IWWz ȤI6^S[-yx&ǴיxO;id3,%B&HIC7L6l}+&~ji(xqJf@fBjh{&͖kO:CC_I6/}EO`]飿6} ֿ\.81rW5Չƥ9ؔёFCu㏺r;WMMN;=2(( #Tv3rT"4v)nԕ" ٟET>- _ZXXYsݴ;gn3wj(Mգ2! M}'MNb%M~3Ү -w9$jD㲄ǥAa\"hJjh@F Lf`6 pxAHRN0TKO0FYʪup)`Y_b mdP8 -gq/` eB(g8ŸgO5-`]mI)يRQI)Gl =A?=} -;S\ $-hYJ)((tR*fh1VovJ͝׮ЋDE$B)z(i 9&җB)!Y0/"K61_HNE[SDIɂH !ƾF)e5#+JxJUY]P6Frw&7P4׮XPԷd ư=PJzcq4Kyvӱ?6DVKP!(e&JRw!Ż'z~(e)( JkK @ T@  " ,N 7/?H`9RqKϲIVʩYu=s\)ⴳ7rZ,-,cٜsB>fϹ8R(I)ܽRJQ}RzrEw^Vw䡕uSʝLAG{:d)\-)eC )]ݹ*_ȃл5 mO;+LW LgDgnZsC_M4K \WĖ@)>^.4S=_{liIJ:])՝fKJu$ -ݾv|+9+X2j=4KUYl);o{m1Ϸȸ?h,-ZtE dƷMfS=h -)Ctby4K-GS#M9bF'hs9͔pW#=500h)JRTIaHHѐ%EG)ab$%)<)fR,HHq$E$M/)>R$uvxG5n[6+|*f͚xmOY?3tCCG -xӰo[}ͶNPّYYl;}ykqol6IFfiy56B}= Ҕ\|]ҽJ -$ J -h(窡7mXhﶡY*ٷ,5;ijޯO鋟OP#MYŵhwC`͒ZCN RHIJCq -֤$ěT;1SlZl 헟8VZ{t~RP<eW3ײ'Bsb][JWӷ0l:+ln1.˵K[- mH6ǎ"4݋dSwɦvx;H6H63 ٴAzɦe))@PPp@@@80H`<0 8 < |@" RRhXE3lgUy:eY,1#.0,0dQkf|֜ZX8#,1"9͈'XwY`mܻRW~r8Rn򸤔 J霛%yx6{AmعSFL tGAV8%`閜Ar\?`YAs-ҨCwbeAꝈ6E[:Ĵxȧ6OѾ{~' w znj).1+8kK:n~BIvX+5Y\b|1WHˊSNZ;=}\P -UU]]:!0/ \#tJ7ۜDԺVV4gm1|jc7ѱm1}2GpJ=ƻ^S&S^SSÒSp( -8Zz`a A$0؀ D> ,/^. 2Si!VjYb )%dj3-|gc-3X8c +_ZRHNɓ˃8F)ߑryx^) */-S$27uʸtRN䔿HNrʩ NyYw/N):8e<忐S*,+#0[ᔷ wnqZM򩗶r^=[XpucC-Ť&E55M XvG*t1XGJruJ~~yiD䔦m;]=,(xIĮN?!0WYzЮ=培R_ m +NOblm KYbna5rUup1itV3j 8<g Ydf,1a9,kkay1:Xquqje$(NQԕq --9e{jeP߄ S7$2գ)Smb%4HN1}{>Fu: z[Sᔿ[m?߶2Fv'iA_C>S~75&І^.SB/I IIV"+}RӬ;!R8 Nmr-]q[}}byA9ͰV˾?BR=MC}^!'V>Ř?fs]VK {}8%z{Xvw}N{N9)S_nrQ -* hp`x``p'x$Dpv 4KײN9̪J8̳\Y3&HN#93Mp|6g`-mj9GLJyfYHNa_H9 NXr_rQ\|^r߮oڰc=|MdeS$97U}>|Gr_ 9;)7ot8r鑕LrGSfkG|>z{9EX.;lw&vC:idWL555aw$98֢dYGVuQ'>K%M2E;dکC2Hy9$kƻɴ][)~jȩa›ȍCw"L {xά\yJn98}eOǏR7)KnNwG -)O˾g(|3ˬM/L%uESФ(HQ"EM -C -G-):RH #%#)HI1b!F@')")nRgX$i>_7œ€1uNŋ|ַݐd]C?Y507u@#eivO6 O6'7^o;t vzނhJ1ACoi>hLm>祭1vK|7bCPrֱɮ8 9RSmDۊ* 8cSNXw6J?i?EK"BUoW(0=[(kfUc>\tDXɛeWDDL'S#S#Ԣo]xގxw#6 ȼdcdn2{7^))@PPp@@@80H`<0 8 < |@"X>x8zU V5Ϫ89M6G6Ff{IY3 V C0#8˺X<]7%Tݴk')ED)I)qRA sUJJIu޾ȋʛV)叒R{ yLo:R&URY?&RH,r_9 l|FGyV) XHG} Jy1C>ҖRr ˳P̪pz_d p"&1$GU%ĦYS4T169mR}+JI_RGvaAQ&(k#TP1Y"4vm)na֧Oa4|6Ʋ""즾~mܭd PJtKPJݣd l1ɗ>3PJ_sQ -* hp`x``p'x$D+JXzQd%T\i`霮/2)Ji Ji`M,)BTٲ8u*V,ٜVkv]ȃR y(c*3Ei!F`>R6&2;뿓}$(p^ZWEA|`͏fhTg%?ȨKD:r1`-b%>zt|v8ppcJ虢8{錱:B5))5ƚs]kχ&152ph)V^;=bS'?BӮn7aOtFCNcXgӲނ-ƅvC~6svVKcR -R0r 2:8SSO)pJKpJ[_Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕ:Ĕep<>#,Sq-ֱbN_LR]myLNuc]VVKNy%LWB, ?*߲^D){-%lG)&abʉkB椻/ -TJI7+m rHG ώ?yȧ^6|\@rW= SBk $8+Z1"uX1-Ytk5Z97h.}EPtʂ=L Gh22cs*]sf}_ѰV2r-Ɩ""{|w槶Zơw#dΌw-wRNC)dPmĔa(vFR -.4P%P5`4@ t@ #&38 $}>;RE*Xe?dsj8M.-t,XB:1BT9}sny_DLL*Mi(MI)D6RE)?}jE!ȡ#`JMA)I)cRQʯ$#)e(R~u^"ޞ<\'(e+){fEJyM#kmd=U PJV= WNk;6Ls'Yn{?v)NW`g#(@w AY\cW"0nJiȲkOtW+>04FB3:zq-cdǂ)gsoBf=Y!9Ro[꿑SxDO>&OJ9#m%R -* hp`x``p'x$ϷJT.GW\NYª9upe}v|ִ,QJgaLqu(<7J()ԍ+fߓR(E-){RJ kU)o3+Bu=q2ƠJ?>{g4RRzg4RA矜Rv+-:z'Pn(%&nR&GsmV4>gގ_U)+JNTW 885-$ƦĤ=nn|Ur4>` }^P 4}*˺fNDh*>'T?O1zRr[Rb̫BJYz2O<۸q -)#nRg{?R_EJ)̩GJ))eaNJ)D -@@Ԁ-=00 l@"pf#Yv[*+9r ԰\.c3לUQJ/kc%r#ZYG`J%g=Ŭ],$\#) JVr+(e{-窔YD.ߵRnKd1K$$)e*RR$KJi J?U)Qt5+ )?e7pӚK[ў۠PU)%&tJIr$ -ZD!ɚp:D{JRl#RJr]#Vj^;]}PP[/ 3~of鴠mV?R?a4 fê --ƥcyvSIqf..jkR%YrInݝC)3d|v5PJSPʉ$)@PPp@@@80H`<0 8 < |@" 2TaV9ȪRYnce eD)gb I -;mOIJa ]RRiMPƷlt6(AA)|!B)?h|h…S;,rrLCHIv[J& *㜩qqJɮ//r H')TYl;]: ('P^{RRmI.wJ}2Xden1V<9n'l37nTԓ5 h|_RrOxMCPJV2?FNR nb,E(0:DX @n^~>_tQ,)FYe#dS,Ka55vՍ:0KIpl?ƚ[9kj1:;9srcBKf XIDHK&V'hKNƹRccqۂ+Ni;cto}VP<jɲ LƳPf!߮]K z>%+l8̃S -) pJb^wz)R}`?MVf)JN%R -* hp`x``p'x$Dkb:;Ί,9f#ŔPo8*ŔI)|3S"]&1Ri| J1}>RA5kR经C)Wbp;P׺i|ꥭ#>gӾ;V= Wh& 3՚*4=q1d1#5 G9wn|QW.iʧ㘕*e[RlɏP5@)]siCSmR:f~aX+߉RƢvM96xVK9q(%oJ}JIC7ޝRt>|A)ePcRp( -8Zz`a A$0؀ D> qѕ t, K\f wfbΔ֖ cu/"OQ嘢kS1QJ\u^"i!V\+hr/URsqȽARsqȽA_8suqkrb$NXSS/kĔk=S/m[p -O?hæ_zALiր;Nc85.-pc:\}OK{NZ%nS2 ʇ#T R8 {4B$h?R鎼w>CBDX;)^mBS(Rmӿ"zr4pJ3)_˟SS%"Sv=Sp( -8Zz`a A$0؀ D> KfA^Z^@;ΪXuT,YVsfV?L4dC%,7,4rf=wzє/ꖜ JNGr7SJ΋S+jg+Sf9'>RNSH)RNSFp:h͛w?)#ېS6{ȧh}h>8e-…s+j$&.anWÚ$$TvyNɬqՕI#SV*;Nw$(ʼ{#Twյ}S"pc!k ?T&ԗ4:wZw#bSҢm⭖f2CڒW%sdS2NxYd[79EU8N)S2$)@PPp@@@80H`<0 8 < |@bUqTGϳbNYũ -8u9,\-fi==ʤO gcnƑ=P:9g&'qoy2o+byIrJ|) N䔕1V;%#>r|)ߓ>>%)i)|pMS>柋)>bx&&RsPʙD} +1J{B#PJ{ȧ)'7hy - …Sw -58)1%ΚLM SΠ)jq<ڶ\c9ArT`JMg[󌆬{a5MPʑJ(e_DdvS~$fsá[<<`byN{ 2:[\)tJ>#)H(0:DX @n^~8}y 1V9˪YuVS:X}-g'1e>f=kkf.:Y1uy;O12ra#D-Ʀڈ?6hVX?yJi~(ev+C̞'Jy92222YLR -.4P%P5`4@ t@ #&38 $8#kOo=u#)xe bM93WpbV cu}rtpjn}>R$|'RDI)YROܕ2t:'cݘDfƠ=_lj҇;/u>[*lh1p02cP)>i7!"h}xsluR{ZhTv%9\Xgړksf}>RMtvqAUv*p Дdص*]+FCa1n/i}ҷ;βcE[-])9pJYMyN)vq0=OV)d֧RTwp4P%P5`4@ t@ #&38 $[YB͜U1fV2y)f6QN?f~#,_ϚYK?k`~:kXqu9ֻĬ{rSvH)`Nri4E)%nx_#[raiACZoQ>ܵ窔[:[6m62^'?HG~C>ELyƯ;sΏ/]qHbcW]CI/b?G$91 J!6;?ޓ4Ć|zg窡?{8\Y^rHGm"<' y蜶BC?X.#(DtNB:SXkgM 8Ng -PS~y}I?ճsr_:rf+b([:x+G.CoGkJ#fTSԷ0߷%4bNFn8ZZVSCިϚGI 1f^nkz|^.7}*w,E̙o̝Ӎ3royo:c̍ Wb4P%P5`4@ t@ #&38 $r)=QTc+Ƣ&U Ώb(Mmu=0/u6 ىV}QPDֹG>t8y_M2P7uZ:, .4P%P5`4@ t@ #&38 $N~XU,1ʅ5;+Y-`uݬ3 +Ofղ1:Xu0\'OJFI)_ TI)FI)(%((El?W#嵉̇f~/9_%|CjhN)ЇsAw-N86t<\/[z򩗶[= -~g8ګW= kSqIbKs)nM;b{ʜiII11aBq8ui␕) geO3y8J7FC^cXY-RT8?"n 7j)NS Ȝ wY>wiS29)r-9)89\"hJjh@F Lf`6 pxAHR9jK9E9T:c8nմ*2,TpY>֩UqB9GF{Yqs7q[NKN mSrʌ,Զ/Ŕ_jHJ9.^D){јh 1{sڹ+,3(%j(嗋+1 ,2y>Z1Uf,L\{f(o'mP'iM]=.SMrnNiY]> Btm'2SS&+81NVx(B^9rc -GO,q‹rmwuO_k/|tL<3z>K kYAtJz)Y?7uT, Ȁ(0@ 4@ t@@80H`,+8 < |AgɊb 9Y)aSrZ3YFhTfFEzsI2V3l+cc=*ȕͪs*ؕ*O۶N -JQIA)',ArrCeӛC d!}t9,Ҕ'e) S) q?(S(E(~YlS~$LK[?%X#~ЧQ)QɮhH\ΑdrDǺܮ =*>3zF8YI;>NXwEۧ9EYNU1UYyNsCvBaCX9)uO&[""wKOeLeX{Kp) GQ8wB4BNk(_d $RNr*Fh 2  - P Ѓ0  , -NI -21{yʑS36d{Vd{" mNY~c#d}$Sߠ_7SzK4,WDD6:#Rّ[2cv#dNE{ -)]v֮Ly -Qh 2  - P Ѓ0  , -NEY]*i%#kgȶ t~7:ۚ(39QDْbNLNvA6;*:SsEpʧ6Iin礹8r>dW,䘚Cjfd^kL.G諗#²Q/e>ٟ06f ϒ;:8pN)'SP~{9NE5?)S@)9P%`hp`X`V`pxăp|FZ *EǕnzFRi ]J?@S>!=wsQش$+*OJ;bI$Nj<*SՆܞ7(˵8<%-9IzFwgN9Sc')NuIJt֪P?ԗ3lے}"C#"NRƶDlaW@d?5'6~*-J̆NsabXZ:\,^K%fV|+۽ Ґ Ґ Ґ Ґ Ґ Ґ Ґ Ґ Ґ Ґ Ґ Ґ Ґ Ґ Ґ Ґ Ґ!w')s'dYq8ŰWy6x5Eqj.'NՐmͽqZ/[gZ^뜏sM?uy }x:dwC!2nLwˮ4jЮok(,7EaAR6!?zSm Yi6AC\==Y0G,Hm~OA;@ =WKsIkؗ%Wgs\<@ -d@@ :a @$0Xx> G)e%,JͲ%V*,3Ī͚rV[lֵVrh;*0YE=M>̏Rgf׽+]o]/TȆwPC'!ku;].? Džs/tDA y=]q)t_}ٿH\t"͑CҜm5?2{wס'Y:bb -Ĥ.rɜKG^~n_ZrRIfŮUErY桓rEl\ٰ5Z]WxB0Xv|9X?[G<۾%| uƾŜl#-m8"?gOI%ծ?jqHC - -"^~i\W%.^y. ٻ87ٷɉOc~r}4Ң# -E5djKs]'t.E70r^gA(%6\$CQIIdo -kk?̝QRNՉdiPW6bK| PuS.ToSOQ'jW^E2ڜ?*1Pg)CPZH1wG2TK" e3g5cHE޼*$CmZ-+4PhA `v'pxA-ٖE|ZviZsGE<@ -d@@ :a @$0Xx> \`ԳtY:f5yf8dy:L-峺aV_ORC-#,`4ArfR ;5{'uOܝ_/_ |+~ygda믦K[r{ZỞ9=BYw/G^':< Dv/ό/w/O=k͏pzb3W3Sӓm\JZkKNJrtWjQcCկꗮK% t#ˑ//gROr)e{(fmٷR9%D}IObXғE<Yq> K,yUR4?TU/9K> ->z~&/?_ Yx ~//x^ Ȁ(0@ 4@ t@@80H`,+8 < |A RJ:Y:,ae3&y?(7kK;9jX]YEEVdZdk)ef˕&׌5gL ;{I%r:^KL0 _^(Ԟw6%(o] ~Gq!/B/lH}t>"_z˩bh+SgQ"n%>G/@_ollt:mKN%C6cx[lLۖ˥Sү0STT|LuK6bGNp^S瘜JC3 .S7gO:} -oz40DDT}Ɔ _u?IqՔ-媮y;xt%YUo=2a*dF#J @@jZzD#`Xp \n^~y$U,FV+U+K'UF3"[UeWCz;c!N,+cdi3q 3|%UyUކ/orRhm{8}A)o!}|,j[#JSkemrUuB_w6*NhƷS+AWJc5 J78iD.JyPJ^Jљ? =Gq%Qt+LNޙEE QqEݯn[ |9ӣkRhNun5exdn}ĮhIJC&Kk3ٛLܴy*ej]zX8\"%SRnNʲniw0e G.OteJ!e9rrCgG龁WIG] [XfC \_̒qme5fyYeQ̤YdLcʬ#~1VͱlْoֲI[dfW2ϳ2f y%m ˆ?m셱v_~+7w- yҐG.mHIoqh$rܪ_@&xtqEJYR./+Q=5?:INKOq:cmKr-9֝js8yGTlZ]{RxTA;4ql{YZ}%-ide픷oy')@A+ŌSut[y+splhgX㹷%Dl*28'b"s"kV=V 7qSlzKg)ةliĻtRk,}E?+/Q@ZBM r$(I`HP!AK= a$` !# , $IHp"'M/ >$ē@y8Id>yz2?YK&NWAC>hh0Θc,3>kF|Ρ;\^"4%qv4 y $h螐L37hF6%( ~{Q˂A4Cв!u s_J:|i9۟ZaKл=S9Y}5Plj5?;q~Ĺ6\dΖ䶹RQ+Ow\$iN󙘾Փ)dAI{j9YɁy=rucC3\;zN]#t!TxsCc᫩-5Ǫ({Nauur~ȊsgiTN#+(Ө*O~ S'TNM[K㨜{}aC'Q9->K.EvLfTNϣr fޅK"6S#9R 3/PrTN΋urBTNF*'<@ -d@@ :a @$0Xx> aL75+cg-> SfV55Y7b_ /H3LcfYKٺhwihGd{m3ՓV/ -y{ˆr̬l4A~2S$|׉'eB /܆lmQeEL!y_ -w//@)=S/mq ] W_AxBmr̽\[ߓ{b9, ,bO!=9Fvݺmn@mZܐҖRHRdH)""""E"""C)"ʑIoJ..ə')IтKp~pZvX5͹bZZOWM>"zLLچCY~D誧ُf/5֎>^nn~q{XW[çg,uɷw*Ę3=y1uF媚8bswUq9S%E*m/&VyfT|7vV񹽙[B^ 5 3 -@7 -"*5QF;.Ό7G c*fQa[«,[}-aF{Kqu1lu3B0Ì&--[[߄Tmp2o Ԥܟ4$vOZg:Hg{zggFr{IYSpOūWj3*vjKU8mj"xT[$YŧO̼"_{5ع{=AS߿ܽWwܽ$+{+Wuܽ꽚5$ʯ{<ϋ_>_617ccbb"p&F%E''1\/T,\KD ٻ/]O]KL< ~Y߇ѣWGJ`/5b7z*ok@u-fyF;mKOaL yѰr,جe7r6{+?*&veL};Tr{[}#[_߿^ 5 3 -@2DV:߮`#(}N*8˘jsK93LcD~˰-Q\N_%3by&#?LO}ت_/v/G*/Ħy~_s|˟(WV (~ISl_jڂ倃m+o3/N U-0Dw˄>},7Wy/[ Xc)SPKr3.'lr:ׁ_^m2`1| -#1u0MƩTrV#+;atL}(fHu'f1SNO԰GOSgT ZPfz;wU9Ɵh]5M8[ֳ877][NLw)zI\y(.Nv⾲O^ _E(jZ:$ - EpV6 -; - - -G!QxPH(d>((.JDTz^MMBW,^:]Ɇ%qהv 2vi#naIS'IK҆VR樂hZECJ -k7ECtB9)9ɬq^2g{5t|ECVC7/JRP =eNV?ݷZ eiPC0P~ +=5i Fb NwBl((s8љ%l EG"9EgZgV5n |ihILtTm9隞 uSÓ{OcTPjr0>X}@e|}f-~4wPc?h%ܾckc8[҆sEACwݿU(~@C/5RfI."[z>h]4@FAECAQQPQPQGaAaEaCpp`Qp(x -BFC]OhyfЫu^rVd}lh{dSl@@CmBbeQ.dv-y^Y^O,Uv[UO+4dECO+4MЕ/}VC)Z a# u4;jvEC*!cjO>P4d R )^PnjDP :ACjAH-#TCLB5=J".~Y&$r4oj/yb+1|ncq1rP!%%'kFNNjՒs =ht[ʠjm׃m-4C5q${ǯ֧3TU_jy3<{l{ăwo :8t[71f3}y̱X7 Swzjm1ep?%E (ǰ ~,=ݜQ=!wk3ﰍ<~)2~n /v43 ~QNT< mT} -mTʝF@rtpC8o~$q)($F;ȳ΄>)b\;1Y#Uǎe݉7V6V=SjΆk>buu-,K/)zUsi%'햼k!mU|}Gk`9VU`\G0v+>yr?,ärLN1W~ -_oEKP@ =` 0a@8`u6p,< "$@|@<ХtPx&MtEJDk i]6M2Ϣ 2SlRMR)[tZQH)hZiLR1R~qpȒR,Y~D)l-\QedߒS1ɟ'Sn^)[, SV%H͒)Sb\ N{E'*T -7N)8@Ot)ApJ5,btHIltBb3ZKrXgbIbŸ9oU-XQcC3>7BrS43ځѣd#F9% &P0bSa{NiKY*=u -P1U}*GpWfE6AxpP4ԉo݃{ƦĥD'ݍݣ ͠Q((4((t(H4 -= - -# - -30(,((l(v., -B@!P(|(QQ-)/1% %M-rd.ܦ/%5Jb4B<"Ll=+Sfgܨğ)Il4rs‚hHT44h(,MZ* ;ACZm 54AD}hbp4DžHJSb):31Rd11&vuW[kwuxZ׮֖tU0dZW1CՑK#:=\ӹ=lCc;l=:uv@TW39ށiuh>BC3:u7CTg&.LĒ:̓QWI䦗*1_ aCWGpqԀ:h@#`@X+`8 @d~]J3Q)J]IiZ)mkn2.SY\LʆQ r-Rl Q|%LRb3>3J< j\ZU#R^sR*D)/l߹ _6R O>xCPQRVQ —Q/_:K:'}%(W J0( _)&Ƀ>dbAe_Ώ<8z_RR;EKġ&Lt3gĤ.!+S T6`]'Xl&ʦ lf{iʦ ӵ$Fvw芁T}vFfr;󠲩I5I<1lb֎Vl_>bF;)Bkk@eӳ*6 -#8viDkkʦ *:fjJA PQhPhQP(hzF&fa(QXPXQP0(((\(X -BDA!QPģXmi/UW˚JY&eK7Zٰ5ɦeQ ɠtٖej${ȼ*K^nVd_3J +?(wHtmCl"O|NesF1J^CV6S4h( R(|"&)u?ɥ.&;(zҗR18A)s6lQeBz(Sq5H$LݔIig DŽSiU\D)R(eAR)'m Rn 55Nw+r(%IQ -D)¦LM~SA+[ J!zMw;A)D %4KAJ 7z@)wܽ!?ą^'|@I)lG1ЉMIb)~9 4Ks5JMʪgYMui[*Kֽ{З6,kKWY̧2,a A4gDV\`zi-hx2yr4Kà{(ĉ@)9C4pRVJ(JKP@ =` 0a@8` `;\ px t)Rhu#YUbr6ڐAkiS"4]h[Lfhn^, bŞrZtVE)5e?D)J(| BL3 w"~zSnRB(e[StK^)o)N-ypz ғ/SF)r3))>oʔ[=SL-E8|Ķ)kys -Dž.Sb 1X11ɧp N19d6u'zkWwcp;tKe ze*&Stͯq=f~a7tK}>S]5Cj:f8 -Rs-5CTz pWM8 tKi-y`PB&.AT_Q\}f2+hBBBBDAУ0000C‚Š†AaG@BP(" - - ‡"Źqe/Q!d͂mdZ'y,geSlCR/m^fIOJe\ -^ɓxȪtK_)*r(2}QگfÝmZ?.^ }-HihhHׂ6Ɋ{?Bi jz E@i3 NsY햮z薾y!h_O\7ynZcfߔb.8` +zJ{[ڪ#=yn۩ӞqdU(mr.f(m]SǠ~J(m^&JQKP@ =` 0a@8` `;\ px t)>Aݔ:o[jt-9Dу2̓%4I3PԋeҖC1}rR!\JhO %e~[Ԓ5W GZ\):.;J+Sf:%Oq>Sތv .9 -NyoE8y^oNɛ%ǃ~&oedBZ!:0pJ!sF`X!%:+h Dwlt4%fgϬ60(mzJ1ufLStI(mjɚ=mSJʇK5}~ -J὘u $[?ҦjJckּM}ByV!J|ܓ!6E/4r{Pu-UE(jZ:$ - EpV6 -; - - -G!QxPH(d>((VHx8%"mȺmt/j%T5|]hd+E^uTK9r3$ɞI6SjRJ*( CM)m.yAMP913'/!@^ yҦ26w)m~/ZzvאJБ6P1/,_UAism-69)h&B&9V\yq 86Q rD.FpbSLNNqb q~ԦJزVKN5MUOֲ#󬮦%t׾'!cFkLrw[y{,aAiSݒP.m+) 0pd< H65PڴLAi{2)o@i[M6P f%KP@ =` 0a@8` `;\ px t)NeS\JM]L+ICeLCy6$(Sib);(s)")RL}'RTJypSJݿy"tiڱR'V)VRR.k( JfS\B8va)nGK-('!3A)ۃ>zeBVRoCKS>JWDS4=J3V=Ѧ*ڜ @p.mbھJ]Fq-4?F XH{F)) r5(J0R(ţ( JV J. e~Rr!rɐJ#R" NW[Y"%PJUI(p(EAJc"Ŧ+zy<ՏϬyq!^1(3 S+KIq I)h6OX?RsXU]Rd;l7Q9b5y»ܺ!kb}~!oNkR[9k[ _YUAMYWWB)WS(.z$(o*?ɄFg*J_czL71OH$8c< OB^dO+'b YUJ_*UzdGhKvuYdm KgqzJ7 BR ge5ZUJ_E[iCk{OJ*eivg,5RfL1W@)#RqԀ:h@#`@X+`8 @d~]J+SD 4=5Sd9EqʐF)8eGORJ e^޿SlM"QLJ՟m|E)^eD)ˊR~Q/l ][n䔗n+z>3OԠlR2]mPҧ)'LWft>qSPA)=G92 NQpueJG|蚇ptcB/ qpJ:O䒝9SX> ~;^}X5f xȱL ˆFL=S~ 4U[8sӕ<ڈ1zy!UQ̐va_L/_L K{MWgPY`։#U UvZe,U9 -^l]3M8[ֆs%GqΞ.z+=4RQ\;ӉKJe]4@FAECAQQPQPQGaAaEaCpp`Qp(x -BFCbE~J"Zn_Vxu}⥗$}d蕌y)kCcYېd۠4JS䚖/7&{eN$OWpLת|5Z6xh:^ %!EC)mv)PХAJ]2wKhےcE8~WzL萈|`>nO%r2qkKoYΟCq;&ËIN>p&҆xtf9ǹu M-`}uڇi=ٖq4Tt:U;xK?z#AC^ 0;Smca9'AC}阵r4y4h+9}LwV$44{ 4Q߄}@C{%t8 -FGa@aDaBaF" Ž…EQ(D -E< -?N>)y^uW'ikeݢD6xrI5LKl)W2ϣݓhjiJ{A_vHv/;%+y2/ƻ'5Wˊ !wO~YWhȹ)ɯ-οR{}GWhfEC(J ݼ)fnc :}K%hLZYN@Fp[4AC);4Ӽ!sޖKDP"9G۹)l[H Ĥ.9j}6vsV*u]/'XM} q%XTafp}qei\g k+rXm<`&>ڱ1s|V >Vۅ^1'C (%谚?B'*_% t Ѐ0F0 V0p.8@<Ȁ*QJGij)mm(Q -XN)s갚Q5C*HюlʕF K󭔐MeZa}IQJΧ; -\D)(J(Mj~5jj)SݱѢV)U2_ uշ\P_} kz8_CuRMPV *}b*AYlZW*t8 -FGa@aDaBaF" Ž…EQ(D -E< -? -t^TLzՅ^MW׫˕&ꗼ>5{5Aka/.G*Ue3%t2y,#KWl8Zj4t%~r:Ml>s ʹ;xes@ѐfKhE3) 3o83Wǎi H~n }yF"\h -!P? -5g$u6 ^8sTbGUfqԀ:h@#`@X+`8 @d~`uy4MLPJ7L4KkhCmMyye 1癠m3EKi ʤy[fZ^ʳHI Ԇ(_3^9%/)JS JҦT}OS':V)͞ yąSΆ|Ӆ:'ow.V9 -N?!ۢ){ЧᘹG&2֘O|[ky1'𐛘8ɪۻXMCB%)EBaC?LssAKXږvKΫSm-fMgv8Z?>Wq6ۅb]'ot9&|Nt.@ h-H0& 6p,< "$@|@<VvHPxEP꥕<lJM() -9eeM3=v4Q):Z\+߇]zW5Ky&%%g B)⒜I lBrlLlB~>jze_B#SnbV]jj#P’Ot߽tal^k,[Mh_% Rupe`,}m. 0w(~ R28emu: uJc X;uJ@8e ?K(Vhݔ&R9̥~J_F&(c3e})Nl4SO[i,N[ݱR4S[d)aSV`N ׿P:CcOJ>ɺ|D)Vҭ( JuUʱKJ;{>(S"W5(Z vj6vJI;ěV!V"i/u TJCnCvLyl%aPJMvKy:.0=;;ݠPT> J)yva,+LR퓠A)sρR>U -\"Z@ `L `lXx@DH xO"ErZ3FkkiMt) ]6ۗʔH3vLP4Hqi4hqTƭO5ۭ," RVG?;Fח}V)U37o4}QO/+RQQJBQQzŕ<]how~u 5hA|qY2FM9Jyȸ!7b>\X.yJ$N1%ZIIq ;ÇU޺U*sGvYLL|j3Ynk?F6ofa×ۛ[)Ugo ~v1:u|؛U|/99>rUQK*[?;SyTRyܖuVûƷ+VP@ =` 0a@8` `;\ px ZQJKڔ+'B] -ꦷ~d*~y+βt+_ uU|W} A]S@^UN__SKƽ2qŃ>'.'̱;<8w/\ LN 8yNb'Į?wlzN٣;/TatO#]8iR*U5b|Z3F7}(f80DsC԰N᝕Tvcܦ*6 }ʾ>re]p6I;1qfSr.>؄˓/tʤhBBBBDAУ0000C‚Š†AaG@BP(" - - ‡"˷DRISM,%2MP5NzMY^sEKhrQ/㵗xe^WyN/?$ 5̆ܣp A.s -9%'wN{}!k rµ2iǿ_|4t4I'Z-} -s"&}h葬5Isn"5V],)(vuds+K/Frr?d>y,pϓX햱kAm( QÑus>c *0ijo*xż70O^e&-1atUӏaǕD 5 3 -X^ fӺ\J?F(c.mJͅhL gҶn)crPlEM+KdmoY JV(A)8PJ÷do7~'Z?(NuV{ArQ{E2y4S 7Bic[=Z=8eLPڼ2AJ_R˄-WG?|sC8s׬s^F'A_F_%ؘb8>)Yr\pDnlC:%ى/0x?mIs1ds,ToJwꖴƂeS·暅gZ-ց3mf*{r{2̕=SO=qH7(d)}cRF68{bJ,~zj)p8@j@h@4 0f , ` ` 2?4KhV7њIJ;LhGh}ņ,J)s@Ŵf({/Ȣ]m4[Gs4A C{2is)Wfuʠ N?+N)MqJ˔G{|dP8JQ( Q#An~A8pʔ_A)/~ݒAP_{Ч|[2Atĥ7!Aa1 "''&8y -8@"$A^Tw辣gWgsGAtG21MʱЃatW[gcK'橶a*~1KڷĘ3}o3*ɒ|VJȝۋKY*Os*3*o[zAa^ 5 3 -@Ru ŨgmNF;F#Hz8R?"Uz< ~)F{l5L^8 -P$p ?)GHi:rјʠnKk.P 6 H/M:͜tCA*~y6 me/}wg{A]׀_l|/ Y:!L)Kݲ}*w~+GȷT_,kKlylwR'Ÿ r9(rNMfZr@_kvi'/S=.ea'02*Ǯַ| ~9|]KOvsK/_+RM;lEW/= *hڂ,%-{+ߙҦcbUR% Ό3_zqԀ:h@#`@X+`8 @d~]B$>}>R3͉1de$=[cB~d//Ռma#Gikaˢ(/JȌg~KK.CgUZNuz 4OZFoq%ڤnMeR:TZV~b Gs/-W>EzcJ_˴|eG$B6/mW[Ʒ"!Е+P%P5h 3;p@>"$ RF_Xy)JlqVeS4cVfNt6}0j3K.+ɒuX\SnB?;;mb~9)Epe7̶a -/70*@)6(s2#H~9qhͲ/Qa&Gr]_fo$}*bǯ_vJdh;9B}O\X_#Iq>Op};P8C7$74-#i*xy|z|b&:|~%~Z؊~{_]ی^/dzc꣆qE,/3/gO3_:ː_.=$~y)_+h_w/g>_rr\E%/x^9( -0@ t@ L A؁<?A$dYy-KYѪ:̪+j+Y5\s_I}4a4Z(c],7eYn0l/8RK"o$</njO^eH84_f$|f(Zˌ䗏ìE/K3K[tP7WKKQ?ɧVFzPdj /msqGO+įZLCL& '&W+=/5=M{o3⦧2-F; -w/[tҲ~i: B2t_e)@ T@ 4Zz`F`1 8< $0w+?jYEfX8ɳ1mЦsmvq_JH}4n̲gs Y;zX&|6+K/_I~ I~!_6H~ Ez )t_N ,"?Qow%Q?oAĂp 'Q!ד%K(Lx3Ýs  ⵿t \wg}4c|y7u-NqSUec*c{ܺ/U[ Ʃ"Wd1u{כ2p$votԏ핹>#HZqP~DHo&TĮ-U@ϟjdI;?}I|qRFG -(h@b `8 px @H)`$b$VvFսњh-Zkaцhcu4L%J)L2l{%QzOC4-TGʣbN;sR-/ׇQI)Q9(eMdoWQ#K)MBl,}^I)¤,7IJ镔r.LrG'}_KF;(ۇw#ea9`=ekҾ'B)x}8Jy:2ZkUfl<>OjF3#x|0ʙLiG2Ծ-VM:INqSV<Smc -cóJiPc6vm6 -L7kw[?`۞yJi< ySGJe="\}CGR;-P(rÕ+^@ T@ 4Zz`F`1 8< $@dFQ(Ug4 =vFCc#.b2J]}v42,D{Z>oʷBˈ̒UFg[R0JyEְ[Ra*%\,OE`,eM憰`NKߵHꌒRn4θ,J裵JRnRt;('YOEVw$Q'Jyd낗p .:ڙ x]Hwtߪ`7 -ETs8_n]LUй -TA5U*S -ہ*h&TAGV'P5zSIϨ:KR],ETy -*-=0#0`qX`@AJѷUV>RŬUv2ɦ)`V[j#GYCCVI7f2`s\,0`IVa0.n0~QI)ˍ_~/ezf*S4cWR*賈UH_>X_4|HUcw(s]bXQ7SWYrd -P↷7O_Za/ nw'ACg:V -ԐǝsG7L/[deKGwn/Qm<&SvoRsjeeLMKdn~ya`pqMRrmQC[dqYvw>?.'{&dm{O7^'n9-O䁢.yr4< JT$IА%AG FL$Đ`&! , v$HH #OHB$IH!?$?)tSosG9eCEI-N1gcM9n,--f-fh+2}86)lwݙ(R J) sFw^FU@)c;ӒRr@PP `@ 08`,px~ H cyiNFEiuUn- QEZci ߎHv3bx:h~ji_=fb7,-ܖ_-ܖ_Xf=MWl_)}HS\G%YDE`1Qa wo{e5sPG]M]3?;9Rk˿O>SJ~4e$M[]|HXp|ܫOUtgZ*I&x;iJ1LkN)NA҅4e/Upa9\$ϭR ՐS`6NM] ֛bJKܖ ۟ўs$1)Y3Ni; -TDRua|ҔpJpJU@98}8eK)xD9( -0@ t@ L A؁<?A$dȣ S4UA+ie MVӚFɧnd ]q)O usN]4L{.)1Z腿RzftOI ^f ){F/SV7ɧ"唩5ҧa8%\LsFBZ0MHw -HO<g*q{ |(m D?6'ӽ\r|L2'y2eZ`L;U*cgjs +C>c덝כ^+.eys=iddW2{wތ̑w(WVWQk< ۮ+>}*Jc((鯢#O\ |^y -*-=0#0`qX`@A -KZlY+eS̲vV5aUOFi`V]M?d5=B]Zfl~hg]Vn4Y,-gUxn7VD$QJZZ^ezjF{y;9Rk礯,J.A0~1-Kk%N[u?Q9ɧYn~0&_z_%\;x"s ||(=9x2LKKHuz|tS{ jd Ylo*{Q|I[U׎&zF&s)ƃM2Ly!&GL=Mcj۷E˶N&މb+&GǦ客C\xS{r>Buhi;)}I(9-O& uxCIHP$AE Zt$I0`$DB fH`'A <  >$$HH"!"՚IT&ΈDMN"S+jKuhN4$Sr<2&=THfEO}"($WbdW.4v4㈵ShYC/MTq0@FIC1RwL۹E~xtP. YzPANo yO>)S1*p,Zkl\qȥ7ݓL BN~'ՙ yMln>=+EHs:';'Ӝ?&/̤rFFZUŇ$ͩɗi'cj?^>4glJ}i9MMM7]B3Џ4q;ʹ4gҜ9#p]Hs"y0J87+Fӊ4p;ҜKw!)<P@@@:`& X \ @2Hs7:ZgY"Uf[Ug, -Zeƪ/ZU@Y.b-,;`W,5rGYY+_ -YeŪ9N)͹7;N˽+"A-_B"wxb)R4 \#m#F;'AHKBΌT9Oq&x<>7>#{+_:շۜ4ߛ)7幩N,-U}&cwj[vuc*/n1d瘍-fWd1Ygn9גdYhObq_yz߬͊26rZ;X#K*zI+KH{r@PP `@ 08`,px~ H +O1:2Q2,F0v?1&|۱iv72AUHshOXƗ 1{%;%dIN-,)qʑeVܷjޒ%դhn_蔇$Rofw Jc^a6[ǝNJuSCj'?֐~<_$PO>> PޓIT7P;sp휲*k\0$${LP /)zHv;C8=NSVVsS3'V;Vu)ubhk3k,S -י?NmsџmSz!NGFN^(P]SfF_{rWR.i)xD9( -0@ t@ L A؁<?A$dcYzVCҊS)ZBhM!tk]/ qJCB֐4Q9G㪤rSH#LˢG/yF E0N9+9e5q_/S~J;s6E~T]_+GaҔRN#BU~ w(OHSn4^Jy@^̂RέOQ -PWp7Ҕ ^µR;ش/'rN>Ƴxyǧ |"rFK!M B)=͜rpojح?iF9f:V;[7u^=bs9x r->ҙd?|>4Gt4%(b#Bó_c;r)rPJ^(^r!I)xD9( -0@ t@ L A؁<?A$dU2|S!;]͌馵] }oRiK"1;726Z蚻ѱ5ҕ֊ZB_~'ayY$3z} )U'CGF)2'U>0J|&~_Lb!*WA)ٷtyL$P_lG|*R_n P]I%A)B ^5y37?wR7'/uǧwb33>! 5g.ɤN}Uh*S]*iȗw~Uiȓ1Z&g -{^fh]ol>4b*_.AedR]W2{g%$5DV*96YA*)|='?I o>q]TWcr@PP `@ 08`,px~ H uyXyK :ʪk6+j+Y]Mc3!J.k?: -l,Wz:|+%d]r[ͭ0~I~Y'A? -۲Vy*s]/-)g,Rgar@ˊ0~ -KgEQ|0c˳/޽g'+zٯ'y?T'׭MP/K ^W_AxBmtĽ\[ߤ,c0=$prHCX뵛w;w?S"R)""bEHbE!VDDdX)RD:(>Yd-'Mo]t 7\nt_ߧ\S.:-=57H !qcGLQ/J׮~Օ7\~Ea5+/үwInOㄋܜŻ=WrˉĹq B#5r;9 UmބIa -@%P5:`&, X ln<AA2q*"e+*fERh]%C6e,bgkV0zƞK WBMK50f&))ySh&_B+7}m DȾ;%/?U_&5?i==wzz?3J2Id?h,OJ0+O7囜M<-Go|M~|SP))D_^G/5K0<嵾ʵ׺=Zkm\VVtU|yCg\\Z*Ьb'gS\?`lOrԷ){=&:<GbHHHSO:F)K{o~Kt]^N)reC{ȓi*ϓeoeچ<- -}یcL^fΈ=2ztϨ?)5;'w7˝˹C= مr>;dBSrC~*{s򑽻6)P@ T@ 4h V'x~A B@JYbZlʖ8,γi8-'XCk̎_Y.űq8{9eŹ+ ĩE9ejaԵghhAFJD,=bdic ز19pEd_'㟤OS&I)2I) -)FYtg>=DŦ2/D~*\/9ߤ#d%|ώ#8u]D,7;2I]{d){#qC@,!q';UQ*ݪݪ;"?ݱ4~]_?7To5 -"uv+;6w.E~!?wh<$<|OsN6߈CsRF}'T w6!ꈥ4kt:?B&iR9?EtЦ䂵)nxNًA4`z AC{>S?u=UYqM8c玏x.->=-!͑px +1>59 ivLD*D{]ԡ8H*۬tk.tc3j}ǮK c,3ض{\0[lo/1"Tp@޲՘)Onrvɔ2UWe<|p.͕˴Uy2ݾ'׷O= 3͘ճTq1E,6vvزѓ2[ދs2{yp^U&wo使GOɄʇ>O6 ޜ<%` -@%P5:`&, X ln<AA2q*ͬ$S,V]iH3m-V֐g<$L1(-fmyqBV\c#'?/rY_/<}5F!eB"7#.eYbߐ՘7OK2=/Ai5f&jdI5FKy5;gV*'o~y&eDwg}!%)(R{oBdq+W݄3O $;#Mɓ9S8ƃx~_:?o[Kv۩=y2_vTc^n_>_˴=@6x6SNO昼G[ -6/KIsrnpbQsr@uLh*_gCT_6'=W(*@@b+` ؁p <? H! NEKUVUkU-t?Khmэon1 n1K%guʎl -rk`ݕgWnJYti~$I~Ru׻!~9GE/]/=/cdH~yA1_26/K#ri6KIJ~1m_v?SL}(POw^SJzd2e宺 g/B$4W<B#v'^J_fiOó+~i -v*M/G.S////i{߅_*ᗖv%yi8 }zMdvk_y~) ~i_/}o/eKGK͝KKK˾.%/P@ T@ 4h V'x~A B`yڪBeoQlQoQmѴ[+ޢz.0XVe -Z[lY[sV>+7nuO[=MV*TnemX]ryGVY4/[eS/E/My ? oGޮJQ̷#`naCo~9ryI:)Gb%) 5@:~?}z H}w)ɔ)n™'o8K#xGjJב&r)<Og:K+9S TDwL3z 9L;eŪL! -JQ /@0}/^sl5s)y 9 t; -i (rQ M~ -̕B0}/#E| -E;! U -( -Ѐ-=0#0` -X`vp| ASɋSTe2NϪ'YMi]G2mڊ8]ϋ3TSf`=ql}ϱ.cR&[Q -PP Zz`F`1b 8@$(j(Ue4 =ŐVFcY c&l'c+%pcxz~ro>])ߐ#gF$x"(%I)J &}he (*sѪNyDE)Nɔv%"8%3BGs1rS~&:s .).DLpJ?S~%@%YA n g)[Ϯ4H4.P %<4o< )<)}Sy{v)E.}N97\~.jH}fmn.wNoRPXbʜCL){uslC:PffwK_CL@LH 1q-|$a|18ej1eN~N)b -PP Zz`F`1b 8@$Xn#BU/ǔjc4."31F?ڈKtJS*5Qƾ8K̸O)an蘭\ZZ1EpySvHK+N9oC;{WĔfk}+j3)A21%_Rp2^8Si(5?ELVo#p'S:V݄3רvGBiv9o§B8;3ܞpjFݪ'ڸcHB<"-3\An<‘R[Y{ŸpTQfy[Pf?fwfGLyJ:,Ǡn[N& RJRʇg6QCR -PP Zz`F`1b 8@$2t3T+QUQW!B2D#C71.JcJ>28@SLnn_b]ޗb;RJ16B)Û[ΊؔS&Ctb?s#8zSb$sSnuRK91҆Vb5pʮt2S2X?>.~ -R5) g0$Du -MwŇn.x4;x|?1!NLqQĔzo=RܴCх3)T]չqJĔ&4!L5}d_j˱sĘ2 Ɣm)Ĕ\֞l;l/))c)yspʾ[wᔑᔥ898r@r -PP Zz`F`1b 8@$Xn!"L"%E $hgi]'d Ę-vc8cbĘ2I YKiJ<=[ !ƔK?K[gX6$\6ݾ?QR ҵ'3rSzrΆĔKc 'S -¨|^jLr1KPVT>y >@m/(%Xutuŧri^4vy^2HLHYSk;˼S]RL9CQQrʖ8UGY=ҴA)PJbTK4_b:i6j67@)3_nx;Y?KcvZ?^P tu̝8RBbL9_P ˆR>%SR -PP Zz`F`1b 8@$2(Q2csY)hK1Z -/(Ɣf:Lz(/w&bnFg|UvzLIJ)~f(grV+vChoOjSL[.t-dJNh1%sCVhYD$v*<'qJLqRh/OJ:O2> P[42yU7Ŕppˑ)#%#sy.ÕNO\kRbʱ~^llTEfu<{sdЬ?O.5;ﴘN)mTc]֪0{li~8%gNُr$Qg[0zN|138wN 1hLr -PP Zz`F`1b 8@$PP:h2t C `=a6Kj1T2R0&bod ˸{?~)~(|ʢ#!1嵋Tc9ax#Dy5e@29;R~)-Ж=)\-))Ѐm+p}1O5bJ"*~3_)ZT>ɪpWS+%Ob<= \Óp#I |jǻF)5=m{aE)-NbzS.rѽfTK&Gmv -1gC=*l)Js[Rಖf˦vn?$><̋)CL)(o/T@)@)c=OC)scϳ1Eh 0 6`N7 ʾXMibdQ,ǔ0jF7賈a1JcJc$!Vػqq2# _Gv>])j/k(N/{߾Rܰ^Ly1&$V8%S1PFֵ))\) L8OoS.ͪp}#Y$ a8%>Upg;x𦤅kkᔅǽR34P܋:lT(}nwi8OqYsK7e]lϳGr,Wᔑ,"Ĕavlvmګ(rlr_Nic (q'N8)*@@h@@ X`,;p@> ĩ40j" -*&~)&t/C&bbh&}SŘX[z8GcJ!"|5j;?oSS4Q7}S4g>N[~~9e{n$RrʽQ7{#JJZlal:)+9jëy;?1>S?)0IgM8s)9%%=-MItR3w|#-VBi\ؕNOt2.̊Su(F \T+9~Y=wN3G7HoY[>Vs2/5dZp>m7̱.av8e_/ z-DE!x$2SĔGoL GV݄3G_MMu'g N< iH.nǧߝʧUJ#Yc9ͻ\TTK9{]1>"Gڶ\Y~iCyXi1U?f)9ne*};l{KR2RJ]}PJ| bJvJ[hGޔfI)P@ T@ 4h V'x~A B@Jg:Ĩ'h,Cw3v0aF?FS -,}1lr8qO0:117gu+3KJ9'R%JJ9'R7$$L\M)׮ߛr6_EVJtS6BJIJ)SrI)gF^MJyI]í7}JJqOJeJ!]燡U7̥xWM qs >/&: -&zyYs)WG;vP)s_Εz~!ȫzD[,~i)"TAؐ~OOBTAuʯxt 5>S?u7-@A{oqnI '.#ݑ(d8]ް;^ -;U#]9PjF8esnإxHQY;Pҵ!пbm1Orfi2ǎv?fr=^1Ȓ+n5 -j-|T)_( !TAdi]BdilR,P@ T@ 4h V'x~A B@J>7JzL3tC _"Rb̗Nr*!Q¶3~F= ϸ;jm;51TI)N7!)EA)j('۾A«SJkl[>3݊RA)nI)~i[ސ=s9dB)iY}*݈,O?R)*ڄRzJ^Yub7INr'Ap.p#U<؉ {S)'6xso:%.jS>Ʃ&w%.MU GTY[yK73/2i1YL#bt6P9n0[6v{X;:(.VC)}PʑoK˓7V(e&$.()ST -( -Ѐ-=0#0` -X`vp| ASg UE>E2h{k2axHDL0alP -a#xF~2$)SRΊNRSRΊnCsƣ-^Q'YUr~uᤅsu +&}]pJq@)W;JYHEJ1?Spm;A*Q}&WTJ;R#2T!UpݩrRV|xE)y EY_Tufu-RʱɑjKWR%lY(%-]mؙu1/v֝>JiR?/s/X'd׼ʟRr}ϊϛ,R:>MW(*@@b+` ؁p <? H!l7FO1$^F]A4&h])O1:bSR[J6c WNMSE"Y~69RNmZ#)*)"( QJzϦ2$u⟶#]+QM>UНqekS JiRsRVl(C)5{O)rdGW݄3x:W#-#MHMHq$ƣ xGJ7] \a$,(cퟢab~|Sf?é2\(#fm' -OٷBǥI7B)u_"h+W*+[ P%OϣP_A)罓L&'>oBzFZj81[j3R!->HOM]k+n)x+ߓgsr(:*\T+Su?`VptkNŲMŗ -ƆYL,}ŖRu!̎v(e(e1%Geܖ[=IBO|ٯ&WPJ rtԖrW(*@@b+` ؁p <? H!\Efe7dԭ(C2NѺF?L SWT=glôqfnxm2c?JJT]/)mI)KJv7g{ɮYBOPj+khruƷ?oĖfmˠ|'u!UecOPy T>$?E l_&dOJZ|G|iP;RRtGj/[pFFT>]xG[QJWSѳE=)8UaYݺۥJT>fmV2 bm1ugB),G,.kws/v{ (PTJ3Pʱ2TIB^>pT苈l㼨/|;NlKdyg{ ?}DlЯ?{|gu}FI)9QglRJj?^2)}jҵKJ9+RK)%_R}R>Pw̞iyJdC+[>!(m.BJ-S?6@)N U7 (.˛rIK񤉧$8RxGBJ{=7[cPJejeE)G_w(~ʩj;]n}#]%PJK7uv?bCӵg%1sxz2̶5?>2/rn( >osB -(]v1B)#zi1Eh 0 6`N7 *襭!Z#:b]O>!%D; -ch|;"c-mhU9+Z>Ae;*#<|J?R~U;%71e]L~V+RH볧L#(_7}@])%NuRW? -7),'~'uBE&S}|Ӫpt}9!O㹴x7RӽT>Ob;5!OM>yGZeVt86%SLUʨ&e7v4]O[do;^j4ki~ePS^Ry,zO:ixFn:~Bn>p.~|XxNz g{7 {'T+t*]/H[yӡ8i8P8A#8qЊN`8!F,+Vq`&vqp'nq/8/qC8A$EG+ TCI$MkL"yI$]O007Mr1)mOU'I\M]' IBUo(ȿ+-WmfFw#4h#hä M]OC Io(eII)P@ T@ 4h V'x~A B@al!Fb4s ]FH!K'a0cB5RA–3 ^L ǸO ᳈0e7%ĞIʠ+"(e@چR* i{G_YRMhFtʠ#8oS~-t6o#ϊS~b)vwA}T b P 4)ڛV݄3SC|jz x#nHORsSڊw=8eU9)9UyY=RҴ stKzجmҵϩCŬTN9zrKkcp֮4Z9k*eܾᔥ -8%{fUU T&><,^,yS {Jj4 @ t@ L X@,؀8x dT2 5(GiU>5s4C^Z;Mi}6cdS:EL2dl }v\^=9 b= m)8NɔbS| )OηGkX9WB[cʕR$J1xT|fr$R~!1eo=JG^؀{O]Ĕ>S?-@^}#H%*CnO9e_9~].MG]WTV5. d"`hlT\L|xe`J./>T"َPJn ϊ-׉"4ELOR -PP Zz`F`1b 8@$Xah*Qf3S?uѿc>H}LwvM8cG_Ҽ)tΑcx8*ͥf'W-_0P}Qosʩ8f.(GgZZxm|׽{dl̟5fhԌ]nj5ǎ/pڏYTKc]k/E4 VD{mro> SE66c6b4jڕh)*@@h@@ X`,;p@> ĩTڗh#In" Ѷ]ѷC&1ΉRJ [)9LOtTi.*+W{2u%s?CF*>L;'?r>dیn)~1%k"=vޕ9}9أ'Nʹ>^ 'sdB>Yޓ,{%Cc -@%P5:`&, X ln<AA2F)j⨚8,*SwA.#6m3ˋӗ֘)Cw/q4kg6g˕B3ݬkزndqKsrnrY΍L7(?l9._kDWGU7b%wYo|ar&M܄4 qn??톈-EXQDRZ"H+Cd2d)VJ)"C}n\{%9sn'zGRrTK_~s--`,.e◢j|9?4sa&(Y'/{ Ws$_/79݋Vɗ23#1)әt I+'ݫ(e&KɋэꉼG(M"]ZHEށ_:73غᗳ$ o_= /Kه02x~MM+~i~9)K( -h  Ăx,v.|  K12S-pNcnU~3tqtj$~9Eֹ, Wh×ֹ4<0ipqhV]K7/ -l/ -lX|_Q/DTmKr7xu.K~y<:פ%괬_~Q+%7-h9R D_O>_^N -1)ޜ0,œwKK2L>]tf _z3]Ht;33^>ۓ$\2ݥ=~__jvP?oTQ?w7_\OQlP /MKyұ~;_*[=d)/ =cK=_z/mU_4`h@ L b@, 8`< B RA 2jS\_9M?mm~j(8ӌͼLظV{ͱe6OM8__q۴d7F=u_ب}sMOCC.gӴ^K>^^7dro%$㗪o}^x~F򟂋YQJX/G']㘮_ 玠?xKz}6#٢G䝾$>,zIv&<+XyCH^;ԁ=#@1)eK(UOSAJ~0/潹RN1b2-zrc_=I<؛O%n,'*Vp'J/(죏* OӮibY**׉c^|]{םOz:ҡ{>:?,I0$$T$$4$$XzF&f1$bIēH a!ppIxH$D>~!a$H0c偀j)^hcXP4Ms]LF~ 7!W[ x.U@uh[Evӌz^g}?y ׮ 7\ai4m|5/}sQhC9S ;d4ty5t+7ʟ*2ʜ&/޶h - -ɧ(sR:CL?3 CC^TbII7/ddNw&}t71ɷr@%4AMZPI'4T4tDi;j})W=9 ui#%[Bvxuh`y=7h"E|mCTB^144358{^A55pCC/C s/?N|*hhj-@C#Թ6i<4M!$"&!%Г0000!K"D OCB !H%Fz b%i*C۝oL1H1zrH - YC\mоt\%)|YSR+ j<*4/iH)!2r^\?t`^iwʷ[H_[P~-y[IC^v+=݃rQ졟s[2D&zO?  -1J}=17%\5WXb)IN!'#x(ٙ3}Zz?N<ܸ%xh:rhʡ\u]PE9T~~(Fq[@5s P(k[࡚WPMoE9TgC (jkhO(*։-e(FΠj:01ʡ!Cߖ!4 KBO@HDL"D,x $,$8v.<  HGO"@"D"L"D !z$!R5 !mE ꛂ !!ҕ-A?h :z/iNp7OBn~CQ!oF1B9ip!rVw?e4t9yW(yYCAC}E"c\Y|O>E9(BW~ ̤^Ӑ -<Jw;Inp;sѯ=+ˡ]U{{Q'ݰ\#<ɫjSńQ^[ó7<6-# !Ӯxb^|ȋؙMFv-gqw=qBVFJ*_=𵎶Pj@{Kn‡ȹmE#ҀJjZ=0#03 $ 8  0Hi#KXguy88KgqtRc\*t:0Ku:Y7\3*Keʷ$T8eqM~R4l(~RgҴz\RʜRe%4(ehM,%QQTF]X^6l!9%2܏@)3Wȕ%B̆{LʭRk^U<~c}bF'#9;)deyLLvɾ 7ǗHftK;(tKQރ{mj^jK-/['ؠX6KZ^ȍ;BǞ=Z.yJ8ni~hP*3k|A?I{zZG\'U)Cq+C:u5:|qq:^!M`H(IHIhHhI$$ $$L$$bHĒ'@B#a' "H$|$$$B$$RI ۖBm)ʾbH]I.ؑ+d C wl -Sm!bLXReUKJ KVFCke4ĬIt]?{$i+>~akefked8ϔ,?x4ɐq /wK-y؜7}?4|LrRv.{ WoܛtɞOM:}dTR<XWQ0W? -Rʁ)bFuJ3THin7ꏝ) y6O35Z.*u[,Gq{>OOKK -O~#넑'Uo|C)5s@_'\^yh]j9 PP  A  X\ @A*HQZmt甋6Ħ36Ԧ/fSuZzjynui>Eu7-)OQ\Z?u~06;4Zw:Uv':sczNұ :u/u-Zo+ g'iW^I; ANuhF: ͡u:~:A:TVnAu"M`H(IHIhHhI$$ $$L$$bHĒ'@B#a' "H$|$$$B$$RI gLW@YTuMm~ؠo`(`l fb2TZz@Qtƀ3(l #|5AE XUxOu=գu,FtuRҀJjZ=0#03 $ 8  0Hi<ʨ9)m6u/6q3U̻Z=jqܜ>jssy8a*sΟV?3Gu"['嶨tkw_a{]іgC7D]^|TLF]^|L"ybN//^D/U,N/EO/i~)ʜw~bB*ܰ'KpfѷGy9|LJA鉉I(a -CYeu"VϪu|V;βX}=k Yk. J"Ja-,7oc YϬN(fnַ?cIR뛗fJD|[ŲRޕR>R䪔Ƙ+T)YM)pi6N lT(%QfRnR*:]n .f,rfRS*irU߯m6C)w<*%{0̄Y*c~prMP^BQJ 8bӗp{2+ϝR+cHM Nz>;+B)ĩ5y.W u j왛Lsq梥(e'J+o4>~)sG}YnWT5C) 'fz(evJ2^m'G@@z`F`fbAa -}:%t6d]?Y4+YC7KRÚRR -Y˂'XХ;Y1V(neJFRʐF3*F3:P9=RW߷}Nn䒔')=vRN5QֳvZvE:'i߁Rn(e=~18b6<fBTƴpe/* q LgR rJgV&f$f'Ɵݫ(xk}G-{[;(SonT7,4-;wPgSl;(#OSM橃bv=غؒҜ[,CQO(cO1?S#넋T!NNJQrL'PgƒKo4`h@ L b@, 8`< B RAFwsL9춪fjfӎX.N9c)g*uVrd~i,Wh/X6i+k,Z9Xv.0a]u=o_.J~I׺&+~}QMO%ɲ;~sܹV?\J/ ;r`,n]X$9X|%K}~iq]F 涆ae/*ޯ`dwzff=Se龌Dw+de3W_.nΝ)-t[Kc{zE4\VH/m/, K{K+̃G7_?_9}be}~h_ '<2XR임_fᗋC_4`h@ L b@, 8`< B RAJژ -˪*VMU[ʱ}V}?g9SOΝ)# UZUj{lẢ-V_?f q>7w_I~FU//7u --ѪB-M:LZz{T+~wM2Vv~_ݨ_n /ކe<'2 __}O>_%:tZuV㲗pn;ӽΌd!};x=>>;seK@Z-eVzE>FN~~AZ _gz~|mئrz&XO/UK]Rs~; EP| !k>$6%o~i_*]_"K~/%D_"K~/%D_"K~/%D_"K~/%DEN9kU5prNSbqF9c3g*~#k&l+7ٛ"kOp&NV_3?JV#G]{͚fS?n5Mo~yAoJ~_n䗯eL >_?ꗯU/.)%2 >Dn1HOCL5Go/m%W_AxBmtĽ\[YneeYMd1'!1й^nsw9 nv:uHR"""VX)"""bEDDDdXV)ED>U\ q>{>ɶs.Ͼ_G8{xփkc=guuפ]͵2N/Z{ ԳLH˸ru[گt -`ߒz_ϸugkWmkSʸk/vU]D;+\.!L\p5)oQ8I`L{zzBj[ %;/^t^_VQSGH|{dE]Y碌_T HMἠG4Rw3A_sXch>vxTcoyؽ5\HdTve_<[z!J(M -ߪ 37#wo6Q]Քu!7h-/}J{b\KTh0:`&"D ؀@/?Hɀ\~Vݬnd56cYua5fsV.|CD7k5ZJ8~.I1 -Y,azVb=moHڐa0܆O2`94O?vj[r5hJ~a5X ({]P{R(a8SK%eJ%* T@ 4@ 0  -ln  x$d@.%Uft'c{YM!-d+fu]52TB)D)cG cdKPĺ -Xw1+VRd̷BJ(JI!+%^VO(匓#R2W)%åa=tHf}Rnr|JZRVuRVZ) J8QXN]Uy5PJA|J{uP]HG·RJ"xG'^}j(.kC8eJŸJq;c !.NRiN{<QLS]kRڣXz*~oWNJR߷++|ԃRfz+ȋXk %;7i650?.%"\`d` j]lGRЈ.%kc{fq0Q$ҍ.%W.wХTJ%* T@ 4@ 0  -ln  x$d@.Srt-*SVqL MqAN?98gZ"]J֒#>pQ E?r-p%N\*S#"-&++V~Q(Ar1V+ J9j9Q$ E[)p)n\G)G3WRMRΔ򜬔 JMVsRF( JUJ}ZXs7E@)I~(! -Jy`]~ ~?:.<]/K~D{勫©R!bb8dw 8+#.5]%1әF)Rzsn[VJ)l'Sommmc\x8/xA_6~In[6_~(fea'e9{K_~Qnغ^bc=͎ʂ ! fT!H GTuv̵#ARs!&ҳC> -/zOsY)YB 'IN=Lpqi.{3-3\35ӽF0Y:mj[Y?+[tï -uѝÂvG`g,5}*г,g,29Y -RQE;_k֮ͶYQ,cAy7E>썕^ĠY6gh{\@@=0#0p -X@."xAHR&Y8K1VjJX^ au~5CĠEFYK;w^V9zXauVdJ֓z=ܳX8씕1V;IY莨=rÝ9͒dMeDNDvAd<$; Mu'i)㌵N S|6?%QmBSE#|D-xȧpʅp7N| =~C8uNq!tvIpдH=A QȌ fB3kTGQ_.In'=ԅwEj - -ڜ~iC*ԍ~.藏h won6Mn3W>,ݕbi)~8ol{NSZSZƛŎO$2{N}NySv -.Q hjZ@8f,V` pH $r)ٜ[XUe5S6#NiguM#Ŝi88̰|;kam94c!VgSDrQ)lO&}_@zjwmzjqC8uNwrfqvjwkHM$d[iF]?zӊSʃ tny\PmԔ ڢГ[cG5g4T78LE}xȨR6Η<նjʱI5%=LJH2~+ t=Hyg)SPwkź*  `Qx`6p7< |@2 )8-.LesJN_#Ni22Uu P6'B]y3@aSFe NN')Ab?)NeNlYM]JfFV+zY)nY)omʇAEVral R޺J*{JR1u @)%"gPʻ)R߆6~ߗB)U)ErV;-rfc2}ܱ] -q" Hk'vQ}rk]+c+w]~{34Rˣ]nÃ)<ۤ[ͧo4eiM1UwMEԼ8e{ʨ͖WޣF2R6OS8:ۏ)a' b(ag{N,=U=?׵5,iayl׫4P5-`t@ L D3+ $^~ht4=Z,hmG4Ssuͼ#PMdh~V1-vuhq Z&~]ʿs=Arܳ/ړ2in3ULꡕ[ezσT~<_>+XneriAcaĠl[WVo_؄%s~y_*n_~9D//YN_cPHf~)΂_&ۤ?V/]ǶQ&]K /m)"FC_f= -/ /KQ/ D>%eVxz/ KiR1d=%׫4P5-`t@ L D3+ $^~Q^9Ӌ2Z֎F3<7[G&UѦ9~鉆_yKg4c=cˊqLB9xyO6תּ^7I3~었BfLa+S׎~&uKHp~|kv/خ//Dk/r^|h߱]~:)% ~ydfC8usCB%ޕv%L1-șaOb/8wZ+c_f;{VE$*rUw/4tjQJ"u#w:E5-wMwÇ0Kϝ)zDaW8X6Cc!Jg7h|bg^]Dj;5HtZ߷Q*io<̂KTh0:`&"D ؀@/?Hɀ\2bVĪGXM 8(nՍ~08I$ZJX6CYWnaN*<3[W)r$(]Y)#QTDr^HR\RjՑfY))+UY) A2',JY(nWk" ueJ9jevE:BG]!zi[LU QJǪpB*%6UHu&!Bz(aJx JRJ%* T@ 4@ 0  -ln  x$dp|U0tafL1-1>V_X2c&Ji!#ͬkX~V:faqճJֳͬ`_[.D)?rܥ؂(g'E)^ww;rmHfm_e|Ӓ#;EdS ٦8.誸k/1pʿJEpJ'l@C)ڔ=^ NaC8u#W%d]iN=-ಋ x1=>3=3u dEU|O8ec']yJP d;'H]vS?>144%ǖ"MYCTy8%ii 3[s9m%UpJ}pJhS^SyW~N"➁Sv);ᔉ)D% -h8z`F` AX 8\ D $ ȥrʃ,SuoSZYm L8KV θ:ȪiҦrj?Z;Xaǹ9 -9Ovc)Om%򪸍AKUq8塓┬Ǚo;ݦ\^16Y)?|(''C$ZOT) m7hYP -dE)f(sUA)))N<?ěPʅzndE KJOȰ 4;#.֞*%Ō g +9)C(z۩~픩:R>C?^qhbfGd QC#6C,e{zSųׇlG1iر!: CjHHG[!f؝dWb,ScS✱8)m{uKoݑ``fb(on(hl77?ѐekL147U18eȾ<͖Q|K1ʚ5wzLh?V/)\msݡR[a9yl^Ⱥ=_X5,im+^% -h8z`F` AX 8\ D $ ȥ.U1hMug&y"FWϊ1Mdqn/Y[z:FĻ*cU1byT=6 ~1mc?_˻~W{!eci엍Ar˝'c銂CS>3yA['[O"[MFwI -(YzU5IFjYfX%3`eiјV{|k[`9puueVc>z~(+#99e[(+[xRSAj4&cȏIf>\e++SY)SY)w=ciB6:CR^ѭ,B))/<)|m_=W[ ,}C8uJB .{Bff$إt+353LkRWGQQ?9oE)ڕw8A5.ԔnwjH]ES`Ncؗlܽi2R/_h)5G޷X7zꡔCB)PJPJ7^j[RW>Bs.Q hjZ@8f,V` pH $g:gṴ2czVXcgMRJRZX2W8`K 9Hb]{esF\EVʜ ѥɥp')Lf f[d)Nirz)1' l[ЦyNyQZiS".SE\?NyȧhS~3aN8E誇pbz QZ;36!iwd:(rg2TI\&ĠC.J ;|sl3=y?b0bPe&MA"enMAl4z1h-4זz]a=d]lFTfRbPW bPϝmiĠzĠ@ ~P!<bA9ZĠĠ{u.^% -h8z`F` AX 8\ D $ _ -lҳ7Fkx+цn8Û,nxǁawuƸxqjxO]``~DmDY0qP1qRbs|:2t'{}T?LJ;d<*A&'e˥mG{hBm,dnsY~1zK%}tү{ϢٙU,' i hWq.;=.՞HKt16҉ =ަO-veu}QP#-DjjC XMRЗk m;̦d\RsQ! M9}PPRpJ@P p@A0(`<p@>I K=̪*9$`9Srˬn` q5u$1gmKXW9eeV;~\&MJMdJ~dRA½wa]YQ'3ԕA7(~ӑ=)+{AR=Ȝ>H -v=O:/RMڎ9/ѥh8(eo`mboB?6t)))S -)Nb:9H洧X1.5#5#VLXy|bv*qeNzhVJ9dE6TUmz.Mg(m>94 ܠJG޴F>E/_o~ɪwP KRQ˕ue7|}*w*lZZJJw}cYXSGIVz|Jmܧ/(*X&$&EE )ZRR8RtI1b$DJ8)I"B -O)RR\IIH%G$RI!UN$҃^UWyeLk"WU{g6$I'BCg[j^[D-3N -̕_8YCa!7J -7JlnP!tQ\^g3|Q2Ӕ<+YCl Fй=~#ku@JCcA4)Tl.܆thpP 4^6iyȧ^/jnI73©l礻8w-n$Isǧf&ǮP{o-*Xzbeftg>Nk`̶rv)<^ݤmʧMo4e{:Ŵܒ0[1933j͖( L1O>vL~BXRzP!ʥ -O9`1On K:+z*  `Qx`6p7< |@28~l.,^ͫ5gFxFxCCq7MMGdN`+bѶq ZvUB^l०O=M_W@엫6@I9C Tzt/~KI9/j~`meU^ۂ6r}VڜM@ nK;zz}~l#bqz/)Lhs2;L#δ@lF\Bv=+S'v0A5"gEré#usNЬ}Ƃ\{9 ipgȨ;܊`furm;@7SHNHN7Y{HN{Gr*xiɩe99*  `Qx`6p7< |@rWɩSqt>ԭsL3ǕpaN_F9(g%yIrd-,?Y9[?KcF9r0's2)sߞ@bex*RJIBQ DҹϦ ruHf~yyP<$c8 1hU^g٧?ўvEјDxZXq{t)!)gT\꧓RNذ!јX")R]vw\|=- IԵ5{eu+N+tӂz4?RSթy]`N]i g6*JpKQK۝_kѽmYmpJe+ґGFc)CGn>J)p0iS)K]SpJ@P p@A0(`<p@>I Kib .csf3L1QVXc!k"1_5%5 &Vha]Yi,1ޡo;{Sb N)S,AyRxuס挮p)n[M9dZ^&D)ܦ|Z)Im(ѝ6>Բe%=G(e_+&Egbйoi;R>V=Sx]."ᴧ'wfKHH.]ޣ?t]Y{^TeMJLЎwLSW^\G43i<2| Jiмp 25i)XNXnlkRK#SV(7x*)oU#2[A)U,%* T@ 4@ 0  -ln  x$dp|O%f:V5ŪX2,W*Y2ceWZ6̱^dUuLBc8JE X!ѯMG? -ZV54(>)JO?#p2vEPe ' JTV r -KO t?~t)$@)礮t)ǧ$Jy"(eOKGoɿDq!£C'L7 "i ib+.~Et)3GVʶ^g3SRen=/m,fS6٤[ϧo4te}6ŴԞ>̝odF2*Yi:FY{l9.jsG{ -mԗ3Խ:fUno KjyPY* T@ 4@ 0  -ln  x$d@.6ZYѪ -^]kFU<3evhCw7-J2R[6:۲yG%/,Eyw/GK{=ռ7_s߹ŷ2q,YL!w]vRFVtO~Կ-e3+-KK;"mᗷ)RPHA~_ʪp"ą>{ weS4{jö f2k[]{w+~}"ef87RU>Gؤy~ٿ-Bʣٷ7F⺍eS`JKF<Ԓ2ٲ\0NŋK;8cE{ k ݝ]k)i~Q`AHv#',)z_pJ@P p@A0(`<p@>I K9l?l^=)浵1Le 7x|a_j__ʣ-gxG/ŸV^_yeet3Clo v9 Voug>MNf >3+~1K@~1K PDGYlŕ݊Yߊ_h_DOQxȧsNDWz.Ew"(6.t{\zf3 2]kgѿ,<?#?bW.F$T EC9Nm^nH]v~ư}q<ޜHԽ3a}Ӊ;F"ݸ!'\:gBzf= &hOOw%!6CseRf:dDqC8U~C.n[KHJ@] -eI δgz/K(:NnY?+Jt{%A]PfNvhR`j:*Rw`S?tTchI6fMyвL+"uZveYbmxt|lF"<~-K7в >y --K69 B e%* T@ 4@ 0  -ln  x$ί+C,]Ω9u/Sq]n7g 圱3e7Y?yZYrtTZfCJg(ȋDz$c֯ ۿ8ټӿe7lܥ~=|<T)_zP] +顖^$Y'7]2h תv)y;"mORxȧ^v}Z#נU)SJBlhib$ċnx{2.1Mtg־н{e/(%]RۂjWPUEjw)"R}S_MrfӁ.(*\4] l@)Շ B)㥏QgnHΓJQCw~KTh0:`&"D ؀@/?Hɀ\ANyjUrp\9.3+yD)ce[+es̳ܕp|b&oi9&=#+gA"+Y)? Wx7zWf>%3G3Ơ_#+.)? /+.)? /_D)pJn)88H_p|}m~:iY⪇pFnKTp3vwF&kwfn)NڵGQs[Vk+1hx3"u #omz1hS!bPM|J_FA־WSL%/O1z 1(gוQm,N6OJB/0TyIkq>%-?{r*w}IFs٪pBoNH (ڝdf(.e83S]R(o|b_/RLo+ކ_ᗞM23v',K)'6'ʗSL)K//5/-cGUo,/UCgc -G10~Ŏ0Q%?o%4Mn~9S W hjZ@8f,V` pH $/ m<=}f?^uU7u -5<_[;y[h -5p/T{Jbu75e=C忸gHw~aEh{e̼~yP)<_2e|/Ab6_".Ѷ?ѿhV"JK&B&jOѿtSO3ɣU%;22 xw`wƦSB=->5 -ĥ_G&*_L4tCY@t# #5mNk3&p#u 9N}2QyƹHSbdxGTD榗"?,s◶X;箶s:0zrQl+o<>Hy{M}J-&!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! DŜ_dan=usBuXI%'r*ϲRvJCJ)+ArRW_"Ld̕=f''A2ESV[픋8}É:%;%Ft8Vng8E"?g)ǶxȧpJ-ωOտSn޾!!whH+>` bX!Ӟ.9@zf !u:k(B'sԮ餧 zHvN]~zFch:8l:c)*bf{ȨN}~ljuͶ) \ӳdP^iRx5S;k) RIC}SpJ@P p@A0(`<p@>I y}B#%',4|22c,7Ēq -Pv2)]ϮdKPƺYw+6Rfc돳L!i9s) SN ┟٠8jݡۗuL٠e]6%,Rnܦls%OXPC)?%Jr3y|1d6!J'Цp~Ġg⡔m©f =̒ꄅ$+^ѝE]{hK=ۊ^]AG.Rl[ΧCÛ4e]Ay)f92&xc>oh;4ߓbK o{,[@ =UfˡAˣdĠca1ebP~NA}jB̪B y1hd+bACA"$ \@@=0#0p -X@."xAHj%/F j*F]i.Gv2yG̛'[(U3-M;(m_>Gq%7*GQQaq倫qMs\>ܨ2lt?05fL\rKcegڽDOtW%5\'owٯXh:e }h:OhHEa!dxHm<ј;{j 5 e G{7a4DNp93/ -_mNo:4r4dxC>ҎK^BS}] E!јл3].gOCKC4KR`ݮ@$ǺkSkEcMk]ڕNz5A5W7/ -~){V*"u_ -cywM[EEHNHN-QSw:-=>ofujf d749ai#$o'%J#9 9 9֢GtoV-E.}au}1fo o - 'ޫ^;ck'+ )"}Ɵᔇ!":Vs2~w!:5zGt]bB.I=-=eGTgz֦tENw;5}C+MlJY ́e6miu^)6B1Ǟʉu}wR7mYzw(#WJEmGkӲ8Kf揎(l˟S:f)=+]FR PRλRz )}-=Jk(FVB"EM-) )):RH1b"%R̤Db!'J)).RܤHxH#OJ)ɤ_U.MzUuWp^nW_h(kz^ y,M^~lٶIG8u-ϖj׻D!P_nm~/n>tD ̎+)^|a7Bnq<뤼liA .9h;V4wzEJCCԡ$)?}Q͞UHqn -U4NsRte2Ҷk;-w\\,BϽR?GK6ijS׶SLioWo -(|4ҷ?ѐ}geM1͎O6En/Qm۶Xj)xSw4µ =N6J}2Vvt6',My#W hjZ@8f,V` pH $/ds<īՅfE3]<ڣyцE7͓he |w;;xwu?0Z:POCG엟܏TxR_dʴ:oFgCF[NB_OnK_2WrC=DHDi7|?>\B?|fr}C8ubC9遴4g]C`rKb@/{C_Dʚt[MP>6 ́v+},RWéo@t]MUO : :MEF:-U~-ͶD^q7tZAtͿYsq;NHa{ƙq0lq3̡{soiϽW~"DB"FJ!$R $bDDD.RDD$\I$rʖ%Y?>Z2qg> L , D+6p7 $! Rj K׳Lnf5vJg,w5sXŁi$:MɌWmOý]w5baXiZKʖ]>d)*K9%[ҟnZý?Ԯlm峽5nGNNp= -Vz-)xmq2tXʯV@ -O%FPg 9|R|>B%5ޛs%dzDW\&jʟ|yt&I9)SS&m{Q$#$ ӣG(c{Qt2羷ɒȖ訁}[Tֹw:!9{[ŏrT6rvyGiXG/ESHN_o]t04Ki" 5 -=H$("V"1DlDx""N"n"/HD$H$D$H -ŹLkz4QsE"\EaX2HDKn6T* -DSrI΁Dw$KޢD_$KsDs$W6T^نLaoBIe3KZ͆ۘ?\|C^9+ЂT9+ېJR*n^Jb[_8MFy#d[ 9=M!qӖ JIf,떽gC8!de +Ge㍅ ,1+!>+>>>#ݿ&$lh Ck[KNe3UHNNΐ#SHN- gro7Փ+,]"nʛ~ɊY˶U"rŠ.#W J -:'jLO#9{IDD\18@@`&` 8 |@~H)vxxҮüڮ?nnq7!Bϐ+}!9ew؅fkA{`.Wm/z*̗W Tb/fe&˫}QWfߵH[^"_nQ~MG)&$'qyRrY@nZ̝J9)E| (s*bKq{׺TJlV˗r|~WŌt1_y:U O_]݅AK/뵥d}?e̽ސ)oT2o`iYXu򅃔,+dF91{r^L _?4OEZʋUbT Ts#B >yeKh L , D+6p7 $! RyI՝Ӯ-z6;7{a7&v /_mv~NO5ݣvaݻ KM D)ٽ/eSس{\MyW ^_>J#ų5ߖ7(5/Z%cT)]&3:ũ|~9䄿 _@< YGBs7:bԅ/oG-{ 7pSǁc3=tWB'\sgE1MˈOK]96NUOXZp#𴠮/hwGkH/nmцgRkuy|Qb>DE߿:/:fck:Q~"9rTSB{;w1}OT gʫS/^7/i5-= D(`1xN A - ŋͧXmduݬFYÚq -2DVfȦqclg5pvN@++unj?/[ʷŭ`)q$˖W5b pfԱsʷ>a'/[a'ݿu-uTώ,oM K7 B4a),Tb< KG$XJvٲp{ͧiqY/(.7K/O'xRq"GJTU5G,e[+05Msڼ'=w16=1v}5u}z9ID5r#wV58_;KYxi/%tU5b^Qd#{^*8\G:9LʋxD0@ 4@ t@8`F`f` -XA 8D dWYXcNnVjY9Vrs, Ys#g9A,gmz>ṵ] -37(_XiM*Wl)_-W,fpG?^}cb -)gr# -,bc2)0 -+7K)a)YK DnXJ{ļu?sk;7u~8I{ כ9%ܸiR= WVFV˗!o'-2VZʉ]u-x;..LTRйk+v ]D -JN7L|4eҽRȢGDE?m+(D -ʙD9ff)}HA-HA/!\ئK# LHAyW. HAӍHA;@@`&` 8 |@~H)`񲃝^^]m4ٵv4s󼡛7N9n)!S$5m<_bw؝}v0{6^@]* #KwnK/_`"˷7ߵ]c3iձ RiYImw\O­ʍJv//i;u~ -y !$ec^ 8!| ^1#/iW4cW?5ΕyD!ne̡:ՎSs]KIORZi=B:(MGUWP{PϏRG -]ii\uve訅[T'ZV%3bUξV]z+-4]3y;#B<׿,+l[Nӡ=]ty3<4M!&!%#'1111$EJ$OAIMD %#" $I!x2HD juA}A"4''Mۂ+$aКD%r$G~YtJ‚LŢ?7H6: wI`C6tlCZzpmcf~cP/E/(0(|h1NRp -65apŘ9׆7s.(le QSqo?d3>K݊䔷nKք7 }/\ &|~Ǖso\敽u}򼵆.z)S.MS騠?xH& W)ҽіv$I$%ǭ19Wf;+HrʭEr)M$Q$$!$-dZS^$HNmG@@`&` 8 |@~H)`qF1JN]iN.>Y5LBt3d-$9TY8czw+tYַϳ3l`'MWݟ^'v\D*X&R,%RR6p;7֐_X)|_WSn=) -5v{Uq./~dٺmSyud.!@>EiSd? 1)e/01y=YLVb3}0؄ԄHTnRt}lf-DN'Y-xDD]on7W": :Uo?\UKbf[m!=&g)(л*Aek[UN/| -]TIM9r#{ʭ4`hp"A`AJ_;LKeG`3t㣳trIKщ<4M!&!%#'1111$EJ$OAIMD %#" $I!8-n 1 ADPO$Ҡa0x&hI2&Yz`C$:mI|u*9tw.N+}I$i"jqyU#A[lm(A[l - ޫJя)I7~r~ֆ]^ot:P>s es܌oVp6t/N+(sf,@~&1wtIzL^k~;!ޛ&$ O>+Mp}iĪ)GTU/ƗZ5]uѓxzGmF:nhCyo^VKe5rb!*~xtyG?3q49xL$h&򞬡Ct:~d޶ ѩuDكdTQDc_n:Oh L , D+6p7 $! Ry☡ٹӍ - R[^]YsTpI] 9_Nr1V<@#'}rF8ReK_R^TǾ_R^\M \._ݷ)ER-e]K¶O ->Ky^K9EyQ8ds_MM`)?'mgDd7Ae Oa)Q'=BLbҧn[nܨkO˂b$9e'22\qYdl|F|fN]վd)͟]waA_#3Б1hM3c)"yEXJs1ۙ2ܟ;4f;ϏR`)͇a)9[Pip럨)XJTp}XJ׻YR4`hp"A`"|qו{c,am]uN.ep!Y|R|HcέUH:m2Jv|=yĩ mGDMμDY';SQ-ga(GYʹ{=8sI%{DJ^zF%V(%@Jj8sI!><@zwd*y%sT.YG -GdQs9;g~Ka)BA,xKGc·n -~WBB(GG(]fJQ;V8E<&KЦᗷg󩘞?RξU|EcܴVrv;J SjzoGX4Y:fZo'Oӡ.:}|<4M!&!%#'1111$EJ$OAIMD %#" $I!xR'rI irb+'1uHDKDƐ{-N%L>?ؑ(MKn3U ݤСRhc}/" UGS䎢_9(Fy*BQ8&GcNr[b*c`CqE!6GLkqe/]*_MJOMMGe|g.OfBj*#PU:Վ+˗ -̹sfuA=*h*+O{tۺW:\15>bGe52w!*jfk 1gyl}3lg#jwA;*k(o*ڝJwP>T6MlrZQ˕  P X@$Vln /AH B 6ߜa~N}Ӝgӷp\#k8K8 KHszcIS,?CW^}l*Sw5ӕr.2aA-h^F?'pц28;4O,jTvsa) kra)MŰ3{`) /R˖G@@`&` 8 |@~H)5K2gΰzVi'X]?rS˙w'dKemM,?:X8jZ)&)du3CKn2wؾ1OQd~ܧޕe/_WW_AxBmt̩Ľ\Yqng{u])l22R쵞ֻzWDMl[-DBDD R$$"SD1"""A1"Dʔ {<ÙϿh&wb_sRsCbKn؜~}ƖoȾ6ߧ_Y/ufvFW7o̺}CWےK2m7.KN/e6._$$'OH_ !xy -yb_t'In13)ǝEO2LfvUʽCʿ>k k4 ϺxC]nUUFzCO LS;֔5Ϟ4Xjo=nki98S{j3߽:gS] grAJ|PDMR!1*8RC+kPgTx㇩;ڨu(\@03+h 8<@>   r)MvYVXԱ k:ɘ'Yc=u1%}]\>(YϺXO+rì8Ju8Z^.m: 0Ժ1|h1+30ɨ|Q)3)3+gQdUdS)F~sOq:wMqjֹ4ٓ?󛍱-ov{ 38{&{fWn-߬IYr#wi?oּ鷟lYmC NҶ++xRbDx$+s}[ ۗ,HOZjN?Euַ]T R^it(C(c=([pije>2--х^ƔޮϏSqǞkc'^SjSg4փZȾZ.״bDe9Jz\nG^n׆ӆ'zA:Ѐ6m\GH.ZK&#'a a$HHIXHXIHD!a'GA'$"!!IH$dA!a$H]J.K)#)74S~|2Ųb]J*S)|Gȹ;U򔆄_iH<"-S)`o Zx 姩Q< }]F7TNS%hO (9mZ(Hx&]ˏJ\(J˧qZ@0pA 8͟˳iUs.^_d/cL]]F/O<պ|~[D:2g+@> җzUUF+HNJp"$ B m7Cqgye[~,%f$' R^MH_[yҵn3<0]5N Li ǚO~hlZu6p=zzSOT=n!c;uu7:DG-%֩j?ZoJUuB ZpK~nu%j t@ 0 1 \"  B RA 2jwqt;YC-g\b#,ΚfYs'ke%RfY(srnz9O/'p>N`RN.\fr,{g椖jY)cTYi<:]B)W4Rh~3B)TQ3R|9xCITQ3UvNc]%/A)'9-Rzӝ Pܺ}a:d᜕,b?R2Ĝd,E%1ٝ}9$o,_WI'uṣ/sjAt -v8s;:79 4p;enwYB4ț[+1s*z+WفijS+_E:=蜺^"S:/%sKh} Sc蜊_G4Ω.WEkI$t$$ $$  3 + h1$$H8H$$\$<$>" L"@"H"D"L"D r)=L*C#Sy)٠pm JF/uL)]!L&EMH)P` JYSC+|9_ECW+Jes>+MWh(ۧZ&Ifhh?*ECT6T6.Tl *J֙jh܆Ursxaj;Im#툺j O7/ _ CC]!Q$1';9ٝȄIߝ.d ~\Do&oT{-<@nS[ OBC-mA+?T ռt.P--ѣukcNTBCwSqǠG۵jS U<ˣnhhh6044rOEkI$t$$ $$  3 + h1$$H8H$$\$<$>" L"@"H"D"L"D r%!x -]kLO]!fR䥦dR.mGrR A~ txBU7RM<ؕ -ᛊ*:O7 iU4tYYZ* 1+pnQ44󊆬*Qм!nT;_=S mxxS{PK14e j^)Uel( Ogu!:? jX]5šDѝ%@C^bwzf ĬP{52|k'HF骺 -) UUEgԶR\M Q.}?vFŴ(ehʉ+y}cW[_)ʹx=rJ)pr`Q~{8uD#uo/iMج&SPOmQᦡQW hz`FX@4vN dAa -鉪0O$J .xgyn75':N.iHp%SE -C1rme{E$)ٝHwd[t#^1'g_jvT>zھ۽A?Vk;Ok*52XzӬc>hb}(6}{{?&geF=R)aCoOtܾ͠o?89 -5?HﬧUES'ڻ+f}8Jͬ\XACgfUAE)TbU䝕nI_X)|{V٣┏UJ[,)NXE)gꔱ/V-mNڥR8rXS *Ca:|T»/XΙS_²/+SAKw~-'Idbћ)ݫYl~f)n^~LUOwzcsK bMpIec=Y8% ͱqU^G߁,tҶ54 4w)cpJO3ڥ\} NOv\@03+h 8<@>    9mGn՗pzX fY4KF`Y(kk'RiY(Oy8qb9'sr(Gvʏ2%NqOUW8*NIF@k{](ŤRRL*eʿ(T)gש)u OLI^GW%D";ɤQJ5eKaPxnUޗLRrzrzVۇs(Qds|h2e(I2~^MqSJYpk[ˡW]~A?$G^WTkX [+mEPCPL{ظҝ^G_MβנA(eA(eFJu4"~2 JA*"rE)D-8`f`V` Ap8 x|@AAA*H0reu%5L1ӫ -9S.kc-K QJ+)S(eq lg]O),дY9 bM_TW*򄈝'RE)*Jy:?"#eJZNy4f&[LBqΈeJ@qΈeJ@)Wi)D,?NyNy';8o:Ū .;/~CͯLpF`EY7ۇF]OLD}{5[R[ۖ祛 A?|_dX ,<&pkbM^dȚia!6X Cqyӟf69{uN,S&?DSoI;"7))Ep68\q -.Q hz`FX@4vN dAa -zN[X] a 󬱈c6kN}@kӘW)*J٠t>3Dڜ(eH2TrLrw߻GS.D6:M-PED)$ Jo%߂weW 8?C)ߊ2%芇pK'|>7xE$wr7ѝ)d刉'<0SWӒ5zAݾ­ ${$>Hn4=@[(mUh;BH!{$Ƿ}amL5|*pX|EßxC,{P뙼V?ޥ|M+AH~Cɋ wiݓ_۵i]/hS'+䢵$h:zF D4vq$$xN.  DB& $"&J"tcn&RS!p -76N1WX:B[SL.=){$wyrS/7$Vd䉔`5u*+Š*YmzП֪l4?ik=L7)0tVu?_P{K=cW%;OSh *DPn7-H,];oҗ-ӥDwbR2I~w($e{lʛUjz`f֑Mcg'Q(m3\VHq5]n*) Yz澺ُvFŴ5S揳6:Z(G(yh^i~|^#Lz8J<ip+֏4PIc^TxԚ-/j t@ 0 1 \"  B RA Rkx]^0А`$o*N0-c:VFKSc:5zy_sKu<k|MLy*~F◯0ϠsFS۽\ /Kt:מO%eW!_Q̌xn4y:KL23$כ*e'3}r,fތգ1ݕ{5-wN#n푻KnIA?Rk(5ּ$0MvTlqs%^G,~Mι7Et%tNQS,atNG5'9-s :ו 4=0#`L , -l ;'p@2 0HitjbS56L+ղn굜.Y6+Nr(N8%]q5JbSqʑN>S,-ޢ:C&=Y6,,SZ^S~Nyv @;i}Nia8'Y+¹k"ɾ+$$wrrR;9'#CefKI*4ܷW3~'֩D ZO:5|0:֩b:Hqo1-u{e ebiKKkc -G4}Z&NKOu>֩N:Ѯ̶kɽhԊ%?G :tu-CT/|6sDٝD.ZK&#'a a$HHIXHXIHD!a'GA'$"!!IH$dA!a$HͽT[y)=Еf/5 0pT0W_jZࡖK]:fTYtƛ!g^9sJߥO;S*+}sQٔfxq %oo12?qb -]n1M~su)08|{0_/Bou&17 5GfXU[K◀_v.zZ[՗Ҙ]:2sro+kkT+/O+NQ*~_>SxR=7|6O4eKǟS[A*m;ZN/K(9r;;)C mT;9'DM'&yݿWX럨PFfO5ۼذ;+۫c #^c5[ 5Y[bmK5dW J{\^@q?>ɹ΍)OB!7R#%INze5Ty2T_NᇩԲ .Q hz`FX@4vN dAa -/V[n?/ XgYtN~چLU0:Y~udiX\oNr(悇>󟕒Š(ŮRLgek4_5w*iӘ7a?쟬K)Fr/N8rk* eqΖyh̙pʶ{%:CpJ6@> ' ߄S\hob#2-KYlsD91ݛzR۽{5zr0qv6w]^[@2m;(i&{),cYʶVSïlMn}=؝͔rq ޼yy,Ok<O5(q;5ұ;(yUD>vkBK]Q( epZ@0pA 8'~ywA5k@fKe߼!(I٤,!'GpgIIT}^u$oț/}tʘKkg7K:oէ.7L'=QayrSó)sMEW`;誃Kśjsj7::=~i@O4_CO=QgzQr"z7ա'*:e2r@pZ@0pA 8ԸOzv -5=-N?zʵ'܃Pp/hS^W֒IHIHI0$8&fV6$bHIđpI8IHxH$|$D DDDDD*4G{k]z}oНbl 1e!1dN14XNQwe)zgz׮.$ }!1D^<0 -ל@|]lbU4tR|]P.>+RGE^sc3d>t5{F3ޛO2#Jui)ACjYCOiD;[is &D{ah!S|ȕ2|(e[JNwgd3|B)ȫ{+wʦMloSlPٌʦT6ӇP T6D-8`f`V` Ap8 x|@AAA*HR8K7aV_j8c5rÜy,06H*iRa8W?iefַĊݜT'@<ʭ9R4F\u+5/WSgY_EZa|4xet ^*JSY_RTlZ R6Z ]uh󷡔oxh JJϒ踯8hrEw1h~m*m bC8sґH^}q'geybvF;=]t9B gg˫ګiWU^VJ,t R!5{/ \5Y`9:f+ETbx J~p=outfE97C)#'ɜtRP%v,Τȝ?F*Z*T9 ,(J%j t@ 0 1 \"  B RAZO;i8Ͳ)0s9F9w TrE6u]dNzuL<ʹ8.Nho?'qR'\[%!gUE)(%ZQW\2[)[ >Rrv÷T_-*geJz V?<,].ـTrDүYz/@;]fi,h. MId$3YLN&&R?'#9˗zK{5SSF%?HwچN׽s!"ʸbNFqܴ2\d~6lo\($F_m\wFG(/(gu1y=ҁSȣ4BS_;Qbϓ%iJ>{@Sm4mkTjǽʔW hz`FX@4vN dAa ->^[OuxC%olNML6 -t^(:{C.Ayd|Nkx9_<`T n*~aʔt =_r-kez$0(c#Vb_QuSo٫:%MJ]Y>) (Y[ L|v^NI ]PݷV   ӯ崇Xʵ9V?N~?R>cM㬹S81FQcsu->V`}Xvn`#++AVĕ-~)YWJ%ǧٴRi֕mQr1R>Q-1R>neq˙8S7B)(S.SwPJvE6MSA=A)w`!2%/N3ޜt$w?1- YI YW ߳W3_qo2 6znEVos_&Դ$ ^f8qlt?9wظ%Qə鵮2r8o9ʔ1(űt&i胠\(JXDXeN(e^E)D-8`f`V` Ap8 x|@AAA*HRm KO*V?:YKKϚnf-"WWQUzfXab+հr5(dG>?fLE)W9.SE)?+eyy1v]њq\LgN@)S,ND)?W[])prj8/?s>^ K&Dkaz[E5xnh%I&%{ݙl-r/%g˙ +jf5wV9sdۏwQ;Q Eqltڻ⚆/7U=3Tqƣ,3r]ike-%uU˯R:XJ'sN=}]J4Gj\^E)1V*ERQG3UʿGyuk5yx^S9I<I2yG@>$(]I$ P  L , +6`n/D ^\12ͪ*Y+hGn'fM|,kR6b'XxD&z"UoP)7F,)ʹG;]akѪGL֟}f=w'I|7\m-esJfI?nDŲKy$yfr.e~)$EI#1k7;w™.I<.愌PJN^9%)皗;S;L@B]pU|>mBBWPR'3?ޢ0 ;M# 3 -sfc~Su}"z }o9c3J?ܣc-*nwS*dcB/c%uM*q.EkԪ^UZ1i}h j44hiѠ`H3 bhJC< 648hpঁK? " ARiH!^QUdv6Q3vZ59]ESh-o #%[h-!_^ 5D{>݆H24!|%jϐBr6_3XS='WquVl)EIn'/^qJvȯC Cs\.vCN3p6@ {A涻ʤ5ܜtpN0s.3ѓdZ*{\brNnM>niG.Ui{SupHu67ϣ<đ O=I$KEG֘l&[#q}_:Z7:gSB{fٔҞBS -18)3T.Mt` -0@ 4@ t@0#03 ؀8x ?A4DUI\%EGD, ^b,'BW_\;MM;Uqkw]OO~3[8̖]|NFR$Ir6/)Q>>7SJ]%II~&#)#2v)Qs:g!$K]3tly*3_=NOf,}a\d\gv$Kߜ =tKv0?13Sd%]d:?J˗dBG,E럖]3pSWpqnMN?ʑG zUZS{7yVKC5&yHJSzkS5~c˫Qg5HBlUS[ 04dHJ;{rRsRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY|j[:%ZkNV׿Tma q5 .--ZjC,e/Ɲg[cu6"4YZdNVceF^r)gS"&jgs)##)_Iyo]y1P>H׿-{%Erd,HIR%IY-krW;VJ կ_f]-܍A}kv}Yɳ:ƎM7>[O'Wm &Ynww&p ޮ. bIªy8j1"ۈ&XfvU޻D#VD]!mI_*VH{M&W~Dz+ -[f?^X΂8:y+#gxR2|+x>ަ?ImT09S]{#c 5c?0߁,S,KMz멶G#)tHJ_̨u AZ{ZJН2y%rt)}4^+}g)S2)O494~gzL MUg }/ c&k5ziVdžD[L_J&8.;eek\MvoSvW0^T;zP|vAn&EA&lh>0r*)Û-om{S[մCiٮ_x^a=sBi/>}NQ:?r?vH}EU[Ww+RDJ<~.o}5UjGeJ*4hhҠAO@ f,4Kxl4ipMG <  ~D4ҐFC: KS 3#gnLH@W׈dN4!4j P[ D[g^$:r{QҀϊBm_U~WJZFtUJZFkHDBthؙ?$IA٨c. <u,kHֳNy^[|acy&~t:;?s˺ -q -2ͩLp1W $/r^$ޕWVvFF(#Jfb7gro;r=62Tk(4ՓwhNoű)C -_2:Gai[o9޶>ffbǟSX_ _863 -{ڻY^9ޱ|CJ>o -?R? vM"om["c*5-= b@,x`vN@A -@M" 9;45mv]ךߚ@ nn[h=cl Gk]LSv߸ C z؜嘿K?slԱ:3kRTFMVӗו}ηzL/2>'MҗJI_,2&oRFC_?)L|Y_TB_Έ~){LMco^Ed%e ,Jqe&]II)YYa>Z/%ܧl9h]99u4+rZ9M -}UA6N(MNSy|N_6[Jsgss*?ܦϩ>g |N|NAZ=tdBnp>Iʝft9tFwѝCgt9tFwѝCgt9tFwѝCgt9tFwѝCgt9tFwѝCgt9tFwѝCgt9tFwѝCgt9tFwѝCgt9gtTL[@=,jEQWȼhkh9EԏP3܀c.ݹ"7!z[[ ]RW#[$BnM|Ώ_r̪Kv[~Y!͹GJ2d{<{W| u;lcw-o~ȸ~G5l7i%\;Jn)\y=YI.'dg&2|\&LNYٜ1r ڥ:9:k4Mi>ѕMM9Pg~cњfatteqʞ ٛ?a'Tp-Դ,*4*#)ʸO]Ki? ?r -.>HX@-|>3_~6<& .eH* p)E kC}4#2\II|/ӕNN2p -I)}br|?*T{ARh 6ڢН84[!wM*U5UjT*vANAM- :4 4i0`BC 4XiF N4p4xi A!HC* i4@B@հT5A5/ q0'y*`9 -@:E[E^p{TvTɀTsfKw^Z& 22uI~' 225'$7і!w]{il$Cݒid FbVNO>Nv= CK!C6JA[ W? Q?2 5Tk퐡.{ ׮+D|<\\v2 e%q^.913;r:o \!қ Tig9N?~#e=ƪytsBe𣸘ؙwZkމckycy{(' |wN@xrgSgsfnڝOr6xD`h`F`f`1 XA<;p'px@~ Hi ,KyV=jYmcTkgM[y?kɥΦ&KKQEKX"|3+LQSb$gc$E+#)Hb$E+w}KF9%M4gS꺇VӔnO[.64>6JR%Mi^k^;9Ykso'_CSC4e|."s ASȖ~=lip q.7GIt;pR8]9,ޓRM{?svNSxzGW8[9R^gE9hʇr azj0D=F4DJtD?E k']כSMiR^o#락!\@-x)[:(H"RFi)r+oTM)=zK6ȞM$)醥lYIR>+cSW-`~=W}Eiw3 ,۔b6w:L`O? v$'e/ڍ\abr(̅yW7e9Wf()M ee>)˞M>T?R5.~R}cɍΞ?ARiuHJ{[}gRB%N̽IuB< -0@ 4@ t@0#03 ؀8x ?A4s:zUaum).'ڥ!vtgbcmQIhGRr6_=ᛈPgEV/mQ%-jctUfS&.[T5wҗQ)"Jύhdl&O~^VM9t -6e}YS[M9glxhʖSq2Me/i/zGR9$)+) dDJ H\bRJ2 M{:y~L}K5sihJ}-FÜ#Д|OwWxRk̅M)߾:r&.~ࡐil#th짙|}Qӎv$-Ў=tAIS*5-= b@,x`vN@A -@z̧eҵr~n˱Y6sSMy^o_`u\J|,? -]VS<VwMdGDFS59z   @: Rϫ9^sʦ=e Q;l7 6SPaMMi7mRjfk7|E3gOðg2ٯt̅g+()gPϨ1ҬE*V*NʤW {6UWe;TMn&An6MjS@IVm -6Td3kr:{߻YVuǞc3(gdx^4jPՠ=:{7&3zˡy'Ui[WLxiW^į"ƫ*Sv/ѡk™/>1)_@deQ| - өyBt5E$l/#0n+K~)x^19+GSXuj?3JXrKKE)D5h8`F`f` XA8D A@HRXuGpc6Sq]3_`Za%RR9[=ϱNys7sB)d}-I\(+}V)*E)V"EP[QA)?<')oTQFVo䔥ML5N +N%\)F)DpG)_(S&"8 H1j'P -ڠ{HL ]KnH[?ST S/JY~F_4!7xQyb| {Ub˟ )mб'U$ڠ%A=;cҪQ \@@0#03X  @^"@ B r)vu^"]kFv˦;l7ڍ6ӢͼhA;`qowͼ{}-vqA\6,E0 SL"9'SCŏ(,hmcʉYhE+~9 :dYkhfy!j_,](6N%N /T݇ x60ky>ffxS\~!L !ە"d6Ǜ#RmP>RZr[U{:6z]o9aŬ< |}X.McK-GV-/%S霄ʥm£?G^_7/C/d{JJ^ ~y~)Z_͋ 7/VE:  Ă8` xN@Ai KٙnDMM5:͉LI"לhü+\hYJ_0KO-7Ӽ3?= DtKt<0˝_*~Z|IV/_/ 6EfF~yENg7{UF/J~UF/3_~U˛" aK~c~_ܓ)K /A?5<%0KVRF#Y0z WFNN2},o>/o/Cw^nFB~)ͺ]KdifL2zi-ewee ~i'ebkB;l3/CCKi;Ҽʝ;J(~Y|owoj/K]9r~>Iq{/'Ik8\@@0#03X  @^"@ B r),.5GUj^_T -o(O4.q\[j_f_i]( coKZ>(N=ߡq_T_8/_/-lg[ә'_Vsω_aqSax'8Ī_:fN8v~!@>_~D~7_н!+D&ʐ1+#ŕ{ef2=>,xpRe -~{_ӵgSJ=J#E:@i*;)mBn?N1 uRC3{i: 6RƎ؉ u3Uֲ;F6?|Q9ڝZ{GmyM+"Flxԁ mjCAuhC괂F4?DrjRhR4hIё'!#@)fR,ĒGRl8Hq&E K)2)ARBN -Y=&岦Ev˺iY?-32']fM,ݗAC+24T&FJ,ũB}]ԝhHhEѼ0EC*REsN44r%=jØtfZ ݬhHQE]a;ECE]a=碍roSiGА{~UC= 4H^xzK O CCБАyC8C)Q5}ɾde$2+'%ORVrfF: Gwiucɥ>^ ƒbi~FfJ yLS_i.ɳZV4Y~ufլ{*Xoe& 1r ʏٷ\A)?Wf(J9u.~M QJU:ǭWlE)JyJyQQ&R*#ߩ"&$nڑLf PʎcP?R5@>iÚ *sJY!7x%_EߗKve)˗se${d%?+SYllR[٤yW.A[H=}QU:=E[VT5vI(- zl avGO6gPJ7YT7^t ܇o -SN(LPRA)3o)J% 4@ t@00 q -  >   @z_'YuGw"NX}!ǔq$ki5sz2t(%-39w1'p֗ϑ٤Q60CM*JW(EA)EH)uUʿ)?-{µ[#.ԙ]q -5U1gL/dr޴1%&sH1RGZ*(S ʴ }YunN}qĔky)gsRrO+$\9._ dH%ĔGSO:Sz6CϾ+hy){=i 3Ii+͝[vSW6iMتw׷; -9{Sv6)KRpJ]u4qN/S >t6)N% 4@ t@00 q -  >   @: RȪXzq VWYXC3kdM}ԓE/)[?ˏ^ƺXֱ.V`j6P+ƝO`(NɤVs2C_?oU bkoSD|.W'j=" NunUli@LiX sv2PU.і_::)%zL#dgx|.JENpeg lџ~&UmU4@L)нNA_+CST\OcR3nͷZV쫄Rjl|OEhsOd]2G4KbCR#Pb%22<,TKTh0`&` $8 |@At@.Sst?i䴝n'_aA 135 Cr,z)l,:8g? C'cXphXYG')JqR")1rhӹXGySg튓tf(;\ocR^0'E)[aN'ryU᯦ӽJ7t>M#nڙ]sV)ΧJD:.RzɜQ&t>܂g1(5m'ɂKâLred~')C٢)&\U؊KrB|uuE}3!pJRNЙF4/[cgkRV`[y;ۿgj)w;0݁2,F,zMvA)}Oj`S*UՀ:  Ă8` xN@Ai K3,լiV7kX&XCk,Lc9Rڎ|䜧Yw5+,\W-Xp'g#(%YQʌ>RIJ \gW.h| (j?*:(E!1E))Ea0/Mw(pA)o@)mS)%*(D6oSvaҗvU4>=kC8)Й)Is$\p8%x]I gy~7hU)j.L.AJ:kf<o1|3;nmv#߿7j:bE9 !,Rt)eq~0+Ks H);`RY_1j@ pbA` {d-e|H%,+͖DrV8)kRr'VRK]]&`x8^7 @)h|j|n@J)94j;@ҵ{1>ac+ ow{pE(rJRzO   @:83ê3t/9hX?2,7fYckf͵) Jams,:JY3)3'@}mӳJ1۫ "(eJQ\A)SD) NFEx?3F\-cfSbݫNNq%SVoSSNRjSTnq-1)zwO) "|7=H9'Bt('iE5~jɛ)S\9B,uI).6()Ld A3-|(^[O&0 - ?ԙ?r*TBRS_OhU?g;*9zpJL=4*wl2 -$UKTh0`&` $8 |@AsQ^`5#N1,WFXc+gjcsuJr\YɹXαiN~6ELܬ|3SSJNўDAuY>HLgn!^a0Z% +JY0A)oR.:4rI;/,BLDWPJ"pTA[@LV!Y?z瓔H2\9ŗ$y]2#J)oR˛TS|r)_]ʟb@પ yecIiU)=cPJ۽[UG|펚m(nJ{JiOGbYP9IS$,Pʱa(eME)D5h8`F`f` XA8D A@HgGn>vlSϜ)|&Xc#kcGYK5|ILdm,?:&Y.>;r6K>g1ȑDWrv))7:lRUE+{Qܾn$wQڗ6RwQLWs}ylxbSQ#[,ݯn{Sܭ lG)~2wrD9_^V[V U~ZBJ77 - xR%w-my1IkkQ6 zՀ:  Ă8` xN@Ai 屫[xzԮ9m.t6a;d*xéoan)'g MY;V{E.VR>܍7)" Jf"WLY9pNVӖfD냲nL:մ7*~Q2KeMC*~is˨>Dt-ˠe/_16e)ڠ}ˡ}-ˉk<7}5Wx9~IIBf\)a_J3g>A-wUVӾRm;<9MU> hJe^BfEf?ҋX ׆֏;, Ud -ڠշJS v2=RV,cF)x6[1[VJeE)qs]8;7rʱt&+p3EMqJ\ҷoU\297)ܴUSR2Y ʴ;oiԇpwkۈ}h%%9ILpeg{.~J]*ٙ))9R8yrY>_P2K5nǐYv!٬[nDf.FYg6>Dfzb2KXF6#Iz{l-?!yeUc+*o_s*.JZDfiˢJ}CUkDŽJ޻#&lPوUh0`&` $8 |@A'W.Lgyn̐hA -ΏEf}&4.Q[.l|v3+<#s.S4xU;6<ŒΔG>Ha^׫cT\N|Ώ-_n m]ew/_*.\ /o_:!:U%_pvzg'12qiTbWU_L$4~Ͷ(| }~l]VWXV =Yk+RM2zR%ܵ ->ߵ3&X2Uh0`&` $8 |@At@.eHy\"/^乼D>XhK4W$ZFAP$,E;?;:V^佳opb`W7\ǿ wn<<]謁Ei׆iMgNmxӊ`. -`N+8`D/`n?Dї!Cno;Wsw  ӻÅSph itaC8n?gȓOt=dț#Y2]Ox̔7&fWORyJ]H%(OSڊo֕j;}Ko6TO;MŹ'(+;,ewwn*aFVqDWQAl|C}C-Vcj_OqY/ %^(U*`աAuZWVOR &&EC)zRR8R I1b&BJ,)qXII F -O')nRRHIH "$%DJ)餐-OU#]Y_,\l8jI5U'S-2<4BNR8.je,Q+;'eTNv}SˁYY^7\$DCMQORH<'9wi-̿D^r)r Y }h4ds.V/F\{ӍN=IhUCE<4s"m44$ȧ2}UiAZBt4bКpN!_R x2\IILrdlO/;'akROȼK]UL-uy# -LUuoGbklOϓTC[S {l#-a_;ڶ9gʎlMiF=CRF=C{Źr;;ٿMPodZZSN -җCtwuxj5ٌ/E뻑l^o4{:)m[ͺ$'PL7;(cTea;(D5N*>$'l!=dSBͩWljI@)!G)Ei9y@(>$J!&&EC)zRR8R I1b&BJ,)qXII F -O')nRRHIH "$%DJ)餐-HU =+UWԟZkH5tE6NtˡT|*ߘ :RA:;+fSSR偣/a)h($u I1Gp~޽33P ʽ(z e*Q4<'ke#8h [ ɪp 7gShfzgN=mhh5<.z1+, -,Wve.d -y눴>L=ؤm-T.uy9"hm#B~IVw{KH6{e^c,Yc C)bm~%>f|A]{ɦ/H6=O" BSƑ$ۑlF@OI6D5h8`F`f` XA8D A@Hg.% ft~e9#LCy,Ys8G5.p"N弋gM' VRr2hV A)(JqSѶ5>pNflgѶ5n_;fs^Q'/U:}sC}(. *%H6V2(ɴ}(J9<~˚p_%$K+%;GR'edgz)K''jT˻nkeuPumc)shtn_Ƞ]7B^ڿP۱27_lYx2b9<%cv;t5%59lxCݘfR{p)tZjywt Ew4kLNjvY/&/{~q:ڋ]GtڮR1eR1ᑢs{b"?36~9m/ui\m9{^>EdA:d. -7yq۴'z$JB79ɕ$+#GHq$gHPN8;)Bߪ&UQe?RK[/ AA[Ako-{Y&_O7WX-Ʈ4"4bmO뱵WsUms6 {Yg)_s˭b Y"P3"G|7u 4/)mWK],'Sv.'v+~ZxpC8)EFr/#ӕe$2|)XLhR?)oszIA}֝}ި =;=ƮuGe-Z:N>}b0׎9+(uP<ڠ÷#J -,?FnDHr ~5h8`F`f` XA8D A@HgdNjYr`T/βXΙX(k)PR -8sT~νđ=X_'r>.0ɥ%-ITi딘rwh,BNN꽻"uS*bSVO&af8yѵR>裝!Z~p5>S':Ju$o1܇նߏ>iAۇ>#A5jՖ(cpj@ pbA`/i*ސhͥ%A*K|h;yw-/'zk}8KS|`?ܥ}WL\*.m9Y|ף?)kU7o7ҕW3}fM^sK%D=d݅/-#7*wΦo %/#ҶD%?@>EUS_ѡ~}=kADA2}bYw#gdB-7)%fg%13O!iww 훸RO 6Rt_s)yҎܬ+edb뤸› (c4{/y;,;b}]w*;ݍ#*'TʗTζOݕ%OC=j`kj_ 1(ibrc`AuhbePPyh5)4)RHѓ‘b H3)RbI#JJ)6RxR8Iq"%GHDJ )!RHI'\올JdMu?UכoT80-WRMAslN*RRmABv:[Sp,;[rYjN E;}5)vJg>pߵ1]L{ ʔ{:C|4/׮ \DnUCϗAC:͐1o OUC_)D|b{׭yo)9HO8+ɑǓA#ueys2~OdwU7Sʾn򖑽:ςfb@?;ǣ/+z$ްX1|3|ry:jϷIbmO뱕Uꇷ9#";:ao+NJd_ȩ],rni2KTh0`&` $8 |@Atpv8f8Mmtc94k䌅i5wq:͐}- 8G ɹ8r]!eFN}"E)Q)J;}fyFN6y(G~_qʡ-CW}~ERmpL< ܐ ݷKpJD:+SCLڷ HJ/*ܴ!!^O7e)^N)C\^ /9ytuMhQ9neƥ>CWgxlG_2&0C}7Po8^1.ԙ%ܓk̞$GN~zԡ| wow+VXfYC=kdMGϼ^³CX~u9֝ SY rð[ǔH/2T:%KEM>o+AFNyc:[ ~Q_*=)F}G۩Cw]Z' ^8AN8 -ܵ)@>K^/;|Nh.Zs*e,+9YJv)KeB\6N5/ȖSd{$ٞԝjJdG{9!D7/W&4M/C2,d!JP/Q#9h*?qqf_BCP?pYhhvvL}_ - տs:P<4U ie^rjRhR4hIё'!#@)fR,ĒGRl8Hq&E K)2)ARBNʙR2(kem[C22jI5MmyP U 5ʶIu :RmBWwZMb,ͧS 5t$S }%.s[+scOHmHC#LgR#/̻Oפo];ߺ{N4t0j܋4TЪ&h/3PVhT/mW]D‹4(07;П1ҙ& e.-ZjE[ݿOcj vosV]%3qW̭b~c4):a]F:%% 4@ t@00 q -  >   @:8;j3S%/GXfϼ3UsJBFmɨM=k+<"vY͹Y*Ό4r~\%ٮ(QѴ+J~\wN:qC3MwI~)g}FPo_뫍FPCEn8t$[R~RnFqkL;A65DD)OyO)nX/+#khM)! -BfPģGAz_^zE ڹu;=ӯL]W=ֵtsm2kxML|PV77OnwTl~JoRN=JyJg߈R2r9hR*Ȼa(J% 4@ t@00 q -  >   @: ê[(ӎ3,jY.5Ϝ=tsMϱVYƹ;X -.Wìw1MaԦkJJʵk])Նd&dnoj(0FM\tC@`ce V AEEDbqP"dHL|I_ܛ5wP[nQP -%A)P"JIx,JY5'tÌ焿R7uR -"JY$Nagd9u`ɒq |:vKJW, Rk,_='W-t_^ǙI9}r7q̊4uvKW4a/:eMDwcw&)+h?f$mVmD!b*zI%YIɑ2ֺzbp}ǵF(e(H?zE\%(s(eE~tRT8)((PP 00 HV@;p`xp 2@&BMydITWMk#Vb$R}u^A=5AF nȊT% -;B8_D)"J9X}m(4& ' -Z5?^z D*~dvuҹ/1HB)N(FK((f%x{OeRg"“{?JIMc|yl'8=[:ors3>Q=ǿTV^j>U"/Y"QD,QVԔJTnc%MImySD^>\T-1TF-?fv6H,}uo'X'K?ػoܖ8*Ƥ1)3/u.|[V, KCgoIc`sùH0 -ȁ( -h $+8 0 < 8 +6&UaGllZu7E]NkmBw WRGlS]uGh{8AL]c4x47b C~=7Q RX13N- %=' UʸDBEb1/ru?~y،,E9c:!dAcfe~jZ?HO7"dioԼT|sҜ\$@>MsB0O$di^W>px*d _v*JY:#jeU&)KXU%FvKQ[AM2,U[,;[YkxG=z}@=YmF2r!KޞXBY"q> z!Ka!d-@@@@ BD3-F'N!B.Fw z`L,4q\l]w7 > "Fhw򴠔YRjqERnYg.鵮xSffd0~SV{TD{"!<N؄:D"KNV_Pʹe.޵kj6T_eX}!HtJI-ˤmoW_AxBm \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/catalog_sales/metadata/snap-1524015146186969396-1-bffa6a85-351c-4221-9149-e24f845e0aab.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/catalog_sales/metadata/snap-1524015146186969396-1-bffa6a85-351c-4221-9149-e24f845e0aab.avro deleted file mode 100644 index 58617dea3e850699c812aa285b174fd3e3e05168..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3817 zcmbW3UuYaf9LG(xP)m$OVyGH%y7dp0#NH-%m%Bruk+zr^R7|B}iO1aTTyAA==ej$a z^Nc4&ErJSpkVX_EqWEMfiVEVNhk_L!s?-Oq4|(vxKD3Bh3kEGD&g`F=*}L1gm$&;f z^ZWdMGr#ZeI}ewh>ggMV^EST6(JH74|Gi~FA&nYR3W|+1Z0pLfV!K z4HMCy%LX!aM=2a$Xj@T(q(WB&fgEBR)*St=V~xg{#u_9Bw!mYLPJ+5YD&W8j@aR11 zh@mTmOe)nJZjWsuYfg@CVk!1POrQx^g1|AJLBOy;sX>qvrW*uSrNA|46NBYbbZST| zQZi56un5n3hW-gA_W{>IIv^O-ZG*JtJ1T{Rx{masW5JqJ!9)&wSg~Bwq=CK6XTxKe z36?SlsXzj}2=pTY;mNaPP#L-=Q3@c~Wdo^qGaeg3xO%&f%Z?ZSF0dYW80 z(6(WN{!KfiN(I7yZ&j3?N#*FWb|GolBG9m$V$dmpkV+Vv2wJQSJS82pUE1Ph0Ii1K z^A+d-2-qgT^91wIbXz+$FI6O9Ug&5|O33705(kA=2WEWxs5vPG6W5VlEYThn>f=4? z3z+jC3H1uRUBKG~%Gd^wmd&KkZe|0pJXPFUZ4+Q`SNCAGIoljoX!BL6#sbmin!Kq{;3hV8nYDNYrPgoMf4x|Uc2W8j%uSLu3;nUjx-bs2awW* z!?I@aDQY`~dut3Qnb!)$C`a&dB)M!1Logu}xCA;ABr1pezT4;@Tu9R9a`uILGiNU=up#n7CL>`l6uA zTE->+L9$gYE6GUx7SD6EFggG2CFl_$g>JVQhvf&W<~m?KZxz&NLng&`@K`~mlO%Yl zN9{BgYFN~6n74S3i6p)%U}AVgEwpP`yq8AYQp9EVMQzkidIQfCxr!;&!5&56=#@lf zM4L^D;9>%CwMHx|QG6~`$*TlH%$!iC-4jBI%7EGC?NpKMEfS7R>PTDJ>+!1XUtKqG zW#{6VUtisJ0q?rF*1OVs$15N8_e|_~@!Zng*T3`Q1J8Z2@b@))cCDXW|Niu{RyTIv z_tO2lFZE9yd*aJ#vm@t!e@k8aa^>V7=l=Qm{AzaYc;mg7e_AS>7{tqet(Tr%&ENZG=JJ-- G0{#OZ8zEo- diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer/metadata/00001-bc8cb648-42d6-4481-b109-a6e77d653c8b.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer/metadata/00001-bc8cb648-42d6-4481-b109-a6e77d653c8b.metadata.json deleted file mode 100644 index 9dd23d74f655..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer/metadata/00001-bc8cb648-42d6-4481-b109-a6e77d653c8b.metadata.json +++ /dev/null @@ -1,265 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "10c0298b-2178-40a8-ad81-7c732d3ded47", - "location" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/customer", - "last-updated-ms" : 1663712676249, - "last-column-id" : 18, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "c_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "c_customer_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "c_current_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "c_current_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "c_current_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "c_first_shipto_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "c_first_sales_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 8, - "name" : "c_salutation", - "required" : false, - "type" : "string" - }, { - "id" : 9, - "name" : "c_first_name", - "required" : false, - "type" : "string" - }, { - "id" : 10, - "name" : "c_last_name", - "required" : false, - "type" : "string" - }, { - "id" : 11, - "name" : "c_preferred_cust_flag", - "required" : false, - "type" : "string" - }, { - "id" : 12, - "name" : "c_birth_day", - "required" : false, - "type" : "int" - }, { - "id" : 13, - "name" : "c_birth_month", - "required" : false, - "type" : "int" - }, { - "id" : 14, - "name" : "c_birth_year", - "required" : false, - "type" : "int" - }, { - "id" : 15, - "name" : "c_birth_country", - "required" : false, - "type" : "string" - }, { - "id" : 16, - "name" : "c_login", - "required" : false, - "type" : "string" - }, { - "id" : 17, - "name" : "c_email_address", - "required" : false, - "type" : "string" - }, { - "id" : 18, - "name" : "c_last_review_date_sk", - "required" : false, - "type" : "long" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "c_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "c_customer_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "c_current_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "c_current_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "c_current_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "c_first_shipto_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "c_first_sales_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 8, - "name" : "c_salutation", - "required" : false, - "type" : "string" - }, { - "id" : 9, - "name" : "c_first_name", - "required" : false, - "type" : "string" - }, { - "id" : 10, - "name" : "c_last_name", - "required" : false, - "type" : "string" - }, { - "id" : 11, - "name" : "c_preferred_cust_flag", - "required" : false, - "type" : "string" - }, { - "id" : 12, - "name" : "c_birth_day", - "required" : false, - "type" : "int" - }, { - "id" : 13, - "name" : "c_birth_month", - "required" : false, - "type" : "int" - }, { - "id" : 14, - "name" : "c_birth_year", - "required" : false, - "type" : "int" - }, { - "id" : 15, - "name" : "c_birth_country", - "required" : false, - "type" : "string" - }, { - "id" : 16, - "name" : "c_login", - "required" : false, - "type" : "string" - }, { - "id" : 17, - "name" : "c_email_address", - "required" : false, - "type" : "string" - }, { - "id" : 18, - "name" : "c_last_review_date_sk", - "required" : false, - "type" : "long" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.7.ndv" : "3600", - "trino.stats.ndv.2.ndv" : "12676712", - "trino.stats.ndv.16.ndv" : "0", - "trino.stats.ndv.4.ndv" : "7082", - "trino.stats.ndv.5.ndv" : "5119047", - "trino.stats.ndv.18.ndv" : "368", - "trino.stats.ndv.9.ndv" : "5151", - "trino.stats.ndv.14.ndv" : "69", - "trino.stats.ndv.12.ndv" : "31", - "trino.stats.ndv.10.ndv" : "5019", - "trino.stats.ndv.1.ndv" : "12236563", - "trino.stats.ndv.8.ndv" : "6", - "trino.stats.ndv.15.ndv" : "211", - "trino.stats.ndv.6.ndv" : "3597", - "trino.stats.ndv.3.ndv" : "1889141", - "write.format.default" : "PARQUET", - "trino.stats.ndv.17.ndv" : "11748147", - "trino.stats.ndv.13.ndv" : "12", - "trino.stats.ndv.11.ndv" : "2" - }, - "current-snapshot-id" : 2201162249508106077, - "refs" : { - "main" : { - "snapshot-id" : 2201162249508106077, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 2201162249508106077, - "timestamp-ms" : 1648081220181, - "summary" : { - "operation" : "append", - "added-data-files" : "6", - "added-records" : "12000000", - "added-files-size" : "464014522", - "changed-partition-count" : "1", - "total-records" : "12000000", - "total-files-size" : "464014522", - "total-data-files" : "6", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/customer/metadata/snap-2201162249508106077-1-384651b4-d41a-43fe-9b7b-b1ce97b12f81.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1648081220181, - "snapshot-id" : 2201162249508106077 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1648081220181, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/customer/metadata/00000-c81f4e20-55e7-408e-8fe0-c6c22a7d8db5.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer/metadata/384651b4-d41a-43fe-9b7b-b1ce97b12f81-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer/metadata/384651b4-d41a-43fe-9b7b-b1ce97b12f81-m0.avro deleted file mode 100644 index 8808136ba1b8242e0e3d5535fb6cf63f2a8b05c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8577 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 Bb?*QG diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer/metadata/snap-2201162249508106077-1-384651b4-d41a-43fe-9b7b-b1ce97b12f81.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer/metadata/snap-2201162249508106077-1-384651b4-d41a-43fe-9b7b-b1ce97b12f81.avro deleted file mode 100644 index e8c6654c218dc24b8d2195befc794a9e77df73aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3789 zcmbW3O^6&t6vvZgg@DT@pvIV4Nu`mXxCz~}Gy8?QtYnu2gbhv-4{;c4x@&e?`eW>_ z-pv>$_<^8kWG_*opb|U?UIGciUSbjw#fyX-5;We#n-ByQ%tc@IM^*LA^xCtx`KWro z_p09i{cEZI>ei8+c*S!M_jaq&s<$celXT<2niRN&J!-OiYJQ98H6ZiE zO^5QVEAS^ed=LdbF%Wgps%O&fe2Y?8Y#79-`3`RRO_$1HPbf}cSr9nPyqX+KC0@!P zw23JSBfv+3l9Ol2kOmGcs?4$&@g2lPP4v(WOmTQyeoc3Su$bsdcnYjSp20jz84-ti zZIrCvfLD@uf=2c37GSb zBzi^RE)?!U4c9{$%4X7MH?sj)o+?4BwFz*zYjCjEsdmN{Xg(V>7K?Ve!wahfwv9o0 ztQ3OvrZ~rtnqgN&w*=dNZdK0S0I$+Pu26tBtijVnGsjEg-lPo8Lr*7M^MndhaI z;Cdbotx$IjqK`rgS(uWY$qa`Z+gB{Anl@Q1mS#Y@nmVaYO^v~Q*_iHMA)XH|GNg@} zI;j(HId;@bySe_s2m_s2O60X+3#cjm$G+F=xv`^qF81ES7P?ke(I0cc>$7sr(n!Jn{ilvu-bGVZ0N0mnr_Iv*ghUBs4z+5mwMVxGohvf zcEh|Sd`#r=RRI&jBW6P$jPt2r+XKoeoY&Bq{@{ohLw%>@AXvO>m@b?De?igIzzJTln|T8-+6$ zzWejap8G>z4n6+<{ODWijxBF~eVW)$AG>n>``0wa*s5`fFtA^7_laKDj=3`SUv$ U@7?*K^xoo+_wE|+&UGjGAFjC!OaK4? diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer_address/metadata/00001-b07d6011-b792-4256-b355-f7a7bb59ff30.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer_address/metadata/00001-b07d6011-b792-4256-b355-f7a7bb59ff30.metadata.json deleted file mode 100644 index 4897c3863dcb..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer_address/metadata/00001-b07d6011-b792-4256-b355-f7a7bb59ff30.metadata.json +++ /dev/null @@ -1,210 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "ab82a1dd-e6f2-4b02-a632-c71f0e4f7b9a", - "location" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/customer_address", - "last-updated-ms" : 1663712690547, - "last-column-id" : 13, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "ca_address_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "ca_address_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "ca_street_number", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "ca_street_name", - "required" : false, - "type" : "string" - }, { - "id" : 5, - "name" : "ca_street_type", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "ca_suite_number", - "required" : false, - "type" : "string" - }, { - "id" : 7, - "name" : "ca_city", - "required" : false, - "type" : "string" - }, { - "id" : 8, - "name" : "ca_county", - "required" : false, - "type" : "string" - }, { - "id" : 9, - "name" : "ca_state", - "required" : false, - "type" : "string" - }, { - "id" : 10, - "name" : "ca_zip", - "required" : false, - "type" : "string" - }, { - "id" : 11, - "name" : "ca_country", - "required" : false, - "type" : "string" - }, { - "id" : 12, - "name" : "ca_gmt_offset", - "required" : false, - "type" : "decimal(5, 2)" - }, { - "id" : 13, - "name" : "ca_location_type", - "required" : false, - "type" : "string" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "ca_address_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "ca_address_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "ca_street_number", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "ca_street_name", - "required" : false, - "type" : "string" - }, { - "id" : 5, - "name" : "ca_street_type", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "ca_suite_number", - "required" : false, - "type" : "string" - }, { - "id" : 7, - "name" : "ca_city", - "required" : false, - "type" : "string" - }, { - "id" : 8, - "name" : "ca_county", - "required" : false, - "type" : "string" - }, { - "id" : 9, - "name" : "ca_state", - "required" : false, - "type" : "string" - }, { - "id" : 10, - "name" : "ca_zip", - "required" : false, - "type" : "string" - }, { - "id" : 11, - "name" : "ca_country", - "required" : false, - "type" : "string" - }, { - "id" : 12, - "name" : "ca_gmt_offset", - "required" : false, - "type" : "decimal(5, 2)" - }, { - "id" : 13, - "name" : "ca_location_type", - "required" : false, - "type" : "string" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.2.ndv" : "5976963", - "trino.stats.ndv.7.ndv" : "980", - "trino.stats.ndv.4.ndv" : "8279", - "trino.stats.ndv.5.ndv" : "20", - "trino.stats.ndv.9.ndv" : "51", - "trino.stats.ndv.12.ndv" : "6", - "trino.stats.ndv.10.ndv" : "9232", - "trino.stats.ndv.8.ndv" : "1815", - "trino.stats.ndv.1.ndv" : "5947530", - "trino.stats.ndv.6.ndv" : "75", - "trino.stats.ndv.3.ndv" : "1019", - "write.format.default" : "PARQUET", - "trino.stats.ndv.13.ndv" : "3", - "trino.stats.ndv.11.ndv" : "1" - }, - "current-snapshot-id" : 3308787911813009439, - "refs" : { - "main" : { - "snapshot-id" : 3308787911813009439, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 3308787911813009439, - "timestamp-ms" : 1648081228935, - "summary" : { - "operation" : "append", - "added-data-files" : "6", - "added-records" : "6000000", - "added-files-size" : "71390909", - "changed-partition-count" : "1", - "total-records" : "6000000", - "total-files-size" : "71390909", - "total-data-files" : "6", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/customer_address/metadata/snap-3308787911813009439-1-e4e9211a-0cd9-4034-9266-c7722c1b7559.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1648081228935, - "snapshot-id" : 3308787911813009439 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1648081228935, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/customer_address/metadata/00000-3c5e5c24-68cd-4bfc-b020-4ea48fedcbe6.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer_address/metadata/e4e9211a-0cd9-4034-9266-c7722c1b7559-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer_address/metadata/e4e9211a-0cd9-4034-9266-c7722c1b7559-m0.avro deleted file mode 100644 index d6eca5971c33ab9b12eaefbf7735990afae5f61f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7793 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 b*6dA9Rgp~lo&k?_4{x>BDSNpi=l1^ruax^l diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer_address/metadata/snap-3308787911813009439-1-e4e9211a-0cd9-4034-9266-c7722c1b7559.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer_address/metadata/snap-3308787911813009439-1-e4e9211a-0cd9-4034-9266-c7722c1b7559.avro deleted file mode 100644 index 9ac258f6da61982a148e32325d95b6174c41e238..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3796 zcmbW3ONbmr7{}KILBxchOTav!>}W3TYIkOK=Ya@oV%)esHkpkO;xg8B*KD`x$5>sx zlQA+#mpH{7xvu3_&UUpJ8h|K52@A-xV#>Iz1>hYe*~aiPP$JJ7Y-$S2LVWh44? z+d{VCEA`{2GAlZem~=%P$S2rxSLt`d(`lSXJRK6tbHULghd|dNCOFstA)QB^u?(d? ztEz+PY-}63t8#SPlVUG>1loWl2z={V1S}V{dIb4Nx^ZAl3fzI1SS+8W-$8nll6m5m zOL*1|`X@Ma00cfV0P#Qa0XHkc|jEQa}txc3+t)$X>#R2jA4)d zO*_P)0^z?8D+O`dOD@x@_W>_vI{Az+)OJ}q zY9YKDJL-o!xc;dT20F8p$ZNwEP*eJkeJ|^|v7=)2OkiO|-I0bu$pBKua9GwX5k<>V zxVPqTig~R-?BfVNjwF}Of&9Pg3P6biqRvn5D)L(aXhYi<$CWChi0=wuc?dYddh)1p zU0JIYh*^%{8rvj=3r_ZQ0h;5WL|mKXBuX2Nsc;N$0yd#@j){xKrY{P*Y-n8aA0%7l zvXYE6YVjIJ3zLi9UV@$yQsQ=-aaew^T6F|A^j1MlH)K)l2#*z1I!WS}dfHBNp_WVS zhIvc)m?+|_0w#t>+Cs;M<$Gz$Ek#^*U(`mUq&M(Pk*k&67?tksvZ3izs^w?$dN@dIEyZ3+cJpSX$!%y!2z5mqZSD$!z{>-&+8arNp z>q_nXme1}wz3uF=x%I369-05aeEE(3`%CX!oH+6B&Ha^~znp*XtM49ovwiZVKd-N! Z{ldS{KmAkpj<=QT{zo70)&I%l`X4d35;y<= diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer_demographics/metadata/00001-5c1100bd-e0de-4f2b-bd05-5f9c3a482dcb.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer_demographics/metadata/00001-5c1100bd-e0de-4f2b-bd05-5f9c3a482dcb.metadata.json deleted file mode 100644 index 6f3b6a6f6705..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer_demographics/metadata/00001-5c1100bd-e0de-4f2b-bd05-5f9c3a482dcb.metadata.json +++ /dev/null @@ -1,166 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "f8e17ec3-c3db-4c4f-8130-42adfce65fb6", - "location" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/customer_demographics", - "last-updated-ms" : 1663712694391, - "last-column-id" : 9, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "cd_demo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "cd_gender", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "cd_marital_status", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "cd_education_status", - "required" : false, - "type" : "string" - }, { - "id" : 5, - "name" : "cd_purchase_estimate", - "required" : false, - "type" : "int" - }, { - "id" : 6, - "name" : "cd_credit_rating", - "required" : false, - "type" : "string" - }, { - "id" : 7, - "name" : "cd_dep_count", - "required" : false, - "type" : "int" - }, { - "id" : 8, - "name" : "cd_dep_employed_count", - "required" : false, - "type" : "int" - }, { - "id" : 9, - "name" : "cd_dep_college_count", - "required" : false, - "type" : "int" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "cd_demo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "cd_gender", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "cd_marital_status", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "cd_education_status", - "required" : false, - "type" : "string" - }, { - "id" : 5, - "name" : "cd_purchase_estimate", - "required" : false, - "type" : "int" - }, { - "id" : 6, - "name" : "cd_credit_rating", - "required" : false, - "type" : "string" - }, { - "id" : 7, - "name" : "cd_dep_count", - "required" : false, - "type" : "int" - }, { - "id" : 8, - "name" : "cd_dep_employed_count", - "required" : false, - "type" : "int" - }, { - "id" : 9, - "name" : "cd_dep_college_count", - "required" : false, - "type" : "int" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.7.ndv" : "7", - "trino.stats.ndv.2.ndv" : "2", - "trino.stats.ndv.1.ndv" : "1890006", - "trino.stats.ndv.8.ndv" : "7", - "trino.stats.ndv.3.ndv" : "5", - "trino.stats.ndv.6.ndv" : "4", - "write.format.default" : "PARQUET", - "trino.stats.ndv.4.ndv" : "7", - "trino.stats.ndv.5.ndv" : "20", - "trino.stats.ndv.9.ndv" : "7" - }, - "current-snapshot-id" : 7513288259375812719, - "refs" : { - "main" : { - "snapshot-id" : 7513288259375812719, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 7513288259375812719, - "timestamp-ms" : 1648081232219, - "summary" : { - "operation" : "append", - "added-data-files" : "6", - "added-records" : "1920800", - "added-files-size" : "2239967", - "changed-partition-count" : "1", - "total-records" : "1920800", - "total-files-size" : "2239967", - "total-data-files" : "6", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/customer_demographics/metadata/snap-7513288259375812719-1-da89f5c2-ecbf-4b3e-8734-19add5f223b2.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1648081232219, - "snapshot-id" : 7513288259375812719 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1648081232219, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/customer_demographics/metadata/00000-9e16afbd-444f-4b6c-a2b5-1c50ab83f764.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer_demographics/metadata/da89f5c2-ecbf-4b3e-8734-19add5f223b2-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer_demographics/metadata/da89f5c2-ecbf-4b3e-8734-19add5f223b2-m0.avro deleted file mode 100644 index 4c3513456370429fa303b93e4f7e5c14b70c41dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6940 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 P!Ljdd?ruKrSyTT9(YBdK diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer_demographics/metadata/snap-7513288259375812719-1-da89f5c2-ecbf-4b3e-8734-19add5f223b2.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/customer_demographics/metadata/snap-7513288259375812719-1-da89f5c2-ecbf-4b3e-8734-19add5f223b2.avro deleted file mode 100644 index 2bd3f07fbfe6e76c383f36e4d55821350b9ee15f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3800 zcmbW3U1(fI6vvx{f>3GwNJC8`j2Fp^sqEckv%BeoEkr{>w00{(LfFjSJ2yM^eysP- zO}eIAv3Y2~BBCIOD2k#FLSIzSN@Br>6u&`G`&20vDfnbWL|>e_A2V}z_r|+<+mD&^ zJ7?zn&wn1To$kAR2rj$MgPvAL7W`*qRUyp=Ql=FbX^yKa z*}%xuJ!N`+wPQsCk~&=x1@efC?IrqMcQhL3QAdLWJ2rUox^K?d|qqD-?G(H%rZO?1$8NMLYVUR87apqTJVcnYjSp20DeGC($Q zn;>4l18-8|p)=Sc^pdMRA|H-Y%@ClBda)16>z3 z>EE7+9g^=t$d7TgZe24aGB@-q={NpibmVrF@}0Q7n|yd{Hfr()+R?IlPQqkGe>| zI*K(^L+(=Ks2>e;{Zk|KHD)QH*9I++OXxrHy-v@K9Mwh7_}E3%9cd^O4`l6uA+QudS zL9$gYE6GU17B6$OFuCyUCFltu#csD7hvf&WmbzdAZxz&JLuSQx@mN8nlO%enC+##9 zifw8)%v*xTL>6BaFflxm7TPl`-Afa0DdMvGqBa^Py@O|pT*VaXVvizl@=6jjqQfRd za4~_nS`!wPDLxmfv7MYBe$N|`^UA7 zqYt&V{`z%myZ>_ky%*K}=W~NcpTG9O+4;Hk8|Z82-+eO={5{=(YY*x02v zuMC{&dupKh#p(}7jANG$?Ynbh`0V;y_4h|EoV)wV;>?eyPu_fbePgQc{5R#_zJKTX zfx+3?_b%LVWcHkO)&F>F6Z?xVDvu4Uef8z5b9*p)`^3wOpT4yB=E330+L* i2R1*mU-^BzwD9fcKW)EZd^rBvC)YP`9eS=k-~RyYpcr)k diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/date_dim/metadata/00001-17d74180-6c5f-4ec9-9bf1-1a17a5e7cc1c.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/date_dim/metadata/00001-17d74180-6c5f-4ec9-9bf1-1a17a5e7cc1c.metadata.json deleted file mode 100644 index ebbcaf860b23..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/date_dim/metadata/00001-17d74180-6c5f-4ec9-9bf1-1a17a5e7cc1c.metadata.json +++ /dev/null @@ -1,375 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "266b092c-6ecb-4d1e-ab7a-9fb1caf33152", - "location" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/date_dim", - "last-updated-ms" : 1663712698177, - "last-column-id" : 28, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "d_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "d_date_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "d_date", - "required" : false, - "type" : "date" - }, { - "id" : 4, - "name" : "d_month_seq", - "required" : false, - "type" : "int" - }, { - "id" : 5, - "name" : "d_week_seq", - "required" : false, - "type" : "int" - }, { - "id" : 6, - "name" : "d_quarter_seq", - "required" : false, - "type" : "int" - }, { - "id" : 7, - "name" : "d_year", - "required" : false, - "type" : "int" - }, { - "id" : 8, - "name" : "d_dow", - "required" : false, - "type" : "int" - }, { - "id" : 9, - "name" : "d_moy", - "required" : false, - "type" : "int" - }, { - "id" : 10, - "name" : "d_dom", - "required" : false, - "type" : "int" - }, { - "id" : 11, - "name" : "d_qoy", - "required" : false, - "type" : "int" - }, { - "id" : 12, - "name" : "d_fy_year", - "required" : false, - "type" : "int" - }, { - "id" : 13, - "name" : "d_fy_quarter_seq", - "required" : false, - "type" : "int" - }, { - "id" : 14, - "name" : "d_fy_week_seq", - "required" : false, - "type" : "int" - }, { - "id" : 15, - "name" : "d_day_name", - "required" : false, - "type" : "string" - }, { - "id" : 16, - "name" : "d_quarter_name", - "required" : false, - "type" : "string" - }, { - "id" : 17, - "name" : "d_holiday", - "required" : false, - "type" : "string" - }, { - "id" : 18, - "name" : "d_weekend", - "required" : false, - "type" : "string" - }, { - "id" : 19, - "name" : "d_following_holiday", - "required" : false, - "type" : "string" - }, { - "id" : 20, - "name" : "d_first_dom", - "required" : false, - "type" : "int" - }, { - "id" : 21, - "name" : "d_last_dom", - "required" : false, - "type" : "int" - }, { - "id" : 22, - "name" : "d_same_day_ly", - "required" : false, - "type" : "int" - }, { - "id" : 23, - "name" : "d_same_day_lq", - "required" : false, - "type" : "int" - }, { - "id" : 24, - "name" : "d_current_day", - "required" : false, - "type" : "string" - }, { - "id" : 25, - "name" : "d_current_week", - "required" : false, - "type" : "string" - }, { - "id" : 26, - "name" : "d_current_month", - "required" : false, - "type" : "string" - }, { - "id" : 27, - "name" : "d_current_quarter", - "required" : false, - "type" : "string" - }, { - "id" : 28, - "name" : "d_current_year", - "required" : false, - "type" : "string" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "d_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "d_date_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "d_date", - "required" : false, - "type" : "date" - }, { - "id" : 4, - "name" : "d_month_seq", - "required" : false, - "type" : "int" - }, { - "id" : 5, - "name" : "d_week_seq", - "required" : false, - "type" : "int" - }, { - "id" : 6, - "name" : "d_quarter_seq", - "required" : false, - "type" : "int" - }, { - "id" : 7, - "name" : "d_year", - "required" : false, - "type" : "int" - }, { - "id" : 8, - "name" : "d_dow", - "required" : false, - "type" : "int" - }, { - "id" : 9, - "name" : "d_moy", - "required" : false, - "type" : "int" - }, { - "id" : 10, - "name" : "d_dom", - "required" : false, - "type" : "int" - }, { - "id" : 11, - "name" : "d_qoy", - "required" : false, - "type" : "int" - }, { - "id" : 12, - "name" : "d_fy_year", - "required" : false, - "type" : "int" - }, { - "id" : 13, - "name" : "d_fy_quarter_seq", - "required" : false, - "type" : "int" - }, { - "id" : 14, - "name" : "d_fy_week_seq", - "required" : false, - "type" : "int" - }, { - "id" : 15, - "name" : "d_day_name", - "required" : false, - "type" : "string" - }, { - "id" : 16, - "name" : "d_quarter_name", - "required" : false, - "type" : "string" - }, { - "id" : 17, - "name" : "d_holiday", - "required" : false, - "type" : "string" - }, { - "id" : 18, - "name" : "d_weekend", - "required" : false, - "type" : "string" - }, { - "id" : 19, - "name" : "d_following_holiday", - "required" : false, - "type" : "string" - }, { - "id" : 20, - "name" : "d_first_dom", - "required" : false, - "type" : "int" - }, { - "id" : 21, - "name" : "d_last_dom", - "required" : false, - "type" : "int" - }, { - "id" : 22, - "name" : "d_same_day_ly", - "required" : false, - "type" : "int" - }, { - "id" : 23, - "name" : "d_same_day_lq", - "required" : false, - "type" : "int" - }, { - "id" : 24, - "name" : "d_current_day", - "required" : false, - "type" : "string" - }, { - "id" : 25, - "name" : "d_current_week", - "required" : false, - "type" : "string" - }, { - "id" : 26, - "name" : "d_current_month", - "required" : false, - "type" : "string" - }, { - "id" : 27, - "name" : "d_current_quarter", - "required" : false, - "type" : "string" - }, { - "id" : 28, - "name" : "d_current_year", - "required" : false, - "type" : "string" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.22.ndv" : "73023", - "trino.stats.ndv.2.ndv" : "71926", - "trino.stats.ndv.7.ndv" : "201", - "trino.stats.ndv.27.ndv" : "2", - "trino.stats.ndv.19.ndv" : "2", - "trino.stats.ndv.18.ndv" : "2", - "trino.stats.ndv.14.ndv" : "10536", - "trino.stats.ndv.23.ndv" : "72827", - "trino.stats.ndv.8.ndv" : "7", - "trino.stats.ndv.21.ndv" : "2468", - "trino.stats.ndv.3.ndv" : "71148", - "write.format.default" : "PARQUET", - "trino.stats.ndv.17.ndv" : "2", - "trino.stats.ndv.13.ndv" : "806", - "trino.stats.ndv.26.ndv" : "2", - "trino.stats.ndv.16.ndv" : "792", - "trino.stats.ndv.4.ndv" : "2391", - "trino.stats.ndv.5.ndv" : "10536", - "trino.stats.ndv.9.ndv" : "12", - "trino.stats.ndv.12.ndv" : "201", - "trino.stats.ndv.25.ndv" : "1", - "trino.stats.ndv.10.ndv" : "31", - "trino.stats.ndv.1.ndv" : "73040", - "trino.stats.ndv.28.ndv" : "2", - "trino.stats.ndv.15.ndv" : "7", - "trino.stats.ndv.6.ndv" : "806", - "trino.stats.ndv.20.ndv" : "2411", - "trino.stats.ndv.24.ndv" : "1", - "trino.stats.ndv.11.ndv" : "4" - }, - "current-snapshot-id" : 879409309086531902, - "refs" : { - "main" : { - "snapshot-id" : 879409309086531902, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 879409309086531902, - "timestamp-ms" : 1648081235817, - "summary" : { - "operation" : "append", - "added-data-files" : "6", - "added-records" : "73049", - "added-files-size" : "959327", - "changed-partition-count" : "1", - "total-records" : "73049", - "total-files-size" : "959327", - "total-data-files" : "6", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/date_dim/metadata/snap-879409309086531902-1-a5e086f1-09c3-4393-9b69-b50996314dd3.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1648081235817, - "snapshot-id" : 879409309086531902 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1648081235817, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/date_dim/metadata/00000-cd1283cb-681c-4ef4-8b85-6b6825ef12e2.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/date_dim/metadata/a5e086f1-09c3-4393-9b69-b50996314dd3-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/date_dim/metadata/a5e086f1-09c3-4393-9b69-b50996314dd3-m0.avro deleted file mode 100644 index 1ef6f8ae92d1e9dc4741a07cf87873e21364b6e1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9267 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 Syx7`VUes}4-@ivk67~NllgKdu diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/household_demographics/metadata/snap-6765154030047008235-1-4f730f87-2407-41d0-9fd5-85e7668336e3.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/household_demographics/metadata/snap-6765154030047008235-1-4f730f87-2407-41d0-9fd5-85e7668336e3.avro deleted file mode 100644 index ce3882ad9e269b9e6baf31090e925265657f847d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3797 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 S#sBVnzVz0G&(G$B_wIjKWGm$W diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/income_band/metadata/snap-2915181097642837852-1-9b3490b7-e1c2-467a-8a2a-0731a236ef94.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/income_band/metadata/snap-2915181097642837852-1-9b3490b7-e1c2-467a-8a2a-0731a236ef94.avro deleted file mode 100644 index 5c45cd88863977fbbcb4573442dacf6d8ab2dd2f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3789 zcmbW3UuYaf9LL+#s1MbuZG*HChOMndQrW%al1mg#B`U?J*IrwN7|yxfx!jh$o$Ksu zuGercqP`e?(Aq~qtS>%Uu}B|VC|LVYibaZ06$Kw8sHK#GQs~V7nVG%2jeB{!KQq72 z?>F=N{=V~C<;ePtLvWGcr#!QU>hRy~OB!u8kT#_eWMX1zBbp2A{Cgd`b`^QFT(up< zey-Zcu{>>RZmDZU1JW8>5e4!nvE2ps-NGh|GmA|~ZR~5xA5(wxb>K@zD zrt-Sp9_fzlAa_BH?qDVMK}?|oc!I#Qk0W5apwgnqi_?t)8%p2?B-G~ll)VNr%Zw}% zw_Pf-F0ntsz8T5!gor;*;mepepnos!j12(H%rZjdsy3NMUeWUfINcP)v9wA_ZQd$Y3u|86cOE z7Kqoc!W&a~=$P$McFE;FkuOC1vQzU4)j|%U?3DY2&V<{tGshLCXcyj7$Lg&#RNcBHkUrXnN`5*RPh^)HGqR%{ezA6e0xM=%~xj{i$q&!i^58Ny$+f5 zcqs^)HF=z?)}v1-2N_Nsf}5#|PY#_>@_c;;#T)8;6N=9c6JpQGQzu%i_3XG<=2>ZD z?0U8YR%l`inGdoSav()R@eF4-wktPj3}P{brlcKQlt#wD5@lrZT?riP0QRt+ zG^$!xx7A8SULb_V)<_Y8Q$1aR#sw%A*BUvo(t2Yi1V%IgpU^4CB*fyd7X@3^F)qaq zimeJ+DMlK$_@qEflMCNolAaJ!>~?E$SaGmMp$9hbR!L1ZWL9hskCjw5Nurl}(oR#M zw#)2>drR<`$l|LKCWlATLi>iLduhTgWn6w=%tphcSMf}ltDHhT>`?|zUP)p`blIc~ zE+>#sYr>*3#TPV=q+jMcGIdE#=vG-3ua9)3C-Hz9npTBwS^ylyBgWG5De;XbqkF0Fk{MEfn`rwY@ zf#Uw+{?BHfxPGX)_2SB@+2t#*8pa!cUN_!)X6HZO?SA^DlfSJ$MxWh##QS2)@}rkG z-+1%n{NL|Qe{kXFOVi7j4$qaYet&rS><`5gTh9;W-~Mv;!=FxE{c-S~Gv{6&{`8ey Ye|`JQ?{`}5+m)SPzk6%txy~f-0(_?p^#A|> diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/inventory/metadata/00001-69b008e4-f403-42f3-b3d6-73ee96472da3.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/inventory/metadata/00001-69b008e4-f403-42f3-b3d6-73ee96472da3.metadata.json deleted file mode 100644 index 84d3d2dc2c92..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/inventory/metadata/00001-69b008e4-f403-42f3-b3d6-73ee96472da3.metadata.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "364f4eef-f59c-47a4-81b3-2c4d5e01ea13", - "location" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/inventory", - "last-updated-ms" : 1663712710812, - "last-column-id" : 4, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "inv_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "inv_warehouse_sk", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "inv_quantity_on_hand", - "required" : false, - "type" : "int" - }, { - "id" : 4, - "name" : "inv_date_sk", - "required" : false, - "type" : "long" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "inv_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "inv_warehouse_sk", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "inv_quantity_on_hand", - "required" : false, - "type" : "int" - }, { - "id" : 4, - "name" : "inv_date_sk", - "required" : false, - "type" : "long" - } ] - } ], - "partition-spec" : [ { - "name" : "inv_date_sk", - "transform" : "identity", - "source-id" : 4, - "field-id" : 1000 - } ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ { - "name" : "inv_date_sk", - "transform" : "identity", - "source-id" : 4, - "field-id" : 1000 - } ] - } ], - "last-partition-id" : 1000, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.2.ndv" : "20", - "trino.stats.ndv.1.ndv" : "297612", - "trino.stats.ndv.3.ndv" : "1010", - "write.format.default" : "PARQUET", - "trino.stats.ndv.4.ndv" : "261" - }, - "current-snapshot-id" : 5575001584338726042, - "refs" : { - "main" : { - "snapshot-id" : 5575001584338726042, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 5575001584338726042, - "timestamp-ms" : 1648085030991, - "summary" : { - "operation" : "append", - "added-data-files" : "261", - "added-records" : "783000000", - "added-files-size" : "1875364557", - "changed-partition-count" : "261", - "total-records" : "783000000", - "total-files-size" : "1875364557", - "total-data-files" : "261", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/inventory/metadata/snap-5575001584338726042-1-16d94170-9def-424b-b8a6-ddc6c170d9b1.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1648085030991, - "snapshot-id" : 5575001584338726042 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1648085030991, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/inventory/metadata/00000-a1c36345-6ad1-4c31-b1a3-b0609524644a.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/inventory/metadata/16d94170-9def-424b-b8a6-ddc6c170d9b1-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/inventory/metadata/16d94170-9def-424b-b8a6-ddc6c170d9b1-m0.avro deleted file mode 100644 index b9fb612f10c58ec57355bb8804a1fcce7bd9f310..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22474 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 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/inventory/metadata/snap-5575001584338726042-1-16d94170-9def-424b-b8a6-ddc6c170d9b1.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/inventory/metadata/snap-5575001584338726042-1-16d94170-9def-424b-b8a6-ddc6c170d9b1.avro deleted file mode 100644 index 0bef485a2925d5a41d0a6472bc6574b592eace52..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3807 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 UfBtiR!&v?0^}pz~o&7of2hINzumAu6 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/promotion/metadata/00001-a0a4a9ff-53d6-4cd0-9639-089a17b5707f.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/promotion/metadata/00001-a0a4a9ff-53d6-4cd0-9639-089a17b5707f.metadata.json deleted file mode 100644 index e2575f3c2835..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/promotion/metadata/00001-a0a4a9ff-53d6-4cd0-9639-089a17b5707f.metadata.json +++ /dev/null @@ -1,276 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "679be629-3f2d-4962-95ac-c245145828b1", - "location" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/promotion", - "last-updated-ms" : 1663712718252, - "last-column-id" : 19, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "p_promo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "p_promo_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "p_start_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "p_end_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "p_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "p_cost", - "required" : false, - "type" : "decimal(15, 2)" - }, { - "id" : 7, - "name" : "p_response_targe", - "required" : false, - "type" : "int" - }, { - "id" : 8, - "name" : "p_promo_name", - "required" : false, - "type" : "string" - }, { - "id" : 9, - "name" : "p_channel_dmail", - "required" : false, - "type" : "string" - }, { - "id" : 10, - "name" : "p_channel_email", - "required" : false, - "type" : "string" - }, { - "id" : 11, - "name" : "p_channel_catalog", - "required" : false, - "type" : "string" - }, { - "id" : 12, - "name" : "p_channel_tv", - "required" : false, - "type" : "string" - }, { - "id" : 13, - "name" : "p_channel_radio", - "required" : false, - "type" : "string" - }, { - "id" : 14, - "name" : "p_channel_press", - "required" : false, - "type" : "string" - }, { - "id" : 15, - "name" : "p_channel_event", - "required" : false, - "type" : "string" - }, { - "id" : 16, - "name" : "p_channel_demo", - "required" : false, - "type" : "string" - }, { - "id" : 17, - "name" : "p_channel_details", - "required" : false, - "type" : "string" - }, { - "id" : 18, - "name" : "p_purpose", - "required" : false, - "type" : "string" - }, { - "id" : 19, - "name" : "p_discount_active", - "required" : false, - "type" : "string" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "p_promo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "p_promo_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "p_start_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "p_end_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "p_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "p_cost", - "required" : false, - "type" : "decimal(15, 2)" - }, { - "id" : 7, - "name" : "p_response_targe", - "required" : false, - "type" : "int" - }, { - "id" : 8, - "name" : "p_promo_name", - "required" : false, - "type" : "string" - }, { - "id" : 9, - "name" : "p_channel_dmail", - "required" : false, - "type" : "string" - }, { - "id" : 10, - "name" : "p_channel_email", - "required" : false, - "type" : "string" - }, { - "id" : 11, - "name" : "p_channel_catalog", - "required" : false, - "type" : "string" - }, { - "id" : 12, - "name" : "p_channel_tv", - "required" : false, - "type" : "string" - }, { - "id" : 13, - "name" : "p_channel_radio", - "required" : false, - "type" : "string" - }, { - "id" : 14, - "name" : "p_channel_press", - "required" : false, - "type" : "string" - }, { - "id" : 15, - "name" : "p_channel_event", - "required" : false, - "type" : "string" - }, { - "id" : 16, - "name" : "p_channel_demo", - "required" : false, - "type" : "string" - }, { - "id" : 17, - "name" : "p_channel_details", - "required" : false, - "type" : "string" - }, { - "id" : 18, - "name" : "p_purpose", - "required" : false, - "type" : "string" - }, { - "id" : 19, - "name" : "p_discount_active", - "required" : false, - "type" : "string" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.7.ndv" : "1", - "trino.stats.ndv.2.ndv" : "1543", - "trino.stats.ndv.16.ndv" : "1", - "trino.stats.ndv.4.ndv" : "669", - "trino.stats.ndv.19.ndv" : "1", - "trino.stats.ndv.5.ndv" : "1497", - "trino.stats.ndv.18.ndv" : "1", - "trino.stats.ndv.9.ndv" : "2", - "trino.stats.ndv.14.ndv" : "1", - "trino.stats.ndv.12.ndv" : "1", - "trino.stats.ndv.10.ndv" : "1", - "trino.stats.ndv.8.ndv" : "10", - "trino.stats.ndv.1.ndv" : "1483", - "trino.stats.ndv.15.ndv" : "1", - "trino.stats.ndv.6.ndv" : "1", - "trino.stats.ndv.3.ndv" : "675", - "write.format.default" : "PARQUET", - "trino.stats.ndv.17.ndv" : "1466", - "trino.stats.ndv.13.ndv" : "1", - "trino.stats.ndv.11.ndv" : "1" - }, - "current-snapshot-id" : 6100987187671647433, - "refs" : { - "main" : { - "snapshot-id" : 6100987187671647433, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 6100987187671647433, - "timestamp-ms" : 1648081254366, - "summary" : { - "operation" : "append", - "added-data-files" : "4", - "added-records" : "1500", - "added-files-size" : "59840", - "changed-partition-count" : "1", - "total-records" : "1500", - "total-files-size" : "59840", - "total-data-files" : "4", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/promotion/metadata/snap-6100987187671647433-1-a43005ce-7a3e-4260-872a-e609a7597081.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1648081254366, - "snapshot-id" : 6100987187671647433 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1648081254366, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/promotion/metadata/00000-99574bca-cf65-4261-981d-6d18df174588.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/promotion/metadata/a43005ce-7a3e-4260-872a-e609a7597081-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/promotion/metadata/a43005ce-7a3e-4260-872a-e609a7597081-m0.avro deleted file mode 100644 index a0d2a0e7e6299e030b9c057e44ac723e6557bc56..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7863 zcmb_gdsq`!7Pq)qd{yl#6-7IakNSw3Oh6#k$|}nzzIdor1Wboy0wa@5m`p&H5^C^W zP(VOY3zinETUV{mZ$UpS_&}|X*0t)YReaV5)>SK7t?r!}GIJA2BasxeV2j1f?~hs+e?? z`=XGc23*grH>!;ct*6zdBw)i3$!3xvzycjknFyH;l%fp@XogI%#a^-nh|X<6KqO?X zgse?0&ai4N&Ju38_k#ckgVtShf5@C<3B4PhM?j_;+QgCUj4&;sA@w*lP~k5_ut6eq z0T2R1n2fZ+M5qA?34{wQ(!h!Ufe?VOZ~Qho55$4}Ajn>mh#L$9rPk_kl5*kS0YU+) zNvs_k2B<-|FyQ*LVQ6p`r|1MXB7rN>hQg+}0RR`H4S>P5B<%(UT#q&kBiLM%8yIj= z+AxR|0&IMuV|P!78ycAGu)VJEL}ZrAa#U^RRfZlH1Hi7kc~^gbYjh1 z;*FD)>%+!%!c^r3qHV1KH<}V@R?Uw>!5{`Tm?^HY_~{uZL$!nsH&ZOC;=aR33J3{I zLtL#RDK1+9$7v1>W{XD{?2WN-#%d$ZCW;8$p}@^x5CW|MGk+otdX1zF$h4?1B$Z?n zkHs(s2VLhXUV-um<76Q6b!R$Pk**aQ_e^&Lo=& zdma^XIDfE$CAr{4AI|ev$n6-2%dig~gMt_KU(J`3h%D@kjg`q}eLTS+vn}ew|7;roq!Y$|EZOdV7KL|UXqBTis z*Wj?NKV+++DYM?d8%q({ic%yMXBeETf9@;=2tB z0*>c+m;0iGr;*7>k*u25=}ZLM{OYxxkOW2OfmjuUnNleohEq6L7LNzYOTu=3@F`(g zWTL?Z90+4jFHI>|PV$N*OaW_wh68Jnun?D7`w9Cf9aP+u?aB*t4Gpfw?||qiaO?LK zl;Y47f-wQb=b=DF^*7XHBs3sb54kU?A%HGRXw>w#`iT++sM=;cbx7A$QP4}bc7Pzg za=;gD>4IvqllGEnvtCGGH7}XMUSG}zQdj&_jao8~bPad72uS93B`osHNo^Sl=A1BU6yQ~WlUyoPxB&BGyzwXCLzwC+ z8=u0jk_)cXftYtowo5rkj{!WcBrqih&C3A*ImrP7-SuX2aIL4sq@ee+rlhl*nPH%H zon;2P>~%AHN$84cD5b}uCN!;^StvmvImto^w(j+_c%=dr^TMR=a}b+u=7fO>OAdO5saw?;ryu#@tSU1ApzR$XcMYU49`zG z@f`)4gl4ykjk8(cz+R{U=>`6L?UmJEV&YmQUhFoj`{k}LN?%+W-Bz_}n%BB6M^A+> zIzQF-?R&d-RdwvMZdvEC9s0~$d0@>dgKog;;{RQ(cr@UO>de+riD9~YRZ6Pvi$gmv z4SGBJ#ic`5S^B_oS=0+(@93W+Lb`6hv_EUnLUhdZ8QW`7D-u;!oUB_r@i5}IN^P_uy<+k&Ha{u8TF6iM|z#hd2hv^cjuQ}c&N!- zcgibkOZqQMr}sEoa^drKORt<6VrjT`N`ANMN^zkf;opDVrK&Ifes0+mnX0@j{aEsn zW7MF`AkQ1IyBeaNrEz3Tzd-Z>fIz zInUD3C-vCMnA(a3>z`2`xvjovwY=5pRx4VCeHI=(E_h<__~6OGVZoDvCj^J@9k+Ml z-tlWQi#(R-x-TgYocP&64{vgDk&oxMGip@&69e?udOnSLQYH7F6I|MJba+P#X&E_( zx|dT|)qT^9=Y<7dWNFQoZ&qJrpN^?CZP3^K7a2RkEU&4qekQM}Hx|^@B^KZf(^l37Z)RgWK1v@q zZyUbx>4fAn`lrda;~)Pzs>}N4)1J|_z6T#HAvRMhd^dkmIX7lU)~Dq3vRl;tq=E~x zrv@&P_1$GD?SWPfslGe*;oaoxHCOKZ5@zU>7od3I<5fB3a`?DEybo3NAv%7Wn!9t^ z!whZg9_!K3dB6X$_L=QmXR;`aoD0k(FY>Vc0N1maEb57bCNdRdGOuV0@W%k zGbr)*&3nFS7*LQgdenwH|Cxv38}C#ec`-9OVMoUEd6pm5GwTyFatn^O$-CXHW=Wi^ zVs3qC^S(>BMJBoc%AsdHwYegEXW)+c`R)mZAzG_-AKij^d4(4P-^E7VUlB9mU%QLjbn3h{e%nod z>zbk~s>qLq@5ztL8QiVXr*QBoX2;cnntKSg`$~t!L#x~Esg-U0{5%?c>A08qyJ4rU zRYXm{Ju_}|&zRazzyD_g?bCXlA}e~$@qiCk(le^D-p2x{PqqzDSii7GhcQbcKI|WX z_K(EMBLAY11`VYod5s; diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/promotion/metadata/snap-6100987187671647433-1-a43005ce-7a3e-4260-872a-e609a7597081.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/promotion/metadata/snap-6100987187671647433-1-a43005ce-7a3e-4260-872a-e609a7597081.avro deleted file mode 100644 index 7b1d42fd9d8361c7f7c756f72dab47bd10b231ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3789 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 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/reason/metadata/snap-501198784787015747-1-c0fee408-669e-41c7-a70c-719122d7fdc4.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/reason/metadata/snap-501198784787015747-1-c0fee408-669e-41c7-a70c-719122d7fdc4.avro deleted file mode 100644 index ab930f0bde80166c74d2e9f348d46668fbdadfa1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3785 zcmbW3O^6&t6vxfr!XC_`5;w3Wq-fTIvKzXSnau1YLJVspAZXkKR>IoUbl1#u($h7m zuHJQanTRfu2)coI5OP=~pm^{U10Dn+NDe`w9!x|NBu5PiAyGCsz7av>|P>6>%V+631(>?>4qroY%1hse?Um{H-Hk$)Qbfcn*Yg5p~+J zwQ5P%yHowKUF0>?=q^@bAHx*7fF}riXBh#<1GNrCev)n+*iZtuA)yY>$MoCCG8tJU z?s!yWJ;(k8M`l6bBO6c*mWV@p^WCF_g|>}s)AwN8Z(^#3J)wDl>$1RM<_pQO%*9JN zgf<}sVFdOOf#l?QGN=v%mugiWBff*UsJ(r38&VkFmTy`(2#blXM5Mqg6dAn2QwGSR zqyv)m8}Nz>554R7lwGnpB=XgGUw&#yq1wnrl%4XB(Aj8Pe&#-fDcXhiRQfc9av&kF z!~SL+(qaM;zxOJNl=KQ))-NOrdlWjJZ^oSx3#o>&i=fBbAXL&Z60jDp1L$?4o^L`Q zK)^QvktbM$ZqVDQGfG7g7KTpOq$NzzC0S7D4Pa)sk5N%laPbl%W{vfzRG;Y4NWg;s zW};UR?gHU1P{#y7RyLPDznKld>Qo8Z?M;BgUBiQo?m~A;W6jrM8jD3+>59VUf>sMM z>G4t!w3_lbH>^i5D+d`(9fFrK6`vkDk>vUM9Evy8`6d*fA11_}m#0p&MCjwv)K6n(oGmY*nOnyE8@pXOwSH|d1W8-(Pe)q!s>PKk)?8sl& zK7If35BvW$Cq6r6UApw^>g0@!dbK;=*&k{4uir#fo>~{_=ZwzJK^zWBtZu N``(4M$6I`zQ25s4KjA{Y>|naM{qFi26^_;_{ zckkoy6F$lra)F#LNKU_INd+^fO3nZn+Uj6YvsE~imvu*t`pCLz^l>9m4?w660NCLG z5Sqq7h%5w&zHZYl)>MJW>-QQjGnH*yQ|@d#!lrtks^3NKw<@B4b zL<-{>?Bva6W}dhOM2v{!Tc(S~h^P=>wI4Eb1$iHzXpO}ijt9!jKC zAa4NQ$UB2@IBA5-5k;o0;6KQc?ZKj2&LbaV^M@0zhJy=ygy+x4${5Iulm{;`!F%>$lRZa?JXRfFY4WS|Kzj`pT1X@-;?c2%Zl${cJr zU0Ek+2p`Ar{&5z&i^0z7rZOPa24`*KjICno#ezYMX+(Zg2BgAd%DDeES1mvr1}A3$n$H z;29zfyjiwzoxBW@mP4)yAIvTx7CJkpksTX8av4er<=v6V!MI*SAfJSMPgx`SO?yVnGE znLM)B1fd}U2~z|ddeGY_;cW5j5;bIF*2xl+K$e(lpxeYLLr|;R#8iW1`Fj=m3K7B);CeHm`Uu&$g1 z*OAD|BFD5LB0C5DQcbzyH+ew9HaH6!4$h+P^)<8Z^6E%mRCvoSCc+$FB9vB$PM2ME zxu+C6hg91E#n;Spf%@xoELDMAn`6s0MFm~fSJL!%I|7LU+Ov);7+lJHk?sq6dW!%A zdhx>9;-Rj%xRptJYFgK&jaBp1l=V6_8$ug%g^bY*h}0}e`|5Dhk@`WdF?vlU9s-eH zpN|c7ihk;k-fDw_Lqk>fR)e6-2!*7#+zP2*9o3&IPfymC{?rM*4_6ia^|%VX1W+&Z z>J0m(iu|wThN2{czOT3lQ1h}D4tT~=JIcYH^HvR?ApgL%(bM&0jhDUxAKq3^`+R!e zQWv<@LCjA}w$mI^K@J3$3QX#tz8nAu%f>1>2ryl2N(q65iX{VWm2#pkyg$Sjac@~dUpg~>&oxTdOInA5|_zf#0Cjq$g zbINLlOn!hO4?3xnM@|l_4Qcpv>?>p-MXqaPgz%9OnUQHcd{(su;fwT51l+L#EhKy{ z2*6u0HI*85Ltj9ZqAbKuupM>H!9QPBsIlMyteDa@-9JGA%PLw$wFTjI(G^iC&?s72 z6-&F>3DHR zt1*0f{H+t$ufHsPJKiz2^2Ar8Lvoo<`tYho*Vk*AsLT2351IB)*5Pk-(lj{X(-H6I)NM7jC!yE9kMo^M+GOYfE5tCN=}f1bS3G}yi~ zay^oaj7Q#xM2@U$Za&zGt=H zdbsQ6{^EPRcUrBt|7d;UpB>iV;hU3Bwl1(8cj(rQckZ<uAfy3v0R)ZRdVU R++T3PI^Wxrnm2{e{{f%X7@q(D diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/ship_mode/metadata/snap-8517577181179121697-1-070c7bc1-48e8-4f95-ba69-e3703029c025.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/ship_mode/metadata/snap-8517577181179121697-1-070c7bc1-48e8-4f95-ba69-e3703029c025.avro deleted file mode 100644 index 58b22ca3be4ffa8961f4e45f8a6d86e8ae473d3b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3785 zcmbW3O^6&t6vwlJ5FskM%3|UwMI$-whUv}D&WBMVtV(qKz+D9sCqqql%}z^C^;liK z>lnrm5Ak5clM-T%MiD${A|668L=i#`lH?#!!Ha?8Zzn-tZHg?AZUSZx2-)fQ;zV*NXgLm4*s2Ge`9-on&r5TDl?j!WR@ zhC^K2H!AxM^sQ)P-h>r#AfGdbF2i@*vmnlbXJPJm6utD~9<=K4CVFxag>)Hp+OdsF zA)oI~^~ZJzUDl(!o)&w_BfOgEpQt%?udM9ew$b|Aj`xZ z%4ODz@F&_cj{=|Ah~52k1F-a9*qPn`7b1T zMd25H3L2dqz(pxxd8INUWl*z8ujQwB6&3p5ssw$zn{Ee5R?2I+}X z2)3H)IM=NwU(^mVnmWu|9jf^B(1|23)@M+>sn0j5`0Ow#_N+X8q9t0-j!R^ol{O34 zb6;qMmS+>|E@&Yab25?4aJaGUrGi;36pLobG>c_(#+)k`C*i(Ym`|?|=7Wpm%t^;0 zb;6coNBwG>)IS(uUq9ypAEJ3=Uu3?O3!hZW5dQOtgd z^wuGqY+kDnGZG=kk>;{PAph^W3b0cGqRvn5D)L(eXkgb@$CWB0i|;DnY!7gN^$erx zb@f}VLKGxIYHWiPDLCEJ6=+U^5^-&ilPGO4rYJG83B-gRa!gVzF1#pUShI?tsEmd4`UtptA((8Dp%2iFF0rsc@r>`V6Bl>Jo1y>VD zsx@U%nc_>KYF?!fYUU(59i5O!R0mYM4}&7bTO=8q;7GTN*W;!~FCTsA*&EYq?LYqg z{>0e9YVLUM!FL}Se>;EoZu8SGUpcjQ+nFQuTjR*R=iD#;o`3nniECST9sc0AvB{&w z8~gv-@$}{QejGpl(WPfLefQKal|MHhI+nZo!%2Mho&1%(yN}&}4@L*Kl^5>Z`!l_A z`kTh}(#2i)(AW97!=L{?e)5wG=PsVkJ-21S{A_A_x%BF#pKhM5e!Tzve{SA->(|G& PoLHJYb$o4eZ-V~;_;(O< diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/store/metadata/00001-7f61b83f-1f99-4c10-b931-ee450d7c8bc5.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/store/metadata/00001-7f61b83f-1f99-4c10-b931-ee450d7c8bc5.metadata.json deleted file mode 100644 index dbe800180cf3..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/store/metadata/00001-7f61b83f-1f99-4c10-b931-ee450d7c8bc5.metadata.json +++ /dev/null @@ -1,386 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "9e06c18a-c898-4a91-a044-1dc315d4a2bb", - "location" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/store", - "last-updated-ms" : 1663712728533, - "last-column-id" : 29, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "s_store_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "s_store_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "s_rec_start_date", - "required" : false, - "type" : "date" - }, { - "id" : 4, - "name" : "s_rec_end_date", - "required" : false, - "type" : "date" - }, { - "id" : 5, - "name" : "s_closed_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "s_store_name", - "required" : false, - "type" : "string" - }, { - "id" : 7, - "name" : "s_number_employees", - "required" : false, - "type" : "int" - }, { - "id" : 8, - "name" : "s_floor_space", - "required" : false, - "type" : "int" - }, { - "id" : 9, - "name" : "s_hours", - "required" : false, - "type" : "string" - }, { - "id" : 10, - "name" : "s_manager", - "required" : false, - "type" : "string" - }, { - "id" : 11, - "name" : "s_market_id", - "required" : false, - "type" : "int" - }, { - "id" : 12, - "name" : "s_geography_class", - "required" : false, - "type" : "string" - }, { - "id" : 13, - "name" : "s_market_desc", - "required" : false, - "type" : "string" - }, { - "id" : 14, - "name" : "s_market_manager", - "required" : false, - "type" : "string" - }, { - "id" : 15, - "name" : "s_division_id", - "required" : false, - "type" : "int" - }, { - "id" : 16, - "name" : "s_division_name", - "required" : false, - "type" : "string" - }, { - "id" : 17, - "name" : "s_company_id", - "required" : false, - "type" : "int" - }, { - "id" : 18, - "name" : "s_company_name", - "required" : false, - "type" : "string" - }, { - "id" : 19, - "name" : "s_street_number", - "required" : false, - "type" : "string" - }, { - "id" : 20, - "name" : "s_street_name", - "required" : false, - "type" : "string" - }, { - "id" : 21, - "name" : "s_street_type", - "required" : false, - "type" : "string" - }, { - "id" : 22, - "name" : "s_suite_number", - "required" : false, - "type" : "string" - }, { - "id" : 23, - "name" : "s_city", - "required" : false, - "type" : "string" - }, { - "id" : 24, - "name" : "s_county", - "required" : false, - "type" : "string" - }, { - "id" : 25, - "name" : "s_state", - "required" : false, - "type" : "string" - }, { - "id" : 26, - "name" : "s_zip", - "required" : false, - "type" : "string" - }, { - "id" : 27, - "name" : "s_country", - "required" : false, - "type" : "string" - }, { - "id" : 28, - "name" : "s_gmt_offset", - "required" : false, - "type" : "decimal(5, 2)" - }, { - "id" : 29, - "name" : "s_tax_precentage", - "required" : false, - "type" : "decimal(5, 2)" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "s_store_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "s_store_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "s_rec_start_date", - "required" : false, - "type" : "date" - }, { - "id" : 4, - "name" : "s_rec_end_date", - "required" : false, - "type" : "date" - }, { - "id" : 5, - "name" : "s_closed_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "s_store_name", - "required" : false, - "type" : "string" - }, { - "id" : 7, - "name" : "s_number_employees", - "required" : false, - "type" : "int" - }, { - "id" : 8, - "name" : "s_floor_space", - "required" : false, - "type" : "int" - }, { - "id" : 9, - "name" : "s_hours", - "required" : false, - "type" : "string" - }, { - "id" : 10, - "name" : "s_manager", - "required" : false, - "type" : "string" - }, { - "id" : 11, - "name" : "s_market_id", - "required" : false, - "type" : "int" - }, { - "id" : 12, - "name" : "s_geography_class", - "required" : false, - "type" : "string" - }, { - "id" : 13, - "name" : "s_market_desc", - "required" : false, - "type" : "string" - }, { - "id" : 14, - "name" : "s_market_manager", - "required" : false, - "type" : "string" - }, { - "id" : 15, - "name" : "s_division_id", - "required" : false, - "type" : "int" - }, { - "id" : 16, - "name" : "s_division_name", - "required" : false, - "type" : "string" - }, { - "id" : 17, - "name" : "s_company_id", - "required" : false, - "type" : "int" - }, { - "id" : 18, - "name" : "s_company_name", - "required" : false, - "type" : "string" - }, { - "id" : 19, - "name" : "s_street_number", - "required" : false, - "type" : "string" - }, { - "id" : 20, - "name" : "s_street_name", - "required" : false, - "type" : "string" - }, { - "id" : 21, - "name" : "s_street_type", - "required" : false, - "type" : "string" - }, { - "id" : 22, - "name" : "s_suite_number", - "required" : false, - "type" : "string" - }, { - "id" : 23, - "name" : "s_city", - "required" : false, - "type" : "string" - }, { - "id" : 24, - "name" : "s_county", - "required" : false, - "type" : "string" - }, { - "id" : 25, - "name" : "s_state", - "required" : false, - "type" : "string" - }, { - "id" : 26, - "name" : "s_zip", - "required" : false, - "type" : "string" - }, { - "id" : 27, - "name" : "s_country", - "required" : false, - "type" : "string" - }, { - "id" : 28, - "name" : "s_gmt_offset", - "required" : false, - "type" : "decimal(5, 2)" - }, { - "id" : 29, - "name" : "s_tax_precentage", - "required" : false, - "type" : "decimal(5, 2)" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.22.ndv" : "75", - "trino.stats.ndv.2.ndv" : "511", - "trino.stats.ndv.7.ndv" : "101", - "trino.stats.ndv.27.ndv" : "1", - "trino.stats.ndv.19.ndv" : "530", - "trino.stats.ndv.18.ndv" : "1", - "trino.stats.ndv.14.ndv" : "709", - "trino.stats.ndv.23.ndv" : "54", - "trino.stats.ndv.21.ndv" : "20", - "trino.stats.ndv.8.ndv" : "733", - "trino.stats.ndv.3.ndv" : "4", - "write.format.default" : "PARQUET", - "trino.stats.ndv.17.ndv" : "1", - "trino.stats.ndv.13.ndv" : "756", - "trino.stats.ndv.26.ndv" : "362", - "trino.stats.ndv.16.ndv" : "1", - "trino.stats.ndv.4.ndv" : "3", - "trino.stats.ndv.5.ndv" : "162", - "trino.stats.ndv.29.ndv" : "12", - "trino.stats.ndv.9.ndv" : "3", - "trino.stats.ndv.12.ndv" : "1", - "trino.stats.ndv.25.ndv" : "21", - "trino.stats.ndv.10.ndv" : "747", - "trino.stats.ndv.28.ndv" : "4", - "trino.stats.ndv.1.ndv" : "1008", - "trino.stats.ndv.15.ndv" : "1", - "trino.stats.ndv.6.ndv" : "10", - "trino.stats.ndv.20.ndv" : "543", - "trino.stats.ndv.24.ndv" : "27", - "trino.stats.ndv.11.ndv" : "10" - }, - "current-snapshot-id" : 5538226458318650132, - "refs" : { - "main" : { - "snapshot-id" : 5538226458318650132, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 5538226458318650132, - "timestamp-ms" : 1648081262291, - "summary" : { - "operation" : "append", - "added-data-files" : "1", - "added-records" : "1002", - "added-files-size" : "63284", - "changed-partition-count" : "1", - "total-records" : "1002", - "total-files-size" : "63284", - "total-data-files" : "1", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/store/metadata/snap-5538226458318650132-1-0dde8a60-e04e-4495-b6ec-4846f70b4b6b.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1648081262291, - "snapshot-id" : 5538226458318650132 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1648081262291, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/store/metadata/00000-9465c2d2-6b5a-4997-a88c-fda0fc92d247.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/store/metadata/0dde8a60-e04e-4495-b6ec-4846f70b4b6b-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/store/metadata/0dde8a60-e04e-4495-b6ec-4846f70b4b6b-m0.avro deleted file mode 100644 index c6e2fecb886c8155d5c1fbcb343a5e9e4fbbbaee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8119 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 C$OgOs diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/store_returns/metadata/00001-49bd4963-e9b0-4157-8aa4-2a3230d8ecd3.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/store_returns/metadata/00001-49bd4963-e9b0-4157-8aa4-2a3230d8ecd3.metadata.json deleted file mode 100644 index fd0434016857..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/store_returns/metadata/00001-49bd4963-e9b0-4157-8aa4-2a3230d8ecd3.metadata.json +++ /dev/null @@ -1,297 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "af82eb5f-2484-4532-a917-98632cf7dd12", - "location" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/store_returns", - "last-updated-ms" : 1663712848369, - "last-column-id" : 20, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "sr_return_time_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "sr_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "sr_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "sr_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "sr_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "sr_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "sr_store_sk", - "required" : false, - "type" : "long" - }, { - "id" : 8, - "name" : "sr_reason_sk", - "required" : false, - "type" : "long" - }, { - "id" : 9, - "name" : "sr_ticket_number", - "required" : false, - "type" : "long" - }, { - "id" : 10, - "name" : "sr_return_quantity", - "required" : false, - "type" : "int" - }, { - "id" : 11, - "name" : "sr_return_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 12, - "name" : "sr_return_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 13, - "name" : "sr_return_amt_inc_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 14, - "name" : "sr_fee", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 15, - "name" : "sr_return_ship_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 16, - "name" : "sr_refunded_cash", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 17, - "name" : "sr_reversed_charge", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 18, - "name" : "sr_store_credit", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 19, - "name" : "sr_net_loss", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 20, - "name" : "sr_returned_date_sk", - "required" : false, - "type" : "long" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "sr_return_time_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "sr_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "sr_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "sr_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "sr_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "sr_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "sr_store_sk", - "required" : false, - "type" : "long" - }, { - "id" : 8, - "name" : "sr_reason_sk", - "required" : false, - "type" : "long" - }, { - "id" : 9, - "name" : "sr_ticket_number", - "required" : false, - "type" : "long" - }, { - "id" : 10, - "name" : "sr_return_quantity", - "required" : false, - "type" : "int" - }, { - "id" : 11, - "name" : "sr_return_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 12, - "name" : "sr_return_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 13, - "name" : "sr_return_amt_inc_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 14, - "name" : "sr_fee", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 15, - "name" : "sr_return_ship_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 16, - "name" : "sr_refunded_cash", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 17, - "name" : "sr_reversed_charge", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 18, - "name" : "sr_store_credit", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 19, - "name" : "sr_net_loss", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 20, - "name" : "sr_returned_date_sk", - "required" : false, - "type" : "long" - } ] - } ], - "partition-spec" : [ { - "name" : "sr_returned_date_sk", - "transform" : "identity", - "source-id" : 20, - "field-id" : 1000 - } ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ { - "name" : "sr_returned_date_sk", - "transform" : "identity", - "source-id" : 20, - "field-id" : 1000 - } ] - } ], - "last-partition-id" : 1000, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.7.ndv" : "513", - "trino.stats.ndv.2.ndv" : "297612", - "trino.stats.ndv.16.ndv" : "927393", - "trino.stats.ndv.4.ndv" : "1890006", - "trino.stats.ndv.5.ndv" : "7082", - "trino.stats.ndv.19.ndv" : "713783", - "trino.stats.ndv.18.ndv" : "677045", - "trino.stats.ndv.9.ndv" : "177092218", - "trino.stats.ndv.14.ndv" : "10141", - "trino.stats.ndv.12.ndv" : "119761", - "trino.stats.ndv.10.ndv" : "100", - "trino.stats.ndv.8.ndv" : "65", - "trino.stats.ndv.1.ndv" : "32934", - "trino.stats.ndv.15.ndv" : "351933", - "trino.stats.ndv.6.ndv" : "5947530", - "trino.stats.ndv.3.ndv" : "12236563", - "write.format.default" : "PARQUET", - "trino.stats.ndv.17.ndv" : "697964", - "trino.stats.ndv.13.ndv" : "1229056", - "trino.stats.ndv.20.ndv" : "2017", - "trino.stats.ndv.11.ndv" : "659344" - }, - "current-snapshot-id" : 4803221953065828720, - "refs" : { - "main" : { - "snapshot-id" : 4803221953065828720, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 4803221953065828720, - "timestamp-ms" : 1648085545939, - "summary" : { - "operation" : "append", - "added-data-files" : "2004", - "added-records" : "287999764", - "added-files-size" : "13589615887", - "changed-partition-count" : "2004", - "total-records" : "287999764", - "total-files-size" : "13589615887", - "total-data-files" : "2004", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/store_returns/metadata/snap-4803221953065828720-1-fbca9089-7256-4012-9082-858ddb6c2eae.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1648085545939, - "snapshot-id" : 4803221953065828720 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1648085545939, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/store_returns/metadata/00000-5a2bc303-c811-4e6c-9612-3ba906d0610b.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/store_returns/metadata/fbca9089-7256-4012-9082-858ddb6c2eae-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/store_returns/metadata/fbca9089-7256-4012-9082-858ddb6c2eae-m0.avro deleted file mode 100644 index a91eacaac23355c8a273bfb848219cc52272a666..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 574282 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 harl`z;M!i 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 qKY6^M()DM7ya0Y3{41Hu4DO7Rg z^z@6-P03?W9iGws_yN9~(oPwDtvypAU60DDMaY1^4`N0Ly-~AoDVcH)63%Ne1ubA3 znafbwFD=3gty_xZtj#HW(%x*j0Eoqw?r#SK0*0XHo{;CZ!i6mT&co2}p*&QWEMipf z-d7zqfdyxPaJPf~J*|}CT{2lK5{vBLj{Pogn9)xQN=aNqVsT2(&JO(BlA|VR-||Rz zngvZ-wfh&tv0acg_X`)X>hUSUsZn$l!cwD7Yquytx;~mO<3eim!dpag!bO05&Q+9A zHm&zC=LolwzfJ}sY*Jlk7jn$AE}jzs_VEyex%AOT%WbE0#39um_wjFiVHbK)_)FZz z&8ZKk)F7^QTy<5~%cBCO4J&-OWgV!u%9jrS^@>s{YlE4(l2pU;>5|4YTu`2UF)M7k zut9j%%4|<^paN|VO^N=Ll~!O+RH?ToIt^FwEa6br>yoqAUYzSFHQCdiK1I0@<6pdW zYE&yD&d`6Eb~pIFjF-XNAM@5&Z^SBv5uRhbYVUfpFMSzj^ud>f_jikp@WXEY;DKbUQ*w zywX!;nMR;DIBU#4b>`HvWL>@OKW~T-JTf2rd_`7~8BlwFy^qIO8TB+D>F**{e6e~THalF6*CNhtR;;%lo^P%j z=VCq^OZm8D30Zu%VBoi`jAK+z_Rk)*Cp5_X3n&%fOQ?z%q6B*>(;nJvGNnQ7r{7Ka zNhemBnX=2akzOf#)vcCNHrK(`ZS0dYZel!dvH;{Lpl7lG+!2ULz!u)pWJ<8teI=Bw zH_`yu&f|SA=rU=8_nv}yd~uqw<*^Po_Q31(;Aof9sDyBj*J*IHeEh4kPQ>qcy^F{K z9A_A#=NB<`roopqgw5p`B|Jb+ho}*?rAeG#f4QJ}~UUkb4I6u8tQ7V)w>-*F|%nMW^wwEa7mYY`AqEd)y%%7o9jkl^;>Um&=%9ij;ePUbKSAtstXu10Yrc`7k~!J2VW6~V|!OImHW|4#hgBFX>z{4a{#HJL!Aqg}1n36vWZ0 ztyOrl(BQ+Xi#tr3Em`cxEm=iEvZ+Nh($FI6MJWW|pK*0jYRX)85ZGDg1~37x)v zIGWZaQrMZMJMw)^sD)Q^Lxak6v|w2X|K^i!R*^`NBid2bf`CyeI6v)(J&ywya>^E& zB3-+vD`FR2t(?<9Da3IFWGwKwnCDGGKoiUSrQR%P_YiVU;$USbg3kKDk1QqwMzx8hp0qirP3#C@8PQ#Wy3uCJCyOf)K8(lXsH8h+# zyjl-3-gfKBjMzVAaMxlZwbWoMw$La%GAtai<1Z8_W>mET9TlV>&DjcRUu1KC=SyXr zfp7^eCUG%rJXN`Hr(1nbMS?!~Eaw{1`&l#V8BiS#|0(fDk&8d#Mt761>Rb`F`#ejo z0eQC*lZrFBB5M0H@%{%ElB)5?SwcPYB!%st83f-b%W|SY9}l=|qam^_tm-`GB+W_0 zH}lBT)E%0mqt(TpQ`_)D(d4}tR&2=8W(dpn%p$Le75;^b_JrO#nS!C0^ZESXBDd}P z;3Ko}9)Fw^usV^|Ob3NH|K24XiB=z+QENZ7^}6&!62G4WYAg#CL zQ$h1UTGE zM)g@V>$5JD40SMc#==6PX|0o^FSvOphIJ`;Cr6Q+w`$`iZVxw@umB$jvh+T$LCovly+md%LR}J&SaMqY1X2v?B2U67ZSmZcU zWgY}Voe#7p&%Uw2+LAom)BaR;_E*uVlWIeaH}B}Qqb-O*TbRDAsDD%wW;VHU&+@># z;cZ?dCAY+AVSe!lx)ss~-Q2f^dzOB7^$0u`3cMF;-uW@2gam8~4OO=358gDdp<(G~b3hTxmKg%W{ zM~S0RS*dT8FjjOkZxh=Y>l$9@AcTJ&Tq_sx>)AYLyzHCkx5^gJ#1(Wx4*6bV0uj+Rx~qmYyYJp*W|gD#G-$2!^;4AwuF1d z>S&5WR6j)_H?@0`6Y&X5m|i>aU)LqoiE&vsn0r@P4U|W&u#T!#%d_grsCl}wT56K6 z6imsf6|2JI^i5QK?Bhig*&R`-3dmyeK#unoqq5vp0iEt*IT{O9LDi<{`kXmDlohSn zfc-FF9Hc!rXyY}=!E-rz^l_)2g#kcXdtUmYEGxvAO~W$y1RliSr!-Vf2nJ^Oy2Bf1a4^XIry~5-#;?OQka%K+34qV+9MKk#FFI;f6{U zgD=+!X}Owl!~fQf59}I7XQieW^rYP?VGXP3dmSS<^=ZmD+9Zaq=;Y!-o1+@fVA2dH z{E57{CJZF_&6rCSiB>@JYk2b*u>*dK4V<#>;g*L@6Kyh##`g6{FXKRm^ihJ}b`;h6 zV|14D)%O%iE5Z2DPCyTr4^Xu3;oW%kP4ElaXs#BxD8lI5ivrjWmxtf>VEstS?^u* znYN~1bpGz%(Nv{o@q(7{Br#??2=yZ|Hd?w&N7lZD(Av}&94`gJ+A=Ty;!IvGC;U_5r>wsWrI&aNc< zMQ#qCX8_#86Ov|QUhLMAT+wOdzY|9y{jLxz&7HiLwh1OAwn&C9Iphyd38X0L&nqT* zQBdh)K}vxIS+AY8Zedp6#yAe<-1NFo=bYQb356{BIJTX%3RM;JX>!Thf$6j#q>f%F z`7{iE@kuM_)cdz=Jxz0V1D}*!CZfx$)PU;w_F!57unBWr-HHp!()l79bEtHaXf$M_ zKNIhiqEjl!4!Dh~z(1+r$)k%hRX*Y_fD?ik`G4ELOa=BEg(EpM_llZ;RYE$$v zVy+ecVb2tyUCXh`7+_@n2#SHv^;6HKwK1N+hs-`Pix8Dc__UJg+|+csbUe0oJW7xH zv0Yr=bY$}ixfwj(df{#jL;d#}reD;z+- zQ_nra$TG~vr{=scEO>dQyMeq{#2hYtZ#KSGHe52P4N`dHOt+)to?~cC6Tf1wwvM{) zw~yMsHuLE!I4qUwLh2jUmXuc%4wR~d&4=^k-{?7eQ!wV=7(%kbF90vvT`H`)0$$2V z21&tcOhF9DwE)v0>fe&Agzkw_DK^M6tzy z!4~-k-3$d$GmQd0BMrBl+$SFf4(dJVi>A<fXB1_%T5Z?HQBLiz|=b|JhuAqboHSO8xZO^d@j(~b-(c@A5TX}jNd|{!RP&!Wv=j7#Lmz!SJe~q z_gl8If%5@)>U>^{x!s%OQ#&%n!q)p&%PXdGMJylxtWlC4Q0YLEN3bLpO1|OvG!!sd z!P(&K9irH@p;%KD62o$Y>1EO$_@yt$CZ8yk)&6LMeL5xh1FXE&g~65md#;3F=uck5 z``f~?*G9Js;E@xka{BGmn2#>CC-Pd7K4AeZ_zH8IKR`u(OeeD1GoyE)w*2bC)d#Go zZ?@Ifpeg}2>_*^T?dlhDMPlu0icnAEqLFl%$k&+<1esJlfN{;H3Rf3J=NMa=;*80* z)v|HP3dliL-2t}EX8sAy0PU_k>B!2yUmupb`AHQPItRP7>!3E4Lg&5Cq{;9#+A2_w zsgU(f$p3;CLEzFi;_FdVaVKRmIo*tn(mjrc;rSt@?a}9w!8#d(^+ITAl>lQhpPz@J zeK~k&Hgz1ivR&TUh30B9hp3JhXmT`_o=ed9d5uB*W>LgDv;F81?}`h@WLfAS**IgA zwTEh&{$B21N32G$oY~J4h&nm$2CtAorhmCOj!{#=kzv!e(%J^Uyrl<)a4y=Igff9_ z)H;5%26WrhpEC!dZO_d+T_q@cma-$Rp;-{bHC+AbV?ic>XkDP5@Qe(fKTok+_(^15 zClYuFHL-~IJ|2!3Fiz`TPyZBMCECv|DVitp3MYi|1uv04%*4$VP<<%P^h^ZK`1#Y< ziGf39SDBM*&br&vy#sY-%q`qmACU&q9~C-;%p~EeC%EPkr`I z`{HzHn(=ML$Mat|RkdbuyaiFK{(QphBQ?yU+|?}BE{al+3>v(fPC9kxGo2DzG>Hx; z)%ZKJa$R8k@Nt*C-yF#7e(3rgb<{Xc(g*Rr5D_}(Y%*4LZl-`-6d|k10f31mI_%Z; zqZ_YoUjQ>L{<9}_>t8*9O2@Z550S9!E1oZx!vdJ8GW(Wt`zF5pv3A>ZN2^}?XI(%W zp=Sh~_!wPy*3)}-G%$%9upfBkAr>ii8psD-%u%Ngd`)j|!6MM#zv(h~#aV;g&ueYX zbnk#CJRXgNyrHxdkZ38(@EP;)UH|1d_i1mz<0#Ek5_CM# zrE-Hk3aSwo^}zP~tuR&F2k$i$;_fC}0e?@K1%Uq_P(H9VejnwJ==Z!&`XvH3!?nwp z6IwQ=XAW}@A!M&h@&CN))g#vkXLjR=+|hTxHE#`AX=98V0~W1r8QpI;>bPO!TP32z z6`x#+L4GhVuXheRveVV-E0&tZP4-tB^A%i;9xs$ywlzl;pZo1|$^r%AF=P|PSgV0= zQRH5h@?z8_Ocy-=g;-nHWq6YuonA`8eRW0NvW4)eTWI;Ty5kyST^HwZ?EzReeE4i_kd_9Mx3hIsE}aO>3eXiQ%)O1{HTgdz%&}|9GO5)vZhH=IUM)OGWvxX+ zS$T=FwHvV!{Le)G)`bBi?+#oA5!};`k%jFH~ix0NA36b<>1awaPqO^*mr${9%wo{);wSjixB)W=vM7Mv_vb06N$bqu(V# zUi+B!ah=4Gw2!pkxn4LU?U}ECVE1jW zA7IQ9r>j0lH6&9ZC+3$hNgN!Mdn8Tji{K~Ek#q!`;O(j_!*m>c-fUA!lD?F}DVK1_ zluISsTQm)hLOuGa)6)y2T{48s(P8|w#IA~_z@l{Cjq-3#>&XCBCqlld%&RS)}r zEe5sm512wlq1^jKj6Ir`Hn{ zD@Wbj{jze-!lk;+4hl0{wdX5e zmaE6-S-l$cMJpbB9>kJGc%)xW-wcy(Qb9a5rGaX}7ikzA}TTL&aRpdXJ7^D@Y zrWN%yzlqv&{hOET`}RNo;N@yPjg+c@9@DcY`P>Bj&b3SWz9TIJ^g=}5acljQr2J5G z(>k7=UOkC}5_}0TNgU#Xr2#_D`<#~b8pui@fJ&E2P2vGuZ!NVmJ_s3Yr`s|ufTrMI z@Yx8y!inh1OA9r52Eobu<)w)RznfkkzMMMsE9ibOz7qT26Y(4*<&IVo!$hxcWj_j4 zI1UJQlYVsL>VcW|4~JEMFEjD35fho{mL}^=SDsL&Pq^ed*Sd+Y`a6(^qrDSpcMSFn zd7A45!o)4;+Uvk&^@#}Z>PC>gv>!|?6)OgQEEHc56YgN^*fhy8d5eP^BI5_^NBB9$ zS1K>=be#M#s#ZWU+?zFBStul6`?DJft{N>Vt_u<1M3)tb%=12x+$l~Pu|iUSX|12& zu4kHMQa&nxp6o9^=OXWR^}ddebTjQA2SvjygHs;4HfBAPEgz-|^LyGHpE2Dw zXoG>s{E{ph?06ei>45Wz9R0BCWo*6jI!|cqD=>pSAxEFr%#KeaR)5>;jRWwkUG@JH zMow~WIQ+jO>xXGJ|HJ^THu<_hn3qMvRQEmn6bZj>$Z$LTK8~ZQ%{b8?$xR)I-nXzD zD%A@7#_fhm?I>Q?%l@i?B(@2l?Ly&Omf-`kO)t7}+2{usL`NvjHTKkVDF>j!?7D8e zTG0BmjVeviL8&tU2&O11dUd?)n~u{dZJMB|hsAR?&53}F;o92OFe_=XMkw1)k-=x6 z;06@7zn6Sf@dY1tB(60R{EgoMCg>7c>eY7v|dI znrp(d&UchR0RKXaD0h|HHN0o?(rYtAqDn=)7#QR-uZJqSlXi z@06QcdyUr_zq8M2l%^W+$qvb1hN}xkq+Il>IrCKBG(`N`PUA%Q4y!Gv#gVNAN6ISl z^5cu);6)-}|85gnc;}hVJiuYmevX`0rVW}hsbn=Fw&kqfv;&=6><2&gYM#R$ZY$OC z=Yis0KN-y0>FqaOiwhBT!jEM<6L!yusbCd3D405J*kvW%_D#N4E(sffmV29zYHD;l zCX~C+zLu?BL~ne04R6Rf!zbIQzPL!dVh!?mKQ@xWL_r9N)Sai%=SaL>D|n>^4)AU1 z{?+NdT#kDD?iT5n&+n-0O@l8Gy8|V|u;F{BO#`>KFwgdP%XG6OEB5m_+dl9e z6{_@e;=WYqZ*R5g@8liM8>^}dfB^c~wB=2@6%-e0Vuu{fDgzZJpquP0LyGN?6kf6J)Yi|=FbIRkB8=Z9j%OVh zxvA(79wtnWgdeB`tXTBPbRY4Yd==h(Whgei>7i^!Ff)2dk#}>l-v}}c0htA6%dUzL z{SzFZMqgueP>C1d987k!FV7Y1i7VB$MW%>Bg0S(#TMC~N-sBd49|}QlD>73*tNdnO z*?FA<^{mK45NL3~Mb_|;wjP1$f12xy@yaL~t zwiDQfJ)7s;pg5zR@xpUAjo zl(cG($PQEJ%W4#UZ+txUp2Bn`p_AHV$!8{NaA(iohXouzUHt9~-K9iJiuG%^EPJSb zowWn?89-@8C*@Zl*nrvT&Zw)fj~`W*qG$>gkcR0mG;%qJJ2XY^i|lo^LvH-1u-RlH zj-+BG|38+pS*&0L-92tnVOWirI$ux#WJJ>O3qAIs$x_u1GeGl!{BO+%Kj2f_ptj7sCgnxpy8{UoLnjnwezXxB3ez@N(-z@_-4sK5_`3(mvgrr{fcYtm6n zqPm3qeHHQKM6lP{@8B^BBwx{r`%7Q>C*xfr%^L=#KIiNlu5Vd@_g*F}=48>D1y+0g zwzHmF+gmEC94u^tNxdn>47g^Z7$PSAZ+B zovMI1bmOfb+{XEnOAkV9c5QCL4>4Z{ivh_zmdtf8G}`_QQqV-gDSJBJwe-#*pI-e? zwZ4446BG^Io$Z}buh+@PKa}?Rb-U;;l;`4Umxa!n-z{mnb^|TDUwF$xQd)Mw&fNwD zSkdd^pskvD>?*c5%~!Qbj5QTT7_}d7Z_b@iH%g*5&S zYpz=RM!8w(!P)qEGh}Kd>VpmDI_cg0)qJkj5Ppi zlHA63y_Ukc`*VRXJziv67`GOE-V9 zu0bWvdpwrU{%6Xe%OPU;sGWB&Vd2FmZfEtM&dRL0I7iJ#(0c{VgGD_Ki-|@ivfd?Z znp)*6Ds6KZsz286Pa7SdDNCX*j-&xxY7 zEi}_Ht#>-a32-bEz&IWh*mO}lPAJmvjxDZjox=9vEi{kVy=vOv-ft zRX;n6v!$E9#4-q}@4LHJ1XLmuO^x`Cg^D-bo-I9JQyIvq!rd2?EdrNJ^|Af}=7Ju8 z1w9>V0<&n#c+Nbk^XqcA4`i;JQRdMdCyIHl1T(di0qNl4IXnts7$e)DNDqT6&Vn}9 z;3f%wEaubA`3Ijm2at$=lB;zXqJnA!uuFp9f&FaXN<)KGi{46^yYzfy7g%kEs)a<% zq{B4rUitE5@JOcJM}Y-P%g{0$rggf!ma;jX-2$AU9Rg?wEs^Wsa{y@K2j~lWaT{8S z-o59|6KV>(lknG-V7#WH7l;x?kcFX{woMrJPUhqwqInKy^A>=Z!-K${R7ml9!1Lhy@tLPZ1z`K6PeVCasbK_~;q?=t4uUz&8Lji_Iu$ga#p8#Q?WYOQ~)Xd zF4=i1%-tz4rauQU#Tt2YH0h!=xHr2y3GUFq7k}< zH`a5sXi6zmb+LErgPJ;u$Lcn>ro0+_^PVaG#o zQ^7<1EA*DSsdJ?)=8Dx0_i*66gN{<8<=QHL6hJ)(sw6Ce4NW0F?Ovgeio9AnTf>bBh|n1w&jt{W?%sKDQ*Rk2a=(W+U1v(WSS=By4-Q1LOG4w zXD!*FePlkJ)^{uO_0)Tt$yu^3JLO@!7;fks(k}?Ec;8*O%TaKwQwdwQ9**+RfS|<2GOIe(mv#Y5VqMty`@*FO#uU zzq6!0&ya<06hh=g!_zp6dG!cmTzZ7hDe;zogRyDL7taZtQ?4a{1-j4X3p&U1auwXm zyTS&)Bo2+54gg5)pgWw1Gp+bn#G%CB2Xc%K*$(EZz^FVC{du(EhJb%ekNqo>JV%}4 z`#b}S?Lt_3<3V|}+yQ#MBz%UVLg5V8YWcy1UK@RfV*&IRCio)c0L|&`+8frlp+_mX zH^2_+tgMr?ukf5(Xs7tKo_H;Mp=#c`o0bpegW-ekIfUll%AP5wL1QN*_!k8x0qurA z7VedI`d=VR1$&n?N_bKj`T$3usbLMT&;WL$Po1Lnq+sS3?B`FGz3@JXvq8>nbbS-#h$Z35I`XT{GL@nr=mk1o z1%HC>CwD(k(z6(OJypaX5xrar(EBfF491gQ)E`Lw=xd@z9mzKKT8t;5zkuRb8u;eB z8D;73Y%RhKcqErUB2Tg4#^YTbuH(fLU$X{9=aXRXqv$^eQ|Su82=EwD-b7{9PgIUhlvk%MF4+#2 zfj#YI(57jsct2or=BdNG9vX5R&?T_Ec~y2y5`qIU$RX>ohEeI*3cZ^k*;qjGB;j|G zp^vR<<0NgTO8DLuk)5%E+mH!OFcLM(PTD08e8$l^C$xIRqZ&YQiu-gKG@IJ8LBLJ4 zD$Mqv-S1p<&i1y>@RY2XJ(>?VAhAD?zAmYcoVnhu`{+!@cyL^Ld(Am#kv%irBQ~Xy zQ+LJvJr0_vo4SGx7;lxR^!0yyD#825UKbc94g_WSK27FPD`=qH)Kk@vqm*=O(aOl_ zpwAA@@_nMiqYhTe#OddaWPTChaOBkj3bJ|)xDPMFHPxBNSnCJ`)Mf9{n$#0#uXJg$ z^6rQb05OloNPr}JJP}%$32OkDV$-lY8M^!>9j7(xLse3^z2B9YiCgdk>z*ER(B+mS zhTG|tn%~1hq%lVdaNa`0$70>60fi|vAh#o!3cb_X^ z2qqYouRs3=h!+1byJ)?sXF8#rrE$_4O;^pKZ7l&GbPir4N@BeoQj|~PZavx()R(}f z`Pf?9QdIOTPVz7Cwya?kdGhfvBy@S?#_1@>|MdRH=;?|_er`V?u_V&A8!b|ZfWmTa z09`dZ@KRWAFks-YfB;eMLhtE@vis8 z{KS(V5lA2_Bk#z~F5J(9&{eFtn8UxmV-?=(OQtd%%U0=$3sdWFFOWI)8|6#7|GA&3 zapqZjKv2qhGz3%4s?fG;@b(><3z*G89&Vj~~QK@#z>`=n2n9&#!?L_-M^Zt z&H;@sY2f%aIf5&Z_k6LP|OF+S1j%0=simQ za=-E;r{lwK#eS~ZgW^~`k>Yx?l0t!ywL)B!O7deR1qIfqXUs|kQBn7knd4RN-hHO{ z@a`g3>z({N;i1D8=iyozVWs^Op0oZ+L_oGfc69WE@A-$Vgsvi_lH zaTn7v&6vC|tK3hFvLByje6}y!JwWW~VTi|{C2H6ntbb4U3F?zzvd_A21F@dyeYOow z_i^fz;9a{ccs8V6N4UVo57?}9d+pypX}0oZU?d&v8(`Vo>X;7bS)P+349+h?v$}s7 zt*uqLhcG0P8a)fmQGOyp!AvN3%#Ok+!@)1E_JLZmPiMW+_9?U(RWK&4uwY>S>mrhWpuNvw;+|?DSuQ%E* zG)ef_#rQ>?vLOVai3t{$Lm~#5jSw&{gW|eUz1rE@twv$5-robgD}N94?rQvVp!Xn8 zp5}>40D;ESn=BO`&M?&$hjiJbDLC(=sc<5beN5(17nNT<)}koG$$e-#Mpf-=2mpc ziC^{EzaM^h;nFk!qs$lpN+;sP=`||TS#V?XZ9vJfT+UtW(Vpq$OZ61gb2MN5<+G#M zD8Dvl$v_0}+@1!S^9>lwvn%eMnn6+u14ZWts>QjvN0JJ5`?a?2v%>nF%J!!}cP0fR zLm)ruWGS8d#CL?pTU?e+EZ~3fHxKd&`Rr0%clK4Io`#@`uUG0v$jlbgFwPcdoo_Ep z1DzN7-)j`5r<7jFetwVoY5rv^UJoWhZbS7^P!P zLI{RqxVgM(@Z>p#t_3&ORXQR+SJkkyJW>{E@w7^~0xeVOhBRkz?y=fe<}u7^ck*a> zBsb=Xm|Xt#A#36J52wS%c0eKRcr`h_&(H?c&C%h4R9~34N)N$rD@-(kdbb*nbb}Ln z4-On?pLU~crd#+X{NK!2moz1!PiJ?6OyS1&{kxcs##z;U5c|DZ$FEYC&&-{cKw9PA zNRi)W5dZnx4C4LGf6V%x%6s?dNhaY&=xrx&bA?`)ZU}=;4`ffh1J}E*-swy< z(_$!Vj6>2HZN@lMyk^f?o^Aq((0N(Dh2CZq^_2E2*fR&r!)76l*1d8y`sdRY_@VBz z)RE;km?fikqZhLPP9JJM8<*?;h;u@TO0h6wei9c-D7g zy%ZbwlCD$FJ@8QG2|=Ljt4P`tlG<$)M*xI`4yrU(lf^GPQ$1j(p!4al4|(#m?@j1M zaV%_R&NlF*{6b^A*~7&&_paTdJyN_Xdi$(AveHTW?(LwZyC9^HV_8VO+1oAV!rY!> z!>m0g2Mh1Pe>sh}yP^Yp1x*gL&>S_Nq7h-kBGy#;sc>+8v1g}Y%MUK^!WenjNFQIo zTZpHC!H&u}mo9KS_93cpfD~?SX>5)(w?C2vZi*xT@(|AP^n`-30FQ6>CNUk?(NDpQ z-WI)~@bT5*G1&bJzdUlWi3VWM8gQBm9 zV!IUabDBYdi^#g|zKQArf-$G?Tb4*<59AH>FR1=2N~gYNR( z)>3(Qs*b7iZK}=zD&naTjfPA7nP)LUEsuNId!~&qk!|DX`b$*#?F00s`p$J9{AC8n z{N_zKq(F+cwrPcI?bpdRum@>odpM_M_R;$x6VdV>B|fD~u>G;%R{PxvhLUU}1kRAblse+jta$#LNN zl2xWuq;?UVzP*B#S4uK&!M5TVv{IEu%k{4EJ(!>|!%OgjZLNT@vnC)dQF(Ttx>+=L zR6AZc=W5Sy%!zIX2xR(zP$eC0VGuLt>A|Kv5+NZmTUDU_7#&Uo`VaiS_@qp7gbmBYgI?39^ zE2Wamh))>oos?rs^#sE1!;;crjYMe>{ip=XSL5TZht4L=MHnY4qfqR%b8m>|n}*OF z|MToD-SJ-3>K2kawYMnP>F~x$VI(yk z{@57i8!NcsVSMi9y#a8fwkX6f6)*iAk?P5~wxjPct*vnIu`F81TlyD$-uWF}!j!Q7 z;;encO8Itr(=650mV8xLrdy@s0Uwo-fybrAWEHZK+RQy>j=mJT+c0xr898W z44lNHkTG0C$_6S7XaSWiejf+mdQtZRIh^u6vrfeuJ8I)W@?I`>k>;jxkfC3PEBQ=b zVkHng%)MCGyvK`jvu(V%qlL9Ukvm|&zrT-X&B}yo+8bbac?XR9JhQ`Ob_ym^j}Vl)>RERkU3lvL60>`jxXCt)_%h6)clsc`g2gNi2)y% zt||!lm=$Vs2!?abGVGuvQ{cchPL7)WW9LHyiDVwyQZ>EmKnY`KvkHFTo@D3GR?U@W z;4CXV**tQf%`Sx|*?|7br~v08z{o@dohE{E_Gm{{@X@rxRY{?l>UjGx95F*h%y^lk=sdc31Kb^aC&7s3Nxk z&|OJ%CFoZnTsW|!q*&s8)XR-7I@$)0`eBW{ESeR#Wkl-#CAx~$yHJ_ih^H&n73skr z94JT#rVeNoFrm?oW||U~nM~kb#Ul;9NP%y9jt#;1T|BrOut&)Ote1Xq zNv63&r>`5T;Vswo`1n(B?wduKuYlmGO`r_MHvngnW(m~iTRsA<{bs;#?@_FFzxy^b znzxpW{rCJ!J=6%0a>dVUH7+hwoHDDxXPs~Qw^_wbx|7Y&zkB{;)4aiA;i4lseX0J- zCrLqaFHlbW&4>zzx;v@Z65B}|z`uLC-+N-XTmx$(WJ;U~T$yUmgB|xC&b`Z12-#A# z_=3^6$8BO$h(y}LH?yv?)himBGC*oGN0(iAyH7~|MqIL_{}XX3s1PYNz=Dq7TlL6x zTHm~#4PBjfkugC9>^>Q!?I081v%bQA_{&qaQfPlms4^>VQWGwg<7p2Cn*su*o4}c1 zq0`BXm4T6=a1%naR5N(7#$)&)s$Ck8op4h?rpRQOtDY}e0XAM0@|Gd9wSaE)>3`^B zgD-&Ob6AlSV#!b!(Z2h-$G+;USp`0CU+;g{HCEJGzB@sU;Wh|T5JXC^j?U4zzId@e zSMhRp+B{q%2#)^;UhP%iC4t^k-*jzPv;ArM?ff=|If9PvxtMjL&aCYoG`r%l=E5(EJ+ zqRu?ww3FB)Ahxu;1BaeRPMZ+6n7ny};VSrUY<}A0z~{73huPmJ0#&&1;<n&|9_gnj1OR1j^uVqVW#15RSjq#gT@PF^dn%0(tj1G3 z?ZOP!L5q@lW0vo=91q3}i6t{nG&8H>8E00PnTmCffAQ6V+VbpL8f|u0cOvQj+#d(L zaXxqyt2W_QSYpuN&z(|O-Oi=Ekn3sQ8`No-t4_VI4|6J&D&vYYsMQ}&C;^P^0Gu}1 zPB$bsq`MkftPO3|q&YxP8h373R2^Y&Dlth%krrE&Z?Fv~T&Ke4@mYMq zs@~Jiic7sM#D)?K)p|K2X;*vTXk2C&@(4M|ldu;XFeQfzICEgoH&f2wQc_&RKwMU(I5`x+(2$f@Tp><@`yFB;Z`lE<(S*;y7+`jY$aKOkvF^zG8`1k5fTN&AYCAkH>hR3Cil zW@KAV=}_l_9otjNYPxdzs*-_Ft?DP*pcpB~VVE3^p5 zz|F;1LIg=xyw)k3^`sVnbCU*CXJKknjtYNdSZZDUeUrQN_?-oymMCJioKGrLIN~8G z0+zVF$XpUvnh-D%4|yE-nZnY#96V(e83P;VRfXxkSi9F+JbyMWRnijHK<^H$Tp0Bx zsXPPy$0Q>=D2SriMy_^h#rts(=0)jJSW! zOZ&r_PvkqPE1|P3GjGLUg%Q!qe%OaPw?9I+{EK*Xe2w1AyNQtIzzEZV0B^aYciz4U zBU^@!CTt5kj3&k1@cWS#H@9dTkLvnhudY=G*-OngQ1Y~#1BPB%rEYErlu|Oa8tXh( z{){{A69;38gD<-6X&xRi0`9Gdi9RP)F*-Tn$B{Q0T0}a%0_&zH zKf75@JO&t(+f+0|$5kJEp!nCH{tjPDaWM%R`VvpwqzxYS`*~g-xvry3B>V6D={v8n zNU=TTBasH-TNQpRJ_Sun-ebb}yRx^8zFA(CIWzs$E#b_3QBMqA3Y_e?W=^<(yK%KL zS|Do5mM3Ig40_eJ)C)!OvHN8pajWEM^ScJMoHp7ID}0|ROFoZt*U?Q&^A8=!D%!3n z6+MD!^gwLL=gLC5=y^Sm7yi`$uOqw2^MC5dt}IN(>j3WW^sPFtmTxRBpUts&M#rPf z)PAGp&MWroZv`F;X?(hKRz~`<3|H>XYs1(tV`YtiyWP(R{JdUJSQ+8WJgxZQ0-CWM zo=`$cip?VpE$7s^^ypcQ7uoi>r1z=$e{ql^m2ME;F6EsHepWffNlK9BBJtb)DJcAV zi=y#|tjIX7kC)fUxTBsk^F5i>9B)Ctl#fzCNgX8f&t!2m|EEl7@y!u)qMmWmvs|Pf zP1NxR4MwIseVsKsTyBtHAM|lKx};D#mQ7QF0SNAjPHAO#nF~IORW2>%HxzJI|0D;~ z)IA?baD-nnjXn7zg!#YPjo+8-v)3MCD}N~>b|Ci+E;Dh)Nk{308xz`uuqhAnGuLso0Xfaq=AZB@O1`N zL6=X+9x^M(1u^(NQT@YIoNf{e?wJq#;=ee@Ayx&;b&gv3{+Dr}bz7W*Gkja$3Y$dS zB41^*{2^=@kQEduKmSyqZuz5`lkr+!BgqFWIv6fPsWjL?p3Juh%Azm%)>JB~r%?PfL*?mXY<)cE8F?wOU7Mf=VWJ(q}C z;~ogjk*`h9m=J&;r_1BGWqw-@>|{} zF1gW_bx~NW|L39n?^v?=#0Kg4;~p!}O-O14rn9l{!c4;Krn7HF{7{pp$>}K;(U``t zlubR8x}Zn8rn3XHo2Ka}Y}9jG{8K>Ju?8}4wss1T4gT{j1^6giY_w1tVD_;2P9^Y& z86O!#BYyfiwOYKqQl9uRmQ?z*pDdRM#5m71&MwR{|A8k%^ezYCScPk4{%`!q6(KCN z6+)fwo0!}$2tBsH_sr8zDe9$fLSRS)Tj)~#o7DX;2oaL~2Koy0r;b&Qt(^aFPjSiJ zCqfDqU*F_0Pkoz@)NQUxya#OxyXk9XK2L*8yW8LIdOeVLHf!cWRoiSxbj&+F0~egq zfKCOW91uj3To{72KfoT~aLNqrInQn=vjRAemvx z;b?IXPH6)`hzSn~AO?``%fP)Ai+EB+uhk3@CpNUvp7dTtR8L+$B9?^iIL(oRvXz?e z6Mn$a$lkcMQ0$I=mWn+uCc1y}|MeRtI?C#)THetQ7Zk3?H7$K)`&k+AgRxq4?$Wy= zm|6EUka?KM8`)T)(4rF&*Xe?yZOFWBssq#7M$K_+derxChj1!Lu3O83rON72b|2B< zW7BaIeIchZI3_6%@7*51yKi(UF z9IsTuatw{y*X(w4&>{uh65L2k*+)qUJys{OdM72!c0=^`0XCfL*+vYU zyG=9(R-P05@~DH3KlN2O3`*q-8@jPATgXw}bh}J7vP`*qtcc#lEYZnpeH+1^ci%_Lc%cFIJhB`)Q`S z7o5KDZuu;|-ehzmLu-)b=NAI%^4#igKFf>nzfpH|`z)0TkV({ulcQ09D#=VD?^zku z6M7G48T3vn67DddU<6Io-wNGx=R2ml(;vltD7B zlU1{DT}$$c0!2H6+GsnM2e#1Ism}MBT_kB~UhnGEx*eX4riG3vJSjfmW(ONqLv#Hk zwB;t=do>JA@}vY2vS^3u zGJC**ISrjuN=6%UjPcCpXvj!mMGTh8@~!hs!wsZKIh2?i#H?{(@dYm=zXz!LDjP~n zgJc>xol2)_;tCIHtUJEuh}~&8tJs>VKq#}csMmVoeUlALO;ZvmT`IcQ??DL$cBCtS zPy}hJH2qbW{)L%28>VAs`{DH$Aybhp5VP*(ucJREZDF%+N(4HI#LmgBL30A#;QE7` z;J`CI+lxq#oMZa@uQCap8@QMy=a1IR5qH9&>?2y?vq?4YkaSahahxB$0-D}nB=Qy@ zwWL%oCP8b|W}bf+rD9UaGX#)?{0*A)aCm0$gV7p3*?O6IUa4pqN83nMx+&VVna(Wo zM4n=m!RiwNlw?)V>uS(tTnRfCG&uk~^{@W%%@J_I65C@-w)YdVlRO$+2~(!qddZKF zw~9yr_eef1_CLOs(FAp3wiMx%Qy^~xdv z9M8L5Du)q|(-anepFh(HMDB91fy;4uXVN;_i{g#?d2W&OtF8s}>X(%%X9uiE_0=<& zB`krvop%dX!c)#=IOKRJ3c#Xe(JzF>j;ABE;Xl&I}1Yufgt9w zl50u);;EnntE?mcpMNSVq3Ga3mGk2`$(f7ih4IgBA^aQJs`j~nEpf;c8`&5EOLK7$ zW>*m|2^b%ilX|eX?VoQKVwpwJuv-0IKR4GGn42ydy*T}o>3*hfbJ<#pvGt~USf0Bx zmr;t#LS#dO_N`l$VG|~8de)gn#V(o#0ueF;>6{p!{UvMx{Xd^ZrV+Of=FyZG4R8YfF7w}YisOU?5tZSD`)$5hG ztxPwxNx??B8-yj+6Bt@dbEC|(a6AiXzE2IgVFiNSL7Jc~~f_}b<3g#a))WCzoW z_jaY#9pbS@;4qjcGY_14>gjfO_?+_htNPw+w7B&Nw+G<{oO1f@x9LcjB#$s_EMYWk zzugM+V!0`-3EEVf^?463;v3`@u1ERNw7j^++nTXKhA<(urJyhgqg+<||OsI!75(zBE@tSKpfPiJ3I1(;z3PI^mc? z=0^pBNPRdaIt#!}-6gDKi14FfH~uPR(Rhx%eUMy&<|MH)_#Lz-D--rWA5BkAmH+t( zQSzorYc{(){8C(LPI%-IxYz58VK*0zoq_zzSzndHsO&hoG!+6dEs`1OeKM1tL61qt ztOhoA?@QWrj1=T$7TE=SJ(A5?fW8tYL`t$1lM`uU6Kh80M1QHuxhS4Y95yOdZ^Se+ zi$))^*XZ2_S3y|ooYD-Rf_=Gx)V06R_S54tHeOp>Z^Pu5G6)J@1z~v}<=H(QT>*4M z(=#gyiaBP^J8DXkgMUvzMZ(-v$bp9S@N@8K+hReZ8sX8*~6wxzaMhE zbeJjnuy$Mu*zPoY=5~=v&;Phs2QIruQdlI8ZA^A=E-?YiofNvZcV!%+J;=CV=Z#@? zN%`BVkO^#9Uab??*zP2yxeQVjPZ;f7h)C0!=S@|=Nf0zBUH7V!fR-zP0zxt*Ca~IT zEPWO0nE8l*48)xlCFu9SHS=eKP<<$;#XT{GP-jn0Js6+6WM~j1LpO;pf?H!J)FUo8 z=3ajGXQ|{G0uw*LwIYK7^Jg3(#y)-|2HiP%@&ZJHQ!t`zj^^8(=in}n;3w5YT_XE& zGawcTu(jHH&%iIL1r60sJ37aYnxFpDZ^A+hjYapc=4^kUe8`#_R>A60jiOH25PQ}6 zy6Z_lUE#PU)OiGDt}$o)j2p1nInz?byj+HV++SaxluU6tFS(;jKQU1o;^6b{n0@%pimcZ*F*H^#6($0$*2it|41&uYE=YUQyOdE@C)u0!XmR&vHO z0}VI;+E>?~&GQ9WekgVOKtn%iP+$Dj~ zm&jcbI}jDx#`_1Fu!dQHNrnwwjAbKd7+0_nO|NnGianEo`jP8MDxnst|Y z)iX_PJ?k=ugN)}Tx984>Mtx6e>-BZbI1u#dpUOHif zy>NRQ3-jH8QZr5vVYJ-M4Svje_|hw^MCS4#CzqOiL{{?g6@JlPU1CGfX-Hc2)Ssbm zywrFH%62(WiNpS(&88|4^D?pU^(@PNpYFq(zefun$MuzaZIsiq5?@d#CfeMdfCE2s z#`GNf=g=Qw;qtnfPZ5x$#pSqaF5C@C%v#)fD04m%KL{xt@QUH3=}VqLYn1>XKr=<(xoeX@W8;^=x}9ce0D}n5l|p zEK_s4gHgD*ODFHG54+*XGA@#~pz=_;7yTXfc)Svy(9V1R4L3AF?!nyd5)C@RsBZb2 z5^QbtLh-+r77zEBc>|tOZ_}96hdGX^?5$b7^C?Z}2|LjAb%!$I{o{18n(gDA|MwJ>-KTp%3Ti%_j6xG(mHQj=*;@Ba#<$ zj^c1DB%X;Fc#Z2oU>X&x2cOIf<&2z}_ci)rz104M z&`0bpokLl0NlAr^0)%t}WeH}vA^zO=(84n8*t~`QH%@r+WuLwK;nAzFV{Dg<-)IgQ z>ww+w@oZ@eG$2k~(qCh4T;Wy)Agmj(+)@&d+}XC1EeQ~e-n-QiQB@)>W%xd)uW1FV zSTfeqU?eqHN!;OWfL=0H9Wu`cOjp(+o&!aUUbGbCn7Km-9WhK6Y zPMHA$i^;=&xcc_nHP;g9TgN#r_n*7JbHOi8+7HiEsxomXxkmP3w_j4O$;N(nw;o8C z>hKmSg5=y1xKqr7!-(9iWy8OeJqgvKo407K@M$Td^c9w7JH_SAoQq`+ocx{^Fd?*U zB-@nOz^xgA-)`d#*%b%@`{p6sYAQ~;*2@1R2mfv8Tc^jyJQs#=ctUgMZ_3*9_SHmp zd{{@Ms!hv6_X#)Cc~pedqYj{RM=6BO6i53HKJqHnu};7fiCf7DIuNI}{K)3cPn{+xe8Y z+{0H7i=LziiTCh+s1~cYe_plqAKO)6YVz`Uu3$uc-MBExB>J&a&FZ)>SwbO~ppX!% zvC_)uy*(jp+H~v*N&CCr#M5m=Wy+21TC40U0>h%mt0nqjF^&T<Apkodz!CNCD$NB1T50AL%^tQQdLV!0 zyKhCtR{Iv@%re<6CeZh2|X@heF-gHx_wn(chL&+)oh z;b1pL4gxxj_pNXaHQ-n|w5J&A*(iT<%^v;coKtO9{()O-N=u2sd5nl5uZ~5b(+` zE@eT=`Pc}c)|WhgR}6HMLy|%he2ecgzZ1b|z$P`MgDBoPWb4#5aMeisPJW-nfa4qw zGOg2I0js%K!>hE)Dy@m8TZ=wCS$L3rymiHUb>K?TbY;cJKQ!f?6CYf|))Av^vdn1f zOyX+$+mw`#NV0fWCu`Z(IUmbmo~n^-jU@A5x=+>|Q!JADsdM(Ybs5Iv0{RC0LCjR` z0TVb*&Ob03$oMo4;&}?XQC~141vnDnrN&C-e~YS7^3GiT9>pb4x|u`I#T<~hXV`>9##AzDeNTJSC9;TmH}*KC!__~UBcIth|eE{Q>Pe|$9=YYLN{d7#DC z5ZuZRn0)Oo$_m&!$|;!pa`o-FpB_81EOg)Rl<3oi2}y752M0Z^SBeif;4IB);*>wJIHPkGqym~m4ceZ??wg-RI{HNVk^KDNZR)ollBnjUv8~&Yqr#}CO z>!tl7P=)X*r^c|@rfC@N^Y8~#WZ6%NeIM$0pZ#ua?Ui5?X?j-sz37g;b6D+2ig;tRCA;RLq30?5?@2)WeiGJr?l+0`lIm1GDpu6O%I|VawiAl zW;g#{JQhEl$y1}K3yvgeFqkY&>*q+IS;LCsB6B>n^MV5NyKNZ4pxGJv{+tF&S_X2^ zO1ooIHdBmm=(sIVES%7RX-jsyQNX>scpAA8bx_|`%LGQi%H#qfNUYnWb*!h*tF(+* zaM!P;@(MWjFdghY>>>{J=W$q@SaSC)(4BLRc*(u`0XZyn0EemG!%5nBo3VcXI>lff zz_ZDx2Qp-pi%*alD%DyryJ}B2rCcEm*Qu&^zl}Z))?i0&T(+CLCE(U$XT6pCGv+up z$|>k>ICwiwf9D2<$EAhS_$ z@mt9j0u5AIif)%SLHQ?x|Bi%*drtaqF*aGQrTSW0MTnhN$uxcltI%ggH-kHq!B^D z*sLBmM7uQPv5^s7J#(hvkudns`qihwxb~bN%{N-{JiRxqJny|XJsALO{Y|O=CDIfQ zoP?rY3}3(E&+^=&@RU5lG;`{4#gh!PbeMb?&ivFyFYC#xWiGKBZq1Q(eKJFF)${&W zwKq;)=7QYC6Sn5G^*yI`aKZ~?S}+SvxtJUF&Z%wpSrM;i1tKQfBO%zgiBoWac~rcB zH^?1relotJ0i8lt{>C1g&a=$TxN$@NEQrkHF zRDn2*0Jq_eP5xjp)u&whe@0xYF=-*G!NE=%z#@G(xuc<_K{7KVioBIko9Xs6{w(Ko zXNbKMcJDBk)Y}25I^P+*j+A)?h@7i{sW;hU@3K*=Kj-7fj_I#8ijUYj_e0KGO3+-wbW3@4ZwapPa+8lV=TEdwJ4w zZPv>zzZ(p9H9lTQ@-40q+{Ug`JdVTYp^VwdX~--5kRzE?kQ)|N6+7i2qjwT}z49ny zF@WMveHR1os{ua8(e_d=*ng#BBqQzIj+t}BS9TQRMbo8!U|_>gMt9&PKIKct=J zBc>!$HFz7b(bq)6?$gG-806l?W~o=3ujrk(aEvfx_7aXJmxy>q*kX6cfQvgd&HxtU zhfX+d5F8*=kJV&qwFqZ`q*Id$!JE=Es)UAJmQ+wJ)DDXGz6nX2I=KbptFbN;*+cOv zn{g|DZY-edgAIIQHeqj5_=fprtG+hfF6AWA27a4SVY+dV@`JV{aOpzDWv7V73NMfF z5~fELw$bM?11&ulu=M9wSgBSR`_2VO#mMWt!WPs*y+9L;7f=CZF+>Uq{*u+KK63 z9l$-kbKEqrO zEBPCjtL^^t`~citgOxhOdMe<<^Ork|pTJ*yBr?XxIr~FZ?@xigTC@W5v6lIeVX+a3 zrs}eRS<@M_Y)*q>C>mF^{4t*)x8P`Q!D9o#vz#OEyCZE-SJA66le-RJHHV@hrlQiu z7 z-K+$wtZ4LTW7YSZZW;l^`&dDp_hNwfA*rGG#Y%TLZfHBu3(8jQXS4_wl6RlP_;Qi_ z)q%UD_v^oT#U=FC3*i6{ux`BVyMHGA>yYm{FzCLCXj)Niv8u!hpMtb5 zyg0k|7;fXxqW3O>d!yPMIiLi1#y!vM==R$-Q{EaWiN=Z>=|UU&77uAwJ z04Eo(-lRvdHNS5UgGf$ylcDld#>17l0M;W1%qKUd@?^aPD4Umn3$1r^I_uDeaq%GH zpbjQYnmQolr-4w_dT0CH%o9!oJJCLf1ov?o@?1GbV}R1Z~2RS9+W(&0ce;-n+KFuW<6aH@e!k z2iziWQrU~4$| z*K8P%@8yxboX|HVZqa3FBZ9uk^~(Hy4=ohL&^aXEj*0Uh{%_nn9rf3!1PY{qY$zIaO${H+9W=ok>-afAH5}SyOp_)sR z;Gw`#pQu=Q5^zRPvEF8O+$D{|EpBzpggnU#8lq9$RxRh>^HGLOBHu-j;=s_*8aHh& zQk=8#AFha@7vlf$Svai4t@133PIc2#g{tr4Gp=DBYgcL}PStX)!VZdc!FsShdX&@( z8jEg>>SI&d-9SFN8HBtc^E=K>Z;c((J+L!nb$J6~69yuERE2llXP9$0-~6S06xx7& zn^}z7JgL=u}ekSFO^eEC+h>Z^*4le)K&IZ98#`Vknu? z>Zn-Fq8q2O+U<*mIuG1%Z;o!+O*}A_asAciwmHx;u4w<=)~ZdcK!3Y+fILhi&1by< z%hy4h=cgcTsQ>CQ3$$02_du%Rtz}p8<==?3Kf5eGj9a9TL$4G+mqtlgJny`=BAylN z<$vFTUjrH@Uh$^Z+lanoKmpEHf_gJ90GV7DD~~`A8W;D~o2^LW_&3bFBDgt1m4zF! zZ&%iln9BLV9$Gvzy~Tj7s7{$id~aeD85R9M^6&yg@D|BO-unC*M)_Ayu0`>o(VsV(qY~CEl+c61ppgS7kfim(%xL7m;1r*$~G< z>Bpc1W(dA^$SZ?0LJqoXfj{srdGTvju<`rhn%?1v6AAYrDdylvu>aMb$dKJ+t@FPx zwt@loR{)!gvE?JA5mwUTWg#geZ(@wrNo9NFguvif1*j>>UNvE~C}Opzd8e!N zD~{J~574p#M{KerqauBx(*l$Y03;~jau{V+(fs+E4148~R{EoRFwJ%Zjo1x-DxpA) z5*E@EHwN@#c6jxlz3<+V`80?zse=tsJF@(-_?{^jV6-7s(zLLP{msN_xd2G!<(?8% zlPIal;{ZwW^q15eXj{Q5WB$BH&3u4k#`6n^XZug=?y1^r;onOR)~DLtIqGdB)%kKv zi7#KX1J$XuQ_58w`A%@=FBT*Tp$)JsA8B~j&WX>ulfM{aH_oEjii#vmoZS^yvBJqzB`XmCo6yG zk_Z)42>6#ruwwe+UfGZI#Yv3x^UVWJ{PRUtLsTQF!ao)}a+`kxVakU<28ob7dVlY29lA_9s>p=yCkD{lRb^fI{01?B@#GP z8q28fY6KtM4_H{cdYWS*yTeClV@l}8IEMSOWxhhVX)5k9SNzr;;1}cqbuF(o@(Tga zRUA0?)ZaC0oVg+!6#EkV)|BnaLGojxZ?jiLwEdw2QSnvPdGpH@w-|O8jr%MtpF@*R zp$iKP^6=J4ONE%~2fU8Ys}3Vq!dW9f)|2hnr5c2(^ayKwfq1p#FwlElb35$1@GVP3g0~1^h+0o zInQL@hOtef#XtuZV}{2`0>udstY<=uCypNjEI$1@R;gy=$Ny<^pY%QB8$-me0H!Rm zDTc(dRRy6yBFECR4Qfguk1I1(J$VQbT^-#yHsla^!FMfbF9o0CaENcKh-NBy(0sAr zvf=$7w}{3~YCqmS`&cSu&1wBn?8&VgTKAjIT5E~%s5RdrD*HDOUtB(ucurgHjU6a4 zO2TyQIesf?YHM{16Oc(E6Jzb25CZ`SN}>$oX55s7(Io!v)80w2g}VQtf*NJdL*H?z zh^n3GQ@<%M8_OzM!{7DhEqDueqSKv_TYY+-kdYe5@>!+<*_Ak-QLD@@nl`J^0c?0o`am@&2iYtph1k)((1o#=r|;Z)_Ua59V7`7z z`O~BG=Pm-?z_hN$C%X=lH4kjMmCk#P5(oXTif=^4eV?*Ozg1Tf+Sbr~CqHK(;-~&5 zoz|~XZ?<{w^05f%g7DtuwQ?>0a+f|BhrD7@t&ysi>mEbyFG~Y{j;A1vOkI18 zNAo6MO0z`g;NXK*IbYYYk-np0C_|oGnn5_u<##YROUFocu`TXLyIx#-sw@Mk8D+U( z7ASMr?KU29gCY4v4e8~>?l^7(&`cC$Kn^|6h14@=&nmn$>nlN4p-42~RJSvi zpFewfj@A$G!~Gwb`rR+D&&$?WQhw8H(NO)vQ@gAfC4Ig%S@i9@Z_%L2HE;Dd%4#q7 z%{gfE_p4!ZV8q^K^MiQe0}{$=q*ON+NVF78I@y)kIliiQ5=C+>I!e-{D9x(*gJol{ zSd$`ZSnI2=IYP>@M!85@R!wz&+1Nk107xn!kRC z??H3dw@k-;64oL15v|Ucc7v=f;dzHIH}V8xD!;1F+t-VtYJH_ba5w0e$ATpHp#c~p1|?RIJ#%y zg6E>~3COWn{Rpmx{q5u>bSP+8Kg_sCdb;8*bng3WwFil5+h>*1`Sen?{H&tBU|osK z;cpRmK4hULMWvrSJ(9uz0mbK&T52t-vYVzbvLYdiGlxp;zerv_zbEbwZQhUr8#axs zOm@0&!zVemLG|#$WN3Ek)Ows;07Lecxae*EtN9(0*iq-F#Yo$LDGkDA5VP*1GiQW= zt8G@6yDu-&w|@E0DF%t|?^ox!lFHiq9+fZ7-Vsk~s(L;?AcVY2!w>I$TP9TcI6Ag$ zu1Y8y9H#HEtfKAI)hk#o;>h6r-Lh;no6RmeeTlGSYGjEep7u)P& zKkR*F=yP2UTA@YGowjC2{anf|wtYKDmpr2wdN0NAL+b+jR6z{mO4j~Ul{LYR-Uw9T zGLxxZ6H8b1sdEzdzn#7y57@2$kF)qS;H-mn{Hwg1HCu7zC!H>g_yqk}V&_iwR*Ie2 zh7MC=UsJxm{!qpKoC4@2qc`rFh za%|)}r84gJ@x5?X#7I{P_0vKv`-mm@xRB_6Vqtjekkd>^PATgIRxLlsBS?z#P(M7z z$!-BVz}=sSgKthFg8VI8E{{%Kz48@sW%7CP@;{tvci)^otKa?Tf^>Xjp;3qC%!8#q z0de4JMZl?r{Fd|WQd_fteOij&k0WYRxN8Kme|j%d|uRtn8a4na>yNt2W9<7G?g zvBOU)gStI_$WM99F4XrYnj*b_n8yr8f>JzkglbDbnZXr+h0wq6C(_b|Qp`HP? z)LXbZ_5NNkCz5Mf^tUqolPkiSkGpS#&tH(=e0jZ#?$(LlgRdvU>HPiV?;1YazrStd z&@-4FN8R<#@?y8>iTc@(stPG$pK7U8XxuD^-`_omOpf?2_T(W#1j>@99Vd%S;g6H& z5%?)Sk`>sUCsOz3{Pa0((WCgntiQjDXP`1dopG*Cj-XA>{Wq+iu#5?!5~7TEJ_nyS zsf*{>A(Ar+y;|p9yc-p9!r0U+R%uZ<+u?~Rfh^?O+? zxXg!xXh!QLG-7m5BVa!qJ#FvHp$FPe*4U)-zltD*KsZU@n!x{d7NxLw4BQfvnBSpRWzN+v>kDrL%JoS}27VH_-If_S zk=CN`6}oC%`~7O?mvC3RwUmY)mLY<%rOz;T#1(!$qZE-i(5K>x6q!dNoi=$G(b~#3 z*J==S!Bk!5uf^2Te_#C*`MhG6Y=X%T9a$_JFB}M;#Jty4Z*)?|sm)1Y9?aacP-f*Y zjfOU;2|-V4gN1gPv*dT#%RZ4n($vu>(Lxr~2#T9 zk8PFC^kwmVSa~eO?xN)FnV@(297Kh(UbN*P+4{Dr>bpsFXvH!OyTY5@%GxHu?Ij@l?16`hT)8b|NRWZ zz-RatcjdyH=Z03W6844<%&EDe*M6T3f1MkZcCnGGXD5ulK$nO~oyUP(H3rD(Lk4EE zDp1*G&X@+;#{etw2{iQ8Ynvb67RA}NEVplm z`Oi=`-}*-xwljtCM1P+`T%uH#uVo>(<0aKO{>Q9?_b2fuhbDI&Fa`rs*A`D_+Sh*w|!P(rtB#u zizUFX<4X0C=?UQpf}$DA4xaj2NE#o-2T=|RC&fO3=!k;+sQg&nz)$|dT(4jn~`e#5=YDRQ3o9p=xX5MsA&fOvJ2r@_lO4sm)<~lipT2 zHtYr~rgwavw}U;TKbVeOF)6*Z4FyA7+?xN|GKAb1dpoQ{yl~3)=Pg!i!kh*u<+6IP zbQ*4)pGI7SJ<-T3TO_G}aru3*_VNM41Udc$eDHaI2RVonoto2{QE)}tWyYYaOo&C& zn^Z?_VTfl!%PSZtxCeto+HO+@VTvP>LwWthOrjj5Y4?vHrieck0@%5#NIe{tVH3F@ zX}Q_l9-el~BVcCo{q*LY^kTWhW1E+b7Pe^e58K6otP)=o@p;%HM+E+%L_Y_ zosi!TX+LrjFay81FeJhM+rYDSHN^g^s`i$M<#gz$huf2&yv?+q9+U=L%#ZI%S&sX3 z{he`Kra!r^*V{kzR%iKZu>_E(zo+K-fcb%%NO=qq)BUh4eft@*>7J1R$|HN3YZ z+sA@+yRDzFfX`)|b9cMXLNoEKTo?Q#%>_J$oLRk**RwEvdqj#FtRZ~zFi8YqWKWp# z`g$g=96O`x^Sd%qd>1EpJ0elA`f`eD%FK*aqSu-?MxKoS^05-9w&`@+ame4m386I* zS&znfoclY>3fj%U@|CJ)$Z^~8gj=Z1j&kFjCs%f-`H}e5m6mcW7Yo-b<6FZB@ zC6!ohw;jKJEWEMLqnCOzp3gKkW4aDd`#hXOU0spV+3*_=>HC-!YT=eYH4Oc`mYjWK9K) zY@s;T-hUx0up8@_6^6b*Dt_fm(q9{DJ(`vq_yMhUK%}wt1FY&;(!VgcXnYv`g{?)< z8wZQYNps8JAss5AMZ~eBkgIv4pW?_dTjy^}_ovGjwRJNU(rEyvv2JODs&dtCJixs?#9f{xuOBT!tUIOu7>akZps2s zJi-n=N_KYVAHDLQ)V)6y&393?J5IF&p}6^pcUkit{r8tcOz#W9pKR$7{wPXuesovP z0U4?~QPmV7mzUk*mRZ0|_W(-Aw>piXbr=3wOX-bG*?rf9h?s5hy^IjY&U~xj@x~|c zvZwMADE4o%sFi0Nt&@zB0>%sIs8FfK%SNXoYI>q?#*LPalYmd%eveA9{^75U< zU++k7=qJ=DZbxp$Nt&E4|*@Mp5hh`?q@YEv1+Dc^gWT9D3 zaw!Xf0ECr{%w;rwNut_hMv$m52xYNeI*FW6+45@gfrH3>RpGc%CPO`BO9AjWlJ)sp z4sGv{TG3Hth{&J%QA}bsr)T=8`Ub}J-0$`GXX7{SQ}g`j&nc|#Tu?f(X;}%4T*sC;FP_GjA;Rh=T)E~u)7sGzlT4s`$kLby^#-$3f4OZFrw@5CF9?JozIA8Oym4(V#C%w#I!}K znwmOl_Zj144!kaDO8z@2UNAs_)kpc<1~F+Kpce#9N1pEgxowL#i(;-T2^_0oFDR=;hzry-4^b#*|Rp zrdyzsJK8&-g*YQ~jwK0{lnZ5_MyFB68F*eRE3^ z+5F)`?;WSfl~rhmSWlq_vw%aeYrxVP(;tvAKactfl+f;-7&+HZWH+-RvS^V>%Z#ux zyfd&nPV{OPZM}jT-?SN3C3~OARS7<6-x)`(`orz)1Yvh#YuFoc|@(kP}2T@|6iJk;;9ovteXft$VD%CNQxDg83 zLRyUi_@y^kjd{T*{byr~6l<|N-Fe>JQ1TpYaGaFP| zW+J{==lSVk_QC({Cg#LU{8c>SPU(@~@WQ2YT#Lln&6m8(Duk_`>c-P}<@=1L-~)4I z;SFZHQBVVZv%E}r!ca;xx7j4T7zwjZgpGDEHZ0C`o_r*Q9k^C@$@9wPB)Kx+3d9>j ztCjPXQm$ae)L$H1`*?kpb3>apA(5g7l6!QiJe zMjd`UoIN?xefUeP?%aHAjWV8>G4dTCVY_F1O|4nCcgRtRY-8xFiA6T|$q@-z^YOHnIns~tFLH^{P3Edv zP}6k93aKs;Ihe+mUz;U)txU+oR?4@KAH}-F^7}fEZ37^Td56RrYc``yj6u0|lq!*T zO}EJNs`oTT7%i5FYu0Z^lMte^u_tCviR^ViRLfo&K>Zn(h*pDWC6c#qyQfX>+`I>% zz}M69yzd%F>SyWCf#Ary;9qd6LGD-~iOz#dJTgc2e-ZYcQB8K;7U+3JMMXhDK&b)( z(v{v(P?0VjsR9B@mtI2hiqb@i5PFl|!2lujD4m2Vy@W^!ogkrx1acq0Grl{{70FTDcidt5|UTeR%9&zy>+D&oWW=;TlK#ReC?k zM4_Q&p?+Usr8P70E-<$aLu1-Knh*O704d5aLW;tzNsQJcLonhvC+SAMk<}Fj)`fX25f>)+Tx+ec zg`$3QIw{ByW{H!UZj)BZHTM2lUxWc7lyjp_uMjOrWt(TlM2pY!oebT9lqI6{mwzu_Gy_Cj6l5R%&yBnBkN!C)`1;Fx)_!&A^gW!f zb&zCS>0wR&Mbj-DX@E$8;mWUXw*L6Ub;N?w<|y4SE3~d0DK|CqSfnVS0lQYa+zl}g zd0cjke6n73F=$7}W*$^+uhT=Y@c^9jC@NY;Gkuv!u{hKnXJSs1G3xBV|G^2HC?I&s zX?|e00{KCq{{KqoSDgKp&^Kd0$Wl3rEFOKEsm;gFpZFgMeQjM)hQU+%JG%x9`IY0= zV?l3%t9)m-b!IV6&9hdIA4@-ZobXihL6;;q-;D>{g*OC#3+X?2QV@AdfSa2;Dl#&X za-{Lj;hobr%2{^AVIW*m3OhKuniZF`K8G(eazAAE!*Z#VprKeE9dZP#&CGZmPByRf5Bgw&I=me@mc%+Rbi1fkcY$L zTeI&5B(w4Ghll0Swon_RsXq9wJ48(8aIyB4qvHzi+lhwIBlaSyp?U3n6H7H|NpMUV z*p(^-^lMYQul1Y-p{~m^aKiqLa%;H4F?N=i;Fu8)cZ!&ruhrOiJtMFK`Q===HO{{A zZjpT^+exB=hq>Zgf~1 z5$L^NFWhbCL`<3s*bsADCVX?wVHQH(FH9>Uqfgxrk>L78vWoWB1(O~ zm}GU-0Vw#vVXI~b-}k*+8Yd+W*su&|lBaAodYM7AFEYWgJ9EK%W8V8VvZrh*gNo{d z8!o93MRnOjw#Oi6CB4qe8Ru04?5khTeA7ICx|`!*mxVOqGTFAOq;~-mv|?Tg`VR$t z-J<{o8Yp(@>dE`Kcadm^XnCHXd)H&vsv>%j##ay}QFs)(oV2XSYBxz9l(|y-7A?A!`Yb%e^pkq7t#PXkf-A{R$BY zHNBf)A7vEGZN>K>O~W;q+h9JW*O;Fm5~ce7?#eTR;5^WC1Hrp+L46@jN$o~AK3vfS zgY4I-1c;QF2+q`eno}&9km6|2=J*a8eAUppcR+(!tEgu=40{5oo1IJ~2J8*3E^5f9hNRC}<*CzB0ueEYlaG&*_PCzk zBdhVJYg3_%3P9!h@?h&HdA0(5Qp_Z>1;;0>dHh?+I=KEdUHDmHmN|Hv(Gb|6%J}c# zRR7C={*MxRg(tXIvH&ETRq-s}e02T9$5A|H`iv&ecrSgD?uH5ceynE#{%57TOMIbBC>-?AF)KsxO$zW<`z+ z{N8M4(EXe63m~xnowaN%Y%GO63SEuZSNVky(<=lG)mrF;Y){_Ek5TCBMpS>%ZB5l) zZE1`aD7WZ|Re2*#*ScNz3i365^K=YNmyHpA)waYy*H)^0da z7_MY#{`Kxu#X^r@IAl(a11wY zrCV%8Hx&w2j8a;r4dS*{@0An70dQ(zvLEh&RmvjF_vsW#P?<7Ba5R7DcbU|yqXM+`ceO|^r(WY!3eY((#0&^wp>SGY zF9^sR|2fiiWqc-#X7m;f7|+B^(U~MdQ7`-^cw3)54>VM3M=EY373>@dO*zBC^f`q!@&WfIJbQ%u=7b zfjq<(ITNejr2W)|3w3T--EC-&=lqw?Q1xh>lcQ$_|P_u34&y!Lhs-zJHkeQ zH0hnxO4-}u!hBS}uX007&em{Pd&nERQLb{IibeXy<0>mnjK`1h!=ur<6%&dPFur^F zzoKwgnf|XR+!YuXom@Pj!_R9@H)(p^i`pAC$WhOO+m?23ADoo`@HRqrEq+jLSfv!G^8im$4!z4m?2h#X16T16X-C(p$VKlkJHKNB8>x#+FbCWe8G z6|;qLfV9g;FWDEBIuj=e*A$X%h6*yx?%*=68qOc$3JXCINvC+b+9B7D>D8mFhTvDa zB5}olZ2Hz+gEOx|qaMQggS4-&V)K)+XXl-X^FSE#SOJn1E<1#!8g`tg4C%#tujM|! zHPATA{(~y^U%jKpY|0t@%O8*1zXu9%e%spnMzaeKw#m*8am+qQC(K`^#~sZ*<4AAF z-oVdjPr^eY))iBG{XqR`%?dw6e2=^>7x-m(u?|f{V;~ai( z?P2Cm#4J3!zee+J2%WkJW9jlSe^6bx%!c6eZw7c*b` z*8sEUA`CkqF6}98*&t3yAFfm2~^fqKRTR(J{WTBelV5aO5SWiJwP<|lpi!- zGdzn)gO1V^WJ+l_wscMx5232eE0Pg|1)1db+M!Um_k@|WQfwPJD=wZuN2hayBfa82 z%d)=$lRo+%=f+d%0Q8`28obIe)t`1;->i5_UAkUK7pF2jT(XD+LxkzjFUqIw117waFW(N);i0{*z7 z8FR1KJzh4OXva;v#<<&bw!PN3oPpIB>~Nhm?zK8)bj#cW=gj`s?FPA8w~hvB$NeJ=Oey<1l! zRtsA=;Vn&lUO!u_)~B7+H%$P4ODwnGj9&urg+l53?LHsF!z68wO?khT30Gy$Z?%}_JQk%I ztV+qKs|7Xoz~2}j!&6A94v|ZBoBrp-Q#|Oru9%YGQj;xB1rgqUgs{>vBHqHWyAJ6Q5IiB@Ln!X0F z2*OSznk9jimF2~wYEx$xzw*MaW_U=9sczw)Vcm7s8bzLo6sPzug_8EGL5lAMW(TET z-N9X$Z4AHKCa=~zFY4&=D9;T`cjY((A1af&{`%~ovnbvYAl zu8{~a0HAYrw*enJ?&E|+pNc^2_EZc8@3sK1NDB@~#8DfEp4QO`P{x;lhtmi=oDQgh z+i^L!bq)tX{j^DF@zchmjE+i@(xd7Q{K0viLn2^S0$8w`f_6#Oxx^oYG*``z{w_Bs$oOyFw#3Kel?i5<|@F<(<^I#q%#TxNE#4{xzK+;(7WK_!Q zdz6RZg`I69306PnZ3p0w`WsqN088L@ zW4<-!Mx+Su%qf?LCm3ZBPuQZ({kage4mF)`92g^lKHLkWIEo*QXOyg5%o+r(nDFfd zMqy;y2Io%5ep(lt1K*AefL00BvJ_tX*PJh#m~!fiz; zIk90c0F-jO$kN761UZ8`1h5;wlL2_-oB&0v11jNtsN4l#Gg~}XI>$9XBWsMFHcvC_ z39;dtP~5i>gcSuy0pP{OQEAF+=hrN=TqUF5Vh$H(^%|h~9BA1zw6MGWh`f7=WckQc zR*}M{#qOBH7-z0Kju_m>1tZdRSFP09da)WD&6F!;yScCAk8-WF`*=2|2oe1&hNse6 z+rj0Hqj~{ErGbpFGx3}$QXL;7MM|v-b@ox5SYaa)^sU;VZ8PofnBTTS44b3r=T?}G z_>oqs?I&oF_7NIa=oMi|WGGd|nZW$Z$Lfrglgo?>%8X0R!+=c8JXio)0*)ZT78RV_ z(7gz+ftU&7AgLNe6i`UorV=;@Gy+w}0e&neMDb;jSR$E9(=sl^%}c@gya<#YcgwHp|mr@9Z4;R1$pmzWH{ zVKqpdNdn9XRMh8B^?3Xg%T-?EUf`vP7(Nw|d62})lykCAbhXun0iapop!@BB`xh205GykuQ%UR>Dpl@KpFOo z!C|i%S}XhW?q*Amd68xT0kEGlrLnjs3;WukDHbh?cM0w`u+Sv6GZ^&MLAP-f@I${Q z=c?~CoEy;!i|t}3Z2aSMziEK;%6T_;i!si0q|g{3#MLHt)pTA-iPRtDlksMV`UdyT zx<&7bx<`>b$w&Sudq-T#LvyQCab9u%7p+zwqMvhhrd_>aR2!OW@{&!x3LQh4c#G)mg27l`6!I0d^hPb0DCIPWPe|E zx)dSa*>v@dqMCAg?y_Tv-0hjjwNe6Ok=a-?k z0T^pvf(sEEhPmF$DZ|~qk#@Zb*z&9ftMi&TAm2mdmH`|KoJdbYvii1iTo0??rY%CH zp!pnZDe~-#>Ct1V8!0NwyUpA6#7!JdeKAOPch$NTuq{KVEIi06JyzDId`I-QBaj+N zrfzw0la@m_Z$`U9l%14kkOSgIP06e$12~XNy`iiznQ`}*l||u3Q#lT$VpZ-#SrBse z@4?Od8cospOXBea=l8`y{g3-Dt4RGUt|j*?tIRGvLB$apX52t$(zX=o2c!JgPH+ZD z@eRXZ@=OE399dkQG$V%#Ah$w!hm=i)>l2Rqv{4Lgzp94!yoXXG=nc*mVp2n#k*S(Q zzGCf)Y2VuhV0*wBm{mm$`<|%6E z?&lZc<3ghs#7B8+(>Co_%@P_ertN6ZneFYl1{IHWRKHRt*%8L&ZRR zyVQfc)hQ4Um^nCz54Uwr0PCLWwU|f=F^`5kj2=*?zQEn~b{r^nJi^w@z=R6biovgC zoihjul>l~J8XygVTj%UE2@h{t>3qu%-mdzBCp3;EkX&ssZvIcXtG;3xJO^yV#8i94 z#YL}-hh<>vloEO8Y}v8$MN zQ_Lzu`k7x+!>Vs^aaM%QDvwP*bf zwJYrV=Dug4f5sKloZeo1-XxjIm7`&LQDQ88+x-2m#m!Bi43zDF=2$MWM0cR-g_8;H z0CerYkIMqCNF4JW9LMF5#&v`toIKd<01$Bgxq$yjOdDvZm<2{~UHlK#6l~cIXRGah z*z2G!;vaUd3C#do2V3+Ol1_6=FswBZJkpabP(2PsdQ;=3!{Mmib*bc$I0GF<|M`b? zc#O^=XInzqhUxn5MtN%6mF(@)0T%D2EL|=Av^0hL8>^(V3Yv%rL{#x!#gC~~g=Ktf z7HYjs^5D6By0rC!5CYR5Ik@Yr;yqE&6op<8b<+BmO?K_<_w1jtS}MPK+xxYlMC)Wb z4I-dB&{}p)m{rzS7fd(5hnCp=4YNLKlgp7KV6duw->h!Jh*9)P2e&Z1k(sWX61leB7?Kecq?)w=jK;Durq?; zh*LPMFV>^cUz@Ka^rM!-2LUL zV!saHyk^<IlXsMdRJB5CeLi5-eki=JGp#_@16r-N4X2FxCRmYJEo`Kn`Z2k z%U0yjW5=!klP$+F`d`OsX?sfEMM?_XCzJ&O||TnN1*z{bUA<&poG3Psxvh{slO54u*f)2nc9 z5M%91w|@0#l@-+D*~E?3c=@!NH(GB>XNNR78DzITM9Be>WA=o%aITKK z2L*<}x?6_suUK5OKiit?R($mI!OmIK!%KO=8&V$$!f9sS8>_Ehh<6XyD5k`uR$Hn7 z_oMNF|15JFI0$4Ld@pa-l0#d?o|LUDcEYs3tn5Fv;1lMySDYjF`UNf6@eOG4{d%`z zv*5U-L4cC}!K_f!IbX zpK4@Nfb@&j6@OIUHi)+3s26%64gXeUd|PIPuVXUfefR^LgTg|_y=wF6RVJ5yp{q8F zGRn|nRFhl;Jw=JSYYu)<07CXR>}pxolw7Hfe9Z9Y0a!w91$!8EyouT+{94@?{KUMX8JmL+s~ ze>6%Fj=P6?Rb!r5!LBW>O@xO}(z;1$6K^4xlX2?NaA6OCnl-37 znp+-5&bBEh5z_hf(frm=(LW}>(689!sG)Q&{01b3cu8gG>$sV|PcHa8rJr+T!(JAT_hVi)L-gsl+Sf$OkY5& zQoOm)GZzf-652#1EydoN0o0i@EY@KFXaQN@!4>S*$2;hXAfr`9Olhw?22JW13LpzE zjt*hL*WjQbTFDg`?NspDPvUQ)aa1p2cVFVxm1(xuNM8j1O@$!VOY83<6)M!<K?A_IU9PJnZSbjS9`6X5k4wbzFP|?j=T*q#ug0Kz$ zuxEiAzkU;HPzB~`rYpqu8_l!15uh8z_>Ur4q!3yp{!)-7I74N_KXAok*OVJ)k1;Z_ zn(#r<*gU(>*jAT)i&eibmO;?B;_bDp+0^zrC5>7UAjxouFo?7cS$0Y#@e<$%Ezc)2 z?m6aI3puo=3hxA~Jn9R1Vp1E6Hs5PpfAY7iYt9h|<)rV83MdoK&i>@2+f_Ra9L}vF z-=4kvT~+HA*_&dx9(3@cK6(rlP}vpm8D}aXVAJ>zMMqK5AevSPBRx?R=|lo1occwT zxOQMi<}^Qrq&E!6wuYLYeVYQdFiKJ`?@72YhPDd&=`Yp-9VB?-|2`TSaO*VF+Y23j z)mleH(6hk**&F_MU}Yo(oHFYWcih1-NnSFi9)aD&*48w6*{$Sts!l`4fAR?8C;Axl z+;V9Zn_>qk&4X7ltsg4`mh8OM^=k_8QPGWaiGAd?jwn?_m>{iRrz2J z^Tsa?9ab@&?7uJq3*_n4#|4U}+!6Gpv5$keKc_u=wOKH=QrzjQ1%ltr)V)c^r%AdS zh%ETTR|b!od6j1?QVe98riUn`dn!R5ogkx9oss3`$cL*QDj*l7K{MKZ88eoz3a9qy zCHKfDD~=JyRnpDvTK9Fbn}u(R-hH-Uc4Qx;*ApFe%KAE=3F9(UINKV$+Jtxf`g* z@)RjfiL<&ir4vc}D$$u&Zw4J32K8k2;PxM%&(}Y3?bS#?r30!gSd8YVse0k92Tnq~?>nIlp*r(5wzcFS7 z_~$SQ0pygY#WP85qG?KirI4!w*7~?%f|aHM_RHq@Ik&{o!Ac_JDaus&9dxqgfiW5# z`ixkOthioIw+9fRDiZMotN#V_JZ0T9E8ynY^{=x&aUwrQt^BGj=mJJyQyp9pv-rzX zEi`PJ{c=Hf@l&Lg5xt!(YmI)Dmg%wM`SH%uHm%;d)0F33JAFP@-yO8K``}x<4<<4( zY0iCRj*Tb9ndCJZlZ7cYQ5e4C)f++xa*nb=TezTxFh9(+a0&K4xgz&RRsd*lrB5+> zSy}W}h>U$>I{E3i(&^}T&o9h!l&|VF4FnWD41Y0)e8Ms>oZ_y3=w?FW>*h0hP5}8D zFp~qi6esP}{ndOa$zE|ut-sWx_i-MloT8H_4^*7?ywU!j2{dhD*V|z`ItMsPix%Ip z)<{Y&r z`$K>p00guJm93{CgrEDGc1xs9okFWOoy*Fsfxr5U%8qri-6>PupFm9?m5lW=;@Xm3 z*IQFo%y-6)SB7|Vw+i@Z6;tmV7X(}YI0r>@{-wcr!=y9=)qQd92gXcC?$h-Vhi^1mS_*xFRFwmsE%OC`u8 zHxSq1HHhEjK=uO8+`0ow<9$N#mFclwRFMlCs9{lT`W$t2nnS7dp=3g^UbPIxfav0%ImT&Lg;ftXn|B)^gXYlY3m?Ew(m;06ep}gR9xy@?ICCQQGf#GGQi~1E^n4_UjRQ0-p*X z?d=Bt*(+OuP2S5}Giy^iSmZw?0Xk0`@&)9rq2UyWSl*5PYa_ppWs+9J%lL`pt(br; zT_lIkY-9kY%$x*?r0VqPzQj+&eGIDz5ozDR0GV%}JiJRoI@+|u*lQR#)*HIZuIY!< z6_@276*%+r75Rt#`L4jYev_l9o8mHIER$;|H&=!)N~=hdDzq19ok3ZA%g%NPri_r4 zqI;F;<^^vs>9##y-!D9^*4IYPG0-zT+#VJTtZ7xHQP{aGix_FCffTO!&N zt1i~+!QBOM5G4VI-7D{)9_~g_zEzMXs0f4KJiWhgv?VnQAc*=z#9!8p3&R-WyLLnJ zzHP?n(jIkI?Akfg-k5w~jFv6qbgE-ipqhPH8}0!PlLb}hxPp7tujxkm=$uIx1@!l? z(A{52-qlG?7Yt|ZA0<{%m3>9pFl)_gCUjeSp)fD3aX%lInV&o@GdV^jmjV*Df2wor z)R;OA<|{tG7cfeCiYUB4z&eF8Ln+h&aaSq*eqPi#Yk{VY`wwW|HRrSM-ZaR`w%yUdg8(^9KX+IKonLHHG?E;x*}KS%We_E^vG0fjZXn>>jS z=oBpcM+8li_WA{$xC7jfIg#kym16AX!DIb;dsDtVhuy%AChQLFdTcyqC+0Daat;KH zHV8|`jcJtCt>x1c+kt<2hOU3p5`X*k5%27tvEU9&%7=0)ppAW$UI_ud-J0h>{`SOj zFe?f)dBb`QB(J|!Vl6a2akux0&SlhY@vDP=wVH`BxmJ-6e|$Ew6o=fY`saR%FS-pw zcK6?A%K`p2J<=*Ji;cF~)ltk|Lmo8nU9Z3vuT0aPfJ)okWE@)J0cXp%wG5%)Yxc^g zs5!eXGQE&R%ap-lt%N`^kg93IMYQSz2lu_nUIn0AF>;Wt`ckM3i&o%D^Su>Tp^=(Q zrFT#)dGGh3_!~FGAR8AwiRYA^%=|O4iwKfxDp+2zN$KX54;ayi$<@ppLWO#4C*xQr z9j9w1vF3Tkq|aj44nu@>>pR5*@`2G3R%SE|Nuf3Lf^g4Dd`r{X7!_g1u}w|}Q6a1Q zMQ%>)-0V4n3Nk8Fq#7%aSW%_eZKLM-7S&C+w6@vXe@r^SeA`q?*i*#ktaYO|ELQ2= zV}-E5RgX!{rNe^VV#HLJi`P{l3;s&bw>^jpiLvX{(3LHbr?%R9MX=n8=kaTw`yQikETNk_ zC14^~NFR~8Z7`GNoTcXCd8=NSlZ8W*Tw{w8d$Gtwft&b|?9@F}WI)l_W4}ty{t%0` z7dHfu+vC5}(myPfQ=Jl_Yf4t8EsHLWY;CcoM{|(<)51 zbtZyrGL@^s{`h=S<*JOK*MYnPeRy# zVJ!K4rOtf-yIzZPxh+;RSJP>JX87R^?84qP2?5IJxoF{#aCWU#lpH%+hX|X6PvqJY zXH$xq`UjN6ZO`J$8ABVbpGO&=E5E@^2l@@|y?Yj=l&ctNsoReee~#qaBGmjZu)(VJ z&xbViym#catEE=0C;A)TgmOlq*=#09?u%OiJ~zG@^0P>-{qd0Z{S>tCnCJAsF*mkjOql&W&*D+M$t8rRHaq zm(zY`pWR}oIh6(EOKo^eFneOZAzR%14D-5KJBLDhg?cMTGv4dCHR%3w^hR!pM5rI{ zn%~nE&Q}7DMLgVF8%pxeJY1X%0gtw^H zNHQ5(x;fFS9G2W!W4$E`tTBt1Fv(@v&hA%homN0~xxLKlE7eCSRHUaZU<-S_V z(14M4cuNT2Gwx{Me%2mBR2d?InT~v@oCB90j1fGX4UgVgAOEXe(TJP90-I?x=I!5s zL_$3-0Ub~rn0FA-D}!>Q&7q5FK(kud!uP+^C-9q@9_gZT2|J?F@kcjf+EO ztsxioQR)|7oAv2~a7eW5L`x#uPHQL&h%0SyW3KD!ShYQCwq%B$iWB1I)yfsZU=T4m zVLJh+8iA+ zm^aa;cW+hRWx*Q1{hpZ;HlgE~m+aBN2A>meO$gYz>zKpPxMnslY7jL(O-v1O2fYEF z&feB2qBpb=yHrc!dEKc-TaCX$d_cPTR4E7;S(_n0@}JsF#ip#XDbo8N*B;M=;(>Nf zlwZ!9eapyupc92xT_dce3&r)rPF^04O+$BU?BgmVastIL30A#tyGY0mC-&V-Jt`Pp zMx-(u{gddn8D8EYbTC(uSNST6JzH!(w7sio*#^_m> zEY91bHN;cguXzGV^Wm6SU2JeR(fW*t&k_7Ey?fsPYzove2|d5Zk&5SFz5jSO3$a`w zw!GH2%klJ+3t2Pc7F7Za3Agdj@4T}4>5!Wl5!)#h^S0PJ5ZokIO{yIA!N1$NUb!?G zf8cMWHm=91vQ(|#9}=^e zf&3KmqT~YHQZr)%P1$tY$Dux`R-6offF5nKe7}lS_)mjNG!78yROhLBIm|k?&4J|i zt5B?<-k!hY+^BT(T11wX%9P#9O%Wzfsl^ya z`dA_2`l=*qLx5I6y?lA$%3jjkZiNWwXtb~W@evbDR7^vDayfn$>nn7P)5Dk!4)nMF zo^)g>LEf%=dq!8%;O|glYxtW*YW8}a(kkB4o)`g872i!4JByD#Mv1jjg=&UFZ_KAv zGjfV9w1wS5K15YN&<;#^)7PxZES5I%}xkDtM=$N<={~PXv@3+v=eLy zZ!v>jMx(K&mktYqjY3Zc_DXOJFMdZ@Qeciu({`}|y#6F}a(!A|ow>|rPJR%RJ))BIH^KP5C4H<6ec!tt=D z-O}iHuRQRuvDb7l^oEhU>D`LiOl8Q{d~ONHJ~1Sy_r|Vz2fn0ZH4|d|gyDoR(K7+h z0fhjq!?V&+$+HZLs18V5vT(_%yc9Smi6i((zUXAvepCM#d+I8uE2s z6zXTq9ZLzR^*PBF!lh?d3-;ifjNF1w=_?mnw+#VTV7@#5p8t71IFs!ci$O$H zffQgU*x#;CC@O+!)2t<&x#rd+p-L_|hdi8OXA{+YO~AuBO_PYaQP9#J1@%WVlWK-_ zfcR|n8>da8PoqO|11qd<yB!Qfq(2`-Cg+bBD06Uchp2PE&QWvP1DRR8DoWjQ z?y`&up8g8P?)HgS3aCB*RpZ#Gs0g}23_17fz;OwRZRl(rax+P`s7RhbgiE=t}`pp%Q)BuyN^~~l{Ye+bV|7q_{3d&Q-C?i zIR2r`*3td^V$)ixQ=tK;r&3yP4m91xofvY6b3wVy_K85Ex|h^ym*}lL`-LrQF0Y=> zb)?36Ms_^q&qeScWt;1^0H-crT$z!#xKe6Aw8XXQBXmc*}D8Co&A0!B{X? zKeK(mvO%K#IoKz(^bx6sL{pIT4NoMB?@sod8%V6Ztw)X|QoU_iYQT9t~|EQ#U zd)&EB8@TCkf{PA^D|)^g zVWA;XmX(LgE4LVlf3$(}(V15Gs>j0RXj+~;U@B(us7{S{EnmMW!Rmyqv zE~4yDM0Ofof5Q;Xyvbt+t~iL zBf4OGdJOL8rD|^YUv~(kty#IDU_8g6my+duKUlYBYqvjX= zOe*%(R0=C(@(?#pQNUSl${K*nsNj|%E`qpa2&&V3h&IgZ;MCOEZXD78xXbzW|0d=a zY?e0%awA9as#^H^F6*ehr&UzQ5LED&FWc33IE?7~oZ+)dDn28W}u@$M1*SZ=B4K?au633qE_# z;$~K>o*$mR$(XT*>#()~Uwiw_?hnwcooK}c#Kzg)HX~=@Bo1wd!`Z2`DNVbu7?=9p z9=G}4_wQ%;>FFkG<$fLBF*g{C8P=JGDOyQzvap}Q()~Hht8@1RYa7_9! z`eM*pn@d_Ymd5p9k7@6i%37<-!lmIPAAqZV8DFdIBBn?qd~ZrTiWSYg;R(bMTKKY9 z?d0ERd2DKIC5+8CF_B{9l5ShhM+9zTf3^k|@LgmA6 zRl^hskmE(CNqR$YOj4^W%>1lvVSaV;nqjumk;unb0QRy`0NWp2X(GZ$jnzQ8UWaQU z#E*%cCMPNuSH=pktl~Q%OV=_g;8*U*1iWobN1mbbbKd zlD8}6tD(BhDIM8bOkaTS6Y;W0DTB5)>brq#^CwKjL-bVsSjjAq(>kY27d0KcREtht z1+9@y7cb(bw!D=ldH2+6JT;m0dVg@da5t;2Lpu|LD(8>0&p2-vk)6T?^vvvfyuy-{ zNbjEL4Quw7)z|qkn*1+$g0KZ)MSR9Lww=wv3EW}+*xn*oJc?H z;fn8t&_Ah1!%#lE+TlhCTy#Z3a_i24P00@Aqz12!*sg%XMu+uPH1@@o$VaO1VmKHG zEzK(v1U-A3C=!Yl=yT5JXcDFL(|!)h-)wI`ehxI*CG8vvLjIR0dVoi(*%phjJgIPV z_kZZ$*a24h>3L!JTY%E{*TXjssaKIVCINlKsmS}sy-K54)r!)2$1>&HTX zMJExyO(j2@wKo;$XDxa&^rW`G-ZZILIvK+B=albXo{lk%8c3sE_v!HUS}2|rN>pkd z&r|nz2@wIl)%izonu)ZPT;g;Q#@sftx|r3PRjTXcV&@{vU+hM$;$G%Qz zyjlou4iZ*lm9n-c<|HDR!3))$IAK__jk^OS5GIv8^Pi_O$6WCM&oLDW2 zL^WScfH>LFHoz6xqvJHZd?=>!)Fykk{5R|D(Rq6(WF}KIc=-Jl4zFPQ$#?RYpl^(q z);aXPZPT51YwJC?YW9?tJxU0&y!cUF(^0f_`=$;*haR7x@>p-Eii?qYR#m9 z6njg^wbVXb|gJs+QTuYtyI9~CSsu(Rg9O;dJH}EEy?^g zTXiM~)rz)XA2cgY+!LP;^H7w5QYLt3^q3tSf!%&QQok;3djEPbkLV#xFGua@r%dB6 zKj3ov^V}X@g(J$v!%`|}D{;LIw8b!P^aCvSnG~L(cnpi)2vU5fJh5Rrb7ESXm{TFS zs#N#=$1jv7-e{cfY*_8%aPW>JY=c9CC8w^hF;Uk60E%Uu?!^JE)w}1tr z1`ji^gn!%Q@IA1Pgbr_3?PgAu8C0r#0u({On#qlvy-r{Z`saQ57u9N4F1W z$1EPt=8YMp3Qj6j3KZ+Qn$U2}R4~S9SN{#VL#!Xvci0pqU<9QywM|R3pNLzeeuCy0 z+9{B&J`QIKukdP7C*okQ3VB|*nbg@Xv7dcRgI3!Bi#NA`_28gBc63B^B~0@6pmG)f z+!y9rhc*wpbX^+t(KZ$`@a`YkQL$a*Tk^@M9%DP)p3E09&e9BEOb?U7ioCH(AgP_b zzLOUm?}EWS9-~_Dls!zdu(`7sHN|#FH0{lLWHX=;5Gx)*{8OUtX0$!20hjy`D?R5bDeo7-fwcEKq#=4oVHsu7|*>;HRWc z6+C*jB(RFc=+mR#&ZcfBM!WG_h4kgfx0m2#gWphf9+<--R#oyZ$2H{TM&j$9oWkNd z!}eeH;(p}gBZpS%)D^8~$r4f5sAD^0>h$yJK*}MAiPy%VZ?Gc=kGBPqgzp)bo)u3F z7bXexmv%@7uN6l3^YhUQ`^H&z!dH0-`BE+S$#!{DMo5zlLA_)lYn|tO*@@%d#b2Po z!6)t_f1D+0Cpu*K!ggk=cxVF4Vv0$DE4PM{yQ-+z7bwU@yh>T@$slWh%n)W>Ha4*j z=XfjJ7SM@FM8~n>OK*QMNH{8Me@lQM&+;oL}H9my>?g4 zY|Z;jK$OVu(Wu2){HJ@X{|{mB8P(+8e1X0m3o3#FBGLs!dJzPqOHrgt??mYxqz6bi z7J3s2gdXX=1f+x(>5u>s=_R0G=tY8nArS6!-uIrh&bt5QxnJQ6{MI}(d-lxi4UL&` z{xVN{Hmo}+@xnQZ@wKB^sd`r$s6g~=3Y~HEq5_iWm?0Unlg$>MtK9GSc3(jhZ-2Ww zJ6ddtK)vj&h8k^qBwojlso{nPC0?OXi`cVP+DoZweBGsxpHDIHJw|DiuD@V{K$gVP zm*ICCTi-TKu`PTnRmvSm00qC@oo2mhmv9svk;1VavV7``FQQ5%`UO=Kk#_;Bxg`|a z^4#AY6`-R3550_R@zx7yb_1vX}B*< z!blg{x5_o@x=+7gCZHQU#=IdsUdvbgNF03RB)dvtL`2;n za0}kzI;uv+7P(z_LX;f|sjCVzqO94~d+Sz5z?_X31CBadP2idm%@MuQ!VMn%pnY^a z@6=kwW_GuKlNRisB(`K>4Ttw_Ol>LW_@AKs_6~^cX6GA;F>LuCh+K7>vv0<~K9a*9 zu5=HL`w4jT^JSj~j|KMkjIjklS8$|hSv8kXXZSQA2+i{Ar!AP6aaIKj+%}xU1iIQI zdgTXRgt7j;kIl~Z&wXqLr|W-a2*sGy546vWFL<=v&vG3Cc9G|X+b!m2ggeBnZV%JX z)fe>%g9Wu+afb4B4VxB1VeJ_<+!$9Zzw*Z}Yf5v9h^;8*?8g&Q3$HIp1_+@|YBEx% zF;DdDr{9p$A||K8Jh3fl#(7l#85~VCQ71K^HDG6l#J1~J+dx&2xDH+Ue$lUu11=`R z=Ccl#RT(FoLt{C8HT+(Ocum)cd5wiHv~?p8=HlRc9o@<|_h(qBIe~`vL=H?skxSRd zvexq@!!bXlFMH_LbSwGK?5?$>eQM}H<)DxAtePd7)hbVu{L2bAL{aW8O@^VtB>ve?qz6X&Gal}wo>?&=>N zGM{V3a2|3nCiA8Hu{*QJQZ(C}N0Ul)NksGdIa3)TP5$y==)kmJkdY_OGv8ip$LUEZ zIIvcFBVPW%T+d)vU-t(4&Lewq-HyKUZ((9V&jbGvVb zzALeu(YC7JnVFDep@eXRbeymP=BRLjLwMahiKs8)j99FTC(7K~`oXu$uF|{Y&JYbt zn+;Y@<5nj%p%Z3SLp6+Fiv)3Fu7^L^?KYLcv_m)jg!4+j^&|2`kFuQUhXq`w=Pye6 zs=o4h_;rvbgbAUUT~-N_JfJiCc~Y^*k8Z{}Z-yVgu=80`BA)cL8yieHg;WytGh)^L zn42Kf2HE_SC&sZ`FJk9NEk}|O>#IT0o>OCmn~1IV)X)ajjG&T`m(Qb2rRbHbBKAE0 zKIjkh`R75uUON8U)bMKkwYu34eb-vXo?p7wh*7ALa`EXuJ-Ls*A=y(Ac~kPcikj@2 zSq-ygUUf-;cVFXh^uM?)zla z8ioeBDxvHS+aTOjjM#!2*9%6AGQe|?KTb-ts#4mX@ta+L&cbpvLmw7po%g(Yj?qU$ zp|h4_gqk2?Ymf3Rg$}P>DL!$~4r^~aQtLGxLnZK^cB$QBBR)ItoU+!{)UGpSk=9O> z_1tAPWmhY4sXpy-qI@5!Qq_nt@*rO~*ns-RSs!fNbMy|%w6w`89#&um0W`1l-+Z_H z!+-MKd_zjYA3L}D^WU9kEp>~iHcFR^*p|S-nFQuv(S#wn{RQkYxD!0z;k=(ckttpm-ML8lo5>a{4kKvQ_2LpL)tR zwZE>fa_0@j2K?n~-t3Zli<2 zRQ+0tg^sKAocoB9b6xX$VJN@$8uq`sufimUMnjaY$l&CqDrW?%$nMp$Gy_WCRrvwW zfI75gwH@=vO6(cj5f%<>zJoy@?M4%8ps!lmerWjBeoQGDM+abx1d-4LCIe`h z`$AOJ;nrA4LT~i*5az3y%=6BNnnU}ju5vsJYr+Vrr@mOoR;z3XyiG1}1LN3~xlBK0iGH?VQ!`Eh~zS=yWBdCW} z3S=GALNla<=Q2z53Jj!;pwZ8j$zK&g_I-(IIT=_DTMn1%^8@e6L+1{rLG+3sg-5pF zrE_lKP&o)`3n6(C&u#TGyB=;kz436iE;maU@(vBPW^$sP7mz;mNN$-s9H7#hulacs zUp9G4q1|>Hn_9{L7d%4#S~4?(;#b$ZRQmb`p4CC?%@*lKe%RfChPS_n@18N!*IX)G z|6t8zIC5dUK&6Vufthe*1xSKhtrD$F+xqJEkdA@BtVHw|ENkFn)RWkV;%HP~oyHw? zOgffuR)@=mEEM0^1cm>{po4N{oFG76flLdo`XUat#Fj#L6X4|a53Xa)lcNtp-E z8Gkg{VhmK3AbQ3wgHdCaVnFHNMM-Z8*H3`pL&HsqYVAWpYa_s&%OfnAo%|+ znJ&GL<>%Dqt@u(Zz-I&NyIF$Faa}QrcAbAJxR`P8T2TyNj-h9!b!PF;;xa%~n_!IP z*KZ8_ky01$yL#1(e)nZv(qHLxv@V>amIfeS6;b^SIgbU#kn09s;V-N0WFc8P59{>! z)w_&!cqcvOqGF4_cLHFw1w!Ahv^i#(>|%cO*3aMU0sZCHpHKC2g|-5ytVewwPO%Q| zgS(HH#w=554%|i2E9oJ7stOqAkf!NA5DJl=KJ%DEc;+!X^Yqgswi5=YkMhYPlp?&X zTr)XIvJ% zsxfyrCCV;c$EOV%qh?lg{B|W%vIf4BB_razkRu{F!wZoRQ zn6vp4(vfL8Hb~ZD#R}U-ceIoFE~F7?YA` z@TfUsyY0_M@32I~+Re`R-}i&h0RX zS3+~}VGyeA@x#M$X=Iy-N%>4rYuJY$UPa49>4tC9(p4nmlLO&+@ALn!J{RDM^$&e6 zpVzVuDJWZ|vJ^9jaaGdJ;r&xN`E5yglQrvzk$!*Rfr_>9O7->of955UK3>5S!({DI z?|)4Jfw7PA?wJs2>g%$YButhx(CVGyn)u&RwM7#X(5f~&zC({mJn{xeg*FSWNvPv9 z-t?bLQg)t8*uOp>X9U+K|Md;&7SvRn_{kJnv-F-SIfZgO@}&N zr;^xwogMRgpzm~c8B6Dy9=sPgauC#zoUr%uLGDJT%}mlQ58aL@eHE9zI#yfmUHci*L*W$olF|34 z!U5)%gXqC18&x>*$24&83B9$R|Yv2Et-PJP{%z zQi|o2CL?_^AKldjWnl@6Cwv*^oX05D+JMErt^Gx5Zp>Nn^Zy}{CY>|i6BzC?s*U6N zhHugg-+jI*_nnwX$bxlH^_+RwZapiiZOLDCZ=Xiy7>-VUA^(F+j?O!-Nrs0*t9x*1F-*jynPQKgrVQOHYgN?lP=IEAnWTGGvGIrH z9wS5)kof!UL)b(~+#s}z`aaBY8~h+-Gn9I*aXx~d6Zch6@|Ss}c&4bS^HV8N)L4;m z^SCQKZTI+WfDnk~=d}D5LQ>N^L=t>SYYF^l*BJgYGc3QiRkJN6T#Orz3;VOB&GBJJ zclfOUu+KGv1w=GI!c85vtC84db5fmbGLm#gdEHO)@q`js?n_<%2Wdk&3^kF8umjo z&dayzW_1~Z*Lb{&c~V4VgwkHfm}Lxd`cqR$49~1SDh|76lrdsVjh`(I==E(S$fh}L zN~JMY!!@!iJFG=HTj&Uhy!l}RzK5vLPFnPx>s;_%@GXOdvu>1i$C=CkVboxp!1>ky zxn}Pq^EnhiB5sS{nxJuAS*sd%$^K^5`bQb7*z?ss529yEUUYCUT_*h;pcjL20`at* zl@xa9dqNw&+b_^K+MN|0i_3A?1?&!bV-EcLi;V9{`%}+JJeskh_73=&o8SE2NCMO* zX2CBwf5*1t9e-+g{-7?WrdI#-6RF91gH5tZ+p5TKQEDe!-S`$*9;UMrXB;YlzbnDW z9cri!yMEWNotsnpmR3sd69YHBv>HC4s(ecxxBmAcq3);OKBk>&qwxy0T{F((gA0w; z%4u|1tDBP$dT$Embts}Hym#x;Y+`H?3bIrE%o(n8H{0$V7JZheRS8*fW>Ha=lmfas zSe)USK}EM9}u z3mpwSjYv@M)?l$1Ei_=LdAJrkGOUALu$o>m#3+qsEb+6q#K<33_auRI=!u|a6NS*k z6r#5DAd<-tmO{Q6@H2&Gb`;bjZ}R-N1kn%|)vz9;q=LoWXBrodE$PSjYA04g5$%5j znSRAyc=1qEn6dEu>oDVIBbPVMskqad1}8cKO9KX2{-dfNVW`Ey$h1Tvwf4N%t+W9` zkognv=4+*xlklDUQ|7Zb2)lhjzd*l~oDjX?g*P_Kp?UN}1FZvuqv*e~X~Dd`4KjE> z`C0XhglIp*aKJhj{*kE6P`B{8?(NC*vQWs&l303mu%iGT+^)z$SQgW4+~ZDg2~|!N zuF@~Mx&YE=0_>sWsBZ@b$JXgLE^Is`$;Se+X|FgHQiVUZHK-_19*8koPYWYtAr)VK zvYN2V=DV4R_WJz`3v#$nl7Yn<4qQsk)GF3^pTWZ||6+ycmb`hBbSlub=YV|pn1zY3 zng2zsobPd!xMK`ID2NC1Ce5IuFXS+zwvPWyh)-J>j%5IdG81g9qlXF{3iZ2&3WG6T z{+1gKpm*Gkip|1D27``OxM1?544dOn^j{GIPT>7+weloe47a)$Vk7Gm>Z}+KdW{On z6}mlnFz@r=dbsDyUf)Xrq#yd*37m<2Q|8CeT&D`?8Am>MfiVUt`Hb1~ zn=_q8mF+*?T+>)wbw{1NvF$*)*NgusGuz@{;FoHrgKaJh7+cB(1mqh`T?$d$lRr^z z_${@Xg&iB-C%8HY=WJ*f;0h%j*SDU@<+t6*Vbxx?8hab_04ZhT_7pxhmOA>Nfk(GO z4^2lPX}8gpSS$c@B4)xBp&o9AavpNM>~`UGpQ zam1?RNiuJUyYAFP4KwMrXva@Y1vm)Uf!Ec!6x|XeF)&@vY^?E^+s0?MHy+Yg%LtK~ zv#;^;xyIg}vH1|0%sHRM&35`q&=w-1xiP4pqD>{u&*SaBF{-CqaiW@m1+LRC{VgX> zMfDFk@vm=ZG;l^kEea8psijw9INqhF-E1_K2z4*=(ctY6f9#UlGkCMi@IBRCM~U7c zLO&cskP$(4pJ{+X5(w*zf$v&}2XjwWB!HLyxK-td4{&knY{wPGLXCxwGTns>N|sNt z=r=RYt7bO{^26=8dMLo1bw~nng0j0`x#^2YUdF|Je`{j^x>1jFnO?q1KL2#BlP!Fj zL7IDXZOJCDlVzWcT1xWAX)x@l;qKg3+nGlXY>Wq2>;+ByBwl$z9T|-Fxre25S_f?> z-!DBh4`m2yREWTZM=)uH8uehR4-AjuSP98ZDXG&Hb(ZA!l~RBfmhr3KvtRNdDFXX2u#s_5B){EF7nvL7Q>d&)sIxF<5!(fMNj ziP-To73*8kvb#?VeJ%BDh9Ua~MgkN+H_3=d=&zJdG-rF-?hk)dd>Y~WOR1kmxt6lAn)gGj3LQV|DV=q& z`id30w>eN_cXlRp(xBYRMQmr~Mjs#0D4!BW*3oNlnVxWEdI|>=_yvg4ZySa644MH1 zVx*(GSNxkhoa^PYz50*DbG!H2S)nBRUGE=3{>PBcXza#esHZ6t6vq-h8)n)h!$yoy zqtzH-#nYW-NrK2-G98rRxN5!9)+WKcvg{qQdlDB9&vMrVZB<4bN0cGIHfR(Vg{>w75v|-02ITn%cG4d(GvNuwBRke)_&9-f|^D-y`XLJEaF5U4e7kh-G3EQ4w$GFA z?j=r^Jhh-H^9^+`m-x|Kc?-|w&Kpj*3@kWb`kV0ON&hF|OWhO6_AQy{COLyKI&eC! z%#iJ@XLl+Li(Rz@cMJ&|6Ya7r-0ovVoQmcv6MxKJKtIJWI2%!_OaLu`=gBaJ7J=~b z+BR6L@Yb@Q(a2D7o1ZiR-=%{Aq1|3!YQ}U)-uJ3c;4tdy6oup;Iq(arL#^_;#MK&j z%=gMO)64dq6@tmI(5NTk=$hR9=R(=OB_L%2m-lDp!{DlsQmlKxV^!MxwfHCi>yH{T#(2Dnhh5jSMh!6)QuDC(gjkfP5+v&kg)12AChK1$!&se+b`VvB zD{+O^z$3hRODGCMM6?I&SuQAN5xsH63ZBW9&;~Dz(E@o2wvR^r(Qwk>SxV5;P$1;q zD^!>(y)kqYXKZH^Uiai~2vlyh(^Q(^)np_?Pzk-W^!Q3sh;<-*f{+wk1r7S_b?{_+ zoI059+D7oEV6JM5Tq^Z>d3 z|D3z~U(@E>ofrNwSTesO?eI;hu+5osrVTE*sIR}}sJCP9cf?H)2}Pwr!X|CM&2u23 zi18LPui29E9i#dq*ODXA_r$j|usLUU5;(3f`-?94*9q`YO%XIFKBKUs;bH}QX#$a3 z0R2dY0KalJi%f5tr`t3ayr^2L7h7`j3Q&9vpNmO!MA-1g>$5z=hL4PZYZD za16{BOYe2i>ak|Ty-+&A868q}u11Uv8NsHP1bKlWKf`M4KNAtt8Sxj6xL(Afw~(sm zuT=EDzuv^HSt3L9asT@OvEe`^bYHNO@Pz;250;$)XKsW=H#JzlForj&Wqxd9pe9yWl1NSxJimk%1 zwpv@Zt7#oLAbV=$M+<}$h);a{MHl6s!5-J$H7RU@&8Y{@9J$%!G-L?NCv6=H>u21A z-NS-3kUv@3u;VZ<$-xdH_{x&`FeDGdXDN|F*TVO6IxE6m|EAcJO8-HXo2$1X^ojo|FU{o<4|F#s2stx zzW0an*p#aSYocV|f{bvnkJZI6>kfbZcs|SDNOZV3+x9LYG{8t{P*jWnYOFnLHOA}*xRa#uFl9wCLRmkc zXOq1BM{=OYLtAXUw>qXqTNPw`bk$IgG2fol)W51Qt#w48J+{!u{k(Ay%7&D> z1(#a8K36sJbbNC^(wa`=bK@NXl>yd$*V^pi@vIJL!KRYs*bDW}rP?QXxIf2uFY6D; z$D!ey3TZuuh^G&v2`*!uJY_@a1Z@`$?SKB|mVGFt8B*42W}(&cr3<`L`_=TH31M0g_uD!&=OZK8o#sBoOvNj&m`-p!p>I~o3H}4HNV&f+Oi>msfVc?C6+tmu{JFN zz|&+~-zAvhVHGi0ZPLN5L!r2mwoqvTp z54&h+i+SwhigAqdb-^SF}@V121+|{SCQ6Ze!6wJ{=Q)PoEhXV255>D(t z(lZyIu8b(P!gsUg1&ozG>Q_QNDvbR%dxs4?>wtRtu9*>7u}aP6BMT6~!P5#R#wk#1(m!ed2XS!CppLZhWz{ zZmGw`&cJFc?h??SbxD3q!eRun$X0}QfN;%FakkA}!qjH*iT1t|;^YdoT-qM|8Ma;s zutw=wQRXHL9@!tdp1sma=Rg7KV*7C5Q@UD%oK`$`7uJK1ZC@i-8kEcdiWVElOMmEr zgg0!1J|{fEe*?WE?!7O6yW%IHo3tpKLVT^z`a*b_CI7S2KOb?- znu$^7V-4NiPk-XAw-RYPGt3zZiSjw_;Xr=yiF>fO!Sp>5u#f2`gigfc4qb|5aN~f& z_J97ix_Eu%A5?nnBaeYda}lwZ?Y(vM5U`(C!UPj$MaQW-XR) z8v|3zsdIN`YiUpbs;hi}5p-8#%6$G-AnrQ+DXG>AL!2Q78%AB`4h_d-B z1Z-z_MeY_fOZD#AWEs834T$W14ku82Qgu;NRm$XR$83T6pBS_Jjtfl9KJRESC#429 z;h7p*=cPkkzAZQhS(vB>ahjZU>Pm(pB5*}4S_pkU;)2_%H?6F zQ*Z*ZOmTSJZ;_??d*=h_R)DPws}2LJe&JKFm=}zCq*#z@A#I{yyKYiZs=n{_9?jpQ zxhLZ1{^472E8Vv7KH!0(Alqxyy}5ohm)Qd6)C(NN2Ji6WR&{o->Q=);z^Ev*#635Z zg?zoO48aSZ=vd{2sj;eWgN65Yd67ryLlL0?@@Z!kS-!w_OKJ~RoUG&X5s+6St0)lA ztt$84gU;Tlikm^+!j=(2$Q`n)ABRVT{H-QvKmApdN8_LDetJ>t>~TUY$m2Fq#6%pp zRC4RGh63%;YuI%rGv1q=Hp(VPvpOQ27=Pv`n>9RIw!c`wKYD8nooTJsI;}c7v!Pbc zljfr_R;yBsi{4ALv^%R*3m>UzoJDVLF5vGmnm`{b1z=OPC^n#H8Vjgr?zlzHntw|x zX9~gZh)UNpmh<`)erk&OVY+V}zavVE!B>~H6}kj!%)i0q+I-)b4IY#&+jiHq~D0B@gCaePLnED@{6Rn}phL1BgMnI%XCY-4+R#v+_v^!|!5gk%H?W`T!ZMw+a zc~L<5b)w@>=D_dEWG7R4Tt^Xeu`NYnB^F8?-&QLB!ILaHFx?9ab8)~@`fh7@FWR}e zIy8UL5n4PRe3D0|shA#XQV|Rp5n!SXg^i5nY@iZzED8H_BF-g%%f;`aC7LFqfQulN zHPA9_T=4|%GGvi3oKSW5&}%6~X0Kn0OO2slS~DUuyWT=zu`tnn_HFST{LHi|ZF3g_ zwV$YyPYVE23L2qHT4b^iGxIlsu8yO3Ak}AJrDNg|l*Wn;~&o-)T;HyJQ%nX!&F$USaK1KrOe?lWnE3%oFo_lbr( z^|e&+a)L;`0H_}Dql)mtpcQvNrjaJu#(kH?-N2p8KyiK!zd||Leo88fGw%8or#}jx zT6p;1;>-~R6-j!y9cVS~Sswzq+>G7SFga=k3iQ~kCyHOM1oI?b6xijj(&BFphu}`} zlI+@hi`kK#*K7FnR>Be#d{@oNBIQN=xA#HLZY>uYpZ7ShwGO*)h)KV+(Iu18R9l}> z#v|;$3C$JO+rtMmKq%EgIY^$|LP%adP`Z?_~{I)cUw+np)4n*g4^jwYKJUQ}q8gbp)Gw=7+T6UA!m&$zB zXu-B1Y~q!c^`Pi^y=9w4*0{er%H)U*d$C2J1fBsXC{G8$wMYmjqYj{(3^$P>m<=oU z`vwRaDjiA=DRFjiE0Np2UY1L%LYqZ@XF+JC2Xd(N*D2eNys7d5?R-H%TC%+m!wf!F z1%Zg+4d3OG58d01$K1(}K5Y}H3l1Y%Rh@ULv<52e_2QN8k0ImHMYsD{59#?9YEu*u zGo`W4!#}@j(>k@KIHKp|^1+|6aodnae(TLA9U!`6 zM&uvt*e9q*2E{iUJPH6;n10of;uHOP0U4lAl=nCCvRz@pnu%4nKA<moa|Afe75D-|Wfc z(>phjEgl?6QeUq;9)-)Fr_~tW^}7I}HncT1P5m77+_SSEEGZPT{W$;ImiE>AKCMqU zLSHhN_}0hl?0!j)RLN94+GANpo;(=KQ?B`Tl~(iQ6J7l?A47T#YT2^2Qo*C3I4x)U z7CFo0>KqoWxc>{`{&TianfmC`MdQz({upae01n(-??nw8S3TmqnJz6Ue;W{@b6SZU z4$HmsGppB7h2=hWHMj_0c`Lo4zzJ^&B@4EvGkP4=TQWv|Z@F#3nI1~7I+TO2yt|0)s zRo;Fril#|2YxOM(YuGo5?`YWL#&(X5yJ(xlYVQyldQGlGaAqdq=USbL?ZG3?w?oAq z-Mj_K+B#ltFz!B`TTkxXOsANQP49(Hv2JD_rMu-WH`ir#3^O5qTwZxB#aVT(rjf(YkamGPD-_Q+*5aJ@JzXGd%4F+fbYWVj12*43T>b%kk;@ff(kr7{9c zk|?@EN@9@n4JR?6es{3#WeBN?lZ$ZuR=GOy%~SA__Lsk!=8mwKCjPFGM?LHNmns1T zRM&tRH9hw`EVNbyqtX4_)Kp!gAA(eXL~&>#oou)_^etn?IlK%n?vHJXK^6lxQd6P@ zMCCbkIMnx|B(x;MGfZ(Nee|nDY63GV{wQdl?>O+X(e4(VJm)O;K_Wz@n(ZW3f^uTp zK=@(n`N89m4XYmeJ1Sv}RTr?ifxQ_DG-Ri3nIDXwomrZeO_QUNv0E)+>*6Yz{tgRa z^Nq4uZ>XT573t2CC;P7-5S=w@W9Dt=7x8*{*h3=7RN2x{RrUO856NU_sKd*Ah@ufMvbe`!^*uA6UF+b<9>F zsjiU4AzFp!5@~_0y6riG){}efC!gD;^BV0D=7--+%OuHmsJ$LXtvNh-Vx^w^@jQd% z>c8+SV-52Wu9?pNz^2#aYkfMK~l)NzD@0{K==hEQU5=8k#%@_+qyG1<;v zSnaK9VdZ;q@=53x8x}eS#0o%QZ7uM+fgda#>YDjv?Q=TQzxn1ezCjbIkmob7dhnKM z=Le^pb?Un*HO3C`vJME^c7Nj8Nw8ed%m3q+K5%R&#$PpfF>D z6V%t4V)wDsfn$<|JdBj$im<>dRi zL~a<_#IN_mBcKwv7D2snqOgg@6YY{L#HIKE*8*-@$0J# zV=hBya^gU?ZBCaz04*iG^`N{{q;Hfv8L6FMj18_*$r5v;2`zZxJityHYE2C~QAq4N zSredq9c^`D*h?uH z=vRg&XV!8Od;5W3DwNU;T04xBYAv)zTYoo8+%nZTopnWf-AC&MSv@^l+wR6@Ln&Di z6KR4gZ(6KQ%mApe1nzHmy9NlXvrGNKwX?|NZLzbaiY3nD-Vx)v(xHKTu01U@vVVO& zpp)^%6Ms_I3#)-$jmmBpi!B$6u)y%o8!?yEMyBTwTt{7fZKoaOZ!x&HeNA2&;61-` zPdL}^-sSrfL5da>`0g`j?|Btx6DZvE_>o;jT=4Rv1*p+r$|frF^(LwE9OB*Cq@&%- zTs60?vzB?pnFwL6r^wByzsT}%J%dU4_Y60ZiwfXs`Hqqg$faoz|4nGI3+Ehr=$@pX zvP@f&a+ChrjL@wuxbM-RRA}nZ>Bd*+_Gc*G4hjG^fw)2i6&ZqKi|R+$I?DQbn{>_eH57R|N{RsJ_HlP! zkPLn5LDpaa!gYLoO4lFK5wXs%=irAGSk7ysDX!Fs6$ZIm0P$T; zd2KHIwEeX;7r(`8%iQvY%PnGiKw8zeyh}i=7oY5o1CSqCD<)=wJ)1$cAqNbXEb%b( z{qW9{SAf)``x%wM+1zZfJ=ALJj<#l_>Rm+&cQIl_-+oTj`+r0>bdkPce)H#C+d22U zTi48OI%{z(KEG5aKXk89U)g$bDew z(A#iTt7+685QF(n(RGNL8%4IYVg0@CM7RfUvOs+?yzxOccN-IT-bF5SsB*BMw)p;- zi5rjYiu*V!35xy8!0h+*im0;TLTwz|!s8tL$fLwjqM zH()1?Do8-Lb>bF!5@pq7uQ`riQU=KoA{{4vYa)hEztPXBo>^<@SQe&%O0$T&!;4GT z_4Cu*o2b>@1c1(+^9rC}ziu009+dbq>UYD0%dJcn;%KUd6Yt_l^l3nV0zt!T#`QL7UnZJ?PmGIip(CAT z&~$y{2;HUA)|BhHbDt$^*Dsxg?|pMTlvDYzCou1)3HssTXQ#6Lc_r}Iy*OCQE1QjJ zp4N{;I-q}}wbudJ%06jjPlw!u@`h1G>Edv{P$z(@bXeZ-JaNtX?9CAB@ugW;>A6v) zr(2dy1#&Y`7!FkDcrT2ytrG@d5~oBDmR4Qy)D|7UldO>Xfie(pV}j%aJl?o-iMozY z!F?7HPa97;1e8brv{a&W{)JaA`Xy0bgZaNUfYn|kNwN4$S5-$Vvb9=cT@Pkozbfrt z-J3nlyC_i8h8BGBkrynn`Yi({bLQImVr`;^VIn9j#v}A+-1sNH^`Ab&@C?5s9}n!l zW?x;myzZ)(&e9X=`4d!D(^U=H;GV8GS%qB~yzgop=$S-jo11gBnf$_b?El5Mk1kws zc=qTbjj%XG2v1uW=0*h$p$d3TWz+gb*2$68PJk2XihbMB*<%e z5N}d3oo9uu_iu`Lh==XkEO2Xg_1-$1;1u0kT2&UCk_!h4w1zTaA5c;{^VmvlHhgs> zc7I7A{7Sw4j(LJ=`uojy#L)INEdFQ+VB~hpJ&xJqL~aLM_`}w{XERoW_sM7E+Y?du zo}J5c?6zhD4A#@|)erWvlg^^8NcUp<^@m%MY;An% zdo4~@5sn3v>xayi4lSn7YY!{c@4_d!wF6~1Hk=ZpXM8TSmNdw0%bkvD)7u!n*P%#rikIyrdfqj;$f}i zhHatPu4=zwMvk>r_>_)_7}Py0Z+B8Crm_kBs4>i+%fv58PLVRolwrhKSbGq3mwA6@ z_65u4b5!Jc(T);_xObPi1*4)VMu z&B~s7Wo~e9+jvU41uE~?hq>jQWCLr?5&B5%Z=?>G>wUHTWoFx#5?BOGRy-zXEd!F= zGk{JxBpIgRZ$x?H=tT|b?4ksh&8#lh3!uOHaZVj~wk7f&blob<7NEPlm)jLA5(K$Y zkGuXEzF!;#od4HoV8UOX{`Dn!a=`(f<*iNUhbc~Eq}sXgEvc&rskMZv#)sWX`r_ZO ztG}TJd3z~Yr;1y2GzI~R${_D4(WTJhVWmR4NZ@rZjoH$8zWxZR{9~_YL}26#yD57k^7A0BS zzmFQbsea(Q=rlc~*E4CJd9hmcD|xu9`Hu9}FR(jiC4zhx8~9Cx=>6SEW&uqRUjTr+ z)8>d%LUY=16Be`ZCpstdQ?%_0XJ6*1LI7U~2(#QRlJlNTp1>sm+;N2J-DJ*+^u%kf z7f$p3*Ef$=g>z{S&=F@;N8U})1)>0!@{<$ITOR2OzKyu{!}Y7v`05C;y+y0kXD`QE zM&T3%zn_iUV+oNR%jB5+plpX-8@=+H#~(J&HtfoGHSPS8cv&i_q%&CR8BiVZB4MPQ zbydfyTgbC#nZ}DqBaUA>Mi}qe>dE#FUDoiIE3;g`Ekd7uZb>Qz^1tMp(Q+yWb9H9gqt z4qgS!B~XtIDGYcR0{);TEqt40+!w?sm)60)XGvM5_ZJR=nsLP^`i>KC(LqR)tbdI@ zrii?g=GJ+oJh3swQuKTIVofNX$>(?0>Mq%bzn|Og1~u2(3kZ~JxpcVN^v=w{R&_ld zjCa%_ZgU2`A13LQugC&QkGNjI7M2R3`QUKHy?78kW({4r)j{;G6OVy4-65dFwqg47 z%@(pDn}H49E?K6BJvzT%QuuO`1k*1(TTX4B^)ENgn;XlhU-vK)CfKact3>R=Bl7%_ zLN+WZ1mVZ)`esyL8tT_uHUS$0T7M+hD?+Y3N?8Xzi=Bk|Ct-Phr)N zmJ10p&Ko4>We92ErR?G6QxqvnFnOGKUoI_f1OcRyOy8` zLk&}aY=<&Y`c%B9(F0Pa=Yvdm;~VKp7L?L%=!3ESPtnEgYgsl^4D z$P+%p5z0*EjZOMR)0pp`nrr5kzAFmTm`PtUy_r0f$EZ&ad#f3i{gXGFZR^D9UT76Z zt2|45Fpr+T0}!J{L1w(!s0?>Sh0uuk&qjf2y311PhbUk&W&tkRh+p7a=X_sFnleYo9i#wfHQe=?(>>EGt= zC}cqsRgOc_n{vO;DYQ=0y&MTPz4|%XvVpEBtM{zylh3dHqA_n*wO@H^@OU2)rV?CSSJh&yZR~&m}>c{9!JekS9 znJ0h~PCU9@w9r3=^~6<~-SbmF_-<5YIl>dxmiBKwa$3n9Ldrd2X$(d;&(APDkYUbS z72~IC-5#?mafQmT$1=rV{ISxz{A-!x?{9jJ7tX;Kt3q|dGrcgJpcSdp#roQnBDu8p zXHir9@qke~XWShEBJlYk3FNs4+dXO5xh+P1#-($;#rX9)6!3W6gZ$O(n9*+oQ`6k1 z1ItKRIv@0yXTZhRN)?M74OVz*(D;k%v#KxpyAq3bbIaf6mc%Q{(Be84PQI9`?SI3@ z#7-ThSag$1lUp4^mJX;h23w7$H{j;>!%=g(yqq=5dS>#!lF8E4r$KVP$*nS4GbIq> zpr3qdB^ot8RAOVf-Fg@-^80l#oy8M0ZyK@cds)?ZOx4d{x9)`sWwlF9B0auc5(iD+ z%bpg@oxYdgl|pXfm$jPM6PZO-wXkcO1G67=Pxyj_nE0ami}03lEjyqy$|rj z+nBL1uD2MY{N*tSWV7d+V&aQF*t%nPda-?o2w4k5)TZl zDiPGb_XTqFW2ndTuKch6yinNB9yIom@1!y1-7i8_eqPKKmQJfo8BubDNrJ=$52zkC z_zON38q&-~&4S;H{|1rVk^X*zmHhpxHpkQaO!Jl$$8rm4zAFOC-M%@(p#H0=ns(0b zA9P}LD}7-mLKY%#CUn8yJVX`&h1b&-0krcn(U8JJO@8SR1LFhu>~5gaykTpHp%u|$ z$_{E6+`TIo!k@&IWRutpJIXi&a0{a$jl{C4s6g|1I!E5p}vF0G#)g_jklUlU_|CkBAvUx5uxzsCB za$BEi<=~|Iu|IJTlU|yNG#hLLr`Vp>{H~KxZrjs-BF`zc#Wc92;Toet^79SG%cI%M zAkDCkdiQjcp^vzx^Ks(jdGiXo%}Wyh-8;A|x;MzE&wm3c-%jWL&`qs>DLS%B$@paj zcJnfcC$MH}*UB7QA6rgetfV^>vhyk<+4cM&_2?h0*t49G2k| zVCaKe$3#RpXFYC?g8S?1fr-6DrS)^XY}wL_nt0PUzd@8$ocvRfrD2wxD@vyE*HYq> zTec=In+5agqBh)eA-vV3GGn#T<#qL!2rW~kd_!h^-Z3h~LJwPM@k}Cvdwhg`aR%*p z^|zK?t!2L?qUsB?%-|N0$AYCZ{G4GtCa#_-6UBlS*j)sem-e@~HT5i*cPDtOTUA5> z?4US-9cxz@8>vrFFwE`ulTuh5p}yFb?wF>xht%hC+S|~y-6RAA{R+A_R2bIHEujAe z9q5858bTu?+I{z6N5Wr_PZ8ud2-*FG=;eKTcVX`uH4t#@nA=S*44r)m%nyfvK+%RwSj+Ul%bT5t%$=t+E59w3D zZ^S7?C*+bx`S{Um6R!?%%KN>js*N9^Rv+UJfxqeZaRO`u>g`@FO1ZdhT%@yFB6)G$ z!*wga=Viub3W;Z3)xE`>NBeXPp}M7Orax%;vc*eev64)xJR8>~A>d7YcIE*o5sJ0O z_r<=nUmve#-Ty|a={XI&dH?KtIKUn?^|J17?mQW)In7ZOcfWN1)MH3v2l08Xa-IBuQ}Y&%mN!$_abq@3skO# zdt@&YCLkR$FHF-TEjl;e#28#O+5=bRcyZ)BrbYUv$5|nyZF23T-^Gil?MLnB#(?hF zw=<~2u%OYf{sy>mImwQhB>}PiFej7GV$19g9nZ(33!5w|BWGVu69rn=Q%qF5>5^tE zVf=lahg>C%q>p@$?{LJWzWD->)gm5mQ{T9F4|c@)rPTOm&3U(}aZu&803j-W7I}F* z_i6IHvZ+-mY!zsA;@57=;EG&K7AbKT%)*hViL@FzEpnbXr^Y$LKb35_PCXRfRhJ&O ziE2T4mf{y)f>JNZr}eys@Uj zm2M`Sg~5_<8!)u8Nnhi^$H$(TNx z`I~$M;e9Ue9NB$4IG!owt{4wjDn%T48eN)#SWicrXG(%+D5ANYhT2BcH)+(%tNjB@ zQuBuDo|Z84FrPd6Bu)1FXs$IW9>1NW-SBdK8E`)6-6wmmDPxXu%3XHT=U324)`yWK z@!rNbYqmBx7cup6^+daqJ_50C^u0rTRVCuj$bQy2e~-(mCaio$h60ox$+gEupe1F# zLfQCBflKC){IF8Ke360JDEV&G+`G|_jaxpP6xY(!)BgS$`nrLLEIT}zmgyNp%LKIP)UO5W<%RSY_Z*INtWRu9 z>TIkKdi6&H4IP%M?afMm?HgBU%2Jnr#fty>7=wKJ<3pJ6I6tTGxTUo^VPYgLJ8cY(W_Y*aO+8O`(;sCaRj<*2oQ6OzqR;q zPilfmLu7bDkDP3rYY)%n)wM^I7=1B0CV^{3x&xF8T(Q1}MesTbcrVnrz^k)S!MP`}#Vj)0t^jrv*N$Wh)BhJ&Onv$_50P zPjPoOxkx?HCA7zRY;jd%R1KF8P-{4@y}ZyX<909;ItO5=8%;jNT2C^4K~35p zCQmQM8A-ijl^a{m)rokqJO2f>v;fr2SUAavS>Z1c=M}f-lr^o4BQt~>BY-=v(`(0x z$A@i)jPgQ1v+@&AU(_7K892uhuLwg~_ zi7{#@T5VJ7m-?WF@Pj~`QnyA6X!>%CyqNZde9KC{O3JNg7Zb6P1btppubv&miic^H8mz{*(s(pziwa$u%o|@OAB=XC zcNJuLSZB=3P#Df6sTAYEi_Y|y!^i~OJ;PcoBkF;!B6!z#mCe?A0^`_9)@6XBQ^Y0R zQf%2y{CyJhGUp8o2Sj%lMqbVAoQg>5>CbG}-wkphW-c80(oY3Q4Nk+tNjQl0nXInE z%G($%PeGqFiYQy@R9NP&3K9j0%O?zEU*;U1F_#?EXAVo(^v!58WbTwtxh?vZ94cgj z&9_hjHKl^`<$+Fx3(I-s0x;H!wYY(~?@JlZ|Hh|1_&TE&amNO8n{J+5te@XnVxDxK zd}pPFnm^B)oD%GwoECfNK?r@4J{f3=KmmQ?2iTt`swfN7pPGT;0nNB_d$aO=zAypB zxxi;}K{(mzLQC)(B;=>G%_M}6@KJ!`%;<$VCczAd9d7vB*8Q0 zo?*ZW40k}!PUL692Onj99A|zzhAzbmQyLi{b{|%d7`cGPwT`xiM(0BUz}?$-6Ij`1 zU)iWCPkhI}@c(&yx1>GBLgAs3rCQXi&c&=l^*JZ9=}F0bYiwq30*tcw_ zpR~*Yc~oX(vO6>i`WJdpU3+iK+3?}4c|ohHS5w5Gl6e3SzICB$3{eLD(2EpFXGqCd ze~|RjN~=!0zU#^2?B><1QD*=!p8$qj)_tgAyaNFTpeyLG=`gwyTuc^zF|up09sukp z&;TMEiazbD9nh9Rm&S8c+KX~Ii(pX)98JUTL9uJcq#2K|!YzpJi=7&=j>le{H_QNh z`0g8}hu?ya?TjN9Ob#L7yVoGGsaw9a9cQZrIj_-?1v!hkZ;_>Bvul@;o(y_zhGwN( z5|23@s5nM2+jWm4Or@*l+h6;q=Y`(m|j7oRp~3vjxXRLo6z!?QPEJfu_%Ozj{3+mOLltUoQj-Uv?7I=(;s+WJ* zzHy8iny&t>?rS$iPb^<}iM4lr5{_JrktPYSWPL{{Cqs{aA`e~tbawM9EN$s@36#Ri zn>}+ln2>RTnbIF8WYR!0N181w`K<31onQ5g2l33L7t&Q*isycg0rNNsOrpk&$A2() zuG1*l`N@zHxTvLM9?Wp-tGnpz5Ibpxs~grA^xMQbn*SNFnz_c(OjE`Wb`k1?sQS9R zV;`Fh_25Kp8&BWu^bP&d{JJu}n9|T?{_EmrSV?!8DA{1RTPcdn3U3j@u-~Yx zIhDtqK57wlD$)hXn?`ZDNenA*^H1-sMrLBYP-d-B?^*C?yMyoftdN>z27DO@^M-`UIZd{j;Lt+wtyjjZv;EkeII1`Kl>1gNQ%tSp_P|rg~t=H$5z3 zd(8;JAE!o6yF>l6hrV+tF=9k@2; zB783~-$ncM-%ylzV_~XquqM4~Uanq*xAbgCC%!4`_xDWcLKTc-9ou@#1=M5f{js2C zI?K|Gqv?*MUT}}KuYA{t9dG+D4)@C~gSp%SDuTNl2BpTl`m(CK~rQnCFDG@)YmO?@3qI@P#RDpjT$fFFIZq@?=Hy9DIqKd zRBMIkrP3>5_%x5_DY!W7a#kw{Tw!=%O%Lp{38!!Pass4n>HC`rM#j|A$!FglXYkSm zQ8yTSl;6InyXnPAZ1SIJ&FS`3UeHGdpEO2`Bcq& z6GM%ba4S0_#L_vUz#3<+#nXfJh>uj-gQSc%b$vI*&YeK)N#2{3jr_h1d}}99v{3=K z)5KG|@+ehF9Ed$FMieS_&hnLLk*b=J;$ZLEu<3`UO8>T{!W(4w`~e%1KQTOd;xXgm zJFC0Vdvbf$`HvWhWQkVYiQWQLkX%cj{3A{4YxGYhwGrCE0ejjah-3 zCN}v@ky=d`@r-in@}$W+`C2``fNy_Z+u^-D**H-IZ~E7MEESs`mz41Imz<{pDOGN? zIOR&ee-lYD4=zhiivkya&?f-z$6o=9@tfNw_sXwHVE#+BjxD}r$uNEpA_jq&SXCfiS9Ah%m9%A0eaYs4%OU+O=DGW;Pd)Pixp*ElU~yf8v_oFZh~4%HJMbV!@||%nfg)v zdMacUsJTga{-ndSLzQ_0+VKftT?0M}kRahCIshY7mHOy|IjM$;FwW5Sr3TqI0I2`f z?ik)oPzuw87(YL&T8>0YT@QyDUF{Q)Su7&Lbr}xlCASchlD~<0#}*A3kkBG~0|dwl zUiHv?ZC#L(hBr$)d7lG2O+dnJD|yRY3rz$5)pX|RAcd_1*uH8u9$YGyyqu}-)6S^9 zsm?Td%e9D~@s8u{AS}KQ%~w~3HcaI3H~1N@EV?oSdY_K9shv3Hao)h}9|v_|YE1Ei={rA1C56s9;tcPIJO zIn1CS1Ps_RZip&csJ_0e{9rxp;SHocZaekAJZ}+C)#px6!`uQi61G7%Yk*kPTukXT zMIAC6QvCOx{%w7510O9Mu z&-h03p(ZJtm!?T@{A-S3(OA;n)7SfKC3&+ex6$8yCAPP&2j`I{{gZeOujk6^6P8V3E%Eb2q3Si1<;lZ0 z`z!Im=5XX^%d$ZU7}r5;Mlyw3HD7j_p{ziPmeum(NC#Z>=Uv*8^s?%s+4mv0Ey0_V z+T)H7jGfsAg*zIIM9%NCokCiT*de|Rf$NKt@EQfZiqMpbhS1;_w$(IniGNwEEqzeb zp7(rSPkZzJ`8S7>KtXZ29x?Lw9Hzt5mE=T~_eVd@PXa3q2 zan?rwQo&}x^FYYSjzbya!- zP3fKHUS@j_TQ04xOv}M^k}YW&3kaWss2rt*ezpA-q8W51R<(+rI=w1R261^TO{DQ*xPhb|Ieai)kZEu;U zmZ{o|9i6n85{V`O4QShyBS4w_3gc{KsxJR^W_%-UnCjv$1_$u-pEc)!E^U((Cw>y> z&q#9uuD~dUweRI0)^s<9?&am+p7!>Po64fPmsymvVDhJnL4@8LDCgq|U^yH;B;e!q z5C4YMfl$nbn{fcD>C98qWo2TKB5EJIpX`y_s#Og+q*Au+>vh;hP}rB!UV8kieyw+K zX0y)Ya(#fi{F_GHS;lMWd8&&*y{x?5(<8&S4+|!%=^E^FOw;>X4tR$OGpfn!pSES_ z-QjbJ+V=V(zzM?XC@!@*9woncgt`I99R;rE^5^IZQ;G9avkvf!*8R=$o4$jR4&Lw2t`Q!V0Q09?FW91ERNSMP_n=;+muJup?b1qFg$Lx&myQ5{Q?`zc@%6SL?IgWV=sYpLR^syeOLcXmdyWp(&=G?|nl z)pn0Yd+_$|>7EYq05QcJ zUVFUM)HSHE8f$Ct-#uTEuQ!QretI7A=;4>oAw)#!Qjcx-c%Z&X4KC#iYWjZZ9>gH4 zz4m>#qhXho0)=c3l?P3MONA5u6GlyX+oM!NzDrAj|9JrL+($T!buJl3Z01LYO0?AV z$g#ubg{t;dS9>zSHLwK&!&x4qb}}+Ddcv;_>CXbD%5FDp5>S3Gk^0cuzTF~92wG`f zfb~eFx>a-Z(|!7^9Yy^9_?6(rex%dUa5?lPJJ!n~Vph8UF z@RY__{?Vm7nIU$G-C>Kk%xn3gB0W-t&1tZZZDHm>&k#yh^b6eZ;m4_F`OK*VJz+fw z<_E1zOJmksj#ll4OOvwmZI+0PTI#%}|5AKz@9zA6$&57Ayo)KN4C}IGR<`WA8OnJ| z)~6(U89E;VIa3al$Xc~~`*g}gbzM|+jkG2sb@r{wbMqX;68xIBoj*S~ntyd2Y~AAf z9rOg*lPbdQK~F5o31Qz-i`*kpF=96)x+^ zEd9uLc09G+lj%D(Vn3s-QIpQ|P~MY4%J&6{@5XH)P78@T5tm(RMrN!<^huify6~pf zxdnj7*96(rkfnOTUxiza!PU_X*-yACylED81u6;Ut$>3H4+)`kkX#1{^d zI+pQeRs(sE<69aDn7f&DMPZMP_C6P@8_^b};2RtWswT($nViMW@PQ0`^@{L-;iekS zgMx<$3U=x}aTSx~vk@mVsg{AQqe%Oq`OT__5gAa%4O=?Wh?9d$w-TOZvCn@mob0pL zoQj*re9gs294cm8t9R=)T096F@^!ppX|BP$7bD_n!51whE822Dkaxo?OrpjO2t8?* zCD_Gu!ZVX2iLQWAFpn~)|98tW_RcK3z%?qXE7bg#J`HdG)sXp&a;)>-H*G!ikEk2F z24-T?_#L7A%}wr_^M?Jg!Og}2%djR^77(mNJ$boEkl1+|YN~9S{OJJnr;6%a?G@n= z*kJ0H)X7bg+P7ww*i%|7=#iC@S|R23?`4K2{~FMxwSGbtrC)KnqboHY8790~)i5Wp zA&6K2w@rSZ;?hmiPnXiX(kLNQIcyK&Y;5<9*?o2WArdt)#ONL^_nALVi9O-8uIgzJ zdOmb?(-8{XmWO8h)B@6rdxOIf@+Ge2pTZJ6fY^vfEnTAO8WJQ&xGrveyM!o`0EB}U z=2{U&GmsZlfNwZS?V8^28N~|Ju93w!IB1lHw+iuvtB>t1HR*ezOsFaq@<79RV4Kh% zSCP+&j2E6Wweqj8Obf9y(pBp3UVpZ;RcG_R%q8T{5$$xn@qY{RKfgVcxc~h9?U$^d z-`^DzdFSsjHj`vv&8Tpj9r=c?kq-=5;#L0ApE+|BqBkc6-Laoxj(Y%4S97kh9~KJw zX5+7Ry;+)dEBGjgQ$KY+gFKg`Rs@X^ z>FD$m*g0Jj_9v$;6tSho1EvQM>G*7`XH-OnBhpm#aD`5lSl?we8s}>g6WoyGPzF}` z>oC&VgN3Tm5cK23M*?eNj`4d%&EBEAQx9I1Tr^ky#AbA}F5|sZqwxGpb;|GolAN-(x67&Q zB}gi#*vq8s8iH@hIaUWe)&QtFNsBl3{dH08gmy!N~OF9K- z!B2`L_PG<{Urvn_;VB%+7`iufB91*NMl<5@l3KLq*x$XkOfC;RNJ7{aNQ(!#^%bJy z$88rd)REyqPha1m0p>|Pr zbC3=DVH%P{ZbM(MOs=n!GUjKn+bylm)?lEsoPCXVjIhps_nbEJcHqdni4@Brmb=-a z?mC-%8mvy)bROUZ0v;}s^^LLn$eT|g;tmV#lDNVbY2yY>K<#-JOaLsxTaahN-Tfy7r5->#KnxbV1VAi34xv_1VCmS8eQ76Rgq_KC3RaKymptOfm z=nqWlbK0jf&vn~V=!x+@g&sCm9sVoWNIkRCt&Rhhrlr*j^8fmZ5mkN*{fj~IdFV}c zyU(GwO7hlUHZJKsSf>wZO;yr=s4DXg6y4RX^s}Eo%>DHqMUlg6TC!xD*HwQ<%i^uO z0R4J9j|TtZyDOUU zOZ3KZeKAlbevV4b5_}KdesheP*Z$p3ILMJCP~m$EXOb)N{9A(&`8>n{*`hUe3(sQY z7FrJdUa+LMeYIdy=+zQpFvYKjtdj&Ap6IFUFFn<~KE)0mu3_JjrDc1~_YkL#-pJSw zmiq}uIyId>vB6N>J9y09Nlikv3uC6C^hL-bgH`N)D6w3>FVg`tnh(#dk#7D8HJAK@yf21eq`dh{y*o#k;YAf@W&CyV-Z<4v9|Ahna% zJVr?e8?#J7P8p5>B>t@ z8S9$S)xOLeC#)Y=mU`JS|A|1`r53uNhRSOiWjqK-ZxuFdgkX)TPr-|=2V|Mjmt!_7 zKcwV2dE6B*RG3j=12KXXI7W7L#hhdeU)1(=5Q&n0tCYqBQxgI3(|U8(=RS6xoV z*Se}c!i`X$PF=ExI__}peQlIdo7R_dZJ#Q=NrE9W*G{kyO89cPpS5+GGk~4Xx2zRU zKaQ;flv^m_5fDB(_F38zgoA;EYUy*5D}5uo+&PPD#cc|CliWi)3ij0C;tc_(;$`GZ zU_0^7t?vtewYsNqY{~Q{uKB*X8S)Q5Ni2>rL6LRcou`)V&>g_ba@nrZf)~~M1ly@0 ziH{13M3O3J(v(hgs*2+}a|Z=tt1Jj0uiet3#;X?F9&`qNAcZe0ky2rcRT?a#C0etz z&*?TMB8BI?AF}?quf{(WHg*LJnhir;&wP3!*EQiZ0l*;NRYMy>=N5J0hP zsHNLN{8`R1D+ixGxOs0B^azGGGNO>}1td;cm5-;e=;;VS!Z;)VW9e3f^LTwxeI5A* z0(-{=Z`{v~6Ta}TKUfzFMf=UCThPq_4@Yw zgbQMaiI9(8{=Wv8H!PTjV{?s$Q=X_yRcKUh%+oGBua{!_RLtWVf1b&|{xS7Lc{yu$ zvKz^6v~4a=7c6O*w)Uq+Qiy{ptol$DB9%j^$-bO!k8b|+t&!3j_s_Sc`(1Zlw?BVc zh)hsL6@AXFvBwE@{IK!=Rd+&Q!)7}S-8S3ssS>Alnp)+LSOmP&j-24VFx;}s$RclRVmOV-Tiu$OQTSIISN$Ioqw;8iq!6g41Nw*6kf^=( zzI~xBL1c( zxBXMHD;>#$GKV8o6PF{lGw2ZydqT~w?3zq!;nSd8U7&odwP_6)P%F(GiKMxmAA7?` zZNK>)@+a^5_%G%wa?&DC`eX^398q*-N}~Gl?q107LA8j%f;13DkA zO;SpenWm^`XT{%qqCJfd_@E8VAJzi0x@3;l)ZKqhgNh@*4}0jYsloFto4)808X8?8 z>yGh9`5DQ7yehX!t^;_5m>OFKl_@4S3dD_7fxWS(StKlrc$cQPTjXuVW1m5}9vr>g zCz&d{p1UzFcvCLu3*xe6OND^JRueKw6QXu;8b@hFI)X4A*x=cuceiK|Nq$1eIE zGl#s-&*&5m9Phg-)<#TD&U{$xgS43i@1**sUw6T)2|3D(Kc^=X`+e=YVK610mU?Gq z)LG}9|1(+*xyAA8`9Jqb20s7ukaFwiUu#KVh-K%3!qoYf4yo1wZhzNEvxf(_4vD7z6I#{c8!@{1%L$$#e~vL8 zPYw=V+8XcM<0CqhW*}K5ZqDdzZ_uVcJC1o8G!{zvorXisj5|AL{3{u4(0S_2z$($* z0@kjazBith#}THBQMF9zyN7oqMqdd~8~Pd8JuF0yZRnN;a3j&m_^3%OqYS0>hWUYV zv-vN%Z$6^^Us*j2R;Vouy6t;XFlmGZy+3-XaVz@JbS99j}^9d zusq2)&wsP4YVt3r^{VL5-;+nBu}+6YWfN-IYUq-=GhbxK!@8HbaW??;K;vPU-M4edaV&HSDI5ZIEdiV?|+gy(~|m6#O8UcY>hs1|Sh zzMff8>h;41TB`9t)+A|B;`7PdWlV~auf<4;4*Nx|(&)jF#i9TkQKYvDe*FX-k;E3J zEgv&lHd`sho27fzI=jeW?dhZR6P0qeFI6jJE2%h z-$twU@#IQFeMI0zX3C=qUaF~0h#mP|RL#+pkVL%r=A^xN{GJ1BaNT!yraMf} zrJnU2jf>J|8%tdnx|*tYN~2N91P5BrP3tT_4r*0#-kYz^fi6^IQz)nJxr1Isl_8J#$5Dcfib}a<*bBppC zG!xiQEW57Vd%doP<>vbo*ymNg4jJdhr5zzxVLepX@@7p_SMihu7Ik*HH_J*X23Q_@ z${KXz$yTBnI(PhQN-jH}bksqd`~n0vy?eZVw?naoP&a4L<=vfr+Pz@%JD}w{yU-K>1Yc1#_DBwiX=t-X}diN zA-%+%9JoiKP`&sf$8Y0q%jUXzu`_m7zW(d+ zi}=?!$OL|TzcKD>1x~;`rZZeA?F%}V=h5D>lquDh*h?eAt-Iunp|p_~ z4R7+6Tyi}swl7{?cMG7*3^$%*sEB-awX~V0ZM3U76ESodfHTxpP`ED@h#)70fj*}m zS%j~9kUV`VRwUuiayKEb(c(x&JsL`mE+%WZawPt zXulCBOdiupeu}b%=qA--o1M4TdVm~(A~SOr-f}9b9lnN z89Fd}j&ybkE^Yg(YS{Nd)js6Vc$RA{f>9Vwk=dE=m2)1@Q2vwLxpu$e{FNmp`Q;z+ z?YFb_h^juBOTF4a4)2#yXoN*y-Ao9tmTAhc*?oduLkrE+i56X#net<2-Tp=gS6~W8je7yv%p%QUnE`srsQ@PW~6+pO5+^bH5Nv zdw5c~kn%|BB-LK%mYf)kS~5*A{?d=-`tRh9+92fK z4Y~OLm>s>kcTd%oR5+J%*_Op|U+4P7t1O4@Yn^qUOi)!0oz4ey`8~}dhBo6W)?uHM z4<=M~yJ=8ilP*=hoKvCTQ8!LYFxxWv9i5Rw)>-p#r3GIedrMxHu*M^q0j!v z)ba*J^x9ZvHLIf+Rb!k=#Jx&PZQHzni7&rdC4qCRNP)_QFyg|ySYN-ws|sAVRF&nz zRhg|O3M{fdJL=7`K1E603-BtVAgohEu3l%^!*3m#sv92E7Fs9;Q<5Qe*4uJon({3~ z&u;(;xAKf<*x-MX%|yjZ%{eabdb&k)F04g0w{zP{M6WJv#%_ppzs^H$51*j=0?l1r z{1OfoPgys&2D@^ViKOK;`@VlJj%p~+^)nM-wAX15zfNo2 z!-hy*jgqiwr0a2B&kLZYH(+*tvop+Bk7FWHEFc@Ls|xmGly)e40{B=AP@F*s3z6f* zNc;4|wBzf){{JZhGw=VKG9Zn=?fY?%_fhxA2=AjL^OqTq#M2&JK$H{j~_GkzUX_(m;dB3+Z(weK8D|^14W8_3=Dk&m%+ZR zuOGJGs>Yh|m|Qy9r-J?T0N4rJ%=n!%;O{DZ`c}gv=wx3-gK1%PAv-4s0lg`FFF5Fk z3pN&-VD>9t1cM1v(O&r@BM#lHtj-uduShg$Xj&9O&E?qHxM0(3u8Vjf3(ahleGlzi0C5q4@Xh@BiK32UAJ)HPz5vY)s&0y^!*3 zwkK=z^v%Tm9G~Z8aS%Z(j{D-KG!|rzR#bYcLC!$vnm(K`XIaldx=n64V4r0F7adPy zESJf9AZXyhe}e`dk^gVdz{48)HsP$)za>EfAzTv!Em-|3k+6G&58AE-=xU^40=(>raY zgI-?1sInB`3Q0#>Ez?i(`V*rI55Yf{jmYxCIn_tj!hIM2&g=gPPC9CS=Rh0AQEhc4 z1sHMA%FXqAOC-ei5y+kE78kHZ0Z{{)<{3Y$o8{@7CwVG*B4ae{RB@_*Gc4D zDDJ#@UewB$Wz^wE##WY{``gv;W}x>jZ(1|FSaG_&yi$$u=&5?JwK>oQ& z)=Y3zTh{6A zb#&L}z`xNYh!aKCEW3+K^p-*2b+_oyseQfFWGdQdN@EC}gyu1#BDC4A&voFWn~I|5 z=^hcrZgBnKG)z#J6&kvh*9gjJ7^Thhe!r*U?bmN{=DmM)%0S2!92}(c~?s;ZrkCouysnJhC3JV9lH5nTj$U_ z|7+_k<1_#lRK)dAVg@M70|`xPKRz?+ZiK#u2k4^zfyY%pj;(s^TQT;F5}a2)QK@;P zTNMyv4#yQIv6%@ez@K#|j}z<}blOM)$l2fu-LYyg*MJ^3Q$!Xs~*)~C74WvGj6)Fk&Wv17L% z)d~wfaosDCXO}mSmC2h@?b7*2H09PJuC=Lg9qHz9SCE=cp6*~Gd5YNK>ciHZlm^uk zeu1`&6Vyh&t-Q+S3Th$bvf}KcgXz|e2@`FAbMeGX?HeiH%={f0y8Y{q)0T;_uwl>J-kA=+#cy_EHh6PWR)7=SMn6JQ08Q#18BkLCURyWz4q0!*mCRVzX(|Dj7t9t=ThJW_ck(?TOru2(LxkC*v1a zV5cR6_u%ElWb5_r+b(#TL}_!?uXI?X-d4io!n? zQw_ZeX#(a{u9hP~EKyOI=_Ogj-l1F(-il#5bAXuTAq#15-3NhqR$(Ug3zEsjh&gmm zS@Py^%{ktR9n}fk84ZJEGwc~iTrV0Xu_^{#TwMt9n5O&t-9#uONQcW zETmyw9p=-FBsB}N_+w`tx%}xA>CGu-Sf-tlwKb|Xl<(ANsanP=ZF`HC8L8DO=dFs)sM zbuEel!H|1SCcS7bHU6~N4TIPw_l4Z=2bhjw+WgfNmEMIm=dpxS4pc1&svC)J)7cL` zBB|Qz9n6vYx1Pf&s)z$pYcpkIHh++c4h?cSc{;JwIB*DOcjwJAcsY}+cod~pd05Ae z)#^$%7*Z`crDGgRv_?h}JkreJ!7wj#Heb+eJx&XXb-FX|l8&+aLDn$4cw>=n-q0QQ ztLps-?^M2N+ag_k_#%<7n;u+26A>3YfR@k{Y)LY}UvO$K{pywLzRVhg_v3XSm+YmF25ksLulz2s^o6Xe5UpvF3E+=l{5%$ zO(l2&@j=Z09rN)U{BL{^N6$xzl(6KyAqJ3u!;7fuVGAOw8u2PcaCOSOIZC|l!$;0T ztJH}ErcJ|?M9u~_m0kGDJw)Nh3sj{!oCoxi7U5g97d(=NnF3xh;3?O57_9~IhD(P3WfO3GU4M3reL<_x6uryuk4>VqZmFXy~m`ZYG`AFDz!(MyW!(m3bZ`W?#h zy((|7rLBgcgr=7>@0;{9RnR9OpT(NQip#y55U+6UFN2qvB8Q*2_veDc7Os9ZC3hx5 z%lf5S-*Sk#z5Z=3@@K5$Uz5eF_QXd;wU;DQFiF~M8aa%pX{ssA4VO1u z*Jt61V4(;j$kBD5BTm?JeI}3NcEGkId(F#pS4G_DCeWA@#K}t!)h&8ZHR6mTU(vp^ zp}4$1z+L3xnM7+==6u z?J}Q|FvxOdTORZP`-oDbYAHg?3E`@ zJ()wBb&q5G(=D?hWw110b_08Gt1VciS6gbPbyg>nj-I*C^#$m{<@4!cYXsnx2pkLx ziMVlJ7O}T;)1F99pYC!1#KIX)X3oGf-QM!!N%T7T1n{-y}Q`JzT=52RkPb=F*+F4erl zOwAThGwHy`15j7 zrgQ(OPeGTV!L#BG?{b^%To1W8>S(X#ERY^Ljd~U8g5Bj-_>q56sM&`0p6{X$pQimJ zwpgdN6q<~^QhEwT9}UI=SQ559L_uG$Q%p6G*?ORTiQGDt>m^P%K`J?|u-I?lL!47h ztnH1Ds)T=m)lBKJ>!?+OLKBAoEPKmU^=@)aJ7O}ViDx|ak(pRaKZfP|pD~|p69X!| zG0_rKHZJPbD^%i5HBYAkMhR7$Q<=4>eOcl@bxmUSSg)ooc5j>O&~+T*Qxz3X@i^x= zcyV>ye#eaL+^#cO+NJp%KjC%)?v1`M7`y&uQj{&q`1iA_=|1gF&%YZ2O~f5=Q84-b zu|{eD6G;KDb;r2?;H&t!ZF&2okcoJX=Im?O@jt_~leb<&#h=`lb5g(F>%{-9C3S9R zG_69DRDs-iU20#jdeQT0QrD_Gbh+R()o2?|0kw%VqnM$wB==OPn)Yb#>S~av%Q=8d za_dM3bo%Y}+%3CEQb8s4cjni7=Rs2ivZ0fNPQUHcv;M2=8+v!+e-d#j-MCS-MS*GI zdziN@5?)zp_P%xC+?hoq)9P=Cywm&Qk`{#^;M+mh6jiCPxI605{*et!RV_vONPDU6 z+vFypUZZ1YvhdRqiNUX_%Th;kn=IEc$+jJ_u}o{_u+r=nyogpcEYj%evV*;b;n*`q zF`q75s77ABrMuZOqCoRdY&22)7jdPO!LS81J?G{JYi9j>(enU?*D2vmZMZ3A1XC8-_QWH5! zmfeG>B;1V(^Z;o#j@8WSKOi6e;H_-uYdhd6pr2}!WIoVc$`)I9R_c)&=JHt zqR2g>$82EV3fW^ttf(IsSFY0mmYcp+$k20K zj00fKj~x}{X}kjK;zd>1ESPaG^d0q##U04Q<6`=?A)%j=mrtKM;AH1V)1=qN@1JW3 zt7XkF9K4u|)DXO4 zUajwVo^9D^e^*SO0q?!>*qWvK(xu_mYJNbRkxKlc@k_e!fmHqC*tHYoG;Wp;l%nCd6>~tfVA3Z19tLCbmfddgt!K_0Ab+@tz+CjTFIoiBB zb9!LU&^1m%s<>VLQ$%k_0}z~4O@6;DF}gGmdFy43$JWg-#TCjQX)J*slBMe$fhOCM z+6J6T!QJKx=EI{}im!rsyg*-pFLh#y~b<<`yM zw|`UKu91+X{CnN~H5fm-|ch8dB-!x zceX$BC;zgt)>Y;;uQ^-MS3=0+)>%MQA@*$T%|JH&PG-*jR!KN%QHM-0j)vRNC~fbk zj(Ov4*TFM~9 z-uJcx7$KSiCg|l1`9?et!Y>}AH;8<+;d90L0f#0XyP*Acj zk+cP*V*4<*o=&6MK=kM;D*)>eu*gPhg=%43Ko7&F$r(=vI;L>O4LA(biW|k}kS8ak z)n|inM6k<$E(zrWGDL>!1zhs22+{vpGf*o1!@eKY64tfvkJ2cqb7}vUng&dC)4gGK&Nqv1BNK|t218-%2-yt@6Aso;Of9{+Qc! zqP?>45unKsqDcV7gvSKf#u z{0&BuRrRM;4 zFYa`ny3!9+BjPW6jC&JFTGw33N6aQYtu_wEI5;V`P+vht4J*k~kb&@mLd?DbMzY_J zF28McC2WKA0;M0_=0~$V!_p(33PLNHOK}Uzz@6y^`Lk@pr|-Rh%io)p`LNhjPtb)E z_f*>&_Gm?{ zGya7-`F^K3&o1Uu1nGg7-Y2oAKjbwupMEm?lsC%TH&_{L+M`tf^sv(UL~(5h1wtE0 z9Ijb5;t%2y!M%fY8a{yr$EVJF-r3u9lsk4N@5Mb@>AQa6dwIfqMT!qwbSmQh^IkpT ziz@}62j1u9w){GqCy3QVDUP>n;7B$Zvw%V(_?SEJ+tS{8*gwM_^g&!t)gH+4uS7ni z+~yHsr5oJ+h-m0Gyv6elsw<&dJQTaWz#%H{QL8RtOzj%t_NJgQ^p<=CGP<6^Iuy3u z%{I;#gw&5}&EIO8GXo0^-cBos*0$wJ%IMDH5m}GccH~OR>=x6rH{%g$h}I6}GDz)a z;1$V_)`oByq;(teilCykH&q-7Nb7eyR!UZ?mMYfYEqbipPprH#-ceX!U6jn@Uye;? z9d)hYFKjcDVH~p-&b+oMOR_gn%vU@KBK1P^*B8`mZie9E}{^=!w&q;LEcV?&Kg=HF$h0k;A1=D#)^ z%eaz+g^k{KgyH#~D!x&4A$9QsNL8h4)$S;p=YxAyHswXKncu$>uU>Kwys@b{>v=RO zXMb5!7o)c|oz)Q3w*7^a->U@oEg?Y_f7JDp|z)m~yn=1LVxj6#}yw zmc6H{>~D`)m4JP0ZlzTxBndsCc#V=p)5LZZ>(o8anY zd&0*uHS6c^lU+l@9hMPlVs9G=IRIESk}4bQx0FuHreHq7)VVmgpa$OpziWGh;bgw+ z);iA=Gu1*|BVdwa5l_QUb(PwQ)E}wz@gECBp%0?RqF_l|6u5zd&ZSJsGMLA68$u}V zy5xPpGNd0M7FO!2yKKI##lUB+!LXOWquGA+082yEF#J6CU?fIYi4`j2RrNjeckqMk zUmeXc_j~>-dG{suO2PYAT30%n(|$Jog^supf7_OqbBlZVmeM-s$TOwV`zx>IMG9=s zsD|OEaY(9EN0JuPoxNcGpiW$2XGTU0`A2aoT&J_yAwHG=os+H{rnjE^VX%UX+Y{sa z9Wz}4o0Uh;($uBvwKzXP?>04kC3@Ge3%C55R&wZ-o$1-|#g z+RN5M4MdX~d`lD(45sEOdK(ML@9i4nIV2_?6 zNVHy`^i!2<>|y^qe9M>OV?5yM{WI#pTaIv3(00Wn;_;Z$VG961?X{FX?(;ch3wu;< zrCQ5Sk#i`A9U8q|nmQPbck}YkX(&vrBY5a8YsTm8wp`|q7z8#Is=o=OB)4 zi@p$>wFa&dwPP{Afo&?BD(|nPFjZlc4ouc0Xbn7VXap+_s*J$mOz0n6*UUQtV|}{0 z#?6zBVy7Gj4^6N6qpcBUY9-d>Vvf1yoh*l8+^VS>T8we8Lt4o0tk4EmwJ0naFBeQk zJnu!4d1A;VygVF+9?8)uTMbt$ZSk{frb7LL?~}Q;ewB%K3OJiLgKxfh#J)Ri#BBfV z>JclS>y{8WP~1fQuHMbmG$2Yz73gSQH_bV!RMhskN_Uv5gV7S~_~sGcJOBN~()O^A z)OqxL6g-WBkT8#l7hb~l7m#GPHE+forgqNii41iR4Iw`h4f9v7$4-T`<>)kL z%0QMEsw4hwfVoOE(SF(+tKV&R4J8>Vq+>%@_!Yy_LH+w`9Y3IRd6UneV}-%lIHs9j z6{P;c^-g(88u!X-pO+Tag1#no4p~ z$b8Woew3SfjyDGy(Uqxy9`Kg@TJQF8sgem=rzC>BcH88itme?E4#$Gvwez~<^TlyR zO4{xu>LybD7G1#eA!XfJ{6WLPmp&%h(P84(X@UpB`!N3Jr$nbI_&A4x%#+Dvgj-0w z2-{q%IoDxkfJ};9z;vO2xEb{uxN$o_w^;jh&g!i^*>+FF8*?1+DgV5G749Xj_W2Gv zf0aIYU|`?)**zJldW({#AhoKQ!;|t4`um|CRa2x9ry9Q%J(@eN2MxU#bTR1gX@^4h z1PvqQiyenHAv`W5)s6!;LBmY>X~&^NQ2r32UP&)~nBAc;G7-f@`P!w>FRlqa{uY4% zw%qBp?LCkFiRSa5ep5M8I0=h(E2YL@7o+jHfFYt6seV|L}rJG+Ey+4%^|v%P^Hgoika zxSmB_f7G1t(TB$w>#e(c^KX^qT+{Supa4#o@E6hr;6>DQMNbLd~QI zHtw1UnWq|dP0kIgf0(U@fHVYMHHS_?7`k1V=76hvLoTK}E|4Phy?!@iN zS<(`Nr24Mxr-y9(kylb@z-2iEGbf70HD zVXbf)_OMvmsI2u#@8Mt4OP`o5cFde{yjV6uo`1 z(Dvsx^4szvGe>q$Jro0@z|tYCk~*1apS{Ck)XT32)6CLJoVM0{ z@fp+SavJ3*D4Y=Y`_4sy1%$NeF0tQG7m-iy_2H>*vMZ-X+gHe@ap*GjKZx!7p)bVE zp=H#VG@5Ad{umN(>1WG_m(>ACJJRDqO*UA1VauN}2Xy>R4rs>B^E_7hL+4=Dp4Q$E zkP|7}vqR5ITwAcyU0dRoYHxyLPO4(-%ZI7{wLoM`O$T7K_=eefhf1;%dphl??Ai`w zu&UlRAw=e0;QgOhqbjYgPpnGiwVtk@UQt!%chMWwWtTDB`^V!h1aGclwX3>p(mr04 zZ5CNk)SBwNzA*9myP>x!qHlpoZ29y_XNig$#9Hl1G{? z72I=vutD$B{jR?J!anUVuaY%b+@<4#>L*Nh_-(w0B=n1WF5*0Hl}JWkPtRjZUryA) zR_M6=;Bo(3awb(Hjc=u=6@2?_?1HC8^EYhhPBkQk?D}pip!;j;k#Oc9KsNv2H=`d9 z>lC@$3ai-^DKnY>i!Y)f$LOJeO2xAWv69*V3|%;5H&b=&*8i3J*6xOG!f-+|jYZ@9 z&C2!ALfJK+ljaxZ0e;x@=e?}#8hrNx-;l!U7ySl(ih{4dIIn$146w+326(csoCr92 zdWcM$s-z`^@R`P_RmKOKFS7nQ969Tdwiw1NUF^GR){yU(B0@D+-3oF=8Tq^>g-QJ@ z)uOGT>E3W6Df0qEE^|Y#uyi=hZrv>(SZUK)jlV*j^jS!~PgkHNzfSUaDlJe1Y(Mke zDWH}=d{*~>hqZt@&lGyEXQRQD42ORzdq>~FqS(fvp6UfEwxQP##RI17#n=Gs?Gszf zgGPq8EnA#50#iJ3uNWx*8Gg2O32J@YtbxddDpqZ&*|9f;b#Bwi`uLaG9bImV|H>=V z1mniLUp71|uH%)Ql;yi@5TeENVl4Y(NCLm1Ms8XK)b0B4a9Rh`sb2%+zGVB5lr}h5 z*kNb3F8|6_U5xGd9uw$D_o)K;pD*{M9v0i3e)6m3;{I)W+vG50i;{~ISSXgu^3b!B z8Jg&?G3oi#b&$W??%WpexgHKb8D6dub9kgvzJQvgUf@`m-$|ioYvl5>0kw8w9sB%2 z>pu;#=Q3(`p|Lg?E?m8F?70`=kaT1!w(>n_(VSoMoqZdaK<6W%cgc{8OcQc9u}$&+ zuyN9N1*5-zmA@MkAQ$+Jg|sG=96GxVz6?zHR{R1p6Td1-Q7UmvJ_43_STx2ejbLJi z+bC!YD}BE)F3dwiBb*aMG*v#LvuIMJ&nyxt-MQCIxUfSk$Ek8lWgBNyVt)1Sm-Mo}t$mxoxn4M2sC4Hs9$8o=2-T241bla436w~4MZ3ydQ%Ek^d$R|N-v;>B-J*1a>ibIW z^$d?LLFbAxAr2(G>F>yYz8mw_S-wX3UE>3Bu4xu2eU`Dwl4T3vmkR#n19>q^B|b(e zn3ws^wP&&g9-;!;F2>IV#Ilh#A zl{&1OnV(!RMtHL&)ngyyooTfjFxUzJc_x$cYnO~eN5@0xdh72bODwBlNW{ns-=UQD z0POi208JzuG1y-AQaB+o+W}Ef7@%Qu6$WR8XI`X32yX01AAFTOEP8%}BkUi^JlU@s zHJ1rOb;4p4Cgc+zftDtNpzYE$xuTp!ztZBRQ?2(fwc3vuz&D;T(^8drCaImF1^rgu zFv(U3YLn{_5V3#_l-!MUaE&A?I>w$t6Z4zdJjiad&+_ZPR}cfbE7+iJ!^<{bnhgGJ z`Y8n`O*~~R6eqo68Z8;LygOW2iK-0eX_~SkfD+Xz9ld4{ zn!XUJyClnkH2rD$$8;HCoqT9=Hn}svxs3(3<^6X|&wz0&34y6^7JkuKEF4LJt&w;DV zXiQovnQO^XJj^nGon*f{zKUV#o)T7-lURNsf@ zQN!8((~bL6g7@zIxj|Ro;t>_kBzFtAvL^I0mL34`rIKqB_Kt0CQq9#@9;LW2@rSqT z=1+S;j)~Se!@V;`z@4Txi7l$`g|Cz7x_thssJMTHJ-tHD=RL%@V3S{!#o!hldiOve z`#4#-tk0Z#6m`5@1+PlU+!FG&M`*D^ZAg!w|7#)!c-Hcj%*jV(ynB+*PdC@Ye6@!I z{c!IlEY~+1mNvb%x8pBB zx0EdF#u;>fv3A`9NgvXH35)OK@Z03{@8-DD_*S768c(@1SSdzT@Le2UJVgW%%=7v z>nvVm^T#DfjTkr)%x3!uzZ++*9-7#)^*rS_b;*w@_pxNE?!ihpa^l@hXB7BbwQg&c z*{Z_C3Vtk^YL)|bSs(8B3v9Fa3B=f7C53>Qa4^5@6KgE*HTv~)>-J>hUOCcX-$d+< z^WZ-KH>e+{q(0^xVb5ZtzY6MKJkf}t|ct*5@_C->vl%9 zF=wym>9dCd{lkH zE=n|Zchxvm%WkQYRV3=hyAVH^WZV`5Y7Jk*F;yvzZU?Vw{k}QYTYIEylB=Zk(Y=rH zdcwg8`A+EM1Yq`?#*J5eqD}tH5=EE4Dw{|ur{}`E_Q%d+NPe~2d8e2~ zFSyr&B=vwZ83)66X|Y>R(`VVPZF=|lzzT|B*f@>tMg9CCX9n-ju&lAyy%G^k=AB zFE?Sc)k1E_9^Obnema_iW{LzH;Wxr=!<}z)Ckb18|Mv_$Ej)jA6rTB_XRmf}YdZC_ zJ0Wf2d;ivS{jfPXti0{msB&+{HPWn(FQ~3GWx9rGDLhkHT<+S)@zwKlft`{8$#TLR z*$81hmt5}>pF9t+s%z4ajDd$UPokN*@K@$PAQh~GSW(m{fBudb=GM|%goO#wLD&fJ zbO@zNT{PX3%7b5Mo{V$bINb?>e5;^QMt>w;QL~h&lV9G&c}2G7IYQgA6Vl#JpNTmdK|JA$oYo}E6vzCB&{^@jUYUd zN#`Qk5b}q3)%t=9sF@Sa6Vj)M01U|ukgTX-TQd=AG=HeiAq7y@roR2B&shat&&IWg zoX|q8I*J`K=P#*yfdj5b8}YBZZ~qk)o~Ryh*i%h+ACK4LmGs~KtPR0^adV*z=w$@I zx;;sa(LD%Ge#Rlw6kS}VV}m1n>7%iJDR6sBW2P8xc-Cued_}%#Jmz-7m7%}a|0M5F zVunt@u&Glp4B#yM4kdU)99QPiSGb?3nr3LjGn?{5Ow%nlB6-{%%$P<6X-p)6h&{== zn&7592B#9$PexhJCOybZO=)RhxO=8JsnI-x1@Glkx!cFDp2pf>kNV}%RG8lgDO({- z{XCL8i*&N24r!0Pnn*f!PI(O7-c%<$HT)XhvBhEn4%YWBU6^~1D^<(2VKzbxPH+$Q ze<6CQUSx;fXuWDty=}*$jHz19+@HOU)bSIzNKe+@Lj93bvE8(+K7C*~xy8C(6|*hs zd%bj`v1zC`ia6^63dva7aBb~DpvSpK6atGUD5|wJNTOy#<>@pwIzjja2GOL_bKc&$ z8-MvT`RhCMX;O35b&Fdn0)L6SBJT6pnt}pH5`^yK9)z0xao%Q}?Qmi#ht8Z(GyO&H=$Vq0+Wn2{ z$?$3l>28?<#@fvE)bVVYe^J-YVVpYd8eJuC{85(9S{J2I2~(IQ`4;TEmnk63Y&^=n zOs7uzES7d0LkY^3kD#8h_0_|C2U*hA6@e7}{CZI}!Se1w8LTA6ApT#eds0VjjJ*1D zi}%wr45O0zy&w0{)%HXuZ~K97)17@#mfE~wes72_D!8qf;8Sv0S8&{+{^UV{KJg4o ze>UH|Q>w(fszuhjX8jy!hq3LVMIMrrSGSQOqU#(m(#pOduW z)Eio$0MCg-)gxU{kwKL`Niu|7qh?2G9UIUD0(;nX1mnv zm>^zP$mkxsfzB3v^ibRO$0aAU&P$2%Li&~fHqk0$d;fV{TGafcoAhwaC)U&1@nh0_ zV?>sd(5;?W2@PIra^uXPSBie%8W)N*7F)A$@`L#|3zhv_4?Lb?StXKaYM)l87faNXTPBD>MlJy*BYQ_x7Sz{GN zgP%6+ZiohTMO!!@Wjw{Mv-=20q7@sgv-ofGL&@BERA_7QEm#cii=OSqU!-V?Dd*BC z@06#2NQx9PrkGu@Nmk9A+=M{olsSot$_9XWv;W51aw=mX|ukNy) z|5>@Zc6oV%b=3gnUskYb-SBb!^v%Pjy?(>J%IPXWNS}bQ!rC%@hz(<)wQqE1XNef~ z*b#zhS>=~yKP>WH@+imKIQ@=WGx0H#)s}*1?Vh?m^7GG=i9ZXV7tL9Mo{}r#X1-q! zo_w~j^QRZ?&*FgGnOdvj$)Xj{k^Z}_eYoUZ8%c6)i&B-F= z@{rDKwzaJ0!{~p1H{)GDBQAF%Yb6J;xm zt6Hyk&%5?@+QOzx)hWOECMp>74_uqAWFuYYh<_Xv=*6W`7m~ z*^Dk?NV}T?q}yS=VYC<3N?d9RlDK-H6Ix02H`O0qlaM~&>C~L$PajQ`m2hpswEUiL z$rbZt%Q)V4elaGTd~HFS#|NixuCsKk9mGsFmP7|zd`A@n#pszikDvB9^(^{``CG06mxQUuAEUe z0btN0B%1jcfFc(95I13`@#-Tf#W+`ZzO74^$%!!1Q1R49VEo=wYuzAQnyO|AV04i| zg6Lwe*Hb0ZF~q^#?-M9J_;3YfD-=$orL-p&$u5HXz4Sfg_2&S#ZZziXF_}Si+Wk}U zegmqbk}{S^GheCCC|sV$3LY1=gz7rR&gJ&vU#7tZj~;pG!Fv(S!pc(Q0hisMuRf0$ zWIP-vVhvsViZ-&oi<>RkB_Tb!{1l$)LqK6wRXV-|@ ze=2_B6tjO%(=kz+uBDQ%Zg>=9mh!qDqr@@>9({~AhY8l(C9dd_f3aLMZLF zT=4^XW_3Jd?)9b9zz*i?Q8PAbK8FSjjB7ovJo+*7t=?6_o>C2ChBLmLgC=VaY1m26 zmm0Wc4Q)nGfF z~ld_N5r;l|U%d?f9i(W0nc z&}NwRnfX3G-1n-@*!lZpj(g9nvq)pWt0C#;xM#Jall7?~({UC*Jfcolz|>4Cb`81E9*$CY=$c8_sd6)I{7)v;pySn zOGi5gS1T7WLMbMY9@Wb6kgQZ-zEy$W%P?HH7TG>nBv?X|`$AEl7}qj4h97F-R2%T1 zZR9@OA;jnZfoeXBAE_LtyJY|nhto)irijV7N$dWd7-`WXkfj}H{Jw7WY4!hRwhvJ^E5ZBd4H~u?&>P-dAwQ|p- zs{4;PzW$u)agnC#3ARc~4~gZ*~&ZHmB(y=&o6T-Ki z)yijq#PPCtQF=&n$dOYijYr=YI7*`9cZI7zpQQjEGyaKpTtEM={Fl@DMWRAUJZX-( zek!CesUhiyeYGgdI~x(;!)wzP3cb>i=MtA?%B!4h-I#oSYF*hAFZO}w?X6A9$ zOyp8kiFsfPSQ034aYZvE7F+_^b|gQ4`(FKts;0F1>%8~7p}bLR-~zjl(y=>sjylb7FZGCQd8)4Jk9KDn7@f)x8=x8AJr z@Kblx09?^rbh{I1`bHI znvOi!7q`+T*nt8>UxTC>Ij6Fq@7+!be{4K2*7ZL+6CFO!=%}TusIxlWahyKJjd{1T z(UY9?jw888{bLSjGKQ9EtrH7r&Z5B0M|1~DlWbG=mW%vX<^ET#`PF6C|JKxq;0?;_THU1p%ify#u@wx7A4uocT~;rj1QYwPCh=2f%W3yB)tOao z`qQe%EOu zT0=Tdp1qVAZq=zcUed+8CFrrq{2VS(P$$@Z-}_(PHlwe9{MXi;Up=@ru`(gwlDA2; zcvhyx*eS5?D;sbWByA#?XTC*M>Jx~4B>zwltW%&5FZOeAX-_Kv)qN%ccs~~r7!tT5 zpq6}iWLj3aL+HwuAnh~6qH>o46Xp7a#HlgSae#)dW_{*$kF94r=%wveL(_{ML@RVI zwtW8#yk|zdq7q_-5Us#u08W@s#Q49PJT9Pcz2FcbUZbh-(p{8tfs98kH}-o+n^}cO zz4`&=S4oCauUApSkp`lXX+HC@^ddm;oJ`{#H%~9Eo4$)!sV_eC++kBy$S&}Lo1Az@ zjSTzMWk?1ihsYg5zH2jEW(IbEA7|G8+9iB_d;PzKXxdYzzr@K$mrL8@{Yu25g!*m% zteRBzUaic)F346#E=k*J#g8Nkj^iJpCO7ik1aZMo@>W66tv^VlJsWrQS2>L@M4|00L}cFh*!xz z?ea)vrs15TeIp^wJT=;Xyt0YI(zTCu-eof|4S!X3HW;6q9uXoO)R84$B6e*0+#_=) zyQVK-RBJm3l?EEO!0rbBU+u@YjBfv}>cm#@K_Sj8lrPB*mv>iWeMY;0Gf7a?1)|0y ziHcM#rJYT)J!e|CFOWl<7XL7$seNsbR^W>09ZiOw-33|OeI{AY)f3V>&RYzz+pV8T zd9HbYrgt(FE4XG7AA2W+&h(NJ)zdpf`t&eadm?ls9!-s+dENtc$}rqz_5F24NA->|}0Vx=WDFpTqN^n*A zz9Uri1}9Xq21@Y|YDoee2gpy8(4$ia(LH1Pc`337UjJ+>!a*Zvf{(I79xA*1nCCQZ z-c!wA;g}wo%W|J`^Ic>O{Z05de^Yz6F2XS}>Ka*pKn>8CXwFqUhCft=a*vjL4z?0|>XqYZ_yt z2YLVX%^XozC8-TvYu@*xnv-=HJqsz`sgrJg4=}01^Zjx5dk|?Q?|73eZbmn_9{$m( zw2NoF-Gap#vg4pK% z?yjqc!SCZklxNWL$cHOEQEHYj7}4{F`*r`Ma$aJNS-kzgtn$(0>dP0*2VPf?S?*7g z)IQZKLnQa?Lg=#3D%or|De3vt>bjmNzSg{Dbdz#<(R4N*{WU}g|L-Zci(4y>36I{oPfTytY&Fh2d>zR?oLrciRi2-YfHd`* zoqfsdv*yE}`>wbT`5xgt1tu_DRKIH|syXwM6YU^RA^dqEJBoUQ%ZAN|0`BB}XV*aV zauPto4P4#2CAk3D>OB8eg!f9YKI)j&m0ymJ^iANs;~!ud7I42gfTO2-w_OsCZVFrz zZq@CDGfR5ZTnlZCD+*MI6$tX@PdUG($1Jz-N^{=4y&wIzIVMZ58YTbC+o4rw6-PC_ zfa=wXSjvPvG&JTl{}jWjouBsH=A;7O=~CyVs!=+7fPG zTqFiiA_kRVY}2m@!t>wQlF3grq|i@$r|+}PzY1bm%c{5$PUc2B8-D2uLVGhf;l4o}#;{DXH(2Bb~^NLGVV@cC8< zq{n9VQ_uqY;)r=&qSIVMiVNZSTE(8V{oV7f%7+}~D5G8(w;+Uh52O6*6072bfEfI^ zb0*Bkc{%Q0gFk@@zr6m>lqt)=RR04HT-$HbuE0OLjGbR~{XFwed>bFcrj1Qb0s1-= zBae9_v{&gHkU?+XCmtWjkx{;oR{31X%gq8x4>VXD-zKh46xAVf_sC6>tz;>=`TFQ~ zpSm$VppXRvnnWkUMR>wdBI$EPkv!q4;r&%yGK zn)w&At4BF+DH$*1@BJm~W2eE88OyuoFF-VL8LYe~(_@_l(%i?N3R47BTOp}ZqFx-* za-qVbr&!0vrF=?SxLzz_4kPW_+38-}jP;%Qv1m>X5An!8A8@yw!_{g&^bFQe2q5um zKE=Dc;Fim$Mz(KROD_Gt?g_*5rx#@{Omz=fT@eaZ9eM|s)+7{%mz;R1>g0$7*Q+~r z3fYswE0lcw)|o|PA@b<#ALX5%WXKrx1!v0flL9zqG4vkw=C9i$xw>co|Rj^8e#<{x9BGu{vvtzi|pOe!ED)s zR}Yql*g}EK`yRs;y1hsy(E=31TiN8u1r;pq(IvsH)wYff!o}r%B2AIjY?=?QiT;V8 zXtA5aUZCsneBJaEowo&M;9~IU2jlvIfT)SF&5wt^Gu#RBbA`$M1bta^e`h?e2SV%L ztB~;B?sx9MA}O78qRs(+APSsXJ?U2cPA+nE+7bI3)0}(CA_WYK_#%nMskF|7Jg>JF@HP}bHG*rOe=&j!U-09hGxlQ+o#%+lkT&ws zJX9R-3iL5L1t6S>qad!fx=skSceqswDy&E((Ev)!b8pGuGtniqUXIn{(7sX97cVsn z?>eOk1)Ri!y+P;wbp!C#vpXQY ztGU8@K^H;iN6245fNsNZaO6CTH@RIm%9#F{yj9*zRQ$tbpUq#~WB-U;I;g!DM#LcK zxZr!cYbAb$U<<=Z2BQ5--4OsfUi)k7&%!aKi=Jo1Acu{ z&kSaK$*8$LzLG4vdvn=?~<^5J|a6~p= z)CAN%HyOU2)PwcKk5mi!?jUuy1`4B-e-l*6$s_T}b$~94Ux{$ILh^URiOD6uJgWMi zF^)R6+jkzEtipKK_Jx{s?jjgp|d`LrH*kDUGf(EZTbZ*|ipy`+GrDLnoyA zcq_X->CbXEReC!?fjRs1b({Jh{o3dTkj|sB!On_;>qEPk8L(ZZUfNj*%7 zDC}_lrf{FW7e`T;ep&S(^Rs55)^Hj)&Rz5nzUnfgE~A(sWTD@A<>iJBd~+zJ+2`PG z31eY;0rlB%5+7jXrDwGQ0A$mXl-4GKSr!;DL8Ud0)c5mY9jR_q6Wc7*>9gV4VO7_fUIu@8Df# zEKb<-sdeZf=%me4k5{*3;wZ*m`en||gYyX$t5C@8lprs*+2NM7;u*|W0Fm-tGnh1r z5Ncfh_AInkTy;XS`GuP@tp#=U8U)yC)|}ofnL4d1>C=9oTk`E+V@ExBy#c`C)gKB+ zk33Awx{=17D5#@~rPXiPHAFvpDJ2wB4I29Lh~s7o-wQ#_(a$Qwdp!y1#4!?ZCVv&Qyq@Mc!3Y)({2#aCc(xESjHjX6P@33(NDPceAd9wh&Qic!Jt>1o1Z za;X17?XBC#?YI<(6xZ zo!&nkHUwX?6&s+cVt@NOq?3g%gE%xeSq+L`Br0%=7=Q@sF}vj-yYU62;he`ATtue) zbq0u(YMT0sLj9U-24ESWzZAp_WEO$pP=Il=29xH~!T-Z)o(- z#*tUMkU$mYT?e$ca{t6Toa-)8N$1gnP8z4c6In207vl(dHjt-cd-jzG?gA88shsj| z8ROfr^=ww~X|`Q8=5Z1@ulc9mi2e4cG;IAg2v)Vk8r`f|ysVv~iIRLUc#0WH8*CRd zC$I2rrsd|i@29<-QPJRoZrl+zL<8ahwG~NI^aVdUR}GS>MyBqve6H22x4nM@7j|(A z@2NwaL?CH}W{}25Y0lb?=5f-n7<-S5)zYNMMf*wxQx{!W)2Ea{dvQh6^c5Y0{`9P* z*P$B&O+6WQkZ=7JR%iXw>rKd``1&BGxml{`s5)d@>%w=>P$!Q_Xw4PW%*(g&_+2N% zx5ei-Pe{gD8ArDK8DKbBl1k?x-N(Wm1Lci?A9mGd0HV8V;j_GuPMlZUU|{s9rR+)UWnbR%?XB z23(C5$%-1GRJ)t;cMAcB%Ut2}djOUk6zK4lB7?k9wsa2gMCm#4=AHlPY^@im;8x+E zS0WalX?a*gtz8S+|N9%uaPEtQah3xWxrL9eFR+D)B%}EzBVGF zDDo8vC8ZSvq!k3Dr5mKBrE6i4O2_C)H%N|=D!l;%iBSUqM~(p_#u#yKfBz@v^*Tq- zdAdCSKHT@`x~}*24rF9Z!LfamL%s*+L($R%KXHEX9d!mdT->1V=M?(SDLO1S-Osk0 zc4dM1lQAO8q2DSEyta0woj>2xVgvCimjqfGeROrG2olsq{}Bpmr7E}v)#{?n1HIFW zR1#M?W%U#Kjc(VsyjeaBYJ1P*o!F&rCH)nx-=f!FCe$)+XN7KekWLI7eS}|}snPR5 z-op$I3N!A=6>1#KNk&UGZ%yQ6w7pH;+|7B|UhrCYNoTD0!(KRC>EY>j$fKpE>b-qc zhu{4IUn2 z{pj9qvC<&g8bCG94b*a<5e_ix^JG&Wt|B>TmBFBuu9ojAz5X|-w2EC zSq3kI=S-XG$R~xo!E0;iO@+mYAI({-&dvdIHyQ;FJ~k8EROwY9%!l7}&IBB{C^!|D zbtfe!`w{ile<)=|7B=Z7uN+LMH^{pbcxPLw8OzqjNR2gp5`t!kVn3;;EMoL#+RmgY z%g*lfT3)wL`?%RPN&w;?}ioVSekG_IVHp3C&(pvm8J3w)l9Fy!*S?brUS%`*h1 z>90QLAN$J6b~65JH1AdgjUxQ+KKJv*cQeVKKV5dQdgm|tt;xRc$Hb;1bZ3XV=+wa$ zbAYASxa3di)cG}9rK_(nOgO%w@KC?KTl?7FQMGNS3NC0*z%~|W9297LDA4FExaz#h z5VXqRu*%@K%HY20U}SW(oen*&uLh7da^La_c=XXWi}-hq8<==s!-Y2=`9a@}-{a_Yre(gEyQqSr{S?*pojN)|Wra-09O2t*+@Pz2^4I zFZm@uBb5T|Y@wMFDL5uwWpc^x$`E(|R#DkR`qkQ?#%Y_r zDUYR68SqiI65sc?x7sHxYmAtsQ~20Rr*bo;GbFK~QBy(%O!c}u z8vtzb6cj<*4lx}^QoMsr??x0|W_7l$)MD03jxFTha9*o^RG|RyKaLDgm`qN*L`Tvo zsT|->F$hpcW0y23ssJ4XFtZW>*4%PoeC>hwPf2iVAg$)a;O++@KpcTbLnZ`nqy#`K zuL(3&y`b5t=mD%i4vwAa0r!T0>Emub#H1_)jw!k*Yh_+uqv{L2`yuJ#@$6>-mi?*Y zY*Ri@Wg(H<`Q#QKx0)15RyEN*FoF9jV^JLEru*ma?#E5Xa6V0A_KuWxNDOi*axA5r zikS~?;NCHi#(y)(y z#;D=@ltZ7VTA4u)@;Dxyd@%kj=LLf*LqX-UATHh8ItKJSE#QUjrN!Whz!|1FH6tp3T%*m_RbW%x=TQ5xMWkj(*PY z*b?pVXaKxoJ$GIgShfA&yb{t0W-FQF+}ypdid%!k8ZAj^BBhf_ctEvy1vr}Ph%ZTi zh2ZI;>P|k$zBFY7_`Lm!zE!=83JSRsXv@dW5OFR$^EuKXR`YdpTi!J<%v^L5yelt7 z)hc`c<;M)~BOnUosX5mxpATo7aE!0lghPhPE@u4`%jWg0WBB=FM}Hwt2xRj|c>rb= z(%@Z_vMdjKRSMSScB!ebUxZ`zs5M4EMocYAry7bw5TI9H+kKvT-A+a86<=ESD~rd3OyfVYWXU|P}|b#Wou zh2jZNLuAQN{p_vriefyc*O~Z(H65Nq(gMcb*6=`i7Y|?J#4qDNi&T03K9WptI6%I! zf}!1a4v&MmSk_I>48CFeG$7AW5;(CF*VlWE{YHGh$B*&lNnds))cgEdZYTk_5CTz_N3vC7=-|liWLgf;u{5-a9OO^nfhd$VNrT8gkx6 zDhtT4aPbFHKW5*WoebT%{Ot-P1 zG{)UQg{TUBXfI(rvs|}Jc;sW*d<1d|iF%QmAso{ZI=QTrfzPQHLggGquT|!)nyqIf z*6RFCgC;GbXJyOl<7Uv7#n$kU&fk9`CMkBJ(ObsaiAlxHwq1+A22CeXFj2`??;Pz9 zIKda`Wdm+_iC6Tsg)B`_BNc=jI|K4M+GF0U<`{X?kxypX2H{E8=HX_L@r+Y-DORu= z&q1kDG2VK-q%&%{>Y24X;CJ#K@WMV8UYvCLI!~Um6vo-n_c+3zHnY7x;0S(9xHQ3W zFC4v|kwvWPW2dVt-X z7JQGX`q?zQwhOF8;4C&fx?zS}n_M zxI{Z^P{RT6gTYJdK^}#!4^yNqi^sYu4NC4CdW6mSx% z1z`WoFFyhG8MYcEl}t(oE|WPRGluOHsy_%X%C(~quxGAswg=>jKtXpL;dew{MBt&z zh8)BdVDW*5JlOd5Kg2{;j*|#6mgu#Z;(?szck;1pyDGyus<_HH4BI}E1@OniJr5ex zw>TYQ=W&8>z$dkodOYs=i(6_t?g3s%SZCjjK7rJ*?m!@3>9G%m1_1z6UNggkS|DSs z^cQ3C_r|YOCGyY7SbGF{mi-`u3e8TR6pl-^d3o)puvjxGd=T2t$AqS8ogA(_3)W>gLGDx0{uIwWhro}WN!z4e4&&cq!2h7RY5^K zlgZ=^QXc~3f3)BFHCVw zVS1t51V_ema}-+4r(b*=NSv?TjvI>ELW}Rg@{vwRG0~Y_muOLc6lnr6g5R z3Vq`0$nfl881%@GP%)$o#E{Fl=BAwHn(-_MyF3*-eRcbg1mDk1QP`RPpQ(tbVDRRANpk1Yj1n;5AgzWi z_%*R&Xz7YpHQqt z?k+APhX{2|G}4F3|E(ey>3b-l0QQkgG1xyC5iRxWC9uV%*vLqFEMMIC9%m`*l6ZmD zJI~U&u8eZE0@ElW34|`(xuTz4`?U>JmW&e*5ITL$kMAkeV*|V;J;lrMoMN-!N>uah z`pkppcMptJdG$^Jj-8i-HU9@n6R+WS&~p8A%4r!KxNKXxZFiUpW!K2zvelDC(|B@@ zj#t>;%ZUvv=TkVAHEK#$IN+5LDmkV*=0D(C99P7F=r zWmOi6rP}7&MXo2)R9e1>Xso{?ow6Qt7k|=pBZE@+@%a+H> zLYm9GsbEOM_nG6cjvofdcV&a*EgaNZWOr_vu}o(Cd54ZiLacvw)u;kqKLCs$i;>VC zl>b`~7jDZ}rN-T(SNvYuvO-K*diq8Nn5c=r?0fP?_}A(}VAP{xv_sR6&t;~3W^VzD zT0?!l*bGkmB+#w|yk-5^fieOzPILg*1;FdKJ$OkzrO_ZCg!9Nhs-gqRkk68xs7=sK zaL|lSRX1TrQilT{Ll0akwq*QHdG&{xwl@Q;V{$n!p9M?~WnTQA9Y?H1#`|XgiLXt7 zazc7P>~zn^g(w6kplR8(9wp0uW=P(w;#T#qDmav{A4-PSiK@a)yBiF2mp16sQer?+ z>tLzKIbDuR?|BnD7s-_gVeMv|aOvz~1G@Vz9oLobV6q8+ZD~K+f+=5~G|(C(K7li& zy3-JdCf3;^uLo#A;de*5BkF1N{NBmls;ax}qWi9)r%?n7T(j%T&yhecy5V{&g=B4F z3JJCz1R8x<(YVRr;rOOJT>YS(k0}+B4^oCb;hH(wyiwn>dQko#~G%JwIq<-g+4JLGztg4LMXdpLw{l264p6hjQ@-W!uW? zv0oL}RTfJ=KX9rblN=YnT#rM2zS6jK_VY{-S?WpJpGn}UK)79y_Z^+R?cKYWPgST)F(Aky}W`VY%_&wGd zzRg9y&aDV-kE)@P@oYn1V;z1m17xPq;UUY;+Y4Pknm1W0cchXrGTHYZfeNAqn$J0( zhOgt52BH%7#?8b7=FBRbxq>+U8cfEjR=wvPNzlwyPh9!N_>*^F+dv)%+X$*?|_2bHn|Md+!&9dj)N&HpKxYJ z@G@OAvUa$7Msm1%NC%Lq9ZjBwO)l3!^&^u#VH|kBO|6hGUD;sfO*LQSkxeX?eVo&3 zm#;E{mXAznkuJ>1`GQ@#^h~Zcz%}}_h(f0O;NbBq@BPUk#Gvm=1v)Y%T&(Zi)Rj^~ zJ(o1KZ$Pu-I<5Ih$ato8f=kJYKw%0TpglXA%E2xI2`yCO_>r# z&E%`KcK>oFBUF>|d-!Q=BBxQQAFE|+c8OUJn@M<5fC)0s2*8FPe6eCN;WaEzbS+0U z?72)!Z=?c8QCN~>zVuuQh&v>nAC$K=+Q$Q?6Lw(&k@tW{iB*9UUvxni$mU6W(n)V2 zA5U^}NHeJ~(6~2oI%q`WvsBAmO-R6xu(?$mGel!eWWT+bSd5tR3mKiqT57}3wrX^S z2Ng>$(1&h?eNvP@=UqA0It6Og_HxSDc0L zv4TtDaqqEzDds8r>jzm^8zdVNCSESD5kLsb2~Vbg=>nVP8GB=X7MCJRS4k_iD>?u*!gSy4T!yNRY{B^GJ& zcg18J8o0-+Z=x+e_IoCVLQDmd3gk`YQ}TCtOQO}LbO8%&g$L#Md>a&Oh*krMLA3#M zGL>!)Y~`6sf(@qYqKz$L%^>yyE|h?DUVqnCh)21I@es`VUYSbQB0+SfVzrn_X2qfU={12jn-;a6l9hBmD{T5sKBxoyY5a*?R_k!A)**BhA?ZA1h$7<;yPE z?Pl#NSZpPXghh9)C!_XUD8UsWt@C#vlUo%pT5M44R!u+mNCiGcNAH2?sJs7ewBP`R zV_RAXLx}kcw8)d5gssHW^kTeISDu||$n$%GX+WFbzWs32dkGC33?LY{%dSH2?%JWkdRBN5P}Sn#n9k#sFg`m{@y^Y}KeGEeKdcDFi^D+_#<1e8O;dYRZ#q z-MZ;5f1vkST!x>R{y1+deK7B1AXfu`J;jJmu2suqeRyv zoQ|h%^97O7tGgBtC`R{1X8&YEF%c(z0t|>MHJn-eIz{G^q64dLgDUwT1iWl($8uHi ztu=w9fnW9ZYSK%nyj9?8x*Apw+A%;)O6dvTfiv3P0&x(-cQyby{p(iQSF+ShFAnX` zrEw&m*{3K^c^?iul6)60j^XPhyV$m%X=S76@KJmc%z=D_N~p+4`*k&OKCvJ`K}ES3#TzZ>r0F1<#cHi@q6!VuKw4 zuib`Y*Hm2cBlFqdhM;nlI>K}S0IL{tSdaJ8PS0uG)M=~M9)I@%%g%MtN^RTHiEKo|Vt3SetJuEJ zaczgyAH6F{^6|-+1yWyPg{)0bP3?6~Rhev(ok*#Fe(v_?+UoXsg&G|(Z zbMnz;pW!@zbQ`B2c5Y*V_G;w~Gjp76P%Qu0l6z7(n~ct6^15M9OkiCh3-;MxGEFk& zQ*!{#oqliPonp7FN^P49`V*x$^IIN;PUq~5H4U{qEq@-b?uWdq+^4Qf&VQxM7sjX1 zvqjeqlpy>s0IRJ|uE+)=8HT=yv;!TT7TVEQ@RTBZNb;=FMZ+?Im&aHBx9~eOVj3!Z zNXmuZ!>`Z%9)4zEA)lP!!m-mfw91N2J@{Rl{oA{1C!a^k!Y#C&?h@*!&*V%}O{Run z*zxU)+VT%3s|}U{HBBjV+@(HMya7ii(xrjR!NFgaFE|Ef^zMUNf5>jBH*s%PCtE`c`LOLzM(9+$6ktCASN1 zE3T_lLlS)h6D_LMoY61VMeCZP!}xgVfY!pZv@!TC)nvwKf1fgLk_}|cK2}9Rf!sc{ zV>ztq(3Bvb5-4@%M3}$gZ-35G^*k{+qbly>Y@3nPN!P9Vt=?M|#Gj#m>)A4A zAH}gXo1=9a4og_?tTy--%4D%d`w6CVr`jr_6%c#6dt%5F&(3*{6>qar4HJG}GPAGkmE5O%`u z^~+baX>$IHrTg8)%G_EMzXfF8>?3oZ;^wb*j+@vn%X7YsAo@;p6ytSIEj&Z_@8PvD zmmP{u z%ZSC3<~4~gFIcDWU2%w@-2SoOZ_<;HmBZFMgpvTjcQ1bY5epyU4jE4i>S!NB%{FaO zO+I*n!=U3xojo33LF<_}G5Egt$Ru+p*Nqu{hg9%vaD{rR%qc5h`%c2|_r+C^Z4qbo z?aBRXbRn!wXm)cuuW%6H#<;(Jz#S{J_Vg+JMaP1cbzu67_18ld6&f*+M|o0RP8!o6 zZ!skcLR1P$*A`aBQR15Z+ygPK;07eI{u#P2susDV3Jk?6Rx9pmH#4qQCN&uHppS7h z>KK6>(n`_bJ~+jM|%wm?x2@vy;`zw`JoiulnY@a#DFc zOTYfnQUIvF*JV1O{LzA%c}*iL?h=Ll-NcW$#EBG?Vg9zEv!Yh|L>5nUeqWHYdY;xm zwX5pL6@50qBKinHL|-gEUv$(5sKmz&P%un9i5(D+q*J9|^lI29#UoBA?N!y+cr6K7JE|bt^d+*kxwsU_<%hiTQ z$T0DF4siL1l~ijsaV7T2!7650%J3P;ngP%^M;o$ZHJ5&=tTA!2yw1}|12dK`MyH?c z&}h{*13VRiTZ7XA<%Hh?`aS~`1=uXxbtGjt=F5I#oDOn5R=>+&MGfY_FM0b48fU#v zVn2vc@3d?-i%tl`8B12(w7tPPLiJWCNj%Wrcc>L4=i>Y{S>&PTF!p z3S}G({q?)byDwu4DQ`LyU0C!&Swq!51{!t^zD5W0Jh4BYSl0&$`uiiS&3lE zmQt?s(a6}d@nzTEAIGl6ZQGBfPwArjC~q8H+hnI_po(=@+Mp6x@|#Xc1vZgSEy9tl z^gSLry7+lX<$|5qvN6kovihuzM5oX{8>|;$O>8B5SwHcvFP78<23yOD-NM9l9+Kt* zg*ASn7eku@AIey*eS`&c3TVAEW2)La^0bC*vhcM-B8RmYy~2R}QY5facXogyeSj=? z$z*SAX8pD<|N0>q>qmbUG|vj7QzqgE*I`h`~OmwEK{Mw`@rm zA81hTyjQTrQ#jkD32-s~YB-zk;rECpk$+W=HYBhF{hQ(;WMR3z71CLxDo?m)I^ zYbRbrjq`UT^R0dL)UTTtsrXyz$%J%NbraPzc~lchSotk;MWgwr(?2C4N@s@c+a8*( zXx`dMEXcM;J}~7o9GkVTc#@Tu0(IiY!vH7X!wSq+yRzT`-Dx~h|K!8~)k8P8bq7ub z1uQYsS~8f6I6a`w;2r@(9}M+u<{NvS9dcj6*7Lv)FmxFFy;h&8ejH7%Zfpg-e;0h# z8=n7%k1WM0-E^~x7@^|!?F(j=jC7p43p0;QGVU^z+uQv`s9mM!D=gd>h|rf?PK@qY z*2RY#k8mF|Jy44EaqDVhT<73kfL+1{~=#&B=Yyz7N3&Wv>|*l=4cTde~)jR>5D( zQclM5{BCIz?fzmy2;8^RJwI|1-%I-$6#4i%W(>x34{jk=EfNRWv7s?Ht94=0dUzwt zK5fH*H8cIvdHIxc9g;@^KqhScSIMJQqm-64b9g7iFMRot(~5@}VBtIa${z#g%xv5lPC zMLnz$x#Do}vd|%^J5h1^<)FQ_)n0H@iM`W@SI0;G-lsAM-2&_N-M}$&YPwA%Yse=_ z$p*KEUHRH50nmYeTjK!lrxs{QD%m{TxA>SDxt^1eL~5&W(cUxK_&ML#_P5U7*QdMP z5$=#?V9A4vZIex`Ku6&2BGRVY(%2`sd>x{u#R~M(NY$DSPK8rVrt4EJUp;mE{hES~ zPeu4Jbv4U_SQu0;**gdb<90~(Cihd(tZL7ZpF8)th-uF$P@$*RR`rVa`c+SvWt4|Q z&9Zkgfc|8{GxU>d(8T6$;L%eA(f<%=(TdmPjVH>e9mJQti#wFu!AYkBfp}8eS;bZG zX(~wI(Fs%g>Zo-`XSS**r-rvjK@{4zX|Z=h(=@qrX}3xg(GGXM-EY=q*b*XO-8L=o zNw(dPMYvu=idvac*6i`Z-`6VFsQq=M5oPL^`Mp2lYu{JJQzp!FGOyIQi5a$}3HRC` zicXLw2s=ZLm9AoQ+4n}>Zs|CxH;dwzG#zC1vEC_Y@kKtebS9Twlyp&oESewwclbNIC-Oxkzhazgvp%{aEpTWGPyzi?}M?)fe6L;cQi)r9<-ysJ{1jpijSB3c`V z_YGwzMRYg*!bSPM+SO;D-vXiw(kt*bkcQHYw1l~vzE6Uc&-0IOH9yJ;jBW%+`@W2} zK&sp}8fy`sB8me$9h={9z~Kq$9XG8;&{x#{oRm(_57&dRIUu&_qGKf&s;FW_1D%H91jm0{p_pua0^LpZ>SrG%rgjU-wlxGZ?2I5ICcY!+)CCeR)blBkux;x9ySe^crX@n zTcYeIi_chMO~IyqlnlgH+?x93{*|`e2%P7Ro>Qx#yaB&J7q{zZb@=!zhk-I)y+5=5 z9=CQgP=m+S;srJHU2d$(ap>QzVT^npRNp(sMQY7`kbU2XOTFh=ka_M8!BW=T1|a}Z zQ%F}wN~oeRKyM#;K0Bg$3`K1B$=1IK%~GdS3$$+xPA7{|Nhu+=h+Wlq?-GDtVC-}k zA6fF8a$BK;;IpgnfGX?S_xn&{x*Xw~IH%slO9xx;(Rs~BkZ>aHW7(zwXZnlZ{f&Ex z>8(2r4i8o0I0-81yF;M@fL;&JG)MwFfxaT{Z9DsqJL0>8%F$z_^;d7YR0 zL1$_)H&VgbUkN+s{(ry9B%))x4@se}Uv@IPj=BFWrGy)9PeC zbW!f9@#=a8qzfcOHs0!&H=eas6*`?a8YP9e{$6ubRZlDjb+BJPpQqcdK_5ma#@ht^ zXF&aKvCJ{CSdN)`sW@bRa2oL0(DJN&MP2)8-}6JtO&ouOw_SKL&EpN-R9Tz29$BdX zKtfCA6XiN78S8U%FZ(d2gL|=s+wWst6`f#Og5Z$)QSv+0O0b4;lMKsR-<(Vx5jMg@ z=4wNJ|1)|$T~VFJB(ob|GHO!io~TWTBh|$Mhhkeab5QZ(5p&ET>--m@Z)iN}b58{|)1#S4ZX(3I z>SUs!EH_Tx=EDTrbTt%N0}t!Ekv?`)gl7uj&LO~t0}K=H4pWLG9#-%!Oz3Y4OTwAW zJ^O^L1{ueSw(~4qKOFVAFZ>iMD_C%6lcL&Kt_FY4kzlWNMJ|>kIvrS>*!+Ri^nJBz zsvDrdahHwjwYsjdx~8G(-LwSpc}A75g%s==W6Zkd>-FZ$kRcU691P3GUHrNOTvzq^ z$UxR|0nqEp_qE9pfdITEED^%aOI(A;%Ek6fWv*E+b)p8l*H<bXM(F=i)0XAVhU=K$``NAI*RnZYma8M*bT2q~O+K9>r> z{l0R{h~l$73-r@0HpZi6%^Xj|i6mtu0Alf7`SzLf-`CT5`*FaAediT^wqW|=!o^x? zz+9{ypb130>TFbxzavA)y%RJ3}Ad+zM&T#2_k(l6F=+DLuRgWcs)0OM# z^cI2*N8>!n0akp*U`Uqt*2%{)oDh?9D62Wo-!9{0%N!F_fE(A2Xz}}ul^L!6!7B~- z_KjX)c`EO}SNNh^2+$<~4&eLqL^W9qYHB$| zV7G8y=okhq8yTSSDk#SP5Q+0%|N0C?HS`s1Zv{+L9-DKgw6k^*M#LTiOAhKx(ROgq zqgOk~EPpuA*eR}<5F~I+b>V)ok_`1h;K3)pF`JiJpP)1@8#A7+;`)5xj8L!iC^OG{ zE(*<2EUc{wYkdtr>@e0HSL!Yi4&u5jux+}hKs@iohzi2F-TPl-%neAeoM2e=}@8n>)SuWi>HN;53(!;VJz*v;hz zUI0^mSZhsJI8aEyv{+xkUkzC8?OW!ce&cWgk-|WF!F!1Ds6)W2b_TEiono0>s@!ZG zv3fewzp7)N=A0uL4`vL3N&FfPhr4E2sIfqeM908CIieIyRbvRIs$<}$1U8VLT z1EKFa3izYBG;D5kxueYooUq)NDm*K1QQ3YKEHu=6W6RXk(E_Dr$91K2ko!92e|vYJ zoBE$BU>|>NpDPuA%XTfT?VP{tJ^y9*GguzdG0peDcc~)kbTlN0Zpp1N_gsn0mj~R> zGUl^8EdgNvR^a+jfl-5FTU!g}i^wTGzz+l{=%E7XcC{Eld=wMHGYf9$@>s2E(Y|LC z1CMMwLID=SEfT-iVC)~ak(k#L*RO=++2Gi(gxXnKMB*y&UMu%hd!zN({F_^9T`B*% z1nnz?HeD<-VsK6Wp;d2s-L{VEFS^_@T^v%JFuAI=1g@P+jMYewRuO97QA&;wu*kXYyTSMy zyKW~t9}!2Gwm%CX!gpT_{uFK=$-Bt%TO5WdHHW&fgM`}~}KeZXq3(#$*-o_;v% zRErzkM+tQv*ufI%lwrDdInsJN8G>BUAcKI0%uq=-faE!r7{ zz^WyWEu*6`ZibiHPzE6iy+cznyzS(_7zH0oi|@B$ye=Z=ls5u^2$7{PiE=~$2h5y_ zNSs@~sj^{Ppg*;da4{354U=9OJ9-ufqr4 z%?Gt{M0r}gOG>;5P*c8WXH$1`MRXqLURx@aA0;{2Ez3ixiNEK=0N2}d7uIM$utwDA zAXIJ1u#huWa@YL3$8WaAKbhFrq8!(fVF5WQ^huqu3X>0{sO8W>*U}K`)!hBajNWhM zcS@#;B!(*5F9RZzR*@&`)N}tL{fbpzyh@=D=QP7~k6zNCU(pVD_4`|2FRDt3 zgk|Ma{v&sT5J{KCRiN6I&Icp6uX_3Co zA}qdl@o!gHJVX6Ii-^c7Zj`kK)QBkTxx90p`>e@AFz8!iPUa61k0Azm(di$v3|wvT zX+V-OktqqwgG;6CI=^(}uq}h>@?w{j6*C@h*Ycyk%)Le8JHX6=lB>Bu z>s3z2q7FbX*(_>SVeZZeeUY9Apy)VtPbJl)k*kw{find7{|3b0VErhg!LyM4?-P`Q zpfSyOtw0K?0C;P>^gS|`fqnY?s0>Hf~e+^{1XG@L&M3e(uVb^Zw4JPcvu+XY6Xx&zIvP1 zO07Rr29NW}%uRKLQpkYOw7Vk;s?Q>${FL_D5%idBk8&M`k-aZ}nh{L`Ie2^3@e#)cWj2+_n%*M6_R?7ZcISYfjo?AUc_Z_WP^+o z)~E#Uz0Dc*W&IMDzD>RQ4Y_q*m%m}x1B2B!yy*BREA)B{o75ec$G^M>KIrJPfT6IJ z*ZoUvvv`urysJu+Ds{~h>MdQbxJrp7dt+Gmeq&0TXQ;wd-6Bn^*%gcNtN zO}n7z#Yi!i%je&1rfO2k=8|cIz0(v0p)%UsMlC`A=s{*Nzgu}SzME?DF6PBowktuq z&$;$PNpaJx)KMpc0GW*g4J8SkyN{_SB<)?iD)pc+gtpKlPG;dDqx_Lv?yt~0y)jYi zCF929BO7^=qTyxA5>E6^v$OFYB~UMJ$t^$~l=-me#cWW{ghhH3DkQCCy{s%bYty25 z0e*IBOM&NaR21zhuI_yp;;@h4%P*2e)N;CT>;?hy$@YDEj*k9mf3UeVeUycKX89GcvtgNZ#Z*kIE6?Aa5nq(u93y{H z--R@3;gK;l&FucGU2Ot&A>sk*OKAHX&0FMNy%NFYUiI*DVnTiCSQsDqb{qAPl^P%u zOYY35aN7YDWni_zV=1Ye_y9mKIFLp)AvOUti?CaON)9UZklON7C?B*Hd-*!-_`W0D z)4{ouv|j(aO9LYZ4l3d>n|{+u@&8$1YcBbI{mSY;2+m@v>_6?(n>9fe-=ws4fx9)( z(_vVFE!{#*7SichD%h5`0wL_CPRl6b4NSth$VwAl{5NXoY2X)5{4~M7K1=%7=V?O! zmn`=vFlXiL;Ku7MJO3P%@cwiGPsWU`u{yz}O$qGH( zKPYf*U*+Asb`8YE2j~5AJcrLpc)lQCP#XRscJtC<2T+5TbL^atG-d{M%6OgNO4j+Q zW@}Yk{$S8QrU6HwQqsfFSqhmpEwO^doN{t`XS=<~RC~YH!lx{wO8$#gaqs|V_E86O zNf|V6=Af6Nz7MnV3#m1B9n8uE|M`B^hI_;S_p2s%Y>^(a#Mh4B$Ha-gsGB&hK2Or8 zxR-o?YT2)zT&jo9D^b@jZBoUm_yOWc+s1^zG#gY1Oh^!a+bAV-vtmdFsLW_OeJl6u z>R1PY?5H%bmj}chpY?ay z{TL{{e4e+q7Vc2XZt6HIR%y1+nu9D&bfv^I+0VZ$Gc2!UDmawX!>2*IM~1ijjB?*?I~L> z1^)F-KK0`cGu0M$2gH%UIGfTbD$B;+Eq1nz=Fu8B0*QM>T>EnV3<-Q*thQeA#q=Yf0zNx`qz%9!S z>@wiM@>D_SHI$+vK%EEiRXvmy*kuK#oZuy);|vI3Z@MJ9i(hZK!FQ@9?-H~yeYx7Z z!yRB13#+0!rhhYZF!BIO3lQ6IY|Z)s=Kx7=4-tEu^TZ(bT4;&@aM`KL-dkjl~YgHz)S~6FHUk%u~nqvV*^c z3X072n|V*-%27}&Pb@fA^L}6cNX^AOK|=U8z$W0wrvdE8oD1=@xqH{#OUDM z?1?Nyd>kvFfLR=R-|$6?e!{X1z)EOBzt=j!#aGa(-bj6~|`ov8#`4{dv%Pd$5 zNgYNP&vHZ$1|456L~vxYGRKI)nf*+qYZe<>LnO8*Db9-6#oM;qIetH~od#htBm}?m zY43~LX|cM+P3;Z4KD_3W|IpkV`uZPQ$1nf+-&~B1BIqV>0n5N&Tp5|8Z{An>K2*|f z=w_6;zPuD8b8w+3Zk-{+La#()mZ%rgG|EvlzF_fFi_r%s?Hg``Dq~6|$yB%MTtJ2p^Kj1SrTO^|KrUP$+3 z*SP~3fdNh zgYJtcuj1T|$sp$ul=kZQJT!~3$upGZ(%rXbnE!qG?aHnH-TC`+k>pM#K-hBalMtwP z7i#qs(l4%_1e!HDX5)sGIY-7YEP$f0Sqm(iB`?*xL>dazG80>HZh^}ifNTXC^!E2s z5Zg~C)=q4Nz=7WD#AoS*1i;&Ejoy_{Iz5A&y<25JwSm+W^sMd|Kbz627O9L_1#Xde zeR)jeB8hp_(H!sSxV@o)mFqf2S63Ee7RaS;e8T0Gd9WdU=kSl2xcAtF0iD8I{Qz*JZA&o z_7rB>Bw|m$`cJ=fmIpng%ypm1I7$~SaMGB2G=k-E4f1$DW>B}Rm&*}yV{-eeic5=1 zuBwi^?bWPLcIM`=jPM!>ldCae78JBI$O^+e|8r5II?uj-^f=mF81&p&h0Uv&_ab3x z1)Eru@qg&wI!~(_n`2A!+IB8lZQv&R>_s(&B|BzpQe`xGIznHMP>-&i;c-T!%O6Z! zGt3r{x|+^LHvs-J|u(sb!SZut+*Fa5g%+d;{cbJoi}dk zcFcD9zH=Ske6*SWV2Dyj+o*YZjD5`V055zqppE|S+bfrgKfh%yl(_{C@Of)lg^zaj zg=dvi*H#1zdDi$e7m~MPiz4^@SH06ggOvnzV>DqZVwrQaGD8MbFCDVO8!X5_|uWJ zn5mA^RX$yLBcormU!zyxA~{!}$-ZqooLole#Zu;enl4e2{S+M9B9K>vr7iPp`QcU8 zIFlL`)kUUSc6LpHRqu(wVUMH8s3;OAL7kl=>lMapJm2+lJ97G2qF3vsZgNb-y^eXJ zda}}VwF&E*H6(SD1UwX%BCV=tfP>j5>J>^=19FEZ;NokCv5UK}h~h#6X^PMB9*=IHF@}OG7}jwYl4? zJZU+m)enq2`wVdiT{l3rjDRK?+swC{ePYLc6%Lm~SX_KHSfIvr#s2wFB;9ktSS~b+ zX=PD5?|#GJ0Ewp-L>eeMoIhpnc)KU#nC7*b+4+TxdVJ?@Hxy=ue)vNl!r}%|DDMY+30cC$aWea3HZ|bT;=U~j z)LyS5BpaE%#JTQCv3+rOn zd+vaxFU%RJ6o-)f1g4kh$cWKY>C=d`B;6Dn7SHjJo0gUc-5YzXu<#gsZKAh@OE|rE z$}Z>m%g0N1nlkSycCAl}&QMtTUIU3*0#u}tq<8$(hLum~S5;~ABKf)U!g&)Iur<7?= zr!X~b>IQsFiIcmjUvHoDA-p7Te;Bn!tXn{EJQLlCwgx4LLhiDG$HRW>bd-~(4IJFk z{gk&2P>WpVKk%vgno#-*MxjNXdjb-EANi$bXDXYMF2b_PjGVNWu2g~QcoK-WnccZ| z2ZKgY)wd|$*JNgS>vhi-dxpP;Wfg<5DldBlNNJ+r+A)wwIbOR?*zR9P<@_N!+wn4{}~QlV684W5YuP)JQcXWccBFO4k`-s|fE{H$i^=-&Q~u5=T*FdO&|=dFS^f0laZ~kjIkS!EJ)>fvTCft?%`MKmS@KoG@l9A-Fd` zT@8Ch=tx4!^m?5sI-9^QA_CCXyp_eC9U4gt9&vb@G(A;&>JXl?#i!g{>xo;)f#MgX-u270l`gT#x zOOV?er^+R(BAm~kF@vEg#P}w!`g$)u3t1;j;^iFj~mqPmC5aOX`aD566DHP zUG7UiuU3BWqD`J*9?7TP(xA@wuvHx-1uyGSsjTU+UqT-5{_#u-D$LR>UTYNAs1 z<50jE^q&*-OEx@M7Kk0!SNkq~mI^XRJLO2^T}J88cN6cmZUI85NBJLJ0hz~~)dS*6 znoa(pud(F*yRXmN=$J#6NF0yfT)pY=`pq>#qyNW9N==0(JuvgnBT%&0gV#iqNw0fEYY`C&>$tpqlB&sFKY6YQ5ahY4SQ2 zwl5Ov7x?kDq`8b8-l;p-6HOl;Y4V~Px-Bit#Eo#zYGGI>9x#@S9wjjQ4gM}tOROTp zs5_-TgTqKvnik(9TP5C^Y@_$Y$WJuCKlHj5hLA5e11a$Cob{On7qGO(*{+Dtt0$&x zKUN8?j{ds7pfV`|SJ{DDihFz6Fq&%BPf6@nkL!&0dOf$7ARp7L%fJc^zw&A4S9T0~ z?GBZxbhC!~e!&(#m)=fWH~MoHDbRc{D2L`0PAH=omipi|DMg0i7B$I7NGY z9~FDWyloX$Aki)LCdqy1@4q595y~ViBJsu(0{u;tL5VE7t;9WYWBPZbG+I9c-YWc! zrmS}Io6hCkCWghhP?Qz|yY3;M`6G@~dTn=wk9;^2o0P&}JT59GD&9+MbtwrE&ngP> zq%roLdPN(YBqt)d2eQNMUaj@#baitzd={bckI+yvW z%2BjXO(?%Q{cLWJ2qmMj^&j{=oj#=%VE&!D_-NshP6bHyGgfNT!dNM1*LhKT*a(Li zyelwc;&q}TTmEo?-%Rx@$22<1XHj_)7{FcA)-AVwd`PN(;`RHkHEB!bWX@s_SXU~6{CX31YG$RHiCI5{1_!EcQ@!4Q z7$e5m?l8Ulmsn>*t4HVrewSAwE}tQHm_AqeRH1h{cX;-0Me$*=JGCv{HVX#VmdM4U zR9G>!(Ajm!QI)cRP4jQLS3Pkt)KQa#1I(2>c<;5=sq;7tCyAB#^>IeI8#W@Q%MIF^ z)Jws0V!D7}>>n6Cj0J`N9OPzklWo#se;$o zw>E)w-O@9Yh+%#_=mVzad=^-yYaK}Iip$D}m*BTPK#rp;p0@GBXf9sfS3vFvS?;Ip zWGR!jAuRYgr2fbEy&QU0TW$5vPJ&4kKwXxPMFDd5FTxNXv2+SiOT=vJ{&DdAn*jgI z!%gU(uYzoQUxz!Au?U~oo7Q7Ex3ppOq2Qjb;SR6drm={qXP8vo~dzQ}OHA z?qnBD_6(ikC*e{^v|fH?*-6jvl%5-Zvb(|i#9}MO5+9BDIo;$by&6s9;AH}&^`<0{ zIUMFwF17>+M%z_Y@qjRU{tU_M#2Vt9A+%$aomUC0(~aw2Zg2a>87kkvMH88O1bD(2 z=2J%!uT2ma#aDp{!Ft8+217@-i1^bi%Lq&!QG0|H;ql@PvJ-IIiP@I+ z*!!p61Jx3jZr)ot}ZM7MaGU4+NAJpNPTjk7Id}3@tym~2jH#h5F;)8&xdyEgCS*dX;;mBxh z40r6et5{3X&P1;U*8WPIk+H4?EQ6lA#sfCJgxVtW8I$bYWNzo%bnFQioPv>}eurBW zDN3Y+852ak61ob&{>3l;vv>X_RGt+2_V2dkbY;@HCZK=3v4`gFbr_eqgAyIRhziZRmwBVtWO;3&GcEmX-OxHq2*P?k1?;W96!u^Hlk-oOy-Uw5 zdIc^hFV@|&;jtIFFO6`S4@1glPK(rxSb~77a=i2Gg#4I-QT3nF-@S}8bR>Y|*;3ST zvs;qqSpZO(~4dg-874 zKMBT(3HDZAJar<4iNq1>#Gjqa7eJ^*el-9+O-D$Bdt`1_u67~k=8%6t@cDmEybKYn zsTp)0@4XjV$z&~l=}nX4@HVOtD4}U{q*Q&^#9}(mj_c%ytDJJX9Z}Eo<0E(F>{4co z)gvdGnORQifvqd+@M+@@&d}cW*e$(qMRAeH$spTe-xNcpO{%B*EgCfyJ;lC~%>SB3 z7OpxfK{K*8m2-cNt(E!}ObrRIXM_ii*XKR}=TT-kT5at23?B;NozesO)JugIk4c&z zN^ANq5_vu6wl*bI2T_^Nnx!xS%Y?Ti)1_9A%b;P=w=uR>XKK7S1x#0Co412gP~do9FBw1w)0gMa$TbvT$3=ifSvF#25k)%h-oS&kRy zn?%p$8i^&oLpG?}+P7(J2K|V#!H$Bp@|Pbikhizg+QkiOtH!L#U+h$EN+k4ceX`xl z#Y(d$Ng!Fb$&Mdb?cx5b#g9K`2;2w8m=h0^hUMuXL4*n^5s~Qt?`nU4>g$rW)L@tUgD5|J@rmq zRln*a@yARO#5fO52TWA|A;)xAT>}|PMaJM)=yzMmNd6P@7s?t5=Qb6i>4-S5^Ml{f z0rH2!k2`kuR!HU+5yQn5$^(Owv1&d)aZ=viA)_z(!Iet9;LbI{DRAcLWb`rLCui8L zw38^}$Ns&e_yb8up%|6;TJGtN{j5d&^kS*<>bp)+cVQR5cqNt|l9=$sL-1Z2l?E;ulW3WoSiLS{Z%@QY|0`8PJygtB>(l zX6E}%j}Z)(FHBvA;sY!3Mzv#zTn)Sf!3*9HWi`nHvIm%=eFDzV+N==Y;#Ts*PIqJv ztgUS#D5P3vV=}n651TX z9Emb&Ot|Gq{CowCT@k^J|1?aBQ1a0uT;6wxiv$QU?@stW{RjJSrSM%xcii-Y zBqtd!T_w=%;^bGZj~!BpVEUny3ZTtBN`aMasRKdstTTxa@)h&+tF>Q^MtQqC_>YXNLH?7O4umioS3yUubk;LqhZk882zawK?UM?Dx`bB)_d4B zl^z9lsl3vOn5j}%|pJaKZM~b?q~jT4v7u0*NORa8PZ~? zBu(WbkjUesg-_dGQLvLpl+1@HjK=kw_pdYwhR3L(zCTg7MXRTw9SUZbMt*PNo||{J zN&x*!VT$O<{_M;a0J{U@)1!ymZs&_+%+qwhv2j@Hy~+8nWkE*F0wCf?kShIqbxRTO zsw3wpu%#OOZfPu_&g3n@6@SI~yRcT)ugslGw+Rggn5Qf;-{xq3k#-9Oo4>(<;-!ws z7#pyL3V<+``lt7tw5n`A!o4y`+Wf-h@BRg&B0bCiO}6Jos0xWx2l5bNGa5ck4F;cr zB1QtAs%}bHr5yUYCUYL)N*GA^`@X?ks-;r&(s9@R{Ju0=j~XuCZ|aZY`mW_6)OdDK7*-ay%8Yiy0+!-U0Sq*ao1_&R}D(|3${X?+lRf z4>Dq$Y!UgTVN8+X)*@$W7YA)}IU*Y(1W`t+h;wjuM(9BW3POKz>P%jQwSo>4GpyRq zu^H##G?{Fovk}t8IS+f@>FX8^nODw{OJf@E#2eZYcy#szS_`@7dMm1~ch~ z^;O6*ESG{fpZyU-mx6+)Z)%Zr`Vi!L%Lo<1^<_kM)`x zl9Q)29RA)5brLUE2?R*oYt091z9x;*TWb0;liC`X;-gyVZk^?P9h5p%s_VNl|3ld)jH?J6%Q_ zuvuyV_$FtZ;b6wThtl^CGuz1DKE1|v!&r>P&S{>X+B*u1oYM%NI&W@j8XQWu?WQ1I z@^+ogI=G}PljFXrsCg*gC|!B4uhPNw+%g&dZE$G&0(t^DH(3^)3KiO8aKBijhQ$oO z=Ug6cH_KvxQ5k1KV=}hupPVyrwmsWppc|6vWv6e&kny59J>Gvul9drkeU5iOvb7pi z+kB%&ao8$x2ya{6d)LyY!Co)zHu+bN=T;_#_$h2mC725AqMaQqEA^5wx+O*$E+_g5Xt8VW#$cDkX9EFg$;I+0+&3&s=XW^{oUkzl9$ zyYF%b&JH)IDk?qyIjL}w<8K)k=VmzULM(0C<3w5;0||b`K0ORkKo=)^GXXw&Ode2m zU8cxJhWH2Q`r#c}G@wtxbgP%>1M9-~VJLg8sQevXp*1C{r7f|7&XlLpBs&Sb`c5f) zN|KIR5pj@E;~r47aEEP_0!e&!&Aq}}Rm#HJHDkm@Tny{+n?mB+ru)8=*_Ap%lT%f` zLTm-~q;9Oq)UbEhWz=L5U7dnd7wxJ0&m}gkmshUv7`0z#`1SbBU(|hH-T)S*?GL(t|R6iG9=oz8kDo1-*5NTU7QHE~P$A2qf(j1Q|DgeQTgz%Y; zXoxL5h4?M&Y;p;7G46t{$WU)+Rzh1BfG!fw0R8%Ez!(6jc=P>n>~OC$sX@Sr#EFyP zg^lG>PX%3kz3be1G?qzW`XlL70&ptyzP4~zq;dC~IQ596c7@`d6V_Lf42LUt`FC#t zT}P8RV&FcW_oxErpJ!EI?br|!WxH8c`f>YbR3EVroOhD0v;MXxDy@+vrYz7QtLCI` zruM@fqw;Uvohcy>bZDl7Pb6^ZSj=!Ox>u^@<@)J#p`ZUrb&TfxF5m<4Cy#Lc51Y7m zNjsDMURh4lYFHOLr@C*PK=;lrX4n)iU2D9yz_UHiLhBZqSp~F-=0R0Ad4faalV~o0fP?9HxvuOI%k(a61QwwFRJwa70KhO};_Qmy zm|Np@ZkT<`)4HDKV>v81vxK^9EEW=LxOgyeKmSOyaQ2j<(62~{8v&>mUb?LG<<0f$ zM*p8-c-S)=&FeZ+ww!ltw1T$wEyWX&{Zux9pWW6xlQ_hhPGh!Pp{W|n=RcRcd&`$a zR@@Hw^EawM$ka0YCJTt5t{bYjRC!8#*R8uKodFCW3xc{&Yj)N`uI5dGlNT z*`oe(&F#%!un&z>iP!h>v@{(AonXPP779DQw@0tOm0n;EU9jWH5jUl{qEu}gk%6u;W-ImuL2|exSYtu9Z z2O@)QJOU=a4+XWMht3^~O%_EmygcKRU^pTCF#FI3rZ*^_sO!&yFJBKp50g{W zJgyMF0Ok%ibj}Z#l}Irn1c_;M!TgR8#Co?u=B?56j>6ZLXA6l0pd61>I4J788(q)p zV0IaG4TRIKk+$5tw{SYq1P?$5ebC|Aik*&Jy<*yz?k}RN3-R<(L=EZ1@Vj(Jm6k<6 zFRopnEp0Gw(_Hq?x*NVEGTuc)L7gudmPRb@)lJ?md|u;?ESPL9?fPw%@#m<~70_u_ zWD!p+ZnSBH*!U^AC=2L>K2CF1U!9ZB7B!YY@GvUD@33(X_B*u|MSm(_RQ>6Kj+EDF zofX2{V$>RjhY(eI=pbr)9YnPfTCxDx*8z&qR1-jkCQ1p)_F%^-l;k`?gflFewJ%l8@b{pIlSe6oeYJZJB*+n$mw#f^ zAOp<7J**5ot$JSrpQx71A2C9YW3f*enISp%rD2fk4fOo4s+fllk{ zo;~n(-^3E%Kp`4ZHI}JMOXSirYWB)Z&;1@rs3LhA+EgnQ#0>MIr@okyvP&5mu z4S2IIxmlp4OP)^|)w1I2LPZu)WbZpV=euWXMpCznx*8ela^Y=4&j{@Xjo_mUe;$;C zB0~KB$Q3Hi6R(usVu~y0Pj5D|GW72S?6GKEyh{R{x80$N4r{~6bqu{qoe7l$4XX(! zP*k*@+|~CmD?GJ06E7e=k1U&RH&vYle~S)9d$tUU{8GObn!R!89>wl1o=?P?v(gD& zXo>2^wvwYTHDR#SbwI29PZqB|apIp3h%F5^hJ3#KDQv-<(FOPp!CNNhQBX$J zN{(D>(Ou2UPeS%E0laP1N*vTMHSYK7N?JEIlHCCh1pm3;3HLdi-3|#IdYn!Jk@G(Z z>oLuaGrZF^k`lyX0eQT1J77;Hs`*rocc(dwai`52xIV{JgF1)*bjyXtA~)#|wQcoT z%tM8~-;%Vo^_S{4Qsv&PE!cfN-&dGHimVi}R6M%BJHZPYD<_x}k25UtLwHmdPOacm zyM3zxfd1$G{~1|wPyat7%LlSk!0zUAFSXp0@%2L=J2l&g@p$5^=nWMDr=BI`gAs!7 zb}7+P-EndvE8QT`E#1pW7+*@KEDGF{e?(FiP-MH}=Wy174g3X=g1HTbO_j+*%6ZMF zs_n-h3(SngKA!Yyf{0~$36Tn<*I^2A1#-!LAoRNszWg;}*pjE@YR4sJCn$eTgluRV z)6FlI?aX45<>8DNOW^HE+?Z=~N;d|BCG$)eO|*@~m3%ea(_TN# zuT}SP>)+J%zb@ZAJUuRsSMOLm3q`G*`8Y!CW9H9BMU;7()6*k#$JcR=~99*W}DVE zjAD3MY+H5JUw^k_TO_f&AVoweYq+pqzf&3g!_cPYOxbu&kgv!2U!P++fP=)5P#93c z6^~d+L&?=D{Qki!{4z{H`$hayKTefiC@Jm>gNj}dO-;7kxJj$0FuoxwZ{97Pm`eZ> z3f;7^UvW%6$LMBR97YJ=(F1knse8C1B^W~~r&G+{+C#L=S2F^)Jg6ae)vJ7!K1|p( zSZffy0|2K-X-D|;)J~eH#`!=C1SU+X8xZ1#zDi-bsTlkU@^p9yZEA$ke1cyVH%yTr zpQa`46y4*sb$=>%#1}1bY$v9#Z zc>I+Ro&)R_&`yPE!{lN~GJJKNcpvM)Mi2J8Ay5dN_zNw`@f7A0*AKRvQK}zjdmhhB zGkj6;V;_&EHBBq;IN0phe7K<}Ur=@|`oS4yM3~#q&;IsUmFGnk3*#eS%wT?KzI}@9 z78Gon!L(o{?JNARo%E$#FnF|2AsSGdc7ARb+av6#M6!(L9zRaFWf{wH-)StBBi0e^ z(>Pn|$huwt0-M<$en1MTlP0?=#o-smTzHYe0<+(@#yXw&69^DxQCTI@wBy^qcSz>V zU~KOQioB%Y!=uO~2XVu^^=uTPOCLvgL1D%S%draY)n${d@DnAml{yV42BK z+M-<(cnhMQ)>Y`$fp={wEjcH?zy6AcIxp}QpOOwSaf0R5hUr&0%v%`WQotC63yuyq zu|UIY?Y&b}NCe=S0kR8BL>)RqHj%2i2D&KutA@NMyPE{*%dr*}D*IcF=$ZW5|F*-` zvl!U#)Y_!EmsR(+NN{-P_I0g^mM04|zCrt6I9I03vz5B&eBbB%kUwP8kgk``&h{Ey zG^Y^+xx-%7GPn$~^#uA<78w=BG&ja?yZ2l9NqvJel5J{gHt%;&&D5rsHkslTk(&1( zQRweZm%W>qYExc3P0iVK9MIzC7N}lKbo_T5VNVw@NsM$!DuUl@)V5M!^D~k`i?eV0 z2;d&y=uVhsS8mc>aZ?uMDGup3Nwb*+Hl`i#F za~rl)dubn!z{U|f@AT@PYiFr_m5wxNJ;XzbukOjGX=9kPey3Daz@Q&@a&3B_)h{K=TI|J)^9#t z_Ta)ehf~2TjV4*RcC)i)sw6DKby?lHAwhtmtx%Q*&B17O@Up~*?28O0o16DDdluIW z@|A5F9S$@NW2cP&ho=1X^%bgszh7RxVWai(+U-pbzwtMWAn>#t0-ZN2xW4RHfEn5r z_H*8v?mDm~F8gJ3>1KXY%}*o#P*+*!sPRek`^}3%0#vDsEcFJUcCX7QAUFGBq_}-f zb;R8(Qq@U6<#pVi0o7Ww3E#buBK+{Rcp{zj=Og?D{0R+xQ)o=m1wm}W>&RM(q$`aC z&L+s9ucnLs++%=1`C>F7_lu$y#yT}Xti@ts`Ie&D-B{_rsd#{;>EMg=_d^D?2Qwjp zAaF3Gs#?!X=GM-wgdnEBw9b%G`>%Hm?1t$K{QkMpGi#pdSn6O~kGPywK@w0eVjk$RpCc>9KDIKtcbrUyt%9fxFAi>BYz~=juz;k0H+n_@u);hy6AuAM9SqO6 zjLt`e@SFr>&hgm|fKQ~5m#O)&0(C3t^`SBL%?zOd0N}?3l|;wIfzq`Wei}T)uF*x3 z361D23<^7L3Sby5Vxsy|eag_PHL&9w5z-odiaE-$UJsg{BICIQH}Ex0l|0c$7OI~! z>ysFowscB1-UATQ^4lQ^OeSJCW= zblfe`uWD+a7Ts=^_GY14HvMD3M0Xp3!DtTC%!xFfMsO5pz-}X&#xtU_Vb`UV)$|3N zm&#t1(U=(Nk`FeOgy-wqWDH4F**iB7Yj8Ki;iT<70r4Q8;uu>mVSH`HLJG|K{G7;~ z&=Xm&_EssOjd8~)0a$F~6uUonP*?K)ARWSr4>uf!eoNlQ09Q!%l0Oglk&Np0=M&#p zEaSMWTZCq8V6#Pq_ya$&1xja<#(-4m(U|XTSHTd{>qMKGpbCZRMO383c&75!nah=h zy>pI&@{g8LZFygPdv#oBDzwaYSc}QuC6#54khzuA&A{aD3bu0dVzM6O15;)C3H zjC`g#38+cPX5&ee0A&~EY~R+P*Gt@*NkTR$p@X6Zb@=91TWC+7;*7u-(na`r0x+Q$ z`7}eAD3bZceAcA$wSq*Yvv}O~U)-%J<;BrMnY$v3znaLGCWuB85lM>kjo?iW#WM@T z@5|nNN>Eyz(117TF0;=Pi9@V_)e@a6|J>eOlh9f0PHd6T zA0E$=(Cw;fY1YO--CS5-Rm^-MwAuP{gxPeu zQtkDLG5G~<-kiVcz>iE*f&Yj3BIbqQnTXBV{!wu|W5ybdCi#u1a9S5tNOSw_dEu9` z0POYh)3m5#G{wA3&|LRYT8d9`$geqK-}g!MkTqTDco2BXkIL+UyYasrPj7TtT_JhJ z=1s}Xv7lkMmaKi&)v{OR{NHz|ZtA~6aX(eRjz9ULb5ASc@@!05K@`u4%K$cbOFgoV zZ!f@mWJs8=CMlrHM|a7;Z7TjnDDPVC*_* z3fQ&#%%;Ep0$>b7lin@y_&Uj9seIi&(Ru$7O6r`sy~?E%Di*z|k{eU%6W_C6?9%4h zO!5$`Q0i#bvU>rA#Nr!73f->48A|Iv-XqsQ(!`PT_g5GPiqq}w@9g5@?L>F= z`B1q-^0myqI*N;`dA(foYFkL7L#^FM&bxFauJswK8qJCAnmh5~h%i6Y@|&kwC71;9 z597(jN5aK53mMg;L4r$w8zlI@*k^9<~Sq+cV;IGcyO^6HV;i!pM6r@a`7ovk7P~-*gnxDeOhL-gNuS@ zZ-$zAuj|ny$+k!^`{F1s&Bjr4+277PdHiQ;%Cfw$oyy;BP882JyG5vi`6VGvs_Lf} zb=#gzBdOw9mJ4?Wz26hb?af8*mb}s^cbH7~vKjQeJq=S6_{kiOih#*Y;Jj#OJbL|$ z5F*bT3)h>}NGLf!_sGH7(`MY%915`k>cg7`RfZ?v+~_t!>y%4^cXgnFgu3Ka|GLtq z^jc?{2roNfcB!F1*DCGvVM*A4elD;+A$zW6peRGq&aC|TDn6#3w9J&<&C0Z^WZ&#n zYAU*>A0%ng((4nCx32<0fV9dEK+>pz6eKVK>P<1RapYeJJiSu@Y71F(ezFTsiSu%j zdz`@R7F$T(`=-WrzE3+r97t7h2@s>NX% z$$WK89@kmke!(t#@_IEGa2Fs8aoL|jP5A}SHbdC^$NVLdU+g!i2s7&!Co*oV=Pdfz zZ7Qcr?)E^Z;IcVoOQU=Bjk5j02lUM&2ilcJ<4v!)f<)EJGgNdmLLZm*irJJM9j0xW z{M+R4cgJVTG83nirIZ(Rwcts7mT;}}B&Sz&B$?@z!&0|wvtIgBEF0HB>0g&ZZ$qZuu z4xbuHQ_UGlXwL7FQ@x|JsQV$QF63ENkCmDD{v=->Ic$pyb+`_2aYe)cu>R(-RLTpU zl^dd0ex%~E#bp82EXa!-isok#+c&Rw_Pl)pL%@FMSOxLwoIiX{?%EQpX@+t2j~!ca zY3~;LRBj=BoR&Hd_Pfp=c{fMd#l~jZFYF>5xTU<9u;PdT{LjKc(*HLOfbsmz5Y#?YsY|ra6qDu}gjd-c%KW zQ)}*dndf+MY4fU2^a!UH1h=feBl#A9G|g5wy-Tw~Jc^^JnFT_+r7as10=Aly_Et3V zf+xa~G`p}y2Dm+x3t0?sF^4FTG|(@)+U$NmzMJEg2%^J;GY`jygE-dJNl7Y0xjXW4 z{wY@&Kli4k&>iro{f6Q?&Ywr`>;Rw7s###t$Wccf;au5|!d4Y6DnbK&3d0-&gLs2Z z?#`Mt2oCzK-A{LhNn}+#NP0iUnEpbHONloz1CJOI%3r5#YTVIog2U_z{so!;{NxDT zWBDXaf>+nJc)xmJzwjG{PP7T1_h4~w3l9UlTguoe9^YB1!&jOMnym)#vOPQ9^Jri} z^(~NHA*okZXvQ#D2Jn?Q1`~>o2zqwqPR@ZpMI&I9y#^h@* zJ2&aRk3V~8bxyB5_OgXf^^kU_4BMxoWt~@$wV3pk>g-?sEau;;HU{6mzqjd|1+3lf?^J9 zqXH$D`J_l4JBSgab(L^P4u->!$5 z2H2^Sy)!n1mYIq2Wr+pc#%nGPY-Oi zhITGCqJY}?s~+9OK%|;Wz{bOHqa)P;sTo5tZ47Vte|ZJl=1a6~m@Oi`J@3fJh|8(( zRv%VrYQFRjlI2tEqag+?P-2bHOw-5_@rK=z2~I)o$mF^p-msuhwSA!wB!g7&ZQrGK zwMHwlduk+S;xx)$#Hh4&-l_C>qKC1hInMoo&yA28x=7m{_xKZwW*I(rQn}Yx5=%|p z3QO5`GUXC?K^jW@lk(}z zD@ya>UNLQGS+BbZCaZvsJstAoXj(x0enr;V;ZjZTYhiqodPSU|G|z;%`^Hv}AvDh{ z1Q4t&6r@Y7D-vkeUwwpqyR8*@P{Gg%D0uP%K_ZNIP#B2Seoth8LZ2YFKQ)*&a;_3{ z!FR6vwt;^hTr(Rj!nAyYWA(Q+Uy$Z}7r@xT=2I8(c*}L@vFTb)T|#v3tCGzHn?ORw zTxthls)|?Nx$MfR$V^GZgYpxt`#2B##u5E=UPP!oyg=%(FU;RX`)~oTHz)1o&?G&+ zZ0i2!@r}Jmt*=DGRBV4x3ZtgW{%}KHO;4I-?J+^ z^DE=APNPc&`)z48I-;u#!C*Xg#@MYm=}vmVJlToBy5~&VX^JP7mIJg3PSIA2^U^k1 zG$iC2C|2zYyn>5AM!Q)OIFS=CUZf-1S{u^!L$Qi_Q1-hd;(-|a4KDliv$f0*qZjD} z(|})9w-_-VoaTN8_X#G3es$8Z zz!9oTOW@+d4k<0DPP!N<#YYx?c8*Xeyt-U?{75yA8MO1_LsFYpz6$@*2fz=q?Z43C zb?-eBCV?S-#o&*=-vL%yl>Gin1g_%riUnOkj)vj{qgfpB(SVdG(gb2)E>hLr+~65y zTYdh>YItAX#(PG7qCBy9KYk&F-}jF9rrx-ivwrrt-x3?f%SUZ3P<>qy!FgIr(sb1RByZG-_eoq3*wd*6Vv;9MKC>6~l~!m~mND zobJ&JNh!&Df1UckUQoweGdus!Y}z#txOf(+RTs7$h;l4|W0RENG>YSqSB7m8LYU`@ zvkiquuGG0&HENVuIxiM}4SNo%C3Awx9hFvlxf9;!{M7RdcFFZ2p5KBFZ{M@UW|f$n zYo{B81lWcB+q{OTmVh$lj&z|bKCx+3wP}>|8>@ec(pvc`*Q$MwD$)v)pqIra@)5nx zR--yW1+Hz+%v2Y(KCyfyyms)q&pq?p=lIG{ z94q*zN&TMR-yrG3>opZlM0!E`fs6#65DEYkInuAJHpmauf;Mf8r^P5b)HZBLY>a4( z!rOKvwtsEgoa+gK)!Nc|4qLvxDiKX*+X>)S2Dv6qw%o219s}Zx;a{%F-!O97h!4y2 zbR|Phfh~z9Yyh`jR$taC2_csqPv_635*A5Zl*6m#n81tlIr#RR2-Ovmr}Nd80Ec9L zNDLl$1f6t28-{Eh5V!X3>pirL#C z6N3bKLzpczs`XbAIH~Yj>Lk$q2x3dkuq7IR&&qsj3NC~5){LhQ)1{64d$FQA22KSzSX>bi5RSj3(5|B>h`Zi9nHQ*_3>a2)16{v4i zlakhbk@e*MO!SPsZGo47Y}wFA<0e2Y69DkjK_Pq{ZxtBlX1`;^g?CRYXKMr)NIT@% zuC1@K5UFbA$H(6rc1_Op0T!n+;Jel2W*AwMvMIyMd><&oRA4tp$$%^+&0ilDfUzBw zVl?GIn1$p=T&^OkWg+XD$8pZv@S5N}fd09bTU#DvFC9K@jIagOpPDS`o!9kEMS$~9 zoW|A;8!@SfGy0jOh_6#)idnVG;IidL#L*r*Y>_94>DEViu*2BI%W?_dBX*T@)mOfa zd_|1(2+`xnKWC1ntM2>wkeQWY9w)XaCbX5G4N6i068boUeqgn!>V3NlicOVk5Z+Xk z%xdf;TRVjSaE`Xn&sLo*Ab+6rdS}ZN*@PYnObP6~z$SM^1K^?|!0!uOG|tY0wcZ6? zG+zyYYB$3Li!a`^k|{(0hRBvYFH%C0b?W?Q)bHk&J5mZrskc|J$s#_=#$IIs8oy^q zrq9R0VkfjIa}T{v%of89^^^3rHK&ixXVF3Sn%bWkH0tyW&E=AOPWzTQnrd{nItt; zV~ZQ)F+=nRx=<-+-1++sO&Ud$Q+tiBgIWq6$t!oQAO{C4eTy4KLqB=qQT#=^}@Z|$_H@h*C%8F-zH{Tp2( z^NfO%0t6m>l2NC_D7rr)%po1IHTNmW&}~`8`#8b1V?-F`no;eY(HxX9)}+3LD`3Rg z0b~-N!+s!O%5433q-(3LskZ!p;r3ZQ+xP)KANhb7($kxPjk_GbKW!v|cZcU#H~g>+ zH$oEhBM-2Uuq2E!P_Tv@Ql}g%nT_mtUf+}9ll+PCUPEY&R7B)tKej^-DRnJ-JTR@h zpBGFGLOS8JW<%ySL7(nN;D1fysM-BPGI(J7H=RnvvbJ5D6GX=_RgAIab;7ZYu3L-- zZbVH}Qu>0ijN`B{>gyceQT!au&swX$_bq1H(P*(iF*4 zmqyRX)@IDUT&7EFtnE3W3LULkK5a7~bpbE~DtjimY3N1BN>9F$U6n}74*@eB>~DJK zwvpMYd$@#K-T00GUyL_$q0IO2HtdDwQNtO+Na)_jlMYJUueyac>Fg!$#!`DPJNEy> z*IR})-S+?E_hnv+f`Wj8fOIoLKyr!*NJ=*d2uS0|5#zE*k={UJDka@9QfW528;Q{& z8w|$A_W$_({qN(rpLjjpj$;qrJJ0i7uXy#7X1M4fmCZN0Q_F}f?V4D2vwj`rq^`JqEvBHWMh8DJQ6pcLbs%`l0`%%Xc^_LpyVL~qS#d`E z>#Nel$fC{Hom4RJqT#VUt%iHifyv7UX~Kw;A1{^yD<5Qu)BwJTy_yL*GFld1l`f*K z1HS7e#P0N5X$<(7Sj7vFwzU^Sv%Egxj_@Y*(8Wboqzd`GL6)C!6PyEjD-=4od-aFE zf8hmEqJ4%Z`|tbZZ(KvgGxq{JXnDh=_B(B5ttAWDZ>wv@ALB|DQA? zBWd1;>?tDJ^CQ~2%sKm{kNz3N4u#qT@UVE-zr8ux7AgYJkw{)&;&%2j3b4EZPvKoj z?Dp$PX{$s0{;sD}mx3UZe0e{|SO|UMjvg?9PLtWEua4R!7hmZm4n0Ev6P`tZBXBBb z+}>0D!hMtR6$TdzV$#2EYMhz*W9w8#&!vG3oG+Yu(;Ff?{Q2*2L^0Gy@lebX#WQ%| z{7>ILg%o@{gdP_y8V)G>M+MdYq~ARm zj#;kxbEWqD8dT_cG0&x*S72l2&opoQ;*q=31fHy4J~c4d5GeU_2Vd*`o5^nHH60%+ z-5MJ>(J`bjy20=E+glt*PH`^qIm^%FoUKm6f{H|AUG{zmw;jJGN=$xNY+H=uXcX_*t%R*g5kQ zu0J{VSLs7yC#+UwfDvk10}5iZyl#< zh?3<|W6i|Ev7|?{$2C*g9DL)V(fjcA%8smHN0(EHxF)=DE2Lrm%U1|bTF6Qxn5ifejs8vmNI&GB2(sk$QPT$WwsSj?a`H~$i*9ycQFF2ddNR1gZ!o6Df zU&U?)p@>)uSurWBIj_8mMDKyhAf`3*Aypc4q7xBlt(F2n|zwT{1&FCb1Q-6EZ%o+)8_FLnLHsS=F> z{EDy0^=xfByY)W7Xu{rot-+7@s6-Z7b82zSR_U#cM0+|z>++9E&ljOWxGsYk^n_r=?h!t|6hR+`Ob!66Wh^8!SRjz*NPti9B2NMiY?Dhr(Y-hZ=p3*9 zuGG9v&a`$M4irAHKAI2eTe@;v!K*!}hk0O>ZNU5cIzUI6TX?VFm0ozv|6$I#Xk$AS zdJH|KnIDlqRr;EJY*6`g5b?~fabyFXv}jLQs7feV@Tei|`ObXGvhI%EKfq^;roRuZ zyWFv$e&3~D(Nyeo)jqdb=9`T6rRvPGfhJPNAFKL|v&qLy1~w@=gbgDfnR$4p@5WB` z!JZU2eS4h9ZLZ=4pUUF(bMAHa&6U(r`s!xbusJa6qqF!c-S%?O84mp3tYS?QpsfgpJ?ii$ugw7Wc1})KsG;F55fW;lK8nwuJkd9j|v&Q(qQC;-aRn*XxO0Zixz*x!#s(R=`<6zD* z{ffga_SYJoF0sd0lr1N=bt*0yIq(SW#_`0~lX(35qzDE8h(&>MW%aB6|8+` zkJDDdn5MqGxjvB)N3J!bo2h?IH?V&B(%rV7-OQ1^E9tqF>SBnZm{J4WvCX%=w3dgg zvwoDm-#={e$IB!0to2n*6>@Gn8%_2_#>wqCM1*QUQHbqh{1MD`3W~TXid;ucGLnaPL5=Ta>~wr3>K|7GtLC=U(|r2Z@3aiU7O&D z(v+wpI$psG!B8XKsRO|C(AM$yNl_&ErtO2`o@CIPexLe=rMrh!*3+hw|EjELG{o_D zy_IJ-7s>3Du&XDqw?4y%%zO$xm=)7n%9g!>1-ync6ulY8^%0Mb{jK|NEpnCjN4QTI z+Mami+gc7Dg~Bm$nY~O``Rii3Z5DZI?uTeC9CX&DSnI48c?4vYWfu2lqVToIsy`68 zmUco#x~@Y?*daZScL^hnirV=RN{(~)OgFF=N+Gd&8;4O?<;`+aaFD&;^bji(1lRqiE}F8fioCdI?VMATjXgH z=~sJQ=!dmz!{KIti9y(^~X+q%|yT_m%jXBO$-F>7(n;tV%$0;CVxDzBCV)g_rZtuu_jx z(T{F-8nVDgk+uR*0_5O3@~%O^>^Nklds0Z+SrLVga;D$|n zD`#X$+pK^{L)&fFabigjl$0EKU{}SXw4UE|Ynm@$P2h>#)(0P;3?7+bko7Dn9Hp7JAA7 zD+-Fj8#v{M_^Uix!Pc{&t-e{yje#UtaejY|orT)iiMJ)F+Iax*wt5t@;k&occ^PSmlrd zX-(@h!8JOmJ&Z+=oKY06!YY?nT@*cT;iEl?k*j>34oehBY!orh}fp7N75a5jbx(#?WdJ>C_gH~RZobJGfap2o_C4IEOj|~Sh8jJNntGafbmGMt)lsk{n;>2!G@=`C+x9t^a8N7 zLH3Vz?WLmp{{C}7Y8@o@914KQ)#}ZqMHO*xxfChJJ!NYC*Z5L2u>s1T)OR#JF%N;~OC^;)4nd-!Bpd1*qb}cMM z`xi%R>5XlwY##kYB^>}_Ed9S^V{VC;hL;~SJ4H+u1uG4F-aC+247#`WYJ2#z!d<`a zH(x$G+oAPLfj30Hy5R_LO<>4h|ImPq* zNgg^#WVdum-mm%hZ-#Id(Nj`=PZ{;{F-q!5DVZhFK^u>r2(u(Od9uk@&-{Tx98tB6 z?Q-_mz6e1)WTZJX;_`X+oV1J-*w5>Cj;rU(m4 z&?jIZuLM{}{mA>BYC?(hfRtur4C=h{JTu{vuip2S;kPz*T6Fd^n}2uQ9gZAq^^V21 zoL#w$K}iLGA4rk>+&ryBbA^)^W_GuJG6VcaP+g}2)4u?WGV8i_s(Q4t4xc4? zHY~Is)=yt`AegUX2@OP*GVkx)gy5V^c4&mU+@?u{l0_!*3@bL;(lm4*n}*rJv^qM2 zEDJ##GS&{Lh5OjI_`y(rfX0Z}x}YKu9riV(e+$Sh`IxIeey&!}<_vqV;jey1vlzPfu-m|g`U&1(3BY|D)?Z*w zNPm6Zmd%9E**pIoK@7=W}lovwO?DLYQGu65!wEXAvSjfV1H))uH(-{ui~fkXK&&B0%R$~$al?@XX3Tq>$|^zBQkDmwP{%c03@~;^07l6U@re-ScIY^jMwk0 zO?EK86{D?)>|sZBIU4XkU{NYME?li-RfYQ zt*Km2^U{C*FT`?%ISnE$2sNm)iyyk~>;_HJ$HdHA6+|p<-fU1>j@K$A^l-B~PL)gq zR>SZXlu2ng4f9h}=Y8z?b+KUHgE*~b#-JJBFlYzBZN#7AI`Y!var>b{H)Z^yp*jCmrbzxWk{;x@#vMB9_p1wI)r8 zPP4(X{l+^JC!YXNK=`qA#fz{+Bl+qlrG+LbQgM!j`TIR%pCX(*&v*X0h-Vx5hRt`Z z)XHOd(IEQXMaMCKC18Poo--2yXK`J_ESDM;fS;76q{x_O>JFos8u4(8HC9UOnrx3K zzN&V1XGc9IK%}`!uyy|MIyEdcFJ!T)PDSP5-778X>^E%fmo!l6XSk1TmP$8nkv7Hb zc%Uo+htmQeG@AP$*?lv6&NvYL-_Ros9@mfNZ*-GFY-~TSzl;xVs!z*<7n&fDQI=!z6WkM`QM6=uK32-!5KpI>Nky}=tEz6sZSjy ztaY^Gk#mm{K#cD>^%avvu19xKa9uB?ZO1eL(h;R$Q_~*MW;nZou>pncIs^OuS!YA# zv-z@MlZ_SPVkBwv(io(LJ(Rd;b+Zh6%!OrLSD!{kfqLz4HFi}y6%-ni#70V<<$PTrYy0$!ak86$N8E>ZW|~r zpkc&v+29zqJf=p;kRGiVEBkY$Dj2QPm09Nx5lM3G&ta8H`>0LO$g8l)>SgO)?UsCR zm}bApxA`K^D!#zT-K0perne8k(Zo(CSLHds?;}BAX;p*5J3Ca-Jxcc0xBaHqUMql* z|AVw^vWVUnd*XM(d%e$VH?{7wU5}FXEb?n)jg!$s=97Gn7ij9;>PVxtv-4HfA0lr# zL{HzkdDC3Q;ho0Qj^rDvOfEd`ObI#3xwp8vIwO;FFC=hHAD3If&u9EaU5=aN$jazL zb>o$hAuX+5#p8&@IXl0-(bMAi0OB23o7GZ4-)DzcVi}AxBNp#1D0mScju;)@TIG?? zH!WMw(1{$iKYT>&wi#Xx7k^&-E-)&xvc~#~63HHW`;ijAy}!cie~6Vs0HqmOHf`iD zQsMH#9&p;D%(^o-vfslkpdyN?Cox8(L8AuLhL-jsjiN&6s3%xv+0Q7W&Zl$!64*3{ z<~+ianU&8lcOi~|aV~28e5-c;3xRq5i}7l=UdIwi=D*ZQg$Msjon%f$X83VqYdj%c zkJ*k#R@n3U+GW!Cm!p-e;v!l%7+DnUtJLL}6ZC?T<>P|pipGP1{ar;WZq?X;VvR|b zE%ni}@99*opSX)D6WyOU)xsoSMl9}R9NaL-db#(sX`2FsI;1yyN7kRFlX}o^NZ-;pUiz965osf6Huv!!nc&IGuC@AWb!7@zQ?9{v7*~wb=RfnULT(Z&P{0S}ap{K#+{$)+!dV2ZOc<)a?Ec9(q;+Dm%8D^) z1lO$BCW*DRS-%^z*y&VSLm=|Qi&Qu<$zjzY>$|~MG@$2Vl1+n{4Hmew;Q`bn6&}UM zU*WRUTcJeoeG#G^YdXaCyV*7cVmr1^*xVCQo!{@hx)siPQE9o|_9%ipEV7TTQt8J^ zr#oqSdMF=hH-QGjtgB20Bc~G0$_z9rl+a7-x}~y=$6|`WQoR z8)z3XbabLy@$Gez<|QMPFzBfZg~wbJDr??oLT}61cI@$AiDZB8|23EqOD)5$X+>T< zCs+Np@Ww{WIm3J9D{F<%8o$Tx{w%s+7ZUNH>2V?bs?8%8#xc%E=_iZEU*>?r(Fo5T zybm0Zvi-cPv-`JDQ_K@WYm9KisLP@b3zZ^kvmkYGaudB;pY~8C&nScEk&Ie;a|c+vcSdYly&iW9t0^2;}!7o~@p@;PhhtIK8m9j>KjK*Ot;6h8HgnyK!p!Jh4?Rz8 zq264Vsm?Av8b7x@%)zH+$Z=_PIPDsCxslzkem48aI!1Qwq~-d5EoNwG|JP#X;u=3| ze+)g1VehHYR(Xu4ren0V`!+e@0rMmht(SXMv|w2dF_tB?-jLijzm`ysGs3{JJ~7Od zQzlVFc1&tOF(}(vmuk8q=kV zmn?4Kl`cQ)vz={RjqvzYImD7AqGocR|3=gkKQEE+CFk!PQj<#DFZ-G|Z0%ciM2^u< z;kd0&It<`!7MMJ9>!E-3f(t80d{mOD5pGN{MIM*rakwefm|9?XUjYWKYL+(j{V?XS zb{d^{0_VWDJyTCKJ5u|4KUCz&9tmZ*>6S1)0WnvQyxV%nv{?6FJA$bnfBlb$O#O38 zi&#}EGk#S>?+AY=kyWrhZC{mI_iOILU>#mX803#PkOk=-Xfl@(@D{B{RkZ;kO!$?e z9I^x^d9ih%K=s|`WxBFgW*caGG+R-Q;Hh<>>e0DvDuIUsjax`Z&n&anmMXYA%SC_TI@&9|MV>aY5X#cQjr) zI=~o3wah_&t{Z@|j$y3sYzOMaSC@ypTH8jhpI@;mJ-6Al(y2PjOy5n({kme0bt*jz zqz`LKWe!@@q>ow9PL6gnSS|c3Kk+!c*uPoC&G`I5^2)zGtM?6IANk`B#*DK(SJF_s z?xJzYwyUTj#l=?ZjpfeiU4uIyW}N0Q?U8ty^{l5khQmx7+9o8(ENZyWGO(0C*=bTX zUg0Z}7lIKIq=V&seDaxkp(bNGD%tid2B3nj>s|wcGxYx^pjr*1Iza2sPl#o4AmABM z?6of*Prc0D@{FfA;+&k#{65}kTg_iQPyM`_iHUy)8%?8W9(frro&Jed;Um1Zi{D(g zcDokkOh>PST+lHAhsusid>cB`3wx9yqZJ7NEWxet6+{OFi-`}v^{r(r-xyb+D_l9N-X*JxB zsqwAU%g>YCxRQBiVpsk#q!8L zNMdiwrk`%=;;y}eTW(Vi?yB5^BsLc#TS>N2FlZt1=FH(`X=<7d0^)Iv%Btuv<6?UM z{YN?EHPY`q1AA}gm@|P*PVQyevbpCAXDa=9S$U!e z1!Ob`HZg#%om-xba_bN7X_ClvZ9-+TE4*SIooGnORJ2zyrC7AZ>->MkbKahR@bl4sZt}!G`it?t_m2>U z7dD)el}U=tNkR)x*Y{qW@)F8XCuyxoMeq}c#AlU;#WR8_`^gGCx!MLr0j|7>j-q+~XMl+CG68BYT<3NK z&~k}duD?+2F)>K52OixsiBZvVDT=rT&6?(lSkL{bPNR%3)0=z!gl+8C?4>>@PqHd~ zSWOBuWKoAccu_N%k1G1alg&?PGU$chXX6PAwG}p+e~E^zr?Of?cM?%ix+PP&O{!fn z-OXRbSaI^r$M?)QXhi}-D3HiicIa;E11))5XSEzQg_F3tt>wj`V_}P~vfA0K5t|G` zSAyzV3ctdL0AqwFhn%_pcgM%z1uG5W4>w7>R$Q%$d!~-6Xc-JpuiZ@p(o1$eeF=Y` z&owW9fvtlr&obyQDFt}8`iGZhvTf6k0t9aLN8JH}AxK3pm)Sk!ROyw+MP&`hnn zl1Ouoll^2*1x=MR<*Us^K0(KYMGmi50W^d#a5a!vyt|ic_b^J7NZGaH#-mh)pO3ni z37h%>Q$s$Lu^A8V-~A9&X~OPRs@g4iNGRb~a9tMk3X$ubI4RV$Qr_Vzv(?OHL2BpF zgj@G%w=>mNCRE0?3dwzn5iAXS$qzo@eso)jGlpT%1EGr?S`Bv|X1}Ya-`#!7gUo*r zEAr=5i$^Sc5-jseWYFCzPionF5uUjd`A;%O00gDm(l1yo-}&^B5CmPLi_I}q+8W!+ z+*Vw0x8=nO!UaJVRKrLwaP{8yL(|#h6K;&#X={snBOL$N6+!;6sN|wi0AlG1zv3CA z$GKyT0!l`E_T{h+)bAv#{WUmNc(syCtz$p-Q`tS1hDFxi5$?69FAoKTF%Gv|%*L7_ z=HKcYoDALQq|XkQ?`mP8`5xz}g;)ubz%II!UNLM33+I+MN3d9l=&p(vBKQ^fl$D>r^E zihW-lY%QHW7+xnb55v=LIkiXlf+aMx;htliM%B5RA4R%cC}r>pCEezI$glMQr-ei+9utnf~98!o^DO&VbTvdH>Bc75n=&-9K-=q6SX|r7~WH`Yw_uRDF~s?*4jr z^iuqG|CR7_dq6wKqowRh{FPhs*h{xNpPq(e-mxfj7q5Cu`U|pp@Skx?VbUH9+%xu( zx16!ca)%g~Cy%2z&>%=bEQ{1PT=x}|#3qz@pFSQ8UTq0G5JrGDw`v#~OagrZ$gwyr z%RjX+;^66c`-PrF72W)2)F_Q4e-iCGAY|e#OYgK#GmXu%at+qe3sH)7^L)M zTcbjom<5Y?U>IFy;nE&@mJu_c)EL{ z->D313;@OYDT4+yt~spw-a>`ZK<>1dE$OlSR;NO5I~%FM(5v@wlLqOmHppLDvJcS4 z1m`7ik{G{`9ju;o{X6(0WZ(fLdhEE66In7-c0YseTdewUg%On5RqoIEmOMYOsX(=C{lk7eZGYxc>}SslZ}gRz&?;_w2g`bwK3Ryt0M51A}npbcRMfl~}gwvmn#x?4rcAF597#uWvF{ z&c5-k&nF-IVwAJ4twL+*IG3ox)xABwE(s~uBjs!8^FE?nJ(kH z$VHz}UHcEwqm@k2#Q7S@iro+AMKL9&MqGBTw4G!xq%Ck|0{OMPW9|278+<~PJ7&B( ze5()48|*He17u`<)>l=z9+f$#blSy&+~4>{@@AEtYKa52t}*d}cA2|rMsgF@TLcElIA!eEu)=tg zFsTL=x5E5p`CocEciA!k3l*RJSE0m1B(1pVdG~Z^{8zCa;|nKkpS_jt(VpZ=v}SNT zQ#Ubs&Qe7y~)i# zpF4wRT)h;Ldk;cgg(r_5%PvmmUEyu@i^<&Q(>gl;2=&~|^itT0u@(qgleaLtY??|H z`SFKA**Sa?6nH2kn5V1Kg?xt@3bZYS6o4r#NE2I{kJ~L!Y1P{CdCDk;g4B)d7+4zA z$Ze~e7BU+SlqKTI9nt&N>%Z^{VuXYm6EKIBHgs7CPzE|0Rl$IYQY8X-e;s=!(+W` zCnN0-w=Cs)M*uyL%Gmv&gWi+(pI34p%^n<3_3paB;zeEt{!>VWv4naMg!|@_xPtd2 z25cU9KDdfzK%wdOmxY}|RVKSHcXa7nvp^}zsG7sil9GN8n+c=mXH=h({}TO|@gYXY zQyuMdy^_~m`LnT|cz(Nk-0S4Qa8$uPq}Si5@COX=Pwlvba!EFm%WW1&yEL4a1ueLw zOuV(65_1|*W{g>^u{tB?9WBf{VpJ4h60W+b!snLA0$yJnh;snDJZbZ)9o6+xII{KU zGYl0+75%^I;Vry$7E^TivqpUkx7r0Ztp~;G_G%9Y51`G{3(u#1IhH_-?OpownqCD@ z4;dajg*PBZJ;r0h9p!Oiuuipfq!1q5Ehc@Mqc!;V1`U+pnwQH zh6~1t*{)FYb@A0VIBz`lGX-|-zPfyO5uh)ieJ{hPUFJ^-kfWa8&uaGI=c8q3_^fxt z^{{8h7tU#*Kp&iV>3?l4mGiTPb?-)^vycG zDi^f#sPLrZe#73|$J)lRJ^qRjVhs*u*@Pq2(gw6@% z9y!3xyRO8Zx7dBw1pk-bE>8J6-p=xE6HGyxAy4>v9=MMwwWev|r{=Ym=^L8YDkMbw zZbS0FSkV=g*p&=5!1pWX>^wt1E|*P0Enc?9G@~X)Rl+==#>;Qvf@NkVnD^>^-8YgN z0EHl7^9{2RqZ~!q4=v;&S$zy$rb@Q~xtSN-M@AkleoZ8{_3ValM=yTsMW557l>W8% z_I$@Yoe>*06HNIB@k;Ve_!0!zortGt^ChLfd%8yj|LCBa%y+p`z1-aPZD;5G>&9zI zyRb$v164qFKUGb&Bl4!i4-E;mXpID}C()4!TwJk{$@ZEz9aL32_DOlnny(ih4iYol zv+!Ga^G64A!3!_~UJ-C)r$}X<57Htps<+85fg|U9w_Er9>*xIICYwj?d>**#Mr`w?&oB8Z zc(w)7cI(v5oq8=$N0c4~-@8#g>z6*iJ!#!iu6}%3NqK&)F8J;Oz(=PG_V1vs^&KH# zlyHzWv9qHhB94~{CNefT{{*|XW|R0*XUA*7ajWP9{chR3#$GUSd#tZpKJ?^7Hq9rS zUIEJCHGrIRa0!%uD=g5E01D~i?`sMvsV|%2^vi5{|DWKM!I8#V`T;9}CG8mC8t``kp-{e2i3iG=yKG$ngHy9)rv zL#eyWWV0`1>+IB)nkU^R6-`smD4+0DM|%beAwv2>BPWGF(VCUYAbkwZoz6HxrlP1H zGD%5kqTCb$Kg}J|V10*l$TAD(tJu_yc>2knD7WMeY1d^I(F2Gu$#!vpTt}Us0%UtK zGrKo~bq=5*_Y@hB-Qk0do$H)2Rrr$wPzyV>24e~#@0F>xjGs*m_9|Ma+g@3*~H51&>qU^ z;Ei9wzDUvKQEN_TGdfi(&iSPSxwo~SyZ5d{Fy)ecrz<+b>7;~g@2-u$98n1c}m1J1F#E-!1owN={!h@J!Q!qUi+@*9}|F-lXJ`U4!2{Z z651$?htI|HVi9{4!Q2hPcOhFX-zKwXoAi+H&Kv*VZOf>AF%AS2jF zk5}8M+`a9cK-!1scjsR?eThuJ13u*}! zR`(j0dFbQRtHzrM?jEOKN+v4t{~Dg1sdvc%f-h23j)Z-e`0{)hX20CSb~Ba+GlacN z8u}J_vC!8ysY%aRNTxuYarY-*y9I3A3Jo&j!ZNidBa(!*h<+yi{Ud<=7EZU6WY{?gEnn-G~&nL1n|1UhFd*a$q z0e8CD5aqf(pVI^g2)7q^X}3Xk%fDWQ@EOBqWrexSt_Tk#{N=NR6b~9X%E>U>TtfkdTl$veVjXU3-Ojm0A z3=?1zo4jq4L++x+=94cUN&eW>fNCnewP>OG=a6f&pbVDSOy*YAq38wnN(zgI zZgwNAIn5>HjZ?n-~8=Tux7U$F?*)O?^5p;%YpxVW;jPQ%0L<%MWC(e0e8RbTS zL)P*)%pOz~-`Je>G`@mwM{r|v8Ss3y4T5!z9~9u;J**!tG}9{GI)eI{j_r<}+105B z%s$?WpIpB>`?wU)lsWlc(IG_|35K05ysr}6pLP-2J2@wD)?L*5qZJk|wD)XtZPP8) zFjp~n)u9S?>YtCD3ly3peMNd@^)Owv@Q)gIu*_MQ1i`!GY;yrOux$1QkUC%k%v=Vj zvi!m58hH6@{BuHP)Fx)Wol}_ia(3Erf6pe!QTyslhLWsvyE^KvOhLPt-c+(;qlLpL zVHPklBBB7*)&vM(Fv;78HrOk#+#Pl_SWrUX`gjc}w&8+cZV?S)7m^Db0?2Y!6fK7m zAA14IgRpd!R1!Ntfc@|pBpW-BKDCuMw_DCS2?@Ku6LZQH^x$^owwUd`9MVwV_9KK; zv|oYnmX8`!uHycZLsY)a&jCgJ*v?kfhG2wC{m&v*0P)y+<^_WbQO&Z|@ygxzV_#He z)0y|>cg?}c>!bApr}H6uo0%%k^Xv!T4?GO;BvbTC|D@3SW2<75fW2Y)AhBowET8^g zSa{#_zy3#zviJsT<-#2;R7|mt;TdhiWbfgF=`*pMrv6UG2I61&&sJ{nTaPFs6nBg+ zWw_=W7QGTpz;&RKM}Zq;#f6QwLpx8FV{*w?g@ZRC!6JykTt&aAcBoi!3nL5ibtjwl2S~vNm5fb88rbK$5IZ)`D{LtxdL(@ilKMraxZ% zmzdzJiX94ic=(}PB6=LyG|w!LC_j|6-su5wE{%E26CG`#IWXCIpm^n!8*{CLofN4(-`jNMK%l; z1l%E6J_eWT7Ytx~Jg!iNh~!|7)Zgh@uFWk2_e^{_;C;o~hx+ynOeVY1jp#tXbegaf z(DP$}LCKuLu2mfRPcJK2xzTpvRJim0ueB!~-mf!7LPpf2=a5fjLti>K2$|Qq#>*VH zHugf6?|e#!TCVtnH9vZw-}vNiphNS0!xGHfFyoX{AItHXtdg066^q}#41e(RWwd{+ z>-7+Q?7`D5`|PsJh_s}kP?eITfeq?)Q6f>^Twqp!$tzh+riZ(71XOgr(xiG=92+J+ z_w>F^D9mjhZ9w4_4(=w8^H-^gQe*6FVQ=lw;Kt7mOQV|4FbplPKVsE6Iuziu?CPRm zrXnngIUDW__ybv%Wyn?-QwT1yx+vM_H0o@e5qoq*s|~YrHcCHZPY)wU_g{s7)r|v} z*MrIg6yQ?^!bVNt7MUzu)e+1PO`YsnYYEGxUElWItCE#`>_~4pn{Jnt)-p9h zk?F26g2jRqHyO8%t+{2$ZDPjre$7^dIYe@rbD4?WEt`^*~xyXV@Fr+=WVfwIp{L}NtRyy)FU?pgqQ1%f;K3)cjXg)3QX zv$rRENs|dKWaEv@4#s0_fh-7bype`VE(2VR!P44tsB$>-!W#=^c%;RE^4&7*kNsZN zu62<9-)s6vO>a}D&!EK$r~i9dbP)&4u&^Gs^4YgR9jrL}BbUbufG3+L4eFeK9mbjF zglQ57s8XT?@Q07%2J)KZC*9;n{27LE+n}T;dLdDT$Qds zEZ?`3bizoElC@G9_A!n22%F(#Y{r?Re(tg+Zzc+l6DowOzbs%@{1r0z~0d35z+4LO_p8yU3K=bXjm88V)A! zSOZ=>eMhNIHk3=R@>T)M(iYIO%Q1Ds5w@rFJ`YS^#D%4LvO`6?4e%QBvU9KQERurr zz021j2O4!^nUv|)9vudxA#IZnO8#UOJF`Rwiv8F04B><0Sio}F{Ew%bEMUWRXUk9x zA`BcbFo-;&DS=h^B8+wr&i;aLVKw9V*Y_l?Liuj*@z940Qj1+WN!VG0?(brbiu`}Y z4A2L%GVSre9C&I^$#)FciN}H6kjH7-zrbhVDrPo8R=ft2`T!TDwm*GJD2%c4P8jL% zBH1(UeAs#Qp6v;1O1V0nS|+E`hX18=v_`&Vm{tMo?gr+#wTqTLEM$*{I&Z1n&uB`| zTLP=xzRodz@Q_LAlw_^F{`$U*SGYs~$D*r?r(J!^rA)6dRYnNLKQfzGIp&SfQidT- zU7%BWv>}y-+k=xO4(ot&*q`fu=vZ{}q-F1=SeAK}I1js^+VaPi@wf@mIelSp<&%vm zuB<7pSU23aylS)YkRq9iPI2t26}nC=0q5SH99G#jYJse&j8j8AB{up9B2k{N(A4e| zZLUT@{b`kgsaF zLRnhbgJT{}r$sKc%h;MVN`xwA%E1jqCRLqm>e{^A?WMPL0Gth*pNfUKH5*KF7oF+# z%=G=*(dGJMdLE>Bw34;mTbtzGDkeSJUEh?+njWlDhvtMxzAeHM6RvS0m?NZe7Op`h z9~Uf-3);+5kI!&(yaWtKx)za9D<>q|4qaDT4&Se+M8=4XBEo=vv5mfl+!!vn&g?5P zH!FXNJAU%|Cv`nMup1W!tjq2PCE(IHid4~#wqt(>9TxoVj6QZcSYa~l(xq0fhaY+i ztd0#wG!9q1YipQgr43j7@vLX14z%C^{bjbibhVT;88-+c6$cJ_3W*Z0z9Jkt_maPa zeVIv@ZV*3~&!YsS?M&;9_Nhaf;aQ}S`juwpgV?hDTLhq~n38ERE`+XXqSnUUlJ`C- zZS^V~zqIC9@A%D3`GW;;8_|Q6*}!IlN6Ts~IYxQIK00%!UdjYxZPk*_0lAu_TQM(a z+_=hxFjz4-M%Bn8fHeKM9S<1I3x)-}Ct*E>gRzjJLD`GhQc-`kCl?jaxDsdG;&I@! zn>A!kXGQFF5GVHWqCsTx%s-l`QnzwKcszht7v=DZWgkJZ0yI0AKW^Mquz(=wF3#`M zsUDUuGF8hdeSG#Z>Z9W&Hm%M5kp{1_ta%W}Uat%rdBsn4I~$QC-P-y=KDB75M$}Z? zaM0U*Fs5J)eVPf6ehL>Z@x^x%XWyM22X$m0yTjE^eO3u|Amd9&d?AVsTC=2U`dhu@ z&rK2Ibj2CYy%L;WII>yb&Zp&zkbOZzoVK6zFOag_8%q(hRzj3y_Q~v+ zNv<1Ym8+sNZjqcirZmJRLYxCi!{^t4YNK$4Jf5I5Is4`4UC7r zVJE{FLW!m_*`U_lL*N3rqCH^w2fFt6HoO%c>$mRJL9HtoqG3uu@jq>wV9sNAeg(BD zuYXNOHUsp8A1yQy`x`nL^A4zPdspS2nADOwtcE#0?M_hlw&G;jSFTG*855=mrhlvE)Q%P!2(RU7b{k@0CUWhCxdj%MTl9kYYX(^v&#SvD2gI?1 z#olpPCt;HQi?rl0A2Dq2|GQUIQrpG+8c#pK5s3Qa^$pz?d|h&xFv^6_pfUf}8_H_D zX3kl%y67?ZftP(zDBo~RU+ja+ke%2qPOp348ov&d@5O!gIGD{W{j79@c1E$~iQpm? zXrsGiMI#8U(v}#veE(Dy^Nz1)Ry<}=cUpqezs(Z%kkz%yOTFIoa{s< z#n^Ap2wZR3O9);VPI*Ij*R@xRcGqfg;5S$0Pxaioo?EA<{-GByO`U;<Wsyabv0FJy-tYRpbxa)lSlB8QHjbVUo2`%dOhRWA6EbXT+ku7dA`^55A{G>@PUTNhAzl!DPmkZ{4EW$+o<3j%h(g zic?ug6A`c3o@rXp-B>pEQ8~DChltzK{&NRF=ptbF0}`YfzqMHV;-B<=7bE->%Lx&Q z5T5Gf=(%YT5Y@^2v>zttg&2V)$fLB1?o)(TWp*%CzyP79Z9E@?rUfBzobAC#Di9S= ziRDm5lTxfj>G%~@CO>=sIIN`xG2WO{B!FbhrBraChD_z_1A=ml^G370N&Vzr_UGNZ5Ph2xv_xbHE+>=>Ma0& zDtJSh99ANYrj|PYT^sQKG4_^WQMT>3`1{z1fV7H$A|c%!qkwb>NSAaobTcRt0s>M) zgS51O#89I&LwAG3&>=I_FvHBgefRJG<-fPjvHiy3aq!{hIs;4bJ=Q!;+4XN) zoz`xZXa(lzO`gGeqjpH23=JAj+pE5p{<!yYXxUJ2~CocauRO{xXfpe;y%o0$wCAN{Y-^8ixGV%n3;Gn`-AG5QnflD<8P9`z+vF|0RAB>-w%6l{P-bJb~Qx2L&;cS#Tu=?-z80(<`l_3 zFgU{d>sGGu02ygw2R?N=+T~f{n-g|+vXRd!cJ!qTAsLe@jK*|;Iqu)?mQ@^!Bn3M! zTfa)8gz|VjvyM`2IJ5ThE00)abCtW1N;Oxq4wtg9k8d26Pxr&;iN=2iVdh}T=56wb zKjVH1q1uycI0Oh`uW;_fCA0C?B-!VvVzw#F!_&*#+C=(c*e*pO?HH-z&Be-}DYGtcW z{!({@LmpA?n+H{;(q@PTcubBNk4AUIl0^yV{wWX`(>68>fvKK{7v#)B{F$kK%UkMW zXmh85z)_jZQNjS-`qQHzg!LntO!-NDijDV_H*}?J@*{We6qku4NaK5NpkGj0q(h|N zXSRZ(ss}VFni(`GO5)j+yBX6rhKZ82anZ^y`$7%}=gi%5ajSoW0%8QMzOc`X{0XP5 zWpnbr>1jrk3)ZrI1L`J~QPm6@}S@Ggb5c35mzBLz() zji0;`rBmpHILhICD#QK zGJF9~@u6&vmkXAnYs(`U|79FtYTS)Cfv3l#)Z=nyWS1Vicbh@KP&;|===ZcUhiJb8 zLvKH~9Ct32QHYihwn1;ivqjB?F(L{NUsRg@9-NKH9NpPfZ0~;)r?Q`i^+UhjD}oki z+PQiAF7`DhW5RSyXQH`%B??3J!Jo<0f(^y_ycG z7;0Tbg=-_LmWRF{Sd>_?f%nebQUWh@Xo;o*yK9ya}~b{6|0V$!3URWw-{(SDUVnUoZ*=WQ+5n)dgivXna`R`Sn&F-`Pl zDWF;LZN`lTlaJX}{TmrKWX$iQreiK{7rpqbxynfS68Hl?{0U5Uz^yw6(3z1Ih}?EJ z8+JlmN-UReoQ>GTb7YVC1^6i~U^amjcf|B!Z_3&m0&W-8R}X^yFI~POZySxK=E7*} zamw!hon|2GSEw%&>v1_) zcD8StmyoKfGiJ{FwCo(#k5@FE9LudP(cJV;Yfjaf8wasv8p}*^-Q@0HOo*xs8Uu^m zJ>_+wjJaJ~*kPL~g`VL?wENahmBu+&9%AMi>;H|=Br8+G5T#V;l!mIGd_mlEwj_R2 zi0fjlQbWc&Qv`@aEtO3+C@)uYOk_?5=}Ow+8m6JJWMY7|oQYsxY3RuADzDi8nF4>4 zP$Q4h8|v20s;qRf&5n>5;QD_A_b5yaCe$n^^AF%??-0wvBKdzNUfmsy-i} z$gC|~Az}y}-aGOm01kIVGccdtlJh!hR zAEVIL)9R(ePhw0Oc0x@T(lnE(XtE^Ll2YU9e(Dp3C2q7+ex|yWDm81RAXJ?H5Io;+}i%rYyP*fdP?d%3v3_adp-}Vo+|b~vV+QxGiBlC1Id>(}3Dz*q)!rOR zYt&rVg;*(4t=Ie_`M{*wtBtuF15d0+!Du)BS|#%*jc83< z%13wsKu|*5e)pKR$5qSZ8w2}_F71)QUC}&VI13%HpD&|ufIpH)1d9HdZjVv}V7^Kew4CjGa?=9v!AHNi6z4@V zHpQJ(q}l)Gz+7i1`Wan1e$m^xGo}z$$3CHLsP1vSkr-DhDV~RPcv<17krZvdimZYF zn`bqs5$XlhCm$sEFJ;n!SyCotKT={lFVP9#IZ5}jWzObCTGnT+-x|kAXxOXJ8jlwp zd%^fjAFAt@iyc@uTOdN5{Mr|RieR0%+ekrNSwUfH%Xxe>^1jNLX%i&y2?PCpGn31V z&Jto3`r_mH{5t>9!_QGC1YK&T%3mT}pd(_Z8&^ca0AhaZ2|pe)j$B%A3_N%$4Lrlv zC4cFAh+eQ_qbvXr^e)kptr$zlE&h>%JX^C1FOkj297FJ<0FP&5r_CxkeriQ3d*j=O zY$Af)xt^dj?Nq*(8XVW(0U71^qrfRS7AmXwdC5I_h)g z|F>N{;C&$Z=4aBDUs+@c)^GF12x)J$|4UZUAH6yB64~qwYef-iFd3bl;Wxy72VM~t zR`~NYFs`Ip^vHDeA9S^W;kidQ?rA5W6&D1OeZnIt*j5y*mQUs08-yVDuWvdX2qc`n zSAIGuTKUQfVj&D*#v$MyJrlmW-t)M!sW;-#r16z313kSe@Khj}bm@o$`{C?M@75iz zb}pgt9;rs5$}{`9{jr7oMQ0sDteIugwtxp}yJ&K*L}Z+W%xw7a@cL|7r1lUdWY!}6 z&=$7pNDHraEO#rOm1uG4M)oo+&pkh&-$oEno- z7D_1>cA}k&eI)~dVBnnGAz_!Ty$0gQ#+YXdg`Eq?o&&@WpL)+0y}G4uX@sjMsy(3K z=kBVgXhv&)S6K22DAmIu%7_7A{xfdsuL&ID?$=9Q1$^gP@#kDl(*(spi#SZ5rloU! zMx7kx*c-Qn zIz#lIvm+z0$KX8#fv>;k8&`d}rQ9bTpCC@5d9LQT6Sq28p|Yi-ZTa8}J5aD27tykz8qn+@N@QDzmDT*y}8`Fa^01c+hu zSq4Vg9I-3uJn>fors<`KH@8&;>Pw?e<_2WX6aOY>$pZCRDj=`-3NG-oW`+sb7ODaqwcB5jE3tpBS#Yi2 zC!Ir&0NDh$$>N1;7VyJph3w}?4Dk#^P12-x&)4qWqGg%K$`e(UoW>9({ro91oUVy9syZ=D zM0r1{P(#@@G1*9&d*#ci3NVpbHKlx|E0#L@=@6dDMX-Yhmv4gHk7f(6oPTK#}|6#H9-YcAH<0Fy@sbl(ncuD#VYAhUFBAAX-Y-b9vE#9ZwZuU8xs zZ;IHMDXHsxR^eZ!-}qSihey%YV;qdFvZGWq%d+!GRM{>bgB+Km&N=}N<)#bu z6LY{90i~k62`6@Wt^wWj1J$Xj7;MDI`$u3FAFkG}qa`3cCWZTc2h`>G16S^Go|X6+ zWrYl{{i|M&04jFS`5yusHDV_o8}H{3A~ur9s0RVr1<%|h9>@l6(hvcC)e#JSc=Do`w3u*A<>Tg&4ZHYkDgJv` zqH2Rb<>tgh zy6p@SwU(~U9vNb9=U`M1}##JN(Q1-ho+>{dABPUy`IK#*teT=>+wq*!%apVlC2EOT1kJ3%iX z%Fh>`#MvlYB}#FymoR%1ZdED^EL!^m>%a{P_9Fo`OtzCg&iEt9j0Z{HIa5z@PY1u) zu~I)(r!}y0?=2?X&mjl{13%^-&~N0p;bdG*QHmpt{j#(A2h`9SLSfugcjx3*?d8cN znQE{@q%8~|Bb)<958TBck9}j?HNi1#j|TTrjCfJ7nT0^kHn;p4!rjZ%NNh`x?~P(gRocjBnu+yGS03qPs_m?i|qD)}UDwGW=w8tNdAXcb0Oi zXf}$R6puvyq*48iNCLE^@ljW`_vOw6CYj|#i}~yxG$g%5U>{cHuI@cpz5fhaq$KxA zYUnvuXHrH%vipI^rn%^%O6&YZt6mQvSCQrn6saAYTbPJ|=cd@Ep_)J4jP>u;|BPBS zzQI)YRXPV{F8$GnLHbiN==iHjVH-L20&^kyhj66xgomV?(|FwKT)lId{JFQX#pLOv zM<#dv?I0g>KB}{YtoPmYsw03p@I#72k*2gj3vR!acVQAeW|sfuP@uN(NV^Qx9qXmJ zg;YDedMp{tH5IfWmuX}93AtV`wdqf@52D+ubQ6Z?r!m3JeBVu_(z&VkJWLw|vMTlL zoa|y#bHgkUu`Bdzdb2hAwY3ZIwm~*L_#qF-lSBvN_|xTSRf$A{1-|Nqwc0X+IRq-y zFF#K&NyUVeEL7p6TNtg$@(Tjmjek-aoqr}Ul|7jj3BR6VBwQ4`%l%iH`l*QZ z3?Q5a6N`Tj&lugdh)rMa&@)It4RbI4hR&8Zy6Cw(#&~Hbkr}0wa_>&kxQt);x*%c6 z#`qnykui_SF&n-&HPCnaRcCx|DM?IL&^t$=frzqUjT^otERluepu?)tL-Ff-o74lH{tRZ6_AM8PEJ2SYyL;G ze>f1UbLf6^i5$Einb{UM%Z7J*bB^8DklwzFm3taFviF_)aA2hC62+gtT@wccJxiw8 z?p7(YNjM4i<@XAH9g9rbnK-iHyUI1|Ygo}tBOaLGiuI*1(J^}zf~u#=`;{Q%{%e@3 z6q{=6k9%5|xR6^V;#J}_^uaS2^8om*CrGR9f!@*7_&i=lAP6U-J?P&5(|-(~BU!k# zmwOVihK|Wa#s;jsDF?O*sNm8?WoWf3b+JyDo?eDEGXITDv@ThpBV~mJqK!y_5v1Ii zOwiv&Xy;hjfD8_42;fZ_NE~6Jx4!+`ubZB$TTZVB1Ob{c`>59mh7m3d^4veKlksd* zanm)6Rl-|N07Hvw?;4s8-bZoomdn^|i=0*?Z(+eVV2R3gB-6*TAd*#>=B~#-j z%xj7u)07OmUTw-sgWMUPJ(xlCf?72m5+-aMw**ri+McC$VgHq8yWV0MN#UDt`|8ah zPXJ=%(}AV2B^>54-UonbeV~v`hsAD-R*KLEv!$u({r; zHa>hZU>tGu9^b`>oZ%JgOFU)+^eZ2fD`bw2d$Fi2kHXfj%KHxIc6rch*fsW$U*SMY zLYjjc9s=?Vrka(^JD8K#d_M6V7U*r6K)z3*htW-swcrZ}ra>mxjhGdDZDH1 zI*LCL;t$)`A8z3K!r_cVyBbxWb7*TYl^TaD2rY$rK%SfNOXVT zz*90R4}MUS_U%&bfi%zkhw#8FFnbAv1rdfBDZ2X-?q$Luw8 zBlj+`!ofdF40qn1?szqyz8l(`>5Ti*Y2$m-#pirY8Y-uRF&Iuy{4^Kb)p}z>KKNP! zcHxG{W%!MN&mH4hfoCws4en6Z6RlPQjv+^*%R?quhCiw>y%eP3%`vJ~y+mxFfCIYmZ`!GIuovX;8FEbA*T9Y!i0|!4=9HtG&5F+vV2%OXhhh%X1n>*gd!a zy{2`IZm!2>aefTfI%unj(l;oDciV$lH`j!PA=tAd8Ga;z`IW%?T&LQk2nzB`nGP69 zIqtdf*Oviqco9>LefNc!zMqfFo<-&~e-v+wk^cN4E&gb2c=LC!FHu2D7m!O*N82wa zu5$j$(JD^QKVg+^7*5#8Yn2~bb~dP!<8+4e*3~{6kZu*=)30SqeLiQ-@2uu(?04n{ z-p@k`H-6#g?*-vUeNCQH?lck@!}%iNp*ywE1iNaW3t9V)z4kJ5g??0y zCuCpab7vJc-v`ccrzzW~WWkmgODoD+U=QE3t7J%P9pK zt@WzqyQuDe8KT8P&iKwA6F$Xeeb++zG*9L@@2zXBOts_~nvce8_hAn_4gD*5^t&u`bTWdTm<@OR0^&zXFl@9q3S6=#9znc%#yD?4tHr(Z_46cBAZ(hp9sr=J&S zYp}ROH|wZeJqJarS`etF^qE*o&ttxH*77@+1j!rek}qF>IAF(q-oO%;cv@^Efgxrezsg4-cM= z5EDr{)XHGElsnIccf`|Nla`J%I@7}fRgmife=3T2OXQwT71pj%TfzLQ*TsE+dZxh2 z|HAwIudfMy*rnNsxFTEmr-|@dT^8*e#MDRcl&U|UC==WhSsQey)Mwj!Dd0%fBXaLR zi%Vdv#c$s=b%5rZ;?)^Ig(URGvG`6W43OL()^Rr1q zfALDa7(GG}cxkpQCDYnFuAlHzHybMfN<5;x%&4a3^rRmj>!I7IA%j;W!-HcbjND^I zF30zWME?b#=T^5p;0FT)Y-`%1B{@;k!K|erk^Ps0k-na6yqlVbyk@HrtJI6&Jto|mSBsz)?sm7%*LRix&--tL z`AX7s59Gy1rrsHCL8qPmdNr2rv+7rfkqYEIJN`c`YSq+iGb(aU$jy4hwvlk^IO`0L z)*x?|D$IrlDVeI|U*?X8qNYLHaxTD5%n$>f{N{E|sfn%+&;-NZr;%7g@IH0rxz->k zd2K}u{Mm--py9*apHvLmM1twDmJrJ;;70Zs3_i4}Y8M|ZxyTRbWB9(h^2)>j5)sra z`f0cu=`$zS-re1Lho3jA^~?6CXB~gFW1)Hq*{J;Tm^G@aIfd5uE_B+EHR1R7+XHP{ z)8GOelqQA$VGr5p5XtKaH?4U&SlCW!{blqx-4mkWz{D}AoVAzAmuL^GD=?-Ts5tW@!8Vum9WiY zdDWGv#Z?gV@6!~bj=UL)uCnK?%5mm7}Z3 ztD}>=*%qwa(APODon!9KW@n*a)ez&S+qdEILJPG*p=-{69sru}rz+P;?h-HHcv=pL z=5hAwK|V{gUSd`Ritmx3ai0a7E|?F}72{d-)QazYq8`rScMi0wD?L9L_~?1=*Dn|E z_cdqXRWsX4@1$cJp^@2#t%4jN^7TT^qoCLJr6Q3v~|V48$Nzh z6gPzWSf&QnBP*N#?AVl!s)tQ)goXqR{NAO840svMF>=qmgFE>pPG3YUY+ggN5WNee}uSUY0D1k|RQYx0(+&0VZucZZt8$5gaHN@u#GZm|#3 z1RoMEB9*!(qUJw;SEqdjG0o*0@9D^cWHdb=!8feh(Y9JSKcmVD*kj=kr?X}c^B6}742ng5wU?*%2tuoy*_h)B+Oky$Nk!z)DT&PZ z*h44Sl3>N}D{n)P>Tyad8UZ+pXj`)h> zYW`|tLqv6BEy$P$DIKw;jRQo%c0a8Q_u!AQ(iLMRH)L_~Eo1XSYp zjFPRKFsF=V*ImqkSw6abpznQ|YH7xN?8$K?kKHA)*x8_5=_Wpk9*SG3Gto+kIdYxj z*kJI8p^h!{+Kq47xT-eG&pJSVoq(h43rcK{@NfSvRm{Edap}rogf-B+j4Lx|emHA> zc(Yr>{M5$NW)3Ic;3j0u)d$V+hKU&OOiis{^bTJfj*yXz#Bc(b)nhH%oS85@G_V+b z(YES>G$zqmuKl$X+%E2OzSZ{7iz~HTv}cFJz@|9l&4)PTw4yH4=}454`0>zxk^J3S zx)P~hcUq*(;#JRYn-0%%Oyprlziyi;{vf}5ah*KZ%Dq?H46us-@@hM1@57uXh7x?C zejOb4@ryETipOxkuSu} z6(vsZArDu1*`<%nD&s^oKa6P_=u==e8M6Pu!>3Q%;NQoa2L3j zM0v`*bxua|<;*Cyt!?_S?O=K8AjMOxq|GVJetS$S34EI8ze? zUXexQVsEF+&z=+QqK>_?*YLQDP{xLy+j^m!nOQgg0so*qziG0J`!+JWajyk+9m9~y z2HGZO3FQYf)t_4ry-Q+8ew`OFF=A(3u3+ zd^qwKS(3OinbkXMR!`e*%Dy)f(7mRA^!ZUP{1rnk;N8-GA?&?|pf@@v_T8?u^6x*( z0nc;4_=n`)H^5$5@7w$T+>rnL;?~VKqc3i=4GPQLdxGrzCqU@OvBZ(aS?$@_=MM zAUAPxm}CL-v_RLBnp}d2{ps5M1lE0&R=!DHd8~YS-5&SA>5_Tlp!6_OA~6sM=XAR) z6BVZqY1x&;un!alMLHnRG^b0x*1@64A#Qi9F!7dXc95HPp7ViZ0%|?=bm7wEfx@EPmN=l0B(sS<8Nosu$mqb4uNIkHu2qsBysPS~@NT;Ju2lK%Yj4TVpZn^hS6 zZDG*6JJ-V|@4JH|gg!sjw|g%AaChw9!Q(OGA@kIW&C}#+!Xov+@>Gt2KcYV*u;?ew zbmA5#NFdf9W;14nCpK5?mxXg{$I(MEo`zY++f7H*$y{LijuSNbdh|jmBhQ6}lXO~U zt^rZ=`k4Jou0xdkho`A4+oZH-un$!)p5H&a8T$T3N6Sg+7o&TdSjd1$OTZO*N_#3j z6hSMhqkH7w<@BK^%W0|0r}guY@LN7mfXI-93CEnz6_a|?nHMWNBrTT;QK%#_P$EgC)5?o?b2*2qQwW!8b+9ld zjIAB~&t3bs?{1R%eG4VI@#IP94T0xhLT^ck{k*WQ@AO+7eR<@=NBh{h@k1s#=AU(< z5st{$yot9W#duWO@)uWM8@;O6n=GRb$iz_Qw5V4%x$vut*CPZQ?&vJ5EXdkbmt>i; zBH4@CK}{DVDfpP@hLEd&dQg#@ji?!HF;%oluzr!qy{xIP>MK!iFEPv6K&2RgRvp^8 zsZewe<1b8W448INY@a3?dLZp=YiRj`8r(I4z?C!|DTDGu3AjbYdeTOyzno12632M> zMP(rLH z1G>EPsO5X}I!{|Esu%-!`2X!}Y|4}}Aq5S+bZ90@Ya@lKwAOn+-9)RXKj>$wGqkK^ zO|k2z&KS=u(y(^WH4KQwgks=nuEbYu4v6Z-Z@aIcsFX()i%&D8T^|UVt1N!A=h`}y zM_>sLCr0Yf+^e(17U8ur1eN}qZ{pnsDUBtr_B##Cq<@$|0#cCyiXM?gMOK1SUWNsG3qqUY{p$OE zz9PQg6OeTLSNj19wWlH_L|deGiM12*Sq}!2RRYG5$7ftNYv-iiXZiWDt)V+x2DO$y z|9WE#PF?y~{mdGr4l5mgt9WNd?SiPlGM!mg=hnla`z;^6()*I+)0rJb$8JSILepF! z0>v?0;y~J{vC(NwMW)`P0Z`x~UvPTzid6<`viqC`Uo-8u_*>=;S1EXqQ6}h!&!5^s{DjbFN(Gw+{n7044=yg0wD)gXVZ{Tt^laahp%rV%!slfi`KC^O?u%Ip0udBs0npor@$UyNjMcb9Z7sf&@mhWlf+flbFUlIBwu^=I*Z z&cf?-;rR%@9Z*or?3KKH^XsNp%L^Ih$jqP&_m=9MB}q0O^WNZfpv772P@c*v(M7!ky}u?AR#rYg(akuSL4}JO$L8<6)bg zyjRT5Yht6?WWq*Z)HC8^x;0FUA%uRsvaoGgLc-w?b8sAs#YU4>30lEqUQFF)p(6j+c;)}L+x0h1)O(d|7 zF_?tp_C}G(eQq^|8OO^029pI}HsRNdg?19x2C-nV*&RDslbvUJ?Uo?BN?3EZo^X?y zn9nJzwx0>L>c|q8H(ndsVs`wzz8X8#?~pnT87pe6XW^s?TDe9w`r|*ukAJ_tNq6VP ziyK7NuV36G@p212?zdQPnyz=gtnE>Aa@G>@<;nyjL}&)f%Zm{K36j#Wo5TO{_U{Zv zvlCvX$hrgwFnaqFVjtPG)za3D^veACA_44Utvn-S-ROd+@LnVlO_wPi2jhl>Ew2;b z2*vP&xoAu;8!TeR%KRhJgYC>~EgxP^T0AV(F5O*qZf<7HEQQrZaf9+bO@p~W0#MuR zX6Cs^!^K3VI_ZVM<`D*XaP1q+$0v<=H62^COqBwt}G{ASqk@{G+j9Gu`QobS#$}g zvVLFyi|zG@_eW?2L=sE=yc3-wW}AzDu}IDSyVoS@t=LAaLTLM2uN{Nsn3`6f(1nl- z(f+xu7v|8S60!IU`;)QMk%08LPNu+8#7Hd8@7YR-S6e-<(%IN^EMWC*Nz$MOhoySA zo`a)oVhgFW{v%26`h)t+di0R!UU0m4Qg1!081d^D*ROK{F-p|VPyX}I|KwbZ{v^sB zk`bKRW?;6LNrD?v&c-hN-HJU&@r_v#b!rQcOn_?`(e(& zH3!uQ)79r%F)if@nfz`zEvl}=V3h1}?8x7PI&xr2dFYa%iMgK256x|fNG4ohAF=qg z$0pk;(%`-c%T!VGnJb^X3e9^xnCo$e7?xSu%5t}MAas;N}WPN16@8p$qZA2GKvg)}CG1e|Jq9UUmd zQBH`+hM0JDCbgPjEQ5v*iV`Zh+Y8T`U^dx6IN<`AJ8;4N4b=CX~b%ewW#O(01X6VnCnJH5%Q5 z(3JGpF&4(t$n<>zwa2zipSx5Y#0!AXk83n!670&IYKe!a$r3a0-~234W)5o)tED6Y z-~!sm+}`IShf}-D%cW#N8Lhpaj*Q=3d-UM#`|Au9Z?FGz=l@HDAq8x0LqDw>al^cV zNp*4u;8)Rwm?1nye09TnLmG2{T&o>-9#V^AjPb8Wc5@n46J?3xEU`kU?@e5-H{^eMr_RVoGrr5Qnbl7r)*CJ8Q9*pVP$7 z8(h#AnjHRVwaq-DE7+486o(&qAA5(R3;rpT+Vy;O9shM;oh;x3k@+N}d9k71^-9XzXc+MYM&5-Dt327q<*)FJPg+8|xu4DU6fei4jp2H&MMYpDyf z<$8I#wq0Pe$>PCl4j!?|IE`1FW%?)RBW8Vy<6z=nG57xO5KfxPdJ;(;)^2O3alawk zP1>h@(Li~>k#6R>&xp%0Ed{K%%A~fO`k|5N*gzkLu`r;NPNq#SZgotlZojHaBAjy; z1$B6};XoP1xUn%p3tRXVYUL@?*oYq*(?1}a+EBm)N{^}uvVC1}@k5~yhs^(TeHaTX z4wA9-pwXQ*O_D3fxB`P?T0cJM*6r8D*!ANkw#TH{E7j^QQw~s~_6#DqT_#7(zmq4j z2%?nNY*)K|mcz9m3nOa1UuL#o#=uhVDoHHtY@q~_L5 zsQcJX4(ELH1YUqIxVDOGgz>+pB6k-}7@kE6`V_5H9o9Nk`UKBQAtkFzOpI8OWSPZ_ z?ajW9+~!}sP&p}xdy|g5oX9*r%xCn^^CbFSi6%$eHBNb?>eV5e7G~>ps z3HCGO0&t7e(+_@lioS_9c>d@PvNyJvD&d>Eao^V6mUW1})IJ(Z0i@+L3>K^_k})c? zW4enyho<^(qMH%MT!B@=*%_Co*+6Lr zHk;mt=q37pN_X;EXK0p-KhZ4ZiI|c)UJriR1`er>0kVVsI;n4@yL|Lt5(#)qCs-4E z;N}h}gz+vt($Ov*Cn6q0EKuhpqlSJ{o}%EEdAAt7cGH!{C85ndwUyV({IM`_N;oE0 z9D_fqx@;Kr7+S;EVG7n`QYi?=@Z$&s-z{`jNVd7k_Xozfb`dpDhcAnF% zKd3g9i&HozhB>I+pwI8?wDG$gyUEyOEU^iZ`IjGb@!=FVYl_$geG9C`XL~2xJg+vl zUe|SM>oum&FO-ihjX0El(D zJE$DHy5xdt$;oj-k-Zgl^fJ!JgYSPJ?XURT5&!>-WcWi!GsX~{%rzBO#jK`$D{yGI zo)X`xReM5iVSbC9ZLMNy7U#@@5Yj67s0Ymx%_Q5bHq-RK6ktzP*MYoE0e@Xw-#9XH z-~u^aGEAQX$p0{ZNHY;7U5Ja9dp_7(x7mw|_$+IvS-`h)F*Wgg5bXTZzzp->FQN&0 zwkZmd*3ulLCF?S+dAr!u%}Caq-|p5$EziDwdebWB?a%S#c&kfscipV?xuh%zX}e!} z(ZPZwu3Dp$L+FMlTW@BAInS-5(S+ZPC@DCYKC4R6(Kp_=eg?Sf?NE2(U6 zHWH?#Y9#f`5J@fQWHiii=U`r`O6Id&GbY_z8ks3`p4SC8L1s5SW+RNLu!j%=5cELf z$yL)iGznrmOjlXo+`9c>;9w|3MSP2jTIF}890E4Kg7rypa6b`x-IJ5kL6ifW8{%c^w=k3U)Q{1o-?=Ke3MaRA5r{h zMY#aGqHZ;Ro4o=3LCCcX8H1}|rgEAJ7h1p{QstegaKI+hILrdlpwT>q2-9Ajlq$+n zM=hZoXv?S)1#0(Gu`sBl zsOTVT-`HrD89~bql&73@0Cy!jX>!48-ZWKFE_DpUM6%>}f+8X~<}SJ^CGMzsA-3jD-DL?$1+B)mFul}#KR`cl9)Y442TgiG};@16It{}8`FN(_o)X4G;}|Lf)JYrnJ&Uf9XolOFt(>4Nf*b8R2r z21~Bd7)e=AayJANelUamEKl~ERBmjTDG&`vbtpKU^RIj}q2D`HO)Tx(S#}%Uka8Ej zyT?x0;FSTZLTkHI*QSBM(W7id1pE>o)IW~aDZ~|psTjhkURdDI&-1MPh5k25QvHpW z>O4~RJ)X1e9~+jii0cciz?d)c466k%Fx1$ns(DN!_ORLio&zvIdBk1YGwW zil?q`{Ox)k#v7aZ z9x2BiEt65XD`hDMFkQxANq;lqzxSk>ZhuK_v$bN?#lbj+K9zM z>y}x#X~sOykq2k2YyCpyDeUxaql1MjEXQMD>oDp9m#TNwv|4&p^$m;pb(*{YWjDkR z3n8;(hMndFV|j+nxhU8vB*j2itzdvx1hLEYqo}mok1=$wYjNJ|e}Hibh1{l7RMfi} zIu5?7+pnY;lDYZ%Xal_*Ed=>-@@tixGTm5D^!WREGOi z?sat1rY2W2d~dld*Ishi@UMi)p&B+ev0E`7lYR>nIM=I=xpE#B0 z?)bX?2)14eZnh)JJ*cBL;-m#kx|-!u{cGMRN&We&px%>)*ONlX6(f_hTmnxIYNwYU zN}HQgZE)x+iSI0BFNsl?_Re?QwUxSO_C7FDM`FvV zI64V&DlUHKy^p#<{%k#{0zwlo_q!w7HdUiA_-kOMxbzaXWTgj{6ml1eFRO`P`Sy9M3mqr%lVsjT?j9Bb;C9=8TYEd_d2Zw)FS$c z`kA+`SgPA+QuUmioXSPcS{oYGGp8HQm(R}dG5p0sJs&?R-GA2eQHc+TK_GwDqeTAf zm3$$)!lML*!jHheiy+N_FG9Z=w*zwO{hFssaz<;H%J=rF;d6l_GOYiLulEdUdTYNx z-*YS+ML|G7sZs<(RX~ug6zN1luc3-`>4cVJ0R*In-UR7Yz<{Ae1O*ZS=^dnm76=e( zNC@}md+$5*pSh!a$_z94u(S75*0Y|q3|Bc1=<}2;EQ2*5=ym6stN8J=&D9P|RrIF= zXXSC#LGCE@+54Z>%9Kp5OH_qVROK&&Pxi;Q*Okw-9uOr@o$p1>dXB>xTMtUnZ>S2? z@%*L0z!|nS+}PRx+#8<&Z=}wMpKgwI<-bp|+#Q$-DX9C9qbPJlib6tej+*Jol{9jFs{d zSk^itJNebNGi3?Qp5MJl|83R##Kza2%k8x?|4N5-i^4s6bB_G(;=ox%M)&0MF3d{m zUcy;HjUDw~f@Ua}+D6-XF#pvy{%bDlxrH!hzysmnUZ&ZhX0nReYQ8~qv77Sn8+A_R z6oCK&yhv6DrT9}?7uC6*aG@=)jBo|z&^*1qztN%DQ_$i-hn{En>;>aAgxJ^wO^o>3 zd)wfCT!=<&vzzpJBv`3kIh$1F;_x#R1s4sTgA*m<;FezqY&MRdwI5Yc`XY+~N~yg~ ztDrAQoA>QcIg^djwi0!={e<6Z_#w|ct=TE=$m%sKKtk;1|0H4mHu=AjuoriE8ccX` zoR=pnpOA#tTT^v=wHt$OL)rsxr#B^Uo4(FUwU6e#UyHyq7yA@9hZHyJ+|wngGl8H| zdKjlp#5Oq38iV$9*mIC9qSP{hxKU4CF>sFzqO#b@I3l(t7WM`4QSS|0yNEK&mia-6 z8mS`EcB_zav;AV6ttSar6y4{6nVtI=kZA5t?B3pK-hck!=isauT7&+gH6Y~v&*N8~ zV5!&N!CEl8T|2{JCikzM^w**h>EkXdF)d=Z*`PZqIIrWfEE)BaC)WSo44eMKg1ncO zW|4g`!4ruM@erNg9n@Y3LWvzJw5VX@*OI%1+Az-#f|2C8gQ!x4bGve^{Plf5PFKS#9W%8{*3XLiGD9)4ekYcqh~pv_QXys;&^KY7$~jx1gAK{lcW z)8`mNW~u9*D;Cj<5Ux4}8_vv$We&8<>pj?c);vM&kSH&3Or1$btCR~FjWY2GBkN0X zYJa>pwCY8z=KSQLGCY)E|B)2yEt(6({v)%V-x<}2n)^AL_N^MpaCO*)z^EFyaVf^(9La46waL& z2dUH*Io$|kei^m}?B>MivDIZ90mm{^G9#&qQDlHWV2lg$H~pS~nC1*nd={wghaP6y zzhHl_rIRxwnfQJU8{7Q4-WhF{s7dN4%Vg-}M_*%Bmr1$ZG^^&{S}=UUD=f;zD|I4C zJ_y?rEjqSX=wxNtrx!|vhJBeM*DxNn1741VR(-M1rEG1_gH+dkKynrys9q@k`+Jk4 zLyFLZ;aKWDXpI{pJf|@To1Qc;bxdW`bEG9{N>l~UQn#kE{v*O?l%?1yHVbV$?KYEs z7Q~C|9WKg|(nUF>abo?Y59K8faCOlL>~Ik!xDz3h3&na3s9`jw2h+v*nLy*Lg=z3z z5lH*YTZDZ0ON;RNXf3AfCB$?b*#~~hHt@YWKIy}JWy>kgKCbSW?h)$CUx?#pw>W%& zwgCH@Mf!NiUqASRxkltxEyypn)L`hcA62WfIBkl`=2L0%Lf1&`saVc zO6fQBvQ>OIq?~zsyj=Bgr<|`Xr)9fX9uaAU?%y7Y;A;-D&6TP*@w6`Gur58^upl+N zK$PITICfyc^J*tz{C1xhF5A5-tOS9!%^*VAN2Y&IN_P zny?EyClnwAn>?Qwq0&truP!bF>Ots_->smTkN4+6NuAE=hI-&SKh>75{TYqAQl%^} z&)oiEknqenz07h4(vTV^(*;7E2dG?8xa<656+yDF^Aak>3R>;sqXd;IN zi!5J~b>PvMSUA+xBK5rJESY1esB?R!fa?R7WYW?VGs#0Cy@xbL8xL`i=EeuFWX)6> zCgW&DB52g`<$^5F?@wY$8`?HK;ohi>BdeH^eU&VQ9gD1FOHgIog?bZeV}>T6$an59 z{{N04%syZKKZ5@gx0D>AK!g7|rC4F~^RTr-N;W1>PaT6bL?s)ABUmd8zw(t2)fvgk z8_OnE1Q4fgDeGbSm_VxnuCNU$&{-xR@S@)@hSO&!&m29~B*g(4OF<8#3eddj51@wq z2R1Jd=p4Rlp}`h~X#>+y7B+A;q{2JkMHRhsxO5gg)V1=-!MpE6@>)D`6&JfCwmmscOiG4N53^l(VR-IH7uo- zTu%x_cW_`>CY!|}B2k|6j(u?6h-uiNefY9$`jg{@+xAQH%?SVbQ6IN{)6^lfb>Wy1 zwD9VRRn8iq`~Tmpz5h`2Dd)h;|KGg{I7uW5DVuY>tN+{2>ECtw4~l>dpgxD`m6z zH|l))3Sk#jo8_0Uu7x-8y(4oR$BJ)sgY69=b8+#-E6f$g1s%H<;cH1!dlS~{YOqx8 zL#OhQ!Q^5;aKJ@L%B{XU+4j#vvKdb~AAJ1R?M!odx+18cJ``nUmBgDXqLb2+r(+nY z?dT6~uev`HKMrj1UHqRx`@hWp$DsYgqtG5{K)xtjyK%>e7Q9*{F{?1$#fNKC2zGTH7}rzm6YdSY1LS}AJ|D$_Q> z=4p?~on__xBOCdLH{>Fr5`HJMO1GpUd40%HKt9t$^Kp+(3M$gEUCI1Gp2*m39ll;4 zSiO&wxnfGyeSYw3g%I9lF!*)rtQ40IjCZ-0iD%yN29fEkDGEG}em$eG&CG6jBNssg zYCt%Q7pFnmMR<`gK^RlrqM485cX0E{UZy=+WI*5YkmmKVU{Pq2nh0fxEz=f?(%@kU z-V}Ni*5TIm<*XqHcw=%nn@&U&-=hSctYX|8yfH4v3! z5lu3JIBBRnb%zvC4+}iJphiw%O*BTbJ{cW-+Kh{!&9H9DoXBy|N+b5uRyiTHtir1^ z2dbrMQ^hmB62uz(e)2SgE6W8}<;}6U3PlFHV&SNl9^WCH>@l3_`;ff-tIQK_~kow{XR5Cqx-8l7?t0br>+d zyik1&0jXe&J##bTD;oyu0|lURm|R#SD3B&JN%QmisI|U4grJH!XQ%M;aTr$f^3UPC{ZJ=Q6if?zmm_R^WaPJ(sZRGPaHtXG_1li-nh%NZ*y6enS zRT^YgZ`^&i0)7(1SGw!A2l+_CEVJQl$J zT;d2^Y4qCNDpgRKoy2|}?!yabi^vsm6z!<_8{XzVMw2s4YY zI&HIbh$d{M=f7PQ=U?f&vMhlU%rOtn$+58{#4D@q|J%Jj*!EYK{EZ!1zQb}5chJud zF7tCAXZb?92lyM?8ahJP7VaeZk^_4p=jU`eQE<-O;f6Tby@;+ug>RM^nnsHT00LmI zY|T_2-eig=v|G#sPJnVr$8xe~z3hlNO65p0eeHdWRm+LyY0hBN?x&vZR#SELdG6R-HmtnEJvoQrzQY9xl$m)06F|ZCt_a0 zYfLygzkh$9Nw|EOf!p%ds8$K;^0~TV`hXUv8)p*jbQ?G;b&rX?& ztU`aqoCi3%rS=*2%MVogpQt4?F9|YqhRd%k9pLTq`BrwLUfRKb$lu0oMMUY*#HMkG zmghaF_RsIL@+POhsh{`a_k~V9WS$cpya4vJ_W_GLu_uT%UV$gO%I|R9O=v$ zXUZrE8L-p}#lr#V&k7X`z0B<~tPEFTbg{T!VOqAePzOO@19K)E2As&r$FyBGTCTf_ zFEZw<<*jV;36IYTX=VyC2r=JSe^}d={tx3`>E=fxh7sFmX?+>#LVYtlJZNnNN_;yuw!o^ z9bNYiGE}S8VSR-O8QQxgQMV~w6uMgvC|WWL-haugoFP>wtB)E8YMWuAjT+1#`?n~! zAKmxF^SkQ8-wy(6|moy3?TN%T+kV%k}oq{9RPXP1bdiFYF4Utr0a zT@#HlLEFP#t)1NaK~{&)zn9XSGpHp5 zJhlgx730?FKH?doWHw{vN(f=ntQ^v*^x{2S9_;hv?`Kk@p)2vsY6R=clN3)mCKBbl}j;tv*nm*R{r}fRzorK z42(#?T<+_mXXe3}fvFsi1id$>r82(acZ50|9v2^kv{0-BI?7gTIA6ZnC;kO6K z*TZz)Kx5R=^^%n30^z!=OFAnV5W~&a*EMEBsLv|~*|1H31iYUOw7-dR2?Up61`X&` z@rzZp*a%dyLZeCt=7NhTKbXpJ`8j$2eK^C%>rA*g){kVXX1fO576lA>c$#JzXxHn?=2-!S>~~QLHlTl)L+;ZcO62UfuoaW(v#K0&-S$$rFlT3o>sMI$R2 zBUKzJCRR#Zt6w-*zHq87(A7VuG5+*QY^LKn!In1A>~f$PZQ%0=`QtB~DhqUV&!db! zsfx`MTqiuC4SW_M?;at)h|Y6R$)VL-R$?-KR!FO-`y#qehNqvbYn2y0AbF`pchNRk zDvf{p9_WGqvZn}_RZ`w}1n!!YWLTvabZv0q?T4G&USEwxyJpR%h2_V!IiHA#a1)kvLj!omu+XT2(zCDm0e$& zz^+|iI0AqOCe^Qx?mYe+^nlI(;?B}BJ{tf!-ds5NSNvss7uQ?Z%lJ3b%6+?)$))t! z&5f&}&2kK@$@D4TOKoHrJlX{6i>K9aZFYqN?OyhnZGHP``?RV>M>n_E>~ijl3CbRf z!SNYgo8Xk9dft@K#lKR9TDpq}t;0o5%Fx+n2aH!iUM0WkhuFs1W^3YUQdo*zfa1L*#xZJ20@^KM}uN5;wZV=yPzcE&}GcbY14(Sa+8?N@a#f;t_-m0 z{uA9!Xv!otJjH3;7vVon77#POBP6YhCXTLw;+O$dG#bs@`2EoPEn)4_JB*os zth^nULW^2cZpE$`n(wXi?PZ>jHvf5nFzsK(tnT6VBHH%8{-q+6C%&Coi|8{==o>%m zlhK8BtKCD}W+C9>y*nuH*-c3M=r^0)_c!y{&(hh!q!<_A10eef4nMA~z7*J}u1R{| zt3om4#hI2A++ROVRT0@|E*L)ae1wVe%9N@@>7#aQoJ4HtHMSP6aotq>YnF5OlRtW3 zT^*B4eWS>BIMrro+8oG(n`B@7#P@B;Oy| ze5Qk^xr2Hv9X;4}SLC(}NUF|v?B;naW{wzd=LS0!%H-*i!wvcS$*CrI5yGlDeThLcD1+_DE=e>;woLt{A9Q&v_lOe z3g8_ZE|BmBJ5jjLv6L1%#+(r#g18wFg__8a+z@3*#==9Zt`WB4SO3N3ye2 zTYkm^IR!)dzQaPNb$0&veVMo>5p^%|`=+fKD*NrtZ$;pqH~pI(8LU61I0t(PPG+5Z z?R1o)Q>{07UM653(oU2%>8Y_1N}Fpi6AByQu^>K*89Uhx!oK{&t2!T{ z{(YJ?1sPi&nl{%fL1W%{cdkCi`n$jQim+M1v${{&sqyv1$*-6-`}Kzp;81}Qpe?dc~GafS;O-Tz77k4meg5A{rXnx36S}~& zKPF!x0vpBW_7+!{viF$jpJr$%HNWYBP3TA)1(%(e4zD%9luA6^FJ;389c~7Pruy&9 zs?SVJ>bBRZGh9xnX2|AFDI#h|Sy3`9L7^I^Zs3iniT2(13%@VKj}u#-+=%{m#YeOF zn^Ci+6UI0|R~`y*x{i7uY|;i4u3+QndFdZ%Zn!<<*43Q5FT-692-EQA*@ z{eq>%kxbn;FG${BK}#t}wE)Ayv^{!@>S$>3&`H6=H+ziY0^$mx(YiaFo<)>C<%+Ik z)9}{_<$yt;Ch*2se~W`jUAr_9#*@Icivqm=@QwJO4Yhp2&L`f%L@dr1JY0VZM-FJf zId5|*>zhd;>^vuYFV&S!X{_YzLr&>q_J5>=uh>RJc$7JcXegO^C?<73zp!1ifE6~m z+}1r7!>O7q1zASWSSn}(QcokQ;wB(sY`N>oxsrF(eZlkE*c)OsK2GXs*|V76vH=dT+0{XU!JwxiUo+hmK zQ4ytksg`sXCedMRU3?AHHC%>khn3+}TmE|<1j!m)|1*&_@5f=mQcpDiVZ!qV@30Hw zVFAh&AyPl-VWndAAHTW54qiHh*xXQl3EAcGQaT{x)J%$X>1IHNqXQxZdrIk*6#nLS8Q_JaSiPHL8HWOb^)zx*c-ngWQP6SxgU(>{{5geg19x znvg$steyvQWYFLQ7fe^{KDum~^b%oejxq#M-WacGGFVnad}DI*3#E$74nP(Lfd4t# zA?Vqm0RW5<8}wl-y5RhPPK0uJsR1T1C-5teI5QuPo18=pd98+lu00lo|Ab$+*BT5@ zHFK>|!^|Js1A$czC%JictjNQ`68hdYs& zZuoWMUaVh5V5Y+1x&-bsw>t8mO{#jqI{eo+zW5y@WmU{c{}E|Wb{ddXau=L3zh^7{iLw8ucRj@?xz^` z>jJ?c8Uf98^%FHIU0~C}lW3~@ztvWTZ95gsGzt%9Scv}ZDkWy115N;H zlo|jNP;F33m$~*lA~*Qf^_X?S!-)c#8Kow!OR%l7B|;)r-ap+}5MpEeJ%D~C;Rr{% zorPMfO~2+(K6SJyPuK|2Dw_8FTUWm!Vs^5}Ovn@J;c?=}Fn)Y&(M|>nN0iJqe>xgu z9-3>=M@fuVa=@Oq1s@LkTXe7Qw*S7Lzv~U!Gf(9(F6R@0noxUXRDMpwb9;gd-JO*r%;Lf|h+AOGN=(DkPI#Qv& zhFYN9DUU)}Vc;d$rv5B}Zj$ej#t4_6-kv>RuMJht--5ffii>Zge=ptMW$aVTbUdQ! zR;=FASe34_qeDn++7eW-?dR%|c#jzQ2Z{aT@x_-E!NT^w=fsVb?NacUor;kURJTah zktW93O4PsAm_2?!qGoxyza!(|Ina+Z#fLlcY)AZ31^7WMG_JAToNAOpZh1RSm1 zR%RkL-hbl=Hx})|1EiSjD~6sZ+W^qKy~Ec`h|SZWoF|gHF!m4|QRlJuX-7(t7D_{D zWw((X>5YYZaA}}fC!=ct4 zQ*=2~)(`!jg5{<`e7LLYGf(8;+AJ4`zS^5(^wal-QYF`2!Oe~0v&YYl72fv^)B=&z zjrM6HxrYFD2CftkJm2_DgVl$aihO2#A-~d~t+s7#?8(qRS z>Fv7J)&HvK4GB^<;(_X|9f`9HKRD}-wF?EW4XNtWxS|AEc7oa#p8Zfh;2n@eE4x>e zHzGDq$bhb{NxBOcM_bGd`Sw&1C1ivUHfgigy{>wT<;sFX$3k; zI7?1ejP7FRB~YrFq1GjL>n#*MSqSwYbR<%fbPpU$(%PP8Aq2rip~VYsO5c!HPziv) zvtJ*VcE$Gg4)mN49BcvbudC%<$+O{c zwk*xRxyr zg8iE<$s!=s%c+Ar?I!M)n02}D4IUxs7X1p_b$LN^U!3UA9}_~YpRLW`;+l3#d5oSA zqDT9qAiIN)k&DNVtIsrV8DRfJh1|>4Bryo0#^U{NO;!U0{U{MuNEYvkvIs-MXOdyREiorocGn0j|{|d|*`* zFt^^>C+$cAk-*vUCD^?3npMS1i+;eCdWJOmi?3xtxHrcf6U3<>#p*PJ_xU!CJ++zL zey)C?IfC~-=6F;pBZU+nfCG@hv)RQqc!*bP+UjBXO0tF+FOgl*adO(Tf;=b{430~e zrS>y6ZY`;&)peVix9E#42GczyI&(dFotC6q2lqqy*-nT7%pQ<4-I{B+N3#j(Xty@7 z^AA(H@3FOY98j&p>LUH}{mfKdB!(c{bX4x6VM2T*9Km9&@J`!b7`NdYlFuoObX{s2 za06J5XY=iM*OJW%VKy!fwRuW7FM##e5RDMe7r^OUF(i^pN23N zTGE^Cox3!8n4J`(j?L@cPfW&V#1*iJdPAv@!wMK#l?r&lh z0bJ}CK1Va4zMON6!R&)3ImBM5V-WNL~@lIF5VBYe>#|zQB z7IQmm+I!^{qH1B;DGH>BS3;)>sbUWn>@oAslTxYIg2>_Zs#M*6?SRskh;57B zQDKUU`UDc<{-nrLUCa;>uD)gX@gy~p<~u96DCfAT7IHHaSM#G!IsmVWq<|ywxb?^u z6`7pC)XQi2jucuKv{09F96Z{tk>e~y#Z1h+oKcr44l&>A2^6luabJPi9P3*+%15M- z=4C)Fp)o`?_%7St+CoMr(+7>vx_?7lyM0)YttZmh@`UN`akIx~ty5N$0k^$fgCr** zA2X*;qnQ1sSuHc-S%jy9xZPZ2+s3la#=p)?*w8@3j)V%lfAB~vRBKiXhWWUV<%$WS z>CNOs9lWwD_zyP&AR)axX45%x^$EH^+LR?`)ydK482FSIcUp<6(QTJCywMmvnh7~_ zg^jZnJ0s=N%I`mAE6D)}uy^$^K=%tf5~+nea=81zQ1mQzA+yVi4SK_GPMh~^YIL`B zK*Ud^mY_32^!W=(ISP+3J2?zZwe5B+q9hdq(}i))ScgbNi%P+>&+nsD0KT6RRFhtf z(@yb5jQXp4fSto7-7PbpBx0*b``N3G-FT;WpB?(e+cL{e_r`k$$7|nj2%8M|1wsc# z6!O2(jg*=_hF&&{RjT(oc{db1tk8MeoU`4_2pLEQVpSaB$6$VKH6ZVB#!d#2N z)9aF(W@eoh;Vk9LYU=$p4eR zg`@7r2wvN zom1x<^!m%wpeAjRb~pp1{Sf&p?I1$p2I`JqF_`vnMZaSR?528Kd;GxBdWlpOhS7MsZ}i8E`!LO6WG!It<<|TI2GTeo=00H zQ8#iV>x#luc0slz;%pyl|9>4|sv38Ue-Ew>A0Bx9f+9!2()#e~FrS?U|5`oy^wIc8 zdn*m6PS|{M%&Y39Fw)L&W#0vIEr0x}w6pb8Ogo^jn0ucx;43gc}Z}Bl7SFbNmT5NIu5}j_fxYHn9 z+)VSgPxk~1?qQyW2m5n3(uMv%;nJ&-Z zNn(=;2L=&5-lLa^sk!eu$hP&Q#IBoHuP6uD&M;USwrB{A{d>#n41i6(;Q=op#MxRa zVh`=60i{j?9dVl~8&qp~cOSJ$qlwyM+Q+GT0j$rDzXoQX9CmNi)~QjbiSjG)8wMf& zK`Jcz;loR5VS$V5!^}{v1idY+{Es?>;_WL+b24*vo0XR`qPQ}_ng;o=1g=L{`3KY0Bn6+wEOo-5X zJ&E^#Ov|{!vah*NmWXGedChFDh1{}%*oMfoEwg|^t^>crf5~5!xZk1u*)9})z zbNz#Aqcfc#1e`C)a{#G|Zo_l%)U~?Z`)LK@94oEeUbpnor%n=qo30h>MURgZgRUC- zOQg=8T2=zGY2$MHT@cUB?d;@&pvj_`hz-qyDu0^u1 zR$BWv+h^TFE%4+O^X#-z9+(5-+#o+vn&6JQWYq0k2>evqG=LilhH@K1V!mQQzVj3;sY`? z@H6`N@ z3OO3}nHL-vI*3azGp2_YA_Jg^vOisqqsVWjjM%>&_5O9;9d-&Y!460RQVOku16y=E zdf^4%#N&^P4A7wx?>MfOsfJhIx1C52xnjzgegnQc3Z!6}>4_77Cv&KVF$5T9@o;>} zP2-YW&+jLCSB?uebY{f zU`sV?_P!oQO8N;L#=&suuD1Q1$o4MWKkEVDiRzYDhyQY2k>_ZZS$-2#cn>YVnIGIV z=AsZr5N5he{UL9IC1d_xY0kN8FzUl6OZn1Nz6c&4nxyf1T2I{BpKSf^v(_s**7`VJ z-sCwS6ge$uL}$e#9XDD0g6f8QsnK4Lb9~CYK^Q<6jUGXjsJA1XUj7!2wo#5z4twe$ z5G7*}B0bnGE$N>B?7UYv^W zB(3oiL;jkuQ#sG)obz=y&nyKSYf|LKdr$EQjKAw(u*dVbSYxHo1t%R&X#>z$eBZ+RSN=RcumGV^5^gLAU}Wj9hl}&yjuTJf z|3y*6?G`_`$s}Dpn%9g|C>&O8laYLt%9uUc@Esx(P;g+Y+|7*k=Yu|3VG2-!7n_~F zRu!tIRt6)3D~6q`ht7^C1huny_UWO&Ia^ukO#Q>t=0w zlogLDrBF>F=wz`)#Xwmu?E|!_JLg4)_{v^)p<&>0h%jAt{1p*7pOPTTvd2NDpRdXA z<3coC(fs){MSsfGJf)q+Tv^h(ePdl(BS)B9u|Jq1P?aEYe9oD_fP6u3b3i90p4(bD zVGEL4HD6;Ad*(Tu6(5J5ef#?<`FMKw{2OdaFHh!%#iMOK=P9RvK+4vNQl@YkDUbQf zF7x8QBAn?s3G;Q!0u#(sqWeT0Kah320Bkyvxj_eo{T2*F72!ze_Tf38D;$RjM`QI3 z*UW6ll@E&;p?a;I^T)!#ut%~CCr-T)hDt1A*w+yLFF38#IafN%y$49w)^f#tTMUDJ zE~ZiCk-AF<@8LWMp~Sn?1b9BikXzPYe4_`FKnT>*u0MQIg9GIzYCicP()GsjTI3*7 zm+BHJT50)gvQ2rm8UG@}icoqPO3J#kSz`&3zFw*wHc*om6*H7N6D8$8?vW*ZSfBid zVerqHko?_jq08whH0g$^*mZ`ab%sgmv9|rZkEfrk@&~Ij8?hC!bc=LsRGP3ccGLlj zWLe4<3YYcTXfn_}VN~s;{qB|J)J7-`{yiiqA(#d&gg<71JzBko@p1)ztMiO)YX{X^yq4}F84-6Wp(L(eh~De9*W zuhPkX@thxN8+gAvY$0Aec2@1(>Hs?Z?A*V7DmIvOAnIfLGkauE(rv8)PLVbw@Ax!& zylJ?SKj$dgnzEFo-1@@nY=Y0o-$sn()TIr9#63kl8s595sHN5)5^4C>yLj?jY>ybL2K_Gug_sn3>G$WA**ZzU)k_Jb+=7e3K zeuGW;T6t&5#;iMmkNgen#(Dan(+YXSxVX8eByr>~?!OYARCCgnFMEK9P&Dc_tAN&< zAH|6+Zp>MCa<_UFCmvBOb|(arNBp}$+_2I2*V`NPXG8pQO^l?&n*i&B-)urwoO!Q$ z7*g5>`cobAwK=ix=pFC+3t3)+Z#Vef9xo%}DiYtvq*nN;rOF!>LyRkpJe?T6gds5c!$yLz+&5#LI>D<3}R2m z=w%Dlbj@ro-2YdrlO`Xxt+n55#pQKpb42y8T3QhO#e;X__(47~uaS6(?L%1UAM_05F7$V@v{+RSZRdjW$N zTLEtcUh;>xs(ptUr&lO9Ybzc0B7t|FUIqj9igPKpp{D=*p59EDzPhv>c6icjp={jp zqGnI>>R$oQp{8YGpZ&=s&-^DFtU44(F0^J(I5+(MKrNCCjQwOnPd)-c<%@Z{uwQXp zn-{Q5?C|LuF{~2-g;8^x#_9QRTD$UI?3Ik*{5=(k4qxz{uEkEY8Z>-UXoQGvQvfy} zPv=U#MUSRVJrg>&M%w0rFaUDT<##@3_vjHP;aQJ!psp|e$DI>mq{H_gok*smx+|cS z;jQfGdih+(z9qViV|@932Ys%tGZQwi$HLJCGw{KL6OdR}u34PcaV$`n7vK`PUOczy zPwY39Kr9+&ZhUUYwCMKgo+tllqlUxm1tLGO_hP1N;`*aZP~v3A@$hQKcjg|-kIQzt zECW45HGZUws_Vs-#_JZw&32e^V4H^OH4i12OSK^2)89NZUD_xnod-TkdpPdgrf%@{;LBUw(=`149Th`Y3tEoeO^#lyMQ8 z71pr1Vv0YnO!+4auN3R90gxj%pHxHfVFA|i65>EPAgKEP9 z2#yjr1;BZ_D>ozUGQLY*xulvmuPkJMukdI>HqJI$Y|phF!Txm)NjSxsTrcig|G%a1oo;*% z@aMI%+)4th3`7swIT5jfSBm!xYb^r5RBi=1TP|KXo5p+mk8K|C{6GI^I93Zg}&%dM>Bb_L$Jz7o^h;^G%h&$caoIW5=HN&GlZfQ%MDSPtC?~)1pu- zQi&60Nw~_aRgN9%*)*0iZrCrHM5GZloNHNA;}EA@1f3bCJlsKN!Y_Nz(&L@%4VU#J z%+QCbZW?iuB`Bx&)c{lm39uONU#HR zE)KCRU)PQ&jWdD7&orB?Lr?H(-pofg`P5%#V+qWml{T}{r&ny{b zlp5l^ghpN${M8ghjgxXIx_t+_E`>-+u|bv$RgT^lfW#mE_C%)HQasy_%TxN~QfmPb zfCkA|)kA$@B62bndtYy=SVm5i=@>T86bbvTyBb7Fmsr+Zu14n(<8+Ezha_eKd^TrRZF0z_7;`ngRo*iq$V{-Y#FQG;v* zRtaz{;WA&TdZHk{d}ROW=5D0I3MfW_kK;sFe!Nya?e^BS%)R zOf=t6K|lz8T_FQAYA?*so3sno_8+F=p5WKZ^csl-*v%$$v@_oc`(?Quno@8B)0K7E zXHM%x^Oq{g-x@hCE4)Blg3C{mP=9e}G0TH5qt<@jkfBv!?FRvnrJYx9nQ>vMvwLCq zx6dV3JQ?F!?r(Vd{skjM3s&gAJT$RvE;*!p7cpmQfPghrAa=Q&z~>ZG6MmFq7iC&T z+E4_yG*{JjEkz(^$*Si?>pRB3TM|Lw-MjXo?$nF|E{UPXK^F7p`$+hF@Oi0}?~a(O z+eG;RxAM8zzs4)l694?ic!jx-Gw`a4KkOfwJK2RqucEAyTQwnn0A5prw@(S7{|If- zpI_DWf7-_IqWj9m)2B0ctALG_x^x>4(?Y0Q2(5bN0G%KLpO+*OK}h2E<-Lf4`eZIUw+eyxY_Gfh$Zqoioi7urteR*Kj zJ9H~Qq9}Y8wN;Nh%2Q%mHC4@*cISxmo~c5nn|Gt|v?X|>2mWglCTN!sZT_?aCtR?a zKHn?s^?&!8$jJV)*OdKk%C^9Nr&ju1*|%)8@LJ|AdoOZnhjARW)I{7D%JqpIo8FJ9 zBFpd0;9?f#Gr1Ya3JkV26S6^GL|ndk4rw2_^h3cZCCasQh*5UvT@$FD5(yE9uciO< zA0q4VGNr;j91?4e9vt`n-o=!Kz1kn+$i0Y1FB{bT5fC{Iaf+w3OZ?v9jm|Z&<0xfyfHk@AKNf2J$%buveVV9b( z|K*Rt532CjeQwN)Lre#-5dAzKcaY+eQgd?sGt9_ zbmaFivn4&8i_1#^U3niz(A03gP>+BQMn7s8UYdz$B4??wiw$eCtKnVX6_m62ROQs{IoJ!xcsf$MXQaWuSg%b-6^Bt)pae-MNg0`Wz z|EMso$f&VhCcAbDbA)@B^hti)z4LL@t{}Wec1b zz0)rvE#GId9ya*8!Jo&vB{2%H^DfEONDZ?)Hy3qwHp{<*-~QLhih|-lPF7FJYJ0D6 z>CXRPr<5p>BHoco(d9DRcw%(wzauoLjsNOaB+;jCpyi)x)Z|w`RWc@J>dlYyvpPs!ia1kc7MaQYYs~L z&JnbiH9H^68!pJxT#YfjKkx|>|GV%7Hq&(-m9}!8e^>58*3N0rVq_c&f>~PJ=+8-4UeRbm@Z7 zVmgo7WY)>E!UOHMb0Z73)K^@ypXJM>q*h{fmB9nsrl0OG9s)-0&M_|!Q!X!nBSF)u ze{K?>4D`%hNEQsG;@DC~Ur0WGBFH2PnH_o|LFc0kNs>?++HYnPHwbEK2pAkv>>4Jj zM!KG<(hrG#N{Fsp(;taqTt|uA)97j5oU|XMF9_6SmZs2|Em#r;5lilm=3bE#t5S7> zJ+jz2Rn!$|tu|qnLYeTMc`t6;|FW8XajB8M$6cw$DIH(3V;dllQbIK)f3Uqm700N# zViq5=ZtEdn!gB|gV{3Qcq+ESGHJ;yrYS5&RD2ai)n=i;E1$y}`zp>NJyl`k~#2+H) zqve8$l`ENwEO~?HG6%GnG)my1P07l$?lpM_Crz*);`9oBLNQ)Cq(h3?nau<6Bf-IN zG8;MIpPI8VP;}n{k1q8HJ^4Gv5BU3_4^1sUrs_pwWdIr3g>-Itn}>)F{*zbD0&R4v zU)QtUZEc5C-wKgd%~sxeO}qlwxm z%Rain3*GfBN$CU|7ZOj7X|d(?9zZa}!4bSxJ>R@ug8q877cKuBsjAL1KBl3MIas_>b`vf6vkE(RJYH+sCmZqmd^cHJl%#lf31)Y|o!y_{gA;KP)keR2YB z-9F6@4YD;#!tbtcokC!QqNH6|3=J%9sOWX=hc+T5A5T(nt8<%xAjc3QRyGuF6l!1% zJ851Sm^u4dKe)m62H#dA!XW*K1^nB)9kC#{_ZMHP-pF&jCy~wSoKV61DRqOk_NEt4 zDo=3CtBNxV2r=q@J%*{N3Z1KKnC9yvDe7IDla7Ztem3qHlyD?3>fwGXeTICXXQ_nP zXVuz70aW1wC2pzw;-jv}@nD&u-xg8+6(-C-2`)6nmTxMkldozuXbPQF|7DK+JSY|N zyvUeMh4!sE+aqeNdb97LWqOt`{T~WU2;)$^jIimx)?gb-zL6BLk12D+`5QZj%dd-F zgR7Q`4nZ>pUMuCh9M$&WIf>BVG9c=rB#033~=dF^@ zB?jH;R&uiMMt93U*OEyNi{lRZt3{n}9~8V?xWAu2mnEBSOx$wum>OVh$w=NJj}HV< zyinTZ81XJr(CAt6X~<8n_LV7<4lSJU$cS~vQMQ--wM}i-l{&$`zLl?>nS$rd>oVCm zY-gN4l{Q8Rg^Cd|SX8O7r6%DYlA*42J@xbdhE!(vLxGh?r?ci z7DOZ((&+|006*B;u*`=B-U!VKCgOiKD;@IVJ&zVG!IP|i1>VYM4)O2YRqCapWVoxX z#mBWmah(k%ZYe$b5B!9pN=chpE5D_hea_Irn@i{X`Fj`E8+x?b`QL50+P><`HN444 zvD->mx)-yc6)OGRe$QgCMIQ&3Pn9ZmJW91gixcln zP*n4R>CT2AAp}g)#G9Z&VVq_Z;uN$6tO5rA(>4q9Y7@-5Y|?OXc|rR%=Te1(sjV^< zBM$-t)dHrI>unV?%$~%EiLm~DFFHk?=)txajk+DnCB74}8UjOoVdOGt5+X7=<`?-= zaH%EDYje!>xxeddtLC>gBhZyz{-sRW(v-*@7;5z?sC8WK1($Ra7k9;a!jsD%T)pyG z@jO;5iQb4(h&9r1Ydk z={8!$?W*5(((8<*DS4ojNt0-zGnbJo8XnwjKx;QX@up%g0>8Ui*5rjA1kC*-aI}y} zJ2Yu9H?qq_&&I7)7JwDLK!Og!;TF2HkRD_d9t}Ro6Cw>`ZB9+vJ->vNA3sm}EZmdE zR^7rxsRpm|&+(+lWg-7l{&|J;G6l{bvbZzLD4v`p${O*>Qk=LK(@7P)?>J>RLGyqD z`s!me?e#H2kLKYwGZPI`4tv8T+Vp&9m9{5w$r+WRzylWg_;Bl7k(N@1Q>7e%{8n4Z zgLZLf3fNEaa>bsfQO9-~RM!5wuNO>-?Nqh3;Sr6KN2>O9(KP4@XAQOKzi}}_M6dsJ%*QxsA}`B8oKN_8M~cIVWCmX?&#voUCLH{HUBj|4uC393T0cfgyZw3f?kMgL z_sqLdfu1^rJ;eQ&eOXZy7u}mr9~1ee`jgr$Q;Ur$f_G}7(_0e$esw#}uH)Ip>jD+= z6lGqM8dw7@lzU3kY&7eiv4T#2Lff(zCEGpJB7{@@1A$#RPT>b3m zl0e(VJ=IsR2@bJ#4iyduB>Hxk7_Y0c*O75a#Zha5dZ;r(CHvs9 zXo*YK6arZUxk?pmNENBqHLhQx8n4VzAYqUZR|nbDjBvwz${?X@1XXHIiXd&AF_b?n% z*gRN*pNOw@t%-2a0q%5%3`4IfdcJuC=CwU#=b}5DZR`Z;`7Rf>Dr^e2dhrY)P7wB! zrm5|!UPLEp6jCOwwB_N@SO`+6OO&F)U#fTBB7Xf5ulC~2(mCjys^%oBhfNCx=NyCH zfzN# zTX8iXkFKp~3kDgljXyiY1wW2yA|AH@iELk8g{v11Z-i8lwrbR>wixnd+@V0#a6`l9 z?O5{u!oC0M;c4>C!AuKOmX2l7_O6<8Tf z8t&dFi32du&)=TFgo$#Zt8O4~&QyqkIrYZ2Uq~pH{Pf!rvRlk&& zaP5SxcxWRf!ETvXqXT78Z6C#%ZYd!o_};g*oZRRcU@oFgqiU8DajD(C5Pduu%rzw) zKqgx4AYvgLKtF|IC;u~NZ%}0%I<9`s^^|f+l zdDCw=OEbRvJsCLDrx}5p`8Lg0>cA-LB4kuEx#FrqDB-IRjM|;&)OV#8v{Ox-S^i|A z7MvvYv4DBfJ_Uao+zXvoY4Q|I=dy9I&wn5Q#tDQ@n zvK=29EpzZ!9B6|6n6KL6)1SadFM-!@!Ef=$>JLWCnR)FUuIr%ow6ZCG@iF`>skxv% z7W<9Jgx0gNj-UpSwMIXmtKyz+(qR2fx|M;shji@?O4Iu@&CBuR!3ADSEe6b&Yjm!q ze&0|9}YqPFzxHH{SnYPXnwQGZbkHh@T>wmm~}#K7v#ZyU-Q~Bq<#3_ zi&#Qk(&w9%rN&Ne&^$XdxuG1m%@c562**9R1e+ETPZ#04p!aRF`+w@riwD;qG$}jT zy%m|42g{u{sg;omQE|9l*PrjpCO-TqPU1kS^CgQKjrJt7IPr1tWcYj8$80pp7CYoC zT?&_staPQNAjn8{*aY1X82hb;u>(FV7P59at3-!H6*E&xuJIO$+P@69CwCa1>|xRL zc#>h!p}7@*S|h7vRi;P9t0q}OoG6ufZK@yBnp~J9lUC^cfnBU7`&Vp7&=$$nwnPX> zUXkd9ykYPd-qy0$f9ad5PRzSv1n-NL4mr@O*7>-S4hhlYrXOXYk$H<_iW-5$;DSvG zTYN&y+;>N&jxhb~0Tq>d81Si|=S!WghlPM{9~s1VXE<0L$g(h18pSy@3!QCE#llJD zl3*54?T?N=AfSN!{|_k0zqrylM{drH%K%*_XKMBI+6qfbrrhcfy2qKY@Km@L1y;Gg zAJjg!(JBBd0@G!r4PcZWh(x2^j3BCNF!Pkwm6h$-5 zb5@=bE?QxmTw>Be+ow0WJtcx52mIpx!6jj{mVw`0DcAo{{$AM(Whr8*BfM*p-H4XD z@&Gw#MeZ6!>Pq=ENZ1jbu-I!&lpG5)T+w{p;ohofZ)dk{>1Z(pUwtBaGWS$tE{In0 zqc?}>p4Uy;)5$Rn-;~c{#4c+A<|C7slgcHAG-1M-RoeHNI(4R2Sd{+WOw@A200v(6 zcFt*Vrn!z7D^j-PQ2sgmZ|tA{67JW@|3kRH44M2`WKl@lX(c|jkZ)@4g-Gl5tR8ba zxQWS+;`P`MvyhK7FQR^iuP7?cRL9m)4QX9g_H0Q>ikin7MTEolRk^QAGW`w5at_ zH@So6Rv$?PlLQGHaZiSX&9LmDI&|h%e%h%|I!?e&>vudGx7@-8clH%C4s?UEH@>4~ zTa$L)BHO982_=-sl#N&mg%1b|MIQr86&$oBVyC1vnVVtqGmstDf0ds?eQCdb_A@IC zx+-E8Qm7DAeX>L2xICso*psDyR1mCcG!r>ef3*V`p+9cB@h_=+i|#+9?&q7;56oOt z2y)UX4n-TThjP**P7aJnHk^f6#pJ+lE%~%0^~8|wr(3p;eNjM35g%~X@Y3LNp(y{Yu%4@ zHn5(%N-DVPoI32F>Ef@qgO|9i$8sdK-B302N%@@^a_441UX$Ri;rxSpccFLb#^@bw1cy-c4HttexvP|ux79Ik7JDi#wgEzJK zdWD-bu7REt;a;UvAus@f_kor=c<=-|sO2@9%t1G3NdsMQP4|)<@Yk$e2`)-!99)d3 z&5Zr)&tPqEOVYUy<~`0aKYh-qn96znLWOOuaIGwY-yJJWl{vl7@t~yaAN;zGFFF3?v?B;Nu3xQ;+G-!;^rFi5@qsn&_%~ zHgD#5%B)A|cA$5n=2jOz7m(xgUwOS{pEuj!LX}kyzA}^>^yP9FelS@7 z5a+%{75=^tO9bxDd;mE&N&gu?sWJ_i(5DSmZ*69ZR^v2LBh4y3R1{?UmG^-~=&A5dcoX7Gm=WQw%utm?KIJEA16OGk zEeZ4A27j+Evj}S=f?5E>?@f1G>$z>QL!1%GfTK_Q0`OYsa0b4r6luCJU|s3>RVPa| zAziA;Gi?Vl(-QD`{bbV$3HtjpNan!|5l)|D(~ALnp7n~iGi&D1~cm95-lRmDX)#fNkY>UDiBWiP?iZbSS{BI7+@ ze=_~pY=F5}-|Y-o&i~?Y9*8aHzlGDgJ|q{@;i5UK`+Dn_DHeDFz;_5gCYT6Po58bXN3+l;^W65jHiyfq>}1V;M(Q{Af|9Ny$#wC zew!O>9BLY5Yu6%3dBLW8IY`O-o-pPchiA3SzN@iV`SVbmQ5fKW!$p{4{>)&JU4H%E z+vqdv_p8S$FObUSaUb~^ZiFy3_;a%cUeV4f61R;GmTT>X+#hsB2WoR_!preo%PW=F#31OU^eot$_T1 zO6ccLOozo+S<3sIlko;3+@#|xfXzZOo~y0C4t`#~RY~^=$05S4ZJeD{d2a8_S84kY zr;4QtS3I8BhnsEe9}>2&j6ys!)T&}B^OBIVa?aM63`m+sk2U2aR3O&Omu+~>_@UO^ z?*5Dp<|+`|K#?hy zdNKQ&|EhcJtNyF*F$}*giHjbCuucVY;`}XFiV@6!T+l?WqN4*D7(!Pmv5+|3f`4!1 zxL=}a>Dmrlcu=M)VdI)^rfo6=Gi*fVZc&y57D~Xo3x50=XF+@dDiXwA(&%QM(l`;= zcKp!b&JiwOw^I4n2{meEuuXSu(Xhk zoT~m6@{ks*cIQh5byi)u(+AkI7rkC+#`%-vTQq2!Cr?npDXhwY5+N~q&ry&>X)^EUQHAM4;nqE^Aw$l6bwqBbn zC(eQYBcxjhM-L5j!>mkDng8V4++A6!=m6OqmCtu6`YLf3 zerK~MUz_$5#zwTWLM+TQ!${EEL#KQG%ladz(ag&D^}_Chlo-<(=2s@p^XFYY>I5N- zx7^|iSx_3MEop#l1>0(ZhB=0{AW2LGH65Smk^<}B3K+=%>m|SbbWk&Kc()9u(GtUq zohkH_&8^;Yz_)3@U=x}lur<_G>Z*$rV4W(w-0!4y1XhE^EuW%;hl}qxlabz=UViRc zGakRR9`8(&#JM4@(6U;}kJgj`g1D6dBKh!v>{`^RSLFy}4PpN;h7Wz(1k3yuyf0&n zSF6+<0*RE??8$16`^Jij;TX(}w zsky6T&?Qc*1sc~++bTrpDtcooC_I2KhJi;AW*7PZ4ydJs6Quw`N#XkEg`-U+2@p&)p{?9Hk#_lGw`j>^H=p}pemeL*&LouEVbzE4 z1{JVqKDTBZbeiDTQ&R$%J0ml0T2X*P06%Crp#0tKGNCqG0iuJX>4Mq+-cx3_np-8$ zO(MIZGq-!<@9qBGnR1$F<@=2;9f7}|Pwd>5-|5-aI>BA^Knp8!?hMTxTorC6oa^H} z&DICV++QA3a8wfVBr+XT6;Rz(DBB#|gTpY%H&pVa2!#VE5!K zBS7N#hok3|Y(sIdB^p3~8eS$0BngQ*1Z7)ue;tS9Tn0Q!>TrFa_65Gp(|~2nX@j zK?;*9&F|x|VjBMaRKPx8xyzI_%pa?P#sTPGJCvK`q}B6~oyaqeV8eZlrq`}yZ{w$) zWh1=@u*|bjJf{02elHRDg8JM;wz^@GEOL1ble(yx9g0l>_a|j!@&m;z9)Uz))$jN< zLkhO~KCVmB`2_2o9A4D&BY|e58Vi;O)ZkjjtA%&$81i5uOqOq)fSpL){q{7TXc>s0A2Ia?!TIi#bj^rk1ru^u=)XoRmgO44RWUX;kEHR<`6GxM5R#;-s;N4DW9R`X& z!fMeUTu?g}whDq(N9UT;m|jgP(UMdPwT&`5;A0gI&hys&1-EDTqkXH*u~qLtuq=bM zpnh)q_9|7Wvwy%l%{GX5(m!UdjNXpkC(?^uzv~wv68sGoywQDF`3#l3Ps7Q1W|NV1 z#*eXIZ@Vmn>4n%hTI-~a)+<$Db4LqH0QFpien+y1$YTe(a7nwrJyXq1AA@?(a`ncK zNl;GQ&u&r~=oDp(k`GAZ(cREljyuHDGe7hJMV=jD*OT_EZu8YH=)qq=997^?fQy`$ z`k?2ARA##@7SC1Q`a{MmV2(xhrKg`)5%%7mBHJIaJ)rhv2P0gS@yZ;Hd*ALoB{X$=Y&Wr=wiqYb^8EWY_9t9%>tpWjdGz+p$C>qLMK8El z^G;2edBFX@Z&-82sj#m_{MnI7h~i`RBl4ANtOepj)_Vqr0xC9Rc0AOxyIi7jaKrB`*&p!RK65NS>b*PdScav*s6ZH!XD z74F?;IZI4%D{ZvDwlKp%LbHLjX3DjCXvf6QqEbKWZjO1UQ#7(0G3^EZwywzU+jN-S zvoofU`E+#i_8GI~Ik7|jJPr>_kRN3B)!m2tf)H-#lBhd2g7Yfl-m_1=wKopu9fJFq zm=Y%wP7YQUV=R*59K>HAox3kbslmSXP6*Hj?s%yL23Ev*?w&~(5TevaTME_dhJ6Jx zgZQ6tC;yUi5e}r+wQP#w8_dUxtk>M)45XKC0|KMI{g@U$jnK$>q&M5H&eIEzwd3JW zandOiO9X5u%0qh|QQgw35cFtH8bMpu8S}HM3Yn_4j0@oqeI~XGo&&M2YFkgyX;()o zLpW_>vyh!)9J;^;4v8u@%iNc`r>+XneMnnGcE=xs2>AsQQ(JJg&AJH2W0{-NLT)!99QVw zwg1R*tK?n2)l6FDw2IZBpWk*bs@>Meh~AFjuc>ogJ)Sw;-N_wAF_7rV|jfR5`fs{M! zJz#?x`y4z!5*x!&b&WfCBk(79GjoqdP$>uisdYzThuL#H`PV*57_ zrXPO!+ybYU@qcAEIf97g9PNwlW@R>#$uCE%T|_kqC8&BLm2d<1zD0h^R=*z_GzSc;wE;j^-J@?^ zp7<0Xry72uw-~#)Yo3UH`h5wu2Xjj?Qs1lJUu^>Ue3#sy$mTeae0Vq3*AlJ0v|bC9 ze{>4n_3F{}XP<+M-{X89>dwx{=$73L8d+aiWEIilVn2TUzEp zCBqMdAZ?nPuZnl21sf&It5w$wWw}0nAlG?#;fuo|V9TXdiwpWmvyZ=ARflglS5S=FXEc+zlX{Lkif+s_pEMN0?0Y zbs18TR&lHA?$8#(@q0<$2<(77dT#E@gBIe2A3E#ieVjWu`%d~c;J2ok_5$NB{ePK* zrE+9k_TcVKiS(us9As0V2vb5f`=EV?QsV1f-_Hw2WXuQR$!s_-5Y2J4y>=1w$IGUY zRv`_qiCGmE?$$SGhT-*?bXJiIcXbySg}dD4nt(c%y$9Fgc@?}@V3ZuNsOxck;cB8tmJ5wEU>75)axN_ZMS0yg3M*o%aVTQr)4-bp6*QuJ5`(b++wQ=m%% zRuXzleIM0)ddY+n2W}ndhl+i4Ookls<%!ZQMf0&ztIJWXnw2HBOD5pG$w%?bfcso> z?HBWJSn9wolKYi7QG4mdDC+=_#5G+@Crr?8_$rIL?t1-mYon zHi4FJYzhQ_j*jV~|4^PjE}ba&EU$lSqVQTxSyJ16;Z4>=1j~Ga3rch@OP}2zHdRez#)1 zw5oKb?PN7^@bS$f#&$aEK~o5tjBXQy%~B;y1Nhas10L)?aQ)!@Adq`BwyK04oC5g6 z5hE4H47&3v!kn+1T{$#u_(hdqzvFSD`CW3JB-w=dKm<&r66X0y~OUFf9I z#}+OSVE!cPFvIcIjtSWpPg>(8j!Ju;j1o>1_rGj@TOw;y-gei{OVc(Vgv)T(oU<fQz&@Zrzg3~!~Kj%kB}9iSYK@y=d+ zFd%A5hn8905?Fw^>i@q)f!NX!Bx??L^BUklxZv&QjUeww4Zc%;5!iVT;(x`by+@QJ zpY%?}e=zw496uVW!(^;LL<)bha<9 zr5ygyvgdiX9Z}AnWdGVV1?n04B8{8QUZv|EL_KskDQDc_6%aJ%ED3xax5tsE=4h;(IEGN4X_dmKjJ~X^)W_5IX^Jm}<#*iA@-O>_m*=r#}BQOn+W}8DCTTKU&EwzpUl6M{f(Q-OUR$cG^{fL z7|(5);Bdvx1OE{lpE+$rlr7O4cqcj7W zs5iMuqt2|0)D!UHw$ZAe%`uP$?uk`0;Py-mL>Oa&?d?tk8@eI0dvmMTfG&7*Ry4`C zT@mo%XRRleN-uGf`YUa%GbTgeS*P#ycGqh>ceYq z2Pn7tQva1EQ{5)9kJh&+r(K zuk!iycDucw@vB-ZbV+SeOT6C!K(Ez?DldMB)j16E?jr4A+MCE=1mnD?e^KyU9um4# zz4+21zay|URNU>CkEG~kXn$gO-UxxErT#dfGxxyz*9)EI-=PI>vs<-j64z}#x7q4+Y2RqvdY}%bvh6A}d2g5CL;y@YQ_eGr*7`ydt9!!- zI|$gg;Cg=IRc`SL+_PBhf=9NJ$8;{soZZRuqknjROw8$|@h|(3ndkiDZi#mOh;MSK z;A;14vRRjP5%&&)GRK_|?3RZF1qxDk0Los{NXVOvYVzuCg$TWPBY%HUpZQW|1{CWs z^M0u8R&|w}fmGwFIpOoXI=t!2t@{0tTicy%BfoiifsxlAWV)6+;RVpI&sHT72c_^N+asHYO#^WsR`9q!DOur+w*~v?NZqvDO{A3$?smCq<(c6OBI44~~ zuYS%Xk63|fy(2zTzd+i6S#zu=`UFw=7mmutXx5eg)f%{gmm%dJF}sI<$~>5( zYnB+G^$zfsi3;_S9^O7ewNUuJybK_`#N{Lcv5O36RXzdH1!t}7daJAkxu?mAOqC6~ zu49|)vi&7}UoyvBI-KDsH? zP18A72sHZF6ys}q)~J8rNb=?Ur_8i#0&jY$C*u|KTNHk1-Gn;kzDRoHb94CFvjI*0 znrlb!Y#bLb%2EJ4DjfiKu&-3oNxLRfsa?94U=i-!c6{274a#rFJ3Gy86mnMePm6n0 z5frBh*rZtimQZir@l;o=?uxz*I!XH3&zSCrRp3~_LVw?y%5Cc3^~e+SJv)55#UdH_ z7e5NKL#gDlq-Ka}BMY^gdhVz%|>Cx9v2yRK{=Ftw{o#7Z6IhZEscVps@ zjNH3k31oBnv1t_$5ha!eULVci%rK0{0kuHRcYdG70yU(}hT++qQcv zI^_pWsOS`cFsi`qD9yu~6e^0D2h(?6Jc%@Qi?x7?&QT63D^4EyoFqCO^wmSt4s@+w{F(GSIIJlX~X&cGVx&#uwj>W1&m& zh}))#!`HA^h(@q#Q8mSPvZ?A;K04v#R!$hLVJ}yEBGX9%?aBeIT!{Hk`nlcRZWAaW zR{>UFFM9B%D9k~N|Aa*meS=pj34V3IO=jOIMF_P0BHa6GWl3l1s`ho%Tx0-jEM!;xg zGa&?RTPZ7<|CGcj$jkxmZs;7nVxOzvaajU0M2`(9Ntu*j@$NJAC2nip8?ji+0A~ zZ}g*AwFGrps-%yyXJ|*a1p+76A!p_kB;rW_c5KhMaq@yPd6C}yfp<+wxj%Uv2_EeN zgGncz-_T~|tr97a%&qFuqxhU@CJeI{Hlo+6-aAA{NYVvJU&acl33#mIZsBq#8Bb71&; z)namezD+w$3E`S=PZ&EzPhb#@Ttkj_c~J#K-%$Ew5x_s&XDl6_X5-Aa3b^B8mzL^h zN#Hf($s1mLzR}r96L7~)2Jeagg{eq?IuyldMEn0(M_-?his@PzzW4*7dE){9%20Tv7mE$Z0dZuLDnk}0}{1nQOan~ zYY_02#H^ zRM)0$X*oy)N%vb`;|*6r4jZ z1I`cCtoK>P)1+8IS(693VO!gtdI`Ji!`_1dDYCw4Qw>+Tp<7EdnD1Feo@B>v3u%^y z?`x3AaIUPx{6)kzWZ_%@x6C{AKaQ~y5sb3#-Nb>6`2aJ@lj^_0 za6i%n>3q)N>xK9OoDn=XshvLf1a-#3D3ug*CFEu8Fd*$q#lHH}b0F;}MCArAmwg<( z7v3l@$EwTXwJCs{`E;IPRk?FKB|D$p!?J(A0+HT@n zi}vQD$l9TlvQZ98*CF1%Lm_2xAf2S+#W$tAR=V+K9c~1DkP;KT!CBKY@@<6HQGddQ zDPyJ~B&FDP^6giHmE15p%;^jb;=oQg*o8;umA}?qWhuY)SMLleg7pm}cc#l?=BrIM zU9;qr-u(KrIe5yb?q8vj#Hpk7P&V!xJi-O$5^cv9w|SS4qnKktqfAvW5&>x~9*uIc z$TehVIL5Us!1&$*;Wy@s0fZ34<8?q$QnaA9SO7G3{j;$;WoJQUf+R5~0uB^eL<|GT zqnSyOrfmztiN=h6H-KJ-{^upBpS5Z^7-y^_xD-zXOsHCN9c!DKJ#M3x%BS9~5_k9(h^xUeizj_4u%uIcGkq zF=6nWfEa)q32hZm7AC8d`GTx!^$#btP)#`Vf~L_TI@r$G%`Bgu2_UNC*Rt5Ch6gxr zsi~@HhVSc686`8_EF*DvYAK`A1DILJu&Z^2z}PzeW5wb6#+q2UPY!vX(LnyJOJn>g zKTtKk@)717x~;iMlY6o!yt3O4 zyAhprG+-WZ4j+6%R19C6JGm;nd(fVTj%*l4@q=ugrsF3|vF<`A$J=#Jj9#1flVMlS zFMr&fo))Bv!n^94VH5Fh5+-;QIOt0f6zHAwVD!J!V@Y^+VF0&TTP~CX*;uAl?Doq5 zpDG(^pr!Jvup$Gc3*0J-;ua!&qL*XoIxSr2ja^gjvJ_Vc=X6Zog! zHun|N-R4$2PQ4NQOrx;O$sI-T=*nMB+hVbSL4beQ_ngGQ9TREu!?5+D!An6s4Kfzg z$44!}WG19@uTmf32$sz0FB+|FF&V`sHUbRVwfdTm&9@n6PSJ1szYej@bhRzj{2#*J zGpOmT?;pN*Ev%@ZAh3cIrAbGsbVU%PO7Ecb9_fUZRYU}&htLd>7J3N?p#-9I5{mR* z1VfP~p@c|C{y(poYv!7H#(mxfUL;C=Z zy;<;HFsFNA#(SN7qRGbE4cAXNhMwwkRR&)@jZac<xs&8MH7>x3QzkWiF^$3>LNH`7CNHmgE4 z7)t>;$}+&RqJ$>uSgOVaiK=0KVUJF$zT6?9&QpNIvoodA{?;Vq0w2-_QSl&g?1PTn1%lylSLLb zRxL<7H5CR)FBD6ingwIo!+*|qnfa`E$zLLrXBF$jl+PCl@08D0da9H^$AJJ#0&@-| z#y`#wLGyGuWi=o>0GMgi^Ioa z$dIhDj)N|Jg#2gB%CIU=amVbm|07tcHb@TZ2?4|qN?L5ftfi?i| ziB(KHI64d?e;SKJ#-m+U4(EiyhejFYQDK}a#%$*1U)?k@0ooP9%ys!dpBXzX!6aeK^V*?YWe`nH?SGoF{3>5nI;Yfo zgt+s<-_qLK?ylv&y1TXqzyqwJ%z2pSYmz?Pqa6jHbU}u92o8jyoN0fwXiqHQ82oH} z_2>@ad9nzA+<4n-*s7oWvrsjpYd!dJ!9mTrs>e9pc1P=32e_}5wc4yRd#e-Mh{^_^ zpaAE1oZ`!&$j%;yO%i-)<;u4h;tRGhItTngX$&-HMm5T%zdDRsy}EJ^3j-Cke;+;JVo z_a=8zQ&iZ0oWFH^p>a{Jz(wK5#URQ~_9F^G>q{Mmm$^zsc;+Na=c-zLlB!>*v`N%k z*93djxrl;${i7%2Gqwk9Pa^!JC1kMg`M5y#67Z;q^oZ{@akyvol?*Dky`1(8(Z3HQ zDnHN`6j{_GO53Gx&+}$u*DvM?J$Z(A64M3!86x^pt>dyCCM%OiR-ljnk`71BBn5(5 zp&_iJ3cs!j`eYHi8gzBR<2Y3S6CeS9Le9J#C|dORpK zx5Ck-LD{xvgc1Xs`thDgcL8(3cwW7oujeE*y~f+97ms#dbSVbrMOwDHM zvGSXk^&gfFIP=$+ILDajfVRUdOO*HmA}|fI~HES5o<8^NdDV zF6qU!w_A#-xi&#V0rOzS!ANp5e{l0;u0)UO<9&M!Z0UBP6PetrO?KN1rBD`Kw8>=s zUnAC<69Bzvx>wrE{bLi*?z(Zh;`WrJg^^R)h`SG$Lw9=FEay;Hlew*}C^jt{{XLu3 zi{V&jMdP~c`_vNCy049<-@CT9Hp^CIkO%1#03-_9ToN`|32ABlm6_ok75u=Yq%o|(?-V)M5PWUwoz`e zHmlrmK-?V{j6YW}&V##JkS121QDAu%}%@ueu-?GHz*Ze-JW@X@PlSm;W{agK7;mFM@Y{=U>R(j@fI3k0T&EQOo^mm!ZnfZJ*L|$U?SaB~O zPZwY;5f&UBUy-T8ourGTef7G%n!wbitM=G$ze8vbSo!kO7%j|{Wk{`!f=&$-{N8Os zufw|uSf*ODWBhj!XdzjBgNE9^vLMRHzc<0TNK0I- zN&idJ@0P-Cf{8cvU3#5*a~Y9-W@VI5tY6*Br_+Q9NSsrdj#18RPC48_cH7cE8-Sc$ zFpi3OjuX?xrVxk#@x;5cef}i$U?;%-vL7=VP(Z|L0QHH?0?IbNUHNM){d20Gd0c}m zIv*C0`76tC7rqelH!2WZW_)bhGPoPOCC*mBRSQ1XG8YFafbT!10KlRkUc$MF3c8;L z`XQn_(RF28*`{}t+&E?yOHEEyrypcSv?TVQt+LB)$;~Q{lw0CvE&Nu8%Oi$Re9vF( zx$gh?~ zrLHDTiGb}tzx{I?D9|Fd8@@E(1QrvWfw)KVyo8#}LW!m64@_Rv@q+7zY?%YViGXxu z?49<*?-+slo{x2Yoo4ZmDw^L52@5p*v>Fma;c|0-Su)&@dn8h#TG*>b3sfY8tIfE7 z-y<@?CA|4hRhQJSuKRwl;hxH-82;T@>Xbn~3FwDKCs(gnbIo>f@36$|GRnni9$gyk zw=gDP)|_34Z;@;BH6Cz;0XOCMLAC46HZ+AO6epTlDi@ch<9O`t9P20q(pmB6r$YIz zXs;|Lj;8P>^9voNg*_N$=j)6{#J#cTy9sgz`^0Ij+%i8lOlSjgk5_Cvz`lV14$|1% zxlHuJYhZrvSM;8&nCHJ#f_mP@3;Kr7-l2vZ)6eddmvm06+f0(roiOojbf6b1LgW8M)HD9diK1(NR1* zPgSd0CzNKYhNkoWr*_S4QMnvH@9(aE(=w)jq=e3F9lb7b1Z$-zh9l;Ggstl zw^(ntIWlqGZfmKr>#|j&cnUEC7$yFK%ZrO_Ic#qpaJw1j)dE~L`A(?TdXzumDDx?P z%^iTJMKamabn4>y#U&Y&~!iq8+@MFK`;OF#D*sBBifF;w@NC^6D$!t|)`C zRRtOxgk;A{oNwd-wKZ>AAzZ$t#CWxb$UBUCuziO>lCm=D&C0#oExAgY)n~#FZ#AHP z+^TB#oeIR!eorHiUQfmV6=mDE+E`)Y=xM}2ZKlfg*7Aax&Z{kC_T(2hvE8F5;q(5v zHXg+C9WI|Qz%S`|!JDw}=0kJ0vc^f&)eSgClCgi=k3lM)tbMdh`U=buY47?u)C1;F1S7WU0@zZTr4TtU7+vqvusU0v_3w#`D z6#j(}GP{mt`TnSP^k+}``{VM>B>uf@zOguzzQ%C0IkE2)+R{2PzxW zIxA%wb#_IMevPkLck@__+6v|~OWzxq_A)o{_K3vE4;IaT ze1NL~GH%-wpc6I_Iqd?nur46KZvuFrF3o$z>QK3aNNE5J?U->NI#;dH>p#4u4T#PL ziqiJ)=cz(Qlz<0hhi6k*+(tFSQCrvT7N>)&QU)vNI{p9*=`VEV*GjAe&8$xm0gt7X zaCy`AQ3X4EMGhRfzwXP2v~P{y-fB%NSISVgSOi}+Il+9|1rIwel3q@wp{F`HhZeg- zuwKmNd(&bDoQrn|v`s+Nb(nyA*|YH7^t10ysHQ6h_Ael#dCBj9;*>JVI&UvOcR{Z> z*5{yyVL!adPk!keYtDjWv6kmSAHO~SQeklG$0+o3G1>vJ{jCRxMgYHWPr`3&r`dcG zV<|>^CO{aKS3Lo6-z1cBC=ZMX_@j^zt3p^JOZIX>H2r7lScdTDr1`|8;=W(&-}nBR zeh#~rTYaJsBIU6Ws)vNqg%-8H={NJWyIAPK=cwB5R5KR4WKL`F_mnRHzB{`DUU7!3 zqe`Y8Q5pN4B+mZvd_S+VCxlJiJcD=hETE174ayHTJ2yV0! zrtD1k2kivuP^H-_(gh_(iF<8#|8)f0-9O!X_t+(coNcNvuz>h%>Q>u~O75C)eQ_#v zLb;M3R5G1{5|2WmAeK!Vr?MzYvQdKA?=P;{$FI&8;!IO z>zh4~zk7J!(wl6*(Qtg;Ad8#~kzR%IT300zAR$J&@akHMZB&hWpTx zZOvYy%XLpM%_sCHhTv1AobURdQHdNdDzUe@|E>1))ds%s_5$IG#^FD6T;mI$-}2r% zjpkaX$Wz2NECsZtNJ%&JLPS}bgU7mBmjrtbBzSd`Y;|^2P}xI}9e?CL`qH+>lw!c= zw|L`F+(04*6I}zbVJo+v7h1c4)KM7D3{kY`4l|;yG z0@=5&?+N_HsGyx*VZy>*yxp_iZ{_bPuOGSl#S?tXJ_#i&W%!n~EEVsB01k!R9QJ)H z@_NmnqqG{%*^ibuh|hE;hXeFvDPS>@NW1<3hYes+gh7(HDMfAa{jUjlQ$PU^00K_~ zEG;DOuNEh=*{_%BxarIL6DJ3oeO|)zRvQwWELEU#i$T+o^z!k#1Jv>jl+g@ypE8;| zCM5CRKgWP@_V)hSz=U&fr`h<6n2c(U);Il;7S9r}jagdsk+9DKm#0o=#iRCYg7eEl z(9^Uot@XW~NNgWGiq&=>pa3hI}bO&{1o}{%jMd<^ynNJ#=c3e{igD z9&bg_Q3na}_YXe|v&F#+UbMo$mNM%PP~d27aMVrtpA$}FLVM(CU*SROZ6nE0g&C>P zn?cK^kJ<8CUMcLapFjRIwKYJu6k_u|3Z10%1)W|)ISbA|skym=8Zq7M+FHk1sbtZBXqw}$*1T@6sjkv)E%e_e29uc*%MFeUnC5b7$5@$ycH-vt9|S2pA2AjM+O zlKIW2b#^p#(gm~$QF3#Gc%}y~8Yvxz&O$0<22|pf(DnUs zYnIhnyAq^qWW?}bf_aQ04!rVH!k6_gzhU`x66(+)7DY({TmTcP;Te`~2ZrparYG-Y1hR{nj37VqcUDYhVR$8NFKiXz~Yr$(?QRyPiR zSKa(L4xf2+Wm4tAn(UIH?1aam<UvO`b?x|=Rr4nbtQlL-dzJWMF3j= z^6Ds|v6V2bs|5Yd?N)(8iB@fUV{Tr^1O4$|-SAge|CgxHIdAD}D%$N!%*s{VD==)$ zV)kvsxj*(B=}v4V%b_KAnMCFvpH}*28t<8N*v{65XS3DK|0rgR+O>0)rboatn2RY@ z*nEP{YC*iqLW-Z!N}^$bIOJ&fKnRQC3?j4bt!y1+-j0Y=1a({H9aCPh*?dJQe^zA$ z@!j}wx*)#5CR<(p=~@ziw43PUid`ZEINlEtw>uSmj3b#HXfdvLuUq0453Ju+hoTJB z|LM0?1_9K?g3SabN@E7VIZ z)!YBs4GEt&0vX)>`--Sa7i638sN!doAXIj^$3#_<<>R=#MqKq+cz?$t=;_&lqKfrV zvYt#>dN9p!=w^Ch8!=y19a18Y$2=&gxI)ST*Hj< z0V`P4L?N$u($$OC3MoWu*#v%0TvOT>tWEgPuOPeY4C#QN9T%IQfX=WzmU)qd59W_U zF;!JNQo^jQ|FkHB(K?l45EdcYV$fxK?--L^Y1+@jlZA(?H?UlPt)CAx?22O?woNu+ zB%Fsg8(^n4871Hn*w;{QuGR2_>c+d=kR`X9DyR4TmN$Hf8e`(+Ws$s*FOH2|zsaVG zg{9UXQ!U<96}gDXFSKPyD1N&h4+%-0Hp+biU6Qcq;xcZbo^l2KY!3hU{bgu^|Gyjt zATKM@HN>O9StJmB`1GIbt966G2kC$8+WrG#_=|AU5%l0YBQEA)v1%#9`G$@Un|h9Q z9(a}m*Bi8^ftH9^cpI_md}~9VY#VYZB8JwlxvmNtTzmsn%XkTuj{>O@UQS8E=0I9@ z)iTRlV`rS>1W=RU)x7T%yR)49*54e~j@`Ux4L#-kZN$##EPeL29Z1BYIg#Eu}jVw061fHO1Rc&ZK-G4PTeBDSG6NG|%>EIXJcf zLaAZ4JP4Vo(o3Ava3U9Z*!e~^Phv&c@iA?5sl@Q8e`wNIV7Ug zlCtM{zP>H$Kc~Swkhvb(w&?CF`9TXHma+h@hlG$dw|Y577sRC-y-fDfT;E)01TADw*}anmQuW|EsB^^mnqTWDhZ$ zXXZwcINj#j6{L7y9`M21m1@E0npx3-i`_r3Y(9IVoSr{^abokI;Askdvk7utRbdQD z3(Xdx+zdV*)BxH7`N|(^^f3(<0dWb>dv=gKI8fcmMwD+x_=ReWNJMZtt#s{jh=_9wU|VbT~oLj2ats=ni6+{*l1f_~RhAQu=Cq)jCPuFNn@GYI)qJ zqgk!o@l^ViOxW|nTT113QA}BbDCOEBDXxK6FA7U2pLIjaO1#CAulCO2?rj zY+-q2HOwx1YTCh3CnQs|Ncsiq!)qo)%%?_-Q$5!JX+|;L$2LS?PHp7tHHLPKrYQN~ zaoP7(AOEqMU5kqn-bPxB^96*Ep4_gl|8hZ;-7D$MPWqlR%5D_8Vf2zSs{mmXY5VHF zUUX>8sy%8*g!2fe!c{y}!FA{vvsR)0-owry3rR`ffJJUS4JbfGta#!qA~#VHdj+|| zCn|s`SCWJzj4jefl`sR!7pJYcn2I*EJ_W?X<7$QrsX*1yA!HbMXNV>K+v|vGG93LJ zZNd2FPkR+X!IZ5YLr(GD(E!8(U}k;LcwC*lT5^{GinO^%$1NbB+MeupsbbDUd*ryS_Bj$E}mq+(eH?pet54&NYhhn@ecQHn?*L64`4^~KH565 z;%V=IEV}-->afZWbP^3o8DuDzgSTf5$sp^^hOl`Br8?`mx71OOZUt$xFp!_MjJ~fv zMtYDL#-Nai!1j5{ycz8d#gc5Q z(g_B0n@iPe3=u1JZw`?Yd5Rw# z{y|%pa%0+StiKs&&=r(xPnQGy3D$jTNpvZq9GDie+>us;p~rFOAr{%{l8BR3qL`Wn zj(e01Gp!@f>M!Wt%ydHV}p}~m{Ea8CrX>M&(@LTaHjo;%m3wfv3UP4zl(DX>GLj}i9^QUwop-~ zMb9b8WBb=vi|uLfjK~%ESi?3aQ)ZveAzN&2;wHQE4_PIye6!gQ;em_oa~aOuGYB;4 zPC&9GkgFbXi!Fk4UEcd1(~Nk!Qt4Jpa!;S4WZIF}wn=+hc2Oz`EtsE|CCWUbvU?5jQO!X9qb|DO}sgabhe_KQd=R$Ou`n<~&y~%Efv)W;Z|Z>c*5T#sMoP()KQApn9zo+ke}Q;bSSpn2Y9;Ju zO6)+i%hc-4cA|nRUHr?ua{))I^DxQ+})NEVaO)m@<)TaS9;bx`u6Z6kA_nG8Um^x zF*Yw4ETnTJUG%3YB=yO%UmnB+x=)~Zy%$l4-d-XsJ*W7nPo--!;Sc{8IKu{tQIVD^ zgH-oDk+~ok%3Ri`7ViD*X(ni;SFQEYeQJ_8D<)I)D7b}?4*4OZA~-sc*SR@MSUa&y zTDSQx!@Tm@x&IOM+m=Kpce^vmf6N=OG0gA`{2aE1=?P}nU+I|)INBl$>AQdFIzBSq z3j=$$jW*hgO@6#L7?{NG+0rqbi&nR_?=-{*T?jsojZ;;ldAEpu9FtkwllU&$MaT7^ zAMiHi@oi?>6FvL_mwMi+@Fr_2f{q`5#@ZFph5>>`sAP!X{l)jpK}xq&lk>LXKltvS zpD%U97^tkgQ^ZuePp=<{FZ7KbOIr3+X6wMW%?-+NfukWdQ>LE7;9ZYt-or5Kz^S8N|VDX^hn~d+iieN?)!{- zFvg^g!xGEXrx)!im?lZ=MpQ)niWzK10i<$spjjF2X&HFpQjH(9|<&f4=`bKP)1udc`Fo z%7Aq!nLq>I;@2H~baxQ6X>0mq!zG z*{@c&rg7pGL3Kkec!d3+{>1qcT$+BzOWcQP#fm3BAUAw5m?Rt>D>Yt7zB#+8cCxBV zIg|TsFonecv_Db!wn53o(T#l>q&QqmPTW}`b%;}My>~QbO$Mc6r?`<4J_enYY3Ruh z%690h`wnWF1&3y*i#7*0lH$CO5hv_z*J^^yx zMI9d0a|k7K+S>)7q*Y;tq-n7WHIR%A@gY7#3tuEMN&9f5q&{ofVY0TkH@(t?v#c?V z!dHDd7^n)t3cTZf@fP)SuDFE};}4FhewW!Yo+fRuv*PA)AoMTswDr@xukEuu_1$_C zy6r%FdJ|VSj-8$hYr)lWG1$^eEF4|ug3F{e+us))&P_aNHUIcohTw1y=-4oj$s`vp z(DBU7h#-@xD7HO^xK)!%EYUNeZgo3>5%>E2-EOO@4SU0DEQ1RitqwYzw8=?sfR5o6 zPYvS*hy-v+44TQ7dCbJuukv_|xvs7hjwR49ls-S4w=Egle5)b(LE?UMo$w zx({W;z#YrLUP?xGxew*g=gVf779W$^nzFOKZ95HMf>v|v^0SI+&P`Oe(Ub3#BlXXM z9*&iou&||rzJlJ>GtF2JwD7r^GqFA=O@b(bvL3g{p7jc)JAB$KgXd)W>N;`(4|T20^Yg!mg~yn z#0p{Mp=aaSTYk2y>af4tReJ>{8{m0OEvYWEPH_a4$M35lS)w&R*@Fta@=YGw62 zLf3(PysA)$3qH~%-nM7$ttXbPy6z$^Hp;(Tus{p9*`O+=H!u-s#ehxms>=PLll`^0 zpx+(J-^trD)tos$QhB4weRt`btN(A#RWFO}H1X2GMeLVb*D4~7-5ylav&Y87|7jfu zWDq)ifJhC}iSW&yYiOLEnIwR8oUwtYmAl;>R<8A_#KazCm(}wqQ8h}<-+0JK$u+rS zz@>rOrcLGm{R*V2qR#DhA{+bn3vU{{f7` z#Lej^2&?-p`FRB&inn5J6_kDDaQ6GkRlYhP5cbSjA{}e!xYasoRbo@`EiHXZ{;XFe z!n;FI=E#>#0uiL$5UE7i)oq2A>5|F>ms$+HA|W=EWw+wrW!mWdzI?qRj6}L9zDPi7 zQ#UsoGl1n{RZQt-aVE-Us{_Ca1rv5W)X-VDGk3xgY3>}gb1hPq(CAeZQRRUXOoO$F z-q3{-w|o;z-nTx+(H3L~^$CrOM44gcso{C|K*~FA%9XveOLJ&QT)fNAtM3R;KreZ_YIehOjJCzq4n4s%=;TX<$q9?dsP?+mOIWX0Y^ zt?W4Kg3}T1WYLVuF#Zh1vwK?dwCBvyKU6#IBhCc2o_*bET*T!z;p*v^z6jE%ODx@H z$XK|OVd|4|8PRN)JzY|k**oR6Q`21&-19Fz%c~-+{j){!n>L^lepkQ?Ldc>f)xW|6m+fLMYB)PX$5f0t_-;uJnbIbs z0ZG`U6$O|LAVZ-h(qn1RwPqr2>LjXmCS)8XZo!{6uartOY5mok; zaq=TjGtiLlRoXn~m8}!zJZ;~*?^iL8rQ-#PFfFTV%r($1`7AbvR57t(=WCfiNBbyh zo2HLc4U=V&(&RlpRLt<`WIKEJB&j5~X<8uzbehfgO}>V$!hx+QK+BlWB5{MTtPyVw zSk48XG@Qtug|=V#uN*hM-2cjPGXyvwdI;8I{ok6MJ(aWbQnSsoedNTa%JtWJ(tzBU zugjX7R{Yty+0MKtZGySZJOeYpPn`pD2*+SeiW4l82x+XRrQ9W)Spd$piBQ_Iu8)o_ zX~a&OjIi&v9Qrm~LeakdohT>{?6OyTr`D-AO+|-QT9&)t0W$lH|6iH?>0dJY5L3=0 zxLQ>A+~$1)f%M$me#tAV&VRqT+@ErKsuk7iAUiobJ3Cpu&`5HFc%$dcLRuFZmo(%5 z=GBb9&fD|$?@LS?8tm6S;@`?V*3i&+cO&(V<~u-c-?(cpz<9ayPq$C?kS_sTCD8A! z0bzMyau{~~EXq(_eW^-}M4FU`E`?FGJM?$Oa^;r-12%eoK;}co1_5Ef`o3hUY~U(( zzDVnle5@SpKGWr1UrK(|!7%Sg?qTkVL2PF}O&RzHn%1`K>Jq4D=@ca2(++XkUZXYb zyUF{glH?ZxR}Udcn%HEAu6D>6);k1@F~IgV&k$ZT-}22|KV>Mv?*v71uQuB*VfT@U z6G(pSg1y%LYw=sv+C0ecJAP=cc#VayPu!QoRjz|Ru?4vr(2}Jc=FX11uwZh~rK8wn zfpJ=*XANdBEhD2r`q03C_q- z#$)ES0WYJ4;mh6W{f=L4wu<5xZ#2tjnHrqgnU@&!&wgHUKK*?pUR2vs?-cx--U8%Zxcvo(sK`3Gb5^q&JIOu`Do!-`!(@QZ$Y%3(T|K)%Gyw zSNB|L>+9?A)65wClU~O4>)S1x2<=I!Gv@9*wKL{H0exE605u7I){j%HqMh_VX2-5F z4XrW{F*6M@Gus+>fc-nbIvrro4zP8HuTm~$bsR!z=n1Hdf~+YlHsPhtZlMKL70zyL zwdW|G-Kx8Pa4Q`wucy}g<%0AuD*RaCO?vfn>i#XGRfWH(Y1i}aW2-cHPNoM7_!Be! zvK~yiWMh$|Zi%)sd&Lss*;S5k9R$|~&;+Y2ap&x@z@p6$EwSw%b7a}|bwUM|!Zy5m zX=AskljRtE&d5Qug@;fK09qp_w;31jAWarCGoNpYVB_6Swit+C#)Ca+f^1`g;UQ8p z+;h`CT2@=r6}jH$qAQwl27*gd?*+zH2~7BlH#~UcdC;8ml{eVH<7uP?>&ZV#BDcAP zu*06eX|ba3eS2`xcroQ+qubL{`BAf*11}WA-Fb~wvp&Q7mJg&~S0*}Iw2Y_TUU$1& zA~oK4rAovp6^c;HC63QP5m;@U)fg0EU}=xTI=exzp}25Y6Kbhsy1!p46tWuPDx@|v zpi5@9_@VLVzQs#z3i%`uhkb(YYFTn9y5?n1Dc7k~If6Zw#Fl0sJQI2EpFAu>$P1NR zqyI?>>jNZwBB@PW!C}0LI!(jee<>+PX+=E50hrbK@X1@Dl(=l@)VCQiD<_L<#mT<> zUKQx4F3n7KkWh}X(KeWhIeNAQ0(ny<7A`v_nbyC~^@Qb({j4Y#S9)>TX`4R;FHl@P zR<^6OGH$(#|D_c8+pg>HI$2JkVI4t{(K0C|I~TW}ZSQ(cp&EmAgK2W-rh2_yB}zBl zgIHwO-%zm{yyN{7kOAoM#?dBZ2pmA%=R{$k5d6@Zcm~nX&5Jy%Y0M?BPd1vP8Gs9B zqoFnA^ap>difNct=X138$!xoT1ON>n6bU*A3q<@|ZTbz7-5lFK+W;Xr}7r_*2a7s`loK zWaW^CApi*e_fMIDMTpzHKP*ELsv}>amx_ax+##e3+2-PsOa4+QpH97FR_dQwK3-DV zYr0e9X^kh(K*th_Y=I48l7zSfA$)rqE0#x;cQaX3wZRu_0^0i?IBq9e2ree)+=eBx z9nf6iBx~U$!l>&~sV58AyXQ}H=K$U8dF1uOJZ|8HWmFT^ICVeWyc9l?yLFE(Cm^}= z>)5%Z6dTf;&OZzX2!Txnc^8C+wZzm;mUGJ25%0?gCZstrV8t8f9tsw*ns12tuEaAU zCfIndI2r)MKXxXPIDDcDuUNp**=mtq!khPEs!MNRO&v$36Awe2bWJ7WP0j>*=^viiKr_UYU1KbLXT&qs%Z@HwOx znGq|%JP$g!(Gh%J$RYQ3zLU^`)cR+jIsM}R8tzDaCdzsM@b#2R!zt9aT=W;OAHD7U zN>qqu&{D3gh4B&!Yy6wxhN)vhpzRk{Z?$B8M!d%;DW2W&{&Cc z-GPj_ZAJKtO(}kaOSDYgzQM42Ey2K5`vRlg?LF>xo&NHLGS)WqqY9B(RX89T_}f_H z3}X|L_>s2iTRx+^4$dCqYV3`MW`dgq7?;*r-n6XOfR(J{CJ4sN50_55^#<9g`gsrz{HM_S()SA=ShjfdTqE200>tft`xh%a@UZ8u; zkKbe{oyLkqShR|7S1k>3J>4x1Z)mL@ev#o^8rC5bLX~O+sEM0^&u#c}1}S4fAn; zQ+vOVV8%Lf4z3rIFc@@)z%TAwlc2V7`ij{8&-ni`-BTIGsEp z8|s_nMNx^yyN&gqxy5pbi!)F)>0F|0LJ@@~T}3!1N0KGni{8^v5BPSj=$BBybgqcb z+cCA&UAqj+sR%VsG8@O5GO%cyUzc=v5_qD{pml$D#BTrp6j>o(Ue1(Am(n>_Kj`}U zSM#6kF%6iHhwdHOq?3G9G@C&Q#<$N+A%ppjfMWX-uBctba$m{lA%#O9@lp*vrkx_!#2GIvKSO{uRZrHOX{8)DJBymfO4Slm!{P$}!#ae_3 zx;)bq^L<_&?fu*zT^nPQLL9Sc`YaZq|Mc$V_`-)mpoDo2Q+jmy!ow0U))cN1f8jfD1b(BF@~ zY;6BydE_<9VlpK1=dqrc+RQi9xas19KskHeVC7TU@_;V}Q^^tVn>bxZMow!tpRKFY z<;gQm>4tkN6?wszg7jOv<9Kt6#tJQPvEl7N^|M&DCbu`~zci!pFK5%)d>a5(P8%AK zOV3nWdU~O!Gk>ZXtF91QA|o;k?-STa;l*;;`L7n95#RqY-1-5uDMJSU;Q2>0p#IDR zv%}Zemcy_>HZRgNrZNMY+S`8iVoR-fODBLONo7)k6GhPdT|a{w=us_Ug*uiq*NYJ1~<>9Y@RDG!7r0Dt~$$fl%zsvEm5^NGEGEJ{aFT_X; zRHAOFW;-g}YmDxb@K5)F_!~Kq{yL;8DeL)8vNzU6WHf9Ro6da`l#w5KwqDWV@_Y%t z=}7%>mvF-uCWx4X_P0e$ z1)V~^h-GP4)%<&bC%8nrgoXSd-ngc8LdZJSaTcW<)^647D63Bn5yMv#_KSZKXJ?=_ z_Iw0T&M^T7Zm`uO(;K%_!ww0MWEwBw9V|Ji`0JlBmJkdZCw>^5Xc;-Rx2`-*Kjn`F z_cDbjs2G99i^Koq3KacXYrz<+BCfJ~iSA1sow9vz2U?qJoO zquw9c{~FSN90K})jLLn&rbMXCzMUBRLxzOQr&>E-Vp9Qx{(6f3E0y)oKTf zaHeo{j1ZffLoGUDp~1<)$2As1fue{@znyeEVgPC341EP-(I){3?EOTmqi!1xBD zWbqNcH+%ca&NYrk-L^Tx3LKZc^L>$XcKX*=65Qz2{45o^C!MNkNaS#`4JbIvl@T5kThj6I%YJV(HEO`odp0vdjW65@QRS`x+_mk&F02w$Vk>J=#9u-+94tDDq@f~AwQE8#QSaS- zOPU?=N4ose47b2A;q~x=d^G>D#zY^*guiuivgvy{ zhkkk$uzIEwKuh%+RJz1+Tlt95P9NLgd#{9SH|s??1ed=c_PL^#p`t7iYpKa9O;3bE z`^p#~D_>?+$9LR@if{ePOS1B;1Pn@3H9bsy*dx`QzRL#BTS^Q+S*;s8d8ps_gqdTk z2q#0XL_$l-8geExU32^=KEkAd6Tl1{`RfrEsUu7PkN!^9juSb}1Ls8s3(>OA2|>!A z@SKE5APm3*2DFWEZ0D_g#Ro*xi-EAkoi;??JmES#9Ub+08%TO}O znu~tR&y8;_F6>p_(*Z0GVTNCe`68FyLO}0!>+rGp#HdnJ!u{GQ-8|)${Bn}O2$?%P zdpmQowgFUA#{l^-V_;gfl9Y%%2p{&nreh=3#jMhAJaRAAcHcM0f8$W#Xt_*(w!bAi z1o7KE4K%KCfRMZ%e}Ji{R7|x+_WX_Dt_?iiv|2ev4M~5s{C?V<#FD=5a(`jX9Zd&c z>h72-kc$xj7apBClj-2;HtgVeOs=1SI$*Q`+@%n}kp@QY{(lS6QaZNltK5H9rO2`g zIY5}|hl!BR?SQ$x6e8&Tukz(Rk$;i?!mXq%`5VFCN1lE0sYJa#d^XNGZud2Gn&U6U z&ZHO44{^!7gx;{pLvBD7g3uPT2}_T(U@}Aa^3Lfr8pZbVx5%);LCuL`KiwM|>Oh4% z&O4sfRr8+_8(&^mN0c;?1}+MD%?pOS+rrJ#`GOsSWD}VbW;dt33@94+K>*JRR zI{u!n!HZnm+Kt;BCUHsTN;3$z5@Cj_LP6YU_Bn}a$II42Ui}qmRfW(M;vS6rIqIQ% zgc++Y`=qX!gcos1f_W|p zf%H~d(ogE2sOR3z(@C36|2EH8EcIlNrQ!Hj-tC@&g7Wm6+qH2u()!Jr*2nDl>h38^ zd$@jcxEz5QsO4M_X4W+i6k&0q1FG^Ak)K31+I=_?K}pc7tZxJM{WDjJD28g9INDbG zuYx6#%p?>Nw)*+S0Ou5(p~i&N>KuUZNfrzT-Wd44bYxs+fUZMgV$0 zZIU%GEu|FycN%o1gx42&<@EFKJ!oF+g@f3$2Mw& zoSfZWVD|r8Bo6g~>(XQR;tReT3@5C+Pv#-t+&dPZKi_kjk@ga0Vf01a3KQ1Z4*%Bb zY~Fv>bi_;1jyj_zy;{N-yv(o|h75|W+T94NK1k#^iElmE@tM~mF39b@F`*Dh%v7RS zag$H7qR~e!k^6G>J->fJl5dosSE2%Ucn*S%;Yu4xoO;~`a$ao4s+eew`Ok(s*NDOK zkxE4-O5f(;I$sSM0%FY_+k5;!d*dW*`3PRXdQPqxXcj?khT;u^`|mt(G`tK!&6-^qA|S$aHTFqhw^9@mKrl8c zf3h4_PIu+Vv+d@Dgz`d`k8k9!!So#`@M$vRJ5Y|jLt$nusfjfqJCP_grxrk{H$@0@>zKVUnV2=L>W@eyEPLa8 z5{i^NWe6b>0sMXy@JLV@6bnFACr`=8D9W1DIFXso2p_$aS52VZ08DjyBu}@S4Ed+? z1d410fX0x1SQWmur`mQMq|APBG01J4V@%~Q5^vpLwvPW|dyb)kA`is^)VMrHcu9O(^r`U4XQ#$y49dt*?C_AsyzX>Mj1W-Wp3 z4MfApsxXtS^&rtkN2f%B&mUlHm?EJNqgSb7Q*mwb>J3F$HOTY46RrPO$p#G{4Dq_~ zD(Tt%@5HUrbs(;`f8g?e-5JKmb#xK_hsFJE>nvg(Fo|oSsvqTLH9b=#C_oJA4$r{n zMo!yxj#UbcV{;})7bJ?M6b!$3KUt5N?CC71swJw6V@0z;-d|&+&-xu9r;25D@=tc7 zB%W-BYMRd_;b%8`R{{aM)ZguA*5$7L&;|<`n*T5rG~8RyHm7oz1yX=Fx z-=S-QQSJ~SV-$zHOfVi|=&ao*5uYemX+RBIGd(i^bUkW=$cWHXSr|=HpR9GTL?8LL zTo~tOrI(9ZJla!;aqjW=oM10t4c#zcn^+jyN{sWEV*#F1Z zdqy?6bzPw691F(=3L**uB2791(u>loq4%Qn9_ckiML0qZPL)3-h@4ZOx6Ud0vy z86Rt1nPqvA8F3D4dQF94yGF|E{}_AEcUqLRL~%Bjx-r!f&k*iL7+1IUkb8CCn@j+= zX^)lZ^Gx1kkjEXjYZB_*AuZlVYFWui?9cu3`$`?q;y%WI|4aLdoHrE$Ba)O{MxQda zO!R*&gkw7xwG0?1>{rW2-i0=qW^rxTV^Ul+bF7%3k2F3RF%OGg4@RE!Pk(f%;_!sm zLajC`2dCd+kpry2+!A`c$2?KyJovgU=V?v4dzSSOS9pS8MdCp%Yn6m&n z=Fw(4FO5}nT=Mz#+JR-*!3fX1PmcC05DI|y5hi*V)<7zr`Nsc23py8tZS1Zf6<^*a z-z>^=?w5m`8q!=Az7*Rq^Jl=L`|&3;I}uF?T?`GXb2*?{@*onuUwB(Qg{G-9KfZ+5Iv{fsMOklfs((-s-7XV%puV^sk)Np36>!R0yXtcQ7h z$+SV~F`>ugXW=h?JO1^zJb&Up=g(^oQ;aPdJo~U8iMDw>ca_I7yl0~D?=v1-r4NQ* zx(t|-6L0&uhx=E+~Dczp4%(mH_$+L9d9XQBHaBmC+a=s@wJ zizDHAPYsD3ij>#fYhHnW#a-e7KNZ+Lc81uUuW1Xgo>oqH<41|_l@K=Ee7Oy&t|1R{ zszO$ov^VKA-1|m0D^Aqj*6m+mn(!X*L=i3(>xQh+dX+$vMo(v0kd*{R+~HDMiHcO3 zvs_cNs!Rp?N;81tZKX`!5m=9;0U8m*MIV6L=qZ2jfEx84 zBqnYFCbgIBR=1ZOuD;wn&eQzL1Jv$WApk|uoWOr}>G=XT<=-Jv9X7NsOvv4`1Lco6 zSXwR|sGLzu`IpTCSuqz~Pu97O>2q__&Fs4Gr3$3f+usb??dHIMHVn5(o9k24;-bh- z<61eXZ%2coA1d+fF%1c;hN#7G`}rO>XCY{QNMkDc{6;}2 zDHd=QMa?H zUBB(-*g&T7LzO7eQwP|8D30&a#~QF8f2PE04)IYVO8WE0WW{O^?)Ms^3_*49sD|GR%3U>|r4sjIWphoTBm;{-~65p+wx+=#R}H+EBQ69@k)p;I3CQZ2ffLf-dqW> z`e9a|zMZN%i!ZS#C6+|VnK!v5B-;pQ;ct&HEk6QK=~o%dnEEYuJSerrnsNgEqy-}y zzb9Ho#Np1&NAby}>*iHc4z_0ro)M;QCrqdKZs@ZfD6#Dq?1(J}Fxh>omdAFtZb4Nh zC0Q9d4%+?-9^H*qzOM(v04OHYW77RFJIF+9v6H^_`~v*;Hi<=#Ohzp0k|s*#XtsT= z79`R$^Ev9)x}-PPJL|J$T4AIfK~&KUry;7&qJq-ORl2<+=iS$2qu1E!Up@=o8mG4J z>y`hPT0*T2Y=dq55yY#`JgF#gF&+NLG`Ls(Aly>plhF06QWkA=VJ>Y#t5hkcpQUL> zz3YJ4rd%7OmkwJ1MjIY!Se@8r>G{R(SuO3g2tRP|`a#pu{wGY+tCmn8 zS=L!<9%9qq3*m3QV2#)UHX6lIEXQ0UWKY^@h35V#D~4|fz;^Em zE9%|6&CfdoV;kPws)@ptiK;|CP?(VvX;o`1K}K*)Gj~qTr`}lVU(kU+*>M8TMEKN? zF2yGA($qjydo02&RX(v^``hDBxa1>x_XHYuy^e5KJ6*o1n%)pvA#)B~jZ050`sk zDi2Yr{tc08)s-@z+e=jFxJKXCy*Rq>r?02+!Ez{};C_guaXCveqAJbF-|gk^JJ-rI zrX~b0Z8urj75Y#vvFjUW4STeDki$ch^e$HQ+Z%N4b;_mcpZI7~F1($3gJ=}{qgKPiDoS4Ap@|4h2;|rrZ(&=?ZByr{iXzXYvs{g|l85qd_^U8f zrL894*SYI%X+`Ls>}8@X?Utu16*fiZt~T56&dZKIHKT2*2A02?5s`hT&M8NxUYV6^ zNF&RG)_dgR(aU-E50&nd#mst62H93ae;TV&Z@QA&xGIaBR<0}jOXJGcWVm!&g%>H2 zvSG9yZRlv|&}3B5td7(CmEo~qS_Vfqa~QGc7`YmZ=^M#7qtN7-)vU_2-N!n}?5|r` zryu&Y1F6cjB+U7=7*0g0+U{gUZq4+52~KgRYCRbZWp;!?TNn|t;i>|#Kn5Pxo?Kw& zlO(y~#;f4K60R_lKX3S7q?7YJ>gjdT#;5GJ|3l6ev@&e^AU4El}+TJU0W>*2X064#|a{)I2TLcsKwI!Ng${Zb}TlA5) z+8-m@;K_K;nbr0!(Vt_jp0Epf#CEIiuP2zS>{+!&&eZ6h&v-I11rlOJiSXt!3-kEU z*pk@*DKko!gCXu-L{%l~trThc$Q@T{>^2rfZSlO7v8rXDvwo&V&)I_*(_ZhY-w&?G zjKy1(i9u;~F_hbbHP=E%fC%#6VQJaZL0K*Zp`t;m#itS@0vXytToz6Dc)(rD(xNBL z+AkjgL43)0KctOGqaQ-lAgf1ACn$1#);y(%$0-d94{hy_Ox-a3aUSIZV_Kazgl`%H z!?j=q&e_tMHYJ0IpblCg>`Ka0QT(J;s=UP|l&vze#T?&bq{tY?j$2 zAL)bu^6kA#v$+k^^f8fl#_>ut%sP`HF_jB6eSq(>`;dSW0TO;Ulq6EK6Nf43mXDCn z2)UG`@EsljtAZ0|aW!M|wUJEFxlYzF4yE)8Vhl+^GevcNipvRSU3Nc|{c~>Vb>|E9 zy#@b9T9|njG(Sno?k(uIAC3~qsM4QrPia9ZcmgMOKjhQ~`UDO*zOQznIqWKyX_bTQ|Y zhx!yeSBj~e?rU=jcJNofikfjk9gOdK$yiY@jpG|+3PRq>r?JE-w+2tLdP!&NK?iZB z>G+m4jEuPngE3TOByDxI$ozTTE?1BIej+FOK)(>gIpj3A|5%6JZSu{4v6QwundK{Eo_qh#LS1 zm|^sJ<|VW6#Hy2KX3W$HGp4X(*1XvIpRI$TVjy_KKAV0DehCufCFBUp0Aj|@vxNbg zBADP32C{ubyVo9Cf&;>Vt3wpoBmfzXVL;@f9;jWyGf59a!K^-CI5V^8L688(>JtUXpXsej^*=gpqozMG)) z$7Y!g7$9crF|hgXpETv?oRo?HI})OQ+q+8!2)8yotCEc9)i15P-6$Brql=YZx{Rdb zv3z+|;q%`~A11RO;sAHA4~bvO+Igl%&zrDPZUBzxJ3{Bx4ZJFl3~+=Xq827F2A6eh zv~Wvu>A}#g6w0m#`|_uefdn~dE~V-wBtSqqRVWn1HaP|)XmOE5eEBaC0X#m1GFJ@j zG%NJ@iQSA|gQxET*aVuzSMB2kCCcmh?Hd=#Zz48v4i0K`r!e^QInwvu+_sUNd#MB} zl(rbEF6lmrM9oWL%h(BaVi>V#jH`5Ws_fj5n68mG?>iE2Jbpv9{R1viANZMHg%>UN zh_%Q6(OD7;V%;cyJS=!+ob9)SRx8pR)jerbm88Q|S{HkWyxn&`K1x178pnebc`1_-k$iCeg*H@}4$-`l_&@CkF=N-xE0Fn(`l#K-neA~oPcgnc zAE%`mPngi+D``7Q1hbZae(nKzDsn^3=@h@Qqmx>i=k~t8Q;?Hn-SCe0tWpsqX5EoI z;kA{=FqpO~<4z@SkHmL2hq@R?4(e^uVRoLUOj%=jS3hnUKhHiaN5&r`h5}?oxfgS7 z+$rBNtLq6bx^7)LWgjYm0t0a?4U5jC z0#|k*Z-C?K_KWR1T3Vjj&TGel*rQISlH6N4o|1cVMgF?BGGPN2#Iu)C9(#F5Gj9@@ zj!*Hg2Zusa0{JnV^m5Qj*`xLPG%zhK!0K74bqj+*7;*wgAlWWj- zgDie0zG^maXaEn^o5(quhxYjFvxE9rD%S&df?3=3@nGHEg57USoLUIYL&dEmkMm4q zgN##xb0>LO!@TSD&kJlS2k>r7N232qjGL7M>I8`dL{`a#thJ$7ZJerqPYKRUTHiDENbQlxB+lcPH5#d7zQi?>oK%FE2?Z^lVYW;KYio&uWAJ8&#*T*o8c%hw zJAS%DT|j=q=Ze%9&B5>924sB8=s|e1*AxA)W#C`X*;7@#V!D4xX$IEm;Js~WlbcKeF@RgjaI1jV zM!SIKti9bA{ZCC)5n!1FbCo%{+V^C%c%KtlBit|`=)cQQh?fz;6*(ECBndf`6k|lc zMNT`R_}=}ttO`fE3qO}&)$L-8;5Av3kHD6Hz0k3~GIzr=>4yogV)#RGp5gr)-iUlL zUe}2HlnkiJ+CHEh0aOAefb__ML4dSrCM1O5emvyM8UysI83#KW%k$0GnK1+-AdFxe za0so`;|dJo)~6i5OnH$N3vP?9`~{uK+=&W_&f?A1gLTr6f4Q}E4Y%;xhDI_LHM34s zwX+WB-M%J3F7xOfdeabdQKty5ar58o zz`xpAgxV3XWl31+vC5HHX`cs49(`i{kbI$mWi7uvGlpru(#Hn!$)(rVT+^!jGA8h2 zfWb@vpbd6BkpW8xjlsY57locZm9xz-19#<`aH2Z9`5PH!FX`#Lf$_= zvUts@Spg!vfXfY>gH4hgOR<-ltAFEs4&lWf6=~83R27156p1L^5VOE{M2sgm4vp`x zMOfv9YneZn3p)Vf_5I8VSJAxFMZxml; z^Q(7%egeEqroF2~Mi~4_|6UVWkNoZ}OT3h9-qXR|tAPyu{GAE-1eUV0Gr-X=Kq1TC zCCKwa!cm^4xLl?Fje&f;c(I7xn3&9%SKg6(gmZaW!zUT?D-r1FGwZbwQM2A)mmIMm zF%=dFw??-|+SnQN6JieJMBwOY`KiMb=q8)pIB>loLTgYFnCL&LhCkn zVCjc68ctrRX7f)aeg{P%w4zRK$V24(9)r+9#bEXJR(lK)S1nW%Ul@{5uUO+E0CoFb zp9T72hfVnvKn}2H>l1630a27L#i3lYBHAiE`TWK7uaY|bw9g6V6)&@LT{0)QO%l25 zrIH;iym57%8vKCedPKH-eZWhB0hJz_^NjwW3O6oXOMs30J()z+L)IyEe}5<4rS1DU z5DXU=DgL^}@t(@~hn5eOJ4*I77teOj^1niLZ(W;TD9o+j_U^o5Rtgm~@+j9Fi&U5t z{ue^871`JPP~SRNyN=T!C*O<787d;f#Yjwxu~ZRJcy!gQ+xA(FA$t!pe>kgx5MvGj z{=Xm*x!F*_@sBUf4D-dG@V3x3Xx_a?0T8+Z!jXwyz*mC`{S43#`oFnJIovwr`LGf? zir)hBy&KDXjj~)D{@ZaN>(|#Z4{1z0rnUX&me0leEVE3HQN(+%?m9*M2$Ng%l9KhR z7v1sZm*pF!{4@0KN3qG1F|wy40qbX6r>%W48se8>zwMI4OY)V2EdF#XIyiPE3^IRv zQm+KRaow`*MspQ@DI#$&gu}?ag1(@eN}?x)mA7Y(W_YwiV)$EX8PGWV3u@L6VJv?1 zfwP7yV+y7n&n>D#o8^|B|0t!wIC>X!=W=4Zw+3(8@^_!&N1B`hCL(8xJ=HqkvIeXe zt`(zw@?1vduod9sm;s7&@gXyYs)#v8S_r4H^nBD?O6x)5JZZsK0QT{7D%!tAu>wh8 zN%<=0o8VPHUJrn0ngjBE1}t7OU*=v+mNyN4-eiG3$dAg7lgw?y#RV-6z2TOPeaN*`aCcz-5-Ck<(Qm zA(a^5Bwah_3FsjrW6d8u0cfTZp6>TY2=4K2Jx}yQb?%5s^-??!%gSh@;>W z5_!tK2L~+YAY25_CxVJNoATN80WEkd=XubaU_o|to{$3M4`vL+ARR*B{#8Qc z`RwQ5%Hcb@GdTavofm_pA$vBP-%TI99%lI%40(*^zI~lvmKe0=tT^H}s;)Dx-I;h~ zm{>@NzR~8Krs;23YG`z3-dG@=B4wmeYbRvwEN37%h36Gorl#jSkc(~pdXBS)uSy-_ zK%a#aQVO!A*~;nSyUgcEqLANHo8Rx&OiS_^rmN@vELF{&!-yiH-SdXh7&yI|WucGs z?yj~6TTACYV<UXqK+AmTAoji+Kp~C#+j=;1~efuEk*-|2hY<5f$UlACvQ*$*REa zB+jL0EJy~7%o2Zq3s<=L{%9Tv$Uf2{hv1g5!kfK)n?|-rU*rqO%MrgmA2*~^^zPM& zv&SH@ol&Jf0q{(>tDI^nFC9o{$d3CwvJ?{uRM#<}bL~PIIhSl_NvU|fc<{o7=Ner* z^n}`MZ|2C5Vv)60MgqSuKTqvlk*}MZI#~9fURUs$fp?-?iDM`4E(F-{++ht+XQv4@ z+98kl^=rARnX)>Mk$(3lIS|hih0tBmhF%GB+B9}EO`7j8+C^YML%>j`OaR$+0N(r!p^@s z(r(Imp;_WC+Li1J+Q{DaYE+8Z8s}BD32Ru+&xf>&B0`r7oYxi76%8pW>@>0!dVOQ! zjSE1egkz#3aZbaGswv513~bePqlIn%%K*pKZK4(bPDor5Z><6DEh#Cbd!EO&fAVr= ziE(BIB-PUXx5JHnj2?#RQjI{uhIRpn12Ck*w!HOri%( z)vMGRjidzzIyXlH6tJUY0hnmM%m|py-sX`IEiLAX6J&o0Q!;P#8{y?QMvSSQiIu>2_@5CLh@hs%3rvJ zW)Xgl5Bak2c^La$40KlB{?kV4XO8n zwO%mQj+-5@;HR&$)Oi(J$(+owKVLB174+HwGZj|6<-=ve%TwM!n#QJ3R#Wh4|K}6G z-{)#YUa9n2-L_Uh&a0)IuCuTjY&n5Qu@+HeOOMyP_-kfi@J^wlQ`{Bw@J|!s2_igf z#2hty=z9RzX+U{`u$wrOUo6)D62j+GfJm*k$%1cx>}o;ive{C3-T3;G)Y&AF?`N_F zKL_aI%_&ZI5ajQxz)k5vEd*RlQ%AC|><7tOFI|?3f?M!aP~CqS&q8zBoZ#F`zbC`6 zGu~4snK?M9k<*hU*E%1{-z{>|sBPF>kmyHcL=^hT>yY%OSZOzi<+{7S2lB?ZHD5?yh7;h!&AZNo1$HiFtX ziLx6;1cXzWT}p=~$TPs{~ia^O_;XeW-UhB8i&QxSP?(#P@25sR6mms47CFcY= z0JeG$02;5c-6V64Y~l`y9SL|$mEPZ*PZSKUxJAR`IftbIG_6s{#+LK(9q-0P_$QWl zl`oB*INx=)`uSp+nIY#HTy*>FP6Y5(s@YhFtg3A9&O8}Ot7oMb;Jk2kNVg!t3aVAu zzf(U%$LieMCES2y2Gt?Z-fRICnu`{>>bIq%IKA>eCT-Xl;wVHFPLdGQXd@ld zw>*n~yCHxIE9*rwN&BJ9#WKCmowON_Z#+a!@zQUYZXVxy_UOlT<4^d+<=|&7w?=oA zw<}&u!IS%nEUh?jn}Fz0;GpOOW^1b(plq1DVrFf20I@g_Flepw5Xb%Gje13>U!&bV zxm>#<9>gdEZW>H8X#kGK{8w)hz^}EX@lpEMc3*yz;`cxFS!mY(WOWVVxZ~5nE}y2b z3}_YsOL~D>!@15{zUa=4B>rLw7hUXb*)mGaXrp(;ZGFE}KOakF{i**Y%DVZ!n1EmzD`D;rapb&D^%kA*<7Z+rdIioNpJs3rn(0s(w9 z$ac`Ko2B_pNi%M9`M7POWGmaZv0{TsJ4l08#uU*neOfF~X8;ke3U+6J9^Xn64)m`# zrL5}cJGHYQ%YHICY*4gDb48mF(Qh+kz`vIUbbNAx05ZkbviCkmk`F0=jI08yx2ip7 zmQC^1N7q6w*jAcMF?=c}_k12WUet`5_iZ85j-WzoU3%2gkcokXIkUB+TL-&uv6V_P zfIK|rL_!AbGGmzV>XPe8dq2Qnh305aS}RoEbVH{{VpsctI7?{guZ@a53$_Vb@Nvs6 zC&`s->pQx=4aei_Bdv2UbZI2R-#9<`aIXZsz=)Xy=o;xc`>5Tj>~#z`b)i|Dgw6B& zOji<>SgA^?)DqXfzY*xoxZsA}&mJ4Eq8ahHF!0zithnFl+?sxaW#D$!HHT94Bs)#= zNeTocJvtH~{cRioN`MI1-)ToAiO&R=wB8%7FMG)#=AW-lWz@R3C+!P-Ac*aJ&H-!C z6%Du9`SVN*#!sds`TKQMF@r?6F|cEWQ1+cck%6lxTU$^ezeePYFKDq|+rV1n+;R21 z;)yY>i5&rn8_qbxdHM41?yB`Lv$Elk;f&9S9>3mQh(Ux8z+OeoGt*o=d^)-SkXOqS ztuLe00#xq5bmCw3R=#smwv7Vzh3SipC0H{vv7I7EPs<7)Y|`2;-(n4_>(@fGBr7v) z6GcP7@S3EckBDz_eg3Zk=>tL^g;zriZ#8C0e7HoPXt}jqn6&CkBt!r9>2>8t_%?W^4!5P7nwMpnJUv$cF>E7=_#&%=(n}D1OZcpuj6V zc;TmY)pv<-W#DVMS{xc}ak@10;*qPRIMfqJ6x=uUeTN2zjhXJYaK2SRt05M6{Ip9}5md?}MWkbMIlk$CcCEJZU2 znW|!z>fm8!eZgU;lB7?>mvLd04{WpIX1QIGa<4Hoa%0!m`?hwl8KQDpbdj8GH>V%O zYTk|FdUu&AHZMV3Ebq$Wk9k8k8Kafl6-U2{v3(qV_lS)o5V`sFC(oV`Uy+E8Iv`w%%`|LJIhqHb(D@KG5u zF6dk|?{=}(@zsJ<>H*Hr>jvqh1#B5VhI``ir&{NrXFjeFoWL%mp!g&JTwp=P>JU6o z=?KcPS(mlG>2DS0$vJTZwm8ncqI5TFhIfT^v~b*{9y41glU!BPGzPg~foKV_wL0C0 zkONxjp8j=7O!%L-be8|^amrKyV!3Vx)=OXhe)Gx8VI?kuJGR9Yf@fB2RZ?vd5fKJ2 z_wChP#c`2ehwmCsE1G+DiQ@n_eGGFQ57aRlEK!U!gTPw`Sf-!T%Xk=2_Q?{9Va@b@ zQkZKOp=a(Dphb<3xJvntxB=gXI0bRxRGU-hUq^C)i<$9^TMlG*6m#7$%gEI>T>t)Q z?D9l06$BeL!XToQkq`mayKXYpt^?7C%?@D`Pp!ZGJ}z91eo=Eb7{3KSGMu)Toh3DP zj1orNv6C>F;z;F*wh(B4tWNcUg%903vIK|M=TAvo7$fAFj^28ZN6wUa-Se!i=vn$7 zMCSiki9dh!-&W$D=_m8yelhu`58}CX>8|*K{}N67$Jne%v6!}jC%5|fO>erk+>50q zpAIf^n&=8~IDa8DDMH;sxb$kEO+oEIhRWBh8#ClPD!Z}C3XV;!U>o?iJ|>LGS?l9X z&Nlt;rz2aMi#90_aKw6^cLzr?ybHbR|CojyZe+98&2WRaWx6hfCwyAJM&-x8c*73R zCl4uQ+?_f*4lxvsOrWz0G%u&|J5jeFqXJy*xWVNE98v^-dF`!P9|^O;=9G^i) zmmwdcF8s$$>WbihyGh*`=Z?1^%5VDBNJmSTjHpQa2(QmKQQugb(WRYF=eWILix7d#vVNBpG5Y}3WypvWv8^HWAlpCe4h(R3`+;>H!mrkJKP=zyZ0iiaAWFkqtSV7X)g<9|L1`T>8f{d5 z|92NeijnSVPkFd~hQ{`jb#@>%61 zF!nL#|`q~=oR|RX4`N!X7OctB`I(TcWnC{y=F~I zE40a@XN#FUzRs}{ud zjB}?1?{bm-o=@1M$bdY3wGEw!H>Xyo?Zg`0+?fiIe%WJb`z&m_1UE@T)I(;)q@UAy1PmF2fn@N)+3~y9N9|Hjkct&sH=Dlla z1nR8iUrt+KLUcEQoPT%;Qw(HhIf*rzb7?xaQ6%t_k+cDmN>tkzBPbrQn{? ziC5XV8r1uV`*zp$HTmk!c)08`qTd7e!g%9K#22YBu)lBv0CL^MFvS?#G@XW4gwMd= z*sHCUW0jXE++F#Qrc%OL)>@4gNaO<24Lf8K=tRl8KB3;nTeZ;T$?~LOP@%p<95)QO4ZQI%pBOWxk_w&w^Z(L1eqLnwue6Szkr_wc<>8hdm$5m|agKwl zBN?sh?qSmU)+zlu1qV(e5yRKLICOfn(n3U&dwdaWI&SLqIwxGy7PkpGIjpf3-tBmB zOf0V0qZ5y=(Lp{&hu=qEv6CX+?f_Da8G69=bMNV9uP$WDe)xbpLM!!%cC3)UI%E69sgUQbifK0sI2ZwN5;~u_6DO;`eSLyMTy(dDzxn zvP1IS{#l(nR*>vQvP_P=_sSK=`FqjZWY-j*pRv9s8bX~$;$dmKP;riIO6T@sAj~B5 zR6zz;Iv-|%3=Pbhk_$zTADlpHc{+dps<;SezC6;<<}4mAA^0{v&RKu%6g5)e`MK26 zj&VMnZL}c9!(}+IQ8pJaUn=ceH#f=juXyBG0H@!(d{4c?i zOx@I%4qpeXY_DXy%Vm2+9==zgCD@eP|5AVc^eH6fbU{LX{Uxm#xzIGOMm#608zX2v;E2n3RNu)k^yF>$!TifOdF8 zbngX$Yp22jYyBH}#Yt_yKa_izO01L3>k4Y!Ct-~f1BaI0}c@FaG%z#{nFFP^`m|0GjU*y|N2wG}nWNKHD(+-JYvf5l#?!c>)ab5T&XbK6ah%-W=At%fV@eBZrK)wQHTqg&{}2q`>eD>@px z=3{R7@q*3OswXq%hw$5Py! z400)S89z-{fCo0Ciq;);k!OBC?N=d%jpHZhJ*gd>Rxdt$!!MWaFq{8AUyvq5QBUq# zVcbL-Za@7^gcI$6Ey2u^b^dgn4}OH--{9zUUT9U`%&1)cCq=*zx+~oX{^{ws^+zX5 zC?w*btJZp=^l@rAa(PvTOZ=)_%b)PGd|p2?ze?_-`TTF&5@Y=eml2B$o`=nq@xOgm zR+5a1KXh>5HEE5SqUBTCu5;8F0l%TnDIYqq)A&)!0cY3X9mPGgLZ%k5j3P!f>gZH5uqXd5H;)b4Z}}eg@01GYxsB`iQPw$}#(qZSQAK$E;^y|2C$$8GAy|32b9g znvzj^mNsEqjL^)a??mMp=C2qDNe%DhT@A~9YJzqaMyQ$9xxM$NPAjV-OSn`mm374X zSJG=vNxYX!Gtpw+UGqKxjq@Fa>=0dT5FAXfKQ# zzkt@(mU|<%Tmlp{uQQk5VAekDFzx3YNU_Qz_m!B+pMin&RJgx?6uL-*%aY0FX-~ZI z7h9e%Rra;U*b92aj5al^UQgO46JnP!F&N8SsU9RkCHXDVae>Ud~o)TJOv$qM#acpHIz>T@|y z|90CD7nWbPcNbPjFDn~HScccm^Vx(C1VCjhBo=5G8KuEHPgKTa`y{Gvsd z@(l`1Fgr(h73)XDT8=1qz> z+k(AgPE=;MP5ga={_fo%EHDS(AFpn;alq2rWyLAmJ=of4TL{Qr7-c>_#`D4 zV~d)-D8VNNW9laxp&xOV0{VkY9-%`1t_5wp(-iKUQ|Xy!fgQA_o@_hLKU_7f(M<_C z!v;S{$0@Zh(y;%0^WO8g_x($ICTnda1{B|+tCc0@t1e^_I~g40XH@frq%8h;KW26r z)jD6;p)kBYQ_7v*92VW8+a)6tBWgLOVAXCVdtepz6?l2cC;t0A#nz?yp~bTJo%$3z zFxX&PtIp*H_rIRTI5*MMDoU^@afHWEhj%vP$8s?|c3g8YHzTDjQY=r-Y73NBRnU`D zs;O2sl~9Bz`gJS%JoRRr(0t$8o}?N|goMHCAECe@aqVAqkWOgMtB#@!?CN|;4|-E~`1Yu&@5az)W_S=u3XtUB@@ zp8aPAR!ZLPWxKwf8G{L$J8!ks#BLJ@2`c)6R8YKL7nbE-I;(;R4dS*^IALDtx|F~i z9OILl``jzV%B(w#G-g}1h4lEWqCA7F&d~cb;`UDIV-T(jV z*k5ZZJiBybM%W;Y+rsL!)!yRjlSFMIr)a0aWWIet^P>!m)(TZpMC8}WtcS5t%VoE5 z=B6H+z{Y!BEhx&v>N#*79M@YA*5vOGd062sDz^~x3oyX)%)RB^-E*41*t8D;$GRh0=eaxUpdtg{Y+KE{^u;~QShO<<7n~fT`9*@ zZXoBN+sqVhm~YD?-6*mPvgsH%WwgOdX2!{Cers-t-6H)AD-#Kjlk@hI898Lil?xS~ zGb3aSAXYynxsg`wQES(%9?2v>q&8_Ptb>75nP5$zbzTi4%z1i#O4!Y~?W@dI>%V(@ zdo6B{OQnj0yYK54$~Sht*tk(Q0EFi;S(c`;3ctfdN_waput*K)mfi4! z{5kdN0Gln&ojvX*Y)*_3KW82D^49$OX*mU)yqif^-b;R(TYxs9!=|gPr!7r4ZMCrM zozJq=N8FpnU%k7*&J{s@c=}KY4QJ4UY`XC?vvE>g`umN97n5S5XOR-3Aq(YoAw8Rm znZ$~!Ge55iTbC0_te#bhPI8LeGi$*qog9H_WeLbB$H+kv!o6K6&}o40Ue!#$k!5FA z)5&@{+rq1b3$`XXY&im9$}HfMIB%qFQ}8R_EPvjBY4?8_WGX<`aNbn1n73GtH0(L3 zSR|>`JrB`3pV{%G_<)_~hiTbl3RjAKSLaAdv=~Cbs$9cjkx}c10Gb$dSjRbmYqRA! zpRBVz3EyUnaSH=iZi38rA*i?TW|B!QJtl)n4=#&0_=w|%0TSyvzPp(DrV!wZnM)42 z6SOPJee>^#e6z@0dIE5Mk*$9R`kSgXp1tY)Dj_QBwqPnB_E~U;JTy&AmS?cgXcm7Y z(@6TEyo<@|>h{lZWieL)atCdTY?{JzdJ1#;sOTqBwB~c+sx{eZ^HGGTiS8e;Hkw<(s4_*KoI|VQ#(OE%AM~HDYqUR=3(6< zK@v`#FM7d`4wwLpVQl$rj&jGI?IZw8kmvpk9e7F_0Dm~lEf58UX@DXr>G-qgit4mIL6nfJ8}yIk=QL`tUY z=fTQ}otKyA7bbIk__76#SWLWdSF4JI{#l)v=Si-kYTr0L8a8}ZW@!mG%0M}mMGa({ zk5Np|z5ivfyHe>Fvl#l&Ou;86|5?3%M4V~;qn@OOI%P_x;75+=sH6gn1|F;IAg1A= zF%0OD^kzcXad;hlN{L4gEY1QSd;(_BI^mptk{S!(hu+py$aizVb|pPvVAvh+IDt_U zv41PW5|yj~xxoX~f7Y|U`fIeSGW{#2nRxF*AiEUhD@$8}0Wqr5iVfP#|RzCUgp%4{j)ah*F&gr$+IPjQK-WknajM9iBJ5r6AFBQq4CcCw%|8^fp6LdPy z`&7P|9#ek$ARK&-G)X^xRV(?5N3YS?4;}jI^jYC@yMXJ^KaL)UwUbhJ{^da6Gt!;j z9P0iTOaDsp{9D-WPeML`ABuMwT|Oay*0uj|_Gjr#^1)v{@&-9qUDLwrcqTkRZnx@q z=FbtFyO2zvZU2Z)z#m)eJXp=DZ-wnGs1#&}G661h(40!l`BdYrv=$>aOTKyi=;Kc4 z`(!AE2sCBhyL&=-Rm(>shpHmAm2u{`KH`S>%bRHDXV!Nge#vrWpy7Y%xdS{PXr!ZfnFTZq^2*aqLvbnWf{?FT2(OA8E_pL zQFawA8F-|ig)?czM}S0eNXQ$HkWZHQyV z_TjWkEbc^O2B(aY(Oc7|^+_s-RzC5~AI>Q;=5TF|uH!-X5tg@!9tPGeZP?<0KKT~Y zo@sDFb~CR2(OgD-tI*-xn`DKfuTLwmh-yoNhAvT`qf%cl zO>s9xyS2|hbkk(blOFIa@M&UoV1%MGnPuyTv3`PEygA-p5Ic+G5xYp?qZ4CzrFPl} zw|a~Uy-YHkVb|D4$YYF<+PSm0M56vr5+irReZTvi z$#WhIq)qgw2ld;jOEGOe2(SV6%!B3hDcey1{opn3QppO8Fyjit=NJCJ2K*o2U1*!V zVR-+jMS)_^%$4-=ZluFK$EcUt+FY%wqTTWe9E=Dfi|y@Nw0y8z0Csk2dT!3x4GjFm zh;`h(qN@Dy{oPx){=9W7LG|r>b=wads*DC7HQK~fv>)p$YqL}4vIK8mK)(s0ocpXv zRi_rLY-Z=y!@^cbVs|k6)4?pVjpZ$by24@siIDMXivYlKUP8qaxxNBpN|*%U!DQl| zjzS~$eI%5{n%GJ<6*`nYY_;V+qM8CLuc}Sn zEvYwDPArya5vE z!vA=v{viA>Ugyu@7y55xXz;VqIO^MF_!@DIq;(UVytgyPIqM8E$m&I}fZeyAKao+t4V6%s&sqa%c6WawWQXi#qmN`T_n&n%dR(=jImQ(g8xN_Og zT1r>D&1Fb%KOE7-;D3){~SgVH@v;W=JNDXOoo-H%SdM4BH0I5x$zs4 zq=rsLI^HQ21>#9j25(WZwJi!DDvX{bp+-a+&O8b*zdYetBn8@;yb5q=;}9p}Of*Zq z@zODy< z1#975D6IiYI);PD>~9M6um%q4gQ$D91rR)K7~;^n;2ip1DZd3<%lZA3?!&<%mv;O} zxaW`pr;eu&{(??Jx&x1ajbvkJ{gXB|RNA@qqx;a|rz+S~CNJ9c&Er;~yn`ocV&@It z&UA6Fk`$`q0Fve(@9C5~hxT%UOrq6j8vob%$92*{;)8Su`L}^8A)2XlZ>S6R+=3-XUr#56-o>{! zM-b-t6bOV={Fg{4=_z-;cal=w-DsX|m7_jVd&+xm2YDuT`*?=#3pX5z3>902txgf% zC^LClX+FIHUa9}~fpay#PxXe0L&!f=4=3mNler8sZ4&4XNi z@KaeKck9J7-m!u=i_Z{K+vZs98_(#ZnO$=)c}$yMrM30qNA@jl-h8a}o3XANBdbE2 z(gx_y!US30wg#5z>TV|0)7}(o!_aB=bD=Zu5!V%C3sKNzUT<#1^Q^y!SigO^^GxK& z$0xo|U)_6TrTOaq6HnJy7JbB&u_$87qT+#_OqG;lmAQ_A#*Tp))Q3pO_M3OBM8~IZ ziXb@<_Z&V*(R`B@$6TSy()(1@>yt$xXlv{@V%O2ecomTQTz4?M;yqSPA=l~W#UUYxw zPF};8V^9=LP1IbZmS2dYeeCzT(jbsgfCF)(i0=CousJw}=udE2p=EsI5*;HM9hhbx zuxWhS)h?(y;lJ`uVWkgoRZ?A+rwHD-lbJP(a?0L`4s?5e>Y?pZ27seatr3$9Gez+u zf$HME*W0JxS4=bhJ^VoLYD-mp5NrA1y!6wt>C+OJP2b~@_Hy8(74Ps-@9>5VqM~!{ z#&sg=UM73=gEE^|*gnxjLPd(Ys5oHC0EmE*O*~6-uLz>dvoAuZu%U3+<0V8KMd0CLc*7(smC{Q&Jei?Ldx}``t^Y86RU$f`qM%~=*92l8>NjMUBbl4ZK<4i$)h^>{gI|D@iow9O5jLP$` zmqGmp0b3^ryB{3O^JeV18hRZ#&@Y0HTprcuY9OZ8ErNGs#?MJ*QX<)cl4GGgy?VinVIvjT}0bfc$J>GytARaA3nzL3`mlk1Vo5EyWNwz)eX9~swTrD{L4BqBn49`;K(Y=_iPOAmqnflqR_wvQ z1|GyNpfjjKZN1B$L~b!R#5prekBH=4X~%6ga8qgW3`-=aR%R(dn%645mF@=+^SA@L za4z5}(M=?=CNemHpdYMZ%`&nIZ1rirci5Wz>Agxw=%YU(Ymnf735IT!L_Yjs%V2gzG)W}k0D zOGZ}*@^#sSNzzx>wH+Vg^ehw*Vs;CTnr(Gf! zQXM3=^UWXiwMn9`tKADhhe!ltd^25Nk$pbN%>cU_XKL?iHhUd9W-ce&rlJD!Yg>K_ zAUG?3B!Jdd_qO&8VD|HskQo8Q0N=3xwaI*t5e+6>F@gn_q>9dw$F=iMc*R9f`kD*M zrNS~1-6{;62{@{IU-)RCGh1#go3u-u1g!FjdhamjoRT`Wn&E#&+=?WXbo7Al}pr+RDRb8#5{0R#O$-B)O z)+kt!qGiS$YSqkiH&E}b2CPP#!`ClQ%I~zpitAsJ_>+{N1hP%d98phMd=K_er{Oha zrrZ{m(4#)WsI4$2QEjIvh@39F`(%cwk$)7-kS{~&u|`2U@LtmG;3s4vz3!1gpGSn_ zcYypA3|u3Je72R7V=`$#8>~VWKIgJ_coH091?MR5T_||2t8^a!E^-iZZ05;n=rip5 zqZ@FgoVWcM8YqEO$Xa*ndF(QX;O#gTQTFFtjr8pt^Bq0RNJL5Hw%D@Px?v}D*p8&* zR1#F}H&bS)bwQ0m%ZBmYRA75S|L;7JX}-aAbzk6hnSQC#S3l@aX^Fd$GV-YqUgPEC zr1FFtt+y%{rIQqyCPn&@>VUHPe}kj`#(DeW4#D4HFFriRE&TQYA74aRl#L$oL{v9e zF?su?HNC&wroCJ)=1boZad1DCn@*H&3d2ko{A(d{Xsm8~r@%wi0Sxd-$UOk>1v@xs z6R7&I?yk~bE0Xk8N0Q@#GZD^b<7r-PF$&yz;K>P%qDROv0{lm;%_LC7C)V7W%Q2Lt z;Q;J{Ng%Rx9}WaLXQ7KVB@R9ZGpI8>kNEcSTC=A!tbQ%L*&;J+1>vS(GlPn&@>9vu znoN%!y!8FV+Tg&}SnbFr=sTi)wF7W>TUF*hj<}gRl&W#5I2}4%B3B-=SXy9R^2%xZ znc8%XusV_`{<3vjzXhl`{Fc|_{nLO4bGDiR50-3S20U1>bqxrq9rguSY3{4I`8?;!W}Mr;v|s3BkC zTzvt@iJHe?paSk7-*w3^UF)dyfl|R%;*=*Kj<{(Q=tXrApAvL3KW29mhg}KVYQ|9H zV!LCtcfWoFIJ&YyBzSs1h`V5gZ0O?om@j=bFX-t}99>21(G&3chX!$E@)cS!pH2UyLaSNB`S?QB&?&=H(YnqFF ziy5oOpYJsGRA`(fZ~$iUn*&^U(A9Zg3`s* z^vH4F;;X&kI~;t|dXBpPcB@8|y?UgEPg<6{ln=VxZW#CrYj?iT!iVL&ZaKTciO$9v z+{7v>xA{rVK^Mn!W4s;6*iRa7Ep55jPkeiRS2#XOZD3~vNyZJ=wyB>kUE zg)_9mba(BCwDjJ9u!TB^>IUcbEnSwl<7N6z6T`B37vgkC8Qd9kmNZ{ocYD*RQ@v8A zQN}QTbR#C}IJL3;+J2*#8FT`ps@Yl@PO5yfTg3%UxbV?GIvM?M%(X%)3M9| z?G$&hBem(Cm656AwwUvgSZ<3CHE5UQD&3+)hus^_pq@8abS_r=-Rp|{WbWv5fsQ(G z1SAs)_1vyKn+o_y%9AP7Ouh~`w!&8R_GgCh_0543aOw2rv`TdzU~V0d|oA;^Ye-G>AaDt-40 zT6|$fw=5G;2&k$~V4ABLyt(2P==S1;_ALg=q_cb`*`gu_45)($BL_u((P$4Ne|O>O zsDA*V!tndwS(o2ybr80#o%QCtVce0^8&PN`l5>hiTU6`w#iHg}=8mt83V;gTfN7ql zH9_LC_%j7~MJf7bd6;U)v)b0j!vyi%a%txKTyffS7#1e+4i%5r&2Y4-B$&0?XRJ;E zpAOY8N~3L56t6m@4>)j=OmY9Wm{Lv}es)EwdxYC$e}AI(V}?n4B$GDG5W%AVP&@`j zsvXNtYnGj^Hwh=5ZzsLl#GL@k?0dp>fIdJzpRJz9HL~|;)j7+B_AuxBob$0uYChzn zC=Vj}*$c#ta9Hlr3{cR>GTNiPDi{#i16l(s>c9X4x(@`E1Hnx2u-vDNO5dORT@LSG zeu`pB=xZLZ+id=h#1yh5(Vh&NZICOL9!tb&y=`@{k1V(%#L+Ul5v|&;eKMJnv%kBJ zV{J2$sWRK)!`IkEHeTlCQd;S>k~DmgSWes|rXRjxxu07p{ga7<&>RG0J;U|Ub5`G|>YRTxbfsGy_2MI|q3Xrn((U6fw|t~8M#?ce@!`+gRXyrrF+%&<-qfTq~8v)i>Ph1fGhMIi!& zjkbdG9Ov*zqiU4htuO4YuI5&9czE<|C1&Xh7VTghOgoPozw_#kK1BG(pfq2ugnfc= zcM_laVZz&9t`dwWgK@OYFfV*+wwH?%j{Fr_C_<%Fyc2MWh7#{I<(OJDN#j@H_?Imn z(@f7VB7X>CxFV$+Jt8${TeTMhy(4vRMuSniZB2k%pLK7xuBc`d+xB<^Rw?v~V z<$dEkjdDPR}8it)WA73l&ekq3yHk;h)=5MJldC7W!2KC3IM;!V4V7U*#`4>J;-$4GB3VtI-Y% zsELXPz|_7nAWDGfarpPP0k@ayBBZ&SK%?Rq1CBzY2GmCC8;2L(g*h@H{9%zs&T1N& z;j`afAZS6wadjNn+}P>2q`)$$gq)ymHlPs=Y2?YI9q|EMUeY1?ThyV8VR9LCorj+{ z71Cj0($7*`qwD^vZGniCRUfn$!7!r^9AR}V!ZCR+r8(!%N^>~QaR=p^kM>LA5v$+K#AnaNY=Z@S5|=}#u%)kcu|Ra>N@1SX=G09$FO z!D@dNtw_Vi;0h}u4}jBU#}(arv3uqhxc@8{6NG)mg`G~_gn;kWF8Y1P4QMh2qfgX8DRg$tJV0`Bkx;-VQc6$N$(hL8jPMGYEx-%Vc$MaprvX(1pP zn*KWhxksb%J25dM)K5YrLs*{rkS`lpsE?81uaXKpCD`6*>n8x)Z_dc&!4enX>UaYMfi^8Lx>61TWN5AagEq~@8U_O^DOMcFCYicd67d*-%q zWbkZ!8cO$-Fr`pQ4xLDRtuC2bp6Gg&;t535x-LgrEBC$_-=)P_vacE za*U2bBhGcuBKnHdT4PgGBV(@Ez8{y%@i34~HJBWb-gYDp63t%>AKTFKB zeWhGXUtWJgeQR+{3XYUqueLw!vJ{j`2F_CkY*Ic;#=p-*VHEo)@tWy(3?Pc&z(G4X z3D`et(f96ZL4T5aBSy?f#XiDXYUnmj@kZ2!Dpl)5!iFWY7+5r1zSU)!YWPg{s~+_+ z9UM=jEPe>so!;x^sXJUu8upQDQZEd!OPqzK>7=$mgG~ZE3cdu^yy0pBya-y6<9N%N zsY-v|Xc5UqvDgrCP*1+1F!8!0eF$F4{4qi5 zBmZ}`kAqj@lrj5lizlklg;`b#A}m8S8~>UH!x$_Uh@<2KlX+=e7kf~S!)0<^qEZ&1 zmRoj`n1@+?PIuZW8@2&u$?ZR6b61hhPB#Z^jSvKbv1aEKh#bx9|`a{j`qQf`cxR$ekejfN9d^bqU=mvxV5^{Rz750@-Cj z$!^pn6`i!X-wQ06w4p&CbZwO;v-zD}8a{fZfHJ9VaO}sTIR%Oglwv<{G7`KvtwFa* zN)~1{&8_bqVcm^~n=-bp=$I4v-im@hs;*^y;P-%67Rd`*y=XF3*B9C1H_0xfZNvTa z#dY#f2!f7vg{@FVf6A#A{yam!nbT%G-!_u*7#-Wo+4b{=ET-8T^s2Vr>{V9N{@*qy z6J$S3&r}-6LBa;;*O~59^OFf!)_R!*}1gJOI#44j5mwN?OA zy8_U&)2{FBiXt>Z9d}%2e>F)IuKDf?NR)^1Q{dtRz(kWS(thpDRWcz?H?LLV=~lF| z)DI*w)zwLCHRUgCM{`ETRZozQ>#XU69WZy_Q3od1KB;*M8M1jRqBk7IL|W>Ju_`v1 zOa%aa8TnCJ<~2RSKjUANW4Dk>htn#P_GowC z>K1Y6EvFTK$t%8FWF(kFtC5ithpje6&+L9x*e=|pneeu*uUXx=4BhrW!;npQBL?yl zAv-O>8ksb&;QOMno7n3VE1g?fKn$}!x!CIJTKJnO`tU-tKa2f$ZxrytB6DDMoi}=f zSiQx|j~Fj&EQZh>{QAu#sZY<%*mE;gTVaHJ;ie!an~*)_ctYLcXlnBVk9OkBs2;b? zrja*9uwHPX=|sHF`2+0&>*J=by@A{uDX@9ac&kj=Lwi!00DyLH7)FsalRwAoXP@pthRGOK|P;%jFV|e3*#4?mDYb0BJ zURxC!dAzSM!9tfl@vb#81J_}oR_9$}erMC>+v*=?_26PgDAvS)zdr2YzVK`MltvXf zgJk$-)wEHIjJ;$CR8r~BM~J5cX6xWp`f11oty2tfM2M9PURX?5<8L{rj9 zQ^Y6^hT2mTbbmz7c`Q4L?{gW0`kwn&I_y2ES}hFPX+1mH+#ycZ>}(buEx^9u*V8b8 z<2%*5-6)Kg8H1W;-+F3@t(;AZW$y*)Sr|s^q`n8y`8b|)_-6@D5|1?3&R;KGEi25Z zWqk6b3;J^^A1WahFQ=MZoAO+Kb+zJick)IFaqSS!7FP zR4?lyxbnGb$}@2p!UKnLVFJ~6d%()>sk`a*XvtS>+lL%A*oGIC3N2cU9?phooc%Ka zAUgD{pl^^#{TqfxW7Z6-n;{?sRl0BsNauvS5lcG4zaMGdwJWo+rZEU^Ce~LHk#z=% zJUvmc83B_aUwPcXt16MPMWRPT(KdvpE;jK_S+hlJI^Ip^c1fZ)EqyeQ5Ayb3;9?3`bmWUMUnmgrof7i z4dWUbe{?824SzE(9bA6w-JDobOOLqbyX+)?9jv!#ooG~w>VItgSr{R zft^V^6&uJtP!|5TPGDY;vDnHLA`AG{D(c)X#}0lZU z0`}ZPrzeMJEB7K*p~WUF6YPP%ciaA%T@ayB;z)7Xz>1l$;3b*d#T1uM5`k@Nk9$ux z-f2iA(MAI8q^;q4;Ba0j4Yp@8(OLc;FOa>WzT|5npc>hsW-MR%X$yW7AnRR?&v|NT z0@G_qkBs5b>GYp+LLa(WB*a85r_c+Z5yAV-ULO`S)$CEc&Vroz7x}^3O8I;*>u8p-d@~K#DyTpBw$5n$@=R=L}{`)o(n zzlhKU4spax9e{v4N9+3v0%v)ym0x#TFIvCkfo z6WXrtS=;_3^Hf-BcXd5?G?!It&8rqUaYf(gYzNFQH-3HIwEuZ?GfiL9SlCoX_-z@$ ze3$B^k&5qjYUAlNs`SWkI-wN_G*8nP@xz<=VyOl2} zYJDT8-73v$ZK8}|j$&I_De99_QHx9)qXARA^b>rMhAP@&!+~hPAl!TG;Mx30>NG0L zV0hoanOm+m4kgHYtW%W)(oGe49M7GL0PumnZ5SaN@(KO| zaan&hZ!V>c^jc@)dw%9eKjzB0lOd_c7h(jfIR0pw7_F+7C=j4X+*u|tXOO1s6v&(? zRpmm?-@=l})ta&D?cDft-rb?NY?Bxdg!$>7T@Xq%A^|fIyJ2U#pw3IER$!~WkW!{> z2pw0!OJ@1}XK0P9v;P2JZp)YNAxUGwB>XL34h?&#K_Lk)*_YnRJ0{Hd-0|L;s6IYx z_Y|=@Rq+N@T0UmS+}NE$dqoQ)~$H4^~R)p8#qI(TUZZO2A#GaAQtlLlHcLS(m9ZXeh1Gl zXDn>uS2qzQ9z*31!eO85C-oz{5x$+}u0Q#&!%*a1EvtEWJ#Y5%EzG8plu0u3`qko# zCZk@5EMML7+2nqFw~u6KKt}z^ z@&vT@@Rd38PLCTc)HQlBk-OyWPdvIHk#>V1xS;`W+hY4+rpudAHq|^6U}GmU0X|AK zmRhQ^GL5o;dCo-Ar|A9QL0_RaM@ZtDy`M&+S6#-wO4}(|a-)G}e7%m7rXJ1g2Kq_s z>1Qf)w@jt$R}V&u0q_HR3>Bckz0dFt%KFkj4si;M$BRFHDRz3I6VF-Jiweu0FX^x9 z+%Jlf4&5o=XdF!0pqyRX=5K5YohTD*=Bb*(uf)KbYCFQ4^}`Prrj^QCBTOzb^^4}3 z-Z(96jlVe;Ig2kS;P&eLGx*4~u9PL}^^R_^`o->`TSggt1D7bClR*rsNH)B;J|)8pyokU5=EAiR&aDAkOC2F$t7#N8k9$D(jfI45}eQE;IG`RXD^Y#Y!L zE?M)gV!^uMwJ-L|n~$)sw&(XVrV`JR)O-pu{A}F!no4p6ad|~(7zauo7q3PNdV-9F z`2CKIytmIio%j279r|}k>q2^U&`Cb}xVH715zR&dMZZj&7i+HRtyhHfsAuB}ydrn} zEPLVqw#y}wrAdxO*{7rq=Jk4{h^0s-3`A}-3q9LH`SXwzTOWVnq#jPBbWh|nl>Azf z*nedRpsl3&mfV?A^A}rxs;$q6H^iaw*P;EM15@JpY!xC$te*l#_PP`_i4^Ey-pI|o9Qt92ZvV!923I7>>1m*0+eq;NYB6`*j#>+TaPKG0_cu2V^OPSDrX5Z`Qbo8Lr z9MAyoX0pKv zlu@8@wz7MC_+4oNpYKWAtuOzxVG122aPgDKbnQbNQ?^3{w$19r!(bB6g%>L|`;GvK zxI5T`1TraI7xKfrUY}?+r>rIY3|Za9){h7Cd0IF1cAm1P%sLHDqzeu4YjBW}^Wh@< z9KK{-`icbiBjPsVuBaXpyKkZ@&W^)c#b$k4;ZP44jb}j z6&BT>o<8gav8A$aEfHLWNAY8o&o9groS2%A`l+sFWriR`HUy@8>JtdDECp+p~~}BFd_lf5PtVpnIPf7W^Gv zLHwXlO{R(NXU)CY)W3XK-DIb;|ANxL`0}7js%?@c!RJAO?42{$S82cYYwt{0+{Wz& z5N}-rhS{s!$12n&*AS2J-U6NB5v@Ty)z9E-@WszZmiphnsw61Re|AV9KlK`d z(UnxTENkB=`#E71bP9xhmMbGfsgE)cK#b+wQW=D` z+ube+ZGF7|?Bq3$&kAH9izVOEIg4RycT0aetNuv&Mb;}`=;3n{F`P^DpUI7%!_q7s47^ z9Jiw#PCoQbSY^>b-6Dx8ls55GK%4=Sl58AAQ5(*9_9^!;=5*WyxG-?fL1|%yL1{~= zj_Z`79r9^$8xeV(JP3Q);>^pf=@KR z0xi~@9YGCkjuCNlh~Wf-pXgoncYAth2~Dh+|7a~REHB`h68p7?YrIWd1hJh`&fc#x zadY&|o4igpUA|bh&#Uwg_A6GHX`-1pE5Desg8$p97RMtNHNiK z;|JCRc23vB{#X(zPK`k(F`!QIm#MODTvvH-$jq+G*NL3$`_jyyh%O%iZi;*$Hhr6) zbxiT+A#TXutb5_I@YpW9u!aR|5r*YglL(YQBzul_P2$+RHP4!JW0e7#p2}MRc_+v_ z?ft|Mb)Kh--C;Ui3+1s=He2Y!_g-koFcx*#C3bL>ugGb?EYaEFgcFan?8SK>Xd2tB1= z>#}!))J|3#IkzaLcot5P*#(uHjZfu=Cdf~yNN(h_(VDD=lpTpQqy@el(ZbkFOpVZ{ z=C|y%^en0eP)4E=8T2V0k&=c3M>MbD6^%wr6DLa)b^$?32PAFpe#aM+bqQlbiM24{ z^>ew%81c$Prlcdmk9VLy!*E996inJ1#6_BUrH!>*iX?jomMZ=2D|{~&oWJjPyTsn; zfxateV(2BcGkU3zSUzLl&^|$FhiW0roQuPi4l@YT9NBShUm)VUZ>>G@Qyx_mpwHp| z8TRe%VVf!?Y@08jW_P@(#3B0lPndEd*=I~<(*6OuIa%dr{9bp?bF(;94mhO|8}-+p zh@~p$d7MxK_XG2W3zGb5>D!i%gv$_EJ3pBdqZ7lV`}vrCeCG{FJSTB}06JP#r#wHa zp8NEus1OLW9};llXxFUly?xKKN zoJ~-AF`c`1$D%YDw;~&3uji;$R-*g{!Y2lLqi)^oxhsE}iu0als;alNeVji<;a*)X zz0LQnfD1{*!%4A`Rnw>2v**knY z-{JkYRrYpWl@$X*a84H_)GC@bmp&w9;~_W~9>tXAGV*M`s+(D6Zv%1em?vTE)IS|@ z@8+S4SaQRVXIwWV5}R`0%%`Ef9WmsiIOig)f<6Oazqc(T#PhUCF{n1lkSIjP=QuW~ z-D8)m5l4NcawOoQM~RJ!LKIp9x8&R_^aD7b$nw^lamDFgz>ZE!rs4~?Jsh4KG_U?T zB`flW5AYXB#92~Laznp=Nr6~Wl#bZg)p_D3AdfX^r559SV_@OER13K7`EUpyb2~^b zH_Qu7x1%=z9DBjRCo@kvB@ z&ZWOsF;nz%Lhq#LEK;KUh{Bc%`g6YiOe$0egFMlf#-4yZIcLtlCHX7?SO-@gdvuOJpf{bje(14% zScsS@{k>TUD1%EZ5Sdnf>)n3?w?qeaQl55w`6j+8@VdQ$h2~hK6Si|8c>8D)RmI@h zU1pna5AOdg+BJ*E(U(dXS^R!tCzXsZWeut4C_~(t0rqOFJ+tfg{vId_J&!ecm;2@^ z&dOjSep?*V#gk3ep-xfzLyidV?H4ja0>o=Yt5Er+aW>X zDxdS=2chRxOUMlM6N>g9!tWc1iFZDD7!TU=;AJQQsA zmI&-m&!<(lG>6#hYNFOs{gaIsXp$Vc6w#MUjU8Jo_bXQsCPhX3Do8(zym4<4g{8Ti z_uXd^%dQse|H>k`8%|LFWTB{xehI>jAf@+VJYK7#rUx#T6 zt^&r9g31q^M3HJRPhZZwK=3R^8X>>@#%tbwt3t2a!UlGaMecIn&6D_IL~}wJ+c02z zc@rMcP!92#74`p&{&2m=CGQg>o@QTw-<^2N6eJ>I&olzF$Y}!Aj$POB4{9ns^FJN7 zHW@*Z?9Vcg*A+LBz}87v9E)tbKMs+P|N0=%XopvzyE0IexRhdK>yW>E3!n4~{UfvD zXCn_jQ@MDlP0_7DQ;;?D_-wyGIsLH=LdOm>Ch+csnnWBkX%*_(+g%Nvf$^FT0<9~U zrp|A>2$^QJ5j-bV(63f@*-9|?LR+sV^B9J~K;zf;5MxgiqxuF`jxl(zg4vb`3fQZo z_dzo7JYfMo%LU#^R2Ekn@LVK7NPZ3QXa;S#0!FHn{P8Bs@Z)SIg0967vNr0d?9$Ms zYK@JHw=>50H2KkOqP%(o8a3Nb)tor`mUAfdPCvc5kVx*Qnr)5XDz zbrsm0(GGjCBCRJ(!&d-$g~E1g0B$q!40RNh0+;m3gcTRo>Tfoe+3*DD^$113X-15mr`;QgcM}G9NbNu0&-xTVe zsOMo$J?JYhmhk$^)i{dsgV-hAM&VR`UQGUba_l~<%ZTaq1rNtyT208lX4L3t9GAKT zrxIz!!e*+U7Ws8+VN{Z7;Z#QA8F8~m{oU6$_sH9)cK~Dm9J4iBd>IhBsji8PjszsC-Tz* zqyGirEM?Eek2Lxs($bZjBrjVsyW~k@-><8SL@!XE`hNc)>ReJNGe{Y&og+`6*4=V^ zF(;>`hu?_aFxk7@Z}c3ZiDL6Jv#ED1Xf!?Isy=UvbK2hp^=L#{;iZ}NlMqOG)U0^ZpqW4{%Eq7wbS)IS(99V3uE{HlyV@=lYY9#O`eLIaP z8}o{D#|Qib8p!zGHwRWoht$#7toET52U_zx@Vnhsy z7(+p3vU-cK5NFnY8}=;z*?aKey+q}fp+5TkXD21_|KchZHkjFnTT+cV&vIO2FtK50fg*+z>{ zPC+>8eYrckz9gH&P?gk+wERWE40ifH(+S$ON+Ks$$P_D1M6ld|!Y?c}_1kdZCu zRR!l66Pfg%n;ehr%Nb^*m7#(lg=c+xy4gS!g^A00b4IrVn!zEsxOV+39kmI^k#WI+ zX^GJD`Q_TH+;{U;5P){Hh`rKWOeVX$0w)IfJHgUIo%u{L0$AK>vbhsx!{zu`ltx`d zuJ1{|6`WQwJI1h12_V`q5k!Kw0YT&y(cdegYq;!5R5#fm0r=)PCYJWI-JrkPsEQcQfa-1GL;=$c-+ctl{@(h3IKA0egW|z&UpWDGk@WlIR6fu#cpeJg8ZOw zMZ1At@{?C@;Pf=+ak~qeRjrWwU00b;rS?RnZsTF4fb|bHwG7{kG3E0d6pcz6Y>q$p z51YiFjXe@>h_!HCAXb@XzOa*7sHx)MXv$YDHsB5~~Axyr;vs8_|>{e_8Nyjk^* zR0K1WB^;W%u;1IOMz?(QEc}HtR5i&PlFmdc+N^E&K5jAuMLIuP#&KFq<{tlVoLTA! zVxrLXd-lm0$SVFw@AJY8@pKXHNtf;Gd)Wir$Xb=O0~A{`A7>%U;e42>3yGR|lCc^{ zx(z7UpvJ59%YWlXWw_|j+leJA0;H_A34ng&h?Irn7{(61dk|j>>Ae$NgEvx~dx#fF zk#d%!J&DR20eCj@NCT6TMS3zHa0cxCI+grGmn@n3iHE@fJEIwH$pc4^5zQUdE;M_E z#r2-8$);Bw*E$Qe^UN)BXUj*i@dN>p#r~@$r(^za`8RGrwxN6O4MR?b(@{=z#)mSL zZ5{)pua7OAE6>VD6cx-s49PyAoOEA|%HH(!$osM^lkEC>v&;j7KaSi0x~u}xLhaSx zlUsk85}GUjeK7r^UeL|xJ^j!6d!NegR0_U`yxL6thi^;2`R@ET5YOXOZ_c26Q2RQd z7hd(#I-7%PQTD`(b40B`!XYYQ1vsn#ATe(}1lERK`-b_`E+m%G$+|p(3 zX-jg6>wT*}0(~91PMm?hj%~K$<7g67FP?r~n!MS5Co3az^3P%cP2Bb(#GD+EnUXDc96=qqU>T|5g_L-?q=maUw*?uc=kD9kjnr zlhla?Qz|N%NF;O|sSrjHTj)gS#?wdVu|~4l>qXSD$ycbS;G>VW9yapz;$o#M=-Y+_ zHW7_@c;Ll$!C^;(@qF39_hOr86T(%Ghbzy~E5<{wrOvOw!d0Uc%+6NmR7F%9ly~k& z0<~`6>CamBl=Cwe2gc?cswXPvPkzIM7Es{w8Fz$&B=SeE-93w2NM{=D8O55xqGeZ$ znQfrJ^KLBJU?AFYMa#vm*v#y@z*L7gJk;<0_YKSN#j_c_qWNU*TuPcg^K(ItgITv! z@d-u40d{%g!zGfHt6bCBdL9)COp>X6BfpLme@dCIGuh+^%Q)}Ne{%InX8-j?nLdQG zNTqk^BuXp|^Lt4wbb4?}XfF?);0!O_3!qmiUPZ{Zj1QEUw!H1zN3_^=t_v=KoCANI z%;2el``Y_Y)GAG!#JVj4ThP`(D`PFY{(4l{x(30ct6ugCTk_gQI%c-)Ott)t5%fB( zOW{Wk0Q9zij*hl)ML!4%^Zu(6Uv7~V@z9r%;uqy`3=d8Cz3=TmT+Zdz%~~KyE%AH} zi1s4V+w*Jwun-Fl!;`nnQvzR=T*wsxlF|I^5Q|dz%^J!6biL@pmJ=IBlZ}PsNkKSH z)@p9`MSee`Y**RTw#o08X~|m*S&0;HX6CUd^g(Azt`7{YF!g#sCt#28FrL)wgQ3FI-I88`E4XVOnqVLN`r3_WBMrUAR;BJiM z$4Y1p?xVne(^afRKd>l7(6tdhdNA7dts^Jk*l#g&j<$fyKX+Ms#zy=Gm6gv-#6mGc zWD9~tarzzG@5!y`c@BASq|66oSrSO54V_87t4)gbceL*1yDS&J`??pz^GpTwMAUrgQm+USU%t`$}Os_NfJj?`5us*KOdb0Vh4O!vPA?a%IcnM@evwzzPC zD%E$4IFica=cnP34v9JrQ>k7?2(eb%GCSkj`xA97K$+qakC(}f?TkEWf#3s`@5z!; zz&7aJ?RrHFYAWj+SKC6YD%!a5{eZNe0+Z;5W#k}c|L%@KY6DQX^w|cRY;8*gmA~g) z^$2~^*85q1IA*~1-bQTK7+o?8^6ZkneDz>r$eQ#l>2A<^3i5Biy{FjL{qS~{9INBj z866zqfaLOkA#?nevkJ#!2R>mvGqt!+#9Syvu5CxRMwBNAwdr zZdNoGyiIeSKv3Y;@2Hdig<+J7HD0_?n9^EC-zD?o$KSBxTQu3i?|k_s-VK-{`tn=| zOB0o8IoR*dpXobI`#~!gB$p?d5 z1w%LqAuru&M67w2;)NtzGU~qf&y$-VX|wIF^z>fQbe~MfR?0S7;=g&8Pq>}_8jy$c zY{dod7gm{EnQ-f54G3)ky4sD4!&ER~eb#K7;*e($18q6GqL zG-P@UxqZR@OdbI8sie7&{5_*}5A|_Ok(H`efofSrzZHET6VOPtc7Bh430(fe1=%=) zp)NF_A-kfbju5s=N&l7REkR8;uZ(2yiCj`4y(rE0X*rj)@s-G+o_7)SxnY~(yZpQN zkoJ;W`uvB+dpk^s2zZu`l%~nFe6LN%j;2k%Yzg|g?j|y=Ks)MK?SIt9ZGQZP^M8qg zLZhLMV>-+6vd_wLh7qHcCN>J(#zh4!R@6S+p|rz<_*)SR9(lzk;Fk(T!+N0}wQn~P zszca>Vec`&=#vd#?<`NSb{`_FJAQ?{*kDRp8BQGt-iC%GH2(ehS;S;&t?X~WMLBT zAyY*v1$Z>A#x4>f0E;oWW(nQxI=LX~bfWBMU~$fiz&DdWx;#x&J$yJq1NO=TLz@@e zf90k)TzLtBU;$;Wk?qsY=CfCxwYKRVmwod2&~ z!4qy$8SdPcDS0zbEmCK{wzMLj$^hq9y3c7G-Y0q7@5Y&ET$KX2MYpt#OzbmMINT{= z0cI|rwiL0czKwD5UxS&+)Kpr9LstjU+IPA+=qINqx_;q6K{Z`9IGFL zLipv}b_(TUaL9p#7p~F8HR{c}_wbYKav1E9)=JNJ9=59hnrO*Yk2fb0YlREEBY7Mf zZGh3J6rF%xVi!jeCde>9oQJ+^xL!Ec$psqCe%+y-eAtv_cWYpwP~z($+y5kKqQa~KGOVMk zott|`;M@1qb8VUPSt`++e69PlvVb(~M5BoV_eu*8&v=qbe}uz_vmZ2ZT3z%xBtV}dj>VVec!{^ii&^&f`asB=t!?o=^a9^ zk=~^De!YrxklsP0N$*6uN)ILU9*`0`L_!S-P z2lf|%MJxMRQ?ojvPM7fjQs~8Kl->Quv*13}zdZ|XUwkwR0FO?;!5E4(g2P>gEz9cW zsM;CqWvL8+RB27zSKrfa1hI%S&8ZZ!EA8`WU|tLP#6@C2K0{lK&z0BbcA+(C8&%8) zbVHy5ns5U0T%&8Qk{5b4sSED{C*Qs5kQqYFOqabE#}TCub{?XDj$^fX z(XNecY~}d&qK0etLBFTiNfnGV@LPGw;+&BeGsC&0eEuE4njBr;&}1Ag%M_HAMH zwji*8Rsgk;nFq(zQcY%6$fnE2H)3W5&N-t( zB!e$*yBt~usgux7(#g{{bWj{BL_S`=`F{-0>)XWt5@vsWBe-7~4A^yPHsMOmjJ(YL z6sLc!c%qp%Y-FnWXWlR3%WLk>7-Mg)%Z8u~Qv;YjsndjB4DsW?@jZclO8NRV*$V#D z(4H4;X;_ON;zv=`<7!R4uY;h?I*bsL`eM%1hCco@v|;`OF2sUB8u)#(pzsag_LNpf z3YuHp-4vJ74&}Edv;AQG!cGG&pfAN_P+V2d^GBX^!NQmhn4vI^{-B{s17aP zgb88CRm3~Ji^g#gdUV+A33i4!C`I^h)9crU-oy$hPqG@lO7VQ+w$8wi;q!&~)wyxF z2WlWF!|0hZRhE&%$CU7-6qW(Umv#CXQV3ff4+%iIs0!A6R2VeLkFpzzmXbe=h`sd4 z-+k6$HS6=`o7?wAnZV6`hop|qpqok9s@9TJ^1Kaz>53k{S8Lf^Og>}=8VnNov4 zycTQde%1tlrOCYWSe*lpbLN-TeMM<&ewW8vu+ZDmGJ&=arUHZne;ze&@Uk`O?<;4Y zJ45<5rq4O9@+aCHFjz0kt-Bd*{HZTf_En2bK3EqYTSf6KX=Vm8s5UkjB$-}~>4sSI z%YRwoj%dv_0ZoAi2d8j;MXyy`0uC#_JUWOBUF|z(|3}VG(D2`KezaS%aYc+ME71BC z<<6^n!OUUXE3=1`g>B4Xk~u%fjNjK%)(XlyDko&=BztJ^Y&N7O`?DWm<3ydH*ax8t z%z=`wHMLKOb0PwLB~W}r^$92_iO$t@#Sc@lzzp;BUC{*%8Bx8u#Z$y{3#8L`(or``g zEDl-}?QEh`i1{qkd_Evlv~T&r`0(Y2=~J&D-rt{y>BV^|0(C>bZbW6jahycowjZv) z(RqFQ}i2W%Y+R{(#S(yW=(e&K7cx%6Fi#{y%HEIvF^}% z3!3WiF7ZBpD>dz`4foO- z1!cI{G5#EBATq6bz)97)Oz(j`%zLK)*HX;{+6QrY?tdS|x1bg~l^0Xe27ARnwKvF^ z8B+3bf$Nc@S6+q;q$w)$a^+srwt@1-f#+XEm(qz&_9|=PGHdTM>m63R2dPRoUzJIj zvOX)gk!U>6;y^?n%i!Ak+wd^m;g^_tRn@DKVzf=erQ>Wvd_v_hrSCEe&s%-(QZ6>4 ze3BisShiPJwwCP^BUoWLO~cDZBdXG7hhio^vU{_)6Q+_%TVk~&im>kZ&c9(|m&P3n zl^tsCJG^QZ;D2-!d+*+nQ%LLpUiVHIR-9bGwZ$1oBHk7*QW->M4@3JCp48#mFD#g< z7fM|DeP6qrNglP0sJWW^9dN1&+&gOLC_U|(2=o6=>rgx|TE$9A`ib9FyR>-bYQB8+ z&b_0KF6E6TYB|-tDb_0QK#64xxJ}FBxmm@&O2TPVMWQ5FMJIDPacU&EPTx~TCgVR) zns@sY#}QL*%%QFgXFFF4NW+F~D+vqAb!zq6h5x}9cQ`(x+bp8@WM%5JZspk5*6D>0 zl14GAsVC7;)y108!wrEhS3Foq4cqD&&@cVx45G<{aswQ}P;lpZ_^Mdlk5@+ILDaFKCtg@~h-OG4q4NH|gJdNahvOPUP+QA`MAb~4r19$`| z_-ZR;#Y3QT$HU>+>eUifAm2-b!m(%Yy-)2P=05>z4w(5knU6*FE@$2+tHE@$n+r-? zIpk^X!Z*PUYFkitAKs+fsRK6#lXGnxujM~__@SITK64f18;aU}mW%6j8TA;>9D>cy zrfooN=D>7EKep2uE~gEUL4k8{qXlvkg@;F7XLoNx>)ahvP*=Azl&-ua***6QRk)XD z^B*3g69JNf9L9h4SaaiAX9vKz)CQX6GFsAm&&yt;g$qqp*@@xdc(8^Nn zu`)6qLNXb?Pd}=WsMA}1el2XY5JEB~mir1FlDS`oOD9Ld7k5u^relAyJkHs?tY6EJjy!oZXwrmd%f+;N z*OjiYfX5mT8ciLr4?FmrlqKwzadB(Vtyb@?d!7?>T!&45w?xHTq8R3~cqbdbtVSm2 zmTtvPc@V6kAjTn$nc!lF)`iuy5X;}UH{RA`fqGAq`$;1uo{ep?7DFs|Ps|_5lFy`# zxTd|=P+sIy*4y@oEYwwTlx>-Q9Yhy~Cahi7d%CM`Tx=|S4})B(cAX)F*3J+>Y*d<^ z?Gk}7TIto1d8^CJ1^k+Pa&U&(*hG}t15hV638=H(Y|&0{1>NIb7cXa}h0n`_l~#VW z?B&1yW2q8AGwrz(qrc|Cxu1m7IOaL| z*rccgdYI+7U-dHUa=+?hR^$HB%e-OOO&;x5dxs(VPHn{FXr2DFeS%M6Sl1;L7D`i!|#7*p9Q@b zj(sV+$(8&5vOd*IHH-VP7keQkT-Ic*1=@L%6G`FsY&5bJbC;54+Xb7eI@p%V+pN-t zy)Jr8A`YzP*3Vy13G1_rA<^snjdligvaS{UXztc&h{SidTbibQXQ}3F4WS-6j~6_O zba7RR2Uz>qaeRVKrHij4K(b4o2s8Y)NBl%)RJC07+@6J~s<_Pg_vdXaQ0%B9MRavSl)<2|2-|XTD!>-a-%kTjNl!)=$A(W=I=H=md|grGG(U`e-fGjI^ ze4~4nFyuD6iz)cyEdP%)o6moo1xowv$4I<;*(ta~G9u_u@CSp7IX%ux6+hpV8gDZZ z$9F?+Vx)1sn_*w~@M{V^s6B2j`1LK5%RA08GX;j664gKBp6X$XIau?zGRnIn174P8 z8S=n#^pfK=tM!QqevQ-k3K~M@a@*o>x}kTft#3}o*X3>-ap>?o@$$c|ll48-b}LU@ z@aw_-5K~G8wpwvn&cTn-Hbt_KL7PgZE;k1}Q`a=t-(^WSjju%r1bv3;`cMv{Cef)H zX#sHwKb3<|;k5s(?rqU2G>Y8 zZk@0U)3--^K9-MSarC{V^(Rg$#DLNDz!Cnk5I@^DIbsl1lGT%=EI1`q@!%Z-Dt{VM z1(95WDxZNr3&^{Uzd^oEY~J}|zdf7R9owEXl1INDmU~b6>A9FZ4c0nE%7~4VL#0ZW zgUH&sP-ZFM_dLQUvbo|j*f4d!!dpkpDcpko-Mw|)-IQG2_7nJ_ZY@}EM?FRLrVYQ| zzUmZv?96?bT1n89fw?*srWdV>y%af}3xnj^iKRtaw=H%u-6QCm{H=b+JAaWvy` z$$7@AK9%{TbK7)I8GkjtIiqPmiOS)sKeF+$aH|xnQ)sbIQ{9JuF{?BxkEIcf_M{Ps z7BqiS4P8g(E;rPU0cqH#Z0rdl!U7&Df{WSgYg_6u>NG=vSIx44VKxeiBju#pq)*`| z9AAgBd zta1n!Co~dWw_kCZ5r7EE`yw7FYUGLGb#%ox9?u?~J)aUtaNgM%$_X84n$#$CL)oD> zBZ70>Qz8CYQd+<0Y*tUQCUZKQmCG+yd;bf=95WGLzX*0ST0i_lnEX@V zjiFoMK;}P_XYAZ!H%Pw#7+I|1xPS~6dXgLO^>9S6H&*I8gk1eqX0T51gK7Rw- z9!vb`_DS||yVki->O`Vt9VOCTj zFf63+%QiMylK4nc*RjE!4cV~CSzHlk%R%^PUFE0I@CKRI?VVP1bn8)D4Q|-0cKw91 z^s`0EqPvv3Lx{XSS8h^c9oZUG{I{rO8auO~cl)F|yHd)iUx!C3IMeQS zv0unIr*26UM+F4LcV?6lU|#vN(J~|#4GvYs3QvGE(>YLjA3%20bAc#xT9dPQ;x|Ky zC7T$;CN>rExL8W0^>7n+(ahM_$%yhMNT*<=vp!-5fr!!5WI=&>yG=v7{(d{$?b*EW zl`Bke$2X8}B?3w9rEq-OMA`~Gunrq0>4(yV6T}G)oksYLgQ-2o9BoYA1k9zxus1Je{T~ zZ9dd*-Ys?I8lHk0oDl$JS;C=Ty;zLv_5V{{;(vGRUtVT4Z)L2*Lo7dk*Gvs4osel6 zLQrC7nHV4;V||Zqo(JWPu24JSAt{jx5TU~ahz;Xd<LTnkq@Aki}4X^c08^t|s^k`fVJyfU0|7b>tM#U0RKq+~x zvP&(y^hLFn1;&R?fWP)z&h4FQ$?~yauKMmk-ZMbB`t?M{6^ng12gmCW&Y1XUh`erIA0cde^UBHkaAu=RVr%G8x? zHzYHFA2%utlD*@NeTUbDXSLe2MT-qU$il zoC_7p(FD7*K<#2lSzg6&+tCu22hHl)lKg3+Ih#(Zj;r4OSV{AV?Z40ztxK)ZF(=E) z!pD`xCLw`j6P`-elF-#5Nrauz&IRP__ajkE*py3t;V<@D%t_;TvomZU+`LV8n|}FI z9Np|EJ!y*S!QbAV+i3?6#u0u^n;(&LlZI4g$-~DswL~W^crPknsc3K_&mLJs*xdLw zce9LYX+I?V#{9vXt`xbY8m_R{>6qYXsmrGCy=BD1s61dac>&ME1lP|DhL%CAFvHGD z{xgUY$kklD^(!}4z;AfAgq%C)eKMFDNU#@V+o9L~|JDe(gzMc%V!#>!3BGzU?fyJN z?SqZr{UnQY;L9{;jZS-(^zxh9!t zbX6r+E1Rv@=~O$~{ zn&&;ix1-K1vZfpxP0^hzP{M;20nD=KD&p$k!>Fd~9prg9Z58fvx)gkjqx~F8ckha_ z^TEY7IyA?^x8;pbT3|WS5oSW?v#D7r(%NN10QJ@{ga&k>Ls8fQHcF3q>7|-sOWyz_ zRSA$}{c@UW*z8Z{_Ibm(wlG+j9ARZ>YBMzO?5qQeoLza>XBo(#VsF3ASYKD?)01h1 zIF-DQbM5@F#nqB2rRDc%$}K*l%FkPLVvMO~EiXMLJS(Vp)s(^j+MZM`=|I8h=o?vC z?VOm|%bSSk@M(rzotZgyskSymlKG}9TIs)*i*fzOwu%3*yZ=fv{Ib^l0WgbOV`x@Y zq;68Q%#P*h*SULX?qQ{~J+Uq4*I=WoRGNN;Qn1BUNL=eADNKL;d9~p~TI|GLUKLc} zgzpCJQ9OhGgT4K+Jf+l#E4g7j?{L&uUU~-9$}Z%29kqPu^MnNGnx(<7TCVrAk1Q+9 zqRg23s=ut_q;=u4Zs#5MvCY=)K^@yn|$}yUxa6jG!Uc6PgV;ymdF7xXLkz& zN$LB;25pwDD_T40*-3)ZV5&#?a7ms={1naU{F~IFKj}bZE$vj*e3T8d$PAgRE*iHF zT_gS=up7E|wgS);r%2)|09Ai6cD0vmdHc*?ldY8=Qkt6S*K*#kuyI}>WHQ$R|GLRE zHW?iB$RIF~9IE?`Se%b7a+qYd@L&P5{&r@XOOeJ}82!+QHpEPbAzMYfSM(nb;K$!@ z{);G1XL`QQADWvM3vXn3a~Qs; zU$~g(a98Y|Nc6%ssb9s*@Chq#;2D+oVeM?Opi>`WQB6)Kce!Rv%RKm+J4Rj9NMiN# zNJnRGl{m_Rl#*s{$+Fk*(4nmK;!@ajr9JEXaOM#cdQIIjw4SvwW_EpPLc^xbFWMNB zD_4`Uy)kR2{Erpkg9;#LAKaaor2T%gTgCfL$fHPi-7C={*mU(E;I6+|Wn$tK=_>i_+VcPk$U|B}OTZ|RNJ z`Z>!)BR77{R~*zVFp!R@VjmW^Rl4nOABZ9%i}9l`r`$G1%;cOhlHP>$yre@@Z2|9r zh7$T#XR{5aAFI;OsFjdbt*8u+pVvl_{1{DhSWSSlmWKM3FJ8SagLrw>8~g6sR$47S zRsYY>d2$Qns=>qs`7%v;p>|KOw1x;!mJ0?g9#VF^dgP&3%P(K1gQAyr(9j-)G}KZe zjj!r6lKXf*VhoYV1aTh+kfN1VjUhZQSJ{8{I_PY-7*Fm45bwTBs7dp;530;4)UgX` z1*xGeFPTs8Q^;U~AVAByA1M5On}xljm)kg3C{s;v~zQfYJ! zQhi?ukzy11aL=#3?x_d~_?<4_p2!=nRS7f)7N|oa!=h>ybDmI;d3j)TETAw)`_EW6 z=Jme~KZhPXd7kwWF8QP`EuU22IJZ&lT370GC4b^BlIQrl3XhFh;`kwCEM$TzyajR9 zV=9l49r#dN2z}=ZIEA-P2uo)}G66mm@;EaEBP<94q{L90Lxn+8*Fl5~7dX*0LRma1 zEDptTuMw}<4a@^CzTuu*NZ?$*pf;td$%p8IX=*Rp37IFa2Tcdf=#j~i*}T<=OX`(fYjO=GQ(l1Qv=TcTD}ofoI1Pa5N@d)C>c z?eq-t8CaaW{m&GJCH>!17(uxYZ!})Pca!?ZS<8eX57aDG5@+$OfQ4Zsm+I zqI~;Cl$#$mN)>OqPh!v-3QpLh+AYJYE53UcZN>6zvrgz&JcQLqBeJHU#>H|lu3O^WL z<=f`bkJ6j9+%GwQqH=E*#Te}RJ9EDjMbX+ps(3$QqP=yl%vZ;O`S6f2l?HY@BbjWZ z(A4--D!2{GhbjppGHqyz@nJJzhF0+Hg9NGAnP@D@Gm~b z$KO@?zB{nSzug~q1~@~s}RJJv+~P8p!*xL z)_TmjR`zeLk9MTgoRlN_TWtr@a z7#4t7#VnE+JdM@fwQN`u5VYvLLYZ_)Ea5QkhI zQ8_Bh5IP$w#4dVIID~9OVI5MvCW0z9qwF&;bsR+e9TPSxByZ|&SAV`dQ&(52A1ixs zd^S>cGe5d9kf;pB%(naK-gJ4pbQt$O#ep zAQ_nSwwhpBq?PFOAS;(&$&8^QIq<@ObC^TF%!Y@n@vp;_vBcOFl7CjV6!@?GYh}we z;=aE%8zjV@&EJyE+mg);mI#6lG*!sKiS<1ixv*~y^Zkkp8zE(;AcHDhxPBRyt1&Z$ zIC;-o5Cj(1sq^L0stfc|^(l6a1i1>;8xVZHXClRuFlKirK!i7MuW&2D0U*RkmWKZZ z7$W0A4hlULeETy51(fVDi;UOREeyafUsWnbqFxe$ul;>eqA2?65cmyJH5Q~kpKXRk ze9!j%R-U*0x`LDq^KY)@k8EjA)!LEq>ic|y>Bz>|3a_s}SPi6|AR0@<<*`Qg zzOH7}z{BU?p+*ZwGbvI5hG>@XwJ(`s0gQ1Dkye_$@|N^{kDg%f6}WL~yopM3Tw70a zSMkiww3|GAOuf|^nUPMAvX$7VCGN&=wg{B0rA=qxhlb{wRCrBNp z$(8-`|3KfN_?o>%`Sdk|#So2&U0HC#&&=EEL3Ya7qrNzaT9k}_=Hs|D$<8PPjgHJu zP11RvY&z|u{u2PyeTztr?@jP_pLAz2v$E!lc@xNrQde-vOUR3%fXA#_6bMmr_|l}j zD#&!6B;`rQqzHltBWiGj86zrft2k&AUQ~pa5ZpBsLlO22mok*G{ns1YjC@m@y^-0C z$q;HsY%uLD%gfa9E&9#YYZ-=Tl`dh1IGb{nir`a@txiu_;B+!1smfJFQM^vi9snAJ zKbv0s(YQ!IH{hG!fHNhHXHfqNJU36n1aBGk1{mmHoeZSg!xdeMNQfj2C@Rt8>-l63 zWyeg3ye$qS6A-)Ct&aLML{JlFN2#X7UCxqoOo_qWeedL1gLW0pr-p23-0PG&^pf6A zMytMqtgp@(umyPIpDnd{P|u?V}R5LvQ^LWs(H16;9g-Ya->sws-5dm9Hp4 zLLV&|KqpBa-#JnDOR1{H6_k;U$(hy$EKuyH@SeW`K+j?c zbVz#TBZ2_5RTL8VvfGpp{=!>lD{&(iwV?B>xTAz6W*vsDI&&y{A6+JdAsQVI&)B{ zvbRpo&J}AstrV*y2(r_7{#UC}AY^I&DrsLxPjjMCD5>>>v^>j3rI6^?%+r||I zMJmPz`6*1s(sE9*4K4!9V2UF!&x0c$WIF8(cV#+Sr2a{#*{^SKRY6d7CTK%|n9gvF zXT!=f^**)@Xc0uT8E-!4$;3qO3S8oCBVTog!4oiw9pT${o6Y~bPY0u#%?lLw{hl(Z z&N>hqXEWs?{|aj5*6&0(oqf2TL5jnN9VRHjRU_*|t1~tH(b@$ThylOPzrwt&eWxVK zj>R*pXw3U!qyl&Y@yFu5wO3*~!AIhzD}wHLEm4++wucvnP)`R@Uv)Wf;;r$(|G75M>ATH-C(XKLsY)JItE8+`v295dP%Q52(#=(maS z2yjfQ_sv%y2^|4**g`@MuY>YN&1~kH0wQ~Y@-j|%o*s$XT3&rq8)~I`wy_KxOyKlL zB(@V!7?4C>76ivS=9JHG8KG1Zosb)LPtJ5oV@+ruC_=|*{B)IAm6z!(T-$+)Pg ztyx8KP7qoD`uBIql78E0Bys(LvUh@)=z!YCH`ucNZP~Qi}2eDqOUhqCDC2S|MI1R%n zY)2^Zq6E42;ssZZq{l2n)CHojIjF56E5c{;A%w(OUC>GR|971#p+mv+C4En7nz@-Y zi>X0(hGXrl~i>Dgfga-E;VJ?b)NZ%v8bsswi|iN|0|rWh9z*s+dE> zk>v*MfA<=DlnEH-NN$bKJBeEu)?%qhI;sRr7r7`Crc3#ia-v}pt2gU_Vp4JfYa_4n zuIjk6QS#&VONJ~LcoADMUeea*(h}I{*g7CcRV~!L-^^y!#3OcDTC)r6jp{k=susz30a!|eu$fEtv!&6sBMgaui^qDy(t0p@~dq-Me85VsGss7{Uk z*%|h>6-PQtvB{z`3aO3D0_}qt(j!l34jx3b7|7MFd{qJ(iK6JJr1Y+w^4V$X5#fy( zw_;2<0;NIX8FbL$xALw1u4Sliw$S0cL~}=A&U1;}FQh@qUr1nX|CM@~0W8RZvRfUX zIMj8OGJAmV>aAZpOu*Vn&Cny_`-sEUEUns-trD};RYKb&9#%JIc>yv5I)es$A2FWC z21e&>)odR$CFsWjuIEWNnFb+A>T>OXZXI zrd28MXkO$2QL@;$Sl|D|B%WpO=v=D}DHAO?k?C;P@Lj&rjWtYITSi<>@D+~yM9Cj` zE_!JbuMT)4ZLrFoI;1=f_@O33Rs`X}?r!B?I;b5IJIyY#!wo$k&q=^??gd8Aq2tf) zG-?i7E?M18=XS&#mar9F7)D718AMGVrLEh;1v;+%vuu2a{ol*R_jST2C|KxJnZ?<) zNUoc)@h4pml)!Yz!M@qt#TPO*+_6ky6uyJ8|2)qAg(A{8^-~tc(+<*M@itkf1r(ZT zXbeY))(=D%Nd>Z_4xzYXi=#9`V1oR)I|XE^MwxLsUP#UfdqY{tf7JpIr$F}{PjmoY znADU1BX)MtsD(E#TM;WTqvwL{841P*wdaC48B|$Fn=|a?bd8`GAvjV6XnmZ9{iZf2 zKn$%$BGzdEk@9w}@9XG@6U@}V#2#(PO>}|M3npQNi3{<*EucKZ)mB$?!9l(z%d0~G&loYH zULupBf}6p258eUxkRySk!1<^Op*nF-E-)N%pHk8OMD+-gN+t2L)xXa~p6YV0WP-wN zVUcM_rb~nNf;xVJ{8yMWRo+a_lm3{t2h^E_j_zj=^X9azfR;A_2}S5GG1*NHh1ECF zk#F9JYKLKk+BZ|Z)sB`t=ekZdAD45js@ZWOR@h}Mv3Qo4kAY6v>1Xpar5hspfJAtC zOvmGDpQ%14yZK_x#Q%US=HDZ@>z>;7U;mJqZfw16JzLz*Jot;IDr|-0mto9opHhL- zx}(CA*OSstjT)Cp>!L%2_&R_HoKgoM8#%R+%k%mLDlR}(%<`)D`UFA(*^5VsPasZ~ zHD3*Z_oyNYbD(l2sRSKXiej)#$piNeyRkYkZhNEN|DhED-yMd!;rn6lwsfSnwA(DY zrS=-mb4bg3cF1gu+j;GZ5#Z2-qQ?T&E*7_sa;lOAd>h}~N)(Ho zM~>|yju4;HXX3fuAEp7Y`>^e;<<&&u->C#NvwU@Il4DWc#ZcqN9D9$(b4aQyq@>5L zQ6f0*<1+g02vmSCq9T2{MPxT_rbK7SE6LH`--bi`SJ69e|8Fl(gzp~DrYFsLip4pH zgv8dKXbgJ=#BAKIn>c^FO7i0nHs1Z?Gb^atvwYFhvka2eX;hgvTPWNnbBUq-eP2{& zqx=$1^r%iVfBsR6yhRJ;soOdV)qY?xuDCVX zFF!;8YB}hUkzsbSAa9;7CXu@I2uK z+{#NQ#3!;5zhLXLE*dSgakgu&a;S(*-W=XGB=-$XPs z4=I$wJ$A(M1p)-l^OB&1as$AW??XE;5zW!-KPC_#sR0=R9#WVnWsThjWZXA=T+#hT zsm*Gq7WxjmmQ)7rh1X)(QxVIp)5`U?B~4mgWQSfO)Fv4nBaM5dL=J2H?PU(1eT8S- z>{-AUb*wR@E|eTgRE3Ev>K;c{Ao$9ft?=&4dXZlVG?o9!_E98k+u1zNVAG)(@Ctk` zm_6jf&VphYFl`=UNC+#lKGdz7Oys79t8`Xu^c%NVm<(2DEBgaumQG_ppCTKWyp&`J z^N3vHwa(->kS!o|-b5{HNMC$1)GO&l1>Zre5BK&a3#wao%aC5c^1|PY0dlo?^}p)_ zwFU`1Xb5C+<;7j*_cwhc3vTXv(sVAB@%@wJ#F|&YCPRmGRC3-I^9lS9=Lry4@ENVd z(&0lJG8d)=XuXy{%iZJL#Y(m938%S46MHM1a*vk?pb2fPccmN0dc0V#{f3XCaOp>JfBKdgDI=IlDC2rF$DvQpSnd=UuCgU zaj5n{tP^a+lzG&r*^_|T%DW8b{K;F8+=?tr9(S$PVaA*{P>Da`n2%5`pqYMg5aFyo zfjT3U@c}*@p~0^r6LUkB`N!hiE>V*v}y_dx9lWn-*eD%*-a}2K%)#SGv2|HkDVe;z_r>9Ss-;@P8@%Z8qB3GkVx=aMXDthv>Qe`l`|LO#f%0z^$bI4Nu zBRrC3-|+YMCV`1)oH^K^CL>EhuW%x8JUv_J)Q<*|n=_(Ua@f56${^D)n|G>kAb49v zJ##*_IHh>IIA}tRM-(80Pg+2tTB@p8dE3*ZW$x#=Aa8aQ9vC9zxWLglp50Q1W!8m3 z{ZVnKg)}+I)VkXl{77FAN+LQL)`3U`p8)RPZlV}9x*_!fl`5GdyqbryJ4zOe;+CN2dLos>}JPqbvN7Wbm2>ueY*MNWenzfX=g zWDYJBV8Y1Q?%v=lwLCNY?6raQYCik;*;+ceOa!eyb&^~xO9^Vum*A|d zOUB+epjO|V8}Wwt5xZw#BDTUh-CLkQJp{XPjLT%iF+njPM29Am{8dUEoIs zyv=;eZH91kx^gqVamKJioC6iPDHYuj6m|_1y3%FehnaLgMoErq4>Aap-psnnP#DD} z0n=?v@uKrYsM#{T+x#mgSRZ+vLQ~i)-uRx){`)3C^EY!)BN0Tuj2}j1ms!;&p{uyi zU#%`y5w+Uo7WgpqLpyMDvQ(BgXllGRfj;P`NAbC5E?pqglHYO6gsz0;I-F}{Z8vY8 znNqsWOzJAdX#Qvqivg4z^)KiLv~V9_PVH#tXrKQY95^-XB~Wbm)QxMPB)6UtO<33s z?kiynV|N=5oqh6qdG#dFhy)InAq*LPTB4FeeXW$XpMHAb)xPa$>#H! zaZMdr@{L?f3@q|s-n`z3&N*rn%tOcgCqIe#lL(ae>*Fo+vN75CuNkI%CfhIJ@vY7G z`0{oiX#{jjRC@sxJ|I~k-gtFv#Lvq&>=}~l;mO~17)1Dx(e7EBoVIP=jv+=SM@M=} zr1oikPOTR?M#Z&$26C=0z(*c2I{I5hsifZ){gg@&5BXm%k&b@Ke3#0qU@(2Q0q&45>p1ghc0;(JhD@jd zrH*zjC%SgV|DN)DFhFuUW4X_?Ph?u$X(!ND9DwiAv`E!*Uz#h0Li_4_gL!n4YOqSF zrgW{kA7YJsvg@nJcbsWdmWVLoryA{-X7h2wb#LyiHV~f zO@k7uw+2nBbR*A^R6{aP9RGJB)XfT96Daq<%8>Y;hj%2oM`~^~N^PAO?PYZQ*|qNx zo|B73chf1r$vP1Gr4@Nqs2dCmBy?!}pO5|bRO)_VxxTdZLAK-Kt1Rn|ZZ&-!v96%^ z&XUmV^X!44e{v94zM6BE>>26s0=1${JSLpeKvNvPWy9e%oOX?r6iNOG5Rq(k&nkpe7HI zSHqAtwonspRC^2Eplj#S*g|>PSnYlMonZfwz_Dbo5I4o$w9qi)t8yzPZ19v4_L6!C zz^~?3M(brB@uJDwP4hRXe80KIK3%Fa!t>g(JGM=Qht?o0>UrG0Jq4i;1Vp52&KB+A z(FcpxUz$-Kmg`|=)>q3ukP`E>ZTQGh#)l2>H{ThZp5l(EChUa=>G<4!;phd48|~;U zcNak8ku$*_%D@81FVxxzt!LPRjN}R8!t4ok|F`ZAAXwI1Jhdv8wuLKl zW~;c(eq>LlKeuJchvC8_z(xpFsKc(-`#j8W^XpBz!IKOnY}C}>bgWZz2=w57OU}qA zgiY-QU}g0Pb3hW9-5=Ob1#iIKSh{6i@Js=&NUtJxxK+AsoO|}RYt$VrEVt`ykmP@6 zDxV|sx;PNJIK1A$RuitM-kV!buY-1GuR1^12-*5meJd~^P3(L|9WpX1@8Lgzg0I`P zPG+yQHWUcv<$U|y0RN~TEFP3jH~HQ7t7)8d&I8@$yjT?HFUDZ0Ui( zbkV5`Ncbn!VIL=?3)1)?S6Lju@(5t_WnZ=J5NpDg_mSspK5e2XFc(l z--He%#wKsJ)l~&!rhz~?s+-dC>pP2>c$Ah%;hm;+sYknAJTjPGY@X{}5hXg^ejqgU zx%0~vPUPxWrOErf??>HDu%Yqp*4FjQ8Hw*?h1KW^;Br#LmW5X1=j(?(8UF6-ace z7;o?+kt(b;nZ5VRjqfZ z?LDI0?4?H%fhqz|WwKv-b=tF#k(!h)X3Di3Karf!?W?m1Es5n}C@n7I%%pAON@0*r z;=bRO#u*n{YhJcf+mAa~{&GL9u)N1^qM^Xd*vsI~Z#RSZ8_%9#_qy{**86Yoe7k+| z?RK$-Ef;{pwKf#2LkPDmUcVD#QeCTCY_$Yd5mtMlr;-AT#j+*jQB|ZP{fM-0G znK@i9&*`XX+_S)xHDZ--U#xq&F-0Rc2am&oe4x+72(Lz;AEQV#-NO;OYBQK==GJ`7 zWak6*9)pi3MQJ*LOA}k;ItI@lnMJ-?Gh?^vy9RT>AP!9f&2KLsjp^rOM(8ZNklW+m zpq-$!4YM+#?AiPcaGmS|v{Bj^*eCuS{R$xYY$dD%wb5pwhuQHHqKQ>&;%p`V$mm^n z%p7k%f=NWI$R&JnSBq3)%A-UdJ*wVZj8id?ff0ZHEx=iy+;Z&wQ z!DVs#P=fo)t^bLC$`3lX^BfJB^HrZ74Fj0UBtVFFa|~eEYSnZzN!a*ISHKKBOZe;1_AD9z9|?XBhk&ba0Fm+$~28w1v>9&(ni zzJE~XP;_bXUo#7};A4K_@wBXg1l_W4YBO0+C=0qa`W-5Ts=f`uGQ*~IYkAc(l~PQG zRVs}t1)ScM{{7D{u0VnPu{AFp7o$}Np6ZMfzToz3V3<6!1Cf1ssp7eO={>AegDosWVRTz?{?tCw| zUrK0#2HxQRE(DDy#?~ibHaZ140?acG3{TqHn9#;U82R(%RLsX2Puqx!cPnw-7hM<*OZ3PHD3yBX`t|3GEZ;dltXX zBr;5t{sv$4nd)`E1f^6dTVzr;Nn)zBH17E_hq)?0ETeRXMos21i*l*BN6jaGezeue zWu2oe4toJN*v;eHOijhy7YzLFO4Vk%n+GD)9yG$709I*pQFPTMF31#B8Gn=cFqAjq z6cry}%^cFG$`m7MHJT#QLBg2RJ;=~Kirx5h)E69 z`*G|f&$12nPMgg ztr7BzJxj~5O*Q)ql2y1PAi?xx7Nw%R%zN^)tF;M^`A9a6mO^q^HVQ%mNDP_mPNN42&0LhY#%j6%KA!lg~)l7JB!V!{vGLVWZfKkm)Bl@JP4!h#OIiU;O>{4Cfn|Hez4h*(uUU?!ef1RM zOrQRe(_kHk(xqbhMVLAl{u{-Atm#_p%;)nYB*9Gj<9Kuzr~-nBJw3PrU#qU0<@5YM zzTPsf$@dTY|CuN#s0b)2&8R6Ut%P()GfL?k(u|slfPj>AZGv=n!@dT!k4kJBipB!UQYU5Gua)(jUd3H&1cCasV6AT>JUz@MMQ{&-nQ`G%-+eZ zVC{3m*T+pvR&&&Ax*G_$9;Ln%UYUj+{Q$X{Y+RsZTj%?|zpZ+ut{byC^C$IpOC%>y zHqU;$|8%mayJtF(dwgl>l<|9<9;2s?)zE-&Ykre#!Mr_bNr>DvA@h977ZdhPV^1sX|m+s9cVA_D(2OanG6%`pdh zh_(IZtmh4)9-*1j3tp2#@%Y}i9ZpnI@|QBk>AZR^!AL~`7U^0jer|6K68y%iB%6rY zxl@2@OzmyCuqysynU~=Vwnd-!)|{O#M9a)l>IHp`=VK=9MVHZLFD<%S^}E^?oo#A0 z6>XTRb?w5|cQGrDz+2+K@{M8`V>K(zTg+qT#qEjJ7kqk-)?_a6qO@cwZi_W8@-+sT zwW_m*#4nmfl_dgVt|;#akmV|QlXo)+^=9;!Zg5rF*{~I`-o}M<2dWgISwP~{M!6qJ z6VG%$T!_OyQFbPt^sO3O0hXiieMTqPj)4kMI0Hz+9y_}mJ3;9NplK^>EuleT$M5zi)zy#e-f|omgroRFN@n_Z2JUEOax+f~YG!gbWEN~< z%bBOjM>lqMjetGvt#L$9uyq6z=BUr5$0`Vxx7{;+aSq{jqedM()IBfL$R!;*=Y!h> z?dB zIBO#bdm~`@v_N}Kmd5@sik%-L zKE)E%d^TJeefmMXv-Wn8zb3-E{b9(o5)E8NuYA0Ti?F(Y9jv=lBxCe$O|tWtxpl5rOh^QLfg^H#^LfEEJvn4Xwt$Q&YS@0SS=co>j(f@7L^x}#()HY*2n?Hk ztVG;Col7sr6<8e5SGVJ-CvD|8Vx{p{)_1Kl*4SAre{8O)SuvX z?rs=u`A{DVzURqOr^bzH)P)A@wx}ufDLEajHqidVg;1R%~gN%hR! zDh`sGjsLb}QyDJIwi9IcX7nLx=vKCz!sd+NZMT%^dcMhrPFs{)y*{&$cEckLtM^be z{IxNj65eOUgxP2Y2>fLk`1GcuDt)Cf3`&Ij!|u{P<;8VsJLDu+ZlGqlT4|e)lI^XNlfZv0g%*Rcj7^jy^2}8_CbZ2zzcbGLT!sR(E0^W! zhTQ|&`D42(w;KI+lkK@uFIU41F=v_#Z!FZ%%NdppMYBBh?WrUAMvdYBGMHH!ncNhdp^?wit}9dj~W{ZBxqT$=x(pFrDHz6wKEaev_&Gnz&gajaMqxZ5hu= zK7x*MZT!j!K>qzjtEhy|Ah(q@luJ6&_<-snQ+P0&lhk&20!XZr-}n5o@8KwSC5#gsn>xTyIS- z>ILK(D2@kGZj-NhH&z#0x7Wn z*Ef+G5{|>?->SBj5^c@u$Glqq%7g(>aPeED2_M#gaKFHo#0=I3TOQ^55~YVz&(C6T z(z9&?uFW9(M!0^*(nMVorl+hJoM4qpS1#T>_ru2R1c zu=I57`%{@EZj9qK8n_?SO8%gYo!+i|nw7>JH9%W!x|te%efIb?5J9XX-O#lN!mLMz zk=7ECg7jx0-tGETWfV!0x6B zk@X#)_EO8WZ2ddo_wvdWVbU+xRP+b9oFsra3XU3TL7Bl1iQ$|GRZ?=As9-YQlJgvH zsk#}NMtBlw1O((xL-MN^KQ_KLP>KecaxLt@n1WzKv%u=}`4v#?#kW77qaov9+}LGa zXJW@QfIlYghqe2O@LBTz&ZibAOtYR>3~V2SuMEgeBcIX+iBIK zD0ZA;A5m$VJ@cOVIw*m(=w^g2Pt?aewH~H69F7QBXw1|TY~R}R%B~+0NM@eq*}5P} zH{r@D67@ou>zYTu*3B+O{_`EHqo;e*^NlcUP~O0J^)iKUbDHa?UReqOvlY8wl+tSD z$1U0-*Ba?o6Gv>Rm~riAfo2d^R1b5TA~!G^iY9Vy2VL*o{Lcym*YFL$0744@#Oak~ zk%&nQc=+zloMX%(E+r51vsvoF2wpvl_pZG-8;&gc&jfl$bp#nS^}auDUVurX}`x=BF!dJBKpOVc@H~HCXjzh_>WXh+A_~`B8nb1lV;{5>hbm zud$*9XLP3OrYh&DUXe@moV{JYA^^ThM;jF2mHXt)iziZfajCd${XA7Y3f(&CGXid9 zV{Vg#JlWaRQ%3}80mnm67*qf(xoQD2MCdjV&wf=GvCqDbOKt#S5xkxa$Bl}G%(Y2+ zz=;4EdU5Q}#)R+OIud-;{5`F3%Atqp-P?|bw*my(HAptz*(O!jw&tI`Iu8%M9*=+5 z-&m7Xp;6Gf8Y7}`SQHge+V)U|=;QQ8BWL<>`&F~Lrb|~hWHDPSs%WXbZYDyP&yrZq zwb?Su#eCrq;iPKd)uZ-03s-*SMnrnVLHP?3cbj$Hn!KOUsbS3K4SzO&U z){G4~>zk+Rw{mTL&39nD6Yry>7xVYl&glS989!%9+sTe;T$7|q*s?Wc7wFpd4?%*p6Erw1ykD?tgti#GpIvy4zFqd`q8Qyf@C_9Tdfg(hdc$-R@DN_O z6%a@ZJ`au>-MQsQLOeP-AqK&eZ8-OAvzPae_tmVn9wM`$A_=Q)^v?V= zyf;yfv+6XQ`jdHpZ#sHM< zFr8)vM?3$6zsOM^*A3mDJl(`<9op)IO0lgNU$3vR2IlhUTjdT?$QJhMqsdwY$qd#b z7o%#Sk*Ql{YH-dcr5PS);tL-*%Q=FFdzpc^ybs$Fv}X-GG5zLXRDU1!6U!_<#o);N z%uh3x{pXj0V956|w(56)+avj>vqQFy?qIgWDujja?B{szO$}yFlW37$J2igtV$P2@ z6&MyXFMJ5Wxvdxf8<#W@GRH_sxrT+swrhJOnrUsjHGY!ZvybjyYkV(Zme4&0k8H9k zz=-%gMwA5Kb8Pl4(K)jT^K0hQbON_LlRX&ZRO^?K8u{vGXcD8!@S>@he&zwR)O!$st^CE*Qp2a!`rKV zCR)C>^Q1KeUtl!Q+_cB{fK3k&+zo0OeEL5p*p#yhF@AI?#`YL2^lgO%!V5$~lRm=d zwF_}n(tng(r?tuFJ*IS$pLL$vM7Q>`G@)iz>aMn@dsiuZ-^;X&Fu>s>9g|4+ z(6o3iYa7$$rYfGu)ROo>t}cX;0sV!yIbm%Rk)?6)C4vbk+D)Sb@_SHaI9`Y=3Bg_@ z@0?^_>aQG+k7~pF4H@FP?-uy)x`rnojP5t^{C&kp49pTkL$#RUO2u} zOu1d@Nn$51k5ISv{hgOPym=&niwfW{{TdBfg9J501>CY!VZSbeQoQwKoA)z#gU>RQ z+V38n_o^+VUvq**Z_n9JW+pU{Qh1$+D@rQFuf(Cm;fJdj%QjQiPtmC7UB8m@?|K=Y z|LJ8CO%G1-anl=woHwTO&yAJ>o4R6RI+$vo2F4%pls-p#zDrCoVvd?6OswZC~e)xQL9{KIKVroJ56fwZ#fCuc-?iXqPWg+He!>XKjCYORB#$P{ zTpeJc{lH}*9mnI%g${{O^Ui@J721Mp`D&fGaKKR60$4_)O9^J+0iJ27))Cf2@Nkr8 zKR24XLgH;AD!A$yhfin@Nba;sVnZT$gv<9Xc@xtm7 z#;0IFkIVUDM@**%=wiuW#C&kZ1vWsH)>xiO97PkA*(swKV7{zL!KV&8%&o)xfqs=I(bV93~X%)V5+C2Ze}%NeJ6@+ zyXpj1!e@g|y{Dc!3j6+d5G#UB3lw(EN7+@DzEb5%eWdZYGnLi2^WKY(uRh%{EL7Bw zjF5lQ`M~-^1pi%**SC{xKHc380EL4dQy_f83teC#NO#k53yDQA4udZCW@$vupEl@&JPx)=`6F^etcJ}=f zlm1e^gZ(UzBZ*(kA8^>b|9Iw8G$x3Bgnroqu^M;U^dDC12qf53^rBKf5n~40_6Xt5 z((>D{#V^*JK&YlTe8j+PYJoP`rW3)c7z$6CzG3BNw~#_U(;(-Cy34)35#x zYx4Ap)+YU+Z0`8BAF(MB-?IL@;FC7VJV)nIJZ-q&5G#%3l0~>jZ+47StuKAH#Yq5TA3k>;9GbWT*a0%Bi&J>dI5Qf!Ge*N2M6lI?r*1DX zI2qo$1HJGUuCp)0l5wk1Q5G(3DRb#=wCHHt*Ntf^ejZmIz;d3%`sI9kOx`TMOx2o{UqhWM7}&Q#MOLZ6!>!ebm@rs*qmYjZD;CBPsnC z4)2vGWiF6z>OY;@-pRgX2tZ4%+auu7)XP|J!coPU!%7l80WPFTjOV0YKKyhPZG8d< zz;SX<;I)KUqydk|%zKC|o+*0DcSBrp{|wq{)f;L#0ziPnviyHC2J?_ROejk z%(v()J0d}Wq3kT^bJ<#5^mpTigio2ARzdCj2NpAe;?V_s)}f!L#Pb~LtRv?R0zH~& z5pI{r9bUb?M(J1i{`S>JkKSJu;`#Fax@6<)@|!~Rgq!j!)OKN&q8}JINVnf7ir;$0>zcQ_zVLp>xI_f}zmVJ71s|r%;-aZh)Y7ELDgT3dp0UI$fV3 zf~2n>6D`jWDtWDYTSQ3v*~8Fk?;Qp7#RWIL9j$X*4Vkh*PZwt<53Pt4JN{A<&+>X= zBv)INw=y%Mac0-PN7LzFr&J%!{Q5)0w!X)%?MoLvo!p@U-K9e+UmlfKNiWUObTWgs zR!Q%F-V+!DnXWAFuR1n8+$#pf?aGh=-$yu^oI&o-7B5}is9ec0o7ZV(atbe;y>nnO z+QUqDZw{%XaqR)|afyXBt0!XBoiTTnlv9juGd6 zpmp_1Fx-YHpG^DJI&Gn(qBSw|@y@!p$vUDPNiPj2lbKHI5JAn`C_}cF4pGg~;2>yJ zU3+lKR_|kOjl><+1fxOv-p%;+X_}_>*)O_#?qO5@(K4vXe~+Z5qOTaq zrYeT1q4|A%F^!F11=~A}yFDg2_ZwB>)mv9Thj|f`)tk0;oy{ThS~A$crm?epBwz)U z5h_+e9Gu=MH^jK5Gk4>Gy!KN-Lcag}=xZTP+$7#(^LGC@-uGJQ)cNQ%Z^u!f<=6Th z+Bm!syneM~EFh+XL=ES-x68FKSIM1>i+wnqQ6pnAKl6c`d-ZmJ=jC}7Hb(Pgd<1ou z`2DsH4Q90Z7(UWcg8$S~^{Zf8-cW)!z1~c9v_LWT8;-@EIJX@=7ADkVco-G^{alyZ z(UsL|7*?Q9%GmNy@ND+jey`vvQR0wP7a6=9vcyR7_j>{zeHK|27dmJQ(vW#QebY$$ z82Z(^N%4b|{ELVQrCdWcq}I#zkCMqbqW8{tpICNRKTdjJ!HTN~r{+nwceazBi(d>K z8L7gGW=p{rfX_Qs%=r;ydqVb8qP7|vXAnM#2w?^TPsLQgKbo8sMJyUWR-EiSMt?2E-k<@fcsI`>E%(tu}C4rqOhd%hI<3<^l=acLj zhm5FVrtC(dq?TKXX>1;!-o?C5frUYBwuch(f!3IZH1_iS!Tb-R}!;O6sR~8 zeSs9DWWrGS1DQoKR`zU}LGJ4l_zaQ>z4zg}ELf6O?gg^S1;tGpbJhCY%#2?zD)8mP z4JYE7UdHqszlslkLc@K`KAFi#UcQXiRseJU>f(K$9JYiFe^&}i_{X}jgTnf9^^6ZE ztkIkBX^>%Cqk@w(2+cHL&qD?+cIxR8EOYoTZ+zT|P{%mX@V{#!kN>+&CT;xf`sF)! zo?m`=U+4K1rkTgSD5YZeO@KH)CiN%Bxd8Oo5yw8!~DP$%l^%jx#y7zp!3B@k4btoPCfQnB-~OWbLLor;1`yMn^Mq!iJ9F`Yd92&HYYa?I%F>2 z8lNVfS+~|L(FM3zE5lwt1wYFVm|6Hw@fmLbyE*gOIKOSHPX%O-_Lmcqd@alVa~I|O z-;c>5%fqO9Ekdisx0X)?>z{;VbpNIh`XrG&`(5XH+AJv9PKEs;o@lOS8zvc-&D1}5 zDx`6WkG=@WYoKy5CNQcGqw7dpG|A3o`k0GvfYYv-zy38~u|WL7Ja<#Pjj&-X%lJer zc#IV{ARP`V_g=q0z1Ix{)SWa%LJVgjQ=i(>(pBln`N}HJeuo~wOf4-Xz$nEq|+TNky#SK4P@3odKBfU4O@RN~9`1tp9n_-=XGG5J$a(wfU zL2hF<5LLBD$W(ve886>3vHclhhUZKX+R`O6pZZ{m^iInseOfzQ1_P&L#d*{CJM_H5 zFhiA8nmkaxYp^OvD7lkjac|sPPIt}^xEcy@F-uH^22Wy~cvzy`#g`WVvN;lR4*vcp zyR=JJq7O`l9G=GCP?ni9=6^qAKVh7hdc!|(Z>CW^=7PMs6{C#&3TD9$j%ldzQF4k? z)XCA-ds!Sa8yN4y*S3Ga`!yJ+Iu6g~?+WhdkzoU!TMiZ~wkv5aTv~K~of!xPd;|O3 zn&V44f-ND|DnMj*EQV6p7Ne5DHH#pteFPEm_jc|vGwg|#3vJ{ty}MlC?}o~agxBqz z6Zeeh@A~KD@~}~cvWd*vto_N9bbPbtX|ephDGuHF`+S+d zYB}hnl6+Lll54MM1&zH<$Fsn{@#Ew~G>NP9M$Ttx8F1gaw4c5j;E1Sn7K5s#lDK`E zOCUMY2bG=`M20p|yX`^Hwr7z(IusPWfZA`>+H0}20KSMBMqxSQ_{yZo-i!**^dbd) zMkw2ZaJy^OB*Ysnk$aeLtHX2_=b;ymPVlXy#K_o|1W$f;Fd4s+c2G*O%;976q1`)r zr|nT&%qM!#R@Eg2GM6!i0=D{dtBgVSH9_6SziBqgw~}~~19A3FEIM2TsPcNl*|OWg zjYd0uT(GJ;|0v61I9*D|$cIB;+dcZ}6V_OD2QFyC$Vzvw`TNo<#Y}QlCRSpg;zOz; zBL#?pamCGy7};<-Ckd21`bR#<&3KX1{gOERRb8T`SX=94Yw|_#^ye6yc6gk}wyPIE zOJ=$@F?RMrP;d|}u()D5%CjIGb0z3E`cD|CkS}5MXNWv$Jy&_4B!hTn$*kd5ons=a z&GYuPqoEQc0)O?1)L;q9!tl;>;2cInjU_SV$_1E&p3 znc|n;7%N`C_;zom9JT#@_&Dx`P2!FjwP{>JD_dtS$`+UAY6W*+C zdFdLi+^4V{%&nsEY0CCb@z_`tsawh~AHB79vKh5T0Cf=ro?W_qs}g`I8CJgg-&Mu9 zXV}|io-+Gm=jZvXC-c==u>T|)vBD^ytw_&km8G!m(=E=3PF3jhbr7X&vw$pi$n*78 zZ3=6`YJyxr@R7cPGO;dvy{@)*9EEFK?&{F)q&L;{Q_yP=c<%^d5ZlXov% zT_qwQ?R!nUcZxiT)aLUJE7BwQ z`mTD)c0FXKYH9F*ZBNFWa*|nl2(;yMM{kVeK#GQua}kgpCPgjh%$lasf5vL~ieF?oqEQOTn^3{R7nbGq zR{KQmPy!RC{Yj`AZ2zhg@i#1&guqDFg<6pQPkJfw;?FLW<4#^YIA(vB%Doz$10J}2 zGW4I~y?1^biAh`LvI!HR5R2g|N7C_k!N_@bE%k2+^aet|bKGQhtU4>EC(uSSGu=ad zBn=sB8L>F04XN%Mngx2Q^EEkseA@9R%-Zo4u{%1HflK<~07BY6w%C_Zbvx(3zeWyz z##eK%3o545tRFB=H>pa&`zK~+9O;TTJ73bw`m7c5tVE$dm}gZwuWb2;5Qq9L%~hIe z<3B~|2thFN$Vid^lh$)C`j`9FFW#w6Eg-X4{6 z9VBOo>ANq^y<$9NVI+_=zLQfNd%9Z83ijOnkR?!j;HlRqZ7KZ?5{?~piLR*np2lsK zyC0(e1pXhVXwYh00QP|AtFlkw;=uN=DLbEaGO2wVOACz2-{$I4#otq{K?ZB-BKeP^ zU9}dchL3fvV{9L578zvZqrO{ zEC&FeqaZ)1iRe@|txh?9ue}{u)WU$`)rhmCCugNTn%VV9BVUs*Jn3H`TeMg2OP&0g ze(advUuIH&%6;!sKuxvGT;Y=w5hs|4gNu9r-n{bMqw{P0a&b6GU0LFF&x6(P=;^r^ z;kaIPM}1e+ixfjg4clR{!~ceu%yy#qEvM>+rp)*z{bStjzo5uk>Q!a4*zri^7qTA59W_k1xP_4Js6$CofCBmap`#91qL;YM-Ewy;8 znba?g6eFdRXO1Ko^m(S~@bUQZ23Fj7@O#MlcsOla>R!q|^8e5He|ruv{%-?}|2Dbv zkJD>5nHs)#zApPdiJ*PfOW5^tj?gM%-46atzpa6tz_=-eEaOkGWWsV zyCAlFmH5t&Dt7?R{|f-^|AdV#?*Y4#qS_Os$d6a=UhL#i;4WXhLlC6G5d<}B5p1s` zc4cwp^f5LfVy%VnbH|?{)@Tkk=g{jKlen}r4L!or9}k$iwanhTamI1%CU=NE`8v)2 z@O=8LNA5=0P>ShN!xCJhsIG-uWb(8~;Rn?dfs_TqEMC{;eUx?gsqEX`hw8AT!@Tj1{c*-dlo5uvK@+EOPO)fiZIQPUVI#jY@lTSy9kg?!EWbCDSP@+ z+?9aBWErnD=KKKh>6)&_e*KyZ%;XR8ZQ69-D5ikiRvC{uD6X4X)Gb!5eYzEDuCe4> zgF0Djp1gQ4K{!88@VyOewON4k2yX`Nu4YRu3g-@a9NTN3zj}&3_4=S8Q7znpIr4!ZEqJi zVXS_7#F%z8cRwg5o3Mgd%ptD$MYV(xPd#lE5g{M5pvw^tFuBiu)?H|bY}#-xyq6M8 zyg7W~tp8E-BDv^ZMmoIk?mkr*o%F=!g^F{sM++t|!viUo8b43zCm@LAe&r51(~6S& z?Q8me`%7QKJi*_La^rKNxB*La4|+~=f6UoN1XpK3sc_cB@UdFCU5mN6C2T#=nmY*J z=JuFL`{ri6TwdZ_8c14${+7Q_^{%JwYF=cSln+G%vgn}7kl>*1vv-u#2RZC z%WZYflZc5m1Q51L15E%#-lG+zf;dMzpHn;t?kkI1S=JL2Ouz$MXWAc46Fp!|+L=ct zCC_f$WVk?h_^)#dRfU!*q6@a4qr>4vcNu~SUDv4U>p{ML`_4^WS@R83A+k|Yuc%Bj zDeb!#d|NIjS57ngNVZW{fPXY`;AjeQL>8Bx3(a{mG)At59*=B$_n^Dwb8q_!d2wX` znfb*}gWhi-#8#kp&ED@*ExnDD7v^l&i8<-XPRe?;I)+jRo3GI$#;^ONK|sF-otWYAGjKFP1`alT0o; zKTz+phD%$8CF_LsN*sRL@&y`eg8Kb!>?zKt1&U_Mj_X^dFclbkGYAa0Zkb+&l3<5% z12|6@$;bY2B?qodn=x9#ldImoH~@f(hr>5SGrMoz({!78Ce{`xV_;k#E^EARp5N%s zU>ZDNxY$E>lolgB;C`(O`nv$(L%|ddrqhNe^LvX~umgS|0(k-ZLUo5}nA-1HOZtC^ zBUpuOzFFi3@`P?`X8Y@|4z&SW%^7uomrwk6rb&!=!3{mxz26pVy%tU4N^6=gr;nUw z15#9pL;^_84LB+S2_CxQxTci~L7Y+ga=5DuHEO(66#ivuSv3ShHz%!{h0XRlWBX4cLWS z*81boH)RW1PAF0d1-GxN^kaw$A$a6Y%6SC z9N-et(_@$jdxAC|ZkiQ2qwTPreK^1pET;7oCS=juoRGq6IRl~_b2Fl3dO3O$U{F?L zDwfj!4}QX(ht-yf=n_TzM-|-1{mO2}`;#~Cg5)?o7TrlEFCFf6Tz|bW z)%dby)FBRO76xIyk&}#UR^k+svs17qE3_NQp=q z{X?G@&sw4vj~5Q;nG;#J;Wbb1Ox&{VVykQAiI$2-)8&z81$2DZFXA#N*UA>*+F9DLULjD*{wdy|UluG>(@+XbLx-wd%(!q3Yx&Y{?gA`5$^t1dk8lk^d{b4@ zLZ1?+#d5&dO@o_@_NZq9sP8Sn#cE8R1}GH`64Jp9(;JnIrLRZuZ+IN`n$5<#OGj`% zlOgqQI)8~KTaDnYV_jVW-~8qqN<3=&La*g*sc|b-qNiG#+xrdVi>d6JC(0JHKgHS_ zzWHr7*bQqOT!-$5m(nx*+S46N*? z3am)pkXt8}JvgPr0&XFueagndxGpRBJPc%0#CB(__ee`xpO3vPi2m&El#iHW6>JxN4I&L|)B+ zQ*P^e*6lLntY`|pM!SQd_&ix<-;g6AZ%b8wmRfpN-@%r5z{Q5Z=dfs_`o3uSD@9{} zfzNJ%W!#6HHKv6g*5}0}tA@pxX?5S_O8&(vwd1axj>mUy*^K!VdrHaF zX7$YV8@ef@yEw|Nq~sdSpK?ScRgbiq_fMDWyBclOJBv8GlI_m91^BGKs(0(Cy0G{zg+9v--q5`PbxtkmR>A><;cN-`k(PnAr9>O+LhZ6;-yDWD(J7-Mw^I z6Ly*v5tc4h!Bsq`#;yM1S7EX6^cuQ7NaNguTFRt@KEs1QKLkF}t=TYnJVoY9zNJ0+ z#7IB4KCr^f8(#g{d|5xLixJY3D>_y6ciJzT0yBV&UD@I4x>qzmyRX4EoYjq(iB25f zTAuPD7(~eTL96T=^qWj&5rrb0#WWF9I)GR}h5$mn0*qGx*aprASm2w05l^-2{pU$ZZJg!h7<_QCm&NE6oq?A+~{tB2Mh`AQ3wd*_N zO_?#3!yz#p^Uo}y1Y-e<5^k60HQR)1@~?d#GeSP&*;R=9-O1+xlzn9!l(?x|G;Y{u z`ibVM%&>LxVp|6?^EW5F`HS1e^?9B(?)d|*lE)c+X8juh#KU5aZ2WgdCZrIROMos`KyyGY3Zn zbU4Smj~nYB`(;};Rfrojz$ZBlRdXSlGfE~~$~6N~#m}g_0QLES$2HRH0y!7=@b9a} z-(|x)81^M!PALc)b*InYu(_I&D-Cc?TP?e2?k@vybZy_<2OiiPcguX?v6~NCBIq%y zInJi2fqhMtO??SBm#y}0{o@hC?e6&uBD;)5Hts9m&z$g0-RW*|-1hoL(s@g{&NNoy zizd8M?%#DxG6nkvZp|i0QnWpG8x`lMXeUPwjkDq!CjQ)_agUU9`luiCdlT^vKV?2bw(+I{m)suN+`Q)E z&?_AHJY%$u14k^xbzv8sBrP&aaKk}93Pd+38zja|Ax73mG>N#Y<^TJjLbzk{vgyUA z9=~0!SkTUJ-^`!}J=TAO_m*Zs)R|1guOd3Zgn;0nykGk#vZ5PDr?m^eYX4mTlK~m0 zTZvMt=sKOus!e33i@VJZAvFQxyUV1NJqeA)^o72q;Jz4OS2x9WXPUD0p(ZgGV7MX!6qOe&fkPke##0gO>HxyR(KZ*cB(Y_S%14$&nKuHGa&sMTpJ{9p}(J|-!4!vs)X<FQ@QEXmHdXWU6IOZohU0q0AnYyjJ!bAg z*jF^g^_h zb{(ESX9N=XI%QztMz{>Lp=)FBIHg|}FwbP#abp&ANmVEAnMKWyERNrUg$uH3lg^#> zk6wnB!&Y^dHN?9Pk6eX(jp=s1zF`KUH~usM*sIiQMsCloRScCgJ3%XsX@Un z8O^OHB|y}S&Wh+FmPa`5#dth-G420JKI?YjZ~nW z8D*c%uT#To=|LJjvi01*Qdp+C>NXb%M>n(97S@7vmt~2&vU$Ab0@wnkRvmHN>586~ zKmz^&fPt9;5@iNA3~wDu23D{cEicfi-#RHRC{5Frw>q1E3hM?6a=%&)m*qQopS$dp?xh zOcg9Fe!AIL^^g~FF80!EAZcmB+DOM1KKGoe-mIh7rlVSTDj(}MJu0Fdzk$bJV1D~+%JoRso7 zVBoe^8(Intc>1cUIq*`{@OFRYaS(G0031A>J$#F`_65(LZo=>m8j}I~ssUp&j%C$G zi?_@xI;pQ9i*2-O)DGHP)3#xbWWt2|ePV5Ga}&Go9e80-8S%YtvGX@l;LMX@G56M> zDE*nlN8{(BT16*cOUFceAmemaUOUu(XFyG-5LAdav=NhPntNWN0~&jqAZ8G9z9|?{ z5lh1hs}eLsui!3Q^h6nu`E9-06arnW2cp3UZHYDw^bXJj!aGWv!E!CSXe?42HXzsb zV?eMRcSx;5?Eg`S8)yZZ44OKj*PE~Ie@`V{)0N$0)a}3U`g2e$w?{2i70Y~bKs}Yl z3eQm;Ym^fLmr6u!Zia0JYd^l@A?4D}36hJLw1<|g*PTHq@30rSb>^7p-K< zsU`jrT~`B&&vio_vk*jK0AxeXTTieIA4rV0I~+>VZC3}>jn@HkXXynN6wB^ESd|&@ z?9tCgg`q@v#N^6Y^UKhQiCvkb3I3EvIItg3qCmzy&X|IY2AB7rUNMa7+jt=m>+<#_ zL^h>k(eq@}Cw6wR(vDx1c*lttAv}^zwUb(cYXf&Ylr&w8K)WX~I_VVQll#0(hAH!N zlkF&UFl5Yhnwr|BD^o8q+A;$axE)vOaO?zIbS+1Bnu5C;Jf(DO1OplCHWT=#Ki)?r zwCB}oo@A$}yl|_uG2?n}S`bSt4sA^wu9A;=#2lGqXN7Aw&ePXXwyM*8?z@vPwuLG1 z*0bmZLWu*s%SaM-N(w|<1GyM*0EBxE`Ab~5KCGKLdF({Y0>99kGYp?xx!nM|+A@DP zY2xW2r>@1aF;Jnj{iu*T-M{+72i|R4GtnlNJ7cF%vp{|GM6@9-T0hKN7}DKf_5kgviPUawZ(y9XOP3nVcB^EjHR_y~I0^ zw>!zH`n{Vwprc2&MqkP+dq&Q$N$Bi#v41PCL5BaEa`4|jNiRb6(1#pqvii6Vl^ z2dl2iw^w#;)I&&!N@}Jk?jQHM-@pVYmAfB$!QMkn{nlFKO@Kk1Bl6zV945_FmC;Y4 zBkt_k#|NZcJCEN~;xCERw9Pf^r@h&+ob|_d+)Djzk&c@`4Y!YbH3R$ZRt#djL;V(* zTl9mLGw>RR2k4#}vXS}~N{6!`Pbt#h*#g~h2Wxxac|MB&nK~1jf`}STote_$w(v&JCpwhe2d!&XQ z>C&ZlSQZ3CKzb+An}DH1fGnjGAV}yfQUZh)2sIGM|8ec#o_S~9QNO99A0F=ezS?;m zXVJ+VPiYWQOdUX$S~RSQrbHX98LRjJ9$JTZ^O4lui`i;T|8U?M;s8=MvQdFs4Au;+ zIgT=XPJxA}bEF*}fPha@iE}#@rLV%q&tH3Y%h~VI$4Q6F0S%YltXjy#dv5Ic(Kvb^z{}GW>EJG%lLKAC?#=etlYT)> zT=|w7y}ib8$G3V5z3KUA9Gr^!VKS}b+A5Qysp|TSBj~qluiWV~tU#J6PWBWuL-pOIN(_i9n;I)$T^MiRBYr>3DzTaqc+05AP$ZA~x7&7NRZ zYGcA6o=R*4I^9Ceylkc|?Ub4a1u~nTo_6rI{xEGLL7u>I)K@}C9mrfEEZf-S=HoM< zhuu{-lMf?)9@wM_QcQC*Jr7`WYC&@^E^+72{oQU)bBUb$&#>Q71dxz;v)rnVpgcLO zd>-0_{xo^>KA#j)b4)gb!)IYpc2DhKKnoLcJM;9|$i@|#f%oU>uAAMpXmjcSB~b#9 z6O>XzzN7VV+}N-?e~8`5Xmu{CuFEU+PrHth*G;nM0|;J1COIlB;KI1##}SxGq8i9u z4JU;;S~hsVqwS)Ws8iJEpqCtFui4E>UHf!|>0d-!w;nvlF{x8mjv4uKA znoVaK3y1U-oLzF{8wE0iipP_R~A)=1%sg zTCLoNqGGuKqI$D}Z#3H3$5_Fr7;DpTtne(;54btN0zOTb-6&Ri9BzA1aF_Zv_Va-dHy_iDt~Q z>f>BkxYX|QUlksKl{Vjg2QL-w*LexJz0Ao~OJr9_V*jEM|+ObAqLICLF21DLp(942&i>Tu~N2X^D&K|#8(v=4+Fo1dM^T3sA`;5GBNFz@6p?5 zkgj1<%edG=ieV*5sCyYuqFh2))sh19KCd`cH&gh^0#x#yJ&}VW6i^E2sI@n3H*0#t z-&0t8G@)zdfa_YZDW+2x%%zch2<2|5v&`L-Z3Jf@KeQ;%(TW-Sz3FrNhGa@lh(NfG z=hU-0=~YuI{mmDe0Es{IZD##03w175a7?1`K}2wWY3=kVMhAq+MI;|U?OI$uFSzAmUGUnK@ColV+T;mu05IYr z5sCp?vFj|Mr>)?KaYwJfTie$mwu4c}t~*qUj459+EYue_#Ht2r_AGKe3tF5;4ECmm zW-6bnJ62o$jB<|pQHtvQ0EOxR;-EsB*^s{__wh#ci(nO-MYimsEoP2X@p&8=*`}IC z?%^?#Y0xiAQ%_~WHX5YgQ`7Z|Eq-}kwe?R;0(M0)04$(m+GEpep!sAL)-S>Z59p0= zlZj$dTe$Y+dlV;JP<>4^0g;$oWLw9h&ZIWBX)HLjX)H0Dg#6l=dCEilMb@SL}2Wmx77}$tSc*)ikl42^1O&DPu!0# zmAwe2nVs{Y6+Xj@)o(RbbY;~z7M1cyRu=B7wApomw2TN05mI0D+IOL;&R}TqNNZ4& z>ERyEbR6SV1O9!;ae=Znm9h&bGh;PJ3x)%D`@Q*q<)hL(Da-&Ap+l(IrYv+g{!Er z8ZN05<|Co>NLK-1dBgj&5M^;ujkLsajn$=sFy^^lB(s6qo>s6*?ZkUT2U9H-hx{!iAysE3yVFwckRUou_XZW z^z;xo@&Ja@9A|bXHFI)XqXvy>X3+rV5SOi4eq=q9gwwi1Xk z8J2IO`{5HY-|W{RfwfpzMaXXuNgM=`pf7((=WSgvVP`NfR$vYLXUnHi(U&r8k<08Y zpcs&ui4A>KX;aEP<6448eCT08L<*4il?~iYWMl!JLjO-}{ntVomnS-hoas99D9=Tb z&Kba*eAkQ4vR>3RlnUikb_?86DBNdkFn!fiF~AH%?9%_f)I&%xHtp7X(U??6^udQV zgVF|NH|7CgN5sLaWCRI=2tSrxn4HziSC(s0#ZTn}pq`wM+cNekGBV*!}-B=O?75gwSvw!=H4Zsq4 zn$`Q~ovtaLnP|Zd5`ejG%d@!7dHAHPFG8OAx{xe1^eQhKYJNRaJ~J~Ur^1_&ug0Cb zS-verW^YHll*r)sgDVc&UE4z6t%HPj`WLl3tmQoNc)Q84lX%{DUJKo6ZI`0#)VO%H zX(QS>X{&~zT(nqltPk1v=nw7ab{EhgK$dB>&5i5Z`*CUsfIL%z;)z$;#a)=QOfF)g?QT4!yB?N8!Mf*fjXi1bF?n_og zJ7dZx46gRQi(D0ttyu!`pe+A!KC<<5-Tt?xx6qh6TmQ`0Mt={j{xMs_IddCheE7z$)+~AW@DVz7gOeP zT-VyxHUki2cb@>NIC0!~xP;Lv;Ef%H8=jDGK(p$ z8P<-`XNGtlBZ6mPGT&D*R2}+pQw0YrZ4Xjmr}a-}YoMFKR>-ll1)Nt1N%PI`a9Dsi zMcEz}Q@Ui#H=3*>UI_fgfjtP;J=}yhkQ3H^sLlZH-wLrWf z{1Q>VB9)SUvhltM3I&T~BR*vDHh;M156M@JvuMBL(^=iUCCnJnZC=!?D(*!$=}2Dk z>Qyi_YEDZ|eTZu?=Yz1cDD>iWAenIm&dTV)iqt=OtWm3sp=hLgFERLxsWV8YQ&VNK z0dc0UH(7#&5uE>|@8YH5z~ULM-|Jm^s^h9eVZK(3;P(a^t29-fTsuy}{6K1!H4?(H z6$Zd5YW*W9yXU+GmZ)DxqAEUCau!+s-A0~dqI(k)01PWE4}A*An-2d*-o(c5Odf); z?!{LZ1Rfwq-^X^+*Iyuu!MHZ|rCltmrd|C&6*xmMYYcDp#H-`|?@d9s*UxUdOL%&U z|1f{aWp*#2H09908Ru?f9~{UbeZcPeB~<>KNT~e4sI6o07DK|TJVn|#`8zxVEpSy< z1lX{eZLW~D$tG3obSCy?_aDfu*&!-h>iyF?ImspOH@)HgSyO{Lz@Nw@t5U;d z)U#3wK5TmCe@SwKAh9OVMA+tAr0 z+Gu;&f_SDbif1S_`cyj%ELk&eo&G30&FJ!?Xp4r6`eOhU9##+Bp5_#QDeZS5`1N)E z<2%Lt%mD<1Wa#1qT(avX_9@_HR%?x{D}QgLv` z>qheFflf0e-Te_q+|`Yjh+bp4pJLjx&I^4|%@uxZ%~c*KmgfMsl`^%}V$QnSs)v8r z@?2J~5iGsa&}0_zdzecNOY}8fm~Z@~pwmCfsf}riI`!;&D1^mKV>Hb3vX9_Sqh^Lj zZ@6ILbSbN^GtVBfvD)NtEEh=8imT2L3d0&ubJFv}OHE5M0jKiR~jolM_ zv4FxP@OmXD_j*wlbg%OHM!ty}Nt7*5h>bG-5+olgtaoOGP;hB;%-48@4Wwp?_OJ%} z&-=Y*KxyOf&!X>Cd#Gj))78(_4j0%fq|ToF+z)@F)2~#aiH6@%ayu`}Q!73v>qo37 zcHU`Uy7s_&bz{Kxv|L=*0r_>Pn7<93CBYBp=xl*gwxYFW58ZJ^ZH-arW^{BL`6~Qz zx8Q#3fi}ryY+qw*ooeKL(fO&|Inwk~^p)TFOZT_&-;sod3S_%z7u)OMk45bHk~d?r zT3En1J`8El+lt-71)OX;j7t!)6R^luAFamtfSsFsuqT|9sw^9i^vP5H0l98(H2lkU zXRVW}pN!@c7a@w8(LDVsrzmnk5dopX|G|D1wNi84Lw>G^eOOxwOA6yy-?1aIqR z#>>;f0)ro~BRXO6^EE#Y11)qA7(6v7gs|JOsA{7lKX84PM4s8GsR2J`MtZS%8+8No4yJ?bJj&k z<_lFFKbnHqUi?HP1b@D{fu=QlGr*_Z7Zm-g89)eG1AG}cZq|nj&y?VFGCT2UvfT?_ zCU4S00?nU+dp1puRm>UeVIab8_g26ww^o@djB82b%8Al02Fpz&=g%U#=gV$E9w$87 z2Y+y}VRUd2&=IbaX%6RqNxmm+oB}EtC^q^5?pA0*5LkdKs&W$pT<0IKg8Rk zHs$wW*k1m@J;`ppECp7r+^JUY*0}WT>hvF80_}2b0?IFmk^7dsXdc)epPgJ?d-07m zLP$>WiAvU4V89OFiDyH)wz*@gjNu`Z2L-U38(Cmly2QSYbD&Xa%b^3Nz+bskxf{d6 z=IRXiZfSPnn9)t1U(8b%a~F*#Afy}6K49UW$J!R92YC; zPhM~LIpvM23JQoAqF7fOpD~!M1a*neBBgFus{ z!f@_VGrB+a^i_CL9Dc5w9Z>PXEJaSPhXO;xS&b2cORg(W=fF_R@k&M*6)n@v%12X=nK&(|cWkHodw(Mqatdjzel=(6U|E zCRwICzC-HODtxXGm+%sWCAvmtIZ1UtNGE@2q}8a7arimg?b_0VW4cu4=*m9Wc>!G? zT!-hTOc z^6$xK0wv!mFsn+0DuXDiOh1y?PTai(a18)1-!h^tcsx$jda>wN2@D9<^0Dv>GxJ|j z8L#a-o%Vl5e8Mj5PQ0OqJmhG1s*`Lv7y@cwK<^oET0|K=1`>LTMd#4*xWU3wrdYMz zjD{g|T;a8x24UI(f$7IOOknELqAsHh{v#Hr<<#l$!T4E*BV1+q zNcE9pY|i>0rJghZ77hY?8q4z>cg5S}oR`GwO+%oB<(;_|4txGiOu65%F!n;zgJtbJfZM!mrykx1 zq~jZEq)eeb$+qz0LayZ89@qG%<>s!!GVMaj9Y?+#W0A%}4syyp+ zUhMS@=Zh+KNJm8w<)?4~xw%Owi68}{EoulWxmk4M+%ZucklKfZ z{z}f)#TT;pD>$dl4PJ+JW?T8?+w4$rI15lmPt(^U&dq<4UNr}jihjA1SXajZlIK~< z*^}~lwF94y4l1gixo@Wi&U4hqFUFF!#>!=aN?dAoh6>t6j#W7z`kCL^T;P{6ahr5B zqv?fHse(xeO(94bN`F)s6I4fh2Ce936$sDFUVHmXDo zich|i%A9aXY<4B8mwQf?*-zJbX1al(@-U(+a90q`yoWI48cQffBM9cV<|}Z#fBny(kg)b0=OUI%%F1uz^Fi{4+8>I#!@IGmK{x>tSec4VTDbgv@JX+$e7 zigiF#IE0I70fRbxKZNr{kg*s@fLKDz8Z2j(O)&x&XA@#5#fkJr3=GX?i> znG={|@w?X|MD@pZnHsBw5l8Sc{|Cj~y8V((+H0XBd~v>;22Ll3B?Cg6KQj7O+Gaog zRzMPYU6K)x5~l6xyRc?_PWID3%vvwHfhIikoOtPWc-c_d2g5RhD|3H<#>R>h=!yS%lkJF^5#Au2Vm~o06Z!zzPf#|$u~)H$k#~}vpsut=^f%V_`!e9G+yynTq12YMiYbIZ|wl~BVX6UQ<@BTw$88(E!96-(i zKFDGmTMYgv!HKcWuQG*ZCF=dmnSUP6UAm|Kn8l!d1uz{Da_)aYGf*E_DZV^cGca-z zv2VW4avPRN%fHn6FpA~y)o^ZvVbk)vl0C!Da^=n))7^5f!#~J>?a^?hxjc#6nmQeN z74SWe9Q;RsZ0jt*Ap>DUv3;bQ^7nXXlo6SnX_Gu=T&Fg~y-yIr2Ib7Teg+;-a0M}@ z@s98b7FA5nCM8i$fKi{hJ>+G1Cf*BBz}4b)g&0di7osZ zU6w|&cH@H2r%Nch#%XBBIQeXdKRMahiJf zdq4klN98hT7#>8sM3(R1b#Ss1EjJ!}kIgdsLnp1@lFVkJ&WzRY-7#PSj}!Z!WOtNj~WdHX8HCPC@U2jB%?ata^H{Rtv)6GayY~pexK-79t>HP@84+UuYg|% zA}@FaUubRu&_Vr{0OOxYnb$vcegH)KKU z#>X}cOktDx2XbFk*E1if{zinkN+q7L>Lw{(ajMGO=lntpf&m*$q2*@{nUDbRLa6X`&G0>xCLl(W4?sBokffhK zS@CD*Ubc80dE%i}n-YgwHP)l%?N}B!=Yx};$ErNS%6q!me-CWx#(PpJ`f5%@ z$ams>NT9S>Q_!=Bfq5_KR{xa35K``1{$t_MqJ(kaXKTYqa!tssp)hrP3licuK?xSV zXVv_SptcySm2XQP2pf5sCFvy0vVrZ$4*AjUH?m1@pL6gbK*)Lcd02*wZeUabW>a67 z?a)ye)*tdG@fE^_7SNz2DGhS!O7?m`dy)w2w^teDjOFpj?hW>>e)Ngcms{PU*Hgdx zNpe9wr{kp2Y@3YH;imnmd&j03P9tx;f0#de?D&NojwwZ55@=AzcM`4|&U{GXr&DX% z=K<_ZP5EfeVC(vqLxX6KiA2qKVqOt&b;gS8fMqw-;#V_8YWF2R2P7BKR6S2A$!*mGm9majj{`I*vV=kN8*_{}D1xw;5Tj)n~J;e-=SX zatFk&Q9vkmHMi(ZtV`8z7bV{xAA>3tiE;iQAzx8QHs3=$SVSrVH zR$6H74L~C9_W-8?nBaER0;lCT7;rgt+vk|H$`mJxJ`mS)>+Znd;ifb7lMozv@cR02 z&}}OIxULbubiToT^e8do8>gb>enV7Sgdh(UE8=6>@x#}G!QS%1KWdUIf<|A;WUq@P zpHB5?8~T%o^|Igh6yD5Sip7IxevpSi=sB*c zY_{;=cr5#{FJICAgpF| zjlZrshka%+ldLAz))p-#(G*^Js?RR$LDNnjtj-vr+K~AHtZTlKxD%|Sn)O_UUMMAX zM~~2>6F{Eb)NpMmqn0msDwa=O8AEA0p1=9@+%LA*n_F4{W#rudkh6^T1ob;WPopz_ zlU?-wuq?aq!|GY zThZ5JTU(h=awQ&}qp4S2lePA|L9zU-|K1<34%bl+#FhVcZoijrI8-x+3Qv)s0&gXaiRt81u&|a`Kr}N0aCPnKK;8;j4VEc&&w8r1Gw2B`Fq}o z*5oKhtMBHLvFD#Qa3&FT{b3YeFJ!uuU6HEa6E6GuZ_Ie(e)BQGvjUTzDK7f0guygi zvqfK|35%HXsXc&+);*L<;RpKh?SPEr7@po52vono@<}<6=NC8gQY7n2gb2ZGb>wzJ zzK}dpID?1~N{Z4>R9`ioiujR=9Di-vHqZYnvDbGq!@GfC?YM1YvL#Q^XV$QrAx(hp z2yvdZYCLs*KO%LEs8Gb4}GuFx%%$xm9X#2EGMh3CnVD%{mQufBczXJGC0_|*IC0rx25%7@n)m9Vu1uSz(_>URBB|fP$v)4h5Jq&b z#7xisS{OdOSPuBoejVPZ0T~g1A-S|Wf&tl13DsFzHwhYoU&l)O)$9WMomJkv9Yqd- z25(Lo!zWjs*#QT{vCmbm0Wdf%9^c$p~<(biu1IgdCPNzPvOGjx^(FX{I~H+hiYl1`Zbe5|D|LROw<|3 z+~|xe_=HO5LGh9v9ACj%x=>oa1cx9?nNdgs+1)`dQ-|5{NJLnw zTVEB_u6yB84UF<4+;pz|WUlyZ))XpWsqR8%21t2`PsBEHz)}o4{R6%i`|#ak4o2Rv z18Z*qm=VxYtFyHEQH2YQ0Ntsba-~OL7o%I0=ix_ymDDyGQE~QJxE-WbIY0ilno1XC&lGYF}mnp~TCqXcZnj}((z(IrlNiD&>PpJnLY!S^tED%>kZ}}%?R>3sx1j4o>T{)x+ zJ|GMFWDkS2OSksRSh`zGF_Mr!J}6gQZMi(yqp~#eq!RIMwncKy{06Z8QJ&q1itjHI zCO!$!@$`zS_w8*|4m~@~$mx&Ct(ZBP^ZyI z<$0J=cQpm{IrM&+Y47gxjzDTZ!&{6D#{SFFS$^9sG1>A1w!cYjJpuRqOY3BWD-$*;4bhRxL zlX(mzn}6ud%SOfT;)*wyKF9QV40UhxC&jo%jSy9s#vNDUro$g2ChSsUr*Wnc!!IDy z8>Vl8Fe5(_0$JsuK-^e7GfaF6Cmmk9fyR+>I4Z!y=UBY}@D>eEq72i^b`QILx^f0- z;J={xYSsAc7{C{0x&u3(qFX3epvBT|$xxII2;=Ffb8S5G6ls*Cydf$*tkmy@Y9mqx z=Tbb0pJU4SeyD?%JH z+FAl6&=$Fnhhe){J=nVief)y;2b*^TaeGjjl36tKkW=Q$*6+2PUi(;r4gN#$l^uVn z$#j*NLJW(emi@gTDNi=deK|MoWOq#lox*4fM?H^bne?KM#pMHdz)wu*!M$aDx3gk* z-})+Itr1|Q1T7V+0umMqWM-DC(__x>KXId|z&gv6eSGcIwAn$5oe7Hq&+N&yKsd{^ z?q*{^RkldB*r6#x2UCl2Sovw3_9KaXhl>~a+|OToGOnqW$5MTq=!_23GUQ=pZ$6}4G1+&gc@AO_ z3H-SNv%89|ufAbNzY9-%hT4uB^Ef%&_^BDzZ1{4rMxQy@K5-;?%`$*^a>ze`oGYb1 zb1C*MkqlV|b&3D=7w5nD`Lu86n78l$56Ca^;y^Lc5|!ydaI4%KPL=I%%$zO9-V$G1 zK9(W>t(xW;-Dz3+oddf6Fxjo{TfS%@QZPWL0*}J^Zu(wI_ z_Gv_<=?pwLN*RRRXBi=u5e9q&h%8U6JXCfLNI#=XaB*jsdEI$|#!Y#aC*?_<%%|%Y zLRhPW8MQk0SUgsT@*h+QZ|%QRjQSg!87VY7YaX2h7EM=`T5-5tRqOIZ)B*u(oC>5e znP^SSILpx*XKPJ#404l|9_`KO28is(sPq}!iVS&sG)AGjK=n%~w<@3GmEKc_dcI9w zSm4%Iu=5)DmtPg_|8i$Wf&XvreBNGYsZX^pnz+vd5nRI1lC+fN5b{vumB%pYOtiQg z{Z;Yk(qKH0vR^GAmoO-qn?wGa3=g;a5Rz=t*B8VlyR=nv)HOnm;vjA zIO#rS@z5{9snQIN63_OZe3K1jn3F?~u;b#8@7iYe2Y^%3#}bWgOo_eT5A|$swPa#L~w~?>#c#DM&Dzm~IwqPSc$s;bD=^AG3 zRcCQs*$M&RR{ZvaJY^#;h@Fy&n@dgS%7U3>TVot22gla@IxOb8Vq6vx4OdR5J2|Ec zw^%%lMX+*T3DO7L*`wT=` zi+mc14G0m$2>~mpZ^!Rg&o-P{a=BsM6#L|-00oDUB<|T_s{(6Wj+c86p2)Koz0h(6 z0A}}y-nJwwug)o%HjHBC0VfviNerMe+yW1PEqs| z_*s-yc#pRs2M)}v((QSu2;=~D&P_WRNYyD`YfGIjLP}!NP$1po!z}TIlFz|P~z{D&)el%>xb178011fS9gP% zuY9Kmt$a`R(5z zi&$0|wtjCTgIP9Ifvcn5zKim^S0od*!;AvKo^}(b1HA>@S+dJ<*uiHY?9t(>v7A-+ z^veCWre~=!!1qYjZ|fJl>i&d2l1f3{;X7ehA|w<^6A%}a#p*7=_>m|0%jVNQ z)iD0+a?)YTn|nXbVR~M^QS&*K3@E`lkL)!j=9C>@D*^fzj`YAp~Y#j5-E6D?AE^qgGyIQQPDsq_A*sq!^2 zo-z;Rzi={F!)Hax0E(}pssDzYOYNwcoI-bcjZ4`;ZnU*Wnvrvuz!9Zo&q~CZy%Fc4 zdWKsSxTt8VnuvESOp`RKT3VaIPYlC;a#Zf&;;BCDmW=JLujU7^_dDzwV80vKEAj;RGHL)P+5J0a4V61G4p@^X8kYzqV~V} zKPo-*Bk9Bvn1M&HidfW0($lz!G+B1(FR)=Gx=Zv!a38mMO%#<y6;w|sWWCx zxg8JyCWX!y9bToY+IFcx`h4{hN+rq%&lkIWB9>V7b<<}DpeP$Ur-D~oQ`p$j*Nw+y z0HahgK*}XL7_iq+n4L%|hrra8uUN)~Z1`|~w==l%ML%A{KflJ|rXW@>!bN-Rk%Zl*ob%%p^@& zc6xqU6mu&x#%UsI-sL5h84KGbH-p_0s>siEr<}ygM#n{5x3vb%`yT)Pj6^7Fss*Gf zQLBw8M+qjTIY|d8At5he+3QJ1{0T#xQ7fGIG|q7~jVO!kgj)9ASFt$Ob{3DWpzvC{ z&=Ydr+tUS4UP>lu)D%hC7}$zDIn5h24K5F5MM=}0v&w#%QH~1}4>x5!&Q?h4o_hw( zd61ui4GRY9PS3tnNuP)KxFB;6KRx-3?-32)hHl0ifZz11f3iPbYJCO<_-|)6brIv< zls9~Vq$qC?+j?y}w{&%sCZTM}Hd;g5`%69(6lKQ^Et*W%cODR?Qr6tetusHRsX6j; zZF%G&sJXFaCNwIx(!=oHRRgPT(mKTHj7h}H0=se-E`0hIko)4&HNX((KmTXs@bm9U zVfTGmai%g`aMQJ)K?ByN;1`%`<&t~tkv}O$faWatr|(lQV}GSlMY}XrEINd zji{tjjITJ6SmZI6kAFabTEYpP$}~>=ipth{XMUwv{&uWH$nk5%Tj1-t;O5pG+lP3K zcPT}|VYBn17>)*p8+*kosA6hdy03fy+vDRzmZiE8-!<#>>`M2!-VxS@uNLFN1G5+% zO)FJy2zF(G2kflStJ^TKQ@~CKxrD`xj}Ju7ps-F%MY0dIkbCz&Xv*VE#Vg4P!S^GM~YVG@@A= z9c;tAer9?_`aJ;Jbyo=X-|3sa0pujee&s$UZ05SLWeaT`8Ck`SE7Irt7E*f=pjNg0 zN=kSS?^da6T?(muIl|`6&5`>V!|+OZ2>woSuwUJoxQD(<@%`hL_)m{f7k=l)Y+vH2W*^IyVJPCO3Ha zN#MOMy*=Ktuj2ohGPCJ>mM1qxI(6br?+4OHQVXHs8`D?-}w`6vns;3x?w;IZc74fFR zCco$$tT-166@pp5A93IR`@+8lQo_c6pJ%=BKW7W7{+xhvn3TxpPx)6xONTI5F`0w0z%!=b{?WRWN`WQw~7s zyRdSJ>VBMz-Y40;{>I2FKX6_utw*;o=nLB1t+_A!fhSe3fV@_-w_1qVt!vSFGAg^y zqoP9W$vq!1(uP_GaLC$Phuw@WI}u3%>6>e4FvZt!qIkp>%7?-JTwwrcS**q;Pht#wyr8?kRhN>ND-+49U7!!4UCOUe)g5 z-2>CzLUCo3akptDIawg|YXk%JNBbz?2%73kdSVK+CwJSvPot^kpWy_3ey~nbApRlhmy?lPIrEqqYPFjfw?F%Z#5m#TL(=G9$A(ow-+4L(#ju z5Vzyl#&zIakh9(ngxIDh@|`)$;m9`tv&DQqRq#vg41tPA zK?w>PyO<;IO!xkawX_i370+-($FF7QjVdOcCg8!N+XWx8-}6$`OicQaKVKv#hs`s% z9ls~iFks*LuVYq?YZcW3x{8__(8uDo-#y1e)JRi7Ko^98@!q$V?I}QyFq=e0MrBDQ zy;41YC2Rz*enA)2`Z`kh&S7I#?sLwiE6u@kY0h|NO?-63xQLDkUo3^YmEc9-H`A^Y zl2J2|-|4m`Xd+4u!XWs$w}+1nm1Tn5MqJ#EdTb_ltL(RCavnj$+-g1rmOb4FvdQ-@ zBvO<1l7o_+F&CW_!(JPCv4<%BzUqH*uzf9c4&K;f&oH`CRBoYF5iPr-(5Gr{UK0&= zrRy`dPSk8}F=U-6t3`~Q3RcY8rFnka-m+iRKVH}7-$Ef+EsIhbm;i9?Q@O%dh(HN$!1)ug>%*^fgGV|dW(AcWI^s!wdQo*GWUgSE zF;+=o@acU3wr%~mN**qP+l-*8!b`ydcDB3mfHBW#Q_)sEBJx}Fe*&5(!8ZzA_VC2} z!oH`z2j4Ltn}S~D;}pU!LBy-0*qrp<5o3elRh9{YEZ(S7ZMj09o>I$~K28jAZa|j& z+XwXT$w9x2wmw(SWV-4l z0HXK+_b~$nK6`q5kb3C@mEIc>vTvJAi@a*-G~u~(AB*XJfREtszXuRd6r}a$9C-LQ z4B**Y^}?;b^`_A7VYijzjbNC?YE86-uVbHJ!*nt|Lh7NTVuBUu@Lr{m_!(bP>vz*L zJF1hPo-C+RN_zuZGa51%y2n^RcG0-sL68>{2LeK|))!>C@`JH3l1BmAOUUs;h(Oc7 zT_C~5r0!X_5yGn({H_wM4h)U+P_br33a5NjNoXrUEz$^RlK|74<8fir*uknXr(o`B zZ`=`g-ZGiKdZ`p-u;!SPvpUmvC#T<*iSMP?Nr81~AVsXwTLTxp#+kdkZ@x3J{-(B= z11g6QS5@EiDG}CZs*Bq4{zLJ0{HZRmFCyMEUCOl>wW*b!&4SUFvs&?;CaEKyJ2Fsh z4x1p%Z6Q({Yaa_nr*G(QHFvJ|n=Jy(Q{(w*hoNse@-6*sXol z#K9}Czgm(dW zdc;uuQSi5?L&>i$o)d3`lw7N8DGle{I8~RzL9h_5_I^_EcXJEXv>j#g4tnp2zH4k-}>S;XBjM`LY!E#mJ zU+oSbtnZelgTEu%Ct~I?JHIHzYxIMD(^TO~a#NDc@0-XpXXs&f^IrBiTis3*Zz#QR zT4(G!H2fYXfK9=&Uj>;ge0|8$n-HW_sA*7MQ){%Cn0@UA5K=R&`ur+hX)m0oJ`hF? zaE{&Ga%&rZgiGwtGj)>+Pg((LbI@EIn0FALE4^*%(3u(K!JeSDvtJtk63UkUit5gEy=R}dL__ZG(jp;iCqtG`8m z9-lsgK+Baf!S+6_CrAA&V!FD;Vbi{p7dHAhcXZ~*=Fa$L2Q&yt!~(MJfd+El?aLIe zZ63-JlEmf?e@8|V2_(ly%$Kob8&p;3t?RSaOeNGN0Dtr;6R(@wgPBqfz2W-|d zh|>shEKwMZG(9I6A)YqBV@j4Qo?W;u@s=Jo!1(pTh8P&wyryDd9Gu+^lknOP$F?m8 zK`Cwqi}13NH`w%Q&zzH~T*U}#rS$xwx$3*eY^@nD5$o$qFHgBfMi|4lBtv=iGg{wf zH_pDy8fZwTZm|3ZM;;xZ87;VM=kDK8FD>f$$`>YM83rHT3x{NH?mLYly)w~R4OjjM zOIJ@f_8P!5rE4B4$zCoW zNQ-0u$xkOuVXki6W~Pu4Ov^BvJ^pKGkNE{JvSs53fXV-k9sNv6m9P|55-ms{na1=) zNs*oZmUp8$cjM{ft5(N>DH`V6d7p3fv#6%XOf*sA#RPDy<(lHSP+0rQ-pMIFvVFcr zLKxW08;9Ncder_5CX^IEf1?Rf6uhQpXfVg3_01syDFq|G!ScVaCtE1_)g*6FUebu} zF@hW9+-!7a2C5wpjy5L*Lw$;(2n1TZlO5M}ML9icK}-Lm2$|B0RidKdr&&kgiu41-g_@0j|Gq}5PB1&NN-X?2_hwsP((VRNC~}| zP(uRzZqNJu%$%8b^gkSC80Vh7_gd>(S9x55V%GWrvAf$}xT^bSn#lX>fdC@_E7FIRcx&htNM~Bp}PD_pHovv( zWqOV592AoOO+Ve|EGr7}5X4!3lShkJB@)g@H3QEY&NIC_B>L{Hwh?zPva}-XK{(X? z^!LbC1C|)qB<_xrQqe_nJgjE9Lre?@YB3SVjbG0lV4}Rj$I^mw^}O1~A3b5c2e}yP z{?(c49pm9gXC-tQb7hp%xyFOCmbV$&fp*TNd3nMb_uB*6rltcg=?mHC@szn)z2JE4 zw`M*kv2WE}vJ)@eglJ_TZV6R+6zzK9jdIMSMuKByf+l=lj8vqSDF`r*ZfKbS34Vge zKP$r-ek2h3L~4ou(a+FIEfB`7yEp=&D9M8Zg?PqwdF_nJTz={z%z)zy?dItZMYhG8 z;{+30Fn@U>0SY*85I0VA&n^jL8x8n!jWi@1U!beLYZB#D1j=w={sQtA3kzXn9vm7f zD{v3HZ4lP<@q~ffl2=<99Wk9MGZ|hq<5mZl6f#|JE(ev#+2DiB0CFA%*KjO9{%?2E z!mr~BKle#nsw&k$BEIzVpkYat^eR#)mymQI7@iMG?+w;r-(-GS^6kWRzhB{+Mn|WJ z^jOW51j#(6Wb@e0ZPC9bWFR*%ubR(b%V~~ZbfyK&A1bO`;*v7fYF#UK8Yl7hZxajz zIpbs`(O}L1DV_|+m8owI1tA%$CjlTHh(t%U@vttlK<;vKkh$w>y+W%S8cYk&B9h?% zyO%izVu3#I?582S#KPqo(g@m>kaWWNxF&ID8>4%+8dMOn1JI28PXWfXidy(5SYCAn&VTyRMbjiQ|IfC-RpbCc$v8C>Q6h@d;cMwnpa7G zskMHNrU+X9W`erD$0bk_E3DDJz!BG$Ew6PB3ZoB={Uq${FNiX?noO!UiAk=*7xf7| zsYIGBWM(KWD4uC~u27kCBZSpJg8V$&OV^y1qOU=;BZYGRV&j8A%Fu8PMX^&Tl}e=K z(4_YmHAvBL?cPBw8X-jt9By})7_B>$%kXfs)oi4F7DZJi5~}!%$YXmnx^5QixYV_x z8A38}x4eW&qYy5bogmJ_tT1IB5H+69)bQf!giZ)+z~WGpdBC3+2e@UopO?OIk&8!? zGa_^_u#^5o3hYnhOj)iM5YXZ`&3GZ2@73N%0iaX&l#r(}e7bebZt`u%zXOe|fJ9th zDO*C`kc>YFgCF^-hu2P+DK2&P*>p&&jSln1<>YAEDeu}%E|&B46eW2UMYijjs;JA% zlb^?Y56EP_$+%jW;!-Db5Sr!X@nS`nqS-vv6!2PYaDDnBk8Td2S~9uX zK&@+w&)S+6qFGM4&mBHCch`86yea<|djUXp(ycNF`qamfv$43o=ui}GAFm>nVw&Up zlla3SFOs$;zcAbZAq;VhGw}XY-|qmZ=Xr}{oRZ-!Kr+h8#CR&8J8@ars3#9}!-tdv z3dp0}jF?8?cb9g~i)54!^HUh+@BCAP1ES+|e1&xf&rDewZtmzuZZ9X%KZoJJ{ysEu zwz27oxj?rbEN>Y-u+)hyo?Wdu?i7pYUiE5b)GEfh?^TuK!u0iZyzPf_b0lh?q7wH! zTP@4o1cu2JJjV025!|5>#Z)(^(hjjk4W!4e51FPNOWWVBP{OM4P$kcAKRORJK&d@~)yuT;) z82{xlbss@Z%`pbt!tVOmO%N9Wl&Sh|xJ5&nms>L2q6oM;5PK9sQ<(G(!if29#W!E` zf=?SLRR3BSV5(&Pg>&vGq7udCYqHLR80SEz%&a&CYwj9RlDrQu8%~|c#af!C(d>L% z_#OR@7sd7(n0%pNW~uR*{JOfEjt~Kz`-pX`mog}5t;^@$=oj5R?3;-enzLvVy{*^v>7C zmk|v-@RD0MKNnj_ePo2mxrPFeAjAJHBV%4&{GVlHKM?%UM_q#BuN_pYms)>LEBw<+ zItwt@&UE&{pYrDourv9oC2Juj3in$^9UBdPj#uCSS8?HHE?64y2m`?usPPOnnL=#M z8h{)zq!zZ?(MqMn6M$H~c1a7&xyo~k9V8PM>=Tna;ELkSJ0IfM_tw<|wfKG4$W)6{ zKIhJt%L*(7Gq%1)7Xkb1yR{UIpj_-iBZc8$&9ba6wfluMDctHhFUOFA@~sCS}IJ8#$5?TzYntP^mCSRZ9x+2g1(%O6f4UBwmrC_fuTD zc2P6gi%HeLl5$8O_G@%^>Q#X(gI zV3OlC_Ya>~JPO*nkc&yZQ;Pt;i;NMqsO%H?AR{DAAQ!DIDw0cik8esm5d}izYy?cn zHy)BN36Hba7<)^{$c|BH#Ij2_ZOF}jotWRgqwFO>8-IW;h-!6S9)UF*a8*cuUg5L9$N7ubh+}XDhPFPh;Dn;q!}TgjWn%UM*+S_7b-#rCb5SXyJzhB`3t#-LJV0 zo|BgvKBAbb!{3Zka}6n>NoUp84Tbi3*6G`R4_?R-FAPhc@e#gL80y%&36IPwO*{oG zZe6<5`qI4HheQJ7Bg^>JR@HYJuBq(GJu(m}R^4`oD(sZD%dbO8|4JQ=UZ@~yQ!!Hg zjRC7!U`^+f;ObV2(xh!k=eQL-9tQVsZW49_jj(ATx=C8d70ROm{=Kw}-^Bb1-t9FMfHZ*xm%>T?jDBqh%#2 zJ)chna@TA(!=9tO+Z6-Qxb(8u$=ku7 zXd)qlO<$)X5SvlEYch%Q+Nk0RFJCN+%*vzc8}X+2j3h0suN(;{uZkEqR@^u)yGdjj^R`q^018nU{5xZnb*$bfIQZd7@XR6}r7G zjWAGFF!QmU`->x#>nMF{VxkmxPC7(P|Ce;XV~UJtr>vJjd($}u&TF6N5ctdt3drA% z?pt9Sz1j9~?vHT#hQGTz^9BF4p;T$cosV+0H21Ghv676+LknhhzI=?sW?x7hW26i= z+!+sQ;&uwqT?^%R&a++*;vM@gcArCP)9rDcv)f0)eBJj>qPU=U=Ur*YR|b_FZDB@G z?c2?>IO)3vUPrPc@z?piZ9uih&vbr$9U zB;JrGeNRIVh4L$c+?#7yP8QFceJ3isnbmvy(Q?MtNODp)9M*si zAy)Z-LpKS?5OKg=tx)?V21X}6Uvu5{8@9Mki$&w?Lh{oyLuy?&L;z5Weoe2^lZ>_w zZ@*~lwDu&rJ_m|(&2ekqNA|VeeyeaJX1U7OCY!9KdCT6#|Jbd9ERvnD&F!zBNyr(f~pFVio2GMSIX zeNj9EvKdlGX+^$s4c?*1Kbwqu>}-RhM!h2YqduY8gCtT)-dh*5?%_siOc7m2>hiIV z#k$pQB+f7uFRN{26=WwHs0EH%p`pz!^#q`ItMlElCWfca?vIF`5KzGEbgI7F%4pHR z>8Y~kI7--)eEmKW=?TJpV;A!5*b)sOwQlnjkP|1q*N~f_ziaew_jPK@t1|}nkG5(j z-y}%V*nhXiE03SZYPDmm2deFkw|SfcZ=Dd%I47eBQ9#qXuQc#o*o$lUS4wIY)4@yo zd7Ab93(O-TPBwdURx!|#zM@06>;8mnvyh{Ht-NizXo+LfERo;#A9uc2wPy6gV&b0@+z<9!4IT9}^*w_oEkm{kKpvro1Y0dP+!7x? z>lTe+qzv4i>us1xE|V{QiHX(%r?WTmjS|p<4-~K%yy7`0x8!lwiyLz7vHaUFUeV_i zOl3`oG4SNx)ohD*U!iA;#>InBRNkLz7W3w|fqWQ0be+8w{A{FFiu9&j6V_DyE&gxS znJE^Ys+e?7Ms#q5IIN)~Vlv2vGajCKIHXOG*Zd-ZRu>)if_?E~g}5%%^F9~HDMfSH z8>$htfs1k^1r9AqpRG6GW46!;+~?+M7t{7{94BH{jCK}ZVsNr+MmIx)LAYRTz}G{K z(zB!Pt!iMm{hHBW`|n=vmqxxJjAPjT$o?bT-pF*JH>!BTvhMiX^VIH5{{UAyI=Tza z0XR@ffA93_ZrEVP@Bq~wPS{*Q$N2pkPisy&<2fi<7!eJc4!V>?(&3fPT*tZTAzE-__>bm*Vl;oqL;ClaE-5PJ9{tbS;o>Q7Edis27q z`%~?=*|c{)O&JlNKSlvT>@bg29RXNh>*~;*O|6fDo~ro z@iD+}FBBLDyta1%1mHN>gds8{gUyVX~=Q?9G@+Bb%~#(BK5 zMckrb`N#EGVHfTW3B;xF25Ql(DKZ+}J}Gnime2F&#N@%50jFV$9fa@7&Pt zXXxftOqCH2ZkMkpWHZ&A^w6RqUTKX_2@Pvde`GZ>y$Y@z#aup5dj}tCi-jQf*kUIk zXLCT`vQ{VWoJH#Yr)i6ce8JLi`F_|K)0Y6(P@lEu#n^-e|DXPqrlj)VCT{Lkg}jrq zdH(*#S~4k4y&yV{4fCditdRYyPKUBo+1{&~{m0c^G&6uk{S5BL9T-9}ao%$y{>|K$sC|i9 zm2wSVp@ltz+gLnbNrokHmwjI&$eH!|j4IWE?_pykshPlbXJR{RouuQ%ELnMs0;#)s z=ka>~aP;yz8%1(azlg7kN4tUS?^g5A&i&p%eN-h@zvcDU71e5|b$*YZ5}OpK-_!-S$*@3P+OVZte55H%SINt*%nK)MKg62X1bOr%V<#sJ6gn3{(D-ArJ1 z%vD?DNIZV?ctJ5SE%UJ^dtdi|6PGyDq>~GR4c264Av*waFka+a9O6+yeYWazRI^q^ zdD^oN1M|-zISGfvr&1}x69dJqhqM8t^3!N}7i$oZ6B&*_4D>zxeSzSc?a*e~o5A5j zVgCldVTvAE@r00B$ToX{@>=n`rrtA!to#U zv!ogvegBrD_(zo&kS3JSTl&(3(l}MVo@01Pt^cJEmcf|16x&T*@C&8;?&N-rq*AF` zf;t$!VpA`wgwYz_#`p^q$=D^sRo08huYqQz?`fTTWir7Zn2;2J6O>vQf|9=V2p0~c z{^u+YyvMbxf|hJBVM!Zumq)`60ZOyGy9sbYPJt7WwAO$SYPF2$T=Z!Qp#l6{s*h#H zKXxwxvKlQ|_-sNAAh43015tn$8CG^o(yZ5^W3Fq6g9ybP&Uv<5H$qZNkDsRV^R~xH z673ThOBZ6&Zxv)fET#s1LjwfoWj5{yt@7V-&Yk5OU_{6dLJ9PR?uU0p%NFidd|F%l z`zYjBtSm*tJeBZ*QL|8`O~a5IyB$61#q2MYy!(rCs$@~IRBs`PGX(8XI=wjJbO;!Y zIY)ER8mdW_0D4-fc@U%B;YR%&=)5`2%QG1cZsuzDnKEX{%fG_aol~u!wC&1U6?H24U6(3}ViWY)9&zIcKEBxCaS~!Z zRBlJ_CY38~lGASpesijUTDHMDM{d7ERBA!-X2D{lVx$E4rLl!$Dk8}>jE$e zm7mhr6h<2XCuXL`sNw{Qn8y*CP5q(=m}6%2Ubm7em>Sua3B*$%YKhvT$_spD4y4_N zN{vhA9VGyK<%N9}Io6x)zhnskSpSl^RUOP;rS(eA-KkdVEd2M)lA@i}MsKLQd4Ig0 z^gD&6)c5JWErsi3p5@9n&hX|&ex3MFDUG&#qkqBv7d5Qjre^Ky@|mB!i+9|w4NQRK zY~xPa$nUfzHmOO_AA(ggxutq1qa)^g6U8l@g8KeQQ<%2ZxfF?@-tW=@sV4``0$$xd zV5IjHrQG@VC%R_~br)I4zvciEIOoE)FsUqnx35mX< ziHD=>BNxq1tE1D)TmIc%mzp>7DYla$M(iPaP)Wy;tBYX?*re_mPOHMiy5Qxx3`N_+ zJ~JoZ9e%zeg}so!WC~?r`^z5TMkuEcNf~-;tYLj~eS_SpM0~bs!Z-0J@^4vl3<_A3 za`fO0*&7b6<8@$v0AiYzObM8U$E#GdSQBB+1CRXx46dKMrT$Dw9QUJEk_P#a)5`#e z>fI<8nGW zn&7W1hY_cixdJv82?XoUIN$<%yU(?}#O$5SgwNoa@37y3#&3oEYSGiS7Lt|m%V0n9ac%pewNJY#}iv#j6 zo{ocU-*=SI&PR#&KpQ^XvN-E$-Ko}5V0bona}khB;EpMXk}c7-E)C(QFr~OO%sF%{HQ`1Yi*3)`jw$p_OHnB9Hky9 zOQq29Z=SBwu3#c|JReZXwY*NtX^MJC+}79gQ)iu>@DLUD)OZzbE=EU@>zi5XF6_x( z;YjWWxSRQWX5~m(CGM6b27=F#cyi>MOHLNBW-*~U7y#!jT>n1~yzm#_JOKAK0Fm7s zN*&n*j8HSCmYfS)eFSm2Rk|41r%VDWC6_wLM41n^8~+(+E1f!ns%<+92J#KeitZ|( zY17vnbd~`N`m6rD=BJI?UwGm^@+>#l*hsKC=K|2=1jC`x%y{+QOrZ(#7drT^He*>- zDKcJKmhe*^?HukBz93M^zLC@Vra$;BQ%~uw@~xW|eOWG5S5`99`Qu)W7>FWw+@L*ZH2po_!bydaItiPIdp=gLXQus`J}WV#n_96QYUe zte8(-f{twa84c&ZBd-~mzHb&^j>+;qw7*=AeEGc*J#DXUtUtQ7Z34;VGM!8^GT^4K zHP?MVGw)=xU3N{+GcGbGP{BGp!lA*{uFmtzbD@Lw+H~g@!$s0gG>sJLv0&A^q>Wqy zT#vY-?XgX3P?3~52FmG&{EKT})+UB6_^!!i!b=hThnlI1!htf_I|$#;;u)J!E7AF3 z4Gx}Jse=Rp-k~tvPa}gTpMrCFy+U_)zozjH-Jo85V>}GenLPIqSx4HWDt-g+=#Xn*GEBr1tT)V2GS!7;~ z`!TbsTBo?Hr7Zs9vxlh6wEXzTe$8-kJ7|I(x9e<2{qk(D;oK5+^BmH2s|m|r-x?Zg z3iirQJeYbeT1NUfF5cXffTQa8&;O~6F5mkfmC>tkZ|`3T>h8`rKiID8sYyYf$a+0? zd*rq5>Mk{r)e~QIZOqZiBtzr_hxU#k-I9~ZL`FH`D9+AdtL@!` z!Tkiyqrj4W$AlcTmMt12SZlBRU}?DgF?jV_ID?eUg8hy`*j6K-75-J0-tfu?EXbFK zFDZDGydyi^*AQk6h>QO8EqXu1LYAlV6ophiW!zi0BY)yZ02Bd)&{*lLURq8{+eoV# zj(TS=rF<_bo$S#s2*ueRGOX%@FhJFK3+$0iR`nK#2DV^l0kgDebrwP2$lq)+^25QQ zZ{gqp)xd3mRq&ZziH1vy$OXRW$*Q|s>AiZ3oXFpp5{2AE+KYV&vR@0zsj(@f;Xm|y zPtE1ns1)e{T1%&!xBa_qo~m^@{4;$$TQ*tZPb?K5=GeIJgT(@eR%nI>4JbncXXxo; zU`Ppu3{Sk3o5qt{>~z)BS+72Xw6o{ZgKn#>fd_{JhPPl49I7)JmsP0NXk)X9Qh}ZVIddCJAF0C28Xpe#)oxfFn ziZ%Z|0k?L{l(rv?@7$cc1d1QIJC$YYd6m5O{SsizjOw%2v*lTHlBJ z#KHXN+q*!&*Ap*;N3})dh|t=zn$6D}Q}>%Ub2ihh4{El$wxwfmS4bL~VGkz`e;pAc zrDx?W@Zw$hk+Qlf2l`$(;dvdxc~HspaaZB=%;|3p&>ySK!VIS&YeZPlQGzV#^F7j9 zI8=iKYT)tG?~a7dY?+@;hFVJK^UiZ7?le|Kc;k!VL8;m#e#SDJBk(EO3iVyElzeTV7{N3~y8o?$k8k}(@T~~HO0Gmfr zBXD!E*dy>eaFlKMo0{P`OAraAmGjB#zwcbw2>ceoGG3>c%i;)y@%TU|$L z<8ULrMT(fA;Tlu!{^HgRj!}Yk9Cz0t=UqLkO=?sE!J>;7D8M^L`vMcSB;gM(EfaUY zOl8v)3*2tFeDUmK_s&0ILNntZ#kU}mmUA(2w#!s`XGzk)nm(Tve53GISY$&UL*F{! z1#`n@yQnM|5y>UPR)azZEd64m&FFFR^N@^iU*61Y`?iKnjs6t2W;EBVJlMV~WxJO2 zcEbnz-1X<|-*#SXuw3i(N#+uqK*xcC{adm+Y>x@SoblP76p-@)gJznl4=aMHg0-E1-YaN(_gOoUo*IxmCYvqaBt zdqCcVMu={CwuE$iM|Tf(0{AOqyyEKl*$@>Y<`~SRaG-D@zxqt7cD2&E1VO92hKxrJ zJ(RwWi9>eNIR_nOaN9R{7nfBnq3BKJ{qcp1HKZd|`#1}()Gsg-E;*8K{qlO1b9aP5 z`d=^4HZ8KZun|<-sgkW$*aFoE<~Ct8USvM>94HAXGkh|~lQYSqS%_u>6>{mz;zpzn zEBB=5nQ(P>X`mhOgDJsf-u2I6Mm)j4@Zs$sYO}ZzJ(%Y} z^IN+AEMYF65E=n*$8p&2ZQx95m8X8#>_TH$$n%!&Ro{=+*M4@7tfHUs4OL%-zW#vP zq^z3h6^cTJ?2oNAA6b~I22L=VE3CS|RYXPZCbiu%SBafNL^H{fE)}ifjT$7pBRrd3 za!Z!9ANsFU?(Q)f^XDBOy1&=l-AS0(ldklANJY7_FNg*#^@lYH%A?rw(aS_kg=2+jMKRK^dh!yZR__{kk5I(_Mv4-Gn`^ zgl5;z*xL=2kEP0;oiAP7Ys18M$({>*#;s2PHqwJIN<8`4B0puQwdgjsK<@K9d~kbY z|Kl?+V2!cw#|L!u@Xe3HYaS*puRRI`ZY^#L+jI(anp^a4`eHj5lA(cs;B9Rrq?;j- z3W%ceWXRY(z9SgC++x#tI-dstY34^qw4>lN+_jf-UBy#|wnPu+Te3GnzIR})gDe`k zNTaO~3@hURv1sP3yAi%#6FrtN9bLcEAGQtob7SJ&s1(dPaWk;@g@EX4VQ{NPT*lKZ3#;`0ap%!gtk(uJGXCMW_<9d$t$v*noA%cNM&BZozKO_u?^*zCUba8 z@zkP{!(O>vDOuK~h$p&zU^(PbwSpuz_TB;0$gSh`Q~`H&kZsoaG=+eAsz-c0ekcq=FAd?CR!>1e-{Mr4 z)(M3h;!!?36`-PuOXoIj)-hrJk29lo?3)HD{PEoaTlckcXW9Mcm^noOUn~N47Cj}m zr4!8Q@5~tlUis~`@u$V;szDeqhobKFYQ&aKtR>NdKc1aZA|#WY)qZl`eYbeo6OtT$ zcX{wK&gc!K)M@Hkb3IvR*7`opEIjws1hh5XoCo)tOZrydAgmJDmSQo7HNWKIO-JMi zmruOThlz28_qgWIcq8I)xy?G5DwqLVTK|7v6H*^_iy17g+s;=xbiKN17RVK%J6zfx z$$353oxN}~xD8Z>s;jk@G|{IXlL+qU>z|<21rfax^S0vNDq{CTYfp1dM;9_Af=>2C zIF8uwnV9#|lWy(`G0N5_O9%J%OsqbpocbhIQ>dI!WU=47E!&cuqZKi2zW&|Ig!U%> zFO?W-)Efh5)?~#FjIKX!mQ6?(tcyql8kR&v`eTrsTAE0-rrHmUeA9%2{fsf>Qi~S(?|k&+%O$ zeCM0-zYd85L19g=_z8!BLG<-x#I4^OzzMI2GnUYJ6b+?xwp%o{J|kl^#tDB_Ayj>m zq08dNWwSD9D`NL`>1W}pta%gPp>LLXJ8byybVG3D;*9L_v2{xc_!hxuAYHb9rlev^ zL((%>B2$HIClq z%Kgv`99=mmoN>0BH*rR`+6v-Exw-t;in5FiFk#>00f~|2K2BT?pnmUit5~?5^5~w; zi4~A-qLf`u4UT}4JPF6Pof!58hS1URw^~D7&U!^pJDxfr2G(l>TrOlz*w7mMe!~IJSQ%A>-)L8f_gO2)ZX^pM}f@?LfoWF$^5QuS#+OqQ)gt?oGK)$io%)3UzT}n*gw(SB>aUs!{Pqe z-Vw3V?^>zG=|T+B4!};-)igSJsPWp8foDIrFLoIzi#9HkvK{GhX@(L5fonuL3(t)7 zJ;KeO;ptJCK++KO>{+{jg*ir+mIaq<5rZVlxn)%03McZd5#VQNX3GS^J?(|{ZvdIRBEe-Pp& zMm?Ci(`P-Db#F+FFgahYx0y7&Kc%wWA6abNKv0;TG_T6Y$aJh+t5ih#aA>75zPKMT z)T5fjMnm*4ej^0%Xl}M<7TiEq@RdUig#WZW&yrTBkB9sB**MFH@{I%vCGphx0x8vy zm2jI-Bbpu?&lf4mwg>|Q`o%Gjv2!wDS8q0}UD>r>^{Z+R{N7S{^L(A2ggN&QgN<-= zopSR)Q_l_~KwMT>Y!_P<8VgrU)2LpL)brU&q@07&(8!}^rGbX}aS$U*#K3C(B=oFTW%1#2 zAdZ<}gl0LbUX zQFdh((=Pp(`3n4vXFCoG{# z^XcWp#%Fb^CsXB;tsbnmzWF@R`eCb5`k5_^Vl zep^K$`&ND2ruWFNj z<4E4Tfaa!qbMLY|^25;LO=Ul4#6!zy@lIyI_eacU1Ju}=U4tGQyrrjvn^9lYXl?#p z(+^^&3({_SpMp*7Yqd}O3=wSJwL9ng4t?ON(CWe;hQlF z+EL#ub$DqCYaU2y>%~QH#C*=a;|tCKIQJC*!`vm?Iaf}k&b|ELFwo$L%~Jq~Tzba8 ziGk4~{MPnho!C{AwmtXUUCrLh=YC~x~bOaKTyPoD}K85X7q^bE!#<AgYuXH?{+&9ezvi6`jYYVy z!!^lPbCaj{{aQ|WCO}x`lBf(m+uEFfSC(9epSbAl{V^*6%PBLzQm?~ zwwW4cnU)Aib#j7ct#F`3kO*O|&k_kw4sZ3S3j5L!x9Ie_hBOQ|wK*Nf;`#djk`ltI zkOTZrG|)Ej8p%B)Q+N$^ng8au^>|0vA~k8embzaDlxU@(YzoE98I$YrV=G3;+B5=W zHoXhbvdlL+&H_Fw^#);~dBHmQX0UDVslrCGfRGM`q7pbGZ1b7gh*dTpUe`cM%i86# zu7;H%fK4%Q`n&d$yoy7C_^_-{izfhD!iHMR60?}R`wKfu)6V&G&lu_!FWW?5^PF14 z6x&asv1K&lpt^Yt5s^6<*Dy(!dul%=-ih&jp?5A8^U!&^hlyw2$pr*=*$Bv8h-01| zh^Jxyr#Q4lr99X?MM%yI9ox90>s9NrG38$l!3L)gM+W{f_(g2pWt(`-k(eB*_ng%% zqxU{XZE|Enyv}1UB|DIu&I6765h9L{p696P5$=wSD^|#OW(!CDNCh3K;Wy_jLa`fA zB11Hn|9a@Jy>@_zR84-Hd!7~rIr{{8ecR$;rZzD_BK@h^(SsAhlnr33aAw4|R!0GB zx0Hu!){14;>LJ4^B;N6l0LrTT!~t*Y%B0@AdGjR16805|=0x+v87kQx=nA8XX;+N2 zM!O%-YxOBEsB=r27wJ+TVAn=a_tN^TMX+-`k%+$L4I&tWcemSx0A@YNG{Re;0-Lbo z!~w!Z0VRUZMWw zLO!}W+HwUB_Hzj1N0LIX_$ zLgTv6XbVh3fbij=Dn*OX7m6q$Z;#*nV06WaaOC)h0crvSN|PxU6W8swXL6ChW#|p; zpMX=b*4O!J7DS-iP5|H62(;gnw=nlaz0^$5>;i4 zKfk;py)F~hr$1>8thm&vD~%WS?&oot^Xe6RijtwJ>hCVwjvB8l_vX+c|KMCai#0!@ zJV!atO6gSqG_CTThnW6AjBY3i$hTKx*PR>t@+*?ixrV{EBoLN!Kc)=D0XD5EzpJiu zF6cB}v0XMsZos`0JSnS+7_r4e%=n{GtKQPB&vQ{L5X84!(lhHeVV@^Hfh+30`H=8VoSVafqvguP{gxqsC36HFaN za;f=5l%iu>;dFe=tI0{jPNG%5zW~zs+;9bM0wCJlvM@UeA$mJkK%JUt**A98Vs_dH z=Sb+dX&7A2j6*RrLz~ExFbm0arnbUnmvDF39kJkE{Fh3vk*|J!y&bcC^LWOfu{+7q z+bHYo--*RNX;~WW2R8!4JDD3QYdJk>xCBm4$LqYS1V#z@E&834={~Ha&q=&a9xuJC zuI)d|iI&9oUfIf{9TY#DD@q4Q)T=j>Iq~ei_-}nBk39)S@unUY`9eoO`475G zqr{E|zg=1R%UjC1$V!{$&$ApOug~K%u2p2oY0@SP2%3^IqgU(vDLMV3G^Got0wh&* zxcy6^QR9q2lwE8y38Oy?-;FXqs!;HDE)02bq?mQ4kq;$u8Ww4>;ZFU55iYZeSMm<0 zjMO`L`&$U|V0PjI0W$F$dj3u4bgQW5@q*T2Ew9;2upZS&2=kg-EQ7pLhwS$j-KPxK zjZS_YO76|1M5)B_?c<80t#43kZ$GA=(|jx)|5&$TiK97xs$WFL3lvb!1=jV!J6nrM198wmmlUFPJl6+V81RAP~=j8cs!h zCkkp9UpHa8m#%idN#?bC%pLkGf+ioPt(Tpi)7>Av_Pp#iz$F6v>#sS@!rhHmbGl6W zUB)27k+5;uJH_*<^9|&yh+3M0kl&iW(f0lcLdlSg6{($k-91-2i`(;gGA&oTfb;)* zEJ&CNt=xI)o6UTcFT;6+EaK(>bBjg^0F)0Ov!{wM<=Iu^RYZG}N)p=n{dnzjBL%Y_ zCP#j2FSS7}agY6IjopDtyM#1O6eZ+bh)nFmS?%m;?c7;RmWFi%YpSz;vF9ZcG3E5zf)8FVW3<77 z{{NLWdepH~5IF43eK8U2Mwd}=r zgA3*{02gLKVrj$RnI~S_txfH-k%~*o%(N6rj~VHEinw}p1=J6H#G*YeBib-F4>55) zT5Y4>kWhh7A;Zl-kd;XZ0^--ScQ_ZJVQGp*-@7xhAOjGVsWh)Yl z6|rjGwM6jB_0si9wtF8h;RKl;t}M3>t%j_SR4?4PUj6ej?f@iV}$`}+pl9bCl zw70jnJF`W*@`Bj3y;^*Faj)3^dRkR)ZOYuq?sIF~)-CwKRI+L&aS1`jsQo;~`k5|WqZmd0Wsa{Is}n9EL}4cg3K z^4T^}dx$6U6ZhfKWk&rf8P)yV=J+iK5~Z)})7YJ0va^Os#|yuq;H)}4YSWZmZl&p- z*%2Nmkp2z-$)xLKMWf{Kpk+B}bw`K$rkjpBs0qvv$mh9rJ7@R*mH{4bsQ%Z%ud|Et z>E^SP6W-b<(Hl2L3Z2vAo(6X`vsX&@AK2lGJLSz&B<`jRhT-tlLiY_YwO{N=pSb;| z-9D+3&b`!UXv9f|-i-R5{f9k6B3QV4rZI-%bo}2yHm5)zf*9pVG9c%UVD4UB{jQa4 zbmE`>8J9Qth1TRJ1cbWd^}O@F;zi^Krl$iRcjAd&`yU+N`LfZn%dRM~-x|sx17_ty zGwEIpN7;&eRStOR%4C^mG4Od!bD3jXr(fc3&33<-ihil^Oy)o_S)vt|h9nkRaih8f zf#WW=>l5yGhqkp-3G-Nv)P|Wt_;nZE7J6~V*mjMF`ma}||F7_8{rq1!_srAb8Y30` zts5UiIehyH_AY9`{;gd6e}kM3m!T<6*qOdu$U z0zP)w;oiaT^a=;kKeis6kHWrmrd_~ z#UWkJh9e0hdNhzvzz;;byc9Ni;R5#DvveDU8Wn;g4Fc!64049^`xp1l>m24TtpHwA zrBCV@ZPJ%M18QZ9v+uWFVgwFZaJEZHT>j*Hx@T|eC>N&pHIt3Jwz+(G2PuFr@~vn_ z7|tkqNYem?-@F(85@?&CK-tJ1Bz-k58<1^59$H+{_oar6IlTQ5b%6$Wdk zSnq-*6+0@cl8DT6+GFjdBNZrQ$JRD{a#$Ojg+q_!x(G&+MC zLS%L4k_XI8hQ#ftPB**A2fB_8Gl>-s&8lveXKrrp$+<+_O>zZS4(@I4X^97O0h)xW z%ol-ZO=gOdwsb~J{!M2VjMi(T1-cxKz!!NMP5c%LeC}lXf*GZ}l=CJ3-={>IDr{Ri z#X;s_-?atWE}4#pv&`|!8woQH@^cvC7qo{z(521RWGV8^pT66(8pDDqYE9c`J=;7e zhRHvIYS;XyMkCL#YnzIRO4e8<_=Jj> zIsE7z6e)RX21+f`D=ecOso!&KfD#R`ecGv~r^b8pyk9mly;l35c4nws_^1uM>$=mJf;(xI1xr zEV;R8*S;hECS068D#Nl9$RU-|aLF-lr}Y2h>%F7d{{Q~}>-}y^ zOHs76W>GWYCeMGY zk(zpv#z^kaW`yZ0GrE4nT$nk(BDqw97cEWjw(Vs7%Z2%H+RtXMs?rrHI>|i7jof`LaWmDj(4vJk13@uq!$@0X_t+|YlP;Fu0QXC-<~P_&+uB+9HO8$-aHQ^Dvq$g7A~UQAG=$ptAKQk%(Vn*C;~!g=SnzN*l*ilPTXqL z^o!=SfJxObNKuhM8C56p6)U3IX~KVbsGITMYyZ|G>qopBuOZpKcL7YefDYS|x9tc= zfiJr>8xt(O=D)6<>%x34$&27|wwlrn6NWj59+=7J&FM${%O+){mML}JqklK*9BnD! zwnk_Qy?rZS*b$?O>*nrvJiZ+_{+C{Ghxh-|3&^N_9;h?)Tf|NhO*)ON5sf=nvojS0 zOmx8?1~jr{{xl%2-4k7xjj3IJMNd`{&_P_guoeIOy9h?=*f_DNYp7ueZoF$c&#li? zZw8bq=zEU!`mm$&?+i@ipO>yKx(>Wlap2Ml>M&t<*d)1 zphEYK_DD^)MJQ7^W_@dl;vzrJuA4qPP^agqgP;0Q{KiTV0EhCAnSUE-MOxG71op$s z@|3Q!gfBr`qPP7$Kv3UbdbX3f!j(G|k6iHkcRT7ib)Vzhi)K!8dAxKp+*g44q>CoS zeejW8AlmB5!Bald)jvM8y_&nXn^i>pEQEDeis%;x59VjIjnh;NWTRHazcqVx*v}>l9He z_FORzD{WJo4AFm==nLhJ;IWg?&RyV>{Vo` zjV@vKM(9V!yPJs2u=W!KQK3A50< zjrRH9C9vn;ZvCGnuqJbhwuP2*=0`UL zVzCMz+EQhrtjBH;c|1vQ?yo<1dhRwEDjYwP0`h*SRmb9E#g(7t5S~l+s9_C55K6-8 ziU$7UH*Pk3xqH0StQ9olO)nqnjO61<8%}lT{^%2cCl2?u2@cHWtx}!4 z6!&uRbx<`v%zY5OQVdbl$pkuuBuP82MkrYtyY80HtoO0IEN1*)`#y+uFi;ax><6*dI<% zx;yh#xqW81WEj2bP|=gB(#on(-Ogq!QJZ8bz)weJ4fW)eZH`z}B0Sm6+#_gPmJC4I zkUF)}K#Qb+(*=^kQ)|FTYa9F>UbFr&3Zu9_r*t3EL!R?+s`{2%i2<)X$Y9vpFwAk1 zXETDc;q~X^RcliRD6i--atX7ujWI%8<#nsftFC@;03mFH%yOd9k_R@L_@Or9!EO3( zd}BjvNM;Tmarrbk*oWF8D2%j6(#M^-$98$1g?iXbKQ9%6wM}|Mdhilu!J7_bFp}W) zx*Ysn7`~a(%tZ%1K-TSvm_FHkEl4v;7K4rhadm^Ry#W$9C2ju?+6Y*HiEB+<7TKT*T4yU0p?NsFpkIBl8Ibi zQuP{@XrL)i0DVpNxdo48->^cx|6|1dMygi6F~U!Lwo)4>F7;I~Wo&l!Zg-o^CW+Ve zSKj)f$Q#>ZFItRqx!boL;wqC0=6eIybOxL0y94r_2Lq2KeYVP68k{TbbNOZt@_#ir zKZ6K*Y;HY(OvOV2!z1{~>n61`(Lt+~3QQaPr zxlK$*H{CrE@GHU9^Ua;2M`thJvy9XJRIt?~l`=`0IO}?l6b$33IcX^8MAckQH+PRr zRwX>pXZ|vbu^rjexDxhwt7f68_jCAKFvGZz3}pPR&TR9TBJAT4WbYn*&WTEK-vO{u z(ylq%>KG<|G3Q-jF8K@>{>MY%AKir}4AC;HoP2B-%YGAW7W1eG zEndc&rzj5A`%JX5D!K&m*_ADMHkoZE{Ln|?CJbDy=G5zWN5?`Dg;)2y{ zC8lgUR1-0H()OfUakx2ipO45y?vNKl6x{TVuaf`cZmVd7^Qpbo8eao)GiRpcF2np+ z8c4_M`Ahu8K;jyX{C}wqoK63i>L99im;E9RYXh03|I&Nq**xcC?Yb<*x|zfQ<;+=L|h=6i&REa`x0nYcH? z5lsz5AP7x59*@_f*Sg;RU^keyscSL&E$}ObEJw}1CZq#`3W``}t_!8F@CX~UdvQ)I zD+Uu{05)^qYA)Ojla)d5<#s0{TL4fdest6^JaL*(y#17!;FxzBa~|8nyr8bj)Sl&1 zj^{4AUMCSjc%*r(xARP6NO=!-O4RYVW&e<|Y0=2SV2zYY<9S{){EtW#^iLajsbAgH zucO|7Jq{t6zZ@H5m9vFQ4*n~Ux6}A;HJMC7+q3Q{;$PC*b|ev5G9|1}eQqY1$iM}@ z2+uC(z{kC@$d|sSzYJLsgYOBM$&x2QgML0oZ^cCNK#33gOx)CW;>zSapjNJVGzTh1gEJj zh!3Hg%pYwpQOoU3HafTUDt3>t!zbyiSe5!p>(WiEKU1GgDs3oo0H2-Pvd=C#IzoDu zA!tyL5Rqozra|JbT4%GM;;U^VK_WxFUq*l)%(CU*HMJM532@00dU#uz9!v6i{x|bo+*1P(Q>S;@ z2Q`2Tk^dQ(PW{X)=aeG&$-ODIP`KnXs$==jv5$T#+AdS+hjz4mm3!Xd3V2oTllJO- z^Ct?K&O!e$l(grRmQHW?;$8W#_>`Q`J@;*KzhKjGIYn;M!NHK_0FkH0fnKe?*VT$# z5|`Gvotbtd8-=TW%(X*?_qGp|IwM5<<{yKSsq(5i4J$)Max3i{yIZSXgG)|l3*rTB z!QknPA<+uzi>dIQSuJS80$gJoT0CXr_~A^|mAPh-$`l_LXYR(pM0!v`RLGy~#kfoR~VnMtIVwv2AQ7bp|V z(^pK4zm7dP?~{XdQ{UZgu6gx%CwVra51|91?MTu~Gbrdl46a6p!*Tf$evlNd zd@fJcN6if^1OL;X4TR$?-sr{#&hFT6he1>Sr`jWQGVr<8xAN0(WKaez)OTAeVqt)r z(+^NBD641YCk##~hZ~A2;W&O53vIz@?>e)zGRs+kBa ztKXgUtp>4)zM(ssEq3%unqbdbPVv0hchwBV+s(2n9^R8VndV__CFjedi{Tu<0gg;9 zy?%1$WEx-B0gEfUoHKLlnNGk(UJZO74ZBl=$c!C6Wk*d04yXh<*S74YSveotVXgX) zeE(=9y&MS@QuqNYLjmD~FZA(gc&oFP8!T078^{FQ;=uQ7V1~(%OEsB0A*s{~&SAK# z(jn9Dnn8kV*Dk~()vNd**jYb{6i9-EA_x*2e;YSilECGXLE^WO@6AFO{2c3{*gINX z?@D<)TXk=RS+PHB8c}-BbInJ0HCcd&1#&6+`x5!Q{wyv!W!?{Ost)97^E|3tdJ zwGt~@Ne=*dofJMu3yS+cuBpl6-TvOez7Mh22ne8&-tac=Xb9PYd4mn-;^pBG)=|YA zXy4u=17tM9t;}PZ*wJ_qfKE+^Jqp-uEq!&mt9FEZU`QAEEiuDXT|fOo%E3BIu}nPE zAOSFdKh#*?NMXz29BWx-*BxaQ{5Q;qrcG!TO2wDekVFLQ($}^bSJT)o1&hP7ca+w97Jnc)I@4 z!u*hoH=G+-H1?^RCYMfD@|!Hw>HArn5k6*ib3jpp`0JcN+!6ri+@a6fvtwJ63>w@Q z*Xno6N2sq|BK7WEA(UuUA@K$6Xg^HHjUs`Q`MX{Ov|xQv4oBtFzGKG!-N4px`@b95 zE~zuAxklWs+5_o2J1Sg(y4K!>wI8^&mWtdTa4naTDC*RR=sLW{kd|sCl|~mkfJ^u9 zp%ChYnH@4n!l($wbx06n=2;iHImbO=?eAB2g`8g$)N>4vJdVZg*T1=%w*m(TJaoo! z`jM;z9GTW*C=L|C!6q4J7E@+p!GHb#b^h`1Z*(sP>o8#2AwO@14)8w;9jHya+r^)z z#*v`D%5ix~{0wrTJMS>*5NubqU*VIoxF6&1?^PjOm+=KrTpN0&>Uvt!k^oyS)<{7s8fEwd$yHy2}aY&ZO}l~w4P zDy|xfGoTJGQ0ZjPIzij{h78v`3-|Fy>Y~dQWT6DX^H#|1g_A>3s2rg2Ip2{&Paf)e z5q+A>ybHHbNphYfAJE!x#dWV-Z4Jw&fG9TjCD4zI$683xJx{%-bm z?0-aJXd;rbOy@@ItS5Rc)5tCAggRAd=)HD3OGgJ{=Bmuj;02@0j2aF>p^VG($?+*E zjS>U#Izg8(i{EU&ZW&_T^ZG28=LrKR%WGC)mtvLIXF}S?SM-Aw!IrGYBnXsN8X|8rEZb0lmtARUP~P{c&XcA{NSvdmRoqco-0QyiCh2c>dgE}YEzY45n z#{{0)rzNh(CM1KDQ4g}+b9|rtOWh_HCkaa?)qR)Qe#=Zd*g`xsAfh|FHlTE zghD<&t55UrmyHus*FfFzFXI5FVttdL>p-U5Ey@1DlgOLyxH5ItG#sW%sarvVBQ2O- zOUYxNm}?;Ds9wPx8kC!0Z`Nuz+&xT)skZ_(N-TteD4GJzb0F*Wq3@UenZz`TmCLlw zyLdIdzm7#S-@VD!;Rj+ad7i10WLxTcZ7q|e#UyVIn)|(Tj-tMwbVgy)4J|M`Ac7W^Q=GE zzd7ngtQ^hASu#bd+;S=kQhO~rknO0l8am7)`T(JpPE4G9+MMk|ZaYvRg|5{cJ9XpLBR;P+~4>tA^wQ_Majx(cviGUR|wIOM&OSmR%; zNyfNqcDgj)!19E_9xo4$hI;tIp%0x8kGEpy+UaG*@TYNCuh}j@YkFS++1@NtyvXPs z<^-IOfyeU_413VwafJ@g2AuEtCy(>sgayI34<=s+JP7=85T!P-W@dvzTf0Y{+Gr0X zNFV=feH3t=A@OlQvoRbaCs8<#c1qr5x@Z%?PjgRbgInuMT79;O4;Lr_fK<+ZQ9b?g z?k}3>{O=z9hk5H+{lc?fn$sd^L0!FR%UjfAxJRU=nTFOvdUe%*@%vwm1}pI-i`W9y zM{Z?TE)`1^K22nfxE9~R*_+Qe0mPgPa@7GR%4587PCbPUF2IlZA`5SVC>|BzAFSj! zHXHc=&4}tJWlcLxXtjh(p8I+7fOWajaszeQ1idx#Epf0(z=qy}eVPFfw~qva2BObJ z-gT{}6HI)9a#Yp0Q*yEG%8SAav4q-{UP9fTw;OaU%v0Me<5FG^w!bzO@2BO>2!Vv}&g& z6Uu296uKS6LOK73)JU%OhUh<}Mn)dD4s&INe&YC&N~;#`v~6V@O%9KWfzeR*v}L*5 zCIc3(wG**CF;BSL2OJ!0y;%(p)uO{B`Iy{6Ojx*b9m_fUZCsjFeryb+x+#{>`CT`Z zIuwud?a%ofh94^12*VG^=Ep+S>sZiI%hXdcHvmo zGzh&?kPc%#)RaP5f^vdHqPFZb*&>MyN;h127QOhBO8PZzB~-PVq?kD~&OD3LR233% zW2_1f{X=N_${v>dd?QECi>*)T;R@U~3w)Gc#5Ad1FBK7K;X_YR|_2Uc&*- z2`$-q%`M{V(Uile;sAL`Ok-RfD410Sd8;c?|2d65s;b@%p}LUl(<^uFvfTQ9nnZ=N-0{`{8p zo~xNwv8uP@RY=3=TkaYoX1Lw3h(DdE)EuCRCvAxyA-rd_AtM_bA$J7 z6_oC*ALC-(T_Ms`|5xMEs>Ul6VKkYLvBH+@)RhdSiM-YQeix+U$QU6;le=#%1hK-T(P2o9yonqiI6 z4Sp?FeaLmHv$$E0rSRUh^x2E}mmBsR@$2Li{WZI^Sb)xb*ANki`mdQ!abaS97Dyu)TSeDg3zs8@F@hw zd*Kd?WhoBwrztyOF6~c}-9Kyo4{Mnrwe#+OSj*xDKk(GP5MV??4vrZK2`aLZfF^eh z{Q|^6#R9ydN7iU#HkI;&n*Cb^4)jqT_1Z!0qprU`^)P7qIlNEg1fD59htU|6Xn^fI zMV_dm#47bzEc3%6$uPv1aO&yTUg~DWoWZRjk1mEpXIf;!CFX`RE{NO*dk(8MxHU~VCpuQCB(j^;=pL^>uw?Qao81x4tB+_ zC`w1XmU^^tcK$l%YyLbOxqnf@&}G!Yl8yDcqt&s%kKw6lN|XREw1k)j60L%YftzI1 zm545HevCmd%k|Zs5*4&vpVuVvA%Kn_9uUSn0~-WfJ1TLe*&b#J*03KK1a#aoCBfa$ zh~cy#3u|{NLs7axuUM*>86zctOzH|?CJ6Iky0#3`A(X@roG_!R-{`p5%asoP+2u=c z=`P%LEXxm?Hr&@SMAOdavH#$G$ziU^MsKn({%6)Ql>@YGRf+ImOeb89X}?`>;Bx5H z;i?}qH_(-`+tX#1sgC1h{d3@4ArXgtZ=y4+aj~vZWWfpTh|HLwRL7Bb1Qw69t1>Kt z80SEnhdM1jrT*RymJ%mY5_cVO=rs^6RY5?AIkJytw#L|bg@ek6=jtdCVmp&UC^JV& zd?jT{3C2=txoqDbaJu5FNkp8N%aRZV*f2R(4pzPLCE{rs2uZ-n7^C(@);boL8pN)c zGK9AVy$nrOUi9yvynHLnW6&#~ZniPSi1PDctiNI=wO2<)T{F~ixycNx{lqNpTOqEm zZ#XX(>_X}V{12{0{P&#GcH8v%Czs%;1rJ{`GwgLQ zL~;iTK6N8ymoqzEU99&{5zjrw+P9)wL0cXZ&5NF+E*rmiz2DG^){%l-7ls=5sCEER z#@51>EcZ(nYqu1mX6Wu*;rYIh8g6k5 zIz1oj?$fTM0_C&-E!W;HLpL%53e71QLhfGZo-G3;QyO42l^B;bqXUW=V{)_Z7#nu0 zx)x(Qy#QVw1&?-VVCUy-{=*93WXR!d9Kq^qY1DOP=$0;xRkd+MtQ1918uGGh(sT2N zg3ZQ|WSnodJFU@MdHmct?p*TctwP6Nst&sQE*1Ov zCKuP5HfDi;PJK6*N#yl&vRoWy)Ek@9K%&sSmGt~Fw6bXM1X!oFhojLG38rXG(suD0 zshDg!rFQ9xmKP4_{Z3aTnc3EqcnMx8>Hj+r$9hu2FJMv~#eVqw0=r^8Is~X1 zA_@6qj40-Eu7N`57f2sxIizD9;9KmqU5@8zXW}3JZ2e#m*wQAQGx^}fqjQPB? zoa=B@Cm1EatF6)j{}$WECk>Ld;ZK)>#SDD{h$lzMzJ6BpG8%-qk( zPC6CEI9qUloi;ftJKrbfEOw_jQO${j<1z}yX~cr-#;jjEirV*NoP6koQX$^_I!F*v z!$r%xv|W{kNYuVULc)s!yxu1g2Kg}r4;e5Tmh=M5+;SU)7JsOUYm9$|x}r=n_L2iu zo@x)jBrjhMiBUsWja&pPF-)%f>vuj%D)~LP^P2tc?bsRBM!Y9dzX^b7x6%o91n05Tn>^#cTCRx-5b(k)ToCyNXKdrqW>@?V}s<&3kb=K{l=p zub{l2@)>8xW@;wgExTZas~8@5p86>H)Kl1Q_5FWIE<157AzQ~-;R)q;*_5f4e@i@d z`yhL=Slp>^L-OIh0k4wZ`!;n)86*+k|; zO!&n&wc5+-m%4bPyhzx1S{!A#a}Y<8qgcy#9UO_POK`xg<;%N#pWAJy969_-ngWv+ zOb|woU>0!Lj~+GWRdm|Lw!8Gd1T2rm*XuzoG3+1c1V$Ghsc92QSgsDI$6`gOA+aoX z)4DJ2mOPeQ%E+Ct`dawNC`P$zn12uD)2n@;9E+vDtGxv8@4yq0_VMVTG`qE>)85cW z+68=CeeaXwcu@&F%_|xw7=Y}aj7`wbOD4!`E1&JP)7iJ!GQitpQ~%q`GObP8c5e0% zC>j28d}z`%8Sg}J%Kd=(C2!+z-}BE&4xk6Vap%z^=JiMIV#}}=0|EIGjrqiKCo(a zT%dnvjbTq|W_)+=5J@q4uCb&{+j!xAD&BJb=2rh@lL&CV;*!Urj$Q|lOr1u)lA`U_ zn%HwZ?AEgxnfUR>sxMrJCNen=O6H;Dl+|m!T0+(hTMsQuj-(=+qsA8;bD>xCM@?Vi>tY>RZeyi{R*T|~@?>Y|NY-=Vieqx7#YXhPPR zqAR6y_T>89&*;UQu?gkBeN1$ZB|a2#@6#{i>sOr)kvB(D{6G9vF5UH)WA}=O*oSxj zpqS5wRsE#IlM|>Xwl&Fzym`FX96uez@zfrCREv^)GLA7}5HxN}uE0wTOP6W(9`(EU~1}&mDoI^+GJd)~q#hfH0 z^*)(>AETV5rinWSoXshb$C^G~M)UCxH{S)l;7TbcuaNgHjw8G07FzF=W@tTI*oJWw zhFk|?nG+)_o?Zya)^BZuE+LDH4?^y4X5y2<{8^PQ>eemLvDveo`<32&=Hghl^yW8Q zX@_FS+g*b71~?wJB#II2^-+?_%8AOU!H`PD`MV_8wG0Q1$G>Dph{t*KgJE_%#$1ap z1c~h$k~fO9D32}DAxfnFdJz@%I&vtodV#FcvqA^qdQ(MCJHzXm(=*2b1u&`W!(+`9 zu)qd|QPbvk((VPSs*PZ!d&df0WE+C^w30xF+xinO=8$S&kGj34z47pj>j~kfdy?9v zFFh>T2N80*$w&=cukOhOm$vgMOzq|dOqQ}tVjT`O@%HcEA#J|Wi|N`u!n7E7d~&`v z&*klI6OM7J@aG579H-IhQ5Ktt#1$$IPJ|N)E}m_xZ0vna1JBgGr^j1Hl+-vSHRHbB z?>LaxuxpBWusiHNmR3Br{{DPCsl;R3!8tH=3il$kgYe9yK@iRMN3XM7l@yvvTljkO zVT(wVSZr@{@!Rpc7E77E`UQ8$>@9TDbz(Vk%oC%9ydp-=iniPBLw7Z`@%1B(RNeS5 z=7ZYJJzg7TwQj-cZC!ZgEL{700Blwakknh<4)^&;N9zK{p2n}5DOgCUp4oQcZs(rRw?y$gI7unn&z&KlPc%+;lQ^?8e9-v!uf&H}4WvWXEz(XY7)Rvtabtf1*i0va6cAKaffQ$D%v3Oo@wcAS{IRC@obaQUb}?zI zaHrifBxd_(^_pqrKxCqiAC5f6?K_)@U!hIa!k#WUZXzf~WOXv;;_h^g$x(I>Hf{N6 zJ_QWB@>};O-Qy2_3J9sF0gB#qJ{zua6C>jM~88 z=6k#62f5T)6L2pY3#pyAt)SwQ$r_D?g3jAcQ1R(x7Y9|*#=>LgZCPl`OfoOL5aqnB z0&ST~u7nrvI&UX89P`I8Cl%dTYXZXw<`aQwKV?mZbTk3y>$1eoGIG&G~=i| z=zMs#e2Esl6XC1Z8vnWWqIpO;ylw}-*=vatAoYQ+_O2m)4W_g0BGe1kk+aniN7dIm zv~}i;D)*)+`YJz%2>(1MUd|yNGREg*ZzQ7h%RnYW?>4zsZ zV#l@{pEvId*J)v+4H@ky>uZT*asmRp%8xq#m@Eu4hkcU%#U${$=aFY*__j!m2}dRP z;z#S#4^@J9COK;Qo~UQ6izn1h7igr_6D0&qST%iKpyL}Dx}$FWQy%%cGahS8f9%a)9Z7!|pJGEFbN!n0 zZNUMNd&^HchqfKm$wVMc(M7Z4EK>hvrRA8D_!)gp^OvzfW|(`*D_Dxjo*on&;J!h&fG6krci*zL}H}v;*nXfRZFZi}mY2paI+8rD+ zYe&vqyaQk&G4%(=`KIWmc8LHxi#?FSa z>Knm0kBX&gQV_-E-+E@9YYn08EciZJazEo^iYuVt$Qi2dBV{I#fsKIfcP|N=ar%=W zUY;)b+*%Z`ekXR?o#dow%He~by*s|e)ad8>mI#6HYaQ{K<02O};b8>NJ@L6-@j#A- z@|AX3OVl?UMV{iXGY$TwuX)^P@H{V4iF5Ms3o1)3BVpg$jXHWOmzmgX=blRDs(qNy zR%}?NN|`p;%Yjj&y~iaLOBNayWDLZ6hd}i~h4g$xcK_78Yg$5|t_W{STV$-4=VN6O zBX4DC%Y$(ZD1RBu2y%6rR7x1mOE$}X2G|Hd;2ZzN4)BvX=9u0ZwdZc_%K%yUbc;BO-$FBKqKLNSXolQ{{uS1&wc|L=rl|cH5u~res5N4OL-Rv=2z5(%W zsz0_>{G3cq;(0Vq^Gxby)G};Fo5TF<ouhpLH2D9GpZZ_1gtoKQ9DZ2-XmW`Qkz}_wtHNmz9}ks`&l07l+6fTGJ(h-V2`Q z)TVP{#X1NqVf;VoBYiJiiiO@D!o&zBIb+AeB3`hC!c(cuG1O9ZpTnP{J-6#^)P@~+ zSytF3w^dXy2|AMvHkLW*=z0eZSfnv`i}Z!n3AM`~EaaUyzm9Pk-QgXJaebzSlRrv4 zzN2;R0pyjn{raatq&}Xx`lqh+(p+(5+Ck_L_`->#UXD7j;-W7dRfy+MVVO3*M0~|e zOXbIvRXzV4jVT8I4rV|;kC-oNh@F>|M&23B=l8KNxP(p8%4THm*7`HVRZCFaMhEL6 z&O5r4LlA<~+4~01E@v*axnDmEBJ(?#u$~z3;Vo=cnHEes49W&KxspwL#SC~J{MFiA zY5gGB8=?Q)%OdhQEKHNmPbuZ{{P2}V``d8X))%wgUlYYKkp1v~YY`B>+d)0&lcIKJ z6jEHRxf*NOMOJ`gH`|xeK`Mb>i3854>qbS^% zOxGLA(9ou}NC-Jg&_>kip~)+hd@e1B@ajb$c6ZYP6db^Pfps){rF3A)4*c0HJos`=uc3h}i=G~8FD}tB9B@*S6cAj)aWZ8UnP26g zg|5h3m_Qhol340-CiM#`|8RYt2XApo!0OeDdTY1W1O(%T4hA-M ztO^2HGa%fgYqJ)94W6%(`|6UY44q@)4u+5fXJ3X@pvy0qBZlFsYf3b8S~*Z#sL6~d z#>;he{W^RoZ8_U|@jj7{5Mzg|dV$QDy|;^U)qgOCUr1GPhwJC_EiXnSrV#po3*?22 zGCTOy&Km<}i1Q%xuJ0W>9U+jvGWk11~+`-^D#wo$6;4j>WBS4;y zO7?!X?XV5GNbgEi-XY)oJ=K4?J{^4evDg8)PKK;L_uzz4gYn8vejrXJ}&=>~$%{S5*Y^#0$w>l^dGZg)ks#X9WZ+O3$JlrfIvR-`8C@0$pdPTMneWtb!4X>NFz;m)YE zJa>@V=y9)2jNF@lX+9F?^|5e#+{9XZRymK zdQyj!FT$i3YI$q-bR;pqi`?u=el6z6ZFy6%BW=>@^yirNqr}E;?d#1L=T&9J~$;lJ$UXdDTBT;5b_IIV~0|q4pI633LH+Bj3|)lOGpf-tbSaodHFR8_lCosr{)Jorl$- z-{ZcZ7_l(6T)t z^wn`#P=n$j%d*C)Q*lP4p!WQ1+x|j$WWGOX<8c%%X_A;ljG1_=3DZ(#qDi^NFSv&o zOgq{fs5k(#3y@RkXp05444*-8mSpUf3a+?6Q8Q_y;ClI zT&v?gbuc-Zao>pgqJ% zKh$%J2ZH-XlUwZ2Zs5PlYQH)KcQ>n$D-p-&wJtA}K9FwL1pz8+f@14(V?~I)MX^ht1IzPNV3RDR1GJg z92pe)wJhO3`40h>_UT<+)pAo z_H175icG>^^1YS_3!Iyv%vsU~zKimY$@3?2L#wDDW;(a*BTY8m&+*+oN z7|x3C1-g>OH!?{Bn78oTqV-SB zTTu@dIS#5XzMEDFQ(<3HxXyj&J;Xb&vYj(&&|QDr4)Atp;Dh*;$*M`Ov(aDh=FxHU z<#8Suc4h9>)(SJ%EL~897JH)}8fQOihdR5ao`F zF*~rebteET_vbo`&|@~t2LWOB(ZJck0jINn29s<7ohE_h3^2nG4Sijqh9OgIl8B*m z0zB6#P;#Ch;}uH${83k;tMNKCR~5VvL%U;OD%eKi(t4CcokrXCKpelgCBIH!x^hSQ zSil$dyLReG%h0-z)Y1OCsz7m5yLIz-3+=#50?7ZyH$MIHsAvvSJozu%P8&enS-NbQ z*8Am1#V^J?yn5Ru(wRD^BvLa;q8ZpU&e~6-`MdoeKG1nxm`Azzv3>wa*1ls5__#un zttrfifC{7t6=JE+ZAdLE-Wy=G8g#rY;F*M#=^h~T2$)IuEDG8^nWJ6 z$QeJA?7i+C&ZIinw}jWk`ax?_yB)imqEPZJCp@sb`=2 zA>dA>g#aT7Ge=e-epI~LxJj$-cTzD@R%0|Au%;CvQ~}l>ILVa|9uo@WbHGtt|99P_ zh9?&Lrn2ks_*Tur-MKwI{r)vLk$ZlM@a1zcibe(!oU? zw&anJuPCC;xjg@Jdf}U3l#jD{7}BtETa;U!%?Gq0ST%}2s`Qfeu}n5FC{n>F-o*9y z(I<_i!5s)+i*!56xBJSb|Ft0!+{GMtF81VgJCzjsvnVPk=UbCe&hRy=Wzd~lv#XDk zxf0ivl^-QdNGq2moX8CPT;zl=Z0**^nq7i%D9m1+du9~kvLc#m=v z41Vwu$?5VEc`nWGzZeuHTY3GJmND|=Z>{TZa3vgE>c2Cbb6&T=*k$MA)tKtJJ#WqW z&US|wyhdy6JSte|@Hex<<0L7nkR4A=J)qoWz4k{5A((_EGj>BV*x zjCKLjOF&J8#QGiLZ*=Vdo`IsNLME)w^)#2;M{F^! zdRmOoauqaz-G=Zl!mD~Nv2_Bk^*2~{HJppMxJ1S`xqLT9M!`8~)2>3A_Y025_G!%Dyr9%fa*d~3 zj$4lioKH-rZkf9nquJkD0mEhod;hg&M8TPViBg<;O|0AU!Wsyjthw|{75E0Uep%># z)#_<4)hR*ZO0~Y03hvgc=(_3x6U?ngiHAzD*h!#l^a#FX%X5qL6ca;dx{KRK*T+~7 zWsNqQ*c8*3$e53>P*tXpM%QK5zwZe=AU9Fbi9CiL?AK`zY#B8( zO<=!trDxFJdA|4UpYbG#V9iy^2`rnO=ZZ;V&+<=1ro3mSq2knH$2+p{wfm8j24Gc1$HmGuHW;&N@UOl66Wo_!MXxQHN#ME(YM}AYkPq+?W*uQ9J=Crb6XllCVSO3CG?tba zPbSw+r0*%*2~XIzyVVmKv}w{zXwY#8edowhOa|T^M))v@D!oS?_r|G*{iRmgcCw{B zIxnOnPWuYaV?-Y!E)+bg2qkzz8~ELej0?`R?gsjtFN<1eo_Gmm>?&g9?upy0?@p8Z z)ga=83l%j(_;4B-xr;H?dgn7RkF1eIG%L`${o>4`I?OmfUgD!+>eZ0E8iu>R#AwwW zqNLAC0G5m)sMvqEArWb`S8ujoO3xjab1`bPN6Kz)CMi#ZZAqiGqIKX@`Ha(&`JvG2 z)2><0=r&k2iywXEGM%<$J}bNlVN#;>)2BVEYg3x!?_j4#HAs`%0RHopAihdP>fh$Z zBlsIL0Ac)wAUKQN8G2)Xs_`_8JPh3{BSL6F!jzg347ike$~PfCk%q|-x-VY~Y@$Wi znmWJmoGmfEd*eBITohsWsfIvU`BOH#S{>F*!yM2(PRYfqggj-SFbI|}O)gY*l42Qg zk+_mmB5GjHaaVoq`|hhi6a z^ZrX&qcBB6h7tZWc>mJ+wl7a4|LB@p84a^oF8ciby2?l6qQ{B(oA2(!?LP*yDoE#* z9FGy=q~wCyB%h*OWb6hKI;yJpoz1fvszcmD&ND=$J;cjQO2R zi}X}x#(52V3~&teE|=LmCq6cB{?8}UqgqW7pZCSFNpt~%ze+xAx|Fp{7CpGPBpY5O za?mmcgG|R}Bp0aG6$FKg!B)Pydm;>}0;JQPL!924 z(EsBuFp+qog1$c?e)8j;s4caCKhlT0;k3>$KIo9Dfu^1Sw+21{Xzvr4fsyfmz6OB| zBtaJf)A*Ax;GTo@@FJ*-M5F;U7GahGX(m_jMpI9mcog(vly_&e0Y|$0o!Ed{fweoPz7{3LTqz7K3qS6T-;)iJs18ia{fxH` zI0NqWwDynkH$2S@Jv8YFCBs$JmENEmxNgaKw%Csp) z9m09cXonx|&#OV~(Jh|~vgUT(>lA-#j}W9)6xxz*L3&Zcf^g+W=ma?C(b3*9{}--C z$d@dbt@)_Hj&c9Q;Vb@#GgqNYy@LmMbu%jA8S0lK0Pr2A(XWC2X~2!1zYU4HY~n;% zaAaqIcK@TEw*!>2eaAzsw=KT!Wb6A_$Qcb$fA4*;RndJ_)TcTZ$<2{$09~yUJ;Jf={*l4=H4COQZYxQ~O zCC<;`%QFj)kb!c8fZWq5@o78me>W!MR^jA5vnML_bD&p7RYEsi+@0vfQyzt(atFz( z?m}kzKR&OHP9>hs`1-t6O4ChyCb^uhh&ouJ2pLaNUOU~tA- z+?FDYJ_7bva2vO6K zBb^Z6xwLf`BTE`BDxSs=)_DBKB$A-6+&cp@u3u~NLcQ7==q{%hig0p3OjRz}TB04r zg!H@L9bodK=#qL1`C` zs?Ng{`|Q=)X$KVFkqNT2TFAt;w6 zfeRllz$Af(B1@8p?h=5|p9B`YR%s4xRjcgvHheNhC+lp|=Jl6Z3!d|RSIgs0$_H4f zkD1#K{8jg?m)H6XbCcQ4^P5}XW!Z58#j?fH7d(N#{|OmhJP*sZe~uJmKs@M{4Kqu3-EE@J}qQqRg;)k>?XP0x%ZW3N4I z=W?5i8V**s^Wod1E&69|=89FFDr{HPe<^HH2uFd`+wmeyxRn}!B?Y|?@3ufVc6T}(7J zKL)8zX7?IdQz4!9oiNDdlgCzJbK9>+7|vtZ@dr)DLsniorP1s9J`O0kQ+U~q@iaIH z^%u5>*mhsuUy2M5@$0RdKbFG3jxC;}>G6S)2PiizqR^DGb}kB#X|$i(l^aJhllt}y zqtL|~kB|MV;scQAlmW4kaMjVMbu@^rGgX$may4|^TuAGWXmSOKX7qIb;`U4hQts8W z@JxfRxRIK4CC)ltSh*2QdWm4d@XxSe`7WY@rd24k-f6zC2O2yVF%LYYN8WS^rF)_w zVteyRNvnW{o)m4XsAjF|3wqQ17gMW zdoh5bNvRa2r>Nd3y{5?B9{riToqy+S(fp;;FCqMWfEsX)WEfJ*l6zIzU$UCn-~T;x za~MNcb1bMZzx+xF$bWwcAM$=*GQ(X8h*$HqG7yHLylW&f+iF)2q`rdj~iqP1-+EB|x9{V>myRsOFRi)r@N6K=raW&n3BI96;%=MTT)6 zPhrWXUbWiG8Yv~0m5lHt0)bSj2>GHrOPQj4+5IA_xXy^cFatbv_W2AsBx zzpp^uE^=Be&0Tq9@KM%6`%NL)lk^9?Vg2dZzj=!Zwt$joNeWcXLq zD79C*Dg#PRcaxn6t^@yi7{%yiN=+c85?t@};$CHjElST^`8iyPiXeL$D(ja~%QJaQ z_zJnydz<$T5AKE8D!osNO01H30zNq>ChSNb^qC%6^>$?7YojoFLGR3>!ITYfeu5XU z3;~bpwT?jVB@ye4Vz|CsbR#j1rZ~e>y^Z9pvaG=RX_fPaPjILLAYY`h!gG|S!yO?0 z5CL4)s$DsHpbRMc-E><9XI{I+@+PV$HKuPXzxSk#ycKJ_g4|L7<*$5h70=!2jAosX zRn_5$VB3jXw|A^ew9dcG@>Q=GU{((9UooUM;IGA3MiWQ9|AO=iO+9;%~{D5C5Y#6Ml6Kbsml&D1Wo1NyG~ zou@UaNlxLOUXx5Qo)h;z6dD@cA(FNBU8w7P*+uz)uoL?;2CMUD3)WM5>3Fj9eIcLc zW0g$5{Yv8Ii!*jCDR zXJD|w5fHBCuyf`%H(WE?avu0>jscVm(|?_!`VU#YuNs}-Vd$?DSaw8&5np}Q6S&i( zVBS-UK54IB0-#?*G7+}gS8Qiu#GhvJZehg~w8V7vMy*CIPgtJbB@6Ilui0bf@605Z zMa0|~w9*-FG_>FNiqVEl>JimxF=VU#w!)OSnq2gRSM!)UFc7|89!pvR&-}fRL3TyB zShRxnXsX;of(vrCS_O+K z+&4K)H5a%^@4t7X-l`yARZ3%k1O3;4ed2Xd1|l9o>Gg$w6^@qNaw6M$GZ0?h%@xgp zyt^W4+hvt^I+__?aTPieg1t1^$Y>BX533{y&9am#L))X8`C3lSwLfmlCH6XBl#v{F zTj0~3A;^1|$b368ikX7IVu1;?c;@DkxLf(~V^;Y5?EfrH9lK9{?l%!#h4+rpzgd+3 zxdR9NES`9GdyqUJ=nL-jYtIQR>_}+@0~x6Qcq91S8(k);2zwF3h?{Buq!)3)X@RKE z%;m$0NuOmA1P|OfacXi-%(5LH8<_ExlV;o0WTIA&&%$i8%@*qo=qu~>FqXaMwNh_7 z<*rU?{;T0DRS-K?9H|X>tsFS*6ewsQy^t>5`MyvEc&r8Xt$O1gZTv-@ewiC}q_Y@U zLoM9`X1e@E+ULZV|C@W_0^0sgu6sxvOJGp;%Zm?*4(b&V5-@-Oa!1q5`q&^@UC~OY zCvcYggGKDCm&Nk+<3Uy&W4)Bmh3&eOxE88d2+d7}X6KSuHsqmRm^wquFjhEtSn=Gx`MZ(_CLrrboHmvJWDr)w2u&5_UKCbotujG?h1QMfJaX$ z`0e0s1Iyrd~Uh-Yw2%(OdYP`st32&8Fu0`%U z47M&i1hFLvY5=F%w8aUkAtsnbaaJ#*_Fs)OUc4`Z%i>RhQs_nQ4vkUxUqZ@H;}KNU zm&#j%CLkK2f zPrl!3fA

A+dYmytR2KFi{yAk)ABEK*PF!jHTHd!psvqhXrgei+bLTg^1-#n>7yl zZ9)gOenbOt{pzrXdEp!6%Al+Fv%0S7$tbHj{V=S7zZaUOWpI6BU$nvU(SJ}7@C_Z9 z>m$FRC%1~G#5H?0mjW!U@*$NTi)}8ex2*aw_%H*-^iECa*to{IqgcACu>Ud3udbfl zt{(R>4No_5W!gx^yN^FUELBM;`*2-8#pYq6n{nB#REhf(KOV}**u*%cNDR_OKi&k7 z{b^#J|MepY_y@7im4$=m|`B8M&$qxfs)~*)eVWl0nRfy?IiBHJnK=JQmkP zCp#WevytJysQ``34P`I;s*~V7=BIG&GQ0JNrrWJpjYqCPhwlkAW}8!0uu!J>r9|B% zn@W*~xyzj_cW6=ffCWzx-*wmzMi3vo-6R=6l z1Z;w8er5ceIxN}EQ+11E(QvUoy!*RlRyfX!bmifH zfeLi-#~e*}O?bpwE#<27z4+^4K)8r1_9gP#t^cdQAf`?3rg$+T{r=QA#pQle)XRF= zZ&uIi58dzFt9aQY`;_!{MQU-kNO=PB?C%sqn(2J`N+$I@ml36z46y6odbN2%7x+<$ zs|^J~hu6=3jbmDV9C-x$9<@~yPC7m+etY}2G2yl}!>^auZm_AaM&96YE{?p(InVC+ z7%ncni{#f(f3%c8z%=?BLNE+f%=*F=f8QxqGrwZr#0T|=1E4P3s>J)Nblx%dCo*9- zB@>~!S10oS?wsoEoISS-L^kv z^c>xne6Bz|iHN0pYG8jSRJ-Pnl5qFJyM(tQkA{Z=N+|=S6(s4%6Xb3#q^dmq9e?Sm z1w_P)s^^W15P`J%=d+WUwc7lcO%*I88>Xn?^fM>>)CcEz`t|$NSpv@Oi{}|?=O0~b zCp+9kcV#sC59?|x7Abk*>pgCnhui6Mahys^XkMjb+89t>)VA5%|Iz3$yIT`qzw>{u zJdoxTs6HS6>B{Oi!&Av0%m3IMu)dVg-1;g;M_C2O=+-#gQQx#2qw>7 z2E}%};+_+izlCOzD5J5UMkOuaP-sI6@1r{*O$yhYp6?-PxrWdM->tRUL=OCc7DCAo zmOropWTotmv7AZxVdf!1#A^x1Kj(8eWT6i~zVaKUW|@&pg9e=6>Zqq+fOTXy!PyXTu%Xna zVg5Xy7ka32*X*Br8+k7LRzihH2P;fLe^k*yf+;umGK#2Lm8Nd%o+7Dc zGQ@L!BJs|7NK7YDy#VCt@dla`3d&1D^ZWK=qqiWZ6F5G&6%m^IR#vM4cC8MZnff(*!%MqA1BmrdmuhVX_~r2jVI2ve9Xt0#fy!SD zrnDuSpXF;_DK!GO>0SQxu%m5l@^k^5weSeb+RUD2!`?RWa2>WHuj}`mt=|%n27P(Q zbc>bs{Vn0A#qV!RNmwJDo$SJZV8vrC#{jNoV#TAJ{%l%e4om8&>#5J#<2Oq6Q~6La zBjJ9phfVdGRPph=hz@{kIt`5sdsKg<^!H8p1Ob`a{Oi{p9($%IQF;P!&zD-^a4` z6p_sbmwiHig#ch6Pa}a!xB?+~E&>LtDB>68v!>Et?|n-ro2mi_rAVF^q%%pK2nNDa zps3Ikdxi_v8A}-dovLwcN&?R9#b=2YP1mLUwD{WI+Lqp{u-3~TJ*HFZA!nswD2)dI zL@0^Qi<>w32sSwn4p^^UCudK2d7b+C0`se6 z7*D$tz|KRvC_2imL2{q<1D#)V39lwQJC;+1w_zoaa>@VOe{2Eqyxpzix!%K$3*`=io@Np<`WUF9C8we z=t9CdA!8fn7o)!uFAH#QXe0QfrMU!^g#hinj;ixt>gvLruwroxm}&m7OjxPutM*BB zr&-Q2it79ayl(l$k{WK41!%S&zkf)S*{u^gq!8DuLCHYUqdPUw>a$Dy7;dkewK%^$ zqn31e`ZI7DvGnw#$G-}2g7OwayZCWuq-SR&otjg~OefY4%+4ea?-G4HeSn;Xo%Ft2%MB-TXq8s5G!enMrcK$T3=H$D5e4E@!sU-`QrSOKgg)&+WD^PjSq4H6??IXhpDs&k zc|CuNsN%{yOdz_NQVE!)j)Qu?2QtN>s{?bgD6RVAf#_6wb;~Pyo(7Au5Hc5y@2S-8 zUKTuopYz~m6Q&-89BDmO<2(tE>NvwfVuP-ruH2|iHT1V#`-=4y`?E_`C5CE)(~qc) z0mg~;YyWd&N#@r#(i;ySzPv$4rStM86Qi|^&$p*rOdp@4`p7-iB+wT;>e-ypDkc(@~mQeS7pxoNdUsVe; z8Q17`bBKmy`Bo@+?xoYS<({hpCJZQu#gRl8H&X!K}OLCdO5j=*?~ZAugV;67oK-%n;0=LMN8EZY^S1x@-mDE_FFQ%G9k_mRR*%2a{;Dj=7Xo7 z;}&-_s>V~1<}_cLNt9W5##ZC)W}h|2WC2R=Permd?fn%XBF)ccQ~gmH?V+pA@I z%&>B7W$|;Pm%)hl%D3F&PUepr90IHvNiV#; zGQS0K(kgRs-m$p5Y5v|A#Cpa#WN=qa>3uNw%UZQbUP=u+T>o?s6qNg9E5gYX8qqcx z86Cc{mhU(iy1=+GvACO)JVt)vm3PipHzUoB^D&K@owbEFg|g|^f-q!yylpk&y!sRt zb-L$Xz{zI)6~XgxLnxuSFLKCrGBVc+7&;z_ag-=ajhWaTi}_eUaIf3Bzqz^Du@c@Z zP1OO+a=_#AI3laiam~aVR_IZ{9RFM&tejp=!B5*DW%p9emKe13O%uB?*q->NZQl## zDG-rPc{{#vbHpuA;!m}nD_S9LdG7di8-Q9TUjSCE*|J(~!I&R5y*&QYHQ4%x0v^2uHNk(WKP$sb{nc)JwE( z>u%*7cDasZRrULXxWkd)iooeaBM?5J)#9e1z4NE{l@qFsUAE4(97xjVWdYrm*0h-d8HOVfOAb@P86Nj929Eku&i3?=^hF7$;yqe3ja1J|jOu z&GxJ&JwHpGJup-~2;0zS8yIW7Y~(^Q=SP~)8)u?no79m)PG8W)Hv&FaPU|3#5{KyoKZ158Q5@;1E;*CDL4 z`y`Np)MA0P`{=2>ZABS!?SrihE4~JkX`G?1Tc0wbkV>f`n14P%PO+70MSDIwJe&K- z=ZjGn?ju9qL$nMU#@^&uiP3YZ_RG*Fa$UET@H^Hp8 z8&<9PLXu_*Lr_2YhC|doFCuJ0xkryiU#g=7p6KsF$fh%ouikP4-I&Y}dkV`tL#}cS zLc@k3oHMJ^>~|s`kfq@Om;KFSo1GEq*Fm{v_%(Z6=;q85+6!p8w=Zp1PecXfTxB>3h{s9d5Box>snw zYqDl$(1m&wMpeCe1kOJLS;c$!~59d^BHe^_AxP{A^|v zptw=3wj(r)_ZZ8S0}H-lukO5NJN?5~GG?|a(pv*1Whm9Mh=Ab-kYgkIS9n9Fy`@Tj zL-(1$_I?!NKk`U2v+y~B1zH*{CUVo{V8^juC?1gNz$lx+VcqZlOq~y!HYhf#V4`N3 z70jp30G-x$7t)k;xIEJn)a(p=X9Wch0+AfSn)N&iQg})Fu5Aa zI`P(f|Js+c;>_sI*1FuEkJCKcFURpVnIq!NI{o^q&i(7>JsB+_<(wqYfOVL_!?-wvpi?uqVlu0pz6P3E)1>;am{L9Ryo(|^L&;i2f zq;aypC8oh1ksooD@KHgC%#_mU>4C@*Hi5qDw~zd|GwaU15ZNNRsei9E^>Jq z6}e#Jw_(|`xoX|NpRoA>JO1MN6N)=Z?;khdxAZp88BpRV!JFE!n1=bUvjy%h9_9*# z2a`LMriq6{DEYf~TE|hpeVXX_hYG+f}#+%~MtAntuKtKXjS>|G)|b;P|=IpP3$& z-1vphx%~!4xO{KqLsuZO@}j<@i=UtXSJNdFgt%AyfEMa7^v}N!!UnRDgRRKZ)!zp} zXaYVqThM7WOfdL=EVJDB)?dJ=&Au4`k@sEJwKQyIM?LiYJ+mG(g6UEmjoPNFSu=9m zMK~!i-GInpG4+Lve3QYQk8um2xelUAAXlOvOT3c6} z(DNEG{bwhMav+hF_0SG7>@MkfWI&pmA|(RnXQkMHxud4)FmU6sYKwQ&qyymzqk?YD9KCgI0NxhHI6@^E-E ziZ5R1R@6f~PLtavchZR7K&$QFKHh0KFr;bISe@ET4+%b$y@DghG93h-7yc3#d(&N2 zZ5ubH{Bw48lVsR@rOg~{J?GF?ZW$tyyHwCVJuKhksK=w1J>26;-BvjbGe^Rxr^AXssm@f%@4mt1BTiZ$fZpg z?k%O}Sdd^A0z97KgRxA!%&|&qb#@NT3pv!9<+=pugQ>lAU6D3vciPCP6QEUO)xI8; zIMLmI9^n(i#AA3K=GRpU28Z)A*hk}#K4DjE^xg1{=&G=yq*3Nj>@^PAkkp_p3S~3W z;H&Lu$?8MCR224o=F#0!((sQrm5=W%Zfcbt?@}jJms%&0rdTTz=`A)I4`06(w052L zA?uBM*cUg1b>7~)@#3bG;M=?Ji(4F;zX^R(TC4i9{pqcMtq*y8m5>9L(l~F>r=eKG z_Kbq``5n`SVj3d5TlwwYDK@{R=YF5pL04$F4556q?DR)zH1nO?GH~t2U;EEg(FMkq zuc!8(U4^Y` zJD18UlF$7ElH%(cI(*H;odc5m;gY2MX4;NhD%o2vM%%nn$wy@>S%G6AJg5%Z7s=fr z`U>HW6-nJzFu|0-%beIR*5h;sN1DNJGMCOvGnEV8NH8Q1@-J1Zn@wdxZlJF7=#79+hOI@G(1?TX!@HEBtt zuOm*{GpA83n9XA?a`=G_D0ox+@gy?yQ!80q@KS5V;OT{KGdgR>kQC#4?K*SNwcEF1 z-rfMDyuD9V{QlbHThja1?^&OEESS`Unyx9|n&7xjMp7OXQ+JDpg`)iB%Oa|b+of&k zFK$l7y_b7T8~6oiY+UTqW{!^Yfjhq&A$w+uPRv9ZNJ37D2Wm;2m zG`XrttTU~!c7IwHrCJo9bgssSC)GQ9`h&UIKr_tafbLY5e5{k$$#4%vAo9v}cBZ$W zmoQ;Z?8*r+!%EU>t>pL~NoP;?b+JeIUQtw^IA+QaY*KDwIQ&12(a-{p=jD2~=Bwyy z66;kLupu2IV~pq9*KWxCxpx1~!|O!htk>zDz970@e4UB*+pU*6i%8{rxmLw8*aa%? zqsq-Y)V(Ds=x`oQ9bf^^cJ-p>O~C6zo;my}9nJ6vj?l5lskn{1X~QkrT)i;bo}#1OW#_s(Xoy?bY! zJ9oF6vM7oVd9V~IwBmzb0Z|bVB@|lvph)Rs(ISed_#%o3DhTR}GxuX=?ta9(dE1Yf z^E+qe{Lg>Rl%CoBz}6=5Kk63ba*-%V_=IH$1zsf8Wz)RPOKVbKbBbP4!$5p~ou zwL(tUTO*yZZRD(}(QT~6K8q=|0Z$OP);a=~14>Pb+$h~Ja9RmmgM?Z$9V&o`rlg{FzjqU*q#TftNfyP`RsZL`3B=Bv@MEQL!s zgjOI0egyUrf#~FUGN=qan`#9fBfNvKsPPWE2`Th%%Pkt%^NR_tM5Mqg6d4@nDFftC z(ge}^ZFpIQhb~wyWtUv+6L~V+mzDNmhfk=B#rBAF+p zW!d!%`c`OQ6B!S(7P28l`=c4oZtPe#ua9Rk>8aefK9S33@{<|;5WBA`q@#62T;?JJ z=|h%*N{Fn5j{3=v&_6Rm&)}94c&*<8dQAVJ?{#`^=%@re>sbUbcVwYZG=Q`|9F{jr zKuLE}gtzwKB=cH{$OwcOM~ch#fc(GfO2D`P1f3tZEk1JM265o};Y#XqP z_4J~ub#+>;MC1fQXl#cRAvo33C1^r`B601I6Dh4bW>R286YvS$XlZi6+e^}8LW0qScEi2Je@rCtRSA>BBW|I6!+LvZ%q?YHeqYQ+gQU0dOqr{kLS5`p2994zY({k0 zqzo=6kWg#Pq7uayLRGvOOs7bbaf{kvC^A)UWM2Iy5ww zYL#cdJU#pJftU9Dw)yXTeeb=W_rCA*@bJf(v)A71w^#hu+WV-P*@pV^W(~EoK!U~eJ;BFM__)AuQ~Q(mk;X^|I}2lHYkR0M z(iRx#1a)+=c7g(L!A$LtP_92D+d053!QNb71weijVC@XG{}*HVe;jM>f^>GUhdTX> z@d7`Mw}9F^{ENYYKMuC~mxG0V9BgV~@o!cw{Nq?aXQzJyx5$r!5l#;ON@dX>2RmDv z+d`d<;V$-OP^VwrZ9c$)zT1nViz(dM+S&adMq9(3znKc(PsWC_ah5PsBMnz^wnhWkG&6`QxZ> zVe0%#VaU*%+SJv_f#+*dDdzo_Zk(Xz4o=4q{T?3dP2tusDBzV)xUt%KWXJ`!fWk~& z?3}@3$IpM1#P@?FKvPp=n6=$;r1&c5Z*_2j{IQ;|3G?^091jKz!rAJZgyUpu4J1Wi z2@d8!?p1LxH+8mlfHP^{zQ%;IcD7#m zjsGZF0hpWV>pM)x%MoKb-VQD%2PgC6e@qBdCr20P&p7g3{!xG64IpL%i~Bam-|NqJ z`L7W`{>~M?Tg|`Tk;{PUhTcDe0r>%pul5Aw1|WQXTj|%G{sU#zlhnoJ0Au?4GLp#} z&g5(b;PZ8Nf5e&p2hk@uBdtB4fF~N8xjRFDu)z~k`eOnO2WL~e6KPDxuOEy5M0p-aVP|CmXhf9?SUM#{TShz{nq#8o-4 z-#{e_Kn1z{U!W5G^Rm9<`nxgx02Sn`(f>E7PJDq2utIY?7YiULFdc_ECR4Ztld7re zKV$dfB9BKIpKR*?A%GCRV*vj;TKu!EL4FKQ|6f~!K>m}hogj509I(N!5#{eKjqk^x z3^#@UpM%9O0)WLY`X8)~|9?2aKUo{U=zj$3xFk6CSHP~APEh+6TEBYmKN=hVkHN#v z0R@zFW&q|u(fLQf`Cr5|h|sZ}3I7M^AVPodsDBaFA;SL&yAyqxPW1gpkRU$>eisDd z{}!a+MV+R-v33dK;>-{4`LhYdT0Qvn&^!WL| zCqwmPvCgJu0F(PJ`|raM|Bti?>EHyc$jJh*m%n1d$C>;a2M$}Kug>RJy@~2Ya`_Nx%V*OYhd>{Jbtp8J+ zAK2ANOYQP^4Nj=$@1??Di~BdF!*KxnI>YzE@??f@MZ>Snq40}E#P|8WSDxQC95ATA zp6yov`i{{5EG526{i_`_^!~o8?`7zfWh}qLO$R{G-{^4M-ub`L z;h!n_H>G}PoBkF1{+F5^x0U`*Gr&>*x0?MAuKZ06{_icnzo5ncQVV{-kp4yseqihV z_gegaN$fYhAm6?3Pki-X>I4Crtbd~u1i&AzUQz% z^SZy8=Ua3{$A<-@y2(7KkevW5WjEB6Zjz|{)yv#m-f>x{vMmZ zV(>H91K1nzNB=EXpOCXuY~alAk9O++Y~VSW!jZ>3;yCDA0KE0-lt*w@Z<_J_nK2}pR zg=J+RCCWmECKY}0#!?4HblsL;GRC#TAzl?kc&)qqQOk>N)r{`nuF_BZb`^A${wiof z2@w##6RK0Iv z9NjkbG(0CR;^|ZRF-NOWPXElAvTfrl3lHx!lvXBVuG# zre~qEt;0p}nQ8LSqb0Jkz`C2c;tYhl6Ne@e!sDCnQXV;nw#K`AM+adwyJDO_@}3;vDJ$MLk)FX_j}KS zA|&!4iykS-=k;fm@zp!u+{a%}TfbI!+ilky#w~HH$v(J?V%TV_erUMsaQq?Dt@{#u z2NI~Q_L_P_@v+icNx=yhH!z9JjdbQo3q1%3outD0mWe;|e2a<&3_^^KZBu};AtSF@ zK+rL5@-xgS8F7iz^9uqjxER!y6L%_|$eT@;1A`o?7XDm*JMs1~aaAYvl}m62$p&uu zs$nrN%#h7qS=Gz?;vNx5Li@D_>}3g&TEi;uVdRKQC?ogGh~vn_QO~paHx9YoLB$c( zli;II;`UkEY_w9ls>wDK>SNHL=-oFfXE1{gxNOXB@mF-b&Mhohu;a3^+!p!Za<)Ph z)-O8X!qcvi-N8cb<@@HoZ`i#JlyBCql~PJXrZnw)hqgQ)fz-~Uky$C)==K7ER0m0? zAw^kQ;}U^XQ&VfDBdc=xqmPl#1XwqdLoRM|3l;9hoe6E+FQm(f@9d=<6i6O)E;KYh z;6Pb@Kr#f2SdnYuVga;PMm-&G4cr!QE&O{aR6udb)M@Wy4ehzodc{gZTI+*WQ;LmWdDW!`JA z^GZ_=KUD`@=PO zwR5!oS|?&5oMO0mTckuA^D#Vx!r*FxMDN?gyH&%QD82JJYN@rYI`}zs59@YQoK3t} zIP$|CBfbPWn=IY%cbSfhHSxYeo|EkB>XKhA2JOl@dP{(XS*$LLN^@GJnu8xa4V3$TOvf6ka z(e?)<+6*)*G(Mq0ny$~}^EGOMgESqzq)RUN4!vEfa-kS@Omo+Zd@jr2y@!7Hzfwg3~MG_G?T>O*p6_RTnjPjed%n{kc*SO{Y-HfO`0RDZepz+tRt<3w@}?_ zfSbeMH|?EIu`0-XwBF1qPK|JcmaTI`gco2trmmxmFo zO-sb}$YFA0*EZ0DQ*Q=VZ!)nxjw!;)q4n$C-e=Xh27S5+rgPP93%qy7yC zvJ&CS18P)Tu@N=ve57SYb}4d2g&P&NRs9l=`ribg4i^fYlqOL|kzkF_r~BJMA9w3i zc~qUrbv)G&=I!Kg8_C9YsA5@KMS?!3SgHLsIebOD5fnB8;At?1pRbDOALfJR3tYVC9(SA&yd z<|jBR+5(HQd{j#d_`0SmPEL6Vw$>aH%XU_Md;VhbmHJlCPp&T~F8FC4K)_mW`1}N2 z7)(goaB|REJv!5Sm&{_HG-mByu*pfPc3fsRkl|1Qt@U8{=nxoR4ePyy&U$HuWdk8O z&(kB{SSF~LC4A*AW~xOmMw89T5Di2*Nv6rc>!D#jcj?<#<|Aztv&;B*;2;!e z@mgAd0G1&7-h&wH7H10&NEBCKn+%MVKMLK#WuXP3LnpRfxfs=Ap$efv-+z!a=wtS> zaelfI%LF7BCfL`$87pak5mhi*l4uMYx@kVmI6ITc1Giq8?K_+t)^ds-x}`1Zy6Hq{ z4>?@SS)9tvQg1cMR5$T9qsl*&BhT?ID~cO9QhxrbSuptiLN-())UtwI^h1WG_v*BU%)F?$DD5Qz`A z+cCSv7;p}H&!la(J$UNWK^I_jxH-|1D+MjlV_g1%J-GQcs9{tUY>gQ(uUU2~uPGx# zQ!;ShhtQHx!#u1U;t{OuiZgLt<%Ybpt%;gLw~cutpn`~2Das(j`P??~$e9$Qhh-M>x| zF#Wz^?D9fK94&l7%IColS(R^;xLyehRVrzj_q^^hrm`TS;v!iDm%lvj%^2%u5p@!P6SM)uCKo=2{%HKrL2Q zy?u^mPwdg{It8JWQXw%x+z=%zN8(`MF})T_MrDKygn}WdA5jtBJQB zA5{-Wz-EIBV@cz6gS8;C4BUklN}WE<+5+}~%@U%*fSDN=tZe=-1cd?e(s9QzF{5$j zp>=tc?Erf}eC>4x{Y$H>lH&Y}qz$-6e$yo7Z@QlMgSIW@`B}QGilr@~93QaqNhP;= z7JQv&QBpYBi!b*y=bzyQ=}@dNnUVA|+_K(YY*nA*Sdq?puWejyXqOf#xLT{sA=`zd zEJ%`R=E#+4zHX$@JpZ`HH?iom>75*ghv2PX<FItd7f>kaeqsy8lEH--fnZeoEWhP_S za+esU%HMYzNQ>1r7-j_J1Y^Uw=%>;{ElbOmSxe5OG-rvn+fyA3OZfzdZ;=A7?c~Xe zT2_b>lz5^NCn+E2NL7%l&APz0AO(-=HC`rv+Z`qN>@l&AVHDTfBB#2Qz#Pvt8Lg`Z zU{2LSnjvp|$G@nx>X0ka&>m%30Jx9K~r5^JzWtq^_BjG~WKr(m{jm+BT% z%mbOdm~RxL{h)M;-Pd0!h~yaM;yI|h*idR{BTjaVIWbd!bG(yKnac9!Q>NwzXIyF!dBf`1LxW(!Ln z;cmkuTf{|C!(P~!7B6wj6vm3)i%|h6S?&$j#0o9{<4EEM}5=X71F$jmXnbnN!WD(Mqs9uJvu}IM`J0pT4nBm0v3e zVquS--$p%uCI4c3o@WY`wXvi&%F{H}d1N5y_!%4QPWPQ~F zJTAow!DPKWXVJQ8L8j&5}wWtI_y3a0g#p2!MBUoLZ4&WWY`~4td+khdumt0 zi$zTE+Ob5KdSe%tJsE-wXuy}X>B3`fZebTCUhWfW}!+R>Af zpNb_m!QM2kKnN!3r;#h%ve?_+#@F2nAb5AFO?0r;pyGY1Q}l$_7kiEi2!`%7+}R9m z|3DmdiFlsa(6m~2`&dCm>6>`7sCipI^$6M^K`cyid|Yi=yBv9@+DQRPyU-wwF9KaU zW=G?xx)%~CLSeIw2d1dDgu6kM9l@o6QNwnqHkvbTiZN1Cp6{ku%9)gT; zET4 zEdGI%9Xj`EPrsO{QkoWvZi}~4;JDvKhD+}i`|ctx*Q^~svR#lfaDDp{!a3V--52m~ z&B2}Jid>x8s$`G)#+9^S5osaLG^bX+4kt3DQtNRPO=;b;(~=4 z_3~S{nFDA#mB?tM6UoD+u%?4mtJ*L#w(h0%`%WWuh&aY5_L&Iiy~)wxFRd+sQhvyn zum$I#x(kSza-l5VKIRE}H(!dez1oZRAHzw?l|6+Q=*0U&(<-n#njLCPo_m{yzT6=6 z>t3e?tCGoM4hdFXg&QfKn0EON9KFHmX}JfR3$3y9@CABuKN_tf}g{sb1LdK^q&f8-tZG0jWg~Av0V&sK+U5&LFbbuvMKXkFCxc zUt|ZbL&cqlUP7D1nu*k!8I*cojvW^wqtJ|pW+t|{YZnP}k~zyNtJ;k3&b0gGb%Vx> zjKtO3sWv1^y<1>SIx9=FxrwPd39zaI9Y0ACAMyqrQ6@EP$sStFsnp9;bXn2;te^&Q z3;`LxCAiLD`bF!wmo$F32Ni-r18=sTBxJmPaj`<%pWomkxn;M1;RSrvJ&w%5GVle2 z)X|(~?Mo(2x)Lju5(iw7;;st_>PI}oo3D=Tk7;86*_rYEjL15Y>|26OTh>od>eh-mw`r#wBJqBdU=Nwbkx>P^3-xnNb%EyfqF!Tpl4MRh^q zc2P?ubL<^g_v-VRdLuDPy`)rWw*`L%#3v0c{-ih=)pu9tSi5ns4K+Etx7pAlJ*wkR zno8F%^OG=X4xugLcC=vla>WxuFh(^UY(j-Wq2KGxwsgzj<5_pKDO7~+r?j+-GeHAY7aH+jf(Qmg zF?_+V&3v93grp~*o!CR^lqAd4qrEA~XRix`Rkd-!q=VBPl9Z0Wg&Xis+AEKbm)R)c zIw}#*F-fvsJs*h;xq7krj4l(WMc`s2ZZy+*r>i{8xZFG(4q!wh-FaRIGrfmf<&AjV zv%-Ajy(G1nwq}jt3NQ}1*$lB#FD(AC3ylp%@|?F(fap9O`i_wMuHek#)hw;joW?Q- zDnxZWj-n1eeJog{(|147=YV0=t-Qs*fKnvc2p{8n6ECCQjRZmZq_J$;BfIha#z7kG zY|<@1^{v^Hei;JpjWe#)dz?N5!`0kh8nDJK5aSmdEVEb9Z9?xsMxusu&Ou6)+GsWi zyt|rPNv*PW?d(}# zqXK~FM|^Yg40uALh&^y!w;|E1HFFRdPc>eD^c%*vj0i*iNO50R3^97mfBldnQ%{B* z!|_J2wu@PFT^I+mMeBz|f@;emM%VW@zB;ewUIMFscNq`>PpQT^WyPUEouv0!98vYj zbWdBb^TaXdL&~bU-0Eq{K!P21_+Zuki}nl6 zr$&-8+jn2CFEk9&V5yGO(UF*M>Oasn!WTg%Vr6Xh3y9uy%qS9M%p4Rrj2g^fyqYDX zDIbGNg=+Qmz*bcgw81R#rrG>z3=Cd9fGUP8b-1Q4$Ycriysn=z%jz2o*B}w> zxS*%|u9fSBQm<57f%d1C3TpzH$^Oxe0Sz%>^`VZBM0B0>j0T%dTKcHvB(H5g_ z{5k08F)8LK3diz*gWNL1EQ|$eU@jJqi+OX_2IJrpcv}#+cHnB};8xz(*3QnsBW%^E zWB=vRR4y#2WqbfrC~z9l)vXh;!C19{2>rl)|RfgCazs`rN6saR~lte^}*;AqV+ml;DLq%-^FZf)#>bqSzS4L zv)w2?lp3)ARZ1(4Jj@d+B$!)3VX{Yom5~@XwdfAPCr!$}C-B?-SCOReWx)@z&oi-D z1hhrs7hDx;CKY8OjAmw=3Xpg=@%k>SKmR>i0%*1CX>x+J8V02x+0YnV+#nJ8Yp_8= z#4bhK`_LfdY4X{d(E|kJj^zo*(A^Z}1y2YFT?(AMjR~&?>ap(D6~_tAN5;lqQWYhl zN$KNoX75E*?dw(cade&Xi@E06WR8n98;Xx*Ns(qFy%T(rF*I${b&GaUamS2VwcMM8 z&zOn&ZCC|vwE=JfmjJ>M!LCp86HcPqQm8r{j$Lu|Aa|U1br>1NWH~A{3T4@j%4P}O zEmwO1&EAIVfXeei_cZ8W2ug=W6i!M^uXSo0rdqMVr8sf z@l$ucbGwM_l;+vNY5$Q8oDJ_(o3J;T8YI6)^Qx<3YcI*wi@tmP>PwdRV~br`!iz?F zIgZ!$(Jxc)q8lmN4DJPW`vvjGS_q9po7BXzut^6kdZat%SdlKTE@jvp0r1oT6|fFD zMh((%LG&fneV)pUohZe9RUqPZQHsQ5r`ixuv2?t03)3w++ascT{uS%2zH$ui(U=-m z2dd`MEduVA9cpmLqeGnWp50>vRryyr^5{PUZhwdnQ5E;wha0of3972H&WAU=@z0TI zONgN|5Cuu<5+Jrzvw5U;#*Mk73Ri?&6lVzn(w~~&4`H=xfs0U0qG6 zpHmLxO8Hc`r+IH+orS|(SN)Axw?X%u%gnx_YHp!`Ku31Y3(G+~{q1*8Or$k7i^o54 zRadG$Bi$KKA*>s4&$@vSlvwAiLtd*m{aaGruQwUVFcek<3+Mf3$%qCgn6IE#A3Sjo zxe$bJiI316<~^6uaXNjqFre({fysF>ZXL2D3cIv}0B^IKh=(MEK*=0vi8ni17?8dA z>dCpt;!8h9AB; ztz`9RJ8s#sV;^M%%QeinZ7a8GAeibel6u!UlHu&rGtF4%nJFypaYa%UTybs2z7}~# zl$mDApPJ^K;B6VP56y04wkqPI&CWyd=)+oD6^w5f&TX}T^R~w3=|GNYRMTlCw8)0bm#+}7LmQq>CCUbzA7$cV&)L2z-F?S@3?%j?eRp|P_H&z! z2f)z_+kD!!5}9!EsZGUUBi|2?COg9#ULVF16Yr8orZb|o!h}XkC?n_N;p&|d!G6xG zAiUJTgPNPx1f!*z+MB0v-dl9hG}O(KM|v13%II0i%ILYB!M5&~J*}i!Rkgsia}?~S z@i4+q!)ND#$rzsqkH|e^IhkjqWna=R)(w0f(yA+ZhjP5c)V1FG>qmrj*hEUuS-epn zy>~_uoS$H-#Jgzz01+`cZ0o)Ab*0`nFW(>~oQYMSTK!*#U{?LAF55z$k+B`coO98{Btp(li7t z=Z_^%hbw|#DhaUt)=t!u^@NwIlyQE(74iK1NV_@mn<=8qQyO#E`28Y<;@v%LLl|2iNNQP#dpI z#&+r-kAHHXdf@_)v7LrI!j8H0)bT8zPp{8vZ+QLcIgQ=ww7s;WrU?7z(D5uBgC$zZ z4RfW9HG-gQK2{HJ?*MC4nad91+&Y75gne1+0WYv?xQTZ0o6`6L05bVZ4H2G1Zh)Wh zu*^fHsJ?esv($;xX|q=?HB?)>(2rwO{%YvUhoCbzcuLY!0_9*xv;mtUr12LZYd)YNdS5gui4&Lv6g$#j0|nN z)sLkqZ}`Lyp0NNpd5tEXBE)d=!YxUh_o{Rptn2N{<9MGgbQy-7b$N1i7w^UZ)dAMH zigfjHT^)(jiX2(JDZqBFnW*$2NCs{k$NLr({Kac|xJ{`(8AJwEz~S8H(qLvXEZ<1V z`m|kRM5GipU6NVB_?Si@n1(#ESd)dRccn&Cdq)E2_6VJDFqQUik*&*}>gP{wF?Dg4 zZLEo@yVxFyA-83fHfR~;Wd^G&H1k36=YLx)dNg*H9v^=t7dQ`ws|gTwj1okC0xh~D z|E1G^o@jgX`I7%5($j+@vv?z~FI}nG#xR9r(*2yrQ{)^Lb4*mv5K-?}x(q!r$v#KE zNQjr(g?$A;)=>cg&M7PJyhM;%qw1KZdpG+DSbO=ErKLA2*3a3t+|OC=rQdE6SevR} z?BOyK(ewDF@%h~VX^ea6trMG!B)Vr1ua>EQ++M`Ft>iw#%n-2~;4|1hejyj<6-4~E zw=R}5r>=e+_Tv@KZ^h}pPI;52Q!V?HezH>gCs4%=$>DwREBv2@O5@A1a-(M*dSg`s zveyE5Lt=Q7<)3YfvwDc5s&gT6E56V~Yeavo7;{S&Pb%|`FI zO_LcmKI{QVHh>U6dIyK-dH9t&qJeu+pGcng4GM87eJQM5$Beg$vz6ZKMoepA9)Gz5 z*6z<3F-Og68eCA6eD*jz8wpEuOKXeW)SLCu=cau-8K|^jv}8Cje?NA7zAjJc`0#K- zbGziXJRgsBpiib-dA$%J{urG=x4wtF?4}BPxAgXbTh~-X8Sa4SVb1eMK2FmQjBqD3 zz1v^e%dpIk>_LO$uBZsqWK7!4V+)DSnr)!3H zvi;Xd7F4v)X7r=)m*8gOIxrb*MwAd1zf1`X@SaaAAXm?jiH-f!rkVn*pO*^*TnS!luH`&Q#@AX8)hUVq4*CMD``zoj2tRThRhm>? zX8JI*`gQ`GKQFuW$uVy~S zHPZ&Iy7`I_ou#V2%KCHfYxTXg!lt(~cjtsWkb5#G9Y-bu$pQi$?{#G$p}Xo_x}R+& z@Q`ZVZyD1&3X`7<0Q7Rt14G_&6>yoYrf5=WG(yTOM0(uo*W8oei^5_ zQkYGsBPfBJ!z1OH&BJ#l=Q_ieXF3JMA8o!Ic#;H0fc0D+GU>TU1X;OQpVkSJ47KVV z^tURqWIV(UufAjDGG6_50bm!LmVUhkEYEuV3p8FmYs&uB<3mPISQ>}7QHeOEb5|DU-Co9{k*1LkHZ^_cFYGkq7`u_^j?co4 zUvu~p=`8KU{Z24{BhX@OJ|;yvJKgll6r@vkDx*|TTCn<+E$IEp<(tC^lcZCPm7-6F zl|*O_%b1?g>$FSpzle96^uLfJ);XHKY$f}uRT?+vRE1wJqVq+0rgCi-a_i#igtc7u z)%a!3^zCxuJ@Q-|4)WZPQCXK>{y;?0EZ*y04er;PODU(dwL6UT*noS60;6}h@~J=C zSb1_7{5lsZXqc;Rn9s768TY(t7?w!x>>e0!_)hnV${Ajmzhzyob={qu&Ic-;w*$Bk z%MVP3LgO9#Bga2TvIDk=N~Xd93wUEay0CoBu|ezOyn=l0M1Pt-uj6KQf376MYW7@u z7wA&1R2h->QF?Q)XTP46#$8131u!w!!P;4EVVZ#K)}a%6scX`?Qmc8b4sdKkm$ZEk zj`7gFH%~Fo#p&1X4Oa*CFNDjPW0y#OijXfr;)+&3M&WCV-!d$!*-pxOb||b|NZm~U z@Jr!N;`p`x+TIU52$v^YS|d6*2Ag5Stge!$auqHv3sUyuWOsag)Q>I(tr%PK_|ocV zN)hCwlf5D;=478;lGPqPOFzUYL#+gIn%+^UE1Qhcvhbh19Yek0_Uk0*cG|7iSy&C4 zhxt#q>CZY-G~*22$6FmYD{U3$Jy#Mzl-_7sHyk{{!N;nza360d4GEM!atI9!1#lc= zUQ3ibJ+DlW>qMz`YS%bK$D4a;gjbvQkyYkaP`UHR)*O-0YI*YPq1-6^QL{pT03A}) z&TFHUb)bXKO9#Qnq_TFtM_k~1DH7n6b_YiBoE#Hq(XJ=F!pq@JpDdo}sHTfQqzEcS zz9u(@35Zj-ljIoijUG)%33(m$4Zhub1nVM>rt59EoADU7myqB#P<&$$)CqqF^71OI ze(qQ$a^R84k#Zz#e0j0dH`H-T=_-`={K_q~ZSwqWT^s? z$kw!YO$)|oXBwWw3bc3G1|K6miIHKcx_t0km0?^tr^8uf9jOn*IgO=@G~Mc7%=N8^ zs*g()w(d$^Bd1?UFgBEA@a}%bC%G@^r!G>2*O7c{=V9K-Ws8SuO+45bMjGHUUwlJ~ zO#I|6mXE_Kyh?=ig^{x#*+#C2#da{ZwFggsDfH@c>mWiX7!Ni@tHC9E3~TT@+$I%V zWY3PPfJW<;JH4+BN#|cFx%pDJCIwdIX0-=g4%qLnF*(OOWQMt9r-gQvcD)K^fa8il zBZjTHhsSE|hI~j69JZwVO=;U$K!bPC-A~qHah$TO-R!A3tu+#a{cIqP_6M2?}!t5E<8-@%zU6T>jn{e`R$mqi-LgxDIhfc;8ZIzV7>Kxou-$TXw05jNVeLG*RhIG zoRe%)%iKOK$?kIY&l!Hd^Kovn4ZmYUB%N!X`m@4vl9kd88g{%<5C#pc!U3)K5S*gDB!Xb^3e; za6s6d`I=yXqAf{H|FH*!<#Ef6M=XRVZ;@)|I(1s{H~W9aopV0JA!X)VY%ktfDe62If4zSQ#haP#D91sSnB zD$AosGBO@DPK21`?$`Zp2`61kCc%eT7&R~8B68v`i=^njFQNEwTv%lsOTkf?j(EqT;Q?4Q7tfJ=N z;9ZH><@dZ1SsO7mRfb%hytggesyik4oJnh;O*lZR6FT5%NTL3Cc|z}rgDT8-mg>1y@`(vL-iew)w5jv%b=<9 zR-}QnU~|L3VME}u8gg>V8V+2YYEQH*g~B5BQJC4{UG-rpuQ>6Udu2e~-7mVFBk{)2 zv-47Nc-CzUasQ?0qAQz@+H&*K`d z7rek&EgM5@c-G||XF4&{LRp1OUrpt4xu~e66<;_HX7&}Kck-MZa5gWL9s;$5HH<_d zMBc8nZ$KLlI3HD02j-0AsTPEUBwZ+Q)eOkF+juE=?m)2F#(Od7dPU)sR4m+!rkVC= zb7|sk7Vz!GG_Q8{nKZ&k7rzb=!y<6yxymy-0Gdv1(R(m|0lq-D@^LFT#BuptCgv#t)B^qn6pl&d-(6f^J?^N!w?diOT$ z1zTxm>Z1hI2J)>NV+q5j$>LRt;p&n{;vOEQ0HB{mP~pi_x%+TvL1oCJLMzIaT|>Qs zXWGUc6Q6^8ABN|gZ;+opdal&BlDE0G1rB+h!T*9b8#>EK|LI@_|M)P>-(m$6s=R*b zK>N5e+}ui(F51>Xw~jAsih^r>Q0sQ(Ly1g8lodMdcPWMQPLL>QcSyA@4mH`#@)SM` zuMV@XxWo%&1A&t%4v+BuVpXuRqsGiu93*?ITk|sMUw~eN9597Lef=AE! z|7yrAR?=dNiuHZCKA@TlS{WDHzxWOX`e^oM!rKgAayUqgn>|JErb5w^2R2vE_X4Bwp&G>h z?uY3&6jd~80i``{U)-Xt74>f^6hXWd`6(=^I~nF3OdCH^!?LVRXL7Y*b5_v4*Q|x@ zMVCD_iIER30>?pR)pteLhaTHXv=^*tQ?k|ba*Wy3zvt$xp~buf8pQj~&>kHkdf+yo z9OX9I@|QM2p;r!9&u_lg^;-|?d40c4S8QoH=Bx|Tfb9(&jP$j4K4*rGt8*nH4hO3U z#XxLh3AQQHJ*VdnTF;aQt{RjtN-&T@8z4xQ>|q+A2#dV6s24jGw<#tlWa)Y}for?% zS65-xdjmHHM}TX7qcI7a-I5CpEt$798(*&r({eD3nsIpuKZR`W7XzJkuWDr|`fwA} z_i~A(E$+#}4Oj^X7G6Nmd5p9=%@)69o&Twscn>~7=I~#+OZBSMJ1envpfCKl{?=EL zSe6Beb9;Jdx9QXV-ANXW@WBYj$LP1%9SHG4o>zSOo_qKA_!LO_fhPMEP9#9Dv?mMJ zHHj(7me#x%#aX3R(GeBD;eVtl7y<q07iv?~|jm<~M2fQ3c^ngMvcz75(UGAGj`H6Ez zCL~Y<$$Le8X^ka$gO%~zD|%CE*JJ+FTV(Ted2!11c&RXh+{Mfkj-}y*?fDSpTdXb& zrG}V_I~wo}tlMP)iR{rF$ycHT*6~78%Y~y_4MkkX#1=juut%SzVvjx*UVS%bR^+zm zd?YcMrsM2ZvEDMRJCBPf@vnb8qVLu1{D2qkJW)A$x*wiG$EAkwcA=wLAXDc+WhT;P zmzQNSWEz;0l`!2cEN1vv45z#{6z)XO$^m@2Hp;VXC(ohnsUaG>N6~P8HG!&b5aX(_ zKmS%&?9eyj(=~0@_Di|;UxL9pi5Kq*-vF+HSxa{_4i{=W$~_BD3sdcs+Urta>l{qJ zmr+T{%SqY1b*hpX1%q9FyF{$$y^N5cbeyf1HMKwuRNu6ie(>cf&Gt+5Y{@cQInAjk zhOReEJhHWSWw@=$R>*p9LHHAFPk4c?GndAeF{Ar4LuJcNt)d3E%(I!pvB15stWGd^ zDY0s(e8EL7HPbSxwW#`9o)z0~QSW%Pmw@+)qFO;*Drru?R{2;y=Sp`fBy1#(Y~5r- z=d!kJOe#$e(yTmwtSCpT`lRRjWuyat;f63j=UK@iIhH~-AwET^+oG>p8rp-D+S>%t zGweY}O8&?Lj`7nW<6ak+;x@DMh!egLM|@G61HWg<1O?RSLlyR^T5oW0n$T_b-_ZyMFav^&E3k#m=9(g0Gh|j*4Jk{yBf4cbNQgcUJdrt!X zKBibYICQ4K6?R^cL?B#)G&b5BFL11@4k*QA?c3bR1>fo1hlmIR_X~E+)!rT~uyv2{ zIl6G53L06o(uefK)7_ftOQ`C0>i2;9F?NqQ6n5|~=DB~oI-3tKTyu97;6}j5V2J8M zs`16k$MvIsOeUuFuG0jX`SZuE`%$$!1<#b`1DI>XBd5vg1N_?-hjU^>Yf#_`mCE9x zB4H{=S8h8EvWvet-n&ljNNV>!+}Z9O0Ah~XnOvH6;F9xBSHhVCUI#u`&?ylRaO@8v z4iX`0N9mDCZW0y7K46-7C^6zsYsGtfH|Ra;H%FdgmI)1dCHY^yoe|X#7z*6$s}6*_ z%GaZ@MJR5l&>LYSnr&f?Ev)=S!u-f*cXjVYx9KHX6ziEtN1BAc7P|QKhSa;)m@kz5 zwFRjBZBC<(f;n?uB9Hf5j+P_4_arB@;{w;I8q8Uxi-<%}>;W;&r3xehcWFOK*gWYp zEQMdv)HeW*j>^p^By5E!xp?H zolEHP@Lc;z;2Rt#>rvw20mE0x6}!c0Jaieq{?S9|UXasj94_4nAhFC-dV0=*5S zyWTc5N^u0ZZX+g_)rVhYKT6o%aC{q%4Nnwpv8T!?J-Eq>+Bp8A$qB`a(n|iAJ8Coy zW`77?h{=vDQjN-S{Wv+}#8$mbe%D$4CNhNUfWzWI!@X>zh}x~YNKra%XOUtDxP)~u zWDMNZRwrnFc57Z=st};+3R>xFuMAk}A226`R-`;#d@?jED-eSB486QapC?V-*0BY> zt2N^!ySMdnUcEc3PLWZ6FydAAxc+=Vi!pKhUg)TA+Sa@ht1gDNb(0+4F55H> zjh3AS50?3NINrPq%*%cd<7uR889yqN-!Y7upAvgpU$M?%`vy^AlD#Cs`C)JVBA#N4 zxcYG`c!4M$-*TILFxDt(a8~ixk0m~3#)LL`Y+@+fR)e0ji*tS=t-ADyvx9KrF1a4O zUz+tC#hZZ4i8DUtXNcooGS`WDDCBdWA=(1X;wesw0%ttK9t=nFxB;A^IhDi9>meDE z?EOk?-KsnWRgTY$m&kcDJk^|yR_hVdo?i_52O~Cwl{VoCINEA7M|JpdcgKl<+XjBw zc?W!@VvZI-ZMhsCk|R*)TBmfe3f%hOo-Ab%8$Jan-9E^@%ZA-I7|vLc5%sh<&+`#rEc<~E^kMbaaQZ|D(4jDJm=7tD^+(| zHS|I>@^Z;DM?a(h7l~@#*V?_Pu{YaF$1~<5tE$#nH@83^^9o=1|mngY1?w!oYidQ zZMjv$%Y{d9Jdx?}3y1)MImxca$E7(IW@ryvwYLlKb52A-WwpSg3b#TW$K*~>XIltU zZ|J=d(`4!8aK*}xD!wA(4`Zv&VwS7+bQW^0dzUjgl^1F%7!Kpg^L+K*^+bS~G}|9# zy?T@9W7zLqt&zqEKYUiYSI_j8{e_~8n45Nwq3Rv1)hiRU>NInFM5e9n_ifIoY; z@U+s~KHof{ZG?*B$Ja+UrF^VLni7|!BqqtTiw{dIEAou*W&@Qw|4ybYs|Pjk4SkP0 zqR74TthV&r{-*;@v06ioU!V=MZbVd-JNH`)q}FaZNsDR5#0WW!EF%`%{WSl?G<;gF#sw|k9SFYL8}2_-?ZtDAax1xXu^P!cOsE6; z(bcd2vKnj6)GB9hf7x`@Jm-s2beb~Hdt56}T6r8s?RadRe9CD$hMdrn>R3*&tl3oh z_{TLIvG-Q_f6Q6ips{d`l0N^gOe#*J!y2i0)-P<~x_Xs73Ng|(WpmaL*xN148PW#+ z69F=K>hMn9wLu0;Fe}Evs<%}{96qmPdsjJL=5|wY7};2s3ptML>gB+-7jUImw64o! zlLRCdSV8KVRGyu-*LAOA8Cw6VdTz6yEGDp);L!7G9RN86YazW%anAvg!-3#yx@czU zLz`S`BappxIeAV_V0qu&R%adVcVf0hRw@5I0!wH)n1TWY{p$|#tkwcYrN97Zr$nA# z`9z2_S7)-lB#DFmPR_U`N}goNI7m-Aq~1|jHurV4okl?(h`>7qqJ_>)INrITJfGKY zrIm;})Q(w*Gz=Ix9_sTC=6IP`itMIs!)GLQ)XpI+{J`tx-eb8pB=Akc{ayip85=E8 z4+UJz`fH%isQdOjk@g-6r~Rk9kb?D3MsqqH-aVG|@sy7U zb;GH>nt7ZggPq)xGfIZ;{g|%|j-~+x@;YmaRkKTi0iD?+ezN&&>{nf5)7f6J1ouWS z5hn8S%?N)CpL1cyhYFq-zj!U4fm@3M4A;7^Px98(-2XH}o@wHLG4FKtjlUtQ6Kg8p zIQO*t|G<9YRG)2H{kjXUyTJylzUjwTiGYB2Oco>b|9hT*UlKsa2v29tATVqv@#_?0YT3^>a6$LN3s;giwIQZv zA%#LG(iahJs+t!)g+M!1*!H)T>V^&0Zpp=<>00^KgFm`U#^du)jRl-x-{SE8-XU9c zW@NoQ1Z>-QW=`!VN+#hjE=atZQ8d8 z;XvRTN@vaFX&Q7?L{NumA6NDt6z!~?L;WR2R}ZSwz^^qLKaoW)qtDC1M|kvgiZw2` zN>X)iH+q=kHZ!`Vd6*5t)Ng%Np6?AxC1~@quOedAOV3|&4OIW;>}l+XvRjOll{Txu zmPA8(6Mlv3T1utJ0jShv57p|=LkVLN;+RB2v%S@E#ACs{>GM~jg$$009ILC*$*!kf zn$xm7)Q;Hg$)_2&tyZ2T082iIp{$k%QlvSP> zy^{)%x1V_LGintCY|@5MC)&ZMFcU4;`SY=4{{?M|-aAr!kho^OiRKo$I7swvqV#ff zpSEpj9VdQIgA^gG>JQcgdkkS!4eEfi=^bCGFRI+%RwC62X7U=9IZ7pn%Lj|8;(Y zcOThm5gH*-vjRu8_oZDii0Nz+(W;m(cK1hsnuSlF$?s#dL5XdX-olIw9HVx%dUU9& zprz*O+mv~(AT69t^(7P~zG`Ks7~A3Nn&hZ&AyGQwh-{ z8yQhTKO`vI)x8amOb`!b_YzW+fjrQ&7m&3}E@uOyzZ(TlPH7iGaRTo}t#yn^GU$f4 z3EB(Y)z#G_dbQda#+x?OU!Y%4luw48tXI|-J6C^nE-U3~iq8()awvK6FHb7AQ4a#5 z6)Nq`voORF$TDB%_<1f>~d~@RK8_u%zbxQOiWfSnv+7 zG}YwzM82Y$eLw@kdVOIvHI1(!po$zbHG_;9WD9A0cX0e$@|Sd+Ul#NfDlfvU{s2#a-MwI0B|SreHl}*Vg;8zFYeFNp z8_(Xx@f<+M9lKdrbQ>qc5k7=i$Na+JnB9KG4>_@@KOsq@c2cq@qv^%|D-sln?qN8 z=*ebi=bu_Z_ekvp)Y+pLBR!3I6?=1QTSFh7+N9e#rg(s1K(px;r{kJiX3zk}&@)L) z&-&ZAZGFSTAyPn}rO0DbewpEQB>APC%y?(t7a7N`ZzfcS-%h2rzTpM-?!ZQ2Z+)Zf z?EP{pqI;Ejd>61d+CyC6*wFD4H*Pikw8=DxrEPAv!OwvvZnAK1v{~;|d-F=`r+Tz5 z_3bC5+`7e5LVPbFI-rklL9}bJZ^?0>mnd0*^Co%BJ_&~G^^~*e6B?>UV4m&pFnk~&KC6}hzq;4kRme_THTb}14P}G{0Y54bx ziZqnC|1BXoC}wb&00~kE@|72S6f6-~)#l-{$kK?|@4L?@tZIr0y&?pZl3au|M^AJ! zA0-B>@hLmX)k^Bau#kp-4D+(ZCt{j=<)TWEZ}|Q5D8lg?t9Lv*IFvLok>ie8!@I{g zgVf!4{d5$#cQmPb)++R&`^nCh9s5OR?f&8!6PNZo!5Da}MyC6p?x#Nq)XwdJ&!i8} zrvj#+Lzkys6Ydw`V<=OL{kB(c)y|aBCfq)q*9gT?<{;`kD z0)bCFM3q7lflEAWn!+7HC{)S?0j58il@bd%LG4Bhg}pCQjbbApQT8e_hLc@7I~M<` z+AW&SZaBIzFujsOt%Q`6CU;h?B%d&1a{*!IQ!EDV@DI@SAV!Gdclv~S?o$8T46bw9 zYPnUH_v?hdP43*BG57ALo9ol4)uk1SgHPJmS_OKgPq(NSTBF#7%h5V-RB_nE%E9$C z#?Zz;xjT26m+En1nJ06Q+lIg`Pl}{9pNP&n*J!c^awFnS9)kS3KsaYhz)){3uh-n7 zw~Qo>dwWt}RbAhGLQIbyWE^eNPnxBq!Lr1^sAv z6eB&sw7(x8UMs8EKN;U`;HCQs*V)?T%^up?(te$HBhT6D&nH8G{$NFSN6OX0X5Hh> z$&$8hT$>^6C4CZaR4gG&V7p zGqqv+TVDTAB8}QKyXt5pHmawc+jeI<-=9Xr(Ka7I(Q>IiO0lrNuvbA2FRt>@n-8i| z5S3x*BsURyO&3+a#MIPn@I}{1)*(yB!P7$8?L6v-JfXw^Q_XYH9aHP(5v_#f+=pKR zTfirM(K9j9W_{;)uDRO}kocXq?=+`666rYSYjcC?wt8cKKMw|lBmyD1v@C`?Y*;e+ zE&`l*j4Js&0;+i2H2DnzZXY=c0%d=^?7WA$t|=<|oV!#w?k^eJQN{2~7L?hEZaQt< z6#UwQPvsdx1I-K{aJ1hsYUxReTMXIscu31`FX<-x@pW7hxlahn6AUrmkd8aM_Gcf7 zgkNMp+Ft9pC}eL z!TX!cn|hEIsbhL6Fiv`5ZPgC^%_@k@-X~hVzKdu=w96DVY2EbfhKq!UB8hK5;FA6O_#eEgdn5sdcxY^jaakKqIT@>!%oNSDnKP;}-# z&@j@~_+pQaqSF3z0M&H5ku&8=%p8m)kFfyM`;W~)k+2~|mb+Pi8Sh6Y%HieQ z`Gn~Qe{%8gB22SzSaK;=1Oo9yRkCyh9Pxx{vStJl@gx&W;{IS(iiv2+tQhimU1W)H z7JQ1GD$@~IN=ZUuD#b?x>2Hm6YOuxqND_m3#AHWzVkK4K(JgpatD$r=NlAYTyPw>w zG6u`sHkZ*X(z{KI&L==>vt=NPqNL%0i(~Iy4Te4`ecigz^s~Q4pn0Q18)%=WnW2J97?-x_4nol1=nG=|5`wFV2rz{ajjGntusmpqm z-*-}&CmfhHb@LxpkazpcD!+FahauHmvTEu|&8eDdk%l4JUomLvBJXcIn|SeP>K2>- z3pIAeHq?>rERdfs){Kal!uwAt*<0b6K+PX#Oy)j^kI7?1mRc7lxq?qSvEjXAwt6i& z^8p7pzS4C^^5?VQjG__M${O!Xh7sA*IygLnt?n3_P1AsEIFXYFu6wf6)pYMR$lVBN{yr5z@F-%f`NkwR!yK;XGE))@P*h}vzxRgP$YD7*X!270-cGH(x6hE!XT)DefSLZtw+vTkBa3$L%x zUjBSuW2GaJtSnAsLGFSda91TrfG_o^%V)AP<(sDau@_HXjvk$pn z9##oTKL*g!?HMGi59!l;lxEDPH}@a6sq;Uy1QsWlZTA>R2w08?i4j!H5_>!uZ$5qIW_?e@06PHi*jp%zgf1nWBB#x2>6-FSba$s0fCZWr{78pZqm+d)<0 zvB@#Sa`-S9LQ8#>rnCI`Jm#L_&=HPVCNb1T4+Eh2J*|2~iGwbM<#h0!_1;Z5I6D7{ z*%*BGo_j+63O%HFo<}goxKNC;&<{DOU|!t#XoFOtg5fwInti}f^BMEC_KWO|OX#t0 zRSrJ?2NhAH8@;1hcK>b+a`9;dHu2D83jA;sav3@d`EaCSX&Vf^aI|8X?+9q7qWG9r zu)?>h6f?)fG&d8hnENs@RP3^)?J(h^NhYEUh}cq4RKCx{(bfLRm5g5{HQ-~6K8G)V zki99vgusmsmn_PR+aP|-#U~Hn$a`s&^o#1_6;{0-jQg0g_dA__%!59IB;^f5O3OE~kix{OELu z+O;RGqVy((*ls~8kFVi#H#U+oLZ(O z%;B30^B6PtD439@c!0om6BP}h7%V(+d`Er?oUBl~(e4ocW5aLUQs!UwrB6rSJAkWw zrD7AhMfP^QUcAedp*zOKj%%nd?aKL2LvDNz+g*? z_>%fZU>g|2KU6!_CE)Le(Hs9~rZ^rSB zkX+`vk*klEzF1g8m{A5t@mwr;XMblMo{Wewb&tlUrF`jDFZHK3?9)*;GIqqgd>X6& z8raZpRmL;>ctmlYKW&>itHW4i)vI%zwpH_dP+E_*{uGpuK(|lJ2MiYcUP<8Z;k^VY z-_OQh8fQPME_Vn2I={_nY-GZy_velpJ@xvcgez0N`p4D6%343`ykOn!0ingk(R!nM zer9b*ATPXS3Ro;DG_-oq9a+1xo|2IIQ)0CgS%N4#LL&nD%fj~LRV(@@bxv(KxUt_= zMj_SCcO)Sl2fN_h(XG7n)(20HGJ%gLMK|mFl+d`t?R5dps*yXFJoM(*E=M*jf;tFT zAvVeQR8VqaD6$_?!#ckH_eHzgX{YgUbX)E+|^ z!f`HtB%MwZEyl>r zh5^EJ>JGVKOAphM57sA?a8&;@&B0eB-v5n9RPd<^mmot$}cDyU6W@ypJxlNe)JaPLrFAiA(B>k<)xo-zTKz7j5QrAb8vN=oF!t;LzjqCh;olFg@?j>&5ZUvb$2Rtcss#hzoX(;Y%fn<-E~KOJpI&)kpAeUjI7xt z)5|NwoMb;{?vc*@K>V^0=N<~6kstV5%d>i{p#FURF~I>$9!nR zw&jBh%;^@*8L6OVbwxijdO=^Hda!BwV{Ljh)q`AG#u<%#oY!jnr-~b;+Ct7RRSqR6 ztj;ISf2MZdBHK{QvPE{L?SJ03H!V*9{Vay`3q`n;v%PFhL+**^g=z@)i7)`&BB+=Z zhXodcY#rZWpY(BQd4a)-PB*yt6~$(w`gK`=6WiQxzoBz7Pnc>ZcfGbEETOEqjcRsz ze1HX_<-h)TSv+amNIYI)R(CsE4{&^@PT=5alH*-Pg#@)F~8K=VkK{gifP@I zF`F57nx{`M^icugfi*D~y3>_Wf>= zsb=+b)ZO$J?2+SuOXU5*`Ag%<8dSyPq1$6)rZZ6?G#CX>c*Ubt*cSNbg2wc|)0%$9 z2E^@qpWrvNJwQag@T*nMZ*#f#qiI@381nsyAMz*doApS|(?8eNWILSh$n$ekZw4Hf zdO~GCvFwZ8%c3^RZz8;FIpW=l+j;0D_78o>e*ey}YlNQqZ|qv$z-$KSKyQRN{3SSA zV_jOVGnc-c4G=SA#EOP5g;bft!Hr}b>hszm^hHfXrwmf%MV5$iKM9(HBHY zqqW5b5!=KDV^q8l=UaFJ%a{H3QAa$CLZaPVx=7Sp#G`pxL0Z7bXl#b4XpJ|3{?Ul< zt@3|rfklH!=royvzTGzdTJT&da1+c0nrtQ+2x<^da0{#gba#dVHslEQkC5V=_+Hw$ zIcf}c@9FUOY2OitN9CE(s8)PWyFfdzlE7SI*@Z+?Yw+iH?|-Y;z?P-qg0IOSFmBWF z>X~?%CU3xsqrJ(WJ8j+ZlHYLAL8m;(r&{~@2($Vgy(#n!RI>GzaDei7<_Kap2=Ozh5?5DEsgn9Sl1X?R`x;03ZjNZA zTV0OGl}xUGBJZdIj!PQPUxpPGL_86e3%FFvUDW;LRkMuxf6>?VM|CpD`6fUldxT) z4@OXnN5`~W##cLy#k!rupJ4*hu0*hvASYnGZd^lTvXfOhel>UyKlbum?}5F({sFH^ z1gmU(_suV&UiAK45j&a2TcPOF<*vZ!d|Dmht1 z4_f!i8^n%{3G#kN|Np<}BNu8o74o8wAcMTUpYNLnyP&hOFP=$5_!1MYuCpg^yHvI# z=&8UdZIU0?d?Ky+`IGslKt4Zl+46OC{dG`aK>~%gq@)9VV{G(Dvba=f^lxzl5qWxT zdHOO@4N1vwIG7C-6g&4GWvLC&Udr!}uB1u(<7eGW_dlDupL~1jpF8UElFQqXQ%;`X zje^uU#m)dPuZy+!Fa2I#PoK}b_wh1sdZ%8!UM9TmRn8hL9s;*leJezgNq;>5$}~Q? zv0gK8r_FX~T4oXm@H&68+`_vl?CCxKd3@G;=jShiy4J+?q<_|K{Ve45%4sg}22=OG zTjNa@q1)U*u=)%c}7+s3+k3xGmL133aqO8)xsm{oyz#Cx-`{F@|%4 z21HU|Nwr)SK><_->awu;%%xPW`Bd zuS1$tnz{_?4D1Z948$3d3`h*}3=9l*41^4N4Ezkf3?CSz83b{#aX4@oaeS9zFr~ZX z*aarOgC*iQp9Lw2c&A3}WeDp0v*p%GgBkE53AFa;z7_~f*&tEGx0+?oM6j?veM2xm zATW*pF??@qq8KHOf4zY@++mAdERs9E)=vKvWTx!ChnGhM+@3RTSi=)}&EVu_0G|}T z$&+>t4+Un71QfPkF~TX-)5}H1!p>WyMLNu;D)D1Yr;&VU9F^t1v`C9_U{|n8JpQ)! zJCRu@kSL%u=(X`+PR7@=@L^5{smA!>CZ+qLo+t20mVfe!^7T*gC&;IG*qNW``+jeSZ{o@94EyD?n0x!YaxUs`6;YzXPO4~Zp9)m!$ZnVNJY3N9*&U^FnpzGh z8GM^FR^ue@Hq2RGpiswY>0we=nQ)KegbVYs9p4B_Jovzvg!OvjEx?uzSt=v|zmAS{C8FM|JI0xQT0#qk~b z39O)x5I;99{yXHWB>%LyMHo576uB zM7c4=MG^RbgnApA@-+;$D1F%p-p(afG&~OFe!^k;kDQ76&11ZMIK!jUX@y3X!252C zJtnU@R#4j_E6A_f5_|yphzFZ+JrLfKaLrsL?HZOB1rV2D1uX@lf?xLAOo8u@B`IhZ zSd(t3G%nkWCyo5dP<5Pz-GFzEBHUz2dH zF9tTSSds%85AarF#Dkw~_yB>;*u#&v)(P&x6)>zI101Yh_ZzVlYjj|$3IrY3?xN3I zckp4jNpnQaysvK`<9L?$nt!OjkoIh*rJi`g>JZQpWvu!4eXF#-N!gQk+8uG+36)VB zwLFui&T5CxFc_NKOgqi_{BiVies>HbLUF1mG?>KyTF>bSmx^#uHic?R*%p6u9gBn2sXDQPL?h_9)# zQeskaQc_Y%QW8=MQZiC1L!v{n5fx@7X60sO;^(os3EHlhk+mXIvXH+C2c7Ne(KIrT zusCB)H(ZS+-&%8>GX9B*p&d@s6@Q^PcJ6Bl$2>Zlv;p{1Wp2-O7$`7|@L~4cygRjS zo3X8^UT^S{p}o*xuLdnC!#+u8G~t*3Z5bCdy~eao+?a>Ihb*O94|qYod9#iAncI#SV^1`ft}*5~>N%#gk}DxUfKe;={tS0{c%?Z6yNhf1q!9Ryw2t?FbR@gyO*ppDv6}Dkulhm6qI$> zs1c}xoNk-QgKKZw4SS;f|D_?+2-uL3AYiwCRxEX^+!ZI0G$No_GviE-#gwH+pn}cn zXwI;a9h|w9HL?rx0X+!*~$DE9t0hFX{e=FO^lnu)2bO3H1ngBO52oC>IfuDx{ zl7}Jd^|Nt@o@a~~ElXpX*RyavYp1bHU(gd>uShiZSL&HtzmV9x*C7F#ajba@fKc9k zMDs3@()uVfVeX|*`*N1jI=ZxoHQ|(AJ|olJDTOOycGr(4^~ce+xSE;i8~4!Yb1P$< zO1Nb8+o(~BZv#Sx!TM1CO6=o+$3^>NGUTGz<(YG--A)fiBeRW#y^~&<@`kF~h)#F{R}z z9HA}s+6D%fGH!g+Z*fgiO;^@r8srQE$ZyqxpUG;&lAw(bvwtzj&R!W96r877gI+cp zPN_8|&MN{S(Aoft3SLeaz>43s&w7)V6oE6@0HbIEnV*q>H%M&YwT`Jk=*het*Q|+O zO_q5@h{FJH7u1v_TZjL|H#x!%w_E!|k}@5j8jDCsd*Jq)o%oO*w`U;30YKxI%1p|s zY!mr{<|jRUdRIA@8t!NREIpjyhFgvs zPmz3EfDYiY0vBJ=cGHx!9gG5x>(X9&W%O8X z8UB31k>0dYSSGQa)nhMs5>l^7`wX`Z3_k2n8+_S@Jtx33p0-#@m3}^@oXQ1Pjk0SO zP{A4JWB(yl-KCZ!2p_2Nx_vrUl7+$dxR1d{>ovePw{UNh7lpw$mqhM*hcZ2~?51on z=Zb+Xl9`t!Cx_2iUp!HAPYe5?lKo5Dokp4gA1M3%##MTnq{!O3%uTIdbJt z+ixXv)rS28nu_&ZN?zYcHIKLB!72viszGQj&$P{+xNHD=+ZlF(;?Vr z28{{fe0Ft;A&}{e1a#Be0Cj}h5&?n8;I5WWDFYcZU)m7j(}py_;r|b9viey;)ARqL z&F?gg@I2tnM;ZBEuxO5zmp%|JX>!Mn(mxbUpLO7q;vGM*h8BHH-fm<J5Lz#Q$Ox$~dw9OE}E@6y3 z8!Xtr+>8(KRvW|3R}ud|pp6`qq{rND2PtFST3qWz=B@93r(#h9J4xLlEzj$j?iJ&< zi2MP8odZtSzR)$;6}wf0>KD_AF?9pu!&Ndn@jNp$`!SrY*O!{M9@n_bWnkMYsCm1z z^Il$`jMa~n=#DztJuu*p%zeC3V=24}90Dy_NNP1W#4x*a#x+?S0?U*#F^9Wq&DP}G zH2dRhVE-Uga5$ubI}KR@&^38Lwz7=lpOm1J-k)q>s#;UvkRUm@(pi@lz`&VG^#0h! zs+?l|C^Xe(Z2~MbaxR6TTd-O&1)@7e0lGOlAaLT)!N1J7w)-&m%B?~+uyb8X3Ahko zIz-D_-BCd-f9?_v>iCt?IL6zuz*Sn|AagLVpOr8dMPprAnk362k~z#mESHh^Rr8os zG0n?JcE{>mBF$~L#W9wJlW!846*R=o$>;ZE0-Rn`b~Hkd#~u?)oBIt1zPah={Q=?; zPNdo2eP&fQq_QGj(?OvMhEpelaqSI}Ec!>j2->a)O0NjU*W@4RUKngv7{(%z?w<*S z72h+yo10RcI?*&PYg33|(HH=3;0{F@)j<8c5;8GQq5?C$+AA(ZzMO)uf=WPEwjIJv zVv6!^*2YGyADT6Z8FsCZ^s^TO3$h4)dNmh%+#_s)NvZ=Ty$ur%kH!#KndHb4{|`HuCBoyLa~ z<@zCln;*==#=SAVllzzaYL(RWYSjtFnnZp!zx;ogF7wI=FEg+R#SR{>qzz|^6f#T_ ztWx9=DW^>((gMonWP#(K9W=tP+8C`t1H4U~fU#;5U~!U{-LdTsP%v!pa3T_*EDQ_Y z;_nf~rmY}=7_ImmX#Z>Wd7Q}a5JzX)%{6Ps{qqNw+NfW!|1*1y|C=f2m9|ufk>+fZJAE za8H`x*WSl70Fcy~bts>>+55H5eFY55Oqzvvn>#@I-4A+{G|3ObWD35K!`$v;Ar*Y? zR=KJ2;J-$X9n2CC;8y5B7RG33=eVY@{iEI@yZD>;b7~g)YshgOvo%)56fqwCosloO z7@s>pv;@iH>^0ukeM+Km#@(tXwTRhgo2!bY`5p4+pV4csJk+cX4ZghP)*fWwEAC{KX{heM5ngTsY`io=XUf+K_jgF}f^ z5E~Pl6Ptnb)86XM>M7BE%F2_;xyhZyxygautEp?NZ_;2rEYGwSCdok?ZP!s->wNv2 zk+)5g6}fej=TYBe$8y)?(++SlatNNA2#`=XzV2}3@6zOR@irN{*f`8C*AUqSZM&@3Q^c_dXZx>M zw@_F4@J);DJEY`KNUAtM@h$6zo(~Qzag6KdZM*@>A{}HEbE&B(VUz09NYwR#3mgyb zsO_4N`yd`}^L5Dj`~A;wv$`t!1k^(1j6}l~3dSG-C8Gx`ES=;iGpELYyq(^()eM;X zwph&+yAB)cWUE>8&gNUlAfx)en3zx?MzOlw5Tou|172+c$3Ik&A&4ha?ngbo>_5n* zq91mHVPZs~(naCsMPZsn;ct5Xi4TUSG5Me^8su->oSkPw=|CIXPUMo{1_eZ{D z$iOX|PHnL2Nk7_`Mv^lsSuXz|6scdbXIdI1OLbzKP-I$Kkd+IDS0`4+b28Z}6qAiFGRjnkdo15n8YWTBgro`EF)|4vHJKE?OtethN zL?X;s;y|6J`NHJ0K$P?O#l=rF9S|EP=FLKimhA=)B_{p{XzEm&P3I1>_FRWD^k@Bl z=Z(Q{-XCpP(cXSH3Dru*qRB4KEAM`X;UO|KaAB8Br<>LMS}SRYN0YmPR>CaBmW2*3 za%TS+6^*&%?JinAhQX%OhDcN)Xa$^tkZ*|Ac22f8JoR*)8GGg2s0Km+Y0Z1`+NnpQ z{yp&~vNI44+*P5=ov4NmUfXA%g(`sHyRDlnaC5f~;#cH(T_bpjy;fGjS0mHt z1L;}x7U9Bh+kWi4vK zr)X_v(QM9T4JzH8VN*x4_-g^Kwob>sS?ftwN3LW^9Tjgo@dvnMwZ6`~JunJ9)5V90 zv_(FBb8~;%VtLz5o5IpKUvnGuucU45CcMXiK{N^ZI1)_kLWW5fj8z#-XcLTk5KM|6 zjCs)uJJE}DkR(Eo??ohq2g@isU`oMgLw$9n-LGZ%g(jB826Nap62L+M_@E!-`7Pt1 z+T?jh+iXM}FWlaLs3Hb(M1iaq>mg1F*2yHS%y1nK758eJy#@);Jer?QH;QAE^zCozBmS_Vb zey|<3AaewIW~H&T+zzH`vKULi7)+L zsyayOF~YJoSfeyG$v5e0*&Ba>Zd{;Xa6_l1TVKVLD#q zCPkLW?##qpX$pny>9LWAKw|e`^{+|6^K-R7o3NT6;$)6Dc$D0{{r4u>@;pZ(5`lNX zKQqFJ(s_>~t%qr9F^PxobBZv4nqP4&=?3B$1F}hLxfH1FC$i^8s7dSJGkn$W>OJ@c z;)r}y8-}3LzR&Qye*s%t4?`FD7tI3_m)NwfQr=F6t4=+(qvLm1o;6HsUsH|Q#`+&z z{Sy{?Nxdu;o^XzRHd(n{ho1K2X+RM|{Cnkx_* zmAbnTOMLxA*iGEZR}Ls{vknkcKGf zgrQl=>EbDS2pH>W>&|{0ivdoQ*ca zFB!`W>I?h%SXG_kLKI5iXMvUkB~(crwgvGUsi;Iu392Anh%oGa$G#DbGIA#SbCUJm zsN8H5^}nGEMt)v&rYp>3II}(ZEajvmw|n}?@d`o~PCx|ztav&#vm_Tb#Sze|gx;Ba zWyq?eCJMkX1LN@(zMm^lSW=Pt^V==s{0UcllfFy7O(aCwC`VfZ^C9Xm!-3H{X$d9( zqh^;ILe4)S5L*B89j*!m1QLkv_}4q|0lIZQfIpsc%tGCWI({Mp1STVcC0+mGjFOFC z1op2zBpPej*)UvVR+5Bd<{|q_pD=EP-mKJAhkNfA`_qSIeKauVDeo+)9=9?=jK(al zC{cqY&P4486#WAe&4p!n!D|-28 ztB}Tj)fC_h*xz|OvIJWgtue4T%T#H<`Z5qL=QFGe^hhn;Eg_iaNa>9#;1sZ$@oGcrEH^FO3dgWt9t5VO(vEf(EM3Uqa#SH^rzq|=-`j< z6x&mr%xCsUOkX_&k}V>6ef+tdN6+RJmBBw~2mi;>T;qW)InDrzfLlmPW+HoIKe=?E zZS-~`iqw5}BKz1da=aa9f2Cg}fzm$4S0SH}nOmUs4fa4h`Mn^-st{(M$gZZ0Z@1I+ zKHKu`lRt|K?N&Fse%A2S8@avwgC-pRDBl!W|3-Gec*@`Xq+KFivN#1Cp`OaCH0F`8P#yflF`VLr6FSrQtFj8?vhSfynz-h z2}5{@z&4H+We_FH@I(&Mp?D*v`7bh&tQvq8q0^HX7(7o83c zm(<_%!|s^dc2 zHdd)pO$BVzI8oq*aB8>|$~N31K--eJ@`^3E;fBoXM2a$;jZm$WeMKm}3_~c5h{BLv zaC4@H&@N-4&53N>BMjM;h^0)JmC1oXF zODfEY%u4$;*wolG8q{ODe+ntU(EBBuKEo3n?yyp>x+SJ$#j~!tb0)K6%4U72Q?%kq zWX+v$i*I#~o{5-Ze;RFyhk~*g!6kz@M7vi)J3ul>h!2(-PTm2vET zO%S$0)`fE2N=UsyUZql&tNV@uXfI%QCu~J7H8~ z)Qn!8sEvRyHVYlae?xf7;P3IMa-ns(%4R(C99Jb<`CYSA(?WAslb0bz^P^_Erl#h+ zCZ}ezrn}~iCW>afrZ)o(gB61|!%f}eer5gD)PN4AbS_W-NdG`Qu_eh8t89Q4Yd_t% z+QA;=|HZ?foQIQ+Gd`AxkFdvDkcS-m}Vxed#5${6>YH%6LuHUtP7$fU4JV0Jr z5xqtK(w+oWO{tDR`?6U zB*^<1cUrM?=J#a}smyOCtV27BUhP#2v&?-(B7BfX;=@C4#A|g|*Hx0Y+zPmgIDKKc zLAqNWjF>{fkPDO|)PHxVc856i%Ud~|KKqPv29Ae;YG#|Os~_8=S{bb&c5bmEKeaku zw**h+;@K?P1P{jg9!t7>M2_L$^v`f{^rc78hVMsT6(N-gK0k8}&;MOBV;B;V=KG8s zQaMy6$g1^^9<4@q5l3w7dFmaIAYodn5UDYT$MAoaVEt#ibjv5UKd$Xuy_Vye6$)G` zjUnrC{+Z~Pw95AKYwN!;47nlt=zH~~1ko%5(4)54EH{GP7Aj=xP7qIVn!G(gAg~W%bnj&DHN_}p|X67dWA*&J{O9i9oMfLu#@h~Q524z3fVK3n@Opa z2l-0`1HB{vsupAaeFXFBN)(PiSIG4&@L2z+;VM=>(x;M;bgqf-X#?>$y?oMn^3G4~F zjW+^#pwinftLft*m;Qg0ePvYCT^Fy42#SP&v>?*bA}}=4-67qbLo=jG4c#CNHFS5E zbc1wvcX!=C&wJPXazDJ!`w`Y+SZvNdd+%TD6XnZxygoN76zTji#ELuOb0f?s+Nddl zG6g2Kg0sz)agtyah`?y68apvuF2e9J{1u6dct);=d0bWPo+~_&fd8Y`Oc;M)1!@og zMwrMB$IR-zR4|~%5&Q246999DnF+)w3RSaO%Q=f)ju5|8FhfGkfT6+rMLyD3y>pb| z8V+{Cu-d{K_6>W-<6*BkBMwMra2+reg$bF%y41XHoS9emG}}NZKRd6HhAWAWZWU5u z`eu)tlizr)YD{vTh9}hw_Wu$h6Te}Yzq)7HJb4&*+v3P{tp?CX#$oP+UlI&!rBsNvQp+?At-nK64wyUnF zjiP0*^;%=$%1U{5Kt}1ukby$U7^e%H2vjYxDE2T4u$B#WAm(mh6%7H&RMFl+_F*`~ zW#CwdY+7WWr+=~{1n{m~Xaw+XHp=L4rDbaX7208kLw|>7iobte*lW7^<`3F`r2#q9 z-W9INRhA0w3#t1`6ybFCSdwYVRzwPVWbau~rm3IQ?_}0F9TT8{I_tNN+s_BFYt-^}vJC35dCC^)5v8PpmBe>d?bxr3 z^E4svxTsUBoDduWh zk#@eK4?R0erQ+B+uD#FStSgta=geS=99&g zM^M9F5_!=Pm|SMu*DA1}Zol{96}im*YjT;1x4}_f*ThymU8X;9(8$1F_*!*lBn`xf zG&fIgBS0PR#b9Zo2GD=OUkx`3Bn?NEhG981MTzasKnw2iBEp<84!W7mVFJJ}V}}1M z9Q)lBU`*7RyYq=NBgC`h7v4rVsHT%-`(qYdMxU665(bj$2i>XPstOBqZr=wl^-S@N zM(JoZs#3T08r{1Lv=u%m7y=*IdcOHA#4Ith00z^$%03 zY5Pcif^SlM!JjsB!30iblA?OBVJd)7K;~iA3n0kW!{t*)S*0@sBD4AL!bn1*r5os= zCn=w2RE;@pNE^YPsf8pdyFzuxk**ayaxDm&S(UGPE({$`y8uk7)|d9Xb#1cr9wbWYM- zW(o7k$j5O?Q)&ZdJ-NL3yLZ8-NJCFiV}bbSU~(u}9{CSgmu6ng<4`3K1a<8?55In? zBNE6Jeo*DALSlWcgo+O_6djx`Ma)7t6T5C>8oa8{-E$YubEvQiDmW8La4%ql_C1CZ zTgTK&GhcQ)84VLskGG=#0ih1Hf?`Ig$ISs^O)uIU_p%L?rvEe`4beOX$%XSq#jp5 z46)y?a||B!Z~t8{OgnrxaNd_W6Sl3AHqQc_Hp$3^dc@A!#=msebG9u{C{4P^UTdF+ zC9ywan=EepOnQ_xpn*u~TP?2{QRGHTB-53Y)ky-0N|=kOjfWP^k(JV;+E~Aj8&xRO zrHP?0l#Llop8rI9AQ>e(NDeL!BL#~b%w~Nrm?|o8e*ukpBnoEXeX|O4cg(J`AnM$P zy!ARW?$m=_8!({(N*qTqSPK$Bs~&dzJLcJ0xVb|vq@Ctlu3xAyn~1D03KrTvZ*X~b z5;fx|FhQG8QdoN~Iw@F}2-|VE-3$$jtmqi&?`nT#=i|62ShwvUmnP%q$17az}OyGz@E(zg}miksrv>2akrAOEW9ge`rJsjuLi*{RP<2 z>3Xh8*Xg13dfr#7!N!hh(k^J7Esne+2&?@QEg-G+Qevck16wZ=O2eix^mf&u6{J#z z>tZl*ylm10P#TkbtUol50BTPo^i_<|iXxcBr(w^G)Xx{t*HvzT0|!X;tpiUJHiLfF zE2dz59bD|3M{(#>3wcPQ^6v9*Bf;j1WK0=M4hhrF11Rx1|-4u zOI-h4ZfBrNErDF8`znAU1?xP%OIA*oTvYgZk=l_YHTmJ{&M9b8PqsRP>sK;=86(+r zT-gn%g9ph%tj4M9uOwsVpx~4hmMrh0DEaT(k*VYfrS0Te`m&bkuh(b!;&ab$&Qt16 zXR+szs%fc!oXliw1##H4f)s}iMu-!>t?cm#p<}wDLlqy%gMaPbtpP;z*>gN98|Qw| zfA3N-c;-IW;hOGH>ut6QalTxQ2Fz^K<8R=zb)wt3wqmdYCwOQDhO`;dFheDVmJ{Ky zbLBgPvYr5o;o`@$>gAqUAHJ$^4BGLzNq6t!OO|5@OsD9Wn2LU3(#w;!CjS<SPOOC+>+JrI4|M5mU{RILyv9y$xE_%SLf> z=SAT;=v|RIM)bHyohK2~zW1!yE+zPK+5UA}!*i!Ah0~;NOFN<_ZPP%-?n}N#yUYhZ z(#Q?tL!6$vWG>Q3)xBfz@89TwSos9`LV=R9s2D~U<-Wmr1tn4PmxGmG61qo-hC}yV zW5<)+v*N}{@Lhkg_IrSOfQdN8t09s#Bst$`z}~93Y(h};EZm&?$NwNr#N=_kHHs}7 zI53&cv`00*nJ$8x zp%UOeq}4H&<}gvScO{|46Mu*=$1J|ih^Xfy98%-cSiR+o(1od6tM;qqvBJ(pznN4P zoU;1aloCMenlT2;<}q&xeg!fx3)A}pIsKQBgP~RJaikW&8mytYM}#A~B7s8(m)RO$ zzPxxIG0l3Hm45vGC*Uh4;$M+>H$QWO><0hw{o^}@pABTkCdd8`jrZl;80dERboRl=DjOd5y%!dSt5b) z8X_*9H7`WFb&NygH%EWY&JfE^!#5E{j&{>9Av~=sqV(eCKX_$BPYTdYKfKW%`(W3N zhNIqJkA$Z&=BPTQKbtUZ>N?-^uux&ESl@I#?((4LS@5<{l`I4%V}_8M`QtZza_wt+ zGonzen;n6cI>sd!7vF1jG%kJeq>eD@%tUTRvdfr-ZG{rWMjM88t(Qi`}uB@IOV6f#|HEF+9~ZG-MkGRdu% zd7>e!`?J`%LpG`xhv-2{xab$qu^r9yWC#kV= zDhV}jEr1eO#V{h)F?FO0j7^FhFrVl+1(pn54LS*=!k z?IO7>gB%Na^KEtR{@RfZg$6_HAX^t^(|*DIomLmts`hjT{5hG~>*skQ`+`@GJAcPW zzRoh4Qf!Fj59DeS!0LZNdrgneCi>?$u@snNa_W>xRZZ|_cY$<((RiV>;cUq@8seB# z&qw>4UN~13@+Nb^1xccD8Up+gB&XmI1J{-IK3Vu!AGbp+^)5OtbDo$MZn|~9mC09@ zVt4+EbF1@$qAkFL$-5g-Ew?k!)b*75T+ZRFA@n?53Ant!zcZX~VFmmTV9s!Q<o3?T0^sj<~V|$wnVog2$>~Ev0rtHYYiVP~5^p;M|nkq>;fTIM+5eTjP-5mpf$5 zPf+Jax!>bWn?_jgKTJqcMO^9 z!EyVuU0timRy|M+X{vvbxu7{;4{t5aT~{b}KJ#CJvrwOR4yBI~W@Ks)Zfh95FNTlp zdLC029pXnru9xW|;A;jzgtO#!=+EF|7E^AYzbT%-k!Y+#m)jXn&dSvOAknyyCHcgg z>Og=`Uto@g1bKzVK>ionG)BUK(>*>Gg6of8b^L;iUNCpka6E4aMMnb6p!8Ztypa&NbBqvu!7*=CPL32gEHK?bbEbig7DPrCZh6Ud$5w3S_lsCV`H-yF+8%Hri{p%QXA8@6deH4;lYhYDaPcHW=cjmf3S9fI8M%cvGL= zzQI=pb~~pZm?5(yAP9;T2-fd@*Xl@-DZPPZ1r?+@UlKmac7FS(7)z)7CmRg}p@%Y3 z;ds1Ye*gM?Y+!U?a3JiBC`Clz&%nsQz`%q+zrfhQ(7@!tAAu2XWIKd9q&g%h>Qc6_ zr3VgW4)uOn=Np+|>40%v7qUx^~dd z%U|8i0@r4~Y*`lBuL6#0RZuC>&)c)A*PIrzbLvMs9uTr~m4ix`D6G?;i{I}vQ_Byo zTW?6HnwI+I~j6Br9kW0Y)0as+4Y8ch3^`B_|F|pj|pFO^4cX zyohyZCLDZIH^0pmM%A&ap7dSWtB5qhZSlQtpN^JH!2`P&b@Kfdg2)#oXnTU!J1Fb* zptBK2h*$w^C9!4){gt@>L7P~?91~`K4jPn|8Q3}3G&hl$n(OfnTIpODGaTcGgm*76 zzq_?6+YqChf#7a@{jPJ2b*49dmL$X~=zlCkDI%dVGD5HLoGiT~J|!AT{t9;24_{6g z9B%*2(?>1(3Qu`ZO{CGpdw4C$sNx;D$~IGa3Zd@XI3zqE-_;O;bM3;JanX>eRMh)b zUr%yxS925<7inMrowi!H4pDvG#8@*gM!QAZ1G&{H|7+ljvy|VI!f?Bj{V295I1b!c zvPet$nl}+K2#-&Ue!prmD8JsL%6l;x^Im@0$ZWfo8=oQhdE1flA-TKb)}O+%-z-PtD=6$>lwUb7+gL7_hpv95 ze=^$qR#$w7rSy|UE9Ye&u{MTT{mv|&R*h-+u z$9(*sP)C8oI_&w!H_jzmn|Rx2$p6^Q1j*jAzdIivK~9Ge=qd5M6VZZ7l<4{lztCDl zPA94PgNLJ|7ko8zNB6m<@m(kPyKKz6xcK6x3cq5ypG`LjSL$2^J+lRKd>yCC!u_gs ztnTlz&&KI@J!V{pU0K0M?v5(ud-)n1oX+w_Pl8A9;)gV>rn>0nr<~XG(Qx^~GrxSv zF<)gE)F%=qa$b9flhXBGD|)m0&!LR$#kK17&Pf!j;davpqilo4mpXNml%Wlyvg8Jb z*=4TNxg(vERoz*FW+#zpBX~4A~dr z6%X4FwPycpM$x;&B(H^r#VYc3R1ZF2PF9C_BYcXcZ;Jd5IE+zx`U$95rw(FQTv6=p zI3i+zg5DX>-~J4$W?I<^SvBCKy`1G*aX+`%dX*+{sNI4tJXg687h*FhR#bhZ3)4~r z`g;!9YC@0BC;aT$>emvcL0RD^@OGkv(-(n8*Lms}aXpF*xQ$`k)4wAz5~e3!>gr)# zKfCFeSq%Ot=!%*F*}@_esTSQ?MIsS1=x81B9zCnaLNv1G$Os~4 ze$CeL=V8CMX;KfiKvc)H!W67{JR`HOD`%N59jO|0-kBrvW_!JaW>_B`;Qm;Gz@bj+pD30Xdz5LP;Me6Y^Bx13utbb*N`r z3?AekD~t&(^FTGGy+{7h%Ap(`*!p`LhzgLJa2Wym(c|$H%DRr;%61L;uQXGnjex#v z*SNWMMiJx>5G$B7GNchN`kYeUf{Pl z1a(>b0yj80h{+U>u9Rd>!K}k(BbL=Y#LUIOPK;*G^BB#GPoHal zB7d%pj9busTlv9;9Jpz`u5*n$qc1r>*FNk_iBtY>^}IrmQXmLET>IsdX3spG zOCA!+BSp=J1qTCPmbC<T~`E=891m&8X>%5HP&%=hP*NFZFF*a8ZVye;ypC*ga z4)0yJl!41ww!xQ-gimD!L+sM%H$_U~)!q}ox?1B~eSEFS>&9g2tnstQx`O}0(7yxA zHuHNV51P$KUBM4=rJcF+%B*W*`=OGRrPA+5Z!K5)5IlqwZsoGu1G93n&5&a9t8pehG2Lu$P6|taGrx_MlpE`nD>DwW@5_+|&zt z7ZP)8*$}eo+!;)!(FSV9;yv(etVOt4HsJB#s^8pLmtcrG5P__GvaM-d1Fvc}~( z={m3n{i4B79`_$qZUtc}I;(IKaOII--WRU8L*_hg-Eu8$g!9h+M9`OY2brB9+>6ik z%j21t4@n0`9WDT`A|%I}Gma?i4`6+g-n_pwWt?aQd8`fy@u|I9B_bfi20Ye`Lm6H3 zVMLT0MF!0O!UOp2EGKeHlv0nN>Sd<7TR=C1Z?QWSoJKuwpz58GCzbq%YX|lbCBk8x z7SKR7zM9b^Ue)BJLF zM62a(5Pe<95Ha%ikw%A0mq3;dqsy1`PK!IT>&m>^f}Pq|J?ryU&l3)2h>y3L#ZVIJu%GGJ#)}alO)# z9Nb%7BN$MQ%{@$lx?n2`5`Xlnue5-+dQ=+sjvQ%QRfXHh z&5vq&^Lu(1zI&M;_Jgaayysp{=Ye^v==aSLl!xy0P(qhz zPxJwCDN1F%a?AbJT?t$CRAX3qkNR)m3RDCdt5`;XE>~DDv9o6nTK}Uu#6->ooOv z_!myj${&k*)ch+;fSEu-3Xy&btnL$X3hoE(00ttmZ>!xxKdBfD3zC%+cdP99pzIo2 zv@S%eLx>?{b=Cm9%X&suC`9{Eyv~TVU(o_m=41K~olsXOeKxnh@`Q`i$^rEw&G+>y zqAg2H0Ml9NKT1`yCr6^=E$+h?6R)bD!_UXs{ENkEc)U^%AI`j6Ki$hlU!-K9)6^yZ zRlJW?npO?y?xufe7)4y72%nt9*{m?Eqerwq^t%ZM-f`aih*3qS%3-K{OP<5Huo#y* z%~g_UdZ|>!QkR?gO@t40{K^1yz4MPU;*e*dKVD?f!k~X3y>!|f;`KjG&@v1+;0ZW# zn~W%)*`uq73VVBZmk6FU4J6rP*j0oBM^8zsEf``OKdechSJ5FDY<G(ivTXb*=Psf0zY2TL7<6OWoMBXPmV z8T-P0KcYL@RBtc-Z(TlEY*j`7`VTGbP>xc~|E(F2=j`{CWN`6l2aOuoYc!nwQRI5i zx*1P@>vpq)fN!2sL^`0xb&?X|acptas&c%J0VwTDPMuuD}wSBk1{WlNuKdjIF3d@ zRRO9i0p(4fo<6l18Gt*Gnf?HlD4hNXKiX9k?g!~;w}Fb^5Nry}8OS`|eqL>`PmJNh zwP_jDX)@<->YQs4{Yfv^a=r^gzk3haf&n6k^{_aq`KB$k2hi>>2>=yGz;AlKy7%D! zgxk`G(}sdAtu^cOZF+D~d*yBAEO)`Do8yf+X_D8>txgTe(hKER8t__ZYf$O*RLP#K`R@jL)A zFK5RmhQ220KKlGl0+xbIkgco8y{dViVDi^&z2O*hvtsFDLgCx0`(xcX?%v!{XE~G1 z_Z!vvvlll1D3GNv_mR%-O^sTgm{oE1M6E0GC~vMU=h0Du^kUZ+Gz>wLxFzYWlHlt- zUbn!$W?%upwF@du(zN^(#C#eQmr?~aS@Z`>bdFBlq3gl2L&~!5G}d@X$ibto!hm!; zXcjvOYc}S+tgmn$!V}pQ#WdIx=WJ#l%7&?hHiLSfb_CWBm=hfugt+4BIN2aDD<}T~ z<1n`W8+vsZadCK(J6*38rxJ799VsH|trmv^0~e<=6Yi&8vVEb-DVZ??Qh1LLibIa? z)&^$)Z*~%1)+`jT2$6@I$;QW@a{7kYCoQ?601m6IrjMpDl49-c3Ct?H7MD(Z+&LdA zOuF%2o9`wEOP|zJf7a{5s;1^z*teLoo13PehGOsb(uX&l_!>{62>Uh;6_{JbCUADS z{A&BlhwOn+1^NJEYP=GvTW%Tup~g;E6rj&qnMx|BR|c%S+B2Vv=+h*HPDiIkOcXQ8 zOsc!Hv_*2Ac%k{G{JA*1HPXzi;QF#Upy}|Wkf^+{rxHpSPD`4(5S7b&@4$|Ae|1ki zPQV8c%e!~|E+3db@bAT6a^oi!{`Y+`Keup0;G)lrDlw~Aq%unU3YewV8mY%m;F;?Q6_}20gu_DWP^X5ZExEbvSq-Qjr4Uex)7@DHDlqH?ztHqzPmEmNo1k7O^3#w=aoeb?Z=?X_nAyqM}rbyJ_-C?fn-> zM};N(>h~B@YE;RST^UcyBgt;-h4&QJe8ki}=oHBWw zh-q}3_DRHX={JK&((lnh(=X*4(5m95NB3p2M+clnoDw^~30T7JnPq0o zoU3Q)TiT5@{V@m?150-?2+O@c6zKl^hAE)?leyEIS$7Wu2}>$Kx^xvo3VGUo>|4vL zV@-MMj0uej>e%ch0v?w}N3PxXOrk>1tC^56}$Bb{%%>D-9^1A&n zM|&%8Wg5OC;ZLm!5<6f2byPeq8`5?^hE0;n&5iZuCdaiPOg#^&Lw4F@MswFb9~AV? zabkIB;gE)_!Du8pZ(@HIK3V<{ms6gsMjL1G(nPBfc@WZNJ9%DE19^t<0*znnW>VO8 zq7ZJRQ){iYtpHQE1(myhD`xE5L9R2M$%ck{ubatH+cC|l2f!tv@4a{M!5jAtym&n@{^HWQi1rP(lN#--No2ZWfBYv%;w zOGc%kgR=udQ{*FLE;W#%e*YeYepO2HQv)EcG8?tzm?^Mje zJ=Jui1y^Psltq+$XZs#O2&;(BxB~Td27kU=38hV#FP`Eid9hIfb-l|i2W)*rz$(qI zW8y-D;Ux4q*L7u#*&N?Y#woLnF&y44M;%Bc`~xneC$B_pj*S|KBkbRrwk7|TS_B*G zP1vW@czHT%wrHWDqV|@<-16vH2l;DvTxF$m`E^rvBa0yevAlz{ga#HRx5ls?ra?ucL4Es`H|59Pd6QTUnOrY8LX`y+xcVb;602gXqZxfOv@jwAz2-#?xoiyo?$&=< ztFLFHF-JH0d^%`t7=(MtFIIQ$=qR|0l~Bu0hH16Er3Nj+riY!?Md}>^US1)Gc0C4BN{$KT=RY*_ku3<#hCZ2zb`nw5zf4y zGrgenMtb?@?e}Md;qgAKRGe?y=yJ&<)`pSgu|)&y9?133DFc_y$(SU~DQA-2!D*qG z*31HD=koeFf4pa;r1swd$5D?-s{cKc-Jr|u3(2a8NsdoDt$;HiRHvP zlNqEH-WSr8B+hlpvVKp|u=4k~SrKP%WlDqb8w2}7vznH6r7+7S=9(QAstbNQXw8o2 z&}Nif|N2~$^{#mGD|+qR68=*{W0?DCT9@6uEY)00q-*xOlz5KM|C}TBS@biMoM)`5 z&nvvqmY(tG0{-jQOV6J(A>jOZMJ|DbshmMk=rhp~z+D|f4YZl| z;fwYF>$(`*Wfk}k;_6%=)~Y~sJjB4q-vrp-gtk(r8TiYg8A~f}G$>AI(b=BPa-gw> zRhZ#2VykYB4BmMyR4*cXi?L>>g>?3%ND!jxzYGU5<=Lu|W{YQt7s|V?GK*_)3!OfA#c#6uqBpb&pLrt;@-z z2I3|-yz(+Pr~HEDQUiuDIx}UPr+EN3Wr(BQDLAeibaMVxuHKZ@MNnJMnbve?K#cT~ z=$VPu$#+AqBK)TuR-xa-unBwrHp2u7p(&MhcG*{xfi|GJv~ILu`&)nrMxxzF`KSuW z;zk#hhL2eqqQS_>k-w&FJJJ2NKhj$EM>zq3tmNAI;m>m8JiS<@Gj|*fl`F#Byo->} zwkp`h;uN^Lu@zIV$}{Hfnf(ao5e5?i+UU%7zVt`lILjy~UbVh$TlJQ6K`<6SM%Rs1 zT8M*DEaQsF8&YEnhr;>g;U(0cu~`FO2?xOim*vi=UtyC4zV;P;C)ZDngH0#_S7&6F z+}X-@I3bEbhouE>KhFzJ%C>AkN~%mxG3R`h%XY{;zQ2_|SHzo_LIn5o<28rjr)HJ$ z0zpjE4^P;<%~}5wUQ-^hg(&j#YWZ+z?yyyBC^$q~w$gTza)<7?EG0xo^L7m(yS;4@ zC6>a(!}*no_~YQJw>QF4!60>e0qJ2`v1WDAYnfv^&J-3NJgL+A!j{xFYsdAP@$qg1Ka!Fc{>+$EG5(7*|M)L7bt~3&{Q4dW% za}`?~y7eA8t=*ZsqCC{qn zy&C*8zkpBD2qGH?oj#@5kOsD@z%9exIgysW=9(7xdLF*_;isX^+sMZ6!CS@rWzI*~ zVs9rM6X<=DfQY(Mh8P5P4tJ2(F zr{#bbMhiXaON;8!A{$){dq7?xh;z+<%AMvNU!eV=+ZH1VPIt9^O6joWug7W%kOY># zUF4bqvjd_%THK@Wjo+Hv5>{VxmfvYN&qp<$6wF`gA&c9yQ=D?)#MMzM#qu!>+_rI( z1uaaIrQJ^kQJmbx9@4xr4x;F7WhXXv^W*}o#n~t1JKslhgcR$pW?t>Db^JiB^HYVe z+X6clF+I2mOx}K=^21wgiKE0J{s&bcE?-m7z2@|)7J0L#Ko7q z3~>3dlitIlZv9@zLwQV;!h`UVjk>ZKBJiUJH_z11yQeowu6Aw@>42U`qe^l(abNxhs|HN4B zo`a#9Ys(4o=Qi!^)!vMbAOxp6R*G_{1m|&?)NGaOUWm<=WG04^szZOMP6F-rB^;#JUB6?LB(fc4)=`w{BIf?r5Cx537D1kp%|+NlHdJrKE;j# z-`p|-$@DKbK=e7766vhH|4p%38j_{B(UI;#-Uk+(b>PWI3-IUpdVh@r;XO|iFDH|! zc#jvZ9}eIi_oX=crZyBJ$oyG#c!q(6Ldc@fT9OwEu7NABq(?w0IvpT%D8c6ZP?maC zJ^M%chmlU+6UO`^UiXBab*d1SCJFu=$4$O3$?R(26hJQZavh@ zc-<15*NMzj9Z_pNOm_cF8$Ng5-Vz)syR#Q^`p6eWOgj*+>c>t)X#q6jCTwL%ZBy2U zL&oRpT(YK}^?yKV-`FBbE@A!J4 z6`!C^?ztB_qh3IjUv3M6wEY9HY{-Bq5KWIm3#GI_Gya8#`k8@d2Z#gS=}MIeKA5Mji?T6b%#awr z@D$%Wy2O!Ry^6N5#1?M>g@t;97akKSR`1Z+3Ku^gfj%xh6N2F~t$as{^;o~L!p;Of zF7tMT=LemI3=@(9tCtLChI#1Md@}kiA7_ZxkBS&kKkHxMHv+P84I^cSu%gA;Or!)i zXhAwhl;7=_wfoaVY=?f=YX5OO)$w|QO(x$A=<;bL=JY|sz&xa6>1D{ zSzilih!Y50-FRA7P2kAE>x}7nwCr)K>do(3#8ggxl6_U29?#vJ>gJ<1E{nPEv>ZzO zzBV~4V&1IPX0jBdwRUCNyKUl6ulc)?e^n__{lu7in+&%r{SLPb;O_V1CH|Bb>bps? zW8g1JkpREco=xSM;wG>oY1NxVQB2tk=uGXzQf4l=HzVv3tAI$xz zUpgCtf^+wz{rpV?IzAc@f6$}TqJfxrDOXT6Y5)Z$Ztjrn9dU4KE>8KQd_~Am8@!3L zLB->eH2%f7CZbMl;9;HcdM(NE(^0&Vn1%9eKk5Nxdo}^4>c0PR>Ful0J&f=@nJbUD zHq>$w^ePrd7p6k{X@nm!7D$aTtmM2Nzk(ZvODm+mHo4Uc5qwZjEsGRx5Jo(_-+>Lr zP-lfE$5P`SaQBFW?MLUWR4QYhr(K@{yOn3!OijQDY?qlheq1Uyq{D~tToR~SHP7q6 z^L&$z(ozK4@K&V`pbh7-i&lT5Q&UzU?ep*3a40XI2h^=uggQjuW;eGTK66!V?sPLc#E#Hr$E_aS*c3 z)z@TqKe&_mJqD!?LB^zRe&>R-oN3&i+}gQvrhJaWDULS&U{I}gX1=!BZ_UwqAEX|! zcX|rbQlFg_HT>oLW$U2ft?MRLB6i37-@QHMgwuWs@nkWYQw0ZGo&t)u--O^iY|bk1 zKCkr(pTz5VHVHMbUnqAX5F;TGBY9E&r~d6jRN_ zD2XCl@rkebe-e@(7K%n9^C^78NY4<=ie+^#4!5wOoO8k35F2P`nwAPiYHR;GF9kF? z{mJATcOv?=^W?muBjh2c9m>J0xU7Q|*Y|@FekPWdPI>xumczf^vB|IQJqEEV@L>65 z*fb#8XTYf9IQw{@$~m?6z%UJZW}hKi8gKoW4uo(%coj=Wv)k!*kHHJ_C$KU{MfDF> z$qo0nxbb&V_J|)rt_VMt-mLCjd7zqI9c3A=&0Ss1vv!~;70kK-1g&{-ja@SPA;ef#}5((%N0EBJS+49Ixb$8kc2--c4B zmEk+f-b`2TG9Ze@-}0*Kb=_&k+b$*l?HIH2M}Fld?TEq;N6T<6J$y$Rm8JLKo!OEO74QJaDIJ9 zvH10d->nt5v){~Al~NNYW1Td)=c|h~bw`wIn3SsL2QIeG*cT(0$8+tB^W12Oyg&$g6u8*wHZ3 z_*yAUkGWrjFvXXjF4^i^&6kdzR7B;s!y~T`+vPzVy}Ss*U10`TwiM}(Dg}Z zVN?M77rl_$tLHSt;srcH0a<(k6*2N~!cdGN7J)O>*7g$Q=VrVDQc>~%v_lHW7KU;3 zp;^dcz>|GlWr1MNkLIwX6?SRfN;|Lf$ktBYc=JlP&$Sn+8Xce@xE8Luqs3y#UFOgs z(kgd`)|b$rpWru77rO1UVEfHPU6@JUQ?c1E=4{8^w;Qn^nQ!u*-x<1iRmhOtXL0WK zfj7VF03BktXYCBpU2oLNTnE-^o^EfM&PIY6Y^o)>>W~5otYs;aphn%afvz2QZohk1 zdP4SWfhv+}T@E3w6INt?+d1uu6+HUgQ>1mB#6zU{ulr9tV?Kbg3vj?toJ4)<5l^(; zL*k1ydA`w&HueZ}{TsaY*~6VJd?}kv#`GIuMA#27fdh z&}CQ6B(3bMdbjP`)|HJGn3=hJi0krY&EYK`KS0ZH{e>o*FuSW(+YHI@J7bvpXfVl0 ziHoZ}#Sk&bKqsfRU&&7X)h7sL`R68i;Bi|vMepEd*;J`LfYlb+D`PqiogxS+Lafl! z_wmgP98MkS-%dJwcK+3rdV$9(#SY8toVLIk8^cf01^%~oU1*A@XGo&CnW?nz_=+o>7;Sl z;sf%*5Fpx<69|wb67a_O`8w^TPdIankM3JMgP*TS+ev*e_AmsHDHeA-zooqj-Dp1A zJ#Pw3>DuD9b_XwBp6)VDfV@1(>kkelJsA^qt0_FUXDn>$d2B6?;;I*%>dE3CPAd{U zaca(~s)0AAXAveBV6ntf&F3Or`?n(k)g0D+w)L6}T}^)qtM9(tJKZeK9yd1EuzCzx z{Yc4LblUH&o^zT_p7lH){Y!lJYM<}AtSfQtf(lZ6Uc*hS3h%1#T9-LWy(Qt(i#{d3({eHvHkwlZWxS>3bCiV~@Oj4!`iQXJ4p*F_n72!{lMjH`F zQb-*vX8xv~Nl$X4q8lCHv6aXo0WDClNXZzF5c?8}l*r-|-cPS#Geun`A1P(3ES}V94U#j-MN$2T+>ptEJ(>ex>=_$JZc&yM$ zYhrgCpjLetnticKeyczp615D@&Zy_c53LX?o?IR{$^Z)sKB=GQt#(cV z8;7ZsqG-h#&TD0QT}Iev(Rz~GxHv-5{touW)%1A*_(ah*yP;MNFiZ3;0Y?fm-W%n|Y8 zbmS_S5$z)SRN%;hDW~XP#XCb__ba080-_Bi(LwM|$0>~I4iib|JZZBcsSRemXCy=N zVfifr8LV`RVlet1)5O{fT2AEpyBsb}M6hgmx(T{na+MiTt&1<70^XyK%qZS*71#0Y z-!oCY+6nHX$!I?v_l+;ULUljScgu9A7`0iV#d42%tuxT@Qir4QXk1lvDgW^b%{}H8 z^*@2f3*&pOGhbU`91T46f~`}QQ9L35J;1i6GDx!E}<{-Ssg6g50mNGjz?)W`pN;FN-#uuYJ*sVk>vPnXUnJqKe` zhgz;6-L(gY4VRoo+bj}VLn~K4;!Uv(S$Gt+mE@A6296Sx?sVSrC`(_i|T-`kR7`N$oHwuOXumWRfHeEnBAb~ob7r3gX0UgSyV{h`t@Ruq!~uf7~6>(K&{0uB46f?mu21UkN{ zr%i42+(M+*Xi!2X+CfFajyf6>`Rc>Ac5~nI`Ss|B2RWv#?xQT%qk1_Ab$rdBnmj0< ziMy>8iJ^$oVuJ>hzt#Pv7{rZYqFnVyjWv6z;6QY_pyBp~z-ols+G`f&_g|zh>}^$Q zum;sRQA|+zFqYrWI~!TCZ%A=>B-)k99&f;I#1}^XzA=&a2e}@uHWXehDK_v}JYMCM zo4&NeM0_m^Czp@N5q}x^5)%~~Dmm)XDM1jT#k(WkHiNnax#^Ahm6D_>S zBzzsTiLaIlMjIfdDY&h)gPbl2lTH_>b^WB;i{nFtIDd!7qlZT;9U&yQkzSsLD3 zeQGev=k1scOO9}w*fbc7v|1{#03yej60lj2SY)eW*iUoEn_R9P%CUP2MD;du@FsCU zeenqd#^TT!VEFHM5Rm+yWPesimWt}uM{FoLiY}t1Ey-ZZyL#P~WxW+vTGlH3GEeatjsvHLpM=fp6IlzrQ)UYK zl$ZWWSjqD-yg$&X)Gsh+FHT-a*iBy;JDK+Xu=bWwQFc-LxQGhUDInb`HN?;%(xEg+ ziZFuI(2YoU$G`y6($dmWLk|KX(!$W)9lx9BUF%){Z_o3t_5Z*cxcR_h-{+jY_jO%o zUl>?F(C;V96G%V&-{JE2-bD{M2?B8c1Q7r1O)VpN)5Df))2Z4xJD{ybSSb12T-KP9 z^bK29?TLBemR+Dk-VkpDLkU->s)e>(9iNI4CvX%*)$&tfaeCWpH-#yOQ}osbKqWb? z;aXdfUtO$G@L?!c`*&zGw2c~s1n!HxXehRmI)=jTCdRELtcTeIKFXu{SlhJ1;wP zKd~}?enA-s`6HgAepax{YayE1+jj?32?ilZ*c=eG^f{rD)jTD>vMh<956W*G`@~Bu zuV_Oqt7sz{8J8p#8~5vqMOM>SOjdKzn#o&%wroXh(J&}~6ojcX#bf-p>=(Vf7(v1l zQ)$Hr$>-Qg0uM#PKL@eCN%*Sxi|L`#!|)I}m0ZTrL~_L_CKe)4cyPBeL&6`$Z6>^g0k3#HjkAMJY2o1f4K$Ot{t=VISxYCGb1n3xvBEJfM}@`X zEsfhaOWW-85kh=Lu?;7<`=`?;8*%v&A}WmmHMI%ICZ<@j1V5+X8pC~u-)`d}^cxnC zRz}#iug;jk!YtF&RsBQy;&nf8skhUF!9rPZ&8~=j0UnrhtH|-UJD;*^hQobK4I~>} zs>yI3gZQu8ToibjKgA`j_UO?(Vr>v(WR~d4*md=u49zdB=Ft81z2O43ZljnU{(Tw6 zib2kCrtRnSuA#aGSu!B$(JFd0M=e`RM9+SC&8@ajB{7}AN{|quG2SvyB0Kg#cI+?u z2pyAaD6H{v9?!~vN|d!G*SL%_FHZXor)Ffbt}=v4Tn~@r6UP0_e!5SO{&#Huz1b6* zF5N@QZ|JOFVjW^M6(i&*7QEr?Jhgl}zoCC>qn~05@FIPZtSMtba}2VYdX%8|^?Vk$ zP%GJH3i<)f8D!Z9;^vvO7iZuaT1jr|1e!<+g_-~;KT~v9jVq%6Tnlx*iG^yp(@a-J-E&T5X1x;At&8ELghjMI*dOESelY2S@(UU$vQldrY!)@z0;QVRtiT(AplgqjX*iaFNk`~O(BibXr>xzL7XR?< zB1l!t>o0E+XKP;II9b+56pk_LYi8FNso{+DT?GE#sfAWc!A&O)qyX>W$0)1E+chl> zMy5H5s^G@sre)^`#k{27>YeD_H(zu}F;k}NPQ3^H27(h#S`Q8; z<6jEOzY7b_=P^1Nnm2u#p{=BVgB3Xa5OP!YL6GUD@Bz|me*xHP95{QXv2i5p!8L7d zAOBgYO5uJ=kOIbJ9<27lMs1@%-r+`{Nf+uZVB@dG*u1GiwI?O?#Bd3JrQP zd-W1q`#;;l#6=G!E7`>ETB7zm9`>6oDi*5Nx$wY~MGh@;8uslyWz}dBTBp1(VfF8J z{nUj8eawW2j>o!hs(cMw`F)3EUJ=k|Bf^z!-{pR}t`y{4fo z^Bv2BiaS+oc<+Rnxsm^O>YMn`QvKZ-VTk0>u}!sb_Fe){>=|FrK6>%>V#(Y9w!@w_ zDSlCjH^L2*Or2zaC>@u>fjHb6^B#i z11rdTrwH^X`rnNPZDDJ%eBOShE`n2C&MVSr9{-r@Jyd+~zy5l_DF0_>j~Tw9i8(&J zJny$sK7Ga*HDY5qFl|*R+gVhB3(9e~yv?b}w2qaB*+1)_kH?&y8i)#=Nn@{!hAG5= zRKDXPY%P~$v1_QK_`UUxdi&=CaHF(HK{1XKT1$-CVZwKgN2Cam8-=2Ml*bYN9gWJV zWNQG_kHH!~^NW(zdqJI-%|+q`FEX_hSX-5M4*&zW+GJ6J1RJ+0lI-?#EpmuLXZjm^ z1I0rXjoHVVKGp%pObv{D%h=!+3O^?AoVZCs<-;b>nE0((2*fuzDVD>U{iB1u?+{od z)abCW>qd3dlP;N7r133GtX)fREW5;Hdx8E}R3~_@pjjZAcK!>sCk3L|gBoGrL!dLJ zcqNI7e??yOCFij#qL~-DP97EL2Hox{dSjFH8+5hpTNN($@29Fh4C26yQmyB`{Sd_N z{Wi^}<*^P?)OgD~_~^4jiP=sU1v5C8wo>r$kUlOd%Iw&J(5l$h64>mPgjSTnP{ee8 zX}(ly`E!Nl0B9S79aZrAERhsSY1Vzx-XGuvA}5h#x7-Q2G<`V4s##$d@d;)$|zQTGdqtRVfP2b4W_20Z11zO z=Bg^YT~W(qlXb8%GInKVQQhtG zlwjsbN`k3zL)3Oyn9+N)j2h&)d8!;u=$4vVgY@cpW6;mz5XlgJ_R38OE88}F%Q00~ z9tUpE8{B#R*>fS_Foia?e(u%SH=LJzeYOi5LEWrLkcgF`o0;6vW#GQA zyGc-t-fvVKW3{{aJIW~%z5?wDSL#U0C#mdB4xme-a`Ny+ODEO{XytFIc<;bFj5?@T z&=0kt)w3eJae)E-0~&2UCJYobVQz4t=;XL~{JfDNS2`OXRAqH%d>PI&(2Hv(}X zBs*3~f~n+NsDnEHKq^G0LjW9}-`Jzx4utuiYGXGv9ePL(+eg5ce z$omGyIC#>!BL@G&ei=Qwb^MCT&xf?d@1j9umdUQ}6}x>O9nRxh95;N`t6m#~g(Z0_ z7TWskUzL}- zifZdw*2+*Tndusran1NW3h>NT(78Uh>kZP+<#=L(+Vj}w|4^N}1i3A*L_7H$sKEK1 z!?awZ9XE%Z;c-&ZVuwu_V7qw(^;GASaY!Z#M{rF9HR^{Hh;7&DA%KWD#oQwRw5ztm z5Rt0_XLx>PZ30?&;_(D9@M~^AlGA76=$n?uEdq>YExO9t0|4uE6me8dY7fWoajKW;9dCHzCEf6l2hC+bIUExRl2xtv_RWJ@xNA#o2GSv>ik zNX_2gM)KZ|J18$-AeD#R_l997Pp9m9E+cY2#*VO($E_b@PdfgWPU65w)}L}JU+*Ft z8?ertSpCa&I|b?IJhKHG>FqLNrjeh1&8whUKSi^4+0eDvVSkh#e1oZ|riQ6#JiK9JglYD>Rj$;3*;XfS{>ohPk zZ?5@1ey?UM0+1i&S)wwo;`eJ|e z@TRU5LE&|0c+uG-S(rAgv9c!S&UuC0(~gcRD48*xp%;km0#Efc3q-=aYtexbh>Ln= z@bs(Z_aW6OzKGEVGxz|ID-LPM*T%$n@uqtUt;pA;%_V9ywqu96|28^(c_99eu_GGO zga7q*1WdWGzda=U^NAXa=6l^eQ2(U`6-E02GLVI-WhoD9YK(rmsLQYJN;w{qrz$); za7$o;k|&;Vf~b>A+DQ%NFhN$~}Fy!AKiKz+VPM``^*oX2BoanmhDDWrLQO@2DXnL!rOr7AWv;PNfSPoxuPlz2#A?P{ZQ zD5^c6;0Zz|<2#9Yy)Ai>cns7H5p`4yNRRb^MfZ|s@AX)@UW#A@;hGv=22pYg2%;RW#U#k?s?$gaI6&I;pUN4@U z{Q&^O-%b~oD0GLnwY=1q3=DH#3@}a~lA8Zwx2w=iK_TB=Jd|TB1v#U@Z}}WH?t)2J z^iXQ`1xB`9w@fp5;ZvAa8!aKkhbyII@n4r?M+8*Y?Z)K?IGI-+&rZ$`vKjw-Lc9E7 zv7sg9xS(8G8vz1lYk^=Vhj0%JjoS@re$vLZV;B>93k)!mXl1%D(t~a8U55QNyom?= zEDG@AMG2rRv>{L|)q8}lI9(WN-Ig!lPKQg+A~BV@zW<~ZP2s_tf%xT-{l|$9Wh!TC z{2XLiHS3DW&Q{x9-%Ws3G`=rCz9NL;2^SqULfkns=BJ8Fe1&x+PEYZ2+HeV3+pSYs z8Z2T@_qW2p7-SkbpZ#$NA%Az@MrqB3fzdC*z?Okw;8d(ob9l1@;m26Ir6kkb9VdsL z`oVvXA+V+2iBYhgJwiFhfGpi9wN_{$o0llnFnK+exfqb$Q%yl8M=$cOPqSm+Nh}My z$Zv3>Kwh5>Mp>T8!OG7c)r=tckp4>c5^?j~XEPeg^6;pWnh6lLdWVxU{CRJ|TfrDx zbkqmt=%e6aUgm45&Wjqcw-_)WFTyM~#Iu24COQT+kJ+C(5g~`TjK2vZ{jp=DxEca=a~{} z_PgJRdh9w$1ZoE)!Rl$X?gl(-n&Kbv{{i;*p8d*Oon7&K4TF%p=v)7NDNeTpZ$d** z+;#vB&D6sZP9?7`87DdsKEBZvN6!QwJO}yQnZ+~jF>3nCksaK}TKq=4C&km}@f;~r z5?!m8rpCt7c7Eb-S)_Pthd7_@ZYCWh53*Vg^c3FkPiPM-!9?%Oq}R(@6|x8_D&wp} zE-})C_I&7zwmd5{$MIcXfnUyFdk>|-^aeAY+o&I`BAgRj3eL_z@MO{M5Ja!%Tjv|C z7T;nHx8neLI@ULF`%KD2q~YmubV_uiaUYain7oM4!{fW<-mz50=_|<*QHE!BE6Hl? zycJnXcMVyK&00&w!0?z@^fSqv8NtqzsQonUnlkA&E;8w7l07~`>Gi}D)oDHd3*48X za^*SLN$2n?LtnCch6A~O8X$=M^@BaB12npJ(fHv zvKKHQt|yQIZNbJNhcflOjg8<4-lK1T}>OHMw&wRaLRVf@cS-vuN|0J|Xu6kVO9shw0y)!J>l9lex4S z5=!tO`v%t>1l3c9+ChKw3*CbQk{G-%BCgMU}9oX{?W3tAemxs7IU1&*0QNss8C_*eP0YO z;{b=&qr5#^i5VYD@elR zW2Mmi^x+uE>*w_Sr@rKnvp+V9(`xkWf2@Jv`9;3pt8B+ZUH7!z7l?CC>|^~Vn!N0z z$?&XQ#Y4uCBded#*4Mcsgo=mFiHVvm8}xgZaRNtjq>=D;&u^`^6X2CSBhHb?W8PO& z+xC6}4h>FA7nvnXdBvM;71XlJiaA{7>mA1F>wj>k=kedZZ-Q6Tz$BZ`@`oHf%(APv z*2qEBA8f5w86$gPmKz83Fw&hRRsRFvQFYx=hiSQ1w3tdmq8q**NWh{`^AE|Ac{KLdY0Ge$X0Cw z2VN4K{+@yiFsk5w+&GKu0}>4kYBbioGMn@I#~PLwRey(za1;tE@-UqHyl+Pd@BR5r zR@CuH^U5Vhi9XWUyNL3mZnRdbPkD>^Hi;IF|Mv`@vOncRw9~~L9+Eo7{4FoPQU^el zBiyVJyVH5YnfZD6fOGA!V(QRO$>xzi*9zpUcly|VR*+1y6oe%?#?HI%ch*)JzO`9X zTYKSl>i@m~KPMODVp0?phaZf;P=yhjmlRkPR^WkhieORB;Gx>q7pkd`QAQpy6v{vq zM*zJvgn5eMplbPmj>U?+=oukIh)V*R%8@vYcP65QqKY%&P&SoO~)I&~||Dd`Fe3d69=ga5qqK!i%=42mCWa zd&g1ec7xQ7PKVO}7Jkn(mhF?*yl4r~F8>>?UlOu55b$tc`l+@w?xVN|0)R!6G(OG` zOu&hr>j##c8M@owN5=2C|7wWuP&VnV#g1(oMTT)P!nKcCcK+>tm$yDIUx z)^s`mg<*i-5rsqM3A-FTu__REfjc)4x2XIV8fuy42bc%2KsIuA1H!h8(B7Cr*VEMW zQsxC11#&EgrSpWil8YAlP*xHgufTwAoK#`!o2au<4qZu~H$r?#txZzRAC5y0`+0RC<=)-lI9(GB1yVA^>3-fSvi2?4i6~pr8r|4hjR;ezD{2V zC1RVSbjg2YM6aOSm${AHZys;m*xGXyzkE$v%ARfBv-pSE&{f5N-}5SOuq8K8H^B=- zeITjF7{;%&D8XvE*)QzRrb6-1lEIQ4*c@%~;tO@kms}>OU{a2yPPpyx7tIf{HeV<7R z6VLZ?ddX*pn;oDQUcYp!73B}Q_MavvvKCLyYeIVb=D`cS8BJH7N3v250LkHT)8g?x zv>{mI=rjZv>zthcPX;N9evI#N)u-zt-~L{%xT8C!4h9e?Kx z=|C(;`$IqT0IC9{(}zW_6<^2S3r6?YPJ=wBMB`sIC3U>ZgwJ229!iEz?iqyZE8*$$ zznDU~V)*Qy5N>tGa|j)VHcUxt&2Jh@kIsv|KfKty$<`)u`C&jh;?kd#mkF@p{bw88 zUw(h$ol9iZ=*#j@F5K`q#TmyFcTF32J#lrsbW}6ZLpSNRD=8#l>6(<4+GOwm)~THA{KDvaoZ%m8v~OmfqiS&6eWv|br|)s7O(n6- zVTiu$2&(UK>Bl&1q4ofyZ#l$`e;2*I8z}iTxCWvM3f>?EobL>fExc-tf3g38bn#ll zjCk+Nl4^KPd!1yw+Ws@oI52F&2{}oL!=~{J*Ls$irEFBf4z4NmXUNEYEYcN@*B{-T@VxK)rui9eBrn1ff=pbj>6~q>vr)(S| zyx(71O~>e6UQ1f61X~PgpKD`-Og$Pi4V10X!P7mX@}i~g-?FASccX?k{~fwW<#FiU zGKS%eV1Xn{gV}4kR43*Kes#T2?TfMN-HPHh)=JyJ=p2e$7>If~Jfs~oI#S0wD19%3 zF|x6&kFc`JHU^16djXR|&mr(E@WHBtQmrn{#FNT%#>$QmE^VL!Aun)|n>P*lP-Urx zaOgIsVkhCF{C1?VAd&|d0%PIV;T&i^P?26;Jq_?vcWIJxF<0ZG}VyUm{=<$0FTFKV86n+jx zHj6-d@B1hg6roPO*0<+oUA<=i^EAX9Zqp>oc~qXr|I(z#4xtE)mE@fq8XBJfumy>G zV)61pCY)RZvAx4;v8DL$0@81XZ7Io##1@2zNm|H2j&9*VEWfKUCGo(F9$sTTpPdQi z_zU?ozpt4TUU@_MWR`^qm>~RDX$7nUfM5eOlU81lIaQ>gSjpJRLIJf;6^MyaQV-oT z_U}6xDpy}z^|;c%1J%a}Gyt``%otc|YT4zyAvp|u#K3^WZ?Ct3-ziKc&tACy!-K(Q z0~&TBG)Bbh?_qK04rbqFwyET2`;S2c5F!aBNBkWNwX-0qfH>BkRCo#zK+^uEC~7K; zz@6OP@v0Ye6K(m)yl1=%iCA3Jmh`f2s7_H=pLK5avc)1?a}Rz06g$<;8mr{=!&bI^ z^2W1t_5R~Jmr=*#^%sa${7u+~?8`J5#)4D_mqFdyd)?Z|6=T;Sx8iLItM>ioKQ7>Z zmt>WxKC38WjP3+yzA1cE0zi%r2#Y*cu*yY5m$6VoKfr>7R%{Lyh$rMlh0!p=46avU z;19%{=I|V$?g20vZZ=?wveZOQW*!y=+9Og58$|7E6HL^ADul7t3eYHObJq*UihBMr;CrSHi z&?K?fn4V{qx;fsP+bcGO z(*|ha;pB+pYU3$3yuf**dsU1kKS+?NX1EH=XsohxfxD7CCwYqw&4^cNP}nkCQEHh(3@# z)I1a1pQv%TRgxaK$-4(ArCCxbK39rrR08n$#Q>-8H-_QtXR6KxkE#3Z{d2pv4m$KI zYU2X&7#qcJaU8~;%(OdiJbrg(k`&FQR(4tPD9t=SIX3?7h%Rt4$Mw6Vm@4%=eZ>bNd5=(t%V`l0KfW;v;5tV~-T#5WNbtbc&_g zVMh4wK5vof^Cg^`H8AQL6S7IYI#kmIFYrqPj!Z7e!6^>p5|4}gv|f9hPyc8Uv#)F7 zk_{)5p;Z$ot5VW0uUnq=W{lZ);$TW`DY^g4_tm4nDT>!PqnLKXO}scSc8v(>F?9#pOe9)mRMZ=ia9{G;d+88HsO`*oR#yho?C|6Ye{1 zW7&X1gd-H6rn0V7Y#4_)JF)6!=H-K`uSj$^rNgy-y9>{+$Bi9hX6`FZH$LM8p7{VK z)1Ny}MH7h7H(WT_M+x_tFsQO>qompR{;FxHn)?5$tg4N$q@I~^=F??3A^9TbW%G&s zx#1gzlO0_lZG9aK{z^S}lRxw%DL+JD?j}Xna^WPtJ!D1q!=$8tVhPhx(W3uaDu<&><*xB)L(+O<7j z0hB;R&REO`6>Du`LJj*NSPdz?|2fU`6TX`$h zK;Q@PudwfpIbQ&}CeV3uz+3&4ujj`vdq1;_(t*`A_Ywxu;>~I@H%#Zc;Od~wNYme{ zM&D(;_iw+c>S@j~j-SQAlPER;(hDPE{bBj|@Q{&{hFA@klrR&(8ny_$iS+kX>rHE2 ztooBr_3ydnXYWXbZQa&WP+My_dvB*|GNt)u882~)Zq)ebK&^AF0x3P@T*OM>)*ONT z#;WrS7TPuRnfE;)x(JC&42=U-S;g9%K0&+zn8R>@IdoM*ajh?9M*MLPhK97ENK9hf zk7kJz&Zboyj6AMuGBgG@n(ocN1b`Upc!eLLxBio{8x5-;pp33B{sDzr#t=`Ly>GH! z(+v}1HR{dZCa>>b1uEgi0`@;qrdjVNFQc&yc-2p79gxwIjW+P$o4$$pPf!H5=0(Ke z+`ulGTM7S_<^*=DSWs2Xv20kUaeBy*#PpI3Ib_3<9J0ziJ$rK~iF(|G@%4t@P>~`4 zx`~AZ$`CS)b=?qPERp$oc^Y)zNaXbc8nyteuK^E;K0WymfVUoiMjr4qE#R?!0H*B^ zJor@V9@=w*a4_I{0Z=|%fTvg(m^preOjbZ1=9U}1VUwpS6=Z}9?fd@8$gYJHVf`xi zDe8;b_D9S54Yr!KUQhZ0S$@5%Dq#0cGFii0xAxF%f}zt(1vZHZmBUvbl~6bi^*G0B zf$tRN49M4F6HC|jb-5SXuIxBne}K`%7}U2k;z}smYwwF3zvpXe9BuDSHF=j0#2&IA zpB@|wG<0RBZM&bNx4qUh1dtB4Z}Z@2QXMWQ9|r|HE}gCGeZvc*1M9+dc9oUECO6Jr zja}`DR(J_gs{Q3xTbUAFttyrAi70KmPxH%OXvNct^mFLPT$*)jaV3JjpR%X07xAq6 z{i97X&VYw-i=p41Pl^pUVE+*a!sR3^q9DY0npU0lTcb-=;-!0L#VmRfvDI_|@AyP5 z|BcVl%G7rJGb_U=UT2C3ctOfw&D4as)yo5m>}Y2^2PLP{SyTez;_%d?c)FaD%1p4k z+2<;1nKR6f+ZvuUTtBd326sSs<|OaD&qm-0A>gPlZ{K9JI5#TzO`-ZfA*b41zs-jlvl(KOqFvQTm?nDa0F86u_;fzC&Ex zHz!H?P^#>vP3yW!(cp#c8z~Ju+ghCbxR$!Mm6{CGsjpY3ra!!n2LqKnLA-3ieJnXgfr;8CI1W! znGb?K$2Nzj_t_!ht;vg4tpVKDuBrNf?Y=9?#YKew~W2H{;qT9p|JE`xfzy1Gx$R}s@m|u z5x%$9Ao+PFJGg&jW*4zAuX=nwbhRSf9d9qfH7jypPc)cyiJJBD+!Y^)<;D8YaE0oh zC#cyCg%Y(BovaiO*&o!|w+`&5BkWL9bqVCz{qz~JZCq-ql| zjjJfY7r9p2B~geFe$hjU6D+>bb|U6hs0<9)XpNNMsql553|<33*)6pd42T;izLbp) z>6~k86VxsA^(l*H-HJ-H!1zn=T9hXt%)UOK4B<&wY`D7o0&(3;nZ~ZHAni=!lVuQd zYp)MTaQ_HLX_r^$1Wb9WBe56q}%3i3_xpYm`--lv7U-{c!?FJ;^`sbzh`DbMgxbt9h})h%%C#aJ(z6jXMI} z_r^Rw>yCA={^6*t;b$UDMMWKK)+V8x>l!A$UC_Rl4p?W8AIj+cpfxf@sV3=Fot`%Q zDM7Vdf&f{5~SUaXi8tR^qKT1Qf+i((<&UGTu&-?7WL;M(U^Oo5w@(`Cjeu; zdc#KL@oC)EG|K7Hk!gs+>luFoCE2cbf-rSUdxmi8^`hWBk>nA}J)bJ>mDFGL1612I`HKon+(dy90irBb|2j>g5vCxrwvyT3;g@ zpx)37!GWt(mrQb_k=20rNl9e-*YyPnFoL&-?2K8!&ll z`A?)Daku(JYH*IH|$Qt>ScV4r^S8ly}*`?=iQb zbbI2Bjb#Q;P&i$kyVx#)%10U>ZhCvcaw~+AM7vH(0 zTJ6V6OvCVtF}=={RQTl0n*a7N`o;*v!)e5mZ|Qa5|ZIf@XZ{*rvkb9FE-m-H^Yw7KqiDPE74JvyY5(-fZkRO_{CjP6u5nE z`LfiZ6woTCppnSnua-TBSf2jP8qqICfA#Z>)TO918!#^Yhfg&^zG$6#Awl9&6#6&m z_}GP<^>3k?$c77+IA7YTQ)t4N?AD0inYeC`m!H3q5G~TURf|6TjM4LA10VeAY74*b zJOqMeIj~yPUbHFm?TtpeW9CmWa5vGDncXDo5(Ndz>C2eQou-fh(9B7sbL$MQ+HfSl zh29wynPe)~4RfaT4XmL;`dKssoI%XRGo;&MBMR=jUNNaF+&p@;!h}VXC8w0j@Fo$H z=_%85q8H)7l<;?Sdlhl(kY z&Yfcfc5YkV;p4h^PEFG?>QXKtAc3}S3RVmet$72a_*Acf*N7v~oSdwIXX0z%t#n9{ zrpyE}{CweNT!;4Qk1LlU@XqLiiaIHZqlx;`ETiux?Ml+Qso&;rnIAE!OaU0_bsvC_ zd=Vx{rl+g>xL7JT37nF1W$@ZcA_=ti$(189pWW^+T^XM%AKIt(E~-Eq#}dXR=uHuq z&Mw9UI<^end#YO7Ze`m(sXk?2v)MUT-HsG!{*-fV&KE!5Q`HfU*(OLS`k=e}lY7|l zl*SGTC}@qEs7o(-ALp-2qDI~0`lH7pJ3_g~M4HZ?5XR+Rbq}Ci$|dGvNF~X!{|kVq z`H;Hk(=Qq;A7gASns8nni=bjiEQsELIgt_6P<+3(K<^n>*g<39ft0V47kvY}Hqo^an`}6{XCKQZ}dTP$LnHm0E!@SVs zr!sL!p9~;hh5mHQ%uo8nla};-9vsMv{PodWp~<4g>18ALh2|hdAzPa23Py?>XCAhg zPAGPz9AY_See1N6)A7s5!&>Trgr!2aZ*WOyekWM`qu_y=ntsEGG7rtMOxN zFw4_^bAa1_*VAJ$<$0ap&xw)l5?q0I8BA7Il2ff$M1hnu7g zzfF^{)pI!JUEE;#(EL3M9LEfY%HL|fSuyCwN31-WjcXXdYC}m+{(Mz#24=S=rc#0XMVDsq>hWfQSECZsJx9RJ*FVfj(#C+t z&}01hcToc3fwWUc=P+Z(vOt4sj*xq-bpo*C?*0WDYV+Z)KYH*Vl8B_AiO+V#Kx2Tk2fne*+6*XzER}97@G~^NzxyPM;*58EKd`_%s-3u zyDw@==eJgyHWO#wBx@;#H+-5Vo!Kg!{sHE`7vW9mFp%LyNZmazN58q;DkWT7Sl`@k zW&KC{uKdNnEC_o0`yU!ASW~21kEr5tn}H9DSEb!H_pDz>AF#4RRy; z`XZh~_k4+Ys&AvKl=<}qu~Y)&RVw(^hDegMhy`1~rX&EcZiAJ-H1@f6cF(Qp%kCWd z!B~9$TEb5M&W+ekl5*!h+T0asoqH|ynds|ZeKAOcxMvJVfi`W4>m|>yEr1Am{5|1? zWO!F+)`TuZCrkfdsA!3N&NW{a)1~t`f2$?gq| zP;l2T0*I052rNQg#Q9VNsp)xttpG0jRKPK&#yvZ=O;b~KS`gHvmkXdQq@np5GO z5R@_V*ozCI-+L;Tn=gl1(3C#DUcYIVlfIPq-99WeIN$7W`A1&ziKGR$ZD8RZ`FERV zMRFd9XVpsoNTvDGK*pN;2#ZDZGn$ir?gF){%Fz|^+}6mb;B8w3wf#(nAZ>Gkq|rvg zh#H`A^0VWRy4bW|>GUhLsrc)4a(<1MQ;dgZU1pieawYQKX%16SL4hEyaQCHj343Et z+7bAn=EBT*yk8Cx+D0d_0IQx;B-pzuW{TqpK^z0EsZ#8J$;jB8Xd6VzU$DHGX_UW& zqf~wKGoKQ3p21@j(%iTQTmsDtd0w&T`N1hzXVNw_gJ8g<4rVeK)pU0iYDnnNA6&Oz zmj;z=4}kh4Bu-7<8UTJ2jiJY_B1QXo9^Q&l^FFPkvoh8BP=Sm&=S$}#X?!Fd-Q08U z8a)nUW~VN#e~=io4Ki9lBs>_rKe1x>tuFq>PO&~Ht8RPs!BK_vlZP@ zQO5)&jFXpI*b)k*`u$78j}_iIV@ULHd#;$#YEgOeCbw&&qgv{;CVaVdd|f<3dB^qG zRM@85RcgL@1lAwn*J8O8DilhkzpA{EtQzq!3lcKDt9X2Oyv|(aTz)*gM|{Ff>MK1!Ua|bE}M}7R%-vs=HSGrE*+j8I{QgQO+y*VUCf zN^X=p-o93*Ti3{28GlWrSr;XzfJKFJ#F+D=?TqhTGic9)H0DRF%(c6p)=8B;U+Y9m zZ7GenDlOH#SX!MMs3I2Aq4q7_1jw;>z%3cicd1sZZ4%R`p5#S*#KneFfDOt}O#=l6 zK2Suzq>UQ)AXsr9|0iO%xa36*z{}7i2I2h|+|<1iZus>>vk!)bZd!4nIsAznFyLcl z{Gx9~BEOYaT`P!U`YjBKS3Hd)Z%W+O;u9fM)f7A>PY4x7*|zzK0yT-e1Ac<)1j6#b zQJukQ+ua`@Uawd1G3abXv<@zQoLM)+`J{)KCz-hS@{0$hY+}Z&cbQ$+BtiafX;qwXx>>wKr0<~%hN~D(1z^3V!-E$ByAOBPq))octWiGX*;jv~a z8tLdn+Qyzg`xw8#je}pI^4YJVwGPm1sA?l!-DxfA(TTr83$H8$|3lEq%3>?Xi@qlx z;1U+f$rg4_ZUV1mNrYcriF16F2GTUWmUe5FMqv|Ivhs?%{gm?VBh1aLtM zXFYuCGDF}t+^BIT3aDhS>5iCgbK4S$B*h50QgJ@Ywp4L zJu9~DcixR|>4by=sZP93rf!kAZ?1C5qlZeM24u{}P@Py!W}{F|&E#-2I?-lyWdo%H zXwJ5%rlUKj+cgYDpj;F5E`|UvEYd&8>KS&p%k*|qs)=4!_ zRqt{(l&$*29NM^=nA5!`c}iyPm6&K)e%ctg2#*s>Bj&;(yYP9HqgHWM)-pdNpXj{( zbA95NVhnwx^p_w{rPFX#b$*0~`blYRsa}Lm)1k!;$4#e0Hc{_8ZRfU%HdR3VCI(T=D;-m}_*-NS)- zvzksJZ^=Txmv2xt#Nt5}4!KhIRcXWqGsCfG>jd(BLbQ|JQtXK#*2y1f-k1ILb`TED z@r)%O)ONl#nl`|9SSs7&5H0n&y74ZvZ!b{bJ~R4l2CKU1Ui_8=i&%!AX^^m5d#YLQ8H%xDI$YxBSu5w6=5;6+1U zoI_4uY`tQ=6|F~@kb-1n!fln?36_*s>~!|Lo3I#~nhcMt{S}X=d`_j-wP9`YMlibekHJm z!xD^#qD)`Bd}_55wb1(eb*!8VULu`%B0f`^Gau6EAk9g!L~DDnz^J1^2(`F%*Q%u0 z6u-eX3$iK*aT{TKUbOB9KqZM^?u4u+^d~gR)jn$uhqoVnmqX{r{Q(Y)9bMciaW!qU zR9NKyRm6cQFB`xsOXNpRAI=d%V)lo6c=%*Y{`DIZGYf9L(FZ7>Q_6`>2bJHa0fsYK z+Oc?C*y)TA6P<$|tx)ah(}wJy6TYu=#`N0nzI&|ig9p=%d}S=x!IH)N^HbjMGORZ? z*M*K$yY__!m3OKKo;Rlv*jiRaer~PW4EYNl6Kqz?MQik^({en`QpT(RZhZoOXZ^;8 z2=^)L|7h?Tl%D;6r!n2D@B`%-;(^IQ+(h_KTV3fVOg^0F{-ET3I^FbCrs2Hpi)kB2 zn4O+A?bp?6Gp=j`)vnY>&b{Bw*jt(OA>3H?NWKn9?>CNA83at?iKVQMARDhbCfp1> zY5OX^;~%HxR+38=)sng=u$y92#vsdX_J{+&xoL0X{ zGTuX^Dz(txtq%+vWJX3M@>z9`!1zq7fAz-YFwhw*fbC)zjE#PPE?h-N$A^trmj-Tj z+fszoPgxL39H~ini5NT31fMa+daG#+uUA!FHZ@BHA?kC!4>hq@6&*|ip@svuUwiv& zE{JgZ1RkJNU(=7#%-l!KkYNCE@Yr{1boSGTF$wOorklmtFtH@9xM*n|@`9diaZ_-0 z2e>?X*w)SM?E3s?I+VrU|Nl|;-ce0&Ti5UrL_`!sdXpwiq)0$QN027H8IdL;L{NI~ zBGN;zfdEPs5F*l~1!=)KqP$2sGB-+Q0;mh=4Ka2Urx*si_TTx-oWr-tv* z5k(&TMh~7dP_kl&GD!<-##DL<_4G*tO;Y1TMhDtC|w@!-50KoDujN` zy=B|&_Ku=|C$`cZp1t7w+Q~zJ+ut1_p4>K;^7gBwFBRE_0pYEgKbg-@Vu=0^`5BlN zGRYALI}p%rzLy=iQ?P8s1W&6hX9Nknb}}I0!sH~C6~%V+xP$cQVa^0->lO2%PCKij zrs3g6FsHP76UdVRUGRj<{gaU=%a>KMEMi&6xn1wl0-t^@ zeq!Aky0dv^6E^T%B(I9)G1oyZqyNCK^-cdjGf)14tjT#V(6$)3JUs((9Hn%Sr=lmD zndt~~`z@>5v)`f9Cbu_rEjCsWaynnyY1_x2(|h~7f*6rwCdT6#Q^aF<13`UY-g&nh zzvb?3!GOFTVd>J{`pGXB3a*y@lpA6svEoBhUC(oR8GbKOCszmfPP{j9B)ISPwNzr-4Fx`o zq0ko?lsgO0qZNvvjkDTeGMR}MuyZ&C9&=U*;e3!PIC&anU{M_>=hZ4z!UZp0ze&s8dH1kvs9}_IWZ5o*cC~dwgCr^-E^Qph z-pXy7H!$0QCLdN*PRGWmLZ^^izhfN!>Qu7Fy^j3{ovJ^3l`8k{z*|)3*tcSGY@m_9 zslFWg1_<6F9Xlj?JnUuqYomcRbc-UDfIsqv(kYDh;r$eH(>o7Du^;yK92*}6@oEo# znOUr7Rq^1WjV8skK5P_~$de*E77RbAX((DODuwBNnoM&k@)qc);z`;c4 zu}XgAy-lF|MTmQmOv#DViC{G7@oD2NRBExcm2Z7*L~{Q)u-H<+d^Db|-tWhoX?F5D z|Iu&uC9=AG*R%GG$_UQ2_IaBr&xv0H^<8wbfH_2x&5?$|?DGBn=Wit!-|e$=QK+T+ zcI$|Sz5j^cR_9IcL1~UZ;aCzCuY=qYc~8jy>x%U4$23BJaQvm5cBxA92TA)+662qQ zx``DV89nQkJ`#=P%=PYCyDeTRDJqsuSYP+5Fr`{JJ-tyOg59sefP+>V|}dSD*`QB z)iH(FZguW7dJGxsFOiJ)I2-!rX_79 z>ROg*RRfRYqQEAO=10WhazPcVq@a6z=dkmwp06$*QN_FdKPnquNbT1>HSzUMd}}k6 zb2gakmsq*KX_~k`(sp*=w^cjfdH;c)>yYl-?9N|cnNHZP2IOt^kY`>*JImU}-^%Me z(hQOHqn);arDYQ+AKW$fo81$a*c%|nL`c#STl>2T)i*pf`}BA3?k}V9-v0CDQVPsl zr5|pq1jC6YbH@s_KYmDPo)n2W@&T zcQDBn0jJ>!MckP|$@AHRh;U42$Bzat`^b!pPs%>rdIATGt!D-}KUcF_ErA1mK7{LT zn!`}Q3=7Ul);jqOAm6ob2wrg#MC^^t0SV>%io7YVfAsU0>C833b~jpT4_~{6URqaL zDy0IYXhAVQl^*!GW? zf)|SxjiqDmZ_}O-I!%v1Z1luyM$as#tC-Pd9TcgG+(Y~&+827y@E?~nFJ=44?u0$z za*X?-B6Y6-^rZmPOw4sv#!>OtBU8Zh^UKa=4l72hkI8!0w-Nl1EoF?GXlFvS(P_T$ zrS#+BppqN=hX#$mC3u1Nu))vJp|W1^hZ0u;Ud7B{g6mEKlC)Gkq-sOD%Asy6W+*Z2lPFPl)>(FmbKdLs`dmfb7y^<_vC$ft z70(|``^O*rkdkc?FL4J%;S-;wx|0Yz6^X7iAcgXI^nYw^x>262CEJnfPtO== z4W(P822|}F%}YvFP*cYVh$kXh&ez-=w@tcZ(JStB%RqbB@jO@hbndQpulP2d>7zqu=th3n~xu z(!(B3ri1$h@1=MMi7Jl`_rM5E+#$8xG0S`C6j%1fcL2GdK0>^6?h@%dz#v)&iavXn zIg^g}p*_L2Ow(NGFdhczwt1^!?H7}?U+3P4{)4K!zf-NNPwM8b6>TrAeeHR+_P`w} zEwx>5)+qjLkY$Z>%~Ln)3jEsbSk!Dz97%LBkg$!`F=RaU-)IgA103$nVK8cYESN4= zT0nm|^Q9&$(+w-)?d?aV_ypKb@kH-g1ZGZps7p3`U#|I~m3`qy=T!6zlT@;# z)tAyCUNCg~&6xOqJwW0dFSmTnQ;~ag)BdT}%3dk!PJ;c(d%}*hBI5YlT&jB{R)tFz z3rBWVSB9zqq_|?WA8>aeW!17zDp7sW95dD5Js2~4dzn&wDJ$LVx40xKhHd@}i`V&G zd>wKpbR1Od4#{(W-l~Dv@m0hl?reb6ya+JW+JyK4AOGUGB{AE9h57M6V=x5^Y8Ll4 z8Uo%+6FOZPzT}mNV1?Ma(AxH7toupx@0eOvdCvENewhtI$(nBV#-^deVr3(IwfOt} z6=g=U7pXPHSgC%O%K4pTJ;{oq4rSLsmiZM(UR6YHroor*3#Mb{i&8(jo+};PEot}z zk2s`e)=2Rh^TN||r>vn*T>48EM4Xw9=n;WCX#}&^cXun>M=A7ZH>_jSHrH&ql|7%h&%0Ecju4x`Bqsc z_xF=c4$b;CYftm#oA?MqW67Pn!~C4pezVd7WV`*_@+b6m#&>7(lFaQgR`E$S2R|z2 zP3+P(@cRIRUwMgMU>0sH+@4+!{ZZs@LNe1svpx{sR%P2cI-x*b<3M{+s+=HfpmF%5 z^kmaHoNcKTum&!l*zebBHq^>3;*wG%q9N`DHR_`} zaf+I46RMk+f-(Z>2V2$Iu@14aap`zcA~93g7iD3HzlVEWwln`d(Py56WjlZ~+(l|H zx$D}1K&HRG*u`7*K(5LNKH}K!^e+?UZ;ppe&?p6!g%@NT_Xaa_V+QsJ-{Nk|>&D%O z3utp9(DRBPb4pm+IR;f0mT7^Wj$p3BovEU03_k;C7T@0uWhJG1Pw|(u*`>_U`>U^K zt_sXtDR?a)dW&Jhs;)4?TF`<)Bs50dsw0T=m!g^AU`%kqvbAYq`9{{GNPTfv2$(t1pF^9F++?^*co* zXUG(tv+WAgnIq?MM9l$_YEz^D=2>Xg8i~d6y?x00`$LM$Q89Iip+sa#`359ZwVtnc zX-xKM;V$8hR#@@g$)F);nSH^{($Ssn=Q|M)B0W{T=$9qcl#~yYxx%Xq?bRE4Y>SHm zCuE+<_Aoam(_F@3VrD}}dWC&mkI5}TV4)~iP7T}P z2h)5K5quJFhY4xhCDyh>xk1Gq4Fx}vbC}{x_1`3XKNBoze%||HNWPCeGf!j0I_%n; z*6|N9yLzNBgKcu4f4IWn4u!r+&{dI5T*GV~-B^ly8&%7?+~ek5MtVB>`;7ZIA^P+{N};VB?(mzhh036 zneP9MFkD9$yvXaShxOh|*?%F{-|Cfu+7+QB&v~{WL@FGGu*&PaYqcH$>S~m+;?EzS zmF||AXl5cL;hKd{47DK$CsR20c#od9YS#=Zvc zV|y_@VOM=d*PCLZZya6CUVr(M(SbPPbT;e5n;fC}Nsm=15qjy%q>1Tp>?6%;h>0s{ z^+do$d~a9u#=ySc@s|>d2WDoz*0m$b%o}{m?sR!Y?)3Q(pn_q?ISunBGZv3nFoTt= zC$7vpbpXHOK?66xzlxr4qpO~nXP$S`W65#;JJp`+2Z;_a|3h?qy85_d_>zs*!!vDe zN;Tas(l9zulB=+uq*Jvb9d!R*8Cgrp+I1A_n96xZ3&4d)bc*wRv~hiUXZ1wND@kHx zmZM*0>D`WocTQfjX2O#`Uo;r={VB2YJPdf{Xr2LK-)(^Py;UiSUCk--xF}Q0xaW9#TQmdrt37+HO|d0rUC|W59z{w_@1jgwkv17VPso( zq#dCz;I~Hk%(TBgsWY)R>C3>$cx)%97JFivH&MZalVRyo$c53o(@wlrm>QBb*~nN) zK+KuBdcuRysT$qfj*B`atDb0C;-D9GCvODI$uchO^FYpt~q_!`vX z5?PU!R*%BwU+>}=Bc^eAt$ucQ85WYp%SUVse??ZydLe{Wj12_hk>Y$i=r$$xk+X zSm|zjyn9-Lon>yU3t=J;0vQV=TZCLRrpsQJR=+qBI-m0Q6e?e1yEu?1pQOJqy6g33 zT;XE7zRvcL6n4>X0=LsrN)kK4s6(vs`1tzkdlF+4;Y*Xj(+Tj3wZ@Q2{ApTqGUav4 z8spKZn^oNN4&Kc&%0DyLTz}MgUO`T;?ReDr0Re2_zZPM3A?tqtIrv`Pnf+jYcRzSo zlVN9PXTwT!Xfr`gM#?pVw~)d4NI}6Ukzedl0Ru~|vWaZH4tRHm@cZu42maKIPTR3y z?(x?(9Z*c??(lKV>tNiZWF%?*t?u%bt?M92omaS_TRKym;X?t;#rKR+rKz_s_fZ{W z3sPv?pYi`l80ZW5S1TOv{m$Ny#@lP_x6ql5pc6vIQkK0w7DRBU^F)>I-bxL)-XEn1 zFOh26?V+$r0X8mIY`X`3p}Qw3tmy45pTWX#I#xwdLf)Q|&UPa|7a^oal8;(0lm*&l zPR#@5-<&UUn||}%jTaU#dKy&_Zy~>(?_z#o=5rVyod1x&tN+sWMCjWsrOL*Z?e0}J z((#v;j87^X(T_%`LHR}q`5kvL-@~fkapwnkOBQ}>mxj}9&iee5HD$Hqfs6B1Wi?^? zJ8E8s`03L(s>6ME)EH*(s0s5n6rMY%dY0ci!UMJ^MO^i#e3sW*(Sjkc8-xun0YclJ zU0KuNF!aHV{TWw2jnMGOz}k1=y;0}D7-KE;$E(Dx3w&{iqYr%?ngmKbIs1*Tmtxzh zntA;zpxf8JAI$XmlUOffcQ@J0hOs3Apc`y;68_GBQlhK(^^)u6wq)|&q>gs7oZ_BY z;u+KQPsnhEj3qsMU7_l-i7XWoIY;?|KnQS(!X}GY4H1;4$~WLJj~$HqiIbk z{F+#xHkNWbEXL8s2<|4;XqxdYK z=;X6zstk4%P-B30*%ra#tD3-UAnzG2Y`G{ns1op#%+jJ$E(>UJnX`}jo~1WVj(5KY)7v#luJgMP&r3O15SZTS zH@5se$7OI9aqvZLYMLiuk9Cg8ZaRzi&2-+{ib9LH6Ey{Nix$~_)51V9(t=x4s9)sV z5fm+!X_Zu9_=E`-(Iz9;!pkV#xTyf(&f5uVK=P!u#Yqq77lm{-+YCTkd5QCE1QVO=IKtM5NSsrirkju?o$jE}Y>;Rnu zX!hke?!Ki{=C?-lWX6hUj&!5E;~if~BATMBkk$x3e;61L@wB^zJ&k%mb&)YL6pQHwzNVW}a2I}u znE5=0yMLC6o_;jr(=_#Nh;b3j`52-+3a@7Tz8FBhe$*}=*iffsx*g&! zu4p~(M{hP}d|z)!Y@%p+oj9EB ziT_WgF>cWgn}Zo@Ow&@`yb&Sd20fgbz8;~JG{oYEHizu_#WgKUQ8%5ASCYZaeSbh< zWR7`&;)AD4N|`a^+*dlv6Jqi4B{qzVb3puVqH)sZU?y_5=z*%4;tzfJM4;GF;SNmC z!`D*oc$wi!0l>6tW!vM8hEYWEb zl5gnRT(RLdU-TQ3**QOa_v}4nK+01I?S8BNfnHKWyr7-|v!7v}>HS^O9;B3jmYL~c zg}UC!3)%jCTILxS=MzoGV+%+Y;=9ZYJ+D6#4^Rv|3z3Qi_ zRn#W_8CAWSQ9Bq5g0m`3zJGG*`OgO)wYTjs^isALl8#LTA-4;g%Ia#Wl4)&DJitXI z$-{uIhN~Xl!`D&J{`opCa>uu>V2jB}1<1n4_pSG=tC!%gbf~RNqEw3@5UgovM!m%A& zSAFOk5ih6iLvP)c>U^az-j-~pnd4O}X}p~)O`9_J!c4&5_g&HV-ARqYPP3`(bzf+< zklU34Q(Qq$s)_G-IS$D&$an0CRA;eODikwq=zlH|p^vPgzd4Ln=0q4Ef^=nj8KAv>Ydgzp>LgFkX9quyQjq_Q%S);r5%r038l6 z6)~lQ3c|{0(W9i;)gZ^e{V}7MrVb1S8pj;2KgvTy3K@7x>Op>j^JU)&l(T)W0GZIj z^sZrHRS&Upk|1g&cy&R#;pXr&)<=lfm6~)=)*#0^RqXY*bliYEvaVvG@w&GdJ@l$! zpZsCNr1A4E@CC53bAK}#Zj&2A5Dj58E!dq4< zVzdOKjo-xR+!}e=kj?27-A-4Xq&&%@#hNqI<0&yD#2<=a{d=oA&kp8qjke6GXDKmY z8nmWp4QHrgflW(vxDLlH)J0WcpjMXB{fj{ww(l40YJxtrqJ6VrUBb3I+P2hc z#};MM*1<{KJMhvYI1hB}V1x+cbG&ST(RJPG5doemwfY_e|AkyanY)<}zYfR$*x*?D z4;vhF^&uXSBB1uINm_BsK}$_hH@MzLZ3{_7@)sp}X)0taS`bjuVZ%5$0Lrh~N~zV| zaJ6gVYO_Mn))cUK%czYu2Qx!#NvusgYHnyVQK=6SmT5OxKJFQh^a@XxUksqC54pvd zD_nh%usgWPT(vpqcDOuEwP~PW(gY!ojzvCtVNHOF+xrRem%L%-xefX1G$~yDm1@#1 zK|TZVAetkAc3-zja&`0A#4m)eD@X*h*3sos?59EXn9Kbu)Emo@AFol{X1rZ6y$5QY z#TcVbC!XPq0}Mpnr6K7T3re3;Qr${3dQunq$IC4tu^s$9nTbo@Ph&c$^*2O@qS)j4 z1QfW$#6#8ByEgzvJuJc^1lS%BK)l?y*x*WY`#Ts6B_@BR;7jC|9!ovfeS6Dgf~z@G zfL40qbrlf5x_e&Y5_Bx(^S2;+Wh2>}Dz}QeKgikwhu4RGSIgc#HF=*oW?mIL|J59E z!Dfv}(#p9XVCSc?yX=l=!t09CmT(isEm?@s`X&02ViV}7YR*~csDm3WoPgcRQDQ9B z>DW_Bc8w7#^oHMW0MW{Hg&UL@$vLSpwq?d9$5L+-Gt-+dyPXh-li9Ze(qteeCvW2^ z?GdbzCE`PB_E)RT;8pD%oChpClQt@DfwU^-^yBw7(1ax#5Zn8hXReB;%5R zyYTUD-Q0R2cYk^fU`LVsmXQcc5>;@L3&sOiBywHvvFV}!* zv#oiNQ}>~ozr@fybz|5e|0FLF=85cXc5 z`LfPCqp(VQXR*H>?|)Mz8i-72Jo-2A8xk-A&Q-HSoY)IvjNHLNeJ6k?43{<2Bm?>= zJjW4jZ^=X?T%9g?JLbj(1-kmA3s@H<2jXm_1J;%d(9A`qbCE0AclK5*kZ zh*Fd!2XQa?4;{9nGGW{zv}7;7)6^2NJk4b3to1H)r9g`g3Jf;43${)3 zz=vhVe)ABcMDHj&UFwV;1NV$+o>fm4G`-;DS9;%9X_en@!zW75+UILHgfKrd($QOh zn*Y4F5JsLE2HL(ZEDnp{AlmDGBy$~WyrRZH{Eurh%>UA^V~$f>DxU8#7i1ihFxWo- zlG;^+k+Np4IEd!8Q>{6|LWu0UDBujLgPZh)gNd8p8bw0 z_OzJ+Jqy@!73d|(&58cleJ4g3O7!2(ZZz1)86oqW(yI#E!Wb4*_W&vEn!lQ zn#S&34a|_s7_P11K3LVMoOsB$;gQr%AqpMHyz=-bO51*GyM;39nF8Q75p)SfjLjTWa~~_?=9!f0CP=I za7#g&fP*~#wxyG@fa6$@8lr>%ZrpVnO4ra%f1)g|cgBgS!?Q!fZaM(Ls@RMuUc3WE z-c7>H3&*44if8%YIYl*h-w*0{8DZ#$7sPlEMm^08pXyyMm8fcMSmI8q6psZZ0h zTSRA5f4rl2tA!jDo_1uf&e&HacPqse2W3{zn;4v_JAS#Jyb)z*aQC>G%b-WHdO3Ia z(GBNb=9pZ;A-kc0`fnOW&jQ~M?A{>gD|NpeV?z<+b}@Gq)Ps6*5^z^K(m!8}mDSvs zDgB9DVG&TciZ-ZL(){<^{@%~`F~HPR=7J2N6SA+X_}Z#Z-ZpP~0(@oz9eaCSW9?&`5@39p&6ZV)m!{vlPLf9eU( z+o|@;lRk9f&0p~Id@NSEIsj@(%)Aiw-*qsM;jfJ*S8v4%Rfjhnzc3A zsi^avT^qi0Li2{`pBy+{{(}SORuWo#tm@mZww+jrhvmf|$YtL|2ze$hXxq8m-n=qx zSaWZ)W<=c7O!y~b@QH_oIjw!FSEAVP{$^6i-Kp@0jz5yC_UbyvNxeN)p=VEp@4*3C zaLvwzru{U8YaWIdp+X%e19utX9s)!gRV7wRj9hD>G0W1y`n^0G>K}7X$eIlkZ}4gS zG^9n(d_8^yersdZdVhFhuSl!Uu;hU~IAzwg-}8E?WXjrR{{oSgsawW4HCc$1XOTkR zww|)#ZWG~TZHDT{oGCu{<hmDBf_>>CQ~gcSTNCFXIb~9zmFWU9*KY)do}|A?tnf zY3cj1d`m%Emj?RG!NXU-#ncXR)>n zE_`P7PD$myct>|BPg!@BFdw>Q&dRFG-eSM8okc;n5lB2bPJHP$x3Jd%ZIqTw)!>|8 z%lw5368W2t3uDR^1zmYw0b3F%--uhXW~1iX6SL>gd8f40JLrdfs34!>OH?lxxywm` z?zjU$&vY1e+fv0HCmh>U?eiJ%SHA&ml)jSOoQ&OMiFwaHAEgnI{xvOJ0%4>=HB{EkrbYS2Z|ZlwLj>jg&tpZO7$3t$bs}SY-{Bi0Psx zV@)m^J-Y#;9JfaBDhO0TuL2W>{&w8`DJ6jrN5`4_PSw>)^G=+luLVN>afj~To#TNb z*1L9`PWHBC=UuvdN%X+5IpVvUFb2-kzy20l<3*Q`ill!@V<8Iz1sV&`m?zadvT99v z4p^WR(v>AwsxWdZa1COT)EMwrfy|zE@ag>AS<_r9VNO)XZd>H{8gSL`dGg4g6d+3` zjen3+;B8qd%HqAXFT4nW(Z4F6hPI?PF>L4O1DJ)-*XOvJwZ}b4Q-KWa9a~m-qWUP< zP5WQ7``COr@VwJilZ;P%g3Z+X^Iz=*5}q+zV%zN7Z`jqPd1&Y#wfk;QijpF-^4>PU z?4uIe6MB#nMK$~K1?nbVJ`vwG1{7Z#5Z`#6+BDiFyf(72((f{iL}>NXLNYZ#-Q5cy zi>}^ralNGhBFx03Oow4G%ZW*9B3H}QbjJKU%MC^GlhVAcC(Z#Gg8$^E!2LVsKe#C{ zd|Ht2@Qh*s_wp9?Iu_RKAZqMlD36c7RBQLQS?7C;PcC|Dn)zgaTy8m{*GUm!ZeKST z<{xz7#eq1rI76&?u|Sba_~GzJ?X($S3&Qsiz9{nOOxM0$7G zJ2-V`+GBBNTf710yZ2uOXIqq*bLEGsF|hZnef&wl;?duJMRx~+J^Jm|+1s>sojZdg z5H<@0`RffWBzjxJ$iu1+ZY7!h{D6LQ)?(s>~pppw>xO)dEac(Cv=0{CV#V8pSo|8nZ&GMXmd~tfc#wU5l@gLVN*M4 zGeu+PvIQ|2>!p`SYIAVJ?r@pd`4UOl$9~W8CZDQPcYpRX6W5OEl&jnJ5N7s+{6Vcd zg6nXC)(W3_7Wo=%re%NK3?!zRl1Nh7U8Y%2h-Q-QqO#PQ-v-4NcQ(^LD(LjjELu+= z;pzT$ZLqmb>@CAil{IPhml(3>EcyGVipkyIL@JtpyxjzYB4x(IZ&?3`H}n!DyL>`d z7|mW*j!a#%+Q`((@N4}?g&N|2?Gd>L+R8Ken!XkJB_;+_pv_s(mZhE&4N<9mr7k&) zcG1wuL)t)ta{(=YchJ0Z9*|a6-FV;=atn69Xj=;EKI1f(tOMD2^urQ!rraDyCz*5` z=s~pIx*MtHY(&8nyEKnyDDs8yZ|^O=syAbb6ft(SfWJ1CEF6%}5QptV^aGgBngIRK zF8hYH7m$Y5Mm+K$H5D^mm`)N85YxIG4GepunmOS8c;JhUwGOgcW6uECU~$lCXZE|{ zy)|4fLW2&!8!vFAf+x!RQlHwzVmMOBBv@7Wv~xqpOgTuuEcC|GuQDT%;kGJkEm}p( znxGm}IO;%DU!@=K&Yh2dcxC)GQylZI9w{R1t!T8Za`hjV z8YTXv7j%me=~ntA5i<-a+nSKetS&m)IFP=k{$+&~tM4Lgs|Z^PmrU`Sp#rV0zefKi zvyz}u@FgA=bG^(%fYERBk9x`0rC~|V=qIbmBABjO7(KH-8 znl+>zR&ZdFrvVXrdrH{O+lS_e5Y#m+RlWUXS)|S83LT-Ys&}`&JVjBRN*6BL?O>@{ z2k>G$@J|Swe^n3Z4fzK2@r29dr*7ISVV3p0%CzoU?$BDj<}e@(Q!l~c>^q|kx}C$f zfVYB7HbTARVza(*F1~&Km7>u{>0Z|Ow2PkXFV8aMxgh?_K_Vut{l3=6BNae9gqkfr z&71#&ddYzlRKWYyhq7IkEcKF5;p*?S;zo*EAIl)jOlq&4{>NUt#J_igjyo&~J5;J^ z@{2->xAE3uoC_&lJ6ZZ7eo4x~jb-lV^wWx)PzzUYU%~w9@1^HE5%2F4$4m@&87g6% z05A^}zY^Z!-gt4KW)i&TJ=vGvx#8wNaVD4UsW~gFTd`ULl=EgBtY>mwvcHN6`aV4_ z-I^6-jh7sY*&IyX;4(XFx$K8%2VH{eooPb=$lHv1mg(-+D1=Ii%OE+LsdK7S=K1`8 zt!?5w6b}0h?V`Q+{639+&;DBCa558F^F%JyhZ$~+^UUKxjCBse27z22p{E#G?WG*||F0AY(S^7F|r{8aW><6nF~jJa|)QjymHRXsv&7^Zv_ZF8DL^31MF{KaGo9S)mj|| zXHL!|&Tr;#wsdApCJ#GceEP)EqKKX3I84W3H@}ygtT2yD=9w}W-vxccZNiKwYbL_` zcsE`i=TGrzy7$iKHFiLV_J~h)QxzCn{n0EF(yPe$!Dsenh^!#TYCF(ie%v67NT+&f*D?ZuR1#RgWC* z(He@-R@mgc3_6SO19SUdXky`{4w%04>!9Slj8{nkRkOe}FWX?R``Hc|LT@i5$NAsZ zL{~mcy8Zul??{%Y=|4XDuX`TKj}qZUsz7^eIZm~6n5B0XW0qiIyWeSE7r2Ii`xx4p)l?EPVJ4QcNJGpa3Uh`JE-Q?M_ z0{XiR9n|3uHv9T`wPn;lhN}* zBqTzc&>+bU8GZW59MK81hJ$5lzLtt4XG)eD7?zF~-uID)m`IfB@ErbuhI@IJ<$ZQZ zb8`gT7QLw`xGx}p@1yP$#0xs^eHvK5E)n(tEqMo!pgAIhFiikK>A6MU-x$o_wqI_I z1obx_K8Nx0jinT31r`uXz_ls;>m|k-%W)6$80A{%qaG?a!|01*iW*fGuCrs_5tqGs z;Ad!|<-IJ4kGdC`ZPE2LFG6_ZB0-xc9B!;MDF7WTv~pE9U$I}*B!u!WH|o%$DNmm zZuK0SjF%%6yGKsO0LS5Nu`fxY%Aifg2)iTKKG;+CSxgs6&v#xITkfX&+?Gg z7Qw#!-OoMZ;8EqQbLGNnU8+D?^WzUJB24H6^$6IGd9i&~pwkDw$b4Whytc3|_jZ66 zp-~|?OVrUbYL+B|`E5iGB}048mI+0D=A>{Ge(@5qx%MZhp}+%jf=6f!(DALhB|X~5 z=;iYm65N|k;WSS~z*ggo+1T`y_fvLbU3JD^-K-*pOpCk!IOR#>n>5`u{*Es2o#5P% ziVOvYe*z67l?H~7N>yEd$U>wEu@5e;P~^`ir-_nsj+c|#v(N2PH-h7Pgfv@M1JO?< z_(j|Rb@CZ!Ax(;Onx-EfJef8KwkL?Db`~vCwz)t4kEQUxOdR3|<}FJwE{tz>s*V7M zK_JmUzmTQ~O5B^bvs_sl5C;)DYgeX?9jFB9QM{97F;7xpF?rHAiGKIRSGTfgLvsjR zg#=6x0Z$ovYwSk|W4`?_-Z#1xgN1SE(H{VG0aWNGmyPH`bSvx_VP!jBO<+lymxzng z;J+qgd6-7`gU1c zx9U6+mEl{F*3AI(4@~%E*6sg35LaBvJrhRF+Z3o*`y84Mj=aqkc0Etzm;dS-nyV!Z zVeMx+!BNBaU#^9Tn7HrBrJ^j?jZ;5w4sUe8vhCfS@+|Ze5S~k8RM3J?5h`!wSN$s^ z%hu>O=2x@xxhdCd{)fv%`oDLxCy{m)_M_1|i#R$Q689rF@{k4EYC@(bnxxPyQ-0VN+z z%=a9z&Z9CPl!K;bCCAJjG4uK-+CeRF0&Wv1sbhL^dmo3uD2YJzlJ7p=!vSaj!SFkG zxL2^VCO*S&6Pg$$irU-iMn>WbiFF7Jq{7upasX9&N?;7$e_bVHUx&oy&Xs~X?+{1# zTLil2R|)#;Zm6i81p*+qwSVLD)zli;dlARuXijbMr=Oe>NI=5ApI3jiDHJ+!K&vD}~Jefmi@gC-7X&J+R&l~=Le?v3fCYnc(iC8rMFkbVDNKa&WXgD-iGJGOM{ zyTaSdOjh$frUiF&Mt@zw5Kd;#GmhJw>xEi*E~1YIH?)AAWdkxhq(>ECJs?eNybbRp zRo9uw?CNc(HGp57a}C1#yay~mj6tc6bkMn2{?xfC+p%`Kr=RAe>xF$eCR@Q{W&I<& zCzRdgwWvtA8m$(nt9_^W@|>GpXe|c&BfC*W_o}!4Nyh76-gS~WWv)5&W5;H}3Xo-j zfVv#l@Gs2A43CXZG;m`Q;p*IYb$_9zo4#w^BTN=~qjVs5_ISq7%c=!eNnSg+qqD>; zDaaL>ZQKm52Kr`kYz8E|XK64{zSE7R0(Q)_swppotJ(rD)@5cWIk(F;$MpLr_+T;< zaH9HkOQ!Xr`A}2Q`Acp!oSBZGpG0=N@70SRndGEqeui|-wUc0&_lTjadSroQo+Y!1`7Fp#X z>xF!5uDvA`dMcqWofx7W%MRi9}A!-wVJ?c}_#S(~GR{3oTrO zd)&^0dPqB}Yl1()pyc(w2TfoNej&^pCmnvwLP*n@90nr8GVuW!`kFc7w^(eUx$$aA zkmI%OCDGpO4*gqyWBSGNaw$|9!Gn#JxDQ~!W$p10G(1`v;hUf02FT90zC<|IoQa`f zzB*Q^i-(&EhHp^F2zi0+{WtU?^h?Ec}x$qgl41mSY{rb?Axz^J?K1E&l>_>BCMM^WKujNq)zx*}^^RX3<$uEHW8+F5_=hYjue{cUigU~pH78iLwl zZjlHx0C{HTsu zarS7QdO(bGY*a^z^(O*OtrA{`%fDtBzaN{3-7!*OoVv)X3lks~2yKGY)+`F3#WoBE zhkruq2aP`ZiqaZ-jfx*GkL&>r+ddCzpFuAf4cRhcR4T$3V)cMA(kbsWiWE`3`s*H4 z{H*&g!ho8lQ)N(@4#xDmm}h&`-*495@h7I9mQR&!-y)?2TecEuR;&=@o33L&=I+cF zfdUb3ekuKpp2r+`afL2c=!94|{EfvsF`q%uPk^*J2Q}Kc4HZ<`xs;Me=-V`%E>HCuF1 zQ4S>ReK%3P8t&xT4oCc~KXZmUO9hQEi1~c=Q?dX%iNyqd%MvNmul>R;!C>!QX0L1_ z;G*HT9OtgN2U1*h5SVtdF8V$@!8N#OT;k>7l6`wkhE)v-E;ZEiwn?kJc$cPLwK7>I z?E4!8kY0z=+!)lnwS@F&`?#H_NBg_KyKQv-hg5o+zi-K`sYiyZz6m>+uNj9|f;wHGF^hYEM~BXCbMliNpaUwA zV##$`DqEy?KcckUKc*54LvbAWCYS(a@ONGVxtdWhWM3jEOJBNL^4*-3SF&Wc&`Xzw;#w&AD+&fBLm1trFr0aEqjV%wb;d5{j~MRsWFQNQd2Q`Ma;@sVh%ZCJ=)G*I#x_~<+H>ld}2ZF zGVYOO`jZ|UJx?iLHDZeqw&KB0kZ=9BBTWA{U9c*{kh!oap|+}<4xK19#C@+=J;*I5 zd7V>EoufbUBiUFrSXnj9W^NKCgRvt-^T|fU@s7lQEaTtjPt8Fg$aM>;ieZS z^XKZv5vJSG-nmSiURrrKR3My`(o1|e{i+(>lBQ3;(ixQH=1_;+f@Uly)qhU3t6NiGs{nqMW;OD&hr5^ z3R?FXBzKR~w?~_t)pfia`KC{XXcN17?R1&Hwi{@b+fjv2O$e$XnQGNQM@>}~l!L{> zCL0|P%j0D+O#98ih7(!~+_<2S9u%i)CBTw1=R1=HM5E)66HhBf@4SprTAHj&$%%h3 z&ir>7dc`OX%8&|oDuowV8Nt2Q6}gzAs#ejO!nQ;NeW$8WXm$kaUs+mMxnuV!Z#uyyb0Q_j5pZ0d z)S9@@dd2}OI{<)iazipeSBY`F>o@Xe%L|MXYz9x;Us3^T8@O9>Kk4be1=DJh9DkAT zbd)6UOCFA;?e5;OJn_kp8eTSa@1j&TR2bhME&Kh^@whXlLmFtqmhdOD)PFM&KUn4( z?6WxdA~`+5v-|r@I_P&#YR7`@Sc$}Ir;y;-jz}7y4J%&V|KmB(C%lLRHa!3#o2t{Y z$8c)wT#JYanGYG<*xz3`{+}!W{@q}pe=GS(d)whTkDi-i%JTDs0`F{fKpuUST_-P6 zDWwFk>WEnatc4X_2r!K`jXWl@tymIsUrZZ@xVd*lSdOIlN^uW@Q0@LDnF zRTc3BkhP|q&G0UnR+(_^nX?t=IEJ6AZ$J3OU`8lAIY;G}W)o=rwhuf8U`2%v{oRwyfw^(K-2~@}p7=aiZ$}5+Y~MFY=O@7$r=j%jriu4bmF^p(gewii#cNev z7nDl8w)>Pm?*ewQeNzAxy7ygwl#H4g;RQdt=I+zvZXx5k{^!S5grLOw_o4lV$GldC zUjiM41fqbg1S*+;P>^v7?C*5HruS~k`bmU^>d~2ZqGEHpO%A7yV9 z6?gY+i$ZV`9D-Z0U_lx}t;i zw0Q#;*I@yCS;#HT7Ono)jrh>luz7JeK?rzbMi8rDmh_dFprero9!` z8e3NJt|2vXGT_rKCWJa40>~?gM$2DLOC2uKdA1kY z7JIV}$gkm!%sl`eF(klaM;u+C=a2x~U$g2x>eB zY-nD%q#?8LdF2K^?@l|}&J@c@PM`1BIh-!wc-Uv2r~+W@@`uQFntHs<%d`eQkA=tT z>Tk}Rj<}b-XCa@bq@Zmot4G>yS6u1Vqs03MK$%4WFvP2Y%v5ql_y%r&?F zyQ$3oS==Mps6!*pra94lbwj4n{o|#d5Ud`HN$q3&6Lmw%F=t?bv$g3Bu*X7ILIUt} z90QgU&s5{NaDGrK3g^|ABt~X^VfX~bwD{y67@4LkUx)Rkat(=94YSV4k;F8*=d&w4 z%8!B}Ba5HRFD9c)xcM8?_Neaya|@qGdpicP0=>+S;a0Hz0Wg+wYTy&@NLmRuAcxVr zr1ua2YfNMOC2JUBsByst3ym=>OL%S_2NMm?Wwtvz99ojJ5~rA-s8595u8D3Y3U>9D zDw^JzoFl`^U5<>j9{o@NoNVxU=G76mqHVpuFQ5DQS0&*;)5$rMpPQ0%Xx-L4Q|M>z z(+710JySrOc#xIBTvdZjjwvgjB%qmrmgJ>XOtg5WR7?pND5kr%p8kgg!2d6SoHWg0ZH2Hlee9FRo68;QOeP# zQ}cME`s(K+GxZJPvE_eV#|f`R;2yYo-@Bl!XMN^08OuN92oWYe6PjBt@?}`M2%?&% z!TXdTkQ}>j==Cj+Jj`fy3rBDBPPi|;^yDy(i{m#bV`|Knq1E?!iC7OUb4v-&J}cY( zON68>&FFUH^lu;j4>JIRohsPO4c59eDtC+AMqN*9HZaGF-SgGx2 zW8CvnC-Z;f`^_-JeEI2BIY^DiEoM6Nt9`@$D_J@aO9#QlM(va7l14bZb;t0jM}Ks= zU;RFVav!3jbL^Gd@z3$ylEL*;{TepX?$`MWAr2F0+oXuzYi!W$FYW`ad z&UeTnsqM6KH(5(*94m`ZGFwYPexWdoo3bUWtSHR_FXL1FG1-`^Bd@eEAm4N1CL$C8 zeS4$GPbeDErSI%Wv?>+ixVcOzzxo9L$EE?cBZH|#{WaqVyT8WOn*!|Z~{}n+`%)=|qVu{}w^7gGNS-b>=Q+N`P||Sgp;9Ty<_R zXcN&v-&cD3V{_O+5YYZ}$f|&2i0s{XrKb-jSi+*dOBF0scnyR4^yRP@YBie2L;x z;1vsznn9 z_C4iy7OP6kk*C}I5pm%2rfMinv*9TCvUK4<8a5ZCN!ncn7_wSMhW<>906)e!hhxjX zg~6Y8ljkX%)u)ZWfB9oz%hn}~4|&y~KI2uy>@T2@)S;JeG0fg2z9&!r#SkIBZnwGq zd+OauIkli*<_B={`bqJ*a}_0t`n0W?Xe!@AI(i0ryE*zSK?hKWL^;~!1nHm)p8grL zwNISB2mM2Grj53%29-M6U96jbgT!QQ4*c)_>Z8EIt@nh~Vd4|4rH$BZ^$6V^pG&W+HQ9DDE`D4(xh&`wZr>X=Nk|Tp#`Z*q58?=q-JdEdeYW=h6)xl+Lx+b;p<| zP@kx;{+7x7`syd`Vv8QA;?CL{%}~bYi1*3p8=fcXV*|nw4SA*l?}L5$P8C=xsKRjm zlAJuEUeGD457;;gO_%@oos71ptCTJCLOJCZV^0f84TKD=fWCj6gn*f^8--A&YRp2%(T7FcR(3V}rP{|3xOIjIH6t->c#(1n!yL zQFF^tqB09lDAL4gRfZI?ZV~JDV$O;-? z)0y|T48qJy?GipF5DkO> z_YuK`Xwz*Sq4Ku7O^e+`ELcJ~>FK(nY#^4ORlTWAlN5n!k}bfXCQOao1iIHx=1k-s$}UP&AVMpkSy~%daXm* zpg?F@;%okh(VXBDaU084hjpZfc?1fR8Ztk=#Q%jb#zhmQBX^D|H-#UQ@;=$JJ}AHR z?ce0WU-)CcBd#=PUuki%{aN@sv!ZD3F@k#57wsHOJiufcjBi?vrYT3nxg33(h#kc~h@ZAf2vQ1zb8>zvqV&U*Z>MJezV=fm! zU(doK+e|&f-R_wK4i6&nO-z0b8`bdR&4y)gPdGL!oXFADP|#aeC1-g3*xk#`Z)pdG zAPrN_c)g;$Ui?mkASSAEk>${Xf@E~t3y`NpGte0}IG0-5b3GaTv-CW3n}aQ@54Ru& z9XOvFAQ{~;v1RQNHCY75k`lzESmsH&qmqoa(w0CImyc`4fbvV`=Dnakl!q;=W2{dZ z=YN8aU5o=HCyV6O2EZ?*18(V=tdUXIiDi21BG;7!w%)|gWpC^R*2ix=Q_PF;&G(20 zapBn~<`W>kwmdfv7S!)}X?{oJL~ z!86j=F(a##rfEVr3mVXFw}Dm9&z^ELalt$@Zda4Qyfw)&Grm-h(R_1T`ge4_U#@t3 zOk5+rhqr-hPqiRnl-@y&4QwvhD~e$lSViEPtX);yFHAI3a0&ObnO6%KDtbHRZMh{i z?nDBkKo{1e!}P_1A~%<~f{mohyoOByY$7z%^0S$m*H&yLHe;_2Bl{iChk#+=C8}3a zm0NDvMV*WpTS`aEXkm)cr^GGAe};j}L4du<++yz@XMj)Row->9)k`G2cnP#|38eqq z8iQZlpqV;&m4HnI3jp7fJDPu z4+hAmJj~k8|12y}&HXbq&wO21Vyn=?IGV$Y2(U8fI+7~ztps{+YFZDEoykbTDe!_f zMufu);A1T<8O;MTmj7!$Lpv5ZivxiGK-p8d(WJ+noWy?PyBz{G0%8)dnbKnask^W?@NO9q!NAc+N?-S_G!n)Y{41CQfyV*j{S zv3_8Ie>jKN#kQC70*&Y0s<)pXUdjJlaPvpn}>Ll`ulu^(1)AJq)lwkK3j zSv~&4{0;(^&Q@!Zt?zXe_Z`ZP{tr4D7RV}qB?qo1p@U7Y0ZFV#Z$4g>UHkGCmZ+x8 z%$*|qfcwel9uq>%8{pBt)s(}O6UCG>f90TkSEVc5JB^ELe{oFAa@I`mCh&Wc$%{X> zT&n&$(5Oc;S1o~!&QR$_;pz{BI;AP0l=&II!U#%HE44Dm;6j;x6<*Lye z-r@lF;l{cD#8^0T1e6zJd66k4G#81TfAQ?E=#YUVFJ{A*kK1D?a!r`=znvtogtmA_ zLJF!_`mem+M z({D6osspSCn_1olIm!awW!GhR6kflUSGDCzorQwh0R%UL(Pf&IDH7yUc&nPKMxTEu zDbarlIDEBej$SfU{If>j$*B27D>!rIZ}&1U!TR2LAtGir?rp|yKc*YiL4*=*@QFPY ztOVG$H8|LZwO+JP#jW8TKOtV#)WUbYAHSU~yxz0*Q+nRC&hsp&xc`q;s^*mY2Y6qk z)T)TJYT80kpARsGDF-ZE*@B=Hvwh&F!V%Z|XLThm@87`%+QHWoCh#EYl|4X=;X&9f zIG(}xA3h=f=d|*>lq)LZcPe0Zgu2RC1)A`T&mZ{NySCDFFpW8uZed*Suc6fP2l0$Q zjn=4uAAMHmMO^CNO6Lt(Up4zpF0GN_06490(jSl#asxuc`Av25O+eeeIixCtW?K;Y zQ-Ox>W{slT&8U3p{;F7I%Zs|(4OoPRYLxP)>L>`9;+s}zr+nw~O9I52>aE~O4*+w# zk62X%Xi*gocjTBnHebPhMZO)Im)9}NrmpRJl0MpWmF`ngZE>>GCx_Jn>{GDtpkR z#%&6P@39qip7U1v2@Z;>niIDN?+(2^e*WT%4y^!3ggTg}R)>o<*1H0%5!M8WT&_L7 zZcDW%kxe0lc?|%Ls))*j-`$n(7B3+RfS*xmiir|{1pDr>E6qbwe=4E7FucV5N5Kwt z!8K*Jm@W2;`1c)ebPMGd3vs#ll|hDmj+0qqNjzl79f zE;Lk`l{s*RIG|Oxo9q;y_d^6$=4*Ofra*jPFV%3(`j6j8I>(T^<9mw5)vI*JW4A-E z1Hs!oI`n*coU9(-!sbJhA1YruK#b*~J(n0AqFMX3!;g2t_Kd8JUWkxk@Y z_sBZx`v!#@L7GQtxO=QsF(V^LlwkbH8YoX^w_##jz{G*q{fD*GsWkf@h5n^<zI;n3-o8tT>P~O7lpZ;=?(bDdXmHd^-zPd3JPD?zd}A z#f~`5X~(ohV4Wt0ff}7&w$JXgH-?(4Bk*lYDVC^}moFQj!pp%Zqb#e{vW5=7dzT3> zx=trDjGNs})1u$F^xf>_Qd-Y7fAx)*yDWKFc}Va*9$olzy#Qu&v%d0+mJqP6s*Z^L)9 zeA!le=~VxIeeV@Q$F>g37|kAA;E`i9;Pi zO;i~*B0f}3NjgSCM^w=^32s6qR8X7LFTyXVv2+A`s2FVtkC%m78>NTuYg;QuP8;h# z`A-d|ZC1^9G^%kcG-F2|--JOI)I9tIg1L@zx}XaNq6mFUqqSm>;nV7})4vBghH6Vo zy+O@+*kCoOEU;I2T+0GM*{y^&il>*B^i@OeeifB^H@*wX3Ml|b{Gignf zaTIAqOCFD~DYZqRMscCuGBI_N{W=@XN8M6}P7pM)jZjc_agV#RP@z4iQ8_isVPP>f zMSHSsHC4}WG!Z)~_}2B8Ig^oc^fR zs>s0+Sn7Vl_3~`FBZTN#+^_huf>R~`d{&I(f?w4MHA?GvCzDq>9y>&Z{Jd>+}+_)%n# zlI!xA`EJJ3E~X~_?07QDXyG~4+FOx01%gss_&W8|fRpC>a}@hj3PKaFiLB;#kdcJ9 zm))mYxqE1Yk8Hbzhcvsj!`5zo}Ax|t%hOCQnTvOdYniQJZer~81Y3>SJ?jF??QH=Q;HvLaNk8BPq&e!eHJh#QAXU`Ns>wwBJ^B z$~qL&nY8ueIo!mDyE(Ht$UdK=Nz&`Cr#zIiGewN|L>EB@g0%{`q9 z0ccd;=$~)Y|0eIKoqczAgj`OnQguW()^CAxVYv1)ax5&wGA^7{zEJ-jL1`Bja|5w9 zSdKrOyk%D1)xW|haMwy0OfZ~vvkC{~8^k?=nJm2*B;IP1U{lL!rUu%g`6+6+9TrB# zqRZo0bg?nK!8GzzRlxc;Wgd06peFX}+GOb)IM>)W!|v~LD7|adrkPSR_8HRW%i>X` zDeD{LEP_S?^+jJ?oC`oPGV@#C?u~M}0-~zjNAc~-EAGkltnl7Awt-Jo7aO>J9J|0c zwsSxByyH?VIc<$iTjp~PgzB_3JU%s4ySs*~RfMEiQa38==vGP7x`!t2S>6zL6V@Np z3LPRlEGtIBP0b+e=4n*oZPPEF{!QIgRF+G7X^!*9ZH8s{xvr&z>XOgYr77f|VyV5z z3OI{F=9OaZeWpjpZW8cD&VMfk#eN9#ST96!U;iHPyJ<<#fCzS5Jby2^zy4a~<=0o! zLui8cU3BJomg^DR!TIro*JCj zHjaK(=IxIsf=O!B!(a!V)w#+js2JOA>{d1>Ws%e3SIvD&$%_878v$)#Ud=EPKuc&L z@dNn{-1;#h^mItE(5%jaDMmo0^SG`lzpu|IH}$=RTU><~cNEJ!EFPAUcyONMR$ehF z1NV-X&{<5^pxR4=u=3x$6F8_>)ObFBaC)~dg#gBvu&#OR z`E=)a36FFOIqvwu5Tb~uLrqvY{ryyk#@relCN-q7GE0hnjrFO1Z0xRzws+k{bJ;P6P%%jcys`ap!G4{?mYQP2 zx{ko86J+5QUz3!+N_{PTScCe$bjqJ@x6o!i^5Q+X6pD4(_?V5>g(I*=bMpsXp!6j{ z%)$&d#PE9NYw1DOzF0I+ej+uyPlx!Py$7Dlkq+P_-9(jE9No^`L|6|PYe0ewOR~&K zYaL3XC@MbMDZ^>8Yl@dS#fIfpzNPLoZiZzwV$P=un^ucjQGkT_=UQPp>2l$T6=aH| zvP0*sQ_WM2hP;qIqB?9tEiO;~Q{gMNMl0^NWKzFU=8WksSfPS{qv?g|RY5*wa|(qI zlVldM#m;AtZN>u08Z}Ojar2K$b}Y6h40%`-{)u$>gjhoU=^yCGu$cW}ALwAbnyq)2<=?eIwoOw#Bn23Gh;s*VDN+x?l|f*SsEaXvB1I+gct-7c+zT~qf#@lF^9_^XPW`3E-{;ZSHQW1HvyhV$Q2rdM>s-4g zq$E)E4h|mu&YW`06&OofSGqL2PHW~U1qK#uUvsOD3(^mA;4POsnYy#fE2MM%qdzP2 zKYkESovt!$5-HOHUH+k-*$}A4^ss{Fjz7z%*iZRn|f_LQts@l8vQ!eX5hx>A5*j4+aAJTXvv{yt$b!Dbdh7ILAAj1S*&~EH$ISQDb7nc3CNW#kh?^?p|TeA=hc}m}fvBF@cSqqOh~ET_ldW+i1;CvENFf+{ zq*Q=ye_^I6HvzrKI4LLj=E>Y6=J6Bb1@nP3*4yJ7r=-3E@%xG|LE|QK->>ngt*ymE z0Lpn-Lg;CbZ5w#_<_$&6=hl*PT??LD*~_Ao^&IjZmKLZmKmSBpT71gRnH%V?lCpR8 z42^dlQIr+WVe7lMYS@sUxg-!$yAg2ts->aw_stAv|A{|lF2!_1nwDspvf)j;w(`ix z{tUd2Q|f5j>uYClhz-ybPG`2M`V{VCDu_OgLuczxsf&1kzLC6J+QLv_L3 z+wAWJJN%5{WmE(hEd$R|->MRFRykCwu>-kfd=4>fr7tLJLAClNl6>=2z!UZGqBeY5 z+$$O?b7+T^23Q?4RX?7cJ2`Xi#qdX&|0KezVxH(pX|dIY+4kRtI6IiK{u9Qb4vVDJ z1<@~swTJ9a)%mXtpKd=4R>qGjwSbry7$&xr75$*2-$W4c94^&8yQ=+4s_$pJ%bw@Y z*v~e#JxuBrymkmL2aJg=JY9v^-ON&Nmcoitw!`*kzGZT@+lxkk#Gt9KJRL*4GcmgR z4uha2V*Z-ewloo^b5G%>8(L)MYx(%*`ZFP+0h^=F9n0-aXs+s^AH z`Yg*9HeLCNA2@X`HUxjexS`*3x8<>^^p|&>M%CK5$#vHk)x_CV#fvQPJuA6QuE3IP zB$1q=Q$5N;rIJr;#`5X)RE{N1NbLi#&}QZ658lH8T<&d&)bzv|uEsjmGgjT6a2Dbn z#8N3=5{oKZr9+*|Q$q)j_^_XcRBBt;K19oB%7-Ftl+S4o)*_3Its5U8G@in^$|K(h&rQ?9Y-Sz!Sf$zGYoQGz96cZ3cT*dw%1C5eBRrDyJ0IzL0CXpKd4 zlk1m>$0E|rW*U*1mC{Tchg<>Q8YhtSy}a4nSeFu-d2(G|xZVrVL3hEw- zcel%}Q|#)zP(PcQG>i@htdbD*sK!kXoMcwF)PKvK?vu_iIdZ*)wJ)~8pjlec*%VlW zEDWZi8q778mbjy;aFSKB|A1mVBrY&8L|b3^sc6oOtnj-DEWcm1P1l2FTRrb-s*$w6PhYj`f_(hTHuM!-rR8xVCdyHbs|*I@xexOn87cy!+jJSmxV0a) zsJKRb$Az$sof;Log{VN!mH*{Ax=;PlWGoJrQW|eHg+^!@%HV_!jU{ch7szkeI z8XOQX=n74S`J)Vqcqi&9gd9zE;a5cBasx&9J<8yMN1|YT$XevN*R+=4HI8`Csxyz< zp{0wkw+Z_Sxs`!4!#t^J_bPpZL~1Ovh{2#q)6l z@-)|{K3e|%Uc?!5HvOmU8&N%#QZAJ4{tHvVxC)JLDCFDQ(+g)+1iN$*PG#Gr28Yx2 zB*sHDzGbftJW})D3w3tG0(eg{W7JO}Gy5e_ZPE!A4H2^MlqRwNR zytBI$nVF)!pg9d}zS>ls$_d z>g|S{zeJj6dRhHvrK9F)uP)jPoZp-tMBLhm{~sX8-zZ_6VjkQ352Ao|kE)MA~6*XJBM3l8>Oy0^1^x)?Fola=kdk);eI zU#RaB%=WmrwY#dQr5-WW+q%ZMC;dWLKkdtO7Swyf1rRj3p$WIo&C@9**mNJ+yiuj2 z89wbpWZ2|T)LrObOkNp>>Q0m@0la4B?~Hkv5#O0VW4`y7kYn7!boclBz?h6_<{$Kd z=?~@$$rpdHaQ(ya4XqEEj5#m3wL>)V!!pcDLM$9qL77*4%`TnD_&>>Adre^t8+Zdr@zyB z$q_}20uc%E2RU6>S^h8>a*O!VMV49VzS3MoXu@fTUdiSD0*P_Ld8_W24{!*s$lXFG z9O`Y2i~Hlm>yE6I462Kqn#6P@aEjov4NKsPDv()S>t*>@O?@Z|Mt)N?`YOx6ZdDFV z_)-a^Kd%951Yojp;s}yDbYmykPx*BJGLZci+3UlC~o%=&?m=vWCm1lClTDwtTm9+@4>pLn;<=@T(q zvW#)aqrFIwbYvo8?DZksAoAQ;&v^Z_0QaOwgcIXz5$~ebIbKcJ{-by-`iG+H(l*^O z)pp&a_P?(n!2qfGaeEqJZ5m1W6N_l!DU&LBDRr7@!`-xcEr8gzL#QGVh&yEP&9$&E=Uox6Eq3md#+PjREFzi!2HQ; zhyP8HD%YblO6GjDM)(A98MwTWx^Liw-8Wj-05q0fw&EG0a8frfhG*YGEbjDm5VKXo zLc;kQ%TQ8c`ktY=)-T%b0)sntoeM|1F5RF?*OAAU>TL`_z-fu*m6bfW=bCN5jNrf7 znwQ!o$nrOi3)RKJYtz+AN|%Tvkzpef^9|)cUrB}6t+HnMFB1V5&^#x(IPk@D9J%n# z^p6)uOSH+W60#g5Sy(UsRoC}M|Cdap*8GqzmSqtfa}4!kQK1~#>20sB8}R2Q2K$0% zwU^bbU$&D?u!ExwzhWiBx@}CU^HHJ8JlTtaU;BXE%^0hk)|I5Z`v%|TXQ4mKbAxu# zzRlU~})?x2+2RunSt&c8jTx?TkQ!i>%w~N{7 zKd(C0IFC9vG|UI%uMkQZNQFvM_c*aK5gRLDGIwrBVk%K7aku&MbpN3b#FxU%?4<8x zx;)i-kw770>HYYtbZWB#A&3iFxN6Ecyc~pqBKRS7VC~;_j{`07%H<7>8MD>2?lG#%3X5B#@L{8s&e9?^J>>W9)0@U9J38U9<)`cJRFbwO1IxovH!qeyG?28M`ZIPn~!W_>kuIh+Cr@ zPTPX_TdPAqNyZx2TNO{?z{)u3F4d#zY+0XyS&tl}wX~{sW`kH836&hW)xLNWHeP<1 zM51I4{n)qd-ZK+)UItj$ppqGV#kVk%*Sx~8I1cE9m{GGl&z;Dy>e)$uKaG*rg-EH9 zYdYx;`gMeI3QWw%<5$wu`sMLW?511CF{lyxJxPFnRh9o(#1Bn!rZR6q> zxtALFA@qErLK(ffulF@fcYN?+sQtCxG;^$Vd6}|vmuG4h>)l<0{^H}*R^r1Yq+hHy zdjM}hyf!=OOx4FuG2?nM<9aDkDR_-@UCebBqlT|Z&`r_U;3P49Z2pJ$W8x1-#gi6d z#}j=e_iLRJ3S#|>yM??frOQpFuc~t*X4+&9x187)zS9asKiawTD_A2+asQC)a9Kl~ zlENNWt&wzL^ev3K=^w^PFEAXart98CcO<1!JE1xpMc0+6y(Gk~1%VvwCw=+v*9e^N z@&&cWerKQ-u3`CVy~q1iGXA7!Wjl`7^t@M96Xbhp+er)esbqH=vamW5}{u;Sx zV-=}z!yKvbtaWx!cl_zE=dV^vmlD`~tGMsc%>k#1BBT%e#7IScH5wmAaC-ZQO+;Yd zl-}kr6ZP`;@|nEnrG}*sN|-T%Vqtw8CfdBQgED3;4&O$!tUR!a?#4v$^5UKD8+Ufd zjppyExR74^I(hm|y~C0?U!^oP5b_-#`8RU?99{pg{% zz>mcxkAI~zR= zcsQ$_kd59`V7Y<+Sk+a+c^i!HrDID&#G4xhdEVIZ3Z%`bjDN^CjH5nOGB4JZOX>$A zpY-fP75!In*3k`@_kJ-f^+$m{ZCpwi;&;!5%cTVuz6gA6HgK`S0U!UA{iC#AvR=Ny z2)-}1i5oTo4-kU}^gIWC>h+i87Z;>l;htxL^aHzieL+S}aXDv6!c{oYDDaLJWboUc zNSh}U^W8yyGG;<|+^zY7F5mPDf)N7%XSRVI8PVAQ=+`b3%n7Of)roHPkoI7~>&RC1 zg_7;B88!Hk;9a=P#NAkMcAgy3KL|%1;pArF?q>l!W<}vV8uYE?XyB*Re~+^~V7mU( zAWgV@0Fj*EPvFp0<^4}La`5!~?+;XR@r`g0NmIz13B=%4;Gk{Fh`|CorEy?uq|NJ| z2<#3YPjMJ{F%KP_F#KOECPp9pB`Qyc2}2v*6_ffbA{KlsS81f4G)f#{Pcg`Ef@cl$ zQ6K15h6kY`jmr|BalnRh@RP5G8VNn$mf~sVnM=7ORyg28E0>TuJvY-s?TOJ$>Kz(J z#Dlso=zbE{&9X=H?4>pji4~P$c}!PNozI zo?XFeXJ;swciy`_!>+?OJ)#BiTrzxBj4+*}kl9L_{ z$}9+P-9G#@vko3RcjOgSsvX}(kI zbvh$>M70E&#he5zXjZ*TVVBM?7!)Qv8gDruz}R*@E|fj9$wag{*1ziuvQ^u%GLeHz z@L?fJ%-ALlevP6MZW7_h!98}75RtUN^GS&w#jZgJm*@rD7iIpq@^&Dx#KTrwGA;|X1x(F;X;UOhijZoCq2PU7?f z3rpkscyYy@;>@o6(q<9_05!(S<&DWr83Pi_!<~(B5%JQQOEvH51fehJoUXO4_Rr-? z+@SUtx?KuhR%@Hndd!m*dZsM@@faji31Cw69~6(U7@Trxl~8X_vg!Qt|^0nqG$AJro z!Q{KX#!0BT+OM#13C)N3mnysA?PZSH@87XO(+ydm&Gc9*-LjJyo6pf&S<{|?Jz8Q= zWgpRe9Tt86YN3(9c-l|kDq{r=bG0-qFR@e@cW^^~rW=H?2$R`g@dmt-ec7|`bS1hB zSSMWoW}v+kcKxy7Rq6(98e2twjfECvZ?>pTm?u{_t#ER7M1rlBp~YfmNu)JF&ii~# zk0WN~b&zn?5K9c0i8L16Auh}bTD$j*h87@;s(7M@Ih;>zK?_Kc01?IklLDOn3>>$g zPlGX)AdMXw5+8%DF8w4P85sEf$Bd0p>NSAH)88Bi?4zoPx8`q~^wappks9w9F{R=hOaa4{%BESy$&VBgVttjJ~Fz%V^x_u8^z?)xyBos^gYJdZ)(Tk0wnTE|D7-YRrB z)K^2;*`sSUByP#T%PzI?ZVO3+BhmCYC{loZRr0T;kpT+8D!3IafpaiFCZiiU|mWnm4h+g0!w0boT(jO?zkjhmfRP_(o z?WafUlm-Uo`Si&BzI-vy|AU)FNKa`J0>{6<^1md-m?0M*L&R5DEAfYP!rrXJyWoR8 zgp-{KL~+f|yY>gwMiCM0NV!y^Dg>>p=wKjOQ(=Jj2fdw0z}LVtw@M7Ag`t7q{< z6tT|4s3P2Yef6|N%cbsPIEq*XANZqc4m*Ayv{pJ+DTHeWNAN}6NDKoRw#N&b8sS-z zAwv|z-3Y_p$@qTwuqJbxg~crMVI&ItB#PtV%1VHp#j3jCgyv7?D{ss2m?=!VlvJD1 z@gYwQVMFPqyXQ;s5l`SL0}YWA(Ma&-3Z&eV%vWaHKE`CgzcRnFo`j4Tk3-5W<3+pY3DTcgULR&8IOANKW)Y)nSVfGBGZvcdKBtP%# zs*+)FCrsIwD4J*A$Tt8XD<0HofdY=)f}$G_iUiY`3v~l>G}-#(;^MgJOBXp76&q}- z385u2;K7rZsaM~BZ}+MS97LOq(!&J>!h7vpq5;zot1qjmgeE z9RsWpj$|(AoK&{K*UI`H9em$|tYZ8i2ng^$EgNq^u{V)a4%M*#Z33qKN&rR*vfcXB z&t9xVBAx$wzCY0aO@8%wcV$JfQV6edG@?f>m?oFETd9{3q`)^o*4maFnpKv$SK(5+ z13dszuM1Pg>Px1!={xi7*6R!YrADtmtd+Sao3pHT1(Wh{pBU85OjRQmXif~+s~Blh zUZY%d0QgcD&;MGNebn&WxHR*1IB5VPch#LLnh^{eksGHyGZTK{2d4@drhOYtbnHg` zs#3tn<>gcwtigFxl1H>hudyljQb690t)aRv^<_=whJD0_Ano>0Iu?AAEEBO+!)4}UkJxD4OUd3`$XS|8c3@<0RUrjzxEot7< zEDb)(`By0e(SO;ttr~BMV;v!G_e2y|p-LLCF~GFLtswxfvLs)CTkGuow^`ngtXA?G ziz-yr5e?4Gc5|2_VHmF{u~$%k`FOGXe7mzx$&W!FgQ|iNycy_NXt~&Mf&95jV@m}C z;i9o5QjH6C1MZnqQx`HrYyO*+L0u%3V>w=E^97Ty#+lXM1N7^JEh8g74*X3U>z}vP zIemqOC=m$9HxBl!(Dv!558T3Ap)yjL+(PMUtu@t}yJ%pYsDt_&cS}L{jXLWS4Lls) zw})!aeN1o6))#VX3i@dlGt+GrUl7x}zDa!NaV#^WaIRwg&aFX7=a0Qi4kUd)mi&|L z;p@c4kzOT!*%2e!4ADLg4CYuc8L46_WwQ0fvhkpiyR9V@>0c%f(KIYOAu{pNzWDXw zADX+LcA9aS;X6uaU=~<)QR;U6s?s8%d9a_2*_SAghBXT)`lqDupDrMF6@_8eLWn)~ zd7qtv8TYWCv8bC{O5_N=DY60P~Ni> zeN8#4+rtQKX-{4(1O$*5!Abv->AJ&32ezQvSAUC#+%;FjX?j(&KLoh$ceoin z>n^DCA2S%%oUT1T+S1`0Z6beu+6oh^wK_i2khjb6sOgi6H%# zUtXetjU9k;`XOW!Tw6k=E+LW?s2M{+`2Cq2iUmD&R>-p$0$xe8-Y0tW1mys|7mG=| zfhc?Cm`tJS^%*!#X=1W%{8SE}-ih)X`wTo!tu!^-f5!iE0{2JHw&lm14#Po%sWy)J zw7;%yhTp8k6{ck#*mc;9D9V#0~bu ztzjo~y80p6!mxK9LVY0dQeegT`kvr%rnAnUCKq~0tr2uDZ-<^f&kU)zO=guTF*|l! z*X!54)ShTdr`|bE=ajlT^462t4iK;~OLVq;>hXzp{^(7MQp<^2>EQ4C-n8zfOOYWZ zotfWEnj&^z^;AY2fVG~c=9Dn&gRtCpM@$wUQ*t#8#icTYKTn;OFN`$ty4 zs}r*kEpha~oD0&nFMjsGY|Vp9qJXU3Jp&V@6;+Xh1C=#yPclwZv{#m4ea|&e_Eqls zR_GDJxp?I5)C%UIIAHUXwDvH^?3vkmMsNX8cYkFBGfa6P34Kx9s!>IPw{W3?A$b_! z#arA~R`0IA>s^rGNzTY{A4>zEtkx;Wo<`ldban6wC5sjYoHWBx=cDXeH(gB3%*;ci zM+WDV4rgd`X~P_9y>v!C0NMUvbru*fzMMce8eF+-@~Aku^3vBLT9<=spMfJa3+^9e zhU~^vtoQg7s!|l?;3Ms0ywZxNG{%w>JZ3u?Qu)=o(?c9d8K^*PpDyHVf(>6ztQh92 z~)1_(+b$-?D7(lHA8Etz8l+|Qb<~cB2v*w6S zJ*nsbo-&%HB08w$CZU`+!>?2ki_KQY`p!DI0zBBs9o~|J+H+gB zD}u!T1TOtVp=zN;{uS<80t(!Azx=<@2XB@qaB(hu;OweBgtk;F`am&6NPM_HW}N}* zoWfHC&|9gfJg$^9>LXw`BZHM(lViJ;U-e!~h zH#v=WW2N~Q4|^YB^_eaWzkfSMnQB7n^~WwI-@=@an}Ubx*GUmJlXxP|W)<|0K_1D_ z7o`Cpk_^g~LH1$|c=pPcRVq+2Y*34~P47S=p%*{?+K?7~dx#rbUpK+#O;?i7d7%-9 zWkPO3!OIDYQc^Xe1AT+_4w^jYWrxX`F;v6}_7PIrBOF>Sp}wmg!ww2^6T*Y}l*3S| zX*XQmeael>3E+p?qspbFS$oFM0Ee2yfQD97s~n>$#xqudXVU`#tuAqZdszm?ajZ4d zPa{LW>3V4&f6O?Em`#&HxQu<^k84+`R-y6IcR#G$5po;HE3%<}OT*d{zvp|=xy3q6 z^H1gxao@mbWwZJObj@ViV%z)zIxj)OJBoI1^d){uz8AvupssuPP{PtfdGU5iPpJ02 zPoT2cZO&e61xUUqho(t+;WpI;+?8qef&cV0#H7EYbfMsV9=i6adCk+1U|+3MQ8!*? z+v8d#VfEW_Gs9(%rJWC4wKKC793YwivHc{Xufu$(X4bY9nSoApp&ORux*ughu%oQcdq~yEmFM~Fdjqv#m7STSwxjJfsrXXC?x6LFAG?m=+o>iz2GN_giSeZ= z%?{Ag&hLfcr`fl|6%iv0OFJh`NhUKIAyL+oY4tv?70pXQq^~J)sa$o;8ioFJ!Q26} z_d$>r2%deHemw;ARfQ$h&}zTj2Jr3Hy@q_r){Q+c8o6o?qeg6gA8PqCUL07Wl57qO zD2BXG$;6EJDJ(&Wu_AW54{qW(QGlvry-J1W9?#y(o)zWp8Bc`W9a`81FCz0QkS&Q6N8_(?8tig06lQ0HRE@@ug?j;i8?-?L3LKB!1T}fxd5o;RLVY2!C;( z*hZ;GOZF>!htU5+*;__M{eEAdba#W4fFNBWLkrR=Idq4BNP~1rcZv)kA|OLbcXtdD z1JVuB-E}{F|M$MW{$9;uUd&=W>p9Qa=j^@DG4x_;nDxlo7p=3TAPK`E^g2K9lo%{- zN&f$;L3N4xn_I_{#v$CbLE;1$d;HRVY~y!%*7Bv5q2oLpoNHihMh<%dF;;lU53ikG~wFdF; zU|$o0{p*HSPQ&KozHu*md~)sTsNfb{#g~~r(9=cHQ5nImoH5x~G1oTm&J>lPbJkY1 zT2Ae1w_7?Zg+~I0mcSZt`@uF7bE9j&RS6lWbl^K!^<+-^4g~qvwD>d%CfqxSc zz;%?Le(YFTIfWwYw%4zIII-jfh7cu$5anRr#+IH5T6}cEcCd-M~IM zvYrCxyI|1J8B@h~Oy>YeU=HYCDNk*WLnVchzbF4gFz=XkLR~`FY}8RGp<9K2%hE3L znxL)t3)*`mt9HSw&}QTXZ3W!M;xf74M%pG{=RN?0OaPH{%^6B#v7e9T0;!S7zcOtx zWwXAK_4=}gDmhyz7UV+kaT(Q?ON~mLy~cjqIWCvR2PH`B34_gFpG1Bgqg7>KGU%yx zx3*v-drV}0I%sZE1|}~0d?Zz>vT2rK06s*g&+BJ_a zOVP5IxJZT>%e>JJr&3ZG%Ps^M$_AkX;J!jaFk?+bF?%{8xXQU3Zb-Tcb=*vS$|si+ z!S%v!RL%kypV-R&hYJ{au!CfV8hXX&2IkCzKwVNak(td_%`2Ov&rKJW@N?jr2>?#SA>7Tm+SOf#KE= zi+wHJ80RkiSy>32p<*@(yJT@8rF)0W+oR^uj9mqx`19c(l7fH_XgY^i0EHE1mdd(d zgc{Pi7dZMEiDwxX)uXDl6h&7% zJumFlltgz%`734Tv_E4rPiL5UnX~-i8krX+uAQZCQ8+a8ue{)kMQt?>{XLqkt4?QC zJ?9L~{kHa^zIUi`Okayzwm=R&ph9*zeSy3NXni;3SV9I>?@^&}pB|pk)6gcRF+-JD zmy1?2)sw`dX-!?f4>TpMFC*cCR&*DMPTUWji%DFi*(zo=8vy}-9Liz4`Pw1C)NfXr z3M@4y!RLM^a)mq`|BZz(&8I-si7y8-bF6`dnyZG9bvtoPs0$5O{OIl*_D$!|;Esf@ zXRrYntfy`Lvb@I*)mA{Kkv`L2PrFTxDecquAa5g7zNIg~4uq_LaR5DH> zt!I2TUmDvfw+O(dL-T^YdcXw*7=tf$MzV%9YE|!7Qq~o{WA9IG$vIuh7v`sTNyKyF ziY2JAe*ic#2S_vDFN8AAGU~I_m)SpPZ z+)lFvr;vkoj6%AWcFG-?Yj1}-Y?6O4BNKQ{-k0$(!(v=(uY_QD^Gqf`7m_-67wqW zPqDq$K6pm%7TQ(*Q4hd2(}oHbq$B4ycW1$}F9pf-zlo%*MSOB;j{5+nUmn3kSrBTi zi4T<{1Y4DJ^>g9xt8wX9S1&Ph|GW|_eD6c53-1loUJ|K;;m3slO`_V|>qHPNp2XP#f3@O@0yu&SN5$yu@N{u=Kpn)Qwk)}ny zLjktPm!@C+ZTH^;+yjldmM7JC^@_+z0HN%{WOef8RcJ@|cbTCVss>>PcB@E*03?9> zCGv0P;z;*daY=g$@bm%?<@dSDAnseZ^LBk@LH+Dq?x^<0cv{eyeZ8Hd8&;f~;X>-_ z&gj(24%hZHTAkrzjildz$pqudbduq{pIH6PVF0jBwSKU^qk!l2rw(|4&J!WMrs!?+ zZE52|y7NG}S~KxO_~^!8-C8y!_KS0I2;)5n-LnBnM`?B2%Q6U@c+9a6U zrL)s&6&escLF=73(PJ#-;~vS6^9@Iu-HMHNYq)0_ioZ|V+daSwL#y4>ByW&7UMB&n z6TKh!fqzmy@*d45YZ_lAXu`D$)bo{g!D*5VTYOrQHBZKj@6kCcAFUjcin;HJ zX@o5QTkX4S_dwSUmw5Q}R8@l?65M=J*RZAE0z9Au^&3hG92x58In(<`C}OQIH}9T5 z&RS?cdxxg_s*QC{gfGLcocU44*xM^obA6(!IM`Fsa`5x9fp&ED+QneMM{eaV^^mTR zR~_@?LNuSlxki_@(a`$L$W7-GWah))GDIEtXBvpDopS{LrKg&6+Odzx(i7}CyTXL& z-0R0wrRT_(>i_;9s+Z>(x)vr>8&_VYpT!kOGuJ0oh#@>4K^0W>{kGwRV3|C~1KVJ` zbxP2+1%%Zjh=0`h2Rb@1IiBHRW(v9F1T5Cn=t)yDsG*AqM7adhFQ~jv*_s$QAGSBX2xfy~`2)|BLr z%tR5ji8BAZjZx$;-Zvba-*I#AyA$^ZVl90t?=#BW$&l5am*J(fu{JdZyo%IbT5juq zb`q%VEbd^LgQOFl-J&gULSOOKc7}`kC8`d(NxgyQ9e^4<(R4}gsdNRDFCAW|w4KBa z|CuIOQ(nt&huW-I!8BZ~te+YbUj^e_Yr{;Cif5F#nSXu%_sl(}twaf};q*>6O!OJy^GI|CZem zrq6AhZfv3;*;hajb^7QG6F6JbXFFaQ0sN|eFtnW|5Z;OWx9o;{9{M!^L07xA!U*&y zhOJ_!bKxt9H0YN79SYgK+xsg2J0X&wYqh1W7$d7Jh;8T;QaMf@2E>O{hF&EiRV@#L z+T@2!v@R6m$G;!q3p~naOeHtBLjG1?G?D1F*Ys445K3-fL^@Sn5RW73LVL>9vzji5 znN=wN0gbdPYKd%^`b_o~0Df#noolb?ZI(nhj8HzRnDtC`?8t5>$4T6nk#ibzd1B?L zhQsZX;>&(a>`d*7HA3ZFNriDrnu~CiFUR$lL!AF>mN6WiC%5k${(0(&GrYUGCy)^S z892A*{(I=GdWlTN^dr*_MrcciX`DzO-BH;Bg>EE+Q>~q(j!@RDqlk%X=G||kQq-SBidse4S? zH=$r5B{=b!Hz3MvxO)wI4IdwvksH#yMcV%7BCv{XudbBxMM)Hw%UzKxq>@_3bO2S= zNT?UHiZN8%H*`WLYs<~=B@(-w&|7Mmdo6WkMlzg8l9$Ty{dWv8Xaub67gSnI-W}AHEv=hf6o)t#E?qdWE4t>;>&3LfVBnR zJCCHW$9FD>+ZC;zQgz*u&P{ikeEZ=ks!K>*py0C>QvX86q-`mEnLMG7o*q2>J4Q`p zpy=w^^fvluCH^9MC~1c+DzpK3wgN`zC1^_T^LqMAp^+xY#cM5dpPPZtpcg;1St{&q zDJ!UQXy!}e>Ckof+fa4#$k!m-rV``O1B!7dOW0x*b9VuHqRweyf{s|5Vm_&n@xNAq z9KR}ZKDpGqx)axl-U4Z(I#g4vgf^&mLf^+sz>ol5-vrc0iIt#pMN^>Ry|sa4BLwOM zaxS!%7YqsA017D3woNd;vBcJAanK^JF4&zT#W> zD95yOBGTC^|7zmd#b*ZbrCC=%Q~>Aph*imlxw48>sfdesrLh+DRb8EQa#(qe=xC2{ zd;Bbpb0j?Re~947yWW2$un@mL`P#%GdAlIMU$!9UQ<i4#m1!JE284btX z3f9{>&k})MI9#L?=$ytMEia(hMW=@UD0pfUbmr($WM2ao;Zzo672$dEoa;U#W+SrE zA?wcYXh_}G5&rT(-(s``i<#iHrrI^#tOZq3|1||JVJywC&TPkCtZiKW`xL{Jt1nC- zaINnj3)ZrA;m=Vb59^}(hlz(viovdLeC}PZ9shWvF3&@RB{_Ea+OIoG|La67e~A|V zWc16tD?a~7vHaZYUOYa%eP9>|jYd4cx{Of@KcM9!xP8NOV-u~*zNt`H*!b?QtgK5+ z!3nRWRmIrtLj-FrWP6nJ9y`rGs{4NhX`4$&3xsGEK)O}w9jW`%Rk&$E#bO1ONXr zlXu@Gc2ub^6j>tNc{etam9>$byhSp8XI_1i(I%hJ@#Hn?QYb)w8TOsaTGTfwV^fvy z)HqP{KdLo-ZFq{r;$p~oJ0zi+)k*v~mrh(}oGZzs$>Ji_ywHhj0n^+;9Zb5Vdn;YQKBnDFn0U=fa#6mJjJOY;?a7o;^}&gOHv5?7a`6=fS7j?UbIHrf06XZoM{OC^5KQB{ zGNCtk185}*B58K=s|-Rlodfw#t~^wxBeQ08+1s}%<~fVBL~HBkc1p=rlA4KW!3-!gwP;S2C$}xNeP$ZiEy-KZL}hW)pkw{=&1y`J zX@QKX#6mBJlEbk68-OOWI=g|AiSIROYy@0#j`N|l6c_6o<8svdUCb7 zt)hJIP>vT#+3g?3+YT1flPh;x-wf*tq6V90X*i%o@@v-HFA*1MCiTTRCQobU zYOy~9I6?gRlD#H7b(%*P9#tJWNEBG#@+(FarNPA zkA-RZ9B-MT*T6FQs=WX*K(+Y6Yem6YL|1N!cJ49UF`o1w>7N8+;5iJ4I+H4=VvTPg z|DWE;8cyXeN`VvegVpYUjN*&+Q!7fFD+$^?uvZ> zC~`U_Vu?_x=54J@Wep!&BcZd|L1R+YLxNlr12b7aZ*LlW@!zAg{woZNxvH@}L*Y-a z<{SEh35@pq^C@^nQI$tTLyia{k?h-`;O)GeswN+NnpH!$zk405`4do$U1~(04!E~R zDh8h}?a~aGBY-}meJ(oz%R@NcR%#J%=XcQE{kOUJo7U|#GZV~n9cV%1wI7H6xLmpp z&Ip>He;3D?qC)q2^!$6@v0O8r+T^Zmd2-f1ewmT$T)2iqg$KBYTf?M@qhyBJaW-6| zbfuJ|wUJLr=;{oh`3pUN`ee^AXieCdUrM+o7pw!3d_$FD=jEp8vV=lZ=KreL4kI0j{zRQ(`D z?6VoazvxoG-!+{Dv^?M$*x4zdr!pV@vj9`}${IEd>)zgfQ$%KAuE|nGny1CCPz5>) zFE`SkLX_k7Q#?rnc>S8&4hsnntbiseNE@KV%M+9rL%#WOmTp>>4)SM$xk@srzQ|Fg z?qd#gefD3u_qxX>`<9JpOXf-US)sxC@*d|UwY!x!Vr*ABF#@P;m_Q79(5+tdC%n?= zAVR`t-~TB1hVhLR_}E8{rNfqXo;jxs1vFG{b-ZwC$-0H_sNF*+9Y4`5qbrQ!gmv<9 zJVpCp5~hU!*|Y%UWq@+*Y=5B_3CZRPi~^GhbhW|(LU=EKttVOuQFXxQADR8!?$tM| zDeICcc3m{eLQB0`GBnD56|tCAk=@%4X0yTg;<=%ryd-&A$kqkEDRv9I*0wRcz)0S4 z9qXZ8Q;!sTKz29-XtGO@I{;&Up5pV!qhzfPyP;D5lH}<6hA2=bfEcOgT%v_K6llQuw>$I{OpYC za)0(OK!&;0b@&?wOw&~!HaiqVHr4jiZ@f6p$68w*PW~7m6y6nzT+NywLRL$mJ&QCu zMK*O9_hJZJPCY7L?`pGVSIq+X1yP-LE^^W+<0NYPK>D6B8C2!ta<|#r)@iyAKc7}u zEiV>+Dcu)sFQQR;@o!4SNc<5dB6QoXEl)lxS9DEMH(qy+-q5jb)&W38SOA*P?#pOx zOQtk;wl0a_*MuurmmQj>Re}Ig4WaXm#_ zc;vX9mZv7~Zz9Ci03v9HUE`S?yncDsRfh~2KCxV#|0-ALEB2pC=l7a#2eOm@P1k=y zCzKyP)L_+GcB(hsD3u{*z`SitP%szx4<9qq?Nnw^jqd5FvzU@chsvCTbK#HlYCKVb-Df4aA;DF<^F zhT_41Mmaw<187C9uH%32>!=4+oG?|z;tKNJ7AfF2 z6sO_9mIz8NRTc@RMQ@3IXvehHwYBMGV}dap(Arvd99Ekvmq@Qp_I>ed7E&o|w`zpwGy_$+dkIHa4tNE!s>~hJ-BzSl!dIglUx8;49X!o80Gl#(c+m zD&{2Vs{0W@cK%Oxf%s%rBdC{O$9#fB(wdVM`nG+#u|#{MEcM59K?>YZB*GjYPS8k= z+#i?i-IDTS@+H-e(_Z2x%-r@_8>HaPCab%<0ZqOQ7w2qc?eif`zVoxsj+g=hmqYv~ zt7`qPh_!7pYPS+KR6T3xVcXH%)h=OoIVNVk8@F~|6H+U-i=u|S0|qB2CR+e5$QHg~ zU!1zUB3u+d>6Ka3WB(lhj4Tk!2##UVOe*T1v_#TF%Lr3eDqx&QrtFWgWai_~_&lu0 zoHWzFXQ^7*Jr!(BYe|~Day`i3T)B>FEa3bVEZE$RJ>n> z`fw|DsHM4xHUL`0-)wcj;Rq}>225cpc&8UT{LPk3ey>Os=#0WS>X`KQeH+omru{9p z8|Z{kyKWNQSY@X424bDYxqB}Bc7$u@e@L43&xOi+uP6<`Ls0bc>FUN&7`EH~_UB4C zBI^l+KNT&Bshd`=j+3ct&Ni|%LI(+>E3P)1pw5hpnYej~vL}e*>^5ZA7haBKnfiEu zJ4Nint)LTd-ooeAF%;eWJ>Za)QsQ#emCx#a^f0lgzWsgYZj-b(J zI>yVf;jooQGsxD+$kwN!I|Sf_f#%NKs*TsJRNE^(vw`w-1-i|1HCxY*CQX;iZf%-Z zTEdn3OiN42i`AzgyDc`8(dOlsXbJ9$mvWgN2Ms_WO@KCPmLq;PDAr=)lzDqjh&?lZRvztz~Ss7cgGsI+ICnM7zIa=yK#Z zY6sDB%9N9@*!G46Iw&QrGqdQ3S$M#iWaCCkU#X~0V#gA2!&7L`T9`b+jbH5?Hb5&q zwP-@Kq0*(&f?)sGYLQRuhU&@WcBuTqQN+C=tqwoCJ_RNE-~Eew_=y6azHZqN=>6i4 zC=oX{a`{SrbAwp|7!_;JUX?!+AKBgY_IgXqP<)NLsC9|t*c#POwYLU%+|XgmYHne5 zYCj@N92J6VRERiSXUQ!*%TEWJp!X7)2#YM#;M|n618-sl5!5_jvGhe zk=+OMl2?co4^WJrW&3!flDnWMQq{yMO?-r%2rGOc$P^{C;X9s5oZt>dIPyoi?$l=^ zXSdBER?3hhq8PyT)akO#FVbyV@qusTR=RT0ZDRuvc zu`-6eZN4mHf3YYl%PxJ}dr5}e{kq>aBe%#p{?~^i3jY1P`0!CYcmARAJFh44cSm`S zd2PXl#c=Cm*5nEDKY_`}wHTsZkK3o^85V>0&ILR9@vmxlbFVB_?vIc7Z+Hz4j~>|Nf6%w{a`YwDET(V2t(Ist&Bt_8Uot`g1uR&Ceea;Qz*{=c zsI$$5<(Jf#OdKWr-Cy6o|6&th_xV{O;7wdgG%vt|SO5O+G(+YUhG9;xjx>`{;FZvNH1sV&vF`$pnL z$F==I*GIUg(gl32?o`Ul&ZqHo0O zUxH@MsmI{CjLQ71qMNyl#^sPK#JkuNsC;NhWp+D-WTXSPcp ze;t;3TeAz@lT>lPE_{9dPtYgj%1-~gR;*< z_(r3P>%u`qAK|fg3to=k#ODiUDn;s2Dx7*3hK7ThAs66|<<7Az70U-txH)Gwf4D|1 z=oiW9p+f?p8`MFg0vg^Lk30o~#wLQ+%XNI-89Y%rE_5FyV4`B9w05MTuDlt};fOlj#f8YQLU4xfr@qS33Y!&C@F8Uh24{9@Th_42hcd{CSD*K!~-Y z4uxy`8EgKo)--S<$=Bb&1};WGyoQ6*A+5#fXgsf`c+IpXer}(wH)TFmZx0k)q!pdp zM|tM}?fWOMgfLvlusl?jOw#pf{sOu8W?!%1 z3*eS)(@)Fipjd*keR2u+b{r+kU4J1B>$DfNmBsR~D?h6qvyF;?L;@?Wz1-@YKO)x5 zYWT79mk)o_dA1brpPhXCoT&=;0ZT=7;kK9WB%K^ zyY72PW9QQSbaqYNY(44EHXRN%C2*494_0_4QS{<7NJzzGr47=A^N41iJGrkjWjRMR2`bp6RR%u zq+vzqszW&1?mjUS=!8Ne77>t676KvS$#uZ)EQxEgD=u?5D3%=cZ*DcBVvk5>n->=k z>=HjawJ-?JHQ0%vL@Ic}^4KYEArX+p*VYIWy-}~NkvKL<1FNWQp1S@K)CkseACl2* z_)y~lXq-MIEw2YiSLzHI`4Fwy17Z=2ZK5smq-FUWYD1?k_jcR6A2OH1DY?d)4rFovtq+fDrgO zprBc-moYik&MhD8Dw+6EZ+~vL+45%JZFw*C?k@Xmto_o+5&7aH6U>i`Np9}c1j332%Q!gRHzETxo`2jHNVmJQj-yEj{ZFLWSoCov07(lpn>{K6oUTA zg8ObY_B#q_0|EWM$)E6U zA<@sYHjhL9ZvBqVLQz#?S;O!C;(`;Y3AAA-A~mx=xtf*5+Fp9_(rCzU=;{Se1hS%K4LM82=#Ry|L9N_@AsWv-49|mq$-uakdd~@lfML zUf!cjX6?KBxKz4QXH$bivjcBvRv>$Z-h5QJ^b(STJIRbKwbiWqKVj%3!Y=+-hezbfSdF5zo0v-L#6G+y--?g^Z1iga6+1Kar$ z`!5vpCFLatOCSC=Rdeuj;r9t?&LnSvTqi~gg_O3r3j~23$%g)QpQ|{tV_YvPa~@Wx zH4Oh*JIs;j8R_UpQ{R64i%?Zaptp#sSLhspMmFb)d2I4!VODehww;`iU(ZXM_L$>A zAy~CbB)^)m|6`^!@>yayAReB_uo+c5O+**((e4cw=UR8v-k!oM^YglGo@a&3owFQT z8618x*#>XxGl{=zwG#2mIEntXKHH)86SXlQ{=alVKY)wzLt@t$IM`n8uZpn!=+K3k zGU{*8on}q@V-;gvpw}qDvr8sdv2l!K?l39V?Y(vP>SK`sWbFV*rSeB=1nv$kSz^B_exNH)gQD;y4tfF#L$gvTbczI_Hi z{=Ue>_x^AA!&JK9eq`iywuwOXI!U%_AN=RK?b%~_{VBfPZsW$T!6*tlF` zXjeg@BX0eVD5}egcyy$enCfAzD*?`--NjF4KhZXeJ9jdN`%lSNX-v?w6GqNPHLoK@ zkF+;%KPYN7%4PT<_`2WxRxR^2i}{Hxvs?^SE1+=LDm+(cY(DK$xH$pli{8y=n(&0> zdcrg;t}_Uav|&w?hR(b9Oo-^+1;G=OmGiD?`UX=4Ybue;_k5w$9SkHGFQh&%6WE2K z<;wge@J5N}p^w2J?u;QIVCW34VA#X(-R;D%Po-&vC`ZqKuo$W1=0WuDJ~oxQtOp%#I9~E80LA2E!T0TQwA3-zFJX-gDdf})l4OO^6ZiDB0vbI@XMn& zqt|eElLkLbt>9!Q@j`BXAt6*pzrtC=>=esPmadhMExmz*b&@*np0;B{DHNig*+-+B z@h!AU--2!E5=c|x2(DB6xjamd&17UA?gz)nJ&Vak50GriU^jZP2z1IfvI&@$XqfbbmWX4Wrb zUktu=kan-T}acN!G2`_ZLn213n((M&Yz`(9CH>V;J@$0_J4laifz-ChA)A z9X2AmG&RCnf?PZ3a^L7ObcA`8E>o;5Qrqn5=br4SK>!8zdPT|V)$%u zsB?rkHzG?5kkcDlDY=z4#l9b!i{x@k`|CQ3VKSzO18xVg?{~Yscr$$NARTC9e#P;r zHC1;d06nQ!h)xjZ`I;BDmc$QB;7W8pD9Pa2tZ-dYvxG#fE7beEh@B{2G%xiDOkXOz zMU!?})%`GVfcRnheD+HqG6P*H%vQ|5V83OlD3Jo?NO&$Lx1v5 zy@#Pru*6Mel}np^A5d@G-VpqH!A(Qi2By7 zaTWnRpTavZeM^}45mW3(WOFlEq>9AUt?)FQ^4`UVt}ET|Bqt2YUl_p)lwjL3!i|w$ z`Tj{y_pt8K!JM+eB}%tgxY4{f=yLFl3jwx9UOh0$^CoOK7mP(6pqM%tGkyj?~(^JCVbf(D&l{H)mCnor0c3 zZ|{Z7KWsA<7=GbWYK%Y_?at;h4>Bobfjtg@8Zs%ij_aKtuFHlDNT0o?=zD~}+s{z( z<`u181jTIL^Od%!#n(c^RoK-DSv(ddv zP8_8-r~Ec9^52a29)HrDm=ME@AK7+hnKRrLFXZe>N0ofm{V9Rqx`l_%f#Wq2>${H& z3=%-`zjt#eDza6@RkKl7JK&v$rl={yDPoWlGn3eMe>f zS;ReOjWJ;~)^{)`B9qDsYTn_N3CUPw@(NFQ(Pfd)V={gc3N- zP{-vEfeT9XJNgt1(%`p038_NKa^LdA2iIZiI;kLa3#G&y-{|tT?t_FgSWwHlfI_!w&>pPkUYyw z_^#wp?x*yWXiTN-*t>}fG z##?CXMq3ox*0hhbxARder}zk)ro~)LB^ZYScFEXtgce6CvT_VfNzn1ldqOX;W9$y9 zS`4k0Wio`Fa=(tfQjH^W&>T>QW{SDQ?I1(=5ohXrM=~zs+#J7cv_G*9b;Q!2nEZ$Bv1^+>Uchhfh!gL`}r9Lt=St2LgrA) z=k(H{sAe*M2(PAcjhp|7N%U75b(O4o(-nW%v(gs3=K%kXCvVzC)7^j1U2Ol?;=BVf zt^B|W*FB`r+z@-koXk&TrL^jdCE7r*=G*V%aK<_ zbJ%+}m7ME;oiz7tDx6x+wA<*}QXb&QrMX)C^dy#QDVz6W$+nO9>@CDCVu@=-9@$)A zDroS@I{o`*^lfSna+5YYzeqiVU|qhd2H$qdC1;!r|83l>d&EUD;i;}8nJTqsHDrgfx24&13_&%+*H?x?R4NBjck4S=F=5TScpUS9}=bl_-k{#I$|@tIS5Lm)vqkwEuu-- z!{H|{j(a1p^ryRQ>82{rFz=iy53c)BiJ(TkpEk*_R8#GumGJStBzaJEfC7F|WhQ-3 zr+pY=S`LgrHifjEhB#+?yDfk}8vNW+bfWFgNZj_QU#P`#R4l;7hnp?+Yh{kEsgAe_ zH5;o<^`5>-{HkB+a>U3i(I!f^qm3uQT3mMbW$=~?o6;lyFGk+`*+*onzwo>mHeUyp zX;Bf$N#xPPzJ-~gnA)AGIfcgYo_9&DzucK3bIKlLctcJ8e(#OC#t?9C^$Gc!85i&c z__sjXq~Fpu8(b&9dLHX@Om_WiY=gPJ=Pu5oSfzJrsS|u3{EDGH683VMs`FjBe~cYt zy~wkIn->`8W$^(GB2B`QD!EFNzTUxDN6I*|KT#ik2oR*&xj~8BjSS)#!&N-3^NlV>ywQAJ3e}eDq`Qf<>_C?qVxJiqweRk|fB&Cq=+DL23it!zJhlq|QwjP690?(D=C<%aZKo zE+?0KW}h6Qa3_C{Wl=BwM>Vz~h(MsU*+2Fxav}ZaPDJOw*JO5j_6~(pso=Gysw;m= zsW?+YtWcs{DJKj8sgU0cZWv<0aufuzp_pc}7#M_RlDdQ*(yu;8T~rkZl0e@FvkfvA zRhmaZ3wmGHQS6iYi%Hc8nL&#YO=Or~nutr{CMwv4ny6TlW*B%70d_|4XK+3Hw+JGh@&ZiurwMoXuyWT%$-l&&t@pCZ;c1FueP^!d}SGmuePThOPv;7m}LQ$ zZ>k@yufVy!MbgJ*%_D6i)0dGZTnHvAeq=v-VcX7jUk_VUM^4R-Nbw?2Yix@0S6k?7 zxZJzdh*xf{{^?;HdmFg&zUqj;N=~+j0pk=ti=hpV*Zj!py_1WxSzLetcaPV+e;0Zp z#$&R8qiMl<1?5*BDYnoGv{`+51+|LN-y(7oxKPaYu28ty;ZPt!KbW&@g{px}di>6} znZZ}I$!Bmz0|&m14qta)+RftOvoGZrv}h(`kAnXp@m4!hv(`bPX}^uy>p|B2Vh%@r zSv!}?rA@WoYc|T&H)=Z8m7M%T$va2aJkoBC9LZR%Uu_qry+jvl?9J8@^{xcFq-p=O zWzw+y$*<@0ZR2zEsN1n#!aKMb$ct&LUY1np$tbMun0+k^jv{B1J(7~?xVFI&uyQ0` zo3&);9hr0^biJs-e0mp0YS7FxnvP!oqTP$i!4CgN@N?8BSS!b^^#%z(DM|7oOL_(e zQwy8vouEP9{sD{-Sk5`7?MCEh%(4s#M7p4Yu|9&d+lzBx%Zm>BlR^iagxS32;b42cw1fmo0doi z=y8&w_rAdHA23^~f*X558sbEZ%JL zbV9_qoh)9%w-dgU$QK)f5B}9J$K$#A$|#0oGW8IXyEUs|KJ0(W!E4kouQ1$j|6it2 zJvHdoFr1Vx$&Cjh`d>56WV-qq2MUJ4Z&i{!#P6b$l3HGpovD6amo-`M3)?{*^c00z zM=JZ>2+Vx7wZxi!315Ihhl%V;Zz|Y*tJ4*L393EhpK+1&$(^b)Kl`Xd8+$z z+WiDmZ>*19%h;nT&}_?*+Kgl&WC@%`7xu1f_m65>1(sC9*xeR|$`~ugMq1i)8U5an zl^$GzL4lm`cU*^Pp)Rf=1cMuQv{DUeyMj;nksbf-%jrnYHXHwE_!~$6R%1u}{ME~o zVoy`};gsozycx6vUlSh5vr#6SPKy^>^cp1Pe*GNEO<8#7@`Z|CfbVszx+a1K6UG9{ zCuXi#^&z(ieWj_-E}0|pYDx!u3Sj&P%=DKsv+s`mr(oxImJ=^pwpDKAKi2-Zq)Tdr z-c6C^&eO^#8u!&4Inu^)itY6c>h*oZ_z(NYJDQy@8{wDT4muck%TG4^$=p*r&JRfx z{Zup`Zv&p_0JpBM$GF)vjvl%?G#6=kAZh$qMb*d-5U(AYJRS6x)wy2`FS%2^675pG zG&Uuv0#{OQC?BKB3ba(FXL?_LQ*O~R9(iw#Ef@Lv_RVwn$fk$JeXhMj3b%tHLs-8C zjXeh!t*MIr$h5AjNe3s}av;U0s;C+if0I7_^0qtG59yd~Z^9B7bwiPIrGFAoOU3@C zug0K}ieDx~3nes@`a*~#C9O-K6G}rEdyeLu&iZ?#(od}9dr7*s^<^dW4fl`${u{~&e3l%QI{!%T7s2&AHIZ@)Mt5 zS?m^rOS>K}H)q1z(k*is+eb8;^VqCy8w=u6?tXNh@BH3!gNlW5V9-5en^exTs)Fxt|CUHBKR8fi)m)(2XDs5wgzG`X zu4Mi}IM3xOFi@o;-#rI~Qsqj%Cln5)?O?>gVC)R#VMGaysGu*w5Mj{A^Toa_Q= zG}9UTJ<5jMRkHINVW>3jTUF=Vl3JfYI4OSRO+I1T=rc3i4&$jm{KH?vxGFb0{Q*3_ z`i9Pao7TMa6q9W+;BDmv8TWQc9T8Xhl2=phev?O>!g`5ui&ln~t<8P0fAqZp?Pf{a zgRrqu&ZDB)!A=0N69W6>h{?-x4 zAdL0|t)Q8+{_JTr?R(Vvayu#hZ#|&u&-Q2f-nI!9Lx|shViH*?v8xN9Vt9fY0}OsE zRDYKI-n5{42H(Ok!Pg^M2g?H1q(Kh=mJUd5uU#*|2%2D0xNR&yv0bb4%wOqUe4jZL0)5_fluOH;@jr1;($Q+|C3uS_qBPabF zN0a|vAVH(NitPA?NLw94LF*H*w2BJx^v{Ny5PJmxQ5fNJPV&{vtCpTF9Q!f*w*BK} zyt1cG{+Z8Xwabtot@3W;O(+yk?j=|(OhN0K3BIYKu`o#jCzpHK_F57*H+qr+LsRpm z^a%SJ0D~CrXq7%e^j8jF_~oZ4e@|la6UcU}%cXrS*qHiW><)F{q-$JT>r}uX)Vqr3 z`Thgzm0f`KFnFn>YOSx}DcvCW3E;NcIqCvQ+Y?3S=8bw(SL)`OEconbs&QiLmluC! zMW>NY7{Aa9PWakf{iqghtA2)&C0-d>FHDA{<>tqp8{B~{r_Bw;cZ%eW~2V0~Ch5RjJc zmhO<22I&%{LtrIby1PR zuBOQNp&Xe_{=S6vnAr~3Xpq+1qA1+U+43<5jaZ2PZs&yCme0sS@s7#0|F^91$w3o= z*JWUTn*-XZPNW;#hT&gqJn){8@3m0dF>}3bqp$?g8%#vU9~Wil=~G$pAjg76E*k*a zEn@9Ga=A;;nAG6b;LD6mtth5LR=CcXqo{D>_gtc4ZghwnkmPNipXfA*_`ZrV1k9B! zRiGUI^eaT1UC6bFzG(C9ku^(Re2j%=00HH=(9%Uz&KhQP&&M;WtI9G#*8nBYK?oeMC+!20w|S_qoeZt@$zTh%F%WK^cr86~3;^ zIvedR&&dCC$w*=9e@jL_?YKYNFYgv#YibG3=x703MtHSEg^ZNcgJOefRzdZJ2TkRJ z3%vI~K2^Q*u-W)wi&KQTxM2SZOsZh;-ev=911sPal{iXgDi->CVM4Pg&TR?c2SAJb zeMkX?YW09RZU|je@9I9V#qdOBz?&pP=oQ6}gQhGDD(A=)nQNWrt7uDUTyvu#qa}yo zHsqG;dmoqQHe{BzHI)a~=Q+>km@8fQ737xsB^c&i!1eR^Sx4(rhv;ydYSqZ+>ZOC$ z=e8^)ijH=%7pwPE_ko^Q?$4|ZC&{(;^Y!*)ZCOvBj4>Y&YMysXT|%eqstcA(>*tT^ z@S4qHSOt6STu=N}s)`O9eOFhejXuVE^JTGa*60e4`i*U%%z5D(RWZ+RoO@)f_>FD5 z1bO{dA!mLboqb+!e!kJEPq>NIP>f6h>ymvT>4m{YPiNZ#UMVBt-*V{4 znIh6rB6*V37z?*K7a-Gjle&_>YL&`+^g3+<89E(%{SdSL@M7T{{E+bca8%(y;s{o7 zWZB5*e%Na-$T{FXcc8BI8(GtD*v9A3zbB!VN+YAHTKP;p7L8R`^_{WqZPBMN5Y>li zBJ5nK9sL57W5PK&yshXv;X9h1kGNE%HGdQt!mVq*r{*d3G})4L#(bVSS+2WENqyt} z(98GAb1k8QvGi_}*}6rikNkO)*}Ww?+PA>i_jx|1U0vNqBhD`kk;gne*=8!v)&qwl z)|WfpmUO-^!Ir*igylO)o2f;6m1}?hYKUah<(O0!`retz9|p;HCGJ}ZROL%o1u7x= zvwa~>vnMAv=5iYHl~mDOl~l_GO-fIHV&Mg7|72By{@8j%-|%HkkJpO_=wEC8Z?H_g92#N3i?AaCZ)$*D%1IS6VXh>7E~@ z4vdE0$ajCQAMU%}iP*eVIWhg>D4)&T$6WwpVnDh-YA~lvrb6ZO6R`r5e-)Hpk2`0=E4k?)Tdcu6O%eRa3=8Si4o)qbku>uM^=^;;*>szV&hHS|(pQh1Y)-Wr${;mlE%Z$9E|q`j?42x89k&V(%O!WO zl^*JJ>P)5BrLcAqoW-XZ*qDpmfDUg%rH78s*GlIVF0}A(N1lq8>I!$b16^}_G|FrCiKf=vH_ zc5v?Y$uNZ64qq58KN8=RoErU(c8K&!Is`8wd2cxdscYEOW%s7rs8kA&LZJXRZx1Xb z^Uoem!9i*89whN{#=~hHnrID1D>4l?e41q4=n6-ZiudJ;#&e%qV?d^b^7)3M(=?Ol!DA6MM;6z6pS4x69=o9QreUM?xMs7CG=GmI z$(?Vw8c&Y3v21T35p*7>`ka(-jFd>`ex$~5NZE**e#GN1^u&>O;Sh`w1^kdd_!0et zzxgA{nz|d!i>o&)lb=y8r9G{%sW=ie5@np5;uBGQ*WTmKrZIJ(cm!`aD zd{rijt~@A^&!_2oYVtx@V}X{yL^H1)=>&kH1ZRV<9_6WFOO6@fm(kkVZ7Tx4_2FP2 zk7`x%G*L!#Qp1*TuoG4+tuaIlu|r@XBo%zD8VG)>j_t-E7y5u>0LF`QoSJM>vr7Um z+0|!*^`WD&%W&9?GjD?YHUlcWpb{x`Gbfiqq~z(d*44Hgqe<}&>X(}=&||`fb%xQm z1Kx79qIbukn(E;7nlLv0OlT*ifuUHIi&yP>5_Pr*q)&6gTqJ}KBi91B} zZ#a4`l*WKi24h2J~5r+~uR z4&_n_xnz_jZl5-Oh6TE`wN|C2c41~wZZ`(sEFlQ%pv%2Lm4hRqME)oa#|ZZ(`z4Pb z{y3aR#|uI@j5Ro1b_oKexf&Y)&NB;y$MLQ_+!$L0+p zAF)B3aFLK(@Qv7=S`5$0P9n@30X(+tXK<>v3ikQHo>)kJD1cyDN^!!x+;L&560ir$ z_pl|;3~&>v_EmQ?F5%8Z1Gudw9Q?6U|Cqt3MS{&$j(J`*p(4TRpzSdncuEts?^gPC zD4DQ}xZIGaU9#7Y7z6&Ha;|UPZ%N}RcW(l};kDBlU;BZDsn~gtNbv{YZK-@22~m-b zfMwY2KOs@RUzKWZ#yi{ZdIaak9!dqyCb?*cJdr{9yP>Ky(e2=778q-B+?nBguL}hc zbwnO35w(TqRH|I(781Cm$TTSJzsZ~4%TAt^QygD z%GkI|Sv8|Yp3qqb;PlOfqbj?b_rN3y0+LKp`iMgyUVS~ch%4C$#FgJa+_Q==T<=|> zwSA65&;6A4u6MVYJ%%G>uiBowuWYc97DMRfR}pm-)an$^?1%gDNb3J7c)hFJE-i^f zH+W$*Vq|VUwN|QoswFRWSK<(+#)~e2fz}3U=&?d$<7x_zj1om}XO&^+lDQE=w)NU! zaKv2Wl`w)LDl(cFi9CFlYFqHks8If1Zey0)cqY;5MkicUzq3t3>w)LRyZoZ<-Dz!V z1$haA(c|Z|zb7lD;S6wke#0mSf`5|gAjY~|5-B>}{jz8ze^}dDfdC7+XTS*ZZ2pCsZ?WE2KksG4!Hb}lj>{QP`-q1Wfg>H9D>IR8rRK!wfrk)*9W1%7 z_JQ#;9y6+_vmLs3Ey|w~{vkQpmqhatr&{+Y-M`C5lEz+hl}s|`84~$TevO^@7R}Kl zd^*F_QyNFrmx}xcb>YV6RO^}2j6C{jLI=}sqx7WTyVeoc=uv!JXL;jU9`22Trg$sa zb&@IYSZQ`eMExqI;cE%WT)+VNHZGdkemOZ(?aspS-_@i6M)kwuXA`k6|ttGeOgMiBKz1gISCcSFN7B@`BAi=B$I zh8FsZTj&(#!x@#8%+Xb~`YE*tq@abiQJ_*!C>y)N<$Y)|m54&LmfDa62?bGhVHqQD z4Eie-rPF}wEqn>fR!XJKG77_;vaf|nWSF*1sF(d8PL~_1r_%)ve1NAt0zaJcAxV`b z=wem2wOtrdYsdZPt{u9#EK+4c$9nujtm&*OSd}fA*~>c)i!5vebHj1Oh?b2AQ2bA* zUPPyE8cTL#FDI(&Nq++<&*+@=)sU>jg2y+ssmZLVtGYf@XMMxsE48q#YY8t-P7m|QkF!Ee592Ee-v!Y~wNHOQ z*BI&fxcEW93YJ3CQ#1GB*VIUt2CmRX->p$Pp=EP6c;(iM8uqaJ4#u+b*+K|6INz|- z7Tfo&6{J0RwL<`>d~@3f23a{WtDHZxUqb7q(213(3bkwRHMyQ+HMAVC12=oWJzb^>75fQRW-ls+)I=|9XzG%3kH&R= z>N$6Bm5RT>nA)r*sH;ZK8hP{fEW(v3ui{bMc`c5ER)5STk|#;T zQflEx_4Pg~cMPug_TEcVl%jfUv#amLK;-vf)06&CkklV82>YI#3SMh86^B<9M;&+} z*a43!{;K!|?+cT4a@K5w3qLpxc+w68`VRDua1S?-T8qM9?f00xxYicjaY}sH?y}q_ zMQP&#Gb2(nFZtz9qI*;csrtU5tY=4OeW5Q5#5T>;tOQZ{+md_8kUsTy)my6RTVmRsvB&`!vszVBl~wwKSFY1Y38>S zuWm_692-Z?w#+YDJ-JrA-hPH$A=_@G@plNA^-o$UciZhbIEAwKaw%ns{>)fjebu+; zWs*|0-%3&ZS;ZgxZZrU#J8TSjv|lYfI7EUuJ>vUZFnM$cxKI5DMI1h)0I0D)xXZpb z8sT4j^JkstVpty$CN3&i+N0Qipy?poD?jrmkqM8oXKL|6rvSG}E;KkSVMfmBROIfF zQ)gs7(uAnx=s$w<}+oz`TU)AnZ1#5Re3HiR!V2cd8;ZS6 zYMUDNg?IU8Wc$bq82WKk0m!I>y7Evd(F#0rj-F^_pzQBux=Mu;G=?AY0&m{iZ>c#)h1O01v>*f;^@ZGQ71TU8e7A)EyUiG!Fy^JkJQUH;oF|-wEY)MoYAFk3ev+hl}o1u+Q2{80jtff4OJ$l;F$0+ai9T5-t7QY>Tl@%)mRd z^Xf(eg6~_hXa+`9x8=YwDxu?DQ~VR6?ses724hlRLEFlAF}K$iXrn1MUdR8Vevd>= zv?rHm+udA1rzm8eM3Fyu&TeZtg`Cp|^>9kznyi=AYDMK$emwD%O8a$JO>L{Y<=wPW z50s&E!%96IfkFW?7*SyYRy(pPHiHPWvtU2&pYo*N zI_u^WgM{PqGX?RLCh?RFnK;1mV4oz?TlqmF3JPR1?4>?+r4Qsmv|Q zm}`idns3ZQ&mc3;$2E&8JLHvF@d-Bx{he$?V;049&6PLW+GC|HLv++>B9n4WoL3Rx zTiV6hC*T_gI4yf~PioUkB5tpMwfx1Djkdcb`Zfiei#H4{(!34e@4J&#Ni+0%KHl`1 zgUc=Q;&(z4MvwaLff+6h6a!iCZJ@H~B=PGF{2AGO0RS^;J!=OS8IU99SG2SMcm5W0 z^9Wtb6kD%-C}hN5zOE4{K>4le8xj&S@Y5M)4!*vGbgJ(6m9HYlBqvDXdO8}45R7dO z*>3DrBS<-19PtqQ_}16FOHYW_@s=bvSQ^zf2=LQ<7+~9)h!ZK92*i%_C@4l|Sf-?$ zF>F(D-R;!N9XkjDUNL8F%A()0}-q_K#Da!W#-N$52k zGc)E>r^gFxo7rqEn{~1g-^I3Se827?*SbPa6i4>;HOwc%lzdgAU!~%TS3>Ij8jm1k zV?(3oBbNZ}Ms~)@4VFT-Wa~%xRvdQ!|0Swt!`1j7LtGxY_xG14h)J(N6( zdq%;^lbMj=z4@D>FfYyzb1SuyURonkNy{ImZ_A|;7C+R8*h;0mqQgoZ#N$P{>PF&$NFdv2@0TJxGlU#Hj!C7`5#ID(GSo9&Mc!UfG z&S3)MqZCjmec`SdLs;WuT;2bg8~sl!{$?txwaCn44Z0G4<#dr?kqOV}PyZW3dXooR zOXs}5dIn8N6`mK{057VV@Ye0SW$r75Z2B%se{f-Xn4l1~4TNCuS$Lymn3l=k#~}m! zL#FIdjGoC~8ynvcVkUWlIcSR`-?2g&Df}{*<6Mm z$TV6c*kPE!5QX1nn#-bqGB;zFH{|>)yj1yh4@Zqb16!vI$Oa@S?p?iGwCc6|cM@j5 z1n30${&c3?hi|U^;nAD;`Dp>{9$I$X%5ytedvv%PX$&d*lQDEPBvO(Fq&bbz#*pB* z9Ll3P_fBtsoZt;Zr$7R?F=Y3o6EK`gO#oQoyiN5|R1FPJwfzE%O|({iQhVXUJ%%?Zyl}$7oy{Pa4GvE32n#sLj?9&+YhyOC3V= zl)H6N)kcy?$vzA)9bnF61Xb8Y-AWNNR6yb)39u=w;OMm46;YknYNvnoB}{5G8%zy` zb^IHhRbN3=1V^o`h#Ti3-lGemIzu7c8Pe-{3!*FO!q3gLp(hTLUr76$OM;2(t%Js? zZ%x%V`Zpgp3JFU<;#Jm>x*^Qu%`6EQyZnw!ri2CVStM^671X)VzM7$+PbC93xXCt$ zJ09cxnrW+^e-}dxImZQ(N<}w}lcZlCe|%=`(?8PrFCnM0{D?5>UT3k)SkJOaE6_nK z=i(tkB|EU8p!9mZ^J{Z28A^fPd#Vbd#KY5)(p!Go#dV-)-GRM&)E%g>br98vPC3Sw1vfsa<3B+}q5-8kM^I{|>?FF7DU4 za0oLDjW;Pq)p&-*B}H?b-I@S%X&0r^gnEiYLWrDKEMyR@0C>*e86~fR)C3)t7ggyJ z1Xwpl-~7XUD50Z8fFx#JL#1eQc%=+dv{u{E)bZTjLt4_~=iIPq%7z(MgK-`HWv{?! zWHf*|a(qT_tNPXBmDCwn;V}6hU`!o<4}ZgS=?~O7RZ97TU7_KJ63vSkrj+3=TCPDb+sc_*LAkHqxr|3VR95FN=+*7eSu8a z{b88a3>ea#-eZC+rtT8Yxuqmcc;5Br&;JE}v#@n^p|~p{@#38wle5f?Ds%FSi;*I# zYsK`|n(8m!zbA6;e33WU+F`YxW+<&BPP`{2cHRzhn0s~4fGt?BJ? zX+A(5y#}ZwdX~83dGn-Vu0%>hG6I&kE0nu^ZusvjBO6A0v`u7nsTDqwojSVpj&q%6 z$UUWep}GzU)b@aj9*zfU+&Fy!RSi?1ERZD|qtd!#)Q^~e@6{iYH~mCr(ho21mouYF zrReCk%o}ZzYgi2;Gf1+0kE!C^_Mlo!MBZC9VcM-bHuD~?Y?yMyiYff4_m>TKc(ORrAxcsp{OGZ_xYR7z5Kwq!W8jlQvc*oa-iM3TK9 z0#_0)1HBqYDmfs}zN>@-?H=Xzr7>;+*(GsdVSoh4@c7LN7hKk0{D);DPV*tP!4$@zux zJ{p95wAcJgaL%Z@dIL?W@h)pu8~Lw>!K4d9aT~i za$ugNVxaID)zUpa@YAc}98lJWJQ0iLf>uc(a|>_lv&P89`2~XPkRkRtDA4_#HzW+l zJMBd>t!jJX98r^Je==43hz~06k}1ylj4A$F>wfnywc+FP0#uwskQ1Wk!D%S@PvU^G z5nCRU*E3TetRzz9a@6B%d9iOl2L0szBIF|jKFf(KCZLv@8keexRFZNMG8U9I0^pkG|d+;O!ahtCR!9;`U`)+BZuq zH63>(60kY@ul2vnMfAeAZ&@;=?gKbKL)yCrLswb9&UGr*+@Dd>zE)1B%M#M41(NmT z{Q_X6DQcU%yz?z9)%C)%o?8hVChV5XU%cb^d|u;$gn@!9lZj#C+H&BjGybC-@h!^x zNw1ZA&G>MtJq6LAdQAzK|ktlm`GS_>*S?~ol+Jhb8#rKV#D`pmPQ zCL@zh?jEz7tT0SPE3zv8+APJesBf>Wo)Ey^yIdoRp|&MaX$Sjkz3*|piT`UMw5D;; z#7SH0tk$$74a_6gO!m$kIv7=R*+dV?&pFrBnpT>5Bq%OyO91sZ7u#*2&(vq+g*Jp3XQJ^U9LzOmll`N zlJVQI81L0bt$yjQ#`sldK8uEuVa5Ag+9pUnCz+zit3>@p;cZ_jU}r*53g?z3DrT+0 z4PI`|+8uU(3Yszi-WdC|StFp$&lC1LIMZ@Sem-(m%8^^6lA7r{P^d?TI4A7g0;2S^ z&k{~zqgOMFJz+%gWOi5@lQ%b@b7A#jZz^vgF9gM91s+M|LBDyi_w;e&02cmsLc8lK$ZY5I-KVf7MD^A(?c5P7`lbz; zTqA$YirnvnrFyRbI;bW%bwMv&Fy7eziUR3Nz7S%xo=Cb9#6NK9Kug zp6xprXX)UmuAorhaTHIniWQMJo}bqk?3t}swFvnp?%+^0wKcb?DJ3sR6y7HJIXNAm zm^V%+XCi%W;F5w;C}&7S5TJ&4k+J*H6z>qAD?+Z71@pS5(mP$})D~NSiKNHKK2H-N z>k*AykeJ8_T?nk`>0DFKutSb=Lz4ISi@_FGwjqFXnpCEyc}yAQZy&Y|S88JZJK_F& zTX@u@L}y&Z+D9x&*+qRGFW)1{@@@KJB-C&RsRRVrvMdrr+yt$AB%O(NR(mw1;H@#p z?(1R$XbdCQkhJ~gV)yl^uO;SVl~s?sjN-@T2mK$3qN!A{5HS^Q&TP35AD3wn8WH% zz&y*~;5TE+*nL+|zE|>7s!~T{zAAk;=asQMqK=Vn4;g?kgeTOT7t9p2wKY@^5inU&$CYAk&4kw;(uGJXg<^9ogg6K!|L`@VdngX zj~)&{a_3)4!mO`4VLeOkwSqn?yALA&18o0a3QB52LP>R9?i?(y{Djw1zbE82Utb1| z7%txZY>z8;NDFI%(N0|O;n>>JOl*aOx8cfQ<_x;g{-rMtNW!eCKm1yLW%uiugxaY~ zi`fc-!#DA{`QSv;4F=2+G0Y|2i|MD=1e%f4)_#Wy?Y(vojMmQeIeas))kfNG@Wu9^ zoBiW|LK|4|F%s-SuktTaJkQo?OV?@Kkwy9yiIplH;eGt_arF(Ue=Hp@*4ewmw=2_l z&wV2<2JDFmEtRb^p|Hi~#WS0YDo>}w5dVUX=%TbC$jy|IqO6wVepop_aNgu#z3*+)|pUU@%{jg5TtV6zG~-R~d? zBXI!!`}+T6E&dlnMpY*dk4Ui9<55k1MT?seXRDu+n%l26D2W`AswJk{zs1kp>(EiE z!7`9Xo0tV&Na4^}h2N#LK5?qileqHX*NkmfdYfSeCj)@$xuY$5nP(n=jF- zdU*`7;`kVcXS983cmLSvJ@$nzV?a#>OHb6f@9|66gA*XE5hkcKj^91j5NC6}`tr9( zcV(Ij9+MV0v@2RYsn=DXWupPFXXcojDFtl}MB|p1kp8qyFXFfvx1JRvU9vhsJ?H^|&fnxKA#VuD`zBVJDvq zuHY)7)(yv|gL^`fW){MHe9q2p`t;n@8ExXmc;~E$-g_lnc1?B1;jW~QY@sw*d2*_Z z5qQ!e)UqaOc#H~oZz71L19PP(a(30w8D;P=f@L{#Qq{^BZ>IMRe{FRcB$AUAPHy-B zS!edy;CUkJ$Kfq5c+ta@TJE?P|;I^IqIgB3t786jNOM?}kxn(xG5Od$o z{e~0}d)(cU2Ia4aV8tug8v&@>qQ)j3hP;dYIQiGciqib$qZgK~Vlcj~%hk$-$(gh>*EROu-nhDRSJoiV>}`Ll_Z;MjhSkXgpe5LB@QPU zsF;4c_rZ+~`JXr-7d9AeNPZbg4$u}A-zDYp(|>@VE66~!k+1b@e6cohxJOy06_+xO ztpvh^vEOr^JofGR4=d}HQq(y`K%bzGWA0w?fj#ry zP<54lF13~3XU+#s@${k%kxK0-RL~#rzyoGj@r9F)M9$lx z~W;85hTC-d6tl zJFD+Rp;ZhQ;`}w~uMz>E%f)KHO5>w zfJ($HQ=kVgk(|IW32G}7XD%=5l79=%4;17Bj~0kh>$-DAV`MC`Mn5DcC|wl}?iAKl zo3#frS?8J-RqgJHl0(joemT|M_rO!d*2g7)F7HTq1HhN= zO=+n^E9F$%OM^GMqW?y#l}69gTVx?58p-8%kKL^)i?1MNn!_>U4`QKLL3{c=WlBw;$4=vhMcquOQ#*qeT}Ln z7WX*I2#L%MjS^H=f2YPy!RoiI>GpG!i{sfuu(-b=*<0LPYW=@h^Wk>@Sbz0D(Lu^N z%8A+Qxd+koS(ADJjre6%Izcl0irKB28(R!l&^UHgL7~mko0>CC!NJtUW|U4r7mo*` z^oBMUKRJh#DXTw{)|0uig7b4|tFbXH?HNZNyJ2z@Prul>2cZ$qWAlo1rtXDd?aySpNruXzPoAyREBCkG31TwBIZ0 zW$(hnH2JUv=Yq@i(hV6Sv9wHrikm-$ZHuE#)bZjP$TOq2ClS7*!e+Jot1U+=?@63} z|BLPwMyF_{(k@HZDQj1|q5!+=V=bC39CLHp?vJSiFwDyi7X~4K7!oq@b0~{SLByUh zq)1)8PP)oPvX(qB|H|R8dCuN&j2eypEySfEh?cj9gE@)D9oSv3*T`F3IuRQ6J3dzy z-DxDWwrP5Hw1N%oDvKeSz772EeiLwy3QIwt)*Q-ri`GtO-OH0LkC4ONO8bG*AGVe> zjQ|VoD3qk@=&9K_76_j7>=YowbPlh< zAK#cB`-s_*{+lD>s(cAG1{mT;6YAgws!{;a#O6?bN&_=dZjf5h?FSk|OgTnb2rY}h z?{Wh;tQ#g4p=I4ItXY{#`Fl(qiY#Q*_I-s3_6kJxQd$BQgudCI4uJ@KXWSrtBRqVq z(HW+3bvvR_Yi|@t%@_t1{_jvfR{hL4KwM(scYdj)+HQRRLJ z{T6EvBRasd#P+RX+qQ}{M#(*RloLKZ{ ziZN>3@u?P>mAA@nU~Nq^ROYb+{ALC_m-DZ&=T|563_ci zT>@yGn1Zuepe6-*VF(S)^hZJQzX*mW6nad}yMu`)q{w#TVX>a7_5CynTWp^8^P^HO z(TRnG<)i+%?j1=_+2XV+odGG5C2Nnp@a>ltR^D@!|Da&g-#adT)oJg5FnwT1M(_2s zw60uvD=8YL`J~F!Q`!9JG_A!Z}L#L7AFD9fE#?TNj40G|&upQNmWWWmwv*?~pMwX=&vAZZjU8=hOCaw^u1GMreI?oR?&}qs z%MTQX&f6#;F9-d=Jga&+fFxuXp#SrlaxnPb%f1)O`be;XJXwitde6$n$5_u@x+uVyR|DBI?UOx zGAyw^$X&=m(@dFBy41CVQk@9%YG)M!(I;uA7@8ZJ7OIRU%aXOGc>4r+BWzLh$VaGU z^?pzacSEf+qwJY-w=C4IMy45FD|uJtkEs_Qex*he|%)MweZ5W5fwN?y+4Bt*?f^X-(<;C~FiU}r#4Q_SV{P~79Z#&oK?bNo7>+qhjmpVqwJRGcPtXZDH0NB%!_3~(b@ zmBqs;U^;17ds8GuqpP}Wv0Fsm|6T!4%WCnel^7o7rcF9(3OSNTl%Z+*V&N=VQ9LMK zssSRcUbNVyfXa8cEk3v00kee9;n|g<2^{9j)q_IczR`01mRtI$2)N~xU(#W8B+Cdj zX5Tx}H{8F+PFbr|d}6vWIFbRP@^?FCK&8+0f6y=LLNLQG??KT|3RSb8UwMe|MRrrv z=z6g=PvJPs+5Yew!Ty?W$D@s$A7Dl?5=p-gGKH??;JQX|v-u3jMaWXVVId0C{93r_ zGJlAeA5d+7Xj&~B5}sR(uHHG3HcUN}Q_2uO9h{OYp9UrHNT}IkAP>Mf=z1%YwJ6j@ zh-&Da&WYcK0Ht&}t)<}QES%idh;rZiezVB-?=4vtw!guI8l7E{=p%$C`SWTr$zojt zi!y{RE=RU02HjEnQS1`!ZDy11n%iBo7=n?ADxo40MGKmj zmio|Nb&;y6?KA$(zWe##EWI#Ss=ji8s;~f2gO#u7wL%#dF#CJ!;-h|!_yE4sqA*p= z7ud@`8xE7tle`m?=iHUme#kzaMGbDx_fh#z)Hk3D(i@QTm1ork->H_wEn8ia4VFTg z*XhjAVv1DBuL+>f?ZU<=z7T5+{%W+HebSj-mhe%b|AiT2Nv3(!RQ1%~_JC5ioS`Mv zAWnkq-ZA)%|AYLx*7Sw97PiIigrQ8+(%1kOJIC3)VMxY_gwauZNAc%7qRU_IGS!n~ zRtz452GW>$C;MjT%a6cv)7byGN#hS|VvfB?>o^De0gJ}L70Ay!V+?vlO;2)Dni)yX zPuH$8HZE#va^pJE{L5~ZK{8&U%DJr$bododZy1m$1pgBeHgtFr-0T}M|J7>!@cv0R zN3@RS=H;U^zTxaaiS6{#3!i1yk<%Tc zB>JJMA`pwC?Y;kG+SSJTy!-|e;EgtN1^N%+Rq4wB0s3r2_0xBF? zSB8(__@wv3oPc4M=CW0*7LE(8gCq#*LWud9uE)@m z9}z@Fbp!OHq=$6zld@A*ch$`WxVMn91{$SIPq2_DUT(bU=6uFb;JKLU8-ft&{jLzZ zxX;@e+|;&vQ}<38V`L0>UHrshIrmULQwWq0=da3|MH^eAzXNRgqcpl*5B* zp^6+Pzi5Kk8I6<8HFPWjI-sk4ktA7W)4uP6HQ_ocf#7$U@_BSh%>{2Nj$&srRT6;p z1eT#)#*nQ;gQ2D`#*m#3flGFOFxL-%FviSkX*WOB9s%&Xwz3~MIidp)z0_N@e)w=y z5eP=r+xR!t%g4pan$HkmNGn~m6sp)$RUAj0tO^cai_65gS8DM-N%*@ZaULw1THe=E zv&-TaNgN(wd@>xk01b{sxFepLI*ilu>&ys|=G&>wjmxLpFvrKyu-y#pt79qboul;N z<2E5mLh#kn?!wCi?cAr|fQHONl7jnNE?QIqIPpKcLLkH07;+2wt15s7Yp&xe=HNBY z?zjlCVi_Ldhpr+)^&(zZ*ver9bHr2^*g=4qDl7~Jme`{xTLge;el^*=@)sMcffe5jKLLoO(E9`Ym6DXh)43(P(C!V)cMtfo z5i~&22t@AHZKvS(KU6z*<_TlzjTjk{#*WAu9k_hXmpRh~yedQGK$(6x#l&2Vv#tYu zqxpk0750_{fZi{0{-=hnvrP|H6Hzm-CP6MpUqK;^jzWYq7}Tmg*+i3-7>w&Q5n=FG`*Z{3Jx_?rUT0&0t1 z?nf!DKT>RYqaIDKeZXV9oC|l~q;n5Oe8Tq?6`PI6p8UD#p&Sm-3y3egFH-#{D+ev~2y#vt33}y;dgM4zwc0kgb)$Uox=QFfN>(D`2eC)I6zn z1lki-3j~N#7>2@+p1o_q&tly@br+_}8g3o5D3jjGzc~|eF0iPK)x5Z4-LQH!Etg@| zY<(?jl){#L9P5l`iJ}-4HCu*>IfU36N>Cwr+x^&-CYvKu z5+gyQhkk2%Vv+RpXQsSn)}fy}MeV9fu< ziSJ(w=uFY>dAODa&aJ>%FqF+#tiaJVM2I8Eq_y$sUSLk#4+lNas$EaxjtBsa7_<|e z*SlZ#!%dpL5%%x@CAw$jP4#?|Qa|knM{D5mk17F#A$LF@kv0QUz>n3grZY+A^y_ZK zoutB<_tz)|Xv%C}TQ3hvSAA5N)t*5FggfI1m3xO2+v_6liT*QXAG8H?RV29?U=v-( z**lpNTa;UBsb+8#UeB?9^XoPPBPW0jesCo=AX;F}xVH)|`$DB8dW9S&P;i4<0BcT^HC)L{6GiR&ZuJsY_A3y4hOttV z{^=!5v(w&LQq1sqc?!PIALx2UI|*XxbIv|&_}?0z{uc_-aH0OHZaBFvX4R!}Nx@ua zA8I@M$|up4_nJwzQp=tZR__+V>RSIl(%v#E%J%ynR{<$$>29Q(p<8-Z8Ki!6w7(kg-MuTmLY;}fHT2NhQH0&ikRk34^)5fkm9ReOxr!LSJH9$V zIO9USfc{~>fa2UG4Clt5NW0POkuQwWJo~dC_xMuaGdQpQvY^NzBBG`{4z6MA7O56Xl7PcIQq;`AhGAD9 zC(7<*&Ln{!v2$2giRU*n@`IB_AvC<8`uu~U;oTmv;LPnSrUVh2&MVp#o=+-Tqc$Yk zO&xFMzCptJ7;W+E{Fsb%tH*bRwKabCjq z6>r_J>FZo^+?Dir(eBdi*Cd`cR|Dh$ZqG?6@6Z8VHmgAj%jo-#zmtF-$vhV?c()%U zej2t?qvh(|@JX8qFqtr6m?A2oc-d~R7nFV< zwdP&^;nI}zKT5)ujt2gMmv!T#m$`>cw;$tc8E-j0OO<2tw(0<-H~7*p*feeAPz4Nb z6qX@Z$uHbus5#61^EQoS*;=;c{oCf$(voxbfYn} zMPcGmO5i-~b%KxmR<@#hF|u<0U8b++ZQq<+-r13oB}q*f{?4GwKlyqeD3^FQ%9ysd zW=@Ph{im`0d1nK|tdy*9Tq7Jt4LyQ{;;@KsB+B{ajn)O_Oa(?*1fJ3aZ`>>j4nN_S z;<|itqzeUB|5dWkfDl?D@iYqIo!Qm9N-YC-;FaXXYEJ{7rc_&igLmBga2tWO8y9-# z^znW$Y>IQO?K+k!Lhtl@^8*EMYxC%%wZcT7*FRGhAI?{~dO&&Zf7u?&GnKDMbCj-s zWCFszZ+poyrIDQ)i+$GNA{hX-<$YHJ@~q%h*TNI2Ne5xJLsNYnwT7+dB~CTv7VOiB5mwmGvyfhrsVSfjU_W8HmCt(~H>GQ+E92<%8v~ zb+^1X=9D9)&?dp;>S{J%Z=27EJi%yF{MDr#Zy$9JUfRrGt5gm#KrC3m*K%KdlI4!_Ms)30H{vJU1{_<6?5)!|^ zY@O(s@UQM{ZIre!DBzS?7(XUdMWUu493{SOLs6M>h#D11v-{Z;Wi>0mb4uY&LD9(` z&QGfwa7yQGY8a5w{v}MC*lrgynSm7_T);FHZ>QwJ;De78kfMow(EqR`Unlx}PThVDmPl7lC3M$xyL5tq(9EKAtc$%jeCyR zoYD>mf4$V>N;F;AE8}fSBXlsvaUSAczmR%I0h;WG#T`ZmoXBb2TA6OG-WICPZDdcd z-8NSsY?|!LClC$}6Eq4UuaEhtTSq1rTKf!?8{JZfcw(@(`GYhzcwi4McZ#jQq15>>mqhYvU`Y}Oe13u?gfdm_p z{~GnG3R{}ENQ2(-$ zBFdaazsBs4jU(9hQZu5_r0*V0>HZ{T3PEb|gioM6sssz4ogoRjlCZA4XfnSxxubZ8B#ZhwKuU9}MNs8z8b3!s2yL}0P7 zds^cdq8hK5XJ3hA_;3H<*)}kR8DYbxkG}VfwmQ_sw3fGPGgtnBFls!-o|QsskU~B0 zeiGe{Jlg%Nvitc#_ft8Vr_8QTaavC9_(++T{R{P+pE|T|BhRG5GOF3HfAklFZ7O?= zoIfXJRD4YpUBH4*8gY&vG>VVbwns&fBvpIx*wQ=Mmb^DUK&>Ml*`nUMMH(T4%XO~4 z*rHxo8t)W`3{7Mo2y{rM2!MxPuXU8Lmvb<>bc5m~zgkip(?y^C*$C%<3Kx@7i{ui6 zYpp#z9O;_1zQq9}Z$_zN*4Ni#x_1OL3uT8int-@|Vc%e!tp)}6hI0+<<9w=LaJKd4 zH|Myz9MA03>3?-|4z@O=Ps}er@sr?fgorn);l)!(e&>uk=FqjF739qjpdn-49OgA3FM1l&}I%ky2PPLZH&X~}~L+z?SX6+6l zv-?1_^MQ)w177+EQiBhKiytUKAJ7a2C2rHAuZ=h(`d}9<9J`=)FO1sqT zOgkQ(_G^?sjscCS?jRUi-j-4I0+u0<&yR0K2P*>b>$F`}l6BmoJ%Wf$9oWucyXPnc%*vN=&8adT|%LD5; z7ng4QzUlpGQY4MyU|jal7NK^$ zVz?e338E}E-7-Jn){YSZ9jUinnW@k(?9ECD{oSsd|BRwE=($}G z_D;}Kk|6Z-AQXcjti>RtaH(hDAJ3@bSF;t5s9yGgIK)+BND6{b8sx)4RYUGHa=7pm z`ceM(pE7FSsl3j%k)vo!=2;n{IIJbdv+Yb0UTI6O_^h%HD$&BJ1ud+IYYdVOe2I?% z?Z52n2plRjR@Hy6k<2@Odgan6((d{Ge(mG&T2%4!!~+HF{Tm@tgarvga`LFB50g%< z8)Vjsg{QFh0ZPSTbdi$Paq(KbgUbBfirKjZJq~sksJ<7uyAH2hi@0-IScdmR4u?- zI0T0e=d4yWCBDl?D@`S>8oDL6U&cH-xP(@?Tt^fJf{ka+euz-gitl>ih&Z}*ILH!j6hZ!8FS65kVtZhGU$JOpd#RZGs~kPph> zzZ{BeK{7l?Go#rYxv&nwaI2g5x;g?a_LE&6P>?tpqbmv2yh}MmO+Y5>NS`QaYpMuC zG}&KveVv&;!+=2P)Gxt8_936J@^h^%)7}CVamp= zjT)>Csoq0IP27_K>)Fw^@c2(OHpksY)fBYoR$AY}gmJvAIEa&Ql$e&1@bp9cjNuIEeK2FqJNfayDCwZ7peChB)R7Hbuu`;--v z(3Zi;6@!@`9q06&jWXUg`6-?%JVP<;8Kr2JRT%Dcr|=-zkob92S0ytfVWpY7Z_&~5N^c!?WIl+l_(I|zdPcj~WD2BwWMN$J!SlaBk5 zZQuIKxC*?;F%z~Z-q24hRRl$3%E=ZmP9^Lpye4>fJaVKu3_X23B|rMDhN zEpjcQiO<>}aYwzk*i>9+?-IrRef@*YjP`-=rxZhjAWaIrf_?ibUY7sySjxwOa%}dC z8(U7dxj#t;dypfsYK<%Lvv3&#mwv69{nlp?Gutw)E{(^?-brwd-b6>UKKC4zBx`z9 z-XRgYb{FK${9Y!JGn=zS&*9ksq6OZ&>4KcVROJ4V%gt8dYGhS+IZ=eAfJ@`Q#x-$e zcc|EO?bUcqG}MzL)#s1x;lbcArXm9D24_k2@$XwTfu6o5$X6+8al*x1Y)x9_;q@p) z*SJ7?hyg?O-7`PZB~{jMidK;=RZ+dXSl^zaR7X@M*|ctN;0%iFZ@B1$w5=2@pQd$v zsJ|iWoP|^zv?&tctC^Px`4gx5AdU_H#!;?##UE|^cZ&b7wR8^K7(#w-Wsb7OdG5tI!ofJSX@}EHI+nld&Pg_K3)|`h zMTCVlz}SNGJ)yAHwL6X>+kOH)N8an$+4f@+_gPLbt6a1SoSIg_UObkftn@yr;+?NI zYY>KO{&TW?6p?(?=jO;v=Ff@DQ3TCVv1y*6gMu*MJ-NS78KQX0-Sf^fyX@+Md(4k` zCY9Q{jctf^HU!zN$ZB+Up>OnlFu|O`!5XK2jv-xqYK&q8%4DgU3Ffw?>oJji+qR7; zil(o!%61thLu9>GA#m>Go+Z87vE0Ruq)O&ca~*n3ahw{n@s>xD2pA76!|q?l)eJdKp7$Re215 zLkAUtax**2kG@BezQ1y2`MyrPOSx?u$k;o`y7q$tM#DGos!SN-cs|v?Q=VAVq-t`Z zt7%N*a)lCBoCFOidpP~tYjQW5-^;spsXpINU&FUD$vu6x=6jQ2wXtvek`7ipQnH-x zz7e#L;cyhwIg7rZdz&JK0@6UjntVn%iG;-cjDQ;n^XoJ6uSlr4LCD$NC}K3%yauN^ zsoz2HGxRT}%G_!$I5Vb))@^drpgx5R=q?K%7bbmDzS1n)!}*EHs@w0m+h?u(*)uh2%wi`$wiu9 z{50r3cY+neJ}Z4}z>P@~0`+<=_cw8?2>3pO346Kz%{utW9D&E5xkt#c-u(Lt?LJ34 zdSjcleopY;L~E@Zbiev-k9m_N>-#{u>s8#403y7SDr!7i?_)`$G!Z-~>yGu;heDt! z#+9QNPLg&hIW?T!E+N-XN}?i9@$dMLp`D2VCz7mFyI^Mpcuac<1lsEQmp95Fm@qkuUi>K|-_i;k_ zLEvgdr5Lv|j_BsucF^HoWJXnHafqMO0)<;PB2R0FAjg~3$ZXw0%m5Ycaoz*6mHb?d z{vF3~CGeW8p5yxvDDIp`fm9&2uZ3R(1Kz*WtKpvZC)UsDgd1AVG}h(yKkX_QMP3G;RL zR9_lUChDYymBD{Kh^6gao?%sVy3M58XPm-@UTkm5`F*DF28pIeQ4Hkma-!M#uC-bO za^s&Pw*EYG;Ck^Lo4^fc{W=uEC83P9~ZhjN6-c zI8B!-CKn(#O%``C)Z`VW`=6lC)mcVAM%`nK=v=5*MdeZ1G<^@HAMNyz=%LUJWBPl1 z*MI7#yMN2gRp3;DICdZZ+YKmH$4~oSzOj`6wrjb!>-^=_zKxNAtWc64)q8Zjl7?HD zFm6FyG5>gFc#0qiOk(vr#h-e#BD-i|hgfcXU)2EjADm0f7e@dtHhkU2I-P~15oRmi z*5;9gWR~RLe<8iZR$fdl{;RyXRJCjPbwBz>lV!H=&X=|ON8=)NVEVWC)ZrnI<=5TM z@|x9#EHBgRVk6Fg?`fpJ+s^17*gxec28mAW$F;a)+zBU0fmsqeVxjhV4weF_5}GfG zJMu(OMQ*PES51ACxz=AD7^Yg9#TpAK>xB^#n#Gfs_x4%NtzN;qT#{`n7UAQah1}RV zc4OR)U4E>6VqN^n77;KBZ3DzcBxVyER~-7KLsvEdMPCQC4Zwy#+U99NHBB%S{jI}%FX7q^z>w!Q89m78_eNYl|!N_e_O_R7u^&kEhfExB@9 z_{MZ&=;MRr-&cy3PxsTcFD~Fbm79?je-ktz)1R%1djE%fAq^7rR`+kMOGgJ%B%fdX zz-qhiab+MuzrCY#!-OF0^`!27yD*Dy!LuP&ns|ptcQLd6lt^fPJR)}y90bBwsMFJ9ky?5W8WDfPY<$qbH=zXOsC&HEeAJk z4$ySvRJF|+wOZ+mwJ{&7)U$aD5GHndjihzmhaS76(>v9!;3Yn+Jj`ynuxKW589GG10W9rdZ@l`$X|qsz8ZpM^^8|WY&0)^L5iitxG_df)2QDGD1;m(1 z$)oOLCzDt0)x&l%juvrfz@hFqjvBYF@!Hxq{?r<0)$q*WsrJS>LL$h*mVHQPtxeYF zq6=nN0lhfJOpmuZ>nQ4FwuS*wz!}c`&v~E}E^N0aTyx=77zyFSz=Ka8Jid<+_Ewk6 z*kAS?&=wE&J=T9l_(KYdUkcMh3g<;Pnrin8_HGQvZfpuEWPI0W#J@jmu79uH^zx#X z(W2H?h~pcZgR2^^jCf!UurTOe@>;=pY{-tRwlCqM(*enbwm#{H%bGTMM^>>F0HjYE zj^(lS=W?f&8wzC@-*hW7xsE?i@o0D37nj^r^u9LE;T`|(DKl+0Ovb#=MxKrI9yc*` zhr({5m}~A_Oie&smR8C7x(L~!)mJhM8t^;kFHdpoQ_KVY!i76}dcF3!@_>8Z&CtSI znjyH+L~ro;%qE2XJ&ME1p9WfnMtMogi{pMhnd!ObpNbZ1CtB@yP(Bqci=!#c@q8*8 zeh3I@y1!HR*g}eXbD_hMi_rEZY zp$f8(jM7EuZ>xXNcoRuZjY4!6CcfDF6zI$l6ldnL%$)D@&u>U$%TXAm>0lWXWs&hjm6!*eaWq_uch!WAX z_@r6kwiW4--BQ(_;>UOs?Gc&kOyY_91J<0Iq>Ay3`x)i@u0IvTsqOw~zwTIEjD4RS z`f)An7$T#X*_M@kLCiM3EPt<<`$sf^snkzY`B>bfgnhU3FG1OkBYZmNW`N~`=SO#R zpa`DKQL9pXCY^^9i+N4|Qz}SE2-gH&Ieo8Ic{Iu#87m}VyyU-lwHlYaV0xpfq3hS8 z-?L&<@p2EMa6AYah_TaWR%9F`zn9EVN z$dE_UWq&(z;%EdI^Gnc7@O5{4Frx&c!3s0VO0Qv00My7;HJ3;(T1oyGV|>^>*x{P; zdM!-6Zsn)<(9K?PwDEVkhJwH#$l)L0k zmT6?ai+8pmVv9vCw}5<6^@WT&O&sF!I?}7>=E1l%8{IQSKQ~xn!M%AdaS9ieDwmtONoTz*WzT zsu|?2wD)TR0ulnpkD@Lxs=ucp^!((zY8W&VT=4}WLgiEn=%enMj`8pwmYY|We*ciE5uFi=u9#?RAja)SQA$i4}=xs0@Kd{9)^R88|2Ol`M zLgw{-1%J<7rTo@iD%4B4KoGLE`$(49FhZ~amguHX`yyslFm3OubFOQTZ5Qx~!TkUF z#LOoGJ~4qNCUZHkJmCz{j-1#T<81K2kcWttL z-=&T!AN`gV_U}n1cvklX<0cU9N395at*+n0t~O)Trhz8B;LFy|CaZzYb|guxhl7&a zqo(JwlHd+_*E7lcDG3LoA4lD>qS~@Olj<(b>m5P@PI;bYix$l*RlZ1)9s9PQ+jq^5 zVaNAF>ay)uC=YxME(!!GSP&Cj5w(zG*3B)()x^Y(S!fLzn>H&Iee=|edMXZ-@F8h_1#tamq@_rqBec(O~iM3!Q_8`7$j)R4a) zE$@CDf&09yXTMgwJF9axOEf_Uw16A@5*E=5(j;NVfXi9@w-?Fyz(pBkC3_4OwnhBSnOWkPE#V2!*`|>^TfqK`X!@)`pC{4m<_eJ6lPwiGk@wVu&R9jtJc=Clz8V0riP~0&r$|W;7fZ-V@hjEb4r^>gGY-;^Jf{F&ZrK%j-$?w zE@t5iKV6ajuc*&Su1}v7rpNFOA7H*n^9%~AX$vLs{Q0CX0}x*edrrD`it>8HKbPmt z3trd*Elh{6|2GLiC@RU>D2-Ja^&2_=Mvf!4x}cimg3h)OmCcIBwo!eq%jFht7xf<% zBARAv9D8i1A#8sl<;#ThqWf?5y!BN&@{U7lr~<-D2%hCgm3hYzcPi%@xTj@pv2~4b z?y>oT#tu)Ald5{mS>AYtX>45|Vs`;D%3i*`z%JvasoCBfRRxW(?U7@(NSo2#*wtbD zw#2IJ8(F0G#n9TCk+YE|Hsw4;&FbuRSc+IU)vM{iD%Ofe)VD2eOcJPa?p#fQrIu0&_VIY zI{%AfKA=APOWr=cftO_2m4(HIK^z%Qr-%v$W#VQRWm;fNMtfL71fJIPQ#zj&$QIV7B$INhc1w)&5-k6a+qCeYppPrMtvA~Yj-v6=tNs1X)k%NlF zw&=|m#5ocABjXKlStLTdx+ryXXMU>Jx8?l&FqcR4)wqMUWPtJ%Cn%+kciffl8l9wMR9{~hd%(J)R@MBdq{d_8|+~>RFR=Bq&y=v529fC zfbU9!Ui#sGh9{ojApdP9Ae|2=Wa-Du%*kzjkCD@yl}p~qZV7&EpB>Gl6U`jXRG(po zN-yVKny>@#tMuI10bylr2|yH=!MJCBNVUm=QCEu1fK=}Vpuugd3IA4(ua>|qnQD3F_t8wn_z=k|t<%E{DINLI#`oCK07&jk6QounzJNX45q>$*E1Yv|TCq2_ zUNxTQkZRYa|08>uw~bw>Nk0#wq4GlE1Tp?@XY!S$(W@4kpFEr|z!EB>hFFbu+PRTj zBmb##Ia#bTQ6sSM1$PVM6_g4~C%Rp(QME%#cHVv;+ z(+K_w*8BRpp(92G~aLIF#qB!}ju7}*b-)4F1%i-|NPox>$WM4SCj>DMzY zZjn>-*-q9ThW^I{_x;8bb)lCyzJ^wpC$Hvp0#6{JB?P^_R_e9eOU${}91s+EMFr+L zc9G5-9RpT3Rt#2UR*Dn`R#sMfRx(y2RuNVo*5|CRS(#YH2!kRsB10l$Fgo_+xF8S~ ziIKVtgyH*ZLp>hfR9(Sb>sCc-2n6I4s;hokmzgV}XS^`5XMbaGZ^Grk5>*FlaYfN#zms2<{6?rV-n;!ZXZ|>O!3}LeQEZrB4PIK2z$5J>Rv82Fc3rZ zYLWAL@JVEA>`UvlLBZ;)&!~(_@;C=N@=*X&deyAR53#moSl)RrH$8c0>W@I9dj;oy zDC`7%Qp`M(<_UvZd~OL%_q~)l~l7T?roq-pj1bXhS{9@}R?#kGvaqd*=}Y^BDq)*b|XFq#D+6~JmC zjLRrJAj!5-ka)eIGy7C&k?@YIm;=6R$e!A)5M`1twzcF`~&qMu0()k?z7S zb!i1xJ|?U0En|e>EsuvZ>G8ds*&{_;euR)ZE?k4BEg#9B8ygm~F1r)#Ghjc7A^=On zafQ5!e@ZkM^o->xYT^fs|DB-vE%gK#eiW1n^t1j%R#KAk)jCFQ-7lDBInyFr5RgkV zs%}Xsb+E`@*DH{}|B@Ar>mNYQRu`;xhxcDcL7&56hMjLapbuu6@N*N3wL@4q__FxAo_pLAUIDwV zaOnYE#r^Xza>t%JkszpNfJoYq4rH+Bg33(Ij56st>@JF)CsjFn)69|mMyAyB?j}|B z3Vrt3e6f8TG!jy}b+o}(=ixU8nn69B9&U1%(|c!zv0~7r()oMaOTIx!Uikb*u-f%G ztjyo8gw^fQ`Qr3NX1?k&vRWEFRIlxFuOf~Hx(EK%wWNSe2R)6NM7A*5@uO7Jc+f4?lgEy$<8qku@1gK-05<4kw$DZ~TkQtig4#b@`F_9LjxEeKcG!hv9tZ*=$p_*(j;Q8ucG^%}5 zmt{^bRXh0}@KtVO)+|@+YS0$AJ$Lqm<0ac?9J0&}bM=DiNxgSbkNsmwhVx|0=9{0u zDayj2+?)-b`8vj&@@0At8n@-p|MWz{mF%;xy?UvH24i%a@`xfxx8Gc^8?7IXAc&}8{m{TO%XZAkI{mLFOg^0HRQnhHgi{w(N0V=?VO)!q1`Gd1KZwn$Y^gKN2fBM#~}=@Ri`+)j#1_DF7Hl$KTk_ad zn$Q5mgXF`V0WV0+b<)db0=NtT52eg6bJfz^ft>U@w+cY9<$xmr!{GZZWo21ZHy8_q#-mKZx#}?Tk&zJ#f|{-w{UvG zH+zT8Lxbi6dvz;8VaF+oY197h#5}XN@f@%_o8R;tv3s)-L@vE;+eMI zBrEA`@gc%}PFnk-ZBEOQqA$B;IijoUY=d?Xwz)P~R&kyEWD54o1~Yb#qJPmR?1tI! zEC`Wj!@-9WSIyZzB^s}lplMMySjJMyQs%0YSPFcV*_GOr?Ue46k(82@rI-HJL1wjK zC1Mq1twlKuXgd(x7^zG0uk`DT(>>3#PC+%bY>j4tcxCO`%oQUH)z(q;jNj$Xd1l*Q zglNuBPIBn#v2@S|+^x@f|rW~AoYaiL0^7?=!bX&?BApHO#wk0ITRdQMWn|s93Pa{M} z5{OQVLZ^_gDftucJleA-^6WjAW_rOj1C*{|^9s}F+Jr?{y*d60aaFIU-+h6I+$!TF z%#9sd*4KLvDQqlOr#q@?65%+(Q(1A9AjP7(BQvPVS$g$3cH~XV_cjioVYKEP)#s8u?%F&xoa(h624sT4wmSk_?t% zz)ZaX-&w55M`KZD)?Mu%zlapOV5TYLlqUQ>H#Q!k{M3*3$08dVBG6&#GQW(jv&cxB}TXzI3vv8aV1;*#hz6C>_r3ubE%nDQ6cZrNf z%FMI7xPqYp(BQvVl6$06?2P@_b1k7B=1+tq4r0$Dx$ktSZ9Q<95;3$rv-YmZZ2O~;yOc& zj_9J3E?{6?=Obs&Yy9Y>c51cBVd$(I7N74$U$IAr4_TuLjtGwS<@sL}87~@TVpz`izph(Z{2BuYd>S3O zia`?rzDZCM{?hL!MxzNS{=fxMbp1y|r(hj^=uyN!c^hDw(EKOQKAQcl)p=t_Xdjd{ z7I!g2XLg=>y5Yi<)Gm$hy^NNHlaK<^R(4%)=OtxwLZPXMqsoyg1A+0$3elpqHGjGJ z+>G8!ibckW()H?{015f``GMpM8&Bc<63^hH_fl(fFW~GRbkO0nc#yU2)t>EgEP*V} z!>yxZWp7@`H^kq2(i5i}RO~_rC7T!^_)oYIXCO0QkWO#e> zb6+RMs(nt}5HI3OQ8~bjcK$Aq6S~-j+yKL(mV=qjfDm6KaroO4EMR9rX5bd^bgQX} zHCP^pnW6W37E3)hph3mRN2XAG#`?dreBDS-fm!@Tse`AEYyx0oa*GjQs}Ut)WaLe-pSlYv;LUE z^={Axfxx(|V*ztfTADrAaU=9WF3YPEPiQ<1P^xPyg*wle(n4)C%J0XBEhv5>Dk9v^ zXrj*+Rm5^|caVLu$6qqUDzpP&h7IcHd_pylOZ4I$Mef(RpQ_mO&~Ih)`4d|WKC99$ z zAXKX1ZP?iHMXdvT4CTQ;^%?et*E6d!rwf76@~f_;dR?KJlOQ=W5B6S?ZR|YTXnD*U zzAvLe%}u~5Ne}D@;yLDu=1zSWTF^5y$zMqOza$)D;+x$lOoeeC$B8HO$K>WOosgpI zuEhGVuqTO$l~GA97T>>~p|)<2zmIml5i6wr)=l#IIjL1c8V?~3HvoCcS**rM&DrTS zPl$<|2KfFPbolgKFj$~)0-DmliP0nx45KP`fg@|RwB|Hl)qqgf#M&4tmT$Dm$ofq zL5%k0y>?%xRCi-1N+eDE4bD*pTPXyqsIrH0I`P&gg53(x3+p`fyq=9(Aq)|CHGDL) z(n8S|)v(j&pzDw7T~XLqy4jYhIdmZ&LsaVFn%DVBLQ35AIme_j_FA2LZf6U3i~7zx zq!bel{|{{d05i(}2rP&cpooMM?a=)H`i2CvsB1HaPVlf9=k&R`)(3Bx9&9eq%S}JH zKQzYzZ1Q*)_7tjgi0gP#h{KUvJ^s-C8RNUHfnB#L6i0QIF=tQTyWjDm2VZ^qu=O)H zNE>b`dOXKpy{ikR7;I(97`~mcr;C|bW6kk-5;1!sUHu7?E)nh5@@VtjoC*IeJ3@r> z%9LUHj|GF)t}w&2G6#Oy-BX6?K{WibA7c1r2pq+tpGyI-#g&h8_IC1gQ|PqQpXBTx z-GLnvF-mm?AOBZ`q=c!F*^$AKi5Lou5s{IRnUSH9sgbdfxsgGU36T*P>hk%PUo1ac z7UKH`9b0EAQfh{ZyT&G{pYughWhx>_j_lYIY!Vdx5hQ8=b6iDb?%^aG-FYo$l`cc) znQN=MB>Ms&>W?X`s19t|G4G>GYmZ`>6D^Uf?mJ3r1G*SBi@o2|c@i(SxG*ah(M_op zJ+fsg8fLP&(5+n-OZEWm-*1_Ul7%=1fmgWd%#(B+FAC&E*3rzCmj2eHQEuzV12HWf zR9%wEHfk}Aicf79$y|jYdis;_OJ-4k?jS!1Ve@MU9?f0Xme}abR742_IL_Jtx-5Ur zkeKts#I#r^{msDmg6y0PSR;-Yi&7+!NeQd{N);8UV}jkKnHc?)7x8C#GRS&G&0awX z)0={!@N+L*ZT%4xpkn&`Q?o%)_H5$wRC>&@YWKh2vX%0Y9Sa)sa*JLJTGp7F{PYPJ z%V_$u$^>N<8KwKi5nnn(>@bbOoj*tH;Ls*))AbvK$jPYmTdCUF(Qf-OXsHFyIRYId zC32E5AlY|T5Rf96fr9Cr%V||DB9{Dmq{8C>;6M{gp-hax`DbWQj1$L#k8U(A(|xBx zI-J=2OB9dt_UW!zWT%b;A$&g&6W**S-Q2eyB#JBBNuw;Bwt&&qQUzv6=!2_z7C+C~ zkJeWAHtT9B*)~U%LvLc2ik*&oK_MmDi`Pq#(I8?e?U@_Liyjl&UZ9V_`W_N&zI~Jyd*)}?d@eby zRLN!dC%OvM4oL0p1ij7*|56%b2b53p+}JmiNOeQu)kB}0|MD6i`VE7i82FVoEAq|f z2Y3SG6GeGm0=H|tv>WUV?f6Bz$3-Ohikm^^r+kN~1SNL(0m(r1^1esl|PCQrM!lMuf!FDd^m zOM=jmDBKvwxFP)upBdh>YaMS$uc@^|OArAD?4& z;XnSv48S`%@@Vb#*Nd6N;ot5VG-vbZhMTZ^t-s^jUcS^U$@>J22Vnw!cmR`O#T;SL zkV8^05>Zy21GFaGR>I#`e>A8gIOu%voziW74zy$NFF|R?om6c-u#OxS=z~`ID1^w^*_^Nd<|NK2i|p{+Z)dD(RM z9dg?0Xqs@1c)7JmzS5E+1dLLobcpKq0izUL5W7^nZL|FZJC(C`hPIn8{x_A)aRhx@ zZwYt=;S+gfvDIxBCM?eHQ`66=N*Pl_D*u#|{})p2IQDQc;{y~shv8I<1XWK9pyx1F ztVF$*BYc`->}=veA6e$U0J`R=_^6LXwRI`!yenvNJQn3C>9&*b(Z)z}W;K3jC%Zkx z1OlIu__P8bCckdLAnl09Z=l~d&$ATg3W}H2HZkIYV!J^K+vmJF%z*+>ORnMj%a;JY zUTR7UjL{lH)DKqsoL>BsGLX>6m(WpqY4ryQzB0jm^t>m@>z?0_=M#piTrx8=`K38KxUci_e>mop75?L z^uIuC1kN+0WtomW3$7Qrc;?H5Y+$_j~{`%S)w#!-zy^$+6`n*H_<&m+|O2I3n z|CMx^;2D1O+1;J}FTb>Ey~lv)e=0?&GG;umr3e9fv|c+w3kl}X?z97(;?Sy2^F<}~ z(+i{fr(q*xmyD?QKV4Ziy=u7Tn5g0y!AHg?{kwj)VfbPD-34@}>;g&2i$w_#<)&pJ#4b8Y0=9p8J4Y-Fa{!X7m zb`VD4pE1P2eE20nolTQ6pB|VjspR9dQ}T6^qEhW~!pTaQe{S!!bMcL5iI6vQX_nbU zlOv+YJC5D+(xEMXTU=|!LaH^b!j#3!S1v{Zv024YNQxAyf6t|`I5Iq$+8BsRD#-an zYwQT~-8W~2!M*dg8DJ_yCvPq~l`ele{_E z`zD!$`2<FUN{{DlDF1EhFIojowtp8J1BaiSW7Wn}0Ez z{gc||-Ns?rGv!I?TUxP*nf1m-v z$y6rHSMBSW&ORgok#RS!9>p z5u?_;@x!^#?&vWPJnV!2-uK6zwq)mO-2RCKO5t(W{i{x+B&VDE4|ci~gAw6zlS;7# zC!xZ&Rx=yUK?#8(*;av?HSuammbY-Zo>l+JOP|UjZkn-G=%vmC0x6DbWq~?XcIKl| z*MqDOZU2zDU++fqt(V2v*jf)wQERS`l!5P?(9vph8A2PMWn`Ju$CEds?9v18I%c*} zOE~wURi`wjxH|p-jop$_XZpBP|GR5#do{! z2YoV2^1sU7xn#449a7%YA9H*6!3|t;;rnkAx1a6I1GbHwb?w65=XhDCz0O>v&1m6C zlU0@_*$dB1HU$5kbQA8CKN5#KpIkhpihSr*8ls3Bcoy&EWvPhK4?7nx^~6$<*a0gg zj8Jt_*HlcRng{27xgs+$bz)xZ`+!6c1^D$3vVjzb19mk0i!QF?nHz zSNkz#LH<>7@+Ji$3CYVnkTM*R$5r3_g5`xyz5C zFf%m0vof8?&pg+2LeqcJy;uLM^;yEicowCq68Y=+ZK+|Yq>&}WMNSiT&L*W8clwl%)op*nO~V>^V*~|iq=oS_lmcrH7OzrML}9wT3lK# zI2V8>OG_(BOG+z9%Sx+siFHA`q=Mm8Z#mT~-d3pQXlD|A6>`N#B&xOi91k~okGpdp z#Rbn^DHt9l6$)kJ*d6kfQFqB2bv@^94EB}D1_XW^**Us5#Je>(dV%7lGW6JPV)z{s zXrHo$?yz{+_II&e#LX^W!ZvT?fDbRX@mikSUG_9S-(1GMf~t{^v`4UX1G0u^8*EwN zI&@Ccs7qw}PgULDky#BXzsGNF%r^!qz`0ZPrmH!LUSh}4maOl*zYEN-UX5Wv3fbCt zXAjQdAtW)MhZ}s?#HRsJ=;@!VlA*>cqE>F9JkY^bm^CANaHj1NZ8rNR9;&%|(niO8 z{n_O&HF)Cd;ZFZc zo$`gi(=t52lCd>C`XBSJ|2v8eL&cB3rIJW@eHyV$kXIrXwDQ86R50G+%5^9 zB1frs>#%%mf(S8caHt-;0CwcqJ6$k1MP=~hEn53<1*tZP6CGeT8!7f059$`VE0 zX9(n*bHd&g2l|=aO$_(IEYmxx&ti`5(5cV#>y?6ulCGhmyxYhjQ{`sEsoHUeZd)`a z=BTa56;Ak;c8OC%L)?|&qSSFHsaTNcE`H|5VX8R6VWS>W%f`=QyHXT*DQ!%ub=lsw z#RZ2N&c&|V@0Ya}dkZmXZ?5~*qjE~U!#G{Z+#@*$MTM)9;UI}oX4a^Nt5)@>d^2xj z-ni}a{C^LjkpHD^Qjz-F&}?SzgNz7IFGCC(cmFkZh|mcNdfss=(fJuiZXvO0wL)%G z*%h9Q(G8QyCz20QyyX-hLkWS?<$PXvjMqK(h;r(;fZSvAyZ8u*&*&i1u?;qjROr(F2bE89#Gz>ka*l`}xxR-zXJaPrq}-D?lXSsW_C4 zrkRo0bptNOW;+dMgG_BYh3=wt`-mcvfiw*K@mb z2rUTz$-G90kIi)gqRBsipZ#Hj*QW$lkgYk?-066*wLK?kLr`qEYs_oVQc~NPI*ESq z{iau;|yL;!mR*%aZgj=E@}WDebbWvGM=B#>PTzf2F&t#Eb1(7T!DXIvlnV zD>dsAU{6#5@|9J_axvf@HRZ=X@J1M2$)1iCRY61bqv^g7#TmCp0BWX*DbVREdHBa> zaqoi_-BHOcQ2-NA+%-*h`0i0o=w_v~=kbW1E)G5Flzw#bPgxo+W1WxiKAE^l~eS%&qF`w@vw@zWThYe&{pVN8iYt@NPen8I?keK@1 z0s7O>Vhi^R#I)PUnHUgmhF>RNiRb64%;cZXwn*_^n4;*px)KzX9YpLl;C}q6H(&qr z41o6-3>(5Nf6bO=`6iO_o(P>me0aCx`%P)q$1$U^gh}|eu zS=}CajYIW>*)5^H$$aL^A)VK{)KRCID=N@Gx4D^ruM?uy@?=fOGjxD=^CCUd=&tic zljz{VxuCFC==@=8^%^~vvJ0@nHsq1Z`aTZQZTqBsWBRhsV;7dU(y}ut-Lx5Nn{3wB z(%TDj*^Zyn8>K$~xLDHrCYo$#WPI9TAQ+*#NFr9l?HDiI^G;$=OUZAmp>z&%0c7^v zOBT5^1#_yX4>=ESOiI|Z-YXK3E7@)A1jSv4f}%W5f{{#ilw#TEM)wr{J!Cq0Mx1`j zB31+3`~4*Wp@(LYs%fT4(hfWKLOa$$JVy~(>yJ+;A%r$ZSh>s%Kel^dQ91X|P4oqN zzF@Tc0`mGvDBJxYZf6z!1Qq*Fv{p-|->(W#?e)ujNR8P~80NFp z{zayus+_glJz4%Wfp}=KP|(@5u5s@HKb-C!={aK?$e%~INkw!*US-HohN6a_I!>^1 z$I7+lHfRaaDy+el%zwg#jqG34nqirr$Wika@Y~s_pe83jOKa zydu`Im#5=iE1x5(Eq<5@7Lk^Bdil0Y zv5BkFF~}@plrC)YlV{$a z#RNT{`{N2kiKR1I0H!I3fG@e-Gyf#F_0m${dTiccLmFgti4@L~-6{UVFuXi5KJwW@ z?`vm9!B})Bo#`_b+2@vzxiq_Q)ZjyoEvUyQAtpF0qRQe1EQ@K?)p#d z!G>vvNcxrHS&2r%2XkD{DxE)?$3lNJFUePm!m!BJrBIj(9 zc=IazX!-~qGXIC`6grYy9*47!A*|6{C%-6VMqT_>W6-$t&pFOD`~J$cmCSMb1gMo` za5ait75#Z`0)B3kjK)cK6$8Me)}NaU5x-|K+3ND?3imykr`|0V0{I>uo{al@y&%5K zkP2QhUeGQ=_dX|9HM$qu&(+VqML=l@ls<-6Irkm<%m3Z`c<}N@G+VYjEdl3VX*Id$ zq~c2j6Pz`b*up`s)%VdSqUut$HRvf_;w&*OQO{1rYRaFqFyRKelWh>Gm?aAs)?R&8l=FDkVZ zwhgU(lI_A+{*hEI&bcx3=&u?q8$agJL76aLP6;$x)>K2(aywdJkGjNXOlC=&(*7bi zOWnCC&ON864~J;$gtjTHyE|S=!Bri$9F8j*wr;0Y_T5w#Q$YBNDvDaQBn$rgTZqHM zmSvVQMO^D>tk})oJBoYvr?cx1l)Yn2Sl)7Zr6vra@N(?ky?BvO+FJcrQA;(&u5dQ@ zEa|&PXV?0?08`Cn9U*TTTPOgue}1D{*1OGC>$S6Yva>diC!NgLKSxmOAZbUQLbK80 zRchIAh`8H0u7smxGOd7Pvn&CZn!fBbydM$f?R=~57C;DJ`YMY9C4Qno-57d?d|{5X z4BObwxsq-87sZVpb`+^pz(a@ujQpJqRm3PA4El*eUvB?X$qJI{@VH5h3f6%&!st04 z_C1MUo?)Y1CfjU|Wc!!z6W+kv8_l|ER8<^C+|%su;LBzSeLV{|&<%^41M9);JpHiv zhxCY@Awts8PTTV4{pY}|t|CFpFWQn0zP!WgX7uof10qPrx>dg@gu^-+o9lt@djRa) zcEt>O5aXEKaA+#UsADJUc4*kyaqOGo$(|=Y2?}_K^ShUXZC0Xi8Pvkp)2_@Zm__F( zGntQQE7RI>Ynwi#HeXL3L@Z3JB{2ui*Y8V0puRN9vVFEL>lOIno1gG-;10OqiT9yR zXXxq>=p6vxJ65Gpb`W`*qFq!$psgiaVfjMLt>Sq9jM}L{2r$MB?|#ir1V4JAipB@} z=*hj(XdE8crtZcELjRRpYE9K>O{1*IZA*i61AtrDthFX=LmJFiyVC${=p&^ibdh|q{@9lSh!ZGgJXrBg5@R{0h3gmu zJb>Wg(Ifc7ngh*{<2AqytLANx+G4zFcJ-rJub8`E;whg0c&USM__-&2AN5tsh1d^b zdBe7NwzUcrH5YG{!Q#Q=-13L_sVSn1DyD0pYBLpc1}E(|2NAV>xF1)x0o*shojYk) zo|K=ob?H0hP}X9O;(ox0s(LJJIy;>Ocz9mi58E!Lz#bF;QU^M$yv6MnI6;fz)+6Nq z7f@T{OigR0vKQfjq8BHQG|GHY+)GmR?K6_Fa5|W>Y~j>0r21e`s%}B?ED(V7@ELa= zacGWp5N3proF0#AU)!t_@07*R4;k6LhYm-)-V#%Too-)RkE~VGF-;ao^WW(MC1IUN z+i|-7{%zsZGk{Mgs?R%gxf4p#Z@cnei#(y-9cJvwr?v#Aht;r10E^w?3b8axY$Q^q zM|s)S1+iqQ`fa-KzH34LtEqBQG=O$=aP3uK6h=R!kzyby>KD?xO9uI{x9iqd>{-jq z^^gREzMk(+W?7d@@SJlinj>E*6%mXkgKttqB@+2Nz>W^a*)=H~Wau)&I9t%dxp_m> z(AOmV1f#0JpUJki6^_aN1=^`XQq9D!uC@-F4?%bc>A+zwtTNB#{8zmspgfLG`TC4^ z?kCb{X*KAc!4vwDkRH=zQyvxQMSf@V5sZMmaY%|w3GSUozJGIQ&AF?-_4wvc0Aj@; z!QsUKmV1TF!Up%0QKzKrQR63l?5>Zl3IDYKKs$SpO&Nb6n6ud=MD*4wjW(>%z zz`wuiEk1nj*sX&HmwX!uuvl3a8#&Z@>^e-BWrEChaObr&`MJOsq?TH6EY84-jF&^G zIDE{H)lwp($!w}D&0~zJX0m?0F0tfsbV_&tgJTr&Hr(?j{*6r4qrFhB0o3+`bD%jv zSnKxX@b{4PX0jfAok# zMi(w=s12?8Jt}P{M%>uAYJ_$aENKRExk}kJd z!w?PzM0y?JOllQ+Eq5H24axXYYV){&W#U8AS;J{qUX~~`jyx;sZ;&~Y{Shl^w3T~+c;Fv zbQaC}DZp%pv!Dw;@*-Jr1ha&1kv3|JgnpC6Gs@z?^k1&y1#9>DW*v9#mIZjkX*q^e zdBoK##tE%PUu@K34Pq0*bECJJGQwJOk_q2d5#K1QBIQK9&3+ULW7_Ek;IrD&nEF_vkC%UBj)JXJ2b6Y9cSX_+3%ocU7!lcp zSGUMgo^CbAz}cfCZuhK6BanKJTI`P}5$fi{n~&i-_qP1$UpW>)e`uRZmCJ>{>>|%2 zwEp&7QxrB7N6DHSpTZXfNrX|hLg9DB7px&nd4mJ(m@lFrIZVUYge(`^` zE8w}1JEG(f-@e^ggpinqRPNKPq?-)bol>9_bh?8CY(RIY-~PFM?AYxoY$-K@WW{VW1p!$h{R{n!_7 zf4&;9eN8hIp6r&Nsq)O<=os88AJ*V%Xn>}hX zn+XOd3#jvWvZ_Us=yq!xyZ`0Gl z?*wTSKbxu%FUc|(Shv732}4zIK>gfgChg8boaQ+idID)bz&I6wRer3NP;R5^>H*s` zo3Cd=INnKnoAnO30E4Ip)_vVIsXc=N&&{9HRxLVM6FL4$e^0?W*~&D6Ot{sZ`X1`H z{Dkkc8HiDT(ZeJE&d9Q2D{?g+x)E3#$WuRaoa)<3+;sXh_^&0HuHN?7Q69hC-We_(ScIvX$RA8nZxm8)jDO!|XzPk_iahI=n8iK2d}R3E$YK zN-e27#7d2dcjV5w|gpM zt3rMv-o7*GPOE#Tt~ucK#MB5+cbwA4E&RYb-eYKFkeusA@gMNjjR$rDoi)o2Ne0X^ zKWu~!RYpKXv~)Z}HUx*pG#lETN^V93v;fyk%bwxN#-CLstzTn#e@W?>%<4?>yy-n5 z1f_VsbL!KX3J_2=U!>nHVUG~pYfDq{a@8`giQWSam;d$O^vfw$*ihr9g7$+<1Rt$7 z`?y~>(Xy$$iqo&xTTd0^Zx&yyp>(CE-~A&hL!`>S1tw|we*h~k|bFB>25u&BQbetnc#dXIP5DduU%CR2K*Gja$xzeTo~N=t8tz&tX&=Z+n2(3-)6pb zdR9N%EvcYPIq*qKW=F4#=&LGmbpE`dWjxfsZ9XY*Us7=Lyx0Idyeqg;vQIhnHpH(u z>$JpT_YJV6-X;Th<$)#n-eLj}riE$?Co8fdi*Z&iCj{ju6n@pAm`3!Md2G~@akg@rF=SB?0}suew z+tg;uk4>FwA;{4Y8_^5xbWB-fJU-cV{O)z5THEz$Oa6(5_D&0vO6 zSV|Vm>EEg)n@%u3WkIEDy9vt$Qtd5Gg(=-97}YUdf2F^JGEnv82C=Ui z`2P4Ka2J241!E^B2rCm=0C2{SZw*$kz5iypF81xb!vB_h`rleU22v3cP=r_i}7|ZRWgbBL)`E z^r?}7Xg93w^6lkA>DX+K=zCMyh;7uet_B>B%k9C482G5vhDbEsZs%We{lhY#EVrJ^EpSw%0)Qk%NXjsG_r^3G;rN3j_md>W`eo+(U$kkZQ6Fh> z{Eqw$jsAm>94VH$F+LK8n$PSoP!Gf4W0~ej3ioKK4q1GvnOj8X@uyLnZ$)!U1%|g@ zBNE=blbe9Wmc7J4$sSoiUsH0%jYQMUHN(CnMI?nF=Phujrzc_qaGQBXECN-aSHUh9)y3vo%ZwiOK(zQ=7x{TPLd*-Dh?$b`SKCZ z?LpkeK$|SKjP_hpb5dI?mtj@9*wS>6STiBm*Q#mHIr_2JHG{8d+@dK%nFKk9ZmGIv zd`D4ek^BY7QmB&_BL^2GVKb#^o%)_wDCl$Ci|)^y-S4`;yjNtP40^9fviep6dT%J8 z_RSxDZgh7nkhnm6YRh%)IMi^t-~>IL!4GGn=1!xiW#W;kB0C#MV`ZoVq-vxjYL9(9(3Gw@~$Mhgza^bi!*q zUQ0`!3Hympg`=vu@NTi__}5vlLA+*`B@;9*$@}#qA`~Ay+2l;4Q)x~r6>z%lq><;d z7ba2L7*!{MkG4Jl(|kosLK5$4hV`h6U|)rR(~&cPL*0F4-`0uSLcvM!(H=4z!1_5T zQsXgUlycESM98td|A8V=GF@SBx0rc*clo7@%Xx7zn1+>p(AOQonU^bjOtW(8a5_;& z@aH)43(sUxf5{U_YKh|@7frVPhd_EGMt`u^r zniO=)D^+H$NjF;Xy8%Wa=p2I7FkhhIzn1PPeozNqjq{@TkOpPzzA(a+dZitIn8bYk|woow|-Q z6z>>^H+#BZsNmb$A#U{YRImA(FSN_XX;xo=(ydjr{_d~32Rw$o%+hl_^RWXGX257_ zpMI>!U*GJkMX54vQO0Baw^`Q${p|iisJ)!}u*g@%<_2laR%#}->^BSaWBa5b3cK!B zO|C^ZG!I*?WAbMves7e}K7N#>qVB#Px?D-1OIN`;9hxajQfPPU5=ITX?njsUdg>q z=!^Nlz)w;0zJX(R7G?91P?A3Lx_T&^5yy7b%M+V{rm;ctH;NY|%`&eqT?+KoRCx8( zY*u=roUPBQ!**B%SQBd6^e7Uto0k~XNT8lkHoFV7Lq#I5@fqRD`V>V!$GvEGPd4kN z#*Z_?f{5Mo-)Fj>{J@sco^T5p#f*vO4jGHE2nQa`dpolCipPB&QP*#$A^m`eUsz$h zTE|=JTp8u)fY&MMES5)`vZJ_pn zy+bk6%rrSOsu>=t8q`EsdrIOWx7HHHG^oZ%$ZN7daZ^#@%@BDq}P;Ku9;t zB@=awx)^1#Wzh}owYO`hakp!y{8my-WH5Gt7jc}PedWh&mG2uXisLMoU7~esAC~Co z569YXF+9<(!Bknc`}<@o7t|PvtsWCJ%Vujc73!Bw3@JVhTyD*?h-inI$%(_e2<+Wi z%ETCYJ~&-DlK5|ILAW(T<@zQ|(a*QWQ+vFuSN|MGN(m~35sRbhfT;*xuw8}i8BPFQMuYON1 zoB6Jn3dBL=iS}mYYEfGodAkD`^?7Zj@;2;O7Aa@N*wmSTV3nNb<7%AF%|@+(f>wiB zdmpxltm7+$p|8_xmF@_AupE-uE?FV8Eg|p2yU#u5!%i%A+G-dpXlQnekNR%9V1B+Z z5X3ic5KH+oo4?OeREM3NWuXM3Xjavw0bWtSQhvnOtpBT>ks;8YNm(4sUuHXfp|4SnJayPQA4o9R0^YHuL~O`o z4WQRxor@#E*(K_4kVY{mQmn$TeN@pR?I?i|I|$o|n6Eeq`))5UErCv2j*0?TOxS53 z&AeTV2i&?9(t=L2Eml4qdA121EolaEcgXQ-flBomAV#z}JAD*=h_j z^MSgjyqrK0Yk%!#IAVtSN>Ttnb@^iz7(oC-;m2lg9r*tGnVq38QsIbWY@!&>#KRW7 zC&qL^5ckE-D+dP!_IN&@%5X*>hs2V*j`Nf77=7u}VC&kni)y293*Sh5wp1q%7pvj- zIfn6co(&3}=Pk`jdb+OA?V@3RKuamhl}3E)m9$LMX}&VPxj0U?jEBJZ z{xce&RP&tzmBIf>smA_)bf4XyZ2DfF^(-s88iYw9t7B1C_Ty8r+fhaoFnribXb=Cf zCZ@bw$uLTxx%xY@T2d8OERMr-IV+CceRYV$xniDH%uRxOFZah{{Rm04DxpI0e2BkG zR7-cZG%bO`P8UqdOe0Q?N+)jTX90Ng1VYc2Qe2T_Gt2a^t~<_@j*0#&-ilgID1uJK zo~~+IkHZYN^(RD9KKh_FmqrU5T!^8=xek;vqhk-_rrB`$gt<)B9Xgs`D!f(>58|$` z7uOec_J@S}W)Glc&d@X+AA5Q{4(fMRR9M%B7=lq^?KEPJ=3$L;LIdX{VEVD7%p-gU z^|r+_)-0+TrX9PL`IXv_zlZ=a()Z%{ zZ=XMjk^cT4cey`L?tFrtO;rmLCk14tKhC{n|b1kzYq_Z-Cl}=d;0mAnyl)+d0K8S+Rsr~?V<#Y&)z%P zEMf(5KMEniFQ|gX`B7KiL(4_cR`%Hi#DDo@7X$|$Mfl@#h`j=cZ|~w>CjCu(1OJaf z%^Qwq+}}g&_lxy{#IMpw=5BYcF;$>;0i4ht@tE-# z68K*^y3c|FImslrf^(53xhDE&UnqdGuyBXMHf`$`J()`O+dc{+GqtL0zns<*k3H?U z)xuUrr*AnbpZhlh`eZi}+>XV&8 z#SfZg*4w$~C$Q5Dysjp2ZAFuQ67TUSLVp?onL4X?0Ev0^ZZm{?be4LR4!_n4#9EGR zu>SIHh>ducCM1Ws*MVc`s1)q~)@GA8$dX@Bt^Mae)+^8Mk6-q|W4Gpz)ZHU`;|>Qp z9ANdMGBc8WgU^D@igq+4vvRbAA89b@17DaEH z1MItBM6TcmWq(RG>vuRrn-q)}=K0uII8ApomS-@s7N%|W{H`3TVRJAt=QI{ih7v{iEr)O%IwWKDm>Ra%9ykQ(lkBL^j|JRM2MB1MM}YhRFvXA7A+ zy$rY>F!K6VB&>*mXKiq&H4q^=FbZ4DI}RM$n+)8@OUK()wzVrW@kpxMFxg&fK}p+=&&|P+uwq zR4))qdCTE-k->5|KaT`>yonN;d1|rpyUCd1C0g7wK!^+9^cmno3J7sw{I@6MGfcoA zpahUz4w+x9qJ)N5(=(p8Hfz@m zK+LI(7}R{)G^iN}u<@=%M%ikKQu=(v($;#Dlw4yvr>UMF%-z<$`J?=WWX+0Z(IT0p z#J3Ax(x6;$!U;IVivsG`$2Eo=WQocJ1YBFNJ>R1%@Y4NSa}N@ZVINKboozO-xMpMK zr1R4+ubzub_-2UW{yM!Tv6X!|DL^?}EL(12k64h`jz5I@N$FSH8B&$zyLAw=C54-! z4|}U*ePRSLy0+c?<>k~vo1p1g@HTJ1#alWr9qeYE>FK!~?26_PpZBbuFb;u3P%waWN~}o}6l=9ScVxQb6&7 zI=a?5ala~OOyD$Gt?X<9`TaWXIjKdHLFITDE>guZ;4sLnUsxCbVR$J>#{g9_YS_CB&Z@3-iZu^g zx6QxAXmy{y+;{HRyuc`me@;tqU4S!}ETWpk*4F)x`9r)?U6LD5*(r#_OV^Hnn$3zXqLSeQ zeFEmvxpdg>J5<%sj4-pSDhS(>lr!}^AN3$YvkW^~M}DeK4Km8jZnG80VaWRr{@;5> zeZ)Cl_D?5fFX_aeeoxNqrGXY(#OBuTg+$43F!j7SfND^CkhE9WDdmeTzqgQ9{_$Uv z!G%+fpwk0?AbT{O?4j{D3Ae@#uVLz*#Ifl>1#&)jWo-RmiD@L@! zNZdZQ%GU+cK`ssZtxVVae zMNPTdkfI1S+k)dF?xzo%_2Hme@eCEAnY*ZxC0y*(Gbi*}Tm_pDdb;X9O=FH?bh~*@ z_p!U*jHH#h{KwN6HJv`-f3poicH^k*Vv|{#?qaxrn$2{Zmg-4k=Dt4oFv0IdG`K(% zk|8wkDYqr6tUh#!o5XZ(%5|c zu?M)#YQOd{XeZ~s;g|I^-K?>9i}#~O8SF}z&nvN3aPhiZW&^T?KlwwaKCf5SUe2Ya z+H|Ya)kcThxFNwHYy$_m)A1v1*aqOFNdA>psH*ySVXIX6*nl(c`K#W~Bn*F{R({5L z(i#2>@xM*3KO+LBg<5e7P4$Zv*cJLJQN8%aQigc+5ozELa9eG%5rhdon5_g0KQ8UU z5*Se2!ZslC!F?l-rm1S&NoX`9uXvTX8G+X17z+qcTw44*OYGQ(Jh{ zH-38jr0Jz$AR*R~2BQTfYKnQi5K?cyzNsCqt>P5-dNJM;Ngg2%UQRazUO$BZ#~`@o0XIwgfD zpdlt1UuIYQl9ICx*~w>;i@)sknQg(1us+hp*lJ*q_-XKx+F%!gMysflW^0tO>n?+B z=8AT5rRv_DzZyOA=2k{K9*ulzebf03X$xW^Ogi6)f|QUE|5XDAIqavTGLesak!dZ% zg1F*bi{r}XQRkJnaWSs{5g@~jNYwu{gDF+x7oY>EnFj(?l&qnAxZ|sii~ac{wy*YI zCd+8IMVWCn3|G|JJxp=iJvOh0BcvK&mhRBS;qlG6MFlOn$F+O%|G$4pQ7qtJ;-jy> zk$HKf|0_*6QyC6-7K}-B5X|&)yjZB>P9)lFa3f}|_j0@aeX->>Wq=| zVrAOZjXF9E!bl~P7e zclR?E;quNtmKV34|NFt=FMAH};mgv?!+x}bio`WtI)+2MTOIYA1d-r zhxO*e%17(~ieNmA=t8PQyu8QOMc|&&iUQYBXAu|$CJ_yt2|dS{pPXlEJ5Tm|5PiJ4 zbc?&|m(1IXzob>~N~}9rY`WAkrk3VjPR8G4=d3KCCy)P5iisis#Fx-xgS(IL6=S{FFgx*;}BXZV}h=NdFM3%z1JrE~9Fnt~!Ltv0yBk z=owgKAEv5(#{F7<6yuD$5h^Z|&HalIf4glG%cNgm;7>iB^9AQ-*R$?1?jJL@_5LH| zg9nQ)l6Q}J(DiasyGZ zFlxMk@IQ_ZYLoYU)$_@-dpvB?Cbtm$cpfj4HYwZqwb{U9BVOVxKF@E!BCn3PT8w_Y zRk$Q|huaFhQU$(jnb*eE2N^4?_WvJ>~Ml_oZ9p{}~x}(b0lW6zLuP^@}0e!sxLqMOM z2RkfrQGVu-j_SykCIW^JgW6K*`!f53*I1 zULI_+=^y}O7?o|z_=yhL;c#9InhILI8+^42k?Oh*TwjYAKSw9Nyw8;Dy!MnJ>3Fs3 zt*QYX*W<(A%2*Bnv$Jh~+ZIC(6V!e|C*@#^o^|Fr?i z`+M^CKT8|X|Jne-_?VPEgg#vMgf)K)WRRZ=`oG7Y?9)k*Xa z9c_{jKAwdYIx7Y>ij1f#SrQ`=?rcCaAJDG!z?3F%-M$E=V!*tZM%>n=lxh zz1+CT$ge;WtMFzG?1Q1AtE=0&d)&lb%9f3=YTCG{6+67)?Ae_5tbDmm85)U*5g)lG z)MN;*U>aH{-yiF)vX zfR(7gNca4~so9IA>{r~KX_4a41Vkgsf|fS&_&@b*Xjvg(Y8UiQHSg`KB5-~A|0+=x z{vS$Iz6@;2xT~gh*aB}SYC#|Uh*yf%h|G%uzd6l?nNi48GpoY(EU9k&D1lp$B}8QX?YX_sM}=c)}RW6^&^aA>7YcX;4+$po*o;hS&qu0k7nx?Ao7X;w+< zEi|FSKKp71?z`PYTf|4Zrh1CQlF%dj;`}*LXK^UKZ}<4Ku-rT3PJuJJPGnQy`YYk| z9M&~;c2yu6U^mTwM1P53TO7%Ri}vzPOPhh@hv>nDkp7U%>V$}ESQ7e?zU4MhIkt_k z9k7Ig^QnofAKx`L*T!ip;w=}DwABY^kbPZUUxEqRJBBu{NSZ=#mL;Lxsg%kO3QgxN zM`F-!hE9v-uqdbBZdi!Z!C8D^RVN&sJDzVSF%JJ9m2#&4L#5mT}P+* zK?c52+j}ORZ;5ETW3-IZ1Htul6n^QdwL0Cf)%-bDWCt~VtSuQ;2Q|V`HCAhyrZVg; z;o|69pUSB5>|QXxEyWO-s)M7b7pVijb7jUG7Ot41R1K(+1k^}H5^6y2@pPw6p?4Oh zP~y6BkN!cNo()j8bCnm##e0VJDl-`p!{PyAXWhW~U*CBX@R6^Y#kFU)T+2c5{5!0) zbgxuX(!IL}O(&GszRdB5>_#6x-liJGf7cw&jO)#f^rDRzl2wKzIT5t=Ot!=^D`i?0 zCwz-(^-KR|f*#9}URmZUqjv#e&eij0DbKb z{~u*<85U)`whiBkf=Ehtmqhl9x6%zmN{iIc-2+2|ARyf!NFy-}NP`R@LrY7) z7x#LTdf4vxtAt_N`q4A}>$s zHJXA9I}SU=6u+(Q#AIHflzK=oIwKAX#e0fJ6yN#vZNI=J53&NH!VG*$^&2;cUYNa) z)zS8-8!i`5%igwl_oHt{|1{WCS8I8|o>Hy0bvOBQ;41Nt3C}D6HJG`o0jmMfocaQP zh|Y}|TC3F*Jdae*kbCuVhp@oi|{1 zoVk!^8J?H88Pk9t*>GRbV}5TnXmjhY@0S~ozWZvR*VSImh;CSCT6srj6T80uxUUk_ zyN!L-UXS@yKOi4MIjY&adDPl2Ze~3%T*pEqA*-AF-t5hftFvv7v%ZLflr+)K-oaW5 zn#33e`H;(t{F62Of_gCi;fbQB|%sqv(m(7$yV$jpjg>ary zr0~1)7ft3BNU4T|qjTa2Q=IUV*}j~7TM%3-mDjBj5{6ERb5hlD;~{)?L>|86os}4m z!yDDd7vt6n12{dDhu5i{{F2aDTU_jy^QnlKsx|CFAi7Ite@V`GyIemtevj;MUjAy~ zi+S8da=LvMss~&C`XaSVk2Ps*_~ti7wV}!@XK%|`y(X8R({^x!mM>*`cb;O^))R~y z?by}292LdO%jWNOc)lVY1mk8{q4#@u>T(^XWKT0*Kxs6qc>|pN48PKTnMZqo!K=$= ze%jI#=z!lfL>nV}%~fraqo;F)mx%n-wz*Pi%z5f7G0U=a1@QN$9qD5<*D|f`)*cl| z2BnUgRnEobbj@v?X-B;c<7Q=(CfefV-D9@Xv@uu$N@>Asi&9B*TPN3np?_aA#aAF8 zgn(uy>~_x3d`ujip^vM}swB4fzXtGY|Jwoly_;Nyz-p(Dv3tPwDgwFq&^LIGY0@c+ zi%s8QX$s~|RY=CyFDiMsbIMAPGlzGe61U-uCXS3ivoc^iwJz*^11(Z`!_r#=+yM>+ zNR9vogHPwl4!sv|yZecmcM^eXMr>#xH}wh;JM;Xs70$@%{|Ic_Vj)1^@(Cm$c<3RS zhlamtBM}aAp)o#A$!3WgVP%hFbug;DzO4=apB;lJMUOHam zxSnT}m5Nw%0R&EXImQ{>vz4@dS5J5>XMRd_?Dnadvp*-h@Ad0c;ws1#_u2amMR0sMaV6MX&eklbqrN0a$gwHA>&!5UuU}DSK%I3?2vdV~g#(0aP za*9fHRh=be{MGJdJ`@!dUHBSuyb)VcC)c;7`Z^69=n)HY(s>2Q92{#*y<5!z9iZx8 zk&(y8vA#^4y+iP!`Z!F;@lZ$Px#SxD<}Gn9?h}u%9EiJHJd(N6@UCV6GC2t^u-?)P zTExpf%vVVlKpaHro5ZQW1m5BSQ{tOu_AnPhOuHo7)u3agc!?c;-eX)dEG2cY8fA=H zbwY#beC)}tK@AdBr&YIQ>3##DIun6bmk8e#uqCwpxg|8R$2rpZ89A#B z6*;Tnq)Lp>%^;|+8uv8-KMNF0nSL!tY)2MfhsgGK2mG(8+>ih7RPGNLOLl+BzkT>W ziy*kgb8=?nXFHr^aRrD*@Jq$zo1x7ht36p08s@BS-GbOYNL5}AN@U^ikEXe?Gi%8_ z*(z8S1tB=LrL2i*ic;s&2k`s>PVYr=8OTT?n`7M;6d-l9*JgYHhmAjfrP$M+v3(9$|S03pw=f0dH^mgY&hrLVND zKLf5F3$NO&6At>vOx-3{)S-`Ypr7^pr9n##Ql_ZF+(ye`tsEo}w8*m8^$1)}JN6cX zZj4u{A#sK;C19I!vydc z4Qok=O*k{~M$#en#NXuYHE2!I_xW)3zZSJU{r_3iwu3}|FVxQd0Ad>?vcShMbd@wKX+jcFMeen!>}eP1oWNHuPmXu=9vy8;~+B<6CYP4Z7A`p zOpxEJ_ra}BPVvX1IJ|1Q($GX4-m%yMsn|JP@D zbsa2{2
xi8&D@Hi*C+ThwSwy<%agW*K(>-3(B%3F<+8T+P)J@GcgenUjSPOp%L zCuB;Zq0V7rwj9*rzEDKXeg4c~+@0Lf#G%!{#-_p?6^p&G>^-rz4#0AY1)Tu(U5#<^ zr{G~&7)Ze^41_`3p||2nf}C2!1G~jbL9;OEaI3IF)TNZ*5^@4@RV2R@)d}zXV@oPawt&}IJRA&WTndqx)X(A_q=|i@Hz@E~ zLsYaD*X6@gm|R~AlXy-?w_o1uhuEv{V>o>m%Ka@I!Z|kw1xV4`tsNCye;roDy6p+? z(tCJ0$a@^o;CwfPcpz1>up{&7?3$t1uGMo?%h@nKIgN!~?Ii)Wyl5w#GsU64N#_^D zb`wU9ZUn8*;0}16kGjeext>4p+-0dHVX1~)%`DSo-p%^MAjJUR?AGztQ8(#(xwCIg z&;4wgTtigxH{V`xmbdp)ZznoK_&i9E>l+iP7}IU1LgRS@k+{;(u%{;=zqb3r6}$7V zwY!DtyHOA-d%2qZ&tj%Gf7@LE8Iz84m;e7+DMTCHB^gHGD8Y*x_408PpB1rEL+$%d zKY)co7z@Cx=VaUX#%Cs&y;}gn{XkPnSxm9Ms*J+YJ^}qPNNZ&}2)^YNaz&scGvxY& z?nTeHe5ix#ejTZ6(3gVRdj)P$loV>ZKqc80w-2&g4oq?DU-g98|Bs7p1DIB8J9xE zaExb~QlBDza;`U;8FZw!wRp|TI5N%&gvMwBwHa0xyduo+j^mY5iVK@u{LZU6gMydzg#ZvNU${|qn$2dw9Ac{A38t}XlDVdNobZQ)53T%EdyO+RfB)W;W zCEd~?vFN@t?exM%IU<8!h)cfw`KCgg%_VuK73eIgfy1x}|gFAl%GQ-v857CX1 z21#-Cr1J`|^nn9!L$P;l-?pDN%lvpdL0cowK%XwS>nThfeCiU6KJk#a%h!83(MFf> z4I_KYz@Z})zV&|xa;vO=Wh`S6@pIFXUKJWwm zRW16iVrA!89}O6dyy+^xIQIKesAY&1H!-a$$HH^30`t?Qx7XGYuW-?R0k+o)-_}ta zpAoTz4Eb=+7U!Z0pKwv4O;d$^fyk8mH&g)%J+L+jfN zoIZfR1?b|HNykyqYv-2Xdt+Z_w!JX+boD^%vdo(&hTn4Q^S_o`E%1H0#nkgQ^qE{Q=!rqt~B&n()O*MwY8Y_m=4&H2bO3zS3 zx5GfR`AH*o%f&ESkP0@R&}i@b`ihVYxAgFPmYA`Ro4|cZAtKW1-K#^AC;tH+ZR^*970PaX>hPqcbJ%bA{paa=yR=Tl3NZk}VFjoRBs-#kO^4^$mn-2F; z9S5rqRtmRp<{mNe=C4yNti-M{kVKn2bgS@{8jNGjpND?TM&fLKclNXq4sVjA-J(;o z`eBc?Zu=3`ru8>-X$c*_;kJb8{Se4ETAGUy;HS>7E92rX?I1%gzQMK`FZyG z3x&tjO=S)KGXOFgNEK=c0fWowaV-M9(2&=Cb1mIUO-Ggvb!dW-PK#bqQEMGz z{d+D@87%GXkD#1z9dL{X=++K`Sbhk0cGd?Tf3H|KNmiyl7~%$~FA&55#S}FMkS1QHA6}q>6tfJ^lu~7bzkU9Z zA07R}7!mq?yMI;0;MN+C*tNcDbN!$bpJOdBVx#YLL03^h_4bsE=Vxn;)dzvpvl^h= zTm*b>vCn8A!iH3j-nNArz!txL*4C^G{OZ3*EXG;)YTvOorC!G4T~9}WHSv;1PKtJZ zvK_30uVyFsm6TuSltSE!7I=_Rp2*rB4O-D0x8}AW>mz^1<(??v(FQiwK8&@lHj0CB2%W+nuFK)nMZ*QV%u;k zQMGzBYFRxn6xF6NlK(`YF%Oe{iw>yV+}=cOto~E;px3YG3hLY_1*8^a`!Z8mj51vJ zcDsuMUhV&|M7A@wO6mBwX)Li;*A;9sWaU-+ho($r>pwkTD8%0kb^$(sJT%4E>bK}f zHvan#ot4KV($=uG=6U9sb-@fRxzf4g=kI$f=K9MTTMi2R+0F`ku3iig{Nw zEAP#e+i}@OiQyALYBRW2wBbHo@AF88>IR0&2K)f26f$4ov~8-?efe0QBivgp`|PJ{w} zeCLOe-sxwpteJKFX)RvI7rI=(O6t>!+}fRw?|hNd6|ZK~bpi>k6gOo`r2X71Q&Zib zSn$27H7q-5o#Qoi*-%p1(|!QlBzp+AZTA7#1`Vs@d26JEX%dRkuP`S6H$d`%pSwiy z5a(p5u5Nuus5>CD7;}i{O}q9cM9iJszM-(>|H40Lr!kRN6kJH3icXAUPr=V`Yc~PU zEA5sHNkk{a5yg3kV@<)qKWm%kog=1NT6cv`-nfCy=py=!trmma>hP~`IM5QrT#F6a zX&*h&r*AP{mJm1&4&UWaUCioNgMMI9GLMPqH-L9M!Q=fEfEHu*R?%PhD!br7uaES4 zmtly##ldDnINNbCdB_Vb(&hR#zf6zZKGDQ0{j4CVqQiZ*$22+r}q^`Dok9uWw=V#R1$42mG|GU?;zej|x9V9p7Wo&4D z*P~S9wJ@>EWxcJBw7{%Z1wro0lKJ0g^-kf*ZD5&w9mfi3dc!nV@e zF08wGhYAmWTC8$8Dd* z)dy`n!xOT5=*@Z$j3ruLJ_P@=_evAbc#p?#@Gjl!js}?`mfjr)$h6EC?dHzBG6vU| zBvn;Uk(OO8CITL|5x%Yxz0XAy^@d~fk}7By3%QNtne}c6oPw=2hc-MB1})uU(l(^* zEiJr|W_yxfJkG|WOwXR{2A(?9gJvyFTr^R4V^n??&oH>GrA zlZ51s3aYK`B8|jyYT>oEu2^~+3PyPYO%>bmdm87|At0e-{8CX;avKJlCh>GN8?!(R zq>4m*jMgPpZ@1?v>M;bNnx65hfwlMFGKzT^=x-mf{C|7?{P*cP3rg!#zW}jRAADAB z`4sr89%@K5Vp44i!6{oOAzy!^w}RF;56P#GA}Pb!;`QASn|Q)IW{Ztv0{%W#$l9zV z)hE;^*SkdiHDjal9Lg_XZ>EK*-@w1D51o7yCd)S_SmiB0UWva64{Rmc7py>D7f~VC zC4ggk|0BN577L_qClffQcEGyr<%bs!J2k#Kc^lqj?k7wW0}~P7H1j)H&*uEXj+lH% zvw6u}k~#=NI~6%LGSclMAF5Ht+A1>AazYp3N#I(!n%C{SJ?Bjcb8%X=e?hM%<S*(QRk+ZhtI@`a;M-ny2P#KdxH46a+OnXf34gF^{SSL#^ zSeS0i?M336M39^fpC&_>YvHIiG?Anr*NG+TjU}{$ZJI8zlL9%#u(szIKcv>eyO0X}x|i4hkFvFTG@%H}{1iEj^k#U$zC4316c6_Y?r(e@pP53DaUk zfl2TL@s)i{%-3H1vucf`Y+9?EMmgxOU3qgiUEJ^;JH*RUH1a4g}v4bM!3 zlp3Bx>SAID9hh0J)d2pr*vp(oCLTdfXy)@jI=}}ATJ-Mgzqu)gF)z%pf zTouO*pLp~4sp;{=x60cN9HhUhRQm1|g_R)p4;iRJ?#4Z*KsVN9BH9038<6@(7jT%w zg?|2S70Xwgwer09=2h{6Q_}1>v|U#Lw0k6w=K=4s3fh9UU>b$~PolvwgnoYM zfyK|rZMNR<@tDRH49uMm&|}8$NXtCn9YekstHI7y{;mrQsGvjR0FqxC4{`s!|Aa13 zTk(3`VIAA6J#x_92*OYi#&$)ir$20o%glHsr|f z8Ndw1c|GNZw#TiC9vS~q`E}26AdDAqfHG)*Gs?xT211Zx3iUjw;6o%@ds>C_R!Vgt zTuy~@5)-(SNY08YNlU4U4V@RphPHc?a-xjeJg$9G?1{N;zUVCBZ}M0|@%U0&NE5(@ z^lCFTd*z4e-enlJ@d!ROo9U+sU|pK|O0!Q=@qCBqN)1t@8s(g7^uV4j*Ap2CpG4@J z%JT-CYEm~3AQ{an7wB;|fvKNR!&-57JlbRft4 z`Yyh}=JAZ5lrQ)9A-^ZjH(@Vy$F*W$muV6JWgU$B6^eHdCgi+yUo#o6-qPub!S{=5 z)e$rlN%D9Mr};xe9tJn>ni#20nmghKFA4oE6oszR4yyb>hc#hP<%ObXFKQnX@ zjZ`j}<3hRZ?zo0moYShXd=%5Ck(Q=1C4eQ+6KGWAH$dwkaJofP6xt~D-eQfwBmA}@ zeVuP2qZjF=O26^5Rpa(Ly4U|_lcErz3#)D<@}Ivq(+Xyiwn~0Np%_i=eBBocm9L0b z4){K>V5DZVROi^1W<#7?GEoEX)PfdLWxOch>p!zxTZrRnx6Ic74Btf(p!q0_%r=)t52dDTxZvgE6q$zaKYQc zUb?yJMbNoar^%8LMWLEj>_#x1iRsH09iCU&`NkQGJvfu3LRS16{0ADO*bEyUoslV8 zI-x0izn8fJSeMc#_~TQwe6gR~*pWO33^_ei{6eQL6Qn|{gwHYlnN|UFuc?m76;TZ` zj7tyO0RV)i2s(0TSor|?y%$o#B45vUl8{3NAD4p{%GnjaR#6lL9%BNELRviLlaG$Sb$}_S+cY@p)I+~(#KD$(O!npb zBgb6ybb?&deQSUR&RXtM}o{ z8h&F%%l`nr^`__cZx)hfRdWnU?G&&%u?&B-2+fYOfp)TKdJ8-owIzg~Q8Bg}T0(2B z&C5jOV*svMJB$Tm=Fz&kJCUlISrW_ULS#&GV=(CMIeVOXrlZVw0}?t~1J-irySPM0 zQYk_hZ)R=6+QA)l(kvdldoJF9%dX$M=M!CAjx*}{-E(ye)PoKo{LOVg{LLEF{eczC zd;P1>B>2P3-kDJdP1nHo(+iDqIflRr#J=m*wVJmf+m~+d8r$UKg`C*)8RoI4&zqDn z4kT19Iq_`15RaFPT!lBXZ%6Q^V64pc5XZ_u!v|b6H+C2Dxv0WHS0!7Shw%m4Mr3ov z2gNr1HZzkJ_RIM;{oIlOBtfU7A6s8zo2H;u{Eqw2)Q3MzKfU?A< zS&*}ejy#4>+q&7q*JBvhv9BVD${=2=f>^{-!Cz+l_vzTvp^{DdxNWOe#-_@sW7)8?`Lo19_CM?8dcODWwu-ciS+6PEuyC;&@OApT7y>Kp}@_bT&y zT0k(WZ3UARai=JrKY^X0UMN#S^8IR4$+B_VrBwnt81KCu!%bV>H3fh(;pUCG_cOzf#zq4cfK0hdAWX&yHrfihT1Iw;N#1`kzB8>wR zg4V4WEi?Fg{b@vSj106V!kc7tidCGgsa@4jKRkvn8z7H$ibuHPopfaV_)=0WjGE^j z3k&`J?xkbZjqAB)1pHa&`2(M-W%})Wt(yXok-q$xAt%Z)%mb01{HVx*J1+6uu769m z|G6(qBKY4bnW*3gWWkufCI7O1``{J6OFl9gEginL-V14eEQTA!xgklp8eYW#m;iO- zXg1CH;MR<5>%iRMhrGD8u{BT0OY83+r|a^OK99&tubP`d=d8SAn}AtPLbgwcvvc@( z{}gGQQ4c<|M2gZ~a)u!a#MFMoGQeJ@`adAI$-lPmZ}G`3gq>dqsAD`5ek;Fn?2T^| z?F3wL^XrmH#b~%eY(%cY4|UrRYxxVf#=5j7(m7l1^D=fcrHI_dK)1IsYPj0F#zRU} zOu`1X>A2961>D?`w>t}njXC8(&_9Hqmb_jzC*c2cW2k6zOF+H#cYE8iH(cW! z<{{o1!gfWcrsXeAQJmiT2szd8%t?(BZY4u?O@pQcvIu;^BhdQ6@GZ+Cq*LW-e6wC7{xzNqN;qCbrkV zdZXl<6taDU{{qMTpj%tKm4O^*cHAFQk_u*_z~QyyHW|~>1qu&@69zExIyetucRHK% zzy)Zh?-_RZ)a-`n-x#@TzR}Qc_Wf*x(DGKgI@TpKsNZ~s>G%7TA+mk9Lxd=G6G$=S zt~I#|1;{X+e!bv`EcbI(Dd5T~Q-^Ce!Cu41h@eXFjeuqjxVLH(UwFuxObobwz5Gl-8eA}zH@xOSzrK4KPb$;`c>mW%kRQYAVgLf5G32J; z1y(H}|2j>^Cm3IStS2||39Jp(rn^*iI@}ZGHgU0%N>eK#-wxN~{&QE`!iGu5MzMoB((t%ym8=mu(Qt1(aS_?AcnjYvMS5LVWj{%X=9`|6k zT7~1Glm8-DWjQ%rEp#8d(Vf9W{7bD4JS8aG?M}p~If)e&L9wz}C46~}d-?~VsJJ$e znBcZ$*cg)TwIxy;hcB}aW&$QSBb2!0@lYN0%AncUP<~yXQ2k*Vwg2oz5o0GxuT$`N zTNaD|8DrE2o2D8g{i&HjQixI&E$0Lo!6XxVOE(+5@Ld!uQxUfo76CyOrU{REu8Yhu z31RhmnrvTUbhVZ8CC{b982@|NMN;h5%1|#kzF6M*F@W%pS~Jf|6I@sWs$DJ1!ql6ztB zQ=gfG_Ax0e?VAa__)f8U_A!-MllhAyy2RAamS0ryYzDL0edJCq7E*NPgq`;%0$o@wu2$tR)I0tlhrbF@>&~EVM9qRHdkl3E8(46onxhZb>tu>d~FFH(_O<{)quG#gjiDW0P;s$DI~X ze?Ao!a8CT#$%!aK3_1Jm4A=&jdsRmr-*EsXqx~Bp+XlyBaMY@Ic&XHNILvuDV~)NU?UNbf)Ur-jlgzj3l4w7u(a^vV@acJ5;qY<1+0RW+Dj* zYRj$L(|G5T)#j&vdo8UqS0#j)icmd3S^29)k<>{DyKxV>hYu=52&ARm`Xvx#NBS~= zE%*~Vg7}Gb)v^79RO8osS4p4O4yv5LS1tIJ)?XJh%QsGZNvrZ}Fgqx@<&~PBXtrZmPm|lF`e{>C4%r z-rY?|wP4oHt)6D}^^C}=lQApe#z>nRv0~c3t%FMcy{(6Lz$wjPt5>9HW_4Q%^uJDL#peg|OL*1t=wy3;$mN?3unTGPOmX=7foz;MI2dJmG;`-YtHSBwyQ7~OC&anAlaifDfa zV{O0PaA0lEq6RE6)6Ml+)g@g1dWKg&nB{+5p|@v=V^p)tA870b;F(g#F87Rf#rjX5 zu(VSIJT#5{K_%bGPN@k0F-C8uy8g9d7^vd#s#O2`mTjm0Dep(7@CWow^_Zro4LoU+ zrOU>rQU7)4KI07JI-}bDnyV=_6F)P|!a1ZnD|`kr~^>x;-Ty*TxtMaEUN^o8cqWS?ob<<8tTMLXX}u9dk(9TKy?fGI31Cw|w7E^RxWfY0|okT2E4%!KfQ1Kpyz3LTiEfNd#I7nVhT z{~J3`LQQXAb$~P_+)F;vxiu9=zW}RWpLU9(;wMVTu?{>kvOP8pzEuS+F1skAM$QsqZVjBI zOnxT6hdzHH7w_16pQlv;bgGE5roSedn29wL^b843GZc-q*xUuAQ-K59EvUXf}3XpixKW)>tosWf#OtM>!;qaNRc2#GB~ z@J%!~up}(QHyg25BWBKBUQWrO;@CSYw5^s*N`?gnS|4ph!Ov%1zsUE$rU$zhaie>s%sj>>A zxI+nR4DRzJ>n8M$?l05tyjyQFGsp5?d=Xz$kqCrL1-NRyF}ysnkC@W*DLs+Ag^?R9 z8@3ppI$7#b#l=gWf=X$-iD8%l?PiSXH9RJVTm}9^0uCz}Svt>` zHJbrLc{sX6d&nqZz7ag5n`5N_I74MPS$#iF9Uxb}MRTdvb^Gr{;MhKqqG8SOQ3{K7 zlpoP)+4`?>9^)CZ%5^tX%yM>&^>5KaG2t;(RycWpM+Of+{L%x$LtL2jP2k{ zdF83d)sNz4At3k6-|tAH1)ZpZ`c=t5bsd3k<6&7piH z1n~f*vpk1P2_=|Z*vlWUggX;@`{M;A!_y?DqWw_NHlQ$-mw{HinX{j+G;|k)FC;M+ zof9BmQbOD#%0NNasO%XrWli_PW8ciGdJh5m8@`;UcNaH*UyuZo+VE-5r6#A_PqWBq z3en8KQdXpUVNvw^N-jKUI@D7mMz5eOO3b00Uq(T-)4TzKSL4; zl}_`FHGM+sRmK`U@en6%!em7Z-{xn{(j{8JY41d) zI;p@3VC^UNmBg#UdZ9l3>=@g`sws%0W*CrBACkWfrega*;7a%JgU^RnYlliz?U`SA z>LuqmWfN6N1!OV3`MJ~xdq3H-WC3HmOeC4p37IdqNX(mO6=VJgzD#`uutuRgKw4Us zW5fx`IiXI7R0PP0Y^3`VOs#Qvf1Vrap*P#i%{l^Nm3<*}m?O(LBNTe#m8sUafg$k$ zRE_gFs%}JW6E_!t7l36I+;hj>+fwWg@Dt0%O(H{r4U4!<-x6PUjZ@dOQk@iq^2d*F zpI&~md)I~_U;eq|s5(SnJLRug%d1mwIK)Y`_&moiFf1{(Ph&`~O=D=|`%-?HSfrBk z=VKD*6+qr!lPOyl*6nB79=_5T+M^arE}-@~(~jCdN-kShDi=9%6c zF9y!CzP;1C?=OU?W30A}%~Pp6x>C@|aX4_?D2Vvk?0(uR7E8&6WTE5W7*crfqwHQz zye;aMMNlCri;gWbXXTtU=Go=#^Ev6@=rBc*@xvzf}Z_%2{WKH`xi)UsDV8B0`Py5uEOtF%Aw zUB@Z(YQLtzG{6F_nuf^4>Re8<+OYZ@cPqVYy;D05cguanaEb61?ecKS>^kjlep}Di zcm6t7!Vs`(HR`hs<fPvA;bif#U=4)URQ2LJNyWB!D-z6LQ(Nb-ARvgz^lw=oRL13 zbAoUUwc1I@*mfGQ1cb-$V2KIn3&N3~+>&S-K($6}Y(NCc{eYG}?V2xANa&wCO)s7n z#DzagntA3Kbwi!MsFeX`2dDQiGP= z-bfV&N;IovDT2X2g_+^~OHo_^_b)1FzB|*tH53fYuX#?X@5&}Izwx@m! zfe=FQAI5$DCT;?KRy9(M)X(8T|Ir2G@SZR&A4;7qStDSTWWpPPW<67Nzr6ERZ2j*b_Uf@)qjrC;?X++T^E zEom7QLEahq=(Pl#`2Tz*-nyADK7eQiD^(T7vgN%th93Q~JhObExhWs{>uroJFfGry zJ`5FY6q%nFOGXKQDBy(;9Bgt(GGWNk>&C`gwG(Z}e8U%`0+Lt)1w1!s0*ZcyH=ZPNIC-$bLk@m!cYA)Le`PSRlS}SNvKA(u>z`qh%3SOudn(lF+zXlS?__%$%hs z;MTiGU_cK5#C8ER{L*NpaLL+u@*`pWUabjt+`Fe+| zuk1#!*rtz;cxRW99!wuAqjL3lQ;JI)U3KE~w08ixUS|j)GR3GqIaBHpvHaDwNmkHf z#W`S)?&hV&bESs9ZeXyRXK0}2PUn*J?uD>9&9L+*-J{Lk#RvcVxuN1E@Ph&<4*KV! zAy?MK)%r3e`!k&xkDy)iTT-Nnw;^X4v} z@ojIiI3`orC8`t^c$PYS@RACgX(j0G|KZKvbuj?=05cKu^4j|xwV*+p_=&Iu(~1{} zgF5twH0jejSeKfcLSm~L?{8Z4*rvH~ySOvwuDZ9Sn`;0+CFgcq@1PO|kQVVwl_R+5 z)$(TRH|0!XqjYdt-TpO}G&OVuq($_%GGt8_T|shn+TsZX)S1GW1v_@8X($nSlyvjH zRDZMf@hb~^#{gDE-7WzU1WP0J`gtxDOJMi#b zzo;wvowS1~%y~W4z=L;zYde)r{nzvoW&7hOri%4t{3U!qi8PH3JQGfy;~p(V@JjzKT}_BMFohk}4}<_)CX$;zj`o9NO~Zz*?0?V6BP`O+0Sv zaHMWUhYqn$qK-}>ySGb2(nHkaPj#wq{7PV6d?E-(Z?9TPqlZ(%qm6cgTi;8_F|?GP z)L_#0vV`I*J@NX>BBn;!37_oPntVk6Nw7~1Pj{$>ilfRC`V=65+|FtLpN@8^qO@N82xhK_zE8V6SB~r3gmPC5V$;yIq`CoVKrOGdMF%SKRN@I!|iJ!hv6Cf(u?WY~#^~<9R8>V>|UCJPv zxEk?M8^Oo^#k^H&3imsXd7}~`xuZT}XFE@zq_9$9|F69ZabnFCO{S#{G_?L7sPjI^#Ri5zm zM13{mj3gx1`L1r+=#t5)`j&dFnJf78v z#;fmStny^IRflWO`X2>-Z1Ix)!#RP)Q@y|Nwc-HN_q1Q^u#tK3V#A?ge%QMAg<=rV ziPC!s^($+$POopW2f4H#^l0JLPRj2uOmQ*ZQ+-`icMaJJ>lW~C@f4{GqvdQ4=sc0- zRyldIOLH>(tG=09KWh>}5a88>BLOk#>L$`vEXHcX!yBN0HU=Fgyl>@`Wjf9jv31$= zvg=mB8&;AQS;s*v%x2T2o9|9A4j=Zh>RbuG4b%3ZXPaEN2tj1_+juZL4MYY$q=b_2 zHv#uZzSJ{}nG08*&k5VV;t1Wxr@O?zo6d5EE7B1$gbmWUJ;BnHOu;9X#eqxK<5SDx z@kqwt>E}Cw^nLn;edaU)uZTc+8?8D$Wx2@g$$Pw?)uf2a1w!UTOgT|DeAfKd-^)eZ2+j zOEjAKsr@Gzy?SXw@#yEfvLX15=$2aaz}D+J&>NGjswpDacw&2C7}4``7AN<%7gc=} zP~AOsqI8@P5aO{2YBF+fGU*s^c;3%@&_TYbW{y-6n)>_O@b5iV9UlcL=tmlSn{_^X zo6lQao-YFNORwr!%*h7EzWQ*|;Tmr`m_7h+SFe5BbycDIW5NRr0>FPRi=HT>4|!M+ zT1ICNK`#hH(fL9in1{OJ)0ltEpx1qJdngCDal_#Z{YWyIY0WSaoM|k__imtouZ3Jf zGxblTZ{x*C^7d1}8TBa)B{R!WORG@ha7y|CsaRVL`Sq4?CmE7H6y8xa=&{7sXJHryLQNSCv-4- z`2BI-+k72^D24AGcBhOcCFMRM9>}!6I?`Ar@=FqfhjTV|?6h5JzpW1IGb;FW*!6jA zU3x?vGEk95_S%o{`}%dqV+K3W@8<6Z5t)m_a|*LxvqNMPy>YGGtOlwf+R9xHiW)ZY zVTAlS2d6BydfVx;!d+dl@TM7@m*Fu-&-*jP4mTg+V*FXe%O=w@P^GjuBfV*ud3fDP zMlYoiN<=R+_>4n3Sc5_PiGXy-1cS#Dv7Qev89{S;!fZX$*f-INsbt2E>0*yx+P70xcmt zoY=g-!r!ebV68xWQojW2tq`A-3+)HyioiUZdgcr1a`j+O0^2UJq^fh5b`880U(B)S zh3EypFuOu_@BDQAFz_H_h*%uY)Z}ghSesqzqL;i2J{w|go18>ArfGAZy#&t%ka+tV zCNc9&N7%@el+5n!2xf$LS4j48YOW0{RG3ne1xPQtdnP7+{>j)@F7s2z^mMIuOYOJ) zr^{!Jx0MeH$_5`)vy|MQXJTE&2G&ag{=F7b!g`?_Sfq#Cj1l>bv6jSV=H)CPjVvTc z0HXSd<)+S8qS&0uv$+?4YWQ`9l#%I4R04zL6QPfCZ1_r{PxIw%@eM<9^A*VOXKzdgU)8h5CP@Bbo_2U`un9gN{}eNFPuA z-_aPWG%OOGLC0rhc#eEKO!L1_%JuxdP%crf{JJmX?`?%=b?YuJsg*4woY|;iAjEFt zUeea_DxF!VZ|y60WD>`6y+;J|Q11R=GlAGA=kd$*>c9-tDUEMA7n$FMAuOAX%U4W= zi7;qZigLS2+0R*=%?LV2j~DM~0n9O3JA={a>fXL9E$6*FI%R8ApvHvMzrYA8#)=oj z4=8%NP#EWlZvnsl014T-o~ZH7KyBpE{rZ+!qu}+s%^dPbn*I$0n<3&0{$p88O{pn- z99b;5)CoRX?~_$J(cVWqGM^JrO%Gc$k@dOk15MrH;qkpA9i;9C<7X!-`Y z@d|P<)cOsY7F1Kaa@gl%eIERi9G~`yc->X5Z*tTL8Asxgi+?4<|Do)yqN3ox?_oMb zKw3H^luj8s1(favrA9!yK{}+n89*9l2B4`%vmS) z+2?%Dwp^9+xKjjB>FFkP)NXA431QO^&bj_ri`b8GEx~0?Wb0pbFDx_=b&u;!T|f42 zAKJ@>k5QE~!v$28P3%krO16q*Pdqtugs19)Q%%E|CQMXxZcgxSrY`KhSn|9j6~FmW zvw5td&Ea<%{~*s@vMHQCId{#ra7vtB`;}^x9E9Rx6=zE7`grN;E%&9+7^pk8Z%!hh^s-8_i1LSPsD8-D)5UA@w(U3A%Z3< z;_LhzkxhuBf7SKoM@sv+z0*yi28W7|S^qN5Vij3m>P zH-zKu?Mh7K?b!@1MvEMYmFu0aDLa2NC2}1t^*3~6vgyc@#)T)J75K_(U%W#u!1G-k z`0UJiO@5&9(IKd^?HmgRq&~f(p$O!hV>%*IhO`yMt-v`_1bKQOd0gb(c1Rn zh7%B?y;C6I4Wa60AjV>n4pn=_kHsPV?boa4Sg(R)jte}ja4u1k1Tlp>>L zz%6O1kE%t3t@)N$vN~bywRX&fy4w~VY|6N3mhC}HS483BUGP-AkV4EPYI3mcy-|AR zEJkT_aqhFo3%l;fKCDPL_QT##5i+T4KS6kck^vCY8;bJL{pr z9?<%{RoaA8?q+yjK=^xBkiswQB*I}Krd5Y;5vj3)aAYO&?7~K)rq}xF4~Gfm{f&F$ zxcNz8QK2E#kLwff{WtEW!?$Hd+j;^+*11dv`Z5qRbP>t&fd|t7Ju=DiU%yon>fHR| z$^}ad8FRn-lFjK{Me3=$uf;3q`*SL33Alttdt2vc|4!zw=bcUIQH`))`X*oISAEPN zaGsNqKM>|&&xb$}8%*CycO3iGZJ->qrX)BHlF{*Wucag7>DRGH^CVvru!d0NNqr^| z4x#xZ`I3MtNIw0ARR{^e!76uvnQ~5v>s#5P5wfy!9@&j-4AoQ$F($u2XYx!$&v$!T3u|2up1T$BH9T->@HXADA7z`U3_+C^j`>gEVIs zE-JncKL~2h?v43Y1>i7goHMUpdDKE&a6f0PT*?zhHba+}V8%s13?dF?{-ik1!h&Xf^(@qPQkg{crr(a-pU#JyC!U- zbgyaUw!X{{D_bSEVeh~&TR}2yF?y%WHgYi~`gC)lU44wvhlL%)P>4k?9s7~sSqNdC zbSVKv2x*?oHUUNmK{vf;2=*sAJ%Zoa?!B*d29S!rr@MN^4}aj)^jfnV=8XQXC-w29PR4-+{O;~tX^aqOv!R{H@fMVYDC*& zso7l!z*G~~oPWb@S_jZb0$rnrV%nsUjXzU$YHg2YY@KZGaM9sZLKlew4)yH>!XmYo zF{^DBP4g0a=G3agG9}ATev8!{dPtR%lEx8);YgC-2Rn`%T7xqd(mX1^rP9a&YWyDqu7?`r9`6{J0ef(v7~*9Nl}0MKIWYe?ccb?T=b30OkEO@ z9!uExL-EgXyAVkxSA8m-e0i>BtrI@{Y+Tbz8Q+o;^|j76iuT5Znpbvmofj7@ z6sqof$)qPF-H*b9u$>@!S(%9J7+v;pEQ3@v4awof%f_4INy>fPCuPxUQuNMdPj$8O zAv=`ZpEPagW*4@d4TAS6Gyf|pjjOmpdZ?9u4TsM~xc0?K`SMSjcvZihhZnnhE62j> z0Lx49rkwqgVz2DW3(anZQoS1oXrh85BeRY`xPZVT8Eb|Odi8x2HG}n7y;w~I_6ta% zBblKnj@eBb>$~ADsZzZXJn8<(HZ&5*3TmkydWH{?>)oYHf2I#DtBx1GR(VYhJ~^U8 z0k5f5vu5U*kx)l#v$rAhNC$n#dLtbc|4J_eB~P~Wg-r-X-n;D=-XX}JWF4_+K1rt| zTHZSOJt1PqIYW`L#X`(Wvg_&nfH#Guh0vWuh{SKG|MI--V>6lX2?=T zPs=lN+mR}tjDeUvV8m(psduj+#i6QIoAlb>aQ*pTy0au(6v3O(BwE1x*1z)M%3%L} znSB&xBX!YT;{48{MH&aJ+K=@Qp4kTJJYDVB2E+`PG(fK#%*49GfmS^rvDamOb~zY1 z9;_GKE296@$`wUo$&zOVOP1`nnb++qCBvD0sl4vYSAe_aEA1K#7tk_I^~j<4Fw87j z{>tA9(Us_CE)^BSiC|MyCKSg1UM|GK^4Ww*o~zBeM|{9{Ro7P)Oi^`HbFV?|f99$V zQxnnVa#tS$ac){M6ITm;QC?ac>l7ebFA=V%@I5HA63te!ywOZk>R(;Y73YwN*!EdpBwDHKK0``M zfF9pM^4PO{y%iTNw+$3+j~4*@p8WMz106F{kcR$=*=`YptJrEBz3y$!W$QlAhu zQUc;V6RGO3cPF!c4(B&^Dx%dk=4K22Gq&tk%(>WRh_$f;?H9IpGbKTtPeR{N>87L3 z)T*7`=yafih<2E_dbRFB<%4ov^B1UW)X&2@!+jIcStfY4&50MGuXWjb_Yx61-|<#2 zJ#*ZSbDoTL@p{&23#>kyqv0mi8V9^SVf|T-#IUNvgpCcisC9E6kLrXVU$b{m21I4i z{mA-~#y41fE5?mGb3TkO+Wph1W`5QgZNY6`wTaE3w+lsIi)Ye`V*uCvHn#w-^0d0k z);T7`Y~>%Z-?8pofo*xHVltlbcMbpeVTxd_9IuEPKJ{v8Zj$F=Wi8-1n^d2&h8KNc z91XGZ;pa(drPbe)oDqn6x1e}caZ2mXTiTb@}h7L5}foA(FyCBKb-N=>3nlVKo#!2ul?T6Zdn3~f;cdtFexj#3v(h2#SeG;r^Rbpje ziDj~~SC&!)qbJaax+Kip-{?O)X7r4Sc-EJM6{7_maKiyjP)x2Fg2@jsCo?ElxbGoa zC)>Ypt@^kt9{%U_pvK80s64@na$cu(J#F4(DFmBXj&?iy4ZS_l)To2=7J z4<;{ue!!v+0aVKTSZj(^3|GWLOf#h43`l=e;--IT9*u0S6fQ$0jaAI)gvGS9S0GCv zaQ*o)s?B6Zd#l47I<)CgIld)+)r@bhdFyAewbGG~uUe-&@MQnol5Wp7ZixpF40DIl@|S+rlrn`t`IJ zymUwF@vd=^x<$mAUT7)H3zu6nFM8l>sxzTiJRbIgd$ATiwKA1)oREhS zF5iRP&J#gXVC^`qMQ~@%r%B_IM==SBC+1BvYFzYZ8+zS@Rbc-T&F(A47UEs8qsO%3 zoAxx~*$7iV`OgOI>Q=C}lXB_C0`Gtw`KvUiu#TU?fVCcx3Q*bOe(R{w)xJwxQDH+` zV`@xW_ndp%@mrROkh5j^@%PM3OnBV|v^@hb9A6bXs4pZ`i)E+Ks`GrP|HC0h*V2a9^x=eR4o4ac}3U_;gJc8E0>Ouf8GQ zc>d$0k#=Ds%>VvgzR`lG$bP5to5RC)DrdRs!o=&emLl41`)NNQl4O1+PjWN=K zQwR|=%qCr?m=X zJ@G1e((=!*iJ@nAU!k9;sNHURI;a9t_B=2a4ol}~aW)5};cQ*3{)cq7!i^Orr%Y$^ zM*XS|8t&qbk!8>P>O)Pvooc-uX5_{Ohd5^Y197D4CSVdiliA1- z53OGPGxBUw`YwADen**MS=gMSKSJXb0gr5qdo3HSxZumh?ejY+W>np8`Lzqo@%+iw zNEf?|jz&XntAZKJ{_Ayu&hGRemttP)cINo(eq^`YOAmEY?>Yo zyvph#lbp=yYN;QQfcY^yJT|58xz`SRN2h)bj1qmz*l?z-J+!rKh(IIaVU9tR3vi4O zC#_k{vS=~}OU*L?Z5sL(TW&l$iW9y;h~DCo>) znsxbQ%y1TY*UdQ*Qpt8PF+jou)6wsgJb{@p^9N)!&IcJvFjCWM#OB!6{YBr=`NSo- z`(sx|b`lW^fS{9aIZ7_O+sH!&upQ&oo6|@>dr;hg962wHlCwWt03Q8GsWlJe%w?^hmcg zwfmSFB)>W;Eb=uOzH36O5&kmGJGTsYxqDBRQQ&u|@QmsoP5TXx0KoL^Whd>j7L8S= zC(fjay;aXfFPe%z-((t%RoE=4)xM2YF_hG*QmBtT`&&l+I(IE@9WR0T#AENM36kSw z!*DQ>Lhu<765!Iw4mh$<0&SBlw|T`Wdfxhw6KOXzreJ~uRtRx_Pb%RnW4)$1R8P+wBh|pl)Ap_R zi$@yEqtuIq>=4S2;Fh7Wd$Vq$IYln}V<#Rv^lQ~*7UHRdwexo z{p(VK1Ezzz#aFY8ct9>wR&1o}h%UrupXSJ@S>Z0O3^f$2Vl2^41)c0wN<+nyhL2(X z$U=rCn(JtplF&8Gzv&cZ{r)~K93*mIINZj<-!8mT-P((9<5IiA(@5R8< z?dg5sXL}WQ#^kitAP}Lf%8Ja~B~6ub0^hN%EOfFImfG_6Oh(33BTl?iov*_ss-f9C znEM)!0rBko@@YFo zRMwLVboNk%((rh2gWeg=^n@&0RJ@Haz%6_!htIbr3(DAC9W@Fys6a}nB}$v|UK}7NvVbdI6z*#>(z~2ZRL}_U& zP~?7LZ~%#7PB|&2&C)KuLwVzP$ch1wct}tn3I~0{R#sUf4Je>CMR?-$`t^Oyvc=Cf zFJx1|?m5N-56diNCm+?bO@Nq}*J1k1!C35d0b5sw|0NyeS9ik&+i#l7(XKBHi8U&dRKIp}O3%%9u8cIZG**eR}1d-IwqNg2CDn(3YV90lV zsJO({cQoNPVRCxerSBm|xFx0d3`Dzt?C3{KVvQ1U_DYa&iy6PmYgzvPa-)2Q&IlC8 z`#~z6aPYz_o)1nBq}%lnae4J9s#+jvX*_7;W-x{!oTOP;EiXjt@2n z&x-v6w}h$evV548Lydo)#Y^`nvvCZIkUV0Ri}j;JYR+Fi|xq*SPB*dR6{;0bfu zu@1B2V?2g@<1>In68aW=+ytGf^}U<%f^fhYD-$%$@42Jjqj`Yl73%*LY4$iR6i{R? zN9#zvtuso^a$gWZ+|mKmZGd4>v}Bz2Qb-0lm+Zozk0{(*tUKMqGD#E5(c5HU`qz94 z%GPOw^6*1@N&0RVk)da@?e1(&OlGKH!)KA(w^$-9$k1(6!EMgx1w&rkff@&rxjjwi zrwI7daVj##(fP$9^)&5xX}=GZi^p(?zpJ}x%J&rAD!1IhB6a{cvG=>Fl2AiRv@nc6 zo|N!Ay8`A8J*q{BQapK$q|P8lL_8Xt9&E*gWIEsdd;WZFysQRgE=+94>9Zj zd{*ATvs~SP$t#YuUpdzL2N(Q+TvK$a3U)ve?N>J@fK|aMEpq~(mnYzCl_9VI74W1a z25qoUU{(XW{ZR)$976D5EI_fpUf6XqC_x|7^y-r--LkvftHXdvvSFM8t+RL8LftPu z2$&A8K~Kt&N?tD4Jvq-^^SQlobf;P@9F+*^D-)~DSh^$i21UkX1}1XMUf$yN{Gsv& z4K#L??#)+gF9G6cN?2$k_^zl3f$Kfgo6wR3z<~_n%Kkk?gp1cRF}#7I%cX*!W#VBp zz}t_)2vMYe2w^5N+C(wvWpA|V_n&Vh<0dJAv&8_S$W=6GXs^;dQ>yYlUU&D??-bKIXbFy0UO&0LN!6tfB{;9l!hQm)|w)h$E7o zZY({Tt;Aw~vf!wwsIaF~M|o!pC48UR8G*(K$zYKKxqlMHhWUz{POX_BtB`qvz%va8 zo}Gf|D&2j|8&mWM2r)+LR|BkV**=yVLsVS5tuBl_PrQiQ&Ij*>$GnPLA#W6e6GlQH z7(ZgN`HIHn!krP?A$9*-q?0USa-KK;1rH}ITYB}pJ-pA_TRa=^Pd=V?`t?e+yLr&}Hsg)iW1KgR4fu%O<|!|?tO5Vareaz=AR;F7u!dvy z_cg|+6KzIl-vpVP*gL}uG8H-}xurwxmq2Un3iF=}n>bNi+=`P3%XdVTy6@&nmW4T4 z=aEI!9upIm6?{cYIsMR{{2YitKAHbixLv z2A|uc;zN7N$ZyMfOt|Y_ z1NabuyQPzD`m6A{wU6$t{A#|`wj4FbWL7tD%$B9S#Y)3}<>XC+AV(fsD{ik9D-=Kk z$u^<*3{Di6^Vas2o(cN5z_rnq2fEw67M}#b%QTybNfCLeIQGM`*;pif4tJyi|X#ciTCb9}OAq$=(H?td4 zy6-bF=-045r{eCL5$!H`emp8rZ?JC5Y7PN!7Ip|f@G{4$+wTQvH)utXQVQ#JWEzcn zDI1%^Z~i!O@GrbJ|6sL;Ir`L;QmF3su+IST?b$egxgdKo$iRVJ7~Z(3b#))qV{|%5 z0IhMjEz@If^cp`k|1o`jwGYHMIdKL)*DBbPva^Zz?-v z44qFUIR7?>%Qkz0FBHab|CXMn(-E!~uY4X4z|ZWhbD#Uqfei3vR$ z?MghfvWCit#G~`a;x7}iABzBPm#^(RT{mB?7G0WSY8eMGg>0{m7MH*D^Es50Sxehg zeOcL0a;S5P&EK8TF#3IwV!_IrqoiC+KbeSap>H9~D~7mjq+rFsm!v;fZt<2kL5ZO@ zNodf>!WEWCunJz*j&-0~okHCLjE!2Wv^r)I4I~-GWN(}clnC{9VF5>JH~yM6AhZJLCw4;-y00cjhXeK3f1r4q!k zC~afc=|P*c*5?ndP6DTYDu#Ci$%J4< zY2Dg0$d0gh|0w3CmrY>52aLSRPwAEyIgg?C`THPgT8ZOTZ<&KCj2TMBVwG}1?e=d? z;LM|Cc0QtORKWQuF|hUcc5V;RYq8?hIbWuh;X$iA?RY~;j7|QP|4W^h5|-7&#dH3O z{$o~R)VeqTw*N*YotClunC;>66Z9AVD7lY3;vAPX^eeKS+wv0l@;Q)=Y6D@;xj^my zH&7m54H`CO3bq+mJzq^y2-Gd($L%7CUc1&dVV{zoSkAyMALQeOuYUQct`9J8ppe({ z09gn4^d=PMuVIwpPd9<~W7ry5C-_?c?5U+~pL6CpTkgGkjvD)N%m(@ys9Kb^rMT^T z7EL7vC`X}d54YJ@z0+r-G6bbhyEluBPG}a233Y{1Ij%sn>oYpgc3# z1-@Rz?U|q@&{cf*8kN#7xM_9?@mUCJqL_{4{Q+uXWwYC27vZ9>++3738k30Tjb|*LR&;!c8H1x1q`YhyF-nX{gZmbE zykz9YZr1Tlh{tYZO%&uS{8%&xHv}rLY3M6oXx@SeplaBaoS+pYre4|s?Ot-|tZQWL z0g~>^q+(1VpI*AH!SOCwuyth}#1Y0a0wYh2fXpF$l2GcnNXbZfr~`-b=|k|2&D^?N z>GQ&-VU5T4?P8~N`b2TiYvd}#*6R(wlA^_F1sxoqq0`awek8xUo03hWUW|S;+qGz z!2-Z=NaZAzsqkpo8W163D8#|Ym}q1yP5x4%m%Jf&BbsiU|Lt&w2-GH;B zSLsWyqczHtaskmMZte0}SBm?N9l*%MeJi{!bRragSgnGGUtfWLsaiC*`1Wcyfsu48 z&O^$ME{SzNdH0-8>dO9)?XB@lS+@E36>eNi%3gzB7Usk}cJl zHOp4Dl$*BZf@xMwVpLC8mATki`nu|?M2c?FSR?D5PlB%bRyRzNsENX%{T-h;R;3k- zI)*rU_>$^$*O{Y<0xW03K_hQ6*6x{Ei?`q01$XCUsQN6O*%970 zdxLUnvvb1#A6!oV6g*@Z6-p4BOS3#B5(ZE{<1SgisdQ(YgD|YaOr`>vfZFfEYHvN08!ni_NuIko)pzoNNThL;luf}O2TWRy91?G zP1WM&+jUkWU-?vsrmhdY+3&*HQ9h?aL1WdbJ*E0#L!)5+E=@PFY|xWeAR|mkkvZNnVQ28%0)zKe zM#QL+6@6_Yd{CA*VpL_5&gH_dj-n|&8p4wt_&H}P2H}M~Ds$-NyK>l&1U^s)t>rSq z>a);1>qmB=9J_uh)Op(aqkzO{#LFb%ai_~39w?8i4h>t}{-QQ>RLa`gi~$Ce&8Vj$ zQa%-7|&idUHDHM{F=}KyLX+!$=;&% zo}ZHt`m6zEsU}OKxLbUT!^B-H98D>G* zsXp?htdF~gI>q{;J$2dG%+V~gs@iot4~J-cAP38Vptn|FTl39KwA$w@hkI+IVNL|* z8j7pEf`(U?e_D4Hg4ayZLHd0IqyR`dwtUOTkPfg4i;o&P=@rReoBR%zqlEV9eOhTj+iiPPS6Sd5wO%a%y?uJl63w{Rrg5$+vz00WYjH=@SQ*zadYDnJlc_QFY$bz3}7THc+G{oBwO zZCL5a#f7#9>Y2py?G{Ns+q%x~4q#c1w%9^$`uK}z%;bjm9d#4mO!uYmP-69?X1L}4Uzlnm$%tVt=Kb(yz=`l^{wfmgan_bLmFRHibTWXM( z3YmWdykJe@mE&l(hO8X?K`fhb=SwcF`loE#^s*XRTQuK-P7Ur# zI?gy(a`~JD9Fq5P*G)g!z>M4dc^^y-4t$);O7!q#lCjYO%23x!-VHdl99Y}FjDE) z*50WYMy;Z+#ApbHq@Ib8><;WyS+{)XhD~+hN-r6PL$Z52f02mdm8Z@U=(=olf!9P= zZNR4{R`xlPM%vjl>*aT>OUqv@F_#Sf2KN2yAK(MKH7bsxof1t~um1oM-!{(*I%Te{ zmAud`McMlXIJkbnlFXEQ3q=TwQU`tb!bh|Ax|7puw386T|IJ6 zC<^4;h^F3xeAb&sTe(8)2<8CT-PQ|TyIh1mifvemE)ArV>`BQaXNG(@%2OTP+Zhy? z&NV}R{>@pMpo)b8t4dVxGoc=?5lAx{_eiy0+w4$hqX*qpxAn-E_#GT&73^?As;vDf zLt3O`PwtaHQ48ep8xBcJ{s&iG58yk5KSR3sE>`}v2mk7V3AHM3d zvqQ{W2v7TMoI*^!L6v>uP}uaJJd#W<0`mANR-V4ZgldD|90)idZp!>HpiR~S>E?m{ z3gvyVkz2OO4qO!L)2Bcb?@c~Xg=;*53mU_7x;%7({!YnjohWXusq9J7O@&~?>MIS} zZt*$0vmzi8!}c1t`S)H%x<1s`Asfa7POu65EB^)8CjS1LCo@p>(2jm z)x!z?F}-P&qi46MJS7t8%0{8{uTj-W#?tnxK!25>qhcAT7ObeKi2Y!gk*~~ zvPMOv0$}G^;gF)4-^TTAK2gOFap4f~oda>I35KqV@oQ8Z$}U(JT@xz}nr?k3vTlU9 zBh2lkQ4CWT?6IU9_RGO)CW@b4+Z07Nv=d#|=c>5*KbddgeI;|Z_M^Va0%oOLmv;AS z7AghK0uRvI`NzQ@0lYu1o}cbM2X^oe=wC)VPt(-tq81E7_&{3118|%HfV>MvrC&Va z1n1bu_LA;k$42#V)_&Fe0bxgC1Oy#kqS-j&T+}%W?nfDxj6?Av;k_TSx>#ro=MQYV zJu_+D#aU{?H+nb-`&(tIl1foa(&Bab>6IVw+&*5=s$;qf6zxg= zr!wss*+fCRaV@^Cjom~c=~M5UPBf6{r7du~a@cvQ334KI`*)J{G%yhm^N1hQvWNJI`qmAHCS@fPUEy)VZj24D6U4G$GOAyu7=;@@D-uzL zOvt;xsi9!5nsKB313s$!to@QAN1~EDHO`$)3C84+36b08u9^VUy5mz@HWXogBViaQ zdSuzl@V#rr9sTX`=3$n;5)z{I0>6wNw=fj#HO_>T_aG8EJ&{Eidpt>v6a%A_`yg`j z_jQ+r%k^%E{7X~N$K3<$;^z2|vyo^-!ih(FD)oH6=4^cY{TbEZn2h4N46pAg`lJRK z^;-pvCEUz*4#3Mz_s!CgW6xd0t1av_b^y(dWMswHgX7X(rf4hggX5b026)y+?w*N9 z%9Y#I_g{BLoLwj4Nhn(mJAJE(%KwKKXZ=we`zrtTEDoweWZqr`9^o_x!ZJeeW|dsQ zki|FSah~>U&PAlc5J9eNq%^4q~+Qnp8c$_yT>ra?T9Y^QX)z+(>Jq$KE_{J zQ?~VYtI85xa%ZIEt{n`)pq3L_;8ZJR;M5r_N||k9!&t}kfd23*R$-zkef;W} zLj{5g+w~4uJOxBb&g!S)G5)*vu{XDKHB*blN>^FLYsO`7@E5&4&-^8MKvN(oZCI2` zvHm=?07Sq9!LCU4LJkaB`qVFBoh3Sgs3b3BFZGGNZ-P$)U3te+K9N-R)EJ4yJGCDT zHxl}g8ux7RqEEbo|J2m4F=}^7c@<^!l%!x)#3cL| zSERc=G0rAd$mZhl%MUIanae1KPzSmuWAjlHE2Ll-FD(r`Pv;?Z=O#iKHkyXA_fwC~ z!Unz+$Rma_JG|6%Qu3shScMXby3VQET_=2S7yS+0^L(~$>&{-LuVRz>rYsZ7(G{mi zyR9|L&-bbb70j7aRe2FdPa-gBCBBWy1F;Z6 z>a>ZL4=-JB`yAV|Qpz_+IWiWrxo)|2t&5={&?VIAI`?s<_Kjoa-P<=;B&eXsow57F zpt0SFgqY0Ll7`Pdh!Hh45DBVwa?#R^-%-K!66x=FdaH*ES!Lu8^=7vqvdWNOK<4RMXAuH@hLb&^G~MQ;uw^3c|{X4AcEQmk!e(;HT}i7uJ&`E#Q9 z9xSMq>v+w9YCG>e&zaKeTe9KIvwtUAZ7J|OWT=`E4T!_q(G{`&RPbf>-fe(% zd$_hE)_;NND`F-nQ^TRAMGCUZZTo=ZiWuUrB6T%CT}oI79?miS&4i`vCZACN67$Un z4}3ebv4{xdiGVDz#8bmm1Mg50@uFdB7xGS|}`FhgzPWuZPM)o?!e9CAGd zpPGr^rD;{o19`1cn`qBY7A@a-$Gw?)IND<4=cuC5{LCdmOgL=9rfy~IcTOH^znOqs z%yr#c0LnXUe+6>d)nWsJJ&zYJHgA?@PE)H7+S>$7E-)Ngjoyw#APTdX`q11S-qdOA z>D@%GOumTPKhhEjOg@F{Tk)&Dq7u%Dx>uKHnfGgL4Lgin{4^!w^dy&Ox2fWb+^`YK zR@IZ@Y}B?JX$gl6aj0R|ou}cMlSyAX|J2Cboa#wzkBLcYc~zmUkRM5nv&GP@o?K0( zXye6|5NCJ8Eop~~b&Z^*X<&0^Neuf#H z?a>lf>xjTBHoB!(UT1B2YJ)_Mw?Qk2&N@jsA(;s6a|e|_`_HIK>y4anuXt_Ynu=Rk zL74)We7-GDKxm^T@W>Jl6lIHXao=qdAvPdeC467L{J-d?ziasa zSwG=Yp&3i`Pd_QDP-^!Kx1Kdjzm%pV%1nyFdBvgBSrSUljL&0kiqO2p<8!&^u3cX{ z^r+)Clwm^vMPr*!m()(1fL!`S@#1(N7F{+RsDpTpb^YD2f^dNowtx1UM4C>O!>+a5 z;o!4si0nisU1D7)H$wO^|erWac=UJs2%sF{+nxbAMUO$6z zM>WoQZJIkU<9H2us3jttte0NcAoRr5Mh>bJ1*QIx=80qMhT}M@!OG^=l=xx|@L-ug z(nX(tc~NiVB*Hg3kl{qNtW=Q77Rxsh_}1#3xkZ*LH-h8AwrNI4Xg=fn3R<0e)?BOG z!G#0$kjN>+zP%w?2Hh1az8*${UQHmj6H=JB$>F_S%vCxg=<$FnH6qua2~(dw4yD<5 zJ|+J(;&<#@HWI?EPR_p@DUgkIqMx*Gt*^u#DkpO(3)A3Yh^6e*J$E^0=Y@_^uFNbl z!)C)$#H?OaxOX7uNEznbj*9-w^5%C`8~D*z(qxpLbh zzk>w#6V=h{%xr}eXIIq!Fe8jbX5F|1E#Fj&n;5$V&A4{-*xq4y(I?#3So(5RaZxSf zDzbQ)+tE5u0J^h?7TPS4(QT}`5aqKw3c*6&d5J{o$Zudj`g>jvv*;qT(LH5XN8eFY z-b3|72zR)6Q=A<}?0fGF;j*`*C6yY(zdZ&_Q1pree`~$bm2@zl*Jb;gMv)Y6Gy^V3 zj{c4ixD1DbH}cxuiKTMXl0P)J{Xh(}WV6Nn+2@kh@V7SQbo}5otIfe*z>bD95U%3R zFE_W$mdfb9-yJ^j`S1K5+dV<06u8@M{JbS1h}B-#^6T*!wSyx|=QEK=7Xd?J;RWNHgQlI}_ay;XrPawT|m2fGRqr&)4E4V{3?i|Sq)Cz@} zGD>2zJ^b$~l%jf+UaM4cvQwGe4(V8J%QN&FrdzXoXjWm=AUpK?9txku=R^1<523Y3 zkPs{WP@3~oI>y!+V(gEO*d)`m_)yqdHQ#CDjLpK!q+v<{^3Zu-Xr%9GAnunU3l=P^ zJiB5@u-{j-!@7OqLjt>~psm0w>L8TG%v|E(w*qEQ*%!H3wV z`1o=;d2%Lk_35GhpA6{ce_daQGj#Q&_TvPxp4?KLVm-X7)cUGBWQY;4fL%;$YaUko z)@tgm$Q_dXY`Kfb{Cf@q7hyURz#hApq zdLwm-@gwSGm^o@&@eOfRwn#S)(}UKCF<@v9qIw$U+ur8n__BWf{o?%0xZ$%~FIzI3 z=3V3|XBIDOAAE-+(IF87la;4$4oH=k;TPto4oH6Y2DH`g55bDpvc(36iThj0>-eQq z?XyzeigbH%pJXSN1n!3*AMlkCY{2gjV@vUD{F;UoM8U4NO0~V%%YZi zv2pDJ3Up-0{BC*2kVl6y)arcXAvkE`?;A!!{qlVROd^iidWVDmy-1j;#_`FPq|VvU z*HEj5=N4|h$JBW{fWg@;{2Gk$pz_3FW6-CcN!y8>ZKyS*(17wY#CJT=iJyj)Iq%Y@ zu6n2td+Ag41(A(M{!OtS$y=SN3{n5ZlI`>I78g`&HwpG8#<#pxWrr*E<_m2|_L4f9 zv{g%9(SpRTX9EX6y)>Qh9ZZB{pFW-+Pv7hQV!Aj+P`5a~ZS9@kJkHzLRj1P|1<`#C zQp?xKO;OsMudb#fT0>$o2E8>evoGclpePEGtvqF>L7v-!YO$b|#IQ%X;rgdn@PF&e z7BPO$EmC}cAY3!L{5fq3ZfGsCdh9s)Aoe8RpVB$q#J5D#0=FY(6bzBJg6Y!NTDiNkO}TNss$D$_D$gvDksho1{IGKE^32$dZ7|dN zoz(%Ytd&4oaQUk75wDA}1h1_tcJ}SEy~VWA$s9<5NaL<|upBHlSbi@!neoo!JELa8 ziG>X>ZbqsS-3Y^E!p_ih3prk*jEqrfE2i3n2n)iD>`|#r2E`nW3`R(2+7=}4Y!_}~ zGU=B&1EtvtJ$qln>f`lLwAM!7nJY+oc~Trk)OK_FezE3?WO3Frv5_Qay&uECe|zKL z?4iw^x%al$!(z$cshnLu&O-G5VtB@Ou4A8YaVa&h9B8=&xlU!*Uuv0sJkpQfcotB8 z{VVrt{osQ-$F)c$OLfiI?L_6eS&`+ z!HgrzV+HMhxt-g=(k2TEC%)Yg4;(JFmvdhTE?U#H9y8P?i%t%gs} zclsbJ|3Av!DlF>u`yN)hLApayI%T8<6i_-vN<=`A8oIlsksd%o8b;|Hx`&}fx*fVZ z-w)6K>^=GVJA;F{uDSNy_ugyod#|-_pc`*3&^Hy7{t5de&@?ArYxCtqN3y3j?qH3> zYlUoCXNtzw^75~q7c6?9iL5`dfY@0UB)eMB+bdA~=_xzih5ugc0hS~#jLwd9|F%K6 zLDB~7b?pXs@1T}~^m&d*vc)9*b5Y*qFKICvm$Aa28DKv$gd4QjGLx6z!ZwNQdH$I8 zu4m#(?#O-W9w>?rv1F-{LUFhoR(p6d^l{dOMLQ}y&4SFSKd)fG_O4bFkELZnCbBL$YeiHmrpE(;&T;gDGOzp4(wJ~Rh1kSlDaFw_%J6b{M9CA=-W(#`ocT}X z#m67f?`GvGZ%DoY*a?vYKclG%W>dlROUlmtPGHO`mYFn{;U^Z`J0Diid{7Rss?Oc` zdl&GoH*$nDgLP1>-YtMOS9 zsDFQ8u1UJaLC=|_4SN?2q`>_!t8ME)2({t&r+-E};PhH@nZJ?6zKtp-BoOgan(`2> z1?IqAOALAGKR(}Bjyyag1(^*uB#8ry-aah`7nx9d-C2A^3g9TZjpg7zS*G3r@3bVCXwY z<+KdEp0G$9a2O|iq$-nwg50it;sRB8V8Nu%0EVjkYe3_&vhXl0!*x4?l6D_`j8}wb zZl7FtdZ?P%aqQr&hsYv6022%1@DL~F1}?(SJf$!C3T78ASW)@%)4$V+d=8A1a?jA) z<(4DSc3Ml7ZMh!u4k_4t_P+jlNxWl3oRG6>OHKEgvl+XZ;O&j}GfPU3mP{dn7D}XY z#F&Q#tomK2!4|z7^TNc5n0*GlPZ)`nY{?g?wpqT3se_o%bf;pr@P98a$#ZI=iYg-= zN?8oj-KBNshv}nI#$Hq^>Mlo2J0GQ?&==h_7GCaVO49b$v`a%lsLksg*b8DK!?U4R z9sqVkVlPNJ1iGDAd=8XiZOT@?b3&+@+XPlf;Ic~X5>KQhQ`}v1_fNv!T<-%r|E+sl z3{!JI@9pAF^9G+)CJZNV5&)o45yy}EQ;W+tS5b+F`2|p56d1fJ&9df-q_^_bM#Ovs z01x`86}<*iH&=eX4a6y$F8#a<{U;R|f2Y_U!<+a?b-c=w{noxx-q8g2A^twjd6VxW z+I0bC5xugqdh2onWxv?7v^HLsV5Y*>Lj)1(=^IN?NwKUTt#wSsFP=*;7AkLdjpI%a z&=;QTiad-g@LTlyXX_}Wd&R9o#|CkoG5@ubsa&hgUzPv;iEmi+{9vn6&t2tou(`G} zmLk%asnX)w{nk+0zQ`j@l)(A8pnbL30{}W!Vh96d%yDgH3J}pTtiF=R`p9edX&*W2 z>CP+L%1_h3jkeD)JTrz9Bg#zr?Ch}y5>E4m2X?fF=x-C(L$b$mR@Ag3gdbN1JI3i; zx4zU>y#xA}s*l5bi>&CfITCIoiO484jg?DB=9ivOK1^T-*Td|^olILs)sZc1Ca5gW zb#`K}b8}yE*jDrTp6oBZGcHmuHCtVZTv!Q_D|_i|1FzI zR*^)YAE#|e};?Lmr4c*A9bQ9gbl&Hf|5UWQ|2A?SeuqBwymM< z6{w)a`e#1r`Ml@^nS=ho7*`W1cyqw@@UZ&yS?6I-ySnf&MZXh6`CW_< zIj)yPd-x&=LAiIbaFF7Rm)&ev)8=|e2xpyR<2tF93;lFQ9iEM@iLYf|!Rjr~%-=du zEWjO?d>=vMGR|JNm0|G+foSo;Sv90-%+IP2ADZ0HdCMskDP?xJoRVsL(C{QA99(>N zfy+@>LX(!~Vee@~Fr_2Q86GwfhcPCL@(q2HQSy_2R2;UT9C;tkG~<&5M)5U&L!#RY zpMGAeW&Txwpyl!^(CUgn$kqgceXQib_C6IR)s8p0kx*ceS}cr!BOj|e;a2V0JqCf< zJ}Re23teA=oV05P{5T{zuYbqOl0%HHlm~Io%PPBPvy0z)?u#{?8{W%32k%{8=(K^; zw6nd=sr=1uj1evv9GyTNf(e)_yy{@Ue1-bbP9%^{dbh_tshzPvHz`d}rM)t-)%`V`1#> z(N%c2ly*Hnc8U(7dapJr(?@1b8> zt0_7NF8IWwcO(d1oF1g{j5+M-N%7E{7g&%8;N6swwzu7R$>SCXFu_lo;j>(g9tx}X z?p~ej6&|Z`LS{VY`+D?b6U)SR+*&;6Q9A|-M@E|SljM71Dka+sBLL>zeOBo!LugvM zM{b4k>DHF$lg}<%U*mckaG!-L1tIXIV~gut4K$4|ijx;Uc4`$uJBT6Iz}k1l+|&^m zx-U3CA1uAp6_YBYEK^}8^NSB0nlUr@x)*~3$A8CwJG!RV&P9hEt}1}H zYM(42f~-~Lw|Ni`J4n^HSn%&+;IY~_>Vb_U(K{`!@S=C6=%ydfbed-=JAQpD$tBJB zO{3!}lc@u?y}2jab;V9U-qhf^qMrE7UZ7IVGKMGGtbUSb0_k>M7boz_l#A^k;)z=C zxx4*v)9Kp>&J}&@dMWV!zIv=F+`t$^|LiZJ*gWiAUuGiR$;EA{&0~N)ID5BI8(Feg z@TR4heQtoxhdr&N$Q*Z3s9N91*CVgs?vC#ZSK)IkufLyh-8-WjdlYK722Fr>8o3lt zvYuaTCyFlibEdqD(e%&m;ieXn_aM)Fx_yFYz@SHHAE0AuWdd|e&FqE#$bU+c*s4cL z#GX%mn=^|^%9Vv8>5E&Eu!qWvGs{j`imNiIdNsCGzE8|XXxekOm$hufM~*O?DpOmT zpLw4s;$vTG?k8F~z*=xXZ2ArzCCW>N4UBi>p80-=3Fi4M3}jWQKS?q=a`zNqD_m^^ zjbwao&Aj}f%{VWokSEm|%+Bxr9i1!P-Z1Ej9_UoXH_@g68 z&W<5e=x@v?7r@EbRQ2fU67FKGe{0~-|l#1PeZo?@VaId+^IKg zUTv|WWr3g)W>EcreYmqF^xK=ii*6meLjq)sE5<1Z$z*G zh}vgKBMb_fJieTR7j*A&_&zGX_YA3<%3h+o78KUd)vv5bWG%f)kDa$?aKOG&R?(JA zFXJnd)m2O`6RptQVO8HTQcw`JQR550hqy|ALjtgE6s<3P<)v~%l~v-?8Fk;fQ5IpMw03HIpvRn>{7brfWhtv*67&GYNIemXIR-eFp{ z`<~kEge^J+>S|K=E<<-h<~X)*2ctV!d{_&bf63&0x$4vpfw`XJZsXJCu}{&I640C2 zr@K49G=(*nMpy0l?2=m2y;XLsl^^<#ld#e%*)`qko4rJPOt(4QHS+H)kABSF{?%Qv zs~2}i{Dr)pyM=n5O*?}w=O3m3|n7d*#vN^WFAh=Dq$(-lAf zJm2THY!!yTc!L=5L#*t7I{!7mcYRv*#fR=^-XNLyzZv#fLfzMUV)TxCx!LADItOwbE!Ky4X`{<59F9XO76(xYE}6)v^dK^szj)D#J0YPw<^2pm5Um}cW_ zJerJLRpB*tD$s-q(crF z3Nl>6J0>#4OZ58^T3T3Nae*3h98M3BA}g866vPiTK$x^uqo{)Fqy9A~k)qI{KnScM z-?Sy?x+dVX=Oz?(T5#5hm47?G3o-YHL51}_0;*K-jo&@2@SXOOBNnD)7xg?DZ+%$* zeeEKu;(fx6o&L2|%%X3)7GLJFa8M_t_8yI$MU`Fe6j&-9Cu;08MNmo)r6FP+{=Q{~ z{J^}|jl*v>0iI<8Wwz*+X}?ktJQ~*NLm%_hueghSKEBeGski4{zee06h^`5&*r=5g zE7F)Z%=pykB_o*QvKH_v9MAIW8?UoB9vu|^H9TCzA@ML0N*K_v4)GnKRO$eR2*UkZl?{h}=E(IrqRboNUIe zC{Z|M=O0C{IC4{g0^owPOcFkgYl5lwBigEB=QFL>iwQu=G}sf-HP#Dn`V_@A{EJuOy|bg z=b3xtB)I?=IQ7FDI@xCybb-Y|0_~(hj{>l}33?cViBQaB`I)-jVzE94_hseb;Rqa% z4kNhzAbkBz{mn=`2IRsuLOdmXJFkPJr3sxvy@O%e)tfa*uTGEjl~}orM3Np?pw3Te zl9Mfs%jd?KV{e2_{7{<3T)gK??N{aZrzyiwB?x3-SDV{QI-MJ#g>wxHZfB&($DR;X z%hv_n$hrVM`oi87msuz7iZbq|&fVi5*WMZIo`a|CO3i+Lm)rddZcb#ID`vK)PdEL{ z2(?n{6dEtueJcA7?fa0@f4W1PVj@Y|E2O^LZ1@_wY{-Tw`s}m-6kw}K^!VDdlUU~; z^H36>{UJtyiL-NJvx}otKqTFJBSff6TliW`AO+6C6}bfah=q-Y$`lg!vXAK<7J4wT zEP77ph|FguXChB5)Ziz9K}$@^L>5?F^0^26AcZwl9Gp;Gc8Bv4kLV$?U-T! zc{ogwVpsaCg{Mrn<|Ppe?Oq{~h9QH!Rmrtk8@F_*E8d$X&cwDJOHNSx^Lw5!RQDy5 zHkQk+138QS6zG(#`vBcrMc3uvPj82RUGC8H!FP6i--eEx)_pSfs^XFI@=PAwJH0kC zfGGdgcS|5`ZDdK0#rRD!=8UU+Qt?tY-ItKf6PLd=u?OR9fRjK*_C4kNP_448>{feb z*ocnACMyQE)#f>~Rl3||%6RqVU|d{G=6=D|CS%#?v9V7WwU?A9Uo;?Ud+>xMzcS0t z8d2`+CCRjW+YLwpzxE~&b z(%xM>;+e3-F6K&$+nebqwM1(X`>7(GWIr6J*E)xeU3^^Z0wrcoymh4b?{vyuy#)NO z69@3s8l2D-I?SIuuj^fJ7p==u2BhRs2m7k#c*DB0%)`*XO)WdG=i48ZkP(kLp1X`S z#qC_FPo0)28cP4_Q`DWgmRCMdY*lRh<0!*Y+H!^&-+WY-V&t{!@hkUy=2^Vsg~&=jT5t8UhwpT9x9lv7g8s(^s(wcj<4Sqao5T0p@! zb0<1Xjq$Z25#Ik4sg4I}_)wk?I5Fcn`2@#T#0XZJh+9Y^cU&&P@aB> z8l8v{n%UNPtw(Hm60Omr{-TGHB8&>qZgnz?0+{y#gx#>uQ(<0v7eVYpMs7DhmW;Ws zC$+uSv*~Wjyw_C4_th4fZuQff?p7Gu%6!y(E8HaxL>??+{8=aXvL00tNH;7$bK^Gq zf$NvNe0U+~Bb70gu-;dzFW9{2?4F=p*qyyTP+Mr0Xtw{CFe`GKIONG!v%*9km=w%} zHoH-th1J|Ewb+U9&GsTpt(J0@S^CLrxAAaLu`%O~%lb}yuTIYNcSR##q5{uli=Ae_ z3x&qwbhccR$QqK)@}uLfKL)s3lJy8a-Ym9)AIBnM#)ySp^22g8zX*U7kD~ELj#K!l-;(N zxoiT%%+t}jFD(OoF|=A%)=~oP zB!bVR?;#Ir%8aa3v7OrX7Z*St6ad?FczN#8h7DVtU-Q)B*`y?Ff(<050s9xB!Avu? z^FM}CdOd7lP8rA?#hDvy`X{gIbj@}5Wgep{c#_`Y`{7HgpO;e8CnxkCjE!Gy-#sIH zmMlxik%7ygr{ec$1-?K{WQ9^F1DQT7ba^$&h*;qW*Yc(8+Vmpm3fT63tV_Cn_qeP0 zMJ6fc9#?3b3_p=37F959V2HX52a`6DH5Ps_xh!T*7{3f969|jG1I;w_rz}ee4Q;bSWclx*D-^xbB)OM#i!={XAFShJD6ed{hU*e3kzEW05^`Pmk_{ zOT-4Pz+_$LR)vatG%3j(9+Z?68R;PU`xfJfp)SJdHZ+N6h67PoM}+M63RytlSV9!; zoww^02^mnp9XgDv!j%gQH(6{%NGg(tTk8)(W}$#}k)jgoF%hdo3x)^Ng15#glCvuZ zOH4|2X#_W6$m46wWUV>5F;4JSR?QB2Bbx6mazt^-^t;whR#r%sV zYd%Ih@zg$+=jl(+Ym4&qMpeM>1yRPH&+sv2weueFAhXP$=w!BealHJH`1sO-OX+Ql z4H~EZI_pF1nlMFoZOEaWiReNvng)}TR^g6+|A>0I9I@zU7!oH9-&p(X=PcFutSm4O zYHLcM8oycq?8M=FNj$CS#zUw37JM+l*Q6@@66~4;-;ACVtG3*Erh6{`P)xVj%^b9v zNoG6Xto$u^I{JrFq#7TgWj1OhK}LshahXnFz2#LGmOi+sm>jeEkA;NTayBMM?~B*3;ZPcp-R z8ed$yKnWxs8@F)&r`Vb(zkes?e)@X%0pX|#2A`iC%xe~K{aXD{XCMf<gI<2xWgpIL5dpMw5*=Z_I#M8^g6PqnD#8`GE70@sP5*%N^Dv zZASjgokocPbNP+YpPjM2Qcf7V3MrKx=9 zBPUeq36T7<(ONv^t9|?@jt`Fa1N1Kc7~U+^0Tshz>6q4?>`&c8!7zCt zq)D5cwi`&6IK1Ng=_8-7qK+L@x^uu7mE!PCX;0G1UIQ<4_BXMSYa)qF(>-i`dLn~Mldo6q<#lnggvTv{KXtd)im9N=JHM1_40}t0-STAMlHP8%xB2yJuJo_?OFH*Qje5f? zJ@WLu50^L&%KiReOE2s>kO)1JR&H4P6k_m9VmcEx9X+MW$BsR1>A&*zBH=%qoOY&P zWQelawO^I&^dx*fEgx`!a>nkGgc01uzd4Ha*JCAHn(P)mAFx&p1gxUms^Z@+>A0ge zmq_3ni!|dK>ktO%-IB>!&D8iFlvI#4Z+!`+8b9LN*IfNoM==fvrK@rK>zRuniyM{? zYFwZPcOQe%+8O_+F+>D)yZo=w6S9G7WsOZZWs-_4EInG|c>-vzfwGx1Qp3do??@-g z`s<3esOZbnmV?=+Qm+C3l7V&+m|sGAGNTFEtB_+I-!ZOHa^rdCjmh?Jms@8a^X{m* zXq49{o2YZ#n$iLaUp5PsTe*Oy3a6pRQ1WS9KuU%By z(EBxZX9bnF3qtJFhb*i97=6f)7DDZsxv{Gw5WUg$rSvJc^XY`zANqowu34Wx-%^-<+Q zFI~h1gilV0RwtCTyV_l(pl8AYWZvGPsWedb;i8c1NG28{ykJsU45qLTOr(HWB3UrC zENV{3$jc2TJfhcF1c71vFH@Pyh$6P8}x2GdCrI^Z~1uF4V3^vbp z6e8sC4o#LLa-hfd8JnkX#`vb|yCswUKy+=@P}V^LEvc}v50RVx@1Pj@3$iZ{?LVHMmc;9Pu0!~i-` z;SyUo3Fz3&=+pzS4%R1PZ>8$8+I|dP5;O5e>q_&)?b456>kjxoL@?PS!a8@0w?jEB zT$uK0WdKq9dFXGou^oKkNo@*^DOcj5S42opiqUdcBFQ2P4(unZvT+aYLpw|b9jYG@ zZx17%J^dr7#>asW8q*dhym#>u3TH3d$@*rio3IkT?_MaO_A3O0Lj-s(Sskw8DBU!` zmSN@um@>4TL2+Q;Bi``imWS($@abh~*0NJ`yOP+fp#3_y-ka%22w#(+$4n+Q7poGq zd5$C(Uw8T`8=1lv;i*biF}#M;85^-Z%&pN6|H>Dcnr8X-)!0B~OC>Dv$J5Il+@|UF z3Jwi8DJ{{nupahTB{c&D!Y*>%Kn=FG=2sDSyL>bnqUNmSGYtI@ZTwSEAv$`vH3cXm z(v(((1uH3y#U7?i4O%hh4>oKV|I?sSac7uaSJ5Z&8@57G^+(9S5qpt@uKTVC(zUviK zYl{%pLE|j;YbytfiO00ZO8M-UCtI;SS%2Fs>KZ!P;x&72<(vk8EJizUs@$7T(s*l|EO?_ksXE{MwtbUVR}!(1e?yY2kVGH#1B(wI4mp zh*-o#6&8^0WtCmOjOCtmE`DI$NZ!mTU8U!`!R;F}TARV+iQY`@PN%CFKo2B8yk?wc z2GwzSgSOI=)zYU%mo-F_6bcH(rn{}}r9YG&iphb-!PcM8;pu}hY%LwvUjfsmp#SM) zw`9~Ak$FU-*Iuf@X%wlcKTgKkip7=Rz_l&D{)$9<{*kdxJ5Di!u3}wVDb9uMlX}_c zC1Sw$paN6dt$aZft9% z&aslr;>_cjNm(_QO|zPPdfmM#@~kcW-jk1vMf8@Dlzb_XsmWV;Pd7EY^C~rOX!F1} zqO;G@fa(DpL4$&}(&@(s)_;Z9N)gQf2FwK?lvGh2=@{VOpb@lO5QrE92cC8bRqxkT z4e^oAO=7;@zr$$rk4Pr|H)+#g&S+Kj=?PF$=RQg;i^eTAbxw(4pPuPKi3RPE&bO_C z_R>lLYYy7N9zX&Nn)@v~Mq|7fBtrP(voRpB&*h2>{nYW}^@{kZ_1zP$+3bPRj6e}j zz*})ke0IGwyWB4ZoUEUX>3gnp1K;dC9Tq^WpjiKW-0aB)1Mth95c{&n_s+CEHOTvn z_}#OmG|NqL`%uDy5ExwfLd}pWt#)YW>sEW|^dfh}UjpCCENI~Ab=TpmGES2KZ=L7( zmnB=%3g#3@CKH|WeT%oe{(2&2anLuf502Z%mnJ{pySv~b1D0POl;qPir;A}5Phz%} zE=?FxhoO3}_b%xek+@Gnvb|A5{_)DTHR-Z*vTe1M<~-pH@k^+YFKV_4uC(pkm>e-L z!3itAQV+Hk3oCw3_&4CylM<5#dHi*=2@tsga} z^bG--HAtW-w_!=rV`|qD3z>aKU+;B2Gv!sqfC_bNJ#W~bKKA?1<7H{GR~iHA+iL^N zu#N_jw}vW){(@(!Zl}qigoHJxUXtW$1>-;iS1*}G8#bSu+2TR}&JdiyP5XyoL+v+A z7e3Ed!A;ASP=Gw=PnslrgIqoD=Y{=JfC*N8l@!qD)ci*Kr`%@1%h2U1dGPd~34cYc zqmQkM26ac(ypG#=qjg!I*+ZM_Ji~ukuyC#{iDmoU2@9Ok+CTk!)!9!hRS%_V1oC426h~JU0+UeP=Xc%~^Xyez8|<8ww|R>$if;Mn|pu44UoStL+2ag^_n2(bTb% zMsI<{!iQU}G4A|@Lc~Pk=C`Npzve-fm`941lk({;^G|;qEweAdKqcXeQr9v;6SR5fo_o$7$Y6%$=0~Tb z-{Iz^a@n0yz7wL2y$QV|44bX6IhVX9Rz8XOh`l&EH>`3J30Nh$(>{V`c~KH;4ygfi z1w_&TwkS)O3vpKU=Fj>Tf^62#-R?c;1>4jvK_Ql=K5Y!I1fg?HRuR=r%f4AUN zOR*VWUWg@qBTNcUU0-?VkXBk+lwcL9-_cx0rsOMDIJB2ZemW$%VnOcxfO7j z<^SIfPAiWPrYD@_Ka|^vl4^|$D%S_KVZ@>66RamC_TeQ*yjrIt{K_Y0}?SRSm2ZvsN5=k6m5pp~CH#gbmt^a@Ljr7ND$^SWT7*1y1XTN}F zE@Y|});WvCByKv3W%tiFji>d)`P<;WzyDyIU(e60&F=0sX5VT|Is3|p?EWs=wVc0o zc8l){7by6~*hBe*`Dp?r^GkV@PJX6fA{I2jmxCxOoYIPg^ohXA`6(OX8-`=y-SLmu z-eYr#!e#o|X785a1|($g-7Wkwhesr~`L^uixB2YD!Y@+`_s2^Zd^@$!sqIDo?6sjk z4vV)mRrf<_DJh%SXpVA-pS>yTC+1#rs(yAB1>>118EMF`^{Dy{##5HDlXpwve%GRY z_sF`cv@IW-jef4kgc97kbtKX+qv20&JOgJKgLk&puCGI3AVYJdRt~-2jjPHK^bP4u z8eJWZZMWq+Pd#vetlQ7~W?7R_twf7d-yG4QdZeaadhx}4Ea_j#8UaM*y)h-i71c*PwS0%8hm$(r00tqRSd%$18nEl6Wd#<=0`YBh5UgnzvE2Xt)m z1bo%P{zC4a3PHKOu8ckpX{gSwnmBj*?apUeD%l_LIdMSE@hpuV@vAAtsr-HSh63(o zhAz*I!ZVJF+mA_aF19RNSI}oITQ%9eL^`s4&$w(WRTS?yjno~de1EJKtNcIv4IQ@H z0l4mi{l@d@Kb-O934b`n4c66FV3$TI4(_YzUs|I>PgtV$xTRV7f(`N#EEgu3AMf;e&yH=Z6-jjA=B3L-TLU+P~kxy0MfwSEnVawze0! zDvMyvceA=l9&ceP-2*UY&9MBdE13qP(`Y>g$D-=Xysxht4x|_E?_o!1%~X{z983xN z3-(SbIK0p0>V~M5eSG`mqBBbd#3c$LbL2bJS;xKpb2B%ug(OUsuF~1D6MiN3&gzq z4f2T8?rGQ;TNUm$l--~I8g)E1NQrbD0~MG@3Dp@PK!iTF9;@DbA2sKjmrMCoFHKmY zj@KF5K=(e%`2-PSA0Q0(_LPNIlerodao}dVI&VhvHH<1jxhz9VU6v*A)tra1CF2yG zv&CO@!JuVDkdUa86{Cyxt{LkqnkEYZw&<$)@-o^G@R?tAR;*;L@w~5jv_+YSg1ECq z&4|e3)h>F2!fkJxAm1H1%ED6;%0g@DsPU^iw&M$-8n_@Br^l*0Tgbrtq)b-6N&Db& zl-3N!s&k7siz%&y9=5hWruFy7?-~7_zoUA7c=Y-Mip7V=x*t%V#`)8M{BcKO7s*&x zZEQc@tg{vDiNS@vGE&L?_=JTM!2@~9((`b7;utS}0B!AfMXG>TQYDqj66b<+MNiYAdb?wdZnyjS_jr$4$Nd2KWt6`>5GP zA@OPJ1ofHI4w2(2+qh!4d(Dq#s;X}Er`sKB`P?GF*6RJyMt}a?kNgS-vmt+T4pwA+ zEJ3|?yqL$|o@~1Q{h4#2f7H08{(b$)-fHAYz>G`d*d6}WOu0Y^(;cvN{LXhLxMRZi z#@Ojl&IK;$Q<|mpZ&?P80j|r z=_&o6r2136^C$b`kHzuG-xC(YjCDv;b_ z!``$ffKtfF#`Nzdxy6f-zA{Dn*@-7?xhh-XOyKvadB5}*Kp)3Ir6~f4lr<7$fp*04 z_;wUYrUSNGfgqwMgQRCUMG1nVze?(}i4k#ZONrd4fpZ?~-F|{#et=-kJOgJD7t7@m zG!5J&4Ch=OG3@FK4TQR)uvhVr7p5i|c|p`VYqem;Jp*hWCxhQ873#!JZ_qiIVSp%v6+C9@*j zm8xDJ2dWY%$hQ3$x~pLk^%T673!kI3Bv0)Vqc*Qywg4h1=HKpU9|}SXI03o zNSrG~Xzto!_guz$$BVIS$*iOTNrl~-BHD4Cn7x~pU7Wd-?%*2IkXAJ2ou$}E1xL7w z&o9eZ^kv8Io?ay)y2h( z0pii>T3R7o`;y_y7hTFTn4hNU+!Y|*4c_u~@>?y1Yu;OZ(`Prch3lTuM+LbSFKMu- z-p)b=6?E*sbb$UTT^jKtIHH1RHSD7WhwNAov~OyPDX9u^BeZDDLR$nZY~)u+pMsC_ z;DH{|H3bpV2iLEvmC%Ra#z9?F@4kTyfK$kq*rUN~25C(eAt-$R5lDA1urD|h?CG~d zoHdi$0rE>1j=+ zqV&fAt3lBz8 zgFY|oK7ZrV8#M118>Hz&V0~JD1<`M4pr3Hm2iW{X9S{ytW>Ilwt`)?O5{6 zdzZag%JQ1SAvFUX-twA8tDe3eHpI7TNxld-(ZfdxM9ausO{R+#_kEAajoP=}ws5yI zNJHt2>^Y()^=8QmiV61w?0L#flXHG|!r$L2L8c?svNXa!r}XriSe&g)S*My^C-U^a z-y4~K^BqM(9Tk5B<#9eL-3TfwKMDyyD(+7d8lnKSowmn@{=VL}sIN3Tx1$&&;1hb= zX$3#yt=#A=I^3CGjUAX4*ng2>5#cwRHMO7?gTq;&LW3VrtL&`WSc)P5-1ZS44!h|)TLqv3p$p=dB7hV_t+b zAb&AK%sub$Ea#ofAyw;o#;PR6ax;$g5mmi^S0KzYlIiY*^22%wMT+jkA`zkTZ=c5H zCvGU7uwW1~M?`cfe`NZV@JCUeIHF5+g%KP-qV@KAQ&PSCQLn=F3m0$y@o$;y)k2n_ zoHKZb*Y}sIi$3W>)r5{cJeHvG z`e{5sCn80YhG3boSYDvqqien{xG6CtQauW8Hnz+X7hgNMtZD|D65X~4799CNQ#Jmj zGrq&gfJz^m>hryrWh6;}_VX&;hFc!Au`C70i!Gz=Nf-NyQTbS=I7B%)TNal_zKPG7 z?)*ZP+m->8G43z@t|p}d0fS!ZS@ztgy)+p{x-#oezk`?+Qf+TY(1>S#^oIt)7mS_5 zUqo6Y1Oy{F18@#x@P%b?(c%c4WeD-wu$0^IxZ7}SWYCCzqGCD(giO^Akt)usMA?7- zxMd2DudUl-fc&XYb+CIT^v^SP^nT|mfNsW#ET&O7K#$C4H5?~ zYTj23oOGN8gYBXN#RP3!uhfT49E*?VTJ267L+Cn6p(gfzeE>94Bvst`g5e0@u)RKrOD#M3(MO-!N)B75$$4zHc&yo3qPs_ z@pn6z+{3w4@1GxESNGW*pdgQS>^A;x;kj<|J@l5!<4HCqtC4Q9wYl_R`$)PAx zlde#-=Q1edDw+Gfs54o{KS3)|%>NvwBu-en@MQs9oUC7x6)X5=CsSm*;dMmnUpZM#MH$LQ zMpdCgif<(>Va~}_-NOyR3Z~)P(Ob;fpg5-Y7f7usm9Uvi-_>Yr(OzSrQrMl_VKDSg#~(^h`d`l!;QSGvvq5F} zp*ZO{KF+@BK?6oVQw(R~E%Ds3;}ou#keBvdt9YUFd;YUDXO>>cSJqfbbL)Z8DO?HS zp0#!^Tn0$5)DlNeN$Hav&(o0+YL(7W+TB;m0R^Gz{WVg*RP}zD2i+ZcXgh}3kH=&^ zMH`zuRQWq|uHLUAtsw5no0lNY@BfBiSMv)hXG015mo&3pXyU%!p&_gKWBQ^1jL!j& z#sUa70v=NaV5bJ4zYD+*mqBq|dc-Ru4QmWRDHa^KxTDn4V~&d?Qf%T!-!lA@>Q8># zPAT+3^HZ00FKI6+R=oMkQwusd#v$go5jLHtaj(+_j!4Bl5nRMQqdY+0nIwV`XtH6g zgQpw9mxy@_-zR!$W%I|FQ?1Uieu1>p*eX+ofGvh|61;A-J3!}T9Uy5h0Qfz0?`52n z$kI8WdH)7(HoolMnQM-csFN02!D~4F0O5A;$=*B@6FL@j6bgLl=GsOF0N@$P>vxuS zJrPI~6-vvM@|uClb5lq=(($4s^mJ>hQ6#-_k9Id1w={%~!W44Lv+cZ2XgAUu(*Zi} ztPVTT<43gLo@aRVf$-JZL96?1AUQLBf|(zUuPF=b(RwzgA;SOAAd#QN@lslfayHbG z7r>~^(>plE>E&7LTugD$Xf_8wKwfJ*7A+_n^aEZVf{#~EOmTvrC4@h>5|46v)bk=W zH=$IO?E$kFnMZ5gCTSZiS1fw_96nC@pqvESXCqN)i30m_e1V*|0{1^EyPVE@lbj{S zSxf-^s)V7(<8QSh9kI(FoyE`4P%t;c!^yLSomm$C!9n)pgjCKW=61>eGqc1DfXyyA zkBCzIL(w-7i6+TzCcj z0I69t20A0b81_LvKWYwf(Uh_7O?(SL(E6ocfe>Z*+6zf3d%F=p>&U|V6StAGmo>xe z;mUPcUfo8;Fhr=;uS$0M;^iGfF-B}1*9EMDAmmD>GYDs>J!=)m)=OXgk8Z0`8hz`# zA0tCa*%nu~AwM8F)g{qF1n@eCf?qd7R)1?LzPJy5LOxp%19?T%)$F@x10XY9;`XWi zFoSQ18$6p{#YXx26AmS97Kw;-dAVHHCyHZA&Mcn9s1fm9@*mlL#l2IqVR;%M|B)RW zSFgBHY+m@;Om@mDi%+bxl*`jY<(#J3wKjvoy3xw4V@=6;ab}RlwWXbA5YXIPdAu;^ z8Ch4|+HP>IdRW`y(;WW17?x3|mbpUbrPgn+e4#t0Z(`h|ud8o)OLE=pwf^4D37gsD z+0$at&9aPS@Nd+keLefefU3wRO0)NLi1bD-jX|;zJe5sueRRULOSp+d7HSRkM2mor=`n{HP^5(_qdS<*tHQ&rA`!$1u>8GY8B73RiH zPVpvYA%`0z(OYcon#^&rjAYO&VwZv$3Ei9kA)a412GuH14w!T7ok%T|`!w%tsc1_*5ls7F}YR2*s!If0SrhcoRbE)Pt())rH`P zc}S4U^b6IhYl*^nfj(?4ODnMHl|&(5LmL$iuS3tp3q&;%&l16~#Jw8?A!{Af748Ds zrGhZfb$}DZ>|B43aPFYWu=t!z-(<4nlTVVRxtj^H^a3zAbS=@`cb)DIxn3jLehuU7 zrR}dqhh6C^b?BY*mAU#E{fC&g%rTZqTW>ajJURzvx_l+>#xG6`A9NA#WD+|iiu@!l zHyzJiD9%NSWvL*DvS5yY_5e&vNTc|3?M#l-lCSdv3AXHM)=M?Cup--*E6sqS;_d#? zv#4@%0{TwcV$08&70z3#w;R4Q%|0kj`B#f$*OQ{p)7Vu`#%)gN<4008DJ%$#le^-C5Y}>ZsD~%{2I?)nD??ViN1kqd6s6&*AZuBlBh~AZJd%EuLTkrGy_`d79e_6{~IOlZiZQHkPJIZ1)8_lNH0&%4P zFwdfm_eRzP;a|Mzd`3T$zgy}LoAfk6q^X)9k|UK@_&u9M^Z+8-^Hg4yQS2nf48uwF zJ7{N{eDlcS-RBXX{>A)5-(-x4lLD*+*H(ZzFMtoPeqVYCR91Ue7UETUAd{q(hW?#JSM%)$ z7A04KBbWqJx8dvScJ*paEBZ*XvM=hh^KqQ&cW;lQ1+qccAK$M;Vv54(JpzX^Z*eU< zbH2s+eVzeimoUEa41}<{z;4I&-KDDKTPSWCuJEhZ$Vk1x6t^6+TiU8B(a5mPT{?r`)SVK=9E2_7T@zz2ItApxO8Cuj;Vf$fdz&ukvN#a zm1eR?ja=-4t#qTwS*cky6SmX`M-gSS-Z9P3}DdU)(#nBBmF_G13e_zUq>Rry@PGGPtLq-gl45o^pL5;*(*0{w9-siCDXSuIm2SwaV<+ zcx%{)xC1ZuizWHl!AXv$2~s=?F3 zBEq6pRMyMGx$;MZ(Jxd)(Xiq5k;;_~Aa>;q;J$TJ}rF0UjCJ?gT7%RIEqjI0%*Db5Ts)Jak<-ek!j=td4ihltd5TpQ|9P z_8{S^N!aQ&AMr-;^}L4v=U3i zp==KmYNo7%sLAuwu=JE#&vL~xSmSXu4to0S12m}}(e2u(H>NQqs%?MdwolqNklUwY zqr|nq3Yd7-8_qzK$HN%Cj{l1XRc{j|vDD+VdRDJUSVJz}5Y@i&qRMw5ju&-AP<^l{ zng_R^Jo&eZFhPG`hS`Em^pFbl!51)WSQ6gKiV|2kx{* zj=%ISiPWtPGwl^|#Oi?8JVbvp-0-eVm=Klt2s`$U{x@@xuN(hMa_FUWcs>uENpcvM z($r|n=ZwJiz(Mld-OsR0uHJXl=D975y)Pf4m0H2YT$J8;B!kTD=w2hY&%(^=l&jW2 zwZ4Yvq7n0E6cmn$69=FG2G&u>WA`EOdJiQus|ZF4X$0<+_lE_sn;`spRVi1QlkaQ2 zlm2^wiPR%NPkT-QHR#WxWK}&(t;ge~#{$s#2qkSEc{IRLY8I|R!@GwT!3-ss4+64tIgBDo4hQrV7U2@giUsza&h;b zIGqoI5GG!P(0bs4^I8?Bl`1+^Y?NO?#vPRQcs~&8-03S@W})VsaXx~V)lnFU z8rTnBk51KVQ#a4@=|Q9pHPQK(>Kv-$@0bYWV`ONVb7UmJfs*yH!K~oF#$K9t#|n zF7FHH2{+Q;E{O!$^4-mMG2t-uI?{VOsP#OKtM~I${?4pkbsFdxfV0@X1xC!;Pz3^s z_Meq+#G~m{pFmfEu&ynFU2CLnTy0=<;*VTvKZj`MgI;w1{LvX7A+Ai> zUbU~Eg#{~!66Tel?WooN3ruME)_E!F`CUMOPOzNz`o8)$<=yom{pY~e?PNQ-0PkV; z;%RZqfU|^Rdhwrv0WvzNr&^;7(YfLq@*tIJhG;Cs$8wb#eTbaK>Zo#prq8dee2&y_ zPofXm-TZ5H(CP;s1uSAQhLN>@2VX~5v|b?wDjm4%_XYCkUp*yzDb^tyZ9#;MkRA>;(uWR^}>H|x0h;6ucgZs+y7yK>hU z>)+Ic! zfm71>X*F2&VB-v3Z*hFTG@|mL#Xz*!m4bd1gEp+sX!s;#C)}JOqk6CsnpJPkah=bu zvRBpp!bm!0#3#0z9^4_y9JyCUP0eh^Gd|QMvN@}jej+xsk8RN6l&{dBV@4P`$FW%y zNo{MB#GkJ%VG&S%?Rj)EMn~uI`nD6AFDk$X_vkbM0lvaneR(ml^-}fMJwyg4sk^v6 zFFy3F9@YCQL3AEx#S?U?sH!aR9#`w5U$AUCl`@kyW<~UCxjp00TC13M54hxt%2w^h z+oBkL1`*7$kMuJ9#Ok0|U$bKSNwD?${hLyTpSZKH-nU0S{YmgNj)ar{$leKx8a!-87h&LDN<_kX@FvnqQ50WbU&GCe;><@Zw|Be}zTs~5H<9XG~Rojv)ig~`z9GFqD z`zR2`a8uJFAnq20^mcoctF{m9e)%Y%Roi_~QijPKWJn^sp*BBA7?&ef2tNPMqxix~ zNvl90yQP`852FU;Wv_tf6L^(G_vfI%_PsK!+)|%Zs$*l^_!|+>D6PfHHI@bP&+no- zzQB!KYjr}#T>{XBG^0~R1Ghxrx%?1V!k`1(6fLOItfRpd1o3;;>fWg6ki(8KQh6f# zGaS}Z)}iZ^RV|0%ja=;EjeM`QsMelNX^il@!Tt|k)H|1PSlT#w+fhb5x^t%9Pkrpc zUj_oab$Q}myfBkS+;Ou@Be8H?t!)sm=BaRQRa>9+DSE%MQ!txI+>w9jUR-8c5uC3e zq1R6+jx?HqnMCA@CucRh1lk~Bjez{#E|=Ox!rc*wIJp*ON!pe=<;jv8tI}v(O7CGO zAz#ldc1n`V;;1&|>ikE5iRAoqiOgaSPiUKh>| z0SZ$kQQ1-m#TcE8UXr*33cs?i_Z^XGZpWX>6r~lQ>v}j25o-a)bB*HhlE0GWVGjoC zaFMF7ivV=puZIN{RlEfqr&`^JGHl6myJ`+O#57&kyA91`BAYW)3F4=lrXQihX_Zhh zzrhk$@LP!Z7dW-L72LlI5Rk|e5g#vScWmr)g-BnwozD19W9d*C9uy%OZ}c@G0Wdx67_d&T5S%Bm6)Z)ux4VS|R`{o;&JN!0ahkjyk2{3#pu=^T z{spvg=Y0QyVh0sqdCD#Vp6wMl9JJQ?YqXLH)il%?IP=zG>w3#~p~J!g?!R5Pj{kmZ z@~Y(!z%=O0BWc-yn|9=x397?kbP0l}$^M4;oz!517DA2ZDoV$fn?mLhheHuNC@bE4 zyW`nA=$C4zUWup~A#QbSTAI8b8axFPgdiip)qKASDPO7{I+L6=sP3e8mT?qw=Bp+I z)^EKwP^MvY-_tolx!)m|N-s?b+=${#HzHV;X_Y^7> z-{v-6iVPnK7}kl`Y)=Yl@^()6B~f)GC&9{M;0kI5@h|ukxhy_t1eeZyh9o&9BF)Yp4K}{3N+D@}-_(|8?^Dy!= zjGQ}_!~JgpYc$x@z7<>`q5pS~+MJC``x~T>C#J?M;9IPoBN~k#J0T4GflSUG9AaB1wxii8 z{?!BbZ&**hJ-91}E%FT;%lJOCF&4S;gBMTl;{5TySM%4^X-FVft|{wiZgNsBkMDLz zU^hAQ7O=eOU>&Z`)J&0CJj&2a@rQA@fVbrk$&}?<0=F#S*NBM=wF29)E2ZwcK-3>6|UTmtmI|qop&yz*b+ac$A*Pj&Jx)5I4cvqt_gq zx3H}o!rmxDgD~>8Ku}Yt`}mFS60AI6xC2e=Pgy@gexgD}i*7Ybcc#P;b)z21cq>L| zoQ;TUAD`R*$w?IqIeA5!lS0cusq$!@&t z*Y&#eQuVdt3Jm|%m|{D^z{i!~K81QJ7+&urr{1*?x%0bH{uTDy7<3uAEXmki4<6;6*PBRC){&&lE{g)(| z&UP+U!2>Q?<(nHmacOO9&vlaehDv`EZZ-w|
K5;9B?CFHeF9?Of)>gI7+|M^zxP8%a2{8?_} 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

3fj$HA1$n`@ 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

55>BsB$-fyVWB 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⪻ex`gOR4ko2Qeih=xSgG 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 b;A(AT=@o$tq&79^x{W(K2}ig8JALmzS38df diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/store_sales/metadata/snap-5829085943537843612-1-9299e356-57c6-45af-9c74-5b984ffcbb80.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/store_sales/metadata/snap-5829085943537843612-1-9299e356-57c6-45af-9c74-5b984ffcbb80.avro deleted file mode 100644 index 9abb9f8cffd9c2e39dc64e9bed7a3d6db9f29411..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3818 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 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/time_dim/metadata/snap-4765156773063077910-1-c1b011b3-8b57-4dff-875c-4dd6db7ad6b4.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/time_dim/metadata/snap-4765156773063077910-1-c1b011b3-8b57-4dff-875c-4dd6db7ad6b4.avro deleted file mode 100644 index cd62c8044832cfdee5b5e82fbc3d0dfe9326445f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3786 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 L`Qe${sr>!}dh!cT diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_page/metadata/00001-cbf8131d-a417-43be-b61e-766768459e3f.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_page/metadata/00001-cbf8131d-a417-43be-b61e-766768459e3f.metadata.json deleted file mode 100644 index cffb3d64f16f..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_page/metadata/00001-cbf8131d-a417-43be-b61e-766768459e3f.metadata.json +++ /dev/null @@ -1,221 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "78b2c7cb-843b-44b5-b83e-5c46d364bdb5", - "location" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/web_page", - "last-updated-ms" : 1663713412091, - "last-column-id" : 14, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "wp_web_page_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "wp_web_page_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "wp_rec_start_date", - "required" : false, - "type" : "date" - }, { - "id" : 4, - "name" : "wp_rec_end_date", - "required" : false, - "type" : "date" - }, { - "id" : 5, - "name" : "wp_creation_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "wp_access_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "wp_autogen_flag", - "required" : false, - "type" : "string" - }, { - "id" : 8, - "name" : "wp_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 9, - "name" : "wp_url", - "required" : false, - "type" : "string" - }, { - "id" : 10, - "name" : "wp_type", - "required" : false, - "type" : "string" - }, { - "id" : 11, - "name" : "wp_char_count", - "required" : false, - "type" : "int" - }, { - "id" : 12, - "name" : "wp_link_count", - "required" : false, - "type" : "int" - }, { - "id" : 13, - "name" : "wp_image_count", - "required" : false, - "type" : "int" - }, { - "id" : 14, - "name" : "wp_max_ad_count", - "required" : false, - "type" : "int" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "wp_web_page_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "wp_web_page_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "wp_rec_start_date", - "required" : false, - "type" : "date" - }, { - "id" : 4, - "name" : "wp_rec_end_date", - "required" : false, - "type" : "date" - }, { - "id" : 5, - "name" : "wp_creation_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "wp_access_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "wp_autogen_flag", - "required" : false, - "type" : "string" - }, { - "id" : 8, - "name" : "wp_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 9, - "name" : "wp_url", - "required" : false, - "type" : "string" - }, { - "id" : 10, - "name" : "wp_type", - "required" : false, - "type" : "string" - }, { - "id" : 11, - "name" : "wp_char_count", - "required" : false, - "type" : "int" - }, { - "id" : 12, - "name" : "wp_link_count", - "required" : false, - "type" : "int" - }, { - "id" : 13, - "name" : "wp_image_count", - "required" : false, - "type" : "int" - }, { - "id" : 14, - "name" : "wp_max_ad_count", - "required" : false, - "type" : "int" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.7.ndv" : "2", - "trino.stats.ndv.2.ndv" : "1497", - "trino.stats.ndv.4.ndv" : "3", - "trino.stats.ndv.5.ndv" : "199", - "trino.stats.ndv.9.ndv" : "1", - "trino.stats.ndv.14.ndv" : "5", - "trino.stats.ndv.12.ndv" : "24", - "trino.stats.ndv.10.ndv" : "7", - "trino.stats.ndv.8.ndv" : "699", - "trino.stats.ndv.1.ndv" : "3006", - "trino.stats.ndv.3.ndv" : "4", - "trino.stats.ndv.6.ndv" : "101", - "write.format.default" : "PARQUET", - "trino.stats.ndv.13.ndv" : "7", - "trino.stats.ndv.11.ndv" : "1883" - }, - "current-snapshot-id" : 8492339180313289358, - "refs" : { - "main" : { - "snapshot-id" : 8492339180313289358, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 8492339180313289358, - "timestamp-ms" : 1648081274568, - "summary" : { - "operation" : "append", - "added-data-files" : "5", - "added-records" : "3000", - "added-files-size" : "46135", - "changed-partition-count" : "1", - "total-records" : "3000", - "total-files-size" : "46135", - "total-data-files" : "5", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/web_page/metadata/snap-8492339180313289358-1-1839905a-92c2-41a3-ae7b-25d23273dccf.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1648081274568, - "snapshot-id" : 8492339180313289358 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1648081274568, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/web_page/metadata/00000-4b263598-80b3-4573-b1c7-6c8063fe7726.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_page/metadata/1839905a-92c2-41a3-ae7b-25d23273dccf-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_page/metadata/1839905a-92c2-41a3-ae7b-25d23273dccf-m0.avro deleted file mode 100644 index 565b7ca07b2e1f28ad90e7e8a28939af114fa1bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7544 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 RrI*Kl-FkK5)<|!PcLDwz4+sDN diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_returns/metadata/00001-48e3f75b-93ef-493e-a23c-2316f1ab787d.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_returns/metadata/00001-48e3f75b-93ef-493e-a23c-2316f1ab787d.metadata.json deleted file mode 100644 index 4d46c022e154..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_returns/metadata/00001-48e3f75b-93ef-493e-a23c-2316f1ab787d.metadata.json +++ /dev/null @@ -1,341 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "be6e55dd-9558-4d93-8bcb-c43fd91e1f24", - "location" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/web_returns", - "last-updated-ms" : 1663713489606, - "last-column-id" : 24, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "wr_returned_time_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "wr_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "wr_refunded_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "wr_refunded_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "wr_refunded_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "wr_refunded_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "wr_returning_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 8, - "name" : "wr_returning_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 9, - "name" : "wr_returning_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 10, - "name" : "wr_returning_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 11, - "name" : "wr_web_page_sk", - "required" : false, - "type" : "long" - }, { - "id" : 12, - "name" : "wr_reason_sk", - "required" : false, - "type" : "long" - }, { - "id" : 13, - "name" : "wr_order_number", - "required" : false, - "type" : "long" - }, { - "id" : 14, - "name" : "wr_return_quantity", - "required" : false, - "type" : "int" - }, { - "id" : 15, - "name" : "wr_return_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 16, - "name" : "wr_return_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 17, - "name" : "wr_return_amt_inc_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 18, - "name" : "wr_fee", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 19, - "name" : "wr_return_ship_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 20, - "name" : "wr_refunded_cash", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 21, - "name" : "wr_reversed_charge", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 22, - "name" : "wr_account_credit", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 23, - "name" : "wr_net_loss", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 24, - "name" : "wr_returned_date_sk", - "required" : false, - "type" : "long" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "wr_returned_time_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "wr_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "wr_refunded_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "wr_refunded_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "wr_refunded_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "wr_refunded_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "wr_returning_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 8, - "name" : "wr_returning_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 9, - "name" : "wr_returning_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 10, - "name" : "wr_returning_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 11, - "name" : "wr_web_page_sk", - "required" : false, - "type" : "long" - }, { - "id" : 12, - "name" : "wr_reason_sk", - "required" : false, - "type" : "long" - }, { - "id" : 13, - "name" : "wr_order_number", - "required" : false, - "type" : "long" - }, { - "id" : 14, - "name" : "wr_return_quantity", - "required" : false, - "type" : "int" - }, { - "id" : 15, - "name" : "wr_return_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 16, - "name" : "wr_return_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 17, - "name" : "wr_return_amt_inc_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 18, - "name" : "wr_fee", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 19, - "name" : "wr_return_ship_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 20, - "name" : "wr_refunded_cash", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 21, - "name" : "wr_reversed_charge", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 22, - "name" : "wr_account_credit", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 23, - "name" : "wr_net_loss", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 24, - "name" : "wr_returned_date_sk", - "required" : false, - "type" : "long" - } ] - } ], - "partition-spec" : [ { - "name" : "wr_returned_date_sk", - "transform" : "identity", - "source-id" : 24, - "field-id" : 1000 - } ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ { - "name" : "wr_returned_date_sk", - "transform" : "identity", - "source-id" : 24, - "field-id" : 1000 - } ] - } ], - "last-partition-id" : 1000, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.22.ndv" : "664097", - "trino.stats.ndv.2.ndv" : "297612", - "trino.stats.ndv.7.ndv" : "12212431", - "trino.stats.ndv.19.ndv" : "442621", - "trino.stats.ndv.18.ndv" : "10141", - "trino.stats.ndv.14.ndv" : "100", - "trino.stats.ndv.23.ndv" : "801756", - "trino.stats.ndv.8.ndv" : "1890006", - "trino.stats.ndv.21.ndv" : "671800", - "trino.stats.ndv.3.ndv" : "12209422", - "write.format.default" : "PARQUET", - "trino.stats.ndv.17.ndv" : "1350317", - "trino.stats.ndv.13.ndv" : "43539834", - "trino.stats.ndv.16.ndv" : "138447", - "trino.stats.ndv.4.ndv" : "1890006", - "trino.stats.ndv.5.ndv" : "7082", - "trino.stats.ndv.9.ndv" : "7082", - "trino.stats.ndv.12.ndv" : "65", - "trino.stats.ndv.10.ndv" : "5947530", - "trino.stats.ndv.1.ndv" : "89157", - "trino.stats.ndv.15.ndv" : "794159", - "trino.stats.ndv.6.ndv" : "5947530", - "trino.stats.ndv.20.ndv" : "939647", - "trino.stats.ndv.24.ndv" : "2219", - "trino.stats.ndv.11.ndv" : "3006" - }, - "current-snapshot-id" : 4600928459638848296, - "refs" : { - "main" : { - "snapshot-id" : 4600928459638848296, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 4600928459638848296, - "timestamp-ms" : 1648090756920, - "summary" : { - "operation" : "append", - "added-data-files" : "2185", - "added-records" : "71997522", - "added-files-size" : "4266769124", - "changed-partition-count" : "2185", - "total-records" : "71997522", - "total-files-size" : "4266769124", - "total-data-files" : "2185", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/web_returns/metadata/snap-4600928459638848296-1-dc6373bf-8105-40e7-af78-8519b9edebff.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1648090756920, - "snapshot-id" : 4600928459638848296 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1648090756920, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/web_returns/metadata/00000-42b1fa58-ae42-473b-8a26-4872514b27cd.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_returns/metadata/dc6373bf-8105-40e7-af78-8519b9edebff-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_returns/metadata/dc6373bf-8105-40e7-af78-8519b9edebff-m0.avro deleted file mode 100644 index 107866fbe21d698efa76ac53479f0c13f472373b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 761962 zcmb??1z416)HaBSpn!lN(o%wSBds7vBOpi)Fw_u3cM8%V(nu*SAxJkvch?{(-CaZf z1MZ5u-|l|y{+}0m!SKH4dCvXZ=bZbT_f=NM2odA970^)6R6B*xiQL-3T#uZG+{)V0 z253!APyYQk3?N-{9%g!SeUP4st`#|tniKiOCoJ^jU~SWj>+LK7mU`AUmS828*_kS0< zx_Us6skR9X7d;s(?UkWFarV-XwYL4gH}n_01;BR%0D*yk|77qF^wrnX`}bS=3+4i> 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 W-PtC&so0i(uhmT-0hzU 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�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 Lp5K4FrdRClFW#gp;! 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�(WOHGc^RnAQY*lv4O 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¬#LOqcAD*plj{!6RGQCkMS~z2hf{~ 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 z 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


FI=Hz_#M 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 z%0D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
  • +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 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_returns/metadata/snap-4600928459638848296-1-dc6373bf-8105-40e7-af78-8519b9edebff.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_returns/metadata/snap-4600928459638848296-1-dc6373bf-8105-40e7-af78-8519b9edebff.avro deleted file mode 100644 index 6a40d662e0ee39714345a0fdc4a77fc2f2d8eb32..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3815 zcmbW3OKclO7{`glBZ#!+5tTMXV!ARFm6FzWY{zyKXq7Z56>3qFo}yrlcgOKo>s@Dd zHo;UrkqQ+q6cwolR0)tca6`0}ij)d0)I-Gq!hx!y^a2t>Tp;2N5;Oalne`*AbMs^7 z`+eWc_y7KLqI9}%U^iUB)INGsYVtl1Td$YB>0(=iMh*v)(~IF>no zDT9y-B*2Y8KOzvEJWB?Zp<@uGz+(7!;1@O4M%N&L?rqsc)pFcoyer`;unKtwM_9@L znFQBCuzm|(TH>LXbeqsiF7}8#>hFtA%}7)Y8HmtR?h!ieZHvy#N=)7^tf#`K$&~{! zhIRTk?T{)J2>-oRQ8<&x(PiyIQZ17}-L#8-r}#oDS(bsI$=bkG(mZx(it4@S zpba2kn*h%ftU$wQ?bN(fk$}0O!!;=ZlXposC^S1TySGm=C#7Ims|XiMv`2;dc#nDl z=KL1|y#jX^aCd>Sg#n~xGwHLN*#az26{l9)2H4%zJ2=@`Y>X(h`KnZ7zG!m|Uf7&d ztwJh2Rtkb@MI7gr^(4lngY>2jwpP0;K0I_h$+Ptl6rYsm8&`aE7#Dj~o;=Y4tw+ZN zGLK42)9V>@tx&Zzr0%3GWI%#;2Q!@B*#2}jk;tcV=}bO5mdmAcseE>r-d7cp(G`Sk z>LRh^u&$yK!b`rRULNB5r$*?g%u+nBbz2}2(!cL}?Vjs9Dn`qWjuCZ78VUsiNa?|0 zS+jVQWIKg>YZp#5uN8(tD z06SPuH>zA$yVVLrh9kJfwn^cFlRaI4vK$nMYnz-vX`M0S9K)M{P3SJi#KmIJ7X@9` zGA{WKlC5%CNk;0mc%Gw$$$4)tK@SNjaJ%g|EI-&}t^?NfRzVFnWK?Vij}=rpN&J_3 z*iO4bb(7i+^A`6p5ye*pObm~(h4u{V?xi8O6mi*oQ5*G=-oi6Qu3`#xutyO%d?le7 z(Pon(xR^j(ts#qw6rT%K@+yH4Gbhk#?}R|2GGMV0r;226kzj06N9t#<$9o2c7Y=>< z;@@lE&t|@U_tJ@1W4B^Yy*|0KnHYR!YUce*edm5YKY!otl)Yo^m(5uJl?(B+kGzBX zF7C}gJhgYv`91l2M{m9P?5^L=eOzf?+-%<5DC~OFzVi9n;=~uthpwJ{`pbi#JbrAV zw*R}gr}q7Hb^7kcN0T?N9WCDe=EH&YKki%}dTu$sBfjs+Pv1}nnx8!we=qUYfzuam z9KP{DX=(h|<(VJ;wXZ+(!S9=g4^18$`s(;&N6t1jUc3L!KX?1r*MHO}{*1Mz{~z0s B81(=E diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_sales/metadata/00001-c5b98ee0-296d-4767-99d5-2dbc01e759a4.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_sales/metadata/00001-c5b98ee0-296d-4767-99d5-2dbc01e759a4.metadata.json deleted file mode 100644 index 4aaa262306d9..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_sales/metadata/00001-c5b98ee0-296d-4767-99d5-2dbc01e759a4.metadata.json +++ /dev/null @@ -1,451 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "c80b0742-c89c-445c-ac17-eefa21057435", - "location" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/web_sales", - "last-updated-ms" : 1663713570459, - "last-column-id" : 34, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "ws_sold_time_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "ws_ship_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "ws_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "ws_bill_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "ws_bill_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "ws_bill_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "ws_bill_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 8, - "name" : "ws_ship_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 9, - "name" : "ws_ship_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 10, - "name" : "ws_ship_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 11, - "name" : "ws_ship_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 12, - "name" : "ws_web_page_sk", - "required" : false, - "type" : "long" - }, { - "id" : 13, - "name" : "ws_web_site_sk", - "required" : false, - "type" : "long" - }, { - "id" : 14, - "name" : "ws_ship_mode_sk", - "required" : false, - "type" : "long" - }, { - "id" : 15, - "name" : "ws_warehouse_sk", - "required" : false, - "type" : "long" - }, { - "id" : 16, - "name" : "ws_promo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 17, - "name" : "ws_order_number", - "required" : false, - "type" : "long" - }, { - "id" : 18, - "name" : "ws_quantity", - "required" : false, - "type" : "int" - }, { - "id" : 19, - "name" : "ws_wholesale_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 20, - "name" : "ws_list_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 21, - "name" : "ws_sales_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 22, - "name" : "ws_ext_discount_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 23, - "name" : "ws_ext_sales_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 24, - "name" : "ws_ext_wholesale_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 25, - "name" : "ws_ext_list_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 26, - "name" : "ws_ext_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 27, - "name" : "ws_coupon_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 28, - "name" : "ws_ext_ship_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 29, - "name" : "ws_net_paid", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 30, - "name" : "ws_net_paid_inc_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 31, - "name" : "ws_net_paid_inc_ship", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 32, - "name" : "ws_net_paid_inc_ship_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 33, - "name" : "ws_net_profit", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 34, - "name" : "ws_sold_date_sk", - "required" : false, - "type" : "long" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "ws_sold_time_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "ws_ship_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "ws_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "ws_bill_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "ws_bill_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "ws_bill_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "ws_bill_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 8, - "name" : "ws_ship_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 9, - "name" : "ws_ship_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 10, - "name" : "ws_ship_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 11, - "name" : "ws_ship_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 12, - "name" : "ws_web_page_sk", - "required" : false, - "type" : "long" - }, { - "id" : 13, - "name" : "ws_web_site_sk", - "required" : false, - "type" : "long" - }, { - "id" : 14, - "name" : "ws_ship_mode_sk", - "required" : false, - "type" : "long" - }, { - "id" : 15, - "name" : "ws_warehouse_sk", - "required" : false, - "type" : "long" - }, { - "id" : 16, - "name" : "ws_promo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 17, - "name" : "ws_order_number", - "required" : false, - "type" : "long" - }, { - "id" : 18, - "name" : "ws_quantity", - "required" : false, - "type" : "int" - }, { - "id" : 19, - "name" : "ws_wholesale_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 20, - "name" : "ws_list_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 21, - "name" : "ws_sales_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 22, - "name" : "ws_ext_discount_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 23, - "name" : "ws_ext_sales_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 24, - "name" : "ws_ext_wholesale_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 25, - "name" : "ws_ext_list_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 26, - "name" : "ws_ext_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 27, - "name" : "ws_coupon_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 28, - "name" : "ws_ext_ship_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 29, - "name" : "ws_net_paid", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 30, - "name" : "ws_net_paid_inc_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 31, - "name" : "ws_net_paid_inc_ship", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 32, - "name" : "ws_net_paid_inc_ship_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 33, - "name" : "ws_net_profit", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 34, - "name" : "ws_sold_date_sk", - "required" : false, - "type" : "long" - } ] - } ], - "partition-spec" : [ { - "name" : "ws_sold_date_sk", - "transform" : "identity", - "source-id" : 34, - "field-id" : 1000 - } ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ { - "name" : "ws_sold_date_sk", - "transform" : "identity", - "source-id" : 34, - "field-id" : 1000 - } ] - } ], - "last-partition-id" : 1000, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.22.ndv" : "1059688", - "trino.stats.ndv.7.ndv" : "5947530", - "trino.stats.ndv.2.ndv" : "1962", - "trino.stats.ndv.27.ndv" : "1509736", - "trino.stats.ndv.30.ndv" : "2360578", - "trino.stats.ndv.19.ndv" : "10091", - "trino.stats.ndv.18.ndv" : "100", - "trino.stats.ndv.14.ndv" : "20", - "trino.stats.ndv.23.ndv" : "1059915", - "trino.stats.ndv.34.ndv" : "1820", - "trino.stats.ndv.21.ndv" : "29691", - "trino.stats.ndv.8.ndv" : "12132428", - "trino.stats.ndv.3.ndv" : "297612", - "write.format.default" : "PARQUET", - "trino.stats.ndv.17.ndv" : "59619264", - "trino.stats.ndv.31.ndv" : "2436710", - "trino.stats.ndv.13.ndv" : "54", - "trino.stats.ndv.26.ndv" : "211496", - "trino.stats.ndv.16.ndv" : "1483", - "trino.stats.ndv.33.ndv" : "1994307", - "trino.stats.ndv.4.ndv" : "12188957", - "trino.stats.ndv.5.ndv" : "1890006", - "trino.stats.ndv.9.ndv" : "1890006", - "trino.stats.ndv.29.ndv" : "1750832", - "trino.stats.ndv.12.ndv" : "3006", - "trino.stats.ndv.25.ndv" : "1115360", - "trino.stats.ndv.10.ndv" : "7082", - "trino.stats.ndv.28.ndv" : "552520", - "trino.stats.ndv.1.ndv" : "89157", - "trino.stats.ndv.15.ndv" : "20", - "trino.stats.ndv.6.ndv" : "7082", - "trino.stats.ndv.32.ndv" : "3163161", - "trino.stats.ndv.20.ndv" : "29733", - "trino.stats.ndv.24.ndv" : "388752", - "trino.stats.ndv.11.ndv" : "5947530" - }, - "current-snapshot-id" : 6086383195474739821, - "refs" : { - "main" : { - "snapshot-id" : 6086383195474739821, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 6086383195474739821, - "timestamp-ms" : 1648092245511, - "summary" : { - "operation" : "append", - "added-data-files" : "1824", - "added-records" : "720000376", - "added-files-size" : "38596638688", - "changed-partition-count" : "1824", - "total-records" : "720000376", - "total-files-size" : "38596638688", - "total-data-files" : "1824", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/web_sales/metadata/snap-6086383195474739821-1-c08c5447-c661-442f-99c4-cd2203afcb9a.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1648092245511, - "snapshot-id" : 6086383195474739821 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1648092245511, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet-part/web_sales/metadata/00000-c9f38021-83ff-4c98-9bf6-132c5d477ee4.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_sales/metadata/c08c5447-c661-442f-99c4-cd2203afcb9a-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_sales/metadata/c08c5447-c661-442f-99c4-cd2203afcb9a-m0.avro deleted file mode 100644 index 933658c31278..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_sales/metadata/c08c5447-c661-442f-99c4-cd2203afcb9a-m0.avro +++ /dev/null @@ -1,3052 +0,0 @@ -Obj schema%{"type":"struct","schema-id":0,"fields":[{"id":1,"name":"ws_sold_time_sk","required":false,"type":"long"},{"id":2,"name":"ws_ship_date_sk","required":false,"type":"long"},{"id":3,"name":"ws_item_sk","required":false,"type":"long"},{"id":4,"name":"ws_bill_customer_sk","required":false,"type":"long"},{"id":5,"name":"ws_bill_cdemo_sk","required":false,"type":"long"},{"id":6,"name":"ws_bill_hdemo_sk","required":false,"type":"long"},{"id":7,"name":"ws_bill_addr_sk","required":false,"type":"long"},{"id":8,"name":"ws_ship_customer_sk","required":false,"type":"long"},{"id":9,"name":"ws_ship_cdemo_sk","required":false,"type":"long"},{"id":10,"name":"ws_ship_hdemo_sk","required":false,"type":"long"},{"id":11,"name":"ws_ship_addr_sk","required":false,"type":"long"},{"id":12,"name":"ws_web_page_sk","required":false,"type":"long"},{"id":13,"name":"ws_web_site_sk","required":false,"type":"long"},{"id":14,"name":"ws_ship_mode_sk","required":false,"type":"long"},{"id":15,"name":"ws_warehouse_sk","required":false,"type":"long"},{"id":16,"name":"ws_promo_sk","required":false,"type":"long"},{"id":17,"name":"ws_order_number","required":false,"type":"long"},{"id":18,"name":"ws_quantity","required":false,"type":"int"},{"id":19,"name":"ws_wholesale_cost","required":false,"type":"decimal(7, 2)"},{"id":20,"name":"ws_list_price","required":false,"type":"decimal(7, 2)"},{"id":21,"name":"ws_sales_price","required":false,"type":"decimal(7, 2)"},{"id":22,"name":"ws_ext_discount_amt","required":false,"type":"decimal(7, 2)"},{"id":23,"name":"ws_ext_sales_price","required":false,"type":"decimal(7, 2)"},{"id":24,"name":"ws_ext_wholesale_cost","required":false,"type":"decimal(7, 2)"},{"id":25,"name":"ws_ext_list_price","required":false,"type":"decimal(7, 2)"},{"id":26,"name":"ws_ext_tax","required":false,"type":"decimal(7, 2)"},{"id":27,"name":"ws_coupon_amt","required":false,"type":"decimal(7, 2)"},{"id":28,"name":"ws_ext_ship_cost","required":false,"type":"decimal(7, 2)"},{"id":29,"name":"ws_net_paid","required":false,"type":"decimal(7, 2)"},{"id":30,"name":"ws_net_paid_inc_tax","required":false,"type":"decimal(7, 2)"},{"id":31,"name":"ws_net_paid_inc_ship","required":false,"type":"decimal(7, 2)"},{"id":32,"name":"ws_net_paid_inc_ship_tax","required":false,"type":"decimal(7, 2)"},{"id":33,"name":"ws_net_profit","required":false,"type":"decimal(7, 2)"},{"id":34,"name":"ws_sold_date_sk","required":false,"type":"long"}]}avro.schema/{"type":"record","name":"manifest_entry","fields":[{"name":"status","type":"int","field-id":0},{"name":"snapshot_id","type":["null","long"],"default":null,"field-id":1},{"name":"data_file","type":{"type":"record","name":"r2","fields":[{"name":"file_path","type":"string","doc":"Location URI with FS scheme","field-id":100},{"name":"file_format","type":"string","doc":"File format name: avro, orc, or parquet","field-id":101},{"name":"partition","type":{"type":"record","name":"r102","fields":[{"name":"ws_sold_date_sk","type":["null","long"],"default":null,"field-id":1000}]},"field-id":102},{"name":"record_count","type":"long","doc":"Number of records in the file","field-id":103},{"name":"file_size_in_bytes","type":"long","doc":"Total file size in bytes","field-id":104},{"name":"block_size_in_bytes","type":"long","field-id":105},{"name":"column_sizes","type":["null",{"type":"array","items":{"type":"record","name":"k117_v118","fields":[{"name":"key","type":"int","field-id":117},{"name":"value","type":"long","field-id":118}]},"logicalType":"map"}],"doc":"Map of column id to total size on disk","default":null,"field-id":108},{"name":"value_counts","type":["null",{"type":"array","items":{"type":"record","name":"k119_v120","fields":[{"name":"key","type":"int","field-id":119},{"name":"value","type":"long","field-id":120}]},"logicalType":"map"}],"doc":"Map of column id to total count, including null and NaN","default":null,"field-id":109},{"name":"null_value_counts","type":["null",{"type":"array","items":{"type":"record","name":"k121_v122","fields":[{"name":"key","type":"int","field-id":121},{"name":"value","type":"long","field-id":122}]},"logicalType":"map"}],"doc":"Map of column id to null value count","default":null,"field-id":110},{"name":"nan_value_counts","type":["null",{"type":"array","items":{"type":"record","name":"k138_v139","fields":[{"name":"key","type":"int","field-id":138},{"name":"value","type":"long","field-id":139}]},"logicalType":"map"}],"doc":"Map of column id to number of NaN values in the column","default":null,"field-id":137},{"name":"lower_bounds","type":["null",{"type":"array","items":{"type":"record","name":"k126_v127","fields":[{"name":"key","type":"int","field-id":126},{"name":"value","type":"bytes","field-id":127}]},"logicalType":"map"}],"doc":"Map of column id to lower bound","default":null,"field-id":125},{"name":"upper_bounds","type":["null",{"type":"array","items":{"type":"record","name":"k129_v130","fields":[{"name":"key","type":"int","field-id":129},{"name":"value","type":"bytes","field-id":130}]},"logicalType":"map"}],"doc":"Map of column id to upper bound","default":null,"field-id":128},{"name":"key_metadata","type":["null","bytes"],"doc":"Encryption key metadata blob","default":null,"field-id":131},{"name":"split_offsets","type":["null",{"type":"array","items":"long","element-id":133}],"doc":"Splittable offsets","default":null,"field-id":132},{"name":"sort_order_id","type":["null","int"],"doc":"Sort order ID","default":null,"field-id":140}]},"field-id":2}]}avro.codecdeflateformat-version1"partition-spec-id0iceberg.schema%{"type":"struct","schema-id":0,"fields":[{"id":0,"name":"status","required":true,"type":"int"},{"id":1,"name":"snapshot_id","required":false,"type":"long"},{"id":2,"name":"data_file","required":true,"type":{"type":"struct","fields":[{"id":100,"name":"file_path","required":true,"type":"string","doc":"Location URI with FS scheme"},{"id":101,"name":"file_format","required":true,"type":"string","doc":"File format name: avro, orc, or parquet"},{"id":102,"name":"partition","required":true,"type":{"type":"struct","fields":[{"id":1000,"name":"ws_sold_date_sk","required":false,"type":"long"}]}},{"id":103,"name":"record_count","required":true,"type":"long","doc":"Number of records in the file"},{"id":104,"name":"file_size_in_bytes","required":true,"type":"long","doc":"Total file size in bytes"},{"id":105,"name":"block_size_in_bytes","required":true,"type":"long"},{"id":108,"name":"column_sizes","required":false,"type":{"type":"map","key-id":117,"key":"int","value-id":118,"value":"long","value-required":true},"doc":"Map of column id to total size on disk"},{"id":109,"name":"value_counts","required":false,"type":{"type":"map","key-id":119,"key":"int","value-id":120,"value":"long","value-required":true},"doc":"Map of column id to total count, including null and NaN"},{"id":110,"name":"null_value_counts","required":false,"type":{"type":"map","key-id":121,"key":"int","value-id":122,"value":"long","value-required":true},"doc":"Map of column id to null value count"},{"id":137,"name":"nan_value_counts","required":false,"type":{"type":"map","key-id":138,"key":"int","value-id":139,"value":"long","value-required":true},"doc":"Map of column id to number of NaN values in the column"},{"id":125,"name":"lower_bounds","required":false,"type":{"type":"map","key-id":126,"key":"int","value-id":127,"value":"binary","value-required":true},"doc":"Map of column id to lower bound"},{"id":128,"name":"upper_bounds","required":false,"type":{"type":"map","key-id":129,"key":"int","value-id":130,"value":"binary","value-required":true},"doc":"Map of column id to upper bound"},{"id":131,"name":"key_metadata","required":false,"type":"binary","doc":"Encryption key metadata blob"},{"id":132,"name":"split_offsets","required":false,"type":{"type":"list","element-id":133,"element":"long","element-required":true},"doc":"Splittable offsets"},{"id":140,"name":"sort_order_id","required":false,"type":"int","doc":"Sort order ID"}]}}]}partition-spec[{"name":"ws_sold_date_sk","transform":"identity","source-id":34,"field-id":1000}]Fk1Sa}x |u~cvt1 ِc't21q/."U^pubʥ"V,jKZ+bZ+e[֊T2c<iV_~;#FҫY.t,\ܒu_3ܼЛ(ҿdr.5+{waeYﭙ n=gQ/Μ 3,t_pm~M o8^_ʱ8zK å]`Mwzc|.&y{̘7ȦeyENL ҂̀_q̌9Wn>i kU/;%)uWگ}4*6ƾƙhsNK_Dkqƶk^LO;Z#]ױ&ٝ.yJ>`=1;Z{B;6ths\|fh{w#;^ Ix'x_Z t0L` XvH8nXD@!QNRI>[y&dyd-5lrj]; |ı$"' +N$E4#&h_b.8ǒƟ+Lϐ~0Aʟgb:+,ʯʯlʯʯR'+Gr95.M[$4IfM*5I&I$I$Y$5I 9S We?ߕ% g k&~pm1p෿<&<C8a5*?Qa{~a;n{~¸Ork].~G]RQ\2.(}Y)p -)Ly -CTL8L]vtQW5CO7KƲsxoZv0ƂJQR>e=D)|<^vLm3OӺh}wGiC^5mlJMh䱉5=LI]sv2ϡU|b>k~.󫈰coo7fʽ - o|HֹgK -Azfl`Tp\x@ dA"[ kMt Q2f"XyogQ -na7 kdQ),J)~CR J)OJ\)BR\?RHɣ"9R(eJ(TBɒ*S ŭ?Kbᡸa݅*`W!/T(+B(VނPy@~}Bq RUv($L]5i͉!y -Ebii11ݛ"sX?=-gz8,٢)tݽPT=;\mP!/GL{m({6aYF䴬6z};wӎfuwix,÷,h=1zJa}Mts\_{jvo㜳ɠ=`3X -6C*8 .p|< 2 BLNC(E1DaSV3\F -/^͉|HDI -ˑB&e¦bPxTBaC(?PeH(n4KF -;'̙J(P :S"_⫇xՔU&1b*.x(OG]YR<=k-@)c)*BquY;LC(!P?+=ՇW r^>;ן -(B`cն-Sݓ:KbuxmV3,o^rW)S}}e4soS3>ܸi鬟h-<؊1vgjC ȫ1; :OI _lۡܪkWWKΐ[2BmΎ~- -/-P : &0`;7x,p"H C( -^'a!0.PX{x[. =x0Qy "F G_ E_iXB1 E(IT֤(Rܡ?OR,UJ%Q>bJ]kQX{޿U[y#Jӈy!OT8e98C8O# K+f4oߟs4ofn  d t>Rc(;R=^[j=\>@G"Tarx#ũy NK*m2ʽdyрk[.Z*yg/RM,.u6ϐ վ;+Y:;RY(ޗ(H `VRNp<8A$!a@o%28Rt^CTS78RF -Il Q!,:%AF_$JATF -Y)D)*xi hF)i>R(N;%7)x_ D(>R P\D(N^3<C(U%n%u k*HrB׊P.D(/e 7b0#Ŵ).!Pp"'ǟ˥ ^.;73G{s1'tYBуP41z?HYn6w&2Ew4E_1F)̲R =_1RH1:-+6N؋9S[_ʰXp}}۠gO^ aLsOB~Ml^1]>C>.#tzpّ}ɠ=`3X -6C*8 .p|< 2 BZ`u}3zL8.k -r=cyHYPT *8Mg*)(UB@e{3)FŞ=QP] E+`NaPĖ'BaS&)*u,EP,@(<;&E^#B[B(6}!_)DXxb:z|k738j(t>Jfe`7ٱ@ cYU[4GD(:kweJK>OjNW=I3ӆqqURsuYD[yҙs?z\yյ_v7Kgdޜϟ;bM\5Ҏ H#Í[DZ'{}idЁh`F0,`!>`ABD!V-?!gfцƾLki)]meK'w?%uχcͿ3/dwX{R4-?:Sܫz^)BPfFe?sLYߗp㔇i("R\W)P)G*S^C!J:3;LE)lyC>T -}?3rEyܜ7?;[dcYgZbǓ -?Hexm fbͼmߥ>@GWhq\DkQwWuVЎSbnxA4ߝV4^˯JaNtXaEF5ώl>X|}idЁh`F0,`!>`ABD!VDmxp˘)dh"X|M\|NrGXs cKJ)T*D)$RXTv)r۰Rd?F -#žUG*#E٥xD%s+SRXU.|\#P\+PSzDʸ;,_H,) -S]xY>C8_WHQC܀ p^ޟ?͛#lvI*>[k7ϤZϤ;?G.)yLm;-u2ř6(dv6l -<"K͛2|5l3+OJa6RG35oe*.kilIK $@0 `+'   0D -"R5L5ʦ9d!۴CW3b`}K(FrG@ ŷ*xUEJ(RBUm#q #ݚaF -#x;ʜP,P(*Ph;<J(^R&'U&YPLO%fL;1Qyłz"{eX{Y.u!J|0%uEk|)CbY^>-E XzN?+/ضED(66^^kv\v0Ew}qtf)S)Ŋ)NtDkg3~-gfѰ\Vp TKW2|-ꁙ\Cŵ|_BIM.埘!WpSH;P}idЁh`F0,`!>`ABD?pD=OMx}g|X{E[rBAP pb)k)BqPreԗDWeQ2QP)ۙD14_}r3";~#)܃;%B?O3Uż"Zo<1{f, ͥqU`@DƂǿw{Wgs3}d|:\yD(&q  Ή'v3@A2@40`#  -p  !C+3W>]to 1l0QH֨jPD!WOe%NÂ$NB'J(>S Q\]D(4*t {{/i~u#mRh̡X"ua*hPJqP٤RLVJC)/P)PonQ0'PP#F֠7ԥKR|~G -愩+#u4C8OF?yc2ӽ#x?HV1O&mSFWkK_4S<0ۏO1R,_(Hm.vZ'Zel)Vt;Swb,=> yz2|m3YܱkW -lbӥӏdȇOd7WxT9$-P : &0`;7x,p"H C(pR:I/Ә!jdᬬ-bax0{5B)jyڈG)TJ5o$QJ>RưIaq~[G -kׇbUFKPLT\D(*ύ!~jQCyƯD( abꢺ|iMn&G;oT $@0 `+'   0D -񷲝h DtۉЍA 5ĸs 6b$m!$:GAQ?RU$?LD_5C8?Uhd4/˧eqL/+pi?-ǯrs;m]wCSwUU -gs fJn:3rl@I'Vm[4'NZH_bQӸsvūfq^Sty8mTH1z,jCNKk'[ ^3۴c}ٳ7oMoI'3|u_eUgq5{;N\)Įk ʙmW<8'r8 -Azfl`Tp\x@ dA"M !!SuQ4jF0Ftj kiTT,j}װ27xvU ŪG L%%+*@%QuQ3&7R)2]Q)OUT}e*C)jwS(E#g\5它?n1IWesK!*Je?&-1C8OHL6-7'ec9~o34VH\(΁IMyR~kLu=/yϷ)Z80n\I3UR ;Ǚ*EG'Z|V؛8Sh'yH™ʼgq+]|s&=t y`ƮDOK $@0 `+'   0D -?ܜA)$8x@g7 곅7JY -yz0/ JQ).VJJ)JcH;eR*X2RkG)>oD5CKqR -F_|](F}0N?n%9R,Lq;f -,k-Jq?W))R*S(y_TH0Jmǐ|~3%Lo-x ҳsl0=n:~éOΖȃUs侗3o&z!?s?kd?ZH,dk璭d?ZRHRԱqgU>s_4m -а4\u^.",{FpCFdF^T&VQ^VYVM'F@A2@40`#  -p  !C+]D_ NI4C1 }ZCl_gqwt_=a{Fv"$rDTV*^edd jw]p3GΪ(sЕb*T2G?*G -Un;94Jט?Xm':ròU.SQaaT*jK13qXyc,oVf,-Md*75m,{߭HTewiK>M|WI^_wx0{kL -r;k\a277Z|v.ְKoXJR?Q7y*D/O4ҧ7hMOj}_lQokzB#U4=O< j&2^ -ި(H `VRNp<8A$!a@oe6ƫEtНi'bl#.bDl{{*ۉsq&=E|m&N !R+A*w)UaU2CJRJUcVI:ǰ2sb?L*(USUnQGcU2F)Tw>;?DU_ƫ,)*2'ʬ;0,BrvȇpfvY%s1>+s1ҽ@fN8!~khطU)] $cb -÷T!)^k ҩ5Hʎ2}v^${nYj7n3tn>p|ر)֖v-i_107qcw_^9EGQDoxUb -V(G;^%3JQ@A2@40`#  -p  !C+Kѓ;tD_Db(&ƻL7XX߅,r*"ŞeėO%p wHED^~h~H*8L$V~pJ?TaPG9Z猲 Q&8 ~<QIeTqPoqC]-,tkbffr:)Ly QIa*`!yhM;I.ܬ9\oz& l&Ϧ ?zz@LlV8^[\dv>\I}u8pXqc8S)%NKqD.VV8S˞s·/s7+j;4=y kydzX(~&ZrKgkgEV>ъ -Azfl`Tp\x@ dA"[q zNe2feٸ]q\yY1I)Ž*J)~R*k0iO>#{t,moY2tJ(<<)xGe+H ŷ*c?#HB.$~%v+EIO2uB - S}a5IC>4hp_41>^ğfe{iYt.L f\`]}4*_ޚ4c0mܿʮ;d޷&E/ڙի=؍oN0{n=:xtuwV7}17uQcOoݟ.ZY%k%~A5 1ht*?AU  aШ| F0h < xZ t0L` XvH8nXD@!QŃⳃ"F1mzb#8kxo {7DX,fKK/U J@8Q>% QY5*3h?>qEIn% T&(٩J@^c_S=b;9Z&]2$ʽ)ᅱT(fB@zȇp.ԤG0-f1TB7oܴ@nn,8|r-ןz`HYOEڪgS4ͯDϜ=Wj25* eV\&ُ* q`l癧WOĽK:dm~  ߃L Ċ6h'4uж҄{߯DnN)ɠ=`3X -6C*8 .p|< 2 BH=$fBwc#1Fbi'&b"gTۈ[G7mjmgsJU)U1T%]J2LR%c.2RU:(sЪC*U9Te^*)RK~;KUPQ_2QTec@MikU!{*!8%rAo0[A Xo.%i>|,ٱmnR7kWַRtͻS;+MaZS {S ƙZO1WrZh]O6K=/iGkzsݶ7i~u{ lQX8oZ=[~PC';fEֿX}idЁh`F0,`!>`ABD[o}HG'b2m̬Xğ$|(;s?U%VvxG:;C) -^9Rx\"bϨQN؈]ܪ¨RJ) *r[+~0.{ooC],`vdo(E X,ʔw_ǿ ~ek<?u9sXK荥 H9"KMXt=uE ҩ=X |7YW{`iΜvd㑗'*w͵|8źmN榮zegs_=gپ uwx@jvʔ2͘2ySF!y2opAʍ#㽙|f11ņdwdM*7+6?xힵf`nr)eb7.?x7p}L=_:-'ZglG32k#򓗹kN=yen~f&9NR3#71eώ=K $@0 `+'   0D à -:I=L0(d4 ;(brʮ> qa|(þ? Ket> -Q%Mʔ)9ԮǎI@&ie:e\?Oq}gq2eؕ)=Rd)䫗b~Ŕ6qp}e{Q -iSƴB47LɩEjҟ|iH+\fX@<bn} ^ӇG-yv+N۔2}[ө#$ɺ&eOؙ''>l\Us-:X**rMvפ;+5g@|o4eµ?)cFLF͟AUb({SC2Zǔwk ɠ=`3X -6C*8 .p|< 2 a ($<_B M}R Kw[򈵀%UBsŃbaH7Rʸq2nL:׵{G|(3mqfqce'.P"rO>ʤ'6t7ƍĦF[yߟFD.(_){BTl,Ue0n/SO(Mb31s< ǂt631g9ȱwJlH[lp&}V׻h3ѫV==ykth߼qwk%[Z~.{RǶΊM[V/O~:o4uOh {D6OxF9]7._ ~JnZٱI*ɠ=`3X -6C*8 .p|< 2 ah;Ξi'>BwiMiݷ}ĺsL{3흃g{z 6x}g8ܣR>i[eRUJTe&V7RV{UoPUFW*YiWryMe6J㧛<%(%V19KyrM!2Y37C8_{jO` ;+//ofs291(A1>oۢivұ7(ߺxmN3y^)h}G~bX8Sôr͉}g[w3unQw0:->2obx8”t4PS0C8_wߎ!Y_d'<~//ycDf~ fwҙʃkkTѓ Om:C8O;fr8ov.xc٘(cLQȊ ?n ʎiM&yˬ*W4߱LNPz?kO-?0+U)Qؐ4ڙ2e^ov\O)qħΏ2+:*eQrJ?~Oj7,j:執(5'B9RFz~l)LN,CUP{C>tEHiV|4+^ĊE{gǶgh> ?6qt+$~0Yd}ێ tv仓 &`j(]k>ڷik\>rmj'%+ӝ?յ%/|"uH؝o㟦Z }[bwmDܴȧHBݕf;ؒ؂ -Azfl`Tp\x@ dA"[ނݢ17MKs,by|1v>w+*J,u* Y%sh"*s<0YzĻ2sկRETc¨hV1-c؞?-ŨOE^! ?$fKjlLb-_`4"]3|w<~W37/cgeF1Q㊒-jyڮtշI*nj~'C3{ evc'LvCs-O}i;>87uy \ uo~tgࣗogKFo6b"*Vl'Rϓ D.%* 5HA0G-P : &0`;7x,p"H C( ~-ǐ|+0PNĔG̘Ju}i!q"ĽSN|e$~2$~v -\)d*A%]%(L1e+Q_fJOQz/nǰG8qn*zrr Cɛ'Kʔwmkd~=9C8_Äs ! @Ā{t.;'7G[hnє9Xr8l3㵧T۹{ߟ~g}ñOSǙ6~b>Stmh]cc_3QVnv6\'/s.?WVz?/[yp`MxxջfώN}idЁh`F0,`!>`ABD!V${𲊠gi'1?qV'Spq>9ap/þQBM_('ɨ<J(*c8^pɨ_RPpY|-pTMQJ¦Y.^b 8v.r~=] oHk6cJ"bȻ?DI0%_rC>u8lR |P|f,7KOiBsXROeWl>1y?f6Sbȷ>yk_l7j1oSIݼչ)ZL[Ve;u@ս{<0y a(*:^O1yE"-bO3 ~*Tg(Qɣ G&2Le<0ya(Qɣ G&2Le<0ya(Qɣ G&2Le<0ya(Qɣ G&2Le<0ya(Q68yܭ]<}:J!S11bkaxX^|XAĵ -+< GD'b1ȈLW:UbwJL^S|T?&$tws"&6e2 1JLGߒO>sWb)}1G*SoZ]0uY`Aݐ|ݐ?jfnVn,W~/zt^>כ23Y>8"-W{k"& ^HtO֝ywvΜlh7ٸ vڹXltnr럝Ɏҝ_͵^w{<>o?I[˄;1FL - -ӧHh{=bR2bRu\9i -Azfl`Tp\x@ dA"ben|zG 7n1ez-؋⊹ịc­]|VQ)_r\RJ?wc+[QO%xR%,ecyR Pܿcvb -9ZpT PS"e*G)dW^ }:q!:]zc` 1Xnv73yV#n7skM>&z/yշLnP5m3}Bl6H -HjO˥f; WW]xi| 4l+Ohoj4Dh;O<T\V#Y -{)M쁧5k1xZ t0L` XvH8nXD@!QD쵘J_C&T >Vވ_JTKR=l2!J l~Ŭ'rqJU̺ړČJUlJU~R=\79GL7FTuJU~RIw* -cX/o@o3yvC瓍G&:oε ?v9unjɎm@)6fX4qPƑ1܏qmNa~HVem -Azfl`Tp\x@ dA"[YA%]faVC 1*b.%_VRZCUēO| [Nnw$'QIJO*;o*=` G˖hՓ*߉U}DOUU̲ 7G֏Eg\ÉkWFOY=MRU2D S!e{%9٘F?3'+,o􊁬ia=9mfct]3k5SGoL.XAWG@3eцR}di`m_&3ϗ )/+r33ٜ,>;7;oj3ūv}q6thȠhK`tg[Cocx16/9`9%Zvi^U+: -@?3y3!3g4ԡi8~8)}NL߆5©'+)qϜF?!MgƊ*1P АZЁ `VNp<?0< !C h&8h쭆qb#bn"^b#C=rC\=B<;-w;C~"#Xr%*wXq(JDf+R$ WuRgPyᜒ*-MϜT *r,ꏴ3]TBr9H7h?_Q`az*B_=/G\]bH& egfɇ9;L4r^9Fm{K+$~bo&oRxb2;Dz"'b?:gj** n!SRURWIf*Δ*G 6ؿn%UTReXIbTuY̒sKPG3O/Z;_.Cr0>2S>e+xY dxL/=/50<'x>:~kW>jF^~BK-<9O764W?Hq21j$Ђ`#  p /8A$A"y(כEnLİ)676yVL8Jʣ -c m&bG-R)_'foW(iX0DV)iG);WIgYMIK~Qe.[UHR"*a}Jxz,?ˎ߻O{sY|d?$/<}헫[!: ? hf2~0#\,/x%Hr|\zV@^Ieyl.vhݧhf:?̾uKPIv3ź'A0c|siO|տX j4>2]ORZ>;p/G{ЪE,K=f^fިMe~Ň*/W$+Aѣ*Arjf Yi|6;"TF[ӎx1Tn궤bA‡mM[O5wo[l\d-]`+ٰ~puâ΢7,R{OK/Eo[rxrfxl[WDx$Y|iX8L*=`?UE㷝;53SQI5mFwY?0T˞eLfsKb9&ٚDxwIp֓-ē0v:b6=N?Cqy/G)iTlT᧨G5T;o IF0,`)^,p"H0D -"hs-Dj~14c31^bi#}V[m돊ضNK_Cm"\=kPHUdCnOx{y++Y %f%Qm#*r21*) J+JAf*ϰ:/]7caY|W -O/w{nRx=?VNLY6-'-,./ˤe`ei<>5 gqF[$k7O<01ռa6X*umxhot/vgFg գzQߴbN&-JJ,yk'%}( *|I*rv  IF0,`)^,p"H0D -PMNfmͤvC'1vAbn&b퍽bMr$.DltowA"OeNI{TRERR%Y%U^PRů -*O"UG0S5A}[ȧ*\›ٟWR%U/=GpٝHHuTqG|z%O> %ʿJéjPTf}S> L1,t5\OfPpiyT~zT UJ> nO|ۢ{,Htw&kϼ;8_addӞˊ[VZv;\SE+SN';Gv]2t^o~ED- ip  cQa(F_$gZI$2A1j$Ђ`#  p /8A$A"y(w.]~a1ty>Z:Z:$ܫ=wxs|:.`Ur#S Jn$)!tg*3=RW͔&~657PrJn)qN%7V*=]JnT&Vg/-륯I?ӪFnv2UȍT#=εi!stC5)rx{^ pY>.O~Md"FxNؼuj'ڶlHKWh#GnK/kN~6XR7U~8s+׳9ۅ/[,6, n%2d|6 IF0,`)^,p"H0D -_߿!+$- yC6Eͬ%bl}C]'ϲ^d%[AElH2opwAqJP|!o&_}3!A%(Rm%(ʕ?V )Aѩ&;R`,E-T_s> -2{IH.0L -сEO'-ЦDzQY@M^Y$G=IS>e6 $~&;3]H`MM,A!e2s٬;e -iӶWt Zے"t3?7埚k.APl/Ydmxc .JY7z:)yXr+˙3ܹgsRqWˤn yHH<(0. АZЁ `VNp<?0< !C  ZQ!(% -޲ō.;xy.iAq4!ZRQUE#Ite,*F'_3UUQC+٬ʋTP%(R:Kwo20kKQQ$ݣ̚#(*݃QPNe=䔏pj)4_Z "g|Bcӳlj^O P)hWIP7YwMqI66loa)~o"H~872Xs͠߸;|}ޒ?` -p>G'T'˛ta |Hِc IF0,`)^,p"H0D -Pjjb[L yhF[u؋Yֹ=&¯ɷq&#U#9O%?nxD{žNӀ5s 5$E[%@$)-\W* E/Nv?n]<@~zrd9O!@vԉsAJ EhiG<$a -t&G~*;e4_05=7= fO뱫f?uUvo/:5goNcG]fڀqe41a6 -Xun ֒^v[Ut @R?YNõ V /4Z;8Rf ?j߮մHa%U樤ʯ)K)J9w -՗&g|L}R6 efdp,) S6u55ϭѬ򢚸Ԇ-TvK7O5?>ݓjћj*?ܝj9Z~mq^9öGigsWMs{gc-l_q*wi'o[tإmwvM ;5Қ?=Zcz[3'I -ǥ -tL` Xv`Np<?p"H C"!q(~mJO{7 %f$Ć)NŻ'{xr+DňHJ4%OI$KR?Zt'9Sg5CD3k0g#uL33?Y_*?ٔOI?rd5MWפp[yI2rY7$M;PnߛJ5TCHezcnnʣ^~ b1]2x<&RΛī(Ÿ*&h@).4C87#HIfnn6N|B IP+!jX9aª o$KRX3ru7~jhy ]bTcTӑc{?I4JQ J15L]nBG+v({"-8b)8O$)7IY+94U(Ŏ[Zx'߸DtI۵םP+ 0m]L!g:\Hr%Z1l+\/۫!-!p2U.:xzn!jߓZ 4BΝDR5'53Vk"Uu/ -FߣM7 -tL` Xv`Np<?p"H C"!q(D{ ;~/1!t/av.bj%IJXwnbE%I\}x.AJ]DIDFGY1)YU`J2+ܡdE̊Fe1b_?$+J+kL,TDy*UTŦ2ܹT?E*GR JU/*U<ڻ{)ω~st*LAU6"VIٶCN|ΊueŔ(wU-A9Z -B99>N 29>D)IZXt^V48_B>znǃ6l QD3hcy m*dArS{?4^X4~dmy}k}޷*OQѕ{w7^4_u~kL.w\tZH3MΈtxvqi40`VXp\x D!qH -7RR0pF$yd ۼvQWx9xQ)"KaG)X -yPL%"I1 A%=P9v-\W7vřK1ے,NO(_$K=%*(,JI"Sm(EfH9oejī(JmTQ -5C8GKҳLTҲeg2%T҂,1ܜ!hE)*MXy0Y/hުI՟n晨:S\ov}; =J13M4qtGT1J)2TJB;HSFeRQi֍X^3wPA(nJ('CѪPeW*UBP?^ʽ *~P\m?!7SXC(:{ -O+BqwF(A(A¹HyK#}i9t,T/qBFzVH&}H(b- 3h7LN#~S!}3uy)3Xəͥ+EXlo -۪2|yX -XaƟJ+4|u *˞BbRt ok"<*e:Ѓh`&0`;' 8A$! B 8uDےX"@4c=1*bi"&b z¶%*UOMijxp-%*"i5H JRi)#TŦIV*U)1*BER<\V?zgn%+kTV*+*YTV*uK@}2Uocrcr2vdeIHM[ī -W“Q*r1jՏ MVW*BP/'SHCVrP𥋢!p|NYi|R]ֳY}̢]DԿItKY}8nhdYcɋL755}ko..qێvw|b7Qu>(ߟ.k#zOۗjkD8CD,>qtl"o&-$v> -UUm'U%[:Ѓh`&0`;' 8A$! B 8*-:*b('t-aXJt (5L7>{Ŀ+%*"q\B%dóds#6$WJ҆@f/#34e EV:¶Od{Qm88N] 0dfs5i*jptjǮ&4/oz+goL{6{kڰQW6󉣪 -移6}B<5]5\~?%o$ok4wԄ>ZiMg4ʹ(Ё @F0,`؁8n a@bġl'=c"fBl;1m$涁s5Ķ؛ ۞XX#1pQo'"&: d倒1*YN?V/BF#_~H+ϷnpVSR2,UV0 !e;e@%+X7s5sڽ偽@%Pbsu:ϾAVr0utFN!ՑU d |0+- dH@}RvNMK`#<\t.{6H>_dhKcָlk:Xs&RV>Ӻ}i_bkX"l <~󅞆Z`m"k7z | Cbd5">DZyD[LbG6)8F-P=`3X -6 p /A( CӗC6=m#"b.&{Ķ>o`3ϕOs= ELy^xΏqvM33T1Cg#(/s.+R3 ^S1IwQ$o*w|v'橴cFc$·ݏ{[ю9"uIɓX\څvLۀTb$yBv<0b;1]ss}tq?J`f䳇ݗ6d;>5^{ okuUPC<2Y:t軱#mfZ he}X:IUw=Ս ῷ뛭?a\\,U,ȧDµ-$$z6;īǶIZ@z0 fl`^< 2!QA@FJ]@Bb"*b"Bb-"Bb/#libYZ^L<g<—,q~fA,o *-^D*-XiɣE#QɭbrgwBUb򈗷S 򝲾yE%&cFqh$~7br!&3E1B<oCL*gb*7'A9DoLKKr}!)x$3eXڈg=CslM} 1V4^ ㏱=D?KBؾ5~4|z=kٱyu]mk]lK3jYgKAWwW|>o~fHŷD;^%BI "eLv.o'r/nHۭ$ZGT(Ё @F0,`؁8n a@bġ"DMʈz& UXIL51P%r–'bRJ\]D<[A+#2"d/IN(PFO\62ǙuTOxUzrғ Uzr2\ 'oX sHKyoSmɞ8^EO6c8Y=RГÉuQ*FOUOF>5\ 2j/)2rsWОCO}`}'\M3)P`Ms{㡙3L5>{n,= topy+U*EON,l~($޸԰ j&c,l -ZI@1j40`VXp\x D!qfp -ۙyMhv}6[(=^)J%_Jn{]<R]W("rKYJg3 +@S"#JRXT.e}ldKY/_T":yp*rzXzD]r0_cRV,q80C8WĭB9P +[y381͗IB( dee,q"?ȺȑV_wa{Xz]3˔<TXs%3^h^Zb8dʠk#J.T-ϻTGEAETRq\كBE_DE -@EjP@`0 `+,8 .px@ dC8s{01=0V)ȋdA*CɂV sGQ7IKy"uMdA.M\ fH]z C9* -sۣU=SQA9*HpMP($/-È|J윴Y+tdytks뚲|V_E6Lk>kTuMC#k9vD[jYzLG# -Rbеj=eo;(g(H(Ⱦ)hAA*'K[p35 }(H^35u۰vJ)Q @#  7x ~D@0D -1CPʈ.I%1̣K S9pjI"'*Ejp`USB/&ByD'R #ҒTZ2WG,VƑ'5x\8gU y/)1Ət8#Oxn$Yw}15OyW9{Þw Ei$G.v5oc_ZɿeЇp.c=#˒$'f>$=3CKW7,ϒ'_-X9:XFo- 6Eθ3zl^pr|bͯ V2~>qTC8GY |u|zf/dBPN"+|YIav3Pv`hVbFu"RTrF)¢|2H=UNgEC>Z2__d"ӎ+XKkLk։#e.{c.v7:,bO]=݇v-,.۽3JYf-?Re^ΝXl|B >-Ǘ3ii|KG*]9m=kW;_[yV]a5/6~ q-=AЦmy:hKkDk{lEohlcklﴸN?,_9YtwӾiq%O+_)l{{R~iꯦE͈;>#vdD8.-P=`3X -6 p /A( Cq\׋3#ge˵VJ!Dw#z9~Am$aG`R\RRM)F)Er*"R(J⁈^{~PQBʬ(W@U/ȯ#UBq(V-S1wbXԊPJ>9uȻI<9U˿u!GbЇp1GI RzB/#C҅`vzV1^Ttsr[6^[ߊb` qVYʻ5L㬱=ּ}7k0Zoeoy6>?Quo _ݹ~OG[ ar ^%B!K=S_$6HV82Ֆ)vqZ@z0 fl`^< 2!QAnC5 - Gyy{,[gg&bY-#|J8?yCT2QYU( ^%H_S?sG8 GIHDeإʬ1MI>e`TV(b]4̬1Ƃ~H>DBdx$čv[#UQ* io!|{NvvP -|čvRZ)#9LO -de MH5#V)=;^ +/^']ObпuLrָ5-^˜Zuϖr}[=329=]ұ}EHHޙ KP*`RS kN^#YJe;V(Bih -%=Pڋ -Q @#  7x ~D@0D -1CP*,QL(#Bcf)xH9a [$qj>j%񗐳(%b1@徻+=yQ'LPɝʖǥfvEO:+3Ǚ{2_ ғ#>`zR ғ[Gѓˆ]-S0;9F e~Z9jQjR1j‡->sՓG1'D)IBٹi,^ Izo↓ܸa3Hva$Y׳84Miwcx|2Ӻ/G^vGf:z{Xg;ݍ/x3:pjv"*"bE5RӷIo"ᚯ0|e=bu+# Q @#  7x ~D@0D -1CPv.r2[O 1dZXgf籹hWFL#nٝ+^\ ͖ lXllQb%lܬdc̈;5GFaRREҍ;0{U2Q<.UIhKԟ߳ع)yF.9v3&9*2w'"G0!nOP@ee9>)qjzFZv+JjA7N2n/71]kYêҭLIk}5}?ZJzA=SwcחUuyֵ{7hZ%5"(FD^ռH GV -A -tL` Xv`Np<?p"H C"!q(s9$$ʑG -cI\rX LxfE$)$\ $.H\X6k ˞R ɗJH.R ^ W%$(B7nwCV_ϘU:ґ*_?Q=*["SFёg{DRt?qm+q?~wTNZ6Fg[6Ũ OihLnvK8##ݗNA^/{v u;:VP%1t4m{6uc.{uY[}ɰu9v^Ns7 {7ކ=S-{r^PcviǡyESÕ_N[9=X}8.-P=`3X -6 p /A( C<>jӘךi}}]q{?]LJ8MrxڏBaPBqJ(8%n2TRvjJ(c4gY4?q߿N/ƙC%:ǔǩ,TR{s2(H Q,Tv]>s,Tڼe2?uP!#Ey~ s)1p(D8[8C8Gf/@3B0egA 3s}(e,S4쮽+>\=sݪ+ >093rm=+m'B!qx#*xz5qw=B<ݟo"ލ/<};;Xu,W*{xB>S&镚hӷ4'j40`VXp\x D!q8ಁG4jbF6zbj"mIJX[w#4o3P3Z \$ ;~hUVR?.VRQGQJ~צ}c6Μ7ܫ2N0de2LHfe:fh ^[kWW<4 +/īʣk#Td)JMw3{d% -x^ -i"ew2E1] JiMŋN7u$ڗ|B?M՗}j85>]* ƖөǘwJle=8۶w{^=Ur΃%ס{WzjL;X0?8CX,*>n>N+.聃7vJ~K @#  7x ~D@0D -1CPǕ1DhQSְMXِ\aw#{yd. $),O~T -*(KU^R -rF(RFQ -'^R {nj)TNd)|*+ RlPV**+(V*~R<9br*?7X\"RԂR-'^ S>?Bs(@_9_hMj(l fjdjpYgk7`o%W iӲԖ/M -OL?ujFTwJmoLܝ\ิ@`0 `+,8 .px@ dC8$ZRD#LxV*YɶWllk"'s0\],*ʾP)R*(E)n #^knrqC+%)#EJ(* *83;UBa@(Rz B %/J< "%r0yB-J]UYP<ЇpF[/32+q41y9_FLr3@rk4-h7TmDwr)"ñc/̪BX5pEֲ}D[qm}}c},|=j?p=e]y7!Q-JM;=wTN㟾Hۚ7Hͧ]~ĺS*8F-P=`3X -6 p /A( g$H)"[n -W7pvam9a  ĵS@{T\Q9>mj]*S#OT'FXFђ-cr}HR}*듵JL֨Nex_%&qh~;aLr}YKg1ٛ''^anM.L}i#O4!]ҳ||V⛩,_nzf03=Kg9ذF糛In{?yԱOjt;8}CLorei ĺ㉰B穀كq+[˦\CܵOm!_m&cE5k'_i_AĆcJ<4ᆯi"5D[)7@@`0 `+,8 .px@ dC8 6('nݓQu.o]~Vv?uYf{bk:4c.̿RRsݶN}.&Gq3yk9kg' ?o![WKkDh>ZDm_#5-A$\DڛHk>Aj40`VXp\x D!qHJ9VCn]h*${q f<.K䤊n]_B*Kiò3ci(Mc̫iJzn/c_.6ݴsRK}C!wkߞm˛Y=C2*v^0%)U7DWζิ@`0 `+,8 .px@ dC8$E8ے:"bM2G,Ql WE5DŽibTNQ(J(2UBqyPR%5P"M%=Pd BY?p#Ŷ81*دR?J)J)~,*d4*F)&nHqcrO- -e~ FJx{zFI + A9:ے>ӂ2 )GyL|PFvVz#C<O5.6մs .gَt2vcY\Ueq(}JOw!oSU?CXjq{]ZVy)ႊWo^2=T<ۂ:Ѓh`&0`;' 8A$! B 8H{ ^Q -aZ6Q -vzxr#Eˮ0R+C϶ҿ\W{<*xt4#Ř:rpk?S;K%+[+TBH!EʸI%>"QwB(iS)1>щs#!oMEm*t/Q[@(!P|%eb6 #;r| #e$01tPַd(ݳBmlU. _kL"QA -cHqdDkl{?0DG4wmO8_4+f -cb~=\/W.yoZގSo%8.-P=`3X -6 p /A( C Q=g#`5Kviബ?Bx1PD\qS:Rh*xI ůUBx_ҠMyjR ǙzǟUJR -ʦ/S)*)*X|_~~bJlJٻァ=܏b(?nCJw&wua1A¹)$ăCYbrB9t|AN"~eH)hS)MNmTa ]CW8C_ůck__X'֮ڰhl_)}򉣬s6<5սu q۲x* kYm^J\4BuN"vU.˟ԄO=\}iMthb{)RpZ@z0 fl`^< 2!QAJ[[~1uv \AXډu;"mK}6n"zm k#V"'bm$k+HSɊv+H7)[*{*Y(N@TΠR˕4w+ F=ǗKKs@(m7߮[F {?~794:_Guwцe?>J3!s냴q8Ì0vs9~B;X\iž}uO<<۵m{аjqSv W!ڶlzBǥ -tL` Xv`Np<?p"H C"!q(Q Zc+lzW)lt҈{GF4- N? -8%O*L /嘢b -oBeOrcXfu1f --FYW}|_sK<,L҃`H2MzyFϳk4NO8ᘲ-Ԯru+i}sTG6̩zڸmm:ܺtY+^g[waOt9}s]ŵ4wm*?4S7 -N_-~..^piCӣW͈-[qi40`VXp\x D!qi!LKd4dZ֘Z{|4Iu; {ck 0STJ)rIӸLS|Sl5b)n~~gS EJ(d%*,>*7IexnuK]6S@(H> HPT9f"5w;K% {FqA¹)FKBfV/'S̠/'$rقBл1RpP2zBՕN&ŧz$" DRlmBR(wIbH -I0O!n=7IIAWh3{i,勊9CQF|VPْ`FF͉\6?tEӺ)m>]R5'6=Wbjڕawj8A-;a?Z7͠[W18l}[$WV]bӸVgZn2l4}`l-O +OakפplN+k8\Ee?G^| -rc9l%`aaAfPdMɎw:CR J۞qOkK>H5XMe%ťy3&rr_DZ7-$mG7׼@Gצtg1ʵ3ʽ Ƴ һj5\+/ KgEQqG.W3?axKv\m{ubfsZL;ҙxl s>t*{!DtdR#ab0)+B!I_&>?.s(6ٱX0A>3#q-3fpz־wvnc\2v{d3G;7]n3k.AhS໻ R]d6Rzɠ,a7obF f XvNp<|x@ dP@0D >M8Yc0W, }\}/sUnp/W J0V&.5pe$7iA^6?TNjah^ޜr H0x@˓s ͓gNL*ZQLE1O 7_0%+ w)~E$?F*ϼ٠]"O7 WP糂|aQ )’`,\vA35M<*Sx&.TT4W5 MM$%ڴ "}q5OSO]޽hX&:憖<};k==(Tu NVn-J݇fW'H1.#`3X -`;' >CB< 2(B"p51!Y$R$6ծҊ#ɻG|_(! - :Snj4EzVyi#Aq})ߡX2\R ML{{Twt{ +T703_GGy'ʟ")v=(ސG.#ERlZɿ")1L >•JϜlVI0ȋl6S sBNgg ݠQ5{'ZRjZOLm/Swl,SJRGҬ.;ڛǁmΦӔki}CƳ|:t7삅y܉m3u]oΩRQToTY/*٣`\F f XvNp<|x@ dP@0D >.\4Sv!gźe$~%lSy=_e{Iqw$Ż#.4h%EؑQlmjĻ6bH͑tFKeՒbNRuNg6CzIWٌ:IjCn?ҙ,źX….@bcH[)")f!)^OGRܣJ_e"B *1=HŃ>•Jm, -b /pE`QI0'/`nh@RtWdR2ո5oN!-/M亍$uri]u=eTGΓ(/)}mފhL_UFM 쉲se3։BjSǣE՝]ɤ@ `( @7x   Ƞ -asf[TtCAM1q }LCaS}}rd؇C')Nhe:I1I;$-)]5g pA6B58(ӂNPܯ5:Aq^+)mjР<SR|l$EP"%GJ~J([|A -Ee=K2K8H 8$gd -BAQv 6tw.ߟ Tc]M}צ7HҲYYLR^!5o%KLʭ$]xGtgtWtVz':_ uX<3ɠj:QTw0ޣy ھ*EQQ,H_e"ا(Vת1{}+#oX.$t xV( -!}A{N֏Rs1Ez1i9X*Z>_J:k?RNҭGtgt#k"(Z2}gߐۥ@EG4hmZwO{lr\TGTWϞ/HV0,@Vhp\x!!`AP! xuJXDBE!aT\O܌!QQHOXY:A1k᳴xE 'u*Y|9e#8NktN8w}c$$őQ 1%MUHJnSG")V{T&nY?pJk~3#\y/13(%"4]dbX3XۑuzƕD{w԰4]EZ6WTuKd]O>`[ӝm_Qr$W{홾Ûe:)h(<'w(TopڧJ]rWQGW~xbeL` @l`^`~@XD@TC.ތ!$JJ -l~3ߌ0~%/ij=B@ I>piIqNR8UkqxJQR.F%E@ - N"-)ءi+ZMNRd")9Va1D<o>l)>RQ n\A{q|ܿ5U%n]q=#\1̑ߊ.eeH -1Ӣ!Qg/)2lpHR#)Vt?L R:i6U斵BXr$յ@1־uI׾4ܞ\8ULM_y4Z_„@_onhgw.{d=;SuaaӤr'yg'<3?7|`\F f XvNp<|x@ dP@0D >pbwldLNG&j -)ĵ|*#D†QSH FICvXK -J')~R\7f$-)IE)|}#uघ%:5\-)]%EVS謑")zyߡ֎bٮdR8HE"a7D0ks*N5ŪAJ#'WPK2bAW\ `A%Ċ[GSP2)N5.\O'LO'-cIi־ol˺~F8NԤ;{SNx/[G3;*3}MdeRde4Ve}LfUv6]NT*O]\F@ `( @7x   Ƞ -aWC\bomupH -! -_ !?j r8%EH')nHI]ZRю^|27<3\M1PZR O (V>\v|X#fǷ9@)bK_1K GBI1&3^_PStu[#'EQqfAU,b`'J6Ƣ:Oѱ~cΝϜK&ʼnTcjU~ej&{kHK"ٸ6oE&{.zq5g] KPST.{3iyXrj=-rٲ|;ưP_~X7M^+_,1CvFdMq$P`؁8n8A$A}H -H@ VI!~T\M$E'XcQSp C٠VS:IQ%"NR}{=>G0BCN-)ѩ) -NRBKZRI)O![|")BُHRWҥר)W()Hs3 -Ō`c~WZ'(zGןxh>Jv0]_@P gI5RA&e -WnDP\pGRAq'X> f ADv,3%(¡kO;E{=`i_6-$Ou_%-'Ҭ[%mcϒǺwҝ_Se)wqznj3}-})P 鏲;qf;OMVqNUO/Xvqz0,@Vhp\x!!`AP! xn%d;C)$w QRė>Ij%/ʒ4M /tap\ڄGtJwF] =8(҂P'(ZEѧSQ<E-+tX#}a^-z0@N 7}4YQ4 (~8Y$lޑh@Q*19aA -0m%EBNqQdy^, fpl3JĘ8ttƔu$Tc|(TQ—ie]c֓iToo\mwX3itt{ yO3| MW~h5=4SQnb_ڣ -KM.]t<ľp 0.#`3X -`;' >CB< 2(B"p:Ӎb)+Jߦ)2>?01jpjn’02cA(XoL$;;G9{~\sdRN5h=1SR\4hx9+HqkbrUr8xN|BziLdE8Tnma.הwĞgI߉MQR|6U],)0.#`3X -`;' >CB< 2(B" -o+EFR(V#)dܡĶ+ @K$B!;ZR\F[K0IϴXG,)b#%Ec>8)~%B5ZMѩ)6h>8e=h>>6̥'D`HdqCUM}$[*{*e);oGBI1XIaFAL, r31 X̉e )eCk.GxU 5ͩƕ/D昩l4˩d߅4LBmeX2.8:ӝQٝMSLzkU'dRvY4t(Ԝ5o(ԯvs+J=NjcyW+0,@Vhp\x!!`AP! |d-<99ASK\rɌC p! 'nК.JҔ!5ť4ZLݬu磨)2.w՞q̢$ݣ{)tV>fiINRH]UkQSr>I֠YX-7:3-fwVER:4#Fk1Sa}x |S1;d!b!ˎ5xrrzrr1dџצ;v].9DKuPrS*baP Zj+tbÊ kEDDDӑsgOgU$ΗDz}[+Dz4UW͛qyd]bYȞ{w̬yy9~ss/{~_uv]'?yu;~'yw_ ~)x KO}{39QJ`W7%x@vff@Bl(=e򴩷\7CZEӖۺ'F5qMm^9Ñt~fZ=TgmezjcvJ|Ff{IZI-ؾq-ĵ~XYG<4ֵoOijOq;՚pծȹg45/6kph8P-P=`3X -6 i' < 2!QAJ 5Jẅ́i'VbG AK썄=@F9ډꉻxjpo#B-$D"bFiQt?Q 9t?_gP^?1OFF'OdQUɦdW~bҘ1r̩riݚQ(f5 -Qf%kF5"QQͨuqf7^8?+^?X=^/<^g+kK%Zzu&י{9&^ӕjRޫIy?If%߫vvrJx E57_/QKaw3mnvv4F66 _I*qA!C^ALx|ߛӃ`f0(UUiz?IUdC^4:]qN_3t  -~gK!W3127f| +'0(1~OT( -v=nuewk(h4s|,XccMy6W|9YV==ζwǰ˟vI;WX]{?ڽ/߾CSW6"<2YlȷKQyrJS"m[GWM5q>8.-P=`3X -6 i' < 2!QAk#&bMBqBGY+yx'>~^ -RXA()xKeӥBU ŋ$ -2QUBCqCڠP ݼvCg+AgͩRyp)ҕR&JqLeX?_~RKPe(žSX?p|dH]OK铓r0uǡ~xG.ʹM> P-ENz #DoP -?"fŌ?'*`;.<2Fwjߛ:s+3MqƾG8SM--XjۮS!{[2Vpcӝ{6W:~ Wk|>Z*>_ߢjbrdR -'5Ҏ՚Hρ5O=m|Sj:Ѓh`&0`;p p"H C"!y(Dۛ\4}10DL{Xu;#66'*MD\mFD-¶oI8Ǿf8}82qW#k5/jOiMZx;>Hg4}5nIU -tL` Xv`! >D@0D -1Cp*DN --ۉk7o$r87U;nDj'r7 aUJUUVUMJU)UR_*j*ęWVeRJUv+U^eVVrDUeUsq}Erޘp}x_.Xռ!'_ SW"5XQ5 .TUD)= b-C^!;M$B 'tY%U9=eUC[4:n~8C+M~cTxQ/m;e2{vC$}73Yy)Q:ҺB[[v=^DUlAUCj:ԝEUNB]SVAUVT -tL` Xv`! >D@0D -1CPm{*DM G}0]XOL-H,Gla;U&CL܇O=$êhR^ɥ*U )U)KKUAU5( K򡲲)4 H*YyIɊFeXdeۿ8+MyC +UVnubdDʕ9EXF-Qİ2wBee3|C@r$2 {A1`Ÿ3(+EJk?K +o8AvF[urt{~Lx}Tg>,NmVs+e +Ms?pV"+bXY%@5VΝ]:cXٱZ#T6` f:*MMd hUئ`(Ё @F0,`؁4p\A( Ci&چdV40MK6b۟VM5mIf8W q7O#\ỉ?#+5CgY%+&eX deϔaeJV(gCUp[ym`U(UF*)UTA*' ;UٽL*PPW>Q6@Y;PHưh X)R׮ނ…J00` YY +|F7[9|vvFl۰EQ[-cZm+fJ7maC>f6B6/יwҖ%gyxqG'u\(8+ٯgeOg4_]4| 31]*xfS;Lt|XԹ(Ё @F0,`؁4p\A( Cqh9$ħ 8nHV泻X`x\>=GtI. {~ - *(T`$C9J(S EiX(:ńQq20*(sp( -'BʪT(.VB^ɯF8WjVlIW"NeE!}|5Lf=I(%f]> `pP$l YWKA>%D@0D -1CPVQM #L1S1W=Rb_G?9WW1q/'<+%\>K7D*#r. =sTRrP>D2sQI/LJJ|#HI)#C o3dҒTZ7%*-2te e ]w)}X:k:8c5k6klNwlj_[k溎DK[xђ=hɹdKrEKzT륭%hI_ђ3ɖԭCKVv(-Aj40`VXH8n8A$! B (D[l"0K%Ĵd7"ֶɔas[|[G̀HYY|V7+$ 13BL>=P9eӁT6['hݩmPCdZXWŚzb̅&zk5Xܼ13n?tupǃOM|kIGG[pe/?}& $+$V)(Ё @F0,`؁4p\A( sҿ)"b(&t.ac!1B=|b+!2–$s8ˉWO!narySYHED.!?D%&=JL(1Q_|STC8s<#,WV7}*1q+1Ą&9?EL2F!&ϜNTcFL"uy+M:gW1ߎPEI_xpb"& ovN +de7'!Ŭ,!#>[ES쵭bR u4br`ob 2+_b]ϱ3Խ?|Z'ݙiﰎܷ^W?i݃T7\KIE!fg^'w!&yM!&ŘMlҰO Q @#  B8 .p| a@b,.@J -*Iruиȴ"b#rb.AF8KkNr&)!<-r" /AAR*٭Q(JA Ҵ*!oL&l!\P^?=rB@ -ӣ3bWB:Ѓh`&0`;p p"H C"!y(.PzyF6M.Ye]w#(K>Y; ;jQF -eXר??zeO̾[)E\(ʪT)XpU`U2ARXPJI'FJq[ye6yUQ#ltu`xxp߆)B>$|s)o"= J9B>٢i}ښ/V%['h _ RGaU -q`Orǻ'8*)e¯5"wbU}uRKV%^"X7hšWHʪǨ -tL` Xv`! >D@0D -1CPjA5s 9&8tyeu->]jv|_7_,,gIX,L4 TƋ xqJ5.BnzP5PTcPh-LU H2_ -exa,R_KG5GyGH]t:υjZoiI N{3Ni_`} twZkzpUo5P-IG-PkIITcfTcUJ5 -QBT(D5 -QBT(D5 -QBT(D5 -QBT(D5 -QBT(D5 -QBT(D5 -QBT(D5 -QBT(D5 -QjfQ%=c1-0/k.͵/b# \ĝO<OJ9RA=.Q*xNYJ=2s\Rqv6Ɲ%cg8*W9UFũx4 {O#9^[8I^[~>9ww*фx,-)g,N6WÔI*vًQE<>v.T<~13Ar2BVvV$m7/bQHd G=q|K\mA԰aC ,`hfMus֒m3l%mN:]KoK.XY\WaA.q/RG|śVMǛpeuR7E- #^!خYZ(Ё @F0,`؁4p\A€5ӵQA1sG=fgw1Y9EdYvڑrv/c($=yN8kr A|nhnD9nt=k0T7>I3nҍJ7ƨ /(t#_erX7y1t\rG J؆X4bWa{Mjw4J]1j.ЦF`Y~. $3WIJYɧ'g~!4xҼqf;VjSc_h>iZ[X"ڰtɟhƼBtBRi]_5Kk3m jes)pko"\5Yܵ.-I^iJ8xjgN65p\Z@z0 fl`Np<x@ dC8$JDGQ0mm=ހRΨwO| K GJ(WYD@0D -1C,ѮO^QF+%ɓmK.$YW!<\_QH5$yQ©ԄRjrqRܭR5#$Q0-.3W Qe3j'κd2W^y_&+ RsɚT(RUP:9j1wrEȯ(c> unB9eD-PF0y dH|v@J ,$jPzWwjRyKA}DW՗` g^LcgYcjtۋ͍ofZ[ޞd;-Nm̴7YG|<9q7\Ԥ Ԥ뤓QԤ959juԤ{eǨ -tL` Xv`! >D@0D -1 -5Yf1 Yɋ6ov,qBw'r_",JK:~jv\.vvV:,*Gd'>c0O2L"LJ:UDzTAD*鈏`tP[dDrOϋt<)$ʼc9*,o»7  H .gWrPv0G!!y=z+'A_n/3usztkt%~}Vx5?ض3mo{oYsnVVy*d2Ķ}*Gitޗ7סuݎ&x6wXz[OnO!ȧ^yR۲Z9&ڻMWy>T @#  B8 .p| a@b4mg~tucc; #.b$lk:xډ0 ̏V"uy w!JUDe#VJRRR#EvPUg7P*l\e PK@2^*W*ᇆYHYtUUbyuMU9|Uw> :|UxA -Y@2D+$7L.3;CWw4D@0D -1CdPV}i.1"b)%EBb%<M)%E{\['KJI1JLQF_%&+[A%&WdW-ѸUSeDyMY*1q)rt=F?lÈTLnDLv,Ku9IKNFQھe|hu#`̑r|?='MLfz0Xݜ)>RIgoEhۛDW|so.{Xf㬱qk:/[7Nu:.g޼5mQlyyxw~bρҗoӊ#MJCD,:iCK3$-/wIt=$kyc*&8F-P=`3X -6 i' < 2!QARBqYdXB>kI޻,,}a%;&+;O !?SPIHET3%!>$䱋aN&lȫ#UF%! %!G=$Wx+Js A2  ^1/z>' ^^t_|YY$Hi_R mie~qulaŬ!!nc5jk,`{YˉW&:pח;L[ذ.l^We?=9QVK|}kj WT2m"‰uH\:u"E‡%s={HW)$8F-P=`3X -6 i' < 2!QAh )% SL2ߺȾRFUB܋=ķ+ |Iނa$ۅC"ת$"įk["a&F}x|j ,JIjTJp+\$R #lP+=\ԥ! (IRJrw|Hߖ%߰ U-k'VnH]Ru㩼ukqp43YqMXSF7}8r֊q&ƾa N\YE;*Nwxw]U׾eSSc3b?nKjn\3%Gz3ǥ -tL` Xv`! >D@0D -1CPd nL1̼6.2J;W\rr!A_.5F ׳J(^P *RxU%=#=EO̩YB1K =*8nICgU+KFCb]X|ԕ#zBb+enKW F1b,݉JPk|&bh/wefY !rLo(K{D /V>nˉRkij]Eo]JN+ϝk:hh˹7ֲVcUgز7iͭ<-Z6~J=S;;xdtͭB+vikMS%S#ψ66#ֱ?uK @#  B8 .p| a@bxn¥ >:>`v[|Vٹ'fc1Аשϊ3,ET)*0Ķ*#zR`[cZ -ZfZWd+o63;9y^W w}ӽv7MG kDh"[z{/ D%vȚ=}+rQ @#  B8 .p| a@bhg7ð^565϶`%r}{q"9Y\?G#Ηfɳ](ո^e_>J5SaS3TcE#[^0^,"8su<%/D =c YT!W KO=ԕOIh\/>iG4ʴuIr0uՍDk;!x/҇D7|a 2b&@/  l>KG>`}w֗l :r7y6oM5ee$k[lyʢIi;J޶8 dϙi/NNZ;S{"&٥Mo>5HCDnmk8.-P=`3X -6 i' < 2!QAEL7(-a/m=ڿ9cXԘV(ƍNa'T(rPh%729|NP."UNT)leb2R89pjikivZ6y~a[7:jiGOZp>qTȳ)/pnKKo[6N W=65Rsc%*5M:Ѓh`&0`;p p"H C"; =ጢɅRH֘cvKt=a__ -I PSJѡ2R̼h;UtJ)v*;Wg#bPCǙwJ)nR)]J)~OeqR_░\iG)x(/ (y 1|(R;4JYPh}5Q؀/ M2B\ + eHA༉t+fes9b( 2b&zHVf6qnck-m][̎L$mZ3L[NwZ^g;xa3δӴ װ{C)yd`t[g'߶K},福>̴H'oU?)p\Z@z0 fl`Np<x@ dC8co'A0SDLdZ[.?0 "nG)|DŘA'Alhʅ5*RVP|)~X|{n3ԝX -Peب }#ſ/Tڜ#ź>J2ugvK#.KK)_՟"ܻX|l .H#gE1[B yѧ(zC,+lA2r/>oєؽ'X|Xgе>E[ X|l'iuPg-;Uٶg=3l;δoh=WGuP#{6uoܑnޞ,Ky]7k{F̈mFlבH:Ѓh`&0`;p p"H C"!y(GQH FZfX_o_|ĜQ"mK,diЭRBKexMY|}J(."w#8[svɓ ;g~U*<\TJq8A~8[u('u>~䵝Pezbx*]Qj9FqpIY !gx3xI(邨L'8ob7Rx) iaZ(m~4f}3ָumhsNu8[͇L;uvYgq֬ "ٳwT_oTy[5vt&)U[F6=zhѪT)p\Z@z0 fl`Np<x@ dC8$EF?rC,ZVgw.s:]ϓss,JI}a0(V)F)~_*T)S)<Ňsw} (ΜEJ(檌_S?g nPY{8ǞJ垿wxL>kWbr01RĦ &.H! T/D."+١D0# ~9g>}P*͟lk_7߱Vdt&yepeH3bRqi40`VXH8n8A$! B <ć a:h-nk&ۣld7$ND(D9~P4ZqdyʉbMިvH^bS̞YXT)EKRQ|lip),>C=NjC)O9|M)JѥC)joݿHnL]ID◱upaJ.K!eY,1)yYV9~ #E0SH$ ޥyj뉫$Uc Y֏5>I닟 -t4m쨠M]t-(E8o{=L;=|0*9vuɞii\Gt[ t7˧lwFĺ[bJRิ@`0 `+,7x D!qǙG. L191M;xIp J!|XA).WJqHQRt˔RTJaW\6g>wR6LN_ytRlSao,_y8U3`Q[|\עs7bٮ (S/Lr0Pb⧘)f -v+2 o( -Rfț2R)/eަڢ;S(vvN zp5rO֯9)wxm~e{g!Bl}2vLwL\=눻xZݹZ_>٬#:*4r'5მVk"g7>ݠ*j40`VXH8n8A$! B < D[lo&BWf1n&6b#mĺꈽNĵۈ@Z"!bvkɐ_Tyè2㕕OJ_P_QdngR*8TeJU-{hJHwx+459xuru'!T.??\_DU\> 4!D z3BP7dJ9 aPUb(`jmjU#Elm[+h6y6ә˫iK[Nkٮq3 y3kѰlp>voV5M>;4iBbgv쁛ֳSg>)Z>#zmFl}wj:Ѓh`&0`;p p"H C"gI[38i&Yxcw+yz>-:%(%*+J(&J(PJ( /,I U85p[e_sD? S82GP;T4θsуIꦲOEm(QrUTp JA_؝XP> I$8A&23x|6F?ymgŌ q1:6lѬ96qJqQk -wݡ36}XGYG3M崱v=NqK=Lk=C;jk-hw] {6T|nbKvӛ'[N4D@0D -1?n*F-MGStI=q \ yzAw>g(x}{SܴTLˌpߪhAϥ `P@,`NpA -x`A(ܞQ2qTy km=␝ga=7^6I|DDIGhԤءQSի4&ԚK72)CMw₣6VSc-kjRHIw5EX]}5ךFMIqo⃻ {I DbKR~IߝIW])~j-^L~YN(-7'BObZgE.?IM #}H߷Fv64.5i+bKݸ2W,uH(ৌ+u5ЮϘ{٘{R=o):e*e[+grUϧu&d[gfE͊;^ `P@,`NpA -x`A(}'~:"8$S,Y[fا:f93`dycl%/ KwHT5)lQkhDRI[{ Iq1c6[>ߩAѯv^62(z!_AgKyRkREMgu4'ŗ9;6 $3GwrMAL>&o}7>+.RI%l1AYxQLRsrF.>l4Hlj87^F-0*Ջ)ӑqT]4e޵83κu1W8zΆ/hWNה{SJy=V#_l5=~(kW"CIyTsx\nFxߙY]s` Xvp\n`^AXD@0D -1Pŭ (X#o)ܰ͢g>J -xeGbGt^Ϭ()LgNR]|jPw]3I 7ܣ9gcjPjTujPWjP7rdEP;L;9k"')"(ׇIyqVY.RE^`q!nA>9iļ< XXklL0|zqTzBR90z n@EG;~F63)(56ii@=@m/>4erƷ8E8)&̐wtόY=*x.=`#`+p R x >  D!v)\Pl8Yd8xkOA1N 5*%FP\=.I6@z A.tͽSKCSI[fKՠxMc;"0{߁bUe$>|,EPaȟlg>~K 509yfcD=ﰗp"$\` C(-g`+3cP}B;>]%F\Y4xLԌg25]eSKJIegȕK2{Z%YDP@` -h0`;8 .H70/ ,p"H C"(tHֈMK鋷h)x!+ -7aQ8OAaTQShjP,Vh3\r^{de;<(Rb@#({mE:~cFPHcQ~@` -h0`;8 .H70/ ,p"H C" BD#uJ:EvRGF}@p9q4%QSW1\}ţ-TnluÓ^5)\;<=YR?o1EOhOU.>^,T~~cIAv!)ʤ4&8FV4%\K^AVpCRsPNjYJ.Z6?muwE{U(hx51j˞7ZJ^h-#eDZ$kM'ꏆy{,vS_ oi%W-+uƺ:n{:3>-GyGUJ]E]duhT$&3X -6p< 2!Q( ?}6V"&º턣pn!\mJN0-n"|D`!rBe5b%O?Pjx^rIG^׶1FGќtԨQܩkAx4阒[2\5Jqj^q$>>[ =P%W,ê67V5NeU؁a/ -/$*҅<ޟ& -O sRsC~ T?"U`UcryVJ}ىٽs/a22>v;qYikYն} ﰟ>scM'<>{掔%W˿ -1T޾'}MD`&m_&UEvBCMbۻ FBD;""ǟAD׽MC` Xvp\n`^AXD@0D -1Pe/S0\AX\EP ,R">0>T" h5TEȏ(&@bj$W?8Q#PR(m h77F׾oFLQgk -1hH#P1ʵE9̃2Ww'4ZP@[2e2:WwˤQD6 m]pOaljZ_+|:_|\W1d׋tiNJSw .cm4碋?t+\Jں6s:`Ƿ1ݷ,w#?y{>yk?؊M ldg՛ĆmTymDt!d"ZzA0Q$&3X -6p< 2!XfM(sBRsߙXcx;%Agx>[Df? IOsF(q5uwB#62xL /"Qǥ1F}H5(I:O:56^Sc2}f G(C]>[{ bc7vJ+bؘ}JR~aRz)5bc^Ś@<6lNK+䤇|"}ٜ [1{ݱ'UEbm䒞ن)cZT@57Ptoe.~  D!ʣ1c$RВfKv!;H|D[|@$"FhQ%4j}aTuzO4?i\|h }tx&EFRLQ?zuF1u -`=}ò]'zE}~@^sUH'NJʯ2yϠxFIat { k415Tџ!S`(-$,0:lDR?D|C o'jN^8:|ƯYNm^ -G.yvusMʞ(9O7xWԧ:82۵9k8?j~JX>U*lqpo{fmh_YbK$&3X -6p< 2!Q(;Q6IZbV)xR쑼$/xEň9jR7i$ųjRnm\7!)˓&rghIq=YjR\&jRTϴfj$žgԮr|FRLCW2&,EPDRh$ņq#|ܩތ0)>!oI!c*&q -r|j -17愲r RYH{'5a")[Ȯ(cRʴ@ TRtB|f1e)gm^EZWp=}kLJQݷƔWzΗoMEF{q[ՖkS\;Mn!ofd`EVtķ<H0L@ fl`8)<x@ dCs5E$>v@FM#)H -&c'<>ʝo+k$EZShI|_MԤ\#)!)9.>BR?jR|%5Vݢ{GFR<>GjCRQ6->ܩ1#} H?FMl6j$W?0)߰6BN>$`KXM`BȆs4Vr5_q/榦<~@MIͽ܁egNRƆŔqԱ ->I =elgdwv6]uG.$ES?s]{W dobOtip[\&3++ұhzG0K$&3X -6p< 2!Q(xhM%>3oIGPS.1~-I!6 |Da1I_jR\MIaCR$s6bhIIv^rWKv5OդPܩ&E}収aa}k[(;H *)a2 )B;DI󚇽uIp}l6/[g~>'ق?/=/QST]rhFVnm85sqԁ]2YBYvX?طl5:I:3)=&۵S|8s(^6j">~=M:\!ox5+໬h7ϥ `P@,`NpA -x`A(Jۧ&Y])Fۧ]6<)&IAj W&~Y~ZSX}0c) -GX#^j~8D\XvI I5+ äuK ).RFZή4.;\eV b8n -O$>oR_/DVKt+\?\Nc].K<8tԱk-q-=.S>{ρ?y*> to#⏶ֵoq)!TxX~6rmDx][D/-DϨ `P@,`NpA -x`A( ~(ЯPZ0ӓTA5e a-%lJIG)Ļ/ -";a>uybHz94 E1OպFx$()Qtܬ^6Q {9+d;aߚPt3yKXgst1 +WyP6 -`~^~Nưk7jN}GrxmdElC -X2 c+)seeiat-e7o;@?dR(-6eGU[E5fz>`Kfr ]Xt}I53䶓e"5_%<H0L@ fl`8)<x@ dCQ w*$;㐜Ahp ὼ/XsADqIAߨE48si"(6j|9?@1^qђKGs}GjR6r#Oz5)4c~(O.AWNNld-bQt4?XX(2yIHW<|($4LJrQo\65e^z{~L%}YtCAcŢމz:4w>;ڽ3yOvl+9KHM,f_ Oqv[ostJ.]ؠ#{uҙ+trStuOt=uu?C|H0L@ fl`8)<x@ dCQ }Ry´zMn$l̈́p!IiG0xm%~m fo'IH-+ked?F^οcO/@ʾѳʓj(@Uck^-@ΪbuHN(k/}a5 &N -)[ "VtKʯ(@>&sQ?:%(Fk1Sa}x|:2.:mu]g'8.vޝ/ZH)1c1 C1 ƈB 11"")EDȎ93 _sx<{d|`ѝ]ZpGrw,Ȝw,dޖ=w{G 'sAտʝ?'`^v;B<eο {ͺ[s︺wto͹ &;~{ ҅/| i|sqKٲJ /C@&-,9+5Sܜ๿(=uⴌ0,]gh?<󣆸ৣGԮGzGЖ&X#͜gWѶi:Uvq7`ʿs)=iOMٖߢC2+ kߘ%~6Q[&CH 3'[ 2G2&0h` -67 >Cx@ dP@D!qHdR#LYHtA[.8"N5ɭ|)|DEA7*QUaH1W7?Ősgbnݯ w,ܯh/Aw2>i)v&tWگ\گگXW)L~1%~3x s|~Èao#6alFÈaDlDiO!a~6a3/aOxu%ؚ_a?8Msvwſ>þ8*æi?Mam{/Iϸe?nny҉* } RwL*S?NkPT`*|WARYc} }/>d)D!#23)ʁ_2stAؽzHQSshy2cnbaG2,f k=0YG'^m+9vߒ)^e=۫ӹKn}oIrY3J^"B[D\^TL=q5{Q+4H[I6[+$^{Q:(0,@V^"H *D -1CP 1\@,wYNĶؾQH ]+{+ⱈ'r/$E| ""/RRoAA. -rt,hlD # $F,1 -2) SdN, -;7 -B+:) -RvY u -ɂl -",P ~w {ԑ( PM3%*e=(țQ%Jx$B]yO6!F{~_…)&=kȡܴ@8"(H e>Pt`\AYcQ/8>B;*hK}&zՓ4aZvۑմcUllZ)fc ۽K|mښݸ^mڨٙ˟.yaXkTTgFdS͌gĎO8.#P`3X`;8 .p )?D@T@bġ(F?!B gR~ DJȅ zS0k* -Q-dAIQ?(G+ksZ))I+/R)a?9d)~#c#L~ΨQbON(jH9+sD'KQû@'q~BЖM=Q4B#ħE1QB tA2K !>Ǒ3R)5 +(9s|\yP;4ʸAnc:6>N[zi]A[OW"KLiG'+c\N1S 0RP;Sm]o}:#x gOŢ2ݏLm/sq40`NpXHp!< 2(B8$eqB3˖(-3uz{1)b(C(n&{cR0˂$ƤrT Q:dPhxJbN(HasEɠ#Eqmެ@h)Kt5RS"rH"R\!9R̻VƷ2e7ORbQr#(QNߗpaJ&=Rd P83-bn:@/dg -b8o@)_gM7̠ڟo4j/ʊ!fcqZ,o۾r1X߀9|c)Q%(u$eW{՞'/[-Ŀ*CpUzvA&Jޚ#w,]mPWԎ*CЦZCtײ:C ]?y8P#P`3X`;8 .p )?D@T@bġ"Ʈd=1K7 X7&b@H\{adZe_' :WL +&-+qrm׵8RjGr lغ1qV-: B_ ʟԪb,,*oTBmTesUEUbQT%hDU%ʗy5åJS -"T$=f(0,@V^"H *D -1CPdc P͒%BK9TsN%clp|q7a(J2f¨"K/A E£PN(cq"; 4ʚ8VP+SӴх1Fқ9~3⧼cu2{dxzD} x4$>13-B]vbY%f} !KἼ@#1ɋCI˖yYyOD)0tvQԞG明 iK2]fhjֻd/k;9g1O [6ҽRϚFvK^57(=mg&Z;Kywt-ܠzzrFdwŌ=  +p B -x/AA$A"!q(SPYLg1q"n:8I8; qqRDDY$G:RJq6FVuJa&Rk#xR3Rq()n3Z(> Z(P\-N~>6 ɕEb0Rz}zQbP\QPWR>WF8&F9GK@?d(dYN&7 dဘӄ'ȡPXIk:vd(6=ʸA{;teӱtQ Bqmmv%cVι1iRў'\[MwU.WG23ӄ_wl(UWgnPZwMU˷dD]XOC28.#P`3X`;8 .p )?D@T@bġČqb9nIL CqF][ee"żQ◃2dqS:iеǓ:d(;ߪbQ -0M,ƙB9"jX@ ^-aP|^?=V!-X{\u_b"mA(r^(Tħ(PQTOL=K@Eh"$rzv -2Jy9i9RjfjHyfa펛V|<ǸTϩ ␹foijK1B֖wy[Kc;B 3DuU= P=OL꿬'MZo[TeTev bo9rCVԊUYuY(0,@V^"H *D -1CPvcGb/K;f?[/"cK\G{a; Co? i;{MԶhЪթY:UPIh,Qbr+n*-qfYuv4YJgUCm2Zg0΢wU9짮1+H= Q?3^*OU aD1.%\1KM$/3si9Tr3p =a-qZQMT9u9ڲa}%~mŕ&{y99wqw k.RnWu,ջ՗?P;-XP7?;]8tt=|=E꿘Ḍ@ ` ' ,8AP!QAzL5G,Si}SSS\}dgx'a-s}p?$iU)_d4Dk*} h1}F*J.T!ɧ 9;;=-99$3k[Y~폆x'yۮګkC kV.QMK=W{zb3%[)mbD+3mX?̞^,fv#\'^9~ICG[@@ ` ' ,8AP!QA$F۷Amĺ:}7qgq ML x#Lq;~|"7eQwpXJNVK?l:Y6T?tQʔ]fQ85]qF쟕ZV KjYJ0__`btayn5߱Y$Q^GWb5ۊǧ0 IL9MH%O bZsá -I7"+EF:iZwt#$kn]Z }'xdm`dvn* nj)gwU\o]}J7f?B"-D7.7FMۛzDvvo%іH~7(0,@V^"H *D -1CPn3V'rXJ k]`+%"Y@\E]D؊DKq[J|_DU_,kb"eZD=}#,ZHRrz\p`ٿ1X;Rӑ^e: 7jYӑ~x|SHM^0ބlߡ$>EGn.B?kRFGj} h< )-;-+G{,9NyAsԁזb!\Ȕ}8Xc:6W?B[J5g*ifMmPA6gBf'hjq9°or)NӞb7IW^wnW:M_=KܽuT-o|eSsזּ5#vɓ28.#P`3X`;8 .p )?D@T@bnSnXbHVۃ3b {ezA#(ˊ*^u.-N(i7ߣ"1q,5d(nQ;X)>0%K+Am3q\M6:fw^:_RWxm)Y-z+ԕ?[RW8By9J?̻} jz#'M2PɬJ  -BJQcǑ5ɉˌ-Ԫ0q¼.{kYc\a{p5:3ydxW2]0ǶYzIOZ.ӛIfw"&+ ҆}qyo-&vL`lq|;&3gkKqFf Xvp\Rx~ -( C٘j,0/̣1Y8nq.vscq cmy,ax@^SFgwq2fl|k`t1va8Be[8s\ I O9ٸTgw-#%N6avqF|a?p::ɔ}rg9q2++/K)¼30V3Ml4t5:Mbֳ̚xeZXk:ֶe;6vzwνY=YϙtvU߹p /P)%"mz-/#]D)oLHE{e{V[@ ` ' ,8AP!QA -FuJ=1K5 SGĶ[8k[X'\%.'Z&jo$BˉTCJ jW,D+WsߩS ߟzъ)ʄa "E,Jk?touGˏ9dyx>{\8ꧮhoKǝ3Rt-w_S(7#( (Iɛ} &(4edB8GJ\F'Nd y9]!1OJE95KxuHw:C]˸o&W,MvPLFwm/F3{96ֻGZ˸[e&Ӹ-5\{*_U2M8LqQ->xrl󩑖5ӣk?;Qt(0,@V^"H *D -1CP8͊ea&Y#6qDﻌLoܧDž(Iqy2E~ -V S:I ?d(TP5PxYeѠO3uC­afm!?9%oaq6P~ed SWbB62q Fmb ST+3"T(5aBFr]zc/MЗe iYY!1QԀ(gyHRE^oae_o:vYsfPo4=/UώWm 15%[x[]X{ !G]c!q6?֔( -[4;6q+M܌՞ 47gI sm6(۪ SMڥuئoǁ40`NpXHp!< 2(B8=G1vB7݌Blf}7=1~xw݌E{ oh컌(Dfhj4E"mD*èJΈ!Al8󓹺J|n͕ڂ!#FVt בm R #iZzqo\rJ`8?~zPܯ-:XP|Lǭ<_Ѕ6H]TܩCo#[O)kJS -mQ*(} 2CNK aBΒbZNV ,>/7| -u7Օ594=1=x98oޱ]3>)4[7O7>8}rk'aw0[v>՞mNc8Ǚ߱QCjvjPu HU+ﯩ6(+ jG 0D>iݾ69@@ ` ' ,8AP!QAe5ļXV0=DlNs quswx7Fo o BKÀ:NzAiU0 [jثO:Um9?><g"'l7tƏUU 0è?r5?Nbzc.){| S0~4Fm1n%\U ?#>$1+T!|@L ҲrB3bv(Kxf:Cͽ_ސOm|}}:Ul71կgVd/ynxu2cճƻp]9vգSvtny/:_L=4~oKD8~ RqyD-~Dv,i#K_&Soj8F#P`3X`;8 .p )?D@T@b.HgͲ^̳ε-/r9oqwpwzo-!OEʒ+ vGZ7vL#P7=uc֍n4_FC-[6N7>Ԗ-nӍ? c" 2lF7"eAXXn%A7,*OU7B] -ʚ5nס~_6MYyYB --qKx^zng&6C22u^ʸַ˖QƚU#t ˖Xx$]SF3'G[wӶݟw<@;v{Ƹ0= ۻK~TT:%ލ];Eؿ/򧞙&)zxT_; oP<=U=4#r]LuM.[p\Ff Xvp\Rx~ -( ]CM'd{B;ڷmrw/ar#RTo Sc2viC'/#65x`uh*<ӿWiN)Jq@g˴RS0JwI8q(EjJ8kOD)򮗨@~Qb%B-Q~~_…Z }%=7;;'MB -RNZ@R̋|&KNaRכbzc-A4L_O+UBLgu3c+6+$A lճ$eѫ=gΕ'M[NaR} VevYe176I-s5 gW֗ /RgMqFf Xvp\Rx~ -( ^ݾi=Ĵ F;[8ۈsmXǺe- n"| Fz"'J3Q7~ZuC]O ydY!2W0,l8t_QQ*@ԩuԯU -mX:HU)PӶO'޶\.?~8q6fr:;n5%JE㏡*V.к%zu?ȩB SrrZz(q;Pγ7s2Y{Πvy`:h\.o9~,}!fgeȺmojk/{OTlj{Bq*쩏PήuuU{^~qpxx;ޜ#G ꪭ"/=iV>OVj -L` l`8n`!<| Ƞ -B sUMHm;M=}UM\]{;:IN"tlL+Q4NU*0:UhUhwy0=B׆<1ڬ#U"*j{NUr1q F>lUJ^xR(,jRQJUąQ u7mGd҅ ԀYE Rv'JgfT` ݐˌϤSŝĴskQZU0YfS3kgm+{~qz}b Ƕ=6;e?XOt`C_.m{nq[3 lm&|kKD(<@FnȮfP ${c4&0h` -67 >Cx@ dP@D!qHʝƒDOKo*!L![1/p'^u+)-L2urk;s?c_vvԖ񮺇9w{쬔ͬ"{rk7nˉ$X}r3Ol!BF$d!Rv\n| 9،\^7m%HlO(0,@V^"H *D -1|w]nSAʈ|gwEnJ+NIWNP)&ou mBmĪuN#vRё]nqD][ܪ$B'#/haɌ5ث_SW #2YdyD|>뛗=JS -\ym -Lk[} hUz -eeeBad$MI 15+/# 'ӏ3WtjQjߗsLjisec.H3giۡxs~mk]5|ƥ)#[SUݥӂ5EMmӅM6oTd"{{fDwUΌN>e -L` l`8n`!<| Ƞ -B 8F:9fD$$[.9g%%6tC+$4QD%EH. :X-Y :ة".9 A&g;o|sY2_tBqR ů|qm1 vl,Yz{?LsP!{;;/)wħ'O捗G".ceraKf^v@S@VZLN(6%CQ(usLGG3zd$o 4m=v?mydv49ojqf3\JA7[jI>zkƌ` |ӄgݭ}n@ JS{gDf־qqFf Xvp\Rx~ -( ︍[E*Dꐜ+V`߉/R0‹BPH,+A(Fjb2yP'i8dHbN(~4PlyvV&;cP~mR\Rdk#EN)ld{'/CƮ]NUJܛ?P_oB)JԿ]cL|AbLQ~_*y$ iA(*iXD'҅<)SN ;y"qq/LwqF9y_hKR}x%FRZ8m{zqsY׌iKYTo;fzhZL4'Js'*0RlPi3#Xɑe -L` l`8n`!<| Ƞ -B ߍ1RL1OHֈ-n9DRH}/r7%;y%F=e{N)h7ٮ)"iw]JN.?]g95ꔢX sr"oݭ;~R(;*JC(EX|Q|A)f?%&\5F uOyP+̜,! $21Cpz)d6HqzFB)JKis#eɑ)m_YI;~9ۻǸqaO}}qz|~jLO.[Ĥ3nF'C]:-ѳ 3cq40`NpXHp!< 2(B8|_ -,[ZebVKrˬR1SDxG3UFk^_P uy·RE!/'ypi(sRs^=#vT ɿ2lvPmkJKhsa9mY|$VI3i#g+hǪ8gwW猻8Þ|;)ovpoz7|BĚrUj"˟m>1#e -L` l`8n`!<| Ƞ -B 8( ᬼoAguYBs>/߇v]qb[9}?B-u&˴]jB 3Wzu~(nrMWE!Ru)B\Tׇ(>ȍQ.c"rv(+=e9 f3aْ+&h= GʶqڵEmuRO<se]WUؿ-$|JmD}H'_NDL$?Vh -L` l`8n`!<| Ƞ -B 8ŨƝŖ軘|b/H9pdb8?CYX:~xR[H''U ϸoSyӛC>JS -ޏ|<cڈ~_ʇ<Q|fI 9{ -ip:Êm٥ 2nZ噺m>Q@[Ό+慄]gF[OѶ#KiG󜳺~m/zaws) ў3UnTok| %|٦iBbUDi׋n%TɌȾGK͈W&W$8.#P`3X`;8 .p )?D@T@bΛV$%Hy>\[`$/ b/YVRxRl)ڠ[hoRgȭks{!AJ+qG{VN)N(YN 7i${KyKMq^/.BIoGT8t4$# uyy΋r^8='$ޤ ef™p$V*B^Xwh\~Hݽv9ƥsM惏ЖߎϬ?euJڶU@xc\-cŅc=OԿI<[WlƟ7]h-nz6&Xz\ʍJi%3oΌMnr⸌@ ` ' ,8AP!QA4r(߬bI"3B߉S]AĩA -E)D)(K4WL1NOi3Mb0J:3ۻGǵG:_ jVoMowqN(FRWLJڐ|RqbZB{ħ*ugOE(u(5aLF?.P(Rj%UX7܀̔Phfq+zhkewPc8<\rretəL󇣭eߎ?pzs6İ\J+gnw}wc;LF СiBk,PDi3nƏeofD:#Vْ)p\Ff Xvp\Rx~ -( CPh/b~]cy})w6G(Ep)x@'!d( Z(B!k`uF -nxw跸#E`w"/$#tJ˹U+[+ϴR,na`) cqNR,L\Ǔ#?P?H/]R<`R(2JquͤK5tr#.ԵC_+r Mb(7- ˂NK'S>u 5$.vW\fؑN~?š>ղֶ֖֞βǻn+9)ϱosm}_÷wWlB^B[v_'oue[a3Qo!#E$ +p B -x/AA$A"ġx[C͗K1 S@$OEYB\܋ػЏ|• IֻEby2_uЊԩȣ>9W-+u*FE^6"? .9׮ULuv0~E!hEy-Hے;}HNjdJS -:fG(5uyߗpa"JA -c"ԴsJG -߻hG;[7UyAz`*:›w=[Kz:9[wۺki -9[ -pjHq/W؆BRtjO\u[Y|o Wl\c*$҉-sU9V!:C+~y(0,@V^"H *D -1Cߚ4s0 Llۉ8gK ےm$\ v_ 7F·=gHDn"JQ7~K7*WkUթ;ZUU^{?w}is$Tzmc.^X 'Yw=j90a_;ݛE?m蝕i̢EFl3;wMCjLNI9$៑ |T&*AFѠT.EjP&L1TAqF Qhxq5b5كjPLR[A>'d'iȫ"$&*pަw]+w?'+J#pnw@! i}ι0ϧRl8  s ;5cm zK do.c{.SC=MZu%˼#y:J[߶}s Ks~=лWq\`z+ڶf+4l#n1w}^B,t !T~[ -wJW** `P@,`Np<!!`x@ " QHtt;b ҡ&<[Ejw(%B0K  l= 9z5$[ Q {$.t3wnw ߫A*)AҩxlQ+4m=c؍yFoBdSطy7*5 a1+1SQqT\%Oߵ $i#/Gg{0|F_" ϱiyAr˷HT]- 3L?:gGORkl ^{ӺǑNY>ݰ,[i}Sf -fNbTeޘǝE77̒JdFVl-Wl.LTH0L@ fl`8n|8A$ -Dۢx>fnRilef吝]ӑsgBY8fӤY?5(kE5⨺q:54x\98nL}jr+tA#(m4<~V ԋjP뮥AlFP\m៷zDqIwIdr3y+Lᰏp&=?/7=/XHFŁ?p90HOGRGǟ'e6MiRތIyrquM8Z8S\&VL;d,|{$b`Y ۳ f>7ZDh=S"S̊_2Wv`abi 0 (  ' 0< A(|4*]NQL*BDJ6#()r@ -E冞TDYA[^/ FSjEF(4*b˼(}UKcnT"G#)|4}1MJeo/ !cH,b!)^ɓWd!1Dǻ#T&vKB:ab-b$*|zn>cy\0(uF8Wˏ<]^Q][~3('f(E(ӁR=Iߧek*l<|$Źי&y3KSg@KYL󹞵 -kpmx7S|;+R\yJѡDR`\z F04V^XD 2(hd")|0t1>jelCR(.f̨\ bilSxQ`DE6jRi$"FFI1Y-)d]V!)^kwиD(Pw[5bkgP-)18404b_p^j WDPHBPIFJ -v1+Jg1hGW()޼|GHBEt6L,"sBn0ec\EjCAIq`DPuGl%NDkSUnnm_,5OGSK&{Nq@IqbQtꉞwlb?Д@ЦY̲of5/*TE?ξ1S\{>Sj8+~\k'H0L@ fl`8n|8A$ -D^*@͜c|Ï"~f}^?X&2g.svެN [O"(^XKq?J-rhAϊ(}iEfEU鿩4b)$*?ȫ~Ll2~d !(~1O2W&ʆs|GHEj֌ܴ|>e$#-|n87-=7c?.x~inc1e<2UhRJSbvl[RMݴ#Uuڼ=¾"ɿ11к"3LqG.w乩|NFr,iCcfr3se&)0.=`#`+p /,p"HB|(ӆ.O3L3*UEc3:Hx R9`K5*l"ٱP7h1T8/x1~ŨDn٣;&E5JjIHOPRk$D$ ERoMM`3~17<%$5'()?!ɤxibgGH%EzXe328&g )r!(L0''KRssܑՖ")z_𖽉<^ IQo(`<;~R >`Rƾ ')oΝoӮ+󍔧:/4}VY/3٪ڹ\?e¾wg"Gϖx7B 0 (  ' 0< A(ć=d=D*D3f2ۢ#d:+^'{H -I!#)IIW=i1O#)RԤդ0i,g.CI95")~sG ϧF#?ߦQRiEeI"(AѯwxX{qˆkK971+Jd蚟ɩ)2<#\B^ƤWRD^ZOq|aGݯw (uUP'(ST_)q\WIY|})ʶkoݞ *njz+k]޾Pw_&snl}~*PՍg2Y99JyIbq` Xvp\x~@`AȠ@C|*L4G,5195g,Bf8v&7I!#3-jPFPC]!kGu?4JGv%EhIz JnxRjR|Qmf3Y%t")ƐӞ|FI +}O6S^%|zv\\eWjz F04V^XD 2(os%S~wCrFf#Q>hh:# 3Pdp#Z[դi$/IVwBZՆ~h$E',I;rU41<)~&jR¥{Ԥ4gcHsyoWO*Ngz)~;<)ERL(2~`XBQԋ"cѽ>źN4)!-;eg\`46#-N۞ }kUu|`K:SO3ζITU5K7M59XjOVU=BػޓUsEZzݿ,-Fϼ}.Ծsxi[:¦ՉekW뤝-ktꔦtѵ?` Xvp\x~@0[YD 2(6tƽ>¼%['aE8 Aµ#~s/&|I: $#]cIˡw%m(Zܡ+E\(ƨ& ï<|TY^'q$=%9cyd${M&"}-RLwy-:K^13Y̶l\;S:M3źL܎H祹Dq` Xvp\x~@`AȠ@' -C`Mө͛#?6.99CF| H!Xcyiƨmu2[#(P -cFPc?YEwGI5)h$E5f*w~TOFqb͟52i&b%<IqI("BJ"8X3#\T`Nn>R3٩IgrҘbߧ(83^߸F}xaFXD6/3P;^ CB  D@LG e5bfWQTT/)^Dc%}Iѯ&ş4[MgH(M~#>S0j:%ARHBuҫ5)jԍ HGiU.1W_T$??&7RPSL]?W˟>6#\;<+1rXPLrbȋHrGMW_")?sFм2DJ T+=PKn,փHgOIcMMtyJ8ߊ%)}Y}Yɯfg^ծQ8fٞH᪹s㉤@` -h0`;8 .p?   AdP -n$7(=ms}ݡ-wB!/a 1פJMI1;k&רLu򑭵q2ɻ߅xxISY#)Rդ(H/k$Ŏ1lXx۳M!2P6w7H.'r_QSN_%)%B,'gV+VzGHk̞˳| bIF 99Լԑ'VIAۏMd'C.g\MOTIn2lpYNV֞\ۻNv:7x]{>ry־o׼A n!B۾h!/쾦 QS;^!-DHS!|m;Tn`z F04V^XD 2(PrF!a\hZ+EB[%a_佌{}PaB}-ZBB#B,c46-(>_K#BFJ]Ո%S5֮yǺċHǡXϓ?8V_%2LK~],ױW6#\Ӓ -sB:baӒpN0&Ӳy!5;!u=_~X83^d#{XAPEw. Hڷu8vӴ쟔m 75CC̩lQ<|mS(F$S*ˌtΑw~4Gq'ƥ `P@,`Np<!!`x@ " Q8Pb(0- YeoStE0xI,8/"yxOKԕNFR(Ԥ8:1J|tb^lDդxJ#)Q0J5bzNM[4^:b㪿r=50-DRDZ(Iϯ?AR&ER(ؘzIq -.VCK9 fgX^0sQlYl>U+sKs~\<Ɏ/_*bITss*2chdx-Vo|XrGDW"}ZɁ_ݱV_ԸlZW T阝u\o|ዷ guūtREU.kMNnEm` Xvp\x~@`AȠ@C)FaHV 5m%ue3a&UFQC8xLxj¿l&B`[ n":B#UĨ'85 -[җ^ 7Pnw {G?;UEM_hʓdVe 5U>Ѩ?C|97[ */OM,v㹦'}b&ЮLN:TxFk1Sa}xxS>1,xcOOOOOg1dqen_MC,ZkXk ւ-"TVb"C"~__{Zk]?~(#n:ݾw|:_wG|w,J_q;3yӟr?+}Q^̬;w$r-}wf/yqfqпᎿޚuE wk^HNɗsIp''߰SןLBb/deq82yK̜ٿ)=gFZuW5Ia]TW$}Wj/Z`h,Mm&M+h%6XA[h=0m۲m߼yqc؊7 y]u6<+dߺS i|YB[3ĪCNu7Gi؝p"o^=N&Hp\zF0 Vn|x@ dP a@b"oM<0YxgS` p"/yMOLN1dgn\_Q_=)&ogQ:߳rrb_%0=.ˣɫM&p^?TMu$Y7AM&u"ԗ(sٟj臽;aWؽ/abGM|a߸?\?,?/?gIVPXZۙy?YV?1GM.-&XS)P)RLD]]}PQkaJv}6a_?K(%Y,.YL)I_Š_JJ'qY(}^x`ZGRLҗlQ kt[iKrh%m^~ƹa׽Nhvn'y+ކfj`TdB;3V4=[ZJ ~b^x"q40` XvpXH^A8A$A!QO@)G ,XVѦSf(y^qQ^d1 qrTTR ,ŁkbZ&K(ŌqbaD)r" xxR(Ef)UKx)^;wRX~7W8ϫiuّUj?>RrPrl(ErSvJ?]OcȾa_¹)E7v)DB23`FOS̬Lnd):1S=igRt}:I_j,0+hӱRV myzY~)AY2ȰG];_O|Reߡ4nc\B3GR#ʃSڴ`"=ן-K&3X -6B  - BPGQQ103ˬX$l3sQVA)BGJ1WHȢQ)fIcu^&RLUK}^(ŪqbF"kX)-QC?P|+mP|UCx(CqHcC~(F(EyoD(<+_7#Kا -d#L{0R_:K87ıC33bbv_HLA!;ǟgpI $Ŏ'uKK? S'߽M UXcw+kzKᅣi5Z1cgX[E8r[rM>7FrXqg뷹Ļb=@;^ 7s`Tk'+6$5J"m$Zs^u8H=P`#`+p 7x >< 2(0D -C#Rnc!10 -KĶscfĝO<˼ G\"qT@D$CchUCrFHԐ\k2TG$9tKF qsI>q,UCAȐRCFH!d;ԍcx@#$W Lt!$k B ?6I.9)B6YR"Q| v%I7fH>%,~![Y))~I .JM1"ِT;Qel%7|ęZ_q(9ѝh> g9z`mEq.;4쾚HBv5s{sz)~x>o"Wmu\]q'W$xJ'w6VݭպP۵puȺb@`0`;8 ,$ /  Ƞ@(b1A[]IM@MDMQCmmۈx[EV"Dj"rQ6*ԪlӨ 4P:fU*QQê6JUGërZ^c<ߍCxZQﵪ@U؏ԛUY`BUV_&R"QVƓK?^Pxމ0uQמa_9OM擓$T%;:&)YӃ!%( 9;OJﻲxRT})Tо5V`M2t,\ǚkm7k+o5ݱlqGVOjߐ[{e#/;s+ l#/o'|ODk-$QDxe~j!^%w_O'8F=P`#`+p 7x >< 2(0D -CYMƥ2X5ö^NKKܳI,ŷ06Ke?D5i5W񬚏4q8qє1AFǑQq4udBqn.ȇ|\]||gA>*#)rG>f(>E>^C/JTKKr8Mþs GNF28RJF_5'grDQzwݓ69x6NȏY -!+|r!$g%3Dɟ1[VL+^ZO'ڨs r]^M3GVҖCygh[})Og ٝP5_˹x^DYdQ?WKGRx~/ŅWGSHqF)*1RP\܄R<0L12^R(*u֘)RUwLƒ$gK5JaoQnYRXk'a(żOD*e[,>L҉Rl9LH9R>K8G3EFJvbv"S_s(`]"H9ټ(E>~~K߲IgX9Zȴws՜dj=*ѶoU>SΎ<=VO|kWs>#mWJ]WWuBo."5/|W+J*uJ*]}e.3Hg8P=P`#`+p 7x >< 2(0D -C@-fbl"&B77zbm%^EMĹ;b+^7QE| $pUCj"Vi3e+2uìԬYqidƑE?y1V*oZ.퍎4rZ 5Z&uFUGUJjot,J@Un.^f@U^xZ~dL]އ"D]ܾ+ݰ/mtHcVE҃I`fN/,.|&gdh)57z \p̈́o=:?ǟJkh'rg/m$zDnjot@`0`;8 ,$ /  Ƞ@( dqIm2]ŶRX\.ED򈻊xJwoYpKDK\* r1Q -Z3#яk.G8S~Q)ޏj?htܩìяF àяƱ/^6J/{ʅ~Ă~\ ('7(Q~])>E?3DTdGR/\]&ُt2L,]2$#K@:sA˒ӥt>eD?b*Yf|uMtبc/0vЦꁉtˣ467`rtUvqn9ð5߸~@߲[&yWʾUi#Ҹ=77_f)={|;UmJ |1/u8.=P`#`+p 7x >< 2(0D -CqHdt"~>_|4<>(A}#. cP|P ЩXbPE8Bq?Ұ."t9=iܬN=j)/E兢x)/E쑥rƶ%qe(yg' _כ(E޻XcJ1p)D]q~},b/MD)%9##9/&B -/ޟe -Fb &]YaC|}aPc -5FZָQtb9Cw2XVLrY[sߙhZv\fOd]_Lqxgs=w|;+N5Oc(ۈILTڝIcK-&¯Ps466c(E=vT5mZ/&}G0N9$D +ǜ''cY`U?m-3Jޜ?z~Hm[NھHK3g\O[-m͇n)gW {ǝpb{>fgˊh+><>9z~`XS*?:[nz"UK 5mnr^>)K&3X -6B  - BPd8t z"5*fE ݬ] ->.y#,*J1M-E>Uq1YNW j)SKaG)~sO]vh h SԱƌF MګqBq'T٫_"(__^uJo\/RKʱOʄ1b({Ea_9Z"\0##Gs)~!]Y)D1Mz ޶iyCQ1I?0ЁəhӉtn!4O iˆo'Z -i[nuSsCݸΝjR,SKQյIڎ# ubHq8JnKP0R,g^U+ey7/]/`mSb þsR -.2H!&j ɟ$e)\Q洖7K$֕6ÊbX m;5yf*\`/-MvҶ})1 ;;!:QGigݷWxʾS;sSMi|G !y)?6[k_j57t^Ѓǥ - `,`N`!\xD@BD!v(^@)6 -X{H gYV#0T - "O FBkrxv)#^-ťj)x:>j>"lF.Eͨ{D(+4ΆR5RZ|s]6[gmi쮶kP"JnHB)z˱OQ9kӧa*|](a_¹l$db~.;v)Rf?3| p_WW:,=<:Ue -Nb.kܳ550t7,s=:k~^bk.tDZw ]nvKv~ʾHq[Y@._DW_~ =ED,nJjyM"J6P$\^ z| - `,`N`!\xD@BD!v(%D_># b$vx9b[5U'–N.k%WRjOX.QY:v]_=j̋3x_GIOU);Ԝ4jNMս9GNZ>?Q)Dye rb)\+(ا -e7r~$'M8'?qy'deefe3L,QhJzLI -<<"'uOoW9鯘 Zz1{5_LwLEkx ~>ٚuo|cgm;0?rj}??xk=҇o#cl#\[̈́߼59/񹫤D>jR6: -5ٻq\8Ǩ - `,`N`!\xD@BD!v( e\f*& yr5r0v#26/kb7+"<D#m`d׈KJȟՈ|8/:S,-"MQK.u5"/hDExL]\a| u,C|CEH Ƚ5!*a/""2K8Gcʖ Y|PsY~KAL"t1+EฑLR~e/#|~߫)ԚbDnk컗5 OZX1| L<^tǎ n슗'~u5mSS}뽻}EDv""Nm^+ED;^&J{"тWIdcjDpzF0 Vn|x@ dP a@b*RKI# b$*b+'[ʈsac-i e[J|e$PL2¯&ơ+F+^Qړ=FO "gzšѓq>'O(S_j=C -qS2_ JT?_KWKJ7X -(c*?(募WwF(ljۆ} (()?D1e?%+){~GvfRfr$ JSohpB]uỌzt>ض3^D7W%2rݫ8ˎ.Muv{Bfv哄ʶ]sOX|:Y =Rx):8b4*yNY}.gt{kuSk@@`0`;8 ,$ /  Ƞ@(ŪyBļe}Ķ؛85VIU(/ґd|} (#7d%fp)l.)/$ 8`?)Orddo OuW1e.}W\:CV1՜vEt3Lng{"'m%ptvC% E/wu/#'oɛ[d.RǝZW鄾O:XORmJZ 8FFP.40` XvpXH^A8A$A!QZ:7RIibꈭ[c+q!XQ6w4Nk$@"&:"y5QvyI*jUUԪ\VŬufUq~*(Ө}z@*pbv.u8FUf*卲$.*{vn|uTsr=2V1/cb;̽fLl%ULIbF0#g%gD 'OOE!9[GTU;Zw}_TM7mJ,0\KKM]4s6-r i8Oqv,Ymh.\6_h7"Vv;3>JUzB{O z]fG2(0L@fl`8p<AA"ءz5ƨ)J*FHC!;giFHސoV1a"^=? -Ņj(&yC B5KEdPLG(\0v_xF(Ckh,cETKZ4vQƳq؄eA"ԄY(_Xgt9B2B0ukea_9*0(dK\VIBbR?]JD3l!#%+(4(?}]O[&[7٨͏-0Ԝx<6 |5P@3%^`ͧ-5Ҷ.iGQ9{۝аvUmlI?x˾@i{*:XwP q;Nr75t`ży=(0L@fl`8p<AA"ءDY&2טel ۔y!o fAÒ(R8RQ\sRBPKSK8J_1o.?5EL$g+R8#:[>JU95#v[>f%Juا(ŷTpR5A߰/]8A!'(%R$p -|/:r(~^vtZ׎X$}vջq (Eg4S~iiۊO-Sg ۸ߝPI6܇Nٮx7˾/S RUswN}8C)D)֦*J mȱ3K&3X -6B  - iP -țx:fl:Vucn}s%"0SZrk5JzG[x)4f(ͦ1wFG+}2ѼPYc}ؠY3 (o~ǝK:Rh|1iRؽo(?W(t -0GQgwؗpnf -)8K1N0cH2 |%Fz݊Շ 3I_[o?1؞O=n|fN}@)mkp6Lql98{?c u}Y.SYx7Vɾ cϦr{^8}S:lePɹᲭ"ǥ - `,`N`!\xD@BD!v(7ySv3nr6p:=p&OrRXRthCztȓ6RxQnݘVV -KkykI4(8ՙoSOH)3X},Te϶ZP"5Z9B@)Zލm=ǰ/\~l.IgSB68)+1/ddSbȷ&םMw? uQz^% r/MdmN4t{D۾oLW';{dD>IhŹnWO<=N5C+u:{)WNXP' Z u_v?S|R3p kuoM40` XvpXH^A8A$A!QVGiۈXwG;q-O[Ipu?@Dl%"&J/iƛ5UYTOYyv<='?mj4F4?ﱏ4z ԥ -Ty{۟/.\|ێ#+obql HE"+(/a*|1% @Ĥ줌` -d8IrH%*Lk9^Q$lTicôة·iH>m{Ԟh8m+}m:Q< -A`s74cyNx^ٕ(ۙ5 yμ1C<)c@`0`;8 ,$ /  Ƞ@g祉=#jeEm}6lDi9ވofj^faZy2gԧ]q?R\QիR*F)G)niƱ{jKF@2bZ5WK3u[[R|HoR5ӯR{IwD)XW*Q S!slS#/\)7v)$!(e3=?%JN38N@:[Wq4nmM6ƞvlkJXIr.Q"1ϤLVWXF6 G>֭Q%Z1su5ݥn {bR_g[Hbվ(gۈ\KDj ?L‡Dv|U}K!=P`#`+p 7x >< 2(0D -C%P%ŦBpG,no$WBy]=wyKpb)F2D%)]"yR"KiTd8YԘ{Kh(smr{szG{6uG$kH82wJQޯ ^VOmL,]N#^ #CTddXw/\=]ZrSR1d -~!+9.џ"#b )y꽶_/ -XYSMCoeNobϰ53ﳶ}mn g>`]wn{eV $EHn@r6"tUĊ1=(+>@Rȫ$$R:@`0`;8 ,$ /  Ƞ@(pmYl bb']L1/,lOı8=.w.ݵ|"/$֑Dn{5:RЉs d@]\ёёt?q˾~BheS4q$KSGXHrs0.AG~qdq~|go7#'DWwKdvt}1DHajF*ta_9:2 233D>ǟ.\VbFrz:/וN:ķ@*NNү~F.04/Ц}-4SQG_-_mgw;8vNqNav'l}dk.ėIkZ86`.҈SxZy'UZZhy"=`8.=P`#`+p 7x >< 2(0D -Ca#`L>f"X%4@tЃJ}nG}ހyx^$܈>5<z :QK:q(8J5ả#VJ(e9;Rp8yaԘ8.L-Q -Q.;p9)@y܋R4wcR!QK"P?c0up1K8GHN{4,9>)+vz?=O9"YIȇF);:kEN׾kY`h]Iw>EJHWORwfu*V|}(ŽSNa;'=Ecso&~T}/ -6qe+ kuJ/]+7NUj:Bu ~]og8.=P`#`+p 7x >< 2(0D -?lƮx)D_(k,QL[>q*CWrG=!}P -FVJa9M zZN5QI(~:JN2/EZK4Ju0(W7Kj7 -i|.3E3P6^E)vʱOwtO )y@þsu $~!"#I9Rr%׎|ǓX#u.}S<tDVδ"T{"Whn,YWw$G쫟WuΝ2;p:$tpc'l'ފMWZؙ[_ e::yOꔊPgtΎHEz - `,`N`!\xD@BD!v(g_8tv+f1 6bHlľ8sakbۦ[^VAo# T눰Dj#r%QjFD5+ZoJW*z$#+??usS0ԕwcP2kƱqF*(9ӛcxhHypا -ŹR=a7n%wڬKs|QA >%OZ^9OJֿ6uϰ2t?Yfwkng-=Lwx\n1*Hq=pg{%'kFgFƧ _1: ګv"(=m#-$RD6lWw3%v5%ύvqtl  -RR~CR2x/Hwa6nM)Z7 -܆~tۛ0R<81Rr'ɝZ#ŊwAٗWXd͹{ eyzSڽZy;ʙ-iG慻7\ޤI4=P`#`+p 7x >< 2(0D -CQWt3h|%lee!;9644RD)IB]:d\D} jĬA"#($GF?%)=Iđ+HѳwZk:CôqIڔ{HQF#jV70q#EvǗ>\{B]-oL {>M[^hyeX͖Q:RC[nmo8.=P`#`+p 7x >< 2(0D ڳYC/rr(Y$kca=9PtKɫ"9;?SKqHI}rKdRI()<{r񃣾(?|LS=C±e-ib>uiUS_R)*>U(ax:"j%gncGIH9_ ٢?='%!k6ECEi/IklԁC9 QڴDzX|GOw)2SRbR,Hx?#&es3G^q9ƷI9 GN]`?8n?}S掓XjMGڷ-(41;lIv:N8\.RpwWp]RkR" X BX+VDĊXJŊlP }'бw; ߳ѓ>3y?Q/37I_4S%Ej)X;?8Fw:z8lR[TlS\>4M]d6G -< 0@6hp /? a`APA\()KՃ^JK|&(q(By0sJ#lY:qIqjsN$N$E"hoDRϲlͲ,l8Evgَɲp|E^8we)gcH#o2?35|g*dY|qz$E÷SGJ+Ӵ -/&V 48n|0 -A .K?>uF*T$BpI 9h>T")Q!C\?ѓ&?ӓ"h&=)~oSARG6t{zbb5~AP8h{U -7aEP@IQ#q'{{+2(FDJN{.86%\Kdӯg bOB!4HglIiN*GՇ/Ċ泳HK{ i:{TËI{7Ot؜8A7Pm㤯EY1_%6JǶʳf[ U=*W+;j-y昀3X -$P`;8' C8A$A4U}z/&u ` 98.ͦz? "ySMU -AI{F+xmr (\7=~AKn;엏wzRgp'v]MFIW*LSOR6x.` X -l`^~@< 2(10Dk 6Ů9´\[J%(~5c񬒺nZyx1='IQS8 g}\ARlw/WSwI )nֿ^mUIq^St6>$ƼBMqsd9g~!2Iqs1d/bbBo8kQS˔3KDq- yHh(,Tl"/_l0vu"LYN˟ $EͧifL󡥤q1i]m I"$͎$yܴ1۵$>rxwv/̱MK8,wog̚j'T7]O˝ʩeC)>ח)>1fHvp Np<0! ,p"H *h䣄S{4,RM+fw{RAs>Ȇ(ܙ4FO -Z=)TWJIwɃ[DŨ/$tLzd )Ia7Xмf_>3wja"ٯnOu sIEIE*kz5__{gK\_,*K'l,ij`dMQr2 I׎qX?CxG-4|Lyek5im*lX@R&ms}E I0ۜj(O]'=ryf{]nM'5'쬥ӸcgO'owg&KuqyIUBu緅Z_4V 48n|0 -A .t -bN Jv1SȩMQSZX`ENFM11US )i'_ї)vARAM;3mМ=\Ԙ>`ޥ' +Nu%s#7)(4~'H} 'G\!?H8fVK_e"h{I!c>n9b:%\ݕ-+B%EE>DHPiq^~Yn8771 $E_kށXݝi?@Kݞ' )NDR$m5ϓfG ٴ7_^Hgyϒia9`u[AxvNy!iíMXx-=E}@)POWM6wVL@,`(@\x !A  Ƞ -B1I!ZTD( I8DZq.=%1f_A!UVb|LEU kJ=c0M FzRx8eFI9%ś'1HzR08/W_8ݍI}Gx(=j{:PSL!)O#)&mԩґJbPS<{p殄KuH#e|1 -lY/KeC{Qr}6~zOiXd,Kä ~Im"m!)UsHlnv}Mw\&   .p x@ dP@ b|GR"")&PMwHInѣ")Ԅ;yII(dže'twG1+3GPS,w/ SZ_f8ԃCJ4SiciAPa ڂ9():' (kǯZ!J&9%\k E"[_TK7 !,'%y%COCI/ ]_gziMZH[2UHzfƹlג{'*Sg,ˁKSuqnlTk &KS7ʩSMZ^QL@,`(@\x !A  Ƞ -*Xb - -8=ZDabzP2(|7fiAP6]rj{ ĨΚn?п|oJѡ|y "y"+rҨG*EQLr_HD_>N~.~LS4;Ӽ!Ҳ?AzVβUT35$}q.z75[ʽ[3 ݲUK3{"J޿K[gc|+U]ڊe`+@ 7x >`BXD@T -S$Io1:xˇWS?G1WMI|`2>̛ "'ūzM5H#XxN}܉8\M0FupR$AMݕ#5E/_>oFogN%Le/MH#)>b2|QUx^)ҟEҢh^(RM^9‡sBѲ|1+-)MCn$ž]KgSMTiBMi]x.ܹj"m+kH{mo1q{/5<۽-BA!yOkN٥T!2L@HSh+>4INq_$rE%zRXFzAR,ARL~ڤ(F.(>7FMtAIQ7z|5h> (2^м%ŏ >|fBP|x@\;Ep&%Aӽ()~X˵]Hn1_ʆfR!8$!$E!AрXy>hݛiIG>ii93GOYXXE-$s 閳tsZ|,EAE)uI - --mӸn O嫦Hߏ(=h>0x.` X -l`^~@< 2(/n8O.SHʥ4Y;h>Lz -%'>y.;dJ҃#cWEj}A9=(FAq?fm>GSvopRܦ'خ b^RԓcGFL񽻌{m]()4^<*tr{] gC5_5_&B0{7%ş z %)8^H? l % PQ_ż,hza95cLS 4oL鯳,֎ !j&i_8=8>NSV[zf_^wmNp-gonvKޞ,<1U^a\9UYV7|L@,`(@\x !A  Ƞ -BL -%5v%PR*)!6|jB a!uܕ "+CI{ВbezR"lPR \6$ſ?A3|luAI'=)f9ԓѓARlГb>?6I!?&ŵs  -cojBRj@0goXj*qӦ)/.SR\bA3=F4B%M1$TsCb(`8t@IQ{4gLWib,ճIk[Q|a^E:׳]_R9<owi$7Jpǁ8[4|E8Iqdq^\^u\i\Un*ԪN $V 48n|0 -A 2!L!!)DR4Cl;ܪg*baI)$&M>=)$zX' )FRS\ؔ6C6F}48)[O[ BՓؤI>g ⿪ancWPSEIM.h)9!)YRW5ůU xF}᠗pO6 2>`(BRDsCE|Y~HKh^Qa-H֮w2Miqy,K9im2CM1av~.io2ñIq iv!)<+3z$E9V_X"qv+bk ~qnqa$EM\^h_]v~^H -< 0@6hp /? a`APA$ş:JVPSvS=,BbxlP -s)8^TQ!] zR|'h=)El0=VOa "6kG6e()8pCKn}A+YhK qIQaFCI ͇8|2)ޕHYxfۯT:A%neGIqA/!Fk1Sa}xԜxS>1;ԘCe1p!$c7 -(Sɟ9ZZkPkZkE -Rb)bRke +CZ"""b+gOu~`xoiOߟf{|O4IҥҖaee۴%/f-K7YYKyݙԻ4qδ%,.cVKZz޼wdތu/d>)r)< 7s|r_9|K7$)=3g?]H)YMK322}K ϙz 4u5CoΨ}缜&:OjؙD]O7M_&K4}>ռK uCt=hi|xJr q9;ro*Wq? B[u]j[u`S o/Ԯ!zL; =#   p"H CBD!Vk) !H_e B*xUL1b{' -Br0M OLNMt?@+KOHO=ItY?fU~gS~gW~(sw΄c5.M[3ţL4w) -)f"k5SB)a͔uc[~O5*a|GݷZ{-t?,c/>?6]w6vyVT~XÚG֚Ex(wbuɲ -j)?W)>W$'Wc)SP;<>B'.Evf!x/{HIJ I3D~L)=^Sob_ySk-S ku>j}n8hs=8^ -/-P:Ѓh0 `+ 8 , < 2!a@boťQΣgLZya(z\N},'>y'A8G)qRbڔR -x)TJ1S'cJhT)nC))QѥoU)Eb@)4^kǖIJ)~.E)V]wmF)돠)brUܩajN(}pJ"2SD>śHI -ɁlozJZ -)-%)EJѿsFޗKv6Y UܟP|Aw*Ǡ/|(@A)NL?j.RZon;7-n6T$ݍgTGSy|q_K+RCCxfAx)@A@&0`;0'7x !Q+W 4&D-d1陂sGz4J_PPb9*)QF*3PZ%B18p̘ -ź"T"Qc"a"uyԇpvB"M$efey) ~tC -B2&X|l}űG-T;&>mЭ1臾j-37L } YN OW<>Y,ѣg/ \魜;|p쁛x깙Rޓ9;~rCx#//< -/-P:Ѓh0 `+ 8 , < 2!a@_|+uWEU\` -"Bd8Bf9\w QaHJ?_ J(QB1[I%M"b26WM"7[X -Ixii$.3+dY^I0QH1=]2Q1hC(>QD& UҢ_&d@' =L5WJ܋bt[QgX{baKLrl ׵ʞ}#R7 -k?#V?o?F}<5xTjnAg "_' -/-P:Ѓh0 `+ 8 , < 2!a@bo ꃆ0-B(dll{ tIGk$c"J̎Y{"=t*PBY(^Q& ->e]EM_Dѡ -e+"x(P5BWep#xEKW  -ȾGGy@.~2Hy.>D];T|QYZzpŸ&y2b&ozzff 9#%E-/EAyښr UޚpxKS 5?M4o01ռAgk{vm{=fcoY\?}Og꿸]1AX{<,i|}-ȌC -@z0 F0,`؁8>D@ B쭄n£ Cx)PDMG Wy"EHPA( J(UBqL(4*M M k*KILGtgXzhǢKL)WC2RV_8^ -Ze;Iwlg.ʭ3}>;%y)vbثA꒍9X{0L^}ԇp6)&^{Y|$bmM$'^)ii_$?c80KyC/Z t5?jz@o&XWh0|6ռ`|6M?Ndޒt5Ȟ]oL_Qyk]5r]njPcp "F)`8F)`8F)`8F)`8F)`8F)`8F)`8F)`8F# 2JFla{QrOP -I#RLQJCeg*^V9RB)'*$J2}vQkt)UJUY|jۙvex])T)I"'We.1S\og>^R}-RQA9*JbBٌR<ě#I< 7x@v7EerIɼۙkNqx)|/;aG=_$7j"8XUiTpreֺjtۚ44 /[$WAwT_#X|29r0j]Su < RT/ޗ(H4fl`\A0D -r6RDk`qjk6Y|Dٰzy+1SDQ4WHTJ{ KRȿ_ǔ7Jk<^(bc(i<UBPBq%TB9P̽"'Y~w:^rB#ELbIjCT0 S⣾xԇpvB|P48R8or?+dft)-ÛƉi|Fr4,PzM>zz:MV*I–JFX O3ldgr4sk;c;кY.ˬ9QZ8? -˗}ynf;@3 _!|?ډP"t]!uISo7`f|U>v)|Dמޣ(H4fl`\A0D -#n kC1ˉ*XVZk SBb®!*^E4)}lgvU*{rwcyV}8Ϸy\ #= EĞvr9Co!rOx_Z t`VpXp<x@ dB(|G1xFtJ`w s((G8,QABaRBѪ2xRJ(5٬Y*h1gExG~"J2zRʅTQE9qQh R\4mTJtkB)^][RdJ"3y+J eGD Q`=ǪQY<_S2lߛ$y+@)ɣU{WD@ Brdcn݆q^˷[CR"WH9D+xUqʜaU1G%ʜ񸒏Uy<1Q9zR -9:ՏНJ=TI[ =fu]9fԣU豻Q?WȱW1g]pÜ~٨, ';3ҥt)3II3x?MK{,Rf?'36<1R#-T۽;Hº/ff;6?Cg g6\WZ[ߛe2խ 9svb׾{GϺķt+rh!wPBC=Nz>zTzRޣ(H4fl`\A0D -rx}1zFCyĶľYʈ;xr/p_[$t*Aȣj?ܣW|*2Rq=(;R*9t$Q#*5)U:rdՏs-P,%ȋatdzD/X ^EGZ.qQ͈P\sl:ùO!S,oVNgxӒެLQ d RvZ)c3j+oj;z,Q}*Q7XqW480՜J7kt['u08k,l IԳgT_Tup7爽/إ7>jwjh ] "o7J@A@&0`;0'7x !Q)N1 z%:r3X=yAŐɡ14Wx%aPP\皡 -$?:%g[$ FJ)uFetRkMRROVʵQbvJ0W.|b&[ثACKuQYHx4--Y2H{$Λex3333BJ]g]ϯ.l"i{ߵP']ʠ,5[>jh}@OL_L5|`)(e-{{mwt{ot}}wIGᓲm/g[JJ))cw1k -hVVmi-TccqD}#%՟%~h:T-;YkKmh{͜b-lˋI5kdOͶT߮TihߋG]]5rgO#5`AsAx)@A@&0`;0'7x !Qfy#7\n55?DAەϫ؈j՞yBH']%_RsT)?7^GRhRX4RTJW)J'qF| O]RT7S$PJ\j>*3O>*z>]纵ˊ=%sRl\p5J{ԌRtʱW1Sd`3Eh-B͸Q:qҲE>Or)Roz KfIYbfJZ7nc(EeosG,$X=4S}aw=C|7XLP4捌o`mV^e -]ek{Ë];qt -ڷnMK  ډpB"~u&T ?$rf:MKP$Jozfe&gd$=ڹifꏕjT?x+@lG5r ]wobBƸLS-e]BklkY{K,SBGK1y+}r;狿x:PMSFaqGWJ{zPkͨo[Ix~TUxZ t`VpXp<x@ dB(J1LCtDC t51aZi."HǝLNXWa +sgo cH%D* rʙ&V4wJ"JEڔk$TN" ߾ֺ-JgR gJEꔊ$TITqt%;K~.+H.ޔldc*_^?c*wyڂ: UQvBD]_ CG tW=˱.8ݶ Lm'ipvle I'ȞXnK>7T(._#?z}pP5V)Uob_}iЁ @L` Xv`N`n8A$!!C9_#EC4]m,um*j$#^ -ÞJnpxs_Sp)TBl QFsƆb$B>aE)Q7)Fe;#WD7P"WY\H94Cޣin0nX;}"%SbH"#B]9jfՆ eL >ud#]RV - 邗K{ΛӐĤl^E.yk?P>OjZPtAQh}6PWbj0/2jO5W?bZsOU~Eۏc–-zKK;5W{0\?G`*r{kRG*~uA9HK$x_Z t`VpXp<x@ dB(|/^#sW"fEFlsQ&:u[5ċG(>__)\PZxO =*ņIW&=%/"Jޮ0EeU)RP6>oUJa?{.DKu1>=R\r\+D(Ňr 1.+HqZό„4$%gcBz7%%K|ژR֯ykvއo&U; -wc]3 /1tjxLSg&0_ϲڛvܛE[זr֕-UPLBDh|WHmo` 67P篒o2kP; =#   p"H CBD!Vr6?v -}1:~dm0w9]yzaRH|EˋLr\'r1MɈ[eaJFd0JFȵSpUsxPQX}aJEjhT7ZyRJEkYq3 i -ʗEQD9*g䪪05e+aF}gk{c d1H"I4ś-J@O[M5uz7u"NjT͚C%~ݱFN?4`6lǞ{8SAf~bmx-h}Mwl2G': w.`;@\ϴwf] pVkSu|'+nv=F>R VVjB}4Ⴟh" _DjЁ @L` Xv`N`n8A$!!C0V!V&GMAL\G,ZKlO^G;aW'q#~k'\=[ID"l"JUGy*UyV -T<\>}΄U,*b}*U*F\&v쥖g ATx걊CUf/ld{U)l8U!Igl>)%ɟ\y^i1UijX)zYRB&4 eop9}*O}3V?șZwKyĺleߤ Ia -cRg욷kkOl%#<黧45B ojt|R#w}&Xhu>4-^Q-P:zw3 F0,`؁8>D@ B4mw*DA]Ifbl$A,ضf´ƪMN|, D"R+IgTT%TŬRRNʹ*UO*_ɞ;BO~;*G xTnCaޘ;ΊZ*w*.><;>gHk@U#^ RmU2L*I>42RS)4!-+HU҄doJZ )EI☪ cS7{+|ۍ -FW?100CeCe&Y,S|ƙw,n޴uxtni߈ϺzxrCkBjl$ӋO}Ick$ =#   p"H CBD!Vnm. - -NcwܭҖCw,w {k{gIlspGc+PR*آZz4+*gJT1mXrzdLC֨|kTJ6)T|,uvlxԱG'N3JD¾cÝrUc %c(9U|!k0od$7#3SҽdɂuOC)Lޝћ;D@ B쭔milREtuD_E Մ.%bb%ժؼQL5-\ vG)ُa ˉTFR,GXeHT3m<(B&ԓ3?DMG%MLgSQ챓G٦UnZ)ޓuX'ԩ؝'f?ofaog 4f7Ruv,[Fކ]5])%t%1S1WK5V[ !Li'k[A\%k*'\%W#+ݾb\=WfexZ t`VpXp<x@ dB(^@OVr~aܘKL+wY#l~{BwyVs+!btG9?t%㰲ITǹJWqJT%#T/"R1ITJR W7MxtIaO?1 x׎.R$j[9*[!$n1!/nnĦo_2C]rCխG gާߢ$u$-ɘC.~4DI{0G9V5C8[`|Ff{3l+$eΎHWR@Zjb%iDr"B힏Sj6i8ݺW ptggťC_rqm)SJ-#UXML\;r]{abM"l%qUwZUuDl9VMdܯ~Y Us%+RUʝ/|x/)D%+>e@2ܥ,o,*ݓPs|~Y!}m|@Y [sYYcb@ e]4G}g9e/o22l13ۛ!zS,D)9=;Miq܏gJPKݟL5 BqK24ռ`9Zߜnkf`k68ߴ'%z[>=OUܻP(ɋ]vkަyC o]K $``3X -6p .p| AA"ݮօF>l甹lnx sn#\W[dfzrdzJ)~,eS.ULC /JnRs^6Lɢm)SkƖJ)+R_On.]/Ūe=5bqd<'Qȱ{h='D]v+L]?Q!#)IL $KI~ov&kF.+d| ;gsc7BkܼH{jKY@ 4hVusWN* I,k]Q^":PW  qax0v-xUXpO}o*k!F $e{ ~@V7Ʌd3̱_zfv cm Pw{ݱOgN3n`އcoO3g,Z+Ͳ5of/LcBG{wgTJ|eTi _'D,{ -Ц"o8vRmV>Jm߽J"6+$xZ t`VpXp<x@ dB(JѮDOVWvGqN#۫˙ -rw.-KbDʔƿkc7^\vHiGx!*$kXKҎ*혢f'UDOb"e=,T.yhGkH^-B;ncb"YBm[Ԝiԇp p-,kћ{bWJC;24 -3*A;6[-ގ׮{'@uvzѭ5͐C=`Lf>{ou,[Uk_ecg ~;F;޺]}JpyoۉpB"x&݃vxUzz; ]iޣ(H4fl`\A0D -ڱ,3ؑBb.}g坶"b)~ku8v- - '{j -2XD}o}G -Ґ2xG;\q%n]JCUiwʪCV΢ yR!Ob;tſ$x - DC>cbp2D0|ؗdYoNIS4oVV -v+e)ހ&'^*Yk⫚_\-{r4 t'*8}nD>g<,gj|l߲w7V5OmX<)3xRg CXT?Uͪf9gp}`UPE݋X4&Xpp'XլjUV5xZ t`VpXp<x@ dB(JU.Vm#t1vS'1K36[=Lnj9ք{."l!=jVVܟTJU(UTr$&m$Ue(]Jje29:CIr&1|qvU/P}7ǾQ['RliD +ثASu[?L 2{Ψ,a$;3ӟ,bW|^Ayi޴4.;#]2>sugT}/[5ts UM5c'KcN5])*~ޗ(H4fl`\A0D -Žr:N1DiQ6>BvUuBĻAtI=];]Ր2~&&agH:a(MqHҿ]u ʦjO0MxKOtx_`WuquWP:v#?vqثA+0~he",-aoM dHIHI -\f7M^)Lg@?(<0kC5%!vs.>F7 } }⫙C3MEoL3^XrZ;fvU#O 3lc]͟t箍ݨnj"\m{O=@āQi7\jo"cp__%W/{ =#   p"H CBD!VYݪ\Nc1|e>VSJ1w{)U[j*FGR2bV{:2ohT2 %#1O,wEܡ=b~_bSV1&%#*I`k0o\Wd-u##Zr>ԹrU\ -},{ԇp wUOʖހ_L -B7M|FJ_IYifovʃEZJPvwgF_waUn162kK#7Y+eOL79gv K\Js{Jφ؃E45~0 "4|ZHĞ7vuI-[IhO3 5=Dv* =#   p"H CBDy/` ``H11JK*{bA$F΅Tw!:R*%`˗*96VkOh*i˗Loԗ/*9yRWJe*VYTqn}C]NrJ|Sފ9,R?`Dc)w 9{;!Չ/4.2^!%֤ߛ-fedq pcrSI^C9l~|5 9i-dCJ;zw5cX͘rL3[|к,ۆ.־eʿY}q(==WzjDMVjj'Bkj+CkPwHpiԤWIvR ޣ(H4fl`\A0D -I|gjG%#eR72{aV_VK &6=(R権HM+5ReRkTCtT"U|'=)GkKʯy#?6%N5HɼsO#BaA0("B$(ʅB4[c?nHy/թ~(sǎ7Cu-ݖ/[cR붷{gm=jzqYPVF}_ -_ګqJ?J_QV޵ZDlÉf"_"ey1`+p /0? ,p"H C ␀d;G -R;X󿤃bFP|V'~i4 -Cߢqyvӻ$ݣBh$)[ՙigRޖ؀*[9sWm2@^ut>!)ie}㛟'qpޖ.95'Ry|RbS\a0b|6gEENmyGƥe_M'upt;k)aM6hi޼*clKѸ=?{!ǵBKU/T]Kwo&KWq{K4.-Kи4NWƥj, -S:yA9ѸtxAػZm\0P=`#,`Np<|x@ dAe( q!݄0A&ㄥ%l{.Í"An'B{zwNB oQ7BaXI Vv>kc^R}GxwkjHjqJm\oF*KrhRQ47!Uf7 %JʯeP;N־#.S3ϮT"ǧB*Z/f9Eѡ/ִqxr־v}Ϯ(.OoZnn0|zx[+n{W筎 CM }[=*޷wFg -tnDR kѸt\@Uk~).mC;4.g[Ѹ,7"mj1`+p /0? ,p"H C ␀$(CYIX)&LUB j 2^I8g&ܥJ<Ĭ"uߥD <[J(;X* l/ESS -NB4{ouEΚt?S6ZC;YsbaX(e9E/̖s(Y2GǞζұұ<ߣUvB޷ٿh{n%j:WVs cX4'mOΌ7흙hNŸ@` -hl`8n8A$!qH@>$ȘTX|V 3!/ı~N9'$ {^i~fNPU *4|Tƽwy~$.u݋>V>PctIF?sv5i|I;''S/RbO ݖzr-ocKO<֗L6J6jZ3M]c *cl^s;Qg3ƽv魞O/DUv=/~OWqeѧ$eePFBj9Hȕ-MD)eʏDm c 0 (V^`~@XD@!IP2O?18۔[f[s)I N̼|+pE|P9|.rr<E12t\: {닊]v,CqseYU]Eѧϙ-Pͨ//;Lp13:{(o.;l߶#rnк\vStV~钄pkx4|kn܌x晉-ƥ `P@`;8 .p 1C %W/")M1*I'-IM7wIq#D JS8O -(J,((]|Jةՠؗqa,KΗ>9\R|$~j$x5)ZIHy#8L}s/C"})H5.HZΐ_e:bkDoXU;#\ND.q% \A!䋸`_ G.%DѡE :;tRno K7z̦YԞ#fz>gf,#fvڵi;U;D@mMe[7rO*]bORƩrb}[Ǘ>:#"q`+p /0? ,p"H C ␀$(CVVd#hY6.98qP@ X=(b\N#)LKA5)NIH -~IoΌ\$EpIq$]݉sդY#)RWVV,h$K#H-܋:g#:.ȴ HO@sH'PSX&gW!)Li H -A20[,Hq9Q6H/ʆӨDr"C,*^^+_=|1Z߷N<CB< 2  H2~ -"ibEM1">1ٙt薐1&鋣B16%0E()ljRLH|ubf u vL#)~3c)cR(>:˔.7sߺkvuveЫ5v5)^՘ۼ=[ӅkJdۑ[7")1Iʯ>^PST݌A2g>!#\éŠ("88z)ײ-F2t3uMϢj?EMٲZ`پiqnXgv.c<5(ocvo:=5tliu3oš\bmt|tjzz鉦OgH0L@  ' >CB< 2  H2^")Dlb)PSHN58O17r"&&, Y#)vV&k$ǵwJMHGPSlxCo9 1IAjPdkR'7ָp:G Q%]#4ܫ=M9ҿzJ36 (B +O#(&Jʯ2r{̙厓C'ϣS}|\Ja^*cQHe|$'4`K|* =Syq}}8h}6E,Cfcr(j\LSʙCYUeq3ֹ[sv}xPRjgd/OuIsCs[sg=;IhiwEiFIbnl֧g&KH0L@q ' >CB< 2  H2$dSh֒r=)$wA!17~! ݈"(bRi>ԠC+(tX]:(jGpi鄌?S91>IϺKM7]]rʼnyIO (>;oGPLHoœLbdێ89/W㸩S*  #b]r>(|SECg)ԭӕQw(2l;P^ĚNoQ-tk'ki_Zl ;KGrYQ-WK Ju.}rS:׸ZD`gt'qߩ]vBĞguR:y]lӅպ=kt?H0L@  ' >CB< 2  H2=?ܤBwQKpl&DU6a\dEnRȴB!n#B]?QSŪ*jH\eHlUT?jpsqwi$Hc䭟e,z >T_& /< -X)mܞc"i.)Q_^'Im}T돜H6pT|_)*l634VP,p\h}Kvrs,CfcfӱdQϛfK0ϡQ8z7un8L3wKv\moR_Y#^ZY;=?_+~E$%~T{`\uZl٩H}#=q`+p /0? ,p"H C ␀$(Cꥁe$SIք-F%bG -럂Fe}F8&& IMUjUIݪAR3Oitw$=]{W7jPT#(Rg4Č׌#ݭ? [P7JҵAQT!L#+ꏱ2^ >e -pZ|AN4' 9rZS\C\،ygܦG'G?m'~2d6?n6ˢi-]_eШ70ڱiׂnڽlkMOۙDz}'͗秆:Nc_7==Ihkr+Y*k*W>;-ּczqŲtP`\z F04X -67x   8$ ٓ^%Q.c)@hAx9%Wh`chT`B@%yHrmA1'QRPFOɋ+Ƌy5$_65(:}jPnTwר(Hʯ2z1,_Lܯ"7#\[/x\vJ1 -Ѣ eA1?TQ87Z/w5RolEEf'>e}ll/LeQPt o!eim e/?8u>OJ/Sgoml3{}MR{EnjE.|tn[& GwUH{{rcǏ̈wL۔@` -hl`8n8A$!qH@)X*@,,z.(8T!8%yЏVAN(^ -{LٮGp`K~%OIzघ&Qb:njg_g=t#hZNAjL8#;Z$٭s_}>M"P|d.T -*:m!}qm5:yG+tkJSH0L@  ' >CB< 2  H2-~2H76#݄eam l; ۄcl"\-Qh%ofm%@l&Oij򦺢Bi[j,R4_qT(nJ$=fp<#SWTnVc2,7`g040 -OnM,#V>:/W=b$W sc8y'ȟ٠pbͼDD!//'pA>))3l~*'X^:]gM8hoKCƷ7mX?Z\{?oio-8valM[&81'ww}<[<}!G8_Di"7\߳QByRlzo$տ-"޳HU1F=`#,`Np<|x@ dA<^{r;itZ1ag혫=3j*Wm)[syńr(5E>'Zo`SS$HFAu?"';57p]I!r"nt?Dk0 P+ V7pkG<ŸJK% |pS89Em"qG\벗+b "A!'!Rt1q+r4^Xx]̆=|bJG_=ߤ G}fScO>Ro6ȲlozqԾ2Yv42eQު} dG8bZil\L~~K\f콩rI؊L׿4#xSz 0 (V^`~@XD@!Ix݆ׄ-P}x /t1&vMTՏl̶6QV:g]vwPfK۷W'H~ᖖl&|^!"6OHխoyD}`u;U ~ X{^t8F=P`#`+p ?AB ␀I1cZH,#Bb:VJ GdHw{#xWL{"-(gD) - 5 1j -Ȝ -(ȱ{(]jA~Q5TA.<4UԂ0epAf Cd~;)TA -rwH|c -GE)%^K䇄de -ed2B ];Sqڦ?Du/VMLomv1]5,g|e;{[:57uyأ;zP@|7}L4nˡ -",.%Ϯ{{(7hHloSo0 h`  ' ` >C8A$A(  HJ)GB w;,wZK-߾qF>磈o . -PS\ -Qn>T۱E(TۑюciP;_P`6L<50ÇE#tܩcF:~ScC wU>VD:7t|Q&R} -uo:0|TǨ H M:B|ht`@zz~$ d!,>żPDÃұZ݆' {ƜIǾc>Qm/lmo+}Kԛ6k0l=.vJ '{T6>2=Z:3qɬىK&3X -67x/AD@8$ y( "fTL2-19aZ6qHNŕ@*r0Ɖ,$Ę$qE8+D E*e.lPt0R8O#cF?3BqPcxpR79 \LQKfWK+R\.Sj)^( KShTK!,E ڥE ,D)1b2RD(RQWLy(u1ĻԅM))|vzD$JYB8RdqbDs}X;pӶOSw]T6 :CY3g 4QV2JŅ3BHF(k,.ʑbD BzhPVjuG޿ieoW*+%_//Auo猛 -8.+Y4{rCu鶃#+KGٹTvowV_9yz:x˰񕼋؎l{Yڍ4 E9JcruddeʮKe - `,`Np<| p"H QA<-D&mĸ6zaZXk 5{=qg+qmKfT 𻈰uDN&4Ԭ\1sJ$#G@./Xd!0D.Bb^FޠTP+Y9nuh͆RPI}=ZE3]Ki崥 -ڶbD{'i:kv>=}`=Vn[yoW>m<4[(~#.)U8E>!Si-xoflKxquj]0 h`  ' ` >C8A$A(  H -!g#h x:.و>u `] ~x٧x\׼sZj)SRLԧJqF)f~9m(ڡNE)o@^\Z5hk|__Lb1A ȿD)nHdK婵rU0J%7O8e/  R7.eEճBK遌PfslCn"U;跽c>ch?5ָl m:xt4ݵfk>ZJ[m;~`MiW }cc -y}}.WfD)ޝί(-8.=N{:M)oȌbFvfYgJ0 h`  ' ` >C8A$A(  Hw@b&2Y$-jQ'w@F}Sb0MEA ǔxHcB-YRKaRg - .şFPk~5H[0T(\\)Yj(Fi3u7Pܫ£KP;4Bq>B=BE(HN #ŻS)X)6SkJE(Jyń9ZHÇ"rP@H"\ (9bDcXߵP*Ƕ}FUw156ctC4rtoֺBڶkvciy:ú+ўm/C]og_efLn[SҸXZ8AyBQT=Pߵ) -(0L@fl`8n ^ -D!qHO#PpƨId-AC( - - -"yMTVPj("j((5ϩpiS $M:P<|)Z|l9`U-FRQ)(Ed 0j)i"غX,>7 GrqJ"(W*0!.HۧM>st>VJ~O#/';#7+'Wt|>)+- VpU_v+Lz_wj]e ]sxe˲Zw#{ޭkϧݻ<ٶwy˻._/5/\ɞ-߲n;jN?Dkۈܶ= [Im$^Ӛ:c0 h`  ' ` >C8A$A(  HQbb,"bBKX"ݎ{\ QB–B_1`!KP@R"-+Rv&8#5F'=ё4#A4&珠#[nb$Fˆ78>7zڑՎ|vR8Z4:2OP71Ƚ#ĸxMQ~_~qdQ|Y.9>KzoB2JE]8J^698#"/x!;/ dʑggq:rUW5:M_Nj{a os1&K>R:κe[l{Yg{:>O_[]u|˾_ :izYDX1:R$tWUtDO JԎ@`0`;8 .pXx@ dP -1CRH_ց̥K sX -؊}cs"NݾEJ,#!_GZ(߭sV? j?kRu {mǥj?80~!qhgf`>Wq^u ٩u #8rcCB>r3{ Eʳ!1ǘ|?WĨ'1?C87@|.]ȉ;Ɛ\r90/Dr#YqZB]_,{OFY6P0m<mZh*Ym>]E[jm-yuw9Nܟy us6~BdK>wz޿9-}]ӔogF0#x K&3X -67x/AD@8$ y(>bF$<Ú%,X[wDhc""e^ -EB ş5BqT(5B1Y eƂeB3zX)= Rup)u(|ݿNXGP`؆R E",X*(3_MC)t -Pr1,X _ˀ\]ۑ1l)0hĀ<,a!,[VWWزˋu/ϠJ^ި3LV<9gzt}a?n:UD/(dgCqyrϚM3Ļfll&U`ˉU:?/'olٷk#U[W|N)b.3؞ןW7@@`0`;8 .pXx@ dP -1CNɓۉ0uD,{6K-ĵ3yN|{;m." "Ɔi:oԬiU#JnS+?*U ܦYј?1V1ut|9Q_*}AͩK;rJJ&h{|UWQ =N] 3C8G Pn$K .CHNV ;7,.'KsǖZƫm#|9~~anbضenr>Ы\L_]e磌e?4۾Dѭ l>0{`w|W,8KXTeSv"4>XJIJk Eoyu]Q{|iInԴn#Ǩ - `,`Np<| p"H QA<%ɞ,4" e"UBvwIp]%RPyc\X2nSڏK5qQs׆4T?g$'_kC_jL%'1Q]GגYjcq'%oT`a;~o*TKWRB?|h" %#,9 -pᜀe"H =[o!^ - 뗊wL_Uب=yc iӺ4||yBڶs k?tcgUu='M?ϯ{_`硩\_y&xL-&}w^5UYhSXϗqf0 h`  ' ` >C8A$A(  Hf6jNe5nۣS_XL%s -/~Qd9:̬7yج"S /4Ba(|-g]U=T)>K0/"muF*UbRJ4.SKQQ/FpKI9P Rieg/ xmHe{AN&{TQW.SN>sT;RFFC/|Nz #|I1d!#JQI+S_ѯkQ>7\I+MK 4I|.1XѶX{i`O2ƂqVs_[Su-33L|9KXw˦ȇ^<-5#VbfoUjǥ - `,`Np<| p"H QA<Ԭ`TLgf"[ERNK> \V,+Y𫥸_j).(Q-ŃN5Jo`8gOTU\)Uf;LaXܥtԪk4JN̿o= (E u]gf -],K|"uqFreR(wA'Gqʾzp$qrD>=$r,"Ddd#B|[#Jq~*Ɇ16Ğ|Öc]ƚ?9xpg֞XWGb_35hϦM6vKs]_wN ;+???Kxj-oiJi3b{fmR@`0`;8 .pXx@ dP -1C2OT2N6ML3(E1uMF):o7?9baxL1uȓ4J"j)j)|hqn^$gC=Xj),o=QZ j)Bj)~6c9nA)wKԥW|wݘ:{RC)[.>&QV:'z׍(?D))V"Oĩ;~!Rp9JO99\@H/DEsp7(6J1'mT<Ñice m:hأ432ڲ}m͋`Mc~ƹa?jzuw=V!SW6-%ӂkJqf5Z\町^>Teۺiцv?:#^x8.=P`#`+p ?AB ␀[úW=yw·tĿ`Zo*L~G3ʲ -Vv|ũʉǦE7M<8#^, -(0L@fl`8n ^ -D!qH@PP8yo᭼m9|NC(+x9JWG0R,Мs?uu u BjCiCE},G~9B]R[cG\mS B$''/,P@ HR^32$1+7w)5OՇm`kQM 7ҦGFU(e m)<:Z;nMs8 c\]Xi6vwG[Umh_O =۷r:,bNgXmn[zjF6K&3X -67x/AD@8$ y(ӿ)'19n dO8BA)&~` -"/)rBo -]X|D{ ]R\lA)LqB)^W*p8b5Œ¹zWgDt) "<9)',1+$.QcWլݔ:u]tok{bXQ2֟`8e9=rkeym𞉎Y i{zwK]vp}C?5,/\Ǐ rD8_#C8A$UQAp^KNKnS!fʉye*O\uQD-$$XBB/#r"h+05:wq۝;Q - M5&ڄ\vM#&Lw.־k \1"IY]_;Tش\t16jMÁ2ڸqTv -S4s67-G[WmXw{ -Ҝmi}!.&oBCa߼>3ؾ>;9_laq=TyӔ3{{ffƛ&(0L@fl`8n ^ -D!qHOP1f[D`0q+޿|Bڄ'R\YP-YߨخݩKF-/5J`><P?ꩡJq|IXR<6iR;VY;hM⤺6qmk -5& O; @x.J1iH]yݭRW=}3&˒8u8fpJ^)? -| Yb ;%3K|ܱꉑ#c>QU?GMݯ̉hs&Sllm596,Ms.Ms>^6ecח Wv^/83I&b ~[qS:0E>R;+?:3^ؗ*K&3X -67x/AD@8$ো-& ~gbhcskR77^%9IE%qV)`˾=QC oK`״`G4|˳m{w|ra4hMX'3GHw@`0`;8 .pXx@ dP -1C~z`FrS1&V,lQ쐝Z"r^3B'J/RRKqF)֒.Ы3a?3`h#w&w1j*E3g`)RW/تP=?όhJQ~3*P"y)25nݵ"JIˎQ.9N]y>su<\8??H@^NBBn +',r"yY|$$F.VW֦v1ʾׯ SoN05tV&L7[|d͉'XgJֵvlga.oǡET7M^aך㋄-"(M5hWX-$8F=P`#`+p ?AB ␀ܣ__A"2bYFK؋ngX|ߊ; WR".%D<{RCG#$!ncܨ>{ثG0r|s7G=4w Á!G o51UC2NcqrCB{4B2a1|>9~ 8?TH<~!\rU -"dgdH9[8)(vS#O׽ozk+hhSt}5|6W-'G[w*I$9gWj).(EӰ#491R 4 ufK- -FRR-ōj)/b{FOy݂I`}jóe)(ſW/Q+cԕO?:cpFaI^s9S9鹁 Gs\N+&?uՎF]VITWİe25C8A$A(  8mJ3'Kq$X[+$eOݾB_I$JDXwO۠QmꆨN$|:sQ,^~J[st$4DȍHK2b@VF pj8Q|YO-U[jmԡ}yŴqiM.{fxUWqnf\K_g]iϑllUMVe˦iq?*f KvJE[|>UYhckg 6Dq\zF0 V,x~< 2(!?N фI7 oS#oʬ}ADb g_5Z-G )Sg5JѣWkF)`u:l!sg{6FF(*ʼnI#cՑזT(WcG{4n>cpv`qS=|'BqR>9B]v$M10R¹~JqA@XF(ÁH^2CBVYy -ab8Q2Vu={[],51=.[ݫ?8uumy{.O0{ͻ׿5l,zpKo&|oxk6"WwJHZ>J{yUQ&3X -67x/AD@8$ y( W\AnBkAw::tG7L^е$/*#ܭ=|i|kƪ@cA2]GF<XMQ4;x줆]i:oL0N:jsuEb(O $!7pzv:|u#{3ucmTձ|úUz4Ӹ6yTc8m+ޚۃ);ru=7٢zi#ӸՏMߙ%4Z:N~\fRPf=fķ@`0`;8 .pXp$ -D!qHk3wubZV6>ɡ=N|_Ô`ʈaʐkGU@7i«bը@+Ff];qxz)WiW:f\>7Ri~URܵ?u{{ -RlV,s(X.J)b$Jq4C8W,Qʓrya>B9"#X \|u#J%j)>F߅R4oPIO?AN?`[崹Ҽ`mD)Q-i?f]~"`E\m&~QD4 Zi[3Rhkӑm=g.Ƶu4О#Kml1[cGeSkwL꿛Ʒ==S.&n)-:yYTS-cmǏI=` ǥ - `,`Np<| p"H QA0P#,7X`je1y]0&aΗO,)qPC1G#Ph}5PF(sg[kH+,EZ}QG>ɐFp?dt uSCbRS)'_U(?EuŨ+RL}p8j>ϋyܬ䷴f@χrr>xhVo[Q1m h*T^l1R(͛Vі= )zd iC_2Σ_2`ƶ>b[V-K6M x}峄MW%O;-Ň* ӢkNLl?ܚq40` Xvp\! Ƞ@b53PLct-g3_gIX'ۮZ%iױ(ŵwp8Yʔ'+םU -ZC[7QIHQ.>(y#X|HK*Řh~Mjbz2}yj)6ŧq31D)l(Ŝ):_ˋei&*mxuN D,vTR64f54 &srQ%(JDN>\0C8WLg\8p$ #@$!.]H~ '/!L]VC %.c?&z:>b*J]掾 #gze[9^DǺ&yury: mWý_#Ѻ-oMd1ryGJ7 ';zC=ec0 h`  ' ` >C8A$A(  8HkKqi>}sk-%UY@\7$٥[N|ˈ?$ ۅ"6ҳOXyU"eTEZr˗K}I1 >3ܩ#]5"B91_:zwzffg򗴥V/=}1rIf5W};bT8uacpV/b"\l!!9!=^K[J^*Q]s khcG m/2kjif*\4ж Nww99\U?Ҟcغ!coےloZΗ<;[^9mSNS*2'+93~bwjCǥ - `,`Np<| p"H QA<x`x^l9N ]<:|.?ܼ_SC{ Qϱn Z;Qw5iBa3ou|:pExSCqz+9yJuƴ9OƮ1eTVW\/EF(OWxq\^P|!+aC$)ģ cwϊ]a.?#gшP;1osw*M=c߲Q+uk2ԭ͇iˡG i[7[i>yu[8OvY[[H7%3xdY&twsxpSj"l4fFw}4#vlڵP@`0`;8 .pXx@ dP -1C~z?rWE4WKD1K U5RкS-gP%(KJ P;SX]R.W\|(SWQJQ7)/L>s`%yɍdE>7y5genw]_[ߧJc696mq4f֬Gji˲ӣKD{iӜ5^>nyY^+x -Qӹwf Ni};2Ɔ3cMuϔǥ - `,`Np<| p"H QAx#5S1R8e¹ $ VDȓrB@:|gV8 Y >;''#P[W;M&Պ='gP _n<ƙMolKg*pC/ZVط<8VNpxH Z-L|uӫt5VҺt|Gu:t߻D9G*zR'ZNx|.ڴ]u-5Vā40` Xvp\! Ƞ@b$W'7E7&b#:¬&zbYOZ8g3qmHn'j#uH[IDh%"5(k0U٨1,ҨʟժLT1Zw`f԰GCm [42QcxFƾ|ũ17d/}5?H]Җ%.d孕"u_ +1VNP]1bh3C8W'd3򅬐Ō H ;;'/%y+5/h~m?л6VWЦt9͜ee[ڷ>W<1lq<Ǻ[= -y+·D_~bj컩\әΙB㑘XSZ_x\T3U9cǏ-Nq40` Xvp\! Ƞ@b( 2ɨ`Yd$J 'cP8/`%YvweJcR8A)7ZBq#PjuxNcJ],B1^##rN+6~=5 - " N!+0TK%rU~<C(ܱ9gڙs#|^r F017'2XlzM916<k~4~i?>\m3ںDZKXib-eo]o|C ; ǐ,bIvrrzzr<ƘGKanmꆻq:ևD䗀JZZ+ւ"cu2u""C(y',gikuy<|=/i~?b1oHg̔;>_zwωwfg\zF0 V`vp\x~< 2(!."(DoeqR{zuJ;)s,WnW3O-bͫYrg}R0zꍢŅsfx.=P`#`+`;8 .p?AB ␀Kc7J&?-1Y<2c㢇GRDr0GRD񚬑g٤~%1j$Œq4sNxrcM07׬)^H٤Gs60y^m~ʿ0(X~sH)R5acTlb#^•:2Gcťya1HgcQR"|.IRqTRGR\w.<Oa064ЦƋ-L_5mn-Mk 4ۿ:[Q8VNqy:!}kcM-{o7 -7. #._+?=BmN󹱦Cm'K&3X -,p /AD@8$YBfdi?0ߊH -1=~to8' Ax$؊HcLjh$0NsNI=5)6jlE՚bhU'/PkcR:~()BR|){p߾15EZ/9+$El$Ea$Eժ/J*ELHЅ%Cdq'"HިhI&vlR4Ow» ƽiӡeƻ᭴czf+xloN\`]_0κx\;i6sh0=k'Ϯ+ï97Oض(.tH7&?P0ZAQlKxAϥ - `,`l`8n| p"H QAp))$EIqglYl6.:dg3|?q!*3;L1qhlZ]3!WCJMGԤHj$Ųq$Erٻj܋I0޴NFM"64w5D|gګ{Sׯ|S* Fb4a!K£%Vg&Yc׌g$a$g_3[D[*?d=f+=}S'GGse=ךKgްAxI,.><߰sph-b>7KnYZt\=(vE#x.=P`#`+`;8 .p?AB ␀K>dQS͊%aU[3(ыX0`>SS$xQDYenmK]}5Ŕ{nGR|89{2wQ#ki>jh|$ݩv4Ƒڵ:w>XSܟoukP\|rW4JI{bbۂ/J%E>Ei/-K©T@%>I )+D.JF)")v_ֺU:džq|x1)9ЄX},?)QI@R7.6>%\)-2JE>,HPff1 qR@+.KTjaM۶ dVSn}]M!gly3oJj19seS vʁEĶF晴e疾EU~;#[պ~Emڱ؆n7DF︛x{%ѪعwJ:Nz'{ˤo: պXÅto>M<(0L@fX^ -D!qH@Q~:Uc1%n4skaef?;8[өxo u3 =D"b7:Bd=RSE*wk\MFG7Jү|TylXTBhJԫ+_k5bj2cR.Vlʝ5XiXۃeͷ+ -8jRޯQ7r.b%]5^UL%BH Ӄ%$X۔&|QHy? ?]7|fZЇXc WEizf7Eik34[ucowq;8/|qo޵ 5K -k)v8>O\qH5kn/)Pz E'o.k\zF0 V`vp\x~< 2(!G#CFq3ϰ<+8!dn&PN߈gk鯯bUsfj rYmS#^•:/w4ĥ"H*z (J©@SpD(IsɈTnh?y&kNMR r6jMWhfq=mn-+ ï쒭َ7ž{S-s>rsbmXwBՍr}uE Uņk }uJ9і?΍U=2/c]vNϥ - `,`l`8n| p"H QApԮKצM7JdNimKd.&HSQ}AK !.N(chtJj1ʗjP|S#(n~)9'55՚`\k$jRH -ZRxՒ ;1DIqhmjifQJʋHլUQJ^5n%HG+9JPHDP~zK6BT^ 'FWX9z;dާX2ÁqFڴ@VLcym9\n.myǞٻvf=abW? X+[l0T}X*JYr㛳 ;(l -K&3X -,p /AD@8$ҔL,3dn!9O ~.($D I*5)fitJ~SzΒbz6L\Kwc%#jP jKCnjIѭ1еq?(g( (ֶYCFH}VN9Y D݊DZ_6x WQdYI1>[mixݭZD zQg}HGdK<8!L4ᇉpuFi;QƬ?XS[FTyWMyjHƱPyQQn~BwjRv4{5@x.Frv'烔dF11+[*ӿ*TkRE1ߏSxhˆp"ERiR$H@X _"e|1"U8S]1ld,ճ̰6?N.L+̑ OЖ&YlNp{{?g[P~ }H۶O bٿ`ǪnB~'sښ[ґgfɕg+' -{ߝ,>-?\zF0 V`vp\x~< 2(!3yi70,>mˌtr>'8PxX5(nXܢE\#(85(CG./xCcDž u]5(vhEN9N4n4)]h,<}+lR?Y蘼A֬_K#U[<Q2E1\3%\cwrb)@Y,d IW=%Z:5mj'뗷T ƪ'iӶq)m>,^g.A)Gyƹj9Az*BP/=S-yjydm &([//U=27޲?)>s'*9B^R -3nR>S/ -E2CU\8b"H鯮@ǧ'}fnӎWoon95Y_R[6,mipn}fzі is}{!kŲ-g'ao=*Wrm]s3umw -CX Zv̍}%x.=P`#`+`;8 .p?AB ␀(o$qh(#ˌͷch|'͗lQYKQY5$-5(XzjPi3afVtc.D˱* ȥl5(9ɵR Ն>;Yk֢HHA4f?;(R&AqoԍK(eAE#^ -e tER>8?Ox$eQAqqvݖ{نFm[a!&TW7h -1}s͜eֆ۴lmF{pt!<"j;mGx:;v_`Ff?вODj[:^Nn}I磍hSXKO64zF0 V`vp\x~< 2(!GCF71SẼCG {:8:DF<]O|]0ۈtȇ5WF\SHjD-?trz>q|ȓ`^*RS%_˫иZ#Ukr1avvC ?nʦʒR Gs}  -~ Jd.F}q`ՈpR%j!|S\I@H&^>#H㹆w=E6UwvQ/ kBvttuw2g)1:bF_jx}N{wNYpr.zi],=ob{6UzF0 V`vp\x~< 2(!Gi$T#=@v´s.6[%7>ܕNi&VC$B:71s."72QMW5R>5U$jܧIH8RjeRE?aX f䗠+oҫ[]N8Zw@Opo_eov +5" xe*?[ߨc1*Z{2NM5%\/O%+H\8b%_ -H|$HRqO -W#V,vX{íQ_D@T<28ęv4M=!s!r?X9bVJv&;|X͹yNv"V+˟Blݤ V?omUt'Ҟ'+-5+B ~~}40` XNp<! Ƞ@bd&ߑmX}&Ḧ́%bo&z +MijxIp7 ߜ(A*"7vXY+whʿҢƊA#VG,ח17,H0yU*fbeX-V|9/wcʛ hޏTy5nܛD@ BPNZ;JOzj'c05Frv~`.Ls #B f"JFGDFDG~Z3h颻I~LGx+=mAyW򪑦.}eR~ξE1Mʮ|(_9WS1Vd5#<>Nk4#DI3B֌jF4#šQKTd:j֝W^u{el_^igR~{/};Gſ{5)߫I^[^rW_^h/څwxH>rG^ w S"S"l{5H^S5LN"Udz7/UIH*Bf&)yӓxޤ@JI@') R{䮬oUisj$KWmWr{gqucEG.Te]yжL7NTX>DWRNJR*UYBU?b@U2U%iuGW*TR*UxUT2B*FU|T*nT=[|g U܊$P ըFTes{5Hr7;BȻa22Tb72UEJZ&!%1Q -~oR~7Q0dd$e&e JrI۶bT3ύN$/4CyMj`gՌ-6wb,y̲`k`'7LY']mxȳjkoG[^# 𫨔d!7mM{ЪDU"R{^ǨtP@L` Xv`N`n8A$!!C;\B fBG*.!+ Sa%,ka[E؋4 -]O\ B!R!$9? /,U JP4*AIUr*A51퀠?neR޻K()7*EFQ2ԊDQ(ʓo("^Dx^9tN M*LNi{.WQ,JjzjR@ tyS2AQ!Bm(DQzǨtP@L` Xv`N`n8A$!!C;ѓy9lO37-2C`ztFeRU2\RJ=*h: -1d9{Q<}zw#;y[؏I_EI!LF_<~oeG0*'Eo@HM@=+p BfҀztG\UNuSucݺvN_T~cRęʟknXtv-%ge}d2Ysf);re[[pmǽ稾X;qX{F߬rm$C6`S`UU&M4W98P-=`3X -6p .p| AA"8@l(!􍄡* 0V { .^O0b5i$ؽpo#TݟHVUc&;C[Muv0t.Ƹa cڸ6hg,fYW?VڳLYgD=󰻣!O2$I.*5$c&]~mfUoc&)LRu3ɎV$Lc :ЃI4fl`\A0D -C&Г~_-|F#~H<Բ^T^sLYrMtP@L` Xv`N`n8A$!!C;FBK pj al$L a;z#XM4#}g_k aWFBKt.!4derdJVFi:Yr0!W;f`U9+J޿*)U2lWr\rr]Ps'X|;\҅r撔~U96M$񾋹z9j!U}jD@ B`BxN=ah$F1|4#8a?tiL W#nB^ _3#:B8EY8!QYx -vJUTER^V&+t,*UaT!>ĕ7uQzc,QbUJJU)UiUҠre0V;SsTeb^sY4 Uyj*ثA7}RNv6?YSߛpJBJ -c{DT%!˥ii=EzvҹW;uM+%vaB*Ԯoig1Ckism/cú I^2;pu3+$7w҇=%OÿZ?tu1B\Fq!B.om>""\A"rtP@L` Xv`N`n8A$!!C;}lBOQ {fyEQB(10rpĐc\^`x!JߧT;mc٪9,vJ@4ıD% X/" w⋝c9"Jy\{<-L7{.O@;|B!zSD_q7S$*/R3i-ݢ|lgZF] -ȎQڊr yBn[h}цFRߍzG {Fv|6Ҽ54d^c[mA3_eoP[آUAwӲd_d}:_v -tq׭rY˴`kɡUgvόT-MFqi`VpXp<x@ dB(ŧe VܔvegXVggzf.>/$ -TBA(PhPLS/˔"_"^%3tCNyNK)LU޿:w*/œ*a ,S(!מb⥰D)TGQ㷓ثArH9ef 6F)ބT -a"ɟ&dzBB -FD]$RZ4pSZ5JQmk<^e-ٺpr|+7Q:s+0{u[cl =2:r^\y6w\Asg-ӹc3ճ'"bWvi򆏓 C-fk)?tRิ@` -h0 `+ 8 , < 2!a@b"kyBd)g-A+g#Awq(Es>D@ BPkļ2:׶>Yj<}'ӷ{PHKsqwLj(Rj^Yܤ2_L5C^vBQzu7Uq*g2nTѫ\ugx^-߹ [9xzM|RhlmI "{ثXl+ap7V&\h$}6]R#5E)BW D#=3 q8^d!Lj8j<Ƕz?,|Ud 03 -[vx9ƴ\)cYyr5[~޾e.rc'Kd{zwutϑw#o!m5mm=!\XOo4򎧫`{Pӯ5[#'~n$-=`3X -6p .p| AA"Nd?& y:7.F9K6ag^H0ٱ Y<[̭$BX"&$"f%"U"DĨXU" vy(RȈT<6*g3v*E%"kq6[lc,"rDd.m)o1y,ثA8Bdk͋ l7E!x%A29o $4brO(%-Mu{SUXz[Rƕ3S6ֺfmxf6:Z:(]!Pt=?J(+z|ۅţӷkHV&J*ftΌ|? :Ѓ(&0`;0'7x !QJP+^2HT)bXB)]bE$ws/F 21AGN/TBqX W  -?ɌGi()ؠe8$eX[ah_~d=[#ݣK.]yL$験H{X<]icٟCtUSL8;BZ?MLMCOiIR:FDWK ޔ$BSR)*`h+>eq=6Ñj;Ѕ11>FK?c,]ͳlv,i܇ԛl}g˘6VV`h}Fa7(0mtuF{7(^Ŵq(0mTR HЁ @ F0,`؁8>D@ BP~%V@j8ϴؼ$6jd|^Õ샮½ؓCnDHK ʜȈncXF%OIt`ssb*جcb2g|1.Uw1gU3:ƏY?Q^!FG1~\؄؍sJDoq(QG1Əb?1~c(QG1Əb?1~c(QG1Əb?1~c(QG1Əb?1~c(Qܗ%ڵu̥ 0!LDEŶ=;U@ -*" _D|B* "?PsxGt*ґcCU?2&׎,$E)rQIe3[YǘuMe|GTBbwk/+ |{kZv#$I"c)^ ^oM '$4:R755)=ћ*y3Sϧ UڸiiOxl`h?RV,TBqx(PBq^avS/bC^yG^;bփP;t9ǦrULokCE_so7rV4џOS3@ r܀Pԗn4dTΌ5[Ȧof.RS)Ú:l?ESƦ(Seμve˞`ic+coô|:t_\> /~){Nn2zo,yUD\kμ4U^מܺozwfsmW7p\Z Az04fl`\A0D 6P!L9db B0#\; qaa$9$cP8UFJ(C7W*7C=}^(}ڤX2RS)ڤN)ŧ*k)>HBl=FM$?5z=D7'~R^&]ɉףPkB){ߛpvo +-=#Û&yt.ɛS ))Bz?e@)6`mFhuI?id7 ]գ g*1tV6clpJs^Rzvkc[w~ys7"۰o$Ş΢݄oӻ nU^l>H6)F)zB>ӱD}MpZ Az04fl`\A0D -CGjܥ_hCeҋsLs-KwhjuuӅLq4_. ~ i*8dJj%E|aR/F6hq}]yx5WߔHR*՘33 7,CNy 3 ko ثX;h c7yߛp拡oDIJOSLoRjR+dfp^)v_J/k0_t=7x[]|(=Jpq,yl6|<*l4]hcghӑ#%-{aͫAޠ]82ln,{*u{Cu'4_ӸSu|MLpX\.]xܑ VZurzxզe3/8.-=`3X -6p .p| AA"ءD0mEfJ=F$*$;wU`EW}"9!"%^>XF%3STBqBY|ת~8ȯ9/VVoWOKJ)(8)sØ/B&\S8DMSLxSofF.rishjǟ|#^Q: yen*Je8Z6StӔ1 [gDYZc{h&uTK9ZحWAwdO龢\|CB?'YWK˧ʽU >~[f}|% :Ѓ(&0`;0'7x !QEP6)V"IJmlܲ'WV"$ʡ{4rFbqFUY=WCsQm҈;樞b[PV" D ?zk#]{$XG+iD&^ ^0yC9 *d${3L%oRsibF86m4.U>u&^_g!{(}M9e8TPtI e~2U.ՙǥtP@L` Xv`N`n8A$!!Cc)D=Rp4g,Q -9 y\>x=$sMEJ)x)TJ1F))Uf+3K׎(jKP)(hUJ12ԣKWv.ǞabʣA4a8Ma1_}hfEґ!f=rU9a2|ML^}Hb:'yx.+ddr4ޟg&'܏x|=:JBl^A{R#(r(ӑ#ͥ+)KỬz7J%m?ͬ=:kYؓ{[eϮ7} /.4OKwإu*]:*->:3RQ2K $@#   p"H CBDRMsY3(f[e2Q -3,­YV4h_()V(Be0fM~Ao3y)JP)xeELeq\)T)s* ?C)Y).6[0SQE5)^V&]9_ɉCdwaa!S{.S)~6ĀySm&*I<~0޴](E|/E~(|l]JQ8ef$Ux2RF[W-5[JE3XGaܶ6\g^ -˶˞m$NU.,4٥Q'j7 ;RtK :Ѓ(&0`;0'7x !QW -Y1HDaS,ZD>voeݮ0f!SdquR|Rk Tf_*[InU) _~ăޏƢ(?.%3v4E~uJ(VB8PdMxH)&`P)BQ8_ oXثބŇ5%LN*,BML'4?% D+d&t`DN)zҝm$Sh\ЖVRt7YK)y/FXk1wh{Q9$8rr*]+ݥmIi޷q%^M8ah]3gZЅ'g;[gDNOh⸴@` -h0 `+ 8 , < 2!a@bb -_߃14kfiem.}#BKENrZ$WBqQ%w)# -X*>q(]Rd*cء6TH0TV,|߭RHNWePmD׭۰b{xt Sd2`c~oe*|WHL{S7%]OTك[ESKudX:kz<24~=j{|?XhӪs(˅]uo[c;m?1ʹngߥ %ēW#Awc92׳c 'y{RGiUɡfό({p\Z Az04fl`\A0D -?>ç <ӲQ2f"a!Ň"np௑$ ?>YXr!eRlTJT)ø}ȟkd`#uQ -gm]eH;Q _*{@m֕)ǞՀPdZPlytBQFP,"o"LP#սބ˴'R4>5Ë_H&^/&i􁷸۸EO8ujv~ l]Si=eh}DGou12fJ(OYkcli4u,lWAwk\dD ۅSŷv)­r`]P뾙UgFg⸴@` -h0 `+ 8 , < 2!a@~؃] -S|%d ڢ  -_Z{["RT W_Pܧ=^SU UX{353O^)\x;^OYSe{G뛿mI̡R,>V=B -i~Zוc4s֑sr殳Ugw6s4i\kt>ۄGgR[uӂ=Cug}?#x)p\Z Az04fl`\A0D -CqAdZ{~'gᬬ-h1w\"J!}W>X,>8RR,W) ?>*eR -R -RYX|CRVQ:)RRP)*RrB6AY}P*;;@UNh.T):RSJ {X=_ jrUB񭲕L%\ N%aBP/1R|{6"yVB($;4Bq<9*Fob -ni}oq7rm[.L/zD>+%$fzDOHMKON,>Ͽp<#'Fi{X΋J_eIUN⥨S0NSTׅg~)&&~_|xZPe/;I|{5HN,#&|!S )37}$ JbZ"F -NqK /3*v -xs|K-(E,"SW?2Q'VS唱f9e֙ )ˑl[ ~cbkNRg,l?sw{TT'ZKןMzqػ.5*>-X%9pڙ3Rิ@` -h0 `+ 8 , < 2!a@bHpn#Ɛi -Gd37"rMs \[)(Eh@)~TJ\qJ)HKF)4!r_?+RBcTNSlSfMiS>lzмvb JqMi%vS/E׶fr n8"C͋d/{1SM&\C/>2DMt.RIOO -K))8(<[_Kqv1 `c߱KP μEʒ;Z1G0r'8Z؂,u\};sfv:5X>K81"k_*:<:=tnmַo8.-=`3X -6p .p| AA"S2T3-m.b!a}!.KBPJ,xNho"7*HRJ1KEø=&3El7JYg 髧W*a7]>4HCJ~H:ޮE)^C)j,DNra2˔iwߛpNh -C?Z/-3#EEI$ 6hbR$)5QA[ݢʝ导*cYe.'ׯ RGƭ9S宱:siaz[VOILeV<}惛 ש{ {Ϯ3_a{ _VE#l̖z^ߠ/քδl҄/T>Q>tP@L` `at  p"H CBD!v(' }Nb=AQBZ yAX[3ao&K -G^Dp !\GOAYz%+1ԃSǔJD%+a\Q1y>'kDcM?qoSXyZ*W)T3s(*eU,s Ua9&1>؎*֊${TĿJ7֏ٌ_&\gؙOLfp\WS)|JR4 $JoO߻qMήQچz q:J߼2\*_')c])eZμq e9Zؚzifq{ʙ]gaO|pj -O=[Z:۵{_BoҺr]ursPљkot)7⸴@` -h0 `+ 8 , < 2!a@~x0-n%>H4)5j ٧0ݜAW{$ĉ}7pRPW*ϨUS%[ǜ}X|rP p?*0,T=Z#U*#t΋?cr*oB%oC*IB7!3Lj$|#^ };D%a2$B/XM\OzE.cJt.h6M*R ~8mw"t{}PMdmo0ƦB\9cyku ;Y{.Y,G{+}#iuhgrк:儸B.=W!B_o_%"k+O1j`VpXp<x@ dB(%ЮM*PHPS!a^b){8x a_G0c- _ g*B'|"Xөc;TXHf(}\%& cX+)wv%eTz*1V2VdPMDL!67{D_RT? H%U$ey7P< eØ:#SjsSǽJ(8P̽J(V_2PLy/cqi.Mr oɱWczE+{zV ބ_LJJJScyғ bf $%']ݿ~v|!egx;,dٺoG۲(CّTjn[FWR]# )cl{Y:*ޣGk-lNKusd_\O vd.O{+ǞEė'8.-=`3X -6p .p| AA".D{r -ђ1hA g Kĉ l]},$YܠQB %(rU(6BCNC==[sT7gd])KʅB/UJ?\rmwkC (؅&Lbم(i{X">؉b* i}"}YJb7) ($y@bן%:KhJߚt1>0N{DwXd5[Ù4%C׽{v0&זwX{'غX{aϾ1E"{tXGZ.ڴՔ%sO'ͱO)8O{Q!}HួDWpnA"rZHЁ @ F0,`؁8>D@ #D6ҩ{香,2m}^9dckE}\R!#DC1&R37}#+<1m]_wGrR*嘯 و1c%W{zH0?@9u$uɱWXH!r5LF0{.bdR(%>956UWJGz6X3>M9zQڮ Y>[?QT^9E+%ŔiG|Z>sof֞e)gdݠU[=Wt| ~W -= vbTy[}rbzh3ùn4g HЁ @ F0,`؁8>D@ O.}TcQ0Ef wNf 33"/e߀Kv{T#iMW)Z(3rTJab٨}FN`8g*{J)7pJ)SV#iZ]*K?~t~V#O@)Οt(šا#;ث( 7ӆ'DHߛp.rC"1=!ݟO&fƶuI7Uoȧρl5DR)N4;ǻfl*{rc[xo<7mk=JXښޓydQ9>^A?!=UOg4}hMbK!aB(Uv/nԨ?}㛉7OPk|2tq17 Ua_e*8C/R升|˵sNdc٩\.+L_s|BJ gZ8؊|u2QUV1W_ϫ (ޑTQ)Eo*SWe>;R4emym^;^ vod\{WZwS-}itS ӸO֍ίv%ONN:ULսs{pyFd(ƥ`#P@ `+pB27x ~`ABD-`l2T'dsm{?.2QNd>,DPDmիAQ5smjPhcjPc[v2D}q$.,jդ դୃOǂ/ԙ[.\ǿ<$גLzE:Dʟ\@:N>7l| חxMJfVZO rSbiMZ_dɲxRkzJ\:CiP2XZBR2>Cw/[NVQuЎ0Cm2uRyoȞėclMɎ;O :ēMUǖM >#R8ީ`\: A04  '$ p8A$!a@~\6пl5ކN%ÔabfdqL?6}'fpS q4U8X&ŵI,i?jØ 3aR$()g?^67rL uElnT64*ŧ4J!(>B P&C;7Jʯca27_.%iB*X/bvnN,b9K ʮc/8^7/ kFR}RtWe+yzˊg)[էhG)YIr5feN⮨=$摒4hPCl$].&=W."#ut@ ` -h0,`NH0x,p"H C(XR/ uXdmwxoޒe%OD& &]]#4&?Fc_Ւ+]uҠ󀠘aV[FA;)9LktuJ1IiS5C,Cqw 6~VIbtm% 3X]}8D  -4')K\b" r|rD_Zf@ 78xgM mٲ)lҖ?F|K>Nw?4:cա/#;M͏9}{iY?VqqYU3=yŋNWumt{ղ=o_5u@ ` -h0,`NH0x,p"H C((C^l"c1A~д@y}ͳrjy3ǽ<}ПGr?S'Ŀ*_WqkDHyHO%CQ<0=WӣHc!١Zg<y-C=׾q '}|4>!T!cwH7SiIUV~գ4$;"#y߀p. OEY0%_ӎ'hu{(Woݖ^^~Y6ojOgϞ5nxudąrP҃q_YFߞa$D4$QuF aQx9tQR\&~h >%;&AM -V1kss%C%Qz&T1!ݣ&ū׷ H -qwƋO=ёrciA.jFRT@E(]W쮞?2l,GRŔir|'(񃌭q4EGRIn\L':eOkwG֏كHNo?]=^hiwݓO5Cyk3{ё\"HЃ@ &0`;8 /x@ dA"89qC&= 6.8gI:B L$CNr~I1Z뜫1u;Fցa\KwE9;Jw LTi$95)>Ш)Ϊ9uO=Øp9y~{g!_I1])+j6^&o!5~߀p:\Y6ͧ(,fT_f9l,0Zquo;5ώmn vGkV; Nc7WSg˝SW[΢#)nn[ ï0Wӓ׭s -2M6>9t6¿b>6~:տ+jБ|t$CGёԞEGR^=h1=`3X -6 .` XD@!QP2O7a($mZhoo]hc#yʢln=<-|𳄅,i!rJ#;"DfuƍI6h eD*#;Q(mydF?*ᥟ9j#F?a#-\ U+Pe<=?܌YxixxLG oʘP1#\K?RXg(xH,UlVekI>zCWGt^' -f )3#EHLyyչ)k{;c[h{1qne>ClNq?+{Hέli\JTE5eۓSS顪ewG>`#P@ `+pB27x ~`ABDyON&9bXE9NOc~쑽e]\[JܠԟP-UZ/SGZ5a$+&Ž\Ҽ=ig~BQd\oJ*{H-2U=i0~AuK *+⯝=APR (Ϟ*)ʤlqG>eZ &rlZJ -8Y9>6s">eøxq1dM']NñWOԹcN{Nӎl8Vn m`#.M --(DrdӸuocK]Ǟ i_LS:i>Ֆ O{n8:{'qVuzسEw'B~5!?ꋐO!?wN>A~t#?:!?e#?:1=`3X -6 .` XD@!QPRFJM%A -0晖Z[ a_X蜉 )$b],/Pz|edHZ /!R^VjdH[R3: yhW"a܋nCvWi?fP[T jHZ!2Z#Dc nB2(B$IJM dxJt+0#\䉷sƊA_n BN/}L6++;+Gz!-Ƿkvjvf[7 _Rut'ٓk-'֊G[Vٞ_/9IΓ%ɵﱮMLυ{턧jO#}~U?zU{fuWKI=pLê$fie\Z*)TZ^>bR0PL` Xvp ^  0D -P.%I: x>C[ Ka lG+5y9N{7B!6B>vqj2^-MbQK O|:XL\b$/C.{/ET(9L4i%zá/ОC}~_] 6݅ۥH3D}R%X" LS!4#\lSId8_6/OL_ 3lVO*{^p<>j+ }E9eZM詳)td5e_T'G۫vvv-!;Um{1ٳ]4+ _>3o}:"muNzByG=I z0(fl`8!\<? !C㫋|^QxZ2f"s7z =n/YyIz:5(5⤺rFPܪℏ #(I?.ZjDU]thFөݮQxRxR՘yt=2.{}od*_7].$a)!R&"͖7#\#qK d3s̜D^|i) 9Y>ه]?E\%uúݬdԆt3S.\Zˊ%kP!7.9mU?U fw -ݨ?Dӂc]3 7&Oš3ž+֡C -ꏎҚHj=`3X -6 .` XD@!QPru*GkNa;HZh#l&`\37B!"ZBn%ӈ+PceZLԈ0be܈Kđ4kl(^YQg5R~xB?j? ^=:+~GX⟉T~r!I2鿲?^|u-,.;Sy1+M8WWRRR GiUeǁOFXɲa -XH -./LOR悾(kVu+5Cʉ(oGR]RHvQ??MY1WeiLckV+N{kQ6us-w9ꓓC3OgDz`#P@ `+pB27x ~`ABD˽oFi1W")S*[ -NE8$EhWI1O8[/MiLRKߧ{t8%Eh% Ս*)>LԤrघ^FRW;\FbѯksAR)7-g=aK-돕# IU&}y9#/k|˴ܒ<ɥS|$E, IپL!b\ {³듊KƭyK|KctGdمvBйix8W5hKNkyt۱n[8vc5'/t WaOɻyN4 { l[zQBX;|Y+!n% Љ&"\M"zJc z0(fl`8!\<? !C ?Ȱ0 -."LKsrp1ao/! Kfڢ\UB%WBń0[#ma7jgrF1W;?huÈDy4:RLFȟޥv&o)Bh3w틾yI3?u&k",?HYs$WdG9B| LFg5#\z#5! dq,e}@0חY9B*k;sǞh0^o:\}]U5qّTcUŔHKE!ekbl͛FۋЎ mLr~ճy.[*yW?ZΖp_x!o,*7pfz z0(fl`8!\<? !C ߙxљL~YyF#y~VQoxENr La34ՙIAQv&^2 eftPIq6)JjҨ7P*Iqڙ|z;q7>?rFkN4*77!R|0:rl2a;l.''Ey7>M `V#GLIA;JغƞxRt~3JWj%\Ij(b=uO[G)sqRXOYel'm8rI޼jWKYqiHVO<6-kBgrNciDX!MV.^;-T~0#;"רk(cu@ ` -h0,`NH0x,p"H C((C!)& mL.$D6DO{?N$ax']cդ(HsjIFRܤ&?Ւ]FR _EQyt5D'OBcyt<)vkt&[MosmOkt&YØ8:l ޅ6KYH%KB$EC!CMaE1mwFk1Sa}xؚ |S1Ybb̲eI<99===c"cNz3M99/oj)Vd""Z*b)7VRւR;V" "b;gOz<^4Ηi48ܽb/I涐tŷϞySnfϾ9yysoɽ["Ȳykߖ}sm?o͹Ȼ. c9 q,ǿvnLKK㲳B(D?Χ%,  -gNyYʵ|ך4\m\"RGZ"=7E ]Ẻc[TT.4rQc%J{y7x[Zl&lM->VbŖOw:~=3{|ps[{RIxLdLtLlMߎюq~r_1;gg~2( {'yOFfLʟgfg~(UɦdW~r(?9O.]_skr=6S^nf8+ߞ~U8I ǥ -tL` Xvp\x`A( y(1mBeSYk3,Ӭl@+d+fQn/ Q1,͔# >PT(,*V ¯Q!;(s9Ô?SJqReY)ŅM®""f -ŲT)^TJX5R -X,~_oD)iS7},%?E)m@)ʶQg%RK`9ޟ'~bBd!.#-'煸!X9{R{{v W- Nh[LZN5oy4涖m`+;w`-ng+JQ]mq|&Y*{WH2k3ûgq]2EXeK5S^53ّ YΗSqi40`V^AXD@0D -1HQ>lFOcc4Ĭa[v(zF}@M$M+K)sbR*J1T)*#ŲQK".ѷc̲9BG%HQ01*#BѯHb#yÄbBaażjBx5r"BE)oEyy} h8rD"y>H9`(ܐPZVsrˤ#u -EQ8G-T#8;6)z'M5vxھjn8\H=jq<ݟ\]Yo'3Ud=:[v߾pв._)u?5Snz03_7;RtbvT֙Pิ@`0 `+p / ,p"H C"$T"Ft9BE풃;W+0MH!UF -y|*ekSxL%W" CB1gP(nHpEabRO_}{.HS'HىR~?)tRMiٹE?΅ -&PH )EaZ͡ūkR@[T:BѵU:C߉u҇S?4W氜5mmpp;JYQp5V-gG==9L(K=Ɋ4,P.~s-OJOQԥo$l} &sG 愐xNsG'CP^/Br{e=ͩ쩿@[v:7֡xa8UЋzL -17t;,w_c-?VWor;J߹ƹڵ=ݽ/zW+ڼ -^Df"?qn6"5?Јvl"E&e-$ޚ Q @#  ' < 2!QAP=<}10XHL\L,EĚOl8!&KޥWB))&|> -XD{W+%9A)W$QJIb$)IюY0\Jc>UIM*)G;4%{(JJ&,aQ$@%%>WdPoLgj=f2x & G,)3K8G{bڈ)3Vfp >\4B`sq 8VVSѤ%Lq]/Zest崾pm8PG都KhUiIμa5mٿm4u7ev{+!ϾKogwfLvKlW{MvqӥfʅG2{ʊl(+0ׁ:Ѓh`&0`;8 .p a@b^G -:HFĚ#5fqM yyIŀ+SJѨBb&U -J)*{J)t*ARWWŘ樞? J1C)[JaW)ŌQ{7;av:Lg_i%nb.)LwT-Be<B)~?K8G O͎ 돋X -|v<Ċlah)tE|u]gSzN;ywHd. +ZXSwDsMcBbے3Ud8/.v94}imo"޺'7_}A&;\._TV7n|]v{\#}&s)M鍧5т'hb;@@`0 `+p / ,p"H C"$ev'1mDK ]"L-1n#bG,ĺغ}7q&(E<*_5 p݄o#B51uy2+YT2RJVf)Rd6n̈gek|VP?+JUc1OO?UYFQwT}qTrsjhBUJʵ|Ew*L]֞P\~U0K8GU2FJv^vFsٹ~>;㏇09.#켡U)[VRr[SU9UvoMmiXCA1x1TщGkQ% { ǞĹ݋]ͳ_!7oGs#-NlqiTw}B#^k&|*MԺUϤ:Ѓh`&0`;8 .p a@bpfw"Y"fBrb*'URMˈ7GM*]A1L]5R%e ;ii顜ųstkZ͞I}-5]/̷P ݱ{KtH.kL@2$PJ1j)L_d&B7.2Y7K8WgfG@0xB8 FF!e HzIi5޺c^ҩ+;~sC `^s7<0*a-~=v]lu -?Kwɿ?xmǷkC ^D+^&_/_.V׼NGxD*Zj3 -tL` Xvp\x`A( y(4̣176hεiٖk7p=|7rY\]EAE}}SʀAi7DMʀAdQdc\N3ocCT*˖_"vRe2_mSj/>.f’⼬d5@5db@o"e.DJ~e uau#%7C |!G/s. \HcH?GIB!!;78-k5M&H'v־nv| -kE5ᚖUH՚)8P-P=`3X -67x >  D!C'F'o#vB L;1S1o!-@l;}]Nn%:H|;Is.uUD'R' CeN.cB@Iܐ*q"cS)Si}%TJE)f,Wcʆ+E-d@nTJ;J)PYP7.4Jѯ2PPeי <aY׀R_"PX}RJ~*S/@vP?Ris4e\NOrQ -#O DF.zG uߨv>:M'ѕt869 2tWfKXa^ƘOrX?}uۡnGO}|]>ϳk}>9x&-em%%D(o{u"-}nKj"U=:Ѓh`&0`;8 .p a@b<{%٣4I.Q/.:f!$]/gjnOOtՆ5_b;K8G[0bH6%o8p\ʕb*Y;iۉqڎ<ݢc˿o(b,xߎ7aس~m]۪7NƱlzv/Oy|wke3dV\靖O[ -?\63\vlV%/Nิ@`0 `+p / ,p"H C"|w,70rȆ0-0ac5*v!SݜFEaIrKT #*[FePJ(~Y9&xg8Z1L}C PܧM:-Nex?N]Mˋ\ܴ<=Cgdei񜌴!XI듎վ -Ek +,Tm[B}c4xT7ܑ n5l5q{܆PTX<ͧ/Of/d<;[U|)BٓvqCtrpy·IMp\Z@z0 fl`8n|8A$! B 7 ke02iiXD4"LNIb$bDR*kNPLU&?dQL`PpHg1fPܥ5SgT J(V& -D1nU&o~wכBR@9J"eC/%?)K0Q7GH(5GK} ّDLq^LKz$-]OŌ&k5%'=n*-i;WYoί Іңcif߉hӒ#c͕Җnk׆ }(>b]/UD+|+2g om",Ko͔;_ 90;R&+z& -(Ё @F0,`Np<x@ dC= c1QƘI6,dOwt1=nRJѧD;ڣYĸI)IEޡ~|GB.窝R^B; !%?H!p< ЯF)} h#?+I6ҐO`П\ + ->1#)Z&PR1N[󠅪/O躾SHvKfVhӡcӖ%nkCn^8-q;]%wG%m3[g˾:+k>{xdוR7 wcv]K @#  ' < 2!QAP؁NN5xhEnԁӭ}r@dyY[J)RW)E2RRKqG9͝Å57ܡ~GPܦM}b(6) UBAaPC y#xgSw%og %?E(0uɆ%F(N,%G^s4F0"y9ټ<#74wHnmyʥ-YEF|ֱA笱5*h>R,>VHkd۞g$5c_!qVշ&ݶ}-LO5_+4аZ>^k$¾9bJtbyc -MOk"h_ԤN@@`0 `+p / ,p"H C" Dh$< JxZytk+I1yuE|]$N5Dl!RI*M"\Teڈ;~{rӭo}ƘiWoQ8,TNT%WleЪ,TB9P>]*7ZSUٝf@MXeX"ԔnRMs4~d|[^(g1j<\(Eggr0=(xI)7mjP OѵhCB9ǥqTU3/-n`[9c?vqv]U !OAmyY򕽟ؓ -_/xpr],<0]:HMf`mVgYѦ -K @#  ' < 2!Qw i`SBcX#g -ol+?pP}NjNEBaRBѢ2~+H*U .e_~0S?<^Im1 ?PdeB\QS -EQT10B<7F(reɻٞ0~c2.mDnA_9:ʎ|7=H\Zvȟ#~>!bCLr2C>={צNo@[L:Zѵ6:sv]:u'Ƽa)}& -VݎqosJw^Ϣx?yu}# i$ Wq+w?ʎi"Woj$O_&ůhٳc~pZ@z0 fl`8n|8A$! B ނhܬoGity:k6w܂hw{lnKȉECDS%㔧DA%+@%bwgᦋ{b̛1G*xWq6 FQD{,/D4zOmn,ݚ.Uؚ+%?t1c)Dc0]suK9g\ٹ_LxZ\~H4vXit^y0qq 9E iRڰXũo?Bג?B[t[0vcoqT}qݶ1YW!{;|6e5d5_˯ۛ'zקK{>)'3|HkYѝǥ -tL` Xvp\x`A(K|z`LY+y-yZ.[^'TNEt!°&Y%*J(*Pln\ -(Bq=t1ӎ'cnR?aƘIRl1_*e*E(J1~03Ƌ7~RTkQ?F)Vʔo⏽*,>RA_9:ʏ||N6+bܟ y>'ϟrrsX^PY-_0͠e)XsrڒdkO۪Lp7YOeq y6IkgENBqKrhB(J_Ey"eLboPbS%P# 1t,>B?;'Ox"!Cߊ҂P4~CzR&~ݥ$ڶn{A>ְ~"E9~,h,5U0ޱX+ޑm%%{ֽ8kf]]o#wwcfk\&PU-{) W ߿-"<4W\JtyϓOj=DzD_[>?<(Ё @F0,`Np<x@ dCG?mID̍FmIѝst}*$O| !.Rg)LWD(9<=} ӑC#8b,:|(<;I^tqEϢk5]U7(vio͢N=EώA}!ְ{"|<45-h^W`$v4HMc)=_a]-f7cѳ!,z_w}=+4ls}toYI爕0=EO{=ϮDz`Ssmjv|M6aq({|-uU W?JbêRKwܰiV/+398.-P=`3X -67x >  D!Cr&+3 dfe K:ݼgWIilOWH ŅJ(h"2E}T(N)BQ8 ZFzA˷7ĘsT_[Pe!sT?~R -r\r(Q)0  D!C񞹎L0x^[T?R?Ny Uݺh·QLi m[J -uhKoZ[g}gT1*hqo y %_뙁elǩYɵWN*⢝ӥm3U2{dE:gE ǥ -tL` Xvp\x`A( y(vꙫCzs1jjFmS(E}gwO<YJWyzXbQRb/iFzz`?/ůƌX#<=c .ůRTJq<ҞW -cob(Jqu -ܰȾ&8mB)v))J?9LIcP} hs,Ӧg'd$x6s7ާ?3P";oH)=VSe;ٔ]oSoݎO8 3tg9a\aR̘78,o_c=dzHQu~utw޿xj;qM$PXu4_\,xgVDW7o-mQ4c:Ѓh`&0`;8 .p a@b<^pN|\]\K>ε%sd\< -RB7VG*ʦT婣t9n|ELO=cQݴJ6~lZd[9SNY<2`;VhOY+Y+iqd+ -)Lr4g"c)RmЗp^0r6|H# ?&b0;}]0&]r㴅j]i}JڰDGo#մq*ڴHg>Ɩ _0-_0CniWa]VȳIJO2>d5y5_z -%ǧK Oeʭf_ʊ8-*H-Ep\Z@z0 fl`8n|8A$! B NC*"L3̲%lmၷ+=1S+bPpJ(֨\D EJ(*RaL%QLsgS1zR|I9RJ)ƩlZŦnRXiQ??wt\0Jq2҃(ů˥05U0`Tgyz&R}&ӡ4j40`V^AXD@0D -11h'M~10uXGL눹XV qUr i ޝ'M*^jޏȕ_[+?R2Y de(B3?IGjU2UB)De+JUrGJ(揖Ü4yGԳ^pC\{ UjWn~[U? IYK?PnȤ&+r9CTVI-+ =3Nq UБ-/҆]-bڸem:}μjZ[{c_8Vq;],CG/֝bf狙lU,qU|S#o KfUK2ͳ#pjǥ -tL` Xvp\x`A(Z$$d3MS-33lB5b:ﴁ aJ!*.TY,T揸J(*PU -/UBQ9#.T"0+XR'7J%_]eE)_8Vh=V*oOq;P*y˪ -f)LG%B]?NO% y.&_JED.X1;qDH }tדk5ŭz.HZ{oPEaG5C`0Vmr1%KXkOnwOvlbʄ=W*xOeXgűʼdJqyc47{nT9W;/ü>7r#W&_LߋyC)AJ~*S7¿j$<0K8GHP0ĉ\z_O^w.&rE?󡌐ceR}`R{R8uE 9҆7LiӾOǚ-z Σ}QQymU]ZȳNh|K ,ەɖ.t5_av3UYYR+(Ё @F0,`Np<x@ dCdHã J5&͇Rμ87pc>z8 -(JS~9m$uʩߪQjR$/xrػŘQJJ)v*3Ie\5^)>>YQ?2oyVㅫ6f -(Ё @F0,`Np<x@ dCp3[/ܴS3`em3݂GD)[Dy7;r[ -R|y~*UJ5=ZۈJR?N]KqRrR,V0rTfR({9*3E(.7]~Lݩ4>Rlx"e|]-2)y{A_¹zPrrP^z?MK9?JS݇7Qwb!ӜlCf!$;La!,@[ -잳G7}óǣ{<v+ZYE*BEXZk-"Rj.`bEDNE^h^vZ.O.Q}?3ϝLq#W;[}t72_N~Đki~26mLmekj;ePGY7XOB1NUJ8,Kq^=5Z׆ -}&Ӽ{+UO-68C86Wn_t] ->?\xf~b2)\z F04V`vpR,^< 2(0D -\OBRȉK^Z6U`$b˻#5E")¢(#.VMD7՚v5)nWB&ŴqLGdL{%cOդ8Ԥk}&O:Ųq/K>>/r}D'[QSZ>7D?K||:#Hׂ}ftK&E뉉oxcaMe\2u#):_ -\^MYz ֣[)$k}3~ΕK2gBK*ٹ*x.=`#`+0`;8 /AA"oH& -sȢ$eYY Vp˞W¼&.^R8Ԥح5Q]:5)`~gb8j׌y.#~sh'EƊf:0gdR4?&ŭ5H)~u*$M^#) /{d՛}L ҾB$!+icL£f N 9/'&QbPii5qMNC="D}s Y_`8;r~t)E79pa ¡ILqkb r`B7S[uKMRW4Kz&Ͻ̐v.2-AMejDR&EtJPPSȜYH34f}\4֗ZlR{CIQ=2Iq7ђ$UiijRl@N&U#))I&>5)&htXZXDM|sr2$E%LΌ -H_QSOd[QSİpC}ddciA_ZA|4W|/ fpAT#?e@M13ID}6}d$cG1e:=pv1ʲSk^bv4J;ZUaK_>x:7޺LLf xzPtNT:GjG}-ّ$ϥ `P@,`l`8!\<?p"H !C=NI&h>q}=I!BJ~EI -ɼy똧HidȺ- 5)cT򸁪ڧ(P%ei*7X{))AqbvgS.PF>pWx:dSS_dr˟o8@h.jϑ˫2fJvgΏ ')\z F04V`vpR,^< 2(0D -ߔDI(G2*0Qb8$g(1@F!8 -Jb&w~Yqi}jP}A`A4Ŕ(Sb9oԇ4v>W{=Mcc#QBF3,1 -G^DEqwAϊBx G^•6vP\_N,`/I!-+H縑XUw}KwlYi;aqt>ISCNNsN˒UU?7wGN;]=޺Յ{y@n~۲t_)!}RSۄ\UBx!ǟ~|C` XNHx Ƞ@($z"Bk;S!A=H/6%+&bpT2}.?=IXBe|Ш4"AJSkg4"qDH5cutUnվ/Щsbzts@3ZqkFM~Zҗ~/Wɟ~ZI 7w"^5.!Rnnn,/}it_D)'/=FDH1j)_S:L?!N_ddzkh2884|j$ٚbg΢l4%}0'iG3Y–¿;s5oS6d[eG6>lJ\z F04V`vpR,^< 2(0D -lFh0L!DS2:QlY#-?rFn&-DLyOE~Aq|X蜦E5hŴqܔ^`nҺI&ƀ:*nG4q#,t!)IopלxQQ$\werWm,sa2ܿ,B^˟+  -_^ܘO1_@xAɗ2T۪;;ܐzkN ^P2LK T]2W>MY -K6R̚YےhGIYM9E*w0{6[ߖohZ.j.v?#7\MQfpkGv'x.=`#`+0`;8 /AA"ȉ ~d6]@*~7O݌JH5)j$|+' jWrwG75nQ{:9BgjQz I^)1TI]6)>}/ycɏ+IQ܈b_PSC7^mKä=pj"ADFZbO\ b12M9`2)Ȑ EK(ce e;;jXN'J(ˁ&X7?F1{Ym)cHsU=Kodw_)B>_&;מ7~=_>]ܸ!#ߦf. 7?)ߚL -<H0L@ fTp n - BQk5ou3~nw]ew{=W9 "/eLm>h>PkOOQSǁ2G Q>FPC k5J -йE=xqsEINŦQR^ҡh>Bm() { W((,tf6_Fxxp")&sG~(w+Lwnfg - AQj1^e^`--Ohǚ>YĦ\3l{U]]xN=%{+6fm䶼/Vl..[6GnMVfhəy]ّ]ɠs` XNHx Ƞ@(ś -(M+fJgw"(<,=ĎH -<27b:5(n;zA_ -JqFPlAqU95'tH{sFPVwDi,tW1uGdA1oG#?yIﮕ~u#޻V"(<,}(VV&o"PQ,{oO{ Wa[ !&qbrQVD Ƨ I|N,>XV]w]Erd]6Yeai#]ԁit4skg9k4`1a_K+dǩub"b9ڶ&mzq+nMx:n gVq/<G}}lT۽^'[SJӅ>I:\,QGA@` -h0 ' Xpx@ dP a@CߪNBP (a',{ [ao&krvv³v&u|!4bsbQ4>PE㚜'}VFjQxc/JU9%fT٤N>mcghzO'W4҄c?HU!Twp<%\T 1jz^An _/)/2ry#D\4Suv|Fe^fS4X/좘©{oac)Γ5_ưOQ<'efq{[J -MF%? -̕wf*me~S3?2;ϊ 0 ( 6 -.` 8A$A! =>;qS2ͦtf{g$>6=Kl!![ٲ0b|H #?1۫}s:5(L3q̠-OeGÃ>5(Ԡh(?&ǝ&>q܊J{AOO95M_@PDVOi9}\~"=-m/lKB}pqX:edr| r)?ˏb`06V7)EFOԗɐ-2x2>M T3=XJ,&X -S<5%Y-eqŧ[Rs\yϞLqsV3;|fHSU2(\z F04V`vpR,^< 2(0D -ߍ&!b #(fآ -9qn'ω"(LIye©_ãW_IGc_UxNXd+?RZ/ϡ=`@>C^0")Ŀ9??ۏMԯ}!\dXO˞L]jU5E]O6PkMlbsvH;dS7|M0l?nS+{׬ڛC uH+Ed*;B-DzT͏tL -<H0L@ fTp n - BQ?$P21-km]qp!$^6BAI!|⻑̍BbR5ZK-)~QRd+W%)'GO7+3c4adMѳv[7Uo;Zw~e+N -dM5v҃8۫} -jo:cgpNgO/voϞ3;h O6ܹG_# fc)!zҚorzB9\u"`9I=g 0 ( 6 -.` 8A$A!Q?+t7."ZJ0ORQL8 [B'އrTL+/EjQn4RdZo4)rFcrt)cފȒR:t۴;V#E0yw)"_k5zcW(43ٓ<*>7[e7 )pU!usCMw'7!E+) dB/#? $3 /I`d X§H݇&hѝe%d-ejgEdnz4}9bNb_UV=ϦrzaN'O [9_Q?0o~bP2"!n+d*g/}-Yo@` -h0 ' Xpx@ dP a@9%':IZ6Kb$l;B/DVpK7E:^RLUFHs5)Z4qs #~|h(}zc'5bڙiP0*`IF9 -I{O𡒬7f,г2yC%t&~"73=<%\z#M+ 9|Nqi( L>UK?&wV3 J3~̙m2UI]~y5Z֗Ɯz T';dGe|H-m\'%Մ{IO#߱^o۵^,|I'7Ke)?N>SN^ .\_.zZ&qVo@` -h0 ' Xpx@ dP a@Bio4vEP z0',݄`z [7a?@8zgg8A](=@$PzPn>iFRJFܣ [SF3䳫.sbCS>=j_4b%KFjr+ok 9|$beu(@ iG$G|m̬y3^8 ? { W(Vҹ˜b91|6PHtAb,]+w>IrgeKwi6yfPRf<*gj~v2Uz][f^YNolړƔѿʎ2ٸH=0*K6Rp@xxWW踍*u|jPX;!V-v];ꔪ -]hmF]au.RBrg `P@,`l`8!\<?p"H !CxBN؛[ DX f3a%턣pc` w"̈́QBh%^Bj Bi}g]Xy\FWcf5V&jJ8bqe=^ TyN]9Q|+g`z#E 0QyBR埾W)_B[DZjw^本?Ι;ϗ{_g\_N;go9s}sc)Ϝsq{s/_ý9? ))R<\~i)үçji씀/;Kl>˗ }A>3KRSrQjʸwMK>F(hx^^^89ẂV@,}Sq_TS}n)e*{28̲VncmUu#ӎL^\'^GVEStEêtn;Ӆ3׭uHy?{3=T|ljhȦSDǥ`#P@ `+` .` 8A$A!Qx]LG !x'&eK)c#:\av[^s2?^EEȲ %"IѤmO:tߑ4q_~3R˿3K3̬yz5~gUM]CIw\K NIN&'@HN$99IIN -%'"^z֙?k瓺?l_Y}埕'~Vzqğgu*?YYzϚ~ߓ\Ys?n M%~ֺpu<%l1yP ?UH!r찟I>%B%M>iҠ$_J, ))TRRS ~8P]D(ʾ+j%wVQՔp^Kg*)Sz\|fm)q[9:\l{gN[u<ݟ,}~*_ ' rt=Oɛq98.L` XvpNp n - BP½ Qci1G-]q(Y- ?G^A#PH?ŵj(.j?I¦j(ZIwx<AC[^8q(}o)ARYخQʼnRmRthSE=p'(ŝ Oz?QQvrSbq#J͠ߍ7^o%\R"++ -)`PB)_Vv ŗ˧eL>"kurRwtGV/:b@uGRLN1:pe*޺{Ä^^Wv'3&rOcs{Oڭw+ /LV=bCPNkgKK -MV*W&6,_^ݺȆjk'`#P@ `+` .` 8A$A!QJL  C#al&]0̈́"l}7#v%@w+i!̈́#|-!l DȻAVԬlJP?|YaԬY1jd!dD,>y@U"/Jo[jU4E7$2grc\T2gb}߯ɇ5EUR7I1UizU~DZL9rSzS#LyXKJUI*\jn>ǿ, pb,戁~U9P>L˝^e\ FjV 1z?r3)cigӖ=Kniv}۬.)~s$->s!Og" F;&Wo%5ofE#*޿c;|&:HЃ@ &0`;8'7x ~D@BD!~(%$aa,$2#L,ep"䤈` -S@x[FPJellmI\՜ܦġ!䤵_Nr$O@9_ꛓԜܢyrf95' Cʼ!9ztsZw9ٺrN,g\V9)/Bd9̚| W%'\ZP4'BvfZjrs>!}Y3KK IJ~9iX_$3XΔc%ҷWRUqz~2-5Bkk5Ҿ"H3߱Ϊ k.+{ˀS;,{?SUMOG5z|tǦLXt@ ` -h0,` 8,^< 2(0D -Cu -2 9,gBIf2*d,g&2 -;쑼_^B_aP\&Pd%'BqF(j( -Ph8P E>1wPҎ5JI#VAk53P,Z80w$a(RN ODk -?1w2U -ȕYQ -N}YN(q\n0-*Z^>W/;d%Oү. )cE]2x2/[)}FNӎ40}riK?+6s*_m:w칩g=w˛jҕҌЙiuGV=)p\: A04  /AA"KRd75K:k0J-=aoD>"bDdIFu>L-VgF)&$'J1D)5FEĿ_bvRO@F>O([ b+)j(<ϫWR~2I!?żݼ^+M.:S"kD^YM="c/| W'\ -?h()bn W2sr|B.~RSs|1!BQ4D(6\׵J^aHq0T)Ex2mz29?Rogm5u#ӎ4S:wRlwOzRvH:+ߟWjljO;W476.i}=1Rt@ ` -h0,` 8,^< 2(0D z#{͕7)dx=6#H#+%N%!,*(G=FK_d5#8S:f#ňWX{Dhߑj)5F_81RUOg R(!UcH1»uJ$GG)i9)F -^gjIFH7>_U: ~u$H)>!el1%XjPur};&4m,HC[uqWM5cS+XmYXϴ?1޽ukef:u1Ol~w<5+yB}o"W[ bB~.rۄ|B9wq Sm'" ի#8FL` XvpNp n - BP Bdc0-4|ºV{e$`JKFz o''K)ܺZce2J7H Ռ| -c~ЌW&eDw~oFfdU4"+1pSEօC/%=~3+2YD]x$?EFBndY'9`fV,7ŗY)Ԭ@05I\tUO^vdGuK_Gkf7/ +Qƞ<=^ZNv?C7^fi}`_ؑ_L~YOղpoS<--ӹESڭ3Ɖe[҉#w{HWO^7-شH z0(fl`0x Ƞ@(\*%dB3LQCM ( :-xL&q =DL&8j)R4J'(¥ RP4n\ɁPUsRS؏RՁZRRR?qZKRܡu͝R,JmloNP?*w2"}a2)Bp$uM^JM}sAjjjnnR/MJ4|ԾDjo% ;(r^LeoR)kclm#G8^4(쩼|XpWU<6dOL叿4S(8qϊ#'ӕݝ?$qǥ`#P@ `+` .` 8A$A!QJTRL67Π"D `da:"$b";=3;fS qsy2qbF)>VKAi@]E$JqF)C(WڷuO(}CH śEv(Xt';?&B1RpC)6ZC(&ceH1.bGX9Bq{T!7"Cs丣1Rϼ>_UZ⠡ f/5-2lH!f bj}ϬO^V}P\uYdn#gܰjCM -u3m'R%ź–D-EKdfSن35FҺ^ooJJvU'.OJ|%mZ,LVV7T%:9Ȯ/&G;t@u@ ` -h0,` 8,^< 2(0D -l]C|VaMT-A7ܻ#G;4( v{nZ[OBcnЎݠ ?ܾePFU^TOx5U:dhT%:V%O@UiFξUZ BYcpzֶKvGeb7oPQ[EU?!?US"ç? уuƦ}4~p1!> CR9_fDiVPJ㤴ܜbk*季7MvݟΌ0a\0.0S5_ S3X[޺Ў4S"m\Vрc==iޚMVe?.,}zxfC**[.<*zkjxi": A04  /AA"H[gF=TVL*c{7Šn#yy$ʜ" F(d5ib -Y ş5Bs?#ǽSܶ(}GPܣ5u:~8Pt۷Wwe81zBBP\A(<`ʜnP$"NONY *)LJ/O|8KJg4J,)b)?b|(fB(=':4D(U[ɶ'g +)GT]25USG 6o8N; -I3Ίs-{Vo 7x"{N/ە)l!n'vlpH+>[nKWLVN i4 -HЃ@ &0`;8'7x ~D@BDҵ E齄7ͼEmabBg7{ %"($IV CnPj(F2$] !sP -.lRD(}>W5*j,T$uhQ*5Ja74}xK^O㥘R44h\$}cP -9BƜ -w9LBetQW/*"eJVZNVVnV̗%1 -Rrqi*m(EǣO<(Ŧ3/a8SՏPSè}K(z|0K2Z6Ҿ?F3ykKV.YF5^O!<#=Z0-\ִHD)p\: A04  /AA": Q$:;RHrH1~U"A7RR|Q?oRX-E:Ri?B)Үtoy=>G ]j(~1RxP#g4>ጆis7a@(nޛX{\ F$]߉PJ)3E" #9} -~[GĴ`fǥ,Ol);'& SC?=z{D(cJi?UI*koE5PONbF(nJ')f 4Qtw>P,P'(($>PHC?}5u:<=i";![jWSˤ61BbxQ!m%\i:‹BK>!37!I`NV ;-' 2VWO>΃^Ol8(]Ysl[.?Zbݏ.1cť'uݖc^f XHLgOZd|Q_[ Vo!xg !ȻKh# e[PDx6"Ҹ%)Ǩ`#P@ `+` .` 8A$A!QJ( s"s)acc{^N81 {9 w_@8G//Rf8d_Uj0j$WI-|b&U!+elĜ4_;v?ݻk;mv - ]Hc(u}T̘-3ֶ3m9ƾ-ֱe5Y i(CSU=D;ZBp5ͭbBl/Ohl ōDhkۈw㷻kᆮH-#腦ތB)wd[H Oi+#f,%_պ:YLN ->CB@e"_rRRr 3듋K -%z?j՜u?eX2vV=2uǞ[Zߦ?iP1ұb$ng\G^U/izmwiTƊ܆'#ǻRGMd(SC_Mw4#qMkk: A04  /AA"LMB=&&%J'#u-l8=Fp]9Q:}ZTH:_ z0(fl`0x Ƞ@(%  L)Rx;琙ooxn<$Qk?ތ4ΏF{NT-E5YR 0x)*)O}OA-UI% Z]k)|<[L1OSk\׵J^8?KYP]M/ 肧(ӡ50KU-e/c[H{##5d 8\]]zZ^wepEf -"sJ3BUL4t%J z0(fl`0x Ƞ@K]\!RxP9`:ƏRl-y&yC~yYPDYRdw+):F)~>S'j)RWR~/hm"B)+->4R7DO뛢.v57W~:RlşPis: -"Ov "9NK|/(%NO }ߛ>_)KAj&$q)LM bJ0)ilHQu~8\J{%qγ%׈)n\ r#;BT9R{| Wi >RpYi11,ї#9xg7Fs_}.> o%O.߾2z+)H\1#G:d\d-')W+[p:;x򾖽^罚uN7<5SX"".muH_,GRnFp͛#+Z#K$FV/AA"KwF dh`h{wqn -F#)~Y-w\(EgmX4/C)#(}Z{qF)Zԙ£QCz66c^sAM~b,>>`ءOvy&#EH~+ߕeư$_}iٱ`efIWXQ*=8p]?WgRW)czwmL[(szKg#e-X=H{}HGUH1]E׹>܇9rl nig -wFĆқ\rrpЮwΈ)Kq=`3X -6p Xpx@ dP a@.->yzqI&V':/4އ/_d^|^?4JɰD)jbZ߫(ŃC)8'4R tQHRLRK߭{5J>vz!BԸ:k>F5cx̬_ uD)^c/ -=Gz3J<!oKJ>>2%A_G0ś⋥Ҥ@fn0GiOY>qQ ArK3q_inx1dM%6vocjYGC-4ll{q~#l}!O׮x4 k+!,kYB%J OroJ"Z3^6"܎eTHЃ@ &0`;8'7x ~D@BDw:+_F$ K c AUt9aZF㻹 - [a/#e4~%eZ*rB(&|B&eEWHV /ԪeHI -9IJB2PON#7^Ԙz圚FNC IW@99+_erƽKԜXr5'Ϩ991!' r?OzZx?Pyndj䳕rSC䝾a%\46/KI(T BKS2})xhL` XvpNp n - BPާ*(QBXV &Իݓ=<2Nʽvf\6P<4|u_}xPBf -A~(~*W.QvCcaxcMߡvX:).D$"c0MwqKmEϗpޡvbJ,'OĠ/!&d<:0x,sgw{t+NɃGěqճ4u$CʘZsK%m:Xw=z{q}i/Ysq9dk渏/#"תnA=`3X -6p Xpx@ dP a@.*-1'>j6TBX{A㷤}'΋^ -~)%/>gxFAbuD-=!K|SQ x6԰D@zJF֭ r9Z|w:5>(cjp,kYLۚcX^2Ue3uk A܅_b_r)]WC,9O0^3h>b|ސ2)JWj 5i〚l|LQL]RU|lA 9)cA`صϗpq\᥋9b*'f2'dff,iRn^8/O}EuVhWL_#Fa<ʼ{kkmu]3*k\WXewٱŲhItns}taqbzTrϖ)ѕ{W,BhL` XvpNp n - ¥_|1(0a2<ޢXĄQ U!nJ72F PŒEP,y$?՘3r0g^9POvP 3I -9Q ;n7L1g}ϗpgƸ)ܬ`HF b9R ~؀9c[wmzQArU9*V1'SQ{:ocXΘ+β]z쥗=bato`\gنs{N?{is)) w}X7crUX:딒N>lxN]u@ ` -h0,` 8,^< 2(0D -CY6)f_s}н(>h̉߰˔B,&eE+}ZF>nR_bF>tC3\w-m,J/>#zƜ_qyLG3ȇ~sƣ7λs'%.}?m; ]rS|UT| WLN{LC9iپ@0?q'VD>4ҭy'H~]Bypp9c*^cjb̍Җ3{kG{&qu=ӹz*d[:pg{q o{a U>D'zzLy.5~R״FDlVo1=`3X -6p Xpx@ dP a@. %~Cq4Ga s̒G`>ǑG0-ȂEb^U?zQŧՋ*եJJ:Z%$J~GzU%8j|½)G?rhHhWUkѐn[Y- -y.Fo;#CCΤRCtUE f4 _皚b/eRRܔ,TUwGM6JW$DCZCëIt SO=cԝd̴ucu2]tq55pg7v֡!^BC!ѐ]%?4{R=r#HV!8FL` XvpNp n - OűQSe¾[K K"BZAqGjI(A,#sFI$KZ24(]݃ԘFdu(4bSa!,ffऩ %x/J%/tJK^ gHT+?48)DEI#,f}tփKRr'eRNO7x_jZ>K -?QRs׵oq=\Wsylg~# O06 zΏW7tsӷ,y-'Fڗю4S-QU=7M/y-ͻtx -wlk:_iBa̍'Li[3%ޑY65Rg=p\: A04  /AA"Kw g'P2-ds2(;QE{z~g=lj))j)ՙcF)._ =x|R5G¾=~_[W)jR(VCT53Lj!Y p~[/i''nsk"y +B,I Oaϗp6^es|glޤR҂J;[?ޑ9m8H9یr=c.# }h -2uOЖ뱣3m_lcGYf3EWGA]{bs?>f[ {G^Kj8qu8mDxC fߪ3Q$FV\<?p"H !CлGF y S9a.',%/q[Lz/_UPAKW[Wcz:TSG:uX5Z!dԱ|(w꘥;%4ZbQѬђ{pN a'&q%$ls|WخrSvnh'%\cS)iGD> S1iOL81 S۞Uߐip -+Y9?|~#èOPt3Rʼa')mÚcuV}Bj*l#wۉSi)S3k2=M'uH'^*In;751/qǥ`#P@ `+` .` 8A$A!Q_}Ի+L4fm;ZfQߏG~ޔ~/>bk-MqRRWR_~A[5E `ǎK P)F(RCU QP;MaioK:?ygabX̼ ɡ }"*äڊI>_zC{:i10jY>!E|i))/5-Ă)b}rQ?pݱVrP唱0LuՔ0KӔu'xHiG74SrleM*-+ݿtnג|A v+ JPdži_N& -HЃ@ &0`;8'7x ~D@BD!~(StFiYLNLcn#z'܄{M`k5FydF(ƩUG_jˡ!Ej*QZ}YeF)ΨءuJT>:WSrǬ 'ay򻐟uHQ1L$˯rħ -yۆ; UEH[>_U*EpϊrX8#_2bi/|tYɽ;fP3(R=wEUS/QcKW(k3clK?i?^:H1^*ʮ{YU>[y iwg -#Rg26O<;=oqYHЃ@ &0`;8'7x ~D@BDH1zdSHְm={GFgWKO,A)nPKȰ_j.__Q -JbҠ-<)X>txa\qT{v΀/1mLcD52Jmuѐ!ܬ6J[l'mQSh8]m&!?Ҹj::+mxc;2A$SH mIJF4d){09i4>_պmciYB,e!i)/S`ˑ0dvFQ}~p]e#52.+S躇) -|0Kqe=V7W}K;.|rbe+7O[t/ҹL;ߛ!h'V~{|hEHFugE.5 HЃ@ &0`;8'7x ~D@BDSD# e,KDS,Y[&'9&^5 ܊'e.‡bT -aEA(5~PH|XPUCN=qV#Ç0l5;5DiN4JRtRIĠQ!½XzP)~=SD'|+q04oƴOҳr/%XTϗp ~9\fJ07Y4NRܔ`Nka853yG׵찒Kf+S)a -\B -({o&vNe;]{leNfg'a]lyP&`na}N}zcDb 0RĈ=)"" 'b4Hʎ".7 {~#S,O'u[ٶvvl9:|D\d*}q\:O"b1tpyoG2>j+ oxr^|c*)\:@0L` 0`Np n - _+I!"іp omȇO^{WnaIq_3դ&5)25b}tW'sF Z[򫉂bQdlPܫEFP<u%/TPdkEh+UuK?)#tjśn܅e-@[S[/nQu)yk-1/WiKJ+8>'qFn+-\)(bJ-UAn,]{Cm^o^b#JF=A3P1o_|0}ٶlَY72l{ۀDn<㪏=!kڥMrGrXh+";SAz0`3X+p Xpx@ dP a@I΂.0Im&jc chrBt=dI!a/G h_i{3SA1 - -$iil%J'Zo?QbFRg [W:()I}HLNZZDM?ȉ_xhՈSr\B!UK}$UqǕ@~tߓ3V8x$K-w<1b֓] ݵzvak}9Zx:[eoE"yP3K]M.TSY'Mϥ -`#fVx Ƞ@(|1Iq!f.KKٖZhs[!\|!"F<{\RԤ8iEM -iH&fq/bQ_O+դk}=UAߤQS|&w#Kxn[^àFqO ːCH{trWf -S#0%\9kx% -_YeGYf<[Y΋J¸AY5[RK[v}M7B.;CpyqJ<堏0mhww>E[O8MwX:aX;pum~ׇm W2mYҙn|##DPa,HH z`2֪3?u&;3ɫj3%x~řOkpu5ĩc|_Q#J'9|}ړc(9%#5Jԍ5&Qt<4(:㍻R?1F\_"J}rW>Էn߉(aKF;ī ܊e(:*/W^W.U+_H|qcK<*::>ҭG{r}3İybܿTOT1wQ$Kj֟\G c?be;;~GLm|Voy0{OܗmͶ/v MwVWjc wEųxLoMW̿rwtߺڈ]Zv<4R7 ϋp7 cyC&cq1.+kc5˺EOn&&{\PdAEQjI1G 8;hzիA'n>tQPkTW< DP[5JI{^cKH֪TWsH˷J%p IQL#)|H -%qH5}$EUߘp"pg<ڋJ_Yb,㹾S^#㒢 IqKNfZw0TKİ%En t%E޲m-aa^϶y/ J#s0q$[ w(7do߉"GEܪ'D,qd]~6cUrrQh`sI泒Pjϥ -`#fVx Ƞ@($Er5W"H -hUl=l>BCF\bJyIq^\#)]j$QMhgT$B.2$ţ%Ņ3QzؤWBϪIFM:uFRĊ#?`|Oy!) IqWwb:4,'~U[G> Q]")Ƭ+"'^)@'T}G (ǯxܘ99eZ-ZbXC;Ɏ ^ -INo9D.zl}&lFcܭ gwuKdk@/ 8Klo|Be[E J"m;SIz0`3X+p Xpx@ dP a@&G5,Q&bla{1I!|#<[q5)ǭ7cN4P¢&g5X.Ƭ)ˣ%wlFRdNM%O4JGاItߙ|O=}e/ hH9oI1/'~ERCaJiP֓Ƽku,y`EWʥJΗꢒ TK+QYKj$i }tg'7Zbj$ų) -ݧhMT=lFդx@#)f55)Vi$E$;j%u_|E|ZbԂ͖%jꍴUtG.eϦ1/-hK|}xO(K}R9'h@r+S[ژYiO)K;ڶR?01\X8i׏Nl0/ ٶ#}Ѷu.>K\ݫcwřŗW\.Zc#󄭗⦇҆m[ BkPn/46j -<(Ѓ@ Xvp\<?p"H !CV)yX]0ē)ZdFтfNiNjIW)nFS!?oYG()^R-6?+RJOrWCʂPRƼk|\etZYYE,B̫UA.|Ѵ#2ʼn,]6u gCw#1ѓ}u91XEg-+|DIj mC}9=Vc}I_^_[̟:_87K<.M^]Wl* -]h* /NKF @ ``;8 .` 8A$A!Q.B4(F?Qh$9̱m!{$|,=W򇸹|#:nAS[#(>.ݩ{SbFPDILiK뜾3\&I6%uTRt?Z0eZ%T#)#)@RH")~aFR,I;rW$E1g;H;ǼkWU|@@4 P+>_~>KՓ̈́^<1u>Otk]owa}&۱j&W>mpw *aٻj{_j:[̷7!.RSg|lH鋅v.;/rt_s=h0,l`8,^< 2(0D -GH -m5[()dIgnG:h2 դx\#)In2$v"'l>NFc4&IVMIQ1Z'|^񗙞1K<. =vBPuWq’ũϥ -`#fVx Ƞ@($%:5$LKpr~dc&4S7&>}JFMJ%~uz@GM")֚2Dw]Dpn:%'~U(?;=D} +76\? |/ɽ_iGyeYyrr $#HY~$mla ]ѰB.&o4uS, ]ھ-sunzIawm Osw`gkEб߱vYNmJןB+k)M%KF @ ``;8 .` 8A$A!QH<['")UY-fEbDEܡw78E%E(K -;5bHx5]#)ILNQ3)5ŚjuQ5j$ŻSRIUWjMaJ'nM {Ky)T!N;EʱEMvPNPVS3QS:UhrbNU"// H|Bhn@նgtYWܭo{*t+zo'>F^!dzmgWfkv~\g1MWgzs=% -ڥ oĔše煫??zR=<(Ѓ@ Xvp\<?p"H !CxΙ\=M&\2)x )8s^C*҄o572ҭS|y7'+ ')(}5)ht#jJQ}T>$U?<ԌӥDV$ڻE5C25#By-at}Shx}KVO5s)Ob'zO-<(ɍߠ8~,K״_^^B kx>Ketr 1W-ktm;?U>9GM4jSmE)W6uI )݇-US-FRt)׿4sJpLrV}.>sff '|VlbRAUjoP -\( hA4F0,`BNp`A? !_J@͠ZA'В^08~hfB Nr\+ŐKA-]&EJ\ -\+R̞) -`/KqT=(Ž(œcw!%#Jq\Jq^.Ŵߍ.wOGJ"wK1bK8z.yC*CR\;RPyjw _(W?%tdJo¥*?~)0J{;59qs|:n69QHII2)JR ^+RSE&j3E?5C~5:Æ/'+י?LT/i[{tqeo9 "ٱNt*?RKQpE:H~YH)+,>BB))F.+JqP9+Qµ(śGxD R|OlD)&\Rp㗂INH%'S"!9KC3tMKL*E-JH)r۰a?;jhͱ,ɺu9Ioh\\Yi>#Ęnq.DžRP -N.RJRܣP)GJbB)(KqKR<1V)|#DYRܢ0S\H)N+ur)RhNܫP -=f>"6 OmC)m)}ïJyg1SAʜtěpiJMd$|in.#]pRw}^/"*(ӖC'"윢o*\uhMų:Su:&WoxFg86XU3e4fyrie&{i68,Itn])ps- sA~VÛK{X_@JqX.5PЂhЃ`3X -680< ~@BC)V$:k!S|eQCt]~\g (JJA˥x@,RX2>yxH.EB)0R$?SdU -s]RɥѥxK)R̐K\u -3kئHxB+f>7fSg,a@)-I( ~U}hm\9|YUMbסyҙn d%sE)p]j -4Рfl q`8`x@  Q -Iqt@2o}Hl778N%yFυy$?+#BR(DJZax].E)EB)*&PţK *!D_3KWWY7.}r)~H)Jqffb>.}L1@)LQuJ~3oW) Pt>x.>;n)|<#dex7%'SRS _:çdR`wp"xgMG5ǣ&DӇ?ַ6G)ND kYZmN{l&/K k]|Lϱl˷qߝßUuyR9%ץ -@Z z0L` Xvp\XDB—oh(Ӣ^0(<ε(dS@)x.Q -R|P2yqB)֩"xI) -'z)VX3֭}#Kq\VK̑R\OqR.y*>sHEarQWgc푙fx~U\cěpJ2"#AHMgd7Rxǻӽi,Ϧ%$$*E.JQk#詜.`>ʈ:dnh:3Zha]dcѦ{6M}N[zi[[آ7tq=kq?%rs{ڞ佻콕x}*I8Oj0ߟ~KlQRH)p]j -4Рfl q`8`x@  |)z.JЈZ?V~d`I43(8\ -rHX}0÷>Ny,>D)4MJ-bLqeRhJQ<"/jܛXՒFI!ed(nC!)Iy0(,>OSɡX0R,@(rNSNHprlZPoHqCBe Js/ԵP Hjo¥ڦ -!#KeR0Hħ;qt+d$0<>zy{z=֚)JUQ5Eڝ=u{NG _a6L665b訙f)m>.&*Q(]U ~-\{W2/*oB(ϓg>(ݑ,0 -\( hA4F0,`BNp`A? !_ - ײ#oL.,>D+; -;x,><Xbp¨bBJEHW$2:&0R|ŬjX*B#Co0EBB(Sd"Fa8>]TůD|? (~Ŷ1DoS%*_%AR2MD`qCUF7!lB;91sOHO22Fh wzIH(NQmvxwTњ'ŧ M&6}j7O ίiv{-M %ju~|Oշq> -$e͗}))p )xGE/BыP"E/BыP"E/BыP"E/BыP"E/BыP"E/BыP"E/BыP"E/BыP"E/B; -ׅPHZN<=' -#2sY LaDqtP<)мV!jyq*Z1[zT(FOX{Fb\ -}~ -|"RHh9Knz)dx(i(Ez!_cO~JX{|ڈ7Ҕ"?n)R}B:K ʺ ޸܉ )Tfiڷ6 -Ϯ9}ڤKe~Ljmob[ޛb mӃ1S6SKvk{ݶceZU,st$Oqe74OU]ajؓpN8!33k&#SOBSTQ-=&0`X;8 .,p"H!û'DML) LB}1ZbZi'Lb-",dZ#εĕMS *QSj0x<{7 of$ ۛB*<')oTUmRUN~O! ?+$kRIR ɛR,VxaҸ IcUkȹ9SB -s?S"_+ ⻿*oe,tQ[#Z0wco y_ـP|sH cO]]*0w,x.q||n&T&͝'X>Ud_BPda8emdhoRzv/b4ͻc{ޙ.хbVM5}f:m恷fZ1>WNHM2Bb/yضٽM֤<쥪^C~ќ$iל1T7O5UL{O-4wi)|ni*xO-sT~sh#ɺ!ԤH#&w[ B޾N(] Dji $pvw3 nyB5(Ѐt@ `VA,āx8A$C,U߅i&R?0,6.2-0avrG3ŵG.fqgѸLh<(Gí_3*9  D#w˗GNJơ= 1Kt GrN!90 ~,_~A*ȑ x{#ȽjDcI KW / 1݆doF j?RS^ws^aY_ǧ ,1o gߘ}H4:{T> Mє`)~{iq:Fx(TcZhBsљ[ֺw܎bx9ηOZb &jL DL|CLHc@ 1$SK9oF h7t}/Q$\z*bKNqBWџ>ڤzmzȦiQuOWPO)dhO O66?f7fim}?&vZ]ܩsW:߹y~uy1emSI\c~W -ٛoK?T_;_@Y-.l@Ah@ :A0 `+ p <  !AARnP?jI\``No,50|cgx2A\_Q},zϩMV8 -|V'~dL.D8 -˥P+}r)[.y1r)&~IAzyrlPW_s8REJ>~s]JH7(%@I A\{ěp>:GjZr|Z2eJ|"~HcB"OHg}BM}Mmӏm))"UVI&矟>)2.9ml;mzn0RE[[h[+ؚr]\*}9Q5β>WE) 7r(>~>Na(ϋC7bFGHW<㢮:9K#}~HFyF(ïb#|'Vg3G h}xt_J;\ -s ) it+z4>cUX =ÎIvKp9G k?$b.O5XIQ D`#  b!'   O:D"+t1-ܘ*_/qʛ+cNN1SN?,;ƌer=N*c0n٤̼HUPHVEUd4uQB<]}7a 9'Qe5ZQ.ZBUWd/ݵ8l#c% ĵe*U[zյC'ּPJ*k}Y}5U U*"U(Ѐt@ `VA,āx8A$CuG*mDBGn;q 1s5"ֽfFbUZx INm!|:B]ḑ >PgCL[yզPI;sw\Ф])rU -UgCgBU'GǨߺPHUZPJf -ߤ{~UysfU9o%I?\?RX  $+TǤ0۹9onRꝞMMS3QuOUx6ZshmuEӭцLYjN֪{f]\&{ֶǺJQ%>)5~]yW6k -7g'r@fwRP30.5PЂhЃ`3X -680< ~@Bz/3\OΈ3 LcR1~ǻ4.>%GodbQSZ=w#۬{MOԹ$+FSISumkm"+/'J/QrBj;>5!KLKKKLNUVvu5zu*]3GMMՀobmu=x}fp}xӖ/P惒[wQ=]ⲿIW| /To+*O{T7UܱQ3/.:?GU֔^QZ_W׽ale0.T D`#  b!'  \J}*DCLj-DD G.b91:MTk /Kd}S|;|nOHKMIIѷbXuWݱg#Y9srzITjd ?^nnU]O{d -ۍx.d%kd'ZNdeuV\o=ovYiތd +6#+[N~°RZHEVj +;ːjdJgrVpj -4Рfl q`8`x@  |)D.)%fk"tC 16S 17bm&pVo-SG¶K@"THd̝g򝜕Y -YY-?@|Eo*Bt| -ʇT)MҫI T媬X袮.UP. +o1 qQ~ i +x.QU$ LbZFםvs) U NF"*[rZ}:u`Xu58*ќi6QחO71|Phlȉ7=Y,՛PMmP6nns;rpqnn ^VyV}YŜU;a;ڼ-TwxMhy]\󓼳 UQ-=&0`X;8 .,p"H!K!tM=Ѷ]k'Tc#&bm! ;+-QFuH<Ï)5uD,'R k7*Te\+R&+{-eUqя7VUY7(ך'*fU.WeH* TeۣcTe@+0?9{MgS3֕cV#_(O1?93@lWaV #ބKu"Hαi>wzZ ^/CHq' |21՛T5فgEdJݻ^ -?D`tڴ {ꎵ9S?>W찜?iz>=6E[\˂گ3ߣG]]mۈmD/է }:`N"5gWzZHpHk.R D`#  b!'  /ez)zY}@CȰGb?ZWR;p.sex1dsq6"Ǥ "wR/w#~idJ{Ʒ1! -QX䪬?k5ATX,q;U Ȫ1TWQ/BUCUOo%.Ks'1^61ÿ{zJdl[bJRQUKZK?훢n5Qu~T>Ӯ;1YW^N_Pv|qhTLsɁiig^Ks}K̳:3_ƵOLNi[޵+abssfqg)[ٹsF&֢ -@Z z0L` Xvp\XDBci\Z^'Ҍ7FěY clTnxcC3#r#jɡmr$O)~9ɡHT:UyXYª'=9V)>ȓjKQ-7FJ?r)bR,u\;o]'rߕny-lmxǯD)NyO%e#ބK􄢄񟑘M;!9<$rn!)&&2\=Nb]7#Xwp:Qy:MnN}rr>N_Zgh=9ٸ)g',9Ӭl-Ǒmۯ[aW9-9|/-t5.`–k/ G7>}k[?|?6K D`#  b!'  /E[& ?` GZE4Xw."@)G)\?++bɽ}'jK\ -#)tjX(2!ޑxPPH(t - NHk=w V6݁Ju;*;TqY>H9Jz ̿aR P*P5PЂhЃ`3X -680< ~@B^MnY4^b%6bm$]Jw ߌ$n'Law CV"z#QX\)Wrw Teu?v(PX/ry8P*q.fz(#h~{9WI_1iiC_ԝx=LKKݺe`alQk_}=Gq^kz^F/Z&S!!"6@M${~@Ah@ :A0 `+ p <  !Axp?VjJH{y"o|Ȅu_!k=4BX~b"L>ayD#:~oBL1I"}z1wŞB2m0DǎlrK)1%+fhe -X;Ydc%ݝ5N𩣟պ -9=o_aRwoRy_%5%hn|F=/o1쭘j͵\LK{ݺ5ƅb - +syQg˻Z%u{Вђc1oFK hIEr%/a-,w%F5PЂhЃ`3X -680< ~@BL$hB}61dc>1-3gbO]&,g#e{/"B1ב -J~T:dQu#c>yʻ$:rP^PHܑVy&9xmG{d5:$3:rȚfiF~S3JoDG\DqEr).%H^4MT#^_:$ǻ}KgSݾTnR} \:MT~j||n&5wsBJ+$_yGsbȭR58hh3ߘ;F|?irYiǧ3-]Jm୅?:8ⴆd1CnbLt2N1lRwwϣ?֟˺^Z*"[ V (Kb-Xkl-"`P_N}9%g9|鮺)Ɔ5޳_qtG u4h?>BHuuhMߡqCz+zJ3jDЁ(&0`;8 .` XD@ >B jY祱3WRZ)K 8ZFxWHr%cKǥJUJǃxhTdR:ҡQ) dBͿIΐ7TJ8v{SJt&%(ϕk@H=(|?5oQr[!r5Y^^7KH#u( 3!%q)>> Dfe}ɼ(|nRV`ΐ划ʽϽ'%Xm)dOu8n[nlnVxR֮3m2팣_8rLa^y}:ŽtǓަ?`WIp_!m; aUĚ]RM5-MxH-:4fl`8 ^  !GyD@l?J=F/45?iYJX[r!|rOPHwG/*]Jz\4kT%=Jz䫤G==n3;,4yф+BOQnJz *sx*KT 1c("_h5 'zH~@T&}7s+cI_Eځ*rF3'٤A#lOLNݥg /ޠbڮ#K)ځz bAP_ї@ Pt7co>e"\UDYjz]SmECs[ *0%zS[6/Mc ^;ϮŊR4yE`Ю3ϥAz#  '7x ~`A0D (Am:c.sLas`K7!-n23#K#Dqt|Ø[կUف_ă¡F gxG:+i~2VG9*-XTFe_U%*Qj6e_Yw'{~WGIo~/{D m Aq.)Lz&H~"z0yի_Eڀ*\ ((;5':pl_FvJ/ss35c  ݝSG޷w'vJa}|f."K d،28Vaҧm는81E;i!!(QbJ9n?e0R}h)KILkKT[~T1Eκ)׺, SxJ.1߱m>w+wp|_UXX]f)x\`3[o w -<HH`3X -6/x@ dB^Dm dYyo""n8^QJPR.8tE -e4Ue%=]ڙgO=[*3爤)IqsALX" 0{2KHG[822܀㲣[2ؤlCMTǺ/ly7UyOdW&Mbqkt~Z3z6ƎY5fsRYվ(K:9,]ݪ^T Ϭ^XaE}% Ďޗ5R`FnB,/&5pVu<HH`3X -6/x@ dBt=^{ AB;䰅0!Lt.;;F0-FGu:'^B:HucPǕX?ʕaRB̞@r҅.s=4TW4+C?h{]*GK=l*Tc8?mAB|EZB`RS=$ -׹{*l%\A%/$gpΗ܌ @bN -/bV %eGnU.XU;E[^g!-H\m+$Pzi+@r cm1նЎU1ZʕWgap7o==[%o4qw[\]| ?*{JϦ҂UK $$@ F0,`Npn< 2!a}#,e!A1sm2[^_ީu-O4eP]%(*AK\؂>rE‘Aq*+o(AqT~*yTePޔ =4FPWWD+:otEcatF;֣"yy%=T&)<$SvτIqۈ/bGz$e&%Y>1)3ӊS|<ro#(?Xm& Y5_t O tIj`*<*XWM~Iۛ2ݔy#җeϲuwilى\agOֹRyrcWZpגPA =P@L` Xvp\<? AA"}VDPH:Q/P^`e1B`~?ܼTD3(U"gܫ/?T.gԖ>' mQ|uw~jdPd*AqF%(ť*D٬Ѫ)ED6kN%(,S#(>XFo?nݟ7(K~FQ]$90yi/")p˸ٜ\!ǗF/}|v@VͥU9KЮy=,ݬI[>)N`NY攼imbYMFjo sJJMsJᎵpg =P@L` Xvp\<? AA"{u+] CPb6zJXNV%ǘ!vsJCBVB':BjWPTB%UAJR?r9eJ$M UN/c^sR9O'T_@S(9 )揱|7ʇ*"O Rm攚MHRSJc67w7`NIY%' -Y>^X_fKIJNJR2GJGMcG;#~e inb1k72E ƂAsS_?vm_~v8Q+-LsǷzH޳ءsmo/^ض.^6WZ}\JZpYۭ<HH`3X -6/x@ dBp"Dݠęedvg1Nb'z0_%V~W*AN4eWJxpr<((sJJP&kB>U׎ LZrx+AQ)?e3=OW (@PĖIH6)Lz B7=9iėpG`e\gYL("err|>IF)P?*?G= mt$Lzխ;9KߺLfY7]/ȶ%{ah8?7] Q;`mR .y,^H%k -MJ*޽FX,A@B"@`V0x,p"H CBDEG#%Ub0ĖI7ƎWGh=r5TB9V?~sBGȍc/NQJ\7e)>T1O~E\ڼ_23BFJTbwڍJ21_㟣4NK?be{ .M -}?Q7KXInvɬO}B 'Il2rPqJi;ߋS+^&vtK?5['PuG tcair5ƺoT['vq\bI}zΤzc[ bm\^İz&X[=Q,o&A@B"@`V0x,p"H CB?8_&tAIx0 泄d֨sp#spk< o_֨~"pRʭQ ?l_VIW7LVRe*FfR<6yIp%RxL͋W-ΡoU6IO*/H!%\EUvSS}9IHTD_vr -Y_gYMɡi[?5gߘ]BvM6 Ccۀؙ`7Xv3ւmj_ю6ٵrda5ʞMdc1]wb>}[:žwJ̓O>ZAzP =P@L` Xvp\<? AA"bziQ6qfY#62rtsWl A)>5)AKMLJasSDN<*^?uVO'7>YX.Z0S}O%ѵYŬi6y$KI?GZ6F7gD9U6iۇqrUG~ԏ5%imѱ@~CMu ݨ{P?ˣC1~A@B"@`V0x,p"H CB_Tm֏:Bw't S+a%,Zbc >Bxokv^%>cSc]~NnTPsJL*ܿO-QcMVEv^R?|JHTTR%E?Q '1__P3_/xxxReI)^ԏ_Z~*~uӝa-_Ū㯩f "!|B_s\?%CVCXvAb]9hUDK) ̃ˁZq/?N;ξ8[P?6[mw_dϑ$oos9]uj>b|i/bU\iyԏexsiDЁ(&0`;8 .` XD@ 뇨 R&QazQ4 %lm={VD)\IЏ«*AѤX:-~O#)m0$KdS A2?aZ3KXG -Y@R"SL&sVRؔlMaGo>ݴA3xlZ;>E۷B k *ݽ Ti=|`u`>``b};ښi{ lIYvٳj]IlwTr+o ѹeirù`kHp7siDЁ(&0`;8 .` XD@ >[AR0:Y/P^ *~ -cl]m>eǠ'zTv;mJR(?&$R*IqJ1J NXTR=2(S"UuZxPTţJɕmkO^%(&R81TVRs'DPw|AoeRSTǯBZ"oJ.NP` R9>Y9deffg&gathۀ&^JxVv)dMM+7\F-rO7v=ݔ2fֺf{Y(ùܒkzT7c4h:5ϣMÄP"@[h;wKOeS*H_օäF| Fk1Sa}x xu~1;dC;CINCȉ,[Yʃxv>6qpKZkVkZ BŊVDHbED'=vZnCivo4Xnd|%/Xy2oɺ?3v_څ/ rBN"@/33+ΊKRv(={_ځCSsss#{SkQk^莵sK U f8t7q-'/1֙MXk3!\#.qxzĽ~]BWj po<;}cPph">vTԎá@@`0 `+,8 .px@ dA"$h5NЕi%6bZG̛Xۈ[89[x Dl'R P I0q?eRz(=9eP>_13L<{&ϙ NʢUM]WN]\[hy5q&,4D8I3N֌ iƅ5"qQM,5#Y땟u#?k??wgemɟ?gdΤ93s?ga6LH򳚔դ=K}䬟xgNe;<R9~b8}1O Y[n =K9ħ!?'LEG)6/aUSFJJ0##(9i>!3U/de௬aUنl纒NVdڝ}7jtqfWg<gjf6-NUU>+7?+P.q*߁T@UW*uݨʎPP#ʮTe R]UٱUٵUrk*8P-P=`3X -6 p /ABD!CL͉l b"ta -b#bYOm7+Qjj'uSCk XK-D^GB-x*.ae5V<+L -Fiİe/ e%ma%㲤 IIq 'r@jjV*?,+ϯl\SY)m]]n -st_ن= f:ۘiwxCg[j\'Vػv3lS.GI=|8Ż[{/O|糹9|s|DD,<ؕrC s#Fw;BAqi40`VXp\x 0D -1H !>lh/6J&slH.-{B^/r"/ I3d!$1RWJQRgR)"k H7)M(EHi_סTJQeYsH)3R)ŗJ)^5KGKytO]tR,;_.$ti4D]\rS} -ǶF P)HɗO& 2Ӳ}bZJN3sJkZǧuTzM}m{{jZu/<0D)89Ř涉]ϰ]- -Sm/_p[8ZΦ[۞۳{]l$܇6ȏ7w?[Ivk+$gFF">{D7Tl&1j40`VXp\x 0D -1ܲ!%F>/6z s1y%X-NW)q{.ERKB7&*ixDH2")*GYDD$xF7nADFȉ1˛Dz%"Y*ƿ*2ndD)1ac/P7&#"0ns'ZD#75Hѽ"u~2.WN||50>qk>y9K8CII5"\NNV< "ƍ_ZZZ -)(|<HkIb]-(G5l=%f |3ter!X e{C2[p\ܝ7ϵ*o-q;UL۽oƿc - kWWRM륢'5W4wVh«7h"+hKbpZ@z0 fl`^< 2 B E;vG n%>b\GL[Xvk'u{a;=&Ľxa9G"|+FmDEB] -dJVRUr2PJV*Y94døQw\#.bĘCL*UySY**{#aeoĪRa]RGU|.1KrorT+D*Tq1(k-aM )XܸtȗpfQͥgo}B<IFKIIT.+3eXUj><ß%1ޜ}BryaUxz߃4SKiSGxseAuݦIc6}rE;^6 -y:v޼-ӹ=W {߱K;%*,է18.-P=`3X -6 p /ABD!C iH QcD`͢E -6.~T#ὂ_|HŰPxPWs5P4(TB!bBB?ClPtb̼k+Ptaᡘ"В *k"PH]x]Ds]L;sy8L2rc6K8Sov -)GeSS|9< -BQP}Զd(wOP^Aw=J:O箠Ŵq_%m=08mZ1$~|v:U!/]-|9\c] ףbc]b|9{w6͏|??Zt(p\Z@z0 fl`^< 2 B "hC/<4FęyvF(.^9yNN/{}ѹ*PJ(*  -J(n?ɨH>>R)6c̜oTJD)vhRHJ)$*pbr3JS}MrE(7L¬KZHu*9mC3S 7H@/5'Tɗefx!3-Υdqi*XzqnGs=Dw:6Űm,Sk -Yƍ-ڎ{8?:>pw{Kyv'v;l$\VBuO` طDnzo$կH $Zӧv@`0 `+,8 .px@ dA"$evXO%bZb'%Ovn@}^=Ewxލ$gm*THb4U[z.-h2nAULU8I*BF0%x^ $F%;;˗ǥ__v๵o&7+{&h7YS9i2Pxfr h'hS͍Ue.kI3c c^Yfq -.{H7~zڥW͵97E;?z8ہ:Ѓh`&0`;' 8A$!a@b8N+#!1 LZfDde 땶(F5"[~ !),G-bX%wq%TBBTζsH&c ?nQJ˩_J9R*J) d),*>-kF]cI3Y -(qs(C?.Ģ򆩴ߗFYr| gh#^`PR2rD_vN&1)O|Y,>5+1|_?&>)k6))W&{VL7SSL7L4f-;X;rٻt'K8Oqn_tu>~Сe_L/6{ͫ0[XJ5=[| ռLŧ^#E6%18F-P=`3X -6 p /ABD!CYոYa}G7Zn.-/a#׸zb#-BN)ȷ -YJ8P ǛJ8*8O bhWMÄQ0Gsn|ze<w+L+ߨLg)G0.Ä#tc&MqLE)F0N2ebҕu*|OQ!_:Yڴ !H*_V0-*d\`nioMvcsԆ;n+~㬡D:o/ˬ5?ɚJh4z{te}>Ѱu Z.8ugeށ'[E J^#|g[p߽\&w^wu]b71j40`VXp\x 0D -1e# й)#Jb*% -b)$%ebV&+\]2xzWDOOB"W_wJJVFԬ^%%2JdJJ Be#d{qYy>9*)UR2Ie%%))cHYG8 R5HUd~RRD.E uGħ!05-*qR bEK0 #Hf >AJIBR\4 -,VVغw\tv~ uz] Px*)-5ghKSS+LMxx[zؗsWS2'/:6_a߳o#*eϒ U?27\H}WG><:Ѓh`&0`;' 8A$!a@b8Q{1S6Hti dh [cY;] -Lo?xAaI!T T 4exZ,u">cYϑBѶ7?+آVyV95kU VBY+eTBqB~!­_<'bB2O rS׆"KE!_9߸#S|Rj 2}$ef3^~!z*Mۦܖ9c80cLЎ]鈨ґ9ʪ?v,:]*5adr,zy.} kuHrSt)XȟE02cȗp.Sa$# f_] f@vfJj ;#FVGr?:I #G^?ۉno?,gt2Y˾6,j_}T8/KG^w{+i%F}Z?x!UE䆾j|D"' ъ֭ɐ @`0 `+,8 .px@ dA"$VmI첌^,6.1-3/`]j[f-Nd+'x%.#RaDZGȷҏF~<îҏЏty\wǘcW~ܪ)J@:Vj&{ # "_y)oXͬޝr"?(0~g -(5!_GY.'+%3eҤj(2lΗ)HYb0Ej:{'=IrۣlmK*G>Z(m8u=i=:sYmxem8vc/`].GAڹpn yj6ss櫅yMv*CGGib^tۗOo{ิ@`0 `+,8 .px@ dA"$%2x)=$$#s+xc\1Q"rdmSR\R)J)2TJ1baIVF ]<\{m(?J)(K߫\~R'RlU5nC)_~[^S܉QcJMV[H]`rs}%u;0j ԾfSBOL?2RS>.;dqi\fR`4iKr\wj*]ItmM5l~ Zt|sotwt8v~:]ŏ,tw )[=PB{i!\I,Y*aRXDĢw.N=&oÒŒT?,S=pZ@z0 fl`^< 2 B s 1qctf˝E;K؅7o+".no*D ]Yry:^j|5*o57xKo٣>Ze:<3hcw!vDz>|jnj#R јwcXWmR%" )U >K 0X𫴠$SRaxQ'IGwO+x1[}/|6O)bx6U,י>IuI ۵8,OݽzҼ7|:[2_2_,./K^)W.Y77Zu8.-P=`3X -6 p /ABD!?ǒxjGH/x:p+0^5l".S;$EyY◍*WY%J(TBa&C!aVT;zRl=c"CK1C)*%VKi/UƋ;ǰ#`z)1^a!vvX/֤_,Q?b!rO!Jqן"Tdm/!_*E7ܧ2r8_HR28gesRrRb=J1N厷Q5/Gַ:1iYNUЦ\44d+~°\Z,D֯=p3+{sM/O玭dtqU]/J>PɊF͋<\ิ@`0 `+,8 .px@ dA"$%6x_cFěy goϿFܢ ;Fa%$YB_bR>e!rJ) ͦ2R|2ȥQ"F|-YbTJ2SP*xD)*{]5<ϗTJq>fo(O:"| OyoPA25dE.Q;Poԕ;Ɉ'.Nĕ9,.'fų8^J ? }֑7,E[ʞu-}+hîcj|6A)N7{)j=$ۆ&ٻ6<2QR:>UxgCz>?{W]) xJ qB( ܍5mZ*h}G-m(x@Gׯ'iciU\D[j?uY^d+e؃{]UNtxj - t܁tӗ\o\񝫅M+_Ku'L ?'|y^Լhc~rǥ -tL` Xv`Np<?p"H C(秌2*t1]a-Qk٣lD#yg~UB(v5SFUBH(hPܯLru7^W25]jc̼nj\ӵ2Z)(Y$K1Eax'S =RGd\R;1RWK<"u(K:9iXREi9Qj!_ZHo*!(q.͗2xgK}x$R +ņ5]]JLQFYmIt;X}&S?jk1d-ƺT[{.v[5ì@un{u(7/n{k:QFYwb  -Fil\*݌5$:(=_'"QrQW4-P=`3X -6 p /ABD!C?FYAUP3NɅUTAeRDek -–z&[J *"$ޚPMeQ<+{wLG[(-b R'T(}aQ= -eԤғưo݊cg艖Jގ=YHnГ1rLze#Lk#euL;| g_/Qt#'-5,QXH)bЗ̴~ۇcMNBumѵ͕^]ɣQڸ%mי럥-'?pYZ&%7 wq8Zoqpw5bX/{ }έo.z3]lX%JUrqizhEzEK(Ё @F0,`؁8n !C&O\0qI![%dO/Qn3#oA^ -ED BPՎ"#[%?!Y8gŘۆj%}*Ku*KPl2~7%ڸc;/|/BÖ8`I^nrlDI4DM$rɑ|l!_<~M\V!2ģ3uq$9R5ɭ| E ڽ jÛ9h}a=:a9Gy/P9mi}e|imC?c%rl饝[\E/!O[ۚ_Ӛ=4?–}kR֫(=y߼'Mǥ -tL` Xv`Np<?p"H C( q(%JX1hI6 궅Do;Iz<C'X R%*)şTB^.M%UBC(n}Woq4V<ǞQӹw?vpjt9T*y[PyM[qi40`VXp\x 0D -1Hw=/-2!>V&] QO+(*DQ -ѐR)Щ¤RQJJ3^rUJQb@Q.1՟u[ b?uR6΍"<t/ QLgJ;R -ׅV >. -%.#4),$$;(˽c(zcrRkvg*ۛ+lry[\܅/;l㳹usw ͏Nإ]^%w0;HzzȆ&K:Ѓh`&0`;' 8A$!a@bCD)\As(fErX|5\^ >C)D,>~=SRP)(8 /T('RW4Rԏ>S$ xmOcIMqR?_5ʙ[RǰX؎R(ahJq}@eBDK;M9K8SʚH,H}|\2|so⮴gl[P%}CtU/(C:fA+ZhScܹ??:$[M$IlS{':[NCpz2Ͼuz媶@(쎊M\uN5WGwM>ǥ -tL` Xv`Np<?p"H C( q(Wj ^A> 1FCL1:6.^A. -o%w%?S!Βf`1kTJUΤTf~mbJό:SG{sFslh6$KqJ91[W+ma:VzVۙ7{=R,)֐xSrSê0=^&!_Z}ߕN!3Ky Jiß,f]]ʆfymSjiGw>I'icqJԱ\g>nhd<f؎]ohg+׶K!OC$_I:W\~W MK>K^%o.K9.:})0SaLч3Ef>})0SaLч3Ef>})0SaLч3Ef>})0SaLч3Ef>})g^41(ŕ+m]byb`)^$>,x+$Ar3RzſiR~?LW⑑JcQlR]٧VY}x}MÎK) -(EwfJN߿IS4#R翰 -vx0u Mjסo%S_teds$_ 9A_ UL -׮쩚KSj{7S/EtO o'҇bqcۉ7YK}kGSmpف=кlz=m5w{?[Foxp#V7ؾr)wM - <;o#_'њǕ'@`0 `+,8 .px@ dA"$e+"[ .&S!1/,LF]h_$NW%q/o/%WDļZW<'y{GO%qi40`VXp\x 0D -1ڭ(,c#æ9jXe[>u<3WfW -3Ũg[XPTW&Pܬ yͭc ïݺWKG8/1QR[~)UßǕfU)E;' /R}ZRL,ſ4DqV2obx -F)jRC3ubd#-e f3}ʒ|O9jaZMEetv upgַ OmiTxsZڲkuI'{I|vvm? K y},{=0xW2_Bϑbs]% uϋlxb~td)p\Z@z0 fl`^< 2 B 2spS0(Ls@)fnbp(&ax6xY KWlwߩ)F*Q^Xp2R1u,7j%Iq4Sli1ZR,UsLqRJ)R0SxUJ=w4|O%;Q &)i?BR,M5JY_)%δ71rNHq-Fu)ϥ#>?-4U":ۀPܵKN||+B(P̾5JM/ݐ/L8o%xxj>IJ 1+% j7=VS/GʿnPu>NkjiCw^f*hZTW3Ӗ\X|;w}ǰ.G~DV -oކ]GҹX|t_-쫟.K?]%MU woٍG}ɑǥ -tL` Xv`Np<?p"H C(d+ z&14U#)aH\ɋIg#yRB6n"]۔P,Q Ep OF#EhZIRE)VRP7,>c(VcrxsO?ZxC)W”h&Jd!_™7 fsK OHEHEV<#{m)jLknn_70PDze꾟b<~d:k˧Sm^vk_v^hzux+6wƻ=moHu ׽AkgH[;+$޾Ȋ{IPۉc:Ѓh`&0`;' 8A$!a@b82Eņx%Kwo,j~3{ouRX,*.no -U9 J9R1RrhU1]1(RcX^}(OyXeJ8,*sF;ڥ\zѠ!rJ%]c#ܛxE~ۺkHTH]("+rSc7a*<kgC39BN }<IZb-|iA)5- -\45ǚK~=B8qZZ@KttW=ͬ{A*t沍fTkI'Ė?=q|WjeUz0ǟwb<BY[T,aNPp[#]H^z:Ѓh`&0`;' 8A$!a@bk~~bLE+0ti4ň1#ƕHEx}Wb|#FTbh'TR J(W ,%J(*(8ʼnq4#-Fs1DiJ)4]RgUJrd>bm(];xzߒkDKԥrxa-JqJq!_™EdRd2srr22 BZ/#e*)D)~|lamQ^ULw=LOwT<8m:vx9emdka{h皭W!OGXp%sUGV.K;%nh^g(Ё @F0,`؁8nWG7QwjvȆ0Nc6Ct:cEUA^wua}Z`Z--n~jZj[++VDSc_Kϙ2TP(9=("aO:AIq?#$ܟ+gwʤ()~WEI.?LW0vKTHMrL3Jfr3`3sLp S_է4~I[ryrvURu}cdžJuu6}K{zi笷 Wɶ|?Ңζƾl~l'IN(ZP]˔gF.L$VNp<| -A(EˆVM ٩87\ qzL?糑NᕻAYVS֮gTW E/l_ARgWQ΃}kv{xvgT{$WڝlWOѧZ1k{#wL+!-z."u{fkK fJƩDRL@,` -h x,! Ƞ - fjNwH N%%x~I?8@!yVѮS}qo ^R|]#)$R8Diy`PAQgԭ}пn6 -0()8%?[5AQ?V&GL5+Jn 7S&?J -.QP9SeJ! -s`VXfAzV~ffD=Jϴr<^# w _8K|Tʓg(CC,6F>$ bcމl#-vA"钬Ŕ'jDGkFK}dA4"SLaU޳s$h럼+qˑuoUHYS98vRp/wKTM)],eX(*yyrQ9fC-fw4]$7..4W̡,)6S}x.ekYLٗ8rvúlLu:J{Lv{۽.{5YFZ׾-l$G\xu\ʣ}Z$mP)mۦD5'VdL@,` -h x,! Ƞ - o +$JF#"yFDv3+F$ pb@ш? T=(4'6/%]y0Zn!Wy_RT?g#kp'YO WzR`Љ0HYIDR\N'oP_X=Iq'/WR5+:B}Kd.KiE #?/@2$A%='HbbR?()Yq/:FeKO7KFZYFS-_3mcc/.%_2KgX޽_3LvӖ^M>U@ǚaö7 |;tԹPm%CeMDm"CN< H0@ 67x/?APA!Qp@Ʋe[#R6?(%b3)o,G%ƲGRBMOJ3GGm*3)n"s.Kv _/7! R$kɿ<3TE~cPo;:9zǤNF|0x1kхqnP8KKL3ʑ"7oyH -]?!E5%\kXFFP(.d JrѥHyrfI;8f4use-Ŕe2ʺFy5)[˔uٱo9ܶuOuo:A{̺w^/;y=zhZrK;_5Ӥ˻{nSz'i]١cM w)Hx.` Xl`8n^ ~< 2(!CI0bVDk5h˱pMs6KJܥ QeU%w}k D`;*_X"tH7>S"j' E3umUeժPڔpc٪H5)S<< H0@ 67x/?APA!Q´?^s&,̈́u7A"fv"g3MO+4 }[G*B`D@T a@xfW -Uʩ|HD9UAS\AEQngo b0*-5TP4g1 Y+4^'}Jm>Xo0?b7s}(y+U`kdw:TP㿢OYE]sI!On.QP\䦑ܴ`fk̓e9(Kb,WLAAQjMoƜL_Hf'8n\^OћVR%){r5vuU;==woS]q^-ж_Ịr7&5'I /ԣMT7gk9{OZXʅ2 f(vp\0p"H *h0D -+P>[Q*B+]sL@PD< -ߪ:h("BT I@Aaу2}nЃ1V=(wÙh$?">;sB5'\XdO$EAR|FARf+4&=<ĝD!v$W֗8'Iq|L2;$W_57LJHw "W-.佇bbH -0-+ӔŒ Yȋ ^޺zMJ_ٸ_Pj6n$-0} }pc;7L;?c=p{4awxc|u3يm -~ ?A6l'u;goVT҆̈́v$~0ߖmDHw=xF` Xl`8n^ ~< 2(!CJTfTuA-鿇y a/#Es%ܕg\<"~B_ !/ "B$%ĐJD&fyXO a$ʍ#.dWS16rYoD얳Q{|kFr$7#3m&1͈u!Qʟ=hR\=H~*k LFҧIi7 x H>#B~iB%#(KJY|Y-j'ǔ~_ļkHӊgnL e?>jxe;e:9Ѻrb]Ju74sݴmx6MW7i*t6{*[8biR!CłRzVV0K[scVP8KYʚswwn:-4m:$?D4eYjoSIVH}5qr6ՌuU|Y-OeJ1[kVU/[;{2+SwH "rkm?LT+hK_>5mѴȊe`+P@ ' ` >`D@T a@oM#KQ[mWבG[obVgIJ!YQBjTd==ִ ~w.jX?qƕI②3g8n=(~gФя05U{#n|()| '(de 'jmd׏xPjdA:McCI! x jEyiYi`D@T a@腻j0-Ti*J -% q2IHQEV!ry J?%ŭ%  E (Ʊ#yc1Vy(}-g/#r/$6=)Fb,5()RQR2:ڀ龭yoIf'5+bıyc!).CIaK|$?y+V B0-KRZ̓cxM\K99®ŮIxu+'k[u9OWNӏ~ H0@ 67x/?APA!Q8?/\H -d]X_ov߄ET2HbARIQ7 8sIPϔRSORh?\fzM1`La$3.uH6?cTbL2*}vO]#o8!) FtiKDK$$)YrA 30JXz& -ypRծI9Y=nxhS]k&Yڴ07T0WP];~%ck(c\887:~pkgF|zݒ6_@'/m%B{i#!k;!mO +nV*jGfBJjo#-_EDήѷM@,` -h x,! Ƞ - Bk$>,",儵YLVc-,&\%q,fA|YLUR_J儸J P*Ǟ~>t:=O7ȓVEH([1;d!1b<cONNOO)POsNcX;(kXKZ -֊Z2+bŊX;dJ%gӸ=~s\qoj uC,^(O/$nȽa?/{IM$-/17oq~8ًnSbI/D5O,`oe5x}55p/\y. p|z8(]>E˸şD؟B"/}Qv,k!i{\fKJ7Oj]jF_&о};+/U:j] tCt~T&3X -6B  -D -1CP6}'60\G,[ض{qt a;HWqWO3_ l&\wa{NvȔؔ) sڔoOlOF~E1_30'ed2u'0y'ef~xWYl_rrb_1~ݷrͭ)>ݔn -StSDI7EMQtS")QݔuwOyO5%aӯK??.?{5?,H6gaS,8l̳R?lZ֢?H_=pڷ?l ue}:A:=)O-Qg^*-B)[2Tt;F9:M}V$a⬈9bMH|~~>/o$cRӰAWsցNficlTSXWLMN7G[ʎN.m{_v Zf8N|8>a#]Ю=lOJ88\77XQLuD<+U -cKQS򟗢汥Xr̫_飼(Ep1Jqq!O>F) -"{.G)J=2uqBh(u4WY5M85ЄHp($ pFz/d ?'#WIcJфk5~RzХzs5)θ!T_5^Ud>k8ʙֽ=w}::>;vMҎ}k=U3XACķv.PްFǝX}Q'l!]Ҫj5\]p0cdyWA<ǐʇJ `R Y9܀=PYiyY)AVjEV#+{^Q0 h`  '.pp"H B <y2+4D]!b NbI=ı8 {rBr9Tox?q/sT :aUՆҾ-ʪQwQP'b֧0 h`  '.pp"H B 08ߘJ=16S+ SO-Dۉ7G'qn?yd9qO+Vbj#ՄH6"HDn J3wӨJZs4ªUVeFUnDUޜ2*QUYaxMyltVfeFVjVi@3>ΰ|(rk*+EW$D< L=ڋa'JEZb#Qo©ʊ8aV0g (CVļ?;ǟb\~n0!J75G:04dQ|Zg(^4_IDV -Yδ~GVZ_+M{ Im6Y ۀ@ku\-OҧWJ -kt.?Mkuu螕b-@@`0`;8 , /  Ƞ@8$Yi 6bAnl&#'aIX +;ەJqwߎkdXL."y+QvvFVԬ4g5+4 yVBgǫqߩC*jUXajUjΚhʞDUthWeGymX6%^2LP[P)PkrU +[&Tez-VބStJ(/ |!'/~)ix)y1U|!G}9?Qm.44zqb*fj͏ӖoZom֜k/wήU3آ5-v .{B߮ -͖="uDR7̟phP[5+Yyxk͢&JDk78etVnW"k /ܠ Z3C#+s'1,Z6Gu{?tu"+)o1,fd|j ["ş\a UzNQVĉ- KP"=3B(,s҃y~)?]r|"3JYjL G_?Vmj ǦK -iӡϦG] -iϦZh[Sn{u ݌}N]kme!O } l'+l0_8%bCN+9ʞ2#=PH}0 h`  '.pp"H B <QGCO{,a"v!;ln#{E(E(wJaUKqLT'4J1S-E!IBB⧧祸cJQH b{R,RKqF)خQ䨺84VOZ8Ϋ2@N/ k0JJWOnP`R7s1q~I'|nL)76xW \YԾuGƁ~δl&ݺ#o7Yjimm9qQY/;[eS \CYC=@j{^ ޽&BY m[xTw|B' ޛ<2P\^)}ku颃eb 8P=P`#`+p i7x >< 2(( C&Nۈi3;K[#o#^l!lCrB;x_Uho!B+gȮ?٤4f% V)~7Rg^kӨJZ'@WbUӘ?'QqN,$ך[1UkMUYu|M`ik`ˣބSuZU"4>Ky?N!Dz"1UAUw]ltWeQob.>:I<dZV͕8KiLᖠETLqRzU)sk`E{}A5>ZSskMקXkZ?fۿ^ZSԋ.5'tb_V - `,`N`! \xD@"5UKFNn!vbL,uȵ#ִvk*#|TD R7ݪ@U2]* ԪUQ$rVxU:%ԏR*N|VcYUԭҨ/&Q+ǹu 壼BjKVłnAUTZ]π; -ɉLl5_*_,&&1aU2B< E!//d"4b's ~VSfY臨i6U7o(.]ki=fKis{=m)>1H۪//mwZQ5jsn yVmvgf?_5/}npp%bf+ڢLȑhGYOS[ K&3X -6B  -D -1÷[İic^j ㎀3*<1o'p -AKUN(خcT(Mj(f[]#wM'7b)og^]?SKZglR4MĒ*ד(EnRB).D)܁fc݋Y䥭 HePNRS˰?f6M8E0VećB0nN8 P{k0,oxOfR ͠vN bլiJ>22ϲebmw\esngsnvW&O;$iY𛏿LPϤNן'e/@V;-:(0L@fl`84p<A@b,߁j\g\bќg޺v}EF{%۸E66Kϛkt umktNgQ'qL֖X[Vh;C4ux{Ӧ׳wam8ŀq{wjmYыl,}Z<ų0`l%'_r/ꢌWĀ❣ބS ~KWsp~^t Iy~)//_|i36]s9=wzQ85 ?e5y֒.ֵ*ut-l],W1|a8.J]r%y=:M8E% N|D )y7o8?͕BF"?[ǔ%)}϶Jq;}HC#k<k+e,̚,/]HH #=k HHŧ0c0 h`  '.pp"H B 02ѯJN#KuRzsh-&Hu%'"YF7 ooT~:5g~vxv34D;~0"p|al3Wj*}Y#gF<`lxLi]GK q#h)}+a|P;q˱(u8QQo)?Bsoovv"!F8yL^?;;y4|@BHϕ0΃(?K_ZA~1t?WN{XfO9knYZd/O<<dAڡNWFϲMwx{۾9z9z+ar"݇xԴeՁHItu]'~%(0L@fl`84p<A@bt`4uaدu֙r_.VNAZ^SۥxN|ŮQߩ OND5:Vcw.uq׭QgjhT#/xZcke#NjE\ڊj !u1ȶTt(jd?T" gO&Cj<.# ~1= yǦrĜQr2ƞ8P7rz, D?FcY:T72'X36KK c]ը^\ǁ:0Fګd]-Of7),M=9a~#GJbX3i~Tu F#`XVcz-r)!bBfs%R[9G9q0S ķ4y -D(J)&r%Q*LUkQ߄wI&5^"O&t ɚ<88#j M;B&gjBIZ35fDM~0EK׶/ Pmmߒ|?)"7j|3HA2Qg&4zNQMnš|NvHB~I q~!?5I(sg15iCMg[Vځ=kmThci]!)6wO[e|orp48kw1lMӮ*{}! o 3ɹ|{ʊK7UWʻɌ uϋʊ>q40` XvpXH^A8A$AD!qH -7rdTL-2YVv=8}#O"+܁! #FS8[Ej)(E>Uij)L.3'JE\]&cgI|1EAgesy؏kDV;o/͠J_瘚O[Yf>\[Z -On}|ss;r_,H+uߗ.F϶;5_;J -V?m l+%}qsljVzrJe] -Q&3X -6B  -D -1wZb taʉXVk!{qT2®>y_ j%\ Wa5S:2y,Hɹ>\c=FOΝ]'vxg8#hQc9sc:xĠ1xs*''[-uo۞s/9rUI!'m&s~!=8#'۟d c?}loֶU 6|Ñ2x6/7jiA|.7Xi[n{ݓ31#{vg;qڵn y(ފ27X_ɕ1o9>_(]NiSW(-eFxV95x@`0`;8 , /  Ƞ@8|Di2c|f7c<<WyE$,ŦkdžK&#uaؠL"{^ƙ׎*-j)5Ju?j)QK+c`IJaÊ+J?@S J,Ri_R:KN^R\]YΎ}ԛpĥHfd ^sEH(᠟!>IRYݫJѺwUuÅCE-m:>.[K3åyTJ}mo6Qwq>ihn{˾|m-vV xp.yc »cbAW3gFɊϏ5o\p8.=P`#`+p i7x >< 2(( Cl qS}3_jY`vjl+Jc_y|L'7[DR(&|R:U43OaÄ+'z˾8Ӟ)j3R1S+j)VhB?aRb:uޞR+Eu(ůXQbQ[ -PrqV?(F?rzNQ)_9_ Pz?|(?(b0$i<,f݉E?ZQw5fP˒F+Yì= =l5Wㅌukx*m:jj͏ƇJ[Y.Ys;u:l'm ƊҺn#.lNJR9tN";aE,|zS8F=P`#`+p i7x >< 2(( C"R=40%$ynXz9YD؊R;r}Xj&\kL -"(5]RLjQ25%jQ.(b(&%6Ud#{;̜EZn0ijQK-J{侺M{KYGd3faˊ(ʱQyr䖒|P(u1{ȆQo)* -7,bE8aNz.cg#R>qѵ36jKBCJVMNcTLgmn(-G1XwWӶwp4b?gح;ii{K!Ϧf[|33P1;yvкNaJrErHcYѭCY-ǥ - `,`N`! \xD@"!y({A"eȃڽvCvqy'<[WtK٫SYj)kl)erڄOx}j?(E|h:gL)Uk'=j3_RG-bR֫3RL3SL'l)Ҿ,OŤ|/U()m@)WGQ9|ԛpJyRzBJw`NMd'g$ֳO7aKipV=}oPf -x6Jo^C3Ue㉩֢ml)3>g (Ew;m~ڵi{搧 {=3д=k+Ǘ?@({t+ҢLp(3R6+ڀR J0 h`  '.pp"H B <EA)F3 v (Jpni>.s" -$ˊ0e_v"K-MD-W)~Q'ޏ\o8g=S,TKqL1Nԙ_"K>GQ}'C)(RiKvߌ╴uQ'gzOݨ7]=ByA.ȟ_8DzzR6^S[JcYC}w^'Ml-k~Co{eڱ>ױ7華~ߛ>q&+wz{O|y<Ꮅl&ֿvaKYULʟI};\~_;QzwAOxd5m,/5bGY=$}ˣTڬ5Qo©<&>?!I`(QI'?ID87'=;ylΧ>Q'/{٨Zx6\a۞U_i4Ӷf8 -N0c;D鮢m6w!O.[멜<*$<J%͖W{vnfoVq~l_s(K&3X -6B  -D -1÷wGQ+ȴrMxEIrB?$%$vRnz"W=AWVY*{NxFzHm,[i@m^K[Xi[Wɹƾlˊs:Ү6w2yL<7pd\y|iUB#1^lpݹΏֽ>?v*8.=P`#`+p i7x >< 2(( CőGs(ads"YE(Ȋ|q-y.*KȼZxkj(ihQW4BqzU]DP<3wZ& Ed_}68굚D5᝱(Nuk$Ji}1R<G`x;hҋL\3$R-'_HYzTcE&b/rE)W2xzB#/a\~~Dž2D~ayVT):N~FUo7:xtxt?3%N7W|>TcmO'g8 -c2P;EU="Ba_ߑ9#sƹ|]|KN7W OQfFvm++v0u2ǥ - `,`N`! \xD@"!y(ޑRHFF):R wj|:Hjp=[:^(WO#m>W#E5;4BI/k\>}~#͋yERg=E "+ϔ(W^"\.C &"4`qy\?]~! ɿc/ -ݠ;欲oBQ3M?𸍪8oz|OC_OgʏO7ni80U@*_r{g821i[_]6wc!ϱ{)2frߘofpXSjR1G9bfdgY]OB0 h`  '.pp"H B <i#O+ oV,U#g=gd<7 /Eeq#gxڱfqHG:Rܬ1R=O8R 7{@i=Qܪ§ -Y=hg'=? uߡD#bGwd=&;{Lɇ} &R]9rU(=tuQ'DQo©sgm\>/D8m+La>;O%!{slj> -OߵQҦS:GhK7Sǰ{?px ڕ3؝Kud=X"x]m/wojŗxPuJ;w͖W4m7?z0{@`0`;8 , /  Ƞ@8$%G>HdZ![k&EeN -^ (\#B1vSy=~yZ*Wib_xF(L" ?H]o8^g5i|SWouX]=~'zi?QcIl-H*3D}o䫘(>z=B]\]]|'B^7Te -'rl8B(%)J3Ƅxy֎oBQ=M߿F[ G}SMi閎OZ}36趷<>qpqd؃M[i0VgުcaVU?>|aKRJiHK}wgņJM8.=P`#`+p i7x >< 2(( >Z&țtE4i$FV-z<ހOF(,((qX=¢k_tAuXN" '3J)l3/kHcTGR?o-( Gy G'F)VRD=ɔwNBny5B]kR;+{-&R|ύB0?'y?=AޟQ .??Lu(E[vJ4Wƶ24fJhs}e.uCme1 -7]C=BooO}f`Lޢª&SyJ9ʪ-/E{ў*K&3X -6B  -D -1÷{P -1ItK*`8"pIâWP - -FKǽ.MR\(EߴkI/}o)ƽ687SKqLYR|Qf-TRKqZ -$JQӽEkN~B{JUrB,u5(ŵ%ߧJQ&әğyb>&p_Hd%PNz(ˍ" i۬*Ū鷬QE?n\}pt7әN,L]@ګGf8b־v;=v^m |cNoWS0i/4V:WQ~1'b^yYOHf40` XvpXH^A8A$AD!q*NndbtQF͊䐰|#|ȞSP -ɛܕ13RL8SKQQ{R¦Q3'Qw&~$|'vI#juxUcVG]Hun,HQ`(HhC(z6ԏJ|Uz_'FX_0M8U{Llt> gBb\\pn(=7(PvE(v jYQ*fo[hXUDw>H=n\K3˗iKSҶ1pte2OiҮڿ{Co;Z07p⮹P<~haߗo8r3GFȊ??Q:K0 h`  '.pp"H B Jhiѕfup:FVwpcɰZ?ݵ]RmSe_{@7\vv%nb㽳Lѹ-Y5R@`0`;8 , /  Ƞ@x$K4zL>x.겹.ɳ^Bpc ->wEx%߇|LUKB-LRXR 1RHcKqwG+ő8ӣG\WgީQuNAK窫1SnJJQ'o;PX>j"TQJ,A)MބS4SO|SRПN\0ܜ`0;Ҹ)7GRퟦoyFYhx6ѦB]fj5nq}u ǖf8O|ðGv}ENܭB=*޽Ⱦzⱹ7+NiiLbHpVRq40` XvpXH^A8A$AD!qHʥ#Rƀ2Zf$h1DrAW5ۇR_xʥ<}%gΙϤ[ til6X_H;岳2-?xʷ $%Me })rUTh>\~(0eԛpN+/!.=?e/Ӄ 87(e$_>> g ?Zޛ?bZUzx*}p96Ӗc_NvOpKg8vd{>g^wځUͽzssd$j ގM_s^D< 2(( ^퍩'|*?bD-]t2ܲ|rI#'?).GD#j(n*DhM93|RǙKj)NӘ?2R,(5ZϨ(ŋy$_ -揧/&ySrsDY -u~d)yN%6G y|NHMİ_s1$~)ROy9utw_\WdʺOdPC:ָ>Xh=k>k9ӭwMWُ۹X{UZk pMʯnWJ; i'2D2.} -=f$rN}%ܦ>>Ǩ - `,`N`! \xD@"!y(7KL}}jö^BĹýx -ww&H 4DW3Zr ^ GՂ9k2y{sDO  }9֣Ԃg -rL Y`8a}g - nM]>r֪ YcMNY'BEΊRQ+5F (?U|gvN_ <3/ELl){f;fu>>Գi-mԉ#rJhS?ҭLى+hKT -V njcN+v ?nsW>~@Uʾ\ûs+D,pJ˞R.5GYޕr^Xy[jq40` XvpXH^A8A$AD!qHW+ 4i9k5BNB1o/()>m/ImK Sw,CVkNW?>havZ -8)z$F -#ōE)"u'w^&dգބSL|BL>d/#!s-!8rc0 0fCry\m}֋Z-Pjbւ -/B b*`f{촜s<B8~ov*ό 1&d4VИjFSR]ڲMIg7WJ޶0ks]ZO̭\RںGt2굌䝽.wk{*+$ - y/Wad Q#H~&:uA 1 HİJk Ur\>7yB 4zjAh7iUĨ1`Ս}(9NQG,{M K"i%?Oeô) -wψʗ :@SE!COKCod(;b\(GUJ:vuzih}Rٺ.p02,LFQUMR} )KQֺW([;$GqOV0 S%6ŠgIޚ3iMcw-JO.u9M]ڻfzȺ(H0L@ fl`8<x@ dA"}2z0͙hx!o{EFo9&\&EFIP78)jRܧ&EƙU#H -j:~PIcK\5);Uq=jRPCxkSw^ҳ,[LlT58v%G{|*Y"ӦelW~x9Od9f0- I!Đ٢9.;2xbɉscϨS֯l;fj (㙿RgenZLY6eml0ϒ-"yInr|jc -=;dᆵ%_uqF(=2;y?#9 +]8].>|f$&1 `P@,`NpA2x`ABD49I62& -мYK%&y>.a.{V$iС~@#)Sy 5),jRLը)#Xu֐7FjP]Vbzn(Tg5w#()~|A#(!(6Hۍى [GP|is߃pt[?G^i8W#)1?r!R3i9)AU sT1$Z>DPe#weX2L^1PߡRʼuR~eu䍷Lr?优I>|rac*|'{|2y3؋s%e] #B~p|(]ܚ*n03Rݖ -|/=`#`+p x >  0D -WkrL4k-S2J[2sG>1b#\d_5(j5ϐ[5QusFP~A|ð[fkm"&XUY#)>R"WRccI_&4}WW()6xmB8snI5!rR0[ %EGр$6xN0;[L?ǧ -~^Lgp\͜s_zm\\I aԻ3uktcE'k[uz')}&k[/5eG&ɹދDrA#>cVYOx -l!m?n |=t{tktܚ7W/%o褞:*]+tuHŅU.|Q=`#`+p x >  0D -; :e)c5a@TAWjJXk:L8 W2UJ0uuD`%6\B#!R=! | VXy@zy+'҈ #Tn!55w4*ڣ]go aMhקCv՚#U6CҲ2 IT&w0͝6G^ۭߕ>+%ǟTX4 -=lnJ?veGkfQcU@y\B۫(K_l3'9 -/έiLrE]C%{ |m%l\OhTNN.}//Wܖ.5dZw͌6*hTѨQGҏFJ?~4*hTѨQGҏFJ?~4*hTѨQGҏFJ?~4*hTѨQGҏFJ?~4*hTnTV2(Q 8Ku8&QC!!$/G~7AQOʯ5¨Bu0cFP\A2u׆?noLjRܢJ-Ө?ΩzW47#T?Sa($*WN;).t*IBdProz-i\Z)lnJZK f?\͉)1~X;y;7-yoJЩB.4P*(}e> -`)lW1IKhWq{rXbc=u&y{ϥ~<=/~qPS,ɿG:48=<=\ V|/=`#`+p x >  0D -W3B?l)S,yad㋟F@2 -YxIɡ!?Q5)N%EFRLU3T1j 'Ń#HaؒBI$|v=ѧ٨i<&щ8ѩ#HwH"9yGTSH -5EO%SS!R6dȦLf/~{2ƂlJ?+S/-[yDL|VfJNjV,wpRԮU}j1YLxgy|~0O|V79Zu*2cڐP I1>  < ɖ9(3x9ˍ7d.DxyER>ECɃ2=grd׀:5$(3xMD!)YhHDA9oHL!75c/sl|syvoo.Kꎎ1v1uEm)c̫Xe/l_g'ͻigUQ$Jwؘ9ҿO2ilqaaL|dNٿM/L }bK$&3X -6dp< 2 U|QDiXIeF$s1'e| ('G I̠>FCAmHX=uu}D#(-İ{XWPe4JXfU yXWF& uAG#)[[ܑSQfpHgJ$S$4eF݋?~Ԑ\leϊ?ꮼ0 `0bNL|BCSG'h}F,C딱2mY`z(zܻ\hvEʏ2Ihg9U$ϻݷloE_HKtztnw|wbkұ򖲌Pק3{͌%.@` -h0`;8 .H70/ ,p"H C(\?/b(̼%sQʄBX+H -QOIjIaT.H[Fc0O3(={`RdIqP!yYS#) ItI}y>ҳIDM؉5F$-IʧhHq!r҅ws7!) 5E>Icr_l!3&ib/jEOؘ}Ae$E璯/=p|:utj|BۧN=ҚtyAiFyp3#I `P@,`NpA2x`ABD"'??")8k抟)7pL#!)@4*nPkOH -ZSUIvCfd}t MuQ/5)nSO46G)i$EH^-:T2֫GV77@zz{$E~@ ]QS܅S,ŧ C7GHi"ugsYr8pl>-7'378xnUwzE]hYّJpah[2np^jEoirOպ,hkߗ.[u=} WVrw}t9Yea3-WRFǕ)bTr3!YO755DkuTQ$&3X -6dp< 2 sK2?e)$sl9pAle(GW=x|1[Y?!;5-jrN#9^ScH'GPcNqGughHħKcmICyIϣxD7r$e6Hy$St#FGrA]Fq?uFx{ -/ii|?*cW32E~"2Oc5Flya߁;NШ3~!Ÿa k:&pS -|575曬)c {U؝'9\ ɗOp8Jsv{vy.RvV븪t;Kč'tR :J]Vn8.RzH0L@ fl`8<x@ dA"tVe<%>ta&,Mu a G+M){ 0Ixwoi#ؽBGB0xFFAҨƊY#V0s+F0X31lA*U͋5WC?ϨWQSu5tF=3_b5I{Rxƫ8tv"U6%S"C9ar2c#\M(Hb3l ?w?&\r?vZwmdYKW/PuGQ_|KQbֻ׭MrT GiWL픻ƬhzVoJ֤kz2ޥkN.: #;1M.*=TW0#yFozuX=`#`+p x >  0D -W(tM"%oY6.:)MW?oO1$I3 AA(Ac+دEרkq ݹ{{NkϨIѭq߷ǯFR<7釆8V#cn -g?XXhARDt!) IT&}oDqz/ꏣ3u vA4 RR1^Xj0NFe۱K~7Z_F~ga] ez2^E#K)sS)e7X ^l{{Ws~v7ܜhi$Y-{>|5;EӹNVlw#C3̈N$H0L@ fl`8<x@ dA"=& y8zi!&<#zB^V4a%N=' -/CVvg<>1AIxҰI̼zȧLDeA5)4:45)Ҩ)5EapKFyAeB4=@3")r drY$-k%S$81DJ0)ߺ?:ŚzT6Mey6+es.UboيNeݫu(_qF6_N+^LFQE)l1e.+,Όnl}&9M/%VOn;@mRSoC$=3Å3F{{ƒ RFppə5^z F04V$|8A$!a@P=b.$TCvF\F|^.~H9ȕCj1O#)5ŝjMAj4_`5Ԡ WPImmV5EFR$w#4h;B$ED1U9e@YOe2p8$^t<$ſ-#\cA̴XbGNRȡ`j6)2{ؽ_w")귎ַVȼ]S>Ύ192ua ynKQּscl=0+igu}&F}5Sjг$$ߚ3lWs⇙|~d[Nq{O]C_Mیȩ%K$&3X -6dp< 2 U3 ,^+o y)wXH -@q>^"15)4jդ^#)eGnH$]IaAMښ?TPӾ FPdkŭjQn4jbAP,|J{ h>=Ο2?~ A-[ ] (Iʧ2{;DQ&"(:f ׎g#iA!g1! mr5s"#O#(j|oUn}3SM:CX&k:\}UV -}5oi`-g߻ڳ"vlUa/,G -%gS:r ?Mgv_XIx@xmYOW-:jWpfokF%;f*^I+k+tryS.TV> -]$ku_T$&3X -6dp< 2 BGBWY\v!0o%,k+ak&턣1~ve`⋟; _'$ Wv|;!vVBn#[#Ue#'o2_]I#U>A<=Jp/$}&J^{Mc5V~2W?SW?2{Wp? +OӔ#]ѣܷ3ǤCd0=Fk1Sa}x|~>1YǑC.1&r^g !年"mZCb֊X"b-Xk,ZKX*VU VĊ ^^~?x<]%L/|s:];WUv /\0s~-,eܐ=o\y៳sr-); 77ߒ0 /{Nu 2Yp [p'9-łH  - AARu_𗴿i/ -!'eɗ|0kyˁWϲV}, ʐcy}Ja{ HJ^[7ۿ}=qwn Ýtmt k ՉMD:\:tUpkO"meO뢍t] 4\(0L@fl`8pn"H !Cĥ}&vc10]J,ډ0qtga[HWډxz wۉK"o'J' " t sgwOCN9F悓fR?'bɟ̌OYɟl՟ON'V)'!k.xt ~^t׋ n&tº ݄(51f/5] 'vyH~wߕu$ſ?]_<]O>n]-wߵwNU?qo= O] Sg3E -]2ufo4D/ S?E?߉RgvK=U$^3UL'ftIefT!*k:ߛI'SeI 6\CƶhSDz1ͧKio'Z*iۮ981} [ΥѮ#U6nAwʐwYmnc|,kRN -of۾*2;ru0I ץ - `,`N`!\<? Ƞ@( q)>፼IyF6UIvuH /'C˼(xEIe^B§CAq|B2(jť$dP\#(tƘAO5(N~Ԡxޞ Aa?ϫAȠ\ ՠHiͻg<E4"P.DM;q)OCTGҰpj"UJ3(iq$e (?#.`P%9]AGGS7 LwnQ-5QFIge>3IIfuO6*}.TmWP_0VgэMf=ss+oxuͶu4*B2UѨlυfsШ{JĻl\M_FKtoi\NYٻRj>T (@RD.GRCRߊxs4D"Tx%Tn &)Da#Slt*ٲONgbZ&eDR4?^5Iѿl}˱4m1ԲƒGXS7],ӻ5׬a-;]Z;ݎw8.mZvmʮvֵ?iܺ ]om e/7e"=QD}Ƚ/N{P[ $C/*U:̮D/sdyΕ7^ z;W{A/Lj/ 0 h`  '. x@ dP a@beڈ5'X5貂0B,Mĺت}3qg5a7$"pAE3g -^~ߞvNQ"Kc -/e9>>+rOD_zzvVzz5"V+k^W8ؗƧ\̦Jw#V+5g5La\X[Ue~"`ۂX)+ 9KgC{2Jo bu?bf\\/B't?X"-;8WiD XS EToEuD:=P`#`+p )/D@BD!K}tS#ۆbe4k-5{PZn=qO#v;;6+uD"J- a5V~+^Xi -J.+X9"ƌ1O19 Q#Th| &U?2x 2D^}9Y>!-I,l|^+yyjݝ;#-6j݇0?ô`"f~y.4}9ѺnvI~)1Φ\ʑݴu -.mL3 %3G˕f6/\9UƣOHp]zF0 VR^ - B !!#or^F2 *B#"R#ʹݜG n/ GNVPB%jq.AZ--(sF5(;2(.?3G4Vh/O+4S5`n -_{oHCPD3Q~гħj~0|9B]|*n© -A{*|,'q_zjv/Nd#pz]kzAQ$6j͖ic*p͔rr,`ZKNq~8v\Je[tSe׼*k#̐W;嵟\4>j?^^9;rv'Y*K&3X -6B -7x ~A$A!QT1d -alQ2BpN7+#($.{^/!<4K+ʈiAY FEq4*S ,5oܳ߈1ISA#)i2)l%TPlɤX=l8W%>$Q҃Y/Y٩i#r_+zk[MrZl}q]UZ;1\KXSדVپ5~|3ٺ=79ͱιcIYĺJ7q{6.[vvoĿ{=ᗿ֬FcmD?F綐Сԓp&Y+$WT\(0L@fl`8pn"H !CĥܦK(LBKV[>G>qDǭBR{V!r"/TȨ[A5}2>M31d#sݼꌃ1osB!iU^:kuc>=Kx E~O\CztLGz.Gz x YrO"/Hn)jHRcGn K6D|< q|zV4rͶ I׉}:c;Ie6`\C_)m}65=|fG[^6_zuys5QM\]qRߙ*i?;V}utm5' \(0L@fl`8pn"H !Cĥ\5]F_\niE3rbEX.s_2BT;9APXԠXNkjQsz2(3&YgX:ZvRLHzuVQgU;zu+:8]KGYgd7$E2[ڐbD؏ϯW(kC-HK#qSOlM8E#гs' ->13We4_0.ŮII=/ڨs ՃgMHo~f90m)<:Z(m{75Lqt#ia[ws)UҮ/ڸmAw搧g]a :\%rX;Cu?B(m*#ܰnvCɎץ - `,`N`!\<? Ƞ@( q)چ.#БD,Ba -C0:{x ##CQ]KхS#)jI1uXIcƹ1 -VRc<Ã5(h4$1WN.Rdՠؤ{QR||FCrԣK`AlaAPq#(n8bT잧$>EC?P~67԰pJ]RE>+˗*K9@/3+S9rzZ<-UrbWʮdP|0I U=z?m,*M'4s6-Gh=mk6Lqqa{7r){Үm\cYн3?ɿW+Wܟ%j3ǮP*^j/{ȁW'ץ - `,`N`!\<? Ƞ@( q)macAPEJ6EPxQ!($!"J"ˊ<"(_c}\#(%X^5((g%m#"wXP 8AzxPSªQQҜ <¬N.>X k ^MŐnBP.(I5JSEa_WE:G݄S lCJҳ}|ӥl˾lYR 'UN׵?<}`ٳ_Njh'ϲde+Xs#e7emi{;9g]S?'u4q-7ó۽{'#?˘\XQ4m![Ic[4 -kIN.pzF0 VR^ - B R@BG)q.n R.#%#BKD'J pq#{}# 2Ĭ1ihb;c-N=vs7\RoֵH{6OWK}_"/5"GKuKu+ = |DVJGiUGM0 h`  '. x@ dP a@bB/JIn|B m'-$b-%EҡQh[}XNH"N -P@|"-ˈRJB?qFY/q%1quc>8hqщӡR8ٯɿ5jWy$ueyqDrq9~ʳe_2NB|p -`EFDFّ/gGwM/.=P`#`+p )/D@BD!K%bL"gDhdѿC\%EK/zUB!Aq[4եWϩujrP#(21ԏG .1uÃj5(xVCE -A -kř^o@P쑓A˟!(n?&Q) -!(&DBg+aJAP\)a7<+5''-%A)'s|9q_rS圴`~TU6]w=-V]Emxy[޸7m=Ϋ0}Gysso~,憀 q Qw"FkE)#a7TɌg$1."FҲ}bZxV3SL/قUue_Nݵbhb~y*kFѲ6,2Ї_x m`=~ٛ[81ʯv{?tdWikyvx f;૟J(wsx\Wt6#Tج#{:Z^\u40` XvpXHpx< 2(0D -1H\ʯQC9lY[uN?Fj\]#ü(DQRdE"/4AaVBOԠpǟԠЩAq88fA?t?-Da5)5Z>Sդ)YB5ꏛƱ"h]V}m|Wgkޜ!s߹B8#lvdh͞d0 h`  '. x@ dP a@bQ7iШ$Vdv m&s^葽_A'DB&ŭ?Pt§&jR\RSl[Ԛ¢$˫IaU;WLYTI{eT -t*uo#3ZҨD{/W")"L]4B]2n©zm=Y9ۤP=}b =ӗ.e_vfzY>;W9،N}7$'UgwFA +_bXӡReV4YÌ39VqΖ[6'sո=f޺;0,gځy1fj~<ѓj~Lȏ߫V͏)=ȏy+, YTPiLca IybGU/cI0 -P/{qa7M:G,_D~z17ӗ.gǃBT`jyK˟ -^, Bs>xU]0[wz>qKijk+\\Ƿ }%tb;TU=W|qՕr]x}.k:]:40` XvpXHpx< 2(0D -1H\JɃc3mC=IJyhۉ87!'Ci"ơIG-D!r#Q6P蓎K4RŢ.RSŢN:Hqʇ{ױ+iWڠ*jrFT)VSLT4T#&rIGARĎ3ħ!EQqQR jG#;Jx0SLeO_V&ڙ`fn<7;*)_kzoj `5{&ۨ\Á崱fm}>(y6,-=wi9{ S}oRC۸M<'Wm3ݳTϐu=[Pf -;2'T4;ru40` XvpXHpx< 2(0D -6 /Q0]I+dYBVQbyFaϥKb,dY t| -jPܠO -w}߿hL:&#($Ә bh㟫4-jP19G!о|APL/( =bkQ޾SI|.!L)VGPA1'ˇ݄ScR@i`A̕@Hgj(]^˩{ՑhI6jsdmܵ6--454PN*i{ ֊ZglpqVc؁~.eɮf;FSҩx:?R}kfH>vʵW*eW -kY~Ѻɑ(K&3X -6B -7x ~A$A!QID/tDLї2/\fUR"+E #7 -aQ./UХ? -iDA%'ddPlR+MuGFP|:A=gYQ$6wb̟'?ԤtԨI⊑#Z!Ԓ®QR3_0JRXѨkD2٤>Ζ7)KiʉDIqG)dž݄S4H{$*r\s[JYOL q15=UGD*=>}[cklM լAԵ[;XfּtXWwߟ8sycZt`]ҸcnvU[b+4mD^"W]"?Fl!ڭ$&9tX:pzF0 VR^ - B Nn):Wz [L.۬7 8FK ?_xtG22b1EFWr:DxR͐/b[+yfM#n-B^+1gӌ<ǡѕL/ٕ|Ql|:G{"Ut%fOy"dWR4mzB[T8+ ۔7O+V?w-(@v8ɣ34ꏐ+jĊGvX9M#VG\F<8g}ޏǘuZ0\uڑn5z+ܴtŖ(@ƾO~DKI|- SQ=? -݄S+{x*}P9dDžrMS+kO ;&髋mչgѦ#Lg+ iKő֖fg/]5166v>ĥ|vgj -w.v{_?m&Hp|bőT9r~Jω߄>>xUo^=ҕ<#ץ - `,`N`!\<? Ƞ@( q)0(D:HfSf١8nU^^D#ܑIqhL;֩-jR5HuocL[4梏˲5 -#>YiGr(0 -[?GRERLyI9(v(@*ħ!'E)$œ݄S4{r|fOS/\ARiAɗ%Ӳq>Uujrڱl4P;=7'=+=)Rjz{G}2Eֽw~4HYcW k{{Yc;kތy*ͱXWuU}fwxVm|)0RdHwqԺ/brFH[H)&i"F=P`#`+p )/D@BD!KYJ"KLKIM,\@,ĚGlns M"XHK<1R@e$qشJFFjDԈjܩmn׈㈑nӏm=bGsh#ǜ.Pc|5FԈ17q(Y-_/)Mȅ}DF=4Dyo S+E>J ٱ!\)Ke9$c>rԑK/}wNmR}ۊ&黟QѶ#.ݵfKWҖ; mK}wGgG {h R9@zqX{8_hip锛Bپcfh _i^7;Z.ץ - `,`N`!\<? Ƞ@(w<ʡV¦(fs"[c$-ѡV"nQWh[,_xFXq4Zo\WA_]}_#(f#(~M~MkcƹIIz+F&E:7=nI&Mc3xQBRD*玼v#)B^,S_(OCtIR2.GRkvNQ{tVXбH9xPJFڍc_ Mjl}{iT>5Oe#ҝ,qk.|4lm|-`}W4ǮFιe=gNJKk߻i\ћwxV.Bq]-(8Ѷ}}/DM-h[>GѳmKmږՂר - `,`N`!\<? Ƞ@(`;/O-XIL^MC 9*mZ$ȴp];t` -_KD"b%J($Tä&g2yX/kREQ;j$ -?DcdzO95\ks̳IfN;<-=H>؋Dy(ck_$>E¼Y_/$݄SZGi|v05d!Ql_f<=ӗ dJ@|Չˏ$koMҗڨs '0CZH>H3ǿ9üt~6Z m8|87ưO]=۸2Xxk_׿%F,i!nu;Pg{xvЮ&k\(0L@fl`8pn"H !CĥDdhQ{=BvC9y6#⑽_==$)"JlkUSHc͖r 5)^=-5B,טnDR<.ǰwZ{Χ")Bq_.{U^(}o!)<ħRvMS?B)QʾvNYaco%Mdfi,$EZj⽑BOH B0iYa+#kNm]g}P1aG ˬ˚{Xrڕ-|Xͱ_s]Jv7w#]K=o6OGK^&G^#bÁěg;/Z^'JC[I_ $|fYU]Y\(0L@fl`8pn"H !Cӎ6VfILWk.,%DQD]DZ"(6A&;4ʎjqF0yR]QvDtcN<e>r LLRˎ/j#:#L>er楼mhdfos? L&yO $>E!~.hʎ9QvܶpM8U9),=.Y"iỎ|gfdz@zN@c_!ɱצ|{2L6v?Bw?ǚ#L6fXͬeJƚw|;^ud Yc7RuKZMKkG|۝Ȓ42#K*^Dn*GԾFl->42mA#r%F=P`#`+p )/D@BD!KMz|z!z[ܞG؅HMD|, - śMP֨l: |?[ YB~S>1+]7k57^9+qFr8rc?G".O] iM"7ΔP - ȍ a*ia -/nEn|mM8E;7T9KD(C"$=@̍˹13 !g2sSd40i}oHN;*N׽e}1н66lMeEd3 Vʹe}kIm˟f?c)}Soޒɮ7p8>qs3[X3,*d(*>OlR ?+\H֫ݭ.=P`#`+p )/D@BD!KTv$Ē8h9gxݐ"' |DEdVPIq/hc0s1SMIQ4~̕壭\,j?8HcsKa2)fήhP~{8N(>t2oAy"Yh98~??rڱIQ - lԡ0)MLWL3̅іÇ&ZK־^(q|°'q)ӮcظGC劷  ߳kgH N -㙡;2Ey#%̎n~#9u40` XvpXHpx< 2(0D -OEz`-NhY7 nGz2<"*ϲ|OFMQ>8{FRM77H_c.JܼaI1?1yGY^o -_cAq8vSё5$KՉIFvDr>Jv%)J?S 9B)&3=&%EZVvz%SR{H_YbPI:߄Kv${^:[_SFo'={Y5f-3eHc?6͑_9ՏI)Ϻ+Ӹ5+ovw\Uy<_O/h VK}.{(w#;L"mwBK7/540` XvpXHpx< 2(0D -1H\B[<I=,q{>q,NL3 }/[HbhI|hgȏJȏ+Jr0fBu֒|pm٩ݒ\QhU-ɍcFv8`gh?wȏ%$`[!?a%^gȏsF~0uǝ(4~zIr 脎yS '=7>)-=s95,zjnv_b3+U|zۏFͧ;mԆhcA%m\j{մHmxZolAKr W ϥt|Kb*!NŻ'os0tPqW3whI6fv -9CŸ.=P`#`+p )/D@BD!ՇZI o,D:X8?˂BCEE ? -5`oT75n5(3UNebOrR}9ȍ,>#Hq|:"(_IcmT=s }gMLݴy#eD5޳qq1lk\Qվƕ=t yPHo[➧gHv:At$g͟llKv$.=P`#`+p )/D@BD!ߝYyNAGo.'zHHR[5dHr.՘]dyAѣ8Kqt${Oч -FkI|bÓ"SMo4b%FKrڒ&G%;(OGKRⅶgzM,ohIjeRI4> SG(55{簛p#$`ODK"d\A |z ;3+IF$ɧlI;}Fu[qKi }+kZfyV_k?pbmYɱyצt`]_%0]t_T,>@] Dh;[$.7?tu7p1;dC<C1f8N'1rYtizzWEl-kYRˏEZZkZZy"babZp+ڱNsns9 g?v\ғ4Hl7Htu - `,`Np<| p"H C8$`|nu[>L)'JbHĶ؋tpoJXcar[_3Jt5Wa5KA-JQjfHjd(=j'Pz8.`G+=L0 @FZzL(=>VOjrF7@io}2J| C6ʒs"u{PzdY5B5/a/B}u{[nN‹leF%'Cr3?ry;GŎKS>i#]\Gz>en+uYk.[ƛMo_(:+ƺhsyl?Ugk$xpu$M"ONHU]oyJe[G=IbE_'wQ&3X -67x/AD@D!qHwg}wbz1v;p.'b%FvA]%?pD#)9~WK/VuN \v8%*S"thIiY#@Ljªң 3'ݵj{|8:t -zb;ͿtdWFnQq҃5^:Cw=2 B<![ dbF@ ʄOSt6 cwRh@_Zq"@,c놘iҫg12ZԶ]j=˶/ozGΊ=t[y#-z/u/u\ϻ: MOKigNn׫.xF7ٍy% -(?=<(0L@fl`8n ^ B Qz#xe>IcybُG?q<ѱ/| $XN,u -uy99?5(TcEIZu%@]?1裮{"|+9N+NXDKXXc$rb(#PA]}.yƁXpH4sbiFV~P/.)ǨP^$q677RR6bEJdfds )O+oӵQZ34Y}6jE'hi}nCiˁ"uV kkq:ϸ~ͺ]k%IޮΈ_1-qCб&aXoAH2=7Ə44;s40` Xvp\! (  Pūw D&7ffY=6HGjYA/GtIA9 (fAFѬkߪnsNt>("SƹE?rnN0k 9jR_iϨdhDW:`r2=_kwf~DRH]ِ(_iDD{cԬI$ųˆ 5I eBV aQ9L )Y$~rt0,Zn^9%0{\'yŔqw,XO/ܶ驛{z΂YWK_<τٝ#KH5"©}_6eWHdAeʆ&9DzF0 V,x~< 2D -1CGY݁?exq.sܥ\R:8Y\C\KEIyD9^.oB:hȃj\#Fx5F.W;4ꍻ'Po'Yo2CS)P#EvvƼgk7<j/Ǩ7)+7"90RͲ+}*J];ML9_w v:ĔP -B6!1'gd$!U22GXMČ)lꙢFUȰ66WѦ4s`m.J[JK ֚h[笽iWiŞ};mފޑ5gRS|١Bē_87wFLN-1U2To@`0`;8 .pXx@ d@b(2G?1845-smisk6b.Ӹ4!M/Ȝd5(R5|XF5(5b"ϧkL0Y f-Iiej%c5& N#(L>i~VE"[XzLTnl͓_#5D@WeAqjK@8p)b(;7;'PJ #;BrNJrrhdPFP4rvr((ڨf^G^Me)@˪i˪b +?(/)jXy;<9ic7>.xW]riҸ%7o툋.z ?9ܟ-|X~CA0 h`  ' ` >C8A$!QA]ʢ1bAcrvqי/c; ܈HjPY#(dJPu0ڧ.7xqE?3(u[ǺIqI5IqO%d25)2&PRxHSW,ɨGR,l(; S4&R3wƨqj{{ j؍q!.)HʖrOfrpX\I9(ߦ2pEzFөC硩ƣSMG&݇2ySGZ?l=Vk?i݌nƵOhϡsڽwkݵWS On];z*Dp蓘rRyȑnGZn˂xCG@`0`;8 .pXx@ d@b3)uq$7xdqI16uNɕ\!)bBT+I4/5 #?4:X1L )]<")~? pAB]YgFPA_i|Bɜ@Pl堞U~y"(B6w݃B?KYsY%v}k eUl.99,q($2Bn(#BPB\C#=MW2a+Ts:Ckg,.L]]VʙW?YX -C шx(] -{ӆk<.`Uo ׳ox\Tmnݨo ϾCWI6/"uo=;7bue[u]_$[=P`#`+p ?A"!ʣT}2EUĴՄi$&b&:bKQKUU|A'lO5kHuD%Ni'6*4|6*Fk5U."6e0jXL0ojBN#VWHj|ƊKSb%w(xbbo/SqQ|WV~P(595N]7%\ "ǹ>SI)%9 \@EJ -%K#T:9+ {mU&TW]B3{iK]ڶm}J{ii΂i+_/=\{Z[vEnzW.BnߛӰ Zcva?\zF0 V,x~< 2D -1CyA-Ȑ[q<7Bl ?f;nqq0xl;7??8ˋB\'INZN%E}&i?w~Ok*k$Eѭ\P;?JȧVQ:O*?}reH -QytJY}*F]OЩkK@3IJJ D.RX)'% B"W T'gt|<ߛߣ ?P6n/MgOL+㴥dk -9k/kX}qvf\-iO6czWWGy.+\wY(%Z VFJפEON=rcRJϥ - `,`Np<| p"H C8$@y>HZfEt* [~CrFO)~?:`rlӡ&bxTMIdd2 F}M1k5EOƽiԽÓ.5)>Ӛ~^2֖;իdk_WO䐞?j#)j )QjQSܣ+h$E\Y5B>:K { Uɔp8̥ S@J$s$1qy-b3֬Swt|:E_P:א6vM M4s ܷ [i[[h4Gg|uU؁gUOF|-keciB˳gR ijN_˯Olک0 h`  ' ` >C8A$!QA#H)Q&f!)vw&gبggsq>.ARCRvH{U0u՚HLuS6F;79x@ 4Z:{A |&+G#(~?=|h1|݂⣿H6Y%Ŧz{bu)8ecKPsh87IY1+Ĭ@& KPq_[St_(yIZupFտPm,{697+i󩵴x`l9mk{s# <q5~κ>=ް%ywG|wqm/. >[(:;K<*R#Ң[M5<~c|MPI0 h`  ' ` >C8A$!QA&hf-o"(+x>% ~e-#~I1ŏXIAͤnH]/cn@Yn-(0&çRbFPlViEԊMX0]c> bOM){HzAE1>Y5BN?"aFEqMz l#7;FH -%A' m>zkE }{m:UΰqNT_1b: ,ML - -{#dmkny}>zd6V}O Oqt@s:6"v-Noߨw}.{.z-ɝo~A@`0`;8 .pXx@ d@b(r軕g;1S E{X[w.'}𳛰=F|x$xpFD dT5R1/?HVS5UhJ}l߱|H*XbЈ:2f*STs#;ey3Ƶu~W/$y{7D|eSSr WfQty|HhoQs40` Xvp\! (  g/C(lQbCbe/BP$>&(q?$&O4j1[#)UVªs&rHϏL -'GK5 2SSMb\.H#)~H|ˏxm.:d7 -PCĪGoPt=UX޾UH.~K-@zF0 V,x~< 2D -1CGO{nn%L31~?.!v: =FJHk7HD>D"ud(@zT.5XmY{U4Z!n8A{dc{L<5,;SrmE  -Gf;Rӱ+R#eR-鱲M'#x.=P`#`+p ?A"!ʣx2C2&Y$~]2Cdcޠ/A -/#QOk7~().X5)f7ʰMܚ`.>k$ŲCIѠV5)ϸI=p߫)ژۙ u,FR2쓿 )(+F(ߡF)/FEo8N{oFk1Sa}x xe}&!891cIN8ƐKyuW\WWt]W-ZZj+ VPjX""bEZ"RNsm볇cs=gm߷eѻnݜ|%sfκcܿgtkdͼٹYgΙ㟓H8?+swM׹#79ι97̹햜0oBzZ_s<yOI7~97O&`Bp_҂̴\ⳅI道KdLbnǓte_/xט.;{~ZYLþG)cim]Gǟ,kI5e1k_~cշ[9srbc{ڃmMuٷ;#PߝN^!l;A,fLwբwqzw}N9U!ysH8.@^#ϺI 2ՄO$l,Rȋ슐j篋ͻ| - EP"sPf2%>\qP(>S~o~s6HX@LߏQtUe(ei;6ں5K;kL묫k]{.z }-2˟o7VM 8MMO&+3"y]ʴXT(p]z F04V\<A@b(hL1J#f*6wDQFA(DVD(&BcW%NBaVC1~PdJbF(VC1mT*^PS4~pҰUCa>7ӥ_\!ן.|ZvnzaX7ݩe}[ܴzaBʸ9:@XQڥ|Y9k_ƏC0f]?P[%iSoʾo2e_gpuSgG;A8CiZ0%2-Z7='\H0L@ fl`8xD@"!y)ODBn4MH_ealaa: s=q{g77%0^&^']/_\P\ \5T(&P "׏ja"OKrR.HZ .,T./(Y(l&R\͓=JnH?BɟbgE˲k&HG>SR -., _pVn(3l?&r%DPÙJQWLw`T)V5/#E3 UXS3SpaKrB9D_c Yi!1-Obt=^86NtDn.|0,1Ęi*C/172bںc[[}KQe,չqɓخf{ޢ|?m&Gv6\W ~Ko– qCڏEGYG(6+ۈh߿AOK `P@,`N`n`^A8A$AD!qH^J /NRc1a*#*#̥e%pLArRNń%WAK NejPk2W&׫5klu©5ר#;&3_5rt,P }jLԈ³S1҈e:vdjW &K57Ǝ |%X|wc*&ĤO$E#&wɟbeˣd1~ˀm{/P91-~>G%-CR೤p?(&O.}~ίR]N#WX3TLkΡzI< 2(( K%]ɠt6´t#a",ka$[ny`6%Hgm&|ۈ*[C-7FBnЩUT*UkTOjUut\0d6]T8=*wUNc1DQQQLuUٮQ~.*1LUE2*kE҃t>)'\5UkS>#so! (I_ -~!=]gYD(9lZZsyIԈr}YQcl_͘ ܿ.FCZ֠!GjQ$&3X -6 p"H B ␼GBf6I͢6i)$smY=7n]\ZPj7J5Aݸ_ߪXghtcq#9qul1N?40WqF6&X1zl|xN*id+=z̩ކ؁lLnG6;DW ǗM}o2PQ>X%>K>S 18l6rL.3mK8lH_ )W'KXT# Bic/`=a7Y.Xpz F04V\<A@bB_\٦b ]ѿGX [a/$yNn#`O1'"B* YJϷ>F%(8$FIQE>8 ~aݢPtU-G%yS?i$?K4jӳD/Ha8D"WDzbǶegU1F9dŃ C3yi͑T3U4ϧ,ŇG[lkYG:6Ʒif_l?öl=^Ի{]R59Pd|iBmGTSZ429sϔ蚧Ɗv>.K$&3X -6 p"H B ␼q*SH_mY"+mqD5NJ UU,^)McJg¯F]*/kbT(j(&jOK5T(MӾL E~ ۫F(~5;/K 7f#\ {R#Ǔ,TN #wɟ*O9Bdc|&( Pӥ4becɒBX9#34(t7wGúl4}2L9ʼyʲQEָ"h硃4)Y|:|_d*epUSwf5;'8N{j3S"uE|?-V: 0 (  '07/  Ƞ@8$/?1&hDM )1B"' >IB(䟅 L« PA#7`sGnf')W(£1R\( HM=b8u֋ɯ0R =;y4ݍR@$]0RLzucqw JqYAt,NV^?C8E/ 'Ҳ\n9|П%&24LҠRב5Ϧ'kV7Ԅ°b9cl1-)0%.u[[忯7v Z2 ׺[w٢ەgK*^Kj\Kp5|aSXO\jY&!xPW%"[6JD[>l%bN(Q$&3X -6 p"H B ␼5t%a&,K *¶/'+B)ITi ت5o5h"J/%Bl"B.#?_jQ4F5iEqkwԢ\QrK҇8Ӿ={X#(ϩAyXcr\=Av۰#X<\FS((Rgg݂4YEi9܋5 -FgbuF!p _LB"WgefA>=XCP -/?~U*(֏w"tfe4C7<˘ e,%ƶZ{ [aU,o!`Ukmfr|z-z&oK["(]J^eGE+^kRk 0 (  '07/  Ƞ@8$/7$GSAU=yݎr^@8jg 'sRB.o+!WHeP@TI$o.}5c也 NіGpRd -9!1/%r~!MRKBeAX^LS5FYf#˟IQ~!;~q|aʶtkovng\)Ƈm/.4@Ҽ)|ɪBQxSZsxɑ꺩#;Fq]z F04V\<A@bƏ#&Qbۣ3\RY#r R'^)E [[;k|u FJqZ -Z -Jq(w 㡏#O-P@:RI)kxLHwP#˫R͞uBq_EuڨgSxg,BބP\r F}>S -a_BZN(7=rrL #773yw%SF}2F_\LæS]RFS]<2PGG[WP/Y{c GLo*O_M=R:e3ۏgpKuMrJ3US"e}hz"7 0 (  '07/  Ƞ@8 -(8Yk{ngq ހO W]oh4u/PTGɘ1RT ?1(7v2J\|EKTKqF)vo ~R<8pxs>ҳl1J1}Sz:J"~](Bd/UEˎP|fpKB 9 J`x\!WҳӂJђ<ԃ鷾"aStrWIYDef% -9MRDҥǗj655tyF~0ţES]b>^BUSBum%e;"k_lcO7,릙XW7"UMi*İG̓CGPRL( iw4J1Z[Rzp)ƍ`yRG). D<ϯD)EWm:sdfBz[Ώۖ()#}݀"4GCY9`DRv 999B6sP)*ZƗRd'`v83+-! .E)f"Uo]?X=e+2P˗StcYʲ`[Jvڷ=Q7Y7)u-66s[v}۞\3[TZmqk-Js˔oEVM}%U -\H0L@ fl`8xD@"!y)>oc(n`X!9F7OEyE&jF b׏Sgߩ}qϿ5Sloj)Ehld\56U/ŇkŒRAm?ỵ hI٣xhB'^Rܑ㧝?C8U~ s !䋊F~iGv.kÓt;Pԋ87}}SO^ʙ_?jܐFUl,[8Ǻ4ч c:vf ׾];=RS|GuO긢ju|Kt/;q3Ujt'tO"{EP `P@,`N`n`^A8A$AD!qH^J'Hfl# SA5fLX:k#aN GE0;;MFx:o $n'o&]$Oz13#A)sgaxWǏZuA#hhlŮ1~5Ə׏{A$]vɟ*= #䥳~D(4 7-!Jxibz?D؟%~NlB%{F4y2nXDVZ>Z"Ґo5P־󵱎C0,u5c~/ijSߒ}Z2/>CX{XSa\^}hJuZtKXsq*.=`#`+p.p  -D -1É/T",B$m-U 0I`" $D,eq|Pzf*4BTP"B?L wCqjSCƎ[(p.kކPDq y G HQ9SLg牒-#Bqdp^3ckL.ͅ9P_g\<%~l2ZO/_^kߚqmyYf>ǘT cZX[wzk>b%LIu팻lOsyyD#Ώ^%%+$қ~!xvlI}|y]|hH=`#`+p.p  -D -1WxhynZn{73'un=wsHsBzTz@3 Xgic#C>&Kϊ5#}1`PX МGkGٮnM֣0$O.ދu 0flƘ1݈zn111rk|h~H Bf z8L#pBiϊ]+oغ.Gcn]SɎ/ ks8SVj4n+g>^Y -[}j{oRG@}%SٲuK Of»u'u-Oϟ/ Zo+)ɕO蔅yR9i]b_5v9pz F04V\<A@bTړ/^B?~@WJZz@8*:$< 5!FBXCTrcϫrZaeUVjU$LAUJ[V*&C~y`VTrFVljVHmzWqHc(75+!+c(kJms< -YuH^MJSDNT!/9yǿ~Cu)Z$١t A/f lĜ`vxK:c6Țfڪ)c{ e:PhSt"ʼe V/kmsk/zccwsw4sKU.V|ۊ=W7% HTgP)m,F>ژ,zgZtwXm,z F04V\<A@b`y 7 U v9yG6d6ҿ 1"5V/gxNcrZJsPNT!}ݿ!F)n!×"=;-!?!/d0  -7DK_>:~c+޲ ;f*QƎ唩@U:\HY U(۶%?΂c4-ZzY7m=AO6[k|hJx -we*_ 0JSyKkRlJ؎iǶ.Mץ `P@,`N`n`^A8A$AD!qi#`xKf"Z%d9B=^o!d+ r`rZ75yttê1SWXC^k9 Q^a>t)7jhRu((nb^O(h Ky3P҉"%zd9S,U5*n&JF}Ko|ꕀot\"=~!)D)ǟ#)Hߐmb~Z:S_}8Dd3a({1ƘVUTw ݲ1~ycl WyXgVW~ .=b׾4Sx<ߎuDrm3ծ~ &!lRL WH=k"meǖf"mDtwՍ\H0L@ fl`8xD@"2ګ1SPB1[S8]Fo~(yk>yPy׾Z)ﯗȳ-'܋#R/J^<5|hRdsPQl8;YPN8[>9ؾˋԇP6nykg(Ŕ؃P EPg(˦ ֊kk(?D;L맬+o?>lcTW5oʾ nuT~ª&MkRwuJsH_ӴhbJ 0 (  '07/  Ƞ@݉yFi"uf+-1q"2PIW -11R\5EcR$F3ԙ38c3Ňm@)nC)kO~eLqZ4-癿k#׍`ϓz`i|'L1'Z}P^_=<oӘPHy'hP -Ty1- BNVR"D/P4>7--ć<vɟ*?[g%#3Q|jbRil13On}Ĭ;ɹ>'+]sALQw|3d~G쨚i8XL7?LcA\m]]Gvc{΅ifVՊe۵!-j};?ozʯ]7C(`X>$g( ͈3->K$&3X -6 p"H B pb;w3+*b!:֍ 8kuKKO~R\>xD-=j)И)A)h1O /|b0S\RT'?UH_[2z՟dtmst||ˀTÿB8-\ 'C Rvnf6ͅ3y^|+?JcKȖ Cʸ2-,1Py(Ec-0X)7X{걎i/zuA)llcCг1[[->82lP)_7I=Y)~3#h+bk֥J 0 (  '07/  Ƞ@8)(3)}xύR,DEr Wj)(EL}g@R4I+89SiR;J*wFQˮ!Sefe冂 (5Bq";g RBB;(EwJѹwY3VL TE=E/;,; MKP -U:4|;>"cӌe[qF` nOT~S kSZ3I.|$CY}8#iM_L?*K$&3X -6 p"H B ]'fJ)rb:NG)>&b̋B\IQ>~>Sj)6hZp'.vg.(y#Ѭ<yC~}`)nQKqF)UW[5JqOqzw4fF ڡY/J,/>yTR6`p5X}P#RJq=1~!3L1ىdg3CBȟ&pN(-6{98~꽏c{mk>i(+c(0POS')sRrYVphfۗ3Eeݼ#)Gl}G -Z7ML۝RkMJSS"LO-U 0 (  '07/  Ƞ@xO'׿|4^@){}w r`ê?,Qc>(œ#)*a)xqR#qŁZ5J9={>3٬1SG__R"ya{Lzީ}ҷvDHK(W NՋć?I\0%>E:DH N >šSێa#74HVP> zPd_i@S[ 'm*Fn\tMl:-ZV;=V8U -\H0L@ fl`8xD@"+\}_5q)Dsv!8ɯ/RxApġC?F)>P)_L]-RL)Ώ]Þ0;ߌ/幚O5Jrs)(óߝ"1'ąҳ4)*ZJ.o1usG8CGCZ_beLŕ4U^a[3ikg؊km[.tT4i_vr/>R]xr}%|-Z[zU!mJ~qiui;!,{Pj%"ǖW^#b}ԯ:5` XvppX|x@ dP QARG&qVR”GPD eVJ^|ă[B$r.$|D`WFP !DjO\=yT<Ħѓ)#:FdzCN,;1x&^܁\_11rm|hHINLRPBX$41!$YirbXEAΎ藯/4,S* E.1xY`V4Rm|Xr?ͬb]ǿ]/]=M+oW5#p+۝?i!tLw85Ng( dDE" 0 (  '07/  Ƞ@8$/EGp @It֨M3?x(,yr^^$1*Irt7hJU(ZQPEBQ{/W#9NZ㫎f{XG8oX l@(H9Z"-1wEM;~uOTlD(Z);xާJKb?+-M ?+J9e慎?HhFdXF7L)y%e>HYJ#/P'/6qtX綒֊ ]Egvꅂgӿ/=ro{)o_+ĢNikS#S"[_ݳz -K$&3X -6SG7Qnftܹinf$w:N&8;. شv{#xӽ{tV,˃݊X""[RYXYZ؊X+X N Csn9?|.pB -A p "C Qc88:u `?Be:GVP\EAEqL{C6#kqؠ@Pz'wP\x%qxɅ`(WicmAAPL@P4XAߔ^ym8Aqwqr$9%Aq5B}ieG\[C2baEsUA:(HR0. Pq kZOYO"T'8eo)ii\L6eI{c:qn؞%^%Yvno6wο-O'B4()lH'E^Rt$EüyR||qqgJ2_W#)b|T%GODJ4G\p~rrGLI\RTIՕUU|a0ZYl>"B E -/j -1IqI1rLߤ{gWLo +H[Wi?_gv )27\kF()aƻK׳f^%̞}4tRne|Lab}+v%U/ږ]ϧe`+@ 7x >``! Ƞ - BĬd[=b$E&Jx%ELrEVR|OOAR4%mIA$58Edw$Į8%NR=)yx$YhӧG~mg'0Lq~;Dwvӟ 5!)~ɧH?EMq5Ů1ugGLI\D -* H:Q.XQU!#-Ċꚱ )<~:)vZ֚s-HlNדَ$8[^s-?<[QSGVg͋2[2ӒVUo)t4Y<[:4% uϑ2wmye`+@ 7x >``! Ƞ - WQS%l-&5JC!;DZsNwnգBO Y1TE1$Pƽq?ԇ) Iq~KG$kbtRI`S=)K.3H&}aq9$s-!MF&'i~5$‘L0GUB!~7=oJ.3#\#fPkՅA6XQS##aPImHMF#)ziYVrzho#GHȳmٱ8WuڿkGiga҃J0'JC;Kuosn-ҹg˃ e䏥ʵu3bu5V 48n|B8A$A4A25ub)dBDMQLNͥ)R%BQ15ťe?0)v3?6H#;Eo-Κ?&q,vg\ƚb^S )'PS3tBkIO}lO$Ń"q[sPS/2q}ABܠ-U {fxGLIǒ8`am<""ڠTP-gpȾ'f-8ՃKiibóGH˶G}|M-%=u?X1\'(]Ww%ٶ5iW 7l{P R~ 3νn㯨)% r`yl@{3X -$P`;8' CX< 2(1ÅSS5fPS(6ɮ:TZuJ)n6uY4vSQ$ژ8Q9R5+8E^S<~e:)~n|ySt_yVeOsAIU頨6 -=Ƈ߹ǚWW"q7"("S4rŴ{;bDQ2r0WU|UAP - FbEAGcAIqD~F}1E˟sLĚwg7̵t~km9Mv/$gsm} }َƇIwqpӔg'm׵fV :dT"tTpduid|tuBYT]\2wFIse`+@ 7x >``! Ƞ - ~ř Ծ9\r"R@dِU%`zP Jvs:(3zI1b'|K_ICbPRD9Ҙ^RE!VmSh>JF}˵"RXbA͇(+ #5_x8c&ϙeXBZ>EZ7ϐTgiB7λlyfAʽ{tv1ޅ#o5^q$EZf -}[&CGK |{KyltI2fHvp Np<0x@ dP@ b䫰> oIqv>KBRR'X~.") -'cNźt-IқjWoH2N}8-ũ飃V=(vYA:l'|z;!"pśك?"9JH0- (bgSʼny*OhDlF5Jg(+j -%&T$OVR4(_+j Z3v7J=?ۓwrLii{!9k]c`\[W#io9؄i l_jCGyFo˧oq3ͬ7qc(~$4E {E)¹''suKgC%ʚE mKX{3X -$P`;8' CX< 2(1Å(gQ1Sl"J -"BM0Sr@aC -f>DI1s6X yAP\ŇW(@Pvwn6T$E/IѭIqLdcCq fs[.G,("A>. -at ",EQJǮ>ڬb9hbpּԵݍf:Dv(zI )nѓ:}ë&Ig>PRѣ%zPmA2()Q~[:( &03^S%'ͨ-Y!(~w%?]]Jh?=z[fk1w-DRuUH. J|EaP"BX*eW()7w [>NFibvry7LBRsH۾ͤX7\ ϵyA{pA{uގGm꧙mOGn -[r#pihǥ\c|ǻ -Ľ~>]h.UvՕMж|=#64!M.` X -l`^~ !D@T qPRH]<%$"BE!J7QH̍=~EAI> (у+3m yP%Ž:8%n Qak=)dl>&rzީ_wY↛C \1=} -J7' ǐ?t%g|~E@"AAGjHeńP SO|1؀=ƧHGd{LIۺ6޸8IweڏScg绌۲f^ (/lcw";.g - &+V]ɛH(gryAR̛k2'F''}I!5IcN`]5 HSGRtHGf;Kɧh>G%dM#n\Ȟ3#\+2SD96\YjBa5HU<&``! Ƞ - ~řZL)4G1s][M-Ќ1$0-t^SRSS,5h>jARkxV+L )Nde"/(PR6\kj@qMB9Ҽv-6 /IG͎uHzxuyh>:|^v#W6~h{XFRaE9߀c[1q[ZtyӲRe2u` ߤe`+@ 7x >``! Ƞ - ~BJm-GJ -r\!HJ]|j&>o/N07hAPFg(1 b|6QQXEv"zW%Ł*(#(>Q2E8hUaWD\T-975B$" -cbAѾ=tPܐcL+_5 rX\6s6 6p2~?۱X.}r ZZ!^Gy:֍=QQ1n8.HqDN({%(%"?x.Z-N9A;UB*۩-yã>:d;=j -&DA6(EтJ{~;Sz8,cHg:Zw*6.i_w8۱!ްqsG)Oow7bʹo {[@s2DokzY9Sdv4s~DaB~\k<ۓ - 0@6hp /?p"H *hxy.up7gQEBDz\8:OcDEUBQLRŷ]IzPzR$Ej7g!,H]4 )Z _ pT$'")zO%kM_Gӽ>床05F -Fk -T \X 5)#HSjD竨)ȵ4ϵvM&Zx&TO d;Hzƹ``! Ƞ - rKoQS$w|Rjid -C-ő$1=)$2=)4H(kJ'ŵI1yGI^泑M%;룓BOARһ5Sx󖱫gyj>&yx;ztOO"sI_|Fk1Sa}x\Uli6cCI: 0ǘfu+-ڻ{^_꟮mu]RĊlZ+[DDH)KkEX+b[+VX+b[+;rec_k{~Ι3J{o;^I&s,]$%Ky3oͼOKnYJ_>%nZ]Id{[^%;鋳_yq_z%,a˰,r3l?Q¼ EFbӂBӦDY~/5A/GD_V.>'~_k3sO$5'''';}?9ͩrU4FͪTx4A5MTMTiAմuӮ0wa?lZ${X}m8wva,i6h%.xN3b?,'5?lٱ?|?lVU_Hw%z(u 5snKy={9ꬲ.:9@iTk\)[f?f'P73Q-Ad(^[~h.x` ^_uatJ9Y^ZK*ʝ:GL$W/k|-_\ÔV_uy^%V>}T:5~ApxAhX)p]j 4Рfl`8n`A!a^jRsj]\/BFtrU֫mR]!7'2F)$1 ~TDr)Tr)+JD.]+EB)$M5bDx]jl)*R<7+V6O~+~+E\Vê JC3oGKa`)ϓJqt3ybSi(K(9K83fRgf%&&${ /zIl7#199&MW eJ1CY(xM{N[v3jm@ghr._gyinoe9z8%u&$Wf}FuR+/\7hVJUiB=] -\( hA4F0,`Np<XD AA~(R5: 0^i -Ø)xU(΀+ΓsJBH KB%b\ -\RJ1C.tUB)B)LtU=A(L -cCq -A!b^!|0wH~(w*„PTcQdZ4waX)n o(Ë)#ŖA(oHqQޘ/ጄR&?0R)l7Cf,uK1|ʸP >E<8P9Cd6(x́oⵅLםG瞊7`8:o:Ӽe0mmۻio|]frv&KumTg̉ ت븶Byb铩R}_N 揤ZW^w:.5PЂhЃ`3X -67x  A0D/%!ƇGzC&,Z `<X,>D,>? -%-Pr(C H,#6PM!DžbP܄dR>LeL)nKq\ͱRܥPh)P9cX|#F?E) 3E˳A*Pc3T$~RÏS\?˛Τ'{#IH -f'ߦLT5{vyC3*H\ CuGWo Ӎm'M8eI[wӶu9+MξXךW/v=5];P} S'm˿'JwRj{qAm -\( hA4F0,`Np<XD AA"%LxZx3Rp6`v"a}"dYE}GʥxH.Ň -3ůR<}V*b -E+E**2Hq~rEqqB(~/gnV)N> ERSy*HF>+@Bœ1)M 1R|[ۘ/ K|?379)#e^xS8V`"|R2f;07fk[LԚō kv~6]7OG W ÇLuNYcX|mNncIr}Ur=C_Z2 S^ 2_[iN'JkXqmK ā`#  ' ,p"H KaF|FhA21RHfYEPHNsχ$$G r((U^|ܭX( 9*99SB3UL4Q džb - -o]GD?B(numNu֫PxC(v\S #L]P-AD!UL(o|dggdebg /$ -S:NRImQ9p[PyqqU;WivMt]Wtk񆾾xSNs[,ˆhk>VX9JL5I{jO];qЖo - H9R^]lO .5PЂhЃ`3X -67x  A0D/ E`uW`|怅=u]aENCB)~PܩwϚy]y0+BQ/M3;Ɔor(>e|(Bѩ0Q|%/=CaRؤv -+31Q|rGo> -iXWSdhAJԯx(@RR}v2ߴi̗pB1YH6'$gq^.13fDD,JBQg=PD(:*MTS5k ߟ4>QpEUyu,Ohk'-i/lx&հVrm=MLe67:}.?&yJ_M | --Ե= -\( hA4F0,`Np<XD AA5VԅhNF,50;LGqP? -F= -xDZ!!/=iPVXz|>POLi&) ǖn -PDھ(nPpv}k='QӍ#V= -[gYJ0-:^=o9㩾LmkpU'lN8ؒʻ6*=p]j 4Рfl`8n`A!a^J@-> uc9Ș3oX}|N˅s_cxmg^(boR,O]ݣy\sX) -hǗ1XR霱I. RRwKqh1JѭW*Xzr܏bO]yqRS<.=,>3Ef֘/L"& ^.%;ś=Zng'2<2,[TnX)z^ްD ~(d$^xm|r4^qhq -ܳs[ښ-m;Ӿ ԫ&\5oK>g:rw\(Ah,ZhI8ܗ<І'bu8Ѐt@ `V>`x@ ] -Ji|ZY`ap}ʌ".>Q%LJL\9RJqƕOcJh_G73 -H!>fbX)N\5OLaP*4b1JGq*xx`@? F?(k\ -4 R1S\%ROr3ə,ˣ2yoFr$ݛINNdql&?i J1 -yX uvսuQ\xM˷ځӇ>Y3pHΔYa:8Dۚ99:G6s`wk)zJ>Me:_vL;9`6 KKS˿I >u8Ѐt@ `V>`x@ ᇓh)XmXbD0z#`unS+\[ثJO٩P}r).P(y"y;5/L'WLT9)lh&g+KgS -X6RtMp{ifKs|ԯ4{P @6KbS K*HiLq1_ڧW|$KYHVbJ$=)+Ύ+EKtgv]Y u3&T$`y0^۲u89?yaf֡xN1S~WOUO%<(+=u婾T{>۽:f.U2O<{aGjB ץ -@Z z0L` Xvp\8A$@Bv4(]ֺufڥwFn[VlX݂KHgr)T8yOqB)KOrJQ:_LzcGұZI!{Pܪ%a -٧)pU!"bxk7! c9-JZMA*B(+| ghO(B%x#Y.^.F̤qX_Eu07*ͧCaI=%jxLl~+AP;SU`WO$kgWg3Uo\h.|BKSNkN۾LpmNqnzvW=5xH|w4-^*Z_eŗ }_#bcD:^# ䷐`$ - 5es( hA4F0,`Np<XD AACy#|]CtD knS.1! ĺ$8yroa pD,!R>w΀>9K%:%g;*ВdIU9QK0{3"$O%~7S!%Vۨy+q=<0t~k -ٴ-Yt@O59:~?>;1_™92fd%gEf%{96Cf0K$g3+oa(tvӆg#⏦Wx}U|`$_g:i޳}䗴umۡsk09w%*JEOGS,`OrW??/5J#5UiPЁRq-=&0`;8 .p|   !C}'vcy} Kt b,ǹRU(SG-Jabx&+q+xQX|W~xщR,1{̗pfJ2Y$&1`I)YI^.#XdE23V`'/lQmzuձcj L } C[gꎔ E/4vo -_ZhBK߳Nf㹅ß%8*_Nq4*[{{u=C:Lǘ:k1ujXzp a]Zj1u a[:pj 4Рfl`8n`A!a QFU.r&b* b'q%5ēK|)$2oBsT@&|E|ܢБP -БkfԹe crF* rF, -#'{ u2Ҩtācr߅϶~hmd$%Dor$d3lINgD:h?}'Zf`/;yuyӛ1m>o94XM-n,K m==kp&ZV}DžaӆNdz6qo;WE=ȡ8W!?CCqB(6ic״IߣU8Q(vcˡ -ZV6TmJ/ܛB(ߡ?A(W8 u`Yz%+觘6j P^5⭮1_ -?>hDNg#7GgXt`4riie)vaYݦs5eo$$w8wP*sx{p%WXOsV,:-Tx2 -\( hA4F0,`Np<XD AA~8$~ ʞk)~|0H]W8K83&/Ev -l7ILrYJ~d'tnQ~ O"' S /jM9W_3tK~9W1+y-uE'Ws-p{e3qDEۈ*ߑULOOlQqV&7 9ݛTboc*RkVOV=B@Ah@ :A0 `+p 0< BNꈦhНDJ { 15sk;Eo"Vj#:K|$l: _CN"vLBE-aE mQBVOax}ڤMtb?1yêJeyJ˼ nP>W\O{,JUu\W\ew~E5x,-AZ@lu8Ѐt@ `V>`x@ +x';_ciI153]Xpq ?tkKYa&;Q'bjgOB)*MҭJas[cr)W@>Kѣqy^) ]yOϱTcƳ ?)dzG1 OT[X@fn̗pv'߳L%xSF_x͊ɘM -G6*1 _Pv06@]]F5W'lzQWCW3z7;Skdd)+9kE[yľk(͹-ĵ]To6O؂T\oEBa*`_J:zd*w\U -~dsl8Ѐt@ `V>`x@ !z)1:$&m$B}14c1s;tk# ;xowt#B5ۈ>}GK&ݒ=}nGڱr*Z@+ - < rbX2UYBUkc;+;:f/{aE~|T.7>ԲfgDId˥$B+x3Y6#%%DBUO^a0VwHWx˚hDѷf v{M*eeOhmR*q=hEU[P&ٷ}WuijAUCU  (U9?J6' *T\\( hA4F0,`Np<XD AAN/kDN4DLtn'vb"L]L;tUzk/qOa;Of"tq/:\QUJ>VXUH*)EmbSUWr,_ zcGyӮ*#US>Y%T|RFj)HQVk̗pfflN`RY V@:] Q_Rŕ<}o%O&g 3N4"w%KzVw(,9o -hv)V@R+Ύ.T ā`#  ' ,p"H /0$hUڈhۉmDL ĸډXGNMJ܃ijM؃k&|J&2᝝,K\"We\)T=\qO*NK -V<*Te`x@ atckA%!jBw}#1c=&b'V J=qW=D6cJ=6 =¯Ot+%4mGf>BP1#3ie)#aڭ|ֽL(FVy -t.r|x)?Gҕꢍ[}皂ߩxq)TyTuA*tW}:ʞ1_* -tc?YHr^A@`xOO4(X]yiaAݦnz&hH0S7}}mMe ;w_h9\n.wzXh?қȯNqVYUsg7:f-U ^#b/FXSBKv;򃺸F5PЂhЃ`3X -67x  A0~d$BW}1#bZORIUV-z=ZJ`x@ !z)KԷ"'57kE; 7n5/i]2ݭe[wzwr7˅;Rׯ -ݸ]|nɓ3)t#xN=mD8r"L ǟp<[or8*B8~9I }ϥ{ [GX|r6Q&1_1a&xIJJO ~s t/Ɣ69&7oîS c߿;T|5G[ݨ[{苾cl2Ӯg󈹲@ֲVWGhvsWMӺ )ܶA˭۠b6-*6w7OPڇ}~npvų[PPm7QGU_XG%:Cš5+u~y[P;i@ӾwαkYљ:]r)zvNʔ_뺎7o -_wJ4l6-T",/K ā`#  ' ,p"H \ï NԲ:(2 㜬=\87ό7v%άQMZCG>S>O< /H~%*%1))9LrIɼ7]`Bbfr$IV jN~cG;Ө_Tŕ3uF]AN"M%FTK2ѺI!Ə9'Ӝ%ǢKn"UmU̩WTUǫT\G]EBNp'WI/oTKzQjߓoB@Ah@ :A0 `+p 0< B§~8\ROԵwm/5b|1֏. 38GO񵌎݄#|5XJaL2ZƮPU\M4l -o)SGy8ì0$N!+T~E -jn:{=5/zqoaU1D1.@sXլ:͎1_™z%g֒&9OO~&[%3\2b1|V5/\zvH~$q A~n1}}Ӈ g JmMWzeMzi`wR{5u|rmwsa7vWNYy?L(E}7m$<\铷Cpj 4Рfl`8n`A!^ (kf ]6D_xWDYFINWq/09#WMKSqDTrDK!"6dgI#'GĨ0NK_^X -O+Dd|ddE)D$k_>Hls" "H=// R7`s| gmGe#lfVM2"^ΟKvF7+Ŧd%ߦfp<H;&uAu -Waf ړ/U?h+O׽`(1TqBSNkyvjB R'ݵ{<9èzp!" a׈zD*>Hh!M;Hh]|ר -@Z z0L` Xvp\8A$@B.&M>QDǒB)}jjd 1!ѷs!#;myrw黆[3zfˈT9 -='J''Ny#)$y -=iOH@=큉z5c{' -=i6Eސ{ܓ:BO'9dq>Wد@O>hZ)z=R|o/}CIuڗTO&j3\v=a3eN6vvbN)zRKG^}.֓M )T7ћ$h:[gꆊm C= u3 l+޾Ri]i;XIg>OГ'zѓɚI˄0 =AOzw'rOpj 4Рfl`8n`A!a=xKԫO#ڢѩd5C1:b˲Xh ;]9@՘Lj\, ].pf)8Z"-9o6Z?md$/g>1Lwm -R$ĨyIV,BJ~{CyF[~[,% HrH]t؀בUHMHØ/ o~wܲY~&)xS /gGQ؈o`}ӸңC&u[)Tqo*rm%hO4 TS'; fm= ͇?вyZik{{w 灁]ЈͮX a}Ӹ")YWsPޅ ofd7ͻ Q ā`#  ' ,p"H GhJ֏tt4)$İ~]7[qt4)#\ZGkghLWLMk)o5)$OM\ -9IsR>=rBN.JNfL:DLD=ycq+4)d'\{>?ކ]:+OσɥyM=i\ftbS%]p[ -䄰ԩ%w 0?񌟟"BV+Y~/O -LWdYd>y/xnsO5Puoi|4]WZGw׮O7ݧ3 \,˱yl+/W929O&Cd|_W|v sYƫSkm]:;vץ -@Z z0L` Xvp\8A$@B޽ʍ ->`I23]T߄t<>_uK[p6yp+"[>g6K^t+"m -/9zIK'VuDk~>L_}95_zwȡ8, -X)ۛ)|F:1BQSEuݯY=pzpgUp*𷌦QFQWD"=j4vU&YE$K]h=Uu=%YWE\7@&M*L~kC}*qTRߺM@S`ۊJUp\( hA4F0,`Np<XD AAFF飅hꉶ B-PEH8ޮӏ5Wyf}@/ZF&"b5yLax[?JGƴ^La8'揉3M Oғ> L+,grUvwot^1ՙʉ >ꢽ][1~T&F?! ЕA*x/m̗pO0p$cL薫KΌx3)lz㫲Ui{SGǪ2/QU߿kg{im^H׾3-Fc_a>TV=/Z[^mJss/9{?B\Ճۉ{_ zjw>Td>RU){UٰFg;*iFU艧U=ϨB9U8Ѐt@ `V>`x@ +U'ѻ{}{k.pKʣUi!U;Z´po'Ѫ4O|P*rU(T^J\BgkDY9P>vBV4MM4類 <&j,b$I&dS +BVrK%WV7K8CY' YIOΊY9)H:*R2wƲc# ԪMs/u- ]Y?c8Qk4}ƘˣCZ%Y*V֢U-4ثN%'8sL\{mSuʷ)z5Td=A O`XyD%`x@ ѳ/d ZB.)#6b J՜~xJ#qWO}"\ [-Dji2YЗMM!+iYqGmZaI7PfyXQ+Tf*~o ζŰrJYO*OFyhɭOwL{TJ~YU![hUs#|CBJ 𡀐&dƯrR.eqT(<;x")w7e'ˏmPL:zg)z㔥iГl[eo>:ξ1K|M3}򶽣ߗ[LcPcظ%L[e(VO ;#RUUU<H0 @  '7x/AD@(@}9~DŽěGG-o 9._ɱao8~Ʉ̉WD^3uAۨmڪj֩:%CHpҠw 2苝1z ;un|lj2EqZPL !0Ꮿ (*?ABKxuտ@¶hT~6⫽2zorpJdd)IDZB +=+/* -\Nn^Vf cz_$- v"UG.0t%S+gFXw~l]Lv3!(Үgifq}MSΞ4+PR6dpN.L۶o~.WlPVL Y83z8x.`,`Npn ^ -!Q(!LSAPL*6vĜ1D#eoA %EL%dy (hA+X? VR|]F ۇ01}"SKwu"O+)))F< iI!sH -U=I )IQqJhYII>4up݇~%\BK*ͥS'^DRR\v6oJ}w>Ӗ4;74^Rqkg:tfeUw_,ώlj9{z\rY\vu=&3-<U FWآ.i\ەI|כoڻSNl^k\jJ|t}Rڒ$f Xvp\<| p"H a@b>ʖCvZ¼* Tŗ4S g-pyzH%n&2C6*BY?[=Əub=DRVLԉbSÕ@rD+]?R>mIAmKXMKT1}YhTL| R啟2*:eW ܻGۑ9jD>/*JhC fe+<Pn@:--;75% w4*57}v&*g:q&~;qG8l#9}Hgj̬rt8ϙModžYڵஊ瓸0ַ7' KwK;*EԖ>.-z1)r0Re$f Xvp\<| p"H a@bGpyOY%a"UBЫK a]MVv*>`!Uw=+!J[Ak + 恇tRewX-Uh24`amşueu\=L}xwoQp!cP=b#Op`jNl`Ssؘظu7#s{~r 3Zluvqjvwaɣy GIqĒGYֻU$=܏d;+bc0)_r|+b}^ժ9_HSxF<)ի5yiYPNn -Ib5ǾeW'b#5G_&0c͵#ƿ3t*rXk?Gj{sotZeV.^xV{K.m"X@pv 5E/8mRor[j#5ݏ8[mگ3# `+p pXx@ dP B GYdQDAU"ۯYńk sc!{\"}%|~pXHHy~}]':BZaЉqZtdhѱX':Bt' 9lk!Dǝ\u*\m]Y[8Sq8cu$o:#ko0m"yõmQ&o&hWR~G S :>/*JZNMG§IӥCL gdI鹼 ?|ۻySڳc.:Xרmn(NvvSŹ'S _[a9qaxT>eu|cs/꥙էX&ʳrqȻk[?ش`U4wtYB b!TT1C[?-ꊙ5u <H0 @  '7x/AD@(~#Z+xH)Dbd9;>NcBXI\aЧA{P2ͧ̇&S5%sql1emJu=Nٻ.m\F1G/)Ovė!㊯/0-i\|OF5%cS2;]73DR @L` -hl`8 ,x~< 2(D!mSM`9goٰwo_JoER$YLlD')m;SRj%E6MBRC8A$A0D -1<򬱒R.',K` B T@0Fu0 -Rq|ǃ?H&taiBZlnj!,vtIt.<ۡo-NPjoN ֡28 aMae -t(%sPwЉɅ;v<"7su|YU!Sä⟂Y;֏җW~@) -)U'Ҳʙ\Vn'd}ݔ;f]gwpC]<}nc7#LŔ;Z;{GX -J)Sɶ=8:y[-͜a[ޥ<޴%Qw+۞uN+%(-Ecuk]oe([ˏ͈l03oSs# `+p pXx@ dP B PLa2Ctʨ;x!y%藃8Kס|^ڌTBKSZrQ')f !)֏$|$~4FӷGt -_jvcVx -NRl§K')Gc9$""yé<52{_=Zx!NZ]KJ3 0I&B#%7 f2<'|MIGQ")VC9<ƚe+~ѡzG׶Qγݴ Ywڡl^ y{^R|d;39A\%8C׬9}xFa6<H0 @  '7x/AD@(@})~QCERقvIx"wr|-CD~ )D9o%hѾLI-)j7L&'!$ōɃ.z.&F7 t.jXO%#?ſwϹv#(hA`"(>}Q${śW:C!3q}^՚&\aBX^(ΥBiPR -dI)|:BX%JBg'd{Q}):Yʺli~e'bږ>)igg[υ{dEݴiܮϦ=OuIO>PۦW-43Z7QR @L` -hl`8 ,x~< 2(D!QQK0/zr6;N5 #(,`adi|APܢE^Ph_GJtK WèSRa)/ܽ@MwMhYLגNI1\k>vi%ŏt!$ży:Iqp!?w_aA1InHzN/CR.*d0&oycZFh1iRzZ. BtEJN^*/IRz7%0Ou gI#kƢSCXsj)d,΍oikثߝh:zsg= -+bsCف,1%% 9 ]!!=5[q r˵eO6lؓF9ٿ1mFlp$ˮ/Zt+c/3}FmkU4Zy+dþ_>$j$Ț N!AN!Aڛ&J+ _B('#AA - 3# `+p pXx@ dP B 버a>d>s1Aŧ?`]l[j/!k1!O+o+&Dp W@PJ4J)nۿZɑJVmPOD'G. !Gn!0kSrmS'F6ik9*B`4O#BhYҵK(+#]/Pt._#Kä9Z)}^U2xdb6/B d抜34';3KʖSOmڔ]oMHцߤNMm#cn=;*1X -ޑ^sA>zz{{g4qw}ew#\ێW ~oB~6il;hT/n&"}6|F"G &04X -6?ACQ~MeZ,*9R@XKT -GQ|Fz) jz_!/Rd H_CKԓlR$1iIrNKaZJ V|?}Әt1$y19@'JPb-_G(q?|=@>y"HODIYDɞ[D@[D vD|F+\S礦D1$) Er2rdlޔT5amWpCC<^}7#L5\2U ,e;vQ:G9w]NL>]-5W|uPFp{|YB{ĝ{]c?<>#ybe`3P@`;8 .` ` >C8A$A0D -1n/*fh"XE`a')L*>ޯ9NÂOHSg~_8}zC')~.^m#;IbI[%M1:},];ֺ}>OP\y8zyϛOܙ}_i7'kAշg'ys Ιg{Wּ= \}w"~?'_==s-[ZKcY6t5˰,`[6-n6yB!X/Dv7bs|R3duV-UKwMˋb]wEAyVZwM#Cw;ѸcZې~ՎOf9!\ǷOéT`x@ ( y(5 h6BZ o' 0m$K am!l}pn$\w#@f\FBl!-DxLdLtLlU?V4+~EĹЗܯt=+n;+=}_ݹ_3˿ȿʿɿСISRUc<1>Fuë1jVDɾc~uQ:qgOY?xL}gp+~ϸj~ֻs?ʹv~ԿH7׹q5MY j~e5''.߽ng=UyՉrB4’޻8wHV{M"=/>&?;BtFI/?ĎXNc4^r gC9CZ[u[Ne>W^}`x@ ( y(4Y{+] ArBO 㽦"\DXօdNu.!\{) |-"L OyjN^~:o&/o_OǨ4P@ `V>`x@ ( y(%Jm$B#a a\N$SRAX "[Q2%s9ZNWjWL0k -QB(%Crr98rKé]8ԭdTCbKgtmT~bod O{귖OHLul_B϶]83*#WeBU'(TNyHyS^|PQ )PR\!EoCU.$=TRSϓε31D{Rj s"䵕ŷwЗp*@ZN< J($&la, ʁZo)<&We3#1 nT}F1hT U[)Y+E۞P-W;*P=QCeʶQȅo*J[ߒBT Th?r4rMT#rUpj AZ4F0,`Np<XD B EwەF: j/AwvDS=aHXZ 6¶?Y j$[A0 |;!b!u ZaWv< -UQUTI* GQ?7,*⬲N*'xwP^\(veJd*ZZTԬڃro0i@"&?Ţ0)2 -:K8O#Mcx&geŽ\VƏ֛gs|,2$EkG:S8byxBJT~Ew_R@ -4ƆBԷi@(ޥm+Sb3ugƎL7lɳmK;+(./Y{Vtsq hAРfl`8n`A0D -1H -ڨNtT?M[[٦ "13ryAPhP#s -& -B(r(~'SPyEޤ!;(sY+ŧy1M. -Hq>jjUmMjAGƩ7_jv]x]Gc(x}bpƳi>uWmkv{Q&΀`n=?Iά+Ŗ=+h -Op btziFcfdftϕǥ4P@ `V>`x@ ( y(xnJ5ƨI0OpVƠ'bQO7 c`y^KҰqFLy**"U?\*VG1R.qqpP>;A^{'^^{|0R.>iB(y(\1=*[tA(ΓF bS"0yMPr%F{+} iHyG#OlNrxWH$  IJCB#?Jbm86ـPl|Җ,t+KU>JEߍן. 5z2Uq_hi9M[m?uڏPfrptJݢg3L޺Yls)Be qҙ'3g;3#ώ{4q hAРfl`8n`A0D -1HPD>DEaq)b,5:0RD1RLwK2"{='QBğbY -#]Q,xhF[{,.[WT()yqJ(G1Qt*M=DnD(IL^!&?E(ڣ!=Ť]ukx0'1P" -!%q>P"zGwm붬M[zzۋArUW_lӶLwmSkN\f(7fYuwdˊg֎N[MM2>l[7γk,LL0g7o܊5|K+^%=DwU+v'_cT Ђ(A0 `+p 0< a@b60O<7mXBWK2h[c a_^w4Y\-j_pDx>Z;|9*a{[۴9WhU Ђ(A0 `+p 0< a@b0p=KVЮ#tM@{ } ah' 07 JYGʓUB8[F½zi&&#&BY#gšp*吜BV.'y -YEVx#]as|𩔻ND<9^{JWud,UEW~1^_W@6~2ؽ2uOC[NӶN{i'8ٽ?*|[r>x6níb -wU8]8C\1]:TFxH]S[#8.5-`#  ' ,p"HD!Ca>ԃ2:.=c9gf,>+g!q1sn'D5ٖêPqj*Z -(P:aR$]/n0dW.ZRZ -xke^R0uG^ɵ=(EO)J1c ORe1DƋ("%MSG0[sdY/!?ųBxvkU_M{'UDjvSɱ')d%o( MQ](NB)PQ^b{׺{nѳ??÷ Y#s-{BDz➗ӥᾮȉeR@ -hЃ`3X -67x  A"X`ԱsQ<-gQ -[فRKrs3r"J̐RRDJGJP(Ӕbe8Lc~2r6ѲktU}䴟\:| J ho_3kfU}:ߝwr*]*[iɌ>3X*K $h@ :=&0`;8 .p|  !QAP閰n5C5E[֙( tW{g/lOf)2QX}gY -KWJaRzeD)oVGaߗKqũR\&*,uyV)UP PQE՘)T}kP;9t]ix,L*QG)"ueB\xA&`N0f )ZUCuKGNBSڦ(zz&Po6Gm%L'?dK}Ӻuӛ6GA\zrMoݧf'[mWZh9_j&o7-FDh#gO@ -hЃ`3X -67x  A"$yƝEN<"<-wYljן< }w2fr RW*dU* lQKq(qg?c.]y1sMR7aT1*d(_5],EvLC6^^{g€qq * -"dt ȗs} )orgg{Ry<J -CQRWjn"Wj֡^~&E~.FԮt~wÁ⋍GNoZJ[;%Koh=hۅ߰8g:5[̈́} -Sd׽/M8*t*Ty*\:UdժhUi] -T $h@ :=&0`;8 .p|  !QAPuGrж*m@wmH 4pJ;I0 :pJOB/Q8L^ -YIR5f;TtX\ cJ{N| -Y)w8(Yo)+W"+W s!=Lde]wt|\|v]gE2%ȗqYiy>o(;yyr\ +geb5k;ӖF^>$b9MM1.n6 }lc7l9id6Mmyww^;i1L#idy5|[ۯCozL#`}4 -Q $h@ :=&0`;8 .p|  !QU'yDawQdAƅ¼rG](Iw^f{w'+. {VL|Z!7ب٣ r|$/(n_S9f| c¶GXr9|,E> -3{Hw ^JJoM1 _vO%was#uJTT -Blr p^.yC~A`DvNpH>N`1Ӳcߦ.h\PݒI~TҬg-~YFr c(b~S2L%K%KGhzYUa*)/cE͙_S ɷ[OYW(%U*ϫu_$_^ O>Vb**TҶ9UTU Ђ(A0 `+p 0< a@b<+9"]#A"0$ .¼t=mwr*i#mpwSI7$cJvt*!|=J!+SN!+mmĈא岪ovaCin&5#i;(% a.TTUP+UYb(1;L^鏐+*A_y?q#_L`BZ PN <7*]ժtDv}y8&rˉ\)mӔ USՔ3_c{2a3m=5mqKMwvݸCt|>yzwnStti岌-S8.5-`#  ' ,p"HD!Or:bi^0FI0sU8q2.[p>.gq("/ۇby -ɡ_9BQ4P4GLMvyqyW>rȭ -]Ȼ"nZz' -]na[|K0]Ⳬ|1>R.+RP"{-/|(ŧ(Ti4>xxY|›%pq6pqZkUNM?*ǩWm3')UsڎR䯤G)ÁcE=eumhꧭgmǝc&w%w^ѳ f],oW>U~|X5]|6#\/3թR@ -hЃ`3X -67x  A"$ރRH(EX6FĘ^D)XDF@)X#$n\0RlW*J#T)2K9Tr) (#\ 7S?Ng_ɥXyHJ~/rڮPG1S|0cLqe JlGKX1'P1)J1v Jq8+BFbA_c.40 ys`L\Z7;d8![kUTB8uMds5uP#PQ-(&TZó8 -&Z{N.l߶bpYϹZNHʳnK o۹&(U6C?.|?#jHR@ -hЃ`3X -67x  A"$ET8A+Dj= dZZ8b3X 0UJӆ=AS(ET.EB)ޑK:U -Ly)YfQ`[7_ EXŦ;8,Z!7g#BWer(ZFq~6s- !vbAqks4]>5gvbznyfe}_^|Rst(CZ֬gG_MBR Ђ(A0 `+p 0< a@bC(wK8a -ĬQ[9#SLeY"PCCqB(AP\)?2y*B;CMBhP|{Og~0QCqDa&y)O -űPĕ.2attGҩg=3e&e{rB<.&?ȫ^ed5R8%/O-)#OH䃍^ ,srUV]jɁKo}oUٴtL*ltZpͰ L=o2o`FuFlM7l7ZԬDάD0o!oܞ#|SWbٗ73GZȆӯ] 9pj AZ4F0,`Np<XD B `B{%OܪpҗE\[ݸ9u;crػ"qpk9qR8 ck1C!Z仑oMt]CɷYo(+++sDZ"+}.`_u˞ߥFǩ{0bxfJۼXLjJޠL[&aÈq#Fgd&8NsWuhsnh,xw&\wQ{PF pǬfG^ؑ1p\j AZ4F0,`Np<XD B#4ka-xՋQZ$ba-2k!nD&_/4˥)oK\(b(( 1Z\ɥWXt-yiZ<1t8Vu[ ޔϯvmu+2ɮ_Ri=ѝl*Wá/6nyo>0ﬓ,5F\B؋+dž3{7[O*OWUlϫ/$ƽsuVĵOV{UESEW>(_Ł hAРfl`8n`A0D -1PoO_Fhj'VBgf´0",[ך:N*X7⬉y-WHO]SqJ<6pUyU$L^OPwA_vdŅD+ GZ -Ylp`bE_Xt؁qWMd\(')]c]>E?XXAvwmh)>A[IsWQo\-%wW9ܝ^:S,~~OU>|$]{*=lfdhCq\j AZ4F0,`Np<XD B 2M=m`CŨ9tTztL`i6ad23L!C^v/AcPŻ }cS]{-zLp8Z r)*FyC)GR9;z~tk-m(Eںj@;c(BȚR,n%'|ln $^Ɵr g7;+ųo[__pRũS8D{/W -^N -52^ү* hUi.yKDK1Vi?K9^49Wl -KEρ=o3Oe usMwl | ]:IzxSq hAРfl`8n`A0D -1J 'J2: =zmt826Jau]8|,dف[}Je\E -/ߟrB)~+2yxXߌb/SV(yOnr)JeB)>J*XQWQ -W1V*=Bj؝|Jx+OrKfrT":sg u}!%k(/8K8O`F^rs!?Ҽ\0iq≡WwUתT'RSoxD,=uI ( ̓c dDKe?mmm{{ vKr=E2⭳ؒ/pNN+߰ -3m[ӥ22#O͎>:R Ђ(A0 `+p 0< a@bxmLF$Y gm][LI3/no_ȥPɥPĐKR|8 ~VyBqɅ1J_VEB).R)zP\vHO:GaN 0RTgjx_F(J)~+3T"=^&)c"d8֏Pl/% 1F~hvas.'剑BhV Bvb ,H'B3N]Dޘ8ݰҝ\**x׬ +4zԴi.h?L[?m+Lp?kr6W\ۗIkmooU:Sn9ܑEVfJ+ZGΊt=\)p\j AZ4F0,`Np<XD B ~8|ׁ]ѹ).odLxy2nbs3>͉<+8dDTb(Q+ Kl -OߑF5~[|ѿ\r)6+mS\<ҫLG>M2QF1R,a^ؔdKWtB0aZ[1RoC)WE)"C)f^!(E0K8Ow -!Nx"+&-1jU=oOZT)JƩk*Mْ\MڼSumNi{ό7tqdDKAs{i?F9 -֘'Jq/)= E龕L vUl)|aU8qeL)omzxY2Sq hAРfl`8n`A0D -1Ha- S C)b0JF)xu -{P"bydЗp.? G3/Z!>yl6+Hą4vh)η #ԕ&3Wbt%52l)[S{沖>ZG:8MK'8;$&w } 9l3)|VCӥ۫3#G3u_JR Ђ(A0 `+p 0< a@b<:R.B 4`aсg:cH(=I)KqL}H"~H銍⚭~/ݗh6rR˧>>R)hҋ6RмNMkV/8}W2O.ں.ZœgKHT"}QoSlGIA_y*Eȏf |(+H>}'+`., \v1S02#U#{ǩOȾw(]aڻ,K Mg,L˜''Z -hwh[y1VPƥ&Cț׸u<_۾q&H:[lP_WhNo.|~3ÇˈΊ~-5HЀt@ z0L` Xvp\8A$C)sO0̇VfW2|z8waKaϑ)#uʫ)KqL~N׵ -HMI)^wy;w31#Ŕ_cxZަ F[PHA*=s~G&<ySw*?xO)>]&CX!)򂃾i 'i6y -9L BNN" -d\I}O:>Nf"˖쭥Ǫ)]w:KH24,nLK>u'ZΞmuG3_39:$wDϱ2|yLq,n),>!..w\1;z`ej?ǥ4P@ `V>`x@ (ڿ:M\āG1͢N]1D֒? ,B0RRBer(^CqB(X|3t,>n0oP|bR3!J,"7E)KyN#E` WF/<'F9=bS覣(ł]ulhYDV"˛`ɇx" d'|VH~{0RT1^n2,>Ƚs5)m3(EZ'j(ÆbqI=e:DK״״i/`f݀uɽwe7)X3ݿwww -vU8qt8#rKfdK!nuHЀt@ z0L` Xvp\8A$Cnu30RtNI;.cdXVenN/8Xs)%OJ1V.12U;JqMQ&QqCJq)bgG -lP*wȻɡXa>’?{`kΕz~.t|PlbQ}:LJFkGkЗp<~d㍧!/+9|2>VUeP};N]yD?WEJ,P;7St~k mT_>ܽ呉֚G&l_[>yI_rk<dd0Ǟd{w58k!x*CّofGW.q hAРfl`8n`A0D -1b|gwfu ,Ӭ+c Ƹ0$qڐǷ?BX."וzo-(ŲmmLc#*</oM qKZRJqKB)<#W=ҙb#ťAM^vU7P\7 x~v0v,>>TT) -o0jZ (njœ}ҷDK1ytw[MWm$wqiy+w5`NW6[ 6}=SN/ό33+U -HЀt@ z0L` Xvp\8A$CΑ. -79νDD)NźOş!4"RR|+_v5A&ˮhR=R\tg>dUi1G\k񭼟0R4ɻ.>ⲫKBu(M>֢%-yϩP/H#Q&&?Hq&yǃcQR{٠/|"%B6˄B4/&e99Yi•M)3kǿǩ 1j-WrKx/XAN5UQMNs-'?Hq5:?LκUW[>)~2D:N[b6Mdi] bRfE:dFO֦BR Ђ(A0 `+p 0< a@b=B1U+|X|D -(DY6fbK8)EzÿṞ3|Br9Ph/G.p*-'P*1B v)>+Cqs0zG^yڣ -%yQ{7ߖH #W>!]|!&g} ̑ϐx&, qO>aKsBY`(1̳EkԻ?0;j -*(mˋXCUm:Jepa%ʔW6ܳoPDkGD[EEsk^|/÷;x2qWB)L(dXt?#ّ㳣LHЀt@ z0L` Xvp\8A$C\gSuQ*BOG Soia>ps.|L.r}AED!YP<*Qy s(y{J]ÝpcuC79(_ y;*H/Glv٤! -%y(&K7ucWC ^>DY, %BiAEK%UԶBg&3- SgK/5)nLgL0?:Ѳj;m{ްr4_\ɺ:qo yڗ5d[ن3#DB3ōo(~~fȡgEL]JR Ђ(A0 `+p 0oqE}7qnuYkluv2ún>V YK=zVS 1BDc1 1LbBc4)E=;f?^yxgxx| -D!qH*}5C:V0Brh=\J{>Z Q)!ˊq:3_=ub-ZP7~y= g*)^`ݡ[RܢrA -D8)=Ͱ7L5xĔ.):}H_()>Z&u -}++Tp9b b!M(BA07DI,V" rI>0_Yt5XZʎOl/RƲ'¦U}e:Sf~ p}o(!jŹiUFveg@đ,/j`30`;8 .pCx >CB -D!qH@WbROh#1b Lkab%Nb!n&}ݫbMKH:JmD&bOꖀD|w:#.lDv-V>b:Xa -ɷWh}`7駊['U9&kT~Itf -CReoR^uڨι ǘf)ҳ/1 51#\Oɜ\JNQ)0~_97(r'OwW23Fag,/.j`53%LsVZb̬3K.4{T h887z`..oZ 1.~撶Q\c#ュTh0  ' Y/< 2(! "7?b`F%CHЊ.gZ%s.gz){:Aѩh\=!mENP̞tj7&NUqfwOLYZRT')D-)v$5eNeNE6- о<)Dlz{H:IsJ]/'e)ꏻ[1zUǐ?86#\S,&(/9`nQ|uHIbQJNx~3h?}a>;I;cjp'l`K2ZZ{%bbh4WG4w+Teο۽kߺYsCUzu,z{ҕߖ:(o|^tݎ4^<x/`,`Np^x@ dP -1C\xj#Y,4 -↯q{e1$$H"PI3{Po:U[xaI -vnNR9œ,rd'?!!msNR|u*KX3zj61˾LfI=>r$EB򽙨)~էH7wFYAR\Zϙ.Ԫ^$\)ᄠ_^.Esᢜ|II¹V's.ws8 ]j87;=gSfRjdPS4>XN52֞2$ }tl4e\m˦[qov%c_~Ztt >;<IMtF9h7KI~ @L`Xl`8nx~@8A$A( 3_ n3H!)YSvSk9nD$…PSȢ$e!-)L:?4am:VS\LɗhIQi⿴XSSj:Iâ}h -Ib{Eyg?WԥkWgH"Y}^Y^v{qGP7!9\8\ᛟ\.RV:E/36hObDIsV}}:œ-ti]ZRISHF2HL5G Xܣw}+7-zU;ƢLgŶ)lNa;uhCtXz^ ){HAIVO9jsh8}&| -.J`6BqĢ?̓ARbƲƃtRثvڅƣәsFf ?XJZkF[Uco;u,=˺H<^{OU?5sC{@Xx\<4V(o5G)>7zH^Z54f` Xvp\,|AB UO|]Hj*ԴR|J#mZJ^J4WSіuTl Tyh0  ' Y/< 2(!ቡS'L}0#E,]ElG9'9Γ o; ( t0F?D"vۉ2@2jӉwAKvkH6Ӊ) jxyEK2 ݈g=qr]~B'Vj.-V.iUBeMPd:VقXy{6>"OѪ\~Y`On8N(}iG@ß繤]r<!V*JH>R&G*|/sC￟.@\jhxNI1'g0=O1lɣx%c[}^p4𧬫8>7kʶvw`CE4j::+}z?4SbEK}r}ecsuyѶa`30`;8 .pCx >CB -D!qHϘGr=Yx:BF"ֈO{Q9 ;g%EΠ}Z2ZR4j:ޙBR+~X))hLAhIZ2k҆?:I)L|a7")Z[CzWzj$ŢB9v Y}~=Q:ߌ  9*9H">qG`$Ņمs[: ]o6ZA1XGnFOMݜә+ÖnZ鶡a{K -n\B^94[^ۉdp; } -uRCqޫݥkDl~b43\RPѱXz*4|# `+p ܐp"H QA:`FS`jFp'>!a"'^#1u#D& d $g*-VhAb -b1N+]rgXs˭ViRe\g)',uFI]?V6"U^8!({LV*th(}}mN(Usrώs`) -9d0?_Jss -\ʳsjx5C[G%0X*Օ?ݦ!yo\s%n;nk}k[W}7;j9;o:PugţEy/Iv5}g—W" qپK+{;Q*WL==]$wx'W=֓)/i`30`;8 .pCx >CB -D!qH@j 1ԩRJL\E:K 6Rb_EYB\]&J%j_MU$p%j"A( /zP'Pš(_seN̙@4rk2_tʍZC3]'P~ʻZC 2e CMߊV)b+ E9)O{gͬBґ@dG@#yNhjXXD`+.H$[ =3FjK\j8N'<ɘZ0F|#î`,0֓m62us0:ruxݛ$9 Kپf9гsNww?V,y%վcyMJDG̋ ׶ @L`Xl`8nx~@8A$A(  lXj=hqdvSqN 4Vׇn,\9ܐ`xE y-(ub"yޡb_C}KD':ǏNCגd5,Ͱ=giS9 g#)7˴)B"J'F-qz->…?qңP犊b0AHHaRP樣eGO6tC3cش1."elL5my3;@iY7O5o:m<^v_/Gf=yw9e[NKRk)YZ%{r:JWQgPJgZ*9*ToT[y5 F0`V! <!!D@8$ u)1tG?1 s'aF ;H,R M/&$1Pˏ~m&1b$R @"RGF2nmՉkҩ?…jhs++/G₠ ER!ʑ.?R__TtA^hh_xecs-ĸۍTqXf#W nˁN궗.ٱ糙]^WiłOݞskF74?(E?gD?"mD?}W^Q>N vX]$޶/~h`30`;8 .pCx >CB -D!qH*wnA,2j~)`n϶~N/\wG/_Lr"))ߩܗqbxL]YoRv-O(MߺNIAfh1>銱RcmC~=ގZD}3jٷSMMQzvHe>J <Х).( fK\aP(, J>HRAX$[ZkN%]ͥ+ۺܦ-nsRc|mjw[=?vOsLgCu>cnO;GP߃kCHߗ"56mEj,]nFj~;xG`,`Np^x@ dP -1CWRc1%RAE2b_Lw;QG?!;WJHP9A|,niSVd] /kuǷuN@j< b,S|љ`L<-B>׉-BuڙrBg$u"_DȷPx,@!Ց?է -}ՆQZ7!}qڱuG@,:BH, D3Ba0Η8At)߸~[hYc8qxݽ~+e=ę6r溵ә=a8gieumMa{J3#g9{OG\wr3=4yzۉf; \CvYCq]Q|͔TJ5RPцSQe ^4f` Xvp\,|AB ␀Թ!_mm&F¶ 6~Ό0q gHZ DB6"u(c:]+ʍZ\+S% -_e…z=ORTd0RIAA̎#,,dnC;x[ӋSCSyǐ*GքM+3#avegn#l~Zq6Vˮdא*pOy>TY,RzǞǐ*[J*O!UNt!U*^G-?x?*ڿRz=oY -^4f` Xvp\,|AB UC*-ԕzݒU:R~4{JT*C|(ZH Nbf"uXZET١@NLKZ_uR sSjٓ~3QW|kj_~^PPu/"vn# cJƴ6b蓍l5ci*m_p,^:7cO&d{w]wz--[D`{ܞ|cĢRB5JjO{4?&]"V#>o -Ǩ `P@,`N`n`^A8A$AD!qHJ/ Ab¸00;y35=`ynR"p77/!2BC[4HӃ?=]|˯G?g>[#=3R5gGӗ>;YggS]CSE_r߹ ?]ҫ -q.AnEtA18}g'8.ԟY]W?\rge9Fuf]rgMuVͳ~uHgRVZԟuYU?g|.:r6Og<"NDZ2yJ!} ς1~Kp\ eJCB>"?OpR"3LH+u^\&&D)D " qRPFSes3cXXbl Kwp]:װɮxF;6!>;.-Xo|PGDXODIĖ1` XvppX|x@ dP QARrיL5aN'-s3QilᙓlF~P@ӥ9rR)%~J5aP!hģ^Uj<Ԉ Gy1GNٚ^e]Ύ6){uU܋z*޵ʾف\;SG9OoeG} |&ӫ5X{xݺt)__i#7~alT1Pێ}}cQ,[2_Z[Ǹ, -uǦy6ns=^k6#qi+ ds|_XOV!8F=`#`+p.p  -D -1CPfg!0͡:aru.,G܅&=IﭾT=z**OUI:tj:t84aQqi0^Re -jGgIݳhWj;h*nF; ю|gO7X7J FH%yK+Q;2_d%23?3??$838sDK-hGَk~Omwȭ`9 ]Ș0,`Yps7op5ɸ~bK^i.Ҿmu kyḦ́d~#-ߌ H}@uA` XvppX|x@ dP QA dswn zV4kxLwLwʔ-1OJqt<5YStY#|\cF>~=|c -z=NOYj=>ըǻhcZNF=cv=fakQϞ_e+>U+GȑFS -/_K8GCZy?B2H~B -bVzT,_ۻuCǞM'N`(ldV24+nƘKW3 -Z7Lpx-<42U~q{=.yNOz?ǷD#XC#xW6!Ѿ7͈eID`z"ZDiu1` XvppX|x@ dP QA|  }yqa&B$ Ka-$lpT*YH."R!BFƑ; z| |PBKx/$LByYYg^^kST^, şK:c'O_j:zb(523ZwmdlM^o?jceVz׮u{żۼwsƃwnRfٻXl\>Bش|ro_l" -,ejFD±9ֹdlY9UxqK_Yzw{| `$9_ݩ=UI}H\Z|b3!w CKp9F0RIpz F04V\<A@b3ɐۍ LVzyv4cc.f*Lc7750^:G.?vcFgB&+nܪN6n?M=wG4QvQvj7VݰkLkp"d>vw|7clMЍ0yx'ʩOэai&'᧶!rCYHS*$xtCDVNV07% -2<6<~R(&coȓэX_ȘJǗ1Se}ukG8:2ߘZqW޽Z}|=׉@};tb+Ѳ=y`%7}M+82ƖFctH0L@ fl`8xD@"!u(%4) EHG-3[필cBf[Jx޻SSK2'u,i>!RJ~H-IX$7%D$%H-%yl%y-+%?4igJnTSRuQdڃG2v#6"Ñ=R /`v:%qHI["KyGrSGGH|3RryHɟV9C 1/??_J%!GJ&D.+%[0YwM)Y-Zy1(,bc,Svk#ͬs&Ydױ{O[tOeޓ@J,ZKn"oo7J#MD7hGXUzJǨ `P@,`N`n`^A8A$AD!qHJ1/#\ս4˘yNN鶻Dtiis|fsYfʋ$I^# q5 Ih:gN+s_WՀ|F@] J,zF@HT"7RR>kH9r%J|%0%Y$+, -Ya(?_ \8 |VjCHBĿW@`Y_hӑ?VldCS +(U@կ}+(zR3`|m/aoh8R4̹0DWkP6dSoŻ[W@kG&& IJG/J_'oV0>RhіI\?V 0 (  '07/  Ƞ@8%!Q0)BGͼE6ٮ8D' z|;MGq"bT% RGKTn]SKAjeQcj?o8YZg5JZhVKt)(Ue]1G^~KaƤKqD_`r`8.>E)nW]Z%*XK87W-I1 -A 9?7BAx))䄓gV궜taui-6)((@u)*RZWOٺNէhfm=w{i<ޥx}l_8N+Q %j3$enzaؾ5lb+^89qfVɺ~klgwW[&;PXͭ;_z#źgRރ㔵[# ''D˛&Ǝ{ǥ `P@,`N`n`^A8A$AD!qHJk=<2v(8#٘g7. q -ĸt|2'0L/N-GvTKWh5~Ky vvfzsP,/7k#EӅP5F$k`QX|N&p m_Rz,><[1R\=DEPlnH_9 -3;B(B/dfsp?L @(OmI_|͊ݯ;j.o\"Kn# U𣌩PnY͘=XN jߘl?{ue:'c׆ؚ;=k=׉)Nnzjw#}U XkR_:9e=]:Z9W=c 0 (  '07/  Ƞ@8vƟSMRIzv,L; P힙4n6+*NnoW}.DXLU F4UOYE_u-sOhl.Ng4PF4~qO#tqzt>uȕd.^ ?7;uu՝.n1 ?A4؊u -RtKRTK1JZS3BuSOY(58e~Rtaq< }mmNQ% kA)i9B^Y>5B^y{| E  b~"a%C~)凃0{E_ڻ|2`K6rIcE׃]"ü|=z!VR4`v.E)Z(lׂj[);ܜ0B#eo8}ʍ')kfGvL~.K$&3X -6 p"H B Δ"޽SYxQ13{xFy8Bb\#_۫i84$Rԙusy>ocJRd7=QFK-gvp(@)Y}Ř)XJLq4C$S*6]꯻U=#K8G3EdN֒dR(WsyyɄe拽W0S<|KKqt}gܲ3i8Օa>ô3 +üxeW' -)[͋ahOifJUr/{nx(*þ@qҺ|'ŕ#-Nѱ5㔍ّ &D7M-{$] -H0L@ fl`8xD@"J0J&i1X}DSf'"Շ:S"98]h]/UKmD+R\_O|-RgZPWWDNcPéwM?R s JR΍J{(Q)BSp !37sY胐 -sp?a!? ^XR~gxRn߷F% fkf Ϡ[?0~airu}mˬfcs~g]k)wÓ6gWyϕ}fJO' GFGsJ͏k)EUّmW.K$&3X -6 p"H B :ދRpF$P3,U vsFh,瑽V.Y$Q'8_-~}RLQK1LkR[7:+$JqrǢ|+W#REH(yȗb߃z| hCR0/#y9y~! |~N"(OnZ;noZݚ  cٗmtǗ _gVTg9D;[L3royWy;k?2.upWx~[(# -tԒqJ}ّ]kNm=~.(K$&3X -6 p"H B p湠J!y1SJ!E_y#>> s.E@(OJVKTL*iF)RObF)N 7p?{-qɞHPcPKRQ)1S@).>*@^ߠWwa~<HR\Z)w -vv sV눑;Zz| hG^n(/+/uXN3%sD) L!/Hj<c=fMۆ|ugw /}F(i9aDRHgS1 p -? -O^"GcH?%HS:I-}yF)#hw/q橚V؏RHOY~ -e0h?b){(@zϻщR~V$9w(ŽErS]1fIb1>.DBY\(K]STg 1O⹼P~﫤][v5K5K.WԆn% 5+D!MU{^`3湴ƶqd-#ON`Z!Xb=}gs5/ۺtHpG^}Mu 5B9^#i=&; H0L@ fl`8xD@"!u(s,3jҮ1iLUqZ/Cchi\;5I!F?F?^tAV$n wJN}5I͓R lH=t1K8Ws/rDL Cs~>̉Y\8&i^]U_ ﭷ '| e:QfV?IG)s룔eGzqʶ}~av,ʺmW.d_we˯Zk8c5)KfGO6|816zq` XvppX|x@ dP QAΜ ^hbƤY5I0^r48F -^!KWNy_kWj)~AvT¡-A+ݷh(74JSKT"NcҰ [عIcMrJkR0iSL>!*܂Rfl%_;M&y9ԤKaO$Éc^lZ<|z CGJ}a\migG0˰l!k_0G'>Yumxrw>ac=l8qdW_;5+xa@)Jܣ|gGE~ģbKmV$Vc5%IPt_`4ozbd[$Ҳ99)B1uZ<-BᚈPS//bBɗS*A,Irdn2,%{{fcC+u3K6rˣS 'S'S+)Aʼc>exb)6;k?vX׎N݅bGCSZxd߲ فڷO_/,7R)H/;RhuqS)\F8Bx-bK7PG(KWI(j(J5BqX Ńz3~x_0PijŢJzbZ -Bc@ݎ[j)84]Zy!aQRa}IY7_t[HrF=%'eO=)y[֢ -z| 7Yeɜdgra -ssELJyɼP2 GOU~uӮ?֯Y<,o_3T_LGV\DmiȤs]d=r"^XDv\"3[צ+%&ϯ$<ϾAxk~x<.`:u:aBl|S:wKtJiȾgu]b/Ԥ_Ԋ 0 (  '07/  Ƞ@8ץNal% @ks3ai!';L=5`O5NZZDa!uPV}ȘAcu2K=kde"H|+Akг*wUyWYUyL=qqZ=DR*kԪbGynBS\jؤ*ӛ<EHoFUaP/\m;S7fE.ں%sy|Kޛ<*EmO4_Fyx`1e<295_FѥSS#?n}6 slG}ki񒍭o zʟSeҭفw^^Xk}S*3V*VNʎY<1Z؉ц G6m?0ahц G6m?0ahц G6m?0ahц G6m?0ahц{vo=& -`-Qd /W y^HɈbˬy8^L`JQF)ԧ -yV:P_K8Wu9I1sRcϔr29^%- 1t)v/YR4:a<>2ULywa>y/~F.y6iXfRU`(H eeYU`6VRX{aşΒOhP ZZbc;JUG<~;.Pt|b$⚗꒱qʱG#ߜL 0 (  '07/  Ƞ@x{ qc$w?3b`>'(ExXooF%rR\zQ-ţ3*3F)v.>j@N~~J1"* -M&RTKޥ_g?_Ѿq$u(M JQP$}iA)W`{)JZ%feB/\=wI!QK$ yp0/OybfN}^bPnohMUS (ei}3T=AY5X@vU9Ze]4Su}G75ҭAϩF{hҞXr8[녃# )?|Չcc~Lǥ `P@,`N`n`^A8A$AD!q֭Q4IOf^}^ԽY32VyID)>F)U/4J1?I檥Щx}[ Q8}ތ< 2(( gv^DC1L͒E -6!"E(xVF(|>9 "G(~Aj(iL%REcxC(Yxu;x^Ho.ӟ(SC1I$5'uޒ@(FƋb+z^g{!q(&0~T!F~\"w0& -_9ZzH_kVHYD?81'SBb$omU/||4D^a#_M~0OLU`q"|9>>fhM;즙Uf>V= _]|>.q\ e#ŦR1rHC݄Ou~ 0 (  '07/  Ƞ@xźF_-x S>)t߳%+ƤѲ$ťj(\vPC ^(./0Q*E8=gi=(u1 Rq:s¹>JqtH~eDZrSITk Fy_9*r\'  -MfgH1z_"]R{%ϖ)~!=y֑ƚQSa%ctˇ#͋vF-]b[my?=qh/Yh-:Y}}Bx}Cx+U>zT(/\Mw:6-,ՉU'i:yEտuɯE:JNꢫ8,ܖ'' ]+cbASu: U?L*|vq` XvppX|x@ dP QA\%)JyTJZ{!9efJ%u8A%Ȩ>opF(&8 j®yh:UDP,@(P Gv>W)ުwM|dQRQ]j[ޠ_=X>>;W@).{$ w Q,U F)>K~# -7툐Wo~'=sHyy`焥+PD2ߟ99Kᙕ/|jC]?:CnÐ ِ80t:br|i)zĽUx\Y,+.bZK.v+?KkXVXkkE@`+V|Sгs9Qg<}>_\1Pvz+25aҞGћOO0 L8=ZGZW3evu`[Quژu'x|m)lÇ3f ?GS/9o>stfdYb݉ -ƥ `P@,`Np<|B8A$! B ׳H -3( kem$,{r^LÜoh$up6i׏!)wkg<-reN\ԩ(TkZc)h%5:'ԅϳw*9D?T xbgC鎒{PS|- &)< JYCI?37dg&Aetyו) -c[U%[i% U.c2/4UnqKV,ˋhka˶6r,-gצns/ސ̜]wթuUOMJ+][ n=!~KK˕VB:% Dx+Pm1` Xvp\0x@ dC8 xBElSS̝MC>%e  BR?A|d%`PNvJ;,> -nе9eYzmuixٱp "}zտRBʲ8)[njt$GQyjqw<56d[۲>k{CfZR]rgy׋=NsQjHOc'H0L@ fl`8n^ D@0D -1y08?8%QxFi,ZV˃1X>ypJ"R ?o7AqapA]蹥oS +4>͓΋/))> NYhꙣ.^QR=XcJ2k#hB%ޯ잤'xN9IQYXx\V~Ŕ!WxC$o|K1399x3\rJrnVf(#)3gXRaJR㔞WK׿o#55PƎ)ө*o=ER5VtW7$giג@DO{6/->uiiަY|9‰'cb}SZ6C.oN ؔ*=zٱ70.=`#`+p  a@be(3nL7Q7i晖5͖f1獮F&MMg4NR>օQY")ɝdzqCI1w -)S"E#(n\kܓWK -FPt!(Xa Jn 9i"w -Oy+bo"=#48#\F\r,ٟ"%AIIYYÂ(_^6ȓ5n53FXOSg ֖{^`! D!q8`P0ũ5Odfθke1ME.(, r40.=`#`+p  a@bsG<5׊DJ%s" ;yF ->`k幑o5R]lHWcFR~ IQOcHICC65)256uⴚWIq:hSo2ưyV@Ɣsg¥.]+' vJPDM s23^^S4+IڔI$Ū'M?z`~xop|{ ql yx=YBݱ Idf^޻v!]\|wҦx)UlY.ɚ_E|-q` Xvp\0x@ dC8KgQE*BGnBRϢ -R`:3W GG|+luQLK5B?eɋ.S4??NIq~QK")Nj+՚CCM!_w,X{bba8]Ω7SV~ê<"s׸|K"^SdPg'% )2PSdɡ,.9)7x =}_M쇴/~d0,_2=}iщT\tA˼#[7l?ͱWoʱ{ܴq;5ݸ9Vxw{l-D[n7 MBisŻ\[?߹nni b4&C0F=`#`+p  a@be(#[9IR0/A"f3CYb0KJ7,)۠k{qWOɪO> .c]@p . .KSv6{7W9gw=*ͽKPG2SÃށ;mغwuSO'M-%āC]̈́\V"|mDd.o$b[hJƨ `P@,`Np<|B8A$! B eo\B *- [aG*P-#eo ,$؅D` -!|Sdlu]cF kHRbMu"i^Y";0Yqh Њy^y>X(T^Q}~H^1 ^Y5L^} 1C>¥>NIfVHʔlLVD1#1rDeed%q A|>Smd3/Qe+)S"UZBэOQ+)ˁǟ!\R$9×5PoTS=- tVg46}͙ZwL -u_s]yǃń|KY';d78Otu`ݾ~`׾Z t)qu|:y!=sYN_T ^^4P.ָ{C0 `P@,`Np<|B8A$! B eoUZ:AP za", kaK¹pmVJN"uNv\+B5!&vBFFrVi 5V^6ھJbQtHr8*VSLW_kbnUIYL` ǜ%*T1 S-%| R: W=ԋTh_äg"dynpBE6`3d)ӟe~!"Sɒ?;;(Jk&5nеOMG5Ȳcs qԽ@o)sEei(0X(ۦ@$ǒӴ4:-xƜ8VXf/I }2k2fuIL,ᔊ g]SuEңg -V'6`1.=`#`+p  a@bsg(̢%2fIPG7sрI|XD;N2iԧZԠxDIIQ^7 ~{*'> uéTHicyhv3^eI_IY^b[#)N$]o.H,YUy`6+Eʙcˇ|Kնo)2DVf.C_3b+ RR u-Uj7^?l#?kXI^L j.{2//,+ma?c_<2@;]'3=m㰯Sf{6[U?/~й8&uJ+gȫN Zә=Q2;v2H0L@ fl`8n^ D@0D -1;}i i)@h@RH - I! Xfr2Feɧq*A΃?jITg*}mnÙL5)")|z5b/IP#)nQg*S՚bFRLá -s$}ʣ%ײH -OIqlt%jʯ|BR\%)N ()ċCb(SHJJM!l?:(CM1ppɟH$EWoȂ|ʸEtIXB]OQbʲIgE&9VhWm;9SkcV -y-ų?^{)E^Ljx/c'40.=`#`+p d/p"H C"\M!")c$R`+`*v!9k{e`>^$A ;qG5OjM1K5)ӟwjL>e6 =4Ӫ864(t5(>S]uw8i,~faI7By%f"JG#(8B]R~ق"i\4#\G/!e2\N2' /*A?"rYf$m_AWޱj۾M}mdEfmiej)3Pm)v;e.FYv/7XvQNr[=yIW?7ѳ"x\k2xeVYޏ}.TĦW:n~.6+9m;H0L@ fl`8n^ D@0D -12wH"&ϿC ^(:o|Ȍd6Ͽ&OwAq}jPL)q='hũ1՗K#9jPth}W4~usFcs۹CEPTGP<؄΅"Uy?w$rXBEqYcd9BJmQrڛ'PQظ!R=6* $\Rv_iɹ\n~l5{V&>Wwtejut`d'Ho~?hn,oOwv 쒝yȮo,&ܛvpә*۲U.AJu긾u|M:a{q{5:]xyg.r@.:]lSUO T$&3X -67x/p"H C"AJ-oT> c+a*!^KK-aNت {+N8ϯ}n»5lh"Jo .Bl&JBn$I2CsꒆA#UkT~|6N24U櫩ڧS-?.X j4ʏ'ư1_Xr9uG_AFPh ':ԠF=ё;Z㮶qzФfu {[4f5 cX'dϰPܸkYMK>tw\XV~ŬF~w_Fk1Sa}xxS>1;d!YbY!$B2_}is*Z*vZZ֊Z+bZbEDDDĊXJizl;y?O۽H7˾%t%˗_~/m?#/_ o̺[N8- n={a?^nK)vm):nox!%*\s<%u -Jç\S9dɟ͉~!KR SB9!OgGSf͹rcc'|޸܈.;~S6j܇ir_M3hsomٛkm^s hc;ӝt:`s4%{**oZٗ1-p`C7o++8:?KfGo-xmN8#q40` XvpXH^A8A$A0D -1JX?c#=U# 1=W{H`:ᣂ,TYVDDƌB~3ᒁ_Qy~edg~Đ3_2̭g3~,ge^8+{vWWNW$&?e[Սtc8 n#Ⱥ1nLX7&pĘ;P?4rg=ϏK)jQVYg}g͊IG˫_N.`t@xH],Qr-L]"TxKQ탾BF EveffsҳBz0%geŬt3`PlD(&ܟE-6jS ;+iiSy$TV敏ӖR綷w}8bؼCiW&dM0-4YW -ޝ"msJ_#WJS^4;hD(p\zF0 Vn|x@ dP B ⇢E"`tt̂E -6=p9VF(nyy+|(dAPQG"<&%K+EQ"J E 8bF( PjP< 2(D!C"c$ -6ODMKil{Wq1ED+џxDVh5?WCSCQt޿ɐPd -ES>\)>l1eK1[-硥ؤuHR4F#Db;XK`J"rrDY^1R0Ec#sS -!ȍX >+.ßΧe&'PvJ(ϔ`R]Sk8.jl)őS.bhz5x? }5d-yck+Y9^mcg۹a-{d^R6}j孞c_Z{hC ~p@/j9}n"JѶ>"*xD׷LbkϋǨ - `,`N`! \xD@( ~(wo6e EŖqsٸ}"">CYEY^,I6tj6ޚ?40idDF$/1(.Řf*{l\qa"DcHdN5idc j6lT#~6&dAd#)&dkD]eLT.?"5y*Md%lp!gsBF0LOǀgs-SF B*F Q;⩆D6Z_?АJunC6N=?ȚН;Yy_X[Y[ynd#܃luYճ뻽uw^B6mB6z~dcD?&Yl~8N"lt (0L@fl`8$p<AC3ݸt3}7SDwXn-[7#w"wgN_ ~XCZ !.NЫ[$⑯ȿ:s c̶)Xeijxia}!?qM{ -x*B)Qj-s9>UJx%$5[sR$ ȐxauRĆ=ɩ7buҔc8R@?HNkwŴ/2Xi[n{񎝻g˻ ҝkڵn4[ywЌ@3U3WO[ZRɫ3ڥiY#;O>:q40` XvpXH^A8A$A0D -1Q4Ű:Q̲EJ}CrةV؋P;Y~]L3OaJcq2NbS/܄"YLy9>9gJ9~!%80jbr>$zbd{ezĚ#{h?Fzb^ژwmr,}~u|s^!m)b5vmo8qla Iޢ]M6wdOw+ٚX%f&X?W(1Ez)uqrrّc%;q\zF0 Vn|x@ dP B Eh~`=PȬPn3+P n*F(8)* JxPhGH"AP\|^" -~4b(dĘR\bZ׍R1RHR)~1RH>3r(ۏ1R<J#3"?U(/S"'(%R(EoĪ79NautKam.m^NK,Kis5<<>ѹqa_N:9FӼg'F)?HLcP)~}q lIZxgH1#K&3X -6B  -!QAP(J5*-1jRHvqzXP)0'1#,Glx\-EF)S)TG -F)ņ1CJ1P)RWS?wXc(EL:StEcn(A{q->k`P -ayM(oQKEJ%KPxrMf} js- ON( 3%1t>+e詑5=&Zb"BSۻx#k,jdM+3Y䋬RX;>gm}ϳfusO˽6 ۗ.YSyo}ZIí["{o%BŶB"mR[ %V>K$~DK_I\@`0`;8 ,$ /  Ƞ@"KuĔOr߾(&k1-!;Ĺ YB܋=+ -Ip+K<"i oeTUsFNP~՜kϣ=/VUےh=ӯ;B[>o!ܡ_ |A+Dr"~v-$6,w3 ~*K$c0 h`  '.pp"H a@b?%DI)1 -B/!L11 2bVD+8$r)!W@+%|#bJD* r Qq75(r28bN1?܎1xfOhd-ѓ #'z͚6tB߭}^ea7BŋГntr=e=L]&P'|dߗN:.GOn_1K8W;#MLgb&3rs`fJ(Sғu{KqGmT%qk1mj3Lὴt>h(mn{WxGٳawֺ]56w}ɞG|Xy|Y3oxstXR]#7l)=5+!/0 h`  '.pp"H a@b?Iϡ 1:FcSdd#{D'= ->')?)I-ZfzRTEkGQI㆔"occPLE% -ua"7[oGqN"wkEZ>2JZ"_1x2)#L2`ЗpΡp#BH"`jOOMOs钐N A ˮyrҙPkugSxVg{{8 AdU\g96Z~u\8:yI~s5v7'/oɺV|.P{c:}S:P{7|4_J'l[SJ_[ o ]I]4t.S?Wz=P`#`+p I7x >< 2(D!CI|`-S9i&bNk؛c-qv9>'Z#Fo$B5kHdɰ E>U*vuըifsnj̢r?$?UרJT*R3DU ۨ(2p^f,gV:x7GUʉ{ -{0N[̮`0%#<˝()|0 fjgW34*{W:ޝذ^=vh~o:PSAw;ZJbq40` XvpXH^A8A$A0D -1apf*`Mu -Twģxcp@|DPĈ$ɢrp2)#{tinǪgf/y!mqO uF(|c7P<]x(!r}PlOʿu)_biڠ/{|f6##O١_,(3=˟L B!K'W< 2(D!7}u*uXEL;J -ZGlľ8ڈ54J@uWEՄk&|@"U9_%UqUVj*hTEEU|kxí{cq jU*4:5fjUWb 6Yŕ?̺6 lobdFڝ}U[%:9B\Q -HźG%|~&;S - Y|П!r2?!fN^5xݦĺ'w/Rz6iC }c}5{2ÇY[vOr,ymoT>*y9]ƭ%O]._Ef(Lʥmº6{R^Iw)]bݳ=u%[(0L@fl`8$p<AC,{RBT@\”s_89w_;ro n%Pny%QVao;qg:Tj\AL(Y$Q֝.W|1;(%]$^u>@$U3jI>ИO.EI"AoQlc$P/$Rεb>*sԔ?PW^R} $|JYV=Ԍ`j$9(Irf˝)dx.U )ɱz#{(ZU1߰6TҦ\}z *EUk%m:kX5,=?t'u]/]o&{V+ފNWڗ;ծ9Wر)y{Mͳ#KD$nq40` XvpXH^A8A$A0D -1& S1YpVSx^W"GRjJ0ScK!30BVj?CH 8QT:马וkɥF)VlQ'?C)Jhcڴ^[K30S},YjѶom}b1lW)]e6O= (egno\+SĵRatykiJ#kD Iǥ - `,`N`! \xD@( ~(S3`ZV- p9eC)R^gZ福؟0F)x֠5ckDse3;יP^L7K8WoG.EQP?=5g%g YAIԸУ^WU?9V^x3=:1g`M]@uLo k}yUvyC&96y-nh缤XW[=_ǻ]-Ḯ;Tn%¡ˉJi[Dn(l!JCf޾%)g־c@`0`;8 ,$ /  Ƞ@" %ŦE\bzN"M;Fx27nrP9~"a/i쁶F3񘪎шQ#E?h?k3޽3LоJB35ѯkԭ(a6A >ݚ8a{I1 t;$ѪPcT,H|_!}s4f#o]s33sr)B2xΟfbHtmk0fnشggb856Cam,+M=eif\SD[Nemųn{UxG^mnr'vw'{꾽»tױ4-P{zL,H[k;˜Rӊkf(*­fERJ ǥ - `,`N`! \xD@( ~(Sע1jLD1`9GS=WM L$^6& CイTcxT3&ibZG1w(E)|&lR|{(} #J9v??K8G[gyҏ ed -?=9%/drYqd);8mWD) -ivohuOӦ hfC mn4`}wOuwb /Iz مI{zdf`oLn$ -':{[ҎA,Hg>[7'zĂǥ - `,`N`! \xD@(Rp;K&b-kEI%䝊TN %EjŏWkj)j)R(&Fz`l]lI-H $jp)xDcb'V|&Xz? -QH{i0t-G)>$f~'E}y5 -0Ɖ5%y~ŠA_9,Wgd\(OLI03/%wrB:H|AGv)EqKzl6us:ƃiUl\NA@`0`;8 ,$ /  Ƞ@"[˒l!ts3tVb N]/PڈxoՒ@ Fv"n'R+8Re# -f-uYJv{?:1W*ViGzU)XߏbD{r۟}vT&.S/ޅlEjT -gO*p0u B -?r揅ʠ/Tn `(şR1oHMNΖC9:Wv7eqs2f_oĴtsl|srr˱moynqaww:wЮ 6wOS1[ۻ!-д1t&]>W8iSj|7I w.ٲuvJǥ - `,`N`! \xD@( ~(' ƈI Q*b"9N瑽O\d`H^Y!:[ EJeX5BѨĐ;=ip߾|c=-c4BBbF(jQvPPy G;nZ1QP^'1~<8]J/L&uE}נ/\N;,bN&?_R?(s\Ɩ~e]_ŊQKolYg4.y3o殠ri꽠yG+s+2{g9׮-0~6a8p_.Jwx_Q't~MĦ?)y+u᪾]$ok.ڸ!q(T&3X -6B  -!QMyD3>~tc+15bm!ľ8 \-1>~w;o7"µD\O^"we5IjQV?&kTe(Ə]aAo1A痮?C*SrZ%rQaMݒ8lxg1~x+XfcQ3,jnEMՃs5~| z2gB259~R9Y9RNFz^х韛;koQ+ޙoqw?K-o7XF[6~6ֺǞ-}a|N]'^9ԥx),̛ɝ~n_\)5=0]nܘU ̎zhN"0 h`  '.pp"H a@b}?\!蕁$&`[d?,?𾦀|\*N#/v!/bI:%DBQCq> -Q#Yx~C?f/]crsR՘?޸p{j)T5iʍe/lz (_0/G;^(QmKP>Ä0% ǎ} KMOJ -! >~yv23l)RS*9>P98Ntn6B%@W659 %kٓ(o}ye|NJ޲~m}5gַ*?6OU~甎L;)U3[ω6'Zѓ(K&3X -6B  -!QovIF4+ٸFNW0'aAc 7k,Tj?W_5J2b'w4n.cLPE 1RP|qFez-#>pp(F -Ǘ.tKQs 5P< 2(D!?i 1.2-3 7scn&&w.,?W -pXCYY4v5#5F<~YYF<E<?Xa_Rc wjģI]X5 Q=W={T#(qpSGe?OX RzdNSF/Ô-J9lܠ/\s2kYY䜠?+$BPʉ)OC!Aӳš/{뫨X)q٨O7ƶrTvj,]YN3hK֚ǰ嶗n<N:~.x)ٓפx7Ⱦqf -+ߜ"irJ?F. M9eZxc#?=٧>]D=P`#`+p I7x >< 2(D!?GBL}֩XxJ՘2yn*#QLpp m\m7khZssmm1q;>v'5~OR&{NmQ]]蛴iY=s=E[R^t>MY[:3ev9т(0L@fl`8$p<AC8q5EhiMmCq^5P;;D8a=I1y'Zh6$J(i6jܝfš r{NC-ūQ$_ _xA-ꂄعxz4Sfj.hB)7?XLM50L SW\mLԅ6 {9#^J\dC\ -‘΅҅,)=zpM'nvGب]9ykr']E6wSI+Kd@Qaw|sǧk;C+_4?.i-|T}GzF0 Vn|x@ dP B ~+^ -8#LX"^u w/rD|^zua(C-ԋAՙWML/ÐRXaِ#Y)>غX81SlE)V5ŠaqT~wb KrԤw4tcByc"('P} )gyWBjNFF(#˟% 9hF:/e7133CR$ }F[kUN`~Is|C -xpm~,]R4-yZ?B:^r6w=82l oidb۰RM x2|}&߷t~1I5g(/kΊT4Ύ4'J0 h`  '.pp"H a@bC)"3`i̼%fD" ILxT= 8u>VOJqZV-)RUj>GQQg)1b6E#P5B@=m)E(~'mK1RԔ(abN!erS>L)R,>n{/q&|;,ff`J#Eq.$Sr,!( }ʞ5KqhP_іJ=:1ֳƂi} ]2Uum k9ZX{YMLr5rGtulNuoՓ==w֟l!S-; x"?dg_Lj~ȹr$u[;%ͿW140` XvpXH^A8A$A0D -1ňƝM蛘E,Y[ltἙ~ƴŞ M-l&Fy1tЈGFF4ώxg(3nĻҲy_[|r}9}GkF4S}wT!4?bE?ObI[Z"E}ƩgW"F4vR샾s4]RJFJf*RVVNdz*ǟ!d'뻆jrŖjG&ƥ&mn0{6ӦrI3ʹҶ#OMx籇dzkTTukg6S]7+W4jvи#*J:[f*uωtW͍]7t0 h`  '.pp"H a@b?i됨qi=됨Eΰ i!aLt_4 s>"LRTvPt4B[ /4Bq(v,>3uHǰ/1/.|`G((opoat~Dw @`0`;8 ,$ /  Ƞ@"pEIz}1k$=9} -K~.r\;K87= ڭ̐LMsY~!9#˟!|fzJiC7@WJ7^ww$/,s:ڳǜ3O>\g)^9ZT_GW8Z 9W~bHR]>nyx[6_wtW鸊Sktֆzpu"ΗVQ>zT,9J.;.r't7@@`0`;8 ,$ /  Ƞ@" \KF~J#]IZb fbǵ8s=aīRJLUķ*XC:"We;9C2MuޥQZZZ*Wbバ^;'gPVfe؀}^q"+a^ڶ7Ȋa ƔY&QdeٵSj&d嗋T?~9Ɣ} h?˫ ŬPvg`d(;:)*$g =cJ].ut XcٓzCgXzֲcmُNrs;sl۳ͺx{;5|Bn!ۥϿ)%LŤD{yQaT ml'^"b140` XvpXH^A8A$A0D -13n Rb!ځWK6[ G qVL^1jm%ڷ+X!r;I=1Ԡ 5(_$42oA)$g#o SmHјSՠ u+_ uO(ο\a!(uw'οL(BPVm.p@Pv{"TDg:A_9 -4VjaNgx uBNNJVrvzfhU'RЅMzW.<2mS4z>=$ung:7yIT֖[=%%x;}AiAiԾADPNMN|uM-$|Yx+R(0L@fl`8$p<AC/@N22H޼ruF{fv -rޛ}  BH%BOînGkF/j6p"򸳝pcks6xoj85vK1j5~14ü6TۙuBOC5^/R??kffוoɟ]jͮC#Ms6'~ %>'Ofddg%}%'v}}{ښ7pƢh6іch[N}G ;NuXKIŻ^tNlK6|=+("vw:o]#nqZlvd;O\ށ0 h`  '.pp"H a@b 8`.F4qv!8={YG}B@D^H'+C..Ըٵ]=3K#jzP?%Ԉ'`/|[cL|͗(X\GcZFj}( 7>se<|a<%A,XNE)bx\Q(_wZ -5,A_9Iy$#;55%xLy?CB Nu{}&AJZ]C K{9So s6h!gkHlO*5I튿>s5?9۽xư޳s.p:c:)PMϗ -eW:e'۽˸vخd2Lq][K=޺zUo6B)F0@c#CcbD@#wv1pΛp3Up;MϚGf V4N`n|x@ dP@ (l;].º5yc;A"a=SOfJxw$m"Dv|[6Bj&&BKۨabcuu#nYA(n{`Tqbte?"G 8SO[\#]#;f W^“5@tfHXRe0тTOA/R]?I,/eG<"JHqIgKCR'4.g}55w>ǐYR}(c')zqORߍqTR̲Xזi/hCw㛬3_6<X+V H0lwbgds} 7o%U̝$XQJOOU/՟.0#vxFte,`P@\x~`!AD@T B oDOLaJ@r*];ꑒS9V -5!(j'JD4IaדbAR4%l& 1()N AIqbp5_!=)5HsNeIEMQM1I=d`amjظħN&}B:į){är&7BOEI K"c(dP -BRPR2Pav~,gedDz3L]n\ioj%*s?޳L׺wv| M5嵟yh=g\=gǬ6߉yeb+[ DIc(:l2MU#CWYўցd/҃^¥m䯴B!_Q2CyrHWUĐ/Iyy̼'Wo2wv}=ܳ!i9'jV.}r,8D1-'μ^P9;/[Z8xya=ՠءnHH>VCˣ[x[:ffL_5!UVjuUlqfnA/E>ffąPkcmˎ -z|eXbvl'){U(.8QLӇk4w1qvvo(Y}=#lWs| [W=Jǧ= r93#%Όg`,`P@\x~`!AD@T B /z$ժ] -AFe<U|PDQ8YSTU21?.׃ (їG3!^jAlS/q%E]<'TOO Q^b|5H9 ?W_1ʽ@RLI I13&~Ersa26$Oht*Gz ()2F4ETIPVvF<$'ƳQJfQ L)ARtM(ܕJƚ۫\O z t*ߍ,Mۣ웗Pߍq}bξº6~F{}F{kX_O_[Űgj`*Wx_ GX-W,=]\.`F|ftTR@` -h /,  Ƞ -&!Q'i5kid("N NBT"MI&EtHr 1i[ʔJI1Kש\sw0HRSܠSik(6tyG)mI G~_œ5+")oI HrįTDäV!olߏNEK5cQX22(xa(0=3?0#O.c3L OXHCjyt熫k"k_MX굮^~~᥏7z;yMsig^}lי&+_d=^`퍳}ڽY] ,sㇸ pvBVDoEH?.%s &+5$_%cOo'g:~u@` -h /,  Ƞ -!QAQ W'ϲ -"@ZL r"XNp r )q@nGYl<`cޣd}[0;`17d]Оho_;;OKkv+j-xF县okvKU$֣TwZt/'\dfsfjOje,`P@\x~`!AD@T B "'G1[NO3Lq)'%H7&bT"ǔ(@18V ):DO$őQ_䠉)*bՃB֓)/S8 )"z1Q)$UXanϑfIw1H'3PSߪNA5F\8LآDhf$rp,S(Y - 򄐔/Kx䌌9ڠ߼sL%I=vTt]UΊL%k:ݿFXz;͚76cMi$7VmL¾֘3MRs턼9JoqIݷfI*SdzSgMkxP3`+؀'07x > < 2(a@bxz¼-NX; [+A5torN@0݄kBx:/i&ؽ MIS7 P Υ= - zrAGytk)0ctg7J*APz0Ő,x.lOX)zfʾxh_bq4RL]';sM>R6״J.m'jhcgUn[|Ik}jXyԳ6*7(M;.imMi@` -h /,  Ƞ -!Q#5*Qdt*F@( 2D7 S\I#4?,:=(zPdz#SAAP,E^t>:ї1<IQvt*g'ɣLڸ3$EIm\'GLo!)jW 9L"uHxˠpbS)XRzH IrVVHɔPQVzF.'3$)ʐ:'l/Ri;0\!>:4el;[}+)T")[Usuu4wk)^c}G) Yh]/U959Bkg60K*$(GEm`d{NHŵNe,`P@\x~`!AD@T B '<&*Jv;5$E{4MMv*A>,p&UmVIqyh@kNaV.ӈ6i41-!xM_G>5h7HF;HgCRtu:ġ{H;").L\xO~~_$z jMcC'b\(""QSIylY* egɅybfVNIq Uz|y3C>DzlϭSYe)fWkKKڳ =s-Xp_3î8#P[ nT\tqлuxҒ]tZrvxUneHגYmOj -<Hl@ vpp<APA0D -18$1gN>ex\`rw AIy%F|'I6H j?5 ARu7܉xd87FOIAR1#G)4$ãyv/?‘}M1ʺ>-%GX߉Qo2O2ӂ{U\fnp~zViד=Jijg}quncfd_fE{Ve,`P@\x~`!AD@T B Oϐ80м= -vEܲ -ɚ"d!(%%y&^2=iID>$zR8Q]]장a)hzpPL9CqzqAbAP?C ?-4w/ɉJ!EIQĢ%@mqbJOz (( 1*2xH,*IBV([I2L%3?@:fAqwn}oǚة95/SօZGٷգXlq־N1LtMsWyyK\yg7.+'ӂ+]58;C\\?[YNxih oihVtŗs X`8^X@8A$A4CwG"J -][@Pe -5&${%EP!,}3d=KzAqAP6xta߸]1IQmPRROI[}A3'EARvwsG>z'u%Ÿ -$Dr~&~E&o$ĿFk1Sa}x te7^1;aB68Đd:u}ĥz{j.+/XkZkE+TDD,"TZkY"VXJ;-l>wwfxxۊ*!yA媫͏y}YoS|rWݖ;wo9|<ϝ?sn#wU]۸}՞s:dSƦbUwD:S.zɡ2_irj)+u40` XvpXHpx< 2(0D -1H\ -(ܴͼzl~;p;=,rsze~+xCpJ$%KI%sg~;_Q 9{f_}Ϥ~J3b?Wff߳ѝM=+++VU*~2$~uzt)^]_ݟD]Ku).%K R"(e3e?k|\g}Ymu$jV?ϢyV9nYEg?EY,~R%?9U?q<~ʳF?*P^dZ&V/7+OCޫTԽ*R(&M=(ABH)[eIY>QŃRЗ%de9в烢%aәˌ.3u}>n|e̼,5kkia_U=v -g+3RW$;/,ߴлd3Wn&[PݴzHmweI$TWH"hWv$_:\(0L@fl`8Tpn"H !Cĥܡ_($ƅEl]|b6מO wqWJܳ=3+,o(Bw$<.Qc}^𘕒 +4cR1Q#<:Sx/=Xx<c9xhD!J[]g&Rc܄>hxH`v^ 'I9qޗ Ҳ3r\1_xC8qd8p}y,C31Ut1}lԮAL[Z* ֽж;8Q8ٸxU86鉑mܪ{ɐY[eSG -')|TS^ePׁ)GGV7=-ץ - `,`N`!\<? Ƞ@( q)1lhCM_8%6qD!^bbT1P'AaPFAA;xE q$J#(9 22FĘ}[FjEВ ooj=?<(޿O#(\ -K])шu˔_)C\ S?oPg/V )(Aʕt$r}b"+77gdųҳE cZG2(|<\\>p6ibx A,mY`x7ԍrb;Ͱ5\/h׺Wl\!O:{|Wxw&&H^v퓔L זOԵN0|P@`0`;8 , 8pAA"$.EQSV̒%bBvrl7(Dyo+BTе? -?C2(VkŻjP,"ɠ8ӆo^/(EOR|1!Ƙ&ŝjRh$E_N=LfuT#)n}?O.H -G?{$ ~ʉz$ůˑHOR\4Dy|.LJAR'[܄|4hRdAd/eaN}8<9=g㩵}ߗ%瑖KѷC[_fZHf9Z̚O_fi|zvnqXG+{N_66#uĭ[y{=kz7#u'1] 9.rd GbޠE&3X -6B*7x ~A$A!QAR}]"Rc 150y5#Fb#h&FK5w #fGZ"%b($BX9+943A#VnPKgjB#VCX3 -{(Uvc7UJ\LQ_rJM1e=,HRg^;]?}2X8TZcD]*+OC~*|f}nEf੒.gf4eH>Q@֌~҃if15՝issje(_AҦByL3Kis*RYhm=_p7G9~8W~ðvAx]z[nP9CmS.A) UUM ׼7-Rtzte\(0L@fl`8Tpn"H !Cĥx{Q4a"[y[;N#58=~DRX(b͕AQѩAѠI#(dPd룄G?.{܊Q1PRPbT5)^]91IQVLoiơSvR̛qd/BRD'JI.Oy̭%iWBtrZ&\!?҅@K d`d$E^'ijs`0=-cͩ;>]io7 mKƚKɀRʥ=[??ZB#+g -=ڎ~ղ|u#iD82ǁmgE)OԯFh\QӽZm޳&?pzF0 VR^ - B c1#O|X!h!&O6'ijEDK6"w]j5VhXcb%E/Vr~H2XuryoYo4 -3jX5b%G]$yR- D#VC( 55bGlFYB)nN5W!V6X$Ra*( %>7" -/~CNd!s>1'#ݗ}qr+Mꪾ Q\$9etjoWYcrtA.4cY˱G+ə:N%+9vҙg]/s"φPO~oa;x|CDMsu$jG= wJ"Ww$IpzF0 VR^ - B P%ĸT@"];8oOnw9o>ɗ!HD^*&g5d vXMj1 7 !AS.0ShCf1*Osզ  :°j\&H?_yq]]'H}għ!0!0 W5 )A2?!IYi>Yx+9Y>I -Iq>MaPLNtXv(9hut,CWm]J t4SN=E[fzpuug [ʥ.;Nb6gjBZ{L;|ݹ)u3%M*PgដiӢ$7dp]zF0 VR^ - B2GhF%F4k~J0[xˋa$Ie7!s'L -FRRPj4OCH -nФUjcՠ[#(ڒAXAQN0ejP\7y{)O9?umzbAaOe2[z%iO)uu݉(eoxMXUc9 Aˊ@N\ _P5?ҩÙYcwOdUO/FZ7+g;ulsnxc?=3&ֵM:]5}"Bhk@h@#jlDxFmfӎq&iD8KF=P`#`+p /D@BD!K)"D(""b*!tay%,={YFU1+ ")!{_(*D(N+iHQdF0cFܭVZ9keAvvuw $ǘ}d$ Y`qlUϊh4p" !H6qs3 yVt H/(g"HnW(/Pf[& Agdsy}b0eU^ZzvPBh,{$kw<5$kGOS'!x%kj?;2{^b͍HkAvrL{ϧcD=ǖ03[5;y{-a ϾHwn^3Q -Ih׻[Hx6|r;vߙ \(0L@fl`8Tpn"H !Cĥ=ȑbboOg[n&B8f; [|BB] G('\y2XE^H? 2B#Aƪ r f #.{ vڴ]cVMqj$Mj H SHT ߁*a1frrH ZW(}Y:a*`NY.&\!  . Y|/>Q|YYY/;]NˍrϴjhI&]NS`YӮe }˙%mӌa·n7֑>\ƱUޘ5;\3]P>5-6n nV"T|j ʞF޷-9i@=!E#B6\(0L@fl`8Tpn"H !Cĥ{Vˈi {3MX ثz,!l~"G*WKx -w)/NHD.!J -i0S^jlKl>01>נm:c&s09q2d䥃=r&AudF&/` u$a#&]=ܪ45yumsoxTZGs.։$Wq9b$'9_&ǥ*8lRpOWkΝe]I_Mer tU5іruymxXwF9Nr{[.ȣ#]-6lgݥxN0Q>SY?uG7ܠ4 5%\HYhacrץ - `,`N`!\<? Ƞ@( !&wmz@xE9-{>IPHjPX4(A>4Aqiй%T@I~cL}pǩIQIZ;R4Q]uI?O{sꥮ<HLY+OC)e"TlMH#8n^ %KAO܌\_V,9R^%3%E=j㻞ۖ^ؗN|:ָqk~$2Kg'vVsѱ\Z>tn٩9y\=ЂeN .ݻˣhe=\ζL>r18ZǙר - `,`N`!\<? Ƞ@(4RJcaK[%I<)WL~^+ggˉPMj."WPD[ИaUO4d&Rۺ`㲔 ll9j]v|<ԍjTFa/>ԎEԎc݉a(%*jǁ.%iO“Y0cC8&\ڑ>v9!ɢOI컈yf^ӳBPkNJc6r$Y;~1\F2P mx654m.ZC[j2XmrsF90.z1jG;t yQU2'2S}3&Hv|=Iɯ Y:%|iZdӣ{ץ - `,`N`!\<? Ƞ@( q)ދeS2n"X9Ԏ1e.=7x -ׯvRbѱ ÒAhNtv4hʼn!ԎA1'f!`@~I!i<C̽n1RX0|:G#)Ƣv)ϸj_oERӑ揫ƀ -$E&W/vOlRy\LA9- b = ,YVޘuO6o~F;ϰqVAZ7+iAڼ7X?MZruGO<Ԧ/hm\uUpȳg="_,;EhvfsS.~qmr.3|өEMu40` XvpXHpx< 2(0D -1H\JX-$fl -zdKتآ3Zw#{G[AT| MFkAzcs4rOT=~ ~U^5)hեѯ4fyդxOcF4C7ݯG?g~ʯې1ERl3|U")vy”,}}nEjRKA9DH$/7Kx _RD8d͛ͣf~iC: 4Y㩧XSuHz,sj-k.^Z`m^i/7Q[9rWgng]ӹs럼rz_u=~MIĚ&G<7l&"4M$yvר - `,`N`!\<? Ƞ@( q)D_hXHLwoRڻܱ؋#8糋~xwNO! ӅR>ˈ `hF&/h?Q8lк' T7޲Ęjo~Q7sc_jj\/_kCkȼ/Ըf;+ DݨQ(gb|9[/Nԍ %D /bPΊr1Č/%}8IYur1Py~FU,CũƶGNF-үG -hKaֳߍW׏rq}qoӮ61讪 y6*ރd;-*~u Қ7Ec"1-Z0u40` XvpXHpx< 2(0D -1H\JD 6hÙzMKJ?"n~I%wŒsՠXA!LicA c@I>oRX74AV:5)j$mIqlϽKP7m'Q!)YI||<BFiQ~s.RRffd/˓,9#+MN~IqɵA2)5 7h+pƦoFFWdy7#,?f]}nm㛜}ÆQgQݿKFظO<%O)d _KN?2C2A*[LR~29TќvjidR@`0`;8 , 8pAA"$.{c $gfw0E!)x.{B^/_(\0I}nT_UL -רIP+EFRx0L.$: Ҏ1'OPܣ]AA}5Еu5k2(+W!(sI*EW~_U[SظLR A1;$>EP~N"Tho*s.QtB4F|\ٗr}iiAMv.C(\r|h}TK6<?֝̚I|eYKיUY[뺙::j8g ۲ffjFU18]Tzg{!%b.){sIA 撍N}(U/b.?UO 5KֿK:գF=P`#`+p /D@BD!}z+'bb*LD_hYB' }I􅈏;ϾM%sIdrkhрi%mgjfX UfЫ;bM1fU߲@A`ǾVƗ7ec2fd}UK~g_ y*Qy72dNcIlzȐV )C(|vv9y<2$#O}!#/76!oч۟r>*Egh፵'/0ysRYyŀjDZ hQ!I-*WgSwwj <*:c/ϒ}N)>.ӺpѿEo^|P=P`#`+p /D@BD!/I$u$YG뉩Ы e'!M^Kg:x75D#vԑ΁*3M#UjHf_räbe[,4hvrV5X٬ -!\X'7WT"V KO+&TqgxT"ԯw>X}nڬ|4-'Cg/MȐ},}xVVFzZwzޖN|դ5bM 14o`eYKbp7ʹ;0qeYc{fu7sq>wޣKu/?jҹդb$J5ך ܲ-$|}TPM=.@`0`;8 , 8pAA"$.e/-,#rb#L4=VJˈ8~ب'\ܓxc|~)JXBo,, -4G(5GW#GQ'5rdƏOk!#!Gn$rсr ԓ#y_cġݤSsF?a B<cc8#]ȑ7K%j,9rFBxA=yP -=sﻢԘs.ZH@c Ơ\_ ''q<, # rNN6w>VĘ?Ix~6nai m\6|7n[N3{he~b%m+?ًwrta [Z{v7ٸ# yU g2_g-N )~r63bJfHӣ5&@`0`;8 , 8pAA"خWMý1S\g"[#9)H빰; y'g!!$FdI6$$)85)>QBjRdRܭP-V]#)?s[0PP17"1wm`,I/HJ.-vӶ9 2κ z+Z/ڵjkYt7<[xo{)_1Eh.!2AjkpʝeN /h{(0L@fl`8Tpn"H !Cĥ{KF)LTlwDa֋؈r;Q?GKQ~5¯?>7K7<!)_@9\7)jRQ@v4 -HLM|FbbI( qR>ޅg/QTvI12L)P[ːS a #;'7d_<H@L2ri\9g w#)ՎoR_q|hɶtwbkx5-_>a:|VX>fmfګ::9g`fjAu5[5}p}e;wl!M[vCD*oF^I3Q:JIz>6)XD SWM[0Xø]Z?hW3jn|!7. =8 Ksa. . -5JSҊ0;6B+܄6`@Z0M y9>Q3|l1qBLN -eЗ.[;MF}h}t[YcVԽ]㲄 -3r@ħv p]}M2I -JþeX)kj.`],k(c-gխ dXާ8ga%n*u-y&+,>"OãzOS'|MIbϥu' /A"&ڳ(;HzV9vD+Q@`0`;8 , 8pAA"7VcUys~̈́UV]QęOwIo Kz_H ՗PO -5N*4AO8I}nq651找yr'#4APڿhgCXRMyP{efہw57"OKԯ%O'/QNW"s*'܄qĴk q( A&\!i(7/MHZcnK}?\F3GiSaGifݷ#̻iKӷìǞmk8{QsAm}K]I=iݻSu:[wzп'{-}eNyW$zrpAȉSz_u40` XvpXHpx< 2(0D -1H\ -Jd$c^.8B+!5Nq {u6M#)jpK$9#i>& ^Rmi,}S[5(fi0 a9|Oţ AQQuWr_FP,*DP#(Rħa>L](ŵU}n*Rdz|9yRF,_<';ϗM#kNG}N-sEָn%kn$}0ˬ|5׮f-ߎ`m3:{سgfљΝ]:]}5 0? F"tk֢7(? f] &`زDjDO>N0F=P`#`+p /D@BD!?(H"ϧogZfcvkyK2sw~px4[SneMPs#C#7nUdaha c'O/1O͍jn2(I-5r1 ` `ܿXzc`Y|4!7o+UJS1ː>7" iyq1qCD ^<_\ |/EXucf׷بM2{6lM胯̡u5ڲ`-NN揵;y(gy(veWUisK9$> Q0޹3BˬQ~M8I!_$yA_0}b< -|9E(:],H':嚓+&ܟ^HS_%G\(0L@fl`8Tpn"H !C^N* )½ILDMI! )DNt+EB0/ -٩DB>Rq dRH -?`,=\p-b1&IqFRaIFR\nHjR|)jOЌN1I1e.׳(qY./ I1JS$'L@R̾7JiIs.RO0#f0tSH/2yAƟ/)?VW]&bm6eaӴ9԰@ohjr`-m+qfmgv?#][mܺ}Aw鮐gi&xa ;|KSvO|hz,)p]zF0 VR^ - B ~xIÌ`XdbwI!q[^OEIy$ORdn]ޤO&51(ƒo }y}Nc-McjN_5ZRa$6/Ovr}{rg9 -kD!(&U+o -SrCO8pb}nE:06xggY邏ij|bY>!'-'+-Npku-Gа.y`wҟ}~*Uye7n*Kçy\*Kw٥n޶aľjG ٷ2ؒH7r4Hܧl!6oI2i2dN8‹:qow\XB}.t)]ԳȒt_HŅ40` XvpXHpx< 2(0D -1=N{Kq150uļXZZ8ڈ=Zm'}DN̈́#B ȇރmX -N*Wi:,QSť*w|Icߢpso3iUSeƞ QG**!UK<+~k6$Ou#U=(QWl)Wqw, -Q{DžwFHZSr@v -is~!mYaևi]Q%1\juE\tWtvj~ZeK2(p]zF0 VR^ - B ~S&#(n0hL4_oavz4޵ϛk7yxok|r}膟EƏB%?cS]S,jPtk<~S]TW4W jHA񆺹Am!47jԸC ԯߒxhGP0?S(a*o[MY}nE -"(r3ܸxMOe)|yZ]{s{9\FU+ߥ )K$9{lNx-Jm!)H|a* DpN)"js.q9oO8D$O,Qe:4feGɤ؅JU,C%Fj1{t`|9}?m90iۺ8G9.lR7I -۸gŻ~{C&bͶ R>f$%ɡҏ2i}{Ztߓץ - `,`N`!\<? Ƞ@( q){+bbtc5fCcHu<CD!JCRC$&ETcbH1L#)%jRQB& CX|3SS>17IY#)PbFR:|$bFR|<:v F&T H$HÔwE -Os.&Ip鲘d,o_".S\);-=- J}hGp6pG\>ml(MU_ 0|}; mQRDjqk:g l+Z?tz4iPu5ՙ݋g_ ^7:Ii=89oGfꗧEWmN&K&3X -6B*7x ~A$A!QARzktXpTK5{ź^~ףw N]wo4SVFRUSsI2)"wݟ|81ݹwj즞QaX.g^UA<(ZR܆`ϿU[5$+ħ!2OjԘQs.RW)2Q! 9'|BZ<;;C -h<ڊJqG6jU/17+ьaJ;y/?Ck= σ39ݱ5 g/ڟ ㊀i<IZXROQa|HJ?~!'*D^hFt9z|KՋ\dIxIniA> -%DქE9a!()} ^Yo [65Hk+I&}ǚh9X -Y]*ں:5uO -Pal}ƫG(+wԥLZ9oL@,`(@\x !,APAD!*Rm>dI -X{ӜRlEh>p,$1"2;A - (< (zP'L5Z5^dei"`r^4JjJO -KӇ͏ J_ #)fL7HPbLb sJD›UE@ѐxM#TgkfoGT+OxXQ\EEAQ ǹ8jIq%Ƃ>R%15K|݊zu[5E硖-,+)=76/a+e<|ie6/Ͽ#/<ֿ%FrεeMcωt73r53/^zBqkh`+@ 7x >``! Ƞ -D -f1j33șTyjtm wm*lmFBǪ "C|DzPǏ y HFHwy`Aa*9\;Th|gAޫ|kPrԇ?Ӄxޝe|?XEɱ~{0.vIrBĕP[2F,=JM[|KUrpIZ.`Q _KR6OċssKQr}jϑTQ~f{M<ܷYEZ;jdzjyh!efε$}\ږj=O<-og9hfûa]J2M,,+{q*_A{d[||\:~:_mj/ЎLץ&J^&   .pB -Apax"Z}$Cl2:HEQzsk{bů씐ʍћL&̨^`je֠['yt0 -Ŗ5(TUK/FzPtT!5>QhPQQQ4¥.]%.+&rKsBnIrZ{vDѦeDgI%Iw%I.$mǞ'k̎M$]!<599Gyv3ƯH4af%P]fq+ ->+z-U3In]SWk7hͽUe`+@ 7x >``! Ƞ -D -ߕɤ`Ѭ()"HΉ{g""( qH -H wGN=)1=YIѪ'E #6){[?e7=WXg "G/)GAP<0eƫ9c@vr։ EAAq9T!B'U"ocF=NH>¥=ҟK\8,xiq2;|qAPԠX7k}#{'z4QuԼxi\OZWVsOԙjҶev,]H5gL(ʳꯌwWfʞ wV52[A0i{&lR6ʦIr)J׆ڞ -"kVz 0@6hp /?p"H *h(|7H ("*Iv! -BtKBb$(B AP<8R61ypDڕ {,RsJ eab~V/>}HAPLݮC:Kܘ:Dߤ}&/H~_N>EqC%nrFVEku>¥x Լl>A $9. Ja~pQ#ЗhomZHt;>z{jWclk>cqhc1tǛsL׮((ϢVPXױ n{$pzT+u{3X -$P`;8' CX< 2(BUq #D6CP -!(BY9+DQ .`T5hԃb^QAg43_=kԿ )4}ޣŠSKIq0h)'^%X¿,Dt+$E%ŧ/#)hr)ZF%L?cihGD%E‰R.!x<;(Hp0rT/J9+BIyVNxk 8ts1I Hw\^ylcq Iwbt~I{{)Oov?Q;!ͳCU\|Ba܈r-m,/z-{>_m|@Z8-Ҵ0{L@,`(@\x !,APAD!*L%GRȔ&9KH --{p,B%_I'v/~wYbOIk/דݛcP{{Զc:e3zP\Z8g2a(A6znE# g7 ɧ -qSP>K#nDq0#\o9%\˼PRoyDn;Y{֪]zIѦ=*5"-GHkK@R=rc"޷q~)wrt2ގSj5Ja.K,T[漖Ϸ&Tl+6rKO^S;MږDLx/` X -l`^~ !D@T QU 5JvA:5{/$68gId%YV{\XƠL}b>()<;ɞ՛7$Lp̐'E;ֳGQRH7Hh>n߉b_Yj)H9QRB_{AIqGThO -N%Ғgg\6 J*בgOѦD{MeKu3yr-I~Lgדtt-鮫4?~w߼+|+LchJf^ڞ=;lCh]ۺ7'5_Ĕ|uӴE*)^&   .pB -Apȷ#JivwEZN@UC -AU)*GL|4H]#͐2J -v1N" USN LIQb.J\O|Ma S4#w?BR$j$k -;j<9хdD_ԒI1I$g)N]3#\/a.%M.(%b<7\RT:hBR СTRTmڰ&-5y|=BqlIU?F6WG9#)a+7f柦ܝ({p/]K3ǷG_R-keX#>:_P+.~-5$̊)u'cjB4]TM2fHvp Np<0x@ dP@ "(LD2)QM]=*3 (l44x?)dYQ"C )npַzM>ˉH9 R L Jh4t5ŭzRbb0F7[H_IH)9qDے\vI$ 35BݽI9#\a3\qQq$Hr );/X )6wfuNhSY MO7>JZU곣9$[ݟIG9jHznY!uʽxhfÚg].c1|ȢBaQXj֝SEcjmȺ={3X -$P`;8' CX< 2(n_#)B)" )Dw@SeCCRȼ )$k:o$50h⤞'EART #)@{zRm.=)TRnwLo}@`BFMQ5œ,>ц{RW'H&95G?@Rr )*b 'b JBqIN0+y En6 #m_ȯʪu4;isH(m1I5n g{G9tyYݞrwnT~W²NJ^+_2YSW{ -E5 0@6hp /?p"H *h($_>Ydo)gԥ" >A=)35MIq_I4#FR/jUC8S=)1H58EAR֓ ě; FR6ceWOD(³@sybt$ŏǷ695űi*")K .QR\ "K 'eAA(CR^*I\SGRZ~*)w6kERnnOZI>!*$$oF9=E+W3]>P2H_+4SfؿjބXP뫝P;9Vv$|24u/hND})^&   .pB -A|C#EJv1Gĥ]!|@FR^FM<+?H -ZO -g-MIڅeW 1-#ӮNv+J>OUj@OHw˲Yt\6czloMH퍩(jGRܮu/OQSER4,Ո[GpcGDI~sXvqQ"J\1/(Hyx9lI+|ftu󻳱VL7Gei:7[GRK璶eu}QHJtdܕ)3o4f}9%$[cEc܎|BcnL˾G - -"U{3X -$P`;8' CX< 2(lHqHF(6݇Fsj)mFEERhxA#Dy =)3>./SI1^?aQSϒ1>FQWi\Sw%n\qZAH$r}r)3H -ߑy hDSHzNd %|PJdJy9Źb>K|.?PȦϲ/`~;M,ۃ-! ɦ$Վbӏ s][y]%8xPS{fZMJ2{X/լ7.ڿ+-}=gܵ&5uZi8V 48n|B8A$A4@ l7E*#=iũ$w~dB@je) SdIȠhֻ )}CARt#)6J;KK$EPIq(+ a+6k0jxa]YBD{ERLH]@MiOSP*q~c'B]>B_Fk1Sa}xtu&inH!ِ nbLd2L1ld"-MXW@-VX+`ŊlP"bحPbXJ%vZg{Ϗ{cozayߟh<ݺ߬&jH\rǬy,o~ւyD|V -əӽn+Hὣϲ;9+g]rW2;3w\p7?$o8>K8r!x9\ 39x7 ]qB8{/ -l?JM"7gʕyk4۞vdQQQDbK 荺J߱2;x+ - - - gBQSFSVt(O nծ)CM*y^vPSu-+mŢG;#l0_]/_9U9wy܆wEbJŞ&W!uS+^IǥtP@L` Xvp@8>`A( u(P0z Qel- vC ܼHYA='(EP&ҡP B§I"_( -9݃gWk6 _P)kYt(\J%yt(NLboQ ۭ#󑮺?#-HMM7"R_P9B^(JZoߗpnB!fCB`"ɐ7?x\!Cl<E&ikOć#,R͋)Cgj} ('(Ӊ:*ʲ ƺѶh{Y(d|E90{*ɳhSsC[|w=t(ƫ"LZ%U1%P_C'Pƭyz,=nφ›A(@(0yqXzD(~_9 -?# NdˇAo+ Bbk4c*?G:=B{rũ)CԞE}L)Ӓ(K˻Ѷn9(ieNG90בZٽ9FvSo.wbT~+vq{H$ W>^^tѡD :Ѓ(&0`;8  a@b:6Pz<3FLCvw_(x yxK$OB1F  t(v"S(2P1PԎPmBs*F_("W%*0QHP̻ LPT 2QB #/Le#7 -4wKOX>!B^~Qr~_9 -EpP` Yo W@j/=^-Z²ɑ'GO(p\Z Az04fl`dp<8A$! B ~ܣ -7,}Fiv[VY1Kv`A( u(.mo 2b3 %b "#R)$w#X6 |LOJ+xGeةf*R *ŷ(qԀR$b:J`btkRܡ*"W)E5KN)J)*Mށ%z""l)<f3g -OP -f)Lz8R)45Q)ޥf'^^ \ P2? (E+JQ阃KQHkt]RgH1SBHR[LJ}RR!/X}Ti} ;d)bvHq ,A`2Mb|P(ř3c,4]Fj׾i!M/g Π6UQŔx%eZx&ܸ~Xmko.(ZI9{߰0]N|Dl_+}2]di|碨p|XqR˾y/"/L~iRิ@` -h0 `+'07x,p"H C"8SN|xZ0&(h)Fpܼ'K%VJ)>W)mt)2UJTJRaܦ`wcCqJ(֡)f*#!eP%PH%0R6Kҡ(;P:Ň~H>M]J%2y ߿bI%m* ^A#E0 .ex2;  BsdLCHr)~+ +NdPpe9 ->Pbʉe­t(UBQDx! N1]T)cUB1Y(XRb0&_SY{!K<ŋ,šҳ\y Ł(™"if6 m2{0ɨ kK8Wkл~/ă~/%<ǽHxCB6p !4 {M~g~5Rl>x} ?2_Gm{k)W(Sse9iu'mmUGF;N4.kQβf޵ze-z꟞d5Y|yTh.V?s~\srx7S"F^M_s :Ѓ(&0`;8  a@břxJycęyPw#wT 3$OBTBqDeج\J!C%*EB%83in,%וPt,=~`:;~ (F(U&QXzP||ҡxxQ >܎RS>z -B^ ţvЛB_l` -CƊZX NŁ1uCqtH,䞽u)}aTmE!ӸeRڶ^ ZJ9^0 67$Oŷ9're'sLB8~.n((͑k 7̋L/M/=p\Z Az04fl`dp<8A$! B _s%DM }İ.C(RFLv\.>H!I=?)N)EJ)(#oTJ1SʢUJ8RFy#ŭ@GPLPB1E%J(T)~=\&ůU{,ƵUBD(HqO>pcG3] o&D1R̐Rʤ{&ً"|Qbg// }ufa~ ;/?哢 %B?2fѣҡ8ovrۅEY)e8z*:O.Eϩ sI%e)ߌbh[u'm?I;ֿdմS3dn@t,{8ݞ;>8eH 䞵9&G>]dzǥtP@L` Xvp@8>`A(Ǒ" -^0DKF$}궹|Hf|.-Xvb`eߠ7r-e7sJ((XN%6ͿυAw31(SB!%T&ڣT 0BAv3_ >3vMR --Bѻ /1Q8ԧ2Z~F(̉.%LЛ|Aa"DEcqMq1.p˸wǴ{/# #kW[ce{3 fP)xWLS'y댵ѶOhc]uuY9LuBvwUJ%r|E䰇ϟkHGL[r‹Y!/Zr:8.-=`3X -6   D!C=#T[DkRD]O7 C(I1R E -R Fe=M:N%Ob0v3eÐ'HX{,b7=]KaQ)ŝHRNUX{LI3H#ŗ-] =^S$͓P -|ʦ0yٙ"d^=M)ЗgBIo|(bЛ -?[if̊#RtԖﲐ_L.gR3 -O*(/2MORѶh{w &k;-̙CwdvSP\rdnɉlVLvuySO8.-=`3X -6   D!C߷)<+`Z%.:سRn'⋱2ų,J,G)TY{N9Rnb`)ƪb0Jy?[).QJaV)^PJOeQ?(,>}Wt<%>Ie9:B^(9~_9*0B R|/^1?eClaXR^7&e⥑R қue+?4GՕI|?xLS}[yűLKbzjmھh/(^dծXsKݭ{C'Lb[p7uOƫk=N -?9tҝ3q\Z Az04fl`dp<8A$! B ~,U}RΐF3'X63wkܷd,$1"EgJ)UJvF"b9Q_ ]t5a͊8O%2wTBQlg>DepAn6CZ]ˏb( Ft4NG(KO1R[& r#dy%PdE?Oloab)1 R3 ׭4tz6YG=X}Þ;GQ%z{c%cMG+KZ{g+B{ +UX#S^ye=om"| 6lŎ-u~!nIHUOL ]pw6"RD׈Xy@` -h0 `+'07x,p"H C"nmjbAƙyeam[H8ǭݮe{)#|e0S)݃^erť Ti 9#JC4*HƼA/nѿJD2"T"+fK~UYz,HwǷ#"aRj.6"eq/M9{'C{l6 -8Cq.%ϞQoLiiv[I;Su ~^u2Ӹt%S=;kzLOhǼLVL-̪gew}Y6Ƿ.j:º+[b+HM93Nt-~% :Ѓ(&0`;8  a@bXЍ~bt1E̲E!H=Kp /̆"bU?)ſ)S6קKaRbQ~^OhŃK(O~N}BR|hL A);S%*8>I*C}F)ču͓P5 dn?)>E)f<&[,7:VuC?&x-(9/zC$f lAR}~1z)wFjn+Nם>S0eXy<\E#iޗK(Kk'c-jm+7~M;z2YP#[,̉kc^S4a>1+}k* -aZ5MyE+w0p\Z Az04fl`dp<8A$! B ~ěD0DF6{,.+gcCD)\X7l\RDRR emK񤲃1Ia`Lb`3EOiLz])DRlUW2S(;)0`Q; r|ɐ"rbQ@:oL#//|,L#kRyx/-Lğ$':Y[aB2YPhV\{SzarWڪ/BI~b&]XajeXp8[[벎r8kU3]E%E9be=zU;ybH߱J݄ս{͟FDOo';vW&8H-=`3X -6   D!C^{31C?_T>}NӭXjͷiO8A6fns^Hxfd w7K7)HE.TJ7.V"3ҍIX\0gn юxpn4̎)7*hWƆ!R&+^c-rb;l H#yRkOD7Rʤ{aDmQ} h`0 -C\~2d^>Q x A|"`ArEuYAzHm jt]ɩL}\pXU_Zdej<ükuˣmێhV&Mʹy SJ}x9RmaksW+ⱷN$̋t]^z- :Ѓ(&0`;8  a@bZ$ҷ * C6 fa-"=gw>.y|>& IR'I|EexS)*U)ņӥY0Jq/"UAaFR -V&s*أZҥKe-r0J=řȋ6@Ԯr3Jh:Ez>L^RȘsS -z>༉`ˇPvA8&  5_9 eߵ;[]vLmxA۳^bwLm[wƮ3N2Ϝb-GN''.u|9|1Z% կ k ¹[ρ'4ʷаumOjhEoBWh-ko< m{Z^&zjMMwT $@#  ,p.p|< 2!QA3@ BJTAo'Mi/an#,-zўm&&…`:@mAR3!7?h%+O zJV^R7ed0$t?s˙vA8]=CuY+NeCJ2d O>+35>cnL=Cj dp:|  #d } h34{>C`C^07Av ,,'>HwcQ.ho=Bnl+?Z@Π=Hѫ4>@>0wQokѶ}Sӎ%'ʹn4.<ŕ'sOOBh~.|rt9G^!7~$/r)nzǥtP@L` Xvp@8>`A(ipMQD -l:"}p.-z¾ D.̋UbTb}*ȣJ)Fb6]˕}R!O&hkQJqp*Hġ.)[{TűGwG^K nbi̺!bRSM& DHHj} hCVN !/OW o<O^k_X)-(Qܯm5"Y[MxðQTsoҷƍίG}_g]VcxqxΖ"s;\{wsyJJU uM߻AB8ݕb=Ծl%;VND^ADlLoq @` -h0 `+'07x,p"H C" --O wWEULeӯjº#+GYjv-$R%`W\)WB!VR !IL~dQf*1SXUb1M[01徒_v7UV3 eLUbB(ca$Q2Ⱦxȋ>~1.1@\)Lz5MԿ &% 7UI!) D/&B// -8 z B B*fNajj۟#=FW>67SY%fs"ebZJ}Z}Fr>RLd-zC\sk4*)koPJ1_L-jw((E>γbX,pz)Lz6 -aNc&UbgFWS.Lݛ$ -IMb3ݏ\pEe1S+mo}l{mW|9ְQԁ9԰owrl4}l%um 8⴬;[]Ͻr~?l&|]_o&خO\; ~Pe^}m!^o "-v28F-=`3X -6   D!C!ec%_H.%-#%nSa!,-!儣25yT=j]w=2+!*BXHDrb?Q[lJNƪ]eTrFN?iC\}}#ԤMe5SfTS&KV;,XNrzs-jdsǤd=;rЁT&/%dx8~˲GAbA(ߛ ` ]4̽障+5Aa~5j҂T<3'PãMuӬ+V3֧GӲ&AfIy'9ސkIKjߴ5YIL,nEH;FMm&jRz&8F-=`3X -6   D!Cٷ/20M'3Ms3,37fg:nD?dntgl!w70K/VyOTʕJ4!wf'?{uqh]Lyx/J5TTaQV+W:T1g%r3KHwaCզ2\I~&| )J~QK8WnПφم^!/l(D"_%m=zJ3Hxе>?0x>xQsmWӬ_bv2ϧe9{Mt]ρ[ocPF/^+ŵ&HP=^jNDW.1j`VN`nXD@0D -1{:-N a61۸02ϲ,"m6Lc)x,"EgvVyWL tN? JE*/,8O"Q*\=R+Q'{"cD?C6\lyVH1Dd0"2o O!7DhJ:}wSo6ɩ+V+idx ;dA0D)Fjwo5wX}[5k󄟮9=ʚδ͏-uUmt8\LdxgI60#L^67B^D_z} l>(zC\!4S#F(H -Ϡ*s%US5r~&ְK3u?wj;k17[OXwȶ k]u?ug[yLm_+4Wj'4|;-mՍbҧ4ipQ*MzjMt5tP@L` Xvp@8>`A(ĠgOl;=BP awYsf !RO ^Iv}Ui%Մ9xUT"TߕYŠRQè r`U׾uRoTRU<3,C[aT`g -fIs4C$Lz ɐ'B|V` y%8l^pʦgd̈́X}᱆o&uyu~_¹:e3? -?Mf#<] $rOr`=0K ~uo~a(j>jôQMo9,eˮ,v3Ǯ:ùTYLש"½g϶7 _Yc=n*T" ̗Ž{vE[@=޳t;YY:=ܮ @` -h0 `+'07x,p"H C"Ж"B?۰t a,"ḺV2^F8f$0D Kl9U|9!R!iMJMK&3Q\"jè}?=XL`1wLLeyTrEbs4"7M:;[lzgHΤMxZ"K9!b/%>cSg)$zyƓq%!jhEzW84R YFݾG)}We8~:*{.]5tn-eYtѶ޲ vY<.kYna-]G]/S~\<3Evq١R\xnxS";>m"}K $@#  ,p.p|< 2!QAPDmJ\DjzxS؜k:6>5*wnSL'p,IQ9wЗ{PZ%6::G%kq |_|ϱA-%(bԡSBQ*hUU[;s#, X6+5u #OL*L;Ila-ostϼj%x^?& ) fd0hZ>SW,䶹u%g2ez?ˠzStOOqU?C)U.)cƛp wҕzR2M3gbIONc}-Q_K8WF6X| c`x>+zㅢ㉠k4KόGj76[cO--SUԁ)z{eLA (KiZh?Ўw(7,c׉}#ɳ*Wd.{\U4+}vqiDi[9Wr y)#K $@#  ,p.p|< 2!Q-}wG J!æ(JRŧ"ø7GyVE^<ԫJ)^TFkTJa"ӥX*F)uR;T.>-Rv2~RNJ)إrØ)F v -y>i:8ғ߈R<f))f0)A)֥+Cs-ǣlț)o dȟlbLGc*lE)b8Ra~ jLLcWo`yי݌uۋm?LqvT`Nmk-LڀL޾B,~!-n.7oBh}.vzIpɑym ǥtP@L` Xvp@8>`A(RJ10]+>3R1Sp1>ƒRX|9C?֫\R[TJy>.ŋxJ!K47vsec C1U E*;P9*/cbcBaC(e9{3w,BMX|$'#/Rb&/avޙzRiM/zrI>($|?F -.$ -&E6#EݷcNd!vNו/ aO} Sއ(SKO#EŮѶi/mTp8jzWv<s}e+_u~3BGv艉9CyS5ϦG -HЁ @ F0,`Y\x@ dCԡL.pVgY}/7} sn-$cP4!/@RiZ|<>#`i߸EZߩV)keRPN>2R~ZqoNR<{>Jqi@摥(T&O [408_K8G @-E/ǽ|0{ Aog?(p@YFPzyu#]_YU_5}2px(zɷc55ykk4멖qƾpxzZVAfsλ}f˽ _; ޭWNo|B\6^EH^!O6"r5"?@/\ $`VN`nXD@0D -1Hʢ2PFP=XA,ńN}!\B0ńԣы ߬9B1!ΒfĠoI\Rq2nU" caRpύ 1ڨ~RLe]S"^CʸW媯coWc 'E""''`8ŤϰN`M,!CRSPau6%mqs`/H =B0<SzqK *gX.Ytؚ}#G[-m ԣ2n[Ja^4?Zhkvl;.ʹm ۀ[voFt+~914~^iTظ.Vn(ʑ䆫N6O.}3=nิ@` -h0 `+'07x,p"H C"yKiYV&aH=cؾN%>Ǻ׷.~%ϪKQ*]Y_P$`2nlabmѫԟdW7g?V)E2n<1Rv;ߧZk YSRSrSy$/(8o/\]:!_ X$޸x!fCOFҟ)K_ZSnYn2c3kXvLnӛ^eMUߘ^[m.-GwJ'|syڵCK+4аKZ^5EM7jH={iSׇ~Jk&zѵCqZ Az04wQm}qƘƘ1s14$N&0c{׻WݫSW{ݭꭵR""RRDVDkيZZi'6#_Wg̎<&0`;Аp 0< B( ~-QwJ5G7v]7яC1n%b&Vb"b"?t0>Gg݄$|߱JLX\ ȻT?Σ⥺y^gVHwPQAҡ@5P0ߔO3!UZ<(Ѐtfl`RNp<XDA"Hrw=L 6]y)# -bo*"|b/ t'YL\yKG'2EclRq0a7%c̝sV(Kޤ&oZ 6O9L>dJDwaҳz#,K.F\<96KNF+J%Ju{ԅǻf5a,X0##Of -[9oc@zZ0UxëV0ǧ>I+E¥Tm*MF[6; o2ƪ1抇/y?Yʔ.'A!&D#b/ DɩR*ND*=r\&Y!U*6$_ү!{0jZ+ ֠FNSo.={:!+f99Y9,9;V+7W|ƞvR[جҌwu1oO-~0c*Zsy?wl5#wXΧ7Y:|֮W*7W1-7C|z쫽EEPYzUgTuϪeԟ)UЂ`#  @C -8 .p|  !CQڈZF!J'vb#bw>J쥄b8k*SNr5D!2޿*J+6Xɒc9V~+g#VH>Jc|q'rR%"J5#U̱#QG.֖8oKV!U^u Ux+4R%VT -9k"K;B?cK8]C?+MI2K\ZVWS ϧf.R*KJωK/4%~mӌtYc0߸12ZMH޾+}"}ҽRGΆJzJnVwUjUlIRezvwL,}]_IWE<Ӡ|!Ń hAz0L` Xv!>`x@!QS)}DLPKH]NO-*JqSꉧ>*3.턯! I90R -r*WʩUH籞;UTIv˕1ÃʗKZe* -)˩"A!Uc-ZCy'*ښS)HW*9,ݖJ~EE -T<茗pSw;Lz{3A/ 傁@d=ߤdVya˷Ж4!i- u]XiH!mzi`|b mh_fmݱVWotUmRю®4gK+]{wugW].4NDآW'}*afU/P[Hx^:$< 93 hAz0L` Xv!>`x@!Q(ŧHJ*eJmbAO9F -J,%""J”eD b5 Ւ9 \!PΓ -%E @Y=2f{@9P3Dg*9Pʔ[܋ģrlWXS6@9F!Pny(S(;eJgh~"7LydeaJ,)Br3^i -6.l?Kw[Ӳx|jlv 3fʞV4?/>Z|"PJ(/QۈAZ[qr}p⯴qۅhKeEBJ~nYJk Lstt*2p3JJnJMme+(G7#P^CV^3 hAz0L` Xv!>`x@!QA|=QHRFDW"HS򈹐X*2>6Rni|vY!f5[KD(%b9 ]d^!O(;r,Uȓy(4I$Uyt(&_^5> -}rNQ:?#3cEb>/H(Jʾxuq( Du}(P%B]qAɟf$p"Y\q^"ȓ?`ތTWsr4vv7 =h{+.>Y׎metߚC~gƊ77fVe(ڞl#m.zmOcKml}mORqwiѽAŎڞo[۞R'JT7WQE&N)Sj@ZЁ `VhH8n`!a@b =VnBhw яC 16S1wKc3rwzH -Q# vF"Vm$7bZI!r'm{* -~RyF;N9X̐;3VV˱r\!V"I˔tM.S{a+ 1)]߄L<(SZ]ϦQS/8P;B]ݶJPBc' MLJkiǗYO ,5wKjcR:-t?,\*=]Uw e@ʔQTpC2ALXu"$]$$R/c<(Ѐtfl`RNp<XDA"HJ]*Jƫ5PDyĴʼr%)MV;KH~D[\5{QIdR3+˝GcWϕ#^`?J~ 'k dgGcS";;>i[yar/oJQ9S8(O47x kPUYą ҍwL2Xo0+Y-ldoRU.##_YEihwN/ԍHYh,84d misq"[c>I,QzfJѶşiB|MJ'"GF7Dъv` -Nat~gTЂ`#  @C -8 .p|  !CQr7 F~F[/ 5昮2 @ndϡs9 =9,&EX.Z1gk/ -!4&6Z!4#4^#IܜdI#fHW@SX/(!9&ϛ"s v_ѿ'FXB."(>H~ Qc0%j"TVպ/t 9L~L0lޛc32A&eJdfe;&G'Ba:4jl3BcvO-M@RO]hFho[`@[_Yf-zm&I,ڱ/ٲ}kS+ChT#4v?hށXs1X}0фصQ4Bc}F>F3 hAz0L` Xv!>`x@!Q)eDOtD_D wWI $ruKNęO\k^ϝ҄BT]/^5kYbd\{J!FΒk"G!FnϬT͏-\1ڑ|9DB1bRa.k_˵^!FV#F*0?C#zK.؞=1r#-Q{,+@Ȗa^*B]qo<~یp% si,٬t/+fqx>M9}&=O1Rp4jDwZۖK\=@Ҧ{iKˬß/;9e)Uww@W{:!rDL1V{;6^)1REBm"Fr;_!ɡ>ʗ@=&0`;Аp 0< B(@z9H[eDwt9ս\r;"$s  yK.VZ ->Bhp~ p Hqrن<寿)mMz$9A)'H*y|Y7G!Z/ uH -TsvRٰze;iް 3qDٸr>G"_}DC hAz0L` Xv!>`x@!Qw㖥B$h](C0ֻmwI rNg.Փ%].s/{/w;Z(&bIX"}fy^UUYkB3\BGqfB#c~Dg)T #?֭S8#,)Fj,O.ywү![!A~D~B~jtK8MH "IcY7#ȥ{9!{e `2r{kRpD4pγe͚x^Yʯ1NMEG2}Xoi8k8f3?uإw YŻCwE%,a7_vOw_]+Fl._$|`H[ɯci# hAz0L` Xv!>`x@!Q(5Q 1l͂6# va8FB$FNayf&D!)BSgfRHq{ެW}F\Iu8fxki{WDR*$Q4y "{ʵkU42zWHr$u0 ү!=x4L(u(eݶ|K8M|4.(x326z&՟22SYI&7(qԦ.z4ï1jַ_Sو,z*O^w:ك{| -~r]ؕwXuQ .t:ZXs hAz0L` Xv!>`x@!Qw#SSE=k=&gtvr.;qQY1׵Ro)L#!w*ܩTR(yŵƤ 'VX^̰}f{9(RL2%g -A\~ MFu=g7E1K } R#ŗ?b(?¬ZX*^ (n"P~iABP4A -AaAӜ.ky1).p#C?٫Oi|J>X;{CRJlyy_Xw(*ÔX+P\Dx ))RhSg#)``,ΟƦTfvܤrcηU'/Rt=fF[Z~e֗w2ƶ"T6m6Wo9~t-8|p^}8H[KRv4_}[{$ {Bkٽ^kRq[WU -M= -׼A_4}  -4F0,`؁p\8ABD!cD}j{}71tc;1usk'{ͩûI\C=D< J0v·a&| -rJ_XI돧bbrYI$9sp[̰6ͧ[?S -쑗?)A?^S%1U.^|ݥ4b~t5ȃFˤvxB8rCY1$b"z+PŻq6SVjbXԂ j-ԊRkW;Ċ+"""XJGX{u'gw(j#]YM%%3f,Z0 -gd>ߥ/}?+}qffgd/\_|gf" q;_< 2(D!zRb}16)KX󉭈v,qNr-q#۸%i<_#IIIIM?tyw\2͒3g~ԳIg~͠~ʰ3?g~2z`~e|zlrߵQݙ႗| |W-]٩g~I}fw]Mw5ߵw]9Y請hߝ;Y>/ifhD]OsHJm_2! {"ddKY~^ eӳ29BFzvP![6S 7g"Хy1hZ>p/̶ glL͍/-u͊rl?' g:n&M{wᆳ w:*i:+XOyI){Q޾gHGͳTtmf*֭ꀆ$Ѓ`&0`;8 .pp"H a@bJ5I7.´ۉ7K;V.bo'N0$ijx_ "5Dl"R T9WMNǤD*I"UBkJ`[7*UTMzxXy+c ~X)&b%I#VnUc5+\?:V_XI+!VA@\be}RBK# I0L` Xvp\xD@( >Dw$+2Sa)ce*ŔeX([eϧEre(O=F|$ppO ŔOI\H)C?̕Ij"2Y#W.W\j8\94i\F䎕+mȕT͕o]I#Wz(+塀Fd˕/rŏ\ї\y/Q܉\D/}{ʻw"W~s3rG<+ɡ;t.5[f31/Lf'd'HreT:ɕ=St5;,G$}7Uhc<:[5SŌbƲciqWg=}nge:`qww${:]@Ѳ3ۥcɃOV Mt~ݍ  I0L` Xvp\xD@( >. A6\k)b"[6.;ƯW{"^ \I,!MIr¤&e-4Q7RʒMT @`#  '  -!QA|( D/@mXML-J,=ZGl]^Kgvi%$v{XC6"wƊEU-@.ԈNZ׈  эMֈX?U|?ƞ?2UU돇5jaӨ?3T9Wc}e3wyo˙ƿiX? -=㪣az1(}O.N4nE! L#5}Mjz??&;5]LqR mm~?[I`$ꏧ~c]Ϙ&b,CO]j4['h)ϸ~dq]+xK~=c 6;.? J"JP_3l=)<{6h{3l^rbQld{eGnr Wߧi7׶)*p(.iov+% TSU]O[V 럢^|?mB6+ I0L` Xvp\xD@( >=DT:fa;ډXupNNg/_ .{F]D rQzTkXD#VjZQt -wθJO3V^c#Se*4:,TFPTT֨?ޜ -Lc U^9ީ\?v߆Nߠ<5ީ\Ё0ʮ>t*?RLo#f/9~xJNrB!>}T><=Dŧ[StzӾI+CTTdsJR35Ͷ1w}{ֱ[q eqׇ^?'0n1/[>Oش"*cڞ^n{gRV?'\yZxˍc57?0.Аz0,fl`8n|x@ dP B Ctp6pĆS,XdhP?f$.+,R@q5Jů4)5(AFPMB7n)]>VR|ꉱjR]Iy~")hS;&bFRv3o5'?}eu{3I\ǎ_Oz0-W#JmӈpTƯ?T %(+'ѤH<|Jg?6SY]>J$Stw ).HvPda7~;xrd9:V|wg'?r;v2wigHmi}Gfn{&a˙⶗Ruꍳ9ioӢ=' I0L` Xvp\xD@( >exM%M!ef6~&$j5HkkkĘt|ARPjR<Ϫ%EF'sjRT;IJ%쨤X0") )ƚ87_jRIqFRlVT&|kIJFR j'#H&$ŕ$EN Ikt*#)_,?Uh!)+F<\I ٩ٙ:L$EF(#s9B(G%E9jmOon8HҞ) -M *eGd &3e [c_ŘM6W0Snk#jY*'={Wj/=)d_Ss]p}s)VK]]/o虭46 K쫽1!hH=`3X -67x >< 2(D!uN$O6PSnx>sN7{>.弼]n-{¢& Iq\)FR I> ~^r޸<۱#"G 74it_0zJ"5(|jQ6)ԠcA%b)#(\s?/i (V /v A| ;kC8KSA! YtΟ2)3SF/"(ZM_uo"(N|0EW]mk$# &31"8cZdseU4۾Yìq`qMؠx='659+:5_&aoL94vp{𡢴HhOK4$ F0,`Np<ACPd]A!eȬle̢EFQRr\PRQ_8 s" Q1*͒Q7AqFP|.h>AH-)@I]&kh.ƴ76h$oO4I8:6W{{|4罊3$L$R"$-Oɣa#tעu,3ĜlAT2&,dffpmHӏ|H]St+f\k`TCg [Tid|P4ۑ/Y{їcngϛk6dO7Fտ;;lh\K7 -3Mvig+}gsL :H -K4$ F0,`Np<ACP8>CXGCI!v!")8J -͇QRp/ JI&ح&EFIO-)v%j&[/w"yc%E=LIO#)jRLHeOդHcgſoמмk-3$3H+s.㟢BM%B_YEM$E0e#5]?3=9$>OOIB0=%;#ȋ|֨8V*.ˉn'j.}=D<I:CI*e6vM5cZ75WqXzY{޿Ԗ_ո;Bo婭w -Nn0~حu" bY,ȝ{_!Jsv.D"JO0FАz0,fl`8n|x@ dP B CY[_ )!Bb'L>aWc1-4/"֥|bױ񱊸jӗGE[I{D, R!eWFIR$y6&j$H_RNc'<$;o6"I&IPci$v'$15G$&$vK#Zn/Ϟ}K#^$Wo;y҅$ N*_3g"̊Q‡G<$kPSgBS2C9!9=34I$7SlZ_H t -"Ilp; {Ǵ`vW8L}36;,-}7vxksz͐wy[}-H$Gʐ$_"IN$).&Dt}$ӈ$xIRDwI1ꀆ$Ѓ`&0`;8 .pp"H a@b{PL;إƅ,seJFг M/TV1EfZYWb Of`]1o c86|}ysm)}up;K15oZm%{xs?}O _35/Ν' )V_$Y\lrۜ֏"ݚŸt@C `V^A8A$A0D -1&k1}=K|v#b̒%fl>; /nѣx#ky#RLIR"Sg٥NnHsդndk$U4^0mDR܁xh/fYIWg@M5AZH*s")~1c.${Kȍ۝K++oM^菕1'&3y cNN6xk{4{c4ǚ:¸ߵ ㊷jH8sw|AQ;*I.6DhWےἢ9yH,(1o)(6Q4ak:qFPƿwU/{/q;J]2Z@|>A}d3j̦lu%MLId̐9!/e$0 -e$H7+)nCA)7-$^73㓙2쾡ƃ1'2AiS۹f/ji{>Mx{>}ykZm{m.q&bzy뛳ƚ9H޳Aq逆$Ѓ`&0`;8 .pp"H a@bϠPg32+y ({! x|^' -NEYeqET A1M(VjquwFZQY#(^@P?kXI{?4;խIG-)ԙΣ׍N -/xFRLGW2}\<;Y/Gɲ%rSoJ ]#)^h%o OPb(+I%);՟-rYlAjXe3-^8quM/PI{9}Igh[d+{9c|񠥾)XKV+MUllU޺z37k_uοwhx`dRܪ&MݩqL`_TۖyGlC8[G_?IPVrOIH ,$C| -#i\S-y Otkvu[^W8áGg^ AfT֠zI)g25fЪ-sF-^+)w>Mľ/HE)yewG9.mDE:TR-覆c:! `X0 `+p /  Ƞ@" ݾx !MLGXN{q{xN@?xHD>Id̉RF!j60-S?Jj[D?*{sL=yը?^PCbVr5Uj4R uTll"*'H;ŷGO6J_8dC8KZK!d)jx)/e?=S e 15Y}:-'Heit>t5yA}k`̶jVͯr{낖w^d{ZQЍf'3\eOyt5ߣW -T[Gq-)]t55;m]t5?I)C%P#j_R T4$ F0,`Np<ACP-*mDE UJ'|f??㥠3M[7SO]/1)],I3"P50_ͰC11=6|ArmR?ͶK%v}qdqW7&{(ަd_K7gs+_I)mK߻NȝDZiA8K4$ F0,`Np<ACP#^4( ϊF٤yK v#.d,Oثb壂OV -QAZcRdz "&׽'PgJIu~j[LSlS4oToBc:)/hl*]4Iec{G{O7)q_H.i@R4(4өGGh(=oC8K#C\fv& - .9'ՏV&ٟ.׋)YlI飗oѾl}K潉źCsëѾTqeaGfH~i{ -8P]#USSeZTgˋǨ?Qt,FrD")dG?.\ߪ$?UIaZɜWl]#/Jx -_' L?ʎԌ.w<*nC-NiYԴїW0ӓO1l*XUOO61 pa^QD=FPkjPt =v'ꏪI~Щ4i,dj$oNL?jL~BJ")hKXQ")6~ v -6/Y kgVRfe")Bx푚RRGOTmZ6%&:Z^@WTް$SrR| -Ip' wapwI/A{ҹsI"H<ƨ@`#  '  -!QpEt%ңWJ6 Wxn[nʣxWqb­"RaXHD.'Juyv3i|Z=i''+*<Խ_j\Qxl`y8q]0ܿ L|\Z0ƫ"#(<.b/77D+5"}ɕPxTY'ǥhg]B⽈G<3ć?p !)d 'C-PzT#~Z9ź7;z:8}cg8qt˱%qs;Rz+l'?J7:z'p>׽3'ow$I*Zz)RE J}# };RJɵTx`F*ZPe@u@C `V^A8A$A0D -18g.o$T[;X[o^#~QӶ55CF"%rQ^9+Ո߫R3D#VlfJOb쩑5U*)]j\5TY?{/FdHJ)髑*zδ US)]YaʇБwSάΑ_@5"3r2SBJN?5'K9Y9!)'eJ-xYbg]]mit7TRA=aeɎ {4h<T^yfrbnTl;d{Q[8J9W[K;)*i$ޢr*rkڱ;{=6,UהQJ_U|ȋȚTt՝#hH=`3X -67x >< 2(D!/"3$F]J;Wke~hJզ5ᝫCn"Di{Eσ^GMb5Uk}R_6Α1~fHTyOcfZ^0$ɯ&*^k}s%iD˨UޞTrS*k#[HlMəl! :#~r&5ݟHIS,1s~5ozl%&Iߟi/Hz1<>Xΰ+R4dՌf٥֒iii3-CǓ=>Ue_sp{{WvKmV:7|YtcXcb I0L` Xvp\xD@( >9ïDY32G-5<|N>b7s7SQ. DEYبE ՠV#(w'gxA^"q5揝bL -iLlTبNNSD#)ic:u>K{k-g+i'w̗㟢cM<śF<Ռ_dbzNfvȟs~!9IJ ,>3s5G[|~źC?@%p9Cff ;LGz#MOT( ->Nv O[g֮Ks7xz^ADqzYGq;S|W[(wG7@*)J_$t< -|*761@u@C `V^A8A$A0D -1KZjL&bl%=fMĺض{5qtjg݄KV"T/tEcD#VV^gi*hܮn3iEq^?`42USSe\*5RxY]1kʥ?v1O˯U{[Hc\Ǣy(}aF切pvR_BOE)9_eS39?)6Sm{^{kk,ЫNLկ~dfC apqmmfkuԼq-+M9}\>;PR8kwQ8Z1S[jN,#TзH[Tt- I0L` Xvp\xD@( >DxfVB,mNl{gUǮYU IK/Ѧ_Oô|# -(==lݱ>2RCbF?9KLKbJ$#59+;#9'yi7SoM"@w5Dox-TCmy;a,.pgn{ai}}}ߥsۇslsZBҞ/1KtXb{q,F|R=ޅ!pBW)K."/Z׫!ҡ"4މ:ZmB6L!rBԏ"o#Cd"WiOmbtt!>4F3}ʗ\!iOOO:` ӑ"tlrC8[-egC縬,NF52:DPn4>IStk-ޕI'NLoZ'3keee9c<>\]XNTNme{YGsn6OXeEɞWx|k@w7pUO{n+fK݅]/ܠ׿67R<-zxyt@C `V^A8A$A0D -1%6ʍ0eg9l-5b9GlGJh@¼"\-^-]+_DƜ-Hruǯ5£&Ezx'jR&N/bC#AqFP0jP|1BA6َXۅK>AQ6V)?{gKf*_iDN#(lGG)ゼF$]9.T?I丌7 -#{F莭$)oCya2Yst+_rrYs˦{l >v;zkovvwZ$} =oީ$Ppu$7Xv*:e;HxoW35^ u@C `V^A8A$A0D -1e ѕˍDo|KjaK -Hĺʈ8Ľxz*?[@UDX,!j"/(VM5dZw\&&ԺêQw|:Yy'c1ܦ'_͋]x]Wxs_}ߘmvpM3cإ=YNߠ^i -r_gt@C `V^A8A$A0D -1ţㆧH(l1.K-{<ހOp4?7+4«CuwYu]ݻwng0]zIBĶZت\K0#H! i1b)cHӀc罁9fs/əg|>ߟ? an8{赤Ӈ8\OC3wZq}%Hd̯SYI{w: I1I-ߺSd~!U[QQe#\WBHH} P1pzk>g_R8°;iƊ(SQe.)7Rōݸno )wҜ翢]{՟3Q-gwiWK˲d z+c]/LRδd7gE.~2%zؼWm`3P@`;8 .p< 2(BxJۈ)jV(Z+oSCur._YV1"񲤨s|O$D-)J@g()ːRf VR\(ӏЂNPh68nLJwu7{PAJ*#q9}A1ՈnH()DS##ՏGɟ\#GFHs)"  QaHR~HMcUn;hàGGN~Ē?Z" 'D~JEHHQ2q?}k5L1i'DՅyQ. -IQXQ,^y]>° -5EӌP%ee*ո,/")L]zEM5Ecqj'x> )8~R;6gqd ˦1}~rLuY==S] 9U%$f Xvp\xx@ dP@D!k -8I0sHsER)C`>ُXH -I1^K9:IqIK -^'),ZMqN)$ŜatR)qّt uj -IxT')NkCM}FRlk>9ӑԾUے5)IŕS$EZW ݋#w}ka*k>ċ)=#@\¹:ݟ_.ǿܫ->°Nv'[N4uw4}J~1=H_8ZӜ]m#ڽs~'}t.>_ftO_7%,=#V?%=9Q9];Ym^z#;z)ڧ>^ &04X -67x ,p"H *D -1婏qSY$Uv!;W^# Ʊ'&ee:a{z8StoWk5M:I#).C&EX2XRSxX')hNRصqiaL}pdE*KGn$*F"ݷ_&m u* Ebt#\cir0?sQB @^"g ѬFMqiOݗmaX?NV[hlxHsW{*Ui)tǩݟ/8/OsҮӭ{G [bgj>|of_戝Qҹ+wMP?SYٱ}#x/`,`Np<|8A$A"!*\89FEE=P\J>񌂤Y I -%:5E6Io^A pxc,(RYiAai>~ YOm()fU}utPRTUOQR1!BFJcs#\΋F|0_DP/bAr4C:?8֖-u}+Z^м%D:0sVߌ-/-We;涗;Q͸:wSTO}CL} |z;YONgVEk6'W 7J?/]o]J,H0 @  ' >  Ƞ -B |ن{L3egYfYL,ǽG\e՜0}%:8WNSbBGE2]- :1N+4b:[%3sÜSoy]J8]ߒ0:QIgtVhhNKR<0aQdǿ|e5{RF~L+ql%T%RMF[kВ~GV+9^@"ϻ|VFݑ'\0> ?6[ڭuEoG7|"W-m!M'ԙ7]RۄmGϹ>ձg.>>ճ!fMσCo}v6[6:OAvfnGɚzB6"rqȏwNm$f Xvp\xx@ dP@D!qH0;yu$aZLj&=ޓp '?.NH1 -_LGrBxT|TzH.& -B]<u6-H2t$_ &q ? pnGЃwڢ4,urm :ĩӰ|S(hXf 9NPQDk#{~*C?EHyRZZYa54Q%c(IsMs79dw_̱3տ®ؖŵŗtd /NŤ.IʉO3՞YCss93{A^ &04X -67x ,p"H *D -1CU"WVaU%xd1N5-]iX~9q8/K0aa^'(*Ⱦ3e%0ի`'tU?ԂbgP 3ZqH'(ʴ1P^+8t0@_,{8;OΖ'l_.(ɧ*N! -ekc>5ډ&]p$D(?/ @^"\(̗PAz-j(;wkؿs -w)㯧Kov[6~[/d~s -s&U\k%1-` -8WK+G1̼.Jxl3b}k4zj%$hdpP8@n8\#W$P+kץ|:50TƢ(uyFjLYʍ)GKcv8OW3ʧFy[P5nWo+S\LȲlMjDLu_IVd)3H0 @  ' >  Ƞ -B pyaWMYlPvuʮI5L']IT1&qȪm LZRXtb7kS+ #)PW;<1XPcnJ9:3+^-(ѩ?ZAi;a9:A1?xoCѭWT|?\xQ2bpU((2YB0=ȅ1^tI* :A19v# ɕM3=E?O/}JAeYPEYKJ5q欛.Nsxb)2Tg̖,І,XPbIIJL%+͔Ɯ؎-(: @L` -hl`8nXD@T@bWufh"[}6Kp3WVkp~/p,~'~4*TjAlt#10(U  FPlTR,(ΔAq _k -qPKOhSQ?NP|oxx?KV72zA!(X|'EE** -uX#\]eYn8! ʡ@[M?  ܧZR.O%Fڶ֓ e#AY"ec1;ӜPQ\vl]v%ߦT֏QQ|Tf eo%9q{K霨<.DEQ]7%zXSwb H0 @  ' >  Ƞ -B '7ri"YykWSq=JeCPȂ,r,+ -;^-(~%20(F«X8dP${\1衟8?NRԖuVR`Ku7h֟Cyh7KՌ, -[7æH I騒|G(J#)Nv()Uy-p( J+I IQhUvиx!e:TNIN,#OR(kTۮ5Ʊf}ǴP;>Rx|FyUٙK"0Wa>ퟟɗv}#֣(}%9YɌ:Z>%VR_R @L` -hl`8nXD@T@bcރCA6s8M -YSIyS#IT-)l:ch~n=ڵ))FR]*~AE/;%-)]')&jKI -V%)j3:IQh'7Ij/8IeZ6#)#^c$| -)h#7}kuB07$e)HՇ<1#/ gz$a֚u)Mݑ7;o6,X"mC=6sE(jz7[,]X;F^qKMuykyU޵hgvwSW񠯪cKg[µ0Mxj|B*>[M(n%s DӝDF"vb h`3P@`;8 .p< 2(B(L7Bj0ϢJ W َtC#Gy7pxTLȿQ - :zЇt:Gݠ1jq͙Cv-: جSaiQNxq#E\@Љ^dȍW'\a1#[rSTA!Bhg 7~R#\ -C]mFPk$sK 7+:yTߌ{W\p~a{vq4ƍc#e^Xnj(zzrqe]S -c;e/fZҜ{\Ei݌tά,wLu*ƗUYe SŪgcR[T"[?-ݔoxe`tE)V^`~`AP!QA.oT`gOrD{7_\JYQ&Ao*QuNytH uuضZDd*2NiIIPKuF-KbE6#)~3c:Zc=G/l[ ܨ")ٖN%y>g#7?}kLnOy!HX ʉ@a2pX= Ѯjj簝lof\25JW/1R(EҴ=kq,[,͵t~zOQޕLy[أ/eŵwd [͵1i \vpr8KmGVsusb%%$f Xvp\xx@ dP@D!qm59"T3O4coFSunψ>/ -z ZYU]9Izwt[6R8:fȤdݽqzb0kI^/MNЩ)2zt0z<͇ы^WIN$ş(`Sdh3j39uN_Fk1Sa}x |S1, 1dٱ1$!dqqyCokފN"SŰBjX+bTܔZ+VD(ZVX Be;DDSY>EӸ=w:]CGv.{w7I|w͘9wgΚuGƜ?ΘqY99snϽ3+nݹAwf̹9ssd|w9w_p7۳osR`x J7_Oyݷ3` Lgd22BR̿)=m믚Y+p,u7g -_P6M% t*96-0Xm.ygcϢqEؚ.Iٚwb]]ȳ {OjjuQ(v;ʧ?KWz:G -Vy}}dǥ - `,`N`!\xD@p$( C(hM239fQc)\[DHG(HUȨبQĿ3zԏg_Q 9{f_Ifg~08Lʙ߳Ҧ]CS*_eH[FtQ.)FQnFEtQ1hsO)9Ctrg59{\g}ʙu<󳲎jzWu<&*3?+c?kZԟբ{O3~Y?ƣ+ϙ棼U8I{x<:^7&32 -u|} -7)J bԅ^%E=( #fI?;-5/!s|f8-BBuZ]?'4oۗ K67װr m쭠M{ tcl}6/~.2X;Ѷ9d;2l{% /p5dsh -y*ުd_wӹ zos)uF^`rHk}3c_&8.=P`#`+p )7x >< 2(( C}o -/Ùel dQVB(|n#yc>.D^DQHg|59j(SC I#Pbt2s5B<mq&{`)~bmKqd)h➇r|];9xfv)F)K֟)Zb,%(/Dѕ1jm/"4|)$.-(_JQ?33 ibv0'ѥ*E1JQޖd)HFU.5?xtm?=n|f~?\^H[N|7ں|m?Y3αcð);Ү6wd_WQz0+4/?tU7Iz).F>4M9Y)}F3c˒q40` XvpXH^A8A$AD!qHʵ8J18f)dbSfb;"qq~ -F -QɓYRVc(VG"RbZ{4JFPY(EjJ1y`)h(>{G5JaPKq%Y5JRk/(PoRLdQJqGHR&9BX1S9y)&\xpJ! fffgdL ԴܴXR7%KQ3E~om\mZ@O eX|{m^(qv|ΰ۶S]6w+!UV}#=іu|3Z}P4I<)w\#T3=R:3Z؆=R@`0`;8 , /  Ƞ@8$Eԋ(ŇHKX|vpzXyxB 8 -$3kbɨd)(j)ZK R<^ kP3ž ԁO-EF)R\Qä`x^RLq廅C)C)bQ>*JPRLضYN|R@))>NQ%R3E07(ffsp_ - -$f ->+#3;;sP)J׮m~5IxgX}Lqk^_ZN^`]-ϊ3o ,ȭjA&i_,!ܩĩDQ+FPU Uƴ @bU2 P\/ fȉOQ|Y5J1G-3$Yϝ7l)mqPCMݏPV7:^38xϖ [Pl~P#N~G]ZP?PDF( -"MB("w}OG(PyspB:!qYP?;7$"b?- -PPƑUkuI16HAX0m:^RF3Ж]?6=Fۊ*g9*v2wƒkc6wIslނO¾}{q_/6*6=锚{J)MǧEJfD~2#ֶ? -(0L@fl`8p<A@bC!ncԤ -ÛETk+VU~7%s(L9R3K8WD҆-E fr9_H Pf?WҲBR8uHѶf{ Y/]ih"ڴhb|chki)m+7׏sd2FwʎQ6w!G֥3tn;z&GV;κkޮiJ}szdǑэ/̌{%}0 h`  '.pp"H B 8EA)F#Dͼ%fUlq8㬀RDJ1 (EDEIeEU -Zl+uH1ftWꓥ((Fp_Թ)~pę,݋#MPRG2@#^{xȎP4)pB0.RnFCT_/H OA(% E(B~A!g`jF83ZGG2#cy6@-icS-m+05ڼ%r`-D?rh8GC -DZ\ \]6wOY=[}z`ӹ'fn>; O+yxhcEY@`0`;8 , /  Ƞ@8$)JG#bl}*Fh(dxIWK>٣gj(>up(^Tψܥ$yHF0RA}@(HQ7UQKqZj)h)bRإ23"i->~GOG҉RXP{Pt(5*ԕw(_¹)$ Ö"37$rA6/dPgEPB3rkuӆ=gJQۥ_;Z]phи[Δxm2ht/m]]oOl~*owęځUVťPVŪ1ܬniW*jTGP@kBUAU\٨ɳ'RɣwE=ci9B>qF(h0_¹:*b*>K<Ғ!fe=BenOw}Fޖc8XiSt*G悇hӣmn{y8NJ]X=q40` XvpXH^A8A$AD!qH$d=ڬXV6>١8#TZws7Ek)E9+>5S5*F5iyPQCA# ?ߡwQi_F(n1~^ j(X, 2jQPjNq(b"QDPnB]vy:Ə W.%O䤅2Ӥ,?!bdRFvK83(PYrBCPlj[Ų[ _M%`,~6H[:/ж=[9buϝTpi+*C]#Wʒ\K _7&=NiorJ #{Ό=s}~Mr0 h`  '.pp"H B W#c 8ÛX]rLq -#+L -/ bptHT OcHQCѭ1Qyzs.K6@]viřs$4HN,=|Xzd{ Jv#L%"4֧԰? 9s'2sS5Α4c?aOӞd(NF53XXSӗh滱|R?5|uyKmpNdsoybX'NwΕ~;?UwlS*tHS3u͌mIǥ - `,`N`!\xD@"ljB@(`Rh/3,l -/'!PDeyP(ƪXcWh uX^v1A#  ۇYzR8QdIc0'S'{RCq\(Kyesw>>˜P,K5R"7#KPd=.'>U BEoR81kpv>(R6P&LKD* JJ̬Aعrnc-'>$y#jh&U:{YϙzWWo2 -wm,U[w4m_'GGף]r>Ii<~k碙k<3}{*t -`߰V'|bwEj|yN>\SVtT"߯EkVbǿ^wÁ40` XvpXH^A8A$AD!qHJ=ѷ'v>[q15zas=zbJ[w>[[,%i%&%f~5H;C_YQZFU|FFNhUJgh_qFURw>hGԪRǏ4Œ`8?<wzPgcTGb6;B]N7T_9Jx;LBoT%'df(+˟Ě%3 rUUKٙԱtA휩mxx{9,h>yݠ~Ta8T);># {QtLFTe&Tuq#}q.P2gtOQ϶[>Teҧuʞ"5>z`.V&Y(0L@fl`8p<A@b}K*ĸv0ۈy4^b LMTWe5@ ZDl V"U!jU^֨L*4s*a:w?T% y>80*U*_iTťnVg4TeCT/U -PdU*ìmeuOìBZ?sUbbwrʀ/-j/JàgJL +!'UI(BXФa3*Xt󧕛˒giOlŬLuގS԰e<d6 >,[WU~Ow)eg2[HYsxNo"ގi.xrr:z :wX,_ڊe:er]U补ktԳ8P=P`#`+p )7x >< 2(( gHTe1v.Bw17Qb=ElJyUCܻ%KF·,m[ y[FU.U'\VeZUIAU/<f[y]4Y٨feiګa多52mY̓ד} -݀%PMg.!+W;{,q9)@Je&rd_9Jp2D>5;ןL Ԭa0353;-M - ~FRфսcmԡ7r ӦFm?5W--۾mPL=9>g{mwJNZgswYx<+*['ӹӟMwU lWoݸBҨÞX5.dc],ܰ1֦ݬ7w~vccYͮscJw7|?}.ϩw%:$%KWKӘ9֩3oԙո4czϼQn Pރԣ$ϗ$^FIrS,e3sTt59QI/8M8!;gR_H"}rBb(7fgA%AI -M<;YCo^?X*C d"nemg'bwlYq=M)f]HOxNGG[RJ6I]WI*ۉ\&^Dk!I}u[2%8H=P`#`+p )7x >< 2(( C!Y`m*&l> k{q̅7 $Vo> R&c29B]~>cM=qےK| 𗋅B+`_ssR0'1'Lr22ѽr;[v$wW]c;fP+_v}OLO^dN}řrg[+mG?ZD?Qiy`-ؽtwP wG#ڽ?.uޥu7jt귷cH*urtJwӺHgumպXO*T&3X -6B -  -D -1ÙYdibYATC2Fb%jb!Z@umJTw-&ķj 5D b ꈌ)l}:$$iUVZ1U*t?y_mPz ~75+knFMHfWYYfe>Ƚ#݋\2YY8%#+/.RU>*T$BEtgf| (+pHge V7bXKY~!RPd;7}J.}wLst9cM gO2}9ujuq}v&ث -ǑByPf۟')U JQd뛈Ԇ -]`CSJbNzm"nmE*-Y{))^J -EŎvqzF0 VRn|x@ dP QAw&M;i'b!m^׿mA!F6wDڏ(]daeFVjVFVo? @ @KYLV((z.[|48Թ(P`m2+"+oKۋV$'>EVnu傢(5ӊ_¹J+LrRnO e$3p?'7)AYYΞƉo$:[_P߽/+A ^cdM _@2=gk9ֶ_^vvم_ܘ=U/ͽpgm"o6[ڈxV:y˯ -yȻ놇Idok$nG 彑\@`0`;8 , /  Ƞ@8$>}6z2ۘi=eru->qs6{2=3;ח#܆5N<[YpV5j5hT=~jܡV#_~խQ[FP # s8[5oߢ)TjDFu:58`Y?,r>QtS6Kސ;{="vDcaTN|Pc1@<ljw/u@+ZǏH{|(C4hTeZ 4rz>F=רT=zW.**8#ilNUVtjUvH)f߃&sQɟ|7S[& )m^Te[ -/f~.+% T%ab+'·/7tQUn'6X̳[w}lA$=ěO| I`^b6#")ȷ+gWgjE5*Z1ɥl"iTd/nz6U"4f+[jT""ԊR"_࢒!GNRgg:ɧBEZ.함ȮY2u(:MPW\wIRz=1j]5K8GKxoKf0C9!.gdܜ`('ȇ?{zt݄%o'/?[}F>khk,ai|9Xsc-ߌņm;w˞(`+:ĝd:Zhs/9{v\ ۶iZ`4n'|ۓ >-NݫǯS}8-Z0#Zɧ@`0`;8 , /  Ƞ@8x^ʙ7U-(U QwR }1R|u}w(EThX5JQH." 9B]ɣoG+]R0s4o_Pf&/LI*FPjO hiߟq{# ޸P%w˞Mrmk⮚qjħ -u#TDc/| )?xrLAfe~A -3l&K\FNv ~JٺUkuN(,R0Y9FߴFu5/;AV5.zfisO m9hkm[^8α+y+=۝R!x^,yIxUw>J* -gE7'mNiktH窙хfƎ|ǥ - `,`N`!\xD@"^猂IekZHKQ({lRߋcS| KddE2RL f RVN(s.F _\r~S31l; pt S%g.,%[mu;1+tVlQrW3k0TL+]L|kxF7v`XlK7"?Nc.rt=?^RŁ40` XvpXH^A8A$AD!qHJ5'&bl鿫0XV_ 8[ջ7O[?~+Z( gz5+9V#+65+.RxVcY1"_g ʝjV~ww:Ykl\=e;jW}Y١K.kBV%o?`Ys*T`姐z m% ԟ@DLCB?8/aVFDYVW0!mu[c ۨr Gk:;a,c-m\H=Vs8d]X]{;|ik5L竟^鋊9 _-:gZ3fq40` XvpXH^A8A$AD!q13583-1k&!Ɛ;U|J :cRrlЋk'jm;rZiJg~*g찗%{`RM3j}FcĮ il}W-g*~ҹ䝺?R)(cYK.cBTkRc(_¹~2,/BpFԬ4TZ'| }ӨC -:tˏwYWXˑ'kV'q,fOucʁìis$vJ Ԧ+5OoXp}Kp\zF0 VRn|x@ dP QAFxɃD̢%jUln{ġ86&+/Udau~m_#sS4BTZuCI5:5ύdC7<8>RQS3[5j)~,1`Njx YJ@)_|xĀ`H]|F9)JqOK07e)mspZ=?'7WB)Af%g ƀ݉؞0[.K_ezX53t!95j`-Ec?k۵&{8v;uܔd7j*ͽv m |E/`Ұ g`k#B:,Xwt Ư6 F'Ăe]`A40` XvpXH^A8A$AD!qHʝ?>e(+W+Er%Q@9G1qsJo~pK_LyKK \jld3/4RjJ.Sg_j=GRe'g~b'%W=rV*u2Nc?#X(Ɋf,UJ^[zUJr*gQOrS}t%ISx'sT'Ҹ4.5K%̬ĵ<'p\N(C>b/$Krj/Q`u kz5XЕXfs4$7mQTf+nL9&몪Ks/]yޭ/W K%`rKGT)XOReDXJX-R%oYbT140` XvpXH^A8A$AD!q8sz y2BfaJ*H*'ga { oiGUD(#byG Q}]FN)zj٤$M#'F?q&CjV{ 4W/=#Cܖ;gcTm -U=sAOס':9)zuùQJ&l7K8Gk'I2,FfZjb+5ggB!fhܖێ5L ǚ>Ja>?Qobh|6v< 2(( C|34lڼv:jn -XÈw $9*K-[-0(pǷ#(*}Qi?ڷ-mj(h,a~b XPngKG]C(v9;ԯDYN*'>73#d _¹ ( l1C ȟ)$՞)%.U8&dri9ƓR][#5L"1F[ hcJ+<T+ibjVm_ecG5.NYwvhsWl yoREͲpz tn|] B{$Sw_t/zyf໙cǓq40` XvpXH^A8A$AD!qHؿ!6yTTk6uPwf (EZ /ۢ5oXG0R7WsW&JqJQrG/|7} R)c7*KH%ʮQjwbjpJʒ*s%vj|B+٢!H~xZɃ?Nbcl  -ƓOӦyJ1ڼTжn{Y8GWsQ-RGN{ZCozw+=Q: ۓÝ)>FXv#Śѓό?,K&3X -6B -  -D -1s?R&[|V31R^xP -/QK~RpXknp)A)kR:k[E˳4ߒט)ه)֪3Ru3ImGPx>E(8Jqcə֋RG*UN|Pxz2JE?~À/\mޕiA1ӤŇEHJH0Fٮ絖fFg47ۉazؽ5u=Х_YXKmh-ngIɍ)·}pG6~[͏c/q}M$Hie 5@`0`;8 , /  Ƞ@8߿KK%ĸ0Yo_J-&?>~}!='|>fy-$⡟q Ֆ?GJқ?pTŖwzR5GE35N~^jO!Km*8;R bZ7>חC}12-!G]L?39 Gfa^pD_1]]# 4_-Ȝq9|T& -ȫVSw%BNz:5bkApr;zIa4( -bqPO$\)D=|cu9&.y[_xiMC<ܴYFY-0S(ԣm2_fvZM1սs iwgֻsWưBMhT?Y"qɞRžrnR50Ex@x06-lz< e`+P@ 7x >`D@T QA QҫqkRhɦUhBWM1cWN#BTIQ9\F1Г{IARzR\'`׎39kǿ)(&f C; -R}9IcAEPFLBPRy2ˡnEP$ՈL5+"aځD#'% y )({%7)%DPHK;+PPPɕҼp84|1t&{&?w&Gǚ3M,uui:E.lu}}ٱbљ4LpmAgit&̓a+j`ա39X l[(!qSc'PQt?NURc)!FTkQQ|u[vE-p]V.O)*T$!\sʭw"hG2#{+!(.ET$II:(oE3 *A (j$-Ru-.X0RI_1<(Μ% -Oةkҕ_4 ‘ zYݍ{3uuJ -SS")GH商(9(zUD~BTB E,  BnPʓ`8uJ%<4?,)W울LR}si` CI|1βa>e=sb YIg>gvf}aGU5Ŵ:<7CڽCڳE{=Ű{Ve GUn{yPF}fdy2$Ś)jCTPkAHQi͙s3X -`;8' XC8A$A4@bԣHݝ%`(SqIn@ReEI -rvw~suAR|7$Uzq>Zb+MY7ms~7RIwj^;(נLP6%E; s7=nOU()v"(~/\xEU#J,?|jW,BjC}dpJp(%r8('RP- )<9X@TJyKZE+Gk:CvlJg(GC@IG־W.1;ŔS\ߣ=ўݫYoR?X:>Oӥ';ֺ׫mLՎ=\V(v<x.` Xl`0^ ~< 2(B zGPh{TR^r@漼(E>)jʈ,6- (LPL`>oQO^F֎&=zR e}9#e"Q|?]gwBIq;yҿ"5+riH;oAR|U#hF5F:T>%]W)BrNJ2I!+H|9re!D .} -GGë.2m ݏ{,]=t{<8qEc\Zuo,c=53-=gdVWͼ@{Om&̈́iQ+!. -!m$=}qț;uef"@%"ֺf{)$V 8n| -A8enƝ[wRj1OwᙅC'Ε{Mp\ohq^a #W^=8oЋ"8d`z3cOObEJ͞oqР(z)0dM_F3;j:Rw<. Z%ƻ3#ϕ(T C^Z~$/ @İf! `In~D+.2vXrxbA}*ƚ6=ϐ-͛j(KY-e=0Ll]R){(ٶevW=YS ~[ȿI U*5/̐*ߘ,ow++ -SUӢ]LU͔x.` Xl`0^ ~< 2(B zͤ+u -O \;aU9JYT"iJ -s J_Aԓ"GOGI`z%lY{ߍ|SܕqzÓf^aʺ+䔾+-=(~ͣV0VǑA;|az~2W6ֽ%3sv>: =0kJvo%mmس45+׾{w"5kB[t`+YmSKؾg4 f(vpNp<p"H *h( _zWx-a#Ek5ag eSG8z]C#J¿T\y{!R#!/!e(w+Lr0?ˎ J}1^/;.3(¤_kňJ2:Tab7<=L XȽx Pu\#6oBɤ=arCU#x.xU󵰑]LʡDqq0?YsKPq81[.͏߅Xө yäʲe1em)3S͏RtW%eU#um Ŵmfg&jQu`D@T QAε'J:(VR-b1ƭ~M+>q2yrz~+(.уvpUbPQpߜo? .MEEq߱vWs^nrzPcOGK07էО1B7*mwe2GIy/7U!{P$PJXLi,p((%'PQXБLP?6ִk'C9ܹ쭧>VCD[͎54l9u,^9H{N~z;NS%cސ^-p|5-)ۖ --ctјݭA~@ki)]fzdSf7$V 8n| -A8$^* - CE#(<x /z$NQemA[W\=(T(H x|1<"b6()Iq{@O s >\0eÀi;G]-O$52yǑ\~EIGIFI1s搗pJ -%/bE -˳R;8sQR$D^"7Hr/ʐ'6{E هb,Όv}2j{Ngo?1QSN1=\iw\zF?İ='D>o5U8qSӥQ[~zϴS#-*\2 f(vpNp<p"H *h( fkyһ/T$LGDdERnCRX -"/H -I}TIQfED Mh}ųS_w?xf|_I[+GwzOѷ_ 5u=ƐIrʲ>4-(8[oe>3ƱQZ:篙ZnwnE17a7M -4̭?5vr|e1]Zdg+OQw5N* "[b_ee`+P@ 7x >`D@T QAӧו$ԎΈKqsPS5#E(BmzR2HAR4Iq@_1Hh> ?~}H7\KxOEOV% F()VR)ʑ{ % .L:Io -lAqd!/|& eEL*ATJ9PP-N$y4| -ߗ唿?q.}\gGcMy-e9^KYTjF8k){ S&Wq] uP⅐Xr+<^(.zjcܾ֭Կ5ET`Ad#ӢKޜ؛Ys3X -`;8' XC8A$A4@b/ό K -#(4CJR(n)J!_ D8/ JZz\'I#n8k3~@9 (nEPWԏTR3Cf=)0h>^0@XvFOk>|ARL'^*FRl`eҳ,bfU#]()^]%|UStmdK8OKd24\2+8$!nV oPQJR_>db*\$EcMU Yz,e]ZEWlUfǒ SeoLp+^>S~VBϐO W O-n]Gʎm7-hoF*G[ߘ[ҝYs3X -`;8' XC8A$A4@bsIV-:yWPPRD'A\J̠fEARLRIѬ'ER_x`=6]7 -!:^d>xld ӣ=~㠸yRwR&("(^IM!E()Fk26DIuxxېpI{$p;s`8T*CppkԭUEW~|TN7؜cn,V ӂm}ɺ -e- Or}v]@x"rZX+׻v_÷=,Gdx9R|R^]Xx̲Ur?_;1s<$V 8n| -A8"´/b/a& 0A&l}"sK; d.MvA{ R'!&BNhF Q?,==U~ŠH"U>"e֮qz -W}{FѨ\ʏH<-;9VTG&/tʏ~H!X!fs{oqKH/Fk1Sa}x|S1, 1Ę,Ęēӓ1,zG7 -ܽ۴R:cZKDbX"ZKRk*"2D;DSY=z?~{:$FўUo-j=٧.|ywΛ=?dܬYߒN̛_07'w~^eYܬywܙX)}/4-FčJO yr867\ZzP.yo ϧfyi?/|B E>pH"ȧ%gGfd\uNSutʎ󣚸 CXVV҆Bk,{6*M'יKҖƒnkI8(۹ZVӽMwHe -`OU3_iBubI5W33cfŏq8(Ё @F0,`Np<x@ d@bZsYGhMn3oak8=Iup$1 r 2.:.6.>n&'b&;/-s-?bߧcw'=}Wi&Da>ԝ&יhQ~UɦdW~r(?9ǭg~r钿ϭhx5|q8V58Q3NҌ5"qQ͸(38Ͼ7 }ʛ}177.:fԛ]_glDž/}]tͦ~9xY&lfMʛ5)ov7μNe<;]^6-\Rr\:o5G~tu|U|9B]Q\"QSC>"-l"//Mpl?N rӂ< lzNNR[S`JS8mm]2Z$m8Rԯirb:ZR$['v Юz奐t|Kgeǿ 7&T; -={3"dFWdƊ߹l)p\Z@z0 fl`8n|8A$!QA"i(|sr6sQ -^Ǣ">RPJ)yx)|$U -J)ԦJ?/U)TJKqfX)nR[Pu#qnRJ)>WJ)aT)ũRDRR-KRo3&Rs?=rx@J*S"uD~ 4C873EgB| fsp"+';=>S9e郩RmzBѝ~7 %:z[EKѦ]KthKgnkkld'ǩ#n&o yzew%_ۃ3}`׾<[zx &a+Jjߜ!9ԓ~ةǥ -tL` Xvp\x`A"!y(6RCbLs[6.;d"<qpl(KT~SR.UJ)20S|p٨obHxgqfRSJaex)^0U^ -2SdYRE?Lq*`yWAR3ũ(EF~G"e0NJ"\,Q M)anR䅃ȥg9,Vty9\v"'/V> -ZXl)V^]VNu9 _Mr05u၉=g[|4նm/lt;zg;r~_|۷@SVžLt!",+Z<"U6yyV)xx.]Jbn#@`0 `+p / ,p"H C8$hQJka!]Bw1/ -b+!| ('|YBķ - [A2q\D+U'K*)JIJ%%+)cII.u%CW' 4$[YlU9wTI~%Y0JI^'}ԥq(I,#zJ;rԣ$3)*J[nC3GcȇpnJ§>sb8Ҳ|.A.ϥa>8|Nv-I .mLbً]s{P_7ds_ٻ;hl5ռ0ܻ/hi6ȶ]oJnQ:{ L+ֽB|5kVk+VkOi5|sۛD7ho+4e5eOkMjokO:Ѓh`&0`;8 .p B <>ݑIѷ^BwS#1'Il;8Dgv~$l ZCD<@"nU+U (Q2E*TϣV1T jXUkCsHU^gOUZy{TM* EF*+U٬TYw(eUU)L]>i'~y lC>s}> _pΏ% r~AdsDP XSiƍ>lUwi+2/ht +AeYиiÝAˎ݅ZQ!jWIMĹi_T_:o|x򛈯e*Mjݷah?*戍߬H?+Wi"Uij4j⻴ɿpZ@z0 fl`8n|8A$!QARMU!Jb!t=ajq1s Rb@[cs*x6om J V@-D*שTEPQdJU*UY5eO 5h叔xvhVdMy{Ĕп_)YyXɊC%+JHYrټxk!+!Kr3U6݅R'%_)#}aE([C>s \Ys %=|PV_Hrsr< Dz) spv3j×yŴu%mX_N3ݏƪPg-֍&N~8OЮw3!Ϧ2ٻB6#>m`YiBnX:C;2"OΌޚoIm⸴@`0 `+p / ,p"H C8$3x҅ ,cFĚkc. p99.9yVp#ne(xV)MNUNla4IJ9Ɍ3.kRJqʲ RT@ZJ ˚cTJaj`Y3J,kNtvY+E) -O]$R[JWe*׆R4FԕF|h+}+5&џ|z"˚`ټ+RN)f)ئ2SQN^JBbRTJ1M']-Cg[R )8e+uLWf -N9򵲕zRJFJn,UrrbL1Xz@H葧䫘)ߍu)&*-xBYlПM"HOge|^8/b9f,]3U&hPUO:?@w99pqg7oҷm=zƾqUͯҮX5_r. ݷwzw:{h pEӄCJ}壟O쯜`f`8.-P=`3X -67x >  ( g.P -Q/"(lLYV&%Gv_$gK$;줋M)AeAe2SxRs4Sp>҅p"---gPB_Iqp65k4M_>jޞ-zBU?1G ; ?O7?L3E"ڴ撇hKGZ$[a^tq4v]˞7ՇO13c>7ۇہR\!oTV -7HW1S,Lq!J]haȇp?yv" %9Y`^6ftޟZ$+TJ~M772y9vL4҇3-AP+k:Y;<5hYI!6ȶ Ւx8u눧=DwzҰ:h#_"B9ӫ5_ZݼN=iMj:Ѓh`&0`;8 .p B <]DەEE vbB̝I=KёG; _ v Av"6m*Y^%+2ܯLe )2e H#d%>RUN[vn2vcqtvKvӮ-Ɛx}N58 n߽cpSX:)?C=YxftˮXǥ -tL` Xvp\x`A"!y(OA=`a"XxvO ؀ŤNtI;Ǜ~ަIw*SΓtT(TB<ŽQCq BgnTBqP:˕$/V6?ͩPԫ⎒sJ(~G]Y?ƏOϞ}jB(꒹neB*JELoYʖLq kj]6ټ~RJ)hFpJeaX)nߋ?8PQBkP\T(T4Ň}nR mcł#Q.q^D@ؼXJP|Ux1RK C>s5R|M!V1?)J]91j|膶4~GVV(=G#7!NZN.qaJqju}5xͅ0Uc' T8U9 }LK{̉gSDKҾMnGolXK]yغŷ$Pt+aOB_\PB%׈ݕD{n%-$6kZc:Ѓh*#  ' < 2D -1CP9sgl!їRB/%L!1BsI{m) gq/!e[J|%$O=.y1v%wlyP 5,OlJKZF82.Rk˓}oSG9f܀잇Og%KWe*1B]5R1"L QKѷ< Ţ$SG0WgsiP.ĿNSUe뷥Zr =aj~C_a8V{Lc:ƼR7rpmY۾=YpvOyO݆T'o?LhkВ7lZrm$ZvVZc:Ѓh`&0`;8 .p B 0x?ދܣk($Bfq)ZZ --x/;PF۹b -sŻB2m(zadSGҏG~LRGQG} k?ԏqq*P9ʪv5S&mC?>lI][]F?vnZn#%_E?~_ou_|h$}=&fuE,RDN?YE=(Ё @F0,`Np<x@ d@bܥ-L"w0ĸt9+.O.&BwO`!{7+HD;Bz)U#J=6*KZcﻉ^;2oqrMlKUc킯be$|LC> pU|4U(?$ϣ TwƏ9$%_)#*Zq*2F]4~ȇpvGG-k`l <LO 'o_FT|ߔ%6=j{'hmPE.hÑSKiEҕeGZewqTvv|L:--!ٻVz##--vwb,}.\'u Lˈ-͌͌ ::Ѓh`&0`;8 .p B g.r q YK{1`̜%`m^;7@;q{>WúW Ha(@R\I")1눛ޡA),M\竔T):gMl/TcRp%Go(זcЈM J#J]q5SC>sѯNY,1ϧx6l RTӔn)ROi|ǥ=~&E~ě_cA㞓-E ֦Ndoֵ/]ӰxLkB|ݧ+5_Wjآ§4܎5ъ7жVdT7/*5_h]5ֵRW|@@`0 `+p / ,p"H C8 >S|ډvzazvb!nbk$h༓x;z [6HKFP ]*YeLCVdX<څoVP@nQoJV^V/STc@n}@BVn P=ǧrYI.SdeFJ\tY7a)~yȐ\2Yfr"&D(-i`^Z0 fd~[A. U?Gױַ҆tt4S4m,|6(ә7Жldߓ?ɱ9tNgA{S];#~{ݙ܎GgKKcBxWR{fD6ȌVn>ݚ::Ѓh`&0`;8 .p B <vXV04DI6_cᬼˎs+"q^""'r;X)oUJQBV)mJ)7ReJ`ܨgʑv:.35(x[<ךS2z2reYy g]rqOڹ[}ԥ+Noš7[PKW\RZ@.3C8GF)ùlZCaJ!>,la%_%(E٦/ޝ|z䣙ԩN6}M¯Y&TZ7ټ>h9_H'*ݾtG7sש]eg;&Vk&PI [: _sĚ+5҆֕'54S54֧(Ё @F0,`Np<x@ d@b[-i"-ṊH;#N]fm#fh'^#<o#_r~JVvF)F%+1de QgV@^Q@v|k_c@pX߄~@$;:J>[2J^m9(Ok^.! .-;C4 -?Y9<0C|(e:ͩVڒ@jP -It- ɥ )lqO=0[˜pXm94նXl]a/vmy^_sV|}+aKco\Nc;F"|o+.FWXG (Ё @F0,`Np<x@ d@b.K,_|\!-7Xc( f0iYi 敪:MiLգ [Ԟ'PM;m^k jxm?2QcW%o驺ϻ콿܏z,{8+k呫ŢGtLG_[H46[LQ @#  ' < 2D -1Ù/qKw1X@LKyXﱕ{Ǥ{W@ń+!|#y?Ie)QJ{d)%DR)_0FI^TV0-0 +V)6h̒KLG*˛[PmE#""/Ň]~:A)EJ)~}XJ)jT)~RJ{mH8^gZ{RPL̵J)2UJqeZ!?"SXp fbQ -| KP -J~3G,2uK2J1uV,;bؖ!¹>K-d\焓gb /"`"-;/==O +E;fOngFfvLjœ/ht5ma˓AayX5u4M6׶-U݅zjl;BWKwg宥e-ꈧj+>J/]aO=FUuo= o^J,54%Vkm=5gG@@`0 `+p / ,p"H C8'C LMFWn&vb є$i|O-D."l"'ץ .\5귴Rr2LPJr!!#řK7BT@ꭣRU6e#p GEKyG1@V>SM -g^)*2wxs 2Bnȇp$R![CiAznBXԈY K\>N~%y%٧(ت1vmt; -lj9vZ9?pJ4@cMP*i'|oM j:۟yHGHuM$߷D>o%O 1j40`V^AXD@D!qnm-Iˈa2XDLE\N,ˈؖ{ q gvIxw6IaKD*#ïwטrHe1>EUX} >ͨ9fሗUę~Ӎ*=`)^eJғUz1L)P`u~9KKě^d r}B'/20'=S.sD9KQQƔ#}'ɺo~=)eŴ4s` ml(Mͧƛ;-G7 l0NJz0:Xaq<8qw #(= e|ӪiBC]uRMtHΙSdƊ.wqi40`V^AXD@D!q8-!YOs(bf[6|)=>dVx^_X [%W( J(q~*RЩÉ]QwCn6tbO7J)V^ϕ*د[R2g|п_rR (tu3~LLD)^@7cxjJă3ޞόRe5C8GF/H ɯI?+-;D"Рt3Jɧ-ڮ<%Qڰb`<ݻf6Ɩ'hoƛhKcۺI{njDYtv=mqք<}=Ꙍ@]3w?;M],~:򊶌c/f^Nǥ -tL` Xvp\x`A"!y(ow# k"fzm=:4-{x DYs}?A)Je(P)+HQ|֏AԄ7'g~:4J(⿔NPL OIJ(Jưqnb8 D(d(/PL^꫒TZO -߽/JGaXpȇpnBFM -f%o żt/$B dӆ \Kef)˔l)Ꝡ-yB~|}9F-hzcW)m:?޼#Ŋn͓l5{aQ|v5?kq>+|MC[33O⋷ObI-3"2k_όm|3 -(Ё @F0,`Np<x@ d@b3#0x{LȚ8sa -l($H!Q1*eiĝ5*تS ť*O"dUBo k_Z'&ﵭZR -VR)ER5J)~x~ >Gy?{>@]VMoQ|2bB|$OWCuvȇpJ~\8bB\0ǟ q\"=o.(M)=ߚ`*?-ц(ERYRG7Mn놆I`(ŒzX]1)-k$_sMlGg-ӄUvqIO˻2"}fF6e[Sqi40`V^AXD@D!q3Kg%@`bYʟ;aD6E0]KQ)ŅJ)3I!@j¢k$SG&=oJ(Œ9_DbR&J)QUf1>+<&MX|t'IRnB)6<wHWS,>jvG+ïbe֐\7Ӎ\.ٹy!?E|z8 -eU0qM7-tTlmKҜIm:Fɠ;aId/Ȝ.kv'[Z-~Xwlm/H/H mkLuS? -vt6+5*5k5\E}7;ޮHxR#ίDN,D&V\]T,T @#  ' < 2D -1CPꉶ%y椉a={f~8=:xmcIqwO3n!V0x*v37Dj'rȧNnP9Y -QɊ_@ScJθ;;w~(VMS6?T7U1l~GNG1lwUKT6_*PޱLWh䫘?޾!BELQ*r?6C>s>jUB| -;AVV"NZWDN7:<+9e1Pxz4mXIo޸mm+I'ObZiq,9_|"zfW`ޛ/&a{~%!w5ψ ͌_k0&4-`L9jܢGz} (HܰoW3dà\aQt%uJ(֩k _ibVq}~gP?MʞFL)ŀJ5e)E*|/s}SNⷝR؁R,nA)lRU{lv▞!¹z(藂9ܼ4Q,y`v(7='=UuW멓[/nv}NnqW; oiuO?0U3˞/f[k?jxm/lw;zg;Opv/)}ރ~JcW _>\Dڷh[O+/U.1j40`V^AXD@D!qHmڻQn1sn7Y.-4xCmoH*8_ReXRU;cXtW>ʷd}<( r`|+پ%(uSu%C>sT{)''H>ĔC~^ 'TAA秇CW&4?/29 ]bZ6|XGxfN=Jh:hKûX4N؋ǎ|[yjݽId`O~5xq5MaN@|8#Ұ23^f(K @#  ' < 2D -1Ù}KlEfc -=|vypeºY p"/y6U BP\la J(~\P E| Xfjn){ƙ? <%_,L:J(J(t%*!;j#uie&>;b O"uiG.},JE~!PGؼU|hpl 9lnz{+4oM9T(vL.Pst.( _.S -iSËhKe3B$[Gv]-!۰D]8YV=k?u=d4v:fFόUץB:Ѓh`&0`;8 .p B p&ɧz 2#2$k&S(;P>9 QEI~J(UB2Q,T((P&*OWzøQ/_i=9[GsCKP)TbiP)bH!*[Hl #E\2 #S(G)LHQC2x(3C8W׉>RYi\8'p?+/-XIӆov6c1Rmr3Ȟo,ua,FX_~bts4a`DsÆn8!l6;l!ɝN81e{ms_{RJ`-X R -VZ"֊+"~S6Css98>@ćC^:yC^9\|:ŜX05?SrPrI_ЄzuGU8Öl\c{e*qdjBރl;byRM'hi4(ޡCB< 2(Dэ9sBOL -6y-ΰk*BcwOF!"lRD2эJx@kPMK$-)$I֠ܤspWKX8ɳQɡIquK;N$'2%u2Cܼ?"%:'v")ZJ2$Ŝ=7.LN~.'B:!/2%;։ljz0#|`<rrԳr}RJdž'IQY`6l!^eZAVʲme-Ph_כ(\ܲ$Dk<3O7{83:֬ɝ>4_:.֞&5oʖO-ly{VxiHˊ9H -<H0 $ -hl^8A$A0D -d ~a )d:jXe[QSP$t($pkh*;GE%yXR\XK')򵋛:5sZMQܧ1__s_#(}РxP :K!T'(^֠TŞ>(7AT"߉n=NIAzBN.9LwJ.i?O҅Լ 2cL6?I'}p"(N5Cvkܿ2mhGʌTC#E/GPiUK^+xv~G_me= -;en)ᇬPYgp=O~}cS.&ݘ%Zu >>+D4$f X Np<!!`<D@(20A1$R,XZL>%4ZR4Va6b((т j%Sf?)"KJz`oViCb(nv 7hANP߷AoQQ?GPL.BP;;tN3m^RIo (kjE搗p*W2t!GMS̜̜` 1%Zz ;ѾY-?p! ƾyre;Lu-}l]:@;?]Cg}2Íb)R.C9k>:?UNtmRktسYf9<{ @L` -hl^@dMCB< 2(D&s -!(T+sH)~% Vx"((RXJqNPjZEQ_!(*KKG*J/Ӓ"EgsvC:%şxfcX8w>MBRл7B܎+_IU&R89ppy"g/18GL >'̠`GY)W)JVOj9{N'lJf[w=زS3jT>RTfENv{Zrn\Ewnazwr˄cv"ueRye{cuWr13W<6I:V"I{~eڤp$Ej &04X -6`p /  Ƞ@"{^CDA݄eI؛Ggm$|]h&B-Ep@{ ZϫX+XR'V~zaƹqNd K8O{θNӨk됗pvR.ql]k ;d"s2YMS ~AW벴WѶ~c/<1Qu^Z OC_o^pmDh#` mmTUῊAḦ́e:kDu<0($f X Np<!!`AC>JaXJ5aZGRxJºJ±p.&\K8VW -"PNluPD%H#HHٮ}ռ&6m+ť&;Ɛ&.uޑj?Esu?BS,34V;qPIשQ!ѿ,}1i}HGw"MbIU&efV =Hӏy s5PBs|</c9$ $I&'L&+&zgD̪IT'['_w ȓ5L~? y2Yȓ?%\Kr2S3i99\>MύŅ8?ڄ55듚[&4|{!{^OPBʼu [D(KZ?lk*^ǎh}i{Gg듌w4_7k3Lt::kxu_~6,*-]6]..R6Gs$W1U{RN&'OC^3)O l,\)sScaIюط{BqDRT3T3dqcMǛ~L58?200Zv(ٶLv})ulJ\G9$d-vu.B> !s~>$HbD ZSI:-)iύ!):aINjbiώD:šsM-)ѩ)Iavvtp,.?qH -T mK|IQ7E ݛ|HJ*B^C^eJ -q%3=& 㙩!ˍ𙙱LvxMqIq2y")6v3tfus?7uSϓ)Νo)ZDYI.{NqTOq+Jq)>.}};&ϞC6u7.[sMۦIeog+g7ϊu%f\ &04X -6`p /  Ƞ@"={*&79@hzlCB< 2(DP݇B,!+BŹBH -74x{S}(BX%C-)~$1 e$EFj.eXRqQRTTSEiIq.JVSttNR䏡7KIWkkwIq9IU&ݩaD!/2i%&q( -qb}\+RH8q_R8/KzWD -,&B NK æ\Xܧ#WiIM; 6[ˑHV1$ZqsI3R?6Kgs#Gu -HNc(8v=8F>uOKtZbcRgw寿GܥZ:M&C%BN~mOG㐗p6ZKȌSX4&B~^0c(Z.#-b;&ߴtiڗ(n5IGǰ}p[kd&ft&;Vj-?pJRI/*dfUNj"B:!/2}"Z#''7cOrsX5)R|,':s׭\T;aZR3odȥ o, ǒG)z--d[y{k߷%űvBRuvQ$E i -$Ŗg@IKVe$ŗճWbt,e_7gFN$e`3P@`'  -!њIK~"hMDb)jH?PКD8GHiK:IqՕ]t% --)ecX|Q[5)vb~kΧ(/k˝$:.ICcH}HwCsibZ$s)hM85EDREkR&D mg˕`g\O њ uN3cB*5Ez)6qj|7nax!en6SJM)rʺy vf1Ŝ赗>h꥝g>]^w -۷(W)Ylˆlx,?,4pظ6{ty˚,cFtf)R'# `+؀;8 .p? ,p"H a@G?M9L))xk3(~1 I|@İK gg43DRԙ [[lג"E')|R W<1RR&\-):t1j]:I񊶅zv-;ư:5=oIBMGk.$czb@+Y͎097)n.SRdeŅ`.~x`(Χ(K EΚq Svc9e*\I $SStRDzR޷S{8Mtfg|}d7R9;Թ%mb}~&m2Ky7[جp"k-T<H0 @  7x >CB< 2(DA})ԻN4kQM`XQSLARD,u 㣃#Ⰽ-D:C")!IaIc)?u Qnd=$Jߢ~u}V'(>X84o2G8ko3"jP_ /(9Aq^4RHaRvo.SPE("&RS9q>gHf~,'r~%vT:Ns2k3}alGP@PEPU5e.ٶi9r}GɢE)u&O<oCk}&ד7:eJ>f VWN9/Im+y3 [fw0+Һ 1/e`3P@`'  -!Q8L,J --UIhPR(^$D2'2EdEAaЂ*WZQ۵m= 3(}=\6x)A; sN{xK+)v$E^wL')|now+v-&fpW4[PR1Y 1e!${mZVDƘcXYB+`J- -kkkEDD Tr֟=-׵׏0wz6?'Mc{׾UM9/ _{}sdy}sYwgst7>7?ffed͙;;3~l?qss{_2/}V}r3+~Eky/i~u;wW/`z*REovjvWHMMIT!̐.ҷL2i6>I~999MLUwP:wWX|.eNmc9XSw.ca-y'[urۚ/:jN~zB_ʼn+'|^M>O^/N$:}DI]$V~Q @#  B -8 .p| !CġM1E,&ƻM-s3lw米RF89ruﻓwK兡9#4sOty߯(s:RL3(Ҍ/&322)hU,ʟ*)+b_0w+.眚K֌hF4#8M׎ hFfҌkFD4#ԗW1~ؿM&؛LViYa|%ؿL0գ?&5)?K?{W]OqKg[;aQWo?QE#e-5ӭ]<紷?dMO9~u:{_u|ljO¯۶];ԊҊ5rK$sv.|D -]3 (Ё @F0,`؁p\ABD!C)&p}910y52b+$-#H1q -^)%"-&|!Vq5ʉ\DB+BU G$rJP~4"\Oo~AkߓJO2Uz5ӯDWɿғsyO[{Qd1䫹ɞL'K!R)V.ħ!mB{(O3+$ ٓ 3.zx7+_LMc~Ѩ=96J[h -kf꺾CHr?c"THs2Rh[7=qoyXeWk!wӛɾⳓok]:w|ɡS…"ݲIK @#  B -8 .p| !Cġډ(HH5ho1̢El!6hD+ d.G(JH(PD ŗCKBKɐǫØ<4B1_(hɃcg+]J)9yL_LW)L*+pcjJ)ƢuMsrF?ס/Q"iZLP -|kQGc꺏QU -iR ,XRyӽ.#+[ q1k@)zPƖn?YGi_P;І#e46,MMF{-o9FZ>g%Gřr+QD@!QAPCf^!#5͛ !-+;H.Q6iNuHW29GioPf1Rȧ +N 3?6vЦoFk -iם֝/aWrchqnrC2sr@d.6D.$%er_S#Ӣ;qi40`VXH8n8A$!a@b8N!aCsl #r6]dEHv7w)\SByQT<]:^s7nZ`ǭѶד(Ё @F0,`؁p\ABD!.D}p=QK&٣l 8E,>)&a :R\R -Q,*xF)Ń$K1Y0JɥCQ)>b ݿ+S~}J)_,RRܢd)dR6Csշ=]Ňa+J`(Ųfc"5cWħ)LQ; i!K*WWfbŅ̠_ -JJqf&M:,Qړ/[ꙺZIp2WGD@!QAP"Zn-BL1 ݒGqD^eѨEQJ(.H1كҳ] -BmC(:DE$SPJ J$JY_$\] -2/qxf2$o(fI|HEM/M^9 -m]c:|lc k7isc~yuC8[ N{n>=cU{]O=wO٢Wt;֟Ao"‘K륂ܽ%:v; xDV|>Q @#  B -8 .p| !Cġ$.,&YBBfqi%1 k#ĞG؜ĵ<, +g- +X@"R2RXF8&({)*a#5;XF 1L}ìQ\.%#Zn_>ࡼCF -W&/pH,2]L^ j†gNz &~K^!;+Û!dL fŴE?Io$3r -ndV[q5of«Xvtj#cXueޓlEN{N@梫>:jM =MK/?p+O&"ܹ^^*]:ު%5u$\+$Fd (Ё @F0,`؁p\ABD!C]|}1̦g393gYZW\qk;B!s@^5˕xܣ[$S%xU5,*=qĐTy=sm<* ҬXXY =0?F<^>6fk-LB<@~w33ɉOC者ߟPw.bş6d<Ĵ̸q㽂c{x$)30p9JcZ%+]B-yW=A: -tM46Ҧ:'hלUmzy͙2Gγg;g{n9,VM V&nXQaZ$D@!QAPn黦:I?Lo[#bѾ -7zdąBX(M!D |J2eTBQb2e\abOz7`RK\_˕m -K%g%KF34SQ{Jpkr -c{\jFoD)JF2QN|/P6J$\JpJFvZ$o<{4Jq1IxZR^IY3c+\KΡ?ڵU7vq5c~^!ԻmCybm}#dj]dKKHZqi΢-A<+Oi|'z4\R#>&S:I|@&I&M$ -Mx&֮M/ -tL` Xv`!>D@!QAߡmSb8Jİ SG]Ĕ1K  CIJ8;0V'-""!"7cU4&U2AJUFVn7jlpK9xJUV/W,^F(}qVebFUU_$rxwU)F[J;md쵝 ۺΙsv8𮮏sI8p[S -{s&g^K_(+%-g0Lrǥ -tL` Xv`!>D@!Qt݀ܨdx)cf[~+D|27"D)?B)M~qL EJ(+.Ɪ;Cu/C4J}URܯܪQerCR|<&Y{TJƲsZL dzx0˛!d{x*1"&MNWyIYrPڮAvD0?ްe:VŬ̋Yɞ۬/.s7.uߖRuv޼Н{x/W[]Gv|"6oIe496 ei/m ѕm(Ё @F0,`؁p\ABD!}7wK󉡄 MĒKq]ю9"#=s|7/WnpdʨQ:FF)2eԸB )( luhG<<ج~<5*9qH5*[ʬA+9R0fA -rx$/k$юו-8F-P=`3X -6 )' < 2 B -f+ 7.&ֻmyĞO2M\i) |8W//#L% J@h% Jj # ^z`{_~cJ?.W@&*0.e[cJ?F?ʗ?2w61҂'J_ )#g%|YDN||/ SS"Ԅ1U>v; rn -BV:/̀W޴4Y@U:l,kǝGwC1wLF1߯?ኟ}J7wIĦwJG_yR#w-Z =&\3͚؆K63w:Ѓh`&0`;p p"H C(Ġowhw'NU; [i/17K&&bo"l]*MB\@ QnƧꗦ4,:;TЛiT>(^!;&`ZWʐ@F =M+9J; -?HVRT*4E~͜| ]Sgrr3/rgPM~KTe␭ml/|Pf$ucWSg>`?@*QSc td=RK77aX 3c_iJ5~[|r5%T @#  B -8 .p| !CUMo TI TC IMVշJT8k7qWO+n7ᛈM*K Z+*UQ -RxaeJUFUr 5XiPR(Uʬ2OU)GTa?s3PD3dr ATeSF>IN|)BELRWz~'b:IB*V9W@U~gz3y!+--3^z|fc-ɍ#{>VS`m+J>nP}̝|E>n> -X Qܧ{T8By+~'b}gZf ą7;#5XM&M %ov pƥg S^sGCo&Rڡ;*BUN[ gCV/8c3 K'e>HgzI7Ic&r\=RY -T @#  B -8 .p| !CU -ÚmTs-k{aU'έąSxVµ}Kj"אP6*U~TTšRQukȔ6Ƽ~*U٧EoWYTJ2U>YA:=UTr"*}$RPħ!W.DZiT0` |+2%$eެt>Υx0s0c]{NM\9dN׏NrTsCo{q v - -d]2~Rps5ypf~#e2>^wlDU־I)5zR#l+Մ+ӄWؠTlD(w@@`0 `+,7x 0D -1HJ=HTe? #^bl]5Ķ ۞nM\]A Q21>›~lV; .zi~> ىm5f >il ;SX4;ֿz0y=|/. OLo1ml/MGi.YC[j9um;d쫿d͝ΔcKҽىׄWJfz$!5##;rtfѱ{(&*;5J\gԵbʹhӊGtcӖ9y mfy CgJڱy3:r}h}lǧo&}gxStON^:%Z;-[bID8.-P=`3X -6 )' < 2 B rC߻B!F Lx)lYn=NB($gu;&ra>"Ĩ$bhB!+HU JeF%*LἛ!׎l)f+جRB!}g( +"UMHJ)( (ŭ}Ѳ[EB{JW4Dy*+oB%+,T<$\R&R[3#%z -ޛΧBfk2(Ųm_$KQ(,ԉwryhC:9$m,{6mә-[V3V?ޑ?[1.9iq}Hpr.s)=Si|q>tfy)Oid)p\Z@z0 fl`RNp<x@ dA"$%Q}p=b YoD)&S̛#rIbXȑP"EGJ)nT)SyYRBW).7 9R$4{h1檙UY||{~,Y;֡RnN,>bq 3Ŷ];$\7}[sb 5ݛ%fsRf<5- PF)t-yd)Qm)^[?m(^ Յ)*iS:s~9m{-st\cN/ܥg˻}L掞Wtk`NI޽trhדO],>p\Z@z0 fl`RNp<x@ dA"؁VB)DdaF2MYVv]d};]!wyD1&RQ*3RlQ)xhآ0JԯPA1x@)bbR)RSê\Ro~.l0S'(EIH]$.LO_AuS -RTw.jgbWތ@Zܛ*TRMgt?4Ezacv?x]s~K\bm o6lÙ\W9m=AMuijZ'54Bic+)5=N#vф>^ |&rg4# gh40`VXH8n8A$!a@bpƾǎ0XELJ,Hl~Mou" LN7o$OT6?5CФ,UQ2Jsh15r*Uid)Xe *Uq chX2%ʺTޜ=ٙ!R30̋ˉOC?_\J9TmNE{ -83Rbpތ!$=M aD&ޱ_NmGFiswYfV'hÊ< ͜Q4m*JeV*_: Mm={i=L9݉W˛!w~)]2j*mºĺ77˥ՓCmk5-Ytk,Rqi40`VXH8n8A$!a@b8oS{ -3g̼EFl]{pYJN˔,P99&͆_dNw"'O]k~&p{OX㡭i}!cuO~6Ζ^-hzʩoYdžΠ{]=гvk _tV/%bK&[6ՒP׉׷WB"I:(Ё @F0,`؁p\ABD!CܥcKYxȬMssmwhsk{'p9'/.L} ~BtPFHxȗ ֹK/p['cJ:VDP.hTҡW񝒎BIoØD_;w_zzcųHǖbw}IN9)ұ1?LMXt4BM%gwpL;+)ebIQ*dq}x1?3=)Y5VnҔ-5GiOZçgcXI=M3+X>;\,m:m+Y4~hhwΔŧilj=gWW'!/eލS|pߛʟtacQ)-7GN}mJԴF+K(Ё @F0,`؁p\ABD!oF)fz31%bm7 y+ $Lo"aS2ˤnQ)'*CKQ3X)ޜcFT&T67~ M*?S޺줇z40olC/QW5[PEH_&QwJfJ#3LGЋf~'b=p{ii7;ĐG$̴7x6ll#-ӓC+QoVitg{9} xE?S3lLW1<%m?d"-ޑKR^Xt{&:S>N<{{J5WylݐxGohL'4VkB%J5ᮮrMdkMD?T @#  B -8 .p| !Cġ4msbI{ L{&b'֝VA9TKH[:޺\Oۉܡv?pSeZYddz%+w([7ë.Uugyw*שTQ*F#?U+T Y4m`'޺|揟>2)ty_b#-*/Df"G"T NEZdgeT^/i{22$ώ^݌^S\TtlC:.5rk-bwXfK#k<\Κe[g~9'}zOXǡ}w龿{ -ҥ(KS_ ]DXהOk'J=kv%*ҥ? -|TY@`0 `+,7x 0D -Tvn3 wq&ne0gXβ͵/d.s\=|3 1[+ -9!]F%1%2\ ~uU'gǘG/J4ZU-h"*7g(Y,Zyte{ѸNJ"N![?F>E4T53$\ wplЛLM -l,Z^.#-#(Ji}5bM3X[sٺܷ4`ۂuuzݐГhz(?{O]d!ǮN6#[OmO>5WHDەh@`0 `+,7x 0D -1{G4f%fnz3x)nmk%9ylabX\=s/psy) wRRDo(#rz7~B/NՎX4X:;b̚lʼѢJ:lʼѢW%/ csbtXGyy#EDsD]\bG̺!~'ᢤzfsMMg"Aoz0?df>b`:j7i۶}[nyB5]Mц:94m~6iK]Ѷ{mlvl Q׎ ShU; i" }XJ`.Llҽ@<2JMlm')?-P%cRBNIoL3MƭkiS˿FϮ-|{mCGFrǥtӎsk!ww))\ǿE OE٥⦛ɡS‹W9έeq\Z@z0 fl`RNp<x@ dA"VҰ^4ȴĈF2}eJ}.}aS$=/s!QerN6R[RV.W)abCO(?+ŷc+YoHS)EX6+\J)V )ˇqy( |.(ż^jX2(w=a*m׍Xe^; Q*BZ<#;+r@_)2ߛJ1 (,TϨz7y]IviT/jty~ZV9>)99өc{_[%gCc$Եik;xr^|wk{I _ZFYڊʡRg:\F۹V&3/8P-P=`3X -6 )' < 2 B ο^:\i"&bh$taJ9 ĺn<]}kC+5nkwH[\EBmd5LrQ:G%+6%+2S(FVTǡ0V}=We 3{|nVruq c37L2A|8UykIe/'> Qf]=?!J~'i"&9~,ZRbfQ4o(d{8-pYtJ IzGi+Xgg6JڰekicO5m*Z3-9om8fۿvXv#E|0>-{ή{r -WT%оgزR7Gߙ:T7%\u|Zhٶ:Ѓh`&0#kRNp<x@ dA"$ũu !.=gi3J&i>.Ts.>s>) CAo82<;UIWB2U khx=OsbGoU揸%YVʣǕ2xQ.Qt'?^;RJE)2:Ư\Y +9i2R)mP(ew.J%uR,?xEfp7-+ -\Z7iAR,{Bm̹S ZSARٻ66ЦC/ЖgѶEm(EΔ9 -XC#gSS|˦pߙo-.DŜvb~JyhC"(Ё @F0,`؁p\ABD!RlS1$D[+JuDd_RLQp+_D(9w)>iw.c*C?t/f96 f HLi+(ull׽{a]gNf'0~WK/oǢ\j+kc1F@1c̗1Bȍ#S@#Fz=/9p|<,A -:={[Ժhv'2YZk_NQ/Tc^u|r6j,Wχ;[ώd,_idW}lYnSl5 뎼a|$Um]=&бL&vSvSqmh,`@\x~` A`APAD!G6Mʃ2r`ad09-t-r,١TzS\6v>'\:MlˁgHU'BrZE:T[ϑ޿Aٙ)ttGn-C9Jz>_kyszdgՌ%s X$P`8^@XD@T Qɥ "(ʃ"-$2 򥉢yUoEo4(%EAP taFҠ -dQl8i4f{x`*/bAIJm _y62I6ވ>$U 7W#wA폍=_%^jPt-'d1CYc吔#BBL -)NA\kzTr+W+:L^k^ۅcz?xK:vxKo\c|"OL]I8w|\ө\WT| RGrRjhAR64Ǩ=a˛D>_ZUƳDu9k~HfūKAPAPL~X%Bqki.jKJ%ɕ >G -%xaH̕,K9\*Lij0$(*U]yP*(Zf׮5n:ƞdQ]gxcθ3kz(O'WfJV5j)mhz5Βbëź(NU[i'EJ^=ל*)\f V Nn|8A$A4@b|!(8`cIbAEQR(~`ybPf9&J(J@u!ܠ`_#[I9[upAq&F=2x@CbhӠw=(fAeU:̕&RUx#yコ=~لxf"ٌbsF\P9mp-oH~- % $Ƌ!0pqqVQV./ )P⨚Wn;e{jѢreeT D(-BOk 'O%GMH;LZ{K1deח{G?2˵~8wS){Yc۽|}΅U L\{&P{IjSCܯ*|RbR =~PH{%==4mgIAao܅|(m$/d|iPvV/;mr <,r <2lm%7$/ʄs\I4BmiF+R~pW<'9pQrP*HdeR"(,Bc''џZ^TrDח-ͤtiZj!IjIi߰tt/8wDҽƹVt/{8q?6tKz`i-YϷ|(:5wKZF>HC|v P.,gEC%G-mM_ef[%UMmr+$io}t4׿!4YbSUTGn{ҧyڷfDۛ*)\f V Nn|8A$A4@bp,()&5>ӡ "w<+WBO D.I!7zP_>Mh9=S˞JQFI1r3g?GTRaI>p^cY4H#-y^UJ -9DQnX,'BEʼn8)K-ɅxVn`gسghm$@ںK,d*Z~tzs$]˸vmGMOyS޾O߲14Rͻvy\b4aߑby4/yKS/{kۧGJgDTe,`@\x~` A`APAD!?DbGJFђqh>ԁMB27=)g~75220U:qkpIqzMb7$K)BIzRLksY8#qV6\8G3lNW")#;aNPV!)A/*uY)9* >Ca!I䊉 cwL%EzJ²U-dE-I>Jڗ?E:֖X뗐tM3c{I7i'_b|; u)Á JvoYWq*Bb{eG^Ćj3yZMǴǦG7,K-S@`(@ /  ,p"H *h( (Qze2pRQ8yZqEܚG*H -bA rDQ& Ѿ(5>zMH{CSst^7$)Ju%g1ǿI8c??}'nzRܨ'El5[3ͅ?}զfsg?xI1JW`F5"SK};%\kp GRHr("7; ;!_:߳5ťccںSI3ür#M?;۲u1i!mGF%+Hc}rұhɥ$]ܔn>Cy֝2'HFiT6fmz;kޗo/yyhU\)ʥ<<#ӣURI2 Hp .p?0  -A5Er?PSĨbSc|?b@$8M' -bd%2dAӡ'w jI:AxLO[}sIo9>j~ .)҃Do}`GA[/)Ny APf4l`͇r\ w()J˄5()ըD("%gE()^U%_١pP 2_*.FI!sD=yQ{c!e;ġ?%,'|/'m[ȝ$ul1i_WG:8QK03S)ofƷ%EixAžZu{5gF=r SeSզyکOE.0=Zݘ - -<l@vphp<x@ dP@ "\-|#RAI`Kr 260GdMFPA!AAqTF (A@_ #DoO M5ipPܧEA1ED A+k G/pNĺ Ǣ;QɉO"nEDU[U=2yЮ!&͍z WkVXż$9rHE(rx\\^{tU[O뷒0wOﶬZBZ;IF'WT2Ҿq%q~a zqLG3=qS{4\ l[Jdzvsk %g_JgQ=xfQ|3";gD+?IR@`(@ /  ,p"H *h(rP%ՁiSy|an>Cqr6x!T%Jn (j:+nBaz Wk"܂\!ʑ7rDH*.)HQhkwTP>a^8M<-"uH[IQ~t4|14ߐq褼;b|mU4sZt7>Xa.۷o*wc*uP T#hcTLTm͊iU]Ӣ@`(@ /  ,p"H *h(Ec-z=5-{4/?0lOD!3%q!(4˙ )>nhփ',c˙uo9b9݇IIq^@O)C{^*):]01&ho+CItrj3#J,JpJܼyF!ƿ4%EêA/!?Fk1Sa}x|SNYHYBbxzrzrz<Ɛe\.-v]&*c`bX+PZ+TV "VbETrמo~qxwO{9$!=ֽMDg|Ud-ʾu%켛rnS֢rd]E |Kn][qEܚ$\g-[|__^wTNJ9p=ǧH߇O~?/S}BV^O .sRRysOS2&3- =?_PPN&:6^W!{^_/24=LSU}`5eD˗-S1k=ӔlkGYgiU w*Wdott`y‰uf;OWݝ>3\4)Z1/GÑt@ ` -h0,l`8,^< 2(0D -Cr5(FЂ)j,2[%w\+YCWB"% ~~;_4q~iQ}}~Esi9{f3QϪʦʮʡINW.}؄\w qބqq\B>w!a0NJ''SƅƅECs?:/8NˏНYGfwKZl}::: {Z7> -jVVv\Y~Ysc?n2-̣[r3Oz^‘x nK+Z%"9B?"KäiKݐ7ay(ҸQCMSSRS}9Rr|A"?O feq)9aX%7͐. 5L۟̇RLWd[m/-_{'6fغ튧-?ÿ,k{yoMTf|9]Y[:~fѹq=`3X+p Xpx@ dP a@bIE0(S"5E̼gֈMy0[L -PEY -S'`P\'A ]<-ⲋF(6!GLB!X',)Qڟ?$j(5BF(PCaE=|(1<NWz+^?'@(uUPdtkܓyIc*BZ&}aW!o E - R<ٜOHI)9)l#ʧ'$l;6q8y|9Z@we(\O{Su(L͏Rw-)Aֺdہ/h{c/yuva8S˰MOswӹ i<)by]*<:SnYHYB z0(fVx Ƞ@(%S7(:$gXLE3"FaO;?K(N"rX$ j(5Bσ -:Q^PLC(2a\>aHhҳb=PŊ{xF EF(aqF(<~/ye{-&@(Ɠ?#t!erU"` id &\B"+Ë9>.D($)K4_^jJ(q$f Ŏ[J'x/ңu|)Cjʸl"m-EB(?B-[7PɗYkd['8سuv\vwϽq=xt[|y)'R33VzC?޺53L<8.L` 0`Np n - BPB(B1 O1Q`i0[!$KVCQz8zQ<TW! E6Q_Nҏ=RBSR\R-2^RP -sF)-O/Qj"F) x3O܂9rU"a m :M@JB p99H'F -/NRBNVb%'Ցb F u a}Ý#Ey:L}QBeziHq!ٶ #E%Fgraػ3H#E~{Q9g U/MۥC3+ҕ՟*fch銗ǥ`#P@ ``;8 .` 8A$A!ΕG)܆яR&oq eJenr~FXGRj)vjWwF):R|¡Q - ϗ6zfʑJq(ch)B(ŗLL1I-%KqD-E۬ᥘUϗ_Rr\ʡ7,-]X>nYR.t~ 3R&7Q+R|2L*O֧k iQK9y H"JsA"-ۗLҲYYJQЖW}/EuE|2uu'ȩ&Zod[}7mvneۻ)W6-z>R<=ε['>?E{.{kwj8=TyofxȉRt@ ` -h0,l`8,^< 2(0D -C  ӍӨ S<fZg,"rOL1 !!, -/bF)/Ka~:sOfKq)J97F)nUOS|}\[>&hWhm}R,j܃RLQ+JQJrث -_gA).=#E_q |&ϏZ`}iRJƿL`0/Vv݉Ͼ/E˱- ݛe8R\6-6Q̚=d[觴uV-j̰G7)Cmܙ Tswd`!!a24BaSCQ1R|hi5_3h1`F=yFJI~X{H(ś MHqZ߫BP\ -33 b;ZP&9*H& !o)RF)r Ŷ4&y_vNJjZi &uoM~x)Z1ROCgN&N,G{-IYkSɶGicssJQa[ V(+eEӹ ;3_NL7K)jtmOu WTfF</K$F Xvp\<?p"H !C;N'A0(-dJ2xԏRH?)E@-N9hTKqwBk1{Ũ'4sG -ű;tP,RCqF(Mcw|j(Ŵ1b2]E~3#ZO"yy)9Bz/_C#Cބ os҄Q#^es>N@ 5CHb͚- /M|3M0d#.7q"$z$Sé$O--'SXkdhz}r-g ⣫=5y?p6 ॹBU -;Cnzpж-s]s"'{3υǥ`#P@ ``;8 .` 8A$A!Q|f,o{&9bUCD(Ds=9ggeI$#E! -F(P\; QG?b(,%t6F{+4J1A-#JRg)Lc8K!.)\ݢy+c#KqJQ#Wb#ŋrU0DlF)&>!o…)F-$fq|V/'+%'ŀ/[4Ib)0Rl.*<^]uE Jе#ExLj(zmS{dɶ;kLv)\3l@W񔝖2u\9B}eDu.9K<]ٺ/#TrpמUp\: A04 ` -6/AA"nQ -6]WlaᘆRdQlO;/p4q4MI)&X>HAk⒄x)E~3~|4S=-J|\Q;,R hqmj)5f1Ǣ_a(xӅRH:C).c*!2 A'y.io -ib09B~eKy@v~% -×[*NQukrISIӟ%R)zW_iי$sʼnDesv`m>J;ֳmGڀX+{cp:w 뛹B*təf+kCg>[)_>p\: A04 ` -6/AA"ء:JD#O `rVM`=.DD?lЩxBRKQQ -:StW>(ż1,?z-#^tPTCaWCѡz;P\VCѠ0RC#v, -F{%>"tBFy(^"9rUb_B5)kۇP|upțpF -nۮB-eD yB/+M|iB.#HvjbßCQ=^W!5.З hF* Kd 9`n/+%%7( +EJKQx]cC_Hm3j>M2H#.kmiN/hG~YLZkmgf)|VnڗmK ۟'6E]*).JWtevdFv})p\: A04 ` -6/AA"ءxuk1Rtؤyv9<7hJ ˃3ò?)ŕj)^(EZ#E|KVP) )tiG=CKqZF)vJcOu8c"1S|=|J2L2HUR#ISI8&1v֣O'ӎyEʵv#UܛJώrQ}4x3?40W>E]*zj\R]:p~!8.L` 0`Np n - Z{38 -y%̰m}YnœgsS1pgHа]Fd.R5`VRb5XV<PG^MӜ(ՍCSp۩Z=Ih }&+4 %|nYٱ%CXM?;v&7WsKcXMޚ&2!o*4ѥT)Es|\gDŽ\}\L"Bj~~`ۖg'%^֗0dyϒ }$ >M:|d|:\ԝhMAֺammm_vTd_\'fؖ{W{In_s6w;o+j ;Kgȅ~*+<'r%~ǥ`#P@ ``;8 .` 8A$A!Q -?it E,YF=4~߫`5옢(#&IZR\~=I=9[1\Adu5(l{pH)nUKqF)SRc7u5٧I˴..@)`84>܊{-fnʚ. fJ1C_/$!>;E7B߾+fBN/--7'䊼/;%yTh1|"$QNwimҟ)"O2ԙbsVcc ;GrR\R|%=IB)_pEU3Jq_H^|kث(e)^IҺ{ې7"8]Yib||. ii_$?^(E}Muu[}}o:dXiԾ$@i$sI&ֺz[#~ Xg񳔫GOw׆tu \rxYtlLyJ顂9-C[q=`3X+p Xpx@ dP a@~x{MM4g̬cV涋;Y87.yd+Zo>SV/NԙbzfF)JpԨ3EJQ0L1 J -R,QKZVf7euhEvC)~7ϣ/NWtuIKt^[(E}"y%cbx WO]yB) y.!gvr9i>!U}R{|E^0?+ٱbl>pocx!ן, wRc'R%M2|h)[I1—mU_fL#!S< tYG:xߵcU?E<]jxQl&=TYޕ/K$F Xvp\<?p"H !C>9w q^ h$b3f -wFxr~?L/r(#kqQ/G=L"Y<ŕF)b)^QZP3Eze"3ELqSq%<7t:j0SR!mECބ T|x,7Kr}A1/ՇRKIO )b -u܎1ͧ~h!A~eX e:HD)L-c8h&[OI?lY8dck "{SjK~1_|r$kPN<6'>7ȼȊJ z0(fVx Ƞ@(ԼJ1geΰͲ++zS=Wgr4a9DR -ZCg4oS4J!}K!/{wHG(Й"G-EF)3EF)>UgUj)LT늇ϣ[r+fߞM3JqZ{Dv`c{U!}Q0)7Z"Ħ i'Wd J|N R}ĥ4Nx- ي}^`n^,f2._%RP -thK4K%9z:3<ȁmY8.L` 0`Np n - BP">"1cwB7'K9Bz,\P\BkțpFRl}=57˗->.:Rs× &7[g!W$Qƽ݉Ԛ"^mir,݉)u,ɶOi.qx̰O[Q<{N=Ν97Oo"oK5/͔7wVv3# &=`3X+p Xpx@ dP a@~)k9J5sqa,ðn%.v<8RÖ)SPM6R,UCweq6bE '4tҝCK?fQho֖l^ᥨåFxf7F -ˇN(ńX)r -D򊋷cHcbHB"D)܀ۆ QK"9YK)>!?󥥤e\IG5JVE1w2dϷ 'J(c(j>PJ)DUst~d{KQdsk,wEi9-{wg+vep_ /o}٥7f+IW>?7|rHH z0(fVx Ƞ@(1- >71S"3u:J!:(0Ͱ_ "v=N]Rk uة"߫ǝѮq77RupY?4PCowh?"޾ٌᡨC(tkmh-F=b3e#OzD(ʖcsDZtrU7O i=&_^6M@xώ񁼬@{/F -.5Ǘ P >-9b v'E |w ?N2:H|Dz;T֝d}#Ewsږl;z sVջak7+rٻr]t%/nSb]*8Snz{ݐ:>'|HH z0(fVx Ƞ@F7z)c#F -/B{1RpG~!*F$Y'T Žp#E@cTGg5B1a #Eo¨]c8R)̈ҥCKBQ^/ŵ8-c;5Rd;Rp(*js#HpH]6,GɱWQ -5!jOe'b7Bb#7;>.5v>3ȥ/OHlOI?_讛S(Zɐee$C$D$3ɴxDK${ k]>6kbOP{ws4=o溾M<1WmX쮱K3;g++gj>ޜH}W8.L` 0`Np n -  ^#S,Y"L*x4x"BH^<,DQbRܯQqj)nR|ix 0_fb蛎R\c|(E$'}l{U!:AoDH_ y.:ðibJ0%%'X9#sҸ,!7wX)`h+z7Ex]ln~@x"UKN2Q㉖(dp_mGQdǡ&;[(v=|6>NV7dϼ}:|̆}.5K:t->67b1bHЃ@ &0 '7x ~D@BD{c_25NdZ0qf2jB#4}A;2x#Mg 3Ĩ`aRKƕF>+|TRмgc J{9JZ?/))vϸWh9뵯|,zK^YbZl?LѰXBK}\&mCބ 4SGG^\!'dҲsqyyyY\*71S=66c^cʢ3$ (O;)z[1eZSH~h\F1փ ɶ__Ўvֹ}:Q˰=MOv{0߹/k8|~зotÙt'=Բ63HtHЃ@ &0vp\<?p"H !CG;lSk̊Veﻊ=6^+</{}+3EިW>ՏWn_?#^ȉߨ3ŸK1Qc^8}Y25y)wm.J?6S&˱W}L"a2)Z 3E^ 75UI|BRSy@> !%wһڒpV[xw&Eu%RRt2ʴnʼ+2p? ;Z?v3E7f{&;c:w׊g f 39|e|2"إ{f'ҕf 37r}iM$F Xvp\<?p"H !C~89mB3LnsĒ#l3gl-{fxrS Cöj)Qny('4s5fP -zܨA'bψO<RxX5JSQǰ}/+V`=W -_~w tD)'ʱW1S,M_G)!'rCބ }Kcsg|| <L?"vUm -JO6u1ϓ -)c'>{L[1ScTB1βͭɶU˒퇗%;ڿc-M2wgw>6fpW;_y8"6YrtxsFTfnngg8.L` 0`Np n - wh1DIdras~7{9K/z%If}[)+4J8^4J:SE|Y5:Sh;gG=7j>71S(}W(SKL3?1lo~0SK2ߡIO$cc*o!+icCބ S\rRsl1ヱ y.'ƶIR҆b3[ -zk ݌ 𛄾I}N2v4ڻA73ɼjKUOmm`GXGg'N㷸߽%/Hj^$O ]wHUm{ "ܽzV]Ǩ`#P@ ``;8 .` 8A$A!QJ[MC)a,'2%Lel&rº5@DpR»'j$ -B\OHU\C(|0nOPg>ui}l =BNwG̅w]jrGWTQ1+ZAYVsRr _҉N%ő \&~țp!R\<^e󁀏aEKM~1u۶- %cѝ$[h%kM2d<0j\&;&_`iytozuck/_:w6~1ɵ*ȶ?pRϪu6nk@Pi hO3!T (şF_#їP7;Hu+u@ ` -h0,l`8,^< 2(0D -CY[a0-2Z -.a8V%%E۹"B\$u?IG_j`hVJ#aIy.$,)&GiF"eHVFe~/t) DW:[sO~ڍt\tk"rU""C;&]Ձ y.rGsyA.;Geq[;s\_)ٹ|);5e==[$M,zLnnZ|d8ѓd\ՑHMLI擇-M_%1 uSɶCGih:yr|a=p7.W<%t\us_7W8Eܰ[˦f+O>n^P? z0(fVx Ƞ@(pÖ2hiFSM3!̲m!o/NuLNO8C -Ɋ2}oDZVs -zuuz&FP\:1}y,_cw{U!#D^Y&R&\!<_5 rx /9ޗ#ܼNIYMOsm{FTVoa?=XL>V0dKM\umksXp jK* <[\mnh$O\; B,;>U넼~P=uƧ HЃ@ &0 '7x ~D@BD!v(0ۍU`**¶ظ-$K=w _Ip%_J i !"boMthO4j򨺱cQߎ&s]e'(:vܡdFL@,qcQO~!/:q#6lejȥ]IwɌ=rUl,cGG6%oțpbKFrǧ$!1OMebWMIIMK˓g$tymKxLO0ާds6'bc4AW|9$]*Sqɶ*~qѼ~s\;]nqWYXzRׄUBh߅먴KKMDp56 Q$F Xvp\<?p"H !C;Un RRKMXHn3SBXo"QJ]Ex*o_Ep+nˉRH(+!IFHfcq5$!$!$ y0F#M%tB{ɻT2Q#$5$uў/c_.O{j9W]̕ 7HK5rU/9{1d(7XY7jA7d*?0K]fe|k˓mfucE|\l]:z -VݻaBR_!$-o5=JzDH]Ǩ`#P@ ``;8 .` 8A$A!QsR+Ca,"0o ,+ 6b^F8"%K٥BSBxn5&Jy-"F}4QjI.([I^#F{lc(b3!7 T# FIՓh WKҬW%iU/#I0fycH:IثI|PI oӆ j}$Ner}Bj}"%%.Ζxna,e}$=o2·doCO245M2xrU}vI'˶LT~;i,ozim*־AyI5AfwK=E=F_ ޞFx%9]1!<*UlDI;%i~%YL- Q$F Xvp\<?p"H !C;u?Ko2-5Yٗ:@D - hbL)6mb`DJ e1suy\ßV"G4v#U䣪(=yhEVQgjM;AWc>y,~uG6~{%ydk;H^n^T+^UHPa2!oH|OKI lϧĞ[qx!g,%uzQm>'^cԝ"Hn?FO=0l>3ؿwuzn{aja^˦ruM|zu:g]U߃<]^T@xNX-{wk$$w¶%x`ogp7Hez`#P@ ``;8 .` 8A$A!QJ! )% %إR´0vvfp6Ek - B1&, *xn$msՆ ٨^9!}߫xj\6f!4uӸIw$CC+a߅fYF4 ܂&XۧB䔗NȺui>l&\{NFo-bs}9)"q/-'7+;/çS5- }O~>?u'eC 姒 +(ԁe$B\vGf97R~|FDRR4hRg3oR0BRxPZ#]pI7J0ZRԪ3ŗ8^Z19~0-(žwUq7%IJX; ުP+#D*ﱗd}!&\$<9|/gŞť%.ۗ/`7 z{- U_N,>p$^uE/3dI}UO?t䇻O$ $$Zz&1?eUmmiohXw'=p7+oޓߦN窪ߟ'9EljKk -f=O+^yH}q\: A04 ` -6/AA"ء:~.DQY@)D&=ϊn|0ʢZZ -V+X ӠW5J',)( -ƿkB]>)C(> -Gh]Eg Bqk"xi4.9BzC}& _!';3M03R@JK͎e))XrS$cX( -oI/' Et6f&_ {WpƎ,e)tW gj(k,֢fk -_6eGsGU2W/Dxvj$/KZ~,|&A8J!REqeVHRR.!acBx=O$D9!ڮ z0(fVx Ƞ@(e Lv}w}&6z GcTF3x+!JpZ ^BCTBjU.՘? jULfjCUV91W*1Gew1~lTrF*UyU?~qJ2kߋhWEFH ƏɱW>28w65阓ɆƘ'Ƙt: 1FǃU<{QAYK"jE@RˋRJZ -VحXw{i??gu}7mHIQzۨpEe|Zj6`\/'dfx33؀ן˧ff,ڿs;ӷMIE:$=cTKV5'-lɆ%Lmn2Ӷglh"Wх-{z_!5*f>m$\OrY.U`=BkԓȊM(Q $`#  ' ,p"!a@G)"Eȓ"Y'Gņ"K Bgq=^)&|,frKqiq#U!>|>I -{TyB|,@K DiNyʣR!>JkHc\hcVD,T.J=ȆvGmdx1dt/FGw(= q9"OPW'.SQXV6^>3r~6ǛHIft3<ƍ=cWW#EIѧת_ޘN{EM ƨyO EI)c8Zc4M(9!h) ZK֋.%{MstMwGQEI3(JvnEQR^p/>%y'Q+rJ,^dO9(JZ" -@Z z0L` Xvp\8A0D -ң!cRQRG4 D[Ctu(i&Fbi$:b(%:>F<0k A&wa7H}!gR.()wɩQHl^q3H獗*mhuNȩzpl˿˩/%&TL U3;&|-:rJiv-J2~L~ RQThHʿz k4i쌔&WD?˥r9 ?͛@v{{֩읢.}D=ߓYq2Q{l"=Y~׉ƼӉjBUh^Vi?K`rvIqݽOU|%on+~#кv\`cŬ 痥G[/NU -=&0`;8 .p|  AA"mkiMj}:fb7򦐙DLN%>QBwݿ9(v)iR9(ސ:^P Ŏީ˪/(?w^jxBߏ]<픇:E5 |BP,֭^t5g}J4#(<zYA~1-$|{)"?-$b HSdBQ&6HUrc"'WqS$sb]o,c -1Ub,9F -j#y~ ^`ިC=S* ]L+ꍏ9]:0%\fK~ojtRfZ\7/ d e11ҁzԓ7V>E=ޜoDmGu'% gM/9[_ewzuq{^959a]U]ߝ6WLtlwqkυ*0 q`3C5 3R.dR -=&0`;8 .p|  AA"O`D+v3[A:bvŸ]'o>""7 -گWo^NIBR4L )~t+FrRXNrRJ3Y #'9xsgC0chp$)ҽ.@RXۛE5 u;~dt&e. A%Q}MMIp̐ -b\!%۟2 (8^Z9RpvdꏒtU+mtd}YO:ɸf:lnqZ8m[g$;zw;r|qCyG!}#v>*ހjڶG^}rgT -=&0`;8 .p|  AA"Q 5jh-K~To<*! [Y,]E܋L?7G:SIy,sgN{Rgr6#tm=fbvtm/ -ONXuGѧTsЀt@ `V>`x@ 8qwltt C^=۬ر3]ọr3B8/w 4x\:(o夸AixQsUK3ItR#CN -ǖV˥FBSCSdq.MhP.x)5VCjyʾFR'J)oUn>ofSz _@eR,!.r+ if2ss<;vvǧj:OQw0Q'M?8?>Yb"LDCmdcϷ}WYhkd~VXړt7l=u|Gf1'`m[~F*t.;38xlVoEz%=:aR -=&0`;8 .p|  AA"QPѹi^4DA`vZV_Sp nE8/ -n1:sՌ\7bGŻrPZz*9(2&UbxA( v*jba ]>#!r}G]f$([/kWүAy!J,[U"yqK\;7ƛ+d!-yl9lNV -O;ľjz`CESu!i܄%jwNmgO''&+Й?u뮲 cio;s19ZS\-F,߁f1kdOy#U0tXqVrpH<(H hA4F0,`Np<X@S5DB(xkˌXPFuзo1QS|Eb;-"(D-6&ܭ !,}B$ŋ -A`_b4;yk'>I~أJhN=hkѨ{O,a eF~S[hTZZjEkͻ^Z]3ޕ<'-IgOoz*fTM~UkUSgT¼pGGU -=&0`;8 .p|  AA"&KIJ'%-Mc51MZIlR8kN+'La7Ŗ?5 -GB2U^UBrcŬ4>O.Zo^%M&K? --7*Mq?wX0ohT~(ed^=u!Re\Uh>wYTW)@*F4Y%Oi9^j@@C\/)٩iFlvu &uݖUt$}Б-ڝ+\eI֯8lٝdl^e30|:K::ddG ./u\gp[5]?LC=yp\xއB +՛̴7V>'MU)J Pm+' ]ɚ'v%՟%>J6tL2~f*:8ۼuNkMӶ{ld`uYSlsqK\ůF҄TƟMc /dy34 A3&7ٳqz#9`Rh -PgAn %kNmKҝxF6Y_e$ry:KovGwNY}hӛ׿MœzVn,4Dn1Q%rcrJ <(H hA4F0,`Np<XDB(ttM-z~aqiyeumQ磮E龆Bc~c!J1SNxR!9 $GyiY~aWܗYPq(v*$ǯ?XZ8Ru8n (m#}]$(O]wV$LQ߆P_ǔ9cK\%R3ؔl?Mode>;mXHUC MT Mo[z&KO%껾K44~:8x!4Ts m=C۪~31ulo]rPu>s'[~nY -uT2=.K $`#  ' ,p"!a@G] r:9kM>Ⳋ6w]N7q+trn^'EPF -2<"^#{2S&p%uQO*/)>_/rR*àЛ\W<Z!)6NgK5Fr_VP?")):QIqq8D SߍP\^ ť -LnTYH)nf%d2nZV;|&'95qө%oMJFQW.byö#FļbCW&ZDQď凧9Zצ;?"ڷN廸koިb˾Tqmk%|A\%iUptGͪ-H{xP5PЂhЃ`3X -67x !Qi,NZ!؊G}l*sΟ.n&FؾL;-Tvm}!{h~TXO?GBq#S.1'+B~V,JT7r7ex!??^F:i,UrnӢk},DM7x <%HFk1Sa}x|u{1cc&d2sdz(9_]u=ZKKR"""RjPK+ZKEZ beYd?;-y<vwk?LhWXnwͽIҌ.LYz߂{zS3N/) כ0eƟ2R3]8?-^y~eSs_B.qFܕqzܕ~+[*Oar\r/ފ.$ {bOӽbЛI)b -;/j9o5WSf͉]֬&)¢- -Pn' ٌ.i  iƵGvN67g,γ1ݖ负|x^9pnWGǃS.lzUu ɦ­/i&|{~>+gyh!uu$t {DDw؆ &pZ t`VH8nXD a@b _JjB2]./1, 1vK.-&$Np, ^Y[n>?_"bHhixBdBtBlK~-=_ D%/IGW^5=g鴳?;dR~'gV']Q~J'GgNMKLh&4XM&+ h& fBH3!LR~,F|]Mz`zzgWw][w]?zw{&irYڋ⿫&w5)kυw.wMWtڥu|{|[.`(ƿ{1l F6:7b(Fq2l@A@&0`;0p  A(@Ery( #QIpŞxM޶hP}m$t{+L6d/giƮwN8\cΙg0ݶII'3xģMsdw }`+x~a;N?|}IwkI:v7hcokk =#  @"8 .p|< !CR5Y&EƥrHYXf#!\TE x`5z<È5QWzv9;?bͲ"ɯ+I[}}9F ބ>4f<81-#G71ddb¦dzT>5`4~D<c3GʩDg;$9Fol{mbV1w'KM-־.CN{!'S-G*GcWwpeGwoFmyU1%j%R{;HjNDj~DV5끋 =#  @"8 .p|< !C QR#|Bc1-4/E@r=0E9UH܋<9ėE"~Geʟ!TJRM%q$U)2Q$8J2kB'FOJHV R%$&)\ CS!TB8BrRX05rHc -6 *P9"eQ-ɯb -x@K2/{OH6y T`F7)$q^1MRxjZjz SS/m$roߙ~6$i>j{%MBvV }Cٰkny5n=>%k);t"hmHb>7: *]Y߹Z;j : Wf A[jjM{҄jF)YM'4}Z?yP-P:Ѓh0 `+ $7x,p"H0D -1/hYZ~+1; 1".b%Zb#LG\-M<ۈo akWKnDl R *Re*UyPOUr\JRZ*o*[ǞO1䏖2cꋛoUүdeJV2ͳJV*YI={/PWg(2 G0%ɯ(߭aj 4 {Ӟ8vVfM3I <$$2M[6k -?MS-vV UCfBt%G&O4 Dxq7L'Od~i=}-w|M39kk,Μ$W !l_slx\K7߿ZhhI5=<;\R67kh~_|Oץ -@z0 F0,`؁Dp\x@ BD!D$p$!'P)z=ORe%RsdVVT2U)~a*+;Ko\?6R,Z0 Qʕ.UR\+R⾉RVK!Ye -"B]y(e]p؛pJ.E&!dz3`:/\7z1٤69Y<^%K3i3͒g8s.p3fݜft8wO8E#-7, Io'I99looێ^-}.]/xevb͜p;s#+o6C =#  @"8 .p|< !CRBC#!C)bX֘-b1BqF\!|ײ>ND(X1*#Bk%kUB(iS şATBaH;Pbht\45F^R\xW)UJ1M)wHܧR).K1Uny.y].EJ)"].{)S/K!+SFk. {O)8Q<}Z<23Yo ϧyS2RE/HR(ɈRdamcRlx" U]EݑDC) ')4X֖L0֗?^&ME=g^ߦ9l[\i‰b!bP9-7E -ץ -@z0 F0,`؁Dp\x@ BD!HZA%cęyH! =!8X|'"E!,Fp(RLQJqJ)Vߩ®2RhUJ RظH?F -GG+eё᥸U)E5Q٦8ɨ1S,K==/QWyDަZǫ1SJ(Şk̯B*5JӰ7<"tm -$/ -DG+&gzŴԌ4vD)Vol<,Lۼr.5 9ͬ. };fC~-֘ 6)RݔCOl9ŒXb^!GVp<6yd3q=vO`ƗMm;]ng&Bqmb4RePp45Ѫ+㻟P-P:Ѓh0 `+ $7x,p"H0D -1/eh{v%C{ڈy"ֽK을w?ڈ0_aKD#bIZ%+JVJV,*Y!rqJVL1iR˴=Es=JVuf~kf~xTh6f-ٻQmDb[QsQҧPmQ/_ap*UJmb{+ kTrU,W  =#  @"8 .p|< !CRQJ=&b"t1S1o#*bAJ*YI\u]Kl0teN뾶)˧eMaN;+[ o['N -[HMs|ǟö{#W\8PT/]/:8'|ppn$՛{wwUq]Z t`VH8nXD a@b _ -719΅Ag, v }Lwnzc]$;TB1OQ;K%jCq9#BXcEQlS?2~DJ -PcGECf^.?Iܤ"Ⲋaoy?^!d{33ӽ< -b\Ə?1~ukq˴~#u5 [Yݮ3G0~=kf:5b-јmʕ썹G] G_y=qgA<]k|G^(װE[+4\g5|Ne/&n||F*.ӄ)ׄޠ4~&Z -@z0 F0,`؁Dp\x@ BD!%ڽZz+1vӁ֡Mm~0=24~w."\ ;LV"$+UJULqJUMX*qTsSǒѪ@f?|[2~\|bk*%JU~2~.Ǐߩ=1ݭ]{хc]C|cUy-E\7EUŒj*LGHRTcބUo?EH{|WLgS\z1=3)} UڣT]P5ZۄEG73e]15}`6W=巔}=5?+ُnbAb35unڋEM;XW`Q 5aQSӃER,jNaMxXԜރEMC;5OcQeQ  =#  @"8 .p|< !CRjKJ$BNbl#Vbn!Fbm'ΡMսrUgq5 F;H^u~ڇnTP苚T*3UbRbU2M* -EѪrjrN^T%^*UY,j|We(jFUC>yߪU+ƢYgK!jU_P3l+ Vϩ BJ0E9oFzy!b79 $g#7U;P[[(j_]j. Jm=;zz`P-Kf٬ioټMT%4d*7p_9YU9\Rtg*BU7{0uxJA*[`VP Got1ܳ[%.-P:Ѓh0 `+ $7x,p"H0D -1a-}HVg9ܬqlk(Jb(<hn)$eJM<UBc86}D(n1.cx)*x]edy\)])ErPR ׍,Śqvkك>/z]@~Hﮐj`$mê?M8O+dyo|@Hl7ULN rA6}! lx).VVYߖ$ݑ&7}:ѐ$s`qICeKכNkSl_if`35c[yrs,nY'y*^;T9ݷW_w^'rCĞ# s񛺸.-P:Ѓh0 `+ $7x,p"H0D -1qSOst(D3kPkP -i4};Zt[a9|l^Os4|b+S-ˮ'C ;ьz|ZP5b=D:V דpۯȾ&--?ר -@z0 F0,`؁Dp\x@ BD!CwH^Jb( t112s!aFl^D|yOĕ[,Q@%B)TFB9?#cD*OL\0{6Nl[1LY]%&+{qZ%&d( }@Gy/OҰ@;]jH_:0u0.M,&9tOII Aˋ~OG)IԔ Qy0b̺̞m~?@e"&9yi /3tS1;j=ϺyӾwp^⮕潀eWa懗yA|{v &u"?PzA V!&wpӓIĤ= C\(H4fl`Np<8A$A"ȗRBrL. Eĸ[DI|a|rr)ev!CB||P) ~WJIV*%X$DRJrJIf㡐w+sǨKvxIS)_TJrweXA)Ae8dPDocI^O@9)Ӿ%UdTsJcIb7<%c,rA15ś.CΦzSR鬐̥aDITnt~n\/iVPdi} Kve1ꯧM6?XghXR괗9#Kxq 8q,s<:;RTGؖGvnW.<菉u/IՒP:k$HW)@A@&0`;0p  A(@eC//1,,n]b۾Yx,u.v-pgL!K]B6J$h^U_nLT[(ȍ&@!4c;T&tCREen4*ݸXƄرB7^?3^ =ݸṖ Foэ {#cw#)LNH~c&ћ `f*S? -6k?5)KiK&f>kXƚ¼oi؞CM+C%{JiޖCohϝvyȇaĞEnb|ύo"[Cɬ$LaJʸ%B] cOބ4s# 2ҽC^>LSYLNҒ`(ӉUCIgi{jRWkzbŭ~d+k,-`M-fef^ٚ*%JY;aŹε1CztWyd=n3/h5D;vXB#Z*޿F鳚HSsh P-P:Ѓh0 `+ $7x,p"H0D -1/h4.'S51W}n}%ΦGڈfs3ۆ>7u,6""V2 VDwX[$~*Te8sm;ZUOS?7JUTέJUʔĨRwƱY\}(Yx/2ЋxCI0Il%PE~@UJ!{T($B޾UyaozlARIbZ+O&9/!5?)#҉T5޺UL͍c[4 mլ }Uِ Kgױƶog}_P?\i;u&hBUVʧ8Սҏ*<ΙaS\NXެ7UbsfmSPK:EaSѳhmjЁ @L` Xv` >`A!Q%&?L { }ۉX!b"*[s/q#^i'C!F"5Pp<*UWfTŬT2eVJUqT̄s}բѪSgT+*?SUtJU2YV-Ue:X|gc6"պ?*9jPO#TQaoyZ~>Lㄔdo EBf`WH3815G1RN RNC{q.-a?&1|9PUhˋ SD' 3GֶSl=hifӗN$בCԬ[>+:|3_BXR嫳C意37rSOẴ@A@&0`;0p  A( -~< -\ 5ƨ)jr5b ̈́Qku}BLJ҈/oʢB%r"J(*!.S E8B6i{ 8F+1ՇQ *R)1Op)ۭT80g<||gm|m{ ?.(8h2O(ș(Ho+ -rfwCAj^%ۃtԑP(Hn$RQDR -k =#  @"8 .p|< !CR~v_C9 -9XuW AnIgĝM`A!Q|)@tvDzw;ab"vtQ9LK{{p=GVeR TvDf) Y1eJU,Su>}cj?6T`eTm>5c9kS˖w9uH78l9U{R !"Oݏ )wqFD3D%DkDd J%"H-P:Ѓh0 `+ $7x,p"H0D -18%_fXBc11s18&|b_,ztxED]]I•+ZMB?]\m(KTbUbfO\:dP{ϴ-&g1$7-͏<[Dbbx7b>~ ё=4 T⯿EL;]_EL SҧH-Rv {~F$SSy4o0 %Xܣ98vkrp'5sjMBqVZ C~v+k:/̍~KCgnX孑VKL9$1:^L\_IG~A4 e*M1oRM.&)IE:*jұ8Fwsʌ2Y&!e3eXǂg&^<]> -Q"@?&$/TxTN&{xL&^!*ׄ zS&\0-KQ=hjҖ7s`yu&wLծ}9@]!f+`hC' ]1Ę֮ͧK[ޙn;i/d6K,oe}灪{\t|Sa}g'?𕯯 BO,71?֑5HIQ_\(H4fl`Np<8A$A"HIn|tq)S-ĚElwx];\KcGrcG -!B[o樏|ҏ~+ӈSG~{scii_>Zچr*ҏveaQ樇c%~[0\X5Ε.I~8,LJ ksV3do05)sAz3 8~T|8x?n=}*k# u8E2txb#c*̢a,Śe;^.%q˻}0WUZisg[371id*L#Gi|,qK'BL#ݘF1l|HӇFH-P:Ѓh0 `+ $7x,p"H0D -1񬀜}aMLļD~-ݶ؞Me?^3,Z$%.W>pdPZZؔ $ -2!h+L%cZ[NJ)+0 ONO2hpl8y3!;Ƿ9nkECv1z9gN9᜵7Em,~ץ -@z0 F0,`؁Dp\x@ BD!\Ո_F3,UE졡w n' 1ZZ!4m/;fС3vy:)١sFr>~*K $``3X -6'    B K i"4q!3kXy.1*#X"$$EBWJ)RyPYYeX(^UJaG)GnkyX)E)JG);CßISJUc}]2S)uɓ U;_,KRhfſ-ӜRI>YuuUI~3Ec0"TEXHk ds&\Z7cIS)Lod3Ԥ_ьu3^K4NV>m% uX^?Jg/2 v%m>c9S6ZtہuN -'spX 8*qf:o#ɗvn]߶cr}o8; uvԑ$Y:|[5jЁ @L` Xv` >`A!Qmlbz=khaR@, v=bye̳Է"\:HX&iȓCiC*JDJD(eo4A%"I㈈\7pG}C)R6Bc~71qKz`i9u'}6h帹 YP!ɯ(0^"ۏa(i&od1MLJːo.YoP3"i88o֜h(#>mt^Uj:L~nOqD `v!<1!ǘ3$w:a !'^iZ\t]Au j)k-@EĊ [kAւ""V@DXYB^: l9=IZvOTWZw/ mE&%$]2ط"͹z/C1/g4{fn[HoL_wmߏO-ޟ$Z8ZZYgSkǫ ١ +LV0,@Vhp\<^Ax@ dP@!QH %l6B,2JYKxx%ិ1#\+n {2V\kA1^oC6u˵'¦=(6\u:';&זL/VKئI/oI_IA\j_ס/i8-{%KBĨJÉ?Pmڐ/4& t^g鈋x?3^P/(2L FCBkkjk_ V8817ֲPe__kPZ>4ofpg"uf왅jz>-7kQo}#KPm,B)Ts6o\oP֢8集[ 5Z1$P`؁8 ,pD@TA"vrQF`y`-1 rXjH]z*gXU\Ҟ5 -XaH\ZiP +=:oteVyԥt=˞H@)=dx'+ v+@&:筩J(Wߓ%8}'Z7 Pޏ@#'о|aDT!P~GD">L'LYFӒ.Š%ˏBnl V j8edXS]#c[XNv%1TW cݻ99^ǧ8w wna]fcSZOlCy/}w7 oGCm1//$7>A9dP?l!\A!RQ`F f XvNpn xA$ABD!1{3zyM'?&GF-?xP'?ȏ;L#P(;>ŇW'>]v m#Ň]sHef8t.c$;aD\8P$~E=KA|LP}i>¥/l3OT&|ύɱo|lG=RV3{]2>6/Ƹo[q -\:?0 CbO2;c8|>ܹs*Z2Mqc<[3ضϦsE6u=۲2q;+jG7oĮ Қ 7U-[ ʞ oB?-O >ΟeL` @l`0|AP!a@.Z!bȎ;3\$dwrc?MظMLS 6iNlbY'6ZlܡĎԉ1- 2hzq(uH:AѩƯZl\TocfF1ѸܻuÛ>hc&>بlElCىz}KurXQtQ"_ 8d욭m- @ `( @8"H * BP nӦ Ub.61:{^)30ņ/r'n)2fbPm'O<]-JjtPu-Jn0`<@~Y@_*XJE;a:Y ɒײdun ;m,yNdۣd%(A^MLJdۣU"~`D^RM^z!!w'y>3ˆ t9/&}[^jОa~I /) Fi`;Zҥ31KKjKsub=NRxPuϴ#)"M3-*ɉUCĨGDxb+#\#y<4,\$E&ږľ/?Q "H+=q2)1 -L[ MO&bZ:&mffqhMs闔 Ŕ4_|:_Z\n$6:Ztɥu㔖ƫ%odLoYVL -  +48 .` `/ < 2(B($ᵨD Hf甑8ޫԀ{ɒIq$Δ{h5^AZRLȮ,Nq;(4='uVYyV|PصH+)NŏzP\CQRlFIQ0A1A10•%⺇xaBpUU"F~+"f_p"#uI˕xq7~J&rb )Hz%*jn ꨿ ).hʓO=2'+C?3C, -x,#O~U:ʜv%!OiTSseҲ`x-IY(4_!醧;vJs֗1Oww&=k>"0Fy#}9s& -ڦ]"ұO\OrԖsB_82)rv`  +48 .` `/ < 2(B($=H -h>8tء84lώrO(|dU~d$kHuPެ%34c|0}k]W\G:Ղb|ZI_.ʢ~6~{nG ]Ǔfڈ2cAL}?&Fԉp{pJ -)۶|t\/~1?Sǂ-R|Ah>:]DZytC訛jڵ4W>OZv(%ko#Ӆ+?Hs~<͵4jpw쾟c@9| ²ESĎ"R.yź,PZ#'tIL."Ÿ@ `( @8"H * Bb(#9K(B{||`YW -F*B. - ?jy:A6ykʠPꠔQ\nӯ(:t"--/'NqbNL?(GEq/=JQ{3m"APhTc0xGSpO /+#L9W󂔛!edHqjwvXĖ -LgcŤe$RHZ7[*HKQ܆$jbgg v}6)?fwgM&?,V~9Zڿ%WR^V 551\7{RzVQ|;  +48 .` `/ < 2(B($"X,UKQUrN%\+Ā+("sҧ8 :o8}B&B *ZP R\4/WR|MIqtLZ"kR{?2ޣdv? w؏s`݂FRC")~TIHamcp"S$̇H,*>KbMZINRZR0,S')/kmYk)G;)kIT')]οtUؒIAR<83g GM~ )^"")"{J^.Enq>1/桜f܌R$M]h>Vb_2)Vb<M\`=c}W ھO?1&ϿӴ4@Z$UEIQ()Iv3͹ua"ٵ%EPOQon_9ҷ OLK6Nv"ΚZr(7䨇^ڶkRxɑ˦ -  +48 .` `/ < 2(B(-)KadKCEcl"Jv9:j "AIoEP? tuJF-(h%:A@P&eIq'J+)~RIwhhpJ,I&iZR\%yzRxJo0JH\@R<\"<$;q%Jxu!"9a"A$^R]z1_g&ޖcy~1If0B aD%ҚUVPueil^WBٸ, Mel75i](׊Sĺi oZJl7G(yR1J+)?.~=;=ɒ2&0H -6 p pX > -0D -F׹`V4:.rN/s<#pb@r^K))lZI1T')nҒbaW0Mi)) - l(U޻Yg#qmUNP]t exAޣܘx~D\S&lOޮ$~UFIѵ5L% DAUIaђ&73kM:%O+)iI1V')2+.v<~7rub{))jAQENIz/O7\GP|DPL-!A&_O|r.L(#oRH b0MŌiPɱ+G=zş+Ά51&o)06lv0Y IXBv}7^RBKYkӜG(׉,[M#mYzۖ+c=M7:m)ؙ2{bC${  +48 .` `/ < 2(B($tEiS Gv:'brܢCb (nւbNPKuA -v֟uN|@PR_𷴿YQ7DI1SK -JgJtђ+k2)v$ŝXL"_'w]=ԊX4%H&;įh>=DdևkQR8Fk1Sa}x \[~crX.5- ^.Ӳ@/+OG<Ǘ f9\RzEӯIuޝ4]s[:7҆f; iӹƞidzі>ic߸qJYvl -e[#h[:[{|1W*~sEX],r"yHњ]WY8(Ё @F0,`)^  D!CaQj 0g$ĥkmr^ݲJ~p/ Q95Kbg}ZfD1eo~3E'͠J3'd'NdR>3?E5MɮP~Jao~r5,f_3p;x,A3K̒4dͬfVD3+J|+ƼM׿ꛓ'kߙ/|~~%ɿu'k33?8Y$k^M{5)u{Ӿy9]<[R)N{N/E48KJL^ax#TeQ|τa7PM\.3F(rB>>.|riԜL!'ƺ ؝ EmM3ו%eaWhcchˑuy჌S+eǴ畠{sE=Og7~y*EB3vqKʵ#]{qi40`VNp<? a@b8VF(Ddh)l,UIERܜ'PHl("BSBayr(:-*(eV2V%nP9)JY%1fJa;bR*$YO-R?\R)y~[?uP *Qm@94QΈxUλc"J?.9l?!Rq"-B9P0sӰ pT6fgfO* (S ^,hY! YׯvZOA`v?0vv`8üa~jk\]Wpv\M=]X;Bu ?EO^"rߖ-$I+h'җ:HjP/-+ @vj !aRLo.ldLN.iYP.?#oЬ1iZ !;\Əf_ek?4t/Z7`2kFGA+v毵WT$ݿtܪCW]^$lj].uX$>`IdwuFFK @#  p /x@ dCo/'wD9fl]] {%?ˊ , - K -QBQ I QF%*xcxB;q3/%SmtDL1F)i*d)Q8n,NRlS8ޙFiU)#KyPQuޛNb*هR ~O="^R| )Ӗ dC0}|f<|xjN7: -ޠ9Vzd)v9G{m UqnZ_ZF?M\M3留hȑJ^:sme<{}E R좝,w_Ȟޯ$;.f>[U<}5Y߶ -˭.2Rѝ$7:p\Z@z0 fl`\x,p"H C"$ţQ -Y5\b$z-j:.A).qPKb|X O*RXvei2RP6:*J)3(ܹJqRinN P4+=%QF -H9J.g0RܰT}iTHQ}:͉PpkgR]RUz”Uu97C8EM#Ƴx is'l0͗KM͊gB<;:);s'oюPܠz6tM*csVѦ͇-{vYwngۓ?>p2>R2PQBq4%TFTBq6y$ -gk8_ -ŻsT6DP`ڵG -8IVGLR>vo:kųGy"e."utZH8&K -&|$(N -1rRTNHE(23}Y9A_椊9IE(ʯ~ksjWo͠Fw~h'khh9cYSco,CkfVIneEXXS.|+ԾBuWiUdxFC7[5R͞iM_< :&RٻNM_;^@`0 `+H' XD@0D -1HJ#v'1:~14z'aꉱڈXډv{q%v3x7'a;H^"tHD'S.TU.xSTwF3Nf}׎SU?lxr*TwJUPY@JRUr ƏR::o)3z;2{ǧcx"ŨJGg:; )Jj`ڪ9|ِ/Kx/Svx1M͚v^{ɪ6YB߿Ik[@ ֐x&iG]0w:T/`* ֶ{d[YdΗ$CF?\OܵC-ijw=x߱RUkomm'BX~T_Z#?V >X)MtUj40`VNp<? a@b8ݝJwBۉXw wDUvW7q7&V6g*D"R']Ϳ?TbRfJU|JU>RWY3 ¦ˋXR~*?TUR\:r pݏ˼T/5-գ*7*?,%^EU - Sʎ9OULNѢF -r|ܗĬ %kG5}/;~rQB~ G뻋hCiDmܴ6m޷4 [9>gChgKWAw鋲e1oIg]5w^oHhdO|t4R.:=\lFd`F>)K @#  p /x@ dCġ#!^%f5IfJ6̃sn#cQ -^QIPd~d*U AY\"M ,TۤbLEJ(vbr=Y*8_ O-m _b襼X<ڀP K+D4)%^E(^_.%k‡p?ds2x&v?Ā/+b9+W&KTBA(j T.hn?uɉBC(D 30Q (%^)s֊i ) -E*;G0I>>37ח R|&*!Wl6}1G;jh_ mؿBG66 >37Ӗ#.k׌} Ʊ+? nk{ti#yXWս+jp5Xҁr_3" WF7'Iq\Z@z0 fl`\x,p"H C"$mRQ/D:D1K-5jwQBb(9N -P|D *ŀ&Gm3TB:P]zig=6U)Yb́C)E\ Ght*'TRQRY{?}e*#0R^)MތTxy ib;%%^)b0RxZ/݄"$~5l!W@ Ŵ\ -lf>iyͮ[cr9ڊ7-7=B{jhX^fziXZC[Xzv3Ͼy)GigSz$.HǞ_b%GWEv -r{zed)p\Z@z0 fl`\x,p"H C"|s -ጼo,cËR8]^7e~ɼGD䞴R9*k7R\R*.Y -^ΠuӞy㦩B|PlR|OeZ)(# *g^?A(~t:'/+p BX^?_)OoykT}k)xm‡pB!N?R lCE(2S3s|l(7I@(J_{86ZO]մXGGhuiBѱtYW̳ud2W1Yhqվt7>/{%oîtٮtK.F{Ə.J励暌ьM -(Ё @F0,`)^  D!C/`/H!"fY6΍=$WkXbȤPbJ(|J(樄v%%d[sgp/O6HqT8`1U)EJ)ޜ5SQNY3٥P||Jy~Crqab:)ӎkī2w?ugF[0C8Eۙ,7m)B34S|7}Y9X|\V0 ffND)*ߛ_r#|s7q]s1{6Mw<^ڸTuty# (>>1JK;7o6?to|B>-yO4?Y <~t0]ol稔e;ӭR -R,Su}w,>RŐxN -+BeB/7' fs2,vua8o~?7[Mq]uo|6Tݏ|6vM:೴q:9c=8R*;Ylq|#."{V*y7M0t u"uXpriy3" cўRิ@`0 `+H' XD@0D -1H{|?ӫw(Eș$kc$<C)X.G)(eʥ*d))'>^P)ek7(ET+cLxLqRFR*W)EL9Ez?MQ0SnF);\Šw(9(E@J*SԄ F(Z"~Tӟ" }.Nj/--@j"31St?Tvsn} _{?m([GzficCi3EQm~e-0V3؇Ǿ\)UUב|ɻ(s_:u1WJeEB vruQ)3lȟZ0~5Ui>Xe[Qd˗c#wVZz޻x?xWWUj}OTj]UP -'5pF^YZ A3{˚}s%/[ϰ)G҆CE:f=IҦue+l _2/WJ 9jq5{^=Gz%otuKo_Eb˥tP`{Fh}4p\Z@z0 fl`\x,p"H C"'A>6~|I6-U&:U*'eF0JҤ +Ș*+?W?UJqLtOU[bTj(쉎ٛRRRQ)J)f)LA)ΚbYR>qˢQ?Dzw#%^b{(JL Hʓ>SKɅLɎK fb -_x.$g_m42jrHUa,W\}w mȻ[GLݴ6m>>ۼR_̳7b#5hG,{/="G؁\WǎGM%vˤEr}=K"_.M^#:Ѓh`&0`;8 7x ~`A( q(Ӆ%lha]osf: H¤;(TJ,]*׈W+hWJqJ)~8 ˚xf=Sbx4Ƥgi*pBP*m?H¬TKٽS<]MT)÷‹(]ZEʴ_}pPK%LPrm?MNRE;ܐrP<O8f저uN(6yKRUrd]v߿2/ht_GgrcWXl,%a6:$;ޓy/I)u"Uq=q=JSu_o -qYJn0sx_BB '3-3IYBV߼n}HwjΠ6it埱LhCiYS gKX) Ų>޻Br/ ) ;7X?K?o!-[YW>[a۫5\ pv"A]jWȽ*5M V=Sj40`VNp<? a@b0~_ 'ۉнKYi'nb!fbL'oW#.o#lD!R+~&JܤdeJV+YV3L⩿ a|AUfd}XVS5oknAVb])oP2+adx@98G)L/BVbo=~`t‡p6VI`ff ͗ʧ dEd|bZVZ0+C"+-M׷`rZYթm}/j*ǴrlG@=Vw&]7 ;eM4 X[1u˶n^(؂i賬s`0UڀiEL+Ǵ2uem.ƴٺQÏisۍbYoWjᏪ4j"{4ɬ@@`0 `+H' XD@0D -1/:0D´c1us/ko&&A'>?z{J!+V^RX5ݕedeGo%Ӟ4KGbL}X̚*UeXyYW*UmUPm DUae(Ê$Y` TxGQRU٨0%?B-TJO~:6d?Q#9:cXY{|g׺ Wa޿}+ <ق?Lkkآ'4UD{huFjjFmOi"叮D{OV -tL` Xvp@ -8n< 2!QAPvɪ"NBhd$b&K n"]'1"tq73JU~/*UyNW*3J 95MӪ~FPʇʰҪ(Y١dŢ )A 'k:C2nCV*FY9,݃ܳY>t!p߱} x/3;qkm<74ϙҺu4'ӝ܄]7G[B4W\}ݴlz!i\ce!m<>\WH[pY+Vϳu1>qԕ24@;N|ȹWnqgwey[tngٕ|ވPt]ܴ2w/tv,ږ܄qi40`VNp<? a@bpz](fix0~ŹY!q{eǔ+V)qJ)QJ3ynR\;M؟̞A-cLMߵrJ)|ʘJ)B۔R ɿ?)EŽS\=O{(źDZyM2qxU?NSimrq"JYz|‡p7˥bU+f'x& a3QG淗3Y}sY%7ڟhC=:|6nZG#uAuhRR,&KCR̟mӮT斩FoƘ7U{J([`bgmRnJ(L3\p+@R݂byGZg Bs9RU"/L]xw FHq?5C8U۪?83MŴ,1E9.ܜl>+'53>)Riuݾ%W*ڦ bhF7vWԱ#I77cqw=k:LgR)~PJRQV*l-par,+MooJJ {j WH_x;*n;:\߮Ԅk4ZM:Ѓh`&0`;8 7x ~`A( q(D[rI u^Ofblh'4&b gkj}޺w3wpml'U.WTT*OϚnRT=kSX0w=zT;+>c*Y9]%+JŤRlfZ{ofRY>,?CVv|yޝ]o]N?彇N;Q(X%Qye0%mPa!#J;1C8E+@w%5L.񩡸/3 B(gcE3:<ڡ䊦wv`G\w_s+hÑ4SQJM>?MD<[ ؿÕrv~oƊVFoOw^µ>s}н.6_.~H^'=^\ҒZeRZRLg3:ޜ=qt;xs푷T1E%&G<Ī2ufɛR;Xa.GLzQL'RUcf݉u̎cڭ'|sl1; Yg>1;';1lŪ|3K=[$9ڱ',Ҹn`}6khf_mlM uJ˺~vfk_p?kߪ:_O<_6k%߳SLJvq_RkuwDj͈=:p\Z@z0 fl`\x,p"H C"|xŌ0 1Ktǥ5u W_F8gQ!g|RwTJq畇P'y*X43.?wLS 3xeT.{?iǎϕY*nx8{ R(EUbȄfx{@0vd^ %^E)TZj+$>#qx_Yiq |! Tޗ#!.3ii9O=QA>pcGgi[DWs#a*1ouXZ~^2rԯ&ehù@tb9Wn!l/m?W~:l!r݊$\N"D;sR @#  p /x@ dCġmyb(&|b(%*T㝦b#[wV{q'掸kyb(!/%l% - _Bj"SvDTjrRTj_;)5L&̠&Aw\Tc{b‰1UƎ >biaξC%&A!&-ELޱ%OzIk3ƎE"e~ )L'»#jZ힉)I0mژib0KK}L1d⩓O6"&MZch7X#s cΦeZO~Z\ٚG{GUWJ+szk{cwզtsl@UK vqlRˇDNlɈkM8.-P=`3X -6R .p8A$! B F((e"Ft5ְMrez%]/"Ʀ\ؔ?<%w(8G%*PTaj oϬM1fAV)E2v|R1e8nǒ?UJ!z/_&(45a_J*SޯCaLwD(ٸ#J!R?nO!_nR6/;"/cJ:*~5?1uu*hQTq2d:ug0d<=5bKR& mE'7J}F:J|Ɛe2y/ S^zU<$2C8Ed65 2Sb |bVj6&OųCOm?d:7R*+Nt[O9 ]+zG&~cRjk l]\ W4 9}/\[x.Ϧz?o&/{y,XW_,B:_}C:HWH4Qc:Ѓh`&0`;8 7x ~`A( q(DJ1,+ i1ov8RZOwzyˈNv5 pXL;'U:Rt;:[#t0 ;s|41涉ґOU:rGzB#*|;sZӢLq/NBw^KKMBwg1:ABJŊۂ3H_+&|HsrsT֗`9t$7ۗ?g (7n4{hsj2Mݞ3鮎ေ54i>=`\_@+dXd?R$9+ )uwv]>m!ݯl!ޖJJ Rh/ںohOԄh"yh2(Ё @F0,`)^  D!_$Γm&DwvBXO^Bok'oaK"NUsvJV~'eʩ*Y=\^=-tUc*'VeRi܇YYجv d&^uUI׾WNj+ Qw]c}]Pt_)ala:gWYK]RcW=p\FoqW6/~~wt%߿%un?F&"W| =2=F>hON'8F-P=`3X -6R .p8A$! B RL}1&Vs,[ja''R)#-&*&V:+#Jٷ[rҒTZI=g\(*#|AKM8ZBbrT&ΚڱV%%JK檴iZCK<7㋜ b %^)”-/BsMNQKrX6 R3L"'E>5 ܜI-)J\ۓ-x,mߡU_Kt5/:9 c0t'fc\aY5ּ l;]>c`5)>AK> /u<&o[ZBϵĺNb"XD=L? -TJ)f\ -tL` Xvp@ -8n< 2!QAPNd>oO\rf-;w=8""u{v쟸RqY*G{U^)I%sQ.%Wlgp60٘GSc*8"WY4)ب,p* 3=2KR ,pvL>'gY"~<'8+1a‚䣻~‡pb  >^'-3[㩙(/(ºFɁ gi;CT޺DC_aXLsWG7Q}w01qƜB!$;N'q6YWYطz+"Xk)ZjXKZ+["`Xy b@EdYl;Ō=vZyLJgǯudж_3Srxue6LI*Ǹ5ٮy=]ٓbu>a-(E).N;ʡ#B_j$m/~J &04X -6 ,x >< 2(0D -?G%0Ww2šk+% -¹pLO3e3eT\ B=!BB.#_0-NK'Nڂ'Z\z i - (=gL'w̬Ī'`$ 3dT#/P9,ɛFw_?sJ NvE#s{'|1I,Q2#CBg Sb|JrjVF,''Nɱ7Nɦ}cTU˜T^WX2CӶ݌ȡɎ壜 XW\i;09inj)/ȶiy)׻9/Vٺ:t6UmB9tT}.oEgpzLD::<H0 @  '$p  - OlIaZ='RIЋ Ḵ:)21;[ҮPT. 2B"B{BuNܡ&u¤X k¤eЕ¤ϓ>kO4NmRݦSdjaIkm.=䱾ۊkQLX&1cg˗#L $6YI^U 7"LGm/j#Ƥ`fOfXIZI`Z{\ݲA%nV*&훆 ߫$Ey>M=l]X m2Oq\R˺25SV}Ƹdb;ONlW7qv6YIsMxT8!!_x IWPG1jz&h`3P@`;8 .` p"H !C%12OӏXrlE}c<< ꝉX%dӥRB.&G<$DщqZtDGD[YE:A :F]:^Wvz8Jߦ?;F';h':ّeGkGVo#ǎ }כ#9MaH&= -AYU!'79a2dspfG.K )13x/i)<,dg ENSfo~|ĺu yC5u͌1ΘVԒN.}/ }ͱQy#tt4dN=wE~o56GpU7&ddzń;}kV?Plzf JD6k#xF`,`NpI<AA"_Z@ *s ¶v25y-&9EzoWeN?hĬ 1j'K4ּIߦKʌT]FYέkˍFʾuyڕtI^hg [S<oٷ0|`tpożGJ+ӕ LJwM *{9q$f Xvp\@xD@BDA}ADpMccXJw;rqeDWQExVĐ^w\%:IA$+ZRC^I XFz_H>oLwl龬*;)rutmS:S3.5t됺y ?"1^"FqfYVU[~"u%79|KZ_߫$K9\ -3OB뒕,&g`zc]8Y$Qo.f8(H.GRcjϘK ]&cٽ.,m+3)ͣK_d]m YS`ܗyno\*Uv r6BX|JWYkWд׈zDU &04X -6 ,x >< 2(0D -1Bjd͹T*ń56^HLW<($wRWN BB!VyLS3,:1\KHI3J;bFwObwj͊C7ئW'<`Zf#ݒV^5-Hz'PfNIqBw>h=&þ:x Wkմf%;%E,S]H2i9(3R%1+9+3j1ǺmMډBM GdQA3a,S{17TuCjb, ڶ4c/wKM,S픤3/E=ǞV-~f94+K;Ь4mTѬN4+6Ybn݈feN4+_YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYw7+neGAϱLβͶ4^@0ll@_Vf"@~ OZ:Zpr #P~|09l~У}p~g<IÚ.ՎZ..;s}Pwt~Z)oETnBN"@ꯨ>Z&"vT3.x WiCTg,!/䤡OA$)9#%I}ֺe?~D]=I3kwIvS%+5)oE;1=zs:W|f>d> D}~ZP$~DeCt"w~W4PiQA/Eѩׂ̒t* e b}\ްƘy_$T2A d.i=#x!D4mx WkK`-ŬX_HHs$g3L>3% ;noymWҨX7P$[ס8c:c^XJ$C/܈6e%çmN0x9.trúf?Mø7K^ɿXC|qmkq;p3~Ju *_) oPi]J18JcZmX h`3P@`;8 .` p"H !CӸG1qy6Wݟ-$Y3lbc6#` O'yjQD u]4P~3;BN\Owv0-Fȍ:12y1p͕!3F_ӯ7щ1j H-F묋f;hXM#Fޘ{*1ݧdV?T"Fh -۳_NW,E/&'3S9RSR\ GNLv况ח ,Hb(k:4>O -^-S]THY^j^uF9Ju;ԵiPfQI5G)w~;[OSm:';>p`x~kSZ>R8VnmOWV<>hRxŎI%E\ &04X -6 ,x >< 2(0D -`/kO-Um -P!V '0^PB}~25uZP(tzNP\Ԃb˓:Aa@P51-O^[H'~Ag amic-ZR<ܺ !q>I[^GR )!)0 )F@R<*GCd3:9qKJIefHYBh_r2i1L99 WR,GR83|gDR4>İ};uƚi`l1EW>OY֖Qul\DٻNCutҮN)?&9A;쒃)튷f;t)=P~1[zdsKf*]T9>Բ{bxs"+I2 F0(V0n`^A8A$A!Q|i{E0K,^Kd"H+t_ 84%`eERܠ%ŧS{EZII'"%w ` C;ub5?͊V9VwS")f&[A 4֤6O')^c/#)&%q6$A$HvYIy[0~gcp&ѯp KSSm B?#s9\059K3R{/nc٠GBWɹy {Ʌw4U;qFe C/iY}j2&;zu~e6|;9io-n{.v|=S.{_Dsutt+BՑ"BlNCB.YP[O^LO-m$"FG &04X -6 ,x >< 2(0D -^^50?kgU-A< 2(0D -T?͎ߩ K-a O*7J* W S,TuVA/Z('rB$JB.'Toh6@_-Pj-zá<2q?(Qz@֢ Zh-Igec*W0 ~*Ѣ(g{_DZ[+e}!24dM%#B:[։l)XF̟%&$)-J03EAg6^&*C hQV_;o0O1ZEo_@YvϣG ՔRr(]hQѢ(R(hQJТȾUhQ6m<^>Y8w1,hQТ4Z/NEjL(x.`,`NpI<AA"-ܢxVLYC8T -*{ʞ7 |F$hAqNP.^;(kAVy_+C^>+ﯻ'J[{L-(Jtbva:-ʈDPׂc/Yˈ=c"(&-{b7 (ִ۪x4.*Uio u,)5=^ZH¶j0%SFgR1*)`jjwkxڍ,_1~NP(s~:Ev`9UFY8 -ezu4/<ZF3ְIRWS>hbsQ"(\ &04X -6 ,x >< 2(0D -A2 1TX8d C)0rZ {<P+?^׶ O`uKт"_(:AQ?+{ϓMQ_]Փ7$;hkwhӢ, =g4"(vCP0 ([$GC"meWEhQ#(EjziW+ f|b~!LRNLⅠ,0Axtx_&!w䉚GSS唹Ts5E?CYJg/ VRVQPgiTiTprooGS<+o׼?=о7^0[3YhЇ.mϤ+NO i&x.`,`NpI<AA"QB?_汔B˨(bwU96xyU )RFxH.-IX%ZPEbe<y>~pϤOzc;jt Y5b1#~ }GJuD.m -{eCZ_<&CkG{dx0%#5#9$ELeI9,Ji>.iً'IC^;yAW(Ӻ唹tڶ,ǖPCEF}S(G힡Ί?=)&U̽}ή{g׊wC۽t|\gCM -FJ ct–KߙY512 F0(V0n`^A8A$A!Q+}[,vo"zCFI3"{ݓ -1 HkhЙzZubkI\;)nů4BL=VH1C[hV='wXtV=%()[W)qh0AS$G=ȠW"*䭳aR|T0x _Fk1Sa}x|SYCC YY 1&cͼRq۽M9M9\bb*`V(BkX!"kVDSYo{Z?̶4>ui4;ҹe_T.ܐ̝;'9sϻrn??w^O~W(3?ﶼ ˲rǂy;'*H87ւ?ispk7O8rAFp7Ι{KO0Hd9.|. 8qUvWh:ߤ)ya7&F5q&h?ljZE~bPWhӞbAD[>^6X,;D*,Sr@=3ضlw⫄G/IVΐweEʞzdfXN#qi40`V^AXD@D!qH -Pt>b#h[xd1ܢGF|R@f.Ob"'7N7b<-{%?Q̟1gg~20O4 ꛟm7?K|jRyf̏YlOv'o~rRέhf{5|q8V3ӎ58Q3NҌ5"qQ͸AEo5"g~e{H{43?љ_aK??Fsg~ٮ?:˦yff<>7!f(IeM/{/]9_V;hxW;Gy'( ,u~,Q.:'aoDLERSE(i#Q;c5Ð/aQK)$<FHd<28>ϳ!aX)Nݤi|grO;R90A{B*;PD hyeeGhKKۺىS}@hhq;WvѮ-!ϒwZݐXTŶfs{WW_%˛?!ٸ0'ZߜkkL)p\Z@z0 fl`8n|8A$!QAR2Mˡ4Tč1djG2J!SO D٩sBD$YRTJRsR4t)4*`IŚt)*X4b~X)CJG}R|vnUiZlJ)t)~RRR"b/ż^vN6JARDq>U'gD}RS_[dBQ R)QK -ɂPFO&Oܼ?d|.W>SL}Rt-|B5k|TӆR]TO3U&tTg޿}嶞lhBQVP,F(VPlReFyd/ ~7.t(2UB!!Mr?4QgCB1G EDA[ӡxhE9UBq[of‹6u ێP\ًG(?W[[ԧ(C(䷞Q?kڐ/lMQCpF p_, eɠ(&x; jsBB6,OG7Ghc -S3!]nꦉG{QƱfq״a*{7 Nvez}wJ8]l:,yYC59ѪOsbҡqi40`V^AXD@D!qHW(@{B#`N^wzX{A(r[CeXIbJ(J(NP?P[w/#MkC'۔P\ޤ*Kme*?Wߩ)Ev0@]V45I+B1u&S;m>E("xА/BF_z6$ Vy>d_ BA.~bf+vZτBm05kDáߺa{N;7ƎS^KM57M܎ol*{=^{gK_%[޴oN}O5ݭ߽{E$rbN]Ċ_m!u1j40`V^AXD@D!qe%Py w9y_Za+$9bn|-;E+&|H%D^xS1Geʘl\J%J<)SƏT!!'r8s)N%ee8_%UK-xSG#/Gn|#F_:7i<̈́x\rgӈGǾ(y,G>oȗp#Z|0_ ]j~5K[vDwNt -k8.-P=`3X -67x >  ( CQBG 2-cʐM2#k0=7x5 Q!*%VfPV2K oSv8ץCq=! G=Q̩SƟPܡ'CqBe9bRBᡰ~P|~J(^?(ρ -=oqԎPp26)R*)L9?B]1+J]9 SƟ r$㻦܌$g"<fRļ!|3a(G(LG:z'h_POu҆ç Hg޷tE;':ث3nsڵ^|QJYCd}frݝ:ť_\'Y%/͎lω̉' -(Ё @F0,`Np<x@ d@bԡq <#1-ӬM(B1ĽODk8"a?UBK%oiӡ0LP(EJ(i(ZƍYimibOCKRJqJ)^Wv8/R)G>.9*#1l\(a=K.F)~xf𭜁R"Py?HQdRTJ*\};CS >sR!1)r\ WÙ B($$G54k'}`oLVYbZ6ܭ=36ޭ3>D[v=RlhkbkgϸK{hWcw!BٻDug}E_%~.>zy|:+RGrb57.p\Z@z0 fl`8n|8A$!QARrvIS02Ӎ1S5jfJss(ŵk2f iq7*xR)nU)ů;ʩI*kH}vJ1aARܤ⭛t)ZTfJ));bJo*5JZ.󙓦.u@]BcRbQxvy ,>LC4Sp߱((HfeC$2XB &Pe Vk7i*?\w\ F l. +7G3'3.M>ontV0Ѷ 3cAvj[C&{Y0+p,#3YbIVf%%ّ]DrbNK:Ѓh`&0`;8 .p B :\|\kCpVG1"J>! Q^$Q|.FsҥJ)RT*[z߿[ãRvs!oVř@ %*#?] -Je+.Hbv)ݭnZBgA(7΅#kŭ މ)턔3B'JE<C4RG_|$’#/Oy6D!  v7*WW;A:ppn~`)mxzd6ѦOǛ1R4.b#={&:=y`]t")X|}f=կ}$&,ikKM=Y7#uw_]|}kWz:Ѓh`&0`;8 .p B :șPH1P,,>|)8BpG=7P,M!"^+I2PPW EH ]%+Ѯúz Wl.]ۙ7}3̟"O ŧ*e'JӡxG(: -B}cN=> P&Z=*(&PλDJJ}⃋"(ue R(n 6 0Z9|H&Pdn{_O)WBQrdvj›u+h}Px.H3VƲ*ڴk eWnW'vbN1#n羣U{'!ϡd$_@_U6yLp ˜PXT=]ٛ%>;Rߟ=ʺt(p\Z@z0 fl`8n|8A$!QA <$Z&b"5֘3yg \N|LfpbڷBqߩbUBqJ(~6] -I;vX{<<ÔgnZ*V)ŕHQ8=f.Vt8.-P=`3X -67x >  ( Ch}ay 3]o8{:s+bH5xVEQʁo2 UJa'gnPJaWF -N1׎K&8S>ST)Ŝ.EJ)v)3E1]*xm 7.PY|jL1ÇWϔ׷g=J@b1vba]MC9C`P IO->ذ? dAf'V-))gJPv=C}F /lD{!44־ĚTO2X<-ۊ6I͒cݗĹv |:]ܵx6<~Ұp7>o ¢7G+4Ң+4r۫4'4S7hb=nhS@@`0 `+p / ,p"H C8hwc+!fB!L1Abn'fb=Hl;8.BxJ6·{ȍ[Yd*Y +YU%+q QGg~~B =dŬ\eQYTX\~Z%+?S.۪WJYcqTVL@~+deK@,RgJOTe*FV]CɎ<6Te$TI&ryX<>@>or ړ/ZE]u@N- >O[J3]EH͋і#@^h;18>v;Ov7[ y:^}YYl\m,UwbOt3YrHaGNc]ǥ -tL` Xvp\x`A"ᛥJ|j2<*טXLEq5g6?c޸/OƄ(K3*U)2R)oR)R7+Kq*'T+0|uwP)o8P\<v>UVB1O E*p}?_y1BTPl4! 2?%>C06Ba vu6%?c3n esd(,D&Mn,_7=tҶI5}7կ^-Dw<dNwT-KOCܶf:8WkZHGK_E -Mcn) dkDXj8jTF> -Mds?V?N8P-P=`3X -67x >  ( CJ7CJ뉩wK 6[#WǖQMuijxw_= Tp[ HDL&"|{8GJD*H*)Uc?.]*;dz*ǻU*VRsT?KQ Nߕd7-= U=Umqy,|+FM~pȗp.G$S,犜_LSL1 -ھgƏ^:?MlYL+ц聕4S[N{M5e:sۺى>qonm_T_-{w=!z -5ddsgZ1~lM-I?!ͭ^ʉ>:Ѓh`&0`;8 .p B +wJqT5^wm \b$nح(L -;KS J(>PqJ("c)=gܨTRs-m[Շ`U?,J).U?.RJ.ϫT*ÕW)9?6(`]>zAe - )Lz֮(uyVh%#4z)؂dga6!?cI1#7#u9+54'←Sc.>:gSP5{;A٠qA$sUwRUXkeۡMf;PLXW39]6Oo_C#-9J>Jwo[Aŕ[4 -UϞD7mP?pZ@z0 fl`8n|8A$!QARRNLj }0{ >bKı'5w;!nN#DM2}?S?.UU2YL1eCIc?q߱:/LWUePʮjr#e8Oe(+ǣ#l-Q ?HxDU@ydPONKOӊu#QꪅO*| giGJf0/|!h pR(W.ybfkMeCY. {/tK?Pw|isEƎLM7le~xKE^8ax\~V-YkߵIjJؒ̈́^"t-.|l%EM$Z_uLߵc:Ѓh`&0`;8 .p B :y#'I/ 7LҘoyWYou܂ʗMpߋ7IoEVSYdiVF@Eh\c - Fx h6ęPERaTY|Dcryh9/0,`ٱ#JDr[S1;ve+PR|B4b]Cg -~*& ^nfg`+ N! `{&M}SR,Z6N:_#(HҰՌJ(T}"{J[յ쾞7G 5ӥ,ّw_Oิ@`0 `+p / ,p"H Cx7FCK:x K:!kln*bTɱoBbDq%);P\l^1"[|RXgbH[n5J)f+(QJJ)2pu#\1::C|uGӧa&)PΣKfHOQ(Ņ(uC)^% n+܂D" % - 禮pFnR _Qfx7?iLnt~cmfRjtMY}"ְt]8eMdY4?uqmݛ72{7C5/ĵf'ۉw+zf&аe}z+/-/%㏯HUj5޵k5ђkb-һ8P-P=`3X -67x >  ( 'z.C=wi91o%gW:Xݨ#S3xuG# ]"|XO-D^ *kݍsTSr21nGJkc|*/no*+ ~63y1 - P=~ΫKG*EGP/m*bhܗ.|r:/#lip(E&Tvޔ>;-/OARSb1tD)2/!_)[pH?&<B^_âoit>Is+w\ztsچ0ykDTЯa-bf3cS=me7Xޞb;RwָK:pl -|ށ} w|Kﺏˮ[wmD^t;4 Ѻwwغr]8F-P=`3X -67x >  ( CYBթѣ2Nf)12b&Bb-#%Qt1^ <d9^$\aXIJ"|Foc啯)3Ae81"w6RI!%o$*3+JIU9S9Uf?tR%|o9\DI@]j]J}SǢU(ۑ/,$cGy%291eE υ~3Dχz$];lz9]]mGSAtuÎB9R0mr:on9:ve}+nGz9.z.ߒIi&JD}6$F3. Q @#  ' < 2D -1CPkoAG6mb{|-ew・Xl1I6Hw%#_J=~Q1UDQy JFIr?TlqzNzJ=!+S+?TJ=~=zxUsW2搾sɟ0Uc2A/>)wk?V!%LG_ "##ui0ӟ|V1 G&D˥ʥ$Z3V,k8v}!l7hܼ5?1\lRf1=*ZHb\'ʇreOl"ƪW^Jh^JV,_Vn:pjlqic].OhK;kbnL@@`0 `+p / ,p"H C8hRo%-n%L 1!<Z8R9i"|B|I`o*"HDLPr>JUUUe&1T:vw]e:yWuqFR-*U٢TE=$U=U'i (=?=ܜz\$ *E))fLz-1G  UgéL`χœ?3K/3C0|qnZ'~fz UnݴAڰnݱfNg,_LjtiKnkoD[}{fjv5/<{~5#P zS6Wz~Ѩy]l^|to3䍕Yc3jO⸴@`0 `+p / ,p"H C8|,r~!FK5F4ݜmO d1OWM .΋(FȰ}PĔPPJ(4J(J(*p!/Fh řCKqRl}./U:&KqRRf Wf|QG]wMKu/lE)V\/PotN)LPLtU(ExpV/Z 0V/|g .#C7|h꥿/v`MzrrɅڊajDZWn|>0"SvX: o}~!}#nǢg9:\awϗyVߢm$pm-L Wv/zŞ_&ywi4@[Abk^@`0 `+p / ,p"H C8e%.JeD_I ]6x -f1s9,#Jb rHĽxVo -%]Fr¯$2"n$R)F)?T4w+E2zX\,h|*ErṣhEiyV2{LT=CÔ3Q//p/RĊxSz|1{ۇc!teP|PX@V4yo ~E\0!$ˊ~ dŸ '>+WmMej7%_#VhP}`00|1azk;Sln{nG?u:9^Woϻ[m'lMme'.V?HF˷7v;Il*Q @#  ' < 2D -1CPV J!1ܕb\`{K]BYDw[wpED(&R9oq.!*sI3b?SMTPV$in)"m83nX%"U"P.٣2)˨w߱G##uթMtD2nCDv4ԏo 5RS,`^b,&J]3#ƒ!_YHp0 !/" f%1orcrݻ.6q>0tV0t"nعa*9[6}Q %W{Ƿ{_|;SCD~]i)]-vi^@݈ȩ;If٥DǨ -tL` Xvp\x`A"MղTD&! ӲXl1_ -Qwq2"HD.R=*sţ])9Nհ{f}h9[dh׬kc F11l\<G:ca3y| d (PU^d:)L?1,TdU(y7 RAr@/rD?Q*Hf/2\07 -d89r!+~7)PEj [Gz)a,EAeunm,ضqOv|ΚW񰻯~x;lm$͍mJBB_!R1H"Gn&-U/X~ R @#  ' < 2D -1CPhKR 0^B -iyeR2xK1qO 3w}&qC0VYDŽU(3TC?猺3B=o% U0W&z1;GC19#LF[C@!Q7<AG#|(5JX|-N@B1yy(~!/̼$K3d0,tޤYýʵd=n?[YZYpA%YRCMtmenkyD[iuq:un89+z(=L{bBvq˚,Hk_Ntϓvզ%qi40`V^AXD@D!<4vZvv𙦲1jffYY` ݂':ıE)T&Ǖtԇ@)*9!]kߏ4i+4)¤lx@%(t(U*c4./RW|ԥ3;ԥVC1L.P8Reg -hECu -w @Ё|A@R\>zGA2H -oz9I]K=gBƥ:CN[zPb/k>{)y%h<k*zayWrm!vmK:$vQk!q4vl&ͯ4oacryq `Y:;n\_ח?XA]|v&y8P-P=`3X -67x >  ( Ci'ڶԸQM;oC!6b8x -ؗGsj󣆸/e|Pn¶DA"aDFC&2CR*w+U1=c ǽ?(fN3wAA5*Uhm{_RNVJƏ-TH=(قTnYօŢH'$>yy0b18K8KqG  ?/q<CdhPʮ+*J_^PT؋˩NYaeGTSΘ 8,U?/6sۋq;6s밻9Ob]n_jf/7cM[]􋹩&B.lPβN_^K{.|yP߹5D -2-%ﲦIçӲӒ)u86qvXTH|54ßҰGpKjvߊk4RWh4Oh6hbKWX:Ѓh`&0`;8 .p B 0xc0vv$L Nb|a!bo?s){+q"A|{HA D:D䆑_S#N=H`A /31r%7iz'}~zXmV ղuf%?64-5kifq/揕hKAumG{QƱqydvmtޣK@{YlC3];f]]W b,ȡ9ъ#9ǥ -tL` Xvp\x`A"!u(h0A%Zc7OV6uDPG}р#BD%nث,J(UV*?ҌvTPT揞1¦4Ljrψo=CW*)m*ErG7(++R|2P -Jm0xsNJx 6b?2.D)VHOe_D}Oʇ!_Y*w%#7##P,22ҿjdɣR,pv{uiC 4s+uxsfڲ{'&:J*8QuY >xw%9dEbӥ/#uG\;z"}ǥ -tL` Xvp\x`A"!u(?K1`K v|[ἜO XxAERJ)nQ)ŋў4GêTR)^C)ͣޱzGሗv3ZJ)W)EӥWY|J)έaO㎿iыʄu(s7Ka -3܌Թ3ŬC*RR_Z| gqƖg 0$J% @ey| -TzﻩJ6vTK2uﰦ-R[޸IrԤ^-WQǺWʮR+ZT*>JTJJSXcRvbm=V*^JeϾԣIt+{ -T @#  ' < 2D -1CPJ$џ""Mjr81jlLj8R+>^$>&>o'xH|dPYܤdeJV-e|/*w=|nVmJ%TƫTh* *ea|$(c{ԥ?*֡*qέXg -a>,2,R2ٰ7W}|cOGEK[HU6E+_` M+^2Ke&^Z_zl+}TW=*9vc}"]zlju?Ns;?88\s]os>׸p ?MȋR"xz?+O - ijH;cRQҏ_HuG-J56r̤Me*WK,%gZ=j+zR6X+;Vlg5Reط]>[%S-[2J\h{\m|i:aiϾuTReh;R9J*]$~J -T&3X -6B -  !CQ}*8@L݉ʃO,TMT Ά/.L\I(."'.n"&2bIF>-ٕRSJMoh)Lj?TMihjD*jM{TZɛEڧR4TY2Vr jő*WVYT poO?Zиpj'ʲ>l -)Cs|PLZAʡ7>4*\TuO99S~LzRx3,Oʹ6mBTCAp{!JH3ۑ*= Ujˑ*5*{*?H?Ʌm҅zDTC Mܦ -T&3X -6B -  !CQ:CIzbM j'L+1wK[W"Uja{T#fiN (k'Dt31=1L:٧*.5U~*WuZof~0Z%s<sT*OhʧdCS=jR4U'9URvв˵H4*/AtwZܯԵ{F#TxӳU{ƽ*仺|6gyN \.CbNF2LcH;t^S(u }6uHic]S7!)oy(^3?"?PsGPrxKHI!$ Wg\Fj_D/|/ItzNV 8IqyKEІFaTc{xLT*3t(\>g)mi=t:6Pl 97Κdvx$e˜Eusx+ɦ -] -YO}NG7۶*=E'o,ׅ:R W"MjtѲ>u - `,`N`!\xD@!Q(Dߩ-ؕ8~0{XZ{#qtg?a(AIt5H "/u]u_N٧+$LKCЈS?~9YcOejD5V`cjHu]G-?R&) UvxEwfx'k -0P9ԃ~pKH{5if7CIG*_?+91/[ʑRӳ&H-{l?4Vi7.1|6mFQh7ois~ w]?8Ϟ`؞)]E;m4OAmۻS TUݿכּEhzeX)ߡ^ w'_&LuRyGϓɖH6YDϵ_@`0`;8 , /  0D -1P#6 )"b-&{%1ȎRSeXB[!?1l,bҍbnu@H0bé!WL!CJʟS~e@[5X착b(6rb58!?B*6E 1s1 BGe+ZbS\Ŏ^E:1`Z"$KglԴ<)+6`1]^=N?C_F3)gM=:wTmin-ZGۆsGv\|q6Ͱm uoЮ{I -y eߚu\˳ wo6NmA|P͖ɿ-t_0Vl@`0`;8 , /  0D -1Pť!5$!QK٢v!8N$#,s!f[zUQb_ .`Ma>C҃!wxbŎOc=㋍;Ԥ(6#gnx"u{ҏmfWc^QW?<(c[߾x%J}DY<++o~5L"B/{.tSӸ= x?Ejzfe wS]6Vlt2K?ԛA5bc5=ǚj$C-`+csJGUy־8*p3ұ{|#(66jB|Fׇ(6j*NX@3 ًbc]n2340` XvpXH^A8A$!a@b<ʭHۍ˘lJ3˕UFnWHwwN:..RcFQh -cFpdNiI$w)iw.\$ kT[ -Gr2 -H$Bnq)r"&;+rɭalQ?2%\ vJ<åYR* ^!+2TA7+u&h]g_XQ18C%Uo6|6Nmy nmN;.w}8FqwJ1Uns)z @_na!-B{sŽ]Ni卻2C=;E9:X2 K&3X -6B -  !C?Sfƌ7&*9lZ#=ꐝR#;zb7'Dň +rjP\)vI0d,(Aq?^|8V$4>(VAѨQ`_cAhE7'O+4 -Oa7ME+Ru/,>37 ůNU SҪU*xQʾjpΈ]`Gvys`^"#L2 9yB0Oܿenͧ?}iڿg-64?3y<͙Ϥ+S-S],3=c~*(BvS9Wc"vOK/?ˏ?Z(_ ;'Hk䞭Bw> 6nEoEԓxP=P`#`+p )7x >< 2 B M9c1z/aZy/ k+G4G9O? >$pDK)_.p48QFTmWhJʏ_/1&cb_6m[I[j1Sc;)-'O1|=cu=bexH^/CeW][".O{ ig[x.˟! -T!ϖx[u-'7җAM ] Xf]5k̺vSW::΂=n)O3ZxbP -_[zϠo}K{{'Dr:x%eXI CR҇eTUg0 h`  '.pp"H C(Īھ%q]Ա"B:RbY!UAk8 [J>_+-K\Lp t2k|_a2S ^i&mRdޚדnޚJ׹kI0镸waG5L֨Q*P;Y3cG3/'j[:\rI435D143߻'J9Zg{ ddqtXX.sO˖aikhfj>7 glԱ%BXG?2|f?m):5ZYJ۪u_f'םRwvm~noIyZ5 ter.o:7n 5>qQs`Qr)K&3X -6B -  !Cϛhg$l";D3M |b@(|_ $CIfoH&ujR|S#)O!)Ғr$ďJcK4ozB&ED#)FճaecIQ5h -e+If](|q67XlԊI/2~n•?h&"~[Qv4N:+D>$tf.F"E5It_Ϩ - `,`N`!\xD@!QW+u2RPY3W.Z\+@<Ļwg p/#=b -Q㪨/&^kR+#h~ -9>tQ$~]c%ڟ^Wc[#FP jqFSX=Yݽve7@ݤ}(8oC_#R -_ѽe)r1V{ iEJ#y|^%\  \!+&ء;zb`:6joCfڸw3m:|fL7і=F6۾a厺gi9N>IZ_{z<BWd_ñ25 ;ڜR ɪPgpEOEώx.=P`#`+p )7x >< 2 B >q*&)GgY[eg:Ĉ1}oD7oMDﱤ) )'RבEk0FwW5{ 5 $Ev\ʒx?'ʝPqvOiaȻК۫#;6Cxjܾ06!)1ЧLIm-m U-}狗;:N1@;(jes|6n}3]ogr% oGzR2C[> W/ /vK -<(0L@fl`8p<ABD!0B9Q6yiR5$';ꑼ/($HbD!_&7nVjRԤxMMwդJ#)^BR }11>)~&z7nHϪF?hM.Bk]}|]>}񑱚Кe^.+5pvyZ{ Àl1*$$GFv?3\zzNEwF9nXkR8K?2A=M ϰsYSuC25OSOskk;\vwl~uzIRVz5 @Ci NW$xyRyūD>f*B•o&S$P@`0`;8 , /  0D -1HE>t:B_T&j&r#FCi z)'ɎJ"\ PA:"$[HŎojS;85Q4%:a7L]Kr(;.%IƋR~+e,QdR\Ym=ʯRaj"TTnCg^ź2`^A./%>7O͍ܼ r]JSkڣk]K [>Ըt@1ʹ~zn-mn=WDq{P{0 1lݴ>ҝm y;:d@{t)"+)՟A.ڒ:zAEgE x@`0`;8 , /  0D -1Pe~bY4.%BG9j:6~c&vAKѥ|&=^~1vK]HFE*k Z'+(aiڑнFk1Sa}x|S}1;d!1 1Ę,Ęēӓ1,2n/eum463Ÿ\֊VTDR;ĊJZkPbŊ*)䬿<~\24l9h=pk[l =Ak, }9Ƚ[ˋ/_s} [[,3>ޟ&q5ϹuA y xGޭ_o]8>=Xc+˥o%r<>ʍ.!pA>//?oJϜ9l͉'6j~=Ȅ&xL=WWTAцb] 쪠GӦ̍iK.kΉӌ}[+בAwgͻdqؒ,t,ިP],)!ۚ) -o~;;rỎqx'x_Z@z0 fl`\x,p"H C"$ߊq "ei&,Yma9EW=3+#3ӅlQnqqqq4!qgG˜Nd'!OzO~2(LO sùgdɤLOYlOv'S+ٟsiܚ|fW3ίj9x8A3NԌ4d͸f\D3.J1ߞř/;on=/ϸ/밥~Y_egZ_<ˆL?̬Ys _W~Y˚_vR3z䢳l^Na?9=}^ʓSě,uI&^)EŬ@G"o uՁ)_ƞ0%PםD/ /!Z&_nzn||_F~F~PrX~X)j6j +֚˴%T׉tC+ 5'/w0o0\a9N=ז=IkYpvޟ|Nux.6D-/vӦ&• |%D(x&&I wL"E[HtV[{Q?txZ@z0 fl`\x,p"H C"$R-$FWC+ SF%TH̋,KؖJ(#h -*'ijxws B!+TJ/=ғIat,TO*=!c3Q{NsH9`\1߮ۇe\9ٱ4JN?Y֣]P饮Q?u`CNލ~ -9+1GRU򞈇Z"R6!6C09 g >G|Fn}!N!^syq>#7,'[7ju|25xOPUoוk><4ƒhӆ:sfڲlkme&ڋOttLI}WjVqgwү'؞ܦ߈ - ̐ZkgwΎ=8'[ x_Z@z0 fl`\x,p"H C"$ߊG+ opYYDk&ѐ\aF8D9/)VF J(UBs%?SJ(R?;.5xhb̪CBJ(~bâyܱϒ -*sGe*0xfy)};N9x$W|ZMRU&Jֵ!EC>  -nP"wYW:-c_^WڊifPv=oKҢ,{M>*tlϐ_Ȕ{u]ّ}gGOP@`0 `+H' XD@0D -1HVB(dEF2Ms"[ePDxe?BaI EP ER8J(^!PT ş0Q\2iX( E+WF*1CKߨP޲JqZe惩R/K¬2R,Hqݽ*x )[QH#\_?R:!%_k$Rlǐ”es9>?rˈrD !JUQS3T)Z'hP=_^_ņeKiˋ+Tsdygmye="J>gUoҎB;ZeOϳ3lY\sv훥=33 ˲#'ʎ;0\)@`0 `+H' XD@0D -1Hvp/å ^H!$b6u(b"X^H_J1Q)Gw*{RXRLA"o a9^FHؽ4\5sR<9wLq2SUf2S\)ŏTJRX||lsYwbLzq̕Vg7fMeO@1%YY\9š⩇fH;eWg{Ɏ>6;Z*5S}i40`VNp<? a@bgw=cJ t3EěiV3Pk[ļL~$iJ),>~IbJ)(U+3LR|7RdMuR7R)>cnZVJ]=6*3[65T)^RVv1>RQy)SWS`JQuJwIշ ȔQ_FȚ1S=4CP飖"ِ9]t,Ek,AwSX&y|)]]15S}i40`VNp<? a@bpv!>bX}p gf -eK*`ۦ~43(JI,K)(U*3zeu*ŝ6zR|8mkFݦR|9<<1YJ).U٧JKo?*E9OJqןb{|/J3-Շ ˯P…5%>Jq)*JMaJ -lPQO|hQKDNn0ePN9/$& JAaX)6$)&J -΄x/ E:z7Vƒ2ڴHg>sYh[~8=J[vv=nq- ]m[?_7y:lS\frmYfyVdYT)@`0 `+H' XD@0D -1H`)xnZdF1 vpݢG0JaA%RRV)ţdCRJ)TJư9nX)~3}==J)vS|SW)IRW)ŏP{KyK1SgСiDJ*SPŘ)^@)"w|hG?sdӃy 9،DHX.E1gsB"/Jq3EɕR8ovg{"ۿox6n<1)*+MGό7-k]֦'>eg2mM_ζZ..yLt]ddq/OjŢ7K{͔^ w;+r);ߚ*ޗ(Ё @F0,`i^  D!g/}`1So=vF)x818S.2/+hT)*e2SSJQ~QRaAƍOR,_<c^ZRkT.~T)S)E\V)a = T nRrCq>_ B^/Ĝ\969a؋R;:yӽJQmV u䅹崾 (EC_Baq ztD[Y?c?}ߕ,y*oO5Ȟe/K dd<8;2w`P.6YZH|dfgCvdhWR}i40`VNp<? a@b|+ӵF:h6Ii5la1mG_WGXf -^&D)ų*?ӦJqJ)ҔR\Rc(a/iaXcꆆF%NP,>*B %o+*B EBjJ(&#ŧ,B115RyB=I{^,m -{(e!Z|QC!&2BDF/CH|<1ϗl(rahfO@퉍j͓su{X_TH:OW.6MKO7zpY&ʏ1OGͫô~ŵ>^Jl}Jmn@(fqEs3NjVgʽogdGʚkvB -tL` Xvp@8n< 2!QAZ7B9HOc$#k/zmvݒG1Rp\w Ȫ,>.RBK%Z%?HbèǮ7j)~(4v#_CKqR. ͏R|2RQHcU.}å#~j;^<~K6E2ʝHcRU/:Lz:Kۆ|_H fbLt_N O̍g$r܉RzrK'R;:Af::0R҆ E:fZiZtx\=Nf}Q]i'Ĺg~ ){v핼ݟni;6_Thz.<>;B -tL` Xvp@8n< 2!QEҳERH!a:Ňz8~NEARlqKR,>TSTJ1C)J)ưIGZ|m1CKJ)W٦إ\yx)"M)^ 0Sl-P)G)Uf=R^ Pi\&R?RURX|R:֎G!…)E@tI@(dzOd@N~^0;Gب)Q,  e~ڰA]fN>d|Qy> 󇕢zҶéRԿ3Ab:\]2Z?PFN+L罴#i -Rye-kh; c_vqlĕvl?,xj{/!{-yw}od|8P|4c~9B<#c 8']-cFJǁX|>Ĝ|_^z(sWG՛ú?ѩX|TjPDG_O3+hcӴ!lhE(N2}'îsWŵ❠n)}]*_M&{Y\sS]!wTX)<.iˎ͎vB -tL` Xvp@8n< 2!Q'XHAHEdf-+k~4xCr +E!BXd%yc%P(H*2RlhRdx(Bc)F)ߏ4QEy~h(~jP+IeQBv{؁1\XT4Q_SDJ^r-.)s.)Ly7ŏ"Ե.ORG?vPrB /rD.cs6܌)NWnԬ /r.'9dS۟V{^g 薮S:`z5U=?ɼ;`i-"=O˶iiI:wv~m#qjN*'u'G1~3 .UǾ\>|}DX1J[FMGOU);-?5XsuU+i5a_>PG3+hc}-m:μұe:Ѷwgg]i/q7X\_eOsXtfufm߳wP]yfrI|ўCaΏ&k40`VNp<? a@bpV2qp 7F4,V6.:n|V}GNs4. ?dI ŵJ(T9uH EJ(J(*P\ -|ϱ?R(:ǘJ(*UBœ -[*BUBq>ݫ9?ꥮP\ueOǓ)}D~r$b^|딕`;{QʺwC>  -v#Z69^ AB{ y8|C ywvBca DBW/6Oo./6n*M_7ZB[rYLUa{G㮴3o-e'9zZG7?0U2U]oI]>SzfY̊vN]$:Y @#  4p /x@ dCSN}saLS8+gÎ郡8{b_foAÒ4lf%DeC#gR SxW8BbBq 8Jq#ŸG)*)U;TJR(HeGCM#HqPHq>M)'m{#œP 2Qy)!\ ~R`@ rsF=A!7,~deF͖oJQ޽jCZjhÁ 4ӵ6n^OvcyӴ)ֽoOdcCSO}okq5eϖVmb[ -nO=:B)ޚ!ʔw ->:;RS;x_Z@z0 fl`\x,p"H C")z(kbQV٣So,e+;%ܝ>^)C*)9S)ŝH"1`QO}C)֍yɸth)~Bѫ}^R1RܢRư8G)>_R< .ُRtQgߐʔSk#ɿD)/.o\1=ˊ>$Η y ~d0STۺ_=I|6A U~p!}h㱿axHg\K[b-zo⡉&:>6%׆r޽<}ˮ6o8Xս~ __GgH͒ -7bIǘ14|%f{lp!n&Lv7 8_JqR -V10UJ^)wLqJ)EϱX2LqcQ?^eě*(T^3JWRPqw"v\/uO]}m(E 3ō%(E[Dʴ|U=B)7C@XU_>>]\Lry18e6jߜ|%9MЖ w0+RY=>M -tL` Xvp@8n< 2!QU}7x' ,-21d1SH(qat쏱a1D(I$#=wJ)/(Dd\ O2b8  -+7j=r}[RkL[=:RmCia:`J:i2-֝N7ֻ[nI[pݖxe{M_=Vov+a|p /Jy &>N-;^"rm6>D_!H=Q @#  4p /x@ dC~eD ]N*b#\M,ku {qT&/,#.#r-!e]A2a iʃyPO -2zd*ʾ'3"Fms+$C)W*ǯJQ(Yc(J_gb -\JaeɋŴw<ݱfN}yDZi#.W&ڪO2ʓcCWڮNڹkUt7ʞ$ھLlK,p{S ⦓7K{fen͎.;ڷ$H:Ѓh`&0`;8 7x ~`A('7x0-cf`Yr}Hr"#30CrdK&+$vJ),<lgUJ1e 8cuh9ETɃ*RxTJer{E1иX8c󅟺pgbɍEsX։EʙcJV+!…:1X._F$$3>!/'?'/$qvG[mw\)8Ǿ̦֮Ak|7}5kD,ƞL$Ku>VWW-!iKYgٮGӲe;v7ZözJ*(}gjo;R#U֬K7քwm_,F;\u(Ё @F0,`i^  D!ɷh!$݄n#L1&|X^b!&X@Wqo&}K{E&B-]:SҢ2f' 2F%+"P; ?RU:ƘV=Kex:څ׬1ݣ~@tAM>GJP{ -'D\"%_1 -X|q!0&#?7#'=5'rl/H&'6jZspgז\nE5?I;5(Dvk:R4|侀e˫k -?[?9GZ6]>q>xڷn%޾ɧP[Vo*EoPUi+5pŞ5k5сSF@`0 `+H' XD@0D -1HV=Jw>B"*w)UTemaU3W~󪲪yjJ%T%RJT S>SY噂!J0E@~V.9@.`2>6`(ډz㩪ujWfSUm. ng K&gLyaʚzdhXNvk}l;R>.9w-Gq6g߈<}dž|*V4./#O~b,XB9^ qxբ*n;URYTis]1VQD?`|+dMǗO2 XǬR}ZCS'CCR5C\͵}fs-IVמ'4lXF k7fӏ^FU&SȺGDOmHUoT @#  4p /x@ dC[87"C~!cKmrX 3YSĵw ڈ{ D8B=D%g=*UIWUU*7ViηHUi1$TfT(+TWeus ^O+V@zO.H -QʹeT%{|U SSNEG|N?WxK|K>ܗ9<_TWQsL]9eڮ2C-ס_a\[1Sa\T_ØמqX*uwSlǺ\Mo"}"õϲx`N0V$7˱nv~>ֻe?r%`ɏ+gJ -绻=ޮ[LZdIioډv+Nn#^! h牶TH@`0 `+H' XD@0D -1H!)&EBB "c91-6K [1/v@)!ޥĿ-$ -DZ<$|`*%thOHrT -6JIf$N8UG|P)Z?)%1e&BeWrt)e-k=3P%z|dErGj7%m(<ӕRU\%L!Z|N~ @?rtq>c>Kd3ïJkmY>?尉 _BsDz ?D7jN>R9l& y%Px9ʹӦ;+h˖\֭/OgǕfq˞6{LloW*y..fxUܿ$+ȉo+R\@`0 `+H' XD@0D -18ؠ݆0-1Q2IfZ6 >6+6I LycY*SqJ)RkSHS)EX2|{"#> k1d B%Jr Ye0)I99KP8&Z}.ޏ[O70\rLJ:BF(8uxȇpB1Ut67=ɯVr}"|X dB(7oW#:\ - ڶw-ԞJK 'O̙irt(-Ov=D{EDGS΁="};#yW_ږn=9-|Q+3Srّ3{fGLJ:Ѓh`&0`;8 7x ~`A(;ʃx&F2 -&dY#6|lO4N 1*M|T>U Q -ʞGP%!K'z;nrR1Mv{cχbRbr*8Th+ ^3>O]ԩ3({FK'1Rz9LI~R8F)MC> R -6Lz~ljq&K_<˕@FF -ļxlfM(AuE UnˋmцSJhbTzx̽e.keD[[c/ce{]iw#[,y>/؟ɶ=4k>Hb,z0Sn&3\!;u4;zR&/-P=`3X -6 .p8A$! B Ξ_M>`L#E(Xl[%HuMwq B|X8R^eaU)uʭ3?SF -JvN#\NsHpǘo"bJ(àVFP|4oHZ?uՙT(6!gr#5)Lyo8P7EkchtȇpF -a|^flܗ||^: |$rH)mMi͠$7Gtqjq ݱza,za:}M[{?bhq9%F5ȩgZIG{:Ѓh`&0`;8 7x ~`A( V>{R_lXA?0|ow\,_ -xTL䢑Ye2f*(T#7)ݠc3n|둹#Gb]CEJ<=}w+V~SK7.A ʍЏ`n|PFOOL$}tO$y\pEOFM^Iţ2w3/~œC }#x`%CKɡ[G.{.G-ppu"ǻ=o!+X#RģhAx\xS -tL` Xvp@8n< 2!Q5ǝߢ "BBKz{1q܍tRNx./dw7pXBErT1MSL<{f}jM= e7j+F#mJ#ք]D1u@oT @#  4p /x@ dCQo :bJZl!]F̍B4ɔ4W్Ļ[p̓wH*TI*GJU.S/G*UCU'>lI -eLJ#>z IDY")b/ DZ-Q78e+h.S"\H&s!ƿ\"2/&s@HK۶4/EԅuD_5ԟxkvXɚּk,ug*:fkk8Gӳ{v|gvUX*ҽ2O_{wC+OVJ>B2f%-mC,5h%Ѷмe[zb*QFV\<?D@D!qH(PN!L1=b^b$b[l_T~nx*)[ʉDYT.XrnF!UVX?Vg k4r91N wc=`m~ٯ nL8Y͝Dq8[_)#+˕wSYCV~P(uӳ1菇ԌpN|PzF8?']%3B>!G9=/eg9ɐ mLi}P֖O S'VYCe kU_L[iˬ#Ξi?9ȱݟw~uuU -s۞\>pQϚeޞ3WQ; .o"҉J"H]$9AW?u@ `0,`,8^A@ d@b(RNt%JS@ A(fJ^G?w,&J*k)kk$XDI)Q\ JiDɿ7rQD-IW{oiD&%K"xQ@׼]LGɯ(Y`h3(YY}/%' .%Z~/RQ0 QR#.{bԬ}Q/rx-r T&>Ae#J>@ lM+Ӫ"ҕͣv|=M_*`w|>`5Lgxso[T[mA^Bv|Lf;)$Uy\_wmS -mI7>WƟXlߚ&uA*ِ&_x:-R&-Z^9-/|Zi #<(Ѓ@&0`;8'7x ~! B ␀.+y>|a' 1 ~bi'zb N8@ߜOB ;|a~"_c"rq@-P*7\mLBu$R7W^#ցLߘFTcaDKT:AwP{f4)\#R.)DYf5ʯ+}!V\#xG4ɸڌ@07?!1'dK`vؗËB([;9Z5mIa>;Jտs6tH3s=Y~?T62s=40`3X -6Xp 8p (  PE[`9[LY̱l~Yix#L-zd/ /DDIrH -FRDս & INy%] 5/ٚ? iI-u$O2I O<}8/^8O_2$EErd- -EʯƯʖ9 o)۩Q/$tǂeÂrm/|([#kޚV{fFA͑T]n+;pՆhcSҕ4S{jSyS,hksѪ|GvubR<3YFM"bYO?܊)Rζ)HSkyIPG?| -5%\M%f@҇&O >)=/J%>4M[.z/ݡ-uV/6_}2_K3{\m*ͭ'XhkNvw)Qbغ6ٿv5?oNl VE&807ϗ!pF/# rCI1Ic):xHRԽ?UWeQSԮ Si6_G|=]M[ 9i)`تs:i -QS{IQ4|c`ɻsHw!|93RX;7XO7x.PL` Xvp NpnCA"!ʣ0W%C GbH -zm=H )\ \#bTĘn/&!pϩ5vK$}ǧsq?ȺI0i)k{i$E:(R{5>Ⱦ2X?,XnXTꧼ%m#5CHJ\IeWV~ER\x-J<~pq;ҨpƟ΅ŐK@O22|9"H|rC=[ӎힵޑ/)~Zd5bk~uΏYSn||3ck-rñGwV}ɺ -5-^F[?_^K9R׉ܾD>FwئO:H|s<(Ѓ@&0`;8'7x ~! B ␀:DWL>4,4.frÎZ{q,e|SBKp~AXً_H"ǽNc|q@tڔ'1\7Rǻ3c"<;suИ^P㤚5'188#?"$W}G+G=ՉkAO) nt'y@^ 5)ۃԨpzi9gT#)K$Ae|bz +4JPi\Hth8|xKVj{޿fM7zKzڲ2^ya 'v{ v#Vnl V>/.nrWюMYYcjOBOBOBOBOBOBOBOBOBOBOBOBOBOBOBOBOBOBOBOBOBOBOBOBOBOBOBOBOBOBOBOBOBO'#18C("#D`wxΩ\Xn#x~n~Ic~}_ .Jx/"D jP0A!MbzJ?s*.="!$[ҥP2;vy˺io*CXaane7^U'k<ۊc`͛3'ؚi19*U CXWaq{PXa˼M(56Rh J -;D=[ZFrBe/J $V5'C=40`3X -6Xp 8p (  PenbJЅ)T~Krb/ oln{x _ˇl _X.Xqq hFF%fê%&%M!ɇ}e?Knv~2D# Q̬#QrsMKD_kϖ3tgxom)2tu̷?0^s4mAZ~n{᯽+_!+_!|vݻDl-Tt$X -Ibũ]$Vgz0h`fl`px<A$!QA$E?X2PE_N.Á<1 =toִ=3+}ꚟRo}6l~6֬'7̾U6o*[N'8[ɶi#^xsV]EYφ"egM"17Xbbo;^3ܘ`^d}Voz:@0  /  2D -1CGnAD?DVdAo_[^/$bPB$A#5(u\ꇕV5&W^&qk\w#$px:qe0]w;LUʣplk㳓C}RF~җrC@r쬴eFiHQT][Vi} -P6BohSڼche5mm-i;7޳bw4k}vy՝ KE6)YjR iJ_xOOG4$v7ʌ'<IQ;Ie}eTƞ%_2G'F4OU%/./??GY=.B ×Mf$s@F({쬴eFcFʌצ S}D~7k85dY tvknYXp3Gwr8/c](3=ijoʌ_)o"Ȓʌg[IZ9o.+ߢp|X-3:@0  /  2D -1CGf[1̈́i"jb4<('5^M-ݬ WMSEMLj#f"6[أRQzè(5QSK;5Jv2.jc - DyVq|Q{ڸVdVX1ΎYj:G|r•"ʺRYV~PŕQ꦳cԬ?=!/u9!T!A> dR8==;=G5U[1R]Q?UwJU?{`)mh_uЦմy)*>g;4{ dء5bN1ԽgKO%Tf!]XiʌVIVӑ(KFV\<?D@D!qHgi1oLз1QS,[DkET$;Ix~?/ ΑS5Jߪj$S5)IL8ĸo(y'C}l>wc:W?ʖj,D:k}6x)F*YR%=X7(GdE=ug5Ǩ50F?Fk1Sa}0tuNC6 1l88t:Y_/tj6Zk@ "֊\X+V""VD,,"VZ/s{^9sݝƽLΣ[R%k!EC,{0XKPdžA:_gRژgC _"]Q(\awR1EymXS\?^~uDzSnIW򣵯M!2p%.=`#`+p  -!QAR=OfF|h,5`c쬃q2.֝+D{dWpF$#ХhGO䅧~"iW)zqO4}mOf'?ԿfWr?9՟\OnZOC1BnO2 Nw#We I!2]FXeDo.`/uw7]G]y/H߽~rz?gs6)3 -jQW]Z]i3X3e/G3'򬷫ErWȑ _S*dcaREf>%GyNM =h(`"%qI"RҟeKl+E9EBPT,Z6 P,Mb4_Gp?ZOm"U_gИReKY)[ 9Iџ*PvHHBU-xTirx~s6hT꥔IʴJݱ2XLYvb[e4sb>Ey1 !'g Zuv ;;&*Ώz3? 0 (  ' ` >`! Ƞ@".e^D>Xh(J"[c6.98(y#>"\YT$I*iPKQYԿ!5JqB:PKqF)B)&݁J)EB-<7]5A;UKORC;5JA),yQmM\"|5Ǝ1be"oA).%^UH͕a2#䥟b5ϛpfJ#f ^ -Q - -xR( \0D!/ē`O~XQRQ>NL}J]R̢;L[7POSιIʲ`m\L;Q(E՗-}0鮷1(S2[.7PR;wLּ8F)1^E)~,ɏ,z#?:O-K$&3X -67x/p"H a@bK~*EXJ1r&JKM_ᐝW&aEX) pW1a"(ER\ª1SDRB{X,*&JR*I -9q/*l,*d(ds~Uil1/GL}69PQK?G6P_Z\,mZCz{ν#\N0·x;DZޛoJ8'*1~1~b@ -|CQCƢ?4BѮit(25BjX{ΠctWE>IEeybzKbF)!ix˴Ģ­(Ŧ7fQ+?VD2p$L*bK(E$M8CGك"KB0l<,'E0Q80ԯǪWJ;Gm}t)F֬jSڇ TJ(eZAYR'O7ʗ=y/;ƦҰ*h\3m\s.UY4o,ϵ.,hw2<8VΝ+dWwTjsTvŦrʮCT 6u鄃]qcTiţKurvl*'wbS)ۋMe݆guLo*P=`#`+p  -!QAҚTmi3Atan#,kaJw-s;jMm*;n'|x#k"m[ i!o&756j g+cd=l1rZk56?cԘ?>4c揳Y)*wRUU LCU\l1J5S*iD?ދtM8CJu`Ŝ"䰩I#Ivѣ0[ q\YbTOGL6Dziʴ@ZLѻQ]5?4X7eث_M;Cw(OZS[_{srs'7N}2FS:ph\S4U9ezu` Xvp\0x@ dP B RsHHȚMEDn`^ţ<#{_łx} -{Z( j(DPxPUTFǕ¦ro=(;揗?ctVR\9Z[i"S㤴}}1ʒ?`(E?˩W1zMVT޽ pM8CJ(8!&B0aĄ?e ~)(pX+l**zMzl<հQʸ)t hƦen}ZnVbSi<1>A|͸_=Ay7٘OB޶wߎ6=P`$kyxS:\5A_Znjˏ/]$} 0 (  ' ` >`! Ƞ@".e>zLC14ogQtꖊ{Ke -l$<"!&Sm*?UꦢSKI-KO#E!]F)B);gM%utRތsn*TK1Lc-m*ZԙbS/JcSy$fLaեfRI"G+S"aRҚ=(ű{ g/a𛯅!;APsd/ &CbT#Һpnow3ϦyOfOGL%Ԯ]tS.s\vl_ex}ccjƵ*w&βۼ?7o޿#K9 d[ !QZқ} t=|>תϊ@` -h0`;` ;n^ D@( u)wKS;J1afNW;,M -^B8 -wnA;f2wxL=/zn nyrˀn|Hs=|j=Hz4T 51y?0S$ctQR@tF<:ՅdƘq9 v!ݯh։﮻OҷNk 3~hS*$h_tآyQ1ϛp qPv\sD? |.'+#d?81QۗKoxFnjO1fQEΡ˞,+0f-le{+#ô~'㮿Oq)n}AX# 4|5M;]*rëG|-"@` -h0`;8 .pX< 2(D!KaON=^Zog1f.['cY_@(I,)i8K ŋӡxQ#P4On<!\4P<ДqwʸZ O#+;'Ɣ^=aM¨χKN.|>,yqBqQZ=J$~)z~hGyvuaܥG)ϡ6BގU]in\#oNV/#Zv~5^޹(W/ >Yr$?zH:.=`#`+p  -!QtreB1 -&/f"Z%l:bN507X9 qBp55Bq`E:Q? az(u 4Q|:F?8K#VCa=j(:rƍ=>'i? :k#A(<_P N -.CNu(3L -C7 qfK%X!!H ˟ɄP(%fnMiyg:mm6˩ٔu.e9u` Xvp\0x@ dP B R"çB5`1Q6;$L|-Vw+8BD{j秶~q9zX:ߩN y-o#ΑX9P:(h hwRE8nM(uX(KCRsàȋOSQ&Z$]\%J1r9JB#aR -#Ÿ?yH! {8]/ -q" DA'~hHè/ҥۙj#w2հ2VL TO#EW?MkVbx`mG)gZG8Z~~]1exژΐwvV>m/J8Z픶N w|ٸjr!] -\H0L@ fl`8n^ D@( u)1}ԧyC1,gS{HNߩ1xF>g2BW -Z'58 ӥCcSG:uGO Y=⼁6P(,-1zIߑj(oSԧƫCUN8K#&{gȋ|Y (N.9*F #1R<ޮ>o: r"| ~AD",sB$"X' tJ]mhK(vmIC+vY~.Hэbk|cY>±/\v5b%)62vR_9'z~\׵OꖌqJG/aҸ07ܳeRdh#ץ `P@,`Np<|B8A$A0D -1H]BH1hhb`.9dXW c&`^pc{!+i͌A(*W w܋0Q̌ /@(3"d8>Ř gh :bQ!!_9ET(q)[Vg9ʎG;mdE4Â=Dbẋ̡fQìgS9pԗpn.*xt1GKKe7q'&U,9'rGj&G*vMnwzu` Xvp\0x@ dP B R"z";QD̊E=buFF(D6KBT HSo{8N=ͼ]#nP{ E!@ň=ӛ5B5msPcyN3/D1n Bqt( -D#E("0Q|Dz]aRrb4Dq[a7LOF(HB(C x1C($CA1((VcXyҡY򸍬ޔ4|pa_3=ܶyƾ=s[;*[ɸDyJ٘uBo/8öDB`dttSj/oyqRbߛ"'̏LO.=`#`+p  -!QOa -1O{^굉vuzN>|,BpއÍՃwSCF(4|Hg5/k"kEY~bd*x|oy:F/[ j)#ŋH1FB)/P -%}נ?nN´.uH"Qrɜ#䥛?Hr}ބ3T,/X6B7q&9\BYf\/hA)ԎFu:y.R4;xᦣQ9ܼ{pƝì{j{.oydcٸvSĹL;R_vۜG&rMՇ#'Ҷq-Rꟛ^+/fRtR@` -h0`;8 .pX< 2(D!?}e_)c|GͼEJ6;|)S , +HROkb}G5F.]4J1ksǙytRgѵ}KqZ -F).aR"]LF)?ޥٲ+PN"lB/KWH5+^qvD˞ٍb>oZ>KN$)' ?,򇤂"!K~XcFm[:S|oK)c룔зèX>K,f]-dF8iWŗ~sr9qk!eVqM;#vtbb¼#KLVT\H0L@ fl`8n^ D@(/(%lwK0tFO} -ue|XŰ4A(WV -Z4JZ,RWՙbF)B)_է7V>|1o)bF)~?hY Lq\R&ny!*u`"9^$]< rU濅!p\ gꘂ[!)0'El KX8DVb?S@)?U5;7X;lŔiWès)P5Ynۻ/xiKڹ zMlPz![<-j_LN78彟OT o~ ?Ҵ1? 0 (  ' ` >`! Ƞ@"Xǽ]NmIҢ9laM8)TT)"LyY+>ƞVj)qP-ՙbzRLB)"r&Fw-Q[F):RWKqbH߾]@GP{Rߗx.lעO/ -VˊSoM}_NϛpI!'. -ӧP$1L'g W|AE[EHd7þ\k]2<̇_sY =.[3WKʽ팳cu_<-Stoժbq/o3X6|WI%=L(->!7xPVu7 Ȳ7 ۏoJ?܍ 0 (  ' ` >`! Ƞ@".K=ݽ0&L j1AJ"l6YL*S@-#r;l/#{Sz+ߨ>çZ5x3eԚ8՚\Qjr2tw ld֕{jL 1y~16#3ӆ;1#&Q$b!b5rU 9&R$J:&'HYNJ$YPr`)' -b2䳲YY1iY[5}];2e훦* 7n.Lèr^e7,ji6e׽>±[YqNyۘBͯ)-2pn`ח\ϓ#mSOƈ Nx`m=+/b}~hץ `P@,`Np<|B8A$A0D -1H]J#>`#%`H } -"D>Y2F(zNXuA@ m:kߧe' G-bF)Uǎk5Z4>R~j]x6(Eiy#"o(Z)L^rK,VC_MH]?Fk1Sa} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_sales/metadata/snap-6086383195474739821-1-c08c5447-c661-442f-99c4-cd2203afcb9a.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/partitioned/web_sales/metadata/snap-6086383195474739821-1-c08c5447-c661-442f-99c4-cd2203afcb9a.avro deleted file mode 100644 index a90dee9923dccc0b997f8347303d51efe4955d8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3817 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 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/call_center/metadata/snap-2775996447369347456-1-e1cb8c91-e49d-4c3c-b678-7f9305c77c68.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/call_center/metadata/snap-2775996447369347456-1-e1cb8c91-e49d-4c3c-b678-7f9305c77c68.avro deleted file mode 100644 index 961da67b26d4783fc9735e342e8942adbbbf8038..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3783 zcmbW3UuYaf9LH^ZXhaQBOHrHPbg|MvV()VQa=}82tq_oEP5RWvV{UgYw`FhVIy0Ng zSr4>W(1$`R1f+=IgJMB^XiJ}ql`4vTQLsJPONA{*5oU~K$H>NUE$v?Z1OlSViPhOyWrR>3!r1OCU{{H_;eX{#Gw1-3AfWbZ2-q&DcNwBlx?$kF7Pt)wvqe5N+D2xLlV#$z z%VgGz{7Hq^CCug2Vqf#1l@uR`nN@ECieVdf-8|Jhzex}2Sv&Nxr}r{ zw0;v_QRAUCn=*dMwIPv*!+qJQC5>t!2QhxiLqZP)+p;su8dJ85=&AT=O65R8V3+^R zJEX-0B7g5!6e;EA__CysOzbjfyR;T|N+_f{#twq6XaiqKOT^!PFtRldAam&{)sGM2obZ9T&+wE3L?{ zXNPZvCbp0{##_jN4DF0&IKQ!HW-FCawOTF~D}{2kP^=V7bPQMWbs`EEcO8hSWg;N zuPbS_3Q>{>sj)3mq~LT;4MdI2bCsNvAOhsa36Nm|&a!gVz4u4VbWqspP{-D{a zl$B=sd80QXn;Mc!0{`I&4`3es^Dq@ zNwvl-DpPzZRL!dtLd~2=r^6E>iRysm-qT!>;w=)5P3}me;`Mmn)cLFQ*Y(|J7shX_ z%$(oz@yL54liwU1JChqfaP;@y&1>7H*0y*4{FXib{pj1@{e1C@x9(qi@G<=5t@R7{ ze!06*dHBa8e~cf!w|DoMJ0IMd{JM1D+#`RVy?y0cb_nmm)vrDsA_T2eo?}vq5uYYy7@XeTwDlN%d}^!@`|VhjNQ diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_page/metadata/00001-1f5b49f2-946f-4c61-936e-a87e5e77dbd3.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_page/metadata/00001-1f5b49f2-946f-4c61-936e-a87e5e77dbd3.metadata.json deleted file mode 100644 index 536e785e7163..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_page/metadata/00001-1f5b49f2-946f-4c61-936e-a87e5e77dbd3.metadata.json +++ /dev/null @@ -1,166 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "cb1b43aa-eb05-4664-aabb-2a7b0947af3f", - "location" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/catalog_page", - "last-updated-ms" : 1663708950529, - "last-column-id" : 9, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "cp_catalog_page_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "cp_catalog_page_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "cp_start_date_sk", - "required" : false, - "type" : "int" - }, { - "id" : 4, - "name" : "cp_end_date_sk", - "required" : false, - "type" : "int" - }, { - "id" : 5, - "name" : "cp_department", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "cp_catalog_number", - "required" : false, - "type" : "int" - }, { - "id" : 7, - "name" : "cp_catalog_page_number", - "required" : false, - "type" : "int" - }, { - "id" : 8, - "name" : "cp_description", - "required" : false, - "type" : "string" - }, { - "id" : 9, - "name" : "cp_type", - "required" : false, - "type" : "string" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "cp_catalog_page_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "cp_catalog_page_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "cp_start_date_sk", - "required" : false, - "type" : "int" - }, { - "id" : 4, - "name" : "cp_end_date_sk", - "required" : false, - "type" : "int" - }, { - "id" : 5, - "name" : "cp_department", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "cp_catalog_number", - "required" : false, - "type" : "int" - }, { - "id" : 7, - "name" : "cp_catalog_page_number", - "required" : false, - "type" : "int" - }, { - "id" : 8, - "name" : "cp_description", - "required" : false, - "type" : "string" - }, { - "id" : 9, - "name" : "cp_type", - "required" : false, - "type" : "string" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.7.ndv" : "273", - "trino.stats.ndv.2.ndv" : "29136", - "trino.stats.ndv.8.ndv" : "29704", - "trino.stats.ndv.1.ndv" : "29928", - "trino.stats.ndv.3.ndv" : "91", - "trino.stats.ndv.6.ndv" : "109", - "write.format.default" : "PARQUET", - "trino.stats.ndv.4.ndv" : "97", - "trino.stats.ndv.5.ndv" : "1", - "trino.stats.ndv.9.ndv" : "3" - }, - "current-snapshot-id" : 2884094035664543174, - "refs" : { - "main" : { - "snapshot-id" : 2884094035664543174, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 2884094035664543174, - "timestamp-ms" : 1648057275283, - "summary" : { - "operation" : "append", - "added-data-files" : "6", - "added-records" : "30000", - "added-files-size" : "964241", - "changed-partition-count" : "1", - "total-records" : "30000", - "total-files-size" : "964241", - "total-data-files" : "6", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/catalog_page/metadata/snap-2884094035664543174-1-4508e506-df9d-4e48-bcd2-63d0d897b29e.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1648057275283, - "snapshot-id" : 2884094035664543174 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1648057275283, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/catalog_page/metadata/00000-e5897454-fdb6-4685-9a30-ffd76aebdf56.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_page/metadata/4508e506-df9d-4e48-bcd2-63d0d897b29e-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_page/metadata/4508e506-df9d-4e48-bcd2-63d0d897b29e-m0.avro deleted file mode 100644 index 5f192354fc1904ab0ff2a20ca13b95e6e35ce1a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7216 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 LugF&p*2Cs6P>&YN diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_page/metadata/snap-2884094035664543174-1-4508e506-df9d-4e48-bcd2-63d0d897b29e.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_page/metadata/snap-2884094035664543174-1-4508e506-df9d-4e48-bcd2-63d0d897b29e.avro deleted file mode 100644 index d9ea0bef24371f612acffa7190c3db868210340c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3789 zcmbW3O^6&t6vuTCA|?^D#Gq!Cq7^;3Bhx!QyR$3k3Rz)Qh-N1sY}U4>yJowjKPJ`H zvyRJPKnZ9BiHLYmh!6yorlNR@fnZgCR8`N%+OxO$sCvKm zs^0(o>u~dxTlXA*9TGeiS`PByzXvxo+HE6kUL(i~h^$d)aY)Y#BKx>x_~DLLi`#6*ayunMPZh18dz5Xw;`dJ=VOL#WSNXC630Fj zSue9c!O|fRg~$dp0P6(P{(PsEu+X-VZH7KeOLZAf8!TcK$MQCv)NB_ai0p~&DVo-#l_C0&rM z--c%>JoG9KDZ6BIOypv^uQ;`)P;KNQ%1(Jq=%Hj=apsJ|6z#%$Du0?nIgk+8Wq-2{ z@t8ow@BNA*wMv~W8x)ci_!MG4G}BH=h13iJ7eSx5L9C<(iCBxb0Q9>_&pR*#5b#Yv z^jru!vPN_)3;?Vh;w2UdbWE2$oLztu6N3Sa>xWPIiW|Q@(RG;Y4M8Jaoe5O|r z?*j2I&bw)IqIm4i&C4hhyrDn37S63O%R1r)EV^GzteI82DWC{LYenbwQrGMN{p8SHxY z$5vea8}!`NQ3*O8VS<=DvQQ`+K-w4%%bO*k=z|pD zts^+ayjCKn1wxD?#brl8{@-;apeg`K=Vy17_^kxApc~5L%9T;XcO}s10}ipCQB<|A zL93OBnm`DR?T{h_r+T^s%?MB?t{rkRr47f-3XEt1KA}e(lMsu`UKDIu-?$V%D7GqO zr5I__;&TEmO)hzRNqSC5ncMBeVa37f^&!~UTO~E$kVUaWJXTWKBuQWDc{?2m#Xhqe z?k(|SqKL0bm>eE?3mqFa+Dmh8DdY0{Vm6v2y^UweT;&uRVvjO#{z`H)V!$S4a5;g5 zT5}dvD83M?;#CqMXHKTm@d=qkRlsWRA*M+F7RkmYbEMn(>+zOLUb*x0U&qRecPzYq z;=%8(mCly#KlSXy*1}y2Z=L&M6JKBY=-a)GAL%3ePwUm=w>^5)ziRw3bs;Jp+h(_l+-K zTKRmjHsQQ@anJ7WE4$7=bFy)|M~UX RpZ?^W`R4G=o2C9F{{y3G56S=l diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_returns/metadata/00001-001bf11b-e057-410c-b547-ada25a99ccb1.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_returns/metadata/00001-001bf11b-e057-410c-b547-ada25a99ccb1.metadata.json deleted file mode 100644 index b9f6851fd118..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_returns/metadata/00001-001bf11b-e057-410c-b547-ada25a99ccb1.metadata.json +++ /dev/null @@ -1,364 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "e67dc60d-196e-4ed6-9b91-ceba0c102809", - "location" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/catalog_returns", - "last-updated-ms" : 1663709017095, - "last-column-id" : 27, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "cr_returned_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "cr_returned_time_sk", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "cr_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "cr_refunded_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "cr_refunded_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "cr_refunded_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "cr_refunded_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 8, - "name" : "cr_returning_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 9, - "name" : "cr_returning_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 10, - "name" : "cr_returning_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 11, - "name" : "cr_returning_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 12, - "name" : "cr_call_center_sk", - "required" : false, - "type" : "long" - }, { - "id" : 13, - "name" : "cr_catalog_page_sk", - "required" : false, - "type" : "long" - }, { - "id" : 14, - "name" : "cr_ship_mode_sk", - "required" : false, - "type" : "long" - }, { - "id" : 15, - "name" : "cr_warehouse_sk", - "required" : false, - "type" : "long" - }, { - "id" : 16, - "name" : "cr_reason_sk", - "required" : false, - "type" : "long" - }, { - "id" : 17, - "name" : "cr_order_number", - "required" : false, - "type" : "long" - }, { - "id" : 18, - "name" : "cr_return_quantity", - "required" : false, - "type" : "int" - }, { - "id" : 19, - "name" : "cr_return_amount", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 20, - "name" : "cr_return_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 21, - "name" : "cr_return_amt_inc_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 22, - "name" : "cr_fee", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 23, - "name" : "cr_return_ship_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 24, - "name" : "cr_refunded_cash", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 25, - "name" : "cr_reversed_charge", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 26, - "name" : "cr_store_credit", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 27, - "name" : "cr_net_loss", - "required" : false, - "type" : "decimal(7, 2)" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "cr_returned_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "cr_returned_time_sk", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "cr_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "cr_refunded_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "cr_refunded_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "cr_refunded_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "cr_refunded_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 8, - "name" : "cr_returning_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 9, - "name" : "cr_returning_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 10, - "name" : "cr_returning_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 11, - "name" : "cr_returning_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 12, - "name" : "cr_call_center_sk", - "required" : false, - "type" : "long" - }, { - "id" : 13, - "name" : "cr_catalog_page_sk", - "required" : false, - "type" : "long" - }, { - "id" : 14, - "name" : "cr_ship_mode_sk", - "required" : false, - "type" : "long" - }, { - "id" : 15, - "name" : "cr_warehouse_sk", - "required" : false, - "type" : "long" - }, { - "id" : 16, - "name" : "cr_reason_sk", - "required" : false, - "type" : "long" - }, { - "id" : 17, - "name" : "cr_order_number", - "required" : false, - "type" : "long" - }, { - "id" : 18, - "name" : "cr_return_quantity", - "required" : false, - "type" : "int" - }, { - "id" : 19, - "name" : "cr_return_amount", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 20, - "name" : "cr_return_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 21, - "name" : "cr_return_amt_inc_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 22, - "name" : "cr_fee", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 23, - "name" : "cr_return_ship_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 24, - "name" : "cr_refunded_cash", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 25, - "name" : "cr_reversed_charge", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 26, - "name" : "cr_store_credit", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 27, - "name" : "cr_net_loss", - "required" : false, - "type" : "decimal(7, 2)" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.22.ndv" : "10141", - "trino.stats.ndv.7.ndv" : "5947530", - "trino.stats.ndv.2.ndv" : "89157", - "trino.stats.ndv.27.ndv" : "906299", - "trino.stats.ndv.19.ndv" : "891994", - "trino.stats.ndv.18.ndv" : "100", - "trino.stats.ndv.14.ndv" : "20", - "trino.stats.ndv.23.ndv" : "475083", - "trino.stats.ndv.21.ndv" : "1528121", - "trino.stats.ndv.8.ndv" : "12236563", - "trino.stats.ndv.3.ndv" : "297612", - "write.format.default" : "PARQUET", - "trino.stats.ndv.17.ndv" : "95523325", - "trino.stats.ndv.13.ndv" : "16739", - "trino.stats.ndv.26.ndv" : "795604", - "trino.stats.ndv.16.ndv" : "65", - "trino.stats.ndv.4.ndv" : "12236563", - "trino.stats.ndv.5.ndv" : "1890006", - "trino.stats.ndv.9.ndv" : "1890006", - "trino.stats.ndv.12.ndv" : "42", - "trino.stats.ndv.25.ndv" : "808456", - "trino.stats.ndv.10.ndv" : "7082", - "trino.stats.ndv.1.ndv" : "2141", - "trino.stats.ndv.15.ndv" : "20", - "trino.stats.ndv.6.ndv" : "7082", - "trino.stats.ndv.20.ndv" : "152629", - "trino.stats.ndv.24.ndv" : "1084039", - "trino.stats.ndv.11.ndv" : "5947530" - }, - "current-snapshot-id" : 8816593287668123241, - "refs" : { - "main" : { - "snapshot-id" : 8816593287668123241, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 8816593287668123241, - "timestamp-ms" : 1648057658041, - "summary" : { - "operation" : "append", - "added-data-files" : "12", - "added-records" : "143996756", - "added-files-size" : "8251024083", - "changed-partition-count" : "1", - "total-records" : "143996756", - "total-files-size" : "8251024083", - "total-data-files" : "12", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/catalog_returns/metadata/snap-8816593287668123241-1-fa4fa463-9aef-402a-8635-bd783cb9a133.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1648057658041, - "snapshot-id" : 8816593287668123241 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1648057658041, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/catalog_returns/metadata/00000-1be55423-678a-4a38-98cb-c4b21a42b590.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_returns/metadata/fa4fa463-9aef-402a-8635-bd783cb9a133-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_returns/metadata/fa4fa463-9aef-402a-8635-bd783cb9a133-m0.avro deleted file mode 100644 index 1cf828a26539f2625e2ea4411c7772b176c9057b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12195 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 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_returns/metadata/snap-8816593287668123241-1-fa4fa463-9aef-402a-8635-bd783cb9a133.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_returns/metadata/snap-8816593287668123241-1-fa4fa463-9aef-402a-8635-bd783cb9a133.avro deleted file mode 100644 index 532e038cd3fd68ffc50308854c55238561948bb4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3798 zcmbW3U1(fI6vvyq7_^j%5^5TCdQtHq)xEpf&1S0=p$1YbmToGQvTSGXoz32IckViO zZsHpDLGcSkFrqJ_eJDYFP(dkHu?X>_pjL&7HWWog{D8cuPhuZ*=6=k~-Q64S=50S_ z&hMO=^FRN&w|0DJ>nL0x&MmIdKu!41&LxeuTSzNu1Q`x7wFwQwCjZ`q*s3F!R_m6H z*w1wf*`}+NjxP1AXhGUwE5bl7B^I7%-%ZD0aSk{Jq?Uuh;X|`v(V`77QwBa=MBQzf zS}C8&bS8RZ+X&CA(QQYGJ?Btp1D+sot%ne>FsQXDa-(#^z`7E+1qroyK2^7cj4C6G z#4Suk)@Al5nB50F7ny)MV3Am~JKwAl7Mdn9t1gBux8YDV>`@JSw#@?jnOCA?DThlr zgf<`regyUrf#~FUGN=wcn`$K6rzEDcXhiRQxoBav&kF z&HiQ`(qsY=zjrH&fH17AsV#A7X92N1V|o^L=G zK)^QvktbMyw%6UM(@I4W=7)~gq(w~8B}q_-`!JK+M=vTV*v=v%)f($jsXo!8fq(`7 zV5C>z?*jfVPcE_RD68s1d`|LQz%|n=bKP`dYBMwQq%nVd5Kx`K_TZM?v<7J!d|;wC%(7$DTj2eBkaAtM7jH=Khsup4fWLFBeNczqSS2zpnna zeCYDAmCE{&iyy3g`^j_Tr{~)Dm(Kq5g7IDM>t|1XzU#iL9yMQi_1N3(lW)xG=g0Qv k&))vcS4%sdyzt(hz4-e}JD*w}dAzdsLiz90*LP?8AIlFI)&Kwi diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_sales/metadata/00001-0cd42299-4e58-4a9e-8313-f9e75941c6f2.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_sales/metadata/00001-0cd42299-4e58-4a9e-8313-f9e75941c6f2.metadata.json deleted file mode 100644 index 71d89eea3107..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_sales/metadata/00001-0cd42299-4e58-4a9e-8313-f9e75941c6f2.metadata.json +++ /dev/null @@ -1,441 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "8b38f7f6-e84d-4331-9036-41c80b99beeb", - "location" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/catalog_sales", - "last-updated-ms" : 1663709417708, - "last-column-id" : 34, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "cs_sold_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "cs_sold_time_sk", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "cs_ship_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "cs_bill_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "cs_bill_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "cs_bill_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "cs_bill_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 8, - "name" : "cs_ship_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 9, - "name" : "cs_ship_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 10, - "name" : "cs_ship_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 11, - "name" : "cs_ship_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 12, - "name" : "cs_call_center_sk", - "required" : false, - "type" : "long" - }, { - "id" : 13, - "name" : "cs_catalog_page_sk", - "required" : false, - "type" : "long" - }, { - "id" : 14, - "name" : "cs_ship_mode_sk", - "required" : false, - "type" : "long" - }, { - "id" : 15, - "name" : "cs_warehouse_sk", - "required" : false, - "type" : "long" - }, { - "id" : 16, - "name" : "cs_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 17, - "name" : "cs_promo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 18, - "name" : "cs_order_number", - "required" : false, - "type" : "long" - }, { - "id" : 19, - "name" : "cs_quantity", - "required" : false, - "type" : "int" - }, { - "id" : 20, - "name" : "cs_wholesale_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 21, - "name" : "cs_list_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 22, - "name" : "cs_sales_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 23, - "name" : "cs_ext_discount_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 24, - "name" : "cs_ext_sales_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 25, - "name" : "cs_ext_wholesale_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 26, - "name" : "cs_ext_list_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 27, - "name" : "cs_ext_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 28, - "name" : "cs_coupon_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 29, - "name" : "cs_ext_ship_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 30, - "name" : "cs_net_paid", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 31, - "name" : "cs_net_paid_inc_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 32, - "name" : "cs_net_paid_inc_ship", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 33, - "name" : "cs_net_paid_inc_ship_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 34, - "name" : "cs_net_profit", - "required" : false, - "type" : "decimal(7, 2)" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "cs_sold_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "cs_sold_time_sk", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "cs_ship_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "cs_bill_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "cs_bill_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "cs_bill_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "cs_bill_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 8, - "name" : "cs_ship_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 9, - "name" : "cs_ship_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 10, - "name" : "cs_ship_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 11, - "name" : "cs_ship_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 12, - "name" : "cs_call_center_sk", - "required" : false, - "type" : "long" - }, { - "id" : 13, - "name" : "cs_catalog_page_sk", - "required" : false, - "type" : "long" - }, { - "id" : 14, - "name" : "cs_ship_mode_sk", - "required" : false, - "type" : "long" - }, { - "id" : 15, - "name" : "cs_warehouse_sk", - "required" : false, - "type" : "long" - }, { - "id" : 16, - "name" : "cs_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 17, - "name" : "cs_promo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 18, - "name" : "cs_order_number", - "required" : false, - "type" : "long" - }, { - "id" : 19, - "name" : "cs_quantity", - "required" : false, - "type" : "int" - }, { - "id" : 20, - "name" : "cs_wholesale_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 21, - "name" : "cs_list_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 22, - "name" : "cs_sales_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 23, - "name" : "cs_ext_discount_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 24, - "name" : "cs_ext_sales_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 25, - "name" : "cs_ext_wholesale_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 26, - "name" : "cs_ext_list_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 27, - "name" : "cs_ext_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 28, - "name" : "cs_coupon_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 29, - "name" : "cs_ext_ship_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 30, - "name" : "cs_net_paid", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 31, - "name" : "cs_net_paid_inc_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 32, - "name" : "cs_net_paid_inc_ship", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 33, - "name" : "cs_net_paid_inc_ship_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 34, - "name" : "cs_net_profit", - "required" : false, - "type" : "decimal(7, 2)" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.22.ndv" : "29691", - "trino.stats.ndv.27.ndv" : "216236", - "trino.stats.ndv.2.ndv" : "89157", - "trino.stats.ndv.7.ndv" : "5947530", - "trino.stats.ndv.30.ndv" : "1810472", - "trino.stats.ndv.19.ndv" : "100", - "trino.stats.ndv.18.ndv" : "165467466", - "trino.stats.ndv.14.ndv" : "20", - "trino.stats.ndv.23.ndv" : "1070707", - "trino.stats.ndv.34.ndv" : "2073314", - "trino.stats.ndv.21.ndv" : "29733", - "trino.stats.ndv.8.ndv" : "12236563", - "trino.stats.ndv.3.ndv" : "1950", - "write.format.default" : "PARQUET", - "trino.stats.ndv.17.ndv" : "1483", - "trino.stats.ndv.31.ndv" : "2432400", - "trino.stats.ndv.13.ndv" : "16739", - "trino.stats.ndv.26.ndv" : "1115360", - "trino.stats.ndv.16.ndv" : "297612", - "trino.stats.ndv.33.ndv" : "3332491", - "trino.stats.ndv.4.ndv" : "12236563", - "trino.stats.ndv.5.ndv" : "1890006", - "trino.stats.ndv.29.ndv" : "553991", - "trino.stats.ndv.9.ndv" : "1890006", - "trino.stats.ndv.12.ndv" : "42", - "trino.stats.ndv.25.ndv" : "388752", - "trino.stats.ndv.10.ndv" : "7082", - "trino.stats.ndv.1.ndv" : "1828", - "trino.stats.ndv.28.ndv" : "1568468", - "trino.stats.ndv.15.ndv" : "20", - "trino.stats.ndv.6.ndv" : "7082", - "trino.stats.ndv.32.ndv" : "2468889", - "trino.stats.ndv.20.ndv" : "10091", - "trino.stats.ndv.24.ndv" : "1067497", - "trino.stats.ndv.11.ndv" : "5947530" - }, - "current-snapshot-id" : 1828287521862639994, - "refs" : { - "main" : { - "snapshot-id" : 1828287521862639994, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 1828287521862639994, - "timestamp-ms" : 1648060871802, - "summary" : { - "operation" : "append", - "added-data-files" : "79", - "added-records" : "1439980416", - "added-files-size" : "83216510688", - "changed-partition-count" : "1", - "total-records" : "1439980416", - "total-files-size" : "83216510688", - "total-data-files" : "79", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/catalog_sales/metadata/snap-1828287521862639994-1-2e7e3adf-c440-4b9d-bef9-615275412981.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1648060871802, - "snapshot-id" : 1828287521862639994 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1648060871802, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/catalog_sales/metadata/00000-f5afe2f3-ed9d-46a7-b637-aa1b99fc12dd.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_sales/metadata/2e7e3adf-c440-4b9d-bef9-615275412981-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_sales/metadata/2e7e3adf-c440-4b9d-bef9-615275412981-m0.avro deleted file mode 100644 index 84666f97912b9eea65ce586efb5718a53fea98e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44414 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 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_sales/metadata/snap-1828287521862639994-1-2e7e3adf-c440-4b9d-bef9-615275412981.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/catalog_sales/metadata/snap-1828287521862639994-1-2e7e3adf-c440-4b9d-bef9-615275412981.avro deleted file mode 100644 index 674cde6a4bf131afe7b3ed54dce04e412373dae7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3797 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 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/customer/metadata/snap-8107938679113897524-1-45ecdd7a-633b-4eaf-808e-e8a76f2afc93.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/customer/metadata/snap-8107938679113897524-1-45ecdd7a-633b-4eaf-808e-e8a76f2afc93.avro deleted file mode 100644 index 56dc7fddb02b8ebcf01296d9d5119c3816c5f55d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3790 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 a`NHXz`rR8>opg(-%l9~OtKVYq2>(A{j`(~4 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/customer_address/metadata/snap-7742401198892667307-1-4516d408-6c08-4422-92ba-d10aee4c038d.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/customer_address/metadata/snap-7742401198892667307-1-4516d408-6c08-4422-92ba-d10aee4c038d.avro deleted file mode 100644 index 0621c902efc31ce5a4d86a86a636d441dcdd9139..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3793 zcmbW3ONbmr7{}d}fQm+AWRaMavRM}sc6EAoX6Jzi#ob6GkTn|(NnD$n?wXx8{g_l$ z?>Jco1TQWIl%OCe_`2pGaS8aiM1tT&&BfO|C2$*JYV&*y(nhAi$Rcsu zqay2N_9r;F00M$6Kz-1`Hto;1sDy=h~I*$?GL0r__0NsKVhPOow(+|R8qAL+8@Cro+PxF)k@+j_t zWc?PrlEOo;+Jv%8Hbz7)#{2S9%L>&(4x;RoM}#g!+wwE#6{ctx-c#w*6v}}Z!!G-q zb%@IZB7W~z6fPI4Y}ufYOy8r>_J|R8N-U&??>h*3ybVGn9mN4_@g{&?H|qHoBme@w z35Yzw8gzpGPOT{wNmv* z5bgruF3|KbfUImTeSR}rfYqrIwA%EoUjK-SJWg3e`TkVO$mIK#?OnST& z1kIK_&MoUHRFs2^rVjQynTk&jok;S0eGbLz>U}B`mLOQ#KFkvn-uJ5%? z)IfMOcGS&1LjTMN1Cv`ywPs$pF$ua9G|f5k()Q z2ye~cpu#C16efqRvn5D)L(iXhMg`<4Tp0$9E;L)CU}5Jy}$> zu0gAnh_XNkjct=61gCns1kDRjBCc(65~U5tR0Kve0iVzr$0Wq!uonee);BK24~ne{ zSt&*uwRlaSrO8EaFG)`cDRH~)IIK8Wy*dONdaIvb{9rmNG8CFJ_}r(pz|@%vDaIA@(Q(r>`V6BL-|z2A2~^ zs5NC#x#A0aPIT-e;hcr^yP+yefy1T>wmxXM1P|H0V%Z%*8l(j diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/customer_demographics/metadata/00001-5ff789a3-76cb-4b35-a637-ac13df61d7e5.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/customer_demographics/metadata/00001-5ff789a3-76cb-4b35-a637-ac13df61d7e5.metadata.json deleted file mode 100644 index 52e9aa08f8ca..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/customer_demographics/metadata/00001-5ff789a3-76cb-4b35-a637-ac13df61d7e5.metadata.json +++ /dev/null @@ -1,166 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "94e88876-29db-4ce8-addf-c417f6de0bd1", - "location" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/customer_demographics", - "last-updated-ms" : 1663709490382, - "last-column-id" : 9, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "cd_demo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "cd_gender", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "cd_marital_status", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "cd_education_status", - "required" : false, - "type" : "string" - }, { - "id" : 5, - "name" : "cd_purchase_estimate", - "required" : false, - "type" : "int" - }, { - "id" : 6, - "name" : "cd_credit_rating", - "required" : false, - "type" : "string" - }, { - "id" : 7, - "name" : "cd_dep_count", - "required" : false, - "type" : "int" - }, { - "id" : 8, - "name" : "cd_dep_employed_count", - "required" : false, - "type" : "int" - }, { - "id" : 9, - "name" : "cd_dep_college_count", - "required" : false, - "type" : "int" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "cd_demo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "cd_gender", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "cd_marital_status", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "cd_education_status", - "required" : false, - "type" : "string" - }, { - "id" : 5, - "name" : "cd_purchase_estimate", - "required" : false, - "type" : "int" - }, { - "id" : 6, - "name" : "cd_credit_rating", - "required" : false, - "type" : "string" - }, { - "id" : 7, - "name" : "cd_dep_count", - "required" : false, - "type" : "int" - }, { - "id" : 8, - "name" : "cd_dep_employed_count", - "required" : false, - "type" : "int" - }, { - "id" : 9, - "name" : "cd_dep_college_count", - "required" : false, - "type" : "int" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.7.ndv" : "7", - "trino.stats.ndv.2.ndv" : "2", - "trino.stats.ndv.1.ndv" : "1890006", - "trino.stats.ndv.8.ndv" : "7", - "trino.stats.ndv.3.ndv" : "5", - "trino.stats.ndv.6.ndv" : "4", - "write.format.default" : "PARQUET", - "trino.stats.ndv.4.ndv" : "7", - "trino.stats.ndv.5.ndv" : "20", - "trino.stats.ndv.9.ndv" : "7" - }, - "current-snapshot-id" : 2101692149723994036, - "refs" : { - "main" : { - "snapshot-id" : 2101692149723994036, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 2101692149723994036, - "timestamp-ms" : 1648057308407, - "summary" : { - "operation" : "append", - "added-data-files" : "6", - "added-records" : "1920800", - "added-files-size" : "2240348", - "changed-partition-count" : "1", - "total-records" : "1920800", - "total-files-size" : "2240348", - "total-data-files" : "6", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/customer_demographics/metadata/snap-2101692149723994036-1-8afc14d2-fba9-4f14-b636-772729a87995.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1648057308407, - "snapshot-id" : 2101692149723994036 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1648057308407, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/customer_demographics/metadata/00000-bbfdbf65-3391-4a2c-86a4-7461d671a7ae.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/customer_demographics/metadata/8afc14d2-fba9-4f14-b636-772729a87995-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/customer_demographics/metadata/8afc14d2-fba9-4f14-b636-772729a87995-m0.avro deleted file mode 100644 index 4108aa59945370adfe5f51852e77f2701998a8f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6941 zcmb_g4Nw&483sh-FQIB}v<&X7RxuvtZkL~fI5DOXi&a!atqLA{d*5*z_I8imJ${0R z(1fO77!nLcl0ut|rZWZ|sHid48APSgs5C*N)uL6aF)_6o6Ah--e!F|OyLa532QtGr zr*YMK4L?MHl z&|x)z+s+v+HzKb=#Bfp(@i=q{%aP(gh=bq?oWL7LgI=3ITp>_m z6)8Rf=Woz^A&}$p4j$74FQ30cBKMI>Cj$>gLM6j#HvdZTYlGC76@suTznrXLJcDZXK;xVqV@Id4q=7*I*QT?bi6(oPF(4~W>75Q4EF z8AJy38U#CSkm4?rbf`lJAZ8E&^r~*9*9PYrrfnvV>?&R5C~Ck^LjoGup;(oL8IG%$;R?~LK$xNOQf&urN4QoN@hk@;;^5@uSGkIk zR{>!KTnjZET#KZ?7&Pky`5e`YO6;-|C1Fmo7C=V9=x8|E#K=l9z7TLc#5Tf!D(Wwu zcK{OR8ljJ&NC0(No?Fv@HC>S?p-q@@=Jbg4{?i?tQPts6JIttaDeooi2RN8&)x5e@ z>$QJ28g(U2$a*ycNou;Ky?=89k@{i-lRc=}N*)xEADWM}5JkW0Po3PA1BZn2?=1*G zIWEX5t{?d34;%E0dm41cAlKf6hlZ4P|}BZ)yX3(N2?9h z@YS;Sk%6k^kV*y%Tr#+(WCkC;R<$YNILL0$^!UN zx+6{T;`5b{8dE;NHt8PIJu6g$UMK2DwJG5xs4c=*psCipPSM?)WemK98kAn}FGjg& zC1n-4E+_23cT>lcZ(sUF*p~WFPakR}$G$r5^yf=$6IaJ{+&*fY8++*1Q_b5>be(&# zibt$;?~`n z-1g+#pS|(Q=;1#qy&Y{RUbb<6OGD({KRsqHwI(hi%%)iHdka0M@a%JXCW7In%ck!FI zul?=J*{8qRax86p;;PsaomD+~mlwUe@1!&P%D~lyl2-pPf2cTdaZuyh4HVyy=ggLaO2mGzS`_GV{`QSD!hAj_=Gnnv@NSH zFSt4*JGJc@PyiaYo!isUQ@N_K^5KY@%DLPBaQNYdyg9M0J885i>1-|c?ed*%&+7BT zBK7);;UinahyVN7*K5wCHy$wUxP(#Yil0?}vUM{z!)zpP@`#EF`^3VKFvQoT5dRBYpuQVlZ7Y7{Lu39Gy6UzO*h}?duw_t)(k_+D@N&_FCQ0YTitbk z>t@XL#2Lrrc|y;7H^$5;-jZ-^x~}>|%UtJW;~U}kdTgLEebandll+}M}-+sL5{y*z4_O5EopZDX}znr_hsnZ$9fBaE$o0>eAKb5T9%wjK`gI?3|G&F@4A1Vl QxV*9Vht#+`vA#w1e>~!PfB*mh diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/customer_demographics/metadata/snap-2101692149723994036-1-8afc14d2-fba9-4f14-b636-772729a87995.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/customer_demographics/metadata/snap-2101692149723994036-1-8afc14d2-fba9-4f14-b636-772729a87995.avro deleted file mode 100644 index 08e0cfab1b3f8c28479cd2cdaae0b2c5d74a921a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3796 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 VG;z9lPMO=AoXGTsp>0?1{{inX{rUg^ diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/date_dim/metadata/snap-4095010124650684994-1-833fae69-41bb-487f-93eb-39739d52225a.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/date_dim/metadata/snap-4095010124650684994-1-833fae69-41bb-487f-93eb-39739d52225a.avro deleted file mode 100644 index cff640949cc3a677ea5e2b2b8d29a78445238c95..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3785 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 WTH}uLknh%>H*;D0te9GGZ diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/household_demographics/metadata/snap-1609980499255550327-1-c0dce8d0-485c-4feb-83bc-df6a677886fe.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/household_demographics/metadata/snap-1609980499255550327-1-c0dce8d0-485c-4feb-83bc-df6a677886fe.avro deleted file mode 100644 index f694788f50f64ce48292e3b117d6a4d674508e3d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3792 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 IQgq+`2O5+q0RR91 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/income_band/metadata/snap-9031488788150469094-1-1015ac44-d82e-4a05-a038-65fde8ef6da3.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/income_band/metadata/snap-9031488788150469094-1-1015ac44-d82e-4a05-a038-65fde8ef6da3.avro deleted file mode 100644 index 49db7042e62f98ade73a3a22b9279554cf433025..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3784 zcmbW3O^6&t6vuTh@gTuN2&`^m(MD0&gzn7D&ddhO*xsGqlE}t4&(SfYRSHyvQMzOcbzdOX{agGriGE6*hVsRdHF>8UNOCY4ns0XoQ z%$7{EKRq1VMc%3&-6dM=c@g$;pdkP!k3&GiF7M_zvQt3PW@UG8o>LU$;pR786~GOhHsAGk8R#43NiY z47f%zUaZfdcuSveQt|m=QtWwo`b0~#o*$RUJTI-t zuV+tag*I`Jy_>g?3mMv*%y52V_f^e;RjgFXm5Nm|i!)WTTAbqd)rRcg8lpaTkqK)G z+o*x)YV4?&@09xIMi|(_QX;PnTfj`|KlZ(0&y5|GqL%|q5qC!(3MB)`7{g&jvqThY zm?FJ3gOkr|72>c&$Z@2(YzE~2T~`4L5)gHMa#xYxDnJvuzB;Z{8F_qH0gD5`5!RDM z)$1CzT7@V{gw)s$DN=B{rz_Bm1SR6yAtzDVXiQmRWD|%9opDT3EG~ah@MQzzQvRUX zs+5&xq*04kC0dzW^!8Hpl#mj)+lj-*DYsN{#eH!bjgsERGgYo?3XQNw6*zq*sTnb3lPb8H zKvJzKi^>&W3RUweg-|mm(dqbvM4~!irGJ1cQoKcyvB@3jHt~AA<)Pm{+4bCmTi0&B zJOA7BfA&vLygjjR>xtbP<{b~i7k_^GhqL?M+q}B9iI1FUe7Ugv>BbRqAJ}#FRZtTCZXk458Zt8>0qxL;>&l<+rJ@3Eu#;+#} z>t5;aXV#y*eD}R?mL?y#a=!5DfeWu(TKwkQuRd&iUpxQ#PiOYS+Vu;ya~Hqv_AV~J PF!j3o$3HiB4d(YBs9F)b diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/inventory/metadata/00001-a96b6ff3-1f4b-4419-bef6-9f77e0de188a.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/inventory/metadata/00001-a96b6ff3-1f4b-4419-bef6-9f77e0de188a.metadata.json deleted file mode 100644 index 5d725f8835ca..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/inventory/metadata/00001-a96b6ff3-1f4b-4419-bef6-9f77e0de188a.metadata.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "b7eb887f-9447-4775-8a34-101f2342e7e5", - "location" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/inventory", - "last-updated-ms" : 1663710922207, - "last-column-id" : 4, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "inv_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "inv_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "inv_warehouse_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "inv_quantity_on_hand", - "required" : false, - "type" : "int" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "inv_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "inv_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "inv_warehouse_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "inv_quantity_on_hand", - "required" : false, - "type" : "int" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.2.ndv" : "297612", - "trino.stats.ndv.1.ndv" : "261", - "trino.stats.ndv.3.ndv" : "20", - "write.format.default" : "PARQUET", - "trino.stats.ndv.4.ndv" : "1010" - }, - "current-snapshot-id" : 304879748299377848, - "refs" : { - "main" : { - "snapshot-id" : 304879748299377848, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 304879748299377848, - "timestamp-ms" : 1648062951594, - "summary" : { - "operation" : "append", - "added-data-files" : "6", - "added-records" : "783000000", - "added-files-size" : "1935532289", - "changed-partition-count" : "1", - "total-records" : "783000000", - "total-files-size" : "1935532289", - "total-data-files" : "6", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/inventory/metadata/snap-304879748299377848-1-5558c7c7-d25c-4356-8db9-b1c7296943db.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1648062951594, - "snapshot-id" : 304879748299377848 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1648062951594, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/inventory/metadata/00000-c379759a-eda1-4872-ae9d-a4ff859abce2.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/inventory/metadata/5558c7c7-d25c-4356-8db9-b1c7296943db-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/inventory/metadata/5558c7c7-d25c-4356-8db9-b1c7296943db-m0.avro deleted file mode 100644 index 87b35e522df1e01cbb900f451cc71e2bedefbb5a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6440 zcmb_g4Nw&48CIjFQ8XqJV}La4N~|@9-tGbi6dFNfBt|p}I#$Vc?{1IVu(x~c?j1!T z8jNX(ge1hD5MvBF(=@eeNQg6b5?V%W{lS`A)G-sGiO`hzL+Yd|jWg}{?cVOUcix=` zGIPTWd*Ao|Jn#EH`@FZ<{@lokak9hBdYH4L*WyZ*m&LPiS&@7W1-IbZHz}MG&$3!@ z7sv8W8P8g^7Kcv=3obAoc%Bpdw3AU-S}q5dB(}oGNh~|)V>lS_TVBDt$S*Y+ag2E|Pv?$PSMsP++f)f6;g0@Y$@7ss=PorpO|q#ln}`amr3GVNs) zw_yRc4F^gLLJ%FGE1nh|jKYZmR$p7Ascxl{rMP zbY2C0g{i0+)T&Kz)Mlg1yZ)=9V) zobkSyFF2$suPT|qJxsrcf!DJ=YF#KZ8_8atQ)toUl36A8?$zIrEYEs?Sk;6XMWL7B zGH6y|>_B-T+mY#rGRq=alt4t16ZA_s zk93i)3wpE%fprS4LFxt(*n-dsB?n1+bTy_G%B<$mRkPRIvr%C)Ept|sfhsiv(muSo z!AN~bVCuSPMcJwz43Qt3kAfIQKk|omOrzkSp~8EM0#IoUGKn_N5UC&Dsy}js^S@M*c^0gREp2Rzem5bUmnr4VObm_ z32<>!X*}>1@S(AKmX4?PEph={9f)}i<``u=$|2LpLA8qnCUOv44!{tUjqnGNkQ_)b zUBi<@bUke(MWAsGC!GhG83{%=Qf6Sv?l!aAgszbW1==FOt6?Ii8w!F!C z*LL>IC#!3od_L~*xC>jl7slr%{i*UCxBdO^n%6b|rO#D){rx#-HvGde|A8CHKeK(z zp7&kY@lwv_wtuaBrPjH1+C$IGn1yew_11A43ztlqxh3iQdyf5R@((ZMw|?B^tLT5= z%j{}@`k>bpPv)vCfbezzcb%k{?AX-!W}KYp>r@srPb>f-Is zezB?ZNJj6m-7|N*+!FuPK*FD=KK^M>-`IB>I*%k)W!KC)AAeuzqnEN~WPNZZY2M#2 z|Kj(|$-SP|Q^^g}wpzRaEa?2Bh<`g17<+kfTO?!Co)UE{UK@V7g^X}J2x zf^#P^{Ko2@gm>@@%^jNt_MK8zCsfGSPapj1>L+!1hugYl9-WYV`SmmBn-XsB>iv4j z)Mwmt=RUN4)RC7TeeLSz#rfO415oISCva(`>jYcxIk#Q1|B^lz-Xm9gUfv0c9IEoc7SJl$J!@lf7d zPkcXjL0WR$z=~sg#`i8ejckzuf)Aq0Yr`j@~!NpH{bR%7c@ZjCpS^!Sp{jW%Hsu!QFYI@VgB}q4kaD zz2lB_3gdUFl2Y4Y;7y~^c;b)ThvxN^bGs=)!hfK>`c8iA+%}#51<~lA^-pY diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/inventory/metadata/snap-304879748299377848-1-5558c7c7-d25c-4356-8db9-b1c7296943db.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/inventory/metadata/snap-304879748299377848-1-5558c7c7-d25c-4356-8db9-b1c7296943db.avro deleted file mode 100644 index 8727867f8431c77731e9446ded18a244d453e3d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3792 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 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/item/metadata/snap-6329032515952286791-1-41fcc6a2-2e2f-4fdc-bc85-6932abb37aec.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/item/metadata/snap-6329032515952286791-1-41fcc6a2-2e2f-4fdc-bc85-6932abb37aec.avro deleted file mode 100644 index 98d44e6cf7018888214f283b467b28fcd1727e99..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3788 zcmbW3zi%T&6vr>APUyrHpex{nMk_>>LtZCN>~u&6(n%+Q5_6dNtiAE>IJ?Ps*X+(F zn8-p@=(>xwS*s9TLm2!Rs5hz`jLH@X96#={)KU%TU%UrP5$2 z8yh2gTaJz$DR$i<5CfJV@T_+bux!xo6XYf7#(`BSa1Xk~V)$YGm`?PE*>hncsMW7&+CG6*pt0bvCC z5rO37Su&^teN2>f79+lcxTxg}-GBs!x8-TN5fgHWqVrODY1~+j)M`jSsR2(s=GdI@eY7?Kk9iCdH@2p3Gh6@ z9>o51r`Dv31S|}lu1QIlyi4++(4N7}Zy&WPrGTA%9dAwk!Y8BTBPh1KP9t+ZUOsFhl!T&}LJ)zs_szPgay*h8*IU1U+cZt19v z-0j#=zkP=5pBkaBGfRoQHf(`XO8>F%Wj!}`R2RMFTP~vRNJF7y04Z}gENhmCqGl=F zTXQ(YyjCE}9Kpwt)H~0?x3WJgQt* z)@lW!!Vz3!7o>2($(}Ais~nVw>w=s_X|pkF9K)M{P3W9s;$p${MM0NMjZ6N6WUE|O zl95I&UgKzCa?#sM&{IN6-0mU{%MVtq&cKG=DyZp(EQ+1sv4Tn`N&Hez+i5P;vZ>uL zZwVh0MSNAj#PCR4=-jY;FHO0nh|BJa+Gv#YIi4wU6;o)2J&M5TD@o0Wj7^H*Vghls zrYx#Zd@fYUs{}&KoJ6Pd6B3EafY#u7sz~-0Nya91q|5B}cHeRKQu7@UU4a!RX=SaNG!5XX9aG_}R&alTol0eBIo5Fxfb3 zPCDkjdyktB8ui9#>-6N!;poeg@z(J2ot^ga(F7lvAMXY`!Kb^!qv6>&!0?M*`gRMB z0|P%g9-WPMA3to|3I6?ReE8nyfBpW^PutJ^{QY;24)4Ezf8om2$r}s5WODou*#H$J diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/promotion/metadata/00001-b5b347cc-1be6-4681-a37a-e645575b4d79.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/promotion/metadata/00001-b5b347cc-1be6-4681-a37a-e645575b4d79.metadata.json deleted file mode 100644 index 6c896bffdb6e..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/promotion/metadata/00001-b5b347cc-1be6-4681-a37a-e645575b4d79.metadata.json +++ /dev/null @@ -1,276 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "2d422038-c589-4f9d-92ce-cafc64b7fef4", - "location" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/promotion", - "last-updated-ms" : 1663710934563, - "last-column-id" : 19, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "p_promo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "p_promo_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "p_start_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "p_end_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "p_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "p_cost", - "required" : false, - "type" : "decimal(15, 2)" - }, { - "id" : 7, - "name" : "p_response_targe", - "required" : false, - "type" : "int" - }, { - "id" : 8, - "name" : "p_promo_name", - "required" : false, - "type" : "string" - }, { - "id" : 9, - "name" : "p_channel_dmail", - "required" : false, - "type" : "string" - }, { - "id" : 10, - "name" : "p_channel_email", - "required" : false, - "type" : "string" - }, { - "id" : 11, - "name" : "p_channel_catalog", - "required" : false, - "type" : "string" - }, { - "id" : 12, - "name" : "p_channel_tv", - "required" : false, - "type" : "string" - }, { - "id" : 13, - "name" : "p_channel_radio", - "required" : false, - "type" : "string" - }, { - "id" : 14, - "name" : "p_channel_press", - "required" : false, - "type" : "string" - }, { - "id" : 15, - "name" : "p_channel_event", - "required" : false, - "type" : "string" - }, { - "id" : 16, - "name" : "p_channel_demo", - "required" : false, - "type" : "string" - }, { - "id" : 17, - "name" : "p_channel_details", - "required" : false, - "type" : "string" - }, { - "id" : 18, - "name" : "p_purpose", - "required" : false, - "type" : "string" - }, { - "id" : 19, - "name" : "p_discount_active", - "required" : false, - "type" : "string" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "p_promo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "p_promo_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "p_start_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "p_end_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "p_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "p_cost", - "required" : false, - "type" : "decimal(15, 2)" - }, { - "id" : 7, - "name" : "p_response_targe", - "required" : false, - "type" : "int" - }, { - "id" : 8, - "name" : "p_promo_name", - "required" : false, - "type" : "string" - }, { - "id" : 9, - "name" : "p_channel_dmail", - "required" : false, - "type" : "string" - }, { - "id" : 10, - "name" : "p_channel_email", - "required" : false, - "type" : "string" - }, { - "id" : 11, - "name" : "p_channel_catalog", - "required" : false, - "type" : "string" - }, { - "id" : 12, - "name" : "p_channel_tv", - "required" : false, - "type" : "string" - }, { - "id" : 13, - "name" : "p_channel_radio", - "required" : false, - "type" : "string" - }, { - "id" : 14, - "name" : "p_channel_press", - "required" : false, - "type" : "string" - }, { - "id" : 15, - "name" : "p_channel_event", - "required" : false, - "type" : "string" - }, { - "id" : 16, - "name" : "p_channel_demo", - "required" : false, - "type" : "string" - }, { - "id" : 17, - "name" : "p_channel_details", - "required" : false, - "type" : "string" - }, { - "id" : 18, - "name" : "p_purpose", - "required" : false, - "type" : "string" - }, { - "id" : 19, - "name" : "p_discount_active", - "required" : false, - "type" : "string" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.7.ndv" : "1", - "trino.stats.ndv.2.ndv" : "1543", - "trino.stats.ndv.16.ndv" : "1", - "trino.stats.ndv.4.ndv" : "669", - "trino.stats.ndv.19.ndv" : "1", - "trino.stats.ndv.5.ndv" : "1497", - "trino.stats.ndv.18.ndv" : "1", - "trino.stats.ndv.9.ndv" : "2", - "trino.stats.ndv.14.ndv" : "1", - "trino.stats.ndv.12.ndv" : "1", - "trino.stats.ndv.10.ndv" : "1", - "trino.stats.ndv.8.ndv" : "10", - "trino.stats.ndv.1.ndv" : "1483", - "trino.stats.ndv.15.ndv" : "1", - "trino.stats.ndv.6.ndv" : "1", - "trino.stats.ndv.3.ndv" : "675", - "write.format.default" : "PARQUET", - "trino.stats.ndv.17.ndv" : "1466", - "trino.stats.ndv.13.ndv" : "1", - "trino.stats.ndv.11.ndv" : "1" - }, - "current-snapshot-id" : 629877963447712280, - "refs" : { - "main" : { - "snapshot-id" : 629877963447712280, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 629877963447712280, - "timestamp-ms" : 1648057319541, - "summary" : { - "operation" : "append", - "added-data-files" : "4", - "added-records" : "1500", - "added-files-size" : "59165", - "changed-partition-count" : "1", - "total-records" : "1500", - "total-files-size" : "59165", - "total-data-files" : "4", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/promotion/metadata/snap-629877963447712280-1-0c427289-cf08-4939-a662-11b5a04a2a82.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1648057319541, - "snapshot-id" : 629877963447712280 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1648057319541, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/promotion/metadata/00000-5af8e1a7-435c-4809-84e5-8e799e983c8e.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/promotion/metadata/0c427289-cf08-4939-a662-11b5a04a2a82-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/promotion/metadata/0c427289-cf08-4939-a662-11b5a04a2a82-m0.avro deleted file mode 100644 index d2918707a2fc6015f97c5f277431b1faf78040c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7851 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 S$~*qh_m_VP|JfMpP4PdmA`>3~ diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/reason/metadata/00001-db49590f-5936-4426-88ec-e7b613e34aec.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/reason/metadata/00001-db49590f-5936-4426-88ec-e7b613e34aec.metadata.json deleted file mode 100644 index f36d8ed547f3..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/reason/metadata/00001-db49590f-5936-4426-88ec-e7b613e34aec.metadata.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "441be288-bf49-489c-a51d-445fe7f172f7", - "location" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/reason", - "last-updated-ms" : 1663710938087, - "last-column-id" : 3, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "r_reason_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "r_reason_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "r_reason_desc", - "required" : false, - "type" : "string" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "r_reason_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "r_reason_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "r_reason_desc", - "required" : false, - "type" : "string" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.2.ndv" : "65", - "trino.stats.ndv.1.ndv" : "65", - "trino.stats.ndv.3.ndv" : "64", - "write.format.default" : "PARQUET" - }, - "current-snapshot-id" : 204521873767892073, - "refs" : { - "main" : { - "snapshot-id" : 204521873767892073, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 204521873767892073, - "timestamp-ms" : 1648057322031, - "summary" : { - "operation" : "append", - "added-data-files" : "2", - "added-records" : "65", - "added-files-size" : "1920", - "changed-partition-count" : "1", - "total-records" : "65", - "total-files-size" : "1920", - "total-data-files" : "2", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/reason/metadata/snap-204521873767892073-1-75a52cb7-2991-4880-90a8-07a6a56c89f5.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1648057322031, - "snapshot-id" : 204521873767892073 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1648057322031, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/reason/metadata/00000-21fc57cb-562c-4774-b0e3-e83d36e50611.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/reason/metadata/75a52cb7-2991-4880-90a8-07a6a56c89f5-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/reason/metadata/75a52cb7-2991-4880-90a8-07a6a56c89f5-m0.avro deleted file mode 100644 index a2996693efc7a5ae763de7f0584169fd3d2bfaa9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6030 zcmb_gTWl0n7^XlWq>2;^A)%7#PSnWcp)HH13q{OJOELmLI_?^g9?VA(f^#;nR9l#yKJ{jHcdKv z{>%6Mm-)^<8D3K|{sBwrLNWQvQT@E#7e{=6w`?<^*u0N-zNt|oe8BJHqbkxO79UvE z&*LY8kJsfGK5vFhBwL0avbw>@L_G=BL~tT1YZmgkUNuAS zics+TnaqgasA0xr%mZyE)^jt&=A1JePA9>P<{AmmXlj+HeO!7|sLyD0gRZL}1D&w-!nuvvw$r(}3Ic#yN&e>gn$EgO(i^RAl zyjbdUD5UD4aG#Bso=FSPZrHL$ym9zAcJCf%eb+L+!m$(pbHf(5rHA*@LJ+zJ;Jrhtq?U2cWjc_ zDnyDpj0B1TCNOQfy@?X%m5x%Z=|!dmif^RAd+M*oiX#QEj`UZm3Ibi$nbPz> zS9=nru!{^_ZCsJ=3VM2nfvXU`!eF#`sMD`MR+_Y@rUhMpST#>g*{B225ZaiFn4cmb zQZps(v%4FN)c3IZ>1B^Z@I-!LF`5e${WKoEA9@9chRPl-4?)2sb;;Zr$yLvj`q{m@ zQ|0K%{L-HWsr7m(tLQHzRL)BPjZ)`Df&G$2{^tury)=4WGA;ttJgtR2p0U*S@v!Hd zRU-mErx=<%48$)Lm^46P7{C#hjae{= zFkQogA-|sXKnZl5vq|ScA`@YBb0LE%8!EB^Lf3;KL0hEuY8Fj9NE8w@QVtXnly2xK z?yEpOTq2#m46!*#Ok#abOd@dSVhXFeOn!hO4{B(T`%Vt44Qcvx>@#E_^<2=%h`C2b zVn(L;^jX#Br7zJp5%dEq&~l{DdkHuzCa+SXVdx7eQqZ>CvTp9b>yACYZ|(~R5B+jv z&ks+xAE{mVj&E8pp6uX1e{k)E73217JXdkRw{+)(&BCJ}f4Xm5&*7_R*C&ZB;l({0 zd&g9-uGm_#tNN{z%j3b!imw&zQJj*WxATcJk$SHtj#t`lo&DyE$_M zb7q&XoqFxovkx_XUw&!-iMuNg&+NE#s=Q6Pyj@wjdd)XA-7}~5-U#e?WbB<~;|_g$ zVz=n8^Y1UavS;4$ixu^qufFm7%G>w;YrZbLJf?j4!ev*dPd+Hs6ROH)+&FV> zP5GpMI=Yw4`RL}~+pXhba9r%i6ARjZMeX;#_~ZSicdP3Xz5P4S&n|m!^Hb4nZ#OJH O_2&75FWw%TN$>wRi0N$r diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/reason/metadata/snap-204521873767892073-1-75a52cb7-2991-4880-90a8-07a6a56c89f5.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/reason/metadata/snap-204521873767892073-1-75a52cb7-2991-4880-90a8-07a6a56c89f5.avro deleted file mode 100644 index a45703e27fb068e38c8591dc87359342625503a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3782 zcmbW3O>7%Q6vt_hP!&o*2m)#Yj8-m{sB%Rz1U&#@hYbvwvt7!+BA+Qziu!M6C`h8}JqpP4P}BK~s= zBiHtg#_G<%iVkEBUl9lL8O7cP|85hD$9alakYVD17oI;0wlQt+5YIJ*b}AK8EruuU=R&$q0Fg|>}s)AwMwNt{Pt8x;${0`C4);%kfeHVGd*< zjKDu4kes|o2DM<|GNU14#CH%EH8ViBA%o#<`KCpJu$bsdWD24}nZbfc86c0*E=bnz z!>edK^dj~dzhrYnVzezkb5>)@b`d?5K250{NGa^{zj=qW zxj^La{feU1Vx2D=6p}?e2C?Uxai_#WY7*ii=!rH6m9#8U=4)g&8ViS;g zf-UF<{hd0eRit2H=yXj+!jxT-1%=)aW_J6O>RJje*+$fC@*Y*{lRX*CgV$s%nvasc#-G*Fxq7(!z zM;+(B^%QH`K}J)DlI={zr-x1?d9gl+;!FB`lZwv|lVZ=y(awJ{VZSQ6j4iJD`}7e{6e$mKz%?MVkSph?^r1g_8MWj9{>$S0YMjkRq)$ zgOkr{6{0K=avEtSn*sTM(^Y^O35Xg$xvI!+6`%!OUmaJfj6AlhfR#Sr5a-FF>U9k| ztwK~KLQ3p_6sb4e(iLb{f)Z^Vkdp{)IHo2svI)e1&NwD17MDLL__DrfDSyyhRmw{9 z(Wu4eBwBe~^z>5nl#mj$JBY!`gDusEU_)zF)O17U#SXDpMdgzueyFG2G!u$F?l!_& z!nZ^oTU9VMJkkz2GA!FlQ)a2+iu>X&8YR7tWvX1&6dK}=DscKrQZr(}B~@@Wfuvef z4wWmu6sl%b3ZZ6BqSMg{i9~h4TCczrDV`$9*yN6MSUetYdGob1t#9AD`tZW9&($_h zoUD&s9ed!*&+fQUyzB9w3zs%0%(184%L|Q1CcxVd-JH5@{niV!>yt;vPc|mMIQ;5w z_nLoxxbofV2Zg(LuN=R0`?dM&pP#t+uJQ4Ecb@*^!C!tle+bjXS1$hj=;e>7B3E KzL}rzPwzhmQUnhG diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/ship_mode/metadata/00001-b3791be6-2d0f-40b4-9cf9-58a0a294bf84.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/ship_mode/metadata/00001-b3791be6-2d0f-40b4-9cf9-58a0a294bf84.metadata.json deleted file mode 100644 index 38844970a22c..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/ship_mode/metadata/00001-b3791be6-2d0f-40b4-9cf9-58a0a294bf84.metadata.json +++ /dev/null @@ -1,133 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "05ac7862-5a0d-4c39-899f-c41e335a0bfa", - "location" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/ship_mode", - "last-updated-ms" : 1663710941481, - "last-column-id" : 6, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "sm_ship_mode_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "sm_ship_mode_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "sm_type", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "sm_code", - "required" : false, - "type" : "string" - }, { - "id" : 5, - "name" : "sm_carrier", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "sm_contract", - "required" : false, - "type" : "string" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "sm_ship_mode_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "sm_ship_mode_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "sm_type", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "sm_code", - "required" : false, - "type" : "string" - }, { - "id" : 5, - "name" : "sm_carrier", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "sm_contract", - "required" : false, - "type" : "string" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.2.ndv" : "20", - "trino.stats.ndv.1.ndv" : "20", - "trino.stats.ndv.6.ndv" : "20", - "trino.stats.ndv.3.ndv" : "6", - "write.format.default" : "PARQUET", - "trino.stats.ndv.4.ndv" : "4", - "trino.stats.ndv.5.ndv" : "20" - }, - "current-snapshot-id" : 3829200591547055835, - "refs" : { - "main" : { - "snapshot-id" : 3829200591547055835, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 3829200591547055835, - "timestamp-ms" : 1648057324222, - "summary" : { - "operation" : "append", - "added-data-files" : "1", - "added-records" : "20", - "added-files-size" : "1676", - "changed-partition-count" : "1", - "total-records" : "20", - "total-files-size" : "1676", - "total-data-files" : "1", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/ship_mode/metadata/snap-3829200591547055835-1-0e25905e-562c-4a35-9e6c-da1f5a57437c.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1648057324222, - "snapshot-id" : 3829200591547055835 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1648057324222, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/ship_mode/metadata/00000-0c7c25ce-eb3d-444b-8530-ef615539ca2e.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/ship_mode/metadata/0e25905e-562c-4a35-9e6c-da1f5a57437c-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/ship_mode/metadata/0e25905e-562c-4a35-9e6c-da1f5a57437c-m0.avro deleted file mode 100644 index d31936582d1ccd6c649987d6bea0799fe78f14b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6198 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 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/store/metadata/snap-8205194758611994023-1-dbe69e01-f4eb-41bb-87ef-df547e18e0d5.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/store/metadata/snap-8205194758611994023-1-dbe69e01-f4eb-41bb-87ef-df547e18e0d5.avro deleted file mode 100644 index 04fef4b29ee956a099fe567ac1e566273868e2d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3783 zcmbW3UuYaf9LLjORiu<662V58t{Ny!>uz$F+{G7_sziznO_WLv=iKgGZgp?>Iy;-( zr9G`uR49sw*nlFw2vv)*d>1$Uy2sS8hcq{E90(j{zL*@#e7uxH zSRFGIMu3k5B`435AvGL0Oq<~`;yZ|on(U!lnBnlY{EF!XVKLE_h!l8*B7-M*%80m( zwo$Tv9bQ4J;!cT$RP{WEV3)T+sH6oNK#SK9cH2?U*RhW< z;+v4j6D?yW=K2~%`Q78JXEnAz=Pl#~=4Z-vlG6?#;vPxNRcV8NeD z^oqh=DBOi=9z__+=F;akvkq9DDnYZk0dTl$aIn!??2K#Bd=1c8EZR~>6gD3;8W^O< zOCi{-%i~7f%zp0CfLctf3ULh<=wLhN~Y>O@Plo*$RUJTI*P z*K=QJg{Ef_b1SrvgBck~W;oo~SZP8p8s)-tu{32E<#Iuvm>h-sYGAf|nNS~GWY`$B zO;ROvDR$KF9uWEmBMeM#DUsKPEug3LANyXf=f;jo$>G4J1l$oqkz@dA12`;imWX2X zQiQi=aPoPrL`(>T7)OfBWvybP1XgphR37AjX110SfkVj8+xmxrW-OZwvWe3Dom32rJlCaOsMUG z-EeOS9}{_eRl?-(NL%Q@uxu|)xuuND?+a`+N_rj7l)1_&)W;rW;PjQGW<-xo%HVPW z3ALsyDp!0VRK=?#Le88-r-Ks`iK>9b&cmQc{uW8bCOFdF{Pj4r`J2zj-WeO(`98aH zB-UAw>}zq&AzuGc;=Uz&;GSGck1NYvDP(b__sZ8UO2vbRX=-rNAT{6Z9f)&ef(|uXZiNI z7Y^R_T4`C^-s=xf9dwk)vu0z Ou=LP{f6hGGo!)=*Y!0&k diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/store_returns/metadata/00001-c467e79d-4685-4885-8135-377e67bb7045.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/store_returns/metadata/00001-c467e79d-4685-4885-8135-377e67bb7045.metadata.json deleted file mode 100644 index 52089c8a34a3..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/store_returns/metadata/00001-c467e79d-4685-4885-8135-377e67bb7045.metadata.json +++ /dev/null @@ -1,287 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "7edeba10-a532-4ac0-a62e-9c05e199a417", - "location" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/store_returns", - "last-updated-ms" : 1663711017880, - "last-column-id" : 20, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "sr_returned_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "sr_return_time_sk", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "sr_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "sr_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "sr_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "sr_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "sr_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 8, - "name" : "sr_store_sk", - "required" : false, - "type" : "long" - }, { - "id" : 9, - "name" : "sr_reason_sk", - "required" : false, - "type" : "long" - }, { - "id" : 10, - "name" : "sr_ticket_number", - "required" : false, - "type" : "long" - }, { - "id" : 11, - "name" : "sr_return_quantity", - "required" : false, - "type" : "int" - }, { - "id" : 12, - "name" : "sr_return_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 13, - "name" : "sr_return_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 14, - "name" : "sr_return_amt_inc_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 15, - "name" : "sr_fee", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 16, - "name" : "sr_return_ship_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 17, - "name" : "sr_refunded_cash", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 18, - "name" : "sr_reversed_charge", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 19, - "name" : "sr_store_credit", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 20, - "name" : "sr_net_loss", - "required" : false, - "type" : "decimal(7, 2)" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "sr_returned_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "sr_return_time_sk", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "sr_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "sr_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "sr_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "sr_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "sr_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 8, - "name" : "sr_store_sk", - "required" : false, - "type" : "long" - }, { - "id" : 9, - "name" : "sr_reason_sk", - "required" : false, - "type" : "long" - }, { - "id" : 10, - "name" : "sr_ticket_number", - "required" : false, - "type" : "long" - }, { - "id" : 11, - "name" : "sr_return_quantity", - "required" : false, - "type" : "int" - }, { - "id" : 12, - "name" : "sr_return_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 13, - "name" : "sr_return_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 14, - "name" : "sr_return_amt_inc_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 15, - "name" : "sr_fee", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 16, - "name" : "sr_return_ship_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 17, - "name" : "sr_refunded_cash", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 18, - "name" : "sr_reversed_charge", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 19, - "name" : "sr_store_credit", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 20, - "name" : "sr_net_loss", - "required" : false, - "type" : "decimal(7, 2)" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.7.ndv" : "5947530", - "trino.stats.ndv.2.ndv" : "32934", - "trino.stats.ndv.16.ndv" : "351933", - "trino.stats.ndv.4.ndv" : "12236563", - "trino.stats.ndv.5.ndv" : "1890006", - "trino.stats.ndv.19.ndv" : "677045", - "trino.stats.ndv.18.ndv" : "697964", - "trino.stats.ndv.9.ndv" : "65", - "trino.stats.ndv.14.ndv" : "1229056", - "trino.stats.ndv.12.ndv" : "659344", - "trino.stats.ndv.10.ndv" : "177092218", - "trino.stats.ndv.8.ndv" : "513", - "trino.stats.ndv.1.ndv" : "2017", - "trino.stats.ndv.15.ndv" : "10141", - "trino.stats.ndv.6.ndv" : "7082", - "trino.stats.ndv.3.ndv" : "297612", - "write.format.default" : "PARQUET", - "trino.stats.ndv.17.ndv" : "927393", - "trino.stats.ndv.13.ndv" : "119761", - "trino.stats.ndv.20.ndv" : "713783", - "trino.stats.ndv.11.ndv" : "100" - }, - "current-snapshot-id" : 939104734745829810, - "refs" : { - "main" : { - "snapshot-id" : 939104734745829810, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 939104734745829810, - "timestamp-ms" : 1648063532101, - "summary" : { - "operation" : "append", - "added-data-files" : "14", - "added-records" : "287999764", - "added-files-size" : "12546602144", - "changed-partition-count" : "1", - "total-records" : "287999764", - "total-files-size" : "12546602144", - "total-data-files" : "14", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/store_returns/metadata/snap-939104734745829810-1-0f4d5031-eb6d-42a9-886c-92d65dd60c58.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1648063532101, - "snapshot-id" : 939104734745829810 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1648063532101, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/store_returns/metadata/00000-ea05246c-4c4f-42d7-aec1-b7d0dd0e7e46.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/store_returns/metadata/0f4d5031-eb6d-42a9-886c-92d65dd60c58-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/store_returns/metadata/0f4d5031-eb6d-42a9-886c-92d65dd60c58-m0.avro deleted file mode 100644 index 0f4ca3e4056029aae6199cb36487972c59ea3eb6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11317 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 Om$t)Xn()_Smj40taj#7P diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/store_returns/metadata/snap-939104734745829810-1-0f4d5031-eb6d-42a9-886c-92d65dd60c58.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/store_returns/metadata/snap-939104734745829810-1-0f4d5031-eb6d-42a9-886c-92d65dd60c58.avro deleted file mode 100644 index 07f436b7cfb5b7d6f20fa0acb6e96fb20ab0d6be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3794 zcmbW3ONbmr7{{}V!QJ2^pyZGTmQ6e)?uO~zne5E!VPnRSh4`E(%CZhM-8DNUJzZmU z^^Rj05LEOK2n2KSAj!d1Fb7{b7!*QCAdpKir{totih>fwhyf#FRX?h#XQtPlz0ITQ z`+Z;4_y7L&RPE)FySBh(O7?hG12y5_+m0Bl(?Z6qL6JqMZA=(0Y>Mwq=;Au^ShbEF z#DA`1*J zW44gXbtn2`JIGzqqdP>4eV8!l0FfZ@@M{QQ7t}fod2zZ?U{ed+f|OyAPt|K7tIEkT zaqKdgb%p;4=F7nMkPR3CZHifMzEfIQXxqrHdM<2v4Z`%W3x?}E4i6k;z8D`%C0Z&V ztN|GaBJht0#3wJ3L3QXm%$OB1qC1F+n(m|9kip=#ysAa~pqTJVWD24}nZZ7hGC(e) z9T2ZygO}HM=vC}7e#zA#k&j3FvQrBh)kY3t{FH};mcwn?nR_*+Y!}f}$HJQA#FsfHQu92eX>VG0ZabV zv0g#23k17BolpRI*+Tl_X4U}fQ^jw!)&UN74G%WEi`@x>H(!%$ED~+8D+{am%_ij1 z6Qv+%HPmsgSx;_8JIHYAP|{9Sd~)c7k{9bUDBjfPn^b&um=t?fo<7lHt!Kx@GS5oO z^Xs`kutJO2$hwEOkOLXo63=jcV>?UJB{P?wna1~=RW9L!cju24LTr~kbh)*{gxXUDn+mOm?CbDJQRxOk1>S7ie3pR zWbW2yDy%H2_Yn_}}XoE2`5+j>H4Cs_&l45cAgMu&XnU?Yg z%~hqWG#?FHyd=@e@&DU*CN`w|VaS3l@03`0GC}j$c0cd3WT^$If4aca)#lcX0zd2iVcM zi<=IPz4Ow|_kO$YwzK<#doQglJ#g;I^*vjEIC1COBR_s~{K5D4|9tx9cfXW(ZQSw4 z%7?#OlUI-K`tz&5K0EdL8_)PRHl28A?!Y6LDyL6QoOx@vf424H!wc_T`Donzvb(r? c@X_|I)!WP0j$L@6y#J@GtKi>T8+%jz4?^7)UH||9 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/store_sales/metadata/00001-96646f0d-586c-4989-8e91-5d55096c51ff.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/store_sales/metadata/00001-96646f0d-586c-4989-8e91-5d55096c51ff.metadata.json deleted file mode 100644 index c73fcb3632cf..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/store_sales/metadata/00001-96646f0d-586c-4989-8e91-5d55096c51ff.metadata.json +++ /dev/null @@ -1,320 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "824b6ce7-6d19-4427-a400-c84aab26cd8e", - "location" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/store_sales", - "last-updated-ms" : 1663712021104, - "last-column-id" : 23, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "ss_sold_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "ss_sold_time_sk", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "ss_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "ss_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "ss_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "ss_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "ss_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 8, - "name" : "ss_store_sk", - "required" : false, - "type" : "long" - }, { - "id" : 9, - "name" : "ss_promo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 10, - "name" : "ss_ticket_number", - "required" : false, - "type" : "long" - }, { - "id" : 11, - "name" : "ss_quantity", - "required" : false, - "type" : "int" - }, { - "id" : 12, - "name" : "ss_wholesale_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 13, - "name" : "ss_list_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 14, - "name" : "ss_sales_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 15, - "name" : "ss_ext_discount_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 16, - "name" : "ss_ext_sales_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 17, - "name" : "ss_ext_wholesale_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 18, - "name" : "ss_ext_list_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 19, - "name" : "ss_ext_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 20, - "name" : "ss_coupon_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 21, - "name" : "ss_net_paid", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 22, - "name" : "ss_net_paid_inc_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 23, - "name" : "ss_net_profit", - "required" : false, - "type" : "decimal(7, 2)" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "ss_sold_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "ss_sold_time_sk", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "ss_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "ss_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "ss_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "ss_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "ss_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 8, - "name" : "ss_store_sk", - "required" : false, - "type" : "long" - }, { - "id" : 9, - "name" : "ss_promo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 10, - "name" : "ss_ticket_number", - "required" : false, - "type" : "long" - }, { - "id" : 11, - "name" : "ss_quantity", - "required" : false, - "type" : "int" - }, { - "id" : 12, - "name" : "ss_wholesale_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 13, - "name" : "ss_list_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 14, - "name" : "ss_sales_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 15, - "name" : "ss_ext_discount_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 16, - "name" : "ss_ext_sales_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 17, - "name" : "ss_ext_wholesale_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 18, - "name" : "ss_ext_list_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 19, - "name" : "ss_ext_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 20, - "name" : "ss_coupon_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 21, - "name" : "ss_net_paid", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 22, - "name" : "ss_net_paid_inc_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 23, - "name" : "ss_net_profit", - "required" : false, - "type" : "decimal(7, 2)" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.22.ndv" : "1697156", - "trino.stats.ndv.2.ndv" : "47961", - "trino.stats.ndv.7.ndv" : "5947530", - "trino.stats.ndv.16.ndv" : "738120", - "trino.stats.ndv.4.ndv" : "12236563", - "trino.stats.ndv.5.ndv" : "1890006", - "trino.stats.ndv.19.ndv" : "150267", - "trino.stats.ndv.18.ndv" : "752801", - "trino.stats.ndv.9.ndv" : "1483", - "trino.stats.ndv.14.ndv" : "19536", - "trino.stats.ndv.23.ndv" : "1499246", - "trino.stats.ndv.12.ndv" : "10091", - "trino.stats.ndv.10.ndv" : "243256717", - "trino.stats.ndv.8.ndv" : "513", - "trino.stats.ndv.21.ndv" : "1274594", - "trino.stats.ndv.1.ndv" : "1820", - "trino.stats.ndv.15.ndv" : "1149239", - "trino.stats.ndv.6.ndv" : "7082", - "trino.stats.ndv.3.ndv" : "297612", - "write.format.default" : "PARQUET", - "trino.stats.ndv.17.ndv" : "388752", - "trino.stats.ndv.13.ndv" : "19495", - "trino.stats.ndv.20.ndv" : "1149239", - "trino.stats.ndv.11.ndv" : "100" - }, - "current-snapshot-id" : 891179441395529189, - "refs" : { - "main" : { - "snapshot-id" : 891179441395529189, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 891179441395529189, - "timestamp-ms" : 1648074703959, - "summary" : { - "operation" : "append", - "added-data-files" : "102", - "added-records" : "2879987999", - "added-files-size" : "106035522351", - "changed-partition-count" : "1", - "total-records" : "2879987999", - "total-files-size" : "106035522351", - "total-data-files" : "102", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/store_sales/metadata/snap-891179441395529189-1-037b55c8-1883-44c4-9c84-f0a475518c5d.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1648074703959, - "snapshot-id" : 891179441395529189 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1648074703959, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/store_sales/metadata/00000-da974fea-9683-4c5b-a98a-5250201c5141.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/store_sales/metadata/037b55c8-1883-44c4-9c84-f0a475518c5d-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/store_sales/metadata/037b55c8-1883-44c4-9c84-f0a475518c5d-m0.avro deleted file mode 100644 index 75654c4a4f17666294c4d69f571332ae12c9f149..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40066 zcmb??2RNMD_C9G6LK+fXh#GZ97bL-mo?#|}AQ-)u(FZ9A#t0#TAdE5wkwl9cA)-Wy z-dl*?8NK{J$vr9echC7d=U$H|@yvXC?Y-At;Cuc{bu`>$~%lB`1Ef6dc0z53H7ADpRCl(1^PnP{p1bJ9&4Q;+X?_}U)XN@pG z7&@C6I9UM$9ZejN7LF#sQ>KR2P9{8mNVc}KHDf{Xd=~)vKLVUBZ2pb_@UH?aoK0;0 z#^gf3jx|O)IosKoIR1_C!oQ72nAq6;jlm+n4mSUrgGGNGY=}VoomGqdIu_Wox3e~JGPE`^Ft&5rxAZ?VScHkOg^i&#rzj6Ii0h}Y-z?AC!pYgd-qFI?3nybcq^+}oq0K+o;cq6oFYdou+;29!FYUiQfZq&xU*Lap z2)`NjzA$G)x4(Uqpx+D}(7e5!?LSgI=r=RB1>D5m5Qw*b*G56V8T=n&3@mJo|Jn9_ zGx=}g9PLak{)T1gp{NaA9PRkNCzYpsKhlk(iLssI{s#XQA8ZV5Elf=SpE9v^cHH;x zUlPwBA;j6x8R-Ok^{R^` z9uEt`#MBUJ?aU&v|M{kPnZ|#W3<2h5{{9T}{&FOk_m6{z+0M~;{~u<1Lq`Xs$?tj;6!>-jz!N~k z1s3;Xjz8bOpule$&_n&cc+fA4|9<{JBnE=j56Rz8<`1o?eM=O~cBahVA9iB4uw`~O z2ln&*G=9|!_!rUN^x|aUVFK8_fswnj$uBnZ?dboQ0A}ZGX#GtZ^Zw)e;{SO3SJ^_p z$TqUJGqy7LYvI3+75-(cv7I&2#`fFbpDX&qD1Ru`(9zLw-~RWZ4PZt;KL2-zu@V#% zHErQ(=Vz53H%qT`sNEffE5~BBN2cTGw+8bW_;#rOLj{5a_Z9e?cmIgBe{E}^UxUg2*VaIwf26=~n))Uju)*&U<n(@z(#bB$n@s`;l+t zZH*n>?Y}`LU>x%w2pp%H+*e#-vya0LDp7CG5D0xNPv0QT~qkn29={hISxr!M_~Cz&-NDz(nI|e`LlHVH>m%grT*bn-|^&ML;s(X{A5!8Bj`WKRDW6o{G8<{%L2rg z{{a90YMP%?ezF(;RtWqY{+kK^(DOdj=3xQi-1kr;2=FTVkV}H)djb3{jQ{H^V2jd)TK`{x^I0Ydrm9Q@@au z{|P$(&7JKNIe)e@z{~#Io&6u6`(qp6pLE-Qz_fpJ7hu4U{$v+m;OPGQUHpF$=#PDY zetO@(fz7|UClJ7H{>h#|fbRa-laYn3;Wq;6+XuWqC;9(+&ac?;=kkBL{lCG$pOO#c zGN33p+M_7q8Tb4-!iD*UNNkQ-sx)qNw4U8*jMYH0_*(LrGZrza zVW}3hG_)4UujOdZ?EhHE?f-oLJM5#=UdFbVP*_-^O-td}`O#z3rJ$WpD48ZtnYA1X z&8@0_XV(jbJdE2dx2{KiZUB6>uFi08SU0N zJ!N)zpHKC?Q0BY(fS_=W;$vM6%paQ`>E1Z%rMo*fbx2%_98`O2yvE-_&0Wd`45~ z2<3Us6l)?Eopb&}_{TNJqb)_AyqVUVgnmx<-qB9mBHdCtr$2qJKDs~C zqA!vmJC4pq>2u`ycJ`^>ru>g@j@u&}LLB^~do31It7JtBtY4^CX)X{+GZS67di)X5 z3Fe~+U;3LwN>`8Kh{<0Zb@pYvOH3g^#7>l;-pRMsf%+Dw_a z25^=9Q@!d`c8D`WI%<@5LP9Nrw)n(%E|u(FNteVY2kpvbt|%^rRv1_hDT_eLx|kn( z5o)#SbLLMZpx8WCBS*WnTFeY;FZHF?XgLy>rFBu>v?axf*xa=1dC{GFt(faYFJ+ru zPq}T+RT=ZH-1i?6EZh8;(nkZ-nI>iSIzDoQ3awDj*Ru9&FnTQAh}kRIsAaW)sWWl8 zcN$2$C%?|Vf=a%ZEsaW+*_cXr#1cwb&bsKWoHUBk!Y1wEHt-lza-Zc&MqYz$lELQ1 z_TH(LZPj_xoa6S#FRTyN$AK2*9}K1prtFPm1w$37a~cdCZlecYNSfzL&Rz?z9RW)) zbiC-d$Y1C69vlx*QY)i-0uoxO$BmUD+N?ivR@z~{a+_*$@P5>-8&W9OBdyxH=>y_X(7(AJHbqL<4VS6v6wo-9#8%!nV{h zwhI-e*p|paA}euM!WFgD-(@A(UBsRq8q=x8S!~9qI}9q*5+bQWdUBJXG1;YOv5eW~ zZSOM8VRQ4DciT>>TdYqF&UjeDEI~Ac_iMM0h#fiU)zG=lUba4MJZ&B!Tx*o48+Xn4 z^K1d_EOBxF1c8JHqWqcO-tErZi_5je^|+bzXSIdu2{ghI*C5FFRQgEJu6;1Ga|GH_ zrjMUmMD!*GubfIR{8*azL=_N57I66 zg!z3uqNk{-n&drdisT6<>U7yLf1a3$c5M5Q`1%oah4PB;spCY)P9I{qdgul*4KoR) z@7bHgmRAp(6EnOZx$S%Sr0TiJAMK9+3>u?e&U{zIUtM+@mtA6t-!xq=$} zD;G{QX&KzHHb-jkzg@dV);tn&G zmZTCAsdo{pF&73kIfrQ5K1l=j4ngB%0Y!GpLrhLShPIC&+OF)pEt-_35%!Ei>mrRq!(1%?dw6Iklbn}oP6&Q(8g%bcAi+|=|= zH5=R@@V>mStS`Ro%n3LuO}V3)GiuN@=XMDrwpD(zcFx`tt)CHh+FrawQMx$PdvO^8 zA1FHqzUed_4NcUjgKk8NUhrP&OM?EJTDu8?cuwLp-u37tKBLYN0mL>>&bJibD^U;aI(obzh>pL>HM4UGk-TOze1-n3I_5#W5jY>d(X;zDLE%iJlR8S&G2TOClaY z+hKizBS#E-v_aef*TXHi2a@wd0e$MTVNLm-KvQxbYDBJuLnq@9FgESfxC}OI5k(l( zsR2QG=#F#>kF0348NTf86tXQ9o{hb}YG4X$$<`qBYd7Qe@{*tqV4uw|^pQ^f=$PFB zO>F+IFS=)WaNmb`T{dER->nf8w~u6SSg8or+72-z}JaLhHnW&WuQSo#lyR z(snSyYWlMkzc4ch{0cMHL*@m2WQ;J7{>1hVu!x76xH5TwKy4$Vu~ZI3D3f)OHJoBsCG{lI8*$6 zk_BSoa-!>3kK7>QVm>_Td+6pN?yKbHM7%E!uMm;h`w|NfueVh^!EH)ZLcYecU`;*l z!Wd}UJLMqc`9@}6Zg%0=M{$i5Aj{Mf;z8G^=pEHHHDn>e^c>ipn(HXq4(M@vI60zi z^aKcBILj8&^VUBu(8}j^4?Qw>k`3K+dO0H-JIDEM%Lv=!V$4v*Qwya^=Q(s+K@ic# zMD_~ffs%4%-*(9!G+d=A9c>%|D^fT?6RAgIu02pm3f~r!gqmFJsXvT7QB*htS1t$^Pi$iU`$37pDxO(ap5y=IjbH3_Xtf$XP(x~a zpV(cdA}7e+GHH!3RfEgAbAQF9YUHI(Ha5E1%zg+vqUSSqPY#7_*RO+84msK}V89Ot zlmbOfWEfA-`K1jJdtD_G@I8H+`0Q2E8Y1x*L=nEEHxJWZC7U3UDnCSjmq>+38i`&y zYxj!7x=lscrHM=G1X&I185hE(5CQHsD#0mEbkdRJdTDhCm1b5NBRi|`;fR9V)`%MC z5fq}0Ifa9on9uL-jl@Bb6rU2nX|-;}VZFDqpJ_n}QccjoI|PZT@dEvugcBAFc`1JA z9*ylb+^AP37W3-(&Gt@#>kvUw{AP?WkajC+bKSkXqXeIg=F`lI-0z%Tq$`z9=sokw zEzWlEP5*Ml(_+EMY?(nR^=#=24Z1uurt5m+=L?QskFzmiN4*H>faW|wOkbc3Y)D}+d3X_00#7RD%>TV&46-HSUF81v1p3BAHWmMpUAw{>T ziC+$_E{e%E*P`7tQS)+LoL7#X8ajN)m-wOY@yA3OSC7>YAANBI=1cpTNarfa1Tpmx z(c!y?7ShF6vL&sA-kawZ~wuZx4cjRf_~1k$)Y|sYRsMD zkB?$f8y~UxN#>I!JK4pVnjDRbhAzESaE%PQw`QjkDa;|O25*|vQ$yAX&!|LDey~zd z>P?@@uvTh2&oJ@G6gMxPoSl(hFJE3+3p$@{g3*C zFQw?@;gtwj(-E3T32G7iImvS=%Lv$78Ui+b(y@&fWI01YBP{9LzZ{Ez%`rPt9(@}x z4#LOp>S$_D#q)nbQToVZJ^Zq>KnlWST5v*^w1OVB&sx#U%WO@=k=Wq0~Xmo2rJ4G52)IK-NNIbIYm^ zFQTmv(S_>{h`=hD)#8GRyP|J1VZap@?` z_E53$UlC7%Axcq|c$J2iNqDay^>o((XR3R<9l~x&< zoBDOQNve>|YN`;+4Z?Xks@AP#Hcu264-LG#nq|Q`n}0%ozO?K`=j6(zqqSHfG>D`R zlF&&jv^hhaP&Jt&ekDJ-iLM>&z@J(;H3!Cm@TcUKA4h#Ycey~TAG>UysHTbZ}L_W$K!1K@b&x>LomP8oaa-~aU-$94<5{a zw7Se6r}(*jP9h#R|LztQV0yfb@fa|rZBw9Vq)7e4kcxv8>C7c&>I6D|8$N5;#eoHi zyK5!r52wkYI~*3V*W1s#R~EZ1PtpxgbDx8QN5hI-9-^njlp!X1vIa~hVASQKw~xH7 z$mo))rW@G3$A3v7s|2&jy=927P+v$ilgEHx zn~S@TP&V?uBdo5yiub;MfqGzcrtx@HMW1CI)WihQX8uX-UT$JpWF(vVSGige+de3_ zssBse$P~6nHF#wkMOsn*-2j~@NC8oO-3}DXwN9rs`=qkj)=*>ZBX8^S$HXe{I6+F% zodn;2?;R}X=H)gc$lRbc=;yhM|Cdn+6wpOrYZ6?{YbwjWeANO+!*we9cf zwRseBB7O|l1V^Q$#gB37lhF{y5fKxKD29-wqkVj_-CsnDr0!kPQD`=*8()kKA5VS&rqE{qmjSRc{r6Di}tUM1_ zVFJ(Njy7m}l!FwnlK%E%eA$vde$(F-+TQ6AgfDx&+-})$T^No^oas2yUtUZexVedq z0E(nzo&ezaG(cq+6B7tS9grj7NUs?QVLV0G?n5Va4mNh8^|hzIdpw%^?G1=$mWt6hA0~Ef#%M0M``4%t5gQd_+AS*n?F30 zLVFkE}NBFX! zL-@^)nwTme+4rBb0jM;LXe~}E41ek%6`8NS>4&+EFYT|O(YQ^I;}s`#ZU+`ENOPyf zp}B)Hv5x8Mnh#%LxS8bV#od$ROMH=(>8TAV@MnAhu(q`?LL`Uoj0<0oC)Pw&o^Js! z?(-?ac|G(5jpA_t#cE_rDClk028RSib24Lh@0tff*@&r$DToj6#fmD`;s)27C1|OR zOITC8`qo`Z_+*p2mFdE2!k*Slv%ucXqo{bR0HvKav(n)yNTro9j$4fgGS`=-rV%b> z6^7n?H~sJ#*Q6K+HAP1ll&l{C8@wlUPe`cuV;${u4X)#9v6B4U1Hp_pEab)G;Ak~u z_fBVfOpDdVVo##1&55Ef>a-RNaD4_KvwdttNTtTjr1rwcfEh-PP^JSRKlA72S6yK}F9)m280J-nQl6_YC&a{_dxm*>#L!#zK5pvHeMCOjwU`aXL(vg; z5?<2DGDy4@b7qFa4SjFb@dH3B2DV_%!EKS>`<6hOoqy}n}}lyyhJeVan} zGnI28nlG*0I5!r>h^-z|2kFq87BrZiI5zXrq}PKcYVISY_xkn_WZaLnxuhfxU{jXz zvjXo&-&6mfqSbr!^{X^{%o)PT)gZR$b9Q9OehHt$xn{ktMO-nt|5T-W%e+>o^{R5} zO5w>gk0LKbj%m@8rz-s<=Cy6F5}v9&i6+@k5`}y^AOcf8S~SIE@I)Hx0Sk*lh6s#O z4@&@{z&)vjxSJD>3Jme7@YoFiH|l9&!kuRBb{LWM{IEfv6qr= z;gMfMhwO)kC6(mYQqE+6wmwLw;TNtKX-av_teUDCKqzSPvc#`NIWFBy^8PX$wqKWx z^rwnde%M`c(_C~Leam+sd}kOc=|Hrr)P?KF=<2}Q9si_A0u`mOt-smK!Aoa!& zoF(qowK}d`g?i(2NnehTUfERA1wOU)KeSl~-sdAWkJ9_GD5#2$13caUtw`P8{XM0g zdKukH_J=oDl+it2dO0iYt$T|fI7uHv2b&VlLO?(SJ>(!%1x~VE@tD0dFG+Gx9X4v!V_pi zK?TI*u5Pwz1iDU6)0!1ct569Q-NEF~OVCgy$xL}NJ_d-Wh6JN?hs5z?CI(U{8}iuG zvIahE3W*&prZ2@$Z&ipaH@wq(i#W$pf*@6Wb^J>A!_PDNYpTbK8>|F%chs=0BX2$@ z&?`c+`)5I$Ga|a>!;ad0yC#sY^YNHyK9BW!Twnk|6*ri@1V`{P+MQ7u4TrK#mKqsW ze~^N}kRgt+z0bObENsPlSbGKo!#fkw@cc*;d|5A(IQ($8WZcxirwFVLMHxdjFTD&< zKj0<8ar~cfAwbDW%M||zoXk6e7FD$lwvHG%NQ65*z$pXp>0b0Aqzt1fP1iuArDI7& z+#pNNhxmD;15)HFpGCmWYNRJn#gsE(Fbqhq&)3 zn#NX{I(OSsa!)MNl-_bGpLkW+AQII?ee!mca219y7@|EvkMg!>O+e$jLS8F8fDu$1 z4R0um(g=gDi{n?1(L|O38e8P5gQiCpV@Kr+I2B9)*3)v7^eZljH3BQ~hEqX!AdOyF zTwUC9>Bv`H?rb7pS$tb{07GJX_$#h?`v&GjK zX36aNp(Ab=a&>GoiDteTo=Bl6SATg?L0ly3GbNoZc<l|YD!Bz_Ld%@yM#`^DkCj2boyl<<>dr@!)*0kCJP4P zi2P)!FNeaG}I&#(Y8Jd^&gB>n8xL5*FkNvqCQjv^@7p3VWZI78U|uCu#QYAlI^V8Phy34z~YNNV|`;s`pq5mlDtdS?Ey+%3zfyy zDBvv_*8y!UU}0sOIFNTTa@{+;@F1!J65B$BdFbs= zjUPM(2T{Wr9PK^~I-naoS2S4e5Oz8f(;j@N@I*~Z+^8QvvEINK+iw2a%?483KPRxc z-T0!}I4}B1iLJtCI|qP?TP*PugmX0+jA0r(<(BLG11{{Ye6P!%&$6R>t+~`x9@6*z!flHkm7yGwuK$dtig+$Ksh9oH#I@Q-; zvv-e6%OYKy_9_P5LOn<1ZNBFuI9 z^DhA!eR5IEVYPNv&GEvltwMM+!@}(l#(?f&f<&Btj%PRV`g4*sqbcr$`S>X62!&-* zS3SgfsY1)yqo{eIpy+7bghzg|lG%XDvNR*GAkCvKex1GA+I#scobJXuxf;-$!nSaX zf%Fh8g`wUNAK(vei|GkCy4cgVHG86HRJr;bfz^-12taJBE3FRxbEgK-q7CmDEW{4# ztqZ`#I2tWgH(&mqe1@P(U{=s8e;)!Cdnj6UHf=AFjP}@E z>+EdX1^xQb2jg~b=XGR)#>g=z1-6qU(*y5UI+5c9tANyKclSCm`s+O zcsWhG`=%!(!s~bdnX<#%S9vm{um(HerkRYA3(`SnqdkV$2Ne1_u*dB0oX=rjX+@j@ZgHg5Ad!P?SV*?l2~Sk*iR{s#kA1Wh z<(F^uM8C4V(_+dv5Y4G~7r?ih<25*3XASPD&z+`=yqFiPrx!>z5#EpzZHMYf6zsO4f@$}H~HCQ=3FYC-xz%+ zuiZ6>O*2^XXiL>HaCy!Cj)qO$Cjg*Arg59ca8p+EDR?{1>70g`HwCyUI>khMwhKU% z1d-%LU;&>wzt5gLz?Vfoz0n?UKmv&ry8)_Zu^CHqxD$n!Fa%+)BMC+~$WC0ckvX5f z$;YAq+&)}ul4D!lyp9Mxrkc~EM9-J`p;3-ZXZ`e$2UjP}LSt7%_hvZc1aK!U6=X0w zToFaRVj5V;zngtZIPs<31v|H1!I)EaZXD^yg>qY&RJ)w_4pG=< zTDP^yl&o?OEW9+yp5JPjFIynDt53Ik3|*{M9#|+_1Ml-7Itm2pC(w60afH5UvLhvm zre@_6q{r$ZCr7|WpZcDSif(06r*+S`@?>mM7YqicXL?Dm;+(zIg|aX09I7-ZQG>vd zqK~4daw%ThXAZHj#(2AF@J!B>LvfxF*d_I3`C7A0P6ebv3VozwoFmL^fG@3R{`PAf zu!u4M!2uMPsT+YM)!E0H+PTpjgGuyMJxsL+x>o>tag-e_%Ii4{v7iNM{H8I8n<{`T zpoZbZu@FUFb-$39OTv^RZ-rjOE`{O8&(a8Q3U)xr#YmA<#pAj5EygbF!9)dJfu+)T z3e}tfpmu7e#h2~PGU?Y!SUQl$y)og}QAkf<^z#_#JU<|%a9(4Sv(4YftH*C;Xwl;q zs^gMVIqd>%17C5gxu~QKB)cjWv2v_|IJVmQwPchQD){J{5d^|7wDJZ5&vzj%;9R7OwShGVTl>iq` zJBlk=zThyh>UR>MzcKW&!XT(@n?7$z0zVYMFHMx);$x=ReqO{fJ63+kqTOsH%eCEX zx%>G`-(G8>tuX8%7cd;97S(+I_^}e|0=6N&U1@IAn5{~0f2|pWJo#q3*3vMQzs$`& zx8N7%0I#@+f)RvE!F7)yGAT$HVVL32r1fPu%2;#&6E0e&2BC_teEjtKO8h)<^D^9! zWvO~iPrt-v?uONUTh3s7S(Q57KB=+m|5QwN%gD4OJ)5P)6LYY@g-oJ@585Yd<{y(f zmyo1MJz^YuOh9^7fClb%5wr@>+rkhRuHqsf0G<5E*gS6qnyAjpYQSxN28X#48;2IX z2H!)%M8wT$WmefvBKWik7MLiIB`+&mqqnhNrQbM0j5Kn$4VMrQPhla>Q3=JZaLDS& zJ>lKQvJ5wtHt8v_gCT0#QT;Z~#r-12<-<-zrNlW6Bez9n-jreeI-^|EtVhmasp(=x zmo@`zZAU{Q?PuU#;FLEM4h&b%~*ru*P%fQd0rQ zycRKg$$hubS*o`_PAx&WmZ|)Xaz^u9K{tBNm;Y|AZk%G-i`cW_?%EP{l0sCio=Y2$ zjqNgkX=^+OFl{!V6zo(Fh2S|4dqtj^Pj8zZr8XLzR)+WkO{W8z9NO9=(?_986?)Go zJ)sTjDN7I4vsnJY*9URmOz#kyN}y~0s*S%_A16rb8b3p z_4omqHgJTTTioohbDO`&@`+7<`=^$mGZa@9?ag9xFM~o;{KJEdj)sy>ou8|ltB)5aL>4QKGj zn=*=%n)JOwoJ+E-Y{lOENFIUlrd2)~?<|$3ur9@zRyCDFn?*sEx*)y-36-1x9V^9L zD(E#VAi4i(Os6rvlLD8w;!$!kxz|FT$7<#Ho z$_m-5quu?b(lu$cw6hjhwGA}OP3Qoek*8UJX57YNo2hE+7SQ@oM8|Q~8D2oaG35!m zysx+!U9q>&$TN1_br_+l1ChL^ZNb^)53#3MbD|bO1!Oj34{n2~=>4Fc&>?t0NS(w( zNo)Hi9U9(b7%Jc{Wuymfy=})>_;rapI@tO5QdY<9*;dZeUBC2%%XRO3i+%1AW!VFR z{#SMMd3D&EgLjbk)&>onof5xTR5pK9Ri?#I>@K$YV?$zVa4|9$u_D%bP0@xJ->P}? z+aVNb)e1)$O+WT*-(uaVo?CqOK#_6zjZNt&&=U7_oJ159drDy$biM{x|1?1NNS=u~ znCYF0a%#@n1?X%$<7>oFh3=ur^7quYo{B8kMqHV#1Dc%AZMNYv2cg3-I8*N&?ELuL zO)F~BHzRpuW}$2oA6dO%35fO zS^VIs$M?0=d7qV{JzeCx;E#u5I4qO^fXhRZcH<*tgEEC5j^wcAy8`4w0s?l576W>X zh?y&I@S_}Rd4<4Fa$v*NQ5X50v~0oicXu254Rbi=tJX5p!$ohlyB5!-L-(d7pH-~! zQWN7hA+m@F2T}GawJfvP!pcb{N1ZrGRFi^oDiIvTm{4hba_FUHal6g(dX&vHa!kD{ znQ+BUj9Xj{(tx@S@%uPlaJ)tR36zV3OQAiHD}Rra25yf*$E-)7XnVc!&B@p|(x-AfLLwqFVVi}swgI0eb1w1HaS2KZ zQE9q3&g{7!zJj?P$ANvreLnxVKVuk_&2y`&B6mg`BIK2Tw(J9{d?;BK*57U(NX5w- z?NP1|0C0uO#n6fY*yZD!?uMAjMbVKw;bLbkJks?!Lwnl&5iFLC$qhJGqX7 z!MumPtUUr6ERF_*OSXMA)ptHEk|ElNMC!&J?iBF0m`bZ}s5FNn+g3Q9Q3gv|euj4x>_1E^OoSzTxeAGuA)x_Qn@`p&v?>cR;hZOxf+ z_Np%wu{Sh%*sHvkJ8B8>lvWNNGZZV8n<($tW?SUj5ZmHU*Wd{x<4MnnV_mx?ER)e_ ziES^2cKxf6kkAl;?bKEnfzTL#228_baF&cL#)2VISali~55V9c00FDoHH3zzF{RK8 zuK>Nl?^)`gRVfkJ)baD8R>zo1&t&uVs)<`BMFMyK*EIn(bxJ_Q@tIMMu>PF`@|Q#j zL7n*}RW+abj`rn@P5DRq5_4zoB3`nwS5d?P1+i5PZuEh9X!(sm?mi-! zYdTZhHh>!m$d4d&e+`~g$(oE6kaX{!dqQk zLf=u^XiBk};|2%KtM*P9kk zBYYdi)R3W?Lf?)gEK2=4t~&OPB88{MV@s)=c7NSOFGNK3n7}bTMdEi2Hu9n^AS{T( zc*-eWw5pMhF=(topB=lYu5NcG0;M8u=^#nd?v3eyew{?L)i(eHul1V4t*6jE{Rpq3 zyVuiew%74+@#orOFt+)wG1w^~stn=TEXglEd<1y}AY}YzTyd<_d+t8_%@wi!9rg)Mu6J4{jM^NdZD*O!^Yg&Lx%Z zsO&IutOHuPBHQ!S5vXK8%oY*eIvk$HUECzEJiWSU8Hzfwc6cHds}+sapCa0>lRURq zk_7FPzbsTmyP@m|bIU#jfeuY}VP8R~C01+93Xa`iNE4jMhqjL|Z-$h;_+*iL%-S|h z@~$9<_R~SFQZWWinLOayr79xETwnLSu)oaqSiamf1zZTa#|O$|ZT=h~M@6P*q-an} zcWAN=Xe}4k9mIrdjrYU3{pb5%aVre;&cjj~XPxoNrlulRV=u?5tj;@qrrdgU5t@L? zx(F0o%KgZ2d>Od-i>gI6tLp0mb%8vL1py|r1nI&ho&i}p-a~VL=)%&g1o0LJzE%hh zQ+$1)K@>v|my(6R`&qE5=3&VeMIqGEn+$qACvAP1_8at<(mtC?2gU^^@*6(BJz)mT zPLlePBbY@zLnM=yhU8Y zsq8z1)ZKPTtFm+8*HsW6EIO~bJGTzTrhbw}j}aPx7Qw0D-dfyjwcyNB=4~-A0q7tz6)75VHt}ajSoZcd*bcG zb0lH`$hOH>P_cU%<(~{pfGes7moTUwWv7ba3{-M;)1DfdfR?mAJ>);Z555b^;mR5z zqv3k}2D+E5GrL2iD&tcXK(?0MMj6{B2O&&EjpB9&4ZkTY|7b1lNxjEhe5{RH#^t-h zMS9oLn&U^0YqXDx7|(>Y4p)t`E(1Nim`qQ>!#Pc{MgbDFxG{|KNuQ~f4!QR;HG+cR z*9Ce!)>(yhk3g7ks9&hY(Uwdnn@nqVNnWLaejtdiPCSDb+WR}g+TW`H)JLDEWy5+g z&;c^qk$*&Qt*==Q)xEL3e|enqM!4~D|6`!h$lH;K@n9v^#o^%F3i4>~t4$wxZ2KlY zgwV}azwXphhJ1y@7`?7cn}CoR@bf;0aaDFji#aRT8QNDDs-`*fUWeez-t9G?#RlzRfHv~-^1ZQcN#Ok> z*S=|Yr$m_vb;s_^kC#F}ndExjzlE)yccR-7$$5ZYV%wM<^O>cHZOdp1&|B3XWNL`5 z{V-gi$rX5id-919`}mmn>sJm7$5<`BR|0Q`>y&6RXL;`kUAA(2&cN2h$ckO6bFd#d zPSd{n3}2S3>=Y~9;Obg!RV)FtVx0(scIVmbzrLkUOC#)aj6@Wl+ObFx+oBKRW?hoT zGr%)R1DiAae}8SD_n zHqaAOxMtJ%FZ#QF`EvdZ=4_^Uo5I)Z(YJFWRCay0yK$uNN#O>2Od1#o4iC$=z3pla z*LxbivO|-4FV6E!cZ0)57rXkXiv=efkC-#~-K#hC{5)ab^JM1fi|SbnoEzukTQK=VNLx^{h5$szF;7|@cogZ!xw7k#qV^f1z(aE-pK^{Ovi`d zS9h!PjAj@qq3w73 z+-aHHRT6(GwtafZBZ}N2x~cd)IM-((8cKd%I%rJz%MfJaa=FLnyk=kx^WLA*8Ec}u zGA-hIWdT&vf1;>6Fkf!R*~SReK!Mk@O2CU4*<2+o24x2qwPc><@y|$UA%&hmcR#mO`T>Ei zxgO}uP1(Jeh2=p_yT`CjIJ#l5AMZkjF- z1j7MehM}^_EO}Y;>J#W>ab}f6|4Dk^`Rf?V>Mkt0rb=Y;tv{9^Koi*v*w|rpNt5wr z&w(?Hp3Z0CgE?y5@@O&jRBIC*^R2TK=k>GUFIVxnMB!7#ce-&+t1lm8lQ)zh%YGz* z!!CM2yHImFg0easS~5l`DHOP?_U4!#HMVoa{=%#xz~hc2zpEcB5a%@wXfP$?^Q@HI z+I3cGj+bmg$8h(YZ=kcBGh45E*A>HgVW5-8^rNdwkYBr`jES6776Nvy$>pB&SuJ^F zTn#3?sa>6ms{JiDH$&91(cu=~e9hkW7<54mj*-4It{M(4Oi%a#G?%uU>3%thEC7$F zYL2s1BNq?ys^sx;&P~_&&gL^fIZWh*6sBnpRXz;DTvULEL2E+{vB#`lcPe;u$^xAD z78!jfo2F3o%1BR%EKp+ZB$rK|#!PGYD!;eDl$qYCgs!rc3`;E}c#puH>jtzdnO`?= zQc;xec?9^sE6jFNY?9k${a16Ti4XAV!HEWsRh6zgt9L&~BXXm8Bd~Y$9WM_N=y(|m zIJnGRwHvN=zA=mnSXDSR^K7s>EsT@^&9<0sMZ&)1_=sx8GJ7w~DgV z78R5P#bz^T#0xJW4!lM4c4xY-R?{;X|8(q5x_u{K=DsR!@gchupDPl z+2c!lwHhzmwiqo7%4_E$mvUqlZIGl4Nf3|ts;$;;{YFzSzdb#=&cSloRRZ~yOAYnj zg+qC8q($Soe>Y_smuS_7#*)D0y2?z>rUG{7jJ}8FbM7bH8999jtB+>S>w8fuX-8jM zx>KhQ;u2Z@c1;zY_I3PHQwmeI)xGhho)YhOfI* zw>PSq%)l%3ObXBQ^U6!EWo(ac zoh*3ursOif-==>~)@Xh#VFafphuyr_S=OJ<$lsON!Q=f@bebox@rQiVy}46Bkt%Fzgn2cD&s zHHeFWoW6pxY|Y{A>DbfJ@^2lm^rE&iDb7NRA{6JpP|zKD>*t}QK;!p}(P5p;J-T=) zBkiqfb~4xMFl`I8qL7N>ywN!$C#-F4_+%Wfy2wRN-@>PFnTF~|fi~yd?&ri><+izO zey7TH;~)itBcP(maVgWN>Mw8jFE8|j_64WAt!{rpx+}5ttJRY zCZMP4-O3*=aoeAtNG~|rl0KUTbS$ue@Y#$`yc{1h>&7Iv;_!8%uHx-Ep8g_MZ}0y8`lNTC+8`O=)F&m3DP#iVTJ-9`3|5d9xFri zR$q?S3l#J+E$Dhdas~|rdz$U%awML=t^8=Y>Yi!pb61;e@I&C_vxuDpse}sPMWUFf zTh}4N9BG$Grv{m^brggkKl%A4P*M;Ggtxt@#mp-%r<__#GA~zq`IvbvY{9+_Z`}v| z3X8lsiG6uFrZ3aN+EY@$*JlH9|0Umprz)BxGW*6Bw5k4qW{DdZu_n-BdY~Jut8QPw zJH1VGMTG5-Aubv!VxIw6hXxppZb=FzBho=}p(@~Y<`7wP@H+(v(%7`?3wdsIs8R9} z%=O6C{VvIns9N0rYwRqeqVB%8uP6qkNVjy?(5=J_DGm%EEz%&}jVK^EfRsopLkvTA zH;5o1F?1s}bi+{3*Z-^McfaxtYr&euS{%+fd!N0p>$Bh41Foy*=g4#4NT5xRc0 zrRNZ!Y*t$i4f=esl!~u2|8r;$?c$*Z=jyJW6LS^6%K-^!80e%3Vm6b3sCv_pr*@}X z2w&h3A!h9%o3YG|A?5lZ3Au&849~2pXQMdfWN!M9OQRitAK{Y)%=f_B@GcqUu$JdH zO&$W{WHZGM4H+P$0$Kz$drM0N9ShhAT1vI+Wrs&W`N13@$QAVhx&Df*C>qfxU)&SHojdc6DzZZWl%Kuw+$Stur4%&2OhHw=aFW-Mc9sL?+ z&P^o}g?Opo5evMiWtDhWxLF5wQCO8H9>k6_${huHwtZDwc?{#>%4|SH2hPJq4@eum zkW~86hQOp#H#)PBFKLf5>084?@uv}^x6A4Q*gM#Z~>{1 zAZIoFnuB+fkS1{kUiU@(mYh(CgA882wTs)Lv?E{PIx}LA{FSMa2s4lcdl1)b#;NBH zxS6(RPlVl+8UQsR^oYUE{a0H)`_(4uJ_J;(P{0lDeXMw!=v|(eV_$Z$wf5V%kxiGo z{op)&^O(1~4MWRSucF$5$G1J|?hwT|Ojgf%@1}pn6tCT1V5bKN0y~TdgBaNfCQ7?p zS{)G&C{Ldp@GL0Eg!hVvJb%*#xE?(~C=Avi=gr<(sZQP5Z7Q80K#(e}2q+W)ZURv6 zrY>eeVB!t`c9Y*V5dsCrj8X3qn0e>FS#R6DCKmX9H$Q3zSDDj=>fSfwnqqeDDrTWm zFmTRcjFuOPh324Z@e@!IIz((;V`d0{(r|qCQfPFuflQw{@q%+q@b{Yso@~HVL8FXY zFTlycbbFway9DcQ>i4{ZI1}!BdioO?)N4`>okIeje=M*U)S;uGGrwD1w03ptc~S?a z#sD7ZruEzA6qgR{)w-IaxN=bEfBVx&oxiZ@^Zwbd_3xPn;i_#evmsuN0+-QqK)ied zPxy(UdW_FK_%hmVJ=X(fFfff{i_T-u$4noaBs5-e=meajVaGa+5zpWBz>fR3ni}+l z;<37o)!*V#&COH*-N9Pz?~?r)U3Lju)CN8h`tPuTFe%D@FR8bqq^u-Xv^Da_neDgF z0Krw)ubE0^?JeOQ{Zv3>5@_I^FTkf8#i^142CCjN5HO*(8M2$w9msSDye0^s9yWFi zH&0Afudlc404-^GsMB%78W}fj=u{)wsm={sMJMtgv-H8js(hGhg6_Lid{fiGj!?%I z-p7@7aM6}u?=NomflGv53)S;H$6w)U;O(hx6)gUXzaq+>#_ z#YR&qRSOaKqS@Dd?H{pXXneW;F4>VxC180{juGQ^kAip(Zv2y&R2DCkkyjfX>o@kh zeH^I$?Rn}7D{nHWAj9Bx;-sP|TnW2-3I?S{S|Uu4MY~%X9%NJ^uAkv{cI__4y%69X zd>Hr#83XvYCutTCV**-B`^#wh@qm1f(IskESX;}5o4CT$ zfe*YrJMOhIo`}KlRD1{7mLcovizv~GZnLub!s~Cp8nyz1s~ZIIAsGxaNZKoblq5Od zh(*m8tn7HXe$k(jySxS>Lf@|y3ue_1PIbjC`2oIU{Qz!mwVi%=90eah;Lrf<1Y?GP zJRbcW@U9f*!-`bq;{ow*i~)%|VLar?u}qEZUq|;8m%$Q&PJY8rWWBQA-G4G)EZ*6Z z5jUD3uPecdgH?@K5vqbG$cRX>hu|Bv!-24<-Y&wuqS@MxRX{xiIJk+ml(kiVzd6g` zV-Rz!d<+O;Y=P2}k84)M~Ss{ge9tjl3luq54N*y@@ehu}#A@M7H; z^SavWxw@6L8B~*TU(875d1}WFNOH?W><2wGTD9C8PE;eePQ*5u#+PI{`E2!%pCG5s zU_o=*(t(Z1cE4=cd|))(pwUmguhJup93K)*1j+(*kDYO4xiAo_A}aBiewLh7v$(DR z=3WWcPsF7I(y@?fz;wHgZJtgSTc_Fsg;zKPZ6C}YbbxqzEFLftl(H=HvbdPAnLRL5#`$n6K;WeC zL|$4Z$S*E|X(F5DyJ;X}+Qg~qkm;KApj*Zb^#2EtP~KvGjUyg>zb}}qRr|CrHp^9) zNs0u96x3hNMh@#2BEXr|dn@W->SzJ{N}@>2t5#g^SBHc}aamaU_^eBq9JS^QMEk9` z>mt_)_O<8326FqwpWOM_>gv^tyhn4sJ+p@tTB8_ItBF1*(GmjV(!DpyQqByQc4?VEMGwTk9X=T{(-w;|nD6mR#V2{(~l z5nU6DOF-d8Ln!mdM=SPSfu-onCfm4`6+Q_ehHP3LtanQ6%2pGT-d$@V<%G$cY~+)m z=auymG}_-Uvyop$vXS-gvym0X+%8)$NjSOI@Hp*W=kxZ%d{NvH3c(D`)9G&=jk0Lb za#TQQ#3z1wkLA`K0J-!ZZpOXV2VZXyE!-A*joUuHGh`KbX<#ux&71TUYBaOXuHQrL zOoqxBjxRsb?M&IFWozZn!fbvB;IS0FQh0Sp$DU6$<$Sw2T+ueA-QD=^8F{bAYJR~) za_u$H-jTaVsxj%7^BFoY~H_>DNcU7lAc~<T*p2oiWlign8HNQ^a<0Pba`<7GT@Fc!{p@KWz}N zK79_2Gz%=}MvDhku=bA%Or~@)wuZAw#r3xh&5&-cD|Rl?Ix*sO2H$=Cmvi&KfU4G8 zl($J*Z%ec72QAM7!v?F|HIM5a*t-;p5+^1+^RVlr#;kV&SH$?BJ| zG;<2>XsP5TU~0&W(BecF^IoNO*8aSwYYBhHwu|>NE|ziD%q81znb)y|gknMHNt0tz z$4|Qr>dFKC{!ez7%Cno%T~H<}jFG zhg#9SSNUaqKMVb-hN3EExc(vmAseisw*UOOoZcC?OhX<@y6d+rmAXhZzk7Ca%0)Dr z%Y!|1KSg}MHpFb$Rrt+tNX_HhZ=T@@{N*YAFAnYXEqL(5g1^G)Q@HAV5!5g3Fl^T{{!R)-Nndp}zEZp@M6GJ=O2xQSK{E+K8j$ zMP9{V%Fh4~2;c_(FGuNruCa@ualOY-n1nAI7Vv!EMwi#~zxMQbM6tk3zNS-|J6!qN zrgh@Zmip^c|FDGiv6nLf&&oko9ZR&P-HfebBVs8HrG6%dcCMpio5)j^$fViL)LGBy zDFbMiLu%R{C>t4;n$58|>%rb=DYy1`f%vJ?Y^|n_>DG%Ed`GNUWob{N&;uVHq7q0j zV?{Xq^6?}nec*1m(|Z-QlP%Y`{rs8J-6r<3yp;;=>17rQBBaZ;&VaO<+R&)9B;E0Tf=h9ykGA9b2*`gXmt8UR=8T_IgUSnYL3LT~wrmDI+s+FyI+hG)`Iv_4 z69tXZgYo?-duV>+QIGhIW-YmTHeG;oxnkOP-D!08 zZ82}GYbT&#Z$9#0&gRoJ#K7GwMf6>*(ehrl!-dA0Mldao59=Vs~S-p7koQ^8`Lko?d5=vR+K5 zHx{RER8KAi@O>KG=y+H%>)?_nwMCvc^5ye_s#hIoo5mI9uqpqxe`|cu71p2sg%Ya! zI)s?wii=xAOE*A&K=>$tGfDSRp+GrFskkW*?-!0kIcZ>5t35R^))p#3tE+2HRC#1F zkhr7i>Oy4Ke#_Gu>KUcIc|f=ELS&WRRzt{RH1I1B6p|P=vQZ^#&mybJMFCOpzpUgf zO}hHX3ur9L>DxuV$}eZB6%L~%odvJn6Xik03#Jw1(>}f!ZQo(Dy{P8qa6r2AU<_I+ zK7Z%aVf*11CXt|e68qs)^Ln9bCdObgEJqP?vARs)k3S;i5{(jnc`rH4l z$+Fw0!^PNYN+>d#R0MixQ}f4H?d3oMc@S%oD1%??&oBt~Ph=Xq(i6*d752P(U48PR zopXy91-24igT>pn*NVT>9rp3Di5o9zrHldq*N4=0{*U@JZQI-~gmoP7pT~Y`Q4@_> zz0fW^)QJ_O{kOE^#8RN)tsI>$Y4s74!W}z7|DyYT0V^y5mK^2dV${_#nD&9Yt2%@C z&HBYcpwO>+>2lFfXpKwzr#ZK~XN+Hsb=^B;PnKvVWNsFB>8!u*2bGE6c+yBd^7`fTwx-2bGJGpG}QL874A~^H(!t(n&h2Qex0Bzn+m|H0G>2 zRr@&MOMDi+KLTweA>=r&OE!*Q9};#IA`5GH(!DxLuPP{9<(^BOq?bCPt`x%o0d|*g zx|%3O-EgeblCnEY2@#))?Z##WUY2b3@yT7|hTn?0#fhTSnP=EpA5d%@-*Jxn&=|sh z`xGJl#a#~^!m7Q;X2tekW8kchuo_ZzL2f>6kU1Y?!247QJ)2AG5s*PD;sGEbBV!F+ zH!?=%LFHL~uw%jjZ6H$tqU@DWI0D^W02qs?vQQs(Arm%+X_8O0ety7Db2taP=mCoSeF2Yny0L%|CwOmvyWRUSPR&Zhv9gAf zJoM2GwyF>zKaIp*``fxPmx(K}a$*=t7i+7WxD=HAxlddqN8E*+M`m0wyV?XQ_L;~-&tT%#n(&$TzSQoXVG`%kAoc!I7w;#6tCFcDvurc_y z01jL59q(3xSGT1-ihyUI3)k>CMB<|@!J`KbzLMn4;ZI`AB|%Rs@z@(#YdfU&sW|aA zd9L)g)Gg8(l@{-nmhDjvAbf#qEIlH;G^QW1TQ-&v$SOr*s26bUW#UP9EFWnw1xAn4 zRrut+ota+AMtb3oc`$}LxiuOOFh7e-DP&WcytZS%z^2isjw}-M%&Aj?q?&3wHv22R z?`BSOi$9SL@#>I7ZU5AI>(;*{q(pXRXs$>YpMcP%KFL#fK8LaD z1duMU7oW1EP9Gj<7b-9)Nm}lcGd&|vzIE>}=JNLcz*Px?i3M)cw%)O7#aqaK#BY8! z_3{-|53B;U7mvZ%cI}D4X9(ZW=`av*+eI#=h^9f%Z+QIOxCxT)D?KBM~`qebS>k}$GG|o<2H21uTOtNp2Vz~cgwb1s6&-l6A)JT zfGAn~#HJ>DkFN-EsiXiqrZcdz;AYK6GQvaUhUU8RqsL+30?6Q?tq5+_>DE;A9gmP= zyE*>#_v}ea6U~JoqzW6yLis=HoL=N4_vRYL0|Bx;W;(v#o&tEl4*(3Gf7xB{l#5g% zQPK>$q@q8P4Y2z46JdrxJBzG;T|N>XNgYh_gb!wGzc4ebd1Evm+$MU?BA_7U$(GAu zhpJVKln>5qedwe@;yRjC@<+6LOySO5%_Fr{Bn35D6`c~RlPH(!ai#VN-E@7MXwh(9 zdim}|-;$ixHRlSFAt{XPc>ceTdg0cOx4Y7UV5z48yM)kCZNQ-jjgmxNfblMg>H{-Mk`Tck zl;*9J=5Rr_uaDIQfN$V}Kkl0Q%Q>0Fn@i;iu(Jj=G93-YtX2A?pROGSG@FKodHNrL zXJ0N4FzWcMt3`j^4Lr?BlHvC?_1Vu!>Yr=n_m{hp%b>WqX04b%EMIGoJH%~#M{zTD zdB7iVoz%zAI#sA!aGnHwVllDx*HQJ&#hV3oexE>k?~R5wz3C#)2DF*iWv3&8LNZ96 zzgjBjU`Z{A;wolUF~f@mYs^=HCSK@&Q3AZg^tb7Sc3)K1r@ip2?6#_sKC|eFX3FY%x9{s@Avaeb^NaaeU#7Xw zo|noYWiH=@!<62s)y+`bV>YL#tU1%Z&Xq?rlj$+u`Vjo!^)11txBrW#7q|uf3p&30 z>XyI#;6~QtHX}pmN9TB(_2sO`Y$Z(`iqamr9E!u<*&K?Zu2~$4CZk%USrgfPn;dvh z%Q1>jq_!Ln-q6uDKi&}ADQW0>+{Ox)%Qx067WLy@hY}+X9<6NOsIsY9&lWGP$GUmUpQ>Oci;6&1|kqUXhBEs>{>OI}oehyJl}ny6&ESjL4c1 zjp;Xw620)wOoW$9^wB5HT6@;h(bvSpxJon?cSmte2+{~P9{-4hDmW#C;@6)BbUqOJ zrA4Bc?Hz$Tk})Gs$eqXhtyRQ8eolfq%^0+nBE%!2Px0f0FFX#PaP%70IQ8QQX1S4J{(J>!P8rdq%Vd0jYmih}aEN+$~Y z-c(-vP0M6*8kZ>?-g#|1*T7u~_k}u_R^|m*V<)Y&06!=oc>D-$eK3_V zT-RTJ{YPy1NF&pulO}~ahLw`t>cx*z4(A-NX>*PK`<~tUy;@f9!HZmX|00G7fNE** zf8qW>sTANef4zkt>{RrX8S&-bd$34!JajUFV@YE8`LGPY`8Q%lxca^aCz?J*XaQ}B zL*t)FTM#OCW<&@68Or=8!|Wa2kC2BTJY2qQ5LM!f-D*-J69n}8;AiM?i)jrsezO

    np@!Kq*PZ^R)Bgl*U=@K;pHkRhf}W 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

    j#rGupR$0@d^E-OUj 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�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 z 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 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/lineitem/metadata/snap-4572939840786045920-1-298a886c-55cf-47e9-ba6c-642d28b8282f.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/lineitem/metadata/snap-4572939840786045920-1-298a886c-55cf-47e9-ba6c-642d28b8282f.avro deleted file mode 100644 index 0261bd8f9cf1cb8b36d73f318a31203ae3b6f7b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3805 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 Mn}?r0Ti>7Hf7H8MCiI)r8@ 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�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

    Y7IFsux;;_Ff9;xFt>7?? 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 ARR910 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/orders/metadata/snap-8400893575492285-1-367513f4-29a3-4499-9f24-b7df6ed65323.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/orders/metadata/snap-8400893575492285-1-367513f4-29a3-4499-9f24-b7df6ed65323.avro deleted file mode 100644 index 0682eb8b1d20771001e02f874bae0e058b64c201..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3801 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 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/part/metadata/snap-7380758221979511099-1-41390b4f-4d94-4aa1-a2b7-d60a4f18ca59.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/part/metadata/snap-7380758221979511099-1-41390b4f-4d94-4aa1-a2b7-d60a4f18ca59.avro deleted file mode 100644 index 3d94b84ebf013c1ed9214f96ca730204e9c979d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3786 zcmbW3&x<2P6vuTy5t(seM&__CSTwSyM7lGRO){RubzybG$|OSA?9f~3u1s1w-La~A zVjLo##f3El#GSz(3xbD5FJ48&g9jIc`5QcldfCO3C|K1$s;bk;*qK{?RK3r8Rqy-0 zb-nY}SaHRo4x`*GQ; zwu`)dHM;97v70`HF5n3Q;=GH12(kh7!4NxiHs6vG7TPwlP2#};>G@O*+t$3mby?sr^S$I)8u3yNp*=`J7=e96 zAUS!S4C=zbrP>aU5#K>v)K-RWLkh#&BBtdBVKLE_h!l8*B7>KC$^dy3k3h2i6yCPN zL+?0*vP(9XM7|pD%TFyUR2#X7vQu6XdO6ybpLt1Pigw{Wl|D_O9EdR-vAeQ0KHMv^F2ra1bh<^d4dDz z2D6=7Q!0|MFm$>mEn$i-SpJvR030UyoNc0NAT_D^A zx;_SwmCdEkZ{`%RI#q(f;0)k!*YaRvyf-drtoiy(W3gzfV^LTm==UL$9xnw!t0#~1 z)Oz%aa*)x~!TxZe;?qMXl009ZL-B?>--P1x!-UxL^3;izXgxnJk$GO)HoKl@Lo2j= z8(Gh=7IGm)o5>7kH+HeIRn;ryYN=$@Dz&m<=(XAfc3*u+iw6i3<{~S`1;;`ig!f}d z{r0@jKQqF>;+7J5ZP)^OO8>F%Wj!}`RE*vZ9E_MdvQQ`)K-v-x%bO*l7+H$&)&-n= zUMmqLfe_EUlyUieF&mANKE*R-u5t>^u}2v=eI=D@fYpm zZtvEu&;NM-*Zb`|t>(RR7jfhD^&eiVuih@MO|G5L`fj04>+2h9!N%>$gP`$Xb-jMF wTAXauivd~tYyIaB9-TvmK5n0U|J%R+yyX9)mmccI`|y>Yz8ilCHZvLi2kt8dl>h($ diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/partsupp/metadata/00001-eeb6d75b-e98a-446d-a0fc-de2976040eef.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/partsupp/metadata/00001-eeb6d75b-e98a-446d-a0fc-de2976040eef.metadata.json deleted file mode 100644 index 0720ca6f33e5..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/partsupp/metadata/00001-eeb6d75b-e98a-446d-a0fc-de2976040eef.metadata.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "89b1f91f-a8c4-4989-8c97-943f1527696b", - "location" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-ORC-part/partsupp", - "last-updated-ms" : 1663698133870, - "last-column-id" : 5, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "partkey", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "suppkey", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "availqty", - "required" : false, - "type" : "int" - }, { - "id" : 4, - "name" : "supplycost", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 5, - "name" : "comment", - "required" : false, - "type" : "string" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "partkey", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "suppkey", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "availqty", - "required" : false, - "type" : "int" - }, { - "id" : 4, - "name" : "supplycost", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 5, - "name" : "comment", - "required" : false, - "type" : "string" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.2.ndv" : "10311101", - "trino.stats.ndv.1.ndv" : "205115671", - "trino.stats.ndv.3.ndv" : "10167", - "write.format.default" : "ORC", - "trino.stats.ndv.4.ndv" : "102793", - "trino.stats.ndv.5.ndv" : "317104119" - }, - "current-snapshot-id" : 3761094200008885694, - "refs" : { - "main" : { - "snapshot-id" : 3761094200008885694, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 3761094200008885694, - "timestamp-ms" : 1647962603837, - "summary" : { - "operation" : "append", - "added-data-files" : "30", - "added-records" : "800000000", - "added-files-size" : "28835458819", - "changed-partition-count" : "1", - "total-records" : "800000000", - "total-files-size" : "28835458819", - "total-data-files" : "30", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-ORC-part/partsupp/metadata/snap-3761094200008885694-1-2ec47d71-81d2-4d24-b332-198ce4da3f5e.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1647962603837, - "snapshot-id" : 3761094200008885694 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1647962603837, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-ORC-part/partsupp/metadata/00000-0fef0bee-83fc-4b78-9c84-b7cdd02fd377.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/partsupp/metadata/2ec47d71-81d2-4d24-b332-198ce4da3f5e-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/partsupp/metadata/2ec47d71-81d2-4d24-b332-198ce4da3f5e-m0.avro deleted file mode 100644 index ec4ef2bf7dbbaacc8c266457ac7bd6dc9f6004f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7756 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 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/partsupp/metadata/snap-3761094200008885694-1-2ec47d71-81d2-4d24-b332-198ce4da3f5e.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/partsupp/metadata/snap-3761094200008885694-1-2ec47d71-81d2-4d24-b332-198ce4da3f5e.avro deleted file mode 100644 index ed500d03fd9a2bc0622b49184767706678bc2c9e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3790 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 E|0-usk^lez diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/supplier/metadata/snap-6442399194812117023-1-0940356e-47da-42c3-93d5-49146db8940e.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/supplier/metadata/snap-6442399194812117023-1-0940356e-47da-42c3-93d5-49146db8940e.avro deleted file mode 100644 index ec0aea73f3bbfdcb891fd23829d82ddb104ca911..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3791 zcmbW3&u<$=6vs*6&_l%u^+1altq>PnXmP>S1S-J=?Cu;=JuzkYd*X?_IqNb}(&&H`hQ&7f~-_TU#yV^Zndp zY=WFkH9B#X*j1N80(gSJ#~&ep9bon-@{@Gqz`7E+3q6W?K8D{#mchs(aqLi$^&0yV zT)qSXAK8GqV8_FBJl}#67TPwl4c~!XzwJ^rY(sMbLRjE1^NnO%*5aicLfepnFarCD zK(g~Z8PtLSq1q~s5uZU^)Y1gqh7^XU6ZuB`U3O|oq1uQb%6559=%whh?963_DcXhiRC+gsa-iqI9{ZbhNQVhT zydPJTSIU>!vPmIXu0tVqd?W6ZSV*Sp5(FLI2BDIwUcg$s1)$T5dcF;P00I99h&;hI zB*A#6R+Ne)EDW8lNlTcbOJ+f#Gle<3ee|-D0&#bcXPB%X3dViyz(^&I$n8sq!miwZxwV>01OnST&1g*B* z&J*j&uP7TCO&!nOnW^~n(1|3^*Jn_?uFf~1`0Owt_N+X0q9t0-j!R^ol~!cO^Gs-k zmTM#HY1TpnQgkkv;q1g-TrL(1OO=XVDVFtuuCL?^OBdLAbs)XCjXa;Z$fAA$TgXJ- zX6&f2 ztCfh7KnRVUk|G4Bdb$KH3s54iQ*sieO~^z@l^?v!y|2>bHiqPY052STz+26Mx&%p@JyMjoI+FVQ3g&QNoqz+*rW_DCy-EU z%AzvG7eZCMN+RUUNpw0tA(5yG*yz8&6v^)*$=GC$bcWv^pE%C#zw*QDe>Rt2z5lSf ze*1XwaPiTPHA24m&R8GRhShp?{qDhV|BKO$gW7Q8=wMJA9NxJ#+8@@3$BnCdqpupH zR=e5!^6%&WI%@3Is&|d``jx%UZq>iOb5I|gdEaSbvq`Sq{C3N{JviJlhvaArk4*N6 z+vd%Iy)b;|_g_lCy_I|E=kGuN>A}5+5B_=b*N-26a)0;6?x(v8XU{v&{qdAOk>-DX C5)^g- diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/customer/metadata/00001-b8261ed3-6d15-4bf9-93f6-ff779c34654c.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/customer/metadata/00001-b8261ed3-6d15-4bf9-93f6-ff779c34654c.metadata.json deleted file mode 100644 index 5def7ef729e1..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/customer/metadata/00001-b8261ed3-6d15-4bf9-93f6-ff779c34654c.metadata.json +++ /dev/null @@ -1,155 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "b2d63cf4-4ca5-4fef-a9dd-6029b8479f77", - "location" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-ORC/customer", - "last-updated-ms" : 1663697732615, - "last-column-id" : 8, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "custkey", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "name", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "address", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "nationkey", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "phone", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "acctbal", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 7, - "name" : "mktsegment", - "required" : false, - "type" : "string" - }, { - "id" : 8, - "name" : "comment", - "required" : false, - "type" : "string" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "custkey", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "name", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "address", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "nationkey", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "phone", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "acctbal", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 7, - "name" : "mktsegment", - "required" : false, - "type" : "string" - }, { - "id" : 8, - "name" : "comment", - "required" : false, - "type" : "string" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.7.ndv" : "5", - "trino.stats.ndv.2.ndv" : "159508311", - "trino.stats.ndv.8.ndv" : "118626733", - "trino.stats.ndv.1.ndv" : "153278899", - "trino.stats.ndv.3.ndv" : "150319584", - "trino.stats.ndv.6.ndv" : "1068508", - "write.format.default" : "ORC", - "trino.stats.ndv.4.ndv" : "25", - "trino.stats.ndv.5.ndv" : "144405011" - }, - "current-snapshot-id" : 9117423429073808947, - "refs" : { - "main" : { - "snapshot-id" : 9117423429073808947, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 9117423429073808947, - "timestamp-ms" : 1647875678465, - "summary" : { - "operation" : "append", - "added-data-files" : "12", - "added-records" : "150000000", - "added-files-size" : "7459261964", - "changed-partition-count" : "1", - "total-records" : "150000000", - "total-files-size" : "7459261964", - "total-data-files" : "12", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-ORC/customer/metadata/snap-9117423429073808947-1-adc47218-453f-4b6b-9a31-22e5d0cf0ff3.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1647875678465, - "snapshot-id" : 9117423429073808947 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1647875678465, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-ORC/customer/metadata/00000-1ef26858-4e70-4ba5-866f-9416ff6d67f1.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/customer/metadata/adc47218-453f-4b6b-9a31-22e5d0cf0ff3-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/customer/metadata/adc47218-453f-4b6b-9a31-22e5d0cf0ff3-m0.avro deleted file mode 100644 index 1f4deead89f4fbb894fd29b235be1ef0aa654b81..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7602 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 SH+pkd`PSq+9O~Eg9rJ&I9L)Cs diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/customer/metadata/snap-9117423429073808947-1-adc47218-453f-4b6b-9a31-22e5d0cf0ff3.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/customer/metadata/snap-9117423429073808947-1-adc47218-453f-4b6b-9a31-22e5d0cf0ff3.avro deleted file mode 100644 index 8706b9caa9ce5ef69288a146674f9ad88a0b9ab7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3788 zcmbW3&u<$=6vs(Um8dvD53~}a6>v(C*LG?<2P9BIiUa~RX=L(4+wtyfcH{A`*_}-= zkq`8SgsRlCnkKfCwn|YC5nSk>07ouJNF0&4AkJJkP-gbW%&gZN);UCq?V0y^Z{~g9 zcRp$D%w4_$cRlwVU$;;P{&RI-A-yhAmJ|=^u4gCNL(rt`3RqI^g!LWw2)x3w*c&LOPFn-8Ph^#Zsxi zkd2Lzvn@x*t`vLKB@hFaAn@%k5wIQ5>Jj88>BfOoDR38h#Af+uei!K)CG*5>hw!Xd z=#OCeCJ20F0OEo@&nA=kmZh-JFp#174($4tOXRQ{iW6W=1BaP!CC9Q7FJ%y7K?1@E z^j`#$lV`~w69$+lODslw2XRrA4BdbPhPUNwx*LSWL|4L7U={KV-eV~PY_w75JrTa@{G`%(YE5u8xoVZ3+t)$X>#R2&x1YsGwqNL z6$t-+Qc>PwsZN(=g`~Rj6m1X3}Rja}HRZDnYk<0dTl$cCgyt>Mtm?`8rf%v1sdkUf4>|=|CzyRtkc; zC64pldP+6vAfu_{xqG>aPY<0)@@#zp#jEmsLT;%HCsn5 zd5FYCFnqk8D`!1fSzM;Zzx14x;{VOg_86g5lX z-kQTH=CuM*<_JEHB$v&B{J-l8K!pRM&QI>BgA@;>_rOGJcy8>9908X)bw$ogw?NGa6 z-V#10iukI4iQ$p9(3xTRUYc@C5trQ;wb3Z)b39YzDyGmBdlZ4wSCX0$8JiTr#RTGN zO<7c-_*|%xR|$leIf+hZCnOS;0bBhysUq21BpI93kuI^<<4fh|YwzD~eD?6=x8@dp z|M{oa=AX>(|JcO%>G#^^pgCM!U)_8%8Xg~x?~j_pt+UafIe2z*G(H}#55L>EbufOs zF*dC`cOL!q#vf-Vo9ml{lhOJq{Tp0bJ9;?4Eyu7Pw_7{oZ`%Xo!D;)*!q3{qaOb#n zFg6b!m}jH*qr>Lf@WtsjU;X*py{Fs1e*Eq){~q37zWepu<*WCe|6RG;%;fkVQIZgu diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/lineitem/metadata/00001-ee5944af-6c41-42a6-a608-0139125beae3.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/lineitem/metadata/00001-ee5944af-6c41-42a6-a608-0139125beae3.metadata.json deleted file mode 100644 index c333a9138918..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/lineitem/metadata/00001-ee5944af-6c41-42a6-a608-0139125beae3.metadata.json +++ /dev/null @@ -1,243 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "2d8a3630-df3e-48f9-974b-3b77a1a0b382", - "location" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-ORC/lineitem", - "last-updated-ms" : 1663697785077, - "last-column-id" : 16, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "orderkey", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "partkey", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "suppkey", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "linenumber", - "required" : false, - "type" : "int" - }, { - "id" : 5, - "name" : "quantity", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 6, - "name" : "extendedprice", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 7, - "name" : "discount", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 8, - "name" : "tax", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 9, - "name" : "returnflag", - "required" : false, - "type" : "string" - }, { - "id" : 10, - "name" : "linestatus", - "required" : false, - "type" : "string" - }, { - "id" : 11, - "name" : "shipdate", - "required" : false, - "type" : "date" - }, { - "id" : 12, - "name" : "commitdate", - "required" : false, - "type" : "date" - }, { - "id" : 13, - "name" : "receiptdate", - "required" : false, - "type" : "date" - }, { - "id" : 14, - "name" : "shipinstruct", - "required" : false, - "type" : "string" - }, { - "id" : 15, - "name" : "shipmode", - "required" : false, - "type" : "string" - }, { - "id" : 16, - "name" : "comment", - "required" : false, - "type" : "string" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "orderkey", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "partkey", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "suppkey", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "linenumber", - "required" : false, - "type" : "int" - }, { - "id" : 5, - "name" : "quantity", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 6, - "name" : "extendedprice", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 7, - "name" : "discount", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 8, - "name" : "tax", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 9, - "name" : "returnflag", - "required" : false, - "type" : "string" - }, { - "id" : 10, - "name" : "linestatus", - "required" : false, - "type" : "string" - }, { - "id" : 11, - "name" : "shipdate", - "required" : false, - "type" : "date" - }, { - "id" : 12, - "name" : "commitdate", - "required" : false, - "type" : "date" - }, { - "id" : 13, - "name" : "receiptdate", - "required" : false, - "type" : "date" - }, { - "id" : 14, - "name" : "shipinstruct", - "required" : false, - "type" : "string" - }, { - "id" : 15, - "name" : "shipmode", - "required" : false, - "type" : "string" - }, { - "id" : 16, - "name" : "comment", - "required" : false, - "type" : "string" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.2.ndv" : "205115671", - "trino.stats.ndv.7.ndv" : "11", - "trino.stats.ndv.16.ndv" : "153538485", - "trino.stats.ndv.4.ndv" : "7", - "trino.stats.ndv.5.ndv" : "50", - "trino.stats.ndv.9.ndv" : "3", - "trino.stats.ndv.14.ndv" : "4", - "trino.stats.ndv.12.ndv" : "2493", - "trino.stats.ndv.10.ndv" : "2", - "trino.stats.ndv.8.ndv" : "9", - "trino.stats.ndv.1.ndv" : "1485693109", - "trino.stats.ndv.15.ndv" : "7", - "trino.stats.ndv.3.ndv" : "10311101", - "trino.stats.ndv.6.ndv" : "3790825", - "write.format.default" : "ORC", - "trino.stats.ndv.13.ndv" : "2576", - "trino.stats.ndv.11.ndv" : "2551" - }, - "current-snapshot-id" : 4352555810371227857, - "refs" : { - "main" : { - "snapshot-id" : 4352555810371227857, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 4352555810371227857, - "timestamp-ms" : 1647878270929, - "summary" : { - "operation" : "append", - "added-data-files" : "155", - "added-records" : "5999989709", - "added-files-size" : "163649958652", - "changed-partition-count" : "1", - "total-records" : "5999989709", - "total-files-size" : "163649958652", - "total-data-files" : "155", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-ORC/lineitem/metadata/snap-4352555810371227857-1-27bed02d-660f-4274-bf90-81621ac8000d.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1647878270929, - "snapshot-id" : 4352555810371227857 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1647878270929, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-ORC/lineitem/metadata/00000-f22e58ee-c309-45b2-aa68-ca11028d85c8.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/lineitem/metadata/27bed02d-660f-4274-bf90-81621ac8000d-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/lineitem/metadata/27bed02d-660f-4274-bf90-81621ac8000d-m0.avro deleted file mode 100644 index 52b53b6a5d166d48ce4541c1558418c7fb11c946..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21057 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 B6v_Yq diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/nation/metadata/00001-ca2e4058-ba15-4cde-82da-d240aee66235.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/nation/metadata/00001-ca2e4058-ba15-4cde-82da-d240aee66235.metadata.json deleted file mode 100644 index 4aaa773857e2..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/nation/metadata/00001-ca2e4058-ba15-4cde-82da-d240aee66235.metadata.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "ac6c0342-e590-4cab-a188-3f27c0069518", - "location" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-ORC/nation", - "last-updated-ms" : 1663697943334, - "last-column-id" : 4, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "nationkey", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "name", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "regionkey", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "comment", - "required" : false, - "type" : "string" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "nationkey", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "name", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "regionkey", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "comment", - "required" : false, - "type" : "string" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.2.ndv" : "25", - "trino.stats.ndv.1.ndv" : "25", - "trino.stats.ndv.3.ndv" : "5", - "write.format.default" : "ORC", - "trino.stats.ndv.4.ndv" : "25" - }, - "current-snapshot-id" : 1267796143814779184, - "refs" : { - "main" : { - "snapshot-id" : 1267796143814779184, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 1267796143814779184, - "timestamp-ms" : 1647878274055, - "summary" : { - "operation" : "append", - "added-data-files" : "5", - "added-records" : "25", - "added-files-size" : "4405", - "changed-partition-count" : "1", - "total-records" : "25", - "total-files-size" : "4405", - "total-data-files" : "5", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-ORC/nation/metadata/snap-1267796143814779184-1-49b3f9cf-2a25-4652-8db6-972b8f8a70b1.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1647878274055, - "snapshot-id" : 1267796143814779184 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1647878274055, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-ORC/nation/metadata/00000-b33f7f9c-894c-48d7-8fd5-6d164de90cb4.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/nation/metadata/49b3f9cf-2a25-4652-8db6-972b8f8a70b1-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/nation/metadata/49b3f9cf-2a25-4652-8db6-972b8f8a70b1-m0.avro deleted file mode 100644 index dabf53de3c07ce144cbc5b404f323f1d77f4ffc6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6239 zcmb_g32YQq7*-Ao2nB7afIufBmbPqhcc$Aba@Z6QK@eILDP@_Roo&a?&MY&t*VgH_gY(9QfYN2nuI0d*8PQwlQ$9>QCJOnA(WHtl4t zv}GYPO;c9#csLQ37cN&LYDrZRQz1(Obr@;}ngGchE6Fb4!4H6k5qUSKs1D#)hA3cA z`Hv_nqxux^l^|b2C0cCG+5O ze?-lb4fEt;RL0@v1&Fa8IBX3v1!Lq6I4c)894vE%6R}&n9qY3rrc)Bc2wAGIY#evTJg`n&Lm+L z1z2KfRWO1N4Y$4=0e!RORKZRPQYp_eLXl2z9>z;n!5z{D_(jGG`=nP$@Ge4?zz3qM z0V|1wi&x4)rr}r4niHIpJ{+wBRW5*vvc>{cZg5(_N#L|%{iduB0F|~27BGdZ;B$c` zCLk>ljOZf9Fk>QPhbIZG99mOb5kSfY0Swq~lWQZ-8ikX|eP@lLVggV@N^=foP-m3z zlV-z7RAj_f2aC=DV9~iTOdH*72_j7!of{)q2Css-A~e)YYSlM5;;_*;c?i;UPIfo} z=0MgMi}3)QMrFY{%Z>p$Dl7b=hG2CnJ0|S3dI_!Gh#*lo@%y~q)&&W&8669oQ8-Zq zEn^6f=-B8?t2Y{u@M#Au;_z@P1NBz3Nh0;+%=gVOk(Fn9p(-JO9bwoJ;Pvc`*cTdS zBgHH5szY+S6;6$QdW|%>w=M{(aW$9x>Xq`P+rJ)csinSS)@oZ$Vhg9c?qRl zq2x6|C96)PB}#Oc9&imZM7km9(Jca8o6v;^RtuLp@T#N)NPA=& z)ny4+^T-sB+PoSKHtf=7MHOgL(=Y8qyK9NmH*2+`i=h_56#3D`7#^kQNAb{Y(rh>w zsL;_O08~J4VmBui@Jk5!A1Ms- zl4E)ax(FcifDtxvhEm%?0-n>U#+I%91Fntw1z4kT|5qS}&gyvvp8B^a1RQlB=36A& zQ4P6f4VqshFj0W$Y5wn_<1#irfp#rG#I2XRnVYy4_?K66{uMkg|;t9Y__l_Dl2SF6u{1?$;pe1_5+mm zAOi)t@8;mz5T%dSK1c>4&rz)m6@F!CY-M6kA6IQ=`ZRhIfqB3MTA1`nGXb4qA}Tc$ z2EBlSMOm0QCOZne0)M^=Qe)-=Kr#N`e2f#wVL(Sis5UdbOhggE0u6Z!=%SySbqoT9 z8jxP#)7yN}YRalzamhoOsqbt&lq!B)bW;9u#j@i~ovy?kIXy^9u=R-hYwPy-M{B+7 zIxl&9<=UgSzi&Exro6+`8wPFsFh70NkGn?I?o69}YC&4t`j<)vw_iKI>!A@-rw(;q zo}`X=KBeZ;SEtzGJA>v7=}>jod+YD3$8K%uf37TP@b!vPWx~}Ha#q~+&2IUVPaW7aw4Db@i!HYHB!Qd$4eduu0;-nXkX=cmq{=0dL}507ES#Nq;YRf{`|D5Ky!PtPaZ^4Qk5?B~4?cNwN<-IeO~1@tnAKsKon6qq{i4GY zyNyfCuYJ7zlG6o8(w?x4x9`7ugXtnQ?rcc8ZvSWAq>8#G=kmI}$>+X1`}=eEQooJA z_tfN+(WBDs1snFJb$W8%3zOT`E-&2#{oqsHQ{Re-3EsfYM7yWVY zT048s#+NI$H6+K^IQzf@BjXD zvw3vsmCNv$c-MTxMs4`tTPGUrb&$5D5oCD8)Rr|2+x&YQV#h*0ZCH+r*w2=OT+`RK z_D^yvI*{6IMH0xT#KDK`yXhG$&WD}>spDbr@vR%6>rflKzXKw=h3mm|u+TJ-+3+#!__jyYu=^SgT$croGCxRTs6H*x6mftYEASx!l5|IM0P-JkOrwkCLqzBUV=kTfu z4}IqNlwERTPUL&ZzVg&Hg=!)fQFh96Lbu~><(caWQ?v{3sqASADUkNwR$q|F2( zexFtpS*z68vb>NC4^!x1zmarGBBZA0xd>w329c6>Nx)j%0ucA&p0}Y7AmE#T$P*kx zH<<3!O{F3Ui$Z5>(o&}Ak|HR?GnmEgqt}!aT(65rqse+ys!#N2EMUR^DAg;7c7bRY zusi}FE1OH7-^@8+b*coN&IQ2HuKB@w|DeCDvF2+tjU}S3^+jPjLAwo^^mr)<8n!&n zbL**WC%P_ zOk}*uTF8YIT~22>yRmolmGzB{&2_!HTGOlSlU}P{VfWRB^y)DpK68-;{fc9tCL)K4 zqyF@!&_6T6z~Gh=du`MLm5lxq-^+V$;;00D8aMauikQckI1gr``-1+HU#eORR7Ib}iT$wV;_^t$2r+_o8r--W7mA6`n zSQ7}Lu?td!;8ahSpmhOC#dSeWs_x$rO^r+OgJP>fR*I3v zExswx(&XZ|m!xNel)BwT99A5xUYmi9yj4=O4OtdD!(%0tO_JoLp0(3LsDqi^aBqno z6J>l=!sPJCTIk%cVlU0OrHsq(i`i(L^f{g>bCpwQhCRx_*(=G+h@4Hz;Bo>9wPq}; zRD2;+#j7Mj&YV=I^Al2us(^$3+f0%CEs~B+=17bD_4pF~;*&p*mfm>zmuLDV^kw(o zg>d2Ix4P?o`(xwIupaL2?cVu%96mgq494~F;OTf+A3ix7O&*4O;Sc+_?oYnjpIG+Y zyN{kP{q^+hv_2f2eRnqA8!p~HY>v!{HJUtbg|_*9t7#p5(;5uT$1Mkk=D-@*_`dmM m+|v*FSjv&mT+u)o}FlgV&$^zPP=ROYVQ7k`3_y diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/orders/metadata/00001-de39ef43-376b-4e80-b317-b3fcbee8a4a0.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/orders/metadata/00001-de39ef43-376b-4e80-b317-b3fcbee8a4a0.metadata.json deleted file mode 100644 index a5a23caa13d6..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/orders/metadata/00001-de39ef43-376b-4e80-b317-b3fcbee8a4a0.metadata.json +++ /dev/null @@ -1,166 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "2d725be5-40df-4b6a-88b1-509aaab61b37", - "location" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-ORC/orders", - "last-updated-ms" : 1663697960778, - "last-column-id" : 9, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "orderkey", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "custkey", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "orderstatus", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "totalprice", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 5, - "name" : "orderdate", - "required" : false, - "type" : "date" - }, { - "id" : 6, - "name" : "orderpriority", - "required" : false, - "type" : "string" - }, { - "id" : 7, - "name" : "clerk", - "required" : false, - "type" : "string" - }, { - "id" : 8, - "name" : "shippriority", - "required" : false, - "type" : "int" - }, { - "id" : 9, - "name" : "comment", - "required" : false, - "type" : "string" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "orderkey", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "custkey", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "orderstatus", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "totalprice", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 5, - "name" : "orderdate", - "required" : false, - "type" : "date" - }, { - "id" : 6, - "name" : "orderpriority", - "required" : false, - "type" : "string" - }, { - "id" : 7, - "name" : "clerk", - "required" : false, - "type" : "string" - }, { - "id" : 8, - "name" : "shippriority", - "required" : false, - "type" : "int" - }, { - "id" : 9, - "name" : "comment", - "required" : false, - "type" : "string" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.7.ndv" : "993687", - "trino.stats.ndv.2.ndv" : "101809026", - "trino.stats.ndv.1.ndv" : "1485693109", - "trino.stats.ndv.8.ndv" : "1", - "trino.stats.ndv.6.ndv" : "5", - "trino.stats.ndv.3.ndv" : "3", - "write.format.default" : "ORC", - "trino.stats.ndv.4.ndv" : "41505504", - "trino.stats.ndv.5.ndv" : "2449", - "trino.stats.ndv.9.ndv" : "270910389" - }, - "current-snapshot-id" : 2019712160243546218, - "refs" : { - "main" : { - "snapshot-id" : 2019712160243546218, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 2019712160243546218, - "timestamp-ms" : 1647878813708, - "summary" : { - "operation" : "append", - "added-data-files" : "41", - "added-records" : "1500000000", - "added-files-size" : "39941698078", - "changed-partition-count" : "1", - "total-records" : "1500000000", - "total-files-size" : "39941698078", - "total-data-files" : "41", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-ORC/orders/metadata/snap-2019712160243546218-1-4001c8f5-3b35-4089-97f9-e2711080f425.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1647878813708, - "snapshot-id" : 2019712160243546218 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1647878813708, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-ORC/orders/metadata/00000-74df184f-35c5-4c2c-89bb-2eaae189fa85.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/orders/metadata/4001c8f5-3b35-4089-97f9-e2711080f425-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/orders/metadata/4001c8f5-3b35-4089-97f9-e2711080f425-m0.avro deleted file mode 100644 index 61ff477b3c4d23795ddfa80dd9d6bbf3b34f88f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9464 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 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/part/metadata/00001-a549dec1-d6d0-49c4-993e-aac98024d18f.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/part/metadata/00001-a549dec1-d6d0-49c4-993e-aac98024d18f.metadata.json deleted file mode 100644 index 8f14a5f57f71..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/part/metadata/00001-a549dec1-d6d0-49c4-993e-aac98024d18f.metadata.json +++ /dev/null @@ -1,166 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "a966c08c-6f01-4438-8c31-026a5358b23e", - "location" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-ORC/part", - "last-updated-ms" : 1663697979256, - "last-column-id" : 9, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "partkey", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "name", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "mfgr", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "brand", - "required" : false, - "type" : "string" - }, { - "id" : 5, - "name" : "type", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "size", - "required" : false, - "type" : "int" - }, { - "id" : 7, - "name" : "container", - "required" : false, - "type" : "string" - }, { - "id" : 8, - "name" : "retailprice", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 9, - "name" : "comment", - "required" : false, - "type" : "string" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "partkey", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "name", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "mfgr", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "brand", - "required" : false, - "type" : "string" - }, { - "id" : 5, - "name" : "type", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "size", - "required" : false, - "type" : "int" - }, { - "id" : 7, - "name" : "container", - "required" : false, - "type" : "string" - }, { - "id" : 8, - "name" : "retailprice", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 9, - "name" : "comment", - "required" : false, - "type" : "string" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.7.ndv" : "40", - "trino.stats.ndv.2.ndv" : "198181644", - "trino.stats.ndv.1.ndv" : "205115671", - "trino.stats.ndv.8.ndv" : "118482", - "trino.stats.ndv.6.ndv" : "50", - "trino.stats.ndv.3.ndv" : "5", - "write.format.default" : "ORC", - "trino.stats.ndv.4.ndv" : "25", - "trino.stats.ndv.5.ndv" : "150", - "trino.stats.ndv.9.ndv" : "13911863" - }, - "current-snapshot-id" : 7291036132088373259, - "refs" : { - "main" : { - "snapshot-id" : 7291036132088373259, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 7291036132088373259, - "timestamp-ms" : 1647878896278, - "summary" : { - "operation" : "append", - "added-data-files" : "6", - "added-records" : "200000000", - "added-files-size" : "3743795271", - "changed-partition-count" : "1", - "total-records" : "200000000", - "total-files-size" : "3743795271", - "total-data-files" : "6", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-ORC/part/metadata/snap-7291036132088373259-1-391d5ca2-a258-420b-84e6-b3f2e86fe27d.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1647878896278, - "snapshot-id" : 7291036132088373259 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1647878896278, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-ORC/part/metadata/00000-56564152-08a7-4e64-8f0a-ce45f7eecaa0.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/part/metadata/391d5ca2-a258-420b-84e6-b3f2e86fe27d-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/part/metadata/391d5ca2-a258-420b-84e6-b3f2e86fe27d-m0.avro deleted file mode 100644 index b96820ed816871197314a0f84987c2f624958818..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6806 zcmb_g4Nw&483sfo36NxhKS8YPN|eR}?{-iA5*3r6GDfQbO`^ria<{xRMBZi;aSbm262qAAB&`O|VrbSR z;7RMY;_!)1gIg#wJa46VF_+E<20Xpd#_%-wHH%^eTI2A@a+YkoKqDIxilJoh*$U*r zsO(6^&YYFadx_>^r&sJUdCFq)-Ef{_C;{VhqH&6yz-*zt#A7U?f=aw%WaKO&#aQSO zl$f9x^E9w!tvqA&7T81^88gL3>j(`-{8Y&yQE_AB%w`&7cmJ9*3MNr`JQpi-<(*iC zMLccfc!;*0C1%RPWYL0X0DgH$78RfDM+A`)Z36hoPDF^0{hUlfNFm2r3uP5@IMD#O z=rCA^TWl-~{wwh>EXu};coKZB_M}sJGEpLB$YNLsm>k$+4Lsqgk@ zU!+x>aSD*mi4-e&!{Bk~-f>**Td(q+$#TYAgLmg@+j%NmBgfjz7ReZ8U8mjJe@AV!4N0T$phCzqX0`)T6q*}L*$m5!|C312?g*bSuUF~ zQf#{X7BEv*ya4u)MBwF=72cCvA;y?6kpq7aT?tsgY!f5of=oTPoHoHVCz%{$163k` ziqMV*szlecfD>o7s_vW6J^)lwFK7S@jjYWCnizv)2?OsWYz4K#J9g?Mp_M_p)G-8* z&_Mv5x?7pG-nmAohsk}pMi8R{P(n&_4mc=J6wgf4sfVbAvWz)cWC8$-OdJK<$a|W= z8{5dlQG(@|tAJOSgqkv1Ilw;{j$G5eIn|KMLrGIM0ph zW~vkMqry(A7nADs3KF3n{5Gp~Y(aw4j2z80Bh-V!r5W9TM2>EpN%i^ylA7&ciWtm5 zoV#Zq7JTD4xj zNA#kCQ#M=@=0*;v{VPZefFTi2&F+YRY6Rl=J~gdJuzx+_!Vo+7P9W+TKM5BF{dx4B@$D zNL9;>K7F-nQ_?5Vn+Ra17HA&Q$CU)+5#v>=is}A1 zBj`};rTo9*`$I}Yu5>QU`TNSqQ~&CD^0}QeO0d|qWn$s_^}pV?Fm{W1IPG}zOB2u4 zHZ?U}Y`vcGUgL@S^D~Njd#C^KXxHkL@DF1Td{lmNVCBS5-|S3Tv?yHvS=aiuuTC_c z4%z?Jk~1@3zx&kPp&$D{+TO8i;Uq(UY~S_ve?C|9Wi_L(>Y5t;_3F3Ne`I_2_K*L#`}NZ1Q+!p%@ru;$nUiA+Zoay9%k8CX+4X%y!ObW=^H-j|9*4BX|jA<`;6;XA3IRFWY5GM6W%;H zu7ByZGri}2F}S+BRQUH32iqPO`mA}M&$?P()6wzre}5v>1>B74Yn;6IwMnJN&hEW& zGP2>>{^+_Pzl|4ePx)2GKWNkZ-phB6ZY})s*vwX9rPxm#PiAZ&BlZ?6pee(2kpyZ^b?@a>v~ zQ+5^wo+%1ATom+Ok$?TQJMGtB`E1U%JB`AzwBa>nM{RV@L>BV%O2fUAJLK=7?m6po;+TkJRziddF%UMR81Qq z&Noc!xpj!5e>b@QTxC>QRA9bl-GY5zOj-54u`kL$^q131dg|8ZZZ4$%R6cn2Xk3?e zs#8v>7n`nBmCXHMc*p-v2R~E!RN2f8kVcW3&W6PIVAu9 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/part/metadata/snap-7291036132088373259-1-391d5ca2-a258-420b-84e6-b3f2e86fe27d.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/part/metadata/snap-7291036132088373259-1-391d5ca2-a258-420b-84e6-b3f2e86fe27d.avro deleted file mode 100644 index e8a2102948f6bba2a9031ca03b932752cd14e85f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3784 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

    e z%gEwUcJBIjxrM2hPo;Q4Q9YIRJty=u@e)0+YB-~QpOn67dB0=(X0f*~na73?ogLDD z;^^VEcU+m{y)s|j*jbgcSDsZ8xZeJYoSuCT^uYofFNP0e-nGj(K6OT|vVfKT^~Q}! zL%E||*@do9wO?f5%6nfL&n{d0u%h`~af(USzpva%vIq8o?e!=07Td3%?)LF=|K?^;v!lS@@e z>HTX?AF*&g6h9SpDsw#CJaZ3Xk~rShh+V6ES>j(`-n__zIV7hC_dt)`>-3qyrNu8Q zp3U1*G0yeI!E)2-)3sHCG?_Hj7p0O~DyL!zE6OWLT`d;hxqI!R6Kj6vB zgMnGmc}+>_z6(Mk;^Lp6N6ki<-YGidGAzyd(%hS??*1Dc%ny2WGWEA%Yu;tCqi6Owc~S5_>%@Z$hZO^_f9LP2glo??M$mmtLq-k zu1*_MlMD}n4i=c$51Q$4bY1@udE3@)gcoKW-~a9kXoELZ?`~xdBrBl&Co5r1#?AY1 z5_b~!+StGDtezH_SYT2%C(>1(xY5AEC^pUiQThDj&^IS0K7VRJmV0!IcbLiWw_6JN z$A68CSYhw(|FUInf*s^pq#r(Ht@jx8kL=u-0_Vb*ALr3o^1T;e$^EQBx#2Eb<98*` zyXX*`J}1Vd$E9yhWJKo~J1tb%OozRW#+_GOnMyJ;u<_FKQ0C~b2OrziH$-pleYx&of#IR_m;Hl9 zKN#NLc{=Xm|Ay!d7n`S=^F!-ap8sC|-SJzrHqYDBvbOfc=w+o#;|7dj%%5bPkm+0y zGB#q?Ur6F0mCKjbwRQPBD^^w9X)L1k+v#IX8O`Fi7w#8-bK}J8FAzbQ)WFHhK%G;Z z9W=r=Aieg?>l6O!vc`D&M6r`UYfxU-yBV%DH}CgdpSbj--Q4YV;Tvr7HvhV+axae; zDoEtnPQSB;91H(M?yoMbejU1Yr8?Vl?D`-BsZr4ir!B_~^+uZcuS(C)IbVB5@$eVF zmNotr$)ZymwS67fSh%AlGmV{;;i8E7+u6u|S(4Qe@sgafvyZP}Hi?+$u-w=N=CwV0 zj+*hqlIU>Ut!v>m-KpuFc4WY}`bTDelX%{xn6chnFYzZAo4fB%U(Pq0%kvL3tgh*;IzXfL|6kayLb)YFg@U*87DIWL6S?wRYQsil(^b diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/partsupp/metadata/snap-2966252276871620522-1-a04ba584-b742-4d1f-8008-ec8283bb5946.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/partsupp/metadata/snap-2966252276871620522-1-a04ba584-b742-4d1f-8008-ec8283bb5946.avro deleted file mode 100644 index 51c6021a9bfd39c5b6ceb556f27b7141fef52300..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3787 zcmbW3O^X~w7{}v+5TXcz>>mq3IV~eJBDFj?5Xaq*{=^+=Y~3k&hX4k!^CaOdNYmX1&4x z1UGMhFhCAqJ~*V9P3BwC!a~PEjv08c8@N8x!)_T~NC*!cWxkUh%SN(PK$r^|h$8Th z2&5-3l0j`45@xK47|9(ZMO~YsJCMQXwgS`k!>E|}N@NP6LYcw4B4vO)MtdM#e+F+^ zGE%1Ok~}E%W-#;Hr(D-kApRkurp0?ysZaK3EMUpMmFg8lyFj!H zw0#O7FIz}o+{_tZeX4}r?m58GuKB^`{!V|<;LX?J8cRf5@5{nA!cGTr>4{Phv|V+a zXVz0%(GD`6I@CYRReW~n#F7{53n;#<&o`;~;xH-pqC9<~rCKkJOJ!b^w#={Rwa5x> z-$C{(yoCs4=yE#4`Hj6*S*_J7)k*}Ydh)1xUDH;p z5LJng8apRN3QqTQ1*%C|=E0c@gUW%R(QtEc+aaeh<<@yY4_U5gV2cylw#oPDY)?n!F!AB#Hj#p|Da|3z~u#s2^Z=MT03 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/region/metadata/00001-ea3004db-2743-4941-87ca-07994e16e4fb.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/region/metadata/00001-ea3004db-2743-4941-87ca-07994e16e4fb.metadata.json deleted file mode 100644 index 8a545b7fbd05..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/region/metadata/00001-ea3004db-2743-4941-87ca-07994e16e4fb.metadata.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "748824be-b0de-4280-9db7-6f6d79a68271", - "location" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-ORC/region", - "last-updated-ms" : 1663697998946, - "last-column-id" : 3, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "regionkey", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "name", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "comment", - "required" : false, - "type" : "string" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "regionkey", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "name", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "comment", - "required" : false, - "type" : "string" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.2.ndv" : "5", - "trino.stats.ndv.1.ndv" : "5", - "trino.stats.ndv.3.ndv" : "5", - "write.format.default" : "ORC" - }, - "current-snapshot-id" : 505527619873344341, - "refs" : { - "main" : { - "snapshot-id" : 505527619873344341, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 505527619873344341, - "timestamp-ms" : 1647879209096, - "summary" : { - "operation" : "append", - "added-data-files" : "3", - "added-records" : "5", - "added-files-size" : "2212", - "changed-partition-count" : "1", - "total-records" : "5", - "total-files-size" : "2212", - "total-data-files" : "3", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-ORC/region/metadata/snap-505527619873344341-1-258dcd78-0714-4b2e-84c1-28b4c77b8c13.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1647879209096, - "snapshot-id" : 505527619873344341 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1647879209096, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-ORC/region/metadata/00000-64a7c44f-f0d7-4104-8927-cb8461939b65.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/region/metadata/258dcd78-0714-4b2e-84c1-28b4c77b8c13-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/region/metadata/258dcd78-0714-4b2e-84c1-28b4c77b8c13-m0.avro deleted file mode 100644 index e0d6ad4540612b2602a77efadab98c843172116f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6036 zcmb_gdu$X%94=H0L@b36MXbqY1EO+;yWMhaPyW$D4JmDvf(c?d=l1s6rF*-_?q0FL z{X=*dL=&2Vh6qZLN1{AZlt2s$MJ1*|F($kbl~+U)hzJN#;>_&r%-r?%xL%uFnsje| zkMH|E_M58@Ef`cZ+?1N-DA-fDgtyvaG9Tbg%ZN)B@8RulN|i7l@Ot=&BCBDO56oM_ zqbEKOuYo8!Z^%uGuC>T*(8Q1z#uY<`-$#II${xq3s%uSr!eg5X#1wmQw}P1{jGajA zBt06HHI$=UK*zK(@fc_|^hq|B^^*unLzZ+S3_T=KM1iJ6WYY>lzXpQB;7uMeEnvk> z_*If91!sWI*(4!=&RGq_%x2vRLarQxdAt@^RroJvqtU~MGYVO&P`Y!nthJYgv) zITFJizt5Y(Kw~1+t-vI2*FPxfaa_e@_P8<*D|PW`NH#bEC(qLdrp4zg54QSz6;AE!-H_~mdmXq83bbU>Y6WVXHn%+- zDRuKzBv2()y-AUPI@{h-Q4r%3XoheD*Mb@@`0x=pOilPqVi!oK1#Yh<d5fkDVs?QJeWfSog8`JOxGl%Erul%wBsB*4!)j4S?eOQ+sKTmiWSr&5mUBu?_TE( zDXTIP!M0!&Mfx&qKyED%g362fc3(##D~n9sfEgKK*e{usE0Vl~ga&vPYB+e7n=vgb z>N2~I^hJfX>}V{^B|R)lMKC)RSaON26iZuW!-R@&u#YF|uhfjm62v;#TcSuZ)MZUc zO@D11ktn&{T;N^C5$TSgr?&`r_o25Igcc2T>V3ydk@nOyryCEe=BX(gwKp0}8*}sW zQUq9PCZ&C5cYTri1gn?c?r;Pm@^gz(oulZd@#vM16dX2G=4e?6YD`d<^tPBL^)q{Q zrpnfn*`+@XQbu|yqv+2ilfJlu2k zs_~WMf8e#zz5wGjp8N{Nu(x`^;nV(>hCrjpTwtgr)00 ziU%rCf=i&&mnJs*h)M9fViF*C4yKTz0sI3Leo#t-JaBSYZAjCnW1l7ispp(VMsOb) zkr|o%(`Qv1NnfOIBG?bCKyyi-CkfaqCaY4TVdx7eU6i@RA=^<^O!V_rni?Yy;EGB9 z=Hr~84pS^|f>{rA-Nvh%CY zC})Q)9{c8yCT%@eD26lX}K8K zTy%L&?NBi3L+jG{_)01Cl7FOsTf3<~zpH!xs=%p5(Wl=(&bMy7J+-0w?lbqAJ@55Y z6+HS@v2c7rVX?QMp!o0!|37ngjNJ6j$_Ze~v|BO#`1M=wR*A#E-TuiV1=`C`44XXa zHR02anmxxp`?_mO&z9@Mz8imMe^tx)r5ARt8~@Sh`>!thT&Ng(>x;t|j?@-T|M)x$X1MZMavx jeNNGd0~_BsH@Rx>xz{h9nc3C0VMjvTb^n(q(>ebiLKgxW diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/region/metadata/snap-505527619873344341-1-258dcd78-0714-4b2e-84c1-28b4c77b8c13.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/unpartitioned/region/metadata/snap-505527619873344341-1-258dcd78-0714-4b2e-84c1-28b4c77b8c13.avro deleted file mode 100644 index 1cfc813df8d3e6414a70a19a305256ab6804c3ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3780 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 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/customer/metadata/00001-489a0dd5-e048-4852-a4c7-714bb8baa437.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/customer/metadata/00001-489a0dd5-e048-4852-a4c7-714bb8baa437.metadata.json deleted file mode 100644 index 6d78b0c3ee6b..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/customer/metadata/00001-489a0dd5-e048-4852-a4c7-714bb8baa437.metadata.json +++ /dev/null @@ -1,155 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "9e831046-7aa5-4cba-b992-8b1c1aac90b6", - "location" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-parquet-part/customer", - "last-updated-ms" : 1663700417435, - "last-column-id" : 8, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "custkey", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "name", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "address", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "nationkey", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "phone", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "acctbal", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 7, - "name" : "mktsegment", - "required" : false, - "type" : "string" - }, { - "id" : 8, - "name" : "comment", - "required" : false, - "type" : "string" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "custkey", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "name", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "address", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "nationkey", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "phone", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "acctbal", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 7, - "name" : "mktsegment", - "required" : false, - "type" : "string" - }, { - "id" : 8, - "name" : "comment", - "required" : false, - "type" : "string" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.7.ndv" : "5", - "trino.stats.ndv.2.ndv" : "159508311", - "trino.stats.ndv.8.ndv" : "118626733", - "trino.stats.ndv.1.ndv" : "153278899", - "trino.stats.ndv.3.ndv" : "150319584", - "trino.stats.ndv.6.ndv" : "1068508", - "write.format.default" : "PARQUET", - "trino.stats.ndv.4.ndv" : "25", - "trino.stats.ndv.5.ndv" : "144405011" - }, - "current-snapshot-id" : 4666520520301191779, - "refs" : { - "main" : { - "snapshot-id" : 4666520520301191779, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 4666520520301191779, - "timestamp-ms" : 1647962884574, - "summary" : { - "operation" : "append", - "added-data-files" : "12", - "added-records" : "150000000", - "added-files-size" : "8426783438", - "changed-partition-count" : "1", - "total-records" : "150000000", - "total-files-size" : "8426783438", - "total-data-files" : "12", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-parquet-part/customer/metadata/snap-4666520520301191779-1-33e9cd1b-7781-4dde-9335-6822c5386bf3.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1647962884574, - "snapshot-id" : 4666520520301191779 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1647962884574, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-parquet-part/customer/metadata/00000-a4196fa3-effe-41de-9607-367743b65956.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/customer/metadata/33e9cd1b-7781-4dde-9335-6822c5386bf3-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/customer/metadata/33e9cd1b-7781-4dde-9335-6822c5386bf3-m0.avro deleted file mode 100644 index 33536912799c26bae774a176403be0486ae9b835..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8271 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 X^QLd#IQYugweNnr_WP}E-Ff~8HFFWl diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/lineitem/metadata/00001-0d17e724-b607-4375-b38a-59b0fdd06683.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/lineitem/metadata/00001-0d17e724-b607-4375-b38a-59b0fdd06683.metadata.json deleted file mode 100644 index 41b305fd3f98..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/lineitem/metadata/00001-0d17e724-b607-4375-b38a-59b0fdd06683.metadata.json +++ /dev/null @@ -1,253 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "1fc41b7f-a794-4c2c-b34b-26c4114c2d7a", - "location" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-parquet-part/lineitem", - "last-updated-ms" : 1663701495742, - "last-column-id" : 16, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "orderkey", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "partkey", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "suppkey", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "linenumber", - "required" : false, - "type" : "int" - }, { - "id" : 5, - "name" : "quantity", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 6, - "name" : "extendedprice", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 7, - "name" : "discount", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 8, - "name" : "tax", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 9, - "name" : "returnflag", - "required" : false, - "type" : "string" - }, { - "id" : 10, - "name" : "linestatus", - "required" : false, - "type" : "string" - }, { - "id" : 11, - "name" : "commitdate", - "required" : false, - "type" : "date" - }, { - "id" : 12, - "name" : "receiptdate", - "required" : false, - "type" : "date" - }, { - "id" : 13, - "name" : "shipinstruct", - "required" : false, - "type" : "string" - }, { - "id" : 14, - "name" : "shipmode", - "required" : false, - "type" : "string" - }, { - "id" : 15, - "name" : "comment", - "required" : false, - "type" : "string" - }, { - "id" : 16, - "name" : "shipdate", - "required" : false, - "type" : "date" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "orderkey", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "partkey", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "suppkey", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "linenumber", - "required" : false, - "type" : "int" - }, { - "id" : 5, - "name" : "quantity", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 6, - "name" : "extendedprice", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 7, - "name" : "discount", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 8, - "name" : "tax", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 9, - "name" : "returnflag", - "required" : false, - "type" : "string" - }, { - "id" : 10, - "name" : "linestatus", - "required" : false, - "type" : "string" - }, { - "id" : 11, - "name" : "commitdate", - "required" : false, - "type" : "date" - }, { - "id" : 12, - "name" : "receiptdate", - "required" : false, - "type" : "date" - }, { - "id" : 13, - "name" : "shipinstruct", - "required" : false, - "type" : "string" - }, { - "id" : 14, - "name" : "shipmode", - "required" : false, - "type" : "string" - }, { - "id" : 15, - "name" : "comment", - "required" : false, - "type" : "string" - }, { - "id" : 16, - "name" : "shipdate", - "required" : false, - "type" : "date" - } ] - } ], - "partition-spec" : [ { - "name" : "shipdate", - "transform" : "identity", - "source-id" : 16, - "field-id" : 1000 - } ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ { - "name" : "shipdate", - "transform" : "identity", - "source-id" : 16, - "field-id" : 1000 - } ] - } ], - "last-partition-id" : 1000, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.2.ndv" : "205115671", - "trino.stats.ndv.7.ndv" : "11", - "trino.stats.ndv.16.ndv" : "2551", - "trino.stats.ndv.4.ndv" : "7", - "trino.stats.ndv.5.ndv" : "50", - "trino.stats.ndv.9.ndv" : "3", - "trino.stats.ndv.14.ndv" : "7", - "trino.stats.ndv.12.ndv" : "2576", - "trino.stats.ndv.10.ndv" : "2", - "trino.stats.ndv.8.ndv" : "9", - "trino.stats.ndv.1.ndv" : "1485693109", - "trino.stats.ndv.15.ndv" : "153538485", - "trino.stats.ndv.3.ndv" : "10311101", - "trino.stats.ndv.6.ndv" : "3790825", - "write.format.default" : "PARQUET", - "trino.stats.ndv.13.ndv" : "4", - "trino.stats.ndv.11.ndv" : "2493" - }, - "current-snapshot-id" : 4619553423372716097, - "refs" : { - "main" : { - "snapshot-id" : 4619553423372716097, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 4619553423372716097, - "timestamp-ms" : 1647970460515, - "summary" : { - "operation" : "append", - "added-data-files" : "2526", - "added-records" : "5999989709", - "added-files-size" : "159137255810", - "changed-partition-count" : "2526", - "total-records" : "5999989709", - "total-files-size" : "159137255810", - "total-data-files" : "2526", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-parquet-part/lineitem/metadata/snap-4619553423372716097-1-62714a3b-16d8-4ad5-a96d-06b3319586b4.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1647970460515, - "snapshot-id" : 4619553423372716097 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1647970460515, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-parquet-part/lineitem/metadata/00000-9d2e4efd-0e64-4f3f-a77b-fdfc7dcd707c.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/lineitem/metadata/62714a3b-16d8-4ad5-a96d-06b3319586b4-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/lineitem/metadata/62714a3b-16d8-4ad5-a96d-06b3319586b4-m0.avro deleted file mode 100644 index a1d4d114a01609eb5e1c6051b21cc2b1c8942bcc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 484801 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

    ;E`oo@qU*cysOi+;H(R^IPB3Ir5=P?}7wd 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 H&B*@)_}eKE diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/store_sales/metadata/snap-891179441395529189-1-037b55c8-1883-44c4-9c84-f0a475518c5d.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/store_sales/metadata/snap-891179441395529189-1-037b55c8-1883-44c4-9c84-f0a475518c5d.avro deleted file mode 100644 index 0d2fc20a61343b3c345466f04707674a6812d14a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3792 zcmbW3UuYaf9LG%{t+Y~5&MHj`!_p^B%WZPk+=dALiG^x4(xf1y=`y!Fcei2xIXj!{ z84iL7#RnCF;De=Dp$h&N^rc1GfDcMa-xT^>ebNVgDvDKS_Rq}h-EG{<+x?mOeSW{0 z-}m>Oh5G8q0|((6_KpQc3pwzw@pXlCe5A}O7#SWml}W{g4*Tvv*J_}E=ncz8^yh|! zY%@@1m)HAN_>i>diX>1#u;n)CchfUyoKv0wiRHQA%;}S$ZIKo@u?QkMk9yQHmDy^! z+@0)?Z6miSN4GsG_L4`S4OoI8uwF&LazVXAP>`mZ1XiWMKE%Xg`RIX<44smB;+9Kz z){FE{aB?1m0WtycKpR`6H{U5KEHq7I>VXUWpyd%c?6Ts9woL;^nXjbBvY0Go5YmDK zL=osm1k#gd$)E-dZKBMw7|9(ZMNRk7O-NvLTY+wPVN^_fB|HUIAU zh(iU!fA3Wku9js!0_ISQI*2laexdm*hdAJAj$rK59)$!S>n+>vh_rLVdhPV*zviOQ~K# zv+-@DL&t$sdaM)# zjg~mh9qTF2NCz2D9qhGp6`vhCvED3s10We9_1y%JN@ehRnN z98NK-6^JR0;L}Jl*&N9Ko2~#%b3okq=~cycD*z2>2jaLgWfZYp0j%@@2RKh2Rj#Yw zX$7Ln5nN)sq;S2-mM%cYIVjcEE;*^t24iM8hBpBl&^gD%#bVP31zpxNE%^_Ut8!UM zJ{q^U#?iv#;-{COXM~iR-EIt)AFNs%fQ_tGP_qqL6g$9T1(i;cl9>^GE-8YG3B=W! zai~J^xlkpm5(qJKQk@P@NF^!*R=P*1BH2?U9h=mV?q`q3`#yZ+aA{FD8Y^}uW8(gp-U@bGp(~*H_&5OUf*-?t1G`7-+c7& zol7q-mHg7w_GdrcdUEyDp69f&{ms%l51siuT)lYg{mo$R&#UVTAN>5s)w37=nY+2! Syf|-k+t*+C_wHD4qW=M#UJil) diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/time_dim/metadata/00001-49904184-0f96-4d37-8f70-2d1fb5e236bf.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/time_dim/metadata/00001-49904184-0f96-4d37-8f70-2d1fb5e236bf.metadata.json deleted file mode 100644 index 4ca2c2fb4156..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/time_dim/metadata/00001-49904184-0f96-4d37-8f70-2d1fb5e236bf.metadata.json +++ /dev/null @@ -1,177 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "bde578d0-2407-4d59-9bce-e377d1ff9cae", - "location" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/time_dim", - "last-updated-ms" : 1663712027887, - "last-column-id" : 10, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "t_time_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "t_time_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "t_time", - "required" : false, - "type" : "int" - }, { - "id" : 4, - "name" : "t_hour", - "required" : false, - "type" : "int" - }, { - "id" : 5, - "name" : "t_minute", - "required" : false, - "type" : "int" - }, { - "id" : 6, - "name" : "t_second", - "required" : false, - "type" : "int" - }, { - "id" : 7, - "name" : "t_am_pm", - "required" : false, - "type" : "string" - }, { - "id" : 8, - "name" : "t_shift", - "required" : false, - "type" : "string" - }, { - "id" : 9, - "name" : "t_sub_shift", - "required" : false, - "type" : "string" - }, { - "id" : 10, - "name" : "t_meal_time", - "required" : false, - "type" : "string" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "t_time_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "t_time_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "t_time", - "required" : false, - "type" : "int" - }, { - "id" : 4, - "name" : "t_hour", - "required" : false, - "type" : "int" - }, { - "id" : 5, - "name" : "t_minute", - "required" : false, - "type" : "int" - }, { - "id" : 6, - "name" : "t_second", - "required" : false, - "type" : "int" - }, { - "id" : 7, - "name" : "t_am_pm", - "required" : false, - "type" : "string" - }, { - "id" : 8, - "name" : "t_shift", - "required" : false, - "type" : "string" - }, { - "id" : 9, - "name" : "t_sub_shift", - "required" : false, - "type" : "string" - }, { - "id" : 10, - "name" : "t_meal_time", - "required" : false, - "type" : "string" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.10.ndv" : "4", - "trino.stats.ndv.2.ndv" : "87108", - "trino.stats.ndv.7.ndv" : "2", - "trino.stats.ndv.8.ndv" : "3", - "trino.stats.ndv.1.ndv" : "89157", - "trino.stats.ndv.3.ndv" : "89157", - "trino.stats.ndv.6.ndv" : "60", - "write.format.default" : "PARQUET", - "trino.stats.ndv.4.ndv" : "24", - "trino.stats.ndv.5.ndv" : "60", - "trino.stats.ndv.9.ndv" : "4" - }, - "current-snapshot-id" : 3991191175779644547, - "refs" : { - "main" : { - "snapshot-id" : 3991191175779644547, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 3991191175779644547, - "timestamp-ms" : 1648057328831, - "summary" : { - "operation" : "append", - "added-data-files" : "6", - "added-records" : "86400", - "added-files-size" : "399602", - "changed-partition-count" : "1", - "total-records" : "86400", - "total-files-size" : "399602", - "total-data-files" : "6", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/time_dim/metadata/snap-3991191175779644547-1-3cc582ab-e4f8-4275-8d8c-b34457635479.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1648057328831, - "snapshot-id" : 3991191175779644547 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1648057328831, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/time_dim/metadata/00000-af07b574-39e9-43f7-8623-a68ececd7c5d.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/time_dim/metadata/3cc582ab-e4f8-4275-8d8c-b34457635479-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/time_dim/metadata/3cc582ab-e4f8-4275-8d8c-b34457635479-m0.avro deleted file mode 100644 index 53bf4a153cd0a7a22981ed03ba7834fa13e40ad0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7008 zcmb_gdsGuw9#-4>=uwV#sr6Kf<5XJff}2cG9@WzNtlNr<;uA4Wk_k+i%*2^VQiO0+ zL|a*_XKS_isIHH#??(k63sqNJ-}qd#TG0CFvQ>#zQB=xy@604~Z$c6SdQMLMn7O~# zcfa57es@L~K57}(UNB}-7Sh!&2N$!g6rP9+B5yN_cr>oOiJ?t+q9z(Q(-dP8@Wiwn zTzaC7##z!LtrztoZK3o+7BJzdDK?s?zv2voGkC&RHBxHDQYC45bRzrdy-9U3v# z`4|VWq*+mo8H<=@ayC9hvp$HKg=TFc6{2BZ#89A&92=}eG|@Q3khJKnmLLIrpovFJ zg-qHk?O~t|@i9$6O>G8!6V0`NA1WIx6v@bx2qApBvomSux!wxZ7WP(2$OFuH2{X4_ z3(3-EN)YuFEArXkIYLMG5T~rMCm!|NsD+gks_%#(~Lw6#ZSN*c*2v9(njuFBxAjm6f;#1QVU9) z2Ptqy&|x2PMpC3X78^Zs80MhGOl-&~OomK~DkC9RsMTnY%w)f2j<=8!4zRgV&qF|L zOj(0T=_F#(c0^+wZQQkOeHE8`{g-A=usWAfAuhn=VknZ$OoM%A}bfF+Waon&l~+)8tJ?VC29X$i*DP|hcSK?V*GoT~-25wY8Q=N!w z7It#Im|SmAkO*|}+pN}S3z9U=bj_J&1Ue{un$ZtPy5@~Dx!zDfLfHXR>}x!ZKa8zjTF#j8E#E~qlYR{ zfT|7QVhOJjs-TCL7~lb3D$#2byp9T9(jJfrAzW9;O=5rF1i6jpJTq;@n8=A1HWwBQSZ zB)JlCMc2z?-2Do~P^NmK2T$cK3?We+i1{YTc31=aj3E1k0uu%Ztp?yIKn(=wu4hw2 za6PRSg?!uaC!L#E839^1P-URYp0=_^LRT$A2R|(N(X>shLI;Qxpb8z>x~H$=i3(J$ zi-6tdBQ~2@69GQi1*nMt*m-I)&@3r`fRZ1?z#vcT98?>^^kLik$Ux{hq?RE9w~P*H zndYaDsx~!!9XyEucBnuLkUp*^pp2NHN)3jA2avBQ3lK-0jtnhGpRat>nEC*WN%u9~ zvjRHw+EG8MO-(Nrp7_234SMt1MfYr0Fz^;?Kze~+gnH1*%If*F^cMSXU#dP=c5`gb z{sY(Z2JTwberZ_6!joAMRF^{;mz+gqMPX;jF16!+pMKt%@t~?`e9nc{kCM=>|QA|7Gfg@&3M23NPaCAHEH$8?^j@5dZe~{_4)4apFWfSNsS}p z&9=w3o{IRceeR_Ldv4#mbvO6niPD)9e{-!$TXMPb;hOsHznqU;H>N&+?fFT!?zbsh zX1})k(b~EJ%ilbWt+!t-UiRR`oqr8jTAQ6D6m-n(e&gHXFT>X7pU6#IBK~K?qm&UJ zf3-HdWPIHGuiBam{y33)@bgb!6jblNIrP1%lRZY5s;Y@i?IL$%{q0!Kzn(pAS^epf z{u7J)q?KnRC)NG1{n+w#w;NW(#Ke?$oL%{7z?!#TNv%0E@8ul>?q?nU;Z*r(gXN|A z`*XH`5We)imM^?I`Q0|HHPc^UewubArJ&d1{o(1_@KM{s)24(+-6r4qeqgtc{#V~` zYOBJ7vzBkqt+-clcVU*eL;salj?3=aBb~=aaq!)&q}gNCX9%^XFt&Y zVC3;1Upp!NN=~tw^S?yxxKy*WsB8S=!osa9uP6Pw*YQeJ-s{7*_nnvfrz<1>JTLC* z>J$BoDsC3l&cF%}RXO*p%e#9&^@{83!kLqD(kjmB*DyO*ZXvGylif37qs8$1z@*vU(_4NxxnVddaR ze(V;i-)9Bq7f$bb%pUJzYvOFzjAM4CjenzdSs&Alw4=RKoW_g0^1hq+`ZJm-Yw^a`X&oXo7nV&}TzYzIMZLq}O8vC->!CHXa%S&Kf2l`aN2>cG@;YC% zrbDE8+o8eZ3V+jdo;$9`HCOM;DM<}^m#fZoo$1=N;bOO(1?ejuUp_wWWK~Va`tmvV za^7)-|7AtX*5c$Ae;Bu~b=a4;T$?9c-JCErBdpF9=g>vDCJc6&XSpID?kyEkx(u4z zdQnLtn~-#P{=S|+<6&i(J-_(1e;=x9nEHXUZiCbD*uVmxixaf@cSFi9xQLD zOkWqDR4okk+UO&$e;nl&Q)8lh(*6-l+zuDXC@zLTbj}G72d$jYmMZHceYP=uD aHLf8C-m$lEP<2(K3QC`=bksF!(EkI%b-Z~1 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/time_dim/metadata/snap-3991191175779644547-1-3cc582ab-e4f8-4275-8d8c-b34457635479.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/time_dim/metadata/snap-3991191175779644547-1-3cc582ab-e4f8-4275-8d8c-b34457635479.avro deleted file mode 100644 index 7085a53e263b683a4a5e288f63cbdf86191cbdb9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3787 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 VHs|cu6K7vK{c>Oazt<%1@Bb&!DGUGr diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/warehouse/metadata/snap-5768182382411552162-1-4ce0ec08-ee7e-47f9-881e-8713ab061965.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/warehouse/metadata/snap-5768182382411552162-1-4ce0ec08-ee7e-47f9-881e-8713ab061965.avro deleted file mode 100644 index 1d865b0bb375f342e3146edb5bbf03382bcc143b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3782 zcmbW3?`s@I7{@jE3oSJ-Vw4(T*tWiqRBkW1%k`j;*lMXLYEq#zB*)zDTyD(n&N@4r z^b7~~2N*#V5lTV(R#E61^`-p-EWW8PL`mM*zG%^RDo6vOGy7v^_U<3GE6-1=KLwJj#(SLv;ZQyjCusyM!ixh zbtn5{JIGtnqdP>4eVQ=n0FfXF@H+@#4>UUr1!=lTU{ee1L&~torxEzbYH+el9D7V= zy}2Z)kY3t{FH};o`|;H=oNjmWa02m4z*Yt_!*JL@5Yb zZFQWx)>E3%4l9av($F=?v#L_H1RQS~F|qsakp3G%J;|SuIcS`*I;WvW95DU1Y?Zz!qvE zx{^5Rw+E#DxeH9WEwIy5ZbOEYe%;)?s?HX0|ri)X4_)f5_Fk1BBXN-{H|&n8uHHG!mB zGZs}Sz7(qFRSKbIPO8)438_SNz*6@RSEP80q+^pi(j(∾CLQKQ`~2|Lod>2j{On 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 H`(gBdzrJv4 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/web_page/metadata/snap-5566490230012484218-1-a0b13234-29d8-4a96-a78c-423abbd72428.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/web_page/metadata/snap-5566490230012484218-1-a0b13234-29d8-4a96-a78c-423abbd72428.avro deleted file mode 100644 index d6f7ce1066f4bbca22dd432d46617f8afef6859c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3785 zcmbW3O^6&t6vvazK?7lf7?+?C%0~3y?9x3uGdsH=e#MLtF>!Z=kYzL0bl2>3=;`jM zs-DU0G7tnYiVzT#2sz~9MFc_oxR`?;6x5Ru6e21_^b#b5py(l3)gM*WGt+C&-sYp~ z{obp3|M#zBjnljL&A=}9=7`xsZTRnfTN>^ANL$h{GCgc*1U>a1;P zOJ!Xj6h>n^$lXw*JDw7I)uYe>JV8M0O$2NgH2M^gB;7c$p#=6JrZ&%~PJCq68CfK5 zyHsSo!u|wD4}*Xp3s4XAuuX^aEh=H5Wg)9hT^}kPM#-&nlNyvw!~w^cMumfKSH-4h2d?Hy6FXBG0~NX6nKRqgC}^(0J#+R zL9%`uUPrA(@^_q3x1-+$ph;8lLAM=<+rQm9&Zj*5XY7-G0>bEl27&r2(@ z>p2iwq3K!3oMtWLK#FFP8P0C(p>nxWDJ|;7d0jV(rD~~YR1dNHYC}5PMVK%bnKBO9 zCTbwO5j*Nf_X_*TbSQ-M3v7Rid zTGy!6N<>*8gvNGA5rR`aU4kkCl!$AGoJ48kF$)4Cnt)H}jAIgFaoCH3EgKq_;s?c6 zg{%}KjaqzBpry%0Z!bwt2`O>Aoj9yGSfe@y8+xmxrW-OZc8teLDw`znOFeC;nNZth zcEi0Td`#r=RSA>BBWlfNE!J$hwf^Sy6t;M)5K z-&wDH`1jPssr$b+reD?%oP{slnmhmYCwJW_{(kwd*S@^;@$8v9YPDK}xp}Pr{UZ-FzIgnpb9>M2?_M>3toav?UoYJK>hznR&OQ0l>hiNU4nDu<;ZF19 z*7Xy&F8V*eu>9^@l}jgoIQw~b?}>$(H@KaA;QZRD P!t2hTYq$T|J)Gcw;k6DR diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/web_returns/metadata/00001-1b87e923-ffb7-4294-9b18-d270678ac85d.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/web_returns/metadata/00001-1b87e923-ffb7-4294-9b18-d270678ac85d.metadata.json deleted file mode 100644 index bcacf68763b5..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/web_returns/metadata/00001-1b87e923-ffb7-4294-9b18-d270678ac85d.metadata.json +++ /dev/null @@ -1,331 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "d353630b-c409-4c79-a423-7219fd5d2d6a", - "location" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/web_returns", - "last-updated-ms" : 1663712094677, - "last-column-id" : 24, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "wr_returned_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "wr_returned_time_sk", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "wr_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "wr_refunded_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "wr_refunded_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "wr_refunded_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "wr_refunded_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 8, - "name" : "wr_returning_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 9, - "name" : "wr_returning_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 10, - "name" : "wr_returning_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 11, - "name" : "wr_returning_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 12, - "name" : "wr_web_page_sk", - "required" : false, - "type" : "long" - }, { - "id" : 13, - "name" : "wr_reason_sk", - "required" : false, - "type" : "long" - }, { - "id" : 14, - "name" : "wr_order_number", - "required" : false, - "type" : "long" - }, { - "id" : 15, - "name" : "wr_return_quantity", - "required" : false, - "type" : "int" - }, { - "id" : 16, - "name" : "wr_return_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 17, - "name" : "wr_return_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 18, - "name" : "wr_return_amt_inc_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 19, - "name" : "wr_fee", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 20, - "name" : "wr_return_ship_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 21, - "name" : "wr_refunded_cash", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 22, - "name" : "wr_reversed_charge", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 23, - "name" : "wr_account_credit", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 24, - "name" : "wr_net_loss", - "required" : false, - "type" : "decimal(7, 2)" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "wr_returned_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "wr_returned_time_sk", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "wr_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "wr_refunded_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "wr_refunded_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "wr_refunded_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "wr_refunded_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 8, - "name" : "wr_returning_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 9, - "name" : "wr_returning_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 10, - "name" : "wr_returning_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 11, - "name" : "wr_returning_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 12, - "name" : "wr_web_page_sk", - "required" : false, - "type" : "long" - }, { - "id" : 13, - "name" : "wr_reason_sk", - "required" : false, - "type" : "long" - }, { - "id" : 14, - "name" : "wr_order_number", - "required" : false, - "type" : "long" - }, { - "id" : 15, - "name" : "wr_return_quantity", - "required" : false, - "type" : "int" - }, { - "id" : 16, - "name" : "wr_return_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 17, - "name" : "wr_return_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 18, - "name" : "wr_return_amt_inc_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 19, - "name" : "wr_fee", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 20, - "name" : "wr_return_ship_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 21, - "name" : "wr_refunded_cash", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 22, - "name" : "wr_reversed_charge", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 23, - "name" : "wr_account_credit", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 24, - "name" : "wr_net_loss", - "required" : false, - "type" : "decimal(7, 2)" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.22.ndv" : "671800", - "trino.stats.ndv.2.ndv" : "89157", - "trino.stats.ndv.7.ndv" : "5947530", - "trino.stats.ndv.19.ndv" : "10141", - "trino.stats.ndv.18.ndv" : "1350317", - "trino.stats.ndv.14.ndv" : "43539834", - "trino.stats.ndv.23.ndv" : "664097", - "trino.stats.ndv.8.ndv" : "12212431", - "trino.stats.ndv.21.ndv" : "939647", - "trino.stats.ndv.3.ndv" : "297612", - "write.format.default" : "PARQUET", - "trino.stats.ndv.17.ndv" : "138447", - "trino.stats.ndv.13.ndv" : "65", - "trino.stats.ndv.16.ndv" : "794159", - "trino.stats.ndv.4.ndv" : "12209422", - "trino.stats.ndv.5.ndv" : "1890006", - "trino.stats.ndv.9.ndv" : "1890006", - "trino.stats.ndv.12.ndv" : "3006", - "trino.stats.ndv.10.ndv" : "7082", - "trino.stats.ndv.1.ndv" : "2219", - "trino.stats.ndv.15.ndv" : "100", - "trino.stats.ndv.6.ndv" : "7082", - "trino.stats.ndv.20.ndv" : "442621", - "trino.stats.ndv.24.ndv" : "801756", - "trino.stats.ndv.11.ndv" : "5947530" - }, - "current-snapshot-id" : 5194348898560540975, - "refs" : { - "main" : { - "snapshot-id" : 5194348898560540975, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 5194348898560540975, - "timestamp-ms" : 1648074859401, - "summary" : { - "operation" : "append", - "added-data-files" : "6", - "added-records" : "71997522", - "added-files-size" : "4126521290", - "changed-partition-count" : "1", - "total-records" : "71997522", - "total-files-size" : "4126521290", - "total-data-files" : "6", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/web_returns/metadata/snap-5194348898560540975-1-0e1134ab-6c97-4c8a-ad75-729685823145.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1648074859401, - "snapshot-id" : 5194348898560540975 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1648074859401, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpcds-sf1000-parquet/web_returns/metadata/00000-15f52b14-5039-497b-9b0a-0be9f4a12b91.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/web_returns/metadata/0e1134ab-6c97-4c8a-ad75-729685823145-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/web_returns/metadata/0e1134ab-6c97-4c8a-ad75-729685823145-m0.avro deleted file mode 100644 index df7ada8c111238a448a3ac5e93eb464280b5be37..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9691 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 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/web_returns/metadata/snap-5194348898560540975-1-0e1134ab-6c97-4c8a-ad75-729685823145.avro b/testing/trino-tests/src/test/resources/iceberg/tpcds/sf1000/parquet/unpartitioned/web_returns/metadata/snap-5194348898560540975-1-0e1134ab-6c97-4c8a-ad75-729685823145.avro deleted file mode 100644 index 7e455014845fb2d655ea51f379de08d7fc472cf7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3793 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 iA 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 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/customer/metadata/00001-0d4ad0a4-9dc0-4241-8794-a7b4112eeefd.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/customer/metadata/00001-0d4ad0a4-9dc0-4241-8794-a7b4112eeefd.metadata.json deleted file mode 100644 index dcca337c557d..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/customer/metadata/00001-0d4ad0a4-9dc0-4241-8794-a7b4112eeefd.metadata.json +++ /dev/null @@ -1,155 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "13bdb0df-1573-4ab9-9b93-5929af078b4b", - "location" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-ORC-part/customer", - "last-updated-ms" : 1663698031085, - "last-column-id" : 8, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "custkey", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "name", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "address", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "nationkey", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "phone", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "acctbal", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 7, - "name" : "mktsegment", - "required" : false, - "type" : "string" - }, { - "id" : 8, - "name" : "comment", - "required" : false, - "type" : "string" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "custkey", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "name", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "address", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "nationkey", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "phone", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "acctbal", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 7, - "name" : "mktsegment", - "required" : false, - "type" : "string" - }, { - "id" : 8, - "name" : "comment", - "required" : false, - "type" : "string" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.7.ndv" : "5", - "trino.stats.ndv.2.ndv" : "159508311", - "trino.stats.ndv.8.ndv" : "118626733", - "trino.stats.ndv.1.ndv" : "153278899", - "trino.stats.ndv.3.ndv" : "150319584", - "trino.stats.ndv.6.ndv" : "1068508", - "write.format.default" : "ORC", - "trino.stats.ndv.4.ndv" : "25", - "trino.stats.ndv.5.ndv" : "144405011" - }, - "current-snapshot-id" : 8024485471681810521, - "refs" : { - "main" : { - "snapshot-id" : 8024485471681810521, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 8024485471681810521, - "timestamp-ms" : 1647956726768, - "summary" : { - "operation" : "append", - "added-data-files" : "12", - "added-records" : "150000000", - "added-files-size" : "7456547569", - "changed-partition-count" : "1", - "total-records" : "150000000", - "total-files-size" : "7456547569", - "total-data-files" : "12", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-ORC-part/customer/metadata/snap-8024485471681810521-1-aae1d166-8a50-495d-a9af-52d223afd979.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1647956726768, - "snapshot-id" : 8024485471681810521 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1647956726768, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-ORC-part/customer/metadata/00000-b5bf5dfc-9f9c-4d87-b06e-f6f7e5c1a2d6.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/customer/metadata/aae1d166-8a50-495d-a9af-52d223afd979-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/orc/partitioned/customer/metadata/aae1d166-8a50-495d-a9af-52d223afd979-m0.avro deleted file mode 100644 index f2f13ab5d6e164e36e9114f13284c1dcc0bed270..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7594 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 z z34JnB{zp zS;K4r7eHt~OEUzEVy5GPvpD`;w>yH>iEBzhL*j#0&=X{qlkLosAL)E4Bo_AGqbbNO z9YVrxDk*7Dx1MntWGBv%7Nt_}^|Nkn0Q{PfxE_2*Gy=ft<{dZ>STPeK2|6T$L6DU%Cg{ z*7rkuU6x=NkyR({t;t_WN{RErMwi z%Q_L(L?pbqNv1-!26w=PJnrtgRh~|Jncb@W*RCzj>Ep(tIj1>PfCT(nD+a@HLfncW zG)ZuFd^~t^f{uJi5tzP_KLGXTU{lz?B&7mJz?`!bT)Vp9#=NI;af9bqvy<4KGIkKP zQ+ummP7X#yc)-VRSPsJq-hOtH^IQoq!ws==*wCN$qu&ZVo2ViV~CRkVQaai0(r7N;SreL zH78gbDb!)@MRPk^Rnp3#Ua$rx>jj|M6KKoVGZ{;{7S|tr#s>Pr*(-kUh(tG3SPOuO%sM-a!B3J{*QI0iW z^s$YZi_2`E>fbp@6^RMA)V2?ao-Qtpl1`C(mG_45ZhW=`$jT^tltgW86X!8UM zV)EOGYXueQ#-a?oVIhO6qPFOq9$LpFNJQw9>BNNfP>>9)aumr{(eF3~bZnh$M?@Y6 z@`z@qHu>jaB=fobxWbgLQ#f_md&zWPu?Z#AX*ZO4-q*{UxH$#4z?TT`LmjNHG#yLa zk32Zd)xHOc)k@Jzf7X_CC8B6=PAD0;Us0m8cIR4yF6IV~iEirPQe_81x@qv{QzE~) z#^Mo?6B1EVG5*g7aH?RDt)0M062rm^sOA%v8^E+4jN)Lr3ia8 z>W05fnC-jXlh=z`8wED{qJ;{A%jO^8bXOn1UGWZ3qaW3^$8BG~Vr6^Ew|Jj)4ZPRQ z7F;rwK*_f~J6s9fk`SK@|HJ3yeqch0b=|6X*Rr_wk`ryi80JX+Q(ZUp@jh0zU{mu( z+A-fse-F54$;CR~8E+zqy8*WM*eD|bRM(VL_?$|ATFQ&e@xuB%qv#AY(9_XFkiC$?RhbmK!`#kD!Gw-nYmtUgwad z&XOGi^+xOhNw76eJ(M$;Ehy>pF~8?ws`-nJ{#=&DSHI*@1?C|^dJaUh*_z#T!11NX zumY^|*FofC5);9CDs&gK4+OBHD0mz=`(}_WyG$gO)p${K7 zs~`B|p@g+r`|Pm0G_%yD@Xbo`RtJN(&eT%s-Uc^isKmV3WSP7bM#gL&akDp!S9iDlKJ4u@ue_Y_!UY#paP&wM-y;-m3~;vTb4-`?w}Z zS1z`(Awr>RdrQHs$OamqulLfVK9HnJ7P#G)tWJ$-hwIou&hKv?-AA#W`L<+B9e(YO9DBNTM>!mRre+iKg@w z*OrAMP8H86)g>ew1=F>NkVoY?ken38a01GQ-6_kO{8LH zG*R3Xaig8t)VppQq^p9f!JZC;Bs9X~SGR*MzGQ5Ba@Xo6K1 zDgB3a!x`>ur?5gttCtdAQh%8m9c+u?$EjAODa6?6<5-V~Z?hFKHuddfTyH`jl> z##(IqFbY>X!{8~Tee>3(*(mk}xs6S{$G)W6rs~mXvQI98(^8JoTSd<=y${NL2r+4p zwPuKfUUK-_100&PC4b#UGfSR-VcXptEX{^2zh}mcTO#i6 zbzXSrQ(EHgBMP2>VK`{O3HC%=*wU@Xup>tUnOSK2-sfx|1?aj~_Qg$kCS~g$Z8LcU zU3qiDvcGb$a~An2If2b@vFndtZQ5Un%0Am4ekCJT15J0v3~mL?gUn~fQl}iEL{!?2 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 ?u zHl`4xFr_LJkQ0@OC~A-?r!D@gN0Gd|-QK#ZOeQM-q`3 zsL!3@e9%}?7q{IJ)^|WsMk63AxWlKgyaE~*-nq9j%kUrC&~%cTN75>PlI`bW+<~8P zgW^^%WI4olrCW_B&#tdWok7PMf!32!y}{HgCTtwL2#(9@Z~<-LpQwZcd7J(v)A#Qm zG`Je!6PKI}dXZp#?ciDGHc5$fn5|z}8==c;R;xypWc!0}yk}${1gej7&pJ7h!yl2h zRZAQb_GT>%O2sni+PoaOtm`X|=qxf=svLefY3T0)9kWevN>!f|17HDCJ?FqC*gYWw$d$!ea)-g8}6L%&e5T`FtarxH9)&gUM*Lq44^XyU(dqi z5nVbfpNEw5+UVvws~f|UKh@n{4s{$WW9RtAE+3BA@vqA>4RRX|Kj*y-T$I(R@lw{l3&=={paVmLT2pfVP(38!^2*_h)HM zdAgS=$~bIfKBMbav7>Lt5sJBEbwIM)IPS_~;edPE@!b-?Y;rlnHK@g+-_xE8LC`qA z?q?-$^3T%Dm%G;r@+OK^rPxHBFHVCMGG5S`dFZfDKW29{7eBtL0xpC?gImuCNOq20 z@De|CMTvMXGWR4HPomZUgH`*m$O(R~f)xuXQkE?6;!=yV`n;V~0I|L5(ehNWb|?e7 z`9VhRUMhb0*ws3sN=u3=jTci0f8dSXt77WrXxhN{oNc`MZ{{A~9J3z;T#bB_NWKoJ zV9jUc7HDp8@U_Nw6->JB6!0+$5}>p{@0K_J7ygD$~f)$mXqW)sB8Q8 zLYy4D5=2=dbMC_f3!@vIoomzib2d17pc{;1m=A&Xd)>&jBOqRRHDfQ`;|1BLsdQqm zw<2V?p&t?a`;6VrGhY!(%Mtp_0~C&aSc9VY^XwzlC8Tg~U8nlD9eNI1$GQ(^^)|mV ztu+UqLr~p*;l_0q-)zLGFOL}DbFLaIvau~_3!WXf#a;`g_u_H)^2wv&H@ z1D<9v4+Pqczo*r?#hkr@4quz0vA)W=3%Quq-tu1U?ZV|)LFcO58iYX6#`v*{%w6zW zZZsqD&)Tibj$vEf$lXLy(Q!pJt8b&qbw~ClBX3)R_OtXw!p^uvsP^H3MX9qT&etB4 z7%II^TkG+)@5%g*TbEI)aZ`VgR<@cq|2fBsft87so_6UQg~3g<$VAdSDU?@Z?>?q7 z9kBt-d?b|!P5n8#M9s+I!6TgEo6e6#%4^Ygxd@thM`!j~N!e_sjNV|yX5nF3!yfm$ z-o_ZVgD*3qZ3BlzqEDrMW^2;*Z;lU28&|oYc^MS9%7mg@=Xl@2t2Edu?K&%SB&C;1 zY;O#x;Ct$G?auQobHNg2;J}3mt?4lLR>^zEpk@-anL}5oaD`6>AP*+@$6%_I0mqW_ zkCI~-IyF|?%tCG^WBh-N45~_b4qeQ*<;?*;@(UTTqwb^0;16|mssd|=Evrv}OEO!` zy$$&N4r3)-&efy3L9@-q(g*m2eKNqw`_GJz;;BL6p-AdCybgp9IuO2KqSJcA&nBJT zyU=Q0!8Oy<+28@tt*9!u!f(8->%zX zI|p$$2fJY>z{mE}pF3yN(m?thx~H~2Gt{{{@+@|lr(<}tzZ<8@DQg$T;0pHp21*W3 zv)W(_7h)xN?0Epd�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

  • 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 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/part/metadata/00001-704f5147-bec5-4989-8e44-3aa5375b5231.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/part/metadata/00001-704f5147-bec5-4989-8e44-3aa5375b5231.metadata.json deleted file mode 100644 index 9a6a134fb43d..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/part/metadata/00001-704f5147-bec5-4989-8e44-3aa5375b5231.metadata.json +++ /dev/null @@ -1,166 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "d676a1fe-10f0-4483-994a-5409266c1906", - "location" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-parquet-part/part", - "last-updated-ms" : 1663701616962, - "last-column-id" : 9, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "partkey", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "name", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "mfgr", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "brand", - "required" : false, - "type" : "string" - }, { - "id" : 5, - "name" : "type", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "size", - "required" : false, - "type" : "int" - }, { - "id" : 7, - "name" : "container", - "required" : false, - "type" : "string" - }, { - "id" : 8, - "name" : "retailprice", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 9, - "name" : "comment", - "required" : false, - "type" : "string" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "partkey", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "name", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "mfgr", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "brand", - "required" : false, - "type" : "string" - }, { - "id" : 5, - "name" : "type", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "size", - "required" : false, - "type" : "int" - }, { - "id" : 7, - "name" : "container", - "required" : false, - "type" : "string" - }, { - "id" : 8, - "name" : "retailprice", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 9, - "name" : "comment", - "required" : false, - "type" : "string" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.7.ndv" : "40", - "trino.stats.ndv.2.ndv" : "198181644", - "trino.stats.ndv.1.ndv" : "205115671", - "trino.stats.ndv.8.ndv" : "118482", - "trino.stats.ndv.6.ndv" : "50", - "trino.stats.ndv.3.ndv" : "5", - "write.format.default" : "PARQUET", - "trino.stats.ndv.4.ndv" : "25", - "trino.stats.ndv.5.ndv" : "150", - "trino.stats.ndv.9.ndv" : "13911863" - }, - "current-snapshot-id" : 8171683153397007568, - "refs" : { - "main" : { - "snapshot-id" : 8171683153397007568, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 8171683153397007568, - "timestamp-ms" : 1647972112042, - "summary" : { - "operation" : "append", - "added-data-files" : "6", - "added-records" : "200000000", - "added-files-size" : "4853381664", - "changed-partition-count" : "1", - "total-records" : "200000000", - "total-files-size" : "4853381664", - "total-data-files" : "6", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-parquet-part/part/metadata/snap-8171683153397007568-1-c92610ba-365d-468d-840a-166a97a61f0d.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1647972112042, - "snapshot-id" : 8171683153397007568 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1647972112042, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-parquet-part/part/metadata/00000-9e387a56-058f-402e-9a08-7591597a326e.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/part/metadata/c92610ba-365d-468d-840a-166a97a61f0d-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/part/metadata/c92610ba-365d-468d-840a-166a97a61f0d-m0.avro deleted file mode 100644 index c25ca0c66443eb240c4da52822adc64bd2508220..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7139 zcmb_g2~-p37G@FTRn(#=qV&8mpw${RiwH!iwiTZX;DS6s z@mK}4btx*PeJVvnao<`OM5#{!#XfMMsGx#?BKq|GGec%3kwiex;dqXd|8C#^-8**0sImaS$KSEIie?F(x3-FwGffQr{x?&LW)AhvszfEkH$#_;G)qWT%a{z z82B&EH@Q%tLe#JUBLy<*Ip33*=Sc;VutJSu6ktqXi!~4uV?InDwB$k=D|9d!&3d4E zh~h3t0at<+3B;8!iQ-x)boxYS0ZK+g<3k`CGAb&K1YMz6B;sX8`&HwF2BvV}x|8pD zJctc3XCNva8AM%&5W)#1^$!G^Gsb|J;wTpJ>!;cmrIKS~Sgv0zGC@F|Xqvo)mppU- z3MFo!Z?PqN`eux%1RFGw2m#@0h&il>P%T78gL;_T;Nv3Yxu$WUdP{+76_Ify!sCod zfEhRmW3)GjI!?Ja9_RZW!Sfx7;mY|6>(2SM-aK0+ju|vs+L+78NJ^(^|0s!Q z0FBt^<_$AnEcQ{vip9Q0hL~`U8Mqc21z7UZiiI%)M{cG$OzutWn@HUxhDV`F7@NVo z1vIcukVy3qP2eC{N8J-sA%vti7I0$9R^EL}MDGC=-3vm%LM3KUfhLBiu>^s45)=#vTgQ%{BxR+bFZBZgC=pWt znshfiYprul!XGB@@lPfNj#Zn!p;{ zq<$R)%Q#m7uOJ#VcC<1bY?W+M{_w!?1qh*t1egQuXKal3m}!!c6wiD*0G(u{6S;(S{kM^e5l(&Gfkh)@B(w5D7s$;*Ne0?q;r2hKth$62j< z1aptvi;9}EuCy>$;y~?p1KD9PiFhzdacC?;=z-$16VuBZt^S7Ub%+wwYA5ml%LCA5 zQ7xMOp%*Jr+O`>jUC}7gje?$A48d;6?S=dWh}$RiCeofWZPQN4SM!`HKkD1n(6I3~ zTM<_QEj3%Dee3SpBlW2PM(@rdu39<*EArbGV|*J$KNpW%y7_Xy^=MWAk|%O5xqaMB z>bLII*j0?4v@ZR*Al&las_1V^D2qz~7lqsO!F*{&{#y&fdEvHjvqgY2H=!_#GbOc! z0x;*8Q6mQ50w~EP6EF)fJ;qyp0x_7WE;HiE{K|!(s1C$@yJS081NRAl_R9%OE}q!;+}U|+Pf zvg#Brw>h&5=^5@DRwq|1`Cr-mkXq;ME`Jtl0{a;}PS$0Nx>696|Gk@NfVd&&`0E1y zys(}b0b?_tLP~M-@>}vJJAbvW`O0Zws7uwHK1$z>KV>gppN_1qn3J%h-|(V7UKLJ) z$XkDn7mZ%oee{|KQyaeiB+cn>Pm<0aZ^}jYo>;Ucq5jC>IaxlR&y?MK(Rgj#_|zc- z$9$LSwz_(nr_0*k*2AG&LOVC!UoCdMdN+P(vK|M+aui5!`>xTJqUZfIFb!i1t;?t_26{nhdsx2Y4Z z1x$PP%jTbMbl+QuTwGjv#beOF4@t*V`o+aO=p{%G{QTjaIcqaR<{o`iQyS_P^VbTm zpxCa%)zdaS>XP5EZAgJu9e;0>PuZf^RbOf=Hq@PRzVxC~;7MCM$o8Y{wvG|D-IA)s z9-g8e8KS;QQSZB=OAh${te4ph_EnANPi=nq@xHKgXD)|T8t!}(c}xaZt4F&W_;K!f zReeUh;rx(fS4GoIbRt@(PsrYx8&O`r%wa_O?Mo4dKJD&WmHPC>sdKXg89B=`nv!+b zCT**)t=uMgvNZ$`5O^CB*yr?`boL5mi>(eDA z!}HTUrJrb@)n1)oThe&*(QDFCWZT&(sneh&$3968z7fATAAkNR&@rPb&)A@>t=%;6m>`_>y%Y%DJ!3-!JXz z_D5Ynm!M&p!Kx6=Hp9Q}uiCMINL#h6mrq$yQ@G3X;;ix$lcH>T>XK|GB-uqL+0IO| z&%5P#W7K!2UcRZ+gA4 zV~P)+x_)=ezhW?)UjGHt62$-C@U% zE)3BPtgx|7v2iG}vCp>YjBPx1>DZ_Zw#5e%-F5Tb$30wtyET28=e0`KqwDomV~a7@ zB5(Kf%jI@Ksop`oM+Owdzm&gjepvHsqdeu+{mpI)|MG5Q`#!uO@I-ubZM;L&xANLM zlNXHNo4;~7-WPqEnlWV7jPh5Cd@?LVvSQZbblrY`X=d`F$yv{KkPfDoS`eBYRrZ;0 h-L_C&%#qL1Dw__Baa_Mtv%<6S=nvZ4e>Wd7{|EBZ<}3gJ diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/part/metadata/snap-8171683153397007568-1-c92610ba-365d-468d-840a-166a97a61f0d.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/part/metadata/snap-8171683153397007568-1-c92610ba-365d-468d-840a-166a97a61f0d.avro deleted file mode 100644 index 086283fe837c302a5c28310ab0bfdc3eb17794c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3788 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 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/partsupp/metadata/snap-8775012740330327682-1-7e61b2f6-a7af-4dab-be09-bd5bbdfa8faf.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/partsupp/metadata/snap-8775012740330327682-1-7e61b2f6-a7af-4dab-be09-bd5bbdfa8faf.avro deleted file mode 100644 index 944d190861beca183113c3b01a69a23bc326c80d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3792 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 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/region/metadata/snap-6009178438198803734-1-12a03294-b36a-4455-a41b-ee395d1a5a49.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/partitioned/region/metadata/snap-6009178438198803734-1-12a03294-b36a-4455-a41b-ee395d1a5a49.avro deleted file mode 100644 index 855400d42454641d1387e2e5184dd446b13a3317..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3782 zcmbW3ONbmr7{_&e5@pe7R>(t&MmG>9p?ha`r*}Y6@{lEgEbf}ix(+qnH9I9e-D6et zuH!ly1BwtuRFI(JB@p%G#XJr{K@bEl20W+-Q4jHT2n1x&i&pics(NO6?b+Krs=nX% zRek^OU&k9y-+1Q)TqVH)>aU$x1o=lh_ZSUdx-zs z#K?20QCnIcSkZy3#aASOC?nWk;osfB;c<=y4rDm+!4r?qfi=up;K%}q=rZbl>>9Oc z)9jT7V|&P7(W85T7JD&Z&;uetK=E@3U>`KP4AC^*B(S9g?m)t@$fr&_$f-TZUH&)k zkTw^H{JmdMWZJa(vOys^fzKfJX+7zbL`aPw@DTJx8$?Q4Bq49{CV+l7?)es^00OZI z$UMO+^uqp5oz*H*uqbr4CL?9aF3E#Je+V1+WU?HV0y^_F`jgEwEBYb+71-IIkagzYxu(i5d1 z=(N;vZd#97)ebVAIwV-jReW~n#F7{53n<>w=bKb~ahMc)QJy~0Qmq%qr7|x{tMKc& zH?l$}aFH|4TgZb9O{6oN-`M1gY0g?zyHd8TS=%7K4YGZK`FYm1yzX~Qv9iIGhpCUnj*NwIkRMZuT#jZ681W~)+G znvupWJ}c46s;Jq9EQ%fCv5LwkN%B(9+G#En``m7X zw?vPLBEG6%YItNVbYxh*muB2j#TEC(Z8T1L6VFt+swp(Y9#!D%m1JhbfK964Y63~M zW-O{ud?{4Ts}w@boK&Zy6H7(PP%)1_W>C*KB&e9L>{sfPmGsmpw-yi?r3wo*X=j)eGUU~VB`)?{U^DMutv zW{QZ{#L7U$wn9RULp~fp^f5EhL?fd#FQgz@ikj&8W`>;^R(3=5d3g9^_Iq!?_xHZ@ z`)0Okk%RN|Ms*6NL)Z4tgz0nx1_#4NnlhKDRUQqBqJd zj7B>FtxWxRQ$0%Kr2a9o_2*3uDWo3jdCbYYA*xo>DwMEes=?H_4kgCP5h;X>=aCBF zU8qT&4%0I|u?4ZE;>4`sXc|TOa=k0?<-1B@YLe0b51n1pp?X}48EGZ(OEJw8_1Dr^ z8l_DP%$>x**$mio94VNVTF&ZGgE57qm4F)-gLqhPBAB|_{wGRd4W>m+1Puo>&pRWs zp5!7=8k9zrTAX08<^tQSfkMEUNAqTkl?Ief;XN>%X1D}WAk~24(WIIs0u(zZ3R;5G zDNuL}#6reI6)Kd=WTMPizgm*gp-d-)X%Tx42eBdU48%Ya3^DDHLL{YT{y_$mT5Q4? zdvO$T{on+RGc4s}*sfnL>w-XHR#&%(D0!~`N;PR>yV#jM+ZhWgubXr#jDkol#2q$5 zxE`WYKs{VH#2G6@u321+cm}4#^-5JbjfptZCBR&gMhVs%#2jbbTaJr;`-yz32vVJ@ zwC`MO>o2lZlY~j9XN`r7ETrT?MJWnp_>a?=4y-r+x%I%L%H;vdG`T#`(mQS!cJiNF zDK-WLSc=k0LkW{WZf-cb^i3X^#dMM&7vgG^n9FTt9cqBHm>FUTJPS22ec~#Fa1BJ0 z;EzF<1(wu98r+x)GPON&vOw#cIC4B4sDc1gh^#kI1zD#BoOETYsNaa}37}$UK?+!? zCQKSI#1ONVAXKk`UPoWIkDWM4#!AUH^(g{~$Qb~;OgF!4?Q;zgFOz@d8iMo+Ko%+1 zIp83-QEW3!E?%N~RR7e$QUn696hXaUo8plnu*Wt{Y-kh((QGt=t6LC7VLL zJP2|LMk!SQbD+g6gYg7A4VldFET9+AA(L$nYByUQ3FsAeY`qX$uU(K3@!|)9^yz}c zY%@h~wizN`6jsC74M>XKtuwaXo`57~JJ=#h9Y&*!-g<;dFqa(vzL}|4Q|Sgql|aA_ zve-f3^{len7ln8m84UzZD@m=^h|xV~ujLJi5f~$aIl-t<2rt7J#x0GifbtT2+g?Ws zu`Du@6v&9u0KRmmTp7vBgAfDG0u2YwLQ!FMtM=piD7>hcE$hk(b2SOnzB9;5$P5|FWe%4 z2jQN^RLcyt)%~uEv=>Z!bjK5`dBId1_0eir*hDu?nNR^MH9Mt!_wF7?>hrbAgxf3| zffxBbixJ*K(J#akF2Q`m0in8&W(T0eEWxF4Kem$k-Fvl66{jcdOMfAVaFgy<^!Fr` z%}am~MYwc>{nCy6w--k663Fx3>LMVRccCzkGb6R7FxYe4s*!_xI3u}&VeSIVuJO*F zKn!lH2V3yueicG6R0m@Iv1GeYLl9pB>sJt%LV%vt036w<0RhwXbZW4#r}?7f!Z>#) zogcF@1dOh&%D|L8X=RTHUA_#3@Oh>iP5YQtC;*XcRG|Q^d-5v&QGxPxA;R=oiOt8X z2?3wLY}AAR>?}2@a6QU?fMOr`2todFa>UwDNM9IxD;X$w?$O8~wns)G8kydwFIH`Q z`U>Gq1h5keG#lx|d;;8xv8&XCFv1JST9ny{!*53dH!`2Etkf9)0IbQ*Z$6e2g2OHy z?MAin>G=sO!djpS-nw+Lb2oDsbQNkqdVxO={zc0wtA6o`ecmkaoa6o0o}e+W)F+=h z=ko3I2Zq)bC<)h@9(7B$wrKYe^F|GFidzw}vbAt?NAZ^Y$jL7@cG`2fWPVii)QbhX5+)40DIuaNuI(|GtTdB95VLac z&5NC7CVUPjVIi^vH&vZs`JVLQ%SJ4ASXur|-`9)@g?GZL@73R{yfkQHaLDA(uK%dG z?H`}Ha97E^lFE8XK-T@{+0yy$K}T}H^|!WdbbBakzem5X9RexddGYVi`zYKpyuF^{ObFm zK@;Wz3DC0>70#6`S9KW23Mr!!8kEG#ei^aGc|t9$f~XPWT+Zt)>!K94{3 z;n=fhmaEV0{^imKj+0`t-yZB%Fm5a&E6I-SKRw0s`RLU@Nh;Skd@=Y=AAISKX}=9! z@keyk3*Lt`b>5TGPCFI`Pp+j#Cyr3xJ@4%+b!}ZUulA7d&)Yq-mfl{kO}~)abmgS~ zPJa)_*<;>#*L6rv^vNTWwimWEOy8}nIPgl-hsMA2kL*6Xd8sKmbVX^syK`8x%Y!%4 zGCr!F6nTH%3~Z!V$26DEc55y-^jY(2;STSNjT54%fy2g1DjT)2pNFq)Nj~pTb1Lun zW=T^*_43eLiA`S9Zc@KSSGsu}N}e?9#>4qHY936KF1eKSar^1^2sftyuZb6~W;R@j zTvGgk|FPV|`l1YcP()SC09QCCY)p(_edFEhcg)SY8RvCVQmZ!{P@67a-s|w&*|opl z8d0;~7*U?o@6;XV@{Ov9+5>xaDeoyRruJD9S#(kQq5ypW2&_x-v<^3l-| zFTUGnSL1-dnlcA?IQ`7@m*XQ89S_R?Mnc|hqi3JL@z)w<5Li+GbmHNFL&ycjz z3;lD+*XlbW0=8b-=UM${Gh7d^$u4#o7v{8TdQpnx&gxA*3--^cY6<#koBQfN&3f|p zMbLtXq6gBdkalcfc}rb(=6&b+E$<&6yP-v2lspg`8TT+`+0o?jk~ho3R%v5prPqI3 zUF*;`Yrf<6Gmp+b^W}2`=)Obo?JtF<7X1=m9drHc$&jtrvTxitSWWdGomG4DVqwR@ z1uvefn0WW%Ul*J5{NCTLY-Dk;bl2>(>FFA) zt9LT4b4Vno5b4qgn3ull2^dOp^ky(~L3 zRqyv+)%(AHJzshGzK2HfBBM|GR*ls0-fboV`^@xYefU|8mx!|`J6f40(`cq1#u2j3v-8h=;)C{sOj(;Iyi$ux{SKp zv5l#GF4rFGj_nd}L67cIE%q$s*hL~i1tVjRTun;09*g5&4w;2C>RO zmWewams!uikLb{T6!^qOoT4UkcxS%jT3Bq`#4h_DZum9I^{{h>7q~724l|!mj%6lZ zDj>XuISM1dzXT;GFOnfu9Jt(=5;5XCh>M!&qT86`@V5N2MT4-I=t^V?qC%O$(;{U= zJkDAuS-%QzQsa>mj?dwe%L5`0#QU;S^BUDAF5z&>148#l+p;qY8dJ85=&AH+O66e2 za0`Bh4yl7c6wWL*~U}5NVO-90$UD6MVy&lZ|?PC_T6kOUQtXzQ}RqB&H8VOkP zUr6+d!d)obg{qVx3}p-HitW z(i5c+Y}M3pu3ArSk9LsJ)M2#QSMlkg6G>jI&!BiypKns}*5TZkR?>SI#>V1$7sEG6>Vumy4{{l~u7?YXg|GV)5`Faqufp-3`-i~$^0G)qJ= zyD8FJ`*5;(twM}Tgd9hj%l3i%zw0W%gakyLpWIdCw+c|juCI3*!t4?+tWL5@t=)5TW0@waq{hvyTjLh%ojg9f8^AGy~Drm zSh{)t@eS**)V}%U+FPx27e3hb?%DHiR*w!n@zbNr&un^l`=lA){FrOi;Y>$VBKR)w*UC-C^_4+*DA15Dw>7`3p zcoLBc{G0+YyM z%@S~msiD}E2C&I=+9zadBsOJ`=rrQoV{R530(d;z2cXPiN+9CNRDiMpjnPG5Hj0rl z7Q3JZuvtVuDv<`YWhRuT6m255#?z^DH1*;t0f9`qzNqu@`eIHniFi7b00tlLITfIh zeTghK4ory&wO)c9<40I*fX#uLG)^nBFnQrQ5v3qPOq`_w3>Jyb#(~V5FxZEraVQXK zLiTv+A_+uafJ0#;&7kMw5rv-65}pVE8^HOJDNv_GfoH6Ni2=`ioHt~QV*qTD*aI{X z5EH=^=y;IuJLz~KjSz0mc8DM{n}o1&K?os(qQVuTQ7DPbgnoVLOez315uPc9k`(Z;wyG}>gMccM)?&VSKDnqg2tONq1s0SX5uw`e#f^$l&p zg*r*0`;qYg#Z|P^sQ?4Xg=R=dU`K!f^+{AAf=ocL>EIuPt`Jx{4M8BY0>DgX9yyfB z)SN`*_&iXVf>2>l^MT5AYFeO^$!wMAHwN`Dpc2l4F33VWg+l;`7y+#%1VAGob^*I) z$4)Xy$OSqKHgN6W{G~MD|J3H4flFQ^DxrV{a3qT=KLgyd{MH^*irlBR5C=H-} zcCfG}AXr$_d9V%p(GbkWHmvD9!J62sAg>TY)WoY*G{Liy4J)}kDD)sA6XyeB4l22c z!T1+D4YL{InejZJ!)%^;P^a1I7~^?iC#)ACtanzBFp`U(!(e<~kf3dbozFJING^&g z!#E8{*!it9VZCz!Ns{g07Qs=8Yyi?*A2A6jN>2Q~v8Lgf!3;>1AV53BgdGBWJ^Res z7p&wqVlgOWHjeJ=%ObMp&fbYPB#}adM6gIOVzKbc&;@eK27Ex}1^b?P9bqM95sS_Q zGhz}zzD%cFA;~KS!38`EYB=yL!fyMlRU3->2*0SHExSr6%<**auCNr$jsmcWsv@PR z7f58XKy3emMvB#+9*aT5gSGw#rAo#VL0#rIuIbyf#S#TnwR5=Vfj7Sikv<{l;R^`p z0lwuy)j~r(`F6;fB<*3-Io%K?)jVt}8TI38gs@56Q&DgQLa8|}?WcG5DNDIx$ruJvqDdhl9ZP@M%T= zTtdxw34o)(S9Wl}Oe6ni3j=$RygpC52*Bo(C@jVqlG?gRaL?UgRA=3ci2PokKJvhiePL8BDgwuz|eu@l)JS+mS+ML7%Uts4?*a za7~VX^O-mSJDk+fX;hmyJwtd!OciLbw@FbjeiQ*3Tj^qzDNQl@PUZN6&k>FF7D{xI7m#BT+sJ!oHW$=gD$+`*j#DS2D( z2bSjtQ4i#dRz&*H?q+OGvM6q@xj>32RoUYrwf0Y`y_QSMlbvFEJ z&xY8RO?NV#lD7f9eJg{%-r$r`%C50r9Pd?|xPlqvZyI`Z4fCs##n)>iO;G{#_oLUR|bk*`B{N z*mnLg%6}OU^{Vx3fuFebGM#FNJU)LPBX;JYr-hq29bU{$^;_8+0&M*>|zYi8hmm>O- zvL0+NO*QgAuJhIK!hO}g2UA0z%L4fHA%{23Ltj30eRt4w^hu5N@q63xNVSyRt#WO( z)>V(bO$@zd9~_G3aQZ{er)z8}@M+ao%J<)U?m0P&RFbsGGl{SvOF#M~C$g<8q|X+p zPR`Z4mpT@3cR*=(t;VU26m!NALfBw`*tYx6JOt{u$D-U+ju$^Wsy%E>Z&a_`mXjf6 z7%u%w`08+(?(lB4?(6Y?cHE7Lu9vsMV7kT}b5eYi7qvFH9~g?gyra`Sph`_|G>Y=1pli)J{osO& zbw%r+%I(?i@P5fH?3cKj+_a%TmIf)G-8R&;o6qY<-X4=5rEcZEy56Eatd?oZ=Y z#f*A!+m9b}iy1vzOJ73JkR-n4hoz}eGQXZQIup9K=+C~sT#uf?LsnEr?_Tti-Xv3J z!Er&i$Eh$ow45i;^g({9+2ib-|HUXA;{iHpx%~HS?KQV|dRG3l=dC;8MN-|D(fIlb zUe=FA4p~Pw4aWpLjiJ_$eJjY#JFD}iq~>W4YD|8Nn_MA?m0#6Z^zxZc2Ddu;aKAxH ziomXe)Fp4-kjBBi=t&6b%!_=z&|#yLqPLVTLas?_D`NjxX_V#Q7wWnr*d*n9QMHE_ zV=q$t>t6WT_u9U{mDex(#+Yq=Aw_ONEMLPf!15&;>4;OM3_4b36DZUd(z*A)iK7MEvaX&Le37V3_eUBVQn zRj+)da!l63z~yen{Y7m0scmbw^_?90>-9b*CUe;h7gx-?;xA0QTeC>n@kiF(igol- z3v3GMa(b86m-|{>PH?SQJ;|&rGug1uz&-Ci-m^&mLS1ZLUPV5i-YHlg)O){V1Fx;| zK+c#Bq zE9i;uR{zK?(xC+}Y$<+f7pEy>VUw}q)w9dHd|w4W_Ruu$7_G=ZuM5_oq4MzX2WOTuL2ct&S z9y#wI`DG(Dmi5)~JIMtrSq_=5Hm*coN(5sPD$W15J^-#}LcxlGr z4p!E^exh?}(b-JT%K>+awzB0L%)6Hsj#h+~SE@8f8wdU`^`Z>D;a=tzT1r9UiN@OV z9rvq-%QXUU1;YzFuMHgy9;vF{vZ*O}^5oX?UOps+hwj{^Z#rsXuH|KV$?nd2=N1*K zr{(rTW47;AxL2uRzkD#z*%3>1&5GWLEi?I+xBgC2&JUZ%+H~#0%G}$3jtPxZJ^D+= z@HwZzBNr1ZS1R7MB#g1B6mrRbIU##{ctb#QmD+&1(HK56NO^Zq!rP`+enXq4tap82 z%?G|&gWV#_uHp2afn04_@7vkjqrCp;$E(8SP*O_EWf)TG%gbfXSuvzh-ImfD=ofRN z>Usa?Wu!6)yv3~v9>(=sF@}H8P)?IdAK7SgP1&j-FEQp_VO$=ke|QK&c)*-m{_!iA`4|fdYaZZ;^hte3Qm`(KjJ+d=DI%I{=m(VuR=AK}6?K#~n+l z>Z;lq>y7OqZ&8l!`cmw}K7lS^34*|R0|CbawKhRPnr;$UlL9v(CJxKT2%5+=D48ej zc!Xy?PyYl5W#R2jA5Jp zO*^DP1;T&tRutCNGF{dyB-8f@biBYwIwcWO&G%gdJ=O-1k`CjLws;*tuO0Wi4FdoH z+XQ%?patEqyHhJtMFJLu&eo))Ox`7VQ0Vnx=C_YlmQry2C4`L{?NOmV-lMUAIsfHU zuOQk5qFtcwV*qK{O#19*)&a{?C2Te~0FHJI4%RvgoiT+rUxR8a5pB7{3!4uc4M?TO zNV&H5m6BH0Cp3LZuk4`r)qrHYg>XP!WJufL zn5c&EV&bSP+qwR!5r!tSl-O&d7Em+#PkgV}a}!6!==IRSh`J*Uh0+0}4B)V=Sz?OT zOX1#{!zt#q0x`i6d>lzGn*;fO*A;+C4v0HHy{p)71)vVyKpa=5j3T}(fTb>AAM44Z z%60Wxtw87;!8Nu)3KyL0=>oKugHmyAkdrE{KW2(!coVP*opVfFEG~Uf&}CiYlK&vt zDwmaHq;ZQ^I9ixo{Pq&`jF3{d+la&RgVoA?u#vY4YPKPZV*7ZkpwdZ_ywtOHnhSM2 zYB$VVqQ^uLUllMhJhB!#Ff89oGj1v3viqVo8YjJuXNp|K6zXG-B5?LfGBcvbCPi>D zfw)>T7F8%d7pmk{0wHEjs?)&DMP(zdt);JI5Z= zDnERG=ewa#h93R;(tTIe`=<|`+4IWvKOTN+1fKg$8yd4B; z>HRZrzJ21acmG|xaQ2=D9=vq3y!+yb8^2AjzH{;8+1kz3TdQwWj6< zf4cI)*&kmWK5u`%_5Cf?u`iePEl*GGIz4mOYcK2>|MbE)TZccq_R+b6=TH541GWhK6_5-UtG+G>E_EPV3-m1ELkA zwRf;{G;*M2q5Xb`)zp|)kb{L5Yzne4wx<=;ai=}M!pTBwWng*!ytSP%$j%((iX34F zvUN1I10ipN4J_sI8-cm4m6n@60p?8JSudSTJ&Ou~2i}{UL+*hYXO51IWr4 zWNc$+Y6SX?1}Fsa{SahqYHwuih}09c&r!(W{~^P{z~$c$7x*#U4&>lyX9cz}F!_xX zWGtEfvho*l>tWNP#8M*j@IbFEFSek2&Gbosm2eg@&W084A*-%}1s?a>f2wMN0!KQ!sKJfk?S_=c(}vO8oK2k463iUe2@Y zkLSKa%lQ)h`K*Ys0{PExAfm@YYYYM#I9fQ+3Z7s8RTAef zl90=500f&_oO|l~#3*gB=y@jRN5Dp(sKfltob$iTtW z+KO6D`4P3VsRM*sT7~+X%g$&0=810>a&mC|qS-gOU~4-|gL6Op#N1ycOC#r|{{9U0 zxjKT>=gYxDZEa_C{ueb8IBgw4fAu3L$FKIEL#yezazExkZ9gZ+Up%1a`B&w+eo_AW z`UC&OoRgzxl=9)C~kQwE`Nt zI)KiB;h!c@wRSMD_$H0|{PA<~e?I=JY``zF4K1vV%z?ib{_9wtU&b0)TR2);eH;8! zqd$Z4hf@vg>=deU=U}a3LV4(1O-~FoO`6%GGMg2c?AQ$Jk1OJlUKd|=q zvBvc)nEZcZjf?BQa^N>leG`t1!S8Gab)<3r3Zhm9R{!VD;^s#>i(BBoh&Jy3kp#bs zHg19c(pl%U^*mpZaYg;jYd@g%dj|hD*tmZM4-0E&kR8wv>2oBb|1&ZE7nsJydmd+e z|Ap(gcu^wiQN?RVvV>^D+YMs}_?-zXDu9QB{$sFB;V;cwT4 z`&Trwx3MsF09u2=_8^DcJ@fdht5g+gZ;7E+klLawxV-TAS+HJewh5h>6dT+peRUI`ybT%KP$H% zg#Kswf|T&H*#EVB{-t313$_0_^gn78{L1Ek4*hl3|7p!H?CM*;g5#$J->Bvv)c&8v z{Tr%(4q)GB_-XC88Gexb|7;HF|B&?mobP8%`(we80rk(b{l-Cho*Z++Tzvt6GjQXXO^iS0JUs}e6OkGr#A+zj%YuW!G zx8um5(+E0kxTc#x8ncHK!4c9^)vhaMmGPYOj9PV)cFoL{-&PxXJM{oiQdPs#sL%5b4h8SY9c;eB!6T zzpUs#u(luS0e*F!i330#wr z?2Wr;^S(hsFIw^|^vOva8=}1gcDI*83A@Jq+ucs|B`S3#;gh4o`D<~Bi5flGf_wCF z#v4XGRW%He@$Squ6~&X>A}@LlZ)=T@iI6MpwMk&p_+69u{OsEKFBh~Z?_q0^6o-ko z1cqI1K{sN#7X>=MAN97fp^)eK^_7V?^Di2!3p!d`cn>u?rVmf3PM~?ZvE!aI-GhDg z=C8&gdoG^BhZ|12N)894TLyZBnaLLv4aH7-4hPQmUs4PwAsSANy8vC8(A#21B_pBp zCWKy6N^?m|if1bR2dl$LZeJM|!h2w62P;%(aXG0uGWT}Qda1r(VJzbl-??`6jKlc& zI4Lno60HHKV&o-2gwJf(%W+k}Oqyu4a9>{JjWiLI|BG0L7t2}2^zk}$|J1pDc-ZAV zUtPgm8ao&S@-#|!20hxs^S-%mUFi05&CQlKJnjf`%RDZ}Dr^;Zu>!ogvw?S6!bxF; zsr6y2m-?!!{~ucF~!eLK^SggM?%+sTU+u1xnv|hmJS$ z^w#FR3wYG%3^r(dVNbDK(7fiZfH7-)bPRM1fQ^OM)cMt)GsiRQGEXqSAx>%7h)#!VJCmdb;zo4$>OtHOBlRnR&y56x?8v5mE<*la?q2+b!M``t$4Dca<&ZLf7DXKEP5(- z*y{^!QEf<-lGoxp$)EsSH_%edvK8=;k%~+vc$}JaRl7C+5zwyKi3alQLGHy2r`_9_ z`IZB^^uuKWp=H65d@{S)!NfN`+F3AJE5dz-czQns9&i(t!orD4SKkwr_Iq8?UM~n? z5RP_Qo2%10c(x@jNq1Er4a?#ko(uH_tZUxaE1!}-e1}o{&aLXD8D9IgeQf=r1gGC zOBfpaR#7^y)VbfeKF5T{FriTC!}1ep))lI@Cve9>YkkXuLgf!*Vl3?+JE!ZIu;B-m zxfHWhJSB#$Fnmg9g{idX@F$tiapxjUmFKnZuU2VK#Z?rziW)-v&{#e`#T`MzzxeS2 ztqnS<4aQ}cr=*&pBVt_>p*N1ShKu8cR+I^J`gT_67Sy8LS#h#xcnFjnM-`zN3Mwif z)*DybbF4mlsqx{`vMH{?qob;OWXGa=jAM6c{{G4O{L(Unfy(j+NVrb-q8r79#B9@e zDt!oj+8bBf%$f=ZVps^nf}f_ey8(<_Qf(V3FH4Kwe48QS>@it~wYdI(WpPfyJJx-V zLdwEwEd!zZK6gb+XJ_){bzQcB^Cf7dk><6W@z8v`u8GOcKuh021Kzr_giB|2}{zK9DU@@HEQTL z5Vbu0oJ6G+5X*s16D-&jlhZD)_oAaOj3g36iTg$`-Q_!kK%&xtjj3%%2X(f=;$xjM z^Z}caaGxk=5BhNO><$x4(n1wh@|?_9qwnyL4xRRLh)kPtB|9cfjdDzjx;6En%wm~1 z2ZTwQ73G+es;z#!zHpG7LU5_*DF&}TmhiQw7|-8b)_sS2`y<*DeGy1BKKGDd7pp1O z8u^Bm45sD!%*8c=kS@c^&DgC=6)lqVjxaZBmIoMpSrma6eNrIzUfd@>WAKeCaqk$` zbMT~roUpGaYc0SAh)RQw5_RAS)|q$T%~jL4PrU`ZUrIta%U`QLG`$jLA=DKpKc1Bu zyr=#+b5vctq-&TNkX;j=ae*!qa)o1b0zd@-G+iLEd3R~`DHgiT1pd3mirM z5=ohwyfdua>o3R%*{MQ?&IiT2em-MZIFmIV~4#JdIWnl!JFDPsnc8C<~O*T-;rhk?fb6g&PM=EOa8%8!^s z_T^t6yew-jDK52lVtw+uV>Dqpj9U?IN0n--1l7HK(?ARHrTH$&Zl`qD%l6ZI57pAX zJRIa8!R=GfnEX0Fc{DH4>%X$w`^J8jnW!|^6L4Rsw*F}u+#)TxY`viEW6#jzUbc;l zWffAkM>9jQ0pOEueI->EFYz1- zG7&r6Ey+%vJj!d}H_)4O`KA1@JZ zUSKbxEEQ8**8of-x7PYn-zdkd51+2B?LAW@2kaGI?V}uhsS+IX!scv(S_4)RRk7G&dm-zC>Y;eOhg!4w^xjpP{u{8&xU6_R4{v4O;#bN}$&nqS zT`a*p)nEEdlV_=!ok$*cdfy@X@>)Tzw-62$06a`5qMA9Yu5wQkgI+}}InK&Fz%5JqXrnYN)V?wWZ*r)SH} zM}CF`B8kaVRH=a^p=QMe^b)E|KGOU|A{AFF9Er$k?ZwUUvspbFBS>o~Pcw zd8|$-dpcfQ3}w1UZ8~lpld6cje=0fMjHt`$_;g_npJ`x~&lGP{&i{0fPDYE7;<8j4 zSXccsRePh2+^e)|s}6sH0o?a;y$v@{jXV5JtoOAQ;4B`J8`Ztk995nV?t(C#Mcd}O zr!wrK1`yPviwf2qAA|7p2>izQv^50mrrvmM(USB^PqPo{;#!lKuh(W9%9D*exGQwv zg}bpf!OeGud+VQfdY?PnanLh0^OJ?O?IZZ|Ep?UMSx|jJN-MEQjyXY&!K)YwfCSCx za|WWrBUlcEfRz%DMSD0#RA{NQen;PfcelmLyWO=S&Q5*Ojy*h%G2M)J-z&{nEKYH@ zBu7XUp2KIP@>o`g8Be#-!JYvbp?rP&c`nwm^m?psthVNY1}&e|#*ihU+%HWuoF@yx zA;6@P5F`va?wx!Y?J_C%+GU>1IOthWq*Cf71h*AtYs=v^n}(5GrEe-u`D~zd`9rYg z!xw(nPo-*l4uSPE<_(UPDakxksmWz!lqt#ZdM~>0xI@OIGF;Wi;^em`V;cEZ(|puD zUS84Gv&3P3A}S}|$&}$UbVxy5n#aThx#OzZTdb%eo|{`K1VokPnoVB8RCYrxCPMvq zV^r3s>?ExwQ?c_kKsrj?2a-7?Q3x9!X)vc+I+rv-5Tk!8ed)b>!=$VUcf$ER7tNg5 z2aA19wY_iF>=R0Z$xXPczKpAOX0UnH-pTTl20Pz1#J#isxilk^c>Hl^2CoG|mxmj` zwK83-7A}Y+&BRZTv~GG`KKqpxAzjIBY4D?$U9WG%-r?kuH8hU;(8lrt)s3o%SEDb) zPIAgl$k^Z9dJE$bVzIh>`2MVNVi86FlO^M1rCvF$O!6VYZ&PZ>?l^U6qEedu^4QR} z_tNmhX2J%4&Ern85EWLTqP1(7mWHisas^+%Y7g;p*FPw(pk!(LxK*LtE90!fx30OmKArk#0C@jfJ2e~>DC=N=1nfT|iUQw~92sZf z$pkat?R(Bl7IPlwgGq`Z-Y1fhC5JivSL=5%$zb!PDgvEUmf+?&MkoGHtL<`+vKJ^t zALl$uP*Lr!_Ror+-(ZSkH$efOECwYl*t!uane}|O$y)1EtDFD&SLR#R}rjY zlZ{KgFYYwC!oC0X5)%k>O|?VNs{UP|UL92cQVv`PqUlur!XH$o!)P2CogJ|g3FQZN zpukQ|i8k#9V^-rO_BhX06@#}u$E<7EZV_1VgZEW0lN$)Yu$7at;{fw7b6^yy2Tut& z23-I}pX~?Q)57cXn$mC(s}7#Z`W@z+eJqc>V%ifZIzX1u_$oXrlPXb-_z9_IPKP`> zU=`Br9*V^DOoC^&@x0r+ z1?9xeVYQL%W!nYFBBLa0JidK2rqS6d!>Ytu%rP!<#3^bx)M^VIegUS~clG0h8VoJ| zgIP;6U)DGURHRHe2vs?h?76~TZ)xeudtz3;l7-05pfjn0T7Jd4-U#_nsQjsmkqJg? zE`6JpJjc)vos*kPm!h;bu4n7IwJy7dFmY+$F`wqb*TxBGXSuZk)5M4NsL1l&nKEE8 zK0xo)LA?=!=FG7!@5+rzPm6T^J@GxG?(8xZpe+LGu)@6lwl=%);b_gY$Ak*Ub_Z!~ zew>)*){RrTWZZu&h>HByf}c5=zb`2-wI5PDdedxa(tid6cYA?PZ=cR7dd&rkzevo7Mby>LURLA` zNhXT#P_PtAL+RK_e1!Fc6K}Xv?-N(2y5_`z$?A!{K6=##!_Q~}M1#n>{o3WWYMF3_ zJ0$(bIX3}DucWr%kN#4)+ynuGiei@tINxi9UU?4H{VaRnR2>D)Eh z(Hy+dA@-22RD;n3;m&AMg}gQik0>#f!(d`JRGPI~UJ*b+)n#KY`KfCjU40zq)O|Ww z*Zrw~G*hhnz%|zT($d~RSqxi%Zw@V%yvThOG*-5NK|eFUv>BU;28h^W$K6}NR(DJs z74JkZCJ5G*9+h6VfUoNIWer_mOP)0Vaix&vgDrTrxzA&mq>6Vxw|w4++w%-`)(TT5 zo1FwEvJ}tzD&8*=0K_*$+@Z??+vebUg zh$e~4U`%!}`zBl(12C|yqK1=`a-W|Ppa$x@u(s`2cxEvrA)X7LWalv9tAB3)IXaWO zDz*+VPeIls_i-M6^LVZ+jQV9nublomHvFO;yRvxa%5-X#*?0wH{dh|UkX?{;MSJKD z>@rb-Ec_x0j4VnlDQh^3DT}>#IPSe&fF4BE3zaqDqndfZ&~P)j8J!GMUiNlYZLBR> z$}BKX3mmy+FUHqPi!6R%y`pW;y%TSunC_;43M4lj?Jd&F zvhV4QiLd18nb>w5EM=nak@k9lTM`GU5SuL5u>I)#>EmE8%t)?u_C0(YdD5rPm?cG* zQdABRdh9x233Ob6J$q7e*srk6!|*n!oXLICtl(7g%IK)1{Ogl5?l|D?A{l0Wjh~Mb*TFH$+Dx;_wc2WGjBQM0Nu#8p5<1_w zZH+yq(HVUtV<~KS>7~AIxNi!-8lYi0ny0}ZgE7TVd;ndD^7?uU#X**+ zjlY9Ps#y*NFR_dck)8?TLbQ>A$h~yj8eWnFoUEI*>$wH%+Sp^;u2-}T^tcrS_39$q z9jC|d>n%POaZJ!!;oh&;BPwlKr^}^C7|&j@Xu8z85Y1;=5WC1{y53SR4^u@v5RZ$0 zpKtPDItiZ)1q==O?yfDeJo)t6ODflzvB*v5arJZ};Y~%cgMq7o|@$#`2RHZeY>w9g&m3UAxf=Ds?IMIq{$y86=?t$#OW1fZ3~9sl`_PFIBjaK>p%E zT6NWD!stZY@cOCfBp~+KyDx9vZIc?c#7^(isFjve3`LbyDHRV7^}j4}K2j6EVIhDl zBdZHv5X%;RihGBJtU5Q@;QTnl5oXF8YA{>8TdjQFfrC-FC{i8Sf(!g2G_)>!LqQ1A z$}CGcG`#%)${HDXeNpCd2z|s;3OQt%qhZ=cq5)_S182SX{3$(n{VjQYi01~oL&6R* z;AR%{JA=%6`+-yTNYufcO2swA#nxR5-SSvxHr<0gLgkR^+pGpa`md_Rp}^@ zm@9t)UtGIImX=WWRo5(&jt5nH&{E?)Ddo^S+4N!KwAQMi-dRy?=wNfC%e&{W%CA>d zx}VaMU)Fnt5okvLiH4PslQcb#-gRKO>ZFl)oK?qZrU`=r{{~QY-h$AaoG#z}&JpA& z>gs7fRzz{CqiT0|!$3!%NTzM{(fA!$Zc*WV`-_&oRUP^@Jhi6#!llHL_^98th`9&^C#!0nmuNr&i`#)Q7#YvlBzJH|N zHTCpaaIGC2VV5aq51i6XtZoxIb0tgAOB{z3ad;Op>)ye=Ut_J@+ftz1pkk-Jml`DH@8pvK-3SS0%w2nTAc4&ZMFQKVL z8@;#yM}O%@{~+@5{g(RVO0m3WgZrsI>h^2Zg&!q4n^=;|^fTYRoD_N}zzlVy4QHOH zPAm(a%#L2g@LaK-+zq29dLmfd!5KOIF3{c)AZqSuAHD!{1BEY8))qes$BRe42U00| z21di$op{KatoI&$jECi4{wPalNp?Kx)X2*p%z$o<3Rzo~>_5^!pqf?QM@*V9?Zu5jjh*cVH5Mkz56JPf>UXr8da}tU!+o_Yf z?p<{khV2lkEG`tZQqEj)%b>YM{K}a3r+gUm!Gt<|QnySk+zcx#8Mom+X8zG!%V3(q zAj&Rgw+3{041FwpJJkAAuMFYqXx~|z+)Uf}oFv55PA*ib^=9DyJOo)o5^`RZ4Emf> z^fqhOf}(0EuKG~FY4pn&rFT`Fa0n>40@4(Ff=68XDmMc8@>_>{-)Yq`UaEWjiFpRW zn0&{mzR>)tGTnS$ZJgcEF3CJgAsHX0o}xT|9}3M9OJEX7Wg+$)Yw`K}xiUclvNg$* z)3qmaX@}cyTF@^yc<*5}1=3)>ujB?0Db3$wDY&TC5kCXmb{Uxwx?aC(3BnQ2&9)+p zu|F8`9eT1aPS58Rkb#adeiyQ)!E3TPxqsxXu;0BYn9=8&)!HNvZUV*o-nDbFrODlAMaY(VVlfbR_u+c zx{2`IIy*2jzuvvQi1v26LQwQ*xnurx@~)ZmEPSfo^gLs#V1%^6L?ACVjZ>yXaMzMUv}px@~sg@Qcl zUzR18=QYn<$>|r{Q8n3#3NCdP5&pba%23nlkv1kfHZTxoxhKIl3pcDNK00wo6sV+&b@-(*SapCPJFagT|lPV z^rcf3nR(e!xoJU8pe3y4^qD10)%RrF+1%;N)O$X>^cDyTXwhssU0Y)_KdKm*_FJfX zJ3CXaae`gboke*0#w&;>-hEmnfg9L;#?%I8#naf&Ac@y_RY85N+si6^p4Xk7h52_Q z2JR8B5hdoCkGh63jj4*7uXy@0X%E~8?sLP*PUVSImg>x&?g_jz{8qCjmPuQUM6J|c zCI`~aJyP8Fh&bOy(##U$pL;&&E8;pOM?HDLef`tLN;)9z+ToLd!W2)dU2F2U`YeX` zjWIW{vFK*Wa?Vd{+6+_7>$8Fx?o#9r1=DZ^NhGjnTaL(xNAxXAgVt;u_K28@EY*Td1A8+F3b zvgi6i-2=6*E75fVi=h?WNe@&0IguL)*|KoG+Q&4!x2a|*%<|rjIJ&t z$ttbl6Gn@*qaQ|gh1pIOfpO|biOqB>HJw-;)aqF!)WhC&F{h*RVusl#R}Xi$L@;`O zv?=}L8h7l4VF`LCBzD_Yw6;=u*`|s`oglXOL5d2zl28{QxN-(* za<<)Uoc%m$t#Mhd(G+E+h-s+w3Eh&uNiwODOF9Y_Xm&_!;H-3~_R^Ctr%XQ$1!oT}g!CiVc_7wrte- zDRBL014$w!X}H<25JfWf^6;?8-c~-9178}?K8;N8xd`h|t7TaD&*fE;MqO`BW8~1= zY1qFvBG=OS((L)V?z0o<()2d1T1iSsxmw5)5Kv-h#jIA+m_Q861*<$;5Ur_@2+v?w z=3$0L=8UGl^35i7e(hq*@Z#V8sEV*>oGKLCEwUQX+%wAcTquU7N(7q&;s#AQz&Bh< z1p|XaE~{mH(HiL`V!ft8;)!>oCuvhJvF@wBMY&P*JBK8*)=sorDgeYWw#Hyo|A@tA z6|xP4z&Eu}4^Hkjqs}4l zg(dbx#ExaDc9(zjMkg8#baj?|a{g%|cBgpoVP{tA-Qwk89+l8bhB?KbCGQ>ikJsl) z;)e|K;3F;r{F{ok)Ll=_*87RcVjSxxZjZ)YG2_~Y$uNPOlQNZ^b^(#awO}StJ_8ev z#z2Y;T<=&rogTMNK0VJqOlEX{%yTOp#mf8Ksa@K(rmApaRy)KfnKZe=N_p?#q(Qek zdVtP9;@$#6-kzmA zQpi`db>nx98E~>1EclgPH4aZTJ5k49jj_xSADyFEzgj1ecDBR~IAnKAl0FM>9x2lE z&vX)fxRxgM&!@=rFXLMsYG~&gEj5tY2wLNP$mUxoqvV)|$UNFI~7rBE5Aya91CAB0+fc#-&4`u&(m+`q1KQ3&4*5S*3c9^{L<`!T?A!U zDY{Vptv-NKX*=uq%V~)|fFLsXc~0kc#mk4nF^|hWrWTaUjrmy@R5{Q<5*m_|L6c2H z`o)I|^v(xq5<^jO4I0HJ(WEL;HR8l;w|V&!*FW5G-}uP$!CeQSaq@u#o={hRKfF3b zNvcLQB%!nYY78 zeEE^l7e4~Ui$15#iqCJ=%*S73-Ri#<9Gh}C_+a*&r_7uy24?n2}V zp;IfpOp(*spxqiMa3hVFf+aIqE!up2z;YfoSoeZSdxfNywl;-r$L|J;jzrXE@c@)4 zPyQm=z**jbM0Ru40;x&VN!>aC3)>t`l6O$^A>J}zbWrltkAmaGOStBG2+P#0OSxIo zu+Y(jL~qX4?v*ahUCs<6@88=K3;%4XgeemJkOdF`SYmw%pevODmh_eQRhSgqdF& z%QUuRsoGbKZ{7DMRz$TAZvxqsg4izmUPlpjL%vZ$r#$9K=>7Px1M70_c-vEPmntmz z;U-Rz8!uA*X5v)h=`p0K86R|o-Q3RY)9h{dd`$-W{uXehEDcA<=MwuWVa!+*-&>_w zi$1f0ufezg>2c`2;$g2MA>STYb8rYB^4-=;s^s>2Uje~=#X&hd!;@8Ou$qEe?QrRe zx@|v`g;lnx@u9DF%PJR8_z>A~`Vzt}ns;P6>-d&Ug>JYD4fN8 zcGClj?2m2Xs`ht$vaO4OB^-Ad!b(uFwN4y(*+xUSY2o&=ILbND1`_PB*NZ zH%wJM?!D9lQ0}R^PAUkFo@|@bE+{mztg{}H-R&%#18tTOOZtQfdl^R>Wh`A|PIOO1 zHg&GY0fzb694rLt9);6i8cW(7tZ(q$TS$1Q_xU+0j_40zKgz@H>jz2?Fmtm`k=~YL z-P(wA6Snxg6>V{3+8w(YXGgRne>nidOD$OXy|>ijG8-^vyUZ!!z`7gZh%I-dO;n0J zvK6blqEBgFGa`?fe`(L0C?0vNca2W9*Nr~Wq`9Vo(d5KF9cHe7b)!~UE&R|fE17t5 zvfW*gH>#Ty)#<~iB9kK~lQo{1Purw?yBazk;vWY{OMBk8GDroHOKZO9$Ov(Ma-Ber zSAq#qUKzNUKL>AYjfPSpgH;LpC*5(vb&YouQVOrVnZ5`HtMatSU@dsn@<_U(&)=gP9k+k+CPG z$m@S28?Je8C2a|8SI%0djB@|5cr!0x9_RB!21>H&d^SJx1iFho%+vJcA^(lg5A#p0 zTIP}odFCJyK2e)Tk@qBU?{qagQ(m2j-wsw48IjcyK)z8iYaQMWU&%@?%S%Z{Oug}O zDuB7cCkU7z>3rG4wcF~iiGwy1H%ACym*OYo2c9lOXAQGFD0D6MLwes)%53i@3TTPi zr09UUHH`tNPaK0s+K=iM_TRfcTqU>!=fKDyRFV^jcs>B2zR_b|lmS9-`YbATpV&zR@$%Wx0y~F4JCRl>X?@=E;h~7Os!C(X)<`s_fQUT4sgunZW z4>SAVD3Zruj0j!vA(WBhJ*ElNrCw0HC`of@8AQ@czcKMuErCoATf6a;9`Wt~rB!~%NB5P&%MxppWU0h;10MosuaA_Th*vGZZrvT8z$!)_#w~ck zG&Xah+9$28Je`Pg*9~V3SJYe)901zMtejWX*+XQm{6v6vP+@R$Dz(;q3y1ul55DlZ zKN_~=2I6TUHH7+xpBzBTH2Z{kOevjY)Jh+e2{2y6Kf)EE%>}ctV9CpeJMzp$`+A8` zhGO|PKGatd$ek3PsR7vSH9Cfj9$m~ROT`_w+=%U%liAaM>g1>Fw8x{n{6yhy?nZJ% zLYKvDtS<2AZjE0jkG8_&4(UW$k(VgI&U+_f#{;obRDi2@v&fC; zWbYYBboM3W%LTTi*TG>vi;q>MhA;+$$i%scuKF1chO$+EP5%7Juh2xcQKG_i=QEqP zfGeK6um2eGOje#Fxu#BEdEM)i)k*kGj{%*ClJY%96KCgv{k*CqyQl9S! z)EyYW#m_EE<)!%~P*-T$G8NU4aFYe9(foOqlM;op@D<9Tl@uXz%QU=g!~} zl$Ka+w#iMSxu>b0(*y{79z)x>xc9v7eiYn{;3m+|!9Dl~zqNh-HiOQfb{Sq27shk!1Op zS$kIoK%}*8x#iL4XMMP{M`AbR@yXSD=`hRJ&gml}E_On$6M2}ZGVGFxr%RdJF;BHV7^QbXRIl`8`1Ll z(=;6Kg%oDD4h)3v=q7cJfrkK(c9(g^x%XuYb+7xS4v(t~n6+j{=&_t3dq~{@#4zT) z$ty4rSk0^-(RcnS(DZxtv5vu+3n*SaMr~TW^K0h*yab$heWSBNvqSX69g!5SOoD%h zHgHK}E(?4E(DRbH7%YverSURm`f=&~+og;qdhC;5d2n|`E06cLoxE>WKV==44Bq3a zlC@qeDZk`_&3Jex1c5sfSxstt9l;!+`Bwk>{OamBD59jE3$C~MB$hsMP`UdGuW~nR zdV(_vC0g4Yyxrn7-$;$lJhON=ZGGGoitv0=-34iS4sl7O^;^9Ai2!Ee{~kd8{23t{ zH(|&{!yvn%3Er8wvE%7@ae*2|eHq!J2HVOxUpcGttmkF$C+~B#7;xbWPg+1JBX31i zx6qlp>!kG-M&BbVBvV#6_k(d$$Pm=0x@(yQW)fogsE+I@N`?ThY^56dx|&pKRTNV) z?weYcTWUCy@&U zxg!OS$`HsCY}s{1K`m$QV~ePcWq9v|oha?i4(-OL1G^H1fDqfBSZ8pRwH0zd>H4sK zRcu`lM>^ud39Cw^k#|qgSA3bL6`1nG{`l51k8H)`!r0meh`iJL zW~8hrN+sDF2?;J;W8#^q+w50F>e@eLaBFj;GMAKBzS&n&@!$k~8o^PI#t4uZNFz`Q*;jjy9V>GmpY@8?Q9)dNEfB!&m$!0~ zhOXjrx%1|&Z~$+oXvM2)IO(p;ZX%5JrU&8l1vW*Mwj6gI%`X()SV^8G_wDso@Agf2 zM#}=SZzW@_DQqQbr?SqnY>sn!1noiG1`U}_N zAgQAo#XP?3R_QOmyPbeOigzI;_AaA58wvTa+)OAlJxqtklrXvy2ule|XowlZM!}nr zG52=y^xfD9%Lyj#G~@*$Klpv;fpT3X0pD!jb09CqWe?+iLI4^^6PG-W95>}lQ}55I zmcf!M)(1Iwj%_|K9vR}`r4fh3hT&=%8TU@>f(d9jD@>1`r zlZ>>$F07{XqKv+nkYTF@2a`qXjfF5v1m&R2hepDBu3*j~aO{#8)p0&zuNCC%HDK_x zc3zY%jf|xC{oChxElo_A!C*A)+hiYRKhNCfF!o1ZNG*&_%OCdC=s7c-!Fjh7sNgN2 z6*|mYGUYB7E|fI!VJt@xA-h|C;(F7gXMsi>B4&r=SCZUtwAyk3PMxeOg%BdM^^|yd z*kIq(sh`Q}7~a(QUVqy$DN0PXAxG>{4AEa$0)9L#JFENf>7xEY1JN~Q(|zmP5_ZDZ zg4@L#NE%;DKb1C>W~2zbFQJMMy;+*^cD(+@%*3-h!3&OqmQJf=?-<~z6A0bbhobTq ztdRRgJE;?R)3f=#4@c?x2-5>wHq89GXVvggjS3|xp=x=1=lIEtM|vAdQsfpR&F&9) zZ!a>VKKv6^avC(`6LmVm;_MXEym6r+{LIvs{$!c?d1y{z^CvXchm)WQY?apRXQ^Cx zrX67Z_{T;xdS@FG_8cy!{)J1s+Y%zocgN^B+Lc*)b`xq6+KId7-j>%|>1KWDY`W@Y z#V3!}U3X9AX-KwY-pC>El-)A(kgm#FGwkRSc{2=9=Phm0o=7$|J{iwLg=+uEYqfBx zYo1+P8ZM!-WGvaIILth=JFSW_kv`4{!Q@8tUbR3|p3lmW0zs+e#5jzl)5cO)ycg^; zJV>Wqill0a8=mMFXB0^ZnM9e5&e`UG=rE3MyUzxw-8}x_gggK;p^#Qm<;K!8l@W#@ zQHK;%hj#EdTWLL`PY{0IH!6yJ!n1~Erxp)YsboX)G4|+zPtty#Se!|I#bL-mCNS|t z*pGz}=T0d^HF7CaDIh!GYOPBu!Rb>H?AE5f{ZyGD4bdebo6Pw}X0#*cD_x^PdNC&T zBGK(UPxLy+)=7*KT~h$QVpTy4vGZ{LQPrW2!1pRd#qq)Euw2Keg;+-&S)Qfk8*0TZ z!;nNR4dtk6HGC|EfBGnFO{d>Gyw#nRGDRrdscq^6T-GeJy95+IsExm~8c;dEfNHAgx?tKP`^PTS82t{ES)u zGGY}dU@lv-6xtPX=2_N^89W`QIfUo}(0Mh8-EMv9hdg-ZkNidn2E=P7Mo?E&(D(w5tc1m3QG;!{U66zio z;>dLF8q3=cg4EMGSuzY7%nezeki@WAKAQ-VI)8L8b%Vq&y93FqPT1E#%>u%CD%E8& z_J}!E)AjxwCV8^@XEN%=L3;}bss#L-DEubtqPa+QIx%*U`Xm7TbQz0B-CF3$EUy)y$hhFwiaqCa#d1zFUJqh5=J^GBzoGARjsH7u1A7;LM3ud}I;Gojs8P@`~ zA4V?qpz`lN=*?f4)1VrnD_|ziQ5LT`I6I03-{&z3Bna-1mS`Sfxt5lUiD3sIe14xW zC7D2$MZ5pARqYb_=wRiwQhVno*J9%BS*T~n&$aPurIw~~YtX^ms$%6w1rcKLJ+srBs5RnZC-wS8#fB#QLED|Ho5R>K( zM2oQuHZ;*ec2${K4onF$^75JC+lgKAa=}N+lgJNuoLa&=*Arv8M|T9Noc6kIBPJ2N zLX@qXLtP3Vtc7P$ren$%@-W}6trk;F|EOI~3(RH6v!5xEXkj*H$t@(^9Sxity|Y4(VG9h}I_ zuj7c{e2EAnEEC={h8)WgYe-9^kg)YifYWJZ+vsPW##`>cAoKUzG2^QsnMd^ zDjI6l-h0)qSu;fK){fX}?_H{@s8zA|o(XCcrD}%Q#H!tjRf-CUC;$89^VI$MJg<^> zIdWd-`8$vKy*Dr8STyE5u$lMJCYx_OPGjT=7#QCf?C|?<7v* z*CJ*uv(~EzmwGfyIMtXu5)A8vHt;kM@)8^L9cBK&tAyCa^NZQrxiJjG?GuwfDf^iJ zBV73ZxIuu&TWE9>+2!A${v1K_BT?ISVoppTLC>rBwxbPHR7caRWYAF)3fDLbZ1crD72D$KUeH06KbBJy&pRu0(UqNH4o(lY5urnHH$wY3K;x>_l%+t1&? zG^S5_^Kg4BxWBQ9L7{&qnOf)7ua0dQ2ao?QI@APsAp&ygmHAEJ-B2If@-g4s0DqhE z>WQPl0F(QbQK%-2xe%G`b?Y!*`x4l~Y# zOW1B;QozCbaia(Y+}zmDgTXG`324SLa7)g%ZyV|;iBraXRi+_@{OOeR!$IVl!=0o(5T~D^3_M9N&c?(rK zJVU6;df%?+d+CGIuu%7&jw4!~PYzG48_Kri&x%i`x&pTlz@b8=_@mT%I8f>#@{fk$ ztj%C}t!fQ$kv1N)7U*v_)Oax#pv9~EVTsSaNwAAeq(;lRJT)PK+`&`pu7J(?bgOu! z?H^1JdBH)`dZEeGglzwOecgvNI;ZkfA4U_mfihG&z2?&DW(>;f%L$9$ai!+hNfhX* z{*ciBbQ=D@epM1JBr*|*eE5Ro+5NjhvXOT^B8UB%G zhLSg?I;{9FSl?-tfom*2RhB@1s~${>^Sf*Me2As0dZ+==c<`{;1QD7hEQw1jcj(G4 z$j>xN{irs0N3}4$v_I-Lf2n$r8QTxCeguT=;oZOZbyi-_m-L%Vu5k1^v3fJ$V%{1R z*&GOAOOdHhe;Q6f?}&04;vc%F8K^+hcigwIEk$XkPlCkZX#v}iWESI2kR8_^XFR#j zA^RQF{!OR^`ZQa{e?82D{J-A#aFV;s|8X;JStt_U0Y=dKhhy{Q9(4lX3T<9Uqmsw) zqM&qLV%JNr$kRQ}u2jckQ*>NdZb3f4TJD}+3FUvJ(h+k_&ntEGLRdp3%wr&^5ryZIV1yD>Vj)9Pr*;f-@4pE=S&thx@jMc?9|R)6 z@H>m$g!?ujz5*d4kd@9^G>n0XX3j;tYg<9H(M18yrC@ zPJpvAiGR~0&%XxySYS2a{;kH5n!kPHqom(yh)z|rKZF-eV#ZzgRZ_*}35E%X-X0%d(v@1N|h?3YIRjSgJhQNFUq2zdy)@`#PhHVv9 ztJPwE@B83vHGjpI)B&RWPg2P_A(>lb^x&h@{1>4fU)#^78}Qb02Bi$V zf8|)3zsXcq0ke_O$wAOVb{-E|rnA5Wiax-)(w~lgSz0iyxVk~)>L;g@qgn;o=|HJ~ zw*3Wka!RmbKCeO!FyE%)l>SSUA9mloh38o%g_8{^-sJHhq^xIxXN3BB?Pzz+g0{PX z1&E4c8JYcUweCAv$%<^*$lwB@nlW-SXy$87JML35>9;4HpG)XlVqM^yAN~5jo|RUb zt~jJUN%_*8^K2$;HIQR=IjjGOkY1wd!-tPA06jm;3=wU$`r_tYJN8CArW%vruotZ? zk^+K~so(kWn%0JgyPe-|3NfvP(D`t521AXb2$0Kd;-bC8y76cE`e4e zcCZ7VmU9*7?{)*VVY&lTmMdc{WYOL)DLEcMOU!RmOqPMa+1%uRT;C%s`rxD=+pwT) zloax{9apptO5?k7t+ZRHv@A*5?DUgQF>eeJ9%jP&v(_M>8Fr+5c??v2&6|dU--nwU zCb|VG?Gnt>K`pILtpytr{E}0>#^#Q$zNtK>ZtHgJz5l+w=P2hRuduW!EVc(HsOp`s zT`jE=7gWEsGDQXtMu5m*utDOtk@|Fvm=vg+DGUEjA~75|GKGj%c=T4Sv<4yJjsHEk zMgKD!L7=;4G*=3oQ4)b;kz2W>C@(#;F73;sidNAVNOIoC?=Vl=wh}oa{|1*jk87|M z5*QCCN?{1DMO!dnzw`|$axS@n|S=nHV#Z8bu>xA>|r$d`5!6{=s(0lEvD# z=}8tr$4Jk`<*W=AAIK|{A@lahq6yWw#P(9v`hT&w>nise4(PrtaGLQ8p?u>4IT3df zOGF-3Tf5inl(!qGO;^W*5uM6n&L51X>kZVUPj1Y&;sr_0LX(A;OLXdFmfLQg2{=$2 z+Gz8{?m81$F5K1;PN&EmWus4?1C>`&yKl9D5KpI=W5rqmS0luf$lp?eb%*mH?v>#)@Oe(ckD%1(%6xY=@j^G z7butIeVE35dTI6h!=cHjkq>!l>ujdHzOnDtjH7YYcD>gWA;!u4(4kE_* z$!O?EE4Je#XBs3e(-!5~jPApCbky`s-SDQ~YzSUA;ISusQ3818N4;6?#w$OH=f9Xz z(1lkKmBa-9LETXX@bZgKEr#;gPsWHKRE4t>*OCYB@wkKQLE`lBLpFSm2n zu+3v&!MNR?$Nr^prAjF?J@>GT3f0yC2UO^+t}u04AU}0_#$f0JTz^P6WU*Tl!2}wr zjOVh?$d0NS$8aR!+bNya$k0`FKabht3&NlLpbt!j0>FJFqYg6^cfJA-O9M25O?T<&4R z41U+KI#eXNqKlq2`-wkke*0ubU-ga4EOYaxQ6S60NzcX*({=Z@&5JA7<}dd1yxi(8 ztD^Q2l;WW^C!q>C)|jXQARFUEVX~FVBsQ6uVk6#Q|07FR@;CWOxYl4+8c zSiOuZNZ-YiUsiivmdwwol&%v&O0(U|v)!goXuM~<8=Kd1Zhaa=cy^9aaC_s^%XH+( z!mCK-z)Uk~^VP(F40DOAPa{=*66Z+ddiI9J{cM3BBwgk27d7y>^*47>(LPMOHf+xz zU2sl`$o1+8R5Y>UnKUm-S$bhXTYC8Eb7^#|E>X=HIki~u6i`OKC|f-A{eivt5T|F% zzx{~M@szMu%6aoE%F@s~T%pGn>ziX2LP3A!$4vR|i4fv$HGP7sP5ya=8Hp}&9#s4` zu^!0x@V3svxC?ek+Wi!IR^}@<9M%G|L%j+Hgn&o!aD*R!pQT%Ny)N3}3KmvSHVN7K z#PuP;J#Srh#iC!+xW!=9Of4PY3Az$d<_1`T%ga{WLk5;SJ!^hAIx%L?jeq<1L7p1} z9EXI8I4piyzA=z{)?+s+5VG9x{Se!E)9ReiukWyG^{Ah+4a^is~_dBjty9o^5+nEI^(bwST_AnCS zH*4NZ-P)~uX+41WtTY=-?~Pb8MDDZzm1HJfiUk1MDM*?YJpO+__P6p+&CNR<-O;iq zlyv>L%ja3VaKX^1eO?13fiQ2mxtgJtip_1E#J}E%ZXd*3r7I3lBBnRlQQBF~V=(Iq zS5T2X%~16q6Gq_<8m(-Z{AT(ZEId3r?PA2hO3Nz}UEM=f=-&*t{{RaT^&!y8Ez72! zPNny>cf`9wofdB8{YG%P4aQ|n1Q=cF^S|BMdbP)=Pa~!TZyC1I&Sgu<6`f(F_ii;2 z1oG9MM?L3(+~(D28hN|^EL?u+wm0Q7yLYsCTT+? zF&(Y;EsE>TIz%D#x3#&Yc<8FedC%5y|Lojly)lF&`&2 z=8scrfo};uD!iv}0pTJWqk9eOm@kxjQv@NL8p3MtpLN z?tRTGyhrNl>@^eR+OB};3b+b(8O!wshagby^?N4{sRC0X_lU&Bv=P?4ESd} zk=J0^!74_TH&xU-_}su+r%s-C}c%o*^m>xo5uWj8%;eJ2-h z(z#9r75BTW<8H=S? z!otor{|+&CN%mx}Rugykh>An;%#5yG8kissyIa*z2hbtrw!$;TbiUW`MJL>}Sa`1* zY4nXrB*f_QG&Nz9JV?^pPp4#eC-=n@7)FJpq&olP{Cx3T`JB)4w}|-7kRT&_QsvL& z9=IpsS?w8}BbsGA<+lfdvY_F$#uc}v0^dTzU%CUSgn)WF-REPK90u12Y<*tiI>>lT zVH-UUO$DX@5onxu;X#~TphmXOK(>GaYSYITS+9HRGSfFF3sW1ou(tAEn3@z@t(Xqw ze%`W3-ybG#2HrRSm>Fznbk4L0(7!+IKmn3A4|_Xg`jv*q;SHAq>vl8DU>iRW+4GW@ zJV2eWHqWnGXo}A}3(voluHqkolG0rpQ25sBz*;1<d9KRyF40^P*6wG&WFCG(;?x7z>vr z%f$IFO1diKe>selyjqGdM{JD;CJ!>I!&+B2&b8exkp}4s2JQMjsb_e_S3CX?w-&R9 z&�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 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/lineitem/metadata/snap-2010185327452057859-1-9eb45a05-f72f-48d1-82f9-b6ea7baffaee.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/lineitem/metadata/snap-2010185327452057859-1-9eb45a05-f72f-48d1-82f9-b6ea7baffaee.avro deleted file mode 100644 index b510e9fbd7b96d0f6a605f239fca8907cf6fe112..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3789 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 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/nation/metadata/snap-5987065169814573221-1-e3a2705e-f37e-4708-9dd0-34090f475f02.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/nation/metadata/snap-5987065169814573221-1-e3a2705e-f37e-4708-9dd0-34090f475f02.avro deleted file mode 100644 index c77acadd4ef5874bc8621cf773b894f7c315ad66..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3781 zcmbW3O^6&t6vyKdBqlKzV+e7TvRMquCh6YY`Pc=?w;`ft$*!_w*^D*aH9IXm-IMC- z*^FTjBND-bAmTy6fQLmq1(ZFA5EMfsiI|H*LJ~AWE-E78B`06?M^*LA^xCtx`KWro z_p09i{p(ouwFfqD!!7FV_RTtJ;QNoA(OA1lv?+}e)1#I)s=2tqzc;XJ*ND$5HQOQZ zbIm4>Spl+0 z+;*ABdItVPM-QRECl+EJTB0`V&UZ`+i!F;-72m~8zwR+L?7Zd%jst2kgA^N5bW|c2$eKT18DIY!fre2`8xIy zMtl6}g6>Y8R4S6NFm$>mEn$i-$%10H4>P-cjIxq~<1Gx{KQzd9N*8mQ84GuOs3!PC7ny&#Gi$z=Rh{9%qMgxQN zcqs&%b$Oht)}xn{gN&vQ^_DUfpB_4qiZ|5xCKR6^Cd8hXr%tp)>-lks%=6MF z;Ci-(R%m(_F}FYqIhc`c$qa`Zd#W&5F6qUBQJgFr6NS?F*qAX4_tn5`SBp>|Tx7@? zwoOtcbTM|+FLwz2gAoQMx0J|h!xqp}`j36D*K=b>rQ}p#Qv&V?p-3`-v;iEJH%mk@ zdMUzNGdTIYRwBj(LX0EDWiufE@46B&E&x&ICwCS3tpwDtK^>hg;3Q!`hHF6TA^~aP1Ml=DR&>6=h#Nxn<0+w}+OYwtZ zt3pgpY|lzA9mIc%&_KU|6=7rrc7-<@W_P8YR7oXUbgV6zXG-GI07zQZu5*CS`Cr zfrMI97L_Z$5US!;5+P?!qSL_%i9}VvLT3aN$=@Q$*aSz~z+aE+e$&t1IDYcT*LTWy zFPyl3{`SxZLyuqi;o)!f&HH{`InpwW7eAj~Ikn^5E1&Hh*}dt)P<7pw;q7mk=U!c3 z+c$gc{!4#-yt6a?%tMDumq&g%UBCIs-KXE&`Q%T9Kgv(+UtWHDYwMj2k6ila+NTe` z?+(xYc;NS|A3gu>qX(~l_|4p{gMU7Iwm4_%D;wtyyf^>VcYojeqT1eb{_?-U8}}|P NfB*B9i+j71`w!wG3~m4b diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/orders/metadata/00001-e500bd00-0e38-4dbb-8a45-b1102b4616b5.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/orders/metadata/00001-e500bd00-0e38-4dbb-8a45-b1102b4616b5.metadata.json deleted file mode 100644 index 806282761657..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/orders/metadata/00001-e500bd00-0e38-4dbb-8a45-b1102b4616b5.metadata.json +++ /dev/null @@ -1,166 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "de39f656-0565-4fa0-a349-dd55485b7245", - "location" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-parquet/orders", - "last-updated-ms" : 1663700056763, - "last-column-id" : 9, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "orderkey", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "custkey", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "orderstatus", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "totalprice", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 5, - "name" : "orderdate", - "required" : false, - "type" : "date" - }, { - "id" : 6, - "name" : "orderpriority", - "required" : false, - "type" : "string" - }, { - "id" : 7, - "name" : "clerk", - "required" : false, - "type" : "string" - }, { - "id" : 8, - "name" : "shippriority", - "required" : false, - "type" : "int" - }, { - "id" : 9, - "name" : "comment", - "required" : false, - "type" : "string" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "orderkey", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "custkey", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "orderstatus", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "totalprice", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 5, - "name" : "orderdate", - "required" : false, - "type" : "date" - }, { - "id" : 6, - "name" : "orderpriority", - "required" : false, - "type" : "string" - }, { - "id" : 7, - "name" : "clerk", - "required" : false, - "type" : "string" - }, { - "id" : 8, - "name" : "shippriority", - "required" : false, - "type" : "int" - }, { - "id" : 9, - "name" : "comment", - "required" : false, - "type" : "string" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.7.ndv" : "993687", - "trino.stats.ndv.2.ndv" : "101809026", - "trino.stats.ndv.1.ndv" : "1485693109", - "trino.stats.ndv.8.ndv" : "1", - "trino.stats.ndv.6.ndv" : "5", - "trino.stats.ndv.3.ndv" : "3", - "write.format.default" : "PARQUET", - "trino.stats.ndv.4.ndv" : "41505504", - "trino.stats.ndv.5.ndv" : "2449", - "trino.stats.ndv.9.ndv" : "270910389" - }, - "current-snapshot-id" : 2054734473589120268, - "refs" : { - "main" : { - "snapshot-id" : 2054734473589120268, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 2054734473589120268, - "timestamp-ms" : 1647889492638, - "summary" : { - "operation" : "append", - "added-data-files" : "43", - "added-records" : "1500000000", - "added-files-size" : "43271276699", - "changed-partition-count" : "1", - "total-records" : "1500000000", - "total-files-size" : "43271276699", - "total-data-files" : "43", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-parquet/orders/metadata/snap-2054734473589120268-1-a2c9dd59-f137-4d62-bba4-cd37998c427d.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1647889492638, - "snapshot-id" : 2054734473589120268 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1647889492638, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-parquet/orders/metadata/00000-50f0c3e6-0615-4d82-a0be-20013977b9c1.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/orders/metadata/a2c9dd59-f137-4d62-bba4-cd37998c427d-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/orders/metadata/a2c9dd59-f137-4d62-bba4-cd37998c427d-m0.avro deleted file mode 100644 index d02fd05fa8163427cf75f34fc6b122f6eca8aeeb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11526 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 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/part/metadata/00001-b61d1dc1-a40c-4d6a-adc8-f0abd7376d1a.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/part/metadata/00001-b61d1dc1-a40c-4d6a-adc8-f0abd7376d1a.metadata.json deleted file mode 100644 index 41c5152ec169..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/part/metadata/00001-b61d1dc1-a40c-4d6a-adc8-f0abd7376d1a.metadata.json +++ /dev/null @@ -1,166 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "41b5f9cf-94f6-4fbf-870c-10d744b8e84c", - "location" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-parquet/part", - "last-updated-ms" : 1663700162346, - "last-column-id" : 9, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "partkey", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "name", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "mfgr", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "brand", - "required" : false, - "type" : "string" - }, { - "id" : 5, - "name" : "type", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "size", - "required" : false, - "type" : "int" - }, { - "id" : 7, - "name" : "container", - "required" : false, - "type" : "string" - }, { - "id" : 8, - "name" : "retailprice", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 9, - "name" : "comment", - "required" : false, - "type" : "string" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "partkey", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "name", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "mfgr", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "brand", - "required" : false, - "type" : "string" - }, { - "id" : 5, - "name" : "type", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "size", - "required" : false, - "type" : "int" - }, { - "id" : 7, - "name" : "container", - "required" : false, - "type" : "string" - }, { - "id" : 8, - "name" : "retailprice", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 9, - "name" : "comment", - "required" : false, - "type" : "string" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.7.ndv" : "40", - "trino.stats.ndv.2.ndv" : "198181644", - "trino.stats.ndv.1.ndv" : "205115671", - "trino.stats.ndv.8.ndv" : "118482", - "trino.stats.ndv.6.ndv" : "50", - "trino.stats.ndv.3.ndv" : "5", - "write.format.default" : "PARQUET", - "trino.stats.ndv.4.ndv" : "25", - "trino.stats.ndv.5.ndv" : "150", - "trino.stats.ndv.9.ndv" : "13911863" - }, - "current-snapshot-id" : 2451437136998665463, - "refs" : { - "main" : { - "snapshot-id" : 2451437136998665463, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 2451437136998665463, - "timestamp-ms" : 1647889641649, - "summary" : { - "operation" : "append", - "added-data-files" : "6", - "added-records" : "200000000", - "added-files-size" : "4578616552", - "changed-partition-count" : "1", - "total-records" : "200000000", - "total-files-size" : "4578616552", - "total-data-files" : "6", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-parquet/part/metadata/snap-2451437136998665463-1-5edd0121-9c67-4612-a698-952806d01c6d.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1647889641649, - "snapshot-id" : 2451437136998665463 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1647889641649, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-parquet/part/metadata/00000-07030f08-82f6-4010-98a5-3d0767a8f31c.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/part/metadata/5edd0121-9c67-4612-a698-952806d01c6d-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/part/metadata/5edd0121-9c67-4612-a698-952806d01c6d-m0.avro deleted file mode 100644 index b6f6d0aaf040bda64150d91c7fd16a8e3c64b834..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7143 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 bmGDt}8)aqhhlX!YmFE8s^`k`{ diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/partsupp/metadata/snap-4939042327833546636-1-00c1c648-86af-4ecf-b9cb-85595ef91a48.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/partsupp/metadata/snap-4939042327833546636-1-00c1c648-86af-4ecf-b9cb-85595ef91a48.avro deleted file mode 100644 index 66ca8728722ef99a4277cc54901de71aec4e8921..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3786 zcmbW3O^6&t6vuTzO(29lgdB8*qT`Ar8=3E&T>?hfh)a~HyNVdr-kR>3oeDMGldkGr zXTu04hrsGd0_I{6K>{lt6azvAK?q|&@t{e_&4UDTFnY+vND{2-kE-gK>9uEX^HKGF z?^V73``5ABiAQ(sg{z+XJhd9A3IBcSl)>69WXu{KvRuzLrVIx*#rGz3a2-)*)-gf+ z=Q>8jrpD~?Qv)knkTv*^k7hH|D__W)1Mt0to0b>OpK9 zv*lc_Gc_2SAZJC7PFyYaqRSuwB0)g$Is(`MwKhXEPB#ji*8;bo$FRu9q%CBboGcT^ z4wG3g@IS%)9PlZ!0dv8chgpBV1uZPJZDgC&fi2o_nI87I;rN8`z(M9q@v$sKO9g~A zAOk@J{tImSrH?;gQ%$D0NsWR2De2`%k_g|!Yh#}hzex}FN%}_a+uc! z@%k-zC5?yP!<6w$Hb+DriS}iumNlx42x9z{M}*FW+p;q&8dJ85=&9ssO65S$gKhpd z?~o=Ji2S`@QC>M$<;wmN3TJ(J0;&lL>cG&X`NC5<5 z6Oeg=RY?5)PMy&zQm`O&vL+*D$}UNRLT3mwy?ydkEd}DPANvNoCpWDfWH@y^cP&-%$)OWUUaZfc_`E*fq~f!~q}a3a^obU0Jv%Ozc~)A9 zU(cSv3N6=0)^6TH1TwTYp5gq)o-NH3XL6-Nu`pdN7R#kdrC6EZ_tk{#;41PccagFD z1h!BOc`K2le*2`7K4Y6$y&PwM|Z}wBeX(iIGhpCUnX%NwE_)@5vS1E*=Ik8ShC&Uuf0ZW}{xFW?{Bp#dGk#>mJ<0JdNe*e1zKr*SN{0u@*G(1{`_b4K<~4k{#t(ZV(*uYUyt5ge{R1u z`LWskr29e-UV3Z)j`iQ}f#rW*_Rl;VU;gm)iDTsE--jozkCV+ykDvYi&fC}Kc794X z;Scw|Du1zPPo8`1ovY`k&Ts5G*M9dF`Et+28@DGfJiy)F>xJ&wA8%d%-97ou{O9cl OckeFUzw<)rddYfINHX_oBz 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 diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/supplier/metadata/00001-e7a9638d-1c61-4083-b182-0c99c6f89305.metadata.json b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/supplier/metadata/00001-e7a9638d-1c61-4083-b182-0c99c6f89305.metadata.json deleted file mode 100644 index 18ca1e02d7d6..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/supplier/metadata/00001-e7a9638d-1c61-4083-b182-0c99c6f89305.metadata.json +++ /dev/null @@ -1,144 +0,0 @@ -{ - "format-version" : 1, - "table-uuid" : "66cdf5e8-0ef9-4991-ae57-855c93ae9f43", - "location" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-parquet/supplier", - "last-updated-ms" : 1663700326200, - "last-column-id" : 7, - "schema" : { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "suppkey", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "name", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "address", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "nationkey", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "phone", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "acctbal", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 7, - "name" : "comment", - "required" : false, - "type" : "string" - } ] - }, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "suppkey", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "name", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "address", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "nationkey", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "phone", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "acctbal", - "required" : false, - "type" : "decimal(12, 2)" - }, { - "id" : 7, - "name" : "comment", - "required" : false, - "type" : "string" - } ] - } ], - "partition-spec" : [ ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "trino.stats.ndv.2.ndv" : "10167273", - "trino.stats.ndv.7.ndv" : "9911599", - "trino.stats.ndv.1.ndv" : "10311101", - "trino.stats.ndv.3.ndv" : "10233152", - "trino.stats.ndv.6.ndv" : "1068508", - "write.format.default" : "PARQUET", - "trino.stats.ndv.4.ndv" : "25", - "trino.stats.ndv.5.ndv" : "9704424" - }, - "current-snapshot-id" : 3174586439174883544, - "refs" : { - "main" : { - "snapshot-id" : 3174586439174883544, - "type" : "branch" - } - }, - "snapshots" : [ { - "snapshot-id" : 3174586439174883544, - "timestamp-ms" : 1647890140401, - "summary" : { - "operation" : "append", - "added-data-files" : "6", - "added-records" : "10000000", - "added-files-size" : "526125065", - "changed-partition-count" : "1", - "total-records" : "10000000", - "total-files-size" : "526125065", - "total-data-files" : "6", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-parquet/supplier/metadata/snap-3174586439174883544-1-359263b5-5a1f-4f16-bea1-f0f748f0c0a8.avro", - "schema-id" : 0 - } ], - "snapshot-log" : [ { - "timestamp-ms" : 1647890140401, - "snapshot-id" : 3174586439174883544 - } ], - "metadata-log" : [ { - "timestamp-ms" : 1647890140401, - "metadata-file" : "s3://starburst-benchmarks-data/iceberg-tpch-sf1000-parquet/supplier/metadata/00000-8b7670fc-7cd4-479c-b6fa-244d86a11f4b.metadata.json" - } ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/supplier/metadata/359263b5-5a1f-4f16-bea1-f0f748f0c0a8-m0.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/supplier/metadata/359263b5-5a1f-4f16-bea1-f0f748f0c0a8-m0.avro deleted file mode 100644 index 022b29b6731657e69498e393db13a8e9471c8263..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7172 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 PswCfv`Q^`^+j;*7&Q=3( diff --git a/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/supplier/metadata/snap-3174586439174883544-1-359263b5-5a1f-4f16-bea1-f0f748f0c0a8.avro b/testing/trino-tests/src/test/resources/iceberg/tpch/sf1000/parquet/unpartitioned/supplier/metadata/snap-3174586439174883544-1-359263b5-5a1f-4f16-bea1-f0f748f0c0a8.avro deleted file mode 100644 index b3ddd7c89ca9294b166e4059db09084228bc385e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3789 zcmbW3&x<2P6vubxAbaq~MOM*06pe`BtR|aek{J+GR$)PK*ohL78G0+-)zdZGKVobi=^q6mT~6?f-4hZRv2Px=RVR}icEM^$w?J$B}nA64)3Ue)`) zZ$01LI(g@5xb3-*`I?Dr`1gam3hDQdvZ8oMb3I*IR2*ot?>2O>fqbGG*h2JY10zfK zm6gtJWks0@wlFK0$txZXCEQ1@1wQV3v>S_mHMiGEW>kglD}* z{{$D#gTO~RATHSPFqzKxoD>%7I?`3&fj!@Ji5#}0IDuu+z+vVa$+4`(OBsZikbp1( z{fIzv@+=u-z`!EP3X2inL0nWNL)RgJ;cfY<<_2Lg(UtHNScN=;r&!7WImGLOWc?Am zs>DMtW1rAVR_8=M6Yncdtw~fJS%}b6o)daL+E$#oBr$oru%1evCRYyhJlLmy(+;tz zK=|*|it=iu23?jFlIA)DV#inGPKkxobzKWVhqXbdq^1|p7B>KN`ccoD&<7B(O@QYK zwxJbFcj~fKk${Dv(={mxlXpoT6go4Q`R!9~NGVwE4)WA4?NOmV-lLI#Ie#_LD+qUi za2GIK4?tQrlRmqdBf#=h33|O_fWuw$gUf@B!J z9OseslR4_C_dYOS$Utt`_IjYg$bt)8LxWkYgy8+kr;k%jUZtf4OQ zHe*Nq`T?$gYJ`EtEG6>Vumwsf{l~tS_1xG|J@it5Jw)A+hC;~zQs!`2)+`aFoTYGY z&EXXDT7fvn5qum;E}H}Sf7ca&3I{};pWIdCw*p{5%NNI$Dx-++3Se~#IKz7KsB&Fd zs}+bEM{tcDlfnfjd%6HEaZno)S{xcE@p8ez4`n3~cDFf|_o~qSzT8E2wmm#4q)5;;RBChDX{$=Z58bY051{Ty|g7Mx&&U@Jx}bm_jq`Q3Ot3Noq!9Y*GXl6Nsxd zWl@FVbD>IJB@klfBs!g+kVsSpYz!Wviezt*WNcDLI>la(CvLuJ{J8t=&s!(|dT;dS z_`7=+t}py^(6X#gzEszTtx>bxT)(zI8oxhzXTLStxUoNM4L`ejWilSMM_+UOAHViGzV*kWum1bQZ$I7l!@;eC6K~!1&D*^t.ztOxeS}# ћH3B]Fo;ZˍFgQPi˓w-ԼZolo{{wts - BL@:5Is4O-ZEZeZUZu&m&m6.%ԡ.Rfr`AP2T -5Mh@ZІt. 48b2?0͟y% XGH"VLSʝ+ ^k`R )O~rQ53$Jq„Xbե18JB8\L y5L6ys[k}N>%Z(+Hh;s2IWa ɖW)OYHWp !|jg>3?hBǧ<-\bќ}P|{WVBJU4s6vV8x>j#fJALȌDl'"jfe߶"ю,8 }U]R~H.58qx|+#!VKbJ2i \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/call_center/metadata/snap-180463549666864477-1-f8bcd002-b25e-42d3-9a20-680286272ed5.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/call_center/metadata/snap-180463549666864477-1-f8bcd002-b25e-42d3-9a20-680286272ed5.avro deleted file mode 100644 index fa5eee5f76246069af93db2a57e7724044e9984d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4297 zcmbW4UuYaf9LL+UVD%yTP^%9$4r>I3M0S(Q<#Lc>Fs-x{s$P6Clw)ppHn(wa_qscm zGpW}q)+&nPi>>(Jql!3ziNFBdFd`zJ$4Wg73kLa#vD5Hvl>+E+OJC;d&S}`q~ zz+=-Qw&5$Kg=1YS8kp8#MHI-Vp5-jUZ^P9g&LLOF)N&p4%+m)@%c3>3zl;Jpk2+=< zN@-G6+oRpFZQ?A-(QQ|XJ?~O%BbFfYtyO|72US-n@#A!(z?u}efjw%md@6o}=oKLI z#4U&Ntjq8xIxvTtJ~0q=QOmPvXTIZ7Slx9f3Eq#YAP0Lq{L@FSiD-?WxU?mt&KQ{D zQKZcn#3o&tYf}=!Cr6va(MiQ=)~m!5c_&Izj#i;?6>8OplYpWY_7OG=!gd}sHLQ|0 zDY5Dn)Tb}rA-4sG1jUPEEObKNX$Ptxp@LK@zJnWn&83NkBsUhVU|pd#Ov63&pr{jF z6jtouJo^>hZDzG=mK6sS1l4{4~xN;rPV@{?*OmGra^fbGULjt6{*th_hQRU8YAz*3|UfKo4 zY1*~|DimE}?0I+v{)W3>2kGO_JGIiAREzNuPe>$_1#CG!+*uYlj$3tIn_veXgc>>T zH9@>ggq@X8&1&q9!d;VBk8B3TP72c?CT`kPDY2ZA zx>1x;ut5#HN)_&w(ABuAN+`{o|75JhD0mA6Zz0q55Qc3tQD>d$0hZ@Qv(eZBICyJt zu+~1(9#!DB)j=ep8B=WYjg_1AItJ}vTOnAliE{Sbk2(cmcLl>)cMYNs!Pad|$?jMi zfi$8O)qG)MGCy4?6pDp>erjqDVCtBTEfdd&v6dSjjm)+|LFmK zkYQFgbvE@vI}q$eP0adeGSZ0qppM6s?=W$A1PVp;ei7 zKp@6Bg5N922xLIyS)OhTI6#}?0AUIG2s?;H5U@!Oi+e82K?XEYu<`(AK~M{vnr;*N zA@^w;I8>eB&@{u)53<~cfe^fW2QOv5XF%oC(rqOKK{SsK&aFZCG?guj;D~m`gLG?{ zc)F%iG;y)Q{v?KVMx_#GvCC-i9cZHWyix;)=S7mtfgp%($2A%l5SJ${zA99K%MKfE zqAn)3eXgEdo^3Ffu&iU!@CTA3;X*EbriG$9%@4Bh2ca1e^n`TBMqx9bkY-LqMT;re zz?KMVV$a;4Y=%v-CxQyIEBbs*I+0AM<-nw7V>$R%?Z=V`m>5?{_c1Um+lC~JhKS4h z1r8)kx`*9>*GJ&MF_$8lh@H&CZCkkZ>#OH;-+ubutp^V)<{S6s)^qoNKRfi*W4pFp z-T3C{g?~PHmON2?WOixngn8Ure`w)UsJclMp1i!VQS?)J6z z-WPwrXfJsScb(2%zIN99?9`4A^@p##K6CQRwc!^w_RYQf=I&d+jhs34@e(Q&Je|+oY Lr4#5vXR`kT5{$rd diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_page/metadata/00000-2ef466a3-5b3d-4c75-ae60-3dddfed4dacf.metadata.json b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_page/metadata/00000-2ef466a3-5b3d-4c75-ae60-3dddfed4dacf.metadata.json deleted file mode 100644 index 83fe806fa651..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_page/metadata/00000-2ef466a3-5b3d-4c75-ae60-3dddfed4dacf.metadata.json +++ /dev/null @@ -1,107 +0,0 @@ -{ - "format-version" : 2, - "table-uuid" : "5f31af16-4b24-4241-b615-565c0f9c312d", - "location" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/catalog_page", - "last-sequence-number" : 1, - "last-updated-ms" : 1678866109156, - "last-column-id" : 9, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "cp_catalog_page_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "cp_catalog_page_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "cp_start_date_sk", - "required" : false, - "type" : "int" - }, { - "id" : 4, - "name" : "cp_end_date_sk", - "required" : false, - "type" : "int" - }, { - "id" : 5, - "name" : "cp_department", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "cp_catalog_number", - "required" : false, - "type" : "int" - }, { - "id" : 7, - "name" : "cp_catalog_page_number", - "required" : false, - "type" : "int" - }, { - "id" : 8, - "name" : "cp_description", - "required" : false, - "type" : "string" - }, { - "id" : 9, - "name" : "cp_type", - "required" : false, - "type" : "string" - } ] - } ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "write.format.default" : "PARQUET" - }, - "current-snapshot-id" : 82908533283371708, - "refs" : { - "main" : { - "snapshot-id" : 82908533283371708, - "type" : "branch" - } - }, - "snapshots" : [ { - "sequence-number" : 1, - "snapshot-id" : 82908533283371708, - "timestamp-ms" : 1678866109156, - "summary" : { - "operation" : "append", - "trino_query_id" : "20230315_074147_00041_9f4mz", - "added-data-files" : "1", - "added-records" : "30000", - "added-files-size" : "932271", - "changed-partition-count" : "1", - "total-records" : "30000", - "total-files-size" : "932271", - "total-data-files" : "1", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/catalog_page/metadata/snap-82908533283371708-1-cdf08a55-904f-4581-aa97-f856752b6967.avro", - "schema-id" : 0 - } ], - "statistics" : [ ], - "snapshot-log" : [ { - "timestamp-ms" : 1678866109156, - "snapshot-id" : 82908533283371708 - } ], - "metadata-log" : [ ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_page/metadata/cdf08a55-904f-4581-aa97-f856752b6967-m0.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_page/metadata/cdf08a55-904f-4581-aa97-f856752b6967-m0.avro deleted file mode 100644 index 14726f794ab4f7013f1d81a6864bff06d0e72212..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7385 zcmb_geT)^=6_>R^tcGq;7pSz}jDohlg*P+HepTWZtXYKx!TqPpW}G+gzTJU&GqW@E zcC*436Kuy9qqv^xcuH3F#_&Toz0i#Y9srx!aels#|2&Ira3GHXzTyBH zd4PaQvyjhVCjo?yps5@JO~$xjnt7Nkb~Q)0J>4=ByoltYg-4)z8k>dgp*pqSvF7`7 z+p;heqJuTdAwnn$Ew7q-4!fQL>^g(+oRehn6-d8VaN(z-mI+D4_k0}_=>_kas_ph# zoOoZqZpch5$&M5d=GS^qtD@rdSW>ZL9WLvII zbcC|lz#eu}NLmE(sfO+i2G0VkG))Bt%ztfg)B!S{}5C=V!DL=?x7&S6#)n$oFcDa8#v`hsG^MO;w*yb3HPBn7MvSY zF35$~34HK6pEO}CNy2U(u_hD zdITbk8>wa%b*LR+HGwR66t1gHQh|& z8%3lT%a9~j*Uo6Xm5{`%9lD5;$DT^2gi%ToeCNXRW{IgegLJ@0@Er=iLm9)$B<4lt zyOC?d7^hgdoQu86)f=3UFc6YusW|n?>@ZwKU_CVh;}^q@Z$~me+61#DO!njO$ln!8 z9cp9_EeQrz&C0`&rMqw*(?bijFD6zJeB)*8TCktOS_4`xw|5XH!UU~?cVWnccfm%Y zi3VR|><}dDp(yT1jrh|6|EX8yDQMSGxJ1)%2HIfj{i}hq)ggn%w=ja-S-qusx z$PQl{j!!TgS8z1|mj%I!6|7_IIsg#ZZ2-T+vI_;bWHDo(Cfp>jJAl!PQ$+ym4uEb2 zRidy94SwTd4)DQ`CY5H7yJ3-;Q(Cxwl?M}i@lN78MmjIO=Sj14Qfi8eSd1T$^(kpF zV-{HIsfBPQtIS4Qg)17f$I=w4AF8XowS+WH!g|JhVhWBdX64-q`lp}qUF%`SAF)Q3PQt7hB@JmWLq?oKjDFMzR|2PogT8oUutwnXyqqg`H*zix@)_@8B zS7w6L8Z2DnTCG?N>>`WevL%2Ss4NBmiitsj!kU~Il6RgkD3Miix%Fn8kx5XsagjlR zO*FDm%Saf8%x*i&>>cBbLIxqlL?J`!CLYC@TS6F@#4eH}3&A*JlHfu)CMF5E3u4OX zrb@3->4SP^WX#Ip=Xd5lEBnZ(&X6lA87aPGWUgeYcb}iEL-%EN2myEe5FO*b5IW$u zn8azCnPF#7ba0Nb6Lv>Kcga0t>3|ByM)3i($>LQ?@CE}c$!M9$nz_d`7#X4&SotKD z^$vu{yeplr8D5EV$y0XdX9H_x(Se!)mpEEHL;PHo4%RR#!{3~67NC>x54yUi7pJai zWSBee`&T=EbS{0*T^rl(Yh1Kz_|?`u7p;C)T)5+B z?riJBn(a@I{93-V=1_jm{Jp2^Z<@Ay>$bYbjL$SJhu_`5HU9DP6c)X99N>o-5y z?xlKOn)i>*o!6f2SbI@d$FZw-D~IM6F6@7x`{0s8--t)PJGx`|k`Io|ocHe5ff;o# z>|cJ}2WPH-t$*kA{ryw_c5Lp^^$-2x3+?Z(jr{XJ&z;>n{L|HY{`IGuZ>Y;&(k{(d zvgOf}R}C+I$pMv_r2TKJwJQyf&80OFPoy}H{SBQ-TRg;J&@jY<6FzO z{O`ZF{$4us$rV3bwV~2WA{T)$-2LS8CUv IA5G2w0lDYPzyJUM diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_page/metadata/snap-82908533283371708-1-cdf08a55-904f-4581-aa97-f856752b6967.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_page/metadata/snap-82908533283371708-1-cdf08a55-904f-4581-aa97-f856752b6967.avro deleted file mode 100644 index 426772b56ff96a691b031a9314702bad047c4774..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4303 zcmbW4UuYaf9LJN0wV}2Yl|VyGhb2~xxw3oLB$tz*VoWL2zgH0~;h5W<%dYP2o;$NS zV+^E4MATXph2TRU^hpb%?UN4?tk9SGpruw(`cSG?pF|%jKIzQH*1WkPXao&Byu$EqPrDmBYS z^l{BXwt(UrCp4MO^~N8##+|bla6;A8`q^0ZR~I>m>v%2UM2{!g0D$U`-0#fF7|}J{8Hn0W=K6b{;e}tdce< zvFaABPglIdZj&Ao5HF6gv=j19TToLHDoCY*9oWF8OA-x9ZY)~CxS$-CnS=|0<;`V?<@-($E~`qji5sxgc^Co z^Qm~%0Cbi^H8a^ArFY4;0nZaGLECTDpc&~b3Rn<2xhExNE^G#cP72dNHR#(!DY2ZA zx>1x;u&Ek&l`6ekLRaIeDxoxU{u8khgWxR?yaj5m2O!-x6Lr>^4q$m+_>IOEz`Rl}en&HlM+Fpo!k|O7$F`7fJF4f*`sb*QjSeT%NS}s!#yKy7}CdO6LeDn;y)r zQ3Dbt-N9|p*T?9AV-*@LZ?CRDKY#tFw@;XlUfi=|;M~BS7dCdDQ+GcI zFMNZ4JXU;wfG3sy-!ylHsb+VP$1YyGCVzV_~GH~v_;`?0Uz+5O9R z=$3<{`LjR#JbdLXaPZ`Zm-ZDt?YnF7_RkR7{_<%&aOT*1xBYhQ*Xr!T;FYVN+&h&U zn!j}Z+JBe7czu+wH+Svd3-qxJ|2j*V86951J diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_returns/metadata/00000-78945112-7cf1-4e3d-9557-e434c41a2383.metadata.json b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_returns/metadata/00000-78945112-7cf1-4e3d-9557-e434c41a2383.metadata.json deleted file mode 100644 index 260944123c39..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_returns/metadata/00000-78945112-7cf1-4e3d-9557-e434c41a2383.metadata.json +++ /dev/null @@ -1,197 +0,0 @@ -{ - "format-version" : 2, - "table-uuid" : "3911cc2d-fc02-4124-ab37-02591db73097", - "location" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/catalog_returns", - "last-sequence-number" : 1, - "last-updated-ms" : 1678866267770, - "last-column-id" : 27, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "cr_returned_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "cr_returned_time_sk", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "cr_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "cr_refunded_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "cr_refunded_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "cr_refunded_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "cr_refunded_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 8, - "name" : "cr_returning_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 9, - "name" : "cr_returning_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 10, - "name" : "cr_returning_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 11, - "name" : "cr_returning_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 12, - "name" : "cr_call_center_sk", - "required" : false, - "type" : "long" - }, { - "id" : 13, - "name" : "cr_catalog_page_sk", - "required" : false, - "type" : "long" - }, { - "id" : 14, - "name" : "cr_ship_mode_sk", - "required" : false, - "type" : "long" - }, { - "id" : 15, - "name" : "cr_warehouse_sk", - "required" : false, - "type" : "long" - }, { - "id" : 16, - "name" : "cr_reason_sk", - "required" : false, - "type" : "long" - }, { - "id" : 17, - "name" : "cr_order_number", - "required" : false, - "type" : "long" - }, { - "id" : 18, - "name" : "cr_return_quantity", - "required" : false, - "type" : "int" - }, { - "id" : 19, - "name" : "cr_return_amount", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 20, - "name" : "cr_return_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 21, - "name" : "cr_return_amt_inc_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 22, - "name" : "cr_fee", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 23, - "name" : "cr_return_ship_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 24, - "name" : "cr_refunded_cash", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 25, - "name" : "cr_reversed_charge", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 26, - "name" : "cr_store_credit", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 27, - "name" : "cr_net_loss", - "required" : false, - "type" : "decimal(7, 2)" - } ] - } ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "write.format.default" : "PARQUET" - }, - "current-snapshot-id" : 8292219806794635592, - "refs" : { - "main" : { - "snapshot-id" : 8292219806794635592, - "type" : "branch" - } - }, - "snapshots" : [ { - "sequence-number" : 1, - "snapshot-id" : 8292219806794635592, - "timestamp-ms" : 1678866267770, - "summary" : { - "operation" : "append", - "trino_query_id" : "20230315_074322_00071_9f4mz", - "added-data-files" : "183", - "added-records" : "143996756", - "added-files-size" : "8397738857", - "changed-partition-count" : "1", - "total-records" : "143996756", - "total-files-size" : "8397738857", - "total-data-files" : "183", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/catalog_returns/metadata/snap-8292219806794635592-1-1b002ed6-2a22-4402-8b13-0b9b85931824.avro", - "schema-id" : 0 - } ], - "statistics" : [ ], - "snapshot-log" : [ { - "timestamp-ms" : 1678866267770, - "snapshot-id" : 8292219806794635592 - } ], - "metadata-log" : [ ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_returns/metadata/1b002ed6-2a22-4402-8b13-0b9b85931824-m0.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_returns/metadata/1b002ed6-2a22-4402-8b13-0b9b85931824-m0.avro deleted file mode 100644 index 80fca6fefda1..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_returns/metadata/1b002ed6-2a22-4402-8b13-0b9b85931824-m0.avro +++ /dev/null @@ -1,209 +0,0 @@ -Obj schema{"type":"struct","schema-id":0,"fields":[{"id":1,"name":"cr_returned_date_sk","required":false,"type":"long"},{"id":2,"name":"cr_returned_time_sk","required":false,"type":"long"},{"id":3,"name":"cr_item_sk","required":false,"type":"long"},{"id":4,"name":"cr_refunded_customer_sk","required":false,"type":"long"},{"id":5,"name":"cr_refunded_cdemo_sk","required":false,"type":"long"},{"id":6,"name":"cr_refunded_hdemo_sk","required":false,"type":"long"},{"id":7,"name":"cr_refunded_addr_sk","required":false,"type":"long"},{"id":8,"name":"cr_returning_customer_sk","required":false,"type":"long"},{"id":9,"name":"cr_returning_cdemo_sk","required":false,"type":"long"},{"id":10,"name":"cr_returning_hdemo_sk","required":false,"type":"long"},{"id":11,"name":"cr_returning_addr_sk","required":false,"type":"long"},{"id":12,"name":"cr_call_center_sk","required":false,"type":"long"},{"id":13,"name":"cr_catalog_page_sk","required":false,"type":"long"},{"id":14,"name":"cr_ship_mode_sk","required":false,"type":"long"},{"id":15,"name":"cr_warehouse_sk","required":false,"type":"long"},{"id":16,"name":"cr_reason_sk","required":false,"type":"long"},{"id":17,"name":"cr_order_number","required":false,"type":"long"},{"id":18,"name":"cr_return_quantity","required":false,"type":"int"},{"id":19,"name":"cr_return_amount","required":false,"type":"decimal(7, 2)"},{"id":20,"name":"cr_return_tax","required":false,"type":"decimal(7, 2)"},{"id":21,"name":"cr_return_amt_inc_tax","required":false,"type":"decimal(7, 2)"},{"id":22,"name":"cr_fee","required":false,"type":"decimal(7, 2)"},{"id":23,"name":"cr_return_ship_cost","required":false,"type":"decimal(7, 2)"},{"id":24,"name":"cr_refunded_cash","required":false,"type":"decimal(7, 2)"},{"id":25,"name":"cr_reversed_charge","required":false,"type":"decimal(7, 2)"},{"id":26,"name":"cr_store_credit","required":false,"type":"decimal(7, 2)"},{"id":27,"name":"cr_net_loss","required":false,"type":"decimal(7, 2)"}]}avro.schema5{"type":"record","name":"manifest_entry","fields":[{"name":"status","type":"int","field-id":0},{"name":"snapshot_id","type":["null","long"],"default":null,"field-id":1},{"name":"sequence_number","type":["null","long"],"default":null,"field-id":3},{"name":"file_sequence_number","type":["null","long"],"default":null,"field-id":4},{"name":"data_file","type":{"type":"record","name":"r2","fields":[{"name":"content","type":"int","doc":"Contents of the file: 0=data, 1=position deletes, 2=equality deletes","field-id":134},{"name":"file_path","type":"string","doc":"Location URI with FS scheme","field-id":100},{"name":"file_format","type":"string","doc":"File format name: avro, orc, or parquet","field-id":101},{"name":"partition","type":{"type":"record","name":"r102","fields":[]},"doc":"Partition data tuple, schema based on the partition spec","field-id":102},{"name":"record_count","type":"long","doc":"Number of records in the file","field-id":103},{"name":"file_size_in_bytes","type":"long","doc":"Total file size in bytes","field-id":104},{"name":"column_sizes","type":["null",{"type":"array","items":{"type":"record","name":"k117_v118","fields":[{"name":"key","type":"int","field-id":117},{"name":"value","type":"long","field-id":118}]},"logicalType":"map"}],"doc":"Map of column id to total size on disk","default":null,"field-id":108},{"name":"value_counts","type":["null",{"type":"array","items":{"type":"record","name":"k119_v120","fields":[{"name":"key","type":"int","field-id":119},{"name":"value","type":"long","field-id":120}]},"logicalType":"map"}],"doc":"Map of column id to total count, including null and NaN","default":null,"field-id":109},{"name":"null_value_counts","type":["null",{"type":"array","items":{"type":"record","name":"k121_v122","fields":[{"name":"key","type":"int","field-id":121},{"name":"value","type":"long","field-id":122}]},"logicalType":"map"}],"doc":"Map of column id to null value count","default":null,"field-id":110},{"name":"nan_value_counts","type":["null",{"type":"array","items":{"type":"record","name":"k138_v139","fields":[{"name":"key","type":"int","field-id":138},{"name":"value","type":"long","field-id":139}]},"logicalType":"map"}],"doc":"Map of column id to number of NaN values in the column","default":null,"field-id":137},{"name":"lower_bounds","type":["null",{"type":"array","items":{"type":"record","name":"k126_v127","fields":[{"name":"key","type":"int","field-id":126},{"name":"value","type":"bytes","field-id":127}]},"logicalType":"map"}],"doc":"Map of column id to lower bound","default":null,"field-id":125},{"name":"upper_bounds","type":["null",{"type":"array","items":{"type":"record","name":"k129_v130","fields":[{"name":"key","type":"int","field-id":129},{"name":"value","type":"bytes","field-id":130}]},"logicalType":"map"}],"doc":"Map of column id to upper bound","default":null,"field-id":128},{"name":"key_metadata","type":["null","bytes"],"doc":"Encryption key metadata blob","default":null,"field-id":131},{"name":"split_offsets","type":["null",{"type":"array","items":"long","element-id":133}],"doc":"Splittable offsets","default":null,"field-id":132},{"name":"equality_ids","type":["null",{"type":"array","items":"int","element-id":136}],"doc":"Equality comparison field IDs","default":null,"field-id":135},{"name":"sort_order_id","type":["null","int"],"doc":"Sort order ID","default":null,"field-id":140}]},"field-id":2}]}avro.codecdeflateformat-version2"partition-spec-id0iceberg.schema+{"type":"struct","schema-id":0,"fields":[{"id":0,"name":"status","required":true,"type":"int"},{"id":1,"name":"snapshot_id","required":false,"type":"long"},{"id":3,"name":"sequence_number","required":false,"type":"long"},{"id":4,"name":"file_sequence_number","required":false,"type":"long"},{"id":2,"name":"data_file","required":true,"type":{"type":"struct","fields":[{"id":134,"name":"content","required":true,"type":"int","doc":"Contents of the file: 0=data, 1=position deletes, 2=equality deletes"},{"id":100,"name":"file_path","required":true,"type":"string","doc":"Location URI with FS scheme"},{"id":101,"name":"file_format","required":true,"type":"string","doc":"File format name: avro, orc, or parquet"},{"id":102,"name":"partition","required":true,"type":{"type":"struct","fields":[]},"doc":"Partition data tuple, schema based on the partition spec"},{"id":103,"name":"record_count","required":true,"type":"long","doc":"Number of records in the file"},{"id":104,"name":"file_size_in_bytes","required":true,"type":"long","doc":"Total file size in bytes"},{"id":108,"name":"column_sizes","required":false,"type":{"type":"map","key-id":117,"key":"int","value-id":118,"value":"long","value-required":true},"doc":"Map of column id to total size on disk"},{"id":109,"name":"value_counts","required":false,"type":{"type":"map","key-id":119,"key":"int","value-id":120,"value":"long","value-required":true},"doc":"Map of column id to total count, including null and NaN"},{"id":110,"name":"null_value_counts","required":false,"type":{"type":"map","key-id":121,"key":"int","value-id":122,"value":"long","value-required":true},"doc":"Map of column id to null value count"},{"id":137,"name":"nan_value_counts","required":false,"type":{"type":"map","key-id":138,"key":"int","value-id":139,"value":"long","value-required":true},"doc":"Map of column id to number of NaN values in the column"},{"id":125,"name":"lower_bounds","required":false,"type":{"type":"map","key-id":126,"key":"int","value-id":127,"value":"binary","value-required":true},"doc":"Map of column id to lower bound"},{"id":128,"name":"upper_bounds","required":false,"type":{"type":"map","key-id":129,"key":"int","value-id":130,"value":"binary","value-required":true},"doc":"Map of column id to upper bound"},{"id":131,"name":"key_metadata","required":false,"type":"binary","doc":"Encryption key metadata blob"},{"id":132,"name":"split_offsets","required":false,"type":{"type":"list","element-id":133,"element":"long","element-required":true},"doc":"Splittable offsets"},{"id":135,"name":"equality_ids","required":false,"type":{"type":"list","element-id":136,"element":"int","element-required":true},"doc":"Equality comparison field IDs"},{"id":140,"name":"sort_order_id","required":false,"type":"int","doc":"Sort order ID"}]}}]}partition-spec[]contentdataOjzv&ήiM̽\[)a̲,c44&pH궻ySsnmuλݜCJRDD)EJ)EDDDDV2DF""CDH:ӵ/(>OV}ߟ?CHۅ01j͚5$!f7psm wX\[y.-X濯n͛`nO%gcn7ͷϖnqYable-@->w-Yor}vwz,} =$9_57#=!YY\U#HZ_!O+ -u{U_$'T#D r.55Ԛ?QXGf۫k H ȁ( -h 0DD'kDē$y뤣dj:EFYQש 4uڎu5lzqLmj[gX1 j6Y{5kz/_#%%kNCK^EK\z%-KWipTJ/Ic^CO!+eTW+M6J'm¯#Yc"k̄5 YÒ5YÓMZz7x$jһYwUn^z7KFuKߧ.,>z7v}nw#ލh'ߍGx7nFjLaBj>;V+,p%<-!)|V^屻ܖfrX.NŸδ?&zu߼=gD\_3i?#yVwM_UC_I9~c#F0['wL% ׿4\IOjmh D@ $@ -d@@ T@ 4@ t@ L ,NY:LjlAFfd67E2ŦfɬҦ`i'ch YS3cdAec+m\%ؖ, Y&d)0jBք,cW`eY.0,5,'g[4#d*<ԤI: %fX&ۜK?КJXzIr)wW.9㉵pXgzbϴtbdt{|Fnu]w-OmoUdS^yXEZVeME8uʸL3:.NUz];TmΜgɺi8b.τo |(@h b R rJjZz`F`f` `xpkD׈_# ^#+V#EgrGkiJ~, 0os㏬0sײpS,Yz0L'!KׅTW!K/cV`yٲ,McK, Ycұ0qN#2D_񞓖GzZ~,(O_*t18s1({ll39*ޤ;xש&@EU>Lуc$$e?u=zSLIZrjt%e,"yRvUN=p*7RkZu&ICA 86eo'椺.:j=avu{g5||6Q@@@@@@@ -r"jqu9|9՗d(ʉ2%@T5D=^F4mx5a -=hm~/d&щ+0z;I=hA]%c dK3?6Uf kݾKp.oq0v|\,a^JL0zx5y8mK*^9DZAZY'C0zbxZeUՏ'50:eD}F*F(Sr< -em0aZo' T6-~㳉4"  2  -*:`&`VpKD0zHiG1y>*!}DYBD,%ڮRd?1#"?1yxanl/;3^h2&aW`4zyFhBhM^fF'hَN3Zvě\dZ7dˮKxZqXͅvX88b|vݬpϮ75<&lz[Q}K@%$r'%-ʤd6i\W}8a3ud΋gڟ^.^]o\CVDD PP 00+` 8S\%$.Ky,yhk`je&oK8 %uLs&kIf8_i̐TKC^ؐ򐥊0n^,KMy{¬!2u]Ko Y|*ٺK?siˇ4ykD_|’ͨ?ʼn`i]j{cbXxy-v!Xcbc8YN臏[F 롇ctf]ٿʿ)I-NvHUL}KQ>e%T,(4 _m}Y*}^kM̭W[ng?K:Onq/fi> - HȀ(hXXtfM⪛$37Knu,OYIjfuMhn7o6o2dd5[u|3qe-2J֐eYj?/d!KߋXwo -, >rz/{&dr,5?uE􅻯Ym,8=^fqe}qi,qLǹ9e0SUO4l]N0Ht =ɇ8(IڧeNY+-] ¤Jёcu)u񚒖x|VkzUX -pu7@DeD<[B$DWBdD 2Uz9Q7.#թrb)%1 s]X֗6p%O NEeOMGeONh2)nE\o~WiT/N]W`SWqFwwch}E)KkzZeFǹl1uZ8g8<wxbq1wvu0޶W0z:/i^x?|䵲y͔~ZTOSxxhV]SI3Lڊ_$W(둡Ä"lcO.5e?gONAɜ9%sJ (P2'dN@ɜ9%sJ (P2'dN@ɜ9%sJ (P2'dN@ɜVz*1@ę"-'cD6UF-DVAA*OUD^Aǂљ0ֹJƂ+_T#~7ƈ}^诇թ?ѕW^߇ >[W`'.4pFD[?OGF;d0zqd޸e:-^g|XOq{Y1R#T^=59J+7zLJxdO3%NѹIʪzatomG7FyRkoeIO'{ 0:;{25`?{atv]?oshtj95!ӑ-~"BF#])eDFnjʈzO4~- -FİPzryF#2t ;Xrr)P>? -y+0߭U=7#d`e'>֛ۗ/yuj0G_s;O_fg{aiqű.gwit]O^Sন#~?%-N*4NdMGS)yՊtJyZ5#P%'.Όm-|[ۯ3dMn?|iК?3$}k W HȀ(hXXrxv'nJ|l')(s -}*fΧ-m-gnYǽL6byjY5%bF@y+7SaNX0۽+5tYgM$_U,}½߫BAM6Y}vYvkZ6¸\v[e8maw6D"ה#~QP-LEfNH+ -(E$wv{-rcxO -$D#~kMmE!~цzg]&L )'2"Y3SD_FT@݆h˄E88TJLhk:'LZR"{hy!!ϋht -E}XPtpO!?"iFǟn垟L+4"?zKFhU_,*3>ux-Elq:X['эo.1 -ݕ"5_ѣE.q*c΁хEھOoF k_O]rF]0zFO. U&KDZ8YP(["W:lhGĐ$rbhZ94i*JAFdVAdpT^PN,$*uah -+qXP[* FVku0-¦ThdDSᶚCFSa ̥pו_W<;虈ǦO=qk%Fl?6ti2u6=oMeuo{W۱-_\\\ >vYbcxKyXxYً^~nNM7Ez; [:J4ϧ)lVIgӔ4e,UJ>VGQʁO̪T7^ -T]WU3}po+y2y*ֱGj(-b'+)vTR'h D@ $@ -d@@ T@ 4@ t@ L ,NPDRbJ6dŔ'کz{AWD)1u!@?a;/#HΎh,b}~z{m׮?ܿK.]!GWruƥ50zO#k'1k~ǣaHo2mnn(z8 x8bmv.&ɏw|g~52bJTĵ5SҮ^|_):)eg<ʽ:McN[FP Cwk.ei92i|bZ)v;C9# - HȀ(i@@ -y;)Qa:%NK$;)NJ֕FvR靔2+R)t:Di{w -5hjcج -d>kSZzAMMoSQCOwQè$_"|簆D W<zHDV 2 e^QUuV9,6yq_y 1ڶ2u Όh6dtT}5Jsn^^WeБw~2 _.ׇY.amzZ:)RF<>G?C }j;zey#nS>8&.bg8%p^>6v.To϶+~ԧG׳)xN.%.xǒR"JTHXљ]L)]^A]0N3?N;d8Ӕj -ezzkq[:ؔ7vﭼƧh D@ $@ -d@@ T@ 4@ t@ L ,N(xnIҴDYA<_U5wLEv7I4mnsZ5;iKk} wGr[W+9uYWu_Zy&>/BΎG\^uy>ӦO^CH^# zw2g.r,N8\>6 sh,HCHv,ȑGIPR( )R8 pJoZY<fJQʤT}{5{ⵅ'Nx 鮶4HUmP֪Ä"lU,HG%xO$C~"-@ -זyIQ %WU db -ϖB ʉiĚ -2@r ,"3Qay~,/3ݪH =q%k%5}+}FmR]eh]K+=~ӏ:t;=+Gp8=v/a+L 3 aТGat})zF]ʳa=]:/3`t{u]TΏ׎D}'nCv?)\iNw %э 0'Nw85r"n  L9"XNљAэPὩ$H}056$.#x+0Z2J.@`}Fqm'"fJֳ>yK߆ѿz mR,e )Vp?FFo^m5>坖glX󱖸Wu}g_iDؖ)U )Oއm-=ۛV6wtʽ)tSެ*zuJTp{V]vrcKF\Ny~?:?: -JntʣS#FUyw9QS\QTMO.ɮ$tʳFU+ji///hCF_\`ygA_׾NUSO`?$bF伦ӯ46qhcՕKFGYZ˾+gm;X w**OE]rT5&*3DPt:&injn*}VqkLSnsߵW$w$i'|\ /T<N[h%ٿ%kW7nTݨQ+ѐyF_ZdF뺑aҪuAJ:X>Y_Z~3(,ҕӆf#MVhSK<药1K_:E8Z;ZVDUIFs2WrӍ*iި&K>ްҚG#7myb¸XrG%:,6.峳^. -ct߁gA*>0z0.;. ] NUΠzejzq 񚺶x\Vt~kItIѳ}0j?/ѵ`94:z+8I8UJzt~ D=hm~iG\Į sd?aɕ˫#_5ͧp$+0ڊ=ѓׯdk.a}žӦ^0;1,}ɉx'MI38pnf6u8x)!qqh_Ux`-zgwFU|NܘS Fw7߾Uq0Q߷cNGoQ, 9"1'BY=Ld-'|q9FSFDeD\UN$EeH ƍT=RWTF4եDѭ~b>bCFOkW0~ ?\޸:boL/\Jn>Iϱ~yk6pU]+0z aơ,}vTwNb>4 Ku|vV_\mubv&kyl. eb-NwYblDZx;$c]SAQ5Txۃhp,w\#aJY%xHqqJ9U\ޢLSF+ io6s9Ϭ(\ο=m'`Z H ȁ( -h 0)[:$ m6tz*{z.AӐK4$'Y{1E l7o۶뮈^UF C Jf|oyqg=/*^FL w+wtek?ui }YK68<e5M[xNX=Kׂԡ'BL @4J!k%cG C!ܾVy:U_~/.z4^3;^:E7vfch}t4a9Ѐ.nGwEرgpbv>(@h b R rJjZz`F`f` `xpJݩR4~4eDR_JNYMȇ+(II.#Bp rb s*8fg.s@V}i|+O;1#L|GBN9W0"67,-N_M <oVL[ ky7);X8_,3Ӊ?IQ0z?@Ef֣I$>GI2Mh^B<6ݷM?7&^k.ުIOy }IMGO4B\3&,{@<[&o%us$ϔQ{$ %u" %uW -DY.$Jb,$kZ9JJN7RNό~xMra_$dyzYFiXfIy9}qfN<Z&zWU{ Uw0:F 3qNjWfUˡ~Jݐ̏D}P6ny -F^u/n3Gv?IO}rʵ?@DfqfUwoQ~V -W%un-^4P  0:'Uwpex[|i#ssO=h2DeFz}tP5cM5IvJ,V^wFF6> a-Y\ 籸vڽ^HHBճSkʩ#֝0Yn at|F볯S'(u*2z ^[9E7LW oat>(]'LyF謇94*G[J$%DZ>: dtg(D5>?tS+%x FWhdti}| "ѧ^u(VxN紭XuZ|#`Ü2fba7|5}KzVks>&m.$N{Lĝ53OZ|<ѵG G2G-keGWVYN؄Nj״5k3:Itѳ0zEu[GϾ*a+GatN>MA41)9P%P5-=0#03<1JJUL)Qԗe*!څE~b#K 3쵦#LN) ?*%?4/c-2UJ2W{(p6'#!F -GoEO5n>Ru+K־њ'L<}նڽnKpNcxqy8g,3 ²jNхoPhBJܘ&N RޣJ>sEkg%lpxMkkSs-z4{gebAʜyWl}b?Hqҩxc H ȁ( -h 0JD=S$JRu?%MdI`;OYL2 II{>\}?eK=Ii<2$Q2$KSJf2a+3qs螟qEcy? l=FV Kϟp{=_җrz/,xsuvk#^qn$3簸-rUgObvqtϙO!E]}YJ2ۀdFS̕ygHpⱓOidD}+^C0j*{DjQk>_Ia=d֮G2{n.!R"G"+&eR^(#RX|2bSG 1#=O O}C")^lUZQZ{%+y̓}]NW+?{dq\##{ݱvO#cB<{}|Wjߩ@x F<FF.9NyNtFg']D]$me=AݘJCYoQ0[0zv0g֮}l H ȁ( -h 0)*hH'+RDg5XZ;H3İ$Fh -bm-' zqvt? -8t e>qm/hu[F}K_tq-ajaXSevp -ϟSIn_6tr8*VLa"+B \ٌ /nx[3Y5`Aa*꺂{,(DHªu( -D9+\BVִ#wNn1d/' 1עΩ$ ~dUC]V/WVyָ m_wWk:#wE³hs, w7ѺӊYmPqq^qv!w]z&}M5Uè̲H=c}%n|c$w`%x'(G+(eUqos5XM=aU@]T e~W٣SLUJ!Ŏ>AqOw. e H ȁ( -h 0)Ge#'YOx,^y}*(U_Siv +[5k<u&^v^>%k "^lfc"Y _]_͐0&=n+91^߲Z1ߍkZ6֥':{6'zk.∉uY<.| sf)IẬ pfSkODL DH +Sʎ$-yS/߮kשߠkrVڪW~c@vbUO˭&\J:g6EeD NYW2X'R.B7mV}ZRFLI1ʉuO!K/<#,d/~ \X #_V`}kJ{ F_Mm׃x Kk'9Z U{b9|ixt6n\L,vk~Za0ߠ7*JnGE"ģ (iR(y3eQdJ+5f[7Zp~l*C(ij(SћS汗kCWigoZm~A41)9P%P5-=0#03<%?IjP⑃%d#Ք|ReJpR'RjJ[W-<@)cJ e9@Pֹ3_C%YKt~Sq/mOU2y/C6qm+sh_WL?Y9e/XZ"ܰ/NrխO31 b-Ofqv9.IZ\Nw5CX}D׽>youD E'LO.؋|nDfU%ܲxm[u}ՉcnCkA+1f{2Oz_xr3@sUUwRʞ+UU=T6h m_=}UAa(2=hSA[o쫤F Q\Nj~8N>sQ@@@@@@@ -hd%NCI (iO%.{NRʼ=XCi{t) 9{(#~iheCY;mCqS|㟥siD^KQ?G} G<S] -\#b#\rln6|pun{QuUK_DM5>J㻟 (QfAJy8XN1]U[>k3O>(@h b R rJjZz`F`f` `xpO.?>q%/aN|^w -pmoH,zFo_*mW0UsgZ\>dYwv%Ү>$#НTQsxYJ⯬!$?J{!R*{r6+PiP-[tяrƳy;F(?ڈyaW&.5TWNH᳉4"  2  -*:`&`Vpu 2"*!R"E(OeDs$HӋ+a2bh('>T~b/%2;J{9#EyьFɳm_IEFteF'6}#u-H8C0W50:;zc:atwm)ke7atG7|*esmu)xm]\>e00z(Se7z;W&L0=> -JNl H ȁ( -h 0)0OmD$R")# z!Y Rڙhn,#j?1UVw>',oFז."}q_sU'#?b,_oF4mQ1egr`&-/AkW[;n[by{1FRӑ]zNz`?&=Q!a]9}ZeשIx꺭D}+^D H)'L0v' 7ؿcxl9 S` r8.%ʴJʯ jخѭO}Zdh91w'0Fs/@=2Zd1R dtQk"|9<6]FD%D<[J$kF ʼ2-!i*%1ͥ ˉ֎r´(%\IKJWuFav(hF/<χ#Vݧ2k:ić(߻ky,.3f7w[j9iyBjx866 -;8/gqlks6oLGT$/Xadtr˟&!!ͪ{ kSjx QdtF12Yx0\δ0*cdtdtkgGd4fq2ݎF -UwR)Q :hŕ}_BhtRbm)!LraQysń+{$b-V2yn=F?h;dؗvV80:Ff5: {F?뾲ֺmrYq v Vv |Nøq.[SFϡn_wda -X03h!Qc0Nܬj W}<^]SO诙X.,3*m.݀pQ8AEPߠI?z$Yr J^XW\U2QNΏf}3;Gid&֪UGNq;͋NY.(Mqgt?i,Stǝ|;R;9Xq&.)ƶ e恃)p[LW\nJK$ ̓cEݹy4Uwj}uЗ\0lLC_7?YޱXmFG{wn:$>AZMva(%q@ -d@@ T@ 4@ t@ L , -lXx!.RX ,Re-R5~,9 5{y`%nsvv:qRiʣ6OEJyZiAߞJfltE{zGT&OiǦ={J?*怕/clF*-p̷VH9l!ȥ;=iIx!s.'ޞJI$DHǺ00H ->aﬥqG;Dڼ~*$ݣDQR*ۆTUQ6.OtgCI3 ݴGb5_'oGMa.\!gF&4F@@@@@TO-=DC'z(NUi!D[KteD?K  T}7GbZaqgTEEXaIw@M[C"BsaO ڞwYPjefv*%/1>yvԕ/6P%eӞORț"\Gyʉ$;2].lҖ-ۨE,iXSCOeQǩ8+WlpX;> -E7$G u۞2vH3Z%-V8?76SE7I:*N:']T8UoM^GUnYA? WPk:S6\G*_%kYԣ. Fr3-ћ.>k4Ȃ8ve~3r+PwL蓢~c~}'8'2:ӓ $Rqy-' |P%*d9)+9'0t#v~9qdh#l}LжVŸ -w`rQ$q@ -d@@ T@ 4@ t@ L , -lXજꉴp{b,CT'_5aѝIb⎩@,=WOͻ ۳pc H%v/;/M˙ gD=IW䯏"S&^$;9J.vA.XwqQљsxf ]ɳMΜŧ'.%Nt$j*SRS"mijO$%⎩f*;Tu=>}g}qJT/n]6GSzlm(\I`RS4sZ{`ō4]l([r~j3z2XnfymUaUݰGP͐l+cbfZhXmK`r!PT许:R1&ѷGUL襕ıX vLeFܑŖ@ўXp@щA+cyTAX=X'x+/^mbBrz/,HeS)N;&K 7^>NtR"kh}GPtHN*XCPtIqk\)h_.y5Y{-PIcsRSIss[DCѭP-].,Ha}Ó"x `@PP 00 pp2*RY`D+7Qe}h/UWnjK7R]^(zrH2jY1/l^Fʍn| -cS>_EҞɔoތFG{>#g}=tݎ@ѯ$r4gXG8e߅Ir]0\+E{8aBv˝dOw'BMHkw{P28{<n+ 兯Evzlu3h7TȼG73tZh̫U||,+~i$8zvjնD2zA!$q@ -d@@ T@ 4@ t@ L , -lXઐk2 %% X4C]f$C02݁%%%d8cGp >!\ΘR?b%\<ƾ[{_n"(iAO+v߬k^ *0tC+c~pU_avgk -ݛg lv=$;&$Biv't^}g<]{**s5e/q*zIyIT~${roByb_sޖ*Y?5}8RS81i^-ְ8R6a5eVQ$BgO# R rJjZz`F`f`V`@_*Rr*/A !S -죚lj~&:}\\N-a? R*l3U1*(d'~c<yZz]_ECb޵2-R,soh\(sPd'ps\K6"Va']diiH? B1OnρGV\up#=b_EM:~'ѕ%{%_yXk1#z\&uz6U > -C[T6rȊ/UnR+Qd(Y[']n>jDmuȓK'gv5ugԃlm7nճw_^qM|EWp~ {k]}o"Orf(8F"gimq9 )Dq>dcOa>OoʪI9ŽxrzZRzx"c(:;.ψ+Ffk;-Їv; +=fj-$V_!b]FC 懄k?L`^"7qG)9P%P5-=0#03+`x /  -!{Y0D-!o'ʃ! -$궝D3h;"ڝAo;1g"pen7Uގ׵;IM# ߍJjKSV0J{ j~8]-i^6r]S?E+J ~1@GrOjzv&ήiM̽XY,b1Fc>y@gg궳}IH=SPJ)""RDH)H)"X)E)"EĊ)PLך^Uv] -m\)>]t[GȪU:V^w>oqy~w~v\}oK,߶$[:LtK,kѷD|opϯܿ߯[9v[8qoʑo8w\-N!)³vqn=987⒬teKr:T)O:yO"ٔII~FWRHq=LdwS#"sT2LTUC" %ھt&{nSsmH9}2NSc <~X^Kفs]ٲ**4Uxx7EAH - 9 -RCH = @ 3dXxHĿnU*fUTJT:磲J*QE*T5[FuQmvR/@WtQCS'TWJ[Nl?F(?VNQ?J{nY%/Rj,߲J"X|% ~V&g_,R? R_4W+]>*Z:&J2U&L9+]U]U<ŧ45{FFiz엋O3u -~SX:|?>~eiVi}Ӹŧani#Mm͑ʭ27pMf׿ W ߱Bt)qx,-$nqn>fwzb'N7Ht͡rIQ;$7o/=vȺk:5By*&U##)k4/F Ԟw6x CƜf]N9 ]xtS8PMWAtN?b(HI!$RAjHi!!d q E3Atg8@:^I :@eT竤Jh95) -jʨԚTDiXFO OS׃D }}$ 7 e-}۵ 01gߺH߳MC9937rm<F Hb=6wYxے-|\=팳c/'>SG@I\I74KYu:w 扢,Q6xHU1Sb5#ڹ3uW|'zt1PX -11 J}glq7a N.A/ɣDA[( -@RH!T@ZHh(2@F!+BCtD;@DVL5-Dv(GAƚD4D;HtSZ)ksa{nt?ah?Y9',_ |UAABeW- ؅ \Xe8B2p~Ǐ\E5ӽWmLο8V0IgRl|ʼnnwZ\+lI'4 :m]8|;}{EDܓNDn>KRe>tu5{Oms }km)u_i4? (=j>FDbtąȍw 1P$ C -H 5CP d 2CV8"eTRt?Qy+"w F02Cn[JES|95SS]JG|EjK)_thXA#CMDGAwtID}SA!KaN^7_k3@?Ѫ{o? 23yG>ˮ0cl,Kq(xK'YظĸX=.IoaG ս4jtKltT^9I/R)QygRuM4Cm3kSx1?34zSۋMOdSv+RNʍ[їw 1P$ C -H 5CP d 2CV8'T=Od|**7RdTSjSn.s>=;YcFjjuC6~D_ ->}UkB- "^)n[D$ZD }c ~ ƿD_{nN7ON3Wŭ0B|bb`q.]ݕzx9 wN2T=əLNIK{%y6}*MOէ[t&W1bxOL&u趍t1 un^3Yݔn|N*={WjT2UE9*PyF%UOUT]J-**ָ}6@ c~jB6gS>;3*%,ѷ] h kQ#:niv ݓa-DZ2&gӥd{~oij-7!twAtZI+.txeQ4;W.$[b6>ɝksعHts [ܹkFTPIkG2z MP qjfk4ŽD{]Q~~=ѭbj]C8c:^暶g/e[{yf۹:JJt`URZF"vkã2^xtcA"Ϩ/ ;WcC#ԣk4!N%[хg7 )ѹ'gr6AW+AQ6:uφias@t.*Atc1M;sN]r*˩|*˨rTTAҺ2D\5\S>jAn P -ԝ/&!mye]ѶuE`葰e}KgAO21f}DTLt17YPGXaD;b.<:@lLh|J_:f8O5]q:i[R䝹*EmJ;.UeVOFj2濤xkb \e8S߼լ0Y~}\I9?)(.lAflAflAflAflAflAflAf.$K.F"](+רK/kGj -!8'7ȍErӘ\$N&97.ȅj"AJiJ ǔ R:Ai_?]x )WL7IJQKOҘݨi&8/P=2G&yF5(Zarx\!cQbZ8Ò\')wkQ7ߊ$4|G5g[Hw~%B81BxR_LP?{fD;]'9@LF9fF)%jM=ʪ|j*omYL둤#IG^$Iz=z$H둤#IG^$Iz=z$H둤#IG^$Iz=z$H둤#In!FN#IIY1LVPM#|rvR}j>k)m.VdnIY$馊W>;{ A߸gmt[bZ -܆ܧQ S~]$ݵyev6N}Z~I+XJʨ X$] g˨:G5s6S@Ht؁2feqǑ;AtW҈ _ v/-I O7l @aDgᲦZo)51nYL/oD178v#IׯXq1os$:YKgqbݱvgu=DT)3_&Ϩ{"KRC7F(ҟX_ܑ3&:Y޻ē7%z6, ~ >Y!u+wW6.N'vGwy[HN&qn' -' R/]2R&xtj<:/ݷ=$ztۻA=!UGgKPY;s{Oco~Ļ6z~fCLFkaVjm=ML)Uzd4xt(?[E ]x7EAH - 9 -RCH = @ 3dXxHa vx4OSʩAO5c~m.RZ>?5L?5~jó ~=Kq:,ъ ёq؟ZGc ;G5H\X i-g=Ck.j;1=q-~)xtVG';DѼ`qŻ,NlN|; -щ4wdݑ¤^$Q #_<ՓD1=C̓QսF#hg?c513;s(nbz?Rszܢ:ukP0H $dR@JH! tb dLB,A<$@a>%"/xlY"A;2GTS%D%Y/ΗD[;aKM;)wku)avn;?eI k_ aAJ #0?"paJ7u!`,?V_VBw=d"̿I[~z37?*0Zs5Ļl'bc휂%p"ss6恓>=ʱ4¤V=K&($D:PSX2~ҳN?3Hsar&9z]Ð^Zo?}gk1 816kz b=_' ;spSل?8f|leH $dR@JH! tb dLB,A<$@YNL$Dz|3M`9(S~QL*z7/RDSv31vdL ;JTa`2 r.HrK;C?"A B -e̿]˚{.5fczX.},[?>qQ{`c4hu+e֕hX!ޒ%Zx[q+;\Dd;g"g[}jۢ#%O@Zs+O?Mek^U{zv4moܬ/ێʣbO ;ݷysfk?93,W3 Bޛ܅m(E$@QB2H) %ԐB:HEC12B& Y! Z%Vi -+^-o(V+V՚a6`յZV0b5YLy͕Vk\/Z-^$ 7{>8.(e AxCP6=ˠt/E`goĘ|vq1% -bNY+܃+->mmNk9,-K <by Eƺ7I.(l;]ވuN*'_";qRʨMQޜo Gmع8D׼P/( -@RH!T@ZHh(2@F!+BCDdV:ZVʫm|UeVTN്5tզ~YY>s0.R6I_Z% Rʄ]^NӶ/,o] ->!(V ?x$}FJ\W2pkxZ`pQt;;,6q$ s}v?$C!tS|" -z;;2 I9pm؞)HЧʆC_Vwݱ$H4$aԖS(|_AʬrSLՕew˩TRFHG);?@2 ]î`}!jtT !\GזNX߽x4-xU߹hqH yI:Yݩ̭~5|J`T0qD q`Ot-n-OG#DCWF?,~2uc4jq' K-Y,pI)5B*,2(N?UhATH4j_Pwz9 +ġ!>H2l^Έo])jD 6ƃ7u鎱B-/sZx _,S}/xF"0ꎮ4fbN6t&ZX>v:-vyq!׵+Y :DgL }<D@t* -D葷@t~^9%]}Zb :D7'O6qz*@>{N~*.zxt5Se@tu)ՀOmWUS}BCj=Զx|q zGR*?ïvE=mx>Mu]Xsa2= E.D}o -WWھ'aMt,n{,RwR|%%Y<'/ /ՔQ}АtqKe"lg}T/[Zӵ4G_$Z ji#.HU%z`o1ԽK}DqܺE,wxW, :U҈ND6b=:,Dk{xsP (|Z# N2Q'U2qҚVVs3񊆴xeZU5>^U/ѵ菌ʢ4G,[L VsAߍ'\ m6;Îc9X;Îc9X;Îc9X;Îc9X;Îc9X;Îc9X;ÎcExLf$]iRݦl0nަ5MY&qq6S\}6groSozLn KςJ_]R-3>PG#^ R@mAJRz2(Kn$|_XiaN16vGW WO)xI:ٜx{l\'g^#SN.t(#mt6w[>nVn͑9qt(tZMk۷%蚟Nẉ6nrm@nNIX\.yIڗ =E|7OegQgS^+{Q7ۅ؇$*X_0gմ8tARʵʩ0Q};D3a>{;i8$T柂Dvx9@DߚY;ה 6. -?,}gv.wYlx7>r3lΤDw{>5zoD=E$ǫ{4sb,U"% 9yU3sD|&أD[j]ݟ; Ow:_&:~(aW{E7aݎjH $dR@JH! tb dLB,A<$@qO$MDOdDDrm?  DsĆfbOxĜDup} qMdI"hx}W}xO:c y9R\ses}~ocbL}w -ccn\qo0nAlKAp/\ll"kO -T_4虿{L1{j't>Jf`ՏRy(}Ty8_}Ɓ50 }}7vIta֨F֨vD.vlGaPp% ,^J{0'R zq Q݌&K z]RjEQ_[Zf7x/m;K%NӜ+.#ڴD) ; o,c :2gN#X[,ob-lvDžx<@tJ"|97{7@tvA"8*n0M z(./tyhhq޵bg Y :{ibD%=pݔ;^4[* `'ʩ2#@U5T秚rm'^}4MqP5N qx r(a_ʠQYGGa"XCOygZ@YD5T1UfbMLu G$d1& -]bnyn6!lMV2 >oD[x3EAH - 9 -RCH = @ 3dXxH.G$}D:֋-^bGv=#-bHx>wJFStpBwh 7D(樲ݱ[yph m Ι{7 MgQ B5{c{ǻ(-v4B\kAAΩr*?+(JXF\E7# Q>j]_%kzG\XBw^zYjR,O;ꝥf ݍK2z{x7_.ݍ#8諾XF?dѹsYQ+hmC;Dų;cw ,؎cB"ƹBnK {ehB^''r2{B>ET vuelYFU=r0At(A?2d15/ yO#1}YcEݙψ]ǒ+'TRPN*k(Tueet:Bwj?a21ܔ oYNzK1"G_j2z9|40ԫ*,gÞaH|X'_1@x1ŝ?J9榖<+tngbl+Yą0 3Y79x;~g,E ;W]=n^I%bX1xtsn*Gg.;S/4m+sӔ{=Ü$(3#&Ce$enUGԭ]`Td+m/ZHsxq&XD%u;,D Otul|ZݓBγ$jD>ikdEg -<(j|Dqj>@ԕ7k:R[p0A;L=Ss/} ;|p)%eGI_>"!$ArH)!4Az( 2Af -($YJU?(zX1r:R7>~H{A]Z2ї<$yؐi!sCчرd=$T>?wq~4rҲ#CUO;An{V˿Z]č:obnKb*BNTQYA]TRIp6Sݑ -/\QWgǑԊ_cg.@ڗ#X-_։٢b>3KM]v= Z͛g8&OkWqI.Yb/XSD7:CdْSg_E\Rl]8l@D.![7 [G.DN~ٺM>#KW~pOtm熘ܝnC(uֳbWXqd7%} z+5RF%^*Q[P-]a>j*^Smj}T?]F M><u^)5QkAxj|/*Bq@2afB  K%|Dw 7jl]UV [}m牢 GѣG+u(7.GO/qÞsKݧ>D7o@~KHn?/˗.zt9?37͝C5+mv"v%G,'cY8.6.>O*{ v"&:ՓFjϞ&q"->(!^Gu3iΟ3D;-:3qgOL%osCS:X3Jآs+"|׉0ͅemH $dR@JH! tb dLB,A<$@!D%W(jOe J%DS x^%bcXIb!F|){{Y'WKW},ɢo ;@]Elab&-~Ol#C& h&|J$9,v։2O8=nq=,˝!Ztg[MzdKv%xKPF#t c%)sT2LTwlP5ԽenN_xtFxamGz\Τ8gsWРvpv\n'ޞ$Ѿ?|i6F3>r0YDD:F*wyǨ"GD2EՅi#!N5鋷=& caw11slbm Ļn §!H!tDB I )$RB*H i -P4 #d̐b!!tr'TuiF'5wyD12Q%N$~/}_;a;Ĝ2œ%\c'se -E_mo^EqJ! ۻ!Nc\~i6}Й诏/2,'Ǩegnh>}Jۍn'h`OtW;,IB@v8DN}+V?LCwBw?*KI*=>szS6E(NO{x^*LХ2wQFO]ɩC-~*/d.r(1&ѣ>#tTW]jSc2Ԝ֒rΗQnx-/qƶ6n}SнUnP+ .ݾN]=sO?Q!{e] ڧ[i c\L8bYwڜ[l`vMyw3NcL=)~?'wF=etK8#bXV - ;F;AS(ǞLЧ7TF@t zn8c[<19W]tr z' iTҌ -*Q<h?Ue2I PmV9e~jA?5tr}t^DwD,s@t u߾/07v::11)hq%v 8,.goqb2cqho6]^-/{(EGiToIګDhem~z*~?'g:4K6'|=ߣk'ƌx+*lYbn, [rSO&|70>tfao&b(HI!$RAjHi!!d q E{d$P?gEkDQ5$DѦ$>^%G 'yX ;2 ?G>2a/ 0'VF}i-q:Evtve ceeښcE{gc/B3 -vOgK/6 >Y8˳6{B^j{*Mh` -kx[?+^^ ϧD(f+r|3QU,v]Ǐ]Ca>m 1C܆!q[Y/Px\qLIoUO?/x -4$PN3eT!tzetC)v E 42ݰŦ(X)-.bą/[UU YE#KJ~{mmq#cq ; -'7>uw 1P$ C -H 5CP d 2CV8n8O$]sD2OdyD>Qey#Dh]y~(<1HLsĜ>K =K’%QJCq3聫ȧX5wAmg_&Ta*+DRˊ p8^R]zݸnNH/^봡.]b(&.%M.&bq0c١6&1y| 6ր<ңy}-m#V}$vеP7< ~h*g4ۧ(kMю4!՞fmб<͹,bFO%sxO/^rYLgZ4ybw;nx׋{eq -o-8 * eㅽ2E0% w|IxLۘY7RY5g mlzL̞j%Uֽ!6&iJABÑd `@,`āxJ @ @ $+ eE;PB"k-mRV񙵝sEHo'~|s"2o'SK!=EZ;YWH|T$MGdS7Ed,P`n!uFTt&BgW,3gѣc63wCѶ(zlZkHsyi/5Mu8-_]4v0utJ[j=;3N/|E➇:u-:PGw6ߢ?)^ܕL7\JI9#LuR7I{G&S d T s GI>$yqj٣0܂ݎ^Ky.ʕ+mJE?Rz\#u_¢G6A[([6&vg'1 [pd,viMp]in}Zg L3mg?]Q(#xt9(uuI972Հ4Ҿ'S=2mþt]}c%isޡLc;d -Aѝ9Ҭb=5ZDI~r ZStjKe@ !:8;C̔T. vB]xJBq1ܳء [v$"t#s6*Jz7*Gw|^}C[Օ'] Fm;v 0if$ `<F4ή~CbvY1 ;fa,옅cvŽY1 ;fa,옅cvŽY1 ;fa,옅cvŽY1 ;f;aZ&nD(4NjDp^h5 -~zZSJ'(}KA(z`~̃(kosI%vpW6ne+L{̑*-Ƭl'PtcPjr?KEF#ŹSl"f$OYl|F%M9L1ٙWo£G%?x~T ]+9<qg7Pzi=EJ/? ? >pr;E+?ί6?))aG\xtӼGwBѻPo tj$@2(.ΣJѵ3{j}ODEԈ뻷r9r/ ;-ƃWVs۲iRsϯ.*z瓨Bnh=ƗZv[p\ -7E>Œ":2Sisܮw]Yj4]^& NWJm(.'lO@{SF~GྠL5]>%"]ji z\??'ֿ[ƪ^"Smwܒ -%<Ef/s_ $*x61r( -$5ЃD`F`f<]OW=4ey,$ՁsTFڵ踈{̫I?D?:n٣/Gl|#%WYٖ<$ḅ-S# IԷc7AџStfF`wvZlqd -%#O8>M7=6Ѐ(ifc/zt79f\3_{T`tVAѕ?[kreڸdJjgXiGrr;,m~xx:;f#&seȶ?Α_)) dFG0ծU>Nfݯ;_uycbm8h.7s{SI5s]/d‹O ,8@ T $00$`D,%K̊r)nUnV,uk٭;7!۫nc067f%͸nŏe mnquӢ[0b7LW?ŊIgt6j/XO/͵$3COb3 j!g -zBƪ恲?ZOjzv&ήiMdͽX[sϐƈcL?n~gu[)EJ)RDDD -!HiŊ+"QDDD>Sfw5cK}?y(͒^{YjUmp|_p&_npC{ns狿N{7hur|Ú7XoSouC8ZG_L7p _\<;8y<~;E?OdUdi^ÙOϒzs'cKiڵg(6]հd=o0R Vv<8Od{oYxwveFd%PҴ>>81~{"ӡ;d /ve g!!"hxO*| jb bA9P%P5-=0xl<O*f*1HqU$kR$X%kH"mQRH_\IůJ&ciJ+\"D%B$&DbG%ޕKWI(V-*WWqW+BJ8g2+WiïtW+CUo{+VhVY*VJU" ~,Cw,QwcߵƠΟ/7?NpTS+~q9nݨF~7W%yw k&v 1WsƪIXC<{74O;RNt .㭼/u%~grC[6?(=(%{[ -h$Y|4RզV=2GFݒykG2^bt&H29zc| s[ƶd:0c-x,3&~F'?ftˆXd@@ T@ 4@ t@ $#03 F(.$YZٕnj -j_cUںF+I"CZdtjEA2ɜSC*  OΊjtlh`"laF,ٯ-ha4F5z:lEN =6zmo[эC'}M3{yF Fc]T`-XWѼ=| -'g\d%;RF6KF<dbj5£0: =.2:-Ry)dtm/2:՚vf{cB]ϸ~dVdtf2:zqyECu$ Ԓ2etn5 -H'*H5[Nj)'m.'}y FVqLY0@9Yjd˩$gIh ;:lFDž^5? k?%-.Fχ0? =6e=81*`fd13zk<ˏU=oVn!řyѾZ9ǞNΛxG޹1&f}bCyW:WD]|BRH} bԭWISww]]9We|pM zfL]]Yr؎4&~dk 3%Qu޿𓏯& PP  L ,8-Em$BTwV4n%mVe~, ]YXL9{Y'lw״m$td8}vE0zuFat]菢V7jߖa߽Ī[]ԈR?K2ѿ.X Fo}$z\"ouy+~Nqp>Pu7W.e*TAq5}dGPuoF=Que![PulV:v$g ?޺1a`15Tmo/F=щU 歨붢:|.M$k" 2}GHӂ{ -}4:f}G -JѨCd =@jF`42zUKب}9a٨ -Ҍv- l>ћFE0/0KN^3 FF`Z"jy|Ds[=v*$GZu;>D0:bdtFg%SaG.ѥ0i}hF}Л0:mpFOܗO'P?c|rc‘zlHy0:FO F`<}ĉ3hto47DJI"%gվG`tgt E FxL!2TEq]0XYQ>;lkr'*.ۍq9~O5zMN[qO%4 -{+c>6l:Dby?Rdn.V_i;GVxHfh;/P3aSDS{1RvsEm=0 ]y@5w ]y@5w ]y@5w ]y@5w ]y@5w ]y@5w ]y@5eE|ήᕃvUkx]7i<|፽vS;o-I^t/-lkVҮ_ZI Ke]K hs"X!li]ߩzdXI_֛ Kw\yRJ5|_8J`C7rH -9 #֯V7YNew8)^.Bv_&nn(r` -;'I1riNE%ُ-V{d%FW709wc݂̽ w-C֯3B^DoAfܝ:;J;$9rV1PM~nr 8rմۋܝJ;TM2"jTnbi+Hb~'-j꣮vF.YKݯ/Qsðk#}K詨e==h㷻`A%[s7>J~Ă8 rJjZz`  x |{`tjI>r)BAR Yw'KQ!ne$L߃J:+Fzst$_ FWaGVy"URvjji1޸@:*鳓EVf+Yh1uXvo97vF^4['pb3F*$Ys!F߽HټER5=Sͨ R_&MCm)g^o*C# _|L^{1[F&Vہ?y]$̗:T𓏯& PP  L ,8E).oAFo&E  Md$}0ڕIƚ 2ͥyx Y3Ȗ4$m!qdV~hZbF30ǖ_qW]7G٨FsFrUaŌ{,2; 7eZW>KvYEYൺ}H.;3SZؼ1-{z?j$١T# ʦmH|Z}O?ٟ{2ɐհ> % -"S؛9A=݌m[M {o̪#`-gs=Z"T3Hʎ HVEitPuƀ-dL]V5U7TI|F% !T]#QFaUQ3e뗖UG.^sUw,2yVgF']Ҟ1.AͣVNHɂ!#vmyfF}}t)6. }`-ُb%ك 0J - -E3G=1g̜D|7]ӥnmo:\Khx +H> L9)m2vF$NU = ץdI'-VSꣂrH,F`atPs>j`~}؋ґm?h_Kr|rU;cw[]v}\) -V@2zl2L> 0zqd` n@FN;ѥEuQ Й>~fc¡9q6LM50zCz"uF@F_ ~#Z@ [WX ȁ( -hă`&``D]#0 -Yhج, -+@tH;VNJ# m`%TiMA4VIܤ2 ępF͜^mfϘSu?s9ؑEM0_‰UymD}3kt΢Ѳi5 VDFOLʕ3Q;Dou*~T^ۚ&:(4+}%~=lOI1.'H'e[UW\gow^~+U*hۿx1< ަ+mCSz8gҏ>q `A,2  -*:X p6\u,zyƏ)S_^3s6z]:} יҮ7_g-z|BĂ- F݋Ö,]T7^ K]4֯`@aK,Gl-y"K/jbi\if-'R6 w_^ͳ}"ǵҞ=n㰊>'S_J'F:QT!nA*c^ܝ.Cv=-;܄J)nta tw`)z㖢$L1znomؘ[7Ƥ'Rۑ=%6~ -t᫨yRT3XIÍ;XE*Wb_)s"wCA -nz_O؇ElBoܭ"/τΧ-d(b{O[Ѿz^xZ&wO>Xd@@ T@ 4@ t@ $#03 OB%=xhC%Jzqh'i7.I -2YX:qPI?@T͕$aqOҌ]Lןu%zY^N`͕[ղ˟쵊izd=ntr֯I*&NQH.+ &پ)dRVvU͓_Ү{֌vߍߟkK2 ]01al]o0^s݌=Hv=Dbكgp:0]_MJt{H^Eڑ*5 V!oa\%dJ"sy,!V7]M|C*뫖[3&j}huTu4aiF;'*"aH0z_[·_bo\ ًK[aݻsSNhup>U܉`M)n1sтG`t{P:mu ן`9!TuZ .D]:mó/3i(Pw /=㨺G^CF?vUw73XuO露JH>QJXFF -kVto_\VN22"'ɂݖ@FW I.օ_gD5]9f}kiU{ 瘣z[frHݓrRѿrFjYnP`/z%Fdϟ -3v]v;m'vS"f S+~ ݝy}0=>yr>MGFNf> -mM7$&]_:1ajg~=|M+ب}rfҌvǽگEx":s̕d3uFGUٷ.v~6՟MT+# Nt*؝+dvi2A®(2PF7IFiwLݛQu߆]R՝s6>FڑC՝{}|ߋ||=P%{w=w/NѓՒ84&HqjH;=D2Tc!RMI]&Uvts0j2Ԑ9Dj֐mpqpo&0Db_'Fo]X'R}m1QuG7k"F"2>HPusűCMM`~m@ӾҞ1'Bb+t%hёlM~Ϟ(xNyƜ憲W$Kvڡ: ;}`EFz̝0-}:msv%N2>>PƄA1F}]nzFϼiTizPqgjb bA9P%P5-=0xl<>5koUw(Drb.HF]$ ^kg+I7>zUwBF"CdCPA h=U׺/qe,їFxX{,j=Če'V?Yw>٫}~gK;ŕ*r؅dtѾDNrdOhmݫ}|lL׫^TfinBw(*eFJrL=JSugps:K2M)LS{gy`+O7ן+zEcq"Y18f,Ecq"Y18f,Ecq"Y18f,Ec#seg Wn\ݴ\!\z@n;4,7[䶴ss6z8"_ͨ;A|biQK5aKÖ~7W'}{Ii<χ-4jogbmaKύ[s۩YrܯN_%XjW~_%~[`D*q. ZY=buq$zN}"ڲaoy"wNRhF"w |SV+ݚ[yQIN{pr$}I7&=3N 'Sa4>i5Tn9QILծŵU U´*RΡ֯kHSIڲ rCǦ,[#v]g_'fO+b1+/E?mX먺[iM#mDՃO$8wʩǁkkk9흅LL ;S^Gql]h!)Z5@G_"U]WI0{ѕ}x>s*Ñ_Əܔ0PscsH-3Wcݩ1"ޯ'<>ā'K~Ă8 rJjZz`  x 7N"YhEʎ;I՚E#٤i&mwfwdtV2fu+Ȃ泈$~z+ :u+Rj>/)2:ѷFXjUr2LJNZ0>!yvmK`+LIf9>ng;Swvnw]WKGJlC=[[PLquMi@B) -vECojlQwͽE#}WC_m}__"SCsWk=Yu2*yUw$4)&1%w1 Ȁ(h@Ϙ#X |W{;;/zݨ7,h'Vݗfbf-7їrwo pH`/0G;~/pV(Vc&~bwDz<TuO i2A=Uw=IVUwQuJ7C=SN%Z Oؘ0XUӟC=׈p ICD.1g.E}@T~THTYǞH-# +Ȉڔ 3~$*.'Sy`G5 }m:Ҍv-ag_ԕu}s8OZc0ڼuv XC7Qu+^d_ Ky)NF^kݕl>'Nk?%64<0/}tt.S)vx eۍ$+ѓS)] -'`togjhT' ce{7%dlT; atSnѭ->,땞1o=f4Xd@@ T@ 4@ t@ $#03 Fс H9)`]A *I_AɅ ᾩCdi$$W 3@BG%=K;}Fݫ}NAhvFUݎ%uG~hg<=܉F/hf/>cm4danKWN]V%XbhQq}e{5ɺ/kc^v ~96#.f^|uwzbu74hTW#g)3%$[t)yyjiW`l&Ł.E',q@@@@@A00 @"T9S4ؕMj]=ad۵v]]2ɺݜ4Y37p,mu1NhMLm_u޸.saiːiI~J,m[zvgaKQ_laO拏XjpLË]=>S"5nI:]NUHڭn^zDg2w>y3&Fj-,db:"wg~qrwDh?_[sje0[QF%(YF%=ٜdH=xc|+v 1׿-zy6`7qSt;'=c>XI㫉,q@@@@@A00 @"8|C$˔1Fl x4!#w;C"C]md"H!_I\ttrw$x#U熍$jzFJK25wop1/臿r-9fS *sYk;rO7 \iFx-$&[}v*8 nf|ql&Xd@@ T@ 4@ t@ $#03 F)ntoG$H F%]RIPIg?t1>4޸qklKn9$1#tꮑHOSD0_V&_6lݚNWٟۤL&DFgu gE%S4] OVw X9xRtx#8pFPvb 9](j7'`tF]髕i%Nsndt:xFْonя{SB}>`62:q߿cyƇ>!sgC$v:]yJ] Tedi!H_\k=!0:#H*"H-'%|yKԌ|ԌG8H}t&^boD굨UrG'ʾm܅޸qѻW7@F F_&>U9'%nGs`t譅A&fonF͓N=>xw4ֻ`0:FwHe]= #ՖS{ޘ7o%MzFOIFHEFn_]Aաbo )QmFI݂{UwFZHSIM& -y,dK WP⣆d:'3瘙]9Qr'7ͨ}dTvUqͬrjW})^} ߽F~J~ﶋn^IvpV?9S|bjHԡyF7?;K`t4kEdt0zzۥYm:ѹ0$,ژ0ߘ#9:"D*}F Kѡ<aV2=D դ)EFאUd]05dʁѣW3:FJ8k¨8~9Fw^4%:񝝟Lɏy9\~b}/^[8~w }א+kϿLdWŒ91c[.C:znk2r^>}%[vvJ rHa0v}t}dGꕪ~u 2$}$nLѵ/ѣκ7`tGXMbghds\FI;;Qu&AT5դGFVƚBdLE^C^= w7$>%,q2yQ3epͪ裃˸F.Z>K4ZI M7ކO/ft ˱ y.2޵0l;ks4s'ڻ֕C\v"aߝ$fwE56:6^Fk$`i{}̯EHU#dg}DIv7h3{7^]#9u[^nU\V\^Kri -ONL:.wxSO_rs_RF>>:w72:2zlE2:"TGzZT݅CY#uڶ^at$G6/|}cD#UMyvdtӶ=ĥK]#H<8_M `A,2  -*:X pq+)d٨>:DJ$½5*6T.2,T{X>mٲ'UfTUK3QwǼAӘLhԌ:xFO3ZNsFQubF_Tα)sGTӈ6 -3'])vQu [:' ->?t;Oݫm>|'ft^&v{YS?gj{~$+/^;}=WތW~? -61~_fHSƧos% >vqY9&RsR[K7GBѻ_ҏ>q `A,2  -*:X pqyrA.3ECpzuSS(hrE0t;"NT [q| Lbs҆–Rcҫ–Z"X[jI»~T?-߰%^Y1AWl:q@НF[F+Z8V?gz yIR^7*SDOyxxܭ;PĤmF[rw[܊-A#wgB6rmصޗ^btݹ=I }}>c mhnhfnun;>nԱWX ȁ( -hă`&``Dp\CU&V6VAjR IYE*4j4JT՟nKA2$KMl w +I@}Kܼysԍ!煍|w98K[к?^+uPAqAU|eUjԨ5AҢ^eV>/H΅J2T eFm9"[ά$~_Z=ćNFs.Q/gKœzʊ>tgk"=Fr\uaW.Y4D%ȳ:Ǘwݎɣ+mZtxiO]vX)N}ȯsC4qAL=mjF\*Y!o(:Ձ աzQS^ymz]#g?8'ٜ9ۯ0<ٵ{ܑY?XE!w&2un `A,2  -*:X pqyqY&YIoR^LUwפI7it&er|4LAd5F/zL|Bebe瘰1aK쨏7-Gbei9^nvEWÖf-O[[*`鷗shb-+T,`re +<ܕ]i|K.QX&EyՑB9N_u\!mȜ<vURLGLH5L`㤜h"U[j .}wi^6?TҎ~YsˆO>󛄞:l&R͌yjfYJiǏ2X`O>Xd@@ T@ 4@ t@ $#03 L2q2<.hzQNN1wu{i־z&}$c{㌱}1Ø-S-=󓓌P;Ɉ2K]yܕG84Y_H\Q/!w~!jFnnsz5Uً~JkQI?нҶX'pwyeNpX}^]tNޝ0j ZUQRlTҝwF%}d4mr9c-znjMjvʋy%rn4mL88~TJ&KZtA~ƙw?^- 7} 7έ$yw%)+lZJ.qj%jM7#p>8TI\z\Tғ6<4dN~ ꕨF򷵟f~rf"N5?xT0:wZ/$z]NjR9X괛ngHWEcS&m6m6{FϸEQ,mVI[Q)봃ҕO$釟H2yt}|]"iH#Co<4=ĵy&7dh+)%Iտx[EJم FW&HfYI"& $f:!ꡉ"294p뉨O6EM0'fFkf-Mz$ȱiW"/Ȫ/`vؓ}vךtYI7}^{dwk(nUQ3u0zDb^`tAk6(0V2znjtٵښٟA5/ݘ08&=0.없J&MybwA7"Yn!R !RMD iJѽ.L%5dn"K!>vUl% Hz!.:xe29~.ycQ^%ꁑ3ѿ|g}(u{QzJ3Q~r)KDo#_T'd֯&S|=NT-`t4|!sjeGy]u:3{NK24xzcBLJmd>$]YFOg<=R0muA% HZA:骨 -RH]Aj<@ t.  9U9;DKԃxTo%mwv: NԌk ֯ rD/LKUzT>7sD iF!t^O -=)^+xN>P鯕]aL0 }Lx+fVyZˌTRQMZF f9}Q g(:3 90c^x%2Y5=pfО!4Ɉ/,ėX ȁ( -hă`&``D-L\g6F1(U~Fݿ6IFǟd OHbc~t1z26KcO2BY #η2LUܰk8;꼯4ї<4\ƮwJ/ߎzNy9Dn;4y\/-pVgo+&AM/Ojzv&ήiM \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_returns/metadata/snap-8292219806794635592-1-1b002ed6-2a22-4402-8b13-0b9b85931824.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_returns/metadata/snap-8292219806794635592-1-1b002ed6-2a22-4402-8b13-0b9b85931824.avro deleted file mode 100644 index ad59207eb76c777bca19e9d24cf73c465ca59f5c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4312 zcmbW4U1%It6vu4@TY^N88kN+|brdVQ(V0y)*bhaQo#ow^uY)3osWC(?9Oa<^SHBj z&hOlF?){(t?43KfWnd64IQB!XT1O^4mRXYUVgpGf$w8{^Xi`?PphpM%gfpJ*aouZ#ps4D#NKCPXaJfZaP`9o=oXk;#K?`(4FfAe;0AQCPV=d{4Ww2H znI*1Um}Olde}btp@LZ$;Y=fqwf{oz$l*-X6P24he`BM_AGcai=Y)aRKG0QgtoZaO*aXH6*^Va0TrOu0tH`p#w!8 zZKKd)`{&uMs|q2w_kE_zvaF*)wl?m|z7%R59D+YlTIt;^tL z{|I%;yuI#@-E6Bxtn5KC=(40*DB>Mpwb(EyD?@5rv3F4wHVhjTAT_GkIVJ>DErLtC zU$Q*IkcbL}ml!$@TqJ*!yKfTdW6xW)(#gw(=!nN8lF9Q}<&%jcN z<`mbBf{=ni)WE6Ek-NopHL9u5Q-lF6|4R66zoKj3dhjt%>8Hn;nNaOyi`Oy~}oCM6OIu_NAt3!vvGpeXGWHvdyN zF|kVZ100cK2zIdqCy)XWhk3FyU;t%;0k|jVB5W@nfy43)7WJIFgA{12V3i)+0;i5M zYO+u0hTNrZU{HCKL6aOqH^@pC4ubQ>T6hTqJ_RbCmUb_}3A}l1a&8X7#;NF8IES|@ znxvb-!Q;jqRBkl#Q?n{)AJ>>9f{WNjP23IFCXd{f`EJ*9Zy$g9#Oxn;zSZ}0-(6?F z>Hk6A{@Cwh?>_9FGk<&b`sbJL|MF^fYUt3ZN8kDM+oK;|URytT%de9cp1*YI$F;-% z7Iq%|`VsYX|HvUT`^?Pg>rY&L^GJF2`>$rFKC5hN?0RM9{ReMc7}|N$*)x;n4{omg z@>Bi%+TfL$)eC1Xu8y6Ye(}GpE9LbEu5G#fqoaS_^Xl|!=J*@h9pz&~&T#*quYJ5? g{WEa)dzs9Yy?-wce{%lyTOEC6>5FsgTU%5AA9O^+R{#J2 diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_sales/metadata/00000-f914f08c-88d2-4aca-8724-500bf37495e8.metadata.json b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_sales/metadata/00000-f914f08c-88d2-4aca-8724-500bf37495e8.metadata.json deleted file mode 100644 index 0b9fca7a61c6..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_sales/metadata/00000-f914f08c-88d2-4aca-8724-500bf37495e8.metadata.json +++ /dev/null @@ -1,232 +0,0 @@ -{ - "format-version" : 2, - "table-uuid" : "2b1b132e-2546-45a2-b346-7569ab37dd5e", - "location" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/catalog_sales", - "last-sequence-number" : 1, - "last-updated-ms" : 1678866503955, - "last-column-id" : 34, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "cs_sold_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "cs_sold_time_sk", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "cs_ship_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "cs_bill_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "cs_bill_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "cs_bill_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "cs_bill_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 8, - "name" : "cs_ship_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 9, - "name" : "cs_ship_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 10, - "name" : "cs_ship_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 11, - "name" : "cs_ship_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 12, - "name" : "cs_call_center_sk", - "required" : false, - "type" : "long" - }, { - "id" : 13, - "name" : "cs_catalog_page_sk", - "required" : false, - "type" : "long" - }, { - "id" : 14, - "name" : "cs_ship_mode_sk", - "required" : false, - "type" : "long" - }, { - "id" : 15, - "name" : "cs_warehouse_sk", - "required" : false, - "type" : "long" - }, { - "id" : 16, - "name" : "cs_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 17, - "name" : "cs_promo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 18, - "name" : "cs_order_number", - "required" : false, - "type" : "long" - }, { - "id" : 19, - "name" : "cs_quantity", - "required" : false, - "type" : "int" - }, { - "id" : 20, - "name" : "cs_wholesale_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 21, - "name" : "cs_list_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 22, - "name" : "cs_sales_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 23, - "name" : "cs_ext_discount_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 24, - "name" : "cs_ext_sales_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 25, - "name" : "cs_ext_wholesale_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 26, - "name" : "cs_ext_list_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 27, - "name" : "cs_ext_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 28, - "name" : "cs_coupon_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 29, - "name" : "cs_ext_ship_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 30, - "name" : "cs_net_paid", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 31, - "name" : "cs_net_paid_inc_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 32, - "name" : "cs_net_paid_inc_ship", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 33, - "name" : "cs_net_paid_inc_ship_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 34, - "name" : "cs_net_profit", - "required" : false, - "type" : "decimal(7, 2)" - } ] - } ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "write.format.default" : "PARQUET" - }, - "current-snapshot-id" : 1612602357847496502, - "refs" : { - "main" : { - "snapshot-id" : 1612602357847496502, - "type" : "branch" - } - }, - "snapshots" : [ { - "sequence-number" : 1, - "snapshot-id" : 1612602357847496502, - "timestamp-ms" : 1678866503955, - "summary" : { - "operation" : "append", - "trino_query_id" : "20230315_074430_00073_9f4mz", - "added-data-files" : "1715", - "added-records" : "1439980416", - "added-files-size" : "83778546121", - "changed-partition-count" : "1", - "total-records" : "1439980416", - "total-files-size" : "83778546121", - "total-data-files" : "1715", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/catalog_sales/metadata/snap-1612602357847496502-1-11052cea-ed9d-49d0-8d6b-2ec6dd57e46e.avro", - "schema-id" : 0 - } ], - "statistics" : [ ], - "snapshot-log" : [ { - "timestamp-ms" : 1678866503955, - "snapshot-id" : 1612602357847496502 - } ], - "metadata-log" : [ ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_sales/metadata/11052cea-ed9d-49d0-8d6b-2ec6dd57e46e-m0.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_sales/metadata/11052cea-ed9d-49d0-8d6b-2ec6dd57e46e-m0.avro deleted file mode 100644 index 795cfb87e262..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_sales/metadata/11052cea-ed9d-49d0-8d6b-2ec6dd57e46e-m0.avro +++ /dev/null @@ -1,2495 +0,0 @@ -Obj schema&{"type":"struct","schema-id":0,"fields":[{"id":1,"name":"cs_sold_date_sk","required":false,"type":"long"},{"id":2,"name":"cs_sold_time_sk","required":false,"type":"long"},{"id":3,"name":"cs_ship_date_sk","required":false,"type":"long"},{"id":4,"name":"cs_bill_customer_sk","required":false,"type":"long"},{"id":5,"name":"cs_bill_cdemo_sk","required":false,"type":"long"},{"id":6,"name":"cs_bill_hdemo_sk","required":false,"type":"long"},{"id":7,"name":"cs_bill_addr_sk","required":false,"type":"long"},{"id":8,"name":"cs_ship_customer_sk","required":false,"type":"long"},{"id":9,"name":"cs_ship_cdemo_sk","required":false,"type":"long"},{"id":10,"name":"cs_ship_hdemo_sk","required":false,"type":"long"},{"id":11,"name":"cs_ship_addr_sk","required":false,"type":"long"},{"id":12,"name":"cs_call_center_sk","required":false,"type":"long"},{"id":13,"name":"cs_catalog_page_sk","required":false,"type":"long"},{"id":14,"name":"cs_ship_mode_sk","required":false,"type":"long"},{"id":15,"name":"cs_warehouse_sk","required":false,"type":"long"},{"id":16,"name":"cs_item_sk","required":false,"type":"long"},{"id":17,"name":"cs_promo_sk","required":false,"type":"long"},{"id":18,"name":"cs_order_number","required":false,"type":"long"},{"id":19,"name":"cs_quantity","required":false,"type":"int"},{"id":20,"name":"cs_wholesale_cost","required":false,"type":"decimal(7, 2)"},{"id":21,"name":"cs_list_price","required":false,"type":"decimal(7, 2)"},{"id":22,"name":"cs_sales_price","required":false,"type":"decimal(7, 2)"},{"id":23,"name":"cs_ext_discount_amt","required":false,"type":"decimal(7, 2)"},{"id":24,"name":"cs_ext_sales_price","required":false,"type":"decimal(7, 2)"},{"id":25,"name":"cs_ext_wholesale_cost","required":false,"type":"decimal(7, 2)"},{"id":26,"name":"cs_ext_list_price","required":false,"type":"decimal(7, 2)"},{"id":27,"name":"cs_ext_tax","required":false,"type":"decimal(7, 2)"},{"id":28,"name":"cs_coupon_amt","required":false,"type":"decimal(7, 2)"},{"id":29,"name":"cs_ext_ship_cost","required":false,"type":"decimal(7, 2)"},{"id":30,"name":"cs_net_paid","required":false,"type":"decimal(7, 2)"},{"id":31,"name":"cs_net_paid_inc_tax","required":false,"type":"decimal(7, 2)"},{"id":32,"name":"cs_net_paid_inc_ship","required":false,"type":"decimal(7, 2)"},{"id":33,"name":"cs_net_paid_inc_ship_tax","required":false,"type":"decimal(7, 2)"},{"id":34,"name":"cs_net_profit","required":false,"type":"decimal(7, 2)"}]}avro.schema5{"type":"record","name":"manifest_entry","fields":[{"name":"status","type":"int","field-id":0},{"name":"snapshot_id","type":["null","long"],"default":null,"field-id":1},{"name":"sequence_number","type":["null","long"],"default":null,"field-id":3},{"name":"file_sequence_number","type":["null","long"],"default":null,"field-id":4},{"name":"data_file","type":{"type":"record","name":"r2","fields":[{"name":"content","type":"int","doc":"Contents of the file: 0=data, 1=position deletes, 2=equality deletes","field-id":134},{"name":"file_path","type":"string","doc":"Location URI with FS scheme","field-id":100},{"name":"file_format","type":"string","doc":"File format name: avro, orc, or parquet","field-id":101},{"name":"partition","type":{"type":"record","name":"r102","fields":[]},"doc":"Partition data tuple, schema based on the partition spec","field-id":102},{"name":"record_count","type":"long","doc":"Number of records in the file","field-id":103},{"name":"file_size_in_bytes","type":"long","doc":"Total file size in bytes","field-id":104},{"name":"column_sizes","type":["null",{"type":"array","items":{"type":"record","name":"k117_v118","fields":[{"name":"key","type":"int","field-id":117},{"name":"value","type":"long","field-id":118}]},"logicalType":"map"}],"doc":"Map of column id to total size on disk","default":null,"field-id":108},{"name":"value_counts","type":["null",{"type":"array","items":{"type":"record","name":"k119_v120","fields":[{"name":"key","type":"int","field-id":119},{"name":"value","type":"long","field-id":120}]},"logicalType":"map"}],"doc":"Map of column id to total count, including null and NaN","default":null,"field-id":109},{"name":"null_value_counts","type":["null",{"type":"array","items":{"type":"record","name":"k121_v122","fields":[{"name":"key","type":"int","field-id":121},{"name":"value","type":"long","field-id":122}]},"logicalType":"map"}],"doc":"Map of column id to null value count","default":null,"field-id":110},{"name":"nan_value_counts","type":["null",{"type":"array","items":{"type":"record","name":"k138_v139","fields":[{"name":"key","type":"int","field-id":138},{"name":"value","type":"long","field-id":139}]},"logicalType":"map"}],"doc":"Map of column id to number of NaN values in the column","default":null,"field-id":137},{"name":"lower_bounds","type":["null",{"type":"array","items":{"type":"record","name":"k126_v127","fields":[{"name":"key","type":"int","field-id":126},{"name":"value","type":"bytes","field-id":127}]},"logicalType":"map"}],"doc":"Map of column id to lower bound","default":null,"field-id":125},{"name":"upper_bounds","type":["null",{"type":"array","items":{"type":"record","name":"k129_v130","fields":[{"name":"key","type":"int","field-id":129},{"name":"value","type":"bytes","field-id":130}]},"logicalType":"map"}],"doc":"Map of column id to upper bound","default":null,"field-id":128},{"name":"key_metadata","type":["null","bytes"],"doc":"Encryption key metadata blob","default":null,"field-id":131},{"name":"split_offsets","type":["null",{"type":"array","items":"long","element-id":133}],"doc":"Splittable offsets","default":null,"field-id":132},{"name":"equality_ids","type":["null",{"type":"array","items":"int","element-id":136}],"doc":"Equality comparison field IDs","default":null,"field-id":135},{"name":"sort_order_id","type":["null","int"],"doc":"Sort order ID","default":null,"field-id":140}]},"field-id":2}]}avro.codecdeflateformat-version2"partition-spec-id0iceberg.schema+{"type":"struct","schema-id":0,"fields":[{"id":0,"name":"status","required":true,"type":"int"},{"id":1,"name":"snapshot_id","required":false,"type":"long"},{"id":3,"name":"sequence_number","required":false,"type":"long"},{"id":4,"name":"file_sequence_number","required":false,"type":"long"},{"id":2,"name":"data_file","required":true,"type":{"type":"struct","fields":[{"id":134,"name":"content","required":true,"type":"int","doc":"Contents of the file: 0=data, 1=position deletes, 2=equality deletes"},{"id":100,"name":"file_path","required":true,"type":"string","doc":"Location URI with FS scheme"},{"id":101,"name":"file_format","required":true,"type":"string","doc":"File format name: avro, orc, or parquet"},{"id":102,"name":"partition","required":true,"type":{"type":"struct","fields":[]},"doc":"Partition data tuple, schema based on the partition spec"},{"id":103,"name":"record_count","required":true,"type":"long","doc":"Number of records in the file"},{"id":104,"name":"file_size_in_bytes","required":true,"type":"long","doc":"Total file size in bytes"},{"id":108,"name":"column_sizes","required":false,"type":{"type":"map","key-id":117,"key":"int","value-id":118,"value":"long","value-required":true},"doc":"Map of column id to total size on disk"},{"id":109,"name":"value_counts","required":false,"type":{"type":"map","key-id":119,"key":"int","value-id":120,"value":"long","value-required":true},"doc":"Map of column id to total count, including null and NaN"},{"id":110,"name":"null_value_counts","required":false,"type":{"type":"map","key-id":121,"key":"int","value-id":122,"value":"long","value-required":true},"doc":"Map of column id to null value count"},{"id":137,"name":"nan_value_counts","required":false,"type":{"type":"map","key-id":138,"key":"int","value-id":139,"value":"long","value-required":true},"doc":"Map of column id to number of NaN values in the column"},{"id":125,"name":"lower_bounds","required":false,"type":{"type":"map","key-id":126,"key":"int","value-id":127,"value":"binary","value-required":true},"doc":"Map of column id to lower bound"},{"id":128,"name":"upper_bounds","required":false,"type":{"type":"map","key-id":129,"key":"int","value-id":130,"value":"binary","value-required":true},"doc":"Map of column id to upper bound"},{"id":131,"name":"key_metadata","required":false,"type":"binary","doc":"Encryption key metadata blob"},{"id":132,"name":"split_offsets","required":false,"type":{"type":"list","element-id":133,"element":"long","element-required":true},"doc":"Splittable offsets"},{"id":135,"name":"equality_ids","required":false,"type":{"type":"list","element-id":136,"element":"int","element-required":true},"doc":"Equality comparison field IDs"},{"id":140,"name":"sort_order_id","required":false,"type":"int","doc":"Sort order ID"}]}}]}partition-spec[]contentdatapgK (-v̽Tcչ?cLӘiƘR4I!s3}{޾vo{So""ˆc# H)"C8"0>kXOꚏg[.n߶m[|o;ٻ/ۻϑk۳ב/;~ uu^Ǿpj^ތ8WWIŇwVVKJdxn|<1~wR{/$MIsx qIdǓ '_w4{K\r")w2SwʔǮUET5++jftYaA/'y TpQs=&nyNYLQŻ2e52gT;.w7m9 ^_ Oc'Gsw>#'ɷ -%P5-=0#03( b@,'p7`xAD )yU?Qk&2S`%V-zRK+'s@pu -"zRm6m.mBܶxOJk8Z+;FS?i~҉Ӌ33g2?Yğğşbq7gv6|K-`nȷy|m~|[Po#IꗭzvԗU^eM8e/ 0o Zl75ݕ"$!GrrR#)%-9!%MK;]"%7yJdxQh(SRun jrնUߜʞD5R,\H"sH"}7^}nkM"/@"4L߫H"%SLʼ[DDzD"-D+$T( -h Dhb؀8  < | 8@}.Aх$"( 64xH#C 1S-1K{IdX'WgNU/;x扷)&r!P'l D_sHxiDO-5JjRkX@1 X ؁ tM"G`吠]DsR.~\0ƚJ0,]T"MA"!kC.؋B.Ur lo3|yĿ*C3$ʙD%$r@eDyK$Rnm&tV~Fy$+$%rD/\OIdHd[SʢD~e'ŐHςX2$ӧi2\ p?$;$F͒Hs%l'ÑH 861$2{UC G2';nHd0fj*{tcog'낆Ƒl̗r;~H$ϐ$rnk-#HAHQHySΎ=^ݐHl>$R1#' }kId*`hX@1 X ؁ WYCSFA'M?V - 7M#CtM6 -b6MYH\]&}&x|6XY&'J9DD9gK$Dn$#.JdD[L".!Ŗ3?A9HIdL <|$O"3?zc.mI7Ϫr&=5ѓ:R `ueWEJjWD@t}7 › ݞQdX;mg'*c;y:xk 2>0}"I.Z"ZD)aDXD(%,rіX$ͶgE~D^|m#E>zv|m]"mEʿ.Qd?Eg"8$P$lDWC"/֍"\UO3 OͿCylMlRd$8).W\rJ -+t_QG2šL{$} T5ڪ,]\:hh ͦHRXE"HKb v['E' ;!vH$iDN9;r$m7xr࣐ȁ>.4<@뙻 |H55JjZz`F`f`Q ĀX`6`Nn@_(NL \U576bM󼹕X8H$JdXkm9gjG<݈"d'!E$$uQ"׋ HH["/lVϤ#ܴEY"KEł$a E%Qd-_EhI"(h[@yO} sň"_(raYnƺބ4Gmc4i4G K -ieOC"KH;ٷ`CaUui:Сn4K_44tql:2scgizx^Ejo"#H8,GXd=X$|QdXd!:"lf"Y PP 00  +;ppx@p*}D1,0rWJ'^^7Da7b-Z$.bsF9*rHT qWJ-$0قTLHh'7mZ-¦M[b/ΪȆK冷~ U DLL+![Rό<+E?9u4 gNA"S3=g^G).$I"#!=1 $>`]7NHH8]"y7}wluj'1F7y?zR>w.}.K46NMG{d8Kc%:A]"ֲHd}94LB"^DFiÐHKH-ɩDj$T( -h Dhb؀8  < | 8@V Qn"\^"&qxs7o9Nn[yZt΃$r3{y$5%V%r(D%:E oD|I䵶Z+Q(EiqQ+/JdZ\ʖHD*ٛϔȷD>D=sͮu -i2Vn.3 a; C_D796y=ԔxǓ`3I;If] )$RQ{eJdIdhvs(Jw0S=\te3CACuKXxٔep.j IV튭w5 Hdl~9&woD6  ܲ7r9$;? >iM"xM`hX@1 X ؁ bmQdTPtL\TzUDAaiP0Df&o~Țuq.!=bLjVv - -|SDO-DHlElD4n%rD%2,&%$-aeIb.ɕ)DJ̷tFHM~48~z%ʙ'Dn?$MLHainoMMv9R\q ORIc3h7/3j2Jj4?K]Y ufjE$W&Z.ji$$wvŶݽ: DʩDr?Az@"MC"{}yC"H+KH ÚD -%P5-=0#03( b@,'p7`xADjBN&jխD3k;nC116Y"XD:i9[{y[?<%ĻxZ,|6I3ŢDe5(af%95I䃾ʙO\L"!J(!8Q"Dےrf9rfUҲ+)DnM21jc)? 8&X%ߞMIOIJJM5<^Lzz#qYo:lr̸ɫo$8&cr+랗{e&u@kzF~Xf}AfM5 -m2mKoc>+vl͗*S8'6ndrwFtA៸m{s9uIcWee -%P5-=0#03( b@,'p7`xAX}"Gܠ<(SԿ׆u 92c̔#3,7ЅLq̖̙#s2S$|2+&XQ_XsW5QsDM̄59~M|QB5SM(*d@VEQu(EEeQ37bs:szss ϊBcS싱+%b)! -\|a1Sctf?}`lƙ 51?0Yºd!6'vwNLIHcYT-Ĭn{j_9~l37QrjLuӑްv?K7|dyPrOXm6MV#mL,GQxnT%F6Gj6j_GGX#= !m,=GKj6Hw#mLԜuEeLTM, -Ɛ\w^f eT',m4mLˬM?d/ 9B:Ķ<7G?)V6K=G/fq\6Xq\qՖF}?Ul;QxVy$>"ͺAvqôY7t^- w,3,/每\#/0YӧX䘀, MAHJHBit?HH:ظTOjjBjE[)v2u"a@:9>ֶ,dޒo  ͦ"Svg)F2.,R+@nkUmL}-R7QT3,}rȔ9^j`ZXHDP L;Q6 >.hsSEzicjc`m^*8W=qxBޢo[@#"JX7vXEfnXmoD:Vk(߈aDl`]"sDtV"dKj_"-f]GԈ,$5ǟf?}`!,l^8C /vUQ$)9ѝpı6.1ّfx')>MK+/RB"շdVDrgTUYꕣAM{_X[J,}׃AC+k̦*H0gisvaݓt]mQdJQHdMHHNH Hoʼ9}5OnJHd>u 69)xU >kv&7$BS{y q*BbZ=68xW^z3G'Z ١ $]B"($$%J%rDD"1J"oTZ{/%fdQ E~)ZE Q[E.<`Þ1|xUH\A?OE2È"WyϮDWJב;)MM82܉^WBBr;N"37Fd=¬,ULunkPsv,aac.,2C{F+8Ka.*~i"6d>X44d(HGsi{;]FY~e/1ژZNO`i),2{Yg^qЂCP  i$|AWð`'B|XF ;)Xs-GWg q : tF&O P`|ݧӺEUEE(a/mEH(򫍢ȓ; 9ù!i -Ꙏ|3Ssh1v?~t1Z4a.$$gId w=G;.1.ɝI?ct0{OwHLq;^zbMXTcPSJX[.H>9h sD{0""svȣǧ ?@=DDD*_Dz sCOs'eޒ὾Ct~H>DAh7HiD -%P5-=0#03( b@,'p7`xAȠE"䕣Du [u+Et\0DDQ$Jd"8WGx$GSg݃QB"719_B"FQ"&Q"2 D"w䛭@"Id4\#!K7݃E\?~ ☄D* -RDiD$ >"Di?$Qc.VB"G?$c3SRiq & Iq,JNM=d檦*%b$~4K5ܛRM_nXӘ[w4t^glZꖙ;D&EaH]ٻS=&< B"ɣH㫝rAHnIfz_}\+D>s o+k#xM`hX@1 X ؁ W)9 (Ո>zFЎ]?(Bb#Eb)KGlSp\SI|iཋ8 \"ܻݧ#Mf-%$(QEoyWȨĢ7D"wK/B"cq]"0mz 'Giqf{zH$nznY&I驎o=$;\I $OJ˝|F_U ﭖ3ErE;7 Ұj_-]) Id HIH"ݷ%0@ T@ 4@ t@ L , -D  \ X^~AVR ÂHṔvPua*dL!s`Y3T"5̈́sxUy eLNߟɖ؟4$JD!!+E$JD?֗=sA>/S\+!Q"$ȷE\+JdD3{7(gV$RD*H"H"_>$Rӧ_(g~I޳@Tw+!&B'q'ƥ$.=3#2fXv,K5LuɽAMrKn9S?T4\2VБS'VV%2 yWlw[DisD#yɝ3 9Hd%$rս>H$?NWV; zHd55JjZz`F`f`Q ĀX`6`NnB^ë9(^<==y(d$2@˙:򈽇w\]csp:?8DTIQ"2 ĊnZnDnsʙ uߚ;R}Dȹ(GD+!nDηJ$H'sQ}qye.ڦ1iq^z,xI$:7$%%).=Hs!H"qn`ݩIޤ3u[}JL;DTTw53YLaH1l#H]ejD̻W6@" DOgB"'P =̖sCz)2 -`=}3}3xM`hX@1 X ؁ WS)'AUMԓDs Q -b<2s4SY:<'I}\`kgxkoWFk"I$K7t=ޭ)g>m>ɰ_:mhE-2%ZD#a_úRaӇDﴠS뛼GȻ^[_ "V˃LP1 ]G{RR񉴻=.Mr8Rt6#-M8߬"wOӑMeiSG8XVR/U5ײt#e \9jDJ.jyt5֭l[YECՏz]EhH"="U%6O"K "#/"O"SԈ `hX@1 X ؁ b-E?Lq2^BEDkGy]=$Eb&jL,ER,ROコ扻-!V?D-zP.aEn-b-kEMg^r&IH$JȅE'!;mXZףD -zz?}(rM1$IKw%%sCF#%9H@NIOO>S:{O~!|DfGêa/iuUEY{3Mi)碖iؚnOSDJ&%r"޷W'Hd9@ukDDI PP 00  +;ppx@pTm E;όe?O/i!)^J7 ^bK}ueJd[1oeksl%'84ß]Y]b{DDKH JrQ"%$W΢/6<=jmPb#Q"#DޕXYM%2i=KDje_J=zOׯ[Y?t^&}`OC3S|9wֳ̝KLw%d 4I47:ׯY*3;fHd,cguYL}{U0v{XRe65$,eqQ}ŨgZ; -u1m.Hdj"A=S|;$Rڛ2H^tL{Oȃǻ\ɓg^Xg -%P5-=0#03( b@,'p7`xA8uKUaZ뮁;v]"n޸HLļ$Xj]%jNgj!m=[&:4l$ODDT".Q"JHİ%PN$ҿ(rDD{DI%(D-Yy-[zer3g-'m!H=$bi$aʙ*˜1q6I$19ٓOMJw.G+.IJJI3HiWWVdw2}n"KUZH]wXrj9K_xT8k6-)3YpQ9D}d/!ѻ!hYu=M"t|6$yIlt/^9pI LEhjީ f -%P5-=0#03( b@,'p7`xAHZY!zlHQME!M5. -9 qID0_fi3!0 9-V{T`Ok;,C\!ٞգ_▪h h$nfK$ąs7I]iLDfŻ!E(%$2S-/+=m$ǔk"sHĿ1 [im3_cQH$lHRMIIu3Xu$y)D O\RZzF{ W!43xYLMAYɰn2S֠򮰱ai~H =\Tctcnm)DrA"˨S\ݐȉOD^gM"}oH子HU=mY LgnY^S*:`&`A V`v.,/? U&bg"r"zӴy+$*P'Cիh9B-R{DDvmm%Q"c-=~?Dz7<ӉD'Q|KL"zQ"I4|kKȝJ/۩Ջsv']F~ 9Oy;dWD:ZVGZ|M"GR|J<ʜԌDO#xKwYH"E; ª#Yv$m-h Saͦi9SYF긨 -z s>"죻%OA"B"C" _@{ߒyf>{/A"'> Nоkk*@@@@4 -l܀> J7Ru浳D7cCZxc`$2@H-g!byg%qw{herOg-1=㕐-S(24\B"^u}Q"DtI7["}-DG+!Cy>E9W\Hċr¦/U&&SPװPGz`ӒXW -sJ7Eur -erUSrrM3r-ZwIvad$\s,_y(b_Yn=VsJg״ҽd[jn;7ue`pZˇOu:#x~c?$WYUWbbjMs9،xGr7ޑOd3&eJ"{WyX(S(ɴ⦰j2K]y,9LX]ֵdg髇ͦĎCrH<)يAzMw,bG{bG}!bG孈3J;;H^_bb/b2mc^]~n0@ T@ 4@ t@ L , -D  \ X^~ARv%Q!AYREBꐦ$-ZS0 Ec ;Ib-%\9.:CE]<=!EݯoZ ƎoZ%Cy5߰QZxė7{]#%K$Z,k^|*_vyoBc ] wF sjc.vYu]\NNuqqbIs$sSY:]"W-6v~sH7*=;k &ҏ \k,m6ʏ@"8]\,jwbFwŎNiyeK]3h{;lߓ=C T"}cH -HVHdjGNfO]T( -h Dhb؀8  < | 8@*$$T( -h Dhb؀8  < | 8@גHe4)j%渠 a7!b&6AWQ8 iywaI__ N2SHH"TD'J$uImDymż)>M"ۛ^#Q"ooz9fږHZvCFDAsWki)I?~4ZK&}HgDR\IeYIޔDGj;ΛMq'dqnID&h{NfsGXLDSPSBX;Qg駚־lBi,5C"t5{IdؖB"A"H -=pܛ! N@" ͐H$$246@"͏*g -P)@9SrLʙ3(g -P)@9SrLʙ3(g -P)@9SrLʙ3(g -P)@9SrLʙ3(g -PJdWfWN IAN4YPKi;-gZhgAb-myI\!wA[J|' L(%&e.t+ D.%% |aK$r?eDlDzZ9+rfQEB"D,)- -E9s' s7ήkI$6͑Ʀl\2HK_3s A";zLSIXU{cX=̼Numw<4%lxlZx,]U\Tm -9zEl]G ^zzHw5M!#AzzH ʙ;P E^DDAf]A"iM"xM`hX@1 X ؁ 0{}M$[`"!e:@MIB -b8+Se<"XrCH?]Y -YsWDC)'զB]|~QĤL!R3!RI\&e~ʯp$WB"m*+$ܦrK3'c{!mID[H")_E9c.} \>VR$'mxO#)5%.)HeX/ݳQ*ҏdy;ɏ!Ұ@zѠfb$>t1Dj9K\T(g Q_;nk$23$>rfʙ!ǑD&ޤ= HA_Hd JQLpL%QND]L4D[zHeYX;yuf#UK}w]u=DZ-xyA?J dvDȕN%rDۚ\|r&H"MB".k &!ז h;KZ"GD\b%Hd/sH";LbWqK9&A솳R2)ItwMv$'%:RS]%R|˞7VVsDzRͿ<̵gFgk acN ,Usj.HC{w[k!?s՘=j̎DDi984?$DS_AY3dD -%P5-=0#03( b@,'p7`xAH՘=DQ.0'QIAHȐ{Q0!IDJi9S(X [`o -9C^`k!Z=R8D -%yLZB"yLZB"I"O>$tiDpOpnD -#t\LUpң\TɍH"m]cm4 C z.8q2|r螷dކ}9I:mWO&aES<&0@ T@ 4@ t@ L , -D  \ X^~A !D2O!6&NZTiC"ta5&*b!!^Ihi-%twx&/lpKvmܴtwM˙'w^E4a6,k%HH[2H~!o@"u7ЅU=$oDj$H$/Oǣ s!G gD)(bR2P$$;-3Y+ &Dwm,m|)DDh饗tV C -}vHnfVHd~:Zk*@@@@4 -l܀> 9kIBP(QU>Qw^[!*1-se2F%jd[MQ9O\=O%=y-k"I D$mzC["'}qFw}@bwQ"{Dr6= q,&,~+* kI HygDհ> 0ۢD\K >{V $I DzxILKrx ɉTw =T"m;HrX5;+jkڥwtH"UACkƶ敧d1' T>}n\'XwJ=&w= << B"m}eAW!@"oB"K/A"I PP 00  +;ppx@pt -)8,TS$ P2A"Ԡ8]#!0 eBHQmhHY7";@gGvh6;,J$v "̢DbED|sʙ ߸eVKtn%r %r _ID.t1I$}*xo%B"3~8chjMʙB%a.%d#=MD2RkNx[) g}Ӟ5EHd's^YQLƠŰ,b~ѠaitIy(gE)~IC$vŞcu&!&Hd2ǘM54H}H}=c&~&GH^Qu30@ T@ 4@ t@ L , -D  \ X^~AVw )*B̉rQP -v@ A'2`rJ$n6 -mq- Z<  9B`z#|~DHۦ!2D6= d$ />visjU?jfOd B=`t1_ґ~e.ߢ^I$Dy:3gՅwdG\=wp$x46=Օ?GLcc3ȍYBY|X7ݽs<ïztS!2H"VJRD Rb )R"}AmHvα'#/?#i;ktnQ.m4N\۰A6vhdXq 6$9{t Z%VI9sFD`ؔ{bѿˬmm}q%FRгݣo#ZT -d@@ T@ 4@ t@ D(  -l Xn^OH{%L$["Ȼ$Nj'$nY%j:n7H%:WbnX%VWh8I~kTx%0Fhݢ1ƐC! 2b}c?l-q1g#!ʨ_\T/Ee̋ʸETFލ+q[De|O-~O e(Q/Ҍ&+c,L1Q=mPov2gŨ/ꦟzO2pL;Ujrrjj* H8b6>͉Ñdw:O-.88TF VOmfw/dȦ3}7'ٝ*jڙzhGYPSEtC2懶F' ewo$m-$/=MH^f?@1W$/]Gw:UT=9⎩%ޅ _|[M -d@@ T@ 4@ t@ D(  -l Xn^vt$s}KszTД a0O+ @>S`. Ab'~yn#+ҵ1Nq-Xty݂~alEye#f'jUW?5֖bOtsAa*3ɜy<{a0iݧuϐUM=UԖ=y5-umdzz] OJ" [ J4y9Bw0W䥝ܟDZjHTṫD&k%) 2  -*:"AF`f`6`,p7/^]Jv.RD@ӜWͬ@+ }D)zDj[Lo'A쳎񶚥I>,'lj&3Jä̦]!  ID"m}7SLHdaDf1k?o[yzûwvH( A$r(,Q"H'k"+;LD>>pDvȀ(˂D"E ,D^ -W^hcLs+7zyj@"~즟z/9!pZ5$b]g?%ՑjIID-~&űɱN92%ۺ^YZ^YLmro;!:.x̫ȫF$Q -VjʟC: ҙ0B ghy9Ha3ǷDT&KH3HgF:3w*z:nalWcHg&!B3rT|D[>D6)`@89P%P5-=0 @400+;p'` xXՄ!5\,(&xe$zfמ&7Lܥq;ě򈹈X;+8; w#|!,^)E.9nwE?Mb{qq{2DvkM" S2 /1?jc{H C"vȡ2DUkߟ`c~ZAM8 Ƴ{ݟOHM l[䱥k&mdGt>"arT7]W3v)@$g+"jG$RDƷFw&{*!nHd.'> D$Rh;ND$K ]$RIyfeD^OO#^3k -A_*jD*Dxc146>;ƉgGy!F)a!WZ,a>'2w# -_;N;`\7̓z8x|yT,6{%5?k:bs{Ocmj0iS70Edΐty-ʱ%CuӀv5iX3̗q""omnx8XH7En,2Nv39t c:NׇE^efɽug[&0|x +xE QTłhJm>CgriQd3*bn$ -vۉZp,-2'rve e_[Auhs]kb ZBEB Ne$yQ"QD^% ZPͻ"3v1߸{)ܧw1~a̋ E1gj 3MvXaTKB2kĤ9S~6qۆ/ $C6|"]^YUTPPNW׼մB>sD9z$L[pYݐ:o׹%$8m Dj 2:o7xDf?D*iod $o! DD3@@@@@"@$L . :"ioYy Qe9Q ^S馉W0LRH`l uzTu,,8N\>'%)D~%J䭐hM$r͏s#SryAA4-*Q"!BZHd";ȫw#y%ɥ'2KY&-Djh{?"NH7ܺSťiK|8Ҝ6?%nI%)qil9+U35tS0iΙL DڏgFُ{ 򀪨1]|ȫYc@^7Z3\D+H!=9ҖGDz!}4U:ow>mE;]thl?$AׇL>nW[,L)`@89P%P5-=0 @400+;p'` xHr$r'^)3*fy>U@xm>Cͺe>Ӝ[`ޑ#8O l<ߔHV|&[ȥD. -"(D. -"HĞ&99UWvԞ%EJ+硞gV3s< lQHĬDD$t"HlD6@"@"^O9ٴd$'[K3-ɟ[}dLQH4h;!OO*[ʉctu^&6aH$yREնJ"ip჉HzDfiÇ%.Hd[zo]˽:,VGA]o.K?S -d@@ T@ 4@ t@ D(  -l Xn^һ|"SV%^MrWMu?̥j`ȤHYH1Yb-l>Oge^%ri|&"\"$i\ EJnDa _# p3]E~P{En|u*gScS~ |&9ޒ$Ťڝ)Er{tZ=&+DƫwM^Eqc@9v`:)"_lUa*PYE;3 t1s߶D}"8̎A"@"_B"Zw#$LiZב hG[H1)Ҷ,L)`@89P%P5-=0 @400+;p'` x?H - -<4(=j{xMCD?DhYoLEe''IA$hs|"A?L MJQZ0L>sZDl=s<{DpszV3_KD 2=9&;O꽫Id,] -2=sFȻPǪDՅ:VbZH; ^!yeW錹`8H릟"ykqǼt#im]=ĦKrJB;-~[L֞L(RQ;3-?ϔ,g2dOztugWжu \DAH)|cKƹ7艈/!w!уH>= 5@"{[/ҙ($ҙd-@"OB"E_-fϔPP  -D#03pxpT,KHfo A>L" f@+uJDFolLļ[gm/7 Q l9q $,!YB+Uȕ!wZD"9RCc߰aC|{+H9xw8YiU۶;Sw^q}ZF0_8cߘݱ#ݱ=3iǏfO\_1[WXuѮ:!>q+v'[I [3ݿGZ')dii;ٝf2Դ`ZrFb3zMdМȽmīor"L|1vLyjt<[]h@SIؑSP|8_f6Nt  cŖ%nhu|_ګ2{ߟeV98*w HSyY}YoyUx9;X|>>(P -Ԡ-@0 `hX+Nn^ᣔqbB%#DuSM%mu D?"r9s̐ 1C$vC ly>JG 8w+,L_r aޛ ¿ 'Ei;הZɿ&Z+;'VsOZt3gIYESOڞ)fv|-F+o7XBMw|>zw*wk3'ᇗzN~וӉ Qko+U/~W]S#Q+;C;;]Ӆ視TkS+vT;\)>u~M9PzC&.z 'O9{Y;uӄЄwgED]IɞLWIv'8L#59=ޑNH&ȶw,mv fZcURsH; /0t -M<'3>Z{"؜VKHgHH^A9]YqPj)yO1p7;4>ED "D$D"AD"H "D$D"AD"H "D$D"AD"H "D$D"AD"H "E>O5Qu?#J^B#!;xS=o,<"#DIMgc -=M\=H~s=: -/"%flHfBbb|+HM$#Օrx2`Rr0=qpŹᮦs_e^1Eu>RU~J:43svnP(Cqw}]aiWDcZ[UGTE)kۖeu8_HW\*BwasP)}zcǟ00k` S@T hAz0L` DC BXvp .pB1FMUhnHω2(9Q&yRaU -aPX;1}NUQn{LS0 -wToW|b|C,FXMQ+ŰV!$RӉϠr?[s%uZ׻差VwbPJ1&z1ct}1V~;%|bW.C÷MٿbdC](ƣm>Ғ_P}jMgؑwA,iX$231sħv6Tv)g6Q `hkRW?Fuڲ w5fSm2sUы;Fr1vT lo|c`ހ>=1vuݹ;J˘ѿncGnγrc;{ng?S -@ *P=&0!b!`;8 x~ RMK,U)jKxa!. e,rȘ0vDzwt5qqij0wJD+`)Ĉ2WJJbH;'?'w5/>/wOL=uX>;鳈țr䁎n9P#' -3 -5h@ :Ѓ`3X b  -6hp<|@@(rkuLm UbP8S-17X"e\Ho]mݼw15ʻ{S6S7|3 e`䌠Nw04&wb#pdfaJINKJ=5"So-!"2E3&iD$<[Uђn4mީt*Cc7+Kͦ_XK#ltDKH'[lN? "{0"8|ufyu!+xCQqygHY5b[$<޹ UH%gxsF61"j1";ňl#rDD%F,F|k]"9S#I"2S"1"#rDDbDfĈ\$uMED$rMٞzaBD^BDFS?弫>@4R\fl'#C a9;Riۑ@`©Yc[a!"%Dt} K=D@8?0weM]2sc%n)@D>GDZ˯+.j-|& " [rzDdDiL"Ƕ{CH'"a<0"R҅>(P -Ԡ-@0 `hX+Nn^"Oe1Q5rFs14ΙK"OEnjU3}˙r޿ĭ2|MbU\""J1"(%"5'D$kի D$$1|C\X$"roDD.'ϞJDGDi#e+t!"My s߰9"l˙R=G~㬊32+)9#)!)=f${Nm)?D sUԳ4EuYly]f޷LTѽ%H'H[拷Z""ңHLjPrϑD lt#"Hِp?AD:?|LPAZЁ `V@ ?2}~<~cU:Dg9g#yb,eBD9DX'8[g?Jcn\{tsL5=*9Y%"J%FzyKKD D~JD>O,'Ĉ,1dD""7#rDDn\elWDn'eOr&b%"WЈ#uXδ| .1Q(X*kDxVE$5!-Ƀ'3);ݎ tڅpϗ32E}fjp*e4EnwtdzCgMk}U嚸GZ;;DkٍLŃXL $29"t%Dd~irf}D-Ddȉc*%@ Ђ`# 8 \0!,>J& "jԓh;y/ %):޴+x˂"R[p?89Cj޵I$SKo!$&s%"‰@"")D. k-g߬6"?%"+\ň%.g $\/}MDH9D"ʕl)BDb匓 ?Sw~Rl^DpY'Ou۝v܎d')>äva5ޖW!"3c#HKMHUE˙!mσ!lu~!?d\7Nfnf-ltAyćDf?UZP#DuDdqDdmL"ea-DD"cFDz} W -$R"2ɭ<0"FDˆH #"aD$0"FDˆH #"aD$0"FDˆH #"aD$0"FDˆH #"aD$0"^H[jjW^MQפhS)]WR WSL6/]miKADJRW[\m)L;ݟ\HU_oIY%"rl#rޚV7ygVpM!WzMo˙q1"KDdq1"Qgwgs3n,g$Ri_H""o ?SW7uɣ=g- aԴL;1HfS &v{] Ԉj" ޱjED B,uWm@3W,gߑsFh64T "H=h5ql6W""ԞZDl9:"2|'"."{xu9Dd(P -Ԡ-@0 `hX+Nn^ᣔ啋D5Oy6zRLj0XbO1X H ommKwqO] -)vo[mID%N"_+F%N"_.qgX(6ZL)e]H(MbE:%6o[Z/lIoͨHXϼ(N* ~_zfc4K]U:o>vgd$;2h3II0F=ətzb'ujEo(y")zDU,f s h -iP,}s鐱d7*R/ރQ6t풓OzTJP4F0, 1 q` 4 ` >CX |#ye!:J&!N1sySbiVEkjksggyonn($*"JTHAq9W"wKE*jg-QD!F-F$Zb qEnʓ=s˅ -] c~ꧮ gukgUDInڑ$ ޓLFǑJr%2NRg7Qo""٪K4C!mlݑ,}]CC$d/5:e -20"R;?%nhuRزۇ.TEADz"s•}H_믽H>" Hw"\Qd( -5h@ :Ѓ`3X b  -6hp<|@O\!OMe5Dk#{1S.o-y((g8MpnB])"BI[C|櫣HvbD.^sYHk6 KD?&_{˪bn۝!"K#VX%QY$~/yiDQZQ"{>᧨Hk *T4W;tIESM=g䤤 Wi)w[>.Sm*<;,K]R bAYY!^il9 >F-"aAޖ=[emiTdK[M{>4E {V+?~ݛ'l|y9 O,l7I$[cbD$&:"R$Sgk"RRpia3&1` {5Jo+YKw#Ԏ4bw䉭֎1D2"24&fL"ysL&ɥ Sko^eW|\xz=1Õ|M;v2@x356"o(*mV " 7it#eg!ģfSH6zsD aYO:ʶZKwQ0.\L"e#xD_@D&{g1D%D "RɛwnSJP4F0, 1 q` 4 ` >CX |.Q (&h8]97C1V~bn$\Hi9{,' 5!jL"쫷g##W`MUsZV[ZY}5gXÜi3/=BD M{/lVX̰:i缓W9W%+q{a1"bD$"s1"bD$"uȫ3\X]uZwg:5[%.I\u]Cғȷ04u{ID'_.=L"bDʪ0}A(/D2=tG;3NNCnLKJH;mu߶槗99YD.?*3[]&</e?02MEKlta#ؖj-@Df"/#"9y%D慻3`iEDv?;L wg@DE_$RIS(AjЀtf@4@,āl`'7x/`:lSrT7Th:9"ᄈqΙk8!"…:8q4) -E*b1"Z1" -ňԊz]"rkvInņ*>Ċ+Vk?\"*rQ`dEnCs3#׭gE0,m ?(}qK]ykEΪMLj|z# aǑ̸It;13d yyOQE.R|.tL<:TM+㝔fmyP;R_N&&iaPg~)RƘYulٓT.utHg[4uDW e/_G1{|U'\8[u,lE@3r0 [Soz8`KȸPj6zAf.,c-el[X薸Z+:B B^y^rQ׋KɘO{g{p -QR 0x<1@T hAz0L` DC BXvp .peSTWM7tˇs#i7g^R. B81N\69]˹&81S1wpvnءsZ@.dkz ;:6wNO}*&\ sI""'V^.W1w<-^0""w-2T'_r'^0wlמMa3ғD$>^Xg:ha2<ӶWNPBD&󛨪fl,;?9DzuUw 84@̼~]w'"&"V%֞]H}=6wԁkBDFm-&DdjtX"2%\P)gˎtỈK -@ *P=&0!b!`;8 x~ 68CyuUhyng #Ĵ7Gx,CuX+-ۛе5B Ӝ p3?,#F5W/9O\(KD3=UC ؠƵok-ٺDw""}#4uϺNF- uiOC>X\;+ elHrRENvI&)>ɑrW3U.Ȉ/eݛL9 K=̼/0Ԇͦ̽ 6z6yL¶?oڷ:Ճ})e"[qPN;(w$,US;pG&@gD>$R0ybTJP4F0, 1 q` 4 ` >CX |})8UCz"R[0w\b-ʳ25z/\ q"lsH_n?y?Sȕ#)FJ$KD9\o[-"V&/|5N!um!u%wA[r¡fy~꧜ԕ0K-a9ϪS⅗{0x1IAajf;=)>(JFa`1I=[؟nīJռ0 -'+̦d漽ev]/,gD&Ząj" @Df_$ЍI* Idtτ)`7%D>Ddt?&H݈H[DD1@T hAz0L` DC BXvp .peW⩅9䵍DWl{ySGjYylݻ"2W=L?T<|r[ -@ *P=&0!b!`;8 x~ 5blUcD ObZ2S,$R.Dd>Ś{-wN:Ja@y;E|üTxj1"21":ԊIDv]"rŐ YumD!bD;)BS1"=,XGe6}D0Lv¶yBD3#" ?eDصg׋fR= IdWfÝ8AO2wrH]1!"ml ܤ'[=tv=ҕGO cfS2bk)1WM>z_D20"҉ =Z# r9҄Lc="roKQFDZi:|LPAZЁ `V@ ?o=%*&Zi#ݼ0LqIb,My̎ko#IOKj^/^˿)##R$Fd皏P՝k>*>*/W&7tňdyG""濝,FĭvtDnqR#_yT㗈H/+0ljn -:Dg!jBБDDCX 'kᕝAuhm8E7IcawFX H yZv @E)+xwKc7" 7^"FbD4d}pwḟ;N[Ge/y2!qyqADՃȽHrp{ -7|ORlYA]IdÍ_$He<Tvdhywo:eCW?4=wtsdzm Oa9ۇ̑Aȗ[j]V^ypaAL" " FD"$֏ OW -ǻ -oZxDD1@T hAz0L` DC BXvp .pHʼn';8~PxUStLw'e -fd:T;3H3R3Pvrf^{`3r)Vܑ.x&)7m8[7TG  -6셃0%`h)"R/\X=ZFDۄsÇ{2bb7iAD4܏ߎLy 7(%@ Ђ`# 8 \0!,8ʻ%BY^ul.^uMohvKyXz!":۪x{wz{|ͼLJTH\)Fd}_ km6%"_o,w u"rGs>_t]!f1d&3wRBq{LO3vǔ/6WR-jHR5}R=[OiWjG -zʐ[4?-7Wg,=DE׿hץm<*Z.>y[ҷ6=hrwS9t3[m3ulNl Tخa9}L -5h@ :Ѓ`3X b  -6hp<|@`yl=*RE(M:JW/2tEL,&Na{a2ʶ;^쎢\Q(OyVEfƨXoxN,Ƌ⭘-DR[1[${x1ŸkmriM8E?V;Z__)}1xwͫO'rRn7M]vvI&(F(Ϟb^J>sSsJb^6~l*FbRj|ZrǑtԠI9<itFbfrZZlk}{2Er%;C,ug@^Hۿk(P -Ԡ-@0 `hX+Nn^ᣴ -SLjjfh+9]+s]q5MrC UaYk>, BAz L>6p/XVּk#mu3]E]ނV|͝e?#"# p]"r*sROd " ŅI? |O_h8}VE$#>!!Itdf$d&&2L7~IJ>ܢm#ϼ.DdL1sfJxI"'3[=tvmٺlгCqfSc?"kWFw&9.%ޭ%VLKp{AL"宥14T=N-+HD^$RxnO۝| >(P -Ԡ-@0 `hX+Nn^,,e?Qup]qN_A G8(g!޲[Ȅnbm'*"<(хk' -|GV9L)qjqDDbD~ FKD 2;]!Zq7tl\eؐtEnvR!T$ie O]Q\Q^?t(=,uu"ŇϪgv3S ;INap0tɕIAwi0n3EmzEEJUR h=e^7?0Ԇsefw:d*҈LIه[jJZW햽!4*0Q1"5{Q1t "=l"HуȱZwO<9w(P -Ԡ-@0 `hX+Nn^cR<)[yU7Q&"/ X0V>#\6pw  4L/<)3*%+~"ŊDNꚻBêbZUG_ eIzI9˟ʽm(2.}iDd|OoDD")"Lfr0͕ozIKu=:95>H3]^}綣 -orR/L 7tV t9oͳ]벞ڛ+}O1>W'W0<dwR ǖ:]@DyA'0nWJNK$&';nwP8(1ᴈa^!"G6Qï "MC,Mγ!D8=y8`U}1lia-«v?$󚸲][u""#!"K{ʗ04 `]$X=!c -k{GS""H>}ňHTJP4F0, 1 q` 4 ` >CX 'nsIY>lSw)y7srL \LN[V/\9Yy[1goc^ZV -.r!U- ql3bD.bD#rDD_u WVWt)AUDDx1"5G;o -OB1 -rFgL"?v||")VPO4&ѓLr$x]wRR#9#='`uDl<-3s2Ů,g^Rw4=7i}v~ɀa/']f`-Ub[GTGDvuo+wXxܮ0""?*nDxܮ;P3ol־ {QxfQDdDxXʼnc*%@ Ђ`# 8 \0!,+݄*gìSwqrtKD߷|Qds'g"2~"Y8[9/r>¹z9w `AW3e)#BV)׮͆'5>k<"#Q{Ŋ($*,V^" -$KE >pNvHxʕQGE:^(rmF*}O1T=}ѵ,u(VU$=OHr9RSn&t3 i oךsxTE -6S E r=MAMB;]CGMƶ̦:"PIT?M3$"9_l+ouGEPOGe&Q:jXؠŹϷ{"MޡWPCHzDE1@T hAz0L` DC BXvpGYy`zc&cddbLb!$ô{vg{vw?wgtZ]g.2 "f"RJ)C)"VAD^a 2 -"RqlRgsσϹ~؀8xhׅG1^r'#4ꩼWSZ2^6}f7,1|7 <[y W^YJ]mOj(P^䑷AR5",`kD,!yLѯܡWC`P 9'X bنX?/b=ߩ8{k|,G+,BoyX$[fe3KZR<{zNLDVNgd$ZEYsY卒))FR%+z*J3KӕQ(UsEziҖGj^V٭Uu1N>17_3-hͧjd&{澛ot~& s&z:tj_Hw 2  -*: b@,F`f`6`.^~ -V_"m"R"/!f#*.&"#Vb(&PF12qyk5Dl[[=9UM<'JA]PGPFȚ2žX9s7D9\sY2[%e[lLPF2 Te-̒Jmm.n|;җTyWm*O_|3x7%gSRRmi%#>eWŝnsX2܎${ZB|Jv嬪߭ C&f[9lyI_=wrT>V/7yzbjpc+*?@й+ GsJ]VK{“ftd K_*#aۛT=y @ -d@@ T@ 4@ t@ ĀX -l \4B2óTZAeӼW B^Հj(.n0j c5ujڿzsj&f X tDbbC#bXhʡgDj _-+ -BQ1⍈;~)Au߻;mM8< u X9/-;p(YѴ - NK3-Hp[RRݖLw;і9\=憲?[}(BNR9 Ȇ_ɖ46upY0d)8˙xvvDfr6:y8i }Y"")HdD} m"٘܏mRwјgD˾Ȗ쾘{G2+n{{a,,Buy0Eqȵ^9's,԰ NƟH82ܩwRb%H8ӜnPE"$͟ /eՅ9~EߛAɂ1k٣my ȯ`C.nȉv#)`o""-SEZO#.EPGB< CHm% E"ÿEZj`C.6J*Yi@KQ^5ʫ c U]` K -զd֣`u gi+왦CrEV~y0-sE8ϽEM"s{^qC?\O"s@)$rD9}DߐHQ:CVb56rB%H\"g'^9zpB:Evӝ$gHj#ՒHvڜi"o}Id`1B-sϑʖwALj~>[=zԯY9ԶJ[ap:.{H]qeL"'!g٤?{DZY>38 |H>o -ZHdi{鯀D6Dʓ%}'7ʀ|WIVvTU6-3BM]m@_0$C"Yw5$xs-Nl}:Ɠ 4WyeRtDat"v]"ľu/Ƚ"$)"d"'Y""(@$r]kL'["8?B"ߓ{W#?v2$:`tg ILqXD+5-LLOed;\6ǥ){0Q5x'c+Tge~E."=s'~״tu'/a.D$RB$Š"mUw?XU,$rvB"DK!9H%H 9,ɽB 2  -*: b@,F`f`6`.^~ZvuuSR0SyFTsry&?_Q&\Μej⭣6B<,:)役zAD"DH A"׊H A$${'"m!_Dژ=u"|#FLV \S3tαVX-;B&v\v3'ރD*߂DD*>DڟDFzZ Jv ˰HӬZyLn 2  -*: b@,F`f`6`.^~x&2( 6P4Qe7ՕTSFM.y$,sȝD,ZHDrmҙ+G:s/#C~#w)1\mTLJ=p[l),It[RS"˱;]$wJe5P{W2>4>Ԟa!Nees'co=A9.fn09׵@$\H<"≻wg_D@"lHG9$RTrT$R̖B"yY,TC3]O""y2 -mD BuۆH뷆6 !H*qJD"9"ϖHcMfɏ?HgYVX.B<*";g/xW1o;?QJn}I"N=!%ޑdIOKK8R٩$fw:SlTs|kJfm8Ga|ͯh.}?GUSD$\P[UUQ ? 4? ;8.BD !"Hd=fϽ3<Լ|| -e )H ȁ( -hD X ؁8 xp]Hu@ASYE@^PPifi%X:$2J4͛)[87RG~-so<[F}uf#EfSJH {WmD -s=̳ޯp%s0.A"agې-DķB:E21N<tHgO'vsفT[2RHg2wՈinĞ6U㡬ӫHJعdi8$fb9;GU4^>ׄzھj.'B_gh tղ{ݝZwLB"y62D&tf5=RoH\#tnȡ, @ -d@@ T@ 4@ t@ ĀX -l \6$]ה jG+Ep艏^a3ջ -JwOEzcg3S]g*LFW fsEVP -H]f5%)9P%P5-=0hbA00+;p'pІ P@RE4PY/Uf4]v5Q,5Գ P -5][ 睅ė+  e k'ں16eɖGֳG_!DN"6"-6ė3"9._{> Qb" +,2w"6TWLȴ; $$_67{Mpg$^vώ0-FHvQX$o9G6-A,\GpPU=nkNX[ߊX x-g8QEbֳZ2+ -8:Ea{U"E""]l#b%bzF^cU#⛰HU',s9|P 2  -*: b@,F`f`6`.^~ -دKZyK{yY;S8,~^]B5m׍P}o(J [!2ɛxko E8*Rꝣ/[$$Д -yK a-r~_" \s& -""ey.vy?Ɇ"7Foٷ {߾|ۭp[DB"En"ۍ>b^OMRHh^ro*$ +# Mbےd&9IIDWe{fBYE'C"K%;I$yPVUmlUr+=ڕfHQgi⢇ZF66DXU$Ī"%HhF{ 6W<؆wۈX=ɝL@Ȁ(h@4  ؀8x̅HeOSi+Pdi}&D v.FV&rj줦96nY>Lv'{w\9"ҳzTȎpU >lUİ1'Bi$տ!H3!+]vocNe;W|Oܡ'_}nEn{o\e_ID"y?LHt[Rm+#Ӓiˈw%dRmߚ` Dn EWҮ3Q(pՊs Q(u3WkJV]S닞%f]>f EG;ϪM'_љ Z{5fK bu<#zٛ}COSEpB^euTF&H ȁ( -hD X ؁8 xp2"i'Hlȇ({ljfhۈnO [ c1usv G.qW?U}yH` -C!1k`[DsjȰ "H^j^@duɹտl H -:wTY:S)wYk̨2~2/dOd6rؓAE~QW쫏Xno>+$/)I pgK (-v\&,˲,bYf1!pn;ۺ?lYx67DDDDDDH)bE!vH <<}t9Kv^su_T:=R}߲mٲC7{{%^U{ك)iI!{ɉ/}RJ0cSe^PR^hi? $ᗯLݮ V[]NW3&:v3q¯Ouɱ)h{\ttݝ&ۃ' g%^)T5fߝi'Y;%t@wrɀ@H]ғ~%M;^M} ÅHU>\3wX;$2cvgT,G$֖_z%w[ιVi/J=sy'[ʗZgrsRR[t >> 5h@ :Ѓ`3D@$D`8n ^QrYLy8$({U-Q JA_`H0SyyKD 5M, @U$8g܃Q`ۉ7'@=$BlY|g L?\]y)[IJRO+N= ⟌L"?E#(9{iUbnqH8[\dl˵V+nK[8̌Vlk_;%ǿ?};e5?>5׾_6/{ZtV9eՊ_v6/;u~ Vl2V6U8u2r7欿7wf2:Gg=B^15fxOĿgDEbDOR=5%)v&Yw\=&LsƝ\ՌҊ,-Hd59;b|0]YD@U`H]zgHӚz2z1ϿhzJbL r梁6iŒ#yxefQÇC퇥ETdO)(~WN*^_vai.)w3Tdri"2`@ -P -Ԡ-@0  ,x~G",'%9D˫h3~ ˛j *ͣ"Ē[y[q#Op%Fxv jqbErĊĊ+01+01lJER]_ڸ"zyf5"YbD^4E0yQa"by3LD'c6\<\ӈeLџ oᘋ}'̹WI:=AM=A{0ﶧ$%bXawу4"Yu;·1X|;M9u0*$.ZNdTkK{BFC"vgZC7""t\˒("R06,a](RSf#"yvynw&DFQ"(R8"rq5"2`@ -P -Ԡ-@0  ,x~GN,'/'\UEhzxmM"bhFFX򉵏ڈc8[k6ӼogD&1"AZDda"#"ED&"lJD,/."֋kZ*FobD\bDa"OȰe&ԈF՛8*DD~0<Dd -Hϸq&\."$Rx&EMSb]vg'';퉮$glNL<9"%;'iD2% "ܕHSPeV̮4mSm@rHh8Z.1f>™m#"`hgTu.KKHU&7#՘D*1dy zfnVDH-Dzf~5"2`@ -P -Ԡ-@0  ,x~G%R)"(D5O݂@Ж *XAL#<"RO32[[x['qgq -S(Tu J,F,1"QQ(qQǦDeϗOHE$*:eFbEmXcbEta*/Zp뙻>"vTXϼz7FTd,eQ>S" 0ނ 8*2$=*tDl0v{x6H+<"~Vdi7Q!M·CƜ$];Е3FC4~g>yXFwvF|ĈCgbDFň&"#2*F0리g(3ïgEn(!]F.CD1Lߍȟ_FD޼c.\|ؒ3)"x+:ow$7.6%5:5ML9%"W,҈y)14KW]?Z|"nJ5]s +Xo3滱.LwsZD$]D; -wYJ6D$YFDzOۥ>Dzfiz<ބxDrPT hAz0L`(l' ` >C8 @?J/[LA~(y(QMI)%t!"0xS EfiDeAޱ_pNF<#jkdQDfE -ĈHQ09kS"rWpQ$-zH/Wke̲\F뙮^Lqf\Y|D4L"ϬM"+s,s^c^f{>)&#e ,ފ8gRD\d{rR0&'ckOJJq'E{|rDwW ӈ|" ,_@DrB!eUVpIHuC豀n3 FCsHsgZ5#" ;JeoDDDDCDFfD-!"=#"}6T7 " HU""2غ|L0 (AjЀtfH Xp ?+VόA((XP -JA[+O8-Ј H`֑=Rp 1mL 'a3bD^#rv$yMa")O߯WkM+R$S#r8)N" 7L6Ԉ1%#"XHIu"'|W1<"rLHr bvvw;DzIIAO0&)ԋ">iD$Kb;]1ҕ.Gz5 iC;%G8SAL"]ŒȚQ9u,|Ķ~X,;xXzz{?y* ,#"rLvNMcʀ9(@ *P=&0CDBX -6p\!%_EzyyQd,gyu!iP$S]δId,C8sw w[gg0'C'^)#MHF>LD#ˆj\) _99";q9^DC'F1"Ka"[1"Ĉ޿G䷛0xg6DKk~yH>SD_{cg#"??3IDM;vw*ggc\SMNƲS3jC_ſ5?uηeCD#4#-wneG)74=7kMZs̯#l;;\cZ'oR"82ɗCHo W&kGKS縷ɜP3ɀ9(@ *P=&0CDBX -6p\!XuhUժj5CZmVW/j ۍZSvu;1 ljm Zgvע=og'ެy Ũ -^,FYkň SJkߋQ)Ũ{7rblpCwa*j]\,ƼnGb1~O#޿_)TNر㪷vd?Z1~4bafo ňqkK~1vDOr99a)uF]ENMf1qx='1hW3%b4ι5:v4`s]zCҕ MxHUҴkwBZaM]k8ӑ8s{#ƎLKUw.Ko+Ǝ 1v4߉f];J1v-#ayKƎ]jưviQٻvǔrPT hAz0L`(l' ` >C8 @? fZOB^f8zCh눮Oθ̛2ڥ&ړ`$ ޙǻytv'_go$޲=&"*1"{ı#6LDlOLն]#r/yD;a.\#av]9aok];.He,UFD$K>S%p1v\3*")x).XkƧўTORic|ڥ~I"˿y3~. -)ʋҕ=lkH=Lk:DE_sѐ9E/qZ<;t "; -wYvFD*DvYhAD۱v-k7"]>ADf""cFS AJP4F0" V7x/XI(gyyQUQhZ$i_0kTK`Me'8FJ!S %2n#=LDK1"DM_EED$rdd>~S -72)WQ+`lBN|ֲ]!Wc2-O+/i;&GQIJr'z<$O"G;]vۓ?b='G$?wiHݼDV6u)&G= i{ސztMUI*[85xZbl}35p҈ Dkg]vD=L" ňHo>"R""rw%l{ËDJ^FD0O""=H˫ǔrPT hAz0L`(l' ` >C8 @?4';1|Se3qj^.6VqbzN"͜X{8[8;;9Wn=y9_>=1"Dn#l~F7g~i"rzw;$rLSĈgy);5"H&G^rZDʲG0{Gf1MD$zLMeSS`*&OО ٓ\l|3=VLGV3 ١K+?KS}0etЧiچnhS˫9Rg,$қ;2M3]3HKHQ!"Rx+"2"{"/"2G{ "";?zi5"2`@ -P -Ԡ-@0  ,x~cu9SJyfgEU Dk9m;xSDI(o$Yޖ#8w-nɳ󼷗~&" ?5$FD#0ٺ)E"ʕZȢO'efY={+nތ߿o羌6c9ҵk"w*̷oDD˙Kr3Q &AO -Y˞cILLNI rMu7)2ݟ8y'MXPU G5]iڥѐ~aCb܏IA\ &\"R;Tk"9mH~~Dn -AD^ADʗڷ!Ddxd5"2`@ -P -Ԡ-@0  ,x~G)%aŒyh㼪OPM 6A_I XHLtɡsy$o!~,8Wp󞌕'eY-βe1"_ eq9# ٦Ddg3_/"|{TfÝe#pg6嚈Fd;"8"brˏ,7 ,sƼNzui GD1sgԓ2ɉd1)j0iOIYWǕ&'|FhI"޿Y;ˆo )r?MS TK-!!Mgiڮy$yh|>)Sƙ9K"YuOoe9z"2څߏTvJGnFۥzDd^o!DCDKk;1eAZЁ `!"! -,`8 .pXЏr(A%0&A1!(Uc7As4A;۟o %3L%"2N#Ғ`Y-g%wJ`G >J8!"wY#rU@%bD$a"¦DD76"ے^E:TUbE"+rXOìg+2"쏨ȵXa^ȏֲy2҇-Xx뙫1}œ0x&U$ŝ⊉c.T$M]A=Lt%lL)E&2v>2HէDf-Td ?$M9]PLE!=.-5:@EL圹UTd}nxg,""t{!ݟ*t7FwQᛰ_m|VTd~/EE[> 5h@ :Ѓ`3D@$D`8n ^X䞙 +$LG(eQj4A=,hm@7 cV)Voekk`k&J,\݂Y %Y_" -$VD"+aE.lFEnOi6˾#jq=0>{eMEn0\I2cVbY[D.^|gG Ocy/ڟRՇeHl}Ln-3 uXϔ3%CH1DVu^)<, ]OM""Z> 5h@ :Ѓ`3D@$D`8n ^QZ/!t9JPP $Mt CY OH KlCݟژZ "ͺĻ,*'gň0bD*ĈTȗD%FbD&"ME>n:4S׍ȳzD==n'^Y6|ܮoSnό}]{vL".q21kI/b2RI[/0?<}&Ϥ$It/Y;NJJ':AO0%5&MMeOٟڦ(@+{ {1s*yM -E -e_RMݩP(4w(_ ީw/5,ݘ4cjjgEyFe|5KQYrj[2αU~wgrfor7~=g~;[蹾>84Mn4L]> 5h@ z0L`(l' ` >C8 @?J7#f -F^(4bM1F?c`2hb,M0r*wgع~W"cXVOm[+bH?_H֊f?!/zBꯨ@z>%%=+W)%@ Ђ`#  Q`+Np<|p@~%N61-QtQMV^3kN?ꈱލiwc:yK[坳+w=<{+x[ostenօ\BMY{fzJN\0˒6<ܲ7<ܛzfsٷz-XskG" ߿_r _9/㘋KcXQq'9bY;փ#%6Şm F\1Swg>Hd.eFgtEGmᶀj>7}:]\6΀0'Gj8SV-genΨ]D]D0"RTCD`R֧zDD^ADjzo[]cʀ9(@ *P=&0CDBX -6p\!XE$ό"Qe*#zZH"zb">TFW/t)Xk& I\y{R 6GN?BjFY$SolToltsvrK ׋s#'ms0g0YqmJD$'1{8}h-"ז""Gt ">SDz)͋s3jt'Wݝ8lb0hw%gISݺ{fesYDV|f -)JӔSU`HݜT/}aPSc"2Vϙ;FD҈cGDnD$Rg~a)CDƹO>D#DdaN"}Ոcʀ9(@ *P=&0CDBX -6p\![}DGQ^NSWuu.,p)^KՂ (@'^b%!Vr6x x_wnٖ0}C52LD~fkY/?$|׻\3bDLa"ϟ#b -?lʾ] -t/1:[Ƚ%H,Wc>S?soqL9WuuK7II$r&1vM'%]qqlLRZbr~n3 E;7OCte13-!Mڂ'c!}CPB:p ,gZFDvFܴYdtnL"/`YCD: [[IO)FDJ B W#)%@ Ђ`#  Q`+Np<|p@~"%^^FDDTDI4 vs;xo!e-Ě-JcJpWFIlv_ ls#FDuT,:p}!W:ƇiYHȟ72H "鱜9̮ѽ i˙gIss~ 8~`.`9S$ap&E$qǰ8{trNN)ɉvgt%x1G>"tn֢DLݡڸG14+!uu!MŇic{G3DD&LpiDZ2rތyBf_GDWy!"R"f""9zDdDj5"2`@ -P -Ԡ-@0  ,x~V-!<+*[P51c`ʧ{zG\+Dj;{+$ܾCG '͉3r ~e{e/r1K^D']\6*`:k"]3-?;zWHSDL#9s1DQ7첔܊BD&2,T#-v{YgJ'Ddx >D6DdaKJ[> 5h@ :Ѓ`3D@$D`8n ^Qlc|WLRAϫ{xMG#Rsa7L#DxK;o'ޑ%;x_f_>oMe,gDfq=&"Dd˧:$eʓ?|L=x w7.{ge$2?p)3ODti[tMm337t݅!}FCnkX^ř9`Q4bN" ;1LI~tm9SJOPAD;!rIz}ނ DH "E'_+)%@ Ђ`#  Q`+Np<|p@~+ͭ,gr (2K-=@$[0 zbj˙E:R,X m8Ɖ^pZ9˽fy˙0xň\)N"_ bDltM}{z{g'ms{m$rΆ˙q9'LDnJDd?̾|D߈|۰9cAC+KC&p+]ܪ93lJ }X7:1uݱN'LM -G۬\p#sgyb)[ʒFjo4[ښn[n 7kMZskQgGe-|`+{wUrUr5^O<6𷽣^] q[>LC$%@ Ђ`#  Q`+Np<|p@~FS˴jZEVYUUhZMVۭUjG3f-Quz\-Gnݯj#_wj]Z!爖m^9F/9FnŸL,Fjb+]+eb1Rcߊ݌fWUVkXœ[fSUرfS_xOaqYC`/M86bh1v\r9QW};c,Z]`7b_cu5ΕN;ޙlOL )+9{ڥm+ĜQx)3ctEiʊpH}Xft!M[Pյ ͓HZ|V 7oiT=,bhƎRg}пcGG9Ǝe]!Soa쨥T~t!|L0 (AjЀtfH Xp ?+VNb^Q)y"O-*Y0rxCw4ko[&*jqew#i%+/}S?Z&.DDĵ˶0Qnq4@]<#ֲE;>8{ M;_ ЯMqoCg}%&.|:cmäaY4QiDRqzp;wY~9uzZHK֒D֟|* ):LS>Pe=ׅ4iG澐~_)1.Upr\y4b(.9ʸuP%"DmCD:ަWQW>:K=vvަgv@Df7t+d]1eAZЁ `!"! -,`8 .pXЏROdXHӼro&l]uD_C 17҈,r52O!"}MHADd~]{)ȇH"R|"22%%eOcʀ9(@ *P=&0CDBX -6p\!X+$Ȗ B(kU6QW -fA%&(oX&f4G/4M52,Xq "J3N6[L˹NHVI$[H0#ҿee1Nsd5ad'e>q'e.ߔB/ )D|6}ε:*/3#" "ފ)F\Cz3QtJl=`wn;oOvEDdz11'Gdj>RDK1znHb4,g5kJꐾhm{Jb,+LS8s,}BBf$R:B'qFLDw$l#tk}tj/" "2F ]D1eAZЁ `!"! -,`8 .pXЏ,Z Ӟ ϽT!(/Q&%h -m-İ 3S&K#2`#6Cc3#^E< %E?}0 saM!y"LD.ޔ|T?zٟׄ;"F weSܹ\,gJ|NId"r/2݊IDr)3Co-!"QD1h;9 `c`Гt{j^4"Ո;gY=iʉ怪<iF>KNb9S1wk4?sb<"RC'; -wY$RCcHÈ=iHY^T"2C܏)f&1kuǔrPT hAz0L`(l' ` >C8 @?8fY iThman7 "b .:k"y`m"1;GakrND܊#rdŋ/+ڔ\jFSv9ׄ9 xe Ŧ<*ПWq0֙ES"=LQSEJS}~ƙU<0B;b`뙤;AL!ΔS^]sVvw-Hd9E;1:Lͥ+OSTyXTiB#uFCG=*r>δQ6iD *R"'_CE"cSH绨H2 0ޱ[Ӈ4 5h@ :Ѓ`3D@$D`8n ^QFx)"|^+GxM""?&yc`P&Zbb%8Jsw L~\$V"V +bٔ֬:zf݋":&"Po6 ňl)܃o\zmkZQd>oyD$>SDd '9&pA"Iq% F`˞os:)qIlJ)}CYǗ2թtEp8j&5Miڅ;C;$Ɖi9g>:Qd^YSr^YCDADZW-}4}TfS"% MìnD =Fov 5h@ :Ѓ`3D@$D`8n ^Q.pL=/+xUFNSiy]/ ]q3Ј7B eZ29G5]ubO+ǎ_m -r8߉o( ?\{3qox{DDWF׋dI'^%1"6Hm?ތWb[ϼp #Cbu2Okg}7UQL"O""/Om?DD|f튉1;>*:&qa6g}xEB?ǘD޿뙱iʲUxH7IӖcFI%3V3ПȉڟF\r""q"RA4E *z -uu?"ҔO7_O_3V32`@ -P -Ԡ-@0  ,x~Gid}SAD1E^Y"c(ѷ/瞧" bOLY@\{xzxN_"OXH0LD)F$ ܔr{Rfz<齺^س7ڟXoݞ)!Y0ݴvQ@MUU9ڿ#"/OG201\ -˙WάwSqlrR=r&9obIINLrcOۘ{cqcYi&o(]1a*+M״ "·CcGDVaGD?5Ddj ig-. "so]V3ค-~Oޢ1D=MD|> 5h@ :Ѓ`3D@$D`8n ^i\}RfO -, \^]M4vM1En4"G..#ggKwsnl [Ĉ Ĉ"Fdg)yriv]^Dt5aNPTD4a"m1"n̷7ey2E`9sȯ~G~2݈i>S?0Q&V!"_QEbcq=6cwzq1{J+:5:t+F]=D6;>(IWT!uɛBL"w%FCc+745Hv1"2,"pi[$rh3iO܈W'e*Ոcʀ9(@ *P=&0CDBX -6p\!'e矗Uzyu;3+*D&61Go{+#LTrK׭G>8>:Ǖ]̹gù_|Ko@[X6]=y 5h@ :Ѓ`3D@$D`8n ^7QlMmeg**lUo,n0F_sS=yd+ՒXVGf\9{agl+;;W+N< 1񤘌Wגq~d]kߓ!ߔ]_F0Kd\!9)&got37%#nƬ[2;Ⱦl:zd;~ ҂d\s+|Ʒ%r9;GBCeg#1qў=gwy{j -8':evYSӃE[]Ր`H3o/[أo}h(|rTSΙGoƌӨ]2G/=rtG<zu7yQL_"3ԆPCu7c2vˀ9(@ *P=&0CDBX -6p\!՗t~"*(͂HP vmt>[LuTAW/+0K/oI &9rL l%t_Hg%CUf wPݷ)ǟ~umXfLI'2ab^; ^MY̥\F߬keK?[."2*Dty>S^̅`3}v`\0eOJLEDd$9dSb`OmN07c>3s˞:[8]Dֽ4{6kls2ͧ[ ϦDEw:7Xf:YY79֝t ׭j#KNDd03]YKwޣ\H̾m rBjaSb̭L 56igtѝQvYud7:'sK]3݈H}ROA9&ޏz?;TEDDՈcʀ9(@ *P=&0CDBX -6p\!sgQB_=+=qhW/zaMpN0Coƴӈ fځl ٿr3T%2_WgeZ7Z7aS"5wQqt̝̙NsqG$8g:a 89{Wj:)cZ.H )JHBSDL`"IChF#""x I{./p/>f9ޫ$۬7 7DڬH*[^"y3qKH]SfHcn_D+T&HJ_HIHKJ\N6tLw;ӲĔ.;ٓ^:W[RmYgQmL($ -/;󚹱\];9c;hl !zD*뮌-Ǚօ❶H1: :$z yi$2Ql}yYT +eL`hX@, -l 8><Ĭ#LQBzQs^Ў q\0hǙzmm8ʤ ."liq;-ʉVD -KM2/,DDvVloJ"Hd2bwpuHO~l͐x8RuW&GDg|GsH H-C"N6ەLMⲝ4w;);5E@5WERYH\~Nf H\Ur ADW\|o) ̦#QÂ{~c{ovL@"g!'VDz?S7w~!ڵWLIiǙ#Nd/Q I9*%ZNDBaA4N D -^DA"-W$T( -h Ă8  <^~ wsJd*D͢t}DЭ2Ȅ`-}EwJdD -Eל>/mK :q]LְD2%5bDRSWBkje_g>Bć%g2e4}5WnHDwE"}HǙ G|W$C" ȦA"y=,$#33Mvf'H;әJ'%qDbudKǙ(Em ڭ j rսnu%—yğfS$b{h{HskH)X[e.ыզ QHh$rVw>IWSy!ufC"~,|0@ T@ 4@ t@ L , āx`6`\ X,K((;@RK*I[JtS~hZ dlv[O#9JuJrKl5tE- -]%a() KMf -a<)) I$veGa2Ej9q;"έqbO8VBOB"qLp$磿G!N?Gg6TII$&yT'9LM1'13%-1yUjպO=ocgBo*G=pל=V+$G? ox&h=n6#$rAb>½QUnmiZ!c=tIfʼnhWKщhw H;CAGP=:PFR( -h Ă8  <^~b8/2}D'jzJ툨K4Sh,Zj9M"z'REńʌ -";D<7-x[B׋kH$ZF"߉Xd$-SDHw"?dZ"ouU݃2 w-bBkѰE2Y(^S*:`&` @<H.,x@dVH3*)/TuI@-i$ݱd7` -.󞀥=49` ث%G0@\Ӓ7OM+w7}/<&cn"uSuk4=$pRy Hdbg&[E6'̍޳Fy2RRδ YYWZJV;+3;mzm<3Q/1M'Vݪs-gxM$ro\)ЃDWI!Bmy֑=;m7ޖ"q)癱7OF/}py8O"_֯>CE*[W,RM-r',g|3>~ȋj#&qg;ciY2IjI$=)911;;eU]=K ȟdԎ媪/ _53o4ֶql~,BlۇH$,RQmkN[~X5X ,Rxx!"o";yn_!t4%"Q PP 00 q X ؁$px ~JO@Q`$eZ]fh爮 %cd $K "crH37,m\S q#d2HLje"-E [D-cb=]"\"SW^ҰEN"_gҰEN"@cQ 2V6ĊEYKc=<,Jh"75[; ʛqgrd7v\*=2ى̬䌤UwW+(EA6f*P|QO񚓭AmBn9W_ o( GBfKD;"LND|kbm8Lh.]y)"~@3 ,7E?"QXy"L`hX@, -l 8><SNa"4vNMh!Vb 2GȥUEEGH͎Qfh%/YK2-wE42-b[D#cXT_Tk,Wf.,d$b Kd.,d$b]<w*D~b#/l{QtGD6p9Hd†:$gz .֝d3Ym4gZj;-;IVm(U7ޤ~Y=ekU7ێ*5u%6^TNm6FsKɘ[|%Ei_e;ho:lq0't nq5^gGnL5h=?6o%/޺tPV( -h Ă8  <^~/W21USzhbt#1Cm>Tc1Jb`[w310qư11\swRݙe7ܝ)^A7c"/9c^21N;R|W?ogR$0߸S˜n$cdO3+5d3S]δ̴$gjJjzb&W= ,Gz/W5QTKG;*ׂi{K!xOWt\7 zr5#cm{1@/ў(n< ixV"58bG۫˱ PP 00 q X ؁$px J#aEe%Q3@hh&.0)ϊH,5Ǝ&b$ -d$\"M )k /Ǝ{e#v#;#|c]b?DE~]Wsg¹z:e,r\<S)#ဪg%M]@ %dI%1z2L%rNK)☖&>= B+ x[%Y_/ wKTGXzj]$jZK"2\3ǘqSȸ)z\}luٽWVvdrE"9Hd.د[Di92$ݐoF_D75jەr&R + й$ьUuw.U}( mc ރA=꣯頶6,D^ #AcetlIBlY$wDZ_a-i;FKTcL+m9FKT$OQϿ$2, y'DA""= \e-K PP 00 q X ؁$px 2D!2HPu:QBQԏ ֹKV6.EGP/E֋|O n_VǝR<qMa%HļZ2k]̼8 _t_t>9 ![]!$ -~3 ?S -0Hul$rn!seN6-3ՙ̺٬+5ۓJ RqI"?Rtmcz?{2'rUS9ꑣ/-z'W4{74~lej, B&$}H"wX'iIH-QmwEH""TW!<=ŵ|ug 9HHYH] -%P5-=0#03X؀8@p7`p |x \:A#) :9QŒB2 yX -h)WBL{?qK WqؾgpEoE7*gֺebl}1 HISD:iyX"a4pr'ҽe$җmH"?]}9)$%|;ZDnLli+Mq{IN֓Lg3P~3 C"O g؊}H"t -Pu;mݐH{8CMG|IB?$R]$R]NW= Y|xD4?'HdHg*@@@bAV`v ܀/?HfQQ)1墲*DuєP*.-"b3t -Ѭdk'cc$4JEFS$B5wH_{I=w1EEzκHD[qYV2+D.7,"n]}kV4 -j8c?}w̏_{; __XpgK (-v̽Tcչ?`1ccD.i$!3Cgsvʟ^BtVm2 """""""""R)""nיSƵ>,$,{?;4tf^}˖-]bGJ+;9gXxG_LNؗt:{E2|mKWX[]NW3&:v3ec{=/nvı1#p9ĸdG|R%Ĥ&G_Ӑ? %(Nd_+Dp{PQijGuy3i7h)121>#߹+$b᯻#gsX8Z}澁#PWbg(+{'C7xC⎶P\S{C;^ -%T( -h p"؀D'p< |x@} A1CD.ꈺQ ;![S`.,Bؖ %rV۸`ZgAT`+{pij(z2o![w˶пڐ-1'LM:*9'?UKhOgg$f~HIKEh/[-RIb Mn - -MaCCp[<[[|[[-9}]]{9/'5;Nky]N^AQkWIk流U/x从Ck -}WW6/vmQwJO\̗x?8.nr31֭Oz?o1Ws 1?XEOH*vŲɎ6%nG3 &KNIJ I6c?Ixu ;}?M]_SԶuYiMf­=!rR[ɇA"WGﱶVA"B"!w QHd8Pvq9T DJk HdaHU1JjZz`F`f`a DH`6`Q \n@%(\Q%ʈhzqAwPԷ41YF,e"$ B"ClӂXU,(fDψ}dH$DZF"I ۲&DONHI[O"+Z/I䷒DD$IdDn$%I"d$r˦H%i,Dj Hm$bL0]/~fs<# -}HlH;&&)5ѩqƞ*̽oR4/(>ɌC"cS骪i''ڦwuci5~Cgc84K!JR6L$ȦXD߀DDZtNB"GD!XdYXs"xL`hX@ X Ap`pG)"9AΫk׶ Px1 zZf"[j=;\[,^ L|ɔ,EHdIXj"_,byS,]qERER7"/_JYȇ׮볒D42y'wM"J@F"ߐďh ,xDȾ(J*hٝR1۵5eƧ|LW3̎ǾEmƥI"InW*F;X.5Ots#ɁS%ޕzf!Drx"$z4]4uC_zOP[֔+;]_yW4?`64<bdaB"3B"%>/\-g -%P5-=0#030"@$(.7xXY)"/(TzhrD,ѵY Eb'棂JJXmثH qW aw2E [?';e$R(IےD,29Wȷ%e$rH$-AsuߪV޵JzBȻDe$]ȻDe$}S:8]"63_b1Hޖ5|sy?c"^ ?xƷ$?:lJl\#:J䈏gl I1J/{a*Em)$]4uC~MvuھCA]iAcCٴ<#!>,;<$ՑkDǑDIdIkI$aH%雇D߿ D $9$Rދ$R(jZS*:`&`AV`vX/?[ԈLTV -LQ=J4U6+.䉆ZXNLDsh3ty\ΉUՐ.w v$KPKH$"#FH$"#fS$=_:D' $D~+I$D'2Dn? gDXݷ$1M}|'O}Gᙫ~w1$RpVD)θxGJ-gIq899w4Tg=ZO%2a1cgHdtUHo_S|GP?mHgU '̦C弥>>; 4T(cD ZHϐ~DFE9S$q`'9X dB"{xU"xL`hX@ X Ap`pzuW LQrA*h -[A{"+=9<"XFv@"M&X[h -JF.1+x3_I>I"I"K%(e$r$^RF"lDr.:U"?Dn^O"ʻ>,Q$D&Dd$rX$9}[[eWghk{!S9+{Id5/o3@"1 7'&ݮ$s8S6%>':XWtƝ:Sw"CE'v25srFPٜ^ׯοN{I=]_7 46M 3HC%a>l&OX;@"R|B(g <$H} >H8]} Y|)ZS*:`&`AV`vX/?+HPyQ-u/ cĐ`<&Dsh)q*ژ`'鄨Ĺ@\5"{3DE%x;{"eDD%<#I {"H +Z-DHȬD%HH$[WgDU뿀D oGIQ5VF9s3k\@`c1qH"l -$;ӶVƞPОI1uVGdӭ꣏9vchm/b10~w]Pƍg2cI\#%)ʈӕprl\rRl,?Uu{V: S(jT]. ڞA]d~oy:cAi,;<⥵@2w̃WGݹZUۏQ[Q9Q0ekKGp ~ -c/tA>VsS*:`&`AV`vX/?[-^b0DYj&!A3C 6Q,еb9RO; 2N˼mVil]K&NuO%q lP}KorrW6^StoY=. I"&)m/}J;\hi 'S/&/W}rfcwrktk$$21}F '!y&n=Hݳt-~[s.%P5-=0#030"@$(.7xHj9әM`:D嬨չ_ ڜ狆qbI0-$[Eф݌kGm ^'F-Σ "{,]%x*]}C H$$DB%xd$r$PI"ܴ)a~r]$$i#Z$M.D_$XDۨ'?͆DmDH"eF1WKtsHIwVIr'$肮$:b٘@,NI:M"{O<3$RZ_[rC~huڢ;t}YNXT\qZˇe4veD4/,c{;A"GB-yɹ?X DF!H/ȉɇD!|H$ɇD!|H$ɇD!|H$ɇD!|H$ɇD!|H$ɇD!|H$ɇD!|H$ɇD!|H$ɇDW$(*~Q9BTO拺hX qbnKTҞH։{5#:DWN4.rӌ$pC2B>;oW,I\I)$rD.|^ B-g&WvHBċ7:)I"WJI6E"?DTO!kvvC?˶߅$\=Xrft Idk$u$%G!Gj|rl3}DF?&?R/(?twC"OMS|Яɸ;HM<_o(-h|l*;Ĝq7o鸇~6VGD*vGdMmH="sI$ڿwH"5{'*D>y6WS %#nrc*@@@@8 -l ܀>< >J("QBwL݀)A,=nD{":OlC'D,q5 l%\)' -WCwH$v?##$vi|##Hw\s]!2B$%2Lq)\"#/]M -P& '~1C"OQ6ga6 $2oY|k{ I6$##DiII&iIdtIdećH+H"B"߃DZD2^_M"xL`hX@ X Ap`pG%iI^4mD[FtCD?fWe`=b}ZIb?]]"= w^-~^" -CHD.[HD.{S$r 9n$ $2,ID##]_̘D޽_8~Hܵ]! w#s#$ᥟ"tgJٱOHٕDb㜁X3छܝ. hwJj|C264D!v1sct澠ݯɛju%OY*Ƒ6HIo/Ú Y$]9yk̓t*]⭡3GDDr D_Djy_Ii=:H=Hq!{JjZz`F`f`a DH`6`Q \n՞H/QdLU)AS#fQB4cniO$XDi9S`#f>%Fg t{< zΗ9n(Ig2$(Ig2%6Kq$:9GR|b36.%ϩ[PEJ`]LqXdpPkjG"ɠ̦?=<֘ 4S ;r -QZdr~=St? ,"+,Dw;j<0@ T@ 4@ t@ L ,   -8 8^~V&30azu&C4McrEsj"CCL)ql~pK. -B&+Y$tR HئX6m(rzyuʻ>$E>ȱ %J9 M6;]9YO! ($"+~7x5ȕ<$&l p's)6:&ϥ:gbrLR;)yD殩zU*F3ss;|:THSgQ\ ]W0mf]gl*-%|{GBsL;7)kuij3!3OC"ctmGP ͽSW ~lo ',Yc*@@@@8 -l ܀>< >qh%LTLjhFV:>gej51ՌHI΋b&QS9"z #g7I<%w ->/(R$Id$H"I"%PF"mD3Cs=1sNn*" ])yٸNjDElY"VEQ SX|q5\v9,YguƥƲ@l-,ƶnswZR'{7soQ/c?M[%{|G6{l{p]S2ȯd2ȅ.#cK2ȏ7oTt2l[ǿl GA.K?1֫ah7a.VYeظd6>ᎍIE!t$%:R@അ.uA:BCdh!q4uU_Sv}$M?Z744?a65 ujx؃|XG>tkqHHDV܇ J!ё#B!3Mͪ2Z>•g!?O!3kG!3wtc*@@@@8 -l ܀>< L ϴ tȔ>&hrv\5o(#R geA8yl`BTq Մ+';zX"wJ1JX$TQN"b߭ި`C'NaٱEI"Hٻ%| E2g.ޔf]J^"K n:8Q&˿K{cAK?1Q_?{aLZ"aY58źSS4jHu$]-4߹+;ާCb>j(3] >;A~CX4G!Yj>lfiʂD2cDJ>D:z!;!.pMt@"D2 {{>Dd| -OA"}CtuwtU"xL`hX@ X Ap`pyuu7?A1)2%墦_fJA?* -D`բ !zѶ(Ũ,]"^$\"wwHnI"'6I"'6M>V/YcI" D2I&I" D2I;6caf[Dhc"kݐ ߬C"Ix?㯼gv߀:fjƥ$R)qs;]2I|lRbtL|;!S{'nV(D1s.f0x2*=]=4U麂a:cfSevWyAR06$f^Y{k[3$RA[}mH݉PHAHw2;;= fy/B"3t{~D -%P5-=0#030"@$(.7xRX&LUzh"QW,DD*Vb[HȜh'E^*Fg.ѝ5nJoxjxًDD$V3cO}n?s$$HY59'9;`ؔ$ӝr&Yi2eY{_\Y)Z Q̕b|jb.M_ӊr麎tҳ~Ack4:$2 Wa%DNffvG.B"oB"tb4$&={8WBy j:`aHd+y^S4ĪD -%P5-=0#030"@$(.7x8R̐ԓh;A'yb,LĜG,3:@H;#f -gƈAp- l2BWwN\ )gn$.# $$I$\F"lD -:D߬'SwoYz"eʙpI"ȂL9)9g D1v'sOhC"g'$R1vH$Nd3~ds sɯϪCl\lLbXf.Kvcc]x6u`ejʙһv1#T"EUIW45#AraP7=o}o(ːD- l3oMՑ65@" DƨD -5UyT=ru||D? 4<*<0@ T@ 4@ t@ L ,   -8 8^~4^TWj-uD?H g6%,Mtq&,:KlKHUOa2r걙BraWm8`$6p٦HdF{ru˙"H }S>6af$r$w?Y+gt"c(gى$RLTm3;n~/{gf蘸+%1dHNeSScWyDYaHdz*]5v:߯k j?M-/>7^o+2[ -ޒ]ʇJoz9P C"t ǝKH;!\7xDC"tj)$Rܶ*<0@ T@ 4@ t@ L ,   -8 8^~z Q fXUzPd2ѷAط4̛;&DzDk FG+Cd &\I|ATAtû!>+g.nݦHUȺy[yeVg!DZ$\ #_Hi i̐/gDi&2SOqLH"US^~;~ \r $Rqv5V1l 爏l|j#)%.ّRԔz"_x?ʹpEyLseʮ:_ݺU3\~|jz|9\bSZZ_VMYȗ#;FV5_U?au:/gg7)gl:;72kZJjZz`F`f`a DH`6`Q \nrpx"c+SUYUկTot)3JݒR_ՐXT<,(鞲0 vm^iUYռWr Jόһ-)?gfIhqfwdq4dki 9k*cO55ױ-Abu{xI˒2|2t2JI(+I_ܮ ob.e.9pbxv$rѰK -Ǧp)q2\X>G&tK~v6+/M׷ coufӱ*XdA2Og} -E*̅ET_wҫ zEVzD= -bT/Gy -Zh NSmEEZE:t9/*;WVt{DͲu5Ecc)O4Z:~h@&uJ5U]% l9S-zȩ+YdE'YD-c%|OV"oEřf;}~ BL$ȍD$e$rHlyz"lEl81_q܁(2mOQG9\u $UgU $16RԀu8\JtwŞ69$,χ(bUMwUPPB>oh*ED*!*znGhGHӉݑե{O>1-HcH$?\͐Hjp=H$^J$RMWtD&ZS*:`&`AV`vX/?QxAT%DkkneśJ<-ޱ;%X'K >.U. ;Zu$rDl$bp$zS$-gXcg#_$rD%"Iu?_g$RcJľG]iܛ1D_~fEgigϪC2`SщKfSbU\٣+R!4d!A3ciJH2N Ҡl:L[:jhIvjwdHw$JHƣb?=$3m!4U</@"B"}4T$I畝us;bBdrBPՈJ'ڶC2]aigK#]u[H+7w5O}C_d.($bbS$8S!n|F O])3Qn"6C4Z9Dޮ@9D~P 3DLa~gWF@"gHXɹ@\RIќKg㓜)N|>j7DDT}H%T |^~ʯy-{n􁠾o(;4a6~BaVr;H␈ݑs6wrfi3 #tji9=H<'fA"C"3f-g^]S*:`&`AV`vX/?)^8+*GPTzQ4">b(4A51DQ.mH骚OG?Jוz_3:ͦ޲xKUV>$21Gg:2xڥs aH0RӞHGH"9=m/B":I!V=Lح\S*:`&`AV`vX/?Q aVvu3dXmD:ì*q2LsT"ClyC݄B'O _zs?+gr>[p$x7ɼS$RSl|hWuǩ_y+fB%lE^,GgL2yyS.٭i̔~X̮Z uHgF.@= /c~fGJS< %nM"T cfSA,0oi~;=YȜ=ֆg!: Pgadg#"z6E -u"ϭZ PP 00  D+;N,pxNB$2 +3̊EQQ["2}h4%[0"twFN b/O3 YXDpI,oXw o%x7|2:,2ÕW~$"32e%$6"ޔ-lZdUk\EJc"YdCcf+/+$gE\3.9RY68#ޕĹc\iY$ogJAIL,ҒT-|EƺڮA]݇igQм47Mv-=HA ,Kj:=a"Y:C'!f uye#2]}x,8, ̟l1JjZz`F`f`a DH`6`Q \njQbLd2YD5-3EM]u ~J4hL0%XJ%'X3D[h/*1vבI;%J),r],r]M њ3zy79$]%hɮ_{lRDI륐G!="۾륟+W:QdƳI"n|tpɱ61IF}&%%˴XY] -Q|i(QNUo!45KS麼O }ALT@#ox| - ~gwdAk0] E̬D YHdH?t?@"sGBmKH_!U1JjZz`F`f`a DH`6`Q \n@e( L&Q -#|Q;.z⮋"K9G\={4yzL{ :2@lj{p-"bQ~(b$H˻DXl;"ﺗw=~de&Q·7:,GFv)4ٝ"|H"Og!$DD.zWz̜8@Qᙫ~VDΪQf1h# $s@_c#4f\,Yu3N[?Dj!کuYu~CAsfrMg2Z Ϧ;Eȩ= -Sd$9-zeۨgNſ/Dyޤj/"˻;իc*@@@@8 -l ܀>< ;EOd&(TD%h:(2Ds{wۨD vLב( ȻEOG=C|E߳zLOІ{V?B4$ID6 03\]/w6<>`nx|MY9!Z1$rH"?\2+92rf$K?D. D2Ϫ!qIIɎxz]+9@J 玎NN)^w-* !ûO*g4M u]ϧ1l\DjyʙH" 9H"-YDD?g!J$ -$?A"͏"5)3pI=z|V$YHd-HIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII6wQmw/3^%YccbI nbglu;~=_ =;:"E)X;RZY[Ȑa2{ c{9xx?^~??Ay$"zQS%Du^u`hco#9)7㬳Kݼkws̔՟~'II"%\uWQGyf1C?_ܬh"ْD>2D^HaH$Fy94K]]H"ܔ@Q7 g]a2$Ƒ$CUt7LbBɞs?}td -3ӷP;0=Չz5՞* ʞ :1`}9DwId͖][-5nH$2*BDjV !A$!|#T]H"C"E@"g**:`&` $ 'p7` Br6QY'EUqALDFhM܉Ԑ/(ڏ"%DDO)DSD!ڎKD"C?PGH(Jyv)g.VR9|{M3QN*gv8)loWͶ[!{jYDvn_Dr.Gxj=IěqdA y;4=ov^B]{RLʩ6ҽ/mTN53G7j7Jߣe/m4+Uě>Fo -<8K֩s_.X$:{]#.|}3Q)oyK -; -'B;R[֡Q -( T@ 4@ t@ L AH`6`Nn`AY8G qjՕ(N{*N7N C(Tg1~LZg+%U箎ch,Ͷ -ct2SxV9JƸA'[%c c"c5d9?:o+Uj8&DgMRF;dXQA&w엔e+022.vR[.꺄A70(k߾GN]eؐ;iW|CPFi=)#f=)I$Y$Ǒf;lWR!OHkHcC7nws3U>=ִMj: n3tts-.3p m"wtR]ZNv'H]H1|Oӏ1䎢W;'2.d8rǡf^?ڭU+*:?eJ ר3n M# VX> ˽â 5û" "+Dh]"2%D߲{d-I*>Wf>$TŒDʥqD/^ƤqD&7 +T7"wOC"W/ C;Y*8lO~EQg>(SuUMM8Rtv;^#5bRwe!y[O=9T(Z?%CDyP[MFl,GP7?6>w/ŘJp\|H$m=[5!pWD6B D"d$RVIm8DI]o%T -( T@ 4@ t@ L AH`6`Nn`Aez故"=ēqAӜ="jE}hXi!\M/-rB =/lGҘf,eo7sHZuI"\ԲDN[6|VY,rE,rEkbmUt:).ʮ^ȡ ?NsO~E uOqM}KRǺ*.zY G'u0Y!3#5+fBLsajC֚ECſ"ʥEÚswstEmA†&؞S>H~.&Q巷Yzw#"d =EJTN"C:3HSD]2 E:EEǘ%^FjT%9%htE}`( iQ4wmH/5"DR lUC/ CIQ<(Y{e^:ʨw@SEƎ7A. "E܁|G@vYxy_f\Lrr7,8O&|R"6ɯTȱ;8[6# "dwFM'cӓn/HaPFFIz/X,SƓ"u(:wn`=a9ÇgÚ᳹ک%-aCoX;h9>. Yy}setu QEZnXȃE_EoU62d3HoaA2Dux"L@@@ $D`V`v. @p@B -EH7F6d z47à`,LMyhEDkh핢NsdCYѿV"d_,Bq/EO>,z'2QZI"HH5Dkp@3}+4޽E8qjIuYcO~ P uQD?(rmͺfgw6EINtU5+w :ëSv0xoVy4G&1Gew4pn섨ԅ&DXI"$TYl;m/:/+DvJyI&%Id$RטD"Pֳ2kM;YOE )< Yv9 Yr+v_<,rYXEfNJT{H9l|P@ hjZz`F`f@"+;ppx | 8 ,/ʬ#Ez``AHg**:`&` $ 'p7` rn;i^9s<4%YUFPKa\N,K3 yV$92hwwrCyEJey$,bIݒE1|XԞF{_+ꂪI錺gkR=w;JER颮ʁ,Yy$2~QĖ'" ~"Y(}O'2SwIƁ&9f:2RR]))3P|MT($`.>V>Tk ق\\KPadDk8s.~NH;"ݛ-l"g-w "dE*DzDj!cH58+}Phă, -l܀WA*"]":u]digyühM\TZ$ښ{\m{VdDO - -_+U$##&I"G6$Iض_UVϓ}2qFDQD|~ȭo>e,a;ȈK r$BD) R]66TA";U+`G7=dx_)I)H -e3RϗHQ5Q,VoF \Uo[P=LX328o w7 =1;:cL8ϴN]2ά-H|u4zHdH{1$$Ҳ{{oDDZݐcg**:`&` $ 'p7` BѲDiQ ꌨntwYA?#Dcq0HWR,)O#"ukR3&GD^?+&Vt;%\$#JqJHF"\잤3}Iy"\GZ KIz^Ksm'eW#GV$4$=$rH"ɯ]f_a,GH ja3T#%Ʌ$f;Gr73ە2Lytkʼn*$_oz﯈Q.樎#5Kڒ]CPr^qo)!"dQ͖[ 䁦XdL3;,r~Xd/Y,2:,Rsoh,xLV}QdQ+H4L@@@ $D`V`v. @p@ɪM 3wa^u0rWжA?%ch*̧H|3I84fu9!0KjHz)R Yd_"WE&k EADu2[Lc$/G{ѯDS~zH.D{\"Eu k$/3tsGGֺϜJd2dGVF:$UwY2)Y2"F dkwW'TT:?/ -ժsbjպce%jEƚ+Msb{𚄡Y]bc1mYwmr6]jx:wN;7|-yֺk%.0v]/vn(!t:"=w#j@@@ $D`V`v. @p@ShAUbUXRv V7o5 bMEy/Mf1U+k_uuĺ{bXO) -hOl&Sd/JxeR][?]&euMO]*64{$eHRƸo\.e|C)/s=}]vAն'uFcV(#q 8 _&k!J6eJpgK (-vҔ\[qȲ,M,,1c$p,z߶:tf7sι+Z)"RDDD)""CȰ"""RD{m۽ǣ{<{tz||dSCgvhEޓpMٽǞ%\k=9aOEIJILWmn@Rn(i%xEIߦjE+.+˲ΝqL+15ir٘8{sqQ ) IQ@5W>eF{WTt;eD1*{w;ܵ?T1tT~u[T[@*M7S-Z1>jʽ7Ç=" oehD?,e/5ږo uL<"sf\^n/ o &v{랒y&s[d1)#ouB55h@ :Ѓ`3A8D@$X -6p\8|ЯR* %*eZ",)]=яnb&w b#%be9G\u!xnY{s0‡_Xss!rBVhn9ωXOI ='&ğ4iI?~ 5''0Oҟ"4 E0RAEld!YS%[ܲN⑅xe!>Y_B3+lo~ve[ec=q5~li?=>o|/s:W5H\eu/~'~١//gyMU{M8h -g{U¬c?aK?1u_a=Od3wgDEظX;6mOMJthW=!*5NNJLBCcOHo֮"K -/cj"KK٥4~uNg@ӛy6t~}ՆgMƮCMauiT$/;-YըH˫qTdtY;*2Y1s'k̗2* co*|M90%@ Ђ`# a  <y ^Y$jh&mѵe0 S`Q),u\ -9!JKxx>WȤ$URE""_* lJE"3$?W+#RDRD$EZD‚D$]'RD‚D$ߎHS#DD̅W="RM0zE~c)̷*(ΤE'8ݩX6:κR X!N7OHz:B#q|22f(T17,}%MWO=Ќ]O5 cofya9EGL2#2Hkv[Rx(Ww'+ExJ{_yoKF>YPT hAz0L`0 Xp XpUyŔT݂h -m+ ^@{(RJG"2M Ķ; -E$l&F!\|mH/Jɒ"rRD" D=/;9")Ed׬gW~)"HQH"-HDRDzl _nJDu[ЈAD:둟EbD=s91K?1_3ޡaUMs֝IfI1n{ -~βX`Q%DŸqI'G:kWK4"=Y13#D'U5~&omI}ï4`o5g$RYT>b1L,"" DfCK /EDگk0/8ٿ$rWlޙԔh=6!֍$l kOJf٘8wjr)˙{w ӈ}Lex&cʌTO4Yw=iK&c}3:0V҆I&7wDvW, 1DdDIcDHor{<` HaL"}EDo$RzSSr`@JP4F0 " ,`8 .` x >Wi&rET͢z - -ZbhT+r.g:KlM8ׂN -n<ěI|sɔ"" M6%"!_ -8"֫'Uz"_*!]."IU2:yЁ\h[ERn@Enuc=DEz1\u.FzEhGE[ΨQ$:dR,z&eOX+1)1! -=qribﮖ;V..ʗb=3Q@@Y|Gׯ(Ro@;k;W0`2b>Ta{QO0Tf]9uhoH[EP T(xGEPҙݞ3Er1JG1\E55h@ :Ѓ`3A8D@$X -6p\8|Я2F]$(e?Q5 ADEngaGT07,E:H[huPdpxE_!U*OHކ*aE"6"GT](rzRDn"2۵uxË")"]^ WV뙛A3t")c.n؟g.ы5v&E$͹cb1:k=15$ERT,U HBteL+e#X՝4mt~}l0Yg2ЈU*>lqg:hD./ilED*Nzf?"27!"先HX,3)"Rً~ <4c5"r`@JP4F0 " ,`8 .` x >W)$"D9ETقhvRaKESh.vau=S/Zk[/qg q\t -\v'x'E$TH/RDC" zS"2Ȯ[/"WI\aD>D.0"Dvu^|""rDiDtwG=ci{ 1꥟b=D73EXϜ="&svwLrMJqIvWjR"%&]z_YzfeLѷҕ里ڀf8/x<]W 55'BM9590YId) IL"e;-YGHs-H-2WY"RYzDDFEcHDiDr^PT hAz0L`0 Xp XpU戼[dDEirZPQ%j;E]?xa[My1:#"~њ'ڊDE2Dux{D_#/ED%E@SRD. RD&clݔmꫪpIS)"K{FF)"KY7e9sWXD$̻JDۈ7x>W1GXt "åHה -P -Ԡ-@0 `+Np n^x @J6"QrA5.;fL]'bbGL9yN"t9%Xk剎<(j - w@v."KKy@oA"S)"" nJDRYY/"ot{ gn8 I '?"Z:gnu08ԾvM$Cs3""S,go=O ȗ|&E$%6*:Pur,6I)vMHJJ]\UK#2Lۙ;ʢ}rL"MMm麱 -H&c 39DD홁E;- k"H.(2"R"V4| v{^DDAD -ާjDj5"r`@JP4F0 " ,`8 .` x >dpL' LQdܞYYdFA,ChMEF.gƈFf n;WzexڈwHM ˙\ E[A"b"rDĶ)9gW7DRYٟ֋Haw"g)"2)";,D~.E^RID)qf_μI?BD(CD.@Dr't9&/c8 ŕ~{^#\|mDT1gRDd.&لT;>HG&6ՙ#0RTfU!QM5v!dx"|0DE)^'jr㵹nЊ,iD4ъH5[Eq;!zD_ORE7I p;)l.zO:+uTFȒ @~#EdIbK ٔˍW_\h0w":Dtc,{^-3J䢏[Ψ21ltRbeRc=!!i-%qDvn;"3Ќfˤger6g2K c>D֢Ј~9}NKó4"""#"Y9ggeEDze-d9" "U"2r 9*"Rt5"r`@JP4F0 " ,`8 .` x >uu[Ȑ(WԉLQ5-sDͼu~@4Lt[ȾKmHJDDy^w}\$97"sVF䬌^Ȗ -A"MH;܌4 rjImP-p܌ֹ_vQ#"rzmo]NDSH -$r픗~I1ygDDbϨ1grTj=uҳD{:SS'Ѭ]V34"/c͘′c9M5_=p՚Gڞt̀!NSa v U>Id,L"]uH0"ЈS DBDsO""wt6""5o!"/""ZՈkʁ(AjЀtfpHl'7p/<Ѭ c)%2Q9&JzJm}1Lc11"D*iDed:j#q,g qln#\ -"2w{4)" .E$mM{{OA"2 Ҷ6H`m!?[A6F[,g2& 9]aK?ED~3g}y -œ+8F%%r$։Iu&ع{Lrl\35.sE̛wM[Y|* !"e+TUE{4 "9ci:!Qq*|X+D0Dt<){Fn  )yi~)(FD?r>A= "GՈkʁ(AjЀtfpHl'7p/<_[SFQTѝ%A7K 1S-FXDh"i9L\#ZuRћE|ßs{wwH9r;G(>wƗV?p" +EA&RDdl#B(=k  Ecx>Hs ȝg.d'.(uYg4gKHHG]1I1q)()*5|e9*/*YZtiGf0-}'mr{0o2dfkys{5ּۋLȃe;-]tH㋈HX݌L &ݞeDd.g*ާTrfnP-XPT hAz0L`0 Xp XpUDyy<3GD!ډ:#^3F٢.{`%ѼH#2rjW,`9 ;M̈́'ޣ@٠*A 'spoٹ-bi&߭-䆓v6,H0"EdnÈ."UuvWb9iDڵ@DnD~݄<\饟""f/"ޏ+<\8f1qD9A561...!.*ݼdx%"Dz?$2Ao|KSeV-Mkt#~}]LɦPSGDLVȲ{vZFDfZDN"aɿ>37dѽe ޳=V|M90%@ Ђ`# a  <ҷ: dN\UD=/jDm r0%H5xˀ`m̺&wjgx}U}A"D$TcRD -Mk/PQ E "LXQS"M([ns}FT䧱kg"/cz37{駨Hz"Rs*PT hAz0L`0 Xp XpUJDy+ -DeʉW xmS:Düh,My\zடX*#1 *g2YS'Ϗ"A.IyHȶ 1JyHȶ ٶ)94v=+ӽYϞ-ED$""-ED$"ؔo ~{f4"dq26٥kӈw& Q/H~sQ>PI[Ψґ괳 BRXNbݮSzĻ~ndͭg1%%fEʜUeju'gkF}r.Z_7d87MS[[ò+^ViXܫ̿,KkuVpd]윹b׾{l[|^RCl90%@ Ђ`# a  <o稙ʒꞭ歺~ubyy+qp+}XV֣[mK*GVgVWVvl{x+׹Փ۲7sxX*Ɨb<&};boxCwǿ] Yb$7*Ư6X{`5A-J@4_[KƹA%%cdDmuԾ̿[k7A2]dA2<1}}z駘;=guk/^vb3)Δ8glb%5:91wp86p]ϯ<9o&T~p~;OVx1M5{دn{8ɩZ;6k0?e2fd=j<ěJ# M};"i(qz !#`h@7^K0w۵g1wL1Dוchݏչ_S (@ *P=&0CCD`8,x~d-˄mryu6@9&2ys=SOe jClC1%83W , E;@| =KAl* TT7oCNs3f݃}[ sǤ4wD)"ak6eu$s˾0wd.~DK9oxuWz駘;oo|$gԶgj&cR9z$6ўfI ΄褤8$"ŷЈ̼*x;3:"y,]y4c~uMHvt(Mwį?0֘O2ae=ȢRD.^*J}O}f(nOH~zПFd.^FDo?",3.(ryeYh;JOe˛&xz3FB -ӧ(w l;(:֋HH4JRDII CQJ"޲,+ku3mP@WDORD4EA"MYzjD.܎ _d?,Ë^?Bx c>æ8]D?IBSbYb"u4" HuvFd.\IWMeճ%MoG6呂lb}_6,晌Y-ȁR\dO "H&L"4"],H"RLЈ#"DsxrFDJvD DqA1,(xUO~k]_ qjxBhD -K3om3MpW1a[Y0=(׋$"J9;HDTRDn"rv6%"sw."OF gT7Ed$޲=RD"2doٞMMD~EDXK*EZzF"{1 ->梭5o$3^LRtTrJJ=M7$ =%6ɕƤS5|QB3ۙw޲ҕuo嶀tmí麾ހ&ckHko.=̇-˨Y#";",>'NADZ2ȜYGd*<Dhxj}nYk?"2H:B -P -Ԡ-@0 `+Np n^x EOPm=)E +#b㍙iP0шdm!ݘDУ2uę-*vZ[Y36 RI0HE)YL56 -c]ٙsLDs;|>g~3QO"3"QD6Gn&F9gB=.ѝ$R;we>晥P͗1U˨HΛiFՀf&/gѯ?0?f26=jʮ |H =+ȁvZfQ,;P.D)5^q,I'TRE7<9/|fw`YL kt_ȡiTQO}}>)Os_3Q]qQTos).ݕaS;4Nڐ+sveAPy˘>Ttei޿G4=ӵo뗪LAZx84-X4ѫ"ke^ZyYV2X4cobAS*20=}3]Ы"'kʁ(AjЀtfpHl'7p/{n4DA)"(b -oJD"⾰> ~A5A&"˥mMwD1[3݈ku%"rd?ǜn?^4!"<#\ "^q&E$1&:6ʝbOrz&59WtRj)B:oʉṖۙ֗{eʃ3i7OFJ>L"5%&cM%"2zo.*&1 *;"$"Oj fzi%L"H&D$=DDd)y gJ*V#)AZЁ `! !"V?@`ĝ &̤('lyDKYDW W&|bt),#*cAp amE"D<w{;L"?"roDMCOsXfgmK†g)E gPF?,Iň-9 ǜs?&/3o7,97qFm.\nhOLLɌJsѱ丸$wJ4>:Lu֦mʫ3MHվrX*1橀fր˙rHPUn2Ndc D04Ӌ"=/l=2މmGvznl&/#"5UDC{TL-4"4"%XsXtgqՋl"\#frfo.EdxÝ">)N+6%"~˧~ivn&)"g}3MRDfܟ r}5i9DZd"~JOOy >$rwuM$::9.N"kh+)5ƕ)geJ5JD> -g\ƌM#"3mW+ 2o_)nhu  `(or*D;"siWVՎ.DNDdFd 9Hv܌L!"TDƆv~SS55h@ :Ѓ`3A8D@$X -6p\8|H!"^eDMԙD/jKDÒ`&7vOէ׊FBO~ Л#7I<-ě) -? 'ycwDKypfћ֍Ϥl@ّ_dsK^YIgjؔQYeN*)s"/;Ss̷o 0|LJuX;z&.:iOO&&!&'W@G=,˘}sHlrr:MU^W/?tϧkM7Y-C&~T7waɻDE:uN@KeK_qWB/g"L=mP=zQ$yT$cT^i{>Ao"r`@JP4F0 " ,`8 .` x >En"'LX"QՃ&+^)QP8Ab%.:8%%B}xoLX?TY(DmxW)q3]'>+#MIi"B۔0=X|eFot]=c,qK?1o)J"_:"%&&٣cceNT=%:.ܩĤSޓU_]lgrBD&_MWNOjYL׶ *+.!PS{o.+ʎ+_;-d!"tC-2gc-2Wp=|,ZOe>WqX:edwPT hAz0L`0 Xp XpU@j^PA;-JzeS`.R"hH@wZ37{DoE/ERDLA"JWVc6%"o̍=7qұk 9g-" )"k?~G$cSvvzKjDr$ yc""F#D;Pg.g3j;犎u%ڣc1pI.{\J۞rƱqql)U wjf>TEDF (2U#X `ЖNM69/ SX,`9țaH1D`qGdm;-]w!" YfvD |$Rz| -Ϥ#!L"E""Y7N"r`@JP4F0 " ,`8 .` x >VyTŸTOдk#qP&gW"RLwg˰`]luQ@+Yĝ%go)/) -O^)" RDA"MF=͕kNv go(7lt{Ϟj̵r{8 !,gq""]6䗈~,gpD^!zsOa9S37BDdgRD\Qɉ=ΕƤڹdw=MMKILMqU]4"e;og4,+TuU~ؑtm߇ib|<`33w|XU}{"RO~a\>jx|_z1Q[tW#"g3v}4$&HD^CD'V'|M90%@ Ђ`# a  <yazyE',A=,hm>J0- ;Fb)(8Z/qW vJ[MEu6L_V*2&D\y*ro~QxQcgԻfScS{ˍcdONv\VKZz&{SLG*KTMlS/vFjVFz3:" CJc}wLm3Odo k7s"gϳT엮տw/΁k]%_;玟q2l;rBYw^zg_)畿Q[-r`@JP4F0 " ,`8 .` x >V.owmc:)F)ˌmJ6m66Pi46l3n3݆fmwcYY mm΅mm6w6nbqsofQd{Ԍ6i4cԌ{-MiƓLy^zxo79v8jd;0xaJDW/"9~$$kF7/د?d|g|$#Q3]RrB2㒝v65eOoJJJJo=9J^9~2fM weZz䙀f";m-]__Ն&c3}V:JNc%znzn^QWvc\=zrQpl/!zċe55h@ :Ѓ`3A8D@$X -6p\8|HA>+2Ӣ"3>4vB/ܛ}n&Zxb_H6 xGCTDȍOn]ueL>4Un_t@;Iw==C?zGD -ͽa3٭wG/,.ishD64"/yH1D$(" ɧ沁RDdD6w|M90%@ Ђ`# a  <Rfnso"YAGTD]E4sDIt9"ۈV0/҈Јt[ -Ek%"9* - ;$xjIu}5ȑ-RDp_)"'=/9$;!/PceKOb/ی<|/0^G)oGD -1%1|n.;)}MI$ўM'rql;>e),ؕٶrG7Py֡˘ҕi'ftmɡtp&ƫ &c}R%oz+nwD?CҷS?"s.ʞFDr핹:B#{<K H8L;/F_S (@ *P=&0CCD`8,x~"/*㕅jIP׮lsuD_&u1|Fd>ChY^t g%lq.ѳO6į/wA"#ElJDt-)"[6|-gP=랜O 8 yqff7 "Us4X(e>Oy,gLs~:sF=ˌbbqv6%*3 %͹ؘS6U:D""ҚPd_htyD1/<jjya""M/̨iY;T+6N>ټF)Dd"[л=tsY&}'D"}=;"2|r`@JP4F0 " ,`8 .` x >V^K]Q(%ʒxU.dnQ7(ꛈaA47 z3F$Rˬhmemhn="Rql K6Kdm!Dol8wٮuNuݿ6\(EMf =I:׶7wwb9SeCD.O}|x#@DΨ1'pn6)vp=+KSݮ6)-WVl 8T~˘IѱteB hӵ/fad<ڋIo.³>9pNKDY~zj,g&r&/rf>|jgɸɩDEDoFDo]PT hAz0L`0 Xp Xp20DQ$*D2Q*tÂ>CB+^ٝ/Zrݡ:CTt- &\胘uޓ r)d LZDhDdwyrdj<[/L">@*EX7e~6'xD>DDޙs2|!˴1۱qx駈ȕ?$Sܘ{&E$V9Ξrb9dG\TRT+&{1#y<(g9T^}S>"]P|z4]~}C&cN+"Ror]DdnPșvZYc "7c^XOs_$R0>3|ns!OsDDDDf^PT hAz0L`0 Xp XpUf+VD储ʈWkZE풨ˋ?*Nt:NqN)t230ܙNýۭvk0٤=ᶈ"DHbL "F YJcDĈ " RJ~_\&]9sy})MOAqH=~HYLjm@t4ngRL -,/GC$ "G+/GC$ "GקAkbX1@dksT8_ID)≦iS%E߶ƶ݇"_}!|p̗u`nɾ pgR[#WUj8NtBI o!pNr^O9PRsY=3'0ǐ"Cv,ewYP&R$7){#K[\>я sE/ˌ={y⣼uڡ:Lg3YrFԿ@TB)R҄i.9J|xZƵ团HPy):׻4.1XP -Ԡ-@0  dfC9٩T@wBtD&DC#1$62HB >#I.wi#\;+I GXc Lb t4E>%"hLGSS)^3Wm!$JBu0>ju2}s=BW#(Eݏ!M5>xk"Ng %i3ݝ')tDru/ v.fnO~ Edf)g -҈ٚ,A]34BdHido{!2Dv?r{™m!m+ӂ)gAW?U{78@'".G)2G߬һ2O)ܿ\1XP -Ԡ-@0  $o ,Qv>U*ʉzB ^^/kÂ^X;IC,oi%ab"f9'JzmwB_%amެ^ڶ&DCDsMY}B|o9Uw*qhECh얟]o}JvPU~yf_**zH_)Uu?vSpJRvѻGwR+%޾:DFw伿zA&zu S@Ǫ>y4KXT|.]ܩ/*/d0ʇeƺT]ϛ'@wnOf۩JFɛF,E#D*>B q9ow!2,B=wt]+Bdr1XP -Ԡ-@0  dty](o#LQ9ڟ5D;GtMO43TK߬З""[LlCiZљܵiiܤ_LDYY4DPDChFKLdny+t5sigg%R䥢wvD.v:]sFȞ{ilC)"+A)7?3_1sN䌤;<<χBaOrv׻:E&߱񥁈[cluW2.ʭ5Yڶg#a.soʩyݬv) m,Utn+]{YHRHsHHu9RJ_ETw]tQ|',Ce<"xL905h@ :Ѓ`3A<$`8n>C\^"ア3r^Puu1]OCXpnRhigmVp4<܋5S*~#"DSh)rDs4E"q)uI,"/mGCzyi{4DRſnsuVfD{r%D_>BuI~+(E~|]sCYM<avorڞ¥+wLiLr$–>, j#꺒f>?[;xPwn0m6&_Gy7<׹!dO,㎄mW"'NloOo

    b`k%;`~_G$W+HS#Ul@Q?Ol=_KJaF% zy!u4zGYei@`{*z8UUF_(+4uU?(awkd+TVBi?5Z7h;@rd;b?NukuV3AH(?9Z$V}D%z za>?K44jsO`sm(of^5=Kko=W_C?80X|FY7C&KOcMRz}a)l|8qu8?z*GcSu~FitzI$x OscQeZ@@i92MP diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/reason/metadata/snap-7858783715254064031-1-b535d285-879e-414d-aa40-feb600797de4.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/reason/metadata/snap-7858783715254064031-1-b535d285-879e-414d-aa40-feb600797de4.avro deleted file mode 100644 index 36800c54140a6180cef05c09b4eb819466fcf247..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4296 zcmbW4U1%It6vx{XL8XnNC8G4>GA&AB6K9facC$9n&<|-T8r&$Yu`YLK?q(;>$9CpU z));~!6cItvhgPIeEP|-`pikn9h|%ba;JNd0@15P9&2Aod_Rjg8 zd(OT8^PeZH3)=^W@S^9A_jWjGEg^Nvz!c|@ zHf0ctv}LXpB!o}S2gK1y#R=>x@kHLKl9c0BC|ZS@b>gI;sEK`q4TG?qhfR&Dq~#@+ zZ9;u|;+=Mza7a+HIKe_E_wE)sXbY;uWkbw2o=Chb|O#tc}8o z9iC^uqPsy>yQW!j5OmX9Ldi0zQ0ll8m%M2D6pnkPx6sGqml%5^&9PyMyGFiZ;Oc$^`FM>v63*hjr{=wSH zxs{v(x6KBTh-XZ3g>S4J*fs|3U|S(ruZwba-H%#;u)D+Ith)x$2Vv_LrerA5Mj$<0 zDCUcW;#fh;kLJhKiE(vI+XEaM)8R$p`7qZ8M)xIRJRHq{0d0?|6Brru@znorKR?MZ ztOK2my~q!Qn^9B4KAz3(z963Z9*haogK1KNP=p`xK3o7hKM_T1r||io!AXf#rXLW9 zQI6mjOL77k5P6t)b_N`v6*xe6f*!*5;}HZb&tXZ=g*(WArV3W>!z~Ev1gCcP3B8be z^bH)Uj&W!w$IuJ1+=GJN`9ylH?8qL3BH*QQv^1Je}gJLKV2|u<0i1 zQp1+d)sxG!1@01-wTv46K(ZuU$ffVJNK_~JK^6ufawCGClJ3MQY-SWXxf4;*Vh3#K zO9VBwXKqk7!*=i|f(o-M{(enckxZ!Rz^G<(Is91d#ghn_7*}cg(KjsHhoqc_h|Br~ z79>i#i{F6P55j?CHbrs~cQO;V?ehAye?GeYN@MVYwVlt^F3k;G9@u+xdhptj-P^wT z?WbSgxpb@k!sFFL)5W!mw;nw{_x6Juxr2)%zdSVc>eb@wcYM0?^%vg7ovVLre|NI9 ze{H60EzrmA8QSsMC+MiN>)9v1nt5jV&wB?~|Ni;rnNxRNxn2G8LT>fIU-p58yZ?Rp z#3Mg$jO{vm;r@@$oINso->K0ZhrT_0^~3kynmYXX#uf6$`tLtXJ^$JIe|yj07`grC J_YbtD`aeGUxxD}Y diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/ship_mode/metadata/00000-a8324c13-7e0c-4eea-9f90-c581e6edda1a.metadata.json b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/ship_mode/metadata/00000-a8324c13-7e0c-4eea-9f90-c581e6edda1a.metadata.json deleted file mode 100644 index 315217cb2926..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/ship_mode/metadata/00000-a8324c13-7e0c-4eea-9f90-c581e6edda1a.metadata.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "format-version" : 2, - "table-uuid" : "ca7f3791-4a58-40d9-ab4d-375844d816b4", - "location" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/ship_mode", - "last-sequence-number" : 1, - "last-updated-ms" : 1678866180594, - "last-column-id" : 6, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "sm_ship_mode_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "sm_ship_mode_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "sm_type", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "sm_code", - "required" : false, - "type" : "string" - }, { - "id" : 5, - "name" : "sm_carrier", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "sm_contract", - "required" : false, - "type" : "string" - } ] - } ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "write.format.default" : "PARQUET" - }, - "current-snapshot-id" : 93583429254238746, - "refs" : { - "main" : { - "snapshot-id" : 93583429254238746, - "type" : "branch" - } - }, - "snapshots" : [ { - "sequence-number" : 1, - "snapshot-id" : 93583429254238746, - "timestamp-ms" : 1678866180594, - "summary" : { - "operation" : "append", - "trino_query_id" : "20230315_074259_00059_9f4mz", - "added-data-files" : "1", - "added-records" : "20", - "added-files-size" : "1690", - "changed-partition-count" : "1", - "total-records" : "20", - "total-files-size" : "1690", - "total-data-files" : "1", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/ship_mode/metadata/snap-93583429254238746-1-6db2231a-ca5b-4ea0-b8ca-e712a3f15393.avro", - "schema-id" : 0 - } ], - "statistics" : [ ], - "snapshot-log" : [ { - "timestamp-ms" : 1678866180594, - "snapshot-id" : 93583429254238746 - } ], - "metadata-log" : [ ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/ship_mode/metadata/6db2231a-ca5b-4ea0-b8ca-e712a3f15393-m0.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/ship_mode/metadata/6db2231a-ca5b-4ea0-b8ca-e712a3f15393-m0.avro deleted file mode 100644 index 4313acf3ebe88ba827e8147d56864a676334ce1d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7096 zcmb_gd2AF_7%vFg7=lPpRQktb)DUqCJ2O2u;#nGNq!e0EgLQm6J4?sT&Wtm&kfK%4 z7(odhe;Fm>eS$|cq6UZ(wTVg~l1L;`L4(1dAwo193HZG?JM-R7cb4t8$)-tX-usT< zcm8H!`o3{fCb?>#mcu*Ge?agCY)wcBuIJ=cPe=&>3Ct7}Ha6;jI{5XciT zA(%Kv&*v1kPq&qvmC+QpA1*rD{dwKd;8YeHu9gTS89Z)xp(kVyuz{O_(6HLF$|ZZ zsg^@PD}*D5O+Bl*o}z*50eH>@E{K?iy}S#r3K7>$5^j3Vk2Z->aMr}O+h=(S4UB(b znPBD(1Fq13_9ldkmc@C)6H??kFG=JjK~`v{swrkZm!^=7RGQ35%jyPX+o(l0a3V9< z!wQibA`=U;BL#r@6;S|7g?Iw&;eEkJHe;z!SGV{tU1VjE*QX((DTR{rh}r}a=h>F4 zd%9(!jAm$_<|dFd5B$T1?hORb0!>o3)%A=KP9CZU6tMY^wqAQurmJcIRBnxdOZdH^CG`tBE2prFU{utC!Wxp16_ z503LeR5YIxkV&eM&%j7Uq;Vr` zW>6>Y1g&MT?%t%4M6}S|wOy)RTIJyh0SUus!+}TsGNczIhndO3hKyfH|z8 zLBknEPm`KRo;6niozxUxs3khH)La#I)L%sXEen#=$XS!O?Xd-kbThe{ZYJ@KB2tVc zK$5F#XVl+HK;p9j~JWRHPVVNZO#)b1{zNtC`Hr<=x9t!TEv|**o#zp44k!!;m zr&!snt9g}YZ!jTYA!|7K=QW*2+ z85g_CJ3ky$8a?higv^X$^sBV6v8Q8USduK96q*7Ni?Jg@pUM$4rh$ENp&CL;R+*0W z3Mm@X$HEk5KU7y~ZDqnV2K0>C#4wH%Oe!65>>I3jcI8K z>pw!)nEawr4k0FVC?UWs^2dSLO#guo41Q~-qqgu0boi4u6(HfiWhzLm0pS`EwPHTB zhCWcaj07+RmH7aI$ZB802MG#mY`I4%?Le{plahhg900^XT!)y=!Ohw zQJ60!c*h96kRg#`ypSPuqxa&REFttuVxM0lgkXd|N$@2$#wQ7|3w%oJCZ?ZJ>4SQv zTOw`tI?|ihY?(4bDx7N;FaKWrMHze+u({-al)%eWrER*1Rl=dm{`~aFOAVW*Jn_!J#;&&R ziR$`QpFG@XpO$tVJNedgSGWC=p1rhj^EIbxXXKV_ZU49b@1^_ayx2AM;$1zd)Z)z> z9^3!nGw3_vLZPK|eb3%W>F0-zfAHX)Prvie*17dhy;Qqz(&?}5_n%heoz2rWU36pb zZJWR9*ml*euYA02^~AaZYiinR4!qGP-8<_O?dYMuChxPYRXb)(xNf;};^vQHQB3~_JCj#w diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/ship_mode/metadata/snap-93583429254238746-1-6db2231a-ca5b-4ea0-b8ca-e712a3f15393.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/ship_mode/metadata/snap-93583429254238746-1-6db2231a-ca5b-4ea0-b8ca-e712a3f15393.avro deleted file mode 100644 index d75473be6bc514de32b9d85aff0b54a896869687..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4295 zcmbW4ONbmr7{{}#U;<%bVOaz>YuWg?>`M1Mc4r3{jY-5vG@4073`-=q z?>NTIMHW`jgD(;xiV=L^MT{89MP-p3HsC=_4x$h|=xH%RR!}ci^`okKrh6WHoSy3M z_tp1R{l9-7tv@$1HVIce_kLe9Q3Jj|eM-XZCX%X>hcwsIr5VYA4f?wQ9m_yIRt?KW z&6uuu9v@6(zINC!Gid^iX z&|-(@*;h3;$ZOYrS{y*S>9s+!OemCG65^s4EgzHPt_~LZP`tcHbg>Q5q0k2melR>j zy)y5vyXUsLYLO~?R1CT-s}_oQ2Usoo24!W)jVtypsY2hdNda=Bik)LZK-D6;v?nDe zux*K`P<)A@=fO7lo80{dkv{gkQ!Bl)TuF|2N+PK&K+EyTouz@3xOLaH5p>9dNF$GX z0TC|)K&Kt4nMv;`xl6VUSe{@7+CirVEeL0k!@|(%JxK|3sUH+N8B84+FtD*yr8%W_ zqavhW6E*PEI=Ne1SCgtrku)>@lZg_8@GTI&1q{~%kZhZZI_*pkusAP*W^)JN@U7v& z%IebUj6`l*gNQ^tV=Aj`W3`~sfJ8g!RuI%oUe2ETk>^R+iEub;u8y=ZvUM9`G?{24 zB`p-om13!|P$-uQ#manXZi-ME5YMh4&nHuDv@o4W@oY2!MwKZ`Lu6nq$20%ho$MTw zQ5|S>=tXWI+=`qU_3>oxb_Maw_sEd2JTgnFBotx?d=WtR|mFosL zqQDUBS_wWN2O^I0Y+t|t$~*&bM=(IxVH^U7l^HDQIrj!R&{V-{LwE&FonzE&moNx< zK-a*aa*;u^{K6o}+5rB6^GY4OjPafW6;DgIli&p2JT^CX24TZgbS#|1+m+1GoneyU znn_Xk#ftiqn$`oz`LE)XlOuEo~-yXR|O_Jbhh!j z7|`~adNO&oiMJ%nItC4UAQ%!RVc@a)eNq6EC`q_jmZ^A2D$-suL zgi}*{X7;2XHp888Dw$pJ*K68{~~8<+ES^z5K?F zw{NKb{h+`5^zKRjmc@tn80NV7LE)#h&Fx2DIkWV@+V4xn%l~Zub?F!N>-+wAEjaz% IH=Viu51sYD0RR91 diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store/metadata/00000-5be497c9-30f9-4547-a321-c09c13eabb7a.metadata.json b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store/metadata/00000-5be497c9-30f9-4547-a321-c09c13eabb7a.metadata.json deleted file mode 100644 index b396e2d977ba..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store/metadata/00000-5be497c9-30f9-4547-a321-c09c13eabb7a.metadata.json +++ /dev/null @@ -1,207 +0,0 @@ -{ - "format-version" : 2, - "table-uuid" : "59838127-7bfc-4dcf-8b41-2aa2f25ba37b", - "location" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/store", - "last-sequence-number" : 1, - "last-updated-ms" : 1678866184478, - "last-column-id" : 29, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "s_store_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "s_store_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "s_rec_start_date", - "required" : false, - "type" : "date" - }, { - "id" : 4, - "name" : "s_rec_end_date", - "required" : false, - "type" : "date" - }, { - "id" : 5, - "name" : "s_closed_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "s_store_name", - "required" : false, - "type" : "string" - }, { - "id" : 7, - "name" : "s_number_employees", - "required" : false, - "type" : "int" - }, { - "id" : 8, - "name" : "s_floor_space", - "required" : false, - "type" : "int" - }, { - "id" : 9, - "name" : "s_hours", - "required" : false, - "type" : "string" - }, { - "id" : 10, - "name" : "s_manager", - "required" : false, - "type" : "string" - }, { - "id" : 11, - "name" : "s_market_id", - "required" : false, - "type" : "int" - }, { - "id" : 12, - "name" : "s_geography_class", - "required" : false, - "type" : "string" - }, { - "id" : 13, - "name" : "s_market_desc", - "required" : false, - "type" : "string" - }, { - "id" : 14, - "name" : "s_market_manager", - "required" : false, - "type" : "string" - }, { - "id" : 15, - "name" : "s_division_id", - "required" : false, - "type" : "int" - }, { - "id" : 16, - "name" : "s_division_name", - "required" : false, - "type" : "string" - }, { - "id" : 17, - "name" : "s_company_id", - "required" : false, - "type" : "int" - }, { - "id" : 18, - "name" : "s_company_name", - "required" : false, - "type" : "string" - }, { - "id" : 19, - "name" : "s_street_number", - "required" : false, - "type" : "string" - }, { - "id" : 20, - "name" : "s_street_name", - "required" : false, - "type" : "string" - }, { - "id" : 21, - "name" : "s_street_type", - "required" : false, - "type" : "string" - }, { - "id" : 22, - "name" : "s_suite_number", - "required" : false, - "type" : "string" - }, { - "id" : 23, - "name" : "s_city", - "required" : false, - "type" : "string" - }, { - "id" : 24, - "name" : "s_county", - "required" : false, - "type" : "string" - }, { - "id" : 25, - "name" : "s_state", - "required" : false, - "type" : "string" - }, { - "id" : 26, - "name" : "s_zip", - "required" : false, - "type" : "string" - }, { - "id" : 27, - "name" : "s_country", - "required" : false, - "type" : "string" - }, { - "id" : 28, - "name" : "s_gmt_offset", - "required" : false, - "type" : "decimal(5, 2)" - }, { - "id" : 29, - "name" : "s_tax_precentage", - "required" : false, - "type" : "decimal(5, 2)" - } ] - } ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "write.format.default" : "PARQUET" - }, - "current-snapshot-id" : 1274028951089363265, - "refs" : { - "main" : { - "snapshot-id" : 1274028951089363265, - "type" : "branch" - } - }, - "snapshots" : [ { - "sequence-number" : 1, - "snapshot-id" : 1274028951089363265, - "timestamp-ms" : 1678866184478, - "summary" : { - "operation" : "append", - "trino_query_id" : "20230315_074303_00061_9f4mz", - "added-data-files" : "1", - "added-records" : "1002", - "added-files-size" : "63661", - "changed-partition-count" : "1", - "total-records" : "1002", - "total-files-size" : "63661", - "total-data-files" : "1", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/store/metadata/snap-1274028951089363265-1-f84adc81-2b0b-41f4-84fd-9760561f7fa2.avro", - "schema-id" : 0 - } ], - "statistics" : [ ], - "snapshot-log" : [ { - "timestamp-ms" : 1678866184478, - "snapshot-id" : 1274028951089363265 - } ], - "metadata-log" : [ ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store/metadata/f84adc81-2b0b-41f4-84fd-9760561f7fa2-m0.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store/metadata/f84adc81-2b0b-41f4-84fd-9760561f7fa2-m0.avro deleted file mode 100644 index 0a3656814bb8..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store/metadata/f84adc81-2b0b-41f4-84fd-9760561f7fa2-m0.avro +++ /dev/null @@ -1,4 +0,0 @@ -Obj schema{"type":"struct","schema-id":0,"fields":[{"id":1,"name":"s_store_sk","required":false,"type":"long"},{"id":2,"name":"s_store_id","required":false,"type":"string"},{"id":3,"name":"s_rec_start_date","required":false,"type":"date"},{"id":4,"name":"s_rec_end_date","required":false,"type":"date"},{"id":5,"name":"s_closed_date_sk","required":false,"type":"long"},{"id":6,"name":"s_store_name","required":false,"type":"string"},{"id":7,"name":"s_number_employees","required":false,"type":"int"},{"id":8,"name":"s_floor_space","required":false,"type":"int"},{"id":9,"name":"s_hours","required":false,"type":"string"},{"id":10,"name":"s_manager","required":false,"type":"string"},{"id":11,"name":"s_market_id","required":false,"type":"int"},{"id":12,"name":"s_geography_class","required":false,"type":"string"},{"id":13,"name":"s_market_desc","required":false,"type":"string"},{"id":14,"name":"s_market_manager","required":false,"type":"string"},{"id":15,"name":"s_division_id","required":false,"type":"int"},{"id":16,"name":"s_division_name","required":false,"type":"string"},{"id":17,"name":"s_company_id","required":false,"type":"int"},{"id":18,"name":"s_company_name","required":false,"type":"string"},{"id":19,"name":"s_street_number","required":false,"type":"string"},{"id":20,"name":"s_street_name","required":false,"type":"string"},{"id":21,"name":"s_street_type","required":false,"type":"string"},{"id":22,"name":"s_suite_number","required":false,"type":"string"},{"id":23,"name":"s_city","required":false,"type":"string"},{"id":24,"name":"s_county","required":false,"type":"string"},{"id":25,"name":"s_state","required":false,"type":"string"},{"id":26,"name":"s_zip","required":false,"type":"string"},{"id":27,"name":"s_country","required":false,"type":"string"},{"id":28,"name":"s_gmt_offset","required":false,"type":"decimal(5, 2)"},{"id":29,"name":"s_tax_precentage","required":false,"type":"decimal(5, 2)"}]}avro.schema5{"type":"record","name":"manifest_entry","fields":[{"name":"status","type":"int","field-id":0},{"name":"snapshot_id","type":["null","long"],"default":null,"field-id":1},{"name":"sequence_number","type":["null","long"],"default":null,"field-id":3},{"name":"file_sequence_number","type":["null","long"],"default":null,"field-id":4},{"name":"data_file","type":{"type":"record","name":"r2","fields":[{"name":"content","type":"int","doc":"Contents of the file: 0=data, 1=position deletes, 2=equality deletes","field-id":134},{"name":"file_path","type":"string","doc":"Location URI with FS scheme","field-id":100},{"name":"file_format","type":"string","doc":"File format name: avro, orc, or parquet","field-id":101},{"name":"partition","type":{"type":"record","name":"r102","fields":[]},"doc":"Partition data tuple, schema based on the partition spec","field-id":102},{"name":"record_count","type":"long","doc":"Number of records in the file","field-id":103},{"name":"file_size_in_bytes","type":"long","doc":"Total file size in bytes","field-id":104},{"name":"column_sizes","type":["null",{"type":"array","items":{"type":"record","name":"k117_v118","fields":[{"name":"key","type":"int","field-id":117},{"name":"value","type":"long","field-id":118}]},"logicalType":"map"}],"doc":"Map of column id to total size on disk","default":null,"field-id":108},{"name":"value_counts","type":["null",{"type":"array","items":{"type":"record","name":"k119_v120","fields":[{"name":"key","type":"int","field-id":119},{"name":"value","type":"long","field-id":120}]},"logicalType":"map"}],"doc":"Map of column id to total count, including null and NaN","default":null,"field-id":109},{"name":"null_value_counts","type":["null",{"type":"array","items":{"type":"record","name":"k121_v122","fields":[{"name":"key","type":"int","field-id":121},{"name":"value","type":"long","field-id":122}]},"logicalType":"map"}],"doc":"Map of column id to null value count","default":null,"field-id":110},{"name":"nan_value_counts","type":["null",{"type":"array","items":{"type":"record","name":"k138_v139","fields":[{"name":"key","type":"int","field-id":138},{"name":"value","type":"long","field-id":139}]},"logicalType":"map"}],"doc":"Map of column id to number of NaN values in the column","default":null,"field-id":137},{"name":"lower_bounds","type":["null",{"type":"array","items":{"type":"record","name":"k126_v127","fields":[{"name":"key","type":"int","field-id":126},{"name":"value","type":"bytes","field-id":127}]},"logicalType":"map"}],"doc":"Map of column id to lower bound","default":null,"field-id":125},{"name":"upper_bounds","type":["null",{"type":"array","items":{"type":"record","name":"k129_v130","fields":[{"name":"key","type":"int","field-id":129},{"name":"value","type":"bytes","field-id":130}]},"logicalType":"map"}],"doc":"Map of column id to upper bound","default":null,"field-id":128},{"name":"key_metadata","type":["null","bytes"],"doc":"Encryption key metadata blob","default":null,"field-id":131},{"name":"split_offsets","type":["null",{"type":"array","items":"long","element-id":133}],"doc":"Splittable offsets","default":null,"field-id":132},{"name":"equality_ids","type":["null",{"type":"array","items":"int","element-id":136}],"doc":"Equality comparison field IDs","default":null,"field-id":135},{"name":"sort_order_id","type":["null","int"],"doc":"Sort order ID","default":null,"field-id":140}]},"field-id":2}]}avro.codecdeflateformat-version2"partition-spec-id0iceberg.schema+{"type":"struct","schema-id":0,"fields":[{"id":0,"name":"status","required":true,"type":"int"},{"id":1,"name":"snapshot_id","required":false,"type":"long"},{"id":3,"name":"sequence_number","required":false,"type":"long"},{"id":4,"name":"file_sequence_number","required":false,"type":"long"},{"id":2,"name":"data_file","required":true,"type":{"type":"struct","fields":[{"id":134,"name":"content","required":true,"type":"int","doc":"Contents of the file: 0=data, 1=position deletes, 2=equality deletes"},{"id":100,"name":"file_path","required":true,"type":"string","doc":"Location URI with FS scheme"},{"id":101,"name":"file_format","required":true,"type":"string","doc":"File format name: avro, orc, or parquet"},{"id":102,"name":"partition","required":true,"type":{"type":"struct","fields":[]},"doc":"Partition data tuple, schema based on the partition spec"},{"id":103,"name":"record_count","required":true,"type":"long","doc":"Number of records in the file"},{"id":104,"name":"file_size_in_bytes","required":true,"type":"long","doc":"Total file size in bytes"},{"id":108,"name":"column_sizes","required":false,"type":{"type":"map","key-id":117,"key":"int","value-id":118,"value":"long","value-required":true},"doc":"Map of column id to total size on disk"},{"id":109,"name":"value_counts","required":false,"type":{"type":"map","key-id":119,"key":"int","value-id":120,"value":"long","value-required":true},"doc":"Map of column id to total count, including null and NaN"},{"id":110,"name":"null_value_counts","required":false,"type":{"type":"map","key-id":121,"key":"int","value-id":122,"value":"long","value-required":true},"doc":"Map of column id to null value count"},{"id":137,"name":"nan_value_counts","required":false,"type":{"type":"map","key-id":138,"key":"int","value-id":139,"value":"long","value-required":true},"doc":"Map of column id to number of NaN values in the column"},{"id":125,"name":"lower_bounds","required":false,"type":{"type":"map","key-id":126,"key":"int","value-id":127,"value":"binary","value-required":true},"doc":"Map of column id to lower bound"},{"id":128,"name":"upper_bounds","required":false,"type":{"type":"map","key-id":129,"key":"int","value-id":130,"value":"binary","value-required":true},"doc":"Map of column id to upper bound"},{"id":131,"name":"key_metadata","required":false,"type":"binary","doc":"Encryption key metadata blob"},{"id":132,"name":"split_offsets","required":false,"type":{"type":"list","element-id":133,"element":"long","element-required":true},"doc":"Splittable offsets"},{"id":135,"name":"equality_ids","required":false,"type":{"type":"list","element-id":136,"element":"int","element-required":true},"doc":"Equality comparison field IDs"},{"id":140,"name":"sort_order_id","required":false,"type":"int","doc":"Sort order ID"}]}}]}partition-spec[]contentdata *n܇ُ/ l OhA7뚮ufK !ԡC6mb^$;iBtwcmoBO"b""=T[ ŃU7P>=`x~O^xs *z>q+m -{E|2ب -uj&4U<ͫ0ڸyͻ$Cx(W ƊRM_XJz4(T4XX#VѠejE67{=|vQ؊wyq~Q>#v>\l7'^7Bh7Կ %׮o7gE{gR5  3(2(1(3T02e0`} "`0`4 b8C"e;Μx#P9jTq5 ge !(!(dp;IW8NR+9Y$&v+bar(9昪Y@#΃I{v82jNHC I\FNr)iuD-Nʤ)):J4& "7jLJ:Km_ pTO&|S~} e?87 j0a_;f3\tЃfMHQ;*JOSϧEQ|4Pԥ+!JTlq&-ţLg֒3 Z! -R)?+8|o~ *n܇ُ/ l \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store/metadata/snap-1274028951089363265-1-f84adc81-2b0b-41f4-84fd-9760561f7fa2.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store/metadata/snap-1274028951089363265-1-f84adc81-2b0b-41f4-84fd-9760561f7fa2.avro deleted file mode 100644 index 12deed047d5053452b5642a2529f29e62c80b4fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4299 zcmbW4&u<$=6vx|~D%AFbDuk+Zv_e25JM21f;#ledQ4kJEK)4`8smyqHHr{x>Yj$U2 zLX|@g2!S}1D1?ZjQdO1sai)1JZJ^May-kbTp z@4nnzJAV2MUiaK-UvH5PzBzeSp&MPIR27ftu4gC(#lao+yMrCeBtC7JmQCQXX%XA- zmFnfInH613Td*PyJVqi)iJeP2fg-c4fQPALNC-&NaswDNnv%@p(K1iu7Vuwx#&+HxhA45f|Al!k$SNC#^$><_Gf`)q83LB)MbPaY0UW+HK3LnlvRP2z zwsk-x;u%xfHp`OlW5(pIewC1SO+>A zdyyXqH>0M8eLS19z963Z9*haggK1KQP=p`xF}gYodE}E^Bf>N!3bf;@dyG|=CGva!X4y5Qw6J!;T8mSj#CHwgi**N`UVbF zXE=0_V;BWlAHhKg-fSOlz<|$z%BLmkB?LhBgaBum1DT>4InM0J55WML2@HzMdM=}w%&VMbw)I}sJF48VrIL{L+E z<_6_3>;Qivs4%@frbsU01T%5RzLYjY$WN_;E=hqwC-+p=H&8-vrJ0{-R-Lmo5?QP@wopy8AyxqoIJGa}GW4^s+?wU7u vT6b==Z+-U0>)Zd``rwmm*FOB_;lqRbf9*`a_0`_bKX%_g_w-N4GTHtQ^FO(+ diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store_returns/metadata/00000-19e865be-db8b-4537-b569-7d44104e8ef5.metadata.json b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store_returns/metadata/00000-19e865be-db8b-4537-b569-7d44104e8ef5.metadata.json deleted file mode 100644 index f13af2998cc4..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store_returns/metadata/00000-19e865be-db8b-4537-b569-7d44104e8ef5.metadata.json +++ /dev/null @@ -1,162 +0,0 @@ -{ - "format-version" : 2, - "table-uuid" : "2479bbf0-683d-44ed-9489-286bc5b1b0ba", - "location" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/store_returns", - "last-sequence-number" : 1, - "last-updated-ms" : 1678866641305, - "last-column-id" : 20, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "sr_returned_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "sr_return_time_sk", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "sr_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "sr_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "sr_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "sr_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "sr_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 8, - "name" : "sr_store_sk", - "required" : false, - "type" : "long" - }, { - "id" : 9, - "name" : "sr_reason_sk", - "required" : false, - "type" : "long" - }, { - "id" : 10, - "name" : "sr_ticket_number", - "required" : false, - "type" : "long" - }, { - "id" : 11, - "name" : "sr_return_quantity", - "required" : false, - "type" : "int" - }, { - "id" : 12, - "name" : "sr_return_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 13, - "name" : "sr_return_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 14, - "name" : "sr_return_amt_inc_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 15, - "name" : "sr_fee", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 16, - "name" : "sr_return_ship_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 17, - "name" : "sr_refunded_cash", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 18, - "name" : "sr_reversed_charge", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 19, - "name" : "sr_store_credit", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 20, - "name" : "sr_net_loss", - "required" : false, - "type" : "decimal(7, 2)" - } ] - } ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "write.format.default" : "PARQUET" - }, - "current-snapshot-id" : 2430355542025878006, - "refs" : { - "main" : { - "snapshot-id" : 2430355542025878006, - "type" : "branch" - } - }, - "snapshots" : [ { - "sequence-number" : 1, - "snapshot-id" : 2430355542025878006, - "timestamp-ms" : 1678866641305, - "summary" : { - "operation" : "append", - "trino_query_id" : "20230315_074927_00079_9f4mz", - "added-data-files" : "261", - "added-records" : "287999764", - "added-files-size" : "12589796042", - "changed-partition-count" : "1", - "total-records" : "287999764", - "total-files-size" : "12589796042", - "total-data-files" : "261", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/store_returns/metadata/snap-2430355542025878006-1-579bd945-ce7d-497e-9766-742f5edb0933.avro", - "schema-id" : 0 - } ], - "statistics" : [ ], - "snapshot-log" : [ { - "timestamp-ms" : 1678866641305, - "snapshot-id" : 2430355542025878006 - } ], - "metadata-log" : [ ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store_returns/metadata/579bd945-ce7d-497e-9766-742f5edb0933-m0.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store_returns/metadata/579bd945-ce7d-497e-9766-742f5edb0933-m0.avro deleted file mode 100644 index 79d30b3324811af12d1117343150fe5fd1d02311..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 80183 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ؘYb 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 P;6bkR)~lEQ-)Ztc3vbna diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store_returns/metadata/snap-2430355542025878006-1-579bd945-ce7d-497e-9766-742f5edb0933.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store_returns/metadata/snap-2430355542025878006-1-579bd945-ce7d-497e-9766-742f5edb0933.avro deleted file mode 100644 index 06da9c27bf9f053ca3e1a40cc734dcbe03e13dc1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4311 zcmbW4%WvF79LH&pDkP*zK-C9SWw1&VASw1{H@jK2JVb*ODFT$;iYRH=PVCvNi`QQ4 zah9dZkqajX5mlQA5GoI$h^T_Xi5AgtK;j`qKscmQPaNRDp8zv{%#7FeK5|^|`1kqE z?>F=Pef{FXg?$GO!6n-|>8LeShwmT1DB)HENkz#n3Jdm&l)B@(getqycP!rmf@ld^18=)iN>i-VdrE27Au`(*af$;2Hv9X^TKUtw9Wj zfpS_y28v~_{L|CO>5V)v^aot({6!qnNTP>E5t>w>kcNzz0zCgiC}q$=wciEL!tE;{ET;m zI%OWOyKOaNwTP76F9uzfR0~DC1FRNZgR(ND#ua-PR-tRyumGu1#m+Gyplab?+BwN| z4MQR-6kK9x+ptCcCU?J1q>nvs*GfAl7s4YRkw_{FP&XZNXKCOt?t*0*2%6-9uaRZj zCE`^D&}{i?R-<>6+$F;REKjfm4Yyr`#)Y%UVP5Fyo}`dD-wg`Q1g3_n&^54Bq&Y=( zqadVU5H+wX3*>HbT@9-$`O?hzFNI1Bytjb&7N}Y_fMnZL)M;lrfW>*?HX3^XdvEm* zR+i_Nhb3~`>O>@h8BIwaabw}PNr3DzK3o7jKR!i?Q`r1Z;Y7qL z)emq)h9TI+5}ZH^L>%VH&VT`wJOgk~&_mdMJOYR17%c2LcLyoZNWn^dxCKrfW7K4y z&Ro|d?m-~`@0HaYhOVdGTvES$sJ6;9H< zVZ!m6NKyF3^7|8+)+v>WoW+>aU^`HF?^&h#4$q1txC2h$-41KiHy|ueQhb@K0+Ss& z-FRK9&~TV~GI=(LyClonMh$x)SP~}W!grc4s^jb+bA#Z!5l)Xtcjy$l8HFTw!Yf)x zzdj^`Rnt6Uk28`FB`_k zo0W4bGpps<^0|-JR?1daayN#qU;S!mbLQ#%^%M7P-2QDh^VkpDTjl)t z@24hqwzf~5-Pzv#X=-P8Yv|`}?$IB=+x~MVzw^wK*EWX!yZyim`}YrilK*w{>yzK! XT)N~OzW451Z;ih^sO+0K8q55DNBF`) diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store_sales/metadata/00000-52958805-01a1-4b1c-9760-fc4837d9ec1d.metadata.json b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store_sales/metadata/00000-52958805-01a1-4b1c-9760-fc4837d9ec1d.metadata.json deleted file mode 100644 index 45aad95b29ec..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store_sales/metadata/00000-52958805-01a1-4b1c-9760-fc4837d9ec1d.metadata.json +++ /dev/null @@ -1,177 +0,0 @@ -{ - "format-version" : 2, - "table-uuid" : "aaf45956-dbc2-47e0-850e-1108b70128b5", - "location" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/store_sales", - "last-sequence-number" : 1, - "last-updated-ms" : 1678866923021, - "last-column-id" : 23, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "ss_sold_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "ss_sold_time_sk", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "ss_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "ss_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "ss_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "ss_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "ss_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 8, - "name" : "ss_store_sk", - "required" : false, - "type" : "long" - }, { - "id" : 9, - "name" : "ss_promo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 10, - "name" : "ss_ticket_number", - "required" : false, - "type" : "long" - }, { - "id" : 11, - "name" : "ss_quantity", - "required" : false, - "type" : "int" - }, { - "id" : 12, - "name" : "ss_wholesale_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 13, - "name" : "ss_list_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 14, - "name" : "ss_sales_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 15, - "name" : "ss_ext_discount_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 16, - "name" : "ss_ext_sales_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 17, - "name" : "ss_ext_wholesale_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 18, - "name" : "ss_ext_list_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 19, - "name" : "ss_ext_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 20, - "name" : "ss_coupon_amt", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 21, - "name" : "ss_net_paid", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 22, - "name" : "ss_net_paid_inc_tax", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 23, - "name" : "ss_net_profit", - "required" : false, - "type" : "decimal(7, 2)" - } ] - } ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "write.format.default" : "PARQUET" - }, - "current-snapshot-id" : 6936726980875249890, - "refs" : { - "main" : { - "snapshot-id" : 6936726980875249890, - "type" : "branch" - } - }, - "snapshots" : [ { - "sequence-number" : 1, - "snapshot-id" : 6936726980875249890, - "timestamp-ms" : 1678866923021, - "summary" : { - "operation" : "append", - "trino_query_id" : "20230315_075044_00081_9f4mz", - "added-data-files" : "2086", - "added-records" : "2879987999", - "added-files-size" : "104575999242", - "changed-partition-count" : "1", - "total-records" : "2879987999", - "total-files-size" : "104575999242", - "total-data-files" : "2086", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/store_sales/metadata/snap-6936726980875249890-1-fc3ec034-868f-43ec-90a1-52e1c38c65f8.avro", - "schema-id" : 0 - } ], - "statistics" : [ ], - "snapshot-log" : [ { - "timestamp-ms" : 1678866923021, - "snapshot-id" : 6936726980875249890 - } ], - "metadata-log" : [ ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store_sales/metadata/fc3ec034-868f-43ec-90a1-52e1c38c65f8-m0.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/store_sales/metadata/fc3ec034-868f-43ec-90a1-52e1c38c65f8-m0.avro deleted file mode 100644 index 1010a267125d4ab2e413dec59633550865249499..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 683772 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≠;sxdY> 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

    ptG9j$S3n7#$EE_ffZzF< z|Lb>d`pP#1eP{A0q3Wdc&7efC zXdK3A{AjlrasQB4P5g52&R>tC1mqDCAN~LGGyD61s-ValIFeM?ee$5C4B{de_H~JR z?>}qo1NhK|$OvoIAJ-0lg9m+->M;^}mIZqqa$Kk9m&sWNr3VPw@6lQmZA7IZ<4&CF zqb;V-uGvdp7JUwJ-*@~nvvK66tNw+LUj^-GWwnW)1&6b-{QRz+2{je{4VK_A>HGL$ z`dUNKlfa6%PgOpsm_9b{J;nc*%Y&RaUVb|FipJmaH)sXB;tROdLu0}{s1RSHyMHZ; zU$_@5hg`0W%dS$X zX#ew1&wr4M^?4Hq+Ie4dT1~U-HU{7RZQ`9$=)c>L7}Kv+R$Dm>&Kdc>+($c`H-&2} zd8~5hcRpLD+ME?qNPoP%N8U6t9GrK5>_G3_V28<7H-)bn=}h*O6;zgS=#XxUBAVi?r=)hH1q`!n)&QuN@ve(T4Rm>Uyo^ z+WX4qVHVC04ktP+_zU&|DBOr@!42=(wRnvcb*nwIVVNBjxLMyOJ^K(o;FR;%{_;k) zq|V>JbcZ|wwFLZ#25CR56=1vV@4OgZWnAaELR1_~rq}FXz374rZ(SzS4C&vGOJ})K#l@&N(mQR@L29;p5$X2AO4z zD#qVsSnvOAcw)TOplh8!-FkH~_z_dlkcRc_KMn#$h=&e0_LPOgU3<6-obqK|@J|!- z?`7d+na`V#K7GVAs%7mkjT_JBvDvzu=VW*kMh77u#*XBA9QN60t|wOdUaY$w5B6c& z-^XE3g>=tPU>KA`Fj>P)f?OYB!#dfN$JT+?#!Twz*YweILE$GuO5gEGJP)}a9gbA!wQKqF5u~cL6A(LjMv9e~Gf0MX9irhKaTux z)yw)$=a4b|+hcOwY3Qtj073>9%(m>W ztch`P7ORo%r3jXXj&&xN5o*52?fh!E++QAX)n+Gm%J-)qPSxK~xk@?Yv;ZBFs(*uZ z?7j`x=aI7wox=afVMiXpUZATqpr+zQhqWf#SxaXRTU5_^IG6_O0e5)(_7N{gkEVr3 z&AHp-mhN_4YL>n}UTxM-np9daQA|?QET*R~_w1?imp~VcKW#*REVa(D7>L3A&Z}_# zG2nI+9?OpGn^fGsr_wGFmg&PabbVM2P*3`ql|#?&2dPJtgc(m%f$C>HP+92D?nj~G zd`zy_<4MKl8U^&Al|$5tX@u6x=)#-x1mPSR+`}lRl0GSmc3A>vuaq`(K@Y|{e|v-K z^_bR{WNDN9TiPM|N&wz#_ic!)?i)VzyO7F{Gt2+Dm9wpg>AuB3OqrKXr>HK>_U3=x zaJ(itphh1A-p(Jz=I26qcT`wma<}0FJE)Khnf`d{khX}|3yG&+W=@~~p3U8PSy zSo%qIFK!IoyqH2bPj+BRCUdDT-mR=WzLPu9=%+)IEKaH%xXZY|p8oN})>cr!QkJ;} zps%%F@x3>$rXpBmm9zRR^U+hT1>8YA>ku9tlBVt665)Osp@#YK3=1rMXs85 z8r&EQ1@|gVhPUQ^v4Su8=J?Nb?Da#zNB$QNm_hvkAvF8(l^>4bsUIR7J-eFOlX4K9 z@vs4drnoAvR42ggjEx-xeir3h*9f@G> z$v|@Wz7=(vy)87n+Sk{0!qiLXH#-&7YXX@wGZdoTKZxmGS9Op%k%oWsw|KA|Ek&e=rwKq zKl@;lCJe^BwLgYRVjh-mpGEr_&<(k4yE+Z>&v9G%pJGxTOE$<~5~O>#rDm(DS?w)H zf9m0xXNv_B_|iy(YAUhkeIsjd+y&P;-ya_{LPA2Or7Kh-DPB$na3`zbJ{s|iGnl&f z>c>0$+9;Nn_=?3nUFV*Z`}d%uZ$cm!{7eJM(DJkKRWIt__l@=LAKhqo++xbS?rf=N z88((qGVFM{ZYuF?&v$cjX;OQ3`a*=SSUO z3~oM0)Z@u4a?e?`#n~(L7#WSNe+=)lz|KgLxxU3YxFodVCLmO6m5vP>$$k^ zK*CCjp-jFOd^bYctAXrZSh@M7y`Yw~$&P#szf4ag-+_Mb^y{1^Y=G|Od!AgrC+_ws zLJZmFxGJ|Dhw_*bek;TUcRm%7`eO<}- z{Ns28=}&SghUzro^oJN`!yfJkkfpwqbw~$jh8h}>(VJ{fw>6G+vkp5T|$Pv$9&%c zH9Tw@VRp66+h}cG)ZgI|k8Rs(bkA2!QwPNXU^4G~g?>lR(HTL4h8o`S(Fa2W2hW&D z0Pz-Mj^36&>xNWl47pX_J7H=-|FHL+#WiGa^Zh_BwrCIBr%$E(0KRE|+kMfS3+cx* z!7U87^eg#ac|<=~X*?Kz=G|#e%8mO^?|(Vt9)2UhatEdMePORiXQAZG7&UR{b;#^b z?Pzy~oY|RVU0-5%x5ItWBhhj~PvwX2<*DXk`GbmSvdjL%dT(^+R2&@EF4Nfu?m%1$ z@oL{WZ^K2M?8BhW!IX=QjmLayvJNGCZItWMM%Pr8O0auO|EJf&avbnl2sR(y`n)s#O_KUC zBh_9pBjaqDm|%Bl#+p`t*vRYU57jHnA4X$cl{c4{mHfnt4Hy? z?=WfY`gqsQgq4-`(_QtiQS43{>bZ%)h2fC4@Fm7riMyADsH|?uI6PyMqSoHPw~`XU zf_*w3w0ifh5-(lKlj?VdXXOQtvTr$V#A&yu*urlX_$=*~uJuR<9E5L;GT8LD<5y*= zs)|__@b$y>&zINIxs%Nr9v){*v_DtYRfO+Q*JMYz${&bv5aiYNSjM6D#>QAi7}lPt z;VCYH@xo%GQMv_XUH0ok+}Z;f9*#PC9htN4!?+LA3Y?nP>Fi;WG4^cO&TsdFz|(JR zvNaIkq7ZC%j)Y=-oxkwP?v0>>+Z(RM*{z(g<=J{Mk~~)=JexR1*K=K;;VqyKjUP(TvsgFKTCrnIPm&wOUk!iQkf3rzgFalXC8X|+Kzf@^Gd8YF!OXx9?+>8K12ax- zJXsFSJWc}y#E*aMr>2ggKOm$RNQcm^4koly)CpO`M2*x^rDmg8gZ$!nDP3fuld>%$ z<-84TQOn_tA7m27B?AUp3 z(rr=QW%IqH@VkS^ys?syd!;l{>V~KXHd&;`?5fCW^6`p0H9OU}C?4&8<<0RSA-4sM zl)JW?6Qj~ko}G|>$R09Ptm=?W{AmuclJ(t`Dbv0IF4t28TkLiyT9=~n_MV3`PN4@u z%gxTgu2wu`FNqO$a~;RVDA!f2P8<8&T$yl?sP7o}?vtWfkh^D9?)HS5lTU+&T@Pe+shR?8EJtnIfK-4!>1#;V?RJ0jQZ-2Y2+$7>RfFhb>++>UC z54Xyy(DbdG^*N)MNB}jYJWSNmcEFH3#LyM(6ZocR1Rzl)@}#5|^0)oil+Mp?d{C!? z_vSIkcgwUr64=fcD>MD;Zx~;U%eJ8331kCSijgZo#@SOI01e1-`vsbSlul8iwQ?IH+<&hym@B2d(@o6W~94-VCnv zolI4cOKG*Mq6#xiGA+@B=_=D8HC%7)Z*1D&;$cT+tC#S}vQw_m2(R@TD@m~AislcR zdu6q|Yt?0$1b*@Qa-I7S2bC@GqRtroG8Gnrf)ux8sD%5s8RYuGOW(x24H>@PX{4(8 zr=VNDjH84eXxRLF#*NJlXhH4YGbVtc_YSX_e zEpR?H9Da{A{|&c(4Yz{`O@AxlSia(Uo1uK+{PV|Q^a4OAQ4X;~acK%8ozoC~STi|D z>vuCB(6w*S8|eH`@4M z?+@SO1E8wYIm$SxnJOgP^pzN62Y?xtUE_UvO<1);o8loKqOVchu7!$!R`h&3nzANvr%tu2)e(7y9uK@#)ssm@a;Qd(1X z??$D6%p&%CeQ-bHDZpq0;P-4w88K;#sQ;9w6`K!YbTM%>oat!?w)jC5T-?nNQXJOW zn4^Gs=#%T4pf0JWa&yDcKV*O}=#Q*ujGZ0N8mk^e+878dX4%-3s^3*;$z{jb9o~Rq zd+xHo#FU~T;dq%X?J%dn9E7q;uQxH8;h)Gg&x3pgIkCVt*UddQ=J<~-VU>XwMe?P_ zTTo|1Gq&%iC`9ezV8pml&X>-rTI!O`i$m8OrWnM)6Y4hZu>Ilf`{6*v<$9Lq@ zb6a>cV7pudLwR~&?v(@x{#z`kz)0`GpaHbe9y>HSr3dVBoav!jWEO$%@Kp7^ho}En z59V7qpx26&g0g!M>U+8>thT6nEdkZ5Ep|-PR5N6%@DT@0>BX-`;sBf9CF^5|cnTy+ z$;a~J`%rL$HsHdNKvMErHS%OA9A07wfyL-}11S))o-+6fZanKI8N?q66?wGqZE-Lj zfss(^;yqFo7>tkK_hl$5-=f$qUP2yc^gTPkdypO3V0?`MEkR(vuWr$x?UN;)=Po!e z-#2@GBOYFiZsm0!4z`S*)<$a2Egt?HtGbSxV`lPu>>B@(c1<{igUUPrk0@&$Eiy&D zpzr3YrucP6sR2CkSQkn%<04Xf2cG`+iQ4+DsKSMNB;4d7YT3FJm7oazvv+_ciAX0{ zqZa!^)PUD%>-xt_l<8&+KT8MJfh35a4ugDEYQ}8`^B zZP<Z^r zu59uttEe9@_Y58cn+S^5u9;q^b-@Ivzee{89tGHFzsYcW!1^v~{Il2{$q_%kGnaCr zg@;IJs3?Czxn-9NK`TDDdOiXokTXR zPZT-#IYT6?F+;?(Hc>>}N-xQ$7l%@d?JkI_*D2_VY&E*~JA5Z+Q64wdz4e zJ|aT!QR#3fFM2S=N&4_8MlA)u=%pB3DY|7ERAKq8rt#n;j)Dq(Z#KlQa{4U^S0b92A0LRQ=29N%6PG&j|;MuH}=9s-89D3z+UORP<-`x6UTP z4+|mNb_^*^e~pKcaFA>gJl2_jqe%S$#_i)d@>xHdQvQ`*Y=lrB25n(WFA(-5r1o!c zDq)HrnRR>k7DM3)(8#qUX<4<9ox*!i#c{`igsZ0WJoybbrNo4%h>Qo zg|7JDy7Z|Ly_No(M16opHC(3+@2|z_GemqxFi~HhUI_oA7x%1W+komNGS676#>iD*0^K6Y z2id@S1$pWO^Vti?e(V)w92iOn+FWoU#uNCwnGQaW@o#V_trI>SpyHY$Jh6V*LakB! zVTH@~ocHH9YnlF#aEwCr&jr=yj!y^8AI!OVsWZhpL_4o`q7u!!5`4Kob{xmQhM&lj z?@qD2fDEte#72VqyM9-gA!cj<2?v3bYV8Wi(LDnyPzJ*5XDnc9UYAb9-)ViYJ0S}x zG9&d`ut10YKmTnB%}c7SnnWRmqK*k%I`LJ|A(i;`ut02&jH%TKmY0OHM+vusGcnC> zG4cfBrafAY+aAzmY4hy+@uWge3^S`kah6^mwv!pDZbtF)yMfeJ6&^HYcpD3{BJ<^- znuXeK>~DOK!xB`KG=+aZei@ndp;#7L)5)KQ6yl-LqXjgrZ3+&91?ZE3Ad%&`7E0}4}eaCXLA4nrlan8JoQ$dNJ z8pU35s&>T6?u1&Dw%tcyXGg{Of-|56RRlmjgBQGZ2QqcNf)wcnFBiZv>*25AD=c65 z&P|x{2_R3YKAUk{E2=0N{@&oKxm(M#vgBL-zj+8A!2V*N=9ubc5wc=4I3=itV2Jfe zJ2BYN_bkHTH(?0YH2bc%rul`Lj*HKo@(bdmUA~_rB;A1u(yYOHW$BT!)k(y7@q-&E zlNs4x@|4>5Thd&V0YS%+mLYP-klLp!S+)hpa~S1bv)$0`=Rv>@16daEnL%}^52k+r z=OY8iSKUIo#h`hsk;+);n8K(UuKm1}{WoQ_D#2MkK=lBw)8328eD!2WcU27|vVTu1 z!YW>Wj~K65{XOAx^W+T>Dn@vz&)oET)xOq;67!k8{Zw)db@d_j`c8pZz*reke-$W7 z*G^Jl>t<1KpSdiKN4Hp!aS~{F5n%bQR}oo`P=4-1F~z{&`1Tn3qYOO9c>`yi>~UrT za`|Tka6qDFQ*$2z9K%0o@wRDwvl!uXj`wj$5*SMn(}%HJ56wssV%5Q%Cg%Cr2K}PL zhg1M`?p(mq&of3ozJ$wzR3;=49FHYdKQDiWpEbH11(z2f3(kpb;l-zchP^W&3J!m7 z5)bcNOMo|a!2Xlq%;6280$vc6A95)^`SeS=6Y19{=t9e_q}Rhq*9E999Cp6>r z2Nt*_&8=S{305UQ1;}@xbYXGsJw|_z+@AVKu+Cq6CF7%$>J;@Bq5O}n z(mBH5tyJ*xa3D*#!;nj}qob4Dsb<g8`sz3zJvln9FVZ{-fY;N#S?Mpxw-H{TDuK1XzUb#CQ%x}KmRETab; zFS*IA=%u$WmOgn9GCFi$QXxpDA^0MEyz`9SGAnQM_REAq?(Z85%3rA`>aO(SJaa8{ z>g)zq*pY7qONlE3^NnCTlP@Bb8N4%YSI?xX6Ah z7$cxrS@n*uTmOl5oD~nCGVX{$iZXb0Fo$Mz7s`IDS?m40Gi|!t?-UAdYn`uh#-=do ztlWK1_^44*E_8PtE{z-T6p8*YkPtH`(ZYyN{{Iw1$H8Q8I#a02KNd-Qco3(Q@=rG#xu4iUu3f%JVGI* zOz1OH2@M|?S=9c(cOj#yl8#FN5EKg8Nh2(hBnXCPzg&*JD8Q5Yu=M*e3R=09-BA$Z zd8`{C;?JLu)&&~8Wa)f<9Jv=*tgKP;JsfhOzLE3Veb_{1Bw*a_gOBD~!ku0AYWlHk z@Ww*;nk$8EY+Vy<;XM$3_hH9MedwEpUE`}Y7W_u{f;RM%<2lCNzWfz`APT;=*nv%4 zG1>#}T|eIpI7%x9>D6_v`aM8cHV(LyDlFem7bPrw^cPE~xj8N#7ImDWFTDuWE6%nn zS--2vLhK7(mol7bYRRv%Gl@qh|U6eW_>)-yaU|HY1mC zMzK-(XiE?iXbfB=%<(SosfG~)t~TXqvii^^ z%!29_bnf0}aCcR8T;d@Df|)64dOw~qQ42iYt+^=yx~%6z9=s`V28Ov-g2=cAV9*u! z8h&=d%u*29EcqI~$$5YJkCv!PePs~2L!&J-YR)Z_h*uAb6n$&7Cnn{j6Vv&4ifSea zsD_90Kg7rk`~yy~I45lz#PD@S%w5KtqohFSzHPgB`#w10e|eNH9v#$x-m(4KmWVNcod?sF~@Rm*lG90 z=&Z+s&=7R*wC5ehEIDmNE=nEaYAir+Y-MwQ0%o9ZEgse_X|KX2*Dp@!k(bY8&A*`# z9whLK2=#Cni?1bz;X(JjQK#}B0GzYxMsb#-;a3k5@y(a1Uh5v@@zs2aA^jqi@N&hg zXjexucp0oc*)Kb)QgsA{+C5O2AQJ!Z_67BCcAJrFBUm8_lTCqjUKVUdoJ+SWjSHaM zbG^TQtQWmhz`r&J{?#D~f5y(&V|# z2Ng+k%u(>1ow-+ObfDvlKlDw?HGj=egO8vm8+k zCy4lfLZrkO?Aw0~#j+f=3&V{L8zO>(UO07)+YSw}=D&YKb9{&bw+LLiGlqG($NXx7 zIYeCyhu1E}!L4ZWomT-tZ?{>r@%CHf=VUCoZaUa(XS}#HjlcMWrg4>qzie)-}X{elzo9Ya_ z-n!s8iJv^oH2AC9*klU~5I6ecgk%FFs*BvJato9-AKZ6HXnov?9WgE?9yDq6qR0b= zIrx@(0HK<{h&*EBrCd=FkAl1GB)|pRj{IAaUAu_9annn=S4Xm#7RvG`mgO78ttY3o zr-tX+nh`&wn!d&Ye!kEpo^+eiE`K^fv6ubRv3!+tHibWr8glBwbk$n<3Q^vgf|#sJ z$zZFxSUYfo^^djNb4j^iz}TJvlcBvUe2sluOgEE2HLjW&=~}-E={}%U6wwtM#v1#^ z>Q?dbo*vM1?;>PT?b*x71~Moq!lHq-3K#%Yo^b!AOLhba*rCt4iTn>st4E2b0o$_Q zE5A|EdaA(b}({wdoa3eKYm6cDwy8AiU$`@tSd5I7UF0KAa;<1r)rkKP&O z@hJE7srJn(4KQD9wD#63polU+x2QJ@#gOeZt!C@l!45sc;JI^p;{4N{p)uTT$sJJlD@opL` z+LRxp(d5@|(KoGt=dy9|($aW6V8Kt#0S>;e3yx7S>~@SI18liO|B?M|ao|00G(3y| z`Nm|KL+vM}U2WtoTF71h<(pUOIlLH}sDu&rBDaVt1|{cBT+YYc8U_(g?~ z2mAnb3LkIzSX=sj{q~b6#X!912J-uS4!7!!!2WsG@BI0iH{ug{xl|X(9Sa8Ea&k2_ z;?sDURUZU;ljj$tzqJr-bK4R6Dq3}4Jq9=xQI|_QB5$N9Y<&G&FtX39f2cd-gL@pm z7HLjVqgO*u3t3b+c?oA3<&pO5ik?~Hmj&9KLNLR=58S(-H2Qui;W7Nu62e+Cawyr# zmzVcmQ(|6gQk^qcJ$qF>2OBk&MYAum6}!e-486wz78Go;35 ze<$bMp|9L6TMzFHU-@CzazL3E8}a0mL4vJ^vM#_YIdqW~i$YRh6P@0!BAz1ci-!fX zqSg|WeZF+wL#(l8DQR4HcqganovUPK9-yC``$S4}C;+SRldtX4Qnna%J(}H4sZF>e zrswW!#h42O=waD4n32B+u5#_zaT$J1aD@;QMLeaxV83wPJF#Om;^ew&!7THKb(&<& zv&L*zc6XGzfgLUaF?r+0vGj6yo873_Q5oR0_g$qovBnT_?_AymSc~k6G%(wg8I2So z_-P7v^hkwZULa;5_ETOl8lVViZ7MR1^bqy`2 zYLg(|V|6;06Yf)1ztkl1{Oxk6qhaFb#x)%SaqrK`Z(TEhSz-A^zn%-__uqPhCdk(W zKGog*o+;!{9nqU69Zc~}>oz|1^-p`zK2B*K3Dxi-FND!et)YH)w7NrTIRoS24qDBCZHj|w|$Kbzq0sax4Ndb__GG)JSu7-0wH z&F3zKuHvsGBMozt`yIT=OYAjsS!H}}2LMF!5?^|7g77yk=F1CAptmOiTKO9K1`x)d zsQ7%=NW4$MpL1i5y%kG3y5FG^FS!d9DClhnx*@ej{-{NzSX!y6e2YPBi_Wki;auV( z>u?x5wXWQk)&l2`wocelZkO^A3~ zpb%pOeSWjNVm#$|xnXu93R!1d{Ucthzs^gYr`zvU3lU#1=$YG#uCr?avVfct6dc^U zjsP`0bY2*UR~ypa_65g6ro~y~1>y=GoVfQT3SSnUuJUS2KYUgatW-<*we=0s^ID)h zM*pe4C5NySt^VFIb)Le`rr#so30f#u(1@S^xRnlkO|?;LP6U89L)LuWOq;ZNr~F

    k>;P#_6o8Vt-V*V9(C&UjQ#F3x-4r3`_sy}? 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

    ] XeެNGL.3NwmҽID.L䒷{=Y\Pқ՚?9[1T"eOgGAUQ}D=|fXyPP/V 3~#3=FkyYf8Ym#!leɡ[h*v:k}){ 7xjoeT"utL#Y~!2P]<P JP4F0 V7x!A}qA~jin/a'EuS.WJ34{iV"cSNTY6!:Dgy^D_HC!ֻ9] 1%DS} -;v%rk4DKy%"%>޽.oV= 9@^ݴo -!b޹Rd3 yD駿8sd3|ƍM7_UHrF:lO '=)ˌKݬ=-gds[["(;Pl#޷A]SF1q7#taB 3uE,͕nDB!R1*[^G V!D_kxw nB!:/#T hAz0L`8l' ~@x tOLRHQY!am#)B{Vn5SҖ"㢥XD[( ^j}:|iBd R b!rWv3ﺄQPt[ R/"߻| F8f Q./}>ow+pymjgF+ʯ Dy+ǙfӇcAGxf78-Un ӹ= PoFrJJ7uY%R^)i4jxL˘NV-|zrMVuW͏nԜߨ{՝jܨ_xBn/>p3Mq]SslBK*K˓_y,Pc`-ǜ3.OkCaj:o N8D2^vP JP4F0 V7x!Av*V~eNvmfUW\)`=Z {XSH!q4ּkmg]L\3e=l`DbMX]lXI'+KyՉU׊'+G#CuW_DIߕY<2n˟r. rdXCFՈ1c_?A3{+Pw˒ғ3Qrvwٽt{fؙ=ƅh_;fe-L5]Ylk8K9}(:Q<]dkPWz8h24'ySCo. :zx莄Z^Pwҽ2]PwF1P-GꎪtQ._[;C1$8ꎜ_xk)T hAz0L`8l' ~@x rTѶ -Q9%Du&4]o mG4uG}2A,U9DiqBO_?#j.+2>̡hǜ9.u]{k^[޲+/mۭ{}׸p-^B"Do]y^v^32-8d43 cU{u)CD{(%mw%{顔Lo˩9ԡz`F&_|`+sP8f)O5UuYA]шQ\X77=ݹ!;JjYNн2#ݹ~H.Wpd?"Uqf\psP78ї s9}2coom)ڠ:QyG=,t9UqH3]NUq -Ǚ~&H8!DFv*^we"["1XP -Ԡ-@0  qf(oZ\l n6m;}`捃~ɧǙfrX[9qt YB -NiCB͘/U"ߌ9u W:|w w3h$BDf!bǺ|}{[t!ߍ WѮr̗ξJd5~+B$?'|3_s!2>uU IǞ䲻3n{ȓDTy9w5ޢ!RtA&ѫ/Dؓ YAo#p.K;RP;џj0Nэ-A|!RF?}GB{6Ko"tHSD -hWH -9Gw˷ -OH̓G6.S (%@ Ђ`# q `+Np< @`Q0EN NAuXP4DDvC5TMCH`1FU.,mD8<(q_*D/"wK|g9lU!cȍ!D*!rDtK%qK/B`3+bvѮfg"D6OE,2L n5WqJw{\D;iOOs{)\FJ8!D;*މ q`+S@BrG<[9jf0n "[ہJd?ϙPrt?xo>֑ qIfsw$ix!2?>D%2z!HBd0Bݾ!T"1T"EnBdc9Dr`@,(AjЀtfxH Xp |2ޘ*&^q<㩪\^]k:yL.׏Tci)DzR-ռ[ 9ǻwbT"!50ZZ_8?D%D.\{#DT+[/ n^j !֭ݻeuE_C;"S E>GU E7}NjpyZܸޫ,?D. L7õ==̼&3Us2yѹ-L})2`mV?Tkh"u9PpRdP9 =,9KcHRR!ڠz )RGW-^@__B4w)*RcʁAZЁ `!!,`8 .p8b"v^-RdPP |AS'hϥx}.o8S)mP-)[ :vw5<}GX}aԠz4E133C?Ro?Ϝv*32DCdAlW4Dʢ! [.!f2w0ߙqi]9!Ҷcn<Ĕx!#n~w_U+!{n.3ln{^ݛSHi`0 ?ǫ2UᖝYʩ;Սvjq}!8ϵ o#DMO(EQtܞPw-EV{.J'"lDtWz~}!.B !RBd^,Er`@,(AjЀtfxH Xp |Ұ|UfJ"̄}m۶#|_ O6k'%]yuµ{ٓ'\ĔkDzS}}},? $|Wٗ\:Yg sb=z\̞Tտ'bRv.&d;bKfd.1LR52? '˟ɺw1-/&Ͼlo*4> -jZV{w_ 3˛F##zOXd_=0kӯ9k[ƧZÝ﵇J{-=d>Cυ{+ -y) -'3=pxL`(hDH, -lX./?QYy*[P G0Lf4D":mQłeF.[Y.zH66~ٶmw|o%S/F}ߓ={ -)GRÎR d2?ş"ğ"5-%g O'·9·ۜɮ۸mmmmm|6̽wް;ʮ^ O}stw55<ӈӊӉӫ&q]jw[v]GN]O;]e穭6~\v#XdN.ƞ7fN)1Uz^e3'UƸLwHR||oKܱ8{\LdY;Xg-zzJ{9LVyL!ٿ+ӔUAuh/iږz'D!rW+އD!7wGXj!w H$)D -^D>Dz!y '[}o@"A"5u/Wc@@@@ D(`V`'p7xRMs APeDMu][@_B yXBL$G !b) Xg!a9L\7m ZW$r((;EƦI%rD> $rFڻVIH$\H(DDD%$"yGB"=uD΃D:"?XKH\9 -/~}vHydJ{ -$BOH';ݜIprId㫺{D!2/A"Arr9hPݼ)$MCw SχDxssH[l$\HdkDDBSH"KB"c3q##<͵gH"UHM"H"KGS  -*:`&` D p8 < |x@$y"+#LD6 r#̀ˈ$B%2$B6H3; 8 k:'\%ޜCDE)%r(oIHQD%!D"$lL")k6HڻV(_(D$JdTRB"lIY,I2\gu0o)k[|*g9g!쿼S$X;?@" e'DbRI)=Ku]lKa)ѱIt|"+¾μPrDVkfBG#{"M:UXzHM[wvE[|7Ri!ޘ4wvbD32wQUq;euj;+W:59^2n<%|r+7xd<3Cgȁ( -h AQNn@eLYYLۙZbLeNUSitNQFuJ)_ct`6:g:gYj[U<:oW1DcEc4(0Fkغ1Ec$1&k+1R]m`kZ#Fabxuc-a֍!1Ζ0ycJ[n=M.̀1vha`Oqws~1x~c.|&x3_ sd2Fӝt$c.'b'عWrL8{3zƎ.w]ʔ vN+:Ӕ3OU=՝okFҴƧF bG9jxs{bG]n,=ه;&^E{si#;FQL伀j;;JQt]29P%P5-=0#03 -X؀ \ 8^~dYdy^Q@- ݂vM}`5Bk ;r4`$ 1ʳ9ϻw*ϊ|sW$!J%)J+JL |G^Q"gKH;[;vDR*7v#ߋaD(ƎoJH1v(ƎoJH$fKj%$bDb OXT"#=ȓc9?䥟vi32R3&WLIbD;mw]Ąx2tE5)>!5.e᪶B%2Y,/ReHKo^ro~UHP`PSGvo~]PPgAH$7 ϛ _DZކDFDrX: -!HmD; ßv|)DjJA"U_v}]EҲzvY@ej. ]P,vY@ej. ]P,vY@ej. ]P,vY@ej. ]P,vY@V -J”D1MjY-'RLT@k*jbY XK۲8L*jqm#nH$ -|E"7 %r(lz(MWQwSvr#f]kEI\ȶ%bȿYFȕ[RGm im]:ZؾUǜU$S(|y~.=$IMM#wvW4hOA²ɜ;&s9Hi*QHd:R=HHd6MYWWT}iIΔu3Pa$Q$NH#Hd}wTIKs$r}UiH1$DJ!EHy+AYDf Drˎ$< 0@@ T@ 4@ t@ L "@$`6,ppxI"0|W -qZԵDS$h爮V7C`L4LRK`l ѹz@pw -XHD&!ߦaE6%'ԯʶݺE5cQT"Q$ayu(ҟ) ߠyY,&X3`Pϼ3 |X⥟"gWs>\X䁮" ѮT{R;JNHs =.)56!>6t -HwEjd2H]AEmy  jsҵu~]+A|;,2f~7U桬°ˢXV"9X3 -g:oE w]Y"U"mE:h=1erJjZz`F`f"A+`pGdfȻ:'^!A]"~Q0)F;A,ZvIp\Y],]&fEIX"ZEE,EeՉF"s=Dy@ț?<^"E< J$\B"lI(ru!h=s$d$R|o s/\x.?=e" I"1 I.Ź. lw;]͹bcHhkQ|5LVХLH$➠ ]- cO 9 B"ݐHU-EO}"9lQ&X[6{dU) PP 00 X 8 ܀>< `- BA#ȳ"?"qA[u!ЂI4kUC;gW. \? kjɓ= Z|"F 9_Q"=[bۋ覺D>f~ ,;AQ"f \)J Q"rUd_wi7/șge=.;~㘨DSS3IE=E=<$R?r2IJIu'ĠKbc)1.MIt9c%ґwBH]0B4T_T~LVVuz{ i o.iɯ,n3UH$^OC"SHYH ]i:yV^D -CDZ!E8E29P%P5-=0#03 -X؀ \ 8^dx@Z!ND^'(唠#.A3,h?C Sq7 UY,+Eǰgjܓij5.JQ"wыKHlQ"zQ"z %)8e)I䭒w-%Q"D^%rDy]i%"z5"tiMK$ҹ1)E)~$r(vgVI(Ŧ&;8{39Jwx3KJNsDZo;Zja{/e -iSA`eïZ( 5+ڥ.m L0?t@"ːȝg&oieQ=HJ$R? -dzCH< L$0$ -$r)HzD29P%P5-=0#03 -X؀ \ 8^~, -r9 yŲU:. A+C`\LT"T"X[@Hl]y].pMM"ե;%]D\.zD؟ *O[H"EkZ&DD|WFB"(D.ے$RII=(g^d}eC"O1%k!+DnXD=x<+]|p2I$>)!>vS8aI\=6:59>cYE*~eLHd?]QRʯx8nLti3*~h#̘}oj7ލ$2:$5vK͐Hc $x3$RP> r9pL< B")3{ʻG< 0@@ T@ 4@ t@ L "@$`6,ppx(]D/0 #NU%U%a1TM˙]X5֠Z`6ܳCtkj9S((R$JĶi깢D^%"ȹ["fE$_o$7n\{J lDbD㇛Ĉ՚/s99WB"b\8ԇqw!y/؞r-< `mWu k"D}X]]@I Ę#Zj?ݞi-ue VL* B5 ]|+]DI+!"J$Ju曼7:ݟ$=iD<D"kʙOc}IdmM$ޕO}g. I"qqqqv.%>n$ccnMNbc88浯mL,r3/ef>D -4e$$y&t~]sG$2ayS飼ya{kaX_wG#C"cA"!Hpwm(g>XDކDt(_!$2&$2qt{) PP 00 X 8 ܀>< =Gg2U`|VPd -O%R"hmk5)kfR -):ce-8W9 x+W˙r$R!ѳzD'{VؚC޳OǛ1Ge~/qTIgI$J$LB"~&ݙoя!k",ȫoA"ן8$6HD{H -?1A"Tf3щ8JDzn{;!%I两Ǵk? +\W4LK>*?*WEfvQܮ;V?zaN~Vi*UCwGTYpzT+|y mwYK^6k]ȕ]22,WpE|#[k]cZ`(hDH, -lX./?k'tJ3KJERY]]=Դl׶ou( ۍMeJcI cL*-ۭ}J[vG]U:y%ӧfmo1j%VQDc<̬CiW4w]!JW#7WdHsǴ$Hv2}]Ө(^ֿ+m e #yn_ w|-y ʸgO}e?x&~CKNܑǥ$ݩt՝b8;Mprll2w ܑ10YBh{9E2TWe]n|/]3BnkEFCSKGq7>ě VPT.xi=䎁1䎶g;?FR)ǐ;nBK˫{f@iB~ K!Ǒ;Z< 0@@ T@ 4@ t@ L "@$`6,ppx(slg(j2zu&T&1͛ -H7WqvPp׌e@Y[sGE -E.a-?yDe.v`]LѦBQ$Dˤ{#wxzB"çpܡO;~CWZFuq qxэG;MHtFD+٫<ջ~//B"AEVvreHAuNFP[կ  Kxo,DrvY{,@" [!H$ϐh{+)w/@"M<+ȧ+ ݇]t2*S  -*:`&` D p8 < |x@}"%LgeQu9[0 -1MVeXm88|&ܼėkHļDbDE|CB"1["ol; $%HҰġ-HmC2u\sH"1Q<#O!&?;9GFs.$T3G&] ;bqKR<%$RT}&͸v>HW+gV -!ǂn -Œ%xث8TrYTEAHd5RMA"oC"H"tC4IdW  -*:`&` D p8 < |x@d(g"D";rQ`nC93yz{ ܻ1 $2kF9s窏gD.~r$~q2In%'۝&FgL2vb\Vf{JYzosx *IW=Wn j*HV>ծMFCvה⃼-meKˢC{,tlY݋iDC"s!H/LH]~O+<-gށD!?C"S  -*:`&` D p8 < |x@*űe%#rA"|jԫDSMYN:@ c oIX,O%RN\]]F~{_F)JD&!D7nDz'H"iIcڻ+Z:"J$_Ȉ(s$u_+DJ"ϦJّD>1$ד9($ҟ S?uT\QDjÇs~{5y(gy3$xVTdg=։ኦTT;KO9]JuDfdbjytEjr~*ztM'i~\gPPfqWkxH"]9a\5qpyH#H-P8up R x$~O<$22~tMA"CCHkG$ǔȁ( -h AQNn@eZM&cGP^AUNY;5D[Aty0`TE%C˙YCmwt lq2qWnx*|o0@U&1@*1IHD\%&D["Տ6͎eck%,ҽi꿋޴Au5o+,|Α. [bKB?CAK?1*3]V>&Ιj -LLbqt+>)nDۓ]K&:w驵(R&R}XAErr L~&]AvL}A}"Oi7增i Q$.쎪*ciE'a D!.È"HK<4ӣ23tQvEc@@@@ D(`V`'p7xБ2ف3Oe &tv, 6b'򀩋ZFƵEHCa9@\:@=8]9=|(b\ԉȆ ׬Q Q"KD"%$J (-ef\+z g~7.C"8漐ho@=sQ$\晋D^|RM!w'%8$;ow%%%!N{3.1&.1ޝpDsLNA"m!̠bte?{ӵUuew_ϭ7J{:Ìg*ys $R ,-joϐߨDD*![~i}wgNW  -*:`&` D p8 < |x@dU WxET+D hI+$qb6NE!キ8Z($ O, e]DND:(rD4["omǻI9?S)ѡe{QMqv(fVXwC۰#,cKX_iW"W"|vQH6Dg<\3;QƓIMMsn7V&L `Oq+u?h]R}&+R&c?(< ]GܧvrKyETz,^0(i\l7k,5VxGCwrϴms?(?((HHDtDNbK$̵'H"_^+sĵ2$]B"1lE[[+}̾gDID9] ~1QȞ{>ƑD~3;$rv&ۣD+mOHIJy]q,zD -t*Y8YQk9W>NQxXPuq&kFxGDTCCWfj>nήs#+RGMa~j]rZ+YGv9UpgT{ —}ޢvVw>֎drJjZz`F`f"A+`pGiRFT̸JRL*UJ=UVe;Tp:qtWeiSY[U2cPŖXc{{S;F$+%Mvm9? Sk* edym| -e\߁⥺OH;nrcX6eOp;RD$gBrSw#wb#LV]8*;JSUU%ՙ+4Ը_}06:;;JM9D1=a-}jc cLӑCCt8=OBFg_ w?} VjxvV#wWxL`(hDH, -lX./?)>Md;FAPTe;QMAAS]v@?&v"t7femnYNuvEg!9# ͒ F$FnڡsGm-=]'9r>9mb0H=bhsA"wْFwb)H$-R:ܲ Hd]/\xM(^*oCH?6c) Bp'nڰȹcbcc%Rtުٵu\HdKZ*VӔ~U߂ꖙftJ_W^P_`44}4Z<1~c} 3x|@ҺJ޽ә%D}d3etK#c@@@@ D(`V`'p7xHё-E"WTTT T\کܥ+D_PtsRvﴕN`9Zݺ|C/"Q"r ĊH\B"["<ѝnZĎnͷ7#JۛMPMۊC'-;VXƶhYߌy=WsPK?E5~梀g| sIDbRT:=;Εz+ŌՉt+iʊ{Ҡ4y)MjʃGժ0cS%o#͘QSEH"H0$2C7c>DZDnD2!W!Y$g!Q3"$2 $R~D29P%P5-=0#03 -X؀ \ 8^~^N5E^ Qv$tᰠ$M[u_W=WV H"弩 o|^Kɫr˴RT tKOҶ2*-g[ a3%2m )D -ZΔ4Ho{DfބD&nDJopwzKH(DܟD!:Aum!+W@@@@ D(`V`'p7x8z]4sDP643[(C 1֑#GCt/f^T`8;A+/@Y[!֍B$B(?m:r["?L$6v^%brfBba7Dƿ٩ܱC^+K+ŋr&ci]"!ڛDrf~Jsq+<c'թݤxWJ=99!rq(g\ q)!5)Le[([ph={9L6w)SAT3 *?MS.5U/Յ57kG-~ԗa4 W[1]=;*|^+3 k7 -g;:!2һy?Dh{*$R.$R; ,k` 0@@ T@ 4@ t@ L "@$`6,ppxk"-kGb(' &'@/ P0NS H)M" H"]QpNWFLU)! L(fYQ"(HHİ%a#t[e$1?$q[e.%2lT*43?I?i%oA"3^? D~ӹe؅$xRuƤ"$ RSxT2.;>,7so,\HRx&K2_5{ҵS]~] b4B"cei7O"vW_UDzJ˙AH$Dg$RH-g&?Bw9=ݙJ*^: H^9RtD"xL`(hDH, -lX./?=f3b.lTikKaR0V &tVNOlEk Y6AEZ]|_H((RQ"?n*(lz7zK$2k mYd@/[a޲-a޲og~Ese v;^?#<_t-駱ƹd{LrLH۞fS d.y\=QjzV槲f3-iTwF*ҩ'#ՃOGj*_>ӵLDꗛO3?>TwLsknbD`tdϔ!js-+~km9:> -gN\u WZvsݣQW˗,%W!S]`(hDH, -lX./?kŋNVcZu̳3:N}zLMNӍo<03feZA3:3-m:k6s4>,WYW{gE-<72؋yFTFHs%qiOeqAuvwm|K \WRBwm!o֕;;$Q=`.0xj?qqV1+⥟/C" {Tcx{<:RX{Bl|bjjjlRlq19\5Hg^v1]H{5Oҕ5UEת>FSz¯8o2y!8}oZ7< Kdj;vGf4FQ!펖94p?$2] M"k}3ij򍠺{,]S4m -l@{q7/A"t Q=ô-/mxi느D߅D* {ڟwbˇ:#$OCED6N(j CDStuDO ;94M"-dXkGa~0zgx)83#TMB"lD~GuKd6L"w}PB"IdBL"Z${Q"jQ"Z$-i N`̵(gv6c^B"pw>I䋗clMWޮ-dH H2~c]O\3Nrl\Jb;v3ٟɚ?ttEtp_jPADvį 6Z&Gy`i[xDKFaX䁡Q챬< ~@{:Ζ -w6DB}' =%ctːH,$2>t~D"xL`(hDH, -lX./?QJ٬t-zL)ԯ]gWB.TN%2B\"3*X Jw."$[aƉ(Ze* ._Z$fL H/ -nJZ`󉳝eO hPtGQQ"Dn~tk<{ B'49["b)-D-H,$` -K7a!?~2 < `ms9 U"_ee?QԳf z˪q^05[6Iܲm^p \sFos7L6a! %$!J^Q"ߔHĖHw;1n8,$c]h 9EMu%!?YFȟD""DESr;y!'h7ӼSH]~7p*O{<$&cS1nh{bRrMIKq%\{\AR&މYQT<<|T<Okurˢ}gCG}?']Kob"T hAz0Lfl'^~@x UXfUeuuVjY/Yg(\geMM̓e~ѳYͺyJyYnUW+<(Hv(']!_H |91NB,g׮Fb,Eŝd>xuGW3/@v3]:9)4uӧ'nك@wi,8~[LHקkʁ(AZЁ `@,ā,`8 .8ЯR%ʇ 3OyZdEUQ%kډvBе/ޮ!I4vqDTb"|9'cqt^FmԭGckH*uǚp# -V-[+%bfg1#?uGSqwZ`.!Fw9BQ1f6ȳM6Ӿ7=X0OfnHHL,C_BݱxJ\i΄‹W,odνKCd"GY2VgPR})Nuc=!}U0ikٹ*>~ -!rCME) -HQ :,GL F]x~Duz[ g/r`@J`AjЀt1 q` Xp <|NAQ(*Ĝ&1^/jn&iblMhL,u*Eq!ЗJDߌ?)*"rQCDyn}8Dz[*!7+C$tυCf9>%BeU#-:Zda![w"D6|g"'vw-|:}7svTw;]OB=D9|pOzrËmvzSx.G9t:ml"mDf&-7Co "D_Mu|L؉yHu&s۞K/"#a]:, ̹pNnBd̻s!R\W6Mۨܿt"D~"r`@J`AjЀt1 q` Xp <|=iMD9(zBmѵ>PX4]!C>"SBlgcPp sV܄$9ryJdO8D+p#W%D6U'F{2Vm>SmdzfꥐlAUѱz|lA]㉐~򿌆7"XT1#Dz"来)a 9T@TKTc:MtБCtZHV>:Auѭxn"D^,oPXP4F0A BVx!At/])%Y) )^Yųsꌠ53M oxc`1hoi[H]傧V\+$<>0|ejD{y8D6z5VW玮naLu&_[3eE.wUĶp[3ݏJ֢E%9DMc?E|^dQOmc7R ,˕JĝIN'3LOZZ|ɯ1V4ߌSȫ´%(ߊS8NUcԹ'4;ⴝG>>4կ76uE̔[=2w֩hx+l&kT|w􍻹 _U F  m#;>x,T hAz0Lfl'^~@x @JC|9ȿU9gNzXkguhCX{i$q.z`u:6wv.D=z[!xE_#㎨H[ pdi Ľ*qﮎ{?[Oo,[$^TpQ"2)ZpQ"2hÓҝdw0_۵dm1+/#1q1_d7 A&pg[.ƔxC 1ItwbXm'eyRI緹@>_LGG1Gy`"z):hAHS>{1k߾E?z+2oAыqN)kyT z1?Pw̿N)ށc}|*>^wlyJY}])e>0Y (@ ,@ Ђ`# b!`+Np @rs'Lǒ}D5) dm`X0DQhNb)ZD[q=!zIDz1\X8Dpl; "p;l"w~z0|ŋ|Mѵ -GJIȚ/Es./-Ȃđmz9I˃v](<"Et-4H~i_ 231:DqC]wr\|b#>YKn{Kpr$+%"s?G]4'0O(W=KS{uu!}kFhQ*TWw!EZo2O>b)2v})2:LS$RyxHWz+CL81%.E|D`$Qvlj&z1=@t-qP0-34fXDnV*:ziޛG!7ۄ@pIH\Y21Re"5V/Y㮺M-E|aSx shHݗ",Bet9DX&<2;<^_좿;>ՙaOt{Pg Dvw|"ǥ93 +螜ol (3^RVfsTRO٢V6u-!}FCOJyӹ|Lc5Bc!2<ݞK-m8 -Vp<;MŁb/ !'"C䁈GJ߲yW:Ĭ!rSØ#^[Ø3=t"D~0d,o^{Q1?@pq vpa;Y^=#-!EL&9V=͘(y 8>,.a{kCf|v堮hH?Fq߫>>fy:09q~:B6ҁ}t=a~az+eޡm7c^@tO=*w?uf90%5h@ :Ѓ`80`8>C)ZzDf\w >A** E DSF'SG-!RC,:Nlc8DWGWA|Z$*!rӪȏ^sGdEU˙$3{!Djz8DCI_yD G3D>?KcqW{s"G݅1@7Ed*Dҝ ,=Ms˒t{V7>љy˙537cf.D{72yD|ra6z=dze%E_Qi427cJ?'gQTq)uMh_V23tZt)BD^7{!RB !2@#D&,PXP4F0A BVx!A,-gFDyaZ^dUIzhrEz[I}!O4'h%2B3WNc8E׸"w<7M+.J>wėQ!⎸YsB7E_+ENyJ%Rx{w8EGw՘ -SdHRGBߢycR~`dn|[ {C]Ld833noݓEq]YYތĤ̴1nyP\v)2}8G9v(x!j~:<9ٚ]UO OtF|8Rd "/l2Ob~)2 BKgQk^R$Dq3Ol]@̼=, F{v-PXP4F0A BVx!A .n#Ͻ(NN t\H -Y2X+*j⦈`)!>bMFp &- \$>10}!S#nVKZ*Ȗ5)"lQ}3l7DzY9nW%D3 gD|FibDtN)|7~)Bd(EcP}!,'Ӟ>Тĕnw%z]\;s_oMc=W9~9awCꡧCҲm~OP78O6)McO!DvRdO?b|!2u!2 !R؆R JNeB$AY`hKwV6E5 -Ԡ-@0 b   x yEyEQͫ5zGDC91N Iӂ`l8E)Eu"Kj迲ɗrWI p$B䋫"uz خz1G1/_eb$*_2/Ezg뙵_]Db"r8+=;"܁s D.>١Flx0Fn/4s o7ͳϽ}!rf!J>pa3>):llC%+2.}@+[^5=7u }XϔW-M?F̏#D;6JR,/ D@t]BMyw"Dʆj";P|:J_S (@ ,@ Ђ`# b!`+Np @~LM&YٺUg5euɆdPiz=B&!RZ,mɮdϱd5&=Ɂxr%ryDk"PuJ\zs"?jO%0?qO!P8D!UsMK"_Bb%_GEc|]=t#DsP3vOVRLpړ Yvwzb+5=#!-iP6 Qܟod&onH{6UPU~H=5ԡZVPWҗw Ct oCt#Df̥S,ca]3 H"yX]D#Dvrf!2nsB!߲"r`@J`AjЀt1 q` Xp <|28"̤8Ngєmѵ -Rb(yi_>m%AZDlCkV 1">_IWV"OCdm8D pGls("$"wJdޣ /^umQk ;KHfXB"oX\>|?!bA%#)+=#э_x$t;%Ӓ8wBbj]˜+LG.^Kݬ;MO&q\OJ؞wD>3粜I\jJNse$9̬Tw'=Jk2wiN3 w'_gHTǂBxV&NEt!UqKz79xUgB955fS*#{=A̭]Q1}w*b'^j7j/j?jS jS0jaf^bk/WlS̯VkZ+?o9999o¤uwKU/};'+?N| ]ڕװ/6Ew3/Üqy&|/sF}q=`A撃#- _}cH$5MLvg:ӹTKIRxHēNKHNH-;·B%rCd+8 ?RE:rSH*xkH7ߕx>`)8h6u\] aH$8$RW|y\$R C"͝<$ҕ/ʽT<OA"UC"SHQH$$RqDJhjZz`F`f`VbA;p`px>Wi5$UNP͈tӂI4,^jlL#$XʩDȴ`#Q@\KDXV_>7 H$(I"D8I"gHI"$e$rκHɯ%R]k$Q/Wk$3wDDI"D4;]"D8tqB"C"SA"WH$D!Dn>33|slS?D\HIħg&g8 ;٬Ąx.!#=5Th>L%~\l|o 3TLVV4-!m;9ekZDD-mH+bJ>DW}vB=$rHHdv`'wK5$R QRHdavwi)DB"H'$TDP 00  ؁ ܀/? J~rLdHt*&OԶ}hh".tˈ0u{W/aSܳ'Q·o[" )THJ^I"$#$DIF"uHGS%rŧ$ܔEߵee,-E]Y"Xds:shESpEbajXz"O y`, -X>yc`9mt#Yēʥ;3- aӜn>exOO<"]#*ffV5zͪѮT7k#uOoGWl,߫4b5U,*kaGL~V\[Շ_O.9MĺtlS6Nu\Gڿ+u*ìy6ЛNu*0 hXĀXlXxAUJR֨fUtYazIUiT*Ч2Le*r\e*ۼʞwSU*TyT|;O)#")-)ZR Q+S{$e|N'}qVƙ2ʸsOU/AW@(ݯr?qE}2KU2V>>XWƈ2es.š C -Ce-PF|zYxGB{ 0 2.H`S<$9' yNOZI$)$RZ8zdQ|k 3I7cQUMd!9!=Xo8I%AyAKeMr)9?A"m.$HHcHn$ct3͝ݯA"K!jD\G-ء *:`&``1 p<^~A@jKa%Q5 Q'RĐB7cFQP4$R%S]}"Dܽ;J"zD/D6HLI"2$r$ḫOQ&OKduWߵ^&(%4K1H$EZi$5 -ț%b?cnʙŎȽKȹSȾ|qL +v$uFHj;#ՓyOOp&%Y YI|iSO%RBY -$rUȞ+w>045_FZJZ#@"sH߷En+|D !Hd%H3 0'eœز$¥ed&qIN1Y\3>+5#-3>+1#1^9rxy3| C"o横Q46ctGG 'C -\w_r̡.3St3mqs(gh93gHdJ{cHېHٻ]ޙ {!NH .D*7\9SF̈́Ւn iB˙*Azq\0s9Rr@m!#Z[)Enx?I4]L"gK5"/^sM -.*gU$R\#Sμ"IL\.II"gHu;D>p1@9sJ y|$/c(xIdz$DzKC˙?n$$&驩t72tgr:92RXObOHxDB9W_DCucM!mL;CƎͦwV냖 AkY'$2xc"H $2$Ry(}QO@"+QΔ Gwz'i9Q -Ծ<)0$DH C"aH$ !0$DH C"aH$ !0$DH C"aH$ !0$DH C"aH$ !0$^Y!L]""D+j#kERXF?LG]$i越4Y-(dHbE"JXtZD~.3VVȡS,{\.YD+cd\x]&3L,EaA,P(CO+0ߪGOIjG7)1ɓrg&:9.3ɧlbf:&u,\K" -ȭ[aiDW:vh7`z"dlk290W" EqD ޲6X -t"LHaDDqC/#|oo;9pPmE""GoEf NX *:`&``1 p<^~A@")㢪KTͼuE!psh"Zm8EWȎE.xE荤R)_ \ EE$YD%EEnY Y"_M"B33,%#[O_9KF"EvZ&\p-]Y}Ioglhs|)${n2A KĄLgrvrY Nލ&`3xOqڢHxGyjG -efoHPj Q$)~ m d{Cٷ3DEfED)GiWv[w$$RLތ( EB oc $2rD*oa?;(( T@ 4@ t@ L , -b@,6`,px "20$]TMuhʈjD}hGi>h-yL#Q(qx -||m?Z=H$5ޒ$'#ݺHĜY{׮&ϑDb޲se$$I$V-;WF"I"2ID$HlZ@"$rq$+OQϴ]`|uA$j7ɓ'd997R3Ԥg|fe%S%R{GFjHd|+>~'GUw,[D@xH۷~#[-`y,d}l w+N7M!xImqmK{$Rt홷DDꦐDnD_-xnDz!Ƈ!:H^S  T@ 4@ t@ L , -b@,6`,px *"gJzQuPT^"YQD 6w| --LTMRH{wHל *BƆ -sK$h?hftemqcA"teH6  A" E_!Ǐ![ !ZT@"e'%TDP 00  ؁ ܀/? 'VV[ Y^A}h -Jx0*󈩌KtwJ -aGw*o");HS2m7Hɒ$UD$'#ߺH䡽E6)d$D'I.I$#KE";؞se5VH q|@E(gD1E~ xb/$Rn$d'1Osf;dg2{IYiIɉ#%\^N"M -eM[HTΐ:f Iݱ~#` -̦W!։zH!&͗Uߺv)DDD W#C3%FUXTM#Ѷ #`D9#ZyXhI*}@p ~wrEO w7UOw*ID)I6I"篙D$/ID/#u|Yko(7ݴE>zQd"_M@:$b3GL C.  hCmx8>1!1әX'LJHu32|i*2vMGO6 YHd,eOB"+䋴}iC/N>CZJ%KED&N3xM%`@4P5-=0#03+ ؀8 8<9!iA`tjDzE팠+Gˈ34 &/8Wr=3&p}gǂ|G>En"wHqK9SF"WKqK9SF"?ZY}c/=pLY,UZZ4AiWZZD'"M/|f?,RȋS+yyy,>EyQUXhFH<Z& MZzۙ'e&3S-RtC/S̠Y| 3F!UlꀦO!mD{,[+`nAy),"wEŌ wy\gv[])fE43X~oDA}F,2C Ga,9a0 hXĀXlXxAfYdHtQZb'qpcļ(XhQdL 2(]ń'I%n7 w"ŒEΔ,,r*%ck$-DX$>= -jjykcY9)Xe,rTsRXhzlgea԰Hv"V]^*2G=rXRzϸF$e%rII΄,ţIJu&yR3y6)9?˖E-Z=UluހloH0Gכ7`(}(d, [aYcYd}X[ѲY:BUX1XnLRV"Hk -?;sy9GG\$_g 4A"O@"7r>Qdۖ{d.z0PQ$))9+˓C2iΤxO2$6A.w[R(g0e 9wӭM3!{9a鐱l*n4-um"ee6mDw@"Ðl(a>DIHd(_EFwyJ:|&!}Q{C~OH *:`&``1 p<^~A@}Qy0y)CD+j#JQ?!EDSh#i*n*oۊE{h#Vw ")[EdH$4Tb8%"HĹ.[]Y_XcT|Y",-E~%ckM&"8re/nYȕsWQEA})HQdfQ: ȗMBLXxgۓ O+hZ'OW}Fd9pϖ#C.UA~k򋵣\n&?WY*/`ڭj̪n@SrӪ+@_i5,XG/0_`.Ztgأ[}cע Q}O٣A=r7eJJ!ߔ>GxzDv6謿"LI:y<.1Iu9[pm8ϡ=my[V!- S?㸹@K?<7T;ILNLLs|B"Pt] -\ '''%#d%8tI̜lRRzMϔ9;pWHB4SZl`c@S@H;v4Gw?[_p0LȘ{T ?/*${j[\YvHݙ9>3^L iVi魣M"5UH^jHὰHOՆ;;"(L2nh{ f0"ǃ偪tgfNe$a~@pt؃Lঈo VI(Y<X%kvY"~Df̿)%z]$rEu-"g2C+i!$248䧐ȾsI`!x aλjbC-&dSR|2δtg&y6d$R~|]uhA,p KQ 6g hJBڶ9OdgJ򪐱.<0k5)="EnnK罚N)KyT"մ罸9ȵf*z#D%KUQtI\Tv nTD)hEm 5Eb'jB K"t5jSNwp0.xD_;//E$yvg%l.yf!DK9[F";$$rDvD%8H$@"黃]*H$~ uA"(œ'I"̴ H$ '='N[R- h^ݍPNle& BuS!bMH7V~̓!cfSßDJ%Zz8vqmnAAH63g*D*P ))=t1zcHaK>9,C  T@ 4@ t@ L , -b@,6`,px 8}w( 3J{DUXPOq=$=8sD4Ӑ@/ː#zq'zw -ou7K (ZG}K$0g2?gj5ܚg~ IdNRF"?X;77u@">r9zN\7J'3X|IdI$:zOK ^|!H{C$3SYl*$NcYN6+>->=+31#.OX@<ؽ@SlǗ7jH:_b<2n6?أ0 ZCHGtj۶v>H{![ D@"wC"u{ kH"=H;tLKHAHP 5@@@ Ă8`v.7 | H 4/%D CL 2AH!iK-g*= 8q<n8 ߔ?\%d$rD+IH9KF"]Y?sHDXj,ZZdNi3XBJ?\3?"7>梤qˇE:%l޹,¥4$N.IKrg:SS3ܞDDTt^->Pօ2UE4:T.S@jH[Wҍ0422V><QdX-^7\ݶEfEL%3mȑaɛTӫ:(Wh6"]',TDP 00  ؁ ܀/? JKaQ/,O$%Q_-v;&Z:h̐hOH+7m="WF<"?tNޓ֌"&ٮ>#jNȵ2NZF"$r$D.DvNfPw $ҹgFg4OQLP|7Qg~ֲ$+|"$sLOޛ<صZBvVfyHdXhـcoH[3;ojZC#MfSq3w-Z/싊i}y: Ðx$2r'ݟib; wDq/DyjUUw+DvB"C"%؇=QdSxM%`@4P5-=0#03+ ؀8 8<Ee DUGԕDNuCDO rHDh9NHG!qvRpO._t%""6I"12Pm#.\l_$#V<'m&yo]6ym_YeAJM"UT"}zf@ DLA7Vs\35!I$:ӲXgB#x9}YcQ*yM"OTux i}n}KCW̦\3 -7"Dr]߿vsƈ|$EHI$?$R>$4[1D:C"ڙN$0 hXĀXlXxA,'D(Q牚fQMt~\44G)'6yUz1" [*aZLm^t - [%ܢ)!rN!skNX5IƚVM"{jZk>c@D>&rw2lI"g|ιf_\"ȴr# B |3q!>[(g)r NjROtg:ݩH$>>˙ҹDiya'$C"!ԭjaՓts(bh㻛uғӒXa"G!W0Uͦѷ74B=9/1,vH^HI&hD?ImqKH"UQH 96OVHY-H!'DmV+xutsHюk* -h XA q\n@1*ZT.U&h]/qi*biLh/:Ezoa -.zxkEZ@$MSHd}n~YkX]"7reDJ"HUia5D^]󾆅Dv\r&O6˥IbΈ$RI|)$`|ٗ|#I$3>f&aDH"nO39)^@lzYw۳[ lerѳT,5;JH[_K BƩ'̦Nzw)hiiZKi eٙmq5mǟDF>D{hY(w!F3cC"e!!}QHdbnuB"xM%`@4P5-=0#03+ ؀8 8<# -tCUa4aA[GtK]0T -Nth*Z $(}8D(Ȑo ^XMfD.$┑H$ %8e$.R](7ݼD"v>(w"OɈ5x.+_-=S u+ E9͗">3 ڳz ,5j|ONHX9ROHfҜ۝NeӓO"()]v#`+36(R{'[ iJ>+z̦`*:b5NY"\7՗g""œ"c"uw"aX~X$,J3='{Vߡ *:`&``1 p<^~A@FQNB-5 D+R0'EӘh,4T"mֺzHpQ'\&N$*#O3Fj"OJXD!YI"("mI_.׽2gH3kNj],owE7UC"H$6 _L})H;&<dHMgPIJHJO IgғID.+#JnQ{ty QۼB H|luEK@5v膗 CƂ:;]y0h-+DZ=՗塞x}i~.GgS<D^33; AZzf nqtc}i>2s1ϱ 8 9ݮq.OƷzZ>5VC{B޺|nJhjZz`F`f`VbA;p`px>,V9}hjxz@ެ o5mn6667[6Ӌw7ر6wmvnvUof[4ܜS|JƸ@2c1Α.c[+8G2F1V>g͍qk/0o>!rqL(qW׺xp]br]sկǿa _?+cT31WS? s7c:2 x89݋p$>MK$qyu玲LGn -+r% !mu^H׍Q02c6U6!v,vE5e]ĎΗ.(n^GW@yKvGo^#^mWf@XKM3bG Xw;['Ly -$E='jrSSt)n1^<)Zh쨧MثcRpMu{>էxFRY-!jC6 -k6#>}SWmr=2e{(#J {(#sZ("j";D!bX1FSו[ybX%j6TjbfBzVf3MNDygZRd6>-+KTYrj~ -2~)G}po@Ӳ?m@?0y*d4Z HRz_Z!unn2*&:9C! !!HYHJdnHrIJP`/ jmƂɠi&h.O˨-;G Ap z/|]`!gH I"gH$(I5oDZ>k|Ȫ`͉DD 2!벊ʲ hԮqH<yj}g\w!xd? ٳS\jbLKˤW̤'9S$gbbB'KLug^w^>[8PߺiD&\?G]T4=rvt>s=W0r[!u.:Luw"6rOӋfICH9ztM"tt~6?&nzn=[I!0$DH C"aH$ !0$DH C"aH$ !0$DH C"aH$ !0$DH C"aH$ !veȌ1j$iA(h nFWޠ1BL|DLJ|V za{p* ě'rKT%I"k6gDH޻Ɖl)IYI"d$3I"J$#DMgv(g粌-l?8m1SH&SkzsZIM9ޙN.)Ln'$q |2ɍ)a?uY*ʴ!/T ꣍HH]plH($Ro6M&wHdiOZRt˻WSVOO Tu!r!cD@"#ŐH -h.omr'e¯C"+fӭg7!A(0B qA[At2k͂i4h> XFU2v@Â. n;&xz| Wf~"՚ZF")DReM?֜At$2xJLyQȴ*w$(IdZf;"$D -!{NJıH2::}$e~"\rp.%$6T9ϧI4MLJ䌤LgV|*fxyi#=,ʾ[1H8Q/545/]!B~)`8JXQm6@"5uAڠYZB93*3C"-o{wA"ݴl;H%R9$2ޒiZ!0 hXĀXlXxAUr0Btл!ڈKt D/E`̓M"Hhkı[I' W9Hn[&Ɖn5!#+$\&#ˤ$r$d$uI"Nr׫.N}Dܘy֚#U?1~D2+WXHMD9̒=#Ѓf`84b%AD -#^n'\'L~x Hȏ%ݻ=I"ʙh|o]$ĦϚڙwUw"]2ܵS?I"wy5wea WOC"@" -ߊDN2Q|Z$G2$?P2n֙tr|F39>3əINJHLKJt*wL5-OCl]R(woei9~/Gb'քBq󑀡1!H"{чbHdnZ5^H /DDFGvz'>B)Dr4ĎqH0$>pB"xM%`@4P5-=0#03+ ؀8 8<_%vjx}1YnL,MYI !YnϏ=۬]=mHMVtRRDVRdRD"CDXEr+"b(beRByF n(L2Cv&L /eqVǏGk^qf6OY{,DC"gJ![32  -*:` 0 ;p'p7x֋ovT7#;{|UFU̪Yct$gqD-TeG8Kkccq1d>x9A74D$;A"w HaA"w H/$WcvreI!$r\?H!GydE3Ya$ҋs̬Si#Mj$%~_oc(߁;DJUg&!ՕL9dfHB\-OӜ:1Mǽzd 3ynHM/K^Ԛ.+wT9dsɇDrC}k3s1?D^Do[MO$Ǟ##Hy^FzȆH>]2*{Lq'ߐH<'CN:P2d{)atS3d֑+3왙Z* d +ޝD$b$r"jxŻvE -Ws9urPτn\+b$XdDz݊/"CHaa#d6O˰X4L4"'Ih ,( rJjZz`F b@,03+ppx0 /ZsTv@8E6lgT̲l^HiSzfX$7`2:9"bs5r~. -xZEx_=3{(P=3{V$tB~(u}"D.A"ƈ[ww iWۺoY{fF }Y:t V-w!1 ~k'+{pٻVwSX#=iiݶԍiqt#>% -Lf̴.4 A"{P !yviկ9Ru<Rg!KsMv㐮y~jgpc,}鈊+Nm5MU%wӤ(I;ޒÐH/$RVC2~$ -{~  ~䝆D:ކD:O.J)9P%P5-=0#1 X؁8  < |M,VV{xTd|SrVrN[$Ɩ$2/J s֢}rQw">y9o+ \""j ȊH$v_KׄB~`ڈt"4+bCJSZ;eQ$qx" ,0HoakWD -w 8WՁ&.NqINSlitJ37^aep?s+B#CϒW,C .z/S3~ҝɠ?Igc+H'VuMc{gɣ0yoWG!/;(2;HE^{3G4#ϑaaa+R@PP 0hb X8XK;8js!^Q*+YUd{fLw ՜qhHU3Osgouq1< w1<ynH`τErW"Yq(*U/Z)A"Q"Q -9%H$JD"H~ST"E~#D> $"DvWHE!/xHTjH;.Lvȶ;W0gݶ`r3ՕLuG D"-zb 5:HJ!y,EN_yp :SR7dijB F)2_s+HdHd&+D'vyYyWWz4ȊwwSUD" +Ū>S -( rJjZz`F b@,03+ppx0k]&{3v'nH -#E>HτD*DZǷyfD -$+ gIQqHlQ"L) ȁ( -hA4 \ h^~O3, -9y1g'Y,f5=v5338gna-,B9N.9 8F=3D"$"C!$miE$Ck -,gXd`;EDHpE,r`hUwHRRq՝uFwE<Ðo{puiqG0JphmslLtҮT+ ɛ+>8zY%P٬zx< uʚjx|z V]{ -72T[g8.:|d$5xhﰘwZ֩ >+8?/8W?.q=W➮Ƞ=/xg>{Lk{🟐0$\eEdbff) ȁ( -hA4 \ h^~O6JGTxl~Q1`TW6ۍڢ^HFmamlHL}#KGWGo_.9)wefU{ZLQs Hh4{_$VGB:{vE|Hdp7{7/8= -<4yɯG}{*dUMStj-͑ `ĸ+===5nI 6}G)=%x3SQZWֽRRt4m/tG /" 3x&!VSߞ$s^2HN2,7 я L!!wjȹmJ2Mo?y{d2¬}R@PP 0hb X8XXKxjUyN1)9i3ӷq>ΘK! 3ș8o=٧8G7Gw8wW2. 9"$,r15Uđ+SSVuTd,.fy5↙[$[Vfl+S5 $ҭX D"Xu "ȻӐȑe期bw^U+!Ol`0 - N%';6SN:]w=2mifjHd/K~LE!!=.:),Dt}I$cfrO %I H$T Tj -"3qiNz›DIhKtm29q:+zJ2r, v?2'VL5(:Ȓ}n+;^&wA"g35~mWoHWl@UcO#=H[Q$6<_d>r XݪQ92=o)z7=Gf(o$28ʟ $Rw -9?gJd@@ T@ 4@ t@ A &``vNn@p|'੢l7)ހz ot7tLH&s/o9[{x{(\%@ƀ7.m4+&R"HΈQ6A"wD"D~D^vcbQFD"QBQ~A"2.H~A"2ܾ"EGWJD 4#!LjI" ȱ(2}$%B"nD޸DޏYUNw:L8Ϥ9li7WT'y.Fl vB C{B,E~e鐪6?k~+}ɠoEcLt^/H٘7]d>D ewgD&*!H"Mze  y$dOU $rE32  -*:` 0 ;p'p7xL/m -n*^>+H4POqN;¼7TJ"z"3)@^ zQY;zxovWiQPdO՗Id"rD~"HdPZՈH'+"]Ed|Ilح#Hd DbV$hX o؏! ֥k7 Hȩs 廱ٕΤƹ[z0gJ-v$R 8W|.Ψ="rN"j $ґ+?R7fi~mI>][!u.,Di| Idvhi:|)?;} -Id$w F$Yg_C=MD> A"cgJd@@ T@ 4@ t@ A &``vNn@p`vֲT!'a彬UVpќ`, #N*\0`5O99˺Yw~[`O՗nE "Dz)E$Ϭ=wy}7%HD+" -8+"￟'"wi 3-M$nޘ⧾{Ǚ}I{V׆D3!͖芳6H\t8gzbYK^FmHiT=ZR4WNTOYN} ǙC}3iVy1?DC*T?\AB"D"DD \Hd HڏDf2֬]-gJd@@ T@ 4@ t@ A &``vNn@p-kwң>v^| Wm k {M{l)iIW\pݶ= .)%1bRwUn۞`Rnha%ӋWvE+T㈉sb;q1;=o%;bbٸDÝ찹Rbm D֖NMMNv'Ű`µ\Gq_3C[1$ 'ߗ66sxhKDZYZy招mZeOV\UvmҴj;`t -C86pIi)yne!:3xsv>a^y'}ӟ с@;۸{;IAZЁ `HV7!_V)4m$$T*z7)7F77i:6i 77鋕MưȎMQa}J&K&k&&G&&Wݬd7y670.eu0FYs"տ)]?+%P{OJeYR)iğӊdd))J\'S;sx%0{x#<;WUlx7< qaylwjw ]~uFB/k?N-F9"O\]cס~׎{ewMJ:UWvkF̦KbR7sU,^I]0K9aW#]?#r$lΘ8M_E'ڒ=q1Ngt,9W1"W=G#2DZo;"rh_P6rg64,7*{=mhZ>jK6>0Pg}HWֽ;͵Hӈ]X3"Rـ4ʹu"" !"{xKjeHc."aG8ZZAJP4F(0,`;8 .p ^UJS*Dt"/Dyyu#tm;}=1Hds`&*b/S3[py|'KIHň,FD""bDn# HQ4T)>!"{BDtkY 1l#$Fd1$kGd$DD~}"{'"r"r,shS>)&F"c _DDFϤ$y\W͙lsE{bmg-ř8q#R˯ӈ4/Kۘ7栬=(z4emfP5x y$~Zb^\,`$P?ԆI$N"Y#"eHD2lL"H'"ܲ<"2}Z9I`$)"@rPT hAz0"! -L` Xp |p@~&AJ~QAdc|?Q,s1Z }f?]/ u<"2H' -b,57#%y[+ʉ""u#r~""燈yOH#SEdrbD> ňJ7$"䄌ȞgiS8kIAD^&˘^|˯~梟WCD~-œ6pŤ$bcl-I%{RbSSܮ#Rs箖7hDr?H;>t zJ/F54D0uO>#Tsj8VKx[Iaj:ӼFD; w6?ݍ4v!" "S""c";|M)025h@ :Ѓ Q`3X -vp\?t>"YM|BPL IAKxmiB ):#"Â7:o/wT;]K`2{r&ZHDĈDÆD -I˙&BL"oхX\-Fm1"˙7$"DŽD,CD`,o5!DW ̰wX>`G$Z8٤hI$.IN&%!ڝHt+[ ^T"-ED{tYC_|ဢ7]Z&x2]~\}a>8$&)L"};Lsv#"݈H9"O'QL" {w0,+a2xkADKHf!"$)"@rPT hAz0"! -L` Xp |p@~qA)0-$]5 xxe*hˉLЏ 6Z#EbɌXΖxW-q -l]|Ղ=r1"QbD -MbDĈ""6$"L9Dv*"m+=bDn#Uk{bDd!"b#YCDd_7"\v1 "sjL"?E!"r&G?3֪;L"8"Npl-99asxRmT-LJu\)IĈtejGԢD6fv|4]6ߒ&Ͼ-(*Ucn hJjŃJ9G8Ca| & -,g -0C8 @J&/!d(rWVxM> R s4s,,b"C9B\}̳7G/_9 VD&VX_^T/Ċ|/DE~!FFߺ¯>UD^Zb=.gQifQbCn0oeb)GD&Q#2Oᣟ""}kc]ciL&n{Iq669cKIubH'g5 H'顏1 t9.NEAe4u<93>o(қ{H5WÔwdzSDdhch5"K=$l۽{ӷ4G1f LQdQ_S - D %@ Ђ`#DB /Jxi#aH !XMjWO'rޞ|b$VbcwWO/ze|{bDCv1"qlHDb"W`>UEbEn+&Vu4?G]xC*ro>"QQ1䬍"߾ySMܹG?EEj}*_ZQǙT#9yyIq6Grr\j*ʒ9"JW,H sۘT 74uBv.u_D_>^({;*xDNKHHk"tH5*R?F4#՟bY~AE -?;E"SV_S - D %@ Ђ`#DB /*i"M" AOm7 }`XgJb,eUDU-H_g+}"gNŊ" i>UD&""/9?DDGĈ3_*vs蛼HܷHX_I9"pƦD$ٜqG0$<1[B+&&1!).yNC'K2"F&*ColVLٕ3OUvuk5?k{;ՕO>safcuId^2cfnejw}W %B"Rkٙ8o -o:.PX|z'ד 9(@ *P=&0`8n` >C8 @JQ:1nuo/oQtlQlQ[4[[tsF}C#k @EZr#]F{Gն]-22"u_!n>!%M x "k? cxx)e1!vxe̲;DKd蛻׎aBYAD^EDTܙDD$..5ږDw`òq'8RR'm3+U?L#DZr`;3~D1ʦtU`zH@35ڞ'NLqgx3նGvD={ly9Dd?f"2xpGFpgӈH~~O{aD#sw]Dd*ՈkJAZЁ `HV7!8W0-H -xe Q -RA3kK]/ V,G0[:osApMD<-xߨ#HX638]jC&;$s-KGĈHCD"1"CbD!"rцD$aoȈIڮGDWE$όt "{0,}S?cU "'1gTDbRcb166嶹R,EǸ=.w'1Ĉ?kEiy)"tYCH#AeAUݡtugu@vmf "}՜3Vߋ4!"o0 <|ECdiTKAD&^M"s@"UDix5"R` d (AjЀt!f' ,x~W]PBD_a݇VL"Y~1$;6=WjB""Xx\h;!!16ƺbOZT-\9w1mce3p5ܠw*M],"ZP^WVD_9c_3cѻwFvs H=簜{˙p}nޕɛ]zϡwejS3#"ӻ29ה 9(@ *P=&0`8n` >C8 @/>IdDN+)ճf]; ziiĒC}w4g!q"[D!&#a]P-#r$l 'y4SUW(2)VdS<-^Q}MȦyzC[Cn3}\w*gr"T~l(٥>cC F3"qnGrۥxl.lKLN%Ec -IJMLM9̸}ePv%TdtYilMPӗMS/L??ו<#ѷryTd*Fg"LT$z[iAEEE[NVS@EF+PBTP]dl+@@ -P -Ԡ-@0B$D `+NpXH6z"#LeyyQ4C*Ћ"ÂƈiEfb\F,=0L+޵ a/U$+(-Vi"QbE+ٺ!5JO)3]'xkK&"{ -@؆U}1:"~;sS@DJݖYbw>O"cn@DsϨ "$G\4ɱhgljR8LKƮWK˷mgzkdm嚀v"\#ٟnhj2hDK9CmD%2TS~Nsq"2ЋV "D$.g2|Ddg("Y?=4!" ה 9(@ *P=&0`8n` >C8 @ʾ|!"$(!^5[yM=uD?MVͣvxs B^B6aZc<[{[ ݫQdĈX"DDbDQbC"笷kO"ySD !"ŠySD !"nHDz6Wky$ˬceV<棟b=9`=z$8mq9m l-)):#qVn/HM7ogz1(k8MPTvdU3c hj}D H9g\@D_a+i ?zޞYDDZ?FDzGDeK[xFD5#"5hM`jDDW#)"@rPT hAz0"! -L` Xp |p@`%"DK\>ȖWNKjWO\A;몈~7TIg]#6{q\L"{wu/G)Fuw}q2ۘD~?w "SEdĸ'bD7BD$E1qFlHD6L}%&EDBc 1tN~:wQ>1^YiDQ$`q:mOR-Nv8R5'FdխL-ǻ!" (: U:TT44MH}z]}7"gh~36!"]KH;L3E;}4"Y4"eG"R\ ,]fw'a[NЈd=A5AD2[DxD5@@ -P -Ԡ-@0B$D `+NpX<H0(!ZA>#(儠jՕ&SN]/ +LiAk/g; Zc 7F_eVb9S,FTȹ.gJň"" ȇs3<<BܞFD$!"#r8H6䩙PQg3{! -ye"_} 0G?3X)m1XΨx\N')Ζ\ cc=,JLtyN&WiD>H{>ƴ2'e/; -2tUKiG9C3-SO;LMw4WU""t93PM#2txId^Y):rDdWVVRDj5"R` d (AjЀt!f' ,x~$BNZ3M|'k+ON=i E^kyCH%H-o$iκ+#sP%m圯|9"w=_lU#!"b#b]eC"kO)"ٵ')&\|c5W#"ΨC͒ lӃj&\)In[;eKHu'PXO҉~k:[+8ѐu#EՋ.e.Ll.mtW뫎EFE>,l'Q-^`*{e{xe?Xk?ıę@ĵɿIKK7IؼKOK|EУu'$@ʎw|A)025h@ :Ѓ Q`3X -vp\?4[*SVYVyI1UٺUcR74u[e[uLŭ>b2omVoOlumuNnu-[٩ѭi-Kx$ 򸘏=1\gvߺg"!^_C 1|&^ [q[qc8D>7^]L{ o2sOw՘A~0uFݗq&:bl.7D1aNb<gRIUoݵPOg 9ۙ yLHWAeY4cA\^7xs+2r# 00޳ӜU u-A> Ҟq4Y(~kOb3Hao+= 2ك(f_S - D %@ Ђ`#DB /T^R%ri$DF$ ^izym+"Nb(er}<_@7~w5"R` d (AjЀt!f' ,x~dvi"m#Lf^V# &A9'ui. 0]хLNX*5SeJ q BCQ87w7CDQMD""7n$g4l*q KdgdWh`qsɍ2Ψ!1)qnb36Oll͉I$ŕDϬpWU(@elg2I"ci#EDja&M=X@bP٤)(Rgl,'cxu^D` H =άmzMg^(2nogH{F%z8Fڿ"R` d (AjЀt!f' ,x~VdqBD,("QE^2f(RAGZb,w!"{+'p}f!E~3#ň|?<>TW"r-" Edv-"8 Vϩq @l!f=CD$cȑ=|#yDX>q`}1:=:KuڢYWsN-ѱI'.WM+g"|,Voc*?ww34y֝EE5tki;t3$9@%"R f#"yP[@K'eDVxQ05=6y} g(EEFb-ӼuWG?,"!]ų5SxV3 ĈDRHzwC"r/9Sm{Cl9ifygِ3HkI$z>|!/!"Xf#bY3gl{ns^]5$X'1ցI$:Ʋ16mJtrb=E]Yrr&irBOgK (?ItաEMAr^>L"3rBX3}i5g9 (Ҋ,ލ>Kb93)3C/`93y"2ttw>|7B+3CDz>rf9E 9(@ *P=&0`8n` >C8 $DesrHPԍBZ}uaN")7ˤ`m1$8,qO- /O"GBL"|qxegWV_;XuӜfvWCDu6rulC"Ҳ+>O`y4 -Uzw""'~>n{ IMť&\8[;%d=q1踓"XMI ۘ}wek(UٳGźA{|3=Z;`vez =&1, b$7ԏ_l N"{'"2:IR` d (AjЀt!f' ,x~dq ]F>b -|(rūg6Mt=h^H@_r 4qT UM܃[!xLjP7!nϜ/N"P^pC"ggS<3+!1"a!"K1"rY/s`oM",==S2-"G?3%]IैegRD<9XbRm +:=ē)>rPCOQN:iD>m-7Y彥EUYUU\kU4 SVmꪆ~h66^M9DJ?7Ƈ/4?_S/%K)q)q6{Ff>vy/͗k:(:IH -W6IAZЁ `HV7!X9(̔n,3 ʺͪbug6ר+3󌆌tfsfKZ^ftdFWfwm7Z66)`x<,,1pDa -׆ܖI3wҰ[NU_yx1CѮw[&wCn.;+Pz -QeeUǡ}ȏuyc|?"yo]yF=pDۢcD+ybm1lJLjÞLFƮWW13i6f}rwttK1+?iMS4suKAJ9CaX,1w0i-2D_xWB_.1obS{FnH=hvw똮1t'w0TU.cAJP4F(0,`;8 .p ^X}rwHxf#l^CDLTD]k9m. !8j8U.v|똌L1"ч+Fa1"a7$"lD4HU|q1vݭH[/ِűçdg.|b9j ":VDdN"#"ۿ`|J)߅lVIIJIaq6glKs$ؒ\lB3\W/zcjbS} i=([JP LyAUgiʀf5Bn9tgޖe:tzΝy:+ջcÈx."24t߅t/N!"ňHI+"Rx۽XzE$+^gi" W..&1m ^_8AN"yKo-#Sp.p]+oϜjD~ẏGnHDLss+!n|~g=vq;Ȼ!WT:CDDo3`"5""5{Yg뼌^~g.$L5qLnaO8%$'D+q%z\MܵzтDZڸYx?]z|ဢrl ]5~(y4n&}N/. Df0|0Ui*O6Ј""3oFD -}w͈`"}nݫz;HCd}L܇IDP A;GtSD_oWT3y"2.X{i=C!o _<1"%F$O=DDvmHD~ ;͛9͛wC4L"F$s݃nې{ݻrfyi w缌Irp_|@}vG8=蟫ה 9(@ *P=&0`8n` >C8 @J m!L;*|WL4DN59C+ K߼;ÛXy{%qtg;%:当KNBDİ1"1"^)ِ"UrMNvz;UD*F$ԛw#nDݐ최n9s]f3W~DhI$^ȏ֝D7Hԇ\ 9 -cY:ﰜy&N#\EDxzcUtƜHM@DLu "t"&wʶtuр#,g~L"wqC1.g@D4wNX6"2(}mL""20kwgO=~TDt"26N_}h_S - D %@ Ђ`#DB /dG~R&S/|XPCjh+] 4" 4"mJ,ق07 1,wN ,gx׉ȷ$'bDv}c^{-gpsUqwk"AD?#n\v<;VUzRL"?޽ )e"""B~h$X`9ӵQ[UݞW3좇8SbmlLr²)ԓ/vXe(NdogZtYmG栲<'~?M]d@bP;Ӥ͔=#я<*9cXfa9如ҝDCDɤojbYLϗҷw!"x; 0.#"UtYkDW'|M)025h@ :Ѓ Q`3X -vp\?ұ߽H 3lAQOYjWk:SU ~Qb^- Zr@>qvט^'<4sk'n#bYe3_{,c]z~O1"rgVUgB㌱%;SXHqq+؄DwIOMնwe9S ocjޣwg>M=.} -w2;x$jgp}pƜYL"pwwJ wGDF&DsL"mл3!"w "k"_ADDD ""/ѓDՈkJAZЁ `HV7!sUgz^V*3bWUOEgzMdޝD>YȻ9w#J[c_Dn bDz'-٘[*DNywhmbD~/Fd^lκ⡙ 7ɻ; \ED>23|SL"f)"w1}yQ9+)VD"GHXuaC*".z;sa Y{~n9*Ǜ2mO1|2`/"ߡg"c>zI%gPXEM'Ns#iWg+T"m̾c=i򱀢;lT.gچJ*Rt3CTSӁ -TɫAEtj/m>r{*2޶wfoॻ&Q#LjE5@@ -P -Ԡ-@0B$D `+NpXHki##扬ȳbWUNd C#]ϔъ5ރ:.8xxkoOU/!VdFw+3bEkWV4~}O}u#U݈lݐ"Sә}X+}oky$22[?(2s~QY \u"QO`INMG]('5:MdcN,uNHMЈ""7og_@DFeH/<PTT.3YGޠvVDDJ ]wsW0ܲ\o8}GkZ]""%t^>Tpm N>Y)"@rPT hAz0"! -L` Xp osyhΪZUѪBePIqUUzg۴volkԩoo%p1 !c1&!8&,!;CńB(u{ 3 |Gg>AAqD#D^fzEQΆUzhFСf!_0 -յwDF2_(R6q@{ W?8Y2Y#JD̳!s3׳H߉{Xh̘fbHf̽w _EzaL蕙!L Y:a6|v[Y-4aiz,=yQd))";EWV"{6?(|Ov@ T@ 4@ t@ L , $+;pppx~AO!03GEG$ H07Fh$Mc%x*S8;DA5"S&Z; Hce8Z]-3X^1ŜݐKޯOTWȮ6H$DHu;u&[QVJZfPD{, ߌ掘-2S? !GB趪鋨dVn Dwk Nƹ /"!LӫVT2>GiKO"t|0@@@@@"p7`<>!@&"_3!r:F a:ް1z; -f!W q a4ԡ JfJXdhâEپ!i^꺣k&_DDBȗ$$!ybXzac[&v8j~H;:?5zF@3yeqԟF1jL7OI哝nɥ&g:ygl63e7^eaGSst@7o+ LuE.=T#=әʦ-DEo R\X&PB˹zD3J\]/HQyPf2DCC!2$2@=Sz64T< TA"t!3KH#+GP @"K'!Q =Z3 -*:`&`؀8  8<? ҲzZ ț 3JSDy!MV5am@ t$Qy~wAX-l{q, qy[!q_| Ŀ7Z5Nbj(D% ޗ7Z5:Dj%+YDtL"wnD>]҇$t^Y]HA$2k$_E=sׄz~Id=tMNaNkU;WvL3e򚢭L=4+x>U&G#UmFy;|9WW8fD_DZ~27 YzgYKn@wVu3''ؕH"5Cq?@"K+~i׻D߆DB"}&|0@@@@@"p7`<>!@KDFTah躙^b}a:h8>sZhYqw>N GߝB`sͪ$ʙjQ"\ܚDSOQIH )<[wo]W]oD||GQ"]uvE޷ \+gL,$rIee?I!$#H"_A"NnՌ씌LgjJVLIqf{0 ʳ9^6j󎾁⻅?2#HJr\U߉+9=TP_r0h2vjkiA{&$2ZK^F;$RH"=gD_D3CtH˓H#HU1kV*|0@@@@@"p7`<>!@="1ͽD9Vsa`HLtD6LaSwHcvolYz|tEU!Sg@ T@ 4@ t@ L , $+;pppx~ADE´pfLPUh.?>B#Ĕ6$2H%2$Z"qL b}Z`;pt$Q BggH%r MkGIܴ!#nL9ƈgh%HDD%$!~tH:$,ڙHJdgl< C"$^D9s[em$_old|VrꚔl'fp3Ox| -fHʎÙ?}5[taLqDٺjx9hM- '_Q\g"K5!ss}i+fv3jHNXmE.${`Z4D* tAHd&>CJjZz`F`f` X ؁ g"MуDjr&=ޕ~ vi^IJdQ(#R# <}9%K3r+IK1UIKD>/!}Ddtw{3M'b9(D9gWK4A"t$;&NDrf߷ O 0V 8 (g%m&l&{=h<9N'$%32aה3ɓgΪw r@PPvu5kڦqnY?iX*8GfW͖#B֎ ~`vgkVʸ52 )?e -Yp*YedGVwVu'@ T@ 4@ t@ L , $+;pppx~ADˇxJWk bb7NǛ,a y[l]fc.5g-\s!Z@gwVЈ8*YQOmYo$y{+WgE?rדGQL Oݼ&Ex]< Y8 pIcȃ<ӵ=G晄z+#䧿m3wB_LHO)t.3=,ْ23"=RV"Qοj>T/h_[q2O7:w1d{֮n.NE=!l_ ʘw@E3._^gFg7m3C7ݢI"G3D,f<)a:Y.iڣ؏Gq [!p-aOKg˘2WVD JV1|,$ﱸXeL'azʿݿI#\"E̋D&Abŝl8b%}nqkuoZ$gw"?_ij~~dǛ Tfxw:tQz2a=twrξ9>=N-R|I&_ؿiJ8q ,ۗj;T>Z:ї#.c.3u>2<w%TVU/6kMv`˰H8,2L֟c߄EۇSJjZz`F`f` X ؁ #CއLΪu#D_)f1 -| K-ґfi -(:QjoI -uIK kIpUy _D|s2WdDi%"%HHTuÐuGvH^9q(KD$$cCvsgVU{Q$/&B"/m$Ҝ駿B"'L鵧w{V%pgK (-vޕTc 4fbiLS 1!Kcs_):.d_*#EAADDDDD""2H0f&0|s<<®ɱ<ϽaaU?F{m۶w/tnkbJZ+ẽ .cRJbuZSxm~R~hnW/&ᗯM KWWvci}~=ֹ'.?YcX{˕hMN[D&Үk*~l'\ʓ~{ \HȚV}sizrݯxu:|ҧk3 $Xc6/auI"+\TDK3}[g-a0Gvat_\NH{d?mk ռr[+a$¶cJAJP4F(0,`;80x,>J>'yVOŜS9U4D94g}|.r[0gZfUOlC}u =A\9",)!:ηFm[ªRvn#UIRTʓONI!Re>'{j4iӉO/ ()\SHO/XSL{dsX%l-l=l#,nvmc¶öy¶yö¶a5;[U׮vKN~ٰZgN~YÓ_6ܰeo'ӈU;e◽1qj/ғ_v/h׾le?Q[-(avʼpA`}4etQCE~wM{z)[\Rʠω$'31LBl y4*beX5ՙrĤ$ЧW޲*ҹ(gF{QɛsOU%Gŏ4}._|EX9k\.g""孻*r*P>T$qTkixY~򄄩:v{,AEV^@EQbTx"R@rPT hAz0!"! -L`  ^ RJXjeN*S7M>]`u5 q!)$ keÜr8{5q q8qs n<Ԋ"XY̊XY|{K*ϔW$lFyfVI׮Z1"? -wGbD~""$"gFĎJ7v˳(xU95A^[GtDL XONYdM[?g"FB7s r%)#MHbD~""ibD%F!"-G$"7Hiȭ!F_ QĊBHx+nRbHEE^S\El@En榾wD~ꥬ}ee,{\Hl X] (&8V&5]qqI$i*88$T$kI"-GErJdGɳ;Oť~\_F|ڼ#~]!d :8ׄQ=P9;zi XDEˏk:}a`f{0OFaOQ "}US -@ -P -Ԡ-@0B8D@$D @ peH UZxyQ,@jWfh!7N HP!T͙xKo탼9W ޅSG{ŊHŊ'Vq" Q"nDlIE8("[GĈĈ4Ĉ4$"9rn,@DRMiGD -Q."S"Ng|m#"<"rNeMI҉NkBbbdb.L)LWEJW9x'"2u)U{d)Ϲ۩ϺHSUYT/te]yǩ~fk G o om{Lذ;ዢzqKhn(RwY#8j%OK\YTTm]MoVA7Гpd<  ' -d (AjЀt!" f \<@`u烙.Fsv*w*T-;=Hmmn>R߿кXD=/24wTeG+#5;鮝LNEHoSḤb=z__\)E=n,cKS%W(+OK' u!}\: NkJt22dkB&3t=֑D1w_d~Q"-E-c!<]^N"1rү*B4M{O_A? =Ʈr6~ zh)1jH/2UA)_C0LWCZ:^(AZЁ `pH6hpn|kDZI9"+EDE*K8M 歎 ĐMR+\S`Mc9[%g/=M\Ä!>U.dn bDrA#G6DDvoID>mEtXY1!.V1"CDd[ň[r5w#1cTYGH0.jɯ?Rr}GsKyuASWbRBg1IbRRh~zD2N\S+BD?H[٥t`ibħۧ[zx2M3\V54J ͹6v%, )eTv\b.yu"2DD_l +rߗ0yy7 !"U=歜~>73wtǔ25h@ :Ѓ Q`3Xvp .` X |xi pcfSLnDWgM9v,^F 8'\ 1EFΜ[Jy[/\Rm$t$ -qOxoF"";ĈHCDD'Fdi$" -vI亍"w}\ȟň|KKbDvYKF[2<{Q0%EL"R> #"2Sr)K]E03RD.'$&'X VtZ8nu&:S\3"wm!" pIu/_MW?SοW-3險|ڡn)~]bdl/WG =ǔ߈Idy~^/5"2tJȲyy-hmD5Gԋho"RX,DE/OL [ؗxG1ۉkc*yLq_ <"SbD""1"SbD""-ȯmKɛD]XΜ/F1" - 1"Vdl̬ГH2?b,'BD:P߻Mi "{{)kwW,uS[SH -d&$:V:)9$IIv".g -t`.&#ID("W5CoU4M_=&>nmDĐ[sYBD:^:hi)FDڞED ->ADr2 -&"Dm;9DYDuDd^"RrxdDH ""AD$ "DDH ""AD$ "DDH ""AD$ "DDH ""AD$ "DD"ՈZNͫxu7q"^W'oGDڄ S1oQ-E\=N<3w DM'Ĉ<+FD""mID.4"&[CL"bD!"$FB:DD$"Гȁ,gN匯mADP;;[0vG)"""u˙qD69uE56)՞mMLNJqqD&t8R}"gܖ)ۛ;BD_IJd~y˗iZ *^:k.M_fwuADdkhd{$[R(lbwD̻ܡ~#Da")"2&!Dd{%ssD$x53- mR@rPT hAz0!"! -L`  ^ .Sxl4^*JuQ%^HtW[ EW˯DD̕xs}ۻ<j \{]yJD2Ĉ'F$ FF1"a!"b#rlIDdߟa|V\y|"bDtnIDm+tDk"cYٻᶌTQPS/cyzQlw2Kŷ~x|N]XMLLp$8XpaՎ8cc]Lr);:1LܹZ8!k8W}${>UuimtO# 1e+GzD9ipw{L/""3DlD* Yy/DD+%L~اD{/0Lcyi}k-"R@rPT hAz0!"! -L`  ^ I' U@de(xevGV^H 3q@XΔD2xS o'\vG3+g;a!&4y!"#BDD%i۹,v7țE)#"bD$!""ߒk"ї"ݯ@D\Ic˙vaѣ uy=&}rc^U&0\l$2sjJMJM$a; hkjJtr2`=8c{uHD:UTP;S_>z̯(}ҧjZۧ;]3>ĐQ;K|Ddb˙ڿ -1ED'Q,g&&3H43% '$LV WGna9#U-EDN.gX `9r&L˙3,gX `9r&L˙3,gX `9r&L˙3,gX `9r&L˙3,gXV#2Kj#>"_Dͫljt~xcpw&SX1FGO -axaY6%D$ F[bD2ňID""%I, ;KDmwD#ъJwBDD%HsI2uObI_H#"Ϟ`(ۏIzC]r˙ڛ܅L ;"B'i'bMuS4!UENb]qɮd&޵-!"CI _!"'-_))[l4c>\_WA?Kc16(" c9T=""%5 a9#\X|)H=X4| ikEDzCDF^CDڅ mOES -@ -P -Ԡ-@0B8D@$D `47x >`QzYiK +䵬SpYN=izYm `sXD\aݬ61@52{ -$-&Vf+ň06s[f5E""ň0DDŧfĈ0DD."? zU5ȇwgD;10|8lyGy1K]9D$2Zٓ)).kX~ 1h:p$0N~>k^"Һ(E径K/3~USn,M3}̧m~ίH3Sƶ𬥖ŷ;FFDVFDFD$2$"I[=rHH~wBXM"|3/ǔ25h@ :Ѓ Q`3Xvp .` X |9NINV9a{=ԵfhnpbYD}"AKpQQI52x2wԈbbD~'N"9_ȫ$" $"?}#|jDQDZLlVkIdqӭ׆DFГ=H|&"^p}y Ub9_9ȕrwGy`*rMRDcRqѱVfh+H[55ٞL$O$ގO{T}I]T@_4s>~UIkz;MSu}pM!؂IdafQ]1V""fZDd"."Rq?&L"RfᩙrL"/ -DfG&|L)P 9(@ *P=&0l`/]'"Tb9bD!"bDцnK"R57(˙WG2GJSL"cmO$ -T9qDǦZ])D\ָ$&J'1qt\\ -sFDJxyu93I5g'\}/>j)rSս隦>m_7nW7#"J6|Z+-ȼ:=a93Ќ!"A|rLxvDd"D ɭCD*") -d (AjЀt!" f \<@J֖3Ӝ]!/+%!(AٜfhK9]C1. Vk }3e"tj&AD&3sˈ⻈l"2ZD1@ %@ Ђ`#CDB @ p6rT?+ pfNQJ-jU7pN{װa"L"5\֔ř 8KuQSc9flnwO}"F$"DDBܝ-HcfIݗ3O{ޕll{?E,(Xό&"G"/|.rE=~ꥬ7v"a{QL *96D`"<7Ccah5!:]E$&$^c%{G/I_]E ~.4M14=Wir%M1+_}QbcOKH^=AT~s )GEJCEz"/"+E>n L"JF :/Q/*) -d (AjЀt!" f \<@@(DM^˧bWī&xu|^WxC1 -XNL[o<=J\%Yͼx)bE. QXM3->In("/4"#ҿiD%x~c=sAzD+C.cg^"GA߆||N]8É#1]EhW5r013nϴ-^EHs -vQ9 -5׿">Un>uftU2F֘S͆e!"wyc{L3/ "M(R -"{"'"6D70Lw!"ybN5{)Y>(AZЁ `pH6hpn|'xi 򈬘ۉbS!zujھ -!"i-sĶ< GdwoErCD$VO1;*=#"ADZ#-a{!aHEA!" F -:D\ZD1@ %@ Ђ`#CDB @ pHڕq('k#Ջ"SD5LfӮpN^V1keu3uLeb9WVXWxO.V) q{TȅbD!"{1" -oIDg(r,gBmY/lE-Ylj"U5c)>,!"@D~.Kr\LHahapA[q8g,qQᬺЛ2IJUTi^6=VS(?R&q&#zE/NC[ۍyom/qF4#W;Q=s*ҡHX&/>k{.18CW ]ژFSf[.ckHëGv3IAJP4F(0,`;80x,X}nj;ը)ەU -vvmv]v}vCvv -`ouknDUн]ۙ޺caI6;n2fcŐ(Ǝ?6~ -v,/&cYLx7{jﶅHFOmg$!QN먖KՏQ$_y"N(eX\~PK8i5&&ځKӚXSqtbRR zՌ -s$.}YM~ʣi̇}ʉɪt`q֧ͽӯ|?yxc1 T1w sG排aQ~\X9ȹ -x&ww&{>*=qo֜oo;e;WĹCbT;ĹCbtK|oBDďcոm -q"28CEvSZG{^G|/,ž4I(?\HLL*LtXccJ- cMvD8WR#&6DD.D$I"*Dd~|2']h)tubfimoY~A_>"2_+fk!"K -r, '2w '2 ""U=H-nx).@D -3Hݑs."2%//We*'‰x "e0NDŽ iM<o)m8]ϻJ ͹xo%vC.%ei˙oHG̷CD$eK"Aévýe!s bDtjD`بK#"h2[hX৘D^Gyz W "ןS{˜LL,H.JG٭qƚv%9\vG,91(DdpY")E| Y>ek_5q_wWf٧/Cr&p5?g1yW$/WGU=4ӎ&\FDa0WחHAaoYw7"5#НFD*lP=yCS -@ -P -Ԡ-@0B8D@$D `47x >`,НT/+幼WQmu}D? / Ȉ*b7[Zmswt`0S{˙˙kĈ\""F!"rDSfH&D;O!" FD齘1"{1z]ͳv>CD>1O -ED>yzKQ'?Rܯ$>9$$'$VGrpCיbMs$cwR3;{i_J+_\E ~=ޑxΧtMKDV)1Qb(fG/wG?%"2)D"2t""MBD0ٙ܃s$"Rqx/CD$ ~m-"R@rPT hAz0!"! -L`  ^ dj-"DZ/E<^$ENz>z٨0ryKo" XN Yg!33?#r/D%gyhwIDi-DD#bݷĈCDd|Kڏ 9>ܩ&W)a؆Id!ԅמKqŤ$ 59&%Hl5.Ξ`u9:EcxKUs Ki߮Lp ˙rrԯ0]]z8]O;^KŘDzab90Lۻz:#"r&y˙i&np -Dd8&ZDd h-"R@rPT hAz0!"! -L`  ^ ym9KDdW[xu?Ѵ%"Om &b!Nbim~X$t' Dopٵ6;+FM nIDrzv~bDbD7}hq BL"8"!T1&_ނSwrkD8 *j3L"Oxz)m7a9U"R],g*ΩSRRb⒬“)kBJJ5ϸppojDFHztyX"̧įϯ kz |څg:>D$Y,gK>Y& i})Z=Hpw: 3s~-g|C>(AZЁ `pH6hpn|YXZ6Iy5QdY^ǫ8M6[&@ e!,gv';VTm\}IOĻ MijDN;%UĈ8BD;bD(1"[7N-{fN{?-ģ#-GM7~ܱ("<*cnDEl_N"O"(=O1Wc=Ts-U M&1Ęp~msЉhA83F[=q@Xܾj_Hh)s?OW7yԧ<5>k?=DZN2[FEzvGe1>~̷"Cqw`=(<*Lnp3"GSHKHGZE1@ %@ Ђ`#CDB @ pKKBd <X敝 ^]kZ63^7E>^^4F"6ctu{/lP=U"ybEbŊlt=+Vdz7̆3*[e!3KbDfGM3~vC]Y!=u O_ȥva 4Fx""M3,i.#&ٚĢMH2 :љHI>8̽Wzf.=|.ZJS>SU궏4/y~rAJ9k`_(26/"/cjADyFxEp{p{]p0"Fzo#"%HKZD1@ %@ Ђ`#CDB @ p~"WDTa=S0Qg N]=_"zX "Eb]&VQd0qL+=l$vnL*FMwI$"gY;KD /ň\.N"燈>1"9?DDmͫn }QF]էvʜپeD  -"~S/e#"/$ԅ]֙tRt 8\8k+Iqź))3)x`o`=!"DrC~GiG}ʩ&jzi:Mrا-ܧ8dWMtH ُƜ'<8ĺBId 1征|-<$28&"RӊIdiCD: c{f3?W -@ -P -Ԡ-@0B8D@$D `47x >`QlOsEiNȩWN6N_H cADG9S/"{%q qtq宾.x9o̡WVᦏXňP6DD[sonب"/ͯ~"7ī"bE> -}A\ό(wTygL.Fa,WD=(BEj~~..fbs;{\j=Ιlu&a)C'8#N3:ĞQ̽U=7y$`.jv )gꆣ~M ~3eHžUa)ȿ:=Q*E3KO")D`F%"/| agjHٓE1V -gY>(AZЁ `pH6hpn|\*2KN6M}bW.8Q/*5! kYb"rb'yXޕ3)$ގ*" Qݛr"ŊzuwT$%YF7HOwʻ?pwbS&ňɜ)2`<6ʺ}Œw2KGܔUxz)[!+CD*868N^dc0$cbьp7!%5:sN\Q$p.*_8~|$?]O9_zt >{~]IAۆaG@&F:c*oFDr'%Ddq '3gfc(~ʗHO".uES -@ -P -Ԡ-@0B8D@$D `47x >`!Y'/]Z&hGakWߛ)Y] (2Y-omU<=8f y06<{^vEE~5oJמ%"QDn: a1"E\#[dwfD^Q/|="AD䯇ꢯ|SDq&S7yT&6%aebcmku&'Ѯ+9qzfY_e#Uʏe:yQkr^ -֩uWwh麞#VC s\^~ށGv||ATz-扒KSV|(>\<|%[O8A`=#ox_SW]Ew.^f/ -d (AjЀt!" f \<@@(J钒 -d;U;SJeՂRݰC3CۼCү( m;;P¹e;LM;̍;,;lvw8w;\; wp)xB,\,Ɠݦg_mz߸a!O;O/O}]F۷^])#Y,6q O!.cA,ƟB]~MNbTobHQ_oS@1(mŨ&;HQ~u#;,I|OQ,u:B]x$3q8]b.kmu%cq:9#?coIQ"߾ -'N{} -le2Muħ*6zYb{5o -QWLwS8GxL@+l {[xTƎv=#?x.<*StcǨpGwD2m!R@rPT hAz0!"! -L`  ^ $m[H8^;Jy`bWs&^=M4±e#D*y0v[}I&oʊ7*VGyW&dƻx1[vTm!qq<(F$Z;.d쐜^|?V~#U;~"F!Ǝ-;foj;eȰ>vS$"ryM}/^J#".ݡs&II^=qťXcKr0q g<*~ޮV/%/&˨wFjگj>W5 >m-. "eQ6|).T_\tL8q(O8qh)!"!"'TsH0\ e.kED-t7!"Y%\ZD1@ %@ Ђ`#CDB @ p%"gDYKTmzts£2/'N!" %ΔE2Ll}stڥs/q^ΛMADňP!""#2/F -ٖD$7kmP97]Ĉ7]8ފCnMj9T "!"#DS/u)D=,\E}PƟԞ[LJ$%:I+pZhkt\=Ť0gUfeGV_1 ]Et|.o\ISt=SV7U~\ȧ-y+1j^ʋX` Z"" 'vG1""u/""ňF {c+"Q[= -"ҹ|BT=@D*{;HkZD1@ %@ Ђ`#CDB @ poZ9y# !&:d]tÜ~5rز]!T LJ,sΞ9Ysf -w iἙN"wxT/bD~տiWnPݚ~[ߋn""?u9gN"^=%_yWSQ֟;uIc. ˙Y(uyIS/e{i˙],g&s*"1tj+)jw8tB -mMt8v&E'S3vfLAdf -&tRebb*trEfΧ/놫 H]%k,<̆. "ED$Q%15 })kBD&WvֲCaz;R"RU-DC") -d (AjЀt!" f \<@dD9yy#Q^V -p2"<*Ix݈Eb -p\bdmsfYggnVi\ F@!"bߒ$,T7^Ό *#C7~7DDdk?`9Ox]ǥD҅To?r&Q}DWe\NJr%Y1q++NbƕbwgAT|M{#_J=_E;)/z7MOY_5_=6??aw""˥qa6<)J(㯻cy@DF'DD$"}%VCʳS®OcJAJP4F(0,`;80x,X)ZX=@W״m~.aYd~bn- -͌ۉg{xVS#rb#rڭζ?5}Puڭň|""? -=bDň(DDlɓ\˱Hlr D2TϹ)ozbzGX\"r{ù8&9f\V:&!ъ#)ggJllr իZBDId$]qbOY_U^4M]O[Zו7GYc[9WwGu1>T ""->DDV>@D3!O!"3op""5H{"2 "*bD^#z3yI!p[j_l&?Y_'s ˙gyL"~,\ܫj慇vgOsV c&dB!B̉$ ;LnlםUϜuν~j3ufhK)"C)EJkE"R b0d ZD- jRg-fZ}}^nK1:y!r7D# 7?N/6Зv';H"u7|)AZЁ `! ,` .p ~@x +G1 +doo=ZC{"Л2t4}>S9?Jo>=BdSH!…;L90%@ Ђ`# `+NpX>C!D/2D/*gEU>(jDmY!Xx(|HmO&J6OhHHLJ DZDO?G2{H vJ!B#V J!غh([ =z56VZ!=Q RGd&I%}kN$Bd+ DCtJsǍO̝gy뺺%{3YrljCePp #:zE{vPPq˦鴩_7KbilTeO#&skmׂeq){mś]yU.\ ۵o$_2Ks,PտK<:V,#ϗV;5h@ :Ѓ`3C$`;8n`OepS`V )Tm&Mq;N/2 -qqE£(Y[L8ʋsűq8n3+q۾.bxpQOeov*#[}(ݴ2!1I#$}2ZOeNƖ]G;y}#<~XQ|4G; -7ѪH`L6dHqff;SY֝¹2 gw,O ): -z.c&^ -){UT4?Җ=|-:2Tw}-e:""P=2o޲{i21P [vKO~,*#M@ʰ tYlFr-;|PT hAz0L`xHDl`' ,x!A '̴MD5ƫۉ&LSDOKN sBB*2X -1^G T-KcOπ t3FG/_W -{e:U/]WSumCbXWER.uZ鐦Őv\]agP?ad,ysK||a}czKy&`B.cN D%XT`36},cJԐ?=k_/C|Hv<D Tff90%@ Ђ`# `+NpX>C)D> 2=(EUn%D#F&D441)] V ڷ[v"T_nw!<"WpdԽKR%!&!,9lLΪL}SEEJ!wR"$DxMft/"vJ3W9&)*?w~~,c{ȟ!Dry.LJ/C ?\9.Gv7%6e]2xi//2M!-cAu'!M!][wPQGqPTț__I/EԿtlV"Bd]FHw(VHx>+MfStjHBHBdgʁ(AjЀtfH Xvp 8Oi#DYBT=HEA7(諉E0L,S:Iu"DqMtODYp2ȷ.H=c9:B$V秿"D[3*?B$+ugTכqYtbrR#LJIMfS29c3Ce Lm2RV䪪 =/ i|Ew7/cYo29Jy|;Jf)Ybxb|!ʹ$m2wB%#tzH;i\ |PT hAz0L`xHDl`' ,x!A\xW@C]:%.7:X01,&bͭSu6kh;A YH\=E)K*E \"5R H"K!":)DF/ӭIpYPx 6VY -1?Il`g7C<B/.gDgBّٰd?AS (@ *P=&0C<$@"X -6\< @V6Vy2Q9A5- PMtUD?/JDch!uAX~w6.?/GNA,ij ]BB$%䡧$D~FRdpdKgov{ᛣͻ۷ENR'W.^.(]orm:cYu95UH}"5u{2l&vdܜ#;pegzS2j(K ]P^;)*:oU~v%V_٭ݪX37;ESIafWlƟ$,<&}%xkb;y=;|uGeo9~/S=hz(F&e[T&90%@ Ђ`# `+NpX>C)uXf1V1,3bݱXmAk5ĚͳX:|9-ñX`){ o&?])BaܾC kk:C!GgbnjfGnvE(EƑ]1ōm|Qxq -ɋGZ-"9 2zdq8{<ݛQxi*2uſv7ZzcCzt -?{6+2=%(S (@ *P=&0C<$@"X -6\< @VV/]D^E"QQG-D5' EOvb#nN̕t5Lc&,qׂ^I•B^&ȥvJ')(VD75B$I V_8+BWB$MZ|qw}4i}pݮORdlv!TGo D:~%ރc7M[]aכ8r2aL2d232n S (@ *P=&0C<$@"X -6\< @Vۻ(ϔń%DB4'm0,O򦣂H k{?q6i3s3oL@j!.B̥{vwȢ^~&!]w޲]-$z1EDJ!Bٺ&!BWؾq/9&{"9?z )|(Ȓ0曮˺lNrpY`.΁Mpg']"s}-"geR}{ʓonQu<T7<Ҝl~e.*hx!27'i%2y!2FX&nH!\i/HtW [Cߵ;i+!Ҳ!RHgMwƐs+r`@JP4F0!V7|y&Q9KSMu}~X4z4,O!2%ZE[qm 9M\3}NdkE`$ZE(L+B3!Di -Bځ\A}D0o2~tLf*y76!D:ۘ7ﱜuE_-gʁ(AjЀtfH Xvp 8,&tꂠXaL%A[At~7c`:q˽Hlł}8k8 3)po^7G*HLJ$5ۤI:5 Xw{vQTK!aO+H" DkR(oԁQ =p1Du!#DΞC;^)*o!x!rIMOIrg;2S<66gf;wzzR!R'm!_#3tOdn WճE[T7? jǷFa[b9o^,g&fYhڻZzR}{"eжiۏ97LӇ"t~:Խ! -m ه<"L90%@ Ђ`# `+NpX>Cͻ8qpDCPլ>!h -qADL3'!2Jfsk'8spCKH˙mޒ!H]fRI!""k"Ե:{h uF -}J$W -gGDrפy[ y~b{"ѯ8F"B%[zYw!253{Aȁ_WHr;'+9JM8XwN&};qΦ`zc!osL^ )( )sUս=Xl in|-CȰXm2A%,o}!v!⽖H>XDlDEѶ 24Lx!DCŗ!*"+!ϔ -P -Ԡ-@0   p? <9HSNm2LBޗB!^ -kuGxNNJw/.{*=1ߜXlれ&VȩpgK (-v̽ XO0eY4Y1&1pfxgOqn?bDĈ"Ҋ+"cH+"bEDd )""""VZ/-sꕋD{g/ߟ|gF>rC;]sW5i^s#-3+7KfNGFԋ~yDYGWet -gt ƻ\.]z?(*;SVV˝JOJv|<)a{rWbRr<txxi4p% 8C\}m.c+^wEѵwmʇ)Me[gCHeH[ԟ= 7-#OLy>rx`tݲm2[סe~shpO{Xg4=6b2f]ޙ'}wEoU?Ͷ\4_| Qx8<P@@@@b@,V`v.4xAQfyyOuvVëx]Or%ޘÛf9 g)`cZ9k1ok5syb=96q(>*j ky^K:OLNRid'_5|?\xN^WgFI|e_YW1XͶg [t=:劎rGg(&:勎GG({ƾ]+Z&;]Z'N~|wVk_ӊӉӋ3hlwwՉU'~7#_Z_^+'k56|W4E]sS_t^Ϊl`y)u>O9aP*X*pOOOĿ3B";!NNHs'&tR0`<'9әTTXE"߿D>lGuo@]/z-[{,pE@趐fq)i5>Z !G5lD_D:D -_D ꢧoC"m71vyD:j!h&P$$R$ұDr@PP 00  X ؁>GGyRPs󚰠-t7pÛDxHdZQ>9W+.M+yOTVw7|^"DDEQB";E"J(!/ȕkJda/JQ"ڼ*M?<]"?%9-mO 9.Lh -9P}/u wrdX1y$O̸ H".HAOb0519>)5uD )YJC"f+sfUOԕ!MlmI}HT@_\Ȱ7Sf԰*֒C"}oo{|YDzW($2ZD>$N3t $ $,DzNH)P%P5-=0#03 ؀8  <^~,8'ZA/(8<.4Ղv- a7vf\C"D"%)B6!g4 - @ -uo>'" V%% JdsZs["7lD|m=dԉ(߉IB"WEL"& \%!ߧ)z!|Dηw:)ۻ\a;$D+ G![ |]$)HdV$KxjT%&Nd8WR&xI Ɏ$&> -%3OH]; -{m%|*^FMF7Tȣ!͡CSYa>Kf5/˨>$⫕#٪{ևCWku@93dLn\xkY:$F-Gqyۭ=%H>YY<$2։$2ag4=HGoɘ![!z$=h0Dr@PP 00  X ؁>GaU'(9e+*9fӆy]` xc3gͅD"9Hq%Rpqt91-;mM3P$JD.!LQ"D-!?oDFӞ*+>/߭%C('JD&J-Q"F yKQB""&o\ٙ$bAyU"EDZ [gD P[,uC)3I"tOzzzБH'9x$'3v''%&DZQו$ҁr.w+5=Rz(Bɫ itH}Pk2DziBi$IdȶnHd~){HIdI$u$H"|:o$2:T#B"H"8!<P@@@@b@,V`v.4xAQ*xyX9E \Użzr>^7syCgiH8<l ˭3>.HD6Iɠ݉ITHbx70II6tKo*H.;RsdC!mٺa~eYsU kCR $2xqClDZG!Ht)[DZD&!Hdd/fDf{!B" '$ǔ -(hX@ q -l܀/?[.9D$$WD+&D!/ -z-tN%$%J+IDD%7D"֐N֖lIYw!1:IGS/A"K?O$ &Ltrj#5Ir'''''1S%RQcyE"^F}DQ?VZRU#>,H[Pm2=Ě#{YK'$| -ͻG_PjW VA[aJ0 @" D"U^\m0<<^@ZegI^̷DۋֆH)"d%O"?IHu7t[țn,d9I$2eW%b H H#X9$YvB"xL9( -h ĀX \ h `v<+YQNU˩9M -uݼ7,p>ϙg^L)gyk=g[퍼u <3yy_/^K"Q%(!dQ"b9(! ȦE7tWO$E#!ߊ]9$Gg$5hH0$rG=$A${hH}]37Kf)V$gֆ+#93H;ғӑD 0Lw*vDvt<($-f+٪4!miWuo@xPo2)iI$< $vHdpz Id&` ߇$R$~: @?^?Dr@PP 00  X ؁>G<5+8zׄ9a0;x(kn#)&MU8k gm]y8fI2$rDn%IHQ"?NB"lD Z+,ͬ|G$D,JD6KHDCf φ$ ?wR"eOq?C$r> -\Gyy[f۽gT{05-=9OO8t*d:IN -LxG+I#h+51Ծ{;K5?}jMʉ,]ѣ}LȰtd,{Ff*}5W=H"A"壜iGSD!|{)-ܳ;<$_ H^-$tLxP@@@@b@,V`v.4xA+]n92lU ӴbN9$}RzLpLαn?}N"Kl!J$y]ċI^W""k/֒Ȁ&%%r-#J6baUjK4D΅DDluWD LOكU>r%C"IQ,uɨ<<{&I$ݓLH =f  sj#33R⃉)Qw"c2ycVm!e'Y꣯45O#d -7C&cߐD#Tֱ{H9 r$ݙ7!}D!0ʙ -z3H"e@"Hfixo%M'Vr@PP 00  X ؁>GiHP6EY+IV2U=3͂T&\:ȓ5ay\pͭ,NA$)kH$JB"_ZL(GwR&vC$~גH_w-Y(?yR_?I/^.?.̋]*H䵳*c )HF93S隆 ) 9k:$ -&2ۑI'9 ۑHv%$%&ŧ&] ܸcռ2y(gF!f+  ޼Z/k5u q9$҂rfU3slɶq$W˙yrܮ+!gvj0~ٷY$*HH>!<P@@@@b@,V`v.4xAQf8yG$ -^ϩ9MSupbp3y$>R4s:ۛxgJjGo:+8[+JD)!Q"D6IH$eC$rۧI$f5ܟw#Q%rDYq9"J, bCV~)GDq |7IDFΆD*|]HDBTC$gә$$'!'+#͑Hzh&9!!ݙw,uE"d3ơ,Uuff$[;]7N / L[H抷[!=Uw|!UU-(FaW3vh&ڪ~٬oaDe.)l.7[ƺ#}BBٖ.V7i,pv~pUp]U"<,f QZ-]$Y, u8y/P%P5-=0#03 ؀8  <^~,y.u 5kQoQݢܢnh,ڲ-fbl1VZLGs,ȣlpbOwi0y[G-F?߲ɻ$NŮ;C<'qz;֒էt]'eQoJl|O_Dy)- ٖ)n=tͯ  dڷ@@鬂<ރ= \.;)cߟ$y33I4A'd;4343ғN;4sӎOWd^FUmVe*姮 iB~5$WQa͹)D%+<ց+s; 2:ꘂ{P̿:fd!1H Cϣ# "dEu'n!P%P5-=0#03 ؀8  <^~,yNS"W2N#hxm%w~34Qř{Ɋj3iUłms͹8zL/ONyZ2V+D8$$E랼۲!IG}bHZ]>{-cE[$nH[$e9)[uKH?ꂋD^Q+tH Pq*K]y(c:3jE5->l:$2#+MHe$DH C"aH$ !0$DH C"aH$ !0$DH C"aH$ !0$DH C"aH$ !0$DȉҕHbBP.A]kymkUI0N !a rtډwID&vә I3ƻWVT+DJnJ f+GTu`H{<[;znɀ퐡I5VVH$׷ŵnqrhi'Ilˌ ty-Oʘwz }!2` y38yOr*K :ΨԤxGrFBɫ RUI2[UPjMYEH|р͐adE,MHi$2z9\Ü;'sXfΰ&?ĮQ%ʙoDE|CW$$R!DzHpfl'!즑D&$sL$p2$r3j.b0!--)H%9;Hs1#=13t)Q܇2y;QC"ʁ׳TE!M-[;ؕ;ЇBMƑ!Y"$r8ԓUoNtA" d*W4A"/C"=f{'eLu޼!)Hd TC"Dǔ -(hX@ q -l܀/?`%qN2wUuLӗp޸șxs>#w;xkʡ9ʺzyw9S1fD,1H$M$rDD>%rD"/EDּ^fV^ gI4J"WIzI I"%7K3@"7Ȅf 4c GȘq^ђl\$R->@B0 -n4 +H֕)%V*gm=ÓwU -/%{(emE\,[o׼!kIdקAbgImH$%!BwtIG93T;!fHgw r맜? Pt;IDqF]tIǧ;Itڑʤ%;WRZ0ɝF61gTD?##QB"K/g+RM«5s ,]wq@;2i2!s+_XK3L2l[<$ZDvgȅ3yCn>|;I"SHc7$2Nok @.DD*V>!<P@@@@b@,V`v.4xAo.$?@,uf=Om>vDTGRj&ӘdG'&0 tH8U";FFD!2F\R.|ji>ZtsH{ ?[7d@d$r4[%}"^Wpvk>9;3JH9>$$sC"s]$#Dr_DDȅ3:!<P@@@@b@,V`v.4xA?QTn*a3rSqÜfն^?8c!gZ\K$OVg9a 坭sgZS2Ew5rD?ucPr|1LHn<tԭ3o+F gz^E -e%R̐(!-OEaXQbrݡ3r@PP 00  X ؁>DYT^RbU6},F^Âa5Ns}Xd,qy6;9j`-<=yLJY`=E- pE- pbmE5O=]O4̜ #9;!g~Nt:3 a+k!{DkE2͐*@yY /m(?Ȭ4yGE;UU=_U=y|MCkG<_xQ?[j,s˟cژEo}c{g\opOhOtG>G^j5*ʪ͚ͪںͺ͆Ɗͦa# k6Z M}F{fu`H=Ffm5B (Z([oEkeSXd$vCE([Kt2CxQbdDm( ِ&UNd|jɮNRK|]T0wGoTŦi>rF&z2ntydOÓꊏOJHK?N`Ɏ' #|L&ϙj<1_R=j~$nZS|<[[[N &ck!r?knZ툎T{[Iki GX*9`Ћd{M_NdG=^AX>1GxT0xL9( -h ĀX \ h `Vn<Γ+3x5-<9.SoMdQzH\4 qn=3{x,]h{\]k]Kݘ!珲,w{OKd? ?k|5:Ź.ʮ. #O3TC"}]?uQ1/ﱔӟ@"/fQwRiA#-tЙdxDO|2-qp?oӽc*VV4Ҕz1KW@@2TWeXsnSPy\ۭ䶻/C"E( -bHܭ%=$2{/DɆL]gN^SռbWs6Nݵr=Dk2H%o\L㜹ꎑeK R޵ĺ9TpL5Hk)gnȭDQ"F DQB"YKͧ`Cf9Dݺs/{WL"/ Ȗ=ҧw^00߯E ,>.gKz%@"ovFugf'9ҙ Lv1IxOZb;1>3we_ye2lVjlȴ )UOԑ4 e!m,]t_Ȱ&cMOLKiY<8L|-%:\ LvC"50$@vunD:ɮoɘѷwz[DDȕdWwn~ 1 -*:`&`bA'p70 |v/A"9dMdpB,kd=lM=|-'f21 -*:`&`bA'p70 |< R˧x*+9Z^Sk8]o q3rts*H93Ż[yD82hpnEU]7(D|w2 H_^kW9KO]-jE~/Z}Ց "wgIt廎":`c"xRWH7>3?-e,XE/8,Jt'$g8 d{R '->=)51):}|GÑabj3rRl@*?ο/֖ߑ -[KBzq4ꙙF\w"SeHcznV -"l3,2(Ȼ<~i-=L&CWt ~(RAD; -*:`&`bA'p70 |fkktݝS!C&gd<? /D&^W 4 ,N"OC"e#9HH%H`w) /B"P$DT'S(JjZz`F`f`1 +;ppx XIB pRdBdEU h]P6ڤ:"(Ǚ"b;Js\tWoioBjGw<޲L --+˶%D+/Pdn"=ܬXf] -LI!.Z&DҶMnV.GD7>4Żw/T"+Aф̡PB$3LJu%#:i4Γx'#!їv ]*LCYIuxHױK'Y7t'tX:c]eʡs[.2zmtZk[m5׻D>[PwU]_f_&fya22S*P Ђ`# B4Ā 'x!$neb9lv)][qnh6[2LpxhᥲF ֲpxi+x4>I_F/GѠ C!D#?t!yNQ}QBE 41.Of5'+o CkK!&DlRM [vc8{pS&zO}o!p ^o1I!<r"WzTw}yr7 K! J!-^&D~-MUDn7*37;pxѩPT_̎k!2|ݦjvYVRB\Ff+)33卋p>#ԴČzʗvMT#offeH͙ut6;Jp|B\cCT[׫0U ڷ"n7m )}B,v#DU!Dj#DjhED"F"%'"Gh꾳v) hAz0L` X!bvp @kv20}"}fWhu D_A u:)biT;UaxVEouƵo1b02T"%RXdBe-2!"n{[<}^)B/Y&DReBdi[&|Mvܡq01I ߲JR~GBddz 㜿3T_TeYi| -]xJJ\q\:J)Y5}r-L]6;.{1m}:Tj^э>/ Z6&D%2R%X*k{#rG>ӶXboeH%B@|ZST_B4!S2"i!D -C?S @ ,h@ :Ѓ`3X -6<!i "3BTD!l ]!}a]wXH qy(ޮ-{qL6#tsm#eBe. 8/^ HԠ ǥyL -L<-!e)rL{" JV -˄2E;r6qb;P\S"8_ п8gBf7E]\w"4ޕŇ+)!>gyv54Gr8Μ829k\6Z^=0ϕ -2B86bGܟDt>)"o!D:"?qf]Q_#D"CoEֿg* hAz0L` X!bvp @, bIU'C쀠)Dd'n2u -aR'+#"8E-)./@,3LQT|A&Dv|Lܖ抭(/),E~!o9F)E6m5Fmcd%wϹrAoLj=73o$ҭ+WgF1_ -az:̴D\+yT/+{^[HSk)2E޼.uf\m8 e s5:RdVZN@ˎU?b RdƐ"Et#mP5JW熐"(E -q M=F)9;?S @ ,h@ :Ѓ`3X -6<!WQ)&9%쌨#!QwDC 1v.b^--4EyL9YC=]{B[V&Эbp蓥Hj"Wor)Er?ے"y)yhS߶Otme{ew˧ȍH#E7&D 'R:?Rŏ\ZQܲCf[jf<qy3I.obR+͓JHriq\ym݊/) T E:s8t#EVѵ֏ rEMG" z:Y@沢)}HqhRe)RKN7"EV鋻 -=aHe-'HI?RdwHm -=&0 1`;8 n^~@B 1Dk/UuX}aHX/Y7H#Ɇ0='hfWcJpݵ\kSJɾU Ápl(dVk!l7[mKܴxTm:@d`a c>#V&DB"Zs[J'{7Eĩ6"/̗~ЅKc7]}+&a]J_zFёɹqԸ WF|})%"cwk\{+P?z 3v++f+=!t6/?WW *^51*F?sT>i-KhD^sT"t4-Ew"cQW B"t-D]=Bdٳ V*P Ђ`# B4Ā 'xGR kB k%D]D#QL̖SRr -Q궄HԷǨyq\NCdvVd<;5*}J$Nm 3pA%򕁋j{&=.^#IDW䒸D.3a̿=^cQ,-SWsK9lMWH3z4Wۿ?Wwx%[2t6˺̦coK ։YCЩ{];Bdr -!MgFqcKgi{`oyHG{fZ6a:@Bda̿حT4F0,`hNpxC=HMeQ]NZQ;'D}h\Dև1݊'rm'"zgDΈN10'Mƨ*eƨ%HLK -(QoKӫ.t+r"箹0y_f)Eޗ7|gʯ{+Rkt у\#Egۍi}.@d\1 ~kHGb. Oj\BE%R3] I\V7->Rd|оw+3na"ev虐\9wU(Ek{lZC)2$Xz'X^Hq"fN[OC%H*my)2[Q?!E"GTN)G+Dg"u??jޭN!E*N~PXЀtf!l`8 x >B @J 3ya QχY k 7 Jq#r+S߈HFHZ.}9 "7lRܰe?nKp6 5qNy&GefVvg1}.sq3ίot#D!DyP v D~WE)`m v -pIBjOMwIIt >>ɗu@ĊD>>h~PVxnUS؂l+>]>}fH45 1wZ -bEtƔVn};'qNQ+C<>u\ᛟ(Pc(<w)羞J`@j`AZЁ `V7p/? !ZJ:Ī:cգ6dԮ-mXcI ּkE|t">Qcr{8ۻ̗no586$ L:\Vŵ“JMB|) D>x(k%(73Mt_wBd6z0)v *o&5.5M= -sA`9$XPq{񝶾{1>dAQׅQ -y|{tB r&L-':d'Y ZHuX6$RH̑됒dG^y7LgS>Be>6mn)DNo9miWLy@ӌ~7y:qn)/?EWI?Ōyx~d>lF 2sE"qi ^>gx+)-1 OVbV|ǟwr8owQuHG - }4{8 -i -Zr/"3Cs5̦qi,5$Muϟi~8>[H BVF釙HiiV@ ?GCU:ywD,U~PXЀtf!l`8 x >B ?v&+GL}~B{K5 FƱZ0=T}!2OG]عB5a(i {G6 [H`(,ݟy~ϻ_ܞvw.@!r~N#"V)D2!])DB*"vB-_B%pFl!r<&!r п"D9bBo -M_0ץ]T4.^zP4Wj|B%$gff&q罾=: *´WP5稗_fBr rtDz\cCԵڣ07 6*GQ-=b!R3ޥ! -,ճa m?эL4" 2A{V"EaGqNw{#2!yb`7D>'ȽRFmF&D.Bd߇cɄȥ"߹ B,ݴGwɌvݲ6۶)N>EZQ9v>q)(E)2kF){4g{Q<̭pgK (-v̽ Xcչ=.M4iLcL3H1$ 4Ƹt<_c?Ns޿S>k qZDDDD12ADDDD)""a{095-Nuz_gg=KCEo:lҥ]N])险>Ꝼiɻ/]jzJտuƻ;3~we.Pj.箌Xŗn R+~cW2>rĹbwX6νOۑn${ҝl\|3NtƧ{)47Jkwk3rw,VeB5dSvEDHz5KSHP;Mfs>4 ?4.L=sp²\[gx[]q2;Qmo>ZÞp"C -GCuvp3i%΋J' -':{T۔`mJ~甤rK@%+fwr^]qF~.?]grj}ׯ_ǿ?m9ǿL1eju+kM_;]wUQ]vv-c͜7aYy3cg1iqwD{=>?%MHٿHd+$9CiKD.Db!m|O O:y&p9Ot:I$5-.=d,L8 ) lB\b‰ingOKQHd,uדYB,}{꠱♐itS -K~ڱ-CYDGޱ^ $R2$2_ ̾C"3eH"g CH"% |H"+44A"e#d^S@@"AvX? UzDH$)bNT*Q;&㢡"Xd*_ mI1scNpV9΋q}IIfsEF"J$D܊5@QF"/mD$*D^^2r^lZȿdH$%}JZL痕Puqv_$_ǃIkI.Hvo -QOfqg|0>:I$MMd'%9a=Δ.Ι'6!>>%%9D/] Bz%L0$R^,%nt~|$P4N>2~zy|[aN $iH${[vاÐȞ^HdHd!Hd)*DVF!Dw{ 2H H-D*I jZz`F`f`V` -D;n,@*IiTIjƓn 8$ -yX -T"UT"cĞ-GOWt%JNU1P9I9CH$%2DDD|gS$5'J$Id]Yu2 #%#R9cI"!I"Gr&DBDvo/g~$$jD~iDnH> 0 >$+(gr{N'x38gr|"H\,LvC"i,缱'J"{g$RB5reHd,uio(g -i(h/j?TXfH"eS}wD!.DC"W! !Dr˧iB,yY<& -0 h X D( 8  8~A諬ըȴ EI!Q3EDLa0A#eXDR {]kBp *m&$n' THD!#kkD)IdszsS"KD,2/[!E($YdR&NȤdI(M"I"?B9 -yEj"Z8{WEny!,r0\7\*D=3s:Y$=>##!ÛOOf'#љ&:iiDÝhS[T -/ar`ײEo tCYL@}иb4؁zfD,B]Yz}{t9,!XdXdE߆E+aYXd6Xdݾ`:j>X$\ack"hX@$bx >< J9Q- L/M)  eqP, -Z"iIq -:-xZ /xnHEHE E/EV"MonTϤ"7g_6,c%"G7\Z=SQ"ߑ"g~i!cȟU"Q"+%oY1Xd9\diqf"3|3"o;,X;%-YĝLsش-2vgX]Z]PoE Bꃏgiڱ鐮*}k)h~3diGAAX 򶚖et~"ѳ;՝tiCX-;(=G{aފ~w|guYEs( "s/"c,TD5-=0#03+Hp7x| x@}:A5'0$[ͨ]uD_/coj̕E.RQ!Rp >*x OnY /* "E, "EdUY]_52QJ͛DΑ)h.ٟ9GF"|FN"\Y\&$HD#kK$p- yOE 2Lj $}Z- i^q)H:JgrlZrbbFb=iUo銕D![Q<ׄ-4A[!#Y屠1t4tQEfЂ+77t=Mg!E{Er!EDz_=oDC鮢QYsL"xM`@P 00 Q A pp`p تȘ*2!05 vźAyco[xH.UH dA1!9>Hq9t3%He= \JJNU{@*@?*" - ڇt5Y,CwUؽe:`1/=թ֎|r_2T582+o>Y(~YȞ:p=iS]Pzgn;VҮt+#G;S#+I3/;TD5-=0#03+Hp7x| x@}2AU'0{HD.Q}:&chZe6@ʈ=,G>qݥ++˘\]+c*Hf$]$~48At-detHJF7֝ܩN6?9LH{$J^͒D2$%(d$MȿCl?'wS@ǒDΒ%|,I,}SʘXMmfH"}(c.X/ZHO9&@"O" ?҂$eźI"\Z'>>!ٙljB"ɉ)N/zbS<'\yu*mLǛȞꐺ,KtPUC,CCA⑐iny$P[ mKȢ [.޷w=CS&$D{UE̲(= O+~w,mohtIʼ H'TD5-=0#03+Hp7x| x@}fA5C!yR% -}1c71s3! DjiWYUCU3Eћ#rD_!ϯ]f-:I"UDd$$R%I$MF"";lTΤofvо]Y $)I"GdB $r$#2 )ؗD [ {D~kX+g~\C%B8&d5 SH䆏QΜ+SDiDiɜ'>KKw)(l=΄DojBrJJbz{Dwf=z#jK#L:L&\Lte>4o B&9ͧ0o]mGgD&h{ۣ+OVC"AaH+ADDg'.ӐCH$$8TD5-=0#03+Hp7x| x@U?aHVQ#h .,D>b#ib^,=IHM"5܋'_d{o>ߠ%$$e$b$DΖyS$J$UZO"']W ˙+$ֆMH(ӫDjP$r:I$1NIθ8d=ɜ31ޓLOwYO'w< ǒQfY'AQ3!ІA_J abL<@,&N:V71crxz zDRaD~"%DD~"%D"s[J䃧H$L$D42IO"7]gM\sC HI~cw!_ӧH"! m%/:zNHp:ٌx31#6œy؄ʙ}V˙3{>tD^4=>ҕgML Z̥h5o- ֡\'$R農} $~ )\D*!H$~5H$q&VH ?4C"9D*:`&``6 @4./AR/z&D0ь y^W볉a7b- ĚC%RL%rX -1KApM -i3' p/Hx=DHWD21I$HĴ)Ua9 t|w%D>I"YDJ6L"Y"#(+]H"]+g&|5iiq.HgDt$Ӫ?$.M$9DH$1-ٙLNKgi'ٹTdַPB"c4TԅݽҠhCH75=iDC<3]U-H6oytg{C $2>dLOУwaD>Di+; ,WA"KA"-h9Sr&Lʙ39(grP䠜A9r&Lʙ39(grP䠜A9r&Lʙ39(grP䠜A9r&Lʙ39(grP䠜A9r&̱C3}D 2I?TW$izE휨+P'Iâ0:Ouх|b'1Mc^tMwCgBdKM"E|}9)0 #D_P" wg6G"G~pտ#>!d$%|U&<#-JWerS$@Mk"OC"BՅ?A"m,$t}̷)>3> $?VI$MMMLHvn.ɥ''!%˹NLG_Y+g } >n,Xa2h3ZHY $R&Df+UD={{ǓH $ t@.,NH$n<$R4$$R -$RAwgzI jZz`F`f`V` -D;n,@ա&j0%BD+Q4 DHtQ4ch$q Xik"SUJSS0OOU ^Y/d$"*6:3;Qљ7E"S\^ }>%$CD^$2*D27O%_\D:f۲&-Ho8܁7!g !dLp0c>LX'>MgY'M^"R3ғ2̚|5*%ۘ [Rgjyτt0Ӑ4ο2=`17(,y־ -:r@$w=tM)}"(V<5HHi -_|A"C"4LnMVL 㻭<=3L G4o*%Z4 T"}Ai%D[aoSDAvXX~ZmϤǥr qD6d3))θ8w -xxE%D:w1eHt`$SRN"tgi4y4djxb}Ja):6h6=ޥKaϡ"/F=3H HMH "mg+wE:]:NF,=>^Q0 h X D( 8  8~ARs,L -0a"f%򈾂*Ech*=IiZD'$"^S+񪃢{xZE[:(y(mvdknxns{s.k?sF?E ^#II"gDk6E"3{8ECnf$R< <7zP\YO-]_D@" OzKKNMur)eSN.XMNHDzcSOH`++T=7^t(y/K=דkj+_wio ÏL+$X[smE )-4z&:LKH[`$2t'HېH._ -$2 B"ŇPNHck"hX@$bx >< cwWҤ55D{P͋:0#ESX4$Y¢u%$&'1DEEaDoȅWWV[H`LDn6;CF"L"R9CF"<WO5,oYwt|(߽Lu\͙z_BCw11B9pҪb͵ͰHOQ"wa`3UAIHOHNu&ın?Lxҝ)lJ\\|\'D4ypSdQ$KB:y,uSڠ> +z2 5AcA_T4f1:{ykK%o[C;?E]X(.btYFaDgPt/"EfހEzE`Y jZz`F`f`V` -D;n,@4.bAPU$1$UT'i$itEIlbM^j*Rd/ą"E4=";6#H|шZdd -uS,2_St>I" D$|}qDD_pїyU3D[gDӐ`-:)B7yKbL1qW?q8aKE8UE@7yoD$|}U8I"__[K4~{d},IdZ77\Z$/#nȮ]p3JǚD.ZDc -@"zA& \3HI";>6L g'uz= q) qx)ag&o)8ꙜDR?7m;k/d46"o1 v+,uMЙfJGז"Ez=zsA"eSGD A"3"4B7yFyIcI jZz`F`f`V` -D;n,@A53C$"OPWB"DԵ0IETB#eHRH?7: `Vp.,po%rDJ$*#%IFF"mD0?DGtpvV$Iwˬ$1麈y۔E;Y"A"?B9G(gzȚDD*M}O"\sʙi37\IgN$&sDb^gbJj3˦e${S' (ngͱFmLc?$>RWgi -C9tPHt_aj?$PȴbB9V[+üm^\g -9|ѳUsteAHd9LOϴU3te-$]79^zL!zznV짧gI jZz`F`f`V` -D;n,@sfW L. xM5DjW# -vM,nS!&8W<JDi dab{t;=H  df~  d꺷@Fw:hY8=zR.H 3oo3+٩ jZz`F`f`V` -D;n,@*SjgzID#Q$9A[+41%:T.`Ca@$QNL1yS߂C:~vw RHfI")e$r˦HS:.ώ KFD% HY _;z~ nZŐX$R$ϜoD ?3(cOfSRP[w=N֛Lψsf$q5%ixXXPug_4x>SRԖt-Y[ B{,pS -Ksmcsw(z—FO7ӵAHd6w_jWzҫ3zQ~}coHdr/$2ڮ .!Ӯ\DI QMuIb驙e1BvDF{DkDwiTdP䪓%E]fgJpW&vsBUnYeB.$$ZeoiF#VwI"~HXCՙA$UH_| [x?H$*cT;ޙNHwqNu#i)lllzzIk!e ؍07*"Z=5SpP;-fzjƊΐi0l1 n歝gDFuUۣWw'_DnT8 - 'DېD !I/޳WTѮ< V#LУW  C"aH$ !0$DH C"aH$ !0$DH C"aH$ !0$DH C"aH$ !0$DH C"I"Y#ڈFKwgL\%Z#T"tk)ɾHb:DGݢCdDoG t}eN$R$Mw}hx_KuF8 LI`SIDހDTA"I7g>D;D./D9i?P,#晋#iu~7!%>16-鉋DTg9Y.9#2IdV՞yjii3Ng-<$R5m UƹBO+,H"tY;H"5F܊$B5]D͙sGKD*D૧[U8DvI$OP tH.&qD}`'J*e^RTS3SW&a'zS"{pwTMf띚DkU$pAղ)=쉇f,$3e'II"gk?Snߕ8~7sښH!CrLI-~I?n2D.| -9xi&9iNMsr)θ $xSNi_WT=ک:P5mcjA"og[](g羗ii -CG-] -K=4ۦ'!qHۣnawޅDWB"T"uλ!1:Im._^'$R^A"![ {I jZz`F`f`V` -D;n,@*DU#;FV =wKc`c.G@%!{/:xwx?D \'M 6!P&|$r떍4HuFC6E"y9ivźwޝ(HCI"&4J J1Hd˦$Nr&bbH"[$RdDnَrJOD[x&]*A3ŝAHKv$g8lכ&+ӐsKMfe+Ta:]H,H6.{&K_2iW4=2o1nD3Umj?ʙyH`d{G 7VH"$2EDQΔ#T_+tH$:ûtL"xM`@P 00 Q A pp`p LA"Shmѕ}`8+~b!" -I".}Iy_'9-$ܲɻ7E"/sͪ-׭g/p{!c[ |wSȅN(:J)z=ǜ;s,OEvd.j♋;GOwn6-p,Hjr39LtSbcdoILY!jDKa;BLMgKP;hHwx.K3ihy"hlzQA[aF)F=Ӝcۣw؇j.gyD4TI͔Ɉ`',Ұw/eXd͡D?^S@@"AvX? );V ղ@͔&[h 1oL2c;1%f2E-"teuk wEDw OT _bsϮqn=9W8բȺ',AfQdW$u2"ze[UWgU"9E ?M ,cSDoyc̵"7VS96u'9Ywz#NHHơʉ=iFsm;xJ*fH7ݛz* C ADTxV֩ ֖y[CP.2sitO:5 4|@nv=I QdZ}yvQ"5oUm -0 h X D( 8  8~ATfi#jx_~w !!2[i>Zτ3+8 - E#6^~7"{̽Uck$2ɻMTz?ǻ$W$$"%LۦHO"!%;$%9:hcλIdǏ)$r!PLODC"A"7C"oFF` tC" tFsEw"hX@$bx >< nXT3+F !^mU Y0F#fK7+}t{؇Řa!"i%ܠ)g>;H"?leUD~)S)E z%e$D7<r|toGQθ8"qeB"'|KIӧ1'>?ZO'w\b|\36%MKwrDg0)ID'#$II":6WɷG!kIYD~OH>DI3W - ԜV'8OZB,:ݞx'E8a=Ό8oeSS='35H7HO%L+}sH=b4+h/dꮰ'j 0o]-C"tRv] $GxDQ; ٗ۷$Rz?$R,ݞDD5U@ 4@ t@ L , -l Dh`1\ <^]D.h[ P"xӼ` }3mtM -13cw"zo<#|`DegJdf|MF"z[e$8SްE_6,c%"onxgC -ͫ7%꿮8Mk$,6D,USXdBE""q\bjbKh1t"Gi^Eo,UD5n.w=E|Ֆ;E>EErPϔްW?LO"cH],4zD:`&``6 @4./AHޱ(Ҟ:*2$b$I]!$] Ab'ZjD"'{Ę:QZ%''=,z'W(fEz|zF!E˼=E*YD -MϦX:U=DFOYLj$3R$$7y_(bq pɈ"9ș~oAE"\|<8.6ߍw&\ӛȱ\Z2pLyܧV{Vfz]DKH]}SfߓAm{!S! -AciiygwJV@ǫ(2=d}Hgxôl/DFߦ㈞D-nE }ck"hX@$bx >< Ƕg: $zQSE=D7NP(!EGH/ݞYWmc 9/Of'y W3>g>pHnI"l8yH[_ߨ`IŖz$ $T7p朞}8HHڣ?*֒_@"=]O7F=]$*MtҋXw{MwPy6"\ Lܶ>78ɪxDvT"k h."%Dz\I74އh'<w<ݓ .%gGLS u"Z%Rv %TfĠT F0,`XNpxCDeAgUzVM)vVYCkfM-b`Y{;sg[X +f},?r t[?m2fDHLb4FYLLb4ջ6]?)7_-2>-YLha;b1;2uv]v@3mԿs3xM|uD agl-kqBLҮ -1˜7Uݑ3Ph\Ks&e :u>S @ `A :Ѓ`3X -6x!1UD9+2MD)"oe F=-ꛉ]4YzsNX`6 8F#DϢ(r_!Swϊt'e/{e02Ov@Qꔪk_}p7=[w!2f|g]b+~*zuk.*S\P-S"D癿hFYli3vXTۨaOH}.ΓR}Iii_PHW-s_dsOeiJBlDw,\irii/XkkX4ml{m*:/^rw 7Ho)U_BL?i;DTU"Dzb>S @ `A :Ѓ`3X -6x!$8aOK1m"jIAӢL2ӄh DNtEL 1 -|9us5dN$T Wr;OOL<6DvL]w+s}HxHJvl],d}p!Xtlj|FL"S#D^f+n/n@LIGCгOewH"YBd]HaB$(Ibj3 -Ԡ=&0 q`;8 n8~@B )2咪ۉf =-iGD]+C116H~ȶ5!~ mv_;ڕ^2{לRuh. DRe@".CVJG~J;ukq~!w3_"ykMS\\fzwI+ӛK&3צH(E>”)U SYig"#uUY%:쏍UHyVT aֽ>]8-2HHk Rd7t\6[i{ )ram r)2FSdu/S5.>S @ `A :Ѓ`3X -6x!eDObո""sK$81McC k!ZJ✖xg6ŗ'͒Ip2`d̘"ˤ=1EwERdRuIOfFz,]]2")5_ץ\g_u31̵+r>uL܇:چZ jٳ!曛B#HneItT ..cs+97${}Iuûni,(0e#EJOe E{fφ %/{_4^xMa> Xr .)w})RJOch~A4R?_|OW*Rd~?=9)DO->S @ `A :Ѓ`3X -6x!HsLRUuX6>"bHLκ+$] H)QG턞K]W"wKV)|eWOckvzݺf>Bd0a蚻"_5"eku_r{wk.h^:'wAJ!e!brdo2K8:"->]İ7)ȧe$^"Gj}>S{J}TתnrOX`c|ڑ_ts{}2 }3vtkME?6w,=6k{c"vɿ)˖'Оߜy/=n;ЮoWxvWpCv+|=K -5EE]WBTT=j;>P @ `A :Ѓ`3X -6x!kW36U]}ޮ9kKuv}PXk7ǛO-vB0_Edh|-B%>{¼CoN5c!Cf,܋2!ʫc!qQf%\dy_s3.[j֥ؿ6 L5H˦ '|7U+İK$dQSUo kZL됉,UdF] Y( -C >g+!#D>3wi.@E !ҵ(0 c7n/!!2=Ij+ɧ6|I|:NdfGoVZ!MnaHeCf"6h?u\/V }?1=o6=٫0oR*X+_]̞.Wčry}6e/=pμ)ǻ'b)s),P՝6eJL'bB}1H~ i;T*PXЂ`# B,ā '^ĥȸ(EPG);s`tP4tfb!l`8 >B MUEeLtJzӬR4z99FC-M^W$rSGh,.6)RdL)̸;LɌʄHܺC_A -q5U$s]U ;eB-"wʄ"~矯9;s݌KCY +!P<8O 1!BD-B:$%z2]\8C%t͉ii|9ᆃL%2-0Y-Q7>?bkʲ}!C+9ƊͦôuH`YxZ. Dʟn{!ӅU;J·X̧M["yt!22 "m]|P4tfb!l`8 >B )De,FA]/hDIvAA?wZ0z·VO{ :SW$fV/V1ظZ+DuUf /] ZT"WOwh+'"\4DޕY#"?3-f#DZWB!2zSz{T/`9omD|usHmNr%3$+Mt&z9_f"z]됥;T"W(+0o"DQfi׆sU#ٺ}?}0d-%^yV?'X{"uH6Ⱦ"suYzѬx!BdNjCL<%:jv1U{ig!T*PXЂ`# B,ā '^Y2bZt -<=s[)qe$PB'?_t1iʬ ۣ!"EeBu [TȪgozi/2M3T"dd*uYU&Ur)WB$!]ZAJJ*f0w27S$x}^Oz+KHwqG/JM2̤3;F/EBw -[r -r%ٚ rO躋 C'sc̦Fz[=HB$lEg"gGh)s>3StL.=r|Djѝ#tgmHÛʣ+CfJ`@j ZЁ `V87x ? ! .)rKP -nQS'D[LteE4âB2 Cf̒hyOy"DΉY'|?'2S*j+'{"r33Ӣ!bJ$m]B~뚭3GV 5Ifc "˄1:C.*BlQ86ߢEB|QndBoJ+/pgK (-vTcIoӘ1i,"K\ݶ~mXhADDDd"EdqD""q0u0Ly6ٶTΎ}r+o;lҭ{K{\i)?euvR$]~cϻqMnמpJne]W\cū.L/_+K='{Nz|΄t/늍%8MOsyb] IxWjZ|7&֓']wi{t= ->:jUe -U~w+]aM nð/l&X82}6/Y-U?T#3ޭxGtqN{} -ǡg^Q8sR^PY/t(=Y(U;xh)e o2x2TQo*|^IZ_TnTjЀtfl`8 ,x >Bx @?QL.OtrQ"GECaqZD\%[O5-c$jKthJt9=&âG._W"7+$X-[Ȗ-_XOڷ-=wj}='{:~ Q={f,ҟҟlҟ"?EJ'^S]P;[-rGUʧ)[-A喐r B+Vk5kU˵sǿ?ǿkU{w]={FL*yj[wױ3־ 9_;]SwU}GP]pgL? Пb.7^PE/?"" )I,SR\TוqbRX'Ĉdl9tg?ʆTկ}z6J} v|RGF,7,Iihbn\d,eX+ ;[.jz›e` xdym -_gq/}@lx{ӿ? -I AZЁ `VADBD7/? !(zՌЫ,鵍z]^?7eћ&梳,%gYϲUb QųzǤ٫wgH\?/Z*V*FTVZ1eQ(c bPG[O,Fڗrzxoz>,%c )?89%cFJd<o$o>){G2nYfqӊd~cΫ3:@d\U!&0eӄz;S2<֕Kpyӽ㓓S|_̉oWtDrfSs/53 tYu cwB5ךwZ-Gv)yMq>"ϝ»KQm\ݸӾ2掮[IP#S; *}]bhke`SPr{|2OpP=&0`(;8 n`^~@BQDau+mѕ1!bZ9_6;Z1-!\-"[+zGF yl'_e*rTDiTREC'S7e޽e#իz_J3=y8ϩ}A6w\ED:11w ;  b3V5"t$'{\ x`..=9ɕ%כʦƞ4wdj{F0"RyH m!]_LCTS!XgYdJ>bM,^&xcGtN{H/G ,^b[K{S -d -Dd),^⥫ 9\w,"*`@ Ђ`#  "  'x!AG%VYUtO0tc`̇˲`mlm"RF#rX YJ܅"EЈd<#H{H -ht FDCD}"Ԡ-@0 `+ "! -pX|@Ed&Q=L4%9İL)?ќh]6M'Fi%Q$:sDwȎ n%\",,E(E K1J1DǛ$(6Z "EJLDS -I&")1?wrD\Np3θ(_#"[2#&_\Bs483Q7TOLlJ -"KIGD鮤8W y|O\'=Ĉ4ߴgFBe[ҡLXiL"a}3& cMOT?6Z->.Uj>b0"Ru)趒:D5Dd { 94Ԗ#"+ "ci@DjAD)DÈȑcTjЀtfl`8 ,x >Bx @?ʒA=D4vr}h"]δ Zh/ˈ8'DwN2D L"gL"ۤDDRDIw7%"ޱ~ʞ?Ds"b6)"kg~Gdۦ\Fؑ';0EL"9k˙{&~5|g\O1,<|7g. aνR}:EJJ\TIƗbYADzvD_}n1V=} "Rq3"RtһX@DW܁d2XSDoUMc5h@ :Ѓ`3X -6Hh<p!<X]l4 ,i!A@aF0So.,e*r Ȥ`+D;)xo+ \oBpnJ&")"J|.E#ED)7%":OD- ˙d3-$2-sauϤIdXMD]&"r毘DFo$RD!"-z,g "WO凘D<],g|t(Ir{0$`Ib<O7=P֮QiDdrN"aMLmˁn†;3S!S;asNR<"2L^ թĈ ~|u˫DΛ.gF^CD -i$U=~i0>25tͅXΔ<6r>bL"H#;w "tWH"rYhAD1t!"7!"=xBD:\D$oHۑc> -P=&0`(;8 n`^~@BDь'juݢh(UDdeV#""4"ݗE4qVD6'3.zE_U"7 /G)"_"rs|U&";+E2ٱ)YzU$lL^sr2˙RDΐ RDK9C&"7lJD'3|Dd8epk)fD7ȳS,gg.nd6%RR\tOKOs%s_RB'<'Fd]34".gjhDFiNVt g3EDVKQ="R o$R4Ek"9mۣ05""?#"C,"rȑHHn0"2ӂ,k"+$vlTjЀtfl`8 ,x >Bx @?ʀZՋOvT0c7oj Xk`˙v^X쇈JpV -<˂ 8$8)"RDRD -de"*EdLDR7%"?ا^w&xw}LVǤlȿDdk"IMߝy\30 -kwg-ADn-kGDORL"D,w "NRRRb\tכJNa\IqqlINHG^];ӷt7D57$C[юW6.eKCVK0"Ro#&CYGܻ^ԎO""]JJODAh9"R<4 #"SHైc5h@ :Ѓ`3X -6Hh<p!< O5*ue":Ni-ur$J#2@U#ę "W@a/C$8I.)"j)"wKLDRD&E&Dݒolt׋kyu|*E%)"fTKRD2?IDd&_$B.CDީok3ABKD83("n8"6DJ|5 i {ɻ??TBx p|HA"3H#KD7#ECHw6b"Éza^ "H]mSLc"WN ''.[MΑ[tsUmv0*ئ9MZU6}mm컷VZ{δv<Ֆֈo. Fly;+7?gC.:[e+<#9 - -_R -QRI;[<矿UAnSzө5h@ :Ѓ`3X -6Hh<p!<eez+3cU/m4Y겷ƅ敭ڭޭhGVzgw}bVg]lezr+aZ=`Kha('#$ӎsvv\(ӎ*6<g^Ֆe'w2miMYo܌Ux'֞ޭ &?E<2C/3eOUmV"X bn"#H@4֛R]޴ΕɄIR1LDj_iWF?i7+S[XHaX_P0rgq ˘KaͯmW)J:D@˧5 "w """,t#"!݈ȡFD -/?"RATmU}h$bjK4/K'NzWfFdF,!]$*;O|[!>1P+gu32ݿ.E+TD͹zSl2z.2FiƆG4Jw*=E7\ #Rڌ1?? 2i 1K 1/Vp\zb]TS5ŕMB5rF&nDd0+ ÆLcK!SHRt/&*VAD -vvD7Idhݏ!"ӻ9E ;q&޹izkDN"""Otq/qQ/D}h8h\MEshN"m !iY+Dv*!z -rJ7I1xx|E&"ۥ(d")" -|)󩞙zs'lwv)"VPHLD~)I1&{~+(#"I$*rc&c)$23K"s:E$MNMMJg]q .'5ޕb$ǜ]yώш.)T 1ŷ*Գ mo&3 dsJBFDj+$7#"cȡREۣz.g -ξw(ٚJO4"R?,-PsRk(Cw*>%Y>|L0 hAz0L` X Q vp CDAӻctYh(I4ӌh>hɹ:hkiiJ7&:&{QdEϨ!9]bUDdoDD+EdoDD)9ڲY/"ӭ'D2933KTnL\PMS&luX\=,g>_z!"ADA{"r.DD6.!XOL&ty<8t]zFjl2H3LBCHU7>i(0ԇL{.ruRG4ҝD:]pNmD -oEDi.n2kRՎ]}Ob)$2أs'z$wح]|L0 hAz0L` X Q vp G .AsD |o'xDsZb˙͈=GtgqW lv [-VW@ $0C]oL̕ULDyd{ONd*T / J?(5A"t?FW9ӈϘ)*R];*R 6MNOu'{]ބXWBZj+9՛åȜFTYHLHϧX|mKH7/4,2SVK=H>bqEE|N{k%*2CmWV+ޣgBEJG͢"}"SMH]-3m}HE;zfHPaA)ꆈG0d Ѵ kK6 -JZ=#ēM(+["[d*rՆDp1mJElntUextDN#zr3"2-s{&jSn\Q'ezFkߖgjBD_w1O̅?y"\|53•H˦0I))..5ojtH{ޮξWw-*TeG/crC35Uwgj' :3-!Srղ[aBD:j^zY""2Ď{w AD?GD -#"-#rCD""tj_ǏES AZЁ `VADBD7/? !۪/GP 2m!b_ 8ěs Oo4R.(8s"O.ב@G")ED%t)"RDT2qnJDo8eu+?DD)"ZIb)"$rL"O^u\L"+Xem=*D""r=@d6&lL"y 9㴚DRxĤ蹈I..-ǧzԄ~riD*T}]Ό,?9`v~_H>i 3L7#[-Z_GLvn/@DAD! -CD{'Eg1>3r?"RMDDA"> -P=&0`(;8 n`^~@BM+DU߽hm$&bO4戦b.-uIMѭ t= ڇÉ<]Bywo}"7*@'ed-ED+~)"gKDMSbݭ'L" U/sᪿߔI<7C$?Ijm_Hcr漪)3{:y".UЌOIoϺlǕ}i\},/餈f.]=Hd -h̑f n!oZDq橐&lj?:ZۖGDADj== -"Az '4sk>BDrpnr"2ɢx[r&.g?yԠ-@0 `+ "! -pX|@J-gW/ - -QSCnGWxS`",ZCl9j)D'f=I)3KAB4 h򺃼~7, -T*kxK`=(&yDdFd2ܳ:zMfx8H:Omy\]&"SE\]&")QjO Ed"gL" RD&[)"RD\9gSu\#QdEE_rPr)z{ND$kH3Pc 1A\\z{i֓cfw+))6EK8OZJ\IǼgvu}>P\δ !"S5+3.獰㦰! cs!S+asmղ8"RI$X˙ -Q,gniVՉZD$wF"H8"4"|8}= Pct] c> -P=&0`(;8 n`^~@BQDU)a:'Q3IMJ'qv %GcbsQp/l"%Mb,IS&"wI1HD"i1HDMu=sz]˜iT߽Q^F޸)}Ƞq4EcI"&ڪP>o$̃?(`q!&t *GE~m9*cc8.ɛDoP֕bٴ86$g])TZ2,bLMځn:}qᵆ2!SISZg? ?̃|D knnsQTuT^*R>*TЗEa=Sn=I缏=C4kBE:Ԡ-@0 `+ "! -pX|@~&#D;@t>O4 -ƜDSh-}؊X̋YE"E<[O|'a%'^)9Xf=-<&LE۔)n뙍#TM&"*)"JDD)97;ETnYw "߱6$+/r̶OЈ\뙷ixj;nDZ1i%q1.oj|+!6)='VhZRr /gJETҗETt/]~$0 ckiP6ji~FaeG?@#"ۣni*DDZ񽕾s/3sHc5=Io^YEDz&i;LM"Ԡ-@0 `+ "! -pX|@FOTՄ՝ ΋FQ'ՍZ&ZRzQdFdXWDg#cp`/< sQAwydߕ;]ؔKSYzNbL߿D&7~ܞy\$Q -DdUL7\c뙗CL3!\d LrZ$\bV1$¥Ħ&'dz'Yzjwu9S"23iy;C;SXLC@pkTxRZߣ.r>YWV_]EZv ݟ(9^9'@Q䗏WLEPkt7)))3^Wr|KKbSbO~|&糫_]}oUg -UKʫÚܿeh+u_{ZRCq/!י=DLiok#VzjvnzUdGDL"C}];nVr]g+g(2Ko,z(Ԡ-@0 `+ "! -pX|@r]YY] (q$B?Dc`&B+2BlCt=LG^b#zٸzHHzF -2 'nZW'#K9S"?*.ULtS*rœSܟ;E)r#[Ϥ˜G$Iߔ3W ($FO/_H("FJDEDE!&YSGiʟV-ߣE*_2μ-Sߣ{5G/-[kߊ(/:o8.j!y[yDk3_raqoe)Ù¡=|қwU*`@ Ђ`#  "  'x!AG8h/y8!3;n;A37lWVpdso_YK92/6)גqL2cS}y'&-Q^3O9a[jRaGümTF.-mw}bM Y4ƬJ3 m< -?=.z<1x`=~{.0 hAz0L` X Q vp Ǘ/"ӹnH0SC#aES NkhgGh#Dg'q\=J |yMf]ș._~'ULiLE~)){ZL*gHnj4y|*sSw݌4y|*Sqt\usLe-kcPz9چ,?,Td%3T伢ө"qq\Jsų,f]I1OZZBzRR/椛E\=}QZ)_/{0ۗ}&8f2!SgaP6Gz&sN"#<b.w`Qގd;ǤŤy]/NOHsq4KN`Ӓҹi505r}TLڕBkg*QGCyjYb~(Y8X ;=FE"t,}YdTd>.3A/_*}rIT$g<*LOvo뱊c5h@ :Ѓ`3X -6Hh<p!L`Qq)/aǬއQx[A)* -=ѽY;shGԛQJT9<ԥN׼< _Gߙ922_~ڣ[Uc7d1U4F0,`D@$DA4Np 8CICDz)uA"%M22L'!sސY"c@i zE_u",@x♈2wu8Y/U$SD"z"&$nJE._lp~+RD02H|.M\M`Qq]-"ҍ<DdGwE^z s0AzLJZ'-%7KIĦ%%4*wȒBup2&eDPfڙD-_$p:82e?e?=}u7cA3L_2v蓻oeO"sH>"Y%3Hիt}r7MD$Ez"*`@ Ђ`#  "  'x!Ad6w '|xwhj$6b)$~b[H,r{$)NѳB j7 ,aeJen)"J1D$bS"87Ed"<'Edqә")3E6:|e;__{ }?ľ_rL-s- Oyg.VOĝVzbc}.ÃULz"JHHMI޴T ^L"7_TšghGuT^o &e*:*eKa-Vc=VG/!"m/!"}ѭt|z&b_{ -nzxED>Dd>tW8"RWSW5h@ :Ѓ`3X -6HhU,x >Bx @?!QCRQC4VQ7J!K4Sh%ՇV3Hc8.*"F#\+񏈁1,$r&{l0"2ǙE$vS"2zYx^"foQA3'd)"?nD$yS"rub9st?8Ckڽ8f۽NzQ3_ -OHH\3wzmRMN_Ƹ|qi.oLZ+ٛJƗzSRRNRQVz'>-sefxmџ[| -?7|4g.,| 2-QL"rпG{u# -C{;l -g] -oǛ} -L^,e^(EE`ENEس -~iz՗D#5h@ :Ѓ`3X -6Hh<p!<%6f^ܦuhy~aXfsEeduau H^ryw9O*oumW\lv|) 1) ~) go8-7xoFn w.ℓ s2bY4Xdؔ|V,2yVzZ@2轙D!  LfS9<,a, !Di\zRJj -Jc=Uw)q8WWW|ej˘ԫ$3U4F0,`D@$DA4Np 8CvU"'ۉf2Q;Ht}n1Vr?,XjEkx<{3]>Oysxg_v"W(zⰰ!D -CX)"r/ݍ"J{nܦD$|^D^;"^8ą)/ݕ5CvL[tkYZDNwǜh,enRW )'L!ߺSjj'Ǻb\4%>%ƤzOB:v-V>D20"2a m?25O#w3L=aRղ\<"o#TFv\tGt;FD電GD&(#wW=%J_>=htaSD5}G!Ddm8TjЀtfl`8 ,x >Bx n ,%zQ3!ГD`U+EKHkKCD$q&\'l9,xE_ f L !:C+\f+\,kS'Dz-${Cޔ[1D7CD.y|;"JS9!""0h -1݀X -~:1ݛz]\R}+gc=l\7&Ĉ,d횼{Q%-riy=SӸ7SZU3d:3$ʰ1puGL,cg<=z厝z=K L"&vғ&v_w۪rIf "cTjЀtfl`8 ,x >Bx !ZTE"$a]$EQ?A u8BLQTk1IF$OOGqz-=#D| @' }y)%R(E|)"KLD.ڔ֦9rfݓ&=)3(E\襈(E\7e9hE(DĆvEdGQ/+^GDt<#}0xix6ƗJJ8Mu%pq)4/Mz1'FdeOH#P}p3"Қ Leh zC4d;Bΰw2ӈ>VL3=hoL՗;8Ěz~H%"2""?`9*"X_Ԉ''M"4"e"Ԡ-@0 `+ "! -pX|@~I^U ГꈦXf ^_D ͂R05K,#k,=nX1,˲,R̲! %b|ڻn׺;=خH\;wwb "H[FQX"RZd*V-RJ;'鱼=|y5:$BUsi%R;Ng<;{N)T"2!DbYD"D1,"HdВrFI2KwD\#"Dq|}EBY*dH?5Xk/Lh%2DYȱC^Y}fNq+J"L -!+Օظ2twvhަD7wH ?^TїY_Fbн"shgJY+Dr}H;LE6 zo8陁D&JƇ=AD6{:A"]OA"}44ym]>S -d@@ T@ 4@ t@ @203+ppxphg&̘X4T;f:!rA0vJd*fV # Ȼ[wӧ3+*,rIˑn em!g]%׻#%veYˌ33G޹pb(C;3LtVvvYHTgiGz,`hD!0}:S St[Վw^$[S$$?Iw_%ϔ$PP 0$ L , -܀> #g"gJH4W5[yAJJd$[7s2/ [H<&M/'wDn;hXǵD\Ut"31|&n=3;q~Jd֛\hg׻eo|Bf}=;?s5Cý?[W#fehgi4k5-3re-Yy঱%G1#ވayS~EވxWX״-DMC]}g-g' 0o47h;4G%r*B"ޡfH[ ~A4ډWLʃx %^J M]5O,p1>Dwohi"DvI$W"͂q%2f7|2Y6yp%WƭDb{" m9v K>g-Q0k祿1kl^CȯԔ̴-Id` 6r8Siȥ,Ի5*9uL $Rz:,Dj+&^(+NUuS9'ݫ{^;|*zxL$RSeF};$CF;Ӳ ,M+G;s9c$2 ̿K_$>T"G'3@@@@@ $0 ;p'p7`x8/>L*"/%WN!B.=73x$փľ9O=J|ퟔȯEDv$;&E$IdwL"WHD"='W,4^2mvȴTiؠTia_"["g.XsH￳6)j_\墟gQFwmWn?ӽWP+!U@@@@@ $0 ;p'p7`xݽIi)q"IVJޚOR'WVi:ViVr 7=h\ʒdM'9J\IUU$_OI䩘=>#S>FO{Zrx\)7m*7WbKXk1߭C\2-ǣ{ )z(AK3~FK%IWaHpe\Y,̢fBLK?ȣM-'!f%ҞmS(A͑tec*OUKZW#ڃ/uMi>036.)z\<_tiz2d (AFPu)ىdJ͞v#2JY 3E 2ÐG6I ȁ( -hAH&``vNn@?_VGH)(ve@M'bA_0kN*!\ Xzk!Ujg;'xo[n=AD"OčRqTDxH+$K$3xbQ"SfA!9r-$ gn'|N -ѫ8o'$rUqgYj S\IeQ4Wj6/4r"hfD:f˜}',:#?˯h]DY~Xu,GSTuA"G:e63TsJz-$BFs,OwO2o)WHd($ٿst9 <@"{%ϔ$PP 0$ L , -܀> [ 32+b(w!x)^9ZQx(6j Ź xvx91-#D$iId(&OHdH'/hfӲ?D"Hd eȎ]XW,=,͟C;sDk!HB"^+$ReT"DM/}%I$#=-vllG͕t2RRX[ۙҥ[7JDZZv?#/E%r0 ڞQW9uynD_X:pB"t]}SsFsH1HܓH2k D&ߥ2%l۶->Hd7D:wt|0 Ȁ(h$d`f`V`.,/?SڢePdC@PT"CVASAY@Cݏv~qTuz-3B9λJw٪ÂwjH*ĘDތIDlswHyȲw*&X%Hn);2w +/$җJd>6&V1ڙ#cCy>eJHʚ+%= T"dmdiK$P)?SB"1CZ̃@qX^[b{]*5GW$ 3q>/F0f )/˻¢/$8sD:hj 9}9 -4@"]gJ ȁ( -hAH&``vNn)b"mn.eg=&fD'i;SFۙiWTGխ~MKe98*HhgJ0VA"vHλlD@"OA"[}-,+̑7m+F[#ʲ񰪥=G=kXUD;V-Q2ΐ3vB;3gH _B"@"=E`#Q|Gˬ~w}JH%! ӇHL)`@"9P%P5-=0#H \ X^~R.Z N5(Q -4 Uc0D+̕C%!5'xxoDH~L"jH~L"jܲ"SigU;*Dz⎪FVD"SDv̗)΄4qeHv/ȵt;b!?$p\W-+B",DLr!lg fdSbڙٵMg}1,+x:G>W (=V塝kjhvu{^D;SfvsƁ_ LNJ7{vQU:> ]H;wx#y f~CDvA"yDG3@@@@@ $0 ;p'p7`xyAZ'0 BbQ@wDETeET A7󆼯A"tT57`>&XZ^!8 -ΝWӂ4 Fˤ3_|shQъ ;z5mQۣB/Fqmu/EW6pJ@jЀtf!bvp@ ~@BRPDU)눦h]7gC?16S`-uDb7تx{h!!ySCʳHm 6,`]a)u E銎JwS~O?Kg~(I;''hO1ҟbug[M%=*@GmD1 x6QQQQBQ _]aYqf^K6ڲap篎ؕKg~(I>/W>AÎޣwrEZrXyMzmCюn2rqx8bjG>FE8\쁎ŒbEf-7PQT$y_=ؾ(OH}QܗPvۣ}H=Bc_">BE.S @ Ђ`# B4@,ph^`B8 ^JR#rA3A-W}Мd!R-X"*2NJp'zx[ l v 'T^"'URE2(URE2κTĖ+?*DݰZD&~Y"EdV>]效|S#.EdtWZȟ^ujD.DDb!"eDQD$RIFD/B!Eo#"[ egRD}lbz\8MHe]LZ|6>=FINHկBD -r6Q2-4!m@DRm18b -r:_ELa);FͶ->"5HEz1݅,M`ɾ}TF]E -DD@D掏"L%P5h@ :Ѓ`3X -`;8 n x? !x)Gxe3O:N=N4D;Fyg4'eXGĈtHg3y@&L7rIĈ/E$JHF)"_#EdDYa䈤3Q׭?kLIL"H"r$r?IOTW7ӯhҟV"C "Ru+[5;A^nLi˳${֙&C\ᰋ=B1$iq />9"Wwּ.F+.D uf6ă! &+BƞW"by2RYk*Ww~i;kvDiDEDw;1<XzWv ?O?;T.w - -T hAz0L` X!b l`8 4x/!/3ëum+ ab(ei7".b3bD{q4cY."enwM97>Mvm͢OWOg 3evcT=,^b][#춍rߖpVg)u/(] ] -O/*O/PyyUE?O(ŗby)AZЁ `VNp ` ,!AKq(k.jܡDu;%%];-KZs p R9.q68ܭO%L%J{#P]rB@!I9W& JyNBΕ ȹk-k/eV+ILA1D#3^*H4hdƐ߯KAιf a !Ɛd; -r0 -BA$K](oO1,f(*kZYgZƓNIq8z]x&= /-9Η|rA23NcQ6UYB5.orb/SSƐ]om~uPH -eΟ7Qⳙ̈w~&ooH[FDWy1PZ2FLC[m/!"UXTs]HٗbD7LfDd|~NȭiDdyDd&/{hiϜADZEDFADiDЁce*AZЁ `VNp ` ,!Ao,UTIDSES%I_-yc1 -2RJMʳQ^/8jTp/ ti;-]PL #Ie2kLDΑ"+e29g]"osEҘ"2TYd&9)"skUJ"LDtDޖuO DvZ&b%".!&3. 'bHMLH$%0 JcӓY&{rD: -6.YXP(*6Q#"]-u^}f3ݮ+5/ۛa;2.ܾd1DDrǸRDdv7"RyUGGDW?#"}X"2IPND$2""3r,"L%P5h@ :Ѓ`3X -`;8 n x? !x)X*GP& MѶ]_$ZɛfKkhfP o9Y& LmM - #r$rWD"IwZD>7E -9$m0Y+d~If)"G&""rD~."&M"?r}Ddlfe䟏I6əAD0 ˙oљ'eW|2˸Tڕ̺RҼqq)p)ښ[,gI0h_ -iEtwE—B7#-橱8`=]|m-Ĉ܃{"{DĈH=V=q豈2@ -Ԡ-@0 `+DC Ă '0 üxOWzN릉~7CĴHXĈfry{q -Z.'t&L-/> 2!I )DʥP$ȷP$ȷ%"/7Oh&ZD߽Yk(RDΓ[y+Ry2Yfo!"`)?YxOVi}  -? R߹o!K29,B]2QwSSX&ٕ.˰.NNoƥ{S#2L&C#"3ꥷi -C }6Xex/SG8kΣ\to"Rp"Ͷ-#O""C#HӈH\{=0iCDkw˙;UHSH雘D "%¾ce*AZЁ `VNp ` ,!AK)jT hq9S.C`,$nb.,Yĺk9"${cwb<S*xv+H`vDD)E!)"?_3"K;_fO3\5"˟/RDn"ri)"2D&5w].Og-OāI, "+H},"R `ərğbq""ҜL"3jIOOѮ0T_ZJ*xrfӫ+ZT(K7Q "ug٦x93n3̵FLOY=] -Kნ.z}Q1CDFG6jb*BD#"%HD㯈HQLHӘDg=#m@D "_tGce*AZЁ `VNp ` ,!A{F%IԈHR/$mI^d2(XA^*8 -$t9,0My r,8qRD4RDl"rLD"MY2]"Iiά`"bDvJ!RD 2uK?y_b98)EDG$3 ) D]z4" "Τ$aNu%08a]a61cR4OzZ)Og:3(GӔ56ywUonT|uvfiW7Jƃ<-fk#cJ|7#b+^pg薿Y9|g[S6V|Esῃyw$S-Vimyk.L @ Ђ`# B4@,ph^`B8 |)*.ZͪA:۬)3k.U_Pf6vM]f ,]fk٘4c^#̾j̞23SbV?@օ\ ]Ǥll(MQ+0HٸQ&+}c[N-2٘Z )ioe#5уЌ \8U/5|vw09)UnĈ,.ڦj iKܮ;З0 s -:]p94%rlbo~ED " H[HmH[Hˋ;#-ȃHp="RsGJ@jЀtf!bvp@ ~@BRxk $&A_d!9A̹Җd-0mv>Jg&t?B&D.JD25pRDמnj?wi޷fI?k̭%yߤ|*3Oe,`^S#8&! x:@| S^/):\P,D/μXڦ|)}fl +#=Sa~.>EvuD+bʶ{PcDdeDX|/"2`_!D$}#"#" HL"rA]5޲.{Tݺߖ@Dv`GV"^l΢SL"^Dd+v""3jIiǓb=2qI||fOKўizxk]KMT~VBv{D=)|:~)kω0=2־1$UQOܕVf"gB"`E`gG -*q93fl{3/'""yHnRDdq\<_&1|I&Wc.S @ Ђ`# B4@,ph^`B8 e xU rOn$I^v3LyI'YG{"erfV7 $1 ]9{g~+F`y?1ID)ZH`Zo=4˙voy:-E2.EJiLD=dxM]j.DD;W&yDǰf4& Au+I[7pdꢳΨLjIc]4`IwxWBìϜZ!5Gʖ/#"Uuoi&m~=)o#GLCX̳q+wpYbD籜i~omx}iM7#"Ua9SDxnCDai; AԄ,#"GD:>ު -T hAz0L` X!b l`8 4x/!/eTPT:"g8m Af5p7So>(Xx83%^CmYF]Dyf',:q9=RD"rLD~"E>)"D'C]t!ntqrL"J)";̅2DN)"D\OGle2N""Nx[f؉IRDD|?CrQ?LHϏϨ371-5ѕTMOt%dWKLI xOyo7֭E[~[Srp7D#luCnߨzhy]k\xh=<7Lk?72벘ߌyUg<{ÑqJ>+<] x؉l`~*lE9+'mdF%P5h@ :Ѓ`3X -`;8 n x? !ZeӪqB&[kӚZ\Uk-ѢZ{1uN.ҥyL۫e[sb<$<KŰH7@RZT ~bD֥vN.OxIWajؐw v?  KHyjJ2*d?}o>%;kk_F2>;DSNֲIr^L,o)G0wqKi]Qgj0w|vFKLLNpy)).&q,튧}T_j7>c4sC4.`}52#7/b2=egkf "x1;q{lRZV&%oah{sG=YCsGWBĭe⮐:0w>|le*AZЁ `VNp ` ,!AdֲLA)PsD(30t-\0; YIHxTg't/S&xg ;E$pO;$Ul"J4w|CfH*Siܑ.8C~{ezy^{(U|"&Fiȗ*bTdce*AZЁ `VNp ` ,!Advż -WM *?Zxc51eNOt' O#swTxHc\`s]9L)UD#S_JQJTs)NzYԡ7bOڠzA)"_ȨA<)"_rѺ1yEEIg~ "`9V/|@)V/!2ʃBϨSd_+5-=bԡ֜-"Hf&HuQGf!]^sD_pŐ{Ss=.{H"f[A{SwųňEDĈ44#"#KE|]6D$}1"Hw "2~,"L%P5h@ :Ѓ`3X -`;8 n x? !H QjIR/ ,A}`K2SN9WT <1"bD{?qg. " LN'IY(*(e {~볷/{uYʼ*󥴞"rLD.&n)"DuD6߲K>"#"|&+[ZDO}ȶ߅+(Id㌚D<^_]L7bT_Gђ#Ғ5{rDJJaR|)C]2MSye4C?6CS!cMsԅ, " u}ȑ~D|H"?)>ݏݱ/4"w/&Ww;>ED*ZnD?~!.S @ Ђ`# B4@,ph^`B8 |`zSu4BAWGxC1o,XDc{ZxG9/>ѝI0xaˉj`eJ˾:Dl\.s2|]"rvi&Ek?2{>")" 27E?HY)Beoك ]KRh$IDbL" -?r`.{.ׇΨ^6.Mwy|.&.E<8|rߕuC7E>S(N!"Gԭfh<Ud+r3 {CW"Ryf)DFZHmŘDaDE|nҘ0(&Dp"HHg"2҄L_pp};\(P4F0,`hX7p@vDfxeO 2D3ix];'XLLXɱ'%M 5GÄ^"ZToaKx' 4`7E[V,EkF_m QKDoiOU' 'x=r?ּ'r4o=vk'DrS݄̐oUGeaL? Rt$DqsDk,gJΨ;)L -s$4L"au%&ɾtǗzʝܭY]B+SCWRue˟t ]VOur`I8P4o1t*,9.!Dd>XպٖT/튓HU'"R Hw.&gň'1W,""M;#H"2xaHnU*P=&0 1 6<XCCU$r_"$mk$ICI -AŰaϋq$-.M㒽Zes -eWRY""CuWA@H;0F#υhŜ7IdWgͬ[S.N"](b'qoYxtY1t_]$R\/_'Cȡ}H#"R|,"L%P5h@ :Ѓ`3X -`;8 n x? ![6MITgкB4k1OT=Y'+;0t""YWَ "UO "o?#1")"R!*>,CD*߹?݉Դ "cHA DXDpJ@jЀtf!bvp@ ~@B=:^S340q7efbYrfR'2۫`9"<} L5$/$8w=bHlXUȚ%"GWen^-"GN:f$EIJ޲I{v-#"{iҡI "x0ȫui>H9^wG] ey~T47$`%3dMUYߝxQB9&Mt]j)a=kzv0O u9ƦWثr쌵ꊍnͫUTrUl;߶MߺQ;_ιw?YWx˟P0(Se*؂+{*ߨWjyRռۤ 9?|.P @ Ђ`# B4@,ph^`B8 ^JMef.VM6MM۴Q7a]l8l3-\lhYِVqf9l%{Fl٘^ϴm'^& 2qL>v|2ؽ. r ܐZ?>!|2C~zGz|Sw[Y+;яM9~2Y|0oﵠAڇǕ,JqɎ3j7ݗHT#qL"i L -cO9?&{˷C1o{Uu6M~}H;3ѵlחMm3tׅ5ӡ;~#\tBŧ-3WJ0Lc9Z !S`W2C[2?~]|0ӊ!zB< !`)9 .S @ Ђ`# B4@,ph^`B8 5dž t"ұ]t?"R=Ŕ> 9XqmQמ -<;"yon\(P4F0,`hX7p@`uhGxO3UD7$2.ƛacSS㇪毞{n&=DCu MVoHQD\ї>a7GLyYSWXje\txOD瞫ley[]biOr/$-3DD*""*/w1&N"՘D$ҕI+$T*P=&0 1 6<XC,KP5,m; /');8omW%I^:M!I4<[&KH`|2뙯"-8u@Ht2<4_Lpj'=Ik?*uR*2kT\hV,TmbanK@)*r7D]X\sfb¤%dzy]lr|ؕM 3i(L)LUadyYԘxtH3 343 !mqSDW?|!{OXDTb1/u),Uuv]9T[l/bwwǟ3KHٗ("n4뙎OPQvGDE ->_=u"m*T*P=&0 1 6<XCH="DK="ICD;Mݪ01 J,債'Q|뙌3)",F&S|"N*1>=FvE[ˊXP(6Q F#ꉲ M-FɈ.0x0dzFܭg J:b1{mj}ED$gYh|_=_/S7EDzl;mG -CDܹ/*4[}萡[ދ*AZЁ `VNp ` ,!A sj^}T Qa7M|Xfx8Hݹ<ys ;@; sg{3o$/6xk*~Lg^kRs|-PU"gd8orYA=ѬFС`Gyϣ]M`fEgsϾ'3@%P5h@ :Ѓ`3X -`;8 n x? !x)KaFF͜M[Qwp~x!n2pK>/\oVǑ?kkw|g^ |'"u9_&`&| 9|P| K]|O3qx9Ǎ?(XXϤ|p2L8NNuW²tꩇdnziy!BY_~3z M!mPDwGD6C3!cm6yI esS=Xd_.sOlYlW'aOc7/ "{ӘK>Bfzt2cx '/3Gg R6fYHI3-23̚ ^̚ ~]"Rl3ݔKr"Pq%"r9H֞,f6n›gRDҽ XƇLa!JIHMO&&ǥyN=yhkg[bDi۽*X*ȏܦ~"--k;0Ԇtﶘ''8kU]-1SbU-ӘP8+?@D걐oB&{9(<"R@)2 @ 2 yQ\""2y4f\(P4F0,`hX7p@@)iA5.s4s%IwP$cLuI:Rd]..d{ScLp'ۓlS&yK @SR\X 2h<˴k>Ǘ5n8ͻןdN9[kCkc]2[ʖ0S'nv7aqΜtxaH }!_.S!"3)"a/ˈ_a]Lɉ/-a=t,#23PJ*k7"ґQw gh - i;>z[# C?"71*xf7rѽ],_e|xCDvMTEDfBD"""";O!"#" "]+gZD -$jWJ&VA_E D|0DUR!X;bػ$qw@O*L `jY2W_sgX{!~-^&"/tNH1D&)"[k~ӺL"?yrM9fhI[VnADoX7f !Q$$EHr\B8Mvť]L -qp\"B2tL_]3]-(m*"Cݽ+CӌIdb@v}6"Ҁ3eƻǶ2@ -Ԡ-@0 `+DC Ă '0 /pj%V#:]$l Y -ystq39NӼWpNS3;N6|q(d"_s9sb%"5"s]"bn83RIE&"Wmkv>;sߘrg"t;oADޙfw!"w݀I9"9g=ƛ捏s1ɉ.ƋL8R49e{bDP(6Q#ݳwG4e!n鶈 !csS~ż -ģQ..L"_""%_nݿ~P|cj J "O`)"GgT*P=&0 1 6<XC,v'(j$I";$@2M,ÂLHa~Dp -)NNbj<}DuOYh(L1UdIGEQywtnn^~6koAn$m1!cB!8&ڄRJmB L(%BLC F|d&$'3}<[,房Q~DD$="&[F7E"[n\]gHQ~?".T"n}b4l $qM"=eAH$J>2&^G ynMEu=͕Ʀܨ>l) .[FF#!)=#ˮHx~OʜE~>q.9/d)!ƫk4uuA}:~^3N>ŎiD2:= T @"2*>HdV"yw)ʇDr~L@"˨DV%ϔȀ(hĂ8L , -\ <> `%D-X$*A+Üf  uXKۙNZt[{W -/xZ[#x>PH䥈DD$5""IMg%ڙlXyD~("3ߎH*̷7E"9t=ݻ?$ -ڙ W㧿p$~yM5t$%eX[as^NwXwu7M[Z5wKig1 lyGYAeW]V7j:_4z$s3PU Sމ4EdiGN8.qJdp(]sW7qːEHd EgVW - rJjZz`F @<03+p7@pn%Fyye2YY#* g.U$Y/cQ2$HDJz&+X탂#L\=%x[>|;s@`^[5wBmOB3-6Hu̿G=XCEٔ~êugZ)""g6鯰?? "̽19x3Y$#Iu:lin45f`3<)i FDrϟ,Կ  dۆQ * ~Rf4W~ΔӍ;P,gA)2քRm"q%X$LK"'_D)!J(EFE5=w6&^~3"9WVERPP 0X X8  $gKsy&8UeDi9]^y7C:8:g6[x{h]{sl-| E,x"F,ȣ<J"nE~/gz= 0""ɊH5Z5kS$ҚHlq0I MEfB"g,wf7=TC"Zs穗n@D;3iߤsfL92`;%չ);2:~dNf}Hd4$V *{FCY⾠vlHA_M7v39O764nHd*$F8U݆RI"'!HdM" 3tU4FE{zx0@@@@@ āx`f`V`n,? YV#ҥdf^%˻Ŭ@?LJ*81t z(@'އWE+wG*9|3??9PRP)HDH$>"#""MZv ;~ D^VGg7#ϬwwwP xwz[KH1D~+*`ם{Cx$DOוe&lLg͛IS - rJjZz`F @<03+p7@pC"!0/yEWsB^G4sD麉~W!j*>|tn\C{4셕=w}|`}3%"D$D "愼nK'>'2(ȮD.=Qcٔ_x* 4#2cڡH Qw -y*G%>H$cB"?轩&]IlÖr%A"$z3l˙Jf{6s!oo鱕J -;IH"$o+Vj1^~::ԖLt 7 şrƟrUD&~T,*XAKD!4ҍ͗TvB"=TĖ&HdtDLsӪDRPP 0X X8  O!rdD@W^P ! LJ{mgHG"O:\y[{ ꉿ -HkQVע.uS$2?;rDh"׮gEٜ!OwG;]{{?YE $9ct~D x$$LH˰Ox[31Ӗr92XoZҺ|悽m'V%;)*/f˗Ne)ƪQ|*V/tdiFړ!]oA_yGbDMtSlފB 麙?A"$J$?!>^[HmH$5Hnm\W - rJjZz`F @<03+p7@p>s 46W1r*DFvz0g*X]Q.R|H%rkԃˑ@[V]?!~sוޫKaneCHA - |$t%$ٛI"t'MY6N9m)I^-vxEDZ},i>-h>TtS>[]Ff왠snޠUθ,H%R1e:\< у -H j!b7$TKw>Lz>DH $_\>S - rJjZz`F @<03+p7@pΊH BfɊ|AYGh02]Dxs) _DS |yJD"OL}QD"ΈDG$E87E"?m-oȤ75ڸpS,RdU2ϜE:LU~+]ZdU@7E)ΔDƲHR].'͛LXDI?>kw/VPWoUvP7Pw>CWUݕf~ Ƕ[jvĕ\wknx!J!r)쿰ݳ.䙣D^$0VwHi?"DB揕JV_- 9P%P5-=0#q X؁x | 8@mҎm̈QVnW]۔TǍI4Ǎqa1l=Y*Y;:&&m&#l @϶Ð=DF)g7"h{n.=.]7g"sfD E<5Q) Ӑ5HX`v^mdJأsen;" ~+ac݂dq⦺zYg-3ݝns;HB-MxR=t̺{,̮lIDw2c^`H>R4 -*;NTVvdijKt4zz%cWb[vwvKw+Oi85HgL{p5ثA=}.m)3l zϔȀ(hĂ8L , -\ <> ~I"-rAv7Qy^]J4&~xʑ*As`&x{(\ӼxWjj޿L#x c X*1w< x6ZD"H{NC"]D"۔ӐLmG >6!RL͔evfC"?h_ )[B##;7ՄHRf2RiS - rJjZz`F @<03+p7@p)gxY,Uy"嵣}(%b,9GZܥg2s>1\xMmU"MD"1"ٵ)3ox;RwDH]Ex126E"DDà0Y2I$K|L7 sGA&%1{ IV%3S!DMqxli Ǔ^voAJH݂Dzq>f]ɖ?RԞ*OT=ogg49/s! H mD%'v&vރD:ˇ$ּ!scq-tǸ x.^˥}u{8q:&|o Wx/>%ϔȀ(hĂ8L , -\ <> j%aJu*v?Bs?DZ9cq.v37m3K9L9hgiYDz wyT"UGL ӷw}_F%HCͦ^)5tBڙv) 9P%P5-=0#q X؁x | 8@WC:t\`V :A9Mh;SI4Fu&+Cwq<^'8W>)xvF 1!=5["cf"֦H4Frm3l.:<&"ez*RQ$X͐OGd(ڙJe{!E%Ha?W{pL𛷢񞼩Ͱlב9YLqڼiiLgb2sffם-/Zi$dJm3Bكيʗʱ0'; Ã!1>|DkL@"-@"P<{|g"oC"tl)z&MFZ/@"mHIHdyH{^)` ȁ( -hA,X~A,sE -h$jZ͜9^;j8O+)L=o,/G;q@ D$"^ΈDvEa6E"%75Fݿ^j1W3Dt"H$N|y Z.{"]D_b6}7T!!H$rS]f:N6͖cfΤ47ɛA3VX'ۛ_xnm}IH=|h% TsB꾙,+AHH7A_Av?biH3LWvOB"t]ًLz&r[Dοvf}#o@"QHdɫD*OJ) 9P%P5-=0#q X؁x | 8@WgUF`ډg"LDu\PAt7)*vTs`MĞC+sa`񁊍V;D$D!"lD_$$D>l"̧QæTfKv0;Yr ~2L['ڙSA" 2s O s㦊WMKf:6W3$ؼ^ SlZںO?2:D:}n̖$[kT"!sY tu^rƂ\l]6$\$rxxY+A" Wb n3}oA" H/!jH.b{w]]m """"""bH)R2DjEDDDDX""CDMߏ{O?9y~_ɡOeӦMb<^kw7ݸk51%-7p] 6)%1_[cYS{C.@R.hnWmFI ޾+tu183ƾNuqhg\b5pXnG5刳ƤbSb[ Ɲ7V3o1b=6,:32Cu.?Vbʯ -掦^I k+jrwdg+FtNƶ*BUbi=bn L:K ux9Ux;1SLwP_{wk?B=/}/n5ea@@ T@ 4@ t@ AQl \> /#L6{"(xM#>戱o(&yS>1,u{V#Hu+g;x,|elڴUIpOt?Fu Su䪐?)ğ*ljTSVN=A(.!?E3&C-l졛4W&6t't7t/t?t0ӝ.׺V럶gw]w]5ܰ]?ʼn9999,~%|W]5w}>ى:twML"Yv"qw2m.~2Oy>1n3p 1[q:bn5ሶ:RlLb5%)69ΝNINHACT"]H[ u_)កu1]=I&ɯ~5|ʠ3з3vA"-mKHWE<4D EH$s -kD u>1–T =4$tmDF;. 0 ȁ( -hA8 -X ؁8 We]) a<(Zxe3hn@w1A%G%R˛jyXƈ؋8yW6O rD2E-J$KSLB"%%"/E"sBOHImm5tW.DY"ѢE.-r+-m}uHX1ܽΘJ`&ds6/cWcg/q|w7"ɱѱVjuFG; l|=uOH;{ߢəoajE#~;U-uہt~m;uu C 38cK->]UQy{/" D/,Qq(Ԟ]z(Ա4""y=SyH,w(ԟ "3^S@@@@ DHL ,X^~諔aH8/DDTzhrmfPA]<,G-R@LYEyEj9 -6\pFӑJ[)aeS벻NHvގى>*OCd[HH|;Mѹ̩߯/i&.QgC #՜q E=3"]gMuMH$$Rq+$R_HF>$RR{LC)QdHkq5ea@@ T@ 4@ t@ AQl \> `Ua"o%^BTe:[T(}`4LS4S1W (ݼェT ~$#JD&J^Q"׬EE\fh]$yT\Ϥ&#y5(JDyw-w$%R,&H$"!]sv."$ؙKK^I"D2駻=V[O}rLP$6%&!.duq%Z1+%p%:Jd {KoP̅Ȋme:!Ϣ,+ -Mt?4/B"g'߁D HýWE5?ݔ$6$4$2Ꙫ93HHC͞w 6Hd3e}H$r\"xM`@PP 0p"A03p`xIdH"|X ! -TD=k9m!U}o⍅4 -2b#6LNpVAy1;N|'3)%r(-XY \.ys ߬&rQ"DHȄ(D.*gtD.Dn1.$׮H\2QDnҧ>g.r\V!B٧DDjDWH_"{>E91r濮M;x`#I$:MHIpۭ8K&Zv=KvcOH۽; Qd/ -leރD4E^_9j@5z0n"MS6t y 1ԡɁDrD:?G)*fv\$2:M%$2V QDfD@"iz; dD -ަ̋Hk%ה9P%P5-=0# D0 ;p'pxp*DHa.NBNϫ2xu4!N78 olIDDxo)mӜ}wr":Ƴżgx[xk"k"D(D,D(D,"$ ~$i%GQ"_'J$\B"%OHD~. cJo|Ik!_H, ?2$RO}a=r M*;7T9NHLq&)H"hH$6eNLtE'E'O+g -wT~&H!/0MsH]ytE1Hx@U>AIdd13"LƗ $+^ M"G  \HiD^3: ,~ 4OB"cH%ה9P%P5-=0# D0 ;p'pxp*K0BX1AYCTD]k<ݞ%bblr{:9WtsJ^M7UB|i何E~$rDQJB"ɢDn%HHdnxoZM"ߵVbVV?XXJȽ WD -+8]"fHF$[3Tv@"!;2 ;D.W?#H\ZXeN֕lu1qVgjv;pRcRܧ74(miD=mi~lU@5qH&o~mOc@A_C˙V$aH˂DrYHcH ΁Dz HCtad͞Po$2q(3ïB"=%ה9P%P5-=0# D0 ;p'pxp""k"LP'qʎW -F=&y41TZPEy?sٹ⫰Ȟ eĸD#cuV6du9SR\qN{EKg>y;e G/fZ\"/w뗨_.D3Kt- -axcEC"">49wy5R5V{W}6w8 q;[y92P_w~OTB -C/ 0 ȁ( -hA8 -X ؁8 ,eyc&/5댊|Ǩ2j]Q4jg3Fأh5gbhoQftV]SFv4z -Ih3hf {E{<.ڣxk6ea5JG@&1|&` hoK؃]]5n#25 <{d2?xa4Kҧ>2{A~1MI3s\1Ih{LJ&$%YݮԤ=tf R5"˙FiNW+ -2+_OӌV-٠{2 d6?~uTsvSQ2H-mVKYhkȱC]yGCؚڛ=SG!SS KU"4ݲ) rJjZz`F"@$&``vN,/?UYa%XP *AUEÂhSw %8D3H.Ǜ*y\ "N9H\G{o'u 4J4D$,rhhE[35޼D^n:Y}0Q"%D!A~!J BѼ Yn3B"1 -;"LA"P\وԨ>EB"}OBz6TIuָ${2G՝YSn6.KDr^ɟ -|~H8RW=<LӔV{r#00g\x /80 ̌mj|`)c?$2$N;'ބDFh;HQH&LVnϱ7!޽D $C%H;{7D2Yc<3-JAԝ&GNAoT"T"sdlSQp,w/67/xK}?:I"Q$SxȊDk.|NYϫ6.C Yb5ъIѮKHQ#"x/$iH?zySs_3W4>1Pȣ[6Db.W+.gO;I)V=v8RY{}Dv=HLliq K -o goMWt+3nT5WԵ隦G DB"H&ǐHҶ~$Zzl2oC"C"e4$R$ҵ3$2$2$R -WC"CK) rJjZz`F"@$&``vN,/?':D$W!2%AOD7D#%պxHdƈ4ޒ!:7w8q -$T8I1JHzQ"I$FB"ׯD$D~}L`5Z~JI"QL"J \,JbQJHuHJ'*@"ߜK@" Pμ0$R;/}$2IcP $NH&%&9PθV6a8bR㢓aӎdC%RQ,L={7z|r&MQ_W۩"]]aү]|< C"y圱a.c$ HΫ &!#H>H$Jm*"Hcg\GzSHQg|B9|jPk"ѩN6n'Vg3&X1)v'rIزD"{+SU})]|bƯ, 2HWWhkk]NAp{{aLʙǑDNA"sۢz󷛺D*C"uUt?$R C"=-H"u<}HOP ={DFpLn#;"Wr!']tA^-qJJ7 ěGyK o%i!\&3{_N*9[B"1D-!XQ"1D-!uHl`"kδE5Eyξ.2?V)mOEk(RGE@Y]\~k,ra5a7Uo$RS56B=cMpVLJt쎸vwۦ?r=:"f9XdE2kza*Mt٩~̠Q3syAD>Xu#m7T? 1O҇Φg`AD֛=u"OEHH]$̦9P%P5-=0# D0 ;p'pxp*lc~N+ ^5ǫy͑+WJ3jL-}sdpbF}wծ6ȍY-rhoHX$r],)jRc%- AuD%!?KzXΡQ7[V;nDzX@= )$?B=sQU$ƓX;6$F[ؔkl\3:6κOmYG[1{O~ UꞌtMV_B@Wנ?bh{ZdD{EiH}Hd9$2yۡPG.=6StOO"EwMzD)Hd HdyDc20  -*:` D(`f`6`.ge t}.8Mq[@utu'iF69+16Pg,){>Dަ+mkinD!H>Ien<4.ݵL܏rSH~:؞k€(h ؀8 < |8@yD}<3ˇq^+9"n49"+B_*O)9›xsom}wt6ӎlD$ʙlQ"g٢DB϶g3$?:+l Ex+*AVDY(kUMD7\T"taK0U Bb$N,\ل<ĻH|I%rDԢDŖճ%$^LsɈ\M"](\XW]fs#DCDtHd ?6A"۽]>?sd\Qp6a۸%5n@"V6)81,J8mw3~yyMIdab Gŏ{oTMTMivj5B"sƮb.<H Hd}[TAvS]ȱFHd64A"CDƗDD{˓Owg<] -#tm^S@@@@ DHL ,X^~Y‚D>NلLu%6_^Xm )&2J8o#e/-'B%RA<]&N.gU-^DYѬD=yxWzSFYbȗID%!?(D.KJec,;c[%E9F{OWvg -g2q7;.1)$z]fGh;tDڳvL|x[ ceOٝU'4et -@[@WA??LkރDJ?}"Sۢn|)L_!qHd3%"Trx]-HvH $r$L ' 0 ȁ( -hA8 -X ؁8 =k^ aD'(Je傦Hݔ7L YaeHn9|D4 !38Lj. [@|''%&#> J%Gf9YQ"= 'V-gFNȟ$$8JD/!4Q"âDI[_d.͐CH"̣\>2!a/} -g|(g7DRb ,Lƹh6nL"Nu6,'krH'!ٟ)29;U{v|ү˺QA|C!ʙ}\HƧHa𪨶:DބD! Hdj B"@"Ð._!*H)Gx^S@@@@ DHL ,X^~s\"9D6!0$llywfW ^1Lme:JT"sx I,Ղ-؋GF,VZ;ώO#HP7El͡f_J2Q"Iqn\M"ߵRYȢ(g,,!up /=$rPjY7DF$r|g>Png9gie qT+1ΙJ9O@r(NV{a-:1blbaVjԩ4-l6Nt A}^i̟WqÙuꨂZ͇b-}1;j_\/پzOg+5#YVnk+  -$ rJjZz`F"@$&``vN,/?Uz̸2rlmrhmcks~y!kv31844Wm*m*fGfgf״-|1Dc(Ec4KL0~m̤|1/Y:)Ժ3_\T *c~ fDeԯuR]FO+4]t{^MkJLJqGzzhwnjً1փ1]9Y!we<]A䎂iҀQJBZ6Av0}B/osFu4m -2b##t/&vdƛF`ls}LsWO$}C?<"ߑ%b8o[.YqgAUOʴ Eh%r[,^m29\uDJ1f~itzS?1TCPccYWlLl5՞:S]V%:RΤ-\_;SIl-LH=]^jb$߯8Pe.Nd`AՈe!ΘY΅ Af]U_T m-C^˨H$2U%x͞Z6YD6A"hR@7t81yz`@PP 0p"A03p`xMd -#xEl#xuah/2 L&Zě&  8\<6<)s(<)sHkYkQ{)'en8)ETy.}]b?]}"k C'ݗe.* D:^yx7q8$rUڋa nÜ`u &蘸D{BBgo*D2)DU+jxNu=M~`@W]e7b'!)zdf[HdN5i9o@"%o BHd2k(e4N!!%/@"^Hdx%:">,7ZlUfxm&l0JC^L&1'{ЫKxW=akxOxGSg}*J\ DXE+!ۺHdLgvWm}vHH䗢D%/%ȮCzQ9;cq`E"R ,+xSsi*3>HH䂴 5(5:1֝&8SW5I8gJtBSB"#-ov|"k| rf ]r糀j=nI>fdyw!,9c.|0>$UQMHHU*ՐH$R1 TAfZB"!^Diɥx$Rۓ$rAHԂr: D20  -*:` D(`f`6`.) 1$a-tCPe,ASCDJS;h9H%/sw]u{A#Opv - NB|_9flZ?u}Lk"Rz)D%k 2{WH1LJ"t"M"+qHbIL7,ꖪDZ:]IqքۚlOt8ܩvGi]!Y;:~}+TV}:=ր0H]_A@uW@Vzޯ-}+k~ݐHc񛻇ikYnvaM[ZA"St;=)3=57wyA"H m-ke^S@@@@ DHL ,X^~R{+(^ϴćˏ -%A5kn,^8(~#UMs<)XjL}wUfS~5VD((D~.3Id$ͧٽEkv\/J5B_vk"rmH"mwH}zKՃ,sQ[H"${SH 1W,HGZXn;ɚLNFaCGB@wbe? -Z"y OV;^I{%Mq^uS5SS}ـ>b~k]!J8OBh9wt[TϽMML3ݝɥvB" { !ƇD^IΌ -Lֲ -Hde9P%P5-=0# D0 ;p'pxpT"!L; MD+3UQM-#JdyzH+])"nb&>B;cAeVN4 f7\fDXB"DXB""ǿI";vD(!Vk]SQ"Iuȕ)wD" 8t],u](ȽxS3[~gJc.@1 l$$Ħq)X7+ŚpZw̴]OEd se4 ox7M1XW >LWk`РϜ{![ʹ3%@"͙WEݳ#UKDJ w25 awy*Z!tb$X>LVhrG9@@@@ DHL ,X^~~)!9 a hCy/qCMTYkLeuDX&F8cZoݷ4Dvxi 3KV' i~ecY@Ufz4MU_8e φ -N,6d&7Eu@"5:=($R -$L ̿ ,eu҉ݙA"y@"UB"S'vg20  -*:` D(`f`6`.@_%Ȋ!xE$tzH#^X k!H'eȒ`j 62Ol}wg q -l)d + w+wYB"7m|.Jc~>8h9Dc2?Qا)$r١DFnPL -HvZIv'pBqŠqliMY(by,s?$7iId1]^q4M1ׯ,|95DZJڒ򝺦r>$g,υ\[:h3/B"هB]BmT"%=":=pgxNyɧщ}'g 0 ȁ( -hA8 -X ؁8 ,O&#S,yr;fV< j<=b.'~6B앂8;y4ax |(͢D #!uHm34n5to藐Ȝ(7E/!߉yS_񆈵;V$rҷf!6E+I$M<*\\$BZ]\qmwעjC8Ivk3:ꌋuX݉I1D3!6IvLoY^X]2tz_tE3~l@5P@nk;V~aH H\ЈfD>Utv>C"uO5D?iut!L>-< -,=-DrD ]20  -*:` D(`f`6`.@_e@u -t, Y#&A=ɎfN}oh4LQTS`.&Ybk9c8$% j_%|;sWl6Wl6H~ Vf棳* |"J5/1}gE"W+gDY2 ".9Z$21"jzfw\L ϫ47~7H" ]RLl3u[V;cuY{,aOH]Ǝrl>D6vV^3=.LSԾWPeM隡7HH^1 ,fmQMAl6Amk"9H!m{k]uC"9tMC<܇$]z<5ea@@ T@ 4@ t@ AQl \> u'".iªe(xU3Q |A[+}o( ƚvta5<[[>o&\)HcW.rlD%!//n%- קcLWm6{m38;($$Q"ISbM'2?KJn 6"B"m,sH $"+B9=$?K=ִ$p$8YN:ݱ(l'ƸSROk6+sG 2et> {կ T3隩;ulUGa3—C93I*' g!P LD@937H]zvf9 -@"]B"'.k€(h ؀8 < |8@d n! ԓ[6 è`_9;DLS9Odr{k"uN` UʙM%k&.Q"DE"W).>ew/3YL(A1h%Hƺt*w 1ʚȯdHݝ,H9^X m5c5KRݚJWUF[cYT7l=1:6N9map}"-s2u@"ygstuλicO|Рo1?k\H(8$2mQ NH`D!Ci#Hod靅Dߡ q5ea@@ T@ 4@ t@ AQl \> J=/$ +%~^1+[DPwqle>rc;M"tw^0e -A Lj8+CS<۽d}XբDZZB"uOߝcQQ"%"qߝ~55V.3jo;]"(gnyvOe~qys;)zwZ,0ߝ6T9ID)Vg\b51ΙhMIa]I;ٕt)/\ -ܾ)AIWIS9Toki -e5]ʙ!Y$QH$g$Rq7$Rtl[hvSM7$R$2$20$28 -ߠ!!w{A"oB"_@"S$5pb rJjZz`F"@$&``vN,/?i?.xY -a|Rx!xuєk'`>O0 ˙yl@L,:C5s ;EclZJ/gTOB5wea@@ T@ 4@ t@ AQl \> J׹5}n؂Z~D8Ww*SVkuUj}幆6[ caNKmP[*ԶJ\GYv٥s=-j⹾nI/#V4 2qz9kO 0Ư=_vS~5e#锁CH r( -yI<)Q RzL-%6G eX>^22XƒG;ҧ>vu_#wwˆ -IpR,udu:NkbRl5t1-.1A;2BdS3[[Cº?H++TU NT<G@7Zk}Q~3Nq  -)@|w[FUy`N633i&Ȱx !dҘYqw@;mH)RDD"ZbeASVDd+VTD2 RH mzl/ ^|y}9?;F>@ѾqS;GwkBqV@4mkvI;G1Ʌ -Hf` @@ Ă8, Xpx^~ KyB*#h*IW)$1t𿃶2L d#\5JSRTkRga\+c5Cȵ2Y,qǃWORi/q-a\'wl %rLܱeY* =݇;$2 :%ZVz>[g ⎽7!K6;ޝitm 'oKܶ4wfVfR]1nװcsaHdxa=x |6[_}l8sP[Sk4L5[L`n'Ԛa/d9H% $Rv\_DZD3[<hQ|8D:X>S*5-=0#03  HV`p'pxФ8@^$$"I_(%cd"NM%L)bS;IY:֝+yfDo-* ;rDְDVHDF\AtHY0Uo.=GZ6Lr+"{WD".?N!OWԸ]FzKqZF3l|b$HYYI+$R=ya(E~vU>f걃AxgP{ٺ#~ad,8e)G˅zD"M]/{dzmDC"]\HXGs )$OStٗ}#D\WEw4_ -g*@X:`&`1 āx` -.@?ET$&">&̐#bLu(D:ij),K}BzcDpV·z"ɷٽ?qTDE"k6\-XrRf7+,kd$Dv%rD~,B2wL¹6v1N _C"-^>斊DF~0feE3䌌,;#msff𶴤4ޖthv}K'B󳋇NB"}9fpP3=?xům*_7GMBLDv3klvZFm~,F$2a:)SHn)صS$ދH3H}}3\/ݬ P`hĀX$+8< |@@((eS/)lh%ݜ ݒqA2Ui$Gtۈ$앬OhWHq6 !OL_ -yHAaD\'3,#E"9%QWȒwv?,_&a&@X"?XHd293)D"ej+$rvHv3krWH䎏̭uqo aV3;99͹mD"$--=9ݖvܰ3%2j]V5R*~:k6;WOԌh˟ٯ/ -_1;O !d)B$r`xcL&K?2B,Ѝ# q=LDFx - NoʼtA"n3J,P 00 @<`vpx,U/1ݢUTz"hZ$mKyb24-$aNv{/$Ǥ"ӄ_=N|y}4,KX"q2Q%bXYU.DU%XEUM+T [$3;Au$.[([3[y-ĺk9`3Hgg>Y(0k5gZW4es$D4IIπO<;]W4<:-^Ro`B Y"wkN"yٺ1Xs0hdw(Ӂ:\_y}Gj6Ye3E,r!"tE*K#VOghQoH XdZE3J,P 00 @<`vpxHbQWR2yI'mg$4/D_jRM~1R!%K֦T{^*m )e5'񕒧YJ>i.wLw]Zd@DeHӿ(I78D2_ "fؗ%yE~:3&X]^gBCvP]~KD*z +E7#1tlgsf-ݝ贅si|RR⮹D>;#>v5C9ڪugA"Ew ]BП SE1MtQ$RZ  |Hwgȩ'z^":[G2}>(|0@ TjZz`F`fbA8N</?Sډ"mD5&D]L4D;GtD_LJբTӸhn)!tQ>{#YHi'D_E3b-,;;ؖE"߻Jo}W9oW#>d6|q/"Tݲ,}4]1 H>HHh;"d@ $#$P"i¬^IIOdf%&%ڸ&InGbbYwաH./3՟B"#9ىgmAMX=[wz_?S4t6>2>#bZ&yHM?B"32ːH6D"UH |"iHwVL]FxށD -ޠC%T( -@ 4@ t@ L b@, \> 4z 3%[E%DS-j{C{{DÐhlLPDh>sTxsE{I]ń/ 6K|5v/=\:jD\ȫGe]'Njvˤ3H䓈׺/}ZCoD"ѷ],i37}HKD!yS5#rۊOOJILO8>+`se%RnrqIuEjYυggS3뙙HUcw6;H%?y'[ׄHdj$Rg2"iA:iv$:,$2'7Y6āaHd $25E2Nw=T!*3Rҡn:GHD -%Ph A q X@;8 @\r?)0yDCTm&h.O -bMBtaz3thE$C"[D7˓ȯHgH࿬yHO dғHOge2iĤd}Eى7|jiD3g7sTG*hUPsm_gnb_pАb2G$R`/nD -*J?S2AE&!H.D" Ov 2S͟FCmD߇Dh8$R$ޢD -%Ph A q X@;8 @O)1eڻ-sfFжnA7 b/ h$r2H ul_EǴ\"_$zDoHgeҙ#JdCX"oGȆegHdt#x=23W4,+U,3$?oܜD~wQ"qȻ/B"St&爗 -lyyٽ yh%I$9K de:md3.[z =p8,93h}hlxo뙩QH$*z1-kvޭ j+rt]DF bFδHke &.$MgNѽtf'$OuW!c[S*5-=0#03  HV`p'px.T!!DK'% / X*z.)52G*$h'\;f$W+$Ogeus2;,2Kܵn,2 /D(V]"φ-5lE7F޸,ۿĹ"X\|4{(_cx&~"9c^tOF`ֶsȴiEudRN6gJ"o])@&H33Q[flĨ;U5MhٖYIV[3 g紆K3]m꘦qc\Éŏ=}K q nV}pi781vn33'co;EY!L@"ŃBmLnD "HEz:{ieim/GwC"uŐDw4 mb/ar%znGڡZF "=4(ȌH%GՂp4rM+>t%:Te:T8{",k#6-D\F]e{T^2m!φ%p22S%yyKHsH$]9N !?y?XH=ॿ"yg~LYD^[Q2;1#%ɖ.wGOJv`p)2Hy~h[Hl t #A_]APSP.<;>JMƅ(S>\T+=Iu_&K P%}r?݈DNC" Dm4uDhjH$w'" &9DjAb爺hR#S mD2ՋrڡZAK Ӣe0[V ,Ju:j'oq$DVH$uY$b~:T ۿgeZ{ݣ2Y,Y!n37g!n̍' l_ϼxk@`Vm#̚U+,HlI3)4(q8$wOlj -W}ܹ)2 AU6ϯ j] GMƉףL`g<4|f;7Y[k; h+,(]Bxާ^EzOѳ2GaVzk"L` @@ Ă8, Xpx^~ RD*"NQU&LV Dτ - 4-E6 b'\=]F>Fúk?s#oڷl+{饻v']]t]I*95z/q_]˱;˱+-r9+o&㇯]IKWG.w+6&n+ec];;_HJp`r#5&`ӒYGIc\4Ν %] U'wdݺ\$C|irSȪ |[XI@ZR~9Ctmuq@{,CTc4 3MED^Yt_.նlYeΞ"\cm`9Sᤌmmzᶈp_"_ /Elה%@ Ђ`# Q  p!<_eXL+@Pu).WЗ.$*yKT2#Xy[`+wzSHޛ#J[Ȗ-+:3-_rbY'^Zs)_<'ZvO*59's餟K ($,)JS:~|OH){gWwD*ElFlElGl Dl#f}lw}ڵe'/zw]22.s΋r̅;2G?3ůoqgI1g"S?+"I`)i`"ucW̱i`cg4"m2y6&mDdD8Cy2Y 24ڥҀY7-͕Q "3]iYoi| 9 "!"2roDD*?ADfy}wilCDFDZ "ZՈkʁHPT hAz0L`( Xvp 8H̓Q",Q= hE0u`$I,""EDF[^}Jp.6@"MZ]|V<%"9RDdRDrl"0I.E+a"۔&qD[X"C+"7JTa"_ S*TȿmJEnx3̊\l\mEEdEyB1_et?1Q'o 0E *⳩"C$غXp6W.|SL|}eV8"HT[lSK|S6LE.ٔd6H -"7Kk~?Ij|Qȟ\&"_ |ވADFxkhC3:{Y` ) -)I#g -DPL0)"""bMH_>="zf\>f=s\ RD>w7%"{n u732""D#2\nED}c,_t览Ȼl&D$s6E$qظXg<#)-sp.PNH=;_9L~xfSOg( -s2-T NvMcY6ZVǛz&'DIj_EDp?x(}뙚O1D=ޡܶ_Xu("P=jD5@$(@ *P=&0CD`;8n`x~W) v"+yE1Q -6^C4-B F0('yS>HD5 -Z"ywl t.xxDRDRD"R$ED&"ߕEH,LD~[DS}$nD&NDz)"0H륈L"'4;3"L"dy?]W}k{]\IXD_裟GA-&;jyU7悶Ϧb㒸XL &9ؠ;p񎄸 8OZJռY+"ocf3=+򆐺 M+{ʐ~hȺ9!40on[$2!&'[wX壘DǘD$2w zADdqHKHՓs'wVvʁHPT hAz0L`( Xvp 8ЯR"ʳDfA<&(lj&h[nˉaxeSdFFC -%b/'gDvLdxGD߄o>u) ")"IZD.""L"?F"^Dew"rHiOS>LD{ZDa"Dgr.gv}\r޽DJJODڏru˿b&G?EDn؉kaiN9"˥QtA֑IMc<1)Gdhgݯ҈8.mcJ#"g(ҕeOT CcG~L"9io.CD0]i|h)zɽDJDdwB޾;x&[1L""s!" F_S D5h@ :Ѓ`3DA4X -6\<|@~!ou2="2pVEb &';X.6%'ӿLINacRbNȱ{wv:1,gJ1 "c3%wf(2r UH_H_Un4,u#"3MXΜr]߶[ ]t"R*"R^ aY[$6"t'"3Y8|M90 -P -Ԡ-@0 `+NpX^+ED>'2BQLQ*&/QOtyϋDchv'Fs gq"[*zNx+E_+O0HKWD'r$0ɦ,g^ۭ>="iN"֋G{"uRDn"򑴜͆˙?HGQD&"{>$nFD&21&xzpD>)"ދI$rL" Ƚ?"≋s91)66)q;Rb\i#;[uC_oh3Cmr*ju*gʺRS3vi{.7COcέfSi<=EO՛--kl؋ڙJUW*s7IN+s=}V=t_4U* T5> =R!#Օ2_zr` AZЁ `! -V7?,u[sdVEVYdVnTlՖouo7l5[MYQGj--nOfVwV(Ӱ0{ʷ˷L JXGH?w)PF˘k؈=㭒ߵRox,I.0I.텼fCϽ._ϛΌǹXq2Y#kH}4QX~ޛ`Eq˘Xx#BgSD<1uqdGRJJ IHv%OHm;ɟɳnYBDJ^PPdhӵOt{!-wGMŕyDwnNnTﰎѽ!ziU]DDADCD>@D_EDfx[Di˘ՈkʁHPT hAz0L`( Xvp 8Яgxl'A«J:W!U%rbM4"yD\ې`5ȻyJt \#\ޟw?Dn"b"0KIR7%"rGUyjDvCf/Df)"&"7oJD.:f -co:~z4 yZ\3`.O4 -Dش`j 98i8WB'%#rx>eۘg Epp@VRДtk -Y}FCߋˌxD"2vKm;9?#"w""5HD$#jBDڟCD&mڋt|t^z>+俳z _S D5h@ :Ѓ`3DA4X -6\<|@`*HQQ'*UsiuDAb5inҽѺ$ښ}8'k^pd&r#7 "" s=bF)"[6PUmJD3&Eէ]]2H0I" -MŚ;=bmr&둵4!"ms/"ru~gIH)EtCꪳ)"q񬛋Oݓ<K8)lu%xb1).YW2hL퇈H!Eʚ'f).C[U5o=h48s7.!"Ȣk2k[;""|^ڽ&E:1dExGDxCDJ1 / "ÈHE&I_S D5h@ :Ѓ`3DA4X -6\<|@~"o"%'$yb1TKNp)1`L=ccu<{g'+]K2y&7 Eh9*z1n$C3p_v>z=hh|YD7MWcȱ햩HH}lxHqDEL"y9":?t#"kV'|M90 -P -Ԡ-@0 `+NpX^U<0-Bd>QT吠'riü`&'UW"R%Xa>KkF<7)x =$%$)"0JO(DMbg\ڽaI"0ypKߗ&7Cx @J _Y+zeÂ氠t~7MtcuT&q6 "&2|ݗֻ07B&"ߔ" )"||sS"2syvOaw- 3r)"_+ȥ|=LDܔKCݻMt17n)y ˙=3MOv"/xvDsV]IMusIi[R1$8'JMJM$''qҭ;V/Uw\!"3teS#&'Cꉏ34kk14UFñ:dƆ}X͹""C#" old޻ÚEΔ7=d("2:rD$&x{L "2 -"r)Da/"R|`5"r` AZЁ `! -V7? "JvD>Q9&F[d#Lb'EquADmQ -9* IV1 s"xw0"r0ڔ/o4l}vwS>"2-E&"H"bk6垙 8[˙ypA"}/ "qE%|S?se7=3~#,g~|$.iN$q$':c`LRLlJl0ldoG26f]DdGSi/d4̎b)AD:jx HߑaD Dg ɡ̔~(U"R8"ҐId Hwy.: ?<|M90 -P -Ԡ-@0 `+NpX^[gȳy=!^'r UxLX='@/ -b%FY,Jg 㞙#+x`FhDFKByʊ?TYO4 ښ쑐FCQ_dƱyX5o.$RMOCK=;-#sry>l-ADfkx+"!"smHv#9|M90 -P -Ԡ-@0 `+NpX^]Xm$E, QU9QMN \QE qNX}H.X-mسEgqu b]<"W(z[o?;5"a3URDn.D-E/RDLa"ޔ>|F˙46uJf -B{'$RUƛk1x;R|k}p"RWAޥxw#"Kl"y2.]FDdDN"{&"2Y|M90 -P -Ԡ-@0 `+NpX^U<0B(yWW -Ax7)OQʛrYX;"[p.W.Ns -S7Vr)"AiqKRDRDDMHx׋`Zfc5B{";3uDCD,7!"L*D,g "}O[BD:H/"r㬚Dbc]il#HG4\GJzXwlLޏ_x/Hɇ2yW0-/""=!EL26~-- i2 ]!^ i~7u "#/""""ls"4"'*gf' "ޏt%'vyjD~GD2OF_S D5h@ :Ѓ`3DA4X -6\<|@ -W#RrجA-DYHT˂zhv# 98ELUt4۬`$VO]"NU - ;sFRDpO_}Im|Nd7:'rD ~c6cOZXmDDj8梫룟K3=DQ|mgSDRcbQd#l }Bs+L/9OKl/UzcY´=Ȓ/VdXPlPeX3kf.g-X}ٰ 2Zg4pu -?rf}&wo[evpwxK^Kxwɛ#eH~+ɁHPT hAz0L`( Xvp 8Я2`wÆȜ 3eAUlP4mAW|h1[/0_l .]5 fg^୺d/N~))uR6qIl$IxN%a)ٸ-`~Fܴ^6zJW~ׇl\'eC&e-`felޔ!yk]t26OF܎lO2W6/\𽮳1GJL|,}j#)%&ƺXJ9ܹrQĢL^6(]R4e(j G34KfhG[!=Fps2cmo|7~٣}e;2fYzn0fW1{>N`hnqEٻ=vKղ_S D5h@ :Ѓ`3DA4X -6\<|@WRDԉߡuD(QYQ[*D}yV4V&&ft}VrIt$Evx -EnX?2ye-RDa"b"'E&"Mx/78^i+a"__Z|%_2{)7E/b.v-"t 0m^F.{PgI,`jn`jZJlN7ϛ _$WDji*gBJulCD?`:D=L\lDp=r"2~+̜|9(AjЀtfhl`' ,x/}D>Cz>XTv$ -zЗC -zO ML} =KDrteYnBo#%~ )"09 E$C&LDڔ{i?-"0yWD 3(>}4fzS&ÿjSݘD~2ߵ#"[8&'O>bI>`5=3~䄸q9ؤKMsr,&Ŝ +2O=cۘR:TMe(ZNWf[ghJ?>L2c63Kxs&}HJ2r,0TD^:kʁHPT hAz0L`( Xvp 8Я#GJT.|Q].jvIU}h!THruH {l]{Zd:k$+u"rN\+̟޷{4UV5[?n߮!LDord)"!>D_dhVE">2,gv&~g{1m9i)z2$R\R\前=㖻wr'2DD3K+žz0)tc!}~ѐ/ϛ2巕ȢGTv""3H =Sx(= "Lo@Dڞ}cewK1D]xYjD5@$(@ *P=&0CD`;8n`x~mqzYfբKf.,4"(zWN'R:hDD,v拞^e1^ -l)"7nd/ܸɐ_lJD'">Edz'C]'C}S3x/$dc|zdrcsq<"1ᣟ""W̷o`9s ˑgk11AwHbHHc1A6!96-!M: 3;_[yX]!"9Y!ECUpH]i(]{ɀ.pP̸o:ݏTf!"WZwuGDDDƊ zr1}H3-jeADADhD*hDN/CDHPT hAz0L`( Xvp 8HjDfWFkʈrTP u-T 2QFcHKi?DDʈuXe", n>LpmMuN]d]7[=ox3gT[r׫Ho?3FT?}& RæT*:N>ޡ|jT/qE@E}ST$8*,^&*:OJ8bb0ЊǪrz&.7f<|{(xw7z4x eã֐*0*TS*rկ("rz&t07p%UȆȮM_+ ?<} *h`ScQ>AT"2C/v7"Rxd>lFDd)7M[(AjЀtfhl`' ,x/_F LٺL"Ci%.C&16S#H7<} \{gxO@1$Zx+3?ȧ"0R)"_/mJD~ _F\k"bO{eMa^yoK"Rlmwsm_o"._@_Tu4cW#"{}S?syrg4O#"eMIKMOl &#!>H|+MryigL"; -GhD*d+)D$$NW=P^=ygHA6y /zh(.{^fϛjys!z3U\iiam[tWdn6߇L-}_I'd\-o!"CHLjH"8@|M90 -P -Ԡ-@0 `+NpX^Y\=SGEQQ^A蝻+Ox_ S<]9nF#RAy[`?c{g['Ko+MRD,a"rRD,ggDD%kW~G슜#튜U'Ud!̮H슔u[QNƖu-*ߞ"wQF49"}zNfCx rY L9$*D ACamuD?I %X%VΚ!d^fꉽGpA"=+&>#ĄoDJ +7%"}sg\Yyp?ji%3f6\)DNƞ0bŵGGe31.O 1̷"2o<{1I+9mI.Mu=ɱ1Ig<֝}++'dO1w?PT+TOԭK;2tBȌUi7W>8=ݶR{kA-"}}28OfnOfO!Jfwg"|MX,7f.4)"AJP4F0 ,` .p  >Cx wkDydQіl$)5D&[gƈ!?ؒh+Dzh'>H -=rlTwq%"%h&+2SHLWD$qS"[>꺧ޏN;;́ՃRD"D&)"7|M2#D:.v[ڥH \$"r^J~̤ߵD} Ϫ+M8ΑF{_1$..9jiξ年ylczfJCʚʀ吺$xDH)}LIDn->]zɣ7Tu""Of~l5"r` AZЁ `! -V7?Rr"q>WP4E3D]@4D;.fo('S%=:sE3yjلm3"z6Ua"t6Ua"۔#N"SM"7D*6SDhN[gvȟrOvCVpXh(AjЀtfhl`' ,x/w.gIA1+gU.Q<RMAbh!BTK3}Y6lł}\p\}[U -ܨ@O S#r['3 M=n]D=/mxHZR\)/*q"$r`/3_o_DnwcOO_gH0_gr&NS=tKHM8Rq1񩱮Tޙ1}!dU!BtmހnFC7=iV7͇0gu=QKoOfho^$Kf!"c'0,N~no H&n9("2ݰ|M90 -P -Ԡ-@0 `+NpX^^ȈӂLPf cFT AWvb"::\ DgzHdxH -1[$zΘD -'rwDO7V7MV70u'-O'I iLHB[etlG?r -00D܉I9xV]I86ٓ "q$܎TCYgL"]l^Y,~,~4,f(f^JW$R[R5Cs?]H@WxHQD7UDdW=oez$'8"Nߛ"R^43'x+Z;g""S4"W#)"AJP4F0 ,` .p  >Cx @J]"}{9.*rTT4"D;@tDߚhiN"4"lі#ڛ3[tMwN"/x&'R) -#IȗDdIKa"gS"/o3G/]]+,g6D>=>fhٜ3߆{=3?r#"BkX\ԏIՈT3jL"mD=gM,&$G`SIq.#>95IIg*ёJԠ-@0 `+ p x@BHBI=!im#ѕ0DתSݏI$"񎊾a? $P)KH!R!T 6{Rfޓ"_`Md;2U%xB%xoooDxLw׎Gۙn9<٨D_=3`" -t33ilxΓ#\Ů8SwJd!r !RCwg)wu}D1/76vM " -LQ6pLJd7D.|Q=ާ"gtą2: %D+7+D\>(@%2Jd$mg -"KDF>S AZЁ `V  >`? 2( }hmD }782MmLj$oup3_BeBwg&De;L"K,DqgY6%DX ڙ6|k̓w+?\+w?vVX,ܛ9D9l%4D}͵J$*}gߧ#V _Q|!?JjjVFfv&z~.%i.%˓ޙ^7bl":xL W={_[Q5U%G~@Sl.x[o8|augW^V$T\7d]WuG޼߿ N`PpOw+ |ۓR`7ޗ?_|u٩ s]ѳ@0 hAz0L` XvHDp\/C0@ :թoWW855Nm6ݐS_4t;Nmmvui[rҳNV6gӕ=[nc۷qN/p+mj'fjbqeK/ʖx_ޔq7xbneF42qX|,5۔ܻA#3u.]_;H1o5.qḍ"A+#19̄AvT5HO$c}{7ݏed$e&|ՐΒ}+E_̜X*_37KݭhV_BX7{1<~a!bl VN=`nyj+`>-_D#ӗFfnGggX0Gx -LL}1L#Ry`q@#37Gka Khdꆮ^+٧Ԡ-@0 `+ p x@BH&QuLb&z(U3FuSOGϪ Tsd钬# /9%zlx%_G*H1,H0/54%mpBD)s,!"_ k,D;ffݔ{aKfʃޗ9x1e1{Lc42)!ށ ފC !x7h>ܙTa 5x5ȥ\{"K7U mt'yg2OJ|\/LZ۲'FfSj0FL hN z~O7WMG#斗)Hu`zF^@#2Fhx V/t9/sL}-42E"yp'D""g\ | -P=&0`;$@"8 .p|~!AA @?ESPA/ZE`(uiD0Z+ښh#sȸ]E-3&@j1P&Gи[eb! BD!"7g.7м᳻ݽׅȏↈM&D> lJ%rO<ЃJdoBDXkdhu+BDT"L鯨D? Lnje2Uޓ3og'%g=|ZvJJvv:֒}όi]RVv1"DP4u"#S9ơao"sV /SU/*PTJd)<=G81"#D[~Nh>Jdz!9#Ҳ@,"DWCԠ-@0 `+ p x@BHj%2ET]%y9͓tW$}d薌 ͅeY2azk19J$=B풯2@anna?[շ=zXO -U3QHMGaݱ7#k3g¦ވjY[S`,BϢ)zjL-Et)Bw&P 9qHg"^kH -z7^| -P=&0`;$@"8 .p|~!AA }[T-LQr#~Q?% iIw!Q:KK:ɹ@\=DnIw|HWV/F/(e޽KLb)2KL6%E -ET[ -6ڟ?^woQ NeR2U2)M9Z]90Z->U*HxfGPd='oYF}).߱;ؔzeoygfzғP$'% Z"Ë{kϝEDCsj3FVj#grchh:>{##9Ƃtݓ2 ͵{3XeRXc)IߔdmplT6sYȯy$";M|%G~*]EYtUľ6Nhg_Q9]#DY@CSm0"DƛIesd8#,INLLّjű}cG_A)r.f3HrmaxD\P8a/#W)zoE"/ -v"tiax!2hhGC>J6zZ]44K+SBx!7"L0 hAz0L` XvHDp\/C0@~J#tj-єjkn$Jd*̃Ē/YRm 4Di)R<, -ɻ($pUIj7 ^Cd{3[d0[c!øfnݔ ^OVvӸU7g7>~U>f۱"ȯZx&E"D棟+a,"lvVfE?KʤΤyRlOv/=şurwFEtN/ߍ=є='"ʈa486uo?kZ}eT"%dlsyH9l=B}T"}ѥ24*z7"ç;ϔ"D"U!TjЀtfl`H8nX< !oU I˄?oj=dG#WT"M[BĶ!r6g~!w2HEHhgQ^J?"D\9|3Hf:eLNJS֭]ta@hfk ZGs gK,"\e>QxZ- :7|W4rj|t |Wz=A>v!Dx !ƒ'(T.ڙyHݟZO AZЁ `V  >`? wDD5eH+НjM5H -b铬m |9!DwDIaFn"fnݵwB>}wxDܜ1hopncU"T"adB$;"adB$ ȨL<{&hgw_Z%r"xf[m9BDžJKUtM!rȀvԨDlп,T";,9\RCp_Emt Q' -Uw.ŏs45Gr5/uGK9Ƌ/M5g#zF;%Fk}?"mgcg݋o{3v2*?k!r7;?<oyWIgZ"3/>S AZЁ `V  >`? шLjK`DuDL-PFiljVhvf@tqoe!z] -dV]qO,D{׳)!RWo0_6ڝh eB䡸> >!ϡ9SJ䕂DgwI|>{҉y)|v-]X"0*"uC7S䓲2v Dvjf'%z2wIO2)랛)+eݝѨL~J=̜UjJsJ}y8]D(ͳYgm -䩭 cĚ csڵzj񭾊ʭ@Vktq+_-+`]~*4l%FەܢԠ-@0 `+ p x@BDsB%T)45J-s) -CxBiZP9u^a[R<PJ7O[TuJX_R@*.E\9~&8X &e-0!g|`WO`<̩[6J mjKbA,1n]wkQw]]K;%F]E=/wտ][e}<$vS_@bcA$H 1w%Fbt ?=ޥpgK (-v̽Tcչ?xʍ1MciL#RJ#&B81fه;c{oZBt""""FADDDD8")"RHf́Oעk=('ړw̎||+ڵ3`{NYSS.2CIz/Y~ԃCAhn)2~x0 ٽ:{Ltl=錱'q1i+gM;临3:)ʦ㬎dӕNNINשT'323BBZߕ W2SZ1IWUTwSfhFӵt}Fp32c>.رwE4 l*J4TˢJ_YF^k h)z69Պ?ğO&0p 3B]]]TgH+d E׿k6N|+7kĉzv⻆7ooɉ։iiQwMwՊU+~׿gn|oO(lc38/s>'c-e]G+X^7s>g]|%";B"i.|lt5B"Th3&:qHHwu|$c `@QpGrݧZ s3uG}ހAa`]f\=™:*9H|AdGayD& qH~ -ɼDfV!ܵ! .HdJ^Hd'yaI(Qe ZA[)r21t<1-HDFj,9ĖM ZU޽H<;F6LB"{D|EB"Q"{D|EB"mHNH%rh3}̧[DʻѧD*8C\XǐHŲ"O4C"yHdyHc!ɛ8 -D*f §q/@"%=HH$R<$wB"AH$! $DH B"AH$! $DH B"AH$! $DH B"AH$! $DH B"AH$.bAH!tBP`_Pg A;&f~cq@"$R`&Q͂eF aQ"CS)xDΒmH| 4FHӶH$d$rf-Y(3E \ !kD!J \%2.! -r%r\-=$oC"De"r!f}eWD>:Dn$Ħhk+nuIl -kNGǤ$1)IΗDZ 1Ko@"9=gҕ+>UM0n}2C֒-zȧ- +0:;d.$rHdIddBD& :H DP8foDDi9^}iDD!Gw\"U, MD1LEe^FtDyc`* -o#QkR{'z,A=L<9DB%$FB"nQ"^1h$$-3WIsw%GQ"(k$rD%rXΜ/!E"xyvp†DkD:H"?F9I<)$R1ߺI1s|NHJfSP Ht|bIiN)4L%2,ܲi3$NWv='o -hn.Vt}FH"圩 DJb"r߸,28ќs?$@%2$R14D>DF[!C7 *HdnDj vDD`ڹh%ch[8]7C61q&*&I$Xzx$osz F3$$( p #J$DB"'FDL"/(gZE'!Q"9OB")"WȕN"y(gv!=Z'c)aB"/]O-pLP|IdIǧ&Ӓ;DdbORDFBDV& UH rOUPwd4eڱG|ާ!2T.A"+B"7]q8ќY tB"=4TB"wA"Hs2YHJd3d%$RP$RNS,6E Q -e^.hDFy}`uA˙%&`M -K`Inj!AeO"D%%JĶDbm)$-S+s(}EȪh/KX7EVE|Y"Tze56&\DlFu&,owPϜOn>~1{H+H١(抍EΤ8+'[c٤xGJRJli@iD&od/1,{w@7,ӧ -ԹMDYJx0J-0Z.H{HBN,b 9h^y)" wsH]!E>>E_E``^Xq5@@@@@8 ؀8 8@}"%L- -D9ʫz9u)&q0iSs#lVۛ#P4Tp;EVV-rEE<%Z< DoEgn*w}Db{KD%&!DQ"O HH*EQ$e/$ |{5foEЧH'>*DO!߷($;RRX:SV6:oIu9RbRcNH0`H$2^aY$CQJrQ2z#CS9ͫjraiSf̩L\XA$(ȶkHß ՏE YH?][<ihD&! &H$DrPJjZz`F`a DH`Ql \n^ J/%L+G+󈪅SL^;8}Gx'8Ӳ \E4pf޷Q;8=l'3y/{(D3D\"!3DB$$vDDҜ!L"ǾD(!E%$%(*HDtɰ1ߨ3]ߧ̵;.|ey0CzT17!.Iq%ŧ NdMv&x#5Z"wz}wxY&XdB"H"K+Z|!H䮀fl:]5LO 9..Twv3QDHYHdJiDV;Bgzk!Cz(rg! [ kB( -h p"D ;p'pxp*DMe>IPTe9ԣfVq2A?q7z&b>Jf%{'-IdwR-D.^1\.Jb ݖ$>M=$UD\Y%ҼjD.<(bH"w/SB"߇Dʿ$?|OD] |L $Sbq?5)LK&8bO۝Τd) yu=3{ĉХ2s>u;ҵn~3u<ąM4# -d/l,K4<T uC"(gFnC9OxAɽ :讞D^D>D ޗ!嗎K)  -*:`&Af,X>RX>tSs6^ȩxu ~7T4QDrZ-˜mp>,]M&r(Q"7E~(!DL"?E".9c+lDN#=ѯ6[DΒ>P X%$2/*$%kU$ӻ&B"A H_~ZD5H"yƝ=GqĤX5>cIK',u=棽Qm6 ]Yħz"Lݐmǧk[ o7f_D8H"YIdodZ*W zHdt -L[(g˩DfQB"ME/A"HK4m饍"m^S -@ T@ 4@ t@ L  -X ؁8 < |m噾.!AU%3]&C7o,&;O˙5<.DKo%Y;8BEz%R JDm&&/oDȗeI$QF"L"k׿kDْ(_QHHۢD~)JD!!/,ɖպ!]ED+D&2Q]<>E9̄DZ9G;ISRVď83:aM''[$39)::yJ<0W& cV&!3UV*zږVn>QaHgj)ª?D -f ʼnY(t3]}oHݐ8$ҳz=54>? ="xM9`@(P%P5-=0#00"@$0(`6`.7/{͂0Bh(xeZyD3kgx].7 ޘ#-g戹c}MIZ/|](O5~M5âDrBQ"oo{H$BI{7ofoVH-bز5lճm"^rQ6"E91|e>Ey>/.)rسg\TgocV EMraMIuIs\11'[+z_)[KF2=/"Od(:+'A9PfhLuFC;ݜi)fUov`1,Rd"EJL-YGsEo;>Bg -`'`Zd"kB( -h p"D ;p'pxp*0$4[Pe,BM. vA_("+3M0bb">JkY!zx*vGb{^"9шyTgΑv[sOz`3Z)JDVE䊍Z 'JD&JD+!eQ2){QNH%H7|DF"<)$p$}"$i'IꊋƧXX:SV?ONI9Y"շX^_YmY2sH2}B Mں#>\~@_Wf4>XYÙ򪸰O o.M4? C"% 2$2 HKnk8iyw$RГz>H3%הPP 0 D3`vN,p>c3 A9ërhym+"IbJ0"%%y>Lj]̈́C^"JDDT"D%Q0ֶDwzf=މksZ1t~w3D^ŶH&m_H'^ݹDDJC=ލuZ.k >&wJj၁#T"  {yH prOZPfhrV3˅>=}F4\=gZ&WDFn)E̍H4gnhY?$}߱c!!' elC׸KޅD&'!c!!7!kB( -h p"D ;p'pxp*D^@N>tWeQq^SFnחC1N*RbHSRp0*#l>qO|^"ABQ"%rD(yQ"gHHlK>MT"oŬjQ"׋%J$cr'D2D$Oo%7J9xʙ oz2'Ϟ``Vn ;ccx+>:SkjLMGGǺNHQ?x#_Z=@wrU. ]9G{B 25cm:' ̷Du^dJ[KOwQ8$؊ ʳ|'~+J''?;B( -h p"D ;p'pxp\tiC.rՈV{\mVՏh YZc֔1΅1:qmT͹smZ{sTֲ纇5B9cIĎzW˶j-h!a붥v_?Q_nb-\KSv k$bGhPs#c[eZz 1.n]\c 2g;xS; -cĎܹB\td/S3j7ۍ>ZFk7[FBE#Qf  KD׶>>2.vӽ<;=M,-O3*DpM 8qOlљebnyT]IH =]kŎT]C^A1v%bǷvH!ov#vC"LeX H՟ +wTǹqqIVgASSI8W=%NjQD* }L_!ɑ EJ14Mk*}怾hoD+9Sa%6e ~sdǝ6:ȬJAHd{1]xi(8niD^L?ATOgD=8d''"!BPT Ո4%t˂:*HJds b%F'8FBge ޱSC$$r(3%$oDE)!۞sO'l&NOZ?-ܿ*J~Q"r |u[$rMmL3߷3QmH-?#T.xSHd "dNbvTkfu9NZIi)11ɧtdN_Qv y|ӽ#H+ ROP罐Kv&o|hȆD [ɄD{*Dg&?>&9|Vt$D#[\9̦v_{ -Y}:nWRw[qL_!  -*:`&Af,X>)%(K9Q/r^WӮQXDL]t5!>j4$|޹ƻ ;ǻx""'"JD/!dQ"?%HH%yiVQ7d'Fb|ȸ(D Jd\FB"_/Yrژdg,G6bPB"mZƹA"Ч^fnCÙڃ$v?]~D)iv>şOI$g[rd}L$RQ)CYS?Pw}Y|;]OWd@?sP$҄r6M"oC"##'e+;߄D -nG~݆$Rt] -$RW$,#H"!Tݜ0<2+G9zhyav}?vԮٝ&jbk%&QJ]E-gN/p.~˓2_%/JB |}[$rn735I-D,XbM$-I$z3@"9H"3"}ÿDYD>E8x(x?u!\QIĉ_R~G|ՙJN5Օ'Ǹ2߹>>~I&e725>UOQ@=ݝ)~)];OP@?J[pGC[fi9ȼD ])])sG kB"U׸WDh93gH$aH3e;N"G|gZy(D5OԳDLSg Z8" a\$D3#۽dGj5&5-LuYchg+uDG'3]\Q*d򑥽K= E&$RSMU=CtmqO\o4T8QęƋ' 7 [Ytc#|+!?хَ'!ޔt>L HBw^S -@ T:`&Af,X>}˻xrU (gxtÜ~7T14B%2DVx 8xg/O!NbM/EIH+D~)J$LB"_0K"W%(gbEn)۶H[oL"I[ 6$K?$WYƬyId"Czk˜Q9\($OIMJFDzVgtj5>%?9l+Ξf?%g}u}'2yqHƀ"tec>U𑀺bIN.u C"ýE%Wjː])sWH[ QH,U5~=  C"tjgAt;nW!'!(jeE*ճ&7A[+:5iLsT"MT"=y !N&تÂcPp\ ^< ީq((%Vϒ gIHmHln3O&G57 [GD5]z53=EH"?ؐȕ t0|QX<)$k117 I$˼vg3^3GsZcvk 珍a1)zJd2`>| :P +KDZ;fڂ3.5_{Ff3?ʅCgeEEW&ǻ O !zWU{X%Hd?UHVo=κ-30 -Lg MLA"o^S -@ T@ 4@ t@ L  -X ؁8 < |x ' r*zI ڊ]Czm|NbJppxM!1=Vk"%D4z![9I"HG{㖧MD~ac.b o )HWYGB"?Ч^ƦDp RH:*g\pwku)V֟D\.qʭ eY>ˬһrW2Kʶ'|̷ROڧ [zHdWͅD9>;/7\,OC"*ݝɆDZij$R'zL!w$Hs7$2$R}rPJjZz`F`a DH`Ql \n^ `}wOpL7CZry:9hnx"1>&HDLk"}V)اy -]m.F ?; ʙZ.)ޕ޿?r4\4=m7 ,$r$]){G97CD^k"YH"k3AHdeDrPJjZz`F`a DH`Ql \n^ uLJ!|DQ+xUn#jN[ډ(1tc>|zk\T.o$$Lj]5xlrni3,gz,~?;Oֿ} yB<;H('ij3: uc}6b2!7$➦,w3>2%scH䉙$d3.9b](gIqV?MJubN@#fWd2 oA"kOӕs5>UI_@=Vd_x 觫Hg깟 zXHHȾDs{$3 5@"M|-1]L-ж{ G!vzvsI-srPJjZz`F`a DH`Ql \n^ 7nY& -!5MPM2t A+$&b!tw4 ~ Xr q&vXp$xVQ$l2,DbeD+!(D|WB"mHͳgm+Uwk$EBzfb˹r.s¶~7[zQYD #NC[E>|QJXO7n1k!, dI)IVttՙlMNK:R8{jR)fV-\1zd'2H>fd(J2OTOMdh_I> O %Ɍwrb.=?Dj,D!EZ`:=d#Y -Q /t"KOxamB5@@@@@8 ؀8 8@})"xr -u &*b$aTO뙙˪yD ~BG,]GwOJm|--R%ZĴeKin3׶XdoYL"Kn{ [ݽ{P=[1۞"MVV{=vr'o!HݟB"w?OQN2/efw=cOҕTO [ZŀhXx+8S.: *mY9Sh3mB" ݣCt6-}lmHD4Gݻ4؞kB( -h p"D ;p'pxp*M6B mX2I4A-t~P0d "4F%RDg批D -b6Ӿ -ZSD8{(lQ"_DdD~-3iId" o|wwqCwUw\t7D*6Hlyfe"6ft "D2֬[赙sr$KavbMKCXkr67E$ʊozυyT&oyeJ'~Z9GQʲE[?ܣߣ-˶r/cX͑>F?-;g~Q{52=dQ,ٺGeGe*sf>9-s͗U؅gdGdκQxxBU,)#SS.ъoB( -h p"D ;p'pxp*Kmn{hnE]9nSԃ59vm]7[l7666C!v(n5f WE]Jdvw9<8-9E nyι=:S>p4Oy [ -q[2w~fjF1/8w;@T(E9DOQc|gy3wT*sšq4($96wS㒢]Neο@o1K!5Zv|;)=e !m.li҂bYDȦDR/rH!҆[3zgf!9~''eR!@v[x9SJz?Drȉ;3rPJjZz`F`a DH`Ql \n^ `}B rZm"vbc.i%)Bwx [`!"9fwxOUw9f6[DTjOETj"v;5{cID&,fD&,f۲9m2S}$7$類HO˘y}:Yb2bsH\HQT)$zLjZ[ؘ$k?Hf e^\e}(s2{{>l" CY_SMStm^OW;/ t(s $R…u oU7\%ü7Nq1OHIvwl'x_~(}((}ݶHӬ\D^]>钙?J\2s(3lX%$-frL#o37A"q,0HdЧ(f -vKqw<0/dUgJRE1Cg8S8LN'EOYɼ@>w.$|>CQқ̼ϧHvAvҧ+h|VfDj zz]DrzfOB"+я <&2O/mm;x==L2N2@"OB"O8{9`@(P%P5-=0#00"@$0(`6`.7/Csy Y(WjW7 -&#$H$Hg33;!P -$'*"=!xyof9CB"n~({z?c[$RWy$qCm"ˢD^%rD%(s$$r$VzYǐȋH"_m$B"6A"|ЧK?1x1$5eOMI;)H$5.&9z7%rf2@z{>G{! EteuO5x@|tm>[W3 mHgZV iz#'Uh{?jl7UewD:ޛݥӽ!>Hd iD+K)  -*:`&Af,X>2y}q q"yjW9t D?Zyc3gm"ݴMd3Q% 3k%{ ~KBB"٢D%JD!!mHSgm.ufON9["m-rEc]?eKtEnn߰~ ,R{t6"L=1{d"O^W#e&YchӞtj)80=>2y^bY_3s=ʡ| tvO7p@_^n4}zfO y=$XQ(Rp X`Pf)xY|X}X#X%z,25#m!Oac!CwS?TxM9`@(P%P5-=0#00"@$0(`6`.7/n"/)!OPeVPPOM%6]/ }M/(ƛGHԌ`ilSVp' 'vS"wyD\"?-hE~-y񂯜feuxNy7ﮊV+Dfϭ~qwM+KJDm"7?D})DnrxzQĕfqXMOfO9;ӵx-Qdi>i"GEڟMWٙ2 PWl6PWefui{Oݴ9SbPԥ)D9C"\-yt9g^U=S%^2Q2~I} -~nHdalgcVgR,I?.owcbcbO0isFa3}v-SFԝ]q#qəH];q㐱o溾,F~#5˽AfYxe[9nh9,suV*c{oJp7&vPTl:S5w D> 4fGi~3uVra[Ai_9qW!$u?ѡf# *Bf{B}ya -5]T]")"l8)!bxKe8('q%tYoPEP3(-%"8RO7}3ux՗bV7]$D~-!$\静H7!hC"_+;31,cڋJFC+?\Do1s%x!ou$$%)踸TziMbcVW˕&IW^dd>b;ޫ:VSM+tm>]fG@pP)3Lqad&m5}#nO47 yD5t*$2Gwf&i{2vʙ;!co$DqU9y7t]b(DëWyMNQ ĸʛ:{UrH7Qͼe@5ų={7;E-!$Q"DΖHҶH?o7ȱT2RgﮗdmOKJw:k!W=qoe.2_7 Cz'I3eHdٽ$jOOMv?M&;x#r@fmK2^޹[p@1k*?4՟k}%HnaI!.6A"%ENT$w!bzhα{p=E=k@);-Hd^!G4Lu'PP 0 D3`vN,pwayzj(Wn/C`l/Q"y^L,=.z_!F-ESO&d-?f4[WݞڗeYS4!x!Q"_ȯDԈD~-pXu<$2@'#e r IdAH10--$Jy$O'#tA"5Ӌ — F:nvV^S -@ T@ 4@ t@ L  -X ؁8 < |W)srWuptj+uKʈ7)*5<+DK)$R˳՜{t/r[D8-˙^qѹr&O~$yrL[ucQ@\9_6M}X~;ogjEq5rD~N<)$´&͢";j -J;)NdMZ~{>e2bgL>|>#&r8(f(Z=4~ CO'3pz.X1StY -r$44B"7#߈$R,$t7HːHţ3tj!M"sH HdjDrP͝T̞sr,DƲ,!D{:϶{vJ{턨Njbe"""eY +RDDke-r+چsǫxr_/?yhX@"HV`v,\@n^" Q"i%q4!)gzh4!_ X%Cj%R,!yDb%n1@7hɽ@H=j|s%L^Zف$ű7r>s!O4`H?n4 +3.>)ڞOC"!6%U=Lږ 3C"W6 -xձqάb{iy)I"4^۫g( -h $$`6`N> R:|&K5cPRI*GuHI!d#4kmo/&4IdFrKQ=Aź͉ү. Id!o~D"; xE"GD>x%%FH佽 mg5[{zyMd| YH *.-GNv -))4=MLI fs$RcH%2k#SFVKKCT''Bu1W۾ߧ|5/a4O<'3=!*=Hӽ̞E;C+`34iy髣U߆Dn-t C7hZ>S@ T@ 4@ t@ L , $+;pp. 7/Ռe0|(+~aQ]B4~m_Q/EH>ySٰ+$i2b3'z?+(3ETzH_b=yHo]kr濣9-4lAן["g[$$b+;-g!WDB"VHD$rGy!eZH D҃#;`ӛ -^[P&_~Zfn.)J\SO4Յyٽ>!}0Ɉ}`e|nxwSRct]]~WRc&)AW\ӕ3owOJs3tÐ\ݪDrxJjZz`F`f` X ؁=_!csOds=C* 9NwҒ9g]<|nж-+i^h%B3UyʁNjꝐ8ߞ{ 鏽b4CDf>DP]"$2 A"DA"m-g( -h $$`6`N> 2ج/%L$)jBRK`u̯mtD"*o -S,ۙrblE>E-XY9w5zZ&ь6F{\DHDH^D"(H+s;YE"gf+YA yFog,N~?}'ui[c @"C2]>fe"mDoD2x>KOqp..砝tiL>-Y8goM+骭hg62/A"ܢhhS|ڧj+E] m޸E?n4 B%MբN𶡈Ilߔ mD CϽ:#jKZOŐHǧqdj*>S@ T@ 4@ t@ L , $+;pp. 7/ձ""L!)zTm5-}uDtg"~뤟."F}ĵɀJ~y0ΈDH4"gD"()]l*jۙ\@ QvT<{jvݺds읾aH?vP\)0_!xc~77(2+~Q |jJvFLDL-"lvPY9ʹ>ezdȁ k9PRlS6JGCBpv`O;77 '߄DMGP+d&l]x C"%ؒm6-8WE*C[C"CtI%.$R2}ک3nPP 00 HI -l  < |@#LRR I]N]o N25쭢3X%[S~vpm?p%O'V|wgJ|lHd&f;CE">t?Z,?:'$"ވDNFDF$r2J%\U+PxXVqJ.N`DOx^qD)"B"ꋪ fs\rFcA $g997%H;["[f3 2y΍L$2xHqyVT"K!uqeH3`I|4h9Ո&3D>@%R=)i|fԏ3wnG%ҼF%2wdBO}tct=HI5mg[>S@ T@ 4@ t@ L , $+;pp. 7/gՒ%f!nIuLRWK#>_&ZX ZDh%G_7H;'q%Q"݅gxGZ9#r2D.H$7"el]$VܼDgJ#J -315%屢.Q"gm..HB&2Dv(gH$rDy$JnH䶋joUt6udf H -r9B934J*A5O1Vrç{>>2):}-FCNHdih*yT4/7B"5!6%K7['jːȁHdlDvB"SHQT" Ch{y$~ -ilDԮU3@@@@A'`xpSJ%D@#+gfѯ F!01NML=#Hʳy!ܤo R"HDvD)H$+"ˢHH$+"ˢH]leSb(/`1WΜ9ʙץ(.U8OGl -e -oٔl.E'RG&ϱbrL+JH37@.G)n`1h*e߻fվ.V=Uj&Җg>v s AcO]wY~7q6!iUٛeUr SyBvai]H3nX=L,{^Fj{V2yrxJjZz`F`f` X ؁,r9f,rO "e,9 '?@ GaȎq\} {8J{C R9x%"~%Hj ϔPP 00 HI -l  < |@k$N@Z$S4K.MLu4>g_=B?r{%swDU1#"^.yu5v>K""njxMD"?uIM[c_U/u*iOlXGNį# &QDpBNHq>%udY99i+%sNpoP4-G*725SlAHQrǧZ~+^|"YޞmOzHgah㢩^4ѡ% lA"C"ǞDNiCV,D բkHH}$Rp*)  -*:`&`؀8 8xrȫ%+rfh i 6:ԵLd[u9g$._KХw-$rOHDh[ >Uc+2u~qq%HqڕÐS!-0dX牭Z=NF ",݇%O+|ɻ1vd%?غHm?}r$'"3"QڙO梏4"ەs91 t 7BנɢK/c+vP_UMMNvɼ?!:"yK< o)9L>ɵL)xS*}Ν!uHw4W;קOV GwɌDSyS@$Yhݔ4QٺGHmHdY~#T"DZh%R_L -{"ZަtO~:y} HY")($eDۙ&.MH2vHQZ_C3udcsUwDˉ(w%!6iDy*HE"9HE"D3H${\Ŀ,&DF$R|?"T"_3iv3$ҵel_:}-s=PQ ut|Mc{Xv'ڙ̐[I+&JpgK (-v̝\[0fYe11rҘ&1C8smuw^ mn"VDDH""2ưbE +""VX)"b}Zq_n{Ś̞r{}}f>J[M6u^z鞽I7$={i)*]{I{.~s3H8SR8Ǹ\.]'/M=Iʥ+Q늍gXN|=>vgB:⯝NJ8R{X̲OR;)))&99LajN_|Fj>V(tGwnU۝AućOdd҆q LuO+R>pXk(vDٷӚCaj~EaT8\M':أikJUTo'}Ua0ݞ02~_&<S T@ 4@ t@ L , D(  \ XQeT -D ;MtG>$ch:(K#%Fm.[h#JA܍"{xJWByėK#$D6mZya7(M'ho;{{OJ{Ω4ͧ~JIK3H3Jg~2K?Y""DSt8=kX-,ʆ ćm"\ʻ>zS_'Opw0;Ի~O'}O/}Ϡ8=&&^zWo)O9sOk*}WM-v('W.f\7mc,c_0_*Fzo?~~YDջx抉2˜>wHBj'&66v1)dHqŤb=IKg߮DFʂ3UoA"˙ u{E@_ix&`;4zlz9 o?G uE~ 4 툞guJd9$$r c?DzD=L-_2$2sa|OaO/o - *:`&`"AV`v.,xA('e, -᳼y͜'_ cM0ubY at -#vksJpeQ<7'xK_o$I"$$+I$LF".I"ŪDd$\tIo i-}}]$r$ׯkwV%rDW%=.ϔ{%D&_Dr:A^qy~W MVez^D*>D ѿ@"]'%TP 00 -D+;ppx x@J(JTYTD=E46*VbDF0 -\DzH$W[gHp .xZEn@։|$Dt2!D~$ID'#!WDI"߸UD2%,IyGJ"&$%w$bMwϷxD? #<2~*8HwziD>q~$g!ŷ sl+!>ź$R i)Ó$Ĥ.Ox}%/*Е̡nHd癪39-pPi B}0Z46՛M@)GԽrĻH"5]]Ӛ[ - ? UC"GD -DnDDYjIo#DDrj!,  U#QMH.q q*$oi!^ ,\m_`Gyͼ W_H$0I"HI$9D%he$rΆHdo~D2֒Bʻ>$#MҚHT|&#$Jg29oC$j-{'ȍH"уIfD&<![}S?s - <k#9J"8OJND=8Nf\}בF%Ҏ$RyוL $;@y<ɩj'5Ϯ ^ ;5&_DFyK>3A";JwZ Df: EHd$Rg&r+ʙlH$= H"cHH"O"L"xL%`@8P5-=0#03 @4'p7`p |Gij!tcD?M ĸ Xh9LH - -Dc(g3S-pwFu/'RI"Jzk"ߓ$\wM{"p+$w- jW5s$H1˔3/Hk")D2 Dʙ?}#_B"EЅ՗V%RaDz8VHr3ȳk3G^D&IJ,v\l -HZZ#)s;ٸ6.&.$+{JdjIs;36$1j{9C=\@"=2Qy!iNyARQGT$:TzutSNk~$=,҅ՅHHdizWt{YHqHO 9H$T"VJpjZz`F`f` Dh`6`Nn *DOU twfNЅD`&yTÑJ ]X&zb$NY* -D"agQzDou%_fM~I"II\$rDΕ wz3irSZ6J-Y$\ȓR=sEvIQIȮ;7"7.="ai>`H~ -)Oa/EE>Q}dGJ2q1\s'OƤΈ"YYΣ (۞?LTx&աnU&4lֵtM״MƅOSߓ1;;9ߋ\쏊= ֒7\[uΞǮre?z{م~rnp]v?*{|#? u+3) -h DH؀8  1 x@}-9ar:wd6K٬ol(llll92m˨7novov mvOmfi=ޮ;_RF %e4Icت2v(#Ii-2$e|!xHʸKa c| c($c$c'*dQ1./^n8\l1ϧC2'0/QG?3o`7;n;b, -c$%:8֕HHCIpŤ%ƞnw[YF=P-mgQ:8z(mj>5e껫wF|Zaκ4Um(^f?+ 5WGN<9qQ1tu.䎙N-v爪1A5-D;JtMD%rEHW@KTӮvZBlB!!6/'ODo%/o$I$\͒DTv\(IDnWȅDd$uwkwd;i&Qn;JeYf$fCbU?]x˨[xy^ uc.- w~{ 0WyF s)lHjll\RJl2xrKHKw$ŻRbӒ=1t5_*H=϶3VB -[2›wkGsѻ2wwC̦gJޒ_GTA"7_Q:[ D& E$D! {'!jH9 B"'%TP 00 -D+;ppx x@}\&L o#A='h6&0-ӾDs`ɢiKh&bĵȻGV$Nr[ 承i-##P$uQuDoZxY["5I2^F"$2+%Od$ ~(/cNV_<bgHCॐ`~ -H<-dLMIOKHOuxbllz#ٝrp1ԔԄ$t4}z* -Hvfq{5SUj)z4- Sݛ<06ŏM*̣yKVQ|pX4$wdGXD! W!hj.$2>4VN*=ގAHd ݆jH?%<0 hX@Q X ؁8>trCw(O oThfym>5 1b#)ޔ'4Rtі'ڈ{^` -q -i7 sܟ%O^J"H$_wf6BW@ѭ;A&<"e,OE$lgiC,ڏeTmv[EҺ\%=*XdctaϮOtAm!F3/YJb8G\-KOIq4W|Z;t vu%t|P-ng(fjn)C@@}0m-?0<47Mo-9 Han"ѽ5;34|H-B{f:>馽eaiDXdaEN"#HW?,Փc* -h DH؀8  <^~< `͍(Eڠ$u)LEM/ühl"MNzK[VqW.~%\_1Wyf(rddoX$RjdoXd{ QQdݾҕw}XF"aD$bqiQQI"F߈]3%bAy!W[îeۯDsLUػn 09wgC"cΪش8۝`b]$Φ{Ϩgv-el|PNz%U_0SU|"C]Ӄzf09[ Ca_XTO7c6ꙁ!u;]@"ӷ)R$2_܅H^;$0 |;wG{o`i(; gf©z L23egPϔ)C=Sz L23egPϔ)C=Sz L23egPϔ)C=Sz L23egPϔ)C=Sz LيD0a~D3"jMO XCLyZS(2ID[h_k+N<% KS_"˴!IDȿJQDІDѯŢ$>uD~+I3MI"KIMI"$᤿7v $ڼ~$ڟD]3Wk"a̗U\R;͓rɞDb)tC D%RP˻D2U3ҜAݱCGGИ]i6 vr~R^Gr&DEBC(gi-"ǞDJC"=/C"a#wE_ -,N*ܙ=YzޮimA9{?$2TL}R"xL%`@8P5-=0#03 @4'p7`p |G$3KTwMі]!їC.1Sh?%Hh  qWznL予P,|[-HD.[w{I"7H xtolP责29*&I;2ɔ$$H$sC\HU.ƞ~jɷB"磜^`ODj~`xygvŸo?$a=w#9!-ELHHNNDb=3ʴk|JId,JЧtLbz&i~1} {?CT0)hm3Ux|D^HmH"}o=| -$2b2$T#ʙx! l$ 7 ѓc* -h DH؀8  <^~< >JV^,ڈaE"D"$R,ZULI3+ePtW^&%(.$)g -%\#Ik2$r$H ȻiX/a{֒+U+Z+I72]_$wdܯI"lۆ,}-hD̛D.am-F -I䭐~goT?n0URPny HIc.YX\9BY+$RYjz:CXЌtsL}ahJ˙·yˑz>f -Id>ixvG⁝駩D>DzHխHSH ]X-G937ǻo6CKH"oB"/B" /u SD9&2bxQuMfA݈oJ4t4HLJS &B"Éb;&+m"mP!l,\ί4t%~I"2)YXe=o*vw+7ݼE.>"{d,dDk%,홒o"X 'H(E8f˿oE.1[u!3/TD=Qw6Y$995--w&!)1W#&š9̝*T,Y~)$S57^h hڂgE&2u mt-,0ѓ(2zEWi;L7y߇Ea%3%`ҧa{h{+[z^E:ay:hEOs0 hX@Q X ؁8>t YP ts~7^b9%DܕE,, -}w\܅;(xn1?zz^"ϭ{j&"ϭ{j ԭbe7k!RֳYPڽPVF"ݐw&#Dp2]m.f뮪S=tQD% -Q1 $/x!Ϫ2ؤ8֑Aa$G2HxwHC/T>7  *:`&`"AV`v.,xA5-+3%U-vtCc4ŜcZ*+Qzlmi95`uZfMmk߲)oɜS2Hq+d!CZ?>m=&g$zdq)=_(#r'U@)c\]T|8D h~`F~3uy,=)KJqx<!#tb#ޓrQu,v+%ogGBT |(?z!S ҟgi.?[#zi)s !d{vZ_BYx!$&BH{^K(e\g{ dݏR,B!mӬdc* -h DH؀8  <^~< `e{(KDD !FQF4ˢvFm%f\,Z -h)KKb!9^%:s]|-="wbe?BhW$ӮdXj"IEgWؽnCBD~'IBR'2@Y] drQ%H+$򆴽{W$Ҹc΁DGz$RB7*B$2[pVIēK֥:X7-bܞX$-=%99CYYxẏ -eۙ7 [ꉚ&/9+ C?`X|.hl2N,t(弥hrӷvLQ}SxYDއDnD&zDJD&h{8I1@@@"@$ -l܀/?=弲gTYלT@rc6S-o>4H6l#g) Gl6O纶ymmkHD-#$d$,I I<$oDT]W"]5o-\uZ{PE.)e~*YE.)emȱ>9cB gxO0L~4Λs&633[mX5/x6KT\|^X]L]Og|"́ZYsہ u$r$I %ȟݷDk\]I0fH}~g^ru5$X-$Ryw֑`c4TSoQ%(g3txڀf9G9r|P$4qW¼T[*h=aS3H"S.,iͩD^#M"H"z%(2!HC $Ro$DE^D)mDDfNJ *:`&`"AV`v.,xA&J+f%D.͘m#AA?D qZ01MH"b_ռI ok I"OI1{)I"3jWHڵ$iDB1˒$ZwYֆH烹̞t$kP|j5xؾ G?3NMdOȤ=:$$r[\#MŰ'=]"> $ЕǐȉjLuKͱک}gٔ|¼L4so=8y V!C"C/;f@/@=DC"]{}!Hdvz}9x  *:`&`"AV`v.,xAb(C!ݢE"C8BL}Ĝ/y/GxK  Dg$f%R[&3_^UF"_\T$rD.$$rD.,\o%a$kD,Ju˙ ]c$DnٽDlYHMc}S?s6$xIj{&.>..9ADnID0c@%B,-t7DVC:ۺApW6q6xW+x'G5$IF"{̹D.ߴ3nDRuO!n-I"ˬ$I$7]N"Ͳ7ퟪDvq sPe[cl (D!B撳j8bJ{i$O#)6s< I7w\C*929{Yd;SH2TMvf'A{wzk29C󻍝efS㓐L-o=GhC.ix $2J2/ҡs(gPz:Zu/T"H6>E2wtB"ͭ'%TP 00 -D+;ppx x@a"+ݻUQWYA;-貉~Z0,ZbR2JXy[P#U%6DFΌB"COC"uC"c* -h DH؀8  <^~< #'%%*CL^)EuG`"w1#Dsh$H˙9b=&چ{Ot$ DnF_!k;G'%D$x$H҆HoO'v|LDH$wnJȢLvCZV?1ڕ{B9\j˻FH6'$"^&H"7$bB"Ϫs31ɜ;JHw;X'ՑJpzҸd79c8b֮e_ЅL{Ơo SWґ  -ǑD:Z0yK|DGH"tLs;svZ MB"@"o@"oA" - Y*(zgijCHsd$2pĬ]JpjZz`F`f` Dh`6`NnH>QY 2bx6QՈQsleȌ$#\45cĒCDh%\bKGx1]"9BJ-'9Y;FݒD+% e$b$])\(#H -j-Szớd+I|$D$rL YX_wg΅DέDիLr$%DW~'D>8rf gΪf)lLr#8T$XHc96=&9y8[uwh_ݙۂ uf,w6n нCAcf ]X)-|D!H$uHG3F%D/.DDfmw5ouG3{ˏA"=/-a\OmwxL%`@8P5-=0#03 @4'p7`p |G §jaew栠t>D +D>ђExLDJ{\]Z`[Oo/ck$DnnL$o|5y;mew2+'֭g'֭g7exJ'c;~bj=DSFXQe W~ -s9gm2}jHj*~$$Ż[5)qŸSSbbΘ:kjOW3MG2UMkuP{p.o6C_pOp詠ql*hF<[="U"툞@=86E:"Y7sDR(@E=1w#LKB"'S T@ 4@ t@ L , D(  \ XQD-17Q+'E0юP0Мh\J4&'EKg",RNO,kl StWK"۟YLEos/{/[Bf{RHܺGxE=«<(srߒH#V!Y5.v2A YsLT_*'~Ʊm󌰿1쬲HLBzR;GwexW#sİl+h|*UJȱd?s⎠j uMXPUugkM "ct>ZXʝvj:σEF&!X^X}X{m9a!:8,RG4O "GNRP 00 -D+;ppx x@-SO;jLTMVo'a.)fB[-Jj9*pĻ(fyQqK3LH8tč9gu_1솯2An^$I_hCg6!LO޼k88.FχD* 6z5>)Ȑ?\v+^X>yV;å'$xų6%.%Ħ8c+9KI> oV^0iʹ7k»K/S zyO_y2{.z۫0;zxͭc6̨-"wyEd DF/suF[(~qyIg('rZ8E{jWtwFubȘ`,y_u6.ˉXp9,x:xKx_p~(DDbrC$RwκwάB^oh5kZwOJ!Mݐ;v+4@ -F)=j -`c.D KT?@"of(β~UO;%N8X.!ť6MKK?c7+k.ʙEJftTU g[GږLka%h9b6Y- |:R;s+vZDz d;H>JjVFK[;~ DzL1Hst2) $DBH A"!H$ $DBH A"!H$ $DBH A"!H$ $DBH A"!H$ T Czh6DtD?(E<1ՋrdY!-ekm g:љ-Za|zx}Չ$H$pJ$.#~C$RQ;Nd}zId$Y̶ YDd>t&hH=\3MH&?C.J)Kl+Syfg>X$;dt)$͕GCIɬ Y$5JTJ)W(?Δ"5KM3AmRnXhѠРjzFa> oiE=6%;s`A:N^KuuMljf"3up^oH[ͻ oE`ѺY—U.4Â6OU}'1i+E0HȐh#AEC5O3;C<"&lџ80(SŐfbUՍ<]DֲxiEK, - dH†d\Q{=Y^I..,2{Ym"+,y<ytv\ .'--QФqq3oniz"*Lv"yݪf #KGEg2ywMGxNaG,}9m{lGt'6Vئ泥?yY\,Er{ad,8 ds?m6{0-};}'4 c* -h DH؀8  <^~< >Jl~A5OԹDSm'o C8)&XjEEƈuB,!!=F)Y&,񎋾)O; -RQ'r\">:< -74뮊d&˭6dU$e4y8D?Y-h:&!#f4棟y"fq+ψoBZIpzmq)x#v''ĥ&Ğq7;4+>S(QД [2#4-Am~EP7?R0 5"Ϟ$2Ј$2B;V${IxD$2u IH{]Z}I$dc* -h DH؀8  <^~< `%eu"3L»Dհ !c~K4Le.Lj6!اsIpU,aljg(+oDngn$nH$fC$;ft6,DF~2fJI"[$|*D~'Id$Oe6$\y"ᏑD?TH䉋8MB93/>)ʙx 1D>PUI$%9ƕp:Xeɞ4;͝1hb麞яʹ+HXj umO@3fu=oeƶN 鿏#_Dߣgg^=\:\tC"5tiiwh߆$2AМ*>N6@jDz:cdc* -h DH؀8  <^~< >ʨ% -EuHԴm0LHĒO%H˙Fb-mM>(:[׌]wpÂ7W]ggh{Ɍ{wHd$H"HdƔ3ZZY"o#2Q"qEe%\zEe7$=#:((D0q"Q1QC3Qd3ۆPϘϪKgRcRXd.1#!u9R.IgGUt @LPj uǟg3uKg_ +FQϴ"yKS5Q,r#ڻ#xun yU"t{$=W웣'BH{(B7h -aWO5=p"xLk:;GDTMSUU5U("B\r{M<]:$9vfK6L1&8ĦB1& (F !!;a -s}jh9?q|s>0 9(@ *P=&80`8,>C]yZ+HSAd8)ۉj[8ю toXi?SE6b&sBpWax"}#\IK͟-Ek7[wRPuaKb)q!QkM-kCVj|"/!Dv=J~Rf"{AoS,7ѝlx](E\iL+ CX *A!0 <{WrA Gfh'x]џ 8ACd2oMv8yZ&# ;M|SH""b(#?!+2BӦH ܷ[_@YcbEucbşmCF({I!RHߝ=a;"w!Dk!:Bf|YZv~>W|,C*.d#=)Rѓ"ͼ m (0wBN99m)]g6$HC"bQǽ_[_e:])!b`g Fx#H'+OV)D| -#6Czw3w~zܬp>F{k&*c;d8I g" Lĕv'vNᶧ'qf'B$ۙ)?P1﬍sƱxOY,?h_EAOntZUc[Ӫ͈;߾eF f\jvfS[d5=D{5s~` NVDZ0^E#S~i.1A^S 4$1-3--=.B#lIk=,&l -?R-\H2AFȓ\U ?TNP.Ք4'C' y42U3Ɉ -oIv#i:頓'aUH^᭿Ff5He._ыtj#}G/δGnR`@rPT hAz0L q` Xp Xp|R̻Gvowy"Q#]9SFK(g,5HA<NX&t[D5ax/rQX|9ٔ侖6x/c?bbsfӐ3ޔ!iX]JDzѯSqcV&g'&n֝IڽN۞HvefzkVΌ] -ol@a@,ֆyAe!U@5_ -& g%Ü3?[F{.!T"t0b+gJʙst0q钁wHѝ̺"m#"WkJAJP4F0A,ā,`8 .` Я@ide(NQVU-Q_!^A[KtmD_.ڄ5,uꉣ8GkU_-Q+!%j%M dv&g鯾?B%ElgBD'1DݦVέT"Yu2־Շ!?sT{4; 3A ί My?冚HȢI̦x,eO$$6;Mt&DHJwq+3H'?7h>T.>R|=O\MAmݡn~G=Xz3uGLvPMeB^"e%zmIp B%wfC4"D*E' !DF^Y |M)0 9(@ *P=&80`8,>C7^1'xY/3rSr.NSix V"EJ;щDscsf#(|C@ CP /!rsk37U?۵ߞ٠Yw"ѩ?CSB.1DFTkSnQ3y!4BYq}7!D!DR"{c{i"|~]333PPqmɰ Ĥd{Z=^6+#!!=}݃?)9{WG%B޵ +/* *+~Rk4<-z)9euK p8HHŻGR-/Jdڙ"-zH NHhg.ri?("͗޽iAZЁ `BV!A@=/v󼲝W *i% -;뗈7i>@):bVGS4{c8+S\5)l"EWΪ"UV" -1D>'37GŦ۠Yw 'keYCCȕ!m1D>\"ޔ,BB`l_ BDٰZ$"]0q53t?L{clPw]4=͙DdvO+ɞz2=Yɬw@TG-KWoc^Cy!xuH]| O3y*-|'k7gG"9cO=g?c7;M5vf!J 'Fb\^3 'wG,A@R`@rPT hAz0L q` Xp Xp|w!D| &E}hj(&eb 4DN0l[pLq-lyW -1J Tw!r!w"rIM ;>{7hg"CUt\$"$|^<9kۈnE"~u:뽜 {s02/U:LLQ"g.Њ)r&;;;6%iW)3F1D3My("y!?i[ C!Y)~O?E<d)UpgK (-vX[1YƘ4f )4!Ƙ|Nws۽? [V{"bV"eb;DDDD{}h9˫-}^9911k>m6lٕ]׆I]2vWkv첇C׆.yzFZ5?'?e..DeU[_ft~u&I+ݎ_OIڞl؝JJl\4{Zj8ŞNdSIaG$te\>񿑝m91$-S^p!3-ʶjM꙽iZ>hG޲z6sȐslc_o2޻)vq1%\h/mm=?pL co9(s5EeֆtbOom̻?[k|hM2~<)-*ۀτO'AZЁ `! VXp ?@~MML&&e&UIj,nҎty&}&Cɸ{)dn!n!zxd39&6&gɕorWoMM&|~ٰ!hؼK9k1GOBc?Sjx*͉?ڠd+{ZONN={ό_Ŀ2+UW?u-U%&lɴlpl`c68c®k1 ޘ |La>˕m_|1_O|+?]cM+uí?<]W~O+N=fm?<]wuwՉU'~ב;'3W_pw_ʯM~̓f˻{XfˣNKحcn挻8&w9 -O3+/oӢ.He3BvWfj=ed \JZRF(3N;EwX:4oe2yMVn.,Uǟh~D[xo.Q~jCEɸPw裼yQ>6٥W%L=2vu/ls9~ (>z2upw{x\0Dwz57wÁ?_|&, >>PT hAz0L`XxH X` .pQ3Pe+Quk@3BDLj!ۈ'hn2,ko["@Q.$lSKė-|*" Fv1"+ (5DD.#{Z5"KD#7V-{%*X&"ඕ4*DE^TFNkzdc,ck^ȁfT䙯r#_TJ̣"̦~tZU$%JX{贻¡$;Ⱥ\ȕv\lzVdC|+ѷQ,es;U%-~@*2mة{̯y(byd@ExsWۙ잻Km[=-«Ho3*ғ4lEE,>~zO8*ׄgEEZQ'WS (@ *P=&0C,A<$`x~rE0E-Q6|A}0 h;RA?@ uXNLseQb)Xˉ8:5<p5%k"ր??UB"2"EbE+E|AUbE(Q/KEų?cjyտ]#'1"yEH7ObD^#{HoO&}N";W"r"r=ޕ?EDGw2(SDғRBl=9ɝiw3;)dw9.r2"w4" d򥛶2M!"eYʱwwZj͞,=Y*P0Tj2M "9my@L\-E-je"2هޅG1T>އQdfiZO<"2 "26TԞE15h@ :Ѓ`3BCX -6p Np8|HQdgAQHDUA3AͰ`('A' (REGĒGA[#q v8눫xo{W@>;ň!F.1"1zF.ߊ3rv]"/=~ް]#1"Ĉ,QHDl1"KbD9[""inD^M95"X|̽W{V"݋2/)"\)<ЄIjLB I\T֞:lzrFEEܙ'Gpy4"[޹,tu!啈,m"m맆"Mƺe2PT hAz0L`XxH X` .pQ&C`LP PT hAz0L`XxH X` .pQ0D1FD5NGh_ 4"TL̝FN"K]:!zP`yB[<[E|OEa1"1bD#r/KDdĈ|Y""%"ص"I$ZDw+FbDdbDfĈ&1"$.yqύ˙[1+[W"rJIPa9#Kc.33|yip(=%=5b]vW"=v&rlrFf8߳p4"2yW02EϽW+'ܩ<\Җ˭Mƹ:d}1>vID$z'"2=-vDcDd5Di "?A/c scm1,]8w7c.ܑ+⥶sG?x}nz|L90%@ Ђ`#  \<'.!̬@/jh݀ -й#W@[l&*Rઉgm"觯J\FYܑ(QBq];6IT2wXk]F+/'\F=K;Ĺüeq0KDu;T[Nȅ6ub8e|^L%'^Sg"eᤌ˞ 벧ev.9SSR\S"yˎ'iDv#"leADF;UM~"W-/ 17g\V6ec21,~m 5-CCDj""Om!#"9Xt?#"bp DދyxiDDJ9|L90%@ Ђ`#  \<T(QU 꾀fhg\7 -%blL%As.HHXk - S/w%ᢂgR|m Il I#Q""gI#Q""gKDk/^W!-+rXq+bE2Ċh$*2Wwt6LrITET$s'*èH2QT" ^ST䣨oaczZ]qefd'SӰz ٹT{fbFr89-s&r3fh;ˣ9o -z) K9NUo_}#v;;uG'jo7yOLs;ہ4߄Qde[`vKZ5@сVTIT$.3ZqT$T$ǔ -P -Ԡ-@0  q `+,8n^x EPU3QW -:tအH0VLM o O}[q ~, ΀xGoUbsmbE+VȝbEѮKEbZHwt_;ioI-{Vgu򭋮눈Ɉӻ28(#]aDׄ p<>)"ᔤpz(;Pݕ2$6 -\'G?g`(2(F2@D(dư}jMoED;VۃLt0LsE -xs|"FU 3wlADr""- H?0zfnDdc##"_)DDJEDfrDxD15h@ :Ѓ`3BCX -6p Np8|,osQ” $.-FG(FG%"?6~ƶ?Z'FbD-k-[[uȹYf̍ve_D>DY1W0ozO}>,gy沲 c>kڠ`N:v.ѝnOt2ٔS338ܵL>]} RSUQW4]}1Yةܯ?14zJf7N[8(%"#[kr_ޡ -IDD +Ķ3ňVĶ3%";oDxj|e9SV1 iH^SL"%_$V3՘DºQPzb C;LCa{*ʴ9SCLO:eoYkɎꗗ}(wEuG TM ,ein;#D("R(o~{HK^,~Nn9؅~Ua+81Ǹ)'";TEg19rb{90%@ Ђ`#  \<qyoY7QuU@t=}1LƖ<`D,Ӽup -8npSH5ЧUJDJH8xq9?A}񳮬޸ZE뙧QD/1Z(E~.iBb=c@Ev0[.X(TȾjT"߮ҟkxÄg.ϨuWVg2\$W=-9SHCO23OTP+~ԛ/ь_&:.=waAkL{FQkl3ơ_PRKɳVGoE蕱7g2W=C2wk]2nw=Gdɽ|K2?[d5h@ :Ѓ`3BCX -6p Np8|ЏEkcm:r|ʦ^آk -m->fʵ(dͲf-m-isE 7ӾścEla$Xa;X(|] -5c'^bc57Ā]sco]o ^+rƐC<[u,}n97Kclb-g|-ȓ* \f8#-SvWFR9t;JS qdd[2Sc i|3KNUa_4fiwuT=Lƙ=]2Ae1Nb-3ҶwnTb cH(2}2.OS+Ɛ;0,>Ib kc>PT hAz0L`XxH X` .p嫪D>IK-unIз,oq ZRA,uGUjgNV ݟ #q7WbD"F k5۲.QYk-3WWjvX˨ň+Q9W""?XKb-s."#,c䦕̯l*"Kc.V>W?I˜/?"ٝIDc3!.ӞºtwZvrU&{Gk[?H1|L߫4"DzwեM>2=ԍg頇4=țckFD""sJ(n9L#RNoL!"UG[HCX,CѵL+5Ӏt߉L> AZЁ `! VXp ?@jGdP ,Q >AU)Gf\!"ibDy4" "I,Z,؊-!]" W%x0t_ͧ#-1)F |QLD;/o׬6tnXJ<'V9O"";ňMyٹ.7mDdȮ̅H!{O#~һY,mœOM.=JvΝbHItg]\bdoܑstyiBDz`z&QVg)۩)?Ҵ6u7D7T5Id[B33GkC?'vb$@7ԽI=H.Ϟ6D/5CCDǔ -P -Ԡ-@0  q `+,8n^x p""݄*f[PEAMtDN Gc1sDJo,MZǘ -pK\5@*>OVbD|.WHa76|;V}bWwRD$wĈNwĈ9wyRyvL"Kt׏D䗹s G}K匳00>b(8;Leo6ݘD][2K-vf$SId("ӂIdaDk=m1iD@8;o8M>PT hAz0L`XxH X` .p9H@>`b(KUNAMtDJ >9#$RCw-*csN\p93}d'g>H5!DD~.3NO?Uw>Dž|1"J߭/8uDtr>ce X +DC|L90%@ Ђ`#  \<=>b A)vTy&fAE XLLFdFdXz&hM/8Wrłgx՞QI<9fD~)fk@u?ə]E={]yJDbD%"u~AɈ\grvL"4":D*̍흘D\t{6"2s=3"?;Ld 7gw˞Nv\IINΙ~$r/J^iֈLޱ< <Q*m_vd -[W#q){7o-{˙ڷm X4=H/>?:]zx FD*V[(i_IB|Y8qwS (@ *P=&0C,A<$`x~(AzwhP"(+U4D{ta0-i+t9S/Xk`[.8lj\p ܔY -zYe{?;sDD'qwFu~g\G>1"#2'F(F/1"F1"KDdb]qAb9Z3&W3Dq\襧戗8b~g|y97 "_>"ºR!g'']\sPRf)DrؑݻY+r23bID4SWhoh۩+ F H+t90on~.$1"R4-Hv<FD -/r^q?3 HK9z{=ڽCD"Rۅ9|L90%@ Ђ`#  \<{<|gJҀW-u1Ab.DFeyQ6;x,l]G7,/G_*4Sywoku(`]uȂň'ň'L"&Ԉ|&Ə ?h%"?"M<;>?$jDrZM"!+Mǖy=5Kgf8Pi䔻3 XHL>xVjy/KYQ߇-*hKfwZ"܇M'ebD>vɧUƷ%=2ׁшL?DJ3mwg[M -݄m9+7*x* M|z]BbD$bHDD%"O>k"YEdzHD$F&rp͓!Fb].\iD]25C<"Rۆ ^SsXdUe۳s)"\FRؙa ⴻ ;tuRC)Nk";^>M|`VfMz$QuDYvW,g"EuCMƾ0 GضzDzW%LWn>= d{rDw)穾SR㭣{' BD -ǔ -P -Ԡ-@0  q `+,8n^x Sȫ3PBO45ڈ>0 $2K7Unb-xG -b/hH.{$W%3R^.F5|]"]jDލY8)"kޝ׼;;"RJXX3F*k:_昋.0;ȷeU<8#Ddoi5$&)pbew ˙tzBQ3=KO=}o-ssW0s#t9Z2JzMxv~ CWWDA瀞I$ADy#P -ag3r Bt{(%b]) )d\2_aj7Ε**~]ÿmq'elՁzs}22w} 2nkZe; -^eo4HRe\"AZЁ `! VXp ?@~Loe0Aݬ)ݬ]ڬܬ?ж8olG>&6#-ymG7;ΩxWff!Ӹ;|Q)cJc-b>+Msg=wwjxeC$q[b?];}W~wTQJfpF?|,1ĝQ}^Sc~撳+y4˜9zZ3H 2RS\FJJG:SdgFRJMa%(]~lV27oe`(sߩ*WgwG4sC-{Z#LdxsN%bvB-t{sBC! !bixCH> !{lD#nX! CHci bWvU+QGhZ/"ꀱ:9>OW2Kw:MG%a;Q -\x~ޗ̟$#AHX%*rX k=>/{6f<5{Ҏ?Jx#Ib1"$߭KDJ!""C,c.2h8&AwCx.coe71c#̦iu9$53Ir]IiivMLLIK])SwgGwD'#2_TL "=Yʑ;U EWkei+_ܩD y=&㞎Ndjys=V237CDn~ y +ݬ"0 ADaD9""e_):D_tEfaD""QD$D("EDH""QD$D("EDH""QD$D("EDH""QD$D("EDH""ߖ_P*U4͗kS04ASZ+%Fy1JZ ʂfA0MLͪDL\ 3%">}c3_v9D|RD~/Gň!wň!DvFXvYhrȖ:Da;\xEHѬgvc%i,vs$nq9g*};)-'gf[v B#~V^S9۩CDE4 Yڂ~}g5M.i7O?W6҈ߖ0{vX#"zw"2"u Ǹ;k=-H"RF_D/[vȁ(AjЀtf8l7p/<'4wy ͈Qy%Q+ѠR0MFd3 V@l9\`{rU,nODw2l#rDDwwϕȹdkF9"/V~˼zZ:u٭*W"yHIg~QlTs1>|7?1weeWc=3tWfz8춧鋻ԴL{bJF8%Օ~iDuU ˇ~ \tU?RTz#diu'J0ӗET<ěGc1ݥHIvK>TduT T8*O+x8|zMwݭti/CExE15h@ :Ѓ`3BCX -6p Np8|HQd8 &LK@Q+sрhJ -^g MĘ0zB<]4o FbVgk5 F|5#rDE>yYYsٗĊ"gITKR/?c=qQ1"#r>qJDzq#2z*GQo {c;,0\D뙧㱞yw~7"r}SDB)JeS쮴d{ZȝKLOdl)wf:>E~f04}|@E̽1l٩(ьeikwr၈adl~ͣ|l.}nj -)*p>/=K -=,ҋ"7#"K@D.OADdDK4+FD ->|L90%@ Ђ`#  \<t!(*e=FEmiPG>(1="t=3 Ipd"L\%DABy|}myKl4F""^1"Xk%"R>c|Cj- -s>ٮ:+g/VdpW|]v>~K̖W8T$JEBjKln{ ҟbA]?si7yz_fӕr&)\݅*{sgؑPF(OE:sw5.?8SI-"Y#"~u]wD3 ܝgG 1*YǶ,"sG% ܻ2*2AmoDEvO"e"H}c2nzͭ(R6"=u%4L!)P=b ӕbáSF^~pf^&neFDz^Z*zy%%-#KW=1yd{Sfjx#|l^#39t{oU c-=t$=])D-@ [|gy_{s`936=t{("RK>|L90%@ Ђ`#  \<r0^^`zxE)Q D=kY^I0oMļޟ{gyKҀcgs?ZY\@Df:蛻FDɗ"NS (@ *P=&0C,A<$`x~߮]AS;rU.j˃zA?-l4L̍"Z)Xk`% i5/4V*ɞĈ<,FB0bDĈ\;`rg,bj{IUخ啈~Z{_JD^r=KmWʕ) ,gRa9ӕ+"R \'"?1pk<ϐ?݄̱LbJʹ'Nz7#ŞƱd631 -SO95SY s2w7EU~ufmW~n#&camTRǛ{j -D6D䞫r[!"%=H݈ADd| -ۻGi#zszu)%t{MP@ D0ua˙Y3 [q:,\]&7DV(rEOޠ% _HD$rZ}]UϲekoGH\Zȋ("W(.O4ƦU"(b=c,ߋEEL^S"_gAE.:^~NMt]"i g\N{ZJ8ٞ&.6933Q1wGb=]頛'泔7ghfY2~dex|l{*AOY=wl4s͊^DE&zj>)zHF9"\Nz"^?3T"r`@JP4F0b!!,`8' x >E|(,ů)<}rn^w0 yc91t=AϔրuNΑkwT5+WyrF&WĊ|E"gXHTu/ѭUޡƓZ6,r㙫lv *̲6݉K7+4_IQ/,?3&gVG)L.8wȞqTK ߒ8ΎS8E#st"Rj!oYtY[YVxYq}Zscة(4OhK6UsT{l9 kGƷ.7ӛ3o?|{g_2w9_ (@ *P=&0C,A<$`x~Qi(s4UhѕhmZaOkҚΡ2d j-Zkֶ[ԲE8Gv{n#7]h??K\'&1>#&cRLc1b2&%Wm>9C>IR?9CHCō!TD2C#8ke.򕯜@tH݋dT?a}X4-g.g|x-9N 3JoJKd&2t.%Ub{sd=ލQ*6]}n< -^SO+G}?DoJv}*EuP Ƃf*\WW^aXe0hΧ@ݘ٠e,hfv#Ytu EM2cʁ(AjЀtf8l7p/<i{栠XAnʼ;m\ n+(n~|Rh(ЍN{.Ot 82gJ<-1"ID"F bD.U6uK7Hs?.'Ŋ͵ޘ+r7So/ҫF G5ڕ!Lo(8梥x^@jQş"IQ+CEwTpzbmw.wC ʰ'.nh>|=Qc:K9NU*R^TNS7D m]2X5on(2={Toj}tcAzcH͛H[H*2*.O*rϨH1&QTDE15h@ :Ѓ`3BCX -6p Np8|@ SF;ݛhƈUЍ`!Bb:JJtMǰ@_;J\-BO6 觟AbQHXݚ@Ŋ(c@wN759FКR?y5/x].tKGdXf絈!(v804y?=UUFUEQ"E蠫(9Nod;:5rmӲS !!PB0^BcB0!a)%!6!p)!C!B8<~|yAB"[> 2&D>PnaCכnw&w=%goF5*]_T>2/Olgr?Dlݣ 5={t9g: '.#|L"7vX*i?I2OB"/A"0&- MlcgJMAHdp?O'vB"y]1 -*:`&`1  H.,AF줠e+Q]!HH3ICd~0d9!1s̚k#5 -3=(#H(Y]fD>%"vVQ"ڨ!D>LnoU\u+vhEh!"J dK(EEn$J(Ru/,rR"M~5,[anܼjIHJd3)\ -tOtړSČdof"MV[$݃K e|"TR Y<TOeigܣ~%o96>m2-D y>Y,E:vXVфsX<Ek`7iHBѪ"Mo"5Om -\E /6DrPP 00 X $ppp $!0$ Pnl JCV# aF0V\K3.PB#qtpU)[I֐t?7X _V䗲h_-K)^^&wkf]UDhoJX$Cbu)R};[w<0bGhK{㜟~Eay^X6T}8eOxX;- D3#31ٓto|}ZdZL!"2YVkRT 5Gں,]sAaXH5Ӟ\WvXJJE߂E[`+"#9t" (q_E&`f q:ݽ~UyL AY+. ꦐ&h]oBfT X"`k! 9ӂsZp5 ZdX'h\_fDiH j!"MwEu[K"Sۗk$nEEtG@uG2k?H $rdlin=xWм__cB ^tD4} TUGB8MTdJG†&l$4GytAWDLD(yoj"]YKT{L#E*D^2ҽ? M>ا9"SHdrHe80@@@@ Ă8`V` ܀? ,YH QV A!+D[JtU~c1]Jz. - 3Qr\=L\|łB5kenEQKHD.Tpk/OI܊|.JIQ"S4<)JdJ⁆̈D9̿B"-$rOʭ㣐HY-G ȷ&m2 ~7c UriD'%IɮDKK_KN_-݃37}*33"Q. -44]uP&Cb(O%rO 9&;g]V5Z5z=wWWDHE"Ƚ?i:OK\\Q(D$mQ"%T_|A">p0}J:sy[Nv}?ӯD}"DEHHq'z$w=-݃H$͓j3n6ٕu{ܫ%2{HdLd~"K5dP܉H|v]suP?l08 -ҙü)>lH6Mg~ɥ~D*ߥ+itٳH|SDrij!}~B"80@@@@ Ă8`V` ܀? (!|%l{U6u~HSքtS!}1,b鲪\ΆlUd3]!w6zM˯geDFLg,#[߈HHd$MFOgLV=\u 9"J"#b>3'Z= Eb֨QE>cܵ<'sE~}?P%B& $Mcj5ݝⴧvwr"gOMg]$/z95L჻ h3i a{TOE{5Yڡ? O5 E^+ym-BR:DnHs,tilEgaK4.E$H>tϩE߄E_E"-1 -*:`&`1  H.,AH:^>3D1+xU4㼶WU vb!S5o"%4#ҽ$T -,wVgؗ+ݟ-ˆh"W_y{֊+$hִUy@"QoEX8H<Ш%m}O#+Q:}-ф9En!,ƣAvx&x 5D$ŕƱivǙ$=͞bOJNpfxҮIh 6qML_b'h|U粪wXM}X7GXCmX44wb8azI;;͖ZDp_9.=/s>/su*sꔱU+ -;/2fY8Elo>):eddci;( P%P5-=0#03 X@p'p7`| x@N|:ݬWWuXںx]E?c1LZ]SGxkV0oq[%^;^.Y|-%0n?ػ{D Y(F'ħǍ⏛h!=,/%1ZQ;eB|~2?ee# -yhͼ-$xJm7w~PPf;31-)awr;151#㹶.;{wKfetf)_ܣx%9Cwgi?ܣZjL^ț((-{azl0AZ^PG# -eTեD!|Bz#BZO B*hS콁{ RT张j!MѶIA߁\F0Srz!24U5dl$a8Ctb.E}cTuQX QJX Z$ΰ.q8o^.s`-ĪxhE\6"F G<)Ds0nEEfEԧ`?`2oE*,&dr=H\SR\JzyMУQ ۙr:@,{T9}aMOYX]gUP_zؐxވ:>f12Z~O?}wHkt)]Yvj+4}y[X`+96ki0,R~"80@@@@ Ă8`V` ܀? Z޼{O ̀jfXLu"yS`.YSxk( JL>+/"\T|Z!E61HX䛢E61HXb_fZZyEj$VD-kfF7%,u)H"a)',ټ"Ϟ>X$.X$3l䙭]j˝D򒒄\&-#ÞII{f{[ -z/"ۙ?"-{fƟ -#{5gikٰ| ->b)"W.=Z_̾J!&D|)3y=ȕO*`CH:}ވӭO|1AdrPP 00 X $ppp ,UC)&rRPu?4m+P2Sli󂭘Э -BB– \vWG -e"Q"&Z$7@"uIyk"cVFج7jEh~z4zfqCFsp%In!1M?`Azsan1o(x\i\ιv7IC'=͕񸓮.]ܺTT]E.g){.Q͞ -·5yamYWÆ#&c,r8o>tg#ﰴU֜E>EZi,)A,2A,OcÈE:`}7kErާf#1 -*:`&`1  H.,Arȼ .B'"%^\3n8FLӂޫbIZAh,Rrs_ VU-W:꽈'j#܋tϬ6b$~"J$!(1IHĸ.9ϥD:g; Rlv+S1o_!]a[G貙 L}ڽ/JVQ"RwE"FfkI_!k"%^xX/{3_"̉| -TČA"~mLC[̆j$xX˝IwE\HR˙IJrexWKd~T>LL6]w%K[:s,rX3G7|PB0vdo$reyNK֎CHː\5$݃|e339Hџh>>_C#gґ"Mtw$2,SJjZz`F`fbA+Nn@zR"1爢gpohv\Ѝ a[Ctjmi Y[-BZ ) @@#iD4EB"NQ"Dns]$/m딽9VUŪZ+J] %R+J Z^q鱪; ZIg!H|?$3_ׂL4{R Y+5-ә38;-eONJɰɩNcӒ }pٳKUHg -`ZiHeiXZ}%kt{ C&cu[fU|($y.leN$8:$rV-@"\0;Hdx\t/]{B ǔ(hĀX, -l 8>< e"Ȕ~V G.]m3,"V6H-i9V' aY-Б"[sUoQ/򁆵D QZs_WZ5Yտq=h-ݶjE1MaVQ"G`]}?} $rsE"y߅D_\? -Z XyfG!;6D\Ng'-{23WHs̤t&g^S%r)oĻKfdLHf!K٣* -;4ge5U ÆgMKGL=<ܬ̣E.")i]e bVtѭwE i-DVU3yːX$2ߴ,SJjZz`F`fbA+Nn@;=GPtoUU} V^+W0 -fb3#T"EB VpV,ϴ01>0bQ"SDщ):Qd}Vʹ՚:OJܬ~&Zd4ڻȉ3egeK"/97ȍ+̍?3sKnX=~.K36ªԔ$QHMgfQjfa#/J061?Ton΍irdokm6|7 =k9k^k{Oeld쉐/Go+;! \h/wes쥥Z3PJjZz`F`fbA+Nn@zxyuf֪WZ5ډ--Yaqj:c5OX+Z9k1nuNmqƻوkulY/D??xS\uG-_nٷ> ~Lg՛%X$3qAR$rd(GHD`lM;kV&Ї߽1-Cʻ+?[3m@./pgK (-v̝\[XbeYb,&Y'!d1s׻{v¿M [F1" """VDX""2ĊU!2 )r:/I9y{^m۶;fo_ݻ/tCݰw=-3;'? ݰ{=#/k32ogϺ̽}YNݫ/_]{CW~Jr&%w9Y6ɹ _ONڕvH&۳v6)cO Bvo3p22 {+sF;!1%ۙ?6>h ]h'uadPTp3Ι M3a ˒_)yIfdG#UÖ?BgbR-^b1H{L`#&gc1mx8<0 (hāx, -lX^~A裴"qW)Jye'xMf^?zyc3.~[BX8k;okÜssl=㽍/a>Fm[{au$KjSbԹXLOJO*NA(d2?ʼn?ū{焵dXc~l`cybycbcbcq13͵wm~]Sv]cӆN;~Wq]O#~O+~O'~OiU+m:w6SAUeU_&|o=~\tؽx/$ct7lGZo [ks?3y!L7J,;rB"̤t+IJz1dcb( -h A  \> >J%&>We#E׎2_ 9c?o*!q*2Bm*]<{*w*!>*DdDn%;Q"J \/JwD~K$^pD2?*6Hڻ%kQ"%?D}D~,J$WNB"?5Fϖȗ!G!/\ȓ?D$!k#~rw[0~y#]1VVOq'SBYvw(mg3Iv+%d9=)IY3%Ru{JdIvEHd x y3n) kfs%uSa}}FC]2;9蝜yE$ trMP.;!g!CH!~q5B"E#,KH^_ɣHU$R $2[ LqJ"xL9`@,P%P5-=0#03 X؀8  G%<3A9 u 2^Wy2oLT"M"X{y X䝹U5s(W򁑏JwD>%J((OJH$FJ'%$%yOC"?H"=kzD޹~]%H"?%D~&!l&?KH$D6A"ՐdSU^׫!6A"T J䘫_Di,{Vz$dCP$1ѝr&%f@% -L|D_QL' kFr4Kofk oG9sh(>$2PƙrQB"Ð(H.K!!уijHSHʙg!R$yHtDZ!P*grXJjZz`F`fA+p`xA8f -^+'yU14-D늈7tFTD˙FᭃmwjUógV1_HHH(bQ"!Q"_GHHȗ$$-g/?$? w8-{ w{~c>C%hB9msOoD*g2݉lz'MO@"&g!xP+=9S" s#`zC"9WU'jr4 gku+Ï yB"՜Id$$<L=3ah^HyHHdOH M" DF 9$ܷ ;H$0$RS$R]s*1@@@@ ă`V`.@^4𱫜(kyU#n"&^ -~7_eMǮDFi9YxUA;r\Ŗ&*~4*Jd(D%HHQ";E0aK$W)%17n$w% Q"KD>#J$FB"D|FϖȾ[!U??["N3Un;ۧi$RWHCH"OA"E~Hf$*x=Yi1MJL3=3%2U#!["o)HD ,^N-,Z yp"ɢE a"R"ɓ\WG1(Ev"VX~)7_E.[EΫz&)rv*a3aPꙔ,WrFNtiZɏ}E39ʕXX]ygX3.>ԕ%%FCKg+9 <])~)lߙA=Q>4z' -,"ExYXdnXdz+ES @ T@ 4@ t@ L @/(Ҽ/$\#Hğy^I$tB'bg]!T6Ivכ]3%2O/<%[`6bVRmlw5tշG߹9C;PμdZz!ɼpߢ,H߾UOKe~̺H6{Q89_\#w92z?GgޑzFߕ;We\2rXJjZz`F`fA+p`xA+q\5'婞6;.rH7oEԉIH%b~նsl$k JD0byB!ȯEvFPeZchG1/ "b$Rk[o+$V˻zvxyo-X7'%e5De5D"M$Iw}PDNKHFQ"'E%$rH|͙}ȿq2;3wnD_2_I.$r뻐H"ȁ_@"N C"I;V3BYDH$˛nOs% o'%9].Y]D~)˪]{ųcєC"AtX?~P@i]3;LAH!HdoH"H"Ň #$rie$!.ZΔ> @"M7grXJjZz`F`fA+p`xAHL4U>/0$6/UQ*(GTPOM+Ѯ$5fcLy,z^I$Jq%ٓҼ4dLwݙt;1=)=fHגL>>9GHv&GVr">V7-G3v:+x"}hh{Yq3-߅reHd\0_~$ tu^(µÖD:"H^_Y$WT4A" H![Nw/_'  -*:`&`q $ p'px@p(gHlQT^5«{xM-"^ʉ1IJXjSV)8Zx l1$kB;k"yDD.gE(D\(!nDF1%MH?trf\%[RHI$8D[ؘu|@"/2_kO?Edu꽗 sɧD^;3˙iOqH"){JV:kOOtefBN6tDn]ZȊL~I*7(rAU剰≰f9cA]{FC2cS#gy3MC"隄(ʙnHxDG9ы$2uʙ|3}7ռ ԞDޥ]ﴜHVNI)  -*:`&`q $ p'px@p( |0Bl@ȼZGMc>U&+R uۍ©CH`ZR Y:F&S#xs5^j$ E\%Ji%r(/JHuK$vm"'gΐȯ$$R%JSVH(%$r햔3[˙!5kDx/c96hiMѡ2WC#|y|HFrRv6͝$&CiYn72::w.EL՛tM$hDDVNhD -:zza)3pRZݙI}D*Z!wDDZio!'Djf!6*$r9ڰ0$2vJ"xL9`@,P%P5-=0#03 X؀8  G)'%#+(U7Q -(ѮCDjq7-^6k!XxkD G!qf&;I|]R, DT]"_ߴuK$Rsup.ICD.Q΄D(gB["sQݙ/9!PȍWQO!ד̕n$H$ZI%!{+LO{+39uLLt޺{*{2y[fa'-QUM/adUvhh})L u3&&C)aId97HYHIPH M"wA"-H" })Hdf9L%BHKH$ǔPP 008X 8 < | 8@:O%|0EB4QTU1Q7M]7W ΛVDT"ՂX+w v:&cgxݙV WDbEDbD"WUFy8*J"$*JU%$%x㾻G :Z .3!RHiB$$rNje8$Jvi46ɋ8{==3+3MJLKb:7v-f>隄DJr'VFs4dku a}iPۧeƉ8S}yGD^ڙS2''Hd iBGY]tauIŽ7!N'r쏐HףH!HDrXJjZz`F`fA+p`xAة$-ȧOmE3QQ/Iu~C8]X] Xʈ:戳wfEWK}$0qΨ6]$D~%s7kKk/{E޻JgK)(zZBk(hdwcH2VVT-ԇ%guu㇍1D{aCyjx6Lh/eyYh"E*P`haXH>S轰H` -Xd)^<0 (hāx, -lX^~A2zQ7 [+"D̫򈺄hjxm+!2')BEj(R[׺xg9qldrwQ1Fl-"ȧE6|zK,͉ ϱ= WVWĢȊ(D~!JD>+!_lI=sW#ҍ"#QVwH|q"OE˂Cs_@y_ΫSi!Oșe;"gOtC*Yg+}/kxWޑ{V3y^9leɓAUkX]MA3a}}C]2c_5gZ3C"іUHd{'kxW i=(|oaH$H$ "A"-[VrXJjZz`F`fA+p`xAXKEP`FH((jW#&o ~b%1ZzXxk`  7HyHbeaQ"WVȕDb%$,|"{6glkĢ⢈_ȥ%%rDrD"IreIH+Hu-H"D>$rCC?M <%H>yL=.;aOK۽'ə.w7,vw kLt`S -$:8leSWPtX]Ofkk'N${8S $RN$Hd ŷF!BAqVFٻ!Cs$~HDF)ZQ{J"xL9`@,P%P5-=0#03 X؀8  (RA1$vS4e'QY$AK'D*i"abm}-Fxv*5 - }T"U%r٦3V/%r٦3V?%MПc{&g#WkzDNIM>'fRDntZz~sKC"5ٿyS~)HB:9Hy%L/Ngde%Yo׎&ɞI g3s顷u[Y yLc+V튅7×"9K vԄ][ॺFӆ0v?yiRsbY\o4ve76J2sAr;*=w<'Y* ɂTȸƁ;e.Z3PP 008X 8 < | 8@ڊY>hfͱy)ZʩKUfY3tRݪY?e6fӂXXovm˗:/u]f외[zo1Z/<aQ=xPC%H ?H (%e[uyJAYz@ם5[<4c<@>soO? 0y$osLyaPryuh& ev7,ag3PbӞp;t6-qhhleLId32\VT.d+ڂ氺'~a11wqj\R5rk@fDU:,ڀrV!tC3Q:[{Kt-:Ѭ  d9ty.a&be:Fkk!ünt[8oj+QZG,ئyGqWaK!A?9?lUmto^Q"oMfm"/i_KXĻ1x7%u̵{o)r0Xd:lNh~qgB]x$X gk]1#tB2{}%HtቘH=1\lo =5)  -*:`&`q $ p'px@p(G0|l5X-DU«KM! t0oX fbB"ܩfEl1;x^M<Ljy. 4C"Dx$$%rHGB"-q̵(Q|(aQ"oHH;DE%w_`ь7N~m,MHP|K)HdeH$"aL8R2FҒɉ)HF=e)$gj3ڶLIfa;$ G1;,x,j-z7G2{4+x>o4 Q˙FUT"oC";ne2tHK3$RR921HI}Hd.$!&$VC"UerXJjZz`F`fA+p`xAHߩqA~<>LU B -#~7a4@eh9C,5ĺBl}c8WN ]Qo>z~7ObHÕUQ"JHD>%i+6,GDh%$8[u@VB"ߒ$r7n2}cD&% '$@ J$o~iwG̕_o瘫^|y5IēLIۓٔD;r۽N׵y,J4TMhn\G`>Vt({ᰦhwPW~\0.ʙ$!Hd]HNh:|MB]1:l%z~AHuHo鮥T"%e2ɽ՗ :H9KWV=@Ǽ7w "qόt ﱢDg on|[FQ"E<+Z aQY" hK+ v0Ep^ZGX"CPq%n֝W+̤Pr&"ε"IvMILr'^Y.[E`&J5+ZV6UGڢnhX_g4<3ϙaቝ yw>F.zl&h}: X@<Q*:lL"SCӰȑOY)  -*:`&`q $ p'px@p(<3m|lj?%o&FXz{ H>ok pyB\U{x2 ntN&q3<"C>#"[3lWgvoR }kGڪ;D,J,D'n.JdKZءTO|ͩ!P-lpwi7%Ц["UgodXΰ/$,83 yH IX>v=SxQEQG-RQw/"Oш~)ȏ sW"UD ίwI ;NgEt{(;ilrtjVm_nk"c'3H+9le[EPUzfe"G4m; 7fzdJT^Biz QfgBɁ]CӰHM%,<,R_\tŰ+"}Cy07."tHs!]Z-8e<0 (hāx, -lX^~A&y:y(j^P/M=FtDD0t:h;b -h$~j]UwЁDg>:vwwHl^,aM^w[b*f3?d іw_bHLiM4\@96Qd_,k͐s%r -$R|zO?DB|qcِHEy? ex]L2."3͠C3Yg'+1%-=z#T"HCcY~;G1f3aRfd6[ͷFD:37qJDh1w&4!̓C"D=,9:6(ӄ(ڌ(2WqsIH֣1ez$?s9[ @ T@ 4@ t@ L @XhDQ;+O`sgv"Q(DD/J nD^{mw3Di"~"3-.BJ"$"H/OB"OAHݟy -IIdH"=N $}t7騥+A"=+18DrXJjZz`F`fA+p`xA'[U -AyHP-mv T][;{H0uzfX׮(5UNsЩf}_MΜjV!JD)JR$$ˆy_:EondgDHD\!ZdǦؒ]oME_r2wףMSȋ{g駈"UA&y*,:,fBnݝEQȰ{逢PV+Ld3g5F:uq!دɏߥcZ@h\l[H5z=bU{bQ'Lce+ǵwŷ7hO|2~Z6(XXٻ|)iyG&Ǯ4ܶ=v׹#~LuN0 (hāx, -lX^~A95/O-U3]BK_3jMZ{"EQ]m<6Ve"kVwc"g5t;vR퍪}ԁ:GD2p2$qioeܳ%oV6Rp!IC~~2 -Qߓ>,xU1--]='W^u"x䝤C e\0eDO=qPƓ;i^-+b꼮۝f,(#ͲHPV+3#w]wrmPLLqʿ(:DzMhmPW􇰾h(2cݨ^9s #x,}MBvY*+Gm Erhy3ə-W!ѝ 7ﲴB"C"A"A"yӛߡeDipb]~"&v^V9uX)  -*:`&`q $ p'px@pL^*.+%bWFjWt,%p7Qu !QJ5H!oI| \Q"ozn- tJvܸD:7a0,h N-о5K_})ȏH"sȁV [9 a.)_8$pܡ4Ǖig=n\䲳YINgZRȝ數bb|힪2PېHaESAULX=yOXS?G{D ? }OɌGs҇8ssgLd=^p]^:q)DLvc T9i|pB-z{ol$R,A7cG)~"JkK SD1GkK łfTV Bt;cyN&L 5+T*81$r(o:~rQ"ȅ[D픝FgV7]HMW7]HMےُ$N_ezw!j+P<\ 櫹{`Us̕܇(u^uL')N PbݛB93Y6^yw3EZV3l(2430o,Js`DznwO"u"֣+YA9r,R^O (ǔPP 008X 8 < | 8@"Vz[XyTtjm]nT41PSLZ5jOu4 ζTzn婾9(rS7.DwYhE~(Z$FHP"?t5_٪fWfRbȇ-%(ocw|Hd$A=(r=-t{$r~+/}H|a?:ƩIiP=ٓegNHmOK -edeunݹY ҽ"`r齻G+&Q?T Q>WhOu{hh>,6p&HDke裻14, >rDEj*E#A=RC,-@""{w_>ErXJjZz`F`fA+p`xAX;rG}O$\^2HU5aKfSF,{- gTO N -T RϽYHʦ3Edvk#TjDp1Q"$H(c3ܒ_I3_DRQĒݢzOC"qHpH"ϫ>dg+ɝi% %eS2S,2ofZY+';^^(`8ܰH}P5hX=vfl,d{X?O ͙F8H"M|;vY*EU=Z=rw۽9BW蕙t#޻K˦A"E%t)1@@@@ ă`V`.S}]0]B!3Uzmu ΐj&N*v(RD,'x1vhiMe X_;/}])8,S&J䧢D D~*J%1]};f~D{D q_J"_ؒE$D: }uPLo y+\y8!%?ʙ,{S=-㵇RX:,˙:K"SumQ=|IH9le3AUA ʙ7r4sek^ -jsFH HyI^΄vYԾTDZ+DD{9$0@g^+3+ ,t4&?OoIdnDrXJjZz`F`fA+p`xAԩ$2HbKbWF6 -teu'xTUÙG2ᚄ]zXpUGUy3Oϧ^~"WSG9pk!{@9SL&N9}kh#$4rf5ղ6RqvsC:fLHZv<#?{KQ;Id@@ T@ 4@ t@ "A lNn,AZڧԲbpWUz4jmZW֟RJ0ƘƘܯL7Em% jǢiV; ծܕˌQ'rZ27þ%Y57C1vm4'c<`nj_G?"Ǩ_Q -)L.,⫐2Dbٿ 'ķR#w4BӞKŋmqCm=}Ii>PȧP+;N,NN҉dƑj;5͑aCQH3m4BȚ7#YǷS3ysc(ƻ=OMsG[OWA_~0sCROrw?THƅ|Նqjc E8>BؿgOU#wJc;5) rJjZz`F @ &`h`v pp`W9KK9j-AA c^kfymApoh'xrQ;xҿsyW%x0q"loٻ;va,zwm!,rƹ,rU+U V@Ga'ݚl 5X `~A;ʕO<(|~z9Uyn|A?x Ko k<4ߋe}XÏ4`W`}o$zX{#y8,2]ιrҡ`HR*P@@@@@ D L , ./@5Ȗ)^1+( -^rn_/V2ɭ?GK7Pm`:Z@pV }#="/,7켐 -Ydoy!r$\r=ȷ+$E$"Q{!%"mHK]H9jYȃD~_D>uOɍ.(ӈ"jDJ]S1 ݚAza2o(-˚3 LFsԱ죥T|)v.ng!ss9Zħ*|7W]ڗ;Ѷ?Ӎ3oI =5q6YO/ĴEik#멦С 'NFMd P2?0ûy3aD:sHQd+Q_S -( rJjZz`F @ &`h`v pp`?XVsT'++卼/('XEViYma7#" ޴ |k\3ONTkw\Ey<$a']ca']1e1a:T=W\Ͻ-doD]!8doXd׆DXf? 6FazDNJM:TͰN~_Xꖭw"rM-pl Vڞ`tk͑d%xmO]3ၼ`_Eh;I佇}meG޿_˟ՔѶtou"SŬ9Ԃ(R4(R4ҡD"~ҡZԎ(2 "Jה -Ȁ(hD(b ؀8XWH dWw/ɋ6Ɯ5ӜvםE5֑ۘzrěJ4pq=x{#81u -Ȼ݃!HE~marC, ,=;>+ruwُ.zP]%wҢ Q`c`O'Yr>CoACMYFz[^ԭ*20dk*d&g9 ڳ⓬i֌̬Ĵ5!yoܻ$䃿Q xTSMSGO7A8 -t<ۑEȓ`;b"/"ENdYwȑHcȧAXdcoeGE&`WIwY9:Tq:&/[ -( rJjZz`F @ &`h`v pp`?ZT nhW/qeN[s0[B>,'xK9W[`!89WsN"_?,bC YdEntgD5do]ug7 yiC:Cv.3 ,buє}>{?C|")1R?}5El6k=9jOG*IMKM9hڑj_Z,2q`;50V rKYjYz@ǣ7O"/yR"%_C];bKJwIg oEHgH_9,2"㻜U/"aVX\N4^;hP@@@@@ D L , .\j/+QW^U RAU)kMavIЍFak!H^ ryYL{fk+샼cY`R @<UղE "-dE "mC,R2|tYC7|%bȇaE~!/ųiR}udoc9 hw#lFE %b{{EҮ%$g p$IV,&جɎ$k#!!>NL6<=6|<'6MoZ\y{9rbOU=^|/GS_6<\ƼC!W4d-D;b4͑+qXd5X`z.dme\fo_cv# Swei#W4`3[+ה -Ȁ(hD(b ؀8X?NQclWr^̩qdt"kV b\ɓ18zseQ3͜u+R,R6BцkC,rۛ2W>(s(r2l>$aT}"LDA"14e^|N֗="o]SҤ\XW4dfwmOTi7tղ6$ZZf~GlON:? ̟D^D*_D& 7!*DGhp5N&'#T3 ][%[#~FbjFƎM晅\K񜸉'*$@NĞWq\9˹PwBj8*q?_! I؎=~`$8uS͒_PǼkIYIV:&-dn͊%ÑˤL +^|#j־,=*'O^(G}=.Lh+}F>o -d$PFN2Hǎw:2꘼wA: AƻȳRT"A?A4:dK 2  -*:` -ĀX`f`q6`'p7y+ )Ҟj+kM!KeSH {mڙݼW5,x$A")mPJ\S^GR)wS:@ -EWnH|U&Eb=]r\>$!lCO@$iІDįwO!7rjsg+$%CE?DmE:fg K@$$JLbVG4L˲2 TkH32ӓ`.˞;|l7 eA?Qzc}\dƣlN=k׽4<ɺQ~-^[W4QE6Sw2/&nj-6krrt ;hx1DZZ {kJd@@ T@ 4@ t@ "A lNn,KW.fi/O -^^^#ÐNN5:FAgC}ʦaȬ`Ǽ8;Dp]^"nZUD" ֦y`=\"B$$; TmIwW T6Yc$U4cl$R6 Ȋ!w!G L2ϯ!idҐ$YI$kj"hMKOJJO`iY kS{_'H;?F坃DNM -rbاUj+=>]QAGf=*Ȳ!l՝vZC"^ّIU'S̈DZ$2Y|)}H $R -$2q =|i1?[ -( rJjZz`F @ &`h`v pp`ߺخR]K4+dM3.N2rǙ98o:y{DJ9(\~̭#D ID!"m!,^ƋHdۆHry\Eϻ|'}ڼ\\G[gn͠Dլ6ȼJ(f=;M ȹ&Hd3H$$V}4gw;B"MrfY=br Yy93I95s>NQ+ZNkj/vgX0k$,Fa#o rq<ٖ98q9ûj9wzS"!\v$rD j=3c?2WjW%2)J"NsY"r&Z/gބDd&3?YZ3Jv1}vRSwQϸ)] ?򿑫>nfG;[ӕoJ f߰=H"?DVxGF7! s-HHvHdd# 3raːH_^pWnՊ"Ht42  -*:` -ĀX`f`q6`'p7|QNZS=:(b^YëYu')圮/r28OR,q1YZGVdS#S -!K">KzvȦpR7D"{պU]L -<"b aSȅPыD7"n5,Rr,bVȟZ*v܄(.)MC K:HZE?pgK (-v̽ Tcչ?I1Mc1McRĈI{?jHPk˖-]qK.ٵ;kٵ۝3Jf.wZK~~ww/JNMޕ˲{]/I/_+^og8.ݎ_O۞wno|llj|ZKMus>wxӓl2Kc=k6}GV֎VM5*MV&gNvݰ.>S`ȐvP6ugK:B¶v'U{^!"o:fNUTѽwnwTb r<kUMRsGվ'Ov?{XȾ:Xd:TVZX*yFMz ^ 00  -D'., @}fISC%| ELL,El=ĞK3bĖRԖjY. I2)F#\5J/}$JB[-d˖6ޔ~-a739g:'Qu''~2OMOf,Y??OOOQƉvSNwx[X:S_So[-zKHEPo!̻o-j[ߦ:sW}o=??o ''~$Y=1Y1t[o:rڷ^uU_4 1kYfOY"G":c̼P,e,q(H$MȒ,D6E"{ * ]]/zG!*HDS%OX?D^'A5b.S=(0B[9ߜIINOMa3\|KMNҒc)))\jr}T" *pLǐHEaX7?xP<6N6ܓii -Yf&ֆW ǁ6ur~*m {;:!C"mC"wQ7(HEMڷ+onUڟGy)B|e555Zz`F`f`V`v Dh1<^@2M4"3IDw\H=W2K=/Y˒lyPr&A"+I8YZ$SC|_䟕RLDn%%r,se\ d+KUeY"j]%$Ʃ3ד9I"RȋD +e(K+ -rS$rS -q#|ab>$B kDfA"54ȸ+D!#d]&IKK`9/^#ij;5==Fx}'KdtGkiͿ-/ jƥ¦3-τ,5GmJ@xX(DoDf?D_Dj C$($V$RZ_gH8$RxuHm){gU"xM `X؀8@Q 8A p`px!  .D 牡^2H$sd9H^8FHHEsH3;rF>7)FILDd$9OA"ߑ% K<|gSl($?'wVuB9,_*HzY"eh$,1|SL"~9zq}D~, PO!^ 1B@B_8$;%>s%"$'S|7=#.96-˞,ނwD%RJSVfr9^nNxK06Ng^i Y&Z{춶jHd1_+D4A"c9b變' =H}$#$җ̷@"QtB"=Pk"3H"uHa'$2ݱ*0@ t@ L , -l D(  8<+45HD%%16K1Y"E;+D#Lzi 2!CNMX/l/4Ԅ,%W[Kkѷ rfFHϐ0ʙ{;KB"HӐHk.$RC%$rY|YD? D_k"B"/J 00  -D'., @})'Lu~T4ԋA4%K`m6/sǢ H,JEOvk}C"'HxryPA"IN"g)H(K&9 HȍDP\O"l[kdNZȲ,D~)K,D~)S%D^@9?rkgr$-G9|F-IKsǧŧD7n_MNa3}lqGw[?H*Mޟ/gF_y1SN}վaILӱvgC{֊qHdRpU -QGs*X׶C%۝B"#CHcH{jV7{$2Q9~u!FHdeHd9 $Rݸ*0@ t@ L , -l D(  8<+"PԊBAH+[S1K`l3Ğ+:&h9SK%@=$\t-Bf",J?A]/l%Gȷd$'K;DT -Եa9sRE| -"o7g~+[䷲EUHfX -SEv`߭Y w1D+,իi\1ߨPcKgEx_lU!QPȭPۻY&3r(a+} r"\Yc"3I!^6M2in.=3b}䄌ĸ _|{ʺj^%cA!%34tuYqxiLs!KEYZ]lt5UcdѺ MHGWD!4G)xAAkQDDxAur}"U]"CM"" 0@ t@ L , -l D(  8<UJ$M'azEhT eHVےd'qtH$[rZ_ٜ!a-_WN)8lY"%&DUhQ -"nΒHz%T3IoUXW̡ 7gepsfpSrH5m"Qe.DRȥȏHyo~4ȸ yS_:$c݉ n.9%ݝܩq\Ħ'KdqT" *MV%Hnqd$ӔNsOmP:]g=c^!$~EͨfDDr >H毐 tsEHdh1Hdx$w;J 00  -D'., @}CDӛĴHjKМdLEļ$Y:%M1OZ[WMR̘:&y%FB$0 T&U+iCӆI]j:,FI3S؝-bRH\ W";Uf~a.C5k3+k"]o!<3w,@" xy6b>_ olDtfדH$AA"٢ $ݔf^ B"\eM" xª^$oODbB_WgDRbS4wbX/~mlz||/1%>dhY|q%̩4K[2śj.癝!@Xԛi(4t<}{#K!3z7%!yS)i)ɨiلX/Ϟr`JY#=w9 hsú= ;# ݯ?4u8d.l-=f=rId!b(Id3}nw!He -+DFh9 t?$Ru)Hd&)HeHHdDkj@"@$ b x >? ՎQoj$IߑdXT+ѕ՜$kDWVH!DJ IN)VruOĖ$y$qI&7}R+*Hd,/9{ó3_%rQȷ~WVX}n[PXQ%rD/K$B^A"Q*gKA DQμDK!kwL(H8OD3;Fgj$6#ś{ )n.Qΰ ԴĔČSF9W9MM˃6f";><_0w7Mqɲ֒m%؏W{M";[.^>76gQ_Lȥ׿{l ?[oLWnaл? p㟐^9v0@ t@ L , -l D(  8<UFLNoҶt& ]&c4k2/,e&kas&ccUYh5"ˁ>SK2# -(k1.W5c8{rFƸSq=cDd -W֌qBWCeecܯ( c7*Êڥ\dz`ti;?g.xϘBS jDL{5gTcYBzJb,˹47}l;>-d619CaxH+[1So4G2/ vfr2=u!Cc]Ji|q9oz8ln[+^!C.Yv{}[t۝ {;;Gz=@b;򧎪}!v?Nk vE(x.܃1S} i4"S$jKERbMX -uA5XsvLr61%5D<&ynD㢿Q -O/IٲD,K {r8gwĎÿr'~ʷ).,Fe~ ǎS>(M]-ߴi'XA"*q^1b4O%~_'0ˈ׷IOIKHOq'p H˥&&qޓ%R#ofǽ=frr+32@s3uߩ* jƺLmJ.Ív[O{G'$21L@" WDj :[*24D^o[1A"YT"^HfU"xM `X؀8@Q 8A p`px! R*I#LіHVI_E 8@L<-Y5WJ*QL@ -VL"1kx [%yGW.?M$@m:*HmY" A۲D -6E"-IHID~!.g)H+D~!'$M]b3nRފ1jcHlߚDJt/D& ɫCDnٙ$to -NScݜOv'qˈwߎWDWi -f2uv+BƉLSNs$2zgZ=mm"ԑۢ{;G!=*WېGlQwnO훧e9ːȞQnDnV[{WBۡ 00  -D'., @}\n'I) m -9$*$b%։ DS&DsVYYE ]cķ K_/Hpq$EA"7DdܰalD9E"?Y+Z BUA"FY"o*Hĸ)Id!f&wH$בD[,[I"rs Ч(g~ 1sYYWtFI$OSܱ87ѝ].-Ʋ^d.KU(g|nA}c+SRμ|}~Iq|8Ӕ5\zO2Uqu"nk -GD'DZs^e33]H"A"/ t &q$q_wnbH~Hd)$܇DZ+Dθr_A9Ht"=mwD1<)ZEkh%!I%RCǘy!ZtՈIE+%-KZY" -,keD(H["Wqg2P+oǞ^Ps9H#SwN}Ny0{[ظ|$T62daSNKiWُ?(8jkabZ6wEt=۝ X$+$ LG/,z7&3U_#E*`1X Yz~"xM `X؀8@Q 8A p`px! Rr\N iNiDKh b/&(RCy9Nb -.sZ!1#11$ֳJ"I4IRmtTaS,<:M꺋"[ aWeaW7%~ryyېM^YDxgg/DnOQ|bQ+0~uDuh76KOEMLps>ϝMw)tM?E";nzem{ݗ3C"= ֐𹰱LSC!K呰uEm*{}ca>zfҶzhڝx}Q$vN#dD[ V&wzR0$R-=0#03+;p @4p/ B@8{m{a%i%]5ѷIbI2dMvHgG;"}4g )$⩓-qs$P%;[Y+,dD'K$[$MQӜY3#'IO -i%p{QǫDIdq[X!)D^癋*!^By KD4D>ؙEKq8ΧcT6#99>#..1!u7h~m^Ri/meD -DߩS2Lt%7M4U,}ak^ /8U CHmKwow.W!a@"oC"C3mrm;$R8$R= =JJ 00  -D'., @e,څvy+"ai&Eq&>qL3DasQψIQ 6$F!mZ%R'3(HD)1ΟnwݱϫOEj7i^j Y:֒!EHd )"r YtjynwNvytsHm(gQx[;q7Tc-?($2$Rw*H}ϪD-=0#03+;p @4p/ B@d]7QGtyD?$JEchueXK/(=).:;&j="2-r7-yğOMb0I$WQ$OaQDQd@s홁M:b=,'N|d|X6E"W_&f'H"Ik3S3QD=Ok6CLgTH%''&Tss?KNw|N9)3ޕo\Vi:/gjVn=+ܩ4d '¦ʱCE춾U{G]B](gFDJEWlwhDJK DZ#ݮ -E97oh$R|P\& C ]jRvD-=0#03+;p @4p/ B@Wi4mD7F-GESh^-=:HhYhXirk""M"m-yHq͕ nQ+2%+HD%%+HDگ>Xk֓S'MTD3_Twd4D +?DIQ^X{֒*H';yg, mr7w3I$R$}uL|:Gc _Fu&'ƥfSJHd+S8Խ{of0$R6-]T -[춂^$G(g ,ހ$R覊VHdK2gӅ=H"H"Y{D5"\矜DDZCS$2{"55Zz`F`f`V`v Dh1<^@HN.m"^P(g%Ӏd^-uLU{I$vJJ3#ƈ8aG1+Ŀ$Dt39eLK{Rf&}TšY"_ް۬gÃ_ٔ!k=퐈k}E[VDH"W[iBD5eX_˥8w -yidXO>傈; -Wi -o)DԵuPiqk8djH$7l-n:F=.8Z9Hp/$2-:Ζ4;i $ @"jnf )(DoתT}XjDWvAfj2w  00  -D'., @}&0v@ԵzIk"]DKhmS\t˳DS+ZDϒ 2|{DZ -b\"rc/%%Xd\$KK -lD]&&2OLenx.AV9 -IؔrW\g*OD^D.sm!;y&.Hd O%1!׮D'QgvDi^$đt/O\}^619;ݾ[v,H߂Jw93Fx' K25!CK :_4,[n^T[kGBtʙlHdOѓmixId. уEӅBzAquZi$ꕃo}y -IdO9fh X ؁DHx|~A `e+t ۬Oеi0tB4M\2#XD CW.ͤI$Gfr)j;&r/['D31ݰQ$ZH"ћ" I"^"O|WNA"= )Hg;oہ$r_$$HdmѽU۝=< ^ywWPBWUA"%nGP,@"UB"9H" DX^S:`&``6`"AN\XAA*Â&GdDm+Đ%+C2Kh#T"YUU+gډ]"ݝɗ9k$$;$ʤ`%rD%"D~*KDpiS${4U NZšȏd7GDڞ]gM ʧC|cV#`!}dIA9ߐD3ja՛K7zWnS|D>MH'u+9J3Ut9S$R|cX7ݞ?2&ll[4+4u,yGv[-*P !,oܻ<DF {ͻT"A"u(gwP;ݝYBɚDD:_;Ѳz `X؀8@Q 8A p`px! ҵznfh%  Stf6K1aama8Ę<ɕ%yVn9fP `zU{,76Y" \)ٟi8uD#+ߺOA"! m% ":U"!|9vtM".H}g.=ͥ'zW'򞕵$g"y4G2]#Ha]@~(d9l쫺4xh9 d~jBֽԦWJ!9X$MX#ۢ;0:j!j!XS5u;,RwzOH.Y+,2@-Cjalz9@wwVLL-uD?/*chsEvz(ۻ[u8{Ę.x&DWHN?.$E$[$Q,MH~i-ri&!nawedl$Mɢ= 9Z3hQܜO!&.@-s(tF-Y\ϝA%],NOMzٸT>1Y;zVNߕϨ43 %*a]oiX?2{%l+ Fn4wՆ,Mo"kҮ="8"^kSG6W|c[tkvg0]m@ZzFD8N~@o{OkWo"{[ՁO`Gԡۻ",2h X ؁DHx|~A J-LH b藌siIzZd1HKz1fYt5IEM.nB'qɟK bo3SH@y - 9O"MDΖӌSzO"V"[hȇLD6E"g+E5Sa\3 HZAF*|m"hY$}siwg\3oFZj2uƦǺ֝o7=O)D!y3%]WX2oDj6A"f,v[S*43(hF_GsͲ"Qd>zO=}\4 Q-DB9(2.P$kW:ADvzefSEJ$M a&S'P*Lْ[H֑wcтfQrN$Wig4 AR W -ްI Kz&͹2aά{O'M L,-#Gc -k$ezpZH oꙧiꮳNSu>=ZϨ$}%d76MY{33, X춆:TGCBġ[DiXEsT&"D=Ue!c7M@"5/@"sH($O?{DE怨%Q EqI4-I$K1f[d_<;2@tU y [B@|_(H`T 歗DT -޲ewgeXe^߆[3SH޻{Y"7w2L9Oy dW-k93H䘏gJCQO%׆o<*Ia:›&lʙ8$1syٴXKK>Y"mE;VzNle:DYܩ?xPL^)Ɛe|2l-}n+\@uBD]HH}۝OB"YH4GzDD/D0H5z;H kj@"@$ b x >? M\QW/kS4Έ"ɜd\4D7fJ 00  -D'., @}&iVmu+cDh%ђED[1O޻D&3ČWir=wh垪eB/+>DoohKȷeL|O&fMY(r,:sR "y[ga72u w MƊLNsoU2ֲVmIDp4%D?,8Fݟz wQXVXd~:: `]u>̿?_ ,R7}P/E:¡wԤ힕Q55Zz`F`f`V`v Dh1<^@81NJd% J -P. %S 1 IaɞCtUmbꈫx [-lɷ,]`&\aHHU6"a޹D$'IwDΒ%R/K\'9WA"mUGgD"Q5C"(}8ېH>@"}%l6HQKɾ465Ʀt;%bbccSNiX-(1?2qJ8雥wf>U! 9W¦2sDnhuA"!!bQd$"jvW@"tiQ{+:bue4y]1]ZDX I"GO\[C:`&``6`"AN\XAAIK6HvI_(%dC2/YIrLRƤ㒫Mt'Ւwp VK@lLZg?ZEdRH,]Q - IWX])Wȼ$h9,o8 zS&}t&wPv$Ru CH cݐHWh X ؁DHx|~A E $(a$1iD2e,6Jt:d#Q*<*$g Z$OHY&$;+ O3{dhd%S9Dj$S9"1VEJN3cuE>-Ս>/[_^E>)VN'",Hi-\AWEnE=Y"7aF$fR w7#PI -oHd{3UW\׃(ұrf^7pGX_ -JMGc/_sf}|sf9Yc34ɞdugmgngtf׳sbE )R)2d"҈""""ŊVJJ)"ohmHvNx#>|?uёfaj'ï/7=LqHGL[jkHpRd))]UnwR$IHNRl;R~z9E3 9(@ *P= &0l/O HҦlWH)RJmDA^_NS$H o yiZm$\qwO_[MVkτRD%D%IQF oJ -M+{^ᘗ"ƪD~*7VRd7Byꋕ">{8!I+*R3{BSBLt[9{R}?rHs%ptZQgZ7X>+n`+,|!EdLU^O]3|AB%2XחJ?DF un4? }?@VG"D*O"D>B%2^:J!Rtu:c!2KmC(!)$ AJP4qF0,`'px~ -M-a~BW8Qe3*a^Sk뉮./$\[+2wy2ޝQ3y>P B<s(цU ?B~)D侑%=sZ4{gz"C?S H@ -25h@ :Ѓ `3X,N @^<3IqPZyyQPqh8ë 'rם (|/M7HNbosg6Mw!㽅_Y-Bd!DD ~!DѻUBd{W|_83WF\ -.J< HP\%DXNOT+,5"Dpڊ!n.cnh=Dt9>J7բ:uUms9Q$'*W\Nk -HLIJw8.kZ Wvh(>Y~jMȋJt-;'fN.++9~dl4y~s|%@n]#ѧ"@z觙FUލ*57־2:!<,᪚䰦fD˚­K&$8 i\er6DۘP/eI>ɔM[;B,DsH5SҌTQ{_߇LCkr@s śL}utLcB:FQIqyUH$~T! uBB9AR{()$ AJP4qF0,`'px~ @4/.%LtA"!j("R$\^AD7O*}H7K'o-{o8JC: -<{̮Kf_D T!|J][ЯUۼ/U!KhwzZyBr - r̍l@!E'}&g|<${bDT!ɬ5~IKNHw.z_sqH9ۘ^y&K*Sֽ'GRTMBuaH.o({vBTnxj )GG#tHQz4BB @7" ]9Bܳ"b`@RAZЁ `6`pn|^<~^ʃ^QT":^[ԍTm);yKaN%j4gs>!ޑ^":Jh)BD%D4"Ov_)DVZWx:#y]4]kH{jT"LB$ ]%J D=1w!DF.73ۯv [3fMr$ÑLrKNI>$'oH%zV$>pf6;%˔mCᲐr@j̧n( il tM{sHޱlB6|D'DW&麈"D[݁x^w#]9 -B$c"o!DF"D-~d (AjЀtA<f X<,߇qaz^:Uym@QT|@Дm>  w -K3UVb?pLUkg:u+kaB!D(!BV%D_i+خp*T"PJxJ[Jdf1tT":.V"#D^ M!Diпz=>9~)T"ʼL=1%m˚%Xl5MK:l3u\֮Z^$."Ǚ70sϲm'dc>TCH19G%RRS8Ө:$맞rqQ:}L"eg"}gh.: H>H"{cО1HSk|eSirg HArPT hAz0@ăL` ؀;8 ^DNVD<`Hky`@>PMU^$B@'"*Xmgt7*7G33Hs_g&FIϻ!D$ϻ|Jf"N}[XM"?R䭘xū"YVcd/gۼi)ҌٱanT{gU g:C H_k"J~C_]JƙL-HZ4Dg#E<3JO39q=ަSV["S#(EK"yg HArPT hAz0@ăL` ؀;8 ^Hr)RdH;|( -*f^]ԌmGdl_gZ#/gzx@Vͳ>Nsj,޺3 -Θ".us_t5T8 -ӻ7ۼ+!D"]gP<2Rd7Wڧ"OE)YIgn)a#32qIҭd.ʥ$I8$%'Us7g:EiId Kr8SO>'8T=S -i&iG:*aQHCͥL] !"/ЛgN#D:"KoqVy&>H_""U %@ Ђ`8#  vp8p?GAτ)^:>!A^5Hԝ&u~^t)Ц -bimy<ۏEpռ{l \D }BxH" -!"ϭJ7\R+\BdKw|BØox]"lbI匥hc = .t(*Gcn>3@f^]GZ"JBtkriu9]+!)|˙s"C1"YLг>yHrHj>{H3S.A6#8L"qvlq~&]B$nۍJb3t]*wd-1ȁq\DӞ 3 9(@ *P= &0l/"E 3K$#i>/ $ w.g)癙)o Xl{q稟#9,J(BJaŪDU "R-orTy!R)Gk k!k"7"&󾽉c~y#*4^OǙ[,q掾j{I[օJđn4k3% j!{΁7""q1ï"DgI Oe:|ɐrLUz#YzFm #D¥~gčJdsD"}/ҡDEPG%IxcC6.Equ;*q擮5dlvy~d (AjЀtA<f X<@Q$ #B@KyYw@Bsr*x )jy]7!G+:bgyKsO1b'0qvw7|˃ -T"z1oV/m6ǼY]Dc>Ɉ"Ckê"IOz!DQ3vD7 8s1ȟ~r ts; CCJ$W>f}b!7"B$MKͰ'&YNg3kR+%MIMq]yu㽑JdA$;y=%A'7'sS>H{HN+2!(Bf6tl4<4N6+"7]yHQH6-p+BH#yqgDwb`@RAZЁ `6`pn|Һn"DJ"36j%^SmH D"7U8lSv7^&tfUdJg(!riQBB6k*!R\s*y1^Z瘋xWgeDmk3{>"BMt斮T":3ܺtk#v9PV3mݺd6))v'߹m6ub 'CY}>yDHq`gHYX - i봓tt~}.4B$LΌ|&SE B$<G#DjhY8 "Ϸ_"mh_xn9D3 9(@ *P= &0l/rOxxI ȴf^=Д%'˃gDy0{};ǻxO7]i7 CYɹ4M$`U gs35[Fpp'$(]B4\{ת#pwgQ<x}t^ hxߘпz83qj -ڱʭKM:)t:"ZS85ed$$&^2"tsgimDoc$ Is͒Umu{BY!MaC"b_?7 MG=Y"ƛB!r=Bkإү3tPBd 4E7<i@}rQtd (AjЀtA<f X<@bLQ.e=D>GSDī&!^3EDWiJdjxst:,*H&ƻLjxkpfGw;"i^+ȍBD[yȮܘg2bXJx/-AH_ "]a:",cѱg6"E޿cnJXB4NyJ[BtFZ"""""""2ADD38""""RHAg373lܵ^]27޾wώ\>;ZG ްGDDt.`j~GrZzʯ~t}ԤI:%-9_:]e__ϱ?ϱ/r?y~xA -ܳ/ U犋J8=kn/=rОH;褐IvySig8+Nۯ9eU?r^1$S ܿ:)S?SUiO ox4?vS]WO˳C-?3e4-fo2q&[ȣ*K2eѳ >::W--"[h9^._*.##L=\L]/(_(Klr|8|LPAZЁ `V7|G))vPm>eUu_365=~CS|oEFDM#bz|ZsK|_ߟ#0#F뿰2ZEĉ'5'56Z?iҊO?ſgQL,"?EV|uO1JY6y]Gny*-G0tӋ =&M]'nq;񻦒U-MqN{g]M} ҔcC}&FƬ(oS6t:Ne0,xyϊ\^wj y]:ՓH -%:cBi˝zD23.7\BUvL90jKW<Ԍ=Lf5AC޽aLTS2н62eѭo펙yuNl>P{D\yCj{C,~ZNͼ ,Mɘg"7.vwȃ>+gw=)|LPAZЁ `V7|Gm<5)XU^c5Ŝe9PXk^d-"Y`m֕ŻG9zTsL+E\;%"bDdbDĈ\$FdDDĈ\$FdDDnޖ El4Z~fׇĈ\/F1"bD>ޙBňl7"WlKDî9'e"îP*(QzYDW3 ""⩯d6uY^6rxY";)DD!"/#"k "!"GfƑk|#H݈ӈB"{?"c*%@ Ђ`# ( 0? ,u'"R)8jU5^i92!3pFYVYDCD9kk+ssMng=Y16jD##r$mx%&/JDDؖ&S'E&$&*1"_D$FJW%&?y]""G%"mWhMnD6I$:D䯍~e)mK]t$&}gSDioJjJB#MHrNL -M;\ظ&.Iʽuor& uSWP3<u=֠!륰ql햙+YKi Y)jD_~iL#{o " uGD<,weCD0(>45!4?xX8+g?DDnX>(P -Ԡ-@0 `Hh+Nph>C䣌$"WP5lAky q7snR#"$"u -)ywO1|DD-z1"Ĉԋ1HDĶ- """rDD9W""߈11"JDd-tq!"G|5&;DE,gb1""=~u?wXJ?yklH\j+-Npb:.`C Xwj+wN{<4@"ұ"S zjtu{MPSx4z'Cwt}~mАqlZzVf)c-ťldØD&^$2;f{Yd9ӊIdfDd Hc+&t{&څ7eL>]D*fn$RvHrcS -@ *P=&0"! -!`;8L|G)x*WT强tIN/KC/gMCyԓL,ւD[3o_3wgt/)7W@ީ#!F$[ȗĈ쒈HXȗĈ쒈Hx[" nf=U/Id\ț˙_n$R}tD?_5""/ecc~>2DyxT2ϪL(-aId$A{oBۑ]ޔ4-\_a)EbMl}2CU"熵Ӎt-AC2igd&˙ծ1{KHkHw9DdHLj܃DVGD=Idv+i"{"OXc*%@ Ђ`# ( 0? ,@>zN˫r>Vz81yS'o^-5dO$DdHszΞ;9WjS1u'rĞ'Ĉ|I""{Ĉ|Aȗ$"g["қiȾ"N$"=1"_DĈFD"1|u[D>$"&LD7";D$bx z?)3~0K]mDqY$%RdA'bLlrȑ@{ݡX{zD*3N#)HZE5O "}/g&Փ5s= "R>IFt""ULݺ:N6Vjo=YμCDjX͜K""*DnDDZ+TJP4F0, Q 1` .p `~@X+Od&**_PTzDД%j nA#,oeDGD0Q޾;$VӐd B=qDHD|1"ߒHIߖ~7;çmNbcyq9$b3ϋ^""mY򻹒ځܲ oD܎^P&O~'H]+%K䩯[Ϧ&'<#I8P#9!)MIK%x\igZZHW<4<.=|lZ -Z̦dRv<Կ^L͈H=Y!"Kd95{a˙nD&L"YOݎg#" "Hw"TJP4F0, Q 1` .p `~@XO,g8GrAU.Mu7q<̛yKf" -NVhoksqJd-.gn#"#r{DD,F.FD,/oKDޞܧәf1"QHDY""?W#b{fDId~Dh#"70s/cT?)""<.ha-Hl'JqBt2&7$:P*b\g<^$H2"R;6`9ӕ/jzn }Ad_Xt4y[fn F " #"S-cnc.DD@DK.Di>?t_+@D -YW|bOS(AjЀtf@$DA4Āl`' 4x!A`Q8ES"5+kxU75قrvXh܉I2,1[yg~^pQ7-{YĈ"Fl#":#BN"";%"?|~I$_YDf^]X@H1H,grfB\$"qCW]-wH|-G;Gɞ*cXά]453_sv-gb=G(llyl:&*Rv:E&ңcocL"D=DdmD^,gGDrʃ}+!"uQD$qDHL63Xdc9L63Xdc9L63Xdc9L63Xdc9L63Xdc9L63Xdc9L63Xdc9L63Xdc9L63˙eVSCjq2N_8 -kͫP@DZɞH5g-l =s\ 8Oϔοvj$R(Fr/DD-<#":5"t^]x{\$bHI(mHNյOgLX<οW~uC}8DO~IgAjǠ9ߏΪ3)qqTT~vq)itBRB|DrV)yP/z&Gt}ACacËfS8"^Z`# _CD -?DDjo4ftuQDd|)<,w-~?B"?(Ԑ՞wy@D${"dc1/'ωUJP4F0, Q 1` .p `~@XNlI'˙Y^[:}9ghKi7l!#B^%88#QS3S#rĞȭbDjC[N"CSD6}:3ʬK,g>#2/FIJ9y1"m9'"b:K~%&"\ڈHs"ҹP1ϳH&{er\ uYj/ ->Τ ff5/ua]ɇź#l\lj˙֒F""%czYDnD$ k|-x[퉄yf3J<^HDD.#r GoGD|rfO0@Di#"̈Hq,gMY""!"PJ3gjRj'!8C!,gRBNKuɱi)#R[5DpuռBw]y3]]tk _OW ]ap4}32s#%a6Ny`"1S|""R<"2z"2G{W~D8"2&"vi~DD1@T hAz0L` DBDC Xvp @T\Tズ'QS/h]o q7 DKHDkj7]V=.9).ޛrzEro4z)l^>)S׮R]U}wu;^.b 5reY]wح ?Կm2[GTɜ/sU//sO@FV m1}7~`UY/ 6"cVeX2e -@ *P=&0"! -!`;8n  <!Z9@Vi,f2-a6ěhs^ԃdǣ!}5?v,>M;hDմ&i=4c"5R!$DClKCz2l;bw=$1!O DB bBue5-9)[;OUH/T$$ "!A[~R>@B.<޿e\II䘻ۛA$q$(JZ|BM%%H3wa "sMaU;ca aꪠa.l4xJfUϒc"..(4p&D:ȫ3CrW>TAdo/=oɘw0L߉Ad ȑB "Cgwkbxeżfd q3Mr:1zYyg_服4ͅu]fSsjkd#'1NpfbVXk+#Hs"=AD -nFDY9f[@DfKy!Ox{ڳ."˼"Wj*OT%jfm~n5Q?x`jJ$Ό'H`-lwN]&J`&_/M zGluLb1"\-)~J)n(ig$q֌r_bD-U-g"? "Дx="0T̷AD"(P -Ԡ-@0 `Hh+Nph>Co>L"SrdK䠠C3q\)X$RLD80gk쫜w-n=3xs$&CbDdCbDdXU}ʁUED!Va_ycޖ\-} EٮHET]f^TkQ7HY*x!aTf^$hx #L0D8o7NRV- "]+xE -!ٻ3 -\+VDbEU}wħ6 o6|fiWK쫶yO""_#.F=3۲MF0QEz|YY<2k(~S"؃T,"wVXMr\i)I8A'3o\\ȑNJKNCq3].;&ȊLQ}%h."KXU{Cz1y1%W|:hh{:ͦGa-ED'.p"p) H"( -` -BDz[L#"%*}"s'" -5h@ :Ѓ`3X  b -6\< ( dS`rNP$WyMtU܋ uoBeDdl -U6ۛy -={f'ʦOĈ|E""Hg"s%"t~ڥDM"N/]"FdI""׉DȒDD.ٖI&g -?7"2Է -L"fO1ԎYÈtYIƺhG\\EF;ܱ;NNMKHO;{aD]T[ݙ+UauaCP3xv?/=4LUm6M=tZc#oBD..1ͷ""C/yon萻 )Ҷ(!"!":gf&ȡTJP4F0, Q 1` .p `~@XaF^Sr\P-)^Sk;y 7u -&RONL.om+=ssdguxA}e?m6Pn^w˷xDm9jId~蛧EO1m!FĴjĶ\Qݙ0lrBDdo OԅbGDbgr& -ѩq#!ՍI;Bbb=LlBF/_fP+G ZMz@X[X>]?}(hsQi65<%3gZf#&ˣcnc} -{I^[]GV,g? "!"HDdTJP4F0, Q 1` .p `~@XD&(yUn4kvӍ̑xGA2O6Erf'Ho2Ӆ$"ݛm|F""[>u)#w["b}"i[<]=k$Dkc_#Ry7""%o""=黰| >_y<(X}$g0 ֟D1@T hAz0L` DBDC Xvp @xX?m)xjeSU\JTX}1g捭3N2#yww6.]ѥgc9_#o#&($ޝI#ňDDԟvb׽ٻĊQ?ėgŊKl,rϽ?{=*rw"aT$_DE ?)* RN"}1Ȳךּd厍cR4qPzBJTi[۵~Ry 5:*R*x=]PP̕+u1fS(*҃F(RD.3~kK=*22P1T`Y;*A6EzzirȁqGPaT$0*Rxl4>(P -Ԡ-@0 `Hh+Nph>Cp52ASeEP E2y]= q7M n޲DvV{Ȧ Gn%퓼ss]<]9ߜ/VvQ""_ŊĊ|A"ܖ<ҧU$szeJ\pD;8+DE/;&Q>W2}٨ȏ\.vl䮈}T`A*rO~"BބPR -AǺސ'bho\rxy&3"2E.jM"f&AMi{XzKX> Jr4UM/>'3OֲD8Z}zwLUk0u+xkBDdvY+GDBDf1tADzV*%@ Ђ`# ( 0? ,*&XjUq~V]i -ym)3p\Yɂf=14b^99W+gzS3o7sUvJbA-OXϷ<)mH՟v__ukO.FDӮIL"ٖ]g%. ":'eq?"c=c$[͈ "B!""籞9t>3{jFL(+Iq$%z\)+;=~@͊LQ.j~ljtu=AT_X[5I4<6M%/?-3ճ|DdZzxwL"2q)GDf{YEHѭDf:{ҷeL#= D\0ʉc*%@ Ђ`# ( 0? ,e'NjSr|L/ 7 2C3"[aĻyO >|˜?w>*1"Q[mvfg?)o6&O{"ߜD~/D~-yt鯯jrEϫ݈Uͭd#|Թ=He&X3];"L(9I)iPCI3'&yߺ?/Tx wXxhȻ ";gnG講.dʚdɦ|=_PNd%uɘ&޺Uo,O(_ N&g+:o+‡TJP4F0, Q 1` .p `~@X|ybGS>բWT}^]O?5>ST%_9볶ymE>{gו磫vxj|̐w(𝒐Zԉ ɶZBLӉM- -7ߵZbYk}-o{ߵ- 9$3~k0->EBs׿TO9$od_Y!٘C_-'W04X,镳`Y>t|(P -Ԡ-@0 `Hh+Nph>C?{xbIzeYjIPIԔ -ꌠ_ c^i7w%Z;"P /2)Od_dGd9$WL͔bD[6SnKD|bEiwM[-fbňoLDpIa}fx3Էv!39'?EDAj]CΪG.OZ|(D;\.x _Hqc!rxy{f3MRKlrbㆰft6RZ/}9hX K1~Jf^bi$;"d޹Ǻ8"2Lu'"ҙ4d"")S47D"EGlb&0"쉈c*%@ Ђ`# ( 0? ,~ y^Y$u)Gx2 Y5H3L`m3'_Y'Wx2DŽӏO&1"nsmq=OD6ޙX-X/X\+#?.΍m?t""^Pk1,f:L"ԅ-">}VE${TNp$LJ\7%%!N: _utYq]T"7Z|8]}HmAX۝+kM 79fp99'rk+g#G!"c XG _EUADrɉե.9;ܓMX=>_ `͈`"y):zrPAZЁ `V7|GU5+;9.4Y -Zސ/3ߝ-'VIDxk`[Ggj#+rXDE$ɔ\'e9F7O/ X *ROIE[,uMD/={%4ÓuI񎤸x;D{4:4;~N )ZEf٫tHcPװvt}a%3%fS2se~6H -*XQ7PrS*2*2?F0tv"-|ٯ")rNirQTѓHF"9Er0`(Q$HF"9Er0`(Q$HF"9Er0`(Q$HF"9Er0`(Q$HF^%(#_Ϋ9"Y -q֐'9S+o^--d"Dk;gkÜ3Wp IE_՛FH+#QH";ŊHT$r[*VC[|r=S#t⦈R""?#B"JQGH<Ӊ̻E؈]y(5O~.ȹ9H]b5P{V=u1idMxtB;& yYioV0H2E?F UpH{gX[, ˫GƶFgdZ29zHLjH{XÙ/y(zf.vDd!_<\70 = L#"G "U]'_i(P -Ԡ-@0 `Hh+Nph>Co j啅jl*Aۖ[aUƛg&IDykŶU^"8xW޳3+NS#R"qLTZaZ߶}]<O<8%"[1"'DD~-"~8B'u}DZ7vVmHӫ uDO~?څ?b;lHZrIMaMERI ^JHŹҼg|fӻ Lђw E+UYsXי_ j j6M>R k)z&Dd|He1ecmEDD{o"wgJ9T|s3"RLѻL"7fr~/c*%@ Ђ`# ( 0? ,@>*(^īu@^L PhLݼy@TWx`k콂Tpu -!^<5%Fxp;%3wI.1"[-٣gRgj$;Bbg5B""#n[&ܽID깈llBD$q"+?iϴc9MgM< (P -Ԡ-@0 `Hh+Nph>C E)ΣQ(y(mSn<}m$mzϪz$ySC :%5Mѩ ^&9>sq{Z.3z\XEMax;>>4Յ a]t~4sJcfS]AXKIYFV\Ҙ=f^@^y#H&yif܌H^ilR}~r?F&c*: #"ՇOc1@T hAz0L` DBDC Xvp @xEP6q^N%hJ8Z3 j[ZȖj9YL_GT*'gMkw7qtD_a?S:dJ<+8\(QXߊ#ȅnKE%[}m8{%|\-#+wccj8d]r HQ4@]^Q -\e)kI>vT=^:w$%tZ`Byi'5.)}ƛwM{뚏iLpj|#aUCczᠦ}-C[ؖ[}<]_da.)y^TF.N1GD^uk " "RyDz)>_Hc9"҈LUᄏDD1@T hAz0L` DBDC Xvp @w4 bITv ^=*h9팠7<|U\C6 -|>\^-"ID-1"*"oY޿+"HDĈIdl 똣_!w47"" 4k_g(빑 -?ir|M.|SR|̗~'`btY':ROjrJ,N=&̽%/_0Tj_&V}?k;JúӵACnO8s42s+&{Ȓ^DdDmiw`D5"""2G.zozZӬ33Lʘ}WBrY)H.Hw"RT}"" -@ *P=&0"! -!`;8n  |lj &fI'rwcgj̓eT<['y0oCsq(O5s !mrѻBw~cf>1"7%"ɶ>!ެ"KeDE+QbE>nz&_~wRG+]Ɩ~3/1E(_=yD(v3K?2*RpTWr&XG(LB#I:L!q!W[]^y9b]T_7hNUUꊠ&*mrn@2h{%l̺lizJf./e-}wO"+PtX+7AEQGPz.98J$HGPwڊ(#g+'T1@T hAz0L` DBDC Xvp @x eStTlU:qiA79rj6g*eTTdVI^ɓxwG/q:i}lV%9G$*rٶT䭇Ϩ/O}.s'DDD'w?I3pWrSê)"oλ({锋ާ0~'? P.ejj%Kq?zfz.b߼ct|BJ:B^oRXϴ߰XseKG2T#5ڢ3t]cGql%_\5ZZ:&9:tL&9C##77!"U[H+)""rlreD$!"UzՂ0$7{( -5h@ :Ѓ`3X  b -6\< 93^1Q]rS LNioFl q3rLI"2O"YY[o_قwrt1k@ze[>ъl2mۗ~zfêOV-xmfQP? kJ䧘D؝D,3veYuוƿ#9v ^ם76Oy=guGd}L"XCD&PNj>Lg֦ACuD\j֎I\'׫NN c=r'"RK.5$'ɷEDFnED,<ƷB6E2 ȉCdg+:yG+%@ Ђ`# ( 0? ,@>NEGUqR%.5{UFbMyUFb~[*R?aɥfc=c;$~=ȱ"S?@E>򓟢"BE b==^=@M"v%ťŦzcCgV( -Ї2J%2gê[3ԥmAMbX; ߑi &UT=FvʣFn*Eg^svui?"w<,w&9Р390)c -WP`*2=yrWx(P -Ԡ-@0 `Hh+Nph>CGD傠Y'/ *0Mü9,#"dk53<9(;xga n"_|[*a$rŦ]]+$Fe1"[7Enu+Z3zu6nED9@a=O]o?R܇#:q%x]dIN?KbL6wAmgꉢj5FUUj%]q8ލgM;F޶wC)! 6C0!q0 K1PBƘr0ǛpIfψdfplF{^')_PI5DfgYgsbnX8TT7=m.LZe9oP9:|='Hi#O@"m"!\nf"u Pd<($rQ^*|P@@b@,pp?Xȧ\Յ;6AS%j]/ -O0͊.rVl^M6R -**xZx_HȨȔlȕ 1ȇ?E%]aY"jY"<(KD-Kġ D"W! w,nS8.,6Ȓ,sJސg6DywM"{!!sGd]ċH16zj WD"?2 yT615) uMg%R_w('ވ$2@HMKDpn-}l!"b267} YE%o/~w C"edQή#{\ }Rϼ?$(H+H"#d Y;(DJ^DgȺQHd -=0#03+;p |hp €b8d@aQ D@v̉A\+ZVkn4>KY"n5)ҬZ&YRZ -5}:T*UyI7yD" E61EvE![ -ٳ%*E&Eݺf:aEl"`m!3X ,~W{]d328֛eoe$)LJf"-oRMϓzrv,]O;bqi<6G,U6kmkCyrJF.,<5@nygEj?^:(,,!H;"g i; "sWVj@ 00  \ < ` B x R5S6O9$[`imY(2B&#֋<]}8OO L v\|W J イEnTȭE^-rEnG93#K=Y"d'Kġ זDKy2LdOykynHG9cH-H~ Q̑13ϺE粯V&OJK0 )"l75!ɈOa3%cjenJl9{s$YQ{>8u,63~:1SU5E~N//ˏܓo<.P/L}TKO_P1C/}bԪ_|'P¬*XɄ*T71 -O4kfTw|P@@b@,pp?X|JmriM9]ӝ!/C:JD#qPH^S汖[6kK##} 97!"vq5 mVKҲzt9d99a䐲c!!ǑCU|P@@b@,pp?XrjH!Q]93&NGwy YT3uN=Y"ՒYrJEwT")+nNnͺn%7kD["ۮ1\l$vjv}Dn61sKrnR^~G&#>&Ȯ$H.HYG|jw=⿮pt- -.\_ܢ ­H"}_μ}3w!OHl3U $bI$s=I$-!tIK{S2Ʌo"OHiHXI5~ :K7Ov(h_'J棹K+6_lٿ N2IdNFD DȘW㝐\Uv$RKH;HM}Dz$sg4@ t@ L , -l 'p70 0Xz!$FLqIwTKe)sh) Vxy^杖ܝgDM )Y[/N j1ԸQQA"_tWI"_OD I"5nP V")XY9,&GM缧mI=wJ-"o(#ȏkQ$)9kE._Qϴ'Lq8,2]X$2r=YMgp\7K24zcQdNsu窧ܛHHvQy4Cmy{//~aƲ,Sh~t͚ߏ(2zOmdP!,2Ӻ9xlkuw=GF#V"+G`Si'ZG7[}006;ԑUE.Z-=0#03+;p |hp €"~EQ3%i]o cG"3ɖa-ZD{39W-!KvudYİ=&2C(2!pɯr-c<$zH|JJ9HKrH&dLbzzRfZb+콵磗3*uӻ)D#ھ,]`X?UЏ(r&06Y[HaR;.!z`ntkG&Y=O3y#ꙢDK}dӐH98A*H=sP-=0#03+;p |hp €bj=+Q|C Jy\2Jz\*Xh%D"uɫfwi|9q@D|\ҬW4$-𶿴gȏ˺Y=suԦOPhǣ"~F$+Kw9ʕ"$2$0#!]WE2Č$? CUozM2$ӽg"K*uW.}H${)K>]ϩ7C>|c؜s.b?k$2qRt.C"tNUqվ@xժVHa$H#I >X=$2ti'˼{$C_D_Z>S (Zz`F`f`V`v1 8 Ѐ,@AĜ咺Vz%ML$Ɉohn,eXJyR $R(2 y.F_L LtHMyPzD(Y"º^= '#~xe )$P,!$rp+$ӯl00$b;rAr{DnPH"$=Y"7($[DrȭHn$rԳv&cr&Q  ɯ!w%T09걳, d5M&wIH`ٴFV["K*uy.jOHމڪ}_5%jJQ<1A9s>fu%H5cuc2Q$w|$㤜yF)%jDG)|5{ )iDjHbDj@ 00  \ < ` B x h<,t"9Y>#Y.SPKT#Jh #2+p|B.grdeidKDej"ayg3I~$rAaӶ D [t&\|9 ='^G8yW -0,O *!דDD6cq),eⓐD?eӓR%<<6 b5KdD;zq`PS16^gx5l.XPΌvY8$Ҿ^D/\D'o!fRΌ$R>AV!KH"UO~ L̝D[!.2 ʶ|P@@b@,pp?X %uD5rI[ JE}hy; ZkuYEΐrQrqS%ΊAmɌ&1/Z7z]ލDnSMȍDnSmK$Ot8Xp`x{Yu-eX?/JZR#+ZK"HW䨯}ID[rf]HA"{SP9zgSI޸$/ d&&x32 6i@USѷ3+*u3"~WюUf -^[#q4BJ۫xGoAr; Tqu@"]L>y[S$2^@Vy!wd]H`g HD?D,US}g4@ t@ L , -l 'p70 0زv6YLJ:I(馒+!!F$sdiɶAMErjct{}M3nR]~)P*nNnJW$"%翻ƙȆl{PaGIQm4P$eK$rGfoC"r)DZDjS9֯͐)a Sx*ҷDfv]u'%&r&&-& GǧqHG46X.`'U{.OW_x=b+Ꚉez0$2$o9=$sIΦ=.%HyG^.7 c;S*6DtA"^ʩDZF\.\mT -hX؀8@ Nn>4` 8AaDE3洠-tM~^049tJ4_,)g{)I"$t9%z2 -`DEfHdEA -L -!4 nU!Y"_R!Y"{d|IA"["ץK_^JT-bI=D)H%r,s -$/\4g*ze޻+IH伎\ xs$(_a_.@9C_WOg\F -k$fАH*tFegЙ.loWNLJݴ:{ih[t9at$b\2]86LE,oڬB"=_H"!vfEȍs(g" - (KD㣼j ;v;/d(ѱ8' +H~E9s"<$rxU9Ogp2LMeRX.M=)'DLUޏvRd/9"",SN^<^f:VQۋD -^G#CV,B[Uu"A)^D|IjIdf$GDș;$2Dj@ 00  \ < ` B x )J+i^);Êh.lբ}$ojegX v] -10!sPǧ%'6]a%-u$7}{Dޗ%rDl[rzr&$rZyLxf7I@$I?yd+g?pgK (-vȖT#\4McLcL)"$aCc Z-_W!k[YD+"""""""""""RD\,""";{gp<}g3  -ut^x=I%_=䴌+InngjҞ wJZruc"8i{)cXu+v SWgI Wnd9.݉ݙ]Gg:N|Rj3%=S 35OH$snO+tݵקg (;JBkS|LUuaMiJ?EO\fƘg/3g͟e{DerSE,q]dqQOm/,KsP9{WzUt˼=QW:3o7W3<5 ->Jh^VU!jj` 4F0,`CDB!xЯ>1qaq8mm$N?8"gZcl#\xHDo\dHg=\c;ǹ9!y&9t\ D!!!M;?G?#d? G{IWN?3?g$Y{OVO6O"?E0졩ѡWhMB‡xCC|!А@hBߵk5NSO=[׿sϙ3뿫Q]:~:u7swMh?tD ~je?o_qfx3xgΟ˜>q6.J`.0}Ŀ#!НL0u-uZ-˳]*裂!|毨Ȫǯji/zyi=.Tu7}i<0s'i5AE o *fQ*@ZЁ `VA8D@$D.` x/*0uR!I3I %âd̖LAtkdkP̋.):GrL1m!J KoER劄Iw䊜P\s*-ȯII(rFX]9"ߐ#rDQ!"YrD)D$kG=paV"2szDckBD 览wgmNz<΄$wKNq&ԔX8.=Ĉe*zF$sz6"2sLU5EZf(rR-C=B#ƏbiUZ^u?"22=L(w"w!"G^ Tgv{1{)(ڌQP"P|M50Ђ`#  !"! - p nC kMT'2c$l:Q)i$4ѷ -16'J͍2hl5t=Hv)z8 -%WHA˻Sw1K׈/U(D$YsrD4 -Iޒ>yID*Z/\FG|_"WyDE&.?"WmIE{fX"f{~Q%?\o\sTؘO2Lq99s&pX qtw,ǥXʢ]ݽ#"U+*uۙ#HAMڮ.7:@eyKyTd6hZnzf6S/LW3WD L'*9 w"XόݎP}Xҫ"wVa=׏d-"#"Y/"˯U_S =&0`pH;D\8B|@~6ILUcD)$C1S'1#ibmmt=sg${].9kĽLCAs%d+\Z͑++"v\BE[R+B7*"y(r(K9"Z\.GrD -[rQTbD`#"M%Dxq/cPE»1\n뙲)"qaR\R<Kqrı\\̉)yWCթTO1MHL]ŋA}׻LSSX[-uϩuUZ[(FC;JvګkDtHEY([loFDUD$YZDꉵk0Ѐtfl Q`hp Xp >C @Qfut$}%14Bbj >ђECDDriD}I'J*'^= "_DD04bi$2|&9DdDgȱ/)L"c1̿6k0Ѐtfl Q`hp Xp >C 5kDZu, -a͔!zQ_. 4%.4VN#2Iw !Itt2"W&x~-|DJD -#(9"#-37[Τs oYe+3rEF䊄+T$QȈ\p$nIE>~ݯ"w8X&+G zrT'{y>)ۂQ]Zĸ=d'qH:X&4fjbVdm*2{8S3^$;vwP_0iXy;X|wTsOrh)lUBR*>F{wD-/ދT""Uo`{H!*2Qdi7T^Td)g"{*@ZЁ `VA8D@$D.` x/ݟ#^&~uuDDc 1.2&ZKE[(R*+EG+q lq^Z;$9S"W9S"WoIE.y\Qd+~vvU)D#RNvKV7^òw"鵈ȡϗ`IO̅߼7QFD;E=N>6uij|*{ҕն]me;E3YtBkPvv.kx2Z#f"}AHʊZlDD$Qm;E!"9ÈH!6"R<߇T|,tVu#"MHZڈ =T?|M50Ђ`#  !"! - p nqci\"rw:Eĝr1xgjBɥ9R1Ƨ'oCjݿAD35H=AMBYy3.g{}o]kng~ҥ!|g>ul DD$R>zeu^Y-뙇'T|=Yz{DDd-D\ hAz0L` Xv7p?@T}#aD_] ÂX2͋b'>ѶH'zQpuY9q4H7D/NjUOD~'GĪߖܞ77bRYm!zaxqUgWE]5>-{޺U]׿w\K].g]nlTqm>Ty߫bޕgTwU*X}P<"?X}_R =&0`pH;D\8^ U&YX 3thݺ}kvcL1KۺڊchBb1ޘGEk&]qp1I/t{kܾ~WGZl - a䄬A-Isֵ̆Ua1s)ONrBQ;9RDBR'$KKoμL83?^O+p"*"2O/־, ;WØDx -}n{V "]ڥ@ZЁ `VA8D@$D.` x/d]V%K^~I3%jEݒo ch:$DK>IV}"EWICkDd:SJ~F|߿P˪M%GD#ՖDݷ4kQD^("^H|*"){㔏~,^`|/ y)"|rz -F7%%;gI$q\Z\LwRDrwN ӈR,ocCD35K_dh5t]}A}-ACQG?4WZ-o!"Ղm!!xO9#l}g"rN2&9)"ii\'6u)^.)ř:ctփ?M2k`x?SgV1tjLM'ڞ.ɠLCaV&`*z "?ޥ6T izHQ;""cZD0"2>d!"} "X޾O':DF D>D3D0Ѐtfl Q`hp Xp >C kȲnGFEM˙;Նk6H]PiD&2iD?(5sw%8:GgV/+W!"~g.˙&pœtH ƥǧ:ݩ)nzouI|36͝ʲI |jYڕ5*uۘLM_I6b]Da*8X0~i=r9uRMc934]ADwD OW!"(Gw""(Dd>:3"Rno D$"Rفӣf] hAz0L` Xv7p?@}AdHXd"ѵ}hXDӐh^-ĚGlt9A#LBt#aݽ&zzV_'nBDJ6= -GgJ6= - bK"2™O"6ȳ@E~%W{ -Aȯ|O"7lIEQ\\Fp"eg~ʣ"Ld9 >3.yGE3ө"I11ql,q ?9=NNuE_H629"ʣY -yYȹ(rBD$"7i ]YMՁ6F3g+L"C^˷g -[MnQ}e.xb}َzQ8N" oHs~4367ğNfIIɬ;)qǧ:9zYRL:d 1'=kl'*u{v0"҂Lr߃p𑀩yj)lTY0IxQc{w -o H -=d\54|VC&+.D9Ho?~_S =&0`pH;D\8^ ^Dz0]$hEm/gDC1Sd!%Z-ٖF:ziC",G%_t>?{$5{toDo7[OBN݆[V{.(D:}JbK&N!ݗ̲Lg?]BD}g~ ADZ|S?Es9hiQOgKAN8'.ݝ쉉tM>kWqI9K*-ۙʿ#"%S3A}-ACxq졀ŠyjH>!|&L"tbGTɝ;S4"t*=iF0N#27܍Lno#Ոr"2ZE_S =&0`pH;D\8^ ?~WRwZ -" nNWJJbM<@,u:r6ի'Wa{XOȏKI*}ΦoI#oID<śgBXX6{}lzM_;"cJ˙u1&DJџyOW ">)&Jo#ȥ#"ᴺ&񞸘T'˧#"nɧxt*\iD/QDd.D޶Hk}g~D!Hǝ߾wIȬQ>'r##8,e2+Ymtǽ7$}Ib$ĵJDoʯd9"+DrDB[+D[zO5l&?)L"BD",wg-D~~D/`9XL^Ճ_{~g.5`.sɟ/$̹aaIl'R4'Lp'8㓒RI)1)'_i59z{6&Apf_qP{O@tP_ѓi_4LיZ~Ae-}PU.+Vi5Id.gwٻT|OfsCD -q"8}E_S =&0`pH;D\8^ iY&R+I!.iFvjL2KIIzM.g$,Γ "rg}wD Ii1ȓҋ4ӷ$"r-p9,gtrțN"-Hf!Dx&AD{˘.*OI˙?6L`SDSXwactg2:ٔt>-9.>ÞʈGzhDƎ3 "5ejr.MԷi04LMAsKV\'3 łmDDD7]UnAD/)xg\ct*~FD$PG!]zBIj` 4F0,`CDB!xH݂zHdIX#^n6G =%ZGWV^`gGq5vHpܸ2E|3p g(,gozurO -˙|rD#ݙoID "7cv,g'VYt{>q?L]``9@iuY|zrl|J< S̥ROlvˮV_J]rv&D$Hz>Cr 74t/f6eFX`+$2[1D{cGHN{͓ -}/>T}L/>I7Y,g-HI&R*1t.''%SS|l:&8ޙ؄ԓ:zY/"26&{ -ꮢ/5wd=[w}yN ,m/Ra-@0 `+ " <|@j#3JM]",s$C#1Itnɲ"D,!E$:J%z:bn&܌ND Iٍ6S)lODUm63oID~c'|9-G$r#rDak?ƫta?)?{"$3{s#3Dd_(1"j*}l/"܉CD -_O=Ida˙ɞ=[$2T#"3#"SYHk|M50Ђ`#  !"! - p n֥Z_XlVJiBDghF׃ cSAsVK*=>!| D]QOI}ޝz i"w|w[$8}mG'[&|M50Ђ`#  !"! - p nC kwg$P"6LKD}dhI4f'%s]eX]j뢷xr_DGqU$+o<|)IDWSD|Dy9S+Gn[kp)lOO~¶cހ"Go)D-H){%. "r+D -D^G?ED* 0[>".MKqt'L cbC @QL) +46Ke}d͢iX""E"4"mFk4GV_%ѣ 'y'$N6DTDI -#(G -ٽ%=#6< Y7=O$0('ܚI(D)H/Iѵ(տ]5'z,y+$_Eq޿뼿'GM_;=9"g(Dْ<}%A~vbfQH|]"rݖ~oeG*z$QV^DX~̹K0,VGr|Ljz\35՝R1)nz}jRMN;LGٮ7G4TILDKv𡀮ᠾLCJ<`| hΩZߤG>&»ĎFDd]$rMzYu"2TDAD -AD*Kx>DD -IL"#"G"@ZЁ `VA8D@$D.` x/jψa[FEHH .9_4k!Y;FdѱB\m3ILQ>JN|rX!"rD67(GIJ齙-;NujцN{MjJfxڒ||4gw` m"uE "G~g.(2Kػ0xZ˧Ĥq4'LcXg˧&x<7puћ3M_%S[S->aLATdꝠyQeU[e>G_^5&އziEEBEz&^ʈG~@޵C7WFT4" OU_S =&0`pH;D\8^ [{eĄ^$L 4킶Yԕ}dS񶋖"b'I5}^YD{p%(KwT$ea3_>:S.Wl"*&W͎XMے8kGg!nT>WYx" -͒ެf- WE:{Tʏ,BxE)f'0JH>!%>Op&'\|r3)-6֙{xw,Rkvo"sGUmLֻLM;ڢnfP??i00ueVE/zs0|4*DE-%^y3> QO_ DEQ%ZgP::\*5ը]8o"93),ǥ1q΄dΙ'&tRTGWGlL.i;8L5KΧ;LcpoC SkEƉzZdHL8uRb(&Aє+%˴h=Dl,{|Y=Lx-~U1&A#c_^W)WĽK#trEܛ4B5O͕'"lxj?pfAȇovquAȇziD՛ܡ@E~ͳg=[y˘|8Fȕƿr.a][v67yTORrRV_LpqN.ٓ'$tqy65*uދ{jn\On] KXX6|rs{]Ogg]i5-S>kxu4>w )y|?r%8,|_rS2DxQj23s{qbRr+%3JK|It!o['wT(2 -Ϸ1-two$G=w)K3֞US[m(%l,Ai_E -jxkk 3DwF"#ږ~BOV ޅE|DS@G"Ld{h'HHOX['w(T@ 4@ t@ L , -b@,6`n^?@Aϊ%4lUnUO"vX}h'G4m4k=};E-"؇DVg+OV8ao`; Mj=sufp!WރYmL{ݍ񮹽 ~{%Smݵ!~#=8t{WƽWz测"o` _!w̽?!nVOBʺ46Lz&u%y<\/_xH D14|.RXۑ,˰9li15k=#|Li"$2mmmtz^DjW -n:hn>i"QHdL%` -h XA q < X 09z=%*Df\PyAHӂnY -b#":@T"CUu}8򈳒'x -loJh-+3jiI"wH$^iI"wH$~C$?nΈTɬ[$ҹnJȩuT"'?I'_${ 376!wLz5ϐ d\ޓILbS2\)tYz&?7/VD:?ƌuA"9O4%χe9vg 3GơS]\U$RR[k}Hmm*o}q7+H}$Lk;v.H|,0BB"+=MxL%` -h XA q < X|#!< PTfDP -6ASM EOCh'E,+ĚO"D{(BDv%ƭIjH"*$Thd$$Thd$t"-˰H/o"#//D=S죁v.X", ,R Z P5-=0#03+ ؀8x !< :ی(E*&z:F`+JoiRٔ`S;kHIp\{?S"3QTu=,YddEm6PD$L\J$2)s07Jȟ E8/;"A+P{![ ٛjiDrbB'#9FΕHpge&ff'xg:=8P.<,(yz,MaWX;~>Gr)[?^e9c/Z̃t@bCo-E=Z -E;=޸{94$Rv)jEE=S݅(2J',"On/ PϴJ P5-=0#03+ ؀8x !< Ef~,҈|Q ljQD4~R ZsiɥE{Hv̈́m#D`1 kHk2+3n)IdTϸe$rH?"k^򾯉HdEg~8J!IDxR>܌#!_I"O/B"AD/ymǂWHQTyßB"T޽)7)!ݕb}>ƓILMHKHk|?Ff>8aOkFSvB=4/ޭ]J@_r&`X=3OX_pX1e"q wνѳB>bO3rZ;_xLSK+~M)-o *dy"D%` -h XA q < X 0SƝQ'3T 8ՓN͌SǩqNSouZ]2N1c8Yt9=N;_z`3@jdRH$H))#-@8eeCqy"7^'~5w$w6D /5F -92"'ksH!Ӗ ܀RϑBx&|'ᛪ=×LJw%qbY6.LqyR3R\ZtL#ӻM:žl٦vN]SIy%0634<@'[+c^B -9KOUW|֛>D 51ͻBBBGB&jBvr;7*֙)*>S jZz`F`f`VbA;p'p B x@rW,4@fG4tDB uĸ,E )B՗3[WzKDP -ሹ2y2r2.6D"?f?2)j?JEF"sͤwɔ2wmDW(/A"yO\ȎvH$^ Bh!~b!ۖB!7?3!)˹8ēt֓xDswt6-*Kۘw!2/fk^ k{u.e;>24 s4MUY(e Hmi(xVIhB"oS՗ ̻z#X`UHdJ tK(e&R P5-=0#03+ ؀8x !< 嫭fW`2QϋnQ/誈~D0t 1M t,NDE[D{@GDe"=U=K*-BROl[g~sݭ3sy530xݫI)^jgr53hI2/B"G߹rr $RL@"_ia&Y2%%x33\!lrz+%\?eޛ.5\(D/pO> ԖB"D z$+훂D>ycSȇH"VO&|0@@@ Ă8`vN,@@hA9)0}D y͒]u-D_#yc`̓M"P[O .Y/O=&pB`<χF5Dn I!JI I!JtlDN6}NI"ߔHwD7e$RWtL f~ǐȁW$rˆ9殬83A+Ⱦ$Gy&x.\M">ΗΥ9"8וMJIdӓUq3z3S$ҟSA; -&-h*^kw&ک2T@"Ed)G!T# 1~~![sǚDwmH"t@$RIgq"H"=(fE'D?Bi9$:H/^ޡ P5-=0#03+ ؀8x !< ~J\!̘ԓyA@ eH4~2,>=O{q}LL -<D&CsN3$8<+I{C$F}55,3fOjC"OjcCG}Q_=L>mTc˗xeܽIKE -+o~UO"2.֛w'&)I~o7SʹG.D.n=QW6[>Sr""t(+EEAH'"Q P5-=0#03+ ؀8x !< W3I`Պ.5{m[!K`,ĚO!j>~CV{HT IEWHeBRuED"-뵚$E21lE|7jz_WItRȼ=DNJ9gCwn&R>ș/SOA+$0s_uτwvB"?5 eS$yE] >.%MBmoDgU_V(J`Z>lvTnl,ùqT҂(2q;4^H Jz(2"$2.) ->DJF=S6+0{{Qd)ѵ35Q P| 00  ؁8x8Aa2z(F-[AQ(D͂Hh^ %b(sVsVˬhMyduYĹ@Kgxk%[Kb^ 5o]_U1;MD NqoLҍ$g䚍lػ#%]r:*4T'HB.$-_lD:R8&n[I1A+$nCihaMugdo*];JM`]^.-џMOHKV"K^.+A6f=fu(gn"|Hh!$t"Akq?sKgg IdhU"pgK (-v̽TcչOb~iD~{]S*)np78syG 8sÜe뵥>[ODqo@/ ~NH$=KMOIHs)iudORRlZJR'L4T]sbf>)_)zLN/WFJq"NS<}tS\/a /\m?i0?^)3W[# >E-v_L ߲6#̾r^欯˒̒+V"g숌n[k|M7IÅ -%P5-=0#03HA 'p7`p GL{(Aȩk< ANw8Aߖ`Ŝc,(d- -Mێ{ κa]ɱ z+LN rSBΓ򠤐%|)B$)bI!_ -kE!{̟m_XB[RgB (n(VB. _lB<b+| -Q)?r/PȽB!'}̅/~O? 0 %ߠsI!n.!5.%!w eCS< -7gCNLO T-dɞL];ǃʞfSgs2^>2rHߐCV)k]E!!CE 9a9n䐉AC_DY:WRT( -h D( b؀8  <>< >J9Q@TF.4D!x}>1Lc1s ,]n%j^FÂ+Wp -lYNWKB|L"$!I$[Y*DD~-IDF"m/O?f9}} H9?["?$2 名Oh[$oL'sIk}n#hD1w_ܻ꧟"\%D;\H -$֮sI"ɬ7%9u9b 6ݑfԄ/Υ)}k%2$S,bf -̌e^PW  i+ԍ45H_Pt@"5(fG,B"WܹZC"-M$$Z |TQ-= 4C"/C"HS)1JjZz`F`f` -D`6`NnAXAQ+2D9#)%\A7%{xC7o'I<-XƨD*$2-X[ -T"˂݂Z bśI07I"#$$0tΒHX̼UHwH$E$-0]qӐH. D!gczO!$e#y\Hj;.56)˥:$8GZrzMKbSΔHu+%R,Sf 󙪉 u]AM͟k'&3u/g' ͦw |dK{bN @"ϐ͐$$w VvWzD -!:H$ iD -%P5-=0#03HA 'p7`p GcPT vACt+~T0&)TE K%2KWDu>G\3{gx0?- B"F"0+"煑v[$II$J>&|FȨ$/ DF%|9DntܗDDw2njB"CWq5$$OgA沛n`$Rrnx\)XG;>+HuǹӒҽ))g-{y]"wf!ܐLuw}P3<t7e+񐱩ljI=GA?T&.Z:u 雃D - crWMÝHܳ˙8k_D֞DjކD_DZ^D|J"xL`hX@$ X ؁8~Aԉji^&vQ[#戾DL1ՉV2OUDHdXKE[h |uBt%9Q|3?KG6K"煑dфLtDd"$qr5>qD~D.#kH0f[$.|IΎ$t*=W!Z9kO^ ТYiG g/rfgD8.)9S7>֑?=S"YFDޓ)Vw1A"'3UuGo>S7s2C?$L8Ro64A" H"H"K@)9$3yB"#HcHכH"ŨiC"'DQΌUA"C"@"(-gJNI PP 00 Q +;ppx| x@FO])YDUDԫDLD.kC'1.$KHy"$2OL~}8;WqvN.3 t}\"I"ICJ"0XRQ"J"i7%&Sڛy[gH Dޖ$0BL]i%RMhs'!|1$r w>O!(g#|KI$z8ZĻ]tzqqnL_DzDJgw14yy6nDgHW~ҏ=$22xlD#>G.܈$Q$=1> ,B"/A"H"˅H" J$GD:V ~HdH HdHdfD -%P5-=0#03HA 'p7@8? 9D -W0c -UVz2QӔMu\I44iTxyP!a*q:hKw^Jt%Olk}W'+>Dn$„ȴ$-7xE"|n$n3^ÒD~-IdYQEa$$b$rQ?D^#=~qD.DnD㏐+H8taeYYO.O ʝWdE?E*?1_-E駈"L [a.d.)%5M8<]lBR#))%u{,DwV٭ -n)Ev3o=Bw2ԥAMHH[nl6CB02.WMt#𑍏"tQ䞘{UH`nXNDDnXd Q7e}tQXnyt٭JjZz`F`f` -D`6`NnA2,(Vf(ˉ.y?L !T!FU(D@-$aO$z&FE(7HD$J EJ$$J EJ"CIdj^EҢR L-I1)\&ޖE 7ݞ9ȵcǸQ|@"/?1_o1O  2}vg|KDbRY!$zux= 8F}]ST"+2E?v1#o@"eT5doj i'2Y%A[COM#g-||!$ȑY{{9 -T-"EfHd$2;Qs_$$4+/A"[i$T( -h D( b؀8  <>< tDeDS"jO!Q_!Ec-15 -,nVK]9J\;OdϨ %"KI" -I"JI$b #_KI$b #秖"ޖ(rŕyHQ.fHe"rLtW`gu~i,|c!#@/z\+MTXWZ<%şUt|-e"bvL9Ƶ{ףYڮ\~zcZ57E&+By%ӸzvV}bX.{T{3W#|)4MO˃+kwderS*:`&` @4V`v.,@(w*v2;u;U;;5K>Ww44M>sNˠ m%;~gNWݴzvr#>ߌ? E,,d."a,,%Y$2Eb߾Ea6ȳfF.Y:/,)|>Evq"'C7m3or_EJ@A{}W#,+Yb9AzRt.ǜK`S)\;69ɛf:5EWeŠL"+!LuqKP_;\e V_ gͦˍeQ>rRA,z+cZkͻ"G^D9\rLj?&wO<,GWE)MDKU",I7hj mXϊ,*} -vJ\>%IDܤUDUD'uE$jmH36h6]:xF0M۲l$mos[$[E_EA]]Ӎ~3s9Ҍ(rM$r4tv.Qd,~T$%oÕfl -L⥿]Z -u''%Rϊ"]zjL2)zد7S|P3־w 5%f3K*>p4 -[u.H骂D{PД=.93J[EM&H&*fHdzVHdSc*@@@"A1 -l܀@e(S\"!AE4+D[(Z>W4jѴ Y*RB%R+Z+-ػD`kMt׊<4\KBda$gV%ٲUmI6ȻKg*Lي$KI"+D,a$-"ۤd]HCH"O]$?y  3$Rl:g\too*H'q8bRqqIgJd2wbٟDzD,ʥ[C uymPStHNnd~ޠ;dlDڪyKe-z H?!=1K{C"cH=>$R$2\$8Cg ?w{zDFHdD -%P5-=0#03HA 'p7`p d>AK~Q9$VuY 1ABp\0 BѼ[Dj%V b/s&z7E|#,*?.0ɕ$KIF"Dx)^Δȏ?zp3/twnD>7s^Uٵ-Il"'P\ݐ~_1_$=̓G9uܚ:^z\鎴dW|u)}y IJLwnf2LU2y!v^ MǏvUM|sHg ʙ=1Ҧa}8$Rӂr13cDNt)ozKƕM~hj1ypDNI PP 00 Q +;ppx| x@}:Q9!:hVUD1K4zbKT"Z!ʉ&ѹL\m=#݄_)犁3a6y$+IDF"D$#m7"=" cb"燱%IQ0XdlCxo7,1_mX$!觟Kge=ň" sqv9E<˦%8ظ7!>z)lZY"yZ]Eʗv1tkHUݙ.|**EB'2AT~ca2Hoi#kG=SKH^j#,Cwy'"9T6,Rr-Ea}EEjE]W`XE -%P5-=0#03HA 'p7`p gSADCԝDsDv -"1 ")"IDF"ٖ[2p)E:#6$y[䨏1|oO?Ed'D(ro$xOrz||J#Ńii,]YMsJғa3%RrҺz&=L!=?3P<Ԭy*C_HPLȘTV 䣞)}/@[D -V4C"eB"O@"%i]~l>BWVW_DڎЙ"te($yD -%P5-=0#03HA 'p7`p "HQ i.'e^Jt'>jD:۬Iy4Rta6'ثƉ{Z` Ouv?*څM ov3ED.r6'm6h'IDޓ$r$wjZQ;[M?7ys=\H6MJMNf]g䝼iՁ%L;HxDaH1d+ql*3Ejxx-Y&]Y]D{5iI BkIFH:IdtQ<9AN'<0@ T@ 4@ t@ L , Dh \ X? Qr0u/Ѭz~&b<"zyXN$RLV+DkhYW8Dרn~9NїE$+~3_R"m~aKl&#s[on>h~FHd~E$-g ےD"P|/t cbo3+~)97{Z(grtKp9xɎt芍KMIJ8S"Y&rFD -iʹB"=Te炚ƐL\gP2e6[f-H"3'!9H+ckͪD!qD& 1:ݬKrOE!L7CUfA"fwDS*:`&` @4V`v.,@:?OM]6JԶD}hD9CLc3EzhYf+VE{l%^.Ldg)v@%r[=%|VH$Sg%|!D2E"߭޺iUE>k7a:E[Y-Ǭ^-"79@V?D=ōzqXdQDwOa7EENΩU4zדqlÛd.ŧ{>WwEJfw1ga3UP75!mL]^ka莐Vin35|d9]{{fOQEVӮXdp]EZ`Y  駭44?(}c*@@@"A1 -l܀>#QT(U;3U"gF\&K` j܂,y,o tiaן☯F"?`y#\v/ -Z%,Ry.Y$-M$'9x/ܑ98oJ#\\V[wSԬ~72UC ꎦ5͚/d+4Lt5fS8, -񑣏vʣE_3<͎"sY=&wWтXY>EE:߂E^E`NY PP 00 Q +;ppx| x@}RQQ%2}ZTu~"=@S&ZD}h%ƲDSh.#49B"^>} /ij@RW/M9?d6E,EEta,b~h0>$b;[I&I&<ߴZ(rNiؐ&% -oClڅ(Ϧx_ZDv9˛䍋ccqq.u;RiT65%!>N孺;Yv}N8"S4cz3/Vޭ^xRMnmᇻt+ջ]jlm,ћ&޿MfeYsEe-cFtq1㥉֪"2{2g܌Uތ=;~Rhq\[Z?[,,6w_de/exH`hX@$ X ؁8~A裴)Xf.NS5Yq8]-3LnHy Rr&:g+ua3ν˶=Yqh/BTK -ɑo(ab=(1m=Lqfyy|e$Rr#3CoHɗ2&|c[yMa=K^kCm>H{cn| -A,?Ap iϩwW+I$:)d&d8Y-_rf]҄3ʭT45OtOes &C?""L6f>r*-E7'ֹ?#L tQDڏ#TAiB/'CzAv| 2CxJjZz`F`f` -D`6`NnAXLF#ꈨ&z|3DDE*-gZSV {>qvW"l7!@ǃȍaʙ$\-y@ "clOϪ3-Id)a]+%)dIQ "D$۲=g#߯Db -:D-D>cvQD履dF 0ZY$y=)dieS)8MbS٤gr}Yq7@{Vצ3U=fBї2uK#ACMfpARQsV3/*km^C5SCZ퐻KoIm7{N&頯T3Kt{fj #j)1JjZz`F`f` -D`6`NnAHΩqsDNQQ-rDuafhE]&eELSW4 -CHd>r^D!'N|mtj?r)D:ղH$FvHd=$rI!$CH"D!B9$rI!$CH"D!B9$rI!$CH"D!B9$rI!$CH"D!B9$rIкD&yE, -\*mD'h E7 -ˍ#P0[ X{52j]<9rt9STyeLʙڲE˙V:Hv?$>$Rwk_nBEB͞DN%<0@ T@ 4@ t@ L , Dh \ X? i?U|a,=vgܩ I$GB\K@Iޔx{V=u Ϭ[wE.fd -Tg4uG2uscۃ'ͦE*y -Hɟetىb ZK+hs 0 )QXd.3<;B6"m-ȉJ:^Qp{)@@@"A1 -l܀SJD: `([EDuihۉ._ԯ ByBgӋ# -ihkGPZ="?n0+%K"ElE0h($]gt>̌II"sa$"$2)Id.DZ"El2hQĕ(r}f=T"!(D~HX<Dߐs*pzQux<^G'΋trzVb'rLQ,<}e8Խo}]yWi+_7,566~/Z-kY#W^Ec11+k^rmŹ2ڋ92\9]2wnnLmz?~ULO^kMmzRH7lV>L=&w=-gJ$Lw!s2RHe@1yp^9t=;g - PP 00 Q +;ppx| x@N(6lTh&vh]JOkFbm&t@#2Bǽ!tRY,f$r~I$H$[$2@">uI/x`3콋ښ'.jkf{ͮ{Ww9@Dj~1ѾBZOB 2eA"3H!V95 JKq)^*vp^.>9KO=`}N%R*Sti _T,C"5Y/g3Sw -!cߣfSaH4cHʹI+ck]Dx!*H$glrOm[2c怯lDFoDzPN)8~c*@@@"A1 -l܀@eMT -T"]*WTwMtD,ׯ*%rGwgDֈh9**&Dvx7$&>^d!I"$6 %|^Hx/mA雯J~Q$DKnH"Yzsy6$ؠ~`?Dr;y&?Ƽw׹51-.ΓHN ֓Hr8z7Γ¥:{˾?~}IL!ޕLt -i3u5oe;zCHd^,+G }$RvrOD^ zpYy?!w uLs!H#}cHw'xy0O6^~Sc*@@@"A1 -l܀S- bP 4]zH0 tȸ`^,44rn{qr3LEWHkB`IM$+H䧒D,m\F"?ܗI aN}D$F"ߖ$$09V7_yI'HS[3ȣr& (gO!dF#fuNM{ғ=W;!$.sxxWz7ɝzDkyJ$I.yzX A5dg냆GB̦*xK|dFT;߭~{OLC^kIA˙HiHd]rv%gdM7|9$r$2u+3^9i?%<0@ T@ 4@ t@ L , Dh \ X? QVSdډrRTu -.QS)jE"&b,"jbn,˗C"m4 nJ쇉3OtEmDnوՈ0LwJɏj$mțߌ3bYhIXrm"0|=sNƶzUln$"<租KEdO5T;ť^+6!ƦIl#>5->!6ͦoxK)SܱiyZd1S5|AT[H[k]Яt c!¨TIg[Ea#tQ,ݾU , "uHI5XD7`J:hnISt@ãtPs9MxL`hX@$ X ؁8~Auj@QSP6 b."9^[/%T0ֈ*\MNX-9"Z[#]Ľ*#K>Zd\̊"[-Y$eVnEnQ$mq"gE߇OE*HzH?blO];嘯+x+ꙇ!D;+ Ω;RSXu$x+5őu'886!5Kس׼H%꙱]$lx~Uǂvdd)C_P4CM+әDHKӷ!=1E{B"T"9 k4C"~w!d\~A_>=[p$2LowQ Z"ِH6$ dC"ِH6$ dC"ِH6$ dC"ِH6$ dC"ِH6$ dC"ِH6$ dC"ِH6$ dC"ِH6$ dC"ِHtL ,QU(+Ë́G=#[Ei;. ~5%ڪ=Gt拮9$a9!pŢL͖O!l5$"j5Hk>!l~r/H$r$D"H䇒DNH #nKJϣAD~H%g^0C"?M"{A=d.YUB"L|<N'%q6-]_Tu{SXOl{g֏ߍ/Ek@"TM A~8\=47敛M"ͅZޒUGG-3{bﵮcu<3 |yQ3'S3"\E^B==9EXOJ'!x<.r=)ito|rָ߼*2$SŴУ3 mADAHל. n0`#>>r (k}9N[_F)wޔq< }nҬ#DīZT$$!bC2F-2B-R%XWzt8\]'{N`LjOjx_?A§ݲaEj%|.E- Gg~Dzϐ/H"s_z (2or1ؐH&$#|g.QQ7o\(*˜sol\J\+ݑ?X.s$q^+%՝r.)|]GT" b_Dڗ2UeOw!ԍ!;ڠO!}fS2r%o)#'!tPs1H:vztixY|}KζA"}/", tEHd.H*=T( -h D( b؀8  <>< >J'L,")N{K2Q?&Ec1stEV?F.:جDPnR  $n"&D;#0$Ei #mDnkغ}NWU~)I"_ -#D$|)DoDZo {BոS3V_wz0"t2;MH̍<ߥ{SRITWl&ѕUÝ̹Y˝;sʈe$2RmAM!mѽuyޚBƦ[ͦzN-?GB"ң3i*kmD !#H]Y)GiWon.ЕNHdDfNI PP 00 Q +;ppx| x@fNqe+y0v -fQ?J V4 s=oY+ U N]+zN i-_tOnJ"ŒD"HHI[J$gl& "OwD$ran[$Mn3uH")Y㘯vHq:N5(Re 2x4IdT~NMK&x6%;i􄴸d,_W$zr$LBo))ۯ?g =}My(gJyK|d݃˴9'f^HHc>$@>, IIHdHdH"teuԌUC }mk\2Met\DF!W޵ך8m7+ݞδ9͋oa=Cy_Ϋb8$RM4>$2v*1JjZz`F`f` -D`6`NnAHթ˫V?:y61٘dXdb6&lb|uu{wayflI]=,)""E"Ŋ2V "Eo(Oϴ!u9=9|#)B̘D'E%\H; -Cᐡ$dLm\L4 `.㝓̽C;k %__˫b=(KcH䇲D%1$r˺H$x;t*o}:Ƹr@,qi%C"upǚwNiX>*y&Oʈ~?J$p lB%2RvCMxnA%B7zz\\'/\wf{Խz}&P=iY-j z,/E¦-яꋂ8~,!Z˯l2ޓ+)1/|WvumpJ:$b-\YMg>xOB"a}1$LFHnv~;/݋?.6=f],$y<鞜%_BY=頃/ս/hF:ں~+AQ:V ߤ0Yqjp9B $2_/#^; 04H7*כvytf=ylo.II`ۿQL9S9 }Pz@s@P[2U٦_Y`|țaSK|*K{^T"oMH*: V3^^[gDDJ'" lSF.}7T_z=!0i$RП@.3(T@ 4@ t@ L , -AH6`) AHJ: QvPRu!ݒR2Vz;@,CJdA/9+VI\+ᇈXW@W"u1*zY"MD!,&Y"wƐq]$rPhN7xWw،%Rw،[6x&2#U= D|DޡzOB"6 o]D HT7םOQ]|VV*dy\4(5a5?0z6Z̢DV7h j{/ua}ocqybnSX&kIBޫ@"-JJ2x'$VEGV_L;$0 T t m]G#V/@"uM4bY\ZThX$$ l Rx 8=X]M9CBZ=DtK>zzD, -K)W =DS$e@tȶ\_-/WѬq;syx{gD^)źH7_[ &V=9YXwߕ֐63y3S94mT~{o^uQH6B7hq!۷<( ~uȱPk -i*%̷jJKlǡ\^w+g\/^H喡wfhf:4Σ?_.ުi -jj>+WNm5 {¦6M`=$$7KH:DC*ly:^OC>C2=HSˏ&wѧ3oХ35' ACQ\ARr.J3 P5-=0#03+HI ؀87p> 3DY|/3)1IHg%] !cS2J)Zr/mf"/J'R!;C: |%Bw,K仲DH ܼ.]#k%jLc"}r!aV.DBD"캌c";E~u3>Wf-?۟ʅE>:_QRi\)EN"=EJP 00 D ؁8A -p/`x~A D'1,Dt=!˲d=DD*Hi>HsQJ v;G$бV"ƬY<~k $Ioau~_(Ϋڙc3{lmֺXdiCxSוEլZv)T&,~~E-!li -7Ԝ'#ry砝qg{\iiEJzF^5f[ODDz/*my~Z۪9vrnatrR}X6SKs` XaфZfjkWX״c#0,\;~vRUu@nf Y+ThX$$ l Rx 8ҕ#+ދjZPBQ$џ Y4shtb{elv=$xl- |[h@ưM1nkC d&"X$Qy5E-~xHSqZ_#hG$}#Jӫs"{hTs痐c?ӯ(E5"#)nE)nVSY|>uqِ7ߕIOD؜<6?#?+WNjEA"TMB"m//h"ocu/%_MgOX̵@" -&!1BD/|~@U.? ԽD1:Z9$2x>o=H0$RH5C"S3*:`&`` $p^ @\^[$2JT.A;,}h'B'[KD̸`/Dg I-#Od'EX_/kI$V@⎸)WhzHd쓛j̓կWmy8>Y"eQĐ.9bK$ L*oщG?*]A?`U9е H<\j+'Owi7QVbiPp$[>]_j4 Nz89H}sw}yH B"#B" *QHqHd:t}͗.ω+ >.KdC@D6S^)Zs78qJٛxwwEo_#)r7sdj?E3w`%?R"cPrCYMwl<+rܬ|^s[Ȼl'71'`=atev鳰鰾;lh ΅Musd'X -o8,fcz{$;ޢ_"G`F؎`WӏEf_O4"H dg*T@ 4@ t@ L , -AH6`) AHJjH7UQwqQ"1MtlTNR$BVǨSN;'"RDՊq1P{ H̖-b7b$2+C11iN Y̽~D ȾE3U))ۉ~Ws~5Fw! -IMfxBܜz8W]9y^_kvܳh?SPWmb㙹MѠ!UUj -~6U4X];TXv(Xk[]td>)l۟i?$RGFtgMR#"YhBp+H<YxE"JP 00 D ؁8A -p/`x~A rpLyH*1 GRMq4XfE1E D$낸z(RFMQ-:#ѣ>;-RF$_haeZdD?m3kUxwD!D^Ȉ,C T"}: A" %Tx}~Uȯ_[# _$X7ϥ" 6+= ڨ抝[ꚇDfR(OnbzQN"cm~- ƾ,7~X` G>,W.&Z,$E%DElkߛi9H_4~f -^ YF?3vt  JD!;'E鐴˄$zɸ %sd9-Yi?3AEBI8&9{IJ yG)q$$0O֘{)U]ܨ+#yo]$ݠN*уkfz<gƐOd%b!㙺-OΜhDFWE*w@"gΡOWHİ-ܽ#0OLN~ǹy'??ri9\֕s\'Jd|˥EFHdVM}=$jXW6R/}cɠH49L4 -IJRT"LL{w޹3%D&G%2pمJmHdi/RJ-H>iDtT{r>b%` -h XA"H <? ѨfF574euT, -*I7%ECh<+JĒtJQW9!ݍ;&""򵢯^Aqν'":cC_j1nw=s,qg]w߿=Jd݌cklD~i)ӯƵ4 sZc)ڙOqpS.Bwfy׼;@ŞH rFT~Un<\)|.z-*/DƮaH{'XF:^$fݐHq!o)2vWtiD1D?lC%2}D.= -H?S jZz`F`f`V@2;p'Hn,| @#Ve=afTf-D J~Z0̈6b>&ZJ_=PJGgE_&ߟ{#q,ov D%K1$¯D~שD֔ȩo0(+cܤf,c 㺴3҈_@";'*HٓhggKl9$~1HpgK (-v\[&f,beYYr$ i9ߓ9vc9u[D"bE""""vȐ""bE"VDl=װ:^9G.9y G6m~{%_rݵ{9Rw^kwu%KOSSү#mޑӟu u͈s\/b~#?̟؛rǻvY6޵?#)oTWR -HIp.6ё&;R8O<%rP{Kߧ9qh{]r"/)rۘCegw hzCW3u֗ CfSX'2x'9rn9W TJP4F0,` .p  >Cx @BP -̒&b!jQ;uL7 Eޘ+FqrDmb68osIpwO-JkB`lڴmRhM+bR{ѩ~ԟZ٩?iѧ~N'^9sF??Y?EI٫_XS=<&O79\Mny+Go7M&^0\u?[.;]~ߘ=]کe^?餟K?g~ΨI)U/}W]P#[FZ:]￝aO8R:<9w}c^|~gsɏ.+DS?+"%ħ&s#9ѕ`SIIiɎxz2ғOH4_?q2s'boT#&1Cve5-ud,1-gGk9q6oFĔ|9֜8[kIDحv۝e3=3-~yZ}_{߷_w9)i;I (AjЀtf@DC Ăl`' ,x/=RJ4irZG]"hrxmэP'눩7 icG6,o󂫎w7 - 3*p9wF*Edۤ"/H'ED!"q9מ1vCD~*6ݶq[_."*T_Is#aiT/F;0wLPĹcPPKG0wEEcb?h~PْhPT wRJBJ#)>%It;œN8"U^GEr1uj2?Sp@?vҵܭ?xzxl*}ɪ-|IT$Vdت{vX{QiTYTNTVAEJ yPYZTUTYTd"8L0AZЁ `Q 1 V7?RRZ8 22?*$qAsBtD?G %DLM,tR ":%2]ĝ%G Z;"Tۥx"qREREΉT EQCEY"2iVHy)"RDΏ$)"RDΏ:"Eẅ8,"{\ҽxɟ0BOL3\ZXZyS*TE҂a"_Pk6H.+bEd^sqȩQaFXTd8*OGE#<ë|1}6UeܩInG' xw%Iu{RInw5pK?F[Q}!SU.6u Uw6(r""McgԢ"PQcrWHi){m2.?{weTd^Tdx9RZS (AjЀtf@DC Ăl`' ,x/=Y&l$t!IA[$rchlj"tmJg/qwfZϣGX%U&["NȾ ]oAʯ^9?^5FtKRD#4Ԯ{V$HQdgEc=Cfz;YFDUD$DgyMوo;j"\*rAQD-U;RE.087dkMGN?>HX"W~{0VsU3gչՄ$c=W;?𤥻].=sS7]1|'2EiVf/2enU]}m6''Sw趟{.1,u1 LEG^2ҥQ%mCŲcjeCo?/w˜'g됳Y{70pwqoŶ.9?Q{HNYa*%@ Ђ`# hX p!<CC)&0Q)YDWIp7NS1EiEk9^N\ĝ%Up -DקGJ"H9 UE.BEΓ*"UD"mHENނ&|v.мnD~ Eu# b_Hw5"Do >)"2l̥k³k=e%$:RR3\֕i$69 4ٻ_PheS6Fw2U M!mݽ{tE/=0ׇ5fU^>ytGE;]Eϊy\]`i:Qx-3uanCD*0FH7FƃXt=zM.0AZЁ `Q 1 V7?P: s(1!A#L  yb* -SADd\I^&:WqO6?1}âgEa=s69_u[|uC"S-Z{_ȚV_Z"&Ո|%BD^NN{^<뙰3#b XtM!"v#vDd9ND䣋o_g]cy0g.vȎѳ)"\bJ'>ɑⰞIgI$OFzjr;>LxWGH&O2Fd2չc=PH۽+!/s0{1\k6uW9|o䣪z -e{l;Y#"ZDd%Dd-FDrBD*걞wADz?#"S.0AZЁ `Q 1 V7?˧V"oy"Fn'A;D_ c`ͽM#G1K'x$N,_%ފ3597BDRD.\jxIdcN^'E*2Li]&/G&$!2'EV8;DD$r -1/A""V/lH+)ݕHNX8Rq l -"#G6D4" 2ő "~;S5nuvprd~*` K5$2|o9t#"AD޾"vp)zAD*G+H=z"Rw&c{O""EL "2uɾiZS (AjЀtf@DC Ăl`' ,x/ԮDSTfVPNa9#DMN݀o 9AL=F9F#!X{Ywg';TDvxW%xD"rQh\%EnHD>~VD2׊b"ra(I0BDˍ"oIy5"؊؁I#"W|S?08Ob+%>ő&8Ą$G7f˓vzDJ -w -O"'emLJz7S[H@3+~{~&`~4d20Z,gnrYLa;H"RIwt=Н"݈DQ3I'y[FDx/"JDp -`@ *P=&0 b `;8n`x~deHQLUzZ, .A#D001K DhD:Cx @UT,ATUD}HTnL8+@TI[$Z{D>!:+EWqliB{DO<"9)"YRDjI"&EVD"!vgL"'s|"E*EzDߐs"I7GW -D$o!"s "r}~gcU{Viw|Zu%b$5>9>>i#}xy9oc^BDg2U X44Oy =OOM-yX΄IDd+b'na=p/"ӇLQDd"H3o#"mш>,T`BD1L݇I+S (AjЀtf@DC Ăl`' ,x/,gE>a*r|:Q'hObL͂\TIN"cu\ 9ChDr;J<k_7??FDn"r4DED"Dı!y}FDBkX5.ׇ"p<(Ed"02΍ ͯV#R71>/j~gEvp4"r_46>>ɕHws6>HyqT7Hό._P]d "cT]oVו4=!mΐoٔ{=[jZ#3ýc$rADz3t{X,=riD$H֋HLjH[H t9s}(3+EՀ^"vA&莈b%QtL0 ^:ш -bb"Vp"axOk"6+"ܵu7vݍ Hn33kX]9'`{>'EdCI$$EdCI$!k:3cIADS?CDjnra5&C~IK9g.M#"Y,5ޕ%;HJNNr%bCx @IDN,_GyDG nsCh *<"Xfx:}"WpEWՙ6z"N?3KQJCN*BDHy[*BDlHDM"WwޠQdOQ5w#"RE~+]}7(!s#^;Σ'Ek(<|=|ޣEOO"*YuLr+cS q6.1ޑOKt'$rI)gl|l|V[CE:ߣ**>&kf]*T0̵efSExK]|*RE+21tElUk5ݳ:Jo9*20*R.*2م4}Qd^oO3ƻܿx*Rx @Om|F (AjЀtf@DC Ăl`' ,x/ԭ<@Q9,Uz =NtA}hiB0ϋ3Hh-$8+= AO cC_N^E;yw[IT/Eȏ6<ֿ}F"ҾQRo,HͦQd.LIHe39[\ADՈX6!"=D$MD>)ݲoKΦede;#tCmAggu"[(zJ7OoZ}:"%6Jɕ""[wHٿlw/}=x7y}ƹeBȒTu/򚤊,{״!7y5QϷn7 MgE^O1F2P]igSE\)qIIXФ'g8YW#ݓNc=n7{Ƃ&\+5ZN"n}45TdhgB֒.L}Ik0XXk6\g2>jyTi,ā+b7ֽL*rˠ̞3II{d -iGErhEjmǂCTIy`8[ϼdVRJP4F0,` .p  >Cx @9h2Q͈bQS/j]?4=Ad\ЊQd2hm=a* li r Aoa ׺}FN=Ί\"Ί\!:k׊çE"Ҿ4)"n|Oې\tCO!"H XX7|""ǬfK \rgڕgYD7x0ɉi)ĤԸt6).)5xkPh( "2ӓy~ᮐbnn}Omq4d7j(磆(Fw~79ښES HXc萜}+yq -`@ *P=&0 b `;8n`x~衴 2傪 gz1VӘ`n a"oNb!6"y"{Bt}Â?ӧV+"=S)E(rn|Q!"_ܐ1~5g;U)"wj͗"_v[c\\z}rD1G8DdG?3};1|/0ܾH;KLD,eX.5>1Ց̥&]ql3;]L\:"S L[)OS5лC=Rfdm)#Ivj75v=$3lɩUQ.,~16Yoَm2grW˻r|-NiWT]R Z&׾Y.R%d:vw -5h@ :Ѓ`3X -!b -6\<|@`yEޢ٢*ڢڢآݢygbY "[kmo6GlSޜ--""@S|~3r)"_woHD^ZX#&O.[頻C$e?yN뱜VOd!"Ya97"}G?3ت@Df?$b>T,_ lMKJqd{iiqn6سZUPd2UG1Qufg  h_ -iKBL}}ChRj6t3xK[%&9L"S}c=I$oY=I$L$2r;&&qtY$RZLk&G`9Sp3] -`@ *P=&0 b `;8n`x~neHcPQ 2AeP!hD] U145͋e.g-x3u.HϬg݄=䪂1BϬέu 96[Y龜ZN7$"Yɟu gY}hfc篷'E3oHD'1 0: yBzD$CD.a˙ީ5_XX;ΪG$r֝HKv;t7Ha8INg_{Yz ^HÈHu?"FU܊ #" "KGz˟EDBD&AD:kE޻W"TJP4F0,` .p  >Cx ՝"a *M)ю]-׋Yb%Z\4f=1b-"fb$!ѕ/E3"WE%Ek=#uFrF|`Dn^"=m>(H3nnϯq޻NƾT(r_)*DVҭ"}Sto#T"#gSER3386ё`=)..)ΑJJJxFbq)wI{Ii_9؄{`r9WT$oYz^} -? 5h@ :Ѓ`3X -!b -6\<|@W- 3KꄠjjE풨 ypD4֋f.ZJ(ROG5'hklj= MsZ:n7B=x%\"% ܖMu}&kL֭9}ERE>I H ‚ƶ!9pV0|8zV6TfbAr~$~*ITڵHOc,r$A\ɮ4]y].o9ޱ)zTՍVx$)?ս[_Y3dFE_蒙s-Qoa)zH#V܇@EF@E73 P70LV`ixT9Fg&}TPJEp -`@ *P=&0 b `;8n`x~$oe#aԉQj'fQ3GA];FXNLabn ZƂE*z!ZD8aEO!:Uw׸ O&HٶYH!"ϺkE3t( ބ)"/HjQ;.D~gkG?"WG|#BDQ|̞U7qi t'+!b$OZ뙔3<8}V[d [^Dh0T64Cjz>0g1=e6zJf+-e|TaD$k+b݂T>""AD&kW;>S0VVgO=.W!"'nDD -uIͻ+gp -`@ *P=&0 b `;8n`x~dp&Y""țウCuV4ASh.-ti ZGDۈHyDjy)zGӣHVQȍRDΏ+(EbC"rR}0ߔM"pVu#b"ܺ1nȃV"?`߹t"r}5" "s_DD&Oag.I""b?"¥%ĥ%:\fXpi.RSS\;,|tM7(Sx ))*%+~6( -_ݭ+$r(dl|lZCDZ-QH Ddw찎Io0DY1zV)kH}L>o1}\&'JDVL"wL"8L0AZЁ `Q 1 V7?PDŬȔ-#ꢠf2mu}I4tUh.Z$2F3eAkcV*{cĵ$OVV DOL"v)"_7Y6$"ei"2qio]|aqRD,EnC6d"R!"LKtYwxÕX'yfK nϦx2\,%%9N".q'y86!!.U9oѵ|L9)|>jEgBѹL=!cf("S[z0#"Nҧ5On=>eD>"mD8}qDD}CD;ͧ{O7k˙So٥5h@ :Ѓ`3X -!b -6\<|@V^ѸtC*u"Z^7@=Đ'siQi^>)B'%Cl>,8 %l \'.Bgn[3F)"1K1J1Dȗ7$"O33[+"'V;RDF#D$YȈ}I$h]&}J'c?Xjui7"R׆pt㣟""9)gFD|Qgܩ lÕ`B2z^Rݿ~iDdmL1Dސjp$Sдߣ=Mfk^ Bͦgz2WGwuȣKKd13m ߻ZM~@Lt?}^rKHӃrO w}޲Bg :?)#"ɕ0T hAz0L` DA4@,Xvp 8CS"6QK46Wub#ȴ`.%,ݬFH$8W+qOlrEo3O/[SRD3&g=*arIB\)=h%U *xOuUZ"#ȗVA0cb(=)SLܰyFE -3ՇԼҶҕܗ|<`2>b6Ҋ<[kP,Td"Iz}]To-ŨH >U"Kwҫ3ExKVHaTr`"8L0AZЁ `Q 1 V7?r|L8.0DY":m P%J0z+Ǜ Zb'Ļz]"xy"gME\^ME\3["nC"HȢUD!" Y<țV+o6噯(VG88">+:lajxƿ Y5$%iɩ#>P%p4w:f ֝r4߿Ǜ#"mLwLLqHtO@Sz09;4['}c9q'oilzfgڷݴ:L>GDZz>`Yg>xCXwi/FDZgN}5h@ :Ѓ`3X -!b -6\<|@`y,F”1^# =:+H0`ZDdQVV۸`o!K->/pü#rO"JIQGLO:BDڐLk>h άʤ3x8)"x D$ػVL"a$2рp><"}ɥ;Ef""Uciyi%"8L0AZЁ `Q 1 V7?ұ2 ADT.U}AuNPSܠhh -Dӂh ZFI"zLA|f4h/ݢ q -JIX3JI|i MOD~aK\+E[HT\!E>)$r?iuynZ}pc=X|裟""EDrGDYu#oF+-Mry<61n7Kw$ǥzSݞ3_>竆_ш YS<3tFr,*ԁ8MUڑ[ҕI5q&|D2sգ^KcިGJ=K7b*>b/롗byq=VRL"crw;rP2x^(z_(GJÕr~qS (AjЀtf@DC Ăl`' ,x/=͊6/UnVz>ڱͺ͆Y~yab3<Y67 -~{nmr|>ߠ?S)J")"&)"RI-BDܐTEjV"yF4)"^4)"oEDc9SBw6$VI>h_4y*ϺFD5NCD'jânyb%>|XrN"M{yȱ3Dzy&O\}~3F8'uo9o{#ƜEȚDzkG۷[iq24ׇ"o)$rxÔI$^ہIdL"E}SDzN䛘D`9j{2-\MNNpD.#.ʦt]m'Vg>)>ʴ٪na:/M!DEH7z -^0|Zf>[䣦>xorsca=A"Ҵ|bn|orf} ],7XV.e""8"R@vj9TJP4F0,` .p  >Cx +˙zQQ*2'rA ='R):~Ј41Ƞ.lbk%*ѹ$W`kF%B7*L)"ufJQ˶v>vi3Gѯάȿ"*"cիgXBojO7. 0xOFg՞ OJb\B#ɕFʹlF:&T6>.ŝqRd]7.o7띗)Fr1#V'2U T/٣>;4[toPZȸgin7=[&(Bإzz^7^Go›}};}^$}Fb*2}#c)8*0n]S (AjЀtf@DC Ăl`' ,x/򃉈"Od:2y'A7ED4vS.1wK>;C+R ZOb>-8'+_p6L<7&zo{?=">͞lQdf]r9z&RF&|u#~7G7`cuL\O'fjȥڳk=q GKϬ&,G$!q.%.Q+{ˏXzŠ[R͗g hJ(򧐮C !f"r~2|/ud EDnݿZuiFDwD c,FDƊy'㍈H "WDdaDyq -`@ *P=&0 b `;8n`x~k卼CK`yAU MuD&E#s`ZFfM}wN -VaV -%;B@ |*"EE")""rH ȍc|ͮY+"]NeT6eI $\brz|zrjI]'YnVߺ/9>[r(i>ԕ[?0,=2>a65=qHf.3tȣ+FnL"XL#" Lgw1}^]P"R}ކ1Dd+0T hAz0L` DA4@,Xvp 8C)# iDuHP -F^{ -+o-IR8z{;U"l9sUM)&+ly!g.y꤈;)Εp3lzb#9&qɞĄ3&]#)1e!"5T#S4 i 2CB8LIOS[RDH3W:փ,Ј һgJD|=|S5<;1}3GG)"JDp -`@ *P=&0 b `;8n`x~o)&^$ʬs9Am#-,hlE󴸲񽖞Y#lѶ$ڧEgjD" -LmW {k01^y|}S?c`]fs}{/}>ҧaR(zS (AjЀtf@DC Ăl`' ,x/"*E(Ѷ݌ b\ QD,5(RzD[gZ"I=7Bok5ܠI MI$M -&MI!k3ӵ&Cm=Os"L -ˍoȤ#{دE٘s5]nԺ|S?L`{yO|WFZ#!F -6#JI$&s)驉#rkZEnA)BD&_T cQЄBڹ3u4 {1&ۺeT5@Z&}9cs -vX9.z>DdDnKox )mCDm~v;}wވ޺P -Ԡ-@0 `(+NpX^YA9@ThD@#RM d'~қ hDu&{qf[~9ADDZ RDYuI ^0Q*.7VO0|ܴމϟKy\1BD~!K\aIN:z ׯFىqL!D|jgsc&7sLC 1<>z{BHo蜝eRDJ+"VHRlX)RJAڲيAZir)""e>mzҞ~~@"ǐnpҺLJ;m,a2L&)I:ץ7}!ix. W^\9Hdvzn:G6t ; Z -o;̛Ih&K-䨧sG D;!u^8HdvTͽ=_)@"gC"McCnr z^g*IL+KTD=kʉ_Wח D_OFï'2BKfAK<=KG3wԧ{&kà/+cPy˼iq]=C#6YF!!6H$" D"U!91E ׺=u!Hdai;N7C"y&" rJjZz`F` XA<px@=J~@V`:DP1AU'[Ms@;At~%>`) -Xk\7<&yW xK[DZ+SDDH(Q%&Q\aUE{=y+sNQ[rDBD$&%-/>邩 Hg -݈D <ǘ 3ܟ1VD"CHg$#!4A$BnS8961Ll%28yp]z>ҙ'!g9>e߇!U!:t ;j~ ㍋󦫴Ͷ#HLK&KZ$2,$v)oD!Y< w!hD܉d7OJ2  -*:`& X;p'` p < |У -tc: Q (rA= "M}1cDDFbm%Z 6&8'vHp au@!3Q"E-)Yb))my*u+F"C["3sDGQ"E\z5H"F"Y@"wōW$71{݌>DRB$Dnd]d&es[6BfRd.1+%#!#)[|a^";3i\ܩ>!Uw[1G3] -wU#yyӾIHd H͵o DѵE-\D@$RH#B$2/ct -*=Vz$"Hǔ9P%P5-=0#0X ؁8 \n^< `D6gnA.(,u{P4'C;1S,|ӫ?"X(";;I\y7I@.=o%(;E#H"JNQ"DjC5Vd=BYz]\H"D"5?~DHg.HgF$"D.fp̽L<+""_t筫I3aKHvēt[WLv$gsNqSm3[:͈D;ڂgObOYsؓ#>mt^5@"Gxc_#oZ(G:ӺD~\h?RMb3L #Rg;H-?3!'g!0/ɣSZ/Kǔ9P%P5-=0#0X ؁8 \n^< `l0$f&u{CQ4vP%D*D ybmD:#'″KU p=&xFf{k6[cǾ.FHhM$"hw"ȿ/ȷe釢F"9uk"9ye${DH#WB"<̛>3^ͺ=hK`m\ӓm\FV-&!#3행o=$2:BDNs>ePbfƧ 'ޯ{%XYÛSޖW7>ޖ Ҳ D"s@$2F{[-uǑΌ_SPDZ }-H'm]f) rJjZz`F` XA<px@=Ȩ_1Pa>G4UD;uPJ}􉷘JR $Gq lox -B#zf6 " 7$RH:ԶqL\9ЯHgvҙcHgBՄ{$$:mYlMatLv%;[*7Zv=tcዹ>egsHuj|:GSvvtK[{[bP{M3-H($Py`e4{ #Hn);$rҙ!/KE *t{r \\1e1@@ T@ 4@ t@ L 3+vNxH'Xk%{6M;.e WUǘk۳=\&Ͻ&I}G:|.)9M2  -*:`& X;p'` p < |У̥*N/M^+i4)-1M?fM[.6mR[xt?Yϳܳi!份-GyAVP2ucˣEJ-DD"H,JH,DkQڙx]Hgft7" =Hdw~E:5$q$焹l]W kf,ഥlR2ʦ$;wKH -̱b>G$RNH>'WH!UѡL~ 3!1#15"9̛{B0ѼfmD"-_""nz'2YFD+HE:3|yܓRHgho'Hg!Nǔ9P%P5-=0#0X ؁8 \n^< u" N`fyQCԋLҙq3}\4$>UB"[Hg:>G:Sڏt?=]2ҙHV>Cl ͺ2  -*:`& X;p'` p < |x'ibbʯq~͒_;Nt~}'1  HsuXy<?w.]%~G<{_uG]($11EE$E]/6a~t.GM|G/#Hd.DEݹؚHvbjH`,7-61? =_xu $r3D"WKםHFbJOv,.ۖDR2236-->$ ЧTO{*ZUw>ji6=)1tm -Jͪ66Y !ᣐH$9$Rs"$kx ? -L|Hw:D -[To9P%P5-=0#0X ؁8 \n^< f+~Y&vB-6[k "AW!ޙb,#&wq8vjs~w75+5BD #%r({#HD&)^}:z0$(SFXirꚤ3vG9bg~ ̤8#ܪ\1? -wҙDHdz}a]E"YA֙eKtm33Ŗ%$Dgbf0u櫕Hd+lvCLGbr|sl9jU̼V ~gDfxb5oZDl4nLы:H)HkD"erq։B"@" /A"@"s%1e1@@ T@ 4@ t@ L 3+vNxHtf"3/jl :u hk%A_)ZcL9\` 8U#}Dphzg"Չ|:sU$"J(;"H$eM$GJ$U^:Q> Jz"A  !mDO=1>J<+${>Ay7D$p]݉$'dpN6ŖI#p$,tT'2y(,l Lc͕USRUs4ů ׷随m! -x75t0u5g掝,e!FZ{9J^DA"]δ_h.K]A"y>))YX~1e1@@ T@ 4@ t@ L 3+vNxĤ_VAA!$@+V[bx*$Оt͂VHx1L9X,Mu8;'8ʉKf#~*\'RVx%Te.XQ"JQ"EHH!Tn׊~g)ZD"ތZɚ Akjg;M/5",4Cz[EB^II;B:豌̌,|ec[Je -:9#q,- \)MZ<RmS/Ѵ'BFƪyӹ7i4_xybmirh>Gu/#DKV"vwԌPd}X :Pi91e1@@ T@ 4@ t@ L 3+vNxHr_'0$(ݹ=^[lj})Le9Pl9qM,E+ս EvDm5ݴ&ySf'V߅#gn/EꢶnC:}$)\mWEtÍP8HS/5yJd~2 >&-`OȓjXB: 8N-D,bKI`]T' #;)腰DƿKZ6kS>GQ|ԧreyC3MqS$ -ro@UP_"RA{. q(kPdXk#$:cHp -a5K6]%xow%LԺEyy涀rS~A"Fg D"2,H ~#9t:7C"㼏-C># -b]cJvYN[JfJƦg'ғmt|SLO3_Id_o`:T!yRbŧl{#2W=PYhit%oK =5^B"Hͽ,R=H8 LH1$20|w -Hd-H $KUU/Kǔ9P%P5-=0#0X ؁8 \n^< Sʈ*c.y=Q~n -^j:\Ng,#~k?:@`]~<~wIqEDD\$5=z>#}|\Ӻ D}(Q"~fgvP(򅝉gԳ7$>tp} ȿ=a!Oz<^#ݧ$e`rb-)q66+^[R3Kt97oZ㿲#?P3{y_&Ee珨ZQޤ)O/>뭻K_9PL/VP(h̓ed#ҙ7;%gzKb/u_=u@O7J]m-nQwgEztPʏ>uHJNpL`@ PP 0q , -88>ͩT=5p:UQQ槪S^MW{)U7_46ҩDle!պ良+RCcluux]v?;rJH(sD|)JA"5}X}ǟ1'z)(u5Ȼ'VH$rwrT1q&HeUA"C"pgK (-v̽Tx,fYCƲnMczv۳q:7mnRHi"""EDDDDDDDĊX!"Ves^=;;?>\>=Z/a۴iS|w碋vI*v'ҒWmOIܓxI.:Sn`rnhiɖ[E/߹;hu9]1Θ؝8qbvƧ{LLjb32N;mONM&ŻIq1ю`UW^أ>gdd*EW*SF ! JWvݐ_]|PAz1M[4tf2V7wLU{g#>CYvÎ;vZ2ˬ5Ͽ(7 s?&w'rI3n#ŭrbS?],FWW?'߄k*€h X 8 ܀>,yKK TV.(xl:RM}6'AW q75$sM ,}WgJ)xo{o|ߴi k|gKl:{4=Fq'Fv't'9999{F'Y)\)B)ROQa,<&or7)Z|#o7M|O~]wv]Gׯ5ܴ]ϿWǿt ]]uwՉuv:zw}]XWצs'uaSEinw;?{(wK]x{z,,ۺLi*oB"ID+LL=c])I3)D-^ D"ۨƗ 27']WϽԌ3|8]W׏4to2<)3UŚKb_D2ބDJ+vD߼2x$2~i4C"UC" B"}@"˙H?v{2DDJDD2 $T -%P5-=0#03 D p'p< |X*!AS#|XTӂt -^8C`MyDyHd -:;f9gZn=1󜧙|$!Jd(LQ"?%BIHLQ"?%BIH ȭ$H_DDzD%$&JIQ"f }i.!oԅ_D~DWg@"2w/P4ySO} eNqΘԤ${NA 0$⢙X'sDjsvu<5@$R SL/n!ӕŇTSӵ:C~ᶠa. _B"C"]ۣ2KvZ - Ðp $dWӃO~rixMH$$2$$$2 7^S(@@@ DH, -lh /?`ȫry8aNuS8Mtͼ>3qiTę{DD8K>gmemDpNpL^{xo 'JC%$}٪D:D'!I䳇dII$}-L]%%WSIB"%%be&'KĊ$򤃲lAHd($r4$rP,\t C"?LDܔr:Iq9SSRi5v:Mۙ$e餤d1LILhW;t)m -sea/+^NS-Tճ5ҵi~}!qcHa֜>~$wU} 4C"Ko˘xjh{CY}5n9[qCX9B9B9B9B9B9B9B9B9B9B9B9B9B9B9B9B9B9B9B9B9B9B9B9B9B9B9B9B9B9B9B9B9B9Z<&!lWT͂,AStV0L4țI9S"@"5^ 9;yWK=û@, -WίDdX7lHĺIdۆ$NDv%奕%r(DFD%$MȈ(DmDS;Y"v$Y$?8)9e5y$M$@"B$Rx,5a@ T@ 4@ t@ L AQ \n`%q)24&6q~V_J90g5+IDpj֚YG<ʹ:xzwWrLeoIfQ"aDn%b%]W"׉$ٳ_Ql]"O|r"-(Z"JXE^-rE!9k6QVw/CE߁E3uw>^𔟺X1Rl \:,tE9D{35Nؙ{|| $1)1'Z`j'2R6*_HTz(Mt_]XP3^v4]?Ơd,)퐙g |!Q$/Qd;-"déE2EEE;&dL}OaXby{YEFYJjZz`F`f"@$`6N4pxU -VE.^9«9YżnLЗvΘ/:xs#zf*;g6Zb] 0MY/Z$_%Z@9U%,rh|"F ܸ!?SD߯%OZ1@Y8{C"Czp{TvN=H/ٞ)DBɝS t@"S{ޒ1K=}/@" ӐHU5$*Df^S(@@@ DH, -lh /?`ȫ,z–ye*=fPЎ}`X]7g'@"D"%W Lp=*0ݼgl~ %!E*!͢D9ټ!$$(REF/{hLHhE-R,ZD!aJX/َLIoXQy"[aKPlDa.XdDNXFA")qL,:iN'cIqv?0I'Z~·Ez`[Qs"u7S*8m1]W 4T=c2V~̴tkb{kEއE2}>Xd%Xd)Ez`ܻaIX$IS: !3T,},]u"xM@PP 00p"A+p0]ؐN \P1[7:atHb3qEϋvi&1ΞD*xT:I)8׉{'HJ&"#+ISՅG5OkkoB4,Tb iDY"QmUC"/B"B"ٓHQ"9HdHfglX"OzHd.?pL"xM@PP 00p"A+p0< 2)9ӜSϰ -^Y0gYo>ʒM^*Yryk9okwutI=Sy8D!JvQ"g۟\b=u \!Q@ުH*-߽Dz+ߵZ"E&JD%!yMJB"?ߐNɒHL}HxI䗷y#KB"y)Fb߬C=;$NJ ө4$Ne8+)916E#o)^)Q ANe{N ޖiwj;nݩky>^ Uimnlesxw#ZoEN Q%oR2k~22rN9]rW!9=_=rOYwr_߽Gt+o)P ( -h A8 -X؀8 ^~Wiݬ軘U_󪚼͚֋um7z)t Tl&"-K3m}^Gu\Lzc^fz.lD*$g*E)&DDLB" Ȟ^Lי9^y_\"gy_\"gnE~Zꤾ,gEnJCѼ(R/#"l9g~QdtH >9DcScQϤ$ٓR`ĤT&&{K}omE*HW=Dj^I׎鲪;jKfj:ErEB"L)@=sQ$D)*7CZ(E)'M]O(dz$H=iZ>(R HiZ9E -@0*:`&` D(`V`.@7`x c"Ӽ"QBؤMP -^sN|^_ y,oj5dw3قe61;\7kb+xgy_FӪBi+Z$R"׈5b;7U굢?K3OG'KV%(oJHdsoI3; ?(j_"myHꂧH}Rt" HW15a@ T@ 4@ t@ L AQ \nJh&>lSqA(hV>A9odf3+Ss <%;gzy(|k3gJE"1QDHE!qi]:3p~z3^L(?{~KKdLB"z&9jD 37 E[ 6$r."DV"@bjt;igcPD'DǝEv5D>JC"%gTwաtmwf_?4}gF3ճ$5=j!!HdlK$2d HS(nOHC l $p|QJjZz`F`f"@$`6N4px8&e,TaټSE^3i98?y`*dfDa2,XK[[\m<=3fqaWV=#E%"z !S3kj?A"ȣD Xϔ1HHOR qf 4j5D.(PzG^%SgDr΀D^8EܨfNݕgcIIvc)tI_1а( ,޶*y}/]9N~uo}PIdnٯ+h3G错Xsw g fQ;-ŤiD2BH.PjU)!"2&k~9dL$R ,tL"xM@PP 00p"A+p0< 2)8#8Vjxm%!3f9\D$2B"˜0kcI=;8WG,<ݼ_@9WDΐȹ"^W"_K'3RIQ"gKH%JQ"gKH[6" ,HvND9)$r-$r$2Pof@") HKxx|INJGlN=ISOP2{EoJ9yTٿy,*Ue~&g_P[ySnïϘ j6 `lxF$2$܎PH$OH$VHd{CN7YzSƳ,M?&5JK6Nv܉ޕ -@0*:`&` D(`V`.@7`x x@^IP Ԭ+{T% A)hL0dKMlϴ"3Z`+Jpt$8.ue t.6S#x| iU֒3מDȜ($$]Ȝ($$}C4 D"T9(SrFD4f2TTY.ʙ[᧼I D:I$Ou1=@'1vIB9g@ .q=KWt &zk+LgƪҕT[[tmi;>q.iA6|b -Y$3l(i'3= YY|)$&Ie[r&S܂$GH]$܄$2{,5a@ T@ 4@ t@ L AQ \n=xE@ -arBP-VAӖ-M!P`,LC9t6#yGO%]򞪭K}N`@ܞ9?vϷ˿X\fN%$}Q"JlD(O%gސrЅ9 O ܜ|K*ޅD܍r$s4|5V#C"G!ʖQ;-dM-Djb=$R $0$Rwp'HD!G 'y -@0*:`&` D(`V`.@7`x x@^%SP TCBXUz)ASkx]vD04 ƼS`!3#±f=}e٬ KIHL -;$(!(yD%$[Q"vC$rxfZ5x='H׺~,J_3?\_k<$S &J%dac{^Dn(gf3A"^r`"gb;&'3%`H($:Nj)Uj2EۨZ[v8]/]_WvB"ksgt͏O UMOLw$vyD}n{Tӝ;-}`% %H8$2Wt ,@"ˣ=sOA"s9$Lx3nK(@@@ DH, -lh /?`_tlWT T%(kU15KvR%9oL!<@$2$omlUp]%]*fKgF$([$D>?%rܢDM7L=kKDL4)JO"\+J$_ȸDvCtՐool"L[gF9|9INtG۝)v:6ލLdIԸ&DT߶z%4@"H$2l$MUW<.H>ח=4ԙYC"լ~6>HSD[ }> ;[] H~{Hdݞ6\D%3/^S(@@@ DH, -lh /?`ȫtRfjffqaNyc`dH`ms"Hmߝi%r;%$$bDQ4A"_*[~dT!W)$bzO]r$$SՆVgbc\qvWbH+rr=:1nW }R{+D&dmHSAP97ʪ' j&N.~ˮG{MfHd5OPΔD:{wDUB"]Hٝi"IdI2RE$ޒ1GzI? 䕓"rck*€h X 8 ܀>,yfNQQBX,YL4i8 )ް[xg"Izޚ`fSswt^>S{yFޭD~)[oH(_+g6D"_=DfW$(DKH$VcD%$!ԅo8iYٝy ~{SH._O+Ĥ&IPGr\,KhgR]1Ĥ$ƕNN>+VVemT I"O++?LSWՓApv=MWw_?P4<`2=`ywY/C"5dw(cG;-wA"}OA"-D"%>>r6gDr3Z4J&B"]H?&P ( -h A8 -X؀8 ^~~DJXE>O\^Y8u-)C.O ÚW ID*DyK%ol!1:W5G>)Ȝ^7D"!oIk9[bD$$rH$o)?0DbD;Q$fCvg)& 3:$9'HgH"Wz(}KB"WAy Ij@31.Щv:9:`gRRQݸc+:pRYWƮQ"*2"qa+2$|r4؃~u Wioj{ty9W PyذQΔ5Ge *rxf E Jr!HݞH -CV7B"@"G5a@ T@ 4@ t@ L AQ \n]D8E?OD|$y^/hK9] #qfeHZΌ ZzxG,]] kxTqޗ5%Du%wUH9XX]S"z(? QKH%(D YX/';sI!p))A"me' ܟxS ? ,dLHl<$NN^1lSbD?&9)NuǥO ]ZL1VL|_7lW_֕vi.MW_4}d~Ii֜UˆTB"ŲȌQ-w,w2!qLH;$wjD'&dL׳xg!2!z#g^$sV_>~`P ( -h A8 -X؀8 ^~>tlBJj2܌SgrA^[:x4k93%I4Ͳ,:9GldIH?>1V/^qݢDbE|eݻgbE\}SHd56l'36](Jqݙ 7D"$ T;)۟V˙A"3 u;ʙ;[䩏r@"O >NRS)h;vEǦ$E&' k~,)Ezal㗪245/n,gwa&HYTp#c.f§^ΓG>#_5O,-2k2[xq[قtwEOʘ<=r7}Gn.c5rF5?}P ( -h A8 -X؀8 ^~4{-aU"bea(xc7x%lF7;f=ξͮxnTnL{;z?IяI_~E䂑5ozJHY3? q/yb7~|٩+U{"&ۺN31qqDWj=:%99&ٙNy+zVNY7nɲkʣT%&tmi65  ƚ';D.jX%C2k{p;BD0)c:[w{~:oD钳mrcMxM@PP 00p"A+p0ݚIcKbYLQ"9G"\/J]Q"H$79F~U -IHVgP<+n/y$BA"eȾ ;O3@=N))dHA6CHF m.iY"3ȌD*!AH$$y2y95^>}`'$r 8Hw(E!]P -:$h*m % USiN0$RLޛ,݂5/V(Npv]wѥ̢Mf^[Z+|EB"{$ށuߙ6,y^1R}|0\^W4R^7"%x5b"FcWzr\V9xgqݹ3㜷UUΨ%$l5Z+D6f:S%֒[%'HZ X#JlrfφH9w |A8y C"O2TO!?{g}#ʙ?D?D,ӪK$&3I)v:oOQΤ3˝_$6x_![)Fսކ$Ҝ5AmށtH'ʙPΔHy )Kn}{GTfNp<>;o!M=H"'DFv{ɽGoGH?A rm 1^+3UO ^!'C7OəG[ݼk,,x[jDΓH(D1H]=!97W'\'&%/DE(%$ 7%[v⠬r^M"NHdbOY^I$J?uO@" Liu;K'FG1D&LM $1' G,w9*Sm2i" ʖ4U#~uAMlv4]3~}y{l2N6b|t?NDFDZlGυDDn!k"#W;H"Y!tSBޗD <2(Hǒ^S(@@@ DH, -lh /?`ȫOMa3WQN;y os7ڕ˫V4o]H&]30O|o H(omZoNs؆HWS$Ŧ׼a[ H4~K" ٞ阔>Ļ|37IYZEZ#^]"Xe=ꙔM3pGtlv'cc\@ %:rbbN"j_9: tl_Eҕso҇#/k&tEAC&cy)9:S#H Yڿ=jRZ >Lx{oF K7"7t+#j+EJ#WI(Rw|JjZz`F`f"@$`6N4px=(5+Ke/Zՙ. -}`N0RK,LH5o9,XyEJ9a%ռ{H GgpwaEEjhbCVVoZQ,u$?KLXZw̒(3RI砞F n\6 TYHySe&Og)D>ƉRRcƞ~ؓWL;%ڜ]GsAF lJSU5 kJ7uAC}&c2Sqkds㋐ ۣJJwZ!qHX"ZqM7@"՟A"UC"exz^D:ȜAH$8=v\"9H$@"9H$@"9H$@"9H$@"9H$@"9H$@"9H$@"9H$@"9HΊD9EOUqa͜rr -|^igyo 1ě -8"3ms*uy4 r<*E)J7(OQ"Dې(ĖSE`G#N6&ZȺͶmHA$nFޭ<(V-r -Q5,R"΁EަngEޚ> -@36@KAIuS\vH'i[bՕxV~4JSMAMARn__l0Pn2fxB4CΈ7aGMi,EfZɌ;`Nr&l - rwSH%x`X&i/8fP ( -h A8 -X؀8 ^~ Yc(EͩXuJ8+d!3fp\HVEHA3Y˹;#Xg Zb|2[<w|"7KKj{D|SW$,wC,b>kU-f-s(%F,JkyD)Jk-o#N<"Hw(5P} %OQϴ!~R[T!\LJRl|,hOItjOsad&..55D,5{De {/]9Rگ79v4)?Dr{ xc;Pόzl@"A"EFP?3Sy9J,rlvձck*€h X 8 ܀>,]=)&X jUϳ^V;EWX-ZÚVٙ['X[#ONV=,ss!WqW&qD(QJH7"*NnvQKS7$$*&;E|CB"2 -;E>DȢpu&H$D>H"cB=,u|HVII\;%;*q'-ڷktgbíYY~4]^jȯ^,jҵ5=i{牔A"sJjn6_= !ErwېH98x$2OHg=udi)xn'WzWR -%P5-=0#03 D p'p< |XSb*\NySUYi =H =sA"+h>TԑDwA"mw^S(@@@ DH, -lh /?`?t, -je{ Xœi#aJ0f -&9Gt\zB")<$JNq쁘&'&t+t/sWm[:~_iꂧѼ+i~#ACݭ&c]2dkDɢdQQG -wZ -x3H G4$.$MC"=ݞÐHu$RF{i<&P ( -h A8 -X؀8 ^~,򲊹-T+ 9U+.46&X$ky)37qÜkecCJTyX,+@uϜ#Jp3lD^2Hn},޶#*7wej$wDiF ivMޙ~Dwߒ1xErEIY)){|JjZz`F`f"@$`6N4pxo?>Wd+8$lRAW qL0e ##cv󶢕Mf5ټ;g&8Om}3ܗh}%k XH(IH")V9rB%zYkAygݮՍpvvݰH>%_$CEnD~%^Q$D!1 |p:Yv;]Ih7.w=ޝLj355&}Pʞ_YɆEfR=V4 bftm{WnNd }Jf.g(h'HݣۣjoiEdD#ጬ,2|/ -=jx͞scLe s3d!<'i3;uwwn^QmyRd[D)EʐaEl) "VVRkې?矾~_+rXd9SA_=m_>S  R rJjZz`  38\ < x@f}DG4~0}Dy.󛧈ت(aGv s]-x""EEȖ"e?SޥNyO+.iH0\ Ke٤I" -IӉ~fEY<%DZFW j.|iSW 1*h) )9P%P5-=0XX  .< } {|L 8|i$1*kfЬD?H7iRd@0 g}qb#@OyˉcZ::~<YX"WEm*[. -o~洲+ K[-p[E"G~?SJ;ɨDkOyЬ4ݛJD *; x˸gys{!*" ɜ`-i']=cc-ljn';RRӜؔ6;/-篾) -Jd'_ʫ֚f6KV{:m T"xC$2 $Rsdz$V%x{ I*cgB"է7&?B"t ^I3$RNG5 g$@ -d@@ T@ 4@ t@ #0x`V`v xU)^0DODEDNDU*_5*RZAL=nXyNǬ%Ax4/_ؤyMԡ a\5e`~6fR̛.Bꪕ*YJ,D6?HvFHD(1%Cۙ<sý?DR7X~ӌJCT"P̌]Ip&$-얔d'v3993V+>X˴B"]# K53f+ƳTջ=A]:m];$S*yÙM1 -TzHV"}?A"tfmgFնvH|f+4ެ -~ƛ3!ziȎ3ŀ 2  -*: @<0+;`p`^)f Ik~~qާ*' -j-me$rW]HMA(n 6cEhi? iD{ֈ}1}ݤY]q*? TI}Drտ,H) )9P%P5-=0XX  .<  4gxǜ$5t+D&r/QuIЖ$Ql#A!~B0u`+O;K+WpOR;A"%fI KDD$,yQCޘ.si5L6%D* R<i͉DΜmX!CjH 54#,_5uˬygn]Y#!`OpA6#) i.5-ÞRd$Nev}@%R}V$,X˜D҂Od/z'gʑlUk^uAM)KD_ U/J$&M3Xoʣx'NPL($28J!6P_y'HS/$r=>D+C g$@ -d@@ T@ 4@ t@ #0x`V`v xB2Q .3/mdدәȸnYikg3a*5A?H`m:5lhAkn{\a&5DZF߳,sJ䁥$uE3%"K$5춰DBQ%r۲l8LKr1q팲`U^5g<׽~ VmiHIc-OD$HHK8gBb -w`u(L*МH*' mHAH -iqOy-]4@3E̜D$HȀ(h@,F` ptImDv')*(GU4>mU}!=""!>G5Dl]}B`spUGYkDJ3 "/XQ"9K.g~IX-Ta,wCE,""tjEd"^ TﱴM0?e܃H+!WT\s~,i)K8.-9&'$^읾klt>߬yV$?{3s ,2}2[ϒ{奯GeDzT;sA:mhu׏о}1mŝ:)C"lUj|(E6׸}"GH&+3"wGg[tJt㙻ȴ+$UGg K:j)rϲ3o/LdC?S̹ɪ&>H 鯐|/y=Hgk$83ؔkILw5gs@#rɬTU$Re-hH2-(ΒUx݃AEt2%[uxW]s,Zv^/ WK(Ezzq_w+,DzM3ŀ 2  -*: @<0+;`ṗ$ai%-?e$dfalK$&,gDQ3߳E"ȕȰ>C%J{?DvߧEǹxx>gn:J抺mhN &Z865@oIG¥/y ɬzzLeG%RthwW>r085,KUǫ jzuP57ԟA%RuY]gz:Y7xDFhH$G?9Jԍ)q/in=AGDNI$F_@B6J>6OpT\qU9NȅEa.Da.D"[K$rWDz /C$;m ze3;#Kdc$s q-JBH?rL-ir_!k̍wx!I=$kv2Y'Hӌ řp:2)dVM%rs3_ˌDʃOfF_+GUU{?jQt -Z@o)bːHJH $2tizVSHA;${vA"EB"d$@ -d@@ T@ 4@ t@ #0x`V`v xBFb n"dHY o!AFAD[ JίH|`m(8Bpz3Jd[<QnKWV"mٲe$,YP"vѮ=,"K^|~  -i| 1=#Xd'xhE~~!̷w69==1%ݒ,lRJ%H8GRKLp$]o,?gs"DZZޝAYp;AelUMW+ة!^?U䠟ɡ88z4H1MEhjk%,R0,L37 -߅Ef`zZtS /X) )9P%P5-=0XX  .< `~( -0~YH'9TzRД}=L4+i\sV.O -l*ps~q"5ڰE-rm0]"̲X;3ѮpȻ"\lo]X".[2""vH7xwSӁ1~MD -2N'+\WV?3fĀ%՞DGsӝ .,?:Sd'"qSZT?vhKPWSTy^eYje(Ew6GDv3梟z;v~}1Va > ? w9M' ȹw z>ӱ |0@@@@@@bA0f`6`,px>q "t'ɷE+O -|f&קy a5B^vg=!>𞾯CkUԡ5"k/3k)m -wV*cX"oGqYgFzd`" fԯtlIT"SHCM02u =* W{ - ud~ZRҝ)G ŞL$&.Ik.ٜ9RHg"qk1H13-ٲ|`Ŗ?_d_s5:nT"ռd74ɧ댕כZ!F84 &:JyHV"œL}i0nz}JOtnH`͙b PP qL ؀8n^觌!L$>8bVduԻ}݄ft(RAgjiGGc6LgjL\m]&x,^F=1%xư,Y9s.%4_EH8U"DN%~vƶ,ȑ6ElB;sbȶVoODŨ3ᦿy3j ϸFw+YrR -a)65=¥$Z@ ř:.)wP$g;sEۥ=ns[;y9vU[ >/4ĎDᏝƝcbrEq=w6w;zEdn}Edmt{hg?U8ƻDwGOx&xkz&|6 g$@ -d@@ T@ 4@ t@ #0x`V`v xbݒUl-^u+VWݚ~6*}"'Wnsj)RfvVs-WjwD"5YuQi&-O +/7KId\"3a"H$%,3)RDEX_D4qT"8S.*_v_!$$Qhg$$6!Hd8l{2jd6b'ؒ{̿H\vfF$ϖ6\|/n*Y^\}PN;&**^Yzh;SzF$n\T xsqUûD -QވqG%\ymDh{w;*:T"DZJmDk}+m]Dw?",""""RD)"EWDDDDĕ""el;g{=zyf>XwЫ;vt+$.kRAgJ4}i/IjzJ5W8oΌ\~y0vy #eYv]w.H_y n֝&'e}O?KWd?Mt;SRN_{))l:MKvEYY沆X%Q,)Tv3#/V"O׶5y衫 c5aS^E<_k>.T*>NetaE{bGT ?7!h{C鞼)s9wlҪveCJgdY|9|M0 hAz0L` XQ 1 v7x  !W)Uf53S5PdK2bljD=#Vq}Ht'>D+va'NiOҟtz׉?饟3H?3Kgdd%)ZSnhOjwveZ2áR`;4j^N#T)w;x̿W6\v//{NQ֍_/sϙ'~ά~ٟlF5J'7~YOiUS]:T.QY2v7>a7]e>115n 0g\c01Os^?3"lzO(FDTמ_ "YuثH6EjoEDhDDD*s@D݃ >?BDrJϸ a uӼ_WC 1S+1ˀh(B#E#R'IqWasxrEo -@B'"(▉HHEQ-ж"mD/7HOZ/EWRD)"HUfgI*3DԈI䈋zrt`c=AD~cb'"A)&ea&|&AX}LHBB7O*&5 'D^:#"XԸ[nAD!Ddl@F.Dd;!"ه#Ԡ-@0 `+ -!bqx!a*y0D=E4v 1T%eX ""Oĸy$yB=#,%&ID2mo)"-3-sG"tKXd"KȍRD,2ŶL"My˙DvfLoODnDL"E' 48_b937sϤ$S|llj#IO_gX65==us#2{þ^)YQ "h3u]W -4v׆M9_mZm |T͟0TO#r%ӥ{9!"/#"3-yzQ}GDfo:}i˙'k5h@ :Ѓ`3X -6hXC8,^ AAx @ʀjQC4itmX4#iB4C5_Ћ"t$Rt,Af'g Z10KuI.e.HDD)EGRD.r{"}i36ȳ_D$G2I" )"gD俷eiԻcYyycY="R$r"RtO "O}-\! }3)"~ŧd$itg?N3SNHc޾G׮Wf_,Lejj_;z[XWїdo9lj$Ro<64"ˈH4!"G_˙6Dd`[Pw}?٧_@Dֿ "9TjЀtfl`8p XpA0@~Cj0uD=M4EB}hI2V%&s:6E'>a^/ -*$l qw,N ܰH(W$"UN&"LDm+U͗3'_X5]RDL2]RDL2Děd\$R\FDt "՘Dnz9Dd/ӈ|;3\DIILg$$'n"" ngrFǙ{Y}JDӈԮ*T93\~DSz[v#k{X4yL5oˮ⣊Lλ{bkiADFI-#H Dn}CU=} 0""ҌFD:GD#Ԡ-@0 `+ -!bqx!a*ǒTI1Q=KIqoK2 ƒLG1K[5"[ DDD3ޓ7KIt9Sn3N+J -'B%Dn"))"K9(ED)RD2nKDmQ~2뙥6~w%STw%SLE.w+r箑ȁaen?*ب7աb9AEҢEEFT vX[3"~Oz3ΔDKG ?-#ÙOe}+R<eEc?1"]"G35oՅu""s "'.Q-HP_^=4"KL3*rtKk@E*(@c1P_ *R,WI&_ -_S AZЁ `VADC Ă.` BUډjI`()w~7+)_0wC`[H.(2&]e5ϳ{A4v;.j>V[#UV"1RE>#SX"1RE>#Sm.\fi>)"H93ߕ"(mF~=igyUg"""" "" 𓈈3,GD -jϤS320Ļ`=ΔD'SOHF -USL "RuF oeolz4l쎘g -"x۱; -D%D$krOl {7#"Y-mt#"Յt""݈H<)}0Ӏ'"R>k5h@ :Ѓ`3X -6hXC8,^ AAx @J!A=-hzVxoXEiA07jbmmL]T@ot *SB $HB!)"J)"RDad"#Ed mgm(YD^_]Ȭg>"=y2|O{LD-S#Iybe_lD䁃H8KD䵙 Q̄ ogRDRigvz|N:3R};ݟ~rDA)Qd~7SGofja=gi(ol Z;#[{1LT&gADF=ޥ=3E{ՈHsH(">4ވL<"[Aoό,>EvvQd{#Ԡ-@0 `+ -!bqx!a*+j@`4mk"KÂA0 2Z*$RB#@5$8Fy϶ Q3(xWVq!4{e"rAiLD4RDI2lKDVWVӷ(2h=e*M"LD)"J#lEeI˙kX&4"sDKA)"HɃX|9.$'$$r) N.圞tO&1-!MKytEejez73u i/uM}~caSk\dLIdɾ*GD-mk}^Y}^Y"RDd8"҈03 -@DʞDD:^ADZՁ?""TjЀtfl`8p XpA0@~Qv>.hDkyP"Sh^,˂5WUЈЈn!nRpWa'w3o ᖄ0BU[&s{\RD,2adKDٖ|ҳHG*^e(UY}Ef=="REl2޶TduVĶ~Q1u"֍"yaTrd1Y:rM3Y3[7)ƛhڿQ*`@ Ђ`#  b p n8@BЯRU1>Ot>}03xMǼ\Rsl!!C>{/}l=t37 z3Тc _J'< %$"%,%%$"%,- 9ko7MO+[rRtId}"?sosa/6<[1_\H@B -o]{ }+gv'`|xXgc_2LI;SY_z|Z*IN;esH -U]3c)hhԖuGCOeqk[-/b5SJ2u#搅Kb+ڛ{0{bY.2~'搪a9V9dis%!CfLDlzsfT/qL<킷YrC>ARDnٙIqgg-y2[E$ݣrkkHQJɕ"GCrd"ٖ9_W-iY&uI@D.Oi<"rCYD/-qzҝ$##=11!+Y{vqL"R՚kW{~ckEYzyjm|{O(WVĔu}^W]DDJDDJG;lJCHsJo?}vh@`XU{&Dn6;/ -P=&0`(;ā\<ҲHT׊9AOt>;3!1$ol<"2A/4W#:8a{Vo@BK9-ERDrKȧd"rh)"yl6f銬_" )"J9G&"H_# LWO"t{M1&|)ѧx;^$c:3H?ED~!3_#"OϨ;lrBjB -g}NMs>oqO-& P;dB&"j)"|^&"_ؖ< in[~e<,Ee{3"R^nKD~G2DϿ`GxΛc؏d!K[̅Oyf׹Hƙc4'1u&'x}6d$idxXdi|}6SSu}jB񪰩!b^iZ(#՘Dj&^ncJ@#2Lӣ>$2I$J!&Dƃ`;"ҍ%nw4<Zf \ 4>LQRDRD29WRY&"OnKDV3,"\]ٰ 5RD>#3\#E32m&[o&rȗ(@DZwquhSL"?;<`$ol:OMg$4&{\'HgS|) ),guFja7"jf k#w3 /7V׆MOEYVKDfy[=|TX""om(k˙ҿ""5Hkt8"}>6"w'"Rց@ez( k60"R@6*Y -P=&0`(;ā\<UE3ȫyau׍i*ӄ`.%: e.4-8JAS$x'Z ` XDeTHyX'd"3)"yRD>!D_Nswfӧx ORD2-ֶp[z !"~zM$ҍ| yI"0%r)"r}t98D;ΨLb|z"=OO]Lq>#~wzr+˷%"1D~yk"m6ٖgfo7"r$k["rffHC+"1T es+H}V~b.Qz o{F]XMHLI9|Nw&Ӝ~Λv7}wYr^xt]M~m:,ee˙ƪcTՒlZQۆQSX "5Vߺ_4:GDj'zw>W|=3#D]tl"Rt"2S|M0 hAz0L` XQ 1 v7x  !pH).zPs[]0flQyM3UH@sB >]F!#r E-3mH.oqMd|9cux3V-RD]D$|Bv~Bn$P7vG{ PJ ci8,O#"/[c'^vDX^W 4!"zj =ʈ?!"Cs5DDd)zF|"E1c(Q$H>F|"E1c(Q$H>F|"E1c(Q$H>F|"E%nY IB7]o#fѸ *yI$YVdVmj 2$>SIK[bp1)4B6V"G4Ȍ"ΒIO/e"֖E8)"oI1Dۖ~j+WQ1D҈b8W*8&&ADA)3ozkE: ޗҝl/q'\/INMa>0 hAz0L` XQ 1 v7x  !WY qt19te~qfR,XljmgFC}Zk#kE`wd'y8 d1T!neU!e%"_H/D'/]wF,Qw2Ez-^-뙫̑xI=g*.GDO1<#̄i$;,%=KM%8XL"=V~'<u'rT־֞^RJWv3eGջ#Cڶ啈~LcaS Vr3"2_zf>;^tIl{!Doe=i }o+;~h &>L"!"xđ{#Ԡ-@0 `+ -!bqx!aLߞUY)cZVݐ'!feeEm1qzQ+1Q(zQt - +pb`Q. RqJGg9[9}{'OfGdˈ"-#o[~əD,g>rj"T ǜhA)"r&_f{O&93NO:8LINOqSnϴ7:vYBU~HvBwgDqsvn奈0xaHR?ܣ?۲ -1'{.m$RKO{Dz*0Wb96)"S2\YKy]fm=wO{AD&YUi>"ޅůo OC}ϐk sgԩD lJjZzj3G'wJӟ;ii4M; 듧[2QIV ܨP$k[ T[w5pˍLyN5}K -kAd^0et7*b -{qOv*w>pՌV*َJ;ZiiDDI`^e -zY2lmSZw -_S AZЁ `VADC Ă.` BUCc!fBuYH3Ԗ^kP?{BP2,Z._4], 3! =cz\օPPehId$[,E;:=y["Ku[-g~w4'6n3FIQff[3mvCuM?"rY"6ɤD~3Ӎ"1!zك?$L"8"O}ΔDQqr)3͟Kdӽϝ,sMkgWp1LGiDxѰ豈~a>7Ӹ6-NEX-a9Zj'0t\Iؒ{;3ofOcLjbi|mDg0,{003Idr23?c<"$ޙ -ї1 -ލIJ[fU+ĸ21.f @gx[xHp@M6(rÖ#E-oϜ-Iq̦g, T"F薧]oGd\&"e${NW""" }ɛz UkGx5|e7嫄9ϗY/1uYo3-!ONIOϜrNs־7ޣYvyo73*"aH9^k#q>lx;b(ZKRXmwQUok{bKVe&2AIx9)w0PJwgADADjTjЀtfl`8p XpA0@OJ$#D26EQ?NxE81|Zd]۲J۬89ͭS'zBb[勛lYUlYXf˳.""Fs%"?܎Ӝ4#6l("?lޙ.ز{g.ۖk";.I,`J{Szȓ!"WD"Q-̮lF_rNo -(N#iɩ ,~3XQ -s.fIdE4SMaXUD?xo~CaSV] -\5ok@7T;l=(G㝡ΣD1[y/}L9D$~nY= Y:2^|M0 hAz0L` XQ 1 v7x  !ro*$L5e -`ĴQNv ҁ"]Ğ+v:γĽ"x^G˫JHhޯRDJhe""R"ED+ŶD䊲mYi<Ѿ-/HjʶD$rDD$̗$EeDcηXT""Y6@oy¯c9kL?=1%ݝq&N;-HIwSdOf%.BDn?qauΈLCa]}tLcæ"fei5[HC3_;|^{O"R"RN/߁Aoғ;DdƈPߢVf#Ԡ-@0 `+ -!bqx!a*E0DF4vnkâ`!2Ѽ*Xj'2CT }R'5Wf^i"h|>P!GP$DGȹ2Aly o:"mVW([QdG{VrHh[*2(r~HgFE.W"0$*rG{j *rYl *@&F304_F"LZ2"Ӊ?ޙ'`<'reuiʒ3 -U?v3}E";35uC}˝ݙQW# "7a=3~'ozkEo$vTTdIVRz^z-=~^)6`Ym"*`@ Ђ`#  b p n8@Bx^)aV1^O͂nWfXCLyX$o[!E>.oX.=͂7pBGP%Q͟9kREe*y"JI緥"O'~{Ot ^)"n҈y}?=3=F6"{YXH]":̄vLޙQEnT'>#ÙyN_MIOsi>~µGqQdmvvLCCM#-VK{ -k[9o. -ID=-{w "K=Hg+0, """>F6{Yɣgݾ|M0 hAz0L` XQ 1 v7x  !W&f3HԮWV }M0M]q:&تhD^$5,xxOG)$6IʪZ&"g%"WY,"3Y]+dޣіc$= {OIimٲ[s̵,"rwt97z?11ޅI>o~b\ 3aX<uFEě{ ֗ 4O&}\)nߡa[1z@[DSpva]ٍW1ݛi, ߉kj6QG-,7z_[W>Ij;= nQP܁+HtBDEDnk5h@ :Ѓ`3X -6hXC8,^ AAx /NR̔5I6QW)+ ^1M,ӂuE6xEGJѽD<LjQo)bhR7jB]RD^ޖ n{i{2G5ny{&YDf=息?,˙;3G6*D#ǜ{"2F~INL";x&Q'$pijDǓ\*Ƨ'pɧ<<ӻ|e_6㭽bz~m}Xwt2æ#{Dd5`93":"2إڗE'K#Nz,q,gƞCDLj?iADfoDDF$RWJBD#Ԡ-@0 `+ -!bqx!a/gZUE>B&,Lq-ı('D 9k&!8-r7A&"0h)"09kϜu}>IG["Ŗ' ED!lKD5.q FDnADyȁ駈HޏWޫ䙝FD>:})T/}LrJK';}dsJD:ݺ*33]KȡyDdfHXWhD0i;HT|tj)z -kCˣWV[6Wt7wUVK#PїFTz^WVV;UD$ Dw)X -P=&0`(;ā\<k_MbEu/'igE"ChMEh#qVK7맬&刎1U(IUcTjЀtfl`8p XpA0@ċ4}fGHH 5MLy@nbk˙j>M -(J6.z}JM$OBD"H[HtC&"D)mvx["޳go+<"6dl9+?'3ݝܶDM6[Obt,ѹ'^_Wk"?$r'^|"`ٹLyS)ӓfOu&&'$xDj,e -UN_'TW]LqnuRK w8qCiKs|2Jš;{ -sK  VM{eE\_C‘a•p]yFhJOabC\訸E|eCJ~JRo_ AZЁ `VADC Ă.` BU0_Q5 ~R@W cqT07~N6;3GrUt" %HA +r{Hit{gn|iӴwRRB0!ŘPQ0b%\B0RBPmJq(Ƙ~_0 7d?>3ȓy>t^-;5J{,-i v[D P 6Q 2 -Hu.^MՖ(/˫⌉m/'nx2KϾ%yQِՇnCpzn1o~yclItg8 -o}"Z&5O<U׭iVFugt3C(D:Ƣ˜fFx!>u"%4y'@!2Ewi73JF>pHG˯35(D2c(D?^3*:`&`` $p7`p €Ola3dE̒En,byK,sDމ^H&BY3$ qU["ۤyLIR8W!+$1 㪡+. -( -Lm˙.s/-?2S}.?-.I[C/%Rg-lBXDF>4U'ڒ}}IP66DMX̭),س|btb#HlO7=GxuMHdb)!L$2p)7HbHY| -r$ThX$$ l ܀~#UdU$A;$Z~A41us>+k۫D{h {E:3m\H2BSH䛒D7Nnu&V7ʿL7s>nW%;-H3t3J7(fOnHջ͸hR=H 1wa ؾNWυ?ؽ9,$BWdLczssRtŵsE}];jDJ).Du#}`)lx?=[uyS2^ϕz\9\?ۛvoKkq+- ňa}xyT:g茅}EM-7^QXPUӐH뇐ʁleU{+G6Gi;3{)Dfx.33JJ#>Jfg*T@ 4@ t@ L , -AH6`n?@AHz;B"szh.m!5E$'ixNJd\3g53B|Spm5+%O' I"HD+IDnvK$Do&1rΈ#s&rT:XUT"zwˆD2HƖ3޺by\y"E% nH䋥H \@%K+B[mv&Y..ן򱾽.WrDu3*:`&`` $p7`p €Oi"ʘȔA=+h3sD7HE>=HL\&c[RR.M3VSr\)i..%5;ד]s;Syh_sIBy.f@Buzyɗ~rTx;jZy S -PomX$XI/4챗{g +hO&/LTҧ3Zw&W ړ Ҿ~b)\G v -ZaU1I"7^$?U Jq_ ngy˄DU;KTG|i驮Ls4/f^3k6ؾR\R(OtHV^;4ua⡨~5V6vFL/[̱NT"A"C"tyUřݶ= 4GCM{/D/xMHT"?DzjH׋4cu ҧ3/A"e=>S jZz`F`f`V@2;p'px B x@Ի 0Sꂠ>M4vIu -*J4!K`8gi'U@CӟH{̴Mq+Mܧ3[S<:?t`uX^$%|WHTȗd$ݭy|D^"178~7D@"1!B"·gBi_'̭7=R\)~H$5%ݕw^.3כf_s;SXi튷H3 zq2Oz"=Օ~w3P KA"hgJfO&$ҌվݶHd^v3ZG#3f !$[^D_x({gZ tf-;4r]"L%` -h XA"H X> 0),ܸƵ3mDF"}H 3̓"2ꈽ8ƈHtwY _ -܌ :Zd;S.I&I"Dn$7A[l* k:aeI"÷ēH$O"['rk5qe3}4c2V3;;j{g@;өVS.ӛy/ͷɸr|^{HfѻpYA%rzV"#թKEd, -Y6͏"S9yGaD -ܽU:r,o+˂Jr3OY.)Yl7"5NVK,/ "ݰ(,2֖ڗ#3;6v*,U"Gyk|bӰi)rvoEh?StPQ]]ѕu aNX,,:,2v[ P5-=0#03+HI ؀8 <!< 5D#s:h:2dq=+ZdӸYbo!Dxzߘ!\ <"O) -F"7JfΧ5g6,Od$rDDHUF"-UQRd$#c_D~DM!߄{,/=~߶zjJ"HB&D4_?DrtȏYQ} 4~5H 5ĸn{ /\t#uex8WnvjnNJ6*kE>XZE1sD -^GiFbaQ]yGD,pٰq(j9l1D%rI:S'C"tdݶ؇pRh9$ra i{;JGV ݏx/%&/]o?ח@GVJP 00 D ؁8|8Aa֦aͬ"fo i K STxf8wa'5J!8(>yzXyVK!n?$b)uDثE~(3(2 I 2: I?oI;7EDPg~C"~T17%C"x*H/ 2$rǶGdSr<~of+;K|t'ӕKcsS\:wjqޓkw -ee.fnjz7n<<6>5E%yCacρ&f1ҍt{UYXBDާn[{uD3z;H};4YD&` =Je:(҈Jum{ՕU|0@@@ $d`vN,@@ Uݼ_ͽ}1&332M3d4 ;yw9a4C"WH-$XMB (dERf+I$5n["U&8{^$HdXHLȬD,[RHD>2NcJ@fs#uOQ[xW7}M_}RʄK)Fn5erJjZz`F` Dh``vNn@pJ#'kdV+Xe7j -;YM]euӜ3J֔Gl碶EOr|2Zy{%眕+̻+8:U1۸m>X}MʷNR_{U'B-9RI{'{VI'^{'I)B)R)J}Ord4*fnsH9.ٶkmtG+nK1m5יuHN~?r'?V N~lqcϽғ{Ө_'?vtKN^m>O ->vVɏ=gc?z* '>6|BEv*l.zuA]:cE:M]^G.SKb<e|/Yaw|2˝oK :6:ƕO&cNHx"Id連߄E?HWܐ8Wվn]Jׄku9Яa4L ?aL5LEBEGg5"`"݅ǤۏI%O"wK5%]Wy_E."ï""ϝH "aX$ a0,E°H "aX$ a0,E°H "aX$ a0,E°H "aX$ a0,E°H Y$UpT '/bUͲB^sӖqV_:9kfa,bb-)9̺JxwK7[6D,,yl,,ynEiI_K$  9(Hk" D"~xD%92$ID ,9^)06fHlHj0)9dKp\1[b ɴ#1&)>&uDHY.jG}m$D3.z&!&q%QϤ'ǺI39jDZߗ>EU"T+IS6VU=tM3iڜC~]_E]FC3=&bl43{ q$H3o_A=3|/$s H_=H HI)PP 0 -D3+ppx0UXK 3V$VYǪ9-ct5dsF4LH%1YZY{gEw3GƛڙOIFA"_$r )!|ND" IdJH"[Dn©IDlDr־.A"$eH${Dx$2$2&"'V ȇDk\yH$3L%O}D&!&8]I _͕%:m qؔ:6{Dr-L/Kd+HVete~U$0Mw#H"tH e).&buH` $HyHd~HcD2DZg!!{!P/$ -TV@" U'$ה -ȁ( -h DHX؁8  < |* V*xU >iXm3a5E"aR4sf2YKx{sI5˺yzrB7}J"7  -YNA" -'Hd D%"vFy{k{`|,XdY"ZVȲE"gg^mfnYeW(RqAY_Q""M_aK䩏!?}'P; ->,t)tvЉ6WНl :ݶxWSɩZ}ՙk\Ȏ߶ZEnPw+GVPυ34YϤkWu:~ -Q0cZa"JaՏ`=ѽ{#MkHn,ZEȒDgoE{V߅Eaa~(E2@9P%P5-=0#0 @40 ;p'p7x8@^fN+y ԽANQpV:"ydssU̹8bi"(r`n"vHt XD%Pm*l#t߈" -@D"-HQA"Ho93ͷ\uDq!Q$HHg2x𒧐HBL+C_{I9VV܎X:>Ֆt"Өgl )qxpD -M-| ޲(]Q5z˯jP724vh:C?g4 LEjSN-1Q., If$z^sH{rYԱ\yLy=TEC=oC"B"=yǤлHɿ k@@@@"@$ , -܀> yN6RS1UN5ªY* q N?8c-k'Q$LEj9s+kcݬsp֕Ϲ{xp7՛HD$rE>ȹ6[ΖH$le|sw[$ Y/A"DDEܒG2~3:JE `yc$_ KEF(I@E=K&q1ĸ[||Ę$-vӎ+14Tg~i"%3ٷDJF#)gP kNV>ݜ/7zK!rTZDCEiz|O{͕H $2P $"aH8$$8$R}7I"=H"ՐHS1$2D2@9P%P5-=0#0 @40 ;p'p7xӜl:2ӼWRNX}%g(dMiHdDX8kuL1֕ǺYzT2޷i&{ sE$ H$(HD/"-ȵ?_7J"ÍkzDXY4|(lD>ܒC7p׺Hdz9{?h*2;H{+Z8j q%Nœq5MEYXk?ge!9ƹi<լ~Z"w - KyD" 8D$-HW7۞Dm$'#NYȚȭDBU9"zKD4מ.(Hd 9Hd D I_$?_1 i<$HIJqSĤǛb1&1gb#zjhp`(k^Ⱥ$$EΣAegwT3$MŇIwtuE) w5HzM ވ{nD^+zeq$w+w**A =SJKR_J?Z)9P' -NHhP(  -*:`&"Af`V`.7 - Ҽ]ֳj./ٮhܮܮٮ.i -kW7l7->Ӡߵ ~Gٳպ]^i{HH)$ڴ&HĵimK$r{W6K").5I/o[DʙmByWJD"ɂD$H3a23DHW7'$$5GSѵDǽ)$OQ I"`;!趹$ĥh:lK]䄘dTdfk/$IlTp`4M^Wߓ8C>.3F}OH#SLDKH"#(g=x^sٽH"U/",활q3m(gJD_!L {zɚȝH"H"H"eH"e'^S(  -*:`&"Af`V`.4/?`y ZvR-;%;9;;U;գ;5Cm7 +%ޔ i"k";͙;-%{1әptNO.oNOI$SdM$KDH$VDH$vK$[dnd]UgU)X$B"EnEE?S.e3m[Hя`.EE:>3{GNgE\`L_\ `\ mMuRccӢHu_ P"v7U; ^JWַUKe334ץkkMkC=sqq1DLK#+>EJ~*k^yi~ ?BzV߃Er>Ej`P=4)3 -,x E;(O(  -*:`&"Af`V`.4/?`ȫZjCڢH> hœs ސs9ٞ#2k-y2`_Ӝ3w.=5Jw"2T -E(";%bjK,:sE~z33u"3H&A": IFD"i["+HāzF1AY}wK䷽ȇ4e~,xSHQdϟ8Yճu%c6m LLH9S1qߛ7’D?DF+i~UU(C=Бiz2MyЯ[iWf4 @+SLDAHmnО{ :HdeHd)r#$3(ʂDFEZ! -HdH'gN5erJjZz`F` Dh``vNn@pJ'// (r9jKtټ~3t,tZYœϲ 9͹zvnEڴBA"Wm~H䛼dNHϰD$ҳZ(HdFd{OD -̈liKE%qB+4K$lXh($dHOQI&33Uf 丘$[ -98\dKHI'%$;qg)~e"$ַwQ9!+ZҔuE~U{ꙞtMDvد1C_yѰ:|140+B"]EHǞ1"Au4v:I YRHaG=mRooR=Ǥ|ޟ#岯?ה -ȁ( -h DHX؁8  < |*lXy&a^ȪXMt+5ts4D$R|2ΰ6Q:Yncj3z{8_Fg$"gD.^tӞUH?v %7wDI"D "I7DfDoD"U7(g$D>ucTWDF[)$bSޘyρD+Ϫ홄`!E2 bKr&%$9]$x'DEu<$n+i9$-w5=צkGj2#H"-H"%iN&b4rPT؞QA"K]H"oH~|54QPjI(HaDn yo:D2@9P%P5-=0#0 @40 ;p'p7x8@^$yN9ézX浇x] %o \mVʛW8"O͖9G\s9O.m|.g3  H5A"vA"r%C;CoCyW؞ H*A"$\$_O"A"m%ȑL{ I$u$)gdM۳Żr!_LIiM]hx1KB"砜i21;D&Ϊ=ޘX:6ƒn3:F $%$ti3#e m/Kd+:l+M_W VdK5-iڑ3FCrc*b":CY&}n)k'-DD@ɚF-D&>@[D #}]#d{q -e#," =EO"="ݯ(4i|6>.&ᴹD5L~ ܹʟD2?HW,U+Kj3ԳҴ~pk>!]H1D4] =':\\{ i| D!,(9$u3$2*R$[,X$EHĦ3ERDX$fK,3۰셙S]#ruӞO:[7Yݚw7ޟyzs3"Dyu~H꫼䩏O#z'銟(K\.h&܏+.]?_.q8Sh\DȦ;9Y77J%7k%#{µRGRgcI]K7Kݳ-x͑zXV)Sݕjf}? -ȁ( -h DHX؁8  < |)le C*3Vy&ޘGi2Bi -msxG*iwo<@{io}/B(~A!EBQHMV/̓Ȇ;SDED^rB.򢠐sErіļ#:P%frHLB.~ -#(z]{* -f2CB#DU K"'@@@@"@$ , -܀> Njd5UübSviVk*9(kg!3fL3dwT3Yqi45rUƺ;8zLp:ިcU*ұj$9D,D"9gK$r̙gbf8; 6-f$rޖ3bi6*?kտVKj)K.r9$/n;$Ƶ!?:D)qT-1.&9$%$$\ɱÕ ; el]T )fJQ O+ 39ehJid觪H$1*~3B1[[#J!rkD (fVH1p$2;b 3m=5G {!'!h3Rԕu'yO5f4ü6k0xcNS7Yi L][\^9tvrj]ռNri~sE:V/F5KE$eA"~A"R|yK$Rs'~m"ObE,a̪^")X'˜UEsK, Ⳛ|NJXreXYՏ%O}Xn_9XzVMm)pGl-!֕b ƺbAllXjs"sJd=gɒHW"4! #i#~]cFCI֓蝌iXQd~m]x^s1iYhEJ`vXCEZȭGd> " -R=kY]d"#QWn^Si'y];/Z,cV8s;oc+,YXsOqtheu崖tI"EV}3tm$2o\=s\"2< %:.DDؒDO'D:(Kj!~ -<fՐHݲŝ &lTwtmɎȅv$97ZY]D \2|_5ҕ.OTk:{DZ.H1Ք1[#7D"ֈ2 H$26$^D:  r ^+$2"$z<t9! P@@ T@ 4@ t@ L D(  \ h^~]BUGXEfUMzӬre | k͊ռyL^:F9g.܋ =z*ڨeuH˪[g)" 0 \D"O?w$-xk["bawl 2 1[|ig"#Oμrx˧䩏rd 5G]P|VIIMp$&b4ەhcc qt;%洉"^\*} I䣅4jiId}z2CSj4, S1&X EC?}h9UmV0(ΐYǤQrU{JO]{'629&7-eC=ReEarX)EarX)EarX)EarX)EarX)EarX)EarX)EarX)Ea5tdU$k$ĸd[Br2$ XWRLLR#>.X6-Kd%oBKetE D ԫCiڦ6. }s044>`"&DzQ}{ky -]B髀DJj!!R4B"x -?@ O37C"K"ɖU P@@ T@ 4@ t@ L D(  \ h^~D,'+[۟icaNũr9uans8̚ɢke-kG9G?H=ʐsw9M"KDͶ͢'3ME["/*'$?5"g>լ/n:}VBD"ؒ$Ewt]"L4UE͎WzSe{2dßz殳ֈ8Gl -@ۂ6WLb-p ;!)y3ss[K4D6:侙({&M[W^\N'M;xį,D3r$F3sd0꙼Dׅ+oDA=p%됺)MBWB">"-xB"ouKzO4Q(  -*:`&"Af`V`.4/?`ֆ0 je"W3nVjfY[ba0W9Y%LZj.g-McqVY=]g6sOK$,D -6("$"?(,͒HKz'־ZD"^E$A"$Ͼ(7=rwC")ՒKH"|I>ʞ%3HIJLum)A).7mKJKvŤO/W4J$( eI{R#']9YW3 MKvv~'!IƔu}IF${ 2 iH\=B ,Րr$,3Y#C"˯&O$ P@@ T@ 4@ t@ L D(  \ h^~3JYy#g(465r|WXSH$ZJX2cmu3dmO"C|Q" EvoIL|mC/l}{mCdXxSeDI"] V5'ƻ][LJJ@h:msĻ`+!ke-t"]o"v+2LSWf珤kiK3=FCo;,rY`":HAӼ=L&"1WaqXdv2}Yd%t~Oa7,BSðHV ,2v ^S(  -*:`&"Af`V`.4/?`W|U^SK|f"CjU/4U5^p7VL9mDu9ͻ8w_WQD:E,b,&X.bӖXg+qfw/HdJ?|gH*f2H)H䢅Qĺ 591asEN[ &\H(Itj<}D[;T -_Dr3R u@f'M;Yׅ3FC)hlDȪH̏@!CGH9 -̿ LT4 M֛"=WyG!zH$w )DDjO4xM(h@Q X8 y^z(Ջk>0plTi$m3k}^9xg?j<={xoYwQv(r -"*|E6aUD%"lMjLQ$E&Mo77{]h`""lI+wN]TkXwI\Eߣ t^(h"awaG<9D[bJ -Xږ$''']qq݄W_!i -C+nPUNX:C3ޜ-ǯ+O?d4tHy5i,mF6DgDϔ5=dy͙dx%\~L\|4dY$O6ok<&M,2Nl9+'PP 0 -D3+ppx0U8KMN1*Y.5-v5PYS-˘XK o-d9G sY=s'R34Dm,/X$je-"ҿ:jOzELDE+"XUWJ vP^yHES!DU/yz5ꧼo{ K3 HY5(v:lq1N͕lcT2=%.!!&6Tk]۠ɁDoMEVE&݋iņ 隡4L_Wܛhy4W3 HcQ $2ž;DVɄg [I[]DDD{3!!HeQdUH%Hdk@@@@"@$ , -܀> NHderT+cS0,NӳZX2 d{o5Z8k/g/c%; +%{Knm""cEn9"%gh~M[ڷV !<'7=U[AGp"EdҪzyH٧QnFu +$29L$@"d"@='$ה -ȁ( -h DHX؁8  < |-ĪMT +_͜2%FYM>buŌ~5r%TCVM?KNri9K&t)f4 ыH䏂DnDmD~ӡ)m?qsDȢ$i 7ND"Cl$D\_ߟjD}և ,4zSe5ʙ,CgU9SR !6JI$[jJlbJ%_em(/߼Z|RE(V~qKԆܺKg[hS]9Mf ~d^i<,4J?* }3l6fLdbyBxcLq:vi8"0 fDSH+ -CiEd+"R""DZ)"~/t;}I?s‘9{sGf_)a*Kcm1ιc\qYg>Z+4_֯g~CJ&V_-  -*:`&AHf`V`,p'pxUv7s+ jɭy4nm[7{=nSvZ0g)XEs;8UݞqyP -i(/>»O%QrlG;vvy? -%"$([ynOH [f$o, lU+$pݼpxoWƈDt1"DnD'ju&_e$2EL$X,jg6HdчֵȍKYĥsUKVoa7t,r^ )NE||MJtlLÖ&d_ljLշ3_f} B3r]J~nմ<Ԟ.%F$m^u7 ?ǵ3]ܬbbWb3v3" &o"t$T1E`hLoq,";֯ -ws5}h~]1D0Exq]1'+d oJ<Ұ|xŐa>\ENDq˓SB*舞^DEZi涆()  -*:`&AHf`V`,p'px̯Fz"?A$vLPQ uQ? .chʧYQEKh%"{8WZ nx:EoWHHfDQOEߘFgF?Cg"QKHއZB"n1:_3Q]&D*B"dyϜ5%}SI$)c lH$ÕaKKvWJvfJ&X`tOC+3'-w0}sH\pr&W. hk!|x)Tu+\DF ;˪w_DޅDzhfz[0HC/@" AH:<̽JiPP 0 $3+8< |x@VGlog*nA1]ʫyASk+y]1 x=s+svK7onِXSO#|[͟!u leD]O"G!ȲvȲDoC:C7x>DY^w IS'S~{h[?@TMߝII\-lNW3,)fd&ge^5׻|Oo9zYU*A<juo F؇'dgxSIԌLS!3w$v>ۼtbZ'2WLU@"A" Hd=l ̏@"5@@@@@H~;@SD9DT-v{J;lgz;KE.&Pu D39B\e+Ix[|3]+jL›V_K"qg";N»ɻId\EUuP9SWU՜;m;O /3hM͇$#tٕV\zV4tDў4 ԍA"/-C"ȑO!)HkH$)  -*:`&AHf`V`,p'pxYM"gDy̓JQQROEfhD]=&:CLմ1Q5Ȁh#FHHgDW0+g%HID"#IHDG$b"kH\ǻd?s~&;b$,E#N"ܾ!ykte,[{<)=c~FO}m y,r<T%i.w\͙eK)mlVRRU_4iyp'?ԞU_Q6tU'Ơr/E"`'xS-P7[<+q|yiz=C)4(R2(L t"gG'GEڎ"k'=V-)  -*:`&AHf`V`,p'pxwD>#D bZ`nAS+hyݔ a8-EiHhL4 >b/RUUEO|dw&%,?sEb\_F֓c+k~f>R>h#"rfioQX_ B"oF3!JQS"z>^?sV3_lT2LӖms6wJۖrgR.Y-{`Ogj>}W[{`!n6J簟)8q~CHqvWv;hco@"UqHM݌g8pd;<ʹ1QG-$C4A"3^wPP 0 $3+8< |x@V Rc:êDT͉ꪕcnGE1)z=xGDsQX+âR"S#.' |9fM"_ t6D"߾F3к"eR~hU"#QD+aEFV"ENS(Em:tEt"R{3˛~lh<(r/ET#R2ҹL'ksfq)6g;ņ$ٜYə̤$NTd(rL>9Gs~UG{PXP3}ܯy|5o -"Te_ܕns3m)2,R Sq:Zdq (񯞂a::O"~t"c0bl}9U=,jm݂R4Ijv)$*b9/XKE9CpM \)7*3 QKeRg:6"/y$TIa{,H{,Hܷ!QĐ(03; W$RT buJ$NDjDQt a~8IOJ92lN"#d:R22W5(-ӵr* `?1$=c2? ?}uCAdp17 4q.g$]Mw/DN\D_#*ưGcWGr~'$DϜ*!Ukb( -h āxX  \>< "-ZY"= UY EyA3'j'Dݜ %rF.bݩTv8j=uXDJ-1-~'"-~gC$|Gדȫo$\~7L8$!IѲHePD &. JAHN/?s.y뫛sgr6&qff%L֖9m$!Vy{YKT$2i|5Wܛ_5\Twh;u窌7^Gjx|,H]'m}iIgF~-$1z(2rNDʗysP|< L3rXJjZz`F`q $D``vpxHj92ĪH~FPN!^kj0 ә%o`,KQv8 -$R{x*+%R*!DF$UB"ߓUB" >u=Q&}<$r:"O%ȾDNG$[cJD1$ŵ$]D是 Dc3~07^Tۙt#ʰ9Ys]l۲]KbWmg-iXԂL>Z)? 6Osm~+{-gj ->ƃGΧˌ4TqQHHąD~ m*i[! /A"qH2>WDBd)4w^9`@,P%P5-=0#08@"0 ;`8 p|"fۘ( $QMJ{!CJHV%󉥜XۉS`{G8(ۙ"[):'H䦨ID~+!7D"+k ^*9bn>-"-k}ng^ɆEzawy>ad: /߲,ȶi紹)6e3])Y)IWͰѽE*aww0a/*fr~UIcPlf Qȯ+/ -~m4^fEFhĢIX] ywtg~櫐ye,zZ{c $r6/3Mf?TI`YI66ͽΰʲ.[FvKIҒ~> ->#۾ _)zoUoWTMӽS;]7rzaؗlM]WVʓginiYFɬ3/fďQٴ(sN|3<)μ'}e$ B<`BP@ -(CU(46ЅP -CS( -ć @b(`,5l(+BO( |?CZ[u}/֬+*ڮaxrwb̬vdw8Ny=>> ?ek Tk g^Uk |4 W/xYW weW.p+\e\uDkk>v=cm"k'i^[_~Obww"wU3_x! kmr2_{v1hwz>7fqkRcu/pgK (-vT#M4M4iR.4&1goo۵nk%ֶEDEDD+RDD+""""~_Xkٞ -msϾ?3Ry|۟+xŹk׮n7|Y%&W]z=XOlL>Oه_ݗ\1oJR㊋OKv<)Ib]1 ~>6N_ҝ?27(SfnɚErM}s6N.f2mG9Shؼ6`4T*%y[_?sT9p"K7T8^|N̾gHΛPz^PzWx\^xF_ȟVK> ״+C3J'|mSJW.|8|L0 hAz0L` X Q vp/, !!)U&hx]w -*fsL_SN59'^qO:ğ3Jg'YkOVO6Oҟ"?ER)ZM]P9].2էY>ݹ|L0 hAz0L` X Q vp/, !%& /kDM%.]яC`M#yZ̉rF#RI,b_#Q""k"B"rk\,E+xe"bّiIxD.*"^)" RD>'_JyAd"O =+C#"jsDd"mFD>b 0gEҟᘐӋ$ƚY~:E$JM|qDc\)qTol'6srD?taJL"-W_=dOeh;9]c{X?TcQg}1lnjYmsu""}#"5/퍮i=vD)ED -G"Ddn -;Iׁ.Di ܈> -P=&0`(;8 n|pQVx J^SMnYrbL-G -jV#"4" =Kpg<+Xi^>0#)ED!E$KIe"rILDݙIdu׿r)""tw˛9K&"lA LFĝ}lD(@D.mFDlN")fX;I伟O1\152")"q1)I= ._zJ+9sSl25)HCш""2#"k2 .3rcCt l.Z+[y[|J3"R=̾73kDY AD_R*1L"E d!"o= 5҈(^%x)|L0 hAz0L` X Q vp/, !L |u)$R(09buFdP5t^)8 -E*q/O}ĿD)!0H|,"7D&)"?"bQϤe"bܑ4Oi?!"oJ)"""EyiRD&$HDnUFąI$fιy  }os6"re$Ґ̛)!5WI䊛xf""_;"⊋|1SS}1,srDZgO"sX佳}=i:r~m!0cv>g*[-] -,s'""R}K{CDGfDDdmDnL"u#"+ )僁iDdaL"cB&0dlL"*`@ Ђ`#  "  '^X@BeH1QM6 C16 -|O,hD5.b9.EO]|ݼQ`@7 'яGH.)"RDD$Nۑ||䈤<"""o['E2CwNH;LDw$"?]lDH8>4nN"MÈKf߷c9+;H3~Dj9MIHKMOX'󤥻 id_BRj/9E99"% _)z_jCDQu MwvvN7Wf22L}GRL"]CDm# LA"{g+""2<"ZuT马8= *!" P_ED - -*wWTjЀtfl`8 Bx @?J5Q CP$jrmzD^4OKh=.iDōk"SNUJ%$X+?$)M"|UH4|I&"W:ecQr=qQ."WJH-RE"3*rTLEvE3˒(RQdNjQ8Te:FTASg>cίx&t9*KKI|IERXWB\ϕ≉KN%cNH-oϴ(Te2"aMvY  +k† cWgʼܜfbmM|aTVdwﳷ"c"UϢ"" "}H=HI*@"F)T$;)BE&ب> -P=&0`(;8 n|pQډL\QFD@kP,;˨HGDTVdJqVw Lo[%z!(*R"UD/U+29C~v;R/|lߵVȟ(+HPfqHq홟fFg݌QԏmF-"s%"(OC̹c/yFyI._B -l:EMZKKMrQ8\eΜBձ&27(5?Tk[cKUn,x,h99e!5(d>q2mET跢{'^^w#*Ͽ`Á# `l2rͽJ.|NCEo[{ -S AZЁ `VADBD7x > B8(v8SLp NJw{vz9sngl˻!)mYgpȡ"礈A&"RD,2eʭg~#LDFD$HsNFJD䆋?Dۈdι*gЇ^eFkX|^"j""zG*g|(.?Dݟ9(E/ow9^Z8(o݌Oco|J"\ ҟ3z y)"MrHוIzƟOMO99"K{sZRV0##H@X3rSNN8WU aF}!0TJ>v2Ϲ(}AD$"R=ۡ៼"" 3(Eb)|`)D| -)g#"*`@ Ђ`#  "  '^X@BGUN5D,Ћ"~7 .b#b)Ղn7+gJ}Qp 6n! |Y?B`U _Z͕En"""D&E)ERv$"w~fWl9 ۤJȻҕշID#7#4|A&"LZZL"ID/oޟqL!"Ea=9&{ASgH!\0T3%̿uZ]IHIMu$'ǹ|,rRSSY_| =9"YY')^Q -F0_BDrj24Y.G†357s/7[-a}zY{>7}5}ei~iz iGD -hD#"-UD;@D'IlvD$j#"*`@ Ђ`#  "  '^X@B$kcj%Q5Lf4ADaJ0vDsKeXmN"ji}"ё):[DqOFѿ(+b`A|34IP#E7y%"u$HDn""[Esb!y_Hhۋ"#M"!)"ϧ+we5t7yyO5#"OćD|?Hbc;X~ŁIN?&ɟOI$=驮tKޞ;9"շ_|O^Lr j Æw'K9SKasVH[=o#orf4˙ѝw3K =Sv3UXΔ7c9ډ44 -,ޡ .랧7y=Ԡ-@0 `+ "! -p<!@7n՘4&Q3 hG] 8OLe=&Z "rDz!)*+$"GHhM2gn"+)"_Yȯ|A&"O)rptZwYUH˙ܞ02,g&>5"_shGD^n0N5q3Wa$2 ҟ13g 1|jYrBr7.ٕOĹdO'%ޘԤS67wiQH -U{{!D ȋŘD33 ;`\L#mZ-K5 -kmDg14Nciyhotf>-Ha7"R ?IdiL"sv)}1Ѝo 46aiAD`)EDoD -7"Ԡ-@0 `+ "! -p<!@~&Ln atӼy1THk#rVK'Uy^D=Gxso5-Mn|?R)")"eRDn|Hn]#y/~=_|=zI"/L"KyLe"rDw^{jDX8XIr3"w3, ςm3LJ|8~:E$>pa]qi .ߟb=dWZZJjz|rDdoYU*ar5fh9 aMaCg~13Z0LzL"Y+Hm&.ίgzi{i).$R a$│ׁlL" tҍDFDV${bTjЀtfl`8 Bx @?(QU -̨&A[Ct9J{Vgӊ`!AZDlH3&R#'yǔ$,gwd>7+;Hȭ2Dn"rLD"rLDۑtfnS۪"\edm{e<ܕ>uEe*sjĘȏPuk#nZ}eve"=?FEv/ryN"~gXy:U$6&)O|iqԘXWjl/IHHK9"e;_g{8`*u[5/XsCl{fLy/YJrywm\eUQmwWkT82W8;딞nPn,UJ;lIkbMJ"fJ‡TjЀtfl`8 Bx @?pj6'YMkv.+Oq5՜g^H$Xl4$/  t/; YlxB2!XC|VjHԐǷmHPِǥ/Ӑsw!exsf W3AݵmBwdYW 9'?=#qsHW3,G1 ҟb5ǜN$Do8ƤbS<._2ź&=cRcOlV?} -U2mD k -3ynُ 9f:8S{ؼVnd?#|D5f}7}Drg0>L` | "-X}t0V3DjY "5DV>O$ -P=&0`(;8 n|pއ/명D;I>^P*{Ӛ`.,ڤK"9;WL/[$ ` ';"G)"ADx)"aG"%[^W5gRD,RD,v$"?|X$aIHOwy=I)Դ#RSh}{V3Y^4-!"ׅ5'9\Xߔ6sXu3tZVoFDmE,n)|mo@>]mHMh?"RZT;zJ93?CoΌmDS AZЁ `VADBD7x > B8lf -EUauY"qCİD :Il4"t5G샂8'&k'@  -Ꮯ#EL)"27gN%9#w*w$"?]WM)ooILDݳ3{ JD"=;rI?PsH3VDD~,ĸO ]I{%"li5xғcR46ƺ0>.͛zrD/>O#Bw!S~ДvM/f2_9`;̙K*SX𶂻 -L" tHuT>"2ׂ_@D -*gwqDڈ> -P=&0`(;8 n|p)ژDUX#FA#jsD] ĸ@L#ĜG,:OlyjۉEG q IB#$F ݱ(8L!sME&"~)"6)"w$"IGHw6?lyUD2TȲ4ɽf~ӿ o͸sOvmF0},? y4HI$P1 ">ѝtW'!K⟒S|(̆ժ[ܼ>r&sr3jfک8]cKL"kX-s] -kN3oh iz}otO> "t9"XO#r"8kVEDVfnwxg*5CD6"Ԡ-@0 `+ "! -p<!@~3NTD] XHLyMmD'Brb!Dg3q4"WE˄-b0?M(E$Vd"5)"RD>'HDvDҶyo"'ܶf)RDnh۷kq3\voFWtّ.L"cgҟbIxOIMKNr$ JNw$%x}l\R!G}407(څ2NVX_Ak36wnI$ {Z"%8d~0 hAz0L` X Q vp/, !48arz@d.ky0*{DӴ`^ :$ںwLh_"DgqwO ߨ@O%jrf41TEDvD/D$AȁmؙCVospa[Ed"bVDdYȳ>:k"nU;lȟJI8㥈82Qwىt9 )"2NG>v߿y$/%cIb؄OLG - 2cm kZr25v8_X0L0f=ƙ -'ꬖlL"ռm˙[QOniADj&Dm"kJOJoDL{ Ho`93܉Ig˙ヘD -1䵝v'5|S)2+D])jJE[Ea;DS{yXYmF)9NˢcBt(2N%WK^X&P'JKLD[љHD>)"[VIc9&EA&omvtۧx/ޑk"%Z+~E͈ )"e\x$է$z^O˗JlIHr4w#G-)T{Z CӸt@;^0,yXygjx l/Z -f0 -#5eDd졋ogϡwgV&cm䇈B3"R/""m dGgIj"R҂>uT*`@ Ђ`#  "  '^X@BGTZ~w&膉~M4TN"#2-XsE[3&M'qbusLpz*Q&qm")!XBBS[mX=SfjݶfKDҗtqok׿I SJ)E他.fHD.Ԉ811""ןzRsXk y%Hb/ނ|[}᫈-_9""l &a],1.Oz\R\ߟ?oꥫYϬ3˙0c;zNXXVa:$3Y-O#"y ->uH}n]慧JDw)Vn@D "w;7x0ߊ4!"#!"љBDdƍc5h@ :Ѓ`3X -6Hhx~`!A<X?d}{H't墾chr\4&O4 zX;#O+ߠo"l -e2wFKK݆HK݆HDg}BDҷD'dKDRD};Af:fGgFZ7` y2їX? 1ȷ0c9yi5xxǻ|I/ŗI;;#^Sz3xE훓"2KwO9f03µo?"̦\qĸD|q)I?9"?6~M3Օ2%H aMիYq' cAg=6ga)k/mSwyXLOOj~G$R -=dH˟*=X=&k˙ `)'LciHq$Ԡ-@0 `+ "! -p<!@~ -!L5KvAU LL^0K v <:,Lh$*ӂx{_l@nafgl6!2adY3Uev?lV2x[Ȥ;10i[l =x<}CEY9"qAvcοgmCEfP>晖6*Ԡ-@0 `+ "! -p<!@27US"C]SV:&>b\L5XrDuҋ"t=3CDO.2 "'lGb`ITq{4n{wTOڲzՖ[VZ"j"?xʼQX |ݙu>Hbܿ9^3?CD.9IK`&Iwb3~ec}) 14W&"[Vr/dr;24c5un|6o0TEk[D$}>&"57z};!z(Qa "2""2M߁7z>⫈Hs"{0P("G)FDj1."D/0eD!ЗFT]? 8 BіMoT8 -8sHpW1;!Гͦ [FBp?xY#"RD~=w$"ۮO(Oxx^?&̛y_&""{wD~#އ̕z7K0X-{}U[I7A|z e]aQf X$])^D$&ʆMI%x}L"eEsFhDVUJ/d -EDkowqѰ>8lh-ΙZF^DD{{i .ξs}'2LJ/> #"RY җFH߁"Rݲ:ToDS AZЁ `VADBD7x > B8m{/ \]&h8ї|8#&x`Y󂭅gzmE9ǻ+EO.񖋾*$l$ad"RzTۮgJw$"1-yDD*ID)segRD*= -g;xx,g{ɪ9D"L" )3eb9s(@i'M@:S1 (8 OK%<$IdrH]μ9N_ݾiq.ח,d&_9`l$R?h `93T -#&+'홮/q}YGDW0L`,o#f7cZ>(x{nYADJ홚 -Dd괛DUtu H+͎ 4H̥'X[$H`oMsϠ|!-DHC-[DD)fۈ)E6"of"*"C>+eX}Sg$~w'&^H"&"r}D:6# D$ 9{`o`9{˙s0v7&y9cİ).o|:zc]I .'-ٛr5/Ј (T=aV'34tua}W10g/$R4"-`yzoZ>ޯ':\L"a)|Ϭ.!"KO -= ғ;FGόtoDS AZЁ `VADBD7x > B8o' @%juDJVTL͉vѺ@6{f1.8;{H ok!R扁9!,D>#K)[\"ELܲ#O&FɱPn{(^)"Yx -ݑIԵH 3l^Yǻ>F7DD:O\q=1Њ&&ĺ})i._'ޕŻr=B7"Y ޑFTd&+*2G%:J -*2Wz*)2غQ|L0 hAz0L` X Q vp/, !2=OT&2D]%jE2M}h(NѼ*Z3"tYscJtDό@GlYw $KxzF#RE2XRE2ݑ}'gUDGNz*xe""Ed@Z&"+;^\яQ`#}gDfDs"R2gy"\Έz"c.Gxf(ٯNOIl˗ؔdr_i,޿ Dd=L=he>C36q@Vj3 {LMuas{RM{E04b麖pѣCx""=cɤ'tЧgE&="2=y{GD7"Ԡ-@0 `+ "! -p<!@7FLj"L6QgBQ; }?1oY#Ib#b[Eč{OĝI<#wL6aI``}=sa7ܞ)ٳꖉRD~)E-w$"7~{r=31ssyU9>i=tQdNf=oG9HDϝʷN" ?g`3iqݒ̓W!"cMn_rKѳRSDXa 7_p/dGDZo k22c͜-L"w 3XLa=3Pcd=#"Kt=gΈ#{wL""RC/L{Yxk}H[-]xPkGw7"ۈ> -P=&0`(;8 n|pD'2ӂU4 э Jb$)_4K36=SA3"Z(:(x O/fb<{oD-EL)"ߒ{G">f]nU=m%Ǜgz=vj382"(bDEAE2_j ؃LubxF8|.*ꪈ?.=쑞ğJx]ޘԔ\lB)V;?q]=}}Uv^Xx}v~N6\6,geLas*{#}ia޶|1(35 &oEљExSϣ"cH<3/a=3@߁78 3AEju`(nvCE͚ܨ> -P=&0`(;8 n|px}UZ|n'rB߁7)'y ^Ii\7y5GM{)WE&xJwZJE!8-ZϜ!Smӛ\Ԯ!"w^9C;&!/Ι - Z&汞nmS-1=y>\"RJ}p'F"ziumg(2Ĕ?hH+uG""rv3|L0 hAz0L` X Q vp/, !H8 -bM}\F,5'6G#F3 ľJe%"+@GTI0OMO1|]vۍ"~G"IG\UDz~]kd"KȢDRD|Ufܟyq^H+VqjyQCƻ42ADҟX߾02N%Sc^ZT/95Ε䋏sŧxcԄS^_U%/IddH٫^$Ry=HQL"oai| -H)إCEY*CX{*&NnV_ AZЁ `VADBD7x > B8{Df -5֊v m(hiVFoϔ A !Y$<ט1#IXbٙ2I{o}RDΒHDdԼK|Yo|?[vcVϖ"O)~B~ȡn]I$fDnۅ<|?ӡaD?DD}Dg=a9;zZYIf)ɮW}KшP-a'>|@;V C_L!g* nZ&_xBammUXμDOHtQtS>{+^W,gADj;W;ޢ"Dd`tZ >2v7}CއHvi}BT%2e:GL[Jg8"D`eXkōi_Ymuc@pV<$z}GFSzRD&J)""rLD*w$"|~׿zDIT&"oHLDgUIfIۑ̗(rn机ڌ7U6ͅDk ҟ""oc1˙Nk"ش8oB+|i)4WjK%Q1kfsr/܈xi䀶) [0?u8͙:*XSXmw`V{_]p> -H틈}zuDg -/GDBDj&էGW "eO""͈HY"Rz{YU4F0,`D@$DA4Np?LFCqj%,QM%A/chZROsV nn#Gns{xG/ ;NE$DB0RDXvޑد*"<\) m)"MRD42ΎL"l/QLa9g#"b!"OC*cB_?b1^oyb}IT'MNvqIK'Ǥ&r.Z/+Te2cHa#!;l06<ΙNeCL"Ez>˙`9"2 Vc9S_}kD#XO) "Ru 3]`9"R|L0 hAz0L` X Q vp/, !aYдOЏ0q4$kEK@ s*>$8g`9V 0b!TE>#xHvk:;GTFTMUUUQM%LQI.⢪zsU'9l]zV2Pk,e Cq&61%J(%z0 Ƅ8σ#pr=rPy}4J$OQ/k_Ήg-YEHǦcVoْs:"L&_~xgx毂@'(CazD$ܹ!DK;)tLsvqlw2#/|rvƺkWz7!"һ1u۹戶q Xӏ5*bl,S= -S`) -nc8"|@D<,EDF^@Dzj',lJDm~}>f/^ZT hAz0L` Xvp\/AxBዻZ0DUGMYcFD4Lch$.D4"1Eg -^/qBGIFIǙ(rۦ}qƛے9ޗTWDt%btO"-/"9Y|3I^;Ǚ~zoc%ٰy0W`­;>p=Eӳ47Kqs,N|n/%)."%9DW(;1tBbRz`)WS6+Ϟ5kk+,/Fd)v6O=u@G=[zF ҁ%T;"Rޤ<"Y+Ie4O!9%A3A{\@ϑ` ᆍV"7ljg6}!G3XؒºGU+Ypf_glYֈ[o|kO܈ i-"#"r%DJ!Wzo0>)0HzH6,_Rl365ݝ̹)Y)]{'ҲP)?OW"W~);t y\Ci[ĸX4wbW->)XO7 -fDd:fuf=zk}'r{ᙞg"DdaDSH7ژ՗zx(AZЁ `VA"$x ,p0D@KaihDfh$*KeX&yЕH9HHh:KDW#9ŽnH -\/HG -8G#Gn6c|Ol$ƾ"rn?έW"oq"o,э[Nqi<;~3INDd=Df V"Tq+j%gK?l7)پT>ß_Uz;EnJd+b+ն|LW@L?;ϰlxj6f{b^(GDT G۩?HH"rzU"R3zw#:4"q+h9!p "ragX]{>!^4>@OM s_~P4F0,`$BNp!!C 6(\$L{n64Ւ85EaiOIuXHq8,zgxk%OF@Dи.n0 3 f9"8s9"7Qʼn\Ǜ?> #>*G{rD޽wl7l|wKϼm|zyN䇈HA&"2f̿|>?A3Ì&„?9/0_!X"feYTKfwrf/HIHxX4^tALOɯh&{#O?3gb}Hu`|L6{i܍Ǚp4#"t@QDDdH>zx-D"R^`"RD ""DZ{י9J`@jЀtflI`8  )D9%1EQ,QF5sD[<1zxE+Iu@kJ{pb`QH7J:^+7i'"7Q3NDnڒ쵶wfGĉĦͮDdbf[Wn "!=7/V['^ t,BxyૈȗՋU?Jb8itV;l.ƦK_F=6f%Dd`)O=?OSpD[ ӗ˪cg-Gk/۱>Dd|90ޏH})}yH9}9""`%RщQK|?XԂ-b%2XCӳ3X,q%T hAz0L` Xvp\/AxB-:Y:"K:ĸ*yX%kf1KտNKd% Y)DBRumLmA9"Iq"##'"ܒK9z#{=E$e^wvZMsə 河3S H-˩9v@ɺS3l"+YV N#PΖԼG?Si -h˦c -i;1Z- ,EZkӂm~9KGf?޸qa)V>H}"ix~+JwMfU'" c9J`@jЀtflI`8  ^mV&*}ֈ~Q jEzJ4tvѴJ̧˪`UQd8fEg-q5r[)zkD.!,^}6lrDn#bޭrDx6~y}qoQU6ڒ3o%{2o]Hd.V"=!0zV)0"b켮xYi)>ܼp{<} pu~h" UNJtndݚZ=S3wmXKc*g -K>k>BÛcI/;,W8fUx[眂]PpZQ -lVF|Efߛx9Edtm0Y[V}P4F0,`$BNp!!C @J_YgYUOjz}~ݤO?>S|od~$ƏT~(d=.cjϏ*(nd?8z9!ߖ⋓9!ߖ⋓F52bo _N-NB%NiO+UHȿc$d$}!7tƪ ju.;M }~:1˝ʲnoV&epYf ZoBY~zSE>.x1W3uwLW|\C;=?fo1kS`;Ru<}%3ݾ6Yni[hm"#tH^:7Xt -atYL9"7Ɖv9"rDn[mY::("]I{4/%ND>% G8ywK5O;#^guț7\8Xc::䑎+"s AD~8q]GS}wJvei)X@frfֺu9+O];,,mc流s5umјn<\鈱1SQ"R(XlUt< k6;3""=#"mH=mGD*GDz}9!P@/yhW5+~FD*hDADV/F?S @ Ђ`#  ! '^`!X{+)gQC}D3)jD]INb'\L,Q2}#RG߈n&Eπm"Z@Wuzړ -'GdG|B}rDvĉ'$"?~Z "ue%rf3++6IthK^3^07`%R"q OG6DOa%ŒaHtHϕ_Oa8:rNe&#"^wjfurfV>-+ۘwSy3ƈna4O:k8XX_3M[ůiD`+BC$|㜿ME{"[z56n8ltUD~'"JSq"rYfޒN{ &/ -""FD޻$""_CDy&G s7\ծw/+#͗26NNNKN{4sN]I.~oBt&O]v:W3dD;ӕ_x-P1tL5 -K`=*؊љGgv8':h,"r$"R+t%*2bio=!!| ano_~d`SJ`@jЀtfr! '^`!?e]Q՜^5EDCtD?I $cdj'rɲ(YЕHtDɱ(9=;G&\9 GiDIďD$(G%G'"-HZx7Zs!"rD̛V#bV}#!]a\Dll(׬nXmT&|<"r("وU6Ot>z;CMdp,LSa0H; -޻.>δ穇r5_ts MmcWK4ż"U/Xl퍈H9k("-"Za`X`t1X{D<ϦlefS3Sn_o_˥;@kDU(LN!"TWE5 +/c',ZDF`T.#"+];ekL;GFdtGDFDO*|DWH׳^| 9L^~P4F0,`$BNp!!C ߉zޜ*ԝD6gDCh,%a<)Zkڮww@r g=nTtM ΐPpD7}'*G8q{ZMq"bܒ|lxLfLt[Mμ%/Vp̫^Yv9"__gv癿jBDJ -ѿxycӄ¥3pgK (-vʕTcIҘifcL,4I nb&f}.׭vm;BƙZ-"8"80PD#"#RJD\;9z46ݾU*GjO?;m֩䒽RI5{R3~kvuK2R3ڕ+~׵/׵73񸾳+MK}uKV^7IdxO2zb|?q4W?1z)^WJ/ϥ<H5{~O{?9gwGI -{`Nܥ2 kG#G" 'ӏ16X#WXJ𶙜ve_R_[SljW8{Q{h@n|MiSzWS+}OM({G[g۔g|KųJ2rsmr*` -Ԡ-@0 `+ b '^` !WiT %DMPA(y}1'b9X+۶<>f[l`osfxJ/p$0#˅P5 o㷑mVw_FGSn[Fw.JwNӜF8'IO/=3IYEUMS"(ٕeSͭQn*Y>rPn *mraf_ ?[RqF|o~o5˗OVefӿu饿gQ3oeo5H T#^?~3k5Vu/-#=!ՕHt>WJJH23#RH4"GN)T"} x?[= B ibK3ɼH,˂LA>FUtOOKbopIa18@Bu"rINHLDt[Zf)y[g[RD~'ED)ERDl2KD[{Ύé{8!PHBhDXDH&EH%|Q&"A)"RD(D䟾93"#"rFZ.ERD~[W|F&";HLDvD6ȘLDw:&v30!"|""☯Ou駈VDDD3)"i) q.Ηb c4J/.KLI93"SrV'W0#Hc}ٚ&,g:ގ޵G_La˙טkk ֥;x[]X/"R0rUlnc -Gu-"DDz%RLQ;"RMH_RoADFfJNk(P=&0`hXxp <_eJBTn#9^["ꚉ~Y0c?1 :bt)!^AUYM{& W!FZ$I_&"KID-kF˙_n4|moȒ #2#"gD'$ώ 9IDUIdח3#""?>.ED%E)"ߕ&LDRD+M"Xf9s\˙}^qqM2Dn"2!3"R)EdBfٳ%Du\gD۝˙o~Y;&7 O{FD "O>MKsyQ .BZ&9T6.ޟfDw ӈ-*TX""9uY#am{tʡlSas -J=o]mӏ+n.Q쟿2Cg!"eǔcJ`7"Sկ!f?>KQK9(EGRD2ILDoIDsD$kIov>"swSRD:qk"O/s]9~՟H9DD)&AiaL藽#"3KD>5x;NG싈H8| 4ĸzv"")Vl?.ftW\'Ŧd&""Tz3#2ᮑL)j͝L?'R1^,UU]l}Yq)b:x 33xIza$R}[}Hj &7r,gf$3+#XT703eD:04`)-$p$@ Ђ`#  !bp<|A0@~E^5!0=BT>Q -Ay}51 V. xK`&8]Όb(8[xw)cdn(E$Zh)"څ*αOd OW2D"O2礈LD%VEL;"F;"3M0#"}cO""%A)"fg3;4zD'ϧ$z30r%2t9S""EkTQ hAz0L` X`8  > AAx @Jj%LnRT%.Q;@tI#0OuiI4w'YDk.H=]ܞK9sRt%yo=aEߔ'*bDnz\"\"lIDT]9GD~QDt^̻҅r*)"U&"?ݒ3יn6\}q/gGD_1_7܇*'H? 1΅0s_4IddID5D^X;"2^I$kBDDzD~Dj-"*` -Ԡ-@0 `+ b '^` !}ꔨ$$D/j#8/XJ-q%==(7(r$D bhlp{LD"rLD-;#"Ec3ܝ9%M"Q2)"I$J&"%wgyo$e0fticZ 5H? 1bIWLxGa|jjjF\< WZLu}8'3MMww5шP:J7Q|?~-+*˰Y?l#bs$RV[mMUH"2ꕱ+#"MtH^"rPy]ȉcH@͈B="P/w+kD5U@AZЁ `VA4@,Np8@BЯëFxTjԹDS&hnxä`l&n\E,yO75IW -~ޙ/yO+<5 m+x2x&E2ILD>%IV#"m9U{LEޓ*2%"zTrzE2-ȵĠ"E3HQy|cgQoo >QdA"{<<ʼ0|753S3]7!\ǛqfEJon%jfgޤxV/ޜy2=9 Mgr u5SQTdAzMΡ"W$GQ5T0*R*r"`y\AEjJjEE"CHuEj0ZE5U@AZЁ `VA4@,Np8@BЯ2HTy"IEѴvs"mshit=SG+rǔuA.rDnbu9$xe*rT("\%iYx("bo}HKPȉM/\.|K.re#FVgN G!yG>ׄ'ȫ/c=W2D?JM¦a=X/e9֛<lwFV7P/dED^Vdi k:G2 6>1MY̥ -KQ=ozӮnxd{e7"2@3/ "+wSz?|Y{{DDx+FDzzYn0jЀtfl 1 vp/2v7OTfF3D@#DFaB4 $-4"}4"+^!:zqO"o@w 7B&"JnKh"b8זի7BN&" RD ^Y3nK" EGO<]mFEpLƘh O1\ Zlo/WWV Lb\~63e3RYofBJڙ5ZH -UשsHSg4'ڞl#Yw94a1O>ܥ['mt=S&+c oM݆8J7܎$L3X?I9ж7 "ҵ24 "|Yzz_S D4F0,`DC Ă7x ,!aFQUAbT5͢_}h8H9I$ k9H(Қd'yAܭ_$шnQv1Fm2EHV)"Q2nIDo`Ed{DRD^&LD%E7Gd%IF|Z$ria~Jo|9l?g~䗟OI2<.oZ|LR<~/=9\bYgolnzpV(Y,/RD wR/͉[C;w_ߥ.Cǝ!k/2QXg.vum1e|}"H]O*y*= -)=)CxO_(̯$";X#"r&{[_ϴDD>IdW?}z""ID$zL"?EDNI󥤻R=X԰)..15EMHRSϺ(2}qjn|'3!"2Qk&##iDx|i.L"7!"w "+W.&gEDW:CH0"""rk m`IDvD̫[kTQ hAz0L` X`8  > AAx @J媚$f*)?I'j#"I#I$CƩ$$KhJ7yDG_<=HLb[|uI\}R`.)؞Z?fpLD:eP&"?ے_'׶\IL"*E$[#rSuM "04KxwEf8k 4\` ̄t;y&o!"ϞWOxRxWfMug?3#՛vֱD}jVP AAx pz!̄5EԕD'h?,c+1bi t9L'#.8<'j[&RKRtb=+>ǛOitV0?nY:P990[4[ST>G7xsD?haް)b+m>>;\[\lxɧId "z"rMDd= `zo"N#2'zM>=3UM;h'\œ$Q0 n!_4 f\-XEk=]άЈ, -Ȱ&SL!nk}$CB WV+m7Df:\%uepk>cdKȗ7?S."_|{?"`(rKu"A)**3nS"q>֓Ope"i\+5=3ŧ{g|,zeZK;zez,HcXDDWP/ݒm(2r4h1GEjxx[ۇE -)xضd8ZH/+7c=}+3H=X=7P2"K"GN"Mϣ"/U_S D4F0,`DC Ă7x ,!at]Y]!aŒ$u.r$Ch\$bIk>J3=}TtZ$yDU&)P([IhF`BfRE2TCRE2T 1tDdVȤEL)"F-ٳZx_ "Ҿ ;xAD ?#Ef0\ZgW^|ETnw&er.ܰ{Q$oIڿx)#![Sֶu,g 'ƲɈiiDda=9RUt=s%D9I(|"26Qd.Ddajo`9DnMOH| -9L=|M0jЀtfl 1 vp/$^5ѵ}h5eX[(R*g@t,+#\ $ -xDd"R.E)"DkRD"rLD[ (("#N>)"^ٷ%E.({GDuX "?@e~b\O.پALoƻ2.|si._b"AKwU=HݢBU -iDde9[=FɰpD7їMf -K{#cs=*,mݼmD{5vOyGD*n[%$"R$"@=!1)ehD.%_;߭$%3k5U@AZЁ `VA4@,Np8@B>># &D Y=!qLqb!abEa!&ёCi#]T -l'+I!.;Iw+\\")6;Rĵ%sL"{7HoV4)"+2""3|P͖lu7H$2gBDWD/ADv"";SL"'ߵea83 o˹li..wť ,2R(2vV7+|O -&wZQ/eijDt}I^ňob*²P[«bGrCD[9DD=&,gFGD[# -'3xDӷF4M"*` -Ԡ-@0 `+ b '^` !dl&B#-Du4 ypЍ"X -uF/fYHH"۲)& n|E)""R*E$(M"(E$$M"_{7H&G[a -)"-Hi1og[2|y=/0NtB4"eu󥔱 Hg3ayzkDfz7=sgŻto˟K~7̈ w=l5j>_UOGKL.ߖz'7 ˯Mм!eRkN륶ы 1Lm7{ -Gp&ڣ -w}JԻ)+3()}= -e{7S&RO<$7TQ hAz0L` X`8  > AAx @JvULe(j%.ܮٮ߮߮/ ʶ'/5o77-'[fA:9!GvgvtS;dl5``n{8Zc")"RDi nz@DDϱ}k"ӕglY̖ՐLD~%-gBRD2֜@|Dby)Dk^mv%H)L"ZDA)&o]5"70Ϋ-,2lq3WJjf˓q$R|Hw+>L"D'ִ Ft'd6>1M4Y꽼 U\'h;& -ֈ?a|H"3ӷF4a|co ޞ˙ut9s'D>r&?mTQ hAz0L` X`8  > AAx 4 E^}hvEU5_09b.,-'&ޞ/:ZgY.=]"X-dzQlu;Yl}r#ac#;-撖 -K#5-BDjݙŗy(CҷF vL.}7rh_`=D,gJEDr>z.kkTQ hAz0L` X`8  > AAx k񶉪q4 Q"ڹch&Ybi4"C4"EI$8q $0$l7Nԓ`. ~W|cx|cxU[s{oǾsn_67oL4ԛ7{x?di/D^V`'v|Ƙ ˙R<=n؇y,-#11!1$]?1ѕH;DsV/V!"W0FDn5 Ms]IQD 0xز1ՖZ̋oq5o=Qۦ[#ߧn*d{>&23LE5zMdi*GDu!"mct&2$(rnU@AZЁ `VA4@,Np8@BЯRCT 3G uWL~bMwgڈޝ9z`#gT!%µ -yD27=x37=-HϊOxGDt9aooْ[O~+IYxO$,"97DDv"" 3o̎Ux}דYŋ+5!.ŕHDoFY=ՈLPᅡL=QwF4ځbeXe_+35KIdS3mPՍ" -W0E{"ꡕ,MQkX[n -HI~X\e1Nu+,% y*2ZL WvW'3"}½XLM}Y(2Ay{o`}zQ5Z)y9Tdpl"*` -Ԡ-@0 `+ b '^` !dam=SOTS)F.A3!h[R7nblMyXOo飣Hho%.\ L; XHZm9M㍖*rVng5ȆtDV2  I@"WK*rLEޒKAEZP|"BfĢQmAiq߅Y2A ]ySėij bLzZE=K< /"wO䜠Y>PM~\@EVy"].ۣ[Y-ej'GL'-%,hZޖ_ޮ>x*|'fd{.H=lz)+"""G袧"A2zYߟQ7*@ Ђ`#  !bp<|A0@E#BQ7@aRRb%"Z"E{/qgax$ykYI*H 14K2*rTTREn"/]x"^Z}vMenڤd"]礈d"}Kn&`"zU{wxogmgˏ| S{qnDZ la\l:J:&͗ȱgphwjD[;hiYZVgiֿFtg[^2TKJ+';yz ˙=@Fci7y!"C7kso!" /""=3!"""Z -5h@ :Ѓ`3X -6;8 nXB9*$ # ]?3%;S4 |X4kH!D -E{3q, -#bћCQWGBm$!}tvfG$~SviB&"ܒImXdnV\+="En)"l|lIDʗ&򰛹x<<駈Hr"LhȈy5xR~_bÕ2=lfY7yVv5M޼L0:.{+K3HXU?N*l,oj-:F'zkW%igFD:#x L!&2LPpMo 饏'>zx-"*` -Ԡ-@0 `+ b '^` !V7հzhjvA5 n|L\D,#.g3^AMYL3&xK [`% um4|JftK]K"RB5\QD&^f99)"2J D-Y|DMދN߶b&7DhK[DDmW$E9"xo˟8+Γǧ{zmDV-@ɔN""֧4sw5]s MH[]4f1WBDxkW5"RD7ӕGJW='$6ҧgVyGDDfgr:D)(= F y5U@AZЁ `VA4@,Np8@Bڑ"㢪^d -/kQW O4pezaOM'D%yEvE&qI,I -otB&"Y)"rtӝ"[ڈ+"mL"Kr&Q$b_{m$_~9r0\_9}z"okO[:\*ADn'863}ǥ{\l|%fR\el*vY} -"2O񖼙nҌՇEtm֟|9PK1bڏ :{"2GN{eTy}izM-D.g&"wa9C=TqDddao`q~98 T>Q_S D4F0,`DC Ă7x ,!aTo7 5˫xM7u9~D0b$abH&2'اG qNtȠmBK !I2qmz٠צޒ`9.^QDuFD~)Mj/lID j݌3a^}Č9&fX$l{˙cEX|ȝsՅt]~Oj -= 2\O&K9LMXD[`ӍD6'_/e*;jL"9a,g yƮ-;lm@D* } 1[z}#Hm3"R""ہ>*` -Ԡ-@0 `+ b '^` !~9ÂYhDm1- P"K)o|b=z;](q E=*xxo֊ek3|| RD 2)E=йNHa/셼?"siyRf9-& ߝY}:tMd=qt!K;"Y3Kn#U9|H2镐T?{2\LO:;g{|wgfK -7v2'VsMi<̝ a٦*-o mH!=CH- -g͘D1;"2݆I#o*ޣEm"R1en%]oS*` -Ԡ-@0 `+ b '^` !WYTMJFyu傮#q7 -E2 {tu*o_w8G}u!·:>e"E$YHLDn",E$N&"7lf0XlxV3#xl-xo>s[6Drp -qN}"7cPb97!eI~]-τc$R34OMted8#\<&t9X;vF#2wS9Izl<=(wnlCa||M,gUmCD*v*^~$ѷFѷFG*z9>ۋL~k -5h@ :Ѓ`3X -6;8 nXBY{kDQfIhY0L 2tX0W::E' :L2('aD< wH`눯r9BmdztGo> *Eض|uK"YO\3^X]o-D<~?v~>yD> KH˙Id941?I 5ˆu3YH^yua5-1՗y]X$RS2=.ΛOIL`~ ^ʮz "_t@Dn= o6T<6ELCYSVX>,g&o_[~ ^[?Ow>Dc92Xb9S:;@S"2y?D3!"yw!"cէ[٥@ Ђ`#  !bp<|A0@`9SD1C'"k5^"ZC z7FdYO@3'x;%ZH`vV"␉RDn""RlID-l6dnraDD=zK9!M"_%I~dSV$K~@WCDĴ""dL"ߌ3)"rU0OWD8.=>XOR8WbbZ'ӛOuN"͈mW0StYogDhfxX[1M>ѷeJ kތ_#Mn{˙Ή+csJw!"#"]Cl%~/&!DHa&kS9j0Ob&Mc64YJcMCNs[יz#?u3kۋDHH#"AqR8ΰtgfD/ .{?:|>(gJF AED.?{g@ T@ 4@ t@ L , $+;pp0 |vAv@BWP !M-zB[ B!TpyLmsO!Wj{Q䛖 =(-wǛ0gMC")ngjVF3˝0ItR&{Sމw.Ɇϓ}o*όTֆ5ùچ]A}6{dy;4SǚYKs>$RLFe;~5y~㭀DƏfϒ;ާ>c#}"H^ -)$wH)P%P5-=0#03x\ <^M+}yp -VΩ9.GX-5"Ql8{!)=y:9RHo9/g$^=)J-Q"JHFQ"׈}Hj2!ޫ3>~x(kOfnxǻ9m[KD5&kːȎt$E䎗jMH"CH=[7)ѓ葑D"4N&)-5+v'_gQ"ɏd=臘*[JhXp_d ^$4?ERGW$ϔ -(hX@ށ'%g~)JĴE6|gS$bx+WI"?]O"+g~,Ok;m;T -ٶWvkwJ F rK%~dVP:0ͽU{ ?|!v>3 zr.9mf _?q,>Pi|LUI Qd -*:`&`؀8  ~AO!OS>QmfRa\n<#7͕ -[cmr{1 wrτ{L#wcS4Z4!FNW! qF+"d_dԅF' l -jQXO@nӯ*D0??V/gykapEwo^ e,C}B=?5@9~'Ri#OctoFדqz22;Rg'9Ù/YLFZ -i7rtLrabgsAuaMa\]H0 PE"k)5y^^nka;S 9> CU;PD2#rKc(^ -+cf/ m}1^,)P%P5-=0#03x\ <^`k-TeQNp4sjN78kaM%yROFc퓬sas%w;Y&"v~;DݗDlH;ajoy=me L|6TB2ܑ)' ?L.|rGM9^j&kP #i?c oc;_ӛHy;$2FFyɻO~%C c)ݐȷ[I"YIId39IqɉgZ;MHMS5 w%{E DnJ.Ifsw^ kZ sA} Ld@i k ki˘|3cvYc{ld~iH$JKHݣgjC"dIBT>yrzCw:DޓC"y 5 uXD^-R˲tvRۙN&;9әu5I{X^Wu80$z)UQ ?TTڃaCq88xkk)z I4پ.b[ޯ #Hgi$r0$i?E!+=>NDD^DJ +2@PP 00  -lh ? `ȧCcz_%]@"-GDȦjHn^( -h ă`6`n4@_R'8W.^"OdID $搱7 ]^1CZOp&޶!xyf)!0DRB"׋HHD.JzQ" 7E";]-+?9bF>Q"G6ݦ !%UL](ʙU' чo ?5@Nf$۟ ӝ[dqOLLugd&,gZf6ʙ3蔔$q/-Ow4NPr5}HRUg{P=нs9A}aC& 3=ϳQHd!2c{ !;j߄Db3=V&?;AODjA"oA"mqʚVEiHyBPEBAA ِ4dXi@0W$ԅl}Zp d@;u!&? @z31Q"f ED%$$⮒Dr]Wu˟0uU* !J%!;6It9C$"bUMy:棂I! L{K˞D}[*xo33sy~H\Dozvz&Hz扽N.6jd|*>T凵K9^HxTj2ęYXk)#gK Gw[jꏐKHۤ'g"ՐHd@"&☶M?ڹ9 ѫʏ!"'@ T@ 4@ t@ L , $+;pp0 D& Cv!@PP 00  -lh ? `߳"Y^yET {fmsH8C#oB!r&KZ۠`9k,<<彵!fRErZ3R~2Q"%Ddi%RƻMĤ*YwYD+Jd^ ߉|dD7%/dC̤!7s>7 (gj&+"b~+$R0nj֡) +"~ZeDo -9XMIKL^1 $fQxR-xi_U@9CE*gsTA`Xt>W2tv M-&AH5?Zƣ(gzϠYe.ckD: Hdvʙ -H{гХ}cdL!݇oe%$܊D2@PP 00  -lh ? `_r;b0\U!uqH3t~7xWZ] -:I='xb<%xx;䋄@M HQ"DD%HHD)WrwtQ"J 8D̉WJ 86Lݻi>Y 3G  ݋hO~D|K(g2XGyelE"pgK (-#l{󌹹u=cEQg.žնnlTn8mǏڮR d@D"J4,!c!ٖi4EXJ)M>m@M`%={<_9zal|/0٪UE Dj~ysxu|T"aXN/tnLUU兂 ߻~>0=s+.c "TbPҒrqޓ|PJ ^L -XTLJbI0ૌ/\(Um9ݯ% -S_z~w-מ0405_Q-~¼|Koc:Va? ⸲9漴1/fd-)8=upMi4}xϴ#coYDW_dUoe6&Rު +:]i6&k|U=p8(F0fl`8!\xAH0D -1P sEf=/*:xV15(\l![kl{_oWKcƵAumV}aXO(UjQCjUTLQfl`;GDZVg3ϮɮL%=}fY}VmM{fVmVy*_[p?6\37[a˼,celJd 2! Le,cCcnY$;+3/7kegS9r>rvvV쬹}mUg<ڬ'rZY-ڬ'V벳Lg|uֲ̬8nUWwnNtO =޵1DpmQ-1 S -u,hF|DDJb),˓EX.<JJT<H&ȡg\8LD:?#̤ -vFM{^ ToZO =簯98o9;_-}e"",W+""Hg/`ulȫHǚB""SHaDDdjDpLPЃ``+pBC  a@bXݰLd*հI5.UM2WȖ_s$Q}:yNP}C1F(:5УHj]+!"B"ҠEd8 24h4NDr˾_"2"ҳzl&-"k277kҞmDd)DdM=xu " nzEHtl!jp-"2(~ֿ*tjzVHP*IƋb0E/ bq1'Z5?e" XrS'4n"g*̽VXnSio<@1K,CD:kIAYͥÈȾg)"27΋p9_Z7,AD|BDZz7F=&  'A> /@? BD! -dҢ -U53 cjJŘyj5lWTg (*}YuoX.&Yܮv%4ɑc58ND6k1iEĤE>NDM3~}DRDD, HcöjVvYU1NEd")"RTYvcEi'pUdyi&;gqegޢ"p_*"/?q+U0^(|qϋE~^JI|JJ1!P-l:{оL|L cT3Bm,X;G-sDǾ]Ĺ~_P0"q]º^tDKoQq*R`dDi~6L4R{FG>9,fp$PЃ``+pBC  a@b@(+;~'1zB̭MmN;8in#}B|;ۈA}Dj%u$J"-bъH+FV~+ƶX;'䊑g{1rbLu{/OU  $/HfƦגd.rhh +nLS2$ױo^]W@Q׋u5_B2BFd$wprޖ\μл;OF3.F+ڙWOpgK (- \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_sales/metadata/snap-1612602357847496502-1-11052cea-ed9d-49d0-8d6b-2ec6dd57e46e.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/catalog_sales/metadata/snap-1612602357847496502-1-11052cea-ed9d-49d0-8d6b-2ec6dd57e46e.avro deleted file mode 100644 index 552b228cc023a268bd7ad05dd8de2e523bc3a670..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4310 zcmbW4&u`;I6vv?lR3x;gWwnA;cVr|*ufq* zl&Xgw;ly^MuG)lcw_2nMRqzLJK;jJ7y&;y<3XtHyKd>|YF*ACl8ZFQ)ulplxCUd{6-aokg88b*WsG<=x42 zYzx^NVsy(9VpkmuEkF|lp7|vLrVZ3>jJ!DAD6k>~Zb28DG@qK+LRyWGS>mRRS=JTu zC%AJP_#V;$c0k)TaeuxAA*|-u7zOV~RS<){7yju1rw*`zKwR1~kZrj_A&VriN8giUO-?lKKqA zTX5Rskbrn`j3u2AciM+438)~InrFk7XE->~kmSaq6|^hZfH>U40E#@FqR?Uo=h>@i zj-S`AcWH3|>8864;$=di9 zcB&SMvWLZ>%d%>rh&)FbaGEp%v`$&3hfN0j_T02uvDfwC3Ry?NWmg% z;MP=fx45pxRh2?%X8ez1B?iG;Ab1PZ9Tz~dZ7S-tGXuclyzpDCOMrv7Mh7dqYrB&Y zxou4%648vA+hrT8_{}CH+CjI1pl0xL4&0ACOTu0ahO_4ANV`I|Zb6K$#o9FmTgdguTpKG)#9}-Z&VVuHhN&SkGB%>A|Kuh+$z)jj z8XbF~9|$%hCx(4Ao727^n))so6Q)b1NtuK~?1+!x0_gb(DN35c=6?<+Ay&D5fFlYF z!7i5I1actaFwb@d44}+10QUq#gdN2ra9EMS;+}JNkONH=tTKXI;M5YMX8VL;$V2)D z29>87G|MpzgRBhUAUJQPkC!pvbD-jBNqY%S;LT%`^KuY2PDRhcIlNu*B)uFa9nyROgRm<1BLgVRchq$tVn`8-~`_7xJDxb;__t0m$@o1*`d>o*QE|E zkEthkmXKzMdvcGfiL0I z#GaW!xd@x#PdJs#uIT$UX+?6OrcFjQoy)<;>M)*!!}z#L+K-W8`937!GQtAj ztEb)0(SzQ-?owy%yt}`&e|CJ>JL)WVe%Ne0{pWG_;PC$P`O4xqy~XpDUfoz z^{dzae(m)5m1^()@kFgRUOheTF7J;&u?=Ir(diwXb&u4;(@phIJ=wgc9z5Rc99akZ zYU5!;J@~%6dGcWC)6UN)ZyoIIef8IG-@f|Cv(NtBIs1R%>%B*R{(kz?>N}sjluGlq diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/customer/metadata/00000-1761ead5-fbfc-4a07-8fba-7e2291ed4e27.metadata.json b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/customer/metadata/00000-1761ead5-fbfc-4a07-8fba-7e2291ed4e27.metadata.json deleted file mode 100644 index d154a7904220..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/customer/metadata/00000-1761ead5-fbfc-4a07-8fba-7e2291ed4e27.metadata.json +++ /dev/null @@ -1,152 +0,0 @@ -{ - "format-version" : 2, - "table-uuid" : "c7d9252e-2989-4041-803c-bf4d0dae273c", - "location" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/customer", - "last-sequence-number" : 1, - "last-updated-ms" : 1678866132348, - "last-column-id" : 18, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "c_customer_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "c_customer_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "c_current_cdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "c_current_hdemo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 5, - "name" : "c_current_addr_sk", - "required" : false, - "type" : "long" - }, { - "id" : 6, - "name" : "c_first_shipto_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 7, - "name" : "c_first_sales_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 8, - "name" : "c_salutation", - "required" : false, - "type" : "string" - }, { - "id" : 9, - "name" : "c_first_name", - "required" : false, - "type" : "string" - }, { - "id" : 10, - "name" : "c_last_name", - "required" : false, - "type" : "string" - }, { - "id" : 11, - "name" : "c_preferred_cust_flag", - "required" : false, - "type" : "string" - }, { - "id" : 12, - "name" : "c_birth_day", - "required" : false, - "type" : "int" - }, { - "id" : 13, - "name" : "c_birth_month", - "required" : false, - "type" : "int" - }, { - "id" : 14, - "name" : "c_birth_year", - "required" : false, - "type" : "int" - }, { - "id" : 15, - "name" : "c_birth_country", - "required" : false, - "type" : "string" - }, { - "id" : 16, - "name" : "c_login", - "required" : false, - "type" : "string" - }, { - "id" : 17, - "name" : "c_email_address", - "required" : false, - "type" : "string" - }, { - "id" : 18, - "name" : "c_last_review_date_sk", - "required" : false, - "type" : "long" - } ] - } ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "write.format.default" : "PARQUET" - }, - "current-snapshot-id" : 497222046221833446, - "refs" : { - "main" : { - "snapshot-id" : 497222046221833446, - "type" : "branch" - } - }, - "snapshots" : [ { - "sequence-number" : 1, - "snapshot-id" : 497222046221833446, - "timestamp-ms" : 1678866132348, - "summary" : { - "operation" : "append", - "trino_query_id" : "20230315_074151_00043_9f4mz", - "added-data-files" : "12", - "added-records" : "12000000", - "added-files-size" : "447132995", - "changed-partition-count" : "1", - "total-records" : "12000000", - "total-files-size" : "447132995", - "total-data-files" : "12", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/customer/metadata/snap-497222046221833446-1-94caaa8a-6674-4c6a-84e2-1025ea03170b.avro", - "schema-id" : 0 - } ], - "statistics" : [ ], - "snapshot-log" : [ { - "timestamp-ms" : 1678866132348, - "snapshot-id" : 497222046221833446 - } ], - "metadata-log" : [ ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/customer/metadata/94caaa8a-6674-4c6a-84e2-1025ea03170b-m0.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/customer/metadata/94caaa8a-6674-4c6a-84e2-1025ea03170b-m0.avro deleted file mode 100644 index 41637b6b381bd5cad7a01ce49c7db1f15a929e17..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10951 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 diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/customer_address/metadata/00000-2cb66f64-0787-4713-a480-2286504d543f.metadata.json b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/customer_address/metadata/00000-2cb66f64-0787-4713-a480-2286504d543f.metadata.json deleted file mode 100644 index d06e39bcb1e6..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/customer_address/metadata/00000-2cb66f64-0787-4713-a480-2286504d543f.metadata.json +++ /dev/null @@ -1,127 +0,0 @@ -{ - "format-version" : 2, - "table-uuid" : "e6f7c7e8-62a6-458c-91b2-5d62340561fa", - "location" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/customer_address", - "last-sequence-number" : 1, - "last-updated-ms" : 1678866149684, - "last-column-id" : 13, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "ca_address_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "ca_address_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "ca_street_number", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "ca_street_name", - "required" : false, - "type" : "string" - }, { - "id" : 5, - "name" : "ca_street_type", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "ca_suite_number", - "required" : false, - "type" : "string" - }, { - "id" : 7, - "name" : "ca_city", - "required" : false, - "type" : "string" - }, { - "id" : 8, - "name" : "ca_county", - "required" : false, - "type" : "string" - }, { - "id" : 9, - "name" : "ca_state", - "required" : false, - "type" : "string" - }, { - "id" : 10, - "name" : "ca_zip", - "required" : false, - "type" : "string" - }, { - "id" : 11, - "name" : "ca_country", - "required" : false, - "type" : "string" - }, { - "id" : 12, - "name" : "ca_gmt_offset", - "required" : false, - "type" : "decimal(5, 2)" - }, { - "id" : 13, - "name" : "ca_location_type", - "required" : false, - "type" : "string" - } ] - } ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "write.format.default" : "PARQUET" - }, - "current-snapshot-id" : 5497986337991527298, - "refs" : { - "main" : { - "snapshot-id" : 5497986337991527298, - "type" : "branch" - } - }, - "snapshots" : [ { - "sequence-number" : 1, - "snapshot-id" : 5497986337991527298, - "timestamp-ms" : 1678866149684, - "summary" : { - "operation" : "append", - "trino_query_id" : "20230315_074215_00045_9f4mz", - "added-data-files" : "3", - "added-records" : "6000000", - "added-files-size" : "71445286", - "changed-partition-count" : "1", - "total-records" : "6000000", - "total-files-size" : "71445286", - "total-data-files" : "3", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/customer_address/metadata/snap-5497986337991527298-1-ac3a7184-53df-490f-a4b9-480b8e43acba.avro", - "schema-id" : 0 - } ], - "statistics" : [ ], - "snapshot-log" : [ { - "timestamp-ms" : 1678866149684, - "snapshot-id" : 5497986337991527298 - } ], - "metadata-log" : [ ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/customer_address/metadata/ac3a7184-53df-490f-a4b9-480b8e43acba-m0.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/customer_address/metadata/ac3a7184-53df-490f-a4b9-480b8e43acba-m0.avro deleted file mode 100644 index c45cd344c8b1328e61fe40dd568240cfd48dc860..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8157 zcmb_gc~BHr8doF{;}MB?m8hN0?ut0XIY16?j1V@02e6BAaki(Y2O4^&hwdH_Ei_6* z0>-GgR8W&>EM;pI+;}HJO>`4=Ydr`as6^rwjk^)Wl6WAs?{&}g>ltRo0jZ++qvyTv z_`Tovz2Em{8nU|g@57Usl$p5JcM&e+TPZvS=LOD23b+cFehH(Ec#K+wn`nwL@_5X= zML7JTQQ;QC4A+x{o-i6Yis$uwHrU2dIX0T3z#0?5@RZ77mtid#c!5gVu64H^*!8>} zgheY1=#&P)ZxkiyEjF`(;(WIe?#>2G%I7E|+*lA{^x48VCkr-OpgNpJq|yXQ3;Djs zALX=_wOKl{Ia;}y7YLyPz-a)Z5PufZR^MaRI8l#;L315QS>r^#46~qTO(vd#g!jZ; zBSq3?f(eOGVcO9$g*8qTWLT0AXx5_l@%YmV1`)X&`=(T6Ya`_H<0z8lU?JGyF%uTr zMDc>2vgfHNMEeqmy^RN3>=jH~U^e19sd{16Io4VTE1$^A~4fAk|jbXqRu|(5V zxREjuHb%f>;CH1ZjnWdRbIL+0_}WoxI)yb8%}{#&g-wPlOd1J+&_la2G9E$Zv<|?e z4(FgIYY_nK@_t-G*2t2e?@g0_@|w+K8{X)%w{k`suWi|O_ZrWaCr+M+k)gA@1QAcYkOMx+bIgXPe?W4ZFslw=@T z#%8wY;c{fvQMNL&1jiAu6M{>`40tSmcWo)z8cn1=SEGq?WaC8oYzo@&&`Q8g1)8}8 zV?)*DfKiczCaM4)C&OmYB*COe0>eyL@dDUoL>4C!R*1EfAx0Z9fdzjMArTx{KeVVT zJU1nElxs}V0e1~l(Euu~x-(EkyM~2u6+5j{k!sa10F}sR6<`v{*o;6XVX$#ygvE#@ z5lO(S7wnWl!d-gtP`eO7tp)<U1;1$%{0r)(Y!n@4ea1Hc@N z6^@4U0zFMT4)QFr6VPeLc`j5pomm^%DeT02F)?4SAZa5M(b%lkt_2BpGhJu8nO4~- zT#C^RNV?9oGcjLZKvJ?DbP>Io5(uag+A#^2f|Ku?=@yd9w~E3H?86-UFayIHyq=3r z*^PKBka70O!FTpLZb%>q%|KV$Ri6%BhRM*ZKp24hlBN=9+jH~QDW#1=TLNX@frunU z>m1NH6wq{sU?o{I5Lq-2?ql(O#}eLpPHajV^DGC@;*5aH?&b~{gnU5BU@Z`tU@h?B zHT`gjV@Pkn8z4v=t4J0MZT$dp3>Zs;B`F)0OL069+|K9Nb0Hb)W&yc~BO|*x4;!wf3KY59#KOaUdc6 zid?{20|QrwMk|y9deIZ@q9Fh|@GS>m$U_cVP*`1)gZG(7PD=68=x)5}U}joSwVpBq z1@@wuwHrp{G<4{d*Uj9~!7OxuNFK7#funozES@_h$a!he6S~Vl(7~Lv;3eHdPFjGS zBPRoGA;jlm@k1DL^4!W%>UR`ADti~Hjx76BGFs0iqf?Yj=fhVjYdL%!x`Y6BN)hcL zd|VDdYB65bG;)LP9#`S)!H?V>8JdS9xwAk8*M|KAXp{D*6vql=(CI|oC~FiR;-HI& zM#kJ9qTA>I5m9u``Wo4D3eLOAmg5XZqQq5DBg0OC+V2qQtek~4kjmgMSk?lf684{$ z-p{VO0-%`dHg(nfvct*MSHCH`lQwYf$E7dt=y!7+o7rFtK31kW5gU*Y@cO?taRG+{ zM>d^!mN53vJJhn`v`@A$heA%={PN+wwuftno&EFfpq%9iwY7JvXHEUbl|5%SQ%4$% zDMORDufqe#hSF$badmvvj~i`ICTtye@kwm|qMv#+o_gKfA zz~b!3YQNdv7WH2?sp(=y%dh!wR|J@M$KoO9X9xX0GOzNlp?&LebGx+|=NOl% z%Z6KfS2UM)yLmKa$ffRYudoDY&*V&9y*to4t#SI)QzstJy|re&eq`3Bv@2OWlT$uv za$V|>(&*@?-B0dY68!VvQ$ps|vllN6&G|Vk^4U z$Xi=x+7|WrtbgTvn!D8OU%BMR8!L0mbhkEa+*^Mwq==dG;I~;D3KJJB8FujCr^j^r zuuJL@Gm2~S0uq|9#A)Jsg=`PB}X?tgXup;98qB(UH*6}Qqa41Dd`xvaG2F;~xj z9=hw<(aX2$$M)`7a;{`y(^CAE*4Cf&qi#3uEM1Uy_tb!fwW=|pf9iMb`k+?si|`ri zuV+6x{r2r`Jt{|SIJtC3Ay(dh(3I7&{jACPA5T9XmlRr)eq{MC7wTI+cxjmacI56+ zX}LG*^g|BSeR}Kty8o8eEFjtz#eMatcIMYN?(S`z`>?F3n!mLCx5oOdJ4fiRe#j@* z)wDhyzvtZgA(z(`r;zRdtsiv%+UXMOmhHtmt4IIPnmB#L!Ro))jVZ}pkXU`f(U=@ zbl-%@eNIH)xpQuPZq>HWs)uGr>>eVFLyYrZYm+MF>Ne)tNN0Wvn3!3z|2`#;fTwJN@ z1|pBEIx;j@D$O5jTTzF&MplG@TgPiIj_ zbWJMdWVt!q9@{|Hq8Qz%20>VW{J!Gk;wnh;QaDizm)b+=~YSVQ6)3s=yt;2Oli9y(Cu zu{H`Vwtt@8ifVgl?RtzB2as+$%OF}N6iOZ!;-VLI7n9>&=`QsCaCwL5VjF@(p>-L2 zpMQipW!_$Q$8NOMB3AaG7<5@uEfnz%uv%;yl$9YhuGqV%3Y&(F3XmFA>>LvUsusbe zotG@nFeIWv;U$KS1DDC)D%nu#klN2$JYzBo^0#id(=owfl z(VXJCQ4mrvh#EMR1#-8zu0~ar0%>Oa!;umL|1IFZ1**0KAlWt*b=sK@U~yh}_4*dT z{#(6+mFAJ=utaW~Nkk%?F@+}ESlKg8NVJ1)1wpmO%h_>1a-M`8^oO%*Ye*d+TQ?v^ zJ0fi)q&>OuV!l|I7#qtMi%Kq=&lU?qgkwTHvV|18`5!Mc7_E0*B=oEb2LT2Px24!OA_j1x}q{ z)MTH~4Y^C-z@YLNgC;qKZjj|J90cc$x9}1Md%nW znC#H$#_LjrhRf8G$+JP+C0W)oYS;t8k}x3`zS9CxEwY2m4MN~XI6WrakyF^rC?vTP zUeQ7V*7qfx8rw57D4Ssu{0XO$*%f}j#;r&yRJX{ergPca2Ov~ar@CTEZv91 zoCc3e`$a5BkaP#XL0*5595`xI1Q(H^CT`pI?We8}g1JMNuB`7GJ@Dg4{onN8zcw>) z{?Wm08<$V~@bK8ohnKIu`Ro&Stp9oa^-nsf|J##d3=+;j*GIQTPFlC%uZM?qs zSLeyTFV9Tge0XW)t=%{7e);_IFD}hJckcM?ZGUgPdUkha-ppKBoyy$v*Pd%vp1AsTmHHI^j*Jw{q~7*Wk4GpdT+LPVfysi#hcDf?cIN0rYiq&< diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/customer_demographics/metadata/00000-b75465de-0ce6-4a43-bf66-d25bf0af2dfa.metadata.json b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/customer_demographics/metadata/00000-b75465de-0ce6-4a43-bf66-d25bf0af2dfa.metadata.json deleted file mode 100644 index 6db8c104b14a..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/customer_demographics/metadata/00000-b75465de-0ce6-4a43-bf66-d25bf0af2dfa.metadata.json +++ /dev/null @@ -1,107 +0,0 @@ -{ - "format-version" : 2, - "table-uuid" : "2d8e5648-15af-48e1-892b-f33846a73e93", - "location" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/customer_demographics", - "last-sequence-number" : 1, - "last-updated-ms" : 1678866154942, - "last-column-id" : 9, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "cd_demo_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "cd_gender", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "cd_marital_status", - "required" : false, - "type" : "string" - }, { - "id" : 4, - "name" : "cd_education_status", - "required" : false, - "type" : "string" - }, { - "id" : 5, - "name" : "cd_purchase_estimate", - "required" : false, - "type" : "int" - }, { - "id" : 6, - "name" : "cd_credit_rating", - "required" : false, - "type" : "string" - }, { - "id" : 7, - "name" : "cd_dep_count", - "required" : false, - "type" : "int" - }, { - "id" : 8, - "name" : "cd_dep_employed_count", - "required" : false, - "type" : "int" - }, { - "id" : 9, - "name" : "cd_dep_college_count", - "required" : false, - "type" : "int" - } ] - } ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "write.format.default" : "PARQUET" - }, - "current-snapshot-id" : 3805658040916858242, - "refs" : { - "main" : { - "snapshot-id" : 3805658040916858242, - "type" : "branch" - } - }, - "snapshots" : [ { - "sequence-number" : 1, - "snapshot-id" : 3805658040916858242, - "timestamp-ms" : 1678866154942, - "summary" : { - "operation" : "append", - "trino_query_id" : "20230315_074232_00047_9f4mz", - "added-data-files" : "1", - "added-records" : "1920800", - "added-files-size" : "2108520", - "changed-partition-count" : "1", - "total-records" : "1920800", - "total-files-size" : "2108520", - "total-data-files" : "1", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/customer_demographics/metadata/snap-3805658040916858242-1-8347d08d-e19d-4f6b-8cd3-e07f95b6e1f5.avro", - "schema-id" : 0 - } ], - "statistics" : [ ], - "snapshot-log" : [ { - "timestamp-ms" : 1678866154942, - "snapshot-id" : 3805658040916858242 - } ], - "metadata-log" : [ ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/customer_demographics/metadata/8347d08d-e19d-4f6b-8cd3-e07f95b6e1f5-m0.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/customer_demographics/metadata/8347d08d-e19d-4f6b-8cd3-e07f95b6e1f5-m0.avro deleted file mode 100644 index 3c55523c4a0e579496a999786df3e778a59a043b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7354 zcmb_gd5qLl7-v_}RTM>(c%b{NiGetaGwq&ZFdG#RMI?fW3Ty4OZ+0r3wzQok<7Nqh z2eP0jq9iEZ67NK#qK0_z2Sj5+ypT;46vf0yyy9()-+P_*z3EPe+1X7tBy`^Uj^F!z z=hc~;8%{pKRdYD6tUSJ7@QOAT(t_(b1=SN0g8xg4t_kU6Ldfda&|D$CxL+V&QVGFS z^5nd#$r{dEvbz-SICxn>cQ72tDu#;_fl$LTdxe37e^=!1_F_}RPL-=*kIr3|_{(|4 z(LKeGT~F}}ZtX%^c_COUsEVgs=7`0#@nYhwyyD?1=IEvuqS4L^Q2{&M zlO3=Je2&L60!jxjL&LVLS_M!Mf1fv?GkO{4ZNn;JZA4PKqEb}LFmNxfO-Rqc6s6Cx zX84@DuPtPwgH_AXz>^Z&@`|Zv0bv=No>PSH94|{pLNb&HO?)k#^T|wj7@Rc~+s#>? z44e+gx>zs^h5`Sjr0q!v8qO*O!xPfvJ1;54O9JL&Q^m4b$Y&^_ef70VlQ?Nv-N3Rt zYLTs+NKNq+naB+tjRn~e1Hk-%4>}M)LXeAy9?oF>7A)(goGHSKhrauP6)0F_K?bLE zK`tC8;)CP-qw!>b)KbVpx`yLSY7l54vWnvKq`?X;{6wsu+v>dYe~lVSr>A%HpdOn_%sGh;$8k z7W@%}C^#g3bXsZSlTxxXYLoxKV*^zeKqV&YfvPKN7Q?l~I1VZ?ISQy~^-BPgRHL9l zAw?u{BgNFvd}Tgpt%4nIB)KKiM;$`|#S{UsEX2cVRGn)gpHYs;HBqb!K#CL<4jlA* zhvPpdMFs$X+ zku!ifblE||8AVPL+X>G)>VQscj}O!enOW?p3p?sBqW)F|No?b+DcJVdf<&^JR8KY& z`9u*3#tI-w^`$fFZ!IA4Y=UL_|_)qEupg?fkl2t zTW(N0GiX-Ou&P!bnk?OgdQ6KJntK`1QpGl&#;)Z6S`N&;73U6OMEF1p;Vd+na29k_ zRnh8mj1^H*0Zyf3m1=2Noe0($FqcmCTiKRA?6}a7H;O{oFSd|=5#FIOIaOCNgXxHXH!j8en#tFo}MAIHLHNwVjp(3A$T7&}HVm@y4(p+q%=BUx=a zX4g2PF@5ZrLiR&+Rn}JJnZ{r|V>U4cM+URXW(E1vpB$y7*zrwtjQK)nfS+QjcGFA^t3A=i zIYv*I9Sz+jd&cqx70O2G1Ek5)DkV6<1eU#Mh0dCp$2b^iqM5MDk66(g;4w)JmcxwHG!T^G)2n$vWyW>0>z>)|*4&3(Lm`nB60Z<(@d zL-XF|PuhQ*G0U60;N%~4wpyw<$$^TE6Bd2ZA8Rfo|}A78QW#i3_*T)g)4pEn=+`dvKd-e9e_J4o*bnU4Po4%dXw4xyj F`~MDnw<-Vt diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/customer_demographics/metadata/snap-3805658040916858242-1-8347d08d-e19d-4f6b-8cd3-e07f95b6e1f5.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/customer_demographics/metadata/snap-3805658040916858242-1-8347d08d-e19d-4f6b-8cd3-e07f95b6e1f5.avro deleted file mode 100644 index 07e26b0e0ac5d21ce6ca6c2c4469f11f57e1dc5e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4310 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 diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/household_demographics/metadata/snap-4875603543705301354-1-3f673543-f484-41ca-b5ea-77d5dc1a95cf.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/household_demographics/metadata/snap-4875603543705301354-1-3f673543-f484-41ca-b5ea-77d5dc1a95cf.avro deleted file mode 100644 index f9dbfabbe07715e1e009cb6be9c6259587bad2e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4309 zcmbW4U1%It6vtywRFt3?f|R7a48@lwI+M*N>1su5YOOSWWUC;tZ12w8%}$w}+0NX_ zn$&;~tyHmEe6Yo+MZp$n@tdN8P@$+uAJo!fOQnhreeShgq7G`fCT-4lR0^v*HYLIPQ5EE14~0K^$f+P&B`7X!0jZk| zOz|+%HW|bsEtzW*62c>Ud}8aQWc#%;aYf#Vk`$v=C|rdaRbnTgsDV9%4TG?q2TcvD zq~#@6&4l`N#M|yP;Et2*q9MtRMJrfWXcg0N4{a!F zu7$#i9h_&cq&t3AyB=i4LC{Ti0maLtLaAd?T=JahQ8@0U&O&dAmbZy6w;?ybPIf<=(|rSTSr|fXt|J=eQ6swFocm ztYZ6?rGN@Wml(S)UVxwB?$<#2`15A1bo1(De8dwH$z%bWwg-2X1&-sE9mgWrh6kZW z?r?n&uL{EULa1g{c1PhZSr+1XqIqoj%^I{(I*S4pgih{BiJ3cBf?_*`X^;x`Evgh) zPD$OElv1!j4ct;0?v~KixT;Dh&7A*4ti&jI3k7eXisK>-+h(H9I@1O$&kMg^Uj;aL zt9!7vv~y`#f!kICk%(r@tenN`YO5yWAgOd=e zOg|tHqa496mgEF7Ao4IzcLp4wO>ltl1RaF!#v=$=p2Omv3wMwKO%$xyg))zGT<|y@@Z-H5`rL_$0z6NAbgz4o<(p(yW&Z@ zI!ruXQz@FbSYdw>(>kM4iL=<^H24lQ(R*I0uEX;pN$x-pM7QG_bq$EilNMhUs=#H3 zO*c`O3bs71o?MGb-RG3}S|JS4y$%L9VjA}NQgFmaCcoG2<<0@%Cx`t)@kc86^ zaaq5>f`m!8@f+~^dN^>*rbsU0CT8M#z8L&0zw6-i2iDV%ohs%|pYHvkcj)YN-!~8R z_x$$LzxU;)?|pgbrT4GLBOhHmH(t1OcKNFp4lF-%dGq=U;~(z&ec;UBv(Fzs@Va)j z|HrFuAMg3{;)$Q%y>ff?*6%kE_m5i+mG8e{_WX+yzh|!ZzoTdRKAk#t^Dp$s<~1AkJ-cS#@VS{2cW#||rvBb*H(t3s cq(AxB;#;pC{pZ@^pIct}_0qae?rF~Ze+BfzH~;_u diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/income_band/metadata/00000-3a51efde-0157-4d0e-a965-e7dd6068ec4e.metadata.json b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/income_band/metadata/00000-3a51efde-0157-4d0e-a965-e7dd6068ec4e.metadata.json deleted file mode 100644 index 82b6924dcf15..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/income_band/metadata/00000-3a51efde-0157-4d0e-a965-e7dd6068ec4e.metadata.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "format-version" : 2, - "table-uuid" : "4742c8be-656f-409e-9a7c-9b519f9f9d29", - "location" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/income_band", - "last-sequence-number" : 1, - "last-updated-ms" : 1678866507575, - "last-column-id" : 3, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "ib_income_band_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "ib_lower_bound", - "required" : false, - "type" : "int" - }, { - "id" : 3, - "name" : "ib_upper_bound", - "required" : false, - "type" : "int" - } ] - } ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "write.format.default" : "PARQUET" - }, - "current-snapshot-id" : 6121948948604053537, - "refs" : { - "main" : { - "snapshot-id" : 6121948948604053537, - "type" : "branch" - } - }, - "snapshots" : [ { - "sequence-number" : 1, - "snapshot-id" : 6121948948604053537, - "timestamp-ms" : 1678866507575, - "summary" : { - "operation" : "append", - "trino_query_id" : "20230315_074826_00075_9f4mz", - "added-data-files" : "1", - "added-records" : "20", - "added-files-size" : "743", - "changed-partition-count" : "1", - "total-records" : "20", - "total-files-size" : "743", - "total-data-files" : "1", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/income_band/metadata/snap-6121948948604053537-1-777df258-effc-4253-a398-78634bfecfff.avro", - "schema-id" : 0 - } ], - "statistics" : [ ], - "snapshot-log" : [ { - "timestamp-ms" : 1678866507575, - "snapshot-id" : 6121948948604053537 - } ], - "metadata-log" : [ ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/income_band/metadata/777df258-effc-4253-a398-78634bfecfff-m0.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/income_band/metadata/777df258-effc-4253-a398-78634bfecfff-m0.avro deleted file mode 100644 index 72f1d83214f4f9e7bd28c99f5f1dfc1bb9f6230b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6833 zcmb_gZHN_B7|xGCLAEVSGZc=4lGWY3cV^wMSTxE7EkE!xT6TwX=br11&YhW^nY*%X zOQu9t=7I#~kF)@q zo=X&zmiPev!}?LH{5ZdvNyUNtQ{=$pI?@hMq^C%zY+Z#(vLm1aM`^XHP zQ08K$$7is)hd?9Jvx9Kr9D7(;k%KKxfGw6T$cN?Be6(CXG&vaz$0`atQZ6vf3*eS6&fUTmat_YdZ=oThb4F|Wt>Ennw|tzET2=5NrqL-L61PRaU-}0(R#cdyjI~(4wCND*`ZD$ zfog^lSXScvHmaU$s@zfTn`^3C7lDi^W*l-b>m-X_^|W3guMlI6x5u!8t0tQ!HxFsnnvz0QTWFrO)6E~eKDr&1 zFgSkSthNnr&}DNI>_gE$l(n2}_2iYik?+D7r#tz)PlDPHCb}VEAS}RKS(^GZaT#u= zW&@ms@k`iFP8CfaZKBx{Ci@9Q6i&3|0kw#LR!0LXNGlk!OdsxJc4?tCFHkR4Vw2O@ zcRXOr%Yizoq&sL3{D3yYS{O26E%>PFqB+blaf#9!U@ntYhLa;k14L)xfGiA0*`@*F z`7n@AR~0@lHqm(z&S83bp=l84Z97Ve?BdgWe5y@~f;f#NRwT!S{sb{AFZVolgDD`ieGl|v~7?xOHI$h5M5=qD{E zQQ1{ndIdhho3|rkB77@c&{_k*H6dvwFtl?F%&rgt!a;2qzz`knD_~Heu%-q>bG@06dEK_0tyX|Zt^JZs}eXabp^*2 z69f~)q_!ktQh~b&Q`WRG`;5vSGzlm7tsHrN7vYPtkB#cWa!n%Ra6NZmPk(R>*4?pAY}HSlUj4fAlgHN-5t8{t)hC5 uJdv8cDtms`N7GJS|G4q)l3dGCdt~W>Gq3%y@}Fh@wO-lQx%9g@1^)x+r1$Lr diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/income_band/metadata/snap-6121948948604053537-1-777df258-effc-4253-a398-78634bfecfff.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/income_band/metadata/snap-6121948948604053537-1-777df258-effc-4253-a398-78634bfecfff.avro deleted file mode 100644 index 35c8a1576450d0c8bccf0e87f2c40914cb56c088..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4298 zcmbW4-)kII6vvZ@;)}8Vs-pdI9sGfi)R`okO%~cxZ6hHSLpS{aH7<8&?q)`2XV#fJ z*>38J4}yXSiXv81eUU;7KExk=DLw>69}3b|6dyzcLBv0xK8c)5es#HW>- zWfOQ@vxsf@O6m03t`!YTO;`~J@~LMzEAZWLb%=A+)iJeP2OU2)kD3-W(OemYbRKow zGL%w5Roi3Tv2Eh4$kAT7{Y>aZ*s!#6H4?LD$JIwi#=XDpw>aj*0j`bfOIM|8Oj(V;N<41PE~ zLcKEYuDj^`aA#TIByQDpZGs(m5NYI+ z7l3%x5O!LTnwjj5!d&Urdx3g>RwoEmU(ogkjrE)LCbGfaQ4+G#c9ghi?rI z*4oSMF$Hd09Yi9YF*9wxv2swaW6%z^6@qnBl(Xl4)FOnvHyqBoYY=@Dwr*oeb|%^g zq{pVU32k<220qj3lv8k za-Y6|L)A$R&2kL=Aj^F?2*I1`;AIT>9H@L+y1j%Ti01LhxjhIUr?O`e9MP_1l5P)^ zjMq$xCN5UgpVYL@sZ{DLb~z2c15NavS8Cw!yhxHe5CqZfq(%b+lJaE5SA{BY*@i>8X_+1 z7g&%e=^lOqUVj7*9J48si@2ScxSFZ}c4AMN_$Qy(6D IxYC*I{|t1w1ONa4 diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/inventory/metadata/00000-aca46963-a5fe-43ea-9ea5-e15bec1e97f5.metadata.json b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/inventory/metadata/00000-aca46963-a5fe-43ea-9ea5-e15bec1e97f5.metadata.json deleted file mode 100644 index b00cdf2ea12c..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/inventory/metadata/00000-aca46963-a5fe-43ea-9ea5-e15bec1e97f5.metadata.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "format-version" : 2, - "table-uuid" : "40fcc865-d81e-4de1-a711-b0279018331c", - "location" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/inventory", - "last-sequence-number" : 1, - "last-updated-ms" : 1678866564415, - "last-column-id" : 4, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "inv_date_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "inv_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 3, - "name" : "inv_warehouse_sk", - "required" : false, - "type" : "long" - }, { - "id" : 4, - "name" : "inv_quantity_on_hand", - "required" : false, - "type" : "int" - } ] - } ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "write.format.default" : "PARQUET" - }, - "current-snapshot-id" : 2468336565001458778, - "refs" : { - "main" : { - "snapshot-id" : 2468336565001458778, - "type" : "branch" - } - }, - "snapshots" : [ { - "sequence-number" : 1, - "snapshot-id" : 2468336565001458778, - "timestamp-ms" : 1678866564415, - "summary" : { - "operation" : "append", - "trino_query_id" : "20230315_074830_00077_9f4mz", - "added-data-files" : "46", - "added-records" : "783000000", - "added-files-size" : "1866673522", - "changed-partition-count" : "1", - "total-records" : "783000000", - "total-files-size" : "1866673522", - "total-data-files" : "46", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/inventory/metadata/snap-2468336565001458778-1-b09da5f8-9688-41de-97b7-d7558d5793e3.avro", - "schema-id" : 0 - } ], - "statistics" : [ ], - "snapshot-log" : [ { - "timestamp-ms" : 1678866564415, - "snapshot-id" : 2468336565001458778 - } ], - "metadata-log" : [ ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/inventory/metadata/b09da5f8-9688-41de-97b7-d7558d5793e3-m0.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/inventory/metadata/b09da5f8-9688-41de-97b7-d7558d5793e3-m0.avro deleted file mode 100644 index cb7fdfc0496ba12505c4fc9106067eb324b71885..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10046 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 diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/item/metadata/00000-509022c2-7442-45fd-bfb1-038936df7f5a.metadata.json b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/item/metadata/00000-509022c2-7442-45fd-bfb1-038936df7f5a.metadata.json deleted file mode 100644 index b5413225d5cf..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/item/metadata/00000-509022c2-7442-45fd-bfb1-038936df7f5a.metadata.json +++ /dev/null @@ -1,172 +0,0 @@ -{ - "format-version" : 2, - "table-uuid" : "32f5919b-bee9-439f-b597-a6c98eb55c4c", - "location" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/item", - "last-sequence-number" : 1, - "last-updated-ms" : 1678866169022, - "last-column-id" : 22, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "i_item_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "i_item_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "i_rec_start_date", - "required" : false, - "type" : "date" - }, { - "id" : 4, - "name" : "i_rec_end_date", - "required" : false, - "type" : "date" - }, { - "id" : 5, - "name" : "i_item_desc", - "required" : false, - "type" : "string" - }, { - "id" : 6, - "name" : "i_current_price", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 7, - "name" : "i_wholesale_cost", - "required" : false, - "type" : "decimal(7, 2)" - }, { - "id" : 8, - "name" : "i_brand_id", - "required" : false, - "type" : "int" - }, { - "id" : 9, - "name" : "i_brand", - "required" : false, - "type" : "string" - }, { - "id" : 10, - "name" : "i_class_id", - "required" : false, - "type" : "int" - }, { - "id" : 11, - "name" : "i_class", - "required" : false, - "type" : "string" - }, { - "id" : 12, - "name" : "i_category_id", - "required" : false, - "type" : "int" - }, { - "id" : 13, - "name" : "i_category", - "required" : false, - "type" : "string" - }, { - "id" : 14, - "name" : "i_manufact_id", - "required" : false, - "type" : "int" - }, { - "id" : 15, - "name" : "i_manufact", - "required" : false, - "type" : "string" - }, { - "id" : 16, - "name" : "i_size", - "required" : false, - "type" : "string" - }, { - "id" : 17, - "name" : "i_formulation", - "required" : false, - "type" : "string" - }, { - "id" : 18, - "name" : "i_color", - "required" : false, - "type" : "string" - }, { - "id" : 19, - "name" : "i_units", - "required" : false, - "type" : "string" - }, { - "id" : 20, - "name" : "i_container", - "required" : false, - "type" : "string" - }, { - "id" : 21, - "name" : "i_manager_id", - "required" : false, - "type" : "int" - }, { - "id" : 22, - "name" : "i_product_name", - "required" : false, - "type" : "string" - } ] - } ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "write.format.default" : "PARQUET" - }, - "current-snapshot-id" : 4551617743668210082, - "refs" : { - "main" : { - "snapshot-id" : 4551617743668210082, - "type" : "branch" - } - }, - "snapshots" : [ { - "sequence-number" : 1, - "snapshot-id" : 4551617743668210082, - "timestamp-ms" : 1678866169022, - "summary" : { - "operation" : "append", - "trino_query_id" : "20230315_074245_00053_9f4mz", - "added-data-files" : "1", - "added-records" : "300000", - "added-files-size" : "17538306", - "changed-partition-count" : "1", - "total-records" : "300000", - "total-files-size" : "17538306", - "total-data-files" : "1", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/item/metadata/snap-4551617743668210082-1-7e50169e-0264-44be-8503-dfa9dbed5fd4.avro", - "schema-id" : 0 - } ], - "statistics" : [ ], - "snapshot-log" : [ { - "timestamp-ms" : 1678866169022, - "snapshot-id" : 4551617743668210082 - } ], - "metadata-log" : [ ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/item/metadata/7e50169e-0264-44be-8503-dfa9dbed5fd4-m0.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/item/metadata/7e50169e-0264-44be-8503-dfa9dbed5fd4-m0.avro deleted file mode 100644 index 262be39a457d1a3ad8729b2864a926ec157319a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8452 zcmb_hdvFx>9S@Ibu~I?{ra%z3S3;#+;@vJG$w`z1l1hL)2naObvA4HL7WQ^8yL(_v z0~Hm@vyxB?0(Ht`C{so)VB49B3Oa@I7?@I|0+wnkFx2uGKn0=g_xIb|{rzrpcie@{ zWcXw6_xnCR-_Q5;qbe}BTi@Qg&?tuZH?mq>Msrwn`CPi8MFhj;cA3BANuF6h4H`OMb}^LwrCBr@>Nmikc+38E5A>wZ3UAA24Ybs>pzhXbRj>q11+ zM8)ujHAzTnzbHr{UVfy&&9EbE91AE(3mR2f)OlI-3#yKSmnag!xS|RvQ2~tyM3Tlt zQViRQBIng4${kR6fmSw&1ew?M)YW=v0^+-A=%pDb0CgHvtvLM7SsXoQ7I{=5t+NUr)*IC*FU<>e zE+rz%a0OTM*=|=*tmh-L;qsyHv?MPr30i`v2%=w!gaR1!bfs~Ww0a3joPOUTM^Pf7 zIq-fY*G48G$Qo+_W;VhoSOPE#V7KqPOk{(q0PS&{`J>LL^^DOdGDwq;@f0JqZpK?2 zR&@zw5fc<;(GYbg@x|aDFH1(V_07_X!{p^?>oFi<-e@G+quhb`U;`$pP@|B7X;UUJ z3nZhFDX(QPpOD9xCZ=4Ehe8F*MM=(6emNwUgDZ@AhCxvH7{paKqiO>BhY9l<#6Ff} zUJp$UIALJ#ZR-KfBd(u-X#BAWRt}5|%}`=^@VqcwG!K;61|LR>$aJ5%p}^ zdzSLv%r5Aq5jw1x_m8iU2Cs(;29WoMsVRql}}VVm27FW~>ws9SjAQ{`ZqE3Q^T0P2F;1z>Wv&*BI!AZ%+(Zxen z7r?L;0L)?gvDWL7 zY=j-4S~v?sCY%KwNmYz8IYyc&>Hz)mS|zCP9?}P_gH2W(Y>u+I^F>XEdAU35vggHI zG%rFNx_bu0OAXYvhUi9iXrvuP7>-kTHzS*#GgV5c-7x&x}Y`<9Y>g`wv()5Xi z_wNKgv4EZBQ$SL9{EUh}uS0zxI(FF|+#r;G}O?&z91Wb&IF18DG%?k`k4{gOEBy|#_D57k_H^(W_d4ByqdVcUgs!}4Cs+ugJ3$ZJ<_8bki``Qt@Z zd7o|1%2IE7e&PS@dhSO91Jf^jIksVW=I`G5Y8;on_v2&d?yR|$zsR`Uv}otuw_k0! zyK?oT@6-+ZwqDiJ{_kVsc1*lxTo4|8;`+yZ-~IWup+k7@48Ikw;5UQm(Ccl zVor^FNz;tM6X#5DzqrjiM6c+Pd9~8}uS0h;-W<=hue_3T^uL>XWE)rW9==_=wBX{w zX};U-t0!}3zd3y2stHHfKUW!-3N~FnekXHK-^z#PXJ1^hbfmn@bv|e4=iSD(P5(1O$QHaPYz8~rUC@hqeCr>XPNM!F_xVHq diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/item/metadata/snap-4551617743668210082-1-7e50169e-0264-44be-8503-dfa9dbed5fd4.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/item/metadata/snap-4551617743668210082-1-7e50169e-0264-44be-8503-dfa9dbed5fd4.avro deleted file mode 100644 index 0c183be6ed95048664b6d5e58675d9dd5b33ec98..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4297 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 z z8}_{X)6Y&MT7IQpOdNhy-?+GSVCBfMS5EAG|IcUFwya#U;l=L^zWm)M*GGPM=1On( z`FD4R_H-}$%LjkDe{fdSI?dK<0m66|9#`;kr$6WweJtV`|zR0 z{_2b8emU*buQw^5ZhW9?i*-HnrR}GtFZ(&Zj;cFWuGoC|bK&Eq7e2h+^4Yg%z0-VV O@g^ZuHFSS&%>NHVi_-f5 diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/promotion/metadata/snap-5366699751584273815-1-1634c69a-e8ab-4a4b-866e-344ca294d9f0.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/promotion/metadata/snap-5366699751584273815-1-1634c69a-e8ab-4a4b-866e-344ca294d9f0.avro deleted file mode 100644 index 52923a86a829a3f9660faedaa8585cc3fd29aad2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4300 zcmbW4U1%It6vx|u5L+9+VnK}YGLdQ_k(nf$-7K`&Bu0ZREpB|UB`kMm?q)`2XSOqU zvc?)L_~MiJAkq|#AJnJdi%_eGB7#s+s)7a)`cgkAD(IUZAbRe6+9 zcwDuJZTL#@)R~qQbxciI5eM?AXF1F8+i-P=bJW!_wOj{1{@4O)Sky%GB^1(m)G^CY ziaAwX8*Pnk6K7eDZo5+KMVDe5u>_HCJx!41pvo#Gev)n+Sd#+Ru}3YIPuZ^%y$ocY zxaClubqW4N3v(#&iGiq#8lFX)^BtGM>aIgc_56yMZNMQx$>IbHosf6hgqo62VJcEq9vwbIL}h2)5*B$CMjwj3YsEDM~(t+=jDumcYwja>8s z5U(o2&T6D)CcC3>muwsHJkbiagJunymd>Jpg`v}XQWECUc2MlJVH%{01Dh&EmQz|c z3Q`I-sDW3mz}*tMnp9PZq?z+iBub3Jw@~;Ns=6M+ux%#ltTP?J^1KM@^&No2w|WO_ zYfEdR3f#6Dh(tVN3Tu30rJz>BpdD;01nZ_KXUF}hc?dfg4rkpph&}*Yw=pIA6Kw?2 zf!xH@)YSBJKBwghljHe`f|eTsP7Tws72^3Y*ZRhX6EPl(WfaN$W>A7$R8PHV0Na74S3NxCyk zGG5zKG;y(_{-maLMx|0`vBhcd9cZHWyiz@f=S7m-fgp%(CpGFBkd&ugd{w9dmmN0U zL|v-b_PKg;dA7k_!m_4O!yibNgbTU!ofe7eG(X6~AVh9N&{NW#IEC$uLOXXNDq3iR z4Sk8AruNJY%68Z`{zOn=cE#VXX)BTmwHz4LY%Yf%tKE1K0TbgYZ9jU3W&4nn(-3i4 zzrcb-Nq6uY@cO-Q;FwL3Ttq)Jal795cDC~B<`3(y>{*z6PQ5hS_g>%KpKAl3put@y ze!Okt+TM$goXj5^I`+-=-G^V=nm@Sj_08drzt+!PaW-b|JAUoL>9x;3`0B+M?%aFp zhsD2cIkG=l?r@dC^=SId{_?{eyeP5AM4#V-EfN=H*8RUcUK{>sSAt>woL&umAoweCf-J P58wFypU=*H)ST}Bitf4Y diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/reason/metadata/00000-d391c19b-6305-4aaf-95b4-ca05171fc277.metadata.json b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/reason/metadata/00000-d391c19b-6305-4aaf-95b4-ca05171fc277.metadata.json deleted file mode 100644 index c470a44a1098..000000000000 --- a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/reason/metadata/00000-d391c19b-6305-4aaf-95b4-ca05171fc277.metadata.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "format-version" : 2, - "table-uuid" : "3619eaf8-7a83-4e06-bfb8-3134dedda283", - "location" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/reason", - "last-sequence-number" : 1, - "last-updated-ms" : 1678866176782, - "last-column-id" : 3, - "current-schema-id" : 0, - "schemas" : [ { - "type" : "struct", - "schema-id" : 0, - "fields" : [ { - "id" : 1, - "name" : "r_reason_sk", - "required" : false, - "type" : "long" - }, { - "id" : 2, - "name" : "r_reason_id", - "required" : false, - "type" : "string" - }, { - "id" : 3, - "name" : "r_reason_desc", - "required" : false, - "type" : "string" - } ] - } ], - "default-spec-id" : 0, - "partition-specs" : [ { - "spec-id" : 0, - "fields" : [ ] - } ], - "last-partition-id" : 999, - "default-sort-order-id" : 0, - "sort-orders" : [ { - "order-id" : 0, - "fields" : [ ] - } ], - "properties" : { - "write.format.default" : "PARQUET" - }, - "current-snapshot-id" : 7858783715254064031, - "refs" : { - "main" : { - "snapshot-id" : 7858783715254064031, - "type" : "branch" - } - }, - "snapshots" : [ { - "sequence-number" : 1, - "snapshot-id" : 7858783715254064031, - "timestamp-ms" : 1678866176782, - "summary" : { - "operation" : "append", - "trino_query_id" : "20230315_074255_00057_9f4mz", - "added-data-files" : "1", - "added-records" : "65", - "added-files-size" : "1260", - "changed-partition-count" : "1", - "total-records" : "65", - "total-files-size" : "1260", - "total-data-files" : "1", - "total-delete-files" : "0", - "total-position-deletes" : "0", - "total-equality-deletes" : "0" - }, - "manifest-list" : "s3://starburst-benchmarks-data/iceberg-50MB-files-tpcds-sf1000-PARQUET/reason/metadata/snap-7858783715254064031-1-b535d285-879e-414d-aa40-feb600797de4.avro", - "schema-id" : 0 - } ], - "statistics" : [ ], - "snapshot-log" : [ { - "timestamp-ms" : 1678866176782, - "snapshot-id" : 7858783715254064031 - } ], - "metadata-log" : [ ] -} \ No newline at end of file diff --git a/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/reason/metadata/b535d285-879e-414d-aa40-feb600797de4-m0.avro b/testing/trino-tests/src/test/resources/iceberg_small_files/tpcds/sf1000/parquet/unpartitioned/reason/metadata/b535d285-879e-414d-aa40-feb600797de4-m0.avro deleted file mode 100644 index a7b18c4dbd7ce2f4f0649bf2bc730064966e658e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6846 zcmb_geTWog81F6vLz64c1XFr%NK)L(otgXCTR1TEPSm`@yXZr%<=xqL?s&LXjw}$UsE;c;0t*=6!c|XWiR7I0Sd! z_j#V*@A-J2-`<~oys2fOqm7ar{_e&J$sNxVDJeOwUC>-9CVAhq>lrB-k4agb7#T-O zZkv$kCvr?OagLt1Q#QelWu~0%aLFcP1>GiaB8v@&#C)rUWsXRbG4HZcc^Pa*Uxtu$ z;ksIRJwqI=4gHqKJ9=<#=3Rh1tzob`A@;LYDgh z3i8e%N<|XUT8$yg*62Sdk8Q|(d6CI+u{hv_%g_hWM{_U7%h(^D^Z`EL8$py)K%5F?m3~%g-lmg zu>J%|>1HZD4jB)ydyVBg7_wY!uyR2T9H-{}<6_uiF=&=i$iZBLXaTd>wlSR$ zx=V6U$HC{}k+NNuyHiD3PWWibihetxHX=p|+!WX>Vxz#j%SS1IkQ0+MorX1{YuFg_ z#(ECtrAazvSQT%Q(?GSU6-`j9+SHx~$)Zm^ z?N_KP#8?Z)qu0STi%k`Whhgm?b}9{=!!qX&oLS5?rHj^CcLUTZUD1tNWil(>4ROct zB8ImnNlK>>O(CD3TaxHBQyZCPia01j%~*vbwXt!=@YX|;SnV)HQaR#cx+PRplH>&! zyf=NOW{>CD-URoMe-EV%D_t`$syL0DJgjjkE1PwQTYvTXFC;94IoK;pTc64w!$E4+ z#c5c-xb5h1q>8JJzgxm)KZA(8jkY|XE*?;yzp!dn4wft()D>(lpuf<@_0&s^*u*k+ zEF0LeGf>ObbO#M0NYEghg(VZtf{&UmI=mX=k0^ZsTA8fUtPIf>L39Ro$ue)1Z7&kr zfrY$Tk%E1(o$iZ}hvxV)T_Z5tMoNn8+ttDPRG$n5Vd_gPUygAF3PP3(P>Pnec)56W z%7hueT0^{Y^;s#L>N3y~#LQGdgI2hP78Mr{Km=b&D$Aar=2adPmtWP5&Al88(h}$C zWYm;|SVSBl`BY1oaS!}=3+5q^WcB&zuT!FNe>_b=^+R`6_f{iKb0E)nOuT|a!mN5& z{`~ZmLv5kPo$!M4syrmo>Twk**|?WP&`Tk)#_gB2azHVaLoo^7BJVknyV-x>2L^96 zv!}N775MO8-lWfj_bqopdkqBFjHDIE&=X)_cDV@P4(h`IhUjWv1%m>OH8&V)Yn}j< z%saW7yqO^~1-dp088p~zkyVJ10EP->QF>ph=#CkpP@#|_P^gf)*`v6wN(gW%odK?p zAebR0r7IGX0^IqS(z=P+H!cJAc5og?n=Jbd2vp;et*uB&B~=$281mT5eEakCD> zSNS6Z^&l?M5#dWg0N#kHxlMC7{Ot)Z&Jlis>1gN<{hhIVL4{|d^Z|^R(oZS=32v|~ zqg6I*9v-j3&=Sp!RZn8o=%5gJbmjXsw^tTi?JYZqvxASa@IuWEmjzmSL%dv-FV?Us z!{4%C7hs$4ylumaN?X@_y3E~RPd#~1Z&`DJd~(G+^)?XgIcmMQzx4l1fu}LZR zHVyZ#T5w?TiJhlpZDIEOnSIwjICymB$6J

    getTable(HiveTableName hiveTableName, Supplier> valueSupplier) - { - return loadValue(tableCache, hiveTableName, valueSupplier); - } - - public PartitionStatistics getTableStatistics(HiveTableName hiveTableName, Supplier valueSupplier) - { - return loadValue(tableStatisticsCache, hiveTableName, valueSupplier); - } - - public Map getPartitionStatistics(Set partitionNames, Supplier> valueSupplier) - { - return loadPartitionValues(partitionNames, partitionStatisticsCache, valueSupplier); - } - - public List getAllTables(String databaseName, Supplier> valueSupplier) - { - return loadValue(tableNamesCache, databaseName, valueSupplier); - } - - public List getTablesWithParameter(TablesWithParameterCacheKey tablesWithParameterCacheKey, Supplier> valueSupplier) - { - return loadValue(tablesWithParameterCache, tablesWithParameterCacheKey, valueSupplier); - } - - public List getAllViews(String databaseName, Supplier> valueSupplier) - { - return loadValue(viewNamesCache, databaseName, valueSupplier); - } - - public Optional> getAllTables(Supplier>> valueSupplier) - { - return loadValue(allTableNamesCache, SingletonCacheKey.INSTANCE, valueSupplier); - } - - public Optional> getAllViews(Supplier>> valueSupplier) - { - return loadValue(allViewNamesCache, SingletonCacheKey.INSTANCE, valueSupplier); - } - - public Optional getPartition(HivePartitionName hivePartitionName, Supplier> valueSupplier) - { - return loadValue(partitionCache, hivePartitionName, valueSupplier); - } - - public Optional> getPartitionNamesByFilter(PartitionFilter partitionFilter, Supplier>> valueSupplier) - { - return loadValue(partitionNamesByPartsCache, partitionFilter, valueSupplier); - } - - public Map> getPartitionsByNames(Set partitionNames, Supplier>> valueSupplier) - { - return loadPartitionValues(partitionNames, partitionsByNamesCache, valueSupplier); - } - - public Set listTablePrivileges(UserTableKey userTableKey, Supplier> valueSupplier) - { - return loadValue(tablePrivilegesCache, userTableKey, valueSupplier); - } - - public Set listRoles(Supplier> valueSupplier) - { - if (replay) { - return allRoles.orElseThrow(() -> new TrinoException(NOT_FOUND, "Missing entry for roles")); - } - - Set result = valueSupplier.get(); - allRoles = Optional.of(result); - return result; - } - - public Set listRoleGrants(HivePrincipal principal, Supplier> valueSupplier) - { - return loadValue(roleGrantsCache, principal, valueSupplier); - } - - public boolean functionExists(DatabaseFunctionSignatureKey key, Supplier valueSupplier) - { - return loadValue(functionExistsCache, key, valueSupplier); - } - - public Collection getFunctions(String databaseName, Supplier> valueSupplier) - { - return loadValue(functionsByDatabaseCache, databaseName, valueSupplier); - } - - public Collection getFunctions(DatabaseFunctionKey key, Supplier> valueSupplier) - { - return loadValue(functionsByNameCache, key, valueSupplier); - } - - private static NonEvictableCache createCache(boolean reply, Duration recordingDuration) - { - if (reply) { - return buildNonEvictableCache(CacheBuilder.newBuilder()); - } - - return buildNonEvictableCache(CacheBuilder.newBuilder() - .expireAfterWrite(recordingDuration.toMillis(), MILLISECONDS)); - } - - @Managed - public void writeRecording() - throws IOException - { - if (replay) { - throw new IllegalStateException("Cannot write recording in replay mode"); - } - - Recording recording = new Recording( - allDatabases, - allRoles, - toPairs(databaseCache), - toPairs(tableCache), - toPairs(tableStatisticsCache), - toPairs(partitionStatisticsCache), - toPairs(tableNamesCache), - toPairs(tablesWithParameterCache), - toPairs(viewNamesCache), - toPairs(partitionCache), - toPairs(partitionNamesCache), - toPairs(partitionNamesByPartsCache), - toPairs(partitionsByNamesCache), - toPairs(tablePrivilegesCache), - toPairs(roleGrantsCache), - toPairs(functionExistsCache), - toPairs(functionsByDatabaseCache), - toPairs(functionsByNameCache)); - - try (GZIPOutputStream outputStream = new GZIPOutputStream(Files.newOutputStream(recordingPath))) { - outputStream.write(recordingCodec.toJsonBytes(recording)); - } - } - - private static Map toMap(List> pairs) - { - return pairs.stream() - .collect(toImmutableMap(Pair::getKey, Pair::getValue)); - } - - private static List> toPairs(Cache cache) - { - return cache.asMap().entrySet().stream() - .map(entry -> new Pair<>(entry.getKey(), entry.getValue())) - .collect(toImmutableList()); - } - - private Map loadPartitionValues( - Set partitionNames, - Cache cache, - Supplier> valueSupplier) - { - if (replay) { - return partitionNames.stream() - .collect(toImmutableMap( - partitionName -> partitionName.getPartitionName().orElseThrow(), - partitionName -> Optional.ofNullable(cache.getIfPresent(partitionName)) - .orElseThrow(() -> new TrinoException(NOT_FOUND, "Missing entry found for key: " + partitionName)))); - } - - Map value = valueSupplier.get(); - partitionNames.forEach(partitionName -> cache.put(partitionName, value.get(partitionName.getPartitionName().orElseThrow()))); - return value; - } - - private V loadValue(Cache cache, K key, Supplier valueSupplier) - { - if (replay) { - return Optional.ofNullable(cache.getIfPresent(key)) - .orElseThrow(() -> new TrinoException(NOT_FOUND, "Missing entry found for key: " + key)); - } - - V value = valueSupplier.get(); - cache.put(key, value); - return value; - } - - @Immutable - public static class Recording - { - private final Optional> allDatabases; - private final Optional> allRoles; - private final List>> databases; - private final List>> tables; - private final List> tableStatistics; - private final List> partitionStatistics; - private final List>> allTables; - private final List>> tablesWithParameter; - private final List>> allViews; - private final List>> partitions; - private final List>>> partitionNames; - private final List>>> partitionNamesByParts; - private final List>> partitionsByNames; - private final List>> tablePrivileges; - private final List>> roleGrants; - private final List> functionExists; - private final List>> functionsByDatabase; - private final List>> functionsByName; - - @JsonCreator - public Recording( - @JsonProperty("allDatabases") Optional> allDatabases, - @JsonProperty("allRoles") Optional> allRoles, - @JsonProperty("databases") List>> databases, - @JsonProperty("tables") List>> tables, - @JsonProperty("tableStatistics") List> tableStatistics, - @JsonProperty("partitionStatistics") List> partitionStatistics, - @JsonProperty("allTables") List>> allTables, - @JsonProperty("tablesWithParameter") List>> tablesWithParameter, - @JsonProperty("allViews") List>> allViews, - @JsonProperty("partitions") List>> partitions, - @JsonProperty("partitionNames") List>>> partitionNames, - @JsonProperty("partitionNamesByParts") List>>> partitionNamesByParts, - @JsonProperty("partitionsByNames") List>> partitionsByNames, - @JsonProperty("tablePrivileges") List>> tablePrivileges, - @JsonProperty("roleGrants") List>> roleGrants, - @JsonProperty("functionExists") List> functionExists, - @JsonProperty("functionsByDatabase") List>> functionsByDatabase, - @JsonProperty("functionsByName") List>> functionsByName) - { - this.allDatabases = allDatabases; - this.allRoles = allRoles; - this.databases = databases; - this.tables = tables; - this.tableStatistics = tableStatistics; - this.partitionStatistics = partitionStatistics; - this.allTables = allTables; - this.tablesWithParameter = tablesWithParameter; - this.allViews = allViews; - this.partitions = partitions; - this.partitionNames = partitionNames; - this.partitionNamesByParts = partitionNamesByParts; - this.partitionsByNames = partitionsByNames; - this.tablePrivileges = tablePrivileges; - this.roleGrants = roleGrants; - this.functionExists = requireNonNullElse(functionExists, List.of()); - this.functionsByDatabase = requireNonNullElse(functionsByDatabase, List.of()); - this.functionsByName = requireNonNullElse(functionsByName, List.of()); - } - - @JsonProperty - public Optional> getAllDatabases() - { - return allDatabases; - } - - @JsonProperty - public Optional> getAllRoles() - { - return allRoles; - } - - @JsonProperty - public List>> getDatabases() - { - return databases; - } - - @JsonProperty - public List>> getTables() - { - return tables; - } - - @JsonProperty - public List>> getTablesWithParameter() - { - return tablesWithParameter; - } - - @JsonProperty - public List> getTableStatistics() - { - return tableStatistics; - } - - @JsonProperty - public List> getPartitionStatistics() - { - return partitionStatistics; - } - - @JsonProperty - public List>> getAllTables() - { - return allTables; - } - - @JsonProperty - public List>> getAllViews() - { - return allViews; - } - - @JsonProperty - public List>> getPartitions() - { - return partitions; - } - - @JsonProperty - public List>>> getPartitionNames() - { - return partitionNames; - } - - @JsonProperty - public List>>> getPartitionNamesByParts() - { - return partitionNamesByParts; - } - - @JsonProperty - public List>> getPartitionsByNames() - { - return partitionsByNames; - } - - @JsonProperty - public List>> getTablePrivileges() - { - return tablePrivileges; - } - - @JsonProperty - public List>> getRoleGrants() - { - return roleGrants; - } - - @JsonProperty - public List> getFunctionExists() - { - return functionExists; - } - - @JsonProperty - public List>> getFunctionsByDatabase() - { - return functionsByDatabase; - } - - @JsonProperty - public List>> getFunctionsByName() - { - return functionsByName; - } - } - - @Immutable - public static class Pair - { - private final K key; - private final V value; - - @JsonCreator - public Pair(@JsonProperty("key") K key, @JsonProperty("value") V value) - { - this.key = requireNonNull(key, "key is null"); - this.value = requireNonNull(value, "value is null"); - } - - @JsonProperty - public K getKey() - { - return key; - } - - @JsonProperty - public V getValue() - { - return value; - } - } - - private enum SingletonCacheKey - { - INSTANCE - } -} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/recording/RecordingHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/recording/RecordingHiveMetastore.java deleted file mode 100644 index ee40680388e9..000000000000 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/recording/RecordingHiveMetastore.java +++ /dev/null @@ -1,421 +0,0 @@ -/* - * 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 io.trino.plugin.hive.metastore.recording; - -import io.trino.plugin.hive.HiveColumnStatisticType; -import io.trino.plugin.hive.HiveType; -import io.trino.plugin.hive.PartitionStatistics; -import io.trino.plugin.hive.acid.AcidTransaction; -import io.trino.plugin.hive.metastore.Database; -import io.trino.plugin.hive.metastore.DatabaseFunctionKey; -import io.trino.plugin.hive.metastore.DatabaseFunctionSignatureKey; -import io.trino.plugin.hive.metastore.HiveMetastore; -import io.trino.plugin.hive.metastore.HivePrincipal; -import io.trino.plugin.hive.metastore.HivePrivilegeInfo; -import io.trino.plugin.hive.metastore.HivePrivilegeInfo.HivePrivilege; -import io.trino.plugin.hive.metastore.Partition; -import io.trino.plugin.hive.metastore.PartitionWithStatistics; -import io.trino.plugin.hive.metastore.PrincipalPrivileges; -import io.trino.plugin.hive.metastore.Table; -import io.trino.plugin.hive.metastore.TablesWithParameterCacheKey; -import io.trino.plugin.hive.metastore.UserTableKey; -import io.trino.spi.connector.SchemaTableName; -import io.trino.spi.function.LanguageFunction; -import io.trino.spi.predicate.TupleDomain; -import io.trino.spi.security.RoleGrant; -import io.trino.spi.type.Type; - -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; - -import static com.google.common.collect.ImmutableSet.toImmutableSet; -import static io.trino.plugin.hive.metastore.HivePartitionName.hivePartitionName; -import static io.trino.plugin.hive.metastore.HiveTableName.hiveTableName; -import static io.trino.plugin.hive.metastore.MetastoreUtil.makePartitionName; -import static io.trino.plugin.hive.metastore.PartitionFilter.partitionFilter; -import static java.util.Objects.requireNonNull; - -public class RecordingHiveMetastore - implements HiveMetastore -{ - private final HiveMetastore delegate; - private final HiveMetastoreRecording recording; - - public RecordingHiveMetastore(HiveMetastore delegate, HiveMetastoreRecording recording) - { - this.delegate = requireNonNull(delegate, "delegate is null"); - this.recording = requireNonNull(recording, "recording is null"); - } - - @Override - public Optional getDatabase(String databaseName) - { - return recording.getDatabase(databaseName, () -> delegate.getDatabase(databaseName)); - } - - @Override - public List getAllDatabases() - { - return recording.getAllDatabases(delegate::getAllDatabases); - } - - @Override - public Optional
    getTable(String databaseName, String tableName) - { - return recording.getTable(hiveTableName(databaseName, tableName), () -> delegate.getTable(databaseName, tableName)); - } - - @Override - public Set getSupportedColumnStatistics(Type type) - { - // No need to record that, since it's a pure local operation. - return delegate.getSupportedColumnStatistics(type); - } - - @Override - public PartitionStatistics getTableStatistics(Table table) - { - return recording.getTableStatistics( - hiveTableName(table.getDatabaseName(), table.getTableName()), - () -> delegate.getTableStatistics(table)); - } - - @Override - public Map getPartitionStatistics(Table table, List partitions) - { - return recording.getPartitionStatistics( - partitions.stream() - .map(partition -> hivePartitionName(hiveTableName(table.getDatabaseName(), table.getTableName()), makePartitionName(table, partition))) - .collect(toImmutableSet()), - () -> delegate.getPartitionStatistics(table, partitions)); - } - - @Override - public void updateTableStatistics(String databaseName, - String tableName, - AcidTransaction transaction, - Function update) - { - verifyRecordingMode(); - delegate.updateTableStatistics(databaseName, tableName, transaction, update); - } - - @Override - public void updatePartitionStatistics(Table table, String partitionName, Function update) - { - verifyRecordingMode(); - delegate.updatePartitionStatistics(table, partitionName, update); - } - - @Override - public void updatePartitionStatistics(Table table, Map> updates) - { - verifyRecordingMode(); - delegate.updatePartitionStatistics(table, updates); - } - - @Override - public List getAllTables(String databaseName) - { - return recording.getAllTables(databaseName, () -> delegate.getAllTables(databaseName)); - } - - @Override - public List getTablesWithParameter(String databaseName, String parameterKey, String parameterValue) - { - TablesWithParameterCacheKey key = new TablesWithParameterCacheKey(databaseName, parameterKey, parameterValue); - return recording.getTablesWithParameter(key, () -> delegate.getTablesWithParameter(databaseName, parameterKey, parameterValue)); - } - - @Override - public List getAllViews(String databaseName) - { - return recording.getAllViews(databaseName, () -> delegate.getAllViews(databaseName)); - } - - @Override - public Optional> getAllTables() - { - return recording.getAllTables(delegate::getAllTables); - } - - @Override - public Optional> getAllViews() - { - return recording.getAllViews(delegate::getAllViews); - } - - @Override - public void createDatabase(Database database) - { - verifyRecordingMode(); - delegate.createDatabase(database); - } - - @Override - public void dropDatabase(String databaseName, boolean deleteData) - { - verifyRecordingMode(); - delegate.dropDatabase(databaseName, deleteData); - } - - @Override - public void renameDatabase(String databaseName, String newDatabaseName) - { - verifyRecordingMode(); - delegate.renameDatabase(databaseName, newDatabaseName); - } - - @Override - public void setDatabaseOwner(String databaseName, HivePrincipal principal) - { - verifyRecordingMode(); - delegate.setDatabaseOwner(databaseName, principal); - } - - @Override - public void createTable(Table table, PrincipalPrivileges principalPrivileges) - { - verifyRecordingMode(); - delegate.createTable(table, principalPrivileges); - } - - @Override - public void dropTable(String databaseName, String tableName, boolean deleteData) - { - verifyRecordingMode(); - delegate.dropTable(databaseName, tableName, deleteData); - } - - @Override - public void replaceTable(String databaseName, String tableName, Table newTable, PrincipalPrivileges principalPrivileges) - { - verifyRecordingMode(); - delegate.replaceTable(databaseName, tableName, newTable, principalPrivileges); - } - - @Override - public void renameTable(String databaseName, String tableName, String newDatabaseName, String newTableName) - { - verifyRecordingMode(); - delegate.renameTable(databaseName, tableName, newDatabaseName, newTableName); - } - - @Override - public void commentTable(String databaseName, String tableName, Optional comment) - { - verifyRecordingMode(); - delegate.commentTable(databaseName, tableName, comment); - } - - @Override - public void setTableOwner(String databaseName, String tableName, HivePrincipal principal) - { - verifyRecordingMode(); - delegate.setTableOwner(databaseName, tableName, principal); - } - - @Override - public void commentColumn(String databaseName, String tableName, String columnName, Optional comment) - { - verifyRecordingMode(); - delegate.commentColumn(databaseName, tableName, columnName, comment); - } - - @Override - public void addColumn(String databaseName, String tableName, String columnName, HiveType columnType, String columnComment) - { - verifyRecordingMode(); - delegate.addColumn(databaseName, tableName, columnName, columnType, columnComment); - } - - @Override - public void renameColumn(String databaseName, String tableName, String oldColumnName, String newColumnName) - { - verifyRecordingMode(); - delegate.renameColumn(databaseName, tableName, oldColumnName, newColumnName); - } - - @Override - public void dropColumn(String databaseName, String tableName, String columnName) - { - verifyRecordingMode(); - delegate.dropColumn(databaseName, tableName, columnName); - } - - @Override - public Optional getPartition(Table table, List partitionValues) - { - return recording.getPartition( - hivePartitionName(hiveTableName(table.getDatabaseName(), table.getTableName()), partitionValues), - () -> delegate.getPartition(table, partitionValues)); - } - - @Override - public Optional> getPartitionNamesByFilter(String databaseName, String tableName, List columnNames, TupleDomain partitionKeysFilter) - { - return recording.getPartitionNamesByFilter( - partitionFilter(databaseName, tableName, columnNames, partitionKeysFilter), - () -> delegate.getPartitionNamesByFilter(databaseName, tableName, columnNames, partitionKeysFilter)); - } - - @Override - public Map> getPartitionsByNames(Table table, List partitionNames) - { - return recording.getPartitionsByNames( - partitionNames.stream() - .map(partitionName -> hivePartitionName(hiveTableName(table.getDatabaseName(), table.getTableName()), partitionName)) - .collect(toImmutableSet()), - () -> delegate.getPartitionsByNames(table, partitionNames)); - } - - @Override - public void addPartitions(String databaseName, String tableName, List partitions) - { - verifyRecordingMode(); - delegate.addPartitions(databaseName, tableName, partitions); - } - - @Override - public void dropPartition(String databaseName, String tableName, List parts, boolean deleteData) - { - verifyRecordingMode(); - delegate.dropPartition(databaseName, tableName, parts, deleteData); - } - - @Override - public void alterPartition(String databaseName, String tableName, PartitionWithStatistics partition) - { - verifyRecordingMode(); - delegate.alterPartition(databaseName, tableName, partition); - } - - @Override - public Set listTablePrivileges(String databaseName, String tableName, Optional tableOwner, Optional principal) - { - return recording.listTablePrivileges( - new UserTableKey(principal, databaseName, tableName, tableOwner), - () -> delegate.listTablePrivileges(databaseName, tableName, tableOwner, principal)); - } - - @Override - public boolean functionExists(String databaseName, String functionName, String signatureToken) - { - return recording.functionExists( - new DatabaseFunctionSignatureKey(databaseName, functionName, signatureToken), - () -> delegate.functionExists(databaseName, functionName, signatureToken)); - } - - @Override - public Collection getFunctions(String databaseName) - { - return recording.getFunctions(databaseName, () -> delegate.getFunctions(databaseName)); - } - - @Override - public Collection getFunctions(String databaseName, String functionName) - { - return recording.getFunctions( - new DatabaseFunctionKey(databaseName, functionName), - () -> delegate.getFunctions(databaseName, functionName)); - } - - @Override - public void createFunction(String databaseName, String functionName, LanguageFunction function) - { - verifyRecordingMode(); - delegate.createFunction(databaseName, functionName, function); - } - - @Override - public void replaceFunction(String databaseName, String functionName, LanguageFunction function) - { - verifyRecordingMode(); - delegate.replaceFunction(databaseName, functionName, function); - } - - @Override - public void dropFunction(String databaseName, String functionName, String signatureToken) - { - verifyRecordingMode(); - delegate.dropFunction(databaseName, functionName, signatureToken); - } - - @Override - public void grantTablePrivileges(String databaseName, String tableName, String tableOwner, HivePrincipal grantee, HivePrincipal grantor, Set privileges, boolean grantOption) - { - verifyRecordingMode(); - delegate.grantTablePrivileges(databaseName, tableName, tableOwner, grantee, grantor, privileges, grantOption); - } - - @Override - public void revokeTablePrivileges(String databaseName, String tableName, String tableOwner, HivePrincipal grantee, HivePrincipal grantor, Set privileges, boolean grantOption) - { - verifyRecordingMode(); - delegate.revokeTablePrivileges(databaseName, tableName, tableOwner, grantee, grantor, privileges, grantOption); - } - - @Override - public void createRole(String role, String grantor) - { - verifyRecordingMode(); - delegate.createRole(role, grantor); - } - - @Override - public void dropRole(String role) - { - verifyRecordingMode(); - delegate.dropRole(role); - } - - @Override - public Set listRoles() - { - return recording.listRoles(delegate::listRoles); - } - - @Override - public void grantRoles(Set roles, Set grantees, boolean adminOption, HivePrincipal grantor) - { - verifyRecordingMode(); - delegate.grantRoles(roles, grantees, adminOption, grantor); - } - - @Override - public void revokeRoles(Set roles, Set grantees, boolean adminOption, HivePrincipal grantor) - { - verifyRecordingMode(); - delegate.revokeRoles(roles, grantees, adminOption, grantor); - } - - @Override - public Set listRoleGrants(HivePrincipal principal) - { - return recording.listRoleGrants( - principal, - () -> delegate.listRoleGrants(principal)); - } - - private void verifyRecordingMode() - { - if (recording.isReplay()) { - throw new IllegalStateException("Cannot perform Metastore updates in replay mode"); - } - } -} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/recording/RecordingHiveMetastoreDecorator.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/recording/RecordingHiveMetastoreDecorator.java deleted file mode 100644 index 1451d381df4d..000000000000 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/recording/RecordingHiveMetastoreDecorator.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 io.trino.plugin.hive.metastore.recording; - -import com.google.inject.Inject; -import io.trino.plugin.hive.metastore.HiveMetastore; -import io.trino.plugin.hive.metastore.HiveMetastoreDecorator; - -import static java.util.Objects.requireNonNull; - -public class RecordingHiveMetastoreDecorator - implements HiveMetastoreDecorator -{ - private final HiveMetastoreRecording recording; - - @Inject - public RecordingHiveMetastoreDecorator(HiveMetastoreRecording recording) - { - this.recording = requireNonNull(recording, "recording is null"); - } - - @Override - public int getPriority() - { - return PRIORITY_RECORDING; - } - - @Override - public HiveMetastore decorate(HiveMetastore hiveMetastore) - { - return new RecordingHiveMetastore(hiveMetastore, recording); - } -} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/recording/RecordingHiveMetastoreDecoratorModule.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/recording/RecordingHiveMetastoreDecoratorModule.java deleted file mode 100644 index e140e836393d..000000000000 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/recording/RecordingHiveMetastoreDecoratorModule.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 io.trino.plugin.hive.metastore.recording; - -import com.google.inject.Binder; -import com.google.inject.Scopes; -import io.airlift.configuration.AbstractConfigurationAwareModule; -import io.trino.plugin.hive.RecordingMetastoreConfig; -import io.trino.plugin.hive.metastore.HiveMetastoreDecorator; -import io.trino.plugin.hive.util.BlockJsonSerde; -import io.trino.plugin.hive.util.HiveBlockEncodingSerde; -import io.trino.spi.block.Block; -import io.trino.spi.procedure.Procedure; - -import static com.google.inject.multibindings.Multibinder.newSetBinder; -import static io.airlift.json.JsonBinder.jsonBinder; -import static io.airlift.json.JsonCodecBinder.jsonCodecBinder; -import static org.weakref.jmx.guice.ExportBinder.newExporter; - -public class RecordingHiveMetastoreDecoratorModule - extends AbstractConfigurationAwareModule -{ - @Override - protected void setup(Binder binder) - { - if (buildConfigObject(RecordingMetastoreConfig.class).getRecordingPath() != null) { - newSetBinder(binder, HiveMetastoreDecorator.class).addBinding().to(RecordingHiveMetastoreDecorator.class).in(Scopes.SINGLETON); - binder.bind(HiveBlockEncodingSerde.class).in(Scopes.SINGLETON); - - binder.bind(HiveMetastoreRecording.class).in(Scopes.SINGLETON); - jsonCodecBinder(binder).bindJsonCodec(HiveMetastoreRecording.Recording.class); - jsonBinder(binder).addSerializerBinding(Block.class).to(BlockJsonSerde.Serializer.class); - jsonBinder(binder).addDeserializerBinding(Block.class).to(BlockJsonSerde.Deserializer.class); - - // export under the old name, for backwards compatibility - newExporter(binder).export(HiveMetastoreRecording.class).as(generator -> generator.generatedNameOf(RecordingHiveMetastore.class)); - - newSetBinder(binder, Procedure.class).addBinding().toProvider(WriteHiveMetastoreRecordingProcedure.class).in(Scopes.SINGLETON); - } - } -} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/recording/WriteHiveMetastoreRecordingProcedure.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/recording/WriteHiveMetastoreRecordingProcedure.java deleted file mode 100644 index 1827d3a53047..000000000000 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/recording/WriteHiveMetastoreRecordingProcedure.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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 io.trino.plugin.hive.metastore.recording; - -import com.google.common.collect.ImmutableList; -import com.google.common.util.concurrent.RateLimiter; -import com.google.inject.Inject; -import com.google.inject.Provider; -import io.trino.spi.procedure.Procedure; - -import java.io.IOException; -import java.lang.invoke.MethodHandle; - -import static java.lang.invoke.MethodHandles.lookup; -import static java.util.Objects.requireNonNull; - -public class WriteHiveMetastoreRecordingProcedure - implements Provider -{ - private static final MethodHandle WRITE_HIVE_METASTORE_RECORDING; - - static { - try { - WRITE_HIVE_METASTORE_RECORDING = lookup().unreflect(WriteHiveMetastoreRecordingProcedure.class.getMethod("writeHiveMetastoreRecording")); - } - catch (ReflectiveOperationException e) { - throw new AssertionError(e); - } - } - - private final RateLimiter rateLimiter = RateLimiter.create(0.2); - private final HiveMetastoreRecording hiveMetastoreRecording; - - @Inject - public WriteHiveMetastoreRecordingProcedure(HiveMetastoreRecording hiveMetastoreRecording) - { - this.hiveMetastoreRecording = requireNonNull(hiveMetastoreRecording, "hiveMetastoreRecording is null"); - } - - @Override - public Procedure get() - { - return new Procedure( - "system", - "write_hive_metastore_recording", - ImmutableList.of(), - WRITE_HIVE_METASTORE_RECORDING.bindTo(this)); - } - - public void writeHiveMetastoreRecording() - { - try { - // limit rate of recording dumps to prevent IO and Trino saturation - rateLimiter.acquire(); - hiveMetastoreRecording.writeRecording(); - } - catch (IOException ex) { - throw new RuntimeException(ex); - } - } -} diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestRecordingMetastoreConfig.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestRecordingMetastoreConfig.java deleted file mode 100644 index 54cb9e492827..000000000000 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestRecordingMetastoreConfig.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 io.trino.plugin.hive; - -import com.google.common.collect.ImmutableMap; -import io.airlift.units.Duration; -import org.junit.jupiter.api.Test; - -import java.util.Map; -import java.util.concurrent.TimeUnit; - -import static io.airlift.configuration.testing.ConfigAssertions.assertFullMapping; -import static io.airlift.configuration.testing.ConfigAssertions.assertRecordedDefaults; -import static io.airlift.configuration.testing.ConfigAssertions.recordDefaults; - -public class TestRecordingMetastoreConfig -{ - @Test - public void testDefaults() - { - assertRecordedDefaults(recordDefaults(RecordingMetastoreConfig.class) - .setRecordingPath(null) - .setRecordingDuration(new Duration(10, TimeUnit.MINUTES)) - .setReplay(false)); - } - - @Test - public void testExplicitPropertyMappings() - { - Map properties = ImmutableMap.builder() - .put("hive.metastore-recording-path", "/foo/bar") - .put("hive.metastore-recording-duration", "42s") - .put("hive.replay-metastore-recording", "true") - .buildOrThrow(); - - RecordingMetastoreConfig expected = new RecordingMetastoreConfig() - .setRecordingPath("/foo/bar") - .setRecordingDuration(new Duration(42, TimeUnit.SECONDS)) - .setReplay(true); - - assertFullMapping(properties, expected); - } -} diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/recording/TestRecordingHiveMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/recording/TestRecordingHiveMetastore.java deleted file mode 100644 index f8c077a0a9c3..000000000000 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/recording/TestRecordingHiveMetastore.java +++ /dev/null @@ -1,354 +0,0 @@ -/* - * 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 io.trino.plugin.hive.metastore.recording; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import io.airlift.json.JsonCodec; -import io.airlift.json.JsonCodecFactory; -import io.airlift.json.ObjectMapperProvider; -import io.airlift.slice.Slices; -import io.airlift.units.Duration; -import io.trino.plugin.base.TypeDeserializer; -import io.trino.plugin.hive.HiveBasicStatistics; -import io.trino.plugin.hive.HiveBucketProperty; -import io.trino.plugin.hive.HiveType; -import io.trino.plugin.hive.PartitionStatistics; -import io.trino.plugin.hive.RecordingMetastoreConfig; -import io.trino.plugin.hive.metastore.Column; -import io.trino.plugin.hive.metastore.Database; -import io.trino.plugin.hive.metastore.HiveColumnStatistics; -import io.trino.plugin.hive.metastore.HiveMetastore; -import io.trino.plugin.hive.metastore.HivePrincipal; -import io.trino.plugin.hive.metastore.HivePrivilegeInfo; -import io.trino.plugin.hive.metastore.HivePrivilegeInfo.HivePrivilege; -import io.trino.plugin.hive.metastore.IntegerStatistics; -import io.trino.plugin.hive.metastore.Partition; -import io.trino.plugin.hive.metastore.SortingColumn; -import io.trino.plugin.hive.metastore.SortingColumn.Order; -import io.trino.plugin.hive.metastore.Storage; -import io.trino.plugin.hive.metastore.StorageFormat; -import io.trino.plugin.hive.metastore.Table; -import io.trino.plugin.hive.metastore.UnimplementedHiveMetastore; -import io.trino.plugin.hive.util.HiveBlockEncodingSerde; -import io.trino.spi.block.Block; -import io.trino.spi.block.TestingBlockJsonSerde; -import io.trino.spi.predicate.Domain; -import io.trino.spi.predicate.TupleDomain; -import io.trino.spi.security.RoleGrant; -import io.trino.spi.security.TrinoPrincipal; -import io.trino.spi.type.TestingTypeManager; -import io.trino.spi.type.Type; -import org.junit.jupiter.api.Test; - -import java.io.File; -import java.io.IOException; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.OptionalLong; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -import static io.trino.plugin.hive.HiveBasicStatistics.createEmptyStatistics; -import static io.trino.plugin.hive.util.HiveBucketing.BucketingVersion.BUCKETING_V1; -import static io.trino.spi.security.PrincipalType.USER; -import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; -import static org.testng.Assert.assertEquals; - -public class TestRecordingHiveMetastore -{ - private static final Database DATABASE = new Database( - "database", - Optional.of("location"), - Optional.of("owner"), - Optional.of(USER), - Optional.of("comment"), - ImmutableMap.of("param", "value")); - private static final Column TABLE_COLUMN = new Column( - "column", - HiveType.HIVE_INT, - Optional.of("comment")); - private static final Storage TABLE_STORAGE = new Storage( - StorageFormat.create("serde", "input", "output"), - Optional.of("location"), - Optional.of(new HiveBucketProperty(ImmutableList.of("column"), BUCKETING_V1, 10, ImmutableList.of(new SortingColumn("column", Order.ASCENDING)))), - true, - ImmutableMap.of("param", "value2")); - private static final Table TABLE = new Table( - "database", - "table", - Optional.of("owner"), - "table_type", - TABLE_STORAGE, - ImmutableList.of(TABLE_COLUMN), - ImmutableList.of(TABLE_COLUMN), - ImmutableMap.of("param", "value3"), - Optional.of("original_text"), - Optional.of("expanded_text"), - OptionalLong.empty()); - private static final Partition PARTITION = new Partition( - "database", - "table", - ImmutableList.of("value"), - TABLE_STORAGE, - ImmutableList.of(TABLE_COLUMN), - ImmutableMap.of("param", "value4")); - private static final Partition OTHER_PARTITION = new Partition( - "database", - "table", - ImmutableList.of("other_value"), - TABLE_STORAGE, - ImmutableList.of(TABLE_COLUMN), - ImmutableMap.of("param", "value4")); - private static final PartitionStatistics PARTITION_STATISTICS = new PartitionStatistics( - new HiveBasicStatistics(10, 11, 10000, 10001), - ImmutableMap.of("column", new HiveColumnStatistics( - Optional.of(new IntegerStatistics( - OptionalLong.of(-100), - OptionalLong.of(102))), - Optional.empty(), - Optional.empty(), - Optional.empty(), - Optional.empty(), - OptionalLong.of(1234), - OptionalLong.of(1235), - OptionalLong.of(1), - OptionalLong.of(8)))); - private static final HivePrivilegeInfo PRIVILEGE_INFO = new HivePrivilegeInfo(HivePrivilege.SELECT, true, new HivePrincipal(USER, "grantor"), new HivePrincipal(USER, "grantee")); - private static final RoleGrant ROLE_GRANT = new RoleGrant(new TrinoPrincipal(USER, "grantee"), "role", true); - private static final List PARTITION_COLUMN_NAMES = ImmutableList.of(TABLE_COLUMN.getName()); - private static final Domain PARTITION_COLUMN_EQUAL_DOMAIN = Domain.singleValue(createUnboundedVarcharType(), Slices.utf8Slice("value1")); - private static final TupleDomain TUPLE_DOMAIN = TupleDomain.withColumnDomains(ImmutableMap.of(TABLE_COLUMN.getName(), PARTITION_COLUMN_EQUAL_DOMAIN)); - - @Test - public void testRecordingHiveMetastore() - throws IOException - { - RecordingMetastoreConfig recordingConfig = new RecordingMetastoreConfig() - .setRecordingPath(File.createTempFile("recording_test", "json").getAbsolutePath()) - .setRecordingDuration(new Duration(10, TimeUnit.MINUTES)); - JsonCodec jsonCodec = createJsonCodec(); - HiveMetastoreRecording recording = new HiveMetastoreRecording(recordingConfig, jsonCodec); - RecordingHiveMetastore recordingHiveMetastore = new RecordingHiveMetastore(new TestingHiveMetastore(), recording); - validateMetadata(recordingHiveMetastore); - recordingHiveMetastore.dropDatabase("other_database", true); - recording.writeRecording(); - - RecordingMetastoreConfig replayingConfig = recordingConfig - .setReplay(true); - - recording = new HiveMetastoreRecording(replayingConfig, jsonCodec); - recordingHiveMetastore = new RecordingHiveMetastore(new UnimplementedHiveMetastore(), recording); - recording.loadRecording(); - validateMetadata(recordingHiveMetastore); - validatePartitionSubset(recordingHiveMetastore); - } - - public static JsonCodec createJsonCodec() - { - ObjectMapperProvider objectMapperProvider = new ObjectMapperProvider(); - TypeDeserializer typeDeserializer = new TypeDeserializer(new TestingTypeManager()); - objectMapperProvider.setJsonDeserializers( - ImmutableMap.of( - Block.class, new TestingBlockJsonSerde.Deserializer(new HiveBlockEncodingSerde()), - Type.class, typeDeserializer)); - objectMapperProvider.setJsonSerializers(ImmutableMap.of(Block.class, new TestingBlockJsonSerde.Serializer(new HiveBlockEncodingSerde()))); - JsonCodec jsonCodec = new JsonCodecFactory(objectMapperProvider).jsonCodec(HiveMetastoreRecording.Recording.class); - return jsonCodec; - } - - private void validateMetadata(HiveMetastore hiveMetastore) - { - assertEquals(hiveMetastore.getDatabase("database"), Optional.of(DATABASE)); - assertEquals(hiveMetastore.getAllDatabases(), ImmutableList.of("database")); - assertEquals(hiveMetastore.getTable("database", "table"), Optional.of(TABLE)); - assertEquals(hiveMetastore.getTableStatistics(TABLE), PARTITION_STATISTICS); - assertEquals(hiveMetastore.getPartitionStatistics(TABLE, ImmutableList.of(PARTITION, OTHER_PARTITION)), ImmutableMap.of( - "column=value", PARTITION_STATISTICS, - "column=other_value", PARTITION_STATISTICS)); - assertEquals(hiveMetastore.getAllTables("database"), ImmutableList.of("table")); - assertEquals(hiveMetastore.getTablesWithParameter("database", "param", "value3"), ImmutableList.of("table")); - assertEquals(hiveMetastore.getAllViews("database"), ImmutableList.of()); - assertEquals(hiveMetastore.getPartition(TABLE, ImmutableList.of("value")), Optional.of(PARTITION)); - assertEquals(hiveMetastore.getPartitionNamesByFilter("database", "table", PARTITION_COLUMN_NAMES, TupleDomain.all()), Optional.of(ImmutableList.of("value"))); - assertEquals(hiveMetastore.getPartitionNamesByFilter("database", "table", PARTITION_COLUMN_NAMES, TUPLE_DOMAIN), Optional.of(ImmutableList.of("value"))); - assertEquals(hiveMetastore.getPartitionsByNames(TABLE, ImmutableList.of("column=value", "column=other_value")), ImmutableMap.of( - "column=value", Optional.of(PARTITION), - "column=other_value", Optional.of(OTHER_PARTITION))); - assertEquals(hiveMetastore.listTablePrivileges("database", "table", Optional.of("owner"), Optional.of(new HivePrincipal(USER, "user"))), ImmutableSet.of(PRIVILEGE_INFO)); - assertEquals(hiveMetastore.listRoles(), ImmutableSet.of("role")); - assertEquals(hiveMetastore.listRoleGrants(new HivePrincipal(USER, "user")), ImmutableSet.of(ROLE_GRANT)); - } - - private void validatePartitionSubset(HiveMetastore hiveMetastore) - { - assertEquals(hiveMetastore.getPartitionStatistics(TABLE, ImmutableList.of(PARTITION)), ImmutableMap.of("column=value", PARTITION_STATISTICS)); - assertEquals(hiveMetastore.getPartitionStatistics(TABLE, ImmutableList.of(OTHER_PARTITION)), ImmutableMap.of("column=other_value", PARTITION_STATISTICS)); - assertEquals(hiveMetastore.getPartitionsByNames(TABLE, ImmutableList.of("column=value")), ImmutableMap.of("column=value", Optional.of(PARTITION))); - assertEquals(hiveMetastore.getPartitionsByNames(TABLE, ImmutableList.of("column=other_value")), ImmutableMap.of("column=other_value", Optional.of(OTHER_PARTITION))); - } - - private static class TestingHiveMetastore - extends UnimplementedHiveMetastore - { - @Override - public Optional getDatabase(String databaseName) - { - if (databaseName.equals("database")) { - return Optional.of(DATABASE); - } - - return Optional.empty(); - } - - @Override - public List getAllDatabases() - { - return ImmutableList.of("database"); - } - - @Override - public Optional
    getTable(String databaseName, String tableName) - { - if (databaseName.equals("database") && tableName.equals("table")) { - return Optional.of(TABLE); - } - - return Optional.empty(); - } - - @Override - public PartitionStatistics getTableStatistics(Table table) - { - if (table.getDatabaseName().equals("database") && table.getTableName().equals("table")) { - return PARTITION_STATISTICS; - } - - return new PartitionStatistics(createEmptyStatistics(), ImmutableMap.of()); - } - - @Override - public Map getPartitionStatistics(Table table, List partitions) - { - ImmutableMap.Builder result = ImmutableMap.builder(); - if (table.getDatabaseName().equals("database") && table.getTableName().equals("table")) { - if (partitions.stream().anyMatch(partition -> partition.getValues().get(0).equals("value"))) { - result.put("column=value", PARTITION_STATISTICS); - } - if (partitions.stream().anyMatch(partition -> partition.getValues().get(0).equals("other_value"))) { - result.put("column=other_value", PARTITION_STATISTICS); - } - } - return result.buildOrThrow(); - } - - @Override - public List getAllTables(String databaseName) - { - if (databaseName.equals("database")) { - return ImmutableList.of("table"); - } - - return ImmutableList.of(); - } - - @Override - public List getTablesWithParameter(String databaseName, String parameterKey, String parameterValue) - { - if (databaseName.equals("database") && parameterKey.equals("param") && parameterValue.equals("value3")) { - return ImmutableList.of("table"); - } - return ImmutableList.of(); - } - - @Override - public List getAllViews(String databaseName) - { - return ImmutableList.of(); - } - - @Override - public void dropDatabase(String databaseName, boolean deleteData) - { - // noop for test purpose - } - - @Override - public Optional getPartition(Table table, List partitionValues) - { - if (table.getDatabaseName().equals("database") && table.getTableName().equals("table")) { - if (partitionValues.equals(ImmutableList.of("value"))) { - return Optional.of(PARTITION); - } - if (partitionValues.equals(ImmutableList.of("other_value"))) { - return Optional.of(OTHER_PARTITION); - } - } - return Optional.empty(); - } - - @Override - public Optional> getPartitionNamesByFilter(String databaseName, - String tableName, - List columnNames, - TupleDomain partitionKeysFilter) - { - Domain filterDomain = partitionKeysFilter.getDomains().get().get(TABLE_COLUMN.getName()); - if (databaseName.equals("database") && tableName.equals("table") && (filterDomain == null || filterDomain.equals(PARTITION_COLUMN_EQUAL_DOMAIN))) { - return Optional.of(ImmutableList.of("value")); - } - - return Optional.empty(); - } - - @Override - public Map> getPartitionsByNames(Table table, List partitionNames) - { - ImmutableMap.Builder> result = ImmutableMap.builder(); - if (table.getDatabaseName().equals("database") && table.getTableName().equals("table")) { - if (partitionNames.contains("column=value")) { - result.put("column=value", Optional.of(PARTITION)); - } - if (partitionNames.contains("column=other_value")) { - result.put("column=other_value", Optional.of(OTHER_PARTITION)); - } - } - return result.buildOrThrow(); - } - - @Override - public Set listTablePrivileges(String databaseName, String tableName, Optional tableOwner, Optional principal) - { - if (databaseName.equals("database") && tableName.equals("table") && principal.get().getType() == USER && principal.get().getName().equals("user")) { - return ImmutableSet.of(PRIVILEGE_INFO); - } - - return ImmutableSet.of(); - } - - @Override - public Set listRoles() - { - return ImmutableSet.of("role"); - } - - @Override - public Set listRoleGrants(HivePrincipal principal) - { - return ImmutableSet.of(ROLE_GRANT); - } - } -} diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergPlugin.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergPlugin.java index d33c5eaea614..775dad725297 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergPlugin.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergPlugin.java @@ -131,45 +131,6 @@ public void testGlueMetastore() .shutdown(); } - @Test - public void testRecordingMetastore() - { - ConnectorFactory factory = getConnectorFactory(); - - // recording with thrift - factory.create( - "test", - Map.of( - "iceberg.catalog.type", "HIVE_METASTORE", - "hive.metastore.uri", "thrift://foo:1234", - "hive.metastore-recording-path", "/tmp", - "bootstrap.quiet", "true"), - new TestingConnectorContext()) - .shutdown(); - - // recording with glue - assertThatThrownBy(() -> factory.create( - "test", - Map.of( - "iceberg.catalog.type", "glue", - "hive.metastore.glue.region", "us-east-2", - "hive.metastore-recording-path", "/tmp", - "bootstrap.quiet", "true"), - new TestingConnectorContext())) - .hasMessageContaining("Configuration property 'hive.metastore-recording-path' was not used"); - - // recording with nessie - assertThatThrownBy(() -> factory.create( - "test", - Map.of( - "iceberg.catalog.type", "nessie", - "hive.metastore.nessie.region", "us-east-2", - "hive.metastore-recording-path", "/tmp", - "bootstrap.quiet", "true"), - new TestingConnectorContext())) - .hasMessageContaining("Configuration property 'hive.metastore-recording-path' was not used"); - } - @Test public void testAllowAllAccessControl() { From 3cbe0c2d828e3291c895363abe7305511f780396 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Wed, 15 Nov 2023 19:35:01 -0800 Subject: [PATCH 283/587] Move cache keys inside of CachingHiveMetastore --- .../TablesWithParameterCacheKey.java | 79 ------------- .../plugin/hive/metastore/UserTableKey.java | 108 ------------------ .../metastore/cache/CachingHiveMetastore.java | 26 ++++- 3 files changed, 21 insertions(+), 192 deletions(-) delete mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/TablesWithParameterCacheKey.java delete mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/UserTableKey.java diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/TablesWithParameterCacheKey.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/TablesWithParameterCacheKey.java deleted file mode 100644 index b7459209a706..000000000000 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/TablesWithParameterCacheKey.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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 io.trino.plugin.hive.metastore; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.errorprone.annotations.Immutable; - -import java.util.Objects; - -@Immutable -public class TablesWithParameterCacheKey -{ - private final String databaseName; - private final String parameterKey; - private final String parameterValue; - - @JsonCreator - public TablesWithParameterCacheKey( - @JsonProperty("databaseName") String databaseName, - @JsonProperty("parameterKey") String parameterKey, - @JsonProperty("parameterValue") String parameterValue) - { - this.databaseName = databaseName; - this.parameterKey = parameterKey; - this.parameterValue = parameterValue; - } - - @JsonProperty - public String getDatabaseName() - { - return databaseName; - } - - @JsonProperty - public String getParameterKey() - { - return parameterKey; - } - - @JsonProperty - public String getParameterValue() - { - return parameterValue; - } - - @Override - public boolean equals(Object o) - { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - TablesWithParameterCacheKey other = (TablesWithParameterCacheKey) o; - return Objects.equals(databaseName, other.databaseName) && - Objects.equals(parameterKey, other.parameterKey) && - Objects.equals(parameterValue, other.parameterValue); - } - - @Override - public int hashCode() - { - return Objects.hash(databaseName, parameterKey, parameterValue); - } -} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/UserTableKey.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/UserTableKey.java deleted file mode 100644 index 09210d39647f..000000000000 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/UserTableKey.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * 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 io.trino.plugin.hive.metastore; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.errorprone.annotations.Immutable; - -import java.util.Objects; -import java.util.Optional; - -import static com.google.common.base.MoreObjects.toStringHelper; -import static java.util.Objects.requireNonNull; - -@Immutable -public class UserTableKey -{ - private final Optional principal; - private final String database; - private final String table; - private final Optional owner; - - @JsonCreator - public UserTableKey( - @JsonProperty("principal") Optional principal, - @JsonProperty("database") String database, - @JsonProperty("table") String table, - @JsonProperty("owner") Optional owner) - { - this.principal = requireNonNull(principal, "principal is null"); - this.database = requireNonNull(database, "database is null"); - this.table = requireNonNull(table, "table is null"); - this.owner = requireNonNull(owner, "owner is null"); - } - - @JsonProperty - public Optional getPrincipal() - { - return principal; - } - - @JsonProperty - public String getDatabase() - { - return database; - } - - @JsonProperty - public String getTable() - { - return table; - } - - @JsonProperty - public Optional getOwner() - { - return owner; - } - - public boolean matches(String databaseName, String tableName) - { - return this.database.equals(databaseName) && this.table.equals(tableName); - } - - @Override - public boolean equals(Object o) - { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - UserTableKey that = (UserTableKey) o; - return Objects.equals(principal, that.principal) && - Objects.equals(table, that.table) && - Objects.equals(database, that.database) && - Objects.equals(owner, that.owner); - } - - @Override - public int hashCode() - { - return Objects.hash(principal, table, database, owner); - } - - @Override - public String toString() - { - return toStringHelper(this) - .add("principal", principal) - .add("table", table) - .add("database", database) - .add("owner", owner.orElse(null)) - .toString(); - } -} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java index f69b70c35306..b7e817737454 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java @@ -48,8 +48,6 @@ import io.trino.plugin.hive.metastore.PartitionWithStatistics; import io.trino.plugin.hive.metastore.PrincipalPrivileges; import io.trino.plugin.hive.metastore.Table; -import io.trino.plugin.hive.metastore.TablesWithParameterCacheKey; -import io.trino.plugin.hive.metastore.UserTableKey; import io.trino.spi.TrinoException; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.function.LanguageFunction; @@ -209,7 +207,7 @@ private CachingHiveMetastore( tableCache = cacheFactory.buildCache(this::loadTable); viewNamesCache = cacheFactory.buildCache(this::loadAllViews); allViewNamesCache = cacheFactory.buildCache(ignore -> loadAllViews()); - tablePrivilegesCache = cacheFactory.buildCache(key -> loadTablePrivileges(key.getDatabase(), key.getTable(), key.getOwner(), key.getPrincipal())); + tablePrivilegesCache = cacheFactory.buildCache(key -> loadTablePrivileges(key.database(), key.table(), key.owner(), key.principal())); rolesCache = cacheFactory.buildCache(ignored -> loadRoles()); roleGrantsCache = cacheFactory.buildCache(this::loadRoleGrants); configValuesCache = cacheFactory.buildCache(this::loadConfigValue); @@ -602,7 +600,7 @@ public List getTablesWithParameter(String databaseName, String parameter private List loadTablesMatchingParameter(TablesWithParameterCacheKey key) { - return delegate.getTablesWithParameter(key.getDatabaseName(), key.getParameterKey(), key.getParameterValue()); + return delegate.getTablesWithParameter(key.databaseName(), key.parameterKey(), key.parameterValue()); } @Override @@ -807,7 +805,7 @@ public void invalidateTable(String databaseName, String tableName) private void invalidateTablesWithParameterCache(String databaseName, String tableName) { tablesWithParameterCache.asMap().keySet().stream() - .filter(cacheKey -> cacheKey.getDatabaseName().equals(databaseName)) + .filter(cacheKey -> cacheKey.databaseName().equals(databaseName)) .filter(cacheKey -> { List cacheValue = tablesWithParameterCache.getIfPresent(cacheKey); return cacheValue != null && cacheValue.contains(tableName); @@ -1235,6 +1233,24 @@ private enum SingletonCacheKey INSTANCE } + record TablesWithParameterCacheKey(String databaseName, String parameterKey, String parameterValue) {} + + record UserTableKey(Optional principal, String database, String table, Optional owner) + { + UserTableKey + { + requireNonNull(principal, "principal is null"); + requireNonNull(database, "database is null"); + requireNonNull(table, "table is null"); + requireNonNull(owner, "owner is null"); + } + + public boolean matches(String databaseName, String tableName) + { + return this.database.equals(databaseName) && this.table.equals(tableName); + } + } + // // Stats used for non-impersonation shared caching // From 49072b65cd01c216cfbf2ef5624556b70ff3e19b Mon Sep 17 00:00:00 2001 From: Star Poon Date: Fri, 17 Nov 2023 12:21:24 +0900 Subject: [PATCH 284/587] Remove obsoleted config from doc --- docs/src/main/sphinx/admin/session-property-managers.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/src/main/sphinx/admin/session-property-managers.md b/docs/src/main/sphinx/admin/session-property-managers.md index 456c16ca47cc..fed751b26c90 100644 --- a/docs/src/main/sphinx/admin/session-property-managers.md +++ b/docs/src/main/sphinx/admin/session-property-managers.md @@ -72,7 +72,6 @@ These requirements can be expressed with the following rules: "clientTags": ["etl"], "sessionProperties": { "scale_writers": "true", - "writer_min_size": "1GB", "hive.insert_existing_partitions_behavior": "overwrite" } } From 1f6792bb00229353b8651704fef08d11b2f207af Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Fri, 17 Nov 2023 14:31:00 +0900 Subject: [PATCH 285/587] Disable TestGcsFileSystemGcs temporarily --- .../test/java/io/trino/filesystem/gcs/TestGcsFileSystemGcs.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/TestGcsFileSystemGcs.java b/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/TestGcsFileSystemGcs.java index b9a056e487fc..32c7cfb8ff15 100644 --- a/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/TestGcsFileSystemGcs.java +++ b/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/TestGcsFileSystemGcs.java @@ -14,10 +14,12 @@ package io.trino.filesystem.gcs; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.TestInstance; import java.io.IOException; +@Disabled // TODO Re-enable once fixed the initialization failure https://github.com/trinodb/trino/issues/19785 @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class TestGcsFileSystemGcs extends AbstractTestGcsFileSystem From 4055fdf45794a84bcc2a2bdbd428736f05e6bae8 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Thu, 16 Nov 2023 16:19:50 +0100 Subject: [PATCH 286/587] Verify negative SUPPORTS_CREATE_FUNCTION declarations When `BaseConnectorTest` implementation declares that it does not have `SUPPORTS_CREATE_FUNCTION` behavior, verify this is true. --- .../main/java/io/trino/testing/BaseConnectorTest.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java b/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java index 02ae0aaeeac4..6f7ee9373578 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java @@ -6553,7 +6553,14 @@ private void testMaterializedViewColumnName(String columnName, boolean delimited @Test public void testCreateFunction() { - skipTestUnless(hasBehavior(SUPPORTS_CREATE_FUNCTION)); + if (!hasBehavior(SUPPORTS_CREATE_FUNCTION)) { + String catalog = getQueryRunner().getDefaultSession().getCatalog().orElseThrow(); + String schema = getQueryRunner().getDefaultSession().getSchema().orElseThrow(); + assertQueryFails( + "CREATE FUNCTION " + catalog + "." + schema + ".test_create_function" + randomNameSuffix() + "(x integer) RETURNS bigint COMMENT 't42' RETURN x * 42", + "This connector does not support creating functions"); + return; + } String name = "test_" + randomNameSuffix(); String name2 = "test_" + randomNameSuffix(); From c1ad2efb97805709b2bb10409d23b4968b7006f2 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 15 Nov 2023 22:48:43 +0100 Subject: [PATCH 287/587] Fix X-Trino-Authorization-User typo There is no such header. Only `X-Trino-User` and `X-Trino-Original-User` exist. --- .../io/trino/server/security/TestResourceSecurity.java | 2 +- docs/src/main/sphinx/develop/client-protocol.md | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/core/trino-main/src/test/java/io/trino/server/security/TestResourceSecurity.java b/core/trino-main/src/test/java/io/trino/server/security/TestResourceSecurity.java index 475a39575f96..526d54e51f39 100644 --- a/core/trino-main/src/test/java/io/trino/server/security/TestResourceSecurity.java +++ b/core/trino-main/src/test/java/io/trino/server/security/TestResourceSecurity.java @@ -977,7 +977,7 @@ public void testResourceSecurityImpersonation() server.getInstance(Key.get(PasswordAuthenticatorManager.class)).setAuthenticators(TestResourceSecurity::authenticate); HttpServerInfo httpServerInfo = server.getInstance(Key.get(HttpServerInfo.class)); - // Authenticated user TEST_USER_LOGIN impersonates impersonated-user by passing request header X-Trino-Authorization-User + // Authenticated user TEST_USER_LOGIN impersonates impersonated-user by passing request header X-Trino-User Request request = new Request.Builder() .url(getLocation(httpServerInfo.getHttpsUri(), "/protocol/identity")) .addHeader("Authorization", Credentials.basic(TEST_USER_LOGIN, TEST_PASSWORD)) diff --git a/docs/src/main/sphinx/develop/client-protocol.md b/docs/src/main/sphinx/develop/client-protocol.md index aa1f3413fbbc..fba70dc3bf82 100644 --- a/docs/src/main/sphinx/develop/client-protocol.md +++ b/docs/src/main/sphinx/develop/client-protocol.md @@ -218,11 +218,12 @@ subsequent requests to be consistent with the response headers received. header in subsequent client requests. * - `X-Trino-Set-Authorization-User` - Instructs the client to set the session authorization user in the - `X-Trino-Authorization-User` request header in subsequent client requests. + `X-Trino-User` request header in subsequent client requests. + `X-Trino-Original-User` should also be set. * - `X-Trino-Reset-Authorization-User` - - Instructs the client to remove `X-Trino-Authorization-User` request header - in subsequent client requests to reset the authorization user back to the - original user. + - Instructs the client to reset `X-Trino-User` request header to its original + value in subsequent client requests and remove `X-Trino-Original-User` + to reset the authorization user back to the original user. * - `X-Trino-Set-Session` - The value of the `X-Trino-Set-Session` response header is a string of the form *property* = *value*. It instructs the client include session property From 85d4b6a093cf9b3afa92fa5e38536266c39551ea Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Tue, 14 Nov 2023 13:30:00 +0900 Subject: [PATCH 288/587] Extract fields by names in CheckpointEntryIterator --- .../main/java/io/trino/parquet/Column.java | 25 ++ .../trino/parquet/reader/ParquetReader.java | 17 +- .../trino/parquet/writer/ParquetWriter.java | 14 +- .../io/trino/parquet/ParquetTestUtils.java | 12 +- .../checkpoint/CheckpointEntryIterator.java | 266 +++++++----------- .../checkpoint/CheckpointFieldReader.java | 172 +++++++++++ .../hive/parquet/ParquetPageSource.java | 6 + .../parquet/ParquetPageSourceFactory.java | 7 +- .../iceberg/IcebergPageSourceProvider.java | 5 +- 9 files changed, 339 insertions(+), 185 deletions(-) create mode 100644 lib/trino-parquet/src/main/java/io/trino/parquet/Column.java create mode 100644 plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointFieldReader.java diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/Column.java b/lib/trino-parquet/src/main/java/io/trino/parquet/Column.java new file mode 100644 index 000000000000..a6c703cafb90 --- /dev/null +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/Column.java @@ -0,0 +1,25 @@ +/* + * 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 io.trino.parquet; + +import static java.util.Objects.requireNonNull; + +public record Column(String name, Field field) +{ + public Column + { + requireNonNull(name, "name is null"); + requireNonNull(field, "field is null"); + } +} diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/ParquetReader.java b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/ParquetReader.java index e650f7f31f50..914942f5c77b 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/ParquetReader.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/ParquetReader.java @@ -21,6 +21,7 @@ import io.airlift.log.Logger; import io.trino.memory.context.AggregatedMemoryContext; import io.trino.parquet.ChunkKey; +import io.trino.parquet.Column; import io.trino.parquet.DiskRange; import io.trino.parquet.Field; import io.trino.parquet.GroupField; @@ -66,6 +67,7 @@ import java.util.function.Function; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static io.trino.parquet.ParquetValidationUtils.validateParquet; import static io.trino.parquet.ParquetWriteValidation.StatisticsValidation; @@ -94,7 +96,7 @@ public class ParquetReader private final Optional fileCreatedBy; private final List blocks; private final List firstRowsOfBlocks; - private final List columnFields; + private final List columnFields; private final List primitiveFields; private final ParquetDataSource dataSource; private final ColumnReaderFactory columnReaderFactory; @@ -133,7 +135,7 @@ public class ParquetReader public ParquetReader( Optional fileCreatedBy, - List columnFields, + List columnFields, List blocks, List firstRowsOfBlocks, ParquetDataSource dataSource, @@ -148,7 +150,7 @@ public ParquetReader( public ParquetReader( Optional fileCreatedBy, - List columnFields, + List columnFields, List blocks, List firstRowsOfBlocks, ParquetDataSource dataSource, @@ -164,7 +166,7 @@ public ParquetReader( this.fileCreatedBy = requireNonNull(fileCreatedBy, "fileCreatedBy is null"); requireNonNull(columnFields, "columnFields is null"); this.columnFields = ImmutableList.copyOf(columnFields); - this.primitiveFields = getPrimitiveFields(columnFields); + this.primitiveFields = getPrimitiveFields(columnFields.stream().map(Column::field).collect(toImmutableList())); this.blocks = requireNonNull(blocks, "blocks is null"); this.firstRowsOfBlocks = requireNonNull(firstRowsOfBlocks, "firstRowsOfBlocks is null"); this.dataSource = requireNonNull(dataSource, "dataSource is null"); @@ -269,7 +271,7 @@ public Page nextPage() blockFactory.nextPage(); Block[] blocks = new Block[columnFields.size()]; for (int channel = 0; channel < columnFields.size(); channel++) { - Field field = columnFields.get(channel); + Field field = columnFields.get(channel).field(); blocks[channel] = blockFactory.createBlock(batchSize, () -> readBlock(field)); } Page page = new Page(batchSize, blocks); @@ -493,6 +495,11 @@ private ColumnChunk readPrimitive(PrimitiveField field) return columnChunk; } + public List getColumnFields() + { + return columnFields; + } + public Metrics getMetrics() { ImmutableMap.Builder> metrics = ImmutableMap.>builder() diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/writer/ParquetWriter.java b/lib/trino-parquet/src/main/java/io/trino/parquet/writer/ParquetWriter.java index 1f7acbac194d..c9ddb8e7f94b 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/writer/ParquetWriter.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/writer/ParquetWriter.java @@ -19,7 +19,7 @@ import io.airlift.slice.OutputStreamSliceOutput; import io.airlift.slice.Slice; import io.airlift.slice.Slices; -import io.trino.parquet.Field; +import io.trino.parquet.Column; import io.trino.parquet.ParquetCorruptionException; import io.trino.parquet.ParquetDataSource; import io.trino.parquet.ParquetReaderOptions; @@ -242,12 +242,14 @@ private ParquetReader createParquetReader(ParquetDataSource input, ParquetMetada { org.apache.parquet.hadoop.metadata.FileMetaData fileMetaData = parquetMetadata.getFileMetaData(); MessageColumnIO messageColumnIO = getColumnIO(fileMetaData.getSchema(), fileMetaData.getSchema()); - ImmutableList.Builder columnFields = ImmutableList.builder(); + ImmutableList.Builder columnFields = ImmutableList.builder(); for (int i = 0; i < writeValidation.getTypes().size(); i++) { - columnFields.add(constructField( - writeValidation.getTypes().get(i), - lookupColumnByName(messageColumnIO, writeValidation.getColumnNames().get(i))) - .orElseThrow()); + columnFields.add(new Column( + messageColumnIO.getName(), + constructField( + writeValidation.getTypes().get(i), + lookupColumnByName(messageColumnIO, writeValidation.getColumnNames().get(i))) + .orElseThrow())); } long nextStart = 0; ImmutableList.Builder blockStartsBuilder = ImmutableList.builder(); diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/ParquetTestUtils.java b/lib/trino-parquet/src/test/java/io/trino/parquet/ParquetTestUtils.java index 6f6b5e70b8ca..cceb67158af1 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/ParquetTestUtils.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/ParquetTestUtils.java @@ -104,12 +104,14 @@ public static ParquetReader createParquetReader( { org.apache.parquet.hadoop.metadata.FileMetaData fileMetaData = parquetMetadata.getFileMetaData(); MessageColumnIO messageColumnIO = getColumnIO(fileMetaData.getSchema(), fileMetaData.getSchema()); - ImmutableList.Builder columnFields = ImmutableList.builder(); + ImmutableList.Builder columnFields = ImmutableList.builder(); for (int i = 0; i < types.size(); i++) { - columnFields.add(constructField( - types.get(i), - lookupColumnByName(messageColumnIO, columnNames.get(i))) - .orElseThrow()); + columnFields.add(new Column( + messageColumnIO.getName(), + constructField( + types.get(i), + lookupColumnByName(messageColumnIO, columnNames.get(i))) + .orElseThrow())); } long nextStart = 0; ImmutableList.Builder blockStartsBuilder = ImmutableList.builder(); diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointEntryIterator.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointEntryIterator.java index c2dca1f4e295..ebf150012df4 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointEntryIterator.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointEntryIterator.java @@ -17,10 +17,10 @@ import com.google.common.collect.AbstractIterator; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; import com.google.common.math.LongMath; import io.airlift.log.Logger; import io.trino.filesystem.TrinoInputFile; +import io.trino.parquet.Column; import io.trino.parquet.ParquetReaderOptions; import io.trino.plugin.deltalake.DeltaHiveTypeTranslator; import io.trino.plugin.deltalake.DeltaLakeColumnHandle; @@ -40,20 +40,14 @@ import io.trino.plugin.hive.HiveColumnProjectionInfo; import io.trino.plugin.hive.HiveType; import io.trino.plugin.hive.ReaderPageSource; +import io.trino.plugin.hive.parquet.ParquetPageSource; import io.trino.plugin.hive.parquet.ParquetPageSourceFactory; import io.trino.spi.Page; import io.trino.spi.TrinoException; -import io.trino.spi.block.ArrayBlock; import io.trino.spi.block.Block; -import io.trino.spi.block.ByteArrayBlock; -import io.trino.spi.block.IntArrayBlock; import io.trino.spi.block.LongArrayBlock; -import io.trino.spi.block.MapBlock; -import io.trino.spi.block.RowBlock; import io.trino.spi.block.SqlRow; import io.trino.spi.block.ValueBlock; -import io.trino.spi.block.VariableWidthBlock; -import io.trino.spi.connector.ConnectorPageSource; import io.trino.spi.connector.ConnectorSession; import io.trino.spi.predicate.Domain; import io.trino.spi.predicate.TupleDomain; @@ -67,8 +61,6 @@ import jakarta.annotation.Nullable; import org.joda.time.DateTimeZone; -import java.io.IOException; -import java.io.UncheckedIOException; import java.util.ArrayDeque; import java.util.List; import java.util.Map; @@ -81,6 +73,8 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Verify.verify; import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static com.google.common.collect.MoreCollectors.onlyElement; import static io.trino.plugin.deltalake.DeltaLakeColumnType.REGULAR; import static io.trino.plugin.deltalake.DeltaLakeErrorCode.DELTA_LAKE_INVALID_SCHEMA; import static io.trino.plugin.deltalake.DeltaLakeSplitManager.partitionMatchesPredicate; @@ -136,7 +130,8 @@ public String getColumnName() private final String checkpointPath; private final ConnectorSession session; - private final ConnectorPageSource pageSource; + private final ParquetPageSource pageSource; + private final Map parquetFields; private final MapType stringMap; private final ArrayType stringList; private final Queue nextEntries; @@ -215,8 +210,11 @@ public CheckpointEntryIterator( verify(pageSource.getReaderColumns().isEmpty(), "All columns expected to be base columns"); - this.pageSource = pageSource.get(); + this.pageSource = (ParquetPageSource) pageSource.get(); this.nextEntries = new ArrayDeque<>(); + // The size between parquetFields and extractors may not match when the requested field doesn't exist in Parquet file + this.parquetFields = this.pageSource.getColumnFields().stream() + .collect(toImmutableMap(Column::name, e -> e.field().getType())); this.extractors = fields.stream() .map(field -> requireNonNull(extractors.get(field), "No extractor found for field " + field)) .collect(toImmutableList()); @@ -309,44 +307,52 @@ private DeltaLakeTransactionLogEntry buildCommitInfoEntry(ConnectorSession sessi if (block.isNull(pagePosition)) { return null; } + RowType type = (RowType) parquetFields.get("commitinfo"); int commitInfoFields = 12; int jobFields = 5; int notebookFields = 1; SqlRow commitInfoRow = block.getObject(pagePosition, SqlRow.class); + CheckpointFieldReader commitInfo = new CheckpointFieldReader(session, commitInfoRow, type); log.debug("Block %s has %s fields", block, commitInfoRow.getFieldCount()); if (commitInfoRow.getFieldCount() != commitInfoFields) { throw new TrinoException(DELTA_LAKE_INVALID_SCHEMA, format("Expected block %s to have %d children, but found %s", block, commitInfoFields, commitInfoRow.getFieldCount())); } - SqlRow jobRow = getRowField(commitInfoRow, 9); + SqlRow jobRow = commitInfo.getRow("job"); if (jobRow.getFieldCount() != jobFields) { throw new TrinoException(DELTA_LAKE_INVALID_SCHEMA, format("Expected block %s to have %d children, but found %s", jobRow, jobFields, jobRow.getFieldCount())); } - SqlRow notebookRow = getRowField(commitInfoRow, 7); + RowType.Field jobField = type.getFields().stream().filter(field -> field.getName().orElseThrow().equals("job")).collect(onlyElement()); + CheckpointFieldReader job = new CheckpointFieldReader(session, jobRow, (RowType) jobField.getType()); + + SqlRow notebookRow = commitInfo.getRow("notebook"); if (notebookRow.getFieldCount() != notebookFields) { throw new TrinoException(DELTA_LAKE_INVALID_SCHEMA, format("Expected block %s to have %d children, but found %s", notebookRow, notebookFields, notebookRow.getFieldCount())); } + RowType.Field notebookField = type.getFields().stream().filter(field -> field.getName().orElseThrow().equals("notebook")).collect(onlyElement()); + CheckpointFieldReader notebook = new CheckpointFieldReader(session, notebookRow, (RowType) notebookField.getType()); + CommitInfoEntry result = new CommitInfoEntry( - getLongField(commitInfoRow, 0), - getLongField(commitInfoRow, 1), - getStringField(commitInfoRow, 2), - getStringField(commitInfoRow, 3), - getStringField(commitInfoRow, 4), - getMapField(commitInfoRow, 5), + commitInfo.getLong("version"), + commitInfo.getLong("timestamp"), + commitInfo.getString("userId"), + commitInfo.getString("userName"), + commitInfo.getString("operation"), + commitInfo.getMap(stringMap, "operationParameters"), new CommitInfoEntry.Job( - getStringField(jobRow, 0), - getStringField(jobRow, 1), - getStringField(jobRow, 2), - getStringField(jobRow, 3), - getStringField(jobRow, 4)), + job.getString("jobId"), + job.getString("jobName"), + job.getString("runId"), + job.getString("jobOwnerId"), + job.getString("triggerType")), new CommitInfoEntry.Notebook( - getStringField(notebookRow, 0)), - getStringField(commitInfoRow, 8), - getLongField(commitInfoRow, 9), - getStringField(commitInfoRow, 10), - Optional.of(getBooleanField(commitInfoRow, 11))); + notebook.getString("notebookId")), + commitInfo.getString("clusterId"), + commitInfo.getInt("readVersion"), + commitInfo.getString("isolationLevel"), + Optional.of(commitInfo.getBoolean("isBlindAppend"))); log.debug("Result: %s", result); return DeltaLakeTransactionLogEntry.commitInfoEntry(result); } @@ -357,6 +363,7 @@ private DeltaLakeTransactionLogEntry buildProtocolEntry(ConnectorSession session if (block.isNull(pagePosition)) { return null; } + RowType type = (RowType) parquetFields.get("protocol"); int minProtocolFields = 2; int maxProtocolFields = 4; SqlRow protocolEntryRow = block.getObject(pagePosition, SqlRow.class); @@ -366,14 +373,13 @@ private DeltaLakeTransactionLogEntry buildProtocolEntry(ConnectorSession session throw new TrinoException(DELTA_LAKE_INVALID_SCHEMA, format("Expected block %s to have between %d and %d children, but found %s", block, minProtocolFields, maxProtocolFields, fieldCount)); } - Optional> readerFeatures = getOptionalSetField(protocolEntryRow, 2); - // The last entry should be writer feature when protocol entry size is 3 https://github.com/delta-io/delta/blob/master/PROTOCOL.md#disabled-features - Optional> writerFeatures = fieldCount != 4 ? readerFeatures : getOptionalSetField(protocolEntryRow, 3); + + CheckpointFieldReader protocol = new CheckpointFieldReader(session, protocolEntryRow, type); ProtocolEntry result = new ProtocolEntry( - getIntField(protocolEntryRow, 0), - getIntField(protocolEntryRow, 1), - readerFeatures, - writerFeatures); + protocol.getInt("minReaderVersion"), + protocol.getInt("minWriterVersion"), + protocol.getOptionalSet(stringList, "readerFeatures"), + protocol.getOptionalSet(stringList, "writerFeatures")); log.debug("Result: %s", result); return DeltaLakeTransactionLogEntry.protocolEntry(result); } @@ -384,30 +390,35 @@ private DeltaLakeTransactionLogEntry buildMetadataEntry(ConnectorSession session if (block.isNull(pagePosition)) { return null; } + RowType type = (RowType) parquetFields.get("metadata"); int metadataFields = 8; int formatFields = 2; SqlRow metadataEntryRow = block.getObject(pagePosition, SqlRow.class); + CheckpointFieldReader metadata = new CheckpointFieldReader(session, metadataEntryRow, type); log.debug("Block %s has %s fields", block, metadataEntryRow.getFieldCount()); if (metadataEntryRow.getFieldCount() != metadataFields) { throw new TrinoException(DELTA_LAKE_INVALID_SCHEMA, format("Expected block %s to have %d children, but found %s", block, metadataFields, metadataEntryRow.getFieldCount())); } - SqlRow formatRow = getRowField(metadataEntryRow, 3); + SqlRow formatRow = metadata.getRow("format"); if (formatRow.getFieldCount() != formatFields) { throw new TrinoException(DELTA_LAKE_INVALID_SCHEMA, format("Expected block %s to have %d children, but found %s", formatRow, formatFields, formatRow.getFieldCount())); } + + RowType.Field formatField = type.getFields().stream().filter(field -> field.getName().orElseThrow().equals("format")).collect(onlyElement()); + CheckpointFieldReader format = new CheckpointFieldReader(session, formatRow, (RowType) formatField.getType()); MetadataEntry result = new MetadataEntry( - getStringField(metadataEntryRow, 0), - getStringField(metadataEntryRow, 1), - getStringField(metadataEntryRow, 2), + metadata.getString("id"), + metadata.getString("name"), + metadata.getString("description"), new MetadataEntry.Format( - getStringField(formatRow, 0), - getMapField(formatRow, 1)), - getStringField(metadataEntryRow, 4), - getListField(metadataEntryRow, 5), - getMapField(metadataEntryRow, 6), - getLongField(metadataEntryRow, 7)); + format.getString("provider"), + format.getMap(stringMap, "options")), + metadata.getString("schemaString"), + metadata.getList(stringList, "partitionColumns"), + metadata.getMap(stringMap, "configuration"), + metadata.getLong("createdTime")); log.debug("Result: %s", result); return DeltaLakeTransactionLogEntry.metadataEntry(result); } @@ -418,6 +429,7 @@ private DeltaLakeTransactionLogEntry buildRemoveEntry(ConnectorSession session, if (block.isNull(pagePosition)) { return null; } + RowType type = (RowType) parquetFields.get("remove"); int removeFields = 3; SqlRow removeEntryRow = block.getObject(pagePosition, SqlRow.class); log.debug("Block %s has %s fields", block, removeEntryRow.getFieldCount()); @@ -425,10 +437,11 @@ private DeltaLakeTransactionLogEntry buildRemoveEntry(ConnectorSession session, throw new TrinoException(DELTA_LAKE_INVALID_SCHEMA, format("Expected block %s to have %d children, but found %s", block, removeFields, removeEntryRow.getFieldCount())); } + CheckpointFieldReader remove = new CheckpointFieldReader(session, removeEntryRow, type); RemoveFileEntry result = new RemoveFileEntry( - getStringField(removeEntryRow, 0), - getLongField(removeEntryRow, 1), - getBooleanField(removeEntryRow, 2)); + remove.getString("path"), + remove.getLong("deletionTimestamp"), + remove.getBoolean("dataChange")); log.debug("Result: %s", result); return DeltaLakeTransactionLogEntry.removeFileEntry(result); } @@ -439,36 +452,37 @@ private DeltaLakeTransactionLogEntry buildAddEntry(ConnectorSession session, Blo if (block.isNull(pagePosition)) { return null; } + RowType type = (RowType) parquetFields.get("add"); boolean deletionVectorsEnabled = isDeletionVectorEnabled(metadataEntry, protocolEntry); SqlRow addEntryRow = block.getObject(pagePosition, SqlRow.class); log.debug("Block %s has %s fields", block, addEntryRow.getFieldCount()); + CheckpointFieldReader add = new CheckpointFieldReader(session, addEntryRow, type); - String path = getStringField(addEntryRow, 0); - Map partitionValues = getMapField(addEntryRow, 1); - long size = getLongField(addEntryRow, 2); - long modificationTime = getLongField(addEntryRow, 3); - boolean dataChange = getBooleanField(addEntryRow, 4); + String path = add.getString("path"); + Map partitionValues = add.getMap(stringMap, "partitionValues"); + long size = add.getLong("size"); + long modificationTime = add.getLong("modificationTime"); + boolean dataChange = add.getBoolean("dataChange"); Optional deletionVector = Optional.empty(); - int statsFieldIndex; if (deletionVectorsEnabled) { - deletionVector = Optional.ofNullable(getRowField(addEntryRow, 5)).map(CheckpointEntryIterator::parseDeletionVectorFromParquet); - statsFieldIndex = 6; - } - else { - statsFieldIndex = 5; + deletionVector = Optional.ofNullable(add.getRow("deletionVector")) + .map(row -> { + RowType.Field deletionVectorField = type.getFields().stream().filter(field -> field.getName().orElseThrow().equals("deletionVector")).collect(onlyElement()); + return parseDeletionVectorFromParquet(session, row, (RowType) deletionVectorField.getType()); + }); } - boolean partitionValuesParsedExists = addEntryRow.getUnderlyingFieldBlock(statsFieldIndex + 1) instanceof RowBlock && // partitionValues_parsed - addEntryRow.getUnderlyingFieldBlock(statsFieldIndex + 2) instanceof RowBlock; // stats_parsed - int parsedStatsIndex = partitionValuesParsedExists ? statsFieldIndex + 1 : statsFieldIndex; - Optional parsedStats = Optional.ofNullable(getRowField(addEntryRow, parsedStatsIndex + 1)).map(this::parseStatisticsFromParquet); + Optional parsedStats = Optional.ofNullable(add.getRow("stats_parsed")).map(row -> { + RowType.Field parsedStatsField = type.getFields().stream().filter(field -> field.getName().orElseThrow().equals("stats_parsed")).collect(onlyElement()); + return parseStatisticsFromParquet(session, row, (RowType) parsedStatsField.getType()); + }); Optional stats = Optional.empty(); if (parsedStats.isEmpty()) { - stats = Optional.ofNullable(getStringField(addEntryRow, statsFieldIndex)); + stats = Optional.ofNullable(add.getString("stats")); } - Map tags = getMapField(addEntryRow, parsedStatsIndex + 2); + Map tags = add.getMap(stringMap, "tags"); AddFileEntry result = new AddFileEntry( path, partitionValues, @@ -484,32 +498,34 @@ private DeltaLakeTransactionLogEntry buildAddEntry(ConnectorSession session, Blo return DeltaLakeTransactionLogEntry.addFileEntry(result); } - private static DeletionVectorEntry parseDeletionVectorFromParquet(SqlRow row) + private DeletionVectorEntry parseDeletionVectorFromParquet(ConnectorSession session, SqlRow row, RowType type) { checkArgument(row.getFieldCount() == 5, "Deletion vector entry must have 5 fields"); - String storageType = getStringField(row, 0); - String pathOrInlineDv = getStringField(row, 1); - OptionalInt offset = getOptionalIntField(row, 2); - int sizeInBytes = getIntField(row, 3); - long cardinality = getLongField(row, 4); + CheckpointFieldReader deletionVector = new CheckpointFieldReader(session, row, type); + String storageType = deletionVector.getString("storageType"); + String pathOrInlineDv = deletionVector.getString("pathOrInlineDv"); + OptionalInt offset = deletionVector.getOptionalInt("offset"); + int sizeInBytes = deletionVector.getInt("sizeInBytes"); + long cardinality = deletionVector.getLong("cardinality"); return new DeletionVectorEntry(storageType, pathOrInlineDv, offset, sizeInBytes, cardinality); } - private DeltaLakeParquetFileStatistics parseStatisticsFromParquet(SqlRow statsRow) + private DeltaLakeParquetFileStatistics parseStatisticsFromParquet(ConnectorSession session, SqlRow statsRow, RowType type) { - long numRecords = getLongField(statsRow, 0); + CheckpointFieldReader stats = new CheckpointFieldReader(session, statsRow, type); + long numRecords = stats.getLong("numRecords"); Optional> minValues = Optional.empty(); Optional> maxValues = Optional.empty(); Optional> nullCount; if (!columnsWithMinMaxStats.isEmpty()) { - minValues = Optional.of(parseMinMax(getRowField(statsRow, 1), columnsWithMinMaxStats)); - maxValues = Optional.of(parseMinMax(getRowField(statsRow, 2), columnsWithMinMaxStats)); - nullCount = Optional.of(parseNullCount(getRowField(statsRow, 3), schema)); + minValues = Optional.of(parseMinMax(stats.getRow("minValues"), columnsWithMinMaxStats)); + maxValues = Optional.of(parseMinMax(stats.getRow("maxValues"), columnsWithMinMaxStats)); + nullCount = Optional.of(parseNullCount(stats.getRow("nullCount"), schema)); } else { - nullCount = Optional.of(parseNullCount(getRowField(statsRow, 1), schema)); + nullCount = Optional.of(parseNullCount(stats.getRow("nullCount"), schema)); } return new DeltaLakeParquetFileStatistics( @@ -592,6 +608,7 @@ private DeltaLakeTransactionLogEntry buildTxnEntry(ConnectorSession session, Blo if (block.isNull(pagePosition)) { return null; } + RowType type = (RowType) parquetFields.get("txn"); int txnFields = 3; SqlRow txnEntryRow = block.getObject(pagePosition, SqlRow.class); log.debug("Block %s has %s fields", block, txnEntryRow.getFieldCount()); @@ -599,90 +616,21 @@ private DeltaLakeTransactionLogEntry buildTxnEntry(ConnectorSession session, Blo throw new TrinoException(DELTA_LAKE_INVALID_SCHEMA, format("Expected block %s to have %d children, but found %s", block, txnFields, txnEntryRow.getFieldCount())); } + CheckpointFieldReader txn = new CheckpointFieldReader(session, txnEntryRow, type); TransactionEntry result = new TransactionEntry( - getStringField(txnEntryRow, 0), - getLongField(txnEntryRow, 1), - getLongField(txnEntryRow, 2)); + txn.getString("appId"), + txn.getLong("version"), + txn.getLong("lastUpdated")); log.debug("Result: %s", result); return DeltaLakeTransactionLogEntry.transactionEntry(result); } - @Nullable - private static SqlRow getRowField(SqlRow row, int field) - { - RowBlock valueBlock = (RowBlock) row.getUnderlyingFieldBlock(field); - int index = row.getUnderlyingFieldPosition(field); - if (valueBlock.isNull(index)) { - return null; - } - return valueBlock.getRow(index); - } - - @Nullable - private static String getStringField(SqlRow row, int field) - { - VariableWidthBlock valueBlock = (VariableWidthBlock) row.getUnderlyingFieldBlock(field); - int index = row.getUnderlyingFieldPosition(field); - if (valueBlock.isNull(index)) { - return null; - } - return valueBlock.getSlice(index).toStringUtf8(); - } - private static long getLongField(SqlRow row, int field) { LongArrayBlock valueBlock = (LongArrayBlock) row.getUnderlyingFieldBlock(field); return valueBlock.getLong(row.getUnderlyingFieldPosition(field)); } - private static int getIntField(SqlRow row, int field) - { - IntArrayBlock valueBlock = (IntArrayBlock) row.getUnderlyingFieldBlock(field); - return valueBlock.getInt(row.getUnderlyingFieldPosition(field)); - } - - private static OptionalInt getOptionalIntField(SqlRow row, int field) - { - IntArrayBlock valueBlock = (IntArrayBlock) row.getUnderlyingFieldBlock(field); - int index = row.getUnderlyingFieldPosition(field); - if (valueBlock.isNull(index)) { - return OptionalInt.empty(); - } - return OptionalInt.of(valueBlock.getInt(index)); - } - - private static boolean getBooleanField(SqlRow row, int field) - { - ByteArrayBlock valueBlock = (ByteArrayBlock) row.getUnderlyingFieldBlock(field); - return valueBlock.getByte(row.getUnderlyingFieldPosition(field)) != 0; - } - - @SuppressWarnings("unchecked") - private Map getMapField(SqlRow row, int field) - { - MapBlock valueBlock = (MapBlock) row.getUnderlyingFieldBlock(field); - return (Map) stringMap.getObjectValue(session, valueBlock, row.getUnderlyingFieldPosition(field)); - } - - @SuppressWarnings("unchecked") - private List getListField(SqlRow row, int field) - { - ArrayBlock valueBlock = (ArrayBlock) row.getUnderlyingFieldBlock(field); - return (List) stringList.getObjectValue(session, valueBlock, row.getUnderlyingFieldPosition(field)); - } - - @SuppressWarnings("unchecked") - private Optional> getOptionalSetField(SqlRow row, int field) - { - ArrayBlock valueBlock = (ArrayBlock) row.getUnderlyingFieldBlock(field); - int index = row.getUnderlyingFieldPosition(field); - if (valueBlock.isNull(index)) { - return Optional.empty(); - } - List list = (List) stringList.getObjectValue(session, valueBlock, index); - return Optional.of(ImmutableSet.copyOf(list)); - } - @Override protected DeltaLakeTransactionLogEntry computeNext() { @@ -692,24 +640,14 @@ protected DeltaLakeTransactionLogEntry computeNext() if (!nextEntries.isEmpty()) { return nextEntries.remove(); } - try { - pageSource.close(); - } - catch (IOException e) { - throw new UncheckedIOException(e); - } + pageSource.close(); return endOfData(); } private boolean tryAdvancePage() { if (pageSource.isFinished()) { - try { - pageSource.close(); - } - catch (IOException ioe) { - throw new UncheckedIOException(ioe); - } + pageSource.close(); return false; } page = pageSource.getNextPage(); diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointFieldReader.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointFieldReader.java new file mode 100644 index 000000000000..d306e8edc16a --- /dev/null +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointFieldReader.java @@ -0,0 +1,172 @@ +/* + * 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 io.trino.plugin.deltalake.transactionlog.checkpoint; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import io.trino.spi.block.ArrayBlock; +import io.trino.spi.block.ByteArrayBlock; +import io.trino.spi.block.IntArrayBlock; +import io.trino.spi.block.LongArrayBlock; +import io.trino.spi.block.MapBlock; +import io.trino.spi.block.RowBlock; +import io.trino.spi.block.SqlRow; +import io.trino.spi.block.VariableWidthBlock; +import io.trino.spi.connector.ConnectorSession; +import io.trino.spi.type.ArrayType; +import io.trino.spi.type.MapType; +import io.trino.spi.type.RowType; +import jakarta.annotation.Nullable; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; +import static java.util.Objects.requireNonNull; + +public class CheckpointFieldReader +{ + private final ConnectorSession session; + private final SqlRow row; + private final Map fieldNameToIndex; + + public CheckpointFieldReader(ConnectorSession session, SqlRow row, RowType type) + { + this.session = requireNonNull(session, "session is null"); + this.row = requireNonNull(row, "row is null"); + checkArgument(row.getFieldCount() == type.getFields().size(), "row and type sizes don't match"); + Map fieldNames = new HashMap<>(); + for (int i = 0; i < type.getFields().size(); i++) { + String fieldName = type.getFields().get(i).getName().orElseThrow(); + checkState(!fieldNames.containsKey(fieldName), "Duplicated field '%s' exists in %s", fieldName, type); + fieldNames.put(fieldName, i); + } + this.fieldNameToIndex = ImmutableMap.copyOf(fieldNames); + } + + public boolean getBoolean(String fieldName) + { + int field = requireField(fieldName); + ByteArrayBlock valueBlock = (ByteArrayBlock) row.getUnderlyingFieldBlock(field); + return valueBlock.getByte(row.getUnderlyingFieldPosition(field)) != 0; + } + + public int getInt(String fieldName) + { + int field = requireField(fieldName); + IntArrayBlock valueBlock = (IntArrayBlock) row.getUnderlyingFieldBlock(field); + return valueBlock.getInt(row.getUnderlyingFieldPosition(field)); + } + + public OptionalInt getOptionalInt(String fieldName) + { + OptionalInt index = findField(fieldName); + if (index.isEmpty()) { + return OptionalInt.empty(); + } + + IntArrayBlock valueBlock = (IntArrayBlock) row.getUnderlyingFieldBlock(index.getAsInt()); + int position = row.getUnderlyingFieldPosition(index.getAsInt()); + if (valueBlock.isNull(position)) { + return OptionalInt.empty(); + } + return OptionalInt.of(valueBlock.getInt(position)); + } + + public long getLong(String fieldName) + { + int field = requireField(fieldName); + LongArrayBlock valueBlock = (LongArrayBlock) row.getUnderlyingFieldBlock(field); + return valueBlock.getLong(row.getUnderlyingFieldPosition(field)); + } + + @Nullable + public String getString(String fieldName) + { + int field = requireField(fieldName); + VariableWidthBlock valueBlock = (VariableWidthBlock) row.getUnderlyingFieldBlock(field); + int index = row.getUnderlyingFieldPosition(field); + if (valueBlock.isNull(index)) { + return null; + } + return valueBlock.getSlice(index).toStringUtf8(); + } + + @SuppressWarnings("unchecked") + public List getList(ArrayType stringList, String fieldName) + { + int field = requireField(fieldName); + ArrayBlock valueBlock = (ArrayBlock) row.getUnderlyingFieldBlock(field); + return (List) stringList.getObjectValue(session, valueBlock, row.getUnderlyingFieldPosition(field)); + } + + @SuppressWarnings("unchecked") + public Optional> getOptionalSet(ArrayType stringList, String fieldName) + { + OptionalInt index = findField(fieldName); + if (index.isEmpty()) { + return Optional.empty(); + } + ArrayBlock valueBlock = (ArrayBlock) row.getUnderlyingFieldBlock(index.getAsInt()); + int position = row.getUnderlyingFieldPosition(index.getAsInt()); + if (valueBlock.isNull(position)) { + return Optional.empty(); + } + List list = (List) stringList.getObjectValue(session, valueBlock, position); + return Optional.of(ImmutableSet.copyOf(list)); + } + + @SuppressWarnings("unchecked") + public Map getMap(MapType stringMap, String fieldName) + { + int field = requireField(fieldName); + MapBlock valueBlock = (MapBlock) row.getUnderlyingFieldBlock(field); + return (Map) stringMap.getObjectValue(session, valueBlock, row.getUnderlyingFieldPosition(field)); + } + + @Nullable + public SqlRow getRow(String fieldName) + { + OptionalInt index = findField(fieldName); + if (index.isEmpty()) { + return null; + } + RowBlock valueBlock = (RowBlock) row.getUnderlyingFieldBlock(index.getAsInt()); + int position = row.getUnderlyingFieldPosition(index.getAsInt()); + if (valueBlock.isNull(position)) { + return null; + } + return valueBlock.getRow(position); + } + + private int requireField(String fieldName) + { + return findField(fieldName) + .orElseThrow(() -> new IllegalArgumentException("Field '%s' doesn't exist in %s".formatted(fieldName, fieldNameToIndex.keySet()))); + } + + private OptionalInt findField(String fieldName) + { + Integer index = fieldNameToIndex.get(fieldName); + if (index == null) { + return OptionalInt.empty(); + } + return OptionalInt.of(index); + } +} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/parquet/ParquetPageSource.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/parquet/ParquetPageSource.java index 283eab238afa..c3aac320db62 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/parquet/ParquetPageSource.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/parquet/ParquetPageSource.java @@ -14,6 +14,7 @@ package io.trino.plugin.hive.parquet; import com.google.common.collect.ImmutableList; +import io.trino.parquet.Column; import io.trino.parquet.ParquetCorruptionException; import io.trino.parquet.ParquetDataSourceId; import io.trino.parquet.reader.ParquetReader; @@ -58,6 +59,11 @@ private ParquetPageSource( this.isColumnAdaptationRequired = isColumnAdaptationRequired(columnAdaptations); } + public List getColumnFields() + { + return parquetReader.getColumnFields(); + } + @Override public long getCompletedBytes() { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/parquet/ParquetPageSourceFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/parquet/ParquetPageSourceFactory.java index 6c2ce5cde12a..f7919c4da1bf 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/parquet/ParquetPageSourceFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/parquet/ParquetPageSourceFactory.java @@ -23,6 +23,7 @@ import io.trino.filesystem.TrinoInputFile; import io.trino.memory.context.AggregatedMemoryContext; import io.trino.parquet.BloomFilterStore; +import io.trino.parquet.Column; import io.trino.parquet.Field; import io.trino.parquet.ParquetCorruptionException; import io.trino.parquet.ParquetDataSource; @@ -473,7 +474,7 @@ public static TupleDomain getParquetTupleDomain( public interface ParquetReaderProvider { - ParquetReader createParquetReader(List fields) + ParquetReader createParquetReader(List fields) throws IOException; } @@ -486,7 +487,7 @@ public static ConnectorPageSource createParquetPageSource( throws IOException { ParquetPageSource.Builder pageSourceBuilder = ParquetPageSource.builder(); - ImmutableList.Builder parquetColumnFieldsBuilder = ImmutableList.builder(); + ImmutableList.Builder parquetColumnFieldsBuilder = ImmutableList.builder(); int sourceChannel = 0; for (HiveColumnHandle column : baseColumns) { if (column == PARQUET_ROW_INDEX_COLUMN) { @@ -505,7 +506,7 @@ public static ConnectorPageSource createParquetPageSource( pageSourceBuilder.addNullColumn(column.getBaseType()); continue; } - parquetColumnFieldsBuilder.add(field.get()); + parquetColumnFieldsBuilder.add(new Column(columnName, field.get())); pageSourceBuilder.addSourceColumn(sourceChannel); sourceChannel++; } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergPageSourceProvider.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergPageSourceProvider.java index 1342adb698af..cd49d7f11366 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergPageSourceProvider.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergPageSourceProvider.java @@ -39,6 +39,7 @@ import io.trino.orc.TupleDomainOrcPredicate; import io.trino.orc.TupleDomainOrcPredicate.TupleDomainOrcPredicateBuilder; import io.trino.parquet.BloomFilterStore; +import io.trino.parquet.Column; import io.trino.parquet.Field; import io.trino.parquet.ParquetCorruptionException; import io.trino.parquet.ParquetDataSource; @@ -985,7 +986,7 @@ private static ReaderPageSourceWithRowPositions createParquetPageSource( ParquetPageSource.Builder pageSourceBuilder = ParquetPageSource.builder(); int parquetSourceChannel = 0; - ImmutableList.Builder parquetColumnFieldsBuilder = ImmutableList.builder(); + ImmutableList.Builder parquetColumnFieldsBuilder = ImmutableList.builder(); for (int columnIndex = 0; columnIndex < readBaseColumns.size(); columnIndex++) { IcebergColumnHandle column = readBaseColumns.get(columnIndex); if (column.isIsDeletedColumn()) { @@ -1030,7 +1031,7 @@ else if (column.getId() == TRINO_MERGE_PARTITION_DATA) { pageSourceBuilder.addNullColumn(trinoType); continue; } - parquetColumnFieldsBuilder.add(field.get()); + parquetColumnFieldsBuilder.add(new Column(parquetField.getName(), field.get())); pageSourceBuilder.addSourceColumn(parquetSourceChannel); parquetSourceChannel++; } From e9ba634097147e97bdd19a84f9150685ae760c59 Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Fri, 17 Nov 2023 18:50:27 +0900 Subject: [PATCH 289/587] Remove unused fields from BigQueryColumnHandle --- .../plugin/bigquery/BigQueryColumnHandle.java | 27 +------------------ .../plugin/bigquery/BigQueryPseudoColumn.java | 2 -- .../io/trino/plugin/bigquery/Conversions.java | 2 -- 3 files changed, 1 insertion(+), 30 deletions(-) diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryColumnHandle.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryColumnHandle.java index 7fef53a4cc8d..b99188dc1be5 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryColumnHandle.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryColumnHandle.java @@ -29,7 +29,6 @@ import static com.google.common.base.MoreObjects.toStringHelper; import static io.airlift.slice.SizeOf.estimatedSizeOf; import static io.airlift.slice.SizeOf.instanceSize; -import static io.airlift.slice.SizeOf.sizeOf; import static java.util.Objects.requireNonNull; public class BigQueryColumnHandle @@ -42,8 +41,6 @@ public class BigQueryColumnHandle private final StandardSQLTypeName bigqueryType; private final boolean isPushdownSupported; private final Field.Mode mode; - private final Long precision; - private final Long scale; private final List subColumns; private final String description; private final boolean hidden; @@ -55,8 +52,6 @@ public BigQueryColumnHandle( @JsonProperty("bigqueryType") StandardSQLTypeName bigqueryType, @JsonProperty("isPushdownSupported") boolean isPushdownSupported, @JsonProperty("mode") Field.Mode mode, - @JsonProperty("precision") Long precision, - @JsonProperty("scale") Long scale, @JsonProperty("subColumns") List subColumns, @JsonProperty("description") String description, @JsonProperty("hidden") boolean hidden) @@ -66,8 +61,6 @@ public BigQueryColumnHandle( this.bigqueryType = requireNonNull(bigqueryType, "bigqueryType is null"); this.isPushdownSupported = isPushdownSupported; this.mode = requireNonNull(mode, "Field mode cannot be null"); - this.precision = precision; - this.scale = scale; this.subColumns = ImmutableList.copyOf(requireNonNull(subColumns, "subColumns is null")); this.description = description; this.hidden = hidden; @@ -103,18 +96,6 @@ public Field.Mode getMode() return mode; } - @JsonProperty - public Long getPrecision() - { - return precision; - } - - @JsonProperty - public Long getScale() - { - return scale; - } - @JsonProperty public List getSubColumns() { @@ -159,8 +140,6 @@ public boolean equals(Object o) Objects.equals(bigqueryType, that.bigqueryType) && Objects.equals(isPushdownSupported, that.isPushdownSupported) && Objects.equals(mode, that.mode) && - Objects.equals(precision, that.precision) && - Objects.equals(scale, that.scale) && Objects.equals(subColumns, that.subColumns) && Objects.equals(description, that.description); } @@ -168,7 +147,7 @@ public boolean equals(Object o) @Override public int hashCode() { - return Objects.hash(name, trinoType, bigqueryType, isPushdownSupported, mode, precision, scale, subColumns, description); + return Objects.hash(name, trinoType, bigqueryType, isPushdownSupported, mode, subColumns, description); } @Override @@ -180,8 +159,6 @@ public String toString() .add("bigqueryType", bigqueryType) .add("isPushdownSupported", isPushdownSupported) .add("mode", mode) - .add("precision", precision) - .add("scale", scale) .add("subColumns", subColumns) .add("description", description) .toString(); @@ -191,8 +168,6 @@ public long getRetainedSizeInBytes() { return INSTANCE_SIZE + estimatedSizeOf(name) - + sizeOf(precision) - + sizeOf(scale) + estimatedSizeOf(subColumns, BigQueryColumnHandle::getRetainedSizeInBytes) + estimatedSizeOf(description); } diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryPseudoColumn.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryPseudoColumn.java index 81a78dbf0094..db7078f59d31 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryPseudoColumn.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryPseudoColumn.java @@ -58,8 +58,6 @@ public BigQueryColumnHandle getColumnHandle() bigqueryType, true, Field.Mode.REQUIRED, - null, - null, ImmutableList.of(), null, true); diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/Conversions.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/Conversions.java index 1e53944396a1..f44a1be68c00 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/Conversions.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/Conversions.java @@ -45,8 +45,6 @@ public static BigQueryColumnHandle toColumnHandle(Field field) field.getType().getStandardType(), columnMapping.isPushdownSupported(), getMode(field), - field.getPrecision(), - field.getScale(), subColumns, field.getDescription(), false); From a4efe44bae64c8540892f5deee8c4a51f9d1a0ef Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Mon, 28 Nov 2022 20:17:53 +0900 Subject: [PATCH 290/587] Merge Conversions into BigQueryType --- .../trino/plugin/bigquery/BigQueryClient.java | 4 +- .../trino/plugin/bigquery/BigQueryType.java | 46 ++++++++++++ .../io/trino/plugin/bigquery/Conversions.java | 73 ------------------- .../io/trino/plugin/bigquery/ptf/Query.java | 4 +- 4 files changed, 50 insertions(+), 77 deletions(-) delete mode 100644 plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/Conversions.java diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryClient.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryClient.java index 0c488460164d..c1d70154fdc7 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryClient.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryClient.java @@ -397,8 +397,8 @@ public static List buildColumnHandles(TableInfo tableInfo) } return schema.getFields() .stream() - .filter(Conversions::isSupportedType) - .map(Conversions::toColumnHandle) + .filter(BigQueryType::isSupportedType) + .map(BigQueryType::toColumnHandle) .collect(toImmutableList()); } diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryType.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryType.java index 791f9b696122..f6de3ebd4852 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryType.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryType.java @@ -15,6 +15,7 @@ import com.google.cloud.bigquery.Field; import com.google.cloud.bigquery.FieldList; +import com.google.cloud.bigquery.LegacySQLTypeName; import com.google.cloud.bigquery.StandardSQLTypeName; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; @@ -48,10 +49,13 @@ import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.Base64; +import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import static com.google.cloud.bigquery.Field.Mode.REPEATED; +import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Verify.verify; import static io.trino.plugin.bigquery.BigQueryMetadata.DEFAULT_NUMERIC_TYPE_PRECISION; @@ -362,4 +366,46 @@ private static Optional convertToTrinoType(Field field) return Optional.empty(); } } + + public static BigQueryColumnHandle toColumnHandle(Field field) + { + FieldList subFields = field.getSubFields(); + List subColumns = subFields == null ? + Collections.emptyList() : + subFields.stream() + .filter(BigQueryType::isSupportedType) + .map(BigQueryType::toColumnHandle) + .collect(Collectors.toList()); + ColumnMapping columnMapping = toTrinoType(field).orElseThrow(() -> new IllegalArgumentException("Unsupported type: " + field)); + return new BigQueryColumnHandle( + field.getName(), + columnMapping.type(), + field.getType().getStandardType(), + columnMapping.isPushdownSupported(), + getMode(field), + subColumns, + field.getDescription(), + false); + } + + public static boolean isSupportedType(Field field) + { + LegacySQLTypeName type = field.getType(); + if (type == LegacySQLTypeName.BIGNUMERIC) { + // Skip BIGNUMERIC without parameters because the precision (77) and scale (38) is too large + if (field.getPrecision() == null && field.getScale() == null) { + return false; + } + if (field.getPrecision() != null && field.getPrecision() > Decimals.MAX_PRECISION) { + return false; + } + } + + return toTrinoType(field).isPresent(); + } + + private static Field.Mode getMode(Field field) + { + return firstNonNull(field.getMode(), Field.Mode.NULLABLE); + } } diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/Conversions.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/Conversions.java deleted file mode 100644 index f44a1be68c00..000000000000 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/Conversions.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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 io.trino.plugin.bigquery; - -import com.google.cloud.bigquery.Field; -import com.google.cloud.bigquery.FieldList; -import com.google.cloud.bigquery.LegacySQLTypeName; -import io.trino.spi.type.Decimals; - -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -import static com.google.common.base.MoreObjects.firstNonNull; -import static io.trino.plugin.bigquery.BigQueryType.toTrinoType; - -public final class Conversions -{ - private Conversions() {} - - public static BigQueryColumnHandle toColumnHandle(Field field) - { - FieldList subFields = field.getSubFields(); - List subColumns = subFields == null ? - Collections.emptyList() : - subFields.stream() - .filter(Conversions::isSupportedType) - .map(Conversions::toColumnHandle) - .collect(Collectors.toList()); - ColumnMapping columnMapping = toTrinoType(field).orElseThrow(() -> new IllegalArgumentException("Unsupported type: " + field)); - return new BigQueryColumnHandle( - field.getName(), - columnMapping.type(), - field.getType().getStandardType(), - columnMapping.isPushdownSupported(), - getMode(field), - subColumns, - field.getDescription(), - false); - } - - public static boolean isSupportedType(Field field) - { - LegacySQLTypeName type = field.getType(); - if (type == LegacySQLTypeName.BIGNUMERIC) { - // Skip BIGNUMERIC without parameters because the precision (77) and scale (38) is too large - if (field.getPrecision() == null && field.getScale() == null) { - return false; - } - if (field.getPrecision() != null && field.getPrecision() > Decimals.MAX_PRECISION) { - return false; - } - } - - return toTrinoType(field).isPresent(); - } - - private static Field.Mode getMode(Field field) - { - return firstNonNull(field.getMode(), Field.Mode.NULLABLE); - } -} diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/ptf/Query.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/ptf/Query.java index fe2990f9ae43..c2b40eda2649 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/ptf/Query.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/ptf/Query.java @@ -45,8 +45,8 @@ import java.util.Optional; import static com.google.common.collect.Iterables.getOnlyElement; -import static io.trino.plugin.bigquery.Conversions.isSupportedType; -import static io.trino.plugin.bigquery.Conversions.toColumnHandle; +import static io.trino.plugin.bigquery.BigQueryType.isSupportedType; +import static io.trino.plugin.bigquery.BigQueryType.toColumnHandle; import static io.trino.spi.function.table.ReturnTypeSpecification.GenericTable.GENERIC_TABLE; import static io.trino.spi.type.VarcharType.VARCHAR; import static java.util.Objects.requireNonNull; From 6f7975579ec4a758bbdc1317ee3b079509f645cb Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Mon, 28 Nov 2022 20:33:53 +0900 Subject: [PATCH 291/587] Make BigQuery type manager singleton --- .../trino/plugin/bigquery/BigQueryClient.java | 9 ++++-- .../bigquery/BigQueryClientFactory.java | 5 +++- .../bigquery/BigQueryConnectorModule.java | 1 + .../bigquery/BigQueryFilterQueryBuilder.java | 2 +- .../plugin/bigquery/BigQueryMetadata.java | 20 ++++++------- .../bigquery/BigQueryQueryPageSource.java | 2 +- .../BigQueryStorageAvroPageSource.java | 2 +- ...ueryType.java => BigQueryTypeManager.java} | 30 +++++++++---------- .../plugin/bigquery/BigQueryTypeUtils.java | 4 +-- .../DefaultBigQueryMetadataFactory.java | 6 ++-- .../io/trino/plugin/bigquery/ptf/Query.java | 17 ++++++----- .../plugin/bigquery/TestBigQueryType.java | 16 +++++----- 12 files changed, 62 insertions(+), 52 deletions(-) rename plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/{BigQueryType.java => BigQueryTypeManager.java} (94%) diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryClient.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryClient.java index c1d70154fdc7..df8201fed086 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryClient.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryClient.java @@ -85,6 +85,7 @@ public class BigQueryClient private final BigQuery bigQuery; private final BigQueryLabelFactory labelFactory; + private final BigQueryTypeManager typeManager; private final ViewMaterializationCache materializationCache; private final boolean caseInsensitiveNameMatching; private final LoadingCache> remoteDatasetCache; @@ -93,6 +94,7 @@ public class BigQueryClient public BigQueryClient( BigQuery bigQuery, BigQueryLabelFactory labelFactory, + BigQueryTypeManager typeManager, boolean caseInsensitiveNameMatching, ViewMaterializationCache materializationCache, Duration metadataCacheTtl, @@ -100,6 +102,7 @@ public BigQueryClient( { this.bigQuery = requireNonNull(bigQuery, "bigQuery is null"); this.labelFactory = requireNonNull(labelFactory, "labelFactory is null"); + this.typeManager = requireNonNull(typeManager, "typeManager is null"); this.materializationCache = requireNonNull(materializationCache, "materializationCache is null"); this.caseInsensitiveNameMatching = caseInsensitiveNameMatching; this.remoteDatasetCache = EvictableCacheBuilder.newBuilder() @@ -388,7 +391,7 @@ public List getColumns(BigQueryTableHandle tableHandle) return buildColumnHandles(tableInfo); } - public static List buildColumnHandles(TableInfo tableInfo) + public List buildColumnHandles(TableInfo tableInfo) { Schema schema = tableInfo.getDefinition().getSchema(); if (schema == null) { @@ -397,8 +400,8 @@ public static List buildColumnHandles(TableInfo tableInfo) } return schema.getFields() .stream() - .filter(BigQueryType::isSupportedType) - .map(BigQueryType::toColumnHandle) + .filter(typeManager::isSupportedType) + .map(typeManager::toColumnHandle) .collect(toImmutableList()); } diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryClientFactory.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryClientFactory.java index 882d44be341f..2fc91ea746da 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryClientFactory.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryClientFactory.java @@ -32,6 +32,7 @@ public class BigQueryClientFactory { private final IdentityCacheMapping identityCacheMapping; + private final BigQueryTypeManager typeManager; private final Optional projectId; private final boolean caseInsensitiveNameMatching; private final ViewMaterializationCache materializationCache; @@ -44,12 +45,14 @@ public class BigQueryClientFactory @Inject public BigQueryClientFactory( IdentityCacheMapping identityCacheMapping, + BigQueryTypeManager typeManager, BigQueryConfig bigQueryConfig, ViewMaterializationCache materializationCache, BigQueryLabelFactory labelFactory, Set optionsConfigurers) { this.identityCacheMapping = requireNonNull(identityCacheMapping, "identityCacheMapping is null"); + this.typeManager = requireNonNull(typeManager, "typeManager is null"); requireNonNull(bigQueryConfig, "bigQueryConfig is null"); this.projectId = bigQueryConfig.getProjectId(); this.caseInsensitiveNameMatching = bigQueryConfig.isCaseInsensitiveNameMatching(); @@ -72,7 +75,7 @@ public BigQueryClient create(ConnectorSession session) protected BigQueryClient createBigQueryClient(ConnectorSession session) { - return new BigQueryClient(createBigQuery(session), labelFactory, caseInsensitiveNameMatching, materializationCache, metadataCacheTtl, projectId); + return new BigQueryClient(createBigQuery(session), labelFactory, typeManager, caseInsensitiveNameMatching, materializationCache, metadataCacheTtl, projectId); } protected BigQuery createBigQuery(ConnectorSession session) diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryConnectorModule.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryConnectorModule.java index 733fb33717f9..bc6211493a05 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryConnectorModule.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryConnectorModule.java @@ -69,6 +69,7 @@ protected void setup(Binder binder) // BigQuery related binder.bind(BigQueryReadClientFactory.class).in(Scopes.SINGLETON); binder.bind(BigQueryClientFactory.class).in(Scopes.SINGLETON); + binder.bind(BigQueryTypeManager.class).in(Scopes.SINGLETON); // Connector implementation binder.bind(BigQueryConnector.class).in(Scopes.SINGLETON); diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryFilterQueryBuilder.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryFilterQueryBuilder.java index 53426e0be820..457f54f330e2 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryFilterQueryBuilder.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryFilterQueryBuilder.java @@ -26,7 +26,7 @@ import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Iterables.getOnlyElement; -import static io.trino.plugin.bigquery.BigQueryType.convertToString; +import static io.trino.plugin.bigquery.BigQueryTypeManager.convertToString; import static io.trino.plugin.bigquery.BigQueryUtil.quote; import static io.trino.plugin.bigquery.BigQueryUtil.toBigQueryColumnName; import static java.util.stream.Collectors.joining; diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryMetadata.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryMetadata.java index c035a6677de2..295bbc2f9f8f 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryMetadata.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryMetadata.java @@ -99,7 +99,6 @@ import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.util.concurrent.Futures.allAsList; import static io.trino.plugin.base.TemporaryTables.generateTemporaryTableName; -import static io.trino.plugin.bigquery.BigQueryClient.buildColumnHandles; import static io.trino.plugin.bigquery.BigQueryErrorCode.BIGQUERY_FAILED_TO_EXECUTE_QUERY; import static io.trino.plugin.bigquery.BigQueryErrorCode.BIGQUERY_LISTING_DATASET_ERROR; import static io.trino.plugin.bigquery.BigQueryErrorCode.BIGQUERY_UNSUPPORTED_OPERATION; @@ -107,7 +106,6 @@ import static io.trino.plugin.bigquery.BigQueryPseudoColumn.PARTITION_TIME; import static io.trino.plugin.bigquery.BigQueryTableHandle.BigQueryPartitionType.INGESTION; import static io.trino.plugin.bigquery.BigQueryTableHandle.getPartitionType; -import static io.trino.plugin.bigquery.BigQueryType.toField; import static io.trino.plugin.bigquery.BigQueryUtil.isWildcardTable; import static io.trino.plugin.bigquery.BigQueryUtil.quote; import static io.trino.plugin.bigquery.BigQueryUtil.quoted; @@ -128,12 +126,14 @@ public class BigQueryMetadata private static final String VIEW_DEFINITION_SYSTEM_TABLE_SUFFIX = "$view_definition"; private final BigQueryClientFactory bigQueryClientFactory; + private final BigQueryTypeManager typeManager; private final AtomicReference rollbackAction = new AtomicReference<>(); private final ListeningExecutorService executorService; - public BigQueryMetadata(BigQueryClientFactory bigQueryClientFactory, ListeningExecutorService executorService) + public BigQueryMetadata(BigQueryClientFactory bigQueryClientFactory, BigQueryTypeManager typeManager, ListeningExecutorService executorService) { this.bigQueryClientFactory = requireNonNull(bigQueryClientFactory, "bigQueryClientFactory is null"); + this.typeManager = requireNonNull(typeManager, "typeManager is null"); this.executorService = requireNonNull(executorService, "executorService is null"); } @@ -245,7 +245,7 @@ public ConnectorTableHandle getTableHandle(ConnectorSession session, SchemaTable } ImmutableList.Builder columns = ImmutableList.builder(); - columns.addAll(buildColumnHandles(tableInfo.get())); + columns.addAll(client.buildColumnHandles(tableInfo.get())); Optional partitionType = getPartitionType(tableInfo.get().getDefinition()); if (partitionType.isPresent() && partitionType.get() == INGESTION) { columns.add(PARTITION_DATE.getColumnHandle()); @@ -485,8 +485,8 @@ private BigQueryOutputTableHandle createTable(ConnectorSession session, Connecto ImmutableList.Builder columnsNames = ImmutableList.builderWithExpectedSize(columnSize); ImmutableList.Builder columnsTypes = ImmutableList.builderWithExpectedSize(columnSize); for (ColumnMetadata column : tableMetadata.getColumns()) { - fields.add(toField(column.getName(), column.getType(), column.getComment())); - tempFields.add(toField(column.getName(), column.getType(), column.getComment())); + fields.add(typeManager.toField(column.getName(), column.getType(), column.getComment())); + tempFields.add(typeManager.toField(column.getName(), column.getType(), column.getComment())); columnsNames.add(column.getName()); columnsTypes.add(column.getType()); } @@ -509,7 +509,7 @@ private BigQueryOutputTableHandle createTable(ConnectorSession session, Connecto closer.register(() -> bigQueryClientFactory.create(session).dropTable(tableId)); Optional temporaryTableName = pageSinkIdColumn.map(column -> { - tempFields.add(toField(column.getName(), column.getType(), column.getComment())); + tempFields.add(typeManager.toField(column.getName(), column.getType(), column.getComment())); String tempTableName = generateTemporaryTableName(session); TableId tempTableId = createTable(client, projectId, remoteSchemaName, tempTableName, tempFields.build(), tableMetadata.getComment()); closer.register(() -> bigQueryClientFactory.create(session).dropTable(tempTableId)); @@ -590,12 +590,12 @@ public ConnectorInsertTableHandle beginInsert(ConnectorSession session, Connecto for (ColumnHandle columnHandle : columns) { BigQueryColumnHandle column = (BigQueryColumnHandle) columnHandle; - tempFields.add(toField(column.getName(), column.getTrinoType(), column.getColumnMetadata().getComment())); + tempFields.add(typeManager.toField(column.getName(), column.getTrinoType(), column.getColumnMetadata().getComment())); columnNames.add(column.getName()); columnTypes.add(column.getTrinoType()); } ColumnMetadata pageSinkIdColumn = buildPageSinkIdColumn(columnNames.build()); - tempFields.add(toField(pageSinkIdColumn.getName(), pageSinkIdColumn.getType(), pageSinkIdColumn.getComment())); + tempFields.add(typeManager.toField(pageSinkIdColumn.getName(), pageSinkIdColumn.getType(), pageSinkIdColumn.getComment())); BigQueryClient client = bigQueryClientFactory.create(session); String projectId = table.asPlainTable().getRemoteTableName().getProjectId(); @@ -631,7 +631,7 @@ private Optional finishInsert( targetTable.getProjectId(), targetTable.getDatasetName(), generateTemporaryTableName(session)); - createTable(client, pageSinkTable.getProjectId(), pageSinkTable.getDatasetName(), pageSinkTable.getTableName(), ImmutableList.of(toField(pageSinkIdColumnName, TRINO_PAGE_SINK_ID_COLUMN_TYPE, null)), Optional.empty()); + createTable(client, pageSinkTable.getProjectId(), pageSinkTable.getDatasetName(), pageSinkTable.getTableName(), ImmutableList.of(typeManager.toField(pageSinkIdColumnName, TRINO_PAGE_SINK_ID_COLUMN_TYPE, null)), Optional.empty()); closer.register(() -> bigQueryClientFactory.create(session).dropTable(pageSinkTable.toTableId())); InsertAllRequest.Builder batch = InsertAllRequest.newBuilder(pageSinkTable.toTableId()); diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryQueryPageSource.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryQueryPageSource.java index 5ee3f3065095..9504a370bbe2 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryQueryPageSource.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryQueryPageSource.java @@ -49,7 +49,7 @@ import static com.google.common.base.Verify.verify; import static io.airlift.slice.Slices.utf8Slice; import static io.trino.plugin.bigquery.BigQueryClient.selectSql; -import static io.trino.plugin.bigquery.BigQueryType.toTrinoTimestamp; +import static io.trino.plugin.bigquery.BigQueryTypeManager.toTrinoTimestamp; import static io.trino.spi.StandardErrorCode.GENERIC_INTERNAL_ERROR; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.DateType.DATE; diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryStorageAvroPageSource.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryStorageAvroPageSource.java index f4bede11819d..41572ec1a2a7 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryStorageAvroPageSource.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryStorageAvroPageSource.java @@ -55,7 +55,7 @@ import static com.google.common.base.Verify.verify; import static com.google.common.collect.ImmutableList.toImmutableList; import static io.airlift.slice.Slices.utf8Slice; -import static io.trino.plugin.bigquery.BigQueryType.toTrinoTimestamp; +import static io.trino.plugin.bigquery.BigQueryTypeManager.toTrinoTimestamp; import static io.trino.plugin.bigquery.BigQueryUtil.toBigQueryColumnName; import static io.trino.spi.StandardErrorCode.GENERIC_INTERNAL_ERROR; import static io.trino.spi.type.BigintType.BIGINT; diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryType.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryTypeManager.java similarity index 94% rename from plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryType.java rename to plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryTypeManager.java index f6de3ebd4852..e90ff84c091c 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryType.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryTypeManager.java @@ -80,10 +80,8 @@ import static java.time.ZoneOffset.UTC; import static java.util.stream.Collectors.toList; -public final class BigQueryType +public final class BigQueryTypeManager { - private BigQueryType() {} - private static final int[] NANO_FACTOR = { -1, // 0, no need to multiply 100_000_000, // 1 digit after the dot @@ -99,10 +97,10 @@ private BigQueryType() {} private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("''HH:mm:ss.SSSSSS''"); private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss.SSSSSS").withZone(UTC); - private static RowType.Field toRawTypeField(String name, Field field) + private RowType.Field toRawTypeField(String name, Field field) { - ColumnMapping columnMapping = convertToTrinoType(field).orElseThrow(() -> new IllegalArgumentException("Unsupported column " + field)); - return RowType.field(name, field.getMode() == REPEATED ? new ArrayType(columnMapping.type()) : columnMapping.type()); + Type trinoType = convertToTrinoType(field).orElseThrow(() -> new IllegalArgumentException("Unsupported column " + field)).type(); + return RowType.field(name, field.getMode() == REPEATED ? new ArrayType(trinoType) : trinoType); } @VisibleForTesting @@ -202,7 +200,7 @@ static String bytesToStringConverter(Object value) return format("FROM_BASE64('%s')", Base64.getEncoder().encodeToString(slice.getBytes())); } - public static Field toField(String name, Type type, @Nullable String comment) + public Field toField(String name, Type type, @Nullable String comment) { if (type instanceof ArrayType) { Type elementType = ((ArrayType) type).getElementType(); @@ -211,7 +209,7 @@ public static Field toField(String name, Type type, @Nullable String comment) return toInnerField(name, type, false, comment); } - private static Field toInnerField(String name, Type type, boolean repeated, @Nullable String comment) + private Field toInnerField(String name, Type type, boolean repeated, @Nullable String comment) { Field.Builder builder; if (type instanceof RowType) { @@ -226,7 +224,7 @@ private static Field toInnerField(String name, Type type, boolean repeated, @Nul return builder.build(); } - private static FieldList toFieldList(RowType rowType) + private FieldList toFieldList(RowType rowType) { ImmutableList.Builder fields = ImmutableList.builder(); for (RowType.Field field : rowType.getFields()) { @@ -237,7 +235,7 @@ private static FieldList toFieldList(RowType rowType) return FieldList.of(fields.build()); } - private static StandardSQLTypeName toStandardSqlTypeName(Type type) + private StandardSQLTypeName toStandardSqlTypeName(Type type) { if (type == BooleanType.BOOLEAN) { return StandardSQLTypeName.BOOL; @@ -312,7 +310,7 @@ public static String convertToString(Type type, StandardSQLTypeName bigqueryType } } - public static Optional toTrinoType(Field field) + public Optional toTrinoType(Field field) { return convertToTrinoType(field) .map(columnMapping -> field.getMode() == REPEATED ? @@ -320,7 +318,7 @@ public static Optional toTrinoType(Field field) columnMapping); } - private static Optional convertToTrinoType(Field field) + private Optional convertToTrinoType(Field field) { switch (field.getType().getStandardType()) { case BOOL: @@ -367,14 +365,14 @@ private static Optional convertToTrinoType(Field field) } } - public static BigQueryColumnHandle toColumnHandle(Field field) + public BigQueryColumnHandle toColumnHandle(Field field) { FieldList subFields = field.getSubFields(); List subColumns = subFields == null ? Collections.emptyList() : subFields.stream() - .filter(BigQueryType::isSupportedType) - .map(BigQueryType::toColumnHandle) + .filter(this::isSupportedType) + .map(this::toColumnHandle) .collect(Collectors.toList()); ColumnMapping columnMapping = toTrinoType(field).orElseThrow(() -> new IllegalArgumentException("Unsupported type: " + field)); return new BigQueryColumnHandle( @@ -388,7 +386,7 @@ public static BigQueryColumnHandle toColumnHandle(Field field) false); } - public static boolean isSupportedType(Field field) + public boolean isSupportedType(Field field) { LegacySQLTypeName type = field.getType(); if (type == LegacySQLTypeName.BIGNUMERIC) { diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryTypeUtils.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryTypeUtils.java index 3bdcb3f30627..9c278045b395 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryTypeUtils.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryTypeUtils.java @@ -32,8 +32,8 @@ import java.util.List; import java.util.Map; -import static io.trino.plugin.bigquery.BigQueryType.timestampToStringConverter; -import static io.trino.plugin.bigquery.BigQueryType.toZonedDateTime; +import static io.trino.plugin.bigquery.BigQueryTypeManager.timestampToStringConverter; +import static io.trino.plugin.bigquery.BigQueryTypeManager.toZonedDateTime; import static io.trino.spi.StandardErrorCode.GENERIC_INTERNAL_ERROR; import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; import static io.trino.spi.type.BigintType.BIGINT; diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/DefaultBigQueryMetadataFactory.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/DefaultBigQueryMetadataFactory.java index ef45d7b5d64e..e8efa0359389 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/DefaultBigQueryMetadataFactory.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/DefaultBigQueryMetadataFactory.java @@ -24,17 +24,19 @@ public class DefaultBigQueryMetadataFactory { private final BigQueryClientFactory bigQueryClient; private final ListeningExecutorService executorService; + private final BigQueryTypeManager typeManager; @Inject - public DefaultBigQueryMetadataFactory(BigQueryClientFactory bigQueryClient, @ForBigQuery ListeningExecutorService executorService) + public DefaultBigQueryMetadataFactory(BigQueryClientFactory bigQueryClient, BigQueryTypeManager typeManager, @ForBigQuery ListeningExecutorService executorService) { this.bigQueryClient = requireNonNull(bigQueryClient, "bigQueryClient is null"); + this.typeManager = requireNonNull(typeManager, "typeManager is null"); this.executorService = requireNonNull(executorService, "executorService is null"); } @Override public BigQueryMetadata create(BigQueryTransactionHandle transaction) { - return new BigQueryMetadata(bigQueryClient, executorService); + return new BigQueryMetadata(bigQueryClient, typeManager, executorService); } } diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/ptf/Query.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/ptf/Query.java index c2b40eda2649..bcef9071e8a4 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/ptf/Query.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/ptf/Query.java @@ -26,6 +26,7 @@ import io.trino.plugin.bigquery.BigQueryColumnHandle; import io.trino.plugin.bigquery.BigQueryQueryRelationHandle; import io.trino.plugin.bigquery.BigQueryTableHandle; +import io.trino.plugin.bigquery.BigQueryTypeManager; import io.trino.spi.connector.ConnectorAccessControl; import io.trino.spi.connector.ConnectorSession; import io.trino.spi.connector.ConnectorTableHandle; @@ -45,8 +46,6 @@ import java.util.Optional; import static com.google.common.collect.Iterables.getOnlyElement; -import static io.trino.plugin.bigquery.BigQueryType.isSupportedType; -import static io.trino.plugin.bigquery.BigQueryType.toColumnHandle; import static io.trino.spi.function.table.ReturnTypeSpecification.GenericTable.GENERIC_TABLE; import static io.trino.spi.type.VarcharType.VARCHAR; import static java.util.Objects.requireNonNull; @@ -59,25 +58,28 @@ public class Query public static final String NAME = "query"; private final BigQueryClientFactory clientFactory; + private final BigQueryTypeManager typeManager; @Inject - public Query(BigQueryClientFactory clientFactory) + public Query(BigQueryClientFactory clientFactory, BigQueryTypeManager typeManager) { this.clientFactory = requireNonNull(clientFactory, "clientFactory is null"); + this.typeManager = requireNonNull(typeManager, "typeManager is null"); } @Override public ConnectorTableFunction get() { - return new ClassLoaderSafeConnectorTableFunction(new QueryFunction(clientFactory), getClass().getClassLoader()); + return new ClassLoaderSafeConnectorTableFunction(new QueryFunction(clientFactory, typeManager), getClass().getClassLoader()); } public static class QueryFunction extends AbstractConnectorTableFunction { private final BigQueryClientFactory clientFactory; + private final BigQueryTypeManager typeManager; - public QueryFunction(BigQueryClientFactory clientFactory) + public QueryFunction(BigQueryClientFactory clientFactory, BigQueryTypeManager typeManager) { super( SCHEMA_NAME, @@ -88,6 +90,7 @@ public QueryFunction(BigQueryClientFactory clientFactory) .build()), GENERIC_TABLE); this.clientFactory = requireNonNull(clientFactory, "clientFactory is null"); + this.typeManager = requireNonNull(typeManager, "typeManager is null"); } @Override @@ -108,10 +111,10 @@ public TableFunctionAnalysis analyze( ImmutableList.Builder columnsBuilder = ImmutableList.builderWithExpectedSize(schema.getFields().size()); for (com.google.cloud.bigquery.Field field : schema.getFields()) { - if (!isSupportedType(field)) { + if (!typeManager.isSupportedType(field)) { throw new UnsupportedOperationException("Unsupported type: " + field.getType()); } - columnsBuilder.add(toColumnHandle(field)); + columnsBuilder.add(typeManager.toColumnHandle(field)); } Descriptor returnedType = new Descriptor(columnsBuilder.build().stream() diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryType.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryType.java index 6a91e3e299dd..a8954f6e4f32 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryType.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryType.java @@ -33,7 +33,7 @@ public class TestBigQueryType @Test public void testTimeToStringConverter() { - assertThat(BigQueryType.timeToStringConverter( + assertThat(BigQueryTypeManager.timeToStringConverter( Long.valueOf(303497217825L))) .isEqualTo("'00:00:00.303497'"); } @@ -41,10 +41,10 @@ public void testTimeToStringConverter() @Test public void testTimestampToStringConverter() { - assertThat(BigQueryType.timestampToStringConverter( + assertThat(BigQueryTypeManager.timestampToStringConverter( fromEpochSecondsAndFraction(1585658096, 123_456_000_000L, UTC_KEY))) .isEqualTo("2020-03-31 12:34:56.123456"); - assertThat(BigQueryType.timestampToStringConverter( + assertThat(BigQueryTypeManager.timestampToStringConverter( fromEpochSecondsAndFraction(1585658096, 123_456_000_000L, TimeZoneKey.getTimeZoneKey("Asia/Kathmandu")))) .isEqualTo("2020-03-31 12:34:56.123456"); } @@ -52,7 +52,7 @@ public void testTimestampToStringConverter() @Test public void testDateToStringConverter() { - assertThat(BigQueryType.dateToStringConverter( + assertThat(BigQueryTypeManager.dateToStringConverter( Long.valueOf(18352))) .isEqualTo("'2020-03-31'"); } @@ -60,11 +60,11 @@ public void testDateToStringConverter() @Test public void testStringToStringConverter() { - assertThat(BigQueryType.stringToStringConverter( + assertThat(BigQueryTypeManager.stringToStringConverter( utf8Slice("test"))) .isEqualTo("'test'"); - assertThat(BigQueryType.stringToStringConverter( + assertThat(BigQueryTypeManager.stringToStringConverter( utf8Slice("test's test"))) .isEqualTo("'test\\'s test'"); } @@ -72,7 +72,7 @@ public void testStringToStringConverter() @Test public void testNumericToStringConverter() { - assertThat(BigQueryType.numericToStringConverter( + assertThat(BigQueryTypeManager.numericToStringConverter( encodeScaledValue(ONE, 9))) .isEqualTo("1.000000000"); } @@ -80,7 +80,7 @@ public void testNumericToStringConverter() @Test public void testBytesToStringConverter() { - assertThat(BigQueryType.bytesToStringConverter( + assertThat(BigQueryTypeManager.bytesToStringConverter( wrappedBuffer((byte) 1, (byte) 2, (byte) 3, (byte) 4))) .isEqualTo("FROM_BASE64('AQIDBA==')"); } From beed389c13674a0cd495e64e66d95416b07d7fd5 Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Thu, 16 Nov 2023 22:22:47 +0900 Subject: [PATCH 292/587] Support reading json type in BigQuery --- docs/src/main/sphinx/connector/bigquery.md | 3 +++ .../BigQueryArrowToPageConverter.java | 6 ++++-- .../bigquery/BigQueryPageSourceProvider.java | 11 +++++++++- .../bigquery/BigQueryQueryPageSource.java | 7 +++++-- .../BigQueryStorageArrowPageSource.java | 3 ++- .../BigQueryStorageAvroPageSource.java | 8 +++++++- .../plugin/bigquery/BigQueryTypeManager.java | 20 +++++++++++++++++++ .../bigquery/BaseBigQueryConnectorTest.java | 1 + .../bigquery/BaseBigQueryTypeMapping.java | 11 ++++++++++ 9 files changed, 63 insertions(+), 7 deletions(-) diff --git a/docs/src/main/sphinx/connector/bigquery.md b/docs/src/main/sphinx/connector/bigquery.md index 96db6e68c40a..2bae6d8a7a99 100644 --- a/docs/src/main/sphinx/connector/bigquery.md +++ b/docs/src/main/sphinx/connector/bigquery.md @@ -212,6 +212,9 @@ to the following table: - In [Well-known text (WKT)](https://wikipedia.org/wiki/Well-known_text_representation_of_geometry) format +* - `JSON` + - `JSON` + - * - `ARRAY` - `ARRAY` - diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryArrowToPageConverter.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryArrowToPageConverter.java index fbfd8ff664d3..6b3b9450afd8 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryArrowToPageConverter.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryArrowToPageConverter.java @@ -82,14 +82,16 @@ public class BigQueryArrowToPageConverter implements AutoCloseable { + private final BigQueryTypeManager typeManager; private final VectorSchemaRoot root; private final VectorLoader loader; private final BufferAllocator allocator; private final List columnTypes; private final List columnNames; - public BigQueryArrowToPageConverter(BufferAllocator allocator, Schema schema, List columns) + public BigQueryArrowToPageConverter(BigQueryTypeManager typeManager, BufferAllocator allocator, Schema schema, List columns) { + this.typeManager = requireNonNull(typeManager, "typeManager is null"); this.allocator = requireNonNull(allocator, "allocator is null"); this.columnTypes = requireNonNull(columns, "columns is null").stream() .map(BigQueryColumnHandle::getTrinoType) @@ -192,7 +194,7 @@ private void writeVectorValues(BlockBuilder output, FieldVector vector, Consumer private void writeSlice(BlockBuilder output, Type type, FieldVector vector, int index) { - if (type instanceof VarcharType) { + if (type instanceof VarcharType || typeManager.isJsonType(type)) { byte[] slice = ((VarCharVector) vector).get(index); type.writeSlice(output, wrappedBuffer(slice)); } diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryPageSourceProvider.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryPageSourceProvider.java index b9a37f951a62..51b5acfe84e0 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryPageSourceProvider.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryPageSourceProvider.java @@ -38,14 +38,20 @@ public class BigQueryPageSourceProvider private final BigQueryClientFactory bigQueryClientFactory; private final BigQueryReadClientFactory bigQueryReadClientFactory; + private final BigQueryTypeManager typeManager; private final int maxReadRowsRetries; private final boolean arrowSerializationEnabled; @Inject - public BigQueryPageSourceProvider(BigQueryClientFactory bigQueryClientFactory, BigQueryReadClientFactory bigQueryReadClientFactory, BigQueryConfig config) + public BigQueryPageSourceProvider( + BigQueryClientFactory bigQueryClientFactory, + BigQueryReadClientFactory bigQueryReadClientFactory, + BigQueryTypeManager typeManager, + BigQueryConfig config) { this.bigQueryClientFactory = requireNonNull(bigQueryClientFactory, "bigQueryClientFactory is null"); this.bigQueryReadClientFactory = requireNonNull(bigQueryReadClientFactory, "bigQueryReadClientFactory is null"); + this.typeManager = requireNonNull(typeManager, "typeManager is null"); this.maxReadRowsRetries = config.getMaxReadRowsRetries(); this.arrowSerializationEnabled = config.isArrowSerializationEnabled(); } @@ -94,6 +100,7 @@ private ConnectorPageSource createStoragePageSource(ConnectorSession session, Bi { if (arrowSerializationEnabled) { return new BigQueryStorageArrowPageSource( + typeManager, bigQueryReadClientFactory.create(session), maxReadRowsRetries, split, @@ -101,6 +108,7 @@ private ConnectorPageSource createStoragePageSource(ConnectorSession session, Bi } return new BigQueryStorageAvroPageSource( bigQueryReadClientFactory.create(session), + typeManager, maxReadRowsRetries, split, columnHandles); @@ -110,6 +118,7 @@ private ConnectorPageSource createQueryPageSource(ConnectorSession session, BigQ { return new BigQueryQueryPageSource( session, + typeManager, bigQueryClientFactory.create(session), table, columnHandles.stream().map(BigQueryColumnHandle::getName).collect(toImmutableList()), diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryQueryPageSource.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryQueryPageSource.java index 9504a370bbe2..315bab7086ec 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryQueryPageSource.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryQueryPageSource.java @@ -74,6 +74,7 @@ public class BigQueryQueryPageSource .optionalEnd() .toFormatter(); + private final BigQueryTypeManager typeManager; private final List columnNames; private final List columnTypes; private final PageBuilder pageBuilder; @@ -83,6 +84,7 @@ public class BigQueryQueryPageSource public BigQueryQueryPageSource( ConnectorSession session, + BigQueryTypeManager typeManager, BigQueryClient client, BigQueryTableHandle table, List columnNames, @@ -94,6 +96,7 @@ public BigQueryQueryPageSource( requireNonNull(columnNames, "columnNames is null"); requireNonNull(columnTypes, "columnTypes is null"); requireNonNull(filter, "filter is null"); + this.typeManager = requireNonNull(typeManager, "typeManager is null"); checkArgument(columnNames.size() == columnTypes.size(), "columnNames and columnTypes sizes don't match"); this.columnNames = ImmutableList.copyOf(columnNames); this.columnTypes = ImmutableList.copyOf(columnTypes); @@ -235,9 +238,9 @@ else if (type instanceof RowType rowType) { } } - private static void writeSlice(BlockBuilder output, Type type, FieldValue value) + private void writeSlice(BlockBuilder output, Type type, FieldValue value) { - if (type instanceof VarcharType) { + if (type instanceof VarcharType || typeManager.isJsonType(type)) { type.writeSlice(output, utf8Slice(value.getStringValue())); } else if (type instanceof VarbinaryType) { diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryStorageArrowPageSource.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryStorageArrowPageSource.java index 919cfcf94f51..b230a3146870 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryStorageArrowPageSource.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryStorageArrowPageSource.java @@ -58,6 +58,7 @@ public class BigQueryStorageArrowPageSource private final PageBuilder pageBuilder; public BigQueryStorageArrowPageSource( + BigQueryTypeManager typeManager, BigQueryReadClient bigQueryReadClient, int maxReadRowsRetries, BigQuerySplit split, @@ -70,7 +71,7 @@ public BigQueryStorageArrowPageSource( log.debug("Starting to read from %s", split.getStreamName()); responses = new ReadRowsHelper(bigQueryReadClient, split.getStreamName(), maxReadRowsRetries).readRows(); this.streamBufferAllocator = allocator.newChildAllocator(split.getStreamName(), 1024, Long.MAX_VALUE); - this.bigQueryArrowToPageConverter = new BigQueryArrowToPageConverter(streamBufferAllocator, schema, columns); + this.bigQueryArrowToPageConverter = new BigQueryArrowToPageConverter(typeManager, streamBufferAllocator, schema, columns); this.pageBuilder = new PageBuilder(columns.stream() .map(BigQueryColumnHandle::getTrinoType) .collect(toImmutableList())); diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryStorageAvroPageSource.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryStorageAvroPageSource.java index 41572ec1a2a7..8f17854adb10 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryStorageAvroPageSource.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryStorageAvroPageSource.java @@ -83,6 +83,7 @@ public class BigQueryStorageAvroPageSource private static final AvroDecimalConverter DECIMAL_CONVERTER = new AvroDecimalConverter(); private final BigQueryReadClient bigQueryReadClient; + private final BigQueryTypeManager typeManager; private final BigQuerySplit split; private final List columnNames; private final List columnTypes; @@ -92,11 +93,13 @@ public class BigQueryStorageAvroPageSource public BigQueryStorageAvroPageSource( BigQueryReadClient bigQueryReadClient, + BigQueryTypeManager typeManager, int maxReadRowsRetries, BigQuerySplit split, List columns) { this.bigQueryReadClient = requireNonNull(bigQueryReadClient, "bigQueryReadClient is null"); + this.typeManager = requireNonNull(typeManager, "typeManager is null"); this.split = requireNonNull(split, "split is null"); this.readBytes = new AtomicLong(); requireNonNull(columns, "columns is null"); @@ -220,7 +223,7 @@ else if (type instanceof RowType rowType) { } } - private static void writeSlice(BlockBuilder output, Type type, Object value) + private void writeSlice(BlockBuilder output, Type type, Object value) { if (type instanceof VarcharType) { type.writeSlice(output, utf8Slice(((Utf8) value).toString())); @@ -233,6 +236,9 @@ else if (type instanceof VarbinaryType) { output.appendNull(); } } + else if (typeManager.isJsonType(type)) { + type.writeSlice(output, utf8Slice(((Utf8) value).toString())); + } else { throw new TrinoException(GENERIC_INTERNAL_ERROR, "Unhandled type for Slice: " + type.getTypeSignature()); } diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryTypeManager.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryTypeManager.java index e90ff84c091c..bfd4ab41e185 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryTypeManager.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryTypeManager.java @@ -19,6 +19,7 @@ import com.google.cloud.bigquery.StandardSQLTypeName; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; +import com.google.inject.Inject; import io.airlift.slice.Slice; import io.trino.spi.TrinoException; import io.trino.spi.type.ArrayType; @@ -38,6 +39,8 @@ import io.trino.spi.type.TimestampWithTimeZoneType; import io.trino.spi.type.TinyintType; import io.trino.spi.type.Type; +import io.trino.spi.type.TypeManager; +import io.trino.spi.type.TypeSignature; import io.trino.spi.type.VarbinaryType; import io.trino.spi.type.VarcharType; import jakarta.annotation.Nullable; @@ -62,6 +65,7 @@ import static io.trino.plugin.bigquery.BigQueryMetadata.DEFAULT_NUMERIC_TYPE_SCALE; import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; import static io.trino.spi.type.DecimalType.createDecimalType; +import static io.trino.spi.type.StandardTypes.JSON; import static io.trino.spi.type.TimeWithTimeZoneType.DEFAULT_PRECISION; import static io.trino.spi.type.TimeWithTimeZoneType.createTimeWithTimeZoneType; import static io.trino.spi.type.TimeZoneKey.getTimeZoneKey; @@ -78,6 +82,7 @@ import static java.lang.Math.toIntExact; import static java.lang.String.format; import static java.time.ZoneOffset.UTC; +import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; public final class BigQueryTypeManager @@ -97,6 +102,14 @@ public final class BigQueryTypeManager private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("''HH:mm:ss.SSSSSS''"); private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss.SSSSSS").withZone(UTC); + private final Type jsonType; + + @Inject + public BigQueryTypeManager(TypeManager typeManager) + { + jsonType = requireNonNull(typeManager, "typeManager is null").getType(new TypeSignature(JSON)); + } + private RowType.Field toRawTypeField(String name, Field field) { Type trinoType = convertToTrinoType(field).orElseThrow(() -> new IllegalArgumentException("Unsupported column " + field)).type(); @@ -353,6 +366,8 @@ private Optional convertToTrinoType(Field field) return Optional.of(new ColumnMapping(TimestampWithTimeZoneType.TIMESTAMP_TZ_MICROS, true)); case GEOGRAPHY: return Optional.of(new ColumnMapping(VarcharType.VARCHAR, false)); + case JSON: + return Optional.of(new ColumnMapping(jsonType, false)); case STRUCT: // create the row FieldList subTypes = field.getSubFields(); @@ -402,6 +417,11 @@ public boolean isSupportedType(Field field) return toTrinoType(field).isPresent(); } + public boolean isJsonType(Type type) + { + return type.equals(jsonType); + } + private static Field.Mode getMode(Field field) { return firstNonNull(field.getMode(), Field.Mode.NULLABLE); diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java index 493ec15d9b66..cb92f5e55d5a 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java @@ -135,6 +135,7 @@ public void testPredicatePushdown() testPredicatePushdown("DATETIME '2018-04-01 02:13:55.123'", "TIMESTAMP '2018-04-01 02:13:55.123'", true); testPredicatePushdown("ST_GeogPoint(0, 0)", "'POINT(0 0)'", false); + testPredicatePushdown("JSON '{\"age\": 30}'", "JSON '{\"age\": 30}'", false); testPredicatePushdown("[true]", "ARRAY[true]", false); testPredicatePushdown("STRUCT('nested' AS x)", "ROW('nested')", false); } diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryTypeMapping.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryTypeMapping.java index d2130132351d..c3760d95ed7a 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryTypeMapping.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryTypeMapping.java @@ -45,6 +45,7 @@ import static io.trino.spi.type.VarbinaryType.VARBINARY; import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.testing.TestingNames.randomNameSuffix; +import static io.trino.type.JsonType.JSON; import static java.lang.String.format; import static java.time.ZoneOffset.UTC; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -678,6 +679,16 @@ public void testGeography() .execute(getQueryRunner(), bigqueryViewCreateAndInsert("test.geography")); } + @Test + public void testJson() + { + SqlDataTypeTest.create() + .addRoundTrip("JSON", "JSON '{\"name\": \"Alice\", \"age\": 30}'", JSON, "JSON '{\"name\": \"Alice\", \"age\": 30}'") + .addRoundTrip("JSON", "NULL", JSON, "CAST(NULL AS JSON)") + .execute(getQueryRunner(), bigqueryCreateAndInsert("test.json")) + .execute(getQueryRunner(), bigqueryViewCreateAndInsert("test.json")); + } + @Test public void testArray() { From 28333ef72d1f16a67802e25d655df5db844dfdaf Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Wed, 15 Nov 2023 15:58:13 +0100 Subject: [PATCH 293/587] Update AWS SDK v1 to 1.12.590 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8b51e490eaf5..1e81459e3d9a 100644 --- a/pom.xml +++ b/pom.xml @@ -178,7 +178,7 @@ 4.13.1 14.0.0 1.11.3 - 1.12.581 + 1.12.590 4.17.0 7.4.1 87 From 8dbd60e1823fd0a406c3d3ba2d0de794c5fd5ee9 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Wed, 15 Nov 2023 15:59:18 +0100 Subject: [PATCH 294/587] Update AWS SDK v2 to 2.21.24 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1e81459e3d9a..ed6c8f4252b5 100644 --- a/pom.xml +++ b/pom.xml @@ -290,7 +290,7 @@ software.amazon.awssdk bom - 2.21.19 + 2.21.24 pom import From a863fffc0336e322c20e3253c5fb22e71be4842e Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Thu, 16 Nov 2023 19:59:13 +0100 Subject: [PATCH 295/587] Upgrade oracle JDBC driver to 2.11.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ed6c8f4252b5..6a29f07f7382 100644 --- a/pom.xml +++ b/pom.xml @@ -193,7 +193,7 @@ 3.6.0 1.9.20 3.6.0 - 21.9.0.0 + 21.11.0.0 ${dep.airlift.version} 1.13.1 3.25.0 From f7e7b618835dd78b3432e013024291fba8359e86 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Thu, 16 Nov 2023 20:00:07 +0100 Subject: [PATCH 296/587] Update redshift jdbc to 2.1.0.22 --- plugin/trino-redshift/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/trino-redshift/pom.xml b/plugin/trino-redshift/pom.xml index 2a14b45a060f..e49a5418184b 100644 --- a/plugin/trino-redshift/pom.xml +++ b/plugin/trino-redshift/pom.xml @@ -21,7 +21,7 @@ com.amazon.redshift redshift-jdbc42 - 2.1.0.21 + 2.1.0.22 From f8f0962830cd92c97a903fc0f7e264a8e470d351 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Thu, 16 Nov 2023 20:03:32 +0100 Subject: [PATCH 297/587] Upgrade protobuf 3.25.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6a29f07f7382..11ea04bfc87f 100644 --- a/pom.xml +++ b/pom.xml @@ -196,7 +196,7 @@ 21.11.0.0 ${dep.airlift.version} 1.13.1 - 3.25.0 + 3.25.1 1.6.12 2.1.1 2.0.62.Final From 194994d9d20ace8fcecb6e1cd218f895fdce9e24 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Thu, 16 Nov 2023 20:04:01 +0100 Subject: [PATCH 298/587] Upgrade commons-io to 2.15.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 11ea04bfc87f..b8b209f19e31 100644 --- a/pom.xml +++ b/pom.xml @@ -618,7 +618,7 @@ commons-io commons-io - 2.14.0 + 2.15.0 From d89431a126a628caacff785363c176b934131487 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Thu, 16 Nov 2023 20:04:32 +0100 Subject: [PATCH 299/587] Upgrade minio to 8.5.7 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b8b209f19e31..b0edacc566ba 100644 --- a/pom.xml +++ b/pom.xml @@ -832,7 +832,7 @@ io.minio minio - 8.5.6 + 8.5.7 From 5dede088c8956075a9f853c74d635099f259a571 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Thu, 16 Nov 2023 20:12:06 +0100 Subject: [PATCH 300/587] Update flyway to 10.0.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b0edacc566ba..a3ee0f4e4612 100644 --- a/pom.xml +++ b/pom.xml @@ -185,7 +185,7 @@ 1.21 1.0.8 2.23.0 - 10.0.0 + 10.0.1 1.43.3 1.4.2 5.13.0 From d435390fcde559a079f3f562cc47b448d6217eea Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Thu, 16 Nov 2023 20:14:54 +0100 Subject: [PATCH 301/587] Upgrade AspectJ to 1.9.20.1 --- plugin/trino-prometheus/pom.xml | 3 ++- plugin/trino-raptor-legacy/pom.xml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/plugin/trino-prometheus/pom.xml b/plugin/trino-prometheus/pom.xml index 0b9722d07590..2ba32444f669 100644 --- a/plugin/trino-prometheus/pom.xml +++ b/plugin/trino-prometheus/pom.xml @@ -220,10 +220,11 @@ 4.4.16 test + org.aspectj aspectjweaver - 1.9.9.1 + 1.9.20.1 test diff --git a/plugin/trino-raptor-legacy/pom.xml b/plugin/trino-raptor-legacy/pom.xml index 47b013da9f6c..f223d3ff9bc2 100644 --- a/plugin/trino-raptor-legacy/pom.xml +++ b/plugin/trino-raptor-legacy/pom.xml @@ -271,10 +271,11 @@ jakarta.ws.rs-api test + org.aspectj aspectjweaver - 1.9.9.1 + 1.9.20.1 test From d5b2d4e4130a50ec494b94ba333b1daba3c8c3c2 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Thu, 16 Nov 2023 20:36:51 +0100 Subject: [PATCH 302/587] Update keycloak-core to 22.0.5 --- plugin/trino-iceberg/pom.xml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugin/trino-iceberg/pom.xml b/plugin/trino-iceberg/pom.xml index cb388f332011..66f5f7a4b967 100644 --- a/plugin/trino-iceberg/pom.xml +++ b/plugin/trino-iceberg/pom.xml @@ -24,7 +24,6 @@ TODO (https://github.com/trinodb/trino/issues/11294) remove when we upgrade to surefire with https://issues.apache.org/jira/browse/SUREFIRE-1967 --> instances - 21.1.2 0.71.0 @@ -571,7 +570,7 @@ org.keycloak keycloak-admin-client-jakarta - ${dep.keycloak.version} + 21.1.2 test @@ -588,7 +587,7 @@ org.keycloak keycloak-core - ${dep.keycloak.version} + 22.0.5 test From ca2cde6f7a3f8f932fdd715af204320c3ec2042d Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Thu, 16 Nov 2023 20:37:42 +0100 Subject: [PATCH 303/587] Update sqlite-jdbc to 3.44.0.0 --- plugin/trino-iceberg/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/trino-iceberg/pom.xml b/plugin/trino-iceberg/pom.xml index 66f5f7a4b967..cdec3dfeaae3 100644 --- a/plugin/trino-iceberg/pom.xml +++ b/plugin/trino-iceberg/pom.xml @@ -612,7 +612,7 @@ org.xerial sqlite-jdbc - 3.43.0.0 + 3.44.0.0 test From d4d7d1662db23804ee2618fc523508574fa6fd1d Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Fri, 17 Nov 2023 14:48:16 +0100 Subject: [PATCH 304/587] Add junit-extensions to test classpaths `junit-extensions` is necessary safety extensions to JUnit, preventing JUnit "UX trap" which makes overridden tests silently skipped. It should be on every module's classpath. For the record, the modules were identified using find -name pom.xml | grep -vFxf <(find -name pom.xml -exec grep -l junit-extensions {} +) And some left out. --- client/trino-client/pom.xml | 6 ++++++ core/trino-server/pom.xml | 8 ++++++++ lib/trino-filesystem-gcs/pom.xml | 6 ++++++ plugin/trino-atop/pom.xml | 6 ++++++ testing/trino-benchmark-queries/pom.xml | 6 ++++++ 5 files changed, 32 insertions(+) diff --git a/client/trino-client/pom.xml b/client/trino-client/pom.xml index 9c7a41e5e7d7..24f8af336230 100644 --- a/client/trino-client/pom.xml +++ b/client/trino-client/pom.xml @@ -99,6 +99,12 @@ test + + io.airlift + junit-extensions + test + + io.trino trino-spi diff --git a/core/trino-server/pom.xml b/core/trino-server/pom.xml index 60ce1b9ab247..8151dce764d6 100644 --- a/core/trino-server/pom.xml +++ b/core/trino-server/pom.xml @@ -25,6 +25,14 @@ ${project.artifactId} + + + io.airlift + junit-extensions + test + + + diff --git a/lib/trino-filesystem-gcs/pom.xml b/lib/trino-filesystem-gcs/pom.xml index e600136e5b79..573302a50bdc 100644 --- a/lib/trino-filesystem-gcs/pom.xml +++ b/lib/trino-filesystem-gcs/pom.xml @@ -135,6 +135,12 @@ provided + + io.airlift + junit-extensions + test + + io.airlift testing diff --git a/plugin/trino-atop/pom.xml b/plugin/trino-atop/pom.xml index d957e286b88d..c84aeefa8586 100644 --- a/plugin/trino-atop/pom.xml +++ b/plugin/trino-atop/pom.xml @@ -111,6 +111,12 @@ runtime + + io.airlift + junit-extensions + test + + io.airlift testing diff --git a/testing/trino-benchmark-queries/pom.xml b/testing/trino-benchmark-queries/pom.xml index b9224c4808bd..00bf340cac69 100644 --- a/testing/trino-benchmark-queries/pom.xml +++ b/testing/trino-benchmark-queries/pom.xml @@ -15,6 +15,12 @@ + + io.airlift + junit-extensions + test + + org.junit.jupiter junit-jupiter-api From 32d1b4b1f4fe073ff80f2c1e53f2adc61489747d Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Fri, 17 Nov 2023 17:45:00 +0100 Subject: [PATCH 305/587] Remove deprecated Node.getHttpUri It's not supposed to be called for 4.5y now. --- .../src/main/java/io/trino/metadata/InternalNode.java | 7 ------- core/trino-spi/src/main/java/io/trino/spi/Node.java | 8 -------- 2 files changed, 15 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/metadata/InternalNode.java b/core/trino-main/src/main/java/io/trino/metadata/InternalNode.java index 6ddcecb68a6e..43079b293f95 100644 --- a/core/trino-main/src/main/java/io/trino/metadata/InternalNode.java +++ b/core/trino-main/src/main/java/io/trino/metadata/InternalNode.java @@ -60,13 +60,6 @@ public String getHost() return internalUri.getHost(); } - @Override - @Deprecated - public URI getHttpUri() - { - return getInternalUri(); - } - public URI getInternalUri() { return internalUri; diff --git a/core/trino-spi/src/main/java/io/trino/spi/Node.java b/core/trino-spi/src/main/java/io/trino/spi/Node.java index 42f5e9b8b23d..45162802445a 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/Node.java +++ b/core/trino-spi/src/main/java/io/trino/spi/Node.java @@ -13,20 +13,12 @@ */ package io.trino.spi; -import java.net.URI; - public interface Node { String getHost(); HostAddress getHostAndPort(); - /** - * @deprecated Connectors should not access the HTTP endpoints of other nodes. - */ - @Deprecated - URI getHttpUri(); - String getNodeIdentifier(); String getVersion(); From 63f8e26e40ba60bdb2abb3c21feda9a84f74afed Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Fri, 17 Nov 2023 17:46:52 +0100 Subject: [PATCH 306/587] Mark PageSorter.sort supported method Commit d2289252054151d634e10311301c831d269c98d4 un-deprecated `PageSorter` and it looks like an omission not to un-deprecate it's only sorting method. --- core/trino-spi/src/main/java/io/trino/spi/PageSorter.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/trino-spi/src/main/java/io/trino/spi/PageSorter.java b/core/trino-spi/src/main/java/io/trino/spi/PageSorter.java index 7e23dfce5334..06f23c21ab62 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/PageSorter.java +++ b/core/trino-spi/src/main/java/io/trino/spi/PageSorter.java @@ -24,7 +24,6 @@ public interface PageSorter * @return Sorted synthetic addresses for pages. A synthetic address is encoded as a long with * the high 32 bits containing the page index and the low 32 bits containing position index */ - @Deprecated long[] sort(List types, List pages, List sortChannels, List sortOrders, int expectedPositions); int decodePageIndex(long address); From 98d51a4e5b5b0dbdf13f659f0c6bdaf6f987a3c2 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Fri, 17 Nov 2023 17:53:12 +0100 Subject: [PATCH 307/587] Remove deprecated Type accessors --- .../src/main/java/io/trino/spi/type/CharType.java | 13 ------------- .../io/trino/spi/type/TimeWithTimeZoneType.java | 7 ------- .../main/java/io/trino/spi/type/VarbinaryType.java | 9 --------- 3 files changed, 29 deletions(-) diff --git a/core/trino-spi/src/main/java/io/trino/spi/type/CharType.java b/core/trino-spi/src/main/java/io/trino/spi/type/CharType.java index b9e1967a848a..662634ef178d 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/type/CharType.java +++ b/core/trino-spi/src/main/java/io/trino/spi/type/CharType.java @@ -34,7 +34,6 @@ import static io.trino.spi.type.Slices.sliceRepresentation; import static java.lang.Character.MAX_CODE_POINT; import static java.lang.Character.MIN_CODE_POINT; -import static java.lang.Math.toIntExact; import static java.lang.String.format; import static java.lang.invoke.MethodHandles.lookup; import static java.util.Collections.singletonList; @@ -60,18 +59,6 @@ public final class CharType private final int length; private volatile Optional range; - /** - * @deprecated Use {@link #createCharType(int)} instead. - */ - @Deprecated - public static CharType createCharType(long length) - { - if (length < 0 || length > MAX_LENGTH) { - throw new IllegalArgumentException(format("CHAR length must be in range [0, %s], got %s", MAX_LENGTH, length)); - } - return createCharType(toIntExact(length)); - } - public static CharType createCharType(int length) { if (0 <= length && length < CACHED_INSTANCES.length) { diff --git a/core/trino-spi/src/main/java/io/trino/spi/type/TimeWithTimeZoneType.java b/core/trino-spi/src/main/java/io/trino/spi/type/TimeWithTimeZoneType.java index ee9e406080bb..15da2281114e 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/type/TimeWithTimeZoneType.java +++ b/core/trino-spi/src/main/java/io/trino/spi/type/TimeWithTimeZoneType.java @@ -43,13 +43,6 @@ public abstract sealed class TimeWithTimeZoneType public static final TimeWithTimeZoneType TIME_TZ_NANOS = createTimeWithTimeZoneType(9); public static final TimeWithTimeZoneType TIME_TZ_PICOS = createTimeWithTimeZoneType(12); - /** - * @deprecated Use {@link #createTimeWithTimeZoneType} instead. - */ - @Deprecated - // Use singleton for backwards compatibility with code checking `type == TIME_WITH_TIME_ZONE` - public static final TimeWithTimeZoneType TIME_WITH_TIME_ZONE = TIME_TZ_MILLIS; - private final int precision; public static TimeWithTimeZoneType createTimeWithTimeZoneType(int precision) diff --git a/core/trino-spi/src/main/java/io/trino/spi/type/VarbinaryType.java b/core/trino-spi/src/main/java/io/trino/spi/type/VarbinaryType.java index 07f192cb83cf..1e60556044ac 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/type/VarbinaryType.java +++ b/core/trino-spi/src/main/java/io/trino/spi/type/VarbinaryType.java @@ -36,15 +36,6 @@ private VarbinaryType() super(new TypeSignature(StandardTypes.VARBINARY), Slice.class); } - /** - * @deprecated Use {@code type instanceof VarbinaryType} instead. - */ - @Deprecated - public static boolean isVarbinaryType(Type type) - { - return type instanceof VarbinaryType; - } - @Override public boolean isComparable() { From 840d61bbd4391106c056915e09d80a2d4663128b Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Fri, 17 Nov 2023 16:22:47 +0100 Subject: [PATCH 308/587] Remove long deprecated Connector methods Remove overloads of `Connector.beginTransaction` and `Connector.getMetadata` deprecated since 2021. --- .../io/trino/spi/connector/Connector.java | 23 +------------------ 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/core/trino-spi/src/main/java/io/trino/spi/connector/Connector.java b/core/trino-spi/src/main/java/io/trino/spi/connector/Connector.java index ae511a01d18b..30efe527db58 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/connector/Connector.java +++ b/core/trino-spi/src/main/java/io/trino/spi/connector/Connector.java @@ -30,15 +30,6 @@ public interface Connector { - /** - * @deprecated use {@link #beginTransaction(IsolationLevel, boolean, boolean)} - */ - @Deprecated - default ConnectorTransactionHandle beginTransaction(IsolationLevel isolationLevel, boolean readOnly) - { - throw new UnsupportedOperationException(); - } - /** * Start a new transaction and return a handle for it. The engine will call * {@link #getMetadata} to fetch the metadata instance for the transaction. @@ -56,7 +47,7 @@ default ConnectorTransactionHandle beginTransaction(IsolationLevel isolationLeve */ default ConnectorTransactionHandle beginTransaction(IsolationLevel isolationLevel, boolean readOnly, boolean autoCommit) { - return beginTransaction(isolationLevel, readOnly); + throw new UnsupportedOperationException(); } /** @@ -64,18 +55,6 @@ default ConnectorTransactionHandle beginTransaction(IsolationLevel isolationLeve * in a single threaded context. */ default ConnectorMetadata getMetadata(ConnectorSession session, ConnectorTransactionHandle transactionHandle) - { - return getMetadata(transactionHandle); - } - - /** - * Guaranteed to be called at most once per transaction. The returned metadata will only be accessed - * in a single threaded context. - * - * @deprecated use {@link #getMetadata(ConnectorSession, ConnectorTransactionHandle)} - */ - @Deprecated - default ConnectorMetadata getMetadata(ConnectorTransactionHandle transactionHandle) { throw new UnsupportedOperationException(); } From b468159a4b40437cc90c82ad77694a11b7da8329 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Fri, 17 Nov 2023 16:25:37 +0100 Subject: [PATCH 309/587] Remove deprecated filterColumns overload --- .../java/io/trino/security/AccessControl.java | 8 ------ .../trino/security/AccessControlManager.java | 26 ------------------- .../trino/security/AllowAllAccessControl.java | 6 ----- .../trino/security/DenyAllAccessControl.java | 6 ----- .../security/ForwardingAccessControl.java | 6 ----- .../InjectedConnectorAccessControl.java | 7 ----- .../io/trino/security/ViewAccessControl.java | 7 ----- .../trino/sql/analyzer/StatementAnalyzer.java | 13 ++++++---- .../testing/TestingAccessControlManager.java | 10 ++----- .../trino/tracing/TracingAccessControl.java | 11 +------- .../spi/connector/ConnectorAccessControl.java | 18 ++----------- ...ClassLoaderSafeConnectorAccessControl.java | 8 ------ .../base/security/AllowAllAccessControl.java | 6 ----- .../base/security/FileBasedAccessControl.java | 19 ++++++++------ .../ForwardingConnectorAccessControl.java | 6 ----- .../base/security/ReadOnlyAccessControl.java | 6 ----- ...seFileBasedConnectorAccessControlTest.java | 15 ++++++----- .../hive/security/LegacyAccessControl.java | 6 ----- .../security/SqlStandardAccessControl.java | 19 ++++++++------ .../SystemTableAwareAccessControl.java | 19 ++++++++++++-- 20 files changed, 60 insertions(+), 162 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/security/AccessControl.java b/core/trino-main/src/main/java/io/trino/security/AccessControl.java index 2602e4dddee1..b3e6c24f394e 100644 --- a/core/trino-main/src/main/java/io/trino/security/AccessControl.java +++ b/core/trino-main/src/main/java/io/trino/security/AccessControl.java @@ -252,14 +252,6 @@ public interface AccessControl */ void checkCanShowColumns(SecurityContext context, CatalogSchemaTableName table); - /** - * Filter the list of columns to those visible to the identity. - * - * @deprecated Use {@link #filterColumns(SecurityContext, String, Map)} - */ - @Deprecated - Set filterColumns(SecurityContext context, CatalogSchemaTableName tableName, Set columns); - /** * Filter lists of columns of multiple tables to those visible to the identity. */ diff --git a/core/trino-main/src/main/java/io/trino/security/AccessControlManager.java b/core/trino-main/src/main/java/io/trino/security/AccessControlManager.java index 05c67dde8b6f..90ff4afab672 100644 --- a/core/trino-main/src/main/java/io/trino/security/AccessControlManager.java +++ b/core/trino-main/src/main/java/io/trino/security/AccessControlManager.java @@ -627,32 +627,6 @@ public void checkCanShowColumns(SecurityContext securityContext, CatalogSchemaTa catalogAuthorizationCheck(table.getCatalogName(), securityContext, (control, context) -> control.checkCanShowColumns(context, table.getSchemaTableName())); } - @Override - public Set filterColumns(SecurityContext securityContext, CatalogSchemaTableName table, Set columns) - { - requireNonNull(securityContext, "securityContext is null"); - requireNonNull(table, "tableName is null"); - - if (columns.isEmpty()) { - // Do not call plugin-provided implementation unnecessarily. - return ImmutableSet.of(); - } - - if (filterTables(securityContext, table.getCatalogName(), ImmutableSet.of(table.getSchemaTableName())).isEmpty()) { - return ImmutableSet.of(); - } - - for (SystemAccessControl systemAccessControl : getSystemAccessControls()) { - columns = systemAccessControl.filterColumns(securityContext.toSystemSecurityContext(), table, columns); - } - - ConnectorAccessControl connectorAccessControl = getConnectorAccessControl(securityContext.getTransactionId(), table.getCatalogName()); - if (connectorAccessControl != null) { - columns = connectorAccessControl.filterColumns(toConnectorSecurityContext(table.getCatalogName(), securityContext), table.getSchemaTableName(), columns); - } - return columns; - } - @Override public Map> filterColumns(SecurityContext securityContext, String catalogName, Map> tableColumns) { diff --git a/core/trino-main/src/main/java/io/trino/security/AllowAllAccessControl.java b/core/trino-main/src/main/java/io/trino/security/AllowAllAccessControl.java index 15d393af9232..64f23efeefbf 100644 --- a/core/trino-main/src/main/java/io/trino/security/AllowAllAccessControl.java +++ b/core/trino-main/src/main/java/io/trino/security/AllowAllAccessControl.java @@ -180,12 +180,6 @@ public void checkCanShowColumns(SecurityContext context, CatalogSchemaTableName { } - @Override - public Set filterColumns(SecurityContext context, CatalogSchemaTableName tableName, Set columns) - { - return columns; - } - @Override public Map> filterColumns(SecurityContext context, String catalogName, Map> tableColumns) { diff --git a/core/trino-main/src/main/java/io/trino/security/DenyAllAccessControl.java b/core/trino-main/src/main/java/io/trino/security/DenyAllAccessControl.java index 7d9fa4c2e78a..7d3013b1c535 100644 --- a/core/trino-main/src/main/java/io/trino/security/DenyAllAccessControl.java +++ b/core/trino-main/src/main/java/io/trino/security/DenyAllAccessControl.java @@ -263,12 +263,6 @@ public void checkCanShowColumns(SecurityContext context, CatalogSchemaTableName denyShowColumns(table.toString()); } - @Override - public Set filterColumns(SecurityContext context, CatalogSchemaTableName tableName, Set columns) - { - return ImmutableSet.of(); - } - @Override public Map> filterColumns(SecurityContext context, String catalogName, Map> tableColumns) { diff --git a/core/trino-main/src/main/java/io/trino/security/ForwardingAccessControl.java b/core/trino-main/src/main/java/io/trino/security/ForwardingAccessControl.java index e6ae8161cdb2..a9522729a2ec 100644 --- a/core/trino-main/src/main/java/io/trino/security/ForwardingAccessControl.java +++ b/core/trino-main/src/main/java/io/trino/security/ForwardingAccessControl.java @@ -233,12 +233,6 @@ public void checkCanShowColumns(SecurityContext context, CatalogSchemaTableName delegate().checkCanShowColumns(context, table); } - @Override - public Set filterColumns(SecurityContext context, CatalogSchemaTableName tableName, Set columns) - { - return delegate().filterColumns(context, tableName, columns); - } - @Override public Map> filterColumns(SecurityContext context, String catalogName, Map> tableColumns) { diff --git a/core/trino-main/src/main/java/io/trino/security/InjectedConnectorAccessControl.java b/core/trino-main/src/main/java/io/trino/security/InjectedConnectorAccessControl.java index 7da3441b45ed..33b71e97f033 100644 --- a/core/trino-main/src/main/java/io/trino/security/InjectedConnectorAccessControl.java +++ b/core/trino-main/src/main/java/io/trino/security/InjectedConnectorAccessControl.java @@ -184,13 +184,6 @@ public void checkCanShowColumns(ConnectorSecurityContext context, SchemaTableNam accessControl.checkCanShowColumns(securityContext, new CatalogSchemaTableName(catalogName, tableName)); } - @Override - public Set filterColumns(ConnectorSecurityContext context, SchemaTableName tableName, Set columns) - { - checkArgument(context == null, "context must be null"); - return accessControl.filterColumns(securityContext, new CatalogSchemaTableName(catalogName, tableName), columns); - } - @Override public Map> filterColumns(ConnectorSecurityContext context, Map> tableColumns) { diff --git a/core/trino-main/src/main/java/io/trino/security/ViewAccessControl.java b/core/trino-main/src/main/java/io/trino/security/ViewAccessControl.java index 195298d6024c..59ac8f210630 100644 --- a/core/trino-main/src/main/java/io/trino/security/ViewAccessControl.java +++ b/core/trino-main/src/main/java/io/trino/security/ViewAccessControl.java @@ -14,7 +14,6 @@ package io.trino.security; import io.trino.metadata.QualifiedObjectName; -import io.trino.spi.connector.CatalogSchemaTableName; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.security.AccessDeniedException; import io.trino.spi.security.ViewExpression; @@ -54,12 +53,6 @@ public void checkCanSelectFromColumns(SecurityContext context, QualifiedObjectNa wrapAccessDeniedException(() -> delegate.checkCanCreateViewWithSelectFromColumns(context, tableName, columnNames)); } - @Override - public Set filterColumns(SecurityContext context, CatalogSchemaTableName tableName, Set columns) - { - return delegate.filterColumns(context, tableName, columns); - } - @Override public Map> filterColumns(SecurityContext context, String catalogName, Map> tableColumns) { diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java index 2d74e0a8fbc1..484d28d0e851 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java @@ -4415,11 +4415,14 @@ private List filterInaccessibleFields(List fields) tableFieldsMap.asMap().forEach((table, tableFields) -> { Set accessibleColumns = accessControl.filterColumns( - session.toSecurityContext(), - table.asCatalogSchemaTableName(), - tableFields.stream() - .map(field -> field.getOriginColumnName().get()) - .collect(toImmutableSet())); + session.toSecurityContext(), + table.getCatalogName(), + ImmutableMap.of( + table.asSchemaTableName(), + tableFields.stream() + .map(field -> field.getOriginColumnName().get()) + .collect(toImmutableSet()))) + .getOrDefault(table.asSchemaTableName(), ImmutableSet.of()); accessibleFields.addAll(tableFields.stream() .filter(field -> accessibleColumns.contains(field.getOriginColumnName().get())) .collect(toImmutableList())); diff --git a/core/trino-main/src/main/java/io/trino/testing/TestingAccessControlManager.java b/core/trino-main/src/main/java/io/trino/testing/TestingAccessControlManager.java index 8ed1e6459d1c..9a7d2edb6f8d 100644 --- a/core/trino-main/src/main/java/io/trino/testing/TestingAccessControlManager.java +++ b/core/trino-main/src/main/java/io/trino/testing/TestingAccessControlManager.java @@ -39,6 +39,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -640,19 +641,12 @@ public void checkCanShowColumns(SecurityContext context, CatalogSchemaTableName } } - @Override - public Set filterColumns(SecurityContext context, CatalogSchemaTableName table, Set columns) - { - Set visibleColumns = localFilterColumns(context, table.getSchemaTableName(), columns); - return super.filterColumns(context, table, visibleColumns); - } - @Override public Map> filterColumns(SecurityContext context, String catalogName, Map> tableColumns) { tableColumns = tableColumns.entrySet().stream() .collect(toImmutableMap( - Map.Entry::getKey, + Entry::getKey, e -> localFilterColumns(context, e.getKey(), e.getValue()))); return super.filterColumns(context, catalogName, tableColumns); } diff --git a/core/trino-main/src/main/java/io/trino/tracing/TracingAccessControl.java b/core/trino-main/src/main/java/io/trino/tracing/TracingAccessControl.java index 4c9837fe1e81..672a9db11824 100644 --- a/core/trino-main/src/main/java/io/trino/tracing/TracingAccessControl.java +++ b/core/trino-main/src/main/java/io/trino/tracing/TracingAccessControl.java @@ -320,19 +320,10 @@ public void checkCanShowColumns(SecurityContext context, CatalogSchemaTableName } } - @Override - public Set filterColumns(SecurityContext context, CatalogSchemaTableName tableName, Set columns) - { - Span span = startSpan("filterColumns"); - try (var ignored = scopedSpan(span)) { - return delegate.filterColumns(context, tableName, columns); - } - } - @Override public Map> filterColumns(SecurityContext context, String catalogName, Map> tableColumns) { - Span span = startSpan("filterColumns bulk"); + Span span = startSpan("filterColumns"); try (var ignored = scopedSpan(span)) { return delegate.filterColumns(context, catalogName, tableColumns); } diff --git a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorAccessControl.java b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorAccessControl.java index 59b49306592b..df2a1d82c3d2 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorAccessControl.java +++ b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorAccessControl.java @@ -24,7 +24,6 @@ import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; import static io.trino.spi.security.AccessDeniedException.denyAddColumn; import static io.trino.spi.security.AccessDeniedException.denyAlterColumn; @@ -83,6 +82,7 @@ import static io.trino.spi.security.AccessDeniedException.denyTruncateTable; import static io.trino.spi.security.AccessDeniedException.denyUpdateTableColumns; import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; import static java.util.Collections.emptySet; public interface ConnectorAccessControl @@ -275,26 +275,12 @@ default void checkCanShowColumns(ConnectorSecurityContext context, SchemaTableNa denyShowColumns(tableName.getTableName()); } - /** - * Filter the list of columns to those visible to the identity. - * - * @deprecated Use {@link #filterColumns(ConnectorSecurityContext, Map)} - */ - @Deprecated - default Set filterColumns(ConnectorSecurityContext context, SchemaTableName tableName, Set columns) - { - return emptySet(); - } - /** * Filter lists of columns of multiple tables to those visible to the identity. */ default Map> filterColumns(ConnectorSecurityContext context, Map> tableColumns) { - return tableColumns.entrySet().stream() - .collect(Collectors.toMap( - Map.Entry::getKey, - entry -> filterColumns(context, entry.getKey(), entry.getValue()))); + return emptyMap(); } /** diff --git a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeConnectorAccessControl.java b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeConnectorAccessControl.java index 159f9bd5105e..635ec60d85cb 100644 --- a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeConnectorAccessControl.java +++ b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeConnectorAccessControl.java @@ -189,14 +189,6 @@ public void checkCanShowColumns(ConnectorSecurityContext context, SchemaTableNam } } - @Override - public Set filterColumns(ConnectorSecurityContext context, SchemaTableName tableName, Set columns) - { - try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(classLoader)) { - return delegate.filterColumns(context, tableName, columns); - } - } - @Override public Map> filterColumns(ConnectorSecurityContext context, Map> tableColumns) { diff --git a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/AllowAllAccessControl.java b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/AllowAllAccessControl.java index 2da20cc186e1..6dfd62124b9e 100644 --- a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/AllowAllAccessControl.java +++ b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/AllowAllAccessControl.java @@ -124,12 +124,6 @@ public void checkCanShowColumns(ConnectorSecurityContext context, SchemaTableNam { } - @Override - public Set filterColumns(ConnectorSecurityContext context, SchemaTableName tableName, Set columns) - { - return columns; - } - @Override public Map> filterColumns(ConnectorSecurityContext context, Map> tableColumns) { diff --git a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/FileBasedAccessControl.java b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/FileBasedAccessControl.java index 15fb53c8ee4c..6d6023d85c08 100644 --- a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/FileBasedAccessControl.java +++ b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/FileBasedAccessControl.java @@ -31,12 +31,14 @@ import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.function.Predicate; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static io.trino.plugin.base.security.TableAccessControlRule.TablePrivilege.DELETE; import static io.trino.plugin.base.security.TableAccessControlRule.TablePrivilege.GRANT_SELECT; @@ -254,7 +256,15 @@ public void checkCanShowColumns(ConnectorSecurityContext context, SchemaTableNam } @Override - public Set filterColumns(ConnectorSecurityContext context, SchemaTableName tableName, Set columns) + public Map> filterColumns(ConnectorSecurityContext context, Map> tableColumns) + { + return tableColumns.entrySet().stream() + .collect(toImmutableMap( + Entry::getKey, + entry -> filterColumns(context, entry.getKey(), entry.getValue()))); + } + + private Set filterColumns(ConnectorSecurityContext context, SchemaTableName tableName, Set columns) { if (INFORMATION_SCHEMA_NAME.equals(tableName.getSchemaName())) { return columns; @@ -280,13 +290,6 @@ public Set filterColumns(ConnectorSecurityContext context, SchemaTableNa .collect(toImmutableSet()); } - @Override - public Map> filterColumns(ConnectorSecurityContext context, Map> tableColumns) - { - // Default implementation is good enough. Explicit implementation is expected by the test though. - return ConnectorAccessControl.super.filterColumns(context, tableColumns); - } - @Override public void checkCanRenameTable(ConnectorSecurityContext context, SchemaTableName tableName, SchemaTableName newTableName) { diff --git a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/ForwardingConnectorAccessControl.java b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/ForwardingConnectorAccessControl.java index bb4643aedd9d..f17efc069ead 100644 --- a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/ForwardingConnectorAccessControl.java +++ b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/ForwardingConnectorAccessControl.java @@ -157,12 +157,6 @@ public void checkCanShowColumns(ConnectorSecurityContext context, SchemaTableNam delegate().checkCanShowColumns(context, tableName); } - @Override - public Set filterColumns(ConnectorSecurityContext context, SchemaTableName tableName, Set columns) - { - return delegate().filterColumns(context, tableName, columns); - } - @Override public Map> filterColumns(ConnectorSecurityContext context, Map> tableColumns) { diff --git a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/ReadOnlyAccessControl.java b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/ReadOnlyAccessControl.java index 0d506c962678..0803a719ed50 100644 --- a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/ReadOnlyAccessControl.java +++ b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/ReadOnlyAccessControl.java @@ -143,12 +143,6 @@ public void checkCanShowColumns(ConnectorSecurityContext context, SchemaTableNam // allow } - @Override - public Set filterColumns(ConnectorSecurityContext context, SchemaTableName tableName, Set columns) - { - return columns; - } - @Override public Map> filterColumns(ConnectorSecurityContext context, Map> tableColumns) { diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedConnectorAccessControlTest.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedConnectorAccessControlTest.java index 1e2c0cabd4a9..dd5cb9fb8e16 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedConnectorAccessControlTest.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedConnectorAccessControlTest.java @@ -294,13 +294,13 @@ public void testTableRules() accessControl.checkCanShowColumns(ALICE, bobTable); assertEquals( - accessControl.filterColumns(ALICE, bobTable, ImmutableSet.of("a")), - ImmutableSet.of("a")); + accessControl.filterColumns(ALICE, Map.of(bobTable, ImmutableSet.of("a"))), + Map.of(bobTable, ImmutableSet.of("a"))); accessControl.checkCanSelectFromColumns(BOB, bobTable, ImmutableSet.of()); accessControl.checkCanShowColumns(BOB, bobTable); assertEquals( - accessControl.filterColumns(BOB, bobTable, ImmutableSet.of("a")), - ImmutableSet.of("a")); + accessControl.filterColumns(BOB, Map.of(bobTable, ImmutableSet.of("a"))), + Map.of(bobTable, ImmutableSet.of("a"))); accessControl.checkCanInsertIntoTable(BOB, bobTable); accessControl.checkCanDeleteFromTable(BOB, bobTable); @@ -491,11 +491,12 @@ public void testTableFilter() public void testNoTableRules() { ConnectorAccessControl accessControl = createAccessControl("no-access.json"); - assertDenied(() -> accessControl.checkCanShowColumns(BOB, new SchemaTableName("bobschema", "bobtable"))); + SchemaTableName bobTable = new SchemaTableName("bobschema", "bobtable"); + assertDenied(() -> accessControl.checkCanShowColumns(BOB, bobTable)); assertDenied(() -> accessControl.checkCanShowTables(BOB, "bobschema")); assertEquals( - accessControl.filterColumns(BOB, new SchemaTableName("bobschema", "bobtable"), ImmutableSet.of("a")), - ImmutableSet.of()); + accessControl.filterColumns(BOB, Map.of(bobTable, ImmutableSet.of("a"))), + Map.of(bobTable, ImmutableSet.of())); Set tables = ImmutableSet.builder() .add(new SchemaTableName("restricted", "any")) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/security/LegacyAccessControl.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/security/LegacyAccessControl.java index 8f28b3e075e8..877f33d1e01d 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/security/LegacyAccessControl.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/security/LegacyAccessControl.java @@ -188,12 +188,6 @@ public void checkCanShowColumns(ConnectorSecurityContext context, SchemaTableNam { } - @Override - public Set filterColumns(ConnectorSecurityContext context, SchemaTableName tableName, Set columns) - { - return columns; - } - @Override public Map> filterColumns(ConnectorSecurityContext context, Map> tableColumns) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/security/SqlStandardAccessControl.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/security/SqlStandardAccessControl.java index 47416b11133b..932c247e787e 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/security/SqlStandardAccessControl.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/security/SqlStandardAccessControl.java @@ -36,10 +36,12 @@ import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.stream.Stream; +import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static io.trino.plugin.hive.metastore.Database.DEFAULT_DATABASE_NAME; import static io.trino.plugin.hive.metastore.HivePrivilegeInfo.HivePrivilege; @@ -260,7 +262,15 @@ public void checkCanShowColumns(ConnectorSecurityContext context, SchemaTableNam } @Override - public Set filterColumns(ConnectorSecurityContext context, SchemaTableName tableName, Set columns) + public Map> filterColumns(ConnectorSecurityContext context, Map> tableColumns) + { + return tableColumns.entrySet().stream() + .collect(toImmutableMap( + Entry::getKey, + entry -> filterColumns(context, entry.getKey(), entry.getValue()))); + } + + private Set filterColumns(ConnectorSecurityContext context, SchemaTableName tableName, Set columns) { if (!hasAnyTablePermission(context, tableName)) { return ImmutableSet.of(); @@ -268,13 +278,6 @@ public Set filterColumns(ConnectorSecurityContext context, SchemaTableNa return columns; } - @Override - public Map> filterColumns(ConnectorSecurityContext context, Map> tableColumns) - { - // Default implementation is good enough. Explicit implementation is expected by the test though. - return ConnectorAccessControl.super.filterColumns(context, tableColumns); - } - @Override public void checkCanAddColumn(ConnectorSecurityContext context, SchemaTableName tableName) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/security/SystemTableAwareAccessControl.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/security/SystemTableAwareAccessControl.java index 08329e51f19f..c9a1a32c2806 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/security/SystemTableAwareAccessControl.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/security/SystemTableAwareAccessControl.java @@ -14,6 +14,8 @@ package io.trino.plugin.hive.security; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.inject.Inject; import io.trino.plugin.base.security.ForwardingConnectorAccessControl; import io.trino.plugin.hive.SystemTableProvider; @@ -22,9 +24,12 @@ import io.trino.spi.connector.SchemaTableName; import io.trino.spi.security.AccessDeniedException; +import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; import java.util.Set; +import static com.google.common.collect.ImmutableMap.toImmutableMap; import static io.trino.plugin.hive.util.SystemTables.getSourceTableNameFromSystemTable; import static io.trino.spi.security.AccessDeniedException.denySelectTable; import static io.trino.spi.security.AccessDeniedException.denyShowColumns; @@ -67,13 +72,23 @@ public void checkCanShowColumns(ConnectorSecurityContext context, SchemaTableNam } @Override - public Set filterColumns(ConnectorSecurityContext context, SchemaTableName tableName, Set columns) + public Map> filterColumns(ConnectorSecurityContext context, Map> tableColumns) + { + return tableColumns.entrySet().stream() + .collect(toImmutableMap( + Entry::getKey, + // TODO call delegate.filterColumns in bulk + entry -> filterColumns(context, entry.getKey(), entry.getValue()))); + } + + private Set filterColumns(ConnectorSecurityContext context, SchemaTableName tableName, Set columns) { Optional sourceTableName = getSourceTableNameFromSystemTable(systemTableProviders, tableName); if (sourceTableName.isPresent()) { + // TODO system table may have quite different columns that its source table. It's unclear why it's a good idea to conflate the two. return filterColumns(context, sourceTableName.get(), columns); } - return delegate.filterColumns(context, tableName, columns); + return delegate.filterColumns(context, ImmutableMap.of(tableName, columns)).getOrDefault(tableName, ImmutableSet.of()); } @Override From a5e2aa290e450a85d43fe36b5cdc6b102a40ea99 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Fri, 17 Nov 2023 16:39:59 +0100 Subject: [PATCH 310/587] Remove deprecated MaterializedViewFreshness constructor It's been deprecated since January 2023. --- .../src/main/java/io/trino/metadata/MetadataManager.java | 2 +- .../src/test/java/io/trino/connector/MockConnector.java | 2 +- .../io/trino/spi/connector/MaterializedViewFreshness.java | 6 ------ 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java b/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java index 8708411eb790..8146c5430499 100644 --- a/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java +++ b/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java @@ -1658,7 +1658,7 @@ public MaterializedViewFreshness getMaterializedViewFreshness(Session session, Q ConnectorSession connectorSession = session.toConnectorSession(catalogHandle); return metadata.getMaterializedViewFreshness(connectorSession, viewName.asSchemaTableName()); } - return new MaterializedViewFreshness(STALE); + return new MaterializedViewFreshness(STALE, Optional.empty()); } @Override diff --git a/core/trino-main/src/test/java/io/trino/connector/MockConnector.java b/core/trino-main/src/test/java/io/trino/connector/MockConnector.java index a4b9362cdb60..82bf03c0a12a 100644 --- a/core/trino-main/src/test/java/io/trino/connector/MockConnector.java +++ b/core/trino-main/src/test/java/io/trino/connector/MockConnector.java @@ -685,7 +685,7 @@ public MaterializedViewFreshness getMaterializedViewFreshness(ConnectorSession s { ConnectorMaterializedViewDefinition view = getMaterializedViews.apply(session, viewName.toSchemaTablePrefix()).get(viewName); checkArgument(view != null, "Materialized view %s does not exist", viewName); - return new MaterializedViewFreshness(view.getStorageTable().isPresent() ? FRESH : STALE); + return new MaterializedViewFreshness(view.getStorageTable().isPresent() ? FRESH : STALE, Optional.empty()); } @Override diff --git a/core/trino-spi/src/main/java/io/trino/spi/connector/MaterializedViewFreshness.java b/core/trino-spi/src/main/java/io/trino/spi/connector/MaterializedViewFreshness.java index 9d7ee5df9449..0cd3c986352e 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/connector/MaterializedViewFreshness.java +++ b/core/trino-spi/src/main/java/io/trino/spi/connector/MaterializedViewFreshness.java @@ -25,12 +25,6 @@ public final class MaterializedViewFreshness private final Freshness freshness; private final Optional lastFreshTime; - @Deprecated - public MaterializedViewFreshness(Freshness freshness) - { - this(freshness, Optional.empty()); - } - public MaterializedViewFreshness(Freshness freshness, Optional lastFreshTime) { this.freshness = requireNonNull(freshness, "freshness is null"); From 152e7a5ad087e612fc5d7016fa9f94b99393e398 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Fri, 17 Nov 2023 16:42:11 +0100 Subject: [PATCH 311/587] Remove deprecated MV Column constructor It's been deprecated for 10+ releases. --- .../io/trino/sql/query/TestColumnMask.java | 24 +++++++++---------- .../io/trino/testing/TestTestingMetadata.java | 2 +- .../ConnectorMaterializedViewDefinition.java | 6 ----- .../trino/plugin/hive/AbstractTestHive.java | 2 +- .../execution/TestEventListenerBasic.java | 2 +- .../TestRefreshMaterializedView.java | 2 +- .../io/trino/security/TestAccessControl.java | 2 +- .../io/trino/tests/TestMockConnector.java | 2 +- 8 files changed, 18 insertions(+), 24 deletions(-) diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestColumnMask.java b/core/trino-main/src/test/java/io/trino/sql/query/TestColumnMask.java index 9a1c569409c0..e1d6cc342035 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestColumnMask.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestColumnMask.java @@ -115,10 +115,10 @@ public TestColumnMask() Optional.empty(), Optional.empty(), ImmutableList.of( - new ConnectorMaterializedViewDefinition.Column("nationkey", BigintType.BIGINT.getTypeId()), - new ConnectorMaterializedViewDefinition.Column("name", VarcharType.createVarcharType(25).getTypeId()), - new ConnectorMaterializedViewDefinition.Column("regionkey", BigintType.BIGINT.getTypeId()), - new ConnectorMaterializedViewDefinition.Column("comment", VarcharType.createVarcharType(152).getTypeId())), + new ConnectorMaterializedViewDefinition.Column("nationkey", BigintType.BIGINT.getTypeId(), Optional.empty()), + new ConnectorMaterializedViewDefinition.Column("name", VarcharType.createVarcharType(25).getTypeId(), Optional.empty()), + new ConnectorMaterializedViewDefinition.Column("regionkey", BigintType.BIGINT.getTypeId(), Optional.empty()), + new ConnectorMaterializedViewDefinition.Column("comment", VarcharType.createVarcharType(152).getTypeId(), Optional.empty())), Optional.of(Duration.ZERO), Optional.empty(), Optional.of(VIEW_OWNER), @@ -131,10 +131,10 @@ public TestColumnMask() Optional.empty(), Optional.empty(), ImmutableList.of( - new ConnectorMaterializedViewDefinition.Column("nationkey", BigintType.BIGINT.getTypeId()), - new ConnectorMaterializedViewDefinition.Column("name", VarcharType.createVarcharType(25).getTypeId()), - new ConnectorMaterializedViewDefinition.Column("regionkey", BigintType.BIGINT.getTypeId()), - new ConnectorMaterializedViewDefinition.Column("comment", VarcharType.createVarcharType(152).getTypeId())), + new ConnectorMaterializedViewDefinition.Column("nationkey", BigintType.BIGINT.getTypeId(), Optional.empty()), + new ConnectorMaterializedViewDefinition.Column("name", VarcharType.createVarcharType(25).getTypeId(), Optional.empty()), + new ConnectorMaterializedViewDefinition.Column("regionkey", BigintType.BIGINT.getTypeId(), Optional.empty()), + new ConnectorMaterializedViewDefinition.Column("comment", VarcharType.createVarcharType(152).getTypeId(), Optional.empty())), Optional.of(Duration.ZERO), Optional.empty(), Optional.of(VIEW_OWNER), @@ -147,10 +147,10 @@ public TestColumnMask() Optional.empty(), Optional.empty(), ImmutableList.of( - new ConnectorMaterializedViewDefinition.Column("nationkey", BigintType.BIGINT.getTypeId()), - new ConnectorMaterializedViewDefinition.Column("name", VarcharType.createVarcharType(2).getTypeId()), - new ConnectorMaterializedViewDefinition.Column("regionkey", BigintType.BIGINT.getTypeId()), - new ConnectorMaterializedViewDefinition.Column("comment", VarcharType.createVarcharType(152).getTypeId())), + new ConnectorMaterializedViewDefinition.Column("nationkey", BigintType.BIGINT.getTypeId(), Optional.empty()), + new ConnectorMaterializedViewDefinition.Column("name", VarcharType.createVarcharType(2).getTypeId(), Optional.empty()), + new ConnectorMaterializedViewDefinition.Column("regionkey", BigintType.BIGINT.getTypeId(), Optional.empty()), + new ConnectorMaterializedViewDefinition.Column("comment", VarcharType.createVarcharType(152).getTypeId(), Optional.empty())), Optional.of(Duration.ZERO), Optional.empty(), Optional.of(VIEW_OWNER), diff --git a/core/trino-main/src/test/java/io/trino/testing/TestTestingMetadata.java b/core/trino-main/src/test/java/io/trino/testing/TestTestingMetadata.java index 8abce5709f1b..8b5bad533ce5 100644 --- a/core/trino-main/src/test/java/io/trino/testing/TestTestingMetadata.java +++ b/core/trino-main/src/test/java/io/trino/testing/TestTestingMetadata.java @@ -58,7 +58,7 @@ private static ConnectorMaterializedViewDefinition someMaterializedView() Optional.empty(), Optional.empty(), Optional.empty(), - ImmutableList.of(new Column("test", BIGINT.getTypeId())), + ImmutableList.of(new Column("test", BIGINT.getTypeId(), Optional.empty())), Optional.of(Duration.ZERO), Optional.empty(), Optional.of("owner"), diff --git a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMaterializedViewDefinition.java b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMaterializedViewDefinition.java index 70523c97f018..98a429d3dcee 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMaterializedViewDefinition.java +++ b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMaterializedViewDefinition.java @@ -172,12 +172,6 @@ public static final class Column private final TypeId type; private final Optional comment; - @Deprecated - public Column(String name, TypeId type) - { - this(name, type, Optional.empty()); - } - public Column(String name, TypeId type, Optional comment) { this.name = requireNonNull(name, "name is null"); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java index 8cbba9f75c2b..14767cbde0d6 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java @@ -884,7 +884,7 @@ public Optional getMaterializedView(Connect Optional.empty(), Optional.empty(), Optional.empty(), - ImmutableList.of(new ConnectorMaterializedViewDefinition.Column("abc", TypeId.of("type"))), + ImmutableList.of(new ConnectorMaterializedViewDefinition.Column("abc", TypeId.of("type"), Optional.empty())), Optional.of(java.time.Duration.ZERO), Optional.empty(), Optional.of("alice"), diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java b/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java index 200f0ddf307f..7f3c05ffcff7 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java @@ -167,7 +167,7 @@ public Iterable getConnectorFactories() Optional.empty(), Optional.empty(), Optional.empty(), - ImmutableList.of(new Column("test_column", BIGINT.getTypeId())), + ImmutableList.of(new Column("test_column", BIGINT.getTypeId(), Optional.empty())), Optional.of(Duration.ZERO), Optional.empty(), Optional.of("alice"), diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestRefreshMaterializedView.java b/testing/trino-tests/src/test/java/io/trino/execution/TestRefreshMaterializedView.java index 6c45998fbf4f..b805209c0b6b 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestRefreshMaterializedView.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestRefreshMaterializedView.java @@ -102,7 +102,7 @@ protected QueryRunner createQueryRunner() Optional.of(new CatalogSchemaTableName("mock", "default", "test_storage")), Optional.of("mock"), Optional.of("default"), - ImmutableList.of(new ConnectorMaterializedViewDefinition.Column("nationkey", BIGINT.getTypeId())), + ImmutableList.of(new ConnectorMaterializedViewDefinition.Column("nationkey", BIGINT.getTypeId(), Optional.empty())), Optional.of(Duration.ZERO), Optional.empty(), Optional.of("alice"), diff --git a/testing/trino-tests/src/test/java/io/trino/security/TestAccessControl.java b/testing/trino-tests/src/test/java/io/trino/security/TestAccessControl.java index 195d43bae951..6624bfbbf9db 100644 --- a/testing/trino-tests/src/test/java/io/trino/security/TestAccessControl.java +++ b/testing/trino-tests/src/test/java/io/trino/security/TestAccessControl.java @@ -210,7 +210,7 @@ public Map apply(Connector Optional.empty(), Optional.empty(), Optional.empty(), - ImmutableList.of(new ConnectorMaterializedViewDefinition.Column("test", BIGINT.getTypeId())), + ImmutableList.of(new ConnectorMaterializedViewDefinition.Column("test", BIGINT.getTypeId(), Optional.empty())), Optional.of(Duration.ZERO), Optional.of("comment"), Optional.of("owner"), diff --git a/testing/trino-tests/src/test/java/io/trino/tests/TestMockConnector.java b/testing/trino-tests/src/test/java/io/trino/tests/TestMockConnector.java index 8defcb268fdb..62e927c58b65 100644 --- a/testing/trino-tests/src/test/java/io/trino/tests/TestMockConnector.java +++ b/testing/trino-tests/src/test/java/io/trino/tests/TestMockConnector.java @@ -102,7 +102,7 @@ protected QueryRunner createQueryRunner() Optional.of(new CatalogSchemaTableName("mock", "default", "test_storage")), Optional.of("mock"), Optional.of("default"), - ImmutableList.of(new Column("nationkey", BIGINT.getTypeId())), + ImmutableList.of(new Column("nationkey", BIGINT.getTypeId(), Optional.empty())), Optional.of(Duration.ZERO), Optional.empty(), Optional.of("alice"), From 2f5fb835e4b0e53c99f00c6f910742159e597b35 Mon Sep 17 00:00:00 2001 From: Marius Grama Date: Fri, 17 Nov 2023 06:41:28 +0100 Subject: [PATCH 312/587] Document schema evolution for Iceberg --- docs/src/main/sphinx/connector/iceberg.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/src/main/sphinx/connector/iceberg.md b/docs/src/main/sphinx/connector/iceberg.md index e70440af65da..16d17dafb592 100644 --- a/docs/src/main/sphinx/connector/iceberg.md +++ b/docs/src/main/sphinx/connector/iceberg.md @@ -548,9 +548,16 @@ The {ref}`sql-schema-table-management` functionality includes support for: #### Schema evolution Iceberg supports schema evolution, with safe column add, drop, reorder, and -rename operations, including in nested structures. Table partitioning can also -be changed and the connector can still query data created before the -partitioning change. +rename operations, including in nested structures. + +Iceberg supports updating column types only for widening operations: + +- `INTEGER` to `BIGINT` +- `REAL` to `DOUBLE` +- `DECIMAL(p,s)` to `DECIMAL(p2,s)` when `p2` > `p` (scale cannot change) + +Partitioning can also be changed and the connector can still query data +created before the partitioning change. (iceberg-alter-table-execute)= From 9ee100d53ae376588f4b5202e21b356e6425fab5 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Thu, 16 Nov 2023 16:34:51 -0800 Subject: [PATCH 313/587] Simplify constant property values --- .../athena/PartitionProjectionProperties.java | 55 ++++--------------- 1 file changed, 12 insertions(+), 43 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionProperties.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionProperties.java index 38733e0f1f74..0a496803117d 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionProperties.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionProperties.java @@ -22,15 +22,6 @@ public final class PartitionProjectionProperties { - /** - * General properties suffixes - */ - static final String TABLE_PROJECTION_ENABLED_SUFFIX = "enabled"; - /** - * Forces Trino to not use Athena table projection for a given table. - * Kill switch to be used as a workaround if compatibility issues are found. - */ - static final String TABLE_PROJECTION_IGNORE_SUFFIX = "ignore"; static final String COLUMN_PROJECTION_TYPE_SUFFIX = "type"; static final String COLUMN_PROJECTION_VALUES_SUFFIX = "values"; static final String COLUMN_PROJECTION_RANGE_SUFFIX = "range"; @@ -38,49 +29,29 @@ public final class PartitionProjectionProperties static final String COLUMN_PROJECTION_DIGITS_SUFFIX = "digits"; static final String COLUMN_PROJECTION_FORMAT_SUFFIX = "format"; - /** - * Metastore table properties - */ - private static final String METASTORE_PROPERTY_SEPARATOR = "."; + static final String METASTORE_PROPERTY_PROJECTION_INTERVAL_UNIT_SUFFIX = "interval.unit"; - private static final String METASTORE_PROPERTY_PREFIX = "projection" + METASTORE_PROPERTY_SEPARATOR; + static final String METASTORE_PROPERTY_PROJECTION_ENABLED = "projection.enabled"; + static final String METASTORE_PROPERTY_PROJECTION_LOCATION_TEMPLATE = "storage.location.template"; + static final String METASTORE_PROPERTY_PROJECTION_IGNORE = "trino.partition_projection.ignore"; - static final String METASTORE_PROPERTY_PROJECTION_INTERVAL_UNIT_SUFFIX = "interval" + METASTORE_PROPERTY_SEPARATOR + "unit"; + static final String PROPERTY_KEY_PREFIX = "partition_projection_"; - static final String METASTORE_PROPERTY_PROJECTION_ENABLED = METASTORE_PROPERTY_PREFIX + TABLE_PROJECTION_ENABLED_SUFFIX; - static final String METASTORE_PROPERTY_PROJECTION_LOCATION_TEMPLATE = "storage" + METASTORE_PROPERTY_SEPARATOR + "location" + METASTORE_PROPERTY_SEPARATOR + "template"; - /** - * See {@link #TABLE_PROJECTION_IGNORE_SUFFIX } to understand duplication with enable property - **/ - static final String METASTORE_PROPERTY_PROJECTION_IGNORE = "trino" + METASTORE_PROPERTY_SEPARATOR + "partition_projection" + METASTORE_PROPERTY_SEPARATOR + TABLE_PROJECTION_IGNORE_SUFFIX; - - /** - * Trino table properties - */ - private static final String PROPERTY_KEY_SEPARATOR = "_"; - - static final String PROPERTY_KEY_PREFIX = "partition" + PROPERTY_KEY_SEPARATOR + "projection" + PROPERTY_KEY_SEPARATOR; - - private static final String PROPERTY_KEY_SUFFIX_COLUMN_PROJECTION_INTERVAL_UNIT = "interval" + PROPERTY_KEY_SEPARATOR + "unit"; - - public static final String PARTITION_PROJECTION_ENABLED = PROPERTY_KEY_PREFIX + TABLE_PROJECTION_ENABLED_SUFFIX; - public static final String PARTITION_PROJECTION_LOCATION_TEMPLATE = PROPERTY_KEY_PREFIX + "location" + PROPERTY_KEY_SEPARATOR + "template"; - /** - * See {@link #TABLE_PROJECTION_IGNORE_SUFFIX } to understand duplication with enable property - **/ - public static final String PARTITION_PROJECTION_IGNORE = PROPERTY_KEY_PREFIX + TABLE_PROJECTION_IGNORE_SUFFIX; + public static final String PARTITION_PROJECTION_ENABLED = PROPERTY_KEY_PREFIX + "enabled"; + public static final String PARTITION_PROJECTION_LOCATION_TEMPLATE = PROPERTY_KEY_PREFIX + "location_template"; + public static final String PARTITION_PROJECTION_IGNORE = PROPERTY_KEY_PREFIX + "ignore"; public static final String COLUMN_PROJECTION_TYPE = PROPERTY_KEY_PREFIX + COLUMN_PROJECTION_TYPE_SUFFIX; public static final String COLUMN_PROJECTION_VALUES = PROPERTY_KEY_PREFIX + COLUMN_PROJECTION_VALUES_SUFFIX; public static final String COLUMN_PROJECTION_RANGE = PROPERTY_KEY_PREFIX + COLUMN_PROJECTION_RANGE_SUFFIX; public static final String COLUMN_PROJECTION_INTERVAL = PROPERTY_KEY_PREFIX + COLUMN_PROJECTION_INTERVAL_SUFFIX; - public static final String COLUMN_PROJECTION_INTERVAL_UNIT = PROPERTY_KEY_PREFIX + PROPERTY_KEY_SUFFIX_COLUMN_PROJECTION_INTERVAL_UNIT; + public static final String COLUMN_PROJECTION_INTERVAL_UNIT = PROPERTY_KEY_PREFIX + "interval_unit"; public static final String COLUMN_PROJECTION_DIGITS = PROPERTY_KEY_PREFIX + COLUMN_PROJECTION_DIGITS_SUFFIX; public static final String COLUMN_PROJECTION_FORMAT = PROPERTY_KEY_PREFIX + COLUMN_PROJECTION_FORMAT_SUFFIX; static String getMetastoreProjectionPropertyKey(String columnName, String propertyKeySuffix) { - return METASTORE_PROPERTY_PREFIX + columnName + METASTORE_PROPERTY_SEPARATOR + propertyKeySuffix; + return "projection" + "." + columnName + "." + propertyKeySuffix; } public static T getProjectionPropertyRequiredValue( @@ -100,10 +71,8 @@ public static Optional getProjectionPropertyValue( { return Optional.ofNullable( columnProjectionProperties.get(propertyKey)) - .map(value -> decoder.apply(value)); + .map(decoder); } - private PartitionProjectionProperties() - { - } + private PartitionProjectionProperties() {} } From e6b6dc71f26f173d4796543eb46d365154174602 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Thu, 16 Nov 2023 16:43:42 -0800 Subject: [PATCH 314/587] Remove multibinder for ProjectionFactory ProjectionFactory implementations are simple stateless classes, and new implementations can not be added due to the mapping to a Java enum, so the multibinder it not needed. --- .../aws/athena/PartitionProjectionModule.java | 15 -------------- .../athena/PartitionProjectionService.java | 20 ++++++++++++++----- .../trino/plugin/hive/AbstractTestHive.java | 2 +- .../hive/AbstractTestHiveFileSystem.java | 2 +- 4 files changed, 17 insertions(+), 22 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionModule.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionModule.java index e1c3ae1ff185..fce6ae402747 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionModule.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionModule.java @@ -15,17 +15,9 @@ import com.google.inject.Binder; import com.google.inject.Scopes; -import com.google.inject.multibindings.MapBinder; import io.airlift.configuration.AbstractConfigurationAwareModule; -import io.trino.plugin.hive.aws.athena.projection.DateProjectionFactory; -import io.trino.plugin.hive.aws.athena.projection.EnumProjectionFactory; -import io.trino.plugin.hive.aws.athena.projection.InjectedProjectionFactory; -import io.trino.plugin.hive.aws.athena.projection.IntegerProjectionFactory; -import io.trino.plugin.hive.aws.athena.projection.ProjectionFactory; -import io.trino.plugin.hive.aws.athena.projection.ProjectionType; import io.trino.plugin.hive.metastore.HiveMetastoreDecorator; -import static com.google.inject.multibindings.MapBinder.newMapBinder; import static com.google.inject.multibindings.Multibinder.newSetBinder; public class PartitionProjectionModule @@ -34,13 +26,6 @@ public class PartitionProjectionModule @Override public void setup(Binder binder) { - MapBinder projectionFactoriesBinder = - newMapBinder(binder, ProjectionType.class, ProjectionFactory.class); - projectionFactoriesBinder.addBinding(ProjectionType.ENUM).to(EnumProjectionFactory.class).in(Scopes.SINGLETON); - projectionFactoriesBinder.addBinding(ProjectionType.INTEGER).to(IntegerProjectionFactory.class).in(Scopes.SINGLETON); - projectionFactoriesBinder.addBinding(ProjectionType.DATE).to(DateProjectionFactory.class).in(Scopes.SINGLETON); - projectionFactoriesBinder.addBinding(ProjectionType.INJECTED).to(InjectedProjectionFactory.class).in(Scopes.SINGLETON); - binder.bind(PartitionProjectionService.class).in(Scopes.SINGLETON); newSetBinder(binder, HiveMetastoreDecorator.class) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java index d9175050adec..0e732837cc14 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java @@ -19,6 +19,10 @@ import com.google.common.collect.ImmutableSet; import com.google.inject.Inject; import io.trino.plugin.hive.HiveConfig; +import io.trino.plugin.hive.aws.athena.projection.DateProjectionFactory; +import io.trino.plugin.hive.aws.athena.projection.EnumProjectionFactory; +import io.trino.plugin.hive.aws.athena.projection.InjectedProjectionFactory; +import io.trino.plugin.hive.aws.athena.projection.IntegerProjectionFactory; import io.trino.plugin.hive.aws.athena.projection.Projection; import io.trino.plugin.hive.aws.athena.projection.ProjectionFactory; import io.trino.plugin.hive.aws.athena.projection.ProjectionType; @@ -67,6 +71,10 @@ import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.PROPERTY_KEY_PREFIX; import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getMetastoreProjectionPropertyKey; import static io.trino.plugin.hive.aws.athena.projection.Projection.unsupportedProjectionColumnTypeException; +import static io.trino.plugin.hive.aws.athena.projection.ProjectionType.DATE; +import static io.trino.plugin.hive.aws.athena.projection.ProjectionType.ENUM; +import static io.trino.plugin.hive.aws.athena.projection.ProjectionType.INJECTED; +import static io.trino.plugin.hive.aws.athena.projection.ProjectionType.INTEGER; import static io.trino.spi.StandardErrorCode.INVALID_COLUMN_PROPERTY; import static java.lang.String.format; import static java.util.Objects.requireNonNull; @@ -79,14 +87,16 @@ public final class PartitionProjectionService private final TypeManager typeManager; @Inject - public PartitionProjectionService( - HiveConfig hiveConfig, - Map projectionFactories, - TypeManager typeManager) + public PartitionProjectionService(HiveConfig hiveConfig, TypeManager typeManager) { this.partitionProjectionEnabled = hiveConfig.isPartitionProjectionEnabled(); this.typeManager = requireNonNull(typeManager, "typeManager is null"); - this.projectionFactories = ImmutableMap.copyOf(requireNonNull(projectionFactories, "projectionFactories is null")); + this.projectionFactories = ImmutableMap.builder() + .put(ENUM, new EnumProjectionFactory()) + .put(INTEGER, new IntegerProjectionFactory()) + .put(DATE, new DateProjectionFactory()) + .put(INJECTED, new InjectedProjectionFactory()) + .buildOrThrow(); } public Map getPartitionProjectionTrinoTableProperties(Table table) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java index 14767cbde0d6..ed31db237ff0 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java @@ -895,7 +895,7 @@ public Optional getMaterializedView(Connect SqlStandardAccessControlMetadata::new, countingDirectoryLister, new TransactionScopeCachingDirectoryListerFactory(hiveConfig), - new PartitionProjectionService(hiveConfig, ImmutableMap.of(), new TestingTypeManager()), + new PartitionProjectionService(hiveConfig, new TestingTypeManager()), true, HiveTimestampPrecision.DEFAULT_PRECISION); transactionManager = new HiveTransactionManager(metadataFactory); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java index 86d89f66fb67..623622630f65 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java @@ -242,7 +242,7 @@ protected void setup(String host, int port, String databaseName, HdfsConfigurati SqlStandardAccessControlMetadata::new, new FileSystemDirectoryLister(), new TransactionScopeCachingDirectoryListerFactory(config), - new PartitionProjectionService(config, ImmutableMap.of(), new TestingTypeManager()), + new PartitionProjectionService(config, new TestingTypeManager()), true); transactionManager = new HiveTransactionManager(metadataFactory); splitManager = new HiveSplitManager( From 6394acc0f2d65a02be743f00496dd9c0c77ca293 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Thu, 16 Nov 2023 16:49:13 -0800 Subject: [PATCH 315/587] Move projection classes to parent package --- .../trino/plugin/hive/HiveColumnProperties.java | 2 +- .../athena/{projection => }/DateProjection.java | 4 ++-- .../{projection => }/DateProjectionFactory.java | 4 ++-- .../athena/{projection => }/EnumProjection.java | 2 +- .../{projection => }/EnumProjectionFactory.java | 2 +- .../{projection => }/InjectedProjection.java | 2 +- .../InjectedProjectionFactory.java | 2 +- .../{projection => }/IntegerProjection.java | 2 +- .../IntegerProjectionFactory.java | 4 ++-- .../hive/aws/athena/PartitionProjection.java | 3 +-- .../athena/PartitionProjectionProperties.java | 2 +- .../aws/athena/PartitionProjectionService.java | 17 +++++------------ .../aws/athena/{projection => }/Projection.java | 2 +- .../{projection => }/ProjectionFactory.java | 2 +- .../athena/{projection => }/ProjectionType.java | 2 +- 15 files changed, 22 insertions(+), 30 deletions(-) rename plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/{projection => }/DateProjection.java (96%) rename plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/{projection => }/DateProjectionFactory.java (98%) rename plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/{projection => }/EnumProjection.java (97%) rename plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/{projection => }/EnumProjectionFactory.java (97%) rename plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/{projection => }/InjectedProjection.java (97%) rename plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/{projection => }/InjectedProjectionFactory.java (95%) rename plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/{projection => }/IntegerProjection.java (98%) rename plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/{projection => }/IntegerProjectionFactory.java (94%) rename plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/{projection => }/Projection.java (97%) rename plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/{projection => }/ProjectionFactory.java (93%) rename plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/{projection => }/ProjectionType.java (92%) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveColumnProperties.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveColumnProperties.java index 82eafa1c04c5..955ed02da57c 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveColumnProperties.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveColumnProperties.java @@ -14,7 +14,7 @@ package io.trino.plugin.hive; import com.google.common.collect.ImmutableList; -import io.trino.plugin.hive.aws.athena.projection.ProjectionType; +import io.trino.plugin.hive.aws.athena.ProjectionType; import io.trino.spi.session.PropertyMetadata; import io.trino.spi.type.ArrayType; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/DateProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjection.java similarity index 96% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/DateProjection.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjection.java index 20d9408f3aa3..f4112457d74d 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/DateProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjection.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.aws.athena.projection; +package io.trino.plugin.hive.aws.athena; import com.google.common.collect.ImmutableList; import io.trino.spi.predicate.Domain; @@ -31,7 +31,7 @@ import java.util.function.Supplier; import static io.airlift.slice.Slices.utf8Slice; -import static io.trino.plugin.hive.aws.athena.projection.DateProjectionFactory.UTC_TIME_ZONE_ID; +import static io.trino.plugin.hive.aws.athena.DateProjectionFactory.UTC_TIME_ZONE_ID; import static io.trino.spi.predicate.Domain.singleValue; import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.MILLISECONDS; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/DateProjectionFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjectionFactory.java similarity index 98% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/DateProjectionFactory.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjectionFactory.java index 8f6914a2f6e8..a6bf0abf8414 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/DateProjectionFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjectionFactory.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.aws.athena.projection; +package io.trino.plugin.hive.aws.athena; import com.google.common.collect.ImmutableSet; import io.trino.spi.TrinoException; @@ -41,7 +41,7 @@ import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_RANGE; import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getProjectionPropertyRequiredValue; import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getProjectionPropertyValue; -import static io.trino.plugin.hive.aws.athena.projection.Projection.invalidProjectionException; +import static io.trino.plugin.hive.aws.athena.Projection.invalidProjectionException; import static java.lang.String.format; import static java.time.temporal.ChronoUnit.DAYS; import static java.time.temporal.ChronoUnit.HOURS; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/EnumProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjection.java similarity index 97% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/EnumProjection.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjection.java index 50eb7d1a63fd..9592e43e869a 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/EnumProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjection.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.aws.athena.projection; +package io.trino.plugin.hive.aws.athena; import com.google.common.collect.ImmutableList; import io.trino.spi.predicate.Domain; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/EnumProjectionFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjectionFactory.java similarity index 97% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/EnumProjectionFactory.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjectionFactory.java index 13e897f78204..e186b82339f9 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/EnumProjectionFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjectionFactory.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.aws.athena.projection; +package io.trino.plugin.hive.aws.athena; import io.trino.spi.type.Type; import io.trino.spi.type.VarcharType; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/InjectedProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjection.java similarity index 97% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/InjectedProjection.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjection.java index 3c0d3c9ae423..edd6876729f4 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/InjectedProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjection.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.aws.athena.projection; +package io.trino.plugin.hive.aws.athena; import com.google.common.collect.ImmutableList; import io.trino.spi.predicate.Domain; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/InjectedProjectionFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjectionFactory.java similarity index 95% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/InjectedProjectionFactory.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjectionFactory.java index b90d0acc6ddb..4c4473d9da81 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/InjectedProjectionFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjectionFactory.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.aws.athena.projection; +package io.trino.plugin.hive.aws.athena; import io.trino.spi.type.Type; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/IntegerProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjection.java similarity index 98% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/IntegerProjection.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjection.java index 645b94ca6908..2577e30410a6 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/IntegerProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjection.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.aws.athena.projection; +package io.trino.plugin.hive.aws.athena; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/IntegerProjectionFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjectionFactory.java similarity index 94% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/IntegerProjectionFactory.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjectionFactory.java index 7117221c6fea..9ded00d3e25e 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/IntegerProjectionFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjectionFactory.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.aws.athena.projection; +package io.trino.plugin.hive.aws.athena; import io.trino.spi.type.BigintType; import io.trino.spi.type.IntegerType; @@ -27,7 +27,7 @@ import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_RANGE; import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getProjectionPropertyRequiredValue; import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getProjectionPropertyValue; -import static io.trino.plugin.hive.aws.athena.projection.Projection.invalidProjectionException; +import static io.trino.plugin.hive.aws.athena.Projection.invalidProjectionException; import static java.lang.String.format; public class IntegerProjectionFactory diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjection.java index e20d8ed7f244..5d6410b97bef 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjection.java @@ -15,7 +15,6 @@ import com.google.common.base.VerifyException; import com.google.common.collect.ImmutableMap; -import io.trino.plugin.hive.aws.athena.projection.Projection; import io.trino.plugin.hive.metastore.Partition; import io.trino.plugin.hive.metastore.Table; import io.trino.spi.predicate.Domain; @@ -33,7 +32,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.collect.Sets.cartesianProduct; -import static io.trino.plugin.hive.aws.athena.projection.Projection.invalidProjectionMessage; +import static io.trino.plugin.hive.aws.athena.Projection.invalidProjectionMessage; import static io.trino.plugin.hive.util.HiveUtil.escapePathName; import static io.trino.plugin.hive.util.HiveUtil.toPartitionValues; import static java.lang.String.format; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionProperties.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionProperties.java index 0a496803117d..80a5e8745303 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionProperties.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionProperties.java @@ -17,7 +17,7 @@ import java.util.Optional; import java.util.function.Function; -import static io.trino.plugin.hive.aws.athena.projection.Projection.invalidProjectionException; +import static io.trino.plugin.hive.aws.athena.Projection.invalidProjectionException; import static java.lang.String.format; public final class PartitionProjectionProperties diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java index 0e732837cc14..1b6fe7f7f0d8 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java @@ -19,13 +19,6 @@ import com.google.common.collect.ImmutableSet; import com.google.inject.Inject; import io.trino.plugin.hive.HiveConfig; -import io.trino.plugin.hive.aws.athena.projection.DateProjectionFactory; -import io.trino.plugin.hive.aws.athena.projection.EnumProjectionFactory; -import io.trino.plugin.hive.aws.athena.projection.InjectedProjectionFactory; -import io.trino.plugin.hive.aws.athena.projection.IntegerProjectionFactory; -import io.trino.plugin.hive.aws.athena.projection.Projection; -import io.trino.plugin.hive.aws.athena.projection.ProjectionFactory; -import io.trino.plugin.hive.aws.athena.projection.ProjectionType; import io.trino.plugin.hive.metastore.Column; import io.trino.plugin.hive.metastore.Table; import io.trino.spi.TrinoException; @@ -70,11 +63,11 @@ import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.PARTITION_PROJECTION_LOCATION_TEMPLATE; import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.PROPERTY_KEY_PREFIX; import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getMetastoreProjectionPropertyKey; -import static io.trino.plugin.hive.aws.athena.projection.Projection.unsupportedProjectionColumnTypeException; -import static io.trino.plugin.hive.aws.athena.projection.ProjectionType.DATE; -import static io.trino.plugin.hive.aws.athena.projection.ProjectionType.ENUM; -import static io.trino.plugin.hive.aws.athena.projection.ProjectionType.INJECTED; -import static io.trino.plugin.hive.aws.athena.projection.ProjectionType.INTEGER; +import static io.trino.plugin.hive.aws.athena.Projection.unsupportedProjectionColumnTypeException; +import static io.trino.plugin.hive.aws.athena.ProjectionType.DATE; +import static io.trino.plugin.hive.aws.athena.ProjectionType.ENUM; +import static io.trino.plugin.hive.aws.athena.ProjectionType.INJECTED; +import static io.trino.plugin.hive.aws.athena.ProjectionType.INTEGER; import static io.trino.spi.StandardErrorCode.INVALID_COLUMN_PROPERTY; import static java.lang.String.format; import static java.util.Objects.requireNonNull; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/Projection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/Projection.java similarity index 97% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/Projection.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/Projection.java index 40066baf357f..ff696cb514c6 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/Projection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/Projection.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.aws.athena.projection; +package io.trino.plugin.hive.aws.athena; import io.trino.spi.TrinoException; import io.trino.spi.predicate.Domain; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/ProjectionFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/ProjectionFactory.java similarity index 93% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/ProjectionFactory.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/ProjectionFactory.java index 88d640f4bc89..91e3fc06a3db 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/ProjectionFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/ProjectionFactory.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.aws.athena.projection; +package io.trino.plugin.hive.aws.athena; import io.trino.spi.type.Type; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/ProjectionType.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/ProjectionType.java similarity index 92% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/ProjectionType.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/ProjectionType.java index f5eca8363cc0..37419ebdaaa3 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/projection/ProjectionType.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/ProjectionType.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.aws.athena.projection; +package io.trino.plugin.hive.aws.athena; public enum ProjectionType { From 19ade7f84cf76d6f48afbe5947d4159203a5ce3f Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Fri, 17 Nov 2023 14:58:46 -0800 Subject: [PATCH 316/587] Add partition projection tests --- .../aws/athena/TestDateProjectionFactory.java | 63 ++++++++++++++ .../aws/athena/TestEnumProjectionFactory.java | 57 +++++++++++++ .../athena/TestInjectedProjectionFactory.java | 61 ++++++++++++++ .../athena/TestIntegerProjectionFactory.java | 84 +++++++++++++++++++ 4 files changed, 265 insertions(+) create mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestDateProjectionFactory.java create mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestEnumProjectionFactory.java create mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestInjectedProjectionFactory.java create mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestIntegerProjectionFactory.java diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestDateProjectionFactory.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestDateProjectionFactory.java new file mode 100644 index 000000000000..69af8d65a828 --- /dev/null +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestDateProjectionFactory.java @@ -0,0 +1,63 @@ +/* + * 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 io.trino.plugin.hive.aws.athena; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.airlift.slice.Slices; +import io.trino.spi.TrinoException; +import io.trino.spi.predicate.Domain; +import org.junit.jupiter.api.Test; + +import java.util.Optional; + +import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_FORMAT; +import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_RANGE; +import static io.trino.spi.type.BigintType.BIGINT; +import static io.trino.spi.type.DateType.DATE; +import static io.trino.spi.type.TimestampType.TIMESTAMP_MICROS; +import static io.trino.spi.type.TimestampType.TIMESTAMP_NANOS; +import static io.trino.spi.type.TimestampType.TIMESTAMP_SECONDS; +import static io.trino.spi.type.VarcharType.VARCHAR; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class TestDateProjectionFactory +{ + @Test + void testIsSupported() + { + assertThat(new DateProjectionFactory().isSupportedColumnType(VARCHAR)).isTrue(); + assertThat(new DateProjectionFactory().isSupportedColumnType(DATE)).isTrue(); + assertThat(new DateProjectionFactory().isSupportedColumnType(TIMESTAMP_SECONDS)).isTrue(); + assertThat(new DateProjectionFactory().isSupportedColumnType(TIMESTAMP_MICROS)).isTrue(); + assertThat(new DateProjectionFactory().isSupportedColumnType(TIMESTAMP_NANOS)).isFalse(); + assertThat(new DateProjectionFactory().isSupportedColumnType(BIGINT)).isFalse(); + } + + @Test + void testCreate() + { + Projection projection = new DateProjectionFactory().create("test", VARCHAR, ImmutableMap.of(COLUMN_PROJECTION_FORMAT, "yyyy-MM-dd", COLUMN_PROJECTION_RANGE, ImmutableList.of("2020-01-01", "2020-01-03"))); + assertThat(projection.getProjectedValues(Optional.empty())).containsExactly("2020-01-01", "2020-01-02", "2020-01-03"); + assertThat(projection.getProjectedValues(Optional.of(Domain.all(VARCHAR)))).containsExactly("2020-01-01", "2020-01-02", "2020-01-03"); + assertThat(projection.getProjectedValues(Optional.of(Domain.none(VARCHAR)))).isEmpty(); + assertThat(projection.getProjectedValues(Optional.of(Domain.singleValue(VARCHAR, Slices.utf8Slice("2020-01-02"))))).containsExactly("2020-01-02"); + assertThat(projection.getProjectedValues(Optional.of(Domain.singleValue(VARCHAR, Slices.utf8Slice("2222-01-01"))))).isEmpty(); + + assertThatThrownBy(() -> new DateProjectionFactory().create("test", VARCHAR, ImmutableMap.of("ignored", ImmutableList.of("2020-01-01", "2020-01-02", "2020-01-03")))) + .isInstanceOf(TrinoException.class) + .hasMessage("Column projection for column 'test' failed. Missing required property: 'partition_projection_format'"); + } +} diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestEnumProjectionFactory.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestEnumProjectionFactory.java new file mode 100644 index 000000000000..0ac557c2363b --- /dev/null +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestEnumProjectionFactory.java @@ -0,0 +1,57 @@ +/* + * 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 io.trino.plugin.hive.aws.athena; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.airlift.slice.Slices; +import io.trino.spi.TrinoException; +import io.trino.spi.predicate.Domain; +import org.junit.jupiter.api.Test; + +import java.util.Optional; + +import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_VALUES; +import static io.trino.spi.type.BigintType.BIGINT; +import static io.trino.spi.type.VarcharType.VARCHAR; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class TestEnumProjectionFactory +{ + @Test + void testIsSupported() + { + assertThat(new EnumProjectionFactory().isSupportedColumnType(VARCHAR)).isTrue(); + assertThat(new EnumProjectionFactory().isSupportedColumnType(BIGINT)).isFalse(); + } + + @Test + void testCreate() + { + Projection projection = new EnumProjectionFactory().create("test", VARCHAR, ImmutableMap.of(COLUMN_PROJECTION_VALUES, ImmutableList.of("a", "b", "c"))); + assertThat(projection.getProjectedValues(Optional.empty())).containsExactly("a", "b", "c"); + assertThat(projection.getProjectedValues(Optional.of(Domain.all(VARCHAR)))).containsExactly("a", "b", "c"); + assertThat(projection.getProjectedValues(Optional.of(Domain.none(VARCHAR)))).isEmpty(); + assertThat(projection.getProjectedValues(Optional.of(Domain.singleValue(VARCHAR, Slices.utf8Slice("b"))))).containsExactly("b"); + assertThat(projection.getProjectedValues(Optional.of(Domain.singleValue(VARCHAR, Slices.utf8Slice("x"))))).isEmpty(); + + assertThatThrownBy(() -> new EnumProjectionFactory().create("test", VARCHAR, ImmutableMap.of("ignored", ImmutableList.of("a", "b", "c")))) + .isInstanceOf(TrinoException.class) + .hasMessage("Column projection for column 'test' failed. Missing required property: 'partition_projection_values'"); + + assertThatThrownBy(() -> new EnumProjectionFactory().create("test", VARCHAR, ImmutableMap.of(COLUMN_PROJECTION_VALUES, "invalid"))) + .isInstanceOf(ClassCastException.class); + } +} diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestInjectedProjectionFactory.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestInjectedProjectionFactory.java new file mode 100644 index 000000000000..51326eb2b0a1 --- /dev/null +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestInjectedProjectionFactory.java @@ -0,0 +1,61 @@ +/* + * 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 io.trino.plugin.hive.aws.athena; + +import com.google.common.collect.ImmutableMap; +import io.airlift.slice.Slices; +import io.trino.spi.TrinoException; +import io.trino.spi.predicate.Domain; +import org.junit.jupiter.api.Test; + +import java.util.Optional; + +import static io.trino.spi.type.BigintType.BIGINT; +import static io.trino.spi.type.CharType.createCharType; +import static io.trino.spi.type.TimestampType.TIMESTAMP_PICOS; +import static io.trino.spi.type.TimestampType.TIMESTAMP_SECONDS; +import static io.trino.spi.type.VarcharType.VARCHAR; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class TestInjectedProjectionFactory +{ + @Test + void testIsSupported() + { + assertThat(new InjectedProjectionFactory().isSupportedColumnType(VARCHAR)).isTrue(); + assertThat(new InjectedProjectionFactory().isSupportedColumnType(createCharType(10))).isTrue(); + assertThat(new InjectedProjectionFactory().isSupportedColumnType(BIGINT)).isTrue(); + assertThat(new InjectedProjectionFactory().isSupportedColumnType(TIMESTAMP_SECONDS)).isFalse(); + assertThat(new InjectedProjectionFactory().isSupportedColumnType(TIMESTAMP_PICOS)).isFalse(); + } + + @Test + void testCreate() + { + Projection projection = new InjectedProjectionFactory().create("test", VARCHAR, ImmutableMap.of()); + assertThat(projection.getProjectedValues(Optional.of(Domain.singleValue(VARCHAR, Slices.utf8Slice("b"))))).containsExactly("b"); + assertThat(projection.getProjectedValues(Optional.of(Domain.singleValue(VARCHAR, Slices.utf8Slice("x"))))).containsExactly("x"); + + assertThatThrownBy(() -> assertThat(projection.getProjectedValues(Optional.empty())).containsExactly("a", "b", "c")) + .isInstanceOf(TrinoException.class) + .hasMessage("Column projection for column 'test' failed. Injected projection requires single predicate for it's column in where clause"); + assertThatThrownBy(() -> assertThat(projection.getProjectedValues(Optional.of(Domain.all(VARCHAR)))).containsExactly("a", "b", "c")) + .isInstanceOf(TrinoException.class) + .hasMessage("Column projection for column 'test' failed. Injected projection requires single predicate for it's column in where clause. Currently provided can't be converted to single partition."); + assertThatThrownBy(() -> assertThat(projection.getProjectedValues(Optional.of(Domain.none(VARCHAR)))).isEmpty()) + .isInstanceOf(TrinoException.class) + .hasMessage("Column projection for column 'test' failed. Injected projection requires single predicate for it's column in where clause. Currently provided can't be converted to single partition."); + } +} diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestIntegerProjectionFactory.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestIntegerProjectionFactory.java new file mode 100644 index 000000000000..7c6f654fe7e0 --- /dev/null +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestIntegerProjectionFactory.java @@ -0,0 +1,84 @@ +/* + * 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 io.trino.plugin.hive.aws.athena; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.trino.spi.TrinoException; +import io.trino.spi.predicate.Domain; +import org.junit.jupiter.api.Test; + +import java.util.Optional; + +import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_DIGITS; +import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_INTERVAL; +import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_RANGE; +import static io.trino.spi.type.BigintType.BIGINT; +import static io.trino.spi.type.DateType.DATE; +import static io.trino.spi.type.IntegerType.INTEGER; +import static io.trino.spi.type.VarcharType.VARCHAR; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class TestIntegerProjectionFactory +{ + @Test + void testIsSupported() + { + assertThat(new IntegerProjectionFactory().isSupportedColumnType(VARCHAR)).isTrue(); + assertThat(new IntegerProjectionFactory().isSupportedColumnType(INTEGER)).isTrue(); + assertThat(new IntegerProjectionFactory().isSupportedColumnType(BIGINT)).isTrue(); + assertThat(new IntegerProjectionFactory().isSupportedColumnType(DATE)).isFalse(); + } + + @Test + void testCreateBasic() + { + Projection projection = new IntegerProjectionFactory().create("test", INTEGER, ImmutableMap.of(COLUMN_PROJECTION_RANGE, ImmutableList.of("1", "3"))); + assertThat(projection.getProjectedValues(Optional.empty())).containsExactly("1", "2", "3"); + assertThat(projection.getProjectedValues(Optional.of(Domain.all(INTEGER)))).containsExactly("1", "2", "3"); + assertThat(projection.getProjectedValues(Optional.of(Domain.none(INTEGER)))).isEmpty(); + assertThat(projection.getProjectedValues(Optional.of(Domain.singleValue(INTEGER, 2L)))).containsExactly("2"); + assertThat(projection.getProjectedValues(Optional.of(Domain.singleValue(INTEGER, 7L)))).isEmpty(); + + assertThatThrownBy(() -> new IntegerProjectionFactory().create("test", INTEGER, ImmutableMap.of("ignored", ImmutableList.of("1", "3")))) + .isInstanceOf(TrinoException.class) + .hasMessage("Column projection for column 'test' failed. Missing required property: 'partition_projection_range'"); + + assertThatThrownBy(() -> new IntegerProjectionFactory().create("test", INTEGER, ImmutableMap.of(COLUMN_PROJECTION_RANGE, "invalid"))) + .isInstanceOf(ClassCastException.class); + } + + @Test + void testInterval() + { + Projection projection = new IntegerProjectionFactory().create("test", INTEGER, ImmutableMap.of(COLUMN_PROJECTION_RANGE, ImmutableList.of("10", "30"), COLUMN_PROJECTION_INTERVAL, 10)); + assertThat(projection.getProjectedValues(Optional.empty())).containsExactly("10", "20", "30"); + assertThat(projection.getProjectedValues(Optional.of(Domain.all(INTEGER)))).containsExactly("10", "20", "30"); + assertThat(projection.getProjectedValues(Optional.of(Domain.none(INTEGER)))).isEmpty(); + assertThat(projection.getProjectedValues(Optional.of(Domain.singleValue(INTEGER, 20L)))).containsExactly("20"); + assertThat(projection.getProjectedValues(Optional.of(Domain.singleValue(INTEGER, 70L)))).isEmpty(); + } + + @Test + void testCreateDigits() + { + Projection projection = new IntegerProjectionFactory().create("test", INTEGER, ImmutableMap.of(COLUMN_PROJECTION_RANGE, ImmutableList.of("1", "3"), COLUMN_PROJECTION_DIGITS, 3)); + assertThat(projection.getProjectedValues(Optional.empty())).containsExactly("001", "002", "003"); + assertThat(projection.getProjectedValues(Optional.of(Domain.all(INTEGER)))).containsExactly("001", "002", "003"); + assertThat(projection.getProjectedValues(Optional.of(Domain.none(INTEGER)))).isEmpty(); + assertThat(projection.getProjectedValues(Optional.of(Domain.singleValue(INTEGER, 2L)))).containsExactly("002"); + assertThat(projection.getProjectedValues(Optional.of(Domain.singleValue(INTEGER, 7L)))).isEmpty(); + } +} From 89c9a649ad3945f479a7b72ec988dffd7768da44 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Thu, 16 Nov 2023 16:53:26 -0800 Subject: [PATCH 317/587] Reduce visibility of projection classes --- .../java/io/trino/plugin/hive/aws/athena/DateProjection.java | 2 +- .../io/trino/plugin/hive/aws/athena/DateProjectionFactory.java | 2 +- .../java/io/trino/plugin/hive/aws/athena/EnumProjection.java | 2 +- .../io/trino/plugin/hive/aws/athena/EnumProjectionFactory.java | 2 +- .../io/trino/plugin/hive/aws/athena/InjectedProjection.java | 2 +- .../trino/plugin/hive/aws/athena/InjectedProjectionFactory.java | 2 +- .../java/io/trino/plugin/hive/aws/athena/IntegerProjection.java | 2 +- .../trino/plugin/hive/aws/athena/IntegerProjectionFactory.java | 2 +- .../io/trino/plugin/hive/aws/athena/PartitionProjection.java | 2 +- .../main/java/io/trino/plugin/hive/aws/athena/Projection.java | 2 +- .../java/io/trino/plugin/hive/aws/athena/ProjectionFactory.java | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjection.java index f4112457d74d..ec8f59fd93af 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjection.java @@ -36,7 +36,7 @@ import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.MILLISECONDS; -public class DateProjection +class DateProjection extends Projection { private final DateFormat dateFormat; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjectionFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjectionFactory.java index a6bf0abf8414..ae5aa94503fe 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjectionFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjectionFactory.java @@ -51,7 +51,7 @@ import static java.util.Objects.nonNull; import static java.util.TimeZone.getTimeZone; -public class DateProjectionFactory +class DateProjectionFactory implements ProjectionFactory { public static final ZoneId UTC_TIME_ZONE_ID = ZoneId.of("UTC"); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjection.java index 9592e43e869a..4803fa7a28a9 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjection.java @@ -26,7 +26,7 @@ import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; -public class EnumProjection +class EnumProjection extends Projection { private final List values; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjectionFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjectionFactory.java index e186b82339f9..8deea4748de0 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjectionFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjectionFactory.java @@ -23,7 +23,7 @@ import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_VALUES; import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getProjectionPropertyRequiredValue; -public class EnumProjectionFactory +class EnumProjectionFactory implements ProjectionFactory { @Override diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjection.java index edd6876729f4..e2229e451a92 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjection.java @@ -23,7 +23,7 @@ import static io.trino.plugin.hive.metastore.MetastoreUtil.canConvertSqlTypeToStringForParts; import static io.trino.plugin.hive.metastore.MetastoreUtil.sqlScalarToString; -public class InjectedProjection +class InjectedProjection extends Projection { public InjectedProjection(String columnName) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjectionFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjectionFactory.java index 4c4473d9da81..f5dc0d3a8a70 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjectionFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjectionFactory.java @@ -19,7 +19,7 @@ import static io.trino.plugin.hive.metastore.MetastoreUtil.canConvertSqlTypeToStringForParts; -public class InjectedProjectionFactory +class InjectedProjectionFactory implements ProjectionFactory { @Override diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjection.java index 2577e30410a6..62f7d6341926 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjection.java @@ -28,7 +28,7 @@ import static io.trino.spi.predicate.Domain.singleValue; import static java.util.Objects.requireNonNull; -public class IntegerProjection +class IntegerProjection extends Projection { private final int leftBound; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjectionFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjectionFactory.java index 9ded00d3e25e..c0ce75697124 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjectionFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjectionFactory.java @@ -30,7 +30,7 @@ import static io.trino.plugin.hive.aws.athena.Projection.invalidProjectionException; import static java.lang.String.format; -public class IntegerProjectionFactory +class IntegerProjectionFactory implements ProjectionFactory { @Override diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjection.java index 5d6410b97bef..ace8780c3e83 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjection.java @@ -38,7 +38,7 @@ import static java.lang.String.format; import static java.util.Objects.requireNonNull; -public final class PartitionProjection +final class PartitionProjection { private static final Pattern PROJECTION_LOCATION_TEMPLATE_PLACEHOLDER_PATTERN = Pattern.compile("(\\$\\{[^}]+\\})"); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/Projection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/Projection.java index ff696cb514c6..39f04837487e 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/Projection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/Projection.java @@ -24,7 +24,7 @@ import static java.lang.String.format; import static java.util.Objects.requireNonNull; -public abstract class Projection +abstract class Projection { private final String columnName; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/ProjectionFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/ProjectionFactory.java index 91e3fc06a3db..a4cd8fa444e8 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/ProjectionFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/ProjectionFactory.java @@ -17,7 +17,7 @@ import java.util.Map; -public interface ProjectionFactory +interface ProjectionFactory { boolean isSupportedColumnType(Type columnType); From dc1721a6f2049c31e64ceb48673fe0b32c90ab0b Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Thu, 16 Nov 2023 16:54:34 -0800 Subject: [PATCH 318/587] Seal ProjectionFactory --- .../io/trino/plugin/hive/aws/athena/DateProjectionFactory.java | 2 +- .../io/trino/plugin/hive/aws/athena/EnumProjectionFactory.java | 2 +- .../plugin/hive/aws/athena/InjectedProjectionFactory.java | 2 +- .../trino/plugin/hive/aws/athena/IntegerProjectionFactory.java | 2 +- .../io/trino/plugin/hive/aws/athena/ProjectionFactory.java | 3 ++- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjectionFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjectionFactory.java index ae5aa94503fe..35e1102ed2c3 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjectionFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjectionFactory.java @@ -51,7 +51,7 @@ import static java.util.Objects.nonNull; import static java.util.TimeZone.getTimeZone; -class DateProjectionFactory +final class DateProjectionFactory implements ProjectionFactory { public static final ZoneId UTC_TIME_ZONE_ID = ZoneId.of("UTC"); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjectionFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjectionFactory.java index 8deea4748de0..74e8bb9eb0b5 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjectionFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjectionFactory.java @@ -23,7 +23,7 @@ import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_VALUES; import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getProjectionPropertyRequiredValue; -class EnumProjectionFactory +final class EnumProjectionFactory implements ProjectionFactory { @Override diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjectionFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjectionFactory.java index f5dc0d3a8a70..ffae59c3bc47 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjectionFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjectionFactory.java @@ -19,7 +19,7 @@ import static io.trino.plugin.hive.metastore.MetastoreUtil.canConvertSqlTypeToStringForParts; -class InjectedProjectionFactory +final class InjectedProjectionFactory implements ProjectionFactory { @Override diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjectionFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjectionFactory.java index c0ce75697124..d1a9d5991fab 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjectionFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjectionFactory.java @@ -30,7 +30,7 @@ import static io.trino.plugin.hive.aws.athena.Projection.invalidProjectionException; import static java.lang.String.format; -class IntegerProjectionFactory +final class IntegerProjectionFactory implements ProjectionFactory { @Override diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/ProjectionFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/ProjectionFactory.java index a4cd8fa444e8..592d17c6969c 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/ProjectionFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/ProjectionFactory.java @@ -17,7 +17,8 @@ import java.util.Map; -interface ProjectionFactory +sealed interface ProjectionFactory + permits DateProjectionFactory, EnumProjectionFactory, InjectedProjectionFactory, IntegerProjectionFactory { boolean isSupportedColumnType(Type columnType); From 7ad9b2b7d3350ece0b4c0e5cbf8dd008e2bc5ddc Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Thu, 16 Nov 2023 18:16:34 -0800 Subject: [PATCH 319/587] Add InvalidProjectionException --- .../hive/aws/athena/DateProjection.java | 4 +- .../aws/athena/DateProjectionFactory.java | 7 ++-- .../hive/aws/athena/EnumProjection.java | 2 +- .../hive/aws/athena/InjectedProjection.java | 6 +-- .../hive/aws/athena/IntegerProjection.java | 2 +- .../aws/athena/IntegerProjectionFactory.java | 5 +-- .../athena/InvalidProjectionException.java | 39 +++++++++++++++++++ .../hive/aws/athena/PartitionProjection.java | 2 +- .../athena/PartitionProjectionProperties.java | 3 +- .../athena/PartitionProjectionService.java | 3 +- .../plugin/hive/aws/athena/Projection.java | 24 ------------ .../aws/athena/TestDateProjectionFactory.java | 3 +- .../aws/athena/TestEnumProjectionFactory.java | 3 +- .../athena/TestInjectedProjectionFactory.java | 7 ++-- .../athena/TestIntegerProjectionFactory.java | 3 +- 15 files changed, 59 insertions(+), 54 deletions(-) create mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InvalidProjectionException.java diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjection.java index ec8f59fd93af..c464bc0f9985 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjection.java @@ -84,7 +84,7 @@ private Instant adjustBoundToDateFormat(Instant value) return dateFormat.parse(formatted).toInstant(); } catch (ParseException e) { - throw invalidProjectionException(formatted, e.getMessage()); + throw new InvalidProjectionException(formatted, e.getMessage()); } } @@ -109,6 +109,6 @@ private boolean isValueInDomain(Optional valueDomain, Instant value, Str if (type instanceof TimestampType && ((TimestampType) type).isShort()) { return domain.contains(singleValue(type, MILLISECONDS.toMicros(value.toEpochMilli()))); } - throw unsupportedProjectionColumnTypeException(type); + throw new InvalidProjectionException(getColumnName(), type); } } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjectionFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjectionFactory.java index 35e1102ed2c3..4e64433995f9 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjectionFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjectionFactory.java @@ -41,7 +41,6 @@ import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_RANGE; import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getProjectionPropertyRequiredValue; import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getProjectionPropertyValue; -import static io.trino.plugin.hive.aws.athena.Projection.invalidProjectionException; import static java.lang.String.format; import static java.time.temporal.ChronoUnit.DAYS; import static java.time.temporal.ChronoUnit.HOURS; @@ -105,7 +104,7 @@ public Projection create(String columnName, Type columnType, Map .orElseGet(() -> resolveDefaultChronoUnit(columnName, dateFormatPattern)); if (!DATE_PROJECTION_INTERVAL_UNITS.contains(intervalUnit)) { - throw invalidProjectionException( + throw new InvalidProjectionException( columnName, format( "Property: '%s' value '%s' is invalid. Available options: %s", @@ -123,7 +122,7 @@ private ChronoUnit resolveDefaultChronoUnit(String columnName, String dateFormat if (datePatternWithoutText.contains("S") || datePatternWithoutText.contains("s") || datePatternWithoutText.contains("m") || datePatternWithoutText.contains("H")) { // When the provided dates are at single-day or single-month precision. - throw invalidProjectionException( + throw new InvalidProjectionException( columnName, format( "Property: '%s' needs to be set when provided '%s' is less that single-day precision. Interval defaults to 1 day or 1 month, respectively. Otherwise, interval is required", @@ -174,7 +173,7 @@ private TrinoException invalidRangeProperty(String columnName, String dateFormat private TrinoException invalidRangeProperty(String columnName, String dateFormatPattern, Optional errorDetail) { - return invalidProjectionException( + throw new InvalidProjectionException( columnName, format( "Property: '%s' needs to be a list of 2 valid dates formatted as '%s' or '%s' that are sequential%s", diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjection.java index 4803fa7a28a9..6196ba5e69fb 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjection.java @@ -54,6 +54,6 @@ private boolean isValueInDomain(Domain valueDomain, String value) if (type instanceof VarcharType) { return valueDomain.contains(singleValue(type, utf8Slice(value))); } - throw unsupportedProjectionColumnTypeException(type); + throw new InvalidProjectionException(getColumnName(), type); } } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjection.java index e2229e451a92..43b217467c9e 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjection.java @@ -35,13 +35,13 @@ public InjectedProjection(String columnName) public List getProjectedValues(Optional partitionValueFilter) { Domain domain = partitionValueFilter - .orElseThrow(() -> invalidProjectionException(getColumnName(), "Injected projection requires single predicate for it's column in where clause")); + .orElseThrow(() -> new InvalidProjectionException(getColumnName(), "Injected projection requires single predicate for it's column in where clause")); Type type = domain.getType(); if (!domain.isNullableSingleValue() || !canConvertSqlTypeToStringForParts(type, true)) { - throw invalidProjectionException(getColumnName(), "Injected projection requires single predicate for it's column in where clause. Currently provided can't be converted to single partition."); + throw new InvalidProjectionException(getColumnName(), "Injected projection requires single predicate for it's column in where clause. Currently provided can't be converted to single partition."); } return Optional.ofNullable(sqlScalarToString(type, domain.getNullableSingleValue(), null)) .map(ImmutableList::of) - .orElseThrow(() -> unsupportedProjectionColumnTypeException(type)); + .orElseThrow(() -> new InvalidProjectionException(getColumnName(), type)); } } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjection.java index 62f7d6341926..806aa2930c52 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjection.java @@ -76,6 +76,6 @@ private boolean isValueInDomain(Optional valueDomain, int value, String if (type instanceof IntegerType || type instanceof BigintType) { return domain.contains(singleValue(type, Long.valueOf(value))); } - throw unsupportedProjectionColumnTypeException(type); + throw new InvalidProjectionException(getColumnName(), type); } } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjectionFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjectionFactory.java index d1a9d5991fab..fd356eac733b 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjectionFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjectionFactory.java @@ -27,7 +27,6 @@ import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_RANGE; import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getProjectionPropertyRequiredValue; import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getProjectionPropertyValue; -import static io.trino.plugin.hive.aws.athena.Projection.invalidProjectionException; import static java.lang.String.format; final class IntegerProjectionFactory @@ -52,9 +51,7 @@ public Projection create(String columnName, Type columnType, Map .map(element -> Integer.valueOf((String) element)) .collect(toImmutableList())); if (range.size() != 2) { - invalidProjectionException( - columnName, - format("Property: '%s' needs to be list of 2 integers", COLUMN_PROJECTION_RANGE)); + throw new InvalidProjectionException(columnName, format("Property: '%s' needs to be list of 2 integers", COLUMN_PROJECTION_RANGE)); } return new IntegerProjection( columnName, diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InvalidProjectionException.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InvalidProjectionException.java new file mode 100644 index 000000000000..5e6f5ce62833 --- /dev/null +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InvalidProjectionException.java @@ -0,0 +1,39 @@ +/* + * 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 io.trino.plugin.hive.aws.athena; + +import io.trino.spi.TrinoException; +import io.trino.spi.type.Type; + +import static io.trino.spi.StandardErrorCode.INVALID_COLUMN_PROPERTY; +import static java.lang.String.format; + +public class InvalidProjectionException + extends TrinoException +{ + public InvalidProjectionException(String columnName, Type columnType) + { + this(columnName, "Unsupported column type: " + columnType.getDisplayName()); + } + + public InvalidProjectionException(String columnName, String message) + { + super(INVALID_COLUMN_PROPERTY, invalidProjectionMessage(columnName, message)); + } + + public static String invalidProjectionMessage(String columnName, String message) + { + return format("Column projection for column '%s' failed. %s", columnName, message); + } +} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjection.java index ace8780c3e83..0d903d0f8133 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjection.java @@ -32,7 +32,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.collect.Sets.cartesianProduct; -import static io.trino.plugin.hive.aws.athena.Projection.invalidProjectionMessage; +import static io.trino.plugin.hive.aws.athena.InvalidProjectionException.invalidProjectionMessage; import static io.trino.plugin.hive.util.HiveUtil.escapePathName; import static io.trino.plugin.hive.util.HiveUtil.toPartitionValues; import static java.lang.String.format; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionProperties.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionProperties.java index 80a5e8745303..75834acd351c 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionProperties.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionProperties.java @@ -17,7 +17,6 @@ import java.util.Optional; import java.util.function.Function; -import static io.trino.plugin.hive.aws.athena.Projection.invalidProjectionException; import static java.lang.String.format; public final class PartitionProjectionProperties @@ -61,7 +60,7 @@ public static T getProjectionPropertyRequiredValue( Function decoder) { return getProjectionPropertyValue(columnProjectionProperties, propertyKey, decoder) - .orElseThrow(() -> invalidProjectionException(columnName, format("Missing required property: '%s'", propertyKey))); + .orElseThrow(() -> new InvalidProjectionException(columnName, format("Missing required property: '%s'", propertyKey))); } public static Optional getProjectionPropertyValue( diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java index 1b6fe7f7f0d8..d03cdcc9e9e0 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java @@ -63,7 +63,6 @@ import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.PARTITION_PROJECTION_LOCATION_TEMPLATE; import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.PROPERTY_KEY_PREFIX; import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getMetastoreProjectionPropertyKey; -import static io.trino.plugin.hive.aws.athena.Projection.unsupportedProjectionColumnTypeException; import static io.trino.plugin.hive.aws.athena.ProjectionType.DATE; import static io.trino.plugin.hive.aws.athena.ProjectionType.ENUM; import static io.trino.plugin.hive.aws.athena.ProjectionType.INJECTED; @@ -353,7 +352,7 @@ private Projection parseColumnProjection(String columnName, Type columnType, Map ProjectionFactory projectionFactory = Optional.ofNullable(projectionFactories.get(projectionType)) .orElseThrow(() -> columnProjectionException(format("Partition projection type %s for column: '%s' not supported", projectionType, columnName))); if (!projectionFactory.isSupportedColumnType(columnType)) { - throw unsupportedProjectionColumnTypeException(columnName, columnType); + throw new InvalidProjectionException(columnName, columnType); } return projectionFactory.create(columnName, columnType, columnProperties); } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/Projection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/Projection.java index 39f04837487e..eeb53655f139 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/Projection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/Projection.java @@ -13,15 +13,11 @@ */ package io.trino.plugin.hive.aws.athena; -import io.trino.spi.TrinoException; import io.trino.spi.predicate.Domain; -import io.trino.spi.type.Type; import java.util.List; import java.util.Optional; -import static io.trino.spi.StandardErrorCode.INVALID_COLUMN_PROPERTY; -import static java.lang.String.format; import static java.util.Objects.requireNonNull; abstract class Projection @@ -39,24 +35,4 @@ public String getColumnName() } public abstract List getProjectedValues(Optional partitionValueFilter); - - protected TrinoException unsupportedProjectionColumnTypeException(Type columnType) - { - return unsupportedProjectionColumnTypeException(columnName, columnType); - } - - public static TrinoException unsupportedProjectionColumnTypeException(String columnName, Type columnType) - { - return invalidProjectionException(columnName, "Unsupported column type: " + columnType.getDisplayName()); - } - - public static TrinoException invalidProjectionException(String columnName, String message) - { - throw new TrinoException(INVALID_COLUMN_PROPERTY, invalidProjectionMessage(columnName, message)); - } - - public static String invalidProjectionMessage(String columnName, String message) - { - return format("Column projection for column '%s' failed. %s", columnName, message); - } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestDateProjectionFactory.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestDateProjectionFactory.java index 69af8d65a828..3ddc423f50a4 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestDateProjectionFactory.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestDateProjectionFactory.java @@ -16,7 +16,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.airlift.slice.Slices; -import io.trino.spi.TrinoException; import io.trino.spi.predicate.Domain; import org.junit.jupiter.api.Test; @@ -57,7 +56,7 @@ void testCreate() assertThat(projection.getProjectedValues(Optional.of(Domain.singleValue(VARCHAR, Slices.utf8Slice("2222-01-01"))))).isEmpty(); assertThatThrownBy(() -> new DateProjectionFactory().create("test", VARCHAR, ImmutableMap.of("ignored", ImmutableList.of("2020-01-01", "2020-01-02", "2020-01-03")))) - .isInstanceOf(TrinoException.class) + .isInstanceOf(InvalidProjectionException.class) .hasMessage("Column projection for column 'test' failed. Missing required property: 'partition_projection_format'"); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestEnumProjectionFactory.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestEnumProjectionFactory.java index 0ac557c2363b..63fcadbe4fde 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestEnumProjectionFactory.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestEnumProjectionFactory.java @@ -16,7 +16,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import io.airlift.slice.Slices; -import io.trino.spi.TrinoException; import io.trino.spi.predicate.Domain; import org.junit.jupiter.api.Test; @@ -48,7 +47,7 @@ void testCreate() assertThat(projection.getProjectedValues(Optional.of(Domain.singleValue(VARCHAR, Slices.utf8Slice("x"))))).isEmpty(); assertThatThrownBy(() -> new EnumProjectionFactory().create("test", VARCHAR, ImmutableMap.of("ignored", ImmutableList.of("a", "b", "c")))) - .isInstanceOf(TrinoException.class) + .isInstanceOf(InvalidProjectionException.class) .hasMessage("Column projection for column 'test' failed. Missing required property: 'partition_projection_values'"); assertThatThrownBy(() -> new EnumProjectionFactory().create("test", VARCHAR, ImmutableMap.of(COLUMN_PROJECTION_VALUES, "invalid"))) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestInjectedProjectionFactory.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestInjectedProjectionFactory.java index 51326eb2b0a1..e47088103568 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestInjectedProjectionFactory.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestInjectedProjectionFactory.java @@ -15,7 +15,6 @@ import com.google.common.collect.ImmutableMap; import io.airlift.slice.Slices; -import io.trino.spi.TrinoException; import io.trino.spi.predicate.Domain; import org.junit.jupiter.api.Test; @@ -49,13 +48,13 @@ void testCreate() assertThat(projection.getProjectedValues(Optional.of(Domain.singleValue(VARCHAR, Slices.utf8Slice("x"))))).containsExactly("x"); assertThatThrownBy(() -> assertThat(projection.getProjectedValues(Optional.empty())).containsExactly("a", "b", "c")) - .isInstanceOf(TrinoException.class) + .isInstanceOf(InvalidProjectionException.class) .hasMessage("Column projection for column 'test' failed. Injected projection requires single predicate for it's column in where clause"); assertThatThrownBy(() -> assertThat(projection.getProjectedValues(Optional.of(Domain.all(VARCHAR)))).containsExactly("a", "b", "c")) - .isInstanceOf(TrinoException.class) + .isInstanceOf(InvalidProjectionException.class) .hasMessage("Column projection for column 'test' failed. Injected projection requires single predicate for it's column in where clause. Currently provided can't be converted to single partition."); assertThatThrownBy(() -> assertThat(projection.getProjectedValues(Optional.of(Domain.none(VARCHAR)))).isEmpty()) - .isInstanceOf(TrinoException.class) + .isInstanceOf(InvalidProjectionException.class) .hasMessage("Column projection for column 'test' failed. Injected projection requires single predicate for it's column in where clause. Currently provided can't be converted to single partition."); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestIntegerProjectionFactory.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestIntegerProjectionFactory.java index 7c6f654fe7e0..ce78b361b911 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestIntegerProjectionFactory.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestIntegerProjectionFactory.java @@ -15,7 +15,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import io.trino.spi.TrinoException; import io.trino.spi.predicate.Domain; import org.junit.jupiter.api.Test; @@ -53,7 +52,7 @@ void testCreateBasic() assertThat(projection.getProjectedValues(Optional.of(Domain.singleValue(INTEGER, 7L)))).isEmpty(); assertThatThrownBy(() -> new IntegerProjectionFactory().create("test", INTEGER, ImmutableMap.of("ignored", ImmutableList.of("1", "3")))) - .isInstanceOf(TrinoException.class) + .isInstanceOf(InvalidProjectionException.class) .hasMessage("Column projection for column 'test' failed. Missing required property: 'partition_projection_range'"); assertThatThrownBy(() -> new IntegerProjectionFactory().create("test", INTEGER, ImmutableMap.of(COLUMN_PROJECTION_RANGE, "invalid"))) From 0740c0f792e3386cdba6080f207787a56f070fc9 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Thu, 16 Nov 2023 18:21:34 -0800 Subject: [PATCH 320/587] Convert Projection to an interface and seal --- .../hive/aws/athena/DateProjection.java | 9 +++++---- .../hive/aws/athena/EnumProjection.java | 9 +++++---- .../hive/aws/athena/InjectedProjection.java | 15 +++++++++------ .../hive/aws/athena/IntegerProjection.java | 9 +++++---- .../plugin/hive/aws/athena/Projection.java | 19 +++---------------- 5 files changed, 27 insertions(+), 34 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjection.java index c464bc0f9985..4e0c2a34556a 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjection.java @@ -36,9 +36,10 @@ import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.MILLISECONDS; -class DateProjection - extends Projection +final class DateProjection + implements Projection { + private final String columnName; private final DateFormat dateFormat; private final Supplier leftBound; private final Supplier rightBound; @@ -47,7 +48,7 @@ class DateProjection public DateProjection(String columnName, DateFormat dateFormat, Supplier leftBound, Supplier rightBound, int interval, ChronoUnit intervalUnit) { - super(columnName); + this.columnName = requireNonNull(columnName, "columnName is null"); this.dateFormat = requireNonNull(dateFormat, "dateFormatPattern is null"); this.leftBound = requireNonNull(leftBound, "leftBound is null"); this.rightBound = requireNonNull(rightBound, "rightBound is null"); @@ -109,6 +110,6 @@ private boolean isValueInDomain(Optional valueDomain, Instant value, Str if (type instanceof TimestampType && ((TimestampType) type).isShort()) { return domain.contains(singleValue(type, MILLISECONDS.toMicros(value.toEpochMilli()))); } - throw new InvalidProjectionException(getColumnName(), type); + throw new InvalidProjectionException(columnName, type); } } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjection.java index 6196ba5e69fb..4bdd5b536a05 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjection.java @@ -26,14 +26,15 @@ import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; -class EnumProjection - extends Projection +final class EnumProjection + implements Projection { + private final String columnName; private final List values; public EnumProjection(String columnName, List values) { - super(columnName); + this.columnName = requireNonNull(columnName, "columnName is null"); this.values = ImmutableList.copyOf(requireNonNull(values, "values is null")); } @@ -54,6 +55,6 @@ private boolean isValueInDomain(Domain valueDomain, String value) if (type instanceof VarcharType) { return valueDomain.contains(singleValue(type, utf8Slice(value))); } - throw new InvalidProjectionException(getColumnName(), type); + throw new InvalidProjectionException(columnName, type); } } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjection.java index 43b217467c9e..5e2d290c6d85 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjection.java @@ -22,26 +22,29 @@ import static io.trino.plugin.hive.metastore.MetastoreUtil.canConvertSqlTypeToStringForParts; import static io.trino.plugin.hive.metastore.MetastoreUtil.sqlScalarToString; +import static java.util.Objects.requireNonNull; -class InjectedProjection - extends Projection +final class InjectedProjection + implements Projection { + private final String columnName; + public InjectedProjection(String columnName) { - super(columnName); + this.columnName = requireNonNull(columnName, "columnName is null"); } @Override public List getProjectedValues(Optional partitionValueFilter) { Domain domain = partitionValueFilter - .orElseThrow(() -> new InvalidProjectionException(getColumnName(), "Injected projection requires single predicate for it's column in where clause")); + .orElseThrow(() -> new InvalidProjectionException(columnName, "Injected projection requires single predicate for it's column in where clause")); Type type = domain.getType(); if (!domain.isNullableSingleValue() || !canConvertSqlTypeToStringForParts(type, true)) { - throw new InvalidProjectionException(getColumnName(), "Injected projection requires single predicate for it's column in where clause. Currently provided can't be converted to single partition."); + throw new InvalidProjectionException(columnName, "Injected projection requires single predicate for it's column in where clause. Currently provided can't be converted to single partition."); } return Optional.ofNullable(sqlScalarToString(type, domain.getNullableSingleValue(), null)) .map(ImmutableList::of) - .orElseThrow(() -> new InvalidProjectionException(getColumnName(), type)); + .orElseThrow(() -> new InvalidProjectionException(columnName, type)); } } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjection.java index 806aa2930c52..1728da40bd1f 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjection.java @@ -28,9 +28,10 @@ import static io.trino.spi.predicate.Domain.singleValue; import static java.util.Objects.requireNonNull; -class IntegerProjection - extends Projection +final class IntegerProjection + implements Projection { + private final String columnName; private final int leftBound; private final int rightBound; private final int interval; @@ -38,7 +39,7 @@ class IntegerProjection public IntegerProjection(String columnName, int leftBound, int rightBound, int interval, Optional digits) { - super(columnName); + this.columnName = requireNonNull(columnName, "columnName is null"); this.leftBound = leftBound; this.rightBound = rightBound; this.interval = interval; @@ -76,6 +77,6 @@ private boolean isValueInDomain(Optional valueDomain, int value, String if (type instanceof IntegerType || type instanceof BigintType) { return domain.contains(singleValue(type, Long.valueOf(value))); } - throw new InvalidProjectionException(getColumnName(), type); + throw new InvalidProjectionException(columnName, type); } } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/Projection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/Projection.java index eeb53655f139..7e6eca7212b9 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/Projection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/Projection.java @@ -18,21 +18,8 @@ import java.util.List; import java.util.Optional; -import static java.util.Objects.requireNonNull; - -abstract class Projection +sealed interface Projection + permits DateProjection, EnumProjection, InjectedProjection, IntegerProjection { - private final String columnName; - - public Projection(String columnName) - { - this.columnName = requireNonNull(columnName, "columnName is null"); - } - - public String getColumnName() - { - return columnName; - } - - public abstract List getProjectedValues(Optional partitionValueFilter); + List getProjectedValues(Optional partitionValueFilter); } From 3fc3693b9b57d862981f4198df25fb8c028b0288 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Thu, 16 Nov 2023 18:30:27 -0800 Subject: [PATCH 321/587] Fix IntelliJ warnings in partition projections --- .../io/trino/plugin/hive/HiveMetadata.java | 2 +- .../aws/athena/DateProjectionFactory.java | 21 +++++-------------- .../hive/aws/athena/IntegerProjection.java | 2 +- .../hive/aws/athena/PartitionProjection.java | 5 +++-- ...PartitionProjectionMetastoreDecorator.java | 2 +- .../athena/PartitionProjectionService.java | 10 ++++----- .../hive/aws/athena/ProjectionType.java | 5 +---- 7 files changed, 17 insertions(+), 30 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java index 112f41e4f63f..c99199ff4166 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java @@ -741,7 +741,7 @@ else if (isTrinoView || isTrinoMaterializedView) { } // Partition Projection specific properties - properties.putAll(partitionProjectionService.getPartitionProjectionTrinoTableProperties(table)); + properties.putAll(PartitionProjectionService.getPartitionProjectionTrinoTableProperties(table)); return new ConnectorTableMetadata(tableName, columns, properties.buildOrThrow(), comment); } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjectionFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjectionFactory.java index 4e64433995f9..25d36e508565 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjectionFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjectionFactory.java @@ -116,7 +116,7 @@ public Projection create(String columnName, Type columnType, Map return new DateProjection(columnName, dateFormat, leftBound, rangeBound, interval, intervalUnit); } - private ChronoUnit resolveDefaultChronoUnit(String columnName, String dateFormatPattern) + private static ChronoUnit resolveDefaultChronoUnit(String columnName, String dateFormatPattern) { String datePatternWithoutText = dateFormatPattern.replaceAll("'.*?'", ""); if (datePatternWithoutText.contains("S") || datePatternWithoutText.contains("s") @@ -135,7 +135,7 @@ private ChronoUnit resolveDefaultChronoUnit(String columnName, String dateFormat return MONTHS; } - private Supplier parseDateRangerBound(String columnName, String value, SimpleDateFormat dateFormat) + private static Supplier parseDateRangerBound(String columnName, String value, SimpleDateFormat dateFormat) { Matcher matcher = DATE_RANGE_BOUND_EXPRESSION_PATTERN.matcher(value); if (matcher.matches()) { @@ -166,12 +166,12 @@ private Supplier parseDateRangerBound(String columnName, String value, return () -> dateBound; } - private TrinoException invalidRangeProperty(String columnName, String dateFormatPattern) + private static TrinoException invalidRangeProperty(String columnName, String dateFormatPattern) { return invalidRangeProperty(columnName, dateFormatPattern, Optional.empty()); } - private TrinoException invalidRangeProperty(String columnName, String dateFormatPattern, Optional errorDetail) + private static TrinoException invalidRangeProperty(String columnName, String dateFormatPattern, Optional errorDetail) { throw new InvalidProjectionException( columnName, @@ -183,20 +183,9 @@ private TrinoException invalidRangeProperty(String columnName, String dateFormat errorDetail.map(error -> ". " + error).orElse(""))); } - private static class DateExpressionBound + private record DateExpressionBound(int multiplier, ChronoUnit unit, boolean increment) implements Supplier { - private final int multiplier; - private final ChronoUnit unit; - private final boolean increment; - - public DateExpressionBound(int multiplier, ChronoUnit unit, boolean increment) - { - this.multiplier = multiplier; - this.unit = unit; - this.increment = increment; - } - @Override public Instant get() { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjection.java index 1728da40bd1f..e96103250c9d 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjection.java @@ -75,7 +75,7 @@ private boolean isValueInDomain(Optional valueDomain, int value, String return domain.contains(singleValue(type, utf8Slice(formattedValue))); } if (type instanceof IntegerType || type instanceof BigintType) { - return domain.contains(singleValue(type, Long.valueOf(value))); + return domain.contains(singleValue(type, (long) value)); } throw new InvalidProjectionException(columnName, type); } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjection.java index 0d903d0f8133..eefdfd3e33be 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjection.java @@ -15,6 +15,7 @@ import com.google.common.base.VerifyException; import com.google.common.collect.ImmutableMap; +import io.trino.plugin.hive.metastore.Column; import io.trino.plugin.hive.metastore.Partition; import io.trino.plugin.hive.metastore.Table; import io.trino.spi.predicate.Domain; @@ -107,7 +108,7 @@ private Partition buildPartitionObject(Table table, String partitionName) .map(template -> expandStorageLocationTemplate( template, table.getPartitionColumns().stream() - .map(column -> column.getName()).collect(Collectors.toList()), + .map(Column::getName).collect(Collectors.toList()), partitionValues)) .orElseGet(() -> format("%s/%s/", table.getStorage().getLocation(), partitionName))) .setBucketProperty(table.getStorage().getBucketProperty()) @@ -115,7 +116,7 @@ private Partition buildPartitionObject(Table table, String partitionName) .build(); } - private String expandStorageLocationTemplate(String template, List partitionColumns, List partitionValues) + private static String expandStorageLocationTemplate(String template, List partitionColumns, List partitionValues) { Matcher matcher = PROJECTION_LOCATION_TEMPLATE_PLACEHOLDER_PATTERN.matcher(template); StringBuilder location = new StringBuilder(); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionMetastoreDecorator.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionMetastoreDecorator.java index c221e87e5ad9..a64aa7f3aece 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionMetastoreDecorator.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionMetastoreDecorator.java @@ -67,7 +67,7 @@ public PartitionProjectionMetastore(HiveMetastore hiveMetastore, PartitionProjec @Override public Optional> getPartitionNamesByFilter(String databaseName, String tableName, List columnNames, TupleDomain partitionKeysFilter) { - Table table = super.getTable(databaseName, tableName) + Table table = getTable(databaseName, tableName) .orElseThrow(() -> new TrinoException(HIVE_TABLE_DROPPED_DURING_QUERY, "Table does not exists: " + tableName)); Optional projection = getPartitionProjection(table); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java index d03cdcc9e9e0..58a506c8a93d 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java @@ -91,7 +91,7 @@ public PartitionProjectionService(HiveConfig hiveConfig, TypeManager typeManager .buildOrThrow(); } - public Map getPartitionProjectionTrinoTableProperties(Table table) + public static Map getPartitionProjectionTrinoTableProperties(Table table) { Map metastoreTableProperties = table.getParameters(); ImmutableMap.Builder trinoTablePropertiesBuilder = ImmutableMap.builder(); @@ -203,7 +203,7 @@ public Map getPartitionProjectionHiveTableProperties(ConnectorTa return metastoreTableProperties; } - private boolean isAnyPartitionProjectionPropertyUsed(ConnectorTableMetadata tableMetadata) + private static boolean isAnyPartitionProjectionPropertyUsed(ConnectorTableMetadata tableMetadata) { if (tableMetadata.getProperties().keySet().stream() .anyMatch(propertyKey -> propertyKey.startsWith(PROPERTY_KEY_PREFIX))) { @@ -215,7 +215,7 @@ private boolean isAnyPartitionProjectionPropertyUsed(ConnectorTableMetadata tabl .anyMatch(propertyKey -> propertyKey.startsWith(PROPERTY_KEY_PREFIX)); } - public Optional getPartitionProjectionFromTable(Table table) + Optional getPartitionProjectionFromTable(Table table) { if (!partitionProjectionEnabled) { return Optional.empty(); @@ -246,7 +246,7 @@ public Optional getPartitionProjectionFromTable(Table table private PartitionProjection createPartitionProjection(List dataColumns, Map partitionColumns, Map tableProperties) { Optional projectionEnabledProperty = Optional.ofNullable(tableProperties.get(METASTORE_PROPERTY_PROJECTION_ENABLED)).map(Boolean::valueOf); - if (projectionEnabledProperty.orElse(false) && partitionColumns.size() < 1) { + if (projectionEnabledProperty.orElse(false) && partitionColumns.isEmpty()) { throw columnProjectionException("Partition projection can't be enabled when no partition columns are defined."); } @@ -368,7 +368,7 @@ private static void rewriteProperty( .ifPresent(value -> targetPropertiesBuilder.put(targetPropertyKey, valueMapper.apply(value))); } - private TrinoException columnProjectionException(String message) + private static TrinoException columnProjectionException(String message) { return new TrinoException(INVALID_COLUMN_PROPERTY, message); } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/ProjectionType.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/ProjectionType.java index 37419ebdaaa3..5d327849ed0d 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/ProjectionType.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/ProjectionType.java @@ -15,8 +15,5 @@ public enum ProjectionType { - ENUM, - INTEGER, - DATE, - INJECTED; + ENUM, INTEGER, DATE, INJECTED } From e0e28429c6aa02a8c8d660cb182d0a5068bc2fcd Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Thu, 16 Nov 2023 19:16:52 -0800 Subject: [PATCH 322/587] Inline rewriteProperty in PartitionProjectionService --- .../athena/PartitionProjectionService.java | 214 ++++++++---------- 1 file changed, 96 insertions(+), 118 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java index 58a506c8a93d..3f448fd4b608 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java @@ -29,12 +29,10 @@ import java.time.temporal.ChronoUnit; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.function.Function; import java.util.stream.Collectors; import static com.google.common.collect.ImmutableList.toImmutableList; @@ -69,6 +67,7 @@ import static io.trino.plugin.hive.aws.athena.ProjectionType.INTEGER; import static io.trino.spi.StandardErrorCode.INVALID_COLUMN_PROPERTY; import static java.lang.String.format; +import static java.util.Locale.ROOT; import static java.util.Objects.requireNonNull; import static java.util.function.Function.identity; @@ -95,9 +94,22 @@ public static Map getPartitionProjectionTrinoTableProperties(Tab { Map metastoreTableProperties = table.getParameters(); ImmutableMap.Builder trinoTablePropertiesBuilder = ImmutableMap.builder(); - rewriteProperty(metastoreTableProperties, trinoTablePropertiesBuilder, METASTORE_PROPERTY_PROJECTION_IGNORE, PARTITION_PROJECTION_IGNORE, Boolean::valueOf); - rewriteProperty(metastoreTableProperties, trinoTablePropertiesBuilder, METASTORE_PROPERTY_PROJECTION_ENABLED, PARTITION_PROJECTION_ENABLED, Boolean::valueOf); - rewriteProperty(metastoreTableProperties, trinoTablePropertiesBuilder, METASTORE_PROPERTY_PROJECTION_LOCATION_TEMPLATE, PARTITION_PROJECTION_LOCATION_TEMPLATE, String::valueOf); + + String ignore = metastoreTableProperties.get(METASTORE_PROPERTY_PROJECTION_IGNORE); + if (ignore != null) { + trinoTablePropertiesBuilder.put(PARTITION_PROJECTION_IGNORE, Boolean.valueOf(ignore)); + } + + String enabled = metastoreTableProperties.get(METASTORE_PROPERTY_PROJECTION_ENABLED); + if (enabled != null) { + trinoTablePropertiesBuilder.put(PARTITION_PROJECTION_ENABLED, Boolean.valueOf(enabled)); + } + + String locationTemplate = metastoreTableProperties.get(METASTORE_PROPERTY_PROJECTION_LOCATION_TEMPLATE); + if (locationTemplate != null) { + trinoTablePropertiesBuilder.put(PARTITION_PROJECTION_LOCATION_TEMPLATE, locationTemplate); + } + return trinoTablePropertiesBuilder.buildOrThrow(); } @@ -118,24 +130,21 @@ public Map getPartitionProjectionHiveTableProperties(ConnectorTa ImmutableMap.Builder metastoreTablePropertiesBuilder = ImmutableMap.builder(); // Handle Table Properties Map trinoTableProperties = tableMetadata.getProperties(); - rewriteProperty( - trinoTableProperties, - metastoreTablePropertiesBuilder, - PARTITION_PROJECTION_IGNORE, - METASTORE_PROPERTY_PROJECTION_IGNORE, - value -> value.toString().toLowerCase(Locale.ENGLISH)); - rewriteProperty( - trinoTableProperties, - metastoreTablePropertiesBuilder, - PARTITION_PROJECTION_ENABLED, - METASTORE_PROPERTY_PROJECTION_ENABLED, - value -> value.toString().toLowerCase(Locale.ENGLISH)); - rewriteProperty( - trinoTableProperties, - metastoreTablePropertiesBuilder, - PARTITION_PROJECTION_LOCATION_TEMPLATE, - METASTORE_PROPERTY_PROJECTION_LOCATION_TEMPLATE, - Object::toString); + + Object ignore = trinoTableProperties.get(PARTITION_PROJECTION_IGNORE); + if (ignore != null) { + metastoreTablePropertiesBuilder.put(METASTORE_PROPERTY_PROJECTION_IGNORE, ignore.toString().toLowerCase(ROOT)); + } + + Object enabled = trinoTableProperties.get(PARTITION_PROJECTION_ENABLED); + if (enabled != null) { + metastoreTablePropertiesBuilder.put(METASTORE_PROPERTY_PROJECTION_ENABLED, enabled.toString().toLowerCase(ROOT)); + } + + Object locationTemplate = trinoTableProperties.get(PARTITION_PROJECTION_LOCATION_TEMPLATE); + if (locationTemplate != null) { + metastoreTablePropertiesBuilder.put(METASTORE_PROPERTY_PROJECTION_LOCATION_TEMPLATE, locationTemplate.toString()); + } // Handle Column Properties tableMetadata.getColumns().stream() @@ -143,48 +152,34 @@ public Map getPartitionProjectionHiveTableProperties(ConnectorTa .forEach(columnMetadata -> { Map columnProperties = columnMetadata.getProperties(); String columnName = columnMetadata.getName(); - rewriteProperty( - columnProperties, - metastoreTablePropertiesBuilder, - COLUMN_PROJECTION_TYPE, - getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_TYPE_SUFFIX), - value -> ((ProjectionType) value).name().toLowerCase(Locale.ENGLISH)); - rewriteProperty( - columnProperties, - metastoreTablePropertiesBuilder, - COLUMN_PROJECTION_VALUES, - getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_VALUES_SUFFIX), - value -> Joiner.on(",").join((List) value)); - rewriteProperty( - columnProperties, - metastoreTablePropertiesBuilder, - COLUMN_PROJECTION_RANGE, - getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_RANGE_SUFFIX), - value -> Joiner.on(",").join((List) value)); - rewriteProperty( - columnProperties, - metastoreTablePropertiesBuilder, - COLUMN_PROJECTION_INTERVAL, - getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_INTERVAL_SUFFIX), - value -> ((Integer) value).toString()); - rewriteProperty( - columnProperties, - metastoreTablePropertiesBuilder, - COLUMN_PROJECTION_INTERVAL_UNIT, - getMetastoreProjectionPropertyKey(columnName, METASTORE_PROPERTY_PROJECTION_INTERVAL_UNIT_SUFFIX), - value -> ((ChronoUnit) value).name().toLowerCase(Locale.ENGLISH)); - rewriteProperty( - columnProperties, - metastoreTablePropertiesBuilder, - COLUMN_PROJECTION_DIGITS, - getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_DIGITS_SUFFIX), - value -> ((Integer) value).toString()); - rewriteProperty( - columnProperties, - metastoreTablePropertiesBuilder, - COLUMN_PROJECTION_FORMAT, - getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_FORMAT_SUFFIX), - String.class::cast); + + if (columnProperties.get(COLUMN_PROJECTION_TYPE) instanceof ProjectionType projectionType) { + metastoreTablePropertiesBuilder.put(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_TYPE_SUFFIX), projectionType.name().toLowerCase(ROOT)); + } + + if (columnProperties.get(COLUMN_PROJECTION_VALUES) instanceof List values) { + metastoreTablePropertiesBuilder.put(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_VALUES_SUFFIX), Joiner.on(",").join(values)); + } + + if (columnProperties.get(COLUMN_PROJECTION_RANGE) instanceof List range) { + metastoreTablePropertiesBuilder.put(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_RANGE_SUFFIX), Joiner.on(",").join(range)); + } + + if (columnProperties.get(COLUMN_PROJECTION_INTERVAL) instanceof Integer interval) { + metastoreTablePropertiesBuilder.put(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_INTERVAL_SUFFIX), interval.toString()); + } + + if (columnProperties.get(COLUMN_PROJECTION_INTERVAL_UNIT) instanceof ChronoUnit intervalUnit) { + metastoreTablePropertiesBuilder.put(getMetastoreProjectionPropertyKey(columnName, METASTORE_PROPERTY_PROJECTION_INTERVAL_UNIT_SUFFIX), intervalUnit.name().toLowerCase(ROOT)); + } + + if (columnProperties.get(COLUMN_PROJECTION_DIGITS) instanceof Integer digits) { + metastoreTablePropertiesBuilder.put(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_DIGITS_SUFFIX), digits.toString()); + } + + if (columnProperties.get(COLUMN_PROJECTION_FORMAT) instanceof String format) { + metastoreTablePropertiesBuilder.put(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_FORMAT_SUFFIX), format); + } }); // We initialize partition projection to validate properties. @@ -298,48 +293,42 @@ else if (!columnProjections.isEmpty()) { private static Map rewriteColumnProjectionProperties(Map metastoreTableProperties, String columnName) { ImmutableMap.Builder trinoTablePropertiesBuilder = ImmutableMap.builder(); - rewriteProperty( - metastoreTableProperties, - trinoTablePropertiesBuilder, - getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_TYPE_SUFFIX), - COLUMN_PROJECTION_TYPE, - value -> ProjectionType.valueOf(value.toUpperCase(Locale.ENGLISH))); - rewriteProperty( - metastoreTableProperties, - trinoTablePropertiesBuilder, - getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_VALUES_SUFFIX), - COLUMN_PROJECTION_VALUES, - PartitionProjectionService::splitCommaSeparatedString); - rewriteProperty( - metastoreTableProperties, - trinoTablePropertiesBuilder, - getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_RANGE_SUFFIX), - COLUMN_PROJECTION_RANGE, - PartitionProjectionService::splitCommaSeparatedString); - rewriteProperty( - metastoreTableProperties, - trinoTablePropertiesBuilder, - getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_INTERVAL_SUFFIX), - COLUMN_PROJECTION_INTERVAL, - Integer::valueOf); - rewriteProperty( - metastoreTableProperties, - trinoTablePropertiesBuilder, - getMetastoreProjectionPropertyKey(columnName, METASTORE_PROPERTY_PROJECTION_INTERVAL_UNIT_SUFFIX), - COLUMN_PROJECTION_INTERVAL_UNIT, - value -> ChronoUnit.valueOf(value.toUpperCase(Locale.ENGLISH))); - rewriteProperty( - metastoreTableProperties, - trinoTablePropertiesBuilder, - getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_DIGITS_SUFFIX), - COLUMN_PROJECTION_DIGITS, - Integer::valueOf); - rewriteProperty( - metastoreTableProperties, - trinoTablePropertiesBuilder, - getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_FORMAT_SUFFIX), - COLUMN_PROJECTION_FORMAT, - value -> value); + + String type = metastoreTableProperties.get(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_TYPE_SUFFIX)); + if (type != null) { + trinoTablePropertiesBuilder.put(COLUMN_PROJECTION_TYPE, ProjectionType.valueOf(type.toUpperCase(ROOT))); + } + + String values = metastoreTableProperties.get(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_VALUES_SUFFIX)); + if (values != null) { + trinoTablePropertiesBuilder.put(COLUMN_PROJECTION_VALUES, splitCommaSeparatedString(values)); + } + + String range = metastoreTableProperties.get(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_RANGE_SUFFIX)); + if (range != null) { + trinoTablePropertiesBuilder.put(COLUMN_PROJECTION_RANGE, splitCommaSeparatedString(range)); + } + + String interval = metastoreTableProperties.get(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_INTERVAL_SUFFIX)); + if (interval != null) { + trinoTablePropertiesBuilder.put(COLUMN_PROJECTION_INTERVAL, Integer.valueOf(interval)); + } + + String intervalUnit = metastoreTableProperties.get(getMetastoreProjectionPropertyKey(columnName, METASTORE_PROPERTY_PROJECTION_INTERVAL_UNIT_SUFFIX)); + if (intervalUnit != null) { + trinoTablePropertiesBuilder.put(COLUMN_PROJECTION_INTERVAL_UNIT, ChronoUnit.valueOf(intervalUnit.toUpperCase(ROOT))); + } + + String digits = metastoreTableProperties.get(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_DIGITS_SUFFIX)); + if (digits != null) { + trinoTablePropertiesBuilder.put(COLUMN_PROJECTION_DIGITS, Integer.valueOf(digits)); + } + + String format = metastoreTableProperties.get(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_FORMAT_SUFFIX)); + if (format != null) { + trinoTablePropertiesBuilder.put(COLUMN_PROJECTION_FORMAT, format); + } + return trinoTablePropertiesBuilder.buildOrThrow(); } @@ -357,17 +346,6 @@ private Projection parseColumnProjection(String columnName, Type columnType, Map return projectionFactory.create(columnName, columnType, columnProperties); } - private static void rewriteProperty( - Map sourceProperties, - ImmutableMap.Builder targetPropertiesBuilder, - String sourcePropertyKey, - String targetPropertyKey, - Function valueMapper) - { - Optional.ofNullable(sourceProperties.get(sourcePropertyKey)) - .ifPresent(value -> targetPropertiesBuilder.put(targetPropertyKey, valueMapper.apply(value))); - } - private static TrinoException columnProjectionException(String message) { return new TrinoException(INVALID_COLUMN_PROPERTY, message); From 3c60e698800217b1db002686713a0d5577656902 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Thu, 16 Nov 2023 20:20:17 -0800 Subject: [PATCH 323/587] Simplify exceptions in PartitionProjectionService --- .../athena/InvalidProjectionException.java | 7 +++++- .../athena/PartitionProjectionService.java | 23 +++++++------------ 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InvalidProjectionException.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InvalidProjectionException.java index 5e6f5ce62833..46c3d9f38707 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InvalidProjectionException.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InvalidProjectionException.java @@ -29,7 +29,12 @@ public InvalidProjectionException(String columnName, Type columnType) public InvalidProjectionException(String columnName, String message) { - super(INVALID_COLUMN_PROPERTY, invalidProjectionMessage(columnName, message)); + this(invalidProjectionMessage(columnName, message)); + } + + public InvalidProjectionException(String message) + { + super(INVALID_COLUMN_PROPERTY, message); } public static String invalidProjectionMessage(String columnName, String message) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java index 3f448fd4b608..5b6b13d4da27 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java @@ -21,7 +21,6 @@ import io.trino.plugin.hive.HiveConfig; import io.trino.plugin.hive.metastore.Column; import io.trino.plugin.hive.metastore.Table; -import io.trino.spi.TrinoException; import io.trino.spi.connector.ColumnMetadata; import io.trino.spi.connector.ConnectorTableMetadata; import io.trino.spi.type.Type; @@ -65,7 +64,6 @@ import static io.trino.plugin.hive.aws.athena.ProjectionType.ENUM; import static io.trino.plugin.hive.aws.athena.ProjectionType.INJECTED; import static io.trino.plugin.hive.aws.athena.ProjectionType.INTEGER; -import static io.trino.spi.StandardErrorCode.INVALID_COLUMN_PROPERTY; import static java.lang.String.format; import static java.util.Locale.ROOT; import static java.util.Objects.requireNonNull; @@ -123,7 +121,7 @@ public Map getPartitionProjectionHiveTableProperties(ConnectorTa { // If partition projection is globally disabled we don't allow defining its properties if (!partitionProjectionEnabled && isAnyPartitionProjectionPropertyUsed(tableMetadata)) { - throw columnProjectionException("Partition projection is disabled. Enable it in configuration by setting " + throw new InvalidProjectionException("Partition projection is disabled. Enable it in configuration by setting " + HiveConfig.CONFIGURATION_HIVE_PARTITION_PROJECTION_ENABLED + "=true"); } @@ -242,7 +240,7 @@ private PartitionProjection createPartitionProjection(List dataColumns, { Optional projectionEnabledProperty = Optional.ofNullable(tableProperties.get(METASTORE_PROPERTY_PROJECTION_ENABLED)).map(Boolean::valueOf); if (projectionEnabledProperty.orElse(false) && partitionColumns.isEmpty()) { - throw columnProjectionException("Partition projection can't be enabled when no partition columns are defined."); + throw new InvalidProjectionException("Partition projection can't be enabled when no partition columns are defined."); } Map columnProjections = ImmutableSet.builder() @@ -263,25 +261,25 @@ private PartitionProjection createPartitionProjection(List dataColumns, if (partitionColumns.containsKey(columnName)) { return parseColumnProjection(columnName, partitionColumns.get(columnName), entry.getValue()); } - throw columnProjectionException("Partition projection can't be defined for non partition column: '" + columnName + "'"); + throw new InvalidProjectionException("Partition projection can't be defined for non partition column: '" + columnName + "'"); })); Optional storageLocationTemplate = Optional.ofNullable(tableProperties.get(METASTORE_PROPERTY_PROJECTION_LOCATION_TEMPLATE)); if (projectionEnabledProperty.isPresent()) { for (String columnName : partitionColumns.keySet()) { if (!columnProjections.containsKey(columnName)) { - throw columnProjectionException("Partition projection definition for column: '" + columnName + "' missing"); + throw new InvalidProjectionException("Partition projection definition for column: '" + columnName + "' missing"); } if (storageLocationTemplate.isPresent()) { String locationTemplate = storageLocationTemplate.get(); if (!locationTemplate.contains("${" + columnName + "}")) { - throw columnProjectionException(format("Partition projection location template: %s is missing partition column: '%s' placeholder", locationTemplate, columnName)); + throw new InvalidProjectionException(format("Partition projection location template: %s is missing partition column: '%s' placeholder", locationTemplate, columnName)); } } } } else if (!columnProjections.isEmpty()) { - throw columnProjectionException(format( + throw new InvalidProjectionException(format( "Columns %s projections are disallowed when partition projection property '%s' is missing", columnProjections.keySet().stream().collect(Collectors.joining("', '", "['", "']")), PARTITION_PROJECTION_ENABLED)); @@ -336,21 +334,16 @@ private Projection parseColumnProjection(String columnName, Type columnType, Map { ProjectionType projectionType = (ProjectionType) columnProperties.get(COLUMN_PROJECTION_TYPE); if (Objects.isNull(projectionType)) { - throw columnProjectionException("Projection type property missing for column: '" + columnName + "'"); + throw new InvalidProjectionException(columnName, "Projection type property missing for column: '" + columnName + "'"); } ProjectionFactory projectionFactory = Optional.ofNullable(projectionFactories.get(projectionType)) - .orElseThrow(() -> columnProjectionException(format("Partition projection type %s for column: '%s' not supported", projectionType, columnName))); + .orElseThrow(() -> new InvalidProjectionException(columnName, format("Partition projection type %s for column: '%s' not supported", projectionType, columnName))); if (!projectionFactory.isSupportedColumnType(columnType)) { throw new InvalidProjectionException(columnName, columnType); } return projectionFactory.create(columnName, columnType, columnProperties); } - private static TrinoException columnProjectionException(String message) - { - return new TrinoException(INVALID_COLUMN_PROPERTY, message); - } - private static List splitCommaSeparatedString(String value) { return Splitter.on(',') From 124dcf130979d28f94f4a10d8552cc957a607eab Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Thu, 16 Nov 2023 20:55:18 -0800 Subject: [PATCH 324/587] Simplify PartitionProjectionService --- .../athena/PartitionProjectionService.java | 114 ++++++++---------- .../plugin/hive/TestHive3OnDataLake.java | 10 +- 2 files changed, 55 insertions(+), 69 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java index 5b6b13d4da27..60bba1c10304 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java @@ -27,9 +27,9 @@ import io.trino.spi.type.TypeManager; import java.time.temporal.ChronoUnit; +import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -64,10 +64,10 @@ import static io.trino.plugin.hive.aws.athena.ProjectionType.ENUM; import static io.trino.plugin.hive.aws.athena.ProjectionType.INJECTED; import static io.trino.plugin.hive.aws.athena.ProjectionType.INTEGER; +import static java.lang.Boolean.parseBoolean; import static java.lang.String.format; import static java.util.Locale.ROOT; import static java.util.Objects.requireNonNull; -import static java.util.function.Function.identity; public final class PartitionProjectionService { @@ -119,7 +119,7 @@ public static Map getPartitionProjectionTrinoColumnProperties(Ta public Map getPartitionProjectionHiveTableProperties(ConnectorTableMetadata tableMetadata) { - // If partition projection is globally disabled we don't allow defining its properties + // If partition projection is globally disabled, we don't allow defining its properties if (!partitionProjectionEnabled && isAnyPartitionProjectionPropertyUsed(tableMetadata)) { throw new InvalidProjectionException("Partition projection is disabled. Enable it in configuration by setting " + HiveConfig.CONFIGURATION_HIVE_PARTITION_PROJECTION_ENABLED + "=true"); @@ -182,11 +182,12 @@ public Map getPartitionProjectionHiveTableProperties(ConnectorTa // We initialize partition projection to validate properties. Map metastoreTableProperties = metastoreTablePropertiesBuilder.buildOrThrow(); - List partitionColumnNames = getPartitionedBy(tableMetadata.getProperties()); + Set partitionColumnNames = ImmutableSet.copyOf(getPartitionedBy(tableMetadata.getProperties())); createPartitionProjection( tableMetadata.getColumns() .stream() .map(ColumnMetadata::getName) + .filter(name -> !partitionColumnNames.contains(name)) .collect(toImmutableList()), tableMetadata.getColumns().stream() .filter(columnMetadata -> partitionColumnNames.contains(columnMetadata.getName())) @@ -215,77 +216,60 @@ Optional getPartitionProjectionFromTable(Table table) } Map tableProperties = table.getParameters(); - if (Optional.ofNullable(tableProperties.get(METASTORE_PROPERTY_PROJECTION_IGNORE)) - .map(Boolean::valueOf) - .orElse(false)) { + if (parseBoolean(tableProperties.get(METASTORE_PROPERTY_PROJECTION_IGNORE)) || + !parseBoolean(tableProperties.get(METASTORE_PROPERTY_PROJECTION_ENABLED))) { return Optional.empty(); } - return Optional.of( - createPartitionProjection( - table.getDataColumns() - .stream() - .map(Column::getName) - .collect(toImmutableList()), - table.getPartitionColumns() - .stream().collect(toImmutableMap( - Column::getName, - column -> column.getType().getType( - typeManager, - DEFAULT_PRECISION))), - tableProperties)); + Set partitionColumnNames = table.getPartitionColumns().stream().map(Column::getName).collect(Collectors.toSet()); + return Optional.of(createPartitionProjection( + table.getDataColumns().stream() + .map(Column::getName) + .filter(partitionColumnNames::contains) + .collect(toImmutableList()), + table.getPartitionColumns().stream() + .collect(toImmutableMap(Column::getName, column -> column.getType().getType(typeManager, DEFAULT_PRECISION))), + tableProperties)); } private PartitionProjection createPartitionProjection(List dataColumns, Map partitionColumns, Map tableProperties) { - Optional projectionEnabledProperty = Optional.ofNullable(tableProperties.get(METASTORE_PROPERTY_PROJECTION_ENABLED)).map(Boolean::valueOf); - if (projectionEnabledProperty.orElse(false) && partitionColumns.isEmpty()) { - throw new InvalidProjectionException("Partition projection can't be enabled when no partition columns are defined."); + // This method is used during table creation to validate the properties. The validation is performed even if the projection is disabled. + boolean enabled = parseBoolean(tableProperties.get(METASTORE_PROPERTY_PROJECTION_ENABLED)); + + if (!tableProperties.containsKey(METASTORE_PROPERTY_PROJECTION_ENABLED) && + partitionColumns.keySet().stream().anyMatch(columnName -> !rewriteColumnProjectionProperties(tableProperties, columnName).isEmpty())) { + throw new InvalidProjectionException("Columns partition projection properties cannot be set when '%s' is not set".formatted(PARTITION_PROJECTION_ENABLED)); } - Map columnProjections = ImmutableSet.builder() - .addAll(partitionColumns.keySet()) - .addAll(dataColumns) - .build() - .stream() - .collect(toImmutableMap( - identity(), - columnName -> rewriteColumnProjectionProperties(tableProperties, columnName))) - .entrySet() - .stream() - .filter(entry -> !entry.getValue().isEmpty()) - .collect(toImmutableMap( - Map.Entry::getKey, - entry -> { - String columnName = entry.getKey(); - if (partitionColumns.containsKey(columnName)) { - return parseColumnProjection(columnName, partitionColumns.get(columnName), entry.getValue()); - } - throw new InvalidProjectionException("Partition projection can't be defined for non partition column: '" + columnName + "'"); - })); + if (enabled && partitionColumns.isEmpty()) { + throw new InvalidProjectionException("Partition projection cannot be enabled on a table that is not partitioned"); + } + + for (String columnName : dataColumns) { + if (!rewriteColumnProjectionProperties(tableProperties, columnName).isEmpty()) { + throw new InvalidProjectionException("Partition projection cannot be defined for non-partition column: '" + columnName + "'"); + } + } + + Map columnProjections = new HashMap<>(); + partitionColumns.forEach((columnName, type) -> { + Map columnProperties = rewriteColumnProjectionProperties(tableProperties, columnName); + if (enabled) { + columnProjections.put(columnName, parseColumnProjection(columnName, type, columnProperties)); + } + }); Optional storageLocationTemplate = Optional.ofNullable(tableProperties.get(METASTORE_PROPERTY_PROJECTION_LOCATION_TEMPLATE)); - if (projectionEnabledProperty.isPresent()) { + storageLocationTemplate.ifPresent(locationTemplate -> { for (String columnName : partitionColumns.keySet()) { - if (!columnProjections.containsKey(columnName)) { - throw new InvalidProjectionException("Partition projection definition for column: '" + columnName + "' missing"); - } - if (storageLocationTemplate.isPresent()) { - String locationTemplate = storageLocationTemplate.get(); - if (!locationTemplate.contains("${" + columnName + "}")) { - throw new InvalidProjectionException(format("Partition projection location template: %s is missing partition column: '%s' placeholder", locationTemplate, columnName)); - } + if (!locationTemplate.contains("${" + columnName + "}")) { + throw new InvalidProjectionException(format("Partition projection location template: %s is missing partition column: '%s' placeholder", locationTemplate, columnName)); } } - } - else if (!columnProjections.isEmpty()) { - throw new InvalidProjectionException(format( - "Columns %s projections are disallowed when partition projection property '%s' is missing", - columnProjections.keySet().stream().collect(Collectors.joining("', '", "['", "']")), - PARTITION_PROJECTION_ENABLED)); - } + }); - return new PartitionProjection(projectionEnabledProperty.orElse(false), storageLocationTemplate, columnProjections); + return new PartitionProjection(enabled, storageLocationTemplate, columnProjections); } private static Map rewriteColumnProjectionProperties(Map metastoreTableProperties, String columnName) @@ -333,11 +317,13 @@ private static Map rewriteColumnProjectionProperties(Map columnProperties) { ProjectionType projectionType = (ProjectionType) columnProperties.get(COLUMN_PROJECTION_TYPE); - if (Objects.isNull(projectionType)) { - throw new InvalidProjectionException(columnName, "Projection type property missing for column: '" + columnName + "'"); + if (projectionType == null) { + throw new InvalidProjectionException(columnName, "Projection type property missing"); + } + ProjectionFactory projectionFactory = projectionFactories.get(projectionType); + if (projectionFactory == null) { + throw new InvalidProjectionException(columnName, format("Partition projection type %s for column: '%s' not supported", projectionType, columnName)); } - ProjectionFactory projectionFactory = Optional.ofNullable(projectionFactories.get(projectionType)) - .orElseThrow(() -> new InvalidProjectionException(columnName, format("Partition projection type %s for column: '%s' not supported", projectionType, columnName))); if (!projectionFactory.isSupportedColumnType(columnType)) { throw new InvalidProjectionException(columnName, columnType); } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHive3OnDataLake.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHive3OnDataLake.java index 36b2a793b453..52dc944b3775 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHive3OnDataLake.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHive3OnDataLake.java @@ -1331,7 +1331,7 @@ public void testPartitionProjectionInvalidTableProperties() ") WITH ( " + " partition_projection_enabled=true " + ")")) - .hasMessage("Partition projection can't be enabled when no partition columns are defined."); + .hasMessage("Partition projection cannot be enabled on a table that is not partitioned"); assertThatThrownBy(() -> getQueryRunner().execute( "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + @@ -1344,7 +1344,7 @@ public void testPartitionProjectionInvalidTableProperties() " partitioned_by=ARRAY['short_name1'], " + " partition_projection_enabled=true " + ")")) - .hasMessage("Partition projection can't be defined for non partition column: 'name'"); + .hasMessage("Partition projection cannot be defined for non-partition column: 'name'"); assertThatThrownBy(() -> getQueryRunner().execute( "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + @@ -1358,7 +1358,7 @@ public void testPartitionProjectionInvalidTableProperties() " partitioned_by=ARRAY['short_name1', 'short_name2'], " + " partition_projection_enabled=true " + ")")) - .hasMessage("Partition projection definition for column: 'short_name2' missing"); + .hasMessage("Column projection for column 'short_name2' failed. Projection type property missing"); assertThatThrownBy(() -> getQueryRunner().execute( "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + @@ -1496,7 +1496,7 @@ public void testPartitionProjectionInvalidTableProperties() ") WITH ( " + " partitioned_by=ARRAY['short_name1'] " + ")")) - .hasMessage("Columns ['short_name1'] projections are disallowed when partition projection property 'partition_projection_enabled' is missing"); + .hasMessage("Columns partition projection properties cannot be set when 'partition_projection_enabled' is not set"); // Verify that ignored flag is only interpreted for pre-existing tables where configuration is loaded from metastore. // It should not allow creating corrupted config via Trino. It's a kill switch to run away when we have compatibility issues. @@ -1510,7 +1510,7 @@ public void testPartitionProjectionInvalidTableProperties() " )" + ") WITH ( " + " partitioned_by=ARRAY['short_name1'], " + - " partition_projection_enabled=false, " + + " partition_projection_enabled=true, " + " partition_projection_ignore=true " + // <-- Even if this is set we disallow creating corrupted configuration via Trino ")")) .hasMessage("Column projection for column 'short_name1' failed. Property: 'partition_projection_interval_unit' " + From c5e786b6d27da1cb129cf2034c5b77821d6d4231 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Fri, 17 Nov 2023 00:04:36 -0800 Subject: [PATCH 325/587] Merge ProjectionFactory implementations into Partition implementations --- .../hive/aws/athena/DateProjection.java | 158 +++++++++++++- .../aws/athena/DateProjectionFactory.java | 195 ------------------ .../hive/aws/athena/EnumProjection.java | 19 +- .../aws/athena/EnumProjectionFactory.java | 48 ----- .../hive/aws/athena/InjectedProjection.java | 5 +- .../aws/athena/InjectedProjectionFactory.java | 36 ---- .../hive/aws/athena/IntegerProjection.java | 36 +++- .../aws/athena/IntegerProjectionFactory.java | 63 ------ .../athena/PartitionProjectionService.java | 29 +-- .../hive/aws/athena/ProjectionFactory.java | 26 --- .../aws/athena/TestDateProjectionFactory.java | 22 +- .../aws/athena/TestEnumProjectionFactory.java | 12 +- .../athena/TestInjectedProjectionFactory.java | 17 +- .../athena/TestIntegerProjectionFactory.java | 20 +- 14 files changed, 251 insertions(+), 435 deletions(-) delete mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjectionFactory.java delete mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjectionFactory.java delete mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjectionFactory.java delete mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjectionFactory.java delete mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/ProjectionFactory.java diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjection.java index 4e0c2a34556a..480f5f7fd911 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjection.java @@ -14,6 +14,8 @@ package io.trino.plugin.hive.aws.athena; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import io.trino.spi.TrinoException; import io.trino.spi.predicate.Domain; import io.trino.spi.type.DateType; import io.trino.spi.type.TimestampType; @@ -22,23 +24,52 @@ import java.text.DateFormat; import java.text.ParseException; +import java.text.SimpleDateFormat; import java.time.Instant; +import java.time.ZoneId; import java.time.temporal.ChronoField; import java.time.temporal.ChronoUnit; import java.util.Date; import java.util.List; +import java.util.Locale; +import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import static com.google.common.collect.ImmutableList.toImmutableList; import static io.airlift.slice.Slices.utf8Slice; -import static io.trino.plugin.hive.aws.athena.DateProjectionFactory.UTC_TIME_ZONE_ID; +import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_FORMAT; +import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_INTERVAL; +import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_INTERVAL_UNIT; +import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_RANGE; +import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getProjectionPropertyRequiredValue; +import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getProjectionPropertyValue; import static io.trino.spi.predicate.Domain.singleValue; +import static java.lang.String.format; +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.ChronoUnit.HOURS; +import static java.time.temporal.ChronoUnit.MINUTES; +import static java.time.temporal.ChronoUnit.MONTHS; +import static java.time.temporal.ChronoUnit.SECONDS; +import static java.util.Objects.nonNull; import static java.util.Objects.requireNonNull; +import static java.util.TimeZone.getTimeZone; import static java.util.concurrent.TimeUnit.MILLISECONDS; final class DateProjection implements Projection { + private static final ZoneId UTC_TIME_ZONE_ID = ZoneId.of("UTC"); + // Limited to only DAYS, HOURS, MINUTES, SECONDS as we are not fully sure how everything above day + // is implemented in Athena. So we limit it to a subset of interval units which are explicitly clear how to calculate. + // The rest will be implemented if this is required as it would require making compatibility tests + // for results received from Athena and verifying if we receive identical with Trino. + private static final Set DATE_PROJECTION_INTERVAL_UNITS = ImmutableSet.of(DAYS, HOURS, MINUTES, SECONDS); + private static final Pattern DATE_RANGE_BOUND_EXPRESSION_PATTERN = Pattern.compile("^\\s*NOW\\s*(([+-])\\s*([0-9]+)\\s*(DAY|HOUR|MINUTE|SECOND)S?\\s*)?$"); + private final String columnName; private final DateFormat dateFormat; private final Supplier leftBound; @@ -46,14 +77,57 @@ final class DateProjection private final int interval; private final ChronoUnit intervalUnit; - public DateProjection(String columnName, DateFormat dateFormat, Supplier leftBound, Supplier rightBound, int interval, ChronoUnit intervalUnit) + public DateProjection(String columnName, Type columnType, Map columnProperties) { + if (!(columnType instanceof VarcharType) && + !(columnType instanceof DateType) && + !(columnType instanceof TimestampType timestampType && timestampType.isShort())) { + throw new InvalidProjectionException(columnName, columnType); + } + this.columnName = requireNonNull(columnName, "columnName is null"); + + String dateFormatPattern = getProjectionPropertyRequiredValue( + columnName, + columnProperties, + COLUMN_PROJECTION_FORMAT, + String::valueOf); + + List range = getProjectionPropertyRequiredValue( + columnName, + columnProperties, + COLUMN_PROJECTION_RANGE, + value -> ((List) value).stream() + .map(String.class::cast) + .collect(toImmutableList())); + if (range.size() != 2) { + throw invalidRangeProperty(columnName, dateFormatPattern, Optional.empty()); + } + + SimpleDateFormat dateFormat = new SimpleDateFormat(dateFormatPattern); + dateFormat.setLenient(false); + dateFormat.setTimeZone(getTimeZone(UTC_TIME_ZONE_ID)); this.dateFormat = requireNonNull(dateFormat, "dateFormatPattern is null"); - this.leftBound = requireNonNull(leftBound, "leftBound is null"); - this.rightBound = requireNonNull(rightBound, "rightBound is null"); - this.interval = interval; - this.intervalUnit = requireNonNull(intervalUnit, "intervalUnit is null"); + + leftBound = parseDateRangerBound(columnName, range.get(0), dateFormat); + rightBound = parseDateRangerBound(columnName, range.get(1), dateFormat); + if (!leftBound.get().isBefore(rightBound.get())) { + throw invalidRangeProperty(columnName, dateFormatPattern, Optional.empty()); + } + + interval = getProjectionPropertyValue(columnProperties, COLUMN_PROJECTION_INTERVAL, Integer.class::cast).orElse(1); + intervalUnit = getProjectionPropertyValue(columnProperties, COLUMN_PROJECTION_INTERVAL_UNIT, ChronoUnit.class::cast) + .orElseGet(() -> resolveDefaultChronoUnit(columnName, dateFormatPattern)); + + if (!DATE_PROJECTION_INTERVAL_UNITS.contains(intervalUnit)) { + throw new InvalidProjectionException( + columnName, + format( + "Property: '%s' value '%s' is invalid. Available options: %s", + COLUMN_PROJECTION_INTERVAL_UNIT, + intervalUnit, + DATE_PROJECTION_INTERVAL_UNITS)); + } } @Override @@ -112,4 +186,76 @@ private boolean isValueInDomain(Optional valueDomain, Instant value, Str } throw new InvalidProjectionException(columnName, type); } + + private static ChronoUnit resolveDefaultChronoUnit(String columnName, String dateFormatPattern) + { + String datePatternWithoutText = dateFormatPattern.replaceAll("'.*?'", ""); + if (datePatternWithoutText.contains("S") || datePatternWithoutText.contains("s") + || datePatternWithoutText.contains("m") || datePatternWithoutText.contains("H")) { + // When the provided dates are at single-day or single-month precision. + throw new InvalidProjectionException( + columnName, + format( + "Property: '%s' needs to be set when provided '%s' is less that single-day precision. Interval defaults to 1 day or 1 month, respectively. Otherwise, interval is required", + COLUMN_PROJECTION_INTERVAL_UNIT, + COLUMN_PROJECTION_FORMAT)); + } + if (datePatternWithoutText.contains("d")) { + return DAYS; + } + return MONTHS; + } + + private static Supplier parseDateRangerBound(String columnName, String value, SimpleDateFormat dateFormat) + { + Matcher matcher = DATE_RANGE_BOUND_EXPRESSION_PATTERN.matcher(value); + if (matcher.matches()) { + String operator = matcher.group(2); + String multiplierString = matcher.group(3); + String unitString = matcher.group(4); + if (nonNull(operator) && nonNull(multiplierString) && nonNull(unitString)) { + unitString = unitString.toUpperCase(Locale.ENGLISH); + return new DateExpressionBound( + Integer.parseInt(multiplierString), + ChronoUnit.valueOf(unitString + "S"), + operator.charAt(0) == '+'); + } + if (value.trim().equals("NOW")) { + Instant now = Instant.now(); + return () -> now; + } + throw invalidRangeProperty(columnName, dateFormat.toPattern(), Optional.of("Invalid expression")); + } + + Instant dateBound; + try { + dateBound = dateFormat.parse(value).toInstant(); + } + catch (ParseException e) { + throw invalidRangeProperty(columnName, dateFormat.toPattern(), Optional.of(e.getMessage())); + } + return () -> dateBound; + } + + private static TrinoException invalidRangeProperty(String columnName, String dateFormatPattern, Optional errorDetail) + { + throw new InvalidProjectionException( + columnName, + format( + "Property: '%s' needs to be a list of 2 valid dates formatted as '%s' or '%s' that are sequential%s", + COLUMN_PROJECTION_RANGE, + dateFormatPattern, + DATE_RANGE_BOUND_EXPRESSION_PATTERN.pattern(), + errorDetail.map(error -> ": " + error).orElse(""))); + } + + private record DateExpressionBound(int multiplier, ChronoUnit unit, boolean increment) + implements Supplier + { + @Override + public Instant get() + { + return Instant.now().plus(increment ? multiplier : -multiplier, unit); + } + } } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjectionFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjectionFactory.java deleted file mode 100644 index 25d36e508565..000000000000 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjectionFactory.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * 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 io.trino.plugin.hive.aws.athena; - -import com.google.common.collect.ImmutableSet; -import io.trino.spi.TrinoException; -import io.trino.spi.type.DateType; -import io.trino.spi.type.TimestampType; -import io.trino.spi.type.Type; -import io.trino.spi.type.VarcharType; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.time.Instant; -import java.time.ZoneId; -import java.time.temporal.ChronoUnit; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.Supplier; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static com.google.common.collect.ImmutableList.toImmutableList; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_FORMAT; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_INTERVAL; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_INTERVAL_UNIT; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_RANGE; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getProjectionPropertyRequiredValue; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getProjectionPropertyValue; -import static java.lang.String.format; -import static java.time.temporal.ChronoUnit.DAYS; -import static java.time.temporal.ChronoUnit.HOURS; -import static java.time.temporal.ChronoUnit.MINUTES; -import static java.time.temporal.ChronoUnit.MONTHS; -import static java.time.temporal.ChronoUnit.SECONDS; -import static java.util.Objects.nonNull; -import static java.util.TimeZone.getTimeZone; - -final class DateProjectionFactory - implements ProjectionFactory -{ - public static final ZoneId UTC_TIME_ZONE_ID = ZoneId.of("UTC"); - - // Limited to only DAYS, HOURS, MINUTES, SECONDS as we are not fully sure how everything above day - // is implemented in Athena. So we limit it to a subset of interval units which are explicitly clear how to calculate. - // Rest will be implemented if this will be required as it would require making compatibility tests - // for results received from Athena and verifying if we receive identical with Trino. - private static final Set DATE_PROJECTION_INTERVAL_UNITS = ImmutableSet.of(DAYS, HOURS, MINUTES, SECONDS); - private static final Pattern DATE_RANGE_BOUND_EXPRESSION_PATTERN = Pattern.compile("^\\s*NOW\\s*(([+-])\\s*([0-9]+)\\s*(DAY|HOUR|MINUTE|SECOND)S?\\s*)?$"); - - @Override - public boolean isSupportedColumnType(Type columnType) - { - return columnType instanceof VarcharType - || columnType instanceof DateType - || (columnType instanceof TimestampType && ((TimestampType) columnType).isShort()); - } - - @Override - public Projection create(String columnName, Type columnType, Map columnProperties) - { - String dateFormatPattern = getProjectionPropertyRequiredValue( - columnName, - columnProperties, - COLUMN_PROJECTION_FORMAT, - String::valueOf); - - List range = getProjectionPropertyRequiredValue( - columnName, - columnProperties, - COLUMN_PROJECTION_RANGE, - value -> ((List) value).stream() - .map(String.class::cast) - .collect(toImmutableList())); - if (range.size() != 2) { - throw invalidRangeProperty(columnName, dateFormatPattern); - } - - SimpleDateFormat dateFormat = new SimpleDateFormat(dateFormatPattern); - dateFormat.setLenient(false); - dateFormat.setTimeZone(getTimeZone(UTC_TIME_ZONE_ID)); - Supplier leftBound = parseDateRangerBound(columnName, range.get(0), dateFormat); - Supplier rangeBound = parseDateRangerBound(columnName, range.get(1), dateFormat); - if (!leftBound.get().isBefore(rangeBound.get())) { - throw invalidRangeProperty(columnName, dateFormatPattern); - } - - int interval = getProjectionPropertyValue(columnProperties, COLUMN_PROJECTION_INTERVAL, Integer.class::cast).orElse(1); - ChronoUnit intervalUnit = getProjectionPropertyValue(columnProperties, COLUMN_PROJECTION_INTERVAL_UNIT, ChronoUnit.class::cast) - .orElseGet(() -> resolveDefaultChronoUnit(columnName, dateFormatPattern)); - - if (!DATE_PROJECTION_INTERVAL_UNITS.contains(intervalUnit)) { - throw new InvalidProjectionException( - columnName, - format( - "Property: '%s' value '%s' is invalid. Available options: %s", - COLUMN_PROJECTION_INTERVAL_UNIT, - intervalUnit, - DATE_PROJECTION_INTERVAL_UNITS)); - } - - return new DateProjection(columnName, dateFormat, leftBound, rangeBound, interval, intervalUnit); - } - - private static ChronoUnit resolveDefaultChronoUnit(String columnName, String dateFormatPattern) - { - String datePatternWithoutText = dateFormatPattern.replaceAll("'.*?'", ""); - if (datePatternWithoutText.contains("S") || datePatternWithoutText.contains("s") - || datePatternWithoutText.contains("m") || datePatternWithoutText.contains("H")) { - // When the provided dates are at single-day or single-month precision. - throw new InvalidProjectionException( - columnName, - format( - "Property: '%s' needs to be set when provided '%s' is less that single-day precision. Interval defaults to 1 day or 1 month, respectively. Otherwise, interval is required", - COLUMN_PROJECTION_INTERVAL_UNIT, - COLUMN_PROJECTION_FORMAT)); - } - if (datePatternWithoutText.contains("d")) { - return DAYS; - } - return MONTHS; - } - - private static Supplier parseDateRangerBound(String columnName, String value, SimpleDateFormat dateFormat) - { - Matcher matcher = DATE_RANGE_BOUND_EXPRESSION_PATTERN.matcher(value); - if (matcher.matches()) { - String operator = matcher.group(2); - String multiplierString = matcher.group(3); - String unitString = matcher.group(4); - if (nonNull(operator) && nonNull(multiplierString) && nonNull(unitString)) { - unitString = unitString.toUpperCase(Locale.ENGLISH); - return new DateExpressionBound( - Integer.parseInt(multiplierString), - ChronoUnit.valueOf(unitString + "S"), - operator.charAt(0) == '+'); - } - if (value.trim().equals("NOW")) { - Instant now = Instant.now(); - return () -> now; - } - throw invalidRangeProperty(columnName, dateFormat.toPattern(), Optional.of("Invalid expression")); - } - - Instant dateBound; - try { - dateBound = dateFormat.parse(value).toInstant(); - } - catch (ParseException e) { - throw invalidRangeProperty(columnName, dateFormat.toPattern(), Optional.of(e.getMessage())); - } - return () -> dateBound; - } - - private static TrinoException invalidRangeProperty(String columnName, String dateFormatPattern) - { - return invalidRangeProperty(columnName, dateFormatPattern, Optional.empty()); - } - - private static TrinoException invalidRangeProperty(String columnName, String dateFormatPattern, Optional errorDetail) - { - throw new InvalidProjectionException( - columnName, - format( - "Property: '%s' needs to be a list of 2 valid dates formatted as '%s' or '%s' that are sequential%s", - COLUMN_PROJECTION_RANGE, - dateFormatPattern, - DATE_RANGE_BOUND_EXPRESSION_PATTERN.pattern(), - errorDetail.map(error -> ". " + error).orElse(""))); - } - - private record DateExpressionBound(int multiplier, ChronoUnit unit, boolean increment) - implements Supplier - { - @Override - public Instant get() - { - return Instant.now().plus(increment ? multiplier : -multiplier, unit); - } - } -} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjection.java index 4bdd5b536a05..e3ea35a218ce 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjection.java @@ -13,15 +13,18 @@ */ package io.trino.plugin.hive.aws.athena; -import com.google.common.collect.ImmutableList; import io.trino.spi.predicate.Domain; import io.trino.spi.type.Type; import io.trino.spi.type.VarcharType; import java.util.List; +import java.util.Map; import java.util.Optional; +import static com.google.common.collect.ImmutableList.toImmutableList; import static io.airlift.slice.Slices.utf8Slice; +import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_VALUES; +import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getProjectionPropertyRequiredValue; import static io.trino.spi.predicate.Domain.singleValue; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; @@ -32,10 +35,20 @@ final class EnumProjection private final String columnName; private final List values; - public EnumProjection(String columnName, List values) + public EnumProjection(String columnName, Type columnType, Map columnProperties) { + if (!(columnType instanceof VarcharType)) { + throw new InvalidProjectionException(columnName, columnType); + } + this.columnName = requireNonNull(columnName, "columnName is null"); - this.values = ImmutableList.copyOf(requireNonNull(values, "values is null")); + this.values = getProjectionPropertyRequiredValue( + columnName, + columnProperties, + COLUMN_PROJECTION_VALUES, + value -> ((List) value).stream() + .map(String::valueOf) + .collect(toImmutableList())); } @Override diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjectionFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjectionFactory.java deleted file mode 100644 index 74e8bb9eb0b5..000000000000 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjectionFactory.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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 io.trino.plugin.hive.aws.athena; - -import io.trino.spi.type.Type; -import io.trino.spi.type.VarcharType; - -import java.util.List; -import java.util.Map; - -import static com.google.common.collect.ImmutableList.toImmutableList; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_VALUES; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getProjectionPropertyRequiredValue; - -final class EnumProjectionFactory - implements ProjectionFactory -{ - @Override - public boolean isSupportedColumnType(Type columnType) - { - return columnType instanceof VarcharType; - } - - @Override - public Projection create(String columnName, Type columnType, Map columnProperties) - { - return new EnumProjection( - columnName, - getProjectionPropertyRequiredValue( - columnName, - columnProperties, - COLUMN_PROJECTION_VALUES, - value -> ((List) value).stream() - .map(String::valueOf) - .collect(toImmutableList()))); - } -} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjection.java index 5e2d290c6d85..215b1aac45da 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjection.java @@ -29,8 +29,11 @@ final class InjectedProjection { private final String columnName; - public InjectedProjection(String columnName) + public InjectedProjection(String columnName, Type columnType) { + if (!canConvertSqlTypeToStringForParts(columnType, true)) { + throw new InvalidProjectionException(columnName, columnType); + } this.columnName = requireNonNull(columnName, "columnName is null"); } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjectionFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjectionFactory.java deleted file mode 100644 index ffae59c3bc47..000000000000 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjectionFactory.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 io.trino.plugin.hive.aws.athena; - -import io.trino.spi.type.Type; - -import java.util.Map; - -import static io.trino.plugin.hive.metastore.MetastoreUtil.canConvertSqlTypeToStringForParts; - -final class InjectedProjectionFactory - implements ProjectionFactory -{ - @Override - public boolean isSupportedColumnType(Type columnType) - { - return canConvertSqlTypeToStringForParts(columnType, true); - } - - @Override - public Projection create(String columnName, Type columnType, Map columnProperties) - { - return new InjectedProjection(columnName); - } -} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjection.java index e96103250c9d..dc21fe5cca92 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjection.java @@ -22,10 +22,18 @@ import io.trino.spi.type.VarcharType; import java.util.List; +import java.util.Map; import java.util.Optional; +import static com.google.common.collect.ImmutableList.toImmutableList; import static io.airlift.slice.Slices.utf8Slice; +import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_DIGITS; +import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_INTERVAL; +import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_RANGE; +import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getProjectionPropertyRequiredValue; +import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getProjectionPropertyValue; import static io.trino.spi.predicate.Domain.singleValue; +import static java.lang.String.format; import static java.util.Objects.requireNonNull; final class IntegerProjection @@ -37,13 +45,29 @@ final class IntegerProjection private final int interval; private final Optional digits; - public IntegerProjection(String columnName, int leftBound, int rightBound, int interval, Optional digits) + public IntegerProjection(String columnName, Type columnType, Map columnProperties) { + if (!(columnType instanceof VarcharType) && !(columnType instanceof IntegerType) && !(columnType instanceof BigintType)) { + throw new InvalidProjectionException(columnName, columnType); + } + this.columnName = requireNonNull(columnName, "columnName is null"); - this.leftBound = leftBound; - this.rightBound = rightBound; - this.interval = interval; - this.digits = requireNonNull(digits, "digits is null"); + + List range = getProjectionPropertyRequiredValue( + columnName, + columnProperties, + COLUMN_PROJECTION_RANGE, + value -> ((List) value).stream() + .map(element -> Integer.valueOf((String) element)) + .collect(toImmutableList())); + if (range.size() != 2) { + throw new InvalidProjectionException(columnName, format("Property: '%s' needs to be list of 2 integers", COLUMN_PROJECTION_RANGE)); + } + this.leftBound = range.get(0); + this.rightBound = range.get(1); + + this.interval = getProjectionPropertyValue(columnProperties, COLUMN_PROJECTION_INTERVAL, Integer.class::cast).orElse(1); + this.digits = getProjectionPropertyValue(columnProperties, COLUMN_PROJECTION_DIGITS, Integer.class::cast); } @Override @@ -54,7 +78,7 @@ public List getProjectedValues(Optional partitionValueFilter) while (current <= rightBound) { int currentValue = current; String currentValueFormatted = digits - .map(digits -> String.format("%0" + digits + "d", currentValue)) + .map(digits -> format("%0" + digits + "d", currentValue)) .orElseGet(() -> Integer.toString(currentValue)); if (isValueInDomain(partitionValueFilter, current, currentValueFormatted)) { builder.add(currentValueFormatted); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjectionFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjectionFactory.java deleted file mode 100644 index fd356eac733b..000000000000 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjectionFactory.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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 io.trino.plugin.hive.aws.athena; - -import io.trino.spi.type.BigintType; -import io.trino.spi.type.IntegerType; -import io.trino.spi.type.Type; -import io.trino.spi.type.VarcharType; - -import java.util.List; -import java.util.Map; - -import static com.google.common.collect.ImmutableList.toImmutableList; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_DIGITS; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_INTERVAL; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_RANGE; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getProjectionPropertyRequiredValue; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getProjectionPropertyValue; -import static java.lang.String.format; - -final class IntegerProjectionFactory - implements ProjectionFactory -{ - @Override - public boolean isSupportedColumnType(Type columnType) - { - return columnType instanceof VarcharType - || columnType instanceof IntegerType - || columnType instanceof BigintType; - } - - @Override - public Projection create(String columnName, Type columnType, Map columnProperties) - { - List range = getProjectionPropertyRequiredValue( - columnName, - columnProperties, - COLUMN_PROJECTION_RANGE, - value -> ((List) value).stream() - .map(element -> Integer.valueOf((String) element)) - .collect(toImmutableList())); - if (range.size() != 2) { - throw new InvalidProjectionException(columnName, format("Property: '%s' needs to be list of 2 integers", COLUMN_PROJECTION_RANGE)); - } - return new IntegerProjection( - columnName, - range.get(0), - range.get(1), - getProjectionPropertyValue(columnProperties, COLUMN_PROJECTION_INTERVAL, Integer.class::cast).orElse(1), - getProjectionPropertyValue(columnProperties, COLUMN_PROJECTION_DIGITS, Integer.class::cast)); - } -} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java index 60bba1c10304..45242e07e72c 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java @@ -60,10 +60,6 @@ import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.PARTITION_PROJECTION_LOCATION_TEMPLATE; import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.PROPERTY_KEY_PREFIX; import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getMetastoreProjectionPropertyKey; -import static io.trino.plugin.hive.aws.athena.ProjectionType.DATE; -import static io.trino.plugin.hive.aws.athena.ProjectionType.ENUM; -import static io.trino.plugin.hive.aws.athena.ProjectionType.INJECTED; -import static io.trino.plugin.hive.aws.athena.ProjectionType.INTEGER; import static java.lang.Boolean.parseBoolean; import static java.lang.String.format; import static java.util.Locale.ROOT; @@ -72,7 +68,6 @@ public final class PartitionProjectionService { private final boolean partitionProjectionEnabled; - private final Map projectionFactories; private final TypeManager typeManager; @Inject @@ -80,12 +75,6 @@ public PartitionProjectionService(HiveConfig hiveConfig, TypeManager typeManager { this.partitionProjectionEnabled = hiveConfig.isPartitionProjectionEnabled(); this.typeManager = requireNonNull(typeManager, "typeManager is null"); - this.projectionFactories = ImmutableMap.builder() - .put(ENUM, new EnumProjectionFactory()) - .put(INTEGER, new IntegerProjectionFactory()) - .put(DATE, new DateProjectionFactory()) - .put(INJECTED, new InjectedProjectionFactory()) - .buildOrThrow(); } public static Map getPartitionProjectionTrinoTableProperties(Table table) @@ -232,7 +221,7 @@ Optional getPartitionProjectionFromTable(Table table) tableProperties)); } - private PartitionProjection createPartitionProjection(List dataColumns, Map partitionColumns, Map tableProperties) + private static PartitionProjection createPartitionProjection(List dataColumns, Map partitionColumns, Map tableProperties) { // This method is used during table creation to validate the properties. The validation is performed even if the projection is disabled. boolean enabled = parseBoolean(tableProperties.get(METASTORE_PROPERTY_PROJECTION_ENABLED)); @@ -314,20 +303,18 @@ private static Map rewriteColumnProjectionProperties(Map columnProperties) + private static Projection parseColumnProjection(String columnName, Type columnType, Map columnProperties) { ProjectionType projectionType = (ProjectionType) columnProperties.get(COLUMN_PROJECTION_TYPE); if (projectionType == null) { throw new InvalidProjectionException(columnName, "Projection type property missing"); } - ProjectionFactory projectionFactory = projectionFactories.get(projectionType); - if (projectionFactory == null) { - throw new InvalidProjectionException(columnName, format("Partition projection type %s for column: '%s' not supported", projectionType, columnName)); - } - if (!projectionFactory.isSupportedColumnType(columnType)) { - throw new InvalidProjectionException(columnName, columnType); - } - return projectionFactory.create(columnName, columnType, columnProperties); + return switch (projectionType) { + case ENUM -> new EnumProjection(columnName, columnType, columnProperties); + case INTEGER -> new IntegerProjection(columnName, columnType, columnProperties); + case DATE -> new DateProjection(columnName, columnType, columnProperties); + case INJECTED -> new InjectedProjection(columnName, columnType); + }; } private static List splitCommaSeparatedString(String value) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/ProjectionFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/ProjectionFactory.java deleted file mode 100644 index 592d17c6969c..000000000000 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/ProjectionFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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 io.trino.plugin.hive.aws.athena; - -import io.trino.spi.type.Type; - -import java.util.Map; - -sealed interface ProjectionFactory - permits DateProjectionFactory, EnumProjectionFactory, InjectedProjectionFactory, IntegerProjectionFactory -{ - boolean isSupportedColumnType(Type columnType); - - Projection create(String columnName, Type columnType, Map columnProperties); -} diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestDateProjectionFactory.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestDateProjectionFactory.java index 3ddc423f50a4..e12b073ff344 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestDateProjectionFactory.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestDateProjectionFactory.java @@ -35,27 +35,31 @@ class TestDateProjectionFactory { @Test - void testIsSupported() + void testTypeSupport() { - assertThat(new DateProjectionFactory().isSupportedColumnType(VARCHAR)).isTrue(); - assertThat(new DateProjectionFactory().isSupportedColumnType(DATE)).isTrue(); - assertThat(new DateProjectionFactory().isSupportedColumnType(TIMESTAMP_SECONDS)).isTrue(); - assertThat(new DateProjectionFactory().isSupportedColumnType(TIMESTAMP_MICROS)).isTrue(); - assertThat(new DateProjectionFactory().isSupportedColumnType(TIMESTAMP_NANOS)).isFalse(); - assertThat(new DateProjectionFactory().isSupportedColumnType(BIGINT)).isFalse(); + new DateProjection("test", VARCHAR, ImmutableMap.of(COLUMN_PROJECTION_FORMAT, "yyyy-MM-dd", COLUMN_PROJECTION_RANGE, ImmutableList.of("2020-01-01", "2020-01-03"))); + new DateProjection("test", DATE, ImmutableMap.of(COLUMN_PROJECTION_FORMAT, "yyyy-MM-dd", COLUMN_PROJECTION_RANGE, ImmutableList.of("2020-01-01", "2020-01-03"))); + new DateProjection("test", TIMESTAMP_SECONDS, ImmutableMap.of(COLUMN_PROJECTION_FORMAT, "yyyy-MM-dd", COLUMN_PROJECTION_RANGE, ImmutableList.of("2020-01-01", "2020-01-03"))); + new DateProjection("test", TIMESTAMP_MICROS, ImmutableMap.of(COLUMN_PROJECTION_FORMAT, "yyyy-MM-dd", COLUMN_PROJECTION_RANGE, ImmutableList.of("2020-01-01", "2020-01-03"))); + assertThatThrownBy(() -> new DateProjection("test", TIMESTAMP_NANOS, ImmutableMap.of(COLUMN_PROJECTION_FORMAT, "yyyy-MM-dd", COLUMN_PROJECTION_RANGE, ImmutableList.of("2020-01-01", "2020-01-03")))) + .isInstanceOf(InvalidProjectionException.class) + .hasMessage("Column projection for column 'test' failed. Unsupported column type: timestamp(9)"); + assertThatThrownBy(() -> new DateProjection("test", BIGINT, ImmutableMap.of(COLUMN_PROJECTION_FORMAT, "yyyy-MM-dd", COLUMN_PROJECTION_RANGE, ImmutableList.of("2020-01-01", "2020-01-03")))) + .isInstanceOf(InvalidProjectionException.class) + .hasMessage("Column projection for column 'test' failed. Unsupported column type: bigint"); } @Test void testCreate() { - Projection projection = new DateProjectionFactory().create("test", VARCHAR, ImmutableMap.of(COLUMN_PROJECTION_FORMAT, "yyyy-MM-dd", COLUMN_PROJECTION_RANGE, ImmutableList.of("2020-01-01", "2020-01-03"))); + Projection projection = new DateProjection("test", VARCHAR, ImmutableMap.of(COLUMN_PROJECTION_FORMAT, "yyyy-MM-dd", COLUMN_PROJECTION_RANGE, ImmutableList.of("2020-01-01", "2020-01-03"))); assertThat(projection.getProjectedValues(Optional.empty())).containsExactly("2020-01-01", "2020-01-02", "2020-01-03"); assertThat(projection.getProjectedValues(Optional.of(Domain.all(VARCHAR)))).containsExactly("2020-01-01", "2020-01-02", "2020-01-03"); assertThat(projection.getProjectedValues(Optional.of(Domain.none(VARCHAR)))).isEmpty(); assertThat(projection.getProjectedValues(Optional.of(Domain.singleValue(VARCHAR, Slices.utf8Slice("2020-01-02"))))).containsExactly("2020-01-02"); assertThat(projection.getProjectedValues(Optional.of(Domain.singleValue(VARCHAR, Slices.utf8Slice("2222-01-01"))))).isEmpty(); - assertThatThrownBy(() -> new DateProjectionFactory().create("test", VARCHAR, ImmutableMap.of("ignored", ImmutableList.of("2020-01-01", "2020-01-02", "2020-01-03")))) + assertThatThrownBy(() -> new DateProjection("test", VARCHAR, ImmutableMap.of("ignored", ImmutableList.of("2020-01-01", "2020-01-02", "2020-01-03")))) .isInstanceOf(InvalidProjectionException.class) .hasMessage("Column projection for column 'test' failed. Missing required property: 'partition_projection_format'"); } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestEnumProjectionFactory.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestEnumProjectionFactory.java index 63fcadbe4fde..24a337471610 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestEnumProjectionFactory.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestEnumProjectionFactory.java @@ -32,25 +32,27 @@ class TestEnumProjectionFactory @Test void testIsSupported() { - assertThat(new EnumProjectionFactory().isSupportedColumnType(VARCHAR)).isTrue(); - assertThat(new EnumProjectionFactory().isSupportedColumnType(BIGINT)).isFalse(); + new EnumProjection("test", VARCHAR, ImmutableMap.of(COLUMN_PROJECTION_VALUES, ImmutableList.of("a", "b", "c"))); + assertThatThrownBy(() -> new EnumProjection("test", BIGINT, ImmutableMap.of(COLUMN_PROJECTION_VALUES, ImmutableList.of("a", "b", "c")))) + .isInstanceOf(InvalidProjectionException.class) + .hasMessage("Column projection for column 'test' failed. Unsupported column type: bigint"); } @Test void testCreate() { - Projection projection = new EnumProjectionFactory().create("test", VARCHAR, ImmutableMap.of(COLUMN_PROJECTION_VALUES, ImmutableList.of("a", "b", "c"))); + Projection projection = new EnumProjection("test", VARCHAR, ImmutableMap.of(COLUMN_PROJECTION_VALUES, ImmutableList.of("a", "b", "c"))); assertThat(projection.getProjectedValues(Optional.empty())).containsExactly("a", "b", "c"); assertThat(projection.getProjectedValues(Optional.of(Domain.all(VARCHAR)))).containsExactly("a", "b", "c"); assertThat(projection.getProjectedValues(Optional.of(Domain.none(VARCHAR)))).isEmpty(); assertThat(projection.getProjectedValues(Optional.of(Domain.singleValue(VARCHAR, Slices.utf8Slice("b"))))).containsExactly("b"); assertThat(projection.getProjectedValues(Optional.of(Domain.singleValue(VARCHAR, Slices.utf8Slice("x"))))).isEmpty(); - assertThatThrownBy(() -> new EnumProjectionFactory().create("test", VARCHAR, ImmutableMap.of("ignored", ImmutableList.of("a", "b", "c")))) + assertThatThrownBy(() -> new EnumProjection("test", VARCHAR, ImmutableMap.of("ignored", ImmutableList.of("a", "b", "c")))) .isInstanceOf(InvalidProjectionException.class) .hasMessage("Column projection for column 'test' failed. Missing required property: 'partition_projection_values'"); - assertThatThrownBy(() -> new EnumProjectionFactory().create("test", VARCHAR, ImmutableMap.of(COLUMN_PROJECTION_VALUES, "invalid"))) + assertThatThrownBy(() -> new EnumProjection("test", VARCHAR, ImmutableMap.of(COLUMN_PROJECTION_VALUES, "invalid"))) .isInstanceOf(ClassCastException.class); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestInjectedProjectionFactory.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestInjectedProjectionFactory.java index e47088103568..32ba355f5853 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestInjectedProjectionFactory.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestInjectedProjectionFactory.java @@ -13,7 +13,6 @@ */ package io.trino.plugin.hive.aws.athena; -import com.google.common.collect.ImmutableMap; import io.airlift.slice.Slices; import io.trino.spi.predicate.Domain; import org.junit.jupiter.api.Test; @@ -33,17 +32,21 @@ class TestInjectedProjectionFactory @Test void testIsSupported() { - assertThat(new InjectedProjectionFactory().isSupportedColumnType(VARCHAR)).isTrue(); - assertThat(new InjectedProjectionFactory().isSupportedColumnType(createCharType(10))).isTrue(); - assertThat(new InjectedProjectionFactory().isSupportedColumnType(BIGINT)).isTrue(); - assertThat(new InjectedProjectionFactory().isSupportedColumnType(TIMESTAMP_SECONDS)).isFalse(); - assertThat(new InjectedProjectionFactory().isSupportedColumnType(TIMESTAMP_PICOS)).isFalse(); + new InjectedProjection("test", VARCHAR); + new InjectedProjection("test", createCharType(10)); + new InjectedProjection("test", BIGINT); + assertThatThrownBy(() -> new InjectedProjection("test", TIMESTAMP_SECONDS)) + .isInstanceOf(InvalidProjectionException.class) + .hasMessage("Column projection for column 'test' failed. Unsupported column type: timestamp(0)"); + assertThatThrownBy(() -> new InjectedProjection("test", TIMESTAMP_PICOS)) + .isInstanceOf(InvalidProjectionException.class) + .hasMessage("Column projection for column 'test' failed. Unsupported column type: timestamp(12)"); } @Test void testCreate() { - Projection projection = new InjectedProjectionFactory().create("test", VARCHAR, ImmutableMap.of()); + Projection projection = new InjectedProjection("test", VARCHAR); assertThat(projection.getProjectedValues(Optional.of(Domain.singleValue(VARCHAR, Slices.utf8Slice("b"))))).containsExactly("b"); assertThat(projection.getProjectedValues(Optional.of(Domain.singleValue(VARCHAR, Slices.utf8Slice("x"))))).containsExactly("x"); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestIntegerProjectionFactory.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestIntegerProjectionFactory.java index ce78b361b911..2cf99aa3a161 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestIntegerProjectionFactory.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestIntegerProjectionFactory.java @@ -35,34 +35,36 @@ class TestIntegerProjectionFactory @Test void testIsSupported() { - assertThat(new IntegerProjectionFactory().isSupportedColumnType(VARCHAR)).isTrue(); - assertThat(new IntegerProjectionFactory().isSupportedColumnType(INTEGER)).isTrue(); - assertThat(new IntegerProjectionFactory().isSupportedColumnType(BIGINT)).isTrue(); - assertThat(new IntegerProjectionFactory().isSupportedColumnType(DATE)).isFalse(); + new IntegerProjection("test", VARCHAR, ImmutableMap.of(COLUMN_PROJECTION_RANGE, ImmutableList.of("1", "3"))); + new IntegerProjection("test", INTEGER, ImmutableMap.of(COLUMN_PROJECTION_RANGE, ImmutableList.of("1", "3"))); + new IntegerProjection("test", BIGINT, ImmutableMap.of(COLUMN_PROJECTION_RANGE, ImmutableList.of("1", "3"))); + assertThatThrownBy(() -> new IntegerProjection("test", DATE, ImmutableMap.of(COLUMN_PROJECTION_RANGE, ImmutableList.of("1", "3")))) + .isInstanceOf(InvalidProjectionException.class) + .hasMessage("Column projection for column 'test' failed. Unsupported column type: date"); } @Test void testCreateBasic() { - Projection projection = new IntegerProjectionFactory().create("test", INTEGER, ImmutableMap.of(COLUMN_PROJECTION_RANGE, ImmutableList.of("1", "3"))); + Projection projection = new IntegerProjection("test", INTEGER, ImmutableMap.of(COLUMN_PROJECTION_RANGE, ImmutableList.of("1", "3"))); assertThat(projection.getProjectedValues(Optional.empty())).containsExactly("1", "2", "3"); assertThat(projection.getProjectedValues(Optional.of(Domain.all(INTEGER)))).containsExactly("1", "2", "3"); assertThat(projection.getProjectedValues(Optional.of(Domain.none(INTEGER)))).isEmpty(); assertThat(projection.getProjectedValues(Optional.of(Domain.singleValue(INTEGER, 2L)))).containsExactly("2"); assertThat(projection.getProjectedValues(Optional.of(Domain.singleValue(INTEGER, 7L)))).isEmpty(); - assertThatThrownBy(() -> new IntegerProjectionFactory().create("test", INTEGER, ImmutableMap.of("ignored", ImmutableList.of("1", "3")))) + assertThatThrownBy(() -> new IntegerProjection("test", INTEGER, ImmutableMap.of("ignored", ImmutableList.of("1", "3")))) .isInstanceOf(InvalidProjectionException.class) .hasMessage("Column projection for column 'test' failed. Missing required property: 'partition_projection_range'"); - assertThatThrownBy(() -> new IntegerProjectionFactory().create("test", INTEGER, ImmutableMap.of(COLUMN_PROJECTION_RANGE, "invalid"))) + assertThatThrownBy(() -> new IntegerProjection("test", INTEGER, ImmutableMap.of(COLUMN_PROJECTION_RANGE, "invalid"))) .isInstanceOf(ClassCastException.class); } @Test void testInterval() { - Projection projection = new IntegerProjectionFactory().create("test", INTEGER, ImmutableMap.of(COLUMN_PROJECTION_RANGE, ImmutableList.of("10", "30"), COLUMN_PROJECTION_INTERVAL, 10)); + Projection projection = new IntegerProjection("test", INTEGER, ImmutableMap.of(COLUMN_PROJECTION_RANGE, ImmutableList.of("10", "30"), COLUMN_PROJECTION_INTERVAL, 10)); assertThat(projection.getProjectedValues(Optional.empty())).containsExactly("10", "20", "30"); assertThat(projection.getProjectedValues(Optional.of(Domain.all(INTEGER)))).containsExactly("10", "20", "30"); assertThat(projection.getProjectedValues(Optional.of(Domain.none(INTEGER)))).isEmpty(); @@ -73,7 +75,7 @@ void testInterval() @Test void testCreateDigits() { - Projection projection = new IntegerProjectionFactory().create("test", INTEGER, ImmutableMap.of(COLUMN_PROJECTION_RANGE, ImmutableList.of("1", "3"), COLUMN_PROJECTION_DIGITS, 3)); + Projection projection = new IntegerProjection("test", INTEGER, ImmutableMap.of(COLUMN_PROJECTION_RANGE, ImmutableList.of("1", "3"), COLUMN_PROJECTION_DIGITS, 3)); assertThat(projection.getProjectedValues(Optional.empty())).containsExactly("001", "002", "003"); assertThat(projection.getProjectedValues(Optional.of(Domain.all(INTEGER)))).containsExactly("001", "002", "003"); assertThat(projection.getProjectedValues(Optional.of(Domain.none(INTEGER)))).isEmpty(); From 716bd048768e276cdce6dbff30560142eafd581f Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Fri, 17 Nov 2023 09:30:52 -0800 Subject: [PATCH 326/587] Convert PartitionProjectionService to a static utility class --- .../io/trino/plugin/hive/HiveMetadata.java | 22 ++++++++++---- .../plugin/hive/HiveMetadataFactory.java | 12 ++++---- ...PartitionProjectionMetastoreDecorator.java | 26 ++++++++++++----- .../aws/athena/PartitionProjectionModule.java | 2 -- .../athena/PartitionProjectionService.java | 29 +++---------------- .../io/trino/plugin/hive/util/HiveUtil.java | 4 +-- .../trino/plugin/hive/AbstractTestHive.java | 4 +-- .../hive/AbstractTestHiveFileSystem.java | 3 -- 8 files changed, 46 insertions(+), 56 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java index c99199ff4166..ba993d7217ec 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java @@ -39,7 +39,6 @@ import io.trino.plugin.hive.LocationService.WriteInfo; import io.trino.plugin.hive.acid.AcidOperation; import io.trino.plugin.hive.acid.AcidTransaction; -import io.trino.plugin.hive.aws.athena.PartitionProjectionService; import io.trino.plugin.hive.fs.DirectoryLister; import io.trino.plugin.hive.metastore.Column; import io.trino.plugin.hive.metastore.Database; @@ -258,6 +257,9 @@ import static io.trino.plugin.hive.ViewReaderUtil.isTrinoView; import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; import static io.trino.plugin.hive.acid.AcidTransaction.forCreateTable; +import static io.trino.plugin.hive.aws.athena.PartitionProjectionService.arePartitionProjectionPropertiesSet; +import static io.trino.plugin.hive.aws.athena.PartitionProjectionService.getPartitionProjectionHiveTableProperties; +import static io.trino.plugin.hive.aws.athena.PartitionProjectionService.getPartitionProjectionTrinoTableProperties; import static io.trino.plugin.hive.metastore.MetastoreUtil.buildInitialPrivilegeSet; import static io.trino.plugin.hive.metastore.MetastoreUtil.getHiveSchema; import static io.trino.plugin.hive.metastore.MetastoreUtil.getProtectMode; @@ -301,6 +303,7 @@ import static io.trino.plugin.hive.util.Statistics.reduce; import static io.trino.plugin.hive.util.SystemTables.getSourceTableNameFromSystemTable; import static io.trino.spi.StandardErrorCode.INVALID_ANALYZE_PROPERTY; +import static io.trino.spi.StandardErrorCode.INVALID_COLUMN_PROPERTY; import static io.trino.spi.StandardErrorCode.INVALID_SCHEMA_PROPERTY; import static io.trino.spi.StandardErrorCode.INVALID_TABLE_PROPERTY; import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; @@ -383,7 +386,7 @@ public class HiveMetadata private final HiveMaterializedViewMetadata hiveMaterializedViewMetadata; private final AccessControlMetadata accessControlMetadata; private final DirectoryLister directoryLister; - private final PartitionProjectionService partitionProjectionService; + private final boolean partitionProjectionEnabled; private final boolean allowTableRename; private final long maxPartitionDropsPerQuery; private final HiveTimestampPrecision hiveViewsTimestampPrecision; @@ -411,7 +414,7 @@ public HiveMetadata( HiveMaterializedViewMetadata hiveMaterializedViewMetadata, AccessControlMetadata accessControlMetadata, DirectoryLister directoryLister, - PartitionProjectionService partitionProjectionService, + boolean partitionProjectionEnabled, boolean allowTableRename, long maxPartitionDropsPerQuery, HiveTimestampPrecision hiveViewsTimestampPrecision) @@ -438,7 +441,7 @@ public HiveMetadata( this.hiveMaterializedViewMetadata = requireNonNull(hiveMaterializedViewMetadata, "hiveMaterializedViewMetadata is null"); this.accessControlMetadata = requireNonNull(accessControlMetadata, "accessControlMetadata is null"); this.directoryLister = requireNonNull(directoryLister, "directoryLister is null"); - this.partitionProjectionService = requireNonNull(partitionProjectionService, "partitionProjectionService is null"); + this.partitionProjectionEnabled = partitionProjectionEnabled; this.allowTableRename = allowTableRename; this.maxPartitionDropsPerQuery = maxPartitionDropsPerQuery; this.hiveViewsTimestampPrecision = requireNonNull(hiveViewsTimestampPrecision, "hiveViewsTimestampPrecision is null"); @@ -741,7 +744,7 @@ else if (isTrinoView || isTrinoMaterializedView) { } // Partition Projection specific properties - properties.putAll(PartitionProjectionService.getPartitionProjectionTrinoTableProperties(table)); + properties.putAll(getPartitionProjectionTrinoTableProperties(table)); return new ConnectorTableMetadata(tableName, columns, properties.buildOrThrow(), comment); } @@ -1219,7 +1222,14 @@ else if (avroSchemaLiteral != null) { tableMetadata.getComment().ifPresent(value -> tableProperties.put(TABLE_COMMENT, value)); // Partition Projection specific properties - tableProperties.putAll(partitionProjectionService.getPartitionProjectionHiveTableProperties(tableMetadata)); + if (partitionProjectionEnabled) { + tableProperties.putAll(getPartitionProjectionHiveTableProperties(tableMetadata)); + } + else if (arePartitionProjectionPropertiesSet(tableMetadata)) { + throw new TrinoException( + INVALID_COLUMN_PROPERTY, + "Partition projection is disabled. Enable it in configuration by setting " + HiveConfig.CONFIGURATION_HIVE_PARTITION_PROJECTION_ENABLED + "=true"); + } Map baseProperties = tableProperties.buildOrThrow(); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadataFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadataFactory.java index 9e04878a434a..ec7bdaea8f72 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadataFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadataFactory.java @@ -20,7 +20,6 @@ import io.airlift.units.Duration; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.plugin.base.CatalogName; -import io.trino.plugin.hive.aws.athena.PartitionProjectionService; import io.trino.plugin.hive.fs.DirectoryLister; import io.trino.plugin.hive.fs.TransactionScopeCachingDirectoryListerFactory; import io.trino.plugin.hive.metastore.HiveMetastoreConfig; @@ -77,7 +76,7 @@ public class HiveMetadataFactory private final ScheduledExecutorService heartbeatService; private final DirectoryLister directoryLister; private final TransactionScopeCachingDirectoryListerFactory transactionScopeCachingDirectoryListerFactory; - private final PartitionProjectionService partitionProjectionService; + private final boolean partitionProjectionEnabled; private final boolean allowTableRename; private final HiveTimestampPrecision hiveViewsTimestampPrecision; @@ -103,7 +102,6 @@ public HiveMetadataFactory( AccessControlMetadataFactory accessControlMetadataFactory, DirectoryLister directoryLister, TransactionScopeCachingDirectoryListerFactory transactionScopeCachingDirectoryListerFactory, - PartitionProjectionService partitionProjectionService, @AllowHiveTableRename boolean allowTableRename) { this( @@ -139,7 +137,7 @@ public HiveMetadataFactory( accessControlMetadataFactory, directoryLister, transactionScopeCachingDirectoryListerFactory, - partitionProjectionService, + hiveConfig.isPartitionProjectionEnabled(), allowTableRename, hiveConfig.getTimestampPrecision()); } @@ -177,7 +175,7 @@ public HiveMetadataFactory( AccessControlMetadataFactory accessControlMetadataFactory, DirectoryLister directoryLister, TransactionScopeCachingDirectoryListerFactory transactionScopeCachingDirectoryListerFactory, - PartitionProjectionService partitionProjectionService, + boolean partitionProjectionEnabled, boolean allowTableRename, HiveTimestampPrecision hiveViewsTimestampPrecision) { @@ -220,7 +218,7 @@ public HiveMetadataFactory( this.heartbeatService = requireNonNull(heartbeatService, "heartbeatService is null"); this.directoryLister = requireNonNull(directoryLister, "directoryLister is null"); this.transactionScopeCachingDirectoryListerFactory = requireNonNull(transactionScopeCachingDirectoryListerFactory, "transactionScopeCachingDirectoryListerFactory is null"); - this.partitionProjectionService = requireNonNull(partitionProjectionService, "partitionProjectionService is null"); + this.partitionProjectionEnabled = partitionProjectionEnabled; this.allowTableRename = allowTableRename; this.hiveViewsTimestampPrecision = requireNonNull(hiveViewsTimestampPrecision, "hiveViewsTimestampPrecision is null"); } @@ -268,7 +266,7 @@ public TransactionalMetadata create(ConnectorIdentity identity, boolean autoComm hiveMaterializedViewMetadataFactory.create(hiveMetastoreClosure), accessControlMetadataFactory.create(metastore), directoryLister, - partitionProjectionService, + partitionProjectionEnabled, allowTableRename, maxPartitionDropsPerQuery, hiveViewsTimestampPrecision); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionMetastoreDecorator.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionMetastoreDecorator.java index a64aa7f3aece..15cee54d6413 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionMetastoreDecorator.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionMetastoreDecorator.java @@ -15,6 +15,7 @@ package io.trino.plugin.hive.aws.athena; import com.google.inject.Inject; +import io.trino.plugin.hive.HiveConfig; import io.trino.plugin.hive.metastore.ForwardingHiveMetastore; import io.trino.plugin.hive.metastore.HiveMetastore; import io.trino.plugin.hive.metastore.HiveMetastoreDecorator; @@ -22,23 +23,27 @@ import io.trino.plugin.hive.metastore.Table; import io.trino.spi.TrinoException; import io.trino.spi.predicate.TupleDomain; +import io.trino.spi.type.TypeManager; import java.util.List; import java.util.Map; import java.util.Optional; import static io.trino.plugin.hive.HiveErrorCode.HIVE_TABLE_DROPPED_DURING_QUERY; +import static io.trino.plugin.hive.aws.athena.PartitionProjectionService.getPartitionProjectionFromTable; import static java.util.Objects.requireNonNull; public class PartitionProjectionMetastoreDecorator implements HiveMetastoreDecorator { - private final PartitionProjectionService partitionProjectionService; + private final TypeManager typeManager; + private final boolean partitionProjectionEnabled; @Inject - public PartitionProjectionMetastoreDecorator(PartitionProjectionService partitionProjectionService) + public PartitionProjectionMetastoreDecorator(TypeManager typeManager, HiveConfig hiveConfig) { - this.partitionProjectionService = requireNonNull(partitionProjectionService, "partitionProjectionService is null"); + this.typeManager = requireNonNull(typeManager, "typeManager is null"); + this.partitionProjectionEnabled = hiveConfig.isPartitionProjectionEnabled(); } @Override @@ -50,18 +55,20 @@ public int getPriority() @Override public HiveMetastore decorate(HiveMetastore hiveMetastore) { - return new PartitionProjectionMetastore(hiveMetastore, partitionProjectionService); + return new PartitionProjectionMetastore(hiveMetastore, typeManager, partitionProjectionEnabled); } private static class PartitionProjectionMetastore extends ForwardingHiveMetastore { - private final PartitionProjectionService partitionProjectionService; + private final TypeManager typeManager; + private final boolean partitionProjectionEnabled; - public PartitionProjectionMetastore(HiveMetastore hiveMetastore, PartitionProjectionService partitionProjectionService) + public PartitionProjectionMetastore(HiveMetastore hiveMetastore, TypeManager typeManager, boolean partitionProjectionEnabled) { super(hiveMetastore); - this.partitionProjectionService = requireNonNull(partitionProjectionService, "partitionProjectionService is null"); + this.typeManager = typeManager; + this.partitionProjectionEnabled = partitionProjectionEnabled; } @Override @@ -90,7 +97,10 @@ public Map> getPartitionsByNames(Table table, List getPartitionProjection(Table table) { - return partitionProjectionService.getPartitionProjectionFromTable(table) + if (!partitionProjectionEnabled) { + return Optional.empty(); + } + return getPartitionProjectionFromTable(table, typeManager) .filter(PartitionProjection::isEnabled); } } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionModule.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionModule.java index fce6ae402747..7e0b71fb335f 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionModule.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionModule.java @@ -26,8 +26,6 @@ public class PartitionProjectionModule @Override public void setup(Binder binder) { - binder.bind(PartitionProjectionService.class).in(Scopes.SINGLETON); - newSetBinder(binder, HiveMetastoreDecorator.class) .addBinding() .to(PartitionProjectionMetastoreDecorator.class) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java index 45242e07e72c..e3965ff4741b 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java @@ -17,8 +17,6 @@ import com.google.common.base.Splitter; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.inject.Inject; -import io.trino.plugin.hive.HiveConfig; import io.trino.plugin.hive.metastore.Column; import io.trino.plugin.hive.metastore.Table; import io.trino.spi.connector.ColumnMetadata; @@ -63,19 +61,10 @@ import static java.lang.Boolean.parseBoolean; import static java.lang.String.format; import static java.util.Locale.ROOT; -import static java.util.Objects.requireNonNull; public final class PartitionProjectionService { - private final boolean partitionProjectionEnabled; - private final TypeManager typeManager; - - @Inject - public PartitionProjectionService(HiveConfig hiveConfig, TypeManager typeManager) - { - this.partitionProjectionEnabled = hiveConfig.isPartitionProjectionEnabled(); - this.typeManager = requireNonNull(typeManager, "typeManager is null"); - } + private PartitionProjectionService() {} public static Map getPartitionProjectionTrinoTableProperties(Table table) { @@ -106,14 +95,8 @@ public static Map getPartitionProjectionTrinoColumnProperties(Ta return rewriteColumnProjectionProperties(metastoreTableProperties, columnName); } - public Map getPartitionProjectionHiveTableProperties(ConnectorTableMetadata tableMetadata) + public static Map getPartitionProjectionHiveTableProperties(ConnectorTableMetadata tableMetadata) { - // If partition projection is globally disabled, we don't allow defining its properties - if (!partitionProjectionEnabled && isAnyPartitionProjectionPropertyUsed(tableMetadata)) { - throw new InvalidProjectionException("Partition projection is disabled. Enable it in configuration by setting " - + HiveConfig.CONFIGURATION_HIVE_PARTITION_PROJECTION_ENABLED + "=true"); - } - ImmutableMap.Builder metastoreTablePropertiesBuilder = ImmutableMap.builder(); // Handle Table Properties Map trinoTableProperties = tableMetadata.getProperties(); @@ -186,7 +169,7 @@ public Map getPartitionProjectionHiveTableProperties(ConnectorTa return metastoreTableProperties; } - private static boolean isAnyPartitionProjectionPropertyUsed(ConnectorTableMetadata tableMetadata) + public static boolean arePartitionProjectionPropertiesSet(ConnectorTableMetadata tableMetadata) { if (tableMetadata.getProperties().keySet().stream() .anyMatch(propertyKey -> propertyKey.startsWith(PROPERTY_KEY_PREFIX))) { @@ -198,12 +181,8 @@ private static boolean isAnyPartitionProjectionPropertyUsed(ConnectorTableMetada .anyMatch(propertyKey -> propertyKey.startsWith(PROPERTY_KEY_PREFIX)); } - Optional getPartitionProjectionFromTable(Table table) + static Optional getPartitionProjectionFromTable(Table table, TypeManager typeManager) { - if (!partitionProjectionEnabled) { - return Optional.empty(); - } - Map tableProperties = table.getParameters(); if (parseBoolean(tableProperties.get(METASTORE_PROPERTY_PROJECTION_IGNORE)) || !parseBoolean(tableProperties.get(METASTORE_PROPERTY_PROJECTION_ENABLED))) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveUtil.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveUtil.java index 8d370af19d55..9c7fcdecf126 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveUtil.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveUtil.java @@ -28,7 +28,6 @@ import io.trino.plugin.hive.HivePartitionKey; import io.trino.plugin.hive.HiveTimestampPrecision; import io.trino.plugin.hive.HiveType; -import io.trino.plugin.hive.aws.athena.PartitionProjectionService; import io.trino.plugin.hive.metastore.Column; import io.trino.plugin.hive.metastore.SortingColumn; import io.trino.plugin.hive.metastore.Table; @@ -96,6 +95,7 @@ import static io.trino.plugin.hive.HiveSessionProperties.getTimestampPrecision; import static io.trino.plugin.hive.HiveTableProperties.ORC_BLOOM_FILTER_FPP; import static io.trino.plugin.hive.HiveType.toHiveTypes; +import static io.trino.plugin.hive.aws.athena.PartitionProjectionService.getPartitionProjectionTrinoColumnProperties; import static io.trino.plugin.hive.metastore.SortingColumn.Order.ASCENDING; import static io.trino.plugin.hive.metastore.SortingColumn.Order.DESCENDING; import static io.trino.plugin.hive.util.HiveBucketing.isSupportedBucketing; @@ -864,7 +864,7 @@ public static Function columnMetadataGetter(Ta .setComment(handle.isHidden() ? Optional.empty() : columnComment.get(handle.getName())) .setExtraInfo(Optional.ofNullable(columnExtraInfo(handle.isPartitionKey()))) .setHidden(handle.isHidden()) - .setProperties(PartitionProjectionService.getPartitionProjectionTrinoColumnProperties(table, handle.getName())) + .setProperties(getPartitionProjectionTrinoColumnProperties(table, handle.getName())) .build(); } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java index ed31db237ff0..2252593ef5b8 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java @@ -35,7 +35,6 @@ import io.trino.plugin.base.CatalogName; import io.trino.plugin.base.metrics.LongCount; import io.trino.plugin.hive.LocationService.WriteInfo; -import io.trino.plugin.hive.aws.athena.PartitionProjectionService; import io.trino.plugin.hive.fs.DirectoryLister; import io.trino.plugin.hive.fs.RemoteIterator; import io.trino.plugin.hive.fs.TransactionScopeCachingDirectoryListerFactory; @@ -126,7 +125,6 @@ import io.trino.spi.type.SqlTimestamp; import io.trino.spi.type.SqlTimestampWithTimeZone; import io.trino.spi.type.SqlVarbinary; -import io.trino.spi.type.TestingTypeManager; import io.trino.spi.type.Type; import io.trino.spi.type.TypeId; import io.trino.spi.type.TypeOperators; @@ -895,7 +893,7 @@ public Optional getMaterializedView(Connect SqlStandardAccessControlMetadata::new, countingDirectoryLister, new TransactionScopeCachingDirectoryListerFactory(hiveConfig), - new PartitionProjectionService(hiveConfig, new TestingTypeManager()), + false, true, HiveTimestampPrecision.DEFAULT_PRECISION); transactionManager = new HiveTransactionManager(metadataFactory); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java index 623622630f65..2ead1071c9fe 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java @@ -34,7 +34,6 @@ import io.trino.operator.GroupByHashPageIndexerFactory; import io.trino.plugin.base.CatalogName; import io.trino.plugin.hive.AbstractTestHive.Transaction; -import io.trino.plugin.hive.aws.athena.PartitionProjectionService; import io.trino.plugin.hive.fs.FileSystemDirectoryLister; import io.trino.plugin.hive.fs.HiveFileIterator; import io.trino.plugin.hive.fs.TransactionScopeCachingDirectoryListerFactory; @@ -70,7 +69,6 @@ import io.trino.spi.connector.TableNotFoundException; import io.trino.spi.predicate.TupleDomain; import io.trino.spi.security.ConnectorIdentity; -import io.trino.spi.type.TestingTypeManager; import io.trino.spi.type.TypeOperators; import io.trino.sql.gen.JoinCompiler; import io.trino.testing.MaterializedResult; @@ -242,7 +240,6 @@ protected void setup(String host, int port, String databaseName, HdfsConfigurati SqlStandardAccessControlMetadata::new, new FileSystemDirectoryLister(), new TransactionScopeCachingDirectoryListerFactory(config), - new PartitionProjectionService(config, new TestingTypeManager()), true); transactionManager = new HiveTransactionManager(metadataFactory); splitManager = new HiveSplitManager( From 6ca5d924c3d9a3b7a86fb08a8d88dcc82446dd3f Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Fri, 17 Nov 2023 09:35:22 -0800 Subject: [PATCH 327/587] Move PartitionProjection utilities into PartitionProjectionProperties --- .../io/trino/plugin/hive/HiveMetadata.java | 6 +- ...PartitionProjectionMetastoreDecorator.java | 2 +- .../athena/PartitionProjectionProperties.java | 308 ++++++++++++++++-- .../athena/PartitionProjectionService.java | 306 ----------------- .../io/trino/plugin/hive/util/HiveUtil.java | 2 +- .../plugin/hive/TestHive3OnDataLake.java | 6 +- 6 files changed, 290 insertions(+), 340 deletions(-) delete mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java index ba993d7217ec..c6692dac5c1b 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java @@ -257,9 +257,9 @@ import static io.trino.plugin.hive.ViewReaderUtil.isTrinoView; import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; import static io.trino.plugin.hive.acid.AcidTransaction.forCreateTable; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionService.arePartitionProjectionPropertiesSet; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionService.getPartitionProjectionHiveTableProperties; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionService.getPartitionProjectionTrinoTableProperties; +import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.arePartitionProjectionPropertiesSet; +import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getPartitionProjectionHiveTableProperties; +import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getPartitionProjectionTrinoTableProperties; import static io.trino.plugin.hive.metastore.MetastoreUtil.buildInitialPrivilegeSet; import static io.trino.plugin.hive.metastore.MetastoreUtil.getHiveSchema; import static io.trino.plugin.hive.metastore.MetastoreUtil.getProtectMode; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionMetastoreDecorator.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionMetastoreDecorator.java index 15cee54d6413..e4cbbc1b34e2 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionMetastoreDecorator.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionMetastoreDecorator.java @@ -30,7 +30,7 @@ import java.util.Optional; import static io.trino.plugin.hive.HiveErrorCode.HIVE_TABLE_DROPPED_DURING_QUERY; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionService.getPartitionProjectionFromTable; +import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getPartitionProjectionFromTable; import static java.util.Objects.requireNonNull; public class PartitionProjectionMetastoreDecorator diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionProperties.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionProperties.java index 75834acd351c..2dde57012900 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionProperties.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionProperties.java @@ -13,47 +13,305 @@ */ package io.trino.plugin.hive.aws.athena; +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import io.trino.plugin.hive.metastore.Column; +import io.trino.plugin.hive.metastore.Table; +import io.trino.spi.connector.ColumnMetadata; +import io.trino.spi.connector.ConnectorTableMetadata; +import io.trino.spi.type.Type; +import io.trino.spi.type.TypeManager; + +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.function.Function; +import java.util.stream.Collectors; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static io.trino.plugin.hive.HiveTableProperties.getPartitionedBy; +import static io.trino.plugin.hive.HiveTimestampPrecision.DEFAULT_PRECISION; +import static java.lang.Boolean.parseBoolean; import static java.lang.String.format; +import static java.util.Locale.ROOT; public final class PartitionProjectionProperties { - static final String COLUMN_PROJECTION_TYPE_SUFFIX = "type"; - static final String COLUMN_PROJECTION_VALUES_SUFFIX = "values"; - static final String COLUMN_PROJECTION_RANGE_SUFFIX = "range"; - static final String COLUMN_PROJECTION_INTERVAL_SUFFIX = "interval"; - static final String COLUMN_PROJECTION_DIGITS_SUFFIX = "digits"; - static final String COLUMN_PROJECTION_FORMAT_SUFFIX = "format"; + private static final String COLUMN_PROJECTION_TYPE_SUFFIX = "type"; + private static final String COLUMN_PROJECTION_VALUES_SUFFIX = "values"; + private static final String COLUMN_PROJECTION_RANGE_SUFFIX = "range"; + private static final String COLUMN_PROJECTION_INTERVAL_SUFFIX = "interval"; + private static final String COLUMN_PROJECTION_DIGITS_SUFFIX = "digits"; + private static final String COLUMN_PROJECTION_FORMAT_SUFFIX = "format"; + private static final String METASTORE_PROPERTY_PROJECTION_INTERVAL_UNIT_SUFFIX = "interval.unit"; + private static final String METASTORE_PROPERTY_PROJECTION_ENABLED = "projection.enabled"; + private static final String METASTORE_PROPERTY_PROJECTION_LOCATION_TEMPLATE = "storage.location.template"; + private static final String METASTORE_PROPERTY_PROJECTION_IGNORE = "trino.partition_projection.ignore"; + private static final String PROPERTY_KEY_PREFIX = "partition_projection_"; - static final String METASTORE_PROPERTY_PROJECTION_INTERVAL_UNIT_SUFFIX = "interval.unit"; + public static final String COLUMN_PROJECTION_FORMAT = PROPERTY_KEY_PREFIX + COLUMN_PROJECTION_FORMAT_SUFFIX; + public static final String COLUMN_PROJECTION_DIGITS = PROPERTY_KEY_PREFIX + COLUMN_PROJECTION_DIGITS_SUFFIX; + public static final String COLUMN_PROJECTION_INTERVAL_UNIT = PROPERTY_KEY_PREFIX + "interval_unit"; + public static final String COLUMN_PROJECTION_INTERVAL = PROPERTY_KEY_PREFIX + COLUMN_PROJECTION_INTERVAL_SUFFIX; + public static final String COLUMN_PROJECTION_RANGE = PROPERTY_KEY_PREFIX + COLUMN_PROJECTION_RANGE_SUFFIX; + public static final String COLUMN_PROJECTION_VALUES = PROPERTY_KEY_PREFIX + COLUMN_PROJECTION_VALUES_SUFFIX; + public static final String COLUMN_PROJECTION_TYPE = PROPERTY_KEY_PREFIX + COLUMN_PROJECTION_TYPE_SUFFIX; + public static final String PARTITION_PROJECTION_IGNORE = PROPERTY_KEY_PREFIX + "ignore"; + public static final String PARTITION_PROJECTION_LOCATION_TEMPLATE = PROPERTY_KEY_PREFIX + "location_template"; + public static final String PARTITION_PROJECTION_ENABLED = PROPERTY_KEY_PREFIX + "enabled"; - static final String METASTORE_PROPERTY_PROJECTION_ENABLED = "projection.enabled"; - static final String METASTORE_PROPERTY_PROJECTION_LOCATION_TEMPLATE = "storage.location.template"; - static final String METASTORE_PROPERTY_PROJECTION_IGNORE = "trino.partition_projection.ignore"; + private PartitionProjectionProperties() {} - static final String PROPERTY_KEY_PREFIX = "partition_projection_"; + public static Map getPartitionProjectionTrinoTableProperties(Table table) + { + Map metastoreTableProperties = table.getParameters(); + ImmutableMap.Builder trinoTablePropertiesBuilder = ImmutableMap.builder(); - public static final String PARTITION_PROJECTION_ENABLED = PROPERTY_KEY_PREFIX + "enabled"; - public static final String PARTITION_PROJECTION_LOCATION_TEMPLATE = PROPERTY_KEY_PREFIX + "location_template"; - public static final String PARTITION_PROJECTION_IGNORE = PROPERTY_KEY_PREFIX + "ignore"; + String ignore = metastoreTableProperties.get(METASTORE_PROPERTY_PROJECTION_IGNORE); + if (ignore != null) { + trinoTablePropertiesBuilder.put(PARTITION_PROJECTION_IGNORE, Boolean.valueOf(ignore)); + } - public static final String COLUMN_PROJECTION_TYPE = PROPERTY_KEY_PREFIX + COLUMN_PROJECTION_TYPE_SUFFIX; - public static final String COLUMN_PROJECTION_VALUES = PROPERTY_KEY_PREFIX + COLUMN_PROJECTION_VALUES_SUFFIX; - public static final String COLUMN_PROJECTION_RANGE = PROPERTY_KEY_PREFIX + COLUMN_PROJECTION_RANGE_SUFFIX; - public static final String COLUMN_PROJECTION_INTERVAL = PROPERTY_KEY_PREFIX + COLUMN_PROJECTION_INTERVAL_SUFFIX; - public static final String COLUMN_PROJECTION_INTERVAL_UNIT = PROPERTY_KEY_PREFIX + "interval_unit"; - public static final String COLUMN_PROJECTION_DIGITS = PROPERTY_KEY_PREFIX + COLUMN_PROJECTION_DIGITS_SUFFIX; - public static final String COLUMN_PROJECTION_FORMAT = PROPERTY_KEY_PREFIX + COLUMN_PROJECTION_FORMAT_SUFFIX; + String enabled = metastoreTableProperties.get(METASTORE_PROPERTY_PROJECTION_ENABLED); + if (enabled != null) { + trinoTablePropertiesBuilder.put(PARTITION_PROJECTION_ENABLED, Boolean.valueOf(enabled)); + } + + String locationTemplate = metastoreTableProperties.get(METASTORE_PROPERTY_PROJECTION_LOCATION_TEMPLATE); + if (locationTemplate != null) { + trinoTablePropertiesBuilder.put(PARTITION_PROJECTION_LOCATION_TEMPLATE, locationTemplate); + } + + return trinoTablePropertiesBuilder.buildOrThrow(); + } + + public static Map getPartitionProjectionTrinoColumnProperties(Table table, String columnName) + { + Map metastoreTableProperties = table.getParameters(); + return rewriteColumnProjectionProperties(metastoreTableProperties, columnName); + } + + public static Map getPartitionProjectionHiveTableProperties(ConnectorTableMetadata tableMetadata) + { + ImmutableMap.Builder metastoreTablePropertiesBuilder = ImmutableMap.builder(); + // Handle Table Properties + Map trinoTableProperties = tableMetadata.getProperties(); + + Object ignore = trinoTableProperties.get(PARTITION_PROJECTION_IGNORE); + if (ignore != null) { + metastoreTablePropertiesBuilder.put(METASTORE_PROPERTY_PROJECTION_IGNORE, ignore.toString().toLowerCase(ROOT)); + } + + Object enabled = trinoTableProperties.get(PARTITION_PROJECTION_ENABLED); + if (enabled != null) { + metastoreTablePropertiesBuilder.put(METASTORE_PROPERTY_PROJECTION_ENABLED, enabled.toString().toLowerCase(ROOT)); + } + + Object locationTemplate = trinoTableProperties.get(PARTITION_PROJECTION_LOCATION_TEMPLATE); + if (locationTemplate != null) { + metastoreTablePropertiesBuilder.put(METASTORE_PROPERTY_PROJECTION_LOCATION_TEMPLATE, locationTemplate.toString()); + } + + // Handle Column Properties + tableMetadata.getColumns().stream() + .filter(columnMetadata -> !columnMetadata.getProperties().isEmpty()) + .forEach(columnMetadata -> { + Map columnProperties = columnMetadata.getProperties(); + String columnName = columnMetadata.getName(); + + if (columnProperties.get(COLUMN_PROJECTION_TYPE) instanceof ProjectionType projectionType) { + metastoreTablePropertiesBuilder.put(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_TYPE_SUFFIX), projectionType.name().toLowerCase(ROOT)); + } + + if (columnProperties.get(COLUMN_PROJECTION_VALUES) instanceof List values) { + metastoreTablePropertiesBuilder.put(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_VALUES_SUFFIX), Joiner.on(",").join(values)); + } + + if (columnProperties.get(COLUMN_PROJECTION_RANGE) instanceof List range) { + metastoreTablePropertiesBuilder.put(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_RANGE_SUFFIX), Joiner.on(",").join(range)); + } + + if (columnProperties.get(COLUMN_PROJECTION_INTERVAL) instanceof Integer interval) { + metastoreTablePropertiesBuilder.put(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_INTERVAL_SUFFIX), interval.toString()); + } + + if (columnProperties.get(COLUMN_PROJECTION_INTERVAL_UNIT) instanceof ChronoUnit intervalUnit) { + metastoreTablePropertiesBuilder.put(getMetastoreProjectionPropertyKey(columnName, METASTORE_PROPERTY_PROJECTION_INTERVAL_UNIT_SUFFIX), intervalUnit.name().toLowerCase(ROOT)); + } - static String getMetastoreProjectionPropertyKey(String columnName, String propertyKeySuffix) + if (columnProperties.get(COLUMN_PROJECTION_DIGITS) instanceof Integer digits) { + metastoreTablePropertiesBuilder.put(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_DIGITS_SUFFIX), digits.toString()); + } + + if (columnProperties.get(COLUMN_PROJECTION_FORMAT) instanceof String format) { + metastoreTablePropertiesBuilder.put(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_FORMAT_SUFFIX), format); + } + }); + + // We initialize partition projection to validate properties. + Map metastoreTableProperties = metastoreTablePropertiesBuilder.buildOrThrow(); + Set partitionColumnNames = ImmutableSet.copyOf(getPartitionedBy(tableMetadata.getProperties())); + createPartitionProjection( + tableMetadata.getColumns() + .stream() + .map(ColumnMetadata::getName) + .filter(name -> !partitionColumnNames.contains(name)) + .collect(toImmutableList()), + tableMetadata.getColumns().stream() + .filter(columnMetadata -> partitionColumnNames.contains(columnMetadata.getName())) + .collect(toImmutableMap(ColumnMetadata::getName, ColumnMetadata::getType)), + metastoreTableProperties); + + return metastoreTableProperties; + } + + public static boolean arePartitionProjectionPropertiesSet(ConnectorTableMetadata tableMetadata) + { + if (tableMetadata.getProperties().keySet().stream() + .anyMatch(propertyKey -> propertyKey.startsWith(PROPERTY_KEY_PREFIX))) { + return true; + } + return tableMetadata.getColumns().stream() + .map(columnMetadata -> columnMetadata.getProperties().keySet()) + .flatMap(Set::stream) + .anyMatch(propertyKey -> propertyKey.startsWith(PROPERTY_KEY_PREFIX)); + } + + static Optional getPartitionProjectionFromTable(Table table, TypeManager typeManager) + { + Map tableProperties = table.getParameters(); + if (parseBoolean(tableProperties.get(METASTORE_PROPERTY_PROJECTION_IGNORE)) || + !parseBoolean(tableProperties.get(METASTORE_PROPERTY_PROJECTION_ENABLED))) { + return Optional.empty(); + } + + Set partitionColumnNames = table.getPartitionColumns().stream().map(Column::getName).collect(Collectors.toSet()); + return Optional.of(createPartitionProjection( + table.getDataColumns().stream() + .map(Column::getName) + .filter(partitionColumnNames::contains) + .collect(toImmutableList()), + table.getPartitionColumns().stream() + .collect(toImmutableMap(Column::getName, column -> column.getType().getType(typeManager, DEFAULT_PRECISION))), + tableProperties)); + } + + private static PartitionProjection createPartitionProjection(List dataColumns, Map partitionColumns, Map tableProperties) + { + // This method is used during table creation to validate the properties. The validation is performed even if the projection is disabled. + boolean enabled = parseBoolean(tableProperties.get(METASTORE_PROPERTY_PROJECTION_ENABLED)); + + if (!tableProperties.containsKey(METASTORE_PROPERTY_PROJECTION_ENABLED) && + partitionColumns.keySet().stream().anyMatch(columnName -> !rewriteColumnProjectionProperties(tableProperties, columnName).isEmpty())) { + throw new InvalidProjectionException("Columns partition projection properties cannot be set when '%s' is not set".formatted(PARTITION_PROJECTION_ENABLED)); + } + + if (enabled && partitionColumns.isEmpty()) { + throw new InvalidProjectionException("Partition projection cannot be enabled on a table that is not partitioned"); + } + + for (String columnName : dataColumns) { + if (!rewriteColumnProjectionProperties(tableProperties, columnName).isEmpty()) { + throw new InvalidProjectionException("Partition projection cannot be defined for non-partition column: '" + columnName + "'"); + } + } + + Map columnProjections = new HashMap<>(); + partitionColumns.forEach((columnName, type) -> { + Map columnProperties = rewriteColumnProjectionProperties(tableProperties, columnName); + if (enabled) { + columnProjections.put(columnName, parseColumnProjection(columnName, type, columnProperties)); + } + }); + + Optional storageLocationTemplate = Optional.ofNullable(tableProperties.get(METASTORE_PROPERTY_PROJECTION_LOCATION_TEMPLATE)); + storageLocationTemplate.ifPresent(locationTemplate -> { + for (String columnName : partitionColumns.keySet()) { + if (!locationTemplate.contains("${" + columnName + "}")) { + throw new InvalidProjectionException(format("Partition projection location template: %s is missing partition column: '%s' placeholder", locationTemplate, columnName)); + } + } + }); + + return new PartitionProjection(enabled, storageLocationTemplate, columnProjections); + } + + private static Map rewriteColumnProjectionProperties(Map metastoreTableProperties, String columnName) + { + ImmutableMap.Builder trinoTablePropertiesBuilder = ImmutableMap.builder(); + + String type = metastoreTableProperties.get(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_TYPE_SUFFIX)); + if (type != null) { + trinoTablePropertiesBuilder.put(COLUMN_PROJECTION_TYPE, ProjectionType.valueOf(type.toUpperCase(ROOT))); + } + + String values = metastoreTableProperties.get(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_VALUES_SUFFIX)); + if (values != null) { + trinoTablePropertiesBuilder.put(COLUMN_PROJECTION_VALUES, splitCommaSeparatedString(values)); + } + + String range = metastoreTableProperties.get(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_RANGE_SUFFIX)); + if (range != null) { + trinoTablePropertiesBuilder.put(COLUMN_PROJECTION_RANGE, splitCommaSeparatedString(range)); + } + + String interval = metastoreTableProperties.get(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_INTERVAL_SUFFIX)); + if (interval != null) { + trinoTablePropertiesBuilder.put(COLUMN_PROJECTION_INTERVAL, Integer.valueOf(interval)); + } + + String intervalUnit = metastoreTableProperties.get(getMetastoreProjectionPropertyKey(columnName, METASTORE_PROPERTY_PROJECTION_INTERVAL_UNIT_SUFFIX)); + if (intervalUnit != null) { + trinoTablePropertiesBuilder.put(COLUMN_PROJECTION_INTERVAL_UNIT, ChronoUnit.valueOf(intervalUnit.toUpperCase(ROOT))); + } + + String digits = metastoreTableProperties.get(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_DIGITS_SUFFIX)); + if (digits != null) { + trinoTablePropertiesBuilder.put(COLUMN_PROJECTION_DIGITS, Integer.valueOf(digits)); + } + + String format = metastoreTableProperties.get(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_FORMAT_SUFFIX)); + if (format != null) { + trinoTablePropertiesBuilder.put(COLUMN_PROJECTION_FORMAT, format); + } + + return trinoTablePropertiesBuilder.buildOrThrow(); + } + + private static Projection parseColumnProjection(String columnName, Type columnType, Map columnProperties) + { + ProjectionType projectionType = (ProjectionType) columnProperties.get(COLUMN_PROJECTION_TYPE); + if (projectionType == null) { + throw new InvalidProjectionException(columnName, "Projection type property missing"); + } + return switch (projectionType) { + case ENUM -> new EnumProjection(columnName, columnType, columnProperties); + case INTEGER -> new IntegerProjection(columnName, columnType, columnProperties); + case DATE -> new DateProjection(columnName, columnType, columnProperties); + case INJECTED -> new InjectedProjection(columnName, columnType); + }; + } + + private static List splitCommaSeparatedString(String value) + { + return Splitter.on(',') + .trimResults() + .omitEmptyStrings() + .splitToList(value); + } + + private static String getMetastoreProjectionPropertyKey(String columnName, String propertyKeySuffix) { return "projection" + "." + columnName + "." + propertyKeySuffix; } - public static T getProjectionPropertyRequiredValue( + static T getProjectionPropertyRequiredValue( String columnName, Map columnProjectionProperties, String propertyKey, @@ -63,7 +321,7 @@ public static T getProjectionPropertyRequiredValue( .orElseThrow(() -> new InvalidProjectionException(columnName, format("Missing required property: '%s'", propertyKey))); } - public static Optional getProjectionPropertyValue( + static Optional getProjectionPropertyValue( Map columnProjectionProperties, String propertyKey, Function decoder) @@ -72,6 +330,4 @@ public static Optional getProjectionPropertyValue( columnProjectionProperties.get(propertyKey)) .map(decoder); } - - private PartitionProjectionProperties() {} } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java deleted file mode 100644 index e3965ff4741b..000000000000 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionService.java +++ /dev/null @@ -1,306 +0,0 @@ -/* - * 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 io.trino.plugin.hive.aws.athena; - -import com.google.common.base.Joiner; -import com.google.common.base.Splitter; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import io.trino.plugin.hive.metastore.Column; -import io.trino.plugin.hive.metastore.Table; -import io.trino.spi.connector.ColumnMetadata; -import io.trino.spi.connector.ConnectorTableMetadata; -import io.trino.spi.type.Type; -import io.trino.spi.type.TypeManager; - -import java.time.temporal.ChronoUnit; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.collect.ImmutableMap.toImmutableMap; -import static io.trino.plugin.hive.HiveTableProperties.getPartitionedBy; -import static io.trino.plugin.hive.HiveTimestampPrecision.DEFAULT_PRECISION; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_DIGITS; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_DIGITS_SUFFIX; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_FORMAT; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_FORMAT_SUFFIX; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_INTERVAL; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_INTERVAL_SUFFIX; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_INTERVAL_UNIT; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_RANGE; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_RANGE_SUFFIX; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_TYPE; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_TYPE_SUFFIX; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_VALUES; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_VALUES_SUFFIX; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.METASTORE_PROPERTY_PROJECTION_ENABLED; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.METASTORE_PROPERTY_PROJECTION_IGNORE; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.METASTORE_PROPERTY_PROJECTION_INTERVAL_UNIT_SUFFIX; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.METASTORE_PROPERTY_PROJECTION_LOCATION_TEMPLATE; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.PARTITION_PROJECTION_ENABLED; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.PARTITION_PROJECTION_IGNORE; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.PARTITION_PROJECTION_LOCATION_TEMPLATE; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.PROPERTY_KEY_PREFIX; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getMetastoreProjectionPropertyKey; -import static java.lang.Boolean.parseBoolean; -import static java.lang.String.format; -import static java.util.Locale.ROOT; - -public final class PartitionProjectionService -{ - private PartitionProjectionService() {} - - public static Map getPartitionProjectionTrinoTableProperties(Table table) - { - Map metastoreTableProperties = table.getParameters(); - ImmutableMap.Builder trinoTablePropertiesBuilder = ImmutableMap.builder(); - - String ignore = metastoreTableProperties.get(METASTORE_PROPERTY_PROJECTION_IGNORE); - if (ignore != null) { - trinoTablePropertiesBuilder.put(PARTITION_PROJECTION_IGNORE, Boolean.valueOf(ignore)); - } - - String enabled = metastoreTableProperties.get(METASTORE_PROPERTY_PROJECTION_ENABLED); - if (enabled != null) { - trinoTablePropertiesBuilder.put(PARTITION_PROJECTION_ENABLED, Boolean.valueOf(enabled)); - } - - String locationTemplate = metastoreTableProperties.get(METASTORE_PROPERTY_PROJECTION_LOCATION_TEMPLATE); - if (locationTemplate != null) { - trinoTablePropertiesBuilder.put(PARTITION_PROJECTION_LOCATION_TEMPLATE, locationTemplate); - } - - return trinoTablePropertiesBuilder.buildOrThrow(); - } - - public static Map getPartitionProjectionTrinoColumnProperties(Table table, String columnName) - { - Map metastoreTableProperties = table.getParameters(); - return rewriteColumnProjectionProperties(metastoreTableProperties, columnName); - } - - public static Map getPartitionProjectionHiveTableProperties(ConnectorTableMetadata tableMetadata) - { - ImmutableMap.Builder metastoreTablePropertiesBuilder = ImmutableMap.builder(); - // Handle Table Properties - Map trinoTableProperties = tableMetadata.getProperties(); - - Object ignore = trinoTableProperties.get(PARTITION_PROJECTION_IGNORE); - if (ignore != null) { - metastoreTablePropertiesBuilder.put(METASTORE_PROPERTY_PROJECTION_IGNORE, ignore.toString().toLowerCase(ROOT)); - } - - Object enabled = trinoTableProperties.get(PARTITION_PROJECTION_ENABLED); - if (enabled != null) { - metastoreTablePropertiesBuilder.put(METASTORE_PROPERTY_PROJECTION_ENABLED, enabled.toString().toLowerCase(ROOT)); - } - - Object locationTemplate = trinoTableProperties.get(PARTITION_PROJECTION_LOCATION_TEMPLATE); - if (locationTemplate != null) { - metastoreTablePropertiesBuilder.put(METASTORE_PROPERTY_PROJECTION_LOCATION_TEMPLATE, locationTemplate.toString()); - } - - // Handle Column Properties - tableMetadata.getColumns().stream() - .filter(columnMetadata -> !columnMetadata.getProperties().isEmpty()) - .forEach(columnMetadata -> { - Map columnProperties = columnMetadata.getProperties(); - String columnName = columnMetadata.getName(); - - if (columnProperties.get(COLUMN_PROJECTION_TYPE) instanceof ProjectionType projectionType) { - metastoreTablePropertiesBuilder.put(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_TYPE_SUFFIX), projectionType.name().toLowerCase(ROOT)); - } - - if (columnProperties.get(COLUMN_PROJECTION_VALUES) instanceof List values) { - metastoreTablePropertiesBuilder.put(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_VALUES_SUFFIX), Joiner.on(",").join(values)); - } - - if (columnProperties.get(COLUMN_PROJECTION_RANGE) instanceof List range) { - metastoreTablePropertiesBuilder.put(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_RANGE_SUFFIX), Joiner.on(",").join(range)); - } - - if (columnProperties.get(COLUMN_PROJECTION_INTERVAL) instanceof Integer interval) { - metastoreTablePropertiesBuilder.put(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_INTERVAL_SUFFIX), interval.toString()); - } - - if (columnProperties.get(COLUMN_PROJECTION_INTERVAL_UNIT) instanceof ChronoUnit intervalUnit) { - metastoreTablePropertiesBuilder.put(getMetastoreProjectionPropertyKey(columnName, METASTORE_PROPERTY_PROJECTION_INTERVAL_UNIT_SUFFIX), intervalUnit.name().toLowerCase(ROOT)); - } - - if (columnProperties.get(COLUMN_PROJECTION_DIGITS) instanceof Integer digits) { - metastoreTablePropertiesBuilder.put(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_DIGITS_SUFFIX), digits.toString()); - } - - if (columnProperties.get(COLUMN_PROJECTION_FORMAT) instanceof String format) { - metastoreTablePropertiesBuilder.put(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_FORMAT_SUFFIX), format); - } - }); - - // We initialize partition projection to validate properties. - Map metastoreTableProperties = metastoreTablePropertiesBuilder.buildOrThrow(); - Set partitionColumnNames = ImmutableSet.copyOf(getPartitionedBy(tableMetadata.getProperties())); - createPartitionProjection( - tableMetadata.getColumns() - .stream() - .map(ColumnMetadata::getName) - .filter(name -> !partitionColumnNames.contains(name)) - .collect(toImmutableList()), - tableMetadata.getColumns().stream() - .filter(columnMetadata -> partitionColumnNames.contains(columnMetadata.getName())) - .collect(toImmutableMap(ColumnMetadata::getName, ColumnMetadata::getType)), - metastoreTableProperties); - - return metastoreTableProperties; - } - - public static boolean arePartitionProjectionPropertiesSet(ConnectorTableMetadata tableMetadata) - { - if (tableMetadata.getProperties().keySet().stream() - .anyMatch(propertyKey -> propertyKey.startsWith(PROPERTY_KEY_PREFIX))) { - return true; - } - return tableMetadata.getColumns().stream() - .map(columnMetadata -> columnMetadata.getProperties().keySet()) - .flatMap(Set::stream) - .anyMatch(propertyKey -> propertyKey.startsWith(PROPERTY_KEY_PREFIX)); - } - - static Optional getPartitionProjectionFromTable(Table table, TypeManager typeManager) - { - Map tableProperties = table.getParameters(); - if (parseBoolean(tableProperties.get(METASTORE_PROPERTY_PROJECTION_IGNORE)) || - !parseBoolean(tableProperties.get(METASTORE_PROPERTY_PROJECTION_ENABLED))) { - return Optional.empty(); - } - - Set partitionColumnNames = table.getPartitionColumns().stream().map(Column::getName).collect(Collectors.toSet()); - return Optional.of(createPartitionProjection( - table.getDataColumns().stream() - .map(Column::getName) - .filter(partitionColumnNames::contains) - .collect(toImmutableList()), - table.getPartitionColumns().stream() - .collect(toImmutableMap(Column::getName, column -> column.getType().getType(typeManager, DEFAULT_PRECISION))), - tableProperties)); - } - - private static PartitionProjection createPartitionProjection(List dataColumns, Map partitionColumns, Map tableProperties) - { - // This method is used during table creation to validate the properties. The validation is performed even if the projection is disabled. - boolean enabled = parseBoolean(tableProperties.get(METASTORE_PROPERTY_PROJECTION_ENABLED)); - - if (!tableProperties.containsKey(METASTORE_PROPERTY_PROJECTION_ENABLED) && - partitionColumns.keySet().stream().anyMatch(columnName -> !rewriteColumnProjectionProperties(tableProperties, columnName).isEmpty())) { - throw new InvalidProjectionException("Columns partition projection properties cannot be set when '%s' is not set".formatted(PARTITION_PROJECTION_ENABLED)); - } - - if (enabled && partitionColumns.isEmpty()) { - throw new InvalidProjectionException("Partition projection cannot be enabled on a table that is not partitioned"); - } - - for (String columnName : dataColumns) { - if (!rewriteColumnProjectionProperties(tableProperties, columnName).isEmpty()) { - throw new InvalidProjectionException("Partition projection cannot be defined for non-partition column: '" + columnName + "'"); - } - } - - Map columnProjections = new HashMap<>(); - partitionColumns.forEach((columnName, type) -> { - Map columnProperties = rewriteColumnProjectionProperties(tableProperties, columnName); - if (enabled) { - columnProjections.put(columnName, parseColumnProjection(columnName, type, columnProperties)); - } - }); - - Optional storageLocationTemplate = Optional.ofNullable(tableProperties.get(METASTORE_PROPERTY_PROJECTION_LOCATION_TEMPLATE)); - storageLocationTemplate.ifPresent(locationTemplate -> { - for (String columnName : partitionColumns.keySet()) { - if (!locationTemplate.contains("${" + columnName + "}")) { - throw new InvalidProjectionException(format("Partition projection location template: %s is missing partition column: '%s' placeholder", locationTemplate, columnName)); - } - } - }); - - return new PartitionProjection(enabled, storageLocationTemplate, columnProjections); - } - - private static Map rewriteColumnProjectionProperties(Map metastoreTableProperties, String columnName) - { - ImmutableMap.Builder trinoTablePropertiesBuilder = ImmutableMap.builder(); - - String type = metastoreTableProperties.get(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_TYPE_SUFFIX)); - if (type != null) { - trinoTablePropertiesBuilder.put(COLUMN_PROJECTION_TYPE, ProjectionType.valueOf(type.toUpperCase(ROOT))); - } - - String values = metastoreTableProperties.get(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_VALUES_SUFFIX)); - if (values != null) { - trinoTablePropertiesBuilder.put(COLUMN_PROJECTION_VALUES, splitCommaSeparatedString(values)); - } - - String range = metastoreTableProperties.get(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_RANGE_SUFFIX)); - if (range != null) { - trinoTablePropertiesBuilder.put(COLUMN_PROJECTION_RANGE, splitCommaSeparatedString(range)); - } - - String interval = metastoreTableProperties.get(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_INTERVAL_SUFFIX)); - if (interval != null) { - trinoTablePropertiesBuilder.put(COLUMN_PROJECTION_INTERVAL, Integer.valueOf(interval)); - } - - String intervalUnit = metastoreTableProperties.get(getMetastoreProjectionPropertyKey(columnName, METASTORE_PROPERTY_PROJECTION_INTERVAL_UNIT_SUFFIX)); - if (intervalUnit != null) { - trinoTablePropertiesBuilder.put(COLUMN_PROJECTION_INTERVAL_UNIT, ChronoUnit.valueOf(intervalUnit.toUpperCase(ROOT))); - } - - String digits = metastoreTableProperties.get(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_DIGITS_SUFFIX)); - if (digits != null) { - trinoTablePropertiesBuilder.put(COLUMN_PROJECTION_DIGITS, Integer.valueOf(digits)); - } - - String format = metastoreTableProperties.get(getMetastoreProjectionPropertyKey(columnName, COLUMN_PROJECTION_FORMAT_SUFFIX)); - if (format != null) { - trinoTablePropertiesBuilder.put(COLUMN_PROJECTION_FORMAT, format); - } - - return trinoTablePropertiesBuilder.buildOrThrow(); - } - - private static Projection parseColumnProjection(String columnName, Type columnType, Map columnProperties) - { - ProjectionType projectionType = (ProjectionType) columnProperties.get(COLUMN_PROJECTION_TYPE); - if (projectionType == null) { - throw new InvalidProjectionException(columnName, "Projection type property missing"); - } - return switch (projectionType) { - case ENUM -> new EnumProjection(columnName, columnType, columnProperties); - case INTEGER -> new IntegerProjection(columnName, columnType, columnProperties); - case DATE -> new DateProjection(columnName, columnType, columnProperties); - case INJECTED -> new InjectedProjection(columnName, columnType); - }; - } - - private static List splitCommaSeparatedString(String value) - { - return Splitter.on(',') - .trimResults() - .omitEmptyStrings() - .splitToList(value); - } -} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveUtil.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveUtil.java index 9c7fcdecf126..6c43224d64a4 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveUtil.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveUtil.java @@ -95,7 +95,7 @@ import static io.trino.plugin.hive.HiveSessionProperties.getTimestampPrecision; import static io.trino.plugin.hive.HiveTableProperties.ORC_BLOOM_FILTER_FPP; import static io.trino.plugin.hive.HiveType.toHiveTypes; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionService.getPartitionProjectionTrinoColumnProperties; +import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getPartitionProjectionTrinoColumnProperties; import static io.trino.plugin.hive.metastore.SortingColumn.Order.ASCENDING; import static io.trino.plugin.hive.metastore.SortingColumn.Order.DESCENDING; import static io.trino.plugin.hive.util.HiveBucketing.isSupportedBucketing; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHive3OnDataLake.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHive3OnDataLake.java index 52dc944b3775..9b911653e92c 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHive3OnDataLake.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHive3OnDataLake.java @@ -1421,7 +1421,7 @@ public void testPartitionProjectionInvalidTableProperties() " partition_projection_enabled=true " + ")")) .hasMessage("Column projection for column 'short_name1' failed. Property: 'partition_projection_range' needs to be a list of 2 valid dates formatted as 'yyyy-MM-dd HH' " + - "or '^\\s*NOW\\s*(([+-])\\s*([0-9]+)\\s*(DAY|HOUR|MINUTE|SECOND)S?\\s*)?$' that are sequential. Unparseable date: \"2001-01-01\""); + "or '^\\s*NOW\\s*(([+-])\\s*([0-9]+)\\s*(DAY|HOUR|MINUTE|SECOND)S?\\s*)?$' that are sequential: Unparseable date: \"2001-01-01\""); assertThatThrownBy(() -> getQueryRunner().execute( "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + @@ -1436,7 +1436,7 @@ public void testPartitionProjectionInvalidTableProperties() " partition_projection_enabled=true " + ")")) .hasMessage("Column projection for column 'short_name1' failed. Property: 'partition_projection_range' needs to be a list of 2 valid dates formatted as 'yyyy-MM-dd' " + - "or '^\\s*NOW\\s*(([+-])\\s*([0-9]+)\\s*(DAY|HOUR|MINUTE|SECOND)S?\\s*)?$' that are sequential. Unparseable date: \"NOW*3DAYS\""); + "or '^\\s*NOW\\s*(([+-])\\s*([0-9]+)\\s*(DAY|HOUR|MINUTE|SECOND)S?\\s*)?$' that are sequential: Unparseable date: \"NOW*3DAYS\""); assertThatThrownBy(() -> getQueryRunner().execute( "CREATE TABLE " + getFullyQualifiedTestTableName("nation_" + randomNameSuffix()) + " ( " + @@ -1542,7 +1542,7 @@ public void testPartitionProjectionIgnore() // Expect invalid Partition Projection properties to fail assertThatThrownBy(() -> getQueryRunner().execute("SELECT * FROM " + fullyQualifiedTestTableName)) .hasMessage("Column projection for column 'date_time' failed. Property: 'partition_projection_range' needs to be a list of 2 valid dates formatted as 'yyyy-MM-dd HH' " + - "or '^\\s*NOW\\s*(([+-])\\s*([0-9]+)\\s*(DAY|HOUR|MINUTE|SECOND)S?\\s*)?$' that are sequential. Unparseable date: \"2001-01-01\""); + "or '^\\s*NOW\\s*(([+-])\\s*([0-9]+)\\s*(DAY|HOUR|MINUTE|SECOND)S?\\s*)?$' that are sequential: Unparseable date: \"2001-01-01\""); // Append kill switch table property to ignore Partition Projection properties hiveMinioDataLake.getHiveHadoop().runOnHive( From 24523fb98a460e350e1061414838cf33114768f1 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Fri, 17 Nov 2023 10:10:11 -0800 Subject: [PATCH 328/587] Remove enabled field from PartitionProjection --- .../plugin/hive/aws/athena/PartitionProjection.java | 10 +--------- .../athena/PartitionProjectionMetastoreDecorator.java | 3 +-- .../aws/athena/PartitionProjectionProperties.java | 11 +++++++---- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjection.java index eefdfd3e33be..e4156bbe90fd 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjection.java @@ -43,23 +43,15 @@ final class PartitionProjection { private static final Pattern PROJECTION_LOCATION_TEMPLATE_PLACEHOLDER_PATTERN = Pattern.compile("(\\$\\{[^}]+\\})"); - private final boolean enabled; - private final Optional storageLocationTemplate; private final Map columnProjections; - public PartitionProjection(boolean enabled, Optional storageLocationTemplate, Map columnProjections) + public PartitionProjection(Optional storageLocationTemplate, Map columnProjections) { - this.enabled = enabled; this.storageLocationTemplate = requireNonNull(storageLocationTemplate, "storageLocationTemplate is null"); this.columnProjections = ImmutableMap.copyOf(requireNonNull(columnProjections, "columnProjections is null")); } - public boolean isEnabled() - { - return enabled; - } - public Optional> getProjectedPartitionNamesByFilter(List columnNames, TupleDomain partitionKeysFilter) { if (partitionKeysFilter.isNone()) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionMetastoreDecorator.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionMetastoreDecorator.java index e4cbbc1b34e2..bd7c3e46ad8d 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionMetastoreDecorator.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionMetastoreDecorator.java @@ -100,8 +100,7 @@ private Optional getPartitionProjection(Table table) if (!partitionProjectionEnabled) { return Optional.empty(); } - return getPartitionProjectionFromTable(table, typeManager) - .filter(PartitionProjection::isEnabled); + return getPartitionProjectionFromTable(table, typeManager); } } } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionProperties.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionProperties.java index 2dde57012900..32bd46250b7b 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionProperties.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionProperties.java @@ -192,17 +192,17 @@ static Optional getPartitionProjectionFromTable(Table table } Set partitionColumnNames = table.getPartitionColumns().stream().map(Column::getName).collect(Collectors.toSet()); - return Optional.of(createPartitionProjection( + return createPartitionProjection( table.getDataColumns().stream() .map(Column::getName) .filter(partitionColumnNames::contains) .collect(toImmutableList()), table.getPartitionColumns().stream() .collect(toImmutableMap(Column::getName, column -> column.getType().getType(typeManager, DEFAULT_PRECISION))), - tableProperties)); + tableProperties); } - private static PartitionProjection createPartitionProjection(List dataColumns, Map partitionColumns, Map tableProperties) + private static Optional createPartitionProjection(List dataColumns, Map partitionColumns, Map tableProperties) { // This method is used during table creation to validate the properties. The validation is performed even if the projection is disabled. boolean enabled = parseBoolean(tableProperties.get(METASTORE_PROPERTY_PROJECTION_ENABLED)); @@ -239,7 +239,10 @@ private static PartitionProjection createPartitionProjection(List dataCo } }); - return new PartitionProjection(enabled, storageLocationTemplate, columnProjections); + if (!enabled) { + return Optional.empty(); + } + return Optional.of(new PartitionProjection(storageLocationTemplate, columnProjections)); } private static Map rewriteColumnProjectionProperties(Map metastoreTableProperties, String columnName) From febb70b61df2fddfc2fee00844ebc3965a964731 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Fri, 17 Nov 2023 10:52:36 -0800 Subject: [PATCH 329/587] Move partition projection property processing to HiveMetastoreClosure --- .../plugin/hive/HiveMetadataFactory.java | 2 +- .../plugin/hive/HiveMetastoreClosure.java | 35 +++++- .../plugin/hive/HivePageSinkProvider.java | 2 +- .../hive/InternalHiveConnectorFactory.java | 2 - .../hive/aws/athena/PartitionProjection.java | 2 +- ...PartitionProjectionMetastoreDecorator.java | 106 ------------------ .../aws/athena/PartitionProjectionModule.java | 34 ------ .../athena/PartitionProjectionProperties.java | 2 +- .../metastore/HiveMetastoreDecorator.java | 1 - .../trino/plugin/hive/AbstractTestHive.java | 14 +-- .../TestSemiTransactionalHiveMetastore.java | 5 +- .../cache/TestCachingHiveMetastore.java | 3 +- .../metastore/glue/TestHiveGlueMetastore.java | 7 +- .../TestHiveMetastoreAccessOperations.java | 14 +-- 14 files changed, 59 insertions(+), 170 deletions(-) delete mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionMetastoreDecorator.java delete mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionModule.java diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadataFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadataFactory.java index ec7bdaea8f72..92b66ea251f3 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadataFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadataFactory.java @@ -227,7 +227,7 @@ public HiveMetadataFactory( public TransactionalMetadata create(ConnectorIdentity identity, boolean autoCommit) { CachingHiveMetastore cachingHiveMetastore = createPerTransactionCache(metastoreFactory.createMetastore(Optional.of(identity)), perTransactionCacheMaximumSize); - HiveMetastoreClosure hiveMetastoreClosure = new HiveMetastoreClosure(cachingHiveMetastore); + HiveMetastoreClosure hiveMetastoreClosure = new HiveMetastoreClosure(cachingHiveMetastore, typeManager, partitionProjectionEnabled); DirectoryLister directoryLister = transactionScopeCachingDirectoryListerFactory.get(this.directoryLister); SemiTransactionalHiveMetastore metastore = new SemiTransactionalHiveMetastore( diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetastoreClosure.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetastoreClosure.java index 21e0141b8f6a..cdfa66383624 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetastoreClosure.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetastoreClosure.java @@ -17,6 +17,7 @@ import io.trino.hive.thrift.metastore.DataOperationType; import io.trino.plugin.hive.acid.AcidOperation; import io.trino.plugin.hive.acid.AcidTransaction; +import io.trino.plugin.hive.aws.athena.PartitionProjection; import io.trino.plugin.hive.metastore.AcidTransactionOwner; import io.trino.plugin.hive.metastore.Column; import io.trino.plugin.hive.metastore.Database; @@ -28,6 +29,7 @@ import io.trino.plugin.hive.metastore.PartitionWithStatistics; import io.trino.plugin.hive.metastore.PrincipalPrivileges; import io.trino.plugin.hive.metastore.Table; +import io.trino.spi.TrinoException; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.connector.TableNotFoundException; import io.trino.spi.function.LanguageFunction; @@ -35,6 +37,7 @@ import io.trino.spi.predicate.TupleDomain; import io.trino.spi.security.RoleGrant; import io.trino.spi.type.Type; +import io.trino.spi.type.TypeManager; import java.util.Collection; import java.util.List; @@ -47,20 +50,26 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.collect.Maps.immutableEntry; +import static io.trino.plugin.hive.HiveErrorCode.HIVE_TABLE_DROPPED_DURING_QUERY; import static io.trino.plugin.hive.HivePartitionManager.extractPartitionValues; +import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getPartitionProjectionFromTable; import static java.util.Objects.requireNonNull; public class HiveMetastoreClosure { private final HiveMetastore delegate; + private final TypeManager typeManager; + private final boolean partitionProjectionEnabled; /** * Do not use this directly. Instead, the closure should be fetched from the current SemiTransactionalHiveMetastore, * which can be fetched from the current HiveMetadata. */ - public HiveMetastoreClosure(HiveMetastore delegate) + public HiveMetastoreClosure(HiveMetastore delegate, TypeManager typeManager, boolean partitionProjectionEnabled) { this.delegate = requireNonNull(delegate, "delegate is null"); + this.typeManager = requireNonNull(typeManager, "typeManager is null"); + this.partitionProjectionEnabled = partitionProjectionEnabled; } public Optional getDatabase(String databaseName) @@ -247,12 +256,21 @@ public Optional> getPartitionNamesByFilter( List columnNames, TupleDomain partitionKeysFilter) { + if (partitionProjectionEnabled) { + Table table = getTable(databaseName, tableName) + .orElseThrow(() -> new TrinoException(HIVE_TABLE_DROPPED_DURING_QUERY, "Table does not exists: " + tableName)); + + Optional projection = getPartitionProjectionFromTable(table, typeManager); + if (projection.isPresent()) { + return projection.get().getProjectedPartitionNamesByFilter(columnNames, partitionKeysFilter); + } + } return delegate.getPartitionNamesByFilter(databaseName, tableName, columnNames, partitionKeysFilter); } private List getExistingPartitionsByNames(Table table, List partitionNames) { - Map partitions = delegate.getPartitionsByNames(table, partitionNames).entrySet().stream() + Map partitions = getPartitionsByNames(table, partitionNames).entrySet().stream() .map(entry -> immutableEntry(entry.getKey(), entry.getValue().orElseThrow(() -> new PartitionNotFoundException(table.getSchemaTableName(), extractPartitionValues(entry.getKey()))))) .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)); @@ -265,11 +283,22 @@ private List getExistingPartitionsByNames(Table table, List p public Map> getPartitionsByNames(String databaseName, String tableName, List partitionNames) { return delegate.getTable(databaseName, tableName) - .map(table -> delegate.getPartitionsByNames(table, partitionNames)) + .map(table -> getPartitionsByNames(table, partitionNames)) .orElseGet(() -> partitionNames.stream() .collect(toImmutableMap(name -> name, name -> Optional.empty()))); } + private Map> getPartitionsByNames(Table table, List partitionNames) + { + if (partitionProjectionEnabled) { + Optional projection = getPartitionProjectionFromTable(table, typeManager); + if (projection.isPresent()) { + return projection.get().getProjectedPartitionsByNames(table, partitionNames); + } + } + return delegate.getPartitionsByNames(table, partitionNames); + } + public void addPartitions(String databaseName, String tableName, List partitions) { delegate.addPartitions(databaseName, tableName, partitions); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePageSinkProvider.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePageSinkProvider.java index 1b4219dce3c5..115f277e236d 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePageSinkProvider.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePageSinkProvider.java @@ -172,7 +172,7 @@ private HivePageSink createPageSink(HiveWritableTableHandle handle, boolean isCr handle.getLocationHandle(), locationService, session.getQueryId(), - new HivePageSinkMetadataProvider(handle.getPageSinkMetadata(), new HiveMetastoreClosure(cachingHiveMetastore)), + new HivePageSinkMetadataProvider(handle.getPageSinkMetadata(), new HiveMetastoreClosure(cachingHiveMetastore, typeManager, false)), typeManager, pageSorter, writerSortBufferSize, diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/InternalHiveConnectorFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/InternalHiveConnectorFactory.java index b740d5f42fb0..995606d56797 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/InternalHiveConnectorFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/InternalHiveConnectorFactory.java @@ -39,7 +39,6 @@ import io.trino.plugin.base.jmx.ConnectorObjectNameGeneratorModule; import io.trino.plugin.base.jmx.MBeanServerModule; import io.trino.plugin.base.session.SessionPropertiesProvider; -import io.trino.plugin.hive.aws.athena.PartitionProjectionModule; import io.trino.plugin.hive.fs.CachingDirectoryListerModule; import io.trino.plugin.hive.fs.DirectoryLister; import io.trino.plugin.hive.metastore.HiveMetastore; @@ -106,7 +105,6 @@ public static Connector createConnector( new JsonModule(), new TypeDeserializerModule(context.getTypeManager()), new HiveModule(), - new PartitionProjectionModule(), new CachingDirectoryListerModule(directoryLister), new HiveMetastoreModule(metastore), new HiveSecurityModule(), diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjection.java index e4156bbe90fd..9cbfafbd3eca 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjection.java @@ -39,7 +39,7 @@ import static java.lang.String.format; import static java.util.Objects.requireNonNull; -final class PartitionProjection +public final class PartitionProjection { private static final Pattern PROJECTION_LOCATION_TEMPLATE_PLACEHOLDER_PATTERN = Pattern.compile("(\\$\\{[^}]+\\})"); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionMetastoreDecorator.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionMetastoreDecorator.java deleted file mode 100644 index bd7c3e46ad8d..000000000000 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionMetastoreDecorator.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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 io.trino.plugin.hive.aws.athena; - -import com.google.inject.Inject; -import io.trino.plugin.hive.HiveConfig; -import io.trino.plugin.hive.metastore.ForwardingHiveMetastore; -import io.trino.plugin.hive.metastore.HiveMetastore; -import io.trino.plugin.hive.metastore.HiveMetastoreDecorator; -import io.trino.plugin.hive.metastore.Partition; -import io.trino.plugin.hive.metastore.Table; -import io.trino.spi.TrinoException; -import io.trino.spi.predicate.TupleDomain; -import io.trino.spi.type.TypeManager; - -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import static io.trino.plugin.hive.HiveErrorCode.HIVE_TABLE_DROPPED_DURING_QUERY; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getPartitionProjectionFromTable; -import static java.util.Objects.requireNonNull; - -public class PartitionProjectionMetastoreDecorator - implements HiveMetastoreDecorator -{ - private final TypeManager typeManager; - private final boolean partitionProjectionEnabled; - - @Inject - public PartitionProjectionMetastoreDecorator(TypeManager typeManager, HiveConfig hiveConfig) - { - this.typeManager = requireNonNull(typeManager, "typeManager is null"); - this.partitionProjectionEnabled = hiveConfig.isPartitionProjectionEnabled(); - } - - @Override - public int getPriority() - { - return PRIORITY_PARTITION_PROJECTION; - } - - @Override - public HiveMetastore decorate(HiveMetastore hiveMetastore) - { - return new PartitionProjectionMetastore(hiveMetastore, typeManager, partitionProjectionEnabled); - } - - private static class PartitionProjectionMetastore - extends ForwardingHiveMetastore - { - private final TypeManager typeManager; - private final boolean partitionProjectionEnabled; - - public PartitionProjectionMetastore(HiveMetastore hiveMetastore, TypeManager typeManager, boolean partitionProjectionEnabled) - { - super(hiveMetastore); - this.typeManager = typeManager; - this.partitionProjectionEnabled = partitionProjectionEnabled; - } - - @Override - public Optional> getPartitionNamesByFilter(String databaseName, String tableName, List columnNames, TupleDomain partitionKeysFilter) - { - Table table = getTable(databaseName, tableName) - .orElseThrow(() -> new TrinoException(HIVE_TABLE_DROPPED_DURING_QUERY, "Table does not exists: " + tableName)); - - Optional projection = getPartitionProjection(table); - if (projection.isPresent()) { - return projection.get().getProjectedPartitionNamesByFilter(columnNames, partitionKeysFilter); - } - - return super.getPartitionNamesByFilter(databaseName, tableName, columnNames, partitionKeysFilter); - } - - @Override - public Map> getPartitionsByNames(Table table, List partitionNames) - { - Optional projection = getPartitionProjection(table); - if (projection.isPresent()) { - return projection.get().getProjectedPartitionsByNames(table, partitionNames); - } - return super.getPartitionsByNames(table, partitionNames); - } - - private Optional getPartitionProjection(Table table) - { - if (!partitionProjectionEnabled) { - return Optional.empty(); - } - return getPartitionProjectionFromTable(table, typeManager); - } - } -} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionModule.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionModule.java deleted file mode 100644 index 7e0b71fb335f..000000000000 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionModule.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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 io.trino.plugin.hive.aws.athena; - -import com.google.inject.Binder; -import com.google.inject.Scopes; -import io.airlift.configuration.AbstractConfigurationAwareModule; -import io.trino.plugin.hive.metastore.HiveMetastoreDecorator; - -import static com.google.inject.multibindings.Multibinder.newSetBinder; - -public class PartitionProjectionModule - extends AbstractConfigurationAwareModule -{ - @Override - public void setup(Binder binder) - { - newSetBinder(binder, HiveMetastoreDecorator.class) - .addBinding() - .to(PartitionProjectionMetastoreDecorator.class) - .in(Scopes.SINGLETON); - } -} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionProperties.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionProperties.java index 32bd46250b7b..ff3759236c01 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionProperties.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionProperties.java @@ -183,7 +183,7 @@ public static boolean arePartitionProjectionPropertiesSet(ConnectorTableMetadata .anyMatch(propertyKey -> propertyKey.startsWith(PROPERTY_KEY_PREFIX)); } - static Optional getPartitionProjectionFromTable(Table table, TypeManager typeManager) + public static Optional getPartitionProjectionFromTable(Table table, TypeManager typeManager) { Map tableProperties = table.getParameters(); if (parseBoolean(tableProperties.get(METASTORE_PROPERTY_PROJECTION_IGNORE)) || diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastoreDecorator.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastoreDecorator.java index 50d0dd005e32..db7cd2305a9e 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastoreDecorator.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastoreDecorator.java @@ -16,7 +16,6 @@ public interface HiveMetastoreDecorator { - int PRIORITY_PARTITION_PROJECTION = 50; int PRIORITY_TRACING = 100; int getPriority(); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java index 2252593ef5b8..122de9327503 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java @@ -3318,7 +3318,7 @@ public void testUpdateTableColumnStatisticsEmptyOptionalFields() protected void testUpdateTableStatistics(SchemaTableName tableName, PartitionStatistics initialStatistics, PartitionStatistics... statistics) { - HiveMetastoreClosure metastoreClient = new HiveMetastoreClosure(getMetastoreClient()); + HiveMetastoreClosure metastoreClient = new HiveMetastoreClosure(getMetastoreClient(), TESTING_TYPE_MANAGER, false); assertThat(metastoreClient.getTableStatistics(tableName.getSchemaName(), tableName.getTableName(), Optional.empty())) .isEqualTo(initialStatistics); @@ -3404,7 +3404,7 @@ public void testDataColumnProperties() throws Exception { SchemaTableName tableName = temporaryTable("test_column_properties"); - HiveMetastoreClosure metastoreClient = new HiveMetastoreClosure(getMetastoreClient()); + HiveMetastoreClosure metastoreClient = new HiveMetastoreClosure(getMetastoreClient(), TESTING_TYPE_MANAGER, false); try { doCreateEmptyTable(tableName, ORC, List.of(new ColumnMetadata("id", BIGINT), new ColumnMetadata("part_key", createVarcharType(256)))); @@ -3447,7 +3447,7 @@ public void testPartitionColumnProperties() throws Exception { SchemaTableName tableName = temporaryTable("test_column_properties"); - HiveMetastoreClosure metastoreClient = new HiveMetastoreClosure(getMetastoreClient()); + HiveMetastoreClosure metastoreClient = new HiveMetastoreClosure(getMetastoreClient(), TESTING_TYPE_MANAGER, false); try { doCreateEmptyTable(tableName, ORC, List.of(new ColumnMetadata("id", BIGINT), new ColumnMetadata("part_key", createVarcharType(256)))); @@ -3613,7 +3613,7 @@ protected void createDummyPartitionedTable(SchemaTableName tableName, List new TableNotFoundException(tableName)); @@ -3643,7 +3643,7 @@ protected void testUpdatePartitionStatistics( String firstPartitionName = "ds=2016-01-01"; String secondPartitionName = "ds=2016-01-02"; - HiveMetastoreClosure metastoreClient = new HiveMetastoreClosure(getMetastoreClient()); + HiveMetastoreClosure metastoreClient = new HiveMetastoreClosure(getMetastoreClient(), TESTING_TYPE_MANAGER, false); assertThat(metastoreClient.getPartitionStatistics(tableName.getSchemaName(), tableName.getTableName(), ImmutableSet.of(firstPartitionName, secondPartitionName))) .isEqualTo(ImmutableMap.of(firstPartitionName, initialStatistics, secondPartitionName, initialStatistics)); @@ -3700,7 +3700,7 @@ protected void testStorePartitionWithStatistics( try { doCreateEmptyTable(tableName, ORC, columns); - HiveMetastoreClosure metastoreClient = new HiveMetastoreClosure(getMetastoreClient()); + HiveMetastoreClosure metastoreClient = new HiveMetastoreClosure(getMetastoreClient(), TESTING_TYPE_MANAGER, false); Table table = metastoreClient.getTable(tableName.getSchemaName(), tableName.getTableName()) .orElseThrow(() -> new TableNotFoundException(tableName)); @@ -3801,7 +3801,7 @@ protected void testPartitionStatisticsSampling(List columns, Par try { createDummyPartitionedTable(tableName, columns); - HiveMetastoreClosure metastoreClient = new HiveMetastoreClosure(getMetastoreClient()); + HiveMetastoreClosure metastoreClient = new HiveMetastoreClosure(getMetastoreClient(), TESTING_TYPE_MANAGER, false); metastoreClient.updatePartitionStatistics(tableName.getSchemaName(), tableName.getTableName(), "ds=2016-01-01", actualStatistics -> statistics); metastoreClient.updatePartitionStatistics(tableName.getSchemaName(), tableName.getTableName(), "ds=2016-01-02", actualStatistics -> statistics); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestSemiTransactionalHiveMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestSemiTransactionalHiveMetastore.java index 63f1346c9726..765619228c86 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestSemiTransactionalHiveMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestSemiTransactionalHiveMetastore.java @@ -39,6 +39,7 @@ import static io.trino.plugin.hive.acid.AcidOperation.INSERT; import static io.trino.plugin.hive.util.HiveBucketing.BucketingVersion.BUCKETING_V1; import static io.trino.testing.TestingConnectorSession.SESSION; +import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.util.concurrent.Executors.newFixedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; import static org.testng.Assert.assertTrue; @@ -80,7 +81,7 @@ private SemiTransactionalHiveMetastore getSemiTransactionalHiveMetastoreWithDrop { return new SemiTransactionalHiveMetastore( HDFS_FILE_SYSTEM_FACTORY, - new HiveMetastoreClosure(new TestingHiveMetastore()), + new HiveMetastoreClosure(new TestingHiveMetastore(), TESTING_TYPE_MANAGER, false), directExecutor(), dropExecutor, directExecutor(), @@ -121,7 +122,7 @@ private SemiTransactionalHiveMetastore getSemiTransactionalHiveMetastoreWithUpda { return new SemiTransactionalHiveMetastore( HDFS_FILE_SYSTEM_FACTORY, - new HiveMetastoreClosure(new TestingHiveMetastore()), + new HiveMetastoreClosure(new TestingHiveMetastore(), TESTING_TYPE_MANAGER, false), directExecutor(), directExecutor(), updateExecutor, diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastore.java index a4d65e13e014..117b8384dcfc 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastore.java @@ -111,6 +111,7 @@ import static io.trino.spi.predicate.TupleDomain.withColumnDomains; import static io.trino.spi.security.PrincipalType.USER; import static io.trino.spi.type.VarcharType.VARCHAR; +import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.lang.String.format; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.TimeUnit.SECONDS; @@ -845,7 +846,7 @@ public void testUpdatePartitionStatistics() { assertEquals(mockClient.getAccessCount(), 0); - HiveMetastoreClosure hiveMetastoreClosure = new HiveMetastoreClosure(metastore); + HiveMetastoreClosure hiveMetastoreClosure = new HiveMetastoreClosure(metastore, TESTING_TYPE_MANAGER, false); Table table = hiveMetastoreClosure.getTable(TEST_DATABASE, TEST_TABLE).orElseThrow(); assertEquals(mockClient.getAccessCount(), 1); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestHiveGlueMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestHiveGlueMetastore.java index cfef79f5ffae..091338b1b4a3 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestHiveGlueMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestHiveGlueMetastore.java @@ -114,6 +114,7 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; import static io.trino.testing.TestingPageSinkId.TESTING_PAGE_SINK_ID; +import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.lang.String.format; import static java.lang.System.currentTimeMillis; import static java.util.Collections.unmodifiableList; @@ -212,7 +213,7 @@ public void initialize() // Logging logging = Logging.initialize(); // logging.setLevel("com.amazonaws.request", Level.DEBUG); - metastore = new HiveMetastoreClosure(metastoreClient); + metastore = new HiveMetastoreClosure(metastoreClient, TESTING_TYPE_MANAGER, false); glueClient = AWSGlueAsyncClientBuilder.defaultClient(); } @@ -384,7 +385,7 @@ public void testGetPartitionsWithFilterUsingReservedKeywordsAsColumnName() doCreateEmptyTable(tableName, ORC, columns, partitionedBy); - HiveMetastoreClosure metastoreClient = new HiveMetastoreClosure(getMetastoreClient()); + HiveMetastoreClosure metastoreClient = new HiveMetastoreClosure(getMetastoreClient(), TESTING_TYPE_MANAGER, false); Table table = metastoreClient.getTable(tableName.getSchemaName(), tableName.getTableName()) .orElseThrow(() -> new TableNotFoundException(tableName)); @@ -1542,7 +1543,7 @@ private void createDummyPartitionedTable(SchemaTableName tableName, List new TableNotFoundException(tableName)); List partitions = new ArrayList<>(); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestHiveMetastoreAccessOperations.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestHiveMetastoreAccessOperations.java index 50e5d054a617..35dce20c9f3e 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestHiveMetastoreAccessOperations.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestHiveMetastoreAccessOperations.java @@ -120,7 +120,7 @@ public void testSelectPartitionedTable() assertMetastoreInvocations("SELECT * FROM test_select_partition", ImmutableMultiset.builder() - .addCopies(GET_TABLE, 2) + .add(GET_TABLE) .add(GET_PARTITION_NAMES_BY_FILTER) .add(GET_PARTITIONS_BY_NAMES) .build()); @@ -128,7 +128,7 @@ public void testSelectPartitionedTable() assertUpdate("INSERT INTO test_select_partition SELECT 2 AS data, 20 AS part", 1); assertMetastoreInvocations("SELECT * FROM test_select_partition", ImmutableMultiset.builder() - .addCopies(GET_TABLE, 2) + .add(GET_TABLE) .add(GET_PARTITION_NAMES_BY_FILTER) .add(GET_PARTITIONS_BY_NAMES) .build()); @@ -136,7 +136,7 @@ public void testSelectPartitionedTable() // Specify a specific partition assertMetastoreInvocations("SELECT * FROM test_select_partition WHERE part = 10", ImmutableMultiset.builder() - .addCopies(GET_TABLE, 2) + .add(GET_TABLE) .add(GET_PARTITION_NAMES_BY_FILTER) .add(GET_PARTITIONS_BY_NAMES) .build()); @@ -269,7 +269,7 @@ public void testAnalyzePartitionedTable() assertMetastoreInvocations("ANALYZE test_analyze_partition", ImmutableMultiset.builder() - .addCopies(GET_TABLE, 2) + .addCopies(GET_TABLE, 1) .add(GET_PARTITION_NAMES_BY_FILTER) .add(GET_PARTITIONS_BY_NAMES) .add(GET_PARTITION_STATISTICS) @@ -280,7 +280,7 @@ public void testAnalyzePartitionedTable() assertMetastoreInvocations("ANALYZE test_analyze_partition", ImmutableMultiset.builder() - .addCopies(GET_TABLE, 2) + .add(GET_TABLE) .add(GET_PARTITION_NAMES_BY_FILTER) .add(GET_PARTITIONS_BY_NAMES) .add(GET_PARTITION_STATISTICS) @@ -307,7 +307,7 @@ public void testDropStatsPartitionedTable() assertMetastoreInvocations("CALL system.drop_stats('test_schema', 'drop_stats_partition')", ImmutableMultiset.builder() - .addCopies(GET_TABLE, 2) + .add(GET_TABLE) .add(GET_PARTITION_NAMES_BY_FILTER) .add(UPDATE_PARTITION_STATISTICS) .build()); @@ -316,7 +316,7 @@ public void testDropStatsPartitionedTable() assertMetastoreInvocations("CALL system.drop_stats('test_schema', 'drop_stats_partition')", ImmutableMultiset.builder() - .addCopies(GET_TABLE, 2) + .add(GET_TABLE) .add(GET_PARTITION_NAMES_BY_FILTER) .addCopies(UPDATE_PARTITION_STATISTICS, 2) .build()); From c3e147505f2e6dca8243a6fe9932977af45ede36 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Fri, 17 Nov 2023 23:49:38 -0800 Subject: [PATCH 330/587] Move partition projection to io.trino.plugin.hive.projection package --- .../trino/plugin/hive/HiveColumnProperties.java | 16 ++++++++-------- .../java/io/trino/plugin/hive/HiveMetadata.java | 6 +++--- .../trino/plugin/hive/HiveMetastoreClosure.java | 4 ++-- .../trino/plugin/hive/HiveTableProperties.java | 6 +++--- .../athena => projection}/DateProjection.java | 14 +++++++------- .../athena => projection}/EnumProjection.java | 6 +++--- .../InjectedProjection.java | 2 +- .../athena => projection}/IntegerProjection.java | 12 ++++++------ .../InvalidProjectionException.java | 2 +- .../PartitionProjection.java | 4 ++-- .../PartitionProjectionProperties.java | 2 +- .../{aws/athena => projection}/Projection.java | 2 +- .../athena => projection}/ProjectionType.java | 2 +- .../java/io/trino/plugin/hive/util/HiveUtil.java | 2 +- .../TestDateProjectionFactory.java | 6 +++--- .../TestEnumProjectionFactory.java | 4 ++-- .../TestInjectedProjectionFactory.java | 2 +- .../TestIntegerProjectionFactory.java | 8 ++++---- 18 files changed, 50 insertions(+), 50 deletions(-) rename plugin/trino-hive/src/main/java/io/trino/plugin/hive/{aws/athena => projection}/DateProjection.java (96%) rename plugin/trino-hive/src/main/java/io/trino/plugin/hive/{aws/athena => projection}/EnumProjection.java (93%) rename plugin/trino-hive/src/main/java/io/trino/plugin/hive/{aws/athena => projection}/InjectedProjection.java (98%) rename plugin/trino-hive/src/main/java/io/trino/plugin/hive/{aws/athena => projection}/IntegerProjection.java (92%) rename plugin/trino-hive/src/main/java/io/trino/plugin/hive/{aws/athena => projection}/InvalidProjectionException.java (97%) rename plugin/trino-hive/src/main/java/io/trino/plugin/hive/{aws/athena => projection}/PartitionProjection.java (98%) rename plugin/trino-hive/src/main/java/io/trino/plugin/hive/{aws/athena => projection}/PartitionProjectionProperties.java (99%) rename plugin/trino-hive/src/main/java/io/trino/plugin/hive/{aws/athena => projection}/Projection.java (95%) rename plugin/trino-hive/src/main/java/io/trino/plugin/hive/{aws/athena => projection}/ProjectionType.java (93%) rename plugin/trino-hive/src/test/java/io/trino/plugin/hive/{aws/athena => projection}/TestDateProjectionFactory.java (96%) rename plugin/trino-hive/src/test/java/io/trino/plugin/hive/{aws/athena => projection}/TestEnumProjectionFactory.java (96%) rename plugin/trino-hive/src/test/java/io/trino/plugin/hive/{aws/athena => projection}/TestInjectedProjectionFactory.java (98%) rename plugin/trino-hive/src/test/java/io/trino/plugin/hive/{aws/athena => projection}/TestIntegerProjectionFactory.java (95%) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveColumnProperties.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveColumnProperties.java index 955ed02da57c..11f3da8cb590 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveColumnProperties.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveColumnProperties.java @@ -14,7 +14,7 @@ package io.trino.plugin.hive; import com.google.common.collect.ImmutableList; -import io.trino.plugin.hive.aws.athena.ProjectionType; +import io.trino.plugin.hive.projection.ProjectionType; import io.trino.spi.session.PropertyMetadata; import io.trino.spi.type.ArrayType; @@ -22,13 +22,13 @@ import java.util.List; import static com.google.common.collect.ImmutableList.toImmutableList; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_DIGITS; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_FORMAT; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_INTERVAL; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_INTERVAL_UNIT; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_RANGE; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_TYPE; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_VALUES; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.COLUMN_PROJECTION_DIGITS; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.COLUMN_PROJECTION_FORMAT; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.COLUMN_PROJECTION_INTERVAL; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.COLUMN_PROJECTION_INTERVAL_UNIT; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.COLUMN_PROJECTION_RANGE; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.COLUMN_PROJECTION_TYPE; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.COLUMN_PROJECTION_VALUES; import static io.trino.spi.session.PropertyMetadata.enumProperty; import static io.trino.spi.session.PropertyMetadata.integerProperty; import static io.trino.spi.session.PropertyMetadata.stringProperty; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java index c6692dac5c1b..02f7652ad295 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java @@ -257,9 +257,6 @@ import static io.trino.plugin.hive.ViewReaderUtil.isTrinoView; import static io.trino.plugin.hive.acid.AcidTransaction.NO_ACID_TRANSACTION; import static io.trino.plugin.hive.acid.AcidTransaction.forCreateTable; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.arePartitionProjectionPropertiesSet; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getPartitionProjectionHiveTableProperties; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getPartitionProjectionTrinoTableProperties; import static io.trino.plugin.hive.metastore.MetastoreUtil.buildInitialPrivilegeSet; import static io.trino.plugin.hive.metastore.MetastoreUtil.getHiveSchema; import static io.trino.plugin.hive.metastore.MetastoreUtil.getProtectMode; @@ -272,6 +269,9 @@ import static io.trino.plugin.hive.metastore.StorageFormat.VIEW_STORAGE_FORMAT; import static io.trino.plugin.hive.metastore.StorageFormat.fromHiveStorageFormat; import static io.trino.plugin.hive.metastore.thrift.ThriftMetastoreUtil.STATS_PROPERTIES; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.arePartitionProjectionPropertiesSet; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.getPartitionProjectionHiveTableProperties; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.getPartitionProjectionTrinoTableProperties; import static io.trino.plugin.hive.type.Category.PRIMITIVE; import static io.trino.plugin.hive.util.AcidTables.deltaSubdir; import static io.trino.plugin.hive.util.AcidTables.isFullAcidTable; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetastoreClosure.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetastoreClosure.java index cdfa66383624..190e81bd28fe 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetastoreClosure.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetastoreClosure.java @@ -17,7 +17,6 @@ import io.trino.hive.thrift.metastore.DataOperationType; import io.trino.plugin.hive.acid.AcidOperation; import io.trino.plugin.hive.acid.AcidTransaction; -import io.trino.plugin.hive.aws.athena.PartitionProjection; import io.trino.plugin.hive.metastore.AcidTransactionOwner; import io.trino.plugin.hive.metastore.Column; import io.trino.plugin.hive.metastore.Database; @@ -29,6 +28,7 @@ import io.trino.plugin.hive.metastore.PartitionWithStatistics; import io.trino.plugin.hive.metastore.PrincipalPrivileges; import io.trino.plugin.hive.metastore.Table; +import io.trino.plugin.hive.projection.PartitionProjection; import io.trino.spi.TrinoException; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.connector.TableNotFoundException; @@ -52,7 +52,7 @@ import static com.google.common.collect.Maps.immutableEntry; import static io.trino.plugin.hive.HiveErrorCode.HIVE_TABLE_DROPPED_DURING_QUERY; import static io.trino.plugin.hive.HivePartitionManager.extractPartitionValues; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getPartitionProjectionFromTable; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.getPartitionProjectionFromTable; import static java.util.Objects.requireNonNull; public class HiveMetastoreClosure diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveTableProperties.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveTableProperties.java index 51a70f9bc7e6..321a57bb977a 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveTableProperties.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveTableProperties.java @@ -30,9 +30,9 @@ import java.util.Optional; import static com.google.common.collect.ImmutableList.toImmutableList; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.PARTITION_PROJECTION_ENABLED; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.PARTITION_PROJECTION_IGNORE; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.PARTITION_PROJECTION_LOCATION_TEMPLATE; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.PARTITION_PROJECTION_ENABLED; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.PARTITION_PROJECTION_IGNORE; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.PARTITION_PROJECTION_LOCATION_TEMPLATE; import static io.trino.plugin.hive.util.HiveBucketing.BucketingVersion.BUCKETING_V1; import static io.trino.plugin.hive.util.HiveBucketing.BucketingVersion.BUCKETING_V2; import static io.trino.spi.StandardErrorCode.INVALID_TABLE_PROPERTY; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/DateProjection.java similarity index 96% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjection.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/DateProjection.java index 480f5f7fd911..18cc546b236d 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/DateProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/DateProjection.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.aws.athena; +package io.trino.plugin.hive.projection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -41,12 +41,12 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static io.airlift.slice.Slices.utf8Slice; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_FORMAT; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_INTERVAL; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_INTERVAL_UNIT; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_RANGE; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getProjectionPropertyRequiredValue; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getProjectionPropertyValue; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.COLUMN_PROJECTION_FORMAT; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.COLUMN_PROJECTION_INTERVAL; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.COLUMN_PROJECTION_INTERVAL_UNIT; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.COLUMN_PROJECTION_RANGE; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.getProjectionPropertyRequiredValue; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.getProjectionPropertyValue; import static io.trino.spi.predicate.Domain.singleValue; import static java.lang.String.format; import static java.time.temporal.ChronoUnit.DAYS; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/EnumProjection.java similarity index 93% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjection.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/EnumProjection.java index e3ea35a218ce..45af272920a8 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/EnumProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/EnumProjection.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.aws.athena; +package io.trino.plugin.hive.projection; import io.trino.spi.predicate.Domain; import io.trino.spi.type.Type; @@ -23,8 +23,8 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static io.airlift.slice.Slices.utf8Slice; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_VALUES; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getProjectionPropertyRequiredValue; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.COLUMN_PROJECTION_VALUES; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.getProjectionPropertyRequiredValue; import static io.trino.spi.predicate.Domain.singleValue; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/InjectedProjection.java similarity index 98% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjection.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/InjectedProjection.java index 215b1aac45da..acead53bf047 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InjectedProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/InjectedProjection.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.aws.athena; +package io.trino.plugin.hive.projection; import com.google.common.collect.ImmutableList; import io.trino.spi.predicate.Domain; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/IntegerProjection.java similarity index 92% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjection.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/IntegerProjection.java index dc21fe5cca92..2b07e78bbf15 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/IntegerProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/IntegerProjection.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.aws.athena; +package io.trino.plugin.hive.projection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; @@ -27,11 +27,11 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static io.airlift.slice.Slices.utf8Slice; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_DIGITS; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_INTERVAL; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_RANGE; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getProjectionPropertyRequiredValue; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getProjectionPropertyValue; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.COLUMN_PROJECTION_DIGITS; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.COLUMN_PROJECTION_INTERVAL; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.COLUMN_PROJECTION_RANGE; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.getProjectionPropertyRequiredValue; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.getProjectionPropertyValue; import static io.trino.spi.predicate.Domain.singleValue; import static java.lang.String.format; import static java.util.Objects.requireNonNull; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InvalidProjectionException.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/InvalidProjectionException.java similarity index 97% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InvalidProjectionException.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/InvalidProjectionException.java index 46c3d9f38707..938152015700 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/InvalidProjectionException.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/InvalidProjectionException.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.aws.athena; +package io.trino.plugin.hive.projection; import io.trino.spi.TrinoException; import io.trino.spi.type.Type; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/PartitionProjection.java similarity index 98% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjection.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/PartitionProjection.java index 9cbfafbd3eca..f3214e4ddf01 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/PartitionProjection.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.aws.athena; +package io.trino.plugin.hive.projection; import com.google.common.base.VerifyException; import com.google.common.collect.ImmutableMap; @@ -33,7 +33,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.collect.Sets.cartesianProduct; -import static io.trino.plugin.hive.aws.athena.InvalidProjectionException.invalidProjectionMessage; +import static io.trino.plugin.hive.projection.InvalidProjectionException.invalidProjectionMessage; import static io.trino.plugin.hive.util.HiveUtil.escapePathName; import static io.trino.plugin.hive.util.HiveUtil.toPartitionValues; import static java.lang.String.format; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionProperties.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/PartitionProjectionProperties.java similarity index 99% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionProperties.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/PartitionProjectionProperties.java index ff3759236c01..1cd046ea8647 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/PartitionProjectionProperties.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/PartitionProjectionProperties.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.aws.athena; +package io.trino.plugin.hive.projection; import com.google.common.base.Joiner; import com.google.common.base.Splitter; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/Projection.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/Projection.java similarity index 95% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/Projection.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/Projection.java index 7e6eca7212b9..0677769dfaba 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/Projection.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/Projection.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.aws.athena; +package io.trino.plugin.hive.projection; import io.trino.spi.predicate.Domain; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/ProjectionType.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/ProjectionType.java similarity index 93% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/ProjectionType.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/ProjectionType.java index 5d327849ed0d..7521053f87d2 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/athena/ProjectionType.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/projection/ProjectionType.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.aws.athena; +package io.trino.plugin.hive.projection; public enum ProjectionType { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveUtil.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveUtil.java index 6c43224d64a4..061b08e14e03 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveUtil.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveUtil.java @@ -95,9 +95,9 @@ import static io.trino.plugin.hive.HiveSessionProperties.getTimestampPrecision; import static io.trino.plugin.hive.HiveTableProperties.ORC_BLOOM_FILTER_FPP; import static io.trino.plugin.hive.HiveType.toHiveTypes; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.getPartitionProjectionTrinoColumnProperties; import static io.trino.plugin.hive.metastore.SortingColumn.Order.ASCENDING; import static io.trino.plugin.hive.metastore.SortingColumn.Order.DESCENDING; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.getPartitionProjectionTrinoColumnProperties; import static io.trino.plugin.hive.util.HiveBucketing.isSupportedBucketing; import static io.trino.plugin.hive.util.HiveClassNames.HUDI_INPUT_FORMAT; import static io.trino.plugin.hive.util.HiveClassNames.HUDI_PARQUET_INPUT_FORMAT; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestDateProjectionFactory.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/projection/TestDateProjectionFactory.java similarity index 96% rename from plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestDateProjectionFactory.java rename to plugin/trino-hive/src/test/java/io/trino/plugin/hive/projection/TestDateProjectionFactory.java index e12b073ff344..03cc6b724c8c 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestDateProjectionFactory.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/projection/TestDateProjectionFactory.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.aws.athena; +package io.trino.plugin.hive.projection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -21,8 +21,8 @@ import java.util.Optional; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_FORMAT; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_RANGE; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.COLUMN_PROJECTION_FORMAT; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.COLUMN_PROJECTION_RANGE; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.DateType.DATE; import static io.trino.spi.type.TimestampType.TIMESTAMP_MICROS; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestEnumProjectionFactory.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/projection/TestEnumProjectionFactory.java similarity index 96% rename from plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestEnumProjectionFactory.java rename to plugin/trino-hive/src/test/java/io/trino/plugin/hive/projection/TestEnumProjectionFactory.java index 24a337471610..a3e10d53f41e 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestEnumProjectionFactory.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/projection/TestEnumProjectionFactory.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.aws.athena; +package io.trino.plugin.hive.projection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -21,7 +21,7 @@ import java.util.Optional; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_VALUES; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.COLUMN_PROJECTION_VALUES; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.VarcharType.VARCHAR; import static org.assertj.core.api.Assertions.assertThat; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestInjectedProjectionFactory.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/projection/TestInjectedProjectionFactory.java similarity index 98% rename from plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestInjectedProjectionFactory.java rename to plugin/trino-hive/src/test/java/io/trino/plugin/hive/projection/TestInjectedProjectionFactory.java index 32ba355f5853..8591cfcff272 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestInjectedProjectionFactory.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/projection/TestInjectedProjectionFactory.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.aws.athena; +package io.trino.plugin.hive.projection; import io.airlift.slice.Slices; import io.trino.spi.predicate.Domain; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestIntegerProjectionFactory.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/projection/TestIntegerProjectionFactory.java similarity index 95% rename from plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestIntegerProjectionFactory.java rename to plugin/trino-hive/src/test/java/io/trino/plugin/hive/projection/TestIntegerProjectionFactory.java index 2cf99aa3a161..345cda943088 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/aws/athena/TestIntegerProjectionFactory.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/projection/TestIntegerProjectionFactory.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.aws.athena; +package io.trino.plugin.hive.projection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -20,9 +20,9 @@ import java.util.Optional; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_DIGITS; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_INTERVAL; -import static io.trino.plugin.hive.aws.athena.PartitionProjectionProperties.COLUMN_PROJECTION_RANGE; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.COLUMN_PROJECTION_DIGITS; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.COLUMN_PROJECTION_INTERVAL; +import static io.trino.plugin.hive.projection.PartitionProjectionProperties.COLUMN_PROJECTION_RANGE; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.DateType.DATE; import static io.trino.spi.type.IntegerType.INTEGER; From 160f1128b26dd0776ecfe965203c6bb4f4020850 Mon Sep 17 00:00:00 2001 From: Elon Azoulay Date: Fri, 17 Nov 2023 16:26:36 -0800 Subject: [PATCH 331/587] Use GcsStorageFactory in tests --- .../gcs/DefaultGcsStorageFactory.java | 92 ------------------- .../filesystem/gcs/GcsFileSystemModule.java | 2 +- .../filesystem/gcs/GcsStorageFactory.java | 72 ++++++++++++++- .../gcs/AbstractTestGcsFileSystem.java | 37 ++------ 4 files changed, 79 insertions(+), 124 deletions(-) delete mode 100644 lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/DefaultGcsStorageFactory.java diff --git a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/DefaultGcsStorageFactory.java b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/DefaultGcsStorageFactory.java deleted file mode 100644 index 8224dafaa6a3..000000000000 --- a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/DefaultGcsStorageFactory.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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 io.trino.filesystem.gcs; - -import com.google.auth.oauth2.GoogleCredentials; -import com.google.cloud.storage.Storage; -import com.google.cloud.storage.StorageOptions; -import com.google.common.base.VerifyException; -import com.google.common.collect.ImmutableList; -import com.google.inject.Inject; -import io.trino.spi.security.ConnectorIdentity; - -import java.io.ByteArrayInputStream; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.UncheckedIOException; -import java.util.List; -import java.util.Optional; - -import static com.google.common.base.Strings.nullToEmpty; -import static java.nio.charset.StandardCharsets.UTF_8; - -public class DefaultGcsStorageFactory - implements GcsStorageFactory -{ - public static final String GCS_OAUTH_KEY = "gcs.oauth"; - public static final List DEFAULT_SCOPES = ImmutableList.of("https://www.googleapis.com/auth/cloud-platform"); - private final String projectId; - private final boolean useGcsAccessToken; - private final Optional jsonGoogleCredential; - - @Inject - public DefaultGcsStorageFactory(GcsFileSystemConfig config) - throws IOException - { - config.validate(); - projectId = config.getProjectId(); - useGcsAccessToken = config.isUseGcsAccessToken(); - String jsonKey = config.getJsonKey(); - String jsonKeyFilePath = config.getJsonKeyFilePath(); - if (jsonKey != null) { - try (InputStream inputStream = new ByteArrayInputStream(jsonKey.getBytes(UTF_8))) { - jsonGoogleCredential = Optional.of(GoogleCredentials.fromStream(inputStream).createScoped(DEFAULT_SCOPES)); - } - } - else if (jsonKeyFilePath != null) { - try (FileInputStream inputStream = new FileInputStream(jsonKeyFilePath)) { - jsonGoogleCredential = Optional.of(GoogleCredentials.fromStream(inputStream).createScoped(DEFAULT_SCOPES)); - } - } - else { - jsonGoogleCredential = Optional.empty(); - } - } - - @Override - public Storage create(ConnectorIdentity identity) - { - try { - GoogleCredentials credentials; - if (useGcsAccessToken) { - String accessToken = nullToEmpty(identity.getExtraCredentials().get(GCS_OAUTH_KEY)); - try (ByteArrayInputStream inputStream = new ByteArrayInputStream(accessToken.getBytes(UTF_8))) { - credentials = GoogleCredentials.fromStream(inputStream).createScoped(DEFAULT_SCOPES); - } - } - else { - credentials = jsonGoogleCredential.orElseThrow(() -> new VerifyException("GCS credentials not configured")); - } - StorageOptions.Builder storageOptionsBuilder = StorageOptions.newBuilder(); - if (projectId != null) { - storageOptionsBuilder.setProjectId(projectId); - } - return storageOptionsBuilder.setCredentials(credentials).build().getService(); - } - catch (IOException e) { - throw new UncheckedIOException(e); - } - } -} diff --git a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileSystemModule.java b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileSystemModule.java index 7d4479d3557f..77c801d3281e 100644 --- a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileSystemModule.java +++ b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileSystemModule.java @@ -26,7 +26,7 @@ public class GcsFileSystemModule public void configure(Binder binder) { configBinder(binder).bindConfig(GcsFileSystemConfig.class); - binder.bind(GcsStorageFactory.class).to(DefaultGcsStorageFactory.class).in(Scopes.SINGLETON); + binder.bind(GcsStorageFactory.class).in(Scopes.SINGLETON); binder.bind(GcsFileSystemFactory.class).in(Scopes.SINGLETON); } } diff --git a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsStorageFactory.java b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsStorageFactory.java index 20db0b57b68a..176d45061fda 100644 --- a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsStorageFactory.java +++ b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsStorageFactory.java @@ -13,10 +13,78 @@ */ package io.trino.filesystem.gcs; +import com.google.auth.oauth2.GoogleCredentials; import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; +import com.google.common.base.VerifyException; +import com.google.common.collect.ImmutableList; +import com.google.inject.Inject; import io.trino.spi.security.ConnectorIdentity; -public interface GcsStorageFactory +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.util.List; +import java.util.Optional; + +import static com.google.common.base.Strings.nullToEmpty; +import static java.nio.charset.StandardCharsets.UTF_8; + +public class GcsStorageFactory { - Storage create(ConnectorIdentity identity); + public static final String GCS_OAUTH_KEY = "gcs.oauth"; + public static final List DEFAULT_SCOPES = ImmutableList.of("https://www.googleapis.com/auth/cloud-platform"); + private final String projectId; + private final boolean useGcsAccessToken; + private final Optional jsonGoogleCredential; + + @Inject + public GcsStorageFactory(GcsFileSystemConfig config) + throws IOException + { + config.validate(); + projectId = config.getProjectId(); + useGcsAccessToken = config.isUseGcsAccessToken(); + String jsonKey = config.getJsonKey(); + String jsonKeyFilePath = config.getJsonKeyFilePath(); + if (jsonKey != null) { + try (InputStream inputStream = new ByteArrayInputStream(jsonKey.getBytes(UTF_8))) { + jsonGoogleCredential = Optional.of(GoogleCredentials.fromStream(inputStream).createScoped(DEFAULT_SCOPES)); + } + } + else if (jsonKeyFilePath != null) { + try (FileInputStream inputStream = new FileInputStream(jsonKeyFilePath)) { + jsonGoogleCredential = Optional.of(GoogleCredentials.fromStream(inputStream).createScoped(DEFAULT_SCOPES)); + } + } + else { + jsonGoogleCredential = Optional.empty(); + } + } + + public Storage create(ConnectorIdentity identity) + { + try { + GoogleCredentials credentials; + if (useGcsAccessToken) { + String accessToken = nullToEmpty(identity.getExtraCredentials().get(GCS_OAUTH_KEY)); + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(accessToken.getBytes(UTF_8))) { + credentials = GoogleCredentials.fromStream(inputStream).createScoped(DEFAULT_SCOPES); + } + } + else { + credentials = jsonGoogleCredential.orElseThrow(() -> new VerifyException("GCS credentials not configured")); + } + StorageOptions.Builder storageOptionsBuilder = StorageOptions.newBuilder(); + if (projectId != null) { + storageOptionsBuilder.setProjectId(projectId); + } + return storageOptionsBuilder.setCredentials(credentials).build().getService(); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + } } diff --git a/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/AbstractTestGcsFileSystem.java b/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/AbstractTestGcsFileSystem.java index 805dea0176fc..5cc129bd6029 100644 --- a/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/AbstractTestGcsFileSystem.java +++ b/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/AbstractTestGcsFileSystem.java @@ -24,11 +24,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.TestInstance; -import java.io.ByteArrayInputStream; import java.io.IOException; -import java.io.InputStream; import java.util.Base64; -import java.util.concurrent.TimeUnit; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; @@ -57,7 +54,7 @@ protected void initialize(String gcpCredentialKey) // For gcp testing this corresponds to the Cluster Storage Admin and Cluster Storage Object Admin roles byte[] jsonKeyBytes = Base64.getDecoder().decode(gcpCredentialKey); GcsFileSystemConfig config = new GcsFileSystemConfig().setJsonKey(new String(jsonKeyBytes, UTF_8)); - GcsStorageFactory storageFactory = new TestingGcsStorageFactory(config); + GcsStorageFactory storageFactory = new GcsStorageFactory(config); this.gcsFileSystemFactory = new GcsFileSystemFactory(config, storageFactory); this.storage = storageFactory.create(ConnectorIdentity.ofUser("test")); String bucket = RemoteStorageHelper.generateBucketName(); @@ -69,17 +66,20 @@ protected void initialize(String gcpCredentialKey) @AfterAll void tearDown() - throws Exception { try { - RemoteStorageHelper.forceDelete(storage, rootLocation.host().get(), 5, TimeUnit.SECONDS); - gcsFileSystemFactory.stop(); + storage.delete(rootLocation.host().get()); } finally { fileSystem = null; storage = null; rootLocation = null; - gcsFileSystemFactory = null; + try { + gcsFileSystemFactory.stop(); + } + finally { + gcsFileSystemFactory = null; + } } } @@ -126,25 +126,4 @@ protected final boolean supportsRenameFile() { return false; } - - private static class TestingGcsStorageFactory - implements GcsStorageFactory - { - private final Storage storage; - - public TestingGcsStorageFactory(GcsFileSystemConfig config) - { - requireNonNull(config, "config is null"); - InputStream inputStream = new ByteArrayInputStream(config.getJsonKey().getBytes(UTF_8)); - // Note: the default project id from the credentials file will be used. See StorageOptions.setProjectId() - RemoteStorageHelper helper = RemoteStorageHelper.create(null, inputStream); - this.storage = helper.getOptions().getService(); - } - - @Override - public Storage create(ConnectorIdentity identity) - { - return storage; - } - } } From 96c72bab66db393f211587cccd788bb44a983e6d Mon Sep 17 00:00:00 2001 From: Elon Azoulay Date: Fri, 17 Nov 2023 16:28:42 -0800 Subject: [PATCH 332/587] Enable TestGcsFileSystemGcs --- .../test/java/io/trino/filesystem/gcs/TestGcsFileSystemGcs.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/TestGcsFileSystemGcs.java b/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/TestGcsFileSystemGcs.java index 32c7cfb8ff15..b9a056e487fc 100644 --- a/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/TestGcsFileSystemGcs.java +++ b/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/TestGcsFileSystemGcs.java @@ -14,12 +14,10 @@ package io.trino.filesystem.gcs; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.TestInstance; import java.io.IOException; -@Disabled // TODO Re-enable once fixed the initialization failure https://github.com/trinodb/trino/issues/19785 @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class TestGcsFileSystemGcs extends AbstractTestGcsFileSystem From 06eaf0fcf5ac050dfcf035e3e1f07e81aeecc3be Mon Sep 17 00:00:00 2001 From: Alexander Grueneberg Date: Sat, 18 Nov 2023 09:25:35 +0100 Subject: [PATCH 333/587] Add missing extended-statistics props (Iceberg) --- docs/src/main/sphinx/connector/iceberg.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/src/main/sphinx/connector/iceberg.md b/docs/src/main/sphinx/connector/iceberg.md index 16d17dafb592..14e341256d57 100644 --- a/docs/src/main/sphinx/connector/iceberg.md +++ b/docs/src/main/sphinx/connector/iceberg.md @@ -131,12 +131,22 @@ implementation is used: aggregations or joins. - 0.05 * - `iceberg.table-statistics-enabled` - - Enables [](/optimizer/statistics). The equivalent [catalog session + - Enable [](/optimizer/statistics). The equivalent [catalog session property](/sql/set-session) is `statistics_enabled` for session specific use. Set to `false` to disable statistics. Disabling statistics means that [](/optimizer/cost-based-optimizations) cannot make better decisions about the query plan. - `true` +* - `iceberg.extended-statistics.enabled` + - Enable statistics collection with [](/sql/analyze) and use of extended + statistics. The equivalent catalog session property is + `extended_statistics_enabled`. + - `true` +* - `iceberg.extended-statistics.collect-on-write` + - Enable collection of extended statistics for write operations. The + equivalent catalog session property is + `collect_extended_statistics_on_write`. + - `true` * - `iceberg.projection-pushdown-enabled` - Enable [projection pushdown](/optimizer/pushdown) - `true` From 3c1851d48220ebda0e6797746324a8c68294d929 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Fri, 28 Jul 2023 12:05:03 -0700 Subject: [PATCH 334/587] Remove VariableWidthBlockBuilder build entry --- .../listagg/GroupListaggAggregationState.java | 6 +++- .../SingleListaggAggregationState.java | 7 +++- .../java/io/trino/type/LikePatternType.java | 36 +++++++++++-------- .../execution/buffer/TestPagesSerde.java | 35 +++++++++--------- core/trino-spi/pom.xml | 8 +++++ .../spi/block/VariableWidthBlockBuilder.java | 19 ---------- 6 files changed, 58 insertions(+), 53 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/operator/aggregation/listagg/GroupListaggAggregationState.java b/core/trino-main/src/main/java/io/trino/operator/aggregation/listagg/GroupListaggAggregationState.java index 863c3987c51a..0da363839766 100644 --- a/core/trino-main/src/main/java/io/trino/operator/aggregation/listagg/GroupListaggAggregationState.java +++ b/core/trino-main/src/main/java/io/trino/operator/aggregation/listagg/GroupListaggAggregationState.java @@ -14,6 +14,7 @@ package io.trino.operator.aggregation.listagg; import com.google.common.primitives.Ints; +import io.airlift.slice.DynamicSliceOutput; import io.airlift.slice.SliceOutput; import io.trino.spi.block.ValueBlock; import io.trino.spi.block.VariableWidthBlockBuilder; @@ -40,6 +41,7 @@ public class GroupListaggAggregationState private static final VarHandle LONG_HANDLE = MethodHandles.byteArrayViewVarHandle(long[].class, LITTLE_ENDIAN); private final int recordNextIndexOffset; + private final DynamicSliceOutput out = new DynamicSliceOutput(0); private long[] groupHeadPositions = new long[0]; private long[] groupTailPositions = new long[0]; @@ -130,7 +132,9 @@ public void write(VariableWidthBlockBuilder blockBuilder) blockBuilder.appendNull(); return; } - blockBuilder.buildEntry(this::write); + out.reset(); + write(out); + blockBuilder.writeEntry(out.slice()); } private void write(SliceOutput out) diff --git a/core/trino-main/src/main/java/io/trino/operator/aggregation/listagg/SingleListaggAggregationState.java b/core/trino-main/src/main/java/io/trino/operator/aggregation/listagg/SingleListaggAggregationState.java index 2fcd62699907..892508fc51d1 100644 --- a/core/trino-main/src/main/java/io/trino/operator/aggregation/listagg/SingleListaggAggregationState.java +++ b/core/trino-main/src/main/java/io/trino/operator/aggregation/listagg/SingleListaggAggregationState.java @@ -13,6 +13,7 @@ */ package io.trino.operator.aggregation.listagg; +import io.airlift.slice.DynamicSliceOutput; import io.airlift.slice.SliceOutput; import io.trino.spi.block.SqlRow; import io.trino.spi.block.VariableWidthBlockBuilder; @@ -25,6 +26,8 @@ public class SingleListaggAggregationState extends AbstractListaggAggregationState { + private final DynamicSliceOutput out = new DynamicSliceOutput(0); + private SqlRow tempSerializedState; public SingleListaggAggregationState() @@ -46,7 +49,9 @@ public void write(VariableWidthBlockBuilder blockBuilder) blockBuilder.appendNull(); return; } - blockBuilder.buildEntry(this::writeNotGrouped); + out.reset(); + writeNotGrouped(out); + blockBuilder.writeEntry(out.slice()); } private void writeNotGrouped(SliceOutput out) diff --git a/core/trino-main/src/main/java/io/trino/type/LikePatternType.java b/core/trino-main/src/main/java/io/trino/type/LikePatternType.java index 180f9a33fa2a..c4e442b69919 100644 --- a/core/trino-main/src/main/java/io/trino/type/LikePatternType.java +++ b/core/trino-main/src/main/java/io/trino/type/LikePatternType.java @@ -14,6 +14,7 @@ package io.trino.type; import io.airlift.slice.Slice; +import io.airlift.slice.Slices; import io.trino.spi.block.Block; import io.trino.spi.block.BlockBuilder; import io.trino.spi.block.VariableWidthBlock; @@ -55,7 +56,7 @@ public Object getObject(Block block, int position) int valuePosition = block.getUnderlyingValuePosition(position); Slice slice = valueBlock.getSlice(valuePosition); - // layout is: ? + // layout is: ? int length = slice.getInt(0); String pattern = slice.toString(4, length, UTF_8); @@ -73,18 +74,25 @@ public Object getObject(Block block, int position) public void writeObject(BlockBuilder blockBuilder, Object value) { LikePattern likePattern = (LikePattern) value; - ((VariableWidthBlockBuilder) blockBuilder).buildEntry(valueWriter -> { - Slice pattern = utf8Slice(likePattern.getPattern()); - int length = pattern.length(); - valueWriter.writeInt(length); - valueWriter.writeBytes(pattern, 0, length); - if (likePattern.getEscape().isEmpty()) { - valueWriter.writeByte(0); - } - else { - valueWriter.writeByte(1); - valueWriter.writeInt(likePattern.getEscape().get()); - } - }); + Slice pattern = utf8Slice(likePattern.getPattern()); + + Slice slice = Slices.allocate( + Integer.BYTES + + pattern.length() + + Byte.BYTES + + (likePattern.getEscape().isPresent() ? Integer.BYTES : 0)); + + // layout is: ? + slice.setInt(0, pattern.length()); + slice.setBytes(4, pattern); + if (likePattern.getEscape().isEmpty()) { + slice.setByte(4 + pattern.length(), (byte) 0); + } + else { + slice.setByte(4 + pattern.length(), (byte) 1); + slice.setInt(4 + pattern.length() + 1, likePattern.getEscape().get()); + } + + ((VariableWidthBlockBuilder) blockBuilder).writeEntry(slice); } } diff --git a/core/trino-main/src/test/java/io/trino/execution/buffer/TestPagesSerde.java b/core/trino-main/src/test/java/io/trino/execution/buffer/TestPagesSerde.java index 5340489dc313..676c46f4b1b2 100644 --- a/core/trino-main/src/test/java/io/trino/execution/buffer/TestPagesSerde.java +++ b/core/trino-main/src/test/java/io/trino/execution/buffer/TestPagesSerde.java @@ -18,6 +18,7 @@ import io.airlift.slice.Slice; import io.airlift.slice.SliceInput; import io.airlift.slice.SliceOutput; +import io.airlift.slice.Slices; import io.trino.metadata.BlockEncodingManager; import io.trino.metadata.InternalBlockEncodingSerde; import io.trino.spi.Page; @@ -26,7 +27,6 @@ import io.trino.spi.block.BlockBuilder; import io.trino.spi.block.BlockEncodingSerde; import io.trino.spi.block.VariableWidthBlock; -import io.trino.spi.block.VariableWidthBlockBuilder; import io.trino.spi.type.Type; import io.trino.tpch.LineItem; import io.trino.tpch.LineItemGenerator; @@ -288,14 +288,13 @@ private void testDeserializationWithRollover(boolean encryptionEnabled, boolean private static Page createTestPage(int numberOfEntries) { - VariableWidthBlockBuilder blockBuilder = new VariableWidthBlockBuilder(null, 1, 1000); - blockBuilder.buildEntry(value -> { - value.writeInt(numberOfEntries); - for (int i = 0; i < numberOfEntries; i++) { - value.writeLong(i); - } - }); - return new Page(blockBuilder.build()); + Slice slice = Slices.allocate(Integer.BYTES + numberOfEntries * Long.BYTES); + SliceOutput out = slice.getOutput(); + out.writeInt(numberOfEntries); + for (int i = 0; i < numberOfEntries; i++) { + out.writeLong(i); + } + return new Page(new VariableWidthBlock(1, slice, new int[] {0, slice.length()}, Optional.empty())); } private static class RolloverBlockSerde @@ -305,15 +304,15 @@ private static class RolloverBlockSerde public Block readBlock(SliceInput input) { int numberOfEntries = input.readInt(); - VariableWidthBlockBuilder blockBuilder = new VariableWidthBlockBuilder(null, 1, 1000); - blockBuilder.buildEntry(value -> { - value.writeInt(numberOfEntries); - for (int i = 0; i < numberOfEntries; ++i) { - // read 8 bytes at a time - value.writeLong(input.readLong()); - } - }); - return blockBuilder.build(); + + Slice slice = Slices.allocate(Integer.BYTES + numberOfEntries * Long.BYTES); + SliceOutput out = slice.getOutput(); + out.writeInt(numberOfEntries); + for (int i = 0; i < numberOfEntries; ++i) { + // read 8 bytes at a time + out.writeLong(input.readLong()); + } + return new VariableWidthBlock(1, slice, new int[] {0, slice.length()}, Optional.empty()); } @Override diff --git a/core/trino-spi/pom.xml b/core/trino-spi/pom.xml index 1c0bb8249690..36a598fa6590 100644 --- a/core/trino-spi/pom.xml +++ b/core/trino-spi/pom.xml @@ -212,6 +212,14 @@ + + java.class.removed + interface io.trino.spi.block.VariableWidthBlockBuilder.VariableWidthEntryBuilder<E extends java.lang.Throwable> + + + java.method.removed + method <E extends java.lang.Throwable> void io.trino.spi.block.VariableWidthBlockBuilder::buildEntry(io.trino.spi.block.VariableWidthBlockBuilder.VariableWidthEntryBuilder<E>) throws E + diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/VariableWidthBlockBuilder.java b/core/trino-spi/src/main/java/io/trino/spi/block/VariableWidthBlockBuilder.java index 6ec063828cf1..a56e50d8d068 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/VariableWidthBlockBuilder.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/VariableWidthBlockBuilder.java @@ -113,25 +113,6 @@ public VariableWidthBlockBuilder writeEntry(byte[] source, int sourceIndex, int return this; } - public void buildEntry(VariableWidthEntryBuilder builder) - throws E - { - if (!initialized) { - initializeCapacity(); - } - - int start = sliceOutput.size(); - builder.build(sliceOutput); - int length = sliceOutput.size() - start; - entryAdded(length, false); - } - - public interface VariableWidthEntryBuilder - { - void build(SliceOutput output) - throws E; - } - @Override public BlockBuilder appendNull() { From bf854888e572d7376a68883d605f757f23cf6a74 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Fri, 28 Jul 2023 12:56:21 -0700 Subject: [PATCH 335/587] Simplify BlockUtil calculateNewArraySize and add optional minimumSize --- .../java/io/trino/spi/block/BlockUtil.java | 29 ++++++++++++------- .../io/trino/spi/block/TestBlockUtil.java | 27 +++++++++++------ 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/BlockUtil.java b/core/trino-spi/src/main/java/io/trino/spi/block/BlockUtil.java index e17b4ab28ef4..0f074c5f4310 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/BlockUtil.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/BlockUtil.java @@ -28,7 +28,8 @@ final class BlockUtil private static final int DEFAULT_CAPACITY = 64; // See java.util.ArrayList for an explanation - static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + // Two additional positions are reserved for a spare null position and offset position + static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 - 2; private BlockUtil() { @@ -78,19 +79,25 @@ static void checkReadablePosition(Block block, int position) static int calculateNewArraySize(int currentSize) { - // grow array by 50% - long newSize = (long) currentSize + (currentSize >> 1); + return calculateNewArraySize(currentSize, DEFAULT_CAPACITY); + } - // verify new size is within reasonable bounds - if (newSize < DEFAULT_CAPACITY) { - newSize = DEFAULT_CAPACITY; + static int calculateNewArraySize(int currentSize, int minimumSize) + { + if (currentSize < 0 || currentSize > MAX_ARRAY_SIZE || minimumSize < 0 || minimumSize > MAX_ARRAY_SIZE) { + throw new IllegalArgumentException("Invalid currentSize or minimumSize"); } - else if (newSize > MAX_ARRAY_SIZE) { - newSize = MAX_ARRAY_SIZE; - if (newSize == currentSize) { - throw new IllegalArgumentException(format("Cannot grow array beyond '%s'", MAX_ARRAY_SIZE)); - } + if (currentSize == MAX_ARRAY_SIZE) { + throw new IllegalArgumentException("Cannot grow array beyond size " + MAX_ARRAY_SIZE); } + + minimumSize = Math.max(minimumSize, DEFAULT_CAPACITY); + + // grow the array by 50% if possible + long newSize = (long) currentSize + (currentSize >> 1); + + // ensure new size is within bounds + newSize = Math.min(Math.max(newSize, minimumSize), MAX_ARRAY_SIZE); return (int) newSize; } diff --git a/core/trino-spi/src/test/java/io/trino/spi/block/TestBlockUtil.java b/core/trino-spi/src/test/java/io/trino/spi/block/TestBlockUtil.java index a58582cdb617..496dc2590d53 100644 --- a/core/trino-spi/src/test/java/io/trino/spi/block/TestBlockUtil.java +++ b/core/trino-spi/src/test/java/io/trino/spi/block/TestBlockUtil.java @@ -16,21 +16,30 @@ import org.junit.jupiter.api.Test; import static io.trino.spi.block.BlockUtil.MAX_ARRAY_SIZE; -import static java.lang.String.format; +import static io.trino.spi.block.BlockUtil.calculateNewArraySize; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; public class TestBlockUtil { @Test public void testCalculateNewArraySize() { - assertThat(BlockUtil.calculateNewArraySize(200)).isEqualTo(300); - assertThat(BlockUtil.calculateNewArraySize(Integer.MAX_VALUE)).isEqualTo(MAX_ARRAY_SIZE); - try { - BlockUtil.calculateNewArraySize(MAX_ARRAY_SIZE); - } - catch (IllegalArgumentException e) { - assertThat(e.getMessage()).isEqualTo(format("Cannot grow array beyond '%s'", MAX_ARRAY_SIZE)); - } + assertThat(calculateNewArraySize(200)).isEqualTo(300); + assertThat(calculateNewArraySize(200, 10)).isEqualTo(300); + assertThat(calculateNewArraySize(200, 500)).isEqualTo(500); + + assertThat(calculateNewArraySize(MAX_ARRAY_SIZE - 1)).isEqualTo(MAX_ARRAY_SIZE); + assertThat(calculateNewArraySize(10, MAX_ARRAY_SIZE)).isEqualTo(MAX_ARRAY_SIZE); + + assertThat(calculateNewArraySize(1, 0)).isEqualTo(64); + + assertThatThrownBy(() -> calculateNewArraySize(Integer.MAX_VALUE)) + .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> calculateNewArraySize(0, Integer.MAX_VALUE)) + .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> calculateNewArraySize(MAX_ARRAY_SIZE)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Cannot grow array beyond size %d".formatted(MAX_ARRAY_SIZE)); } } From d2b11f0aaddeb6bd0b45a085139c29167c4d9f70 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Fri, 28 Jul 2023 12:57:00 -0700 Subject: [PATCH 336/587] Change VariableWidthBlockBuilder to manage raw byte array internally --- .../spi/block/VariableWidthBlockBuilder.java | 80 +++++++++---------- .../block/TestVariableWidthBlockBuilder.java | 9 +-- 2 files changed, 39 insertions(+), 50 deletions(-) diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/VariableWidthBlockBuilder.java b/core/trino-spi/src/main/java/io/trino/spi/block/VariableWidthBlockBuilder.java index a56e50d8d068..ba53ef75c57c 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/VariableWidthBlockBuilder.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/VariableWidthBlockBuilder.java @@ -13,9 +13,8 @@ */ package io.trino.spi.block; -import io.airlift.slice.DynamicSliceOutput; import io.airlift.slice.Slice; -import io.airlift.slice.SliceOutput; +import io.airlift.slice.Slices; import jakarta.annotation.Nullable; import java.util.Arrays; @@ -27,21 +26,23 @@ import static io.airlift.slice.Slices.EMPTY_SLICE; import static io.trino.spi.block.BlockUtil.MAX_ARRAY_SIZE; import static io.trino.spi.block.BlockUtil.calculateBlockResetBytes; +import static io.trino.spi.block.BlockUtil.calculateNewArraySize; +import static java.lang.Math.max; import static java.lang.Math.min; public class VariableWidthBlockBuilder implements BlockBuilder { private static final int INSTANCE_SIZE = instanceSize(VariableWidthBlockBuilder.class); - private static final Block NULL_VALUE_BLOCK = new VariableWidthBlock(0, 1, EMPTY_SLICE, new int[] {0, 0}, new boolean[] {true}); + private static final Block NULL_VALUE_BLOCK = new VariableWidthBlock(0, 1, EMPTY_SLICE, new int[]{0, 0}, new boolean[]{true}); + private static final int SIZE_IN_BYTES_PER_POSITION = Integer.BYTES + Byte.BYTES; private final BlockBuilderStatus blockBuilderStatus; - private boolean initialized; private final int initialEntryCount; private final int initialSliceOutputSize; - private SliceOutput sliceOutput = new DynamicSliceOutput(0); + private byte[] bytes = new byte[0]; private boolean hasNullValue; private boolean hasNonNullValue; @@ -49,7 +50,7 @@ public class VariableWidthBlockBuilder private boolean[] valueIsNull = new boolean[0]; private int[] offsets = new int[1]; - private int positions; + private int positionCount; private long arraysRetainedSizeInBytes; @@ -60,26 +61,25 @@ public VariableWidthBlockBuilder(@Nullable BlockBuilderStatus blockBuilderStatus initialEntryCount = expectedEntries; initialSliceOutputSize = min(expectedBytes, MAX_ARRAY_SIZE); - updateArraysDataSize(); + updateRetainedSize(); } @Override public int getPositionCount() { - return positions; + return positionCount; } @Override public long getSizeInBytes() { - long arraysSizeInBytes = (Integer.BYTES + Byte.BYTES) * (long) positions; - return sliceOutput.size() + arraysSizeInBytes; + return offsets[positionCount] + SIZE_IN_BYTES_PER_POSITION * (long) positionCount; } @Override public long getRetainedSizeInBytes() { - long size = INSTANCE_SIZE + sliceOutput.getRetainedSize() + arraysRetainedSizeInBytes; + long size = INSTANCE_SIZE + sizeOf(bytes) + arraysRetainedSizeInBytes; if (blockBuilderStatus != null) { size += BlockBuilderStatus.INSTANCE_SIZE; } @@ -93,22 +93,16 @@ public VariableWidthBlockBuilder writeEntry(Slice source) public VariableWidthBlockBuilder writeEntry(Slice source, int sourceIndex, int length) { - if (!initialized) { - initializeCapacity(); - } - - sliceOutput.writeBytes(source, sourceIndex, length); + ensureFreeSpace(length); + source.getBytes(sourceIndex, bytes, offsets[positionCount], length); entryAdded(length, false); return this; } public VariableWidthBlockBuilder writeEntry(byte[] source, int sourceIndex, int length) { - if (!initialized) { - initializeCapacity(); - } - - sliceOutput.writeBytes(source, sourceIndex, length); + ensureFreeSpace(length); + System.arraycopy(source, sourceIndex, bytes, offsets[positionCount], length); entryAdded(length, false); return this; } @@ -123,17 +117,14 @@ public BlockBuilder appendNull() private void entryAdded(int bytesWritten, boolean isNull) { - if (!initialized) { - initializeCapacity(); - } - if (valueIsNull.length <= positions) { + if (valueIsNull.length <= positionCount) { growCapacity(); } - valueIsNull[positions] = isNull; - offsets[positions + 1] = sliceOutput.size(); + valueIsNull[positionCount] = isNull; + offsets[positionCount + 1] = offsets[positionCount] + bytesWritten; - positions++; + positionCount++; hasNonNullValue |= !isNull; if (blockBuilderStatus != null) { blockBuilderStatus.addBytes(SIZE_OF_BYTE + SIZE_OF_INT + bytesWritten); @@ -142,25 +133,26 @@ private void entryAdded(int bytesWritten, boolean isNull) private void growCapacity() { - int newSize = BlockUtil.calculateNewArraySize(valueIsNull.length); + int newSize = calculateNewArraySize(valueIsNull.length, initialEntryCount); valueIsNull = Arrays.copyOf(valueIsNull, newSize); offsets = Arrays.copyOf(offsets, newSize + 1); - updateArraysDataSize(); + updateRetainedSize(); } - private void initializeCapacity() + private void ensureFreeSpace(int extraBytesCapacity) { - if (positions != 0) { - throw new IllegalStateException(getClass().getSimpleName() + " was used before initialization"); + int requiredSize = offsets[positionCount] + extraBytesCapacity; + if (bytes.length < requiredSize) { + int newBytesLength = max(bytes.length, initialSliceOutputSize); + if (requiredSize > newBytesLength) { + newBytesLength = max(requiredSize, calculateNewArraySize(newBytesLength)); + } + bytes = Arrays.copyOf(bytes, newBytesLength); + updateRetainedSize(); } - initialized = true; - valueIsNull = new boolean[initialEntryCount]; - offsets = new int[initialEntryCount + 1]; - sliceOutput = new DynamicSliceOutput(initialSliceOutputSize); - updateArraysDataSize(); } - private void updateArraysDataSize() + private void updateRetainedSize() { arraysRetainedSizeInBytes = sizeOf(valueIsNull) + sizeOf(offsets); } @@ -169,7 +161,7 @@ private void updateArraysDataSize() public Block build() { if (!hasNonNullValue) { - return RunLengthEncodedBlock.create(NULL_VALUE_BLOCK, positions); + return RunLengthEncodedBlock.create(NULL_VALUE_BLOCK, positionCount); } return buildValueBlock(); } @@ -177,13 +169,13 @@ public Block build() @Override public VariableWidthBlock buildValueBlock() { - return new VariableWidthBlock(0, positions, sliceOutput.slice(), offsets, hasNullValue ? valueIsNull : null); + return new VariableWidthBlock(0, positionCount, Slices.wrappedBuffer(bytes, 0, offsets[positionCount]), offsets, hasNullValue ? valueIsNull : null); } @Override public BlockBuilder newBlockBuilderLike(int expectedEntries, BlockBuilderStatus blockBuilderStatus) { - int currentSizeInBytes = positions == 0 ? positions : (getOffset(positions) - getOffset(0)); + int currentSizeInBytes = positionCount == 0 ? positionCount : (getOffset(positionCount) - getOffset(0)); return new VariableWidthBlockBuilder(blockBuilderStatus, expectedEntries, calculateBlockResetBytes(currentSizeInBytes)); } @@ -196,8 +188,8 @@ private int getOffset(int position) public String toString() { StringBuilder sb = new StringBuilder("VariableWidthBlockBuilder{"); - sb.append("positionCount=").append(positions); - sb.append(", size=").append(sliceOutput.size()); + sb.append("positionCount=").append(positionCount); + sb.append(", size=").append(offsets[positionCount]); sb.append('}'); return sb.toString(); } diff --git a/core/trino-spi/src/test/java/io/trino/spi/block/TestVariableWidthBlockBuilder.java b/core/trino-spi/src/test/java/io/trino/spi/block/TestVariableWidthBlockBuilder.java index f41eaf4fc22d..881840b351cd 100644 --- a/core/trino-spi/src/test/java/io/trino/spi/block/TestVariableWidthBlockBuilder.java +++ b/core/trino-spi/src/test/java/io/trino/spi/block/TestVariableWidthBlockBuilder.java @@ -13,8 +13,6 @@ */ package io.trino.spi.block; -import io.airlift.slice.DynamicSliceOutput; -import io.airlift.slice.Slice; import io.airlift.slice.Slices; import org.junit.jupiter.api.Test; @@ -28,7 +26,6 @@ public class TestVariableWidthBlockBuilder { private static final int BLOCK_BUILDER_INSTANCE_SIZE = instanceSize(VariableWidthBlockBuilder.class); - private static final int SLICE_INSTANCE_SIZE = instanceSize(DynamicSliceOutput.class) + instanceSize(Slice.class); private static final int VARCHAR_VALUE_SIZE = 7; private static final int VARCHAR_ENTRY_SIZE = SIZE_OF_INT + VARCHAR_VALUE_SIZE; private static final int EXPECTED_ENTRY_COUNT = 3; @@ -52,9 +49,9 @@ public void testNewBlockBuilderLike() // force to initialize capacity blockBuilder.writeEntry(Slices.wrappedBuffer((byte) 1)); - long actualArrayBytes = sizeOf(new int[(int) ceil(resetSkew * (entries + 1))]) + sizeOf(new boolean[(int) ceil(resetSkew * entries)]); - long actualSliceBytes = SLICE_INSTANCE_SIZE + sizeOf(new byte[(int) ceil(resetSkew * entries)]); - assertThat(blockBuilder.getRetainedSizeInBytes()).isEqualTo(BLOCK_BUILDER_INSTANCE_SIZE + actualSliceBytes + actualArrayBytes); + long actualArraySize = sizeOf(new int[(int) ceil(resetSkew * (entries + 1))]) + sizeOf(new boolean[(int) ceil(resetSkew * entries)]); + long actualBytesSize = sizeOf(new byte[(int) ceil(resetSkew * entries)]); + assertThat(blockBuilder.getRetainedSizeInBytes()).isEqualTo(BLOCK_BUILDER_INSTANCE_SIZE + actualBytesSize + actualArraySize); } private void testIsFull(PageBuilderStatus pageBuilderStatus) From 1f83b9dd8941a5ea34070ef934d598aba5bf2041 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Fri, 28 Jul 2023 11:00:59 -0700 Subject: [PATCH 337/587] Add bulk append methods to BlockBuilder --- .../output/TypedPositionsAppender.java | 20 +- core/trino-spi/pom.xml | 27 ++ .../java/io/trino/spi/block/ArrayBlock.java | 5 + .../io/trino/spi/block/ArrayBlockBuilder.java | 131 ++++++-- .../java/io/trino/spi/block/BlockBuilder.java | 20 ++ .../java/io/trino/spi/block/BlockUtil.java | 17 ++ .../io/trino/spi/block/ByteArrayBlock.java | 15 + .../spi/block/ByteArrayBlockBuilder.java | 161 +++++++++- .../java/io/trino/spi/block/Fixed12Block.java | 8 +- .../trino/spi/block/Fixed12BlockBuilder.java | 188 +++++++++++- .../trino/spi/block/Fixed12BlockEncoding.java | 2 +- .../io/trino/spi/block/Int128ArrayBlock.java | 14 +- .../spi/block/Int128ArrayBlockBuilder.java | 182 ++++++++++- .../spi/block/Int128ArrayBlockEncoding.java | 2 +- .../io/trino/spi/block/IntArrayBlock.java | 5 + .../trino/spi/block/IntArrayBlockBuilder.java | 160 +++++++++- .../io/trino/spi/block/LongArrayBlock.java | 9 +- .../spi/block/LongArrayBlockBuilder.java | 160 +++++++++- .../io/trino/spi/block/MapBlockBuilder.java | 82 +++++ .../java/io/trino/spi/block/RowBlock.java | 5 + .../io/trino/spi/block/RowBlockBuilder.java | 215 ++++++++++++- .../io/trino/spi/block/ShortArrayBlock.java | 5 + .../spi/block/ShortArrayBlockBuilder.java | 162 +++++++++- .../trino/spi/block/VariableWidthBlock.java | 15 + .../spi/block/VariableWidthBlockBuilder.java | 283 ++++++++++++++++-- .../spi/block/AbstractTestBlockBuilder.java | 236 +++++++++++++++ .../spi/block/TestArrayBlockBuilder.java | 79 +++++ .../spi/block/TestByteArrayBlockBuilder.java | 70 +++++ .../spi/block/TestFixed12BlockBuilder.java | 72 +++++ .../block/TestInt128ArrayBlockBuilder.java | 72 +++++ .../spi/block/TestIntArrayBlockBuilder.java | 70 +++++ .../spi/block/TestLongArrayBlockBuilder.java | 70 +++++ .../trino/spi/block/TestMapBlockBuilder.java | 101 +++++++ .../trino/spi/block/TestRowBlockBuilder.java | 76 +++++ .../spi/block/TestShortArrayBlockBuilder.java | 70 +++++ .../block/TestVariableWidthBlockBuilder.java | 175 +++++++++++ 36 files changed, 2842 insertions(+), 142 deletions(-) create mode 100644 core/trino-spi/src/test/java/io/trino/spi/block/AbstractTestBlockBuilder.java create mode 100644 core/trino-spi/src/test/java/io/trino/spi/block/TestByteArrayBlockBuilder.java create mode 100644 core/trino-spi/src/test/java/io/trino/spi/block/TestFixed12BlockBuilder.java create mode 100644 core/trino-spi/src/test/java/io/trino/spi/block/TestInt128ArrayBlockBuilder.java create mode 100644 core/trino-spi/src/test/java/io/trino/spi/block/TestIntArrayBlockBuilder.java create mode 100644 core/trino-spi/src/test/java/io/trino/spi/block/TestLongArrayBlockBuilder.java create mode 100644 core/trino-spi/src/test/java/io/trino/spi/block/TestMapBlockBuilder.java create mode 100644 core/trino-spi/src/test/java/io/trino/spi/block/TestShortArrayBlockBuilder.java diff --git a/core/trino-main/src/main/java/io/trino/operator/output/TypedPositionsAppender.java b/core/trino-main/src/main/java/io/trino/operator/output/TypedPositionsAppender.java index 2fcc0fda3b52..eb26ff3241b5 100644 --- a/core/trino-main/src/main/java/io/trino/operator/output/TypedPositionsAppender.java +++ b/core/trino-main/src/main/java/io/trino/operator/output/TypedPositionsAppender.java @@ -20,43 +20,35 @@ import it.unimi.dsi.fastutil.ints.IntArrayList; import static io.airlift.slice.SizeOf.instanceSize; -import static java.util.Objects.requireNonNull; class TypedPositionsAppender implements PositionsAppender { private static final int INSTANCE_SIZE = instanceSize(TypedPositionsAppender.class); - private final Type type; private BlockBuilder blockBuilder; TypedPositionsAppender(Type type, int expectedPositions) { - this.type = requireNonNull(type, "type is null"); this.blockBuilder = type.createBlockBuilder(null, expectedPositions); } @Override - public void append(IntArrayList positions, ValueBlock source) + public void append(IntArrayList positions, ValueBlock block) { - int[] positionArray = positions.elements(); - for (int i = 0; i < positions.size(); i++) { - type.appendTo(source, positionArray[i], blockBuilder); - } + blockBuilder.appendPositions(block, positions.elements(), 0, positions.size()); } @Override - public void appendRle(ValueBlock block, int rlePositionCount) + public void appendRle(ValueBlock block, int count) { - for (int i = 0; i < rlePositionCount; i++) { - type.appendTo(block, 0, blockBuilder); - } + blockBuilder.appendRepeated(block, 0, count); } @Override - public void append(int position, ValueBlock source) + public void append(int position, ValueBlock block) { - type.appendTo(source, position, blockBuilder); + blockBuilder.append(block, position); } @Override diff --git a/core/trino-spi/pom.xml b/core/trino-spi/pom.xml index 36a598fa6590..eb497d8c8ffc 100644 --- a/core/trino-spi/pom.xml +++ b/core/trino-spi/pom.xml @@ -220,6 +220,33 @@ java.method.removed method <E extends java.lang.Throwable> void io.trino.spi.block.VariableWidthBlockBuilder::buildEntry(io.trino.spi.block.VariableWidthBlockBuilder.VariableWidthEntryBuilder<E>) throws E + + true + java.method.addedToInterface + method void io.trino.spi.block.BlockBuilder::append(io.trino.spi.block.ValueBlock, int) + + + java.method.addedToInterface + method void io.trino.spi.block.BlockBuilder::appendPositions(io.trino.spi.block.ValueBlock, int[], int, int) + + + java.method.addedToInterface + method void io.trino.spi.block.BlockBuilder::appendRange(io.trino.spi.block.ValueBlock, int, int) + + + java.method.addedToInterface + method void io.trino.spi.block.BlockBuilder::appendRepeated(io.trino.spi.block.ValueBlock, int, int) + + + java.method.returnTypeChangedCovariantly + method io.trino.spi.block.BlockBuilder io.trino.spi.block.ShortArrayBlockBuilder::appendNull() + method io.trino.spi.block.ShortArrayBlockBuilder io.trino.spi.block.ShortArrayBlockBuilder::appendNull() + + + java.method.returnTypeChangedCovariantly + method io.trino.spi.block.BlockBuilder io.trino.spi.block.ShortArrayBlockBuilder::writeShort(short) + method io.trino.spi.block.ShortArrayBlockBuilder io.trino.spi.block.ShortArrayBlockBuilder::writeShort(short) + diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/ArrayBlock.java b/core/trino-spi/src/main/java/io/trino/spi/block/ArrayBlock.java index 9aad1d9da67e..ee7ab4f5bc85 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/ArrayBlock.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/ArrayBlock.java @@ -177,6 +177,11 @@ int[] getOffsets() return offsets; } + boolean[] getRawValueIsNull() + { + return valueIsNull; + } + int getOffsetBase() { return arrayOffset; diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/ArrayBlockBuilder.java b/core/trino-spi/src/main/java/io/trino/spi/block/ArrayBlockBuilder.java index df28648003e7..4e5baa0c5bbd 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/ArrayBlockBuilder.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/ArrayBlockBuilder.java @@ -21,6 +21,8 @@ import static io.airlift.slice.SizeOf.instanceSize; import static io.airlift.slice.SizeOf.sizeOf; import static io.trino.spi.block.ArrayBlock.createArrayBlockInternal; +import static io.trino.spi.block.BlockUtil.appendRawBlockRange; +import static io.trino.spi.block.BlockUtil.calculateNewArraySize; import static java.lang.Math.max; import static java.util.Objects.requireNonNull; @@ -28,6 +30,7 @@ public class ArrayBlockBuilder implements BlockBuilder { private static final int INSTANCE_SIZE = instanceSize(ArrayBlockBuilder.class); + private static final int SIZE_IN_BYTES_PER_POSITION = Integer.BYTES + Byte.BYTES; private int positionCount; @@ -39,7 +42,7 @@ public class ArrayBlockBuilder private int[] offsets = new int[1]; private boolean[] valueIsNull = new boolean[0]; private boolean hasNullValue; - private boolean hasNonNullRow; + private boolean hasNonNullValue; private final BlockBuilder values; private boolean currentEntryOpened; @@ -82,7 +85,7 @@ private ArrayBlockBuilder(@Nullable BlockBuilderStatus blockBuilderStatus, Block this.values = requireNonNull(values, "values is null"); this.initialEntryCount = max(expectedEntries, 1); - updateDataSize(); + updateRetainedSize(); } @Override @@ -94,7 +97,7 @@ public int getPositionCount() @Override public long getSizeInBytes() { - return values.getSizeInBytes() + ((Integer.BYTES + Byte.BYTES) * (long) positionCount); + return values.getSizeInBytes() + (SIZE_IN_BYTES_PER_POSITION * (long) positionCount); } @Override @@ -116,6 +119,97 @@ public void buildEntry(ArrayValueBuilder builder) currentEntryOpened = false; } + @Override + public void append(ValueBlock block, int position) + { + if (currentEntryOpened) { + throw new IllegalStateException("Current entry must be closed before a null can be written"); + } + + ArrayBlock arrayBlock = (ArrayBlock) block; + if (block.isNull(position)) { + entryAdded(true); + return; + } + + int offsetBase = arrayBlock.getOffsetBase(); + int[] offsets = arrayBlock.getOffsets(); + int startOffset = offsets[offsetBase + position]; + int length = offsets[offsetBase + position + 1] - startOffset; + + appendRawBlockRange(arrayBlock.getRawElementBlock(), startOffset, length, values); + entryAdded(false); + } + + @Override + public void appendRepeated(ValueBlock block, int position, int count) + { + if (currentEntryOpened) { + throw new IllegalStateException("Current entry must be closed before a null can be written"); + } + for (int i = 0; i < count; i++) { + append(block, position); + } + } + + @Override + public void appendRange(ValueBlock block, int offset, int length) + { + if (currentEntryOpened) { + throw new IllegalStateException("Current entry must be closed before a null can be written"); + } + + ensureCapacity(positionCount + length); + + ArrayBlock arrayBlock = (ArrayBlock) block; + + int rawOffsetBase = arrayBlock.getOffsetBase(); + int[] rawOffsets = arrayBlock.getOffsets(); + int startOffset = rawOffsets[rawOffsetBase + offset]; + int endOffset = rawOffsets[rawOffsetBase + offset + length]; + + appendRawBlockRange(arrayBlock.getRawElementBlock(), startOffset, endOffset - startOffset, values); + + // update offsets for copied data + for (int i = 0; i < length; i++) { + int entrySize = rawOffsets[rawOffsetBase + offset + i + 1] - rawOffsets[rawOffsetBase + offset + i]; + offsets[positionCount + i + 1] = offsets[positionCount + i] + entrySize; + } + + boolean[] rawValueIsNull = arrayBlock.getRawValueIsNull(); + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + if (rawValueIsNull[rawOffsetBase + offset + i]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + hasNonNullValue = true; + } + } + } + else { + hasNonNullValue = true; + } + + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendPositions(ValueBlock block, int[] positions, int offset, int length) + { + if (currentEntryOpened) { + throw new IllegalStateException("Current entry must be closed before a null can be written"); + } + for (int i = 0; i < length; i++) { + append(block, positions[offset + i]); + } + } + @Override public BlockBuilder appendNull() { @@ -129,37 +223,41 @@ public BlockBuilder appendNull() private void entryAdded(boolean isNull) { - if (valueIsNull.length <= positionCount) { - growCapacity(); - } + ensureCapacity(positionCount + 1); + offsets[positionCount + 1] = values.getPositionCount(); valueIsNull[positionCount] = isNull; hasNullValue |= isNull; - hasNonNullRow |= !isNull; + hasNonNullValue |= !isNull; positionCount++; if (blockBuilderStatus != null) { - blockBuilderStatus.addBytes(Integer.BYTES + Byte.BYTES); + blockBuilderStatus.addBytes(SIZE_IN_BYTES_PER_POSITION); } } - private void growCapacity() + private void ensureCapacity(int capacity) { + if (valueIsNull.length >= capacity) { + return; + } + int newSize; if (initialized) { - newSize = BlockUtil.calculateNewArraySize(valueIsNull.length); + newSize = calculateNewArraySize(capacity); } else { newSize = initialEntryCount; initialized = true; } + newSize = max(newSize, capacity); valueIsNull = Arrays.copyOf(valueIsNull, newSize); offsets = Arrays.copyOf(offsets, newSize + 1); - updateDataSize(); + updateRetainedSize(); } - private void updateDataSize() + private void updateRetainedSize() { retainedSizeInBytes = INSTANCE_SIZE + sizeOf(valueIsNull) + sizeOf(offsets); if (blockBuilderStatus != null) { @@ -173,7 +271,7 @@ public Block build() if (currentEntryOpened) { throw new IllegalStateException("Current entry must be closed before the block can be built"); } - if (!hasNonNullRow) { + if (!hasNonNullValue) { return nullRle(positionCount); } return buildValueBlock(); @@ -197,10 +295,9 @@ public BlockBuilder newBlockBuilderLike(int expectedEntries, BlockBuilderStatus @Override public String toString() { - StringBuilder sb = new StringBuilder("ArrayBlockBuilder{"); - sb.append("positionCount=").append(getPositionCount()); - sb.append('}'); - return sb.toString(); + return "ArrayBlockBuilder{" + + "positionCount=" + getPositionCount() + + '}'; } private Block nullRle(int positionCount) diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/BlockBuilder.java b/core/trino-spi/src/main/java/io/trino/spi/block/BlockBuilder.java index 7d458991497e..97df9b75b235 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/BlockBuilder.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/BlockBuilder.java @@ -36,6 +36,26 @@ public interface BlockBuilder */ long getRetainedSizeInBytes(); + /** + * Append the specified value. + */ + void append(ValueBlock block, int position); + + /** + * Append the specified value multiple times. + */ + void appendRepeated(ValueBlock block, int position, int count); + + /** + * Append the values in the specified range. + */ + void appendRange(ValueBlock block, int offset, int length); + + /** + * Append the values at the specified positions. + */ + void appendPositions(ValueBlock block, int[] positions, int offset, int length); + /** * Appends a null value to the block. */ diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/BlockUtil.java b/core/trino-spi/src/main/java/io/trino/spi/block/BlockUtil.java index 0f074c5f4310..06af44be190e 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/BlockUtil.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/BlockUtil.java @@ -357,4 +357,21 @@ else if (buffer.length < capacity) { return buffer; } + + static void appendRawBlockRange(Block rawBlock, int offset, int length, BlockBuilder blockBuilder) + { + rawBlock = rawBlock.getLoadedBlock(); + if (rawBlock instanceof RunLengthEncodedBlock rleBlock) { + blockBuilder.appendRepeated(rleBlock.getValue(), 0, length); + } + else if (rawBlock instanceof DictionaryBlock dictionaryBlock) { + blockBuilder.appendPositions(dictionaryBlock.getDictionary(), dictionaryBlock.getRawIds(), offset, length); + } + else if (rawBlock instanceof ValueBlock valueBlock) { + blockBuilder.appendRange(valueBlock, offset, length); + } + else { + throw new IllegalArgumentException("Unsupported block type " + rawBlock.getClass().getSimpleName()); + } + } } diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/ByteArrayBlock.java b/core/trino-spi/src/main/java/io/trino/spi/block/ByteArrayBlock.java index 744c6753445c..e5fa09d77611 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/ByteArrayBlock.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/ByteArrayBlock.java @@ -242,4 +242,19 @@ Slice getValuesSlice() { return Slices.wrappedBuffer(values, arrayOffset, positionCount); } + + int getRawValuesOffset() + { + return arrayOffset; + } + + boolean[] getRawValueIsNull() + { + return valueIsNull; + } + + byte[] getRawValues() + { + return values; + } } diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/ByteArrayBlockBuilder.java b/core/trino-spi/src/main/java/io/trino/spi/block/ByteArrayBlockBuilder.java index 559ead304ead..ef9971ca5253 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/ByteArrayBlockBuilder.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/ByteArrayBlockBuilder.java @@ -19,6 +19,7 @@ import static io.airlift.slice.SizeOf.instanceSize; import static io.airlift.slice.SizeOf.sizeOf; +import static io.trino.spi.block.BlockUtil.calculateNewArraySize; import static java.lang.Math.max; public class ByteArrayBlockBuilder @@ -47,14 +48,12 @@ public ByteArrayBlockBuilder(@Nullable BlockBuilderStatus blockBuilderStatus, in this.blockBuilderStatus = blockBuilderStatus; this.initialEntryCount = max(expectedEntries, 1); - updateDataSize(); + updateRetainedSize(); } public BlockBuilder writeByte(byte value) { - if (values.length <= positionCount) { - growCapacity(); - } + ensureCapacity(positionCount + 1); values[positionCount] = value; @@ -67,12 +66,147 @@ public BlockBuilder writeByte(byte value) } @Override - public BlockBuilder appendNull() + public void append(ValueBlock block, int position) + { + ensureCapacity(positionCount + 1); + + ByteArrayBlock byteArrayBlock = (ByteArrayBlock) block; + if (byteArrayBlock.isNull(position)) { + valueIsNull[positionCount] = true; + hasNullValue = true; + } + else { + values[positionCount] = byteArrayBlock.getByte(position); + hasNonNullValue = true; + } + positionCount++; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(ByteArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendRepeated(ValueBlock block, int position, int count) + { + if (count == 0) { + return; + } + if (count == 1) { + append(block, position); + return; + } + + ensureCapacity(positionCount + count); + + ByteArrayBlock byteArrayBlock = (ByteArrayBlock) block; + + if (byteArrayBlock.isNull(position)) { + Arrays.fill(valueIsNull, positionCount, positionCount + count, true); + hasNullValue = true; + } + else { + byte value = byteArrayBlock.getByte(position); + Arrays.fill(values, positionCount, positionCount + count, value); + hasNonNullValue = true; + } + positionCount += count; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(count * ByteArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendRange(ValueBlock block, int offset, int length) + { + if (length == 0) { + return; + } + if (length == 1) { + append(block, offset); + return; + } + + ensureCapacity(positionCount + length); + + ByteArrayBlock byteArrayBlock = (ByteArrayBlock) block; + int rawOffset = byteArrayBlock.getRawValuesOffset(); + + byte[] rawValues = byteArrayBlock.getRawValues(); + System.arraycopy(rawValues, rawOffset + offset, values, positionCount, length); + + boolean[] rawValueIsNull = byteArrayBlock.getRawValueIsNull(); + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + if (rawValueIsNull[rawOffset + offset + i]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + hasNonNullValue = true; + } + } + } + else { + hasNonNullValue = true; + } + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * ByteArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendPositions(ValueBlock block, int[] positions, int offset, int length) { - if (values.length <= positionCount) { - growCapacity(); + if (length == 0) { + return; + } + if (length == 1) { + append(block, positions[offset]); + return; } + ensureCapacity(positionCount + length); + + ByteArrayBlock byteArrayBlock = (ByteArrayBlock) block; + int rawOffset = byteArrayBlock.getRawValuesOffset(); + byte[] rawValues = byteArrayBlock.getRawValues(); + boolean[] rawValueIsNull = byteArrayBlock.getRawValueIsNull(); + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + int rawPosition = positions[offset + i] + rawOffset; + if (rawValueIsNull[rawPosition]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + values[positionCount + i] = rawValues[rawPosition]; + hasNonNullValue = true; + } + } + } + else { + for (int i = 0; i < length; i++) { + int rawPosition = positions[offset + i] + rawOffset; + values[positionCount + i] = rawValues[rawPosition]; + } + hasNonNullValue = true; + } + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * ByteArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public BlockBuilder appendNull() + { + ensureCapacity(positionCount + 1); + valueIsNull[positionCount] = true; hasNullValue = true; @@ -104,23 +238,28 @@ public BlockBuilder newBlockBuilderLike(int expectedEntries, BlockBuilderStatus return new ByteArrayBlockBuilder(blockBuilderStatus, expectedEntries); } - private void growCapacity() + private void ensureCapacity(int capacity) { + if (values.length >= capacity) { + return; + } + int newSize; if (initialized) { - newSize = BlockUtil.calculateNewArraySize(values.length); + newSize = calculateNewArraySize(capacity); } else { newSize = initialEntryCount; initialized = true; } + newSize = max(newSize, capacity); valueIsNull = Arrays.copyOf(valueIsNull, newSize); values = Arrays.copyOf(values, newSize); - updateDataSize(); + updateRetainedSize(); } - private void updateDataSize() + private void updateRetainedSize() { retainedSizeInBytes = INSTANCE_SIZE + sizeOf(valueIsNull) + sizeOf(values); if (blockBuilderStatus != null) { diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/Fixed12Block.java b/core/trino-spi/src/main/java/io/trino/spi/block/Fixed12Block.java index f38e059ad2f6..a8694f321806 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/Fixed12Block.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/Fixed12Block.java @@ -294,11 +294,17 @@ public static int decodeFixed12Second(int[] values, int position) return values[offset + 2]; } - int getPositionOffset() + int getRawOffset() { return positionOffset; } + @Nullable + boolean[] getRawValueIsNull() + { + return valueIsNull; + } + int[] getRawValues() { return values; diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/Fixed12BlockBuilder.java b/core/trino-spi/src/main/java/io/trino/spi/block/Fixed12BlockBuilder.java index f0d9e278510f..3a30f6fca05c 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/Fixed12BlockBuilder.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/Fixed12BlockBuilder.java @@ -19,6 +19,7 @@ import static io.airlift.slice.SizeOf.instanceSize; import static io.airlift.slice.SizeOf.sizeOf; +import static io.trino.spi.block.BlockUtil.calculateNewArraySize; import static io.trino.spi.block.Fixed12Block.FIXED12_BYTES; import static io.trino.spi.block.Fixed12Block.encodeFixed12; import static java.lang.Math.max; @@ -49,14 +50,12 @@ public Fixed12BlockBuilder(@Nullable BlockBuilderStatus blockBuilderStatus, int this.blockBuilderStatus = blockBuilderStatus; this.initialEntryCount = max(expectedEntries, 1); - updateDataSize(); + updateRetainedSize(); } public void writeFixed12(long first, int second) { - if (valueIsNull.length <= positionCount) { - growCapacity(); - } + ensureCapacity(positionCount + 1); encodeFixed12(first, second, values, positionCount); @@ -68,12 +67,174 @@ public void writeFixed12(long first, int second) } @Override - public BlockBuilder appendNull() + public void append(ValueBlock block, int position) + { + ensureCapacity(positionCount + 1); + + Fixed12Block fixed12Block = (Fixed12Block) block; + if (fixed12Block.isNull(position)) { + valueIsNull[positionCount] = true; + hasNullValue = true; + } + else { + int[] rawValues = fixed12Block.getRawValues(); + int rawValuePosition = (fixed12Block.getRawOffset() + position) * 3; + + int positionIndex = positionCount * 3; + values[positionIndex] = rawValues[rawValuePosition]; + values[positionIndex + 1] = rawValues[rawValuePosition + 1]; + values[positionIndex + 2] = rawValues[rawValuePosition + 2]; + hasNonNullValue = true; + } + positionCount++; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(Fixed12Block.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendRepeated(ValueBlock block, int position, int count) + { + if (count == 0) { + return; + } + if (count == 1) { + append(block, position); + return; + } + + ensureCapacity(positionCount + count); + + Fixed12Block fixed12Block = (Fixed12Block) block; + if (fixed12Block.isNull(position)) { + Arrays.fill(valueIsNull, positionCount, positionCount + count, true); + hasNullValue = true; + } + else { + int[] rawValues = fixed12Block.getRawValues(); + int rawValuePosition = (fixed12Block.getRawOffset() + position) * 3; + int valueFirst = rawValues[rawValuePosition]; + int valueSecond = rawValues[rawValuePosition + 1]; + int valueThird = rawValues[rawValuePosition + 2]; + + int positionIndex = positionCount * 3; + for (int i = 0; i < count; i++) { + values[positionIndex] = valueFirst; + values[positionIndex + 1] = valueSecond; + values[positionIndex + 2] = valueThird; + positionIndex += 3; + } + + hasNonNullValue = true; + } + positionCount += count; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(count * Fixed12Block.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendRange(ValueBlock block, int offset, int length) + { + if (length == 0) { + return; + } + if (length == 1) { + append(block, offset); + return; + } + + ensureCapacity(positionCount + length); + + Fixed12Block fixed12Block = (Fixed12Block) block; + int rawOffset = fixed12Block.getRawOffset(); + + int[] rawValues = fixed12Block.getRawValues(); + System.arraycopy(rawValues, (rawOffset + offset) * 3, values, positionCount * 3, length * 3); + + boolean[] rawValueIsNull = fixed12Block.getRawValueIsNull(); + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + if (rawValueIsNull[rawOffset + offset + i]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + hasNonNullValue = true; + } + } + } + else { + hasNonNullValue = true; + } + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * LongArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendPositions(ValueBlock block, int[] positions, int offset, int length) { - if (valueIsNull.length <= positionCount) { - growCapacity(); + if (length == 0) { + return; + } + if (length == 1) { + append(block, positions[offset]); + return; } + ensureCapacity(positionCount + length); + + Fixed12Block fixed12Block = (Fixed12Block) block; + int rawOffset = fixed12Block.getRawOffset(); + int[] rawValues = fixed12Block.getRawValues(); + boolean[] rawValueIsNull = fixed12Block.getRawValueIsNull(); + + int positionIndex = positionCount * 3; + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + int rawPosition = positions[offset + i] + rawOffset; + if (rawValueIsNull[rawPosition]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + int rawValuePosition = rawPosition * 3; + values[positionIndex] = rawValues[rawValuePosition]; + values[positionIndex + 1] = rawValues[rawValuePosition + 1]; + values[positionIndex + 2] = rawValues[rawValuePosition + 2]; + hasNonNullValue = true; + } + positionIndex += 3; + } + } + else { + for (int i = 0; i < length; i++) { + int rawPosition = positions[offset + i] + rawOffset; + int rawValuePosition = rawPosition * 3; + values[positionIndex] = rawValues[rawValuePosition]; + values[positionIndex + 1] = rawValues[rawValuePosition + 1]; + values[positionIndex + 2] = rawValues[rawValuePosition + 2]; + positionIndex += 3; + } + hasNonNullValue = true; + } + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * Fixed12Block.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public BlockBuilder appendNull() + { + ensureCapacity(positionCount + 1); + valueIsNull[positionCount] = true; hasNullValue = true; @@ -105,23 +266,28 @@ public BlockBuilder newBlockBuilderLike(int expectedEntries, BlockBuilderStatus return new Fixed12BlockBuilder(blockBuilderStatus, expectedEntries); } - private void growCapacity() + private void ensureCapacity(int capacity) { + if (valueIsNull.length >= capacity) { + return; + } + int newSize; if (initialized) { - newSize = BlockUtil.calculateNewArraySize(valueIsNull.length); + newSize = calculateNewArraySize(capacity); } else { newSize = initialEntryCount; initialized = true; } + newSize = max(newSize, capacity); valueIsNull = Arrays.copyOf(valueIsNull, newSize); values = Arrays.copyOf(values, newSize * 3); - updateDataSize(); + updateRetainedSize(); } - private void updateDataSize() + private void updateRetainedSize() { retainedSizeInBytes = INSTANCE_SIZE + sizeOf(valueIsNull) + sizeOf(values); if (blockBuilderStatus != null) { diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/Fixed12BlockEncoding.java b/core/trino-spi/src/main/java/io/trino/spi/block/Fixed12BlockEncoding.java index 131837f74c86..5d1799f83c4a 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/Fixed12BlockEncoding.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/Fixed12BlockEncoding.java @@ -40,7 +40,7 @@ public void writeBlock(BlockEncodingSerde blockEncodingSerde, SliceOutput sliceO encodeNullsAsBits(sliceOutput, fixed12Block); if (!fixed12Block.mayHaveNull()) { - sliceOutput.writeInts(fixed12Block.getRawValues(), fixed12Block.getPositionOffset() * 3, fixed12Block.getPositionCount() * 3); + sliceOutput.writeInts(fixed12Block.getRawValues(), fixed12Block.getRawOffset() * 3, fixed12Block.getPositionCount() * 3); } else { int[] valuesWithoutNull = new int[positionCount * 3]; diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/Int128ArrayBlock.java b/core/trino-spi/src/main/java/io/trino/spi/block/Int128ArrayBlock.java index 57b8e4ac9bd4..0cdf686a3d21 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/Int128ArrayBlock.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/Int128ArrayBlock.java @@ -256,13 +256,19 @@ public String toString() return sb.toString(); } - long[] getRawValues() + int getRawOffset() { - return values; + return positionOffset; } - int getPositionOffset() + @Nullable + boolean[] getRawValueIsNull() { - return positionOffset; + return valueIsNull; + } + + long[] getRawValues() + { + return values; } } diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/Int128ArrayBlockBuilder.java b/core/trino-spi/src/main/java/io/trino/spi/block/Int128ArrayBlockBuilder.java index f22ae8951fea..5d512b718453 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/Int128ArrayBlockBuilder.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/Int128ArrayBlockBuilder.java @@ -19,6 +19,7 @@ import static io.airlift.slice.SizeOf.instanceSize; import static io.airlift.slice.SizeOf.sizeOf; +import static io.trino.spi.block.BlockUtil.calculateNewArraySize; import static java.lang.Math.max; public class Int128ArrayBlockBuilder @@ -47,14 +48,12 @@ public Int128ArrayBlockBuilder(@Nullable BlockBuilderStatus blockBuilderStatus, this.blockBuilderStatus = blockBuilderStatus; this.initialEntryCount = max(expectedEntries, 1); - updateDataSize(); + updateRetainedSize(); } public void writeInt128(long high, long low) { - if (valueIsNull.length <= positionCount) { - growCapacity(); - } + ensureCapacity(positionCount + 1); int valueIndex = positionCount * 2; values[valueIndex] = high; @@ -68,12 +67,168 @@ public void writeInt128(long high, long low) } @Override - public BlockBuilder appendNull() + public void append(ValueBlock block, int position) + { + ensureCapacity(positionCount + 1); + + Int128ArrayBlock int128ArrayBlock = (Int128ArrayBlock) block; + if (int128ArrayBlock.isNull(position)) { + valueIsNull[positionCount] = true; + hasNullValue = true; + } + else { + long[] rawValues = int128ArrayBlock.getRawValues(); + int rawValuePosition = (int128ArrayBlock.getRawOffset() + position) * 2; + + int positionIndex = positionCount * 2; + values[positionIndex] = rawValues[rawValuePosition]; + values[positionIndex + 1] = rawValues[rawValuePosition + 1]; + hasNonNullValue = true; + } + positionCount++; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(Int128ArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendRepeated(ValueBlock block, int position, int count) + { + if (count == 0) { + return; + } + if (count == 1) { + append(block, position); + return; + } + + ensureCapacity(positionCount + count); + + Int128ArrayBlock int128ArrayBlock = (Int128ArrayBlock) block; + if (int128ArrayBlock.isNull(position)) { + Arrays.fill(valueIsNull, positionCount, positionCount + count, true); + hasNullValue = true; + } + else { + long[] rawValues = int128ArrayBlock.getRawValues(); + int rawValuePosition = (int128ArrayBlock.getRawOffset() + position) * 2; + long valueHigh = rawValues[rawValuePosition]; + long valueLow = rawValues[rawValuePosition + 1]; + + int positionIndex = positionCount * 2; + for (int i = 0; i < count; i++) { + values[positionIndex] = valueHigh; + values[positionIndex + 1] = valueLow; + positionIndex += 2; + } + hasNonNullValue = true; + } + positionCount += count; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(count * Int128ArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendRange(ValueBlock block, int offset, int length) + { + if (length == 0) { + return; + } + if (length == 1) { + append(block, offset); + return; + } + + ensureCapacity(positionCount + length); + + Int128ArrayBlock int128ArrayBlock = (Int128ArrayBlock) block; + int rawOffset = int128ArrayBlock.getRawOffset(); + + long[] rawValues = int128ArrayBlock.getRawValues(); + System.arraycopy(rawValues, (rawOffset + offset) * 2, values, positionCount * 2, length * 2); + + boolean[] rawValueIsNull = int128ArrayBlock.getRawValueIsNull(); + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + if (rawValueIsNull[rawOffset + offset + i]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + hasNonNullValue = true; + } + } + } + else { + hasNonNullValue = true; + } + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * LongArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendPositions(ValueBlock block, int[] positions, int offset, int length) { - if (valueIsNull.length <= positionCount) { - growCapacity(); + if (length == 0) { + return; + } + if (length == 1) { + append(block, positions[offset]); + return; } + ensureCapacity(positionCount + length); + + Int128ArrayBlock int128ArrayBlock = (Int128ArrayBlock) block; + int rawOffset = int128ArrayBlock.getRawOffset(); + long[] rawValues = int128ArrayBlock.getRawValues(); + boolean[] rawValueIsNull = int128ArrayBlock.getRawValueIsNull(); + + int positionIndex = positionCount * 2; + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + int rawPosition = positions[offset + i] + rawOffset; + if (rawValueIsNull[rawPosition]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + int rawValuePosition = rawPosition * 2; + values[positionIndex] = rawValues[rawValuePosition]; + values[positionIndex + 1] = rawValues[rawValuePosition + 1]; + hasNonNullValue = true; + } + positionIndex += 2; + } + } + else { + for (int i = 0; i < length; i++) { + int rawPosition = positions[offset + i] + rawOffset; + int rawValuePosition = rawPosition * 2; + values[positionIndex] = rawValues[rawValuePosition]; + values[positionIndex + 1] = rawValues[rawValuePosition + 1]; + positionIndex += 2; + } + hasNonNullValue = true; + } + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * Int128ArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public BlockBuilder appendNull() + { + ensureCapacity(positionCount + 1); + valueIsNull[positionCount] = true; hasNullValue = true; @@ -105,23 +260,28 @@ public BlockBuilder newBlockBuilderLike(int expectedEntries, BlockBuilderStatus return new Int128ArrayBlockBuilder(blockBuilderStatus, expectedEntries); } - private void growCapacity() + private void ensureCapacity(int capacity) { + if (valueIsNull.length >= capacity) { + return; + } + int newSize; if (initialized) { - newSize = BlockUtil.calculateNewArraySize(valueIsNull.length); + newSize = calculateNewArraySize(capacity); } else { newSize = initialEntryCount; initialized = true; } + newSize = max(newSize, capacity); valueIsNull = Arrays.copyOf(valueIsNull, newSize); values = Arrays.copyOf(values, newSize * 2); - updateDataSize(); + updateRetainedSize(); } - private void updateDataSize() + private void updateRetainedSize() { retainedSizeInBytes = INSTANCE_SIZE + sizeOf(valueIsNull) + sizeOf(values); if (blockBuilderStatus != null) { diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/Int128ArrayBlockEncoding.java b/core/trino-spi/src/main/java/io/trino/spi/block/Int128ArrayBlockEncoding.java index 78e8191202e5..55539440d8cd 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/Int128ArrayBlockEncoding.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/Int128ArrayBlockEncoding.java @@ -40,7 +40,7 @@ public void writeBlock(BlockEncodingSerde blockEncodingSerde, SliceOutput sliceO encodeNullsAsBits(sliceOutput, int128ArrayBlock); if (!int128ArrayBlock.mayHaveNull()) { - sliceOutput.writeLongs(int128ArrayBlock.getRawValues(), int128ArrayBlock.getPositionOffset() * 2, int128ArrayBlock.getPositionCount() * 2); + sliceOutput.writeLongs(int128ArrayBlock.getRawValues(), int128ArrayBlock.getRawOffset() * 2, int128ArrayBlock.getPositionCount() * 2); } else { long[] valuesWithoutNull = new long[positionCount * 2]; diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/IntArrayBlock.java b/core/trino-spi/src/main/java/io/trino/spi/block/IntArrayBlock.java index 93fa86da8456..11d426889fb2 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/IntArrayBlock.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/IntArrayBlock.java @@ -237,6 +237,11 @@ public String toString() return sb.toString(); } + boolean[] getRawValueIsNull() + { + return valueIsNull; + } + @Experimental(eta = "2023-12-31") public int[] getRawValues() { diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/IntArrayBlockBuilder.java b/core/trino-spi/src/main/java/io/trino/spi/block/IntArrayBlockBuilder.java index bf124103418b..50ebd1029e23 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/IntArrayBlockBuilder.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/IntArrayBlockBuilder.java @@ -19,6 +19,7 @@ import static io.airlift.slice.SizeOf.instanceSize; import static io.airlift.slice.SizeOf.sizeOf; +import static io.trino.spi.block.BlockUtil.calculateNewArraySize; import static java.lang.Math.max; public class IntArrayBlockBuilder @@ -47,14 +48,12 @@ public IntArrayBlockBuilder(@Nullable BlockBuilderStatus blockBuilderStatus, int this.blockBuilderStatus = blockBuilderStatus; this.initialEntryCount = max(expectedEntries, 1); - updateDataSize(); + updateRetainedSize(); } public BlockBuilder writeInt(int value) { - if (values.length <= positionCount) { - growCapacity(); - } + ensureCapacity(positionCount + 1); values[positionCount] = value; @@ -67,12 +66,146 @@ public BlockBuilder writeInt(int value) } @Override - public BlockBuilder appendNull() + public void append(ValueBlock block, int position) + { + ensureCapacity(positionCount + 1); + + IntArrayBlock intArrayBlock = (IntArrayBlock) block; + if (intArrayBlock.isNull(position)) { + valueIsNull[positionCount] = true; + hasNullValue = true; + } + else { + values[positionCount] = intArrayBlock.getInt(position); + hasNonNullValue = true; + } + positionCount++; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(IntArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendRepeated(ValueBlock block, int position, int count) { - if (values.length <= positionCount) { - growCapacity(); + if (count == 0) { + return; + } + if (count == 1) { + append(block, position); + return; } + ensureCapacity(positionCount + count); + + IntArrayBlock intArrayBlock = (IntArrayBlock) block; + if (intArrayBlock.isNull(position)) { + Arrays.fill(valueIsNull, positionCount, positionCount + count, true); + hasNullValue = true; + } + else { + int value = intArrayBlock.getInt(position); + Arrays.fill(values, positionCount, positionCount + count, value); + hasNonNullValue = true; + } + positionCount += count; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(count * IntArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendRange(ValueBlock block, int offset, int length) + { + if (length == 0) { + return; + } + if (length == 1) { + append(block, offset); + return; + } + + ensureCapacity(positionCount + length); + + IntArrayBlock intArrayBlock = (IntArrayBlock) block; + int rawOffset = intArrayBlock.getRawValuesOffset(); + + int[] rawValues = intArrayBlock.getRawValues(); + System.arraycopy(rawValues, rawOffset + offset, values, positionCount, length); + + boolean[] rawValueIsNull = intArrayBlock.getRawValueIsNull(); + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + if (rawValueIsNull[rawOffset + offset + i]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + hasNonNullValue = true; + } + } + } + else { + hasNonNullValue = true; + } + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * IntArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendPositions(ValueBlock block, int[] positions, int offset, int length) + { + if (length == 0) { + return; + } + if (length == 1) { + append(block, positions[offset]); + return; + } + + ensureCapacity(positionCount + length); + + IntArrayBlock intArrayBlock = (IntArrayBlock) block; + int rawOffset = intArrayBlock.getRawValuesOffset(); + int[] rawValues = intArrayBlock.getRawValues(); + boolean[] rawValueIsNull = intArrayBlock.getRawValueIsNull(); + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + int rawPosition = positions[offset + i] + rawOffset; + if (rawValueIsNull[rawPosition]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + values[positionCount + i] = rawValues[rawPosition]; + hasNonNullValue = true; + } + } + } + else { + for (int i = 0; i < length; i++) { + int rawPosition = positions[offset + i] + rawOffset; + values[positionCount + i] = rawValues[rawPosition]; + } + hasNonNullValue = true; + } + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * IntArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public BlockBuilder appendNull() + { + ensureCapacity(positionCount + 1); + valueIsNull[positionCount] = true; hasNullValue = true; @@ -104,23 +237,28 @@ public BlockBuilder newBlockBuilderLike(int expectedEntries, BlockBuilderStatus return new IntArrayBlockBuilder(blockBuilderStatus, expectedEntries); } - private void growCapacity() + private void ensureCapacity(int capacity) { + if (values.length >= capacity) { + return; + } + int newSize; if (initialized) { - newSize = BlockUtil.calculateNewArraySize(values.length); + newSize = calculateNewArraySize(capacity); } else { newSize = initialEntryCount; initialized = true; } + newSize = max(newSize, capacity); valueIsNull = Arrays.copyOf(valueIsNull, newSize); values = Arrays.copyOf(values, newSize); - updateDataSize(); + updateRetainedSize(); } - private void updateDataSize() + private void updateRetainedSize() { retainedSizeInBytes = INSTANCE_SIZE + sizeOf(valueIsNull) + sizeOf(values); if (blockBuilderStatus != null) { diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/LongArrayBlock.java b/core/trino-spi/src/main/java/io/trino/spi/block/LongArrayBlock.java index 2b9aec633844..b79e5939ba79 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/LongArrayBlock.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/LongArrayBlock.java @@ -283,13 +283,18 @@ public String toString() return sb.toString(); } + int getRawValuesOffset() + { + return arrayOffset; + } + long[] getRawValues() { return values; } - int getRawValuesOffset() + boolean[] getRawValueIsNull() { - return arrayOffset; + return valueIsNull; } } diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/LongArrayBlockBuilder.java b/core/trino-spi/src/main/java/io/trino/spi/block/LongArrayBlockBuilder.java index 09a530971ac1..059709edbb1e 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/LongArrayBlockBuilder.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/LongArrayBlockBuilder.java @@ -19,6 +19,7 @@ import static io.airlift.slice.SizeOf.instanceSize; import static io.airlift.slice.SizeOf.sizeOf; +import static io.trino.spi.block.BlockUtil.calculateNewArraySize; import static java.lang.Math.max; public class LongArrayBlockBuilder @@ -47,14 +48,12 @@ public LongArrayBlockBuilder(@Nullable BlockBuilderStatus blockBuilderStatus, in this.blockBuilderStatus = blockBuilderStatus; this.initialEntryCount = max(expectedEntries, 1); - updateDataSize(); + updateRetainedSize(); } public BlockBuilder writeLong(long value) { - if (values.length <= positionCount) { - growCapacity(); - } + ensureCapacity(positionCount + 1); values[positionCount] = value; @@ -67,12 +66,146 @@ public BlockBuilder writeLong(long value) } @Override - public BlockBuilder appendNull() + public void append(ValueBlock block, int position) + { + ensureCapacity(positionCount + 1); + + LongArrayBlock longArrayBlock = (LongArrayBlock) block; + if (longArrayBlock.isNull(position)) { + valueIsNull[positionCount] = true; + hasNullValue = true; + } + else { + values[positionCount] = longArrayBlock.getLong(position); + hasNonNullValue = true; + } + positionCount++; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(LongArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendRepeated(ValueBlock block, int position, int count) { - if (values.length <= positionCount) { - growCapacity(); + if (count == 0) { + return; + } + if (count == 1) { + append(block, position); + return; } + ensureCapacity(positionCount + count); + + LongArrayBlock longArrayBlock = (LongArrayBlock) block; + if (longArrayBlock.isNull(position)) { + Arrays.fill(valueIsNull, positionCount, positionCount + count, true); + hasNullValue = true; + } + else { + long value = longArrayBlock.getLong(position); + Arrays.fill(values, positionCount, positionCount + count, value); + hasNonNullValue = true; + } + positionCount += count; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(count * LongArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendRange(ValueBlock block, int offset, int length) + { + if (length == 0) { + return; + } + if (length == 1) { + append(block, offset); + return; + } + + ensureCapacity(positionCount + length); + + LongArrayBlock longArrayBlock = (LongArrayBlock) block; + int rawOffset = longArrayBlock.getRawValuesOffset(); + + long[] rawValues = longArrayBlock.getRawValues(); + System.arraycopy(rawValues, rawOffset + offset, values, positionCount, length); + + boolean[] rawValueIsNull = longArrayBlock.getRawValueIsNull(); + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + if (rawValueIsNull[rawOffset + offset + i]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + hasNonNullValue = true; + } + } + } + else { + hasNonNullValue = true; + } + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * LongArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendPositions(ValueBlock block, int[] positions, int offset, int length) + { + if (length == 0) { + return; + } + if (length == 1) { + append(block, positions[offset]); + return; + } + + ensureCapacity(positionCount + length); + + LongArrayBlock longArrayBlock = (LongArrayBlock) block; + int rawOffset = longArrayBlock.getRawValuesOffset(); + long[] rawValues = longArrayBlock.getRawValues(); + boolean[] rawValueIsNull = longArrayBlock.getRawValueIsNull(); + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + int rawPosition = positions[offset + i] + rawOffset; + if (rawValueIsNull[rawPosition]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + values[positionCount + i] = rawValues[rawPosition]; + hasNonNullValue = true; + } + } + } + else { + for (int i = 0; i < length; i++) { + int rawPosition = positions[offset + i] + rawOffset; + values[positionCount + i] = rawValues[rawPosition]; + } + hasNonNullValue = true; + } + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * LongArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public BlockBuilder appendNull() + { + ensureCapacity(positionCount + 1); + valueIsNull[positionCount] = true; hasNullValue = true; @@ -104,23 +237,28 @@ public BlockBuilder newBlockBuilderLike(int expectedEntries, BlockBuilderStatus return new LongArrayBlockBuilder(blockBuilderStatus, expectedEntries); } - private void growCapacity() + private void ensureCapacity(int capacity) { + if (values.length >= capacity) { + return; + } + int newSize; if (initialized) { - newSize = BlockUtil.calculateNewArraySize(values.length); + newSize = calculateNewArraySize(capacity); } else { newSize = initialEntryCount; initialized = true; } + newSize = max(newSize, capacity); valueIsNull = Arrays.copyOf(valueIsNull, newSize); values = Arrays.copyOf(values, newSize); - updateDataSize(); + updateRetainedSize(); } - private void updateDataSize() + private void updateRetainedSize() { retainedSizeInBytes = INSTANCE_SIZE + sizeOf(valueIsNull) + sizeOf(values); if (blockBuilderStatus != null) { diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/MapBlockBuilder.java b/core/trino-spi/src/main/java/io/trino/spi/block/MapBlockBuilder.java index 477ae48f4ce1..c1e4af377f2c 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/MapBlockBuilder.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/MapBlockBuilder.java @@ -23,6 +23,7 @@ import static io.airlift.slice.SizeOf.instanceSize; import static io.airlift.slice.SizeOf.sizeOf; +import static io.trino.spi.block.BlockUtil.appendRawBlockRange; import static io.trino.spi.block.BlockUtil.calculateNewArraySize; import static io.trino.spi.block.MapBlock.createMapBlockInternal; import static io.trino.spi.block.MapHashTables.HASH_MULTIPLIER; @@ -132,6 +133,64 @@ public void buildEntry(MapValueBuilder builder) currentEntryOpened = false; } + @Override + public void append(ValueBlock block, int position) + { + if (currentEntryOpened) { + throw new IllegalStateException("Current entry must be closed before a null can be written"); + } + + MapBlock mapBlock = (MapBlock) block; + if (block.isNull(position)) { + entryAdded(true); + return; + } + + int offsetBase = mapBlock.getOffsetBase(); + int[] offsets = mapBlock.getOffsets(); + int startOffset = offsets[offsetBase + position]; + int length = offsets[offsetBase + position + 1] - startOffset; + + appendRawBlockRange(mapBlock.getRawKeyBlock(), startOffset, length, keyBlockBuilder); + appendRawBlockRange(mapBlock.getRawValueBlock(), startOffset, length, valueBlockBuilder); + entryAdded(false); + } + + @Override + public void appendRange(ValueBlock block, int offset, int length) + { + if (currentEntryOpened) { + throw new IllegalStateException("Current entry must be closed before a null can be written"); + } + + // this could be optimized to append all array elements using a single append range call + for (int i = 0; i < length; i++) { + append(block, offset + i); + } + } + + @Override + public void appendRepeated(ValueBlock block, int position, int count) + { + if (currentEntryOpened) { + throw new IllegalStateException("Current entry must be closed before a null can be written"); + } + for (int i = 0; i < count; i++) { + append(block, position); + } + } + + @Override + public void appendPositions(ValueBlock block, int[] positions, int offset, int length) + { + if (currentEntryOpened) { + throw new IllegalStateException("Current entry must be closed before a null can be written"); + } + for (int i = 0; i < length; i++) { + append(block, positions[offset + i]); + } + } + @Override public BlockBuilder appendNull() { @@ -167,6 +226,29 @@ private void entryAdded(boolean isNull) @Override public Block build() { + if (positionCount > 1 && hasNullValue) { + boolean hasNonNull = false; + for (int i = 0; i < positionCount; i++) { + hasNonNull |= !mapIsNull[i]; + } + if (!hasNonNull) { + Block emptyKeyBlock = mapType.getKeyType().createBlockBuilder(null, 0).build(); + Block emptyValueBlock = mapType.getValueType().createBlockBuilder(null, 0).build(); + int[] emptyOffsets = {0, 0}; + boolean[] nulls = {true}; + return RunLengthEncodedBlock.create( + createMapBlockInternal( + mapType, + 0, + 1, + Optional.of(nulls), + emptyOffsets, + emptyKeyBlock, + emptyValueBlock, + MapHashTables.create(hashBuildMode, mapType, 0, emptyKeyBlock, emptyOffsets, nulls)), + positionCount); + } + } return buildValueBlock(); } diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/RowBlock.java b/core/trino-spi/src/main/java/io/trino/spi/block/RowBlock.java index 930f82bbe50a..2e4d98ee2769 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/RowBlock.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/RowBlock.java @@ -152,6 +152,11 @@ public boolean mayHaveNull() return rowIsNull != null; } + boolean[] getRawRowIsNull() + { + return rowIsNull; + } + @Override public int getPositionCount() { diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/RowBlockBuilder.java b/core/trino-spi/src/main/java/io/trino/spi/block/RowBlockBuilder.java index 291c97c1b9ae..ea1a16f4bdf9 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/RowBlockBuilder.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/RowBlockBuilder.java @@ -112,6 +112,205 @@ public void buildEntry(RowValueBuilder builder) currentEntryOpened = false; } + @Override + public void append(ValueBlock block, int position) + { + if (currentEntryOpened) { + throw new IllegalStateException("Current entry must be closed before a null can be written"); + } + + RowBlock rowBlock = (RowBlock) block; + if (block.isNull(position)) { + appendNull(); + return; + } + + List fieldBlocks = rowBlock.getFieldBlocks(); + for (int fieldId = 0; fieldId < fieldBlockBuilders.length; fieldId++) { + appendToField(fieldBlocks.get(fieldId), position, fieldBlockBuilders[fieldId]); + } + entryAdded(false); + } + + private static void appendToField(Block fieldBlock, int position, BlockBuilder fieldBlockBuilder) + { + fieldBlock = fieldBlock.getLoadedBlock(); + if (fieldBlock instanceof RunLengthEncodedBlock rleBlock) { + fieldBlockBuilder.append(rleBlock.getValue(), 0); + } + else if (fieldBlock instanceof DictionaryBlock dictionaryBlock) { + fieldBlockBuilder.append(dictionaryBlock.getDictionary(), dictionaryBlock.getId(position)); + } + else if (fieldBlock instanceof ValueBlock valueBlock) { + fieldBlockBuilder.append(valueBlock, position); + } + else { + throw new IllegalArgumentException("Unsupported block type " + fieldBlock.getClass().getSimpleName()); + } + } + + @Override + public void appendRange(ValueBlock block, int offset, int length) + { + if (currentEntryOpened) { + throw new IllegalStateException("Current entry must be closed before a null can be written"); + } + if (length == 0) { + return; + } + + RowBlock rowBlock = (RowBlock) block; + ensureCapacity(positionCount + length); + + List fieldBlocks = rowBlock.getFieldBlocks(); + for (int fieldId = 0; fieldId < fieldBlockBuilders.length; fieldId++) { + appendRangeToField(fieldBlocks.get(fieldId), offset, length, fieldBlockBuilders[fieldId]); + } + + boolean[] rawRowIsNull = rowBlock.getRawRowIsNull(); + if (rawRowIsNull != null) { + for (int i = 0; i < length; i++) { + if (rawRowIsNull[offset + i]) { + rowIsNull[positionCount + i] = true; + hasNullRow = true; + } + else { + hasNonNullRow = true; + } + } + } + else { + hasNonNullRow = true; + } + positionCount += length; + } + + private static void appendRangeToField(Block fieldBlock, int offset, int length, BlockBuilder fieldBlockBuilder) + { + fieldBlock = fieldBlock.getLoadedBlock(); + if (fieldBlock instanceof RunLengthEncodedBlock rleBlock) { + fieldBlockBuilder.appendRepeated(rleBlock.getValue(), 0, length); + } + else if (fieldBlock instanceof DictionaryBlock dictionaryBlock) { + int[] rawIds = dictionaryBlock.getRawIds(); + int rawIdsOffset = dictionaryBlock.getRawIdsOffset(); + fieldBlockBuilder.appendPositions(dictionaryBlock.getDictionary(), rawIds, rawIdsOffset + offset, length); + } + else if (fieldBlock instanceof ValueBlock valueBlock) { + fieldBlockBuilder.appendRange(valueBlock, offset, length); + } + else { + throw new IllegalArgumentException("Unsupported block type " + fieldBlock.getClass().getSimpleName()); + } + } + + @Override + public void appendRepeated(ValueBlock block, int position, int count) + { + if (currentEntryOpened) { + throw new IllegalStateException("Current entry must be closed before a null can be written"); + } + if (count == 0) { + return; + } + + RowBlock rowBlock = (RowBlock) block; + ensureCapacity(positionCount + count); + + List fieldBlocks = rowBlock.getFieldBlocks(); + for (int fieldId = 0; fieldId < fieldBlockBuilders.length; fieldId++) { + appendRepeatedToField(fieldBlocks.get(fieldId), position, count, fieldBlockBuilders[fieldId]); + } + + if (rowBlock.isNull(position)) { + Arrays.fill(rowIsNull, positionCount, positionCount + count, true); + hasNullRow = true; + } + else { + hasNonNullRow = true; + } + + positionCount += count; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(Integer.BYTES + Byte.BYTES); + } + } + + private static void appendRepeatedToField(Block fieldBlock, int position, int count, BlockBuilder fieldBlockBuilder) + { + fieldBlock = fieldBlock.getLoadedBlock(); + if (fieldBlock instanceof RunLengthEncodedBlock rleBlock) { + fieldBlockBuilder.appendRepeated(rleBlock.getValue(), 0, count); + } + else if (fieldBlock instanceof DictionaryBlock dictionaryBlock) { + fieldBlockBuilder.appendRepeated(dictionaryBlock.getDictionary(), dictionaryBlock.getId(position), count); + } + else if (fieldBlock instanceof ValueBlock valueBlock) { + fieldBlockBuilder.appendRepeated(valueBlock, position, count); + } + else { + throw new IllegalArgumentException("Unsupported block type " + fieldBlock.getClass().getSimpleName()); + } + } + + @Override + public void appendPositions(ValueBlock block, int[] positions, int offset, int length) + { + if (currentEntryOpened) { + throw new IllegalStateException("Current entry must be closed before a null can be written"); + } + if (length == 0) { + return; + } + + RowBlock rowBlock = (RowBlock) block; + ensureCapacity(positionCount + length); + + List fieldBlocks = rowBlock.getFieldBlocks(); + for (int fieldId = 0; fieldId < fieldBlockBuilders.length; fieldId++) { + appendPositionsToField(fieldBlocks.get(fieldId), positions, offset, length, fieldBlockBuilders[fieldId]); + } + + boolean[] rawRowIsNull = rowBlock.getRawRowIsNull(); + if (rawRowIsNull != null) { + for (int i = 0; i < length; i++) { + if (rawRowIsNull[positions[offset + i]]) { + rowIsNull[positionCount + i] = true; + hasNullRow = true; + } + else { + hasNonNullRow = true; + } + } + } + else { + hasNonNullRow = true; + } + positionCount += length; + } + + private static void appendPositionsToField(Block fieldBlock, int[] positions, int offset, int length, BlockBuilder fieldBlockBuilder) + { + fieldBlock = fieldBlock.getLoadedBlock(); + if (fieldBlock instanceof RunLengthEncodedBlock rleBlock) { + fieldBlockBuilder.appendRepeated(rleBlock.getValue(), 0, length); + } + else if (fieldBlock instanceof DictionaryBlock dictionaryBlock) { + int[] newPositions = new int[length]; + for (int i = 0; i < newPositions.length; i++) { + newPositions[i] = dictionaryBlock.getId(positions[offset + i]); + } + fieldBlockBuilder.appendPositions(dictionaryBlock.getDictionary(), newPositions, 0, length); + } + else if (fieldBlock instanceof ValueBlock valueBlock) { + fieldBlockBuilder.appendPositions(valueBlock, positions, offset, length); + } + else { + throw new IllegalArgumentException("Unsupported block type " + fieldBlock.getClass().getSimpleName()); + } + } + @Override public BlockBuilder appendNull() { @@ -129,10 +328,7 @@ public BlockBuilder appendNull() private void entryAdded(boolean isNull) { - if (rowIsNull.length <= positionCount) { - int newSize = BlockUtil.calculateNewArraySize(rowIsNull.length); - rowIsNull = Arrays.copyOf(rowIsNull, newSize); - } + ensureCapacity(positionCount + 1); rowIsNull[positionCount] = isNull; hasNullRow |= isNull; @@ -176,6 +372,17 @@ public RowBlock buildValueBlock() return createRowBlockInternal(positionCount, hasNullRow ? rowIsNull : null, fieldBlocks); } + private void ensureCapacity(int capacity) + { + if (rowIsNull.length >= capacity) { + return; + } + + // todo add lazy initialize + int newSize = BlockUtil.calculateNewArraySize(rowIsNull.length); + rowIsNull = Arrays.copyOf(rowIsNull, newSize); + } + @Override public String toString() { diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/ShortArrayBlock.java b/core/trino-spi/src/main/java/io/trino/spi/block/ShortArrayBlock.java index 336a5b845539..88ed0b470405 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/ShortArrayBlock.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/ShortArrayBlock.java @@ -244,4 +244,9 @@ short[] getRawValues() { return values; } + + boolean[] getRawValueIsNull() + { + return valueIsNull; + } } diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/ShortArrayBlockBuilder.java b/core/trino-spi/src/main/java/io/trino/spi/block/ShortArrayBlockBuilder.java index ee44b44b6dc2..a18d02142b1a 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/ShortArrayBlockBuilder.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/ShortArrayBlockBuilder.java @@ -19,6 +19,7 @@ import static io.airlift.slice.SizeOf.instanceSize; import static io.airlift.slice.SizeOf.sizeOf; +import static io.trino.spi.block.BlockUtil.calculateNewArraySize; import static java.lang.Math.max; public class ShortArrayBlockBuilder @@ -47,14 +48,12 @@ public ShortArrayBlockBuilder(@Nullable BlockBuilderStatus blockBuilderStatus, i this.blockBuilderStatus = blockBuilderStatus; this.initialEntryCount = max(expectedEntries, 1); - updateDataSize(); + updateRetainedSize(); } - public BlockBuilder writeShort(short value) + public ShortArrayBlockBuilder writeShort(short value) { - if (values.length <= positionCount) { - growCapacity(); - } + ensureCapacity(positionCount + 1); values[positionCount] = value; @@ -67,12 +66,146 @@ public BlockBuilder writeShort(short value) } @Override - public BlockBuilder appendNull() + public void append(ValueBlock block, int position) + { + ensureCapacity(positionCount + 1); + + ShortArrayBlock shortArrayBlock = (ShortArrayBlock) block; + if (shortArrayBlock.isNull(position)) { + valueIsNull[positionCount] = true; + hasNullValue = true; + } + else { + values[positionCount] = shortArrayBlock.getShort(position); + hasNonNullValue = true; + } + positionCount++; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(ShortArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendRepeated(ValueBlock block, int position, int count) + { + if (count == 0) { + return; + } + if (count == 1) { + append(block, position); + return; + } + + ensureCapacity(positionCount + count); + + ShortArrayBlock shortArrayBlock = (ShortArrayBlock) block; + if (shortArrayBlock.isNull(position)) { + Arrays.fill(valueIsNull, positionCount, positionCount + count, true); + hasNullValue = true; + } + else { + short value = shortArrayBlock.getShort(position); + Arrays.fill(values, positionCount, positionCount + count, value); + hasNonNullValue = true; + } + positionCount += count; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(count * ShortArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendRange(ValueBlock block, int offset, int length) { - if (values.length <= positionCount) { - growCapacity(); + if (length == 0) { + return; + } + if (length == 1) { + append(block, offset); + return; } + ensureCapacity(positionCount + length); + + ShortArrayBlock shortArrayBlock = (ShortArrayBlock) block; + int rawOffset = shortArrayBlock.getRawValuesOffset(); + + short[] rawValues = shortArrayBlock.getRawValues(); + System.arraycopy(rawValues, rawOffset + offset, values, positionCount, length); + + boolean[] rawValueIsNull = shortArrayBlock.getRawValueIsNull(); + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + if (rawValueIsNull[rawOffset + offset + i]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + hasNonNullValue = true; + } + } + } + else { + hasNonNullValue = true; + } + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * ShortArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public void appendPositions(ValueBlock block, int[] positions, int offset, int length) + { + if (length == 0) { + return; + } + if (length == 1) { + append(block, positions[offset]); + return; + } + + ensureCapacity(positionCount + length); + + ShortArrayBlock shortArrayBlock = (ShortArrayBlock) block; + int rawOffset = shortArrayBlock.getRawValuesOffset(); + short[] rawValues = shortArrayBlock.getRawValues(); + boolean[] rawValueIsNull = shortArrayBlock.getRawValueIsNull(); + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + int rawPosition = positions[offset + i] + rawOffset; + if (rawValueIsNull[rawPosition]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + values[positionCount + i] = rawValues[rawPosition]; + hasNonNullValue = true; + } + } + } + else { + for (int i = 0; i < length; i++) { + int rawPosition = positions[offset + i] + rawOffset; + values[positionCount + i] = rawValues[rawPosition]; + } + hasNonNullValue = true; + } + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * ShortArrayBlock.SIZE_IN_BYTES_PER_POSITION); + } + } + + @Override + public ShortArrayBlockBuilder appendNull() + { + ensureCapacity(positionCount + 1); + valueIsNull[positionCount] = true; hasNullValue = true; @@ -104,23 +237,28 @@ public BlockBuilder newBlockBuilderLike(int expectedEntries, BlockBuilderStatus return new ShortArrayBlockBuilder(blockBuilderStatus, expectedEntries); } - private void growCapacity() + private void ensureCapacity(int capacity) { + if (values.length >= capacity) { + return; + } + int newSize; if (initialized) { - newSize = BlockUtil.calculateNewArraySize(values.length); + newSize = calculateNewArraySize(capacity); } else { newSize = initialEntryCount; initialized = true; } + newSize = max(newSize, capacity); valueIsNull = Arrays.copyOf(valueIsNull, newSize); values = Arrays.copyOf(values, newSize); - updateDataSize(); + updateRetainedSize(); } - private void updateDataSize() + private void updateRetainedSize() { retainedSizeInBytes = INSTANCE_SIZE + sizeOf(valueIsNull) + sizeOf(values); if (blockBuilderStatus != null) { diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/VariableWidthBlock.java b/core/trino-spi/src/main/java/io/trino/spi/block/VariableWidthBlock.java index 931d4cae70eb..58bc8535bebb 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/VariableWidthBlock.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/VariableWidthBlock.java @@ -332,6 +332,21 @@ public VariableWidthBlock copyWithAppendedNull() return new VariableWidthBlock(arrayOffset, positionCount + 1, slice, newOffsets, newValueIsNull); } + int getRawArrayBase() + { + return arrayOffset; + } + + int[] getRawOffsets() + { + return offsets; + } + + boolean[] getRawValueIsNull() + { + return valueIsNull; + } + @Override public VariableWidthBlock getUnderlyingValueBlock() { diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/VariableWidthBlockBuilder.java b/core/trino-spi/src/main/java/io/trino/spi/block/VariableWidthBlockBuilder.java index ba53ef75c57c..59aca4f3b550 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/VariableWidthBlockBuilder.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/VariableWidthBlockBuilder.java @@ -19,16 +19,14 @@ import java.util.Arrays; -import static io.airlift.slice.SizeOf.SIZE_OF_BYTE; -import static io.airlift.slice.SizeOf.SIZE_OF_INT; import static io.airlift.slice.SizeOf.instanceSize; import static io.airlift.slice.SizeOf.sizeOf; import static io.airlift.slice.Slices.EMPTY_SLICE; import static io.trino.spi.block.BlockUtil.MAX_ARRAY_SIZE; import static io.trino.spi.block.BlockUtil.calculateBlockResetBytes; import static io.trino.spi.block.BlockUtil.calculateNewArraySize; -import static java.lang.Math.max; import static java.lang.Math.min; +import static java.lang.Math.toIntExact; public class VariableWidthBlockBuilder implements BlockBuilder @@ -79,7 +77,7 @@ public long getSizeInBytes() @Override public long getRetainedSizeInBytes() { - long size = INSTANCE_SIZE + sizeOf(bytes) + arraysRetainedSizeInBytes; + long size = INSTANCE_SIZE + arraysRetainedSizeInBytes; if (blockBuilderStatus != null) { size += BlockBuilderStatus.INSTANCE_SIZE; } @@ -108,53 +106,247 @@ public VariableWidthBlockBuilder writeEntry(byte[] source, int sourceIndex, int } @Override - public BlockBuilder appendNull() + public void append(ValueBlock block, int position) { - hasNullValue = true; - entryAdded(0, true); - return this; + ensureCapacity(positionCount + 1); + + VariableWidthBlock variableWidthBlock = (VariableWidthBlock) block; + int bytesWritten = 0; + if (variableWidthBlock.isNull(position)) { + valueIsNull[positionCount] = true; + hasNullValue = true; + } + else { + int rawArrayBase = variableWidthBlock.getRawArrayBase(); + int[] rawOffsets = variableWidthBlock.getRawOffsets(); + int startValueOffset = rawOffsets[rawArrayBase + position]; + int endValueOffset = rawOffsets[rawArrayBase + position + 1]; + int length = endValueOffset - startValueOffset; + ensureFreeSpace(length); + + Slice rawSlice = variableWidthBlock.getRawSlice(); + byte[] rawByteArray = rawSlice.byteArray(); + int byteArrayOffset = rawSlice.byteArrayOffset(); + + System.arraycopy(rawByteArray, byteArrayOffset + startValueOffset, bytes, offsets[positionCount], length); + bytesWritten = length; + hasNonNullValue = true; + } + offsets[positionCount + 1] = offsets[positionCount] + bytesWritten; + positionCount++; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(SIZE_IN_BYTES_PER_POSITION + bytesWritten); + } } - private void entryAdded(int bytesWritten, boolean isNull) + @Override + public void appendRepeated(ValueBlock block, int position, int count) { - if (valueIsNull.length <= positionCount) { - growCapacity(); + if (count == 0) { + return; + } + if (count == 1) { + append(block, position); + return; } - valueIsNull[positionCount] = isNull; - offsets[positionCount + 1] = offsets[positionCount] + bytesWritten; + ensureCapacity(positionCount + count); + + VariableWidthBlock variableWidthBlock = (VariableWidthBlock) block; + int bytesWritten = 0; + if (variableWidthBlock.isNull(position)) { + Arrays.fill(valueIsNull, positionCount, positionCount + count, true); + Arrays.fill(offsets, positionCount + 1, positionCount + count + 1, offsets[positionCount]); + hasNullValue = true; + } + else { + int rawArrayBase = variableWidthBlock.getRawArrayBase(); + int[] rawOffsets = variableWidthBlock.getRawOffsets(); + int startValueOffset = rawOffsets[rawArrayBase + position]; + int endValueOffset = rawOffsets[rawArrayBase + position + 1]; + int length = endValueOffset - startValueOffset; + + if (length > 0) { + bytesWritten = toIntExact((long) length * count); + ensureFreeSpace(bytesWritten); + + // copy in the value + Slice rawSlice = variableWidthBlock.getRawSlice(); + byte[] rawByteArray = rawSlice.byteArray(); + int byteArrayOffset = rawSlice.byteArrayOffset(); + + int currentOffset = offsets[positionCount]; + System.arraycopy(rawByteArray, byteArrayOffset + startValueOffset, bytes, currentOffset, length); + + // repeatedly duplicate the written vales, doubling the number of values copied each time + int duplicatedBytes = length; + while (duplicatedBytes * 2 <= bytesWritten) { + System.arraycopy(bytes, currentOffset, bytes, currentOffset + duplicatedBytes, duplicatedBytes); + duplicatedBytes = duplicatedBytes * 2; + } + // copy the remaining values + System.arraycopy(bytes, currentOffset, bytes, currentOffset + duplicatedBytes, bytesWritten - duplicatedBytes); + + // set the offsets + int previousOffset = currentOffset; + for (int i = 0; i < count; i++) { + previousOffset += length; + offsets[positionCount + i + 1] = previousOffset; + } + } + else { + // zero length array + Arrays.fill(offsets, positionCount + 1, positionCount + count + 1, offsets[positionCount]); + } + + hasNonNullValue = true; + } + positionCount += count; - positionCount++; - hasNonNullValue |= !isNull; if (blockBuilderStatus != null) { - blockBuilderStatus.addBytes(SIZE_OF_BYTE + SIZE_OF_INT + bytesWritten); + blockBuilderStatus.addBytes(count * SIZE_IN_BYTES_PER_POSITION + bytesWritten); } } - private void growCapacity() + @Override + public void appendRange(ValueBlock block, int offset, int length) { - int newSize = calculateNewArraySize(valueIsNull.length, initialEntryCount); - valueIsNull = Arrays.copyOf(valueIsNull, newSize); - offsets = Arrays.copyOf(offsets, newSize + 1); - updateRetainedSize(); + if (length == 0) { + return; + } + if (length == 1) { + append(block, offset); + return; + } + + ensureCapacity(positionCount + length); + + VariableWidthBlock variableWidthBlock = (VariableWidthBlock) block; + int rawArrayBase = variableWidthBlock.getRawArrayBase(); + int[] rawOffsets = variableWidthBlock.getRawOffsets(); + int startValueOffset = rawOffsets[rawArrayBase + offset]; + int totalSize = rawOffsets[rawArrayBase + offset + length] - startValueOffset; + // grow the buffer for the new data + ensureFreeSpace(totalSize); + + Slice sourceSlice = variableWidthBlock.getRawSlice(); + System.arraycopy(sourceSlice.byteArray(), sourceSlice.byteArrayOffset() + startValueOffset, bytes, offsets[positionCount], totalSize); + + // update offsets for copied data + int offsetDelta = offsets[positionCount] - rawOffsets[rawArrayBase + offset]; + for (int i = 0; i < length; i++) { + offsets[positionCount + i + 1] = rawOffsets[rawArrayBase + offset + i + 1] + offsetDelta; + } + + // update nulls + boolean[] rawValueIsNull = variableWidthBlock.getRawValueIsNull(); + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + if (rawValueIsNull[rawArrayBase + offset + i]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + hasNonNullValue = true; + } + } + } + else { + hasNonNullValue = true; + } + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * SIZE_IN_BYTES_PER_POSITION); + } } - private void ensureFreeSpace(int extraBytesCapacity) + @Override + public void appendPositions(ValueBlock block, int[] positions, int offset, int length) { - int requiredSize = offsets[positionCount] + extraBytesCapacity; - if (bytes.length < requiredSize) { - int newBytesLength = max(bytes.length, initialSliceOutputSize); - if (requiredSize > newBytesLength) { - newBytesLength = max(requiredSize, calculateNewArraySize(newBytesLength)); + if (length == 0) { + return; + } + if (length == 1) { + append(block, positions[offset]); + return; + } + + ensureCapacity(positionCount + length); + + VariableWidthBlock variableWidthBlock = (VariableWidthBlock) block; + int rawArrayBase = variableWidthBlock.getRawArrayBase(); + int[] rawOffsets = variableWidthBlock.getRawOffsets(); + + // update the offsets and compute the total size + int initialOffset = offsets[positionCount]; + int totalSize = 0; + for (int i = 0; i < length; i++) { + int position = positions[offset + i]; + totalSize += rawOffsets[rawArrayBase + position + 1] - rawOffsets[rawArrayBase + position]; + offsets[positionCount + i + 1] = initialOffset + totalSize; + } + // grow the buffer for the new data + ensureFreeSpace(totalSize); + + // copy values to buffer + Slice rawSlice = variableWidthBlock.getRawSlice(); + byte[] sourceBytes = rawSlice.byteArray(); + int sourceBytesOffset = rawSlice.byteArrayOffset(); + for (int i = 0; i < length; i++) { + int position = positions[offset + i]; + int sourceStart = rawOffsets[rawArrayBase + position]; + int sourceLength = rawOffsets[rawArrayBase + position + 1] - sourceStart; + System.arraycopy(sourceBytes, sourceBytesOffset + sourceStart, bytes, offsets[positionCount + i], sourceLength); + totalSize += sourceLength; + } + + // update nulls + boolean[] rawValueIsNull = variableWidthBlock.getRawValueIsNull(); + if (rawValueIsNull != null) { + for (int i = 0; i < length; i++) { + int rawPosition = positions[offset + i] + rawArrayBase; + if (rawValueIsNull[rawPosition]) { + valueIsNull[positionCount + i] = true; + hasNullValue = true; + } + else { + hasNonNullValue = true; + } } - bytes = Arrays.copyOf(bytes, newBytesLength); - updateRetainedSize(); + } + else { + hasNonNullValue = true; + } + positionCount += length; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(length * SIZE_IN_BYTES_PER_POSITION + totalSize); } } - private void updateRetainedSize() + @Override + public BlockBuilder appendNull() { - arraysRetainedSizeInBytes = sizeOf(valueIsNull) + sizeOf(offsets); + hasNullValue = true; + entryAdded(0, true); + return this; + } + + private void entryAdded(int bytesWritten, boolean isNull) + { + ensureCapacity(positionCount + 1); + + valueIsNull[positionCount] = isNull; + offsets[positionCount + 1] = offsets[positionCount] + bytesWritten; + + positionCount++; + hasNonNullValue |= !isNull; + + if (blockBuilderStatus != null) { + blockBuilderStatus.addBytes(SIZE_IN_BYTES_PER_POSITION + bytesWritten); + } } @Override @@ -184,6 +376,35 @@ private int getOffset(int position) return offsets[position]; } + private void ensureCapacity(int capacity) + { + if (valueIsNull.length >= capacity) { + return; + } + + int newSize = calculateNewArraySize(capacity, initialEntryCount); + valueIsNull = Arrays.copyOf(valueIsNull, newSize); + offsets = Arrays.copyOf(offsets, newSize + 1); + updateRetainedSize(); + } + + private void ensureFreeSpace(int extraBytesCapacity) + { + int requiredSize = offsets[positionCount] + extraBytesCapacity; + if (bytes.length >= requiredSize) { + return; + } + + int newSize = calculateNewArraySize(requiredSize, initialSliceOutputSize); + bytes = Arrays.copyOf(bytes, newSize); + updateRetainedSize(); + } + + private void updateRetainedSize() + { + arraysRetainedSizeInBytes = sizeOf(valueIsNull) + sizeOf(offsets) + sizeOf(bytes); + } + @Override public String toString() { diff --git a/core/trino-spi/src/test/java/io/trino/spi/block/AbstractTestBlockBuilder.java b/core/trino-spi/src/test/java/io/trino/spi/block/AbstractTestBlockBuilder.java new file mode 100644 index 000000000000..bcc0057aded7 --- /dev/null +++ b/core/trino-spi/src/test/java/io/trino/spi/block/AbstractTestBlockBuilder.java @@ -0,0 +1,236 @@ +/* + * 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 io.trino.spi.block; + +import com.google.common.collect.Iterables; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static java.util.Collections.nCopies; +import static org.assertj.core.api.Assertions.assertThat; + +public abstract class AbstractTestBlockBuilder +{ + protected abstract BlockBuilder createBlockBuilder(); + + protected abstract List getTestValues(); + + protected abstract T getUnusedTestValue(); + + protected abstract ValueBlock blockFromValues(Iterable values); + + protected abstract List blockToValues(ValueBlock valueBlock); + + @Test + public void verifyTestData() + { + List values = getTestValues(); + assertThat(values) + .hasSize(5) + .doesNotHaveDuplicates() + .doesNotContainNull() + .doesNotContain(getUnusedTestValue()); + + ValueBlock valueBlock = blockFromValues(values); + assertThat(blockToValues(valueBlock)).isEqualTo(values); + } + + @Test + public void testAppend() + { + List values = getTestValues(); + ValueBlock inputValues = createOffsetBlock(values); + + BlockBuilder blockBuilder = createBlockBuilder(); + blockBuilder.append(inputValues, 1); + blockBuilder.append(inputValues, 3); + blockBuilder.append(inputValues, 1); + blockBuilder.append(inputValues, 3); + ValueBlock valueBlock = blockBuilder.buildValueBlock(); + + assertThat(valueBlock.mayHaveNull()).isFalse(); + + List actualValues = blockToValues(valueBlock); + assertThat(actualValues).containsExactly(values.get(1), values.get(3), values.get(1), values.get(3)); + } + + @Test + public void testAppendWithNulls() + { + List values = getTestValues(); + ValueBlock inputValues = createOffsetBlockWithOddPositionsNull(values); + + BlockBuilder blockBuilder = createBlockBuilder(); + blockBuilder.append(inputValues, 1); + blockBuilder.append(inputValues, 3); + ValueBlock valueBlock = blockBuilder.buildValueBlock(); + + assertThat(valueBlock.mayHaveNull()).isTrue(); + + List actualValues = blockToValues(valueBlock); + assertThat(actualValues).hasSize(2).containsOnlyNulls(); + + // add a non-null value + blockBuilder.append(inputValues, 2); + valueBlock = blockBuilder.buildValueBlock(); + + actualValues = blockToValues(valueBlock); + assertThat(actualValues).containsExactly(null, null, values.get(2)); + } + + @Test + public void testAppendRepeated() + { + List values = getTestValues(); + ValueBlock inputValues = createOffsetBlock(values); + + BlockBuilder blockBuilder = createBlockBuilder(); + blockBuilder.appendRepeated(inputValues, 1, 10); + blockBuilder.appendRepeated(inputValues, 3, 10); + ValueBlock valueBlock = blockBuilder.buildValueBlock(); + + assertThat(valueBlock.mayHaveNull()).isFalse(); + + List actualValues = blockToValues(valueBlock); + assertThat(actualValues).containsExactlyElementsOf(Iterables.concat(nCopies(10, values.get(1)), nCopies(10, values.get(3)))); + } + + @Test + public void testAppendRepeatedWithNulls() + { + List values = getTestValues(); + ValueBlock inputValues = createOffsetBlockWithOddPositionsNull(values); + + BlockBuilder blockBuilder = createBlockBuilder(); + blockBuilder.appendRepeated(inputValues, 1, 10); + blockBuilder.appendRepeated(inputValues, 3, 10); + ValueBlock valueBlock = blockBuilder.buildValueBlock(); + + assertThat(valueBlock.mayHaveNull()).isTrue(); + + List actualValues = blockToValues(valueBlock); + assertThat(actualValues).hasSize(20).containsOnlyNulls(); + + // an all-null block should be converted to a RunLengthEncodedBlock + assertThat(blockBuilder.build()).isInstanceOf(RunLengthEncodedBlock.class); + + // add some non-null values + blockBuilder.appendRepeated(inputValues, 2, 10); + valueBlock = blockBuilder.buildValueBlock(); + + actualValues = blockToValues(valueBlock); + assertThat(actualValues).containsExactlyElementsOf(Iterables.concat(nCopies(20, null), nCopies(10, values.get(2)))); + } + + @Test + public void testAppendRange() + { + List values = getTestValues(); + ValueBlock inputValues = createOffsetBlock(values); + + BlockBuilder blockBuilder = createBlockBuilder(); + blockBuilder.appendRange(inputValues, 1, 3); + blockBuilder.appendRange(inputValues, 2, 3); + ValueBlock valueBlock = blockBuilder.buildValueBlock(); + + assertThat(valueBlock.mayHaveNull()).isFalse(); + + List actualValues = blockToValues(valueBlock); + assertThat(actualValues).containsExactlyElementsOf(Iterables.concat(values.subList(1, 4), values.subList(2, 5))); + } + + @Test + public void testAppendRangeWithNulls() + { + List values = getTestValues(); + ValueBlock inputValues = createOffsetBlockWithOddPositionsNull(values); + + BlockBuilder blockBuilder = createBlockBuilder(); + blockBuilder.appendRange(inputValues, 1, 3); + blockBuilder.appendRange(inputValues, 2, 3); + ValueBlock valueBlock = blockBuilder.buildValueBlock(); + + assertThat(valueBlock.mayHaveNull()).isTrue(); + + List actualValues = blockToValues(valueBlock); + assertThat(actualValues).containsExactly(null, values.get(2), null, values.get(2), null, values.get(4)); + } + + @Test + public void testAppendPositions() + { + List values = getTestValues(); + ValueBlock inputValues = createOffsetBlock(values); + + BlockBuilder blockBuilder = createBlockBuilder(); + blockBuilder.appendPositions(inputValues, new int[] {-100, 1, 3, 2, -100}, 1, 3); + blockBuilder.appendPositions(inputValues, new int[] {-100, 4, 0, -100}, 1, 2); + ValueBlock valueBlock = blockBuilder.buildValueBlock(); + + assertThat(valueBlock.mayHaveNull()).isFalse(); + + List actualValues = blockToValues(valueBlock); + assertThat(actualValues).containsExactly(values.get(1), values.get(3), values.get(2), values.get(4), values.get(0)); + } + + @Test + public void testAppendPositionsWithNull() + { + List values = getTestValues(); + ValueBlock inputValues = createOffsetBlockWithOddPositionsNull(values); + + BlockBuilder blockBuilder = createBlockBuilder(); + blockBuilder.appendPositions(inputValues, new int[] {-100, 1, 3, 2, -100}, 1, 3); + blockBuilder.appendPositions(inputValues, new int[] {-100, 4, 0, -100}, 1, 2); + ValueBlock valueBlock = blockBuilder.buildValueBlock(); + + assertThat(valueBlock.mayHaveNull()).isTrue(); + + List actualValues = blockToValues(valueBlock); + assertThat(actualValues).containsExactly(null, null, values.get(2), values.get(4), values.get(0)); + } + + /** + * Create a block that is offset from the start of the underlying array + */ + private ValueBlock createOffsetBlock(List values) + { + return blockFromValues(Iterables.concat(nCopies(2, getUnusedTestValue()), values, nCopies(2, getUnusedTestValue()))) + .getRegion(2, values.size()); + } + + /** + * Create a block that is offset from the start of the underlying array + */ + private ValueBlock createOffsetBlockWithOddPositionsNull(List values) + { + ArrayList blockValues = new ArrayList<>(); + blockValues.add(getUnusedTestValue()); + blockValues.add(getUnusedTestValue()); + for (int i = 0; i < values.size(); i++) { + T value = values.get(i); + if (i % 2 == 0) { + blockValues.add(value); + } + else { + blockValues.add(null); + } + } + blockValues.add(getUnusedTestValue()); + blockValues.add(getUnusedTestValue()); + return blockFromValues(blockValues).getRegion(2, values.size()); + } +} diff --git a/core/trino-spi/src/test/java/io/trino/spi/block/TestArrayBlockBuilder.java b/core/trino-spi/src/test/java/io/trino/spi/block/TestArrayBlockBuilder.java index 511961cb8260..689504f8f927 100644 --- a/core/trino-spi/src/test/java/io/trino/spi/block/TestArrayBlockBuilder.java +++ b/core/trino-spi/src/test/java/io/trino/spi/block/TestArrayBlockBuilder.java @@ -15,13 +15,19 @@ import org.junit.jupiter.api.Test; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + import static io.airlift.slice.SizeOf.instanceSize; import static io.trino.spi.type.BigintType.BIGINT; +import static io.trino.spi.type.VarcharType.VARCHAR; import static java.lang.Long.BYTES; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; public class TestArrayBlockBuilder + extends AbstractTestBlockBuilder> { // ArrayBlockBuilder: isNull, offset, 3 * value (FixedWidthBlockBuilder: isNull, value) private static final int THREE_INTS_ENTRY_SIZE = Byte.BYTES + Integer.BYTES + 3 * (Byte.BYTES + Long.BYTES); @@ -107,4 +113,77 @@ private static void assertIsAllNulls(Block block, int expectedPositionCount) assertThat(block.isNull(0)).isTrue(); } } + + @Override + protected BlockBuilder createBlockBuilder() + { + return new ArrayBlockBuilder(new VariableWidthBlockBuilder(null, 1, 100), null, 1); + } + + @Override + protected List> getTestValues() + { + return List.of( + List.of("a", "apple", "ape"), + Arrays.asList("b", null, "bear", "break"), + List.of("c", "cherry"), + Arrays.asList("d", "date", "dinosaur", null, "dirt"), + List.of("e", "eggplant", "empty", "")); + } + + @Override + protected List getUnusedTestValue() + { + return List.of("unused", "ignore me"); + } + + @Override + protected ValueBlock blockFromValues(Iterable> values) + { + ArrayBlockBuilder blockBuilder = new ArrayBlockBuilder(new VariableWidthBlockBuilder(null, 1, 100), null, 1); + for (List array : values) { + if (array == null) { + blockBuilder.appendNull(); + } + else { + blockBuilder.buildEntry(elementBuilder -> { + for (String entry : array) { + if (entry == null) { + elementBuilder.appendNull(); + } + else { + VARCHAR.writeString(elementBuilder, entry); + } + } + }); + } + } + return blockBuilder.buildValueBlock(); + } + + @Override + protected List> blockToValues(ValueBlock valueBlock) + { + ArrayBlock block = (ArrayBlock) valueBlock; + List> actualValues = new ArrayList<>(block.getPositionCount()); + for (int i = 0; i < block.getPositionCount(); i++) { + if (block.isNull(i)) { + actualValues.add(null); + } + else { + Block array = block.getArray(i); + ArrayList arrayBuilder = new ArrayList<>(); + for (int j = 0; j < array.getPositionCount(); j++) { + if (array.isNull(j)) { + arrayBuilder.add(null); + } + else { + arrayBuilder.add(VARCHAR.getSlice(array, j).toStringUtf8()); + } + } + actualValues.add(arrayBuilder); + } + } + return actualValues; + } } diff --git a/core/trino-spi/src/test/java/io/trino/spi/block/TestByteArrayBlockBuilder.java b/core/trino-spi/src/test/java/io/trino/spi/block/TestByteArrayBlockBuilder.java new file mode 100644 index 000000000000..91463d2a473f --- /dev/null +++ b/core/trino-spi/src/test/java/io/trino/spi/block/TestByteArrayBlockBuilder.java @@ -0,0 +1,70 @@ +/* + * 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 io.trino.spi.block; + +import java.util.ArrayList; +import java.util.List; + +public class TestByteArrayBlockBuilder + extends AbstractTestBlockBuilder +{ + @Override + protected BlockBuilder createBlockBuilder() + { + return new ByteArrayBlockBuilder(null, 1); + } + + @Override + protected List getTestValues() + { + return List.of((byte) 10, (byte) 11, (byte) 12, (byte) 13, (byte) 14); + } + + @Override + protected Byte getUnusedTestValue() + { + return (byte) -1; + } + + @Override + protected ValueBlock blockFromValues(Iterable values) + { + ByteArrayBlockBuilder blockBuilder = new ByteArrayBlockBuilder(null, 1); + for (Byte value : values) { + if (value == null) { + blockBuilder.appendNull(); + } + else { + blockBuilder.writeByte(value); + } + } + return blockBuilder.buildValueBlock(); + } + + @Override + protected List blockToValues(ValueBlock valueBlock) + { + ByteArrayBlock byteArrayBlock = (ByteArrayBlock) valueBlock; + List actualValues = new ArrayList<>(byteArrayBlock.getPositionCount()); + for (int i = 0; i < byteArrayBlock.getPositionCount(); i++) { + if (byteArrayBlock.isNull(i)) { + actualValues.add(null); + } + else { + actualValues.add(byteArrayBlock.getByte(i)); + } + } + return actualValues; + } +} diff --git a/core/trino-spi/src/test/java/io/trino/spi/block/TestFixed12BlockBuilder.java b/core/trino-spi/src/test/java/io/trino/spi/block/TestFixed12BlockBuilder.java new file mode 100644 index 000000000000..392125972bfa --- /dev/null +++ b/core/trino-spi/src/test/java/io/trino/spi/block/TestFixed12BlockBuilder.java @@ -0,0 +1,72 @@ +/* + * 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 io.trino.spi.block; + +import java.util.ArrayList; +import java.util.List; + +public class TestFixed12BlockBuilder + extends AbstractTestBlockBuilder +{ + @Override + protected BlockBuilder createBlockBuilder() + { + return new Fixed12BlockBuilder(null, 1); + } + + @Override + protected List getTestValues() + { + return List.of(new Fixed12(90, 10), new Fixed12(91, 11), new Fixed12(92, 12), new Fixed12(93, 13), new Fixed12(94, 14)); + } + + @Override + protected Fixed12 getUnusedTestValue() + { + return new Fixed12(-1, -2); + } + + @Override + protected ValueBlock blockFromValues(Iterable values) + { + Fixed12BlockBuilder blockBuilder = new Fixed12BlockBuilder(null, 1); + for (Fixed12 value : values) { + if (value == null) { + blockBuilder.appendNull(); + } + else { + blockBuilder.writeFixed12(value.first(), value.second()); + } + } + return blockBuilder.buildValueBlock(); + } + + @Override + protected List blockToValues(ValueBlock valueBlock) + { + Fixed12Block fixed12ArrayBlock = (Fixed12Block) valueBlock; + List actualValues = new ArrayList<>(fixed12ArrayBlock.getPositionCount()); + for (int i = 0; i < fixed12ArrayBlock.getPositionCount(); i++) { + if (fixed12ArrayBlock.isNull(i)) { + actualValues.add(null); + } + else { + actualValues.add(new Fixed12(fixed12ArrayBlock.getFixed12First(i), fixed12ArrayBlock.getFixed12Second(i))); + } + } + return actualValues; + } + + public record Fixed12(long first, int second) {} +} diff --git a/core/trino-spi/src/test/java/io/trino/spi/block/TestInt128ArrayBlockBuilder.java b/core/trino-spi/src/test/java/io/trino/spi/block/TestInt128ArrayBlockBuilder.java new file mode 100644 index 000000000000..e25fe5af37f8 --- /dev/null +++ b/core/trino-spi/src/test/java/io/trino/spi/block/TestInt128ArrayBlockBuilder.java @@ -0,0 +1,72 @@ +/* + * 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 io.trino.spi.block; + +import io.trino.spi.type.Int128; + +import java.util.ArrayList; +import java.util.List; + +public class TestInt128ArrayBlockBuilder + extends AbstractTestBlockBuilder +{ + @Override + protected BlockBuilder createBlockBuilder() + { + return new Int128ArrayBlockBuilder(null, 1); + } + + @Override + protected List getTestValues() + { + return List.of(Int128.valueOf(90, 10), Int128.valueOf(91, 11), Int128.valueOf(92, 12), Int128.valueOf(93, 13), Int128.valueOf(94, 14)); + } + + @Override + protected Int128 getUnusedTestValue() + { + return Int128.valueOf(-1, -2); + } + + @Override + protected ValueBlock blockFromValues(Iterable values) + { + Int128ArrayBlockBuilder blockBuilder = new Int128ArrayBlockBuilder(null, 1); + for (Int128 value : values) { + if (value == null) { + blockBuilder.appendNull(); + } + else { + blockBuilder.writeInt128(value.getHigh(), value.getLow()); + } + } + return blockBuilder.buildValueBlock(); + } + + @Override + protected List blockToValues(ValueBlock valueBlock) + { + Int128ArrayBlock int128ArrayBlock = (Int128ArrayBlock) valueBlock; + List actualValues = new ArrayList<>(int128ArrayBlock.getPositionCount()); + for (int i = 0; i < int128ArrayBlock.getPositionCount(); i++) { + if (int128ArrayBlock.isNull(i)) { + actualValues.add(null); + } + else { + actualValues.add(int128ArrayBlock.getInt128(i)); + } + } + return actualValues; + } +} diff --git a/core/trino-spi/src/test/java/io/trino/spi/block/TestIntArrayBlockBuilder.java b/core/trino-spi/src/test/java/io/trino/spi/block/TestIntArrayBlockBuilder.java new file mode 100644 index 000000000000..9996772166fd --- /dev/null +++ b/core/trino-spi/src/test/java/io/trino/spi/block/TestIntArrayBlockBuilder.java @@ -0,0 +1,70 @@ +/* + * 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 io.trino.spi.block; + +import java.util.ArrayList; +import java.util.List; + +public class TestIntArrayBlockBuilder + extends AbstractTestBlockBuilder +{ + @Override + protected BlockBuilder createBlockBuilder() + { + return new IntArrayBlockBuilder(null, 1); + } + + @Override + protected List getTestValues() + { + return List.of(10, 11, 12, 13, 14); + } + + @Override + protected Integer getUnusedTestValue() + { + return -1; + } + + @Override + protected ValueBlock blockFromValues(Iterable values) + { + IntArrayBlockBuilder blockBuilder = new IntArrayBlockBuilder(null, 1); + for (Integer value : values) { + if (value == null) { + blockBuilder.appendNull(); + } + else { + blockBuilder.writeInt(value); + } + } + return blockBuilder.buildValueBlock(); + } + + @Override + protected List blockToValues(ValueBlock valueBlock) + { + IntArrayBlock intArrayBlock = (IntArrayBlock) valueBlock; + List actualValues = new ArrayList<>(intArrayBlock.getPositionCount()); + for (int i = 0; i < intArrayBlock.getPositionCount(); i++) { + if (intArrayBlock.isNull(i)) { + actualValues.add(null); + } + else { + actualValues.add(intArrayBlock.getInt(i)); + } + } + return actualValues; + } +} diff --git a/core/trino-spi/src/test/java/io/trino/spi/block/TestLongArrayBlockBuilder.java b/core/trino-spi/src/test/java/io/trino/spi/block/TestLongArrayBlockBuilder.java new file mode 100644 index 000000000000..88766a0f3e67 --- /dev/null +++ b/core/trino-spi/src/test/java/io/trino/spi/block/TestLongArrayBlockBuilder.java @@ -0,0 +1,70 @@ +/* + * 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 io.trino.spi.block; + +import java.util.ArrayList; +import java.util.List; + +public class TestLongArrayBlockBuilder + extends AbstractTestBlockBuilder +{ + @Override + protected BlockBuilder createBlockBuilder() + { + return new LongArrayBlockBuilder(null, 1); + } + + @Override + protected List getTestValues() + { + return List.of(10L, 11L, 12L, 13L, 14L); + } + + @Override + protected Long getUnusedTestValue() + { + return -1L; + } + + @Override + protected ValueBlock blockFromValues(Iterable values) + { + LongArrayBlockBuilder blockBuilder = new LongArrayBlockBuilder(null, 1); + for (Long value : values) { + if (value == null) { + blockBuilder.appendNull(); + } + else { + blockBuilder.writeLong(value); + } + } + return blockBuilder.buildValueBlock(); + } + + @Override + protected List blockToValues(ValueBlock valueBlock) + { + LongArrayBlock longArrayBlock = (LongArrayBlock) valueBlock; + List actualValues = new ArrayList<>(longArrayBlock.getPositionCount()); + for (int i = 0; i < longArrayBlock.getPositionCount(); i++) { + if (longArrayBlock.isNull(i)) { + actualValues.add(null); + } + else { + actualValues.add(longArrayBlock.getLong(i)); + } + } + return actualValues; + } +} diff --git a/core/trino-spi/src/test/java/io/trino/spi/block/TestMapBlockBuilder.java b/core/trino-spi/src/test/java/io/trino/spi/block/TestMapBlockBuilder.java new file mode 100644 index 000000000000..137d0cc6c2cb --- /dev/null +++ b/core/trino-spi/src/test/java/io/trino/spi/block/TestMapBlockBuilder.java @@ -0,0 +1,101 @@ +/* + * 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 io.trino.spi.block; + +import io.trino.spi.type.MapType; +import io.trino.spi.type.TypeOperators; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static io.trino.spi.type.IntegerType.INTEGER; +import static io.trino.spi.type.VarcharType.VARCHAR; + +public class TestMapBlockBuilder + extends AbstractTestBlockBuilder> +{ + private static final TypeOperators TYPE_OPERATORS = new TypeOperators(); + private static final MapType MAP_TYPE = new MapType(VARCHAR, INTEGER, TYPE_OPERATORS); + + @Override + protected BlockBuilder createBlockBuilder() + { + return new MapBlockBuilder(MAP_TYPE, null, 1); + } + + @Override + protected List> getTestValues() + { + return List.of( + Map.of("a", 0, "apple", 1, "ape", 2), + Map.of("b", 3, "banana", 4, "bear", 5, "break", 6), + Map.of("c", 7, "cherry", 8), + Map.of("d", 9, "date", 10, "dinosaur", 11, "dinner", 12, "dirt", 13), + Map.of("e", 14, "eggplant", 15, "empty", 16, "", 17)); + } + + @Override + protected Map getUnusedTestValue() + { + return Map.of("unused", -1, "ignore me", -2); + } + + @Override + protected ValueBlock blockFromValues(Iterable> maps) + { + MapBlockBuilder blockBuilder = new MapBlockBuilder(MAP_TYPE, null, 1); + for (Map map : maps) { + if (map == null) { + blockBuilder.appendNull(); + } + else { + blockBuilder.buildEntry((keyBuilder, valueBuilder) -> { + for (Map.Entry entry : map.entrySet()) { + VARCHAR.writeString(keyBuilder, entry.getKey()); + INTEGER.writeLong(valueBuilder, entry.getValue()); + } + }); + } + } + return blockBuilder.buildValueBlock(); + } + + @Override + protected List> blockToValues(ValueBlock valueBlock) + { + MapBlock block = (MapBlock) valueBlock; + List> actualValues = new ArrayList<>(block.getPositionCount()); + for (int i = 0; i < block.getPositionCount(); i++) { + if (block.isNull(i)) { + actualValues.add(null); + } + else { + SqlMap sqlMap = block.getMap(i); + int rawOffset = sqlMap.getRawOffset(); + Block rawKeyBlock = sqlMap.getRawKeyBlock(); + Block rawValueBlock = sqlMap.getRawValueBlock(); + Map actualMap = new HashMap<>(); + for (int entryIndex = 0; entryIndex < sqlMap.getSize(); entryIndex++) { + actualMap.put( + VARCHAR.getSlice(rawKeyBlock, rawOffset + entryIndex).toStringUtf8(), + INTEGER.getInt(rawValueBlock, rawOffset + entryIndex)); + } + actualValues.add(actualMap); + } + } + return actualValues; + } +} diff --git a/core/trino-spi/src/test/java/io/trino/spi/block/TestRowBlockBuilder.java b/core/trino-spi/src/test/java/io/trino/spi/block/TestRowBlockBuilder.java index c46a57ded70e..6befb3606c99 100644 --- a/core/trino-spi/src/test/java/io/trino/spi/block/TestRowBlockBuilder.java +++ b/core/trino-spi/src/test/java/io/trino/spi/block/TestRowBlockBuilder.java @@ -16,10 +16,17 @@ import com.google.common.collect.ImmutableList; import org.junit.jupiter.api.Test; +import java.util.ArrayList; +import java.util.List; + import static io.trino.spi.type.BigintType.BIGINT; +import static io.trino.spi.type.BooleanType.BOOLEAN; +import static io.trino.spi.type.IntegerType.INTEGER; +import static io.trino.spi.type.VarcharType.VARCHAR; import static org.assertj.core.api.Assertions.assertThat; public class TestRowBlockBuilder + extends AbstractTestBlockBuilder { @Test public void testBuilderProducesNullRleForNullRows() @@ -53,4 +60,73 @@ private static void assertIsAllNulls(Block block, int expectedPositionCount) assertThat(block.isNull(0)).isTrue(); } } + + @Override + protected BlockBuilder createBlockBuilder() + { + return new RowBlockBuilder(List.of(VARCHAR, INTEGER, BOOLEAN), null, 1); + } + + @Override + protected List getTestValues() + { + return List.of( + new TestRow("apple", 2, true), + new TestRow("bear", 5, false), + new TestRow(null, 7, true), + new TestRow("dinosaur", 9, false), + new TestRow("", 22, true)); + } + + @Override + protected TestRow getUnusedTestValue() + { + return new TestRow("unused", -1, false); + } + + @Override + protected ValueBlock blockFromValues(Iterable values) + { + RowBlockBuilder blockBuilder = new RowBlockBuilder(List.of(VARCHAR, INTEGER, BOOLEAN), null, 1); + for (TestRow row : values) { + if (row == null) { + blockBuilder.appendNull(); + } + else { + blockBuilder.buildEntry(fieldBuilders -> { + if (row.name() == null) { + fieldBuilders.get(0).appendNull(); + } + else { + VARCHAR.writeString(fieldBuilders.get(0), row.name()); + } + INTEGER.writeLong(fieldBuilders.get(1), row.number()); + BOOLEAN.writeBoolean(fieldBuilders.get(2), row.flag()); + }); + } + } + return blockBuilder.buildValueBlock(); + } + + @Override + protected List blockToValues(ValueBlock valueBlock) + { + RowBlock block = (RowBlock) valueBlock; + List actualValues = new ArrayList<>(block.getPositionCount()); + for (int i = 0; i < block.getPositionCount(); i++) { + if (block.isNull(i)) { + actualValues.add(null); + } + else { + SqlRow sqlRow = block.getRow(i); + actualValues.add(new TestRow( + (String) VARCHAR.getObjectValue(null, sqlRow.getUnderlyingFieldBlock(0), sqlRow.getUnderlyingFieldPosition(0)), + INTEGER.getInt(sqlRow.getUnderlyingFieldBlock(1), sqlRow.getUnderlyingFieldPosition(1)), + BOOLEAN.getBoolean(sqlRow.getUnderlyingFieldBlock(2), sqlRow.getUnderlyingFieldPosition(2)))); + } + } + return actualValues; + } + + public record TestRow(String name, int number, boolean flag) {} } diff --git a/core/trino-spi/src/test/java/io/trino/spi/block/TestShortArrayBlockBuilder.java b/core/trino-spi/src/test/java/io/trino/spi/block/TestShortArrayBlockBuilder.java new file mode 100644 index 000000000000..031e16b81079 --- /dev/null +++ b/core/trino-spi/src/test/java/io/trino/spi/block/TestShortArrayBlockBuilder.java @@ -0,0 +1,70 @@ +/* + * 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 io.trino.spi.block; + +import java.util.ArrayList; +import java.util.List; + +public class TestShortArrayBlockBuilder + extends AbstractTestBlockBuilder +{ + @Override + protected BlockBuilder createBlockBuilder() + { + return new ShortArrayBlockBuilder(null, 1); + } + + @Override + protected List getTestValues() + { + return List.of((short) 10, (short) 11, (short) 12, (short) 13, (short) 14); + } + + @Override + protected Short getUnusedTestValue() + { + return (short) -1; + } + + @Override + protected ValueBlock blockFromValues(Iterable values) + { + ShortArrayBlockBuilder blockBuilder = new ShortArrayBlockBuilder(null, 1); + for (Short value : values) { + if (value == null) { + blockBuilder.appendNull(); + } + else { + blockBuilder.writeShort(value); + } + } + return blockBuilder.buildValueBlock(); + } + + @Override + protected List blockToValues(ValueBlock valueBlock) + { + ShortArrayBlock shortArrayBlock = (ShortArrayBlock) valueBlock; + List actualValues = new ArrayList<>(shortArrayBlock.getPositionCount()); + for (int i = 0; i < shortArrayBlock.getPositionCount(); i++) { + if (shortArrayBlock.isNull(i)) { + actualValues.add(null); + } + else { + actualValues.add(shortArrayBlock.getShort(i)); + } + } + return actualValues; + } +} diff --git a/core/trino-spi/src/test/java/io/trino/spi/block/TestVariableWidthBlockBuilder.java b/core/trino-spi/src/test/java/io/trino/spi/block/TestVariableWidthBlockBuilder.java index 881840b351cd..5e86f7d4da6f 100644 --- a/core/trino-spi/src/test/java/io/trino/spi/block/TestVariableWidthBlockBuilder.java +++ b/core/trino-spi/src/test/java/io/trino/spi/block/TestVariableWidthBlockBuilder.java @@ -13,17 +13,24 @@ */ package io.trino.spi.block; +import com.google.common.collect.ImmutableList; import io.airlift.slice.Slices; import org.junit.jupiter.api.Test; +import java.util.ArrayList; +import java.util.List; + import static io.airlift.slice.SizeOf.SIZE_OF_INT; import static io.airlift.slice.SizeOf.instanceSize; import static io.airlift.slice.SizeOf.sizeOf; +import static io.airlift.slice.Slices.utf8Slice; import static io.trino.spi.type.VarcharType.VARCHAR; import static java.lang.Math.ceil; +import static java.util.Collections.nCopies; import static org.assertj.core.api.Assertions.assertThat; public class TestVariableWidthBlockBuilder + extends AbstractTestBlockBuilder { private static final int BLOCK_BUILDER_INSTANCE_SIZE = instanceSize(VariableWidthBlockBuilder.class); private static final int VARCHAR_VALUE_SIZE = 7; @@ -78,6 +85,116 @@ public void testBuilderProducesNullRleForNullRows() assertIsAllNulls(blockBuilder().appendNull().appendNull().build(), 2); } + @Test + public void testAppendRepeatedEmpty() + { + VariableWidthBlockBuilder blockBuilder = new VariableWidthBlockBuilder(null, 1, 100); + ValueBlock value = VARCHAR.createBlockBuilder(null, 1) + .writeEntry(utf8Slice("ignored")) + .writeEntry(Slices.EMPTY_SLICE) + .writeEntry(utf8Slice("ignored")) + .buildValueBlock(); + blockBuilder.appendRepeated(value, 1, 10); + + List strings = toStrings(blockBuilder.buildValueBlock()); + assertThat(strings).isEqualTo(nCopies(10, "")); + } + + @Test + public void testAppendRepeatedSingle() + { + VariableWidthBlockBuilder blockBuilder = new VariableWidthBlockBuilder(null, 1, 100); + VariableWidthBlock value = VARCHAR.createBlockBuilder(null, 1) + .writeEntry(utf8Slice("ignored")) + .writeEntry(utf8Slice("ab")) + .writeEntry(utf8Slice("ignored")) + .buildValueBlock(); + blockBuilder.appendRepeated(value, 1, 1); + + List strings = toStrings(blockBuilder.buildValueBlock()); + assertThat(strings).isEqualTo(ImmutableList.of("ab")); + } + + @Test + public void testAppendRepeated1Byte() + { + VariableWidthBlockBuilder blockBuilder = new VariableWidthBlockBuilder(null, 1, 100); + VariableWidthBlock value = VARCHAR.createBlockBuilder(null, 1) + .writeEntry(utf8Slice("ignored")) + .writeEntry(utf8Slice("X")) + .writeEntry(utf8Slice("ignored")) + .writeEntry(utf8Slice("Y")) + .writeEntry(utf8Slice("ignored")) + .buildValueBlock(); + blockBuilder.appendRepeated(value, 1, 3); + blockBuilder.appendRepeated(value, 3, 2); + + List strings = toStrings(blockBuilder.buildValueBlock()); + assertThat(strings).isEqualTo(ImmutableList.of("X", "X", "X", "Y", "Y")); + } + + @Test + public void testAppendRepeated2Bytes() + { + VariableWidthBlockBuilder blockBuilder = new VariableWidthBlockBuilder(null, 1, 100); + VariableWidthBlock value = VARCHAR.createBlockBuilder(null, 1) + .writeEntry(utf8Slice("ignored")) + .writeEntry(utf8Slice("ab")) + .writeEntry(utf8Slice("ignored")) + .writeEntry(utf8Slice("Y")) + .writeEntry(utf8Slice("ignored")) + .buildValueBlock(); + blockBuilder.appendRepeated(value, 1, 3); + + List strings = toStrings(blockBuilder.buildValueBlock()); + assertThat(strings).isEqualTo(ImmutableList.of("ab", "ab", "ab")); + } + + @Test + public void testAppendRepeatedMultipleBytesOddNumberOfTimes() + { + VariableWidthBlockBuilder blockBuilder = new VariableWidthBlockBuilder(null, 1, 100); + VariableWidthBlock value = VARCHAR.createBlockBuilder(null, 1) + .writeEntry(utf8Slice("ignored")) + .writeEntry(utf8Slice("abc")) + .writeEntry(utf8Slice("ignored")) + .buildValueBlock(); + blockBuilder.appendRepeated(value, 1, 5); + + List strings = toStrings(blockBuilder.buildValueBlock()); + assertThat(strings).isEqualTo(nCopies(5, "abc")); + } + + @Test + public void testAppendRepeatedMultipleBytesEvenNumberOfTimes() + { + VariableWidthBlockBuilder blockBuilder = new VariableWidthBlockBuilder(null, 1, 100); + VariableWidthBlock value = VARCHAR.createBlockBuilder(null, 1) + .writeEntry(utf8Slice("ignored")) + .writeEntry(utf8Slice("abc")) + .writeEntry(utf8Slice("ignored")) + .buildValueBlock(); + blockBuilder.appendRepeated(value, 1, 6); + + List strings = toStrings(blockBuilder.buildValueBlock()); + assertThat(strings).isEqualTo(nCopies(6, "abc")); + } + + @Test + public void testAppendRepeatedMultipleBytesPowerOf2NumberOfTimes() + { + VariableWidthBlockBuilder blockBuilder = new VariableWidthBlockBuilder(null, 1, 100); + VariableWidthBlock value = VARCHAR.createBlockBuilder(null, 1) + .writeEntry(utf8Slice("ignored")) + .writeEntry(utf8Slice("abc")) + .writeEntry(utf8Slice("ignored")) + .buildValueBlock(); + blockBuilder.appendRepeated(value, 1, 8); + + List strings = toStrings(blockBuilder.buildValueBlock()); + assertThat(strings).isEqualTo(nCopies(8, "abc")); + } + private static BlockBuilder blockBuilder() { return new VariableWidthBlockBuilder(null, 10, 0); @@ -97,4 +214,62 @@ private static void assertIsAllNulls(Block block, int expectedPositionCount) assertThat(block.isNull(0)).isTrue(); } } + + private static List toStrings(VariableWidthBlock block) + { + ImmutableList.Builder list = ImmutableList.builder(); + for (int i = 0; i < block.getPositionCount(); i++) { + list.add(VARCHAR.getSlice(block, i).toStringUtf8()); + } + return list.build(); + } + + @Override + protected BlockBuilder createBlockBuilder() + { + return new VariableWidthBlockBuilder(null, 1, 100); + } + + @Override + protected List getTestValues() + { + return List.of("a", "bb", "cCc", "dddd", "eeEee"); + } + + @Override + protected String getUnusedTestValue() + { + return "unused"; + } + + @Override + protected ValueBlock blockFromValues(Iterable values) + { + VariableWidthBlockBuilder blockBuilder = new VariableWidthBlockBuilder(null, 1, 100); + for (String value : values) { + if (value == null) { + blockBuilder.appendNull(); + } + else { + blockBuilder.writeEntry(utf8Slice(value)); + } + } + return blockBuilder.buildValueBlock(); + } + + @Override + protected List blockToValues(ValueBlock valueBlock) + { + VariableWidthBlock block = (VariableWidthBlock) valueBlock; + List actualValues = new ArrayList<>(block.getPositionCount()); + for (int i = 0; i < block.getPositionCount(); i++) { + if (block.isNull(i)) { + actualValues.add(null); + } + else { + actualValues.add(block.getSlice(i).toStringUtf8()); + } + } + return actualValues; + } } From b51e407b256a469fb25bd37369d69728c776f3d1 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Tue, 1 Aug 2023 16:34:26 -0700 Subject: [PATCH 338/587] Convert PositionAppender to use BlockBuilder bulk append methods --- .../output/BytePositionsAppender.java | 212 ----------- .../output/Fixed12PositionsAppender.java | 229 ------------ .../output/Int128PositionsAppender.java | 227 ------------ .../operator/output/IntPositionsAppender.java | 212 ----------- .../output/LongPositionsAppender.java | 212 ----------- .../output/PositionsAppenderFactory.java | 37 +- .../output/PositionsAppenderUtil.java | 14 +- .../output/ShortPositionsAppender.java | 212 ----------- .../output/SlicePositionsAppender.java | 333 ------------------ .../output/TypedPositionsAppender.java | 5 + .../output/TestSlicePositionsAppender.java | 122 ------- 11 files changed, 16 insertions(+), 1799 deletions(-) delete mode 100644 core/trino-main/src/main/java/io/trino/operator/output/BytePositionsAppender.java delete mode 100644 core/trino-main/src/main/java/io/trino/operator/output/Fixed12PositionsAppender.java delete mode 100644 core/trino-main/src/main/java/io/trino/operator/output/Int128PositionsAppender.java delete mode 100644 core/trino-main/src/main/java/io/trino/operator/output/IntPositionsAppender.java delete mode 100644 core/trino-main/src/main/java/io/trino/operator/output/LongPositionsAppender.java delete mode 100644 core/trino-main/src/main/java/io/trino/operator/output/ShortPositionsAppender.java delete mode 100644 core/trino-main/src/main/java/io/trino/operator/output/SlicePositionsAppender.java delete mode 100644 core/trino-main/src/test/java/io/trino/operator/output/TestSlicePositionsAppender.java diff --git a/core/trino-main/src/main/java/io/trino/operator/output/BytePositionsAppender.java b/core/trino-main/src/main/java/io/trino/operator/output/BytePositionsAppender.java deleted file mode 100644 index 8599de929e4b..000000000000 --- a/core/trino-main/src/main/java/io/trino/operator/output/BytePositionsAppender.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * 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 io.trino.operator.output; - -import io.trino.spi.block.Block; -import io.trino.spi.block.ByteArrayBlock; -import io.trino.spi.block.RunLengthEncodedBlock; -import io.trino.spi.block.ValueBlock; -import it.unimi.dsi.fastutil.ints.IntArrayList; - -import java.util.Arrays; -import java.util.Optional; - -import static com.google.common.base.Preconditions.checkArgument; -import static io.airlift.slice.SizeOf.instanceSize; -import static io.airlift.slice.SizeOf.sizeOf; -import static io.trino.operator.output.PositionsAppenderUtil.calculateBlockResetSize; -import static io.trino.operator.output.PositionsAppenderUtil.calculateNewArraySize; -import static java.lang.Math.max; - -public class BytePositionsAppender - implements PositionsAppender -{ - private static final int INSTANCE_SIZE = instanceSize(BytePositionsAppender.class); - private static final Block NULL_VALUE_BLOCK = new ByteArrayBlock(1, Optional.of(new boolean[] {true}), new byte[1]); - - private boolean initialized; - private int initialEntryCount; - - private int positionCount; - private boolean hasNullValue; - private boolean hasNonNullValue; - - // it is assumed that these arrays are the same length - private boolean[] valueIsNull = new boolean[0]; - private byte[] values = new byte[0]; - - private long retainedSizeInBytes; - private long sizeInBytes; - - public BytePositionsAppender(int expectedEntries) - { - this.initialEntryCount = max(expectedEntries, 1); - - updateRetainedSize(); - } - - @Override - public void append(IntArrayList positions, ValueBlock block) - { - checkArgument(block instanceof ByteArrayBlock, "Block must be instance of %s", ByteArrayBlock.class); - - if (positions.isEmpty()) { - return; - } - int[] positionArray = positions.elements(); - int positionsSize = positions.size(); - ensureCapacity(positionCount + positionsSize); - - if (block.mayHaveNull()) { - for (int i = 0; i < positionsSize; i++) { - int position = positionArray[i]; - boolean isNull = block.isNull(position); - int positionIndex = positionCount + i; - if (isNull) { - valueIsNull[positionIndex] = true; - hasNullValue = true; - } - else { - values[positionIndex] = block.getByte(position, 0); - hasNonNullValue = true; - } - } - positionCount += positionsSize; - } - else { - for (int i = 0; i < positionsSize; i++) { - int position = positionArray[i]; - values[positionCount + i] = block.getByte(position, 0); - } - positionCount += positionsSize; - hasNonNullValue = true; - } - - updateSize(positionsSize); - } - - @Override - public void appendRle(ValueBlock block, int rlePositionCount) - { - checkArgument(block instanceof ByteArrayBlock, "Block must be instance of %s", ByteArrayBlock.class); - - if (rlePositionCount == 0) { - return; - } - int sourcePosition = 0; - ensureCapacity(positionCount + rlePositionCount); - if (block.isNull(sourcePosition)) { - Arrays.fill(valueIsNull, positionCount, positionCount + rlePositionCount, true); - hasNullValue = true; - } - else { - byte value = block.getByte(sourcePosition, 0); - Arrays.fill(values, positionCount, positionCount + rlePositionCount, value); - hasNonNullValue = true; - } - positionCount += rlePositionCount; - - updateSize(rlePositionCount); - } - - @Override - public void append(int sourcePosition, ValueBlock source) - { - checkArgument(source instanceof ByteArrayBlock, "Block must be instance of %s", ByteArrayBlock.class); - - ensureCapacity(positionCount + 1); - if (source.isNull(sourcePosition)) { - valueIsNull[positionCount] = true; - hasNullValue = true; - } - else { - values[positionCount] = source.getByte(sourcePosition, 0); - hasNonNullValue = true; - } - positionCount++; - - updateSize(1); - } - - @Override - public Block build() - { - Block result; - if (hasNonNullValue) { - result = new ByteArrayBlock(positionCount, hasNullValue ? Optional.of(valueIsNull) : Optional.empty(), values); - } - else { - result = RunLengthEncodedBlock.create(NULL_VALUE_BLOCK, positionCount); - } - reset(); - return result; - } - - @Override - public long getRetainedSizeInBytes() - { - return retainedSizeInBytes; - } - - @Override - public long getSizeInBytes() - { - return sizeInBytes; - } - - @Override - public void reset() - { - initialEntryCount = calculateBlockResetSize(positionCount); - initialized = false; - valueIsNull = new boolean[0]; - values = new byte[0]; - positionCount = 0; - sizeInBytes = 0; - hasNonNullValue = false; - hasNullValue = false; - updateRetainedSize(); - } - - private void ensureCapacity(int capacity) - { - if (values.length >= capacity) { - return; - } - - int newSize; - if (initialized) { - newSize = calculateNewArraySize(values.length); - } - else { - newSize = initialEntryCount; - initialized = true; - } - newSize = max(newSize, capacity); - - valueIsNull = Arrays.copyOf(valueIsNull, newSize); - values = Arrays.copyOf(values, newSize); - updateRetainedSize(); - } - - private void updateSize(long positionsSize) - { - sizeInBytes += ByteArrayBlock.SIZE_IN_BYTES_PER_POSITION * positionsSize; - } - - private void updateRetainedSize() - { - retainedSizeInBytes = INSTANCE_SIZE + sizeOf(valueIsNull) + sizeOf(values); - } -} diff --git a/core/trino-main/src/main/java/io/trino/operator/output/Fixed12PositionsAppender.java b/core/trino-main/src/main/java/io/trino/operator/output/Fixed12PositionsAppender.java deleted file mode 100644 index 685db0271293..000000000000 --- a/core/trino-main/src/main/java/io/trino/operator/output/Fixed12PositionsAppender.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * 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 io.trino.operator.output; - -import io.trino.spi.block.Block; -import io.trino.spi.block.Fixed12Block; -import io.trino.spi.block.RunLengthEncodedBlock; -import io.trino.spi.block.ValueBlock; -import it.unimi.dsi.fastutil.ints.IntArrayList; - -import java.util.Arrays; -import java.util.Optional; - -import static com.google.common.base.Preconditions.checkArgument; -import static io.airlift.slice.SizeOf.SIZE_OF_INT; -import static io.airlift.slice.SizeOf.instanceSize; -import static io.airlift.slice.SizeOf.sizeOf; -import static io.trino.operator.output.PositionsAppenderUtil.calculateBlockResetSize; -import static io.trino.operator.output.PositionsAppenderUtil.calculateNewArraySize; -import static java.lang.Math.max; - -public class Fixed12PositionsAppender - implements PositionsAppender -{ - private static final int INSTANCE_SIZE = instanceSize(Fixed12PositionsAppender.class); - private static final Block NULL_VALUE_BLOCK = new Fixed12Block(1, Optional.of(new boolean[] {true}), new int[3]); - - private boolean initialized; - private int initialEntryCount; - - private int positionCount; - private boolean hasNullValue; - private boolean hasNonNullValue; - - // it is assumed that these arrays are the same length - private boolean[] valueIsNull = new boolean[0]; - private int[] values = new int[0]; - - private long retainedSizeInBytes; - private long sizeInBytes; - - public Fixed12PositionsAppender(int expectedEntries) - { - this.initialEntryCount = max(expectedEntries, 1); - - updateRetainedSize(); - } - - @Override - public void append(IntArrayList positions, ValueBlock block) - { - checkArgument(block instanceof Fixed12Block, "Block must be instance of %s", Fixed12Block.class); - - if (positions.isEmpty()) { - return; - } - int[] positionArray = positions.elements(); - int positionsSize = positions.size(); - ensureCapacity(positionCount + positionsSize); - - if (block.mayHaveNull()) { - for (int i = 0; i < positionsSize; i++) { - int position = positionArray[i]; - boolean isNull = block.isNull(position); - if (isNull) { - valueIsNull[positionCount + i] = true; - hasNullValue = true; - } - else { - int valuesIndex = (positionCount + i) * 3; - values[valuesIndex] = block.getInt(position, 0); - values[valuesIndex + 1] = block.getInt(position, SIZE_OF_INT); - values[valuesIndex + 2] = block.getInt(position, SIZE_OF_INT + SIZE_OF_INT); - hasNonNullValue = true; - } - } - positionCount += positionsSize; - } - else { - for (int i = 0; i < positionsSize; i++) { - int position = positionArray[i]; - int valuesIndex = (positionCount + i) * 3; - values[valuesIndex] = block.getInt(position, 0); - values[valuesIndex + 1] = block.getInt(position, SIZE_OF_INT); - values[valuesIndex + 2] = block.getInt(position, SIZE_OF_INT + SIZE_OF_INT); - } - positionCount += positionsSize; - hasNonNullValue = true; - } - - updateSize(positionsSize); - } - - @Override - public void appendRle(ValueBlock block, int rlePositionCount) - { - checkArgument(block instanceof Fixed12Block, "Block must be instance of %s", Fixed12Block.class); - - if (rlePositionCount == 0) { - return; - } - int sourcePosition = 0; - ensureCapacity(positionCount + rlePositionCount); - if (block.isNull(sourcePosition)) { - Arrays.fill(valueIsNull, positionCount, positionCount + rlePositionCount, true); - hasNullValue = true; - } - else { - int valueHigh = block.getInt(sourcePosition, 0); - int valueMid = block.getInt(sourcePosition, SIZE_OF_INT); - int valueLow = block.getInt(sourcePosition, SIZE_OF_INT + SIZE_OF_INT); - int positionIndex = positionCount * 3; - for (int i = 0; i < rlePositionCount; i++) { - values[positionIndex] = valueHigh; - values[positionIndex + 1] = valueMid; - values[positionIndex + 2] = valueLow; - positionIndex += 3; - } - hasNonNullValue = true; - } - positionCount += rlePositionCount; - - updateSize(rlePositionCount); - } - - @Override - public void append(int sourcePosition, ValueBlock source) - { - checkArgument(source instanceof Fixed12Block, "Block must be instance of %s", Fixed12Block.class); - - ensureCapacity(positionCount + 1); - if (source.isNull(sourcePosition)) { - valueIsNull[positionCount] = true; - hasNullValue = true; - } - else { - int positionIndex = positionCount * 3; - values[positionIndex] = source.getInt(sourcePosition, 0); - values[positionIndex + 1] = source.getInt(sourcePosition, SIZE_OF_INT); - values[positionIndex + 2] = source.getInt(sourcePosition, SIZE_OF_INT + SIZE_OF_INT); - hasNonNullValue = true; - } - positionCount++; - - updateSize(1); - } - - @Override - public Block build() - { - Block result; - if (hasNonNullValue) { - result = new Fixed12Block(positionCount, hasNullValue ? Optional.of(valueIsNull) : Optional.empty(), values); - } - else { - result = RunLengthEncodedBlock.create(NULL_VALUE_BLOCK, positionCount); - } - reset(); - return result; - } - - @Override - public long getRetainedSizeInBytes() - { - return retainedSizeInBytes; - } - - @Override - public long getSizeInBytes() - { - return sizeInBytes; - } - - @Override - public void reset() - { - initialEntryCount = calculateBlockResetSize(positionCount); - initialized = false; - valueIsNull = new boolean[0]; - values = new int[0]; - positionCount = 0; - sizeInBytes = 0; - hasNonNullValue = false; - hasNullValue = false; - updateRetainedSize(); - } - - private void ensureCapacity(int capacity) - { - if (valueIsNull.length >= capacity) { - return; - } - - int newSize; - if (initialized) { - newSize = calculateNewArraySize(valueIsNull.length); - } - else { - newSize = initialEntryCount; - initialized = true; - } - newSize = max(newSize, capacity); - - valueIsNull = Arrays.copyOf(valueIsNull, newSize); - values = Arrays.copyOf(values, newSize * 3); - updateRetainedSize(); - } - - private void updateSize(long positionsSize) - { - sizeInBytes += Fixed12Block.SIZE_IN_BYTES_PER_POSITION * positionsSize; - } - - private void updateRetainedSize() - { - retainedSizeInBytes = INSTANCE_SIZE + sizeOf(valueIsNull) + sizeOf(values); - } -} diff --git a/core/trino-main/src/main/java/io/trino/operator/output/Int128PositionsAppender.java b/core/trino-main/src/main/java/io/trino/operator/output/Int128PositionsAppender.java deleted file mode 100644 index 251a7f25eff4..000000000000 --- a/core/trino-main/src/main/java/io/trino/operator/output/Int128PositionsAppender.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * 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 io.trino.operator.output; - -import io.trino.spi.block.Block; -import io.trino.spi.block.Int128ArrayBlock; -import io.trino.spi.block.RunLengthEncodedBlock; -import io.trino.spi.block.ValueBlock; -import it.unimi.dsi.fastutil.ints.IntArrayList; - -import java.util.Arrays; -import java.util.Optional; - -import static com.google.common.base.Preconditions.checkArgument; -import static io.airlift.slice.SizeOf.SIZE_OF_LONG; -import static io.airlift.slice.SizeOf.instanceSize; -import static io.airlift.slice.SizeOf.sizeOf; -import static io.trino.operator.output.PositionsAppenderUtil.calculateBlockResetSize; -import static io.trino.operator.output.PositionsAppenderUtil.calculateNewArraySize; -import static java.lang.Math.max; - -public class Int128PositionsAppender - implements PositionsAppender -{ - private static final int INSTANCE_SIZE = instanceSize(Int128PositionsAppender.class); - private static final Block NULL_VALUE_BLOCK = new Int128ArrayBlock(1, Optional.of(new boolean[] {true}), new long[2]); - - private boolean initialized; - private int initialEntryCount; - - private int positionCount; - private boolean hasNullValue; - private boolean hasNonNullValue; - - // it is assumed that these arrays are the same length - private boolean[] valueIsNull = new boolean[0]; - private long[] values = new long[0]; - - private long retainedSizeInBytes; - private long sizeInBytes; - - public Int128PositionsAppender(int expectedEntries) - { - this.initialEntryCount = max(expectedEntries, 1); - - updateRetainedSize(); - } - - @Override - public void append(IntArrayList positions, ValueBlock block) - { - checkArgument(block instanceof Int128ArrayBlock, "Block must be instance of %s", Int128ArrayBlock.class); - - if (positions.isEmpty()) { - return; - } - int[] positionArray = positions.elements(); - int positionsSize = positions.size(); - ensureCapacity(positionCount + positionsSize); - - if (block.mayHaveNull()) { - int positionIndex = positionCount * 2; - for (int i = 0; i < positionsSize; i++) { - int position = positionArray[i]; - boolean isNull = block.isNull(position); - - if (isNull) { - valueIsNull[positionCount + i] = true; - hasNullValue = true; - } - else { - values[positionIndex] = block.getLong(position, 0); - values[positionIndex + 1] = block.getLong(position, SIZE_OF_LONG); - hasNonNullValue = true; - } - positionIndex += 2; - } - positionCount += positionsSize; - } - else { - int positionIndex = positionCount * 2; - for (int i = 0; i < positionsSize; i++) { - int position = positionArray[i]; - values[positionIndex] = block.getLong(position, 0); - values[positionIndex + 1] = block.getLong(position, SIZE_OF_LONG); - positionIndex += 2; - } - positionCount += positionsSize; - hasNonNullValue = true; - } - - updateSize(positionsSize); - } - - @Override - public void appendRle(ValueBlock block, int rlePositionCount) - { - checkArgument(block instanceof Int128ArrayBlock, "Block must be instance of %s", Int128ArrayBlock.class); - - if (rlePositionCount == 0) { - return; - } - int sourcePosition = 0; - ensureCapacity(positionCount + rlePositionCount); - if (block.isNull(sourcePosition)) { - Arrays.fill(valueIsNull, positionCount, positionCount + rlePositionCount, true); - hasNullValue = true; - } - else { - long valueHigh = block.getLong(sourcePosition, 0); - long valueLow = block.getLong(sourcePosition, SIZE_OF_LONG); - int positionIndex = positionCount * 2; - for (int i = 0; i < rlePositionCount; i++) { - values[positionIndex] = valueHigh; - values[positionIndex + 1] = valueLow; - positionIndex += 2; - } - hasNonNullValue = true; - } - positionCount += rlePositionCount; - - updateSize(rlePositionCount); - } - - @Override - public void append(int sourcePosition, ValueBlock source) - { - checkArgument(source instanceof Int128ArrayBlock, "Block must be instance of %s", Int128ArrayBlock.class); - - ensureCapacity(positionCount + 1); - if (source.isNull(sourcePosition)) { - valueIsNull[positionCount] = true; - hasNullValue = true; - } - else { - int positionIndex = positionCount * 2; - values[positionIndex] = source.getLong(sourcePosition, 0); - values[positionIndex + 1] = source.getLong(sourcePosition, SIZE_OF_LONG); - hasNonNullValue = true; - } - positionCount++; - - updateSize(1); - } - - @Override - public Block build() - { - Block result; - if (hasNonNullValue) { - result = new Int128ArrayBlock(positionCount, hasNullValue ? Optional.of(valueIsNull) : Optional.empty(), values); - } - else { - result = RunLengthEncodedBlock.create(NULL_VALUE_BLOCK, positionCount); - } - reset(); - return result; - } - - @Override - public long getRetainedSizeInBytes() - { - return retainedSizeInBytes; - } - - @Override - public long getSizeInBytes() - { - return sizeInBytes; - } - - @Override - public void reset() - { - initialEntryCount = calculateBlockResetSize(positionCount); - initialized = false; - valueIsNull = new boolean[0]; - values = new long[0]; - positionCount = 0; - sizeInBytes = 0; - hasNonNullValue = false; - hasNullValue = false; - updateRetainedSize(); - } - - private void ensureCapacity(int capacity) - { - if (valueIsNull.length >= capacity) { - return; - } - - int newSize; - if (initialized) { - newSize = calculateNewArraySize(valueIsNull.length); - } - else { - newSize = initialEntryCount; - initialized = true; - } - newSize = max(newSize, capacity); - - valueIsNull = Arrays.copyOf(valueIsNull, newSize); - values = Arrays.copyOf(values, newSize * 2); - updateRetainedSize(); - } - - private void updateSize(long positionsSize) - { - sizeInBytes += Int128ArrayBlock.SIZE_IN_BYTES_PER_POSITION * positionsSize; - } - - private void updateRetainedSize() - { - retainedSizeInBytes = INSTANCE_SIZE + sizeOf(valueIsNull) + sizeOf(values); - } -} diff --git a/core/trino-main/src/main/java/io/trino/operator/output/IntPositionsAppender.java b/core/trino-main/src/main/java/io/trino/operator/output/IntPositionsAppender.java deleted file mode 100644 index bcb7d73b046d..000000000000 --- a/core/trino-main/src/main/java/io/trino/operator/output/IntPositionsAppender.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * 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 io.trino.operator.output; - -import io.trino.spi.block.Block; -import io.trino.spi.block.IntArrayBlock; -import io.trino.spi.block.RunLengthEncodedBlock; -import io.trino.spi.block.ValueBlock; -import it.unimi.dsi.fastutil.ints.IntArrayList; - -import java.util.Arrays; -import java.util.Optional; - -import static com.google.common.base.Preconditions.checkArgument; -import static io.airlift.slice.SizeOf.instanceSize; -import static io.airlift.slice.SizeOf.sizeOf; -import static io.trino.operator.output.PositionsAppenderUtil.calculateBlockResetSize; -import static io.trino.operator.output.PositionsAppenderUtil.calculateNewArraySize; -import static java.lang.Math.max; - -public class IntPositionsAppender - implements PositionsAppender -{ - private static final int INSTANCE_SIZE = instanceSize(IntPositionsAppender.class); - private static final Block NULL_VALUE_BLOCK = new IntArrayBlock(1, Optional.of(new boolean[] {true}), new int[1]); - - private boolean initialized; - private int initialEntryCount; - - private int positionCount; - private boolean hasNullValue; - private boolean hasNonNullValue; - - // it is assumed that these arrays are the same length - private boolean[] valueIsNull = new boolean[0]; - private int[] values = new int[0]; - - private long retainedSizeInBytes; - private long sizeInBytes; - - public IntPositionsAppender(int expectedEntries) - { - this.initialEntryCount = max(expectedEntries, 1); - - updateRetainedSize(); - } - - @Override - public void append(IntArrayList positions, ValueBlock block) - { - checkArgument(block instanceof IntArrayBlock, "Block must be instance of %s", IntArrayBlock.class); - - if (positions.isEmpty()) { - return; - } - int[] positionArray = positions.elements(); - int positionsSize = positions.size(); - ensureCapacity(positionCount + positionsSize); - - if (block.mayHaveNull()) { - for (int i = 0; i < positionsSize; i++) { - int position = positionArray[i]; - boolean isNull = block.isNull(position); - int positionIndex = positionCount + i; - if (isNull) { - valueIsNull[positionIndex] = true; - hasNullValue = true; - } - else { - values[positionIndex] = block.getInt(position, 0); - hasNonNullValue = true; - } - } - positionCount += positionsSize; - } - else { - for (int i = 0; i < positionsSize; i++) { - int position = positionArray[i]; - values[positionCount + i] = block.getInt(position, 0); - } - positionCount += positionsSize; - hasNonNullValue = true; - } - - updateSize(positionsSize); - } - - @Override - public void appendRle(ValueBlock block, int rlePositionCount) - { - checkArgument(block instanceof IntArrayBlock, "Block must be instance of %s", IntArrayBlock.class); - - if (rlePositionCount == 0) { - return; - } - int sourcePosition = 0; - ensureCapacity(positionCount + rlePositionCount); - if (block.isNull(sourcePosition)) { - Arrays.fill(valueIsNull, positionCount, positionCount + rlePositionCount, true); - hasNullValue = true; - } - else { - int value = block.getInt(sourcePosition, 0); - Arrays.fill(values, positionCount, positionCount + rlePositionCount, value); - hasNonNullValue = true; - } - positionCount += rlePositionCount; - - updateSize(rlePositionCount); - } - - @Override - public void append(int sourcePosition, ValueBlock source) - { - checkArgument(source instanceof IntArrayBlock, "Block must be instance of %s", IntArrayBlock.class); - - ensureCapacity(positionCount + 1); - if (source.isNull(sourcePosition)) { - valueIsNull[positionCount] = true; - hasNullValue = true; - } - else { - values[positionCount] = source.getInt(sourcePosition, 0); - hasNonNullValue = true; - } - positionCount++; - - updateSize(1); - } - - @Override - public Block build() - { - Block result; - if (hasNonNullValue) { - result = new IntArrayBlock(positionCount, hasNullValue ? Optional.of(valueIsNull) : Optional.empty(), values); - } - else { - result = RunLengthEncodedBlock.create(NULL_VALUE_BLOCK, positionCount); - } - reset(); - return result; - } - - @Override - public long getRetainedSizeInBytes() - { - return retainedSizeInBytes; - } - - @Override - public long getSizeInBytes() - { - return sizeInBytes; - } - - @Override - public void reset() - { - initialEntryCount = calculateBlockResetSize(positionCount); - initialized = false; - valueIsNull = new boolean[0]; - values = new int[0]; - positionCount = 0; - sizeInBytes = 0; - hasNonNullValue = false; - hasNullValue = false; - updateRetainedSize(); - } - - private void ensureCapacity(int capacity) - { - if (values.length >= capacity) { - return; - } - - int newSize; - if (initialized) { - newSize = calculateNewArraySize(values.length); - } - else { - newSize = initialEntryCount; - initialized = true; - } - newSize = max(newSize, capacity); - - valueIsNull = Arrays.copyOf(valueIsNull, newSize); - values = Arrays.copyOf(values, newSize); - updateRetainedSize(); - } - - private void updateSize(long positionsSize) - { - sizeInBytes += IntArrayBlock.SIZE_IN_BYTES_PER_POSITION * positionsSize; - } - - private void updateRetainedSize() - { - retainedSizeInBytes = INSTANCE_SIZE + sizeOf(valueIsNull) + sizeOf(values); - } -} diff --git a/core/trino-main/src/main/java/io/trino/operator/output/LongPositionsAppender.java b/core/trino-main/src/main/java/io/trino/operator/output/LongPositionsAppender.java deleted file mode 100644 index 6fc555f02a01..000000000000 --- a/core/trino-main/src/main/java/io/trino/operator/output/LongPositionsAppender.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * 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 io.trino.operator.output; - -import io.trino.spi.block.Block; -import io.trino.spi.block.LongArrayBlock; -import io.trino.spi.block.RunLengthEncodedBlock; -import io.trino.spi.block.ValueBlock; -import it.unimi.dsi.fastutil.ints.IntArrayList; - -import java.util.Arrays; -import java.util.Optional; - -import static com.google.common.base.Preconditions.checkArgument; -import static io.airlift.slice.SizeOf.instanceSize; -import static io.airlift.slice.SizeOf.sizeOf; -import static io.trino.operator.output.PositionsAppenderUtil.calculateBlockResetSize; -import static io.trino.operator.output.PositionsAppenderUtil.calculateNewArraySize; -import static java.lang.Math.max; - -public class LongPositionsAppender - implements PositionsAppender -{ - private static final int INSTANCE_SIZE = instanceSize(LongPositionsAppender.class); - private static final Block NULL_VALUE_BLOCK = new LongArrayBlock(1, Optional.of(new boolean[] {true}), new long[1]); - - private boolean initialized; - private int initialEntryCount; - - private int positionCount; - private boolean hasNullValue; - private boolean hasNonNullValue; - - // it is assumed that these arrays are the same length - private boolean[] valueIsNull = new boolean[0]; - private long[] values = new long[0]; - - private long retainedSizeInBytes; - private long sizeInBytes; - - public LongPositionsAppender(int expectedEntries) - { - this.initialEntryCount = max(expectedEntries, 1); - - updateRetainedSize(); - } - - @Override - public void append(IntArrayList positions, ValueBlock block) - { - checkArgument(block instanceof LongArrayBlock, "Block must be instance of %s", LongArrayBlock.class); - - if (positions.isEmpty()) { - return; - } - int[] positionArray = positions.elements(); - int positionsSize = positions.size(); - ensureCapacity(positionCount + positionsSize); - - if (block.mayHaveNull()) { - for (int i = 0; i < positionsSize; i++) { - int position = positionArray[i]; - int positionIndex = positionCount + i; - boolean isNull = block.isNull(position); - if (isNull) { - valueIsNull[positionIndex] = true; - hasNullValue = true; - } - else { - values[positionIndex] = block.getLong(position, 0); - hasNonNullValue = true; - } - } - positionCount += positionsSize; - } - else { - for (int i = 0; i < positionsSize; i++) { - int position = positionArray[i]; - values[positionCount + i] = block.getLong(position, 0); - } - positionCount += positionsSize; - hasNonNullValue = true; - } - - updateSize(positionsSize); - } - - @Override - public void appendRle(ValueBlock block, int rlePositionCount) - { - checkArgument(block instanceof LongArrayBlock, "Block must be instance of %s", LongArrayBlock.class); - - if (rlePositionCount == 0) { - return; - } - int sourcePosition = 0; - ensureCapacity(positionCount + rlePositionCount); - if (block.isNull(sourcePosition)) { - Arrays.fill(valueIsNull, positionCount, positionCount + rlePositionCount, true); - hasNullValue = true; - } - else { - long value = block.getLong(sourcePosition, 0); - Arrays.fill(values, positionCount, positionCount + rlePositionCount, value); - hasNonNullValue = true; - } - positionCount += rlePositionCount; - - updateSize(rlePositionCount); - } - - @Override - public void append(int sourcePosition, ValueBlock source) - { - checkArgument(source instanceof LongArrayBlock, "Block must be instance of %s", LongArrayBlock.class); - - ensureCapacity(positionCount + 1); - if (source.isNull(sourcePosition)) { - valueIsNull[positionCount] = true; - hasNullValue = true; - } - else { - values[positionCount] = source.getLong(sourcePosition, 0); - hasNonNullValue = true; - } - positionCount++; - - updateSize(1); - } - - @Override - public Block build() - { - Block result; - if (hasNonNullValue) { - result = new LongArrayBlock(positionCount, hasNullValue ? Optional.of(valueIsNull) : Optional.empty(), values); - } - else { - result = RunLengthEncodedBlock.create(NULL_VALUE_BLOCK, positionCount); - } - reset(); - return result; - } - - @Override - public long getRetainedSizeInBytes() - { - return retainedSizeInBytes; - } - - @Override - public long getSizeInBytes() - { - return sizeInBytes; - } - - @Override - public void reset() - { - initialEntryCount = calculateBlockResetSize(positionCount); - initialized = false; - valueIsNull = new boolean[0]; - values = new long[0]; - positionCount = 0; - sizeInBytes = 0; - hasNonNullValue = false; - hasNullValue = false; - updateRetainedSize(); - } - - private void ensureCapacity(int capacity) - { - if (values.length >= capacity) { - return; - } - - int newSize; - if (initialized) { - newSize = calculateNewArraySize(values.length); - } - else { - newSize = initialEntryCount; - initialized = true; - } - newSize = max(newSize, capacity); - - valueIsNull = Arrays.copyOf(valueIsNull, newSize); - values = Arrays.copyOf(values, newSize); - updateRetainedSize(); - } - - private void updateSize(long positionsSize) - { - sizeInBytes += LongArrayBlock.SIZE_IN_BYTES_PER_POSITION * positionsSize; - } - - private void updateRetainedSize() - { - retainedSizeInBytes = INSTANCE_SIZE + sizeOf(valueIsNull) + sizeOf(values); - } -} diff --git a/core/trino-main/src/main/java/io/trino/operator/output/PositionsAppenderFactory.java b/core/trino-main/src/main/java/io/trino/operator/output/PositionsAppenderFactory.java index 34eab30e020e..e972baad6d22 100644 --- a/core/trino-main/src/main/java/io/trino/operator/output/PositionsAppenderFactory.java +++ b/core/trino-main/src/main/java/io/trino/operator/output/PositionsAppenderFactory.java @@ -13,14 +13,9 @@ */ package io.trino.operator.output; -import io.trino.spi.block.ByteArrayBlock; -import io.trino.spi.block.Fixed12Block; -import io.trino.spi.block.Int128ArrayBlock; -import io.trino.spi.block.IntArrayBlock; -import io.trino.spi.block.LongArrayBlock; import io.trino.spi.block.RowBlock; -import io.trino.spi.block.ShortArrayBlock; import io.trino.spi.block.VariableWidthBlock; +import io.trino.spi.block.VariableWidthBlockBuilder; import io.trino.spi.type.RowType; import io.trino.spi.type.Type; import io.trino.type.BlockTypeOperators; @@ -28,11 +23,14 @@ import java.util.Optional; +import static io.trino.operator.output.PositionsAppenderUtil.MAX_ARRAY_SIZE; +import static java.lang.Math.min; import static java.util.Objects.requireNonNull; public class PositionsAppenderFactory { private final BlockTypeOperators blockTypeOperators; + private static final int EXPECTED_VARIABLE_WIDTH_BYTES_PER_ENTRY = 32; public PositionsAppenderFactory(BlockTypeOperators blockTypeOperators) { @@ -50,30 +48,15 @@ public UnnestingPositionsAppender create(Type type, int expectedPositions, long private PositionsAppender createPrimitiveAppender(Type type, int expectedPositions, long maxPageSizeInBytes) { - if (type.getValueBlockType() == ByteArrayBlock.class) { - return new BytePositionsAppender(expectedPositions); - } - if (type.getValueBlockType() == ShortArrayBlock.class) { - return new ShortPositionsAppender(expectedPositions); - } - if (type.getValueBlockType() == IntArrayBlock.class) { - return new IntPositionsAppender(expectedPositions); - } - if (type.getValueBlockType() == LongArrayBlock.class) { - return new LongPositionsAppender(expectedPositions); - } - if (type.getValueBlockType() == Fixed12Block.class) { - return new Fixed12PositionsAppender(expectedPositions); - } - if (type.getValueBlockType() == Int128ArrayBlock.class) { - return new Int128PositionsAppender(expectedPositions); - } - if (type.getValueBlockType() == VariableWidthBlock.class) { - return new SlicePositionsAppender(expectedPositions, maxPageSizeInBytes); - } if (type.getValueBlockType() == RowBlock.class) { return RowPositionsAppender.createRowAppender(this, (RowType) type, expectedPositions, maxPageSizeInBytes); } + if (type.getValueBlockType() == VariableWidthBlock.class) { + // it is guaranteed Math.min will not overflow; safe to cast + int expectedBytes = (int) min((long) expectedPositions * EXPECTED_VARIABLE_WIDTH_BYTES_PER_ENTRY, maxPageSizeInBytes); + expectedBytes = min(expectedBytes, MAX_ARRAY_SIZE); + return new TypedPositionsAppender(new VariableWidthBlockBuilder(null, expectedPositions, expectedBytes)); + } return new TypedPositionsAppender(type, expectedPositions); } } diff --git a/core/trino-main/src/main/java/io/trino/operator/output/PositionsAppenderUtil.java b/core/trino-main/src/main/java/io/trino/operator/output/PositionsAppenderUtil.java index 0d1d6b642096..b6efce94d2f1 100644 --- a/core/trino-main/src/main/java/io/trino/operator/output/PositionsAppenderUtil.java +++ b/core/trino-main/src/main/java/io/trino/operator/output/PositionsAppenderUtil.java @@ -24,9 +24,7 @@ final class PositionsAppenderUtil // See java.util.ArrayList for an explanation static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; - private PositionsAppenderUtil() - { - } + private PositionsAppenderUtil() {} // Copied from io.trino.spi.block.BlockUtil#calculateNewArraySize static int calculateNewArraySize(int currentSize) @@ -61,14 +59,4 @@ else if (newSize > MAX_ARRAY_SIZE) { } return (int) newSize; } - - // Copied from io.trino.spi.block.BlockUtil#calculateBlockResetBytes - static int calculateBlockResetBytes(int currentBytes) - { - long newBytes = (long) ceil(currentBytes * BLOCK_RESET_SKEW); - if (newBytes > MAX_ARRAY_SIZE) { - return MAX_ARRAY_SIZE; - } - return (int) newBytes; - } } diff --git a/core/trino-main/src/main/java/io/trino/operator/output/ShortPositionsAppender.java b/core/trino-main/src/main/java/io/trino/operator/output/ShortPositionsAppender.java deleted file mode 100644 index 16739ae1ea04..000000000000 --- a/core/trino-main/src/main/java/io/trino/operator/output/ShortPositionsAppender.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * 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 io.trino.operator.output; - -import io.trino.spi.block.Block; -import io.trino.spi.block.RunLengthEncodedBlock; -import io.trino.spi.block.ShortArrayBlock; -import io.trino.spi.block.ValueBlock; -import it.unimi.dsi.fastutil.ints.IntArrayList; - -import java.util.Arrays; -import java.util.Optional; - -import static com.google.common.base.Preconditions.checkArgument; -import static io.airlift.slice.SizeOf.instanceSize; -import static io.airlift.slice.SizeOf.sizeOf; -import static io.trino.operator.output.PositionsAppenderUtil.calculateBlockResetSize; -import static io.trino.operator.output.PositionsAppenderUtil.calculateNewArraySize; -import static java.lang.Math.max; - -public class ShortPositionsAppender - implements PositionsAppender -{ - private static final int INSTANCE_SIZE = instanceSize(ShortPositionsAppender.class); - private static final Block NULL_VALUE_BLOCK = new ShortArrayBlock(1, Optional.of(new boolean[] {true}), new short[1]); - - private boolean initialized; - private int initialEntryCount; - - private int positionCount; - private boolean hasNullValue; - private boolean hasNonNullValue; - - // it is assumed that these arrays are the same length - private boolean[] valueIsNull = new boolean[0]; - private short[] values = new short[0]; - - private long retainedSizeInBytes; - private long sizeInBytes; - - public ShortPositionsAppender(int expectedEntries) - { - this.initialEntryCount = max(expectedEntries, 1); - - updateRetainedSize(); - } - - @Override - public void append(IntArrayList positions, ValueBlock block) - { - checkArgument(block instanceof ShortArrayBlock, "Block must be instance of %s", ShortArrayBlock.class); - - if (positions.isEmpty()) { - return; - } - int[] positionArray = positions.elements(); - int positionsSize = positions.size(); - ensureCapacity(positionCount + positionsSize); - - if (block.mayHaveNull()) { - for (int i = 0; i < positionsSize; i++) { - int position = positionArray[i]; - boolean isNull = block.isNull(position); - int positionIndex = positionCount + i; - if (isNull) { - valueIsNull[positionIndex] = true; - hasNullValue = true; - } - else { - values[positionIndex] = block.getShort(position, 0); - hasNonNullValue = true; - } - } - positionCount += positionsSize; - } - else { - for (int i = 0; i < positionsSize; i++) { - int position = positionArray[i]; - values[positionCount + i] = block.getShort(position, 0); - } - positionCount += positionsSize; - hasNonNullValue = true; - } - - updateSize(positionsSize); - } - - @Override - public void appendRle(ValueBlock block, int rlePositionCount) - { - checkArgument(block instanceof ShortArrayBlock, "Block must be instance of %s", ShortArrayBlock.class); - - if (rlePositionCount == 0) { - return; - } - int sourcePosition = 0; - ensureCapacity(positionCount + rlePositionCount); - if (block.isNull(sourcePosition)) { - Arrays.fill(valueIsNull, positionCount, positionCount + rlePositionCount, true); - hasNullValue = true; - } - else { - short value = block.getShort(sourcePosition, 0); - Arrays.fill(values, positionCount, positionCount + rlePositionCount, value); - hasNonNullValue = true; - } - positionCount += rlePositionCount; - - updateSize(rlePositionCount); - } - - @Override - public void append(int sourcePosition, ValueBlock source) - { - checkArgument(source instanceof ShortArrayBlock, "Block must be instance of %s", ShortArrayBlock.class); - - ensureCapacity(positionCount + 1); - if (source.isNull(sourcePosition)) { - valueIsNull[positionCount] = true; - hasNullValue = true; - } - else { - values[positionCount] = source.getShort(sourcePosition, 0); - hasNonNullValue = true; - } - positionCount++; - - updateSize(1); - } - - @Override - public Block build() - { - Block result; - if (hasNonNullValue) { - result = new ShortArrayBlock(positionCount, hasNullValue ? Optional.of(valueIsNull) : Optional.empty(), values); - } - else { - result = RunLengthEncodedBlock.create(NULL_VALUE_BLOCK, positionCount); - } - reset(); - return result; - } - - @Override - public long getRetainedSizeInBytes() - { - return retainedSizeInBytes; - } - - @Override - public long getSizeInBytes() - { - return sizeInBytes; - } - - @Override - public void reset() - { - initialEntryCount = calculateBlockResetSize(positionCount); - initialized = false; - valueIsNull = new boolean[0]; - values = new short[0]; - positionCount = 0; - sizeInBytes = 0; - hasNonNullValue = false; - hasNullValue = false; - updateRetainedSize(); - } - - private void ensureCapacity(int capacity) - { - if (values.length >= capacity) { - return; - } - - int newSize; - if (initialized) { - newSize = calculateNewArraySize(values.length); - } - else { - newSize = initialEntryCount; - initialized = true; - } - newSize = max(newSize, capacity); - - valueIsNull = Arrays.copyOf(valueIsNull, newSize); - values = Arrays.copyOf(values, newSize); - updateRetainedSize(); - } - - private void updateSize(long positionsSize) - { - sizeInBytes += ShortArrayBlock.SIZE_IN_BYTES_PER_POSITION * positionsSize; - } - - private void updateRetainedSize() - { - retainedSizeInBytes = INSTANCE_SIZE + sizeOf(valueIsNull) + sizeOf(values); - } -} diff --git a/core/trino-main/src/main/java/io/trino/operator/output/SlicePositionsAppender.java b/core/trino-main/src/main/java/io/trino/operator/output/SlicePositionsAppender.java deleted file mode 100644 index 204cf679520c..000000000000 --- a/core/trino-main/src/main/java/io/trino/operator/output/SlicePositionsAppender.java +++ /dev/null @@ -1,333 +0,0 @@ -/* - * 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 io.trino.operator.output; - -import com.google.common.annotations.VisibleForTesting; -import io.airlift.slice.Slice; -import io.airlift.slice.Slices; -import io.trino.spi.block.Block; -import io.trino.spi.block.RunLengthEncodedBlock; -import io.trino.spi.block.ValueBlock; -import io.trino.spi.block.VariableWidthBlock; -import it.unimi.dsi.fastutil.ints.IntArrayList; - -import java.util.Arrays; -import java.util.Optional; - -import static com.google.common.base.Preconditions.checkArgument; -import static io.airlift.slice.SizeOf.SIZE_OF_BYTE; -import static io.airlift.slice.SizeOf.SIZE_OF_INT; -import static io.airlift.slice.SizeOf.instanceSize; -import static io.airlift.slice.SizeOf.sizeOf; -import static io.airlift.slice.Slices.EMPTY_SLICE; -import static io.trino.operator.output.PositionsAppenderUtil.MAX_ARRAY_SIZE; -import static io.trino.operator.output.PositionsAppenderUtil.calculateBlockResetBytes; -import static io.trino.operator.output.PositionsAppenderUtil.calculateBlockResetSize; -import static io.trino.operator.output.PositionsAppenderUtil.calculateNewArraySize; -import static java.lang.Math.min; -import static java.lang.Math.toIntExact; - -public class SlicePositionsAppender - implements PositionsAppender -{ - private static final int EXPECTED_BYTES_PER_ENTRY = 32; - private static final int INSTANCE_SIZE = instanceSize(SlicePositionsAppender.class); - private static final Block NULL_VALUE_BLOCK = new VariableWidthBlock(1, EMPTY_SLICE, new int[] {0, 0}, Optional.of(new boolean[] {true})); - - private boolean initialized; - private int initialEntryCount; - private int initialBytesSize; - - private byte[] bytes = new byte[0]; - - private boolean hasNullValue; - private boolean hasNonNullValue; - // it is assumed that the offset array is one position longer than the valueIsNull array - private boolean[] valueIsNull = new boolean[0]; - private int[] offsets = new int[1]; - - private int positionCount; - - private long retainedSizeInBytes; - private long sizeInBytes; - - public SlicePositionsAppender(int expectedEntries, long maxPageSizeInBytes) - { - this(expectedEntries, getExpectedBytes(maxPageSizeInBytes, expectedEntries)); - } - - public SlicePositionsAppender(int expectedEntries, int expectedBytes) - { - initialEntryCount = expectedEntries; - initialBytesSize = min(expectedBytes, MAX_ARRAY_SIZE); - - updateRetainedSize(); - } - - @Override - public void append(IntArrayList positions, ValueBlock block) - { - checkArgument(block instanceof VariableWidthBlock, "Block must be instance of %s", VariableWidthBlock.class); - - if (positions.isEmpty()) { - return; - } - ensurePositionCapacity(positionCount + positions.size()); - VariableWidthBlock variableWidthBlock = (VariableWidthBlock) block; - int newByteCount = 0; - int[] lengths = new int[positions.size()]; - int[] sourceOffsets = new int[positions.size()]; - int[] positionArray = positions.elements(); - - if (block.mayHaveNull()) { - for (int i = 0; i < positions.size(); i++) { - int position = positionArray[i]; - int length = variableWidthBlock.getSliceLength(position); - lengths[i] = length; - sourceOffsets[i] = variableWidthBlock.getRawSliceOffset(position); - newByteCount += length; - boolean isNull = block.isNull(position); - valueIsNull[positionCount + i] = isNull; - offsets[positionCount + i + 1] = offsets[positionCount + i] + length; - hasNullValue |= isNull; - hasNonNullValue |= !isNull; - } - } - else { - for (int i = 0; i < positions.size(); i++) { - int position = positionArray[i]; - int length = variableWidthBlock.getSliceLength(position); - lengths[i] = length; - sourceOffsets[i] = variableWidthBlock.getRawSliceOffset(position); - newByteCount += length; - offsets[positionCount + i + 1] = offsets[positionCount + i] + length; - } - hasNonNullValue = true; - } - copyBytes(variableWidthBlock.getRawSlice(), lengths, sourceOffsets, positions.size(), newByteCount); - } - - @Override - public void appendRle(ValueBlock block, int rlePositionCount) - { - checkArgument(block instanceof VariableWidthBlock, "Block must be instance of %s", VariableWidthBlock.class); - - if (rlePositionCount == 0) { - return; - } - ensurePositionCapacity(positionCount + rlePositionCount); - if (block.isNull(0)) { - Arrays.fill(valueIsNull, positionCount, positionCount + rlePositionCount, true); - Arrays.fill(offsets, positionCount + 1, positionCount + rlePositionCount + 1, getCurrentOffset()); - positionCount += rlePositionCount; - - hasNullValue = true; - updateSize(rlePositionCount, 0); - } - else { - hasNonNullValue = true; - duplicateBytes(block.getSlice(0, 0, block.getSliceLength(0)), rlePositionCount); - } - } - - @Override - public void append(int position, ValueBlock source) - { - checkArgument(source instanceof VariableWidthBlock, "Block must be instance of %s but is %s".formatted(VariableWidthBlock.class, source.getClass())); - - ensurePositionCapacity(positionCount + 1); - if (source.isNull(position)) { - valueIsNull[positionCount] = true; - offsets[positionCount + 1] = getCurrentOffset(); - positionCount++; - - hasNullValue = true; - updateSize(1, 0); - } - else { - hasNonNullValue = true; - int currentOffset = getCurrentOffset(); - int sliceLength = source.getSliceLength(position); - Slice slice = source.getSlice(position, 0, sliceLength); - - ensureExtraBytesCapacity(sliceLength); - - slice.getBytes(0, bytes, currentOffset, sliceLength); - - offsets[positionCount + 1] = currentOffset + sliceLength; - - positionCount++; - updateSize(1, sliceLength); - } - } - - @Override - public Block build() - { - Block result; - if (hasNonNullValue) { - result = new VariableWidthBlock( - positionCount, - Slices.wrappedBuffer(bytes, 0, getCurrentOffset()), - offsets, - hasNullValue ? Optional.of(valueIsNull) : Optional.empty()); - } - else { - result = RunLengthEncodedBlock.create(NULL_VALUE_BLOCK, positionCount); - } - reset(); - return result; - } - - @Override - public long getRetainedSizeInBytes() - { - return retainedSizeInBytes; - } - - @Override - public long getSizeInBytes() - { - return sizeInBytes; - } - - private void copyBytes(Slice rawSlice, int[] lengths, int[] sourceOffsets, int count, int newByteCount) - { - ensureExtraBytesCapacity(newByteCount); - - byte[] base = rawSlice.byteArray(); - int byteArrayOffset = rawSlice.byteArrayOffset(); - for (int i = 0; i < count; i++) { - System.arraycopy(base, byteArrayOffset + sourceOffsets[i], bytes, offsets[positionCount + i], lengths[i]); - } - - positionCount += count; - updateSize(count, newByteCount); - } - - /** - * Copy all bytes from {@code slice} to {@code count} consecutive positions in the {@link #bytes} array. - */ - private void duplicateBytes(Slice slice, int count) - { - int length = slice.length(); - int newByteCount = toIntExact((long) count * length); - int startOffset = getCurrentOffset(); - ensureExtraBytesCapacity(newByteCount); - - duplicateBytes(slice, bytes, startOffset, count); - - int currentStartOffset = startOffset + length; - for (int i = 0; i < count; i++) { - offsets[positionCount + i + 1] = currentStartOffset; - currentStartOffset += length; - } - - positionCount += count; - updateSize(count, newByteCount); - } - - /** - * Copy {@code length} bytes from {@code slice}, starting at offset {@code sourceOffset} to {@code count} consecutive positions in the {@link #bytes} array. - */ - @VisibleForTesting - static void duplicateBytes(Slice slice, byte[] bytes, int startOffset, int count) - { - int length = slice.length(); - if (length == 0) { - // nothing to copy - return; - } - // copy slice to the first position - slice.getBytes(0, bytes, startOffset, length); - int totalDuplicatedBytes = count * length; - int duplicatedBytes = length; - // copy every byte copied so far, doubling the number of bytes copied on evey iteration - while (duplicatedBytes * 2 <= totalDuplicatedBytes) { - System.arraycopy(bytes, startOffset, bytes, startOffset + duplicatedBytes, duplicatedBytes); - duplicatedBytes = duplicatedBytes * 2; - } - // copy the leftover - System.arraycopy(bytes, startOffset, bytes, startOffset + duplicatedBytes, totalDuplicatedBytes - duplicatedBytes); - } - - @Override - public void reset() - { - initialEntryCount = calculateBlockResetSize(positionCount); - initialBytesSize = calculateBlockResetBytes(getCurrentOffset()); - initialized = false; - valueIsNull = new boolean[0]; - offsets = new int[1]; - bytes = new byte[0]; - positionCount = 0; - sizeInBytes = 0; - hasNonNullValue = false; - hasNullValue = false; - updateRetainedSize(); - } - - private int getCurrentOffset() - { - return offsets[positionCount]; - } - - private void updateSize(long positionsSize, int bytesWritten) - { - sizeInBytes += (SIZE_OF_BYTE + SIZE_OF_INT) * positionsSize + bytesWritten; - } - - private void ensureExtraBytesCapacity(int extraBytesCapacity) - { - int totalBytesCapacity = getCurrentOffset() + extraBytesCapacity; - if (bytes.length < totalBytesCapacity) { - int newBytesLength = Math.max(bytes.length, initialBytesSize); - if (totalBytesCapacity > newBytesLength) { - newBytesLength = Math.max(totalBytesCapacity, calculateNewArraySize(newBytesLength)); - } - bytes = Arrays.copyOf(bytes, newBytesLength); - updateRetainedSize(); - } - } - - private void ensurePositionCapacity(int capacity) - { - if (valueIsNull.length < capacity) { - int newSize; - if (initialized) { - newSize = calculateNewArraySize(valueIsNull.length); - } - else { - newSize = initialEntryCount; - initialized = true; - } - newSize = Math.max(newSize, capacity); - - valueIsNull = Arrays.copyOf(valueIsNull, newSize); - offsets = Arrays.copyOf(offsets, newSize + 1); - updateRetainedSize(); - } - } - - private void updateRetainedSize() - { - retainedSizeInBytes = INSTANCE_SIZE + sizeOf(valueIsNull) + sizeOf(offsets) + sizeOf(bytes); - } - - private static int getExpectedBytes(long maxPageSizeInBytes, int expectedPositions) - { - // it is guaranteed Math.min will not overflow; safe to cast - return (int) min((long) expectedPositions * EXPECTED_BYTES_PER_ENTRY, maxPageSizeInBytes); - } -} diff --git a/core/trino-main/src/main/java/io/trino/operator/output/TypedPositionsAppender.java b/core/trino-main/src/main/java/io/trino/operator/output/TypedPositionsAppender.java index eb26ff3241b5..141c26811ca6 100644 --- a/core/trino-main/src/main/java/io/trino/operator/output/TypedPositionsAppender.java +++ b/core/trino-main/src/main/java/io/trino/operator/output/TypedPositionsAppender.java @@ -33,6 +33,11 @@ class TypedPositionsAppender this.blockBuilder = type.createBlockBuilder(null, expectedPositions); } + public TypedPositionsAppender(BlockBuilder blockBuilder) + { + this.blockBuilder = blockBuilder; + } + @Override public void append(IntArrayList positions, ValueBlock block) { diff --git a/core/trino-main/src/test/java/io/trino/operator/output/TestSlicePositionsAppender.java b/core/trino-main/src/test/java/io/trino/operator/output/TestSlicePositionsAppender.java deleted file mode 100644 index c90ffe5234b0..000000000000 --- a/core/trino-main/src/test/java/io/trino/operator/output/TestSlicePositionsAppender.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * 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 io.trino.operator.output; - -import io.airlift.slice.Slice; -import io.airlift.slice.Slices; -import io.trino.spi.block.Block; -import io.trino.spi.block.RunLengthEncodedBlock; -import io.trino.spi.block.ValueBlock; -import org.junit.jupiter.api.Test; - -import java.util.Arrays; - -import static io.trino.block.BlockAssertions.assertBlockEquals; -import static io.trino.block.BlockAssertions.createStringsBlock; -import static io.trino.operator.output.SlicePositionsAppender.duplicateBytes; -import static io.trino.spi.type.VarcharType.VARCHAR; -import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals; - -public class TestSlicePositionsAppender -{ - @Test - public void testAppendEmptySliceRle() - { - // test SlicePositionAppender.appendRle with empty value (Slice with length 0) - PositionsAppender positionsAppender = new SlicePositionsAppender(1, 100); - ValueBlock value = createStringsBlock(""); - positionsAppender.appendRle(value, 10); - - Block actualBlock = positionsAppender.build(); - - assertBlockEquals(VARCHAR, actualBlock, RunLengthEncodedBlock.create(value, 10)); - } - - @Test - public void testDuplicateZeroLength() - { - Slice slice = Slices.wrappedBuffer(); - byte[] target = new byte[] {-1}; - duplicateBytes(slice, target, 0, 100); - assertArrayEquals(new byte[] {-1}, target); - } - - @Test - public void testDuplicate1Byte() - { - Slice slice = Slices.wrappedBuffer(new byte[] {2}); - byte[] target = new byte[5]; - Arrays.fill(target, (byte) -1); - duplicateBytes(slice, target, 3, 2); - assertArrayEquals(new byte[] {-1, -1, -1, 2, 2}, target); - } - - @Test - public void testDuplicate2Bytes() - { - Slice slice = Slices.wrappedBuffer(new byte[] {1, 2}); - byte[] target = new byte[8]; - Arrays.fill(target, (byte) -1); - duplicateBytes(slice, target, 1, 3); - assertArrayEquals(new byte[] {-1, 1, 2, 1, 2, 1, 2, -1}, target); - } - - @Test - public void testDuplicate1Time() - { - Slice slice = Slices.wrappedBuffer(new byte[] {1, 2}); - byte[] target = new byte[8]; - Arrays.fill(target, (byte) -1); - - duplicateBytes(slice, target, 1, 1); - - assertArrayEquals(new byte[] {-1, 1, 2, -1, -1, -1, -1, -1}, target); - } - - @Test - public void testDuplicateMultipleBytesOffNumberOfTimes() - { - Slice slice = Slices.wrappedBuffer(new byte[] {5, 3, 1}); - byte[] target = new byte[17]; - Arrays.fill(target, (byte) -1); - - duplicateBytes(slice, target, 1, 5); - - assertArrayEquals(new byte[] {-1, 5, 3, 1, 5, 3, 1, 5, 3, 1, 5, 3, 1, 5, 3, 1, -1}, target); - } - - @Test - public void testDuplicateMultipleBytesEvenNumberOfTimes() - { - Slice slice = Slices.wrappedBuffer(new byte[] {5, 3, 1}); - byte[] target = new byte[20]; - Arrays.fill(target, (byte) -1); - - duplicateBytes(slice, target, 1, 6); - - assertArrayEquals(new byte[] {-1, 5, 3, 1, 5, 3, 1, 5, 3, 1, 5, 3, 1, 5, 3, 1, 5, 3, 1, -1}, target); - } - - @Test - public void testDuplicateMultipleBytesPowerOfTwoNumberOfTimes() - { - Slice slice = Slices.wrappedBuffer(new byte[] {5, 3, 1}); - byte[] target = new byte[14]; - Arrays.fill(target, (byte) -1); - - duplicateBytes(slice, target, 1, 4); - - assertArrayEquals(new byte[] {-1, 5, 3, 1, 5, 3, 1, 5, 3, 1, 5, 3, 1, -1}, target); - } -} From ddf3684687cd8ad20005780ab692c573ea9da629 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Fri, 17 Nov 2023 14:08:32 -0800 Subject: [PATCH 339/587] Remove unused methods --- .../java/io/trino/plugin/hive/TestHiveFileMetastore.java | 8 -------- .../io/trino/plugin/hive/TestHiveInMemoryMetastore.java | 8 -------- 2 files changed, 16 deletions(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileMetastore.java index 291f667b79c4..2a7f1deb1252 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileMetastore.java @@ -39,14 +39,6 @@ protected HiveMetastore createMetastore(File tempDir) .setMetastoreUser("test")); } - @Test - public void forceTestNgToRespectSingleThreaded() - { - // TODO: Remove after updating TestNG to 7.4.0+ (https://github.com/trinodb/trino/issues/8571) - // TestNG doesn't enforce @Test(singleThreaded = true) when tests are defined in base class. According to - // https://github.com/cbeust/testng/issues/2361#issuecomment-688393166 a workaround it to add a dummy test to the leaf test class. - } - @Test @Override public void testMismatchSchemaTable() diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveInMemoryMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveInMemoryMetastore.java index 47991857d2ba..16617349ceb5 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveInMemoryMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveInMemoryMetastore.java @@ -47,14 +47,6 @@ protected void createTestTable(Table table) super.createTestTable(table); } - @Test - public void forceTestNgToRespectSingleThreaded() - { - // TODO: Remove after updating TestNG to 7.4.0+ (https://github.com/trinodb/trino/issues/8571) - // TestNG doesn't enforce @Test(singleThreaded = true) when tests are defined in base class. According to - // https://github.com/cbeust/testng/issues/2361#issuecomment-688393166 a workaround it to add a dummy test to the leaf test class. - } - @Test @Override public void testMetadataDelete() From a27e5af2d7389a7bc0e906210eafb906f01e4365 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Fri, 17 Nov 2023 13:08:26 -0800 Subject: [PATCH 340/587] Migrate to AssertJ --- .../io/trino/cli/TestAlignedTablePrinter.java | 20 +- .../io/trino/cli/TestAutoTablePrinter.java | 6 +- .../java/io/trino/cli/TestClientOptions.java | 71 +- .../java/io/trino/cli/TestCsvPrinter.java | 22 +- .../java/io/trino/cli/TestInputParser.java | 4 +- .../io/trino/cli/TestInsecureQueryRunner.java | 4 +- .../java/io/trino/cli/TestJsonPrinter.java | 8 +- .../trino/cli/TestMarkdownTablePrinter.java | 12 +- .../java/io/trino/cli/TestQueryRunner.java | 9 +- .../java/io/trino/cli/TestTsvPrinter.java | 10 +- .../trino/cli/TestVerticalRecordPrinter.java | 18 +- .../trino/client/TestClientTypeSignature.java | 12 +- .../io/trino/client/TestFixJsonDataUtils.java | 8 +- .../io/trino/client/TestIntervalDayTime.java | 8 +- .../trino/client/TestIntervalYearMonth.java | 8 +- .../java/io/trino/client/TestJsonCodec.java | 4 +- .../io/trino/client/TestProtocolHeaders.java | 12 +- .../io/trino/client/TestQueryResults.java | 6 +- .../test/java/io/trino/client/TestRetry.java | 3 +- .../java/io/trino/client/TestServerInfo.java | 6 +- .../io/trino/client/uri/TestTrinoUri.java | 47 +- .../io/trino/jdbc/BaseTestJdbcResultSet.java | 417 ++++---- .../jdbc/TestAbstractTrinoResultSet.java | 2 +- .../io/trino/jdbc/TestJdbcConnection.java | 29 +- .../trino/jdbc/TestJdbcPreparedStatement.java | 356 ++++--- .../jdbc/TestJdbcVendorCompatibility.java | 86 +- .../java/io/trino/jdbc/TestJdbcWarnings.java | 27 +- .../io/trino/jdbc/TestProgressMonitor.java | 20 +- .../trino/jdbc/TestTrinoDatabaseMetaData.java | 405 ++++---- .../java/io/trino/jdbc/TestTrinoDriver.java | 571 ++++++----- .../io/trino/jdbc/TestTrinoDriverAuth.java | 57 +- .../jdbc/TestTrinoDriverImpersonateUser.java | 9 +- .../io/trino/jdbc/TestTrinoDriverUri.java | 47 +- .../io/trino/jdbc/TestTrinoResultSet.java | 4 +- .../io/trino/TestPagesIndexPageSorter.java | 4 +- .../io/trino/block/AbstractTestBlock.java | 70 +- .../java/io/trino/block/TestArrayBlock.java | 19 +- .../java/io/trino/block/TestBlockBuilder.java | 23 +- .../io/trino/block/TestByteArrayBlock.java | 15 +- .../io/trino/block/TestDictionaryBlock.java | 111 +-- .../java/io/trino/block/TestFixed12Block.java | 19 +- .../io/trino/block/TestInt128ArrayBlock.java | 15 +- .../io/trino/block/TestIntArrayBlock.java | 15 +- .../io/trino/block/TestLongArrayBlock.java | 15 +- .../java/io/trino/block/TestMapBlock.java | 76 +- .../java/io/trino/block/TestRowBlock.java | 26 +- .../block/TestRunLengthEncodedBlock.java | 21 +- .../io/trino/block/TestShortArrayBlock.java | 15 +- .../trino/block/TestVariableWidthBlock.java | 37 +- .../connector/system/TestSystemSplit.java | 6 +- .../io/trino/cost/PlanNodeStatsAssertion.java | 11 +- .../io/trino/cost/SymbolStatsAssertion.java | 33 +- .../io/trino/cost/TestCostCalculator.java | 16 +- .../io/trino/cost/TestCostComparator.java | 10 +- .../java/io/trino/cost/TestJoinStatsRule.java | 10 +- .../cost/TestPlanNodeStatsEstimateMath.java | 185 ++-- .../io/trino/cost/TestStatisticRange.java | 48 +- .../dispatcher/TestLocalDispatchQuery.java | 4 +- .../TestExchangeSourceOutputSelector.java | 46 +- .../execution/BaseTestSqlTaskManager.java | 98 +- .../java/io/trino/execution/TestColumn.java | 4 +- .../io/trino/execution/TestCommitTask.java | 26 +- .../execution/TestCreateCatalogTask.java | 8 +- .../TestCreateMaterializedViewTask.java | 7 +- .../trino/execution/TestCreateSchemaTask.java | 8 +- .../trino/execution/TestCreateTableTask.java | 41 +- .../trino/execution/TestDeallocateTask.java | 4 +- .../trino/execution/TestDropCatalogTask.java | 13 +- .../trino/execution/TestDropSchemaTask.java | 19 +- .../TestDynamicFiltersCollector.java | 22 +- .../java/io/trino/execution/TestInput.java | 4 +- .../TestMemoryRevokingScheduler.java | 24 +- .../io/trino/execution/TestNodeScheduler.java | 162 ++-- .../trino/execution/TestPageSplitterUtil.java | 5 +- .../trino/execution/TestPlannerWarnings.java | 2 +- .../io/trino/execution/TestPrepareTask.java | 6 +- .../trino/execution/TestQueryIdGenerator.java | 14 +- .../io/trino/execution/TestQueryInfo.java | 68 +- .../io/trino/execution/TestQueryPreparer.java | 11 +- .../execution/TestQueryStateMachine.java | 276 +++--- .../io/trino/execution/TestQueryStats.java | 204 ++-- .../trino/execution/TestResetSessionTask.java | 4 +- .../TestResettableRandomizedIterator.java | 10 +- .../io/trino/execution/TestRollbackTask.java | 26 +- .../io/trino/execution/TestSetPathTask.java | 4 +- .../io/trino/execution/TestSetRoleTask.java | 4 +- .../TestSetSessionAuthorizationTask.java | 4 +- .../trino/execution/TestSetSessionTask.java | 4 +- .../trino/execution/TestSetTimeZoneTask.java | 11 +- .../TestSplitConcurrencyController.java | 10 +- .../java/io/trino/execution/TestSqlStage.java | 29 +- .../java/io/trino/execution/TestSqlTask.java | 120 ++- .../trino/execution/TestSqlTaskExecution.java | 21 +- .../execution/TestStageStateMachine.java | 88 +- .../io/trino/execution/TestStageStats.java | 150 +-- .../execution/TestStartTransactionTask.java | 66 +- .../io/trino/execution/TestStateMachine.java | 72 +- .../TestTaskExecutorStuckSplits.java | 9 +- .../execution/buffer/BufferTestUtils.java | 104 +- .../buffer/TestArbitraryOutputBuffer.java | 195 ++-- .../buffer/TestBroadcastOutputBuffer.java | 184 ++-- .../execution/buffer/TestClientBuffer.java | 86 +- .../execution/buffer/TestPageCodecMarker.java | 62 +- .../execution/buffer/TestPagesSerde.java | 20 +- .../buffer/TestPartitionedOutputBuffer.java | 96 +- .../TestSpoolingExchangeOutputBuffer.java | 94 +- .../buffer/TestSpoolingOutputStats.java | 3 +- .../TestTimeSharingTaskExecutor.java | 90 +- .../resourcegroups/TestResourceGroups.java | 326 ++++--- .../TestStochasticPriorityQueue.java | 32 +- .../TestUpdateablePriorityQueue.java | 12 +- .../resourcegroups/TestWeightedFairQueue.java | 31 +- ...BroadcastPipelinedOutputBufferManager.java | 14 +- .../TestFileBasedNetworkTopology.java | 34 +- .../scheduler/TestFixedCountScheduler.java | 19 +- .../TestMultiSourcePartitionedScheduler.java | 126 ++- ...rtitionedPipelinedOutputBufferManager.java | 14 +- ...estScaledPipelinedOutputBufferManager.java | 14 +- .../scheduler/TestScaledWriterScheduler.java | 26 +- .../TestSourcePartitionedScheduler.java | 150 ++- .../scheduler/TestSubnetTopology.java | 32 +- .../scheduler/TestUniformNodeSelector.java | 45 +- ...estArbitraryDistributionSplitAssigner.java | 30 +- .../TestBinPackingNodeAllocator.java | 37 +- .../TestEventDrivenTaskSource.java | 16 +- .../TestHashDistributionSplitAssigner.java | 7 +- .../TestSingleDistributionSplitAssigner.java | 61 +- .../TestTaskDescriptorStorage.java | 5 +- .../warnings/TestDefaultWarningCollector.java | 6 +- .../warnings/TestTestingWarningCollector.java | 4 +- .../TestHeartbeatFailureDetector.java | 20 +- ...tLeastWastedEffortTaskLowMemoryKiller.java | 68 +- .../io/trino/memory/TestMemoryBlocking.java | 20 +- .../java/io/trino/memory/TestMemoryPools.java | 51 +- .../io/trino/memory/TestMemoryTracking.java | 58 +- .../TestTotalReservationLowMemoryKiller.java | 18 +- ...ionOnBlockedNodesQueryLowMemoryKiller.java | 34 +- ...tionOnBlockedNodesTaskLowMemoryKiller.java | 68 +- .../metadata/TestDiscoveryNodeManager.java | 28 +- .../metadata/TestGlobalFunctionCatalog.java | 11 +- .../TestInformationSchemaMetadata.java | 19 +- .../TestInternalBlockEncodingSerde.java | 8 +- .../TestPolymorphicScalarFunction.java | 26 +- .../metadata/TestQualifiedObjectName.java | 4 +- .../metadata/TestQualifiedTablePrefix.java | 30 +- .../trino/metadata/TestResolvedFunction.java | 9 +- .../java/io/trino/metadata/TestSignature.java | 6 +- .../trino/metadata/TestSignatureBinder.java | 21 +- .../operator/AnnotationEngineAssertions.java | 14 +- .../BenchmarkDynamicFilterSourceOperator.java | 4 +- ...kHashAndStreamingAggregationOperators.java | 8 +- .../trino/operator/BenchmarkTopNOperator.java | 4 +- .../operator/BenchmarkWindowOperator.java | 6 +- .../operator/GroupByHashYieldAssertion.java | 23 +- .../MockExchangeRequestProcessor.java | 11 +- .../io/trino/operator/OperatorAssertion.java | 19 +- .../io/trino/operator/PageAssertions.java | 8 +- .../operator/TestAggregationOperator.java | 8 +- .../TestAnnotationEngineForAggregates.java | 255 +++-- .../TestAnnotationEngineForScalars.java | 170 ++-- .../operator/TestCyclingGroupByHash.java | 14 +- ...TestDeduplicatingDirectExchangeBuffer.java | 137 ++- .../operator/TestDirectExchangeClient.java | 241 +++-- .../operator/TestDistinctLimitOperator.java | 4 +- .../java/io/trino/operator/TestDriver.java | 94 +- .../io/trino/operator/TestDriverStats.java | 54 +- .../TestDynamicFilterSourceOperator.java | 25 +- .../trino/operator/TestExchangeOperator.java | 44 +- .../io/trino/operator/TestGroupByHash.java | 108 ++- .../TestGroupedTopNRankAccumulator.java | 91 +- .../operator/TestGroupedTopNRankBuilder.java | 42 +- .../TestGroupedTopNRowNumberAccumulator.java | 110 ++- .../TestGroupedTopNRowNumberBuilder.java | 34 +- .../operator/TestHashAggregationOperator.java | 55 +- .../operator/TestHttpPageBufferClient.java | 139 +-- .../io/trino/operator/TestIdRegistry.java | 16 +- ...mentalLoadFactorHashArraySizeSupplier.java | 4 +- .../operator/TestMarkDistinctOperator.java | 30 +- .../io/trino/operator/TestMergeHashSort.java | 31 +- .../io/trino/operator/TestMergeOperator.java | 43 +- .../io/trino/operator/TestOperationTimer.java | 7 +- .../TestOperatorMemoryRevocation.java | 18 +- .../io/trino/operator/TestOperatorStats.java | 261 +++-- .../trino/operator/TestOrderByOperator.java | 9 +- .../java/io/trino/operator/TestPageUtils.java | 14 +- .../io/trino/operator/TestPagesIndex.java | 44 +- .../io/trino/operator/TestPipelineStats.java | 76 +- .../trino/operator/TestRowNumberOperator.java | 35 +- .../operator/TestRowReferencePageManager.java | 130 ++- .../TestScanFilterAndProjectOperator.java | 26 +- .../operator/TestSimplePagesHashStrategy.java | 22 +- .../TestStreamingDirectExchangeBuffer.java | 175 ++-- .../operator/TestTableFinishOperator.java | 40 +- .../operator/TestTableWriterOperator.java | 72 +- .../java/io/trino/operator/TestTaskStats.java | 102 +- .../io/trino/operator/TestTopNOperator.java | 12 +- .../operator/TestTopNPeerGroupLookup.java | 44 +- .../operator/TestTopNRankingOperator.java | 26 +- .../io/trino/operator/TestTypeSignature.java | 50 +- .../io/trino/operator/TestWindowOperator.java | 16 +- .../io/trino/operator/TestWorkProcessor.java | 32 +- .../TestingExchangeHttpClientHandler.java | 6 +- .../operator/WorkProcessorAssertion.java | 44 +- .../AbstractTestApproximateCountDistinct.java | 10 +- .../AbstractTestApproximateSetGeneric.java | 27 +- .../aggregation/AggregationTestUtils.java | 36 +- .../BenchmarkDecimalAggregation.java | 4 +- .../aggregation/TestAnyValueAggregation.java | 4 +- ...tApproximateCountDistinctAggregations.java | 42 +- .../TestApproximateMostFrequentHistogram.java | 26 +- .../aggregation/TestArbitraryAggregation.java | 4 +- .../aggregation/TestArrayAggregation.java | 4 +- .../TestDecimalAverageAggregation.java | 54 +- .../TestDecimalSumAggregation.java | 36 +- .../TestDoubleHistogramAggregation.java | 11 +- .../operator/aggregation/TestHistogram.java | 4 +- .../aggregation/TestMinMaxByAggregation.java | 6 +- .../TestMultimapAggAggregation.java | 4 +- .../aggregation/TestNumericHistogram.java | 12 +- .../TestRealHistogramAggregation.java | 11 +- .../TestTDigestAggregationFunction.java | 6 +- .../aggregation/TestTypedHistogram.java | 3 +- .../groupby/AggregationTestOutput.java | 11 +- .../groupby/GroupByAggregationTestUtils.java | 6 +- .../TestListaggAggregationFunction.java | 74 +- .../minmaxbyn/TestMinMaxByNAggregation.java | 4 +- ...malWithOverflowAndLongStateSerializer.java | 14 +- ...ongDecimalWithOverflowStateSerializer.java | 16 +- .../aggregation/state/TestStateCompiler.java | 71 +- .../operator/exchange/TestLocalExchange.java | 166 ++-- .../index/TestFieldSetFilteringRecordSet.java | 4 +- .../operator/join/TestHashJoinOperator.java | 80 +- .../operator/join/TestJoinOperatorInfo.java | 12 +- .../join/TestJoinStatisticsCounter.java | 78 +- .../join/TestLookupJoinPageBuilder.java | 74 +- .../join/TestNestedLoopBuildOperator.java | 32 +- .../join/TestNestedLoopJoinOperator.java | 11 +- .../operator/join/TestPositionLinks.java | 262 ++--- .../unspilled/TestHashBuilderOperator.java | 25 +- .../join/unspilled/TestHashJoinOperator.java | 73 +- .../unspilled/TestLookupJoinPageBuilder.java | 74 +- .../operator/output/TestPagePartitioner.java | 5 +- .../output/TestPagePartitionerPool.java | 31 +- .../output/TestPartitionedOutputOperator.java | 6 +- .../output/TestPositionsAppender.java | 44 +- .../TestDictionaryAwarePageFilter.java | 10 +- .../TestDictionaryAwarePageProjection.java | 48 +- .../project/TestInputPageProjection.java | 9 +- .../operator/project/TestMergePages.java | 13 +- .../operator/project/TestPageProcessor.java | 78 +- .../TestBlockAndPositionNullConvention.java | 17 +- .../trino/operator/scalar/TestBlockSet.java | 33 +- .../operator/scalar/TestColorFunctions.java | 130 ++- .../scalar/TestDateTimeFunctions.java | 3 +- .../scalar/TestJoniRegexpFunctions.java | 7 +- .../operator/scalar/TestJsonExtract.java | 260 +++-- .../operator/scalar/TestLikeFunctions.java | 79 +- .../scalar/TestPageProcessorCompiler.java | 44 +- ...calarFunctionImplementationValidation.java | 6 +- .../unnest/BenchmarkUnnestOperator.java | 10 +- .../operator/unnest/TestUnnestOperator.java | 4 +- .../operator/unnest/TestingUnnesterUtil.java | 19 +- .../window/TestRowNumberFunction.java | 9 +- .../TestIrRowPatternToProgramRewriter.java | 4 +- .../security/TestAccessControlManager.java | 11 +- .../TestFileBasedSystemAccessControl.java | 42 +- .../security/TestGroupProviderManager.java | 6 +- .../io/trino/server/TestBasicQueryInfo.java | 59 +- .../server/TestDynamicFilterService.java | 404 ++++---- .../trino/server/TestGenerateTokenFilter.java | 8 +- .../TestHttpRequestSessionContextFactory.java | 32 +- .../io/trino/server/TestNodeResource.java | 6 +- .../trino/server/TestQueryProgressStats.java | 23 +- .../io/trino/server/TestQueryResource.java | 62 +- .../server/TestQuerySessionSupplier.java | 57 +- .../io/trino/server/TestQueryStateInfo.java | 28 +- .../server/TestQueryStateInfoResource.java | 16 +- .../server/TestSessionPropertyDefaults.java | 28 +- .../trino/server/TestSliceSerialization.java | 4 +- .../server/protocol/TestQueryResultRows.java | 28 +- .../trino/server/remotetask/TestBackoff.java | 124 ++- .../server/remotetask/TestHttpRemoteTask.java | 80 +- .../server/security/TestResourceSecurity.java | 83 +- .../server/security/TestUserMapping.java | 28 +- .../server/security/jwt/TestJwkDecoder.java | 59 +- .../server/security/jwt/TestJwkService.java | 13 +- ...seOAuth2WebUiAuthenticationFilterTest.java | 9 +- ...AuthenticationFilterWithRefreshTokens.java | 9 +- .../java/io/trino/server/ui/TestWebUi.java | 149 ++- .../trino/spiller/TestBinaryFileSpiller.java | 14 +- .../spiller/TestFileSingleStreamSpiller.java | 25 +- .../TestFileSingleStreamSpillerFactory.java | 58 +- .../TestGenericPartitioningSpiller.java | 20 +- .../trino/spiller/TestSpillSpaceTracker.java | 20 +- .../java/io/trino/split/TestPageSinkId.java | 4 +- .../sql/BenchmarkExpressionInterpreter.java | 4 +- .../trino/sql/TestExpressionInterpreter.java | 17 +- .../io/trino/sql/TestExpressionOptimizer.java | 20 +- .../io/trino/sql/TestExpressionUtils.java | 10 +- .../sql/TestSqlToRowExpressionTranslator.java | 24 +- .../io/trino/sql/analyzer/TestOutput.java | 6 +- .../java/io/trino/sql/analyzer/TestScope.java | 56 +- .../io/trino/sql/gen/TestInCodeGenerator.java | 44 +- .../io/trino/sql/gen/TestJoinCompiler.java | 93 +- .../sql/gen/TestPageFunctionCompiler.java | 45 +- .../trino/sql/planner/BenchmarkPlanner.java | 4 +- .../TestConnectorExpressionTranslator.java | 8 +- .../sql/planner/TestDeterminismEvaluator.java | 29 +- .../trino/sql/planner/TestDomainCoercer.java | 204 ++-- .../sql/planner/TestDomainTranslator.java | 182 ++-- .../TestEffectivePredicateExtractor.java | 453 ++++----- .../sql/planner/TestEqualityInference.java | 113 +-- .../TestGroupingOperationRewriter.java | 10 +- .../trino/sql/planner/TestLiteralEncoder.java | 25 +- .../TestLocalDynamicFilterConsumer.java | 79 +- .../TestLocalDynamicFiltersCollector.java | 186 ++-- .../trino/sql/planner/TestLogicalPlanner.java | 173 ++-- .../sql/planner/TestPartialTranslator.java | 10 +- .../planner/TestSchedulingOrderVisitor.java | 8 +- .../planner/TestSortExpressionExtractor.java | 6 +- .../sql/planner/TestSymbolAllocator.java | 4 +- .../planner/TestUnwrapYearInComparison.java | 48 +- .../assertions/TestExpressionVerifier.java | 91 +- .../iterative/TestIterativeOptimizer.java | 13 +- .../trino/sql/planner/iterative/TestMemo.java | 50 +- .../sql/planner/iterative/TestRuleIndex.java | 26 +- .../TestDetermineJoinDistributionType.java | 317 +++---- .../rule/TestEliminateCrossJoins.java | 30 +- .../rule/TestGetSourceTablesRowCount.java | 57 +- .../iterative/rule/TestJoinEnumerator.java | 35 +- .../iterative/rule/TestJoinNodeFlattener.java | 36 +- .../TestLambdaCaptureDesugaringRewriter.java | 19 +- .../rule/TestPushProjectionThroughJoin.java | 3 +- .../rule/TestSimplifyExpressions.java | 10 +- .../iterative/rule/test/RuleAssert.java | 2 +- .../TestCardinalityExtractorPlanVisitor.java | 91 +- .../TestExpressionEquivalence.java | 27 +- .../optimizations/TestLocalProperties.java | 70 +- .../optimizations/TestPlanNodeSearcher.java | 6 +- .../sql/planner/optimizations/TestUnion.java | 50 +- ...stPatternRecognitionNodeSerialization.java | 20 +- .../TestStatisticAggregationsDescriptor.java | 4 +- .../sql/planner/plan/TestWindowNode.java | 16 +- .../TestIrRowPatternOptimization.java | 8 +- .../TestRowPatternSerialization.java | 4 +- .../sql/query/TestFilteredAggregations.java | 11 +- .../relational/TestDeterminismEvaluator.java | 9 +- .../test/java/io/trino/testing/TestBytes.java | 4 +- .../tests/TestVerifyTrinoMainTestSetup.java | 4 +- .../transaction/TestTransactionManager.java | 60 +- .../java/io/trino/type/AbstractTestType.java | 109 ++- .../io/trino/type/TestArrayOperators.java | 3 +- .../java/io/trino/type/TestBigintType.java | 5 +- .../java/io/trino/type/TestBooleanType.java | 20 +- .../io/trino/type/TestBoundedVarcharType.java | 5 +- .../test/java/io/trino/type/TestCharType.java | 18 +- .../trino/type/TestCharacterStringCasts.java | 86 +- .../java/io/trino/type/TestColorType.java | 5 +- .../test/java/io/trino/type/TestDateType.java | 5 +- .../io/trino/type/TestDoubleOperators.java | 18 +- .../java/io/trino/type/TestDoubleType.java | 17 +- .../java/io/trino/type/TestFunctionType.java | 4 +- .../java/io/trino/type/TestIntegerType.java | 5 +- .../io/trino/type/TestIntervalDayTime.java | 13 +- .../io/trino/type/TestIntervalYearMonth.java | 13 +- .../java/io/trino/type/TestIpAddressType.java | 3 +- .../io/trino/type/TestLongTimestampType.java | 9 +- .../java/io/trino/type/TestRealOperators.java | 14 +- .../test/java/io/trino/type/TestRealType.java | 9 +- .../java/io/trino/type/TestRowOperators.java | 5 +- .../io/trino/type/TestRowParametricType.java | 4 +- .../io/trino/type/TestShortTimestampType.java | 9 +- .../type/TestSingleAccessMethodCompiler.java | 15 +- .../java/io/trino/type/TestSmallintType.java | 5 +- .../java/io/trino/type/TestTinyintType.java | 5 +- .../java/io/trino/type/TestTypeCoercion.java | 118 ++- .../test/java/io/trino/type/TestUuidType.java | 3 +- .../trino/type/setdigest/TestSetDigest.java | 18 +- .../io/trino/util/BenchmarkPagesSort.java | 6 +- .../trino/util/TestAutoCloseableCloser.java | 15 +- .../java/io/trino/util/TestDateTimeUtils.java | 25 +- .../java/io/trino/util/TestDisjointSet.java | 54 +- .../test/java/io/trino/util/TestFailures.java | 44 +- .../java/io/trino/util/TestHeapTraversal.java | 48 +- .../util/TestLong2LongOpenBigHashMap.java | 38 +- .../trino/util/TestLongBigArrayFIFOQueue.java | 68 +- ...TestLongLong2LongOpenCustomBigHashMap.java | 58 +- .../io/trino/util/TestMergeSortedPages.java | 27 +- .../java/io/trino/util/TestTimeZoneUtils.java | 6 +- .../io/trino/version/TestEmbedVersion.java | 8 +- .../trino/hdfs/TestFSDataInputStreamTail.java | 34 +- .../io/trino/hdfs/TestFileSystemCache.java | 22 +- .../java/io/trino/hdfs/TestHdfsConfig.java | 6 +- .../hdfs/TestTrinoFileSystemCacheStats.java | 44 +- ...stHiveCosServiceConfigurationProvider.java | 8 +- .../io/trino/hdfs/cos/TestServiceConfig.java | 13 +- .../io/trino/hdfs/rubix/TestRubixCaching.java | 25 +- .../s3/AbstractTestTrinoS3FileSystem.java | 33 +- .../trino/hdfs/s3/TestS3SecurityMapping.java | 22 +- .../hdfs/s3/TestS3SecurityMappingsParser.java | 6 +- .../trino/hdfs/s3/TestTrinoS3FileSystem.java | 150 ++- .../hdfs/s3/TestTrinoS3FileSystemMinio.java | 3 +- ...estUriBasedS3SecurityMappingsProvider.java | 4 +- lib/trino-matching/pom.xml | 6 + .../java/io/trino/matching/TestMatcher.java | 34 +- .../io/trino/orc/AbstractTestOrcReader.java | 18 +- .../src/test/java/io/trino/orc/OrcTester.java | 56 +- .../trino/orc/TestCachingOrcDataSource.java | 33 +- .../io/trino/orc/TestDictionaryBuilder.java | 4 +- .../TestDictionaryCompressionOptimizer.java | 178 ++-- .../io/trino/orc/TestOrcBloomFilters.java | 136 +-- .../io/trino/orc/TestOrcDataSourceUtils.java | 44 +- .../test/java/io/trino/orc/TestOrcLz4.java | 8 +- .../io/trino/orc/TestOrcOutputBuffer.java | 22 +- .../trino/orc/TestOrcReaderMemoryUsage.java | 18 +- .../io/trino/orc/TestOrcReaderPositions.java | 118 ++- .../trino/orc/TestOrcWithoutRowGroupInfo.java | 8 +- .../test/java/io/trino/orc/TestOrcWriter.java | 4 +- .../io/trino/orc/TestReadBloomFilter.java | 15 +- .../orc/TestSliceDictionaryColumnReader.java | 9 +- .../orc/TestSliceDictionaryColumnWriter.java | 6 +- .../io/trino/orc/TestStructColumnReader.java | 41 +- .../orc/TestTupleDomainOrcPredicate.java | 313 +++--- .../io/trino/orc/TestingOrcPredicate.java | 59 +- .../orc/metadata/TestOrcMetadataReader.java | 159 ++-- .../AbstractRangeStatisticsTest.java | 12 +- .../AbstractStatisticsBuilderTest.java | 50 +- .../TestBinaryStatisticsBuilder.java | 11 +- .../TestBooleanStatisticsBuilder.java | 4 +- .../statistics/TestDateStatisticsBuilder.java | 14 +- .../TestDoubleStatisticsBuilder.java | 17 +- .../TestIntegerStatisticsBuilder.java | 24 +- .../TestShortDecimalStatisticsBuilder.java | 9 +- .../TestStringStatisticsBuilder.java | 35 +- .../TestTimestampStatisticsBuilder.java | 14 +- .../orc/stream/AbstractTestValueStream.java | 19 +- .../orc/stream/TestBooleanOutputStream.java | 18 +- .../trino/orc/stream/TestBooleanStream.java | 10 +- .../trino/orc/stream/TestDecimalStream.java | 20 +- .../trino/orc/stream/TestLongBitPacker.java | 10 +- .../io/trino/orc/stream/TestLongDecode.java | 10 +- .../parquet/TestParquetTimestampUtils.java | 10 +- .../TestTupleDomainParquetPredicate.java | 168 ++-- .../parquet/predicate/TestPredicateUtils.java | 73 +- .../reader/TestChunkedInputStream.java | 39 +- .../reader/TestColumnIndexBuilder.java | 291 +++--- .../parquet/reader/TestColumnIndexFilter.java | 7 +- .../parquet/reader/TestMetadataReader.java | 230 +++-- .../trino/parquet/reader/TestPageReader.java | 15 +- .../parquet/reader/TestParquetDataSource.java | 15 +- .../parquet/reader/flat/TestBinaryBuffer.java | 5 +- .../TestClassLoaderSafeWrappers.java | 2 +- .../trino/plugin/base/io/TestByteBuffers.java | 14 +- .../base/logging/TestFormatInterpolator.java | 12 +- .../projection/TestApplyProjectionUtil.java | 56 +- ...seFileBasedConnectorAccessControlTest.java | 91 +- .../BaseFileBasedSystemAccessControlTest.java | 227 +++-- .../TestFileBasedAccessControlConfig.java | 8 +- .../trino/plugin/base/util/TestClosables.java | 15 +- .../decoder/avro/AvroDecoderTestUtil.java | 114 ++- .../trino/decoder/avro/TestAvroDecoder.java | 38 +- .../io/trino/decoder/csv/TestCsvDecoder.java | 10 +- .../decoder/json/JsonFieldDecoderTester.java | 40 +- .../trino/decoder/json/TestJsonDecoder.java | 11 +- .../decoder/protobuf/TestProtobufDecoder.java | 64 +- .../io/trino/decoder/raw/TestRawDecoder.java | 23 +- .../trino/decoder/util/DecoderTestUtil.java | 25 +- .../plugin/accumulo/TestAccumuloClient.java | 4 +- .../accumulo/TestAccumuloConnectorTest.java | 46 +- .../plugin/accumulo/index/TestIndexer.java | 37 +- .../accumulo/model/TestAccumuloSplit.java | 8 +- .../plugin/accumulo/model/TestField.java | 92 +- .../trino/plugin/accumulo/model/TestRow.java | 4 +- .../accumulo/model/TestSerializedRange.java | 8 +- .../AbstractTestAccumuloRowSerializer.java | 60 +- .../bigquery/BaseBigQueryConnectorTest.java | 16 +- .../TestBigQueryCaseInsensitiveMapping.java | 17 +- .../bigquery/TestBigQueryMetadataCaching.java | 8 +- .../bigquery/TestStaticCredentialsConfig.java | 12 +- .../plugin/cassandra/CassandraServer.java | 4 +- .../cassandra/CassandraTestingUtils.java | 10 +- .../cassandra/TestCassandraColumnHandle.java | 22 +- .../cassandra/TestCassandraConnector.java | 83 +- .../cassandra/TestCassandraConnectorTest.java | 159 ++-- .../plugin/cassandra/TestCassandraSplit.java | 10 +- .../cassandra/TestCassandraTableHandle.java | 6 +- .../TestCassandraTokenSplitManager.java | 9 +- .../cassandra/TestCassandraTypeManager.java | 16 +- .../cassandra/TestJsonCassandraHandles.java | 49 +- .../TestMurmur3PartitionerTokenRing.java | 31 +- .../TestRandomPartitionerTokenRing.java | 31 +- ...assandraClusteringPredicatesExtractor.java | 6 +- .../util/TestHostAddressFactory.java | 4 +- .../plugin/hive/TestHiveFileSystemAdl.java | 53 +- .../plugin/hive/TestHiveFileSystemS3.java | 3 +- .../trino/plugin/hive/AbstractTestHive.java | 578 +++++------ .../hive/AbstractTestHiveFileSystem.java | 91 +- .../plugin/hive/AbstractTestHiveLocal.java | 4 +- .../plugin/hive/AbstractTestHiveRoles.java | 10 +- .../plugin/hive/BaseHiveConnectorTest.java | 898 +++++++++--------- .../io/trino/plugin/hive/HiveQueryRunner.java | 6 +- .../hive/TestBackgroundHiveSplitLoader.java | 95 +- .../TestHiveAnalyzeCorruptStatistics.java | 10 +- .../plugin/hive/TestHiveBooleanParser.java | 32 +- .../plugin/hive/TestHiveColumnHandle.java | 20 +- .../hive/TestHiveCreateExternalTable.java | 3 +- .../plugin/hive/TestHiveDecimalParser.java | 4 +- .../hive/TestHiveDistributedJoinQueries.java | 4 +- .../plugin/hive/TestHiveFileFormats.java | 67 +- .../plugin/hive/TestHiveLocationService.java | 8 +- .../trino/plugin/hive/TestHiveMetadata.java | 13 +- .../trino/plugin/hive/TestHivePageSink.java | 8 +- .../TestHivePartitionedBucketFunction.java | 14 +- .../plugin/hive/TestHiveQlTranslation.java | 4 +- .../io/trino/plugin/hive/TestHiveSplit.java | 28 +- .../plugin/hive/TestHiveSplitSource.java | 55 +- .../plugin/hive/TestHiveTableHandle.java | 4 +- .../plugin/hive/TestHiveWriterFactory.java | 7 +- .../TestNodeLocalDynamicSplitPruning.java | 10 +- .../hive/TestOrcPageSourceMemoryTracking.java | 90 +- .../plugin/hive/TestOriginalFilesUtils.java | 10 +- .../hive/TestPartitionOfflineException.java | 4 +- .../plugin/hive/TestPartitionUpdate.java | 18 +- .../trino/plugin/hive/TestReaderColumns.java | 55 +- .../hive/TestReaderProjectionsAdapter.java | 20 +- .../hive/TestTableOfflineException.java | 4 +- .../hive/TestTableToPartitionMapping.java | 15 +- ...hingDirectoryListerRecursiveFilesOnly.java | 9 +- .../plugin/hive/fs/TestHiveFileIterator.java | 35 +- .../hive/metastore/TestMetastoreUtil.java | 13 +- .../metastore/TestPrincipalPrivileges.java | 9 +- .../TestSemiTransactionalHiveMetastore.java | 4 +- .../plugin/hive/metastore/TestStorage.java | 4 +- .../cache/TestCachingHiveMetastore.java | 428 ++++----- .../cache/TestReentrantBoundedExecutor.java | 4 +- .../glue/TestGlueExpressionUtil.java | 42 +- .../glue/TestGlueInputConverter.java | 57 +- .../glue/TestGlueToTrinoConverter.java | 139 ++- .../metastore/glue/TestHiveGlueMetastore.java | 33 +- .../thrift/TestCoalescingCounter.java | 22 +- .../thrift/TestStaticMetastoreConfig.java | 10 +- ...taticTokenAwareMetastoreClientFactory.java | 4 +- .../thrift/TestThriftMetastoreUtil.java | 159 ++-- .../thrift/TestThriftSparkMetastoreUtil.java | 128 +-- ...stHiveProjectionPushdownIntoTableScan.java | 6 +- .../orc/TestOrcDeleteDeltaPageSource.java | 6 +- .../plugin/hive/orc/TestOrcDeletedRows.java | 20 +- .../hive/orc/TestOrcPageSourceFactory.java | 24 +- .../plugin/hive/orc/TestOrcPredicates.java | 4 +- .../parquet/AbstractTestParquetReader.java | 4 +- .../plugin/hive/parquet/ParquetTester.java | 18 +- .../hive/parquet/TestBloomFilterStore.java | 29 +- .../parquet/TestParquetPageSourceFactory.java | 6 +- .../plugin/hive/parquet/TestTimestamp.java | 10 +- .../predicate/TestParquetPredicateUtils.java | 27 +- .../TestMetastoreHiveStatisticsProvider.java | 635 ++++++------- .../plugin/hive/util/TestAcidBucketCodec.java | 16 +- .../plugin/hive/util/TestAcidTables.java | 177 ++-- .../plugin/hive/util/TestAsyncQueue.java | 100 +- .../plugin/hive/util/TestHiveAcidUtils.java | 231 +++-- .../plugin/hive/util/TestHiveBucketing.java | 22 +- .../hive/util/TestHiveTypeTranslator.java | 8 +- .../trino/plugin/hive/util/TestHiveUtil.java | 13 +- .../hive/util/TestMergingPageIterator.java | 9 +- .../TestSizeBasedSplitWeightProvider.java | 12 +- .../hive/util/TestThrottledAsyncQueue.java | 140 ++- .../BaseIcebergConnectorSmokeTest.java | 56 +- .../iceberg/BaseIcebergConnectorTest.java | 328 ++++--- .../BaseIcebergMaterializedViewTest.java | 21 +- .../iceberg/BaseIcebergSystemTables.java | 69 +- .../iceberg/BaseSharedMetastoreTest.java | 17 +- .../trino/plugin/iceberg/DataFileRecord.java | 4 +- .../plugin/iceberg/TestIcebergBucketing.java | 25 +- .../iceberg/TestIcebergColumnHandle.java | 20 +- .../iceberg/TestIcebergMergeAppend.java | 6 +- .../iceberg/TestIcebergMigrateProcedure.java | 5 +- ...stIcebergNodeLocalDynamicSplitPruning.java | 16 +- .../TestIcebergOrcMetricsCollection.java | 195 ++-- .../TestIcebergParquetConnectorTest.java | 4 +- .../TestIcebergPartitionEvolution.java | 13 +- .../iceberg/TestIcebergSplitSource.java | 92 +- .../plugin/iceberg/TestIcebergStatistics.java | 7 +- .../plugin/iceberg/TestIcebergTableName.java | 35 +- .../TestIcebergTableWithCustomLocation.java | 38 +- .../TestIcebergTableWithExternalLocation.java | 21 +- .../trino/plugin/iceberg/TestIcebergUtil.java | 20 +- .../trino/plugin/iceberg/TestIcebergV2.java | 60 +- .../plugin/iceberg/TestMetricsWrapper.java | 15 +- .../plugin/iceberg/TestPartitionFields.java | 8 +- .../iceberg/TestPartitionTransforms.java | 44 +- .../plugin/iceberg/TestSortFieldUtils.java | 6 +- .../iceberg/catalog/BaseTrinoCatalogTest.java | 67 +- .../catalog/glue/TestTrinoGlueCatalog.java | 3 +- ...ergTrinoRestCatalogConnectorSmokeTest.java | 4 +- .../rest/TestOAuth2SecurityConfig.java | 4 +- ...StandardInferSchemaConnectorSmokeTest.java | 4 +- .../plugin/kudu/TestKuduConnectorTest.java | 45 +- .../TestKuduIntegrationDecimalColumns.java | 12 +- .../TestKuduIntegrationDynamicFilter.java | 12 +- .../TestKuduIntegrationHashPartitioning.java | 4 +- .../TestKuduIntegrationIntegerColumns.java | 23 +- .../TestKuduIntegrationRangePartitioning.java | 9 +- .../TestRangePartitionSerialization.java | 4 +- .../kudu/schema/TestSchemaEmulation.java | 8 +- .../memory/TestMemoryConnectorTest.java | 21 +- .../plugin/memory/TestMemoryMetadata.java | 71 +- .../plugin/memory/TestMemoryPagesStore.java | 30 +- .../password/file/TestEncryptionUtil.java | 6 +- .../password/file/TestFileGroupProvider.java | 10 +- .../password/file/TestPasswordStore.java | 19 +- .../password/ldap/TestLdapAuthenticator.java | 15 +- .../TestLdapAuthenticatorWithTimeouts.java | 20 +- .../TestSalesforceBasicAuthenticator.java | 32 +- .../salesforce/TestSalesforceConfig.java | 4 +- .../pinot/BasePinotConnectorSmokeTest.java | 19 +- .../trino/plugin/pinot/TestBrokerQueries.java | 35 +- .../trino/plugin/pinot/TestDynamicTable.java | 98 +- .../io/trino/plugin/pinot/TestInstance.java | 6 +- .../plugin/pinot/TestPinotColumnHandle.java | 4 +- .../trino/plugin/pinot/TestPinotConfig.java | 23 +- .../trino/plugin/pinot/TestPinotMetadata.java | 21 +- .../pinot/TestPinotSessionProperties.java | 4 +- .../plugin/pinot/TestPinotSplitManager.java | 14 +- .../plugin/pinot/TestPinotTableHandle.java | 4 +- .../TestPinotEmptyAuthenticationProvider.java | 4 +- ...stPinotPasswordAuthenticationProvider.java | 4 +- ...PrometheusCaseInsensitiveNameMatching.java | 18 +- .../TestPrometheusColumnHandle.java | 4 +- ...estPrometheusQueryMatrixResponseParse.java | 20 +- ...estPrometheusQueryScalarResponseParse.java | 16 +- ...estPrometheusQueryVectorResponseParse.java | 16 +- .../prometheus/TestPrometheusRecordSet.java | 20 +- .../TestPrometheusRecordSetProvider.java | 13 +- .../prometheus/TestPrometheusSplit.java | 77 +- .../prometheus/TestPrometheusTable.java | 8 +- .../prometheus/TestPrometheusTableHandle.java | 4 +- .../TestPrometheusTimestampDeserializer.java | 4 +- .../legacy/BaseRaptorConnectorTest.java | 151 ++- .../legacy/TestRaptorBucketFunction.java | 48 +- .../raptor/legacy/TestRaptorConnector.java | 40 +- .../backup/AbstractTestBackupStore.java | 26 +- .../legacy/backup/TestBackupManager.java | 26 +- .../legacy/backup/TestFileBackupStore.java | 4 +- .../metadata/TestDatabaseShardManager.java | 49 +- .../legacy/metadata/TestMetadataDao.java | 22 +- .../legacy/metadata/TestRaptorMetadata.java | 199 ++-- .../metadata/TestRaptorSplitManager.java | 6 +- .../legacy/metadata/TestShardCleaner.java | 76 +- .../raptor/legacy/metadata/TestShardDao.java | 95 +- .../legacy/metadata/TestShardPredicate.java | 35 +- .../raptor/legacy/storage/OrcTestingUtil.java | 4 +- .../legacy/storage/TestBucketBalancer.java | 10 +- .../storage/TestFileStorageService.java | 46 +- .../storage/TestMissingShardComparator.java | 10 +- .../legacy/storage/TestOrcFileRewriter.java | 159 ++-- .../storage/TestRaptorStorageManager.java | 97 +- .../legacy/storage/TestShardEjector.java | 16 +- .../legacy/storage/TestShardRecovery.java | 75 +- .../legacy/storage/TestShardWriter.java | 97 +- .../TestCompactionSetCreator.java | 31 +- .../organization/TestShardCompactor.java | 9 +- .../TestShardOrganizationManager.java | 16 +- .../organization/TestShardOrganizer.java | 12 +- .../organization/TestShardOrganizerUtil.java | 6 +- .../storage/organization/TestShardRange.java | 43 +- .../organization/TestTemporalFunction.java | 24 +- .../storage/organization/TestTuple.java | 4 +- .../TestShardMetadataRecordCursor.java | 12 +- .../util/TestPrioritizedFifoExecutor.java | 20 +- ...FileResourceGroupConfigurationManager.java | 60 +- .../TestResourceGroupIdTemplate.java | 13 +- .../resourcegroups/TestStaticSelector.java | 58 +- ...seTestDbResourceGroupsFlywayMigration.java | 4 +- .../db/TestDbResourceGroupConfig.java | 4 +- ...stDbResourceGroupConfigurationManager.java | 56 +- .../db/TestDbSourceExactMatchSelector.java | 22 +- .../db/TestResourceGroupsDao.java | 43 +- .../plugin/thrift/api/TestReadWrite.java | 11 +- .../plugin/thrift/api/TestTrinoThriftId.java | 14 +- .../api/datatypes/TestTrinoThriftBigint.java | 43 +- .../TestTrinoThriftAllOrNoneValueSet.java | 12 +- .../TestTrinoThriftEquatableValueSet.java | 23 +- .../TestTrinoThriftRangeValueSet.java | 23 +- plugin/trino-thrift/pom.xml | 6 + .../thrift/TestThriftIndexPageSource.java | 68 +- .../trino/plugin/thrift/TestThriftPlugin.java | 4 +- .../BaseFaultTolerantExecutionTest.java | 9 +- ...TestOverridePartitionCountRecursively.java | 20 +- .../io/trino/tests/product/TestSqlCancel.java | 2 +- .../tests/product/utils/QueryAssertions.java | 2 +- ...stJdbcResultSetCompatibilityOldServer.java | 9 +- .../services/TestFlakyTestRetryAnalyzer.java | 12 +- .../TestReportBadTestAnnotations.java | 8 +- .../testing/AbstractTestAggregations.java | 69 +- .../AbstractTestEngineOnlyQueries.java | 410 ++++---- .../testing/AbstractTestIndexedQueries.java | 7 +- .../testing/AbstractTestJoinQueries.java | 19 +- .../io/trino/testing/AbstractTestQueries.java | 29 +- .../testing/AbstractTestQueryFramework.java | 2 +- .../testing/AbstractTestWindowQueries.java | 3 +- .../io/trino/testing/BaseConnectorTest.java | 246 +++-- .../BaseDynamicPartitionPruningTest.java | 136 ++- .../testing/BaseFailureRecoveryTest.java | 23 +- .../io/trino/testing/MultisetAssertions.java | 2 +- .../trino/testing/PlanDeterminismChecker.java | 4 +- .../io/trino/testing/QueryAssertions.java | 64 +- .../trino/testing/datatype/DataTypeTest.java | 5 +- .../statistics/StatisticsAssertion.java | 6 +- .../io/trino/testing/TestH2QueryRunner.java | 6 +- .../trino/testing/TestTestingTrinoClient.java | 4 +- .../trino/testing/datatype/TestDataType.java | 8 +- .../system/runtime/TestKillQuery.java | 4 +- .../runtime/TestSystemRuntimeConnector.java | 18 +- ...stractTestCoordinatorDynamicFiltering.java | 27 +- .../execution/EventsAwaitingQueries.java | 2 +- .../io/trino/execution/TestBeginQuery.java | 6 +- .../execution/TestCompletedEventWarnings.java | 2 +- .../TestDeprecatedFunctionWarning.java | 2 +- .../execution/TestEventListenerBasic.java | 181 ++-- .../TestEventListenerWithSplits.java | 128 ++- .../execution/TestExecutionJmxMetrics.java | 14 +- .../trino/execution/TestFinalQueryInfo.java | 4 +- .../execution/TestPendingStageState.java | 14 +- .../java/io/trino/execution/TestQueues.java | 13 +- .../TestSetSessionAuthorization.java | 45 +- .../trino/execution/TestStatementStats.java | 16 +- .../trino/execution/TestTableRedirection.java | 23 +- .../TestUserImpersonationAccessControl.java | 14 +- .../java/io/trino/execution/TestWarnings.java | 2 +- .../TestResourceGroupIntegration.java | 7 +- .../resourcegroups/db/TestQueuesDb.java | 53 +- .../memory/TestClusterMemoryLeakDetector.java | 12 +- .../io/trino/memory/TestMemoryManager.java | 35 +- .../io/trino/tests/TestGracefulShutdown.java | 7 +- .../io/trino/tests/TestMetadataManager.java | 11 +- .../trino/tests/TestMinWorkerRequirement.java | 38 +- .../io/trino/tests/TestProcedureCall.java | 13 +- .../java/io/trino/tests/TestQueryManager.java | 25 +- .../io/trino/tests/TestQuerySpillLimits.java | 21 +- .../test/java/io/trino/tests/TestServer.java | 69 +- .../tests/TestVerifyTrinoTestsTestSetup.java | 6 +- .../tests/tpch/TestTpchConnectorTest.java | 5 +- .../tpch/TestTpchTableScanRedirection.java | 4 +- 743 files changed, 16182 insertions(+), 16877 deletions(-) diff --git a/client/trino-cli/src/test/java/io/trino/cli/TestAlignedTablePrinter.java b/client/trino-cli/src/test/java/io/trino/cli/TestAlignedTablePrinter.java index eb097f24f36e..ef050289e175 100644 --- a/client/trino-cli/src/test/java/io/trino/cli/TestAlignedTablePrinter.java +++ b/client/trino-cli/src/test/java/io/trino/cli/TestAlignedTablePrinter.java @@ -31,7 +31,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; import static java.util.stream.Collectors.toMap; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestAlignedTablePrinter { @@ -70,7 +70,7 @@ public void testAlignedPrinting() " bye | done | -15 \n" + "(5 rows)\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -93,7 +93,7 @@ public void testHexPrintingInLists() " [68 65 6c 6c 6f] \n" + "(1 row)\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -116,7 +116,7 @@ public void testHexPrintingInMaps() " {key2=68 65 6c 6c 6f, key=68 65 6c 6c 6f} \n" + "(1 row)\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -139,7 +139,7 @@ public void testHexPrintingInMapKeys() " {68 65 6c 6c 6f=world} \n" + "(1 row)\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -162,7 +162,7 @@ public void testHexPrintingInNestedStructures() " {key2={nested=68 65 6c 6c 6f}, key=[68 65 6c 6c 6f, NULL]} \n" + "(1 row)\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -186,7 +186,7 @@ public void testAlignedPrintingOneRow() " without wrapping | \n" + "(1 row)\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -207,7 +207,7 @@ public void testAlignedPrintingNoRows() "-------+------\n" + "(0 rows)\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -239,7 +239,7 @@ public void testAlignedPrintingHex() " cat | | dog \n" + "(3 rows)\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -272,7 +272,7 @@ public void testAlignedPrintingWideCharacters() " bye | done | -15 \n" + "(3 rows)\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } static Column column(String name, String type) diff --git a/client/trino-cli/src/test/java/io/trino/cli/TestAutoTablePrinter.java b/client/trino-cli/src/test/java/io/trino/cli/TestAutoTablePrinter.java index 6e06cc3247b8..541ee42ec0ea 100644 --- a/client/trino-cli/src/test/java/io/trino/cli/TestAutoTablePrinter.java +++ b/client/trino-cli/src/test/java/io/trino/cli/TestAutoTablePrinter.java @@ -24,7 +24,7 @@ import static io.trino.client.ClientStandardTypes.BIGINT; import static io.trino.client.ClientStandardTypes.VARCHAR; import static java.util.Arrays.asList; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestAutoTablePrinter { @@ -57,7 +57,7 @@ public void testNarrowPrinting() " bye | done | -15 \n" + "(4 rows)\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -93,7 +93,7 @@ public void testWidePrinting() "last | done\n" + "quantity | -15\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } static Column column(String name, String type) diff --git a/client/trino-cli/src/test/java/io/trino/cli/TestClientOptions.java b/client/trino-cli/src/test/java/io/trino/cli/TestClientOptions.java index 0e5bde65df4f..8b56a8b7bcc7 100644 --- a/client/trino-cli/src/test/java/io/trino/cli/TestClientOptions.java +++ b/client/trino-cli/src/test/java/io/trino/cli/TestClientOptions.java @@ -29,9 +29,8 @@ import static io.trino.cli.Trino.createCommandLine; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; public class TestClientOptions { @@ -40,11 +39,11 @@ public void testDefaults() { Console console = createConsole(); ClientOptions options = console.clientOptions; - assertEquals(options.krb5ServicePrincipalPattern, Optional.of("${SERVICE}@${HOST}")); + assertThat(options.krb5ServicePrincipalPattern).isEqualTo(Optional.of("${SERVICE}@${HOST}")); ClientSession session = options.toClientSession(options.getTrinoUri()); - assertEquals(session.getServer().toString(), "http://localhost:8080"); - assertEquals(session.getSource(), "trino-cli"); - assertEquals(session.getTimeZone(), ZoneId.systemDefault()); + assertThat(session.getServer().toString()).isEqualTo("http://localhost:8080"); + assertThat(session.getSource()).isEqualTo("trino-cli"); + assertThat(session.getTimeZone()).isEqualTo(ZoneId.systemDefault()); } @Test @@ -52,7 +51,7 @@ public void testSource() { Console console = createConsole("--source=test"); ClientSession session = console.clientOptions.toClientSession(console.clientOptions.getTrinoUri()); - assertEquals(session.getSource(), "test"); + assertThat(session.getSource()).isEqualTo("test"); } @Test @@ -60,7 +59,7 @@ public void testTraceToken() { Console console = createConsole("--trace-token", "test token"); ClientSession session = console.clientOptions.toClientSession(console.clientOptions.getTrinoUri()); - assertEquals(session.getTraceToken(), Optional.of("test token")); + assertThat(session.getTraceToken()).isEqualTo(Optional.of("test token")); } @Test @@ -68,7 +67,7 @@ public void testServerHostOnly() { Console console = createConsole("--server=test"); ClientSession session = console.clientOptions.toClientSession(console.clientOptions.getTrinoUri()); - assertEquals(session.getServer().toString(), "http://test:80"); + assertThat(session.getServer().toString()).isEqualTo("http://test:80"); } @Test @@ -76,7 +75,7 @@ public void testServerHostPort() { Console console = createConsole("--server=test:8888"); ClientSession session = console.clientOptions.toClientSession(console.clientOptions.getTrinoUri()); - assertEquals(session.getServer().toString(), "http://test:8888"); + assertThat(session.getServer().toString()).isEqualTo("http://test:8888"); } @Test @@ -84,8 +83,8 @@ public void testServerHttpUri() { Console console = createConsole("--server=http://test/foo"); ClientSession session = console.clientOptions.toClientSession(console.clientOptions.getTrinoUri()); - assertEquals(session.getServer().toString(), "http://test:80"); - assertEquals(session.getCatalog(), Optional.of("foo")); + assertThat(session.getServer().toString()).isEqualTo("http://test:80"); + assertThat(session.getCatalog()).isEqualTo(Optional.of("foo")); } @Test @@ -93,8 +92,8 @@ public void testServerTrinoUri() { Console console = createConsole("--server=trino://test/foo"); ClientSession session = console.clientOptions.toClientSession(console.clientOptions.getTrinoUri()); - assertEquals(session.getServer().toString(), "http://test:80"); - assertEquals(session.getCatalog(), Optional.of("foo")); + assertThat(session.getServer().toString()).isEqualTo("http://test:80"); + assertThat(session.getCatalog()).isEqualTo(Optional.of("foo")); } @Test @@ -102,8 +101,8 @@ public void testServerHttpsUri() { Console console = createConsole("--server=https://test/foo"); ClientSession session = console.clientOptions.toClientSession(console.clientOptions.getTrinoUri()); - assertEquals(session.getServer().toString(), "https://test:443"); - assertEquals(session.getCatalog(), Optional.of("foo")); + assertThat(session.getServer().toString()).isEqualTo("https://test:443"); + assertThat(session.getCatalog()).isEqualTo(Optional.of("foo")); } @Test @@ -111,7 +110,7 @@ public void testServer443Port() { Console console = createConsole("--server=test:443"); ClientSession session = console.clientOptions.toClientSession(console.clientOptions.getTrinoUri()); - assertEquals(session.getServer().toString(), "https://test:443"); + assertThat(session.getServer().toString()).isEqualTo("https://test:443"); } @Test @@ -119,7 +118,7 @@ public void testServerHttpsHostPort() { Console console = createConsole("--server=https://test:443"); ClientSession session = console.clientOptions.toClientSession(console.clientOptions.getTrinoUri()); - assertEquals(session.getServer().toString(), "https://test:443"); + assertThat(session.getServer().toString()).isEqualTo("https://test:443"); } @Test @@ -127,7 +126,7 @@ public void testServerHttpWithPort443() { Console console = createConsole("--server=http://test:443"); ClientSession session = console.clientOptions.toClientSession(console.clientOptions.getTrinoUri()); - assertEquals(session.getServer().toString(), "http://test:443"); + assertThat(session.getServer().toString()).isEqualTo("http://test:443"); } @Test @@ -157,7 +156,7 @@ public void testURLHostOnly() { Console console = createConsole("test"); ClientSession session = console.clientOptions.toClientSession(console.clientOptions.getTrinoUri()); - assertEquals(session.getServer().toString(), "http://test:80"); + assertThat(session.getServer().toString()).isEqualTo("http://test:80"); } @Test @@ -167,10 +166,10 @@ public void testURLParams() Console console = createConsole("trino://server.example:8080/my-catalog/my-schema?source=my-client"); TrinoUri uri = console.clientOptions.getTrinoUri(); ClientSession session = console.clientOptions.toClientSession(uri); - assertEquals(session.getServer().toString(), "http://server.example:8080"); - assertEquals(session.getCatalog(), Optional.of("my-catalog")); - assertEquals(session.getSchema(), Optional.of("my-schema")); - assertEquals(uri.getSource(), Optional.of("my-client")); + assertThat(session.getServer().toString()).isEqualTo("http://server.example:8080"); + assertThat(session.getCatalog()).isEqualTo(Optional.of("my-catalog")); + assertThat(session.getSchema()).isEqualTo(Optional.of("my-schema")); + assertThat(uri.getSource()).isEqualTo(Optional.of("my-client")); } @Test @@ -189,7 +188,7 @@ public void testOutputFormat() { Console console = createConsole("--output-format=JSON"); ClientOptions options = console.clientOptions; - assertEquals(options.outputFormat, OutputFormat.JSON); + assertThat(options.outputFormat).isEqualTo(OutputFormat.JSON); } @Test @@ -197,7 +196,7 @@ public void testSocksProxy() { Console console = createConsole("--socks-proxy=abc:123"); ClientOptions options = console.clientOptions; - assertEquals(options.socksProxy, Optional.of(HostAndPort.fromParts("abc", 123))); + assertThat(options.socksProxy).isEqualTo(Optional.of(HostAndPort.fromParts("abc", 123))); } @Test @@ -205,7 +204,7 @@ public void testClientRequestTimeout() { Console console = createConsole("--client-request-timeout=7s"); ClientOptions options = console.clientOptions; - assertEquals(options.clientRequestTimeout, new Duration(7, SECONDS)); + assertThat(options.clientRequestTimeout).isEqualTo(new Duration(7, SECONDS)); } @Test @@ -213,7 +212,7 @@ public void testResourceEstimates() { Console console = createConsole("--resource-estimate", "resource1=1B", "--resource-estimate", "resource2=2.2h"); ClientOptions options = console.clientOptions; - assertEquals(options.resourceEstimates, ImmutableList.of( + assertThat(options.resourceEstimates).isEqualTo(ImmutableList.of( new ClientResourceEstimate("resource1", "1B"), new ClientResourceEstimate("resource2", "2.2h"))); } @@ -223,7 +222,7 @@ public void testExtraCredentials() { Console console = createConsole("--extra-credential", "test.token.foo=foo", "--extra-credential", "test.token.bar=bar"); ClientOptions options = console.clientOptions; - assertEquals(options.extraCredentials, ImmutableList.of( + assertThat(options.extraCredentials).isEqualTo(ImmutableList.of( new ClientOptions.ClientExtraCredential("test.token.foo", "foo"), new ClientOptions.ClientExtraCredential("test.token.bar", "bar"))); } @@ -234,15 +233,15 @@ public void testSessionProperties() Console console = createConsole("--session", "system=system-value", "--session", "catalog.name=catalog-property"); ClientOptions options = console.clientOptions; - assertEquals(options.sessionProperties, ImmutableList.of( + assertThat(options.sessionProperties).isEqualTo(ImmutableList.of( new ClientSessionProperty(Optional.empty(), "system", "system-value"), new ClientSessionProperty(Optional.of("catalog"), "name", "catalog-property"))); // special characters are allowed in the value - assertEquals(new ClientSessionProperty("foo=bar:=baz"), new ClientSessionProperty(Optional.empty(), "foo", "bar:=baz")); + assertThat(new ClientSessionProperty("foo=bar:=baz")).isEqualTo(new ClientSessionProperty(Optional.empty(), "foo", "bar:=baz")); // empty values are allowed - assertEquals(new ClientSessionProperty("foo="), new ClientSessionProperty(Optional.empty(), "foo", "")); + assertThat(new ClientSessionProperty("foo=")).isEqualTo(new ClientSessionProperty(Optional.empty(), "foo", "")); } @Test @@ -251,10 +250,10 @@ public void testTimeZone() Console console = createConsole("--timezone=Europe/Vilnius"); ClientOptions options = console.clientOptions; - assertEquals(options.timeZone, ZoneId.of("Europe/Vilnius")); + assertThat(options.timeZone).isEqualTo(ZoneId.of("Europe/Vilnius")); ClientSession session = options.toClientSession(options.getTrinoUri()); - assertEquals(session.getTimeZone(), ZoneId.of("Europe/Vilnius")); + assertThat(session.getTimeZone()).isEqualTo(ZoneId.of("Europe/Vilnius")); } @Test @@ -263,10 +262,10 @@ public void testDisableCompression() Console console = createConsole("--disable-compression"); ClientOptions options = console.clientOptions; - assertTrue(options.disableCompression); + assertThat(options.disableCompression).isTrue(); ClientSession session = options.toClientSession(options.getTrinoUri()); - assertTrue(session.isCompressionDisabled()); + assertThat(session.isCompressionDisabled()).isTrue(); } @Test diff --git a/client/trino-cli/src/test/java/io/trino/cli/TestCsvPrinter.java b/client/trino-cli/src/test/java/io/trino/cli/TestCsvPrinter.java index 7bfd9c15d4de..8ea249a88e71 100644 --- a/client/trino-cli/src/test/java/io/trino/cli/TestCsvPrinter.java +++ b/client/trino-cli/src/test/java/io/trino/cli/TestCsvPrinter.java @@ -29,7 +29,7 @@ import static io.trino.cli.TestAlignedTablePrinter.row; import static io.trino.cli.TestAlignedTablePrinter.rows; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestCsvPrinter { @@ -61,7 +61,7 @@ public void testCsvPrinting() "text\",\"4567\"\n" + "\"bye\",\"done\",\"-15\"\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -74,7 +74,7 @@ public void testCsvPrintingNoRows() printer.finish(); - assertEquals(writer.getBuffer().toString(), "\"first\",\"last\"\n"); + assertThat(writer.getBuffer().toString()).isEqualTo("\"first\",\"last\"\n"); } @Test @@ -95,7 +95,7 @@ public void testCsvPrintingNoHeader() "\"hello\",\"world\",\"123\"\n" + "\"a\",\"\",\"4.5\"\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -117,7 +117,7 @@ public void testCsvPrintingWithoutQuotes() "hello,world,123\n" + "a,,4.5\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -130,7 +130,7 @@ public void testCsvPrintingNoRowsWithoutQuotes() printer.finish(); - assertEquals(writer.getBuffer().toString(), "first,last\n"); + assertThat(writer.getBuffer().toString()).isEqualTo("first,last\n"); } @Test @@ -151,7 +151,7 @@ public void testCsvPrintingNoHeaderWithoutQuotes() "hello,world,123\n" + "a,,4.5\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -166,7 +166,7 @@ public void testCsvPrintingNoRowsWithNoHeaderAndWithoutQuotes() String expected = ""; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -182,7 +182,7 @@ public void testCsvVarbinaryPrinting() String expected = "\"68 65 6c 6c 6f\",\"\",\"123\"\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -198,7 +198,7 @@ public void testCsvVarbinaryInMaps() String expected = "\"map\",\"value\"\n" + "\"{key=76 61 6c 75 65}\",\"value\"\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -214,7 +214,7 @@ public void testCsvVarbinaryInList() String expected = "\"list\",\"value\"\n" + "\"[76 61 6c 75 65]\",\"value\"\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } private static void printRows(OutputPrinter printer, List... rows) diff --git a/client/trino-cli/src/test/java/io/trino/cli/TestInputParser.java b/client/trino-cli/src/test/java/io/trino/cli/TestInputParser.java index d40b75a790d4..43b10d3d5ea0 100644 --- a/client/trino-cli/src/test/java/io/trino/cli/TestInputParser.java +++ b/client/trino-cli/src/test/java/io/trino/cli/TestInputParser.java @@ -16,10 +16,10 @@ import org.jline.reader.EOFError; import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.jline.reader.Parser.ParseContext.ACCEPT_LINE; import static org.jline.reader.Parser.ParseContext.COMPLETE; -import static org.testng.Assert.assertNotNull; public class TestInputParser { @@ -29,6 +29,6 @@ public void testParseIncompleteStatements() InputParser instance = new InputParser(); // assert an incomplete statement throws an error if the ParseContext is not COMPLETE assertThatThrownBy(() -> instance.parse("show", 4, ACCEPT_LINE)).isInstanceOf(EOFError.class); - assertNotNull(instance.parse("show", 4, COMPLETE)); + assertThat(instance.parse("show", 4, COMPLETE)).isNotNull(); } } diff --git a/client/trino-cli/src/test/java/io/trino/cli/TestInsecureQueryRunner.java b/client/trino-cli/src/test/java/io/trino/cli/TestInsecureQueryRunner.java index e0ddac8c3da4..d297e5fca7ba 100644 --- a/client/trino-cli/src/test/java/io/trino/cli/TestInsecureQueryRunner.java +++ b/client/trino-cli/src/test/java/io/trino/cli/TestInsecureQueryRunner.java @@ -38,8 +38,8 @@ import static io.trino.cli.TestQueryRunner.createResults; import static io.trino.cli.TestQueryRunner.createTrinoUri; import static io.trino.cli.TestQueryRunner.nullPrintStream; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; -import static org.testng.Assert.assertEquals; @TestInstance(PER_METHOD) public class TestInsecureQueryRunner @@ -81,7 +81,7 @@ public void testInsecureConnection() query.renderOutput(getTerminal(), nullPrintStream(), nullPrintStream(), CSV, Optional.of(""), false); } - assertEquals(server.takeRequest().getPath(), "/v1/statement"); + assertThat(server.takeRequest().getPath()).isEqualTo("/v1/statement"); } private SSLContext buildTestSslContext() diff --git a/client/trino-cli/src/test/java/io/trino/cli/TestJsonPrinter.java b/client/trino-cli/src/test/java/io/trino/cli/TestJsonPrinter.java index 88eb152e8329..d801cb4b7c9b 100644 --- a/client/trino-cli/src/test/java/io/trino/cli/TestJsonPrinter.java +++ b/client/trino-cli/src/test/java/io/trino/cli/TestJsonPrinter.java @@ -23,7 +23,7 @@ import static io.trino.cli.TestAlignedTablePrinter.row; import static io.trino.cli.TestAlignedTablePrinter.rows; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestJsonPrinter { @@ -49,7 +49,7 @@ public void testJsonPrinting() "{\"first\":\"some long\\ntext\\tdone\",\"last\":\"more\\ntext\",\"quantity\":4567}\n" + "{\"first\":\"bye\",\"last\":\"done\",\"quantity\":-15}\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -62,7 +62,7 @@ public void testJsonPrintingNoRows() printer.finish(); - assertEquals(writer.getBuffer().toString(), ""); + assertThat(writer.getBuffer().toString()).isEqualTo(""); } @Test @@ -78,6 +78,6 @@ public void testJsonVarbinaryPrinting() String expected = "{\"first\":\"68 65 6c 6c 6f\",\"last\":null,\"quantity\":123}\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } } diff --git a/client/trino-cli/src/test/java/io/trino/cli/TestMarkdownTablePrinter.java b/client/trino-cli/src/test/java/io/trino/cli/TestMarkdownTablePrinter.java index 64aefa83d575..909fafa878bd 100644 --- a/client/trino-cli/src/test/java/io/trino/cli/TestMarkdownTablePrinter.java +++ b/client/trino-cli/src/test/java/io/trino/cli/TestMarkdownTablePrinter.java @@ -26,7 +26,7 @@ import static io.trino.client.ClientStandardTypes.VARCHAR; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestMarkdownTablePrinter { @@ -60,7 +60,7 @@ public void testMarkdownPrinting() "| some long
    text that
    does not
    fit on
    one line | more
    text | 4567 |\n" + "| bye \\| not \\*\\*& \\\\*\\* | done | -15 |\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -82,7 +82,7 @@ public void testMarkdownPrintingOneRow() "| ------------------------------- | ---- |\n" + "| a long line
    without wrapping | text |\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -100,7 +100,7 @@ public void testMarkdownPrintingNoRows() String expected = ""; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -129,7 +129,7 @@ public void testMarkdownPrintingHex() "| a | 73 6f 6d 65 20 6c 6f 6e 67 20 74 65 78 74 20 74
    68 61 74 20 69 73 20 6d 6f 72 65 20 74 68 61 6e
    20 31 36 20 62 79 74 65 73 | b |\n" + "| cat | | dog |\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -158,7 +158,7 @@ public void testMarkdownPrintingWideCharacters() "| some long
    text \u7f51
    does not\u7f51
    fit | more
    text | 4567 |\n" + "| bye | done | -15 |\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } static Column column(String name, String type) diff --git a/client/trino-cli/src/test/java/io/trino/cli/TestQueryRunner.java b/client/trino-cli/src/test/java/io/trino/cli/TestQueryRunner.java index 2f9ad459d83a..349e1e9be3e6 100644 --- a/client/trino-cli/src/test/java/io/trino/cli/TestQueryRunner.java +++ b/client/trino-cli/src/test/java/io/trino/cli/TestQueryRunner.java @@ -50,9 +50,8 @@ import static io.trino.client.ClientStandardTypes.BIGINT; import static io.trino.client.auth.external.ExternalRedirectStrategy.PRINT; import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; @TestInstance(PER_METHOD) public class TestQueryRunner @@ -101,9 +100,9 @@ public void testCookie() query.renderOutput(getTerminal(), nullPrintStream(), nullPrintStream(), CSV, Optional.of(""), false); } - assertNull(server.takeRequest().getHeader("Cookie")); - assertEquals(server.takeRequest().getHeader("Cookie"), "a=apple"); - assertEquals(server.takeRequest().getHeader("Cookie"), "a=apple"); + assertThat(server.takeRequest().getHeader("Cookie")).isNull(); + assertThat(server.takeRequest().getHeader("Cookie")).isEqualTo("a=apple"); + assertThat(server.takeRequest().getHeader("Cookie")).isEqualTo("a=apple"); } static TrinoUri createTrinoUri(MockWebServer server, boolean insecureSsl) diff --git a/client/trino-cli/src/test/java/io/trino/cli/TestTsvPrinter.java b/client/trino-cli/src/test/java/io/trino/cli/TestTsvPrinter.java index 714e625663cd..e6006b38fb71 100644 --- a/client/trino-cli/src/test/java/io/trino/cli/TestTsvPrinter.java +++ b/client/trino-cli/src/test/java/io/trino/cli/TestTsvPrinter.java @@ -23,7 +23,7 @@ import static io.trino.cli.TestAlignedTablePrinter.row; import static io.trino.cli.TestAlignedTablePrinter.rows; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestTsvPrinter { @@ -52,7 +52,7 @@ public void testTsvPrinting() "bye\tdone\t-15\n" + "oops\\0a\\nb\\rc\\bd\\fe\\tf\\\\g\1done\tescape\t9\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -65,7 +65,7 @@ public void testTsvPrintingNoRows() printer.finish(); - assertEquals(writer.getBuffer().toString(), "first\tlast\n"); + assertThat(writer.getBuffer().toString()).isEqualTo("first\tlast\n"); } @Test @@ -86,7 +86,7 @@ public void testTsvPrintingNoHeader() "hello\tworld\t123\n" + "a\t\t4.5\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -102,6 +102,6 @@ public void testTsvVarbinaryPrinting() String expected = "68 65 6c 6c 6f\t\t123\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } } diff --git a/client/trino-cli/src/test/java/io/trino/cli/TestVerticalRecordPrinter.java b/client/trino-cli/src/test/java/io/trino/cli/TestVerticalRecordPrinter.java index 6f8ed957af7d..809fe85728d6 100644 --- a/client/trino-cli/src/test/java/io/trino/cli/TestVerticalRecordPrinter.java +++ b/client/trino-cli/src/test/java/io/trino/cli/TestVerticalRecordPrinter.java @@ -22,7 +22,7 @@ import static io.trino.cli.TestAlignedTablePrinter.bytes; import static io.trino.cli.TestAlignedTablePrinter.row; import static io.trino.cli.TestAlignedTablePrinter.rows; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestVerticalRecordPrinter { @@ -65,7 +65,7 @@ public void testVerticalPrinting() "last | done\n" + "quantity | -15\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -83,7 +83,7 @@ public void testVerticalShortName() "-[ RECORD 1 ]\n" + "a | x\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -101,7 +101,7 @@ public void testVerticalLongName() "-[ RECORD 1 ]+------\n" + "shippriority | hello\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -119,7 +119,7 @@ public void testVerticalLongerName() "-[ RECORD 1 ]--+------\n" + "order_priority | hello\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -137,7 +137,7 @@ public void testVerticalWideCharacterName() "-[ RECORD 1 ]----+------\n" + "order_priority\u7f51 | hello\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -155,7 +155,7 @@ public void testVerticalWideCharacterValue() "-[ RECORD 1 ]-----\n" + "name | hello\u7f51 bye\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } @Test @@ -168,7 +168,7 @@ public void testVerticalPrintingNoRows() printer.finish(); - assertEquals(writer.getBuffer().toString(), "(no rows)\n"); + assertThat(writer.getBuffer().toString()).isEqualTo("(no rows)\n"); } @Test @@ -202,6 +202,6 @@ public void testVerticalPrintingHex() "binary | \n" + "last | dog\n"; - assertEquals(writer.getBuffer().toString(), expected); + assertThat(writer.getBuffer().toString()).isEqualTo(expected); } } diff --git a/client/trino-client/src/test/java/io/trino/client/TestClientTypeSignature.java b/client/trino-client/src/test/java/io/trino/client/TestClientTypeSignature.java index 4a0a681100ce..d22c87065406 100644 --- a/client/trino-client/src/test/java/io/trino/client/TestClientTypeSignature.java +++ b/client/trino-client/src/test/java/io/trino/client/TestClientTypeSignature.java @@ -22,7 +22,7 @@ import java.util.Optional; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestClientTypeSignature { @@ -56,23 +56,23 @@ public void testJsonRoundTrip() public void testStringSerialization() { ClientTypeSignature bigint = new ClientTypeSignature(StandardTypes.BIGINT); - assertEquals(bigint.toString(), "bigint"); ClientTypeSignature varchar = new ClientTypeSignature(StandardTypes.VARCHAR, ImmutableList.of(ClientTypeSignatureParameter.ofLong(50))); - assertEquals(varchar.toString(), "varchar(50)"); + assertThat(bigint.toString()).isEqualTo("bigint"); + assertThat(varchar.toString()).isEqualTo("varchar(50)"); ClientTypeSignature array = new ClientTypeSignature(StandardTypes.ARRAY, ImmutableList.of(ClientTypeSignatureParameter.ofType(new ClientTypeSignature(StandardTypes.BIGINT)))); - assertEquals(array.toString(), "array(bigint)"); + assertThat(array.toString()).isEqualTo("array(bigint)"); ClientTypeSignature row = new ClientTypeSignature( StandardTypes.ROW, ImmutableList.of( ClientTypeSignatureParameter.ofNamedType(new NamedClientTypeSignature(Optional.of(new RowFieldName("foo")), bigint)), ClientTypeSignatureParameter.ofNamedType(new NamedClientTypeSignature(Optional.of(new RowFieldName("bar")), bigint)))); - assertEquals(row.toString(), "row(foo bigint,bar bigint)"); + assertThat(row.toString()).isEqualTo("row(foo bigint,bar bigint)"); } private static void assertJsonRoundTrip(ClientTypeSignature signature) { String json = CLIENT_TYPE_SIGNATURE_CODEC.toJson(signature); ClientTypeSignature copy = CLIENT_TYPE_SIGNATURE_CODEC.fromJson(json); - assertEquals(copy, signature); + assertThat(copy).isEqualTo(signature); } } diff --git a/client/trino-client/src/test/java/io/trino/client/TestFixJsonDataUtils.java b/client/trino-client/src/test/java/io/trino/client/TestFixJsonDataUtils.java index 26c1b8cb7d8e..83bf38c91fb4 100644 --- a/client/trino-client/src/test/java/io/trino/client/TestFixJsonDataUtils.java +++ b/client/trino-client/src/test/java/io/trino/client/TestFixJsonDataUtils.java @@ -49,7 +49,7 @@ import static io.trino.spi.type.TypeSignature.mapType; import static io.trino.spi.type.VarbinaryType.VARBINARY; import static io.trino.spi.type.VarcharType.VARCHAR; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestFixJsonDataUtils { @@ -100,9 +100,9 @@ private void assertQueryResult(TypeSignature type, Object data, Object expected) List> rows = newArrayList(fixData( ImmutableList.of(new Column("test", type.toString(), toClientTypeSignature(type))), ImmutableList.of(ImmutableList.of(data)))); - assertEquals(rows.size(), 1); - assertEquals(rows.get(0).size(), 1); - assertEquals(rows.get(0).get(0), expected); + assertThat(rows.size()).isEqualTo(1); + assertThat(rows.get(0).size()).isEqualTo(1); + assertThat(rows.get(0).get(0)).isEqualTo(expected); } private static ClientTypeSignature toClientTypeSignature(TypeSignature signature) diff --git a/client/trino-client/src/test/java/io/trino/client/TestIntervalDayTime.java b/client/trino-client/src/test/java/io/trino/client/TestIntervalDayTime.java index f09b6ef58d51..ba9902559db3 100644 --- a/client/trino-client/src/test/java/io/trino/client/TestIntervalDayTime.java +++ b/client/trino-client/src/test/java/io/trino/client/TestIntervalDayTime.java @@ -19,8 +19,8 @@ import static io.trino.client.IntervalDayTime.parseMillis; import static io.trino.client.IntervalDayTime.toMillis; import static java.util.concurrent.TimeUnit.DAYS; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestIntervalDayTime { @@ -41,15 +41,15 @@ public void testFormat() private static void assertMillis(long millis, String formatted) { - assertEquals(formatMillis(millis), formatted); - assertEquals(parseMillis(formatted), millis); + assertThat(formatMillis(millis)).isEqualTo(formatted); + assertThat(parseMillis(formatted)).isEqualTo(millis); } @Test public void textMaxDays() { long days = Long.MAX_VALUE / DAYS.toMillis(1); - assertEquals(toMillis(days, 0, 0, 0, 0), DAYS.toMillis(days)); + assertThat(toMillis(days, 0, 0, 0, 0)).isEqualTo(DAYS.toMillis(days)); } @Test diff --git a/client/trino-client/src/test/java/io/trino/client/TestIntervalYearMonth.java b/client/trino-client/src/test/java/io/trino/client/TestIntervalYearMonth.java index d67ed4989611..e510fcc60e72 100644 --- a/client/trino-client/src/test/java/io/trino/client/TestIntervalYearMonth.java +++ b/client/trino-client/src/test/java/io/trino/client/TestIntervalYearMonth.java @@ -18,8 +18,8 @@ import static io.trino.client.IntervalYearMonth.formatMonths; import static io.trino.client.IntervalYearMonth.parseMonths; import static io.trino.client.IntervalYearMonth.toMonths; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestIntervalYearMonth { @@ -47,15 +47,15 @@ public void testFormat() private static void assertMonths(int months, String formatted) { - assertEquals(formatMonths(months), formatted); - assertEquals(parseMonths(formatted), months); + assertThat(formatMonths(months)).isEqualTo(formatted); + assertThat(parseMonths(formatted)).isEqualTo(months); } @Test public void testMaxYears() { int years = Integer.MAX_VALUE / 12; - assertEquals(toMonths(years, 0), years * 12); + assertThat(toMonths(years, 0)).isEqualTo(years * 12); } @Test diff --git a/client/trino-client/src/test/java/io/trino/client/TestJsonCodec.java b/client/trino-client/src/test/java/io/trino/client/TestJsonCodec.java index 9d62db41db50..050349bbf387 100644 --- a/client/trino-client/src/test/java/io/trino/client/TestJsonCodec.java +++ b/client/trino-client/src/test/java/io/trino/client/TestJsonCodec.java @@ -20,8 +20,8 @@ import static io.trino.client.JsonCodec.jsonCodec; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestJsonCodec { @@ -32,7 +32,7 @@ public void testTrailingContent() JsonCodec codec = jsonCodec(ClientTypeSignature.class); String json = "{\"rawType\":\"bigint\",\"arguments\":[]}"; - assertEquals(codec.fromJson(json).getRawType(), "bigint"); + assertThat(codec.fromJson(json).getRawType()).isEqualTo("bigint"); String jsonWithTrailingContent = json + " trailer"; assertThatThrownBy(() -> codec.fromJson(jsonWithTrailingContent)) diff --git a/client/trino-client/src/test/java/io/trino/client/TestProtocolHeaders.java b/client/trino-client/src/test/java/io/trino/client/TestProtocolHeaders.java index 014e63138b94..ed329ef9de69 100644 --- a/client/trino-client/src/test/java/io/trino/client/TestProtocolHeaders.java +++ b/client/trino-client/src/test/java/io/trino/client/TestProtocolHeaders.java @@ -19,8 +19,8 @@ import java.util.Optional; import static io.trino.client.ProtocolHeaders.detectProtocol; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestProtocolHeaders { @@ -29,15 +29,15 @@ public void testDetection() throws Exception { // default is Trino - assertEquals(detectProtocol(Optional.empty(), ImmutableSet.of()).getProtocolName(), "Trino"); + assertThat(detectProtocol(Optional.empty(), ImmutableSet.of()).getProtocolName()).isEqualTo("Trino"); // simple match - assertEquals(detectProtocol(Optional.of("Trino"), ImmutableSet.of("X-Trino-User")).getProtocolName(), "Trino"); - assertEquals(detectProtocol(Optional.of("Taco"), ImmutableSet.of("X-Taco-User")).getProtocolName(), "Taco"); - assertEquals(detectProtocol(Optional.of("Taco"), ImmutableSet.of("X-Taco-Source")).getProtocolName(), "Taco"); + assertThat(detectProtocol(Optional.of("Trino"), ImmutableSet.of("X-Trino-User")).getProtocolName()).isEqualTo("Trino"); + assertThat(detectProtocol(Optional.of("Taco"), ImmutableSet.of("X-Taco-User")).getProtocolName()).isEqualTo("Taco"); + assertThat(detectProtocol(Optional.of("Taco"), ImmutableSet.of("X-Taco-Source")).getProtocolName()).isEqualTo("Taco"); // only specified header name is tested - assertEquals(detectProtocol(Optional.of("Taco"), ImmutableSet.of("X-Burrito-User", "X-Burrito-Source")).getProtocolName(), "Trino"); + assertThat(detectProtocol(Optional.of("Taco"), ImmutableSet.of("X-Burrito-User", "X-Burrito-Source")).getProtocolName()).isEqualTo("Trino"); // multiple protocols is not allowed assertThatThrownBy(() -> detectProtocol(Optional.of("Taco"), ImmutableSet.of("X-Taco-User", "X-Trino-Source"))) diff --git a/client/trino-client/src/test/java/io/trino/client/TestQueryResults.java b/client/trino-client/src/test/java/io/trino/client/TestQueryResults.java index 0f77f0354291..3dd478efcf4b 100644 --- a/client/trino-client/src/test/java/io/trino/client/TestQueryResults.java +++ b/client/trino-client/src/test/java/io/trino/client/TestQueryResults.java @@ -20,7 +20,7 @@ import static io.trino.client.JsonCodec.jsonCodec; import static java.lang.String.format; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestQueryResults { @@ -64,7 +64,7 @@ public void testCompatibility() throws JsonProcessingException { QueryResults results = QUERY_RESULTS_CODEC.fromJson(format(GOLDEN_VALUE, "\"123\"")); - assertEquals(results.getId(), "20160128_214710_00012_rk68b"); + assertThat(results.getId()).isEqualTo("20160128_214710_00012_rk68b"); } @Test @@ -73,6 +73,6 @@ public void testReadLongColumn() { String longString = Strings.repeat("a", StreamReadConstraints.DEFAULT_MAX_STRING_LEN + 1); QueryResults results = QUERY_RESULTS_CODEC.fromJson(format(GOLDEN_VALUE, '"' + longString + '"')); - assertEquals(results.getId(), "20160128_214710_00012_rk68b"); + assertThat(results.getId()).isEqualTo("20160128_214710_00012_rk68b"); } } diff --git a/client/trino-client/src/test/java/io/trino/client/TestRetry.java b/client/trino-client/src/test/java/io/trino/client/TestRetry.java index 852b02a3849a..9b41462a9ea8 100644 --- a/client/trino-client/src/test/java/io/trino/client/TestRetry.java +++ b/client/trino-client/src/test/java/io/trino/client/TestRetry.java @@ -44,7 +44,6 @@ import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; -import static org.testng.Assert.assertTrue; @TestInstance(PER_METHOD) public class TestRetry @@ -93,7 +92,7 @@ public void testRetryOnBrokenStream() while (client.advance()) { // consume all client data } - assertTrue(client.isFinished()); + assertThat(client.isFinished()).isTrue(); } assertThat(server.getRequestCount()).isEqualTo(3); } diff --git a/client/trino-client/src/test/java/io/trino/client/TestServerInfo.java b/client/trino-client/src/test/java/io/trino/client/TestServerInfo.java index 524196d6464b..9f9c9a0a4dd5 100644 --- a/client/trino-client/src/test/java/io/trino/client/TestServerInfo.java +++ b/client/trino-client/src/test/java/io/trino/client/TestServerInfo.java @@ -22,7 +22,7 @@ import static io.airlift.json.JsonCodec.jsonCodec; import static io.trino.client.NodeVersion.UNKNOWN; import static java.util.concurrent.TimeUnit.MINUTES; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestServerInfo { @@ -40,13 +40,13 @@ public void testBackwardsCompatible() { ServerInfo newServerInfo = new ServerInfo(UNKNOWN, "test", true, false, Optional.empty()); ServerInfo legacyServerInfo = SERVER_INFO_CODEC.fromJson("{\"nodeVersion\":{\"version\":\"\"},\"environment\":\"test\",\"coordinator\":true}"); - assertEquals(newServerInfo, legacyServerInfo); + assertThat(newServerInfo).isEqualTo(legacyServerInfo); } private static void assertJsonRoundTrip(ServerInfo serverInfo) { String json = SERVER_INFO_CODEC.toJson(serverInfo); ServerInfo copy = SERVER_INFO_CODEC.fromJson(json); - assertEquals(copy, serverInfo); + assertThat(copy).isEqualTo(serverInfo); } } diff --git a/client/trino-client/src/test/java/io/trino/client/uri/TestTrinoUri.java b/client/trino-client/src/test/java/io/trino/client/uri/TestTrinoUri.java index 860f6be08bc9..f46a0011019a 100644 --- a/client/trino-client/src/test/java/io/trino/client/uri/TestTrinoUri.java +++ b/client/trino-client/src/test/java/io/trino/client/uri/TestTrinoUri.java @@ -34,9 +34,6 @@ import static io.trino.client.uri.PropertyName.SSL_VERIFICATION; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; public class TestTrinoUri { @@ -197,7 +194,7 @@ public void testEmptyPassword() throws SQLException { TrinoUri parameters = createDriverUri("trino://localhost:8080?password="); - assertEquals(parameters.getProperties().getProperty("password"), ""); + assertThat(parameters.getProperties().getProperty("password")).isEqualTo(""); } @Test @@ -205,7 +202,7 @@ public void testNonEmptyPassword() throws SQLException { TrinoUri parameters = createDriverUri("trino://localhost:8080?password=secret"); - assertEquals(parameters.getProperties().getProperty("password"), "secret"); + assertThat(parameters.getProperties().getProperty("password")).isEqualTo("secret"); } @Test @@ -216,7 +213,7 @@ public void testUriWithSocksProxy() assertUriPortScheme(parameters, 8080, "http"); Properties properties = parameters.getProperties(); - assertEquals(properties.getProperty(SOCKS_PROXY.toString()), "localhost:1234"); + assertThat(properties.getProperty(SOCKS_PROXY.toString())).isEqualTo("localhost:1234"); } @Test @@ -227,7 +224,7 @@ public void testUriWithHttpProxy() assertUriPortScheme(parameters, 8080, "http"); Properties properties = parameters.getProperties(); - assertEquals(properties.getProperty(HTTP_PROXY.toString()), "localhost:5678"); + assertThat(properties.getProperty(HTTP_PROXY.toString())).isEqualTo("localhost:5678"); } @Test @@ -235,10 +232,10 @@ public void testUriWithoutCompression() throws SQLException { TrinoUri parameters = createDriverUri("trino://localhost:8080?disableCompression=true"); - assertTrue(parameters.isCompressionDisabled()); + assertThat(parameters.isCompressionDisabled()).isTrue(); Properties properties = parameters.getProperties(); - assertEquals(properties.getProperty(DISABLE_COMPRESSION.toString()), "true"); + assertThat(properties.getProperty(DISABLE_COMPRESSION.toString())).isEqualTo("true"); } @Test @@ -265,8 +262,8 @@ public void testUriWithSslEnabled() assertUriPortScheme(parameters, 8080, "https"); Properties properties = parameters.getProperties(); - assertNull(properties.getProperty(SSL_TRUST_STORE_PATH.toString())); - assertNull(properties.getProperty(SSL_TRUST_STORE_PASSWORD.toString())); + assertThat(properties.getProperty(SSL_TRUST_STORE_PATH.toString())).isNull(); + assertThat(properties.getProperty(SSL_TRUST_STORE_PASSWORD.toString())).isNull(); } @Test @@ -293,8 +290,8 @@ public void testUriWithSslEnabledPathOnly() assertUriPortScheme(parameters, 8080, "https"); Properties properties = parameters.getProperties(); - assertEquals(properties.getProperty(SSL_TRUST_STORE_PATH.toString()), "truststore.jks"); - assertNull(properties.getProperty(SSL_TRUST_STORE_PASSWORD.toString())); + assertThat(properties.getProperty(SSL_TRUST_STORE_PATH.toString())).isEqualTo("truststore.jks"); + assertThat(properties.getProperty(SSL_TRUST_STORE_PASSWORD.toString())).isNull(); } @Test @@ -305,8 +302,8 @@ public void testUriWithSslEnabledPassword() assertUriPortScheme(parameters, 8080, "https"); Properties properties = parameters.getProperties(); - assertEquals(properties.getProperty(SSL_TRUST_STORE_PATH.toString()), "truststore.jks"); - assertEquals(properties.getProperty(SSL_TRUST_STORE_PASSWORD.toString()), "password"); + assertThat(properties.getProperty(SSL_TRUST_STORE_PATH.toString())).isEqualTo("truststore.jks"); + assertThat(properties.getProperty(SSL_TRUST_STORE_PASSWORD.toString())).isEqualTo("password"); } @Test @@ -317,7 +314,7 @@ public void testUriWithSslEnabledUsing443SslVerificationFull() assertUriPortScheme(parameters, 443, "https"); Properties properties = parameters.getProperties(); - assertEquals(properties.getProperty(SSL_VERIFICATION.toString()), FULL.name()); + assertThat(properties.getProperty(SSL_VERIFICATION.toString())).isEqualTo(FULL.name()); } @Test @@ -328,7 +325,7 @@ public void testUriWithSslEnabledUsing443SslVerificationCA() assertUriPortScheme(parameters, 443, "https"); Properties properties = parameters.getProperties(); - assertEquals(properties.getProperty(SSL_VERIFICATION.toString()), CA.name()); + assertThat(properties.getProperty(SSL_VERIFICATION.toString())).isEqualTo(CA.name()); } @Test @@ -339,7 +336,7 @@ public void testUriWithSslEnabledUsing443SslVerificationNONE() assertUriPortScheme(parameters, 443, "https"); Properties properties = parameters.getProperties(); - assertEquals(properties.getProperty(SSL_VERIFICATION.toString()), NONE.name()); + assertThat(properties.getProperty(SSL_VERIFICATION.toString())).isEqualTo(NONE.name()); } @Test @@ -350,7 +347,7 @@ public void testUriWithSslEnabledSystemTrustStoreDefault() assertUriPortScheme(parameters, 8080, "https"); Properties properties = parameters.getProperties(); - assertEquals(properties.getProperty(SSL_USE_SYSTEM_TRUST_STORE.toString()), "true"); + assertThat(properties.getProperty(SSL_USE_SYSTEM_TRUST_STORE.toString())).isEqualTo("true"); } @Test @@ -361,8 +358,8 @@ public void testUriWithSslEnabledSystemTrustStoreOverride() assertUriPortScheme(parameters, 8080, "https"); Properties properties = parameters.getProperties(); - assertEquals(properties.getProperty(SSL_TRUST_STORE_TYPE.toString()), "Override"); - assertEquals(properties.getProperty(SSL_USE_SYSTEM_TRUST_STORE.toString()), "true"); + assertThat(properties.getProperty(SSL_TRUST_STORE_TYPE.toString())).isEqualTo("Override"); + assertThat(properties.getProperty(SSL_USE_SYSTEM_TRUST_STORE.toString())).isEqualTo("true"); } @Test @@ -372,7 +369,7 @@ public void testUriWithExtraCredentials() String extraCredentials = "test.token.foo:bar;test.token.abc:xyz"; TrinoUri parameters = createDriverUri("trino://localhost:8080?extraCredentials=" + extraCredentials); Properties properties = parameters.getProperties(); - assertEquals(properties.getProperty(EXTRA_CREDENTIALS.toString()), extraCredentials); + assertThat(properties.getProperty(EXTRA_CREDENTIALS.toString())).isEqualTo(extraCredentials); } @Test @@ -382,7 +379,7 @@ public void testUriWithClientTags() String clientTags = "c1,c2"; TrinoUri parameters = createDriverUri("trino://localhost:8080?clientTags=" + clientTags); Properties properties = parameters.getProperties(); - assertEquals(properties.getProperty(CLIENT_TAGS.toString()), clientTags); + assertThat(properties.getProperty(CLIENT_TAGS.toString())).isEqualTo(clientTags); } @Test @@ -424,8 +421,8 @@ public void testAssumeLiteralUnderscoreInMetadataCallsForNonConformingClients() private static void assertUriPortScheme(TrinoUri parameters, int port, String scheme) { URI uri = parameters.getHttpUri(); - assertEquals(uri.getPort(), port); - assertEquals(uri.getScheme(), scheme); + assertThat(uri.getPort()).isEqualTo(port); + assertThat(uri.getScheme()).isEqualTo(scheme); } private static TrinoUri createDriverUri(String url) diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/BaseTestJdbcResultSet.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/BaseTestJdbcResultSet.java index ba7c0f699334..6bae338a8cbb 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/BaseTestJdbcResultSet.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/BaseTestJdbcResultSet.java @@ -52,11 +52,6 @@ import static java.util.concurrent.TimeUnit.HOURS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; public abstract class BaseTestJdbcResultSet { @@ -72,14 +67,14 @@ public void testDuplicateColumnLabels() try (ConnectedStatement connectedStatement = newStatement()) { try (ResultSet rs = connectedStatement.getStatement().executeQuery("SELECT 123 x, 456 x")) { ResultSetMetaData metadata = rs.getMetaData(); - assertEquals(metadata.getColumnCount(), 2); - assertEquals(metadata.getColumnName(1), "x"); - assertEquals(metadata.getColumnName(2), "x"); - - assertTrue(rs.next()); - assertEquals(rs.getLong(1), 123L); - assertEquals(rs.getLong(2), 456L); - assertEquals(rs.getLong("x"), 123L); + assertThat(metadata.getColumnCount()).isEqualTo(2); + assertThat(metadata.getColumnName(1)).isEqualTo("x"); + assertThat(metadata.getColumnName(2)).isEqualTo("x"); + + assertThat(rs.next()).isTrue(); + assertThat(rs.getLong(1)).isEqualTo(123L); + assertThat(rs.getLong(2)).isEqualTo(456L); + assertThat(rs.getLong("x")).isEqualTo(123L); } } } @@ -97,22 +92,22 @@ public void testPrimitiveTypes() checkRepresentation(connectedStatement.getStatement(), "0.0E0 / 0.0E0", Types.DOUBLE, Double.NaN); checkRepresentation(connectedStatement.getStatement(), "true", Types.BOOLEAN, true); checkRepresentation(connectedStatement.getStatement(), "'hello'", Types.VARCHAR, (rs, column) -> { - assertEquals(rs.getObject(column), "hello"); + assertThat(rs.getObject(column)).isEqualTo("hello"); assertThat(rs.getAsciiStream(column)).hasBinaryContent("hello".getBytes(StandardCharsets.US_ASCII)); assertThatThrownBy(() -> rs.getBinaryStream(column)) .isInstanceOf(SQLException.class) .hasMessage("Value is not a byte array: hello"); }); checkRepresentation(connectedStatement.getStatement(), "CAST(NULL AS VARCHAR)", Types.VARCHAR, (rs, column) -> { - assertNull(rs.getAsciiStream(column)); - assertNull(rs.getBinaryStream(column)); + assertThat(rs.getAsciiStream(column)).isNull(); + assertThat(rs.getBinaryStream(column)).isNull(); }); checkRepresentation(connectedStatement.getStatement(), "cast('foo' as char(5))", Types.CHAR, (rs, column) -> { - assertEquals(rs.getObject(column), "foo "); + assertThat(rs.getObject(column)).isEqualTo("foo "); assertThat(rs.getAsciiStream(column)).hasBinaryContent("foo ".getBytes(StandardCharsets.US_ASCII)); }); checkRepresentation(connectedStatement.getStatement(), "VARCHAR '123'", Types.VARCHAR, - (rs, column) -> assertEquals(rs.getLong(column), 123L)); + (rs, column) -> assertThat(rs.getLong(column)).isEqualTo(123L)); checkRepresentation(connectedStatement.getStatement(), "VARCHAR ''", Types.VARCHAR, (rs, column) -> { assertThatThrownBy(() -> rs.getLong(column)) @@ -131,25 +126,25 @@ public void testPrimitiveTypes() }); checkRepresentation(connectedStatement.getStatement(), "VARCHAR '123e-1'", Types.VARCHAR, (rs, column) -> { - assertEquals(rs.getDouble(column), 12.3); - assertEquals(rs.getLong(column), 12); - assertEquals(rs.getFloat(column), 12.3f); + assertThat(rs.getDouble(column)).isEqualTo(12.3); + assertThat(rs.getLong(column)).isEqualTo(12); + assertThat(rs.getFloat(column)).isEqualTo(12.3f); assertThat(rs.getAsciiStream(column)).hasBinaryContent("123e-1".getBytes(StandardCharsets.US_ASCII)); }); checkRepresentation(connectedStatement.getStatement(), "DOUBLE '123.456'", Types.DOUBLE, (rs, column) -> { - assertEquals(rs.getDouble(column), 123.456); - assertEquals(rs.getLong(column), 123); - assertEquals(rs.getFloat(column), 123.456f); + assertThat(rs.getDouble(column)).isEqualTo(123.456); + assertThat(rs.getLong(column)).isEqualTo(123); + assertThat(rs.getFloat(column)).isEqualTo(123.456f); assertThatThrownBy(() -> rs.getAsciiStream(column)) .isInstanceOf(SQLException.class) .hasMessage("Value is not a string: 123.456"); }); checkRepresentation(connectedStatement.getStatement(), "VARCHAR '123'", Types.VARCHAR, (rs, column) -> { - assertEquals(rs.getDouble(column), 123.0); - assertEquals(rs.getLong(column), 123); - assertEquals(rs.getFloat(column), 123f); + assertThat(rs.getDouble(column)).isEqualTo(123.0); + assertThat(rs.getLong(column)).isEqualTo(123); + assertThat(rs.getFloat(column)).isEqualTo(123f); assertThat(rs.getAsciiStream(column)).hasBinaryContent("123".getBytes(StandardCharsets.US_ASCII)); }); } @@ -162,10 +157,10 @@ public void testDecimal() try (ConnectedStatement connectedStatement = newStatement()) { checkRepresentation(connectedStatement.getStatement(), "0.1", Types.DECIMAL, new BigDecimal("0.1")); checkRepresentation(connectedStatement.getStatement(), "DECIMAL '0.12'", Types.DECIMAL, (rs, column) -> { - assertEquals(rs.getBigDecimal(column), new BigDecimal("0.12")); - assertEquals(rs.getDouble(column), 0.12); - assertEquals(rs.getLong(column), 0); - assertEquals(rs.getFloat(column), 0.12f); + assertThat(rs.getBigDecimal(column)).isEqualTo(new BigDecimal("0.12")); + assertThat(rs.getDouble(column)).isEqualTo(0.12); + assertThat(rs.getLong(column)).isEqualTo(0); + assertThat(rs.getFloat(column)).isEqualTo(0.12f); }); long outsideOfDoubleExactRange = 9223372036854775774L; @@ -174,11 +169,11 @@ public void testDecimal() checkRepresentation(connectedStatement.getStatement(), format("DECIMAL '%s'", outsideOfDoubleExactRange), Types.DECIMAL, (rs, column) -> { - assertEquals(rs.getObject(column), new BigDecimal("9223372036854775774")); - assertEquals(rs.getBigDecimal(column), new BigDecimal("9223372036854775774")); - assertEquals(rs.getLong(column), 9223372036854775774L); - assertEquals(rs.getDouble(column), 9.223372036854776E18); - assertEquals(rs.getString(column), "9223372036854775774"); + assertThat(rs.getObject(column)).isEqualTo(new BigDecimal("9223372036854775774")); + assertThat(rs.getBigDecimal(column)).isEqualTo(new BigDecimal("9223372036854775774")); + assertThat(rs.getLong(column)).isEqualTo(9223372036854775774L); + assertThat(rs.getDouble(column)).isEqualTo(9.223372036854776E18); + assertThat(rs.getString(column)).isEqualTo("9223372036854775774"); }); checkRepresentation(connectedStatement.getStatement(), "VARCHAR ''", Types.VARCHAR, (rs, column) -> { @@ -194,7 +189,7 @@ public void testDecimal() }); checkRepresentation(connectedStatement.getStatement(), "VARCHAR '123e-1'", Types.VARCHAR, - (rs, column) -> assertEquals(rs.getBigDecimal(column), new BigDecimal("12.3"))); + (rs, column) -> assertThat(rs.getBigDecimal(column)).isEqualTo(new BigDecimal("12.3"))); } } @@ -216,15 +211,15 @@ public void testVarbinary() .isInstanceOf(SQLException.class) .hasMessageStartingWith("Value is not a number: [B@"); - assertEquals(rs.getString(column), "0x12345678"); + assertThat(rs.getString(column)).isEqualTo("0x12345678"); assertThat(rs.getBinaryStream(column)).hasBinaryContent(bytes); assertThatThrownBy(() -> rs.getAsciiStream(column)) .isInstanceOf(SQLException.class) .hasMessageStartingWith("Value is not a string: [B@"); }); checkRepresentation(connectedStatement.getStatement(), "CAST(NULL AS VARBINARY)", Types.VARBINARY, (rs, column) -> { - assertNull(rs.getAsciiStream(column)); - assertNull(rs.getBinaryStream(column)); + assertThat(rs.getAsciiStream(column)).isNull(); + assertThat(rs.getBinaryStream(column)).isNull(); }); checkRepresentation(connectedStatement.getStatement(), "X''", Types.VARBINARY, (rs, column) -> { @@ -245,11 +240,11 @@ public void testDate() LocalDate localDate = LocalDate.of(2018, 2, 13); Date sqlDate = Date.valueOf(localDate); - assertEquals(rs.getObject(column), sqlDate); - assertEquals(rs.getObject(column, Date.class), sqlDate); - assertEquals(rs.getObject(column, LocalDate.class), localDate); + assertThat(rs.getObject(column)).isEqualTo(sqlDate); + assertThat(rs.getObject(column, Date.class)).isEqualTo(sqlDate); + assertThat(rs.getObject(column, LocalDate.class)).isEqualTo(localDate); - assertEquals(rs.getDate(column), sqlDate); + assertThat(rs.getDate(column)).isEqualTo(sqlDate); assertThatThrownBy(() -> rs.getTime(column)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Expected column to be a time type but is date"); @@ -257,7 +252,7 @@ public void testDate() .isInstanceOf(IllegalArgumentException.class) .hasMessage("Expected column to be a timestamp type but is date"); - assertEquals(rs.getString(column), localDate.toString()); + assertThat(rs.getString(column)).isEqualTo(localDate.toString()); }); // distant past, but apparently not an uncommon value in practice @@ -265,11 +260,11 @@ public void testDate() LocalDate localDate = LocalDate.of(1, 1, 1); Date sqlDate = Date.valueOf(localDate); - assertEquals(rs.getObject(column), sqlDate); - assertEquals(rs.getObject(column, Date.class), sqlDate); - assertEquals(rs.getObject(column, LocalDate.class), localDate); + assertThat(rs.getObject(column)).isEqualTo(sqlDate); + assertThat(rs.getObject(column, Date.class)).isEqualTo(sqlDate); + assertThat(rs.getObject(column, LocalDate.class)).isEqualTo(localDate); - assertEquals(rs.getDate(column), sqlDate); + assertThat(rs.getDate(column)).isEqualTo(sqlDate); assertThatThrownBy(() -> rs.getTime(column)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Expected column to be a time type but is date"); @@ -277,7 +272,7 @@ public void testDate() .isInstanceOf(IllegalArgumentException.class) .hasMessage("Expected column to be a timestamp type but is date"); - assertEquals(rs.getString(column), localDate.toString()); + assertThat(rs.getString(column)).isEqualTo(localDate.toString()); }); // date which midnight does not exist in test JVM zone @@ -285,10 +280,10 @@ public void testDate() LocalDate localDate = LocalDate.of(1970, 1, 1); Date sqlDate = Date.valueOf(localDate); - assertEquals(rs.getObject(column), sqlDate); - assertEquals(rs.getObject(column, Date.class), sqlDate); - assertEquals(rs.getObject(column, LocalDate.class), localDate); - assertEquals(rs.getDate(column), sqlDate); + assertThat(rs.getObject(column)).isEqualTo(sqlDate); + assertThat(rs.getObject(column, Date.class)).isEqualTo(sqlDate); + assertThat(rs.getObject(column, LocalDate.class)).isEqualTo(localDate); + assertThat(rs.getDate(column)).isEqualTo(sqlDate); assertThatThrownBy(() -> rs.getTime(column)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Expected column to be a time type but is date"); @@ -296,7 +291,7 @@ public void testDate() .isInstanceOf(IllegalArgumentException.class) .hasMessage("Expected column to be a timestamp type but is date"); - assertEquals(rs.getString(column), localDate.toString()); + assertThat(rs.getString(column)).isEqualTo(localDate.toString()); }); // the Julian-Gregorian calendar "default cut-over" @@ -304,11 +299,11 @@ public void testDate() LocalDate localDate = LocalDate.of(1582, 10, 4); Date sqlDate = Date.valueOf(localDate); - assertEquals(rs.getObject(column), sqlDate); - assertEquals(rs.getObject(column, Date.class), sqlDate); - assertEquals(rs.getObject(column, LocalDate.class), localDate); + assertThat(rs.getObject(column)).isEqualTo(sqlDate); + assertThat(rs.getObject(column, Date.class)).isEqualTo(sqlDate); + assertThat(rs.getObject(column, LocalDate.class)).isEqualTo(localDate); - assertEquals(rs.getDate(column), sqlDate); + assertThat(rs.getDate(column)).isEqualTo(sqlDate); assertThatThrownBy(() -> rs.getTime(column)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Expected column to be a time type but is date"); @@ -316,7 +311,7 @@ public void testDate() .isInstanceOf(IllegalArgumentException.class) .hasMessage("Expected column to be a timestamp type but is date"); - assertEquals(rs.getString(column), localDate.toString()); + assertThat(rs.getString(column)).isEqualTo(localDate.toString()); }); // after the Julian-Gregorian calendar "default cut-over", but before the Gregorian calendar start @@ -324,13 +319,13 @@ public void testDate() LocalDate localDate = LocalDate.of(1582, 10, 10); Date sqlDate = Date.valueOf(localDate); - assertEquals(rs.getObject(column), sqlDate); - assertEquals(rs.getObject(column, Date.class), sqlDate); + assertThat(rs.getObject(column)).isEqualTo(sqlDate); + assertThat(rs.getObject(column, Date.class)).isEqualTo(sqlDate); // There are no days between 1582-10-05 and 1582-10-14 - assertEquals(rs.getObject(column, LocalDate.class), LocalDate.of(1582, 10, 20)); + assertThat(rs.getObject(column, LocalDate.class)).isEqualTo(LocalDate.of(1582, 10, 20)); - assertEquals(rs.getDate(column), sqlDate); + assertThat(rs.getDate(column)).isEqualTo(sqlDate); assertThatThrownBy(() -> rs.getTime(column)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Expected column to be a time type but is date"); @@ -338,7 +333,7 @@ public void testDate() .isInstanceOf(IllegalArgumentException.class) .hasMessage("Expected column to be a timestamp type but is date"); - assertEquals(rs.getString(column), localDate.toString()); + assertThat(rs.getString(column)).isEqualTo(localDate.toString()); }); // the Gregorian calendar start @@ -346,11 +341,11 @@ public void testDate() LocalDate localDate = LocalDate.of(1582, 10, 15); Date sqlDate = Date.valueOf(localDate); - assertEquals(rs.getObject(column), sqlDate); - assertEquals(rs.getObject(column, Date.class), sqlDate); - assertEquals(rs.getObject(column, LocalDate.class), localDate); + assertThat(rs.getObject(column)).isEqualTo(sqlDate); + assertThat(rs.getObject(column, Date.class)).isEqualTo(sqlDate); + assertThat(rs.getObject(column, LocalDate.class)).isEqualTo(localDate); - assertEquals(rs.getDate(column), sqlDate); + assertThat(rs.getDate(column)).isEqualTo(sqlDate); assertThatThrownBy(() -> rs.getTime(column)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Expected column to be a time type but is date"); @@ -358,7 +353,7 @@ public void testDate() .isInstanceOf(IllegalArgumentException.class) .hasMessage("Expected column to be a timestamp type but is date"); - assertEquals(rs.getString(column), localDate.toString()); + assertThat(rs.getString(column)).isEqualTo(localDate.toString()); }); } } @@ -369,24 +364,24 @@ public void testTime() { try (ConnectedStatement connectedStatement = newStatement()) { checkRepresentation(connectedStatement.getStatement(), "TIME '09:39:05.000'", Types.TIME, (rs, column) -> { - assertEquals(rs.getObject(column), toSqlTime(LocalTime.of(9, 39, 5))); - assertEquals(rs.getObject(column, Time.class), toSqlTime(LocalTime.of(9, 39, 5))); + assertThat(rs.getObject(column)).isEqualTo(toSqlTime(LocalTime.of(9, 39, 5))); + assertThat(rs.getObject(column, Time.class)).isEqualTo(toSqlTime(LocalTime.of(9, 39, 5))); assertThatThrownBy(() -> rs.getDate(column)) .isInstanceOf(SQLException.class) .hasMessage("Expected value to be a date but is: 09:39:05.000"); - assertEquals(rs.getTime(column), Time.valueOf(LocalTime.of(9, 39, 5))); + assertThat(rs.getTime(column)).isEqualTo(Time.valueOf(LocalTime.of(9, 39, 5))); assertThatThrownBy(() -> rs.getTimestamp(column)) .isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/trinodb/trino/issues/5315) SQLException .hasMessage("Expected column to be a timestamp type but is time(3)"); }); checkRepresentation(connectedStatement.getStatement(), "TIME '00:39:05'", Types.TIME, (rs, column) -> { - assertEquals(rs.getObject(column), toSqlTime(LocalTime.of(0, 39, 5))); - assertEquals(rs.getObject(column, Time.class), toSqlTime(LocalTime.of(0, 39, 5))); + assertThat(rs.getObject(column)).isEqualTo(toSqlTime(LocalTime.of(0, 39, 5))); + assertThat(rs.getObject(column, Time.class)).isEqualTo(toSqlTime(LocalTime.of(0, 39, 5))); assertThatThrownBy(() -> rs.getDate(column)) .isInstanceOf(SQLException.class) .hasMessage("Expected value to be a date but is: 00:39:05"); - assertEquals(rs.getTime(column), Time.valueOf(LocalTime.of(0, 39, 5))); + assertThat(rs.getTime(column)).isEqualTo(Time.valueOf(LocalTime.of(0, 39, 5))); assertThatThrownBy(() -> rs.getTimestamp(column)) .isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/trinodb/trino/issues/5315) SQLException .hasMessage("Expected column to be a timestamp type but is time(0)"); @@ -395,11 +390,11 @@ public void testTime() // second fraction could be overflowing to next millisecond checkRepresentation(connectedStatement.getStatement(), "TIME '10:11:12.1235'", Types.TIME, (rs, column) -> { // TODO (https://github.com/trinodb/trino/issues/6205) maybe should round to 124 ms instead - assertEquals(rs.getObject(column), toSqlTime(LocalTime.of(10, 11, 12, 123_000_000))); + assertThat(rs.getObject(column)).isEqualTo(toSqlTime(LocalTime.of(10, 11, 12, 123_000_000))); assertThatThrownBy(() -> rs.getDate(column)) .isInstanceOf(SQLException.class) .hasMessage("Expected value to be a date but is: 10:11:12.1235"); - assertEquals(rs.getTime(column), toSqlTime(LocalTime.of(10, 11, 12, 123_000_000))); + assertThat(rs.getTime(column)).isEqualTo(toSqlTime(LocalTime.of(10, 11, 12, 123_000_000))); assertThatThrownBy(() -> rs.getTimestamp(column)) .isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/trinodb/trino/issues/5315) SQLException .hasMessage("Expected column to be a timestamp type but is time(4)"); @@ -408,12 +403,12 @@ public void testTime() // second fraction could be overflowing to next nanosecond, second, minute and hour checkRepresentation(connectedStatement.getStatement(), "TIME '10:59:59.999999999999'", Types.TIME, (rs, column) -> { // TODO (https://github.com/trinodb/trino/issues/6205) maybe result should be 11:00:00 - assertEquals(rs.getObject(column), toSqlTime(LocalTime.of(10, 59, 59, 999_000_000))); + assertThat(rs.getObject(column)).isEqualTo(toSqlTime(LocalTime.of(10, 59, 59, 999_000_000))); assertThatThrownBy(() -> rs.getDate(column)) .isInstanceOf(SQLException.class) .hasMessage("Expected value to be a date but is: 10:59:59.999999999999"); // TODO (https://github.com/trinodb/trino/issues/6205) maybe result should be 11:00:00 - assertEquals(rs.getTime(column), toSqlTime(LocalTime.of(10, 59, 59, 999_000_000))); + assertThat(rs.getTime(column)).isEqualTo(toSqlTime(LocalTime.of(10, 59, 59, 999_000_000))); assertThatThrownBy(() -> rs.getTimestamp(column)) .isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/trinodb/trino/issues/5315) SQLException .hasMessage("Expected column to be a timestamp type but is time(12)"); @@ -422,12 +417,12 @@ public void testTime() // second fraction could be overflowing to next day checkRepresentation(connectedStatement.getStatement(), "TIME '23:59:59.999999999999'", Types.TIME, (rs, column) -> { // TODO (https://github.com/trinodb/trino/issues/6205) maybe result should be 01:00:00 (shifted from 00:00:00 as test JVM has gap in 1970-01-01) - assertEquals(rs.getObject(column), toSqlTime(LocalTime.of(23, 59, 59, 999_000_000))); + assertThat(rs.getObject(column)).isEqualTo(toSqlTime(LocalTime.of(23, 59, 59, 999_000_000))); assertThatThrownBy(() -> rs.getDate(column)) .isInstanceOf(SQLException.class) .hasMessage("Expected value to be a date but is: 23:59:59.999999999999"); // TODO (https://github.com/trinodb/trino/issues/6205) maybe result should be 01:00:00 (shifted from 00:00:00 as test JVM has gap in 1970-01-01) - assertEquals(rs.getTime(column), toSqlTime(LocalTime.of(23, 59, 59, 999_000_000))); + assertThat(rs.getTime(column)).isEqualTo(toSqlTime(LocalTime.of(23, 59, 59, 999_000_000))); assertThatThrownBy(() -> rs.getTimestamp(column)) .isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/trinodb/trino/issues/5315) SQLException .hasMessage("Expected column to be a timestamp type but is time(12)"); @@ -441,11 +436,11 @@ public void testTimeWithTimeZone() { try (ConnectedStatement connectedStatement = newStatement()) { checkRepresentation(connectedStatement.getStatement(), "TIME '09:39:07 +01:00'", Types.TIME_WITH_TIMEZONE, (rs, column) -> { - assertEquals(rs.getObject(column), Time.valueOf(LocalTime.of(1, 39, 7))); // TODO this should represent TIME '09:39:07 +01:00' + assertThat(rs.getObject(column)).isEqualTo(Time.valueOf(LocalTime.of(1, 39, 7))); // TODO this should represent TIME '09:39:07 +01:00' assertThatThrownBy(() -> rs.getDate(column)) .isInstanceOf(SQLException.class) .hasMessage("Expected value to be a date but is: 09:39:07+01:00"); - assertEquals(rs.getTime(column), Time.valueOf(LocalTime.of(1, 39, 7))); // TODO this should fail, or represent TIME '09:39:07' + assertThat(rs.getTime(column)).isEqualTo(Time.valueOf(LocalTime.of(1, 39, 7))); // TODO this should fail, or represent TIME '09:39:07' // TODO (https://github.com/trinodb/trino/issues/5317) placement of precision parameter assertThatThrownBy(() -> rs.getTimestamp(column)) .isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/trinodb/trino/issues/5315) SQLException @@ -458,11 +453,11 @@ public void testTimeWithTimeZone() LocalTime.of(16, 39, 7)).getTime() /* 16:39:07 = 01:39:07 - +01:00 shift + Bahia_Banderas's shift (-8) (modulo 24h which we "un-modulo" below) */ - DAYS.toMillis(1) /* because we use currently 'shifted' representation, not possible to create just using LocalTime */ + HOURS.toMillis(1) /* because there was offset shift on 1970-01-01 in America/Bahia_Banderas */); - assertEquals(rs.getObject(column), someBogusValue); // TODO this should represent TIME '01:39:07 +01:00' + assertThat(rs.getObject(column)).isEqualTo(someBogusValue); // TODO this should represent TIME '01:39:07 +01:00' assertThatThrownBy(() -> rs.getDate(column)) .isInstanceOf(SQLException.class) .hasMessage("Expected value to be a date but is: 01:39:07+01:00"); - assertEquals(rs.getTime(column), someBogusValue); // TODO this should fail, or represent TIME '01:39:07' + assertThat(rs.getTime(column)).isEqualTo(someBogusValue); // TODO this should fail, or represent TIME '01:39:07' // TODO (https://github.com/trinodb/trino/issues/5317) placement of precision parameter assertThatThrownBy(() -> rs.getTimestamp(column)) .isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/trinodb/trino/issues/5315) SQLException @@ -475,11 +470,11 @@ public void testTimeWithTimeZone() LocalTime.of(15, 39, 7)).getTime() /* 15:39:07 = 00:39:07 - +01:00 shift + Bahia_Banderas's shift (-8) (modulo 24h which we "un-modulo" below) */ - DAYS.toMillis(1) /* because we use currently 'shifted' representation, not possible to create just using LocalTime */ + HOURS.toMillis(1) /* because there was offset shift on 1970-01-01 in America/Bahia_Banderas */); - assertEquals(rs.getObject(column), someBogusValue); // TODO this should represent TIME '00:39:07 +01:00' + assertThat(rs.getObject(column)).isEqualTo(someBogusValue); // TODO this should represent TIME '00:39:07 +01:00' assertThatThrownBy(() -> rs.getDate(column)) .isInstanceOf(SQLException.class) .hasMessage("Expected value to be a date but is: 00:39:07+01:00"); - assertEquals(rs.getTime(column), someBogusValue); // TODO this should fail, as there no java.sql.Time representation for TIME '00:39:07' in America/Bahia_Banderas + assertThat(rs.getTime(column)).isEqualTo(someBogusValue); // TODO this should fail, as there no java.sql.Time representation for TIME '00:39:07' in America/Bahia_Banderas // TODO (https://github.com/trinodb/trino/issues/5317) placement of precision parameter assertThatThrownBy(() -> rs.getTimestamp(column)) .isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/trinodb/trino/issues/5315) SQLException @@ -494,142 +489,142 @@ public void testTimestamp() { try (ConnectedStatement connectedStatement = newStatement()) { checkRepresentation(connectedStatement.getStatement(), "TIMESTAMP '2018-02-13 13:14:15.123'", Types.TIMESTAMP, (rs, column) -> { - assertEquals(rs.getObject(column), Timestamp.valueOf(LocalDateTime.of(2018, 2, 13, 13, 14, 15, 123_000_000))); - assertEquals(rs.getObject(column, Timestamp.class), Timestamp.valueOf(LocalDateTime.of(2018, 2, 13, 13, 14, 15, 123_000_000))); + assertThat(rs.getObject(column)).isEqualTo(Timestamp.valueOf(LocalDateTime.of(2018, 2, 13, 13, 14, 15, 123_000_000))); + assertThat(rs.getObject(column, Timestamp.class)).isEqualTo(Timestamp.valueOf(LocalDateTime.of(2018, 2, 13, 13, 14, 15, 123_000_000))); assertThatThrownBy(() -> rs.getDate(column)) .isInstanceOf(SQLException.class) .hasMessage("Expected value to be a date but is: 2018-02-13 13:14:15.123"); assertThatThrownBy(() -> rs.getTime(column)) .isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/trinodb/trino/issues/5315) SQLException .hasMessage("Expected column to be a time type but is timestamp(3)"); - assertEquals(rs.getTimestamp(column), Timestamp.valueOf(LocalDateTime.of(2018, 2, 13, 13, 14, 15, 123_000_000))); + assertThat(rs.getTimestamp(column)).isEqualTo(Timestamp.valueOf(LocalDateTime.of(2018, 2, 13, 13, 14, 15, 123_000_000))); }); checkRepresentation(connectedStatement.getStatement(), "TIMESTAMP '2018-02-13 13:14:15.111111111111'", Types.TIMESTAMP, (rs, column) -> { - assertEquals(rs.getObject(column), Timestamp.valueOf(LocalDateTime.of(2018, 2, 13, 13, 14, 15, 111_111_111))); + assertThat(rs.getObject(column)).isEqualTo(Timestamp.valueOf(LocalDateTime.of(2018, 2, 13, 13, 14, 15, 111_111_111))); assertThatThrownBy(() -> rs.getDate(column)) .isInstanceOf(SQLException.class) .hasMessage("Expected value to be a date but is: 2018-02-13 13:14:15.111111111111"); assertThatThrownBy(() -> rs.getTime(column)) .isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/trinodb/trino/issues/5315) SQLException .hasMessage("Expected column to be a time type but is timestamp(12)"); - assertEquals(rs.getTimestamp(column), Timestamp.valueOf(LocalDateTime.of(2018, 2, 13, 13, 14, 15, 111_111_111))); + assertThat(rs.getTimestamp(column)).isEqualTo(Timestamp.valueOf(LocalDateTime.of(2018, 2, 13, 13, 14, 15, 111_111_111))); }); checkRepresentation(connectedStatement.getStatement(), "TIMESTAMP '2018-02-13 13:14:15.555555555555'", Types.TIMESTAMP, (rs, column) -> { - assertEquals(rs.getObject(column), Timestamp.valueOf(LocalDateTime.of(2018, 2, 13, 13, 14, 15, 555_555_556))); + assertThat(rs.getObject(column)).isEqualTo(Timestamp.valueOf(LocalDateTime.of(2018, 2, 13, 13, 14, 15, 555_555_556))); assertThatThrownBy(() -> rs.getDate(column)) .isInstanceOf(SQLException.class) .hasMessage("Expected value to be a date but is: 2018-02-13 13:14:15.555555555555"); assertThatThrownBy(() -> rs.getTime(column)) .isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/trinodb/trino/issues/5315) SQLException .hasMessage("Expected column to be a time type but is timestamp(12)"); - assertEquals(rs.getTimestamp(column), Timestamp.valueOf(LocalDateTime.of(2018, 2, 13, 13, 14, 15, 555_555_556))); + assertThat(rs.getTimestamp(column)).isEqualTo(Timestamp.valueOf(LocalDateTime.of(2018, 2, 13, 13, 14, 15, 555_555_556))); }); // second fraction in nanoseconds overflowing to next second, minute, hour, day, month, year checkRepresentation(connectedStatement.getStatement(), "TIMESTAMP '2019-12-31 23:59:59.999999999999'", Types.TIMESTAMP, (rs, column) -> { - assertEquals(rs.getObject(column), Timestamp.valueOf(LocalDateTime.of(2020, 1, 1, 0, 0, 0, 0))); + assertThat(rs.getObject(column)).isEqualTo(Timestamp.valueOf(LocalDateTime.of(2020, 1, 1, 0, 0, 0, 0))); assertThatThrownBy(() -> rs.getDate(column)) .isInstanceOf(SQLException.class) .hasMessage("Expected value to be a date but is: 2019-12-31 23:59:59.999999999999"); assertThatThrownBy(() -> rs.getTime(column)) .isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/trinodb/trino/issues/5315) SQLException .hasMessage("Expected column to be a time type but is timestamp(12)"); - assertEquals(rs.getTimestamp(column), Timestamp.valueOf(LocalDateTime.of(2020, 1, 1, 0, 0, 0, 0))); + assertThat(rs.getTimestamp(column)).isEqualTo(Timestamp.valueOf(LocalDateTime.of(2020, 1, 1, 0, 0, 0, 0))); }); // second fraction in nanoseconds overflowing to next second, minute, hour, day, month, year; before epoch checkRepresentation(connectedStatement.getStatement(), "TIMESTAMP '1957-12-31 23:59:59.999999999999'", Types.TIMESTAMP, (rs, column) -> { - assertEquals(rs.getObject(column), Timestamp.valueOf(LocalDateTime.of(1958, 1, 1, 0, 0, 0, 0))); + assertThat(rs.getObject(column)).isEqualTo(Timestamp.valueOf(LocalDateTime.of(1958, 1, 1, 0, 0, 0, 0))); assertThatThrownBy(() -> rs.getDate(column)) .isInstanceOf(SQLException.class) .hasMessage("Expected value to be a date but is: 1957-12-31 23:59:59.999999999999"); assertThatThrownBy(() -> rs.getTime(column)) .isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/trinodb/trino/issues/5315) SQLException .hasMessage("Expected column to be a time type but is timestamp(12)"); - assertEquals(rs.getTimestamp(column), Timestamp.valueOf(LocalDateTime.of(1958, 1, 1, 0, 0, 0, 0))); + assertThat(rs.getTimestamp(column)).isEqualTo(Timestamp.valueOf(LocalDateTime.of(1958, 1, 1, 0, 0, 0, 0))); }); // distant past, but apparently not an uncommon value in practice checkRepresentation(connectedStatement.getStatement(), "TIMESTAMP '0001-01-01 00:00:00'", Types.TIMESTAMP, (rs, column) -> { - assertEquals(rs.getObject(column), Timestamp.valueOf(LocalDateTime.of(1, 1, 1, 0, 0, 0))); + assertThat(rs.getObject(column)).isEqualTo(Timestamp.valueOf(LocalDateTime.of(1, 1, 1, 0, 0, 0))); assertThatThrownBy(() -> rs.getDate(column)) .isInstanceOf(SQLException.class) .hasMessage("Expected value to be a date but is: 0001-01-01 00:00:00"); assertThatThrownBy(() -> rs.getTime(column)) .isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/trinodb/trino/issues/5315) SQLException .hasMessage("Expected column to be a time type but is timestamp(0)"); - assertEquals(rs.getTimestamp(column), Timestamp.valueOf(LocalDateTime.of(1, 1, 1, 0, 0, 0))); + assertThat(rs.getTimestamp(column)).isEqualTo(Timestamp.valueOf(LocalDateTime.of(1, 1, 1, 0, 0, 0))); }); // the Julian-Gregorian calendar "default cut-over" checkRepresentation(connectedStatement.getStatement(), "TIMESTAMP '1582-10-04 00:00:00'", Types.TIMESTAMP, (rs, column) -> { - assertEquals(rs.getObject(column), Timestamp.valueOf(LocalDateTime.of(1582, 10, 4, 0, 0, 0))); + assertThat(rs.getObject(column)).isEqualTo(Timestamp.valueOf(LocalDateTime.of(1582, 10, 4, 0, 0, 0))); assertThatThrownBy(() -> rs.getDate(column)) .isInstanceOf(SQLException.class) .hasMessage("Expected value to be a date but is: 1582-10-04 00:00:00"); assertThatThrownBy(() -> rs.getTime(column)) .isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/trinodb/trino/issues/5315) SQLException .hasMessage("Expected column to be a time type but is timestamp(0)"); - assertEquals(rs.getTimestamp(column), Timestamp.valueOf(LocalDateTime.of(1582, 10, 4, 0, 0, 0))); + assertThat(rs.getTimestamp(column)).isEqualTo(Timestamp.valueOf(LocalDateTime.of(1582, 10, 4, 0, 0, 0))); }); // after the Julian-Gregorian calendar "default cut-over", but before the Gregorian calendar start checkRepresentation(connectedStatement.getStatement(), "TIMESTAMP '1582-10-10 00:00:00'", Types.TIMESTAMP, (rs, column) -> { - assertEquals(rs.getObject(column), Timestamp.valueOf(LocalDateTime.of(1582, 10, 10, 0, 0, 0))); + assertThat(rs.getObject(column)).isEqualTo(Timestamp.valueOf(LocalDateTime.of(1582, 10, 10, 0, 0, 0))); assertThatThrownBy(() -> rs.getDate(column)) .isInstanceOf(SQLException.class) .hasMessage("Expected value to be a date but is: 1582-10-10 00:00:00"); assertThatThrownBy(() -> rs.getTime(column)) .isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/trinodb/trino/issues/5315) SQLException .hasMessage("Expected column to be a time type but is timestamp(0)"); - assertEquals(rs.getTimestamp(column), Timestamp.valueOf(LocalDateTime.of(1582, 10, 10, 0, 0, 0))); + assertThat(rs.getTimestamp(column)).isEqualTo(Timestamp.valueOf(LocalDateTime.of(1582, 10, 10, 0, 0, 0))); }); // the Gregorian calendar start checkRepresentation(connectedStatement.getStatement(), "TIMESTAMP '1582-10-15 00:00:00'", Types.TIMESTAMP, (rs, column) -> { - assertEquals(rs.getObject(column), Timestamp.valueOf(LocalDateTime.of(1582, 10, 15, 0, 0, 0))); + assertThat(rs.getObject(column)).isEqualTo(Timestamp.valueOf(LocalDateTime.of(1582, 10, 15, 0, 0, 0))); assertThatThrownBy(() -> rs.getDate(column)) .isInstanceOf(SQLException.class) .hasMessage("Expected value to be a date but is: 1582-10-15 00:00:00"); assertThatThrownBy(() -> rs.getTime(column)) .isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/trinodb/trino/issues/5315) SQLException .hasMessage("Expected column to be a time type but is timestamp(0)"); - assertEquals(rs.getTimestamp(column), Timestamp.valueOf(LocalDateTime.of(1582, 10, 15, 0, 0, 0))); + assertThat(rs.getTimestamp(column)).isEqualTo(Timestamp.valueOf(LocalDateTime.of(1582, 10, 15, 0, 0, 0))); }); checkRepresentation(connectedStatement.getStatement(), "TIMESTAMP '1583-01-01 00:00:00'", Types.TIMESTAMP, (rs, column) -> { - assertEquals(rs.getObject(column), Timestamp.valueOf(LocalDateTime.of(1583, 1, 1, 0, 0, 0))); + assertThat(rs.getObject(column)).isEqualTo(Timestamp.valueOf(LocalDateTime.of(1583, 1, 1, 0, 0, 0))); assertThatThrownBy(() -> rs.getDate(column)) .isInstanceOf(SQLException.class) .hasMessage("Expected value to be a date but is: 1583-01-01 00:00:00"); assertThatThrownBy(() -> rs.getTime(column)) .isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/trinodb/trino/issues/5315) SQLException .hasMessage("Expected column to be a time type but is timestamp(0)"); - assertEquals(rs.getTimestamp(column), Timestamp.valueOf(LocalDateTime.of(1583, 1, 1, 0, 0, 0))); + assertThat(rs.getTimestamp(column)).isEqualTo(Timestamp.valueOf(LocalDateTime.of(1583, 1, 1, 0, 0, 0))); }); checkRepresentation(connectedStatement.getStatement(), "TIMESTAMP '1970-01-01 00:14:15.123'", Types.TIMESTAMP, (rs, column) -> { - assertEquals(rs.getObject(column), Timestamp.valueOf(LocalDateTime.of(1970, 1, 1, 0, 14, 15, 123_000_000))); + assertThat(rs.getObject(column)).isEqualTo(Timestamp.valueOf(LocalDateTime.of(1970, 1, 1, 0, 14, 15, 123_000_000))); assertThatThrownBy(() -> rs.getDate(column)) .isInstanceOf(SQLException.class) .hasMessage("Expected value to be a date but is: 1970-01-01 00:14:15.123"); assertThatThrownBy(() -> rs.getTime(column)) .isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/trinodb/trino/issues/5315) SQLException .hasMessage("Expected column to be a time type but is timestamp(3)"); - assertEquals(rs.getTimestamp(column), Timestamp.valueOf(LocalDateTime.of(1970, 1, 1, 0, 14, 15, 123_000_000))); + assertThat(rs.getTimestamp(column)).isEqualTo(Timestamp.valueOf(LocalDateTime.of(1970, 1, 1, 0, 14, 15, 123_000_000))); }); checkRepresentation(connectedStatement.getStatement(), "TIMESTAMP '123456-01-23 01:23:45.123456789'", Types.TIMESTAMP, (rs, column) -> { - assertEquals(rs.getObject(column), Timestamp.valueOf(LocalDateTime.of(123456, 1, 23, 1, 23, 45, 123_456_789))); + assertThat(rs.getObject(column)).isEqualTo(Timestamp.valueOf(LocalDateTime.of(123456, 1, 23, 1, 23, 45, 123_456_789))); assertThatThrownBy(() -> rs.getDate(column)) .isInstanceOf(SQLException.class) .hasMessage("Expected value to be a date but is: +123456-01-23 01:23:45.123456789"); assertThatThrownBy(() -> rs.getTime(column)) .isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/trinodb/trino/issues/5315) SQLException .hasMessage("Expected column to be a time type but is timestamp(9)"); - assertEquals(rs.getTimestamp(column), Timestamp.valueOf(LocalDateTime.of(123456, 1, 23, 1, 23, 45, 123_456_789))); + assertThat(rs.getTimestamp(column)).isEqualTo(Timestamp.valueOf(LocalDateTime.of(123456, 1, 23, 1, 23, 45, 123_456_789))); }); } } @@ -642,8 +637,8 @@ public void testTimestampWithTimeZone() // zero checkRepresentation(connectedStatement.getStatement(), "TIMESTAMP '1970-01-01 00:00:00.000 +00:00'", Types.TIMESTAMP_WITH_TIMEZONE, (rs, column) -> { Timestamp timestampForPointInTime = Timestamp.from(Instant.EPOCH); - assertEquals(rs.getObject(column), timestampForPointInTime); - assertEquals(rs.getObject(column, ZonedDateTime.class), ZonedDateTime.ofInstant(Instant.EPOCH, ZoneId.of("UTC"))); + assertThat(rs.getObject(column)).isEqualTo(timestampForPointInTime); + assertThat(rs.getObject(column, ZonedDateTime.class)).isEqualTo(ZonedDateTime.ofInstant(Instant.EPOCH, ZoneId.of("UTC"))); assertThatThrownBy(() -> rs.getDate(column)) .isInstanceOf(SQLException.class) .hasMessage("Expected value to be a date but is: 1970-01-01 00:00:00.000 UTC"); @@ -651,14 +646,14 @@ public void testTimestampWithTimeZone() assertThatThrownBy(() -> rs.getTime(column)) .isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/trinodb/trino/issues/5315) SQLException .hasMessage("Expected column to be a time type but is timestamp with time zone(3)"); - assertEquals(rs.getTimestamp(column), timestampForPointInTime); + assertThat(rs.getTimestamp(column)).isEqualTo(timestampForPointInTime); }); checkRepresentation(connectedStatement.getStatement(), "TIMESTAMP '2018-02-13 13:14:15.227 Europe/Warsaw'", Types.TIMESTAMP_WITH_TIMEZONE, (rs, column) -> { ZonedDateTime zonedDateTime = ZonedDateTime.of(2018, 2, 13, 13, 14, 15, 227_000_000, ZoneId.of("Europe/Warsaw")); Timestamp timestampForPointInTime = Timestamp.from(zonedDateTime.toInstant()); - assertEquals(rs.getObject(column), timestampForPointInTime); // TODO this should represent TIMESTAMP '2018-02-13 13:14:15.227 Europe/Warsaw' - assertEquals(rs.getObject(column, ZonedDateTime.class), zonedDateTime); + assertThat(rs.getObject(column)).isEqualTo(timestampForPointInTime); // TODO this should represent TIMESTAMP '2018-02-13 13:14:15.227 Europe/Warsaw' + assertThat(rs.getObject(column, ZonedDateTime.class)).isEqualTo(zonedDateTime); assertThatThrownBy(() -> rs.getDate(column)) .isInstanceOf(SQLException.class) .hasMessage("Expected value to be a date but is: 2018-02-13 13:14:15.227 Europe/Warsaw"); @@ -666,22 +661,22 @@ public void testTimestampWithTimeZone() assertThatThrownBy(() -> rs.getTime(column)) .isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/trinodb/trino/issues/5315) SQLException .hasMessage("Expected column to be a time type but is timestamp with time zone(3)"); - assertEquals(rs.getTimestamp(column), timestampForPointInTime); + assertThat(rs.getTimestamp(column)).isEqualTo(timestampForPointInTime); }); // second fraction in nanoseconds overflowing to next second, minute, hour, day, month, year checkRepresentation(connectedStatement.getStatement(), "TIMESTAMP '2019-12-31 23:59:59.999999999999 Europe/Warsaw'", Types.TIMESTAMP_WITH_TIMEZONE, (rs, column) -> { ZonedDateTime zonedDateTime = ZonedDateTime.of(2020, 1, 1, 0, 0, 0, 0, ZoneId.of("Europe/Warsaw")); Timestamp timestampForPointInTime = Timestamp.from(zonedDateTime.toInstant()); - assertEquals(rs.getObject(column), timestampForPointInTime); // TODO this should represent TIMESTAMP '2019-12-31 23:59:59.999999999999 Europe/Warsaw' - assertEquals(rs.getObject(column, ZonedDateTime.class), zonedDateTime); + assertThat(rs.getObject(column)).isEqualTo(timestampForPointInTime); // TODO this should represent TIMESTAMP '2019-12-31 23:59:59.999999999999 Europe/Warsaw' + assertThat(rs.getObject(column, ZonedDateTime.class)).isEqualTo(zonedDateTime); assertThatThrownBy(() -> rs.getDate(column)) .isInstanceOf(SQLException.class) .hasMessage("Expected value to be a date but is: 2019-12-31 23:59:59.999999999999 Europe/Warsaw"); assertThatThrownBy(() -> rs.getTime(column)) .isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/trinodb/trino/issues/5315) SQLException .hasMessage("Expected column to be a time type but is timestamp with time zone(12)"); // TODO (https://github.com/trinodb/trino/issues/5317) placement of precision parameter - assertEquals(rs.getTimestamp(column), timestampForPointInTime); + assertThat(rs.getTimestamp(column)).isEqualTo(timestampForPointInTime); }); ZoneId jvmZone = ZoneId.systemDefault(); @@ -692,36 +687,36 @@ public void testTimestampWithTimeZone() (rs, column) -> { ZonedDateTime zonedDateTime = ZonedDateTime.of(2020, 1, 1, 0, 0, 0, 0, jvmZone); Timestamp timestampForPointInTime = Timestamp.from(zonedDateTime.toInstant()); - assertEquals(rs.getObject(column), timestampForPointInTime); // TODO this should represent TIMESTAMP '2019-12-31 23:59:59.999999999999 JVM ZONE' - assertEquals(rs.getObject(column, ZonedDateTime.class), zonedDateTime); + assertThat(rs.getObject(column)).isEqualTo(timestampForPointInTime); // TODO this should represent TIMESTAMP '2019-12-31 23:59:59.999999999999 JVM ZONE' + assertThat(rs.getObject(column, ZonedDateTime.class)).isEqualTo(zonedDateTime); assertThatThrownBy(() -> rs.getDate(column)) .isInstanceOf(SQLException.class) .hasMessage("Expected value to be a date but is: 2019-12-31 23:59:59.999999999999 America/Bahia_Banderas"); assertThatThrownBy(() -> rs.getTime(column)) .isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/trinodb/trino/issues/5315) SQLException .hasMessage("Expected column to be a time type but is timestamp with time zone(12)"); // TODO (https://github.com/trinodb/trino/issues/5317) placement of precision parameter - assertEquals(rs.getTimestamp(column), timestampForPointInTime); + assertThat(rs.getTimestamp(column)).isEqualTo(timestampForPointInTime); }); // before epoch checkRepresentation(connectedStatement.getStatement(), "TIMESTAMP '1957-12-31 23:59:59.999999999999 Europe/Warsaw'", Types.TIMESTAMP_WITH_TIMEZONE, (rs, column) -> { ZonedDateTime zonedDateTime = ZonedDateTime.of(1958, 1, 1, 0, 0, 0, 0, ZoneId.of("Europe/Warsaw")); Timestamp timestampForPointInTime = Timestamp.from(zonedDateTime.toInstant()); - assertEquals(rs.getObject(column), timestampForPointInTime); // TODO this should represent TIMESTAMP '2019-12-31 23:59:59.999999999999 Europe/Warsaw' + assertThat(rs.getObject(column)).isEqualTo(timestampForPointInTime); // TODO this should represent TIMESTAMP '2019-12-31 23:59:59.999999999999 Europe/Warsaw' assertThatThrownBy(() -> rs.getDate(column)) .isInstanceOf(SQLException.class) .hasMessage("Expected value to be a date but is: 1957-12-31 23:59:59.999999999999 Europe/Warsaw"); assertThatThrownBy(() -> rs.getTime(column)) .isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/trinodb/trino/issues/5315) SQLException .hasMessage("Expected column to be a time type but is timestamp with time zone(12)"); // TODO (https://github.com/trinodb/trino/issues/5317) placement of precision parameter - assertEquals(rs.getTimestamp(column), timestampForPointInTime); + assertThat(rs.getTimestamp(column)).isEqualTo(timestampForPointInTime); }); checkRepresentation(connectedStatement.getStatement(), "TIMESTAMP '1970-01-01 09:14:15.227 Europe/Warsaw'", Types.TIMESTAMP_WITH_TIMEZONE, (rs, column) -> { ZonedDateTime zonedDateTime = ZonedDateTime.of(1970, 1, 1, 9, 14, 15, 227_000_000, ZoneId.of("Europe/Warsaw")); Timestamp timestampForPointInTime = Timestamp.from(zonedDateTime.toInstant()); - assertEquals(rs.getObject(column), timestampForPointInTime); // TODO this should represent TIMESTAMP '1970-01-01 09:14:15.227 Europe/Warsaw' - assertEquals(rs.getObject(column, ZonedDateTime.class), zonedDateTime); + assertThat(rs.getObject(column)).isEqualTo(timestampForPointInTime); // TODO this should represent TIMESTAMP '1970-01-01 09:14:15.227 Europe/Warsaw' + assertThat(rs.getObject(column, ZonedDateTime.class)).isEqualTo(zonedDateTime); assertThatThrownBy(() -> rs.getDate(column)) .isInstanceOf(SQLException.class) .hasMessage("Expected value to be a date but is: 1970-01-01 09:14:15.227 Europe/Warsaw"); @@ -729,14 +724,14 @@ public void testTimestampWithTimeZone() assertThatThrownBy(() -> rs.getTime(column)) .isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/trinodb/trino/issues/5315) SQLException .hasMessage("Expected column to be a time type but is timestamp with time zone(3)"); - assertEquals(rs.getTimestamp(column), timestampForPointInTime); + assertThat(rs.getTimestamp(column)).isEqualTo(timestampForPointInTime); }); checkRepresentation(connectedStatement.getStatement(), "TIMESTAMP '1970-01-01 00:14:15.227 Europe/Warsaw'", Types.TIMESTAMP_WITH_TIMEZONE, (rs, column) -> { ZonedDateTime zonedDateTime = ZonedDateTime.of(1970, 1, 1, 0, 14, 15, 227_000_000, ZoneId.of("Europe/Warsaw")); Timestamp timestampForPointInTime = Timestamp.from(zonedDateTime.toInstant()); - assertEquals(rs.getObject(column), timestampForPointInTime); // TODO this should represent TIMESTAMP '1970-01-01 00:14:15.227 Europe/Warsaw' - assertEquals(rs.getObject(column, ZonedDateTime.class), zonedDateTime); + assertThat(rs.getObject(column)).isEqualTo(timestampForPointInTime); // TODO this should represent TIMESTAMP '1970-01-01 00:14:15.227 Europe/Warsaw' + assertThat(rs.getObject(column, ZonedDateTime.class)).isEqualTo(zonedDateTime); assertThatThrownBy(() -> rs.getDate(column)) .isInstanceOf(SQLException.class) .hasMessage("Expected value to be a date but is: 1970-01-01 00:14:15.227 Europe/Warsaw"); @@ -744,7 +739,7 @@ public void testTimestampWithTimeZone() assertThatThrownBy(() -> rs.getTime(column)) .isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/trinodb/trino/issues/5315) SQLException .hasMessage("Expected column to be a time type but is timestamp with time zone(3)"); - assertEquals(rs.getTimestamp(column), timestampForPointInTime); + assertThat(rs.getTimestamp(column)).isEqualTo(timestampForPointInTime); }); // TODO https://github.com/trinodb/trino/issues/4363 @@ -755,15 +750,15 @@ public void testTimestampWithTimeZone() checkRepresentation(connectedStatement.getStatement(), "TIMESTAMP '12345-01-23 01:23:45.123456789 Europe/Warsaw'", Types.TIMESTAMP_WITH_TIMEZONE, (rs, column) -> { ZonedDateTime zonedDateTime = ZonedDateTime.of(12345, 1, 23, 1, 23, 45, 123_456_789, ZoneId.of("Europe/Warsaw")); Timestamp timestampForPointInTime = Timestamp.from(zonedDateTime.toInstant()); - assertEquals(rs.getObject(column), timestampForPointInTime); // TODO this should contain the zone - assertEquals(rs.getObject(column, ZonedDateTime.class), zonedDateTime); + assertThat(rs.getObject(column)).isEqualTo(timestampForPointInTime); // TODO this should contain the zone + assertThat(rs.getObject(column, ZonedDateTime.class)).isEqualTo(zonedDateTime); assertThatThrownBy(() -> rs.getDate(column)) .isInstanceOf(SQLException.class) .hasMessage("Expected value to be a date but is: +12345-01-23 01:23:45.123456789 Europe/Warsaw"); assertThatThrownBy(() -> rs.getTime(column)) .isInstanceOf(IllegalArgumentException.class) // TODO (https://github.com/trinodb/trino/issues/5315) SQLException .hasMessage("Expected column to be a time type but is timestamp with time zone(9)"); // TODO (https://github.com/trinodb/trino/issues/5317) placement of precision parameter - assertEquals(rs.getTimestamp(column), timestampForPointInTime); + assertThat(rs.getTimestamp(column)).isEqualTo(timestampForPointInTime); }); } } @@ -795,15 +790,15 @@ public void testArray() checkRepresentation(connectedStatement.getStatement(), "ARRAY[1, 2]", Types.ARRAY, (rs, column) -> { Array array = rs.getArray(column); assertThat(array.getArray()).isEqualTo(new Object[] {1, 2}); - assertEquals(array.getBaseType(), Types.INTEGER); - assertEquals(array.getBaseTypeName(), "integer"); + assertThat(array.getBaseType()).isEqualTo(Types.INTEGER); + assertThat(array.getBaseTypeName()).isEqualTo("integer"); array = (Array) rs.getObject(column); // TODO (https://github.com/trinodb/trino/issues/6049) subject to change assertThat(array.getArray()).isEqualTo(new Object[] {1, 2}); - assertEquals(array.getBaseType(), Types.INTEGER); - assertEquals(array.getBaseTypeName(), "integer"); + assertThat(array.getBaseType()).isEqualTo(Types.INTEGER); + assertThat(array.getBaseTypeName()).isEqualTo("integer"); - assertEquals(rs.getObject(column, List.class), ImmutableList.of(1, 2)); + assertThat(rs.getObject(column, List.class)).isEqualTo(ImmutableList.of(1, 2)); }); checkArrayRepresentation(connectedStatement.getStatement(), "1", Types.INTEGER, "integer"); @@ -822,15 +817,15 @@ public void testArray() checkRepresentation(connectedStatement.getStatement(), "ARRAY[NULL, ARRAY[NULL, BIGINT '1', 2]]", Types.ARRAY, (rs, column) -> { Array array = rs.getArray(column); assertThat(array.getArray()).isEqualTo(new Object[] {null, asList(null, 1L, 2L)}); - assertEquals(array.getBaseType(), Types.ARRAY); - assertEquals(array.getBaseTypeName(), "array(bigint)"); + assertThat(array.getBaseType()).isEqualTo(Types.ARRAY); + assertThat(array.getBaseTypeName()).isEqualTo("array(bigint)"); array = (Array) rs.getObject(column); // TODO (https://github.com/trinodb/trino/issues/6049) subject to change assertThat(array.getArray()).isEqualTo(new Object[] {null, asList(null, 1L, 2L)}); - assertEquals(array.getBaseType(), Types.ARRAY); - assertEquals(array.getBaseTypeName(), "array(bigint)"); + assertThat(array.getBaseType()).isEqualTo(Types.ARRAY); + assertThat(array.getBaseTypeName()).isEqualTo("array(bigint)"); - assertEquals(rs.getObject(column, List.class), asList(null, asList(null, 1L, 2L))); + assertThat(rs.getObject(column, List.class)).isEqualTo(asList(null, asList(null, 1L, 2L))); }); // array of map @@ -841,15 +836,15 @@ public void testArray() Array array = rs.getArray(column); assertThat(array.getArray()).isEqualTo(new Object[] {element}); - assertEquals(array.getBaseType(), Types.JAVA_OBJECT); - assertEquals(array.getBaseTypeName(), "map(varchar(2),integer)"); + assertThat(array.getBaseType()).isEqualTo(Types.JAVA_OBJECT); + assertThat(array.getBaseTypeName()).isEqualTo("map(varchar(2),integer)"); array = (Array) rs.getObject(column); assertThat(array.getArray()).isEqualTo(new Object[] {element}); - assertEquals(array.getBaseType(), Types.JAVA_OBJECT); - assertEquals(array.getBaseTypeName(), "map(varchar(2),integer)"); + assertThat(array.getBaseType()).isEqualTo(Types.JAVA_OBJECT); + assertThat(array.getBaseTypeName()).isEqualTo("map(varchar(2),integer)"); - assertEquals(rs.getObject(column, List.class), ImmutableList.of(element)); + assertThat(rs.getObject(column, List.class)).isEqualTo(ImmutableList.of(element)); }); // array of row @@ -861,15 +856,15 @@ public void testArray() Array array = rs.getArray(column); assertThat(array.getArray()).isEqualTo(new Object[] {element}); - assertEquals(array.getBaseType(), Types.JAVA_OBJECT); - assertEquals(array.getBaseTypeName(), "row(a_bigint bigint,a_varchar varchar(17))"); + assertThat(array.getBaseType()).isEqualTo(Types.JAVA_OBJECT); + assertThat(array.getBaseTypeName()).isEqualTo("row(a_bigint bigint,a_varchar varchar(17))"); array = (Array) rs.getObject(column); assertThat(array.getArray()).isEqualTo(new Object[] {element}); - assertEquals(array.getBaseType(), Types.JAVA_OBJECT); - assertEquals(array.getBaseTypeName(), "row(a_bigint bigint,a_varchar varchar(17))"); + assertThat(array.getBaseType()).isEqualTo(Types.JAVA_OBJECT); + assertThat(array.getBaseTypeName()).isEqualTo("row(a_bigint bigint,a_varchar varchar(17))"); - assertEquals(rs.getObject(column, List.class), ImmutableList.of(element)); + assertThat(rs.getObject(column, List.class)).isEqualTo(ImmutableList.of(element)); }); } } @@ -881,15 +876,15 @@ private void checkArrayRepresentation(Statement statement, String elementExpress checkRepresentation(statement, format("ARRAY[NULL, %s]", elementExpression), Types.ARRAY, (rs, column) -> { Array array = rs.getArray(column); assertThat(array.getArray()).isEqualTo(new Object[] {null, element}); - assertEquals(array.getBaseType(), elementSqlType); - assertEquals(array.getBaseTypeName(), elementTypeName); + assertThat(array.getBaseType()).isEqualTo(elementSqlType); + assertThat(array.getBaseTypeName()).isEqualTo(elementTypeName); array = (Array) rs.getObject(column); // TODO (https://github.com/trinodb/trino/issues/6049) subject to change assertThat(array.getArray()).isEqualTo(new Object[] {null, element}); - assertEquals(array.getBaseType(), elementSqlType); - assertEquals(array.getBaseTypeName(), elementTypeName); + assertThat(array.getBaseType()).isEqualTo(elementSqlType); + assertThat(array.getBaseTypeName()).isEqualTo(elementTypeName); - assertEquals(rs.getObject(column, List.class), asList(null, element)); + assertThat(rs.getObject(column, List.class)).isEqualTo(asList(null, element)); }); } @@ -899,8 +894,8 @@ public void testMap() { try (ConnectedStatement connectedStatement = newStatement()) { checkRepresentation(connectedStatement.getStatement(), "map(ARRAY['k1', 'k2'], ARRAY[BIGINT '42', -117])", Types.JAVA_OBJECT, (rs, column) -> { - assertEquals(rs.getObject(column), ImmutableMap.of("k1", 42L, "k2", -117L)); - assertEquals(rs.getObject(column, Map.class), ImmutableMap.of("k1", 42L, "k2", -117L)); + assertThat(rs.getObject(column)).isEqualTo(ImmutableMap.of("k1", 42L, "k2", -117L)); + assertThat(rs.getObject(column, Map.class)).isEqualTo(ImmutableMap.of("k1", 42L, "k2", -117L)); }); // NULL value @@ -908,8 +903,8 @@ public void testMap() Map expected = new HashMap<>(); expected.put("k1", 42); expected.put("k2", null); - assertEquals(rs.getObject(column), expected); - assertEquals(rs.getObject(column, Map.class), expected); + assertThat(rs.getObject(column)).isEqualTo(expected); + assertThat(rs.getObject(column, Map.class)).isEqualTo(expected); }); // map or row @@ -917,8 +912,8 @@ public void testMap() Map expected = new HashMap<>(); expected.put("k1", Row.builder().addField("a", 42).build()); expected.put("k2", null); - assertEquals(rs.getObject(column), expected); - assertEquals(rs.getObject(column, Map.class), expected); + assertThat(rs.getObject(column)).isEqualTo(expected); + assertThat(rs.getObject(column, Map.class)).isEqualTo(expected); }); } } @@ -930,34 +925,34 @@ public void testRow() try (ConnectedStatement connectedStatement = newStatement()) { // named row checkRepresentation(connectedStatement.getStatement(), "CAST(ROW(42, 'Trino') AS row(a_bigint bigint, a_varchar varchar(17)))", Types.JAVA_OBJECT, (rs, column) -> { - assertEquals(rs.getObject(column), Row.builder() + assertThat(rs.getObject(column)).isEqualTo(Row.builder() .addField("a_bigint", 42L) .addField("a_varchar", "Trino") .build()); - assertEquals(rs.getObject(column, Map.class), ImmutableMap.of("a_bigint", 42L, "a_varchar", "Trino")); + assertThat(rs.getObject(column, Map.class)).isEqualTo(ImmutableMap.of("a_bigint", 42L, "a_varchar", "Trino")); }); // partially named row checkRepresentation(connectedStatement.getStatement(), "CAST(ROW(42, 'Trino') AS row(a_bigint bigint, varchar(17)))", Types.JAVA_OBJECT, (rs, column) -> { - assertEquals(rs.getObject(column), Row.builder() + assertThat(rs.getObject(column)).isEqualTo(Row.builder() .addField("a_bigint", 42L) .addUnnamedField("Trino") .build()); - assertEquals(rs.getObject(column, Map.class), ImmutableMap.of("a_bigint", 42L, "field1", "Trino")); + assertThat(rs.getObject(column, Map.class)).isEqualTo(ImmutableMap.of("a_bigint", 42L, "field1", "Trino")); }); // anonymous row checkRepresentation(connectedStatement.getStatement(), "ROW(42, 'Trino')", Types.JAVA_OBJECT, (rs, column) -> { - assertEquals(rs.getObject(column), Row.builder() + assertThat(rs.getObject(column)).isEqualTo(Row.builder() .addUnnamedField(42) .addUnnamedField("Trino") .build()); - assertEquals(rs.getObject(column, Map.class), ImmutableMap.of("field0", 42, "field1", "Trino")); + assertThat(rs.getObject(column, Map.class)).isEqualTo(ImmutableMap.of("field0", 42, "field1", "Trino")); }); // name collision checkRepresentation(connectedStatement.getStatement(), "CAST(ROW(42, 'Trino') AS row(field1 integer, varchar(17)))", Types.JAVA_OBJECT, (rs, column) -> { - assertEquals(rs.getObject(column), Row.builder() + assertThat(rs.getObject(column)).isEqualTo(Row.builder() .addField("field1", 42) .addUnnamedField("Trino") .build()); @@ -968,7 +963,7 @@ public void testRow() // name collision with NULL value checkRepresentation(connectedStatement.getStatement(), "CAST(ROW(NULL, NULL) AS row(field1 integer, varchar(17)))", Types.JAVA_OBJECT, (rs, column) -> { - assertEquals(rs.getObject(column), Row.builder() + assertThat(rs.getObject(column)).isEqualTo(Row.builder() .addField("field1", null) .addUnnamedField(null) .build()); @@ -979,22 +974,18 @@ public void testRow() // row of row or row checkRepresentation(connectedStatement.getStatement(), "ROW(ROW(ROW(42)))", Types.JAVA_OBJECT, (rs, column) -> { - assertEquals( - rs.getObject(column), - Row.builder() - .addUnnamedField(Row.builder() - .addUnnamedField(Row.builder().addUnnamedField(42).build()) - .build()) - .build()); + assertThat(rs.getObject(column)).isEqualTo(Row.builder() + .addUnnamedField(Row.builder() + .addUnnamedField(Row.builder().addUnnamedField(42).build()) + .build()) + .build()); }); checkRepresentation(connectedStatement.getStatement(), "CAST(ROW(ROW(ROW(42))) AS row(a row(b row(c integer))))", Types.JAVA_OBJECT, (rs, column) -> { - assertEquals( - rs.getObject(column), - Row.builder() - .addField("a", Row.builder() - .addField("b", Row.builder().addField("c", 42).build()) - .build()) - .build()); + assertThat(rs.getObject(column)).isEqualTo(Row.builder() + .addField("a", Row.builder() + .addField("b", Row.builder().addField("c", 42).build()) + .build()) + .build()); }); // row of array of map of row @@ -1013,8 +1004,8 @@ public void testRow() array.add(null); array.add(map); array = unmodifiableList(array); - assertEquals(rs.getObject(column), Row.builder().addField("outer", array).build()); - assertEquals(rs.getObject(column, Map.class), ImmutableMap.of("outer", array)); + assertThat(rs.getObject(column)).isEqualTo(Row.builder().addField("outer", array).build()); + assertThat(rs.getObject(column, Map.class)).isEqualTo(ImmutableMap.of("outer", array)); }); } } @@ -1023,8 +1014,8 @@ private void checkRepresentation(Statement statement, String expression, int exp throws SQLException { checkRepresentation(statement, expression, expectedSqlType, (rs, column) -> { - assertEquals(rs.getObject(column), expectedRepresentation); - assertEquals(rs.getObject(column, expectedRepresentation.getClass()), expectedRepresentation); + assertThat(rs.getObject(column)).isEqualTo(expectedRepresentation); + assertThat(rs.getObject(column, expectedRepresentation.getClass())).isEqualTo(expectedRepresentation); }); } @@ -1033,11 +1024,11 @@ private void checkRepresentation(Statement statement, String expression, int exp { try (ResultSet rs = statement.executeQuery("SELECT " + expression)) { ResultSetMetaData metadata = rs.getMetaData(); - assertEquals(metadata.getColumnCount(), 1); - assertEquals(metadata.getColumnType(1), expectedSqlType); - assertTrue(rs.next()); + assertThat(metadata.getColumnCount()).isEqualTo(1); + assertThat(metadata.getColumnType(1)).isEqualTo(expectedSqlType); + assertThat(rs.next()).isTrue(); assertion.accept(rs, 1); - assertFalse(rs.next()); + assertThat(rs.next()).isFalse(); } } @@ -1047,10 +1038,10 @@ private Object getObjectRepresentation(Connection connection, String expression) try (Statement statement = connection.createStatement(); ResultSet rs = statement.executeQuery("SELECT " + expression)) { ResultSetMetaData metadata = rs.getMetaData(); - assertEquals(metadata.getColumnCount(), 1); - assertTrue(rs.next()); + assertThat(metadata.getColumnCount()).isEqualTo(1); + assertThat(rs.next()).isTrue(); Object object = rs.getObject(1); - assertFalse(rs.next()); + assertThat(rs.next()).isFalse(); return object; } } @@ -1061,11 +1052,11 @@ public void testStatsExtraction() { try (ConnectedStatement connectedStatement = newStatement()) { try (TrinoResultSet rs = (TrinoResultSet) connectedStatement.getStatement().executeQuery("SELECT 123 x, 456 x")) { - assertNotNull(rs.getStats()); - assertTrue(rs.next()); - assertNotNull(rs.getStats()); - assertFalse(rs.next()); - assertNotNull(rs.getStats()); + assertThat(rs.getStats()).isNotNull(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getStats()).isNotNull(); + assertThat(rs.next()).isFalse(); + assertThat(rs.getStats()).isNotNull(); } } } @@ -1147,7 +1138,7 @@ public void testLargeMaxRowsLimitLargerThanResult() try (ConnectedStatement connectedStatement = newStatement()) { long limit = Integer.MAX_VALUE * 10L; connectedStatement.getStatement().setLargeMaxRows(limit); - assertEquals(connectedStatement.getStatement().getLargeMaxRows(), limit); + assertThat(connectedStatement.getStatement().getLargeMaxRows()).isEqualTo(limit); assertMaxRowsResult(connectedStatement.getStatement(), 7); } } @@ -1155,15 +1146,15 @@ public void testLargeMaxRowsLimitLargerThanResult() private void assertMaxRowsLimit(Statement statement, int expectedLimit) throws SQLException { - assertEquals(statement.getMaxRows(), expectedLimit); - assertEquals(statement.getLargeMaxRows(), expectedLimit); + assertThat(statement.getMaxRows()).isEqualTo(expectedLimit); + assertThat(statement.getLargeMaxRows()).isEqualTo(expectedLimit); } private void assertMaxRowsResult(Statement statement, long expectedCount) throws SQLException { try (ResultSet rs = statement.executeQuery("SELECT * FROM (VALUES (1), (2), (3), (4), (5), (6), (7)) AS x (a)")) { - assertEquals(countRows(rs), expectedCount); + assertThat(countRows(rs)).isEqualTo(expectedCount); } } @@ -1183,7 +1174,7 @@ public void testGetStatement() { try (ConnectedStatement connectedStatement = newStatement()) { try (ResultSet rs = connectedStatement.getStatement().executeQuery("SELECT * FROM (VALUES (1), (2), (3))")) { - assertEquals(rs.getStatement(), connectedStatement.getStatement()); + assertThat(rs.getStatement()).isEqualTo(connectedStatement.getStatement()); } } } @@ -1194,13 +1185,13 @@ public void testGetRow() { try (ConnectedStatement connectedStatement = newStatement()) { try (ResultSet rs = connectedStatement.getStatement().executeQuery("SELECT * FROM (VALUES (1), (2), (3))")) { - assertEquals(rs.getRow(), 0); + assertThat(rs.getRow()).isEqualTo(0); int currentRow = 0; while (rs.next()) { currentRow++; - assertEquals(rs.getRow(), currentRow); + assertThat(rs.getRow()).isEqualTo(currentRow); } - assertEquals(rs.getRow(), 0); + assertThat(rs.getRow()).isEqualTo(0); } } } diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestAbstractTrinoResultSet.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestAbstractTrinoResultSet.java index 0dc0f4218a74..27f0c363e27f 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestAbstractTrinoResultSet.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestAbstractTrinoResultSet.java @@ -17,7 +17,7 @@ import static io.trino.jdbc.AbstractTrinoResultSet.DEFAULT_OBJECT_REPRESENTATION; import static io.trino.jdbc.AbstractTrinoResultSet.TYPE_CONVERSIONS; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Fail.fail; public class TestAbstractTrinoResultSet { diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcConnection.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcConnection.java index f157d699c6f8..453010f8ff23 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcConnection.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcConnection.java @@ -68,12 +68,9 @@ import static java.util.stream.IntStream.range; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Fail.fail; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -131,11 +128,11 @@ public void testAutocommit() throws SQLException { try (Connection connection = createConnection()) { - assertTrue(connection.getAutoCommit()); + assertThat(connection.getAutoCommit()).isTrue(); connection.setAutoCommit(false); - assertFalse(connection.getAutoCommit()); + assertThat(connection.getAutoCommit()).isFalse(); connection.setAutoCommit(true); - assertTrue(connection.getAutoCommit()); + assertThat(connection.getAutoCommit()).isTrue(); } } @@ -360,8 +357,8 @@ public void testExtraCredentials() .put("colon", "-::-") .buildOrThrow(); TrinoConnection trinoConnection = connection.unwrap(TrinoConnection.class); - assertEquals(trinoConnection.getExtraCredentials(), expectedCredentials); - assertEquals(listExtraCredentials(connection), expectedCredentials); + assertThat(trinoConnection.getExtraCredentials()).isEqualTo(expectedCredentials); + assertThat(listExtraCredentials(connection)).isEqualTo(expectedCredentials); } } @@ -370,7 +367,7 @@ public void testClientInfoParameter() throws SQLException { try (Connection connection = createConnection("clientInfo=hello%20world")) { - assertEquals(connection.getClientInfo("ClientInfo"), "hello world"); + assertThat(connection.getClientInfo("ClientInfo")).isEqualTo("hello world"); } } @@ -379,7 +376,7 @@ public void testClientTags() throws SQLException { try (Connection connection = createConnection("clientTags=c2,c3")) { - assertEquals(connection.getClientInfo("ClientTags"), "c2,c3"); + assertThat(connection.getClientInfo("ClientTags")).isEqualTo("c2,c3"); } } @@ -388,7 +385,7 @@ public void testTraceToken() throws SQLException { try (Connection connection = createConnection("traceToken=trace%20me")) { - assertEquals(connection.getClientInfo("TraceToken"), "trace me"); + assertThat(connection.getClientInfo("TraceToken")).isEqualTo("trace me"); } } @@ -418,8 +415,8 @@ private void testRole(String roleParameterValue, ClientSelectedRole clientSelect { try (Connection connection = createConnection("roles=hive:" + roleParameterValue)) { TrinoConnection trinoConnection = connection.unwrap(TrinoConnection.class); - assertEquals(trinoConnection.getRoles(), ImmutableMap.of("hive", clientSelectedRole)); - assertEquals(listCurrentRoles(connection), currentRoles); + assertThat(trinoConnection.getRoles()).isEqualTo(ImmutableMap.of("hive", clientSelectedRole)); + assertThat(listCurrentRoles(connection)).isEqualTo(currentRoles); } } @@ -626,9 +623,9 @@ private static void assertConnectionSource(Connection connection, String expecte "SELECT source FROM system.runtime.queries WHERE query_id = ?")) { statement.setString(1, queryId); try (ResultSet rs = statement.executeQuery()) { - assertTrue(rs.next()); + assertThat(rs.next()).isTrue(); assertThat(rs.getString("source")).isEqualTo(expectedSource); - assertFalse(rs.next()); + assertThat(rs.next()).isFalse(); } } } diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcPreparedStatement.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcPreparedStatement.java index 7a5d9482eb03..266eaa470900 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcPreparedStatement.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcPreparedStatement.java @@ -72,10 +72,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -132,18 +128,18 @@ private void testExecuteQuery(boolean explicitPrepare) statement.setString(2, "hello"); try (ResultSet rs = statement.executeQuery()) { - assertTrue(rs.next()); - assertEquals(rs.getInt(1), 123); - assertEquals(rs.getString(2), "hello"); - assertFalse(rs.next()); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(1)).isEqualTo(123); + assertThat(rs.getString(2)).isEqualTo("hello"); + assertThat(rs.next()).isFalse(); } - assertTrue(statement.execute()); + assertThat(statement.execute()).isTrue(); try (ResultSet rs = statement.getResultSet()) { - assertTrue(rs.next()); - assertEquals(rs.getInt(1), 123); - assertEquals(rs.getString(2), "hello"); - assertFalse(rs.next()); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(1)).isEqualTo(123); + assertThat(rs.getString(2)).isEqualTo("hello"); + assertThat(rs.next()).isFalse(); } } } @@ -176,36 +172,36 @@ private void testGetMetadata(boolean explicitPrepare) try (PreparedStatement statement = connection.prepareStatement( "SELECT * FROM " + tableName)) { ResultSetMetaData metadata = statement.getMetaData(); - assertEquals(metadata.getColumnCount(), 8); + assertThat(metadata.getColumnCount()).isEqualTo(8); for (int i = 1; i <= metadata.getColumnCount(); i++) { - assertEquals(metadata.getCatalogName(i), "blackhole"); - assertEquals(metadata.getSchemaName(i), "blackhole"); - assertEquals(metadata.getTableName(i), tableName); + assertThat(metadata.getCatalogName(i)).isEqualTo("blackhole"); + assertThat(metadata.getSchemaName(i)).isEqualTo("blackhole"); + assertThat(metadata.getTableName(i)).isEqualTo(tableName); } - assertEquals(metadata.getColumnName(1), "c_boolean"); - assertEquals(metadata.getColumnTypeName(1), "boolean"); + assertThat(metadata.getColumnName(1)).isEqualTo("c_boolean"); + assertThat(metadata.getColumnTypeName(1)).isEqualTo("boolean"); - assertEquals(metadata.getColumnName(2), "c_decimal"); - assertEquals(metadata.getColumnTypeName(2), "decimal(38,0)"); + assertThat(metadata.getColumnName(2)).isEqualTo("c_decimal"); + assertThat(metadata.getColumnTypeName(2)).isEqualTo("decimal(38,0)"); - assertEquals(metadata.getColumnName(3), "c_decimal_2"); - assertEquals(metadata.getColumnTypeName(3), "decimal(10,3)"); + assertThat(metadata.getColumnName(3)).isEqualTo("c_decimal_2"); + assertThat(metadata.getColumnTypeName(3)).isEqualTo("decimal(10,3)"); - assertEquals(metadata.getColumnName(4), "c_varchar"); - assertEquals(metadata.getColumnTypeName(4), "varchar"); + assertThat(metadata.getColumnName(4)).isEqualTo("c_varchar"); + assertThat(metadata.getColumnTypeName(4)).isEqualTo("varchar"); - assertEquals(metadata.getColumnName(5), "c_varchar_2"); - assertEquals(metadata.getColumnTypeName(5), "varchar(10)"); + assertThat(metadata.getColumnName(5)).isEqualTo("c_varchar_2"); + assertThat(metadata.getColumnTypeName(5)).isEqualTo("varchar(10)"); - assertEquals(metadata.getColumnName(6), "c_row"); - assertEquals(metadata.getColumnTypeName(6), "row"); + assertThat(metadata.getColumnName(6)).isEqualTo("c_row"); + assertThat(metadata.getColumnTypeName(6)).isEqualTo("row"); - assertEquals(metadata.getColumnName(7), "c_array"); - assertEquals(metadata.getColumnTypeName(7), "array"); + assertThat(metadata.getColumnName(7)).isEqualTo("c_array"); + assertThat(metadata.getColumnTypeName(7)).isEqualTo("array"); - assertEquals(metadata.getColumnName(8), "c_map"); - assertEquals(metadata.getColumnTypeName(8), "map"); + assertThat(metadata.getColumnName(8)).isEqualTo("c_map"); + assertThat(metadata.getColumnTypeName(8)).isEqualTo("map"); } try (Statement statement = connection.createStatement()) { @@ -251,115 +247,115 @@ private void testGetParameterMetaData(boolean explicitPrepare) "AND c_array = ? AND c_map = ? AND c_tinyint = ? AND c_integer = ? AND c_bigint = ? " + "AND c_smallint = ? AND c_real = ? AND c_double = ?")) { ParameterMetaData parameterMetaData = statement.getParameterMetaData(); - assertEquals(parameterMetaData.getParameterCount(), 15); - - assertEquals(parameterMetaData.getParameterClassName(1), "unknown"); - assertEquals(parameterMetaData.getParameterType(1), Types.NULL); - assertEquals(parameterMetaData.getParameterTypeName(1), "unknown"); - assertEquals(parameterMetaData.isNullable(1), parameterNullableUnknown); - assertFalse(parameterMetaData.isSigned(1)); - assertEquals(parameterMetaData.getParameterMode(1), parameterModeUnknown); - - assertEquals(parameterMetaData.getParameterClassName(2), Boolean.class.getName()); - assertEquals(parameterMetaData.getParameterType(2), Types.BOOLEAN); - assertEquals(parameterMetaData.getParameterTypeName(2), "boolean"); - assertEquals(parameterMetaData.isNullable(2), parameterNullableUnknown); - assertFalse(parameterMetaData.isSigned(2)); - assertEquals(parameterMetaData.getParameterMode(2), parameterModeUnknown); - - assertEquals(parameterMetaData.getParameterClassName(3), BigDecimal.class.getName()); - assertEquals(parameterMetaData.getParameterType(3), Types.DECIMAL); - assertEquals(parameterMetaData.getParameterTypeName(3), "decimal"); - assertEquals(parameterMetaData.isNullable(3), parameterNullableUnknown); - assertTrue(parameterMetaData.isSigned(3)); - assertEquals(parameterMetaData.getParameterMode(3), parameterModeUnknown); - - assertEquals(parameterMetaData.getParameterClassName(4), BigDecimal.class.getName()); - assertEquals(parameterMetaData.getParameterType(4), Types.DECIMAL); - assertEquals(parameterMetaData.getParameterTypeName(4), "decimal"); - assertEquals(parameterMetaData.getPrecision(4), 10); - assertEquals(parameterMetaData.getScale(4), 3); - assertEquals(parameterMetaData.isNullable(4), parameterNullableUnknown); - assertTrue(parameterMetaData.isSigned(4)); - assertEquals(parameterMetaData.getParameterMode(4), parameterModeUnknown); - - assertEquals(parameterMetaData.getParameterClassName(5), String.class.getName()); - assertEquals(parameterMetaData.getParameterType(5), Types.VARCHAR); - assertEquals(parameterMetaData.getParameterTypeName(5), "varchar"); - assertEquals(parameterMetaData.isNullable(5), parameterNullableUnknown); - assertFalse(parameterMetaData.isSigned(5)); - assertEquals(parameterMetaData.getParameterMode(5), parameterModeUnknown); - - assertEquals(parameterMetaData.getParameterClassName(6), String.class.getName()); - assertEquals(parameterMetaData.getParameterType(6), Types.VARCHAR); - assertEquals(parameterMetaData.getParameterTypeName(6), "varchar"); - assertEquals(parameterMetaData.getPrecision(6), 5); - assertEquals(parameterMetaData.isNullable(6), parameterNullableUnknown); - assertFalse(parameterMetaData.isSigned(6)); - assertEquals(parameterMetaData.getParameterMode(6), parameterModeUnknown); - - assertEquals(parameterMetaData.getParameterClassName(7), String.class.getName()); - assertEquals(parameterMetaData.getParameterType(7), Types.JAVA_OBJECT); - assertEquals(parameterMetaData.getParameterTypeName(7), "row"); - assertEquals(parameterMetaData.isNullable(7), parameterNullableUnknown); - assertFalse(parameterMetaData.isSigned(7)); - assertEquals(parameterMetaData.getParameterMode(7), parameterModeUnknown); - - assertEquals(parameterMetaData.getParameterClassName(8), Array.class.getName()); - assertEquals(parameterMetaData.getParameterType(8), Types.ARRAY); - assertEquals(parameterMetaData.getParameterTypeName(8), "array"); - assertEquals(parameterMetaData.isNullable(8), parameterNullableUnknown); - assertFalse(parameterMetaData.isSigned(8)); - assertEquals(parameterMetaData.getParameterMode(8), parameterModeUnknown); - - assertEquals(parameterMetaData.getParameterClassName(9), String.class.getName()); - assertEquals(parameterMetaData.getParameterType(9), Types.JAVA_OBJECT); - assertEquals(parameterMetaData.getParameterTypeName(9), "map"); - assertEquals(parameterMetaData.isNullable(9), parameterNullableUnknown); - assertFalse(parameterMetaData.isSigned(9)); - assertEquals(parameterMetaData.getParameterMode(9), parameterModeUnknown); - - assertEquals(parameterMetaData.getParameterClassName(10), Byte.class.getName()); - assertEquals(parameterMetaData.getParameterType(10), Types.TINYINT); - assertEquals(parameterMetaData.getParameterTypeName(10), "tinyint"); - assertEquals(parameterMetaData.isNullable(10), parameterNullableUnknown); - assertTrue(parameterMetaData.isSigned(10)); - assertEquals(parameterMetaData.getParameterMode(10), parameterModeUnknown); - - assertEquals(parameterMetaData.getParameterClassName(11), Integer.class.getName()); - assertEquals(parameterMetaData.getParameterType(11), Types.INTEGER); - assertEquals(parameterMetaData.getParameterTypeName(11), "integer"); - assertEquals(parameterMetaData.isNullable(11), parameterNullableUnknown); - assertTrue(parameterMetaData.isSigned(11)); - assertEquals(parameterMetaData.getParameterMode(11), parameterModeUnknown); - - assertEquals(parameterMetaData.getParameterClassName(12), Long.class.getName()); - assertEquals(parameterMetaData.getParameterType(12), Types.BIGINT); - assertEquals(parameterMetaData.getParameterTypeName(12), "bigint"); - assertEquals(parameterMetaData.isNullable(12), parameterNullableUnknown); - assertTrue(parameterMetaData.isSigned(12)); - assertEquals(parameterMetaData.getParameterMode(12), parameterModeUnknown); - - assertEquals(parameterMetaData.getParameterClassName(13), Short.class.getName()); - assertEquals(parameterMetaData.getParameterType(13), Types.SMALLINT); - assertEquals(parameterMetaData.getParameterTypeName(13), "smallint"); - assertEquals(parameterMetaData.isNullable(13), parameterNullableUnknown); - assertTrue(parameterMetaData.isSigned(13)); - assertEquals(parameterMetaData.getParameterMode(13), parameterModeUnknown); - - assertEquals(parameterMetaData.getParameterClassName(14), Float.class.getName()); - assertEquals(parameterMetaData.getParameterType(14), Types.REAL); - assertEquals(parameterMetaData.getParameterTypeName(14), "real"); - assertEquals(parameterMetaData.isNullable(14), parameterNullableUnknown); - assertTrue(parameterMetaData.isSigned(14)); - assertEquals(parameterMetaData.getParameterMode(14), parameterModeUnknown); - - assertEquals(parameterMetaData.getParameterClassName(15), Double.class.getName()); - assertEquals(parameterMetaData.getParameterType(15), Types.DOUBLE); - assertEquals(parameterMetaData.getParameterTypeName(15), "double"); - assertEquals(parameterMetaData.isNullable(15), parameterNullableUnknown); - assertTrue(parameterMetaData.isSigned(15)); - assertEquals(parameterMetaData.getParameterMode(15), parameterModeUnknown); + assertThat(parameterMetaData.getParameterCount()).isEqualTo(15); + + assertThat(parameterMetaData.getParameterClassName(1)).isEqualTo("unknown"); + assertThat(parameterMetaData.getParameterType(1)).isEqualTo(Types.NULL); + assertThat(parameterMetaData.getParameterTypeName(1)).isEqualTo("unknown"); + assertThat(parameterMetaData.isNullable(1)).isEqualTo(parameterNullableUnknown); + assertThat(parameterMetaData.isSigned(1)).isFalse(); + assertThat(parameterMetaData.getParameterMode(1)).isEqualTo(parameterModeUnknown); + + assertThat(parameterMetaData.getParameterClassName(2)).isEqualTo(Boolean.class.getName()); + assertThat(parameterMetaData.getParameterType(2)).isEqualTo(Types.BOOLEAN); + assertThat(parameterMetaData.getParameterTypeName(2)).isEqualTo("boolean"); + assertThat(parameterMetaData.isNullable(2)).isEqualTo(parameterNullableUnknown); + assertThat(parameterMetaData.isSigned(2)).isFalse(); + assertThat(parameterMetaData.getParameterMode(2)).isEqualTo(parameterModeUnknown); + + assertThat(parameterMetaData.getParameterClassName(3)).isEqualTo(BigDecimal.class.getName()); + assertThat(parameterMetaData.getParameterType(3)).isEqualTo(Types.DECIMAL); + assertThat(parameterMetaData.getParameterTypeName(3)).isEqualTo("decimal"); + assertThat(parameterMetaData.isNullable(3)).isEqualTo(parameterNullableUnknown); + assertThat(parameterMetaData.isSigned(3)).isTrue(); + assertThat(parameterMetaData.getParameterMode(3)).isEqualTo(parameterModeUnknown); + + assertThat(parameterMetaData.getParameterClassName(4)).isEqualTo(BigDecimal.class.getName()); + assertThat(parameterMetaData.getParameterType(4)).isEqualTo(Types.DECIMAL); + assertThat(parameterMetaData.getParameterTypeName(4)).isEqualTo("decimal"); + assertThat(parameterMetaData.getPrecision(4)).isEqualTo(10); + assertThat(parameterMetaData.getScale(4)).isEqualTo(3); + assertThat(parameterMetaData.isNullable(4)).isEqualTo(parameterNullableUnknown); + assertThat(parameterMetaData.isSigned(4)).isTrue(); + assertThat(parameterMetaData.getParameterMode(4)).isEqualTo(parameterModeUnknown); + + assertThat(parameterMetaData.getParameterClassName(5)).isEqualTo(String.class.getName()); + assertThat(parameterMetaData.getParameterType(5)).isEqualTo(Types.VARCHAR); + assertThat(parameterMetaData.getParameterTypeName(5)).isEqualTo("varchar"); + assertThat(parameterMetaData.isNullable(5)).isEqualTo(parameterNullableUnknown); + assertThat(parameterMetaData.isSigned(5)).isFalse(); + assertThat(parameterMetaData.getParameterMode(5)).isEqualTo(parameterModeUnknown); + + assertThat(parameterMetaData.getParameterClassName(6)).isEqualTo(String.class.getName()); + assertThat(parameterMetaData.getParameterType(6)).isEqualTo(Types.VARCHAR); + assertThat(parameterMetaData.getParameterTypeName(6)).isEqualTo("varchar"); + assertThat(parameterMetaData.getPrecision(6)).isEqualTo(5); + assertThat(parameterMetaData.isNullable(6)).isEqualTo(parameterNullableUnknown); + assertThat(parameterMetaData.isSigned(6)).isFalse(); + assertThat(parameterMetaData.getParameterMode(6)).isEqualTo(parameterModeUnknown); + + assertThat(parameterMetaData.getParameterClassName(7)).isEqualTo(String.class.getName()); + assertThat(parameterMetaData.getParameterType(7)).isEqualTo(Types.JAVA_OBJECT); + assertThat(parameterMetaData.getParameterTypeName(7)).isEqualTo("row"); + assertThat(parameterMetaData.isNullable(7)).isEqualTo(parameterNullableUnknown); + assertThat(parameterMetaData.isSigned(7)).isFalse(); + assertThat(parameterMetaData.getParameterMode(7)).isEqualTo(parameterModeUnknown); + + assertThat(parameterMetaData.getParameterClassName(8)).isEqualTo(Array.class.getName()); + assertThat(parameterMetaData.getParameterType(8)).isEqualTo(Types.ARRAY); + assertThat(parameterMetaData.getParameterTypeName(8)).isEqualTo("array"); + assertThat(parameterMetaData.isNullable(8)).isEqualTo(parameterNullableUnknown); + assertThat(parameterMetaData.isSigned(8)).isFalse(); + assertThat(parameterMetaData.getParameterMode(8)).isEqualTo(parameterModeUnknown); + + assertThat(parameterMetaData.getParameterClassName(9)).isEqualTo(String.class.getName()); + assertThat(parameterMetaData.getParameterType(9)).isEqualTo(Types.JAVA_OBJECT); + assertThat(parameterMetaData.getParameterTypeName(9)).isEqualTo("map"); + assertThat(parameterMetaData.isNullable(9)).isEqualTo(parameterNullableUnknown); + assertThat(parameterMetaData.isSigned(9)).isFalse(); + assertThat(parameterMetaData.getParameterMode(9)).isEqualTo(parameterModeUnknown); + + assertThat(parameterMetaData.getParameterClassName(10)).isEqualTo(Byte.class.getName()); + assertThat(parameterMetaData.getParameterType(10)).isEqualTo(Types.TINYINT); + assertThat(parameterMetaData.getParameterTypeName(10)).isEqualTo("tinyint"); + assertThat(parameterMetaData.isNullable(10)).isEqualTo(parameterNullableUnknown); + assertThat(parameterMetaData.isSigned(10)).isTrue(); + assertThat(parameterMetaData.getParameterMode(10)).isEqualTo(parameterModeUnknown); + + assertThat(parameterMetaData.getParameterClassName(11)).isEqualTo(Integer.class.getName()); + assertThat(parameterMetaData.getParameterType(11)).isEqualTo(Types.INTEGER); + assertThat(parameterMetaData.getParameterTypeName(11)).isEqualTo("integer"); + assertThat(parameterMetaData.isNullable(11)).isEqualTo(parameterNullableUnknown); + assertThat(parameterMetaData.isSigned(11)).isTrue(); + assertThat(parameterMetaData.getParameterMode(11)).isEqualTo(parameterModeUnknown); + + assertThat(parameterMetaData.getParameterClassName(12)).isEqualTo(Long.class.getName()); + assertThat(parameterMetaData.getParameterType(12)).isEqualTo(Types.BIGINT); + assertThat(parameterMetaData.getParameterTypeName(12)).isEqualTo("bigint"); + assertThat(parameterMetaData.isNullable(12)).isEqualTo(parameterNullableUnknown); + assertThat(parameterMetaData.isSigned(12)).isTrue(); + assertThat(parameterMetaData.getParameterMode(12)).isEqualTo(parameterModeUnknown); + + assertThat(parameterMetaData.getParameterClassName(13)).isEqualTo(Short.class.getName()); + assertThat(parameterMetaData.getParameterType(13)).isEqualTo(Types.SMALLINT); + assertThat(parameterMetaData.getParameterTypeName(13)).isEqualTo("smallint"); + assertThat(parameterMetaData.isNullable(13)).isEqualTo(parameterNullableUnknown); + assertThat(parameterMetaData.isSigned(13)).isTrue(); + assertThat(parameterMetaData.getParameterMode(13)).isEqualTo(parameterModeUnknown); + + assertThat(parameterMetaData.getParameterClassName(14)).isEqualTo(Float.class.getName()); + assertThat(parameterMetaData.getParameterType(14)).isEqualTo(Types.REAL); + assertThat(parameterMetaData.getParameterTypeName(14)).isEqualTo("real"); + assertThat(parameterMetaData.isNullable(14)).isEqualTo(parameterNullableUnknown); + assertThat(parameterMetaData.isSigned(14)).isTrue(); + assertThat(parameterMetaData.getParameterMode(14)).isEqualTo(parameterModeUnknown); + + assertThat(parameterMetaData.getParameterClassName(15)).isEqualTo(Double.class.getName()); + assertThat(parameterMetaData.getParameterType(15)).isEqualTo(Types.DOUBLE); + assertThat(parameterMetaData.getParameterTypeName(15)).isEqualTo("double"); + assertThat(parameterMetaData.isNullable(15)).isEqualTo(parameterNullableUnknown); + assertThat(parameterMetaData.isSigned(15)).isTrue(); + assertThat(parameterMetaData.getParameterMode(15)).isEqualTo(parameterModeUnknown); } try (Statement statement = connection.createStatement()) { @@ -373,41 +369,41 @@ public void testGetClientTypeSignatureFromTypeString() { ClientTypeSignature actualClientTypeSignature = TrinoPreparedStatement.getClientTypeSignatureFromTypeString("boolean"); ClientTypeSignature expectedClientTypeSignature = new ClientTypeSignature("boolean", ImmutableList.of()); - assertEquals(actualClientTypeSignature, expectedClientTypeSignature); + assertThat(actualClientTypeSignature).isEqualTo(expectedClientTypeSignature); actualClientTypeSignature = TrinoPreparedStatement.getClientTypeSignatureFromTypeString("decimal(10,3)"); expectedClientTypeSignature = new ClientTypeSignature("decimal", ImmutableList.of( ClientTypeSignatureParameter.ofLong(10), ClientTypeSignatureParameter.ofLong(3))); - assertEquals(actualClientTypeSignature, expectedClientTypeSignature); + assertThat(actualClientTypeSignature).isEqualTo(expectedClientTypeSignature); actualClientTypeSignature = TrinoPreparedStatement.getClientTypeSignatureFromTypeString("varchar"); expectedClientTypeSignature = new ClientTypeSignature("varchar", ImmutableList.of(ClientTypeSignatureParameter.ofLong(VARCHAR_UNBOUNDED_LENGTH))); - assertEquals(actualClientTypeSignature, expectedClientTypeSignature); + assertThat(actualClientTypeSignature).isEqualTo(expectedClientTypeSignature); actualClientTypeSignature = TrinoPreparedStatement.getClientTypeSignatureFromTypeString("varchar(10)"); expectedClientTypeSignature = new ClientTypeSignature("varchar", ImmutableList.of(ClientTypeSignatureParameter.ofLong(10))); - assertEquals(actualClientTypeSignature, expectedClientTypeSignature); + assertThat(actualClientTypeSignature).isEqualTo(expectedClientTypeSignature); actualClientTypeSignature = TrinoPreparedStatement.getClientTypeSignatureFromTypeString("row(x integer, y array(integer))"); expectedClientTypeSignature = new ClientTypeSignature("row", ImmutableList.of()); - assertEquals(actualClientTypeSignature, expectedClientTypeSignature); + assertThat(actualClientTypeSignature).isEqualTo(expectedClientTypeSignature); actualClientTypeSignature = TrinoPreparedStatement.getClientTypeSignatureFromTypeString("array(integer)"); expectedClientTypeSignature = new ClientTypeSignature("array", ImmutableList.of()); - assertEquals(actualClientTypeSignature, expectedClientTypeSignature); + assertThat(actualClientTypeSignature).isEqualTo(expectedClientTypeSignature); actualClientTypeSignature = TrinoPreparedStatement.getClientTypeSignatureFromTypeString("map(integer, integer)"); expectedClientTypeSignature = new ClientTypeSignature("map", ImmutableList.of()); - assertEquals(actualClientTypeSignature, expectedClientTypeSignature); + assertThat(actualClientTypeSignature).isEqualTo(expectedClientTypeSignature); actualClientTypeSignature = TrinoPreparedStatement.getClientTypeSignatureFromTypeString("timestamp(12) with time zone"); expectedClientTypeSignature = new ClientTypeSignature("timestamp with time zone", ImmutableList.of(ClientTypeSignatureParameter.ofLong(12))); - assertEquals(actualClientTypeSignature, expectedClientTypeSignature); + assertThat(actualClientTypeSignature).isEqualTo(expectedClientTypeSignature); actualClientTypeSignature = TrinoPreparedStatement.getClientTypeSignatureFromTypeString("time(13) with time zone"); expectedClientTypeSignature = new ClientTypeSignature("time with time zone", ImmutableList.of(ClientTypeSignatureParameter.ofLong(13))); - assertEquals(actualClientTypeSignature, expectedClientTypeSignature); + assertThat(actualClientTypeSignature).isEqualTo(expectedClientTypeSignature); } @Test @@ -509,11 +505,11 @@ public void testExecuteUpdate(boolean explicitPrepare) statement.setBytes(6, "xyz".getBytes(UTF_8)); statement.setNull(7, Types.BIGINT); - assertEquals(statement.executeUpdate(), 1); + assertThat(statement.executeUpdate()).isEqualTo(1); - assertFalse(statement.execute()); - assertEquals(statement.getUpdateCount(), 1); - assertEquals(statement.getLargeUpdateCount(), 1); + assertThat(statement.execute()).isFalse(); + assertThat(statement.getUpdateCount()).isEqualTo(1); + assertThat(statement.getLargeUpdateCount()).isEqualTo(1); } try (Statement statement = connection.createStatement()) { @@ -542,13 +538,13 @@ private void testExecuteBatch(boolean explicitPrepare) try (PreparedStatement preparedStatement = connection.prepareStatement( "INSERT INTO " + tableName + " VALUES (?)")) { // Run executeBatch before addBatch - assertEquals(preparedStatement.executeBatch(), new int[] {}); + assertThat(preparedStatement.executeBatch()).isEqualTo(new int[] {}); for (int i = 0; i < 3; i++) { preparedStatement.setInt(1, i); preparedStatement.addBatch(); } - assertEquals(preparedStatement.executeBatch(), new int[] {1, 1, 1}); + assertThat(preparedStatement.executeBatch()).isEqualTo(new int[] {1, 1, 1}); try (Statement statement = connection.createStatement()) { ResultSet resultSet = statement.executeQuery("SELECT c_int FROM " + tableName); @@ -560,15 +556,15 @@ private void testExecuteBatch(boolean explicitPrepare) } // Make sure the above executeBatch cleared existing batch - assertEquals(preparedStatement.executeBatch(), new int[] {}); + assertThat(preparedStatement.executeBatch()).isEqualTo(new int[] {}); // clearBatch removes added batch and cancel batch mode preparedStatement.setBoolean(1, true); preparedStatement.clearBatch(); - assertEquals(preparedStatement.executeBatch(), new int[] {}); + assertThat(preparedStatement.executeBatch()).isEqualTo(new int[] {}); preparedStatement.setInt(1, 1); - assertEquals(preparedStatement.executeUpdate(), 1); + assertThat(preparedStatement.executeUpdate()).isEqualTo(1); } try (Statement statement = connection.createStatement()) { @@ -635,15 +631,15 @@ private void testPrepareMultiple(boolean explicitPrepare) PreparedStatement statement1 = connection.prepareStatement("SELECT 123"); PreparedStatement statement2 = connection.prepareStatement("SELECT 456")) { try (ResultSet rs = statement1.executeQuery()) { - assertTrue(rs.next()); - assertEquals(rs.getLong(1), 123); - assertFalse(rs.next()); + assertThat(rs.next()).isTrue(); + assertThat(rs.getLong(1)).isEqualTo(123); + assertThat(rs.next()).isFalse(); } try (ResultSet rs = statement2.executeQuery()) { - assertTrue(rs.next()); - assertEquals(rs.getLong(1), 456); - assertFalse(rs.next()); + assertThat(rs.next()).isTrue(); + assertThat(rs.getLong(1)).isEqualTo(456); + assertThat(rs.next()).isFalse(); } } } @@ -663,9 +659,9 @@ private void testPrepareLarge(boolean explicitPrepare) try (Connection connection = createConnection(explicitPrepare); PreparedStatement statement = connection.prepareStatement(sql); ResultSet rs = statement.executeQuery()) { - assertTrue(rs.next()); - assertFalse(rs.getBoolean(1)); - assertFalse(rs.next()); + assertThat(rs.next()).isTrue(); + assertThat(rs.getBoolean(1)).isFalse(); + assertThat(rs.next()).isFalse(); } } @@ -721,12 +717,12 @@ private void assertSetNull(int sqlType, int expectedSqlType, boolean explicitPre statement.setNull(1, sqlType); try (ResultSet rs = statement.executeQuery()) { - assertTrue(rs.next()); - assertNull(rs.getObject(1)); - assertTrue(rs.wasNull()); - assertFalse(rs.next()); + assertThat(rs.next()).isTrue(); + assertThat(rs.getObject(1)).isNull(); + assertThat(rs.wasNull()).isTrue(); + assertThat(rs.next()).isFalse(); - assertEquals(rs.getMetaData().getColumnType(1), expectedSqlType); + assertThat(rs.getMetaData().getColumnType(1)).isEqualTo(expectedSqlType); } } } @@ -1433,7 +1429,7 @@ private void testExplicitPrepareSetting(boolean explicitPrepare, String expected try (Connection connection = createConnection(explicitPrepare)) { try (Statement statement = connection.createStatement()) { - assertEquals(statement.executeUpdate("CREATE TABLE blackhole.blackhole." + tableName + " (x bigint, y varchar)"), 0); + assertThat(statement.executeUpdate("CREATE TABLE blackhole.blackhole." + tableName + " (x bigint, y varchar)")).isEqualTo(0); } try (PreparedStatement ps = connection.prepareStatement(selectSql)) { @@ -1489,7 +1485,7 @@ private void testExplicitPrepareSetting(boolean explicitPrepare, String expected } try (Statement statement = connection.createStatement()) { - assertEquals(statement.executeUpdate("DROP TABLE blackhole.blackhole." + tableName), 0); + assertThat(statement.executeUpdate("DROP TABLE blackhole.blackhole." + tableName)).isEqualTo(0); } } } @@ -1500,7 +1496,9 @@ private void checkSQLExecuted(Connection connection, String expectedSql) try (Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery(sql)) { - assertTrue(resultSet.next(), "Cannot find SQL query " + expectedSql); + assertThat(resultSet.next()) + .describedAs("Cannot find SQL query " + expectedSql) + .isTrue(); } catch (SQLException e) { throw new RuntimeException(e); @@ -1540,10 +1538,10 @@ public BindAssertion roundTripsAs(int expectedSqlType, Object expectedValue) try (ResultSet rs = statement.executeQuery()) { verify(rs.next(), "no row returned"); - assertEquals(rs.getObject(1), expectedValue); + assertThat(rs.getObject(1)).isEqualTo(expectedValue); verify(!rs.next(), "unexpected second row"); - assertEquals(rs.getMetaData().getColumnType(1), expectedSqlType); + assertThat(rs.getMetaData().getColumnType(1)).isEqualTo(expectedSqlType); } } diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcVendorCompatibility.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcVendorCompatibility.java index fadcf2f2c18a..dc2a7097ffce 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcVendorCompatibility.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcVendorCompatibility.java @@ -65,10 +65,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotEquals; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(SAME_THREAD) @@ -83,7 +79,9 @@ public class TestJdbcVendorCompatibility @BeforeAll public void setupServer() { - assertNotEquals(OTHER_TIMEZONE, TimeZone.getDefault().getID(), "We need a timezone different from the default JVM one"); + assertThat(OTHER_TIMEZONE) + .describedAs("We need a timezone different from the default JVM one") + .isNotEqualTo(TimeZone.getDefault().getID()); Logging.initialize(); log = Logger.get(TestJdbcVendorCompatibility.class); server = TestingTrinoServer.create(); @@ -156,12 +154,12 @@ private void testDate(Optional sessionTimezoneId) Statement statement = connection.createStatement(); ConnectionSetup connectionSetup = new ConnectionSetup(referenceDrivers)) { checkRepresentation(connection, statement, "DATE '2018-02-13'", DATE, sessionTimezoneId, (rs, reference, column) -> { - assertEquals(rs.getDate(column), reference.getDate(column)); - assertEquals(rs.getDate(column), Date.valueOf(LocalDate.of(2018, 2, 13))); + assertThat(rs.getDate(column)).isEqualTo(reference.getDate(column)); + assertThat(rs.getDate(column)).isEqualTo(Date.valueOf(LocalDate.of(2018, 2, 13))); // with calendar - assertEquals(rs.getDate(column, getCalendar()), reference.getDate(column, getCalendar())); - assertEquals(rs.getDate(column, getCalendar()), new Date(LocalDate.of(2018, 2, 13).atStartOfDay(getZoneId()).toInstant().toEpochMilli())); + assertThat(rs.getDate(column, getCalendar())).isEqualTo(reference.getDate(column, getCalendar())); + assertThat(rs.getDate(column, getCalendar())).isEqualTo(new Date(LocalDate.of(2018, 2, 13).atStartOfDay(getZoneId()).toInstant().toEpochMilli())); }); } } @@ -184,16 +182,12 @@ private void testTimestamp(Optional sessionTimezoneId) Statement statement = connection.createStatement(); ConnectionSetup connectionSetup = new ConnectionSetup(referenceDrivers)) { checkRepresentation(connection, statement, "TIMESTAMP '2018-02-13 13:14:15.123'", TIMESTAMP, sessionTimezoneId, (rs, reference, column) -> { - assertEquals(rs.getTimestamp(column), reference.getTimestamp(column)); - assertEquals( - rs.getTimestamp(column), - Timestamp.valueOf(LocalDateTime.of(2018, 2, 13, 13, 14, 15, 123_000_000))); + assertThat(rs.getTimestamp(column)).isEqualTo(reference.getTimestamp(column)); + assertThat(rs.getTimestamp(column)).isEqualTo(Timestamp.valueOf(LocalDateTime.of(2018, 2, 13, 13, 14, 15, 123_000_000))); // with calendar - assertEquals(rs.getTimestamp(column, getCalendar()), reference.getTimestamp(column, getCalendar())); - assertEquals( - rs.getTimestamp(column, getCalendar()), - new Timestamp(LocalDateTime.of(2018, 2, 13, 13, 14, 15, 123_000_000).atZone(getZoneId()).toInstant().toEpochMilli())); + assertThat(rs.getTimestamp(column, getCalendar())).isEqualTo(reference.getTimestamp(column, getCalendar())); + assertThat(rs.getTimestamp(column, getCalendar())).isEqualTo(new Timestamp(LocalDateTime.of(2018, 2, 13, 13, 14, 15, 123_000_000).atZone(getZoneId()).toInstant().toEpochMilli())); }); } } @@ -225,14 +219,14 @@ private void testTimestampWithTimeZone(Optional sessionTimezoneId) (rs, reference, column) -> { Timestamp timestampForPointInTime = Timestamp.from(Instant.EPOCH); - assertEquals(rs.getTimestamp(column).getTime(), reference.getTimestamp(column).getTime()); // point in time - assertEquals(rs.getTimestamp(column), reference.getTimestamp(column)); - assertEquals(rs.getTimestamp(column), timestampForPointInTime); + assertThat(rs.getTimestamp(column).getTime()).isEqualTo(reference.getTimestamp(column).getTime()); // point in time + assertThat(rs.getTimestamp(column)).isEqualTo(reference.getTimestamp(column)); + assertThat(rs.getTimestamp(column)).isEqualTo(timestampForPointInTime); // with calendar - assertEquals(rs.getTimestamp(column, getCalendar()).getTime(), reference.getTimestamp(column, getCalendar()).getTime()); // point in time - assertEquals(rs.getTimestamp(column, getCalendar()), reference.getTimestamp(column, getCalendar())); - assertEquals(rs.getTimestamp(column, getCalendar()), timestampForPointInTime); + assertThat(rs.getTimestamp(column, getCalendar()).getTime()).isEqualTo(reference.getTimestamp(column, getCalendar()).getTime()); // point in time + assertThat(rs.getTimestamp(column, getCalendar())).isEqualTo(reference.getTimestamp(column, getCalendar())); + assertThat(rs.getTimestamp(column, getCalendar())).isEqualTo(timestampForPointInTime); }); checkRepresentation( @@ -247,14 +241,14 @@ private void testTimestampWithTimeZone(Optional sessionTimezoneId) ZonedDateTime.of(2018, 2, 13, 13, 14, 15, 123_000_000, ZoneOffset.ofHoursMinutes(3, 15)) .toInstant()); - assertEquals(rs.getTimestamp(column).getTime(), reference.getTimestamp(column).getTime()); // point in time - assertEquals(rs.getTimestamp(column), reference.getTimestamp(column)); - assertEquals(rs.getTimestamp(column), timestampForPointInTime); + assertThat(rs.getTimestamp(column).getTime()).isEqualTo(reference.getTimestamp(column).getTime()); // point in time + assertThat(rs.getTimestamp(column)).isEqualTo(reference.getTimestamp(column)); + assertThat(rs.getTimestamp(column)).isEqualTo(timestampForPointInTime); // with calendar - assertEquals(rs.getTimestamp(column, getCalendar()).getTime(), reference.getTimestamp(column, getCalendar()).getTime()); // point in time - assertEquals(rs.getTimestamp(column, getCalendar()), reference.getTimestamp(column, getCalendar())); - assertEquals(rs.getTimestamp(column, getCalendar()), timestampForPointInTime); + assertThat(rs.getTimestamp(column, getCalendar()).getTime()).isEqualTo(reference.getTimestamp(column, getCalendar()).getTime()); // point in time + assertThat(rs.getTimestamp(column, getCalendar())).isEqualTo(reference.getTimestamp(column, getCalendar())); + assertThat(rs.getTimestamp(column, getCalendar())).isEqualTo(timestampForPointInTime); }); checkRepresentation( @@ -269,14 +263,14 @@ private void testTimestampWithTimeZone(Optional sessionTimezoneId) ZonedDateTime.of(2018, 2, 13, 13, 14, 15, 123_000_000, ZoneId.of("Europe/Warsaw")) .toInstant()); - assertEquals(rs.getTimestamp(column).getTime(), reference.getTimestamp(column).getTime()); // point in time - assertEquals(rs.getTimestamp(column), reference.getTimestamp(column)); - assertEquals(rs.getTimestamp(column), timestampForPointInTime); + assertThat(rs.getTimestamp(column).getTime()).isEqualTo(reference.getTimestamp(column).getTime()); // point in time + assertThat(rs.getTimestamp(column)).isEqualTo(reference.getTimestamp(column)); + assertThat(rs.getTimestamp(column)).isEqualTo(timestampForPointInTime); // with calendar - assertEquals(rs.getTimestamp(column, getCalendar()).getTime(), reference.getTimestamp(column, getCalendar()).getTime()); // point in time - assertEquals(rs.getTimestamp(column, getCalendar()), reference.getTimestamp(column, getCalendar())); - assertEquals(rs.getTimestamp(column, getCalendar()), timestampForPointInTime); + assertThat(rs.getTimestamp(column, getCalendar()).getTime()).isEqualTo(reference.getTimestamp(column, getCalendar()).getTime()); // point in time + assertThat(rs.getTimestamp(column, getCalendar())).isEqualTo(reference.getTimestamp(column, getCalendar())); + assertThat(rs.getTimestamp(column, getCalendar())).isEqualTo(timestampForPointInTime); }); } } @@ -299,12 +293,12 @@ private void testTime(Optional sessionTimezoneId) Statement statement = connection.createStatement(); ConnectionSetup connectionSetup = new ConnectionSetup(referenceDrivers)) { checkRepresentation(connection, statement, "TIME '09:39:05'", TIME, sessionTimezoneId, (rs, reference, column) -> { - assertEquals(rs.getTime(column), reference.getTime(column)); - assertEquals(rs.getTime(column), Time.valueOf(LocalTime.of(9, 39, 5))); + assertThat(rs.getTime(column)).isEqualTo(reference.getTime(column)); + assertThat(rs.getTime(column)).isEqualTo(Time.valueOf(LocalTime.of(9, 39, 5))); // with calendar - assertEquals(rs.getTime(column, getCalendar()), reference.getTime(column, getCalendar())); - assertEquals(rs.getTime(column, getCalendar()), new Time(LocalDate.of(1970, 1, 1).atTime(LocalTime.of(9, 39, 5)).atZone(getZoneId()).toInstant().toEpochMilli())); + assertThat(rs.getTime(column, getCalendar())).isEqualTo(reference.getTime(column, getCalendar())); + assertThat(rs.getTime(column, getCalendar())).isEqualTo(new Time(LocalDate.of(1970, 1, 1).atTime(LocalTime.of(9, 39, 5)).atZone(getZoneId()).toInstant().toEpochMilli())); }); } } @@ -392,9 +386,9 @@ private void assertParameter(Connection connection, Object expectedValue, Option binder.bind(statement, 1); try (ResultSet rs = statement.executeQuery()) { - assertTrue(rs.next()); - assertEquals(expectedValue, rs.getObject(1)); - assertFalse(rs.next()); + assertThat(rs.next()).isTrue(); + assertThat(expectedValue).isEqualTo(rs.getObject(1)); + assertThat(rs.next()).isFalse(); } } } @@ -456,8 +450,8 @@ private void checkRepresentation(Connection connection, Statement statement, Str { try (ResultSet trinoResultSet = trinoQuery(connection, statement, trinoExpression, sessionTimezoneId); ResultSet referenceResultSet = reference.query(referenceExpression, sessionTimezoneId)) { - assertTrue(trinoResultSet.next()); - assertTrue(referenceResultSet.next()); + assertThat(trinoResultSet.next()).isTrue(); + assertThat(referenceResultSet.next()).isTrue(); assertion.accept(trinoResultSet, referenceResultSet, 1); assertThat(trinoResultSet.getMetaData().getColumnType(1)).as("Trino declared SQL type") @@ -466,8 +460,8 @@ private void checkRepresentation(Connection connection, Statement statement, Str assertThat(referenceResultSet.getMetaData().getColumnType(1)).as("Reference driver's declared SQL type for " + type) .isEqualTo(reference.expectedDeclaredJdbcType(type)); - assertFalse(trinoResultSet.next()); - assertFalse(referenceResultSet.next()); + assertThat(trinoResultSet.next()).isFalse(); + assertThat(referenceResultSet.next()).isFalse(); } } diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcWarnings.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcWarnings.java index 2bbd00ac91b6..d5aa7a6f596e 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcWarnings.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcWarnings.java @@ -51,11 +51,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(SAME_THREAD) @@ -109,7 +106,7 @@ public void testStatementWarnings() { try (Connection connection = createConnection(); Statement statement = connection.createStatement()) { - assertFalse(statement.execute("CREATE SCHEMA blackhole.test_schema")); + assertThat(statement.execute("CREATE SCHEMA blackhole.test_schema")).isFalse(); SQLWarning warning = statement.getWarnings(); assertNotNull(warning); TestingWarningCollectorConfig warningCollectorConfig = new TestingWarningCollectorConfig().setPreloadedWarnings(PRELOADED_WARNINGS); @@ -171,7 +168,7 @@ public void testExecuteQueryWarnings() TestingWarningCollector warningCollector = new TestingWarningCollector(new WarningCollectorConfig(), warningCollectorConfig); List expectedWarnings = warningCollector.getWarnings(); for (TrinoWarning trinoWarning : expectedWarnings) { - assertTrue(currentWarnings.contains(new WarningEntry(toTrinoSqlWarning(trinoWarning)))); + assertThat(currentWarnings.contains(new WarningEntry(toTrinoSqlWarning(trinoWarning)))).isTrue(); } } } @@ -186,7 +183,7 @@ public void testSqlWarning() } List warnings = builder.build(); SQLWarning warning = fromTrinoWarnings(warnings); - assertEquals(Iterators.size(warning.iterator()), warnings.size()); + assertThat(Iterators.size(warning.iterator())).isEqualTo(warnings.size()); assertWarningsEqual(warning, toTrinoSqlWarning(warnings.get(0))); assertWarningsEqual(warning.getNextWarning(), toTrinoSqlWarning(warnings.get(1))); assertWarningsEqual(warning.getNextWarning().getNextWarning(), toTrinoSqlWarning(warnings.get(2))); @@ -204,10 +201,10 @@ private static void assertStatementWarnings(Statement statement, Future futur SQLWarning warning = statement.getWarnings(); // collect initial set of warnings - assertTrue(warnings.add(new WarningEntry(warning))); + assertThat(warnings.add(new WarningEntry(warning))).isTrue(); while (warning.getNextWarning() != null) { warning = warning.getNextWarning(); - assertTrue(warnings.add(new WarningEntry(warning))); + assertThat(warnings.add(new WarningEntry(warning))).isTrue(); } int initialSize = warnings.size(); @@ -220,7 +217,7 @@ private static void assertStatementWarnings(Statement statement, Future futur continue; } warning = warning.getNextWarning(); - assertTrue(warnings.add(new WarningEntry(warning))); + assertThat(warnings.add(new WarningEntry(warning))).isTrue(); } int finalSize = warnings.size(); @@ -232,7 +229,7 @@ private static void assertStatementWarnings(Statement statement, Future futur private static SQLWarning fromTrinoWarnings(List warnings) { requireNonNull(warnings, "warnings is null"); - assertFalse(warnings.isEmpty()); + assertThat(warnings.isEmpty()).isFalse(); Iterator iterator = warnings.iterator(); TrinoSqlWarning first = toTrinoSqlWarning(iterator.next()); SQLWarning current = first; @@ -256,9 +253,9 @@ private static Warning toClientWarning(TrinoWarning warning) private static void assertWarningsEqual(SQLWarning actual, SQLWarning expected) { - assertEquals(actual.getMessage(), expected.getMessage()); - assertEquals(actual.getSQLState(), expected.getSQLState()); - assertEquals(actual.getErrorCode(), expected.getErrorCode()); + assertThat(actual.getMessage()).isEqualTo(expected.getMessage()); + assertThat(actual.getSQLState()).isEqualTo(expected.getSQLState()); + assertThat(actual.getErrorCode()).isEqualTo(expected.getErrorCode()); } private static void addWarnings(Set currentWarnings, SQLWarning newWarning) @@ -286,7 +283,7 @@ private static void assertWarnings(SQLWarning warning, Set current } int previousSize = currentWarnings.size(); addWarnings(currentWarnings, warning); - assertTrue(currentWarnings.size() >= previousSize); + assertThat(currentWarnings.size() >= previousSize).isTrue(); } private static void assertStartsWithExpectedWarnings(SQLWarning warning, SQLWarning expected) @@ -313,7 +310,7 @@ private static class WarningEntry public WarningEntry(Throwable throwable) { requireNonNull(throwable, "throwable is null"); - assertTrue(throwable instanceof SQLWarning); + assertThat(throwable instanceof SQLWarning).isTrue(); SQLWarning warning = (SQLWarning) throwable; this.vendorCode = warning.getErrorCode(); this.sqlState = requireNonNull(warning.getSQLState(), "SQLState is null"); diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestProgressMonitor.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestProgressMonitor.java index 47c57122670b..056728cb2dbe 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestProgressMonitor.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestProgressMonitor.java @@ -44,11 +44,9 @@ import static io.airlift.json.JsonCodec.jsonCodec; import static io.airlift.testing.Assertions.assertGreaterThanOrEqual; import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_METHOD) @Execution(SAME_THREAD) @@ -123,21 +121,21 @@ public void test() trinoStatement.setProgressMonitor(progressMonitor); try (ResultSet rs = statement.executeQuery("bogus query for testing")) { ResultSetMetaData metadata = rs.getMetaData(); - assertEquals(metadata.getColumnCount(), 1); - assertEquals(metadata.getColumnName(1), "_col0"); + assertThat(metadata.getColumnCount()).isEqualTo(1); + assertThat(metadata.getColumnName(1)).isEqualTo("_col0"); - assertTrue(rs.next()); - assertEquals(rs.getLong(1), 253161L); - assertEquals(rs.getLong("_col0"), 253161L); + assertThat(rs.next()).isTrue(); + assertThat(rs.getLong(1)).isEqualTo(253161L); + assertThat(rs.getLong("_col0")).isEqualTo(253161L); - assertFalse(rs.next()); + assertThat(rs.next()).isFalse(); } trinoStatement.clearProgressMonitor(); List queryStatsList = progressMonitor.finish(); assertGreaterThanOrEqual(queryStatsList.size(), 5); // duplicate stats is possible - assertEquals(queryStatsList.get(0).getState(), "QUEUED"); - assertEquals(queryStatsList.get(queryStatsList.size() - 1).getState(), "FINISHED"); + assertThat(queryStatsList.get(0).getState()).isEqualTo("QUEUED"); + assertThat(queryStatsList.get(queryStatsList.size() - 1).getState()).isEqualTo("FINISHED"); } } } diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDatabaseMetaData.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDatabaseMetaData.java index e53425f22da5..7ce476f211a2 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDatabaseMetaData.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDatabaseMetaData.java @@ -89,9 +89,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(SAME_THREAD) @@ -187,12 +184,16 @@ public void testPassEscapeInMetaDataQuery() Set queries = captureQueries(() -> { String schemaPattern = "defau" + metaData.getSearchStringEscape() + "_t"; try (ResultSet resultSet = metaData.getColumns("blackhole", schemaPattern, null, null)) { - assertFalse(resultSet.next(), "There should be no results"); + assertThat(resultSet.next()) + .describedAs("There should be no results") + .isFalse(); } return null; }); - assertEquals(queries.size(), 1, "Expected exactly one query, got " + queries.size()); + assertThat(queries.size()) + .describedAs("Expected exactly one query, got " + queries.size()) + .isEqualTo(1); String query = getOnlyElement(queries); assertContains(query, "_t' ESCAPE '", "Metadata query does not contain ESCAPE"); @@ -241,7 +242,7 @@ public void testGetUrl() { try (Connection connection = createConnection()) { DatabaseMetaData metaData = connection.getMetaData(); - assertEquals(metaData.getURL(), "jdbc:trino://" + server.getAddress()); + assertThat(metaData.getURL()).isEqualTo("jdbc:trino://" + server.getAddress()); } } @@ -251,10 +252,10 @@ public void testGetDatabaseProductVersion() { try (Connection connection = createConnection()) { DatabaseMetaData metaData = connection.getMetaData(); - assertEquals(metaData.getDatabaseProductName(), "Trino"); - assertEquals(metaData.getDatabaseProductVersion(), "testversion"); - assertEquals(metaData.getDatabaseMajorVersion(), 0); - assertEquals(metaData.getDatabaseMinorVersion(), 0); + assertThat(metaData.getDatabaseProductName()).isEqualTo("Trino"); + assertThat(metaData.getDatabaseProductVersion()).isEqualTo("testversion"); + assertThat(metaData.getDatabaseMajorVersion()).isEqualTo(0); + assertThat(metaData.getDatabaseMinorVersion()).isEqualTo(0); } } @@ -264,7 +265,7 @@ public void testGetUserName() { try (Connection connection = createConnection()) { DatabaseMetaData metaData = connection.getMetaData(); - assertEquals(metaData.getUserName(), "admin"); + assertThat(metaData.getUserName()).isEqualTo("admin"); } } @@ -278,9 +279,9 @@ public void testGetCatalogs() .isEqualTo(list(list("blackhole"), list("hive"), list(COUNTING_CATALOG), list("system"), list(TEST_CATALOG))); ResultSetMetaData metadata = rs.getMetaData(); - assertEquals(metadata.getColumnCount(), 1); - assertEquals(metadata.getColumnLabel(1), "TABLE_CAT"); - assertEquals(metadata.getColumnType(1), Types.VARCHAR); + assertThat(metadata.getColumnCount()).isEqualTo(1); + assertThat(metadata.getColumnLabel(1)).isEqualTo("TABLE_CAT"); + assertThat(metadata.getColumnType(1)).isEqualTo(Types.VARCHAR); } } } @@ -400,13 +401,13 @@ private static void assertGetSchemasResult(ResultSet rs, List> expe } ResultSetMetaData metadata = rs.getMetaData(); - assertEquals(metadata.getColumnCount(), 2); + assertThat(metadata.getColumnCount()).isEqualTo(2); - assertEquals(metadata.getColumnLabel(1), "TABLE_SCHEM"); - assertEquals(metadata.getColumnType(1), Types.VARCHAR); + assertThat(metadata.getColumnLabel(1)).isEqualTo("TABLE_SCHEM"); + assertThat(metadata.getColumnType(1)).isEqualTo(Types.VARCHAR); - assertEquals(metadata.getColumnLabel(2), "TABLE_CATALOG"); - assertEquals(metadata.getColumnType(2), Types.VARCHAR); + assertThat(metadata.getColumnLabel(2)).isEqualTo("TABLE_CATALOG"); + assertThat(metadata.getColumnType(2)).isEqualTo(Types.VARCHAR); } @Test @@ -607,37 +608,37 @@ private static void assertTableMetadata(ResultSet rs) throws SQLException { ResultSetMetaData metadata = rs.getMetaData(); - assertEquals(metadata.getColumnCount(), 10); + assertThat(metadata.getColumnCount()).isEqualTo(10); - assertEquals(metadata.getColumnLabel(1), "TABLE_CAT"); - assertEquals(metadata.getColumnType(1), Types.VARCHAR); + assertThat(metadata.getColumnLabel(1)).isEqualTo("TABLE_CAT"); + assertThat(metadata.getColumnType(1)).isEqualTo(Types.VARCHAR); - assertEquals(metadata.getColumnLabel(2), "TABLE_SCHEM"); - assertEquals(metadata.getColumnType(2), Types.VARCHAR); + assertThat(metadata.getColumnLabel(2)).isEqualTo("TABLE_SCHEM"); + assertThat(metadata.getColumnType(2)).isEqualTo(Types.VARCHAR); - assertEquals(metadata.getColumnLabel(3), "TABLE_NAME"); - assertEquals(metadata.getColumnType(3), Types.VARCHAR); + assertThat(metadata.getColumnLabel(3)).isEqualTo("TABLE_NAME"); + assertThat(metadata.getColumnType(3)).isEqualTo(Types.VARCHAR); - assertEquals(metadata.getColumnLabel(4), "TABLE_TYPE"); - assertEquals(metadata.getColumnType(4), Types.VARCHAR); + assertThat(metadata.getColumnLabel(4)).isEqualTo("TABLE_TYPE"); + assertThat(metadata.getColumnType(4)).isEqualTo(Types.VARCHAR); - assertEquals(metadata.getColumnLabel(5), "REMARKS"); - assertEquals(metadata.getColumnType(5), Types.VARCHAR); + assertThat(metadata.getColumnLabel(5)).isEqualTo("REMARKS"); + assertThat(metadata.getColumnType(5)).isEqualTo(Types.VARCHAR); - assertEquals(metadata.getColumnLabel(6), "TYPE_CAT"); - assertEquals(metadata.getColumnType(6), Types.VARCHAR); + assertThat(metadata.getColumnLabel(6)).isEqualTo("TYPE_CAT"); + assertThat(metadata.getColumnType(6)).isEqualTo(Types.VARCHAR); - assertEquals(metadata.getColumnLabel(7), "TYPE_SCHEM"); - assertEquals(metadata.getColumnType(7), Types.VARCHAR); + assertThat(metadata.getColumnLabel(7)).isEqualTo("TYPE_SCHEM"); + assertThat(metadata.getColumnType(7)).isEqualTo(Types.VARCHAR); - assertEquals(metadata.getColumnLabel(8), "TYPE_NAME"); - assertEquals(metadata.getColumnType(8), Types.VARCHAR); + assertThat(metadata.getColumnLabel(8)).isEqualTo("TYPE_NAME"); + assertThat(metadata.getColumnType(8)).isEqualTo(Types.VARCHAR); - assertEquals(metadata.getColumnLabel(9), "SELF_REFERENCING_COL_NAME"); - assertEquals(metadata.getColumnType(9), Types.VARCHAR); + assertThat(metadata.getColumnLabel(9)).isEqualTo("SELF_REFERENCING_COL_NAME"); + assertThat(metadata.getColumnType(9)).isEqualTo(Types.VARCHAR); - assertEquals(metadata.getColumnLabel(10), "REF_GENERATION"); - assertEquals(metadata.getColumnType(10), Types.VARCHAR); + assertThat(metadata.getColumnLabel(10)).isEqualTo("REF_GENERATION"); + assertThat(metadata.getColumnType(10)).isEqualTo(Types.VARCHAR); } @Test @@ -650,10 +651,10 @@ public void testGetTableTypes() .isEqualTo(list(list("TABLE"), list("VIEW"))); ResultSetMetaData metadata = tableTypes.getMetaData(); - assertEquals(metadata.getColumnCount(), 1); + assertThat(metadata.getColumnCount()).isEqualTo(1); - assertEquals(metadata.getColumnLabel(1), "TABLE_TYPE"); - assertEquals(metadata.getColumnType(1), Types.VARCHAR); + assertThat(metadata.getColumnLabel(1)).isEqualTo("TABLE_TYPE"); + assertThat(metadata.getColumnType(1)).isEqualTo(Types.VARCHAR); } } } @@ -665,30 +666,30 @@ public void testGetColumns() try (Connection connection = createConnection()) { try (ResultSet rs = connection.getMetaData().getColumns(null, null, "tables", "table_name")) { assertColumnMetadata(rs); - assertTrue(rs.next()); - assertEquals(rs.getString("TABLE_CAT"), "blackhole"); - assertEquals(rs.getString("TABLE_SCHEM"), "information_schema"); - assertEquals(rs.getString("TABLE_NAME"), "tables"); - assertEquals(rs.getString("COLUMN_NAME"), "table_name"); - assertEquals(rs.getLong("NULLABLE"), DatabaseMetaData.columnNullable); - assertEquals(rs.getString("IS_NULLABLE"), "YES"); - assertEquals(rs.getInt("DATA_TYPE"), Types.VARCHAR); - assertTrue(rs.next()); - assertEquals(rs.getString("TABLE_CAT"), "hive"); - assertEquals(rs.getString("TABLE_SCHEM"), "information_schema"); - assertTrue(rs.next()); - assertEquals(rs.getString("TABLE_CAT"), COUNTING_CATALOG); - assertEquals(rs.getString("TABLE_SCHEM"), "information_schema"); - assertTrue(rs.next()); - assertEquals(rs.getString("TABLE_CAT"), "system"); - assertEquals(rs.getString("TABLE_SCHEM"), "information_schema"); - assertTrue(rs.next()); - assertEquals(rs.getString("TABLE_CAT"), "system"); - assertEquals(rs.getString("TABLE_SCHEM"), "jdbc"); - assertTrue(rs.next()); - assertEquals(rs.getString("TABLE_CAT"), TEST_CATALOG); - assertEquals(rs.getString("TABLE_SCHEM"), "information_schema"); - assertFalse(rs.next()); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_CAT")).isEqualTo("blackhole"); + assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("information_schema"); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("tables"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("table_name"); + assertThat(rs.getLong("NULLABLE")).isEqualTo(DatabaseMetaData.columnNullable); + assertThat(rs.getString("IS_NULLABLE")).isEqualTo("YES"); + assertThat(rs.getInt("DATA_TYPE")).isEqualTo(Types.VARCHAR); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_CAT")).isEqualTo("hive"); + assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("information_schema"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_CAT")).isEqualTo(COUNTING_CATALOG); + assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("information_schema"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_CAT")).isEqualTo("system"); + assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("information_schema"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_CAT")).isEqualTo("system"); + assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("jdbc"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_CAT")).isEqualTo(TEST_CATALOG); + assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("information_schema"); + assertThat(rs.next()).isFalse(); } } @@ -730,26 +731,26 @@ public void testGetColumns() try (Connection connection = createConnection()) { try (ResultSet rs = connection.getMetaData().getColumns(TEST_CATALOG, "information_schema", "tables", "%m%")) { assertColumnMetadata(rs); - assertTrue(rs.next()); - assertEquals(rs.getString("COLUMN_NAME"), "table_schema"); - assertTrue(rs.next()); - assertEquals(rs.getString("COLUMN_NAME"), "table_name"); - assertFalse(rs.next()); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("table_schema"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("table_name"); + assertThat(rs.next()).isFalse(); } } try (Connection connection = createConnection()) { try (ResultSet rs = connection.getMetaData().getColumns(TEST_CATALOG, "tiny", "supplier", "suppkey")) { assertColumnMetadata(rs); - assertTrue(rs.next()); - assertEquals(rs.getLong("NULLABLE"), DatabaseMetaData.columnNoNulls); - assertEquals(rs.getString("IS_NULLABLE"), "NO"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getLong("NULLABLE")).isEqualTo(DatabaseMetaData.columnNoNulls); + assertThat(rs.getString("IS_NULLABLE")).isEqualTo("NO"); } } try (Connection connection = createConnection("blackhole", "blackhole"); Statement statement = connection.createStatement()) { - assertEquals(statement.executeUpdate( + assertThat(statement.executeUpdate( "CREATE TABLE test_get_columns_table (" + "c_boolean boolean, " + "c_bigint bigint, " + @@ -791,7 +792,7 @@ public void testGetColumns() "c_decimal_38_0 decimal(38,0), " + "c_array array, " + "c_color color" + - ")"), 0); + ")")).isEqualTo(0); try (ResultSet rs = connection.getMetaData().getColumns("blackhole", "blackhole", "test_get_columns_table", null)) { assertColumnMetadata(rs); @@ -835,7 +836,7 @@ public void testGetColumns() assertColumnSpec(rs, Types.DECIMAL, 38L, 10L, 0L, null, createDecimalType(38, 0)); assertColumnSpec(rs, Types.ARRAY, null, null, null, null, new ArrayType(BigintType.BIGINT)); assertColumnSpec(rs, Types.JAVA_OBJECT, null, null, null, null, ColorType.COLOR); - assertFalse(rs.next()); + assertThat(rs.next()).isFalse(); } } } @@ -844,92 +845,104 @@ private static void assertColumnSpec(ResultSet rs, int jdbcType, Long columnSize throws SQLException { String message = " of " + type.getDisplayName() + ": "; - assertTrue(rs.next()); - assertEquals(rs.getObject("TYPE_NAME"), type.getDisplayName(), "TYPE_NAME"); - assertEquals(rs.getObject("DATA_TYPE"), (long) jdbcType, "DATA_TYPE" + message); - assertEquals(rs.getObject("COLUMN_SIZE"), columnSize, "COLUMN_SIZE" + message); - assertEquals(rs.getObject("NUM_PREC_RADIX"), numPrecRadix, "NUM_PREC_RADIX" + message); - assertEquals(rs.getObject("DECIMAL_DIGITS"), decimalDigits, "DECIMAL_DIGITS" + message); - assertEquals(rs.getObject("CHAR_OCTET_LENGTH"), charOctetLength, "CHAR_OCTET_LENGTH" + message); + assertThat(rs.next()).isTrue(); + assertThat(rs.getObject("TYPE_NAME")) + .describedAs("TYPE_NAME") + .isEqualTo(type.getDisplayName()); + assertThat(rs.getObject("DATA_TYPE")) + .describedAs("DATA_TYPE" + message) + .isEqualTo((long) jdbcType); + assertThat(rs.getObject("COLUMN_SIZE")) + .describedAs("COLUMN_SIZE" + message) + .isEqualTo(columnSize); + assertThat(rs.getObject("NUM_PREC_RADIX")) + .describedAs("NUM_PREC_RADIX" + message) + .isEqualTo(numPrecRadix); + assertThat(rs.getObject("DECIMAL_DIGITS")) + .describedAs("DECIMAL_DIGITS" + message) + .isEqualTo(decimalDigits); + assertThat(rs.getObject("CHAR_OCTET_LENGTH")) + .describedAs("CHAR_OCTET_LENGTH" + message) + .isEqualTo(charOctetLength); } private static void assertColumnMetadata(ResultSet rs) throws SQLException { ResultSetMetaData metadata = rs.getMetaData(); - assertEquals(metadata.getColumnCount(), 24); + assertThat(metadata.getColumnCount()).isEqualTo(24); - assertEquals(metadata.getColumnLabel(1), "TABLE_CAT"); - assertEquals(metadata.getColumnType(1), Types.VARCHAR); + assertThat(metadata.getColumnLabel(1)).isEqualTo("TABLE_CAT"); + assertThat(metadata.getColumnType(1)).isEqualTo(Types.VARCHAR); - assertEquals(metadata.getColumnLabel(2), "TABLE_SCHEM"); - assertEquals(metadata.getColumnType(2), Types.VARCHAR); + assertThat(metadata.getColumnLabel(2)).isEqualTo("TABLE_SCHEM"); + assertThat(metadata.getColumnType(2)).isEqualTo(Types.VARCHAR); - assertEquals(metadata.getColumnLabel(3), "TABLE_NAME"); - assertEquals(metadata.getColumnType(3), Types.VARCHAR); + assertThat(metadata.getColumnLabel(3)).isEqualTo("TABLE_NAME"); + assertThat(metadata.getColumnType(3)).isEqualTo(Types.VARCHAR); - assertEquals(metadata.getColumnLabel(4), "COLUMN_NAME"); - assertEquals(metadata.getColumnType(4), Types.VARCHAR); + assertThat(metadata.getColumnLabel(4)).isEqualTo("COLUMN_NAME"); + assertThat(metadata.getColumnType(4)).isEqualTo(Types.VARCHAR); - assertEquals(metadata.getColumnLabel(5), "DATA_TYPE"); - assertEquals(metadata.getColumnType(5), Types.BIGINT); + assertThat(metadata.getColumnLabel(5)).isEqualTo("DATA_TYPE"); + assertThat(metadata.getColumnType(5)).isEqualTo(Types.BIGINT); - assertEquals(metadata.getColumnLabel(6), "TYPE_NAME"); - assertEquals(metadata.getColumnType(6), Types.VARCHAR); + assertThat(metadata.getColumnLabel(6)).isEqualTo("TYPE_NAME"); + assertThat(metadata.getColumnType(6)).isEqualTo(Types.VARCHAR); - assertEquals(metadata.getColumnLabel(7), "COLUMN_SIZE"); - assertEquals(metadata.getColumnType(7), Types.BIGINT); + assertThat(metadata.getColumnLabel(7)).isEqualTo("COLUMN_SIZE"); + assertThat(metadata.getColumnType(7)).isEqualTo(Types.BIGINT); - assertEquals(metadata.getColumnLabel(8), "BUFFER_LENGTH"); - assertEquals(metadata.getColumnType(8), Types.BIGINT); + assertThat(metadata.getColumnLabel(8)).isEqualTo("BUFFER_LENGTH"); + assertThat(metadata.getColumnType(8)).isEqualTo(Types.BIGINT); - assertEquals(metadata.getColumnLabel(9), "DECIMAL_DIGITS"); - assertEquals(metadata.getColumnType(9), Types.BIGINT); + assertThat(metadata.getColumnLabel(9)).isEqualTo("DECIMAL_DIGITS"); + assertThat(metadata.getColumnType(9)).isEqualTo(Types.BIGINT); - assertEquals(metadata.getColumnLabel(10), "NUM_PREC_RADIX"); - assertEquals(metadata.getColumnType(10), Types.BIGINT); + assertThat(metadata.getColumnLabel(10)).isEqualTo("NUM_PREC_RADIX"); + assertThat(metadata.getColumnType(10)).isEqualTo(Types.BIGINT); - assertEquals(metadata.getColumnLabel(11), "NULLABLE"); - assertEquals(metadata.getColumnType(11), Types.BIGINT); + assertThat(metadata.getColumnLabel(11)).isEqualTo("NULLABLE"); + assertThat(metadata.getColumnType(11)).isEqualTo(Types.BIGINT); - assertEquals(metadata.getColumnLabel(12), "REMARKS"); - assertEquals(metadata.getColumnType(12), Types.VARCHAR); + assertThat(metadata.getColumnLabel(12)).isEqualTo("REMARKS"); + assertThat(metadata.getColumnType(12)).isEqualTo(Types.VARCHAR); - assertEquals(metadata.getColumnLabel(13), "COLUMN_DEF"); - assertEquals(metadata.getColumnType(13), Types.VARCHAR); + assertThat(metadata.getColumnLabel(13)).isEqualTo("COLUMN_DEF"); + assertThat(metadata.getColumnType(13)).isEqualTo(Types.VARCHAR); - assertEquals(metadata.getColumnLabel(14), "SQL_DATA_TYPE"); - assertEquals(metadata.getColumnType(14), Types.BIGINT); + assertThat(metadata.getColumnLabel(14)).isEqualTo("SQL_DATA_TYPE"); + assertThat(metadata.getColumnType(14)).isEqualTo(Types.BIGINT); - assertEquals(metadata.getColumnLabel(15), "SQL_DATETIME_SUB"); - assertEquals(metadata.getColumnType(15), Types.BIGINT); + assertThat(metadata.getColumnLabel(15)).isEqualTo("SQL_DATETIME_SUB"); + assertThat(metadata.getColumnType(15)).isEqualTo(Types.BIGINT); - assertEquals(metadata.getColumnLabel(16), "CHAR_OCTET_LENGTH"); - assertEquals(metadata.getColumnType(16), Types.BIGINT); + assertThat(metadata.getColumnLabel(16)).isEqualTo("CHAR_OCTET_LENGTH"); + assertThat(metadata.getColumnType(16)).isEqualTo(Types.BIGINT); - assertEquals(metadata.getColumnLabel(17), "ORDINAL_POSITION"); - assertEquals(metadata.getColumnType(17), Types.BIGINT); + assertThat(metadata.getColumnLabel(17)).isEqualTo("ORDINAL_POSITION"); + assertThat(metadata.getColumnType(17)).isEqualTo(Types.BIGINT); - assertEquals(metadata.getColumnLabel(18), "IS_NULLABLE"); - assertEquals(metadata.getColumnType(18), Types.VARCHAR); + assertThat(metadata.getColumnLabel(18)).isEqualTo("IS_NULLABLE"); + assertThat(metadata.getColumnType(18)).isEqualTo(Types.VARCHAR); - assertEquals(metadata.getColumnLabel(19), "SCOPE_CATALOG"); - assertEquals(metadata.getColumnType(19), Types.VARCHAR); + assertThat(metadata.getColumnLabel(19)).isEqualTo("SCOPE_CATALOG"); + assertThat(metadata.getColumnType(19)).isEqualTo(Types.VARCHAR); - assertEquals(metadata.getColumnLabel(20), "SCOPE_SCHEMA"); - assertEquals(metadata.getColumnType(20), Types.VARCHAR); + assertThat(metadata.getColumnLabel(20)).isEqualTo("SCOPE_SCHEMA"); + assertThat(metadata.getColumnType(20)).isEqualTo(Types.VARCHAR); - assertEquals(metadata.getColumnLabel(21), "SCOPE_TABLE"); - assertEquals(metadata.getColumnType(21), Types.VARCHAR); + assertThat(metadata.getColumnLabel(21)).isEqualTo("SCOPE_TABLE"); + assertThat(metadata.getColumnType(21)).isEqualTo(Types.VARCHAR); - assertEquals(metadata.getColumnLabel(22), "SOURCE_DATA_TYPE"); - assertEquals(metadata.getColumnType(22), Types.BIGINT); + assertThat(metadata.getColumnLabel(22)).isEqualTo("SOURCE_DATA_TYPE"); + assertThat(metadata.getColumnType(22)).isEqualTo(Types.BIGINT); - assertEquals(metadata.getColumnLabel(23), "IS_AUTOINCREMENT"); - assertEquals(metadata.getColumnType(23), Types.VARCHAR); + assertThat(metadata.getColumnLabel(23)).isEqualTo("IS_AUTOINCREMENT"); + assertThat(metadata.getColumnType(23)).isEqualTo(Types.VARCHAR); - assertEquals(metadata.getColumnLabel(24), "IS_GENERATEDCOLUMN"); - assertEquals(metadata.getColumnType(24), Types.VARCHAR); + assertThat(metadata.getColumnLabel(24)).isEqualTo("IS_GENERATEDCOLUMN"); + assertThat(metadata.getColumnType(24)).isEqualTo(Types.VARCHAR); } @Test @@ -938,7 +951,7 @@ public void testGetPseudoColumns() { try (Connection connection = createConnection()) { try (ResultSet rs = connection.getMetaData().getPseudoColumns(null, null, null, null)) { - assertFalse(rs.next()); + assertThat(rs.next()).isFalse(); } } } @@ -949,7 +962,7 @@ public void testGetProcedures() { try (Connection connection = createConnection()) { try (ResultSet rs = connection.getMetaData().getProcedures(null, null, null)) { - assertFalse(rs.next()); + assertThat(rs.next()).isFalse(); } } } @@ -960,7 +973,7 @@ public void testGetProcedureColumns() { try (Connection connection = createConnection()) { try (ResultSet rs = connection.getMetaData().getProcedureColumns(null, null, null, null)) { - assertFalse(rs.next()); + assertThat(rs.next()).isFalse(); } } } @@ -971,7 +984,7 @@ public void testGetSuperTables() { try (Connection connection = createConnection()) { try (ResultSet rs = connection.getMetaData().getSuperTables(null, null, null)) { - assertFalse(rs.next()); + assertThat(rs.next()).isFalse(); } } } @@ -982,7 +995,7 @@ public void testGetUdts() { try (Connection connection = createConnection()) { try (ResultSet rs = connection.getMetaData().getUDTs(null, null, null, null)) { - assertFalse(rs.next()); + assertThat(rs.next()).isFalse(); } } } @@ -993,7 +1006,7 @@ public void testGetAttributes() { try (Connection connection = createConnection()) { try (ResultSet rs = connection.getMetaData().getAttributes(null, null, null, null)) { - assertFalse(rs.next()); + assertThat(rs.next()).isFalse(); } } } @@ -1004,7 +1017,7 @@ public void testGetSuperTypes() { try (Connection connection = createConnection()) { try (ResultSet rs = connection.getMetaData().getSuperTypes(null, null, null)) { - assertFalse(rs.next()); + assertThat(rs.next()).isFalse(); } } } @@ -1613,25 +1626,25 @@ public void testFailedBothEscapeLiteralParameters() @Test public void testEscapeIfNecessary() { - assertEquals(TrinoDatabaseMetaData.escapeIfNecessary(false, false, null), null); - assertEquals(TrinoDatabaseMetaData.escapeIfNecessary(false, false, "a"), "a"); - assertEquals(TrinoDatabaseMetaData.escapeIfNecessary(false, false, "abc_def"), "abc_def"); - assertEquals(TrinoDatabaseMetaData.escapeIfNecessary(false, false, "abc__de_f"), "abc__de_f"); - assertEquals(TrinoDatabaseMetaData.escapeIfNecessary(false, false, "abc%def"), "abc%def"); - assertEquals(TrinoDatabaseMetaData.escapeIfNecessary(false, false, "abc\\_def"), "abc\\_def"); - - assertEquals(TrinoDatabaseMetaData.escapeIfNecessary(true, false, null), null); - assertEquals(TrinoDatabaseMetaData.escapeIfNecessary(true, false, "a"), "a"); - assertEquals(TrinoDatabaseMetaData.escapeIfNecessary(true, false, "abc_def"), "abc\\_def"); - assertEquals(TrinoDatabaseMetaData.escapeIfNecessary(true, false, "abc__de_f"), "abc\\_\\_de\\_f"); - assertEquals(TrinoDatabaseMetaData.escapeIfNecessary(true, false, "abc%def"), "abc\\%def"); - assertEquals(TrinoDatabaseMetaData.escapeIfNecessary(true, false, "abc\\_def"), "abc\\\\\\_def"); - - assertEquals(TrinoDatabaseMetaData.escapeIfNecessary(false, true, null), null); - assertEquals(TrinoDatabaseMetaData.escapeIfNecessary(false, true, "a"), "a"); - assertEquals(TrinoDatabaseMetaData.escapeIfNecessary(false, true, "abc_def"), "abc\\_def"); - assertEquals(TrinoDatabaseMetaData.escapeIfNecessary(false, true, "abc__de_f"), "abc\\_\\_de\\_f"); - assertEquals(TrinoDatabaseMetaData.escapeIfNecessary(false, true, "abc\\_def"), "abc\\\\\\_def"); + assertThat(TrinoDatabaseMetaData.escapeIfNecessary(false, false, null)).isEqualTo(null); + assertThat(TrinoDatabaseMetaData.escapeIfNecessary(false, false, "a")).isEqualTo("a"); + assertThat(TrinoDatabaseMetaData.escapeIfNecessary(false, false, "abc_def")).isEqualTo("abc_def"); + assertThat(TrinoDatabaseMetaData.escapeIfNecessary(false, false, "abc__de_f")).isEqualTo("abc__de_f"); + assertThat(TrinoDatabaseMetaData.escapeIfNecessary(false, false, "abc%def")).isEqualTo("abc%def"); + assertThat(TrinoDatabaseMetaData.escapeIfNecessary(false, false, "abc\\_def")).isEqualTo("abc\\_def"); + + assertThat(TrinoDatabaseMetaData.escapeIfNecessary(true, false, null)).isEqualTo(null); + assertThat(TrinoDatabaseMetaData.escapeIfNecessary(true, false, "a")).isEqualTo("a"); + assertThat(TrinoDatabaseMetaData.escapeIfNecessary(true, false, "abc_def")).isEqualTo("abc\\_def"); + assertThat(TrinoDatabaseMetaData.escapeIfNecessary(true, false, "abc__de_f")).isEqualTo("abc\\_\\_de\\_f"); + assertThat(TrinoDatabaseMetaData.escapeIfNecessary(true, false, "abc%def")).isEqualTo("abc\\%def"); + assertThat(TrinoDatabaseMetaData.escapeIfNecessary(true, false, "abc\\_def")).isEqualTo("abc\\\\\\_def"); + + assertThat(TrinoDatabaseMetaData.escapeIfNecessary(false, true, null)).isEqualTo(null); + assertThat(TrinoDatabaseMetaData.escapeIfNecessary(false, true, "a")).isEqualTo("a"); + assertThat(TrinoDatabaseMetaData.escapeIfNecessary(false, true, "abc_def")).isEqualTo("abc\\_def"); + assertThat(TrinoDatabaseMetaData.escapeIfNecessary(false, true, "abc__de_f")).isEqualTo("abc\\_\\_de\\_f"); + assertThat(TrinoDatabaseMetaData.escapeIfNecessary(false, true, "abc\\_def")).isEqualTo("abc\\\\\\_def"); } @Test @@ -1666,24 +1679,60 @@ private static void assertColumnSpec(ResultSet rs, int dataType, Long precision, throws SQLException { String message = " of " + typeName + ": "; - assertEquals(rs.getObject("TYPE_NAME"), typeName, "TYPE_NAME" + message); - assertEquals(rs.getObject("DATA_TYPE"), (long) dataType, "DATA_TYPE" + message); - assertEquals(rs.getObject("PRECISION"), precision, "PRECISION" + message); - assertEquals(rs.getObject("LITERAL_PREFIX"), null, "LITERAL_PREFIX" + message); - assertEquals(rs.getObject("LITERAL_SUFFIX"), null, "LITERAL_SUFFIX" + message); - assertEquals(rs.getObject("CREATE_PARAMS"), null, "CREATE_PARAMS" + message); - assertEquals(rs.getObject("NULLABLE"), (long) DatabaseMetaData.typeNullable, "NULLABLE" + message); - assertEquals(rs.getObject("CASE_SENSITIVE"), false, "CASE_SENSITIVE" + message); - assertEquals(rs.getObject("SEARCHABLE"), (long) DatabaseMetaData.typeSearchable, "SEARCHABLE" + message); - assertEquals(rs.getObject("UNSIGNED_ATTRIBUTE"), null, "UNSIGNED_ATTRIBUTE" + message); - assertEquals(rs.getObject("FIXED_PREC_SCALE"), false, "FIXED_PREC_SCALE" + message); - assertEquals(rs.getObject("AUTO_INCREMENT"), null, "AUTO_INCREMENT" + message); - assertEquals(rs.getObject("LOCAL_TYPE_NAME"), null, "LOCAL_TYPE_NAME" + message); - assertEquals(rs.getObject("MINIMUM_SCALE"), 0L, "MINIMUM_SCALE" + message); - assertEquals(rs.getObject("MAXIMUM_SCALE"), 0L, "MAXIMUM_SCALE" + message); - assertEquals(rs.getObject("SQL_DATA_TYPE"), null, "SQL_DATA_TYPE" + message); - assertEquals(rs.getObject("SQL_DATETIME_SUB"), null, "SQL_DATETIME_SUB" + message); - assertEquals(rs.getObject("NUM_PREC_RADIX"), numPrecRadix, "NUM_PREC_RADIX" + message); + assertThat(rs.getObject("TYPE_NAME")) + .describedAs("TYPE_NAME" + message) + .isEqualTo(typeName); + assertThat(rs.getObject("DATA_TYPE")) + .describedAs("DATA_TYPE" + message) + .isEqualTo((long) dataType); + assertThat(rs.getObject("PRECISION")) + .describedAs("PRECISION" + message) + .isEqualTo(precision); + assertThat(rs.getObject("LITERAL_PREFIX")) + .describedAs("LITERAL_PREFIX" + message) + .isEqualTo(null); + assertThat(rs.getObject("LITERAL_SUFFIX")) + .describedAs("LITERAL_SUFFIX" + message) + .isEqualTo(null); + assertThat(rs.getObject("CREATE_PARAMS")) + .describedAs("CREATE_PARAMS" + message) + .isEqualTo(null); + assertThat(rs.getObject("NULLABLE")) + .describedAs("NULLABLE" + message) + .isEqualTo((long) DatabaseMetaData.typeNullable); + assertThat(rs.getObject("CASE_SENSITIVE")) + .describedAs("CASE_SENSITIVE" + message) + .isEqualTo(false); + assertThat(rs.getObject("SEARCHABLE")) + .describedAs("SEARCHABLE" + message) + .isEqualTo((long) DatabaseMetaData.typeSearchable); + assertThat(rs.getObject("UNSIGNED_ATTRIBUTE")) + .describedAs("UNSIGNED_ATTRIBUTE" + message) + .isEqualTo(null); + assertThat(rs.getObject("FIXED_PREC_SCALE")) + .describedAs("FIXED_PREC_SCALE" + message) + .isEqualTo(false); + assertThat(rs.getObject("AUTO_INCREMENT")) + .describedAs("AUTO_INCREMENT" + message) + .isEqualTo(null); + assertThat(rs.getObject("LOCAL_TYPE_NAME")) + .describedAs("LOCAL_TYPE_NAME" + message) + .isEqualTo(null); + assertThat(rs.getObject("MINIMUM_SCALE")) + .describedAs("MINIMUM_SCALE" + message) + .isEqualTo(0L); + assertThat(rs.getObject("MAXIMUM_SCALE")) + .describedAs("MAXIMUM_SCALE" + message) + .isEqualTo(0L); + assertThat(rs.getObject("SQL_DATA_TYPE")) + .describedAs("SQL_DATA_TYPE" + message) + .isEqualTo(null); + assertThat(rs.getObject("SQL_DATETIME_SUB")) + .describedAs("SQL_DATETIME_SUB" + message) + .isEqualTo(null); + assertThat(rs.getObject("NUM_PREC_RADIX")) + .describedAs("NUM_PREC_RADIX" + message) + .isEqualTo(numPrecRadix); } private Set captureQueries(Callable action) diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriver.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriver.java index d6a65296573a..d70c32d80205 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriver.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriver.java @@ -82,14 +82,9 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Fail.fail; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -121,16 +116,16 @@ private void setupTestTables() { try (Connection connection = createConnection("blackhole", "blackhole"); Statement statement = connection.createStatement()) { - assertEquals(statement.executeUpdate("CREATE SCHEMA blackhole.blackhole"), 0); - assertEquals(statement.executeUpdate("CREATE TABLE test_table (x bigint)"), 0); + assertThat(statement.executeUpdate("CREATE SCHEMA blackhole.blackhole")).isEqualTo(0); + assertThat(statement.executeUpdate("CREATE TABLE test_table (x bigint)")).isEqualTo(0); - assertEquals(statement.executeUpdate("CREATE TABLE slow_test_table (x bigint) " + + assertThat(statement.executeUpdate("CREATE TABLE slow_test_table (x bigint) " + "WITH (" + " split_count = 1, " + " pages_per_split = 1, " + " rows_per_page = 1, " + " page_processing_delay = '1m'" + - ")"), 0); + ")")).isEqualTo(0); } } @@ -164,103 +159,103 @@ public void testDriverManager() ", cast('foo' as char(5)) _char")) { ResultSetMetaData metadata = rs.getMetaData(); - assertEquals(metadata.getColumnCount(), 10); + assertThat(metadata.getColumnCount()).isEqualTo(10); - assertEquals(metadata.getColumnLabel(1), "_integer"); - assertEquals(metadata.getColumnType(1), Types.INTEGER); + assertThat(metadata.getColumnLabel(1)).isEqualTo("_integer"); + assertThat(metadata.getColumnType(1)).isEqualTo(Types.INTEGER); - assertEquals(metadata.getColumnLabel(2), "_bigint"); - assertEquals(metadata.getColumnType(2), Types.BIGINT); + assertThat(metadata.getColumnLabel(2)).isEqualTo("_bigint"); + assertThat(metadata.getColumnType(2)).isEqualTo(Types.BIGINT); - assertEquals(metadata.getColumnLabel(3), "_varchar"); - assertEquals(metadata.getColumnType(3), Types.VARCHAR); + assertThat(metadata.getColumnLabel(3)).isEqualTo("_varchar"); + assertThat(metadata.getColumnType(3)).isEqualTo(Types.VARCHAR); - assertEquals(metadata.getColumnLabel(4), "_double"); - assertEquals(metadata.getColumnType(4), Types.DOUBLE); + assertThat(metadata.getColumnLabel(4)).isEqualTo("_double"); + assertThat(metadata.getColumnType(4)).isEqualTo(Types.DOUBLE); - assertEquals(metadata.getColumnLabel(5), "_boolean"); - assertEquals(metadata.getColumnType(5), Types.BOOLEAN); + assertThat(metadata.getColumnLabel(5)).isEqualTo("_boolean"); + assertThat(metadata.getColumnType(5)).isEqualTo(Types.BOOLEAN); - assertEquals(metadata.getColumnLabel(6), "_varbinary"); - assertEquals(metadata.getColumnType(6), Types.VARBINARY); + assertThat(metadata.getColumnLabel(6)).isEqualTo("_varbinary"); + assertThat(metadata.getColumnType(6)).isEqualTo(Types.VARBINARY); - assertEquals(metadata.getColumnLabel(7), "_decimal_short"); - assertEquals(metadata.getColumnType(7), Types.DECIMAL); + assertThat(metadata.getColumnLabel(7)).isEqualTo("_decimal_short"); + assertThat(metadata.getColumnType(7)).isEqualTo(Types.DECIMAL); - assertEquals(metadata.getColumnLabel(8), "_decimal_long"); - assertEquals(metadata.getColumnType(8), Types.DECIMAL); + assertThat(metadata.getColumnLabel(8)).isEqualTo("_decimal_long"); + assertThat(metadata.getColumnType(8)).isEqualTo(Types.DECIMAL); - assertEquals(metadata.getColumnLabel(9), "_hll"); - assertEquals(metadata.getColumnType(9), Types.JAVA_OBJECT); + assertThat(metadata.getColumnLabel(9)).isEqualTo("_hll"); + assertThat(metadata.getColumnType(9)).isEqualTo(Types.JAVA_OBJECT); - assertEquals(metadata.getColumnLabel(10), "_char"); - assertEquals(metadata.getColumnType(10), Types.CHAR); + assertThat(metadata.getColumnLabel(10)).isEqualTo("_char"); + assertThat(metadata.getColumnType(10)).isEqualTo(Types.CHAR); - assertTrue(rs.next()); + assertThat(rs.next()).isTrue(); - assertEquals(rs.getObject(1), 123); - assertEquals(rs.getObject("_integer"), 123); - assertEquals(rs.getInt(1), 123); - assertEquals(rs.getInt("_integer"), 123); - assertEquals(rs.getLong(1), 123L); - assertEquals(rs.getLong("_integer"), 123L); + assertThat(rs.getObject(1)).isEqualTo(123); + assertThat(rs.getObject("_integer")).isEqualTo(123); + assertThat(rs.getInt(1)).isEqualTo(123); + assertThat(rs.getInt("_integer")).isEqualTo(123); + assertThat(rs.getLong(1)).isEqualTo(123L); + assertThat(rs.getLong("_integer")).isEqualTo(123L); - assertEquals(rs.getObject(2), 12300000000L); - assertEquals(rs.getObject("_bigint"), 12300000000L); - assertEquals(rs.getLong(2), 12300000000L); - assertEquals(rs.getLong("_bigint"), 12300000000L); + assertThat(rs.getObject(2)).isEqualTo(12300000000L); + assertThat(rs.getObject("_bigint")).isEqualTo(12300000000L); + assertThat(rs.getLong(2)).isEqualTo(12300000000L); + assertThat(rs.getLong("_bigint")).isEqualTo(12300000000L); - assertEquals(rs.getObject(3), "foo"); - assertEquals(rs.getObject("_varchar"), "foo"); - assertEquals(rs.getString(3), "foo"); - assertEquals(rs.getString("_varchar"), "foo"); + assertThat(rs.getObject(3)).isEqualTo("foo"); + assertThat(rs.getObject("_varchar")).isEqualTo("foo"); + assertThat(rs.getString(3)).isEqualTo("foo"); + assertThat(rs.getString("_varchar")).isEqualTo("foo"); - assertEquals(rs.getObject(4), 0.1); - assertEquals(rs.getObject("_double"), 0.1); - assertEquals(rs.getDouble(4), 0.1); - assertEquals(rs.getDouble("_double"), 0.1); + assertThat(rs.getObject(4)).isEqualTo(0.1); + assertThat(rs.getObject("_double")).isEqualTo(0.1); + assertThat(rs.getDouble(4)).isEqualTo(0.1); + assertThat(rs.getDouble("_double")).isEqualTo(0.1); - assertEquals(rs.getObject(5), true); - assertEquals(rs.getObject("_boolean"), true); - assertEquals(rs.getBoolean(5), true); - assertEquals(rs.getBoolean("_boolean"), true); - assertEquals(rs.getByte("_boolean"), 1); - assertEquals(rs.getShort("_boolean"), 1); - assertEquals(rs.getInt("_boolean"), 1); - assertEquals(rs.getLong("_boolean"), 1L); - assertEquals(rs.getFloat("_boolean"), 1.0f); - assertEquals(rs.getDouble("_boolean"), 1.0); + assertThat(rs.getObject(5)).isEqualTo(true); + assertThat(rs.getObject("_boolean")).isEqualTo(true); + assertThat(rs.getBoolean(5)).isEqualTo(true); + assertThat(rs.getBoolean("_boolean")).isEqualTo(true); + assertThat(rs.getByte("_boolean")).isEqualTo((byte) 1); + assertThat(rs.getShort("_boolean")).isEqualTo((short) 1); + assertThat(rs.getInt("_boolean")).isEqualTo(1); + assertThat(rs.getLong("_boolean")).isEqualTo(1L); + assertThat(rs.getFloat("_boolean")).isEqualTo(1.0f); + assertThat(rs.getDouble("_boolean")).isEqualTo(1.0); - assertEquals(rs.getObject(6), "hello".getBytes(UTF_8)); - assertEquals(rs.getObject("_varbinary"), "hello".getBytes(UTF_8)); - assertEquals(rs.getBytes(6), "hello".getBytes(UTF_8)); - assertEquals(rs.getBytes("_varbinary"), "hello".getBytes(UTF_8)); + assertThat(rs.getObject(6)).isEqualTo("hello".getBytes(UTF_8)); + assertThat(rs.getObject("_varbinary")).isEqualTo("hello".getBytes(UTF_8)); + assertThat(rs.getBytes(6)).isEqualTo("hello".getBytes(UTF_8)); + assertThat(rs.getBytes("_varbinary")).isEqualTo("hello".getBytes(UTF_8)); - assertEquals(rs.getObject(7), new BigDecimal("1234567890.1234567")); - assertEquals(rs.getObject("_decimal_short"), new BigDecimal("1234567890.1234567")); - assertEquals(rs.getBigDecimal(7), new BigDecimal("1234567890.1234567")); - assertEquals(rs.getBigDecimal("_decimal_short"), new BigDecimal("1234567890.1234567")); - assertEquals(rs.getBigDecimal(7, 1), new BigDecimal("1234567890.1")); - assertEquals(rs.getBigDecimal("_decimal_short", 1), new BigDecimal("1234567890.1")); + assertThat(rs.getObject(7)).isEqualTo(new BigDecimal("1234567890.1234567")); + assertThat(rs.getObject("_decimal_short")).isEqualTo(new BigDecimal("1234567890.1234567")); + assertThat(rs.getBigDecimal(7)).isEqualTo(new BigDecimal("1234567890.1234567")); + assertThat(rs.getBigDecimal("_decimal_short")).isEqualTo(new BigDecimal("1234567890.1234567")); + assertThat(rs.getBigDecimal(7, 1)).isEqualTo(new BigDecimal("1234567890.1")); + assertThat(rs.getBigDecimal("_decimal_short", 1)).isEqualTo(new BigDecimal("1234567890.1")); - assertEquals(rs.getObject(8), new BigDecimal(".12345678901234567890123456789012345678")); - assertEquals(rs.getObject("_decimal_long"), new BigDecimal(".12345678901234567890123456789012345678")); - assertEquals(rs.getBigDecimal(8), new BigDecimal(".12345678901234567890123456789012345678")); - assertEquals(rs.getBigDecimal("_decimal_long"), new BigDecimal(".12345678901234567890123456789012345678")); - assertEquals(rs.getBigDecimal(8, 6), new BigDecimal(".123457")); - assertEquals(rs.getBigDecimal("_decimal_long", 6), new BigDecimal(".123457")); + assertThat(rs.getObject(8)).isEqualTo(new BigDecimal(".12345678901234567890123456789012345678")); + assertThat(rs.getObject("_decimal_long")).isEqualTo(new BigDecimal(".12345678901234567890123456789012345678")); + assertThat(rs.getBigDecimal(8)).isEqualTo(new BigDecimal(".12345678901234567890123456789012345678")); + assertThat(rs.getBigDecimal("_decimal_long")).isEqualTo(new BigDecimal(".12345678901234567890123456789012345678")); + assertThat(rs.getBigDecimal(8, 6)).isEqualTo(new BigDecimal(".123457")); + assertThat(rs.getBigDecimal("_decimal_long", 6)).isEqualTo(new BigDecimal(".123457")); assertInstanceOf(rs.getObject(9), byte[].class); assertInstanceOf(rs.getObject("_hll"), byte[].class); assertInstanceOf(rs.getBytes(9), byte[].class); assertInstanceOf(rs.getBytes("_hll"), byte[].class); - assertEquals(rs.getObject(10), "foo "); - assertEquals(rs.getObject("_char"), "foo "); - assertEquals(rs.getString(10), "foo "); - assertEquals(rs.getString("_char"), "foo "); + assertThat(rs.getObject(10)).isEqualTo("foo "); + assertThat(rs.getObject("_char")).isEqualTo("foo "); + assertThat(rs.getString(10)).isEqualTo("foo "); + assertThat(rs.getString("_char")).isEqualTo("foo "); - assertFalse(rs.next()); + assertThat(rs.next()).isFalse(); } } } @@ -285,76 +280,76 @@ public void testTypes() ", REAL '123.45' as j" + ", REAL 'Infinity' as k" + "")) { - assertTrue(rs.next()); - - assertEquals(rs.getTime(1), new Time(new DateTime(1970, 1, 1, 3, 4, 5).getMillis())); - assertEquals(rs.getTime(1, ASIA_ORAL_CALENDAR), new Time(new DateTime(1970, 1, 1, 3, 4, 5, ASIA_ORAL_ZONE).getMillis())); - assertEquals(rs.getObject(1), new Time(new DateTime(1970, 1, 1, 3, 4, 5).getMillis())); - assertEquals(rs.getTime("a"), new Time(new DateTime(1970, 1, 1, 3, 4, 5).getMillis())); - assertEquals(rs.getTime("a", ASIA_ORAL_CALENDAR), new Time(new DateTime(1970, 1, 1, 3, 4, 5, ASIA_ORAL_ZONE).getMillis())); - assertEquals(rs.getObject("a"), new Time(new DateTime(1970, 1, 1, 3, 4, 5).getMillis())); - - assertEquals(rs.getTime(2), new Time(new DateTime(1970, 1, 1, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); - assertEquals(rs.getTime(2, ASIA_ORAL_CALENDAR), new Time(new DateTime(1970, 1, 1, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); - assertEquals(rs.getObject(2), new Time(new DateTime(1970, 1, 1, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); - assertEquals(rs.getTime("b"), new Time(new DateTime(1970, 1, 1, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); - assertEquals(rs.getTime("b", ASIA_ORAL_CALENDAR), new Time(new DateTime(1970, 1, 1, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); - assertEquals(rs.getObject("b"), new Time(new DateTime(1970, 1, 1, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); - - assertEquals(rs.getTime(3), new Time(new DateTime(1970, 1, 1, 9, 10, 11, DateTimeZone.forOffsetHoursMinutes(2, 0)).getMillis())); - assertEquals(rs.getTime(3, ASIA_ORAL_CALENDAR), new Time(new DateTime(1970, 1, 1, 9, 10, 11, DateTimeZone.forOffsetHoursMinutes(2, 0)).getMillis())); - assertEquals(rs.getObject(3), new Time(new DateTime(1970, 1, 1, 9, 10, 11, DateTimeZone.forOffsetHoursMinutes(2, 0)).getMillis())); - assertEquals(rs.getTime("c"), new Time(new DateTime(1970, 1, 1, 9, 10, 11, DateTimeZone.forOffsetHoursMinutes(2, 0)).getMillis())); - assertEquals(rs.getTime("c", ASIA_ORAL_CALENDAR), new Time(new DateTime(1970, 1, 1, 9, 10, 11, DateTimeZone.forOffsetHoursMinutes(2, 0)).getMillis())); - assertEquals(rs.getObject("c"), new Time(new DateTime(1970, 1, 1, 9, 10, 11, DateTimeZone.forOffsetHoursMinutes(2, 0)).getMillis())); - - assertEquals(rs.getTimestamp(4), new Timestamp(new DateTime(2001, 2, 3, 3, 4, 5).getMillis())); - assertEquals(rs.getTimestamp(4, ASIA_ORAL_CALENDAR), new Timestamp(new DateTime(2001, 2, 3, 3, 4, 5, ASIA_ORAL_ZONE).getMillis())); - assertEquals(rs.getObject(4), new Timestamp(new DateTime(2001, 2, 3, 3, 4, 5).getMillis())); - assertEquals(rs.getTimestamp("d"), new Timestamp(new DateTime(2001, 2, 3, 3, 4, 5).getMillis())); - assertEquals(rs.getTimestamp("d", ASIA_ORAL_CALENDAR), new Timestamp(new DateTime(2001, 2, 3, 3, 4, 5, ASIA_ORAL_ZONE).getMillis())); - assertEquals(rs.getObject("d"), new Timestamp(new DateTime(2001, 2, 3, 3, 4, 5).getMillis())); - - assertEquals(rs.getTimestamp(5), new Timestamp(new DateTime(2004, 5, 6, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); - assertEquals(rs.getTimestamp(5, ASIA_ORAL_CALENDAR), new Timestamp(new DateTime(2004, 5, 6, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); - assertEquals(rs.getObject(5), new Timestamp(new DateTime(2004, 5, 6, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); - assertEquals(rs.getObject(5, ZonedDateTime.class), ZonedDateTime.of(2004, 5, 6, 6, 7, 8, 0, ZoneOffset.ofHoursMinutes(6, 17))); - assertEquals(rs.getTimestamp("e"), new Timestamp(new DateTime(2004, 5, 6, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); - assertEquals(rs.getTimestamp("e", ASIA_ORAL_CALENDAR), new Timestamp(new DateTime(2004, 5, 6, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); - assertEquals(rs.getObject("e"), new Timestamp(new DateTime(2004, 5, 6, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); - assertEquals(rs.getObject("e", ZonedDateTime.class), ZonedDateTime.of(2004, 5, 6, 6, 7, 8, 0, ZoneOffset.ofHoursMinutes(6, 17))); - - assertEquals(rs.getTimestamp(6), new Timestamp(new DateTime(2007, 8, 9, 9, 10, 11, DateTimeZone.forID("Europe/Berlin")).getMillis())); - assertEquals(rs.getTimestamp(6, ASIA_ORAL_CALENDAR), new Timestamp(new DateTime(2007, 8, 9, 9, 10, 11, DateTimeZone.forID("Europe/Berlin")).getMillis())); - assertEquals(rs.getObject(6), new Timestamp(new DateTime(2007, 8, 9, 9, 10, 11, DateTimeZone.forID("Europe/Berlin")).getMillis())); - assertEquals(rs.getObject(6, ZonedDateTime.class), ZonedDateTime.of(2007, 8, 9, 9, 10, 11, 0, ZoneId.of("Europe/Berlin"))); - assertEquals(rs.getTimestamp("f"), new Timestamp(new DateTime(2007, 8, 9, 9, 10, 11, DateTimeZone.forID("Europe/Berlin")).getMillis())); - assertEquals(rs.getTimestamp("f", ASIA_ORAL_CALENDAR), new Timestamp(new DateTime(2007, 8, 9, 9, 10, 11, DateTimeZone.forID("Europe/Berlin")).getMillis())); - assertEquals(rs.getObject("f"), new Timestamp(new DateTime(2007, 8, 9, 9, 10, 11, DateTimeZone.forID("Europe/Berlin")).getMillis())); - assertEquals(rs.getObject("f", ZonedDateTime.class), ZonedDateTime.of(2007, 8, 9, 9, 10, 11, 0, ZoneId.of("Europe/Berlin"))); - - assertEquals(rs.getDate(7), new Date(new DateTime(2013, 3, 22, 0, 0).getMillis())); - assertEquals(rs.getDate(7, ASIA_ORAL_CALENDAR), new Date(new DateTime(2013, 3, 22, 0, 0, ASIA_ORAL_ZONE).getMillis())); - assertEquals(rs.getObject(7), new Date(new DateTime(2013, 3, 22, 0, 0).getMillis())); - assertEquals(rs.getDate("g"), new Date(new DateTime(2013, 3, 22, 0, 0).getMillis())); - assertEquals(rs.getDate("g", ASIA_ORAL_CALENDAR), new Date(new DateTime(2013, 3, 22, 0, 0, ASIA_ORAL_ZONE).getMillis())); - assertEquals(rs.getObject("g"), new Date(new DateTime(2013, 3, 22, 0, 0).getMillis())); - - assertEquals(rs.getObject(8), new TrinoIntervalYearMonth(123, 11)); - assertEquals(rs.getObject("h"), new TrinoIntervalYearMonth(123, 11)); - assertEquals(rs.getObject(9), new TrinoIntervalDayTime(11, 22, 33, 44, 555)); - assertEquals(rs.getObject("i"), new TrinoIntervalDayTime(11, 22, 33, 44, 555)); - - assertEquals(rs.getFloat(10), 123.45f); - assertEquals(rs.getObject(10), 123.45f); - assertEquals(rs.getFloat("j"), 123.45f); - assertEquals(rs.getObject("j"), 123.45f); - - assertEquals(rs.getFloat(11), POSITIVE_INFINITY); - assertEquals(rs.getObject(11), POSITIVE_INFINITY); - assertEquals(rs.getFloat("k"), POSITIVE_INFINITY); - assertEquals(rs.getObject("k"), POSITIVE_INFINITY); - assertFalse(rs.next()); + assertThat(rs.next()).isTrue(); + + assertThat(rs.getTime(1)).isEqualTo(new Time(new DateTime(1970, 1, 1, 3, 4, 5).getMillis())); + assertThat(rs.getTime(1, ASIA_ORAL_CALENDAR)).isEqualTo(new Time(new DateTime(1970, 1, 1, 3, 4, 5, ASIA_ORAL_ZONE).getMillis())); + assertThat(rs.getObject(1)).isEqualTo(new Time(new DateTime(1970, 1, 1, 3, 4, 5).getMillis())); + assertThat(rs.getTime("a")).isEqualTo(new Time(new DateTime(1970, 1, 1, 3, 4, 5).getMillis())); + assertThat(rs.getTime("a", ASIA_ORAL_CALENDAR)).isEqualTo(new Time(new DateTime(1970, 1, 1, 3, 4, 5, ASIA_ORAL_ZONE).getMillis())); + assertThat(rs.getObject("a")).isEqualTo(new Time(new DateTime(1970, 1, 1, 3, 4, 5).getMillis())); + + assertThat(rs.getTime(2)).isEqualTo(new Time(new DateTime(1970, 1, 1, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); + assertThat(rs.getTime(2, ASIA_ORAL_CALENDAR)).isEqualTo(new Time(new DateTime(1970, 1, 1, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); + assertThat(rs.getObject(2)).isEqualTo(new Time(new DateTime(1970, 1, 1, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); + assertThat(rs.getTime("b")).isEqualTo(new Time(new DateTime(1970, 1, 1, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); + assertThat(rs.getTime("b", ASIA_ORAL_CALENDAR)).isEqualTo(new Time(new DateTime(1970, 1, 1, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); + assertThat(rs.getObject("b")).isEqualTo(new Time(new DateTime(1970, 1, 1, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); + + assertThat(rs.getTime(3)).isEqualTo(new Time(new DateTime(1970, 1, 1, 9, 10, 11, DateTimeZone.forOffsetHoursMinutes(2, 0)).getMillis())); + assertThat(rs.getTime(3, ASIA_ORAL_CALENDAR)).isEqualTo(new Time(new DateTime(1970, 1, 1, 9, 10, 11, DateTimeZone.forOffsetHoursMinutes(2, 0)).getMillis())); + assertThat(rs.getObject(3)).isEqualTo(new Time(new DateTime(1970, 1, 1, 9, 10, 11, DateTimeZone.forOffsetHoursMinutes(2, 0)).getMillis())); + assertThat(rs.getTime("c")).isEqualTo(new Time(new DateTime(1970, 1, 1, 9, 10, 11, DateTimeZone.forOffsetHoursMinutes(2, 0)).getMillis())); + assertThat(rs.getTime("c", ASIA_ORAL_CALENDAR)).isEqualTo(new Time(new DateTime(1970, 1, 1, 9, 10, 11, DateTimeZone.forOffsetHoursMinutes(2, 0)).getMillis())); + assertThat(rs.getObject("c")).isEqualTo(new Time(new DateTime(1970, 1, 1, 9, 10, 11, DateTimeZone.forOffsetHoursMinutes(2, 0)).getMillis())); + + assertThat(rs.getTimestamp(4)).isEqualTo(new Timestamp(new DateTime(2001, 2, 3, 3, 4, 5).getMillis())); + assertThat(rs.getTimestamp(4, ASIA_ORAL_CALENDAR)).isEqualTo(new Timestamp(new DateTime(2001, 2, 3, 3, 4, 5, ASIA_ORAL_ZONE).getMillis())); + assertThat(rs.getObject(4)).isEqualTo(new Timestamp(new DateTime(2001, 2, 3, 3, 4, 5).getMillis())); + assertThat(rs.getTimestamp("d")).isEqualTo(new Timestamp(new DateTime(2001, 2, 3, 3, 4, 5).getMillis())); + assertThat(rs.getTimestamp("d", ASIA_ORAL_CALENDAR)).isEqualTo(new Timestamp(new DateTime(2001, 2, 3, 3, 4, 5, ASIA_ORAL_ZONE).getMillis())); + assertThat(rs.getObject("d")).isEqualTo(new Timestamp(new DateTime(2001, 2, 3, 3, 4, 5).getMillis())); + + assertThat(rs.getTimestamp(5)).isEqualTo(new Timestamp(new DateTime(2004, 5, 6, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); + assertThat(rs.getTimestamp(5, ASIA_ORAL_CALENDAR)).isEqualTo(new Timestamp(new DateTime(2004, 5, 6, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); + assertThat(rs.getObject(5)).isEqualTo(new Timestamp(new DateTime(2004, 5, 6, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); + assertThat(rs.getObject(5, ZonedDateTime.class)).isEqualTo(ZonedDateTime.of(2004, 5, 6, 6, 7, 8, 0, ZoneOffset.ofHoursMinutes(6, 17))); + assertThat(rs.getTimestamp("e")).isEqualTo(new Timestamp(new DateTime(2004, 5, 6, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); + assertThat(rs.getTimestamp("e", ASIA_ORAL_CALENDAR)).isEqualTo(new Timestamp(new DateTime(2004, 5, 6, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); + assertThat(rs.getObject("e")).isEqualTo(new Timestamp(new DateTime(2004, 5, 6, 6, 7, 8, DateTimeZone.forOffsetHoursMinutes(6, 17)).getMillis())); + assertThat(rs.getObject("e", ZonedDateTime.class)).isEqualTo(ZonedDateTime.of(2004, 5, 6, 6, 7, 8, 0, ZoneOffset.ofHoursMinutes(6, 17))); + + assertThat(rs.getTimestamp(6)).isEqualTo(new Timestamp(new DateTime(2007, 8, 9, 9, 10, 11, DateTimeZone.forID("Europe/Berlin")).getMillis())); + assertThat(rs.getTimestamp(6, ASIA_ORAL_CALENDAR)).isEqualTo(new Timestamp(new DateTime(2007, 8, 9, 9, 10, 11, DateTimeZone.forID("Europe/Berlin")).getMillis())); + assertThat(rs.getObject(6)).isEqualTo(new Timestamp(new DateTime(2007, 8, 9, 9, 10, 11, DateTimeZone.forID("Europe/Berlin")).getMillis())); + assertThat(rs.getObject(6, ZonedDateTime.class)).isEqualTo(ZonedDateTime.of(2007, 8, 9, 9, 10, 11, 0, ZoneId.of("Europe/Berlin"))); + assertThat(rs.getTimestamp("f")).isEqualTo(new Timestamp(new DateTime(2007, 8, 9, 9, 10, 11, DateTimeZone.forID("Europe/Berlin")).getMillis())); + assertThat(rs.getTimestamp("f", ASIA_ORAL_CALENDAR)).isEqualTo(new Timestamp(new DateTime(2007, 8, 9, 9, 10, 11, DateTimeZone.forID("Europe/Berlin")).getMillis())); + assertThat(rs.getObject("f")).isEqualTo(new Timestamp(new DateTime(2007, 8, 9, 9, 10, 11, DateTimeZone.forID("Europe/Berlin")).getMillis())); + assertThat(rs.getObject("f", ZonedDateTime.class)).isEqualTo(ZonedDateTime.of(2007, 8, 9, 9, 10, 11, 0, ZoneId.of("Europe/Berlin"))); + + assertThat(rs.getDate(7)).isEqualTo(new Date(new DateTime(2013, 3, 22, 0, 0).getMillis())); + assertThat(rs.getDate(7, ASIA_ORAL_CALENDAR)).isEqualTo(new Date(new DateTime(2013, 3, 22, 0, 0, ASIA_ORAL_ZONE).getMillis())); + assertThat(rs.getObject(7)).isEqualTo(new Date(new DateTime(2013, 3, 22, 0, 0).getMillis())); + assertThat(rs.getDate("g")).isEqualTo(new Date(new DateTime(2013, 3, 22, 0, 0).getMillis())); + assertThat(rs.getDate("g", ASIA_ORAL_CALENDAR)).isEqualTo(new Date(new DateTime(2013, 3, 22, 0, 0, ASIA_ORAL_ZONE).getMillis())); + assertThat(rs.getObject("g")).isEqualTo(new Date(new DateTime(2013, 3, 22, 0, 0).getMillis())); + + assertThat(rs.getObject(8)).isEqualTo(new TrinoIntervalYearMonth(123, 11)); + assertThat(rs.getObject("h")).isEqualTo(new TrinoIntervalYearMonth(123, 11)); + assertThat(rs.getObject(9)).isEqualTo(new TrinoIntervalDayTime(11, 22, 33, 44, 555)); + assertThat(rs.getObject("i")).isEqualTo(new TrinoIntervalDayTime(11, 22, 33, 44, 555)); + + assertThat(rs.getFloat(10)).isEqualTo(123.45f); + assertThat(rs.getObject(10)).isEqualTo(123.45f); + assertThat(rs.getFloat("j")).isEqualTo(123.45f); + assertThat(rs.getObject("j")).isEqualTo(123.45f); + + assertThat(rs.getFloat(11)).isEqualTo(POSITIVE_INFINITY); + assertThat(rs.getObject(11)).isEqualTo(POSITIVE_INFINITY); + assertThat(rs.getFloat("k")).isEqualTo(POSITIVE_INFINITY); + assertThat(rs.getObject("k")).isEqualTo(POSITIVE_INFINITY); + assertThat(rs.next()).isFalse(); } } } @@ -370,10 +365,10 @@ public void testGetDriverVersion() try (Connection connection = createConnection()) { DatabaseMetaData metaData = connection.getMetaData(); - assertEquals(metaData.getDriverName(), "Trino JDBC Driver"); + assertThat(metaData.getDriverName()).isEqualTo("Trino JDBC Driver"); assertThat(metaData.getDriverVersion()).startsWith(String.valueOf(driver.getMajorVersion())); - assertEquals(metaData.getDriverMajorVersion(), driver.getMajorVersion()); - assertEquals(metaData.getDriverMinorVersion(), driver.getMinorVersion()); + assertThat(metaData.getDriverMajorVersion()).isEqualTo(driver.getMajorVersion()); + assertThat(metaData.getDriverMinorVersion()).isEqualTo(driver.getMinorVersion()); } } @@ -449,31 +444,31 @@ public void testExecuteWithQuery() { try (Connection connection = createConnection()) { try (Statement statement = connection.createStatement()) { - assertTrue(statement.execute("SELECT 123 x, 'foo' y, CAST(NULL AS bigint) z")); + assertThat(statement.execute("SELECT 123 x, 'foo' y, CAST(NULL AS bigint) z")).isTrue(); ResultSet rs = statement.getResultSet(); - assertEquals(statement.getUpdateCount(), -1); - assertEquals(statement.getLargeUpdateCount(), -1); - assertTrue(rs.next()); + assertThat(statement.getUpdateCount()).isEqualTo(-1); + assertThat(statement.getLargeUpdateCount()).isEqualTo(-1); + assertThat(rs.next()).isTrue(); - assertEquals(rs.getLong(1), 123); - assertFalse(rs.wasNull()); - assertEquals(rs.getLong("x"), 123); - assertFalse(rs.wasNull()); + assertThat(rs.getLong(1)).isEqualTo(123); + assertThat(rs.wasNull()).isFalse(); + assertThat(rs.getLong("x")).isEqualTo(123); + assertThat(rs.wasNull()).isFalse(); - assertEquals(rs.getLong(3), 0); - assertTrue(rs.wasNull()); - assertEquals(rs.getLong("z"), 0); - assertTrue(rs.wasNull()); - assertNull(rs.getObject("z")); - assertTrue(rs.wasNull()); + assertThat(rs.getLong(3)).isEqualTo(0); + assertThat(rs.wasNull()).isTrue(); + assertThat(rs.getLong("z")).isEqualTo(0); + assertThat(rs.wasNull()).isTrue(); + assertThat(rs.getObject("z")).isNull(); + assertThat(rs.wasNull()).isTrue(); - assertEquals(rs.getString(2), "foo"); - assertFalse(rs.wasNull()); - assertEquals(rs.getString("y"), "foo"); - assertFalse(rs.wasNull()); + assertThat(rs.getString(2)).isEqualTo("foo"); + assertThat(rs.wasNull()).isFalse(); + assertThat(rs.getString("y")).isEqualTo("foo"); + assertThat(rs.wasNull()).isFalse(); - assertFalse(rs.next()); + assertThat(rs.next()).isFalse(); } } } @@ -484,10 +479,10 @@ public void testExecuteUpdateWithInsert() { try (Connection connection = createConnection("blackhole", "blackhole")) { try (Statement statement = connection.createStatement()) { - assertEquals(statement.executeUpdate("INSERT INTO test_table VALUES (1), (2)"), 2); - assertNull(statement.getResultSet()); - assertEquals(statement.getUpdateCount(), 2); - assertEquals(statement.getLargeUpdateCount(), 2); + assertThat(statement.executeUpdate("INSERT INTO test_table VALUES (1), (2)")).isEqualTo(2); + assertThat(statement.getResultSet()).isNull(); + assertThat(statement.getUpdateCount()).isEqualTo(2); + assertThat(statement.getLargeUpdateCount()).isEqualTo(2); } } } @@ -498,10 +493,10 @@ public void testExecuteUpdateWithCreateTable() { try (Connection connection = createConnection("blackhole", "blackhole")) { try (Statement statement = connection.createStatement()) { - assertEquals(statement.executeUpdate("CREATE TABLE test_execute_create (x bigint)"), 0); - assertNull(statement.getResultSet()); - assertEquals(statement.getUpdateCount(), 0); - assertEquals(statement.getLargeUpdateCount(), 0); + assertThat(statement.executeUpdate("CREATE TABLE test_execute_create (x bigint)")).isEqualTo(0); + assertThat(statement.getResultSet()).isNull(); + assertThat(statement.getUpdateCount()).isEqualTo(0); + assertThat(statement.getLargeUpdateCount()).isEqualTo(0); } } } @@ -541,24 +536,24 @@ public void testStatementReuse() try (Connection connection = createConnection("blackhole", "blackhole")) { try (Statement statement = connection.createStatement()) { // update statement - assertFalse(statement.execute("INSERT INTO test_table VALUES (1), (2)")); - assertNull(statement.getResultSet()); - assertEquals(statement.getUpdateCount(), 2); - assertEquals(statement.getLargeUpdateCount(), 2); + assertThat(statement.execute("INSERT INTO test_table VALUES (1), (2)")).isFalse(); + assertThat(statement.getResultSet()).isNull(); + assertThat(statement.getUpdateCount()).isEqualTo(2); + assertThat(statement.getLargeUpdateCount()).isEqualTo(2); // query statement - assertTrue(statement.execute("SELECT 123 x, 'foo' y, CAST(NULL AS bigint) z")); + assertThat(statement.execute("SELECT 123 x, 'foo' y, CAST(NULL AS bigint) z")).isTrue(); ResultSet resultSet = statement.getResultSet(); - assertNotNull(resultSet); - assertEquals(statement.getUpdateCount(), -1); - assertEquals(statement.getLargeUpdateCount(), -1); + assertThat(resultSet).isNotNull(); + assertThat(statement.getUpdateCount()).isEqualTo(-1); + assertThat(statement.getLargeUpdateCount()).isEqualTo(-1); resultSet.close(); // update statement - assertFalse(statement.execute("INSERT INTO test_table VALUES (1), (2), (3)")); - assertNull(statement.getResultSet()); - assertEquals(statement.getUpdateCount(), 3); - assertEquals(statement.getLargeUpdateCount(), 3); + assertThat(statement.execute("INSERT INTO test_table VALUES (1), (2), (3)")).isFalse(); + assertThat(statement.getResultSet()).isNull(); + assertThat(statement.getUpdateCount()).isEqualTo(3); + assertThat(statement.getLargeUpdateCount()).isEqualTo(3); } } } @@ -569,9 +564,9 @@ public void testGetUpdateCount() { try (Connection connection = createConnection()) { try (Statement statement = connection.createStatement()) { - assertTrue(statement.execute("SELECT 123 x, 'foo' y")); - assertEquals(statement.getUpdateCount(), -1); - assertEquals(statement.getLargeUpdateCount(), -1); + assertThat(statement.execute("SELECT 123 x, 'foo' y")).isTrue(); + assertThat(statement.getUpdateCount()).isEqualTo(-1); + assertThat(statement.getLargeUpdateCount()).isEqualTo(-1); } } } @@ -582,11 +577,11 @@ public void testResultSetClose() { try (Connection connection = createConnection()) { try (Statement statement = connection.createStatement()) { - assertTrue(statement.execute("SELECT 123 x, 'foo' y")); + assertThat(statement.execute("SELECT 123 x, 'foo' y")).isTrue(); ResultSet result = statement.getResultSet(); - assertFalse(result.isClosed()); + assertThat(result.isClosed()).isFalse(); result.close(); - assertTrue(result.isClosed()); + assertThat(result.isClosed()).isTrue(); } } } @@ -597,20 +592,20 @@ public void testGetResultSet() { try (Connection connection = createConnection()) { try (Statement statement = connection.createStatement()) { - assertTrue(statement.execute("SELECT 123 x, 'foo' y")); + assertThat(statement.execute("SELECT 123 x, 'foo' y")).isTrue(); ResultSet result = statement.getResultSet(); - assertNotNull(result); - assertFalse(result.isClosed()); + assertThat(result).isNotNull(); + assertThat(result.isClosed()).isFalse(); statement.getMoreResults(); - assertTrue(result.isClosed()); + assertThat(result.isClosed()).isTrue(); - assertTrue(statement.execute("SELECT 123 x, 'foo' y")); + assertThat(statement.execute("SELECT 123 x, 'foo' y")).isTrue(); result = statement.getResultSet(); - assertNotNull(result); - assertFalse(result.isClosed()); + assertThat(result).isNotNull(); + assertThat(result.isClosed()).isFalse(); - assertTrue(statement.execute("SELECT 123 x, 'foo' y")); - assertFalse(statement.getMoreResults(Statement.CLOSE_CURRENT_RESULT)); + assertThat(statement.execute("SELECT 123 x, 'foo' y")).isTrue(); + assertThat(statement.getMoreResults(Statement.CLOSE_CURRENT_RESULT)).isFalse(); } } } @@ -622,7 +617,7 @@ public void testGetMoreResultsException() assertThatThrownBy(() -> { try (Connection connection = createConnection()) { try (Statement statement = connection.createStatement()) { - assertTrue(statement.execute("SELECT 123 x, 'foo' y")); + assertThat(statement.execute("SELECT 123 x, 'foo' y")).isTrue(); statement.getMoreResults(Statement.KEEP_CURRENT_RESULT); } } @@ -637,12 +632,12 @@ public void testGetMoreResultsClearsUpdateCount() { try (Connection connection = createConnection("blackhole", "default")) { try (TrinoStatement statement = connection.createStatement().unwrap(TrinoStatement.class)) { - assertFalse(statement.execute("CREATE TABLE test_more_results_clears_update_count (id bigint)")); - assertEquals(statement.getUpdateCount(), 0); - assertEquals(statement.getUpdateType(), "CREATE TABLE"); - assertFalse(statement.getMoreResults()); - assertEquals(statement.getUpdateCount(), -1); - assertNull(statement.getUpdateType()); + assertThat(statement.execute("CREATE TABLE test_more_results_clears_update_count (id bigint)")).isFalse(); + assertThat(statement.getUpdateCount()).isEqualTo(0); + assertThat(statement.getUpdateType()).isEqualTo("CREATE TABLE"); + assertThat(statement.getMoreResults()).isFalse(); + assertThat(statement.getUpdateCount()).isEqualTo(-1); + assertThat(statement.getUpdateType()).isNull(); } finally { try (Statement statement = connection.createStatement()) { @@ -663,38 +658,38 @@ public void testSetTimeZoneId() try (Connection connection = createConnection()) { try (Statement statement = connection.createStatement(); ResultSet rs = statement.executeQuery(sql)) { - assertTrue(rs.next()); - assertEquals(rs.getString("zone"), defaultZoneKey.getId()); - assertEquals(rs.getTimestamp("ts"), new Timestamp(new DateTime(2001, 2, 3, 3, 4, 5, defaultZone).getMillis())); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("zone")).isEqualTo(defaultZoneKey.getId()); + assertThat(rs.getTimestamp("ts")).isEqualTo(new Timestamp(new DateTime(2001, 2, 3, 3, 4, 5, defaultZone).getMillis())); } connection.unwrap(TrinoConnection.class).setTimeZoneId("UTC"); try (Statement statement = connection.createStatement(); ResultSet rs = statement.executeQuery(sql)) { - assertTrue(rs.next()); - assertEquals(rs.getString("zone"), "UTC"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("zone")).isEqualTo("UTC"); // setting the session timezone has no effect on the interpretation of timestamps in the JDBC driver - assertEquals(rs.getTimestamp("ts"), new Timestamp(new DateTime(2001, 2, 3, 3, 4, 5, defaultZone).getMillis())); + assertThat(rs.getTimestamp("ts")).isEqualTo(new Timestamp(new DateTime(2001, 2, 3, 3, 4, 5, defaultZone).getMillis())); } } try (Connection connection = createConnectionWithParameter("timezone=Asia/Kolkata")) { try (Statement statement = connection.createStatement(); ResultSet rs = statement.executeQuery(sql)) { - assertTrue(rs.next()); - assertEquals(rs.getString("zone"), "Asia/Kolkata"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("zone")).isEqualTo("Asia/Kolkata"); // setting the session timezone has no effect on the interpretation of timestamps in the JDBC driver - assertEquals(rs.getTimestamp("ts"), new Timestamp(new DateTime(2001, 2, 3, 3, 4, 5, defaultZone).getMillis())); + assertThat(rs.getTimestamp("ts")).isEqualTo(new Timestamp(new DateTime(2001, 2, 3, 3, 4, 5, defaultZone).getMillis())); } } try (Connection connection = createConnectionWithParameter("timezone=UTC+05:30")) { try (Statement statement = connection.createStatement(); ResultSet rs = statement.executeQuery(sql)) { - assertTrue(rs.next()); - assertEquals(rs.getString("zone"), "+05:30"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("zone")).isEqualTo("+05:30"); // setting the session timezone has no effect on the interpretation of timestamps in the JDBC driver - assertEquals(rs.getTimestamp("ts"), new Timestamp(new DateTime(2001, 2, 3, 3, 4, 5, defaultZone).getMillis())); + assertThat(rs.getTimestamp("ts")).isEqualTo(new Timestamp(new DateTime(2001, 2, 3, 3, 4, 5, defaultZone).getMillis())); } } @@ -713,28 +708,28 @@ public void testConnectionStringWithCatalogAndSchema() Connection connection; connection = DriverManager.getConnection(prefix + "/a/b/", "test", null); - assertEquals(connection.getCatalog(), "a"); - assertEquals(connection.getSchema(), "b"); + assertThat(connection.getCatalog()).isEqualTo("a"); + assertThat(connection.getSchema()).isEqualTo("b"); connection = DriverManager.getConnection(prefix + "/a/b", "test", null); - assertEquals(connection.getCatalog(), "a"); - assertEquals(connection.getSchema(), "b"); + assertThat(connection.getCatalog()).isEqualTo("a"); + assertThat(connection.getSchema()).isEqualTo("b"); connection = DriverManager.getConnection(prefix + "/a/", "test", null); - assertEquals(connection.getCatalog(), "a"); - assertNull(connection.getSchema()); + assertThat(connection.getCatalog()).isEqualTo("a"); + assertThat(connection.getSchema()).isNull(); connection = DriverManager.getConnection(prefix + "/a", "test", null); - assertEquals(connection.getCatalog(), "a"); - assertNull(connection.getSchema()); + assertThat(connection.getCatalog()).isEqualTo("a"); + assertThat(connection.getSchema()).isNull(); connection = DriverManager.getConnection(prefix + "/", "test", null); - assertNull(connection.getCatalog()); - assertNull(connection.getSchema()); + assertThat(connection.getCatalog()).isNull(); + assertThat(connection.getSchema()).isNull(); connection = DriverManager.getConnection(prefix, "test", null); - assertNull(connection.getCatalog()); - assertNull(connection.getSchema()); + assertThat(connection.getCatalog()).isNull(); + assertThat(connection.getSchema()).isNull(); } @Test @@ -749,11 +744,11 @@ public void testConnectionWithCatalogAndSchema() "WHERE table_schema = 'information_schema' " + " AND table_name = 'tables'")) { ResultSetMetaData metadata = rs.getMetaData(); - assertEquals(metadata.getColumnCount(), 2); - assertEquals(metadata.getColumnLabel(1), "table_catalog"); - assertEquals(metadata.getColumnLabel(2), "table_schema"); - assertTrue(rs.next()); - assertEquals(rs.getString("table_catalog"), TEST_CATALOG); + assertThat(metadata.getColumnCount()).isEqualTo(2); + assertThat(metadata.getColumnLabel(1)).isEqualTo("table_catalog"); + assertThat(metadata.getColumnLabel(2)).isEqualTo("table_schema"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("table_catalog")).isEqualTo(TEST_CATALOG); } } } @@ -771,11 +766,11 @@ public void testConnectionWithCatalog() "WHERE table_schema = 'information_schema' " + " AND table_name = 'tables'")) { ResultSetMetaData metadata = rs.getMetaData(); - assertEquals(metadata.getColumnCount(), 2); - assertEquals(metadata.getColumnLabel(1), "table_catalog"); - assertEquals(metadata.getColumnLabel(2), "table_schema"); - assertTrue(rs.next()); - assertEquals(rs.getString("table_catalog"), TEST_CATALOG); + assertThat(metadata.getColumnCount()).isEqualTo(2); + assertThat(metadata.getColumnLabel(1)).isEqualTo("table_catalog"); + assertThat(metadata.getColumnLabel(2)).isEqualTo("table_schema"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("table_catalog")).isEqualTo(TEST_CATALOG); } } } @@ -793,7 +788,7 @@ public void testConnectionResourceHandling() try (Statement statement = connection.createStatement(); ResultSet rs = statement.executeQuery("SELECT 123")) { - assertTrue(rs.next()); + assertThat(rs.next()).isTrue(); } } @@ -890,11 +885,11 @@ public void testSetRole() try (Statement statement = connection.createStatement()) { statement.executeUpdate("SET ROLE ALL"); } - assertEquals(connection.getRoles(), ImmutableMap.of("system", new ClientSelectedRole(ClientSelectedRole.Type.ALL, Optional.empty()))); + assertThat(connection.getRoles()).isEqualTo(ImmutableMap.of("system", new ClientSelectedRole(ClientSelectedRole.Type.ALL, Optional.empty()))); try (Statement statement = connection.createStatement()) { statement.executeUpdate("SET ROLE NONE"); } - assertEquals(connection.getRoles(), ImmutableMap.of("system", new ClientSelectedRole(ClientSelectedRole.Type.NONE, Optional.empty()))); + assertThat(connection.getRoles()).isEqualTo(ImmutableMap.of("system", new ClientSelectedRole(ClientSelectedRole.Type.NONE, Optional.empty()))); try (Statement statement = connection.createStatement()) { // There is no way to create system roles right now @@ -942,19 +937,19 @@ public void testQueryCancelByInterrupt() }); // start query and make sure it is not finished - assertTrue(queryStarted.await(10, SECONDS)); - assertNotNull(queryId.get()); - assertFalse(getQueryState(queryId.get()).isDone()); + assertThat(queryStarted.await(10, SECONDS)).isTrue(); + assertThat(queryId.get()).isNotNull(); + assertThat(getQueryState(queryId.get()).isDone()).isFalse(); // interrupt JDBC thread that is waiting for query results queryFuture.cancel(true); // make sure the query was aborted - assertTrue(queryFinished.await(10, SECONDS)); + assertThat(queryFinished.await(10, SECONDS)).isTrue(); assertThat(queryFailure.get()) .isInstanceOf(SQLException.class) .hasMessage("Interrupted"); - assertEquals(getQueryState(queryId.get()), FAILED); + assertThat(getQueryState(queryId.get())).isEqualTo(FAILED); } @Test @@ -986,16 +981,16 @@ public void testQueryCancelExplicit() // start query and make sure it is not finished queryStarted.await(10, SECONDS); - assertNotNull(queryId.get()); - assertFalse(getQueryState(queryId.get()).isDone()); + assertThat(queryId.get()).isNotNull(); + assertThat(getQueryState(queryId.get()).isDone()).isFalse(); // cancel the query from this test thread statement.cancel(); // make sure the query was aborted queryFinished.await(10, SECONDS); - assertNotNull(queryFailure.get()); - assertEquals(getQueryState(queryId.get()), FAILED); + assertThat(queryFailure.get()).isNotNull(); + assertThat(getQueryState(queryId.get())).isEqualTo(FAILED); } } @@ -1027,7 +1022,7 @@ public void testUpdateCancelExplicit() while (true) { Optional state = findQueryState(queryUuid); if (state.isPresent()) { - assertFalse(state.get().isDone()); + assertThat(state.get().isDone()).isFalse(); break; } MILLISECONDS.sleep(50); @@ -1038,8 +1033,8 @@ public void testUpdateCancelExplicit() // make sure the query was aborted queryFinished.await(10, SECONDS); - assertNotNull(queryFailure.get()); - assertEquals(findQueryState(queryUuid), Optional.of(FAILED)); + assertThat(queryFailure.get()).isNotNull(); + assertThat(findQueryState(queryUuid)).isEqualTo(Optional.of(FAILED)); } } @@ -1083,7 +1078,7 @@ public void testQueryTimeout() // make sure the query timed out queryFinished.await(); - assertNotNull(queryFailure.get()); + assertThat(queryFailure.get()).isNotNull(); assertContains(queryFailure.get().getMessage(), "Query exceeded maximum time limit of 1.00s"); try (Connection connection = createConnection("blackhole", "blackhole"); @@ -1101,8 +1096,8 @@ public void testQueryPartialCancel() Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery("SELECT count(*) FROM slow_test_table")) { statement.unwrap(TrinoStatement.class).partialCancel(); - assertTrue(resultSet.next()); - assertEquals(resultSet.getLong(1), 0); + assertThat(resultSet.next()).isTrue(); + assertThat(resultSet.getLong(1)).isEqualTo(0); } } @@ -1131,7 +1126,7 @@ public void testUpdatePartialCancel() statement.unwrap(TrinoStatement.class).partialCancel(); // make sure query completes - assertEquals(future.get(10, SECONDS), (Integer) 1); + assertThat(future.get(10, SECONDS)).isEqualTo((Integer) 1); } } @@ -1147,7 +1142,7 @@ public void testCustomDnsResolver() String url = "jdbc:trino://mycustomaddress:" + server.getAddress().getPort(); try (Connection connection = DriverManager.getConnection(url, properties)) { try (Statement statement = connection.createStatement()) { - assertTrue(statement.execute("SELECT 1")); + assertThat(statement.execute("SELECT 1")).isTrue(); } } } @@ -1159,17 +1154,17 @@ public void testResetSessionAuthorization() { try (TrinoConnection connection = createConnection("blackhole", "blackhole").unwrap(TrinoConnection.class); Statement statement = connection.createStatement()) { - assertEquals(connection.getAuthorizationUser(), null); - assertEquals(getCurrentUser(connection), "test"); + assertThat(connection.getAuthorizationUser()).isEqualTo(null); + assertThat(getCurrentUser(connection)).isEqualTo("test"); statement.execute("SET SESSION AUTHORIZATION john"); - assertEquals(connection.getAuthorizationUser(), "john"); - assertEquals(getCurrentUser(connection), "john"); + assertThat(connection.getAuthorizationUser()).isEqualTo("john"); + assertThat(getCurrentUser(connection)).isEqualTo("john"); statement.execute("SET SESSION AUTHORIZATION bob"); - assertEquals(connection.getAuthorizationUser(), "bob"); - assertEquals(getCurrentUser(connection), "bob"); + assertThat(connection.getAuthorizationUser()).isEqualTo("bob"); + assertThat(getCurrentUser(connection)).isEqualTo("bob"); statement.execute("RESET SESSION AUTHORIZATION"); - assertEquals(connection.getAuthorizationUser(), null); - assertEquals(getCurrentUser(connection), "test"); + assertThat(connection.getAuthorizationUser()).isEqualTo(null); + assertThat(getCurrentUser(connection)).isEqualTo("test"); } } @@ -1181,17 +1176,17 @@ public void testSetRoleAfterSetSessionAuthorization() try (TrinoConnection connection = createConnection("blackhole", "blackhole").unwrap(TrinoConnection.class); Statement statement = connection.createStatement()) { statement.execute("SET SESSION AUTHORIZATION john"); - assertEquals(connection.getAuthorizationUser(), "john"); + assertThat(connection.getAuthorizationUser()).isEqualTo("john"); statement.execute("SET ROLE ALL"); - assertEquals(connection.getRoles(), ImmutableMap.of("system", new ClientSelectedRole(ClientSelectedRole.Type.ALL, Optional.empty()))); + assertThat(connection.getRoles()).isEqualTo(ImmutableMap.of("system", new ClientSelectedRole(ClientSelectedRole.Type.ALL, Optional.empty()))); statement.execute("SET SESSION AUTHORIZATION bob"); - assertEquals(connection.getAuthorizationUser(), "bob"); - assertEquals(connection.getRoles(), ImmutableMap.of()); + assertThat(connection.getAuthorizationUser()).isEqualTo("bob"); + assertThat(connection.getRoles()).isEqualTo(ImmutableMap.of()); statement.execute("SET ROLE NONE"); - assertEquals(connection.getRoles(), ImmutableMap.of("system", new ClientSelectedRole(ClientSelectedRole.Type.NONE, Optional.empty()))); + assertThat(connection.getRoles()).isEqualTo(ImmutableMap.of("system", new ClientSelectedRole(ClientSelectedRole.Type.NONE, Optional.empty()))); statement.execute("RESET SESSION AUTHORIZATION"); - assertEquals(connection.getAuthorizationUser(), null); - assertEquals(connection.getRoles(), ImmutableMap.of()); + assertThat(connection.getAuthorizationUser()).isEqualTo(null); + assertThat(connection.getRoles()).isEqualTo(ImmutableMap.of()); } } @@ -1202,7 +1197,9 @@ private QueryState getQueryState(String queryId) try (Connection connection = createConnection(); Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery(sql)) { - assertTrue(resultSet.next(), "Query was not found"); + assertThat(resultSet.next()) + .describedAs("Query was not found") + .isTrue(); return QueryState.valueOf(requireNonNull(resultSet.getString(1))); } } @@ -1218,7 +1215,9 @@ private Optional findQueryState(String text) return Optional.empty(); } QueryState state = QueryState.valueOf(requireNonNull(resultSet.getString(1))); - assertFalse(resultSet.next(), "Found multiple queries"); + assertThat(resultSet.next()) + .describedAs("Found multiple queries") + .isFalse(); return Optional.of(state); } } diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriverAuth.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriverAuth.java index a946ec51c89a..77f8f2a9ae0f 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriverAuth.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriverAuth.java @@ -47,13 +47,10 @@ import static java.lang.String.format; import static java.nio.charset.StandardCharsets.US_ASCII; import static java.util.Base64.getMimeDecoder; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -72,7 +69,9 @@ public void setup() Logging.initialize(); URL resource = getClass().getClassLoader().getResource("33.privateKey"); - assertNotNull(resource, "key directory not found"); + assertThat(resource) + .describedAs("key directory not found") + .isNotNull(); File keyDir = new File(resource.toURI()).getAbsoluteFile().getParentFile(); defaultKey = hmacShaKeyFor(getMimeDecoder().decode(asCharSource(new File(keyDir, "default-key.key"), US_ASCII).read().getBytes(US_ASCII))); @@ -111,11 +110,11 @@ public void testSuccessDefaultKey() try (Connection connection = createConnection(ImmutableMap.of("accessToken", accessToken)); Statement statement = connection.createStatement()) { - assertTrue(statement.execute("SELECT 123")); + assertThat(statement.execute("SELECT 123")).isTrue(); ResultSet rs = statement.getResultSet(); - assertTrue(rs.next()); - assertEquals(rs.getLong(1), 123); - assertFalse(rs.next()); + assertThat(rs.next()).isTrue(); + assertThat(rs.getLong(1)).isEqualTo(123); + assertThat(rs.next()).isFalse(); } } @@ -132,11 +131,11 @@ public void testSuccessHmac() try (Connection connection = createConnection(ImmutableMap.of("accessToken", accessToken)); Statement statement = connection.createStatement()) { - assertTrue(statement.execute("SELECT 123")); + assertThat(statement.execute("SELECT 123")).isTrue(); ResultSet rs = statement.getResultSet(); - assertTrue(rs.next()); - assertEquals(rs.getLong(1), 123); - assertFalse(rs.next()); + assertThat(rs.next()).isTrue(); + assertThat(rs.getLong(1)).isEqualTo(123); + assertThat(rs.next()).isFalse(); } } @@ -153,11 +152,11 @@ public void testSuccessPublicKey() try (Connection connection = createConnection(ImmutableMap.of("accessToken", accessToken)); Statement statement = connection.createStatement()) { - assertTrue(statement.execute("SELECT 123")); + assertThat(statement.execute("SELECT 123")).isTrue(); ResultSet rs = statement.getResultSet(); - assertTrue(rs.next()); - assertEquals(rs.getLong(1), 123); - assertFalse(rs.next()); + assertThat(rs.next()).isTrue(); + assertThat(rs.getLong(1)).isEqualTo(123); + assertThat(rs.next()).isFalse(); } } @@ -261,11 +260,11 @@ public void testSuccessFullSslVerification() try (Connection connection = createConnection(ImmutableMap.of("accessToken", accessToken, "SSLVerification", "FULL")); Statement statement = connection.createStatement()) { - assertTrue(statement.execute("SELECT 123")); + assertThat(statement.execute("SELECT 123")).isTrue(); ResultSet rs = statement.getResultSet(); - assertTrue(rs.next()); - assertEquals(rs.getLong(1), 123); - assertFalse(rs.next()); + assertThat(rs.next()).isTrue(); + assertThat(rs.getLong(1)).isEqualTo(123); + assertThat(rs.next()).isFalse(); } } @@ -291,11 +290,11 @@ public void testSuccessFullSslVerificationAlternateHostname() try (Connection connection = DriverManager.getConnection(url, properties); Statement statement = connection.createStatement()) { - assertTrue(statement.execute("SELECT 123")); + assertThat(statement.execute("SELECT 123")).isTrue(); ResultSet rs = statement.getResultSet(); - assertTrue(rs.next()); - assertEquals(rs.getLong(1), 123); - assertFalse(rs.next()); + assertThat(rs.next()).isTrue(); + assertThat(rs.getLong(1)).isEqualTo(123); + assertThat(rs.next()).isFalse(); } } @@ -382,11 +381,11 @@ public void testSuccessCaSslVerification() try (Connection connection = createConnection(ImmutableMap.of("accessToken", accessToken, "SSLVerification", "CA")); Statement statement = connection.createStatement()) { - assertTrue(statement.execute("SELECT 123")); + assertThat(statement.execute("SELECT 123")).isTrue(); ResultSet rs = statement.getResultSet(); - assertTrue(rs.next()); - assertEquals(rs.getLong(1), 123); - assertFalse(rs.next()); + assertThat(rs.next()).isTrue(); + assertThat(rs.getLong(1)).isEqualTo(123); + assertThat(rs.next()).isFalse(); } } diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriverImpersonateUser.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriverImpersonateUser.java index d937f8689f57..9a466d42840d 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriverImpersonateUser.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriverImpersonateUser.java @@ -38,11 +38,10 @@ import static com.google.common.io.Resources.getResource; import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -101,14 +100,14 @@ public void testInvalidCredentials() public void testQueryUserNotSpecified() throws Exception { - assertEquals(trySelectCurrentUser(ImmutableMap.of("user", TEST_USER, "password", PASSWORD)), TEST_USER); + assertThat(trySelectCurrentUser(ImmutableMap.of("user", TEST_USER, "password", PASSWORD))).isEqualTo(TEST_USER); } @Test public void testImpersonateUser() throws Exception { - assertEquals(trySelectCurrentUser(ImmutableMap.of("user", TEST_USER, "password", PASSWORD, "sessionUser", "differentUser")), "differentUser"); + assertThat(trySelectCurrentUser(ImmutableMap.of("user", TEST_USER, "password", PASSWORD, "sessionUser", "differentUser"))).isEqualTo("differentUser"); } private String trySelectCurrentUser(Map additionalProperties) @@ -117,7 +116,7 @@ private String trySelectCurrentUser(Map additionalProperties) try (Connection connection = createConnection(additionalProperties); Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery("SELECT current_user")) { - assertTrue(resultSet.next()); + assertThat(resultSet.next()).isTrue(); return resultSet.getString(1); } } diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriverUri.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriverUri.java index c50b2f2d8646..39f2be0f15d7 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriverUri.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDriverUri.java @@ -33,9 +33,6 @@ import static io.trino.client.uri.PropertyName.SSL_VERIFICATION; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; public class TestTrinoDriverUri { @@ -202,7 +199,7 @@ public void testEmptyPassword() throws SQLException { TrinoDriverUri parameters = createDriverUri("jdbc:trino://localhost:8080?password="); - assertEquals(parameters.getProperties().getProperty("password"), ""); + assertThat(parameters.getProperties().getProperty("password")).isEqualTo(""); } @Test @@ -210,7 +207,7 @@ public void testNonEmptyPassword() throws SQLException { TrinoDriverUri parameters = createDriverUri("jdbc:trino://localhost:8080?password=secret"); - assertEquals(parameters.getProperties().getProperty("password"), "secret"); + assertThat(parameters.getProperties().getProperty("password")).isEqualTo("secret"); } @Test @@ -221,7 +218,7 @@ public void testUriWithSocksProxy() assertUriPortScheme(parameters, 8080, "http"); Properties properties = parameters.getProperties(); - assertEquals(properties.getProperty(SOCKS_PROXY.toString()), "localhost:1234"); + assertThat(properties.getProperty(SOCKS_PROXY.toString())).isEqualTo("localhost:1234"); } @Test @@ -232,7 +229,7 @@ public void testUriWithHttpProxy() assertUriPortScheme(parameters, 8080, "http"); Properties properties = parameters.getProperties(); - assertEquals(properties.getProperty(HTTP_PROXY.toString()), "localhost:5678"); + assertThat(properties.getProperty(HTTP_PROXY.toString())).isEqualTo("localhost:5678"); } @Test @@ -240,10 +237,10 @@ public void testUriWithoutCompression() throws SQLException { TrinoDriverUri parameters = createDriverUri("jdbc:trino://localhost:8080?disableCompression=true"); - assertTrue(parameters.isCompressionDisabled()); + assertThat(parameters.isCompressionDisabled()).isTrue(); Properties properties = parameters.getProperties(); - assertEquals(properties.getProperty(DISABLE_COMPRESSION.toString()), "true"); + assertThat(properties.getProperty(DISABLE_COMPRESSION.toString())).isEqualTo("true"); } @Test @@ -270,8 +267,8 @@ public void testUriWithSslEnabled() assertUriPortScheme(parameters, 8080, "https"); Properties properties = parameters.getProperties(); - assertNull(properties.getProperty(SSL_TRUST_STORE_PATH.toString())); - assertNull(properties.getProperty(SSL_TRUST_STORE_PASSWORD.toString())); + assertThat(properties.getProperty(SSL_TRUST_STORE_PATH.toString())).isNull(); + assertThat(properties.getProperty(SSL_TRUST_STORE_PASSWORD.toString())).isNull(); } @Test @@ -298,8 +295,8 @@ public void testUriWithSslEnabledPathOnly() assertUriPortScheme(parameters, 8080, "https"); Properties properties = parameters.getProperties(); - assertEquals(properties.getProperty(SSL_TRUST_STORE_PATH.toString()), "truststore.jks"); - assertNull(properties.getProperty(SSL_TRUST_STORE_PASSWORD.toString())); + assertThat(properties.getProperty(SSL_TRUST_STORE_PATH.toString())).isEqualTo("truststore.jks"); + assertThat(properties.getProperty(SSL_TRUST_STORE_PASSWORD.toString())).isNull(); } @Test @@ -310,8 +307,8 @@ public void testUriWithSslEnabledPassword() assertUriPortScheme(parameters, 8080, "https"); Properties properties = parameters.getProperties(); - assertEquals(properties.getProperty(SSL_TRUST_STORE_PATH.toString()), "truststore.jks"); - assertEquals(properties.getProperty(SSL_TRUST_STORE_PASSWORD.toString()), "password"); + assertThat(properties.getProperty(SSL_TRUST_STORE_PATH.toString())).isEqualTo("truststore.jks"); + assertThat(properties.getProperty(SSL_TRUST_STORE_PASSWORD.toString())).isEqualTo("password"); } @Test @@ -322,7 +319,7 @@ public void testUriWithSslEnabledUsing443SslVerificationFull() assertUriPortScheme(parameters, 443, "https"); Properties properties = parameters.getProperties(); - assertEquals(properties.getProperty(SSL_VERIFICATION.toString()), "FULL"); + assertThat(properties.getProperty(SSL_VERIFICATION.toString())).isEqualTo("FULL"); } @Test @@ -333,7 +330,7 @@ public void testUriWithSslEnabledUsing443SslVerificationCA() assertUriPortScheme(parameters, 443, "https"); Properties properties = parameters.getProperties(); - assertEquals(properties.getProperty(SSL_VERIFICATION.toString()), "CA"); + assertThat(properties.getProperty(SSL_VERIFICATION.toString())).isEqualTo("CA"); } @Test @@ -344,7 +341,7 @@ public void testUriWithSslEnabledUsing443SslVerificationNONE() assertUriPortScheme(parameters, 443, "https"); Properties properties = parameters.getProperties(); - assertEquals(properties.getProperty(SSL_VERIFICATION.toString()), "NONE"); + assertThat(properties.getProperty(SSL_VERIFICATION.toString())).isEqualTo("NONE"); } @Test @@ -355,7 +352,7 @@ public void testUriWithSslEnabledSystemTrustStoreDefault() assertUriPortScheme(parameters, 8080, "https"); Properties properties = parameters.getProperties(); - assertEquals(properties.getProperty(SSL_USE_SYSTEM_TRUST_STORE.toString()), "true"); + assertThat(properties.getProperty(SSL_USE_SYSTEM_TRUST_STORE.toString())).isEqualTo("true"); } @Test @@ -366,8 +363,8 @@ public void testUriWithSslEnabledSystemTrustStoreOverride() assertUriPortScheme(parameters, 8080, "https"); Properties properties = parameters.getProperties(); - assertEquals(properties.getProperty(SSL_TRUST_STORE_TYPE.toString()), "Override"); - assertEquals(properties.getProperty(SSL_USE_SYSTEM_TRUST_STORE.toString()), "true"); + assertThat(properties.getProperty(SSL_TRUST_STORE_TYPE.toString())).isEqualTo("Override"); + assertThat(properties.getProperty(SSL_USE_SYSTEM_TRUST_STORE.toString())).isEqualTo("true"); } @Test @@ -377,7 +374,7 @@ public void testUriWithExtraCredentials() String extraCredentials = "test.token.foo:bar;test.token.abc:xyz"; TrinoDriverUri parameters = createDriverUri("jdbc:trino://localhost:8080?extraCredentials=" + extraCredentials); Properties properties = parameters.getProperties(); - assertEquals(properties.getProperty(EXTRA_CREDENTIALS.toString()), extraCredentials); + assertThat(properties.getProperty(EXTRA_CREDENTIALS.toString())).isEqualTo(extraCredentials); } @Test @@ -387,7 +384,7 @@ public void testUriWithClientTags() String clientTags = "c1,c2"; TrinoDriverUri parameters = createDriverUri("jdbc:trino://localhost:8080?clientTags=" + clientTags); Properties properties = parameters.getProperties(); - assertEquals(properties.getProperty(CLIENT_TAGS.toString()), clientTags); + assertThat(properties.getProperty(CLIENT_TAGS.toString())).isEqualTo(clientTags); } @Test @@ -449,8 +446,8 @@ public void testTimezone() private static void assertUriPortScheme(TrinoDriverUri parameters, int port, String scheme) { URI uri = parameters.getHttpUri(); - assertEquals(uri.getPort(), port); - assertEquals(uri.getScheme(), scheme); + assertThat(uri.getPort()).isEqualTo(port); + assertThat(uri.getScheme()).isEqualTo(scheme); } private static TrinoDriverUri createDriverUri(String url) diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoResultSet.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoResultSet.java index 8d8873fdd311..af70c4e84bb2 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoResultSet.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoResultSet.java @@ -34,7 +34,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; /** * A unit test for {@link TrinoResultSet}. @@ -82,7 +82,7 @@ public Iterable> next() TimeUnit.MILLISECONDS.sleep(10); } boolean interruptedButSwallowed = interruptedButSwallowedLatch.await(5000, TimeUnit.MILLISECONDS); - assertTrue(interruptedButSwallowed); + assertThat(interruptedButSwallowed).isTrue(); } @Test diff --git a/core/trino-main/src/test/java/io/trino/TestPagesIndexPageSorter.java b/core/trino-main/src/test/java/io/trino/TestPagesIndexPageSorter.java index 04836e0ceef7..38140158e6f9 100644 --- a/core/trino-main/src/test/java/io/trino/TestPagesIndexPageSorter.java +++ b/core/trino-main/src/test/java/io/trino/TestPagesIndexPageSorter.java @@ -33,7 +33,7 @@ import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.DoubleType.DOUBLE; import static io.trino.spi.type.VarcharType.VARCHAR; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestPagesIndexPageSorter { @@ -154,7 +154,7 @@ private static void assertSorted(List inputPages, List expectedPages MaterializedResult expected = toMaterializedResult(TEST_SESSION, types, expectedPages); MaterializedResult actual = toMaterializedResult(TEST_SESSION, types, outputPages); - assertEquals(actual.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(actual.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); } private static List createOutputPages(List types, List inputPages, long[] sortedAddresses) diff --git a/core/trino-main/src/test/java/io/trino/block/AbstractTestBlock.java b/core/trino-main/src/test/java/io/trino/block/AbstractTestBlock.java index 986a5fc8cca9..8fc261abbd39 100644 --- a/core/trino-main/src/test/java/io/trino/block/AbstractTestBlock.java +++ b/core/trino-main/src/test/java/io/trino/block/AbstractTestBlock.java @@ -49,12 +49,8 @@ import static java.lang.Math.toIntExact; import static java.lang.String.format; import static java.util.Arrays.fill; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotSame; -import static org.testng.Assert.assertSame; -import static org.testng.Assert.assertTrue; public abstract class AbstractTestBlock { @@ -169,14 +165,14 @@ else if (field.getName().equals("fieldBlocksList")) { catch (IllegalAccessException t) { throw new RuntimeException(t); } - assertEquals(block.getRetainedSizeInBytes(), retainedSize); + assertThat(block.getRetainedSizeInBytes()).isEqualTo(retainedSize); } protected void assertBlockFilteredPositions(T[] expectedValues, Block block, int... positions) { Block filteredBlock = block.copyPositions(positions, 0, positions.length); T[] filteredExpectedValues = filter(expectedValues, positions); - assertEquals(filteredBlock.getPositionCount(), positions.length); + assertThat(filteredBlock.getPositionCount()).isEqualTo(positions.length); assertBlock(filteredBlock, filteredExpectedValues); } @@ -192,7 +188,7 @@ private static T[] filter(T[] expectedValues, int[] positions) private void assertBlockPositions(Block block, T[] expectedValues) { - assertEquals(block.getPositionCount(), expectedValues.length); + assertThat(block.getPositionCount()).isEqualTo(expectedValues.length); for (int position = 0; position < block.getPositionCount(); position++) { assertBlockPosition(block, position, expectedValues[position]); } @@ -215,26 +211,26 @@ private void assertBlockSize(Block block) // Asserting on `block` is not very effective because most blocks passed to this method is compact. // Therefore, we split the `block` into two and assert again. long expectedBlockSize = getCompactedBlockSizeInBytes(block); - assertEquals(block.getSizeInBytes(), expectedBlockSize); - assertEquals(block.getRegionSizeInBytes(0, block.getPositionCount()), expectedBlockSize); + assertThat(block.getSizeInBytes()).isEqualTo(expectedBlockSize); + assertThat(block.getRegionSizeInBytes(0, block.getPositionCount())).isEqualTo(expectedBlockSize); List splitBlock = splitBlock(block, 2); Block firstHalf = splitBlock.get(0); long expectedFirstHalfSize = getCompactedBlockSizeInBytes(firstHalf); - assertEquals(firstHalf.getSizeInBytes(), expectedFirstHalfSize); - assertEquals(block.getRegionSizeInBytes(0, firstHalf.getPositionCount()), expectedFirstHalfSize); + assertThat(firstHalf.getSizeInBytes()).isEqualTo(expectedFirstHalfSize); + assertThat(block.getRegionSizeInBytes(0, firstHalf.getPositionCount())).isEqualTo(expectedFirstHalfSize); Block secondHalf = splitBlock.get(1); long expectedSecondHalfSize = getCompactedBlockSizeInBytes(secondHalf); - assertEquals(secondHalf.getSizeInBytes(), expectedSecondHalfSize); - assertEquals(block.getRegionSizeInBytes(firstHalf.getPositionCount(), secondHalf.getPositionCount()), expectedSecondHalfSize); + assertThat(secondHalf.getSizeInBytes()).isEqualTo(expectedSecondHalfSize); + assertThat(block.getRegionSizeInBytes(firstHalf.getPositionCount(), secondHalf.getPositionCount())).isEqualTo(expectedSecondHalfSize); boolean[] positions = new boolean[block.getPositionCount()]; fill(positions, 0, firstHalf.getPositionCount(), true); - assertEquals(block.getPositionsSizeInBytes(positions, firstHalf.getPositionCount()), expectedFirstHalfSize); + assertThat(block.getPositionsSizeInBytes(positions, firstHalf.getPositionCount())).isEqualTo(expectedFirstHalfSize); fill(positions, true); - assertEquals(block.getPositionsSizeInBytes(positions, positions.length), expectedBlockSize); + assertThat(block.getPositionsSizeInBytes(positions, positions.length)).isEqualTo(expectedBlockSize); fill(positions, 0, firstHalf.getPositionCount(), false); - assertEquals(block.getPositionsSizeInBytes(positions, positions.length - firstHalf.getPositionCount()), expectedSecondHalfSize); + assertThat(block.getPositionsSizeInBytes(positions, positions.length - firstHalf.getPositionCount())).isEqualTo(expectedSecondHalfSize); } protected void assertBlockPosition(Block block, int position, T expectedValue) @@ -260,51 +256,51 @@ protected void assertBlockPosition(Block block, int position, T expectedValu protected void assertPositionValue(Block block, int position, T expectedValue) { if (expectedValue == null) { - assertTrue(block.isNull(position)); + assertThat(block.isNull(position)).isTrue(); return; } - assertFalse(block.isNull(position)); + assertThat(block.isNull(position)).isFalse(); if (expectedValue instanceof Slice expectedSliceValue) { if (isByteAccessSupported()) { for (int offset = 0; offset <= expectedSliceValue.length() - SIZE_OF_BYTE; offset++) { - assertEquals(block.getByte(position, offset), expectedSliceValue.getByte(offset)); + assertThat(block.getByte(position, offset)).isEqualTo(expectedSliceValue.getByte(offset)); } } if (isShortAccessSupported()) { for (int offset = 0; offset <= expectedSliceValue.length() - SIZE_OF_SHORT; offset++) { - assertEquals(block.getShort(position, offset), expectedSliceValue.getShort(offset)); + assertThat(block.getShort(position, offset)).isEqualTo(expectedSliceValue.getShort(offset)); } } if (isIntAccessSupported()) { for (int offset = 0; offset <= expectedSliceValue.length() - SIZE_OF_INT; offset++) { - assertEquals(block.getInt(position, offset), expectedSliceValue.getInt(offset)); + assertThat(block.getInt(position, offset)).isEqualTo(expectedSliceValue.getInt(offset)); } } if (isLongAccessSupported()) { for (int offset = 0; offset <= expectedSliceValue.length() - SIZE_OF_LONG; offset++) { - assertEquals(block.getLong(position, offset), expectedSliceValue.getLong(offset)); + assertThat(block.getLong(position, offset)).isEqualTo(expectedSliceValue.getLong(offset)); } } if (isAlignedLongAccessSupported()) { for (int offset = 0; offset <= expectedSliceValue.length() - SIZE_OF_LONG; offset += SIZE_OF_LONG) { - assertEquals(block.getLong(position, offset), expectedSliceValue.getLong(offset)); + assertThat(block.getLong(position, offset)).isEqualTo(expectedSliceValue.getLong(offset)); } } if (isSliceAccessSupported()) { - assertEquals(block.getSliceLength(position), expectedSliceValue.length()); + assertThat(block.getSliceLength(position)).isEqualTo(expectedSliceValue.length()); int length = block.getSliceLength(position); - assertEquals(length, expectedSliceValue.length()); + assertThat(length).isEqualTo(expectedSliceValue.length()); for (int offset = 0; offset < length - 3; offset++) { - assertEquals(block.getSlice(position, offset, 3), expectedSliceValue.slice(offset, 3)); + assertThat(block.getSlice(position, offset, 3)).isEqualTo(expectedSliceValue.slice(offset, 3)); } } @@ -312,21 +308,21 @@ protected void assertPositionValue(Block block, int position, T expectedValu } else if (expectedValue instanceof long[] expected) { Block actual = block.getObject(position, Block.class); - assertEquals(actual.getPositionCount(), expected.length); + assertThat(actual.getPositionCount()).isEqualTo(expected.length); for (int i = 0; i < expected.length; i++) { - assertEquals(BIGINT.getLong(actual, i), expected[i]); + assertThat(BIGINT.getLong(actual, i)).isEqualTo(expected[i]); } } else if (expectedValue instanceof Slice[] expected) { Block actual = block.getObject(position, Block.class); - assertEquals(actual.getPositionCount(), expected.length); + assertThat(actual.getPositionCount()).isEqualTo(expected.length); for (int i = 0; i < expected.length; i++) { - assertEquals(VARCHAR.getSlice(actual, i), expected[i]); + assertThat(VARCHAR.getSlice(actual, i)).isEqualTo(expected[i]); } } else if (expectedValue instanceof long[][] expected) { Block actual = block.getObject(position, Block.class); - assertEquals(actual.getPositionCount(), expected.length); + assertThat(actual.getPositionCount()).isEqualTo(expected.length); for (int i = 0; i < expected.length; i++) { assertPositionValue(actual, i, expected[i]); } @@ -440,14 +436,14 @@ protected static T[] alternatingNullValues(T[] objects) protected static void assertEstimatedDataSizeForStats(BlockBuilder blockBuilder, Slice[] expectedSliceValues) { Block block = blockBuilder.build(); - assertEquals(block.getPositionCount(), expectedSliceValues.length); + assertThat(block.getPositionCount()).isEqualTo(expectedSliceValues.length); for (int i = 0; i < block.getPositionCount(); i++) { int expectedSize = expectedSliceValues[i] == null ? 0 : expectedSliceValues[i].length(); - assertEquals(block.getEstimatedDataSizeForStats(i), expectedSize); + assertThat(block.getEstimatedDataSizeForStats(i)).isEqualTo(expectedSize); } Block nullValueBlock = blockBuilder.newBlockBuilderLike(null).appendNull().build(); - assertEquals(nullValueBlock.getEstimatedDataSizeForStats(0), 0); + assertThat(nullValueBlock.getEstimatedDataSizeForStats(0)).isEqualTo(0); } protected static void testCopyRegionCompactness(Block block) @@ -461,12 +457,12 @@ protected static void testCopyRegionCompactness(Block block) protected static void assertCompact(Block block) { - assertSame(block.copyRegion(0, block.getPositionCount()), block); + assertThat(block.copyRegion(0, block.getPositionCount())).isSameAs(block); } protected static void assertNotCompact(Block block) { - assertNotSame(block.copyRegion(0, block.getPositionCount()), block); + assertThat(block.copyRegion(0, block.getPositionCount())).isNotSameAs(block); } protected static void testCompactBlock(Block block) diff --git a/core/trino-main/src/test/java/io/trino/block/TestArrayBlock.java b/core/trino-main/src/test/java/io/trino/block/TestArrayBlock.java index 8d2c861e6af2..eaa2f0b806a5 100644 --- a/core/trino-main/src/test/java/io/trino/block/TestArrayBlock.java +++ b/core/trino-main/src/test/java/io/trino/block/TestArrayBlock.java @@ -29,8 +29,7 @@ import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.VarcharType.VARCHAR; import static java.lang.String.format; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestArrayBlock extends AbstractTestBlock @@ -126,16 +125,16 @@ public void testLazyBlockBuilderInitialization() ArrayBlockBuilder emptyBlockBuilder = new ArrayBlockBuilder(BIGINT, null, 0, 0); BlockBuilder blockBuilder = new ArrayBlockBuilder(BIGINT, null, 100, 100); - assertEquals(blockBuilder.getSizeInBytes(), emptyBlockBuilder.getSizeInBytes()); - assertEquals(blockBuilder.getRetainedSizeInBytes(), emptyBlockBuilder.getRetainedSizeInBytes()); + assertThat(blockBuilder.getSizeInBytes()).isEqualTo(emptyBlockBuilder.getSizeInBytes()); + assertThat(blockBuilder.getRetainedSizeInBytes()).isEqualTo(emptyBlockBuilder.getRetainedSizeInBytes()); writeValues(expectedValues, blockBuilder); - assertTrue(blockBuilder.getSizeInBytes() > emptyBlockBuilder.getSizeInBytes()); - assertTrue(blockBuilder.getRetainedSizeInBytes() > emptyBlockBuilder.getRetainedSizeInBytes()); + assertThat(blockBuilder.getSizeInBytes() > emptyBlockBuilder.getSizeInBytes()).isTrue(); + assertThat(blockBuilder.getRetainedSizeInBytes() > emptyBlockBuilder.getRetainedSizeInBytes()).isTrue(); blockBuilder = blockBuilder.newBlockBuilderLike(null); - assertEquals(blockBuilder.getSizeInBytes(), emptyBlockBuilder.getSizeInBytes()); - assertEquals(blockBuilder.getRetainedSizeInBytes(), emptyBlockBuilder.getRetainedSizeInBytes()); + assertThat(blockBuilder.getSizeInBytes()).isEqualTo(emptyBlockBuilder.getSizeInBytes()); + assertThat(blockBuilder.getRetainedSizeInBytes()).isEqualTo(emptyBlockBuilder.getRetainedSizeInBytes()); } @Test @@ -143,10 +142,10 @@ public void testEstimatedDataSizeForStats() { long[][][] expectedValues = alternatingNullValues(createExpectedValues()); Block block = createBlockBuilderWithValues(expectedValues).build(); - assertEquals(block.getPositionCount(), expectedValues.length); + assertThat(block.getPositionCount()).isEqualTo(expectedValues.length); for (int i = 0; i < block.getPositionCount(); i++) { int expectedSize = getExpectedEstimatedDataSize(expectedValues[i]); - assertEquals(block.getEstimatedDataSizeForStats(i), expectedSize); + assertThat(block.getEstimatedDataSizeForStats(i)).isEqualTo(expectedSize); } } diff --git a/core/trino-main/src/test/java/io/trino/block/TestBlockBuilder.java b/core/trino-main/src/test/java/io/trino/block/TestBlockBuilder.java index 7eb0dac72e12..be7306bbb572 100644 --- a/core/trino-main/src/test/java/io/trino/block/TestBlockBuilder.java +++ b/core/trino-main/src/test/java/io/trino/block/TestBlockBuilder.java @@ -30,10 +30,8 @@ import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.VarcharType.VARCHAR; import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotEquals; -import static org.testng.Assert.assertTrue; public class TestBlockBuilder { @@ -47,10 +45,10 @@ public void testMultipleValuesWithNull() BIGINT.writeLong(blockBuilder, 42); Block block = blockBuilder.build(); - assertTrue(block.isNull(0)); - assertEquals(BIGINT.getLong(block, 1), 42L); - assertTrue(block.isNull(2)); - assertEquals(BIGINT.getLong(block, 3), 42L); + assertThat(block.isNull(0)).isTrue(); + assertThat(BIGINT.getLong(block, 1)).isEqualTo(42L); + assertThat(block.isNull(2)).isTrue(); + assertThat(BIGINT.getLong(block, 3)).isEqualTo(42L); } @Test @@ -76,11 +74,12 @@ public void testNewBlockBuilderLike() PageBuilder newPageBuilder = pageBuilder.newPageBuilderLike(); for (int i = 0; i < channels.size(); i++) { - assertEquals(newPageBuilder.getType(i), pageBuilder.getType(i)); + assertThat(newPageBuilder.getType(i)).isEqualTo(pageBuilder.getType(i)); // we should get new block builder instances - assertNotEquals(pageBuilder.getBlockBuilder(i), newPageBuilder.getBlockBuilder(i)); - assertEquals(newPageBuilder.getBlockBuilder(i).getPositionCount(), 0); - assertTrue(newPageBuilder.getBlockBuilder(i).getRetainedSizeInBytes() < pageBuilder.getBlockBuilder(i).getRetainedSizeInBytes()); + assertThat(pageBuilder.getBlockBuilder(i)) + .isNotEqualTo(newPageBuilder.getBlockBuilder(i)); + assertThat(newPageBuilder.getBlockBuilder(i).getPositionCount()).isEqualTo(0); + assertThat(newPageBuilder.getBlockBuilder(i).getRetainedSizeInBytes() < pageBuilder.getBlockBuilder(i).getRetainedSizeInBytes()).isTrue(); } } @@ -117,7 +116,7 @@ public void testGetPositions() isIdentical.set(true); } }); - assertTrue(isIdentical.get()); + assertThat(isIdentical.get()).isTrue(); } private static Block buildBigintBlock(Integer... values) diff --git a/core/trino-main/src/test/java/io/trino/block/TestByteArrayBlock.java b/core/trino-main/src/test/java/io/trino/block/TestByteArrayBlock.java index 799ac8d5ca82..939ff10e4595 100644 --- a/core/trino-main/src/test/java/io/trino/block/TestByteArrayBlock.java +++ b/core/trino-main/src/test/java/io/trino/block/TestByteArrayBlock.java @@ -23,8 +23,7 @@ import java.util.Optional; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestByteArrayBlock extends AbstractTestBlock @@ -52,16 +51,16 @@ public void testLazyBlockBuilderInitialization() BlockBuilder emptyBlockBuilder = new ByteArrayBlockBuilder(null, 0); ByteArrayBlockBuilder blockBuilder = new ByteArrayBlockBuilder(null, expectedValues.length); - assertEquals(blockBuilder.getSizeInBytes(), emptyBlockBuilder.getSizeInBytes()); - assertEquals(blockBuilder.getRetainedSizeInBytes(), emptyBlockBuilder.getRetainedSizeInBytes()); + assertThat(blockBuilder.getSizeInBytes()).isEqualTo(emptyBlockBuilder.getSizeInBytes()); + assertThat(blockBuilder.getRetainedSizeInBytes()).isEqualTo(emptyBlockBuilder.getRetainedSizeInBytes()); writeValues(expectedValues, blockBuilder); - assertTrue(blockBuilder.getSizeInBytes() > emptyBlockBuilder.getSizeInBytes()); - assertTrue(blockBuilder.getRetainedSizeInBytes() > emptyBlockBuilder.getRetainedSizeInBytes()); + assertThat(blockBuilder.getSizeInBytes() > emptyBlockBuilder.getSizeInBytes()).isTrue(); + assertThat(blockBuilder.getRetainedSizeInBytes() > emptyBlockBuilder.getRetainedSizeInBytes()).isTrue(); blockBuilder = (ByteArrayBlockBuilder) blockBuilder.newBlockBuilderLike(null); - assertEquals(blockBuilder.getSizeInBytes(), emptyBlockBuilder.getSizeInBytes()); - assertEquals(blockBuilder.getRetainedSizeInBytes(), emptyBlockBuilder.getRetainedSizeInBytes()); + assertThat(blockBuilder.getSizeInBytes()).isEqualTo(emptyBlockBuilder.getSizeInBytes()); + assertThat(blockBuilder.getRetainedSizeInBytes()).isEqualTo(emptyBlockBuilder.getRetainedSizeInBytes()); } @Test diff --git a/core/trino-main/src/test/java/io/trino/block/TestDictionaryBlock.java b/core/trino-main/src/test/java/io/trino/block/TestDictionaryBlock.java index 012caad797f0..e36338ee5b9a 100644 --- a/core/trino-main/src/test/java/io/trino/block/TestDictionaryBlock.java +++ b/core/trino-main/src/test/java/io/trino/block/TestDictionaryBlock.java @@ -33,10 +33,6 @@ import static io.trino.block.BlockAssertions.createSlicesBlock; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotEquals; -import static org.testng.Assert.assertTrue; public class TestDictionaryBlock extends AbstractTestBlock @@ -84,7 +80,7 @@ public void testSizeInBytes() { Slice[] expectedValues = createExpectedValues(10); DictionaryBlock dictionaryBlock = createDictionaryBlock(expectedValues, 100); - assertEquals(dictionaryBlock.getSizeInBytes(), dictionaryBlock.getDictionary().getSizeInBytes() + (100 * SIZE_OF_INT)); + assertThat(dictionaryBlock.getSizeInBytes()).isEqualTo(dictionaryBlock.getDictionary().getSizeInBytes() + (100 * SIZE_OF_INT)); } @Test @@ -96,20 +92,20 @@ public void testLogicalSizeInBytes() // The dictionary within the dictionary block is expected to be a VariableWidthBlock of size 95 bytes. // 45 bytes for the expectedValues Slices (sum of seq(0,9)) and 50 bytes for the position and isNull array (total 10 positions). DictionaryBlock dictionaryBlock = createDictionaryBlock(expectedValues, 100); - assertEquals(dictionaryBlock.getDictionary().getLogicalSizeInBytes(), 95); + assertThat(dictionaryBlock.getDictionary().getLogicalSizeInBytes()).isEqualTo(95); // The 100 positions in the dictionary block index to 10 positions in the underlying dictionary (10 each). // Logical size calculation accounts for 4 bytes of offset and 1 byte of isNull. Therefore the expected unoptimized // size is 10 times the size of the underlying dictionary (VariableWidthBlock). - assertEquals(dictionaryBlock.getLogicalSizeInBytes(), 95 * 10); + assertThat(dictionaryBlock.getLogicalSizeInBytes()).isEqualTo(95 * 10); // With alternating nulls, we have 21 positions, with the same size calculation as above. dictionaryBlock = createDictionaryBlock(alternatingNullValues(expectedValues), 210); - assertEquals(dictionaryBlock.getDictionary().getPositionCount(), 21); - assertEquals(dictionaryBlock.getDictionary().getLogicalSizeInBytes(), 150); + assertThat(dictionaryBlock.getDictionary().getPositionCount()).isEqualTo(21); + assertThat(dictionaryBlock.getDictionary().getLogicalSizeInBytes()).isEqualTo(150); // The null positions should be included in the logical size. - assertEquals(dictionaryBlock.getLogicalSizeInBytes(), 150 * 10); + assertThat(dictionaryBlock.getLogicalSizeInBytes()).isEqualTo(150 * 10); } @Test @@ -119,7 +115,7 @@ public void testCopyRegionCreatesCompactBlock() DictionaryBlock dictionaryBlock = createDictionaryBlock(expectedValues, 100); DictionaryBlock copyRegionDictionaryBlock = (DictionaryBlock) dictionaryBlock.copyRegion(1, 3); - assertTrue(copyRegionDictionaryBlock.isCompact()); + assertThat(copyRegionDictionaryBlock.isCompact()).isTrue(); } @Test @@ -132,8 +128,8 @@ public void testCopyPositionsWithCompaction() int[] positionsToCopy = new int[] {0, 10, 20, 30, 40}; DictionaryBlock copiedBlock = (DictionaryBlock) dictionaryBlock.copyPositions(positionsToCopy, 0, positionsToCopy.length); - assertEquals(copiedBlock.getDictionary().getPositionCount(), 1); - assertEquals(copiedBlock.getPositionCount(), positionsToCopy.length); + assertThat(copiedBlock.getDictionary().getPositionCount()).isEqualTo(1); + assertThat(copiedBlock.getPositionCount()).isEqualTo(positionsToCopy.length); assertBlock(copiedBlock.getDictionary(), new Slice[] {firstExpectedValue}); assertBlock(copiedBlock, new Slice[] { firstExpectedValue, firstExpectedValue, firstExpectedValue, firstExpectedValue, firstExpectedValue}); @@ -148,8 +144,8 @@ public void testCopyPositionsWithCompactionsAndReorder() DictionaryBlock copiedBlock = (DictionaryBlock) dictionaryBlock.copyPositions(positionsToCopy, 0, positionsToCopy.length); - assertEquals(copiedBlock.getDictionary().getPositionCount(), 2); - assertEquals(copiedBlock.getPositionCount(), positionsToCopy.length); + assertThat(copiedBlock.getDictionary().getPositionCount()).isEqualTo(2); + assertThat(copiedBlock.getPositionCount()).isEqualTo(positionsToCopy.length); assertBlock(copiedBlock.getDictionary(), new Slice[] {expectedValues[0], expectedValues[5]}); assertDictionaryIds(copiedBlock, 0, 1, 0, 1, 0); @@ -164,8 +160,8 @@ public void testCopyPositionsSamePosition() DictionaryBlock copiedBlock = (DictionaryBlock) dictionaryBlock.copyPositions(positionsToCopy, 0, positionsToCopy.length); - assertEquals(copiedBlock.getDictionary().getPositionCount(), 1); - assertEquals(copiedBlock.getPositionCount(), positionsToCopy.length); + assertThat(copiedBlock.getDictionary().getPositionCount()).isEqualTo(1); + assertThat(copiedBlock.getPositionCount()).isEqualTo(positionsToCopy.length); assertBlock(copiedBlock.getDictionary(), new Slice[] {expectedValues[2]}); assertDictionaryIds(copiedBlock, 0, 0, 0); @@ -180,7 +176,7 @@ public void testCopyPositionsNoCompaction() int[] positionsToCopy = new int[] {0, 2, 4, 5}; DictionaryBlock copiedBlock = (DictionaryBlock) dictionaryBlock.copyPositions(positionsToCopy, 0, positionsToCopy.length); - assertEquals(copiedBlock.getPositionCount(), positionsToCopy.length); + assertThat(copiedBlock.getPositionCount()).isEqualTo(positionsToCopy.length); assertBlock(copiedBlock.getDictionary(), expectedValues); } @@ -190,17 +186,18 @@ public void testCompact() Slice[] expectedValues = createExpectedValues(5); DictionaryBlock dictionaryBlock = createDictionaryBlockWithUnreferencedKeys(expectedValues, 10); - assertEquals(dictionaryBlock.isCompact(), false); + assertThat(dictionaryBlock.isCompact()).isEqualTo(false); DictionaryBlock compactBlock = dictionaryBlock.compact(); - assertNotEquals(dictionaryBlock.getDictionarySourceId(), compactBlock.getDictionarySourceId()); + assertThat(dictionaryBlock.getDictionarySourceId()) + .isNotEqualTo(compactBlock.getDictionarySourceId()); - assertEquals(compactBlock.getDictionary().getPositionCount(), (expectedValues.length / 2) + 1); + assertThat(compactBlock.getDictionary().getPositionCount()).isEqualTo((expectedValues.length / 2) + 1); assertBlock(compactBlock.getDictionary(), new Slice[] {expectedValues[0], expectedValues[1], expectedValues[3]}); assertDictionaryIds(compactBlock, 0, 1, 1, 2, 2, 0, 1, 1, 2, 2); - assertEquals(compactBlock.isCompact(), true); + assertThat(compactBlock.isCompact()).isEqualTo(true); DictionaryBlock reCompactedBlock = compactBlock.compact(); - assertEquals(reCompactedBlock.getDictionarySourceId(), compactBlock.getDictionarySourceId()); + assertThat(reCompactedBlock.getDictionarySourceId()).isEqualTo(compactBlock.getDictionarySourceId()); } @Test @@ -211,12 +208,12 @@ public void testCompactAllKeysReferenced() DictionaryBlock compactBlock = dictionaryBlock.compact(); // When there is nothing to compact, we return the same block - assertEquals(compactBlock.getDictionary(), dictionaryBlock.getDictionary()); - assertEquals(compactBlock.getPositionCount(), dictionaryBlock.getPositionCount()); + assertThat(compactBlock.getDictionary()).isEqualTo(dictionaryBlock.getDictionary()); + assertThat(compactBlock.getPositionCount()).isEqualTo(dictionaryBlock.getPositionCount()); for (int position = 0; position < compactBlock.getPositionCount(); position++) { - assertEquals(compactBlock.getId(position), dictionaryBlock.getId(position)); + assertThat(compactBlock.getId(position)).isEqualTo(dictionaryBlock.getId(position)); } - assertEquals(compactBlock.isCompact(), true); + assertThat(compactBlock.isCompact()).isEqualTo(true); } @Test @@ -231,28 +228,28 @@ public void testBasicGetPositions() // first getPositions dictionaryBlock = dictionaryBlock.getPositions(new int[] {0, 8, 1, 2, 4, 5, 7, 9}, 2, 4); assertBlock(dictionaryBlock, new Slice[] {expectedValues[1], expectedValues[2], expectedValues[4], expectedValues[5]}); - assertEquals(((DictionaryBlock) dictionaryBlock).getDictionarySourceId(), dictionaryId); + assertThat(((DictionaryBlock) dictionaryBlock).getDictionarySourceId()).isEqualTo(dictionaryId); // second getPositions dictionaryBlock = dictionaryBlock.getPositions(new int[] {0, 1, 3, 0, 0}, 0, 3); assertBlock(dictionaryBlock, new Slice[] {expectedValues[1], expectedValues[2], expectedValues[5]}); - assertEquals(((DictionaryBlock) dictionaryBlock).getDictionarySourceId(), dictionaryId); + assertThat(((DictionaryBlock) dictionaryBlock).getDictionarySourceId()).isEqualTo(dictionaryId); // third getPositions; we do not validate if -1 is an invalid position dictionaryBlock = dictionaryBlock.getPositions(new int[] {-1, -1, 0, 1, 2}, 2, 3); assertBlock(dictionaryBlock, new Slice[] {expectedValues[1], expectedValues[2], expectedValues[5]}); - assertEquals(((DictionaryBlock) dictionaryBlock).getDictionarySourceId(), dictionaryId); + assertThat(((DictionaryBlock) dictionaryBlock).getDictionarySourceId()).isEqualTo(dictionaryId); // mixed getPositions dictionaryBlock = dictionaryBlock.getPositions(new int[] {0, 2, 2}, 0, 3); assertBlock(dictionaryBlock, new Slice[] {expectedValues[1], expectedValues[5], expectedValues[5]}); - assertEquals(((DictionaryBlock) dictionaryBlock).getDictionarySourceId(), dictionaryId); + assertThat(((DictionaryBlock) dictionaryBlock).getDictionarySourceId()).isEqualTo(dictionaryId); // duplicated getPositions dictionaryBlock = dictionaryBlock.getPositions(new int[] {1, 1, 1, 1, 1}, 0, 5); assertBlock(dictionaryBlock, new Slice[] { expectedValues[5], expectedValues[5], expectedValues[5], expectedValues[5], expectedValues[5]}); - assertEquals(((DictionaryBlock) dictionaryBlock).getDictionarySourceId(), dictionaryId); + assertThat(((DictionaryBlock) dictionaryBlock).getDictionarySourceId()).isEqualTo(dictionaryId); // out of range final Block finalDictionaryBlock = dictionaryBlock; @@ -283,38 +280,38 @@ public void testCompactGetPositions() // 3, 3, 4, 5, 2, 0, 1, 1 block = (DictionaryBlock) block.getPositions(new int[] {3, 3, 4, 5, 2, 0, 1, 1}, 0, 7); - assertTrue(block.isCompact()); + assertThat(block.isCompact()).isTrue(); // 3, 3, 4, 5, 2, 0, 1, 1, 0, 2, 5, 4, 3 block = (DictionaryBlock) block.getPositions(new int[] {0, 1, 2, 3, 4, 5, 6, 6, 5, 4, 3, 2, 1}, 0, 12); - assertTrue(block.isCompact()); + assertThat(block.isCompact()).isTrue(); // 3, 4, 3, 4, 3 block = (DictionaryBlock) block.getPositions(new int[] {0, 2, 0, 2, 0}, 0, 5); - assertFalse(block.isCompact()); + assertThat(block.isCompact()).isFalse(); block = block.compact(); // 3, 4, 4, 4 block = (DictionaryBlock) block.getPositions(new int[] {0, 1, 1, 1}, 0, 4); - assertTrue(block.isCompact()); + assertThat(block.isCompact()).isTrue(); // 4, 4, 4, 4 block = (DictionaryBlock) block.getPositions(new int[] {1, 1, 1, 1}, 0, 4); - assertFalse(block.isCompact()); + assertThat(block.isCompact()).isFalse(); block = block.compact(); // 4 block = (DictionaryBlock) block.getPositions(new int[] {0}, 0, 1); - assertTrue(block.isCompact()); + assertThat(block.isCompact()).isTrue(); // empty block = (DictionaryBlock) block.getPositions(new int[] {}, 0, 0); - assertFalse(block.isCompact()); + assertThat(block.isCompact()).isFalse(); block = block.compact(); // empty block = (DictionaryBlock) block.getPositions(new int[] {}, 0, 0); - assertTrue(block.isCompact()); + assertThat(block.isCompact()).isTrue(); } @Test @@ -325,7 +322,7 @@ public void testEstimatedDataSizeForStats() Slice[] expectedValues = createExpectedValues(positionCount); DictionaryBlock dictionaryBlock = createDictionaryBlock(expectedValues, dictionaryPositionCount); for (int position = 0; position < dictionaryPositionCount; position++) { - assertEquals(dictionaryBlock.getEstimatedDataSizeForStats(position), expectedValues[position % positionCount].length()); + assertThat(dictionaryBlock.getEstimatedDataSizeForStats(position)).isEqualTo(expectedValues[position % positionCount].length()); } } @@ -344,7 +341,7 @@ private static void assertDictionarySizeMethods(Block block) assertThat(positions > 0).isTrue(); int[] allIds = IntStream.range(0, positions).toArray(); - assertEquals(DictionaryBlock.create(allIds.length, block, allIds).getSizeInBytes(), block.getSizeInBytes() + (Integer.BYTES * (long) positions)); + assertThat(DictionaryBlock.create(allIds.length, block, allIds).getSizeInBytes()).isEqualTo(block.getSizeInBytes() + (Integer.BYTES * (long) positions)); int firstHalfLength = positions / 2; int secondHalfLength = positions - firstHalfLength; @@ -353,27 +350,15 @@ private static void assertDictionarySizeMethods(Block block) boolean[] selectedPositions = new boolean[positions]; selectedPositions[0] = true; - assertEquals( - DictionaryBlock.create(allIds.length, block, allIds).getPositionsSizeInBytes(selectedPositions, 1), - block.getPositionsSizeInBytes(selectedPositions, 1) + Integer.BYTES); + assertThat(DictionaryBlock.create(allIds.length, block, allIds).getPositionsSizeInBytes(selectedPositions, 1)).isEqualTo(block.getPositionsSizeInBytes(selectedPositions, 1) + Integer.BYTES); Arrays.fill(selectedPositions, true); - assertEquals( - DictionaryBlock.create(allIds.length, block, allIds).getPositionsSizeInBytes(selectedPositions, positions), - block.getSizeInBytes() + (Integer.BYTES * (long) positions)); - - assertEquals( - DictionaryBlock.create(firstHalfIds.length, block, firstHalfIds).getSizeInBytes(), - block.getRegionSizeInBytes(0, firstHalfLength) + (Integer.BYTES * (long) firstHalfLength)); - assertEquals( - DictionaryBlock.create(secondHalfIds.length, block, secondHalfIds).getSizeInBytes(), - block.getRegionSizeInBytes(firstHalfLength, secondHalfLength) + (Integer.BYTES * (long) secondHalfLength)); - assertEquals( - DictionaryBlock.create(allIds.length, block, allIds).getRegionSizeInBytes(0, firstHalfLength), - block.getRegionSizeInBytes(0, firstHalfLength) + (Integer.BYTES * (long) firstHalfLength)); - assertEquals( - DictionaryBlock.create(allIds.length, block, allIds).getRegionSizeInBytes(firstHalfLength, secondHalfLength), - block.getRegionSizeInBytes(firstHalfLength, secondHalfLength) + (Integer.BYTES * (long) secondHalfLength)); + assertThat(DictionaryBlock.create(allIds.length, block, allIds).getPositionsSizeInBytes(selectedPositions, positions)).isEqualTo(block.getSizeInBytes() + (Integer.BYTES * (long) positions)); + + assertThat(DictionaryBlock.create(firstHalfIds.length, block, firstHalfIds).getSizeInBytes()).isEqualTo(block.getRegionSizeInBytes(0, firstHalfLength) + (Integer.BYTES * (long) firstHalfLength)); + assertThat(DictionaryBlock.create(secondHalfIds.length, block, secondHalfIds).getSizeInBytes()).isEqualTo(block.getRegionSizeInBytes(firstHalfLength, secondHalfLength) + (Integer.BYTES * (long) secondHalfLength)); + assertThat(DictionaryBlock.create(allIds.length, block, allIds).getRegionSizeInBytes(0, firstHalfLength)).isEqualTo(block.getRegionSizeInBytes(0, firstHalfLength) + (Integer.BYTES * (long) firstHalfLength)); + assertThat(DictionaryBlock.create(allIds.length, block, allIds).getRegionSizeInBytes(firstHalfLength, secondHalfLength)).isEqualTo(block.getRegionSizeInBytes(firstHalfLength, secondHalfLength) + (Integer.BYTES * (long) secondHalfLength)); } private static DictionaryBlock createDictionaryBlockWithUnreferencedKeys(Slice[] expectedValues, int positionCount) @@ -413,9 +398,9 @@ private static BlockBuilder createBlockBuilder() private static void assertDictionaryIds(DictionaryBlock dictionaryBlock, int... expected) { - assertEquals(dictionaryBlock.getPositionCount(), expected.length); + assertThat(dictionaryBlock.getPositionCount()).isEqualTo(expected.length); for (int position = 0; position < dictionaryBlock.getPositionCount(); position++) { - assertEquals(dictionaryBlock.getId(position), expected[position]); + assertThat(dictionaryBlock.getId(position)).isEqualTo(expected[position]); } } } diff --git a/core/trino-main/src/test/java/io/trino/block/TestFixed12Block.java b/core/trino-main/src/test/java/io/trino/block/TestFixed12Block.java index e8db36527c0b..5bf5867aa736 100644 --- a/core/trino-main/src/test/java/io/trino/block/TestFixed12Block.java +++ b/core/trino-main/src/test/java/io/trino/block/TestFixed12Block.java @@ -23,8 +23,7 @@ import java.util.Optional; import static io.trino.spi.block.Fixed12Block.FIXED12_BYTES; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestFixed12Block extends AbstractTestBlock @@ -52,16 +51,16 @@ public void testLazyBlockBuilderInitialization() Fixed12BlockBuilder emptyBlockBuilder = new Fixed12BlockBuilder(null, 0); Fixed12BlockBuilder blockBuilder = new Fixed12BlockBuilder(null, expectedValues.length); - assertEquals(blockBuilder.getSizeInBytes(), emptyBlockBuilder.getSizeInBytes()); - assertEquals(blockBuilder.getRetainedSizeInBytes(), emptyBlockBuilder.getRetainedSizeInBytes()); + assertThat(blockBuilder.getSizeInBytes()).isEqualTo(emptyBlockBuilder.getSizeInBytes()); + assertThat(blockBuilder.getRetainedSizeInBytes()).isEqualTo(emptyBlockBuilder.getRetainedSizeInBytes()); writeValues(expectedValues, blockBuilder); - assertTrue(blockBuilder.getSizeInBytes() > emptyBlockBuilder.getSizeInBytes()); - assertTrue(blockBuilder.getRetainedSizeInBytes() > emptyBlockBuilder.getRetainedSizeInBytes()); + assertThat(blockBuilder.getSizeInBytes() > emptyBlockBuilder.getSizeInBytes()).isTrue(); + assertThat(blockBuilder.getRetainedSizeInBytes() > emptyBlockBuilder.getRetainedSizeInBytes()).isTrue(); blockBuilder = (Fixed12BlockBuilder) blockBuilder.newBlockBuilderLike(null); - assertEquals(blockBuilder.getSizeInBytes(), emptyBlockBuilder.getSizeInBytes()); - assertEquals(blockBuilder.getRetainedSizeInBytes(), emptyBlockBuilder.getRetainedSizeInBytes()); + assertThat(blockBuilder.getSizeInBytes()).isEqualTo(emptyBlockBuilder.getSizeInBytes()); + assertThat(blockBuilder.getRetainedSizeInBytes()).isEqualTo(emptyBlockBuilder.getRetainedSizeInBytes()); } @Test @@ -127,8 +126,8 @@ private static Slice[] createTestValue(int positionCount) @Override protected void assertPositionEquals(Block block, int position, Slice expectedBytes) { - assertEquals(block.getLong(position, 0), expectedBytes.getLong(0)); - assertEquals(block.getInt(position, 8), expectedBytes.getInt(8)); + assertThat(block.getLong(position, 0)).isEqualTo(expectedBytes.getLong(0)); + assertThat(block.getInt(position, 8)).isEqualTo(expectedBytes.getInt(8)); } @Override diff --git a/core/trino-main/src/test/java/io/trino/block/TestInt128ArrayBlock.java b/core/trino-main/src/test/java/io/trino/block/TestInt128ArrayBlock.java index c58a77b46bbc..f36f35678d87 100644 --- a/core/trino-main/src/test/java/io/trino/block/TestInt128ArrayBlock.java +++ b/core/trino-main/src/test/java/io/trino/block/TestInt128ArrayBlock.java @@ -23,8 +23,7 @@ import java.util.Optional; import static io.trino.spi.block.Int128ArrayBlock.INT128_BYTES; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestInt128ArrayBlock extends AbstractTestBlock @@ -52,16 +51,16 @@ public void testLazyBlockBuilderInitialization() BlockBuilder emptyBlockBuilder = new Int128ArrayBlockBuilder(null, 0); BlockBuilder blockBuilder = new Int128ArrayBlockBuilder(null, expectedValues.length); - assertEquals(blockBuilder.getSizeInBytes(), emptyBlockBuilder.getSizeInBytes()); - assertEquals(blockBuilder.getRetainedSizeInBytes(), emptyBlockBuilder.getRetainedSizeInBytes()); + assertThat(blockBuilder.getSizeInBytes()).isEqualTo(emptyBlockBuilder.getSizeInBytes()); + assertThat(blockBuilder.getRetainedSizeInBytes()).isEqualTo(emptyBlockBuilder.getRetainedSizeInBytes()); writeValues(expectedValues, blockBuilder); - assertTrue(blockBuilder.getSizeInBytes() > emptyBlockBuilder.getSizeInBytes()); - assertTrue(blockBuilder.getRetainedSizeInBytes() > emptyBlockBuilder.getRetainedSizeInBytes()); + assertThat(blockBuilder.getSizeInBytes() > emptyBlockBuilder.getSizeInBytes()).isTrue(); + assertThat(blockBuilder.getRetainedSizeInBytes() > emptyBlockBuilder.getRetainedSizeInBytes()).isTrue(); blockBuilder = blockBuilder.newBlockBuilderLike(null); - assertEquals(blockBuilder.getSizeInBytes(), emptyBlockBuilder.getSizeInBytes()); - assertEquals(blockBuilder.getRetainedSizeInBytes(), emptyBlockBuilder.getRetainedSizeInBytes()); + assertThat(blockBuilder.getSizeInBytes()).isEqualTo(emptyBlockBuilder.getSizeInBytes()); + assertThat(blockBuilder.getRetainedSizeInBytes()).isEqualTo(emptyBlockBuilder.getRetainedSizeInBytes()); } @Test diff --git a/core/trino-main/src/test/java/io/trino/block/TestIntArrayBlock.java b/core/trino-main/src/test/java/io/trino/block/TestIntArrayBlock.java index 14359b95913f..210fd1146bf1 100644 --- a/core/trino-main/src/test/java/io/trino/block/TestIntArrayBlock.java +++ b/core/trino-main/src/test/java/io/trino/block/TestIntArrayBlock.java @@ -23,8 +23,7 @@ import java.util.Optional; import static io.airlift.slice.SizeOf.SIZE_OF_INT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestIntArrayBlock extends AbstractTestBlock @@ -52,16 +51,16 @@ public void testLazyBlockBuilderInitialization() BlockBuilder emptyBlockBuilder = new IntArrayBlockBuilder(null, 0); IntArrayBlockBuilder blockBuilder = new IntArrayBlockBuilder(null, expectedValues.length); - assertEquals(blockBuilder.getSizeInBytes(), emptyBlockBuilder.getSizeInBytes()); - assertEquals(blockBuilder.getRetainedSizeInBytes(), emptyBlockBuilder.getRetainedSizeInBytes()); + assertThat(blockBuilder.getSizeInBytes()).isEqualTo(emptyBlockBuilder.getSizeInBytes()); + assertThat(blockBuilder.getRetainedSizeInBytes()).isEqualTo(emptyBlockBuilder.getRetainedSizeInBytes()); writeValues(expectedValues, blockBuilder); - assertTrue(blockBuilder.getSizeInBytes() > emptyBlockBuilder.getSizeInBytes()); - assertTrue(blockBuilder.getRetainedSizeInBytes() > emptyBlockBuilder.getRetainedSizeInBytes()); + assertThat(blockBuilder.getSizeInBytes() > emptyBlockBuilder.getSizeInBytes()).isTrue(); + assertThat(blockBuilder.getRetainedSizeInBytes() > emptyBlockBuilder.getRetainedSizeInBytes()).isTrue(); blockBuilder = (IntArrayBlockBuilder) blockBuilder.newBlockBuilderLike(null); - assertEquals(blockBuilder.getSizeInBytes(), emptyBlockBuilder.getSizeInBytes()); - assertEquals(blockBuilder.getRetainedSizeInBytes(), emptyBlockBuilder.getRetainedSizeInBytes()); + assertThat(blockBuilder.getSizeInBytes()).isEqualTo(emptyBlockBuilder.getSizeInBytes()); + assertThat(blockBuilder.getRetainedSizeInBytes()).isEqualTo(emptyBlockBuilder.getRetainedSizeInBytes()); } @Test diff --git a/core/trino-main/src/test/java/io/trino/block/TestLongArrayBlock.java b/core/trino-main/src/test/java/io/trino/block/TestLongArrayBlock.java index 1c8bf099c9a1..9d38a3cb455c 100644 --- a/core/trino-main/src/test/java/io/trino/block/TestLongArrayBlock.java +++ b/core/trino-main/src/test/java/io/trino/block/TestLongArrayBlock.java @@ -23,8 +23,7 @@ import java.util.Optional; import static io.airlift.slice.SizeOf.SIZE_OF_LONG; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestLongArrayBlock extends AbstractTestBlock @@ -52,16 +51,16 @@ public void testLazyBlockBuilderInitialization() BlockBuilder emptyBlockBuilder = new LongArrayBlockBuilder(null, 0); LongArrayBlockBuilder blockBuilder = new LongArrayBlockBuilder(null, expectedValues.length); - assertEquals(blockBuilder.getSizeInBytes(), emptyBlockBuilder.getSizeInBytes()); - assertEquals(blockBuilder.getRetainedSizeInBytes(), emptyBlockBuilder.getRetainedSizeInBytes()); + assertThat(blockBuilder.getSizeInBytes()).isEqualTo(emptyBlockBuilder.getSizeInBytes()); + assertThat(blockBuilder.getRetainedSizeInBytes()).isEqualTo(emptyBlockBuilder.getRetainedSizeInBytes()); writeValues(expectedValues, blockBuilder); - assertTrue(blockBuilder.getSizeInBytes() > emptyBlockBuilder.getSizeInBytes()); - assertTrue(blockBuilder.getRetainedSizeInBytes() > emptyBlockBuilder.getRetainedSizeInBytes()); + assertThat(blockBuilder.getSizeInBytes() > emptyBlockBuilder.getSizeInBytes()).isTrue(); + assertThat(blockBuilder.getRetainedSizeInBytes() > emptyBlockBuilder.getRetainedSizeInBytes()).isTrue(); blockBuilder = (LongArrayBlockBuilder) blockBuilder.newBlockBuilderLike(null); - assertEquals(blockBuilder.getSizeInBytes(), emptyBlockBuilder.getSizeInBytes()); - assertEquals(blockBuilder.getRetainedSizeInBytes(), emptyBlockBuilder.getRetainedSizeInBytes()); + assertThat(blockBuilder.getSizeInBytes()).isEqualTo(emptyBlockBuilder.getSizeInBytes()); + assertThat(blockBuilder.getRetainedSizeInBytes()).isEqualTo(emptyBlockBuilder.getRetainedSizeInBytes()); } @Test diff --git a/core/trino-main/src/test/java/io/trino/block/TestMapBlock.java b/core/trino-main/src/test/java/io/trino/block/TestMapBlock.java index 7f560fd067a1..9052e58ddacd 100644 --- a/core/trino-main/src/test/java/io/trino/block/TestMapBlock.java +++ b/core/trino-main/src/test/java/io/trino/block/TestMapBlock.java @@ -39,11 +39,8 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.util.StructuralTestUtil.mapType; import static java.util.Objects.requireNonNull; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotEquals; -import static org.testng.Assert.assertTrue; public class TestMapBlock extends AbstractTestBlock @@ -91,16 +88,16 @@ private void assertLazyHashTableBuildOverBlockRegion(Map[] testVal private void testLazyGetPositionsHashTable(Map[] testValues) { MapBlock block = createBlockWithValuesFromKeyValueBlock(testValues); - assertFalse(block.isHashTablesPresent()); + assertThat(block.isHashTablesPresent()).isFalse(); int[] testPositions = {7, 1, 5, 2, 3, 7}; Block prefix = block.getPositions(testPositions, 0, testPositions.length); - assertFalse(block.isHashTablesPresent()); + assertThat(block.isHashTablesPresent()).isFalse(); assertBlock(prefix, getArrayPositions(testValues, testPositions)); - assertTrue(block.isHashTablesPresent()); + assertThat(block.isHashTablesPresent()).isTrue(); } private static Map[] getArrayPositions(Map[] testPositions, int[] positions) @@ -116,24 +113,24 @@ private static Map[] getArrayPositions(Map[] testPos private void testLazyBeginningGetRegionHashTable(Map[] testValues) { MapBlock block = createBlockWithValuesFromKeyValueBlock(testValues); - assertFalse(block.isHashTablesPresent()); + assertThat(block.isHashTablesPresent()).isFalse(); MapBlock prefix = (MapBlock) block.getRegion(0, 4); - assertFalse(block.isHashTablesPresent()); - assertFalse(prefix.isHashTablesPresent()); + assertThat(block.isHashTablesPresent()).isFalse(); + assertThat(prefix.isHashTablesPresent()).isFalse(); assertBlock(prefix, Arrays.copyOfRange(testValues, 0, 4)); - assertTrue(block.isHashTablesPresent()); - assertTrue(prefix.isHashTablesPresent()); + assertThat(block.isHashTablesPresent()).isTrue(); + assertThat(prefix.isHashTablesPresent()).isTrue(); MapBlock midSection = (MapBlock) block.getRegion(2, 4); - assertTrue(midSection.isHashTablesPresent()); + assertThat(midSection.isHashTablesPresent()).isTrue(); assertBlock(midSection, Arrays.copyOfRange(testValues, 2, 6)); MapBlock suffix = (MapBlock) block.getRegion(4, 4); - assertTrue(suffix.isHashTablesPresent()); + assertThat(suffix.isHashTablesPresent()).isTrue(); assertBlock(suffix, Arrays.copyOfRange(testValues, 4, 8)); } @@ -143,20 +140,20 @@ private void testLazyMiddleGetRegionHashTable(Map[] testValues) MapBlock midSection = (MapBlock) block.getRegion(2, 4); - assertFalse(block.isHashTablesPresent()); - assertFalse(midSection.isHashTablesPresent()); + assertThat(block.isHashTablesPresent()).isFalse(); + assertThat(midSection.isHashTablesPresent()).isFalse(); assertBlock(midSection, Arrays.copyOfRange(testValues, 2, 6)); - assertTrue(block.isHashTablesPresent()); - assertTrue(midSection.isHashTablesPresent()); + assertThat(block.isHashTablesPresent()).isTrue(); + assertThat(midSection.isHashTablesPresent()).isTrue(); MapBlock prefix = (MapBlock) block.getRegion(0, 4); - assertTrue(prefix.isHashTablesPresent()); + assertThat(prefix.isHashTablesPresent()).isTrue(); assertBlock(prefix, Arrays.copyOfRange(testValues, 0, 4)); MapBlock suffix = (MapBlock) block.getRegion(4, 4); - assertTrue(suffix.isHashTablesPresent()); + assertThat(suffix.isHashTablesPresent()).isTrue(); assertBlock(suffix, Arrays.copyOfRange(testValues, 4, 8)); } @@ -166,20 +163,20 @@ private void testLazyEndGetRegionHashTable(Map[] testValues) MapBlock suffix = (MapBlock) block.getRegion(4, 4); - assertFalse(block.isHashTablesPresent()); - assertFalse(suffix.isHashTablesPresent()); + assertThat(block.isHashTablesPresent()).isFalse(); + assertThat(suffix.isHashTablesPresent()).isFalse(); assertBlock(suffix, Arrays.copyOfRange(testValues, 4, 8)); - assertTrue(block.isHashTablesPresent()); - assertTrue(suffix.isHashTablesPresent()); + assertThat(block.isHashTablesPresent()).isTrue(); + assertThat(suffix.isHashTablesPresent()).isTrue(); MapBlock prefix = (MapBlock) block.getRegion(0, 4); - assertTrue(prefix.isHashTablesPresent()); + assertThat(prefix.isHashTablesPresent()).isTrue(); assertBlock(prefix, Arrays.copyOfRange(testValues, 0, 4)); MapBlock midSection = (MapBlock) block.getRegion(2, 4); - assertTrue(midSection.isHashTablesPresent()); + assertThat(midSection.isHashTablesPresent()).isTrue(); assertBlock(midSection, Arrays.copyOfRange(testValues, 2, 6)); } @@ -207,7 +204,7 @@ private void testWith(Map[] expectedValues) assertBlockFilteredPositions(expectedValues, blockBuilder.build(), 2, 3, 5, 6); Block block = createBlockWithValuesFromKeyValueBlock(expectedValues); - assertFalse(block.mayHaveNull()); + assertThat(block.mayHaveNull()).isFalse(); assertBlock(block, expectedValues); assertBlockFilteredPositions(expectedValues, block, 0, 1, 3, 4, 7); @@ -221,7 +218,7 @@ private void testWith(Map[] expectedValues) assertBlockFilteredPositions(expectedValuesWithNull, blockBuilderWithNull.build(), 2, 3, 4, 9, 13, 14); Block blockWithNull = createBlockWithValuesFromKeyValueBlock(expectedValuesWithNull); - assertTrue(blockWithNull.mayHaveNull()); + assertThat(blockWithNull.mayHaveNull()).isTrue(); assertBlock(blockWithNull, expectedValuesWithNull); assertBlockFilteredPositions(expectedValuesWithNull, blockWithNull, 0, 1, 5, 6, 7, 10, 11, 12, 15); @@ -305,28 +302,29 @@ private void assertValue(Block mapBlock, int position, Map map) // null maps are handled by assertPositionValue requireNonNull(map, "map is null"); - assertFalse(mapBlock.isNull(position)); + assertThat(mapBlock.isNull(position)).isFalse(); SqlMap sqlMap = mapType.getObject(mapBlock, position); int rawOffset = sqlMap.getRawOffset(); Block rawKeyBlock = sqlMap.getRawKeyBlock(); Block rawValueBlock = sqlMap.getRawValueBlock(); - assertEquals(sqlMap.getSize(), map.size()); + assertThat(sqlMap.getSize()).isEqualTo(map.size()); // Test new/hash-index access: assert inserted keys for (Map.Entry entry : map.entrySet()) { int index = sqlMap.seekKey(utf8Slice(entry.getKey())); - assertNotEquals(index, -1); + assertThat(index) + .isNotEqualTo(-1); if (entry.getValue() == null) { - assertTrue(rawValueBlock.isNull(rawOffset + index)); + assertThat(rawValueBlock.isNull(rawOffset + index)).isTrue(); } else { - assertFalse(rawValueBlock.isNull(rawOffset + index)); - assertEquals(BIGINT.getLong(rawValueBlock, rawOffset + index), (long) entry.getValue()); + assertThat(rawValueBlock.isNull(rawOffset + index)).isFalse(); + assertThat(BIGINT.getLong(rawValueBlock, rawOffset + index)).isEqualTo((long) entry.getValue()); } } // Test new/hash-index access: assert non-existent keys for (int i = 0; i < 10; i++) { - assertEquals(sqlMap.seekKey(utf8Slice("not-inserted-" + i)), -1); + assertThat(sqlMap.seekKey(utf8Slice("not-inserted-" + i))).isEqualTo(-1); } // Test legacy/iterative access @@ -339,8 +337,8 @@ private void assertValue(Block mapBlock, int position, Map map) else { actualValue = BIGINT.getLong(rawValueBlock, rawOffset + i); } - assertTrue(map.containsKey(actualKey)); - assertEquals(actualValue, map.get(actualKey)); + assertThat(map.containsKey(actualKey)).isTrue(); + assertThat(actualValue).isEqualTo(map.get(actualKey)); } } @@ -388,10 +386,10 @@ public void testEstimatedDataSizeForStats() Map[] expectedValues = alternatingNullValues(createTestMap(9, 3, 4, 0, 8, 0, 6, 5)); BlockBuilder blockBuilder = createBlockBuilderWithValues(expectedValues); Block block = blockBuilder.build(); - assertEquals(block.getPositionCount(), expectedValues.length); + assertThat(block.getPositionCount()).isEqualTo(expectedValues.length); for (int i = 0; i < block.getPositionCount(); i++) { int expectedSize = getExpectedEstimatedDataSize(expectedValues[i]); - assertEquals(block.getEstimatedDataSizeForStats(i), expectedSize); + assertThat(block.getEstimatedDataSizeForStats(i)).isEqualTo(expectedSize); } } diff --git a/core/trino-main/src/test/java/io/trino/block/TestRowBlock.java b/core/trino-main/src/test/java/io/trino/block/TestRowBlock.java index 839cd9deab76..399d57adc485 100644 --- a/core/trino-main/src/test/java/io/trino/block/TestRowBlock.java +++ b/core/trino-main/src/test/java/io/trino/block/TestRowBlock.java @@ -36,9 +36,7 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static java.lang.String.format; import static java.util.Objects.requireNonNull; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestRowBlock extends AbstractTestBlock @@ -59,10 +57,10 @@ public void testEstimatedDataSizeForStats() List fieldTypes = ImmutableList.of(VARCHAR, BIGINT); List[] expectedValues = alternatingNullValues(generateTestRows(fieldTypes, 100)); Block block = createBlockBuilderWithValues(fieldTypes, expectedValues).build(); - assertEquals(block.getPositionCount(), expectedValues.length); + assertThat(block.getPositionCount()).isEqualTo(expectedValues.length); for (int i = 0; i < block.getPositionCount(); i++) { int expectedSize = getExpectedEstimatedDataSize(expectedValues[i]); - assertEquals(block.getEstimatedDataSizeForStats(i), expectedSize); + assertThat(block.getEstimatedDataSizeForStats(i)).isEqualTo(expectedSize); } } @@ -71,17 +69,17 @@ public void testFromFieldBlocksNoNullsDetection() { // Blocks does not discard the null mask during creation if no values are null boolean[] rowIsNull = new boolean[5]; - assertTrue(fromNotNullSuppressedFieldBlocks(5, Optional.of(rowIsNull), new Block[]{new ByteArrayBlock(5, Optional.empty(), createExpectedValue(5).getBytes())}).mayHaveNull()); + assertThat(fromNotNullSuppressedFieldBlocks(5, Optional.of(rowIsNull), new Block[] {new ByteArrayBlock(5, Optional.empty(), createExpectedValue(5).getBytes())}).mayHaveNull()).isTrue(); rowIsNull[rowIsNull.length - 1] = true; - assertTrue(fromNotNullSuppressedFieldBlocks(5, Optional.of(rowIsNull), new Block[]{new ByteArrayBlock(5, Optional.of(rowIsNull), createExpectedValue(5).getBytes())}).mayHaveNull()); + assertThat(fromNotNullSuppressedFieldBlocks(5, Optional.of(rowIsNull), new Block[] {new ByteArrayBlock(5, Optional.of(rowIsNull), createExpectedValue(5).getBytes())}).mayHaveNull()).isTrue(); // Empty blocks have no nulls and can also discard their null mask - assertFalse(fromNotNullSuppressedFieldBlocks(0, Optional.of(new boolean[0]), new Block[]{new ByteArrayBlock(0, Optional.empty(), new byte[0])}).mayHaveNull()); + assertThat(fromNotNullSuppressedFieldBlocks(0, Optional.of(new boolean[0]), new Block[] {new ByteArrayBlock(0, Optional.empty(), new byte[0])}).mayHaveNull()).isFalse(); // Normal blocks should have null masks preserved List fieldTypes = ImmutableList.of(VARCHAR, BIGINT); Block hasNullsBlock = createBlockBuilderWithValues(fieldTypes, alternatingNullValues(generateTestRows(fieldTypes, 100))).build(); - assertTrue(hasNullsBlock.mayHaveNull()); + assertThat(hasNullsBlock.mayHaveNull()).isTrue(); } private int getExpectedEstimatedDataSize(List row) @@ -165,23 +163,23 @@ private void assertValue(Block rowBlock, int position, List row) // null rows are handled by assertPositionValue requireNonNull(row, "row is null"); - assertFalse(rowBlock.isNull(position)); + assertThat(rowBlock.isNull(position)).isFalse(); SqlRow sqlRow = rowBlock.getObject(position, SqlRow.class); - assertEquals(sqlRow.getFieldCount(), row.size()); + assertThat(sqlRow.getFieldCount()).isEqualTo(row.size()); int rawIndex = sqlRow.getRawIndex(); for (int i = 0; i < row.size(); i++) { Object fieldValue = row.get(i); Block rawFieldBlock = sqlRow.getRawFieldBlock(i); if (fieldValue == null) { - assertTrue(rawFieldBlock.isNull(rawIndex)); + assertThat(rawFieldBlock.isNull(rawIndex)).isTrue(); } else { if (fieldValue instanceof Long) { - assertEquals(BIGINT.getLong(rawFieldBlock, rawIndex), ((Long) fieldValue).longValue()); + assertThat(BIGINT.getLong(rawFieldBlock, rawIndex)).isEqualTo(((Long) fieldValue).longValue()); } else if (fieldValue instanceof String) { - assertEquals(VARCHAR.getSlice(rawFieldBlock, rawIndex), utf8Slice((String) fieldValue)); + assertThat(VARCHAR.getSlice(rawFieldBlock, rawIndex)).isEqualTo(utf8Slice((String) fieldValue)); } else { throw new IllegalArgumentException(); diff --git a/core/trino-main/src/test/java/io/trino/block/TestRunLengthEncodedBlock.java b/core/trino-main/src/test/java/io/trino/block/TestRunLengthEncodedBlock.java index fc4b869efc94..952fbaba6856 100644 --- a/core/trino-main/src/test/java/io/trino/block/TestRunLengthEncodedBlock.java +++ b/core/trino-main/src/test/java/io/trino/block/TestRunLengthEncodedBlock.java @@ -25,8 +25,7 @@ import io.trino.spi.block.VariableWidthBlockBuilder; import org.junit.jupiter.api.Test; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestRunLengthEncodedBlock extends AbstractTestBlock @@ -68,17 +67,17 @@ public void testPositionsSizeInBytes() Block valueBlock = createSingleValueBlock(createExpectedValue(10)); Block rleBlock = RunLengthEncodedBlock.create(valueBlock, 10); // Size in bytes is not fixed per position - assertTrue(rleBlock.fixedSizeInBytesPerPosition().isEmpty()); + assertThat(rleBlock.fixedSizeInBytesPerPosition().isEmpty()).isTrue(); // Accepts specific position selection boolean[] positions = new boolean[rleBlock.getPositionCount()]; positions[0] = true; positions[1] = true; - assertEquals(rleBlock.getPositionsSizeInBytes(positions, 2), valueBlock.getSizeInBytes()); + assertThat(rleBlock.getPositionsSizeInBytes(positions, 2)).isEqualTo(valueBlock.getSizeInBytes()); // Accepts null positions array with count only - assertEquals(rleBlock.getPositionsSizeInBytes(null, 2), valueBlock.getSizeInBytes()); + assertThat(rleBlock.getPositionsSizeInBytes(null, 2)).isEqualTo(valueBlock.getSizeInBytes()); // Always reports the same size in bytes regardless of positions for (int positionCount = 0; positionCount < rleBlock.getPositionCount(); positionCount++) { - assertEquals(rleBlock.getPositionsSizeInBytes(null, positionCount), valueBlock.getSizeInBytes()); + assertThat(rleBlock.getPositionsSizeInBytes(null, positionCount)).isEqualTo(valueBlock.getSizeInBytes()); } } @@ -87,7 +86,7 @@ public void testBuildingFromLongArrayBlockBuilder() { LongArrayBlockBuilder blockBuilder = new LongArrayBlockBuilder(null, 100); populateNullValues(blockBuilder, 100); - assertEquals(blockBuilder.build().getEncodingName(), RunLengthBlockEncoding.NAME); + assertThat(blockBuilder.build().getEncodingName()).isEqualTo(RunLengthBlockEncoding.NAME); } @Test @@ -95,7 +94,7 @@ public void testBuildingFromIntArrayBlockBuilder() { IntArrayBlockBuilder blockBuilder = new IntArrayBlockBuilder(null, 100); populateNullValues(blockBuilder, 100); - assertEquals(blockBuilder.build().getEncodingName(), RunLengthBlockEncoding.NAME); + assertThat(blockBuilder.build().getEncodingName()).isEqualTo(RunLengthBlockEncoding.NAME); } @Test @@ -103,7 +102,7 @@ public void testBuildingFromShortArrayBlockBuilder() { ShortArrayBlockBuilder blockBuilder = new ShortArrayBlockBuilder(null, 100); populateNullValues(blockBuilder, 100); - assertEquals(blockBuilder.build().getEncodingName(), RunLengthBlockEncoding.NAME); + assertThat(blockBuilder.build().getEncodingName()).isEqualTo(RunLengthBlockEncoding.NAME); } @Test @@ -111,7 +110,7 @@ public void testBuildingFromByteArrayBlockBuilder() { ByteArrayBlockBuilder blockBuilder = new ByteArrayBlockBuilder(null, 100); populateNullValues(blockBuilder, 100); - assertEquals(blockBuilder.build().getEncodingName(), RunLengthBlockEncoding.NAME); + assertThat(blockBuilder.build().getEncodingName()).isEqualTo(RunLengthBlockEncoding.NAME); } @Test @@ -121,7 +120,7 @@ public void testEstimatedDataSizeForStats() Slice expectedValue = createExpectedValue(5); Block block = RunLengthEncodedBlock.create(createSingleValueBlock(expectedValue), positionCount); for (int postition = 0; postition < positionCount; postition++) { - assertEquals(block.getEstimatedDataSizeForStats(postition), expectedValue.length()); + assertThat(block.getEstimatedDataSizeForStats(postition)).isEqualTo(expectedValue.length()); } } diff --git a/core/trino-main/src/test/java/io/trino/block/TestShortArrayBlock.java b/core/trino-main/src/test/java/io/trino/block/TestShortArrayBlock.java index 1bedaf8f905e..ddb7412515ad 100644 --- a/core/trino-main/src/test/java/io/trino/block/TestShortArrayBlock.java +++ b/core/trino-main/src/test/java/io/trino/block/TestShortArrayBlock.java @@ -23,8 +23,7 @@ import java.util.Optional; import static io.airlift.slice.SizeOf.SIZE_OF_SHORT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestShortArrayBlock extends AbstractTestBlock @@ -52,16 +51,16 @@ public void testLazyBlockBuilderInitialization() ShortArrayBlockBuilder emptyBlockBuilder = new ShortArrayBlockBuilder(null, 0); ShortArrayBlockBuilder blockBuilder = new ShortArrayBlockBuilder(null, expectedValues.length); - assertEquals(blockBuilder.getSizeInBytes(), emptyBlockBuilder.getSizeInBytes()); - assertEquals(blockBuilder.getRetainedSizeInBytes(), emptyBlockBuilder.getRetainedSizeInBytes()); + assertThat(blockBuilder.getSizeInBytes()).isEqualTo(emptyBlockBuilder.getSizeInBytes()); + assertThat(blockBuilder.getRetainedSizeInBytes()).isEqualTo(emptyBlockBuilder.getRetainedSizeInBytes()); writeValues(expectedValues, blockBuilder); - assertTrue(blockBuilder.getSizeInBytes() > emptyBlockBuilder.getSizeInBytes()); - assertTrue(blockBuilder.getRetainedSizeInBytes() > emptyBlockBuilder.getRetainedSizeInBytes()); + assertThat(blockBuilder.getSizeInBytes() > emptyBlockBuilder.getSizeInBytes()).isTrue(); + assertThat(blockBuilder.getRetainedSizeInBytes() > emptyBlockBuilder.getRetainedSizeInBytes()).isTrue(); blockBuilder = (ShortArrayBlockBuilder) blockBuilder.newBlockBuilderLike(null); - assertEquals(blockBuilder.getSizeInBytes(), emptyBlockBuilder.getSizeInBytes()); - assertEquals(blockBuilder.getRetainedSizeInBytes(), emptyBlockBuilder.getRetainedSizeInBytes()); + assertThat(blockBuilder.getSizeInBytes()).isEqualTo(emptyBlockBuilder.getSizeInBytes()); + assertThat(blockBuilder.getRetainedSizeInBytes()).isEqualTo(emptyBlockBuilder.getRetainedSizeInBytes()); } @Test diff --git a/core/trino-main/src/test/java/io/trino/block/TestVariableWidthBlock.java b/core/trino-main/src/test/java/io/trino/block/TestVariableWidthBlock.java index f28baf2a23f9..0f51ce75fbb4 100644 --- a/core/trino-main/src/test/java/io/trino/block/TestVariableWidthBlock.java +++ b/core/trino-main/src/test/java/io/trino/block/TestVariableWidthBlock.java @@ -29,8 +29,7 @@ import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; import static java.lang.String.format; import static java.util.Arrays.copyOfRange; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestVariableWidthBlock extends AbstractTestBlock @@ -50,8 +49,8 @@ public void testCopyRegion() Block block = createBlockBuilderWithValues(expectedValues).build(); Block actual = block.copyRegion(10, 10); Block expected = createBlockBuilderWithValues(copyOfRange(expectedValues, 10, 20)).build(); - assertEquals(actual.getPositionCount(), expected.getPositionCount()); - assertEquals(actual.getSizeInBytes(), expected.getSizeInBytes()); + assertThat(actual.getPositionCount()).isEqualTo(expected.getPositionCount()); + assertThat(actual.getSizeInBytes()).isEqualTo(expected.getSizeInBytes()); } @Test @@ -72,16 +71,16 @@ public void testLazyBlockBuilderInitialization() BlockBuilder emptyBlockBuilder = new VariableWidthBlockBuilder(null, 0, 0); BlockBuilder blockBuilder = new VariableWidthBlockBuilder(null, expectedValues.length, 32 * expectedValues.length); - assertEquals(blockBuilder.getSizeInBytes(), emptyBlockBuilder.getSizeInBytes()); - assertEquals(blockBuilder.getRetainedSizeInBytes(), emptyBlockBuilder.getRetainedSizeInBytes()); + assertThat(blockBuilder.getSizeInBytes()).isEqualTo(emptyBlockBuilder.getSizeInBytes()); + assertThat(blockBuilder.getRetainedSizeInBytes()).isEqualTo(emptyBlockBuilder.getRetainedSizeInBytes()); writeValues(expectedValues, blockBuilder); - assertTrue(blockBuilder.getSizeInBytes() > emptyBlockBuilder.getSizeInBytes()); - assertTrue(blockBuilder.getRetainedSizeInBytes() > emptyBlockBuilder.getRetainedSizeInBytes()); + assertThat(blockBuilder.getSizeInBytes() > emptyBlockBuilder.getSizeInBytes()).isTrue(); + assertThat(blockBuilder.getRetainedSizeInBytes() > emptyBlockBuilder.getRetainedSizeInBytes()).isTrue(); blockBuilder = blockBuilder.newBlockBuilderLike(null); - assertEquals(blockBuilder.getSizeInBytes(), emptyBlockBuilder.getSizeInBytes()); - assertEquals(blockBuilder.getRetainedSizeInBytes(), emptyBlockBuilder.getRetainedSizeInBytes()); + assertThat(blockBuilder.getSizeInBytes()).isEqualTo(emptyBlockBuilder.getSizeInBytes()); + assertThat(blockBuilder.getRetainedSizeInBytes()).isEqualTo(emptyBlockBuilder.getRetainedSizeInBytes()); } @Test @@ -103,11 +102,19 @@ public void testGetSizeInBytes() long quarter4size = splitQuarter.get(3).getSizeInBytes(); double expectedQuarterSizeMin = sizeInBytes * 0.2; double expectedQuarterSizeMax = sizeInBytes * 0.3; - assertTrue(quarter1size > expectedQuarterSizeMin && quarter1size < expectedQuarterSizeMax, format("quarter1size is %s, should be between %s and %s", quarter1size, expectedQuarterSizeMin, expectedQuarterSizeMax)); - assertTrue(quarter2size > expectedQuarterSizeMin && quarter2size < expectedQuarterSizeMax, format("quarter2size is %s, should be between %s and %s", quarter2size, expectedQuarterSizeMin, expectedQuarterSizeMax)); - assertTrue(quarter3size > expectedQuarterSizeMin && quarter3size < expectedQuarterSizeMax, format("quarter3size is %s, should be between %s and %s", quarter3size, expectedQuarterSizeMin, expectedQuarterSizeMax)); - assertTrue(quarter4size > expectedQuarterSizeMin && quarter4size < expectedQuarterSizeMax, format("quarter4size is %s, should be between %s and %s", quarter4size, expectedQuarterSizeMin, expectedQuarterSizeMax)); - assertEquals(quarter1size + quarter2size + quarter3size + quarter4size, sizeInBytes); + assertThat(quarter1size > expectedQuarterSizeMin && quarter1size < expectedQuarterSizeMax) + .describedAs(format("quarter1size is %s, should be between %s and %s", quarter1size, expectedQuarterSizeMin, expectedQuarterSizeMax)) + .isTrue(); + assertThat(quarter2size > expectedQuarterSizeMin && quarter2size < expectedQuarterSizeMax) + .describedAs(format("quarter2size is %s, should be between %s and %s", quarter2size, expectedQuarterSizeMin, expectedQuarterSizeMax)) + .isTrue(); + assertThat(quarter3size > expectedQuarterSizeMin && quarter3size < expectedQuarterSizeMax) + .describedAs(format("quarter3size is %s, should be between %s and %s", quarter3size, expectedQuarterSizeMin, expectedQuarterSizeMax)) + .isTrue(); + assertThat(quarter4size > expectedQuarterSizeMin && quarter4size < expectedQuarterSizeMax) + .describedAs(format("quarter4size is %s, should be between %s and %s", quarter4size, expectedQuarterSizeMin, expectedQuarterSizeMax)) + .isTrue(); + assertThat(quarter1size + quarter2size + quarter3size + quarter4size).isEqualTo(sizeInBytes); } @Test diff --git a/core/trino-main/src/test/java/io/trino/connector/system/TestSystemSplit.java b/core/trino-main/src/test/java/io/trino/connector/system/TestSystemSplit.java index ae09476aae2c..f9cdd598fcee 100644 --- a/core/trino-main/src/test/java/io/trino/connector/system/TestSystemSplit.java +++ b/core/trino-main/src/test/java/io/trino/connector/system/TestSystemSplit.java @@ -19,7 +19,7 @@ import org.junit.jupiter.api.Test; import static io.airlift.json.JsonCodec.jsonCodec; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestSystemSplit { @@ -31,7 +31,7 @@ public void testSerialization() JsonCodec codec = jsonCodec(SystemSplit.class); SystemSplit actual = codec.fromJson(codec.toJson(expected)); - assertEquals(actual.getAddresses(), expected.getAddresses()); - assertEquals(actual.getConstraint(), expected.getConstraint()); + assertThat(actual.getAddresses()).isEqualTo(expected.getAddresses()); + assertThat(actual.getConstraint()).isEqualTo(expected.getConstraint()); } } diff --git a/core/trino-main/src/test/java/io/trino/cost/PlanNodeStatsAssertion.java b/core/trino-main/src/test/java/io/trino/cost/PlanNodeStatsAssertion.java index 8dd9738064fb..4eb194115ae9 100644 --- a/core/trino-main/src/test/java/io/trino/cost/PlanNodeStatsAssertion.java +++ b/core/trino-main/src/test/java/io/trino/cost/PlanNodeStatsAssertion.java @@ -15,13 +15,12 @@ import com.google.common.collect.ImmutableSet; import io.trino.sql.planner.Symbol; +import org.assertj.core.api.Assertions; import java.util.function.Consumer; import static com.google.common.collect.Sets.union; import static io.trino.cost.EstimateAssertion.assertEstimateEquals; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; public class PlanNodeStatsAssertion { @@ -45,7 +44,9 @@ public PlanNodeStatsAssertion outputRowsCount(double expected) public PlanNodeStatsAssertion outputRowsCountUnknown() { - assertTrue(Double.isNaN(actual.getOutputRowCount()), "expected unknown outputRowsCount but got " + actual.getOutputRowCount()); + Assertions.assertThat(Double.isNaN(actual.getOutputRowCount())) + .describedAs("expected unknown outputRowsCount but got " + actual.getOutputRowCount()) + .isTrue(); return this; } @@ -78,7 +79,9 @@ public PlanNodeStatsAssertion symbolStatsUnknown(Symbol symbol) public PlanNodeStatsAssertion symbolsWithKnownStats(Symbol... symbols) { - assertEquals(actual.getSymbolsWithKnownStatistics(), ImmutableSet.copyOf(symbols), "symbols with known stats"); + Assertions.assertThat(actual.getSymbolsWithKnownStatistics()) + .describedAs("symbols with known stats") + .isEqualTo(ImmutableSet.copyOf(symbols)); return this; } diff --git a/core/trino-main/src/test/java/io/trino/cost/SymbolStatsAssertion.java b/core/trino-main/src/test/java/io/trino/cost/SymbolStatsAssertion.java index ee6f083d01c4..92696b824a7c 100644 --- a/core/trino-main/src/test/java/io/trino/cost/SymbolStatsAssertion.java +++ b/core/trino-main/src/test/java/io/trino/cost/SymbolStatsAssertion.java @@ -13,13 +13,13 @@ */ package io.trino.cost; +import org.assertj.core.api.Assertions; + import static io.trino.cost.EstimateAssertion.assertEstimateEquals; import static java.lang.Double.NEGATIVE_INFINITY; import static java.lang.Double.POSITIVE_INFINITY; import static java.lang.Double.isNaN; import static java.util.Objects.requireNonNull; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; public class SymbolStatsAssertion { @@ -43,7 +43,9 @@ public SymbolStatsAssertion nullsFraction(double expected) public SymbolStatsAssertion nullsFractionUnknown() { - assertTrue(isNaN(statistics.getNullsFraction()), "expected unknown nullsFraction but got " + statistics.getNullsFraction()); + Assertions.assertThat(isNaN(statistics.getNullsFraction())) + .describedAs("expected unknown nullsFraction but got " + statistics.getNullsFraction()) + .isTrue(); return this; } @@ -78,11 +80,18 @@ public void empty() public SymbolStatsAssertion emptyRange() { - assertTrue(isNaN(statistics.getLowValue()) && isNaN(statistics.getHighValue()), - "expected empty range (NaN, NaN) but got (" + statistics.getLowValue() + ", " + statistics.getHighValue() + ") instead"); - assertEquals(statistics.getDistinctValuesCount(), 0., "expected no distinctValuesCount"); - assertEquals(statistics.getAverageRowSize(), 0., "expected 0 average row size"); - assertEquals(statistics.getNullsFraction(), 1., "expected all nulls"); + Assertions.assertThat(isNaN(statistics.getLowValue()) && isNaN(statistics.getHighValue())) + .describedAs("expected empty range (NaN, NaN) but got (" + statistics.getLowValue() + ", " + statistics.getHighValue() + ") instead") + .isTrue(); + Assertions.assertThat(statistics.getDistinctValuesCount()) + .describedAs("expected no distinctValuesCount") + .isEqualTo(0.); + Assertions.assertThat(statistics.getAverageRowSize()) + .describedAs("expected 0 average row size") + .isEqualTo(0.); + Assertions.assertThat(statistics.getNullsFraction()) + .describedAs("expected all nulls") + .isEqualTo(1.); return this; } @@ -100,7 +109,9 @@ public SymbolStatsAssertion distinctValuesCount(double expected) public SymbolStatsAssertion distinctValuesCountUnknown() { - assertTrue(isNaN(statistics.getDistinctValuesCount()), "expected unknown distinctValuesCount but got " + statistics.getDistinctValuesCount()); + Assertions.assertThat(isNaN(statistics.getDistinctValuesCount())) + .describedAs("expected unknown distinctValuesCount but got " + statistics.getDistinctValuesCount()) + .isTrue(); return this; } @@ -112,7 +123,9 @@ public SymbolStatsAssertion averageRowSize(double expected) public SymbolStatsAssertion dataSizeUnknown() { - assertTrue(isNaN(statistics.getAverageRowSize()), "expected unknown dataSize but got " + statistics.getAverageRowSize()); + Assertions.assertThat(isNaN(statistics.getAverageRowSize())) + .describedAs("expected unknown dataSize but got " + statistics.getAverageRowSize()) + .isTrue(); return this; } diff --git a/core/trino-main/src/test/java/io/trino/cost/TestCostCalculator.java b/core/trino-main/src/test/java/io/trino/cost/TestCostCalculator.java index dbd6cb93db69..1a2f48576223 100644 --- a/core/trino-main/src/test/java/io/trino/cost/TestCostCalculator.java +++ b/core/trino-main/src/test/java/io/trino/cost/TestCostCalculator.java @@ -79,10 +79,10 @@ import static io.trino.transaction.TransactionBuilder.transaction; import static java.lang.String.format; import static java.util.Objects.requireNonNull; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.offset; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -682,7 +682,7 @@ private void assertFragmentedEqualsUnfragmented(PlanNode node, Map stats) @@ -737,31 +737,31 @@ private static class CostAssertionBuilder CostAssertionBuilder cpu(double value) { - assertEquals(actual.getCpuCost(), value, 0.000001); + assertThat(actual.getCpuCost()).isCloseTo(value, offset(0.000001)); return this; } CostAssertionBuilder memory(double value) { - assertEquals(actual.getMaxMemory(), value, 0.000001); + assertThat(actual.getMaxMemory()).isCloseTo(value, offset(0.000001)); return this; } CostAssertionBuilder memoryWhenOutputting(double value) { - assertEquals(actual.getMaxMemoryWhenOutputting(), value, 0.000001); + assertThat(actual.getMaxMemoryWhenOutputting()).isCloseTo(value, offset(0.000001)); return this; } CostAssertionBuilder network(double value) { - assertEquals(actual.getNetworkCost(), value, 0.000001); + assertThat(actual.getNetworkCost()).isCloseTo(value, offset(0.000001)); return this; } CostAssertionBuilder hasUnknownComponents() { - assertTrue(actual.hasUnknownComponents()); + assertThat(actual.hasUnknownComponents()).isTrue(); return this; } } diff --git a/core/trino-main/src/test/java/io/trino/cost/TestCostComparator.java b/core/trino-main/src/test/java/io/trino/cost/TestCostComparator.java index d60625d68d15..7e25db704dbd 100644 --- a/core/trino-main/src/test/java/io/trino/cost/TestCostComparator.java +++ b/core/trino-main/src/test/java/io/trino/cost/TestCostComparator.java @@ -18,8 +18,8 @@ import static com.google.common.base.Preconditions.checkState; import static io.trino.testing.TestingSession.testSessionBuilder; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertTrue; public class TestCostComparator { @@ -97,8 +97,12 @@ public void assertCompare() { checkState(smaller != null, "smaller not set"); checkState(larger != null, "larger not set"); - assertTrue(costComparator.compare(session, smaller, larger) < 0, "smaller < larger is false"); - assertTrue(costComparator.compare(session, larger, smaller) > 0, "larger > smaller is false"); + assertThat(costComparator.compare(session, smaller, larger) < 0) + .describedAs("smaller < larger is false") + .isTrue(); + assertThat(costComparator.compare(session, larger, smaller) > 0) + .describedAs("larger > smaller is false") + .isTrue(); } public CostComparisonAssertion smaller(double cpu, double memory, double network) diff --git a/core/trino-main/src/test/java/io/trino/cost/TestJoinStatsRule.java b/core/trino-main/src/test/java/io/trino/cost/TestJoinStatsRule.java index 372a7aa71004..a86cc40ca3fe 100644 --- a/core/trino-main/src/test/java/io/trino/cost/TestJoinStatsRule.java +++ b/core/trino-main/src/test/java/io/trino/cost/TestJoinStatsRule.java @@ -24,6 +24,7 @@ import io.trino.sql.planner.plan.PlanNode; import io.trino.sql.tree.ComparisonExpression; import io.trino.sql.tree.LongLiteral; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import java.util.Optional; @@ -42,7 +43,6 @@ import static io.trino.sql.planner.plan.JoinNode.Type.RIGHT; import static io.trino.testing.TestingSession.testSessionBuilder; import static java.lang.Double.NaN; -import static org.testng.Assert.assertEquals; public class TestJoinStatsRule extends BaseStatsCalculatorTest @@ -237,7 +237,7 @@ public void testJoinComplementStats() LEFT_STATS, RIGHT_STATS, TYPES); - assertEquals(actual, expected); + Assertions.assertThat(actual).isEqualTo(expected); } @Test @@ -255,7 +255,7 @@ public void testRightJoinComplementStats() RIGHT_STATS, LEFT_STATS, TYPES); - assertEquals(actual, expected); + Assertions.assertThat(actual).isEqualTo(expected); } @Test @@ -268,7 +268,7 @@ public void testLeftJoinComplementStatsWithNoClauses() LEFT_STATS, RIGHT_STATS, TYPES); - assertEquals(actual, expected); + Assertions.assertThat(actual).isEqualTo(expected); } @Test @@ -285,7 +285,7 @@ public void testLeftJoinComplementStatsWithMultipleClauses() LEFT_STATS, RIGHT_STATS, TYPES); - assertEquals(actual, expected); + Assertions.assertThat(actual).isEqualTo(expected); } @Test diff --git a/core/trino-main/src/test/java/io/trino/cost/TestPlanNodeStatsEstimateMath.java b/core/trino-main/src/test/java/io/trino/cost/TestPlanNodeStatsEstimateMath.java index 829bcb02b231..01b64458bd65 100644 --- a/core/trino-main/src/test/java/io/trino/cost/TestPlanNodeStatsEstimateMath.java +++ b/core/trino-main/src/test/java/io/trino/cost/TestPlanNodeStatsEstimateMath.java @@ -14,6 +14,7 @@ package io.trino.cost; import io.trino.sql.planner.Symbol; +import org.assertj.core.api.AbstractDoubleAssert; import org.junit.jupiter.api.Test; import static io.trino.cost.PlanNodeStatsEstimateMath.addStatsAndMaxDistinctValues; @@ -23,7 +24,7 @@ import static java.lang.Double.NEGATIVE_INFINITY; import static java.lang.Double.NaN; import static java.lang.Double.POSITIVE_INFINITY; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestPlanNodeStatsEstimateMath { @@ -37,10 +38,10 @@ public void testAddRowCount() PlanNodeStatsEstimate first = statistics(10, NaN, NaN, StatisticRange.empty()); PlanNodeStatsEstimate second = statistics(20, NaN, NaN, StatisticRange.empty()); - assertEquals(addStatsAndSumDistinctValues(unknownStats, unknownStats), PlanNodeStatsEstimate.unknown()); - assertEquals(addStatsAndSumDistinctValues(first, unknownStats), PlanNodeStatsEstimate.unknown()); - assertEquals(addStatsAndSumDistinctValues(unknownStats, second), PlanNodeStatsEstimate.unknown()); - assertEquals(addStatsAndSumDistinctValues(first, second).getOutputRowCount(), 30.0); + assertThat(addStatsAndSumDistinctValues(unknownStats, unknownStats)).isEqualTo(PlanNodeStatsEstimate.unknown()); + assertThat(addStatsAndSumDistinctValues(first, unknownStats)).isEqualTo(PlanNodeStatsEstimate.unknown()); + assertThat(addStatsAndSumDistinctValues(unknownStats, second)).isEqualTo(PlanNodeStatsEstimate.unknown()); + assertThat(addStatsAndSumDistinctValues(first, second).getOutputRowCount()).isEqualTo(30.0); } @Test @@ -53,18 +54,18 @@ public void testAddNullsFraction() PlanNodeStatsEstimate fractionalRowCountFirst = statistics(0.1, 0.1, NaN, NON_EMPTY_RANGE); PlanNodeStatsEstimate fractionalRowCountSecond = statistics(0.2, 0.3, NaN, NON_EMPTY_RANGE); - assertAddNullsFraction(unknownRowCount, unknownRowCount, NaN); - assertAddNullsFraction(unknownNullsFraction, unknownNullsFraction, NaN); - assertAddNullsFraction(unknownRowCount, unknownNullsFraction, NaN); - assertAddNullsFraction(first, unknownNullsFraction, NaN); - assertAddNullsFraction(unknownRowCount, second, NaN); - assertAddNullsFraction(first, second, 0.16666666666666666); - assertAddNullsFraction(fractionalRowCountFirst, fractionalRowCountSecond, 0.2333333333333333); + assertThatAddNullsFraction(unknownRowCount, unknownRowCount).isNaN(); + assertThatAddNullsFraction(unknownNullsFraction, unknownNullsFraction).isNaN(); + assertThatAddNullsFraction(unknownRowCount, unknownNullsFraction).isNaN(); + assertThatAddNullsFraction(first, unknownNullsFraction).isNaN(); + assertThatAddNullsFraction(unknownRowCount, second).isNaN(); + assertThatAddNullsFraction(first, second).isEqualTo(0.16666666666666666); + assertThatAddNullsFraction(fractionalRowCountFirst, fractionalRowCountSecond).isEqualTo(0.2333333333333333); } - private static void assertAddNullsFraction(PlanNodeStatsEstimate first, PlanNodeStatsEstimate second, double expected) + private static AbstractDoubleAssert assertThatAddNullsFraction(PlanNodeStatsEstimate first, PlanNodeStatsEstimate second) { - assertEquals(addStatsAndSumDistinctValues(first, second).getSymbolStatistics(SYMBOL).getNullsFraction(), expected); + return assertThat(addStatsAndSumDistinctValues(first, second).getSymbolStatistics(SYMBOL).getNullsFraction()); } @Test @@ -78,19 +79,19 @@ public void testAddAverageRowSize() PlanNodeStatsEstimate fractionalRowCountFirst = statistics(0.1, 0.1, 0.3, NON_EMPTY_RANGE); PlanNodeStatsEstimate fractionalRowCountSecond = statistics(0.2, 0.3, 0.4, NON_EMPTY_RANGE); - assertAddAverageRowSize(unknownRowCount, unknownRowCount, NaN); - assertAddAverageRowSize(unknownNullsFraction, unknownNullsFraction, NaN); - assertAddAverageRowSize(unknownAverageRowSize, unknownAverageRowSize, NaN); - assertAddAverageRowSize(first, unknownRowCount, NaN); - assertAddAverageRowSize(unknownNullsFraction, second, NaN); - assertAddAverageRowSize(first, unknownAverageRowSize, NaN); - assertAddAverageRowSize(first, second, 18.2); - assertAddAverageRowSize(fractionalRowCountFirst, fractionalRowCountSecond, 0.3608695652173913); + assertThatAddAverageRowSize(unknownRowCount, unknownRowCount).isNaN(); + assertThatAddAverageRowSize(unknownNullsFraction, unknownNullsFraction).isNaN(); + assertThatAddAverageRowSize(unknownAverageRowSize, unknownAverageRowSize).isNaN(); + assertThatAddAverageRowSize(first, unknownRowCount).isNaN(); + assertThatAddAverageRowSize(unknownNullsFraction, second).isNaN(); + assertThatAddAverageRowSize(first, unknownAverageRowSize).isNaN(); + assertThatAddAverageRowSize(first, second).isEqualTo(18.2); + assertThatAddAverageRowSize(fractionalRowCountFirst, fractionalRowCountSecond).isEqualTo(0.3608695652173913); } - private static void assertAddAverageRowSize(PlanNodeStatsEstimate first, PlanNodeStatsEstimate second, double expected) + private static AbstractDoubleAssert assertThatAddAverageRowSize(PlanNodeStatsEstimate first, PlanNodeStatsEstimate second) { - assertEquals(addStatsAndSumDistinctValues(first, second).getSymbolStatistics(SYMBOL).getAverageRowSize(), expected); + return assertThat(addStatsAndSumDistinctValues(first, second).getSymbolStatistics(SYMBOL).getAverageRowSize()); } @Test @@ -102,16 +103,16 @@ public void testSumNumberOfDistinctValues() PlanNodeStatsEstimate first = statistics(10, NaN, NaN, openRange(2)); PlanNodeStatsEstimate second = statistics(10, NaN, NaN, openRange(3)); - assertSumNumberOfDistinctValues(unknownRowCount, unknownRowCount, NaN); - assertSumNumberOfDistinctValues(unknownRowCount, second, NaN); - assertSumNumberOfDistinctValues(first, emptyRange, 2); - assertSumNumberOfDistinctValues(first, unknownRange, NaN); - assertSumNumberOfDistinctValues(first, second, 5); + assertThatSumNumberOfDistinctValues(unknownRowCount, unknownRowCount).isNaN(); + assertThatSumNumberOfDistinctValues(unknownRowCount, second).isNaN(); + assertThatSumNumberOfDistinctValues(first, emptyRange).isEqualTo(2); + assertThatSumNumberOfDistinctValues(first, unknownRange).isNaN(); + assertThatSumNumberOfDistinctValues(first, second).isEqualTo(5); } - private static void assertSumNumberOfDistinctValues(PlanNodeStatsEstimate first, PlanNodeStatsEstimate second, double expected) + private static AbstractDoubleAssert assertThatSumNumberOfDistinctValues(PlanNodeStatsEstimate first, PlanNodeStatsEstimate second) { - assertEquals(addStatsAndSumDistinctValues(first, second).getSymbolStatistics(SYMBOL).getDistinctValuesCount(), expected); + return assertThat(addStatsAndSumDistinctValues(first, second).getSymbolStatistics(SYMBOL).getDistinctValuesCount()); } @Test @@ -123,16 +124,16 @@ public void testMaxNumberOfDistinctValues() PlanNodeStatsEstimate first = statistics(10, NaN, NaN, openRange(2)); PlanNodeStatsEstimate second = statistics(10, NaN, NaN, openRange(3)); - assertMaxNumberOfDistinctValues(unknownRowCount, unknownRowCount, NaN); - assertMaxNumberOfDistinctValues(unknownRowCount, second, NaN); - assertMaxNumberOfDistinctValues(first, emptyRange, 2); - assertMaxNumberOfDistinctValues(first, unknownRange, NaN); - assertMaxNumberOfDistinctValues(first, second, 3); + assertThatMaxNumberOfDistinctValues(unknownRowCount, unknownRowCount).isNaN(); + assertThatMaxNumberOfDistinctValues(unknownRowCount, second).isNaN(); + assertThatMaxNumberOfDistinctValues(first, emptyRange).isEqualTo(2); + assertThatMaxNumberOfDistinctValues(first, unknownRange).isNaN(); + assertThatMaxNumberOfDistinctValues(first, second).isEqualTo(3); } - private static void assertMaxNumberOfDistinctValues(PlanNodeStatsEstimate first, PlanNodeStatsEstimate second, double expected) + private static AbstractDoubleAssert assertThatMaxNumberOfDistinctValues(PlanNodeStatsEstimate first, PlanNodeStatsEstimate second) { - assertEquals(addStatsAndMaxDistinctValues(first, second).getSymbolStatistics(SYMBOL).getDistinctValuesCount(), expected); + return assertThat(addStatsAndMaxDistinctValues(first, second).getSymbolStatistics(SYMBOL).getDistinctValuesCount()); } @Test @@ -154,8 +155,8 @@ public void testAddRange() private static void assertAddRange(PlanNodeStatsEstimate first, PlanNodeStatsEstimate second, double expectedLow, double expectedHigh) { SymbolStatsEstimate statistics = addStatsAndMaxDistinctValues(first, second).getSymbolStatistics(SYMBOL); - assertEquals(statistics.getLowValue(), expectedLow); - assertEquals(statistics.getHighValue(), expectedHigh); + assertThat(statistics.getLowValue()).isEqualTo(expectedLow); + assertThat(statistics.getHighValue()).isEqualTo(expectedHigh); } @Test @@ -165,10 +166,10 @@ public void testSubtractRowCount() PlanNodeStatsEstimate first = statistics(40, NaN, NaN, StatisticRange.empty()); PlanNodeStatsEstimate second = statistics(10, NaN, NaN, StatisticRange.empty()); - assertEquals(subtractSubsetStats(unknownStats, unknownStats), PlanNodeStatsEstimate.unknown()); - assertEquals(subtractSubsetStats(first, unknownStats), PlanNodeStatsEstimate.unknown()); - assertEquals(subtractSubsetStats(unknownStats, second), PlanNodeStatsEstimate.unknown()); - assertEquals(subtractSubsetStats(first, second).getOutputRowCount(), 30.0); + assertThat(subtractSubsetStats(unknownStats, unknownStats)).isEqualTo(PlanNodeStatsEstimate.unknown()); + assertThat(subtractSubsetStats(first, unknownStats)).isEqualTo(PlanNodeStatsEstimate.unknown()); + assertThat(subtractSubsetStats(unknownStats, second)).isEqualTo(PlanNodeStatsEstimate.unknown()); + assertThat(subtractSubsetStats(first, second).getOutputRowCount()).isEqualTo(30.0); } @Test @@ -181,17 +182,17 @@ public void testSubtractNullsFraction() PlanNodeStatsEstimate fractionalRowCountFirst = statistics(0.7, 0.1, NaN, NON_EMPTY_RANGE); PlanNodeStatsEstimate fractionalRowCountSecond = statistics(0.2, 0.3, NaN, NON_EMPTY_RANGE); - assertSubtractNullsFraction(unknownRowCount, unknownRowCount, NaN); - assertSubtractNullsFraction(unknownRowCount, unknownNullsFraction, NaN); - assertSubtractNullsFraction(first, unknownNullsFraction, NaN); - assertSubtractNullsFraction(unknownRowCount, second, NaN); - assertSubtractNullsFraction(first, second, 0.03333333333333333); - assertSubtractNullsFraction(fractionalRowCountFirst, fractionalRowCountSecond, 0.019999999999999993); + assertThatSubtractNullsFraction(unknownRowCount, unknownRowCount).isNaN(); + assertThatSubtractNullsFraction(unknownRowCount, unknownNullsFraction).isNaN(); + assertThatSubtractNullsFraction(first, unknownNullsFraction).isNaN(); + assertThatSubtractNullsFraction(unknownRowCount, second).isNaN(); + assertThatSubtractNullsFraction(first, second).isEqualTo(0.03333333333333333); + assertThatSubtractNullsFraction(fractionalRowCountFirst, fractionalRowCountSecond).isEqualTo(0.019999999999999993); } - private static void assertSubtractNullsFraction(PlanNodeStatsEstimate first, PlanNodeStatsEstimate second, double expected) + private static AbstractDoubleAssert assertThatSubtractNullsFraction(PlanNodeStatsEstimate first, PlanNodeStatsEstimate second) { - assertEquals(subtractSubsetStats(first, second).getSymbolStatistics(SYMBOL).getNullsFraction(), expected); + return assertThat(subtractSubsetStats(first, second).getSymbolStatistics(SYMBOL).getNullsFraction()); } @Test @@ -204,18 +205,18 @@ public void testSubtractNumberOfDistinctValues() PlanNodeStatsEstimate second = statistics(20, 0.1, NaN, openRange(5)); PlanNodeStatsEstimate third = statistics(10, 0.1, NaN, openRange(3)); - assertSubtractNumberOfDistinctValues(unknownRowCount, unknownRowCount, NaN); - assertSubtractNumberOfDistinctValues(unknownRowCount, second, NaN); - assertSubtractNumberOfDistinctValues(unknownDistinctValues, second, NaN); - assertSubtractNumberOfDistinctValues(first, zero, 10); - assertSubtractNumberOfDistinctValues(zero, zero, 0); - assertSubtractNumberOfDistinctValues(first, second, 5); - assertSubtractNumberOfDistinctValues(second, third, 5); + assertThatSubtractNumberOfDistinctValues(unknownRowCount, unknownRowCount).isNaN(); + assertThatSubtractNumberOfDistinctValues(unknownRowCount, second).isNaN(); + assertThatSubtractNumberOfDistinctValues(unknownDistinctValues, second).isNaN(); + assertThatSubtractNumberOfDistinctValues(first, zero).isEqualTo(10); + assertThatSubtractNumberOfDistinctValues(zero, zero).isEqualTo(0); + assertThatSubtractNumberOfDistinctValues(first, second).isEqualTo(5); + assertThatSubtractNumberOfDistinctValues(second, third).isEqualTo(5); } - private static void assertSubtractNumberOfDistinctValues(PlanNodeStatsEstimate first, PlanNodeStatsEstimate second, double expected) + private static AbstractDoubleAssert assertThatSubtractNumberOfDistinctValues(PlanNodeStatsEstimate first, PlanNodeStatsEstimate second) { - assertEquals(subtractSubsetStats(first, second).getSymbolStatistics(SYMBOL).getDistinctValuesCount(), expected); + return assertThat(subtractSubsetStats(first, second).getSymbolStatistics(SYMBOL).getDistinctValuesCount()); } @Test @@ -235,8 +236,8 @@ private static void assertSubtractRange(double supersetLow, double supersetHigh, PlanNodeStatsEstimate first = statistics(30, NaN, NaN, new StatisticRange(supersetLow, supersetHigh, 10)); PlanNodeStatsEstimate second = statistics(20, NaN, NaN, new StatisticRange(subsetLow, subsetHigh, 5)); SymbolStatsEstimate statistics = subtractSubsetStats(first, second).getSymbolStatistics(SYMBOL); - assertEquals(statistics.getLowValue(), expectedLow); - assertEquals(statistics.getHighValue(), expectedHigh); + assertThat(statistics.getLowValue()).isEqualByComparingTo(expectedLow); + assertThat(statistics.getHighValue()).isEqualByComparingTo(expectedHigh); } @Test @@ -246,11 +247,11 @@ public void testCapRowCount() PlanNodeStatsEstimate first = statistics(20, NaN, NaN, NON_EMPTY_RANGE); PlanNodeStatsEstimate second = statistics(10, NaN, NaN, NON_EMPTY_RANGE); - assertEquals(capStats(unknownRowCount, unknownRowCount).getOutputRowCount(), NaN); - assertEquals(capStats(first, unknownRowCount).getOutputRowCount(), NaN); - assertEquals(capStats(unknownRowCount, second).getOutputRowCount(), NaN); - assertEquals(capStats(first, second).getOutputRowCount(), 10.0); - assertEquals(capStats(second, first).getOutputRowCount(), 10.0); + assertThat(capStats(unknownRowCount, unknownRowCount).getOutputRowCount()).isNaN(); + assertThat(capStats(first, unknownRowCount).getOutputRowCount()).isNaN(); + assertThat(capStats(unknownRowCount, second).getOutputRowCount()).isNaN(); + assertThat(capStats(first, second).getOutputRowCount()).isEqualTo(10.0); + assertThat(capStats(second, first).getOutputRowCount()).isEqualTo(10.0); } @Test @@ -261,18 +262,18 @@ public void testCapAverageRowSize() PlanNodeStatsEstimate first = statistics(20, NaN, 10, NON_EMPTY_RANGE); PlanNodeStatsEstimate second = statistics(10, NaN, 5, NON_EMPTY_RANGE); - assertCapAverageRowSize(unknownRowCount, unknownRowCount, NaN); - assertCapAverageRowSize(unknownAverageRowSize, unknownAverageRowSize, NaN); + assertThatcapAverageRowSize(unknownRowCount, unknownRowCount).isNaN(); + assertThatcapAverageRowSize(unknownAverageRowSize, unknownAverageRowSize).isNaN(); // average row size should be preserved - assertCapAverageRowSize(first, unknownAverageRowSize, 10); - assertCapAverageRowSize(unknownAverageRowSize, second, NaN); + assertThatcapAverageRowSize(first, unknownAverageRowSize).isEqualTo(10); + assertThatcapAverageRowSize(unknownAverageRowSize, second).isNaN(); // average row size should be preserved - assertCapAverageRowSize(first, second, 10); + assertThatcapAverageRowSize(first, second).isEqualTo(10); } - private static void assertCapAverageRowSize(PlanNodeStatsEstimate stats, PlanNodeStatsEstimate cap, double expected) + private static AbstractDoubleAssert assertThatcapAverageRowSize(PlanNodeStatsEstimate stats, PlanNodeStatsEstimate cap) { - assertEquals(capStats(stats, cap).getSymbolStatistics(SYMBOL).getAverageRowSize(), expected); + return assertThat(capStats(stats, cap).getSymbolStatistics(SYMBOL).getAverageRowSize()); } @Test @@ -283,16 +284,16 @@ public void testCapNumberOfDistinctValues() PlanNodeStatsEstimate first = statistics(20, NaN, NaN, openRange(10)); PlanNodeStatsEstimate second = statistics(10, NaN, NaN, openRange(5)); - assertCapNumberOfDistinctValues(unknownRowCount, unknownRowCount, NaN); - assertCapNumberOfDistinctValues(unknownNumberOfDistinctValues, unknownNumberOfDistinctValues, NaN); - assertCapNumberOfDistinctValues(first, unknownRowCount, NaN); - assertCapNumberOfDistinctValues(unknownNumberOfDistinctValues, second, NaN); - assertCapNumberOfDistinctValues(first, second, 5); + assertThatcapNumberOfDistinctValues(unknownRowCount, unknownRowCount).isNaN(); + assertThatcapNumberOfDistinctValues(unknownNumberOfDistinctValues, unknownNumberOfDistinctValues).isNaN(); + assertThatcapNumberOfDistinctValues(first, unknownRowCount).isNaN(); + assertThatcapNumberOfDistinctValues(unknownNumberOfDistinctValues, second).isNaN(); + assertThatcapNumberOfDistinctValues(first, second).isEqualTo(5); } - private static void assertCapNumberOfDistinctValues(PlanNodeStatsEstimate stats, PlanNodeStatsEstimate cap, double expected) + private static AbstractDoubleAssert assertThatcapNumberOfDistinctValues(PlanNodeStatsEstimate stats, PlanNodeStatsEstimate cap) { - assertEquals(capStats(stats, cap).getSymbolStatistics(SYMBOL).getDistinctValuesCount(), expected); + return assertThat(capStats(stats, cap).getSymbolStatistics(SYMBOL).getDistinctValuesCount()); } @Test @@ -314,8 +315,8 @@ public void testCapRange() private static void assertCapRange(PlanNodeStatsEstimate stats, PlanNodeStatsEstimate cap, double expectedLow, double expectedHigh) { SymbolStatsEstimate symbolStats = capStats(stats, cap).getSymbolStatistics(SYMBOL); - assertEquals(symbolStats.getLowValue(), expectedLow); - assertEquals(symbolStats.getHighValue(), expectedHigh); + assertThat(symbolStats.getLowValue()).isEqualByComparingTo(expectedLow); + assertThat(symbolStats.getHighValue()).isEqualByComparingTo(expectedHigh); } @Test @@ -327,17 +328,17 @@ public void testCapNullsFraction() PlanNodeStatsEstimate second = statistics(10, 0.6, NaN, NON_EMPTY_RANGE); PlanNodeStatsEstimate third = statistics(0, 0.6, NaN, NON_EMPTY_RANGE); - assertCapNullsFraction(unknownRowCount, unknownRowCount, NaN); - assertCapNullsFraction(unknownNullsFraction, unknownNullsFraction, NaN); - assertCapNullsFraction(first, unknownNullsFraction, NaN); - assertCapNullsFraction(unknownNullsFraction, second, NaN); - assertCapNullsFraction(first, second, 0.5); - assertCapNullsFraction(first, third, 1); + assertThatCapNullsFraction(unknownRowCount, unknownRowCount).isNaN(); + assertThatCapNullsFraction(unknownNullsFraction, unknownNullsFraction).isNaN(); + assertThatCapNullsFraction(first, unknownNullsFraction).isNaN(); + assertThatCapNullsFraction(unknownNullsFraction, second).isNaN(); + assertThatCapNullsFraction(first, second).isEqualTo(0.5); + assertThatCapNullsFraction(first, third).isEqualTo(1); } - private static void assertCapNullsFraction(PlanNodeStatsEstimate stats, PlanNodeStatsEstimate cap, double expected) + private static AbstractDoubleAssert assertThatCapNullsFraction(PlanNodeStatsEstimate stats, PlanNodeStatsEstimate cap) { - assertEquals(capStats(stats, cap).getSymbolStatistics(SYMBOL).getNullsFraction(), expected); + return assertThat(capStats(stats, cap).getSymbolStatistics(SYMBOL).getNullsFraction()); } private static PlanNodeStatsEstimate statistics(double rowCount, double nullsFraction, double averageRowSize, StatisticRange range) diff --git a/core/trino-main/src/test/java/io/trino/cost/TestStatisticRange.java b/core/trino-main/src/test/java/io/trino/cost/TestStatisticRange.java index 89946ed0981d..1dc5f61c0862 100644 --- a/core/trino-main/src/test/java/io/trino/cost/TestStatisticRange.java +++ b/core/trino-main/src/test/java/io/trino/cost/TestStatisticRange.java @@ -19,7 +19,7 @@ import static java.lang.Double.NEGATIVE_INFINITY; import static java.lang.Double.NaN; import static java.lang.Double.POSITIVE_INFINITY; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestStatisticRange { @@ -64,44 +64,44 @@ public void testIntersect() { StatisticRange zeroToTen = range(0, 10, 10); StatisticRange fiveToFifteen = range(5, 15, 60); - assertEquals(zeroToTen.intersect(fiveToFifteen), range(5, 10, 10)); + assertThat(zeroToTen.intersect(fiveToFifteen)).isEqualTo(range(5, 10, 10)); } @Test public void testAddAndSumDistinctValues() { - assertEquals(unboundedRange(NaN).addAndSumDistinctValues(unboundedRange(NaN)), unboundedRange(NaN)); - assertEquals(unboundedRange(NaN).addAndSumDistinctValues(unboundedRange(1)), unboundedRange(NaN)); - assertEquals(unboundedRange(1).addAndSumDistinctValues(unboundedRange(NaN)), unboundedRange(NaN)); - assertEquals(unboundedRange(1).addAndSumDistinctValues(unboundedRange(2)), unboundedRange(3)); - assertEquals(StatisticRange.empty().addAndSumDistinctValues(StatisticRange.empty()), StatisticRange.empty()); - assertEquals(range(0, 1, 1).addAndSumDistinctValues(StatisticRange.empty()), range(0, 1, 1)); - assertEquals(range(0, 1, 1).addAndSumDistinctValues(range(1, 2, 1)), range(0, 2, 2)); + assertThat(unboundedRange(NaN).addAndSumDistinctValues(unboundedRange(NaN))).isEqualTo(unboundedRange(NaN)); + assertThat(unboundedRange(NaN).addAndSumDistinctValues(unboundedRange(1))).isEqualTo(unboundedRange(NaN)); + assertThat(unboundedRange(1).addAndSumDistinctValues(unboundedRange(NaN))).isEqualTo(unboundedRange(NaN)); + assertThat(unboundedRange(1).addAndSumDistinctValues(unboundedRange(2))).isEqualTo(unboundedRange(3)); + assertThat(StatisticRange.empty().addAndSumDistinctValues(StatisticRange.empty())).isEqualTo(StatisticRange.empty()); + assertThat(range(0, 1, 1).addAndSumDistinctValues(StatisticRange.empty())).isEqualTo(range(0, 1, 1)); + assertThat(range(0, 1, 1).addAndSumDistinctValues(range(1, 2, 1))).isEqualTo(range(0, 2, 2)); } @Test public void testAddAndMaxDistinctValues() { - assertEquals(unboundedRange(NaN).addAndMaxDistinctValues(unboundedRange(NaN)), unboundedRange(NaN)); - assertEquals(unboundedRange(NaN).addAndMaxDistinctValues(unboundedRange(1)), unboundedRange(NaN)); - assertEquals(unboundedRange(1).addAndMaxDistinctValues(unboundedRange(NaN)), unboundedRange(NaN)); - assertEquals(unboundedRange(1).addAndMaxDistinctValues(unboundedRange(2)), unboundedRange(2)); - assertEquals(StatisticRange.empty().addAndMaxDistinctValues(StatisticRange.empty()), StatisticRange.empty()); - assertEquals(range(0, 1, 1).addAndMaxDistinctValues(StatisticRange.empty()), range(0, 1, 1)); - assertEquals(range(0, 1, 1).addAndMaxDistinctValues(range(1, 2, 1)), range(0, 2, 1)); + assertThat(unboundedRange(NaN).addAndMaxDistinctValues(unboundedRange(NaN))).isEqualTo(unboundedRange(NaN)); + assertThat(unboundedRange(NaN).addAndMaxDistinctValues(unboundedRange(1))).isEqualTo(unboundedRange(NaN)); + assertThat(unboundedRange(1).addAndMaxDistinctValues(unboundedRange(NaN))).isEqualTo(unboundedRange(NaN)); + assertThat(unboundedRange(1).addAndMaxDistinctValues(unboundedRange(2))).isEqualTo(unboundedRange(2)); + assertThat(StatisticRange.empty().addAndMaxDistinctValues(StatisticRange.empty())).isEqualTo(StatisticRange.empty()); + assertThat(range(0, 1, 1).addAndMaxDistinctValues(StatisticRange.empty())).isEqualTo(range(0, 1, 1)); + assertThat(range(0, 1, 1).addAndMaxDistinctValues(range(1, 2, 1))).isEqualTo(range(0, 2, 1)); } @Test public void testAddAndCollapseDistinctValues() { - assertEquals(unboundedRange(NaN).addAndCollapseDistinctValues(unboundedRange(NaN)), unboundedRange(NaN)); - assertEquals(unboundedRange(NaN).addAndCollapseDistinctValues(unboundedRange(1)), unboundedRange(NaN)); - assertEquals(unboundedRange(1).addAndCollapseDistinctValues(unboundedRange(NaN)), unboundedRange(NaN)); - assertEquals(unboundedRange(1).addAndCollapseDistinctValues(unboundedRange(2)), unboundedRange(2)); - assertEquals(StatisticRange.empty().addAndCollapseDistinctValues(StatisticRange.empty()), StatisticRange.empty()); - assertEquals(range(0, 1, 1).addAndCollapseDistinctValues(StatisticRange.empty()), range(0, 1, 1)); - assertEquals(range(0, 1, 1).addAndCollapseDistinctValues(range(1, 2, 1)), range(0, 2, 1)); - assertEquals(range(0, 3, 3).addAndCollapseDistinctValues(range(2, 6, 4)), range(0, 6, 6)); + assertThat(unboundedRange(NaN).addAndCollapseDistinctValues(unboundedRange(NaN))).isEqualTo(unboundedRange(NaN)); + assertThat(unboundedRange(NaN).addAndCollapseDistinctValues(unboundedRange(1))).isEqualTo(unboundedRange(NaN)); + assertThat(unboundedRange(1).addAndCollapseDistinctValues(unboundedRange(NaN))).isEqualTo(unboundedRange(NaN)); + assertThat(unboundedRange(1).addAndCollapseDistinctValues(unboundedRange(2))).isEqualTo(unboundedRange(2)); + assertThat(StatisticRange.empty().addAndCollapseDistinctValues(StatisticRange.empty())).isEqualTo(StatisticRange.empty()); + assertThat(range(0, 1, 1).addAndCollapseDistinctValues(StatisticRange.empty())).isEqualTo(range(0, 1, 1)); + assertThat(range(0, 1, 1).addAndCollapseDistinctValues(range(1, 2, 1))).isEqualTo(range(0, 2, 1)); + assertThat(range(0, 3, 3).addAndCollapseDistinctValues(range(2, 6, 4))).isEqualTo(range(0, 6, 6)); } private static StatisticRange range(double low, double high, double distinctValues) diff --git a/core/trino-main/src/test/java/io/trino/dispatcher/TestLocalDispatchQuery.java b/core/trino-main/src/test/java/io/trino/dispatcher/TestLocalDispatchQuery.java index 3fba48fc8ff5..df08712c2977 100644 --- a/core/trino-main/src/test/java/io/trino/dispatcher/TestLocalDispatchQuery.java +++ b/core/trino-main/src/test/java/io/trino/dispatcher/TestLocalDispatchQuery.java @@ -82,7 +82,7 @@ import static io.trino.testing.TestingEventListenerManager.emptyEventListenerManager; import static io.trino.transaction.InMemoryTransactionManager.createTestTransactionManager; import static java.util.concurrent.Executors.newCachedThreadPool; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestLocalDispatchQuery { @@ -160,7 +160,7 @@ public void testSubmittedForDispatchedQuery() }); localDispatchQuery.startWaitingForResources(); countDownLatch.await(); - assertTrue(localDispatchQuery.getDispatchInfo().getCoordinatorLocation().isPresent()); + assertThat(localDispatchQuery.getDispatchInfo().getCoordinatorLocation().isPresent()).isTrue(); } private static class NoConnectorServicesProvider diff --git a/core/trino-main/src/test/java/io/trino/exchange/TestExchangeSourceOutputSelector.java b/core/trino-main/src/test/java/io/trino/exchange/TestExchangeSourceOutputSelector.java index 718f144f81e5..4affb429c24e 100644 --- a/core/trino-main/src/test/java/io/trino/exchange/TestExchangeSourceOutputSelector.java +++ b/core/trino-main/src/test/java/io/trino/exchange/TestExchangeSourceOutputSelector.java @@ -32,12 +32,10 @@ import static io.trino.spi.exchange.ExchangeSourceOutputSelector.Selection.EXCLUDED; import static io.trino.spi.exchange.ExchangeSourceOutputSelector.Selection.INCLUDED; import static io.trino.spi.exchange.ExchangeSourceOutputSelector.Selection.UNKNOWN; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -69,9 +67,9 @@ public void testEmpty() { ExchangeSourceOutputSelector selector = serializeDeserialize(ExchangeSourceOutputSelector.builder(ImmutableSet.of(EXCHANGE_ID_1, EXCHANGE_ID_2)) .build()); - assertEquals(selector.getSelection(EXCHANGE_ID_1, 100, 1), UNKNOWN); - assertEquals(selector.getSelection(EXCHANGE_ID_2, 21, 2), UNKNOWN); - assertFalse(selector.isFinal()); + assertThat(selector.getSelection(EXCHANGE_ID_1, 100, 1)).isEqualTo(UNKNOWN); + assertThat(selector.getSelection(EXCHANGE_ID_2, 21, 2)).isEqualTo(UNKNOWN); + assertThat(selector.isFinal()).isFalse(); } { @@ -80,7 +78,7 @@ public void testEmpty() .setPartitionCount(EXCHANGE_ID_2, 0) .setFinal() .build()); - assertTrue(selector.isFinal()); + assertThat(selector.isFinal()).isTrue(); // final selector should have selection set for all partitions assertThatThrownBy(() -> selector.getSelection(EXCHANGE_ID_1, 100, 1)) .isInstanceOf(IllegalArgumentException.class) @@ -96,17 +94,17 @@ public void testNonFinal() .exclude(EXCHANGE_ID_2, 100) .build()); // ensure exchange id is taken into account - assertEquals(selector.getSelection(EXCHANGE_ID_1, 100, 1), UNKNOWN); - assertEquals(selector.getSelection(EXCHANGE_ID_2, 100, 1), EXCLUDED); + assertThat(selector.getSelection(EXCHANGE_ID_1, 100, 1)).isEqualTo(UNKNOWN); + assertThat(selector.getSelection(EXCHANGE_ID_2, 100, 1)).isEqualTo(EXCLUDED); // all attempts of a given task must be excluded - assertEquals(selector.getSelection(EXCHANGE_ID_2, 100, 2), EXCLUDED); + assertThat(selector.getSelection(EXCHANGE_ID_2, 100, 2)).isEqualTo(EXCLUDED); // ensure exchange id is taken into account - assertEquals(selector.getSelection(EXCHANGE_ID_2, 21, 2), UNKNOWN); - assertEquals(selector.getSelection(EXCHANGE_ID_1, 21, 2), INCLUDED); - assertEquals(selector.getSelection(EXCHANGE_ID_1, 21, 1), EXCLUDED); - assertEquals(selector.getSelection(EXCHANGE_ID_2, 1, 2), UNKNOWN); - assertEquals(selector.getSelection(EXCHANGE_ID_2, 200, 2), UNKNOWN); - assertFalse(selector.isFinal()); + assertThat(selector.getSelection(EXCHANGE_ID_2, 21, 2)).isEqualTo(UNKNOWN); + assertThat(selector.getSelection(EXCHANGE_ID_1, 21, 2)).isEqualTo(INCLUDED); + assertThat(selector.getSelection(EXCHANGE_ID_1, 21, 1)).isEqualTo(EXCLUDED); + assertThat(selector.getSelection(EXCHANGE_ID_2, 1, 2)).isEqualTo(UNKNOWN); + assertThat(selector.getSelection(EXCHANGE_ID_2, 200, 2)).isEqualTo(UNKNOWN); + assertThat(selector.isFinal()).isFalse(); } @Test @@ -130,14 +128,14 @@ public void testFinal() .setFinal() .build()); - assertEquals(selector.getSelection(EXCHANGE_ID_1, 0, 1), INCLUDED); - assertEquals(selector.getSelection(EXCHANGE_ID_1, 0, 2), EXCLUDED); - assertEquals(selector.getSelection(EXCHANGE_ID_1, 1, 0), EXCLUDED); - assertEquals(selector.getSelection(EXCHANGE_ID_1, 1, 2), EXCLUDED); - assertEquals(selector.getSelection(EXCHANGE_ID_1, 2, 0), INCLUDED); - assertEquals(selector.getSelection(EXCHANGE_ID_1, 2, 2), EXCLUDED); - assertEquals(selector.getSelection(EXCHANGE_ID_2, 0, 1), EXCLUDED); - assertEquals(selector.getSelection(EXCHANGE_ID_2, 0, 0), EXCLUDED); + assertThat(selector.getSelection(EXCHANGE_ID_1, 0, 1)).isEqualTo(INCLUDED); + assertThat(selector.getSelection(EXCHANGE_ID_1, 0, 2)).isEqualTo(EXCLUDED); + assertThat(selector.getSelection(EXCHANGE_ID_1, 1, 0)).isEqualTo(EXCLUDED); + assertThat(selector.getSelection(EXCHANGE_ID_1, 1, 2)).isEqualTo(EXCLUDED); + assertThat(selector.getSelection(EXCHANGE_ID_1, 2, 0)).isEqualTo(INCLUDED); + assertThat(selector.getSelection(EXCHANGE_ID_1, 2, 2)).isEqualTo(EXCLUDED); + assertThat(selector.getSelection(EXCHANGE_ID_2, 0, 1)).isEqualTo(EXCLUDED); + assertThat(selector.getSelection(EXCHANGE_ID_2, 0, 0)).isEqualTo(EXCLUDED); assertThatThrownBy(() -> selector.getSelection(EXCHANGE_ID_1, 100, 1)) .isInstanceOf(IllegalArgumentException.class) diff --git a/core/trino-main/src/test/java/io/trino/execution/BaseTestSqlTaskManager.java b/core/trino-main/src/test/java/io/trino/execution/BaseTestSqlTaskManager.java index 3188c833cac8..3323dcef7baa 100644 --- a/core/trino-main/src/test/java/io/trino/execution/BaseTestSqlTaskManager.java +++ b/core/trino-main/src/test/java/io/trino/execution/BaseTestSqlTaskManager.java @@ -77,14 +77,9 @@ import static io.trino.execution.buffer.PipelinedOutputBuffers.BufferType.PARTITIONED; import static io.trino.testing.TestingSession.testSessionBuilder; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -121,16 +116,16 @@ public void testEmptyQuery() try (SqlTaskManager sqlTaskManager = createSqlTaskManager(new TaskManagerConfig())) { TaskId taskId = newTaskId(); TaskInfo taskInfo = createTask(sqlTaskManager, taskId, PipelinedOutputBuffers.createInitial(PARTITIONED).withNoMoreBufferIds()); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.RUNNING); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.RUNNING); taskInfo = sqlTaskManager.getTaskInfo(taskId); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.RUNNING); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.RUNNING); taskInfo = createTask(sqlTaskManager, taskId, ImmutableSet.of(), PipelinedOutputBuffers.createInitial(PARTITIONED).withNoMoreBufferIds()); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.FINISHED); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.FINISHED); taskInfo = sqlTaskManager.getTaskInfo(taskId); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.FINISHED); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.FINISHED); } } @@ -144,27 +139,27 @@ public void testSimpleQuery() createTask(sqlTaskManager, taskId, ImmutableSet.of(SPLIT), PipelinedOutputBuffers.createInitial(PARTITIONED).withBuffer(OUT, 0).withNoMoreBufferIds()); TaskInfo taskInfo = sqlTaskManager.getTaskInfo(taskId, TaskStatus.STARTING_VERSION).get(); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.FLUSHING); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.FLUSHING); BufferResult results = sqlTaskManager.getTaskResults(taskId, OUT, 0, DataSize.of(1, Unit.MEGABYTE)).getResultsFuture().get(); - assertFalse(results.isBufferComplete()); - assertEquals(results.getSerializedPages().size(), 1); - assertEquals(getSerializedPagePositionCount(results.getSerializedPages().get(0)), 1); + assertThat(results.isBufferComplete()).isFalse(); + assertThat(results.getSerializedPages().size()).isEqualTo(1); + assertThat(getSerializedPagePositionCount(results.getSerializedPages().get(0))).isEqualTo(1); for (boolean moreResults = true; moreResults; moreResults = !results.isBufferComplete()) { results = sqlTaskManager.getTaskResults(taskId, OUT, results.getToken() + results.getSerializedPages().size(), DataSize.of(1, Unit.MEGABYTE)).getResultsFuture().get(); } - assertTrue(results.isBufferComplete()); - assertEquals(results.getSerializedPages().size(), 0); + assertThat(results.isBufferComplete()).isTrue(); + assertThat(results.getSerializedPages().size()).isEqualTo(0); // complete the task by calling destroy on it TaskInfo info = sqlTaskManager.destroyTaskResults(taskId, OUT); - assertEquals(info.getOutputBuffers().getState(), BufferState.FINISHED); + assertThat(info.getOutputBuffers().getState()).isEqualTo(BufferState.FINISHED); taskInfo = sqlTaskManager.getTaskInfo(taskId, taskInfo.getTaskStatus().getVersion()).get(); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.FINISHED); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.FINISHED); taskInfo = sqlTaskManager.getTaskInfo(taskId); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.FINISHED); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.FINISHED); } } @@ -175,20 +170,20 @@ public void testCancel() try (SqlTaskManager sqlTaskManager = createSqlTaskManager(new TaskManagerConfig())) { TaskId taskId = newTaskId(); TaskInfo taskInfo = createTask(sqlTaskManager, taskId, PipelinedOutputBuffers.createInitial(PARTITIONED).withBuffer(OUT, 0).withNoMoreBufferIds()); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.RUNNING); - assertNull(taskInfo.getStats().getEndTime()); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.RUNNING); + assertThat(taskInfo.getStats().getEndTime()).isNull(); taskInfo = sqlTaskManager.getTaskInfo(taskId); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.RUNNING); - assertNull(taskInfo.getStats().getEndTime()); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.RUNNING); + assertThat(taskInfo.getStats().getEndTime()).isNull(); taskInfo = pollTerminatingTaskInfoUntilDone(sqlTaskManager, sqlTaskManager.cancelTask(taskId)); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.CANCELED); - assertNotNull(taskInfo.getStats().getEndTime()); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.CANCELED); + assertThat(taskInfo.getStats().getEndTime()).isNotNull(); taskInfo = sqlTaskManager.getTaskInfo(taskId); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.CANCELED); - assertNotNull(taskInfo.getStats().getEndTime()); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.CANCELED); + assertThat(taskInfo.getStats().getEndTime()).isNotNull(); } } @@ -199,20 +194,20 @@ public void testAbort() try (SqlTaskManager sqlTaskManager = createSqlTaskManager(new TaskManagerConfig())) { TaskId taskId = newTaskId(); TaskInfo taskInfo = createTask(sqlTaskManager, taskId, PipelinedOutputBuffers.createInitial(PARTITIONED).withBuffer(OUT, 0).withNoMoreBufferIds()); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.RUNNING); - assertNull(taskInfo.getStats().getEndTime()); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.RUNNING); + assertThat(taskInfo.getStats().getEndTime()).isNull(); taskInfo = sqlTaskManager.getTaskInfo(taskId); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.RUNNING); - assertNull(taskInfo.getStats().getEndTime()); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.RUNNING); + assertThat(taskInfo.getStats().getEndTime()).isNull(); taskInfo = pollTerminatingTaskInfoUntilDone(sqlTaskManager, sqlTaskManager.abortTask(taskId)); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.ABORTED); - assertNotNull(taskInfo.getStats().getEndTime()); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.ABORTED); + assertThat(taskInfo.getStats().getEndTime()).isNotNull(); taskInfo = sqlTaskManager.getTaskInfo(taskId); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.ABORTED); - assertNotNull(taskInfo.getStats().getEndTime()); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.ABORTED); + assertThat(taskInfo.getStats().getEndTime()).isNotNull(); } } @@ -226,15 +221,15 @@ public void testAbortResults() createTask(sqlTaskManager, taskId, ImmutableSet.of(SPLIT), PipelinedOutputBuffers.createInitial(PARTITIONED).withBuffer(OUT, 0).withNoMoreBufferIds()); TaskInfo taskInfo = sqlTaskManager.getTaskInfo(taskId, TaskStatus.STARTING_VERSION).get(); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.FLUSHING); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.FLUSHING); sqlTaskManager.destroyTaskResults(taskId, OUT); taskInfo = sqlTaskManager.getTaskInfo(taskId, taskInfo.getTaskStatus().getVersion()).get(); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.FINISHED); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.FINISHED); taskInfo = sqlTaskManager.getTaskInfo(taskId); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.FINISHED); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.FINISHED); } } @@ -246,19 +241,20 @@ public void testRemoveOldTasks() TaskId taskId = newTaskId(); TaskInfo taskInfo = createTask(sqlTaskManager, taskId, PipelinedOutputBuffers.createInitial(PARTITIONED).withBuffer(OUT, 0).withNoMoreBufferIds()); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.RUNNING); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.RUNNING); taskInfo = pollTerminatingTaskInfoUntilDone(sqlTaskManager, sqlTaskManager.cancelTask(taskId)); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.CANCELED); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.CANCELED); taskInfo = sqlTaskManager.getTaskInfo(taskId); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.CANCELED); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.CANCELED); Thread.sleep(100); sqlTaskManager.removeOldTasks(); for (TaskInfo info : sqlTaskManager.getAllTaskInfo()) { - assertNotEquals(info.getTaskStatus().getTaskId(), taskId); + assertThat(info.getTaskStatus().getTaskId()) + .isNotEqualTo(taskId); } } } @@ -277,11 +273,11 @@ public void testSessionPropertyMemoryLimitOverride() QueryContext attemptsIncreaseContext = sqlTaskManager.getQueryContext(increaseLimitsId.getQueryId()); // not initialized with a task update yet - assertFalse(reducesLimitsContext.isMemoryLimitsInitialized()); - assertEquals(reducesLimitsContext.getMaxUserMemory(), memoryConfig.getMaxQueryMemoryPerNode().toBytes()); + assertThat(reducesLimitsContext.isMemoryLimitsInitialized()).isFalse(); + assertThat(reducesLimitsContext.getMaxUserMemory()).isEqualTo(memoryConfig.getMaxQueryMemoryPerNode().toBytes()); - assertFalse(attemptsIncreaseContext.isMemoryLimitsInitialized()); - assertEquals(attemptsIncreaseContext.getMaxUserMemory(), memoryConfig.getMaxQueryMemoryPerNode().toBytes()); + assertThat(attemptsIncreaseContext.isMemoryLimitsInitialized()).isFalse(); + assertThat(attemptsIncreaseContext.getMaxUserMemory()).isEqualTo(memoryConfig.getMaxQueryMemoryPerNode().toBytes()); // memory limits reduced by session properties sqlTaskManager.updateTask( @@ -295,8 +291,8 @@ public void testSessionPropertyMemoryLimitOverride() PipelinedOutputBuffers.createInitial(PARTITIONED).withBuffer(OUT, 0).withNoMoreBufferIds(), ImmutableMap.of(), false); - assertTrue(reducesLimitsContext.isMemoryLimitsInitialized()); - assertEquals(reducesLimitsContext.getMaxUserMemory(), 1); + assertThat(reducesLimitsContext.isMemoryLimitsInitialized()).isTrue(); + assertThat(reducesLimitsContext.getMaxUserMemory()).isEqualTo(1); // memory limits not increased by session properties sqlTaskManager.updateTask( @@ -310,8 +306,8 @@ public void testSessionPropertyMemoryLimitOverride() PipelinedOutputBuffers.createInitial(PARTITIONED).withBuffer(OUT, 0).withNoMoreBufferIds(), ImmutableMap.of(), false); - assertTrue(attemptsIncreaseContext.isMemoryLimitsInitialized()); - assertEquals(attemptsIncreaseContext.getMaxUserMemory(), memoryConfig.getMaxQueryMemoryPerNode().toBytes()); + assertThat(attemptsIncreaseContext.isMemoryLimitsInitialized()).isTrue(); + assertThat(attemptsIncreaseContext.getMaxUserMemory()).isEqualTo(memoryConfig.getMaxQueryMemoryPerNode().toBytes()); } } @@ -371,7 +367,7 @@ private TaskInfo createTask(SqlTaskManager sqlTaskManager, TaskId taskId, Output private static TaskInfo pollTerminatingTaskInfoUntilDone(SqlTaskManager taskManager, TaskInfo taskInfo) throws InterruptedException, ExecutionException, TimeoutException { - assertTrue(taskInfo.getTaskStatus().getState().isTerminatingOrDone()); + assertThat(taskInfo.getTaskStatus().getState().isTerminatingOrDone()).isTrue(); int attempts = 3; while (attempts > 0 && taskInfo.getTaskStatus().getState().isTerminating()) { taskInfo = taskManager.getTaskInfo(taskInfo.getTaskStatus().getTaskId(), taskInfo.getTaskStatus().getVersion()).get(5, SECONDS); diff --git a/core/trino-main/src/test/java/io/trino/execution/TestColumn.java b/core/trino-main/src/test/java/io/trino/execution/TestColumn.java index 6ff79c9c72ad..1bd6a51fe7b8 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestColumn.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestColumn.java @@ -16,7 +16,7 @@ import io.airlift.json.JsonCodec; import org.junit.jupiter.api.Test; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestColumn { @@ -29,6 +29,6 @@ public void testRoundTrip() String json = codec.toJson(expected); Column actual = codec.fromJson(json); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } } diff --git a/core/trino-main/src/test/java/io/trino/execution/TestCommitTask.java b/core/trino-main/src/test/java/io/trino/execution/TestCommitTask.java index bd656631a04e..f494b10fbde8 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestCommitTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestCommitTask.java @@ -50,11 +50,9 @@ import static io.trino.transaction.InMemoryTransactionManager.createTestTransactionManager; import static java.util.Collections.emptyList; import static java.util.concurrent.Executors.newCachedThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -79,14 +77,14 @@ public void testCommit() .setTransactionId(transactionManager.beginTransaction(false)) .build(); QueryStateMachine stateMachine = createQueryStateMachine("COMMIT", session, transactionManager); - assertTrue(stateMachine.getSession().getTransactionId().isPresent()); - assertEquals(transactionManager.getAllTransactionInfos().size(), 1); + assertThat(stateMachine.getSession().getTransactionId().isPresent()).isTrue(); + assertThat(transactionManager.getAllTransactionInfos().size()).isEqualTo(1); getFutureValue(new CommitTask(transactionManager).execute(new Commit(), stateMachine, emptyList(), WarningCollector.NOOP)); - assertTrue(stateMachine.getQueryInfo(Optional.empty()).isClearTransactionId()); - assertFalse(stateMachine.getQueryInfo(Optional.empty()).getStartedTransactionId().isPresent()); + assertThat(stateMachine.getQueryInfo(Optional.empty()).isClearTransactionId()).isTrue(); + assertThat(stateMachine.getQueryInfo(Optional.empty()).getStartedTransactionId().isPresent()).isFalse(); - assertTrue(transactionManager.getAllTransactionInfos().isEmpty()); + assertThat(transactionManager.getAllTransactionInfos().isEmpty()).isTrue(); } @Test @@ -102,10 +100,10 @@ public void testNoTransactionCommit() () -> getFutureValue(new CommitTask(transactionManager).execute(new Commit(), stateMachine, emptyList(), WarningCollector.NOOP))) .hasErrorCode(NOT_IN_TRANSACTION); - assertFalse(stateMachine.getQueryInfo(Optional.empty()).isClearTransactionId()); - assertFalse(stateMachine.getQueryInfo(Optional.empty()).getStartedTransactionId().isPresent()); + assertThat(stateMachine.getQueryInfo(Optional.empty()).isClearTransactionId()).isFalse(); + assertThat(stateMachine.getQueryInfo(Optional.empty()).getStartedTransactionId().isPresent()).isFalse(); - assertTrue(transactionManager.getAllTransactionInfos().isEmpty()); + assertThat(transactionManager.getAllTransactionInfos().isEmpty()).isTrue(); } @Test @@ -122,10 +120,10 @@ public void testUnknownTransactionCommit() assertTrinoExceptionThrownBy(() -> getFutureValue(future)) .hasErrorCode(UNKNOWN_TRANSACTION); - assertTrue(stateMachine.getQueryInfo(Optional.empty()).isClearTransactionId()); // Still issue clear signal - assertFalse(stateMachine.getQueryInfo(Optional.empty()).getStartedTransactionId().isPresent()); + assertThat(stateMachine.getQueryInfo(Optional.empty()).isClearTransactionId()).isTrue(); // Still issue clear signal + assertThat(stateMachine.getQueryInfo(Optional.empty()).getStartedTransactionId().isPresent()).isFalse(); - assertTrue(transactionManager.getAllTransactionInfos().isEmpty()); + assertThat(transactionManager.getAllTransactionInfos().isEmpty()).isTrue(); } private QueryStateMachine createQueryStateMachine(String query, Session session, TransactionManager transactionManager) diff --git a/core/trino-main/src/test/java/io/trino/execution/TestCreateCatalogTask.java b/core/trino-main/src/test/java/io/trino/execution/TestCreateCatalogTask.java index 330257852c3c..ff30570693aa 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestCreateCatalogTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestCreateCatalogTask.java @@ -42,8 +42,8 @@ import static io.trino.SessionTestUtils.TEST_SESSION; import static io.trino.execution.querystats.PlanOptimizersStatsCollector.createPlanOptimizersStatsCollector; import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.testng.Assert.assertTrue; @TestInstance(TestInstance.Lifecycle.PER_METHOD) public class TestCreateCatalogTask @@ -94,7 +94,7 @@ public void testDuplicatedCreateCatalog() CreateCatalogTask task = getCreateCatalogTask(); CreateCatalog statement = new CreateCatalog(new Identifier(TEST_CATALOG), false, new Identifier("tpch"), TPCH_PROPERTIES, Optional.empty(), Optional.empty()); getFutureValue(task.execute(statement, queryStateMachine, emptyList(), WarningCollector.NOOP)); - assertTrue(queryRunner.getMetadata().catalogExists(queryStateMachine.getSession(), TEST_CATALOG)); + assertThat(queryRunner.getMetadata().catalogExists(queryStateMachine.getSession(), TEST_CATALOG)).isTrue(); assertThatExceptionOfType(TrinoException.class) .isThrownBy(() -> getFutureValue(task.execute(statement, queryStateMachine, emptyList(), WarningCollector.NOOP))) .withMessage("Catalog '%s' already exists", TEST_CATALOG); @@ -106,9 +106,9 @@ public void testDuplicatedCreateCatalogIfNotExists() CreateCatalogTask task = getCreateCatalogTask(); CreateCatalog statement = new CreateCatalog(new Identifier(TEST_CATALOG), true, new Identifier("tpch"), TPCH_PROPERTIES, Optional.empty(), Optional.empty()); getFutureValue(task.execute(statement, queryStateMachine, emptyList(), WarningCollector.NOOP)); - assertTrue(queryRunner.getMetadata().catalogExists(queryStateMachine.getSession(), TEST_CATALOG)); + assertThat(queryRunner.getMetadata().catalogExists(queryStateMachine.getSession(), TEST_CATALOG)).isTrue(); getFutureValue(task.execute(statement, queryStateMachine, emptyList(), WarningCollector.NOOP)); - assertTrue(queryRunner.getMetadata().catalogExists(queryStateMachine.getSession(), TEST_CATALOG)); + assertThat(queryRunner.getMetadata().catalogExists(queryStateMachine.getSession(), TEST_CATALOG)).isTrue(); } @Test diff --git a/core/trino-main/src/test/java/io/trino/execution/TestCreateMaterializedViewTask.java b/core/trino-main/src/test/java/io/trino/execution/TestCreateMaterializedViewTask.java index 5ed1056ca709..7b103a902c60 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestCreateMaterializedViewTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestCreateMaterializedViewTask.java @@ -98,7 +98,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; -import static org.testng.Assert.assertEquals; @TestInstance(PER_METHOD) public class TestCreateMaterializedViewTask @@ -187,7 +186,7 @@ public void testCreateMaterializedViewIfNotExists() getFutureValue(new CreateMaterializedViewTask(plannerContext, new AllowAllAccessControl(), parser, analyzerFactory, materializedViewPropertyManager) .execute(statement, queryStateMachine, ImmutableList.of(), WarningCollector.NOOP)); - assertEquals(metadata.getCreateMaterializedViewCallCount(), 1); + assertThat(metadata.getCreateMaterializedViewCallCount()).isEqualTo(1); } @Test @@ -208,7 +207,7 @@ public void testCreateMaterializedViewWithExistingView() .hasErrorCode(ALREADY_EXISTS) .hasMessage("Materialized view already exists"); - assertEquals(metadata.getCreateMaterializedViewCallCount(), 1); + assertThat(metadata.getCreateMaterializedViewCallCount()).isEqualTo(1); } @Test @@ -229,7 +228,7 @@ public void testCreateMaterializedViewWithInvalidProperty() .hasErrorCode(INVALID_MATERIALIZED_VIEW_PROPERTY) .hasMessage("Catalog 'test_catalog' materialized view property 'baz' does not exist"); - assertEquals(metadata.getCreateMaterializedViewCallCount(), 0); + assertThat(metadata.getCreateMaterializedViewCallCount()).isEqualTo(0); } @Test diff --git a/core/trino-main/src/test/java/io/trino/execution/TestCreateSchemaTask.java b/core/trino-main/src/test/java/io/trino/execution/TestCreateSchemaTask.java index 1a4ab8c50cea..934a80a98cf3 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestCreateSchemaTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestCreateSchemaTask.java @@ -29,8 +29,8 @@ import static io.trino.testing.TestingHandles.TEST_CATALOG_HANDLE; import static io.trino.testing.TestingHandles.TEST_CATALOG_NAME; import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.testng.Assert.assertTrue; public class TestCreateSchemaTask extends BaseDataDefinitionTaskTest @@ -43,7 +43,7 @@ public void testDuplicatedCreateSchema() CreateSchemaTask task = getCreateSchemaTask(); CreateSchema statement = new CreateSchema(QualifiedName.of(CATALOG_SCHEMA_NAME.getSchemaName()), false, ImmutableList.of()); getFutureValue(task.execute(statement, queryStateMachine, emptyList(), WarningCollector.NOOP)); - assertTrue(metadata.schemaExists(testSession, CATALOG_SCHEMA_NAME)); + assertThat(metadata.schemaExists(testSession, CATALOG_SCHEMA_NAME)).isTrue(); assertThatExceptionOfType(TrinoException.class) .isThrownBy(() -> getFutureValue(task.execute(statement, queryStateMachine, emptyList(), WarningCollector.NOOP))) .withMessage("Schema 'test_catalog.test_db' already exists"); @@ -55,9 +55,9 @@ public void testDuplicatedCreateSchemaIfNotExists() CreateSchemaTask task = getCreateSchemaTask(); CreateSchema statement = new CreateSchema(QualifiedName.of(CATALOG_SCHEMA_NAME.getSchemaName()), true, ImmutableList.of()); getFutureValue(task.execute(statement, queryStateMachine, emptyList(), WarningCollector.NOOP)); - assertTrue(metadata.schemaExists(testSession, CATALOG_SCHEMA_NAME)); + assertThat(metadata.schemaExists(testSession, CATALOG_SCHEMA_NAME)).isTrue(); getFutureValue(task.execute(statement, queryStateMachine, emptyList(), WarningCollector.NOOP)); - assertTrue(metadata.schemaExists(testSession, CATALOG_SCHEMA_NAME)); + assertThat(metadata.schemaExists(testSession, CATALOG_SCHEMA_NAME)).isTrue(); } @Test diff --git a/core/trino-main/src/test/java/io/trino/execution/TestCreateTableTask.java b/core/trino-main/src/test/java/io/trino/execution/TestCreateTableTask.java index bb9889983524..55a203759a8a 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestCreateTableTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestCreateTableTask.java @@ -95,9 +95,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_METHOD) public class TestCreateTableTask @@ -176,7 +173,7 @@ public void testCreateTableNotExistsTrue() CreateTableTask createTableTask = new CreateTableTask(plannerContext, new AllowAllAccessControl(), columnPropertyManager, tablePropertyManager); getFutureValue(createTableTask.internalExecute(statement, testSession, emptyList(), output -> {})); - assertEquals(metadata.getCreateTableCallCount(), 1); + assertThat(metadata.getCreateTableCallCount()).isEqualTo(1); } @Test @@ -193,7 +190,7 @@ public void testCreateTableNotExistsFalse() .hasErrorCode(ALREADY_EXISTS) .hasMessage("Table already exists"); - assertEquals(metadata.getCreateTableCallCount(), 1); + assertThat(metadata.getCreateTableCallCount()).isEqualTo(1); } @Test @@ -207,7 +204,7 @@ public void testReplaceTable() CreateTableTask createTableTask = new CreateTableTask(plannerContext, new AllowAllAccessControl(), columnPropertyManager, tablePropertyManager); getFutureValue(createTableTask.internalExecute(statement, testSession, emptyList(), output -> {})); - assertEquals(metadata.getCreateTableCallCount(), 1); + assertThat(metadata.getCreateTableCallCount()).isEqualTo(1); assertThat(metadata.getReceivedTableMetadata().get(0).getColumns()) .isEqualTo(ImmutableList.of(new ColumnMetadata("a", BIGINT))); } @@ -226,7 +223,7 @@ public void testCreateTableWithMaterializedViewPropertyFails() .hasErrorCode(INVALID_TABLE_PROPERTY) .hasMessage("Catalog 'test_catalog' table property 'foo' does not exist"); - assertEquals(metadata.getCreateTableCallCount(), 0); + assertThat(metadata.getCreateTableCallCount()).isEqualTo(0); } @Test @@ -241,21 +238,21 @@ public void testCreateWithNotNullColumns() CreateTableTask createTableTask = new CreateTableTask(plannerContext, new AllowAllAccessControl(), columnPropertyManager, tablePropertyManager); getFutureValue(createTableTask.internalExecute(statement, testSession, emptyList(), output -> {})); - assertEquals(metadata.getCreateTableCallCount(), 1); + assertThat(metadata.getCreateTableCallCount()).isEqualTo(1); List columns = metadata.getReceivedTableMetadata().get(0).getColumns(); - assertEquals(columns.size(), 3); + assertThat(columns.size()).isEqualTo(3); - assertEquals(columns.get(0).getName(), "a"); - assertEquals(columns.get(0).getType().getDisplayName().toUpperCase(ENGLISH), "DATE"); - assertTrue(columns.get(0).isNullable()); + assertThat(columns.get(0).getName()).isEqualTo("a"); + assertThat(columns.get(0).getType().getDisplayName().toUpperCase(ENGLISH)).isEqualTo("DATE"); + assertThat(columns.get(0).isNullable()).isTrue(); - assertEquals(columns.get(1).getName(), "b"); - assertEquals(columns.get(1).getType().getDisplayName().toUpperCase(ENGLISH), "VARCHAR"); - assertFalse(columns.get(1).isNullable()); + assertThat(columns.get(1).getName()).isEqualTo("b"); + assertThat(columns.get(1).getType().getDisplayName().toUpperCase(ENGLISH)).isEqualTo("VARCHAR"); + assertThat(columns.get(1).isNullable()).isFalse(); - assertEquals(columns.get(2).getName(), "c"); - assertEquals(columns.get(2).getType().getDisplayName().toUpperCase(ENGLISH), "VARBINARY"); - assertFalse(columns.get(2).isNullable()); + assertThat(columns.get(2).getName()).isEqualTo("c"); + assertThat(columns.get(2).getType().getDisplayName().toUpperCase(ENGLISH)).isEqualTo("VARBINARY"); + assertThat(columns.get(2).isNullable()).isFalse(); } @Test @@ -286,7 +283,7 @@ public void testCreateLike() CreateTableTask createTableTask = new CreateTableTask(plannerContext, new AllowAllAccessControl(), columnPropertyManager, tablePropertyManager); getFutureValue(createTableTask.internalExecute(statement, testSession, List.of(), output -> {})); - assertEquals(metadata.getCreateTableCallCount(), 1); + assertThat(metadata.getCreateTableCallCount()).isEqualTo(1); assertThat(metadata.getReceivedTableMetadata().get(0).getColumns()) .isEqualTo(PARENT_TABLE.getColumns()); @@ -300,7 +297,7 @@ public void testCreateLikeIncludingProperties() CreateTableTask createTableTask = new CreateTableTask(plannerContext, new AllowAllAccessControl(), columnPropertyManager, tablePropertyManager); getFutureValue(createTableTask.internalExecute(statement, testSession, List.of(), output -> {})); - assertEquals(metadata.getCreateTableCallCount(), 1); + assertThat(metadata.getCreateTableCallCount()).isEqualTo(1); assertThat(metadata.getReceivedTableMetadata().get(0).getColumns()) .isEqualTo(PARENT_TABLE.getColumns()); @@ -315,7 +312,7 @@ public void testCreateLikeExcludingPropertiesAcrossCatalogs() CreateTableTask createTableTask = new CreateTableTask(plannerContext, new AllowAllAccessControl(), columnPropertyManager, tablePropertyManager); getFutureValue(createTableTask.internalExecute(statement, testSession, List.of(), output -> {})); - assertEquals(metadata.getCreateTableCallCount(), 1); + assertThat(metadata.getCreateTableCallCount()).isEqualTo(1); assertThat(metadata.getReceivedTableMetadata().get(0).getColumns()) .isEqualTo(PARENT_TABLE.getColumns()); @@ -411,7 +408,7 @@ public void testCreateTableLikeWithCoercedType() CreateTableTask createTableTask = new CreateTableTask(plannerContext, new AllowAllAccessControl(), columnPropertyManager, tablePropertyManager); getFutureValue(createTableTask.internalExecute(statement, testSession, List.of(), output -> {})); - assertEquals(metadata.getCreateTableCallCount(), 1); + assertThat(metadata.getCreateTableCallCount()).isEqualTo(1); assertThat(metadata.getReceivedTableMetadata().get(0).getColumns()) .isEqualTo(ImmutableList.of(new ColumnMetadata("a", TIMESTAMP_MILLIS))); diff --git a/core/trino-main/src/test/java/io/trino/execution/TestDeallocateTask.java b/core/trino-main/src/test/java/io/trino/execution/TestDeallocateTask.java index cd852db09a8b..9d425fb30bc6 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestDeallocateTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestDeallocateTask.java @@ -49,9 +49,9 @@ import static io.trino.transaction.InMemoryTransactionManager.createTestTransactionManager; import static java.util.Collections.emptyList; import static java.util.concurrent.Executors.newCachedThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -74,7 +74,7 @@ public void testDeallocate() .addPreparedStatement("my_query", "SELECT bar, baz FROM foo") .build(); Set statements = executeDeallocate("my_query", "DEALLOCATE PREPARE my_query", session); - assertEquals(statements, ImmutableSet.of("my_query")); + assertThat(statements).isEqualTo(ImmutableSet.of("my_query")); } @Test diff --git a/core/trino-main/src/test/java/io/trino/execution/TestDropCatalogTask.java b/core/trino-main/src/test/java/io/trino/execution/TestDropCatalogTask.java index e25fc66dc660..5c43b2182fe4 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestDropCatalogTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestDropCatalogTask.java @@ -37,10 +37,9 @@ import static io.trino.execution.querystats.PlanOptimizersStatsCollector.createPlanOptimizersStatsCollector; import static io.trino.testing.TestingSession.testSession; import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_METHOD) public class TestDropCatalogTask @@ -69,12 +68,12 @@ public void tearDown() public void testDuplicatedCreateCatalog() { queryRunner.createCatalog(TEST_CATALOG, "tpch", ImmutableMap.of()); - assertTrue(queryRunner.getMetadata().catalogExists(createNewQuery().getSession(), TEST_CATALOG)); + assertThat(queryRunner.getMetadata().catalogExists(createNewQuery().getSession(), TEST_CATALOG)).isTrue(); DropCatalogTask task = getCreateCatalogTask(); DropCatalog statement = new DropCatalog(new Identifier(TEST_CATALOG), false, false); getFutureValue(task.execute(statement, createNewQuery(), emptyList(), WarningCollector.NOOP)); - assertFalse(queryRunner.getMetadata().catalogExists(createNewQuery().getSession(), TEST_CATALOG)); + assertThat(queryRunner.getMetadata().catalogExists(createNewQuery().getSession(), TEST_CATALOG)).isFalse(); assertThatExceptionOfType(TrinoException.class) .isThrownBy(() -> getFutureValue(task.execute(statement, createNewQuery(), emptyList(), WarningCollector.NOOP))) .withMessage("Catalog '%s' does not exist", TEST_CATALOG); @@ -84,14 +83,14 @@ public void testDuplicatedCreateCatalog() public void testDuplicatedCreateCatalogIfNotExists() { queryRunner.createCatalog(TEST_CATALOG, "tpch", ImmutableMap.of()); - assertTrue(queryRunner.getMetadata().catalogExists(createNewQuery().getSession(), TEST_CATALOG)); + assertThat(queryRunner.getMetadata().catalogExists(createNewQuery().getSession(), TEST_CATALOG)).isTrue(); DropCatalogTask task = getCreateCatalogTask(); DropCatalog statement = new DropCatalog(new Identifier(TEST_CATALOG), true, false); getFutureValue(task.execute(statement, createNewQuery(), emptyList(), WarningCollector.NOOP)); - assertFalse(queryRunner.getMetadata().catalogExists(createNewQuery().getSession(), TEST_CATALOG)); + assertThat(queryRunner.getMetadata().catalogExists(createNewQuery().getSession(), TEST_CATALOG)).isFalse(); getFutureValue(task.execute(statement, createNewQuery(), emptyList(), WarningCollector.NOOP)); - assertFalse(queryRunner.getMetadata().catalogExists(createNewQuery().getSession(), TEST_CATALOG)); + assertThat(queryRunner.getMetadata().catalogExists(createNewQuery().getSession(), TEST_CATALOG)).isFalse(); } private DropCatalogTask getCreateCatalogTask() diff --git a/core/trino-main/src/test/java/io/trino/execution/TestDropSchemaTask.java b/core/trino-main/src/test/java/io/trino/execution/TestDropSchemaTask.java index 47a037528ec4..5af2867e85ff 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestDropSchemaTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestDropSchemaTask.java @@ -32,9 +32,8 @@ import static io.trino.testing.TestingHandles.TEST_CATALOG_HANDLE; import static io.trino.testing.TestingHandles.TEST_CATALOG_NAME; import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestDropSchemaTask extends BaseDataDefinitionTaskTest @@ -47,12 +46,12 @@ public void testDropSchemaRestrict() CreateSchemaTask createSchemaTask = getCreateSchemaTask(); CreateSchema createSchema = new CreateSchema(QualifiedName.of(CATALOG_SCHEMA_NAME.getSchemaName()), false, ImmutableList.of()); getFutureValue(createSchemaTask.execute(createSchema, queryStateMachine, emptyList(), NOOP)); - assertTrue(metadata.schemaExists(testSession, CATALOG_SCHEMA_NAME)); + assertThat(metadata.schemaExists(testSession, CATALOG_SCHEMA_NAME)).isTrue(); DropSchemaTask dropSchemaTask = getDropSchemaTask(); DropSchema dropSchema = new DropSchema(QualifiedName.of(CATALOG_SCHEMA_NAME.getSchemaName()), false, false); getFutureValue(dropSchemaTask.execute(dropSchema, queryStateMachine, emptyList(), NOOP)); - assertFalse(metadata.schemaExists(testSession, CATALOG_SCHEMA_NAME)); + assertThat(metadata.schemaExists(testSession, CATALOG_SCHEMA_NAME)).isFalse(); assertThatExceptionOfType(TrinoException.class) .isThrownBy(() -> getFutureValue(dropSchemaTask.execute(dropSchema, queryStateMachine, emptyList(), NOOP))) @@ -75,7 +74,7 @@ public void testDropNonEmptySchemaRestrict() assertThatExceptionOfType(TrinoException.class) .isThrownBy(() -> getFutureValue(dropSchemaTask.execute(dropSchema, queryStateMachine, emptyList(), NOOP))) .withMessage("Cannot drop non-empty schema 'test_db'"); - assertTrue(metadata.schemaExists(testSession, CATALOG_SCHEMA_NAME)); + assertThat(metadata.schemaExists(testSession, CATALOG_SCHEMA_NAME)).isTrue(); } @Test @@ -83,7 +82,7 @@ public void testDropSchemaIfExistsRestrict() { CatalogSchemaName schema = new CatalogSchemaName(CATALOG_SCHEMA_NAME.getCatalogName(), "test_if_exists_restrict"); - assertFalse(metadata.schemaExists(testSession, schema)); + assertThat(metadata.schemaExists(testSession, schema)).isFalse(); DropSchemaTask dropSchemaTask = getDropSchemaTask(); DropSchema dropSchema = new DropSchema(QualifiedName.of("test_if_exists_restrict"), true, false); @@ -96,13 +95,13 @@ public void testDropSchemaCascade() CreateSchemaTask createSchemaTask = getCreateSchemaTask(); CreateSchema createSchema = new CreateSchema(QualifiedName.of(CATALOG_SCHEMA_NAME.getSchemaName()), false, ImmutableList.of()); getFutureValue(createSchemaTask.execute(createSchema, queryStateMachine, emptyList(), NOOP)); - assertTrue(metadata.schemaExists(testSession, CATALOG_SCHEMA_NAME)); + assertThat(metadata.schemaExists(testSession, CATALOG_SCHEMA_NAME)).isTrue(); DropSchemaTask dropSchemaTask = getDropSchemaTask(); DropSchema dropSchema = new DropSchema(QualifiedName.of(CATALOG_SCHEMA_NAME.getSchemaName()), false, true); getFutureValue(dropSchemaTask.execute(dropSchema, queryStateMachine, emptyList(), NOOP)); - assertFalse(metadata.schemaExists(testSession, CATALOG_SCHEMA_NAME)); + assertThat(metadata.schemaExists(testSession, CATALOG_SCHEMA_NAME)).isFalse(); } @Test @@ -119,7 +118,7 @@ public void testDropNonEmptySchemaCascade() metadata.createTable(testSession, CATALOG_SCHEMA_NAME.getCatalogName(), someTable(tableName), FAIL); getFutureValue(dropSchemaTask.execute(dropSchema, queryStateMachine, emptyList(), NOOP)); - assertFalse(metadata.schemaExists(testSession, CATALOG_SCHEMA_NAME)); + assertThat(metadata.schemaExists(testSession, CATALOG_SCHEMA_NAME)).isFalse(); } @Test @@ -127,7 +126,7 @@ public void testDropSchemaIfExistsCascade() { CatalogSchemaName schema = new CatalogSchemaName(CATALOG_SCHEMA_NAME.getCatalogName(), "test_if_exists_cascade"); - assertFalse(metadata.schemaExists(testSession, schema)); + assertThat(metadata.schemaExists(testSession, schema)).isFalse(); DropSchemaTask dropSchemaTask = getDropSchemaTask(); DropSchema dropSchema = new DropSchema(QualifiedName.of("test_if_exists_cascade"), true, false); diff --git a/core/trino-main/src/test/java/io/trino/execution/TestDynamicFiltersCollector.java b/core/trino-main/src/test/java/io/trino/execution/TestDynamicFiltersCollector.java index 08b79933782e..4613190b38dc 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestDynamicFiltersCollector.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestDynamicFiltersCollector.java @@ -24,7 +24,7 @@ import static io.trino.spi.predicate.Domain.multipleValues; import static io.trino.spi.predicate.Domain.singleValue; import static io.trino.spi.type.BigintType.BIGINT; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestDynamicFiltersCollector { @@ -37,34 +37,34 @@ public void testDynamicFiltersCollector() VersionedDynamicFilterDomains domains = collector.acknowledgeAndGetNewDomains(INITIAL_DYNAMIC_FILTERS_VERSION); // there should be no domains initially - assertEquals(domains.getVersion(), INITIAL_DYNAMIC_FILTERS_VERSION); - assertEquals(domains.getDynamicFilterDomains(), ImmutableMap.of()); + assertThat(domains.getVersion()).isEqualTo(INITIAL_DYNAMIC_FILTERS_VERSION); + assertThat(domains.getDynamicFilterDomains()).isEqualTo(ImmutableMap.of()); Domain initialDomain = multipleValues(BIGINT, ImmutableList.of(1L, 2L, 3L)); collector.updateDomains(ImmutableMap.of(filter, initialDomain)); domains = collector.acknowledgeAndGetNewDomains(INITIAL_DYNAMIC_FILTERS_VERSION); - assertEquals(domains.getVersion(), 1L); - assertEquals(domains.getDynamicFilterDomains(), ImmutableMap.of(filter, initialDomain)); + assertThat(domains.getVersion()).isEqualTo(1L); + assertThat(domains.getDynamicFilterDomains()).isEqualTo(ImmutableMap.of(filter, initialDomain)); // make sure domains are still available when requested again with old version domains = collector.acknowledgeAndGetNewDomains(INITIAL_DYNAMIC_FILTERS_VERSION); - assertEquals(domains.getVersion(), 1L); - assertEquals(domains.getDynamicFilterDomains(), ImmutableMap.of(filter, initialDomain)); + assertThat(domains.getVersion()).isEqualTo(1L); + assertThat(domains.getDynamicFilterDomains()).isEqualTo(ImmutableMap.of(filter, initialDomain)); // make sure domains are intersected collector.updateDomains(ImmutableMap.of(filter, multipleValues(BIGINT, ImmutableList.of(2L)))); collector.updateDomains(ImmutableMap.of(filter, multipleValues(BIGINT, ImmutableList.of(3L, 4L)))); domains = collector.acknowledgeAndGetNewDomains(1L); - assertEquals(domains.getVersion(), 3L); - assertEquals(domains.getDynamicFilterDomains(), ImmutableMap.of(filter, Domain.none(BIGINT))); + assertThat(domains.getVersion()).isEqualTo(3L); + assertThat(domains.getDynamicFilterDomains()).isEqualTo(ImmutableMap.of(filter, Domain.none(BIGINT))); // make sure old domains are removed DynamicFilterId filter2 = new DynamicFilterId("filter2"); collector.updateDomains(ImmutableMap.of(filter2, singleValue(BIGINT, 1L))); domains = collector.acknowledgeAndGetNewDomains(3L); - assertEquals(domains.getVersion(), 4L); - assertEquals(domains.getDynamicFilterDomains(), ImmutableMap.of(filter2, singleValue(BIGINT, 1L))); + assertThat(domains.getVersion()).isEqualTo(4L); + assertThat(domains.getDynamicFilterDomains()).isEqualTo(ImmutableMap.of(filter2, singleValue(BIGINT, 1L))); } } diff --git a/core/trino-main/src/test/java/io/trino/execution/TestInput.java b/core/trino-main/src/test/java/io/trino/execution/TestInput.java index be3d024fb142..c5c413c8635c 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestInput.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestInput.java @@ -22,7 +22,7 @@ import java.util.Optional; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestInput { @@ -47,6 +47,6 @@ public void testRoundTrip() String json = codec.toJson(expected); Input actual = codec.fromJson(json); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } } diff --git a/core/trino-main/src/test/java/io/trino/execution/TestMemoryRevokingScheduler.java b/core/trino-main/src/test/java/io/trino/execution/TestMemoryRevokingScheduler.java index 982efea623fb..c5855316582e 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestMemoryRevokingScheduler.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestMemoryRevokingScheduler.java @@ -63,10 +63,8 @@ import static io.trino.execution.buffer.PipelinedOutputBuffers.BufferType.PARTITIONED; import static java.util.Collections.singletonList; import static java.util.concurrent.Executors.newScheduledThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_METHOD) public class TestMemoryRevokingScheduler @@ -152,7 +150,7 @@ public void testScheduleMemoryRevoking() assertMemoryRevokingNotRequested(); requestMemoryRevoking(scheduler); - assertEquals(10, memoryPool.getFreeBytes()); + assertThat(10).isEqualTo(memoryPool.getFreeBytes()); assertMemoryRevokingNotRequested(); LocalMemoryContext revocableMemory1 = operatorContext1.localRevocableMemoryContext(); @@ -162,13 +160,13 @@ public void testScheduleMemoryRevoking() revocableMemory1.setBytes(3); revocableMemory3.setBytes(6); - assertEquals(1, memoryPool.getFreeBytes()); + assertThat(1).isEqualTo(memoryPool.getFreeBytes()); requestMemoryRevoking(scheduler); // we are still good - no revoking needed assertMemoryRevokingNotRequested(); revocableMemory4.setBytes(7); - assertEquals(-6, memoryPool.getFreeBytes()); + assertThat(-6).isEqualTo(memoryPool.getFreeBytes()); requestMemoryRevoking(scheduler); // we need to revoke 3 and 6 assertMemoryRevokingRequestedFor(operatorContext1, operatorContext3); @@ -182,18 +180,18 @@ public void testScheduleMemoryRevoking() operatorContext1.resetMemoryRevokingRequested(); requestMemoryRevoking(scheduler); assertMemoryRevokingRequestedFor(operatorContext3); - assertEquals(-3, memoryPool.getFreeBytes()); + assertThat(-3).isEqualTo(memoryPool.getFreeBytes()); // and allocate some more revocableMemory5.setBytes(3); - assertEquals(-6, memoryPool.getFreeBytes()); + assertThat(-6).isEqualTo(memoryPool.getFreeBytes()); requestMemoryRevoking(scheduler); // we are still good with just OC3 in process of revoking assertMemoryRevokingRequestedFor(operatorContext3); // and allocate some more revocableMemory5.setBytes(4); - assertEquals(-7, memoryPool.getFreeBytes()); + assertThat(-7).isEqualTo(memoryPool.getFreeBytes()); requestMemoryRevoking(scheduler); // no we have to trigger revoking for OC4 assertMemoryRevokingRequestedFor(operatorContext3, operatorContext4); @@ -250,9 +248,13 @@ private void assertMemoryRevokingRequestedFor(OperatorContext... operatorContext { ImmutableSet operatorContextsSet = ImmutableSet.copyOf(operatorContexts); operatorContextsSet.forEach( - operatorContext -> assertTrue(operatorContext.isMemoryRevokingRequested(), "expected memory requested for operator " + operatorContext.getOperatorId())); + operatorContext -> assertThat(operatorContext.isMemoryRevokingRequested()) + .describedAs("expected memory requested for operator " + operatorContext.getOperatorId()) + .isTrue()); Sets.difference(allOperatorContexts, operatorContextsSet).forEach( - operatorContext -> assertFalse(operatorContext.isMemoryRevokingRequested(), "expected memory not requested for operator " + operatorContext.getOperatorId())); + operatorContext -> assertThat(operatorContext.isMemoryRevokingRequested()) + .describedAs("expected memory not requested for operator " + operatorContext.getOperatorId()) + .isFalse()); } private void assertMemoryRevokingNotRequested() diff --git a/core/trino-main/src/test/java/io/trino/execution/TestNodeScheduler.java b/core/trino-main/src/test/java/io/trino/execution/TestNodeScheduler.java index e95f4e21e67b..f159ee3e8248 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestNodeScheduler.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestNodeScheduler.java @@ -87,11 +87,9 @@ import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_METHOD) @Execution(SAME_THREAD) @@ -174,8 +172,8 @@ public void testScheduleLocal() Set splits = ImmutableSet.of(split); Map.Entry assignment = getOnlyElement(nodeSelector.computeAssignments(splits, ImmutableList.copyOf(taskMap.values())).getAssignments().entries()); - assertEquals(assignment.getKey().getHostAndPort(), split.getAddresses().get(0)); - assertEquals(assignment.getValue(), split); + assertThat(assignment.getKey().getHostAndPort()).isEqualTo(split.getAddresses().get(0)); + assertThat(assignment.getValue()).isEqualTo(split); } @Test @@ -228,7 +226,7 @@ public void testTopologyAwareScheduling() } nonRackLocalSplits = Sets.difference(nonRackLocalSplits, new HashSet<>(assignments.values())); // Check that 3 of the splits were rejected, since they're non-local - assertEquals(nonRackLocalSplits.size(), 3); + assertThat(nonRackLocalSplits.size()).isEqualTo(3); // Assign rack-local splits ImmutableSet.Builder rackLocalSplits = ImmutableSet.builder(); @@ -258,7 +256,7 @@ public void testTopologyAwareScheduling() .build()); } unassigned = Sets.difference(unassigned, new HashSet<>(assignments.values())); - assertEquals(unassigned.size(), 3); + assertThat(unassigned.size()).isEqualTo(3); int rack1 = 0; int rack2 = 0; for (Split split : unassigned) { @@ -274,8 +272,8 @@ public void testTopologyAwareScheduling() throw new AssertionError("Unexpected rack: " + rack); } } - assertEquals(rack1, 2); - assertEquals(rack2, 1); + assertThat(rack1).isEqualTo(2); + assertThat(rack2).isEqualTo(1); // Assign local splits ImmutableSet.Builder localSplits = ImmutableSet.builder(); @@ -283,8 +281,8 @@ public void testTopologyAwareScheduling() localSplits.add(new Split(TEST_CATALOG_HANDLE, new TestSplitRemote(HostAddress.fromParts("host2.rack1", 1)))); localSplits.add(new Split(TEST_CATALOG_HANDLE, new TestSplitRemote(HostAddress.fromParts("host3.rack2", 1)))); assignments = nodeSelector.computeAssignments(localSplits.build(), ImmutableList.copyOf(taskMap.values())).getAssignments(); - assertEquals(assignments.size(), 3); - assertEquals(assignments.keySet().size(), 3); + assertThat(assignments.size()).isEqualTo(3); + assertThat(assignments.keySet().size()).isEqualTo(3); } @Test @@ -294,7 +292,7 @@ public void testScheduleRemote() Set splits = new HashSet<>(); splits.add(new Split(TEST_CATALOG_HANDLE, new TestSplitRemote())); Multimap assignments = nodeSelector.computeAssignments(splits, ImmutableList.copyOf(taskMap.values())).getAssignments(); - assertEquals(assignments.size(), 1); + assertThat(assignments.size()).isEqualTo(1); } @Test @@ -311,9 +309,9 @@ public void testBasicAssignment() splits.add(new Split(TEST_CATALOG_HANDLE, new TestSplitRemote())); } Multimap assignments = nodeSelector.computeAssignments(splits, ImmutableList.copyOf(taskMap.values())).getAssignments(); - assertEquals(assignments.entries().size(), assignments.size()); + assertThat(assignments.entries().size()).isEqualTo(assignments.size()); for (InternalNode node : activeCatalogNodes) { - assertTrue(assignments.keySet().contains(node)); + assertThat(assignments.keySet().contains(node)).isTrue(); } } @@ -346,12 +344,12 @@ public void testMaxSplitsPerNode() Multimap assignments = nodeSelector.computeAssignments(splits, ImmutableList.copyOf(taskMap.values())).getAssignments(); // no split should be assigned to the newNode, as it already has maxNodeSplits assigned to it - assertFalse(assignments.keySet().contains(newNode)); + assertThat(assignments.keySet().contains(newNode)).isFalse(); remoteTask1.abort(); remoteTask2.abort(); - assertEquals(nodeTaskMap.getPartitionedSplitsOnNode(newNode), PartitionedSplitsInfo.forZeroSplits()); + assertThat(nodeTaskMap.getPartitionedSplitsOnNode(newNode)).isEqualTo(PartitionedSplitsInfo.forZeroSplits()); } @Test @@ -370,9 +368,9 @@ public void testBasicAssignmentMaxUnacknowledgedSplitsPerTask() splits.add(new Split(TEST_CATALOG_HANDLE, new TestSplitRemote())); } Multimap assignments = nodeSelector.computeAssignments(splits, ImmutableList.copyOf(taskMap.values())).getAssignments(); - assertEquals(assignments.entries().size(), activeCatalogNodes.size()); + assertThat(assignments.entries().size()).isEqualTo(activeCatalogNodes.size()); for (InternalNode node : activeCatalogNodes) { - assertTrue(assignments.keySet().contains(node)); + assertThat(assignments.keySet().contains(node)).isTrue(); } } @@ -413,13 +411,13 @@ public void testMaxSplitsPerNodePerTask() // no split should be assigned to the newNode, as it already has // maxSplitsPerNode + maxSplitsPerNodePerTask assigned to it - assertEquals(assignments.keySet().size(), 3); // Splits should be scheduled on the other three nodes - assertFalse(assignments.keySet().contains(newNode)); // No splits scheduled on the maxed out node + assertThat(assignments.keySet().size()).isEqualTo(3); // Splits should be scheduled on the other three nodes + assertThat(assignments.keySet().contains(newNode)).isFalse(); // No splits scheduled on the maxed out node for (RemoteTask task : tasks) { task.abort(); } - assertEquals(nodeTaskMap.getPartitionedSplitsOnNode(newNode), PartitionedSplitsInfo.forZeroSplits()); + assertThat(nodeTaskMap.getPartitionedSplitsOnNode(newNode)).isEqualTo(PartitionedSplitsInfo.forZeroSplits()); } @Test @@ -436,13 +434,13 @@ public void testTaskCompletion() ImmutableList.of(new Split(TEST_CATALOG_HANDLE, new TestSplitRemote())), nodeTaskMap.createPartitionedSplitCountTracker(chosenNode, taskId)); nodeTaskMap.addTask(chosenNode, remoteTask); - assertEquals(nodeTaskMap.getPartitionedSplitsOnNode(chosenNode).getCount(), 1); + assertThat(nodeTaskMap.getPartitionedSplitsOnNode(chosenNode).getCount()).isEqualTo(1); remoteTask.abort(); MILLISECONDS.sleep(100); // Sleep until cache expires - assertEquals(nodeTaskMap.getPartitionedSplitsOnNode(chosenNode), PartitionedSplitsInfo.forZeroSplits()); + assertThat(nodeTaskMap.getPartitionedSplitsOnNode(chosenNode)).isEqualTo(PartitionedSplitsInfo.forZeroSplits()); remoteTask.abort(); - assertEquals(nodeTaskMap.getPartitionedSplitsOnNode(chosenNode), PartitionedSplitsInfo.forZeroSplits()); + assertThat(nodeTaskMap.getPartitionedSplitsOnNode(chosenNode)).isEqualTo(PartitionedSplitsInfo.forZeroSplits()); } @Test @@ -469,12 +467,12 @@ public void testSplitCount() nodeTaskMap.addTask(chosenNode, remoteTask1); nodeTaskMap.addTask(chosenNode, remoteTask2); - assertEquals(nodeTaskMap.getPartitionedSplitsOnNode(chosenNode).getCount(), 3); + assertThat(nodeTaskMap.getPartitionedSplitsOnNode(chosenNode).getCount()).isEqualTo(3); remoteTask1.abort(); - assertEquals(nodeTaskMap.getPartitionedSplitsOnNode(chosenNode).getCount(), 1); + assertThat(nodeTaskMap.getPartitionedSplitsOnNode(chosenNode).getCount()).isEqualTo(1); remoteTask2.abort(); - assertEquals(nodeTaskMap.getPartitionedSplitsOnNode(chosenNode), PartitionedSplitsInfo.forZeroSplits()); + assertThat(nodeTaskMap.getPartitionedSplitsOnNode(chosenNode)).isEqualTo(PartitionedSplitsInfo.forZeroSplits()); } @Test @@ -492,9 +490,9 @@ public void testPrioritizedAssignmentOfLocalSplit() // computeAssignments just returns a mapping of nodes with splits to be assigned, it does not assign splits Multimap initialAssignment = nodeSelector.computeAssignments(splits, ImmutableList.copyOf(taskMap.values())).getAssignments(); // Check that all splits are being assigned to node1 - assertEquals(initialAssignment.size(), 20); - assertEquals(initialAssignment.keySet().size(), 1); - assertTrue(initialAssignment.keySet().contains(node)); + assertThat(initialAssignment.size()).isEqualTo(20); + assertThat(initialAssignment.keySet().size()).isEqualTo(1); + assertThat(initialAssignment.keySet().contains(node)).isTrue(); // Check for assignment of splits beyond maxSplitsPerNode (2 splits should remain unassigned) // 1 split with node1 as local node @@ -504,16 +502,16 @@ public void testPrioritizedAssignmentOfLocalSplit() //splits now contains 22 splits : 1 with node1 as local node and 21 with node1 as a non-local node Multimap finalAssignment = nodeSelector.computeAssignments(splits, ImmutableList.copyOf(taskMap.values())).getAssignments(); // Check that only 20 splits are being assigned as there is a single task - assertEquals(finalAssignment.size(), 20); - assertEquals(finalAssignment.keySet().size(), 1); - assertTrue(finalAssignment.keySet().contains(node)); + assertThat(finalAssignment.size()).isEqualTo(20); + assertThat(finalAssignment.keySet().size()).isEqualTo(1); + assertThat(finalAssignment.keySet().contains(node)).isTrue(); // When optimized-local-scheduling is enabled, the split with node1 as local node should be assigned long countLocalSplits = finalAssignment.values().stream() .map(Split::getConnectorSplit) .filter(TestSplitLocal.class::isInstance) .count(); - assertEquals(countLocalSplits, 1); + assertThat(countLocalSplits).isEqualTo(1); } @Test @@ -535,9 +533,9 @@ public void testAssignmentWhenMixedSplits() // computeAssignments just returns a mapping of nodes with splits to be assigned, it does not assign splits Multimap initialAssignment = nodeSelector.computeAssignments(splits, ImmutableList.copyOf(taskMap.values())).getAssignments(); // Check that all splits are being assigned to node1 - assertEquals(initialAssignment.size(), 20); - assertEquals(initialAssignment.keySet().size(), 1); - assertTrue(initialAssignment.keySet().contains(node)); + assertThat(initialAssignment.size()).isEqualTo(20); + assertThat(initialAssignment.keySet().size()).isEqualTo(1); + assertThat(initialAssignment.keySet().contains(node)).isTrue(); // Check for assignment of splits beyond maxSplitsPerNode (2 splits should remain unassigned) // 1 split with node1 as local node @@ -547,16 +545,16 @@ public void testAssignmentWhenMixedSplits() //splits now contains 22 splits : 11 with node1 as local node and 11 with node1 as a non-local node Multimap finalAssignment = nodeSelector.computeAssignments(splits, ImmutableList.copyOf(taskMap.values())).getAssignments(); // Check that only 20 splits are being assigned as there is a single task - assertEquals(finalAssignment.size(), 20); - assertEquals(finalAssignment.keySet().size(), 1); - assertTrue(finalAssignment.keySet().contains(node)); + assertThat(finalAssignment.size()).isEqualTo(20); + assertThat(finalAssignment.keySet().size()).isEqualTo(1); + assertThat(finalAssignment.keySet().contains(node)).isTrue(); // When optimized-local-scheduling is enabled, all 11 splits with node1 as local node should be assigned long countLocalSplits = finalAssignment.values().stream() .map(Split::getConnectorSplit) .filter(TestSplitLocal.class::isInstance) .count(); - assertEquals(countLocalSplits, 11); + assertThat(countLocalSplits).isEqualTo(11); } @Test @@ -575,10 +573,10 @@ public void testOptimizedLocalScheduling() // computeAssignments just returns a mapping of nodes with splits to be assigned, it does not assign splits Multimap assignments1 = nodeSelector.computeAssignments(splits, ImmutableList.copyOf(taskMap.values())).getAssignments(); // Check that all 20 splits are being assigned to node1 as optimized-local-scheduling is enabled - assertEquals(assignments1.size(), 20); - assertEquals(assignments1.keySet().size(), 2); - assertTrue(assignments1.keySet().contains(node1)); - assertTrue(assignments1.keySet().contains(node2)); + assertThat(assignments1.size()).isEqualTo(20); + assertThat(assignments1.keySet().size()).isEqualTo(2); + assertThat(assignments1.keySet().contains(node1)).isTrue(); + assertThat(assignments1.keySet().contains(node2)).isTrue(); // 19 splits with node2 as local node to be assigned in the first iteration of computeAssignments for (int i = 0; i < 19; i++) { @@ -586,22 +584,22 @@ public void testOptimizedLocalScheduling() } Multimap assignments2 = nodeSelector.computeAssignments(splits, ImmutableList.copyOf(taskMap.values())).getAssignments(); // Check that all 39 splits are being assigned (20 splits assigned to node1 and 19 splits assigned to node2) - assertEquals(assignments2.size(), 39); - assertEquals(assignments2.keySet().size(), 2); - assertTrue(assignments2.keySet().contains(node1)); - assertTrue(assignments2.keySet().contains(node2)); + assertThat(assignments2.size()).isEqualTo(39); + assertThat(assignments2.keySet().size()).isEqualTo(2); + assertThat(assignments2.keySet().contains(node1)).isTrue(); + assertThat(assignments2.keySet().contains(node2)).isTrue(); long node1Splits = assignments2.values().stream() .map(Split::getConnectorSplit) .filter(TestSplitLocal.class::isInstance) .count(); - assertEquals(node1Splits, 20); + assertThat(node1Splits).isEqualTo(20); long node2Splits = assignments2.values().stream() .map(Split::getConnectorSplit) .filter(TestSplitRemote.class::isInstance) .count(); - assertEquals(node2Splits, 19); + assertThat(node2Splits).isEqualTo(19); // 1 split with node1 as local node splits.add(new Split(TEST_CATALOG_HANDLE, new TestSplitLocal())); @@ -610,10 +608,10 @@ public void testOptimizedLocalScheduling() //splits now contains 41 splits : 21 with node1 as local node and 20 with node2 as local node Multimap assignments3 = nodeSelector.computeAssignments(splits, ImmutableList.copyOf(taskMap.values())).getAssignments(); // Check that only 40 splits are being assigned as there is a single task - assertEquals(assignments3.size(), 40); - assertEquals(assignments3.keySet().size(), 2); - assertTrue(assignments3.keySet().contains(node1)); - assertTrue(assignments3.keySet().contains(node2)); + assertThat(assignments3.size()).isEqualTo(40); + assertThat(assignments3.keySet().size()).isEqualTo(2); + assertThat(assignments3.keySet().contains(node1)).isTrue(); + assertThat(assignments3.keySet().contains(node2)).isTrue(); // The first 20 splits have node1 as local, the next 19 have node2 as local, the 40th split has node1 as local and the 41st has node2 as local // If optimized-local-scheduling is disabled, the 41st split will be unassigned (the last slot in node2 will be taken up by the 40th split with node1 as local) @@ -622,13 +620,13 @@ public void testOptimizedLocalScheduling() .map(Split::getConnectorSplit) .filter(TestSplitLocal.class::isInstance) .count(); - assertEquals(node1Splits, 20); + assertThat(node1Splits).isEqualTo(20); node2Splits = assignments3.values().stream() .map(Split::getConnectorSplit) .filter(TestSplitRemote.class::isInstance) .count(); - assertEquals(node2Splits, 20); + assertThat(node2Splits).isEqualTo(20); } @Test @@ -650,12 +648,12 @@ public void testEquateDistribution() } // check that splits are divided uniformly across all nodes Multimap assignment = nodeSelector.computeAssignments(splits, ImmutableList.copyOf(taskMap.values())).getAssignments(); - assertEquals(assignment.size(), 20); - assertEquals(assignment.keySet().size(), 4); - assertEquals(assignment.get(node1).size(), 8); - assertEquals(assignment.get(node2).size(), 4); - assertEquals(assignment.get(node3).size(), 4); - assertEquals(assignment.get(node4).size(), 4); + assertThat(assignment.size()).isEqualTo(20); + assertThat(assignment.keySet().size()).isEqualTo(4); + assertThat(assignment.get(node1).size()).isEqualTo(8); + assertThat(assignment.get(node2).size()).isEqualTo(4); + assertThat(assignment.get(node3).size()).isEqualTo(4); + assertThat(assignment.get(node4).size()).isEqualTo(4); } @Test @@ -741,8 +739,8 @@ public void testRedistributeSplit() assignment.put(node2, split); } - assertEquals(assignment.get(node1).size(), 12); - assertEquals(assignment.get(node2).size(), 10); + assertThat(assignment.get(node1).size()).isEqualTo(12); + assertThat(assignment.get(node2).size()).isEqualTo(10); ImmutableSetMultimap.Builder nodesByHost = ImmutableSetMultimap.builder(); try { @@ -756,14 +754,14 @@ public void testRedistributeSplit() // Redistribute 1 split from Node 1 to Node 2 UniformNodeSelector.redistributeSplit(assignment, node1, node2, nodesByHost.build()); - assertEquals(assignment.get(node1).size(), 11); - assertEquals(assignment.get(node2).size(), 11); + assertThat(assignment.get(node1).size()).isEqualTo(11); + assertThat(assignment.get(node2).size()).isEqualTo(11); Set redistributedSplit = Sets.difference(new HashSet<>(assignment.get(node2)), splitsAssignedToNode2); - assertEquals(redistributedSplit.size(), 1); + assertThat(redistributedSplit.size()).isEqualTo(1); // Assert that the redistributed split is not a local split in Node 1. This test ensures that redistributeSingleSplit() prioritizes the transfer of a non-local split - assertTrue(redistributedSplit.iterator().next().getConnectorSplit() instanceof TestSplitRemote); + assertThat(redistributedSplit.iterator().next().getConnectorSplit() instanceof TestSplitRemote).isTrue(); } @Test @@ -781,10 +779,10 @@ public void testEmptyAssignmentWithFullNodes() } // computeAssignments just returns a mapping of nodes with splits to be assigned, it does not assign splits Multimap assignments1 = nodeSelector.computeAssignments(splits, ImmutableList.copyOf(taskMap.values())).getAssignments(); - assertEquals(assignments1.size(), 40); - assertEquals(assignments1.keySet().size(), 2); - assertEquals(assignments1.get(node1).size(), 20); - assertEquals(assignments1.get(node2).size(), 20); + assertThat(assignments1.size()).isEqualTo(40); + assertThat(assignments1.keySet().size()).isEqualTo(2); + assertThat(assignments1.get(node1).size()).isEqualTo(20); + assertThat(assignments1.get(node2).size()).isEqualTo(20); MockRemoteTaskFactory remoteTaskFactory = new MockRemoteTaskFactory(remoteTaskExecutor, remoteTaskScheduledExecutor); int task = 0; for (InternalNode node : assignments1.keySet()) { @@ -796,7 +794,7 @@ public void testEmptyAssignmentWithFullNodes() taskMap.put(node, remoteTask); } Set unassignedSplits = Sets.difference(splits, new HashSet<>(assignments1.values())); - assertEquals(unassignedSplits.size(), 30); + assertThat(unassignedSplits.size()).isEqualTo(30); Multimap assignments2 = nodeSelector.computeAssignments(unassignedSplits, ImmutableList.copyOf(taskMap.values())).getAssignments(); for (InternalNode node : assignments2.keySet()) { @@ -806,10 +804,10 @@ public void testEmptyAssignmentWithFullNodes() .build()); } unassignedSplits = Sets.difference(unassignedSplits, new HashSet<>(assignments2.values())); - assertEquals(unassignedSplits.size(), 20); // 30 (unassignedSplits) - (10 (maxPendingSplitsPerTask) - 5(queued)) * 2 (nodes)) + assertThat(unassignedSplits.size()).isEqualTo(20); // 30 (unassignedSplits) - (10 (maxPendingSplitsPerTask) - 5(queued)) * 2 (nodes)) Multimap assignments3 = nodeSelector.computeAssignments(unassignedSplits, ImmutableList.copyOf(taskMap.values())).getAssignments(); - assertTrue(assignments3.isEmpty()); + assertThat(assignments3.isEmpty()).isTrue(); } @Test @@ -847,18 +845,18 @@ public void testMaxUnacknowledgedSplitsPerTask() } SplitPlacementResult splitPlacements = nodeSelector.computeAssignments(splits, ImmutableList.copyOf(tasks)); // No splits should have been placed, max unacknowledged was already reached - assertEquals(splitPlacements.getAssignments().size(), 0); + assertThat(splitPlacements.getAssignments().size()).isEqualTo(0); // Unblock one task MockRemoteTaskFactory.MockRemoteTask taskOne = tasks.get(0); taskOne.finishSplits(1); taskOne.setUnacknowledgedSplits(taskOne.getUnacknowledgedPartitionedSplitCount() - 1); - assertTrue(splitPlacements.getBlocked().isDone()); + assertThat(splitPlacements.getBlocked().isDone()).isTrue(); // Attempt to schedule again, only the node with the unblocked task should be chosen splitPlacements = nodeSelector.computeAssignments(splits, ImmutableList.copyOf(tasks)); - assertEquals(splitPlacements.getAssignments().size(), 1); - assertTrue(splitPlacements.getAssignments().keySet().contains(nodes.get(0))); + assertThat(splitPlacements.getAssignments().size()).isEqualTo(1); + assertThat(splitPlacements.getAssignments().keySet().contains(nodes.get(0))).isTrue(); // Make the first node appear to have no splits, unacknowledged splits alone should force the splits to be spread across nodes taskOne.clearSplits(); @@ -867,8 +865,8 @@ public void testMaxUnacknowledgedSplitsPerTask() splitPlacements = nodeSelector.computeAssignments(splits, ImmutableList.copyOf(tasks)); // One split placed on each node - assertEquals(splitPlacements.getAssignments().size(), nodes.size()); - assertTrue(splitPlacements.getAssignments().keySet().containsAll(nodes)); + assertThat(splitPlacements.getAssignments().size()).isEqualTo(nodes.size()); + assertThat(splitPlacements.getAssignments().keySet().containsAll(nodes)).isTrue(); } private static Session sessionWithMaxUnacknowledgedSplitsPerTask(int maxUnacknowledgedSplitsPerTask) diff --git a/core/trino-main/src/test/java/io/trino/execution/TestPageSplitterUtil.java b/core/trino-main/src/test/java/io/trino/execution/TestPageSplitterUtil.java index b20beb08c69e..d9b69197a50c 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestPageSplitterUtil.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestPageSplitterUtil.java @@ -36,7 +36,6 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestPageSplitterUtil { @@ -71,7 +70,7 @@ private static void assertPositionCount(List pages, int positionCount) for (Page page : pages) { totalPositionCount += page.getPositionCount(); } - assertEquals(totalPositionCount, positionCount); + assertThat(totalPositionCount).isEqualTo(positionCount); } @Test @@ -90,7 +89,7 @@ public void testSplitPageNonDecreasingPageSize() // the page should only be split in half as the recursion should terminate // after seeing that the size of the Page doesn't decrease - assertEquals(pages.size(), 2); + assertThat(pages.size()).isEqualTo(2); Page first = pages.get(0); Page second = pages.get(1); diff --git a/core/trino-main/src/test/java/io/trino/execution/TestPlannerWarnings.java b/core/trino-main/src/test/java/io/trino/execution/TestPlannerWarnings.java index 0b7af9a71026..7dfd7c272c27 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestPlannerWarnings.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestPlannerWarnings.java @@ -54,9 +54,9 @@ import static io.trino.testing.TestingSession.testSessionBuilder; import static java.util.Objects.requireNonNull; import static java.util.stream.IntStream.range; +import static org.assertj.core.api.Fail.fail; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.fail; @TestInstance(PER_CLASS) @Execution(CONCURRENT) diff --git a/core/trino-main/src/test/java/io/trino/execution/TestPrepareTask.java b/core/trino-main/src/test/java/io/trino/execution/TestPrepareTask.java index 90c910eb9009..c9ab042caf10 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestPrepareTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestPrepareTask.java @@ -59,9 +59,9 @@ import static io.trino.transaction.InMemoryTransactionManager.createTestTransactionManager; import static java.util.Collections.emptyList; import static java.util.concurrent.Executors.newCachedThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -83,7 +83,7 @@ public void testPrepare() Query query = simpleQuery(selectList(new AllColumns()), table(QualifiedName.of("foo"))); String sqlString = "PREPARE my_query FROM SELECT * FROM foo"; Map statements = executePrepare("my_query", query, sqlString, TEST_SESSION); - assertEquals(statements, ImmutableMap.of("my_query", "SELECT *\nFROM\n foo\n")); + assertThat(statements).isEqualTo(ImmutableMap.of("my_query", "SELECT *\nFROM\n foo\n")); } @Test @@ -96,7 +96,7 @@ public void testPrepareNameExists() Query query = simpleQuery(selectList(new AllColumns()), table(QualifiedName.of("foo"))); String sqlString = "PREPARE my_query FROM SELECT * FROM foo"; Map statements = executePrepare("my_query", query, sqlString, session); - assertEquals(statements, ImmutableMap.of("my_query", "SELECT *\nFROM\n foo\n")); + assertThat(statements).isEqualTo(ImmutableMap.of("my_query", "SELECT *\nFROM\n foo\n")); } @Test diff --git a/core/trino-main/src/test/java/io/trino/execution/TestQueryIdGenerator.java b/core/trino-main/src/test/java/io/trino/execution/TestQueryIdGenerator.java index 473a41bef3b5..d1d985a7fc51 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestQueryIdGenerator.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestQueryIdGenerator.java @@ -22,7 +22,7 @@ import static java.lang.String.format; import static java.time.ZoneOffset.UTC; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestQueryIdGenerator { @@ -36,42 +36,42 @@ public void testCreateNextQueryId() // generate ids to 99,999 for (int i = 0; i < 100_000; i++) { - assertEquals(idGenerator.createNextQueryId(), new QueryId(format("20010714_010203_%05d_%s", i, idGenerator.getCoordinatorId()))); + assertThat(idGenerator.createNextQueryId()).isEqualTo(new QueryId(format("20010714_010203_%05d_%s", i, idGenerator.getCoordinatorId()))); } // next id will cause counter to roll, but we need to add a second to the time or code will block for ever millis += 1000; idGenerator.setNow(millis); for (int i = 0; i < 100_000; i++) { - assertEquals(idGenerator.createNextQueryId(), new QueryId(format("20010714_010204_%05d_%s", i, idGenerator.getCoordinatorId()))); + assertThat(idGenerator.createNextQueryId()).isEqualTo(new QueryId(format("20010714_010204_%05d_%s", i, idGenerator.getCoordinatorId()))); } // move forward one more second and generate 100 ids millis += 1000; idGenerator.setNow(millis); for (int i = 0; i < 100; i++) { - assertEquals(idGenerator.createNextQueryId(), new QueryId(format("20010714_010205_%05d_%s", i, idGenerator.getCoordinatorId()))); + assertThat(idGenerator.createNextQueryId()).isEqualTo(new QueryId(format("20010714_010205_%05d_%s", i, idGenerator.getCoordinatorId()))); } // move forward one more second and verify counter not reset millis += 1000; idGenerator.setNow(millis); for (int i = 100; i < 200; i++) { - assertEquals(idGenerator.createNextQueryId(), new QueryId(format("20010714_010206_%05d_%s", i, idGenerator.getCoordinatorId()))); + assertThat(idGenerator.createNextQueryId()).isEqualTo(new QueryId(format("20010714_010206_%05d_%s", i, idGenerator.getCoordinatorId()))); } // now we move to the start of the next day, and the counter should reset millis = epochMillis(2001, 7, 15, 0, 0, 0, 0); idGenerator.setNow(millis); for (int i = 0; i < 90_123; i++) { - assertEquals(idGenerator.createNextQueryId(), new QueryId(format("20010715_000000_%05d_%s", i, idGenerator.getCoordinatorId()))); + assertThat(idGenerator.createNextQueryId()).isEqualTo(new QueryId(format("20010715_000000_%05d_%s", i, idGenerator.getCoordinatorId()))); } // moving forward one second with counter close to the limit causes it to roll millis += 1000; idGenerator.setNow(millis); for (int i = 0; i < 100_000; i++) { - assertEquals(idGenerator.createNextQueryId(), new QueryId(format("20010715_000001_%05d_%s", i, idGenerator.getCoordinatorId()))); + assertThat(idGenerator.createNextQueryId()).isEqualTo(new QueryId(format("20010715_000001_%05d_%s", i, idGenerator.getCoordinatorId()))); } } diff --git a/core/trino-main/src/test/java/io/trino/execution/TestQueryInfo.java b/core/trino-main/src/test/java/io/trino/execution/TestQueryInfo.java index d55420bafa5f..abff70103840 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestQueryInfo.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestQueryInfo.java @@ -46,7 +46,7 @@ import static io.trino.SessionTestUtils.TEST_SESSION; import static io.trino.execution.QueryState.FINISHED; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestQueryInfo { @@ -67,50 +67,50 @@ TypeSignature.class, new TypeSignatureKeyDeserializer()))) QueryInfo expected = createQueryInfo(); QueryInfo actual = codec.fromJson(codec.toJsonBytes(expected)); - assertEquals(actual.getQueryId(), expected.getQueryId()); + assertThat(actual.getQueryId()).isEqualTo(expected.getQueryId()); // Note: SessionRepresentation.equals? - assertEquals(actual.getState(), expected.getState()); - assertEquals(actual.isScheduled(), expected.isScheduled()); - assertEquals(actual.getProgressPercentage(), expected.getProgressPercentage()); - assertEquals(actual.getRunningPercentage(), expected.getRunningPercentage()); - - assertEquals(actual.getSelf(), expected.getSelf()); - assertEquals(actual.getFieldNames(), expected.getFieldNames()); - assertEquals(actual.getQuery(), expected.getQuery()); - assertEquals(actual.getPreparedQuery(), expected.getPreparedQuery()); + assertThat(actual.getState()).isEqualTo(expected.getState()); + assertThat(actual.isScheduled()).isEqualTo(expected.isScheduled()); + assertThat(actual.getProgressPercentage()).isEqualTo(expected.getProgressPercentage()); + assertThat(actual.getRunningPercentage()).isEqualTo(expected.getRunningPercentage()); + + assertThat(actual.getSelf()).isEqualTo(expected.getSelf()); + assertThat(actual.getFieldNames()).isEqualTo(expected.getFieldNames()); + assertThat(actual.getQuery()).isEqualTo(expected.getQuery()); + assertThat(actual.getPreparedQuery()).isEqualTo(expected.getPreparedQuery()); // Assert all of queryStats TestQueryStats.assertExpectedQueryStats(actual.getQueryStats()); - assertEquals(actual.getSetCatalog(), expected.getSetCatalog()); - assertEquals(actual.getSetSchema(), expected.getSetSchema()); - assertEquals(actual.getSetPath(), expected.getSetPath()); - assertEquals(actual.getSetSessionProperties(), expected.getSetSessionProperties()); - assertEquals(actual.getResetSessionProperties(), expected.getResetSessionProperties()); - assertEquals(actual.getSetRoles(), expected.getSetRoles()); - assertEquals(actual.getAddedPreparedStatements(), expected.getAddedPreparedStatements()); - assertEquals(actual.getDeallocatedPreparedStatements(), expected.getDeallocatedPreparedStatements()); + assertThat(actual.getSetCatalog()).isEqualTo(expected.getSetCatalog()); + assertThat(actual.getSetSchema()).isEqualTo(expected.getSetSchema()); + assertThat(actual.getSetPath()).isEqualTo(expected.getSetPath()); + assertThat(actual.getSetSessionProperties()).isEqualTo(expected.getSetSessionProperties()); + assertThat(actual.getResetSessionProperties()).isEqualTo(expected.getResetSessionProperties()); + assertThat(actual.getSetRoles()).isEqualTo(expected.getSetRoles()); + assertThat(actual.getAddedPreparedStatements()).isEqualTo(expected.getAddedPreparedStatements()); + assertThat(actual.getDeallocatedPreparedStatements()).isEqualTo(expected.getDeallocatedPreparedStatements()); - assertEquals(actual.getStartedTransactionId(), expected.getStartedTransactionId()); - assertEquals(actual.isClearTransactionId(), expected.isClearTransactionId()); + assertThat(actual.getStartedTransactionId()).isEqualTo(expected.getStartedTransactionId()); + assertThat(actual.isClearTransactionId()).isEqualTo(expected.isClearTransactionId()); - assertEquals(actual.getUpdateType(), expected.getUpdateType()); - assertEquals(actual.getOutputStage(), expected.getOutputStage()); + assertThat(actual.getUpdateType()).isEqualTo(expected.getUpdateType()); + assertThat(actual.getOutputStage()).isEqualTo(expected.getOutputStage()); - assertEquals(actual.getFailureInfo(), expected.getFailureInfo()); - assertEquals(actual.getErrorCode(), expected.getErrorCode()); - assertEquals(actual.getWarnings(), expected.getWarnings()); + assertThat(actual.getFailureInfo()).isEqualTo(expected.getFailureInfo()); + assertThat(actual.getErrorCode()).isEqualTo(expected.getErrorCode()); + assertThat(actual.getWarnings()).isEqualTo(expected.getWarnings()); - assertEquals(actual.getInputs(), expected.getInputs()); - assertEquals(actual.getOutput(), expected.getOutput()); + assertThat(actual.getInputs()).isEqualTo(expected.getInputs()); + assertThat(actual.getOutput()).isEqualTo(expected.getOutput()); - assertEquals(actual.getReferencedTables(), expected.getReferencedTables()); - assertEquals(actual.getRoutines(), expected.getRoutines()); + assertThat(actual.getReferencedTables()).isEqualTo(expected.getReferencedTables()); + assertThat(actual.getRoutines()).isEqualTo(expected.getRoutines()); - assertEquals(actual.isFinalQueryInfo(), expected.isFinalQueryInfo()); + assertThat(actual.isFinalQueryInfo()).isEqualTo(expected.isFinalQueryInfo()); - assertEquals(actual.getResourceGroupId(), expected.getResourceGroupId()); - assertEquals(actual.getQueryType(), expected.getQueryType()); - assertEquals(actual.getRetryPolicy(), expected.getRetryPolicy()); + assertThat(actual.getResourceGroupId()).isEqualTo(expected.getResourceGroupId()); + assertThat(actual.getQueryType()).isEqualTo(expected.getQueryType()); + assertThat(actual.getRetryPolicy()).isEqualTo(expected.getRetryPolicy()); } private static QueryInfo createQueryInfo() diff --git a/core/trino-main/src/test/java/io/trino/execution/TestQueryPreparer.java b/core/trino-main/src/test/java/io/trino/execution/TestQueryPreparer.java index e7fac59e069c..a45c0673617f 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestQueryPreparer.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestQueryPreparer.java @@ -29,8 +29,8 @@ import static io.trino.sql.QueryUtil.table; import static io.trino.testing.TestingSession.testSessionBuilder; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestQueryPreparer { @@ -41,8 +41,7 @@ public class TestQueryPreparer public void testSelectStatement() { PreparedQuery preparedQuery = QUERY_PREPARER.prepareQuery(TEST_SESSION, "SELECT * FROM foo"); - assertEquals(preparedQuery.getStatement(), - simpleQuery(selectList(new AllColumns()), table(QualifiedName.of("foo")))); + assertThat(preparedQuery.getStatement()).isEqualTo(simpleQuery(selectList(new AllColumns()), table(QualifiedName.of("foo")))); } @Test @@ -52,16 +51,14 @@ public void testExecuteStatement() .addPreparedStatement("my_query", "SELECT * FROM foo") .build(); PreparedQuery preparedQuery = QUERY_PREPARER.prepareQuery(session, "EXECUTE my_query"); - assertEquals(preparedQuery.getStatement(), - simpleQuery(selectList(new AllColumns()), table(QualifiedName.of("foo")))); + assertThat(preparedQuery.getStatement()).isEqualTo(simpleQuery(selectList(new AllColumns()), table(QualifiedName.of("foo")))); } @Test public void testExecuteImmediateStatement() { PreparedQuery preparedQuery = QUERY_PREPARER.prepareQuery(TEST_SESSION, "EXECUTE IMMEDIATE 'SELECT * FROM foo'"); - assertEquals(preparedQuery.getStatement(), - simpleQuery(selectList(new AllColumns()), table(QualifiedName.of("foo")))); + assertThat(preparedQuery.getStatement()).isEqualTo(simpleQuery(selectList(new AllColumns()), table(QualifiedName.of("foo")))); } @Test diff --git a/core/trino-main/src/test/java/io/trino/execution/TestQueryStateMachine.java b/core/trino-main/src/test/java/io/trino/execution/TestQueryStateMachine.java index fda2a8797c71..f58892a405a0 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestQueryStateMachine.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestQueryStateMachine.java @@ -74,13 +74,9 @@ import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -123,19 +119,19 @@ public void testBasicStateChanges() QueryStateMachine stateMachine = createQueryStateMachine(); assertState(stateMachine, QUEUED); - assertTrue(stateMachine.transitionToDispatching()); + assertThat(stateMachine.transitionToDispatching()).isTrue(); assertState(stateMachine, DISPATCHING); - assertTrue(stateMachine.transitionToPlanning()); + assertThat(stateMachine.transitionToPlanning()).isTrue(); assertState(stateMachine, PLANNING); - assertTrue(stateMachine.transitionToStarting()); + assertThat(stateMachine.transitionToStarting()).isTrue(); assertState(stateMachine, STARTING); - assertTrue(stateMachine.transitionToRunning()); + assertThat(stateMachine.transitionToRunning()).isTrue(); assertState(stateMachine, RUNNING); - assertTrue(stateMachine.transitionToFinishing()); + assertThat(stateMachine.transitionToFinishing()).isTrue(); assertState(stateMachine, FINISHING); stateMachine.resultsConsumed(); @@ -149,22 +145,22 @@ public void testStateChangesWithResourceWaiting() QueryStateMachine stateMachine = createQueryStateMachine(); assertState(stateMachine, QUEUED); - assertTrue(stateMachine.transitionToWaitingForResources()); + assertThat(stateMachine.transitionToWaitingForResources()).isTrue(); assertState(stateMachine, WAITING_FOR_RESOURCES); - assertTrue(stateMachine.transitionToDispatching()); + assertThat(stateMachine.transitionToDispatching()).isTrue(); assertState(stateMachine, DISPATCHING); - assertTrue(stateMachine.transitionToPlanning()); + assertThat(stateMachine.transitionToPlanning()).isTrue(); assertState(stateMachine, PLANNING); - assertTrue(stateMachine.transitionToStarting()); + assertThat(stateMachine.transitionToStarting()).isTrue(); assertState(stateMachine, STARTING); - assertTrue(stateMachine.transitionToRunning()); + assertThat(stateMachine.transitionToRunning()).isTrue(); assertState(stateMachine, RUNNING); - assertTrue(stateMachine.transitionToFinishing()); + assertThat(stateMachine.transitionToFinishing()).isTrue(); stateMachine.resultsConsumed(); tryGetFutureValue(stateMachine.getStateChange(FINISHING), 2, SECONDS); assertState(stateMachine, FINISHED); @@ -197,48 +193,48 @@ private void assertAllTimeSpentInQueueing(QueryState expectedState, Consumer sessionProperties = stateMachine.getResetSessionProperties(); - assertEquals(sessionProperties, ImmutableSet.of(CATALOG_NAME + ".baz")); + assertThat(sessionProperties).isEqualTo(ImmutableSet.of(CATALOG_NAME + ".baz")); } } diff --git a/core/trino-main/src/test/java/io/trino/execution/TestResettableRandomizedIterator.java b/core/trino-main/src/test/java/io/trino/execution/TestResettableRandomizedIterator.java index c3525dc4c8d1..010c0d15de99 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestResettableRandomizedIterator.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestResettableRandomizedIterator.java @@ -22,8 +22,7 @@ import java.util.List; import java.util.Set; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestResettableRandomizedIterator { @@ -42,14 +41,14 @@ public void testResetting() while (randomizedIterator.hasNext()) { actual.add(randomizedIterator.next()); } - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); actual.clear(); randomizedIterator.reset(); while (randomizedIterator.hasNext()) { actual.add(randomizedIterator.next()); } - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } @Test @@ -73,6 +72,7 @@ public void testRandom() for (int i = 0; i < 99; i++) { list2.add(randomizedIterator.next()); } - assertNotEquals(list1, list2); + assertThat(list1) + .isNotEqualTo(list2); } } diff --git a/core/trino-main/src/test/java/io/trino/execution/TestRollbackTask.java b/core/trino-main/src/test/java/io/trino/execution/TestRollbackTask.java index 5e56211d22ab..024681b13222 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestRollbackTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestRollbackTask.java @@ -45,11 +45,9 @@ import static io.trino.transaction.InMemoryTransactionManager.createTestTransactionManager; import static java.util.Collections.emptyList; import static java.util.concurrent.Executors.newCachedThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -74,14 +72,14 @@ public void testRollback() .setTransactionId(transactionManager.beginTransaction(false)) .build(); QueryStateMachine stateMachine = createQueryStateMachine("ROLLBACK", session, transactionManager); - assertTrue(stateMachine.getSession().getTransactionId().isPresent()); - assertEquals(transactionManager.getAllTransactionInfos().size(), 1); + assertThat(stateMachine.getSession().getTransactionId().isPresent()).isTrue(); + assertThat(transactionManager.getAllTransactionInfos().size()).isEqualTo(1); getFutureValue(new RollbackTask(transactionManager).execute(new Rollback(), stateMachine, emptyList(), WarningCollector.NOOP)); - assertTrue(stateMachine.getQueryInfo(Optional.empty()).isClearTransactionId()); - assertFalse(stateMachine.getQueryInfo(Optional.empty()).getStartedTransactionId().isPresent()); + assertThat(stateMachine.getQueryInfo(Optional.empty()).isClearTransactionId()).isTrue(); + assertThat(stateMachine.getQueryInfo(Optional.empty()).getStartedTransactionId().isPresent()).isFalse(); - assertTrue(transactionManager.getAllTransactionInfos().isEmpty()); + assertThat(transactionManager.getAllTransactionInfos().isEmpty()).isTrue(); } @Test @@ -97,10 +95,10 @@ public void testNoTransactionRollback() () -> getFutureValue((Future) new RollbackTask(transactionManager).execute(new Rollback(), stateMachine, emptyList(), WarningCollector.NOOP))) .hasErrorCode(NOT_IN_TRANSACTION); - assertFalse(stateMachine.getQueryInfo(Optional.empty()).isClearTransactionId()); - assertFalse(stateMachine.getQueryInfo(Optional.empty()).getStartedTransactionId().isPresent()); + assertThat(stateMachine.getQueryInfo(Optional.empty()).isClearTransactionId()).isFalse(); + assertThat(stateMachine.getQueryInfo(Optional.empty()).getStartedTransactionId().isPresent()).isFalse(); - assertTrue(transactionManager.getAllTransactionInfos().isEmpty()); + assertThat(transactionManager.getAllTransactionInfos().isEmpty()).isTrue(); } @Test @@ -114,10 +112,10 @@ public void testUnknownTransactionRollback() QueryStateMachine stateMachine = createQueryStateMachine("ROLLBACK", session, transactionManager); getFutureValue(new RollbackTask(transactionManager).execute(new Rollback(), stateMachine, emptyList(), WarningCollector.NOOP)); - assertTrue(stateMachine.getQueryInfo(Optional.empty()).isClearTransactionId()); // Still issue clear signal - assertFalse(stateMachine.getQueryInfo(Optional.empty()).getStartedTransactionId().isPresent()); + assertThat(stateMachine.getQueryInfo(Optional.empty()).isClearTransactionId()).isTrue(); // Still issue clear signal + assertThat(stateMachine.getQueryInfo(Optional.empty()).getStartedTransactionId().isPresent()).isFalse(); - assertTrue(transactionManager.getAllTransactionInfos().isEmpty()); + assertThat(transactionManager.getAllTransactionInfos().isEmpty()).isTrue(); } private QueryStateMachine createQueryStateMachine(String query, Session session, TransactionManager transactionManager) diff --git a/core/trino-main/src/test/java/io/trino/execution/TestSetPathTask.java b/core/trino-main/src/test/java/io/trino/execution/TestSetPathTask.java index de416131518a..55e8c8dcbafd 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestSetPathTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestSetPathTask.java @@ -44,10 +44,10 @@ import static io.trino.transaction.InMemoryTransactionManager.createTestTransactionManager; import static java.util.Collections.emptyList; import static java.util.concurrent.Executors.newCachedThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -89,7 +89,7 @@ public void testSetPath() QueryStateMachine stateMachine = createQueryStateMachine("SET PATH foo"); executeSetPathTask(pathSpecification, stateMachine); - assertEquals(stateMachine.getSetPath(), "foo"); + assertThat(stateMachine.getSetPath()).isEqualTo("foo"); } @Test diff --git a/core/trino-main/src/test/java/io/trino/execution/TestSetRoleTask.java b/core/trino-main/src/test/java/io/trino/execution/TestSetRoleTask.java index 748679558e77..e273169b1287 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestSetRoleTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestSetRoleTask.java @@ -51,9 +51,9 @@ import static io.trino.testing.TestingSession.testSessionBuilder; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static java.util.concurrent.Executors.newCachedThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -146,7 +146,7 @@ private void assertSetRole(String statement, Map expected) { QueryStateMachine stateMachine = executeSetRole(statement); QueryInfo queryInfo = stateMachine.getQueryInfo(Optional.empty()); - assertEquals(queryInfo.getSetRoles(), expected); + assertThat(queryInfo.getSetRoles()).isEqualTo(expected); } private QueryStateMachine executeSetRole(String statement) diff --git a/core/trino-main/src/test/java/io/trino/execution/TestSetSessionAuthorizationTask.java b/core/trino-main/src/test/java/io/trino/execution/TestSetSessionAuthorizationTask.java index 5d3edc4e1fc2..ced360566940 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestSetSessionAuthorizationTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestSetSessionAuthorizationTask.java @@ -41,10 +41,10 @@ import static io.trino.transaction.InMemoryTransactionManager.createTestTransactionManager; import static java.util.Collections.emptyList; import static java.util.concurrent.Executors.newCachedThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -103,7 +103,7 @@ private void assertSetSessionAuthorization(String query, Optional expect QueryStateMachine stateMachine = createStateMachine(Optional.empty(), query); new SetSessionAuthorizationTask(accessControl, transactionManager).execute(statement, stateMachine, emptyList(), WarningCollector.NOOP); QueryInfo queryInfo = stateMachine.getQueryInfo(Optional.empty()); - assertEquals(queryInfo.getSetAuthorizationUser(), expected); + assertThat(queryInfo.getSetAuthorizationUser()).isEqualTo(expected); } private QueryStateMachine createStateMachine(Optional transactionId, String query) diff --git a/core/trino-main/src/test/java/io/trino/execution/TestSetSessionTask.java b/core/trino-main/src/test/java/io/trino/execution/TestSetSessionTask.java index 0a5475d52899..fc82b93bd11f 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestSetSessionTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestSetSessionTask.java @@ -60,10 +60,10 @@ import static java.lang.String.format; import static java.util.Collections.emptyList; import static java.util.concurrent.Executors.newCachedThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -223,6 +223,6 @@ private void testSetSessionWithParameters(String property, Expression expression getFutureValue(new SetSessionTask(plannerContext, accessControl, sessionPropertyManager).execute(new SetSession(qualifiedPropName, expression), stateMachine, parameters, WarningCollector.NOOP)); Map sessionProperties = stateMachine.getSetSessionProperties(); - assertEquals(sessionProperties, ImmutableMap.of(qualifiedPropName.toString(), expectedValue)); + assertThat(sessionProperties).isEqualTo(ImmutableMap.of(qualifiedPropName.toString(), expectedValue)); } } diff --git a/core/trino-main/src/test/java/io/trino/execution/TestSetTimeZoneTask.java b/core/trino-main/src/test/java/io/trino/execution/TestSetTimeZoneTask.java index a2209ef5486c..17cd7ab5a62e 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestSetTimeZoneTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestSetTimeZoneTask.java @@ -56,7 +56,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -104,7 +103,7 @@ public void testSetTimeZoneStringLiteral() Map setSessionProperties = stateMachine.getSetSessionProperties(); assertThat(setSessionProperties).hasSize(1); - assertEquals(setSessionProperties.get(TIME_ZONE_ID), "America/Los_Angeles"); + assertThat(setSessionProperties.get(TIME_ZONE_ID)).isEqualTo("America/Los_Angeles"); } @Test @@ -130,7 +129,7 @@ public void testSetTimeZoneVarcharFunctionCall() Map setSessionProperties = stateMachine.getSetSessionProperties(); assertThat(setSessionProperties).hasSize(1); - assertEquals(setSessionProperties.get(TIME_ZONE_ID), "America/Los_Angeles"); + assertThat(setSessionProperties.get(TIME_ZONE_ID)).isEqualTo("America/Los_Angeles"); } @Test @@ -171,7 +170,7 @@ public void testSetTimeZoneIntervalLiteral() Map setSessionProperties = stateMachine.getSetSessionProperties(); assertThat(setSessionProperties).hasSize(1); - assertEquals(setSessionProperties.get(TIME_ZONE_ID), "+10:00"); + assertThat(setSessionProperties.get(TIME_ZONE_ID)).isEqualTo("+10:00"); } @Test @@ -191,7 +190,7 @@ public void testSetTimeZoneIntervalDayTimeTypeFunctionCall() Map setSessionProperties = stateMachine.getSetSessionProperties(); assertThat(setSessionProperties).hasSize(1); - assertEquals(setSessionProperties.get(TIME_ZONE_ID), "+08:00"); + assertThat(setSessionProperties.get(TIME_ZONE_ID)).isEqualTo("+08:00"); } @Test @@ -247,7 +246,7 @@ public void testSetTimeIntervalLiteralZoneHourToMinute() Map setSessionProperties = stateMachine.getSetSessionProperties(); assertThat(setSessionProperties).hasSize(1); - assertEquals(setSessionProperties.get(TIME_ZONE_ID), "-08:00"); + assertThat(setSessionProperties.get(TIME_ZONE_ID)).isEqualTo("-08:00"); } private QueryStateMachine createQueryStateMachine(String query) diff --git a/core/trino-main/src/test/java/io/trino/execution/TestSplitConcurrencyController.java b/core/trino-main/src/test/java/io/trino/execution/TestSplitConcurrencyController.java index 792e7b28ce87..42d045749553 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestSplitConcurrencyController.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestSplitConcurrencyController.java @@ -18,7 +18,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestSplitConcurrencyController { @@ -28,7 +28,7 @@ public void testRampup() SplitConcurrencyController controller = new SplitConcurrencyController(1, new Duration(1, SECONDS)); for (int i = 0; i < 10; i++) { controller.update(SECONDS.toNanos(2), 0, i + 1); - assertEquals(controller.getTargetConcurrency(), i + 2); + assertThat(controller.getTargetConcurrency()).isEqualTo(i + 2); } } @@ -39,7 +39,7 @@ public void testRampdown() for (int i = 0; i < 9; i++) { controller.update(SECONDS.toNanos(2), 1, 10 - i); controller.splitFinished(SECONDS.toNanos(30), 1, 10 - i); - assertEquals(controller.getTargetConcurrency(), 10 - i - 1); + assertThat(controller.getTargetConcurrency()).isEqualTo(10 - i - 1); } } @@ -50,13 +50,13 @@ public void testRapidAdjustForQuickSplits() for (int i = 0; i < 9; i++) { controller.update(MILLISECONDS.toNanos(200), 1, 10 - i); controller.splitFinished(MILLISECONDS.toNanos(100), 1, 10 - i); - assertEquals(controller.getTargetConcurrency(), 10 - i - 1); + assertThat(controller.getTargetConcurrency()).isEqualTo(10 - i - 1); } controller.update(SECONDS.toNanos(30), 0, 1); for (int i = 0; i < 10; i++) { controller.update(SECONDS.toNanos(200), 0, i + 1); controller.splitFinished(MILLISECONDS.toNanos(100), 0, i + 1); - assertEquals(controller.getTargetConcurrency(), i + 2); + assertThat(controller.getTargetConcurrency()).isEqualTo(i + 2); } } } diff --git a/core/trino-main/src/test/java/io/trino/execution/TestSqlStage.java b/core/trino-main/src/test/java/io/trino/execution/TestSqlStage.java index 68b4f0d3cb45..a46bdab35c71 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestSqlStage.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestSqlStage.java @@ -68,13 +68,10 @@ import static java.util.concurrent.Executors.newFixedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Fail.fail; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertSame; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -184,7 +181,7 @@ private void testFinalStageInfoInternal() // wait for some tasks to be created, and then abort the stage countDownLatch.await(); stage.finish(); - assertTrue(createdTasks.size() >= 1000); + assertThat(createdTasks.size() >= 1000).isTrue(); StageInfo stageInfo = stage.getStageInfo(); // stage should not report final info because all tasks have a running driver, but @@ -196,14 +193,18 @@ private void testFinalStageInfoInternal() TaskState taskState = info.getTaskStatus().getState(); int runningSplits = info.getTaskStatus().getRunningPartitionedDrivers(); if (runningSplits == 0) { - assertTrue(taskState == TaskState.CANCELING || taskState == TaskState.CANCELED, "unexpected task state: " + taskState); + assertThat(taskState == TaskState.CANCELING || taskState == TaskState.CANCELED) + .describedAs("unexpected task state: " + taskState) + .isTrue(); } else { - assertEquals(taskState, TaskState.CANCELING); - assertTrue(runningSplits > 0, "must be running splits to not be already canceled"); + assertThat(taskState).isEqualTo(TaskState.CANCELING); + assertThat(runningSplits > 0) + .describedAs("must be running splits to not be already canceled") + .isTrue(); } } - assertFalse(finalStageInfo.isDone()); + assertThat(finalStageInfo.isDone()).isFalse(); // cancel the background thread adding tasks addTasksTask.cancel(true); @@ -214,14 +215,14 @@ private void testFinalStageInfoInternal() // finishing all running splits on the task should trigger termination complete createdTasks.forEach(task -> { task.clearSplits(); - assertEquals(task.getTaskStatus().getState(), TaskState.CANCELED); + assertThat(task.getTaskStatus().getState()).isEqualTo(TaskState.CANCELED); }); // once the final stage info is available, verify that it is complete stageInfo = finalStageInfo.get(1, MINUTES); - assertFalse(stageInfo.getTasks().isEmpty()); - assertTrue(stageInfo.isFinalStageInfo()); - assertSame(stage.getStageInfo(), stageInfo); + assertThat(stageInfo.getTasks().isEmpty()).isFalse(); + assertThat(stageInfo.isFinalStageInfo()).isTrue(); + assertThat(stage.getStageInfo()).isSameAs(stageInfo); } private static PlanFragment createExchangePlanFragment() diff --git a/core/trino-main/src/test/java/io/trino/execution/TestSqlTask.java b/core/trino-main/src/test/java/io/trino/execution/TestSqlTask.java index a673c99f721a..d865468401e4 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestSqlTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestSqlTask.java @@ -78,14 +78,10 @@ import static io.trino.testing.assertions.Assert.assertEventually; import static java.util.concurrent.Executors.newScheduledThreadPool; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -145,12 +141,12 @@ public void testEmptyQuery() .withNoMoreBufferIds(), ImmutableMap.of(), false); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.RUNNING); - assertEquals(taskInfo.getTaskStatus().getVersion(), STARTING_VERSION); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.RUNNING); + assertThat(taskInfo.getTaskStatus().getVersion()).isEqualTo(STARTING_VERSION); taskInfo = sqlTask.getTaskInfo(); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.RUNNING); - assertEquals(taskInfo.getTaskStatus().getVersion(), STARTING_VERSION); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.RUNNING); + assertThat(taskInfo.getTaskStatus().getVersion()).isEqualTo(STARTING_VERSION); taskInfo = sqlTask.updateTask(TEST_SESSION, Span.getInvalid(), @@ -160,10 +156,10 @@ public void testEmptyQuery() .withNoMoreBufferIds(), ImmutableMap.of(), false); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.FINISHED); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.FINISHED); taskInfo = sqlTask.getTaskInfo(STARTING_VERSION).get(); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.FINISHED); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.FINISHED); } @Test @@ -173,8 +169,8 @@ public void testSimpleQuery() { SqlTask sqlTask = createInitialTask(); - assertEquals(sqlTask.getTaskStatus().getState(), TaskState.RUNNING); - assertEquals(sqlTask.getTaskStatus().getVersion(), STARTING_VERSION); + assertThat(sqlTask.getTaskStatus().getState()).isEqualTo(TaskState.RUNNING); + assertThat(sqlTask.getTaskStatus().getVersion()).isEqualTo(STARTING_VERSION); sqlTask.updateTask(TEST_SESSION, Span.getInvalid(), Optional.of(PLAN_FRAGMENT), @@ -184,34 +180,34 @@ public void testSimpleQuery() false); TaskInfo taskInfo = sqlTask.getTaskInfo(STARTING_VERSION).get(); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.FLUSHING); - assertEquals(taskInfo.getTaskStatus().getVersion(), STARTING_VERSION + 1); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.FLUSHING); + assertThat(taskInfo.getTaskStatus().getVersion()).isEqualTo(STARTING_VERSION + 1); // completed future should be returned immediately when old caller's version is used - assertTrue(sqlTask.getTaskInfo(STARTING_VERSION).isDone()); + assertThat(sqlTask.getTaskInfo(STARTING_VERSION).isDone()).isTrue(); BufferResult results = sqlTask.getTaskResults(OUT, 0, DataSize.of(1, MEGABYTE)).get(); - assertFalse(results.isBufferComplete()); - assertEquals(results.getSerializedPages().size(), 1); - assertEquals(getSerializedPagePositionCount(results.getSerializedPages().get(0)), 1); + assertThat(results.isBufferComplete()).isFalse(); + assertThat(results.getSerializedPages().size()).isEqualTo(1); + assertThat(getSerializedPagePositionCount(results.getSerializedPages().get(0))).isEqualTo(1); for (boolean moreResults = true; moreResults; moreResults = !results.isBufferComplete()) { results = sqlTask.getTaskResults(OUT, results.getToken() + results.getSerializedPages().size(), DataSize.of(1, MEGABYTE)).get(); } - assertEquals(results.getSerializedPages().size(), 0); + assertThat(results.getSerializedPages().size()).isEqualTo(0); // complete the task by calling destroy on it TaskInfo info = sqlTask.destroyTaskResults(OUT); - assertEquals(info.getOutputBuffers().getState(), BufferState.FINISHED); + assertThat(info.getOutputBuffers().getState()).isEqualTo(BufferState.FINISHED); taskInfo = sqlTask.getTaskInfo(info.getTaskStatus().getVersion()).get(); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.FINISHED); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.FINISHED); // completed future should be returned immediately when task is finished - assertTrue(sqlTask.getTaskInfo(STARTING_VERSION + 100).isDone()); + assertThat(sqlTask.getTaskInfo(STARTING_VERSION + 100).isDone()).isTrue(); taskInfo = sqlTask.getTaskInfo(); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.FINISHED); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.FINISHED); } @Test @@ -228,28 +224,30 @@ public void testCancel() .withNoMoreBufferIds(), ImmutableMap.of(), false); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.RUNNING); - assertNull(taskInfo.getStats().getEndTime()); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.RUNNING); + assertThat(taskInfo.getStats().getEndTime()).isNull(); taskInfo = sqlTask.getTaskInfo(); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.RUNNING); - assertNull(taskInfo.getStats().getEndTime()); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.RUNNING); + assertThat(taskInfo.getStats().getEndTime()).isNull(); taskInfo = sqlTask.cancel(); // This call can race and report either cancelling or cancelled - assertTrue(taskInfo.getTaskStatus().getState().isTerminatingOrDone()); + assertThat(taskInfo.getTaskStatus().getState().isTerminatingOrDone()).isTrue(); // Task cancellation can race with output buffer state updates, but should transition to cancelled quickly int attempts = 1; while (!taskInfo.getTaskStatus().getState().isDone() && attempts < 3) { taskInfo = Futures.getUnchecked(sqlTask.getTaskInfo(taskInfo.getTaskStatus().getVersion())); attempts++; } - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.CANCELED, "Failed to see CANCELED after " + attempts + " attempts"); - assertNotNull(taskInfo.getStats().getEndTime()); + assertThat(taskInfo.getTaskStatus().getState()) + .describedAs("Failed to see CANCELED after " + attempts + " attempts") + .isEqualTo(TaskState.CANCELED); + assertThat(taskInfo.getStats().getEndTime()).isNotNull(); taskInfo = sqlTask.getTaskInfo(); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.CANCELED); - assertNotNull(taskInfo.getStats().getEndTime()); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.CANCELED); + assertThat(taskInfo.getStats().getEndTime()).isNotNull(); } @Test @@ -259,8 +257,8 @@ public void testAbort() { SqlTask sqlTask = createInitialTask(); - assertEquals(sqlTask.getTaskStatus().getState(), TaskState.RUNNING); - assertEquals(sqlTask.getTaskStatus().getVersion(), STARTING_VERSION); + assertThat(sqlTask.getTaskStatus().getState()).isEqualTo(TaskState.RUNNING); + assertThat(sqlTask.getTaskStatus().getVersion()).isEqualTo(STARTING_VERSION); sqlTask.updateTask(TEST_SESSION, Span.getInvalid(), Optional.of(PLAN_FRAGMENT), @@ -270,16 +268,16 @@ public void testAbort() false); TaskInfo taskInfo = sqlTask.getTaskInfo(STARTING_VERSION).get(); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.FLUSHING); - assertEquals(taskInfo.getTaskStatus().getVersion(), STARTING_VERSION + 1); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.FLUSHING); + assertThat(taskInfo.getTaskStatus().getVersion()).isEqualTo(STARTING_VERSION + 1); sqlTask.destroyTaskResults(OUT); taskInfo = sqlTask.getTaskInfo(taskInfo.getTaskStatus().getVersion()).get(); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.FINISHED); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.FINISHED); taskInfo = sqlTask.getTaskInfo(); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.FINISHED); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.FINISHED); } @Test @@ -292,7 +290,7 @@ public void testBufferCloseOnFinish() updateTask(sqlTask, EMPTY_SPLIT_ASSIGNMENTS, outputBuffers); ListenableFuture bufferResult = sqlTask.getTaskResults(OUT, 0, DataSize.of(1, MEGABYTE)); - assertFalse(bufferResult.isDone()); + assertThat(bufferResult.isDone()).isFalse(); // close the sources (no splits will ever be added) updateTask(sqlTask, ImmutableList.of(new SplitAssignment(TABLE_SCAN_NODE_ID, ImmutableSet.of(), true)), outputBuffers); @@ -305,8 +303,8 @@ public void testBufferCloseOnFinish() // verify the buffer is closed bufferResult = sqlTask.getTaskResults(OUT, 0, DataSize.of(1, MEGABYTE)); - assertTrue(bufferResult.isDone()); - assertTrue(bufferResult.get().isBufferComplete()); + assertThat(bufferResult.isDone()).isTrue(); + assertThat(bufferResult.get().isBufferComplete()).isTrue(); } @Test @@ -318,17 +316,17 @@ public void testBufferCloseOnCancel() updateTask(sqlTask, EMPTY_SPLIT_ASSIGNMENTS, PipelinedOutputBuffers.createInitial(PARTITIONED).withBuffer(OUT, 0).withNoMoreBufferIds()); ListenableFuture bufferResult = sqlTask.getTaskResults(OUT, 0, DataSize.of(1, MEGABYTE)); - assertFalse(bufferResult.isDone()); + assertThat(bufferResult.isDone()).isFalse(); sqlTask.cancel(); - assertTrue(sqlTask.getTaskInfo().getTaskStatus().getState().isTerminatingOrDone()); + assertThat(sqlTask.getTaskInfo().getTaskStatus().getState().isTerminatingOrDone()).isTrue(); // buffer future will complete, the event is async so wait a bit for event to propagate bufferResult.get(1, SECONDS); bufferResult = sqlTask.getTaskResults(OUT, 0, DataSize.of(1, MEGABYTE)); - assertTrue(bufferResult.isDone()); - assertTrue(bufferResult.get().isBufferComplete()); + assertThat(bufferResult.isDone()).isTrue(); + assertThat(bufferResult.get().isBufferComplete()).isTrue(); } @Test @@ -341,23 +339,23 @@ public void testBufferNotCloseOnFail() updateTask(sqlTask, EMPTY_SPLIT_ASSIGNMENTS, PipelinedOutputBuffers.createInitial(PARTITIONED).withBuffer(OUT, 0).withNoMoreBufferIds()); ListenableFuture bufferResult = sqlTask.getTaskResults(OUT, 0, DataSize.of(1, MEGABYTE)); - assertFalse(bufferResult.isDone()); + assertThat(bufferResult.isDone()).isFalse(); long taskStatusVersion = sqlTask.getTaskInfo().getTaskStatus().getVersion(); sqlTask.failed(new Exception("test")); // This call can race and return either FAILED or FAILING TaskInfo taskInfo = sqlTask.getTaskInfo(taskStatusVersion).get(); - assertTrue(taskInfo.getTaskStatus().getState().isTerminatingOrDone()); + assertThat(taskInfo.getTaskStatus().getState().isTerminatingOrDone()).isTrue(); // This call should resolve to FAILED if the prior call did not taskStatusVersion = taskInfo.getTaskStatus().getVersion(); - assertEquals(sqlTask.getTaskInfo(taskStatusVersion).get().getTaskStatus().getState(), TaskState.FAILED); + assertThat(sqlTask.getTaskInfo(taskStatusVersion).get().getTaskStatus().getState()).isEqualTo(TaskState.FAILED); // buffer will not be closed by fail event. event is async so wait a bit for event to fire assertThatThrownBy(() -> bufferResult.get(1, SECONDS)) .isInstanceOf(TimeoutException.class) .hasMessageContaining("Waited 1 seconds"); - assertFalse(sqlTask.getTaskResults(OUT, 0, DataSize.of(1, MEGABYTE)).isDone()); + assertThat(sqlTask.getTaskResults(OUT, 0, DataSize.of(1, MEGABYTE)).isDone()).isFalse(); } @Test @@ -377,17 +375,17 @@ public void testDynamicFilters() ImmutableMap.of(), false); - assertEquals(sqlTask.getTaskStatus().getDynamicFiltersVersion(), INITIAL_DYNAMIC_FILTERS_VERSION); + assertThat(sqlTask.getTaskStatus().getDynamicFiltersVersion()).isEqualTo(INITIAL_DYNAMIC_FILTERS_VERSION); TaskContext taskContext = sqlTask.getQueryContext().getTaskContextByTaskId(sqlTask.getTaskId()); ListenableFuture future = sqlTask.getTaskStatus(STARTING_VERSION); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // make sure future gets unblocked when dynamic filters version is updated taskContext.updateDomains(ImmutableMap.of(DYNAMIC_FILTER_SOURCE_ID, Domain.none(BIGINT))); - assertEquals(sqlTask.getTaskStatus().getVersion(), STARTING_VERSION + 1); - assertEquals(sqlTask.getTaskStatus().getDynamicFiltersVersion(), INITIAL_DYNAMIC_FILTERS_VERSION + 1); + assertThat(sqlTask.getTaskStatus().getVersion()).isEqualTo(STARTING_VERSION + 1); + assertThat(sqlTask.getTaskStatus().getDynamicFiltersVersion()).isEqualTo(INITIAL_DYNAMIC_FILTERS_VERSION + 1); future.get(); } @@ -407,25 +405,23 @@ public void testDynamicFilterFetchAfterTaskDone() ImmutableMap.of(), false); - assertEquals(sqlTask.getTaskStatus().getDynamicFiltersVersion(), INITIAL_DYNAMIC_FILTERS_VERSION); + assertThat(sqlTask.getTaskStatus().getDynamicFiltersVersion()).isEqualTo(INITIAL_DYNAMIC_FILTERS_VERSION); // close the sources (no splits will ever be added) updateTask(sqlTask, ImmutableList.of(new SplitAssignment(TABLE_SCAN_NODE_ID, ImmutableSet.of(), true)), outputBuffers); // complete the task by calling destroy on it TaskInfo info = sqlTask.destroyTaskResults(OUT); - assertEquals(info.getOutputBuffers().getState(), BufferState.FINISHED); + assertThat(info.getOutputBuffers().getState()).isEqualTo(BufferState.FINISHED); assertEventually(new Duration(10, SECONDS), () -> { TaskStatus status = sqlTask.getTaskStatus(info.getTaskStatus().getVersion()).get(); - assertEquals(status.getState(), TaskState.FINISHED); - assertEquals(status.getDynamicFiltersVersion(), INITIAL_DYNAMIC_FILTERS_VERSION + 1); + assertThat(status.getState()).isEqualTo(TaskState.FINISHED); + assertThat(status.getDynamicFiltersVersion()).isEqualTo(INITIAL_DYNAMIC_FILTERS_VERSION + 1); }); VersionedDynamicFilterDomains versionedDynamicFilters = sqlTask.acknowledgeAndGetNewDynamicFilterDomains(INITIAL_DYNAMIC_FILTERS_VERSION); - assertEquals(versionedDynamicFilters.getVersion(), INITIAL_DYNAMIC_FILTERS_VERSION + 1); - assertEquals( - versionedDynamicFilters.getDynamicFilterDomains(), - ImmutableMap.of(DYNAMIC_FILTER_SOURCE_ID, Domain.none(VARCHAR))); + assertThat(versionedDynamicFilters.getVersion()).isEqualTo(INITIAL_DYNAMIC_FILTERS_VERSION + 1); + assertThat(versionedDynamicFilters.getDynamicFilterDomains()).isEqualTo(ImmutableMap.of(DYNAMIC_FILTER_SOURCE_ID, Domain.none(VARCHAR))); } private SqlTask createInitialTask() diff --git a/core/trino-main/src/test/java/io/trino/execution/TestSqlTaskExecution.java b/core/trino-main/src/test/java/io/trino/execution/TestSqlTaskExecution.java index 9bde68b2e01d..282c65442b80 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestSqlTaskExecution.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestSqlTaskExecution.java @@ -84,8 +84,7 @@ import static java.util.concurrent.Executors.newScheduledThreadPool; import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; +import static org.assertj.core.api.Assertions.assertThat; public class TestSqlTaskExecution { @@ -147,7 +146,7 @@ public void testSimple() // // test body - assertEquals(taskStateMachine.getState(), RUNNING); + assertThat(taskStateMachine.getState()).isEqualTo(RUNNING); // add assignment for pipeline try { @@ -190,9 +189,9 @@ public void testSimple() outputBufferConsumer.consume(300 + 200, ASSERT_WAIT_TIMEOUT); outputBufferConsumer.assertBufferComplete(ASSERT_WAIT_TIMEOUT); - assertEquals(taskStateMachine.getStateChange(RUNNING).get(10, SECONDS), FLUSHING); + assertThat(taskStateMachine.getStateChange(RUNNING).get(10, SECONDS)).isEqualTo(FLUSHING); outputBufferConsumer.abort(); // complete the task by calling abort on it - assertEquals(taskStateMachine.getStateChange(FLUSHING).get(10, SECONDS), FINISHED); + assertThat(taskStateMachine.getStateChange(FLUSHING).get(10, SECONDS)).isEqualTo(FINISHED); } finally { taskExecutor.stop(); @@ -242,7 +241,7 @@ private void waitUntilEquals(Supplier actualSupplier, T expected, Duratio // do nothing } } - assertEquals(actualSupplier.get(), expected); + assertThat(actualSupplier.get()).isEqualTo(expected); } private static class OutputBufferConsumer @@ -265,7 +264,9 @@ public void consume(int positions, Duration timeout) long nanoUntil = System.nanoTime() + timeout.toMillis() * 1_000_000; surplusPositions -= positions; while (surplusPositions < 0) { - assertFalse(bufferComplete, "bufferComplete is set before enough positions are consumed"); + assertThat(bufferComplete) + .describedAs("bufferComplete is set before enough positions are consumed") + .isFalse(); BufferResult results = outputBuffer.get(outputBufferId, sequenceId, DataSize.of(1, MEGABYTE)).get(nanoUntil - System.nanoTime(), TimeUnit.NANOSECONDS); bufferComplete = results.isBufferComplete(); for (Slice serializedPage : results.getSerializedPages()) { @@ -278,13 +279,13 @@ public void consume(int positions, Duration timeout) public void assertBufferComplete(Duration timeout) throws InterruptedException, ExecutionException, TimeoutException { - assertEquals(surplusPositions, 0); + assertThat(surplusPositions).isEqualTo(0); long nanoUntil = System.nanoTime() + timeout.toMillis() * 1_000_000; while (!bufferComplete) { BufferResult results = outputBuffer.get(outputBufferId, sequenceId, DataSize.of(1, MEGABYTE)).get(nanoUntil - System.nanoTime(), TimeUnit.NANOSECONDS); bufferComplete = results.isBufferComplete(); for (Slice serializedPage : results.getSerializedPages()) { - assertEquals(getSerializedPagePositionCount(serializedPage), 0); + assertThat(getSerializedPagePositionCount(serializedPage)).isEqualTo(0); } sequenceId += results.getSerializedPages().size(); } @@ -293,7 +294,7 @@ public void assertBufferComplete(Duration timeout) public void abort() { outputBuffer.destroy(outputBufferId); - assertEquals(outputBuffer.getInfo().getState(), BufferState.FINISHED); + assertThat(outputBuffer.getInfo().getState()).isEqualTo(BufferState.FINISHED); } } diff --git a/core/trino-main/src/test/java/io/trino/execution/TestStageStateMachine.java b/core/trino-main/src/test/java/io/trino/execution/TestStageStateMachine.java index a292a0bbcc25..c28298cddf1b 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestStageStateMachine.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestStageStateMachine.java @@ -43,13 +43,9 @@ import static io.trino.sql.planner.SystemPartitioningHandle.SINGLE_DISTRIBUTION; import static io.trino.sql.planner.SystemPartitioningHandle.SOURCE_DISTRIBUTION; import static java.util.concurrent.Executors.newCachedThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertSame; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -79,19 +75,19 @@ public void testBasicStateChanges() StageStateMachine stateMachine = createStageStateMachine(); assertState(stateMachine, StageState.PLANNED); - assertTrue(stateMachine.transitionToScheduling()); + assertThat(stateMachine.transitionToScheduling()).isTrue(); assertState(stateMachine, StageState.SCHEDULING); - assertTrue(stateMachine.transitionToRunning()); + assertThat(stateMachine.transitionToRunning()).isTrue(); assertState(stateMachine, StageState.RUNNING); - assertTrue(stateMachine.transitionToPending()); + assertThat(stateMachine.transitionToPending()).isTrue(); assertState(stateMachine, StageState.PENDING); - assertTrue(stateMachine.transitionToRunning()); + assertThat(stateMachine.transitionToRunning()).isTrue(); assertState(stateMachine, StageState.RUNNING); - assertTrue(stateMachine.transitionToFinished()); + assertThat(stateMachine.transitionToFinished()).isTrue(); assertState(stateMachine, StageState.FINISHED); } @@ -102,19 +98,19 @@ public void testPlanned() assertState(stateMachine, StageState.PLANNED); stateMachine = createStageStateMachine(); - assertTrue(stateMachine.transitionToScheduling()); + assertThat(stateMachine.transitionToScheduling()).isTrue(); assertState(stateMachine, StageState.SCHEDULING); stateMachine = createStageStateMachine(); - assertTrue(stateMachine.transitionToRunning()); + assertThat(stateMachine.transitionToRunning()).isTrue(); assertState(stateMachine, StageState.RUNNING); stateMachine = createStageStateMachine(); - assertTrue(stateMachine.transitionToFinished()); + assertThat(stateMachine.transitionToFinished()).isTrue(); assertState(stateMachine, StageState.FINISHED); stateMachine = createStageStateMachine(); - assertTrue(stateMachine.transitionToFailed(FAILED_CAUSE)); + assertThat(stateMachine.transitionToFailed(FAILED_CAUSE)).isTrue(); assertState(stateMachine, StageState.FAILED); } @@ -122,25 +118,25 @@ public void testPlanned() public void testScheduling() { StageStateMachine stateMachine = createStageStateMachine(); - assertTrue(stateMachine.transitionToScheduling()); + assertThat(stateMachine.transitionToScheduling()).isTrue(); assertState(stateMachine, StageState.SCHEDULING); - assertFalse(stateMachine.transitionToScheduling()); + assertThat(stateMachine.transitionToScheduling()).isFalse(); assertState(stateMachine, StageState.SCHEDULING); stateMachine = createStageStateMachine(); stateMachine.transitionToScheduling(); - assertTrue(stateMachine.transitionToRunning()); + assertThat(stateMachine.transitionToRunning()).isTrue(); assertState(stateMachine, StageState.RUNNING); stateMachine = createStageStateMachine(); stateMachine.transitionToScheduling(); - assertTrue(stateMachine.transitionToFinished()); + assertThat(stateMachine.transitionToFinished()).isTrue(); assertState(stateMachine, StageState.FINISHED); stateMachine = createStageStateMachine(); stateMachine.transitionToScheduling(); - assertTrue(stateMachine.transitionToFailed(FAILED_CAUSE)); + assertThat(stateMachine.transitionToFailed(FAILED_CAUSE)).isTrue(); assertState(stateMachine, StageState.FAILED); } @@ -148,29 +144,29 @@ public void testScheduling() public void testRunning() { StageStateMachine stateMachine = createStageStateMachine(); - assertTrue(stateMachine.transitionToRunning()); + assertThat(stateMachine.transitionToRunning()).isTrue(); assertState(stateMachine, StageState.RUNNING); - assertFalse(stateMachine.transitionToScheduling()); + assertThat(stateMachine.transitionToScheduling()).isFalse(); assertState(stateMachine, StageState.RUNNING); - assertFalse(stateMachine.transitionToRunning()); + assertThat(stateMachine.transitionToRunning()).isFalse(); assertState(stateMachine, StageState.RUNNING); - assertTrue(stateMachine.transitionToPending()); + assertThat(stateMachine.transitionToPending()).isTrue(); assertState(stateMachine, StageState.PENDING); - assertTrue(stateMachine.transitionToRunning()); + assertThat(stateMachine.transitionToRunning()).isTrue(); assertState(stateMachine, StageState.RUNNING); stateMachine = createStageStateMachine(); stateMachine.transitionToRunning(); - assertTrue(stateMachine.transitionToFinished()); + assertThat(stateMachine.transitionToFinished()).isTrue(); assertState(stateMachine, StageState.FINISHED); stateMachine = createStageStateMachine(); stateMachine.transitionToRunning(); - assertTrue(stateMachine.transitionToFailed(FAILED_CAUSE)); + assertThat(stateMachine.transitionToFailed(FAILED_CAUSE)).isTrue(); assertState(stateMachine, StageState.FAILED); } @@ -179,7 +175,7 @@ public void testFinished() { StageStateMachine stateMachine = createStageStateMachine(); - assertTrue(stateMachine.transitionToFinished()); + assertThat(stateMachine.transitionToFinished()).isTrue(); assertFinalState(stateMachine, StageState.FINISHED); } @@ -188,57 +184,57 @@ public void testFailed() { StageStateMachine stateMachine = createStageStateMachine(); - assertTrue(stateMachine.transitionToFailed(FAILED_CAUSE)); + assertThat(stateMachine.transitionToFailed(FAILED_CAUSE)).isTrue(); assertFinalState(stateMachine, StageState.FAILED); } private static void assertFinalState(StageStateMachine stateMachine, StageState expectedState) { - assertTrue(expectedState.isDone()); + assertThat(expectedState.isDone()).isTrue(); assertState(stateMachine, expectedState); - assertFalse(stateMachine.transitionToScheduling()); + assertThat(stateMachine.transitionToScheduling()).isFalse(); assertState(stateMachine, expectedState); - assertFalse(stateMachine.transitionToPending()); + assertThat(stateMachine.transitionToPending()).isFalse(); assertState(stateMachine, expectedState); - assertFalse(stateMachine.transitionToRunning()); + assertThat(stateMachine.transitionToRunning()).isFalse(); assertState(stateMachine, expectedState); - assertFalse(stateMachine.transitionToFinished()); + assertThat(stateMachine.transitionToFinished()).isFalse(); assertState(stateMachine, expectedState); - assertFalse(stateMachine.transitionToFailed(FAILED_CAUSE)); + assertThat(stateMachine.transitionToFailed(FAILED_CAUSE)).isFalse(); assertState(stateMachine, expectedState); // attempt to fail with another exception, which will fail - assertFalse(stateMachine.transitionToFailed(new IOException("failure after finish"))); + assertThat(stateMachine.transitionToFailed(new IOException("failure after finish"))).isFalse(); assertState(stateMachine, expectedState); } private static void assertState(StageStateMachine stateMachine, StageState expectedState) { - assertEquals(stateMachine.getStageId(), STAGE_ID); + assertThat(stateMachine.getStageId()).isEqualTo(STAGE_ID); StageInfo stageInfo = stateMachine.getStageInfo(ImmutableList::of); - assertEquals(stageInfo.getStageId(), STAGE_ID); - assertEquals(stageInfo.getSubStages(), ImmutableList.of()); - assertEquals(stageInfo.getTasks(), ImmutableList.of()); - assertEquals(stageInfo.getTypes(), ImmutableList.of(VARCHAR)); - assertSame(stageInfo.getPlan(), PLAN_FRAGMENT); + assertThat(stageInfo.getStageId()).isEqualTo(STAGE_ID); + assertThat(stageInfo.getSubStages()).isEqualTo(ImmutableList.of()); + assertThat(stageInfo.getTasks()).isEqualTo(ImmutableList.of()); + assertThat(stageInfo.getTypes()).isEqualTo(ImmutableList.of(VARCHAR)); + assertThat(stageInfo.getPlan()).isSameAs(PLAN_FRAGMENT); - assertEquals(stateMachine.getState(), expectedState); - assertEquals(stageInfo.getState(), expectedState); + assertThat(stateMachine.getState()).isEqualTo(expectedState); + assertThat(stageInfo.getState()).isEqualTo(expectedState); if (expectedState == StageState.FAILED) { ExecutionFailureInfo failure = stageInfo.getFailureCause(); - assertEquals(failure.getMessage(), FAILED_CAUSE.getMessage()); - assertEquals(failure.getType(), FAILED_CAUSE.getClass().getName()); + assertThat(failure.getMessage()).isEqualTo(FAILED_CAUSE.getMessage()); + assertThat(failure.getType()).isEqualTo(FAILED_CAUSE.getClass().getName()); } else { - assertNull(stageInfo.getFailureCause()); + assertThat(stageInfo.getFailureCause()).isNull(); } } diff --git a/core/trino-main/src/test/java/io/trino/execution/TestStageStats.java b/core/trino-main/src/test/java/io/trino/execution/TestStageStats.java index 38bbe36d5d0c..d4fcc34fbf1a 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestStageStats.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestStageStats.java @@ -29,7 +29,7 @@ import java.util.Optional; import static java.util.concurrent.TimeUnit.NANOSECONDS; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestStageStats { @@ -127,80 +127,80 @@ public void testJson() private static void assertExpectedStageStats(StageStats actual) { - assertEquals(actual.getSchedulingComplete().getMillis(), 0); - - assertEquals(actual.getGetSplitDistribution().getCount(), 1.0); - - assertEquals(actual.getTotalTasks(), 4); - assertEquals(actual.getRunningTasks(), 5); - assertEquals(actual.getCompletedTasks(), 6); - assertEquals(actual.getFailedTasks(), 1); - - assertEquals(actual.getTotalDrivers(), 7); - assertEquals(actual.getQueuedDrivers(), 8); - assertEquals(actual.getRunningDrivers(), 10); - assertEquals(actual.getBlockedDrivers(), 26); - assertEquals(actual.getCompletedDrivers(), 11); - - assertEquals(actual.getCumulativeUserMemory(), 12.0); - assertEquals(actual.getFailedCumulativeUserMemory(), 13.0); - assertEquals(actual.getUserMemoryReservation(), DataSize.ofBytes(14)); - assertEquals(actual.getRevocableMemoryReservation(), DataSize.ofBytes(15)); - assertEquals(actual.getTotalMemoryReservation(), DataSize.ofBytes(16)); - assertEquals(actual.getPeakUserMemoryReservation(), DataSize.ofBytes(17)); - assertEquals(actual.getPeakRevocableMemoryReservation(), DataSize.ofBytes(18)); - - assertEquals(actual.getTotalScheduledTime(), new Duration(19, NANOSECONDS)); - assertEquals(actual.getFailedScheduledTime(), new Duration(20, NANOSECONDS)); - assertEquals(actual.getTotalCpuTime(), new Duration(21, NANOSECONDS)); - assertEquals(actual.getFailedCpuTime(), new Duration(22, NANOSECONDS)); - assertEquals(actual.getTotalBlockedTime(), new Duration(23, NANOSECONDS)); - - assertEquals(actual.getPhysicalInputDataSize(), DataSize.ofBytes(191)); - assertEquals(actual.getFailedPhysicalInputDataSize(), DataSize.ofBytes(192)); - assertEquals(actual.getPhysicalInputPositions(), 201); - assertEquals(actual.getFailedPhysicalInputPositions(), 202); - assertEquals(actual.getPhysicalInputReadTime(), new Duration(24, NANOSECONDS)); - assertEquals(actual.getFailedPhysicalInputReadTime(), new Duration(25, NANOSECONDS)); - - assertEquals(actual.getInternalNetworkInputDataSize(), DataSize.ofBytes(193)); - assertEquals(actual.getFailedInternalNetworkInputDataSize(), DataSize.ofBytes(194)); - assertEquals(actual.getInternalNetworkInputPositions(), 203); - assertEquals(actual.getFailedInternalNetworkInputPositions(), 204); - - assertEquals(actual.getRawInputDataSize(), DataSize.ofBytes(26)); - assertEquals(actual.getFailedRawInputDataSize(), DataSize.ofBytes(27)); - assertEquals(actual.getRawInputPositions(), 28); - assertEquals(actual.getFailedRawInputPositions(), 29); - - assertEquals(actual.getProcessedInputDataSize(), DataSize.ofBytes(30)); - assertEquals(actual.getFailedProcessedInputDataSize(), DataSize.ofBytes(31)); - assertEquals(actual.getProcessedInputPositions(), 32); - assertEquals(actual.getFailedProcessedInputPositions(), 33); - - assertEquals(actual.getInputBlockedTime(), new Duration(201, NANOSECONDS)); - assertEquals(actual.getFailedInputBlockedTime(), new Duration(202, NANOSECONDS)); - - assertEquals(actual.getBufferedDataSize(), DataSize.ofBytes(34)); - assertEquals(actual.getOutputBufferUtilization().get().getMax(), 9.0); - assertEquals(actual.getOutputDataSize(), DataSize.ofBytes(35)); - assertEquals(actual.getFailedOutputDataSize(), DataSize.ofBytes(36)); - assertEquals(actual.getOutputPositions(), 37); - assertEquals(actual.getFailedOutputPositions(), 38); - - assertEquals(actual.getOutputBlockedTime(), new Duration(203, NANOSECONDS)); - assertEquals(actual.getFailedOutputBlockedTime(), new Duration(204, NANOSECONDS)); - - assertEquals(actual.getPhysicalWrittenDataSize(), DataSize.ofBytes(39)); - assertEquals(actual.getFailedPhysicalWrittenDataSize(), DataSize.ofBytes(40)); - - assertEquals(actual.getGcInfo().getStageId(), 101); - assertEquals(actual.getGcInfo().getTasks(), 102); - assertEquals(actual.getGcInfo().getFullGcTasks(), 103); - assertEquals(actual.getGcInfo().getMinFullGcSec(), 104); - assertEquals(actual.getGcInfo().getMaxFullGcSec(), 105); - assertEquals(actual.getGcInfo().getTotalFullGcSec(), 106); - assertEquals(actual.getGcInfo().getAverageFullGcSec(), 107); + assertThat(actual.getSchedulingComplete().getMillis()).isEqualTo(0); + + assertThat(actual.getGetSplitDistribution().getCount()).isEqualTo(1.0); + + assertThat(actual.getTotalTasks()).isEqualTo(4); + assertThat(actual.getRunningTasks()).isEqualTo(5); + assertThat(actual.getCompletedTasks()).isEqualTo(6); + assertThat(actual.getFailedTasks()).isEqualTo(1); + + assertThat(actual.getTotalDrivers()).isEqualTo(7); + assertThat(actual.getQueuedDrivers()).isEqualTo(8); + assertThat(actual.getRunningDrivers()).isEqualTo(10); + assertThat(actual.getBlockedDrivers()).isEqualTo(26); + assertThat(actual.getCompletedDrivers()).isEqualTo(11); + + assertThat(actual.getCumulativeUserMemory()).isEqualTo(12.0); + assertThat(actual.getFailedCumulativeUserMemory()).isEqualTo(13.0); + assertThat(actual.getUserMemoryReservation()).isEqualTo(DataSize.ofBytes(14)); + assertThat(actual.getRevocableMemoryReservation()).isEqualTo(DataSize.ofBytes(15)); + assertThat(actual.getTotalMemoryReservation()).isEqualTo(DataSize.ofBytes(16)); + assertThat(actual.getPeakUserMemoryReservation()).isEqualTo(DataSize.ofBytes(17)); + assertThat(actual.getPeakRevocableMemoryReservation()).isEqualTo(DataSize.ofBytes(18)); + + assertThat(actual.getTotalScheduledTime()).isEqualTo(new Duration(19, NANOSECONDS)); + assertThat(actual.getFailedScheduledTime()).isEqualTo(new Duration(20, NANOSECONDS)); + assertThat(actual.getTotalCpuTime()).isEqualTo(new Duration(21, NANOSECONDS)); + assertThat(actual.getFailedCpuTime()).isEqualTo(new Duration(22, NANOSECONDS)); + assertThat(actual.getTotalBlockedTime()).isEqualTo(new Duration(23, NANOSECONDS)); + + assertThat(actual.getPhysicalInputDataSize()).isEqualTo(DataSize.ofBytes(191)); + assertThat(actual.getFailedPhysicalInputDataSize()).isEqualTo(DataSize.ofBytes(192)); + assertThat(actual.getPhysicalInputPositions()).isEqualTo(201); + assertThat(actual.getFailedPhysicalInputPositions()).isEqualTo(202); + assertThat(actual.getPhysicalInputReadTime()).isEqualTo(new Duration(24, NANOSECONDS)); + assertThat(actual.getFailedPhysicalInputReadTime()).isEqualTo(new Duration(25, NANOSECONDS)); + + assertThat(actual.getInternalNetworkInputDataSize()).isEqualTo(DataSize.ofBytes(193)); + assertThat(actual.getFailedInternalNetworkInputDataSize()).isEqualTo(DataSize.ofBytes(194)); + assertThat(actual.getInternalNetworkInputPositions()).isEqualTo(203); + assertThat(actual.getFailedInternalNetworkInputPositions()).isEqualTo(204); + + assertThat(actual.getRawInputDataSize()).isEqualTo(DataSize.ofBytes(26)); + assertThat(actual.getFailedRawInputDataSize()).isEqualTo(DataSize.ofBytes(27)); + assertThat(actual.getRawInputPositions()).isEqualTo(28); + assertThat(actual.getFailedRawInputPositions()).isEqualTo(29); + + assertThat(actual.getProcessedInputDataSize()).isEqualTo(DataSize.ofBytes(30)); + assertThat(actual.getFailedProcessedInputDataSize()).isEqualTo(DataSize.ofBytes(31)); + assertThat(actual.getProcessedInputPositions()).isEqualTo(32); + assertThat(actual.getFailedProcessedInputPositions()).isEqualTo(33); + + assertThat(actual.getInputBlockedTime()).isEqualTo(new Duration(201, NANOSECONDS)); + assertThat(actual.getFailedInputBlockedTime()).isEqualTo(new Duration(202, NANOSECONDS)); + + assertThat(actual.getBufferedDataSize()).isEqualTo(DataSize.ofBytes(34)); + assertThat(actual.getOutputBufferUtilization().get().getMax()).isEqualTo(9.0); + assertThat(actual.getOutputDataSize()).isEqualTo(DataSize.ofBytes(35)); + assertThat(actual.getFailedOutputDataSize()).isEqualTo(DataSize.ofBytes(36)); + assertThat(actual.getOutputPositions()).isEqualTo(37); + assertThat(actual.getFailedOutputPositions()).isEqualTo(38); + + assertThat(actual.getOutputBlockedTime()).isEqualTo(new Duration(203, NANOSECONDS)); + assertThat(actual.getFailedOutputBlockedTime()).isEqualTo(new Duration(204, NANOSECONDS)); + + assertThat(actual.getPhysicalWrittenDataSize()).isEqualTo(DataSize.ofBytes(39)); + assertThat(actual.getFailedPhysicalWrittenDataSize()).isEqualTo(DataSize.ofBytes(40)); + + assertThat(actual.getGcInfo().getStageId()).isEqualTo(101); + assertThat(actual.getGcInfo().getTasks()).isEqualTo(102); + assertThat(actual.getGcInfo().getFullGcTasks()).isEqualTo(103); + assertThat(actual.getGcInfo().getMinFullGcSec()).isEqualTo(104); + assertThat(actual.getGcInfo().getMaxFullGcSec()).isEqualTo(105); + assertThat(actual.getGcInfo().getTotalFullGcSec()).isEqualTo(106); + assertThat(actual.getGcInfo().getAverageFullGcSec()).isEqualTo(107); } private static DistributionSnapshot getTestDistribution(int count) diff --git a/core/trino-main/src/test/java/io/trino/execution/TestStartTransactionTask.java b/core/trino-main/src/test/java/io/trino/execution/TestStartTransactionTask.java index 9ebccbee1b73..2c18ba939f9f 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestStartTransactionTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestStartTransactionTask.java @@ -61,12 +61,10 @@ import static java.util.Collections.emptyList; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Fail.fail; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -89,17 +87,17 @@ public void testNonTransactionalClient() Session session = sessionBuilder().build(); TransactionManager transactionManager = createTestTransactionManager(); QueryStateMachine stateMachine = createQueryStateMachine("START TRANSACTION", session, transactionManager); - assertFalse(stateMachine.getSession().getTransactionId().isPresent()); + assertThat(stateMachine.getSession().getTransactionId().isPresent()).isFalse(); assertTrinoExceptionThrownBy( () -> getFutureValue(new StartTransactionTask(transactionManager) .execute(new StartTransaction(ImmutableList.of()), stateMachine, emptyList(), WarningCollector.NOOP))) .hasErrorCode(INCOMPATIBLE_CLIENT); - assertTrue(transactionManager.getAllTransactionInfos().isEmpty()); + assertThat(transactionManager.getAllTransactionInfos().isEmpty()).isTrue(); - assertFalse(stateMachine.getQueryInfo(Optional.empty()).isClearTransactionId()); - assertFalse(stateMachine.getQueryInfo(Optional.empty()).getStartedTransactionId().isPresent()); + assertThat(stateMachine.getQueryInfo(Optional.empty()).isClearTransactionId()).isFalse(); + assertThat(stateMachine.getQueryInfo(Optional.empty()).getStartedTransactionId().isPresent()).isFalse(); } @Test @@ -117,10 +115,10 @@ public void testNestedTransaction() .execute(new StartTransaction(ImmutableList.of()), stateMachine, emptyList(), WarningCollector.NOOP))) .hasErrorCode(NOT_SUPPORTED); - assertTrue(transactionManager.getAllTransactionInfos().isEmpty()); + assertThat(transactionManager.getAllTransactionInfos().isEmpty()).isTrue(); - assertFalse(stateMachine.getQueryInfo(Optional.empty()).isClearTransactionId()); - assertFalse(stateMachine.getQueryInfo(Optional.empty()).getStartedTransactionId().isPresent()); + assertThat(stateMachine.getQueryInfo(Optional.empty()).isClearTransactionId()).isFalse(); + assertThat(stateMachine.getQueryInfo(Optional.empty()).getStartedTransactionId().isPresent()).isFalse(); } @Test @@ -131,15 +129,15 @@ public void testStartTransaction() .build(); TransactionManager transactionManager = createTestTransactionManager(); QueryStateMachine stateMachine = createQueryStateMachine("START TRANSACTION", session, transactionManager); - assertFalse(stateMachine.getSession().getTransactionId().isPresent()); + assertThat(stateMachine.getSession().getTransactionId().isPresent()).isFalse(); getFutureValue(new StartTransactionTask(transactionManager).execute(new StartTransaction(ImmutableList.of()), stateMachine, emptyList(), WarningCollector.NOOP)); - assertFalse(stateMachine.getQueryInfo(Optional.empty()).isClearTransactionId()); - assertTrue(stateMachine.getQueryInfo(Optional.empty()).getStartedTransactionId().isPresent()); - assertEquals(transactionManager.getAllTransactionInfos().size(), 1); + assertThat(stateMachine.getQueryInfo(Optional.empty()).isClearTransactionId()).isFalse(); + assertThat(stateMachine.getQueryInfo(Optional.empty()).getStartedTransactionId().isPresent()).isTrue(); + assertThat(transactionManager.getAllTransactionInfos().size()).isEqualTo(1); TransactionInfo transactionInfo = transactionManager.getTransactionInfo(stateMachine.getQueryInfo(Optional.empty()).getStartedTransactionId().get()); - assertFalse(transactionInfo.isAutoCommitContext()); + assertThat(transactionInfo.isAutoCommitContext()).isFalse(); } @Test @@ -150,21 +148,21 @@ public void testStartTransactionExplicitModes() .build(); TransactionManager transactionManager = createTestTransactionManager(); QueryStateMachine stateMachine = createQueryStateMachine("START TRANSACTION", session, transactionManager); - assertFalse(stateMachine.getSession().getTransactionId().isPresent()); + assertThat(stateMachine.getSession().getTransactionId().isPresent()).isFalse(); getFutureValue(new StartTransactionTask(transactionManager).execute( new StartTransaction(ImmutableList.of(new Isolation(Isolation.Level.SERIALIZABLE), new TransactionAccessMode(true))), stateMachine, emptyList(), WarningCollector.NOOP)); - assertFalse(stateMachine.getQueryInfo(Optional.empty()).isClearTransactionId()); - assertTrue(stateMachine.getQueryInfo(Optional.empty()).getStartedTransactionId().isPresent()); - assertEquals(transactionManager.getAllTransactionInfos().size(), 1); + assertThat(stateMachine.getQueryInfo(Optional.empty()).isClearTransactionId()).isFalse(); + assertThat(stateMachine.getQueryInfo(Optional.empty()).getStartedTransactionId().isPresent()).isTrue(); + assertThat(transactionManager.getAllTransactionInfos().size()).isEqualTo(1); TransactionInfo transactionInfo = transactionManager.getTransactionInfo(stateMachine.getQueryInfo(Optional.empty()).getStartedTransactionId().get()); - assertEquals(transactionInfo.getIsolationLevel(), IsolationLevel.SERIALIZABLE); - assertTrue(transactionInfo.isReadOnly()); - assertFalse(transactionInfo.isAutoCommitContext()); + assertThat(transactionInfo.getIsolationLevel()).isEqualTo(IsolationLevel.SERIALIZABLE); + assertThat(transactionInfo.isReadOnly()).isTrue(); + assertThat(transactionInfo.isAutoCommitContext()).isFalse(); } @Test @@ -175,7 +173,7 @@ public void testStartTransactionTooManyIsolationLevels() .build(); TransactionManager transactionManager = createTestTransactionManager(); QueryStateMachine stateMachine = createQueryStateMachine("START TRANSACTION", session, transactionManager); - assertFalse(stateMachine.getSession().getTransactionId().isPresent()); + assertThat(stateMachine.getSession().getTransactionId().isPresent()).isFalse(); assertTrinoExceptionThrownBy(() -> getFutureValue(new StartTransactionTask(transactionManager).execute( @@ -185,10 +183,10 @@ public void testStartTransactionTooManyIsolationLevels() WarningCollector.NOOP))) .hasErrorCode(SYNTAX_ERROR); - assertTrue(transactionManager.getAllTransactionInfos().isEmpty()); + assertThat(transactionManager.getAllTransactionInfos().isEmpty()).isTrue(); - assertFalse(stateMachine.getQueryInfo(Optional.empty()).isClearTransactionId()); - assertFalse(stateMachine.getQueryInfo(Optional.empty()).getStartedTransactionId().isPresent()); + assertThat(stateMachine.getQueryInfo(Optional.empty()).isClearTransactionId()).isFalse(); + assertThat(stateMachine.getQueryInfo(Optional.empty()).getStartedTransactionId().isPresent()).isFalse(); } @Test @@ -199,7 +197,7 @@ public void testStartTransactionTooManyAccessModes() .build(); TransactionManager transactionManager = createTestTransactionManager(); QueryStateMachine stateMachine = createQueryStateMachine("START TRANSACTION", session, transactionManager); - assertFalse(stateMachine.getSession().getTransactionId().isPresent()); + assertThat(stateMachine.getSession().getTransactionId().isPresent()).isFalse(); assertTrinoExceptionThrownBy(() -> getFutureValue(new StartTransactionTask(transactionManager).execute( @@ -209,10 +207,10 @@ public void testStartTransactionTooManyAccessModes() WarningCollector.NOOP))) .hasErrorCode(SYNTAX_ERROR); - assertTrue(transactionManager.getAllTransactionInfos().isEmpty()); + assertThat(transactionManager.getAllTransactionInfos().isEmpty()).isTrue(); - assertFalse(stateMachine.getQueryInfo(Optional.empty()).isClearTransactionId()); - assertFalse(stateMachine.getQueryInfo(Optional.empty()).getStartedTransactionId().isPresent()); + assertThat(stateMachine.getQueryInfo(Optional.empty()).isClearTransactionId()).isFalse(); + assertThat(stateMachine.getQueryInfo(Optional.empty()).getStartedTransactionId().isPresent()).isFalse(); } @Test @@ -230,15 +228,15 @@ public void testStartTransactionIdleExpiration() NO_CATALOGS, executor); QueryStateMachine stateMachine = createQueryStateMachine("START TRANSACTION", session, transactionManager); - assertFalse(stateMachine.getSession().getTransactionId().isPresent()); + assertThat(stateMachine.getSession().getTransactionId().isPresent()).isFalse(); getFutureValue(new StartTransactionTask(transactionManager).execute( new StartTransaction(ImmutableList.of()), stateMachine, emptyList(), WarningCollector.NOOP)); - assertFalse(stateMachine.getQueryInfo(Optional.empty()).isClearTransactionId()); - assertTrue(stateMachine.getQueryInfo(Optional.empty()).getStartedTransactionId().isPresent()); + assertThat(stateMachine.getQueryInfo(Optional.empty()).isClearTransactionId()).isFalse(); + assertThat(stateMachine.getQueryInfo(Optional.empty()).getStartedTransactionId().isPresent()).isTrue(); long start = System.nanoTime(); while (!transactionManager.getAllTransactionInfos().isEmpty()) { diff --git a/core/trino-main/src/test/java/io/trino/execution/TestStateMachine.java b/core/trino-main/src/test/java/io/trino/execution/TestStateMachine.java index 05d0bca2b803..0ce185228ef4 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestStateMachine.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestStateMachine.java @@ -27,12 +27,10 @@ import static io.airlift.concurrent.Threads.daemonThreadsNamed; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -97,16 +95,16 @@ public void testSet() throws Exception { StateMachine stateMachine = new StateMachine<>("test", executor, State.BREAKFAST, ImmutableSet.of(State.DINNER)); - assertEquals(stateMachine.get(), State.BREAKFAST); + assertThat(stateMachine.get()).isEqualTo(State.BREAKFAST); - assertNoStateChange(stateMachine, () -> assertEquals(stateMachine.set(State.BREAKFAST), State.BREAKFAST)); + assertNoStateChange(stateMachine, () -> assertThat(stateMachine.set(State.BREAKFAST)).isEqualTo(State.BREAKFAST)); - assertStateChange(stateMachine, () -> assertEquals(stateMachine.set(State.LUNCH), State.BREAKFAST), State.LUNCH); + assertStateChange(stateMachine, () -> assertThat(stateMachine.set(State.LUNCH)).isEqualTo(State.BREAKFAST), State.LUNCH); - assertStateChange(stateMachine, () -> assertEquals(stateMachine.set(State.BREAKFAST), State.LUNCH), State.BREAKFAST); + assertStateChange(stateMachine, () -> assertThat(stateMachine.set(State.BREAKFAST)).isEqualTo(State.LUNCH), State.BREAKFAST); // transition to a final state - assertStateChange(stateMachine, () -> assertEquals(stateMachine.set(State.DINNER), State.BREAKFAST), State.DINNER); + assertStateChange(stateMachine, () -> assertThat(stateMachine.set(State.DINNER)).isEqualTo(State.BREAKFAST), State.DINNER); // attempt transition from a final state assertNoStateChange(stateMachine, () -> @@ -121,16 +119,16 @@ public void testTrySet() throws Exception { StateMachine stateMachine = new StateMachine<>("test", executor, State.BREAKFAST, ImmutableSet.of(State.DINNER)); - assertEquals(stateMachine.get(), State.BREAKFAST); + assertThat(stateMachine.get()).isEqualTo(State.BREAKFAST); - assertNoStateChange(stateMachine, () -> assertEquals(stateMachine.trySet(State.BREAKFAST), State.BREAKFAST)); + assertNoStateChange(stateMachine, () -> assertThat(stateMachine.trySet(State.BREAKFAST)).isEqualTo(State.BREAKFAST)); - assertStateChange(stateMachine, () -> assertEquals(stateMachine.trySet(State.LUNCH), State.BREAKFAST), State.LUNCH); + assertStateChange(stateMachine, () -> assertThat(stateMachine.trySet(State.LUNCH)).isEqualTo(State.BREAKFAST), State.LUNCH); - assertStateChange(stateMachine, () -> assertEquals(stateMachine.trySet(State.BREAKFAST), State.LUNCH), State.BREAKFAST); + assertStateChange(stateMachine, () -> assertThat(stateMachine.trySet(State.BREAKFAST)).isEqualTo(State.LUNCH), State.BREAKFAST); // transition to a final state - assertStateChange(stateMachine, () -> assertEquals(stateMachine.trySet(State.DINNER), State.BREAKFAST), State.DINNER); + assertStateChange(stateMachine, () -> assertThat(stateMachine.trySet(State.DINNER)).isEqualTo(State.BREAKFAST), State.DINNER); // attempt transition from a final state assertNoStateChange(stateMachine, () -> stateMachine.trySet(State.LUNCH)); @@ -142,7 +140,7 @@ public void testCompareAndSet() throws Exception { StateMachine stateMachine = new StateMachine<>("test", executor, State.BREAKFAST, ImmutableSet.of(State.DINNER)); - assertEquals(stateMachine.get(), State.BREAKFAST); + assertThat(stateMachine.get()).isEqualTo(State.BREAKFAST); // no match with new state assertNoStateChange(stateMachine, () -> stateMachine.compareAndSet(State.DINNER, State.LUNCH)); @@ -174,36 +172,36 @@ public void testSetIf() throws Exception { StateMachine stateMachine = new StateMachine<>("test", executor, State.BREAKFAST, ImmutableSet.of(State.DINNER)); - assertEquals(stateMachine.get(), State.BREAKFAST); + assertThat(stateMachine.get()).isEqualTo(State.BREAKFAST); // false predicate with new state assertNoStateChange(stateMachine, - () -> assertFalse(stateMachine.setIf(State.LUNCH, currentState -> { - assertEquals(currentState, State.BREAKFAST); + () -> assertThat(stateMachine.setIf(State.LUNCH, currentState -> { + assertThat(currentState).isEqualTo(State.BREAKFAST); return false; - }))); + })).isFalse()); // true predicate with new state assertStateChange(stateMachine, - () -> assertTrue(stateMachine.setIf(State.LUNCH, currentState -> { - assertEquals(currentState, State.BREAKFAST); + () -> assertThat(stateMachine.setIf(State.LUNCH, currentState -> { + assertThat(currentState).isEqualTo(State.BREAKFAST); return true; - })), + })).isTrue(), State.LUNCH); // false predicate with same state assertNoStateChange(stateMachine, - () -> assertFalse(stateMachine.setIf(State.LUNCH, currentState -> { - assertEquals(currentState, State.LUNCH); + () -> assertThat(stateMachine.setIf(State.LUNCH, currentState -> { + assertThat(currentState).isEqualTo(State.LUNCH); return false; - }))); + })).isFalse()); // true predicate with same state assertNoStateChange(stateMachine, - () -> assertFalse(stateMachine.setIf(State.LUNCH, currentState -> { - assertEquals(currentState, State.LUNCH); + () -> assertThat(stateMachine.setIf(State.LUNCH, currentState -> { + assertThat(currentState).isEqualTo(State.LUNCH); return true; - }))); + })).isFalse()); // transition to a final state assertStateChange(stateMachine, () -> stateMachine.setIf(State.DINNER, currentState -> true), State.DINNER); @@ -227,15 +225,15 @@ private static void assertStateChange(StateMachine stateMachine, StateCha stateChange.run(); - assertEquals(stateMachine.get(), expectedState); + assertThat(stateMachine.get()).isEqualTo(expectedState); - assertEquals(futureChange.get(10, SECONDS), expectedState); - assertEquals(listenerChange.get(10, SECONDS), expectedState); + assertThat(futureChange.get(10, SECONDS)).isEqualTo(expectedState); + assertThat(listenerChange.get(10, SECONDS)).isEqualTo(expectedState); // listeners should not be retained if we are in a terminal state boolean isTerminalState = stateMachine.isTerminalState(expectedState); if (isTerminalState) { - assertEquals(stateMachine.getStateChangeListeners(), ImmutableSet.of()); + assertThat(stateMachine.getStateChangeListeners()).containsExactlyElementsOf(ImmutableSet.of()); } } @@ -249,20 +247,20 @@ private static void assertNoStateChange(StateMachine stateMachine, StateC // listeners should not be added if we are in a terminal state, but listener should fire boolean isTerminalState = stateMachine.isTerminalState(initialState); if (isTerminalState) { - assertEquals(stateMachine.getStateChangeListeners(), ImmutableSet.of()); + assertThat(stateMachine.getStateChangeListeners()).containsExactlyElementsOf(ImmutableSet.of()); } stateChange.run(); - assertEquals(stateMachine.get(), initialState); + assertThat(stateMachine.get()).isEqualTo(initialState); // the future change will trigger if the state machine is in a terminal state // this is to prevent waiting for state changes that will never occur - assertEquals(futureChange.isDone(), isTerminalState); + assertThat(futureChange.isDone()).isEqualTo(isTerminalState); futureChange.cancel(true); // test listener future only completes if the state actually changed - assertFalse(listenerChange.isDone()); + assertThat(listenerChange.isDone()).isFalse(); listenerChange.cancel(true); } @@ -287,7 +285,9 @@ private static SettableFuture addTestListener(StateMachine stateMa } }); - assertTrue(tryGetFutureValue(initialStateNotified, 10, SECONDS).isPresent(), "Initial state notification not fired"); + assertThat(tryGetFutureValue(initialStateNotified, 10, SECONDS).isPresent()) + .describedAs("Initial state notification not fired") + .isTrue(); return stateChanged; } diff --git a/core/trino-main/src/test/java/io/trino/execution/TestTaskExecutorStuckSplits.java b/core/trino-main/src/test/java/io/trino/execution/TestTaskExecutorStuckSplits.java index 7a36521c9303..6f305b10174b 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestTaskExecutorStuckSplits.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestTaskExecutorStuckSplits.java @@ -51,8 +51,7 @@ import static io.trino.execution.TaskTestUtils.createTestSplitMonitor; import static io.trino.execution.TaskTestUtils.createTestingPlanner; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestTaskExecutorStuckSplits { @@ -100,10 +99,10 @@ public void testFailStuckSplitTasks() mockSplitRunner.waitForFinish(); List taskInfos = sqlTaskManager.getAllTaskInfo(); - assertEquals(taskInfos.size(), 1); + assertThat(taskInfos.size()).isEqualTo(1); TaskInfo taskInfo = pollTerminatingTaskInfoUntilDone(sqlTaskManager, taskInfos.get(0)); - assertEquals(taskInfo.getTaskStatus().getState(), TaskState.FAILED); + assertThat(taskInfo.getTaskStatus().getState()).isEqualTo(TaskState.FAILED); } } finally { @@ -143,7 +142,7 @@ private SqlTaskManager createSqlTaskManager( private static TaskInfo pollTerminatingTaskInfoUntilDone(SqlTaskManager taskManager, TaskInfo taskInfo) throws InterruptedException, ExecutionException, TimeoutException { - assertTrue(taskInfo.getTaskStatus().getState().isTerminatingOrDone()); + assertThat(taskInfo.getTaskStatus().getState().isTerminatingOrDone()).isTrue(); int attempts = 3; while (attempts > 0 && taskInfo.getTaskStatus().getState().isTerminating()) { taskInfo = taskManager.getTaskInfo(taskInfo.getTaskStatus().getTaskId(), taskInfo.getTaskStatus().getVersion()).get(5, SECONDS); diff --git a/core/trino-main/src/test/java/io/trino/execution/buffer/BufferTestUtils.java b/core/trino-main/src/test/java/io/trino/execution/buffer/BufferTestUtils.java index 72debe088e04..d990d046aaa4 100644 --- a/core/trino-main/src/test/java/io/trino/execution/buffer/BufferTestUtils.java +++ b/core/trino-main/src/test/java/io/trino/execution/buffer/BufferTestUtils.java @@ -33,9 +33,7 @@ import static io.trino.execution.buffer.BufferState.FINISHED; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public final class BufferTestUtils { @@ -55,16 +53,22 @@ static BufferResult getFuture(ListenableFuture future, Duration ma static void assertBufferResultEquals(List types, BufferResult actual, BufferResult expected) { - assertEquals(actual.getSerializedPages().size(), expected.getSerializedPages().size(), "page count"); - assertEquals(actual.getToken(), expected.getToken(), "token"); + assertThat(actual.getSerializedPages().size()) + .describedAs("page count") + .isEqualTo(expected.getSerializedPages().size()); + assertThat(actual.getToken()) + .describedAs("token") + .isEqualTo(expected.getToken()); PageDeserializer deserializer = PAGES_SERDE_FACTORY.createDeserializer(Optional.empty()); for (int i = 0; i < actual.getSerializedPages().size(); i++) { Page actualPage = deserializer.deserialize(actual.getSerializedPages().get(i)); Page expectedPage = deserializer.deserialize(expected.getSerializedPages().get(i)); - assertEquals(actualPage.getChannelCount(), expectedPage.getChannelCount()); + assertThat(actualPage.getChannelCount()).isEqualTo(expectedPage.getChannelCount()); PageAssertions.assertPageEquals(types, actualPage, expectedPage); } - assertEquals(actual.isBufferComplete(), expected.isBufferComplete(), "buffer complete"); + assertThat(actual.isBufferComplete()) + .describedAs("buffer complete") + .isEqualTo(expected.isBufferComplete()); } static BufferResult createBufferResult(String bufferId, long token, List pages) @@ -114,7 +118,7 @@ static ListenableFuture enqueuePage(OutputBuffer buffer, Page page) { buffer.enqueue(ImmutableList.of(serializePage(page))); ListenableFuture future = buffer.isFull(); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); return future; } @@ -122,20 +126,24 @@ static ListenableFuture enqueuePage(OutputBuffer buffer, Page page, int pa { buffer.enqueue(partition, ImmutableList.of(serializePage(page))); ListenableFuture future = buffer.isFull(); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); return future; } public static void addPage(OutputBuffer buffer, Page page) { buffer.enqueue(ImmutableList.of(serializePage(page))); - assertTrue(buffer.isFull().isDone(), "Expected add page to not block"); + assertThat(buffer.isFull().isDone()) + .describedAs("Expected add page to not block") + .isTrue(); } public static void addPage(OutputBuffer buffer, Page page, int partition) { buffer.enqueue(partition, ImmutableList.of(serializePage(page))); - assertTrue(buffer.isFull().isDone(), "Expected add page to not block"); + assertThat(buffer.isFull().isDone()) + .describedAs("Expected add page to not block") + .isTrue(); } static void assertQueueState( @@ -144,17 +152,15 @@ static void assertQueueState( int bufferedPages, int pagesSent) { - assertEquals( - getBufferInfo(buffer, bufferId), - new PipelinedBufferInfo( - bufferId, - // every page has one row - bufferedPages + pagesSent, - bufferedPages + pagesSent, - bufferedPages, - sizeOfPages(bufferedPages).toBytes(), - pagesSent, - false)); + assertThat(getBufferInfo(buffer, bufferId)).isEqualTo(new PipelinedBufferInfo( + bufferId, + // every page has one row + bufferedPages + pagesSent, + bufferedPages + pagesSent, + bufferedPages, + sizeOfPages(bufferedPages).toBytes(), + pagesSent, + false)); } static void assertQueueState( @@ -168,36 +174,33 @@ static void assertQueueState( long assignedPages = outputBufferInfo.getPipelinedBufferStates().orElse(ImmutableList.of()).stream().mapToInt(PipelinedBufferInfo::getBufferedPages).sum(); - assertEquals( - outputBufferInfo.getTotalBufferedPages() - assignedPages, - unassignedPages, - "unassignedPages"); + assertThat(outputBufferInfo.getTotalBufferedPages() - assignedPages) + .describedAs("unassignedPages") + .isEqualTo(unassignedPages); PipelinedBufferInfo bufferInfo = outputBufferInfo.getPipelinedBufferStates().orElse(ImmutableList.of()).stream() .filter(info -> info.getBufferId().equals(bufferId)) .findAny() .orElse(null); - assertEquals( - bufferInfo, - new PipelinedBufferInfo( - bufferId, - // every page has one row - bufferedPages + pagesSent, - bufferedPages + pagesSent, - bufferedPages, - sizeOfPages(bufferedPages).toBytes(), - pagesSent, - false)); + assertThat(bufferInfo).isEqualTo(new PipelinedBufferInfo( + bufferId, + // every page has one row + bufferedPages + pagesSent, + bufferedPages + pagesSent, + bufferedPages, + sizeOfPages(bufferedPages).toBytes(), + pagesSent, + false)); } @SuppressWarnings("ConstantConditions") static void assertQueueClosed(OutputBuffer buffer, OutputBufferId bufferId, int pagesSent) { PipelinedBufferInfo bufferInfo = getBufferInfo(buffer, bufferId); - assertEquals(bufferInfo.getBufferedPages(), 0); - assertEquals(bufferInfo.getPagesSent(), pagesSent); - assertEquals(bufferInfo.isFinished(), true); + assertThat(bufferInfo.getBufferedPages()).isEqualTo(0); + assertThat(bufferInfo.getPagesSent()).isEqualTo(pagesSent); + assertThat(bufferInfo.isFinished()).isEqualTo(true); } @SuppressWarnings("ConstantConditions") @@ -206,34 +209,33 @@ static void assertQueueClosed(OutputBuffer buffer, int unassignedPages, OutputBu OutputBufferInfo outputBufferInfo = buffer.getInfo(); long assignedPages = outputBufferInfo.getPipelinedBufferStates().orElse(ImmutableList.of()).stream().mapToInt(PipelinedBufferInfo::getBufferedPages).sum(); - assertEquals( - outputBufferInfo.getTotalBufferedPages() - assignedPages, - unassignedPages, - "unassignedPages"); + assertThat(outputBufferInfo.getTotalBufferedPages() - assignedPages) + .describedAs("unassignedPages") + .isEqualTo(unassignedPages); PipelinedBufferInfo bufferInfo = outputBufferInfo.getPipelinedBufferStates().orElse(ImmutableList.of()).stream() .filter(info -> info.getBufferId().equals(bufferId)) .findAny() .orElse(null); - assertEquals(bufferInfo.getBufferedPages(), 0); - assertEquals(bufferInfo.getPagesSent(), pagesSent); - assertEquals(bufferInfo.isFinished(), true); + assertThat(bufferInfo.getBufferedPages()).isEqualTo(0); + assertThat(bufferInfo.getPagesSent()).isEqualTo(pagesSent); + assertThat(bufferInfo.isFinished()).isEqualTo(true); } static void assertFinished(OutputBuffer buffer) { - assertEquals(buffer.getState(), FINISHED); + assertThat(buffer.getState()).isEqualTo(FINISHED); for (PipelinedBufferInfo bufferInfo : buffer.getInfo().getPipelinedBufferStates().orElse(ImmutableList.of())) { - assertTrue(bufferInfo.isFinished()); - assertEquals(bufferInfo.getBufferedPages(), 0); + assertThat(bufferInfo.isFinished()).isTrue(); + assertThat(bufferInfo.getBufferedPages()).isEqualTo(0); } } static void assertFutureIsDone(Future future) { tryGetFutureValue(future, 5, SECONDS); - assertTrue(future.isDone()); + assertThat(future.isDone()).isTrue(); } private static PipelinedBufferInfo getBufferInfo(OutputBuffer buffer, OutputBufferId bufferId) diff --git a/core/trino-main/src/test/java/io/trino/execution/buffer/TestArbitraryOutputBuffer.java b/core/trino-main/src/test/java/io/trino/execution/buffer/TestArbitraryOutputBuffer.java index bcb4e368da30..da9f80173c64 100644 --- a/core/trino-main/src/test/java/io/trino/execution/buffer/TestArbitraryOutputBuffer.java +++ b/core/trino-main/src/test/java/io/trino/execution/buffer/TestArbitraryOutputBuffer.java @@ -66,9 +66,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -145,7 +142,7 @@ public void testSimple() // try to add one more page, which should block ListenableFuture future = enqueuePage(buffer, createPage(13)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); assertQueueState(buffer, 10, FIRST, 1, 3); // remove a page @@ -154,7 +151,7 @@ public void testSimple() assertQueueState(buffer, 10, FIRST, 1, 3); // we should still be blocked - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // // add another buffer and verify it sees buffered pages @@ -207,30 +204,30 @@ public void testSimple() // // finish the buffer - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); buffer.setNoMorePages(); assertQueueState(buffer, 0, FIRST, 2, 4); assertQueueState(buffer, 0, SECOND, 1, 10); // not fully finished until all pages are consumed - assertEquals(buffer.getState(), FLUSHING); + assertThat(buffer.getState()).isEqualTo(FLUSHING); // acknowledge the pages from the first buffer; buffer should not close automatically assertBufferResultEquals(TYPES, getBufferResult(buffer, FIRST, 6, sizeOfPages(10), NO_WAIT), emptyResults(TASK_INSTANCE_ID, 6, true)); assertQueueState(buffer, 0, FIRST, 0, 6); assertQueueState(buffer, 0, SECOND, 1, 10); - assertEquals(buffer.getState(), FLUSHING); + assertThat(buffer.getState()).isEqualTo(FLUSHING); // finish first queue buffer.destroy(FIRST); assertQueueClosed(buffer, 0, FIRST, 6); assertQueueState(buffer, 0, SECOND, 1, 10); - assertEquals(buffer.getState(), FLUSHING); + assertThat(buffer.getState()).isEqualTo(FLUSHING); // acknowledge a page from the second queue; queue should not close automatically assertBufferResultEquals(TYPES, getBufferResult(buffer, SECOND, 11, sizeOfPages(1), NO_WAIT), emptyResults(TASK_INSTANCE_ID, 11, true)); assertQueueState(buffer, 0, SECOND, 0, 11); - assertEquals(buffer.getState(), FLUSHING); + assertThat(buffer.getState()).isEqualTo(FLUSHING); // finish second queue buffer.destroy(SECOND); @@ -275,7 +272,7 @@ public void testAcknowledge() acknowledgeBufferResult(buffer, FIRST, 4); } catch (IllegalArgumentException e) { - assertEquals(e.getMessage(), "Invalid sequence id"); + assertThat(e.getMessage()).isEqualTo("Invalid sequence id"); } // fill the buffer @@ -347,7 +344,7 @@ public void testAddQueueAfterCreation() .withNoMoreBufferIds(), sizeOfPages(10)); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); assertThatThrownBy(() -> buffer.setOutputBuffers(PipelinedOutputBuffers.createInitial(ARBITRARY) .withBuffer(FIRST, BROADCAST_PARTITION_ID) @@ -368,26 +365,26 @@ public void testAddAfterFinish() buffer.setNoMorePages(); addPage(buffer, createPage(0)); addPage(buffer, createPage(1)); - assertEquals(buffer.getInfo().getTotalPagesSent(), 0); + assertThat(buffer.getInfo().getTotalPagesSent()).isEqualTo(0); } @Test public void testAddQueueAfterNoMoreQueues() { ArbitraryOutputBuffer buffer = createArbitraryBuffer(PipelinedOutputBuffers.createInitial(ARBITRARY), sizeOfPages(10)); - assertEquals(buffer.getState(), OPEN); + assertThat(buffer.getState()).isEqualTo(OPEN); // tell buffer no more queues will be added buffer.setOutputBuffers(PipelinedOutputBuffers.createInitial(ARBITRARY).withNoMoreBufferIds()); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); // set no more queues a second time to assure that we don't get an exception or such buffer.setOutputBuffers(PipelinedOutputBuffers.createInitial(ARBITRARY).withNoMoreBufferIds()); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); // set no more queues a third time to assure that we don't get an exception or such buffer.setOutputBuffers(PipelinedOutputBuffers.createInitial(ARBITRARY).withNoMoreBufferIds()); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); OutputBuffers outputBuffers = PipelinedOutputBuffers.createInitial(ARBITRARY) .withBuffer(FIRST, BROADCAST_PARTITION_ID) @@ -408,22 +405,22 @@ public void testAddAfterDestroy() buffer.destroy(); addPage(buffer, createPage(0)); addPage(buffer, createPage(1)); - assertEquals(buffer.getInfo().getTotalPagesSent(), 0); + assertThat(buffer.getInfo().getTotalPagesSent()).isEqualTo(0); } @Test public void testGetBeforeCreate() { ArbitraryOutputBuffer buffer = createArbitraryBuffer(PipelinedOutputBuffers.createInitial(ARBITRARY), sizeOfPages(10)); - assertEquals(buffer.getState(), OPEN); + assertThat(buffer.getState()).isEqualTo(OPEN); // get a page from a buffer that doesn't exist yet ListenableFuture future = buffer.get(FIRST, 0L, sizeOfPages(1)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // add a page and verify the future is complete addPage(buffer, createPage(33)); - assertTrue(future.isDone()); + assertThat(future.isDone()).isTrue(); assertBufferResultEquals(TYPES, getFuture(future, NO_WAIT), bufferResult(0, createPage(33))); } @@ -438,7 +435,7 @@ public void testResumeFromPreviousPosition() } ArbitraryOutputBuffer buffer = createArbitraryBuffer(outputBuffers, sizeOfPages(5)); - assertEquals(buffer.getState(), OPEN); + assertThat(buffer.getState()).isEqualTo(OPEN); Map> firstReads = new HashMap<>(); for (OutputBufferId id : ids) { @@ -457,7 +454,9 @@ public void testResumeFromPreviousPosition() .filter(entry -> entry.getValue().isDone()) .map(Map.Entry::getKey) .collect(toList()); - assertEquals(completedIds.size(), 1, "One completed buffer read per page addition"); + assertThat(completedIds.size()) + .describedAs("One completed buffer read per page addition") + .isEqualTo(1); OutputBufferId completed = completedIds.get(0); BufferResult result = getFuture(firstReads.remove(completed), NO_WAIT); @@ -465,13 +464,15 @@ public void testResumeFromPreviousPosition() secondReads.add(buffer.get(completed, result.getNextToken(), sizeOfPages(1))); } // Test sanity - assertEquals(secondReads.size(), ids.length); + assertThat(secondReads.size()).isEqualTo(ids.length); // Completion order should be identical to the first iteration at this point for (int i = 0; i < ids.length; i++) { // add one page addPage(buffer, createPage(33)); - assertTrue(secondReads.get(i).isDone(), "Invalid second read completion order at index: " + i); + assertThat(secondReads.get(i).isDone()) + .describedAs("Invalid second read completion order at index: " + i) + .isTrue(); } } @@ -483,7 +484,7 @@ public void testUseUndeclaredBufferAfterFinalBuffersSet() .withBuffer(FIRST, BROADCAST_PARTITION_ID) .withNoMoreBufferIds(), sizeOfPages(10)); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); // get a page from a buffer that was not declared, which will fail assertThatThrownBy(() -> buffer.get(SECOND, 0L, sizeOfPages(1))) @@ -495,11 +496,11 @@ public void testUseUndeclaredBufferAfterFinalBuffersSet() public void testAbortBeforeCreate() { ArbitraryOutputBuffer buffer = createArbitraryBuffer(PipelinedOutputBuffers.createInitial(ARBITRARY), sizeOfPages(10)); - assertEquals(buffer.getState(), OPEN); + assertThat(buffer.getState()).isEqualTo(OPEN); // get a page from a buffer that doesn't exist yet ListenableFuture future = buffer.get(FIRST, 0L, sizeOfPages(1)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // destroy that buffer, and verify the future is finished buffer.destroy(FIRST); @@ -594,13 +595,13 @@ public void testAbortFreesReader() { ArbitraryOutputBuffer buffer = createArbitraryBuffer(PipelinedOutputBuffers.createInitial(ARBITRARY), sizeOfPages(10)); buffer.setOutputBuffers(PipelinedOutputBuffers.createInitial(ARBITRARY).withBuffer(FIRST, 0)); - assertEquals(buffer.getState(), OPEN); + assertThat(buffer.getState()).isEqualTo(OPEN); // attempt to get a page ListenableFuture future = buffer.get(FIRST, 0, sizeOfPages(10)); // verify we are waiting for a page - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // add one item addPage(buffer, createPage(0)); @@ -610,7 +611,7 @@ public void testAbortFreesReader() // attempt to get another page, and verify we are blocked future = buffer.get(FIRST, 1, sizeOfPages(10)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // destroy the buffer buffer.destroy(FIRST); @@ -625,13 +626,13 @@ public void testFinishFreesReader() { ArbitraryOutputBuffer buffer = createArbitraryBuffer(PipelinedOutputBuffers.createInitial(ARBITRARY), sizeOfPages(10)); buffer.setOutputBuffers(PipelinedOutputBuffers.createInitial(ARBITRARY).withBuffer(FIRST, 0)); - assertEquals(buffer.getState(), OPEN); + assertThat(buffer.getState()).isEqualTo(OPEN); // attempt to get a page ListenableFuture future = buffer.get(FIRST, 0, sizeOfPages(10)); // verify we are waiting for a page - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // add one item addPage(buffer, createPage(0)); @@ -641,7 +642,7 @@ public void testFinishFreesReader() // attempt to get another page, and verify we are blocked future = buffer.get(FIRST, 1, sizeOfPages(10)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // finish the buffer assertQueueState(buffer, 0, FIRST, 0, 1); @@ -659,7 +660,7 @@ public void testFinishFreesWriter() buffer.setOutputBuffers(PipelinedOutputBuffers.createInitial(ARBITRARY) .withBuffer(FIRST, 0) .withNoMoreBufferIds()); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); // fill the buffer for (int i = 0; i < 5; i++) { @@ -675,12 +676,12 @@ public void testFinishFreesWriter() buffer.get(FIRST, 1, sizeOfPages(100)).cancel(true); // verify we are still blocked because the buffer is full - assertFalse(firstEnqueuePage.isDone()); - assertFalse(secondEnqueuePage.isDone()); + assertThat(firstEnqueuePage.isDone()).isFalse(); + assertThat(secondEnqueuePage.isDone()).isFalse(); // finish the query buffer.setNoMorePages(); - assertEquals(buffer.getState(), FLUSHING); + assertThat(buffer.getState()).isEqualTo(FLUSHING); // verify futures are complete assertFutureIsDone(firstEnqueuePage); @@ -692,7 +693,7 @@ public void testFinishFreesWriter() assertBufferResultEquals(TYPES, getBufferResult(buffer, FIRST, 7, sizeOfPages(100), NO_WAIT), emptyResults(TASK_INSTANCE_ID, 7, true)); // verify not finished - assertEquals(buffer.getState(), FLUSHING); + assertThat(buffer.getState()).isEqualTo(FLUSHING); // finish the queue buffer.destroy(FIRST); @@ -708,13 +709,13 @@ public void testDestroyFreesReader() buffer.setOutputBuffers(PipelinedOutputBuffers.createInitial(ARBITRARY) .withBuffer(FIRST, 0) .withNoMoreBufferIds()); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); // attempt to get a page ListenableFuture future = buffer.get(FIRST, 0, sizeOfPages(10)); // verify we are waiting for a page - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // add one page addPage(buffer, createPage(0)); @@ -724,7 +725,7 @@ public void testDestroyFreesReader() // attempt to get another page, and verify we are blocked future = buffer.get(FIRST, 1, sizeOfPages(10)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // destroy the buffer buffer.destroy(); @@ -741,7 +742,7 @@ public void testDestroyFreesWriter() buffer.setOutputBuffers(PipelinedOutputBuffers.createInitial(ARBITRARY) .withBuffer(FIRST, 0) .withNoMoreBufferIds()); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); // fill the buffer for (int i = 0; i < 5; i++) { @@ -757,8 +758,8 @@ public void testDestroyFreesWriter() buffer.get(FIRST, 1, sizeOfPages(1)).cancel(true); // verify we are still blocked because the buffer is full - assertFalse(firstEnqueuePage.isDone()); - assertFalse(secondEnqueuePage.isDone()); + assertThat(firstEnqueuePage.isDone()).isFalse(); + assertThat(secondEnqueuePage.isDone()).isFalse(); // destroy the buffer (i.e., cancel the query) buffer.destroy(); @@ -777,13 +778,13 @@ public void testFailDoesNotFreeReader() .withBuffer(FIRST, BROADCAST_PARTITION_ID) .withNoMoreBufferIds(), sizeOfPages(5)); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); // attempt to get a page ListenableFuture future = buffer.get(FIRST, 0, sizeOfPages(10)); // verify we are waiting for a page - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // add one page addPage(buffer, createPage(0)); @@ -793,17 +794,17 @@ public void testFailDoesNotFreeReader() // attempt to get another page, and verify we are blocked future = buffer.get(FIRST, 1, sizeOfPages(10)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // abort the buffer buffer.abort(); // future should have not finished - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // attempt to get another page, and verify we are blocked future = buffer.get(FIRST, 1, sizeOfPages(10)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); } @Test @@ -814,7 +815,7 @@ public void testFailFreesWriter() .withBuffer(FIRST, BROADCAST_PARTITION_ID) .withNoMoreBufferIds(), sizeOfPages(5)); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); // fill the buffer for (int i = 0; i < 5; i++) { @@ -830,12 +831,12 @@ public void testFailFreesWriter() buffer.get(FIRST, 1, sizeOfPages(1)).cancel(true); // verify we are still blocked because the buffer is full - assertFalse(firstEnqueuePage.isDone()); - assertFalse(secondEnqueuePage.isDone()); + assertThat(firstEnqueuePage.isDone()).isFalse(); + assertThat(secondEnqueuePage.isDone()).isFalse(); // abort the buffer (i.e., fail the query) buffer.abort(); - assertEquals(buffer.getState(), ABORTED); + assertThat(buffer.getState()).isEqualTo(ABORTED); // verify the futures are completed assertFutureIsDone(firstEnqueuePage); @@ -848,13 +849,13 @@ public void testAddBufferAfterFail() PipelinedOutputBuffers outputBuffers = PipelinedOutputBuffers.createInitial(ARBITRARY) .withBuffer(FIRST, BROADCAST_PARTITION_ID); ArbitraryOutputBuffer buffer = createArbitraryBuffer(outputBuffers, sizeOfPages(5)); - assertEquals(buffer.getState(), OPEN); + assertThat(buffer.getState()).isEqualTo(OPEN); // attempt to get a page ListenableFuture future = buffer.get(FIRST, 0, sizeOfPages(10)); // verify we are waiting for a page - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // add one page addPage(buffer, createPage(0)); @@ -871,9 +872,9 @@ public void testAddBufferAfterFail() // attempt to get page, and verify we are blocked future = buffer.get(FIRST, 1, sizeOfPages(10)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); future = buffer.get(SECOND, 0, sizeOfPages(10)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // set no more buffers outputBuffers = outputBuffers.withNoMoreBufferIds(); @@ -881,9 +882,9 @@ public void testAddBufferAfterFail() // attempt to get page, and verify we are blocked future = buffer.get(FIRST, 1, sizeOfPages(10)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); future = buffer.get(SECOND, 0, sizeOfPages(10)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); } @Test @@ -894,7 +895,7 @@ public void testBufferCompletion() .withBuffer(FIRST, 0) .withNoMoreBufferIds()); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); // fill the buffer List pages = new ArrayList<>(); @@ -910,13 +911,13 @@ public void testBufferCompletion() assertBufferResultEquals(TYPES, getBufferResult(buffer, FIRST, 0, sizeOfPages(5), MAX_WAIT), createBufferResult(TASK_INSTANCE_ID, 0, pages)); // there are no more pages and no more buffers, but buffer is not finished because it didn't receive an acknowledgement yet - assertEquals(buffer.getState(), FLUSHING); + assertThat(buffer.getState()).isEqualTo(FLUSHING); // ask the buffer to finish buffer.destroy(FIRST); // verify that the buffer is finished - assertEquals(buffer.getState(), FINISHED); + assertThat(buffer.getState()).isEqualTo(FINISHED); } @Test @@ -924,15 +925,15 @@ public void testNoMorePagesFreesReader() { ArbitraryOutputBuffer buffer = createArbitraryBuffer(PipelinedOutputBuffers.createInitial(ARBITRARY), sizeOfPages(10)); buffer.setOutputBuffers(PipelinedOutputBuffers.createInitial(ARBITRARY).withBuffer(FIRST, 0)); - assertEquals(buffer.getState(), OPEN); + assertThat(buffer.getState()).isEqualTo(OPEN); ListenableFuture future = buffer.get(FIRST, 0, sizeOfPages(10)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); buffer.setNoMorePages(); - assertTrue(future.isDone()); - assertTrue(buffer.get(FIRST, 0, sizeOfPages(10)).isDone()); + assertThat(future.isDone()).isTrue(); + assertThat(buffer.get(FIRST, 0, sizeOfPages(10)).isDone()).isTrue(); } @Test @@ -945,21 +946,21 @@ public void testFinishBeforeNoMoreBuffers() addPage(buffer, createPage(i)); } buffer.setNoMorePages(); - assertEquals(buffer.getState(), NO_MORE_PAGES); + assertThat(buffer.getState()).isEqualTo(NO_MORE_PAGES); // add one output buffer PipelinedOutputBuffers outputBuffers = PipelinedOutputBuffers.createInitial(ARBITRARY).withBuffer(FIRST, 0); buffer.setOutputBuffers(outputBuffers); - assertEquals(buffer.getState(), NO_MORE_PAGES); + assertThat(buffer.getState()).isEqualTo(NO_MORE_PAGES); // read a page from the first buffer assertBufferResultEquals(TYPES, getBufferResult(buffer, FIRST, 0, sizeOfPages(1), NO_WAIT), bufferResult(0, createPage(0))); - assertEquals(buffer.getState(), NO_MORE_PAGES); + assertThat(buffer.getState()).isEqualTo(NO_MORE_PAGES); // read remaining pages from the first buffer and acknowledge assertBufferResultEquals(TYPES, getBufferResult(buffer, FIRST, 1, sizeOfPages(10), NO_WAIT), bufferResult(1, createPage(1), createPage(2))); assertBufferResultEquals(TYPES, getBufferResult(buffer, FIRST, 3, sizeOfPages(1), NO_WAIT), emptyResults(TASK_INSTANCE_ID, 3, true)); - assertEquals(buffer.getState(), NO_MORE_PAGES); + assertThat(buffer.getState()).isEqualTo(NO_MORE_PAGES); // finish first queue buffer.destroy(FIRST); @@ -982,12 +983,12 @@ public void testForceFreeMemory() addPage(buffer, createPage(i)); } OutputBufferMemoryManager memoryManager = buffer.getMemoryManager(); - assertTrue(memoryManager.getBufferedBytes() > 0); + assertThat(memoryManager.getBufferedBytes() > 0).isTrue(); buffer.forceFreeMemory(); - assertEquals(memoryManager.getBufferedBytes(), 0); + assertThat(memoryManager.getBufferedBytes()).isEqualTo(0); // adding a page after forceFreeMemory() should be NOOP addPage(buffer, createPage(1)); - assertEquals(memoryManager.getBufferedBytes(), 0); + assertThat(memoryManager.getBufferedBytes()).isEqualTo(0); } private static BufferResult getBufferResult(OutputBuffer buffer, OutputBufferId bufferId, long sequenceId, DataSize maxSize, Duration maxWait) @@ -1000,14 +1001,16 @@ private static ListenableFuture enqueuePage(OutputBuffer buffer, Page page { buffer.enqueue(ImmutableList.of(serializePage(page))); ListenableFuture future = buffer.isFull(); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); return future; } private static void addPage(OutputBuffer buffer, Page page) { buffer.enqueue(ImmutableList.of(serializePage(page))); - assertTrue(buffer.isFull().isDone(), "Expected add page to not block"); + assertThat(buffer.isFull().isDone()) + .describedAs("Expected add page to not block") + .isTrue(); } private static void assertQueueState( @@ -1021,27 +1024,24 @@ private static void assertQueueState( long assignedPages = outputBufferInfo.getPipelinedBufferStates().orElse(ImmutableList.of()).stream().mapToInt(PipelinedBufferInfo::getBufferedPages).sum(); - assertEquals( - outputBufferInfo.getTotalBufferedPages() - assignedPages, - unassignedPages, - "unassignedPages"); + assertThat(outputBufferInfo.getTotalBufferedPages() - assignedPages) + .describedAs("unassignedPages") + .isEqualTo(unassignedPages); PipelinedBufferInfo bufferInfo = outputBufferInfo.getPipelinedBufferStates().orElse(ImmutableList.of()).stream() .filter(info -> info.getBufferId().equals(bufferId)) .findAny() .orElse(null); - assertEquals( - bufferInfo, - new PipelinedBufferInfo( - bufferId, - // every page has one row - bufferedPages + pagesSent, - bufferedPages + pagesSent, - bufferedPages, - sizeOfPages(bufferedPages).toBytes(), - pagesSent, - false)); + assertThat(bufferInfo).isEqualTo(new PipelinedBufferInfo( + bufferId, + // every page has one row + bufferedPages + pagesSent, + bufferedPages + pagesSent, + bufferedPages, + sizeOfPages(bufferedPages).toBytes(), + pagesSent, + false)); } @SuppressWarnings("ConstantConditions") @@ -1050,19 +1050,18 @@ private static void assertQueueClosed(OutputBuffer buffer, int unassignedPages, OutputBufferInfo outputBufferInfo = buffer.getInfo(); long assignedPages = outputBufferInfo.getPipelinedBufferStates().orElse(ImmutableList.of()).stream().mapToInt(PipelinedBufferInfo::getBufferedPages).sum(); - assertEquals( - outputBufferInfo.getTotalBufferedPages() - assignedPages, - unassignedPages, - "unassignedPages"); + assertThat(outputBufferInfo.getTotalBufferedPages() - assignedPages) + .describedAs("unassignedPages") + .isEqualTo(unassignedPages); PipelinedBufferInfo bufferInfo = outputBufferInfo.getPipelinedBufferStates().orElse(ImmutableList.of()).stream() .filter(info -> info.getBufferId().equals(bufferId)) .findAny() .orElse(null); - assertEquals(bufferInfo.getBufferedPages(), 0); - assertEquals(bufferInfo.getPagesSent(), pagesSent); - assertTrue(bufferInfo.isFinished()); + assertThat(bufferInfo.getBufferedPages()).isEqualTo(0); + assertThat(bufferInfo.getPagesSent()).isEqualTo(pagesSent); + assertThat(bufferInfo.isFinished()).isTrue(); } private ArbitraryOutputBuffer createArbitraryBuffer(OutputBuffers buffers, DataSize dataSize) diff --git a/core/trino-main/src/test/java/io/trino/execution/buffer/TestBroadcastOutputBuffer.java b/core/trino-main/src/test/java/io/trino/execution/buffer/TestBroadcastOutputBuffer.java index 75f68c73cadd..a0b48c3c9970 100644 --- a/core/trino-main/src/test/java/io/trino/execution/buffer/TestBroadcastOutputBuffer.java +++ b/core/trino-main/src/test/java/io/trino/execution/buffer/TestBroadcastOutputBuffer.java @@ -69,12 +69,10 @@ import static io.trino.spi.type.BigintType.BIGINT; import static java.util.Objects.requireNonNull; import static java.util.concurrent.Executors.newScheduledThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -150,7 +148,7 @@ public void testSimple() // try to add one more page, which should block ListenableFuture future = enqueuePage(buffer, createPage(10)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); assertQueueState(buffer, FIRST, 8, 3); // remove a page @@ -159,7 +157,7 @@ public void testSimple() assertQueueState(buffer, FIRST, 8, 3); // we should still be blocked - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // // add another buffer and verify it sees all pages @@ -197,7 +195,7 @@ public void testSimple() addPage(buffer, createPage(11)); addPage(buffer, createPage(12)); future = enqueuePage(buffer, createPage(13)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); assertQueueState(buffer, FIRST, 11, 3); assertQueueState(buffer, SECOND, 4, 10); @@ -211,19 +209,19 @@ public void testSimple() // // finish the buffer - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); buffer.setNoMorePages(); assertQueueState(buffer, FIRST, 10, 4); assertQueueState(buffer, SECOND, 4, 10); // not fully finished until all pages are consumed - assertEquals(buffer.getState(), FLUSHING); + assertThat(buffer.getState()).isEqualTo(FLUSHING); // remove a page, not finished assertBufferResultEquals(TYPES, getBufferResult(buffer, FIRST, 5, sizeOfPages(1), NO_WAIT), bufferResult(5, createPage(5))); assertQueueState(buffer, FIRST, 9, 5); assertQueueState(buffer, SECOND, 4, 10); - assertEquals(buffer.getState(), FLUSHING); + assertThat(buffer.getState()).isEqualTo(FLUSHING); // remove all remaining pages from first queue, should not be finished BufferResult x = getBufferResult(buffer, FIRST, 6, sizeOfPages(10), NO_WAIT); @@ -242,7 +240,7 @@ public void testSimple() buffer.destroy(FIRST); assertQueueClosed(buffer, FIRST, 14); assertQueueState(buffer, SECOND, 4, 10); - assertEquals(buffer.getState(), FLUSHING); + assertThat(buffer.getState()).isEqualTo(FLUSHING); // remove all remaining pages from second queue, should be finished assertBufferResultEquals(TYPES, getBufferResult(buffer, SECOND, 10, sizeOfPages(10), NO_WAIT), bufferResult(10, createPage(10), @@ -293,7 +291,7 @@ public void testAcknowledge() acknowledgeBufferResult(buffer, FIRST, 4); } catch (IllegalArgumentException e) { - assertEquals(e.getMessage(), "Invalid sequence id"); + assertThat(e.getMessage()).isEqualTo("Invalid sequence id"); } // fill the buffer @@ -331,13 +329,13 @@ public void testNotifyStatusOnBufferFull() // Add a page to the buffer addPage(buffer, createPage(1)); - assertTrue(buffer.isFull().isDone()); - assertEquals(notifyCount.get(), 0); + assertThat(buffer.isFull().isDone()).isTrue(); + assertThat(notifyCount.get()).isEqualTo(0); // Add another page to block ListenableFuture future = enqueuePage(buffer, createPage(2)); - assertFalse(future.isDone()); - assertEquals(notifyCount.get(), 1); + assertThat(future.isDone()).isFalse(); + assertThat(notifyCount.get()).isEqualTo(1); // Set no more buffers buffer.setOutputBuffers(PipelinedOutputBuffers.createInitial(BROADCAST).withBuffer(FIRST, BROADCAST_PARTITION_ID).withNoMoreBufferIds()); @@ -345,13 +343,13 @@ public void testNotifyStatusOnBufferFull() // Acknowledge both pages in the buffer to remove them buffer.acknowledge(FIRST, 2); assertFutureIsDone(future); - assertEquals(notifyCount.get(), 1); + assertThat(notifyCount.get()).isEqualTo(1); // Add two more pages, buffer will be blocked second time addPage(buffer, createPage(3)); future = enqueuePage(buffer, createPage(4)); - assertFalse(future.isDone()); - assertEquals(notifyCount.get(), 1); + assertThat(future.isDone()).isFalse(); + assertThat(notifyCount.get()).isEqualTo(1); } @Test @@ -367,24 +365,24 @@ public void testNotifyStatusOnBufferFullWithNoBufferIds() // Add a page to the buffer addPage(buffer, createPage(1)); - assertTrue(buffer.isFull().isDone()); - assertEquals(notifyCount.get(), 0); + assertThat(buffer.isFull().isDone()).isTrue(); + assertThat(notifyCount.get()).isEqualTo(0); // Add another page to block ListenableFuture future = enqueuePage(buffer, createPage(2)); - assertFalse(future.isDone()); - assertEquals(notifyCount.get(), 0); // stays 0 because no new buffers will be added + assertThat(future.isDone()).isFalse(); + assertThat(notifyCount.get()).isEqualTo(0); // stays 0 because no new buffers will be added // Acknowledge both pages in the buffer to remove them buffer.acknowledge(FIRST, 2); assertFutureIsDone(future); - assertEquals(notifyCount.get(), 0); + assertThat(notifyCount.get()).isEqualTo(0); // Add two more pages, buffer will be blocked second time addPage(buffer, createPage(3)); future = enqueuePage(buffer, createPage(4)); - assertFalse(future.isDone()); - assertEquals(notifyCount.get(), 0); + assertThat(future.isDone()).isFalse(); + assertThat(notifyCount.get()).isEqualTo(0); } @Test @@ -432,7 +430,7 @@ public void testAddQueueAfterCreation() .withNoMoreBufferIds(), sizeOfPages(10)); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); assertThatThrownBy(() -> buffer.setOutputBuffers(PipelinedOutputBuffers.createInitial(BROADCAST) .withBuffer(FIRST, BROADCAST_PARTITION_ID) @@ -453,26 +451,26 @@ public void testAddAfterFinish() buffer.setNoMorePages(); addPage(buffer, createPage(0)); addPage(buffer, createPage(0)); - assertEquals(buffer.getInfo().getTotalPagesSent(), 0); + assertThat(buffer.getInfo().getTotalPagesSent()).isEqualTo(0); } @Test public void testAddQueueAfterNoMoreQueues() { BroadcastOutputBuffer buffer = createBroadcastBuffer(PipelinedOutputBuffers.createInitial(BROADCAST), sizeOfPages(10)); - assertEquals(buffer.getState(), OPEN); + assertThat(buffer.getState()).isEqualTo(OPEN); // tell buffer no more queues will be added buffer.setOutputBuffers(PipelinedOutputBuffers.createInitial(BROADCAST).withNoMoreBufferIds()); - assertEquals(buffer.getState(), FINISHED); + assertThat(buffer.getState()).isEqualTo(FINISHED); // set no more queues a second time to assure that we don't get an exception or such buffer.setOutputBuffers(PipelinedOutputBuffers.createInitial(BROADCAST).withNoMoreBufferIds()); - assertEquals(buffer.getState(), FINISHED); + assertThat(buffer.getState()).isEqualTo(FINISHED); // set no more queues a third time to assure that we don't get an exception or such buffer.setOutputBuffers(PipelinedOutputBuffers.createInitial(BROADCAST).withNoMoreBufferIds()); - assertEquals(buffer.getState(), FINISHED); + assertThat(buffer.getState()).isEqualTo(FINISHED); } @Test @@ -486,22 +484,22 @@ public void testAddAfterDestroy() buffer.destroy(); addPage(buffer, createPage(0)); addPage(buffer, createPage(0)); - assertEquals(buffer.getInfo().getTotalPagesSent(), 0); + assertThat(buffer.getInfo().getTotalPagesSent()).isEqualTo(0); } @Test public void testGetBeforeCreate() { BroadcastOutputBuffer buffer = createBroadcastBuffer(PipelinedOutputBuffers.createInitial(BROADCAST), sizeOfPages(10)); - assertEquals(buffer.getState(), OPEN); + assertThat(buffer.getState()).isEqualTo(OPEN); // get a page from a buffer that doesn't exist yet ListenableFuture future = buffer.get(FIRST, 0L, sizeOfPages(1)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // add a page and verify the future is complete addPage(buffer, createPage(33)); - assertTrue(future.isDone()); + assertThat(future.isDone()).isTrue(); assertBufferResultEquals(TYPES, getFuture(future, NO_WAIT), bufferResult(0, createPage(33))); } @@ -509,18 +507,18 @@ public void testGetBeforeCreate() public void testSetFinalBuffersWihtoutDeclaringUsedBuffer() { BroadcastOutputBuffer buffer = createBroadcastBuffer(PipelinedOutputBuffers.createInitial(BROADCAST), sizeOfPages(10)); - assertEquals(buffer.getState(), OPEN); + assertThat(buffer.getState()).isEqualTo(OPEN); // get a page from a buffer that doesn't exist yet ListenableFuture future = buffer.get(FIRST, 0L, sizeOfPages(1)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // add a page and set no more pages addPage(buffer, createPage(33)); buffer.setNoMorePages(); // read the page - assertTrue(future.isDone()); + assertThat(future.isDone()).isTrue(); assertBufferResultEquals(TYPES, getFuture(future, NO_WAIT), bufferResult(0, createPage(33))); // acknowledge the page and verify we are finished @@ -541,7 +539,7 @@ public void testUseUndeclaredBufferAfterFinalBuffersSet() .withBuffer(FIRST, BROADCAST_PARTITION_ID) .withNoMoreBufferIds(), sizeOfPages(10)); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); // get a page from a buffer that was not declared, which will fail assertThatThrownBy(() -> buffer.get(SECOND, 0L, sizeOfPages(1))) @@ -553,15 +551,15 @@ public void testUseUndeclaredBufferAfterFinalBuffersSet() public void testAbortBeforeCreate() { BroadcastOutputBuffer buffer = createBroadcastBuffer(PipelinedOutputBuffers.createInitial(BROADCAST), sizeOfPages(2)); - assertEquals(buffer.getState(), OPEN); + assertThat(buffer.getState()).isEqualTo(OPEN); // get a page from a buffer that doesn't exist yet ListenableFuture future = buffer.get(FIRST, 0, sizeOfPages(1)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // destroy that buffer, and verify the future is complete and buffer is finished buffer.destroy(FIRST); - assertTrue(future.isDone()); + assertThat(future.isDone()).isTrue(); assertBufferResultEquals(TYPES, getBufferResult(buffer, FIRST, 0, sizeOfPages(10), NO_WAIT), emptyResults(TASK_INSTANCE_ID, 0, true)); } @@ -602,13 +600,13 @@ public void testAcknowledgementFreesWriters() ListenableFuture future = enqueuePage(buffer, createPage(3)); // we should be blocked - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); assertQueueState(buffer, FIRST, 3, 0); assertQueueState(buffer, SECOND, 3, 0); // acknowledge pages for first buffer, no space is freed buffer.get(FIRST, 2, sizeOfPages(10)).cancel(true); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // acknowledge pages for second buffer, which makes space in the buffer buffer.get(SECOND, 2, sizeOfPages(10)).cancel(true); @@ -678,24 +676,24 @@ public void testAbortFreesReader() .withBuffer(SECOND, BROADCAST_PARTITION_ID) .withNoMoreBufferIds(), sizeOfPages(5)); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); // attempt to get a page ListenableFuture future = buffer.get(FIRST, 0, sizeOfPages(10)); // verify we are waiting for a page - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // add one item addPage(buffer, createPage(0)); - assertTrue(future.isDone()); + assertThat(future.isDone()).isTrue(); // verify we got one page assertBufferResultEquals(TYPES, getFuture(future, NO_WAIT), bufferResult(0, createPage(0))); // attempt to get another page, and verify we are blocked future = buffer.get(FIRST, 1, sizeOfPages(10)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // destroy the buffer buffer.destroy(FIRST); @@ -716,13 +714,13 @@ public void testFinishFreesReader() .withBuffer(FIRST, BROADCAST_PARTITION_ID) .withNoMoreBufferIds(), sizeOfPages(5)); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); // attempt to get a page ListenableFuture future = buffer.get(FIRST, 0, sizeOfPages(10)); // verify we are waiting for a page - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // add one item addPage(buffer, createPage(0)); @@ -732,7 +730,7 @@ public void testFinishFreesReader() // attempt to get another page, and verify we are blocked future = buffer.get(FIRST, 1, sizeOfPages(10)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // finish the buffer buffer.setNoMorePages(); @@ -750,7 +748,7 @@ public void testFinishFreesWriter() .withBuffer(FIRST, BROADCAST_PARTITION_ID) .withNoMoreBufferIds(), sizeOfPages(5)); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); // fill the buffer for (int i = 0; i < 5; i++) { @@ -766,12 +764,12 @@ public void testFinishFreesWriter() buffer.get(FIRST, 1, sizeOfPages(1)).cancel(true); // verify we are still blocked because the buffer is full - assertFalse(firstEnqueuePage.isDone()); - assertFalse(secondEnqueuePage.isDone()); + assertThat(firstEnqueuePage.isDone()).isFalse(); + assertThat(secondEnqueuePage.isDone()).isFalse(); // finish the query buffer.setNoMorePages(); - assertEquals(buffer.getState(), FLUSHING); + assertThat(buffer.getState()).isEqualTo(FLUSHING); // verify futures are complete assertFutureIsDone(firstEnqueuePage); @@ -796,13 +794,13 @@ public void testDestroyFreesReader() .withBuffer(FIRST, BROADCAST_PARTITION_ID) .withNoMoreBufferIds(), sizeOfPages(5)); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); // attempt to get a page ListenableFuture future = buffer.get(FIRST, 0, sizeOfPages(10)); // verify we are waiting for a page - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // add one page addPage(buffer, createPage(0)); @@ -812,7 +810,7 @@ public void testDestroyFreesReader() // attempt to get another page, and verify we are blocked future = buffer.get(FIRST, 1, sizeOfPages(10)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // destroy the buffer buffer.destroy(); @@ -830,7 +828,7 @@ public void testDestroyFreesWriter() .withBuffer(FIRST, BROADCAST_PARTITION_ID) .withNoMoreBufferIds(), sizeOfPages(5)); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); // fill the buffer for (int i = 0; i < 5; i++) { @@ -846,8 +844,8 @@ public void testDestroyFreesWriter() buffer.get(FIRST, 1, sizeOfPages(1)).cancel(true); // verify we are still blocked because the buffer is full - assertFalse(firstEnqueuePage.isDone()); - assertFalse(secondEnqueuePage.isDone()); + assertThat(firstEnqueuePage.isDone()).isFalse(); + assertThat(secondEnqueuePage.isDone()).isFalse(); // destroy the buffer (i.e., cancel the query) buffer.destroy(); @@ -866,13 +864,13 @@ public void testFailDoesNotFreeReader() .withBuffer(FIRST, BROADCAST_PARTITION_ID) .withNoMoreBufferIds(), sizeOfPages(5)); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); // attempt to get a page ListenableFuture future = buffer.get(FIRST, 0, sizeOfPages(10)); // verify we are waiting for a page - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // add one page addPage(buffer, createPage(0)); @@ -882,17 +880,17 @@ public void testFailDoesNotFreeReader() // attempt to get another page, and verify we are blocked future = buffer.get(FIRST, 1, sizeOfPages(10)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // abort the buffer buffer.abort(); // future should have not finished - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // attempt to get another page, and verify we are blocked future = buffer.get(FIRST, 1, sizeOfPages(10)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); } @Test @@ -903,7 +901,7 @@ public void testFailFreesWriter() .withBuffer(FIRST, BROADCAST_PARTITION_ID) .withNoMoreBufferIds(), sizeOfPages(5)); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); // fill the buffer for (int i = 0; i < 5; i++) { @@ -919,12 +917,12 @@ public void testFailFreesWriter() buffer.get(FIRST, 1, sizeOfPages(1)).cancel(true); // verify we are still blocked because the buffer is full - assertFalse(firstEnqueuePage.isDone()); - assertFalse(secondEnqueuePage.isDone()); + assertThat(firstEnqueuePage.isDone()).isFalse(); + assertThat(secondEnqueuePage.isDone()).isFalse(); // abort the buffer (i.e., fail the query) buffer.abort(); - assertEquals(buffer.getState(), ABORTED); + assertThat(buffer.getState()).isEqualTo(ABORTED); // verify the futures are completed assertFutureIsDone(firstEnqueuePage); @@ -937,13 +935,13 @@ public void testAddBufferAfterFail() PipelinedOutputBuffers outputBuffers = PipelinedOutputBuffers.createInitial(BROADCAST) .withBuffer(FIRST, BROADCAST_PARTITION_ID); BroadcastOutputBuffer buffer = createBroadcastBuffer(outputBuffers, sizeOfPages(5)); - assertEquals(buffer.getState(), OPEN); + assertThat(buffer.getState()).isEqualTo(OPEN); // attempt to get a page ListenableFuture future = buffer.get(FIRST, 0, sizeOfPages(10)); // verify we are waiting for a page - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // add one page addPage(buffer, createPage(0)); @@ -960,9 +958,9 @@ public void testAddBufferAfterFail() // attempt to get page, and verify we are blocked future = buffer.get(FIRST, 1, sizeOfPages(10)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); future = buffer.get(SECOND, 0, sizeOfPages(10)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // set no more buffers outputBuffers = outputBuffers.withNoMoreBufferIds(); @@ -970,9 +968,9 @@ public void testAddBufferAfterFail() // attempt to get page, and verify we are blocked future = buffer.get(FIRST, 1, sizeOfPages(10)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); future = buffer.get(SECOND, 0, sizeOfPages(10)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); } @Test @@ -984,7 +982,7 @@ public void testBufferCompletion() .withNoMoreBufferIds(), sizeOfPages(5)); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); // fill the buffer List pages = new ArrayList<>(); @@ -1000,13 +998,13 @@ public void testBufferCompletion() assertBufferResultEquals(TYPES, getBufferResult(buffer, FIRST, 0, sizeOfPages(5), MAX_WAIT), createBufferResult(TASK_INSTANCE_ID, 0, pages)); // there are no more pages and no more buffers, but buffer is not finished because it didn't receive an acknowledgement yet - assertEquals(buffer.getState(), FLUSHING); + assertThat(buffer.getState()).isEqualTo(FLUSHING); // ask the buffer to finish buffer.destroy(FIRST); // verify that the buffer is finished - assertEquals(buffer.getState(), FINISHED); + assertThat(buffer.getState()).isEqualTo(FINISHED); } @Test @@ -1029,7 +1027,9 @@ public void testSharedBufferBlocking() // more memory is available blockedFuture.set(null); memoryManager.onMemoryAvailable(); - assertTrue(memoryManager.getBufferBlockedFuture().isDone(), "buffer shouldn't be blocked"); + assertThat(memoryManager.getBufferBlockedFuture().isDone()) + .describedAs("buffer shouldn't be blocked") + .isTrue(); // we should be able to add one more page after more memory is available addPage(buffer, page); @@ -1070,13 +1070,17 @@ public void testSharedBufferBlocking2() memoryManager.onMemoryAvailable(); // memoryManager should still return a blocked future as the buffer is still full - assertFalse(memoryManager.getBufferBlockedFuture().isDone(), "buffer should be blocked"); + assertThat(memoryManager.getBufferBlockedFuture().isDone()) + .describedAs("buffer should be blocked") + .isFalse(); // remove all pages from the memory manager and the 1 byte that we added above memoryManager.updateMemoryUsage(-pageSize * 2 - 1); // now we have both buffer space and memory available, so memoryManager shouldn't be blocked - assertTrue(memoryManager.getBufferBlockedFuture().isDone(), "buffer shouldn't be blocked"); + assertThat(memoryManager.getBufferBlockedFuture().isDone()) + .describedAs("buffer shouldn't be blocked") + .isTrue(); // we should be able to add two pages after more memory is available addPage(buffer, page); @@ -1109,7 +1113,9 @@ public void testSharedBufferBlockingNoBlockOnFull() // more memory is available blockedFuture.set(null); memoryManager.onMemoryAvailable(); - assertTrue(memoryManager.getBufferBlockedFuture().isDone(), "buffer shouldn't be blocked"); + assertThat(memoryManager.getBufferBlockedFuture().isDone()) + .describedAs("buffer shouldn't be blocked") + .isTrue(); // we should be able to add one more page after more memory is available addPage(buffer, page); @@ -1180,11 +1186,11 @@ public void testBufferFinishesWhenClientBuffersDestroyed() // and if we destroy all the buffers it should destroy itself // and move to the FINISHED state buffer.destroy(FIRST); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); buffer.destroy(SECOND); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); buffer.destroy(THIRD); - assertEquals(buffer.getState(), FINISHED); + assertThat(buffer.getState()).isEqualTo(FINISHED); } @Test @@ -1199,12 +1205,12 @@ public void testForceFreeMemory() addPage(buffer, createPage(1), 0); } OutputBufferMemoryManager memoryManager = buffer.getMemoryManager(); - assertTrue(memoryManager.getBufferedBytes() > 0); + assertThat(memoryManager.getBufferedBytes() > 0).isTrue(); buffer.forceFreeMemory(); - assertEquals(memoryManager.getBufferedBytes(), 0); + assertThat(memoryManager.getBufferedBytes()).isEqualTo(0); // adding a page after forceFreeMemory() should be NOOP addPage(buffer, createPage(1)); - assertEquals(memoryManager.getBufferedBytes(), 0); + assertThat(memoryManager.getBufferedBytes()).isEqualTo(0); } private BroadcastOutputBuffer createBroadcastBuffer(OutputBuffers outputBuffers, DataSize dataSize) diff --git a/core/trino-main/src/test/java/io/trino/execution/buffer/TestClientBuffer.java b/core/trino-main/src/test/java/io/trino/execution/buffer/TestClientBuffer.java index 92f2297d8370..a2151875504e 100644 --- a/core/trino-main/src/test/java/io/trino/execution/buffer/TestClientBuffer.java +++ b/core/trino-main/src/test/java/io/trino/execution/buffer/TestClientBuffer.java @@ -45,11 +45,9 @@ import static io.trino.execution.buffer.SerializedPageReference.dereferencePages; import static io.trino.spi.type.BigintType.BIGINT; import static java.util.Objects.requireNonNull; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Fail.fail; public class TestClientBuffer { @@ -123,54 +121,54 @@ public void testSimplePullBuffer() for (int i = 0; i < 3; i++) { supplier.addPage(createPage(i)); } - assertEquals(supplier.getBufferedPages(), 3); + assertThat(supplier.getBufferedPages()).isEqualTo(3); // get the pages elements from the buffer assertBufferResultEquals(TYPES, getBufferResult(buffer, supplier, 0, sizeOfPages(10), NO_WAIT), bufferResult(0, createPage(0), createPage(1), createPage(2))); // 3 pages are moved to the client buffer, but not acknowledged yet - assertEquals(supplier.getBufferedPages(), 0); + assertThat(supplier.getBufferedPages()).isEqualTo(0); assertBufferInfo(buffer, 3, 0); // acknowledge first three pages in the buffer ListenableFuture pendingRead = buffer.getPages(3, sizeOfPages(1)); // pages now acknowledged - assertEquals(supplier.getBufferedPages(), 0); + assertThat(supplier.getBufferedPages()).isEqualTo(0); assertBufferInfo(buffer, 0, 3); - assertFalse(pendingRead.isDone()); + assertThat(pendingRead.isDone()).isFalse(); // add 3 more pages for (int i = 3; i < 6; i++) { supplier.addPage(createPage(i)); } - assertEquals(supplier.getBufferedPages(), 3); + assertThat(supplier.getBufferedPages()).isEqualTo(3); // notify the buffer that there is more data, and verify previous read completed buffer.loadPagesIfNecessary(supplier); assertBufferResultEquals(TYPES, getFuture(pendingRead, NO_WAIT), bufferResult(3, createPage(3))); // 1 page wad moved to the client buffer, but not acknowledged yet - assertEquals(supplier.getBufferedPages(), 2); + assertThat(supplier.getBufferedPages()).isEqualTo(2); assertBufferInfo(buffer, 1, 3); // set no more pages supplier.setNoMorePages(); // state should not change - assertEquals(supplier.getBufferedPages(), 2); + assertThat(supplier.getBufferedPages()).isEqualTo(2); assertBufferInfo(buffer, 1, 3); // remove a page assertBufferResultEquals(TYPES, getBufferResult(buffer, supplier, 4, sizeOfPages(1), NO_WAIT), bufferResult(4, createPage(4))); assertBufferInfo(buffer, 1, 4); - assertEquals(supplier.getBufferedPages(), 1); + assertThat(supplier.getBufferedPages()).isEqualTo(1); // remove last pages from, should not be finished assertBufferResultEquals(TYPES, getBufferResult(buffer, supplier, 5, sizeOfPages(30), NO_WAIT), bufferResult(5, createPage(5))); assertBufferInfo(buffer, 1, 5); - assertEquals(supplier.getBufferedPages(), 0); + assertThat(supplier.getBufferedPages()).isEqualTo(0); // acknowledge all pages from the buffer, should return a finished buffer result assertBufferResultEquals(TYPES, getBufferResult(buffer, supplier, 6, sizeOfPages(10), NO_WAIT), emptyResults(TASK_INSTANCE_ID, 6, true)); assertBufferInfo(buffer, 0, 6); - assertEquals(supplier.getBufferedPages(), 0); + assertThat(supplier.getBufferedPages()).isEqualTo(0); // buffer is not destroyed until explicitly destroyed buffer.destroy(); @@ -258,7 +256,7 @@ public void testNoMorePagesFreesReader() ListenableFuture future = buffer.getPages(0, sizeOfPages(10)); // verify we are waiting for a page - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // add one item addPage(buffer, createPage(0)); @@ -268,7 +266,7 @@ public void testNoMorePagesFreesReader() // attempt to get another page, and verify we are blocked future = buffer.getPages(1, sizeOfPages(10)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // finish the buffer buffer.setNoMorePages(); @@ -287,18 +285,18 @@ public void testDestroyFreesReader() ListenableFuture future = buffer.getPages(0, sizeOfPages(10)); // verify we are waiting for a page - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // add one item addPage(buffer, createPage(0)); - assertTrue(future.isDone()); + assertThat(future.isDone()).isTrue(); // verify we got one page assertBufferResultEquals(TYPES, getFuture(future, NO_WAIT), bufferResult(0, createPage(0))); // attempt to get another page, and verify we are blocked future = buffer.getPages(1, sizeOfPages(10)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // destroy the buffer buffer.destroy(); @@ -339,22 +337,22 @@ public void testReferenceCount() // add 2 pages and verify they are referenced addPage(buffer, createPage(0), onPagesReleased); addPage(buffer, createPage(1), onPagesReleased); - assertEquals(releasedPages.get(), 0); + assertThat(releasedPages.get()).isEqualTo(0); assertBufferInfo(buffer, 2, 0); // read one page assertBufferResultEquals(TYPES, getBufferResult(buffer, 0, sizeOfPages(0), NO_WAIT), bufferResult(0, createPage(0))); - assertEquals(releasedPages.get(), 0); + assertThat(releasedPages.get()).isEqualTo(0); assertBufferInfo(buffer, 2, 0); // acknowledge first page assertBufferResultEquals(TYPES, getBufferResult(buffer, 1, sizeOfPages(1), NO_WAIT), bufferResult(1, createPage(1))); - assertEquals(releasedPages.get(), 1); + assertThat(releasedPages.get()).isEqualTo(1); assertBufferInfo(buffer, 1, 1); // destroy the buffer buffer.destroy(); - assertEquals(releasedPages.get(), 2); + assertThat(releasedPages.get()).isEqualTo(2); assertBufferDestroyed(buffer, 1); } @@ -368,10 +366,12 @@ public void testProcessReadLockHolderAssertionsFireInTest() addPage(buffer, createPage(0)); } fail("Expected AssertionError to be thrown, are assertions enabled in your testing environment?"); - assertTrue(getFuture(pendingRead, NO_WAIT).isEmpty(), "Code should not reach here"); + assertThat(getFuture(pendingRead, NO_WAIT).isEmpty()) + .describedAs("Code should not reach here") + .isTrue(); } catch (AssertionError ae) { - assertEquals(ae.getMessage(), "Cannot process pending read while holding a lock on this"); + assertThat(ae.getMessage()).isEqualTo("Cannot process pending read while holding a lock on this"); } finally { buffer.destroy(); @@ -390,10 +390,12 @@ public void testGetPagesWithSupplierLockHolderAssertionsFireInTest() result = buffer.getPages(0, sizeOfPages(1), Optional.of(supplier)); } fail("Expected AssertionError to be thrown, are assertions enabled in your testing environment?"); - assertTrue(getFuture(result, NO_WAIT).isEmpty(), "Code should not reach here"); + assertThat(getFuture(result, NO_WAIT).isEmpty()) + .describedAs("Code should not reach here") + .isTrue(); } catch (AssertionError ae) { - assertEquals(ae.getMessage(), "Cannot load pages while holding a lock on this"); + assertThat(ae.getMessage()).isEqualTo("Cannot load pages while holding a lock on this"); } finally { buffer.destroy(); @@ -436,18 +438,16 @@ private static void assertBufferInfo( int bufferedPages, int pagesSent) { - assertEquals( - buffer.getInfo(), - new PipelinedBufferInfo( - BUFFER_ID, - // every page has one row, - bufferedPages + pagesSent, - bufferedPages + pagesSent, - bufferedPages, - sizeOfPages(bufferedPages).toBytes(), - pagesSent, - false)); - assertFalse(buffer.isDestroyed()); + assertThat(buffer.getInfo()).isEqualTo(new PipelinedBufferInfo( + BUFFER_ID, + // every page has one row, + bufferedPages + pagesSent, + bufferedPages + pagesSent, + bufferedPages, + sizeOfPages(bufferedPages).toBytes(), + pagesSent, + false)); + assertThat(buffer.isDestroyed()).isFalse(); } private static BufferResult bufferResult(long token, Page firstPage, Page... otherPages) @@ -460,10 +460,10 @@ private static BufferResult bufferResult(long token, Page firstPage, Page... oth private static void assertBufferDestroyed(ClientBuffer buffer, int pagesSent) { PipelinedBufferInfo bufferInfo = buffer.getInfo(); - assertEquals(bufferInfo.getBufferedPages(), 0); - assertEquals(bufferInfo.getPagesSent(), pagesSent); - assertTrue(bufferInfo.isFinished()); - assertTrue(buffer.isDestroyed()); + assertThat(bufferInfo.getBufferedPages()).isEqualTo(0); + assertThat(bufferInfo.getPagesSent()).isEqualTo(pagesSent); + assertThat(bufferInfo.isFinished()).isTrue(); + assertThat(buffer.isDestroyed()).isTrue(); } @ThreadSafe diff --git a/core/trino-main/src/test/java/io/trino/execution/buffer/TestPageCodecMarker.java b/core/trino-main/src/test/java/io/trino/execution/buffer/TestPageCodecMarker.java index 94e3cc148cb8..4bf702b33e36 100644 --- a/core/trino-main/src/test/java/io/trino/execution/buffer/TestPageCodecMarker.java +++ b/core/trino-main/src/test/java/io/trino/execution/buffer/TestPageCodecMarker.java @@ -17,9 +17,7 @@ import static io.trino.execution.buffer.PageCodecMarker.COMPRESSED; import static io.trino.execution.buffer.PageCodecMarker.ENCRYPTED; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestPageCodecMarker { @@ -31,50 +29,50 @@ public void testCompressionAndEncryptionMarkers() byte compressedAndEncrypted = ENCRYPTED.set(COMPRESSED.set(PageCodecMarker.none())); // Not set when no markers present - assertFalse(COMPRESSED.isSet(PageCodecMarker.none())); - assertFalse(ENCRYPTED.isSet(PageCodecMarker.none())); + assertThat(COMPRESSED.isSet(PageCodecMarker.none())).isFalse(); + assertThat(ENCRYPTED.isSet(PageCodecMarker.none())).isFalse(); // Setting Markers - assertTrue(COMPRESSED.isSet(compressed)); - assertTrue(COMPRESSED.isSet(compressedAndEncrypted)); - assertTrue(ENCRYPTED.isSet(encrypted)); - assertTrue(ENCRYPTED.isSet(compressedAndEncrypted)); + assertThat(COMPRESSED.isSet(compressed)).isTrue(); + assertThat(COMPRESSED.isSet(compressedAndEncrypted)).isTrue(); + assertThat(ENCRYPTED.isSet(encrypted)).isTrue(); + assertThat(ENCRYPTED.isSet(compressedAndEncrypted)).isTrue(); // Unsetting Markers - assertEquals(COMPRESSED.unset(compressed), PageCodecMarker.none()); - assertEquals(ENCRYPTED.unset(encrypted), PageCodecMarker.none()); - assertFalse(COMPRESSED.isSet(COMPRESSED.unset(compressedAndEncrypted))); - assertFalse(ENCRYPTED.isSet(ENCRYPTED.unset(compressedAndEncrypted))); + assertThat(COMPRESSED.unset(compressed)).isEqualTo(PageCodecMarker.none()); + assertThat(ENCRYPTED.unset(encrypted)).isEqualTo(PageCodecMarker.none()); + assertThat(COMPRESSED.isSet(COMPRESSED.unset(compressedAndEncrypted))).isFalse(); + assertThat(ENCRYPTED.isSet(ENCRYPTED.unset(compressedAndEncrypted))).isFalse(); // Summary String - assertEquals(PageCodecMarker.toSummaryString(PageCodecMarker.none()), "NONE"); - assertEquals(PageCodecMarker.toSummaryString(encrypted), "ENCRYPTED"); - assertEquals(PageCodecMarker.toSummaryString(compressed), "COMPRESSED"); - assertEquals(PageCodecMarker.toSummaryString(compressedAndEncrypted), "COMPRESSED, ENCRYPTED"); + assertThat(PageCodecMarker.toSummaryString(PageCodecMarker.none())).isEqualTo("NONE"); + assertThat(PageCodecMarker.toSummaryString(encrypted)).isEqualTo("ENCRYPTED"); + assertThat(PageCodecMarker.toSummaryString(compressed)).isEqualTo("COMPRESSED"); + assertThat(PageCodecMarker.toSummaryString(compressedAndEncrypted)).isEqualTo("COMPRESSED, ENCRYPTED"); } @Test public void testIsSet() { - assertEquals((byte) 0, PageCodecMarker.none()); + assertThat((byte) 0).isEqualTo(PageCodecMarker.none()); PageCodecMarker.MarkerSet markerSet = PageCodecMarker.MarkerSet.empty(); for (PageCodecMarker marker : PageCodecMarker.values()) { - assertFalse(marker.isSet(PageCodecMarker.none())); - assertFalse(markerSet.contains(marker)); + assertThat(marker.isSet(PageCodecMarker.none())).isFalse(); + assertThat(markerSet.contains(marker)).isFalse(); markerSet.add(marker); - assertTrue(markerSet.contains(marker)); - assertTrue(marker.isSet(marker.set(PageCodecMarker.none()))); + assertThat(markerSet.contains(marker)).isTrue(); + assertThat(marker.isSet(marker.set(PageCodecMarker.none()))).isTrue(); markerSet.remove(marker); - assertFalse(markerSet.contains(marker)); - assertFalse(marker.isSet(marker.unset(marker.set(PageCodecMarker.none())))); + assertThat(markerSet.contains(marker)).isFalse(); + assertThat(marker.isSet(marker.unset(marker.set(PageCodecMarker.none())))).isFalse(); for (PageCodecMarker other : PageCodecMarker.values()) { - assertEquals(other == marker, PageCodecMarker.MarkerSet.of(marker).contains(other)); - assertEquals(other == marker, other.isSet(marker.set(PageCodecMarker.none()))); + assertThat(other == marker).isEqualTo(PageCodecMarker.MarkerSet.of(marker).contains(other)); + assertThat(other == marker).isEqualTo(other.isSet(marker.set(PageCodecMarker.none()))); } } } @@ -95,20 +93,20 @@ public void testMarkerSetEquivalenceToByteMask() public void testSummaryString() { byte allMarkers = PageCodecMarker.none(); - assertEquals(PageCodecMarker.toSummaryString(PageCodecMarker.none()), "NONE"); + assertThat(PageCodecMarker.toSummaryString(PageCodecMarker.none())).isEqualTo("NONE"); for (PageCodecMarker marker : PageCodecMarker.values()) { - assertEquals(PageCodecMarker.toSummaryString(marker.set(PageCodecMarker.none())), marker.name()); - assertTrue(PageCodecMarker.MarkerSet.of(marker).toString().contains(marker.name())); + assertThat(PageCodecMarker.toSummaryString(marker.set(PageCodecMarker.none()))).isEqualTo(marker.name()); + assertThat(PageCodecMarker.MarkerSet.of(marker).toString().contains(marker.name())).isTrue(); allMarkers = marker.set(allMarkers); } PageCodecMarker.MarkerSet allInSet = PageCodecMarker.MarkerSet.fromByteValue(allMarkers); String allMarkersSummary = PageCodecMarker.toSummaryString(allMarkers); - assertTrue(allInSet.toString().contains(allMarkersSummary)); + assertThat(allInSet.toString().contains(allMarkersSummary)).isTrue(); for (PageCodecMarker marker : PageCodecMarker.values()) { - assertTrue(allMarkersSummary.contains(marker.name())); - assertTrue(allInSet.contains(marker)); + assertThat(allMarkersSummary.contains(marker.name())).isTrue(); + assertThat(allInSet.contains(marker)).isTrue(); } } } diff --git a/core/trino-main/src/test/java/io/trino/execution/buffer/TestPagesSerde.java b/core/trino-main/src/test/java/io/trino/execution/buffer/TestPagesSerde.java index 676c46f4b1b2..27186d5ddd73 100644 --- a/core/trino-main/src/test/java/io/trino/execution/buffer/TestPagesSerde.java +++ b/core/trino-main/src/test/java/io/trino/execution/buffer/TestPagesSerde.java @@ -55,8 +55,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -188,20 +186,20 @@ public void testBigintSerializedSize() // empty page Page page = new Page(builder.build()); int pageSize = serializedSize(ImmutableList.of(BIGINT), page); - assertEquals(pageSize, 40); + assertThat(pageSize).isEqualTo(40); // page with one value BIGINT.writeLong(builder, 123); pageSize = 35; // Now we have moved to the normal block implementation so the page size overhead is 35 page = new Page(builder.build()); int firstValueSize = serializedSize(ImmutableList.of(BIGINT), page) - pageSize; - assertEquals(firstValueSize, 9); // value size + value overhead + assertThat(firstValueSize).isEqualTo(9); // value size + value overhead // page with two values BIGINT.writeLong(builder, 456); page = new Page(builder.build()); int secondValueSize = serializedSize(ImmutableList.of(BIGINT), page) - (pageSize + firstValueSize); - assertEquals(secondValueSize, 8); // value size (value overhead is shared with previous value) + assertThat(secondValueSize).isEqualTo(8); // value size (value overhead is shared with previous value) } @Test @@ -212,20 +210,20 @@ public void testVarcharSerializedSize() // empty page Page page = new Page(builder.build()); int pageSize = serializedSize(ImmutableList.of(VARCHAR), page); - assertEquals(pageSize, 48); + assertThat(pageSize).isEqualTo(48); // page with one value VARCHAR.writeString(builder, "alice"); pageSize = 44; // Now we have moved to the normal block implementation so the page size overhead is 44 page = new Page(builder.build()); int firstValueSize = serializedSize(ImmutableList.of(VARCHAR), page) - pageSize; - assertEquals(firstValueSize, 8 + 5); // length + nonNullsCount + "alice" + assertThat(firstValueSize).isEqualTo(8 + 5); // length + nonNullsCount + "alice" // page with two values VARCHAR.writeString(builder, "bob"); page = new Page(builder.build()); int secondValueSize = serializedSize(ImmutableList.of(VARCHAR), page) - (pageSize + firstValueSize); - assertEquals(secondValueSize, 4 + 3); // length + "bob" (null shared with first entry) + assertThat(secondValueSize).isEqualTo(4 + 3); // length + "bob" (null shared with first entry) } private int serializedSize(List types, Page expectedPage) @@ -242,9 +240,9 @@ private int serializedSize(List types, Page expectedPage) assertPageEquals(types, pageIterator.next(), expectedPage); } else { - assertEquals(expectedPage.getPositionCount(), 0); + assertThat(expectedPage.getPositionCount()).isEqualTo(0); } - assertFalse(pageIterator.hasNext()); + assertThat(pageIterator.hasNext()).isFalse(); return slice.length(); } @@ -278,7 +276,7 @@ private void testDeserializationWithRollover(boolean encryptionEnabled, boolean Page page = createTestPage(numberOfEntries); Slice serialized = serializer.serialize(page); Page deserialized = deserializer.deserialize(serialized); - assertEquals(deserialized.getChannelCount(), 1); + assertThat(deserialized.getChannelCount()).isEqualTo(1); VariableWidthBlock expected = (VariableWidthBlock) page.getBlock(0); VariableWidthBlock actual = (VariableWidthBlock) deserialized.getBlock(0); diff --git a/core/trino-main/src/test/java/io/trino/execution/buffer/TestPartitionedOutputBuffer.java b/core/trino-main/src/test/java/io/trino/execution/buffer/TestPartitionedOutputBuffer.java index d360275c86a4..79ed9144abb8 100644 --- a/core/trino-main/src/test/java/io/trino/execution/buffer/TestPartitionedOutputBuffer.java +++ b/core/trino-main/src/test/java/io/trino/execution/buffer/TestPartitionedOutputBuffer.java @@ -59,12 +59,10 @@ import static io.trino.memory.context.AggregatedMemoryContext.newSimpleAggregatedMemoryContext; import static io.trino.spi.type.BigintType.BIGINT; import static java.util.concurrent.Executors.newScheduledThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -151,7 +149,7 @@ public void testSimplePartitioned() // try to add one more page, which should block ListenableFuture future = enqueuePage(buffer, createPage(13), firstPartition); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); assertQueueState(buffer, FIRST, 11, 3); assertQueueState(buffer, SECOND, 10, 0); @@ -162,7 +160,7 @@ public void testSimplePartitioned() assertQueueState(buffer, SECOND, 10, 0); // we should still be blocked - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // read pages from second partition assertBufferResultEquals(TYPES, getBufferResult(buffer, SECOND, 0, sizeOfPages(10), NO_WAIT), bufferResult( @@ -201,7 +199,7 @@ public void testSimplePartitioned() // // finish the buffer - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); buffer.setNoMorePages(); assertQueueState(buffer, FIRST, 12, 4); assertQueueState(buffer, SECOND, 0, 10); @@ -209,12 +207,12 @@ public void testSimplePartitioned() assertQueueClosed(buffer, SECOND, 10); // not fully finished until all pages are consumed - assertEquals(buffer.getState(), FLUSHING); + assertThat(buffer.getState()).isEqualTo(FLUSHING); // remove a page, not finished assertBufferResultEquals(TYPES, getBufferResult(buffer, FIRST, 5, sizeOfPages(1), NO_WAIT), bufferResult(5, createPage(5))); assertQueueState(buffer, FIRST, 11, 5); - assertEquals(buffer.getState(), FLUSHING); + assertThat(buffer.getState()).isEqualTo(FLUSHING); // remove all remaining pages from first queue, should not be finished BufferResult x = getBufferResult(buffer, FIRST, 6, sizeOfPages(30), NO_WAIT); @@ -271,7 +269,7 @@ public void testAcknowledge() acknowledgeBufferResult(buffer, FIRST, 4); } catch (IllegalArgumentException e) { - assertEquals(e.getMessage(), "Invalid sequence id"); + assertThat(e.getMessage()).isEqualTo("Invalid sequence id"); } // fill the buffer @@ -330,7 +328,7 @@ public void testAddQueueAfterCreation() .withNoMoreBufferIds(), sizeOfPages(10)); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); assertThatThrownBy(() -> buffer.setOutputBuffers(PipelinedOutputBuffers.createInitial(PARTITIONED) .withBuffer(FIRST, 0) @@ -351,7 +349,7 @@ public void testAddAfterFinish() buffer.setNoMorePages(); addPage(buffer, createPage(0)); addPage(buffer, createPage(0)); - assertEquals(buffer.getInfo().getTotalPagesSent(), 0); + assertThat(buffer.getInfo().getTotalPagesSent()).isEqualTo(0); } @Test @@ -365,7 +363,7 @@ public void testAddAfterDestroy() buffer.destroy(); addPage(buffer, createPage(0)); addPage(buffer, createPage(0)); - assertEquals(buffer.getInfo().getTotalPagesSent(), 0); + assertThat(buffer.getInfo().getTotalPagesSent()).isEqualTo(0); } @Test @@ -409,7 +407,7 @@ public void testAcknowledgementFreesWriters() ListenableFuture future = enqueuePage(buffer, createPage(3), secondPartition); // we should be blocked - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); assertQueueState(buffer, FIRST, 2, 0); assertQueueState(buffer, SECOND, 1, 0); @@ -481,24 +479,24 @@ public void testAbortFreesReader() .withBuffer(FIRST, 0) .withNoMoreBufferIds(), sizeOfPages(5)); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); // attempt to get a page ListenableFuture future = buffer.get(FIRST, 0, sizeOfPages(10)); // verify we are waiting for a page - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // add one item addPage(buffer, createPage(0)); - assertTrue(future.isDone()); + assertThat(future.isDone()).isTrue(); // verify we got one page assertBufferResultEquals(TYPES, getFuture(future, NO_WAIT), bufferResult(0, createPage(0))); // attempt to get another page, and verify we are blocked future = buffer.get(FIRST, 1, sizeOfPages(10)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // destroy the buffer buffer.destroy(FIRST); @@ -519,13 +517,13 @@ public void testFinishFreesReader() .withBuffer(FIRST, 0) .withNoMoreBufferIds(), sizeOfPages(5)); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); // attempt to get a page ListenableFuture future = buffer.get(FIRST, 0, sizeOfPages(10)); // verify we are waiting for a page - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // add one item addPage(buffer, createPage(0)); @@ -535,7 +533,7 @@ public void testFinishFreesReader() // attempt to get another page, and verify we are blocked future = buffer.get(FIRST, 1, sizeOfPages(10)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // finish the buffer buffer.setNoMorePages(); @@ -553,7 +551,7 @@ public void testFinishFreesWriter() .withBuffer(FIRST, 0) .withNoMoreBufferIds(), sizeOfPages(5)); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); // fill the buffer for (int i = 0; i < 5; i++) { @@ -569,12 +567,12 @@ public void testFinishFreesWriter() buffer.get(FIRST, 1, sizeOfPages(1)).cancel(true); // verify we are still blocked because the buffer is full - assertFalse(firstEnqueuePage.isDone()); - assertFalse(secondEnqueuePage.isDone()); + assertThat(firstEnqueuePage.isDone()).isFalse(); + assertThat(secondEnqueuePage.isDone()).isFalse(); // finish the query buffer.setNoMorePages(); - assertEquals(buffer.getState(), FLUSHING); + assertThat(buffer.getState()).isEqualTo(FLUSHING); // verify futures are complete assertFutureIsDone(firstEnqueuePage); @@ -599,13 +597,13 @@ public void testDestroyFreesReader() .withBuffer(FIRST, 0) .withNoMoreBufferIds(), sizeOfPages(5)); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); // attempt to get a page ListenableFuture future = buffer.get(FIRST, 0, sizeOfPages(10)); // verify we are waiting for a page - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // add one page addPage(buffer, createPage(0)); @@ -615,7 +613,7 @@ public void testDestroyFreesReader() // attempt to get another page, and verify we are blocked future = buffer.get(FIRST, 1, sizeOfPages(10)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // destroy the buffer buffer.destroy(); @@ -633,7 +631,7 @@ public void testDestroyFreesWriter() .withBuffer(FIRST, 0) .withNoMoreBufferIds(), sizeOfPages(5)); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); // fill the buffer for (int i = 0; i < 5; i++) { @@ -649,8 +647,8 @@ public void testDestroyFreesWriter() buffer.get(FIRST, 1, sizeOfPages(1)).cancel(true); // verify we are still blocked because the buffer is full - assertFalse(firstEnqueuePage.isDone()); - assertFalse(secondEnqueuePage.isDone()); + assertThat(firstEnqueuePage.isDone()).isFalse(); + assertThat(secondEnqueuePage.isDone()).isFalse(); // destroy the buffer (i.e., cancel the query) buffer.destroy(); @@ -669,13 +667,13 @@ public void testFailDoesNotFreeReader() .withBuffer(FIRST, 0) .withNoMoreBufferIds(), sizeOfPages(5)); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); // attempt to get a page ListenableFuture future = buffer.get(FIRST, 0, sizeOfPages(10)); // verify we are waiting for a page - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // add one page addPage(buffer, createPage(0)); @@ -685,17 +683,17 @@ public void testFailDoesNotFreeReader() // attempt to get another page, and verify we are blocked future = buffer.get(FIRST, 1, sizeOfPages(10)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // abort the buffer buffer.abort(); // future should have not finished - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); // attempt to get another page, and verify we are blocked future = buffer.get(FIRST, 1, sizeOfPages(10)); - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); } @Test @@ -706,7 +704,7 @@ public void testFailFreesWriter() .withBuffer(FIRST, 0) .withNoMoreBufferIds(), sizeOfPages(5)); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); // fill the buffer for (int i = 0; i < 5; i++) { @@ -722,12 +720,12 @@ public void testFailFreesWriter() buffer.get(FIRST, 1, sizeOfPages(1)).cancel(true); // verify we are still blocked because the buffer is full - assertFalse(firstEnqueuePage.isDone()); - assertFalse(secondEnqueuePage.isDone()); + assertThat(firstEnqueuePage.isDone()).isFalse(); + assertThat(secondEnqueuePage.isDone()).isFalse(); // abort the buffer (i.e., fail the query) buffer.abort(); - assertEquals(buffer.getState(), ABORTED); + assertThat(buffer.getState()).isEqualTo(ABORTED); // verify the futures are completed assertFutureIsDone(firstEnqueuePage); @@ -743,7 +741,7 @@ public void testBufferCompletion() .withNoMoreBufferIds(), sizeOfPages(5)); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); // fill the buffer List pages = new ArrayList<>(); @@ -759,13 +757,13 @@ public void testBufferCompletion() assertBufferResultEquals(TYPES, getBufferResult(buffer, FIRST, 0, sizeOfPages(5), MAX_WAIT), createBufferResult(TASK_INSTANCE_ID, 0, pages)); // there are no more pages and no more buffers, but buffer is not finished because it didn't receive an acknowledgement yet - assertEquals(buffer.getState(), FLUSHING); + assertThat(buffer.getState()).isEqualTo(FLUSHING); // ask the buffer to finish buffer.destroy(FIRST); // verify that the buffer is finished - assertEquals(buffer.getState(), FINISHED); + assertThat(buffer.getState()).isEqualTo(FINISHED); } @Test @@ -789,11 +787,11 @@ public void testBufferFinishesWhenClientBuffersDestroyed() // and if we destroy all the buffers it should destroy itself // and move to the FINISHED state buffer.destroy(FIRST); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); buffer.destroy(SECOND); - assertEquals(buffer.getState(), NO_MORE_BUFFERS); + assertThat(buffer.getState()).isEqualTo(NO_MORE_BUFFERS); buffer.destroy(THIRD); - assertEquals(buffer.getState(), FINISHED); + assertThat(buffer.getState()).isEqualTo(FINISHED); } @Test @@ -808,7 +806,7 @@ public void testBufferPeakMemoryUsage() long serializePageSize = serializePage(page).getRetainedSize(); for (int i = 0; i < 5; i++) { addPage(buffer, page, 0); - assertEquals(buffer.getPeakMemoryUsage(), (i + 1) * serializePageSize); + assertThat(buffer.getPeakMemoryUsage()).isEqualTo((i + 1) * serializePageSize); } } @@ -824,12 +822,12 @@ public void testForceFreeMemory() addPage(buffer, createPage(1), 0); } OutputBufferMemoryManager memoryManager = buffer.getMemoryManager(); - assertTrue(memoryManager.getBufferedBytes() > 0); + assertThat(memoryManager.getBufferedBytes() > 0).isTrue(); buffer.forceFreeMemory(); - assertEquals(memoryManager.getBufferedBytes(), 0); + assertThat(memoryManager.getBufferedBytes()).isEqualTo(0); // adding a page after forceFreeMemory() should be NOOP addPage(buffer, createPage(1)); - assertEquals(memoryManager.getBufferedBytes(), 0); + assertThat(memoryManager.getBufferedBytes()).isEqualTo(0); } private PartitionedOutputBuffer createPartitionedBuffer(PipelinedOutputBuffers buffers, DataSize dataSize) diff --git a/core/trino-main/src/test/java/io/trino/execution/buffer/TestSpoolingExchangeOutputBuffer.java b/core/trino-main/src/test/java/io/trino/execution/buffer/TestSpoolingExchangeOutputBuffer.java index a453146ce3fe..98d591f54def 100644 --- a/core/trino-main/src/test/java/io/trino/execution/buffer/TestSpoolingExchangeOutputBuffer.java +++ b/core/trino-main/src/test/java/io/trino/execution/buffer/TestSpoolingExchangeOutputBuffer.java @@ -44,9 +44,7 @@ import static io.trino.execution.buffer.BufferState.NO_MORE_BUFFERS; import static io.trino.spi.type.VarcharType.VARCHAR; import static java.util.Objects.requireNonNull; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestSpoolingExchangeOutputBuffer { @@ -55,7 +53,7 @@ public void testIsFull() { TestingExchangeSink exchangeSink = new TestingExchangeSink(); OutputBuffer outputBuffer = createSpoolingExchangeOutputBuffer(exchangeSink, 2); - assertEquals(outputBuffer.getState(), NO_MORE_BUFFERS); + assertThat(outputBuffer.getState()).isEqualTo(NO_MORE_BUFFERS); assertNotBlocked(outputBuffer.isFull()); CompletableFuture blocked = new CompletableFuture<>(); @@ -76,15 +74,15 @@ public void testFinishSuccess() exchangeSink.setFinish(finish); OutputBuffer outputBuffer = createSpoolingExchangeOutputBuffer(exchangeSink, 2); - assertEquals(outputBuffer.getState(), NO_MORE_BUFFERS); + assertThat(outputBuffer.getState()).isEqualTo(NO_MORE_BUFFERS); outputBuffer.setNoMorePages(); // call it for the second time to verify that the buffer handles it correctly outputBuffer.setNoMorePages(); - assertEquals(outputBuffer.getState(), FLUSHING); + assertThat(outputBuffer.getState()).isEqualTo(FLUSHING); finish.complete(null); - assertEquals(outputBuffer.getState(), FINISHED); + assertThat(outputBuffer.getState()).isEqualTo(FINISHED); } @Test @@ -95,17 +93,17 @@ public void testFinishFailure() exchangeSink.setFinish(finish); OutputBuffer outputBuffer = createSpoolingExchangeOutputBuffer(exchangeSink, 2); - assertEquals(outputBuffer.getState(), NO_MORE_BUFFERS); + assertThat(outputBuffer.getState()).isEqualTo(NO_MORE_BUFFERS); outputBuffer.setNoMorePages(); // call it for the second time to verify that the buffer handles it correctly outputBuffer.setNoMorePages(); - assertEquals(outputBuffer.getState(), FLUSHING); + assertThat(outputBuffer.getState()).isEqualTo(FLUSHING); RuntimeException failure = new RuntimeException("failure"); finish.completeExceptionally(failure); - assertEquals(outputBuffer.getState(), FAILED); - assertEquals(outputBuffer.getFailureCause(), Optional.of(failure)); + assertThat(outputBuffer.getState()).isEqualTo(FAILED); + assertThat(outputBuffer.getFailureCause()).isEqualTo(Optional.of(failure)); } @Test @@ -116,18 +114,18 @@ public void testDestroyAfterFinishCompletion() exchangeSink.setFinish(finish); OutputBuffer outputBuffer = createSpoolingExchangeOutputBuffer(exchangeSink, 2); - assertEquals(outputBuffer.getState(), NO_MORE_BUFFERS); + assertThat(outputBuffer.getState()).isEqualTo(NO_MORE_BUFFERS); outputBuffer.setNoMorePages(); // call it for the second time to verify that the buffer handles it correctly outputBuffer.setNoMorePages(); - assertEquals(outputBuffer.getState(), FLUSHING); + assertThat(outputBuffer.getState()).isEqualTo(FLUSHING); finish.complete(null); - assertEquals(outputBuffer.getState(), FINISHED); + assertThat(outputBuffer.getState()).isEqualTo(FINISHED); outputBuffer.destroy(); - assertEquals(outputBuffer.getState(), FINISHED); + assertThat(outputBuffer.getState()).isEqualTo(FINISHED); } @Test @@ -138,16 +136,16 @@ public void testDestroyBeforeFinishCompletion() exchangeSink.setFinish(finish); OutputBuffer outputBuffer = createSpoolingExchangeOutputBuffer(exchangeSink, 2); - assertEquals(outputBuffer.getState(), NO_MORE_BUFFERS); + assertThat(outputBuffer.getState()).isEqualTo(NO_MORE_BUFFERS); outputBuffer.setNoMorePages(); - assertEquals(outputBuffer.getState(), FLUSHING); + assertThat(outputBuffer.getState()).isEqualTo(FLUSHING); outputBuffer.destroy(); - assertEquals(outputBuffer.getState(), ABORTED); + assertThat(outputBuffer.getState()).isEqualTo(ABORTED); finish.complete(null); - assertEquals(outputBuffer.getState(), ABORTED); + assertThat(outputBuffer.getState()).isEqualTo(ABORTED); } @Test @@ -156,12 +154,12 @@ public void testAbortBeforeNoMorePages() TestingExchangeSink exchangeSink = new TestingExchangeSink(); OutputBuffer outputBuffer = createSpoolingExchangeOutputBuffer(exchangeSink, 2); - assertEquals(outputBuffer.getState(), NO_MORE_BUFFERS); + assertThat(outputBuffer.getState()).isEqualTo(NO_MORE_BUFFERS); outputBuffer.abort(); - assertEquals(outputBuffer.getState(), ABORTED); + assertThat(outputBuffer.getState()).isEqualTo(ABORTED); outputBuffer.setNoMorePages(); - assertEquals(outputBuffer.getState(), ABORTED); + assertThat(outputBuffer.getState()).isEqualTo(ABORTED); } @Test @@ -174,20 +172,20 @@ public void testAbortBeforeFinishCompletion() exchangeSink.setAbort(abort); OutputBuffer outputBuffer = createSpoolingExchangeOutputBuffer(exchangeSink, 2); - assertEquals(outputBuffer.getState(), NO_MORE_BUFFERS); + assertThat(outputBuffer.getState()).isEqualTo(NO_MORE_BUFFERS); outputBuffer.setNoMorePages(); // call it for the second time to verify that the buffer handles it correctly outputBuffer.setNoMorePages(); - assertEquals(outputBuffer.getState(), FLUSHING); + assertThat(outputBuffer.getState()).isEqualTo(FLUSHING); // if abort is called before finish completes it should abort the buffer outputBuffer.abort(); - assertEquals(outputBuffer.getState(), ABORTED); + assertThat(outputBuffer.getState()).isEqualTo(ABORTED); // abort failure shouldn't impact the buffer state abort.completeExceptionally(new RuntimeException("failure")); - assertEquals(outputBuffer.getState(), ABORTED); + assertThat(outputBuffer.getState()).isEqualTo(ABORTED); } @Test @@ -200,23 +198,23 @@ public void testAbortAfterFinishCompletion() exchangeSink.setAbort(abort); OutputBuffer outputBuffer = createSpoolingExchangeOutputBuffer(exchangeSink, 2); - assertEquals(outputBuffer.getState(), NO_MORE_BUFFERS); + assertThat(outputBuffer.getState()).isEqualTo(NO_MORE_BUFFERS); outputBuffer.setNoMorePages(); // call it for the second time to verify that the buffer handles it correctly outputBuffer.setNoMorePages(); - assertEquals(outputBuffer.getState(), FLUSHING); + assertThat(outputBuffer.getState()).isEqualTo(FLUSHING); finish.complete(null); - assertEquals(outputBuffer.getState(), FINISHED); + assertThat(outputBuffer.getState()).isEqualTo(FINISHED); // abort is no op outputBuffer.abort(); - assertEquals(outputBuffer.getState(), FINISHED); + assertThat(outputBuffer.getState()).isEqualTo(FINISHED); // abort success doesn't change the buffer state abort.complete(null); - assertEquals(outputBuffer.getState(), FINISHED); + assertThat(outputBuffer.getState()).isEqualTo(FINISHED); } @Test @@ -227,7 +225,7 @@ public void testEnqueueAfterFinish() exchangeSink.setFinish(finish); OutputBuffer outputBuffer = createSpoolingExchangeOutputBuffer(exchangeSink, 2); - assertEquals(outputBuffer.getState(), NO_MORE_BUFFERS); + assertThat(outputBuffer.getState()).isEqualTo(NO_MORE_BUFFERS); outputBuffer.enqueue(0, ImmutableList.of(createPage("page1"))); outputBuffer.enqueue(1, ImmutableList.of(createPage("page2"), createPage("page3"))); @@ -238,18 +236,18 @@ public void testEnqueueAfterFinish() .put(1, createPage("page3")) .build(); - assertEquals(exchangeSink.getDataBuffer(), expectedDataBufferState); + assertThat(exchangeSink.getDataBuffer()).isEqualTo(expectedDataBufferState); outputBuffer.setNoMorePages(); - assertEquals(outputBuffer.getState(), FLUSHING); + assertThat(outputBuffer.getState()).isEqualTo(FLUSHING); // the buffer is flushing, this page is expected to be rejected outputBuffer.enqueue(0, ImmutableList.of(createPage("page4"))); - assertEquals(exchangeSink.getDataBuffer(), expectedDataBufferState); + assertThat(exchangeSink.getDataBuffer()).isEqualTo(expectedDataBufferState); finish.complete(null); - assertEquals(outputBuffer.getState(), FINISHED); + assertThat(outputBuffer.getState()).isEqualTo(FINISHED); outputBuffer.enqueue(0, ImmutableList.of(createPage("page5"))); - assertEquals(exchangeSink.getDataBuffer(), expectedDataBufferState); + assertThat(exchangeSink.getDataBuffer()).isEqualTo(expectedDataBufferState); } @Test @@ -260,7 +258,7 @@ public void testEnqueueAfterAbort() exchangeSink.setAbort(abort); OutputBuffer outputBuffer = createSpoolingExchangeOutputBuffer(exchangeSink, 2); - assertEquals(outputBuffer.getState(), NO_MORE_BUFFERS); + assertThat(outputBuffer.getState()).isEqualTo(NO_MORE_BUFFERS); outputBuffer.enqueue(0, ImmutableList.of(createPage("page1"))); outputBuffer.enqueue(1, ImmutableList.of(createPage("page2"), createPage("page3"))); @@ -271,18 +269,18 @@ public void testEnqueueAfterAbort() .put(1, createPage("page3")) .build(); - assertEquals(exchangeSink.getDataBuffer(), expectedDataBufferState); + assertThat(exchangeSink.getDataBuffer()).isEqualTo(expectedDataBufferState); outputBuffer.abort(); - assertEquals(outputBuffer.getState(), ABORTED); + assertThat(outputBuffer.getState()).isEqualTo(ABORTED); // the buffer is flushing, this page is expected to be rejected outputBuffer.enqueue(0, ImmutableList.of(createPage("page4"))); - assertEquals(exchangeSink.getDataBuffer(), expectedDataBufferState); + assertThat(exchangeSink.getDataBuffer()).isEqualTo(expectedDataBufferState); abort.complete(null); - assertEquals(outputBuffer.getState(), ABORTED); + assertThat(outputBuffer.getState()).isEqualTo(ABORTED); outputBuffer.enqueue(0, ImmutableList.of(createPage("page5"))); - assertEquals(exchangeSink.getDataBuffer(), expectedDataBufferState); + assertThat(exchangeSink.getDataBuffer()).isEqualTo(expectedDataBufferState); } private static SpoolingExchangeOutputBuffer createSpoolingExchangeOutputBuffer(ExchangeSink exchangeSink, int outputPartitionCount) @@ -296,12 +294,12 @@ private static SpoolingExchangeOutputBuffer createSpoolingExchangeOutputBuffer(E private static void assertNotBlocked(ListenableFuture blocked) { - assertTrue(blocked.isDone()); + assertThat(blocked.isDone()).isTrue(); } private static void assertBlocked(ListenableFuture blocked) { - assertFalse(blocked.isDone()); + assertThat(blocked.isDone()).isFalse(); } private static Slice createPage(String value) @@ -371,8 +369,8 @@ public long getMemoryUsage() @Override public CompletableFuture finish() { - assertFalse(abortCalled); - assertFalse(finishCalled); + assertThat(abortCalled).isFalse(); + assertThat(finishCalled).isFalse(); finishCalled = true; return finish; } @@ -385,7 +383,7 @@ public void setFinish(CompletableFuture finish) @Override public CompletableFuture abort() { - assertFalse(abortCalled); + assertThat(abortCalled).isFalse(); abortCalled = true; return abort; } diff --git a/core/trino-main/src/test/java/io/trino/execution/buffer/TestSpoolingOutputStats.java b/core/trino-main/src/test/java/io/trino/execution/buffer/TestSpoolingOutputStats.java index 48f5d98b2af2..5382c3df8c42 100644 --- a/core/trino-main/src/test/java/io/trino/execution/buffer/TestSpoolingOutputStats.java +++ b/core/trino-main/src/test/java/io/trino/execution/buffer/TestSpoolingOutputStats.java @@ -17,7 +17,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.data.Percentage.withPercentage; -import static org.testng.Assert.assertEquals; public class TestSpoolingOutputStats { @@ -48,7 +47,7 @@ public void test() assertThat(spoolingOutputStats.getFinalSnapshot()).isPresent(); SpoolingOutputStats.Snapshot snapshot = spoolingOutputStats.getFinalSnapshot().orElseThrow(); - assertEquals(snapshot.getPartitionSizeInBytes(0), 1); + assertThat(snapshot.getPartitionSizeInBytes(0)).isEqualTo(1); for (int partition = 0; partition < numberOfPartitions; partition++) { assertThat(snapshot.getPartitionSizeInBytes(partition)).isCloseTo(expectedValues[partition], withPercentage(EXPECTED_PRECISION_LOSS_IN_PERCENTS)); diff --git a/core/trino-main/src/test/java/io/trino/execution/executor/timesharing/TestTimeSharingTaskExecutor.java b/core/trino-main/src/test/java/io/trino/execution/executor/timesharing/TestTimeSharingTaskExecutor.java index 02b62831d0e5..5c1a2ca06817 100644 --- a/core/trino-main/src/test/java/io/trino/execution/executor/timesharing/TestTimeSharingTaskExecutor.java +++ b/core/trino-main/src/test/java/io/trino/execution/executor/timesharing/TestTimeSharingTaskExecutor.java @@ -49,9 +49,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestTimeSharingTaskExecutor { @@ -80,26 +78,26 @@ public void testTasksComplete() ListenableFuture future1 = getOnlyElement(taskExecutor.enqueueSplits(taskHandle, true, ImmutableList.of(driver1))); TestingJob driver2 = new TestingJob(ticker, new Phaser(), beginPhase, verificationComplete, 10, 0); ListenableFuture future2 = getOnlyElement(taskExecutor.enqueueSplits(taskHandle, true, ImmutableList.of(driver2))); - assertEquals(driver1.getCompletedPhases(), 0); - assertEquals(driver2.getCompletedPhases(), 0); + assertThat(driver1.getCompletedPhases()).isEqualTo(0); + assertThat(driver2.getCompletedPhases()).isEqualTo(0); // verify worker have arrived but haven't processed yet beginPhase.arriveAndAwaitAdvance(); - assertEquals(driver1.getCompletedPhases(), 0); - assertEquals(driver2.getCompletedPhases(), 0); + assertThat(driver1.getCompletedPhases()).isEqualTo(0); + assertThat(driver2.getCompletedPhases()).isEqualTo(0); ticker.increment(60, SECONDS); - assertTrue(taskExecutor.getStuckSplitTaskIds(splitProcessingDurationThreshold, runningSplitInfo -> true).isEmpty()); - assertEquals(taskExecutor.getRunAwaySplitCount(), 0); + assertThat(taskExecutor.getStuckSplitTaskIds(splitProcessingDurationThreshold, runningSplitInfo -> true).isEmpty()).isTrue(); + assertThat(taskExecutor.getRunAwaySplitCount()).isEqualTo(0); ticker.increment(600, SECONDS); - assertEquals(taskExecutor.getRunAwaySplitCount(), 2); - assertEquals(taskExecutor.getStuckSplitTaskIds(splitProcessingDurationThreshold, runningSplitInfo -> true), ImmutableSet.of(taskId)); + assertThat(taskExecutor.getRunAwaySplitCount()).isEqualTo(2); + assertThat(taskExecutor.getStuckSplitTaskIds(splitProcessingDurationThreshold, runningSplitInfo -> true)).isEqualTo(ImmutableSet.of(taskId)); verificationComplete.arriveAndAwaitAdvance(); // advance one phase and verify beginPhase.arriveAndAwaitAdvance(); - assertEquals(driver1.getCompletedPhases(), 1); - assertEquals(driver2.getCompletedPhases(), 1); + assertThat(driver1.getCompletedPhases()).isEqualTo(1); + assertThat(driver2.getCompletedPhases()).isEqualTo(1); verificationComplete.arriveAndAwaitAdvance(); @@ -109,9 +107,9 @@ public void testTasksComplete() // advance one phase and verify beginPhase.arriveAndAwaitAdvance(); - assertEquals(driver1.getCompletedPhases(), 2); - assertEquals(driver2.getCompletedPhases(), 2); - assertEquals(driver3.getCompletedPhases(), 0); + assertThat(driver1.getCompletedPhases()).isEqualTo(2); + assertThat(driver2.getCompletedPhases()).isEqualTo(2); + assertThat(driver3.getCompletedPhases()).isEqualTo(0); verificationComplete.arriveAndAwaitAdvance(); // advance to the end of the first two task and verify @@ -119,11 +117,11 @@ public void testTasksComplete() for (int i = 0; i < 7; i++) { verificationComplete.arriveAndAwaitAdvance(); beginPhase.arriveAndAwaitAdvance(); - assertEquals(beginPhase.getPhase(), verificationComplete.getPhase() + 1); + assertThat(beginPhase.getPhase()).isEqualTo(verificationComplete.getPhase() + 1); } - assertEquals(driver1.getCompletedPhases(), 10); - assertEquals(driver2.getCompletedPhases(), 10); - assertEquals(driver3.getCompletedPhases(), 8); + assertThat(driver1.getCompletedPhases()).isEqualTo(10); + assertThat(driver2.getCompletedPhases()).isEqualTo(10); + assertThat(driver3.getCompletedPhases()).isEqualTo(8); future1.get(1, SECONDS); future2.get(1, SECONDS); verificationComplete.arriveAndAwaitAdvance(); @@ -132,24 +130,24 @@ public void testTasksComplete() beginPhase.arriveAndAwaitAdvance(); verificationComplete.arriveAndAwaitAdvance(); beginPhase.arriveAndAwaitAdvance(); - assertEquals(driver1.getCompletedPhases(), 10); - assertEquals(driver2.getCompletedPhases(), 10); - assertEquals(driver3.getCompletedPhases(), 10); + assertThat(driver1.getCompletedPhases()).isEqualTo(10); + assertThat(driver2.getCompletedPhases()).isEqualTo(10); + assertThat(driver3.getCompletedPhases()).isEqualTo(10); future3.get(1, SECONDS); verificationComplete.arriveAndAwaitAdvance(); - assertEquals(driver1.getFirstPhase(), 0); - assertEquals(driver2.getFirstPhase(), 0); - assertEquals(driver3.getFirstPhase(), 2); + assertThat(driver1.getFirstPhase()).isEqualTo(0); + assertThat(driver2.getFirstPhase()).isEqualTo(0); + assertThat(driver3.getFirstPhase()).isEqualTo(2); - assertEquals(driver1.getLastPhase(), 10); - assertEquals(driver2.getLastPhase(), 10); - assertEquals(driver3.getLastPhase(), 12); + assertThat(driver1.getLastPhase()).isEqualTo(10); + assertThat(driver2.getLastPhase()).isEqualTo(10); + assertThat(driver3.getLastPhase()).isEqualTo(12); // no splits remaining ticker.increment(610, SECONDS); - assertTrue(taskExecutor.getStuckSplitTaskIds(splitProcessingDurationThreshold, runningSplitInfo -> true).isEmpty()); - assertEquals(taskExecutor.getRunAwaySplitCount(), 0); + assertThat(taskExecutor.getStuckSplitTaskIds(splitProcessingDurationThreshold, runningSplitInfo -> true).isEmpty()).isTrue(); + assertThat(taskExecutor.getRunAwaySplitCount()).isEqualTo(0); } finally { taskExecutor.stop(); @@ -180,8 +178,8 @@ public void testQuantaFairness() endQuantaPhaser.arriveAndAwaitAdvance(); } - assertTrue(shortQuantaDriver.getCompletedPhases() >= 7 && shortQuantaDriver.getCompletedPhases() <= 8); - assertTrue(longQuantaDriver.getCompletedPhases() >= 3 && longQuantaDriver.getCompletedPhases() <= 4); + assertThat(shortQuantaDriver.getCompletedPhases() >= 7 && shortQuantaDriver.getCompletedPhases() <= 8).isTrue(); + assertThat(longQuantaDriver.getCompletedPhases() >= 3 && longQuantaDriver.getCompletedPhases() <= 4).isTrue(); endQuantaPhaser.arriveAndDeregister(); } @@ -218,7 +216,7 @@ public void testLevelMovement() globalPhaser.arriveAndAwaitAdvance(); } - assertEquals(testTaskHandle.getPriority().getLevel(), i + 1); + assertThat(testTaskHandle.getPriority().getLevel()).isEqualTo(i + 1); } globalPhaser.arriveAndDeregister(); @@ -328,11 +326,11 @@ public void testTaskHandle() // force enqueue a split taskExecutor.enqueueSplits(taskHandle, true, ImmutableList.of(driver1)); - assertEquals(taskHandle.getRunningLeafSplits(), 0); + assertThat(taskHandle.getRunningLeafSplits()).isEqualTo(0); // normal enqueue a split taskExecutor.enqueueSplits(taskHandle, false, ImmutableList.of(driver2)); - assertEquals(taskHandle.getRunningLeafSplits(), 1); + assertThat(taskHandle.getRunningLeafSplits()).isEqualTo(1); // let the split continue to run beginPhase.arriveAndDeregister(); @@ -353,13 +351,13 @@ public void testLevelContributionCap() for (int i = 0; i < (LEVEL_THRESHOLD_SECONDS.length - 1); i++) { long levelAdvanceTime = SECONDS.toNanos(LEVEL_THRESHOLD_SECONDS[i + 1] - LEVEL_THRESHOLD_SECONDS[i]); handle0.addScheduledNanos(levelAdvanceTime); - assertEquals(handle0.getPriority().getLevel(), i + 1); + assertThat(handle0.getPriority().getLevel()).isEqualTo(i + 1); handle1.addScheduledNanos(levelAdvanceTime); - assertEquals(handle1.getPriority().getLevel(), i + 1); + assertThat(handle1.getPriority().getLevel()).isEqualTo(i + 1); - assertEquals(splitQueue.getLevelScheduledTime(i), 2 * Math.min(levelAdvanceTime, LEVEL_CONTRIBUTION_CAP)); - assertEquals(splitQueue.getLevelScheduledTime(i + 1), 0); + assertThat(splitQueue.getLevelScheduledTime(i)).isEqualTo(2 * Math.min(levelAdvanceTime, LEVEL_CONTRIBUTION_CAP)); + assertThat(splitQueue.getLevelScheduledTime(i + 1)).isEqualTo(0); } } @@ -375,7 +373,7 @@ public void testUpdateLevelWithCap() for (int i = 0; i < (LEVEL_THRESHOLD_SECONDS.length - 1); i++) { long thisLevelTime = Math.min(SECONDS.toNanos(LEVEL_THRESHOLD_SECONDS[i + 1] - LEVEL_THRESHOLD_SECONDS[i]), cappedNanos); - assertEquals(splitQueue.getLevelScheduledTime(i), thisLevelTime); + assertThat(splitQueue.getLevelScheduledTime(i)).isEqualTo(thisLevelTime); cappedNanos -= thisLevelTime; } } @@ -522,27 +520,27 @@ public void testLeafSplitsSize() ticker.increment(0, TimeUnit.SECONDS); taskExecutor.enqueueSplits(testTaskHandle, false, ImmutableList.of(driver1, driver2)); - assertTrue(isNaN(taskExecutor.getLeafSplitsSize().getAllTime().getMax())); + assertThat(isNaN(taskExecutor.getLeafSplitsSize().getAllTime().getMax())).isTrue(); ticker.increment(1, TimeUnit.SECONDS); taskExecutor.enqueueSplits(testTaskHandle, false, ImmutableList.of(driver1)); - assertEquals(taskExecutor.getLeafSplitsSize().getAllTime().getMax(), 2.0); + assertThat(taskExecutor.getLeafSplitsSize().getAllTime().getMax()).isEqualTo(2.0); ticker.increment(1, TimeUnit.SECONDS); taskExecutor.enqueueSplits(testTaskHandle, true, ImmutableList.of(driver1)); - assertEquals(taskExecutor.getLeafSplitsSize().getAllTime().getMax(), 2.0); + assertThat(taskExecutor.getLeafSplitsSize().getAllTime().getMax()).isEqualTo(2.0); } private void assertSplitStates(int endIndex, TestingJob[] splits) { // assert that splits up to and including endIndex are all started for (int i = 0; i <= endIndex; i++) { - assertTrue(splits[i].isStarted()); + assertThat(splits[i].isStarted()).isTrue(); } // assert that splits starting from endIndex haven't started yet for (int i = endIndex + 1; i < splits.length; i++) { - assertFalse(splits[i].isStarted()); + assertThat(splits[i].isStarted()).isFalse(); } } diff --git a/core/trino-main/src/test/java/io/trino/execution/resourcegroups/TestResourceGroups.java b/core/trino-main/src/test/java/io/trino/execution/resourcegroups/TestResourceGroups.java index c669a9a79098..b818baed9ee1 100644 --- a/core/trino-main/src/test/java/io/trino/execution/resourcegroups/TestResourceGroups.java +++ b/core/trino-main/src/test/java/io/trino/execution/resourcegroups/TestResourceGroups.java @@ -53,8 +53,6 @@ import static io.trino.spi.resourcegroups.SchedulingPolicy.WEIGHTED_FAIR; import static java.util.Collections.reverse; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; public class TestResourceGroups { @@ -68,14 +66,14 @@ public void testQueueFull() root.setHardConcurrencyLimit(1); MockManagedQueryExecution query1 = new MockManagedQueryExecutionBuilder().build(); root.run(query1); - assertEquals(query1.getState(), RUNNING); + assertThat(query1.getState()).isEqualTo(RUNNING); MockManagedQueryExecution query2 = new MockManagedQueryExecutionBuilder().build(); root.run(query2); - assertEquals(query2.getState(), QUEUED); + assertThat(query2.getState()).isEqualTo(QUEUED); MockManagedQueryExecution query3 = new MockManagedQueryExecutionBuilder().build(); root.run(query3); - assertEquals(query3.getState(), FAILED); - assertEquals(query3.getThrowable().getMessage(), "Too many queued queries for \"root\""); + assertThat(query3.getState()).isEqualTo(FAILED); + assertThat(query3.getThrowable().getMessage()).isEqualTo("Too many queued queries for \"root\""); } @Test @@ -100,35 +98,35 @@ public void testFairEligibility() group3.setHardConcurrencyLimit(1); MockManagedQueryExecution query1a = new MockManagedQueryExecutionBuilder().build(); group1.run(query1a); - assertEquals(query1a.getState(), RUNNING); + assertThat(query1a.getState()).isEqualTo(RUNNING); MockManagedQueryExecution query1b = new MockManagedQueryExecutionBuilder().build(); group1.run(query1b); - assertEquals(query1b.getState(), QUEUED); + assertThat(query1b.getState()).isEqualTo(QUEUED); MockManagedQueryExecution query2a = new MockManagedQueryExecutionBuilder().build(); group2.run(query2a); - assertEquals(query2a.getState(), QUEUED); + assertThat(query2a.getState()).isEqualTo(QUEUED); MockManagedQueryExecution query2b = new MockManagedQueryExecutionBuilder().build(); group2.run(query2b); - assertEquals(query2b.getState(), QUEUED); + assertThat(query2b.getState()).isEqualTo(QUEUED); MockManagedQueryExecution query3a = new MockManagedQueryExecutionBuilder().build(); group3.run(query3a); - assertEquals(query3a.getState(), QUEUED); + assertThat(query3a.getState()).isEqualTo(QUEUED); query1a.complete(); // 2a and not 1b should have started, as group1 was not eligible to start a second query - assertEquals(query1b.getState(), QUEUED); - assertEquals(query2a.getState(), RUNNING); - assertEquals(query2b.getState(), QUEUED); - assertEquals(query3a.getState(), QUEUED); + assertThat(query1b.getState()).isEqualTo(QUEUED); + assertThat(query2a.getState()).isEqualTo(RUNNING); + assertThat(query2b.getState()).isEqualTo(QUEUED); + assertThat(query3a.getState()).isEqualTo(QUEUED); query2a.complete(); - assertEquals(query3a.getState(), RUNNING); - assertEquals(query2b.getState(), QUEUED); - assertEquals(query1b.getState(), QUEUED); + assertThat(query3a.getState()).isEqualTo(RUNNING); + assertThat(query2b.getState()).isEqualTo(QUEUED); + assertThat(query1b.getState()).isEqualTo(QUEUED); query3a.complete(); - assertEquals(query1b.getState(), RUNNING); - assertEquals(query2b.getState(), QUEUED); + assertThat(query1b.getState()).isEqualTo(RUNNING); + assertThat(query2b.getState()).isEqualTo(QUEUED); } @Test @@ -148,29 +146,29 @@ public void testSetSchedulingPolicy() group2.setHardConcurrencyLimit(2); MockManagedQueryExecution query1a = new MockManagedQueryExecutionBuilder().build(); group1.run(query1a); - assertEquals(query1a.getState(), RUNNING); + assertThat(query1a.getState()).isEqualTo(RUNNING); MockManagedQueryExecution query1b = new MockManagedQueryExecutionBuilder().build(); group1.run(query1b); - assertEquals(query1b.getState(), QUEUED); + assertThat(query1b.getState()).isEqualTo(QUEUED); MockManagedQueryExecution query1c = new MockManagedQueryExecutionBuilder().build(); group1.run(query1c); - assertEquals(query1c.getState(), QUEUED); + assertThat(query1c.getState()).isEqualTo(QUEUED); MockManagedQueryExecution query2a = new MockManagedQueryExecutionBuilder().build(); group2.run(query2a); - assertEquals(query2a.getState(), QUEUED); + assertThat(query2a.getState()).isEqualTo(QUEUED); - assertEquals(root.getInfo().getNumEligibleSubGroups(), 2); - assertEquals(root.getOrCreateSubGroup("1").getQueuedQueries(), 2); - assertEquals(root.getOrCreateSubGroup("2").getQueuedQueries(), 1); - assertEquals(root.getSchedulingPolicy(), FAIR); + assertThat(root.getInfo().getNumEligibleSubGroups()).isEqualTo(2); + assertThat(root.getOrCreateSubGroup("1").getQueuedQueries()).isEqualTo(2); + assertThat(root.getOrCreateSubGroup("2").getQueuedQueries()).isEqualTo(1); + assertThat(root.getSchedulingPolicy()).isEqualTo(FAIR); root.setSchedulingPolicy(QUERY_PRIORITY); - assertEquals(root.getInfo().getNumEligibleSubGroups(), 2); - assertEquals(root.getOrCreateSubGroup("1").getQueuedQueries(), 2); - assertEquals(root.getOrCreateSubGroup("2").getQueuedQueries(), 1); + assertThat(root.getInfo().getNumEligibleSubGroups()).isEqualTo(2); + assertThat(root.getOrCreateSubGroup("1").getQueuedQueries()).isEqualTo(2); + assertThat(root.getOrCreateSubGroup("2").getQueuedQueries()).isEqualTo(1); - assertEquals(root.getSchedulingPolicy(), QUERY_PRIORITY); - assertEquals(root.getOrCreateSubGroup("1").getSchedulingPolicy(), QUERY_PRIORITY); - assertEquals(root.getOrCreateSubGroup("2").getSchedulingPolicy(), QUERY_PRIORITY); + assertThat(root.getSchedulingPolicy()).isEqualTo(QUERY_PRIORITY); + assertThat(root.getOrCreateSubGroup("1").getSchedulingPolicy()).isEqualTo(QUERY_PRIORITY); + assertThat(root.getOrCreateSubGroup("2").getSchedulingPolicy()).isEqualTo(QUERY_PRIORITY); } @Test @@ -191,27 +189,27 @@ public void testFairQueuing() group2.setHardConcurrencyLimit(2); MockManagedQueryExecution query1a = new MockManagedQueryExecutionBuilder().build(); group1.run(query1a); - assertEquals(query1a.getState(), RUNNING); + assertThat(query1a.getState()).isEqualTo(RUNNING); MockManagedQueryExecution query1b = new MockManagedQueryExecutionBuilder().build(); group1.run(query1b); - assertEquals(query1b.getState(), QUEUED); + assertThat(query1b.getState()).isEqualTo(QUEUED); MockManagedQueryExecution query1c = new MockManagedQueryExecutionBuilder().build(); group1.run(query1c); - assertEquals(query1c.getState(), QUEUED); + assertThat(query1c.getState()).isEqualTo(QUEUED); MockManagedQueryExecution query2a = new MockManagedQueryExecutionBuilder().build(); group2.run(query2a); - assertEquals(query2a.getState(), QUEUED); + assertThat(query2a.getState()).isEqualTo(QUEUED); query1a.complete(); // 1b and not 2a should have started, as it became queued first and group1 was eligible to run more - assertEquals(query1b.getState(), RUNNING); - assertEquals(query1c.getState(), QUEUED); - assertEquals(query2a.getState(), QUEUED); + assertThat(query1b.getState()).isEqualTo(RUNNING); + assertThat(query1c.getState()).isEqualTo(QUEUED); + assertThat(query2a.getState()).isEqualTo(QUEUED); // 2a and not 1c should have started, as all eligible sub groups get fair sharing query1b.complete(); - assertEquals(query2a.getState(), RUNNING); - assertEquals(query1c.getState(), QUEUED); + assertThat(query2a.getState()).isEqualTo(RUNNING); + assertThat(query1c.getState()).isEqualTo(QUEUED); } @Test @@ -226,17 +224,17 @@ public void testMemoryLimit() root.run(query1); // Process the group to refresh stats root.updateGroupsAndProcessQueuedQueries(); - assertEquals(query1.getState(), RUNNING); + assertThat(query1.getState()).isEqualTo(RUNNING); MockManagedQueryExecution query2 = new MockManagedQueryExecutionBuilder().build(); root.run(query2); - assertEquals(query2.getState(), QUEUED); + assertThat(query2.getState()).isEqualTo(QUEUED); MockManagedQueryExecution query3 = new MockManagedQueryExecutionBuilder().build(); root.run(query3); - assertEquals(query3.getState(), QUEUED); + assertThat(query3.getState()).isEqualTo(QUEUED); query1.complete(); - assertEquals(query2.getState(), RUNNING); - assertEquals(query3.getState(), RUNNING); + assertThat(query2.getState()).isEqualTo(RUNNING); + assertThat(query3.getState()).isEqualTo(RUNNING); } @Test @@ -255,17 +253,17 @@ public void testSubgroupMemoryLimit() subgroup.run(query1); // Process the group to refresh stats root.updateGroupsAndProcessQueuedQueries(); - assertEquals(query1.getState(), RUNNING); + assertThat(query1.getState()).isEqualTo(RUNNING); MockManagedQueryExecution query2 = new MockManagedQueryExecutionBuilder().build(); subgroup.run(query2); - assertEquals(query2.getState(), QUEUED); + assertThat(query2.getState()).isEqualTo(QUEUED); MockManagedQueryExecution query3 = new MockManagedQueryExecutionBuilder().build(); subgroup.run(query3); - assertEquals(query3.getState(), QUEUED); + assertThat(query3.getState()).isEqualTo(QUEUED); query1.complete(); - assertEquals(query2.getState(), RUNNING); - assertEquals(query3.getState(), RUNNING); + assertThat(query2.getState()).isEqualTo(RUNNING); + assertThat(query3.getState()).isEqualTo(RUNNING); } @Test @@ -287,24 +285,24 @@ public void testSoftCpuLimit() .build(); root.run(query1); - assertEquals(query1.getState(), RUNNING); + assertThat(query1.getState()).isEqualTo(RUNNING); MockManagedQueryExecution query2 = new MockManagedQueryExecutionBuilder().build(); root.run(query2); - assertEquals(query2.getState(), RUNNING); + assertThat(query2.getState()).isEqualTo(RUNNING); MockManagedQueryExecution query3 = new MockManagedQueryExecutionBuilder().build(); root.run(query3); - assertEquals(query3.getState(), QUEUED); + assertThat(query3.getState()).isEqualTo(QUEUED); query1.complete(); - assertEquals(query2.getState(), RUNNING); - assertEquals(query3.getState(), QUEUED); + assertThat(query2.getState()).isEqualTo(RUNNING); + assertThat(query3.getState()).isEqualTo(QUEUED); root.generateCpuQuota(2); root.updateGroupsAndProcessQueuedQueries(); - assertEquals(query2.getState(), RUNNING); - assertEquals(query3.getState(), RUNNING); + assertThat(query2.getState()).isEqualTo(RUNNING); + assertThat(query3.getState()).isEqualTo(RUNNING); } @Test @@ -325,18 +323,18 @@ public void testHardCpuLimit() .build(); root.run(query1); - assertEquals(query1.getState(), RUNNING); + assertThat(query1.getState()).isEqualTo(RUNNING); MockManagedQueryExecution query2 = new MockManagedQueryExecutionBuilder().build(); root.run(query2); - assertEquals(query2.getState(), QUEUED); + assertThat(query2.getState()).isEqualTo(QUEUED); query1.complete(); root.updateGroupsAndProcessQueuedQueries(); - assertEquals(query2.getState(), QUEUED); + assertThat(query2.getState()).isEqualTo(QUEUED); root.generateCpuQuota(2); root.updateGroupsAndProcessQueuedQueries(); - assertEquals(query2.getState(), RUNNING); + assertThat(query2.getState()).isEqualTo(RUNNING); } /** @@ -360,7 +358,7 @@ public void testCpuUsageUpdateForRunningQuery() MockManagedQueryExecution q1 = new MockManagedQueryExecutionBuilder().build(); child.run(q1); - assertEquals(q1.getState(), RUNNING); + assertThat(q1.getState()).isEqualTo(RUNNING); q1.consumeCpuTimeMillis(4); root.updateGroupsAndProcessQueuedQueries(); @@ -369,7 +367,7 @@ public void testCpuUsageUpdateForRunningQuery() // q2 gets queued, because the cached usage is greater than the limit MockManagedQueryExecution q2 = new MockManagedQueryExecutionBuilder().build(); child.run(q2); - assertEquals(q2.getState(), QUEUED); + assertThat(q2.getState()).isEqualTo(QUEUED); // Generating CPU quota before the query finishes. This assertion verifies CPU update during quota generation. root.generateCpuQuota(2); @@ -378,12 +376,12 @@ public void testCpuUsageUpdateForRunningQuery() // An incoming query starts running right away. MockManagedQueryExecution q3 = new MockManagedQueryExecutionBuilder().build(); child.run(q3); - assertEquals(q3.getState(), RUNNING); + assertThat(q3.getState()).isEqualTo(RUNNING); // A queued query starts running only after invoking `updateGroupsAndProcessQueuedQueries`. - assertEquals(q2.getState(), QUEUED); + assertThat(q2.getState()).isEqualTo(QUEUED); root.updateGroupsAndProcessQueuedQueries(); - assertEquals(q2.getState(), RUNNING); + assertThat(q2.getState()).isEqualTo(RUNNING); } @Test @@ -403,7 +401,7 @@ public void testCpuUsageUpdateAtQueryCompletion() MockManagedQueryExecution q1 = new MockManagedQueryExecutionBuilder().build(); child.run(q1); - assertEquals(q1.getState(), RUNNING); + assertThat(q1.getState()).isEqualTo(RUNNING); q1.consumeCpuTimeMillis(4); q1.complete(); @@ -415,16 +413,16 @@ public void testCpuUsageUpdateAtQueryCompletion() // q2 gets queued since cached usage exceeds the limit. MockManagedQueryExecution q2 = new MockManagedQueryExecutionBuilder().build(); child.run(q2); - assertEquals(q2.getState(), QUEUED); + assertThat(q2.getState()).isEqualTo(QUEUED); root.generateCpuQuota(2); Stream.of(root, child).forEach(group -> assertWithinCpuLimit(group, 2)); - assertEquals(q2.getState(), QUEUED); + assertThat(q2.getState()).isEqualTo(QUEUED); // q2 should run after groups are updated. CPU usage should not be double counted. root.updateGroupsAndProcessQueuedQueries(); Stream.of(root, child).forEach(group -> assertWithinCpuLimit(group, 2)); - assertEquals(q2.getState(), RUNNING); + assertThat(q2.getState()).isEqualTo(RUNNING); } @Test @@ -442,7 +440,7 @@ public void testMemoryUsageUpdateForRunningQuery() MockManagedQueryExecution q1 = new MockManagedQueryExecutionBuilder().build(); child.run(q1); - assertEquals(q1.getState(), RUNNING); + assertThat(q1.getState()).isEqualTo(RUNNING); q1.setMemoryUsage(DataSize.ofBytes(4)); Stream.of(root, child).forEach(group -> assertWithinMemoryLimit(group, 0)); @@ -452,20 +450,20 @@ public void testMemoryUsageUpdateForRunningQuery() // A new query gets queued since the current usage exceeds the limit. MockManagedQueryExecution q2 = new MockManagedQueryExecutionBuilder().build(); child.run(q2); - assertEquals(q2.getState(), QUEUED); + assertThat(q2.getState()).isEqualTo(QUEUED); q1.setMemoryUsage(DataSize.ofBytes(2)); // A new incoming query q3 gets queued since cached usage still exceeds the limit. MockManagedQueryExecution q3 = new MockManagedQueryExecutionBuilder().build(); child.run(q3); - assertEquals(q3.getState(), QUEUED); + assertThat(q3.getState()).isEqualTo(QUEUED); // q2 and q3 start running when cached usage is updated and queued queries are processed. root.updateGroupsAndProcessQueuedQueries(); Stream.of(root, child).forEach(group -> assertWithinMemoryLimit(group, 2)); - assertEquals(q2.getState(), RUNNING); - assertEquals(q3.getState(), RUNNING); + assertThat(q2.getState()).isEqualTo(RUNNING); + assertThat(q3.getState()).isEqualTo(RUNNING); } @Test @@ -483,7 +481,7 @@ public void testMemoryUsageUpdateAtQueryCompletion() MockManagedQueryExecution q1 = new MockManagedQueryExecutionBuilder().build(); child.run(q1); - assertEquals(q1.getState(), RUNNING); + assertThat(q1.getState()).isEqualTo(RUNNING); q1.setMemoryUsage(DataSize.ofBytes(4)); Stream.of(root, child).forEach(group -> assertWithinMemoryLimit(group, 0)); @@ -497,7 +495,7 @@ public void testMemoryUsageUpdateAtQueryCompletion() // q2 starts running since usage is within the limit. MockManagedQueryExecution q2 = new MockManagedQueryExecutionBuilder().build(); child.run(q2); - assertEquals(q2.getState(), RUNNING); + assertThat(q2.getState()).isEqualTo(RUNNING); } /** @@ -537,9 +535,9 @@ public void testRecursiveCpuUsageUpdate() rootChild1Child2.run(q2); rootChild2.run(q3); - assertEquals(q1.getState(), RUNNING); - assertEquals(q2.getState(), RUNNING); - assertEquals(q3.getState(), RUNNING); + assertThat(q1.getState()).isEqualTo(RUNNING); + assertThat(q2.getState()).isEqualTo(RUNNING); + assertThat(q3.getState()).isEqualTo(RUNNING); q1.consumeCpuTimeMillis(4); q2.consumeCpuTimeMillis(10); @@ -557,12 +555,12 @@ public void testRecursiveCpuUsageUpdate() // q4 submitted in rootChild2 gets queued because root's CPU usage exceeds the limit MockManagedQueryExecution q4 = new MockManagedQueryExecutionBuilder().build(); rootChild2.run(q4); - assertEquals(q4.getState(), QUEUED); + assertThat(q4.getState()).isEqualTo(QUEUED); // q5 submitted in rootChild1Child1 gets queued because root's CPU usage exceeds the limit MockManagedQueryExecution q5 = new MockManagedQueryExecutionBuilder().build(); rootChild1Child1.run(q5); - assertEquals(q5.getState(), QUEUED); + assertThat(q5.getState()).isEqualTo(QUEUED); // Assert CPU usage update after quota regeneration root.generateCpuQuota(4); @@ -575,9 +573,9 @@ public void testRecursiveCpuUsageUpdate() root.updateGroupsAndProcessQueuedQueries(); // q4 gets dequeued, because CPU usages of root and rootChild2 are below their limits. - assertEquals(q4.getState(), RUNNING); + assertThat(q4.getState()).isEqualTo(RUNNING); // q5 does not get dequeued, because rootChild1's CPU usage exceeds the limit. - assertEquals(q5.getState(), QUEUED); + assertThat(q5.getState()).isEqualTo(QUEUED); q2.consumeCpuTimeMillis(3); q2.complete(); @@ -590,7 +588,7 @@ public void testRecursiveCpuUsageUpdate() // q6 in rootChild2 gets queued because root's CPU usage exceeds the limit. MockManagedQueryExecution q6 = new MockManagedQueryExecutionBuilder().build(); rootChild2.run(q6); - assertEquals(q6.getState(), QUEUED); + assertThat(q6.getState()).isEqualTo(QUEUED); // Assert usage after regeneration root.generateCpuQuota(6); @@ -603,15 +601,15 @@ public void testRecursiveCpuUsageUpdate() root.updateGroupsAndProcessQueuedQueries(); // q5 is queued, because rootChild1's usage still exceeds the limit. - assertEquals(q5.getState(), QUEUED); + assertThat(q5.getState()).isEqualTo(QUEUED); // q6 starts running, because usage in rootChild2 and root are within their limits. - assertEquals(q6.getState(), RUNNING); + assertThat(q6.getState()).isEqualTo(RUNNING); // q5 starts running after rootChild1's usage comes within the limit root.generateCpuQuota(2); assertWithinCpuLimit(rootChild1, 5); root.updateGroupsAndProcessQueuedQueries(); - assertEquals(q5.getState(), RUNNING); + assertThat(q5.getState()).isEqualTo(RUNNING); } /** @@ -656,9 +654,9 @@ public void triggerProcessQueuedQueries() rootChild1Child2.run(q2); rootChild2.run(q3); - assertEquals(q1.getState(), RUNNING); - assertEquals(q2.getState(), RUNNING); - assertEquals(q3.getState(), RUNNING); + assertThat(q1.getState()).isEqualTo(RUNNING); + assertThat(q2.getState()).isEqualTo(RUNNING); + assertThat(q3.getState()).isEqualTo(RUNNING); q1.setMemoryUsage(DataSize.ofBytes(2)); q2.setMemoryUsage(DataSize.ofBytes(5)); @@ -675,12 +673,12 @@ public void triggerProcessQueuedQueries() // q4 submitted in rootChild2 gets queued because root's memory usage exceeds the limit MockManagedQueryExecution q4 = new MockManagedQueryExecutionBuilder().build(); rootChild2.run(q4); - assertEquals(q4.getState(), QUEUED); + assertThat(q4.getState()).isEqualTo(QUEUED); // q5 submitted in rootChild1Child1 gets queued because root's memory usage) exceeds the limit MockManagedQueryExecution q5 = new MockManagedQueryExecutionBuilder().build(); rootChild1Child1.run(q5); - assertEquals(q5.getState(), QUEUED); + assertThat(q5.getState()).isEqualTo(QUEUED); q1.setMemoryUsage(DataSize.ofBytes(0)); @@ -690,9 +688,9 @@ public void triggerProcessQueuedQueries() assertWithinMemoryLimit(rootChild1Child1, 0); // q4 starts running since usage in root and rootChild2 is within the limits - assertEquals(q4.getState(), RUNNING); + assertThat(q4.getState()).isEqualTo(RUNNING); // q5 is queued since usage in rootChild1 exceeds the limit. - assertEquals(q5.getState(), QUEUED); + assertThat(q5.getState()).isEqualTo(QUEUED); // q2's completion triggers memory updates q2.complete(); @@ -702,12 +700,12 @@ public void triggerProcessQueuedQueries() // An incoming query starts running MockManagedQueryExecution q6 = new MockManagedQueryExecutionBuilder().build(); rootChild1Child2.run(q6); - assertEquals(q6.getState(), RUNNING); + assertThat(q6.getState()).isEqualTo(RUNNING); // queued queries will start running after the update - assertEquals(q5.getState(), QUEUED); + assertThat(q5.getState()).isEqualTo(QUEUED); root.updateGroupsAndProcessQueuedQueries(); - assertEquals(q5.getState(), RUNNING); + assertThat(q5.getState()).isEqualTo(RUNNING); } @Test @@ -760,7 +758,7 @@ public void testPriorityScheduling() for (MockManagedQueryExecution query : orderedQueries) { root.updateGroupsAndProcessQueuedQueries(); - assertEquals(query.getState(), RUNNING); + assertThat(query.getState()).isEqualTo(RUNNING); query.complete(); } } @@ -985,8 +983,8 @@ public void triggerProcessQueuedQueries() group2Queries = fillGroupTo(group2, group2Queries, 4); } - assertEquals(group1Ran, 1000); - assertEquals(group1Ran, 1000); + assertThat(group1Ran).isEqualTo(1000); + assertThat(group1Ran).isEqualTo(1000); } @Test @@ -1045,15 +1043,15 @@ public void triggerProcessQueuedQueries() queries.addAll(fillGroupTo(rootBY, ImmutableSet.of(), 10, true)); ResourceGroupInfo info = root.getInfo(); - assertEquals(info.getNumRunningQueries(), 0); - assertEquals(info.getNumQueuedQueries(), 40); + assertThat(info.getNumRunningQueries()).isEqualTo(0); + assertThat(info.getNumQueuedQueries()).isEqualTo(40); // root.maxRunningQueries = 4, root.a.maxRunningQueries = 2, root.b.maxRunningQueries = 2. Will have 4 queries running and 36 left queued. root.setHardConcurrencyLimit(4); root.updateGroupsAndProcessQueuedQueries(); info = root.getInfo(); - assertEquals(info.getNumRunningQueries(), 4); - assertEquals(info.getNumQueuedQueries(), 36); + assertThat(info.getNumRunningQueries()).isEqualTo(4); + assertThat(info.getNumQueuedQueries()).isEqualTo(36); // Complete running queries Iterator iterator = queries.iterator(); @@ -1068,22 +1066,22 @@ public void triggerProcessQueuedQueries() // 4 more queries start running, 32 left queued. root.updateGroupsAndProcessQueuedQueries(); info = root.getInfo(); - assertEquals(info.getNumRunningQueries(), 4); - assertEquals(info.getNumQueuedQueries(), 32); + assertThat(info.getNumRunningQueries()).isEqualTo(4); + assertThat(info.getNumQueuedQueries()).isEqualTo(32); // root.maxRunningQueries = 10, root.a.maxRunningQueries = 2, root.b.maxRunningQueries = 2. Still only have 4 running queries and 32 left queued. root.setHardConcurrencyLimit(10); root.updateGroupsAndProcessQueuedQueries(); info = root.getInfo(); - assertEquals(info.getNumRunningQueries(), 4); - assertEquals(info.getNumQueuedQueries(), 32); + assertThat(info.getNumRunningQueries()).isEqualTo(4); + assertThat(info.getNumQueuedQueries()).isEqualTo(32); // root.maxRunningQueries = 10, root.a.maxRunningQueries = 2, root.b.maxRunningQueries = 10. Will have 10 running queries and 26 left queued. rootB.setHardConcurrencyLimit(10); root.updateGroupsAndProcessQueuedQueries(); info = root.getInfo(); - assertEquals(info.getNumRunningQueries(), 10); - assertEquals(info.getNumQueuedQueries(), 26); + assertThat(info.getNumRunningQueries()).isEqualTo(10); + assertThat(info.getNumQueuedQueries()).isEqualTo(26); } @Test @@ -1122,39 +1120,39 @@ public void testGetResourceGroupStateInfo() queries.addAll(fillGroupTo(rootB, ImmutableSet.of(), 10, true)); ResourceGroupInfo rootInfo = root.getFullInfo(); - assertEquals(rootInfo.getId(), root.getId()); - assertEquals(rootInfo.getState(), CAN_RUN); - assertEquals(rootInfo.getSoftMemoryLimit().toBytes(), root.getSoftMemoryLimitBytes()); - assertEquals(rootInfo.getMemoryUsage(), DataSize.ofBytes(0)); - assertEquals(rootInfo.getCpuUsage().toMillis(), 0); + assertThat(rootInfo.getId()).isEqualTo(root.getId()); + assertThat(rootInfo.getState()).isEqualTo(CAN_RUN); + assertThat(rootInfo.getSoftMemoryLimit().toBytes()).isEqualTo(root.getSoftMemoryLimitBytes()); + assertThat(rootInfo.getMemoryUsage()).isEqualTo(DataSize.ofBytes(0)); + assertThat(rootInfo.getCpuUsage().toMillis()).isEqualTo(0); List subGroups = rootInfo.getSubGroups().get(); - assertEquals(subGroups.size(), 2); + assertThat(subGroups.size()).isEqualTo(2); assertGroupInfoEquals(subGroups.get(0), rootA.getInfo()); - assertEquals(subGroups.get(0).getId(), rootA.getId()); - assertEquals(subGroups.get(0).getState(), CAN_QUEUE); - assertEquals(subGroups.get(0).getSoftMemoryLimit().toBytes(), rootA.getSoftMemoryLimitBytes()); - assertEquals(subGroups.get(0).getHardConcurrencyLimit(), rootA.getHardConcurrencyLimit()); - assertEquals(subGroups.get(0).getMaxQueuedQueries(), rootA.getMaxQueuedQueries()); - assertEquals(subGroups.get(0).getNumEligibleSubGroups(), 2); - assertEquals(subGroups.get(0).getNumRunningQueries(), 0); - assertEquals(subGroups.get(0).getNumQueuedQueries(), 10); + assertThat(subGroups.get(0).getId()).isEqualTo(rootA.getId()); + assertThat(subGroups.get(0).getState()).isEqualTo(CAN_QUEUE); + assertThat(subGroups.get(0).getSoftMemoryLimit().toBytes()).isEqualTo(rootA.getSoftMemoryLimitBytes()); + assertThat(subGroups.get(0).getHardConcurrencyLimit()).isEqualTo(rootA.getHardConcurrencyLimit()); + assertThat(subGroups.get(0).getMaxQueuedQueries()).isEqualTo(rootA.getMaxQueuedQueries()); + assertThat(subGroups.get(0).getNumEligibleSubGroups()).isEqualTo(2); + assertThat(subGroups.get(0).getNumRunningQueries()).isEqualTo(0); + assertThat(subGroups.get(0).getNumQueuedQueries()).isEqualTo(10); assertGroupInfoEquals(subGroups.get(1), rootB.getInfo()); - assertEquals(subGroups.get(1).getId(), rootB.getId()); - assertEquals(subGroups.get(1).getState(), CAN_QUEUE); - assertEquals(subGroups.get(1).getSoftMemoryLimit().toBytes(), rootB.getSoftMemoryLimitBytes()); - assertEquals(subGroups.get(1).getHardConcurrencyLimit(), rootB.getHardConcurrencyLimit()); - assertEquals(subGroups.get(1).getMaxQueuedQueries(), rootB.getMaxQueuedQueries()); - assertEquals(subGroups.get(1).getNumEligibleSubGroups(), 0); - assertEquals(subGroups.get(1).getNumRunningQueries(), 1); - assertEquals(subGroups.get(1).getNumQueuedQueries(), 9); - assertEquals(rootInfo.getSoftConcurrencyLimit(), root.getSoftConcurrencyLimit()); - assertEquals(rootInfo.getHardConcurrencyLimit(), root.getHardConcurrencyLimit()); - assertEquals(rootInfo.getMaxQueuedQueries(), root.getMaxQueuedQueries()); - assertEquals(rootInfo.getNumQueuedQueries(), 19); + assertThat(subGroups.get(1).getId()).isEqualTo(rootB.getId()); + assertThat(subGroups.get(1).getState()).isEqualTo(CAN_QUEUE); + assertThat(subGroups.get(1).getSoftMemoryLimit().toBytes()).isEqualTo(rootB.getSoftMemoryLimitBytes()); + assertThat(subGroups.get(1).getHardConcurrencyLimit()).isEqualTo(rootB.getHardConcurrencyLimit()); + assertThat(subGroups.get(1).getMaxQueuedQueries()).isEqualTo(rootB.getMaxQueuedQueries()); + assertThat(subGroups.get(1).getNumEligibleSubGroups()).isEqualTo(0); + assertThat(subGroups.get(1).getNumRunningQueries()).isEqualTo(1); + assertThat(subGroups.get(1).getNumQueuedQueries()).isEqualTo(9); + assertThat(rootInfo.getSoftConcurrencyLimit()).isEqualTo(root.getSoftConcurrencyLimit()); + assertThat(rootInfo.getHardConcurrencyLimit()).isEqualTo(root.getHardConcurrencyLimit()); + assertThat(rootInfo.getMaxQueuedQueries()).isEqualTo(root.getMaxQueuedQueries()); + assertThat(rootInfo.getNumQueuedQueries()).isEqualTo(19); List runningQueries = rootInfo.getRunningQueries().get(); - assertEquals(runningQueries.size(), 1); + assertThat(runningQueries.size()).isEqualTo(1); QueryStateInfo queryInfo = runningQueries.get(0); - assertEquals(queryInfo.getResourceGroupId(), Optional.of(rootB.getId())); + assertThat(queryInfo.getResourceGroupId()).isEqualTo(Optional.of(rootB.getId())); } @Test @@ -1202,23 +1200,23 @@ public void testGetBlockedQueuedQueries() queries.addAll(fillGroupTo(rootBX, ImmutableSet.of(), 10, true)); queries.addAll(fillGroupTo(rootBY, ImmutableSet.of(), 10, true)); - assertEquals(root.getWaitingQueuedQueries(), 16); - assertEquals(rootA.getWaitingQueuedQueries(), 13); - assertEquals(rootAX.getWaitingQueuedQueries(), 10); - assertEquals(rootAY.getWaitingQueuedQueries(), 10); - assertEquals(rootB.getWaitingQueuedQueries(), 13); - assertEquals(rootBX.getWaitingQueuedQueries(), 10); - assertEquals(rootBY.getWaitingQueuedQueries(), 10); + assertThat(root.getWaitingQueuedQueries()).isEqualTo(16); + assertThat(rootA.getWaitingQueuedQueries()).isEqualTo(13); + assertThat(rootAX.getWaitingQueuedQueries()).isEqualTo(10); + assertThat(rootAY.getWaitingQueuedQueries()).isEqualTo(10); + assertThat(rootB.getWaitingQueuedQueries()).isEqualTo(13); + assertThat(rootBX.getWaitingQueuedQueries()).isEqualTo(10); + assertThat(rootBY.getWaitingQueuedQueries()).isEqualTo(10); root.setHardConcurrencyLimit(20); root.updateGroupsAndProcessQueuedQueries(); - assertEquals(root.getWaitingQueuedQueries(), 0); - assertEquals(rootA.getWaitingQueuedQueries(), 5); - assertEquals(rootAX.getWaitingQueuedQueries(), 6); - assertEquals(rootAY.getWaitingQueuedQueries(), 6); - assertEquals(rootB.getWaitingQueuedQueries(), 5); - assertEquals(rootBX.getWaitingQueuedQueries(), 6); - assertEquals(rootBY.getWaitingQueuedQueries(), 6); + assertThat(root.getWaitingQueuedQueries()).isEqualTo(0); + assertThat(rootA.getWaitingQueuedQueries()).isEqualTo(5); + assertThat(rootAX.getWaitingQueuedQueries()).isEqualTo(6); + assertThat(rootAY.getWaitingQueuedQueries()).isEqualTo(6); + assertThat(rootB.getWaitingQueuedQueries()).isEqualTo(5); + assertThat(rootBX.getWaitingQueuedQueries()).isEqualTo(6); + assertThat(rootBY.getWaitingQueuedQueries()).isEqualTo(6); } private static int completeGroupQueries(Set groupQueries) @@ -1258,7 +1256,7 @@ private static Set fillGroupTo(InternalResourceGroup private static void assertGroupInfoEquals(ResourceGroupInfo actual, ResourceGroupInfo expected) { - assertTrue(actual.getSchedulingWeight() == expected.getSchedulingWeight() && + assertThat(actual.getSchedulingWeight() == expected.getSchedulingWeight() && actual.getSoftConcurrencyLimit() == expected.getSoftConcurrencyLimit() && actual.getHardConcurrencyLimit() == expected.getHardConcurrencyLimit() && actual.getMaxQueuedQueries() == expected.getMaxQueuedQueries() && @@ -1270,34 +1268,34 @@ private static void assertGroupInfoEquals(ResourceGroupInfo actual, ResourceGrou actual.getSchedulingPolicy() == expected.getSchedulingPolicy() && Objects.equals(actual.getSoftMemoryLimit(), expected.getSoftMemoryLimit()) && Objects.equals(actual.getMemoryUsage(), expected.getMemoryUsage()) && - Objects.equals(actual.getCpuUsage(), expected.getCpuUsage())); + Objects.equals(actual.getCpuUsage(), expected.getCpuUsage())).isTrue(); } private static void assertExceedsCpuLimit(InternalResourceGroup group, long expectedMillis) { long actualMillis = group.getResourceUsageSnapshot().getCpuUsageMillis(); - assertEquals(actualMillis, expectedMillis); - assertTrue(actualMillis >= group.getHardCpuLimit().toMillis()); + assertThat(actualMillis).isEqualTo(expectedMillis); + assertThat(actualMillis >= group.getHardCpuLimit().toMillis()).isTrue(); } private static void assertWithinCpuLimit(InternalResourceGroup group, long expectedMillis) { long actualMillis = group.getResourceUsageSnapshot().getCpuUsageMillis(); - assertEquals(actualMillis, expectedMillis); - assertTrue(actualMillis < group.getHardCpuLimit().toMillis()); + assertThat(actualMillis).isEqualTo(expectedMillis); + assertThat(actualMillis < group.getHardCpuLimit().toMillis()).isTrue(); } private static void assertExceedsMemoryLimit(InternalResourceGroup group, long expectedBytes) { long actualBytes = group.getResourceUsageSnapshot().getMemoryUsageBytes(); - assertEquals(actualBytes, expectedBytes); + assertThat(actualBytes).isEqualTo(expectedBytes); assertThat(actualBytes).isGreaterThan(group.getSoftMemoryLimitBytes()); } private static void assertWithinMemoryLimit(InternalResourceGroup group, long expectedBytes) { long actualBytes = group.getResourceUsageSnapshot().getMemoryUsageBytes(); - assertEquals(actualBytes, expectedBytes); + assertThat(actualBytes).isEqualTo(expectedBytes); assertThat(actualBytes).isLessThanOrEqualTo(group.getSoftMemoryLimitBytes()); } } diff --git a/core/trino-main/src/test/java/io/trino/execution/resourcegroups/TestStochasticPriorityQueue.java b/core/trino-main/src/test/java/io/trino/execution/resourcegroups/TestStochasticPriorityQueue.java index a3664b51979c..9eeec4f28929 100644 --- a/core/trino-main/src/test/java/io/trino/execution/resourcegroups/TestStochasticPriorityQueue.java +++ b/core/trino-main/src/test/java/io/trino/execution/resourcegroups/TestStochasticPriorityQueue.java @@ -18,9 +18,7 @@ import static io.airlift.testing.Assertions.assertGreaterThan; import static io.airlift.testing.Assertions.assertLessThan; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestStochasticPriorityQueue { @@ -29,21 +27,21 @@ public void testContainsAndRemove() { StochasticPriorityQueue queue = new StochasticPriorityQueue<>(); for (int i = 0; i < 100; i++) { - assertTrue(queue.addOrUpdate("test" + i, i + 1)); + assertThat(queue.addOrUpdate("test" + i, i + 1)).isTrue(); } for (int i = 0; i < 100; i += 2) { - assertTrue(queue.remove("test" + i)); + assertThat(queue.remove("test" + i)).isTrue(); } for (int i = 1; i < 100; i += 2) { - assertTrue(queue.contains("test" + i)); + assertThat(queue.contains("test" + i)).isTrue(); } - assertFalse(queue.isEmpty()); - assertEquals(queue.size(), 50); + assertThat(queue.isEmpty()).isFalse(); + assertThat(queue.size()).isEqualTo(50); for (int i = 1; i < 100; i += 2) { - assertTrue(queue.remove("test" + i)); + assertThat(queue.remove("test" + i)).isTrue(); } - assertTrue(queue.isEmpty()); - assertEquals(queue.size(), 0); + assertThat(queue.isEmpty()).isTrue(); + assertThat(queue.size()).isEqualTo(0); } @Test @@ -51,10 +49,10 @@ public void testPollDistribution() { StochasticPriorityQueue queue = new StochasticPriorityQueue<>(); for (int i = 0; i < 100; i++) { - assertTrue(queue.addOrUpdate("foo" + i, 1)); + assertThat(queue.addOrUpdate("foo" + i, 1)).isTrue(); } for (int i = 0; i < 100; i++) { - assertTrue(queue.addOrUpdate("bar" + i, 1)); + assertThat(queue.addOrUpdate("bar" + i, 1)).isTrue(); } int foo = 0; for (int i = 0; i < 1000; i++) { @@ -62,7 +60,7 @@ public void testPollDistribution() if (value.startsWith("foo")) { foo++; } - assertTrue(queue.addOrUpdate(value, 1)); + assertThat(queue.addOrUpdate(value, 1)).isTrue(); } BinomialDistribution binomial = new BinomialDistribution(1000, 0.5); int lowerBound = binomial.inverseCumulativeProbability(0.000001); @@ -72,17 +70,17 @@ public void testPollDistribution() // Update foo weights to 2:1 distribution for (int i = 0; i < 100; i++) { - assertFalse(queue.addOrUpdate("foo" + i, 2)); + assertThat(queue.addOrUpdate("foo" + i, 2)).isFalse(); } foo = 0; for (int i = 0; i < 1000; i++) { String value = queue.poll(); if (value.startsWith("foo")) { foo++; - assertTrue(queue.addOrUpdate(value, 2)); + assertThat(queue.addOrUpdate(value, 2)).isTrue(); } else { - assertTrue(queue.addOrUpdate(value, 1)); + assertThat(queue.addOrUpdate(value, 1)).isTrue(); } } binomial = new BinomialDistribution(1000, 2.0 / 3.0); diff --git a/core/trino-main/src/test/java/io/trino/execution/resourcegroups/TestUpdateablePriorityQueue.java b/core/trino-main/src/test/java/io/trino/execution/resourcegroups/TestUpdateablePriorityQueue.java index 42d9bf9cf3e3..f075a8a41c2c 100644 --- a/core/trino-main/src/test/java/io/trino/execution/resourcegroups/TestUpdateablePriorityQueue.java +++ b/core/trino-main/src/test/java/io/trino/execution/resourcegroups/TestUpdateablePriorityQueue.java @@ -21,23 +21,21 @@ import static io.trino.execution.resourcegroups.IndexedPriorityQueue.PriorityOrdering.HIGH_TO_LOW; import static io.trino.execution.resourcegroups.IndexedPriorityQueue.PriorityOrdering.LOW_TO_HIGH; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; public class TestUpdateablePriorityQueue { @Test public void testFifoQueue() { - assertEquals(populateAndExtract(new FifoQueue<>()), ImmutableList.of(1, 2, 3)); + assertThat(populateAndExtract(new FifoQueue<>())).isEqualTo(ImmutableList.of(1, 2, 3)); } @Test public void testIndexedPriorityQueue() { - assertEquals(populateAndExtract(new IndexedPriorityQueue<>()), ImmutableList.of(3, 2, 1)); - assertEquals(populateAndExtract(new IndexedPriorityQueue<>(HIGH_TO_LOW)), ImmutableList.of(3, 2, 1)); - assertEquals(populateAndExtract(new IndexedPriorityQueue<>(LOW_TO_HIGH)), ImmutableList.of(1, 2, 3)); + assertThat(populateAndExtract(new IndexedPriorityQueue<>())).isEqualTo(ImmutableList.of(3, 2, 1)); + assertThat(populateAndExtract(new IndexedPriorityQueue<>(HIGH_TO_LOW))).isEqualTo(ImmutableList.of(3, 2, 1)); + assertThat(populateAndExtract(new IndexedPriorityQueue<>(LOW_TO_HIGH))).isEqualTo(ImmutableList.of(1, 2, 3)); } @Test @@ -76,7 +74,7 @@ public void testPrioritizedPeekPollIndexedPriorityQueue() @Test public void testStochasticPriorityQueue() { - assertTrue(populateAndExtract(new StochasticPriorityQueue<>()).size() == 3); + assertThat(populateAndExtract(new StochasticPriorityQueue<>()).size() == 3).isTrue(); } private static List populateAndExtract(UpdateablePriorityQueue queue) diff --git a/core/trino-main/src/test/java/io/trino/execution/resourcegroups/TestWeightedFairQueue.java b/core/trino-main/src/test/java/io/trino/execution/resourcegroups/TestWeightedFairQueue.java index b4d72311d3c7..98255ceaa8bc 100644 --- a/core/trino-main/src/test/java/io/trino/execution/resourcegroups/TestWeightedFairQueue.java +++ b/core/trino-main/src/test/java/io/trino/execution/resourcegroups/TestWeightedFairQueue.java @@ -16,8 +16,7 @@ import io.trino.execution.resourcegroups.WeightedFairQueue.Usage; import org.junit.jupiter.api.Test; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestWeightedFairQueue { @@ -30,14 +29,14 @@ public void testBasic() queue.addOrUpdate(item1, new Usage(1, 1)); queue.addOrUpdate(item2, new Usage(2, 1)); - assertEquals(queue.size(), 2); - assertEquals(queue.poll(), item2); - assertTrue(queue.contains(item1)); - assertEquals(queue.poll(), item1); - assertEquals(queue.size(), 0); - assertEquals(queue.poll(), null); - assertEquals(queue.poll(), null); - assertEquals(queue.size(), 0); + assertThat(queue.size()).isEqualTo(2); + assertThat(queue.poll()).isEqualTo(item2); + assertThat(queue.contains(item1)).isTrue(); + assertThat(queue.poll()).isEqualTo(item1); + assertThat(queue.size()).isEqualTo(0); + assertThat(queue.poll()).isEqualTo(null); + assertThat(queue.poll()).isEqualTo(null); + assertThat(queue.size()).isEqualTo(0); } @Test @@ -51,11 +50,11 @@ public void testUpdate() queue.addOrUpdate(item2, new Usage(2, 1)); queue.addOrUpdate(item3, new Usage(3, 1)); - assertEquals(queue.poll(), item3); + assertThat(queue.poll()).isEqualTo(item3); queue.addOrUpdate(item1, new Usage(4, 1)); - assertEquals(queue.poll(), item1); - assertEquals(queue.poll(), item2); - assertEquals(queue.size(), 0); + assertThat(queue.poll()).isEqualTo(item1); + assertThat(queue.poll()).isEqualTo(item2); + assertThat(queue.size()).isEqualTo(0); } @Test @@ -80,7 +79,7 @@ public void testMultipleWinners() } } - assertEquals(count1, 500); - assertEquals(count2, 500); + assertThat(count1).isEqualTo(500); + assertThat(count2).isEqualTo(500); } } diff --git a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestBroadcastPipelinedOutputBufferManager.java b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestBroadcastPipelinedOutputBufferManager.java index 88b16bf8e7a9..f04ed24cf6ac 100644 --- a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestBroadcastPipelinedOutputBufferManager.java +++ b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestBroadcastPipelinedOutputBufferManager.java @@ -19,7 +19,7 @@ import static io.trino.execution.buffer.PipelinedOutputBuffers.BROADCAST_PARTITION_ID; import static io.trino.execution.buffer.PipelinedOutputBuffers.BufferType.BROADCAST; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestBroadcastPipelinedOutputBufferManager { @@ -27,34 +27,34 @@ public class TestBroadcastPipelinedOutputBufferManager public void test() { BroadcastPipelinedOutputBufferManager hashOutputBufferManager = new BroadcastPipelinedOutputBufferManager(); - assertEquals(hashOutputBufferManager.getOutputBuffers(), PipelinedOutputBuffers.createInitial(BROADCAST)); + assertThat(hashOutputBufferManager.getOutputBuffers()).isEqualTo(PipelinedOutputBuffers.createInitial(BROADCAST)); hashOutputBufferManager.addOutputBuffer(new OutputBufferId(0)); PipelinedOutputBuffers expectedOutputBuffers = PipelinedOutputBuffers.createInitial(BROADCAST).withBuffer(new OutputBufferId(0), BROADCAST_PARTITION_ID); - assertEquals(hashOutputBufferManager.getOutputBuffers(), expectedOutputBuffers); + assertThat(hashOutputBufferManager.getOutputBuffers()).isEqualTo(expectedOutputBuffers); hashOutputBufferManager.addOutputBuffer(new OutputBufferId(1)); hashOutputBufferManager.addOutputBuffer(new OutputBufferId(2)); expectedOutputBuffers = expectedOutputBuffers.withBuffer(new OutputBufferId(1), BROADCAST_PARTITION_ID); expectedOutputBuffers = expectedOutputBuffers.withBuffer(new OutputBufferId(2), BROADCAST_PARTITION_ID); - assertEquals(hashOutputBufferManager.getOutputBuffers(), expectedOutputBuffers); + assertThat(hashOutputBufferManager.getOutputBuffers()).isEqualTo(expectedOutputBuffers); // set no more buffers hashOutputBufferManager.addOutputBuffer(new OutputBufferId(3)); hashOutputBufferManager.noMoreBuffers(); expectedOutputBuffers = expectedOutputBuffers.withBuffer(new OutputBufferId(3), BROADCAST_PARTITION_ID); expectedOutputBuffers = expectedOutputBuffers.withNoMoreBufferIds(); - assertEquals(hashOutputBufferManager.getOutputBuffers(), expectedOutputBuffers); + assertThat(hashOutputBufferManager.getOutputBuffers()).isEqualTo(expectedOutputBuffers); // try to add another buffer, which should not result in an error // and output buffers should not change hashOutputBufferManager.addOutputBuffer(new OutputBufferId(5)); - assertEquals(hashOutputBufferManager.getOutputBuffers(), expectedOutputBuffers); + assertThat(hashOutputBufferManager.getOutputBuffers()).isEqualTo(expectedOutputBuffers); // try to set no more buffers again, which should not result in an error // and output buffers should not change hashOutputBufferManager.noMoreBuffers(); - assertEquals(hashOutputBufferManager.getOutputBuffers(), expectedOutputBuffers); + assertThat(hashOutputBufferManager.getOutputBuffers()).isEqualTo(expectedOutputBuffers); } } diff --git a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestFileBasedNetworkTopology.java b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestFileBasedNetworkTopology.java index b893b0946742..790e7142375d 100644 --- a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestFileBasedNetworkTopology.java +++ b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestFileBasedNetworkTopology.java @@ -26,7 +26,7 @@ import java.util.concurrent.TimeUnit; import static java.util.concurrent.TimeUnit.DAYS; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestFileBasedNetworkTopology { @@ -45,14 +45,14 @@ public void testLocate() { NetworkTopology topology = new FileBasedNetworkTopology(topologyFile, new Duration(1, DAYS), new TestingTicker()); - assertEquals(topology.locate(HostAddress.fromString("0.0.0.0")), new NetworkLocation()); - assertEquals(topology.locate(HostAddress.fromString("not-exist.example.com")), new NetworkLocation()); + assertThat(topology.locate(HostAddress.fromString("0.0.0.0"))).isEqualTo(new NetworkLocation()); + assertThat(topology.locate(HostAddress.fromString("not-exist.example.com"))).isEqualTo(new NetworkLocation()); - assertEquals(topology.locate(HostAddress.fromString("192.168.0.1")), new NetworkLocation("region1", "rack1", "machine1")); - assertEquals(topology.locate(HostAddress.fromString("192.168.0.2")), new NetworkLocation("region1", "rack1", "machine2")); - assertEquals(topology.locate(HostAddress.fromString("hdfs01.example.com")), new NetworkLocation("region2", "rack2", "machine3")); + assertThat(topology.locate(HostAddress.fromString("192.168.0.1"))).isEqualTo(new NetworkLocation("region1", "rack1", "machine1")); + assertThat(topology.locate(HostAddress.fromString("192.168.0.2"))).isEqualTo(new NetworkLocation("region1", "rack1", "machine2")); + assertThat(topology.locate(HostAddress.fromString("hdfs01.example.com"))).isEqualTo(new NetworkLocation("region2", "rack2", "machine3")); - assertEquals(topology.locate(HostAddress.fromString("192.168.0.1:8080")), new NetworkLocation("region1", "rack1", "machine1")); + assertThat(topology.locate(HostAddress.fromString("192.168.0.1:8080"))).isEqualTo(new NetworkLocation("region1", "rack1", "machine1")); } @Test @@ -65,20 +65,20 @@ public void testRefresh() TestingTicker ticker = new TestingTicker(); FileBasedNetworkTopology topology = new FileBasedNetworkTopology(tempFile.file(), new Duration(1, DAYS), ticker); - assertEquals(topology.locate(HostAddress.fromString("not-exist.example.com")), new NetworkLocation()); - assertEquals(topology.locate(HostAddress.fromString("192.168.0.1")), new NetworkLocation("region1", "rack1", "machine1")); - assertEquals(topology.locate(HostAddress.fromString("192.168.0.2")), new NetworkLocation("region1", "rack1", "machine2")); - assertEquals(topology.locate(HostAddress.fromString("192.168.0.3")), new NetworkLocation()); + assertThat(topology.locate(HostAddress.fromString("not-exist.example.com"))).isEqualTo(new NetworkLocation()); + assertThat(topology.locate(HostAddress.fromString("192.168.0.1"))).isEqualTo(new NetworkLocation("region1", "rack1", "machine1")); + assertThat(topology.locate(HostAddress.fromString("192.168.0.2"))).isEqualTo(new NetworkLocation("region1", "rack1", "machine2")); + assertThat(topology.locate(HostAddress.fromString("192.168.0.3"))).isEqualTo(new NetworkLocation()); - assertEquals(topology.locate(HostAddress.fromString("new")), new NetworkLocation()); + assertThat(topology.locate(HostAddress.fromString("new"))).isEqualTo(new NetworkLocation()); Files.copy(topologyNewFile, tempFile.file()); ticker.increment(1, TimeUnit.DAYS); - assertEquals(topology.locate(HostAddress.fromString("new")), new NetworkLocation("new", "rack", "machine")); - assertEquals(topology.locate(HostAddress.fromString("not-exist.example.com")), new NetworkLocation()); - assertEquals(topology.locate(HostAddress.fromString("192.168.0.1")), new NetworkLocation("region1", "rack1", "machine5")); - assertEquals(topology.locate(HostAddress.fromString("192.168.0.2")), new NetworkLocation()); - assertEquals(topology.locate(HostAddress.fromString("192.168.0.3")), new NetworkLocation("region1", "rack1", "machine6")); + assertThat(topology.locate(HostAddress.fromString("new"))).isEqualTo(new NetworkLocation("new", "rack", "machine")); + assertThat(topology.locate(HostAddress.fromString("not-exist.example.com"))).isEqualTo(new NetworkLocation()); + assertThat(topology.locate(HostAddress.fromString("192.168.0.1"))).isEqualTo(new NetworkLocation("region1", "rack1", "machine5")); + assertThat(topology.locate(HostAddress.fromString("192.168.0.2"))).isEqualTo(new NetworkLocation()); + assertThat(topology.locate(HostAddress.fromString("192.168.0.3"))).isEqualTo(new NetworkLocation("region1", "rack1", "machine6")); } } } diff --git a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestFixedCountScheduler.java b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestFixedCountScheduler.java index a30b86acbc45..e690782020c1 100644 --- a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestFixedCountScheduler.java +++ b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestFixedCountScheduler.java @@ -38,10 +38,9 @@ import static io.airlift.concurrent.Threads.daemonThreadsNamed; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -76,10 +75,10 @@ public void testSingleNode() generateRandomNodes(1)); ScheduleResult result = nodeScheduler.schedule(); - assertTrue(result.isFinished()); - assertTrue(result.getBlocked().isDone()); - assertEquals(result.getNewTasks().size(), 1); - assertTrue(result.getNewTasks().iterator().next().getNodeId().equals("other 0")); + assertThat(result.isFinished()).isTrue(); + assertThat(result.getBlocked().isDone()).isTrue(); + assertThat(result.getNewTasks().size()).isEqualTo(1); + assertThat(result.getNewTasks().iterator().next().getNodeId().equals("other 0")).isTrue(); } @Test @@ -93,10 +92,10 @@ public void testMultipleNodes() generateRandomNodes(5)); ScheduleResult result = nodeScheduler.schedule(); - assertTrue(result.isFinished()); - assertTrue(result.getBlocked().isDone()); - assertEquals(result.getNewTasks().size(), 5); - assertEquals(result.getNewTasks().stream().map(RemoteTask::getNodeId).collect(toImmutableSet()).size(), 5); + assertThat(result.isFinished()).isTrue(); + assertThat(result.getBlocked().isDone()).isTrue(); + assertThat(result.getNewTasks().size()).isEqualTo(5); + assertThat(result.getNewTasks().stream().map(RemoteTask::getNodeId).collect(toImmutableSet()).size()).isEqualTo(5); } private static List generateRandomNodes(int count) diff --git a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestMultiSourcePartitionedScheduler.java b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestMultiSourcePartitionedScheduler.java index 7cbe88fa94ac..749be8e011b4 100644 --- a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestMultiSourcePartitionedScheduler.java +++ b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestMultiSourcePartitionedScheduler.java @@ -118,11 +118,9 @@ import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -187,19 +185,19 @@ public void testScheduleSplitsBatchedNoBlocking() assertEffectivelyFinished(scheduleResult, scheduler); } else { - assertFalse(scheduleResult.isFinished()); + assertThat(scheduleResult.isFinished()).isFalse(); } // never blocks - assertTrue(scheduleResult.getBlocked().isDone()); + assertThat(scheduleResult.getBlocked().isDone()).isTrue(); // first three splits create new tasks - assertEquals(scheduleResult.getNewTasks().size(), i == 0 ? 3 : 0); + assertThat(scheduleResult.getNewTasks().size()).isEqualTo(i == 0 ? 3 : 0); } for (RemoteTask remoteTask : stage.getAllTasks()) { PartitionedSplitsInfo splitsInfo = remoteTask.getPartitionedSplitsInfo(); - assertEquals(splitsInfo.getCount(), 40); + assertThat(splitsInfo.getCount()).isEqualTo(40); } stage.abort(); } @@ -220,31 +218,31 @@ public void testScheduleSplitsBatchedBlockingSplitSource() 5); ScheduleResult scheduleResult = scheduler.schedule(); - assertFalse(scheduleResult.isFinished()); - assertTrue(scheduleResult.getBlocked().isDone()); - assertEquals(scheduleResult.getNewTasks().size(), 3); + assertThat(scheduleResult.isFinished()).isFalse(); + assertThat(scheduleResult.getBlocked().isDone()).isTrue(); + assertThat(scheduleResult.getNewTasks().size()).isEqualTo(3); scheduleResult = scheduler.schedule(); - assertFalse(scheduleResult.isFinished()); - assertFalse(scheduleResult.getBlocked().isDone()); - assertEquals(scheduleResult.getNewTasks().size(), 0); - assertEquals(scheduleResult.getBlockedReason(), Optional.of(WAITING_FOR_SOURCE)); + assertThat(scheduleResult.isFinished()).isFalse(); + assertThat(scheduleResult.getBlocked().isDone()).isFalse(); + assertThat(scheduleResult.getNewTasks().size()).isEqualTo(0); + assertThat(scheduleResult.getBlockedReason()).isEqualTo(Optional.of(WAITING_FOR_SOURCE)); blockingSplitSource.addSplits(2, true); scheduleResult = scheduler.schedule(); - assertTrue(scheduleResult.getBlocked().isDone()); - assertEquals(scheduleResult.getSplitsScheduled(), 2); - assertEquals(scheduleResult.getNewTasks().size(), 0); - assertEquals(scheduleResult.getBlockedReason(), Optional.empty()); - assertTrue(scheduleResult.isFinished()); + assertThat(scheduleResult.getBlocked().isDone()).isTrue(); + assertThat(scheduleResult.getSplitsScheduled()).isEqualTo(2); + assertThat(scheduleResult.getNewTasks().size()).isEqualTo(0); + assertThat(scheduleResult.getBlockedReason()).isEqualTo(Optional.empty()); + assertThat(scheduleResult.isFinished()).isTrue(); assertPartitionedSplitCount(stage, 12); assertEffectivelyFinished(scheduleResult, scheduler); for (RemoteTask remoteTask : stage.getAllTasks()) { PartitionedSplitsInfo splitsInfo = remoteTask.getPartitionedSplitsInfo(); - assertEquals(splitsInfo.getCount(), 4); + assertThat(splitsInfo.getCount()).isEqualTo(4); } stage.abort(); } @@ -264,13 +262,13 @@ public void testScheduleSplitsTasksAreFull() 200); ScheduleResult scheduleResult = scheduler.schedule(); - assertEquals(scheduleResult.getSplitsScheduled(), 300); - assertFalse(scheduleResult.isFinished()); - assertFalse(scheduleResult.getBlocked().isDone()); - assertEquals(scheduleResult.getNewTasks().size(), 3); - assertEquals(scheduleResult.getBlockedReason(), Optional.of(SPLIT_QUEUES_FULL)); + assertThat(scheduleResult.getSplitsScheduled()).isEqualTo(300); + assertThat(scheduleResult.isFinished()).isFalse(); + assertThat(scheduleResult.getBlocked().isDone()).isFalse(); + assertThat(scheduleResult.getNewTasks().size()).isEqualTo(3); + assertThat(scheduleResult.getBlockedReason()).isEqualTo(Optional.of(SPLIT_QUEUES_FULL)); - assertEquals(stage.getAllTasks().stream().mapToInt(task -> task.getPartitionedSplitsInfo().getCount()).sum(), 300); + assertThat(stage.getAllTasks().stream().mapToInt(task -> task.getPartitionedSplitsInfo().getCount()).sum()).isEqualTo(300); stage.abort(); } @@ -301,13 +299,13 @@ public void testBalancedSplitAssignment() firstSplitSource.addSplits(15, true); ScheduleResult scheduleResult = scheduler.schedule(); - assertFalse(scheduleResult.getBlocked().isDone()); - assertEquals(scheduleResult.getNewTasks().size(), 3); - assertEquals(firstStage.getAllTasks().size(), 3); + assertThat(scheduleResult.getBlocked().isDone()).isFalse(); + assertThat(scheduleResult.getNewTasks().size()).isEqualTo(3); + assertThat(firstStage.getAllTasks().size()).isEqualTo(3); for (RemoteTask remoteTask : firstStage.getAllTasks()) { PartitionedSplitsInfo splitsInfo = remoteTask.getPartitionedSplitsInfo(); // All splits were balanced between nodes - assertEquals(splitsInfo.getCount(), 5); + assertThat(splitsInfo.getCount()).isEqualTo(5); } // Add new node @@ -320,15 +318,15 @@ public void testBalancedSplitAssignment() scheduleResult = scheduler.schedule(); assertEffectivelyFinished(scheduleResult, scheduler); - assertTrue(scheduleResult.getBlocked().isDone()); - assertTrue(scheduleResult.isFinished()); - assertEquals(scheduleResult.getNewTasks().size(), 1); - assertEquals(firstStage.getAllTasks().size(), 4); + assertThat(scheduleResult.getBlocked().isDone()).isTrue(); + assertThat(scheduleResult.isFinished()).isTrue(); + assertThat(scheduleResult.getNewTasks().size()).isEqualTo(1); + assertThat(firstStage.getAllTasks().size()).isEqualTo(4); - assertEquals(firstStage.getAllTasks().get(0).getPartitionedSplitsInfo().getCount(), 5); - assertEquals(firstStage.getAllTasks().get(1).getPartitionedSplitsInfo().getCount(), 5); - assertEquals(firstStage.getAllTasks().get(2).getPartitionedSplitsInfo().getCount(), 5); - assertEquals(firstStage.getAllTasks().get(3).getPartitionedSplitsInfo().getCount(), 3); + assertThat(firstStage.getAllTasks().get(0).getPartitionedSplitsInfo().getCount()).isEqualTo(5); + assertThat(firstStage.getAllTasks().get(1).getPartitionedSplitsInfo().getCount()).isEqualTo(5); + assertThat(firstStage.getAllTasks().get(2).getPartitionedSplitsInfo().getCount()).isEqualTo(5); + assertThat(firstStage.getAllTasks().get(3).getPartitionedSplitsInfo().getCount()).isEqualTo(3); // Second source produces PlanFragment secondPlan = createFragment(); @@ -341,13 +339,13 @@ public void testBalancedSplitAssignment() scheduleResult = secondScheduler.schedule(); assertEffectivelyFinished(scheduleResult, secondScheduler); - assertTrue(scheduleResult.getBlocked().isDone()); - assertTrue(scheduleResult.isFinished()); - assertEquals(scheduleResult.getNewTasks().size(), 4); - assertEquals(secondStage.getAllTasks().size(), 4); + assertThat(scheduleResult.getBlocked().isDone()).isTrue(); + assertThat(scheduleResult.isFinished()).isTrue(); + assertThat(scheduleResult.getNewTasks().size()).isEqualTo(4); + assertThat(secondStage.getAllTasks().size()).isEqualTo(4); for (RemoteTask task : secondStage.getAllTasks()) { - assertEquals(task.getPartitionedSplitsInfo().getCount(), 5); + assertThat(task.getPartitionedSplitsInfo().getCount()).isEqualTo(5); } firstStage.abort(); secondStage.abort(); @@ -369,7 +367,7 @@ public void testScheduleEmptySources() ScheduleResult scheduleResult = scheduler.schedule(); // If both split sources produce no splits then internal schedulers add one split - it can be expected by some operators e.g. AggregationOperator - assertEquals(scheduleResult.getNewTasks().size(), 2); + assertThat(scheduleResult.getNewTasks().size()).isEqualTo(2); assertEffectivelyFinished(scheduleResult, scheduler); stage.abort(); @@ -406,20 +404,20 @@ public void testDynamicFiltersUnblockedOnBlockedBuildSource() symbolAllocator.getTypes()); // make sure dynamic filtering collecting task was created immediately - assertEquals(stage.getState(), PLANNED); + assertThat(stage.getState()).isEqualTo(PLANNED); scheduler.start(); - assertEquals(stage.getAllTasks().size(), 1); - assertEquals(stage.getState(), SCHEDULING); + assertThat(stage.getAllTasks().size()).isEqualTo(1); + assertThat(stage.getState()).isEqualTo(SCHEDULING); // make sure dynamic filter is initially blocked - assertFalse(dynamicFilter.isBlocked().isDone()); + assertThat(dynamicFilter.isBlocked().isDone()).isFalse(); // make sure dynamic filter is unblocked due to build side source tasks being blocked ScheduleResult scheduleResult = scheduler.schedule(); - assertTrue(dynamicFilter.isBlocked().isDone()); + assertThat(dynamicFilter.isBlocked().isDone()).isTrue(); // no new probe splits should be scheduled - assertEquals(scheduleResult.getSplitsScheduled(), 0); + assertThat(scheduleResult.getSplitsScheduled()).isEqualTo(0); } @Test @@ -443,40 +441,40 @@ public void testNoNewTaskScheduledWhenChildStageBufferIsOverUtilized() 200); // the queues of 3 running nodes should be full ScheduleResult scheduleResult = scheduler.schedule(); - assertEquals(scheduleResult.getBlockedReason(), Optional.of(SPLIT_QUEUES_FULL)); - assertEquals(scheduleResult.getNewTasks().size(), 3); - assertEquals(scheduleResult.getSplitsScheduled(), 300); + assertThat(scheduleResult.getBlockedReason()).isEqualTo(Optional.of(SPLIT_QUEUES_FULL)); + assertThat(scheduleResult.getNewTasks().size()).isEqualTo(3); + assertThat(scheduleResult.getSplitsScheduled()).isEqualTo(300); for (RemoteTask remoteTask : scheduleResult.getNewTasks()) { PartitionedSplitsInfo splitsInfo = remoteTask.getPartitionedSplitsInfo(); - assertEquals(splitsInfo.getCount(), 100); + assertThat(splitsInfo.getCount()).isEqualTo(100); } // new node added but 1 child's output buffer is overutilized - so lockdown the tasks nodeManager.addNodes(new InternalNode("other4", URI.create("http://127.0.0.4:14"), NodeVersion.UNKNOWN, false)); scheduleResult = scheduler.schedule(); - assertEquals(scheduleResult.getBlockedReason(), Optional.of(SPLIT_QUEUES_FULL)); - assertEquals(scheduleResult.getNewTasks().size(), 0); - assertEquals(scheduleResult.getSplitsScheduled(), 0); + assertThat(scheduleResult.getBlockedReason()).isEqualTo(Optional.of(SPLIT_QUEUES_FULL)); + assertThat(scheduleResult.getNewTasks().size()).isEqualTo(0); + assertThat(scheduleResult.getSplitsScheduled()).isEqualTo(0); } private static void assertPartitionedSplitCount(StageExecution stage, int expectedPartitionedSplitCount) { - assertEquals(stage.getAllTasks().stream().mapToInt(remoteTask -> remoteTask.getPartitionedSplitsInfo().getCount()).sum(), expectedPartitionedSplitCount); + assertThat(stage.getAllTasks().stream().mapToInt(remoteTask -> remoteTask.getPartitionedSplitsInfo().getCount()).sum()).isEqualTo(expectedPartitionedSplitCount); } private static void assertEffectivelyFinished(ScheduleResult scheduleResult, StageScheduler scheduler) { if (scheduleResult.isFinished()) { - assertTrue(scheduleResult.getBlocked().isDone()); + assertThat(scheduleResult.getBlocked().isDone()).isTrue(); return; } - assertTrue(scheduleResult.getBlocked().isDone()); + assertThat(scheduleResult.getBlocked().isDone()).isTrue(); ScheduleResult nextScheduleResult = scheduler.schedule(); - assertTrue(nextScheduleResult.isFinished()); - assertTrue(nextScheduleResult.getBlocked().isDone()); - assertEquals(nextScheduleResult.getNewTasks().size(), 0); - assertEquals(nextScheduleResult.getSplitsScheduled(), 0); + assertThat(nextScheduleResult.isFinished()).isTrue(); + assertThat(nextScheduleResult.getBlocked().isDone()).isTrue(); + assertThat(nextScheduleResult.getNewTasks().size()).isEqualTo(0); + assertThat(nextScheduleResult.getSplitsScheduled()).isEqualTo(0); } private StageScheduler prepareScheduler( diff --git a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestPartitionedPipelinedOutputBufferManager.java b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestPartitionedPipelinedOutputBufferManager.java index 0f90ef1223ed..c2ee6ae4e70f 100644 --- a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestPartitionedPipelinedOutputBufferManager.java +++ b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestPartitionedPipelinedOutputBufferManager.java @@ -20,10 +20,8 @@ import java.util.Map; import static io.trino.sql.planner.SystemPartitioningHandle.FIXED_HASH_DISTRIBUTION; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; public class TestPartitionedPipelinedOutputBufferManager { @@ -55,13 +53,13 @@ public void test() private static void assertOutputBuffers(PipelinedOutputBuffers outputBuffers) { - assertNotNull(outputBuffers); - assertTrue(outputBuffers.getVersion() > 0); - assertTrue(outputBuffers.isNoMoreBufferIds()); + assertThat(outputBuffers).isNotNull(); + assertThat(outputBuffers.getVersion() > 0).isTrue(); + assertThat(outputBuffers.isNoMoreBufferIds()).isTrue(); Map buffers = outputBuffers.getBuffers(); - assertEquals(buffers.size(), 4); + assertThat(buffers.size()).isEqualTo(4); for (int partition = 0; partition < 4; partition++) { - assertEquals(buffers.get(new OutputBufferId(partition)), Integer.valueOf(partition)); + assertThat(buffers.get(new OutputBufferId(partition))).isEqualTo(Integer.valueOf(partition)); } } } diff --git a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestScaledPipelinedOutputBufferManager.java b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestScaledPipelinedOutputBufferManager.java index fba2599dea50..b8a53e7431a6 100644 --- a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestScaledPipelinedOutputBufferManager.java +++ b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestScaledPipelinedOutputBufferManager.java @@ -17,7 +17,7 @@ import org.junit.jupiter.api.Test; import static io.trino.execution.buffer.PipelinedOutputBuffers.BufferType.ARBITRARY; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link ScaledPipelinedOutputBufferManager}. @@ -28,34 +28,34 @@ public class TestScaledPipelinedOutputBufferManager public void test() { ScaledPipelinedOutputBufferManager scaledPipelinedOutputBufferManager = new ScaledPipelinedOutputBufferManager(); - assertEquals(scaledPipelinedOutputBufferManager.getOutputBuffers(), PipelinedOutputBuffers.createInitial(ARBITRARY)); + assertThat(scaledPipelinedOutputBufferManager.getOutputBuffers()).isEqualTo(PipelinedOutputBuffers.createInitial(ARBITRARY)); scaledPipelinedOutputBufferManager.addOutputBuffer(new PipelinedOutputBuffers.OutputBufferId(0)); PipelinedOutputBuffers expectedOutputBuffers = PipelinedOutputBuffers.createInitial(ARBITRARY).withBuffer(new PipelinedOutputBuffers.OutputBufferId(0), 0); - assertEquals(scaledPipelinedOutputBufferManager.getOutputBuffers(), expectedOutputBuffers); + assertThat(scaledPipelinedOutputBufferManager.getOutputBuffers()).isEqualTo(expectedOutputBuffers); scaledPipelinedOutputBufferManager.addOutputBuffer(new PipelinedOutputBuffers.OutputBufferId(1)); scaledPipelinedOutputBufferManager.addOutputBuffer(new PipelinedOutputBuffers.OutputBufferId(2)); expectedOutputBuffers = expectedOutputBuffers.withBuffer(new PipelinedOutputBuffers.OutputBufferId(1), 1); expectedOutputBuffers = expectedOutputBuffers.withBuffer(new PipelinedOutputBuffers.OutputBufferId(2), 2); - assertEquals(scaledPipelinedOutputBufferManager.getOutputBuffers(), expectedOutputBuffers); + assertThat(scaledPipelinedOutputBufferManager.getOutputBuffers()).isEqualTo(expectedOutputBuffers); // set no more buffers scaledPipelinedOutputBufferManager.addOutputBuffer(new PipelinedOutputBuffers.OutputBufferId(3)); scaledPipelinedOutputBufferManager.noMoreBuffers(); expectedOutputBuffers = expectedOutputBuffers.withBuffer(new PipelinedOutputBuffers.OutputBufferId(3), 3); expectedOutputBuffers = expectedOutputBuffers.withNoMoreBufferIds(); - assertEquals(scaledPipelinedOutputBufferManager.getOutputBuffers(), expectedOutputBuffers); + assertThat(scaledPipelinedOutputBufferManager.getOutputBuffers()).isEqualTo(expectedOutputBuffers); // try to add another buffer, which should not result in an error // and output buffers should not change scaledPipelinedOutputBufferManager.addOutputBuffer(new PipelinedOutputBuffers.OutputBufferId(5)); - assertEquals(scaledPipelinedOutputBufferManager.getOutputBuffers(), expectedOutputBuffers); + assertThat(scaledPipelinedOutputBufferManager.getOutputBuffers()).isEqualTo(expectedOutputBuffers); // try to set no more buffers again, which should not result in an error // and output buffers should not change scaledPipelinedOutputBufferManager.noMoreBuffers(); - assertEquals(scaledPipelinedOutputBufferManager.getOutputBuffers(), expectedOutputBuffers); + assertThat(scaledPipelinedOutputBufferManager.getOutputBuffers()).isEqualTo(expectedOutputBuffers); } } diff --git a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestScaledWriterScheduler.java b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestScaledWriterScheduler.java index 441f3c2c4ebf..c507865310fb 100644 --- a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestScaledWriterScheduler.java +++ b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestScaledWriterScheduler.java @@ -59,7 +59,7 @@ import static io.trino.testing.TestingSession.testSessionBuilder; import static java.util.Objects.requireNonNull; import static java.util.concurrent.Executors.newScheduledThreadPool; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestScaledWriterScheduler { @@ -76,7 +76,7 @@ public void testGetNewTaskCountWithUnderutilizedTasksWithoutSkewness() TaskStatus taskStatus3 = buildTaskStatus(false, 12345L); ScaledWriterScheduler scaledWriterScheduler = buildScaleWriterSchedulerWithInitialTasks(taskStatus1, taskStatus2, taskStatus3); - assertEquals(scaledWriterScheduler.schedule().getNewTasks().size(), 0); + assertThat(scaledWriterScheduler.schedule().getNewTasks().size()).isEqualTo(0); } @Test @@ -87,7 +87,7 @@ public void testGetNewTaskCountWithOverutilizedTasksWithoutSkewness() TaskStatus taskStatus3 = buildTaskStatus(false, 12345L); ScaledWriterScheduler scaledWriterScheduler = buildScaleWriterSchedulerWithInitialTasks(taskStatus1, taskStatus2, taskStatus3); - assertEquals(scaledWriterScheduler.schedule().getNewTasks().size(), 1); + assertThat(scaledWriterScheduler.schedule().getNewTasks().size()).isEqualTo(1); } @Test @@ -98,7 +98,7 @@ public void testGetNewTaskCountWithOverutilizedSkewedTaskAndUnderutilizedNonSkew TaskStatus taskStatus3 = buildTaskStatus(false, 123456L); ScaledWriterScheduler scaledWriterScheduler = buildScaleWriterSchedulerWithInitialTasks(taskStatus1, taskStatus2, taskStatus3); - assertEquals(scaledWriterScheduler.schedule().getNewTasks().size(), 1); + assertThat(scaledWriterScheduler.schedule().getNewTasks().size()).isEqualTo(1); } @Test @@ -109,7 +109,7 @@ public void testGetNewTaskCountWithUnderutilizedSkewedTaskAndOverutilizedNonSkew TaskStatus taskStatus3 = buildTaskStatus(false, 1234567L); ScaledWriterScheduler scaledWriterScheduler = buildScaleWriterSchedulerWithInitialTasks(taskStatus1, taskStatus2, taskStatus3); - assertEquals(scaledWriterScheduler.schedule().getNewTasks().size(), 1); + assertThat(scaledWriterScheduler.schedule().getNewTasks().size()).isEqualTo(1); } @Test @@ -121,7 +121,7 @@ public void testGetNewTaskCountWhenWriterDataProcessedIsGreaterThanMinForScaleUp ScaledWriterScheduler scaledWriterScheduler = buildScaleWriterSchedulerWithInitialTasks(taskStatus1, taskStatus2, taskStatus3); // Scale up will happen - assertEquals(scaledWriterScheduler.schedule().getNewTasks().size(), 1); + assertThat(scaledWriterScheduler.schedule().getNewTasks().size()).isEqualTo(1); } @Test @@ -134,7 +134,7 @@ public void testGetNewTaskCountWhenWriterDataProcessedIsLessThanMinForScaleUp() ScaledWriterScheduler scaledWriterScheduler = buildScaleWriterSchedulerWithInitialTasks(taskStatus1, taskStatus2, taskStatus3); // Scale up will not happen because for one of the task there are two local writers which makes the // minWrittenBytes for scaling up to (2 * writerScalingMinDataProcessed) that is greater than writerInputDataSize. - assertEquals(scaledWriterScheduler.schedule().getNewTasks().size(), 0); + assertThat(scaledWriterScheduler.schedule().getNewTasks().size()).isEqualTo(0); } @Test @@ -146,7 +146,7 @@ public void testGetNewTaskCountWhenExistingWriterTaskMaxWriterCountIsEmpty() ScaledWriterScheduler scaledWriterScheduler = buildScaleWriterSchedulerWithInitialTasks(taskStatus1, taskStatus2, taskStatus3); // Scale up will not happen because one of the existing writer task isn't initialized yet with maxWriterCount. - assertEquals(scaledWriterScheduler.schedule().getNewTasks().size(), 0); + assertThat(scaledWriterScheduler.schedule().getNewTasks().size()).isEqualTo(0); } @Test @@ -157,7 +157,7 @@ public void testNewTaskCountWhenNodesUpperLimitIsNotExceeded() ScaledWriterScheduler scaledWriterScheduler = buildScaledWriterScheduler(taskStatusProvider, 2); scaledWriterScheduler.schedule(); - assertEquals(scaledWriterScheduler.schedule().getNewTasks().size(), 1); + assertThat(scaledWriterScheduler.schedule().getNewTasks().size()).isEqualTo(1); } @Test @@ -168,7 +168,7 @@ public void testNewTaskCountWhenNodesUpperLimitIsExceeded() ScaledWriterScheduler scaledWriterScheduler = buildScaledWriterScheduler(taskStatusProvider, 1); scaledWriterScheduler.schedule(); - assertEquals(scaledWriterScheduler.schedule().getNewTasks().size(), 0); + assertThat(scaledWriterScheduler.schedule().getNewTasks().size()).isEqualTo(0); } private ScaledWriterScheduler buildScaleWriterSchedulerWithInitialTasks(TaskStatus taskStatus1, TaskStatus taskStatus2, TaskStatus taskStatus3) @@ -176,13 +176,13 @@ private ScaledWriterScheduler buildScaleWriterSchedulerWithInitialTasks(TaskStat AtomicReference> taskStatusProvider = new AtomicReference<>(ImmutableList.of()); ScaledWriterScheduler scaledWriterScheduler = buildScaledWriterScheduler(taskStatusProvider, 100); - assertEquals(scaledWriterScheduler.schedule().getNewTasks().size(), 1); + assertThat(scaledWriterScheduler.schedule().getNewTasks().size()).isEqualTo(1); taskStatusProvider.set(ImmutableList.of(taskStatus1)); - assertEquals(scaledWriterScheduler.schedule().getNewTasks().size(), 1); + assertThat(scaledWriterScheduler.schedule().getNewTasks().size()).isEqualTo(1); taskStatusProvider.set(ImmutableList.of(taskStatus1, taskStatus2)); - assertEquals(scaledWriterScheduler.schedule().getNewTasks().size(), 1); + assertThat(scaledWriterScheduler.schedule().getNewTasks().size()).isEqualTo(1); taskStatusProvider.set(ImmutableList.of(taskStatus1, taskStatus2, taskStatus3)); return scaledWriterScheduler; diff --git a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestSourcePartitionedScheduler.java b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestSourcePartitionedScheduler.java index 5338d236642e..50f18aaca22a 100644 --- a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestSourcePartitionedScheduler.java +++ b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestSourcePartitionedScheduler.java @@ -110,11 +110,9 @@ import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -166,7 +164,7 @@ public void testScheduleNoSplits() ScheduleResult scheduleResult = scheduler.schedule(); - assertEquals(scheduleResult.getNewTasks().size(), 1); + assertThat(scheduleResult.getNewTasks().size()).isEqualTo(1); assertEffectivelyFinished(scheduleResult, scheduler); stage.abort(); @@ -182,13 +180,13 @@ public void testDoesNotScheduleEmptySplit() ConnectorSplitSource splitSource = createFixedSplitSource(2, TestingSplit::createRemoteSplit); StageScheduler scheduler = getSourcePartitionedScheduler(splitSource, stage, nodeManager, nodeTaskMap, 1, STAGE); - assertEquals(scheduler.schedule().getNewTasks().size(), 1); + assertThat(scheduler.schedule().getNewTasks().size()).isEqualTo(1); // ensure that next batch size fetched by scheduler will be empty and last splitSource.getNextBatch(1); ScheduleResult scheduleResult = scheduler.schedule(); - assertEquals(scheduleResult.getNewTasks().size(), 0); + assertThat(scheduleResult.getNewTasks().size()).isEqualTo(0); assertEffectivelyFinished(scheduleResult, scheduler); @@ -212,22 +210,22 @@ public void testScheduleSplitsOneAtATime() assertEffectivelyFinished(scheduleResult, scheduler); } else { - assertFalse(scheduleResult.isFinished()); + assertThat(scheduleResult.isFinished()).isFalse(); } // never blocks - assertTrue(scheduleResult.getBlocked().isDone()); + assertThat(scheduleResult.getBlocked().isDone()).isTrue(); // first three splits create new tasks - assertEquals(scheduleResult.getNewTasks().size(), i < 3 ? 1 : 0); - assertEquals(stage.getAllTasks().size(), i < 3 ? i + 1 : 3); + assertThat(scheduleResult.getNewTasks().size()).isEqualTo(i < 3 ? 1 : 0); + assertThat(stage.getAllTasks().size()).isEqualTo(i < 3 ? i + 1 : 3); assertPartitionedSplitCount(stage, min(i + 1, 60)); } for (RemoteTask remoteTask : stage.getAllTasks()) { PartitionedSplitsInfo splitsInfo = remoteTask.getPartitionedSplitsInfo(); - assertEquals(splitsInfo.getCount(), 20); + assertThat(splitsInfo.getCount()).isEqualTo(20); } stage.abort(); @@ -250,22 +248,22 @@ public void testScheduleSplitsBatched() assertEffectivelyFinished(scheduleResult, scheduler); } else { - assertFalse(scheduleResult.isFinished()); + assertThat(scheduleResult.isFinished()).isFalse(); } // never blocks - assertTrue(scheduleResult.getBlocked().isDone()); + assertThat(scheduleResult.getBlocked().isDone()).isTrue(); // first three splits create new tasks - assertEquals(scheduleResult.getNewTasks().size(), i == 0 ? 3 : 0); - assertEquals(stage.getAllTasks().size(), 3); + assertThat(scheduleResult.getNewTasks().size()).isEqualTo(i == 0 ? 3 : 0); + assertThat(stage.getAllTasks().size()).isEqualTo(3); assertPartitionedSplitCount(stage, min((i + 1) * 7, 60)); } for (RemoteTask remoteTask : stage.getAllTasks()) { PartitionedSplitsInfo splitsInfo = remoteTask.getPartitionedSplitsInfo(); - assertEquals(splitsInfo.getCount(), 20); + assertThat(splitsInfo.getCount()).isEqualTo(20); } stage.abort(); @@ -284,21 +282,21 @@ public void testScheduleSplitsBlock() for (int i = 0; i <= 60; i++) { ScheduleResult scheduleResult = scheduler.schedule(); - assertFalse(scheduleResult.isFinished()); + assertThat(scheduleResult.isFinished()).isFalse(); // blocks at 20 per node - assertEquals(scheduleResult.getBlocked().isDone(), i != 60); + assertThat(scheduleResult.getBlocked().isDone()).isEqualTo(i != 60); // first three splits create new tasks - assertEquals(scheduleResult.getNewTasks().size(), i < 3 ? 1 : 0); - assertEquals(stage.getAllTasks().size(), i < 3 ? i + 1 : 3); + assertThat(scheduleResult.getNewTasks().size()).isEqualTo(i < 3 ? 1 : 0); + assertThat(stage.getAllTasks().size()).isEqualTo(i < 3 ? i + 1 : 3); assertPartitionedSplitCount(stage, min(i + 1, 60)); } for (RemoteTask remoteTask : stage.getAllTasks()) { PartitionedSplitsInfo splitsInfo = remoteTask.getPartitionedSplitsInfo(); - assertEquals(splitsInfo.getCount(), 20); + assertThat(splitsInfo.getCount()).isEqualTo(20); } // todo rewrite MockRemoteTask to fire a tate transition when splits are cleared, and then validate blocked future completes @@ -315,15 +313,15 @@ public void testScheduleSplitsBlock() assertEffectivelyFinished(scheduleResult, scheduler); } else { - assertFalse(scheduleResult.isFinished()); + assertThat(scheduleResult.isFinished()).isFalse(); } // does not block again - assertTrue(scheduleResult.getBlocked().isDone()); + assertThat(scheduleResult.getBlocked().isDone()).isTrue(); // no additional tasks will be created - assertEquals(scheduleResult.getNewTasks().size(), 0); - assertEquals(stage.getAllTasks().size(), 3); + assertThat(scheduleResult.getNewTasks().size()).isEqualTo(0); + assertThat(stage.getAllTasks().size()).isEqualTo(3); // we dropped 20 splits so start at 40 and count to 60 assertPartitionedSplitCount(stage, min(i + 41, 60)); @@ -331,7 +329,7 @@ public void testScheduleSplitsBlock() for (RemoteTask remoteTask : stage.getAllTasks()) { PartitionedSplitsInfo splitsInfo = remoteTask.getPartitionedSplitsInfo(); - assertEquals(splitsInfo.getCount(), 20); + assertThat(splitsInfo.getCount()).isEqualTo(20); } stage.abort(); @@ -349,13 +347,13 @@ public void testScheduleSlowSplitSource() // schedule with no splits - will block ScheduleResult scheduleResult = scheduler.schedule(); - assertFalse(scheduleResult.isFinished()); - assertFalse(scheduleResult.getBlocked().isDone()); - assertEquals(scheduleResult.getNewTasks().size(), 0); - assertEquals(stage.getAllTasks().size(), 0); + assertThat(scheduleResult.isFinished()).isFalse(); + assertThat(scheduleResult.getBlocked().isDone()).isFalse(); + assertThat(scheduleResult.getNewTasks().size()).isEqualTo(0); + assertThat(stage.getAllTasks().size()).isEqualTo(0); queuedSplitSource.addSplits(1); - assertTrue(scheduleResult.getBlocked().isDone()); + assertThat(scheduleResult.getBlocked().isDone()).isTrue(); } @Test @@ -399,12 +397,12 @@ public void testWorkerBalancedSplitAssignment() ScheduleResult scheduleResult = firstScheduler.schedule(); assertEffectivelyFinished(scheduleResult, firstScheduler); - assertTrue(scheduleResult.getBlocked().isDone()); - assertEquals(scheduleResult.getNewTasks().size(), 3); - assertEquals(firstStage.getAllTasks().size(), 3); + assertThat(scheduleResult.getBlocked().isDone()).isTrue(); + assertThat(scheduleResult.getNewTasks().size()).isEqualTo(3); + assertThat(firstStage.getAllTasks().size()).isEqualTo(3); for (RemoteTask remoteTask : firstStage.getAllTasks()) { PartitionedSplitsInfo splitsInfo = remoteTask.getPartitionedSplitsInfo(); - assertEquals(splitsInfo.getCount(), 5); + assertThat(splitsInfo.getCount()).isEqualTo(5); } // Add new node @@ -418,11 +416,11 @@ public void testWorkerBalancedSplitAssignment() scheduleResult = secondScheduler.schedule(); assertEffectivelyFinished(scheduleResult, secondScheduler); - assertTrue(scheduleResult.getBlocked().isDone()); - assertEquals(scheduleResult.getNewTasks().size(), 1); - assertEquals(secondStage.getAllTasks().size(), 1); + assertThat(scheduleResult.getBlocked().isDone()).isTrue(); + assertThat(scheduleResult.getNewTasks().size()).isEqualTo(1); + assertThat(secondStage.getAllTasks().size()).isEqualTo(1); RemoteTask task = secondStage.getAllTasks().get(0); - assertEquals(task.getPartitionedSplitsInfo().getCount(), 5); + assertThat(task.getPartitionedSplitsInfo().getCount()).isEqualTo(5); firstStage.abort(); secondStage.abort(); @@ -446,12 +444,12 @@ public void testStageBalancedSplitAssignment() firstSplitSource.addSplits(15); ScheduleResult scheduleResult = firstScheduler.schedule(); - assertTrue(scheduleResult.getBlocked().isDone()); - assertEquals(scheduleResult.getNewTasks().size(), 3); - assertEquals(firstStage.getAllTasks().size(), 3); + assertThat(scheduleResult.getBlocked().isDone()).isTrue(); + assertThat(scheduleResult.getNewTasks().size()).isEqualTo(3); + assertThat(firstStage.getAllTasks().size()).isEqualTo(3); for (RemoteTask remoteTask : firstStage.getAllTasks()) { PartitionedSplitsInfo splitsInfo = remoteTask.getPartitionedSplitsInfo(); - assertEquals(splitsInfo.getCount(), 5); + assertThat(splitsInfo.getCount()).isEqualTo(5); } // Add new node @@ -463,12 +461,12 @@ public void testStageBalancedSplitAssignment() firstSplitSource.close(); scheduleResult = firstScheduler.schedule(); assertEffectivelyFinished(scheduleResult, firstScheduler); - assertTrue(scheduleResult.getBlocked().isDone()); - assertEquals(scheduleResult.getNewTasks().size(), 1); - assertEquals(firstStage.getAllTasks().size(), 4); + assertThat(scheduleResult.getBlocked().isDone()).isTrue(); + assertThat(scheduleResult.getNewTasks().size()).isEqualTo(1); + assertThat(firstStage.getAllTasks().size()).isEqualTo(4); for (RemoteTask remoteTask : firstStage.getAllTasks()) { PartitionedSplitsInfo splitsInfo = remoteTask.getPartitionedSplitsInfo(); - assertEquals(splitsInfo.getCount(), 5); + assertThat(splitsInfo.getCount()).isEqualTo(5); } // Add new node @@ -482,10 +480,10 @@ public void testStageBalancedSplitAssignment() scheduleResult = secondScheduler.schedule(); assertEffectivelyFinished(scheduleResult, secondScheduler); - assertEquals(secondStage.getAllTasks().size(), 5); + assertThat(secondStage.getAllTasks().size()).isEqualTo(5); for (RemoteTask remoteTask : secondStage.getAllTasks()) { PartitionedSplitsInfo splitsInfo = remoteTask.getPartitionedSplitsInfo(); - assertEquals(splitsInfo.getCount(), 1); + assertThat(splitsInfo.getCount()).isEqualTo(1); } firstStage.abort(); @@ -519,20 +517,20 @@ public void testNewTaskScheduledWhenChildStageBufferIsUnderutilized() // the queues of 3 running nodes should be full ScheduleResult scheduleResult = scheduler.schedule(); - assertEquals(scheduleResult.getBlockedReason().get(), SPLIT_QUEUES_FULL); - assertEquals(scheduleResult.getNewTasks().size(), 3); - assertEquals(scheduleResult.getSplitsScheduled(), 3 * 256); + assertThat(scheduleResult.getBlockedReason().get()).isEqualTo(SPLIT_QUEUES_FULL); + assertThat(scheduleResult.getNewTasks().size()).isEqualTo(3); + assertThat(scheduleResult.getSplitsScheduled()).isEqualTo(3 * 256); for (RemoteTask remoteTask : scheduleResult.getNewTasks()) { PartitionedSplitsInfo splitsInfo = remoteTask.getPartitionedSplitsInfo(); - assertEquals(splitsInfo.getCount(), 256); + assertThat(splitsInfo.getCount()).isEqualTo(256); } // new node added - the pending splits should go to it since the child tasks are not blocked nodeManager.addNodes(new InternalNode("other4", URI.create("http://127.0.0.4:14"), NodeVersion.UNKNOWN, false)); scheduleResult = scheduler.schedule(); - assertEquals(scheduleResult.getNewTasks().size(), 1); - assertEquals(scheduleResult.getBlockedReason().get(), SPLIT_QUEUES_FULL); // split queue is full but still the source task creation isn't blocked - assertEquals(scheduleResult.getSplitsScheduled(), 256); + assertThat(scheduleResult.getNewTasks().size()).isEqualTo(1); + assertThat(scheduleResult.getBlockedReason().get()).isEqualTo(SPLIT_QUEUES_FULL); // split queue is full but still the source task creation isn't blocked + assertThat(scheduleResult.getSplitsScheduled()).isEqualTo(256); } @Test @@ -562,20 +560,20 @@ public void testNoNewTaskScheduledWhenChildStageBufferIsOverutilized() // the queues of 3 running nodes should be full ScheduleResult scheduleResult = scheduler.schedule(); - assertEquals(scheduleResult.getBlockedReason().get(), SPLIT_QUEUES_FULL); - assertEquals(scheduleResult.getNewTasks().size(), 3); - assertEquals(scheduleResult.getSplitsScheduled(), 768); + assertThat(scheduleResult.getBlockedReason().get()).isEqualTo(SPLIT_QUEUES_FULL); + assertThat(scheduleResult.getNewTasks().size()).isEqualTo(3); + assertThat(scheduleResult.getSplitsScheduled()).isEqualTo(768); for (RemoteTask remoteTask : scheduleResult.getNewTasks()) { PartitionedSplitsInfo splitsInfo = remoteTask.getPartitionedSplitsInfo(); - assertEquals(splitsInfo.getCount(), 256); + assertThat(splitsInfo.getCount()).isEqualTo(256); } // new node added but 1 child's output buffer is overutilized - so lockdown the tasks nodeManager.addNodes(new InternalNode("other4", URI.create("http://127.0.0.4:14"), NodeVersion.UNKNOWN, false)); scheduleResult = scheduler.schedule(); - assertEquals(scheduleResult.getBlockedReason().get(), SPLIT_QUEUES_FULL); - assertEquals(scheduleResult.getNewTasks().size(), 0); - assertEquals(scheduleResult.getSplitsScheduled(), 0); + assertThat(scheduleResult.getBlockedReason().get()).isEqualTo(SPLIT_QUEUES_FULL); + assertThat(scheduleResult.getNewTasks().size()).isEqualTo(0); + assertThat(scheduleResult.getSplitsScheduled()).isEqualTo(0); } @Test @@ -611,40 +609,40 @@ public void testDynamicFiltersUnblockedOnBlockedBuildSource() symbolAllocator.getTypes()); // make sure dynamic filtering collecting task was created immediately - assertEquals(stage.getState(), PLANNED); + assertThat(stage.getState()).isEqualTo(PLANNED); scheduler.start(); - assertEquals(stage.getAllTasks().size(), 1); - assertEquals(stage.getState(), SCHEDULING); + assertThat(stage.getAllTasks().size()).isEqualTo(1); + assertThat(stage.getState()).isEqualTo(SCHEDULING); // make sure dynamic filter is initially blocked - assertFalse(dynamicFilter.isBlocked().isDone()); + assertThat(dynamicFilter.isBlocked().isDone()).isFalse(); // make sure dynamic filter is unblocked due to build side source tasks being blocked ScheduleResult scheduleResult = scheduler.schedule(); - assertTrue(dynamicFilter.isBlocked().isDone()); + assertThat(dynamicFilter.isBlocked().isDone()).isTrue(); // no new probe splits should be scheduled - assertEquals(scheduleResult.getSplitsScheduled(), 0); + assertThat(scheduleResult.getSplitsScheduled()).isEqualTo(0); } private static void assertPartitionedSplitCount(StageExecution stage, int expectedPartitionedSplitCount) { - assertEquals(stage.getAllTasks().stream().mapToInt(remoteTask -> remoteTask.getPartitionedSplitsInfo().getCount()).sum(), expectedPartitionedSplitCount); + assertThat(stage.getAllTasks().stream().mapToInt(remoteTask -> remoteTask.getPartitionedSplitsInfo().getCount()).sum()).isEqualTo(expectedPartitionedSplitCount); } private static void assertEffectivelyFinished(ScheduleResult scheduleResult, StageScheduler scheduler) { if (scheduleResult.isFinished()) { - assertTrue(scheduleResult.getBlocked().isDone()); + assertThat(scheduleResult.getBlocked().isDone()).isTrue(); return; } - assertTrue(scheduleResult.getBlocked().isDone()); + assertThat(scheduleResult.getBlocked().isDone()).isTrue(); ScheduleResult nextScheduleResult = scheduler.schedule(); - assertTrue(nextScheduleResult.isFinished()); - assertTrue(nextScheduleResult.getBlocked().isDone()); - assertEquals(nextScheduleResult.getNewTasks().size(), 0); - assertEquals(nextScheduleResult.getSplitsScheduled(), 0); + assertThat(nextScheduleResult.isFinished()).isTrue(); + assertThat(nextScheduleResult.getBlocked().isDone()).isTrue(); + assertThat(nextScheduleResult.getNewTasks().size()).isEqualTo(0); + assertThat(nextScheduleResult.getSplitsScheduled()).isEqualTo(0); } private StageScheduler getSourcePartitionedScheduler( diff --git a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestSubnetTopology.java b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestSubnetTopology.java index cff00aba12d0..5eda50517cc5 100644 --- a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestSubnetTopology.java +++ b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestSubnetTopology.java @@ -20,8 +20,8 @@ import static io.trino.execution.scheduler.NetworkLocation.ROOT_LOCATION; import static io.trino.execution.scheduler.SubnetBasedTopology.AddressProtocol.IPv4; import static io.trino.execution.scheduler.SubnetBasedTopology.AddressProtocol.IPv6; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestSubnetTopology { @@ -29,37 +29,33 @@ public class TestSubnetTopology public void testSubnetTopologyIpv4() { SubnetBasedTopology topology = new SubnetBasedTopology(ImmutableList.of(24, 25, 27), IPv4); - assertEquals( - topology.locate(HostAddress.fromString("192.168.0.172")), - new NetworkLocation("192.168.0.0", "192.168.0.128", "192.168.0.160", "192.168.0.172")); + assertThat(topology.locate(HostAddress.fromString("192.168.0.172"))).isEqualTo(new NetworkLocation("192.168.0.0", "192.168.0.128", "192.168.0.160", "192.168.0.172")); SubnetBasedTopology noTopology = new SubnetBasedTopology(ImmutableList.of(), IPv4); - assertEquals(noTopology.locate(HostAddress.fromString("192.168.0.172")), new NetworkLocation("192.168.0.172")); + assertThat(noTopology.locate(HostAddress.fromString("192.168.0.172"))).isEqualTo(new NetworkLocation("192.168.0.172")); // root location returned for IPv6 address - assertEquals(topology.locate(HostAddress.fromString("2001:db8:0:0:1:0:0:1")), ROOT_LOCATION); - assertEquals(noTopology.locate(HostAddress.fromString("2001:db8:0:0:1:0:0:1")), ROOT_LOCATION); + assertThat(topology.locate(HostAddress.fromString("2001:db8:0:0:1:0:0:1"))).isEqualTo(ROOT_LOCATION); + assertThat(noTopology.locate(HostAddress.fromString("2001:db8:0:0:1:0:0:1"))).isEqualTo(ROOT_LOCATION); } @Test public void testSubnetTopologyIpv6() { SubnetBasedTopology topology = new SubnetBasedTopology(ImmutableList.of(96, 110, 112, 120), IPv6); - assertEquals( - topology.locate(HostAddress.fromString("2001:db8::ff00:42:8329")), - new NetworkLocation( - "2001:db8::ff00:0:0", - "2001:db8::ff00:40:0", - "2001:db8::ff00:42:0", - "2001:db8::ff00:42:8300", - "2001:db8::ff00:42:8329")); + assertThat(topology.locate(HostAddress.fromString("2001:db8::ff00:42:8329"))).isEqualTo(new NetworkLocation( + "2001:db8::ff00:0:0", + "2001:db8::ff00:40:0", + "2001:db8::ff00:42:0", + "2001:db8::ff00:42:8300", + "2001:db8::ff00:42:8329")); SubnetBasedTopology noTopology = new SubnetBasedTopology(ImmutableList.of(), IPv6); - assertEquals(noTopology.locate(HostAddress.fromString("2001:db8::ff00:42:8329")), new NetworkLocation("2001:db8::ff00:42:8329")); + assertThat(noTopology.locate(HostAddress.fromString("2001:db8::ff00:42:8329"))).isEqualTo(new NetworkLocation("2001:db8::ff00:42:8329")); // root location returned for IPv4 address - assertEquals(topology.locate(HostAddress.fromString("192.168.0.1")), ROOT_LOCATION); - assertEquals(noTopology.locate(HostAddress.fromString("192.168.0.1")), ROOT_LOCATION); + assertThat(topology.locate(HostAddress.fromString("192.168.0.1"))).isEqualTo(ROOT_LOCATION); + assertThat(noTopology.locate(HostAddress.fromString("192.168.0.1"))).isEqualTo(ROOT_LOCATION); } @Test diff --git a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestUniformNodeSelector.java b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestUniformNodeSelector.java index 822371aacc34..299f8b6f153a 100644 --- a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestUniformNodeSelector.java +++ b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestUniformNodeSelector.java @@ -58,9 +58,8 @@ import static io.trino.testing.TestingHandles.TEST_CATALOG_HANDLE; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; @TestInstance(PER_METHOD) public class TestUniformNodeSelector @@ -146,7 +145,7 @@ public void testQueueSizeAdjustmentScaleDown() // assign splits, mark all splits running to trigger adjustment Multimap assignments1 = nodeSelector.computeAssignments(splits, ImmutableList.copyOf(taskMap.values())).getAssignments(); - assertEquals(assignments1.size(), 2); + assertThat(assignments1.size()).isEqualTo(2); MockRemoteTaskFactory remoteTaskFactory = new MockRemoteTaskFactory(remoteTaskExecutor, remoteTaskScheduledExecutor); int task = 0; for (InternalNode node : assignments1.keySet()) { @@ -158,10 +157,10 @@ public void testQueueSizeAdjustmentScaleDown() taskMap.put(node, remoteTask); } Set unassignedSplits = Sets.difference(splits, new HashSet<>(assignments1.values())); - assertEquals(unassignedSplits.size(), 18); + assertThat(unassignedSplits.size()).isEqualTo(18); // It's possible to add new assignments because split queue was upscaled Multimap assignments2 = nodeSelector.computeAssignments(unassignedSplits, ImmutableList.copyOf(taskMap.values())).getAssignments(); - assertEquals(assignments2.size(), 2); + assertThat(assignments2.size()).isEqualTo(2); // update remote tasks for (InternalNode node : assignments2.keySet()) { @@ -171,18 +170,18 @@ public void testQueueSizeAdjustmentScaleDown() .build()); } long maxPendingSplitsWeightPerTaskBeforeScaleDown = queueSizeAdjuster.getAdjustedMaxPendingSplitsWeightPerTask(node1.getNodeIdentifier()); - assertEquals(20, maxPendingSplitsWeightPerTaskBeforeScaleDown); + assertThat(20).isEqualTo(maxPendingSplitsWeightPerTaskBeforeScaleDown); // compute assignments called before scale down interval ticker.increment(999, TimeUnit.MILLISECONDS); Multimap assignments3 = nodeSelector.computeAssignments(unassignedSplits, ImmutableList.copyOf(taskMap.values())).getAssignments(); - assertEquals(assignments3.size(), 0); // no new assignments added to nodes - assertEquals(maxPendingSplitsWeightPerTaskBeforeScaleDown, queueSizeAdjuster.getAdjustedMaxPendingSplitsWeightPerTask(node1.getNodeIdentifier())); + assertThat(assignments3.size()).isEqualTo(0); // no new assignments added to nodes + assertThat(maxPendingSplitsWeightPerTaskBeforeScaleDown).isEqualTo(queueSizeAdjuster.getAdjustedMaxPendingSplitsWeightPerTask(node1.getNodeIdentifier())); // compute assignments called with passed scale down interval ticker.increment(1, TimeUnit.MILLISECONDS); Multimap assignments4 = nodeSelector.computeAssignments(unassignedSplits, ImmutableList.copyOf(taskMap.values())).getAssignments(); - assertEquals(assignments4.size(), 0); // no new assignments added to nodes + assertThat(assignments4.size()).isEqualTo(0); // no new assignments added to nodes long maxPendingSplitsWeightPerTaskAfterScaleDown = queueSizeAdjuster.getAdjustedMaxPendingSplitsWeightPerTask(node1.getNodeIdentifier()); - assertEquals(13, maxPendingSplitsWeightPerTaskAfterScaleDown); + assertThat(13).isEqualTo(maxPendingSplitsWeightPerTaskAfterScaleDown); } @Test @@ -201,7 +200,7 @@ public void testQueueSizeAdjustmentAllNodes() // assign splits, marked all running to trigger adjustment Multimap assignments1 = nodeSelector.computeAssignments(splits, ImmutableList.copyOf(taskMap.values())).getAssignments(); - assertEquals(assignments1.size(), 40); + assertThat(assignments1.size()).isEqualTo(40); MockRemoteTaskFactory remoteTaskFactory = new MockRemoteTaskFactory(remoteTaskExecutor, remoteTaskScheduledExecutor); int task = 0; for (InternalNode node : assignments1.keySet()) { @@ -213,7 +212,7 @@ public void testQueueSizeAdjustmentAllNodes() taskMap.put(node, remoteTask); } Set unassignedSplits = Sets.difference(splits, new HashSet<>(assignments1.values())); - assertEquals(unassignedSplits.size(), 140); + assertThat(unassignedSplits.size()).isEqualTo(140); // assign splits, mark all splits running to trigger adjustment Multimap assignments2 = nodeSelector.computeAssignments(unassignedSplits, ImmutableList.copyOf(taskMap.values())).getAssignments(); @@ -225,7 +224,7 @@ public void testQueueSizeAdjustmentAllNodes() remoteTask.startSplits(remoteTask.getPartitionedSplitsInfo().getCount()); // mark all task running } unassignedSplits = Sets.difference(unassignedSplits, new HashSet<>(assignments2.values())); - assertEquals(unassignedSplits.size(), 100); // 140 (unassigned splits) - (2 (queue size adjustment) * 10 (minPendingSplitsPerTask)) * 2 (nodes) + assertThat(unassignedSplits.size()).isEqualTo(100); // 140 (unassigned splits) - (2 (queue size adjustment) * 10 (minPendingSplitsPerTask)) * 2 (nodes) // assign splits without setting all splits running Multimap assignments3 = nodeSelector.computeAssignments(unassignedSplits, ImmutableList.copyOf(taskMap.values())).getAssignments(); @@ -236,12 +235,12 @@ public void testQueueSizeAdjustmentAllNodes() .build()); } unassignedSplits = Sets.difference(unassignedSplits, new HashSet<>(assignments3.values())); - assertEquals(unassignedSplits.size(), 20); // 100 (unassigned splits) - (4 (queue size adjustment) * 10 (minPendingSplitsPerTask)) * 2 (nodes) + assertThat(unassignedSplits.size()).isEqualTo(20); // 100 (unassigned splits) - (4 (queue size adjustment) * 10 (minPendingSplitsPerTask)) * 2 (nodes) // compute assignments with exhausted nodes Multimap assignments4 = nodeSelector.computeAssignments(unassignedSplits, ImmutableList.copyOf(taskMap.values())).getAssignments(); unassignedSplits = Sets.difference(unassignedSplits, new HashSet<>(assignments4.values())); - assertEquals(unassignedSplits.size(), 20); // no new split assignments, queued are more than 0 + assertThat(unassignedSplits.size()).isEqualTo(20); // no new split assignments, queued are more than 0 } @Test @@ -260,7 +259,7 @@ public void testQueueSizeAdjustmentOneOfAll() // assign splits, mark all splits for node1 running to trigger adjustment Multimap assignments1 = nodeSelector.computeAssignments(splits, ImmutableList.copyOf(taskMap.values())).getAssignments(); - assertEquals(assignments1.size(), 40); + assertThat(assignments1.size()).isEqualTo(40); MockRemoteTaskFactory remoteTaskFactory = new MockRemoteTaskFactory(remoteTaskExecutor, remoteTaskScheduledExecutor); int task = 0; for (InternalNode node : assignments1.keySet()) { @@ -274,7 +273,7 @@ public void testQueueSizeAdjustmentOneOfAll() taskMap.put(node, remoteTask); } Set unassignedSplits = Sets.difference(splits, new HashSet<>(assignments1.values())); - assertEquals(unassignedSplits.size(), 140); + assertThat(unassignedSplits.size()).isEqualTo(140); // assign splits, mark all splits for node1 running to trigger adjustment Multimap assignments2 = nodeSelector.computeAssignments(unassignedSplits, ImmutableList.copyOf(taskMap.values())).getAssignments(); @@ -288,9 +287,9 @@ public void testQueueSizeAdjustmentOneOfAll() } } unassignedSplits = Sets.difference(unassignedSplits, new HashSet<>(assignments2.values())); - assertEquals(unassignedSplits.size(), 120); - assertEquals(assignments2.get(node1).size(), 20); // 2x max pending - assertFalse(assignments2.containsKey(node2)); + assertThat(unassignedSplits.size()).isEqualTo(120); + assertThat(assignments2.get(node1).size()).isEqualTo(20); // 2x max pending + assertThat(assignments2.containsKey(node2)).isFalse(); // assign splits, mark all splits for node1 running to trigger adjustment Multimap assignments3 = nodeSelector.computeAssignments(unassignedSplits, ImmutableList.copyOf(taskMap.values())).getAssignments(); @@ -304,9 +303,9 @@ public void testQueueSizeAdjustmentOneOfAll() } } unassignedSplits = Sets.difference(unassignedSplits, new HashSet<>(assignments3.values())); - assertEquals(unassignedSplits.size(), 80); - assertEquals(assignments3.get(node1).size(), 40); // 4x max pending - assertFalse(assignments2.containsKey(node2)); + assertThat(unassignedSplits.size()).isEqualTo(80); + assertThat(assignments3.get(node1).size()).isEqualTo(40); // 4x max pending + assertThat(assignments2.containsKey(node2)).isFalse(); } private NodeMap createNodeMap(CatalogHandle catalogHandle) diff --git a/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestArbitraryDistributionSplitAssigner.java b/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestArbitraryDistributionSplitAssigner.java index 51d605dc674b..9357db58b121 100644 --- a/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestArbitraryDistributionSplitAssigner.java +++ b/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestArbitraryDistributionSplitAssigner.java @@ -49,8 +49,6 @@ import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; public class TestArbitraryDistributionSplitAssigner { @@ -74,7 +72,7 @@ public void testEmpty() SplitAssigner splitAssigner = createSplitAssigner(ImmutableSet.of(PARTITIONED_1), ImmutableSet.of(), 100, false); SplitAssignerTester tester = new SplitAssignerTester(); tester.update(splitAssigner.assign(PARTITIONED_1, ImmutableListMultimap.of(), true)); - assertTrue(tester.isNoMoreSplits(0, PARTITIONED_1)); + assertThat(tester.isNoMoreSplits(0, PARTITIONED_1)).isTrue(); tester.update(splitAssigner.finish()); List taskDescriptors = tester.getTaskDescriptors().orElseThrow(); assertThat(taskDescriptors).hasSize(1); @@ -84,7 +82,7 @@ public void testEmpty() splitAssigner = createSplitAssigner(ImmutableSet.of(), ImmutableSet.of(REPLICATED_1), 100, false); tester = new SplitAssignerTester(); tester.update(splitAssigner.assign(REPLICATED_1, ImmutableListMultimap.of(), true)); - assertTrue(tester.isNoMoreSplits(0, REPLICATED_1)); + assertThat(tester.isNoMoreSplits(0, REPLICATED_1)).isTrue(); tester.update(splitAssigner.finish()); taskDescriptors = tester.getTaskDescriptors().orElseThrow(); assertThat(taskDescriptors).hasSize(1); @@ -97,8 +95,8 @@ public void testEmpty() assertFalse(tester.isNoMoreSplits(0, PARTITIONED_1)); assertFalse(tester.isNoMoreSplits(0, REPLICATED_1)); tester.update(splitAssigner.assign(PARTITIONED_1, ImmutableListMultimap.of(), true)); - assertTrue(tester.isNoMoreSplits(0, PARTITIONED_1)); - assertTrue(tester.isNoMoreSplits(0, REPLICATED_1)); + assertThat(tester.isNoMoreSplits(0, PARTITIONED_1)).isTrue(); + assertThat(tester.isNoMoreSplits(0, REPLICATED_1)).isTrue(); tester.update(splitAssigner.finish()); taskDescriptors = tester.getTaskDescriptors().orElseThrow(); assertThat(taskDescriptors).hasSize(1); @@ -110,8 +108,8 @@ public void testEmpty() assertFalse(tester.isNoMoreSplits(0, PARTITIONED_1)); assertFalse(tester.isNoMoreSplits(0, REPLICATED_1)); tester.update(splitAssigner.assign(REPLICATED_1, ImmutableListMultimap.of(), true)); - assertTrue(tester.isNoMoreSplits(0, PARTITIONED_1)); - assertTrue(tester.isNoMoreSplits(0, REPLICATED_1)); + assertThat(tester.isNoMoreSplits(0, PARTITIONED_1)).isTrue(); + assertThat(tester.isNoMoreSplits(0, REPLICATED_1)).isTrue(); tester.update(splitAssigner.finish()); taskDescriptors = tester.getTaskDescriptors().orElseThrow(); assertThat(taskDescriptors).hasSize(1); @@ -127,10 +125,10 @@ public void testEmpty() assertFalse(tester.isNoMoreSplits(0, PARTITIONED_2)); assertFalse(tester.isNoMoreSplits(0, REPLICATED_2)); tester.update(splitAssigner.assign(REPLICATED_2, ImmutableListMultimap.of(), true)); - assertTrue(tester.isNoMoreSplits(0, PARTITIONED_1)); - assertTrue(tester.isNoMoreSplits(0, REPLICATED_1)); - assertTrue(tester.isNoMoreSplits(0, PARTITIONED_2)); - assertTrue(tester.isNoMoreSplits(0, REPLICATED_2)); + assertThat(tester.isNoMoreSplits(0, PARTITIONED_1)).isTrue(); + assertThat(tester.isNoMoreSplits(0, REPLICATED_1)).isTrue(); + assertThat(tester.isNoMoreSplits(0, PARTITIONED_2)).isTrue(); + assertThat(tester.isNoMoreSplits(0, REPLICATED_2)).isTrue(); tester.update(splitAssigner.finish()); taskDescriptors = tester.getTaskDescriptors().orElseThrow(); @@ -711,7 +709,7 @@ else if (currentAssignment.getSplits().size() < splitCount) { .collect(toImmutableSet()); for (int partitionId = 0; partitionId < nextPartitionId.get(); partitionId++) { if (!openAssignments.contains(partitionId)) { - assertTrue(tester.isSealed(partitionId)); + assertThat(tester.isSealed(partitionId)).isTrue(); } } } @@ -770,7 +768,7 @@ private static void assertTaskDescriptor( int expectedPartitionId, ListMultimap expectedSplits) { - assertEquals(taskDescriptor.getPartitionId(), expectedPartitionId); + assertThat(taskDescriptor.getPartitionId()).isEqualTo(expectedPartitionId); taskDescriptor.getSplits().getPlanNodeIds().forEach(planNodeId -> { // we expect single source partition for arbitrary distributed tasks assertThat(taskDescriptor.getSplits().getSplits(planNodeId).keySet()).isEqualTo(ImmutableSet.of(SINGLE_SOURCE_PARTITION_ID)); @@ -787,7 +785,7 @@ private static void assertTaskDescriptor( } } } - assertEquals(taskDescriptor.getNodeRequirements().getCatalogHandle(), Optional.of(TEST_CATALOG_HANDLE)); + assertThat(taskDescriptor.getNodeRequirements().getCatalogHandle()).isEqualTo(Optional.of(TEST_CATALOG_HANDLE)); assertThat(taskDescriptor.getNodeRequirements().getAddresses()).containsAnyElementsOf(hostRequirement == null ? ImmutableSet.of() : hostRequirement); } @@ -795,7 +793,7 @@ private static void assertSplitsEqual(ListMultimap actual, Li { SetMultimap actualSplitIds = ImmutableSetMultimap.copyOf(Multimaps.transformValues(actual, TestingConnectorSplit::getSplitId)); SetMultimap expectedSplitIds = ImmutableSetMultimap.copyOf(Multimaps.transformValues(expected, TestingConnectorSplit::getSplitId)); - assertEquals(actualSplitIds, expectedSplitIds); + assertThat(actualSplitIds).isEqualTo(expectedSplitIds); } private static ArbitraryDistributionSplitAssigner createSplitAssigner( diff --git a/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestBinPackingNodeAllocator.java b/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestBinPackingNodeAllocator.java index b14aea5a8026..3402637bd537 100644 --- a/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestBinPackingNodeAllocator.java +++ b/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestBinPackingNodeAllocator.java @@ -55,9 +55,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; // uses mutable state @TestInstance(PER_METHOD) @@ -178,7 +175,7 @@ public void testAllocateSimple() assertEventually(() -> { // we need to wait as pending acquires are processed asynchronously assertAcquired(acquire5); - assertEquals(acquire5.getNode().get(), NODE_2); + assertThat(acquire5.getNode().get()).isEqualTo(NODE_2); }); // try to acquire one more node (should block) @@ -193,7 +190,7 @@ public void testAllocateSimple() // new node should be assigned assertEventually(() -> { assertAcquired(acquire6); - assertEquals(acquire6.getNode().get(), NODE_3); + assertThat(acquire6.getNode().get()).isEqualTo(NODE_3); }); } } @@ -356,8 +353,8 @@ public void testNoMatchingNodeAvailable() // pending acquire2 should be completed now but with an exception assertEventually(() -> { - assertFalse(acquire2.getNode().isCancelled()); - assertTrue(acquire2.getNode().isDone()); + assertThat(acquire2.getNode().isCancelled()).isFalse(); + assertThat(acquire2.getNode().isDone()).isTrue(); assertThatThrownBy(() -> getFutureValue(acquire2.getNode())) .hasMessage("No nodes available to run query"); }); @@ -852,22 +849,34 @@ private void assertAcquired(NodeAllocator.NodeLease lease) private void assertAcquired(NodeAllocator.NodeLease lease, Optional expectedNode) { assertEventually(() -> { - assertFalse(lease.getNode().isCancelled(), "node lease cancelled"); - assertTrue(lease.getNode().isDone(), "node lease not acquired"); + assertThat(lease.getNode().isCancelled()) + .describedAs("node lease cancelled") + .isFalse(); + assertThat(lease.getNode().isDone()) + .describedAs("node lease not acquired") + .isTrue(); if (expectedNode.isPresent()) { - assertEquals(lease.getNode().get(), expectedNode.get()); + assertThat(lease.getNode().get()).isEqualTo(expectedNode.get()); } }); } private void assertNotAcquired(NodeAllocator.NodeLease lease) { - assertFalse(lease.getNode().isCancelled(), "node lease cancelled"); - assertFalse(lease.getNode().isDone(), "node lease acquired"); + assertThat(lease.getNode().isCancelled()) + .describedAs("node lease cancelled") + .isFalse(); + assertThat(lease.getNode().isDone()) + .describedAs("node lease acquired") + .isFalse(); // enforce pending acquires processing and check again nodeAllocatorService.processPendingAcquires(); - assertFalse(lease.getNode().isCancelled(), "node lease cancelled"); - assertFalse(lease.getNode().isDone(), "node lease acquired"); + assertThat(lease.getNode().isCancelled()) + .describedAs("node lease cancelled") + .isFalse(); + assertThat(lease.getNode().isDone()) + .describedAs("node lease acquired") + .isFalse(); } private static void assertEventually(ThrowingRunnable assertion) diff --git a/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestEventDrivenTaskSource.java b/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestEventDrivenTaskSource.java index 7b533c70afeb..fe1220840c2b 100644 --- a/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestEventDrivenTaskSource.java +++ b/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestEventDrivenTaskSource.java @@ -81,11 +81,9 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Fail.fail; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -333,11 +331,11 @@ private void testStageTaskSourceSuccess( } for (TestingExchangeSourceHandleSource handleSource : handleSources) { - assertTrue(handleSource.isClosed()); + assertThat(handleSource.isClosed()).isTrue(); } for (SplitSource splitSource : splitSources.values()) { if (splitSource instanceof TestingSplitSource source) { - assertTrue(source.isClosed()); + assertThat(source.isClosed()).isTrue(); } else { fail("unexpected split source: " + splitSource.getClass()); @@ -369,20 +367,20 @@ private void testStageTaskSourceSuccess( RemoteSplit remoteSplit = (RemoteSplit) entry.getValue().getConnectorSplit(); SpoolingExchangeInput input = (SpoolingExchangeInput) remoteSplit.getExchangeInput(); for (ExchangeSourceHandle handle : input.getExchangeSourceHandles()) { - assertEquals(handle.getPartitionId(), partitionId); + assertThat(handle.getPartitionId()).isEqualTo(partitionId); actualHandles.computeIfAbsent(partitionId, key -> HashMultimap.create()).put(entry.getKey(), (TestingExchangeSourceHandle) handle); } } else { TestingConnectorSplit split = (TestingConnectorSplit) entry.getValue().getConnectorSplit(); - assertEquals(split.getBucket().orElseThrow(), partitionId); + assertThat(split.getBucket().orElseThrow()).isEqualTo(partitionId); actualSplits.computeIfAbsent(partitionId, key -> HashMultimap.create()).put(entry.getKey(), split); } } } - assertEquals(actualHandles, expectedHandles); - assertEquals(actualSplits, expectedSplits); + assertThat(actualHandles).isEqualTo(expectedHandles); + assertThat(actualSplits).isEqualTo(expectedSplits); } private static FaultTolerantPartitioningScheme createPartitioningScheme(int partitionCount) diff --git a/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestHashDistributionSplitAssigner.java b/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestHashDistributionSplitAssigner.java index 1a762aa87cd3..7ad4334d4cd9 100644 --- a/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestHashDistributionSplitAssigner.java +++ b/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestHashDistributionSplitAssigner.java @@ -55,8 +55,7 @@ import static io.trino.testing.TestingHandles.TEST_CATALOG_HANDLE; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Fail.fail; public class TestHashDistributionSplitAssigner { @@ -753,7 +752,7 @@ public void run() for (TaskDescriptor taskDescriptor : taskDescriptors.values()) { int partitionId = taskDescriptor.getPartitionId(); NodeRequirements nodeRequirements = taskDescriptor.getNodeRequirements(); - assertEquals(nodeRequirements.getCatalogHandle(), Optional.of(TEST_CATALOG_HANDLE)); + assertThat(nodeRequirements.getCatalogHandle()).isEqualTo(Optional.of(TEST_CATALOG_HANDLE)); partitionToNodeMap.ifPresent(partitionToNode -> { if (!taskDescriptor.getSplits().getSplitsFlat().isEmpty()) { InternalNode node = partitionToNode.get(partitionId); @@ -883,7 +882,7 @@ public void run() splittableSources::contains, mergeAllowed); Set actualGroups = extractMappings(actual); - assertEquals(actualGroups, expectedMappings); + assertThat(actualGroups).isEqualTo(expectedMappings); } private static Set extractMappings(Map sourcePartitionToTaskPartition) diff --git a/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestSingleDistributionSplitAssigner.java b/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestSingleDistributionSplitAssigner.java index 4da174434ecb..99c0ac2f45aa 100644 --- a/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestSingleDistributionSplitAssigner.java +++ b/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestSingleDistributionSplitAssigner.java @@ -25,9 +25,6 @@ import static io.trino.testing.TestingHandles.TEST_CATALOG_HANDLE; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestSingleDistributionSplitAssigner { @@ -43,10 +40,10 @@ public void testNoSources() tester.update(splitAssigner.finish()); - assertEquals(tester.getTaskPartitionCount(), 1); - assertEquals(tester.getNodeRequirements(0), new NodeRequirements(Optional.empty(), hostRequirement)); - assertTrue(tester.isSealed(0)); - assertTrue(tester.isNoMoreTaskPartitions()); + assertThat(tester.getTaskPartitionCount()).isEqualTo(1); + assertThat(tester.getNodeRequirements(0)).isEqualTo(new NodeRequirements(Optional.empty(), hostRequirement)); + assertThat(tester.isSealed(0)).isTrue(); + assertThat(tester.isNoMoreTaskPartitions()).isTrue(); } @Test @@ -61,12 +58,12 @@ public void testEmptySource() tester.update(splitAssigner.assign(PLAN_NODE_1, ImmutableListMultimap.of(), true)); tester.update(splitAssigner.finish()); - assertEquals(tester.getTaskPartitionCount(), 1); - assertEquals(tester.getNodeRequirements(0), new NodeRequirements(Optional.empty(), hostRequirement)); + assertThat(tester.getTaskPartitionCount()).isEqualTo(1); + assertThat(tester.getNodeRequirements(0)).isEqualTo(new NodeRequirements(Optional.empty(), hostRequirement)); assertThat(tester.getSplitIds(0, PLAN_NODE_1)).isEmpty(); - assertTrue(tester.isNoMoreSplits(0, PLAN_NODE_1)); - assertTrue(tester.isSealed(0)); - assertTrue(tester.isNoMoreTaskPartitions()); + assertThat(tester.isNoMoreSplits(0, PLAN_NODE_1)).isTrue(); + assertThat(tester.isSealed(0)).isTrue(); + assertThat(tester.isNoMoreTaskPartitions()).isTrue(); } @Test @@ -77,26 +74,26 @@ public void testSingleSource() ImmutableSet.of(PLAN_NODE_1)); SplitAssignerTester tester = new SplitAssignerTester(); - assertEquals(tester.getTaskPartitionCount(), 0); - assertFalse(tester.isNoMoreTaskPartitions()); + assertThat(tester.getTaskPartitionCount()).isEqualTo(0); + assertThat(tester.isNoMoreTaskPartitions()).isFalse(); tester.update(splitAssigner.assign(PLAN_NODE_1, ImmutableListMultimap.of(0, createSplit(1)), false)); tester.update(splitAssigner.finish()); - assertEquals(tester.getTaskPartitionCount(), 1); + assertThat(tester.getTaskPartitionCount()).isEqualTo(1); assertThat(tester.getSplitIds(0, PLAN_NODE_1)).containsExactly(1); - assertTrue(tester.isNoMoreTaskPartitions()); + assertThat(tester.isNoMoreTaskPartitions()).isTrue(); tester.update(splitAssigner.assign(PLAN_NODE_1, ImmutableListMultimap.of(0, createSplit(2), 1, createSplit(3)), false)); tester.update(splitAssigner.finish()); - assertEquals(tester.getTaskPartitionCount(), 1); + assertThat(tester.getTaskPartitionCount()).isEqualTo(1); assertThat(tester.getSplitIds(0, PLAN_NODE_1)).containsExactly(1, 2, 3); - assertFalse(tester.isNoMoreSplits(0, PLAN_NODE_1)); - assertFalse(tester.isSealed(0)); + assertThat(tester.isNoMoreSplits(0, PLAN_NODE_1)).isFalse(); + assertThat(tester.isSealed(0)).isFalse(); tester.update(splitAssigner.assign(PLAN_NODE_1, ImmutableListMultimap.of(0, createSplit(4)), true)); tester.update(splitAssigner.finish()); - assertTrue(tester.isNoMoreSplits(0, PLAN_NODE_1)); - assertTrue(tester.isSealed(0)); + assertThat(tester.isNoMoreSplits(0, PLAN_NODE_1)).isTrue(); + assertThat(tester.isSealed(0)).isTrue(); } @Test @@ -107,33 +104,33 @@ public void testMultipleSources() ImmutableSet.of(PLAN_NODE_1, PLAN_NODE_2)); SplitAssignerTester tester = new SplitAssignerTester(); - assertEquals(tester.getTaskPartitionCount(), 0); - assertFalse(tester.isNoMoreTaskPartitions()); + assertThat(tester.getTaskPartitionCount()).isEqualTo(0); + assertThat(tester.isNoMoreTaskPartitions()).isFalse(); tester.update(splitAssigner.assign(PLAN_NODE_1, ImmutableListMultimap.of(0, createSplit(1)), false)); tester.update(splitAssigner.finish()); - assertEquals(tester.getTaskPartitionCount(), 1); + assertThat(tester.getTaskPartitionCount()).isEqualTo(1); assertThat(tester.getSplitIds(0, PLAN_NODE_1)).containsExactlyInAnyOrder(1); - assertTrue(tester.isNoMoreTaskPartitions()); + assertThat(tester.isNoMoreTaskPartitions()).isTrue(); tester.update(splitAssigner.assign(PLAN_NODE_2, ImmutableListMultimap.of(0, createSplit(2), 1, createSplit(3)), false)); tester.update(splitAssigner.finish()); - assertEquals(tester.getTaskPartitionCount(), 1); + assertThat(tester.getTaskPartitionCount()).isEqualTo(1); assertThat(tester.getSplitIds(0, PLAN_NODE_2)).containsExactlyInAnyOrder(2, 3); - assertFalse(tester.isNoMoreSplits(0, PLAN_NODE_1)); + assertThat(tester.isNoMoreSplits(0, PLAN_NODE_1)).isFalse(); tester.update(splitAssigner.assign(PLAN_NODE_1, ImmutableListMultimap.of(2, createSplit(4)), true)); tester.update(splitAssigner.finish()); assertThat(tester.getSplitIds(0, PLAN_NODE_1)).containsExactlyInAnyOrder(1, 4); - assertTrue(tester.isNoMoreSplits(0, PLAN_NODE_1)); + assertThat(tester.isNoMoreSplits(0, PLAN_NODE_1)).isTrue(); - assertFalse(tester.isNoMoreSplits(0, PLAN_NODE_2)); - assertFalse(tester.isSealed(0)); + assertThat(tester.isNoMoreSplits(0, PLAN_NODE_2)).isFalse(); + assertThat(tester.isSealed(0)).isFalse(); tester.update(splitAssigner.assign(PLAN_NODE_2, ImmutableListMultimap.of(3, createSplit(5)), true)); tester.update(splitAssigner.finish()); assertThat(tester.getSplitIds(0, PLAN_NODE_2)).containsExactlyInAnyOrder(2, 3, 5); - assertTrue(tester.isNoMoreSplits(0, PLAN_NODE_2)); - assertTrue(tester.isSealed(0)); + assertThat(tester.isNoMoreSplits(0, PLAN_NODE_2)).isTrue(); + assertThat(tester.isSealed(0)).isTrue(); } private Split createSplit(int id) diff --git a/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestTaskDescriptorStorage.java b/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestTaskDescriptorStorage.java index deede00937fa..f195ad4f258e 100644 --- a/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestTaskDescriptorStorage.java +++ b/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestTaskDescriptorStorage.java @@ -36,7 +36,6 @@ import static io.trino.testing.TestingHandles.createTestCatalogHandle; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestTaskDescriptorStorage { @@ -122,7 +121,7 @@ public void testDestroy() manager.destroy(QUERY_2); assertThat(manager.get(QUERY_1_STAGE_1, 0)).isEmpty(); assertThat(manager.get(QUERY_2_STAGE_1, 0)).isEmpty(); - assertEquals(manager.getReservedBytes(), 0); + assertThat(manager.getReservedBytes()).isEqualTo(0); } @Test @@ -173,7 +172,7 @@ public void testCapacityExceeded() manager.put(QUERY_2_STAGE_2, createTaskDescriptor(1, DataSize.of(3, KILOBYTE), "catalog6")); // assert that the memory has been released - assertEquals(manager.getReservedBytes(), 0); + assertThat(manager.getReservedBytes()).isEqualTo(0); // check that the any future operations for QUERY_2 will fail assertThatThrownBy(() -> manager.put(QUERY_2_STAGE_2, createTaskDescriptor(3, DataSize.of(1, KILOBYTE)))) diff --git a/core/trino-main/src/test/java/io/trino/execution/warnings/TestDefaultWarningCollector.java b/core/trino-main/src/test/java/io/trino/execution/warnings/TestDefaultWarningCollector.java index 438eceea2f62..5a5c82cb43e4 100644 --- a/core/trino-main/src/test/java/io/trino/execution/warnings/TestDefaultWarningCollector.java +++ b/core/trino-main/src/test/java/io/trino/execution/warnings/TestDefaultWarningCollector.java @@ -17,7 +17,7 @@ import io.trino.spi.WarningCode; import org.junit.jupiter.api.Test; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestDefaultWarningCollector { @@ -26,7 +26,7 @@ public void testNoWarnings() { WarningCollector warningCollector = new DefaultWarningCollector(new WarningCollectorConfig().setMaxWarnings(0)); warningCollector.add(new TrinoWarning(new WarningCode(1, "1"), "warning 1")); - assertEquals(warningCollector.getWarnings().size(), 0); + assertThat(warningCollector.getWarnings().size()).isEqualTo(0); } @Test @@ -36,6 +36,6 @@ public void testMaxWarnings() warningCollector.add(new TrinoWarning(new WarningCode(1, "1"), "warning 1")); warningCollector.add(new TrinoWarning(new WarningCode(2, "2"), "warning 2")); warningCollector.add(new TrinoWarning(new WarningCode(3, "3"), "warning 3")); - assertEquals(warningCollector.getWarnings().size(), 2); + assertThat(warningCollector.getWarnings().size()).isEqualTo(2); } } diff --git a/core/trino-main/src/test/java/io/trino/execution/warnings/TestTestingWarningCollector.java b/core/trino-main/src/test/java/io/trino/execution/warnings/TestTestingWarningCollector.java index 58854787148d..52c155dd80d1 100644 --- a/core/trino-main/src/test/java/io/trino/execution/warnings/TestTestingWarningCollector.java +++ b/core/trino-main/src/test/java/io/trino/execution/warnings/TestTestingWarningCollector.java @@ -20,7 +20,7 @@ import org.junit.jupiter.api.Test; import static io.trino.testing.TestingWarningCollector.createTestWarning; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestTestingWarningCollector { @@ -30,6 +30,6 @@ public void testAddWarnings() TestingWarningCollector collector = new TestingWarningCollector(new WarningCollectorConfig(), new TestingWarningCollectorConfig().setAddWarnings(true)); ImmutableList.Builder expectedWarningsBuilder = ImmutableList.builder(); expectedWarningsBuilder.add(createTestWarning(1)); - assertEquals(collector.getWarnings(), expectedWarningsBuilder.build()); + assertThat(collector.getWarnings()).isEqualTo(expectedWarningsBuilder.build()); } } diff --git a/core/trino-main/src/test/java/io/trino/failuredetector/TestHeartbeatFailureDetector.java b/core/trino-main/src/test/java/io/trino/failuredetector/TestHeartbeatFailureDetector.java index 3863adc75c1c..5d91f95d2c50 100644 --- a/core/trino-main/src/test/java/io/trino/failuredetector/TestHeartbeatFailureDetector.java +++ b/core/trino-main/src/test/java/io/trino/failuredetector/TestHeartbeatFailureDetector.java @@ -42,9 +42,7 @@ import static io.airlift.discovery.client.DiscoveryBinder.discoveryBinder; import static io.airlift.discovery.client.ServiceTypes.serviceType; import static io.airlift.jaxrs.JaxrsBinder.jaxrsBinder; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestHeartbeatFailureDetector { @@ -78,15 +76,15 @@ public void testExcludesCurrentNode() .initialize(); ServiceSelector selector = injector.getInstance(Key.get(ServiceSelector.class, serviceType("trino"))); - assertEquals(selector.selectAllServices().size(), 1); + assertThat(selector.selectAllServices().size()).isEqualTo(1); HeartbeatFailureDetector detector = injector.getInstance(HeartbeatFailureDetector.class); detector.updateMonitoredServices(); - assertEquals(detector.getTotalCount(), 0); - assertEquals(detector.getActiveCount(), 0); - assertEquals(detector.getFailedCount(), 0); - assertTrue(detector.getFailed().isEmpty()); + assertThat(detector.getTotalCount()).isEqualTo(0); + assertThat(detector.getActiveCount()).isEqualTo(0); + assertThat(detector.getFailedCount()).isEqualTo(0); + assertThat(detector.getFailed().isEmpty()).isTrue(); } @Test @@ -97,13 +95,13 @@ public void testHeartbeatStatsSerialization() Stats stats = new Stats(new URI("http://example.com")); String serialized = objectMapper.writeValueAsString(stats); JsonNode deserialized = objectMapper.readTree(serialized); - assertFalse(deserialized.has("lastFailureInfo")); + assertThat(deserialized.has("lastFailureInfo")).isFalse(); stats.recordFailure(new SocketTimeoutException("timeout")); serialized = objectMapper.writeValueAsString(stats); deserialized = objectMapper.readTree(serialized); - assertFalse(deserialized.get("lastFailureInfo").isNull()); - assertEquals(deserialized.get("lastFailureInfo").get("type").asText(), SocketTimeoutException.class.getName()); + assertThat(deserialized.get("lastFailureInfo").isNull()).isFalse(); + assertThat(deserialized.get("lastFailureInfo").get("type").asText()).isEqualTo(SocketTimeoutException.class.getName()); } @Path("/foo") diff --git a/core/trino-main/src/test/java/io/trino/memory/TestLeastWastedEffortTaskLowMemoryKiller.java b/core/trino-main/src/test/java/io/trino/memory/TestLeastWastedEffortTaskLowMemoryKiller.java index afd92525a50e..cac1063a83ba 100644 --- a/core/trino-main/src/test/java/io/trino/memory/TestLeastWastedEffortTaskLowMemoryKiller.java +++ b/core/trino-main/src/test/java/io/trino/memory/TestLeastWastedEffortTaskLowMemoryKiller.java @@ -41,7 +41,7 @@ import static io.trino.memory.LowMemoryKillerTestingUtils.toRunningQueryInfoList; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestLeastWastedEffortTaskLowMemoryKiller { @@ -55,11 +55,9 @@ public void testMemoryPoolHasNoReservation() "q_1", ImmutableMap.of("n1", 0L, "n2", 0L, "n3", 0L, "n4", 0L, "n5", 0L)); - assertEquals( - lowMemoryKiller.chooseTargetToKill( - toRunningQueryInfoList(queries), - toNodeMemoryInfoList(memoryPool, queries)), - Optional.empty()); + assertThat(lowMemoryKiller.chooseTargetToKill( + toRunningQueryInfoList(queries), + toNodeMemoryInfoList(memoryPool, queries))).isEqualTo(Optional.empty()); } @Test @@ -70,11 +68,9 @@ public void testMemoryPoolNotBlocked() .put("q_1", ImmutableMap.of("n1", 0L, "n2", 6L, "n3", 0L, "n4", 0L, "n5", 0L)) .put("q_2", ImmutableMap.of("n1", 3L, "n2", 5L, "n3", 2L, "n4", 4L, "n5", 0L)) .buildOrThrow(); - assertEquals( - lowMemoryKiller.chooseTargetToKill( - toRunningQueryInfoList(queries), - toNodeMemoryInfoList(memoryPool, queries)), - Optional.empty()); + assertThat(lowMemoryKiller.chooseTargetToKill( + toRunningQueryInfoList(queries), + toNodeMemoryInfoList(memoryPool, queries))).isEqualTo(Optional.empty()); } @Test @@ -99,11 +95,9 @@ public void testWillNotKillTaskForQueryWithoutTaskRetriesEnabled() "n3", ImmutableMap.of(6, 2L))) .buildOrThrow(); - assertEquals( - lowMemoryKiller.chooseTargetToKill( - toRunningQueryInfoList(queries), - toNodeMemoryInfoList(memoryPool, queries, tasks)), - Optional.empty()); + assertThat(lowMemoryKiller.chooseTargetToKill( + toRunningQueryInfoList(queries), + toNodeMemoryInfoList(memoryPool, queries, tasks))).isEqualTo(Optional.empty()); } @Test @@ -162,14 +156,12 @@ private void testKillsBiggestTasksIfAllExecuteSameTime(Duration scheduledTime, D 8, buildTaskInfo(taskId("q_2", 8), TaskState.RUNNING, scheduledTime, blockedTime, false))); } - assertEquals( - lowMemoryKiller.chooseTargetToKill( - toRunningQueryInfoList(queries, ImmutableSet.of("q_1", "q_2"), taskInfos), - toNodeMemoryInfoList(memoryPool, queries, tasks)), - Optional.of(KillTarget.selectedTasks( - ImmutableSet.of( - taskId("q_1", 1), - taskId("q_2", 6))))); + assertThat(lowMemoryKiller.chooseTargetToKill( + toRunningQueryInfoList(queries, ImmutableSet.of("q_1", "q_2"), taskInfos), + toNodeMemoryInfoList(memoryPool, queries, tasks))).isEqualTo(Optional.of(KillTarget.selectedTasks( + ImmutableSet.of( + taskId("q_1", 1), + taskId("q_2", 6))))); } @Test @@ -207,14 +199,12 @@ public void testKillsSmallerTaskIfWastedEffortRatioIsBetter() // q2_2; n1; walltime 200s; memory 6; ratio 0.03 // q2_3; n2; walltime 60s; memory 2; ratio 0.033 (pick for n2) - assertEquals( - lowMemoryKiller.chooseTargetToKill( - toRunningQueryInfoList(queries, ImmutableSet.of("q_1", "q_2"), taskInfos), - toNodeMemoryInfoList(memoryPool, queries, tasks)), - Optional.of(KillTarget.selectedTasks( - ImmutableSet.of( - taskId("q_1", 1), - taskId("q_2", 3))))); + assertThat(lowMemoryKiller.chooseTargetToKill( + toRunningQueryInfoList(queries, ImmutableSet.of("q_1", "q_2"), taskInfos), + toNodeMemoryInfoList(memoryPool, queries, tasks))).isEqualTo(Optional.of(KillTarget.selectedTasks( + ImmutableSet.of( + taskId("q_1", 1), + taskId("q_2", 3))))); } @Test @@ -246,14 +236,12 @@ public void testPrefersKillingSpeculativeTasks() 2, buildTaskInfo(taskId("q_2", 2), TaskState.RUNNING, new Duration(100, SECONDS), new Duration(100, SECONDS), false), 3, buildTaskInfo(taskId("q_2", 3), TaskState.RUNNING, new Duration(30, SECONDS), new Duration(30, SECONDS), false))); - assertEquals( - lowMemoryKiller.chooseTargetToKill( - toRunningQueryInfoList(queries, ImmutableSet.of("q_1", "q_2"), taskInfos), - toNodeMemoryInfoList(memoryPool, queries, tasks)), - Optional.of(KillTarget.selectedTasks( - ImmutableSet.of( - taskId("q_2", 1), // if q_2_1 was not speculative then "q_1_1 would be picked - taskId("q_2", 3))))); + assertThat(lowMemoryKiller.chooseTargetToKill( + toRunningQueryInfoList(queries, ImmutableSet.of("q_1", "q_2"), taskInfos), + toNodeMemoryInfoList(memoryPool, queries, tasks))).isEqualTo(Optional.of(KillTarget.selectedTasks( + ImmutableSet.of( + taskId("q_2", 1), // if q_2_1 was not speculative then "q_1_1 would be picked + taskId("q_2", 3))))); } private static TaskInfo buildTaskInfo(TaskId taskId, TaskState state, Duration scheduledTime, Duration blockedTime, boolean speculative) diff --git a/core/trino-main/src/test/java/io/trino/memory/TestMemoryBlocking.java b/core/trino-main/src/test/java/io/trino/memory/TestMemoryBlocking.java index ebc8bef68e87..c54d4e647c62 100644 --- a/core/trino-main/src/test/java/io/trino/memory/TestMemoryBlocking.java +++ b/core/trino-main/src/test/java/io/trino/memory/TestMemoryBlocking.java @@ -55,10 +55,8 @@ import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertSame; -import static org.testng.Assert.assertTrue; @TestInstance(PER_METHOD) public class TestMemoryBlocking @@ -113,8 +111,8 @@ public void testTableScanMemoryBlocking() DynamicFilter.EMPTY); PageConsumerOperator sink = createSinkOperator(types); Driver driver = Driver.createDriver(driverContext, source, sink); - assertSame(driver.getDriverContext(), driverContext); - assertFalse(driver.isFinished()); + assertThat(driver.getDriverContext()).isSameAs(driverContext); + assertThat(driver.isFinished()).isFalse(); Split testSplit = new Split(TEST_CATALOG_HANDLE, new TestSplit()); driver.updateSplitAssignment(new SplitAssignment(sourceId, ImmutableSet.of(new ScheduledSplit(0, sourceId, testSplit)), true)); @@ -122,26 +120,26 @@ public void testTableScanMemoryBlocking() // the driver shouldn't block in the first call as it will be able to move a page between source and the sink operator // but the operator should be blocked - assertTrue(blocked.isDone()); - assertFalse(source.getOperatorContext().isWaitingForMemory().isDone()); + assertThat(blocked.isDone()).isTrue(); + assertThat(source.getOperatorContext().isWaitingForMemory().isDone()).isFalse(); // in the subsequent calls both the driver and the operator should be blocked // and they should stay blocked until more memory becomes available for (int i = 0; i < 10; i++) { blocked = driver.processForDuration(new Duration(1, NANOSECONDS)); - assertFalse(blocked.isDone()); - assertFalse(source.getOperatorContext().isWaitingForMemory().isDone()); + assertThat(blocked.isDone()).isFalse(); + assertThat(source.getOperatorContext().isWaitingForMemory().isDone()).isFalse(); } // free up some memory memoryPool.free(TASK_ID, "test", memoryPool.getReservedBytes()); // the operator should be unblocked - assertTrue(source.getOperatorContext().isWaitingForMemory().isDone()); + assertThat(source.getOperatorContext().isWaitingForMemory().isDone()).isTrue(); // the driver shouldn't be blocked blocked = driver.processForDuration(new Duration(1, NANOSECONDS)); - assertTrue(blocked.isDone()); + assertThat(blocked.isDone()).isTrue(); } private PageConsumerOperator createSinkOperator(List types) diff --git a/core/trino-main/src/test/java/io/trino/memory/TestMemoryPools.java b/core/trino-main/src/test/java/io/trino/memory/TestMemoryPools.java index e06dfad756eb..fde67f51b1dd 100644 --- a/core/trino-main/src/test/java/io/trino/memory/TestMemoryPools.java +++ b/core/trino-main/src/test/java/io/trino/memory/TestMemoryPools.java @@ -59,9 +59,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_METHOD) public class TestMemoryPools @@ -157,8 +154,8 @@ public void testNotifyListenerOnMemoryReserved() })); userPool.reserve(fakeTaskId, "test", 3); - assertEquals(notifiedPool.get(), userPool); - assertEquals(notifiedBytes.get(), 3L); + assertThat(notifiedPool.get()).isEqualTo(userPool); + assertThat(notifiedBytes.get()).isEqualTo(3L); } @Test @@ -166,52 +163,56 @@ public void testMemoryFutureCancellation() { setUpCountStarFromOrdersWithJoin(); ListenableFuture future = userPool.reserve(fakeTaskId, "test", TEN_MEGABYTES.toBytes()); - assertTrue(!future.isDone()); + assertThat(!future.isDone()).isTrue(); assertThatThrownBy(() -> future.cancel(true)) .isInstanceOf(UnsupportedOperationException.class) .hasMessage("cancellation is not supported"); userPool.free(fakeTaskId, "test", TEN_MEGABYTES.toBytes()); - assertTrue(future.isDone()); + assertThat(future.isDone()).isTrue(); } @Test public void testBlockingOnRevocableMemoryFreeUser() { setupConsumeRevocableMemory(ONE_BYTE, 10); - assertTrue(userPool.tryReserve(fakeTaskId, "test", TEN_MEGABYTES_WITHOUT_TWO_BYTES.toBytes())); + assertThat(userPool.tryReserve(fakeTaskId, "test", TEN_MEGABYTES_WITHOUT_TWO_BYTES.toBytes())).isTrue(); // we expect 2 iterations as we have 2 bytes remaining in memory pool and we allocate 1 byte per page - assertEquals(runDriversUntilBlocked(waitingForRevocableMemory()), 2); - assertTrue(userPool.getFreeBytes() <= 0, format("Expected empty pool but got [%d]", userPool.getFreeBytes())); + assertThat(runDriversUntilBlocked(waitingForRevocableMemory())).isEqualTo(2); + assertThat(userPool.getFreeBytes() <= 0) + .describedAs(format("Expected empty pool but got [%d]", userPool.getFreeBytes())) + .isTrue(); // lets free 5 bytes userPool.free(fakeTaskId, "test", 5); - assertEquals(runDriversUntilBlocked(waitingForRevocableMemory()), 5); - assertTrue(userPool.getFreeBytes() <= 0, format("Expected empty pool but got [%d]", userPool.getFreeBytes())); + assertThat(runDriversUntilBlocked(waitingForRevocableMemory())).isEqualTo(5); + assertThat(userPool.getFreeBytes() <= 0) + .describedAs(format("Expected empty pool but got [%d]", userPool.getFreeBytes())) + .isTrue(); // 3 more bytes is enough for driver to finish userPool.free(fakeTaskId, "test", 3); assertDriversProgress(waitingForRevocableMemory()); - assertEquals(userPool.getFreeBytes(), 10); + assertThat(userPool.getFreeBytes()).isEqualTo(10); } @Test public void testBlockingOnRevocableMemoryFreeViaRevoke() { RevocableMemoryOperator revocableMemoryOperator = setupConsumeRevocableMemory(ONE_BYTE, 5); - assertTrue(userPool.tryReserve(fakeTaskId, "test", TEN_MEGABYTES_WITHOUT_TWO_BYTES.toBytes())); + assertThat(userPool.tryReserve(fakeTaskId, "test", TEN_MEGABYTES_WITHOUT_TWO_BYTES.toBytes())).isTrue(); // we expect 2 iterations as we have 2 bytes remaining in memory pool and we allocate 1 byte per page - assertEquals(runDriversUntilBlocked(waitingForRevocableMemory()), 2); + assertThat(runDriversUntilBlocked(waitingForRevocableMemory())).isEqualTo(2); revocableMemoryOperator.getOperatorContext().requestMemoryRevoking(); // 2 more iterations - assertEquals(runDriversUntilBlocked(waitingForRevocableMemory()), 2); + assertThat(runDriversUntilBlocked(waitingForRevocableMemory())).isEqualTo(2); revocableMemoryOperator.getOperatorContext().requestMemoryRevoking(); // 3 more bytes is enough for driver to finish assertDriversProgress(waitingForRevocableMemory()); - assertEquals(userPool.getFreeBytes(), 2); + assertThat(userPool.getFreeBytes()).isEqualTo(2); } @Test @@ -223,22 +224,22 @@ public void testTaggedAllocations() testPool.reserve(testTask, "test_tag", 10); Map allocations = testPool.getTaggedMemoryAllocations().get(new QueryId("test_query")); - assertEquals(allocations, ImmutableMap.of("test_tag", 10L)); + assertThat(allocations).isEqualTo(ImmutableMap.of("test_tag", 10L)); // free 5 bytes for test_tag testPool.free(testTask, "test_tag", 5); - assertEquals(allocations, ImmutableMap.of("test_tag", 5L)); + assertThat(allocations).isEqualTo(ImmutableMap.of("test_tag", 5L)); testPool.reserve(testTask, "test_tag2", 20); - assertEquals(allocations, ImmutableMap.of("test_tag", 5L, "test_tag2", 20L)); + assertThat(allocations).isEqualTo(ImmutableMap.of("test_tag", 5L, "test_tag2", 20L)); // free the remaining 5 bytes for test_tag testPool.free(testTask, "test_tag", 5); - assertEquals(allocations, ImmutableMap.of("test_tag2", 20L)); + assertThat(allocations).isEqualTo(ImmutableMap.of("test_tag2", 20L)); // free all for test_tag2 testPool.free(testTask, "test_tag2", 20); - assertEquals(testPool.getTaggedMemoryAllocations().size(), 0); + assertThat(testPool.getTaggedMemoryAllocations().size()).isEqualTo(0); } @Test @@ -458,7 +459,7 @@ private long runDriversUntilBlocked(Predicate reason) // driver should be blocked waiting for memory for (Driver driver : drivers) { - assertFalse(driver.isFinished()); + assertThat(driver.isFinished()).isFalse(); } return iterationsCount; } @@ -466,14 +467,14 @@ private long runDriversUntilBlocked(Predicate reason) private void assertDriversProgress(Predicate reason) { do { - assertFalse(isOperatorBlocked(drivers, reason)); + assertThat(isOperatorBlocked(drivers, reason)).isFalse(); boolean progress = false; for (Driver driver : drivers) { ListenableFuture blocked = driver.processUntilBlocked(); progress = progress | blocked.isDone(); } // query should not block - assertTrue(progress); + assertThat(progress).isTrue(); } while (!drivers.stream().allMatch(Driver::isFinished)); } diff --git a/core/trino-main/src/test/java/io/trino/memory/TestMemoryTracking.java b/core/trino-main/src/test/java/io/trino/memory/TestMemoryTracking.java index 4aeec8b14afc..6069671e6611 100644 --- a/core/trino-main/src/test/java/io/trino/memory/TestMemoryTracking.java +++ b/core/trino-main/src/test/java/io/trino/memory/TestMemoryTracking.java @@ -47,11 +47,9 @@ import static io.trino.testing.TestingSession.testSessionBuilder; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_METHOD) public class TestMemoryTracking @@ -162,9 +160,9 @@ public void testLocalAllocations() taskContext.getTaskMemoryContext(), pipelineLocalAllocation + taskLocalAllocation, 11_000_000); - assertEquals(pipelineContext.getPipelineStats().getUserMemoryReservation().toBytes(), - pipelineLocalAllocation, - "task level allocations should not be visible at the pipeline level"); + assertThat(pipelineContext.getPipelineStats().getUserMemoryReservation().toBytes()) + .describedAs("task level allocations should not be visible at the pipeline level") + .isEqualTo(pipelineLocalAllocation); pipelineLocalMemoryContext.setBytes(pipelineLocalMemoryContext.getBytes() - pipelineLocalAllocation); assertLocalMemoryAllocations( pipelineContext.getPipelineMemoryContext(), @@ -258,7 +256,7 @@ public void testRevocableMemoryAllocations() public void testTrySetBytes() { LocalMemoryContext localMemoryContext = operatorContext.localUserMemoryContext(); - assertTrue(localMemoryContext.trySetBytes(100_000_000)); + assertThat(localMemoryContext.trySetBytes(100_000_000)).isTrue(); assertStats( operatorContext.getNestedOperatorStats(), driverContext.getDriverStats(), @@ -267,7 +265,7 @@ public void testTrySetBytes() 100_000_000, 0); - assertTrue(localMemoryContext.trySetBytes(200_000_000)); + assertThat(localMemoryContext.trySetBytes(200_000_000)).isTrue(); assertStats( operatorContext.getNestedOperatorStats(), driverContext.getDriverStats(), @@ -276,7 +274,7 @@ public void testTrySetBytes() 200_000_000, 0); - assertTrue(localMemoryContext.trySetBytes(100_000_000)); + assertThat(localMemoryContext.trySetBytes(100_000_000)).isTrue(); assertStats( operatorContext.getNestedOperatorStats(), driverContext.getDriverStats(), @@ -286,7 +284,7 @@ public void testTrySetBytes() 0); // allocating more than the pool size should fail and we should have the same stats as before - assertFalse(localMemoryContext.trySetBytes(memoryPool.getMaxBytes() + 1)); + assertThat(localMemoryContext.trySetBytes(memoryPool.getMaxBytes() + 1)).isFalse(); assertStats( operatorContext.getNestedOperatorStats(), driverContext.getDriverStats(), @@ -304,7 +302,7 @@ public void testTrySetZeroBytesFullPool() TaskId taskId = new TaskId(new StageId("test_query", 0), 0, 0); memoryPool.reserve(taskId, "test", memoryPool.getFreeBytes()); // try to reserve 0 bytes in the full pool - assertTrue(localMemoryContext.trySetBytes(localMemoryContext.getBytes())); + assertThat(localMemoryContext.trySetBytes(localMemoryContext.getBytes())).isTrue(); } @Test @@ -314,7 +312,7 @@ public void testDestroy() LocalMemoryContext newLocalRevocableMemoryContext = operatorContext.localRevocableMemoryContext(); newLocalRevocableMemoryContext.setBytes(200_000); newLocalUserMemoryContext.setBytes(400_000); - assertEquals(operatorContext.getOperatorMemoryContext().getUserMemory(), 400_000); + assertThat(operatorContext.getOperatorMemoryContext().getUserMemory()).isEqualTo(400_000); operatorContext.destroy(); assertOperatorMemoryAllocations(operatorContext.getOperatorMemoryContext(), 0, 0); } @@ -328,15 +326,15 @@ private void assertStats( long expectedRevocableMemory) { OperatorStats operatorStats = getOnlyElement(nestedOperatorStats); - assertEquals(operatorStats.getUserMemoryReservation().toBytes(), expectedUserMemory); - assertEquals(driverStats.getUserMemoryReservation().toBytes(), expectedUserMemory); - assertEquals(pipelineStats.getUserMemoryReservation().toBytes(), expectedUserMemory); - assertEquals(taskStats.getUserMemoryReservation().toBytes(), expectedUserMemory); + assertThat(operatorStats.getUserMemoryReservation().toBytes()).isEqualTo(expectedUserMemory); + assertThat(driverStats.getUserMemoryReservation().toBytes()).isEqualTo(expectedUserMemory); + assertThat(pipelineStats.getUserMemoryReservation().toBytes()).isEqualTo(expectedUserMemory); + assertThat(taskStats.getUserMemoryReservation().toBytes()).isEqualTo(expectedUserMemory); - assertEquals(operatorStats.getRevocableMemoryReservation().toBytes(), expectedRevocableMemory); - assertEquals(driverStats.getRevocableMemoryReservation().toBytes(), expectedRevocableMemory); - assertEquals(pipelineStats.getRevocableMemoryReservation().toBytes(), expectedRevocableMemory); - assertEquals(taskStats.getRevocableMemoryReservation().toBytes(), expectedRevocableMemory); + assertThat(operatorStats.getRevocableMemoryReservation().toBytes()).isEqualTo(expectedRevocableMemory); + assertThat(driverStats.getRevocableMemoryReservation().toBytes()).isEqualTo(expectedRevocableMemory); + assertThat(pipelineStats.getRevocableMemoryReservation().toBytes()).isEqualTo(expectedRevocableMemory); + assertThat(taskStats.getRevocableMemoryReservation().toBytes()).isEqualTo(expectedRevocableMemory); } // the allocations that are done at the operator level are reflected at that level and all the way up to the pools @@ -345,9 +343,15 @@ private void assertOperatorMemoryAllocations( long expectedUserMemory, long expectedRevocableMemory) { - assertEquals(memoryTrackingContext.getUserMemory(), expectedUserMemory, "User memory verification failed"); - assertEquals(memoryPool.getReservedBytes(), expectedUserMemory, "Memory pool verification failed"); - assertEquals(memoryTrackingContext.getRevocableMemory(), expectedRevocableMemory, "Revocable memory verification failed"); + assertThat(memoryTrackingContext.getUserMemory()) + .describedAs("User memory verification failed") + .isEqualTo(expectedUserMemory); + assertThat(memoryPool.getReservedBytes()) + .describedAs("Memory pool verification failed") + .isEqualTo(expectedUserMemory); + assertThat(memoryTrackingContext.getRevocableMemory()) + .describedAs("Revocable memory verification failed") + .isEqualTo(expectedRevocableMemory); } // the local allocations are reflected only at that level and all the way up to the pools @@ -356,7 +360,11 @@ private void assertLocalMemoryAllocations( long expectedPoolMemory, long expectedContextUserMemory) { - assertEquals(memoryTrackingContext.getUserMemory(), expectedContextUserMemory, "User memory verification failed"); - assertEquals(memoryPool.getReservedBytes(), expectedPoolMemory, "Memory pool verification failed"); + assertThat(memoryTrackingContext.getUserMemory()) + .describedAs("User memory verification failed") + .isEqualTo(expectedContextUserMemory); + assertThat(memoryPool.getReservedBytes()) + .describedAs("Memory pool verification failed") + .isEqualTo(expectedPoolMemory); } } diff --git a/core/trino-main/src/test/java/io/trino/memory/TestTotalReservationLowMemoryKiller.java b/core/trino-main/src/test/java/io/trino/memory/TestTotalReservationLowMemoryKiller.java index 7ec57fabeb03..03b2d98868a7 100644 --- a/core/trino-main/src/test/java/io/trino/memory/TestTotalReservationLowMemoryKiller.java +++ b/core/trino-main/src/test/java/io/trino/memory/TestTotalReservationLowMemoryKiller.java @@ -23,7 +23,7 @@ import static io.trino.memory.LowMemoryKillerTestingUtils.toNodeMemoryInfoList; import static io.trino.memory.LowMemoryKillerTestingUtils.toRunningQueryInfoList; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestTotalReservationLowMemoryKiller { @@ -34,11 +34,9 @@ public void testMemoryPoolHasNoReservation() { int memoryPool = 12; Map> queries = ImmutableMap.of("q_1", ImmutableMap.of("n1", 0L, "n2", 0L, "n3", 0L, "n4", 0L, "n5", 0L)); - assertEquals( - lowMemoryKiller.chooseTargetToKill( - toRunningQueryInfoList(queries), - toNodeMemoryInfoList(memoryPool, queries)), - Optional.empty()); + assertThat(lowMemoryKiller.chooseTargetToKill( + toRunningQueryInfoList(queries), + toNodeMemoryInfoList(memoryPool, queries))).isEqualTo(Optional.empty()); } @Test @@ -52,10 +50,8 @@ public void testSkewedQuery() .put("q_2", ImmutableMap.of("n1", 3L, "n2", 5L, "n3", 2L, "n4", 4L, "n5", 0L)) .put("q_3", ImmutableMap.of("n1", 0L, "n2", 0L, "n3", 9L, "n4", 0L, "n5", 0L)) .buildOrThrow(); - assertEquals( - lowMemoryKiller.chooseTargetToKill( - toRunningQueryInfoList(queries), - toNodeMemoryInfoList(memoryPool, queries)), - Optional.of(KillTarget.wholeQuery(new QueryId("q_2")))); + assertThat(lowMemoryKiller.chooseTargetToKill( + toRunningQueryInfoList(queries), + toNodeMemoryInfoList(memoryPool, queries))).isEqualTo(Optional.of(KillTarget.wholeQuery(new QueryId("q_2")))); } } diff --git a/core/trino-main/src/test/java/io/trino/memory/TestTotalReservationOnBlockedNodesQueryLowMemoryKiller.java b/core/trino-main/src/test/java/io/trino/memory/TestTotalReservationOnBlockedNodesQueryLowMemoryKiller.java index 710bad545510..41f12f01da21 100644 --- a/core/trino-main/src/test/java/io/trino/memory/TestTotalReservationOnBlockedNodesQueryLowMemoryKiller.java +++ b/core/trino-main/src/test/java/io/trino/memory/TestTotalReservationOnBlockedNodesQueryLowMemoryKiller.java @@ -24,7 +24,7 @@ import static io.trino.memory.LowMemoryKillerTestingUtils.toNodeMemoryInfoList; import static io.trino.memory.LowMemoryKillerTestingUtils.toRunningQueryInfoList; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestTotalReservationOnBlockedNodesQueryLowMemoryKiller { @@ -38,11 +38,9 @@ public void testMemoryPoolHasNoReservation() "q_1", ImmutableMap.of("n1", 0L, "n2", 0L, "n3", 0L, "n4", 0L, "n5", 0L)); - assertEquals( - lowMemoryKiller.chooseTargetToKill( - toRunningQueryInfoList(queries), - toNodeMemoryInfoList(memoryPool, queries)), - Optional.empty()); + assertThat(lowMemoryKiller.chooseTargetToKill( + toRunningQueryInfoList(queries), + toNodeMemoryInfoList(memoryPool, queries))).isEqualTo(Optional.empty()); } @Test @@ -53,11 +51,9 @@ public void testMemoryPoolNotBlocked() .put("q_1", ImmutableMap.of("n1", 0L, "n2", 6L, "n3", 0L, "n4", 0L, "n5", 0L)) .put("q_2", ImmutableMap.of("n1", 3L, "n2", 5L, "n3", 2L, "n4", 4L, "n5", 0L)) .buildOrThrow(); - assertEquals( - lowMemoryKiller.chooseTargetToKill( - toRunningQueryInfoList(queries), - toNodeMemoryInfoList(memoryPool, queries)), - Optional.empty()); + assertThat(lowMemoryKiller.chooseTargetToKill( + toRunningQueryInfoList(queries), + toNodeMemoryInfoList(memoryPool, queries))).isEqualTo(Optional.empty()); } @Test @@ -71,11 +67,9 @@ public void testSkewedQuery() .put("q_2", ImmutableMap.of("n1", 3L, "n2", 5L, "n3", 2L, "n4", 4L, "n5", 0L)) .put("q_3", ImmutableMap.of("n1", 0L, "n2", 0L, "n3", 9L, "n4", 0L, "n5", 0L)) .buildOrThrow(); - assertEquals( - lowMemoryKiller.chooseTargetToKill( - toRunningQueryInfoList(queries), - toNodeMemoryInfoList(memoryPool, queries)), - Optional.of(KillTarget.wholeQuery(new QueryId("q_1")))); + assertThat(lowMemoryKiller.chooseTargetToKill( + toRunningQueryInfoList(queries), + toNodeMemoryInfoList(memoryPool, queries))).isEqualTo(Optional.of(KillTarget.wholeQuery(new QueryId("q_1")))); } @Test @@ -99,10 +93,8 @@ public void testWillNotKillWholeQueryWithTaskRetries() // we expect "q_1" to be killed even though "q_2" is the biggest query here. We won't kill whole query if it has task retries enabled. - assertEquals( - lowMemoryKiller.chooseTargetToKill( - toRunningQueryInfoList(queries, ImmutableSet.of("q_2")), - toNodeMemoryInfoList(memoryPool, queries, tasks)), - Optional.of(KillTarget.wholeQuery(new QueryId("q_1")))); + assertThat(lowMemoryKiller.chooseTargetToKill( + toRunningQueryInfoList(queries, ImmutableSet.of("q_2")), + toNodeMemoryInfoList(memoryPool, queries, tasks))).isEqualTo(Optional.of(KillTarget.wholeQuery(new QueryId("q_1")))); } } diff --git a/core/trino-main/src/test/java/io/trino/memory/TestTotalReservationOnBlockedNodesTaskLowMemoryKiller.java b/core/trino-main/src/test/java/io/trino/memory/TestTotalReservationOnBlockedNodesTaskLowMemoryKiller.java index 82c30466382f..d248cabf7e9f 100644 --- a/core/trino-main/src/test/java/io/trino/memory/TestTotalReservationOnBlockedNodesTaskLowMemoryKiller.java +++ b/core/trino-main/src/test/java/io/trino/memory/TestTotalReservationOnBlockedNodesTaskLowMemoryKiller.java @@ -40,7 +40,7 @@ import static io.trino.memory.LowMemoryKillerTestingUtils.toNodeMemoryInfoList; import static io.trino.memory.LowMemoryKillerTestingUtils.toRunningQueryInfoList; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestTotalReservationOnBlockedNodesTaskLowMemoryKiller { @@ -51,11 +51,9 @@ public void testMemoryPoolHasNoReservation() { int memoryPool = 12; Map> queries = ImmutableMap.of("q_1", ImmutableMap.of("n1", 0L, "n2", 0L, "n3", 0L, "n4", 0L, "n5", 0L)); - assertEquals( - lowMemoryKiller.chooseTargetToKill( - toRunningQueryInfoList(queries), - toNodeMemoryInfoList(memoryPool, queries)), - Optional.empty()); + assertThat(lowMemoryKiller.chooseTargetToKill( + toRunningQueryInfoList(queries), + toNodeMemoryInfoList(memoryPool, queries))).isEqualTo(Optional.empty()); } @Test @@ -66,11 +64,9 @@ public void testMemoryPoolNotBlocked() .put("q_1", ImmutableMap.of("n1", 0L, "n2", 6L, "n3", 0L, "n4", 0L, "n5", 0L)) .put("q_2", ImmutableMap.of("n1", 3L, "n2", 5L, "n3", 2L, "n4", 4L, "n5", 0L)) .buildOrThrow(); - assertEquals( - lowMemoryKiller.chooseTargetToKill( - toRunningQueryInfoList(queries), - toNodeMemoryInfoList(memoryPool, queries)), - Optional.empty()); + assertThat(lowMemoryKiller.chooseTargetToKill( + toRunningQueryInfoList(queries), + toNodeMemoryInfoList(memoryPool, queries))).isEqualTo(Optional.empty()); } @Test @@ -95,11 +91,9 @@ public void testWillNotKillTaskForQueryWithoutTaskRetriesEnabled() "n3", ImmutableMap.of(6, 2L))) .buildOrThrow(); - assertEquals( - lowMemoryKiller.chooseTargetToKill( - toRunningQueryInfoList(queries), - toNodeMemoryInfoList(memoryPool, queries, tasks)), - Optional.empty()); + assertThat(lowMemoryKiller.chooseTargetToKill( + toRunningQueryInfoList(queries), + toNodeMemoryInfoList(memoryPool, queries, tasks))).isEqualTo(Optional.empty()); } @Test @@ -128,14 +122,12 @@ public void testPreferKillingTasks() 8, 2L), "n5", ImmutableMap.of())); - assertEquals( - lowMemoryKiller.chooseTargetToKill( - toRunningQueryInfoList(queries, ImmutableSet.of("q_2")), - toNodeMemoryInfoList(memoryPool, queries, tasks)), - Optional.of(KillTarget.selectedTasks( - ImmutableSet.of( - taskId("q_2", 3), - taskId("q_2", 6))))); + assertThat(lowMemoryKiller.chooseTargetToKill( + toRunningQueryInfoList(queries, ImmutableSet.of("q_2")), + toNodeMemoryInfoList(memoryPool, queries, tasks))).isEqualTo(Optional.of(KillTarget.selectedTasks( + ImmutableSet.of( + taskId("q_2", 3), + taskId("q_2", 6))))); } @Test @@ -168,14 +160,12 @@ public void testKillsBiggestTasks() "n3", ImmutableMap.of(1, 11L))) // should not be picked as n3 does not have task retries enabled .buildOrThrow(); - assertEquals( - lowMemoryKiller.chooseTargetToKill( - toRunningQueryInfoList(queries, ImmutableSet.of("q_1", "q_2")), - toNodeMemoryInfoList(memoryPool, queries, tasks)), - Optional.of(KillTarget.selectedTasks( - ImmutableSet.of( - taskId("q_1", 1), - taskId("q_2", 6))))); + assertThat(lowMemoryKiller.chooseTargetToKill( + toRunningQueryInfoList(queries, ImmutableSet.of("q_1", "q_2")), + toNodeMemoryInfoList(memoryPool, queries, tasks))).isEqualTo(Optional.of(KillTarget.selectedTasks( + ImmutableSet.of( + taskId("q_1", 1), + taskId("q_2", 6))))); } @Test @@ -221,14 +211,12 @@ public void testPrefersKillingSpeculativeTask() 7, buildTaskInfo(taskId("q_2", 7), false), 8, buildTaskInfo(taskId("q_2", 8), false))); - assertEquals( - lowMemoryKiller.chooseTargetToKill( - toRunningQueryInfoList(queries, ImmutableSet.of("q_1", "q_2"), taskInfos), - toNodeMemoryInfoList(memoryPool, queries, tasks)), - Optional.of(KillTarget.selectedTasks( - ImmutableSet.of( - taskId("q_2", 5), // picks smaller speculative tasks even though bigger tasks exist on - taskId("q_2", 6))))); + assertThat(lowMemoryKiller.chooseTargetToKill( + toRunningQueryInfoList(queries, ImmutableSet.of("q_1", "q_2"), taskInfos), + toNodeMemoryInfoList(memoryPool, queries, tasks))).isEqualTo(Optional.of(KillTarget.selectedTasks( + ImmutableSet.of( + taskId("q_2", 5), // picks smaller speculative tasks even though bigger tasks exist on + taskId("q_2", 6))))); } private static TaskInfo buildTaskInfo(TaskId taskId, boolean speculative) diff --git a/core/trino-main/src/test/java/io/trino/metadata/TestDiscoveryNodeManager.java b/core/trino-main/src/test/java/io/trino/metadata/TestDiscoveryNodeManager.java index f2974c16c15f..36801ed18c6c 100644 --- a/core/trino-main/src/test/java/io/trino/metadata/TestDiscoveryNodeManager.java +++ b/core/trino-main/src/test/java/io/trino/metadata/TestDiscoveryNodeManager.java @@ -51,11 +51,9 @@ import static io.trino.metadata.NodeState.ACTIVE; import static io.trino.metadata.NodeState.INACTIVE; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotSame; -import static org.testng.Assert.assertTrue; @TestInstance(PER_METHOD) public class TestDiscoveryNodeManager @@ -113,15 +111,15 @@ public void testGetAllNodes() AllNodes allNodes = manager.getAllNodes(); Set connectorNodes = manager.getActiveCatalogNodes(GlobalSystemConnector.CATALOG_HANDLE); - assertEquals(connectorNodes.size(), 4); - assertTrue(connectorNodes.stream().anyMatch(InternalNode::isCoordinator)); + assertThat(connectorNodes.size()).isEqualTo(4); + assertThat(connectorNodes.stream().anyMatch(InternalNode::isCoordinator)).isTrue(); Set activeNodes = allNodes.getActiveNodes(); assertEqualsIgnoreOrder(activeNodes, this.activeNodes); for (InternalNode actual : activeNodes) { for (InternalNode expected : this.activeNodes) { - assertNotSame(actual, expected); + assertThat(actual).isNotSameAs(expected); } } @@ -132,7 +130,7 @@ public void testGetAllNodes() for (InternalNode actual : inactiveNodes) { for (InternalNode expected : this.inactiveNodes) { - assertNotSame(actual, expected); + assertThat(actual).isNotSameAs(expected); } } @@ -159,7 +157,7 @@ public void testGetCurrentNode() internalCommunicationConfig, new CatalogManagerConfig()); try { - assertEquals(manager.getCurrentNode(), currentNode); + assertThat(manager.getCurrentNode()).isEqualTo(currentNode); } finally { manager.stop(); @@ -178,7 +176,7 @@ public void testGetCoordinators() internalCommunicationConfig, new CatalogManagerConfig()); try { - assertEquals(manager.getCoordinators(), ImmutableSet.of(coordinator)); + assertThat(manager.getCoordinators()).isEqualTo(ImmutableSet.of(coordinator)); } finally { manager.stop(); @@ -220,18 +218,18 @@ public void testNodeChangeListener() BlockingQueue notifications = new ArrayBlockingQueue<>(100); manager.addNodeChangeListener(notifications::add); AllNodes allNodes = notifications.take(); - assertEquals(allNodes.getActiveNodes(), activeNodes); - assertEquals(allNodes.getInactiveNodes(), inactiveNodes); + assertThat(allNodes.getActiveNodes()).isEqualTo(activeNodes); + assertThat(allNodes.getInactiveNodes()).isEqualTo(inactiveNodes); selector.announceNodes(ImmutableSet.of(currentNode), ImmutableSet.of(coordinator)); allNodes = notifications.take(); - assertEquals(allNodes.getActiveNodes(), ImmutableSet.of(currentNode, coordinator)); - assertEquals(allNodes.getActiveCoordinators(), ImmutableSet.of(coordinator)); + assertThat(allNodes.getActiveNodes()).isEqualTo(ImmutableSet.of(currentNode, coordinator)); + assertThat(allNodes.getActiveCoordinators()).isEqualTo(ImmutableSet.of(coordinator)); selector.announceNodes(activeNodes, inactiveNodes); allNodes = notifications.take(); - assertEquals(allNodes.getActiveNodes(), activeNodes); - assertEquals(allNodes.getInactiveNodes(), inactiveNodes); + assertThat(allNodes.getActiveNodes()).isEqualTo(activeNodes); + assertThat(allNodes.getInactiveNodes()).isEqualTo(inactiveNodes); } finally { manager.stop(); diff --git a/core/trino-main/src/test/java/io/trino/metadata/TestGlobalFunctionCatalog.java b/core/trino-main/src/test/java/io/trino/metadata/TestGlobalFunctionCatalog.java index cdcb5ed3394a..8c756bd8da6d 100644 --- a/core/trino-main/src/test/java/io/trino/metadata/TestGlobalFunctionCatalog.java +++ b/core/trino-main/src/test/java/io/trino/metadata/TestGlobalFunctionCatalog.java @@ -56,9 +56,8 @@ import static io.trino.sql.analyzer.TypeSignatureTranslator.parseTypeSignature; import static java.util.Collections.nCopies; import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; public class TestGlobalFunctionCatalog { @@ -66,7 +65,7 @@ public class TestGlobalFunctionCatalog public void testIdentityCast() { BoundSignature exactOperator = new TestingFunctionResolution().getCoercion(HYPER_LOG_LOG, HYPER_LOG_LOG).getSignature(); - assertEquals(exactOperator, new BoundSignature(builtinFunctionName(CAST), HYPER_LOG_LOG, ImmutableList.of(HYPER_LOG_LOG))); + assertThat(exactOperator).isEqualTo(new BoundSignature(builtinFunctionName(CAST), HYPER_LOG_LOG, ImmutableList.of(HYPER_LOG_LOG))); } @Test @@ -89,10 +88,10 @@ public void testExactMatchBeforeCoercion() .map(functionResolution.getPlannerContext().getTypeManager()::getType) .collect(toImmutableList()); BoundSignature exactOperator = functionResolution.resolveOperator(operatorType, argumentTypes).getSignature(); - assertEquals(exactOperator.toSignature(), function.getSignature()); + assertThat(exactOperator.toSignature()).isEqualTo(function.getSignature()); foundOperator = true; } - assertTrue(foundOperator); + assertThat(foundOperator).isTrue(); } @Test @@ -325,7 +324,7 @@ public ResolveFunctionAssertion returns(Signature.Builder functionSignature) { Signature expectedSignature = functionSignature.build(); Signature actualSignature = resolveSignature().toSignature(); - assertEquals(actualSignature, expectedSignature); + assertThat(actualSignature).isEqualTo(expectedSignature); return this; } diff --git a/core/trino-main/src/test/java/io/trino/metadata/TestInformationSchemaMetadata.java b/core/trino-main/src/test/java/io/trino/metadata/TestInformationSchemaMetadata.java index c650cd4a04a5..93f63c11a15a 100644 --- a/core/trino-main/src/test/java/io/trino/metadata/TestInformationSchemaMetadata.java +++ b/core/trino-main/src/test/java/io/trino/metadata/TestInformationSchemaMetadata.java @@ -55,10 +55,9 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.testing.TestingSession.testSessionBuilder; import static java.util.Arrays.stream; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -132,7 +131,7 @@ public void testInformationSchemaPredicatePushdown() .map(ConstraintApplicationResult::getHandle) .map(InformationSchemaTableHandle.class::cast) .orElseThrow(AssertionError::new); - assertEquals(tableHandle.getPrefixes(), ImmutableSet.of(new QualifiedTablePrefix("test_catalog", "test_schema", "test_view"))); + assertThat(tableHandle.getPrefixes()).isEqualTo(ImmutableSet.of(new QualifiedTablePrefix("test_catalog", "test_schema", "test_view"))); } @Test @@ -150,7 +149,7 @@ public void testInformationSchemaPredicatePushdownWithConstraintPredicate() .map(InformationSchemaTableHandle.class::cast) .orElseThrow(AssertionError::new); - assertEquals(tableHandle.getPrefixes(), ImmutableSet.of(new QualifiedTablePrefix("test_catalog", "test_schema", "test_view"))); + assertThat(tableHandle.getPrefixes()).isEqualTo(ImmutableSet.of(new QualifiedTablePrefix("test_catalog", "test_schema", "test_view"))); } @Test @@ -172,7 +171,7 @@ public void testInformationSchemaPredicatePushdownWithoutSchemaPredicate() .map(InformationSchemaTableHandle.class::cast) .orElseThrow(AssertionError::new); // filter blindly applies filter to all visible schemas, so information_schema must be included - assertEquals(tableHandle.getPrefixes(), ImmutableSet.of( + assertThat(tableHandle.getPrefixes()).isEqualTo(ImmutableSet.of( new QualifiedTablePrefix("test_catalog", "test_schema", "test_view"), new QualifiedTablePrefix("test_catalog", "information_schema", "test_view"))); } @@ -195,7 +194,7 @@ public void testInformationSchemaPredicatePushdownWithoutTablePredicate() .map(ConstraintApplicationResult::getHandle) .map(InformationSchemaTableHandle.class::cast) .orElseThrow(AssertionError::new); - assertEquals(tableHandle.getPrefixes(), ImmutableSet.of(new QualifiedTablePrefix("test_catalog", "test_schema"))); + assertThat(tableHandle.getPrefixes()).isEqualTo(ImmutableSet.of(new QualifiedTablePrefix("test_catalog", "test_schema"))); } @Test @@ -214,7 +213,7 @@ public void testInformationSchemaPredicatePushdownWithConstraintPredicateOnViews .map(InformationSchemaTableHandle.class::cast) .orElseThrow(AssertionError::new); - assertEquals(tableHandle.getPrefixes(), ImmutableSet.of(new QualifiedTablePrefix("test_catalog", "test_schema"))); + assertThat(tableHandle.getPrefixes()).isEqualTo(ImmutableSet.of(new QualifiedTablePrefix("test_catalog", "test_schema"))); } @Test @@ -230,7 +229,7 @@ public void testInformationSchemaPredicatePushdownOnCatalogWiseTables() InformationSchemaTableHandle tableHandle = (InformationSchemaTableHandle) metadata.getTableHandle(session, new SchemaTableName("information_schema", "schemata")); Optional> result = metadata.applyFilter(session, tableHandle, constraint); - assertFalse(result.isPresent()); + assertThat(result.isPresent()).isFalse(); } @Test @@ -251,7 +250,7 @@ public void testInformationSchemaPredicatePushdownForEmptyNames() .orElseThrow(AssertionError::new); // "" schema name is valid schema name, but is (currently) valid for QualifiedTablePrefix - assertEquals(filtered.getPrefixes(), ImmutableSet.of(new QualifiedTablePrefix("test_catalog", ""))); + assertThat(filtered.getPrefixes()).isEqualTo(ImmutableSet.of(new QualifiedTablePrefix("test_catalog", ""))); // Empty table name filtered = metadata.applyFilter(session, tableHandle, new Constraint(TupleDomain.withColumnDomains( @@ -262,7 +261,7 @@ public void testInformationSchemaPredicatePushdownForEmptyNames() // "" table name is valid schema name, but is (currently) valid for QualifiedTablePrefix // filter blindly applies filter to all visible schemas, so information_schema must be included - assertEquals(filtered.getPrefixes(), ImmutableSet.of( + assertThat(filtered.getPrefixes()).isEqualTo(ImmutableSet.of( new QualifiedTablePrefix("test_catalog", "test_schema", ""), new QualifiedTablePrefix("test_catalog", "information_schema", ""))); } diff --git a/core/trino-main/src/test/java/io/trino/metadata/TestInternalBlockEncodingSerde.java b/core/trino-main/src/test/java/io/trino/metadata/TestInternalBlockEncodingSerde.java index 4f2ef061a948..5772d4e7312b 100644 --- a/core/trino-main/src/test/java/io/trino/metadata/TestInternalBlockEncodingSerde.java +++ b/core/trino-main/src/test/java/io/trino/metadata/TestInternalBlockEncodingSerde.java @@ -29,7 +29,7 @@ import static io.trino.spi.type.BooleanType.BOOLEAN; import static io.trino.spi.type.VarcharType.VARCHAR; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestInternalBlockEncodingSerde { @@ -47,8 +47,8 @@ public void blockRoundTrip() DynamicSliceOutput sliceOutput = new DynamicSliceOutput(1024); blockEncodingSerde.writeBlock(sliceOutput, blockBuilder.build()); Block copy = blockEncodingSerde.readBlock(sliceOutput.slice().getInput()); - assertEquals(VARCHAR.getSlice(copy, 0).toStringUtf8(), "hello"); - assertEquals(VARCHAR.getSlice(copy, 1).toStringUtf8(), "world"); + assertThat(VARCHAR.getSlice(copy, 0).toStringUtf8()).isEqualTo("hello"); + assertThat(VARCHAR.getSlice(copy, 1).toStringUtf8()).isEqualTo("world"); } @Test @@ -57,6 +57,6 @@ public void testTypeRoundTrip() DynamicSliceOutput sliceOutput = new DynamicSliceOutput(1024); blockEncodingSerde.writeType(sliceOutput, BOOLEAN); Type actualType = blockEncodingSerde.readType(sliceOutput.slice().getInput()); - assertEquals(actualType, BOOLEAN); + assertThat(actualType).isEqualTo(BOOLEAN); } } diff --git a/core/trino-main/src/test/java/io/trino/metadata/TestPolymorphicScalarFunction.java b/core/trino-main/src/test/java/io/trino/metadata/TestPolymorphicScalarFunction.java index 448550ab53c0..5a18885df511 100644 --- a/core/trino-main/src/test/java/io/trino/metadata/TestPolymorphicScalarFunction.java +++ b/core/trino-main/src/test/java/io/trino/metadata/TestPolymorphicScalarFunction.java @@ -49,10 +49,8 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.spi.type.VarcharType.createVarcharType; import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestPolymorphicScalarFunction { @@ -111,16 +109,12 @@ public void testSelectsMultipleChoiceWithBlockPosition() shortDecimalBoundSignature, new InternalFunctionDependencies(FUNCTION_MANAGER::getScalarFunctionImplementation, ImmutableMap.of(), ImmutableSet.of())); - assertEquals(specializedFunction.getChoices().size(), 2); - assertEquals( - specializedFunction.getChoices().get(0).getInvocationConvention(), - new InvocationConvention(ImmutableList.of(NULL_FLAG, NULL_FLAG), FAIL_ON_NULL, false, false)); - assertEquals( - specializedFunction.getChoices().get(1).getInvocationConvention(), - new InvocationConvention(ImmutableList.of(BLOCK_POSITION, BLOCK_POSITION), FAIL_ON_NULL, false, false)); + assertThat(specializedFunction.getChoices().size()).isEqualTo(2); + assertThat(specializedFunction.getChoices().get(0).getInvocationConvention()).isEqualTo(new InvocationConvention(ImmutableList.of(NULL_FLAG, NULL_FLAG), FAIL_ON_NULL, false, false)); + assertThat(specializedFunction.getChoices().get(1).getInvocationConvention()).isEqualTo(new InvocationConvention(ImmutableList.of(BLOCK_POSITION, BLOCK_POSITION), FAIL_ON_NULL, false, false)); Block block1 = new LongArrayBlock(0, Optional.empty(), new long[0]); Block block2 = new LongArrayBlock(0, Optional.empty(), new long[0]); - assertFalse((boolean) specializedFunction.getChoices().get(1).getMethodHandle().invoke(block1, 0, block2, 0)); + assertThat((boolean) specializedFunction.getChoices().get(1).getMethodHandle().invoke(block1, 0, block2, 0)).isFalse(); BoundSignature longDecimalBoundSignature = new BoundSignature( builtinFunctionName(mangleOperatorName(IS_DISTINCT_FROM)), @@ -129,7 +123,7 @@ public void testSelectsMultipleChoiceWithBlockPosition() specializedFunction = (ChoicesSpecializedSqlScalarFunction) function.specialize( longDecimalBoundSignature, new InternalFunctionDependencies(FUNCTION_MANAGER::getScalarFunctionImplementation, ImmutableMap.of(), ImmutableSet.of())); - assertTrue((boolean) specializedFunction.getChoices().get(1).getMethodHandle().invoke(block1, 0, block2, 0)); + assertThat((boolean) specializedFunction.getChoices().get(1).getMethodHandle().invoke(block1, 0, block2, 0)).isTrue(); } @Test @@ -149,7 +143,7 @@ public void testSelectsMethodBasedOnArgumentTypes() ChoicesSpecializedSqlScalarFunction specializedFunction = (ChoicesSpecializedSqlScalarFunction) function.specialize( BOUND_SIGNATURE, new InternalFunctionDependencies(FUNCTION_MANAGER::getScalarFunctionImplementation, ImmutableMap.of(), ImmutableSet.of())); - assertEquals(specializedFunction.getChoices().get(0).getMethodHandle().invoke(INPUT_SLICE), (long) INPUT_VARCHAR_LENGTH); + assertThat(specializedFunction.getChoices().get(0).getMethodHandle().invoke(INPUT_SLICE)).isEqualTo((long) INPUT_VARCHAR_LENGTH); } @Test @@ -170,7 +164,7 @@ public void testSelectsMethodBasedOnReturnType() BOUND_SIGNATURE, new InternalFunctionDependencies(FUNCTION_MANAGER::getScalarFunctionImplementation, ImmutableMap.of(), ImmutableSet.of())); - assertEquals(specializedFunction.getChoices().get(0).getMethodHandle().invoke(INPUT_SLICE), VARCHAR_TO_BIGINT_RETURN_VALUE); + assertThat(specializedFunction.getChoices().get(0).getMethodHandle().invoke(INPUT_SLICE)).isEqualTo(VARCHAR_TO_BIGINT_RETURN_VALUE); } @Test @@ -198,7 +192,7 @@ public void testSameLiteralInArgumentsAndReturnValue() boundSignature, new InternalFunctionDependencies(FUNCTION_MANAGER::getScalarFunctionImplementation, ImmutableMap.of(), ImmutableSet.of())); Slice slice = (Slice) specializedFunction.getChoices().get(0).getMethodHandle().invoke(INPUT_SLICE); - assertEquals(slice, VARCHAR_TO_VARCHAR_RETURN_VALUE); + assertThat(slice).isEqualTo(VARCHAR_TO_VARCHAR_RETURN_VALUE); } @Test @@ -230,7 +224,7 @@ public void testTypeParameters() boundSignature, new InternalFunctionDependencies(FUNCTION_MANAGER::getScalarFunctionImplementation, ImmutableMap.of(), ImmutableSet.of())); Slice slice = (Slice) specializedFunction.getChoices().get(0).getMethodHandle().invoke(INPUT_SLICE); - assertEquals(slice, VARCHAR_TO_VARCHAR_RETURN_VALUE); + assertThat(slice).isEqualTo(VARCHAR_TO_VARCHAR_RETURN_VALUE); } @Test diff --git a/core/trino-main/src/test/java/io/trino/metadata/TestQualifiedObjectName.java b/core/trino-main/src/test/java/io/trino/metadata/TestQualifiedObjectName.java index 6b407dc0e66b..e99726d4e7d1 100644 --- a/core/trino-main/src/test/java/io/trino/metadata/TestQualifiedObjectName.java +++ b/core/trino-main/src/test/java/io/trino/metadata/TestQualifiedObjectName.java @@ -16,8 +16,8 @@ import io.airlift.json.JsonCodec; import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestQualifiedObjectName { @@ -48,6 +48,6 @@ private void testRoundTrip(QualifiedObjectName value) { String json = codec.toJson(value); QualifiedObjectName parsed = codec.fromJson(json); - assertEquals(parsed, value); + assertThat(parsed).isEqualTo(value); } } diff --git a/core/trino-main/src/test/java/io/trino/metadata/TestQualifiedTablePrefix.java b/core/trino-main/src/test/java/io/trino/metadata/TestQualifiedTablePrefix.java index 691272c28f48..449acc61eea9 100644 --- a/core/trino-main/src/test/java/io/trino/metadata/TestQualifiedTablePrefix.java +++ b/core/trino-main/src/test/java/io/trino/metadata/TestQualifiedTablePrefix.java @@ -17,10 +17,8 @@ import org.junit.jupiter.api.Test; import static io.airlift.json.JsonCodec.jsonCodec; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestQualifiedTablePrefix { @@ -30,10 +28,10 @@ public class TestQualifiedTablePrefix public void testCatalog() { QualifiedTablePrefix tableName = new QualifiedTablePrefix("catalog"); - assertEquals("catalog", tableName.getCatalogName()); + assertThat("catalog").isEqualTo(tableName.getCatalogName()); - assertFalse(tableName.hasSchemaName()); - assertFalse(tableName.hasTableName()); + assertThat(tableName.hasSchemaName()).isFalse(); + assertThat(tableName.hasTableName()).isFalse(); } @Test @@ -41,24 +39,24 @@ public void testSchema() { QualifiedTablePrefix tableName = new QualifiedTablePrefix("catalog", "schema"); - assertEquals("catalog", tableName.getCatalogName()); - assertTrue(tableName.hasSchemaName()); + assertThat("catalog").isEqualTo(tableName.getCatalogName()); + assertThat(tableName.hasSchemaName()).isTrue(); - assertEquals("schema", tableName.getSchemaName().get()); - assertFalse(tableName.hasTableName()); + assertThat("schema").isEqualTo(tableName.getSchemaName().get()); + assertThat(tableName.hasTableName()).isFalse(); } @Test public void testTable() { QualifiedTablePrefix tableName = new QualifiedTablePrefix("catalog", "schema", "table"); - assertEquals("catalog", tableName.getCatalogName()); + assertThat("catalog").isEqualTo(tableName.getCatalogName()); - assertTrue(tableName.hasSchemaName()); - assertEquals("schema", tableName.getSchemaName().get()); + assertThat(tableName.hasSchemaName()).isTrue(); + assertThat("schema").isEqualTo(tableName.getSchemaName().get()); - assertTrue(tableName.hasTableName()); - assertEquals("table", tableName.getTableName().get()); + assertThat(tableName.hasTableName()).isTrue(); + assertThat("table").isEqualTo(tableName.getTableName().get()); } @Test @@ -73,6 +71,6 @@ public void testNullSchema() public void testRoundTrip() { QualifiedTablePrefix table = new QualifiedTablePrefix("abc", "xyz", "fgh"); - assertEquals(CODEC.fromJson(CODEC.toJson(table)), table); + assertThat(CODEC.fromJson(CODEC.toJson(table))).isEqualTo(table); } } diff --git a/core/trino-main/src/test/java/io/trino/metadata/TestResolvedFunction.java b/core/trino-main/src/test/java/io/trino/metadata/TestResolvedFunction.java index 5cf46c0633c0..91dd61feceb6 100644 --- a/core/trino-main/src/test/java/io/trino/metadata/TestResolvedFunction.java +++ b/core/trino-main/src/test/java/io/trino/metadata/TestResolvedFunction.java @@ -36,8 +36,7 @@ import static io.trino.spi.function.FunctionKind.SCALAR; import static io.trino.spi.type.VarcharType.createVarcharType; import static java.lang.Integer.parseInt; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestResolvedFunction { @@ -49,8 +48,8 @@ public void test() ResolvedFunction resolvedFunction = createResolvedFunction("top", 3); ResolvedFunctionDecoder decoder = new ResolvedFunctionDecoder(TestResolvedFunction::varcharTypeLoader); Optional copy = decoder.fromQualifiedName(resolvedFunction.toQualifiedName()); - assertTrue(copy.isPresent()); - assertEquals(copy.get(), resolvedFunction); + assertThat(copy.isPresent()).isTrue(); + assertThat(copy.get()).isEqualTo(resolvedFunction); } private static ResolvedFunction createResolvedFunction(String name, int depth) @@ -80,7 +79,7 @@ private static Type varcharTypeLoader(TypeId typeId) { Matcher matcher = VARCHAR_MATCHER.matcher(typeId.getId()); boolean matches = matcher.matches(); - assertTrue(matches); + assertThat(matches).isTrue(); return createVarcharType(parseInt(matcher.group(1))); } } diff --git a/core/trino-main/src/test/java/io/trino/metadata/TestSignature.java b/core/trino-main/src/test/java/io/trino/metadata/TestSignature.java index bb9594b4a372..4dd2208d9b54 100644 --- a/core/trino-main/src/test/java/io/trino/metadata/TestSignature.java +++ b/core/trino-main/src/test/java/io/trino/metadata/TestSignature.java @@ -29,7 +29,7 @@ import static io.trino.spi.type.DoubleType.DOUBLE; import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestSignature { @@ -52,7 +52,7 @@ Type.class, new TypeDeserializer(TESTING_TYPE_MANAGER), String json = codec.toJson(expected); Signature actual = codec.fromJson(json); - assertEquals(actual.getReturnType(), expected.getReturnType()); - assertEquals(actual.getArgumentTypes(), expected.getArgumentTypes()); + assertThat(actual.getReturnType()).isEqualTo(expected.getReturnType()); + assertThat(actual.getArgumentTypes()).isEqualTo(expected.getArgumentTypes()); } } diff --git a/core/trino-main/src/test/java/io/trino/metadata/TestSignatureBinder.java b/core/trino-main/src/test/java/io/trino/metadata/TestSignatureBinder.java index beb3ebffea40..fd740382bc66 100644 --- a/core/trino-main/src/test/java/io/trino/metadata/TestSignatureBinder.java +++ b/core/trino-main/src/test/java/io/trino/metadata/TestSignatureBinder.java @@ -24,6 +24,7 @@ import io.trino.spi.type.TypeSignatureParameter; import io.trino.sql.analyzer.TypeSignatureProvider; import io.trino.type.FunctionType; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import java.util.List; @@ -55,11 +56,7 @@ import static java.lang.String.format; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Fail.fail; public class TestSignatureBinder { @@ -1148,9 +1145,7 @@ private static void assertBindVariablesFails(String typeSignature, TypeVariables private static void assertThat(String typeSignature, TypeVariables typeVariables, String expectedTypeSignature) { - assertEquals( - SignatureBinder.applyBoundVariables(parseTypeSignature(typeSignature, ImmutableSet.of("p", "s")), typeVariables).toString(), - expectedTypeSignature); + Assertions.assertThat(SignatureBinder.applyBoundVariables(parseTypeSignature(typeSignature, ImmutableSet.of("p", "s")), typeVariables).toString()).isEqualTo(expectedTypeSignature); } private static Signature.Builder functionSignature() @@ -1213,27 +1208,27 @@ public BindSignatureAssertion boundTo(List arguments, Type returnType) public BindSignatureAssertion succeeds() { - assertTrue(bindVariables().isPresent()); + Assertions.assertThat(bindVariables().isPresent()).isTrue(); return this; } public BindSignatureAssertion fails() { - assertFalse(bindVariables().isPresent()); + Assertions.assertThat(bindVariables().isPresent()).isFalse(); return this; } public BindSignatureAssertion produces(TypeVariables expected) { Optional actual = bindVariables(); - assertTrue(actual.isPresent()); - assertEquals(actual.get(), expected); + Assertions.assertThat(actual.isPresent()).isTrue(); + Assertions.assertThat(actual.get()).isEqualTo(expected); return this; } private Optional bindVariables() { - assertNotNull(argumentTypes); + Assertions.assertThat(argumentTypes).isNotNull(); SignatureBinder signatureBinder = new SignatureBinder(PLANNER_CONTEXT.getMetadata(), PLANNER_CONTEXT.getTypeManager(), function, allowCoercion); if (returnType == null) { return signatureBinder.bindVariables(argumentTypes); diff --git a/core/trino-main/src/test/java/io/trino/operator/AnnotationEngineAssertions.java b/core/trino-main/src/test/java/io/trino/operator/AnnotationEngineAssertions.java index 7fd382ca82f8..905929ce6562 100644 --- a/core/trino-main/src/test/java/io/trino/operator/AnnotationEngineAssertions.java +++ b/core/trino-main/src/test/java/io/trino/operator/AnnotationEngineAssertions.java @@ -16,7 +16,7 @@ import io.trino.operator.aggregation.ParametricAggregationImplementation; import io.trino.operator.scalar.ParametricScalar; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; class AnnotationEngineAssertions { @@ -29,15 +29,15 @@ public static void assertImplementationCount(ParametricScalar scalar, int exact, public static void assertImplementationCount(ParametricImplementationsGroup implementations, int exact, int specialized, int generic) { - assertEquals(implementations.getExactImplementations().size(), exact); - assertEquals(implementations.getSpecializedImplementations().size(), specialized); - assertEquals(implementations.getGenericImplementations().size(), generic); + assertThat(implementations.getExactImplementations().size()).isEqualTo(exact); + assertThat(implementations.getSpecializedImplementations().size()).isEqualTo(specialized); + assertThat(implementations.getGenericImplementations().size()).isEqualTo(generic); } public static void assertDependencyCount(ParametricAggregationImplementation implementation, int input, int combine, int output) { - assertEquals(implementation.getInputDependencies().size(), input); - assertEquals(implementation.getCombineDependencies().size(), combine); - assertEquals(implementation.getOutputDependencies().size(), output); + assertThat(implementation.getInputDependencies().size()).isEqualTo(input); + assertThat(implementation.getCombineDependencies().size()).isEqualTo(combine); + assertThat(implementation.getOutputDependencies().size()).isEqualTo(output); } } diff --git a/core/trino-main/src/test/java/io/trino/operator/BenchmarkDynamicFilterSourceOperator.java b/core/trino-main/src/test/java/io/trino/operator/BenchmarkDynamicFilterSourceOperator.java index 8b904fcf3a41..4f0f716d9349 100644 --- a/core/trino-main/src/test/java/io/trino/operator/BenchmarkDynamicFilterSourceOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/BenchmarkDynamicFilterSourceOperator.java @@ -53,7 +53,7 @@ import static io.trino.spi.type.BigintType.BIGINT; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; @State(Scope.Thread) @OutputTimeUnit(TimeUnit.MILLISECONDS) @@ -203,7 +203,7 @@ public void testBenchmark() context.setup(); List outputPages = dynamicFilterCollect(context); - assertEquals(TOTAL_POSITIONS, outputPages.stream().mapToInt(Page::getPositionCount).sum()); + assertThat(TOTAL_POSITIONS).isEqualTo(outputPages.stream().mapToInt(Page::getPositionCount).sum()); context.cleanup(); } diff --git a/core/trino-main/src/test/java/io/trino/operator/BenchmarkHashAndStreamingAggregationOperators.java b/core/trino-main/src/test/java/io/trino/operator/BenchmarkHashAndStreamingAggregationOperators.java index 706edf89579a..55df362b8234 100644 --- a/core/trino-main/src/test/java/io/trino/operator/BenchmarkHashAndStreamingAggregationOperators.java +++ b/core/trino-main/src/test/java/io/trino/operator/BenchmarkHashAndStreamingAggregationOperators.java @@ -66,9 +66,9 @@ import static java.util.concurrent.Executors.newScheduledThreadPool; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; import static org.openjdk.jmh.annotations.Mode.AverageTime; import static org.openjdk.jmh.annotations.Scope.Thread; -import static org.testng.Assert.assertEquals; @State(Thread) @OutputTimeUnit(MILLISECONDS) @@ -359,13 +359,13 @@ private void verify(int rowsPerGroup, String operatorType, String groupByTypes) context.groupByTypes = groupByTypes; context.setup(); - assertEquals(TOTAL_PAGES, context.getPages().size()); + assertThat(TOTAL_PAGES).isEqualTo(context.getPages().size()); for (int i = 0; i < TOTAL_PAGES; i++) { - assertEquals(ROWS_PER_PAGE, context.getPages().get(i).getPositionCount()); + assertThat(ROWS_PER_PAGE).isEqualTo(context.getPages().get(i).getPositionCount()); } List outputPages = benchmark(context); - assertEquals(TOTAL_PAGES * ROWS_PER_PAGE / rowsPerGroup, outputPages.stream().mapToInt(Page::getPositionCount).sum()); + assertThat(TOTAL_PAGES * ROWS_PER_PAGE / rowsPerGroup).isEqualTo(outputPages.stream().mapToInt(Page::getPositionCount).sum()); context.cleanup(); } diff --git a/core/trino-main/src/test/java/io/trino/operator/BenchmarkTopNOperator.java b/core/trino-main/src/test/java/io/trino/operator/BenchmarkTopNOperator.java index 1b7112d61d8a..01a8b443edf5 100644 --- a/core/trino-main/src/test/java/io/trino/operator/BenchmarkTopNOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/BenchmarkTopNOperator.java @@ -52,7 +52,7 @@ import static io.trino.spi.type.DoubleType.DOUBLE; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; @State(Scope.Thread) @OutputTimeUnit(TimeUnit.SECONDS) @@ -188,7 +188,7 @@ public void verify() context.setup(); List outputPages = topN(context); - assertEquals(123, outputPages.stream().mapToInt(Page::getPositionCount).sum()); + assertThat(123).isEqualTo(outputPages.stream().mapToInt(Page::getPositionCount).sum()); context.cleanup(); } diff --git a/core/trino-main/src/test/java/io/trino/operator/BenchmarkWindowOperator.java b/core/trino-main/src/test/java/io/trino/operator/BenchmarkWindowOperator.java index 08a76eae33d4..fcf6c2410b72 100644 --- a/core/trino-main/src/test/java/io/trino/operator/BenchmarkWindowOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/BenchmarkWindowOperator.java @@ -55,9 +55,9 @@ import static java.util.concurrent.Executors.newScheduledThreadPool; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; import static org.openjdk.jmh.annotations.Mode.AverageTime; import static org.openjdk.jmh.annotations.Scope.Thread; -import static org.testng.Assert.assertEquals; @State(Thread) @OutputTimeUnit(MILLISECONDS) @@ -320,9 +320,9 @@ private void verify( context.setup(); - assertEquals(TOTAL_PAGES, context.getPages().size()); + assertThat(TOTAL_PAGES).isEqualTo(context.getPages().size()); for (int i = 0; i < TOTAL_PAGES; i++) { - assertEquals(ROWS_PER_PAGE, context.getPages().get(i).getPositionCount()); + assertThat(ROWS_PER_PAGE).isEqualTo(context.getPages().get(i).getPositionCount()); } benchmark(context); diff --git a/core/trino-main/src/test/java/io/trino/operator/GroupByHashYieldAssertion.java b/core/trino-main/src/test/java/io/trino/operator/GroupByHashYieldAssertion.java index 885eb2d94040..298ca2f30931 100644 --- a/core/trino-main/src/test/java/io/trino/operator/GroupByHashYieldAssertion.java +++ b/core/trino-main/src/test/java/io/trino/operator/GroupByHashYieldAssertion.java @@ -48,10 +48,7 @@ import static java.util.Objects.requireNonNull; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public final class GroupByHashYieldAssertion { @@ -116,7 +113,7 @@ public static GroupByHashYieldResult finishOperatorWithYieldingGroupByHash(List< } // unblocked - assertTrue(operator.needsInput()); + assertThat(operator.needsInput()).isTrue(); // reserve the most of the memory pool, except for the space necessary for the variable with data // a small bit of memory is left unallocated for the aggregators @@ -156,10 +153,10 @@ public static GroupByHashYieldResult finishOperatorWithYieldingGroupByHash(List< // The page processing completed // Assert we are not blocked - assertTrue(operator.getOperatorContext().isWaitingForMemory().isDone()); + assertThat(operator.getOperatorContext().isWaitingForMemory().isDone()).isTrue(); // assert the hash capacity is not changed; otherwise, we should have yielded - assertEquals((int) getHashCapacity.apply(operator), oldCapacity); + assertThat((int) getHashCapacity.apply(operator)).isEqualTo(oldCapacity); // We are not going to rehash; therefore, assert the memory increase only comes from the aggregator assertLessThan(actualHashIncreased, additionalMemoryInBytes); @@ -172,10 +169,10 @@ public static GroupByHashYieldResult finishOperatorWithYieldingGroupByHash(List< yieldCount++; // Assert we are blocked waiting for memory - assertFalse(operator.getOperatorContext().isWaitingForMemory().isDone()); + assertThat(operator.getOperatorContext().isWaitingForMemory().isDone()).isFalse(); // Hash table capacity should not have changed, because memory must be allocated first - assertEquals(oldCapacity, (long) getHashCapacity.apply(operator)); + assertThat(oldCapacity).isEqualTo((long) getHashCapacity.apply(operator)); long expectedHashBytes; if (hashKeyType == BIGINT) { @@ -189,7 +186,7 @@ public static GroupByHashYieldResult finishOperatorWithYieldingGroupByHash(List< assertBetweenInclusive(actualHashIncreased, expectedHashBytes, expectedHashBytes + additionalMemoryInBytes); // Output should be blocked as well - assertNull(operator.getOutput()); + assertThat(operator.getOutput()).isNull(); // Free the pool to unblock memoryPool.free(anotherTaskId, "test", memoryPool.getTaskMemoryReservations().get(anotherTaskId)); @@ -199,7 +196,7 @@ public static GroupByHashYieldResult finishOperatorWithYieldingGroupByHash(List< if (output != null) { result.add(output); } - assertTrue(operator.needsInput()); + assertThat(operator.needsInput()).isTrue(); // Hash table capacity has increased assertGreaterThan(getHashCapacity.apply(operator), oldCapacity); @@ -223,8 +220,8 @@ public static GroupByHashYieldResult finishOperatorWithYieldingGroupByHash(List< } // unblocked - assertTrue(operator.needsInput()); - assertTrue(operator.getOperatorContext().isWaitingForMemory().isDone()); + assertThat(operator.needsInput()).isTrue(); + assertThat(operator.getOperatorContext().isWaitingForMemory().isDone()).isTrue(); } } diff --git a/core/trino-main/src/test/java/io/trino/operator/MockExchangeRequestProcessor.java b/core/trino-main/src/test/java/io/trino/operator/MockExchangeRequestProcessor.java index fd17bacf99eb..f7c7b41a2ebe 100644 --- a/core/trino-main/src/test/java/io/trino/operator/MockExchangeRequestProcessor.java +++ b/core/trino-main/src/test/java/io/trino/operator/MockExchangeRequestProcessor.java @@ -54,8 +54,7 @@ import static io.trino.server.InternalHeaders.TRINO_TASK_FAILED; import static io.trino.server.InternalHeaders.TRINO_TASK_INSTANCE_ID; import static io.trino.server.PagesResponseWriter.SERIALIZED_PAGES_MAGIC; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class MockExchangeRequestProcessor implements TestingHttpClient.Processor @@ -101,9 +100,9 @@ public Response handle(Request request) } // verify we got a data size and it parses correctly - assertTrue(!request.getHeaders().get(InternalHeaders.TRINO_MAX_SIZE).isEmpty()); + assertThat(!request.getHeaders().get(InternalHeaders.TRINO_MAX_SIZE).isEmpty()).isTrue(); DataSize maxSize = DataSize.valueOf(request.getHeader(InternalHeaders.TRINO_MAX_SIZE)); - assertEquals(maxSize, expectedMaxSize); + assertThat(maxSize).isEqualTo(expectedMaxSize); RequestLocation requestLocation = new RequestLocation(request.getUri()); URI location = requestLocation.getLocation(); @@ -213,7 +212,9 @@ public BufferResult getPages(long sequenceId, DataSize maxSize) throw failure; } - assertEquals(sequenceId, token.get(), "token"); + assertThat(sequenceId) + .describedAs("token") + .isEqualTo(token.get()); // wait for a single page to arrive Slice serializedPage = null; diff --git a/core/trino-main/src/test/java/io/trino/operator/OperatorAssertion.java b/core/trino-main/src/test/java/io/trino/operator/OperatorAssertion.java index 7dacf2105754..9342fcbdd887 100644 --- a/core/trino-main/src/test/java/io/trino/operator/OperatorAssertion.java +++ b/core/trino-main/src/test/java/io/trino/operator/OperatorAssertion.java @@ -48,8 +48,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Fail.fail; public final class OperatorAssertion { @@ -84,7 +83,7 @@ public static List toPagesPartial(Operator operator, Iterator input) public static List toPagesPartial(Operator operator, Iterator input, boolean revokeMemory) { // verify initial state - assertEquals(operator.isFinished(), false); + assertThat(operator.isFinished()).isEqualTo(false); ImmutableList.Builder outputPages = ImmutableList.builder(); for (int loopsSinceLastPage = 0; loopsSinceLastPage < 1_000; loopsSinceLastPage++) { @@ -131,9 +130,15 @@ public static List finishOperator(Operator operator) handleMemoryRevoking(operator); } - assertEquals(operator.isFinished(), true, "Operator did not finish"); - assertEquals(operator.needsInput(), false, "Operator still wants input"); - assertEquals(operator.isBlocked().isDone(), true, "Operator is blocked"); + assertThat(operator.isFinished()) + .describedAs("Operator did not finish") + .isEqualTo(true); + assertThat(operator.needsInput()) + .describedAs("Operator still wants input") + .isEqualTo(false); + assertThat(operator.isBlocked().isDone()) + .describedAs("Operator is blocked") + .isEqualTo(true); return outputPages.build(); } @@ -209,7 +214,7 @@ public static SqlRow toRow(List parameterTypes, Object... values) public static void assertOperatorEquals(OperatorFactory operatorFactory, List types, DriverContext driverContext, List input, List expected) { List actual = toPages(operatorFactory, driverContext, input); - assertEquals(actual.size(), expected.size()); + assertThat(actual.size()).isEqualTo(expected.size()); for (int i = 0; i < actual.size(); i++) { assertPageEquals(types, actual.get(i), expected.get(i)); } diff --git a/core/trino-main/src/test/java/io/trino/operator/PageAssertions.java b/core/trino-main/src/test/java/io/trino/operator/PageAssertions.java index 8a1a21bf2d8a..31af1bd63b32 100644 --- a/core/trino-main/src/test/java/io/trino/operator/PageAssertions.java +++ b/core/trino-main/src/test/java/io/trino/operator/PageAssertions.java @@ -19,7 +19,7 @@ import java.util.List; import static io.trino.block.BlockAssertions.assertBlockEquals; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public final class PageAssertions { @@ -27,9 +27,9 @@ private PageAssertions() {} public static void assertPageEquals(List types, Page actualPage, Page expectedPage) { - assertEquals(types.size(), actualPage.getChannelCount()); - assertEquals(actualPage.getChannelCount(), expectedPage.getChannelCount()); - assertEquals(actualPage.getPositionCount(), expectedPage.getPositionCount()); + assertThat(types.size()).isEqualTo(actualPage.getChannelCount()); + assertThat(actualPage.getChannelCount()).isEqualTo(expectedPage.getChannelCount()); + assertThat(actualPage.getPositionCount()).isEqualTo(expectedPage.getPositionCount()); for (int i = 0; i < actualPage.getChannelCount(); i++) { assertBlockEquals(types.get(i), actualPage.getBlock(i), expectedPage.getBlock(i)); } diff --git a/core/trino-main/src/test/java/io/trino/operator/TestAggregationOperator.java b/core/trino-main/src/test/java/io/trino/operator/TestAggregationOperator.java index 7d852cf6ab84..e1b1f1ae6db2 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestAggregationOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestAggregationOperator.java @@ -56,8 +56,6 @@ import static java.util.concurrent.Executors.newScheduledThreadPool; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; @TestInstance(PER_METHOD) public class TestAggregationOperator @@ -178,7 +176,7 @@ public void testAggregation() .build(); assertOperatorEquals(operatorFactory, driverContext, input, expected); - assertEquals(driverContext.getMemoryUsage(), 0); + assertThat(driverContext.getMemoryUsage()).isEqualTo(0); } @Test @@ -197,7 +195,7 @@ public void testMemoryTracking() .addDriverContext(); try (Operator operator = operatorFactory.createOperator(driverContext)) { - assertTrue(operator.needsInput()); + assertThat(operator.needsInput()).isTrue(); operator.addInput(input); assertThat(driverContext.getMemoryUsage()).isGreaterThan(0); @@ -205,6 +203,6 @@ public void testMemoryTracking() toPages(operator, emptyIterator()); } - assertEquals(driverContext.getMemoryUsage(), 0); + assertThat(driverContext.getMemoryUsage()).isEqualTo(0); } } diff --git a/core/trino-main/src/test/java/io/trino/operator/TestAnnotationEngineForAggregates.java b/core/trino-main/src/test/java/io/trino/operator/TestAnnotationEngineForAggregates.java index 174820181ad1..6d26abd26b76 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestAnnotationEngineForAggregates.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestAnnotationEngineForAggregates.java @@ -101,9 +101,6 @@ import static java.lang.invoke.MethodType.methodType; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestAnnotationEngineForAggregates { @@ -143,21 +140,21 @@ public void testSimpleExactAggregationParse() .build(); ParametricAggregation aggregation = getOnlyElement(parseFunctionDefinitions(ExactAggregationFunction.class)); - assertEquals(aggregation.getFunctionMetadata().getDescription(), "Simple exact aggregate description"); - assertTrue(aggregation.getFunctionMetadata().isDeterministic()); - assertEquals(aggregation.getFunctionMetadata().getSignature(), expectedSignature); + assertThat(aggregation.getFunctionMetadata().getDescription()).isEqualTo("Simple exact aggregate description"); + assertThat(aggregation.getFunctionMetadata().isDeterministic()).isTrue(); + assertThat(aggregation.getFunctionMetadata().getSignature()).isEqualTo(expectedSignature); ParametricImplementationsGroup implementations = aggregation.getImplementations(); assertImplementationCount(implementations, 1, 0, 0); ParametricAggregationImplementation implementation = getOnlyElement(implementations.getExactImplementations().values()); - assertEquals(implementation.getDefinitionClass(), ExactAggregationFunction.class); + assertThat(implementation.getDefinitionClass()).isEqualTo(ExactAggregationFunction.class); assertDependencyCount(implementation, 0, 0, 0); - assertFalse(implementation.hasSpecializedTypeParameters()); - assertEquals(implementation.getInputParameterKinds(), ImmutableList.of(STATE, INPUT_CHANNEL)); + assertThat(implementation.hasSpecializedTypeParameters()).isFalse(); + assertThat(implementation.getInputParameterKinds()).isEqualTo(ImmutableList.of(STATE, INPUT_CHANNEL)); BoundSignature boundSignature = builtinFunction("simple_exact_aggregate", DoubleType.DOUBLE, ImmutableList.of(DoubleType.DOUBLE)); AggregationFunctionMetadata aggregationMetadata = aggregation.getAggregationMetadata(); - assertFalse(aggregationMetadata.isOrderSensitive()); - assertFalse(aggregationMetadata.getIntermediateTypes().isEmpty()); + assertThat(aggregationMetadata.isOrderSensitive()).isFalse(); + assertThat(aggregationMetadata.getIntermediateTypes().isEmpty()).isFalse(); aggregation.specialize(boundSignature, NO_FUNCTION_DEPENDENCIES); } @@ -252,12 +249,12 @@ public void testNotAnnotatedAggregateStateAggregationParse() ParametricAggregation aggregation = getOnlyElement(parseFunctionDefinitions(NotAnnotatedAggregateStateAggregationFunction.class)); ParametricAggregationImplementation implementation = getOnlyElement(aggregation.getImplementations().getExactImplementations().values()); - assertEquals(implementation.getInputParameterKinds(), ImmutableList.of(STATE, INPUT_CHANNEL)); + assertThat(implementation.getInputParameterKinds()).isEqualTo(ImmutableList.of(STATE, INPUT_CHANNEL)); BoundSignature boundSignature = builtinFunction(aggregation.getFunctionMetadata().getCanonicalName(), DoubleType.DOUBLE, ImmutableList.of(DoubleType.DOUBLE)); AggregationFunctionMetadata aggregationMetadata = aggregation.getAggregationMetadata(); - assertFalse(aggregationMetadata.isOrderSensitive()); - assertFalse(aggregationMetadata.getIntermediateTypes().isEmpty()); + assertThat(aggregationMetadata.isOrderSensitive()).isFalse(); + assertThat(aggregationMetadata.getIntermediateTypes().isEmpty()).isFalse(); aggregation.specialize(boundSignature, NO_FUNCTION_DEPENDENCIES); } @@ -291,14 +288,14 @@ public void testNotDecomposableAggregationParse() .build(); ParametricAggregation aggregation = getOnlyElement(parseFunctionDefinitions(NotDecomposableAggregationFunction.class)); - assertEquals(aggregation.getFunctionMetadata().getDescription(), "Aggregate with Decomposable=false"); - assertTrue(aggregation.getFunctionMetadata().isDeterministic()); - assertEquals(aggregation.getFunctionMetadata().getSignature(), expectedSignature); + assertThat(aggregation.getFunctionMetadata().getDescription()).isEqualTo("Aggregate with Decomposable=false"); + assertThat(aggregation.getFunctionMetadata().isDeterministic()).isTrue(); + assertThat(aggregation.getFunctionMetadata().getSignature()).isEqualTo(expectedSignature); BoundSignature boundSignature = builtinFunction(aggregation.getFunctionMetadata().getCanonicalName(), DoubleType.DOUBLE, ImmutableList.of(DoubleType.DOUBLE)); AggregationFunctionMetadata aggregationMetadata = aggregation.getAggregationMetadata(); - assertFalse(aggregationMetadata.isOrderSensitive()); - assertTrue(aggregationMetadata.getIntermediateTypes().isEmpty()); + assertThat(aggregationMetadata.isOrderSensitive()).isFalse(); + assertThat(aggregationMetadata.getIntermediateTypes().isEmpty()).isTrue(); aggregation.specialize(boundSignature, NO_FUNCTION_DEPENDENCIES); } @@ -351,34 +348,34 @@ public void testSimpleGenericAggregationFunctionParse() .build(); ParametricAggregation aggregation = getOnlyElement(parseFunctionDefinitions(GenericAggregationFunction.class)); - assertEquals(aggregation.getFunctionMetadata().getDescription(), "Simple aggregate with two generic implementations"); - assertTrue(aggregation.getFunctionMetadata().isDeterministic()); - assertEquals(aggregation.getFunctionMetadata().getSignature(), expectedSignature); - assertEquals(aggregation.getStateDetails(), ImmutableList.of(toAccumulatorStateDetails(NullableLongState.class, ImmutableList.of()))); + assertThat(aggregation.getFunctionMetadata().getDescription()).isEqualTo("Simple aggregate with two generic implementations"); + assertThat(aggregation.getFunctionMetadata().isDeterministic()).isTrue(); + assertThat(aggregation.getFunctionMetadata().getSignature()).isEqualTo(expectedSignature); + assertThat(aggregation.getStateDetails()).isEqualTo(ImmutableList.of(toAccumulatorStateDetails(NullableLongState.class, ImmutableList.of()))); ParametricImplementationsGroup implementations = aggregation.getImplementations(); assertImplementationCount(implementations, 0, 0, 2); ParametricAggregationImplementation implementationDouble = implementations.getGenericImplementations().stream() .filter(impl -> impl.getInputFunction().type().equals(methodType(void.class, NullableLongState.class, double.class))) .collect(toImmutableList()) .get(0); - assertEquals(implementationDouble.getDefinitionClass(), GenericAggregationFunction.class); + assertThat(implementationDouble.getDefinitionClass()).isEqualTo(GenericAggregationFunction.class); assertDependencyCount(implementationDouble, 0, 0, 0); - assertFalse(implementationDouble.hasSpecializedTypeParameters()); - assertEquals(implementationDouble.getInputParameterKinds(), ImmutableList.of(STATE, INPUT_CHANNEL)); + assertThat(implementationDouble.hasSpecializedTypeParameters()).isFalse(); + assertThat(implementationDouble.getInputParameterKinds()).isEqualTo(ImmutableList.of(STATE, INPUT_CHANNEL)); ParametricAggregationImplementation implementationLong = implementations.getGenericImplementations().stream() .filter(impl -> impl.getInputFunction().type().equals(methodType(void.class, NullableLongState.class, long.class))) .collect(toImmutableList()) .get(0); - assertEquals(implementationLong.getDefinitionClass(), GenericAggregationFunction.class); + assertThat(implementationLong.getDefinitionClass()).isEqualTo(GenericAggregationFunction.class); assertDependencyCount(implementationLong, 0, 0, 0); - assertFalse(implementationLong.hasSpecializedTypeParameters()); - assertEquals(implementationLong.getInputParameterKinds(), ImmutableList.of(STATE, INPUT_CHANNEL)); + assertThat(implementationLong.hasSpecializedTypeParameters()).isFalse(); + assertThat(implementationLong.getInputParameterKinds()).isEqualTo(ImmutableList.of(STATE, INPUT_CHANNEL)); BoundSignature boundSignature = builtinFunction(aggregation.getFunctionMetadata().getCanonicalName(), DoubleType.DOUBLE, ImmutableList.of(DoubleType.DOUBLE)); AggregationFunctionMetadata aggregationMetadata = aggregation.getAggregationMetadata(); - assertFalse(aggregationMetadata.isOrderSensitive()); - assertFalse(aggregationMetadata.getIntermediateTypes().isEmpty()); + assertThat(aggregationMetadata.isOrderSensitive()).isFalse(); + assertThat(aggregationMetadata.getIntermediateTypes().isEmpty()).isFalse(); aggregation.specialize(boundSignature, NO_FUNCTION_DEPENDENCIES); } @@ -421,21 +418,21 @@ public void testSimpleBlockInputAggregationParse() .build(); ParametricAggregation aggregation = getOnlyElement(parseFunctionDefinitions(BlockInputAggregationFunction.class)); - assertEquals(aggregation.getFunctionMetadata().getDescription(), "Simple aggregate with @BlockPosition usage"); - assertTrue(aggregation.getFunctionMetadata().isDeterministic()); - assertEquals(aggregation.getFunctionMetadata().getSignature(), expectedSignature); + assertThat(aggregation.getFunctionMetadata().getDescription()).isEqualTo("Simple aggregate with @BlockPosition usage"); + assertThat(aggregation.getFunctionMetadata().isDeterministic()).isTrue(); + assertThat(aggregation.getFunctionMetadata().getSignature()).isEqualTo(expectedSignature); ParametricImplementationsGroup implementations = aggregation.getImplementations(); assertImplementationCount(implementations, 1, 0, 0); ParametricAggregationImplementation implementation = getOnlyElement(implementations.getExactImplementations().values()); - assertEquals(implementation.getDefinitionClass(), BlockInputAggregationFunction.class); + assertThat(implementation.getDefinitionClass()).isEqualTo(BlockInputAggregationFunction.class); assertDependencyCount(implementation, 0, 0, 0); - assertFalse(implementation.hasSpecializedTypeParameters()); - assertEquals(implementation.getInputParameterKinds(), ImmutableList.of(STATE, BLOCK_INPUT_CHANNEL, BLOCK_INDEX)); + assertThat(implementation.hasSpecializedTypeParameters()).isFalse(); + assertThat(implementation.getInputParameterKinds()).isEqualTo(ImmutableList.of(STATE, BLOCK_INPUT_CHANNEL, BLOCK_INDEX)); BoundSignature boundSignature = builtinFunction(aggregation.getFunctionMetadata().getCanonicalName(), DoubleType.DOUBLE, ImmutableList.of(DoubleType.DOUBLE)); AggregationFunctionMetadata aggregationMetadata = aggregation.getAggregationMetadata(); - assertFalse(aggregationMetadata.isOrderSensitive()); - assertFalse(aggregationMetadata.getIntermediateTypes().isEmpty()); + assertThat(aggregationMetadata.isOrderSensitive()).isFalse(); + assertThat(aggregationMetadata.getIntermediateTypes().isEmpty()).isFalse(); aggregation.specialize(boundSignature, NO_FUNCTION_DEPENDENCIES); } @@ -506,26 +503,26 @@ public void testSimpleImplicitSpecializedAggregationParse() .build(); ParametricAggregation aggregation = getOnlyElement(parseFunctionDefinitions(ImplicitSpecializedAggregationFunction.class)); - assertEquals(aggregation.getFunctionMetadata().getDescription(), "Simple implicit specialized aggregate"); - assertTrue(aggregation.getFunctionMetadata().isDeterministic()); - assertEquals(aggregation.getFunctionMetadata().getSignature(), expectedSignature); + assertThat(aggregation.getFunctionMetadata().getDescription()).isEqualTo("Simple implicit specialized aggregate"); + assertThat(aggregation.getFunctionMetadata().isDeterministic()).isTrue(); + assertThat(aggregation.getFunctionMetadata().getSignature()).isEqualTo(expectedSignature); ParametricImplementationsGroup implementations = aggregation.getImplementations(); assertImplementationCount(implementations, 0, 0, 2); ParametricAggregationImplementation implementation1 = implementations.getSpecializedImplementations().get(0); - assertTrue(implementation1.hasSpecializedTypeParameters()); - assertFalse(implementation1.hasSpecializedTypeParameters()); - assertEquals(implementation1.getInputParameterKinds(), ImmutableList.of(STATE, INPUT_CHANNEL, INPUT_CHANNEL)); + assertThat(implementation1.hasSpecializedTypeParameters()).isTrue(); + assertThat(implementation1.hasSpecializedTypeParameters()).isFalse(); + assertThat(implementation1.getInputParameterKinds()).isEqualTo(ImmutableList.of(STATE, INPUT_CHANNEL, INPUT_CHANNEL)); ParametricAggregationImplementation implementation2 = implementations.getSpecializedImplementations().get(1); - assertTrue(implementation2.hasSpecializedTypeParameters()); - assertFalse(implementation2.hasSpecializedTypeParameters()); - assertEquals(implementation2.getInputParameterKinds(), ImmutableList.of(STATE, INPUT_CHANNEL, INPUT_CHANNEL)); + assertThat(implementation2.hasSpecializedTypeParameters()).isTrue(); + assertThat(implementation2.hasSpecializedTypeParameters()).isFalse(); + assertThat(implementation2.getInputParameterKinds()).isEqualTo(ImmutableList.of(STATE, INPUT_CHANNEL, INPUT_CHANNEL)); BoundSignature boundSignature = builtinFunction(aggregation.getFunctionMetadata().getCanonicalName(), DoubleType.DOUBLE, ImmutableList.of(new ArrayType(DoubleType.DOUBLE))); AggregationFunctionMetadata aggregationMetadata = aggregation.getAggregationMetadata(); - assertFalse(aggregationMetadata.isOrderSensitive()); - assertFalse(aggregationMetadata.getIntermediateTypes().isEmpty()); + assertThat(aggregationMetadata.isOrderSensitive()).isFalse(); + assertThat(aggregationMetadata.getIntermediateTypes().isEmpty()).isFalse(); aggregation.specialize(boundSignature, NO_FUNCTION_DEPENDENCIES); } @@ -596,25 +593,25 @@ public void testSimpleExplicitSpecializedAggregationParse() .build(); ParametricAggregation aggregation = getOnlyElement(parseFunctionDefinitions(ExplicitSpecializedAggregationFunction.class)); - assertEquals(aggregation.getFunctionMetadata().getDescription(), "Simple explicit specialized aggregate"); - assertTrue(aggregation.getFunctionMetadata().isDeterministic()); - assertEquals(aggregation.getFunctionMetadata().getSignature(), expectedSignature); + assertThat(aggregation.getFunctionMetadata().getDescription()).isEqualTo("Simple explicit specialized aggregate"); + assertThat(aggregation.getFunctionMetadata().isDeterministic()).isTrue(); + assertThat(aggregation.getFunctionMetadata().getSignature()).isEqualTo(expectedSignature); ParametricImplementationsGroup implementations = aggregation.getImplementations(); assertImplementationCount(implementations, 0, 1, 1); ParametricAggregationImplementation implementation1 = implementations.getSpecializedImplementations().get(0); - assertTrue(implementation1.hasSpecializedTypeParameters()); - assertFalse(implementation1.hasSpecializedTypeParameters()); - assertEquals(implementation1.getInputParameterKinds(), ImmutableList.of(STATE, INPUT_CHANNEL)); + assertThat(implementation1.hasSpecializedTypeParameters()).isTrue(); + assertThat(implementation1.hasSpecializedTypeParameters()).isFalse(); + assertThat(implementation1.getInputParameterKinds()).isEqualTo(ImmutableList.of(STATE, INPUT_CHANNEL)); ParametricAggregationImplementation implementation2 = implementations.getSpecializedImplementations().get(1); - assertTrue(implementation2.hasSpecializedTypeParameters()); - assertFalse(implementation2.hasSpecializedTypeParameters()); - assertEquals(implementation2.getInputParameterKinds(), ImmutableList.of(STATE, INPUT_CHANNEL)); + assertThat(implementation2.hasSpecializedTypeParameters()).isTrue(); + assertThat(implementation2.hasSpecializedTypeParameters()).isFalse(); + assertThat(implementation2.getInputParameterKinds()).isEqualTo(ImmutableList.of(STATE, INPUT_CHANNEL)); BoundSignature boundSignature = builtinFunction(aggregation.getFunctionMetadata().getCanonicalName(), DoubleType.DOUBLE, ImmutableList.of(new ArrayType(DoubleType.DOUBLE))); AggregationFunctionMetadata aggregationMetadata = aggregation.getAggregationMetadata(); - assertFalse(aggregationMetadata.isOrderSensitive()); - assertFalse(aggregationMetadata.getIntermediateTypes().isEmpty()); + assertThat(aggregationMetadata.isOrderSensitive()).isFalse(); + assertThat(aggregationMetadata.getIntermediateTypes().isEmpty()).isFalse(); aggregation.specialize(boundSignature, NO_FUNCTION_DEPENDENCIES); } @@ -672,15 +669,15 @@ public void testMultiOutputAggregationParse() .build(); List aggregations = parseFunctionDefinitions(MultiOutputAggregationFunction.class); - assertEquals(aggregations.size(), 2); + assertThat(aggregations.size()).isEqualTo(2); ParametricAggregation aggregation1 = aggregations.stream().filter(aggregate -> aggregate.getFunctionMetadata().getCanonicalName().equals("multi_output_aggregate_1")).collect(toImmutableList()).get(0); - assertEquals(aggregation1.getFunctionMetadata().getSignature(), expectedSignature1); - assertEquals(aggregation1.getFunctionMetadata().getDescription(), "Simple multi output function aggregate specialized description"); + assertThat(aggregation1.getFunctionMetadata().getSignature()).isEqualTo(expectedSignature1); + assertThat(aggregation1.getFunctionMetadata().getDescription()).isEqualTo("Simple multi output function aggregate specialized description"); ParametricAggregation aggregation2 = aggregations.stream().filter(aggregate -> aggregate.getFunctionMetadata().getCanonicalName().equals("multi_output_aggregate_2")).collect(toImmutableList()).get(0); - assertEquals(aggregation2.getFunctionMetadata().getSignature(), expectedSignature2); - assertEquals(aggregation2.getFunctionMetadata().getDescription(), "Simple multi output function aggregate generic description"); + assertThat(aggregation2.getFunctionMetadata().getSignature()).isEqualTo(expectedSignature2); + assertThat(aggregation2.getFunctionMetadata().getDescription()).isEqualTo("Simple multi output function aggregate generic description"); ParametricImplementationsGroup implementations1 = aggregation1.getImplementations(); assertImplementationCount(implementations1, 1, 0, 0); @@ -689,15 +686,15 @@ public void testMultiOutputAggregationParse() assertImplementationCount(implementations2, 1, 0, 0); ParametricAggregationImplementation implementation = getOnlyElement(implementations1.getExactImplementations().values()); - assertEquals(implementation.getDefinitionClass(), MultiOutputAggregationFunction.class); + assertThat(implementation.getDefinitionClass()).isEqualTo(MultiOutputAggregationFunction.class); assertDependencyCount(implementation, 0, 0, 0); - assertFalse(implementation.hasSpecializedTypeParameters()); - assertEquals(implementation.getInputParameterKinds(), ImmutableList.of(STATE, INPUT_CHANNEL)); + assertThat(implementation.hasSpecializedTypeParameters()).isFalse(); + assertThat(implementation.getInputParameterKinds()).isEqualTo(ImmutableList.of(STATE, INPUT_CHANNEL)); BoundSignature boundSignature = builtinFunction(aggregation1.getFunctionMetadata().getCanonicalName(), DoubleType.DOUBLE, ImmutableList.of(DoubleType.DOUBLE)); AggregationFunctionMetadata aggregationMetadata = aggregation1.getAggregationMetadata(); - assertFalse(aggregationMetadata.isOrderSensitive()); - assertFalse(aggregationMetadata.getIntermediateTypes().isEmpty()); + assertThat(aggregationMetadata.isOrderSensitive()).isFalse(); + assertThat(aggregationMetadata.getIntermediateTypes().isEmpty()).isFalse(); aggregation1.specialize(boundSignature, NO_FUNCTION_DEPENDENCIES); } @@ -754,21 +751,21 @@ public void testInjectOperatorAggregateParse() .build(); ParametricAggregation aggregation = getOnlyElement(parseFunctionDefinitions(InjectOperatorAggregateFunction.class)); - assertEquals(aggregation.getFunctionMetadata().getDescription(), "Simple aggregate with operator injected"); - assertTrue(aggregation.getFunctionMetadata().isDeterministic()); - assertEquals(aggregation.getFunctionMetadata().getSignature(), expectedSignature); + assertThat(aggregation.getFunctionMetadata().getDescription()).isEqualTo("Simple aggregate with operator injected"); + assertThat(aggregation.getFunctionMetadata().isDeterministic()).isTrue(); + assertThat(aggregation.getFunctionMetadata().getSignature()).isEqualTo(expectedSignature); ParametricImplementationsGroup implementations = aggregation.getImplementations(); ParametricAggregationImplementation implementation = getOnlyElement(implementations.getExactImplementations().values()); - assertEquals(implementation.getDefinitionClass(), InjectOperatorAggregateFunction.class); + assertThat(implementation.getDefinitionClass()).isEqualTo(InjectOperatorAggregateFunction.class); assertDependencyCount(implementation, 1, 1, 1); - assertTrue(implementation.getInputDependencies().get(0) instanceof OperatorImplementationDependency); - assertTrue(implementation.getCombineDependencies().get(0) instanceof OperatorImplementationDependency); - assertTrue(implementation.getOutputDependencies().get(0) instanceof OperatorImplementationDependency); + assertThat(implementation.getInputDependencies().get(0) instanceof OperatorImplementationDependency).isTrue(); + assertThat(implementation.getCombineDependencies().get(0) instanceof OperatorImplementationDependency).isTrue(); + assertThat(implementation.getOutputDependencies().get(0) instanceof OperatorImplementationDependency).isTrue(); - assertFalse(implementation.hasSpecializedTypeParameters()); - assertEquals(implementation.getInputParameterKinds(), ImmutableList.of(STATE, INPUT_CHANNEL)); + assertThat(implementation.hasSpecializedTypeParameters()).isFalse(); + assertThat(implementation.getInputParameterKinds()).isEqualTo(ImmutableList.of(STATE, INPUT_CHANNEL)); BoundSignature boundSignature = builtinFunction(aggregation.getFunctionMetadata().getCanonicalName(), DoubleType.DOUBLE, ImmutableList.of(DoubleType.DOUBLE)); specializeAggregationFunction(boundSignature, aggregation); @@ -817,22 +814,22 @@ public void testInjectTypeAggregateParse() .build(); ParametricAggregation aggregation = getOnlyElement(parseFunctionDefinitions(InjectTypeAggregateFunction.class)); - assertEquals(aggregation.getFunctionMetadata().getDescription(), "Simple aggregate with type injected"); - assertTrue(aggregation.getFunctionMetadata().isDeterministic()); - assertEquals(aggregation.getFunctionMetadata().getSignature(), expectedSignature); + assertThat(aggregation.getFunctionMetadata().getDescription()).isEqualTo("Simple aggregate with type injected"); + assertThat(aggregation.getFunctionMetadata().isDeterministic()).isTrue(); + assertThat(aggregation.getFunctionMetadata().getSignature()).isEqualTo(expectedSignature); ParametricImplementationsGroup implementations = aggregation.getImplementations(); - assertEquals(implementations.getGenericImplementations().size(), 1); + assertThat(implementations.getGenericImplementations().size()).isEqualTo(1); ParametricAggregationImplementation implementation = implementations.getGenericImplementations().get(0); - assertEquals(implementation.getDefinitionClass(), InjectTypeAggregateFunction.class); + assertThat(implementation.getDefinitionClass()).isEqualTo(InjectTypeAggregateFunction.class); assertDependencyCount(implementation, 1, 1, 1); - assertTrue(implementation.getInputDependencies().get(0) instanceof TypeImplementationDependency); - assertTrue(implementation.getCombineDependencies().get(0) instanceof TypeImplementationDependency); - assertTrue(implementation.getOutputDependencies().get(0) instanceof TypeImplementationDependency); + assertThat(implementation.getInputDependencies().get(0) instanceof TypeImplementationDependency).isTrue(); + assertThat(implementation.getCombineDependencies().get(0) instanceof TypeImplementationDependency).isTrue(); + assertThat(implementation.getOutputDependencies().get(0) instanceof TypeImplementationDependency).isTrue(); - assertFalse(implementation.hasSpecializedTypeParameters()); - assertEquals(implementation.getInputParameterKinds(), ImmutableList.of(STATE, INPUT_CHANNEL)); + assertThat(implementation.hasSpecializedTypeParameters()).isFalse(); + assertThat(implementation.getInputParameterKinds()).isEqualTo(ImmutableList.of(STATE, INPUT_CHANNEL)); BoundSignature boundSignature = builtinFunction(aggregation.getFunctionMetadata().getCanonicalName(), DoubleType.DOUBLE, ImmutableList.of(DoubleType.DOUBLE)); specializeAggregationFunction(boundSignature, aggregation); @@ -880,27 +877,27 @@ public void testInjectLiteralAggregateParse() .build(); ParametricAggregation aggregation = getOnlyElement(parseFunctionDefinitions(InjectLiteralAggregateFunction.class)); - assertEquals(aggregation.getFunctionMetadata().getDescription(), "Simple aggregate with type literal"); - assertTrue(aggregation.getFunctionMetadata().isDeterministic()); - assertEquals(aggregation.getFunctionMetadata().getSignature(), expectedSignature); + assertThat(aggregation.getFunctionMetadata().getDescription()).isEqualTo("Simple aggregate with type literal"); + assertThat(aggregation.getFunctionMetadata().isDeterministic()).isTrue(); + assertThat(aggregation.getFunctionMetadata().getSignature()).isEqualTo(expectedSignature); ParametricImplementationsGroup implementations = aggregation.getImplementations(); - assertEquals(implementations.getGenericImplementations().size(), 1); + assertThat(implementations.getGenericImplementations().size()).isEqualTo(1); ParametricAggregationImplementation implementation = implementations.getGenericImplementations().get(0); - assertEquals(implementation.getDefinitionClass(), InjectLiteralAggregateFunction.class); + assertThat(implementation.getDefinitionClass()).isEqualTo(InjectLiteralAggregateFunction.class); assertDependencyCount(implementation, 1, 1, 1); - assertTrue(implementation.getInputDependencies().get(0) instanceof LiteralImplementationDependency); - assertTrue(implementation.getCombineDependencies().get(0) instanceof LiteralImplementationDependency); - assertTrue(implementation.getOutputDependencies().get(0) instanceof LiteralImplementationDependency); + assertThat(implementation.getInputDependencies().get(0) instanceof LiteralImplementationDependency).isTrue(); + assertThat(implementation.getCombineDependencies().get(0) instanceof LiteralImplementationDependency).isTrue(); + assertThat(implementation.getOutputDependencies().get(0) instanceof LiteralImplementationDependency).isTrue(); - assertFalse(implementation.hasSpecializedTypeParameters()); - assertEquals(implementation.getInputParameterKinds(), ImmutableList.of(STATE, INPUT_CHANNEL)); + assertThat(implementation.hasSpecializedTypeParameters()).isFalse(); + assertThat(implementation.getInputParameterKinds()).isEqualTo(ImmutableList.of(STATE, INPUT_CHANNEL)); BoundSignature boundSignature = builtinFunction(aggregation.getFunctionMetadata().getCanonicalName(), createVarcharType(17), ImmutableList.of(createVarcharType(17))); AggregationFunctionMetadata aggregationMetadata = aggregation.getAggregationMetadata(); - assertFalse(aggregationMetadata.isOrderSensitive()); - assertFalse(aggregationMetadata.getIntermediateTypes().isEmpty()); + assertThat(aggregationMetadata.isOrderSensitive()).isFalse(); + assertThat(aggregationMetadata.getIntermediateTypes().isEmpty()).isFalse(); aggregation.specialize(boundSignature, NO_FUNCTION_DEPENDENCIES); } @@ -947,23 +944,23 @@ public void testLongConstraintAggregateFunctionParse() .build(); ParametricAggregation aggregation = getOnlyElement(parseFunctionDefinitions(LongConstraintAggregateFunction.class)); - assertEquals(aggregation.getFunctionMetadata().getDescription(), "Parametric aggregate with parametric type returned"); - assertTrue(aggregation.getFunctionMetadata().isDeterministic()); - assertEquals(aggregation.getFunctionMetadata().getSignature(), expectedSignature); + assertThat(aggregation.getFunctionMetadata().getDescription()).isEqualTo("Parametric aggregate with parametric type returned"); + assertThat(aggregation.getFunctionMetadata().isDeterministic()).isTrue(); + assertThat(aggregation.getFunctionMetadata().getSignature()).isEqualTo(expectedSignature); ParametricImplementationsGroup implementations = aggregation.getImplementations(); - assertEquals(implementations.getGenericImplementations().size(), 1); + assertThat(implementations.getGenericImplementations().size()).isEqualTo(1); ParametricAggregationImplementation implementation = implementations.getGenericImplementations().get(0); - assertEquals(implementation.getDefinitionClass(), LongConstraintAggregateFunction.class); + assertThat(implementation.getDefinitionClass()).isEqualTo(LongConstraintAggregateFunction.class); assertDependencyCount(implementation, 0, 0, 0); - assertFalse(implementation.hasSpecializedTypeParameters()); - assertEquals(implementation.getInputParameterKinds(), ImmutableList.of(STATE, INPUT_CHANNEL, INPUT_CHANNEL)); + assertThat(implementation.hasSpecializedTypeParameters()).isFalse(); + assertThat(implementation.getInputParameterKinds()).isEqualTo(ImmutableList.of(STATE, INPUT_CHANNEL, INPUT_CHANNEL)); BoundSignature boundSignature = builtinFunction(aggregation.getFunctionMetadata().getCanonicalName(), createVarcharType(30), ImmutableList.of(createVarcharType(17), createVarcharType(13))); AggregationFunctionMetadata aggregationMetadata = aggregation.getAggregationMetadata(); - assertFalse(aggregationMetadata.isOrderSensitive()); - assertFalse(aggregationMetadata.getIntermediateTypes().isEmpty()); + assertThat(aggregationMetadata.isOrderSensitive()).isFalse(); + assertThat(aggregationMetadata.getIntermediateTypes().isEmpty()).isFalse(); aggregation.specialize(boundSignature, NO_FUNCTION_DEPENDENCIES); } @@ -1008,17 +1005,17 @@ public void testFixedTypeParameterInjectionAggregateFunctionParse() .build(); ParametricAggregation aggregation = getOnlyElement(parseFunctionDefinitions(FixedTypeParameterInjectionAggregateFunction.class)); - assertEquals(aggregation.getFunctionMetadata().getDescription(), "Simple aggregate with fixed parameter type injected"); - assertTrue(aggregation.getFunctionMetadata().isDeterministic()); - assertEquals(aggregation.getFunctionMetadata().getSignature(), expectedSignature); - assertEquals(aggregation.getStateDetails(), ImmutableList.of(toAccumulatorStateDetails(NullableDoubleState.class, ImmutableList.of()))); + assertThat(aggregation.getFunctionMetadata().getDescription()).isEqualTo("Simple aggregate with fixed parameter type injected"); + assertThat(aggregation.getFunctionMetadata().isDeterministic()).isTrue(); + assertThat(aggregation.getFunctionMetadata().getSignature()).isEqualTo(expectedSignature); + assertThat(aggregation.getStateDetails()).isEqualTo(ImmutableList.of(toAccumulatorStateDetails(NullableDoubleState.class, ImmutableList.of()))); ParametricImplementationsGroup implementations = aggregation.getImplementations(); assertImplementationCount(implementations, 1, 0, 0); ParametricAggregationImplementation implementationDouble = implementations.getExactImplementations().get(expectedSignature); - assertEquals(implementationDouble.getDefinitionClass(), FixedTypeParameterInjectionAggregateFunction.class); + assertThat(implementationDouble.getDefinitionClass()).isEqualTo(FixedTypeParameterInjectionAggregateFunction.class); assertDependencyCount(implementationDouble, 1, 1, 1); - assertFalse(implementationDouble.hasSpecializedTypeParameters()); - assertEquals(implementationDouble.getInputParameterKinds(), ImmutableList.of(STATE, INPUT_CHANNEL)); + assertThat(implementationDouble.hasSpecializedTypeParameters()).isFalse(); + assertThat(implementationDouble.getInputParameterKinds()).isEqualTo(ImmutableList.of(STATE, INPUT_CHANNEL)); } @AggregationFunction("partially_fixed_type_parameter_injection") @@ -1071,17 +1068,17 @@ public void testPartiallyFixedTypeParameterInjectionAggregateFunctionParse() .build(); ParametricAggregation aggregation = getOnlyElement(parseFunctionDefinitions(PartiallyFixedTypeParameterInjectionAggregateFunction.class)); - assertEquals(aggregation.getFunctionMetadata().getDescription(), "Simple aggregate with fixed parameter type injected"); - assertTrue(aggregation.getFunctionMetadata().isDeterministic()); - assertEquals(aggregation.getFunctionMetadata().getSignature(), expectedSignature); - assertEquals(aggregation.getStateDetails(), ImmutableList.of(toAccumulatorStateDetails(NullableDoubleState.class, ImmutableList.of()))); + assertThat(aggregation.getFunctionMetadata().getDescription()).isEqualTo("Simple aggregate with fixed parameter type injected"); + assertThat(aggregation.getFunctionMetadata().isDeterministic()).isTrue(); + assertThat(aggregation.getFunctionMetadata().getSignature()).isEqualTo(expectedSignature); + assertThat(aggregation.getStateDetails()).isEqualTo(ImmutableList.of(toAccumulatorStateDetails(NullableDoubleState.class, ImmutableList.of()))); ParametricImplementationsGroup implementations = aggregation.getImplementations(); assertImplementationCount(implementations, 0, 0, 1); ParametricAggregationImplementation implementationDouble = getOnlyElement(implementations.getGenericImplementations()); - assertEquals(implementationDouble.getDefinitionClass(), PartiallyFixedTypeParameterInjectionAggregateFunction.class); + assertThat(implementationDouble.getDefinitionClass()).isEqualTo(PartiallyFixedTypeParameterInjectionAggregateFunction.class); assertDependencyCount(implementationDouble, 1, 1, 1); - assertFalse(implementationDouble.hasSpecializedTypeParameters()); - assertEquals(implementationDouble.getInputParameterKinds(), ImmutableList.of(STATE, INPUT_CHANNEL, INPUT_CHANNEL)); + assertThat(implementationDouble.hasSpecializedTypeParameters()).isFalse(); + assertThat(implementationDouble.getInputParameterKinds()).isEqualTo(ImmutableList.of(STATE, INPUT_CHANNEL, INPUT_CHANNEL)); BoundSignature boundSignature = builtinFunction(aggregation.getFunctionMetadata().getCanonicalName(), DoubleType.DOUBLE, ImmutableList.of(DoubleType.DOUBLE, DoubleType.DOUBLE)); specializeAggregationFunction(boundSignature, aggregation); @@ -1138,13 +1135,13 @@ public static void output(@AggregationState TriStateBooleanState state, BlockBui public void testAggregateFunctionGetCanonicalName() { List aggregationFunctions = parseFunctionDefinitions(AggregationOutputFunctionWithAlias.class); - assertEquals(aggregationFunctions.size(), 1); + assertThat(aggregationFunctions.size()).isEqualTo(1); ParametricAggregation aggregation = getOnlyElement(aggregationFunctions); assertThat(aggregation.getFunctionMetadata().getCanonicalName()).isEqualTo("aggregation_output"); assertThat(aggregation.getFunctionMetadata().getNames()).containsExactlyInAnyOrder("aggregation_output", "aggregation_output_alias_1", "aggregation_output_alias_2"); aggregationFunctions = parseFunctionDefinitions(AggregationFunctionWithAlias.class); - assertEquals(aggregationFunctions.size(), 1); + assertThat(aggregationFunctions.size()).isEqualTo(1); aggregation = getOnlyElement(aggregationFunctions); assertThat(aggregation.getFunctionMetadata().getCanonicalName()).isEqualTo("aggregation"); assertThat(aggregation.getFunctionMetadata().getNames()).containsExactlyInAnyOrder("aggregation", "aggregation_alias_1", "aggregation_alias_2"); @@ -1156,8 +1153,8 @@ private static void specializeAggregationFunction(BoundSignature boundSignature, FunctionBinding functionBinding = MetadataManager.toFunctionBinding(functionMetadata.getFunctionId(), boundSignature, functionMetadata.getSignature()); AggregationFunctionMetadata aggregationMetadata = aggregation.getAggregationMetadata(); - assertFalse(aggregationMetadata.isOrderSensitive()); - assertFalse(aggregationMetadata.getIntermediateTypes().isEmpty()); + assertThat(aggregationMetadata.isOrderSensitive()).isFalse(); + assertThat(aggregationMetadata.getIntermediateTypes().isEmpty()).isFalse(); FunctionDependencyDeclaration dependencyDeclaration = aggregation.getFunctionDependencies(boundSignature); ImmutableMap.Builder typeDependencies = ImmutableMap.builder(); diff --git a/core/trino-main/src/test/java/io/trino/operator/TestAnnotationEngineForScalars.java b/core/trino-main/src/test/java/io/trino/operator/TestAnnotationEngineForScalars.java index 78412c918b1e..28f5dbff6f2e 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestAnnotationEngineForScalars.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestAnnotationEngineForScalars.java @@ -57,9 +57,7 @@ import static io.trino.spi.type.DoubleType.DOUBLE; import static io.trino.spi.type.TypeSignature.arrayType; import static io.trino.spi.type.VarcharType.createVarcharType; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestAnnotationEngineForScalars { @@ -85,15 +83,15 @@ public void testSingleImplementationScalarParse() .build(); List functions = ScalarFromAnnotationsParser.parseFunctionDefinition(SingleImplementationScalarFunction.class); - assertEquals(functions.size(), 1); + assertThat(functions.size()).isEqualTo(1); ParametricScalar scalar = (ParametricScalar) functions.get(0); FunctionMetadata functionMetadata = scalar.getFunctionMetadata(); - assertEquals(functionMetadata.getSignature(), expectedSignature); - assertTrue(functionMetadata.isDeterministic()); - assertFalse(functionMetadata.isHidden()); - assertEquals(functionMetadata.getDescription(), "Simple scalar with single implementation based on class"); - assertFalse(functionMetadata.getFunctionNullability().isArgumentNullable(0)); + assertThat(functionMetadata.getSignature()).isEqualTo(expectedSignature); + assertThat(functionMetadata.isDeterministic()).isTrue(); + assertThat(functionMetadata.isHidden()).isFalse(); + assertThat(functionMetadata.getDescription()).isEqualTo("Simple scalar with single implementation based on class"); + assertThat(functionMetadata.getFunctionNullability().isArgumentNullable(0)).isFalse(); assertImplementationCount(scalar, 1, 0, 0); @@ -101,7 +99,7 @@ public void testSingleImplementationScalarParse() ChoicesSpecializedSqlScalarFunction specialized = (ChoicesSpecializedSqlScalarFunction) scalar.specialize( boundSignature, new InternalFunctionDependencies(FUNCTION_MANAGER::getScalarFunctionImplementation, ImmutableMap.of(), ImmutableSet.of())); - assertFalse(specialized.getChoices().get(0).getInstanceFactory().isPresent()); + assertThat(specialized.getChoices().get(0).getInstanceFactory().isPresent()).isFalse(); } @ScalarFunction(value = "hidden_scalar_function", hidden = true) @@ -119,12 +117,12 @@ public static double fun(@SqlType(StandardTypes.DOUBLE) double v) public void testHiddenScalarParse() { List functions = ScalarFromAnnotationsParser.parseFunctionDefinition(HiddenScalarFunction.class); - assertEquals(functions.size(), 1); + assertThat(functions.size()).isEqualTo(1); ParametricScalar scalar = (ParametricScalar) functions.get(0); FunctionMetadata functionMetadata = scalar.getFunctionMetadata(); - assertTrue(functionMetadata.isDeterministic()); - assertTrue(functionMetadata.isHidden()); + assertThat(functionMetadata.isDeterministic()).isTrue(); + assertThat(functionMetadata.isHidden()).isTrue(); } @ScalarFunction(value = "non_deterministic_scalar_function", deterministic = false) @@ -142,12 +140,12 @@ public static double fun(@SqlType(StandardTypes.DOUBLE) double v) public void testNonDeterministicScalarParse() { List functions = ScalarFromAnnotationsParser.parseFunctionDefinition(NonDeterministicScalarFunction.class); - assertEquals(functions.size(), 1); + assertThat(functions.size()).isEqualTo(1); ParametricScalar scalar = (ParametricScalar) functions.get(0); FunctionMetadata functionMetadata = scalar.getFunctionMetadata(); - assertFalse(functionMetadata.isDeterministic()); - assertFalse(functionMetadata.isHidden()); + assertThat(functionMetadata.isDeterministic()).isFalse(); + assertThat(functionMetadata.isHidden()).isFalse(); } @ScalarFunction("scalar_with_nullable") @@ -174,22 +172,22 @@ public void testWithNullablePrimitiveArgScalarParse() .build(); List functions = ScalarFromAnnotationsParser.parseFunctionDefinition(WithNullablePrimitiveArgScalarFunction.class); - assertEquals(functions.size(), 1); + assertThat(functions.size()).isEqualTo(1); ParametricScalar scalar = (ParametricScalar) functions.get(0); FunctionMetadata functionMetadata = scalar.getFunctionMetadata(); - assertEquals(functionMetadata.getSignature(), expectedSignature); - assertTrue(functionMetadata.isDeterministic()); - assertFalse(functionMetadata.isHidden()); - assertEquals(functionMetadata.getDescription(), "Simple scalar with nullable primitive"); - assertFalse(functionMetadata.getFunctionNullability().isArgumentNullable(0)); - assertTrue(functionMetadata.getFunctionNullability().isArgumentNullable(1)); + assertThat(functionMetadata.getSignature()).isEqualTo(expectedSignature); + assertThat(functionMetadata.isDeterministic()).isTrue(); + assertThat(functionMetadata.isHidden()).isFalse(); + assertThat(functionMetadata.getDescription()).isEqualTo("Simple scalar with nullable primitive"); + assertThat(functionMetadata.getFunctionNullability().isArgumentNullable(0)).isFalse(); + assertThat(functionMetadata.getFunctionNullability().isArgumentNullable(1)).isTrue(); BoundSignature boundSignature = new BoundSignature(builtinFunctionName("scalar_with_nullable"), DOUBLE, ImmutableList.of(DOUBLE, DOUBLE)); ChoicesSpecializedSqlScalarFunction specialized = (ChoicesSpecializedSqlScalarFunction) scalar.specialize( boundSignature, new InternalFunctionDependencies(FUNCTION_MANAGER::getScalarFunctionImplementation, ImmutableMap.of(), ImmutableSet.of())); - assertFalse(specialized.getChoices().get(0).getInstanceFactory().isPresent()); + assertThat(specialized.getChoices().get(0).getInstanceFactory().isPresent()).isFalse(); } @ScalarFunction("scalar_with_nullable_complex") @@ -215,22 +213,22 @@ public void testWithNullableComplexArgScalarParse() .build(); List functions = ScalarFromAnnotationsParser.parseFunctionDefinition(WithNullableComplexArgScalarFunction.class); - assertEquals(functions.size(), 1); + assertThat(functions.size()).isEqualTo(1); ParametricScalar scalar = (ParametricScalar) functions.get(0); FunctionMetadata functionMetadata = scalar.getFunctionMetadata(); - assertEquals(functionMetadata.getSignature(), expectedSignature); - assertTrue(functionMetadata.isDeterministic()); - assertFalse(functionMetadata.isHidden()); - assertEquals(functionMetadata.getDescription(), "Simple scalar with nullable complex type"); - assertFalse(functionMetadata.getFunctionNullability().isArgumentNullable(0)); - assertTrue(functionMetadata.getFunctionNullability().isArgumentNullable(1)); + assertThat(functionMetadata.getSignature()).isEqualTo(expectedSignature); + assertThat(functionMetadata.isDeterministic()).isTrue(); + assertThat(functionMetadata.isHidden()).isFalse(); + assertThat(functionMetadata.getDescription()).isEqualTo("Simple scalar with nullable complex type"); + assertThat(functionMetadata.getFunctionNullability().isArgumentNullable(0)).isFalse(); + assertThat(functionMetadata.getFunctionNullability().isArgumentNullable(1)).isTrue(); BoundSignature boundSignature = new BoundSignature(builtinFunctionName("scalar_with_nullable_complex"), DOUBLE, ImmutableList.of(DOUBLE, DOUBLE)); ChoicesSpecializedSqlScalarFunction specialized = (ChoicesSpecializedSqlScalarFunction) scalar.specialize( boundSignature, new InternalFunctionDependencies(FUNCTION_MANAGER::getScalarFunctionImplementation, ImmutableMap.of(), ImmutableSet.of())); - assertFalse(specialized.getChoices().get(0).getInstanceFactory().isPresent()); + assertThat(specialized.getChoices().get(0).getInstanceFactory().isPresent()).isFalse(); } public static final class StaticMethodScalarFunction @@ -253,14 +251,14 @@ public void testStaticMethodScalarParse() .build(); List functions = ScalarFromAnnotationsParser.parseFunctionDefinitions(StaticMethodScalarFunction.class); - assertEquals(functions.size(), 1); + assertThat(functions.size()).isEqualTo(1); ParametricScalar scalar = (ParametricScalar) functions.get(0); FunctionMetadata functionMetadata = scalar.getFunctionMetadata(); - assertEquals(functionMetadata.getSignature(), expectedSignature); - assertTrue(functionMetadata.isDeterministic()); - assertFalse(functionMetadata.isHidden()); - assertEquals(functionMetadata.getDescription(), "Simple scalar with single implementation based on method"); + assertThat(functionMetadata.getSignature()).isEqualTo(expectedSignature); + assertThat(functionMetadata.isDeterministic()).isTrue(); + assertThat(functionMetadata.isHidden()).isFalse(); + assertThat(functionMetadata.getDescription()).isEqualTo("Simple scalar with single implementation based on method"); } public static final class MultiScalarFunction @@ -296,7 +294,7 @@ public void testMultiScalarParse() .build(); List functions = ScalarFromAnnotationsParser.parseFunctionDefinitions(MultiScalarFunction.class); - assertEquals(functions.size(), 2); + assertThat(functions.size()).isEqualTo(2); ParametricScalar scalar1 = (ParametricScalar) functions.stream().filter(function -> function.getFunctionMetadata().getSignature().equals(expectedSignature1)).collect(toImmutableList()).get(0); ParametricScalar scalar2 = (ParametricScalar) functions.stream().filter(function -> function.getFunctionMetadata().getSignature().equals(expectedSignature2)).collect(toImmutableList()).get(0); @@ -304,16 +302,16 @@ public void testMultiScalarParse() assertImplementationCount(scalar2, 1, 0, 0); FunctionMetadata functionMetadata1 = scalar1.getFunctionMetadata(); - assertEquals(functionMetadata1.getSignature(), expectedSignature1); - assertTrue(functionMetadata1.isDeterministic()); - assertFalse(functionMetadata1.isHidden()); - assertEquals(functionMetadata1.getDescription(), "Simple scalar with single implementation based on method 1"); + assertThat(functionMetadata1.getSignature()).isEqualTo(expectedSignature1); + assertThat(functionMetadata1.isDeterministic()).isTrue(); + assertThat(functionMetadata1.isHidden()).isFalse(); + assertThat(functionMetadata1.getDescription()).isEqualTo("Simple scalar with single implementation based on method 1"); FunctionMetadata functionMetadata2 = scalar2.getFunctionMetadata(); - assertEquals(functionMetadata2.getSignature(), expectedSignature2); - assertFalse(functionMetadata2.isDeterministic()); - assertTrue(functionMetadata2.isHidden()); - assertEquals(functionMetadata2.getDescription(), "Simple scalar with single implementation based on method 2"); + assertThat(functionMetadata2.getSignature()).isEqualTo(expectedSignature2); + assertThat(functionMetadata2.isDeterministic()).isFalse(); + assertThat(functionMetadata2.isHidden()).isTrue(); + assertThat(functionMetadata2.getDescription()).isEqualTo("Simple scalar with single implementation based on method 2"); } @ScalarFunction("parametric_scalar") @@ -345,15 +343,15 @@ public void testParametricScalarParse() .build(); List functions = ScalarFromAnnotationsParser.parseFunctionDefinition(ParametricScalarFunction.class); - assertEquals(functions.size(), 1); + assertThat(functions.size()).isEqualTo(1); ParametricScalar scalar = (ParametricScalar) functions.get(0); assertImplementationCount(scalar, 0, 2, 0); FunctionMetadata functionMetadata = scalar.getFunctionMetadata(); - assertEquals(functionMetadata.getSignature(), expectedSignature); - assertTrue(functionMetadata.isDeterministic()); - assertFalse(functionMetadata.isHidden()); - assertEquals(functionMetadata.getDescription(), "Parametric scalar description"); + assertThat(functionMetadata.getSignature()).isEqualTo(expectedSignature); + assertThat(functionMetadata.isDeterministic()).isTrue(); + assertThat(functionMetadata.isHidden()).isFalse(); + assertThat(functionMetadata.getDescription()).isEqualTo("Parametric scalar description"); } @ScalarFunction("with_exact_scalar") @@ -388,16 +386,16 @@ public void testComplexParametricScalarParse() .build(); List functions = ScalarFromAnnotationsParser.parseFunctionDefinition(ComplexParametricScalarFunction.class); - assertEquals(functions.size(), 1); + assertThat(functions.size()).isEqualTo(1); ParametricScalar scalar = (ParametricScalar) functions.get(0); assertImplementationCount(scalar.getImplementations(), 1, 0, 1); - assertEquals(getOnlyElement(scalar.getImplementations().getExactImplementations().keySet()), exactSignature); + assertThat(getOnlyElement(scalar.getImplementations().getExactImplementations().keySet())).isEqualTo(exactSignature); FunctionMetadata functionMetadata = scalar.getFunctionMetadata(); - assertEquals(functionMetadata.getSignature(), expectedSignature); - assertTrue(functionMetadata.isDeterministic()); - assertFalse(functionMetadata.isHidden()); - assertEquals(functionMetadata.getDescription(), "Parametric scalar with exact and generic implementations"); + assertThat(functionMetadata.getSignature()).isEqualTo(expectedSignature); + assertThat(functionMetadata.isDeterministic()).isTrue(); + assertThat(functionMetadata.isHidden()).isFalse(); + assertThat(functionMetadata.getDescription()).isEqualTo("Parametric scalar with exact and generic implementations"); } @ScalarFunction("parametric_scalar_inject") @@ -423,20 +421,20 @@ public void testSimpleInjectionScalarParse() .build(); List functions = ScalarFromAnnotationsParser.parseFunctionDefinition(SimpleInjectionScalarFunction.class); - assertEquals(functions.size(), 1); + assertThat(functions.size()).isEqualTo(1); ParametricScalar scalar = (ParametricScalar) functions.get(0); assertImplementationCount(scalar, 0, 0, 1); List parametricScalarImplementationChoices = scalar.getImplementations().getGenericImplementations().get(0).getChoices(); - assertEquals(parametricScalarImplementationChoices.size(), 1); + assertThat(parametricScalarImplementationChoices.size()).isEqualTo(1); List dependencies = parametricScalarImplementationChoices.get(0).getDependencies(); - assertEquals(dependencies.size(), 1); - assertTrue(dependencies.get(0) instanceof LiteralImplementationDependency); + assertThat(dependencies.size()).isEqualTo(1); + assertThat(dependencies.get(0) instanceof LiteralImplementationDependency).isTrue(); FunctionMetadata functionMetadata = scalar.getFunctionMetadata(); - assertEquals(functionMetadata.getSignature(), expectedSignature); - assertTrue(functionMetadata.isDeterministic()); - assertFalse(functionMetadata.isHidden()); - assertEquals(functionMetadata.getDescription(), "Parametric scalar with literal injected"); + assertThat(functionMetadata.getSignature()).isEqualTo(expectedSignature); + assertThat(functionMetadata.isDeterministic()).isTrue(); + assertThat(functionMetadata.isHidden()).isFalse(); + assertThat(functionMetadata.getDescription()).isEqualTo("Parametric scalar with literal injected"); } @ScalarFunction("parametric_scalar_inject_constructor") @@ -476,22 +474,22 @@ public void testConstructorInjectionScalarParse() .build(); List functions = ScalarFromAnnotationsParser.parseFunctionDefinition(ConstructorInjectionScalarFunction.class); - assertEquals(functions.size(), 1); + assertThat(functions.size()).isEqualTo(1); ParametricScalar scalar = (ParametricScalar) functions.get(0); assertImplementationCount(scalar, 2, 0, 1); List parametricScalarImplementationChoices = scalar.getImplementations().getGenericImplementations().get(0).getChoices(); - assertEquals(parametricScalarImplementationChoices.size(), 1); + assertThat(parametricScalarImplementationChoices.size()).isEqualTo(1); List dependencies = parametricScalarImplementationChoices.get(0).getDependencies(); - assertEquals(dependencies.size(), 0); + assertThat(dependencies.size()).isEqualTo(0); List constructorDependencies = parametricScalarImplementationChoices.get(0).getConstructorDependencies(); - assertEquals(constructorDependencies.size(), 1); - assertTrue(constructorDependencies.get(0) instanceof TypeImplementationDependency); + assertThat(constructorDependencies.size()).isEqualTo(1); + assertThat(constructorDependencies.get(0) instanceof TypeImplementationDependency).isTrue(); FunctionMetadata functionMetadata = scalar.getFunctionMetadata(); - assertEquals(functionMetadata.getSignature(), expectedSignature); - assertTrue(functionMetadata.isDeterministic()); - assertFalse(functionMetadata.isHidden()); - assertEquals(functionMetadata.getDescription(), "Parametric scalar with type injected though constructor"); + assertThat(functionMetadata.getSignature()).isEqualTo(expectedSignature); + assertThat(functionMetadata.isDeterministic()).isTrue(); + assertThat(functionMetadata.isHidden()).isFalse(); + assertThat(functionMetadata.getDescription()).isEqualTo("Parametric scalar with type injected though constructor"); } @ScalarFunction("fixed_type_parameter_scalar_function") @@ -516,15 +514,15 @@ public void testFixedTypeParameterParse() .build(); List functions = ScalarFromAnnotationsParser.parseFunctionDefinition(FixedTypeParameterScalarFunction.class); - assertEquals(functions.size(), 1); + assertThat(functions.size()).isEqualTo(1); ParametricScalar scalar = (ParametricScalar) functions.get(0); assertImplementationCount(scalar, 1, 0, 0); FunctionMetadata functionMetadata = scalar.getFunctionMetadata(); - assertEquals(functionMetadata.getSignature(), expectedSignature); - assertTrue(functionMetadata.isDeterministic()); - assertFalse(functionMetadata.isHidden()); - assertEquals(functionMetadata.getDescription(), "Parametric scalar that uses TypeParameter with fixed type"); + assertThat(functionMetadata.getSignature()).isEqualTo(expectedSignature); + assertThat(functionMetadata.isDeterministic()).isTrue(); + assertThat(functionMetadata.isHidden()).isFalse(); + assertThat(functionMetadata.getDescription()).isEqualTo("Parametric scalar that uses TypeParameter with fixed type"); } @ScalarFunction("partially_fixed_type_parameter_scalar_function") @@ -553,18 +551,18 @@ public void testPartiallyFixedTypeParameterParse() .build(); List functions = ScalarFromAnnotationsParser.parseFunctionDefinition(PartiallyFixedTypeParameterScalarFunction.class); - assertEquals(functions.size(), 1); + assertThat(functions.size()).isEqualTo(1); ParametricScalar scalar = (ParametricScalar) functions.get(0); assertImplementationCount(scalar, 0, 0, 1); List parametricScalarImplementationChoices = scalar.getImplementations().getGenericImplementations().get(0).getChoices(); - assertEquals(parametricScalarImplementationChoices.size(), 1); + assertThat(parametricScalarImplementationChoices.size()).isEqualTo(1); List dependencies = parametricScalarImplementationChoices.get(0).getDependencies(); - assertEquals(dependencies.size(), 1); + assertThat(dependencies.size()).isEqualTo(1); FunctionMetadata functionMetadata = scalar.getFunctionMetadata(); - assertEquals(functionMetadata.getSignature(), expectedSignature); - assertTrue(functionMetadata.isDeterministic()); - assertFalse(functionMetadata.isHidden()); - assertEquals(functionMetadata.getDescription(), "Parametric scalar that uses TypeParameter with partially fixed type"); + assertThat(functionMetadata.getSignature()).isEqualTo(expectedSignature); + assertThat(functionMetadata.isDeterministic()).isTrue(); + assertThat(functionMetadata.isHidden()).isFalse(); + assertThat(functionMetadata.getDescription()).isEqualTo("Parametric scalar that uses TypeParameter with partially fixed type"); } } diff --git a/core/trino-main/src/test/java/io/trino/operator/TestCyclingGroupByHash.java b/core/trino-main/src/test/java/io/trino/operator/TestCyclingGroupByHash.java index aca37be1785c..63c803d9e1d5 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestCyclingGroupByHash.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestCyclingGroupByHash.java @@ -19,7 +19,7 @@ import org.junit.jupiter.api.Test; import static io.trino.spi.type.BigintType.BIGINT; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestCyclingGroupByHash { @@ -30,12 +30,12 @@ public void testSingleGroup() Page page = createPage(1); int[] groupByIds = computeGroupByIdBlock(groupByHash, page); assertGrouping(groupByIds, 0); - assertEquals(groupByHash.getGroupCount(), 1); + assertThat(groupByHash.getGroupCount()).isEqualTo(1); page = createPage(2); groupByIds = computeGroupByIdBlock(groupByHash, page); assertGrouping(groupByIds, 0, 0); - assertEquals(groupByHash.getGroupCount(), 1); + assertThat(groupByHash.getGroupCount()).isEqualTo(1); } @Test @@ -45,12 +45,12 @@ public void testMultipleGroup() Page page = createPage(3); int[] groupByIds = computeGroupByIdBlock(groupByHash, page); assertGrouping(groupByIds, 0, 1, 0); - assertEquals(groupByHash.getGroupCount(), 2); + assertThat(groupByHash.getGroupCount()).isEqualTo(2); page = createPage(2); groupByIds = computeGroupByIdBlock(groupByHash, page); assertGrouping(groupByIds, 1, 0); - assertEquals(groupByHash.getGroupCount(), 2); + assertThat(groupByHash.getGroupCount()).isEqualTo(2); } @Test @@ -62,12 +62,12 @@ public void testPartialGroup() assertGrouping(groupByIds, 0, 1); // Only 2 groups generated out of max 3 - assertEquals(groupByHash.getGroupCount(), 2); + assertThat(groupByHash.getGroupCount()).isEqualTo(2); } private static void assertGrouping(int[] groupIds, int... expectedGroupIds) { - assertEquals(groupIds, expectedGroupIds); + assertThat(groupIds).isEqualTo(expectedGroupIds); } private static int[] computeGroupByIdBlock(GroupByHash groupByHash, Page page) diff --git a/core/trino-main/src/test/java/io/trino/operator/TestDeduplicatingDirectExchangeBuffer.java b/core/trino-main/src/test/java/io/trino/operator/TestDeduplicatingDirectExchangeBuffer.java index 16e02e6dec2f..2ec80f350157 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestDeduplicatingDirectExchangeBuffer.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestDeduplicatingDirectExchangeBuffer.java @@ -52,11 +52,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -525,7 +520,7 @@ private void testPollPages( List expectedOutput) { List actualOutput = pollPages(retryPolicy, pages, failures, bufferCapacity, expectedSpilledPageCount); - assertEquals(actualOutput, expectedOutput); + assertThat(actualOutput).isEqualTo(expectedOutput); } private void testPollPagesFailure( @@ -575,8 +570,8 @@ private List pollPages( } result.add(page); } - assertTrue(buffer.isFinished()); - assertEquals(buffer.getSpilledPageCount(), expectedSpilledPageCount); + assertThat(buffer.isFinished()).isTrue(); + assertThat(buffer.getSpilledPageCount()).isEqualTo(expectedSpilledPageCount); return result.build(); } } @@ -585,7 +580,7 @@ private List pollPages( public void testRemovePagesForPreviousAttempts() { try (DirectExchangeBuffer buffer = createDeduplicatingDirectExchangeBuffer(DataSize.of(1, KILOBYTE), RetryPolicy.QUERY)) { - assertEquals(buffer.getRetainedSizeInBytes(), 0); + assertThat(buffer.getRetainedSizeInBytes()).isEqualTo(0); TaskId partition0Attempt0 = createTaskId(0, 0); TaskId partition1Attempt0 = createTaskId(1, 0); @@ -601,13 +596,13 @@ public void testRemovePagesForPreviousAttempts() buffer.addPages(partition1Attempt0, ImmutableList.of(page2)); assertThat(buffer.getRetainedSizeInBytes()).isGreaterThan(0); - assertEquals(buffer.getRetainedSizeInBytes(), page1.getRetainedSize() + page2.getRetainedSize()); + assertThat(buffer.getRetainedSizeInBytes()).isEqualTo(page1.getRetainedSize() + page2.getRetainedSize()); buffer.addTask(partition0Attempt1); - assertEquals(buffer.getRetainedSizeInBytes(), 0); + assertThat(buffer.getRetainedSizeInBytes()).isEqualTo(0); buffer.addPages(partition0Attempt1, ImmutableList.of(page3)); - assertEquals(buffer.getRetainedSizeInBytes(), page3.getRetainedSize()); + assertThat(buffer.getRetainedSizeInBytes()).isEqualTo(page3.getRetainedSize()); } } @@ -630,11 +625,11 @@ public void testExchangeManagerNotConfigured() buffer.taskFinished(task); buffer.noMoreTasks(); - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); assertNotBlocked(buffer.isBlocked()); - assertEquals(buffer.pollPage(), page); - assertNull(buffer.pollPage()); - assertTrue(buffer.isFinished()); + assertThat(buffer.pollPage()).isEqualTo(page); + assertThat(buffer.pollPage()).isNull(); + assertThat(buffer.isFinished()).isTrue(); } // overflow @@ -655,16 +650,16 @@ public void testExchangeManagerNotConfigured() buffer.addTask(task); buffer.addPages(task, ImmutableList.of(page1)); - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); assertBlocked(buffer.isBlocked()); - assertEquals(buffer.getRetainedSizeInBytes(), page1.getRetainedSize()); + assertThat(buffer.getRetainedSizeInBytes()).isEqualTo(page1.getRetainedSize()); buffer.addPages(task, ImmutableList.of(page2)); - assertFalse(buffer.isFinished()); - assertTrue(buffer.isFailed()); + assertThat(buffer.isFinished()).isFalse(); + assertThat(buffer.isFailed()).isTrue(); assertNotBlocked(buffer.isBlocked()); - assertEquals(buffer.getRetainedSizeInBytes(), 0); - assertEquals(buffer.getBufferedPageCount(), 0); + assertThat(buffer.getRetainedSizeInBytes()).isEqualTo(0); + assertThat(buffer.getBufferedPageCount()).isEqualTo(0); assertThatThrownBy(buffer::pollPage) .isInstanceOf(TrinoException.class); @@ -676,133 +671,133 @@ public void testIsFinished() { // close right away try (DirectExchangeBuffer buffer = createDeduplicatingDirectExchangeBuffer(DEFAULT_BUFFER_CAPACITY, RetryPolicy.QUERY)) { - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); buffer.close(); - assertTrue(buffer.isFinished()); + assertThat(buffer.isFinished()).isTrue(); } // 0 tasks try (DirectExchangeBuffer buffer = createDeduplicatingDirectExchangeBuffer(DEFAULT_BUFFER_CAPACITY, RetryPolicy.QUERY)) { - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); buffer.noMoreTasks(); - assertTrue(buffer.isFinished()); + assertThat(buffer.isFinished()).isTrue(); } // single task producing no results, finish before noMoreTasks try (DirectExchangeBuffer buffer = createDeduplicatingDirectExchangeBuffer(DEFAULT_BUFFER_CAPACITY, RetryPolicy.QUERY)) { - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); TaskId taskId = createTaskId(0, 0); buffer.addTask(taskId); - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); buffer.taskFinished(taskId); - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); buffer.noMoreTasks(); - assertTrue(buffer.isFinished()); + assertThat(buffer.isFinished()).isTrue(); } // single task producing no results, finish after noMoreTasks try (DirectExchangeBuffer buffer = createDeduplicatingDirectExchangeBuffer(DEFAULT_BUFFER_CAPACITY, RetryPolicy.QUERY)) { - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); TaskId taskId = createTaskId(0, 0); buffer.addTask(taskId); - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); buffer.noMoreTasks(); - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); buffer.taskFinished(taskId); - assertTrue(buffer.isFinished()); + assertThat(buffer.isFinished()).isTrue(); } // single task producing no results, fail before noMoreTasks try (DirectExchangeBuffer buffer = createDeduplicatingDirectExchangeBuffer(DEFAULT_BUFFER_CAPACITY, RetryPolicy.QUERY)) { - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); TaskId taskId = createTaskId(0, 0); buffer.addTask(taskId); - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); buffer.taskFailed(taskId, new RuntimeException()); - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); buffer.noMoreTasks(); - assertFalse(buffer.isFinished()); - assertTrue(buffer.isFailed()); + assertThat(buffer.isFinished()).isFalse(); + assertThat(buffer.isFailed()).isTrue(); } // single task producing no results, fail after noMoreTasks try (DirectExchangeBuffer buffer = createDeduplicatingDirectExchangeBuffer(DEFAULT_BUFFER_CAPACITY, RetryPolicy.QUERY)) { - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); TaskId taskId = createTaskId(0, 0); buffer.addTask(taskId); - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); buffer.noMoreTasks(); - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); buffer.taskFailed(taskId, new RuntimeException()); - assertFalse(buffer.isFinished()); - assertTrue(buffer.isFailed()); + assertThat(buffer.isFinished()).isFalse(); + assertThat(buffer.isFailed()).isTrue(); } // single task producing one page, fail after noMoreTasks try (DirectExchangeBuffer buffer = createDeduplicatingDirectExchangeBuffer(DEFAULT_BUFFER_CAPACITY, RetryPolicy.QUERY)) { - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); TaskId taskId = createTaskId(0, 0); buffer.addTask(taskId); buffer.addPages(taskId, ImmutableList.of(utf8Slice("page"))); - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); buffer.noMoreTasks(); - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); buffer.taskFailed(taskId, new RuntimeException()); - assertFalse(buffer.isFinished()); - assertTrue(buffer.isFailed()); + assertThat(buffer.isFinished()).isFalse(); + assertThat(buffer.isFailed()).isTrue(); } // single task producing one page, finish after noMoreTasks try (DirectExchangeBuffer buffer = createDeduplicatingDirectExchangeBuffer(DEFAULT_BUFFER_CAPACITY, RetryPolicy.QUERY)) { - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); TaskId taskId = createTaskId(0, 0); buffer.addTask(taskId); buffer.addPages(taskId, ImmutableList.of(utf8Slice("page"))); - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); buffer.noMoreTasks(); - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); buffer.taskFinished(taskId); - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); - assertNotNull(buffer.pollPage()); - assertTrue(buffer.isFinished()); + assertThat(buffer.pollPage()).isNotNull(); + assertThat(buffer.isFinished()).isTrue(); } // single task producing one page, finish before noMoreTasks try (DirectExchangeBuffer buffer = createDeduplicatingDirectExchangeBuffer(DEFAULT_BUFFER_CAPACITY, RetryPolicy.QUERY)) { - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); TaskId taskId = createTaskId(0, 0); buffer.addTask(taskId); buffer.addPages(taskId, ImmutableList.of(utf8Slice("page"))); - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); buffer.taskFinished(taskId); - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); buffer.noMoreTasks(); - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); - assertNotNull(buffer.pollPage()); - assertTrue(buffer.isFinished()); + assertThat(buffer.pollPage()).isNotNull(); + assertThat(buffer.isFinished()).isTrue(); } } @@ -810,14 +805,14 @@ public void testIsFinished() public void testRemainingBufferCapacity() { try (DirectExchangeBuffer buffer = createDeduplicatingDirectExchangeBuffer(DEFAULT_BUFFER_CAPACITY, RetryPolicy.QUERY)) { - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); TaskId taskId = createTaskId(0, 0); buffer.addTask(taskId); Slice page = utf8Slice("page"); buffer.addPages(taskId, ImmutableList.of(page)); - assertEquals(buffer.getRemainingCapacityInBytes(), Long.MAX_VALUE); + assertThat(buffer.getRemainingCapacityInBytes()).isEqualTo(Long.MAX_VALUE); } } @@ -837,10 +832,10 @@ private void testRemoteTaskFailedError(RetryPolicy retryPolicy) buffer.taskFailed(taskId, new TrinoException(REMOTE_TASK_FAILED, "Remote task failed")); buffer.noMoreTasks(); - assertFalse(buffer.isFinished()); - assertFalse(buffer.isFailed()); + assertThat(buffer.isFinished()).isFalse(); + assertThat(buffer.isFailed()).isFalse(); assertBlocked(buffer.isBlocked()); - assertNull(buffer.pollPage()); + assertThat(buffer.pollPage()).isNull(); } // fail after noMoreTasks @@ -850,10 +845,10 @@ private void testRemoteTaskFailedError(RetryPolicy retryPolicy) buffer.noMoreTasks(); buffer.taskFailed(taskId, new TrinoException(REMOTE_TASK_FAILED, "Remote task failed")); - assertFalse(buffer.isFinished()); - assertFalse(buffer.isFailed()); + assertThat(buffer.isFinished()).isFalse(); + assertThat(buffer.isFailed()).isFalse(); assertBlocked(buffer.isBlocked()); - assertNull(buffer.pollPage()); + assertThat(buffer.pollPage()).isNull(); } } @@ -885,11 +880,11 @@ private static Slice createPage(String value, DataSize size) private static void assertNotBlocked(ListenableFuture blocked) { - assertTrue(blocked.isDone()); + assertThat(blocked.isDone()).isTrue(); } private static void assertBlocked(ListenableFuture blocked) { - assertFalse(blocked.isDone()); + assertThat(blocked.isDone()).isFalse(); } } diff --git a/core/trino-main/src/test/java/io/trino/operator/TestDirectExchangeClient.java b/core/trino-main/src/test/java/io/trino/operator/TestDirectExchangeClient.java index 85db9d46f04f..9ad1370d6acf 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestDirectExchangeClient.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestDirectExchangeClient.java @@ -87,11 +87,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -157,33 +152,35 @@ public void testHappyPath() assertThat(buffer.getPages().asMap()).isEmpty(); assertThat(buffer.getFinishedTasks()).isEmpty(); assertThat(buffer.getFailedTasks().asMap()).isEmpty(); - assertFalse(buffer.isNoMoreTasks()); + assertThat(buffer.isNoMoreTasks()).isFalse(); TaskId taskId = new TaskId(new StageId("query", 1), 0, 0); exchangeClient.addLocation(taskId, location); assertThat(buffer.getAllTasks()).containsExactly(taskId); exchangeClient.noMoreLocations(); - assertTrue(buffer.isNoMoreTasks()); + assertThat(buffer.isNoMoreTasks()).isTrue(); buffer.whenTaskFinished(taskId).get(10, SECONDS); assertThat(buffer.getFinishedTasks()).containsExactly(taskId); assertThat(buffer.getPages().get(taskId)).hasSize(3); assertThat(buffer.getFailedTasks().asMap()).isEmpty(); - assertFalse(exchangeClient.isFinished()); + assertThat(exchangeClient.isFinished()).isFalse(); buffer.setFinished(true); - assertTrue(exchangeClient.isFinished()); + assertThat(exchangeClient.isFinished()).isTrue(); DirectExchangeClientStatus status = exchangeClient.getStatus(); - assertEquals(status.getBufferedPages(), 0); + assertThat(status.getBufferedPages()).isEqualTo(0); // client should have sent only 3 requests: one to get all pages, one to acknowledge and one to get the done signal assertStatus(status.getPageBufferClientStatuses().get(0), location, "closed", 3, 3, 3, "not scheduled"); - assertEquals(status.getRequestDuration().getDigest().getCount(), 2.0); + assertThat(status.getRequestDuration().getDigest().getCount()).isEqualTo(2.0); exchangeClient.close(); - assertEventually(() -> assertEquals(exchangeClient.getStatus().getPageBufferClientStatuses().get(0).getHttpRequestState(), "not scheduled", "httpRequestState")); + assertEventually(() -> assertThat(exchangeClient.getStatus().getPageBufferClientStatuses().get(0).getHttpRequestState()) + .describedAs("httpRequestState") + .isEqualTo("not scheduled")); assertThat(buffer.getFinishedTasks()).containsExactly(taskId); assertThat(buffer.getFailedTasks().asMap()).isEmpty(); @@ -220,17 +217,17 @@ public void testStreamingHappyPath() exchangeClient.addLocation(new TaskId(new StageId("query", 1), 0, 0), location); exchangeClient.noMoreLocations(); - assertFalse(exchangeClient.isFinished()); + assertThat(exchangeClient.isFinished()).isFalse(); assertPageEquals(getNextPage(exchangeClient), createPage(1)); - assertFalse(exchangeClient.isFinished()); + assertThat(exchangeClient.isFinished()).isFalse(); assertPageEquals(getNextPage(exchangeClient), createPage(2)); - assertFalse(exchangeClient.isFinished()); + assertThat(exchangeClient.isFinished()).isFalse(); assertPageEquals(getNextPage(exchangeClient), createPage(3)); - assertNull(getNextPage(exchangeClient)); - assertTrue(exchangeClient.isFinished()); + assertThat(getNextPage(exchangeClient)).isNull(); + assertThat(exchangeClient.isFinished()).isTrue(); DirectExchangeClientStatus status = exchangeClient.getStatus(); - assertEquals(status.getBufferedPages(), 0); + assertThat(status.getBufferedPages()).isEqualTo(0); // client should have sent only 3 requests: one to get all pages, one to acknowledge and one to get the done signal assertStatus(status.getPageBufferClientStatuses().get(0), location, "closed", 3, 3, 3, "not scheduled"); @@ -277,7 +274,7 @@ public void testAddLocation() assertThat(buffer.getPages().asMap()).isEmpty(); assertThat(buffer.getFinishedTasks()).isEmpty(); assertThat(buffer.getFailedTasks().asMap()).isEmpty(); - assertFalse(buffer.isNoMoreTasks()); + assertThat(buffer.isNoMoreTasks()).isFalse(); exchangeClient.addLocation(task1, location1); assertThat(buffer.getAllTasks()).containsExactly(task1); @@ -302,7 +299,7 @@ public void testAddLocation() assertTaskIsNotFinished(buffer, task3); exchangeClient.noMoreLocations(); - assertTrue(buffer.isNoMoreTasks()); + assertThat(buffer.isNoMoreTasks()).isTrue(); assertThat(buffer.getAllTasks()).containsExactlyInAnyOrder(task1, task2, task3); assertTaskIsNotFinished(buffer, task3); @@ -312,11 +309,17 @@ public void testAddLocation() assertThat(buffer.getFinishedTasks()).containsExactlyInAnyOrder(task1, task2, task3); assertThat(buffer.getFailedTasks().asMap()).isEmpty(); - assertEventually(() -> assertEquals(exchangeClient.getStatus().getPageBufferClientStatuses().get(0).getHttpRequestState(), "not scheduled", "httpRequestState")); - assertEventually(() -> assertEquals(exchangeClient.getStatus().getPageBufferClientStatuses().get(1).getHttpRequestState(), "not scheduled", "httpRequestState")); - assertEventually(() -> assertEquals(exchangeClient.getStatus().getPageBufferClientStatuses().get(2).getHttpRequestState(), "not scheduled", "httpRequestState")); - - assertTrue(exchangeClient.isFinished()); + assertEventually(() -> assertThat(exchangeClient.getStatus().getPageBufferClientStatuses().get(0).getHttpRequestState()) + .describedAs("httpRequestState") + .isEqualTo("not scheduled")); + assertEventually(() -> assertThat(exchangeClient.getStatus().getPageBufferClientStatuses().get(1).getHttpRequestState()) + .describedAs("httpRequestState") + .isEqualTo("not scheduled")); + assertEventually(() -> assertThat(exchangeClient.getStatus().getPageBufferClientStatuses().get(2).getHttpRequestState()) + .describedAs("httpRequestState") + .isEqualTo("not scheduled")); + + assertThat(exchangeClient.isFinished()).isTrue(); } @Test @@ -349,36 +352,36 @@ public void testStreamingAddLocation() processor.setComplete(location1); exchangeClient.addLocation(new TaskId(new StageId("query", 1), 0, 0), location1); - assertFalse(exchangeClient.isFinished()); + assertThat(exchangeClient.isFinished()).isFalse(); assertPageEquals(getNextPage(exchangeClient), createPage(1)); - assertFalse(exchangeClient.isFinished()); + assertThat(exchangeClient.isFinished()).isFalse(); assertPageEquals(getNextPage(exchangeClient), createPage(2)); - assertFalse(exchangeClient.isFinished()); + assertThat(exchangeClient.isFinished()).isFalse(); assertPageEquals(getNextPage(exchangeClient), createPage(3)); - assertNull(exchangeClient.pollPage()); + assertThat(exchangeClient.pollPage()).isNull(); ListenableFuture firstBlocked = exchangeClient.isBlocked(); - assertFalse(tryGetFutureValue(firstBlocked, 10, MILLISECONDS).isPresent()); - assertFalse(firstBlocked.isDone()); + assertThat(tryGetFutureValue(firstBlocked, 10, MILLISECONDS).isPresent()).isFalse(); + assertThat(firstBlocked.isDone()).isFalse(); - assertNull(exchangeClient.pollPage()); + assertThat(exchangeClient.pollPage()).isNull(); ListenableFuture secondBlocked = exchangeClient.isBlocked(); - assertFalse(tryGetFutureValue(secondBlocked, 10, MILLISECONDS).isPresent()); - assertFalse(secondBlocked.isDone()); + assertThat(tryGetFutureValue(secondBlocked, 10, MILLISECONDS).isPresent()).isFalse(); + assertThat(secondBlocked.isDone()).isFalse(); - assertNull(exchangeClient.pollPage()); + assertThat(exchangeClient.pollPage()).isNull(); ListenableFuture thirdBlocked = exchangeClient.isBlocked(); - assertFalse(tryGetFutureValue(thirdBlocked, 10, MILLISECONDS).isPresent()); - assertFalse(thirdBlocked.isDone()); + assertThat(tryGetFutureValue(thirdBlocked, 10, MILLISECONDS).isPresent()).isFalse(); + assertThat(thirdBlocked.isDone()).isFalse(); thirdBlocked.cancel(true); - assertTrue(thirdBlocked.isDone()); - assertFalse(tryGetFutureValue(firstBlocked, 10, MILLISECONDS).isPresent()); - assertFalse(firstBlocked.isDone()); - assertFalse(tryGetFutureValue(secondBlocked, 10, MILLISECONDS).isPresent()); - assertFalse(secondBlocked.isDone()); + assertThat(thirdBlocked.isDone()).isTrue(); + assertThat(tryGetFutureValue(firstBlocked, 10, MILLISECONDS).isPresent()).isFalse(); + assertThat(firstBlocked.isDone()).isFalse(); + assertThat(tryGetFutureValue(secondBlocked, 10, MILLISECONDS).isPresent()).isFalse(); + assertThat(secondBlocked.isDone()).isFalse(); - assertFalse(exchangeClient.isFinished()); + assertThat(exchangeClient.isFinished()).isFalse(); URI location2 = URI.create("http://localhost:8082/bar"); processor.addPage(location2, createPage(4)); @@ -388,19 +391,19 @@ public void testStreamingAddLocation() exchangeClient.addLocation(new TaskId(new StageId("query", 1), 1, 0), location2); tryGetFutureValue(firstBlocked, 5, SECONDS); - assertTrue(firstBlocked.isDone()); + assertThat(firstBlocked.isDone()).isTrue(); tryGetFutureValue(secondBlocked, 5, SECONDS); - assertTrue(secondBlocked.isDone()); + assertThat(secondBlocked.isDone()).isTrue(); - assertFalse(exchangeClient.isFinished()); + assertThat(exchangeClient.isFinished()).isFalse(); assertPageEquals(getNextPage(exchangeClient), createPage(4)); - assertFalse(exchangeClient.isFinished()); + assertThat(exchangeClient.isFinished()).isFalse(); assertPageEquals(getNextPage(exchangeClient), createPage(5)); - assertFalse(exchangeClient.isFinished()); + assertThat(exchangeClient.isFinished()).isFalse(); assertPageEquals(getNextPage(exchangeClient), createPage(6)); - assertFalse(tryGetFutureValue(exchangeClient.isBlocked(), 10, MILLISECONDS).isPresent()); - assertFalse(exchangeClient.isFinished()); + assertThat(tryGetFutureValue(exchangeClient.isBlocked(), 10, MILLISECONDS).isPresent()).isFalse(); + assertThat(exchangeClient.isFinished()).isFalse(); exchangeClient.noMoreLocations(); // The transition to closed may happen asynchronously, since it requires that all the HTTP clients @@ -452,14 +455,14 @@ public void testStreamingTaskFailure() processor.setComplete(location1); - assertFalse(tryGetFutureValue(exchangeClient.isBlocked(), 10, MILLISECONDS).isPresent()); + assertThat(tryGetFutureValue(exchangeClient.isBlocked(), 10, MILLISECONDS).isPresent()).isFalse(); RuntimeException randomException = new RuntimeException("randomfailure"); processor.setFailed(location2, randomException); assertThatThrownBy(() -> getNextPage(exchangeClient)).hasMessageContaining("Encountered too many errors talking to a worker node"); - assertFalse(exchangeClient.isFinished()); + assertThat(exchangeClient.isFinished()).isFalse(); } @Test @@ -505,18 +508,18 @@ public void testDeduplicationTaskFailure() (taskId, failure) -> {}); exchangeClient.addLocation(attempt0Task1, attempt0Task1Location); - assertFalse(tryGetFutureValue(exchangeClient.isBlocked(), 10, MILLISECONDS).isPresent()); + assertThat(tryGetFutureValue(exchangeClient.isBlocked(), 10, MILLISECONDS).isPresent()).isFalse(); exchangeClient.addLocation(attempt1Task1, attempt1Task1Location); exchangeClient.addLocation(attempt1Task2, attempt1Task2Location); - assertFalse(tryGetFutureValue(exchangeClient.isBlocked(), 10, MILLISECONDS).isPresent()); + assertThat(tryGetFutureValue(exchangeClient.isBlocked(), 10, MILLISECONDS).isPresent()).isFalse(); exchangeClient.noMoreLocations(); exchangeClient.isBlocked().get(10, SECONDS); assertThatThrownBy(() -> getNextPage(exchangeClient)).hasMessageContaining("Encountered too many errors talking to a worker node"); - assertFalse(exchangeClient.isFinished()); + assertThat(exchangeClient.isFinished()).isFalse(); } @Test @@ -568,7 +571,7 @@ public void testDeduplication() processor.setFailed(locationP1A0, new RuntimeException("failure")); processor.setComplete(locationP0A1); - assertFalse(exchangeClient.isFinished()); + assertThat(exchangeClient.isFinished()).isFalse(); assertThatThrownBy(() -> exchangeClient.isBlocked().get(50, MILLISECONDS)) .isInstanceOf(TimeoutException.class); @@ -587,12 +590,18 @@ public void testDeduplication() assertThat(pages).hasSize(2); assertThat(pages.stream().map(Page::getPositionCount).collect(toImmutableSet())).containsAll(ImmutableList.of(2, 3)); - assertEventually(() -> assertTrue(exchangeClient.isFinished())); + assertEventually(() -> assertThat(exchangeClient.isFinished()).isTrue()); assertEventually(() -> { - assertEquals(exchangeClient.getStatus().getPageBufferClientStatuses().get(0).getHttpRequestState(), "not scheduled", "httpRequestState"); - assertEquals(exchangeClient.getStatus().getPageBufferClientStatuses().get(1).getHttpRequestState(), "not scheduled", "httpRequestState"); - assertEquals(exchangeClient.getStatus().getPageBufferClientStatuses().get(2).getHttpRequestState(), "not scheduled", "httpRequestState"); + assertThat(exchangeClient.getStatus().getPageBufferClientStatuses().get(0).getHttpRequestState()) + .describedAs("httpRequestState") + .isEqualTo("not scheduled"); + assertThat(exchangeClient.getStatus().getPageBufferClientStatuses().get(1).getHttpRequestState()) + .describedAs("httpRequestState") + .isEqualTo("not scheduled"); + assertThat(exchangeClient.getStatus().getPageBufferClientStatuses().get(2).getHttpRequestState()) + .describedAs("httpRequestState") + .isEqualTo("not scheduled"); }); exchangeClient.close(); @@ -646,7 +655,7 @@ public void testTaskFailure() assertThat(buffer.getPages().asMap()).isEmpty(); assertThat(buffer.getFinishedTasks()).isEmpty(); assertThat(buffer.getFailedTasks().asMap()).isEmpty(); - assertFalse(buffer.isNoMoreTasks()); + assertThat(buffer.isNoMoreTasks()).isFalse(); exchangeClient.addLocation(task1, location1); assertThat(buffer.getAllTasks()).containsExactly(task1); @@ -683,8 +692,8 @@ public void testTaskFailure() assertThat(buffer.getPages().get(task2)).hasSize(0); assertThat(buffer.getPages().get(task3)).hasSize(0); - assertTrue(latch.await(10, SECONDS)); - assertEquals(failedTasks, ImmutableSet.of(task2, task3)); + assertThat(latch.await(10, SECONDS)).isTrue(); + assertThat(failedTasks).isEqualTo(ImmutableSet.of(task2, task3)); exchangeClient.addLocation(task4, location4); assertThat(buffer.getAllTasks()).containsExactlyInAnyOrder(task1, task2, task3, task4); @@ -695,16 +704,24 @@ public void testTaskFailure() assertThat(buffer.getPages().get(task4)).hasSize(2); assertThat(buffer.getFinishedTasks()).containsExactlyInAnyOrder(task1, task4); - assertFalse(exchangeClient.isFinished()); + assertThat(exchangeClient.isFinished()).isFalse(); buffer.setFinished(true); - assertTrue(exchangeClient.isFinished()); + assertThat(exchangeClient.isFinished()).isTrue(); exchangeClient.close(); - assertEventually(() -> assertEquals(exchangeClient.getStatus().getPageBufferClientStatuses().get(0).getHttpRequestState(), "not scheduled", "httpRequestState")); - assertEventually(() -> assertEquals(exchangeClient.getStatus().getPageBufferClientStatuses().get(1).getHttpRequestState(), "not scheduled", "httpRequestState")); - assertEventually(() -> assertEquals(exchangeClient.getStatus().getPageBufferClientStatuses().get(2).getHttpRequestState(), "not scheduled", "httpRequestState")); - assertEventually(() -> assertEquals(exchangeClient.getStatus().getPageBufferClientStatuses().get(3).getHttpRequestState(), "not scheduled", "httpRequestState")); + assertEventually(() -> assertThat(exchangeClient.getStatus().getPageBufferClientStatuses().get(0).getHttpRequestState()) + .describedAs("httpRequestState") + .isEqualTo("not scheduled")); + assertEventually(() -> assertThat(exchangeClient.getStatus().getPageBufferClientStatuses().get(1).getHttpRequestState()) + .describedAs("httpRequestState") + .isEqualTo("not scheduled")); + assertEventually(() -> assertThat(exchangeClient.getStatus().getPageBufferClientStatuses().get(2).getHttpRequestState()) + .describedAs("httpRequestState") + .isEqualTo("not scheduled")); + assertEventually(() -> assertThat(exchangeClient.getStatus().getPageBufferClientStatuses().get(3).getHttpRequestState()) + .describedAs("httpRequestState") + .isEqualTo("not scheduled")); assertThat(buffer.getFinishedTasks()).containsExactlyInAnyOrder(task1, task4); assertThat(buffer.getFailedTasks().keySet()).containsExactlyInAnyOrder(task2, task3); @@ -713,7 +730,7 @@ public void testTaskFailure() assertThat(buffer.getFailedTasks().asMap().get(task3)).hasSize(1); assertThat(buffer.getFailedTasks().asMap().get(task3).iterator().next()).isEqualTo(trinoException); - assertTrue(exchangeClient.isFinished()); + assertThat(exchangeClient.isFinished()).isTrue(); } private static void assertTaskIsNotFinished(TestingDirectExchangeBuffer buffer, TaskId task) @@ -753,7 +770,7 @@ public void testStreamingBufferLimit() exchangeClient.addLocation(new TaskId(new StageId("query", 1), 0, 0), location); exchangeClient.noMoreLocations(); - assertFalse(exchangeClient.isFinished()); + assertThat(exchangeClient.isFinished()).isFalse(); long start = System.nanoTime(); @@ -766,8 +783,8 @@ public void testStreamingBufferLimit() while (exchangeClient.getStatus().getBufferedPages() == 0); // client should have sent a single request for a single page - assertEquals(exchangeClient.getStatus().getBufferedPages(), 1); - assertTrue(exchangeClient.getStatus().getBufferedBytes() > 0); + assertThat(exchangeClient.getStatus().getBufferedPages()).isEqualTo(1); + assertThat(exchangeClient.getStatus().getBufferedBytes() > 0).isTrue(); assertStatus(exchangeClient.getStatus().getPageBufferClientStatuses().get(0), location, "queued", 1, 1, 1, "not scheduled"); // remove the page and wait for the client to fetch another page @@ -780,8 +797,8 @@ public void testStreamingBufferLimit() // client should have sent a single request for a single page assertStatus(exchangeClient.getStatus().getPageBufferClientStatuses().get(0), location, "queued", 2, 2, 2, "not scheduled"); - assertEquals(exchangeClient.getStatus().getBufferedPages(), 1); - assertTrue(exchangeClient.getStatus().getBufferedBytes() > 0); + assertThat(exchangeClient.getStatus().getBufferedPages()).isEqualTo(1); + assertThat(exchangeClient.getStatus().getBufferedBytes() > 0).isTrue(); // remove the page and wait for the client to fetch another page assertPageEquals(exchangeClient.pollPage(), createPage(2)); @@ -793,16 +810,16 @@ public void testStreamingBufferLimit() // client should have sent a single request for a single page assertStatus(exchangeClient.getStatus().getPageBufferClientStatuses().get(0), location, "queued", 3, 3, 3, "not scheduled"); - assertEquals(exchangeClient.getStatus().getBufferedPages(), 1); - assertTrue(exchangeClient.getStatus().getBufferedBytes() > 0); + assertThat(exchangeClient.getStatus().getBufferedPages()).isEqualTo(1); + assertThat(exchangeClient.getStatus().getBufferedBytes() > 0).isTrue(); // remove last page assertPageEquals(getNextPage(exchangeClient), createPage(3)); // wait for client to decide there are no more pages - assertNull(getNextPage(exchangeClient)); - assertEquals(exchangeClient.getStatus().getBufferedPages(), 0); - assertTrue(exchangeClient.isFinished()); + assertThat(getNextPage(exchangeClient)).isNull(); + assertThat(exchangeClient.getStatus().getBufferedPages()).isEqualTo(0); + assertThat(exchangeClient.isFinished()).isTrue(); exchangeClient.close(); assertStatus(exchangeClient.getStatus().getPageBufferClientStatuses().get(0), location, "closed", 3, 5, 5, "not scheduled"); } @@ -826,17 +843,17 @@ public void testStreamingRetryDataCorruption() URI location = URI.create("http://localhost:8080"); DirectExchangeClient exchangeClient = setUpDataCorruption(DataIntegrityVerification.RETRY, location); - assertFalse(exchangeClient.isFinished()); + assertThat(exchangeClient.isFinished()).isFalse(); assertPageEquals(getNextPage(exchangeClient), createPage(1)); - assertFalse(exchangeClient.isFinished()); + assertThat(exchangeClient.isFinished()).isFalse(); assertPageEquals(getNextPage(exchangeClient), createPage(2)); - assertNull(getNextPage(exchangeClient)); - assertTrue(exchangeClient.isFinished()); + assertThat(getNextPage(exchangeClient)).isNull(); + assertThat(exchangeClient.isFinished()).isTrue(); exchangeClient.close(); DirectExchangeClientStatus status = exchangeClient.getStatus(); - assertEquals(status.getBufferedPages(), 0); - assertEquals(status.getBufferedBytes(), 0); + assertThat(status.getBufferedPages()).isEqualTo(0); + assertThat(status.getBufferedBytes()).isEqualTo(0); assertStatus(status.getPageBufferClientStatuses().get(0), location, "closed", 2, 4, 4, "not scheduled"); } @@ -937,7 +954,7 @@ public void testStreamingClose() exchangeClient.noMoreLocations(); // fetch a page - assertFalse(exchangeClient.isFinished()); + assertThat(exchangeClient.isFinished()).isFalse(); assertPageEquals(getNextPage(exchangeClient), createPage(1)); // close client while pages are still available @@ -945,14 +962,18 @@ public void testStreamingClose() while (!exchangeClient.isFinished()) { MILLISECONDS.sleep(10); } - assertTrue(exchangeClient.isFinished()); - assertNull(exchangeClient.pollPage()); - assertEquals(exchangeClient.getStatus().getBufferedPages(), 0); + assertThat(exchangeClient.isFinished()).isTrue(); + assertThat(exchangeClient.pollPage()).isNull(); + assertThat(exchangeClient.getStatus().getBufferedPages()).isEqualTo(0); PageBufferClientStatus clientStatus = exchangeClient.getStatus().getPageBufferClientStatuses().get(0); - assertEquals(clientStatus.getUri(), location); - assertEquals(clientStatus.getState(), "closed", "status"); - assertEquals(clientStatus.getHttpRequestState(), "not scheduled", "httpRequestState"); + assertThat(clientStatus.getUri()).isEqualTo(location); + assertThat(clientStatus.getState()) + .describedAs("status") + .isEqualTo("closed"); + assertThat(clientStatus.getHttpRequestState()) + .describedAs("httpRequestState") + .isEqualTo("not scheduled"); } @Test @@ -988,7 +1009,7 @@ public void testScheduleWhenOneClientFilledBuffer() int clientCount = exchangeClient.scheduleRequestIfNecessary(); // The first client filled the buffer. There is no place for the another one - assertEquals(clientCount, 1); + assertThat(clientCount).isEqualTo(1); } @Test @@ -1021,7 +1042,7 @@ public void testScheduleWhenAllClientsAreEmpty() exchangeClient.getQueuedClients().addAll(ImmutableList.of(firstClient, secondClient)); int clientCount = exchangeClient.scheduleRequestIfNecessary(); - assertEquals(clientCount, 2); + assertThat(clientCount).isEqualTo(2); } @Test @@ -1058,7 +1079,7 @@ public void testScheduleWhenThereIsPendingClient() int clientCount = exchangeClient.scheduleRequestIfNecessary(); // The first client is pending and it reserved the space in the buffer. There is no place for the another one - assertEquals(clientCount, 0); + assertThat(clientCount).isEqualTo(0); } private HttpPageBufferClient createHttpPageBufferClient(TestingHttpClient.Processor processor, DataSize expectedMaxSize, URI location, HttpPageBufferClient.ClientCallback callback) @@ -1095,9 +1116,9 @@ private static Slice getNextPage(DirectExchangeClient exchangeClient) private void assertPageEquals(Slice actualPage, Page expectedPage) { - assertNotNull(actualPage); - assertEquals(getSerializedPagePositionCount(actualPage), expectedPage.getPositionCount()); - assertEquals(serdeFactory.createDeserializer(Optional.empty()).deserialize(actualPage).getChannelCount(), expectedPage.getChannelCount()); + assertThat(actualPage).isNotNull(); + assertThat(getSerializedPagePositionCount(actualPage)).isEqualTo(expectedPage.getPositionCount()); + assertThat(serdeFactory.createDeserializer(Optional.empty()).deserialize(actualPage).getChannelCount()).isEqualTo(expectedPage.getChannelCount()); } private static void assertStatus( @@ -1109,12 +1130,22 @@ private static void assertStatus( int requestsCompleted, String httpRequestState) { - assertEquals(clientStatus.getUri(), location); - assertEquals(clientStatus.getState(), status, "status"); - assertEquals(clientStatus.getPagesReceived(), pagesReceived, "pagesReceived"); - assertEquals(clientStatus.getRequestsScheduled(), requestsScheduled, "requestsScheduled"); - assertEquals(clientStatus.getRequestsCompleted(), requestsCompleted, "requestsCompleted"); - assertEquals(clientStatus.getHttpRequestState(), httpRequestState, "httpRequestState"); + assertThat(clientStatus.getUri()).isEqualTo(location); + assertThat(clientStatus.getState()) + .describedAs("status") + .isEqualTo(status); + assertThat(clientStatus.getPagesReceived()) + .describedAs("pagesReceived") + .isEqualTo(pagesReceived); + assertThat(clientStatus.getRequestsScheduled()) + .describedAs("requestsScheduled") + .isEqualTo(requestsScheduled); + assertThat(clientStatus.getRequestsCompleted()) + .describedAs("requestsCompleted") + .isEqualTo(requestsCompleted); + assertThat(clientStatus.getHttpRequestState()) + .describedAs("httpRequestState") + .isEqualTo(httpRequestState); } private static class MockClientCallback diff --git a/core/trino-main/src/test/java/io/trino/operator/TestDistinctLimitOperator.java b/core/trino-main/src/test/java/io/trino/operator/TestDistinctLimitOperator.java index a102f9e31769..2041ca260f59 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestDistinctLimitOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestDistinctLimitOperator.java @@ -45,9 +45,9 @@ import static io.trino.testing.TestingTaskContext.createTaskContext; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -193,7 +193,7 @@ public void testMemoryReservationYield(Type type) GroupByHashYieldAssertion.GroupByHashYieldResult result = finishOperatorWithYieldingGroupByHash(input, type, operatorFactory, operator -> ((DistinctLimitOperator) operator).getCapacity(), 450_000); assertGreaterThanOrEqual(result.getYieldCount(), 5); assertGreaterThanOrEqual(result.getMaxReservedBytes(), 20L << 20); - assertEquals(result.getOutput().stream().mapToInt(Page::getPositionCount).sum(), 6_000 * 600); + assertThat(result.getOutput().stream().mapToInt(Page::getPositionCount).sum()).isEqualTo(6_000 * 600); } private DriverContext newDriverContext() diff --git a/core/trino-main/src/test/java/io/trino/operator/TestDriver.java b/core/trino-main/src/test/java/io/trino/operator/TestDriver.java index 199a47c827c1..d76f0ad55f5a 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestDriver.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestDriver.java @@ -64,11 +64,9 @@ import static io.trino.testing.TestingTaskContext.createTaskContext; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertSame; -import static org.testng.Assert.assertTrue; @TestInstance(PER_METHOD) public class TestDriver @@ -106,15 +104,15 @@ public void testNormalFinish() Operator sink = createSinkOperator(types); Driver driver = Driver.createDriver(driverContext, source, sink); - assertSame(driver.getDriverContext(), driverContext); + assertThat(driver.getDriverContext()).isSameAs(driverContext); - assertFalse(driver.isFinished()); + assertThat(driver.isFinished()).isFalse(); ListenableFuture blocked = driver.processForDuration(new Duration(1, TimeUnit.SECONDS)); - assertTrue(blocked.isDone()); - assertTrue(driver.isFinished()); + assertThat(blocked.isDone()).isTrue(); + assertThat(driver.isFinished()).isTrue(); - assertTrue(sink.isFinished()); - assertTrue(source.isFinished()); + assertThat(sink.isFinished()).isTrue(); + assertThat(source.isFinished()).isTrue(); } // The race can be reproduced somewhat reliably when the invocationCount is 10K, but we use 1K iterations to cap the test runtime. @@ -149,18 +147,18 @@ public void testAbruptFinish() PageConsumerOperator sink = createSinkOperator(types); Driver driver = Driver.createDriver(driverContext, source, sink); - assertSame(driver.getDriverContext(), driverContext); + assertThat(driver.getDriverContext()).isSameAs(driverContext); - assertFalse(driver.isFinished()); + assertThat(driver.isFinished()).isFalse(); driver.close(); - assertTrue(driver.isFinished()); + assertThat(driver.isFinished()).isTrue(); // finish is only called in normal operations - assertFalse(source.isFinished()); - assertFalse(sink.isFinished()); + assertThat(source.isFinished()).isFalse(); + assertThat(sink.isFinished()).isFalse(); // close is always called (values operator doesn't have a closed state) - assertTrue(sink.isClosed()); + assertThat(sink.isClosed()).isTrue(); } @Test @@ -180,20 +178,20 @@ public void testAddSourceFinish() PageConsumerOperator sink = createSinkOperator(types); Driver driver = Driver.createDriver(driverContext, source, sink); - assertSame(driver.getDriverContext(), driverContext); + assertThat(driver.getDriverContext()).isSameAs(driverContext); - assertFalse(driver.isFinished()); - assertFalse(driver.processForDuration(new Duration(1, TimeUnit.MILLISECONDS)).isDone()); - assertFalse(driver.isFinished()); + assertThat(driver.isFinished()).isFalse(); + assertThat(driver.processForDuration(new Duration(1, TimeUnit.MILLISECONDS)).isDone()).isFalse(); + assertThat(driver.isFinished()).isFalse(); driver.updateSplitAssignment(new SplitAssignment(sourceId, ImmutableSet.of(new ScheduledSplit(0, sourceId, newMockSplit())), true)); - assertFalse(driver.isFinished()); - assertTrue(driver.processForDuration(new Duration(1, TimeUnit.SECONDS)).isDone()); - assertTrue(driver.isFinished()); + assertThat(driver.isFinished()).isFalse(); + assertThat(driver.processForDuration(new Duration(1, TimeUnit.SECONDS)).isDone()).isTrue(); + assertThat(driver.isFinished()).isTrue(); - assertTrue(sink.isFinished()); - assertTrue(source.isFinished()); + assertThat(sink.isFinished()).isTrue(); + assertThat(source.isFinished()).isTrue(); } @Test @@ -202,20 +200,20 @@ public void testBrokenOperatorCloseWhileProcessing() BrokenOperator brokenOperator = new BrokenOperator(driverContext.addOperatorContext(0, new PlanNodeId("test"), "source"), false); Driver driver = Driver.createDriver(driverContext, brokenOperator, createSinkOperator(ImmutableList.of())); - assertSame(driver.getDriverContext(), driverContext); + assertThat(driver.getDriverContext()).isSameAs(driverContext); // block thread in operator processing Future driverProcessFor = executor.submit(() -> driver.processForDuration(new Duration(1, TimeUnit.MILLISECONDS)).isDone()); brokenOperator.waitForLocked(); driver.close(); - assertTrue(driver.isFinished()); + assertThat(driver.isFinished()).isTrue(); assertThatThrownBy(() -> driverProcessFor.get(1, TimeUnit.SECONDS)) .isInstanceOf(ExecutionException.class) .hasCause(new TrinoException(GENERIC_INTERNAL_ERROR, "Driver was interrupted")); - assertTrue(driver.getDestroyedFuture().isDone()); + assertThat(driver.getDestroyedFuture().isDone()).isTrue(); } @Test @@ -225,7 +223,7 @@ public void testBrokenOperatorProcessWhileClosing() BrokenOperator brokenOperator = new BrokenOperator(driverContext.addOperatorContext(0, new PlanNodeId("test"), "source"), true); Driver driver = Driver.createDriver(driverContext, brokenOperator, createSinkOperator(ImmutableList.of())); - assertSame(driver.getDriverContext(), driverContext); + assertThat(driver.getDriverContext()).isSameAs(driverContext); // block thread in operator close Future driverClose = executor.submit(() -> { @@ -234,14 +232,14 @@ public void testBrokenOperatorProcessWhileClosing() }); brokenOperator.waitForLocked(); - assertTrue(driver.processForDuration(new Duration(1, TimeUnit.MILLISECONDS)).isDone()); - assertTrue(driver.isFinished()); - assertFalse(driver.getDestroyedFuture().isDone()); + assertThat(driver.processForDuration(new Duration(1, TimeUnit.MILLISECONDS)).isDone()).isTrue(); + assertThat(driver.isFinished()).isTrue(); + assertThat(driver.getDestroyedFuture().isDone()).isFalse(); brokenOperator.unlock(); - assertTrue(driverClose.get()); - assertTrue(driver.getDestroyedFuture().isDone()); + assertThat(driverClose.get()).isTrue(); + assertThat(driver.getDestroyedFuture().isDone()).isTrue(); } @Test @@ -260,7 +258,7 @@ public void testMemoryRevocationRace() // the table scan operator will request memory revocation with requestMemoryRevoking() // while the driver is still not done with the processFor() method and before it moves to // updateDriverBlockedFuture() method. - assertTrue(driver.processForDuration(new Duration(100, TimeUnit.MILLISECONDS)).isDone()); + assertThat(driver.processForDuration(new Duration(100, TimeUnit.MILLISECONDS)).isDone()).isTrue(); } @Test @@ -281,10 +279,10 @@ public void testUnblocksOnFinish() Driver driver = Driver.createDriver(driverContext, source, sink); ListenableFuture blocked = driver.processForDuration(new Duration(100, TimeUnit.MILLISECONDS)); - assertFalse(blocked.isDone()); + assertThat(blocked.isDone()).isFalse(); sink.setFinished(); - assertTrue(blocked.isDone()); + assertThat(blocked.isDone()).isTrue(); } @Test @@ -308,28 +306,28 @@ public void testBrokenOperatorAddSource() Future driverProcessFor = executor.submit(() -> driver.processForDuration(new Duration(1, TimeUnit.MILLISECONDS)).isDone()); brokenOperator.waitForLocked(); - assertSame(driver.getDriverContext(), driverContext); + assertThat(driver.getDriverContext()).isSameAs(driverContext); - assertFalse(driver.isFinished()); + assertThat(driver.isFinished()).isFalse(); // processFor always returns NOT_BLOCKED, because DriveLockResult was not acquired - assertTrue(driver.processForDuration(new Duration(1, TimeUnit.MILLISECONDS)).isDone()); - assertFalse(driver.isFinished()); + assertThat(driver.processForDuration(new Duration(1, TimeUnit.MILLISECONDS)).isDone()).isTrue(); + assertThat(driver.isFinished()).isFalse(); driver.updateSplitAssignment(new SplitAssignment(sourceId, ImmutableSet.of(new ScheduledSplit(0, sourceId, newMockSplit())), true)); - assertFalse(driver.getDestroyedFuture().isDone()); + assertThat(driver.getDestroyedFuture().isDone()).isFalse(); // processFor always returns NOT_BLOCKED, because DriveLockResult was not acquired - assertTrue(driver.processForDuration(new Duration(1, TimeUnit.SECONDS)).isDone()); - assertFalse(driver.isFinished()); + assertThat(driver.processForDuration(new Duration(1, TimeUnit.SECONDS)).isDone()).isTrue(); + assertThat(driver.isFinished()).isFalse(); driver.close(); - assertTrue(driver.isFinished()); + assertThat(driver.isFinished()).isTrue(); assertThatThrownBy(() -> driverProcessFor.get(1, TimeUnit.SECONDS)) .isInstanceOf(ExecutionException.class) .hasCause(new TrinoException(GENERIC_INTERNAL_ERROR, "Driver was interrupted")); - assertTrue(driver.getDestroyedFuture().isDone()); + assertThat(driver.getDestroyedFuture().isDone()).isTrue(); } private static Split newMockSplit() @@ -378,7 +376,7 @@ public void unlock() private void waitForLocked() { try { - assertTrue(lockedLatch.await(10, TimeUnit.SECONDS)); + assertThat(lockedLatch.await(10, TimeUnit.SECONDS)).isTrue(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); @@ -389,10 +387,10 @@ private void waitForLocked() private void waitForUnlock() { try { - assertTrue(lock.tryLock(1, TimeUnit.SECONDS)); + assertThat(lock.tryLock(1, TimeUnit.SECONDS)).isTrue(); try { lockedLatch.countDown(); - assertTrue(unlockLatch.await(5, TimeUnit.SECONDS)); + assertThat(unlockLatch.await(5, TimeUnit.SECONDS)).isTrue(); } finally { lock.unlock(); diff --git a/core/trino-main/src/test/java/io/trino/operator/TestDriverStats.java b/core/trino-main/src/test/java/io/trino/operator/TestDriverStats.java index cd21adee0090..4ebcc82e11ff 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestDriverStats.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestDriverStats.java @@ -23,8 +23,8 @@ import static io.trino.operator.TestOperatorStats.assertExpectedOperatorStats; import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static org.assertj.core.api.Assertions.assertThat; import static org.joda.time.DateTimeZone.UTC; -import static org.testng.Assert.assertEquals; public class TestDriverStats { @@ -83,43 +83,43 @@ public void testJson() public static void assertExpectedDriverStats(DriverStats actual) { - assertEquals(actual.getCreateTime(), new DateTime(1, UTC)); - assertEquals(actual.getStartTime(), new DateTime(2, UTC)); - assertEquals(actual.getEndTime(), new DateTime(3, UTC)); - assertEquals(actual.getQueuedTime(), new Duration(4, NANOSECONDS)); - assertEquals(actual.getElapsedTime(), new Duration(5, NANOSECONDS)); + assertThat(actual.getCreateTime()).isEqualTo(new DateTime(1, UTC)); + assertThat(actual.getStartTime()).isEqualTo(new DateTime(2, UTC)); + assertThat(actual.getEndTime()).isEqualTo(new DateTime(3, UTC)); + assertThat(actual.getQueuedTime()).isEqualTo(new Duration(4, NANOSECONDS)); + assertThat(actual.getElapsedTime()).isEqualTo(new Duration(5, NANOSECONDS)); - assertEquals(actual.getUserMemoryReservation(), DataSize.ofBytes(6)); - assertEquals(actual.getRevocableMemoryReservation(), DataSize.ofBytes(7)); + assertThat(actual.getUserMemoryReservation()).isEqualTo(DataSize.ofBytes(6)); + assertThat(actual.getRevocableMemoryReservation()).isEqualTo(DataSize.ofBytes(7)); - assertEquals(actual.getTotalScheduledTime(), new Duration(9, NANOSECONDS)); - assertEquals(actual.getTotalCpuTime(), new Duration(10, NANOSECONDS)); - assertEquals(actual.getTotalBlockedTime(), new Duration(12, NANOSECONDS)); + assertThat(actual.getTotalScheduledTime()).isEqualTo(new Duration(9, NANOSECONDS)); + assertThat(actual.getTotalCpuTime()).isEqualTo(new Duration(10, NANOSECONDS)); + assertThat(actual.getTotalBlockedTime()).isEqualTo(new Duration(12, NANOSECONDS)); - assertEquals(actual.getPhysicalInputDataSize(), DataSize.ofBytes(131)); - assertEquals(actual.getPhysicalInputPositions(), 141); - assertEquals(actual.getPhysicalInputReadTime(), new Duration(151, NANOSECONDS)); + assertThat(actual.getPhysicalInputDataSize()).isEqualTo(DataSize.ofBytes(131)); + assertThat(actual.getPhysicalInputPositions()).isEqualTo(141); + assertThat(actual.getPhysicalInputReadTime()).isEqualTo(new Duration(151, NANOSECONDS)); - assertEquals(actual.getInternalNetworkInputDataSize(), DataSize.ofBytes(132)); - assertEquals(actual.getInternalNetworkInputPositions(), 142); + assertThat(actual.getInternalNetworkInputDataSize()).isEqualTo(DataSize.ofBytes(132)); + assertThat(actual.getInternalNetworkInputPositions()).isEqualTo(142); - assertEquals(actual.getRawInputDataSize(), DataSize.ofBytes(13)); - assertEquals(actual.getRawInputPositions(), 14); - assertEquals(actual.getRawInputReadTime(), new Duration(15, NANOSECONDS)); + assertThat(actual.getRawInputDataSize()).isEqualTo(DataSize.ofBytes(13)); + assertThat(actual.getRawInputPositions()).isEqualTo(14); + assertThat(actual.getRawInputReadTime()).isEqualTo(new Duration(15, NANOSECONDS)); - assertEquals(actual.getProcessedInputDataSize(), DataSize.ofBytes(16)); - assertEquals(actual.getProcessedInputPositions(), 17); + assertThat(actual.getProcessedInputDataSize()).isEqualTo(DataSize.ofBytes(16)); + assertThat(actual.getProcessedInputPositions()).isEqualTo(17); - assertEquals(actual.getInputBlockedTime(), new Duration(101, NANOSECONDS)); + assertThat(actual.getInputBlockedTime()).isEqualTo(new Duration(101, NANOSECONDS)); - assertEquals(actual.getOutputDataSize(), DataSize.ofBytes(18)); - assertEquals(actual.getOutputPositions(), 19); + assertThat(actual.getOutputDataSize()).isEqualTo(DataSize.ofBytes(18)); + assertThat(actual.getOutputPositions()).isEqualTo(19); - assertEquals(actual.getOutputBlockedTime(), new Duration(102, NANOSECONDS)); + assertThat(actual.getOutputBlockedTime()).isEqualTo(new Duration(102, NANOSECONDS)); - assertEquals(actual.getPhysicalWrittenDataSize(), DataSize.ofBytes(20)); + assertThat(actual.getPhysicalWrittenDataSize()).isEqualTo(DataSize.ofBytes(20)); - assertEquals(actual.getOperatorStats().size(), 1); + assertThat(actual.getOperatorStats().size()).isEqualTo(1); assertExpectedOperatorStats(actual.getOperatorStats().get(0)); } } diff --git a/core/trino-main/src/test/java/io/trino/operator/TestDynamicFilterSourceOperator.java b/core/trino-main/src/test/java/io/trino/operator/TestDynamicFilterSourceOperator.java index 0e5c486c22df..6609f88b2968 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestDynamicFilterSourceOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestDynamicFilterSourceOperator.java @@ -78,7 +78,6 @@ import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; @TestInstance(TestInstance.Lifecycle.PER_METHOD) public class TestDynamicFilterSourceOperator @@ -190,8 +189,8 @@ private void assertDynamicFilters( Operator operator = createOperator(operatorFactory); verifyPassthrough(operator, types, pages); operatorFactory.noMoreOperators(); - assertEquals(operator.getOperatorContext().getOperatorMemoryContext().getUserMemory(), 0); - assertEquals(partitions.build(), expectedTupleDomains); + assertThat(operator.getOperatorContext().getOperatorMemoryContext().getUserMemory()).isEqualTo(0); + assertThat(partitions.build()).isEqualTo(expectedTupleDomains); } @Test @@ -207,7 +206,7 @@ public void testCollectMultipleOperators() Operator op2 = createOperator(operatorFactory); // will finish after noMoreOperators() operatorFactory.noMoreOperators(); - assertEquals(partitions.build(), ImmutableList.of( + assertThat(partitions.build()).isEqualTo(ImmutableList.of( TupleDomain.withColumnDomains(ImmutableMap.of( new DynamicFilterId("0"), Domain.multipleValues(BIGINT, ImmutableList.of(1L, 2L, 3L, 5L)))))); @@ -216,7 +215,7 @@ public void testCollectMultipleOperators() new Page(createLongsBlock(2, 3)), new Page(createLongsBlock(1, 4))); - assertEquals(partitions.build(), ImmutableList.of( + assertThat(partitions.build()).isEqualTo(ImmutableList.of( TupleDomain.withColumnDomains(ImmutableMap.of( new DynamicFilterId("0"), Domain.multipleValues(BIGINT, ImmutableList.of(1L, 2L, 3L, 5L)))), TupleDomain.withColumnDomains(ImmutableMap.of( @@ -233,7 +232,7 @@ public void testCollectMultipleColumns() new Page(createBooleansBlock(false, 1), createDoublesBlock(4.5))); operatorFactory.noMoreOperators(); - assertEquals(partitions.build(), ImmutableList.of( + assertThat(partitions.build()).isEqualTo(ImmutableList.of( TupleDomain.withColumnDomains(ImmutableMap.of( new DynamicFilterId("0"), Domain.multipleValues(BOOLEAN, ImmutableList.of(true, false)), new DynamicFilterId("1"), Domain.multipleValues(DOUBLE, ImmutableList.of(1.5, 3.0, 4.5)))))); @@ -249,7 +248,7 @@ public void testCollectOnlyFirstColumn() new Page(createBooleansBlock(false, 1), createDoublesBlock(4.5))); operatorFactory.noMoreOperators(); - assertEquals(partitions.build(), ImmutableList.of( + assertThat(partitions.build()).isEqualTo(ImmutableList.of( TupleDomain.withColumnDomains(ImmutableMap.of( new DynamicFilterId("0"), Domain.multipleValues(BOOLEAN, ImmutableList.of(true, false)))))); } @@ -264,7 +263,7 @@ public void testCollectOnlyLastColumn() new Page(createBooleansBlock(false, 1), createDoublesBlock(4.5))); operatorFactory.noMoreOperators(); - assertEquals(partitions.build(), ImmutableList.of( + assertThat(partitions.build()).isEqualTo(ImmutableList.of( TupleDomain.withColumnDomains(ImmutableMap.of( new DynamicFilterId("1"), Domain.multipleValues(DOUBLE, ImmutableList.of(1.5, 3.0, 4.5)))))); } @@ -286,7 +285,7 @@ public void testCollectWithNulls() new Page(createIntsBlock(4, 5))); operatorFactory.noMoreOperators(); - assertEquals(partitions.build(), ImmutableList.of( + assertThat(partitions.build()).isEqualTo(ImmutableList.of( TupleDomain.withColumnDomains(ImmutableMap.of( new DynamicFilterId("0"), Domain.create(ValueSet.of(INTEGER, 1L, 2L, 3L, 4L, 5L), false))))); } @@ -304,7 +303,7 @@ public void testCollectWithDoubleNaN() new Page(input.build())); operatorFactory.noMoreOperators(); - assertEquals(partitions.build(), ImmutableList.of( + assertThat(partitions.build()).isEqualTo(ImmutableList.of( TupleDomain.withColumnDomains(ImmutableMap.of( new DynamicFilterId("0"), Domain.multipleValues(DOUBLE, ImmutableList.of(42.0)))))); } @@ -322,7 +321,7 @@ public void testCollectWithRealNaN() new Page(input.build())); operatorFactory.noMoreOperators(); - assertEquals(partitions.build(), ImmutableList.of( + assertThat(partitions.build()).isEqualTo(ImmutableList.of( TupleDomain.withColumnDomains(ImmutableMap.of( new DynamicFilterId("0"), Domain.multipleValues(REAL, ImmutableList.of((long) floatToRawIntBits(42.0f))))))); } @@ -391,7 +390,7 @@ public void testCollectNoFilters() ImmutableList.of(BIGINT), new Page(createLongsBlock(1, 2, 3))); operatorFactory.noMoreOperators(); - assertEquals(partitions.build(), ImmutableList.of(TupleDomain.all())); + assertThat(partitions.build()).isEqualTo(ImmutableList.of(TupleDomain.all())); } @Test @@ -401,7 +400,7 @@ public void testCollectEmptyBuildSide() verifyPassthrough(createOperator(operatorFactory), ImmutableList.of(BIGINT)); operatorFactory.noMoreOperators(); - assertEquals(partitions.build(), ImmutableList.of(TupleDomain.none())); + assertThat(partitions.build()).isEqualTo(ImmutableList.of(TupleDomain.none())); } @Test diff --git a/core/trino-main/src/test/java/io/trino/operator/TestExchangeOperator.java b/core/trino-main/src/test/java/io/trino/operator/TestExchangeOperator.java index ab3f7796a793..bc6ed291249d 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestExchangeOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestExchangeOperator.java @@ -58,11 +58,9 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.testing.TestingTaskContext.createTaskContext; import static java.util.concurrent.Executors.newScheduledThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -176,9 +174,9 @@ public void testWaitForClose() waitForPages(operator, 3); // verify state - assertEquals(operator.isFinished(), false); - assertEquals(operator.needsInput(), false); - assertEquals(operator.getOutput(), null); + assertThat(operator.isFinished()).isEqualTo(false); + assertThat(operator.needsInput()).isEqualTo(false); + assertThat(operator.getOutput()).isEqualTo(null); // add more pages and close the buffers taskBuffers.getUnchecked(TASK_1_ID).addPages(2, true); @@ -209,9 +207,9 @@ public void testWaitForNoMoreSplits() waitForPages(operator, 1); // verify state - assertEquals(operator.isFinished(), false); - assertEquals(operator.needsInput(), false); - assertEquals(operator.getOutput(), null); + assertThat(operator.isFinished()).isEqualTo(false); + assertThat(operator.needsInput()).isEqualTo(false); + assertThat(operator.getOutput()).isEqualTo(null); // add a buffer location operator.addSplit(newRemoteSplit(TASK_2_ID)); @@ -249,9 +247,9 @@ public void testFinish() waitForPages(operator, 3); // verify state - assertEquals(operator.isFinished(), false); - assertEquals(operator.needsInput(), false); - assertEquals(operator.getOutput(), null); + assertThat(operator.isFinished()).isEqualTo(false); + assertThat(operator.needsInput()).isEqualTo(false); + assertThat(operator.getOutput()).isEqualTo(null); // finish without closing buffers operator.finish(); @@ -298,10 +296,10 @@ private static List waitForPages(Operator operator, int expectedPageCount) } Thread.sleep(10); } - assertTrue(greaterThanZero); + assertThat(greaterThanZero).isTrue(); while (outputPages.size() < expectedPageCount && System.nanoTime() < endTime) { - assertEquals(operator.needsInput(), false); + assertThat(operator.needsInput()).isEqualTo(false); if (operator.isFinished()) { break; } @@ -319,11 +317,11 @@ private static List waitForPages(Operator operator, int expectedPageCount) Thread.sleep(10); // verify state - assertEquals(operator.needsInput(), false); - assertNull(operator.getOutput()); + assertThat(operator.needsInput()).isEqualTo(false); + assertThat(operator.getOutput()).isNull(); // verify pages - assertEquals(outputPages.size(), expectedPageCount); + assertThat(outputPages.size()).isEqualTo(expectedPageCount); for (Page page : outputPages) { assertPageEquals(TYPES, page, PAGE); } @@ -337,8 +335,8 @@ private static void waitForFinished(Operator operator) // wait for finished or until 10 seconds has passed long endTime = System.nanoTime() + TimeUnit.SECONDS.toNanos(10); while (System.nanoTime() - endTime < 0) { - assertEquals(operator.needsInput(), false); - assertNull(operator.getOutput()); + assertThat(operator.needsInput()).isEqualTo(false); + assertThat(operator.getOutput()).isNull(); if (operator.isFinished()) { break; } @@ -346,13 +344,13 @@ private static void waitForFinished(Operator operator) } // verify final state - assertEquals(operator.isFinished(), true); - assertEquals(operator.needsInput(), false); - assertNull(operator.getOutput()); + assertThat(operator.isFinished()).isEqualTo(true); + assertThat(operator.needsInput()).isEqualTo(false); + assertThat(operator.getOutput()).isNull(); operator.close(); operator.getOperatorContext().destroy(); - assertEquals(getOnlyElement(operator.getOperatorContext().getNestedOperatorStats()).getUserMemoryReservation().toBytes(), 0); + assertThat(getOnlyElement(operator.getOperatorContext().getNestedOperatorStats()).getUserMemoryReservation().toBytes()).isEqualTo(0); } } diff --git a/core/trino-main/src/test/java/io/trino/operator/TestGroupByHash.java b/core/trino-main/src/test/java/io/trino/operator/TestGroupByHash.java index a0becc743c18..e9a378a5a49d 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestGroupByHash.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestGroupByHash.java @@ -46,8 +46,6 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.type.TypeTestUtils.getHashBlock; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; public class TestGroupByHash { @@ -95,16 +93,16 @@ public void testAddPage() Page page = new Page(block, hashBlock); for (int addValuesTries = 0; addValuesTries < 10; addValuesTries++) { groupByHash.addPage(page).process(); - assertEquals(groupByHash.getGroupCount(), tries == 0 ? value + 1 : MAX_GROUP_ID); + assertThat(groupByHash.getGroupCount()).isEqualTo(tries == 0 ? value + 1 : MAX_GROUP_ID); // add the page again using get group ids and make sure the group count didn't change int[] groupIds = getGroupIds(groupByHash, page); - assertEquals(groupByHash.getGroupCount(), tries == 0 ? value + 1 : MAX_GROUP_ID); + assertThat(groupByHash.getGroupCount()).isEqualTo(tries == 0 ? value + 1 : MAX_GROUP_ID); // verify the first position - assertEquals(groupIds.length, 1); + assertThat(groupIds.length).isEqualTo(1); int groupId = groupIds[0]; - assertEquals(groupId, value); + assertThat(groupId).isEqualTo(value); } } } @@ -124,7 +122,7 @@ public void testRunLengthEncodedInputPage() groupByHash.addPage(page).process(); - assertEquals(groupByHash.getGroupCount(), 1); + assertThat(groupByHash.getGroupCount()).isEqualTo(1); Work work = groupByHash.getGroupIds(page); if (groupByHashType == GroupByHashType.FLAT) { @@ -136,10 +134,10 @@ public void testRunLengthEncodedInputPage() work.process(); int[] groupIds = work.getResult(); - assertEquals(groupByHash.getGroupCount(), 1); - assertEquals(groupIds.length, 2); - assertEquals(groupIds[0], 0); - assertEquals(groupIds[1], 0); + assertThat(groupByHash.getGroupCount()).isEqualTo(1); + assertThat(groupIds.length).isEqualTo(2); + assertThat(groupIds[0]).isEqualTo(0); + assertThat(groupIds[1]).isEqualTo(0); } } @@ -157,15 +155,15 @@ public void testDictionaryInputPage() groupByHash.addPage(page).process(); - assertEquals(groupByHash.getGroupCount(), 2); + assertThat(groupByHash.getGroupCount()).isEqualTo(2); int[] groupIds = getGroupIds(groupByHash, page); - assertEquals(groupByHash.getGroupCount(), 2); - assertEquals(groupIds.length, 4); - assertEquals(groupIds[0], 0); - assertEquals(groupIds[1], 0); - assertEquals(groupIds[2], 1); - assertEquals(groupIds[3], 1); + assertThat(groupByHash.getGroupCount()).isEqualTo(2); + assertThat(groupIds.length).isEqualTo(4); + assertThat(groupIds[0]).isEqualTo(0); + assertThat(groupIds[1]).isEqualTo(0); + assertThat(groupIds[2]).isEqualTo(1); + assertThat(groupIds[3]).isEqualTo(1); } } @@ -208,10 +206,10 @@ public void testGetGroupIds() Page page = new Page(block, hashBlock); for (int addValuesTries = 0; addValuesTries < 10; addValuesTries++) { int[] groupIds = getGroupIds(groupByHash, page); - assertEquals(groupByHash.getGroupCount(), tries == 0 ? value + 1 : MAX_GROUP_ID); - assertEquals(groupIds.length, 1); + assertThat(groupByHash.getGroupCount()).isEqualTo(tries == 0 ? value + 1 : MAX_GROUP_ID); + assertThat(groupIds.length).isEqualTo(1); long groupId = groupIds[0]; - assertEquals(groupId, value); + assertThat(groupId).isEqualTo(value); } } } @@ -228,9 +226,9 @@ public void testAppendTo() int[] groupIds = getGroupIds(groupByHash, new Page(valuesBlock, hashBlock)); for (int i = 0; i < valuesBlock.getPositionCount(); i++) { - assertEquals(groupIds[i], i); + assertThat(groupIds[i]).isEqualTo(i); } - assertEquals(groupByHash.getGroupCount(), 100); + assertThat(groupByHash.getGroupCount()).isEqualTo(100); PageBuilder pageBuilder = new PageBuilder(ImmutableList.of(BIGINT, BIGINT)); for (int i = 0; i < groupByHash.getGroupCount(); i++) { @@ -240,9 +238,9 @@ public void testAppendTo() Page page = pageBuilder.build(); // Ensure that all blocks have the same positionCount for (int i = 0; i < page.getChannelCount(); i++) { - assertEquals(page.getBlock(i).getPositionCount(), 100); + assertThat(page.getBlock(i).getPositionCount()).isEqualTo(100); } - assertEquals(page.getPositionCount(), 100); + assertThat(page.getPositionCount()).isEqualTo(100); BlockAssertions.assertBlockEquals(BIGINT, page.getBlock(0), valuesBlock); BlockAssertions.assertBlockEquals(BIGINT, page.getBlock(1), hashBlock); } @@ -261,7 +259,7 @@ public void testAppendToMultipleTuplesPerGroup() GroupByHash groupByHash = groupByHashType.createGroupByHash(); groupByHash.getGroupIds(new Page(valuesBlock, hashBlock)).process(); - assertEquals(groupByHash.getGroupCount(), 50); + assertThat(groupByHash.getGroupCount()).isEqualTo(50); PageBuilder pageBuilder = new PageBuilder(ImmutableList.of(BIGINT, BIGINT)); for (int i = 0; i < groupByHash.getGroupCount(); i++) { @@ -269,7 +267,7 @@ public void testAppendToMultipleTuplesPerGroup() groupByHash.appendValuesTo(i, pageBuilder); } Page outputPage = pageBuilder.build(); - assertEquals(outputPage.getPositionCount(), 50); + assertThat(outputPage.getPositionCount()).isEqualTo(50); BlockAssertions.assertBlockEquals(BIGINT, outputPage.getBlock(0), createLongSequenceBlock(0, 50)); } } @@ -312,7 +310,7 @@ public void testUpdateMemoryVarchar() groupByHash.addPage(new Page(valuesBlock, hashBlock)).process(); // assert we call update memory twice every time we rehash; the rehash count = log2(length / FILL_RATIO) - assertEquals(rehashCount.get(), 2 * VARCHAR_EXPECTED_REHASH); + assertThat(rehashCount.get()).isEqualTo(2 * VARCHAR_EXPECTED_REHASH); } @Test @@ -333,7 +331,7 @@ public void testUpdateMemoryBigint() groupByHash.addPage(new Page(valuesBlock, hashBlock)).process(); // assert we call update memory twice every time we rehash; the rehash count = log2(length / FILL_RATIO) - assertEquals(rehashCount.get(), 2 * BIGINT_EXPECTED_REHASH); + assertThat(rehashCount.get()).isEqualTo(2 * BIGINT_EXPECTED_REHASH); } @Test @@ -367,21 +365,21 @@ private static void testMemoryReservationYield(Type type, Block valuesBlock, int while (!finish) { finish = addPageWork.process(); if (!finish) { - assertEquals(currentQuota.get(), allowedQuota.get()); + assertThat(currentQuota.get()).isEqualTo(allowedQuota.get()); // assert if we are blocked, we are going to be blocked again without changing allowedQuota - assertFalse(addPageWork.process()); - assertEquals(currentQuota.get(), allowedQuota.get()); + assertThat(addPageWork.process()).isFalse(); + assertThat(currentQuota.get()).isEqualTo(allowedQuota.get()); yields++; allowedQuota.getAndAdd(6); } } // assert there is not anything missing - assertEquals(length, groupByHash.getGroupCount()); + assertThat(length).isEqualTo(groupByHash.getGroupCount()); // assert we yield for every 3 rehashes // currentQuota is essentially the count we have successfully rehashed multiplied by 2 (as updateMemory is called twice per rehash) - assertEquals(currentQuota.get(), 2 * expectedRehash); - assertEquals(currentQuota.get() / 3 / 2, yields); + assertThat(currentQuota.get()).isEqualTo(2 * expectedRehash); + assertThat(currentQuota.get() / 3 / 2).isEqualTo(yields); // test getGroupIds currentQuota.set(0); @@ -394,20 +392,20 @@ private static void testMemoryReservationYield(Type type, Block valuesBlock, int while (!finish) { finish = getGroupIdsWork.process(); if (!finish) { - assertEquals(currentQuota.get(), allowedQuota.get()); + assertThat(currentQuota.get()).isEqualTo(allowedQuota.get()); // assert if we are blocked, we are going to be blocked again without changing allowedQuota - assertFalse(getGroupIdsWork.process()); - assertEquals(currentQuota.get(), allowedQuota.get()); + assertThat(getGroupIdsWork.process()).isFalse(); + assertThat(currentQuota.get()).isEqualTo(allowedQuota.get()); yields++; allowedQuota.getAndAdd(6); } } // assert there is not anything missing - assertEquals(length, groupByHash.getGroupCount()); - assertEquals(length, getGroupIdsWork.getResult().length); + assertThat(length).isEqualTo(groupByHash.getGroupCount()); + assertThat(length).isEqualTo(getGroupIdsWork.getResult().length); // rehash count is the same as above - assertEquals(currentQuota.get(), 2 * expectedRehash); - assertEquals(currentQuota.get() / 3 / 2, yields); + assertThat(currentQuota.get()).isEqualTo(2 * expectedRehash); + assertThat(currentQuota.get() / 3 / 2).isEqualTo(yields); } @Test @@ -440,22 +438,22 @@ public void testMemoryReservationYieldWithDictionary() while (!finish) { finish = addPageWork.process(); if (!finish) { - assertEquals(currentQuota.get(), allowedQuota.get()); + assertThat(currentQuota.get()).isEqualTo(allowedQuota.get()); // assert if we are blocked, we are going to be blocked again without changing allowedQuota - assertFalse(addPageWork.process()); - assertEquals(currentQuota.get(), allowedQuota.get()); + assertThat(addPageWork.process()).isFalse(); + assertThat(currentQuota.get()).isEqualTo(allowedQuota.get()); yields++; allowedQuota.getAndAdd(6); } } // assert there is not anything missing - assertEquals(dictionaryLength, groupByHash.getGroupCount()); + assertThat(dictionaryLength).isEqualTo(groupByHash.getGroupCount()); // assert we yield for every 3 rehashes // currentQuota is essentially the count we have successfully rehashed multiplied by 2 (as updateMemory is called twice per rehash) // the rehash count is 10 = log(1_000 / 0.75) - assertEquals(currentQuota.get(), 2 * (groupByHashType == GroupByHashType.FLAT ? 4 : 13)); - assertEquals(currentQuota.get() / 3 / 2, yields); + assertThat(currentQuota.get()).isEqualTo(2 * (groupByHashType == GroupByHashType.FLAT ? 4 : 13)); + assertThat(currentQuota.get() / 3 / 2).isEqualTo(yields); // test getGroupIds currentQuota.set(0); @@ -468,23 +466,23 @@ public void testMemoryReservationYieldWithDictionary() while (!finish) { finish = getGroupIdsWork.process(); if (!finish) { - assertEquals(currentQuota.get(), allowedQuota.get()); + assertThat(currentQuota.get()).isEqualTo(allowedQuota.get()); // assert if we are blocked, we are going to be blocked again without changing allowedQuota - assertFalse(getGroupIdsWork.process()); - assertEquals(currentQuota.get(), allowedQuota.get()); + assertThat(getGroupIdsWork.process()).isFalse(); + assertThat(currentQuota.get()).isEqualTo(allowedQuota.get()); yields++; allowedQuota.getAndAdd(6); } } // assert there is not anything missing - assertEquals(dictionaryLength, groupByHash.getGroupCount()); - assertEquals(dictionaryLength, getGroupIdsWork.getResult().length); + assertThat(dictionaryLength).isEqualTo(groupByHash.getGroupCount()); + assertThat(dictionaryLength).isEqualTo(getGroupIdsWork.getResult().length); // assert we yield for every 3 rehashes // currentQuota is essentially the count we have successfully rehashed multiplied by 2 (as updateMemory is called twice per rehash) // the rehash count is 10 = log2(1_000 / 0.75) - assertEquals(currentQuota.get(), 2 * (groupByHashType == GroupByHashType.FLAT ? 4 : 13)); - assertEquals(currentQuota.get() / 3 / 2, yields); + assertThat(currentQuota.get()).isEqualTo(2 * (groupByHashType == GroupByHashType.FLAT ? 4 : 13)); + assertThat(currentQuota.get() / 3 / 2).isEqualTo(yields); } } diff --git a/core/trino-main/src/test/java/io/trino/operator/TestGroupedTopNRankAccumulator.java b/core/trino-main/src/test/java/io/trino/operator/TestGroupedTopNRankAccumulator.java index 53b40af7baad..7cf8f1f114f5 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestGroupedTopNRankAccumulator.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestGroupedTopNRankAccumulator.java @@ -24,8 +24,7 @@ import static com.google.common.collect.Lists.cartesianProduct; import static java.lang.Math.min; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestGroupedTopNRankAccumulator { @@ -75,11 +74,11 @@ public void testSinglePeerGroupInsert(int topN, long valueCount, long groupCount for (int i = 0; i < valueCount; i++) { for (int groupId = 0; groupId < groupCount; groupId++) { - assertTrue(accumulator.add(groupId, toRowReference(rowId))); + assertThat(accumulator.add(groupId, toRowReference(rowId))).isTrue(); accumulator.verifyIntegrity(); // No evictions because rank does not change for the same input - assertTrue(evicted.isEmpty()); + assertThat(evicted.isEmpty()).isTrue(); } } @@ -87,18 +86,18 @@ public void testSinglePeerGroupInsert(int topN, long valueCount, long groupCount LongBigArray rowIdOutput = new LongBigArray(); LongBigArray rankingOutput = new LongBigArray(); if (drainWithRanking) { - assertEquals(accumulator.drainTo(groupId, rowIdOutput, rankingOutput), valueCount); + assertThat(accumulator.drainTo(groupId, rowIdOutput, rankingOutput)).isEqualTo(valueCount); } else { - assertEquals(accumulator.drainTo(groupId, rowIdOutput), valueCount); + assertThat(accumulator.drainTo(groupId, rowIdOutput)).isEqualTo(valueCount); } accumulator.verifyIntegrity(); for (int i = 0; i < valueCount; i++) { - assertEquals(rowIdOutput.get(i), rowId); + assertThat(rowIdOutput.get(i)).isEqualTo(rowId); if (drainWithRanking) { // Everything should have a rank of 1 - assertEquals(rankingOutput.get(i), 1); + assertThat(rankingOutput.get(i)).isEqualTo(1); } } } @@ -114,11 +113,11 @@ public void testIncreasingAllUniqueValues(int topN, long valueCount, long groupC for (int rowId = 0; rowId < valueCount; rowId++) { for (int groupId = 0; groupId < groupCount; groupId++) { // Since rowIds are in increasing order, only the first topN will be accepted - assertEquals(accumulator.add(groupId, toRowReference(rowId)), rowId < topN); + assertThat(accumulator.add(groupId, toRowReference(rowId))).isEqualTo(rowId < topN); accumulator.verifyIntegrity(); // No evictions because all results should be rejected at add() - assertTrue(evicted.isEmpty()); + assertThat(evicted.isEmpty()).isTrue(); } } @@ -127,19 +126,19 @@ public void testIncreasingAllUniqueValues(int topN, long valueCount, long groupC LongBigArray rowIdOutput = new LongBigArray(); LongBigArray rankingOutput = new LongBigArray(); if (drainWithRanking) { - assertEquals(accumulator.drainTo(groupId, rowIdOutput, rankingOutput), expectedResultCount); + assertThat(accumulator.drainTo(groupId, rowIdOutput, rankingOutput)).isEqualTo(expectedResultCount); } else { - assertEquals(accumulator.drainTo(groupId, rowIdOutput), expectedResultCount); + assertThat(accumulator.drainTo(groupId, rowIdOutput)).isEqualTo(expectedResultCount); } accumulator.verifyIntegrity(); for (int rowId = 0; rowId < expectedResultCount; rowId++) { // The rowId is simultaneously the index - assertEquals(rowIdOutput.get(rowId), rowId); + assertThat(rowIdOutput.get(rowId)).isEqualTo(rowId); if (drainWithRanking) { // Results should have a rank of rowId + 1 - assertEquals(rankingOutput.get(rowId), rowId + 1); + assertThat(rankingOutput.get(rowId)).isEqualTo(rowId + 1); } } } @@ -156,7 +155,7 @@ public void testDecreasingAllUniqueValues(int topN, long valueCount, long groupC for (long rowId = valueCount - 1; rowId >= 0; rowId--) { for (int groupId = 0; groupId < groupCount; groupId++) { // Since rowIds are in decreasing order, new rowIds will always be accepted, potentially evicting older rows - assertTrue(accumulator.add(groupId, toRowReference(rowId))); + assertThat(accumulator.add(groupId, toRowReference(rowId))).isTrue(); accumulator.verifyIntegrity(); if (rowId >= topN) { @@ -166,26 +165,26 @@ public void testDecreasingAllUniqueValues(int topN, long valueCount, long groupC } // The largest elements should be evicted - assertEquals(evicted, expectedEvicted); + assertThat(evicted).isEqualTo(expectedEvicted); for (int groupId = 0; groupId < groupCount; groupId++) { LongBigArray rowIdOutput = new LongBigArray(); LongBigArray rankingOutput = new LongBigArray(); long expectedResultCount = min(valueCount, topN); if (drainWithRanking) { - assertEquals(accumulator.drainTo(groupId, rowIdOutput, rankingOutput), expectedResultCount); + assertThat(accumulator.drainTo(groupId, rowIdOutput, rankingOutput)).isEqualTo(expectedResultCount); } else { - assertEquals(accumulator.drainTo(groupId, rowIdOutput), expectedResultCount); + assertThat(accumulator.drainTo(groupId, rowIdOutput)).isEqualTo(expectedResultCount); } accumulator.verifyIntegrity(); for (int rowId = 0; rowId < expectedResultCount; rowId++) { // The rowId is simultaneously the index - assertEquals(rowIdOutput.get(rowId), rowId); + assertThat(rowIdOutput.get(rowId)).isEqualTo(rowId); if (drainWithRanking) { // Results should have a rank of rowId + 1 - assertEquals(rankingOutput.get(rowId), rowId + 1); + assertThat(rankingOutput.get(rowId)).isEqualTo(rowId + 1); } } } @@ -201,55 +200,55 @@ public void testMultipleDuplicateValues() accumulator.verifyIntegrity(); // Add rowId 0 - assertTrue(accumulator.add(0, toRowReference(0))); + assertThat(accumulator.add(0, toRowReference(0))).isTrue(); accumulator.verifyIntegrity(); - assertTrue(evicted.isEmpty()); + assertThat(evicted.isEmpty()).isTrue(); // Add rowId 1 - assertTrue(accumulator.add(0, toRowReference(1))); + assertThat(accumulator.add(0, toRowReference(1))).isTrue(); accumulator.verifyIntegrity(); - assertTrue(evicted.isEmpty()); + assertThat(evicted.isEmpty()).isTrue(); // Add rowId 0 again, putting rowId 1 at effective rank of 3 - assertTrue(accumulator.add(0, toRowReference(0))); + assertThat(accumulator.add(0, toRowReference(0))).isTrue(); accumulator.verifyIntegrity(); - assertTrue(evicted.isEmpty()); + assertThat(evicted.isEmpty()).isTrue(); // Add rowId 1 again, but rowId 1 should still have an effective rank of 3 - assertTrue(accumulator.add(0, toRowReference(1))); + assertThat(accumulator.add(0, toRowReference(1))).isTrue(); accumulator.verifyIntegrity(); - assertTrue(evicted.isEmpty()); + assertThat(evicted.isEmpty()).isTrue(); // Add rowId 0 again, which should force both values of rowId1 to be evicted - assertTrue(accumulator.add(0, toRowReference(0))); + assertThat(accumulator.add(0, toRowReference(0))).isTrue(); accumulator.verifyIntegrity(); - assertEquals(evicted, Arrays.asList(1L, 1L)); + assertThat(evicted).isEqualTo(Arrays.asList(1L, 1L)); // Add rowId -1, putting rowId 0 at rank 2 - assertTrue(accumulator.add(0, toRowReference(-1))); + assertThat(accumulator.add(0, toRowReference(-1))).isTrue(); accumulator.verifyIntegrity(); - assertEquals(evicted, Arrays.asList(1L, 1L)); + assertThat(evicted).isEqualTo(Arrays.asList(1L, 1L)); // Add rowId -1 again, putting rowId 0 at rank 3 - assertTrue(accumulator.add(0, toRowReference(-1))); + assertThat(accumulator.add(0, toRowReference(-1))).isTrue(); accumulator.verifyIntegrity(); - assertEquals(evicted, Arrays.asList(1L, 1L)); + assertThat(evicted).isEqualTo(Arrays.asList(1L, 1L)); // Drain LongBigArray rowIdOutput = new LongBigArray(); LongBigArray rankingOutput = new LongBigArray(); - assertEquals(accumulator.drainTo(0, rowIdOutput, rankingOutput), 5); - - assertEquals(rowIdOutput.get(0), -1); - assertEquals(rankingOutput.get(0), 1); - assertEquals(rowIdOutput.get(1), -1); - assertEquals(rankingOutput.get(1), 1); - assertEquals(rowIdOutput.get(2), 0); - assertEquals(rankingOutput.get(2), 3); - assertEquals(rowIdOutput.get(3), 0); - assertEquals(rankingOutput.get(3), 3); - assertEquals(rowIdOutput.get(4), 0); - assertEquals(rankingOutput.get(4), 3); + assertThat(accumulator.drainTo(0, rowIdOutput, rankingOutput)).isEqualTo(5); + + assertThat(rowIdOutput.get(0)).isEqualTo(-1); + assertThat(rankingOutput.get(0)).isEqualTo(1); + assertThat(rowIdOutput.get(1)).isEqualTo(-1); + assertThat(rankingOutput.get(1)).isEqualTo(1); + assertThat(rowIdOutput.get(2)).isEqualTo(0); + assertThat(rankingOutput.get(2)).isEqualTo(3); + assertThat(rowIdOutput.get(3)).isEqualTo(0); + assertThat(rankingOutput.get(3)).isEqualTo(3); + assertThat(rowIdOutput.get(4)).isEqualTo(0); + assertThat(rankingOutput.get(4)).isEqualTo(3); } private static RowReference toRowReference(long rowId) diff --git a/core/trino-main/src/test/java/io/trino/operator/TestGroupedTopNRankBuilder.java b/core/trino-main/src/test/java/io/trino/operator/TestGroupedTopNRankBuilder.java index f095001e38f8..5b5a67a8b7d5 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestGroupedTopNRankBuilder.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestGroupedTopNRankBuilder.java @@ -35,9 +35,7 @@ import static io.trino.spi.connector.SortOrder.ASC_NULLS_LAST; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.DoubleType.DOUBLE; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestGroupedTopNRankBuilder { @@ -73,7 +71,7 @@ public long hashCode(Page page, int position) false, new int[0], new NoChannelGroupByHash()); - assertFalse(groupedTopNBuilder.buildResult().hasNext()); + assertThat(groupedTopNBuilder.buildResult().hasNext()).isFalse(); } @Test(dataProvider = "produceRanking") @@ -93,32 +91,32 @@ public void testSingleGroupTopN(boolean produceRanking) new NoChannelGroupByHash()); // Expected effect: [0.2 x 1 => rank=1, 0.3 x 2 => rank=2] - assertTrue(groupedTopNBuilder.processPage( + assertThat(groupedTopNBuilder.processPage( rowPageBuilder(types) .row(0.3) .row(0.3) .row(0.2) - .build()).process()); + .build()).process()).isTrue(); // Page should be dropped, because single value 0.4 is too large to be considered - assertTrue(groupedTopNBuilder.processPage( + assertThat(groupedTopNBuilder.processPage( rowPageBuilder(types) .row(0.4) - .build()).process()); + .build()).process()).isTrue(); // Next page should cause 0.3 values to be evicted (first page will be compacted) // Expected effect: [0.1 x 2 => rank 1, 0.2 x 3 => rank 3] - assertTrue(groupedTopNBuilder.processPage( + assertThat(groupedTopNBuilder.processPage( rowPageBuilder(types) .row(0.1) .row(0.2) .row(0.3) .row(0.2) .row(0.1) - .build()).process()); + .build()).process()).isTrue(); List output = ImmutableList.copyOf(groupedTopNBuilder.buildResult()); - assertEquals(output.size(), 1); + assertThat(output.size()).isEqualTo(1); List outputTypes = ImmutableList.of(DOUBLE, BIGINT); Page expected = rowPageBuilder(outputTypes) @@ -155,27 +153,27 @@ public void testMultiGroupTopN(boolean produceRanking) // Expected effect: // Group 0 [0.2 x 1 => rank=1, 0.3 x 3 => rank=2] // Group 1 [0.2 x 1 => rank=1] - assertTrue(groupedTopNBuilder.processPage( + assertThat(groupedTopNBuilder.processPage( rowPageBuilder(types) .row(0L, 0.3) .row(0L, 0.3) .row(0L, 0.3) .row(0L, 0.2) .row(1L, 0.2) - .build()).process()); + .build()).process()).isTrue(); // Page should be dropped, because all values too large to be considered - assertTrue(groupedTopNBuilder.processPage( + assertThat(groupedTopNBuilder.processPage( rowPageBuilder(types) .row(0L, 0.4) .row(1L, 0.4) - .build()).process()); + .build()).process()).isTrue(); // Next page should cause evict 0.3 from group 0, which should cause the first page to be compacted // Expected effect: // Group 0 [0.1 x 1 => rank=1, 0.2 x 2 => rank=2] // Group 1 [0.2 x 2 => rank=1, 0.3 x 2 => rank=3] - assertTrue(groupedTopNBuilder.processPage( + assertThat(groupedTopNBuilder.processPage( rowPageBuilder(types) .row(0L, 0.1) .row(1L, 0.2) @@ -185,10 +183,10 @@ public void testMultiGroupTopN(boolean produceRanking) .row(1L, 0.4) .row(1L, 0.3) .row(1L, 0.3) - .build()).process()); + .build()).process()).isTrue(); List output = ImmutableList.copyOf(groupedTopNBuilder.buildResult()); - assertEquals(output.size(), 1); + assertThat(output.size()).isEqualTo(1); List outputTypes = ImmutableList.of(BIGINT, DOUBLE, BIGINT); Page expected = rowPageBuilder(outputTypes) @@ -235,12 +233,12 @@ public void testYield() groupByHash); Work work = groupedTopNBuilder.processPage(input); - assertFalse(work.process()); - assertFalse(work.process()); + assertThat(work.process()).isFalse(); + assertThat(work.process()).isFalse(); unblock.set(true); - assertTrue(work.process()); + assertThat(work.process()).isTrue(); List output = ImmutableList.copyOf(groupedTopNBuilder.buildResult()); - assertEquals(output.size(), 1); + assertThat(output.size()).isEqualTo(1); Page expected = rowPagesBuilder(types) .row(1L, 0.1) diff --git a/core/trino-main/src/test/java/io/trino/operator/TestGroupedTopNRowNumberAccumulator.java b/core/trino-main/src/test/java/io/trino/operator/TestGroupedTopNRowNumberAccumulator.java index 2b65b42f0694..2437f4cc208a 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestGroupedTopNRowNumberAccumulator.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestGroupedTopNRowNumberAccumulator.java @@ -22,9 +22,7 @@ import java.util.List; import java.util.Set; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestGroupedTopNRowNumberAccumulator { @@ -40,29 +38,29 @@ public void testSingleGroupTopN1() // Add one row to fill the group rowReference.setRowId(0); - assertTrue(accumulator.add(0, rowReference)); + assertThat(accumulator.add(0, rowReference)).isTrue(); accumulator.verifyIntegrity(); - assertTrue(rowReference.isRowIdExtracted()); - assertTrue(evicted.isEmpty()); + assertThat(rowReference.isRowIdExtracted()).isTrue(); + assertThat(evicted.isEmpty()).isTrue(); // Add a row which should be ignored because it is not in the topN and group is full rowReference.setRowId(1); - assertFalse(accumulator.add(0, rowReference)); + assertThat(accumulator.add(0, rowReference)).isFalse(); accumulator.verifyIntegrity(); - assertFalse(rowReference.isRowIdExtracted()); - assertTrue(evicted.isEmpty()); + assertThat(rowReference.isRowIdExtracted()).isFalse(); + assertThat(evicted.isEmpty()).isTrue(); // Add a row which should replace the existing buffered row rowReference.setRowId(-1); - assertTrue(accumulator.add(0, rowReference)); + assertThat(accumulator.add(0, rowReference)).isTrue(); accumulator.verifyIntegrity(); - assertTrue(rowReference.isRowIdExtracted()); - assertEquals(evicted, Arrays.asList(0L)); + assertThat(rowReference.isRowIdExtracted()).isTrue(); + assertThat(evicted).isEqualTo(Arrays.asList(0L)); LongBigArray rowIdOutput = new LongBigArray(); - assertEquals(accumulator.drainTo(0, rowIdOutput), 1); + assertThat(accumulator.drainTo(0, rowIdOutput)).isEqualTo(1); accumulator.verifyIntegrity(); - assertEquals(rowIdOutput.get(0), -1); + assertThat(rowIdOutput.get(0)).isEqualTo(-1); } @Test @@ -77,44 +75,44 @@ public void testSingleGroupTopN2() // Add one row to the group rowReference.setRowId(0); - assertTrue(accumulator.add(0, rowReference)); + assertThat(accumulator.add(0, rowReference)).isTrue(); accumulator.verifyIntegrity(); - assertTrue(rowReference.isRowIdExtracted()); - assertTrue(evicted.isEmpty()); + assertThat(rowReference.isRowIdExtracted()).isTrue(); + assertThat(evicted.isEmpty()).isTrue(); // Add another row to fill the group rowReference.setRowId(1); - assertTrue(accumulator.add(0, rowReference)); + assertThat(accumulator.add(0, rowReference)).isTrue(); accumulator.verifyIntegrity(); - assertTrue(rowReference.isRowIdExtracted()); - assertTrue(evicted.isEmpty()); + assertThat(rowReference.isRowIdExtracted()).isTrue(); + assertThat(evicted.isEmpty()).isTrue(); // Add a row which should be ignored because it is not in the topN and group is full rowReference.setRowId(2); - assertFalse(accumulator.add(0, rowReference)); + assertThat(accumulator.add(0, rowReference)).isFalse(); accumulator.verifyIntegrity(); - assertFalse(rowReference.isRowIdExtracted()); - assertTrue(evicted.isEmpty()); + assertThat(rowReference.isRowIdExtracted()).isFalse(); + assertThat(evicted.isEmpty()).isTrue(); // Add a row which should replace the leaf of the heap rowReference.setRowId(-2); - assertTrue(accumulator.add(0, rowReference)); + assertThat(accumulator.add(0, rowReference)).isTrue(); accumulator.verifyIntegrity(); - assertTrue(rowReference.isRowIdExtracted()); - assertEquals(evicted, Arrays.asList(1L)); + assertThat(rowReference.isRowIdExtracted()).isTrue(); + assertThat(evicted).isEqualTo(Arrays.asList(1L)); // Add a row which should replace the root of the heap rowReference.setRowId(-1); - assertTrue(accumulator.add(0, rowReference)); + assertThat(accumulator.add(0, rowReference)).isTrue(); accumulator.verifyIntegrity(); - assertTrue(rowReference.isRowIdExtracted()); - assertEquals(evicted, Arrays.asList(1L, 0L)); + assertThat(rowReference.isRowIdExtracted()).isTrue(); + assertThat(evicted).isEqualTo(Arrays.asList(1L, 0L)); LongBigArray rowIdOutput = new LongBigArray(); - assertEquals(accumulator.drainTo(0, rowIdOutput), 2); + assertThat(accumulator.drainTo(0, rowIdOutput)).isEqualTo(2); accumulator.verifyIntegrity(); - assertEquals(rowIdOutput.get(0), -2); - assertEquals(rowIdOutput.get(1), -1); + assertThat(rowIdOutput.get(0)).isEqualTo(-2); + assertThat(rowIdOutput.get(1)).isEqualTo(-1); } @Test @@ -128,15 +126,15 @@ public void testSingleGroupTopN2PartialFill() // Add 1 row to partially fill the top N of 2 before draining TestingRowReference rowReference = new TestingRowReference(); rowReference.setRowId(0); - assertTrue(accumulator.add(0, rowReference)); + assertThat(accumulator.add(0, rowReference)).isTrue(); accumulator.verifyIntegrity(); - assertTrue(rowReference.isRowIdExtracted()); - assertTrue(evicted.isEmpty()); + assertThat(rowReference.isRowIdExtracted()).isTrue(); + assertThat(evicted.isEmpty()).isTrue(); LongBigArray rowIdOutput = new LongBigArray(); - assertEquals(accumulator.drainTo(0, rowIdOutput), 1); + assertThat(accumulator.drainTo(0, rowIdOutput)).isEqualTo(1); accumulator.verifyIntegrity(); - assertEquals(rowIdOutput.get(0), 0); + assertThat(rowIdOutput.get(0)).isEqualTo(0); } @Test @@ -150,19 +148,19 @@ public void testSingleGroupTopN4PartialFill() // Add 2 rows to partially fill the top N of 4 before draining TestingRowReference rowReference = new TestingRowReference(); rowReference.setRowId(0); - assertTrue(accumulator.add(0, rowReference)); + assertThat(accumulator.add(0, rowReference)).isTrue(); accumulator.verifyIntegrity(); rowReference.setRowId(1); - assertTrue(accumulator.add(0, rowReference)); + assertThat(accumulator.add(0, rowReference)).isTrue(); accumulator.verifyIntegrity(); - assertTrue(rowReference.isRowIdExtracted()); - assertTrue(evicted.isEmpty()); + assertThat(rowReference.isRowIdExtracted()).isTrue(); + assertThat(evicted.isEmpty()).isTrue(); LongBigArray rowIdOutput = new LongBigArray(); - assertEquals(accumulator.drainTo(0, rowIdOutput), 2); + assertThat(accumulator.drainTo(0, rowIdOutput)).isEqualTo(2); accumulator.verifyIntegrity(); - assertEquals(rowIdOutput.get(0), 0); - assertEquals(rowIdOutput.get(1), 1); + assertThat(rowIdOutput.get(0)).isEqualTo(0); + assertThat(rowIdOutput.get(1)).isEqualTo(1); } @Test @@ -181,10 +179,10 @@ public void testMultipleGroups() for (int i = bulkInsertionCount; i < bulkInsertionCount * 2; i++) { rowReference.setRowId(i); int groupId = i % groupCount; - assertTrue(accumulator.add(groupId, rowReference)); + assertThat(accumulator.add(groupId, rowReference)).isTrue(); accumulator.verifyIntegrity(); - assertTrue(rowReference.isRowIdExtracted()); - assertTrue(evicted.isEmpty()); + assertThat(rowReference.isRowIdExtracted()).isTrue(); + assertThat(evicted.isEmpty()).isTrue(); firstInsertionBatch.add((long) i); } @@ -192,31 +190,31 @@ public void testMultipleGroups() for (int i = bulkInsertionCount * 2; i < bulkInsertionCount * 3; i++) { rowReference.setRowId(i); int groupId = i % groupCount; - assertFalse(accumulator.add(groupId, rowReference)); + assertThat(accumulator.add(groupId, rowReference)).isFalse(); accumulator.verifyIntegrity(); - assertFalse(rowReference.isRowIdExtracted()); - assertTrue(evicted.isEmpty()); + assertThat(rowReference.isRowIdExtracted()).isFalse(); + assertThat(evicted.isEmpty()).isTrue(); } // Add monotonically decreasing smaller elements to force every group to be fully replaced for (int i = bulkInsertionCount - 1; i >= 0; i--) { rowReference.setRowId(i); int groupId = i % groupCount; - assertTrue(accumulator.add(groupId, rowReference)); + assertThat(accumulator.add(groupId, rowReference)).isTrue(); accumulator.verifyIntegrity(); - assertTrue(rowReference.isRowIdExtracted()); + assertThat(rowReference.isRowIdExtracted()).isTrue(); } // Everything from the first insertion batch should now be evicted. - assertEquals(evicted, firstInsertionBatch); + assertThat(evicted).isEqualTo(firstInsertionBatch); // Verify that draining produces the expected data in sorted order LongBigArray rowIdOutput = new LongBigArray(); for (int i = 0; i < groupCount; i++) { - assertEquals(accumulator.drainTo(i, rowIdOutput), topN); + assertThat(accumulator.drainTo(i, rowIdOutput)).isEqualTo(topN); accumulator.verifyIntegrity(); for (int j = 0; j < topN; j++) { - assertEquals(rowIdOutput.get(j), j * groupCount + i); + assertThat(rowIdOutput.get(j)).isEqualTo(j * groupCount + i); } } } @@ -236,7 +234,7 @@ public void testEmptyDrain() accumulator.verifyIntegrity(); LongBigArray rowIdOutput = new LongBigArray(); - assertEquals(accumulator.drainTo(0, rowIdOutput), 0); + assertThat(accumulator.drainTo(0, rowIdOutput)).isEqualTo(0); accumulator.verifyIntegrity(); } diff --git a/core/trino-main/src/test/java/io/trino/operator/TestGroupedTopNRowNumberBuilder.java b/core/trino-main/src/test/java/io/trino/operator/TestGroupedTopNRowNumberBuilder.java index 8c0cbcce633d..06d1acce7219 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestGroupedTopNRowNumberBuilder.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestGroupedTopNRowNumberBuilder.java @@ -30,9 +30,7 @@ import static io.trino.spi.connector.SortOrder.ASC_NULLS_LAST; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.DoubleType.DOUBLE; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestGroupedTopNRowNumberBuilder { @@ -63,7 +61,7 @@ public void testEmptyInput() false, new int[0], new NoChannelGroupByHash()); - assertFalse(groupedTopNBuilder.buildResult().hasNext()); + assertThat(groupedTopNBuilder.buildResult().hasNext()).isFalse(); } @Test(dataProvider = "produceRowNumbers") @@ -101,19 +99,19 @@ public void testMultiGroupTopN(boolean produceRowNumbers) groupByHash); // add 4 rows for the first page and created three heaps with 1, 1, 2 rows respectively - assertTrue(groupedTopNBuilder.processPage(input.get(0)).process()); + assertThat(groupedTopNBuilder.processPage(input.get(0)).process()).isTrue(); // add 1 row for the second page and the three heaps become 2, 1, 2 rows respectively - assertTrue(groupedTopNBuilder.processPage(input.get(1)).process()); + assertThat(groupedTopNBuilder.processPage(input.get(1)).process()).isTrue(); // add 2 new rows for the third page (which will be compacted into two rows only) and we have four heaps with 2, 2, 2, 1 rows respectively - assertTrue(groupedTopNBuilder.processPage(input.get(2)).process()); + assertThat(groupedTopNBuilder.processPage(input.get(2)).process()).isTrue(); // the last page will be discarded - assertTrue(groupedTopNBuilder.processPage(input.get(3)).process()); + assertThat(groupedTopNBuilder.processPage(input.get(3)).process()).isTrue(); List output = ImmutableList.copyOf(groupedTopNBuilder.buildResult()); - assertEquals(output.size(), 1); + assertThat(output.size()).isEqualTo(1); Page expected = rowPagesBuilder(BIGINT, DOUBLE, BIGINT) .row(1L, 0.3, 1) @@ -167,19 +165,19 @@ public void testSingleGroupTopN(boolean produceRowNumbers) new NoChannelGroupByHash()); // add 4 rows for the first page and created a single heap with 4 rows - assertTrue(groupedTopNBuilder.processPage(input.get(0)).process()); + assertThat(groupedTopNBuilder.processPage(input.get(0)).process()).isTrue(); // add 1 row for the second page and the heap is with 5 rows - assertTrue(groupedTopNBuilder.processPage(input.get(1)).process()); + assertThat(groupedTopNBuilder.processPage(input.get(1)).process()).isTrue(); // update 1 new row from the third page (which will be compacted into a single row only) - assertTrue(groupedTopNBuilder.processPage(input.get(2)).process()); + assertThat(groupedTopNBuilder.processPage(input.get(2)).process()).isTrue(); // the last page will be discarded - assertTrue(groupedTopNBuilder.processPage(input.get(3)).process()); + assertThat(groupedTopNBuilder.processPage(input.get(3)).process()).isTrue(); List output = ImmutableList.copyOf(groupedTopNBuilder.buildResult()); - assertEquals(output.size(), 1); + assertThat(output.size()).isEqualTo(1); Page expected = rowPagesBuilder(BIGINT, DOUBLE, BIGINT) .row(3L, 0.1, 1) @@ -221,12 +219,12 @@ public void testYield() groupByHash); Work work = groupedTopNBuilder.processPage(input); - assertFalse(work.process()); - assertFalse(work.process()); + assertThat(work.process()).isFalse(); + assertThat(work.process()).isFalse(); unblock.set(true); - assertTrue(work.process()); + assertThat(work.process()).isTrue(); List output = ImmutableList.copyOf(groupedTopNBuilder.buildResult()); - assertEquals(output.size(), 1); + assertThat(output.size()).isEqualTo(1); Page expected = rowPagesBuilder(types) .row(1L, 0.1) diff --git a/core/trino-main/src/test/java/io/trino/operator/TestHashAggregationOperator.java b/core/trino-main/src/test/java/io/trino/operator/TestHashAggregationOperator.java index 0b48e30ae770..babf7dbdf044 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestHashAggregationOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestHashAggregationOperator.java @@ -94,9 +94,6 @@ import static java.util.concurrent.Executors.newScheduledThreadPool; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @Test(singleThreaded = true) public class TestHashAggregationOperator @@ -213,7 +210,9 @@ public void testHashAggregation(boolean hashEnabled, boolean spillEnabled, boole assertGreaterThan(pages.size(), 1, "Expected more than one output page"); assertPagesEqualIgnoreOrder(driverContext, pages, expected, hashEnabled, Optional.of(hashChannels.size())); - assertTrue(spillEnabled == (spillerFactory.getSpillsCount() > 0), format("Spill state mismatch. Expected spill: %s, spill count: %s", spillEnabled, spillerFactory.getSpillsCount())); + assertThat(spillEnabled == (spillerFactory.getSpillsCount() > 0)) + .describedAs(format("Spill state mismatch. Expected spill: %s, spill count: %s", spillEnabled, spillerFactory.getSpillsCount())) + .isTrue(); } @Test(dataProvider = "hashEnabledAndMemoryLimitForMergeValues") @@ -305,8 +304,8 @@ public void testHashAggregationMemoryReservation(boolean hashEnabled, boolean sp Operator operator = operatorFactory.createOperator(driverContext); toPages(operator, input.iterator(), revokeMemoryWhenAddingPages); // TODO (https://github.com/trinodb/trino/issues/10596): it should be 0, since operator is finished - assertEquals(getOnlyElement(operator.getOperatorContext().getNestedOperatorStats()).getUserMemoryReservation().toBytes(), spillEnabled && revokeMemoryWhenAddingPages ? 4752672 : 0); - assertEquals(getOnlyElement(operator.getOperatorContext().getNestedOperatorStats()).getRevocableMemoryReservation().toBytes(), 0); + assertThat(getOnlyElement(operator.getOperatorContext().getNestedOperatorStats()).getUserMemoryReservation().toBytes()).isEqualTo(spillEnabled && revokeMemoryWhenAddingPages ? 4752672 : 0); + assertThat(getOnlyElement(operator.getOperatorContext().getNestedOperatorStats()).getRevocableMemoryReservation().toBytes()).isEqualTo(0); } @Test(dataProvider = "hashEnabled", expectedExceptions = ExceededMemoryLimitException.class, expectedExceptionsMessageRegExp = "Query exceeded per-node memory limit of 10B.*") @@ -418,13 +417,13 @@ public void testMemoryReservationYield(Type type) int count = 0; for (Page page : result.getOutput()) { // value + hash + aggregation result - assertEquals(page.getChannelCount(), 3); + assertThat(page.getChannelCount()).isEqualTo(3); for (int i = 0; i < page.getPositionCount(); i++) { - assertEquals(page.getBlock(2).getLong(i, 0), 1); + assertThat(page.getBlock(2).getLong(i, 0)).isEqualTo(1); count++; } } - assertEquals(count, 6_000 * 600); + assertThat(count).isEqualTo(6_000 * 600); } @Test(dataProvider = "hashEnabled", expectedExceptions = ExceededMemoryLimitException.class, expectedExceptionsMessageRegExp = "Query exceeded per-node memory limit of 3MB.*") @@ -497,7 +496,7 @@ public void testMultiSliceAggregationOutput(boolean hashEnabled) typeOperators, Optional.empty()); - assertEquals(toPages(operatorFactory, createDriverContext(), input).size(), 2); + assertThat(toPages(operatorFactory, createDriverContext(), input).size()).isEqualTo(2); } @Test(dataProvider = "hashEnabled") @@ -559,10 +558,10 @@ public void testMultiplePartialFlushes(boolean hashEnabled) } // There should be some pages that were drained - assertTrue(!outputPages.isEmpty()); + assertThat(!outputPages.isEmpty()).isTrue(); // The operator need input again since this was a partial flush - assertTrue(operator.needsInput()); + assertThat(operator.needsInput()).isTrue(); // Now, drive the operator to completion outputPages.addAll(toPages(operator, inputIterator)); @@ -574,12 +573,12 @@ public void testMultiplePartialFlushes(boolean hashEnabled) } actual = toMaterializedResult(operator.getOperatorContext().getSession(), expected.getTypes(), outputPages); - assertEquals(actual.getTypes(), expected.getTypes()); + assertThat(actual.getTypes()).isEqualTo(expected.getTypes()); assertEqualsIgnoreOrder(actual.getMaterializedRows(), expected.getMaterializedRows()); } - assertEquals(driverContext.getMemoryUsage(), 0); - assertEquals(driverContext.getRevocableMemoryUsage(), 0); + assertThat(driverContext.getMemoryUsage()).isEqualTo(0); + assertThat(driverContext.getRevocableMemoryUsage()).isEqualTo(0); } @Test @@ -704,7 +703,7 @@ public void testMemoryTracking() DriverContext driverContext = createDriverContext(1024); try (Operator operator = operatorFactory.createOperator(driverContext)) { - assertTrue(operator.needsInput()); + assertThat(operator.needsInput()).isTrue(); operator.addInput(input); assertThat(driverContext.getMemoryUsage()).isGreaterThan(0); @@ -712,8 +711,8 @@ public void testMemoryTracking() toPages(operator, emptyIterator()); } - assertEquals(driverContext.getMemoryUsage(), 0); - assertEquals(driverContext.getRevocableMemoryUsage(), 0); + assertThat(driverContext.getMemoryUsage()).isEqualTo(0); + assertThat(driverContext.getRevocableMemoryUsage()).isEqualTo(0); } @Test @@ -741,7 +740,7 @@ public void testAdaptivePartialAggregation() Optional.of(partialAggregationController)); // at the start partial aggregation is enabled - assertFalse(partialAggregationController.isPartialAggregationDisabled()); + assertThat(partialAggregationController.isPartialAggregationDisabled()).isFalse(); // First operator will trigger adaptive partial aggregation after the first page List operator1Input = rowPagesBuilder(false, hashChannels, BIGINT) .addBlocksPage(createLongsBlock(0, 1, 2, 3, 4, 5, 6, 7, 8, 8)) // first page will be hashed but the values are almost unique, so it will trigger adaptation @@ -754,7 +753,7 @@ public void testAdaptivePartialAggregation() assertOperatorEquals(operatorFactory, operator1Input, operator1Expected); // the first operator flush disables partial aggregation - assertTrue(partialAggregationController.isPartialAggregationDisabled()); + assertThat(partialAggregationController.isPartialAggregationDisabled()).isTrue(); // second operator using the same factory, reuses PartialAggregationControl, so it will only produce raw pages (partial aggregation is disabled at this point) List operator2Input = rowPagesBuilder(false, hashChannels, BIGINT) .addBlocksPage(createRepeatedValuesBlock(1, 10)) @@ -776,10 +775,10 @@ public void testAdaptivePartialAggregation() .build(); assertOperatorEquals(operatorFactory, operatorInput, operatorExpected); if (i <= 2) { - assertTrue(partialAggregationController.isPartialAggregationDisabled()); + assertThat(partialAggregationController.isPartialAggregationDisabled()).isTrue(); } else { - assertFalse(partialAggregationController.isPartialAggregationDisabled()); + assertThat(partialAggregationController.isPartialAggregationDisabled()).isFalse(); } } @@ -796,7 +795,7 @@ public void testAdaptivePartialAggregation() .addBlocksPage(createRepeatedValuesBlock(2, 1), createRepeatedValuesBlock(2, 1)) .build(); assertOperatorEquals(operatorFactory, operator3Input, operator3Expected); - assertFalse(partialAggregationController.isPartialAggregationDisabled()); + assertThat(partialAggregationController.isPartialAggregationDisabled()).isFalse(); } @Test @@ -834,7 +833,7 @@ public void testAdaptivePartialAggregationTriggeredOnlyOnFlush() assertOperatorEquals(driverContext, operatorFactory, operator1Input, operator1Expected); // the first operator flush disables partial aggregation - assertTrue(partialAggregationController.isPartialAggregationDisabled()); + assertThat(partialAggregationController.isPartialAggregationDisabled()).isTrue(); assertInputRowsWithPartialAggregationDisabled(driverContext, 0); // second operator using the same factory, reuses PartialAggregationControl, so it will only produce raw pages (partial aggregation is disabled at this point) @@ -856,10 +855,10 @@ private void assertInputRowsWithPartialAggregationDisabled(DriverContext context { LongCount metric = ((LongCount) context.getDriverStats().getOperatorStats().get(0).getMetrics().getMetrics().get(INPUT_ROWS_WITH_PARTIAL_AGGREGATION_DISABLED_METRIC_NAME)); if (metric == null) { - assertEquals(0, expectedRowCount); + assertThat(0).isEqualTo(expectedRowCount); } else { - assertEquals(metric.getTotal(), expectedRowCount); + assertThat(metric.getTotal()).isEqualTo(expectedRowCount); } } @@ -892,12 +891,12 @@ private DriverContext createDriverContext(long memoryLimit) private int getHashCapacity(Operator operator) { - assertTrue(operator instanceof HashAggregationOperator); + assertThat(operator instanceof HashAggregationOperator).isTrue(); HashAggregationBuilder aggregationBuilder = ((HashAggregationOperator) operator).getAggregationBuilder(); if (aggregationBuilder == null) { return 0; } - assertTrue(aggregationBuilder instanceof InMemoryHashAggregationBuilder); + assertThat(aggregationBuilder instanceof InMemoryHashAggregationBuilder).isTrue(); return ((InMemoryHashAggregationBuilder) aggregationBuilder).getCapacity(); } diff --git a/core/trino-main/src/test/java/io/trino/operator/TestHttpPageBufferClient.java b/core/trino-main/src/test/java/io/trino/operator/TestHttpPageBufferClient.java index e3c5bd78d0dc..7f360c4fa0a5 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestHttpPageBufferClient.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestHttpPageBufferClient.java @@ -71,10 +71,9 @@ import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.util.Failures.WORKER_NODE_ERROR; import static java.util.concurrent.Executors.newScheduledThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -140,10 +139,10 @@ public void testHappyPath() client.scheduleRequest(); requestComplete.await(10, TimeUnit.SECONDS); - assertEquals(callback.getPages().size(), 1); + assertThat(callback.getPages().size()).isEqualTo(1); assertPageEquals(expectedPage, callback.getPages().get(0)); - assertEquals(callback.getCompletedRequests(), 1); - assertEquals(callback.getFinishedBuffers(), 0); + assertThat(callback.getCompletedRequests()).isEqualTo(1); + assertThat(callback.getFinishedBuffers()).isEqualTo(0); assertStatus(client, location, "queued", 1, 1, 1, 0, "not scheduled"); // fetch no data and verify @@ -151,9 +150,9 @@ public void testHappyPath() client.scheduleRequest(); requestComplete.await(10, TimeUnit.SECONDS); - assertEquals(callback.getPages().size(), 0); - assertEquals(callback.getCompletedRequests(), 1); - assertEquals(callback.getFinishedBuffers(), 0); + assertThat(callback.getPages().size()).isEqualTo(0); + assertThat(callback.getCompletedRequests()).isEqualTo(1); + assertThat(callback.getFinishedBuffers()).isEqualTo(0); assertStatus(client, location, "queued", 1, 2, 2, 0, "not scheduled"); // fetch two more pages and verify @@ -163,12 +162,12 @@ public void testHappyPath() client.scheduleRequest(); requestComplete.await(10, TimeUnit.SECONDS); - assertEquals(callback.getPages().size(), 2); + assertThat(callback.getPages().size()).isEqualTo(2); assertPageEquals(expectedPage, callback.getPages().get(0)); assertPageEquals(expectedPage, callback.getPages().get(1)); - assertEquals(callback.getCompletedRequests(), 1); - assertEquals(callback.getFinishedBuffers(), 0); - assertEquals(callback.getFailedBuffers(), 0); + assertThat(callback.getCompletedRequests()).isEqualTo(1); + assertThat(callback.getFinishedBuffers()).isEqualTo(0); + assertThat(callback.getFailedBuffers()).isEqualTo(0); callback.resetStats(); assertStatus(client, location, "queued", 3, 3, 3, 0, "not scheduled"); @@ -179,18 +178,18 @@ public void testHappyPath() requestComplete.await(10, TimeUnit.SECONDS); // get the buffer complete signal - assertEquals(callback.getPages().size(), 0); - assertEquals(callback.getCompletedRequests(), 1); + assertThat(callback.getPages().size()).isEqualTo(0); + assertThat(callback.getCompletedRequests()).isEqualTo(1); // schedule the delete call to the buffer callback.resetStats(); client.scheduleRequest(); requestComplete.await(10, TimeUnit.SECONDS); - assertEquals(callback.getFinishedBuffers(), 1); + assertThat(callback.getFinishedBuffers()).isEqualTo(1); - assertEquals(callback.getPages().size(), 0); - assertEquals(callback.getCompletedRequests(), 0); - assertEquals(callback.getFailedBuffers(), 0); + assertThat(callback.getPages().size()).isEqualTo(0); + assertThat(callback.getCompletedRequests()).isEqualTo(0); + assertThat(callback.getFailedBuffers()).isEqualTo(0); assertStatus(client, location, "closed", 3, 5, 5, 0, "not scheduled"); } @@ -226,7 +225,7 @@ public void testLifecycle() client.scheduleRequest(); beforeRequest.await(10, TimeUnit.SECONDS); assertStatus(client, location, "running", 0, 1, 0, 0, "PROCESSING_REQUEST"); - assertEquals(client.isRunning(), true); + assertThat(client.isRunning()).isEqualTo(true); afterRequest.await(10, TimeUnit.SECONDS); requestComplete.await(10, TimeUnit.SECONDS); @@ -271,10 +270,10 @@ public void testInvalidResponses() processor.setResponse(new TestingResponse(HttpStatus.NOT_FOUND, ImmutableListMultimap.of(CONTENT_TYPE, TRINO_PAGES), new byte[0])); client.scheduleRequest(); requestComplete.await(10, TimeUnit.SECONDS); - assertEquals(callback.getPages().size(), 0); - assertEquals(callback.getCompletedRequests(), 1); - assertEquals(callback.getFinishedBuffers(), 0); - assertEquals(callback.getFailedBuffers(), 1); + assertThat(callback.getPages().size()).isEqualTo(0); + assertThat(callback.getCompletedRequests()).isEqualTo(1); + assertThat(callback.getFinishedBuffers()).isEqualTo(0); + assertThat(callback.getFailedBuffers()).isEqualTo(1); assertInstanceOf(callback.getFailure(), PageTransportErrorException.class); assertContains(callback.getFailure().getMessage(), "Expected response code to be 200, but was 404"); assertStatus(client, location, "queued", 0, 1, 1, 1, "not scheduled"); @@ -284,10 +283,10 @@ public void testInvalidResponses() processor.setResponse(new TestingResponse(HttpStatus.OK, ImmutableListMultimap.of(CONTENT_TYPE, "INVALID_TYPE"), new byte[0])); client.scheduleRequest(); requestComplete.await(10, TimeUnit.SECONDS); - assertEquals(callback.getPages().size(), 0); - assertEquals(callback.getCompletedRequests(), 1); - assertEquals(callback.getFinishedBuffers(), 0); - assertEquals(callback.getFailedBuffers(), 1); + assertThat(callback.getPages().size()).isEqualTo(0); + assertThat(callback.getCompletedRequests()).isEqualTo(1); + assertThat(callback.getFinishedBuffers()).isEqualTo(0); + assertThat(callback.getFailedBuffers()).isEqualTo(1); assertInstanceOf(callback.getFailure(), PageTransportErrorException.class); assertContains(callback.getFailure().getMessage(), "Expected application/x-trino-pages response from server but got INVALID_TYPE"); assertStatus(client, location, "queued", 0, 2, 2, 2, "not scheduled"); @@ -297,10 +296,10 @@ public void testInvalidResponses() processor.setResponse(new TestingResponse(HttpStatus.OK, ImmutableListMultimap.of(CONTENT_TYPE, "text/plain"), new byte[0])); client.scheduleRequest(); requestComplete.await(10, TimeUnit.SECONDS); - assertEquals(callback.getPages().size(), 0); - assertEquals(callback.getCompletedRequests(), 1); - assertEquals(callback.getFinishedBuffers(), 0); - assertEquals(callback.getFailedBuffers(), 1); + assertThat(callback.getPages().size()).isEqualTo(0); + assertThat(callback.getCompletedRequests()).isEqualTo(1); + assertThat(callback.getFinishedBuffers()).isEqualTo(0); + assertThat(callback.getFailedBuffers()).isEqualTo(1); assertInstanceOf(callback.getFailure(), PageTransportErrorException.class); assertContains(callback.getFailure().getMessage(), "Expected application/x-trino-pages response from server but got text/plain"); assertStatus(client, location, "queued", 0, 3, 3, 3, "not scheduled"); @@ -344,7 +343,7 @@ public void testCloseDuringPendingRequest() client.scheduleRequest(); beforeRequest.await(10, TimeUnit.SECONDS); assertStatus(client, location, "running", 0, 1, 0, 0, "PROCESSING_REQUEST"); - assertEquals(client.isRunning(), true); + assertThat(client.isRunning()).isEqualTo(true); // request is pending, now close it client.close(); @@ -403,10 +402,10 @@ public void testExceptionFromResponseHandler() // this starts the error stopwatch client.scheduleRequest(); requestComplete.await(10, TimeUnit.SECONDS); - assertEquals(callback.getPages().size(), 0); - assertEquals(callback.getCompletedRequests(), 1); - assertEquals(callback.getFinishedBuffers(), 0); - assertEquals(callback.getFailedBuffers(), 0); + assertThat(callback.getPages().size()).isEqualTo(0); + assertThat(callback.getCompletedRequests()).isEqualTo(1); + assertThat(callback.getFinishedBuffers()).isEqualTo(0); + assertThat(callback.getFailedBuffers()).isEqualTo(0); assertStatus(client, location, "queued", 0, 1, 1, 1, "not scheduled"); // advance time forward, but not enough to fail the client @@ -415,10 +414,10 @@ public void testExceptionFromResponseHandler() // verify that the client has not failed client.scheduleRequest(); requestComplete.await(10, TimeUnit.SECONDS); - assertEquals(callback.getPages().size(), 0); - assertEquals(callback.getCompletedRequests(), 2); - assertEquals(callback.getFinishedBuffers(), 0); - assertEquals(callback.getFailedBuffers(), 0); + assertThat(callback.getPages().size()).isEqualTo(0); + assertThat(callback.getCompletedRequests()).isEqualTo(2); + assertThat(callback.getFinishedBuffers()).isEqualTo(0); + assertThat(callback.getFailedBuffers()).isEqualTo(0); assertStatus(client, location, "queued", 0, 2, 2, 2, "not scheduled"); // advance time forward beyond the minimum error duration @@ -427,10 +426,10 @@ public void testExceptionFromResponseHandler() // verify that the client has failed client.scheduleRequest(); requestComplete.await(10, TimeUnit.SECONDS); - assertEquals(callback.getPages().size(), 0); - assertEquals(callback.getCompletedRequests(), 3); - assertEquals(callback.getFinishedBuffers(), 0); - assertEquals(callback.getFailedBuffers(), 1); + assertThat(callback.getPages().size()).isEqualTo(0); + assertThat(callback.getCompletedRequests()).isEqualTo(3); + assertThat(callback.getFinishedBuffers()).isEqualTo(0); + assertThat(callback.getFailedBuffers()).isEqualTo(1); assertInstanceOf(callback.getFailure(), PageTransportTimeoutException.class); assertContains(callback.getFailure().getMessage(), WORKER_NODE_ERROR + " (http://localhost:8080/0 - 3 failures, failure duration 31.00s, total failed request time 31.00s)"); assertStatus(client, location, "queued", 0, 3, 3, 3, "not scheduled"); @@ -439,9 +438,9 @@ public void testExceptionFromResponseHandler() @Test public void testErrorCodes() { - assertEquals(new PageTooLargeException().getErrorCode(), PAGE_TOO_LARGE.toErrorCode()); - assertEquals(new PageTransportErrorException(HostAddress.fromParts("127.0.0.1", 8080), "").getErrorCode(), PAGE_TRANSPORT_ERROR.toErrorCode()); - assertEquals(new PageTransportTimeoutException(HostAddress.fromParts("127.0.0.1", 8080), "", null).getErrorCode(), PAGE_TRANSPORT_TIMEOUT.toErrorCode()); + assertThat(new PageTooLargeException().getErrorCode()).isEqualTo(PAGE_TOO_LARGE.toErrorCode()); + assertThat(new PageTransportErrorException(HostAddress.fromParts("127.0.0.1", 8080), "").getErrorCode()).isEqualTo(PAGE_TRANSPORT_ERROR.toErrorCode()); + assertThat(new PageTransportTimeoutException(HostAddress.fromParts("127.0.0.1", 8080), "", null).getErrorCode()).isEqualTo(PAGE_TRANSPORT_TIMEOUT.toErrorCode()); } @Test @@ -461,14 +460,14 @@ public void testAverageSizeOfRequest() new TestingTicker(), pageBufferClientCallbackExecutor); - assertEquals(client.getAverageRequestSizeInBytes(), 0); + assertThat(client.getAverageRequestSizeInBytes()).isEqualTo(0); client.requestSucceeded(0); - assertEquals(client.getAverageRequestSizeInBytes(), 0); + assertThat(client.getAverageRequestSizeInBytes()).isEqualTo(0); client.requestSucceeded(1000); client.requestSucceeded(800); - assertEquals(client.getAverageRequestSizeInBytes(), 600); + assertThat(client.getAverageRequestSizeInBytes()).isEqualTo(600); } @Test @@ -513,13 +512,13 @@ public boolean addPages(HttpPageBufferClient client, List pages) requestComplete.await(10, TimeUnit.SECONDS); // addPages was called - assertTrue(addPagesCalled.get()); + assertThat(addPagesCalled.get()).isTrue(); // Memory exceeded failure is reported - assertEquals(callback.getCompletedRequests(), 1); - assertEquals(callback.getFinishedBuffers(), 0); - assertEquals(callback.getFailedBuffers(), 1); - assertEquals(callback.getFailure(), expectedException); + assertThat(callback.getCompletedRequests()).isEqualTo(1); + assertThat(callback.getFinishedBuffers()).isEqualTo(0); + assertThat(callback.getFailedBuffers()).isEqualTo(1); + assertThat(callback.getFailure()).isEqualTo(expectedException); } private static void assertStatus( @@ -532,19 +531,31 @@ private static void assertStatus( String httpRequestState) { PageBufferClientStatus actualStatus = client.getStatus(); - assertEquals(actualStatus.getUri(), location); - assertEquals(actualStatus.getState(), status, "status"); - assertEquals(actualStatus.getPagesReceived(), pagesReceived, "pagesReceived"); - assertEquals(actualStatus.getRequestsScheduled(), requestsScheduled, "requestsScheduled"); - assertEquals(actualStatus.getRequestsCompleted(), requestsCompleted, "requestsCompleted"); - assertEquals(actualStatus.getRequestsFailed(), requestsFailed, "requestsFailed"); - assertEquals(actualStatus.getHttpRequestState(), httpRequestState, "httpRequestState"); + assertThat(actualStatus.getUri()).isEqualTo(location); + assertThat(actualStatus.getState()) + .describedAs("status") + .isEqualTo(status); + assertThat(actualStatus.getPagesReceived()) + .describedAs("pagesReceived") + .isEqualTo(pagesReceived); + assertThat(actualStatus.getRequestsScheduled()) + .describedAs("requestsScheduled") + .isEqualTo(requestsScheduled); + assertThat(actualStatus.getRequestsCompleted()) + .describedAs("requestsCompleted") + .isEqualTo(requestsCompleted); + assertThat(actualStatus.getRequestsFailed()) + .describedAs("requestsFailed") + .isEqualTo(requestsFailed); + assertThat(actualStatus.getHttpRequestState()) + .describedAs("httpRequestState") + .isEqualTo(httpRequestState); } private static void assertPageEquals(Page expectedPage, Page actualPage) { - assertEquals(actualPage.getPositionCount(), expectedPage.getPositionCount()); - assertEquals(actualPage.getChannelCount(), expectedPage.getChannelCount()); + assertThat(actualPage.getPositionCount()).isEqualTo(expectedPage.getPositionCount()); + assertThat(actualPage.getChannelCount()).isEqualTo(expectedPage.getChannelCount()); } private static class TestingClientCallback diff --git a/core/trino-main/src/test/java/io/trino/operator/TestIdRegistry.java b/core/trino-main/src/test/java/io/trino/operator/TestIdRegistry.java index 3ee9d4ec8b20..77960681fba2 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestIdRegistry.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestIdRegistry.java @@ -16,7 +16,7 @@ import it.unimi.dsi.fastutil.ints.IntArrayList; import org.junit.jupiter.api.Test; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestIdRegistry { @@ -25,14 +25,14 @@ public void testAllocateDeallocate() { IdRegistry registry = new IdRegistry<>(); int id1 = Integer.parseInt(registry.allocateId(Integer::toString)); - assertEquals(registry.get(id1), Integer.toString(id1)); + assertThat(registry.get(id1)).isEqualTo(Integer.toString(id1)); int id2 = Integer.parseInt(registry.allocateId(Integer::toString)); - assertEquals(registry.get(id1), Integer.toString(id1)); - assertEquals(registry.get(id2), Integer.toString(id2)); + assertThat(registry.get(id1)).isEqualTo(Integer.toString(id1)); + assertThat(registry.get(id2)).isEqualTo(Integer.toString(id2)); // Should still be able to fetch id2 after deallocating id1 registry.deallocate(id1); - assertEquals(registry.get(id2), Integer.toString(id2)); + assertThat(registry.get(id2)).isEqualTo(Integer.toString(id2)); } @Test @@ -46,7 +46,7 @@ public void testBulkAllocate() } // Get values for (int i = 0; i < 100; i++) { - assertEquals(registry.get(ids.getInt(i)), Integer.toString(i)); + assertThat(registry.get(ids.getInt(i))).isEqualTo(Integer.toString(i)); } // Deallocate for (int i = 0; i < 100; i++) { @@ -61,12 +61,12 @@ public void testIdRecycling() int id1 = Integer.parseInt(registry.allocateId(Integer::toString)); registry.deallocate(id1); int id2 = Integer.parseInt(registry.allocateId(Integer::toString)); - assertEquals(id1, id2); + assertThat(id1).isEqualTo(id2); int id3 = Integer.parseInt(registry.allocateId(Integer::toString)); registry.allocateId(Integer::toString); registry.deallocate(id3); registry.allocateId(Integer::toString); - assertEquals(id3, id3); + assertThat(id3).isEqualTo(id3); } } diff --git a/core/trino-main/src/test/java/io/trino/operator/TestIncrementalLoadFactorHashArraySizeSupplier.java b/core/trino-main/src/test/java/io/trino/operator/TestIncrementalLoadFactorHashArraySizeSupplier.java index f3b5ff392bdd..9d93357fd093 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestIncrementalLoadFactorHashArraySizeSupplier.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestIncrementalLoadFactorHashArraySizeSupplier.java @@ -18,7 +18,7 @@ import static io.airlift.testing.Assertions.assertGreaterThanOrEqual; import static io.trino.operator.IncrementalLoadFactorHashArraySizeSupplier.THRESHOLD_25; import static io.trino.operator.IncrementalLoadFactorHashArraySizeSupplier.THRESHOLD_50; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestIncrementalLoadFactorHashArraySizeSupplier { @@ -43,7 +43,7 @@ private int assertHashArraySizeIncreases( { int size = sizeSupplier.getHashArraySize(expectedCount); assertGreaterThanOrEqual(size, previousSize); - assertEquals(sizeSupplier.getHashArraySize(expectedCount) * 4, sizeSupplierWithMultiplier.getHashArraySize(expectedCount * 4)); + assertThat(sizeSupplier.getHashArraySize(expectedCount) * 4).isEqualTo(sizeSupplierWithMultiplier.getHashArraySize(expectedCount * 4)); return size; } } diff --git a/core/trino-main/src/test/java/io/trino/operator/TestMarkDistinctOperator.java b/core/trino-main/src/test/java/io/trino/operator/TestMarkDistinctOperator.java index 87c085c6a603..1657f51fb5d7 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestMarkDistinctOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestMarkDistinctOperator.java @@ -50,9 +50,7 @@ import static io.trino.testing.TestingTaskContext.createTaskContext; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; @Test(singleThreaded = true) public class TestMarkDistinctOperator @@ -148,29 +146,33 @@ public void testRleDistinctMask(boolean hashEnabled) Block noDistinctOutput = operator.getOutput().getBlock(maskChannel); // all distinct and no distinct conditions produce RLE blocks assertInstanceOf(allDistinctOutput, RunLengthEncodedBlock.class); - assertTrue(BOOLEAN.getBoolean(allDistinctOutput, 0)); + assertThat(BOOLEAN.getBoolean(allDistinctOutput, 0)).isTrue(); assertInstanceOf(noDistinctOutput, RunLengthEncodedBlock.class); - assertFalse(BOOLEAN.getBoolean(noDistinctOutput, 0)); + assertThat(BOOLEAN.getBoolean(noDistinctOutput, 0)).isFalse(); operator.addInput(secondInput); Block halfDistinctOutput = operator.getOutput().getBlock(maskChannel); // [0,50) is not distinct for (int position = 0; position < 50; position++) { - assertFalse(BOOLEAN.getBoolean(halfDistinctOutput, position)); + assertThat(BOOLEAN.getBoolean(halfDistinctOutput, position)).isFalse(); } for (int position = 50; position < 100; position++) { - assertTrue(BOOLEAN.getBoolean(halfDistinctOutput, position)); + assertThat(BOOLEAN.getBoolean(halfDistinctOutput, position)).isTrue(); } operator.addInput(singleDistinctPage); Block singleDistinctBlock = operator.getOutput().getBlock(maskChannel); - assertFalse(singleDistinctBlock instanceof RunLengthEncodedBlock, "single position inputs should not be RLE"); - assertTrue(BOOLEAN.getBoolean(singleDistinctBlock, 0)); + assertThat(singleDistinctBlock instanceof RunLengthEncodedBlock) + .describedAs("single position inputs should not be RLE") + .isFalse(); + assertThat(BOOLEAN.getBoolean(singleDistinctBlock, 0)).isTrue(); operator.addInput(singleNotDistinctPage); Block singleNotDistinctBlock = operator.getOutput().getBlock(maskChannel); - assertFalse(singleNotDistinctBlock instanceof RunLengthEncodedBlock, "single position inputs should not be RLE"); - assertFalse(BOOLEAN.getBoolean(singleNotDistinctBlock, 0)); + assertThat(singleNotDistinctBlock instanceof RunLengthEncodedBlock) + .describedAs("single position inputs should not be RLE") + .isFalse(); + assertThat(BOOLEAN.getBoolean(singleNotDistinctBlock, 0)).isFalse(); } catch (Exception e) { throwIfUnchecked(e); @@ -192,12 +194,12 @@ public void testMemoryReservationYield(Type type) int count = 0; for (Page page : result.getOutput()) { - assertEquals(page.getChannelCount(), 3); + assertThat(page.getChannelCount()).isEqualTo(3); for (int i = 0; i < page.getPositionCount(); i++) { - assertEquals(page.getBlock(2).getByte(i, 0), 1); + assertThat(page.getBlock(2).getByte(i, 0)).isEqualTo((byte) 1); count++; } } - assertEquals(count, 6_000 * 600); + assertThat(count).isEqualTo(6_000 * 600); } } diff --git a/core/trino-main/src/test/java/io/trino/operator/TestMergeHashSort.java b/core/trino-main/src/test/java/io/trino/operator/TestMergeHashSort.java index 111174d4f4a0..9398322befaf 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestMergeHashSort.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestMergeHashSort.java @@ -23,8 +23,7 @@ import static io.trino.memory.context.AggregatedMemoryContext.newSimpleAggregatedMemoryContext; import static io.trino.operator.WorkProcessorAssertion.assertFinishes; import static io.trino.spi.type.BigintType.BIGINT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestMergeHashSort { @@ -60,11 +59,11 @@ public void testBinaryMergeIteratorOverEmptyPageAndNonEmptyPage() .collect(toImmutableList()), new DriverYieldSignal()); - assertTrue(mergedPage.process()); + assertThat(mergedPage.process()).isTrue(); Page actualPage = mergedPage.getResult(); - assertEquals(actualPage.getPositionCount(), 1); - assertEquals(actualPage.getChannelCount(), 1); - assertEquals(actualPage.getBlock(0).getLong(0, 0), 42); + assertThat(actualPage.getPositionCount()).isEqualTo(1); + assertThat(actualPage.getChannelCount()).isEqualTo(1); + assertThat(actualPage.getBlock(0).getLong(0, 0)).isEqualTo(42); assertFinishes(mergedPage); } @@ -83,11 +82,11 @@ public void testBinaryMergeIteratorOverPageWith() .collect(toImmutableList()), new DriverYieldSignal()); - assertTrue(mergedPage.process()); + assertThat(mergedPage.process()).isTrue(); Page actualPage = mergedPage.getResult(); - assertEquals(actualPage.getPositionCount(), 1); - assertEquals(actualPage.getChannelCount(), 1); - assertEquals(actualPage.getBlock(0).getLong(0, 0), 42); + assertThat(actualPage.getPositionCount()).isEqualTo(1); + assertThat(actualPage.getChannelCount()).isEqualTo(1); + assertThat(actualPage.getBlock(0).getLong(0, 0)).isEqualTo(42); assertFinishes(mergedPage); } @@ -110,13 +109,13 @@ public void testBinaryMergeIteratorOverPageWithDifferentHashes() .collect(toImmutableList()), new DriverYieldSignal()); - assertTrue(mergedPages.process()); + assertThat(mergedPages.process()).isTrue(); Page resultPage = mergedPages.getResult(); - assertEquals(resultPage.getPositionCount(), 4); - assertEquals(resultPage.getBlock(0).getLong(0, 0), 42); - assertEquals(resultPage.getBlock(0).getLong(1, 0), 42); - assertEquals(resultPage.getBlock(0).getLong(2, 0), 52); - assertEquals(resultPage.getBlock(0).getLong(3, 0), 60); + assertThat(resultPage.getPositionCount()).isEqualTo(4); + assertThat(resultPage.getBlock(0).getLong(0, 0)).isEqualTo(42); + assertThat(resultPage.getBlock(0).getLong(1, 0)).isEqualTo(42); + assertThat(resultPage.getBlock(0).getLong(2, 0)).isEqualTo(52); + assertThat(resultPage.getBlock(0).getLong(3, 0)).isEqualTo(60); assertFinishes(mergedPages); } diff --git a/core/trino-main/src/test/java/io/trino/operator/TestMergeOperator.java b/core/trino-main/src/test/java/io/trino/operator/TestMergeOperator.java index c92ca8d5bcd5..3fac544a9495 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestMergeOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestMergeOperator.java @@ -60,11 +60,8 @@ import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.testing.TestingTaskContext.createTaskContext; import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_METHOD) public class TestMergeOperator @@ -124,12 +121,12 @@ public void testSingleStream() List types = ImmutableList.of(BIGINT, BIGINT); MergeOperator operator = createMergeOperator(types, ImmutableList.of(1), ImmutableList.of(0, 1), ImmutableList.of(ASC_NULLS_FIRST, ASC_NULLS_FIRST)); - assertFalse(operator.isFinished()); - assertFalse(operator.isBlocked().isDone()); + assertThat(operator.isFinished()).isFalse(); + assertThat(operator.isBlocked().isDone()).isFalse(); operator.addSplit(createRemoteSplit(TASK_1_ID)); - assertFalse(operator.isFinished()); - assertFalse(operator.isBlocked().isDone()); + assertThat(operator.isFinished()).isFalse(); + assertThat(operator.isBlocked().isDone()).isFalse(); operator.noMoreSplits(); @@ -141,13 +138,13 @@ public void testSingleStream() .row(4, 4) .build(); - assertNull(operator.getOutput()); - assertFalse(operator.isFinished()); + assertThat(operator.getOutput()).isNull(); + assertThat(operator.isFinished()).isFalse(); assertOperatorIsBlocked(operator); taskBuffers.getUnchecked(TASK_1_ID).addPage(input.get(0), false); assertOperatorIsUnblocked(operator); - assertNull(operator.getOutput()); + assertThat(operator.getOutput()).isNull(); assertOperatorIsBlocked(operator); taskBuffers.getUnchecked(TASK_1_ID).addPage(input.get(1), true); assertOperatorIsUnblocked(operator); @@ -186,13 +183,13 @@ public void testMergeDifferentTypes() .build(); // blocked on first data source - assertNull(operator.getOutput()); + assertThat(operator.getOutput()).isNull(); assertOperatorIsBlocked(operator); taskBuffers.getUnchecked(TASK_1_ID).addPages(task1Pages, true); assertOperatorIsUnblocked(operator); // blocked on second data source - assertNull(operator.getOutput()); + assertThat(operator.getOutput()).isNull(); assertOperatorIsBlocked(operator); taskBuffers.getUnchecked(TASK_2_ID).addPages(task2Pages, true); assertOperatorIsUnblocked(operator); @@ -275,20 +272,20 @@ public void testMultipleStreamsSameOutputColumns() .build(); // blocked on first data source - assertNull(operator.getOutput()); - assertFalse(operator.isFinished()); + assertThat(operator.getOutput()).isNull(); + assertThat(operator.isFinished()).isFalse(); assertOperatorIsBlocked(operator); taskBuffers.getUnchecked(TASK_1_ID).addPage(source1Pages.get(0), false); assertOperatorIsUnblocked(operator); // blocked on second data source - assertNull(operator.getOutput()); + assertThat(operator.getOutput()).isNull(); assertOperatorIsBlocked(operator); taskBuffers.getUnchecked(TASK_2_ID).addPage(source2Pages.get(0), false); assertOperatorIsUnblocked(operator); // blocked on third data source - assertNull(operator.getOutput()); + assertThat(operator.getOutput()).isNull(); assertOperatorIsBlocked(operator); taskBuffers.getUnchecked(TASK_3_ID).addPage(source3Pages.get(0), false); @@ -377,7 +374,7 @@ private static List pullAvailablePages(Operator operator) assertOperatorIsUnblocked(operator); while (!operator.isFinished() && System.nanoTime() - endTime < 0) { - assertFalse(operator.needsInput()); + assertThat(operator.needsInput()).isFalse(); Page outputPage = operator.getOutput(); if (outputPage != null) { outputPages.add(outputPage); @@ -388,13 +385,17 @@ private static List pullAvailablePages(Operator operator) } // verify state - assertFalse(operator.needsInput(), "Operator still wants input"); - assertTrue(operator.isFinished(), "Expected operator to be finished"); + assertThat(operator.needsInput()) + .describedAs("Operator still wants input") + .isFalse(); + assertThat(operator.isFinished()) + .describedAs("Expected operator to be finished") + .isTrue(); operator.close(); operator.getOperatorContext().destroy(); - assertEquals(getOnlyElement(operator.getOperatorContext().getNestedOperatorStats()).getUserMemoryReservation().toBytes(), 0); + assertThat(getOnlyElement(operator.getOperatorContext().getNestedOperatorStats()).getUserMemoryReservation().toBytes()).isEqualTo(0); return outputPages; } diff --git a/core/trino-main/src/test/java/io/trino/operator/TestOperationTimer.java b/core/trino-main/src/test/java/io/trino/operator/TestOperationTimer.java index 76f19a01737b..0cc686e168b3 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestOperationTimer.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestOperationTimer.java @@ -25,7 +25,6 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestOperationTimer { @@ -132,17 +131,17 @@ public void record(Consumer timer) { previousWallNanos = timing.getWallNanos(); previousCpuNanos = timing.getCpuNanos(); - assertEquals(timing.getCalls(), calls); + assertThat(timing.getCalls()).isEqualTo(calls); timer.accept(timing); calls++; - assertEquals(timing.getCalls(), calls); + assertThat(timing.getCalls()).isEqualTo(calls); assertThat(timing.getWallNanos()).isGreaterThan(previousWallNanos); if (trackCpuTime) { assertThat(timing.getCpuNanos()).isGreaterThan(previousCpuNanos); assertThat(timing.getWallNanos()).isGreaterThan(timing.getCpuNanos()); } else { - assertEquals(timing.getCpuNanos(), 0); + assertThat(timing.getCpuNanos()).isEqualTo(0); } } } diff --git a/core/trino-main/src/test/java/io/trino/operator/TestOperatorMemoryRevocation.java b/core/trino-main/src/test/java/io/trino/operator/TestOperatorMemoryRevocation.java index 5d8661230e73..5739220e821a 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestOperatorMemoryRevocation.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestOperatorMemoryRevocation.java @@ -24,12 +24,10 @@ import java.util.concurrent.atomic.AtomicInteger; import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -58,16 +56,16 @@ public void testOperatorMemoryRevocation() revocableMemoryContext.setBytes(1000); operatorContext.setMemoryRevocationRequestListener(counter::incrementAndGet); operatorContext.requestMemoryRevoking(); - assertTrue(operatorContext.isMemoryRevokingRequested()); - assertEquals(counter.get(), 1); + assertThat(operatorContext.isMemoryRevokingRequested()).isTrue(); + assertThat(counter.get()).isEqualTo(1); // calling resetMemoryRevokingRequested() should clear the memory revoking requested flag operatorContext.resetMemoryRevokingRequested(); - assertFalse(operatorContext.isMemoryRevokingRequested()); + assertThat(operatorContext.isMemoryRevokingRequested()).isFalse(); operatorContext.requestMemoryRevoking(); - assertEquals(counter.get(), 2); - assertTrue(operatorContext.isMemoryRevokingRequested()); + assertThat(counter.get()).isEqualTo(2); + assertThat(operatorContext.isMemoryRevokingRequested()).isTrue(); } @Test @@ -81,8 +79,8 @@ public void testRevocationAlreadyRequested() // when memory revocation is already requested setting a listener should immediately execute it operatorContext.requestMemoryRevoking(); operatorContext.setMemoryRevocationRequestListener(counter::incrementAndGet); - assertTrue(operatorContext.isMemoryRevokingRequested()); - assertEquals(counter.get(), 1); + assertThat(operatorContext.isMemoryRevokingRequested()).isTrue(); + assertThat(counter.get()).isEqualTo(1); } @Test diff --git a/core/trino-main/src/test/java/io/trino/operator/TestOperatorStats.java b/core/trino-main/src/test/java/io/trino/operator/TestOperatorStats.java index a57a2acfa48b..0c3dab4a47a5 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestOperatorStats.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestOperatorStats.java @@ -28,8 +28,7 @@ import static io.trino.testing.TestingHandles.TEST_CATALOG_HANDLE; import static java.util.concurrent.TimeUnit.NANOSECONDS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; +import static org.assertj.core.api.Assertions.assertThat; public class TestOperatorStats { @@ -145,50 +144,50 @@ public void testJson() public static void assertExpectedOperatorStats(OperatorStats actual) { - assertEquals(actual.getStageId(), 0); - assertEquals(actual.getOperatorId(), 41); - assertEquals(actual.getOperatorType(), "test"); - - assertEquals(actual.getTotalDrivers(), 1); - assertEquals(actual.getAddInputCalls(), 2); - assertEquals(actual.getAddInputWall(), new Duration(3, NANOSECONDS)); - assertEquals(actual.getAddInputCpu(), new Duration(4, NANOSECONDS)); - assertEquals(actual.getPhysicalInputDataSize(), DataSize.ofBytes(51)); - assertEquals(actual.getPhysicalInputPositions(), 511); - assertEquals(actual.getPhysicalInputReadTime(), new Duration(5, NANOSECONDS)); - assertEquals(actual.getInternalNetworkInputDataSize(), DataSize.ofBytes(52)); - assertEquals(actual.getInternalNetworkInputPositions(), 522); - assertEquals(actual.getRawInputDataSize(), DataSize.ofBytes(5)); - assertEquals(actual.getInputDataSize(), DataSize.ofBytes(6)); - assertEquals(actual.getInputPositions(), 7); - assertEquals(actual.getSumSquaredInputPositions(), 8.0); - - assertEquals(actual.getGetOutputCalls(), 9); - assertEquals(actual.getGetOutputWall(), new Duration(10, NANOSECONDS)); - assertEquals(actual.getGetOutputCpu(), new Duration(11, NANOSECONDS)); - assertEquals(actual.getOutputDataSize(), DataSize.ofBytes(12)); - assertEquals(actual.getOutputPositions(), 13); - - assertEquals(actual.getDynamicFilterSplitsProcessed(), 533); - assertEquals(actual.getMetrics().getMetrics(), ImmutableMap.of("metrics", new LongCount(42))); - assertEquals(actual.getConnectorMetrics().getMetrics(), ImmutableMap.of("connectorMetrics", new LongCount(43))); - - assertEquals(actual.getPhysicalWrittenDataSize(), DataSize.ofBytes(14)); - - assertEquals(actual.getBlockedWall(), new Duration(15, NANOSECONDS)); - - assertEquals(actual.getFinishCalls(), 16); - assertEquals(actual.getFinishWall(), new Duration(17, NANOSECONDS)); - assertEquals(actual.getFinishCpu(), new Duration(18, NANOSECONDS)); - - assertEquals(actual.getUserMemoryReservation(), DataSize.ofBytes(19)); - assertEquals(actual.getRevocableMemoryReservation(), DataSize.ofBytes(20)); - assertEquals(actual.getPeakUserMemoryReservation(), DataSize.ofBytes(22)); - assertEquals(actual.getPeakRevocableMemoryReservation(), DataSize.ofBytes(24)); - assertEquals(actual.getPeakTotalMemoryReservation(), DataSize.ofBytes(25)); - assertEquals(actual.getSpilledDataSize(), DataSize.ofBytes(26)); - assertEquals(actual.getInfo().getClass(), SplitOperatorInfo.class); - assertEquals(((SplitOperatorInfo) actual.getInfo()).getSplitInfo(), NON_MERGEABLE_INFO.getSplitInfo()); + assertThat(actual.getStageId()).isEqualTo(0); + assertThat(actual.getOperatorId()).isEqualTo(41); + assertThat(actual.getOperatorType()).isEqualTo("test"); + + assertThat(actual.getTotalDrivers()).isEqualTo(1); + assertThat(actual.getAddInputCalls()).isEqualTo(2); + assertThat(actual.getAddInputWall()).isEqualTo(new Duration(3, NANOSECONDS)); + assertThat(actual.getAddInputCpu()).isEqualTo(new Duration(4, NANOSECONDS)); + assertThat(actual.getPhysicalInputDataSize()).isEqualTo(DataSize.ofBytes(51)); + assertThat(actual.getPhysicalInputPositions()).isEqualTo(511); + assertThat(actual.getPhysicalInputReadTime()).isEqualTo(new Duration(5, NANOSECONDS)); + assertThat(actual.getInternalNetworkInputDataSize()).isEqualTo(DataSize.ofBytes(52)); + assertThat(actual.getInternalNetworkInputPositions()).isEqualTo(522); + assertThat(actual.getRawInputDataSize()).isEqualTo(DataSize.ofBytes(5)); + assertThat(actual.getInputDataSize()).isEqualTo(DataSize.ofBytes(6)); + assertThat(actual.getInputPositions()).isEqualTo(7); + assertThat(actual.getSumSquaredInputPositions()).isEqualTo(8.0); + + assertThat(actual.getGetOutputCalls()).isEqualTo(9); + assertThat(actual.getGetOutputWall()).isEqualTo(new Duration(10, NANOSECONDS)); + assertThat(actual.getGetOutputCpu()).isEqualTo(new Duration(11, NANOSECONDS)); + assertThat(actual.getOutputDataSize()).isEqualTo(DataSize.ofBytes(12)); + assertThat(actual.getOutputPositions()).isEqualTo(13); + + assertThat(actual.getDynamicFilterSplitsProcessed()).isEqualTo(533); + assertThat(actual.getMetrics().getMetrics()).isEqualTo(ImmutableMap.of("metrics", new LongCount(42))); + assertThat(actual.getConnectorMetrics().getMetrics()).isEqualTo(ImmutableMap.of("connectorMetrics", new LongCount(43))); + + assertThat(actual.getPhysicalWrittenDataSize()).isEqualTo(DataSize.ofBytes(14)); + + assertThat(actual.getBlockedWall()).isEqualTo(new Duration(15, NANOSECONDS)); + + assertThat(actual.getFinishCalls()).isEqualTo(16); + assertThat(actual.getFinishWall()).isEqualTo(new Duration(17, NANOSECONDS)); + assertThat(actual.getFinishCpu()).isEqualTo(new Duration(18, NANOSECONDS)); + + assertThat(actual.getUserMemoryReservation()).isEqualTo(DataSize.ofBytes(19)); + assertThat(actual.getRevocableMemoryReservation()).isEqualTo(DataSize.ofBytes(20)); + assertThat(actual.getPeakUserMemoryReservation()).isEqualTo(DataSize.ofBytes(22)); + assertThat(actual.getPeakRevocableMemoryReservation()).isEqualTo(DataSize.ofBytes(24)); + assertThat(actual.getPeakTotalMemoryReservation()).isEqualTo(DataSize.ofBytes(25)); + assertThat(actual.getSpilledDataSize()).isEqualTo(DataSize.ofBytes(26)); + assertThat(actual.getInfo().getClass()).isEqualTo(SplitOperatorInfo.class); + assertThat(((SplitOperatorInfo) actual.getInfo()).getSplitInfo()).isEqualTo(NON_MERGEABLE_INFO.getSplitInfo()); } @Test @@ -196,48 +195,48 @@ public void testAdd() { OperatorStats actual = EXPECTED.add(ImmutableList.of(EXPECTED, EXPECTED)); - assertEquals(actual.getStageId(), 0); - assertEquals(actual.getOperatorId(), 41); - assertEquals(actual.getOperatorType(), "test"); - - assertEquals(actual.getTotalDrivers(), 3 * 1); - assertEquals(actual.getAddInputCalls(), 3 * 2); - assertEquals(actual.getAddInputWall(), new Duration(3 * 3, NANOSECONDS)); - assertEquals(actual.getAddInputCpu(), new Duration(3 * 4, NANOSECONDS)); - assertEquals(actual.getPhysicalInputDataSize(), DataSize.ofBytes(3 * 51)); - assertEquals(actual.getPhysicalInputPositions(), 3 * 511); - assertEquals(actual.getPhysicalInputReadTime(), new Duration(3 * 5, NANOSECONDS)); - assertEquals(actual.getInternalNetworkInputDataSize(), DataSize.ofBytes(3 * 52)); - assertEquals(actual.getInternalNetworkInputPositions(), 3 * 522); - assertEquals(actual.getRawInputDataSize(), DataSize.ofBytes(3 * 5)); - assertEquals(actual.getInputDataSize(), DataSize.ofBytes(3 * 6)); - assertEquals(actual.getInputPositions(), 3 * 7); - assertEquals(actual.getSumSquaredInputPositions(), 3 * 8.0); - - assertEquals(actual.getGetOutputCalls(), 3 * 9); - assertEquals(actual.getGetOutputWall(), new Duration(3 * 10, NANOSECONDS)); - assertEquals(actual.getGetOutputCpu(), new Duration(3 * 11, NANOSECONDS)); - assertEquals(actual.getOutputDataSize(), DataSize.ofBytes(3 * 12)); - assertEquals(actual.getOutputPositions(), 3 * 13); - - assertEquals(actual.getDynamicFilterSplitsProcessed(), 3 * 533); - assertEquals(actual.getMetrics().getMetrics(), ImmutableMap.of("metrics", new LongCount(3 * 42))); - assertEquals(actual.getConnectorMetrics().getMetrics(), ImmutableMap.of("connectorMetrics", new LongCount(3 * 43))); - - assertEquals(actual.getPhysicalWrittenDataSize(), DataSize.ofBytes(3 * 14)); - - assertEquals(actual.getBlockedWall(), new Duration(3 * 15, NANOSECONDS)); - - assertEquals(actual.getFinishCalls(), 3 * 16); - assertEquals(actual.getFinishWall(), new Duration(3 * 17, NANOSECONDS)); - assertEquals(actual.getFinishCpu(), new Duration(3 * 18, NANOSECONDS)); - assertEquals(actual.getUserMemoryReservation(), DataSize.ofBytes(3 * 19)); - assertEquals(actual.getRevocableMemoryReservation(), DataSize.ofBytes(3 * 20)); - assertEquals(actual.getPeakUserMemoryReservation(), DataSize.ofBytes(22)); - assertEquals(actual.getPeakRevocableMemoryReservation(), DataSize.ofBytes(24)); - assertEquals(actual.getPeakTotalMemoryReservation(), DataSize.ofBytes(25)); - assertEquals(actual.getSpilledDataSize(), DataSize.ofBytes(3 * 26)); - assertNull(actual.getInfo()); + assertThat(actual.getStageId()).isEqualTo(0); + assertThat(actual.getOperatorId()).isEqualTo(41); + assertThat(actual.getOperatorType()).isEqualTo("test"); + + assertThat(actual.getTotalDrivers()).isEqualTo(3 * 1); + assertThat(actual.getAddInputCalls()).isEqualTo(3 * 2); + assertThat(actual.getAddInputWall()).isEqualTo(new Duration(3 * 3, NANOSECONDS)); + assertThat(actual.getAddInputCpu()).isEqualTo(new Duration(3 * 4, NANOSECONDS)); + assertThat(actual.getPhysicalInputDataSize()).isEqualTo(DataSize.ofBytes(3 * 51)); + assertThat(actual.getPhysicalInputPositions()).isEqualTo(3 * 511); + assertThat(actual.getPhysicalInputReadTime()).isEqualTo(new Duration(3 * 5, NANOSECONDS)); + assertThat(actual.getInternalNetworkInputDataSize()).isEqualTo(DataSize.ofBytes(3 * 52)); + assertThat(actual.getInternalNetworkInputPositions()).isEqualTo(3 * 522); + assertThat(actual.getRawInputDataSize()).isEqualTo(DataSize.ofBytes(3 * 5)); + assertThat(actual.getInputDataSize()).isEqualTo(DataSize.ofBytes(3 * 6)); + assertThat(actual.getInputPositions()).isEqualTo(3 * 7); + assertThat(actual.getSumSquaredInputPositions()).isEqualTo(3 * 8.0); + + assertThat(actual.getGetOutputCalls()).isEqualTo(3 * 9); + assertThat(actual.getGetOutputWall()).isEqualTo(new Duration(3 * 10, NANOSECONDS)); + assertThat(actual.getGetOutputCpu()).isEqualTo(new Duration(3 * 11, NANOSECONDS)); + assertThat(actual.getOutputDataSize()).isEqualTo(DataSize.ofBytes(3 * 12)); + assertThat(actual.getOutputPositions()).isEqualTo(3 * 13); + + assertThat(actual.getDynamicFilterSplitsProcessed()).isEqualTo(3 * 533); + assertThat(actual.getMetrics().getMetrics()).isEqualTo(ImmutableMap.of("metrics", new LongCount(3 * 42))); + assertThat(actual.getConnectorMetrics().getMetrics()).isEqualTo(ImmutableMap.of("connectorMetrics", new LongCount(3 * 43))); + + assertThat(actual.getPhysicalWrittenDataSize()).isEqualTo(DataSize.ofBytes(3 * 14)); + + assertThat(actual.getBlockedWall()).isEqualTo(new Duration(3 * 15, NANOSECONDS)); + + assertThat(actual.getFinishCalls()).isEqualTo(3 * 16); + assertThat(actual.getFinishWall()).isEqualTo(new Duration(3 * 17, NANOSECONDS)); + assertThat(actual.getFinishCpu()).isEqualTo(new Duration(3 * 18, NANOSECONDS)); + assertThat(actual.getUserMemoryReservation()).isEqualTo(DataSize.ofBytes(3 * 19)); + assertThat(actual.getRevocableMemoryReservation()).isEqualTo(DataSize.ofBytes(3 * 20)); + assertThat(actual.getPeakUserMemoryReservation()).isEqualTo(DataSize.ofBytes(22)); + assertThat(actual.getPeakRevocableMemoryReservation()).isEqualTo(DataSize.ofBytes(24)); + assertThat(actual.getPeakTotalMemoryReservation()).isEqualTo(DataSize.ofBytes(25)); + assertThat(actual.getSpilledDataSize()).isEqualTo(DataSize.ofBytes(3 * 26)); + assertThat(actual.getInfo()).isNull(); } @Test @@ -245,48 +244,48 @@ public void testAddMergeable() { OperatorStats actual = MERGEABLE.add(ImmutableList.of(MERGEABLE, MERGEABLE)); - assertEquals(actual.getStageId(), 0); - assertEquals(actual.getOperatorId(), 41); - assertEquals(actual.getOperatorType(), "test"); - - assertEquals(actual.getTotalDrivers(), 3 * 1); - assertEquals(actual.getAddInputCalls(), 3 * 2); - assertEquals(actual.getAddInputWall(), new Duration(3 * 3, NANOSECONDS)); - assertEquals(actual.getAddInputCpu(), new Duration(3 * 4, NANOSECONDS)); - assertEquals(actual.getPhysicalInputDataSize(), DataSize.ofBytes(3 * 51)); - assertEquals(actual.getPhysicalInputPositions(), 3 * 511); - assertEquals(actual.getPhysicalInputReadTime(), new Duration(3 * 5, NANOSECONDS)); - assertEquals(actual.getInternalNetworkInputDataSize(), DataSize.ofBytes(3 * 52)); - assertEquals(actual.getInternalNetworkInputPositions(), 3 * 522); - assertEquals(actual.getRawInputDataSize(), DataSize.ofBytes(3 * 5)); - assertEquals(actual.getInputDataSize(), DataSize.ofBytes(3 * 6)); - assertEquals(actual.getInputPositions(), 3 * 7); - assertEquals(actual.getSumSquaredInputPositions(), 3 * 8.0); - - assertEquals(actual.getGetOutputCalls(), 3 * 9); - assertEquals(actual.getGetOutputWall(), new Duration(3 * 10, NANOSECONDS)); - assertEquals(actual.getGetOutputCpu(), new Duration(3 * 11, NANOSECONDS)); - assertEquals(actual.getOutputDataSize(), DataSize.ofBytes(3 * 12)); - assertEquals(actual.getOutputPositions(), 3 * 13); - - assertEquals(actual.getDynamicFilterSplitsProcessed(), 3 * 533); - assertEquals(actual.getMetrics().getMetrics(), ImmutableMap.of("metrics", new LongCount(3 * 42))); - assertEquals(actual.getConnectorMetrics().getMetrics(), ImmutableMap.of("connectorMetrics", new LongCount(3 * 43))); - - assertEquals(actual.getPhysicalWrittenDataSize(), DataSize.ofBytes(3 * 14)); - - assertEquals(actual.getBlockedWall(), new Duration(3 * 15, NANOSECONDS)); - - assertEquals(actual.getFinishCalls(), 3 * 16); - assertEquals(actual.getFinishWall(), new Duration(3 * 17, NANOSECONDS)); - assertEquals(actual.getFinishCpu(), new Duration(3 * 18, NANOSECONDS)); - assertEquals(actual.getUserMemoryReservation(), DataSize.ofBytes(3 * 19)); - assertEquals(actual.getRevocableMemoryReservation(), DataSize.ofBytes(3 * 20)); - assertEquals(actual.getPeakUserMemoryReservation(), DataSize.ofBytes(22)); - assertEquals(actual.getPeakRevocableMemoryReservation(), DataSize.ofBytes(24)); - assertEquals(actual.getPeakTotalMemoryReservation(), DataSize.ofBytes(25)); - assertEquals(actual.getSpilledDataSize(), DataSize.ofBytes(3 * 26)); - assertEquals(actual.getInfo().getClass(), PartitionedOutputInfo.class); - assertEquals(((PartitionedOutputInfo) actual.getInfo()).getOutputBufferPeakMemoryUsage(), MERGEABLE_INFO.getOutputBufferPeakMemoryUsage()); + assertThat(actual.getStageId()).isEqualTo(0); + assertThat(actual.getOperatorId()).isEqualTo(41); + assertThat(actual.getOperatorType()).isEqualTo("test"); + + assertThat(actual.getTotalDrivers()).isEqualTo(3 * 1); + assertThat(actual.getAddInputCalls()).isEqualTo(3 * 2); + assertThat(actual.getAddInputWall()).isEqualTo(new Duration(3 * 3, NANOSECONDS)); + assertThat(actual.getAddInputCpu()).isEqualTo(new Duration(3 * 4, NANOSECONDS)); + assertThat(actual.getPhysicalInputDataSize()).isEqualTo(DataSize.ofBytes(3 * 51)); + assertThat(actual.getPhysicalInputPositions()).isEqualTo(3 * 511); + assertThat(actual.getPhysicalInputReadTime()).isEqualTo(new Duration(3 * 5, NANOSECONDS)); + assertThat(actual.getInternalNetworkInputDataSize()).isEqualTo(DataSize.ofBytes(3 * 52)); + assertThat(actual.getInternalNetworkInputPositions()).isEqualTo(3 * 522); + assertThat(actual.getRawInputDataSize()).isEqualTo(DataSize.ofBytes(3 * 5)); + assertThat(actual.getInputDataSize()).isEqualTo(DataSize.ofBytes(3 * 6)); + assertThat(actual.getInputPositions()).isEqualTo(3 * 7); + assertThat(actual.getSumSquaredInputPositions()).isEqualTo(3 * 8.0); + + assertThat(actual.getGetOutputCalls()).isEqualTo(3 * 9); + assertThat(actual.getGetOutputWall()).isEqualTo(new Duration(3 * 10, NANOSECONDS)); + assertThat(actual.getGetOutputCpu()).isEqualTo(new Duration(3 * 11, NANOSECONDS)); + assertThat(actual.getOutputDataSize()).isEqualTo(DataSize.ofBytes(3 * 12)); + assertThat(actual.getOutputPositions()).isEqualTo(3 * 13); + + assertThat(actual.getDynamicFilterSplitsProcessed()).isEqualTo(3 * 533); + assertThat(actual.getMetrics().getMetrics()).isEqualTo(ImmutableMap.of("metrics", new LongCount(3 * 42))); + assertThat(actual.getConnectorMetrics().getMetrics()).isEqualTo(ImmutableMap.of("connectorMetrics", new LongCount(3 * 43))); + + assertThat(actual.getPhysicalWrittenDataSize()).isEqualTo(DataSize.ofBytes(3 * 14)); + + assertThat(actual.getBlockedWall()).isEqualTo(new Duration(3 * 15, NANOSECONDS)); + + assertThat(actual.getFinishCalls()).isEqualTo(3 * 16); + assertThat(actual.getFinishWall()).isEqualTo(new Duration(3 * 17, NANOSECONDS)); + assertThat(actual.getFinishCpu()).isEqualTo(new Duration(3 * 18, NANOSECONDS)); + assertThat(actual.getUserMemoryReservation()).isEqualTo(DataSize.ofBytes(3 * 19)); + assertThat(actual.getRevocableMemoryReservation()).isEqualTo(DataSize.ofBytes(3 * 20)); + assertThat(actual.getPeakUserMemoryReservation()).isEqualTo(DataSize.ofBytes(22)); + assertThat(actual.getPeakRevocableMemoryReservation()).isEqualTo(DataSize.ofBytes(24)); + assertThat(actual.getPeakTotalMemoryReservation()).isEqualTo(DataSize.ofBytes(25)); + assertThat(actual.getSpilledDataSize()).isEqualTo(DataSize.ofBytes(3 * 26)); + assertThat(actual.getInfo().getClass()).isEqualTo(PartitionedOutputInfo.class); + assertThat(((PartitionedOutputInfo) actual.getInfo()).getOutputBufferPeakMemoryUsage()).isEqualTo(MERGEABLE_INFO.getOutputBufferPeakMemoryUsage()); } } diff --git a/core/trino-main/src/test/java/io/trino/operator/TestOrderByOperator.java b/core/trino-main/src/test/java/io/trino/operator/TestOrderByOperator.java index f00b0fc5fa15..ed23a1d2860d 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestOrderByOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestOrderByOperator.java @@ -51,9 +51,8 @@ import static java.lang.String.format; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; @Test(singleThreaded = true) public class TestOrderByOperator @@ -123,9 +122,11 @@ public void testMultipleOutputPages(boolean spillEnabled, boolean revokeMemoryWh assertGreaterThan(pages.size(), 1, "Expected more than one output page"); MaterializedResult actual = toMaterializedResult(driverContext.getSession(), expected.getTypes(), pages); - assertEquals(actual.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(actual.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); - assertTrue(spillEnabled == (spillerFactory.getSpillsCount() > 0), format("Spill state mismatch. Expected spill: %s, spill count: %s", spillEnabled, spillerFactory.getSpillsCount())); + assertThat(spillEnabled == (spillerFactory.getSpillsCount() > 0)) + .describedAs(format("Spill state mismatch. Expected spill: %s, spill count: %s", spillEnabled, spillerFactory.getSpillsCount())) + .isTrue(); } @Test(dataProvider = "spillEnabled") diff --git a/core/trino-main/src/test/java/io/trino/operator/TestPageUtils.java b/core/trino-main/src/test/java/io/trino/operator/TestPageUtils.java index 42f41023daae..821c2f5369d2 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestPageUtils.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestPageUtils.java @@ -24,7 +24,7 @@ import static io.trino.block.BlockAssertions.createIntsBlock; import static io.trino.operator.PageUtils.recordMaterializedBytes; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestPageUtils { @@ -41,10 +41,10 @@ public void testRecordMaterializedBytes() AtomicLong sizeInBytes = new AtomicLong(); recordMaterializedBytes(page, sizeInBytes::getAndAdd); - assertEquals(sizeInBytes.get(), first.getSizeInBytes() * 2); + assertThat(sizeInBytes.get()).isEqualTo(first.getSizeInBytes() * 2); page.getBlock(2).getLoadedBlock(); - assertEquals(sizeInBytes.get(), first.getSizeInBytes() * 3); + assertThat(sizeInBytes.get()).isEqualTo(first.getSizeInBytes() * 3); } @Test @@ -58,13 +58,13 @@ public void testNestedBlocks() AtomicLong sizeInBytes = new AtomicLong(); recordMaterializedBytes(page, sizeInBytes::getAndAdd); - assertEquals(arrayBlock.getSizeInBytes(), initialArraySize); - assertEquals(sizeInBytes.get(), arrayBlock.getSizeInBytes()); + assertThat(arrayBlock.getSizeInBytes()).isEqualTo(initialArraySize); + assertThat(sizeInBytes.get()).isEqualTo(arrayBlock.getSizeInBytes()); // dictionary block caches size in bytes arrayBlock.getLoadedBlock(); - assertEquals(sizeInBytes.get(), arrayBlock.getSizeInBytes()); - assertEquals(sizeInBytes.get(), initialArraySize + elements.getSizeInBytes()); + assertThat(sizeInBytes.get()).isEqualTo(arrayBlock.getSizeInBytes()); + assertThat(sizeInBytes.get()).isEqualTo(initialArraySize + elements.getSizeInBytes()); } private static LazyBlock lazyWrapper(Block block) diff --git a/core/trino-main/src/test/java/io/trino/operator/TestPagesIndex.java b/core/trino-main/src/test/java/io/trino/operator/TestPagesIndex.java index 94c5c8173889..089cd72a38b5 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestPagesIndex.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestPagesIndex.java @@ -38,9 +38,6 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.data.Percentage.withPercentage; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestPagesIndex { @@ -51,26 +48,31 @@ public void testEstimatedSize() PagesIndex pagesIndex = newPagesIndex(types, 30, false); long initialEstimatedSize = pagesIndex.getEstimatedSize().toBytes(); - assertTrue(initialEstimatedSize > 0, format("Initial estimated size must be positive, got %s", initialEstimatedSize)); + assertThat(initialEstimatedSize > 0) + .describedAs(format("Initial estimated size must be positive, got %s", initialEstimatedSize)) + .isTrue(); pagesIndex.addPage(somePage(types)); long estimatedSizeWithOnePage = pagesIndex.getEstimatedSize().toBytes(); - assertTrue(estimatedSizeWithOnePage > initialEstimatedSize, "Estimated size should grow after adding a page"); + assertThat(estimatedSizeWithOnePage > initialEstimatedSize) + .describedAs("Estimated size should grow after adding a page") + .isTrue(); pagesIndex.addPage(somePage(types)); long estimatedSizeWithTwoPages = pagesIndex.getEstimatedSize().toBytes(); - assertEquals( - estimatedSizeWithTwoPages, - initialEstimatedSize + (estimatedSizeWithOnePage - initialEstimatedSize) * 2, - "Estimated size should grow linearly as long as we don't pass expectedPositions"); + assertThat(estimatedSizeWithTwoPages) + .describedAs("Estimated size should grow linearly as long as we don't pass expectedPositions") + .isEqualTo(initialEstimatedSize + (estimatedSizeWithOnePage - initialEstimatedSize) * 2); pagesIndex.compact(); long estimatedSizeAfterCompact = pagesIndex.getEstimatedSize().toBytes(); // We can expect compact to reduce size because VARCHAR sequence pages are compactable. - assertTrue(estimatedSizeAfterCompact < estimatedSizeWithTwoPages, format( - "Compact should reduce (or retain) size, but changed from %s to %s", - estimatedSizeWithTwoPages, - estimatedSizeAfterCompact)); + assertThat(estimatedSizeAfterCompact < estimatedSizeWithTwoPages) + .describedAs(format( + "Compact should reduce (or retain) size, but changed from %s to %s", + estimatedSizeWithTwoPages, + estimatedSizeAfterCompact)) + .isTrue(); } @Test @@ -87,13 +89,13 @@ public void testEagerCompact() // We can expect eagerCompactPagesIndex retained less data than lazyCompactPagesIndex because // the pages used in the test (VARCHAR sequence pages) are compactable. - assertTrue( - eagerCompactPagesIndex.getEstimatedSize().toBytes() < lazyCompactPagesIndex.getEstimatedSize().toBytes(), - "Expect eagerCompactPagesIndex retained less data than lazyCompactPagesIndex after adding the page, because the pages used in the test are compactable."); + assertThat(eagerCompactPagesIndex.getEstimatedSize().toBytes() < lazyCompactPagesIndex.getEstimatedSize().toBytes()) + .describedAs("Expect eagerCompactPagesIndex retained less data than lazyCompactPagesIndex after adding the page, because the pages used in the test are compactable.") + .isTrue(); } lazyCompactPagesIndex.compact(); - assertEquals(lazyCompactPagesIndex.getEstimatedSize(), eagerCompactPagesIndex.getEstimatedSize()); + assertThat(lazyCompactPagesIndex.getEstimatedSize()).isEqualTo(eagerCompactPagesIndex.getEstimatedSize()); } @Test @@ -105,7 +107,7 @@ public void testCompactWithNoColumns() index.compact(); - assertEquals(index.getPositionCount(), 30); + assertThat(index.getPositionCount()).isEqualTo(30); } @Test @@ -116,9 +118,9 @@ public void testGetPagesWithNoColumns() index.addPage(new Page(20)); Iterator pages = index.getPages(); - assertEquals(pages.next().getPositionCount(), 10); - assertEquals(pages.next().getPositionCount(), 20); - assertFalse(pages.hasNext()); + assertThat(pages.next().getPositionCount()).isEqualTo(10); + assertThat(pages.next().getPositionCount()).isEqualTo(20); + assertThat(pages.hasNext()).isFalse(); } @Test diff --git a/core/trino-main/src/test/java/io/trino/operator/TestPipelineStats.java b/core/trino-main/src/test/java/io/trino/operator/TestPipelineStats.java index b6a45bc8bd4d..bb4a39459c9f 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestPipelineStats.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestPipelineStats.java @@ -26,8 +26,8 @@ import static io.trino.operator.TestDriverStats.assertExpectedDriverStats; import static io.trino.operator.TestOperatorStats.assertExpectedOperatorStats; import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static org.assertj.core.api.Assertions.assertThat; import static org.joda.time.DateTimeZone.UTC; -import static org.testng.Assert.assertEquals; public class TestPipelineStats { @@ -101,58 +101,58 @@ public void testJson() public static void assertExpectedPipelineStats(PipelineStats actual) { - assertEquals(actual.getFirstStartTime(), new DateTime(100, UTC)); - assertEquals(actual.getLastStartTime(), new DateTime(101, UTC)); - assertEquals(actual.getLastEndTime(), new DateTime(102, UTC)); - assertEquals(actual.isInputPipeline(), true); - assertEquals(actual.isOutputPipeline(), false); + assertThat(actual.getFirstStartTime()).isEqualTo(new DateTime(100, UTC)); + assertThat(actual.getLastStartTime()).isEqualTo(new DateTime(101, UTC)); + assertThat(actual.getLastEndTime()).isEqualTo(new DateTime(102, UTC)); + assertThat(actual.isInputPipeline()).isEqualTo(true); + assertThat(actual.isOutputPipeline()).isEqualTo(false); - assertEquals(actual.getTotalDrivers(), 1); - assertEquals(actual.getQueuedDrivers(), 2); - assertEquals(actual.getQueuedPartitionedDrivers(), 1); - assertEquals(actual.getQueuedPartitionedSplitsWeight(), 21L); - assertEquals(actual.getRunningDrivers(), 3); - assertEquals(actual.getRunningPartitionedDrivers(), 2); - assertEquals(actual.getRunningPartitionedSplitsWeight(), 22L); - assertEquals(actual.getBlockedDrivers(), 19); - assertEquals(actual.getCompletedDrivers(), 4); + assertThat(actual.getTotalDrivers()).isEqualTo(1); + assertThat(actual.getQueuedDrivers()).isEqualTo(2); + assertThat(actual.getQueuedPartitionedDrivers()).isEqualTo(1); + assertThat(actual.getQueuedPartitionedSplitsWeight()).isEqualTo(21L); + assertThat(actual.getRunningDrivers()).isEqualTo(3); + assertThat(actual.getRunningPartitionedDrivers()).isEqualTo(2); + assertThat(actual.getRunningPartitionedSplitsWeight()).isEqualTo(22L); + assertThat(actual.getBlockedDrivers()).isEqualTo(19); + assertThat(actual.getCompletedDrivers()).isEqualTo(4); - assertEquals(actual.getUserMemoryReservation(), DataSize.ofBytes(5)); - assertEquals(actual.getRevocableMemoryReservation(), DataSize.ofBytes(6)); + assertThat(actual.getUserMemoryReservation()).isEqualTo(DataSize.ofBytes(5)); + assertThat(actual.getRevocableMemoryReservation()).isEqualTo(DataSize.ofBytes(6)); - assertEquals(actual.getQueuedTime().getCount(), 8.0); - assertEquals(actual.getElapsedTime().getCount(), 9.0); + assertThat(actual.getQueuedTime().getCount()).isEqualTo(8.0); + assertThat(actual.getElapsedTime().getCount()).isEqualTo(9.0); - assertEquals(actual.getTotalScheduledTime(), new Duration(10, NANOSECONDS)); - assertEquals(actual.getTotalCpuTime(), new Duration(11, NANOSECONDS)); - assertEquals(actual.getTotalBlockedTime(), new Duration(13, NANOSECONDS)); + assertThat(actual.getTotalScheduledTime()).isEqualTo(new Duration(10, NANOSECONDS)); + assertThat(actual.getTotalCpuTime()).isEqualTo(new Duration(11, NANOSECONDS)); + assertThat(actual.getTotalBlockedTime()).isEqualTo(new Duration(13, NANOSECONDS)); - assertEquals(actual.getPhysicalInputDataSize(), DataSize.ofBytes(141)); - assertEquals(actual.getPhysicalInputPositions(), 151); - assertEquals(actual.getPhysicalInputReadTime(), new Duration(14, NANOSECONDS)); + assertThat(actual.getPhysicalInputDataSize()).isEqualTo(DataSize.ofBytes(141)); + assertThat(actual.getPhysicalInputPositions()).isEqualTo(151); + assertThat(actual.getPhysicalInputReadTime()).isEqualTo(new Duration(14, NANOSECONDS)); - assertEquals(actual.getInternalNetworkInputDataSize(), DataSize.ofBytes(142)); - assertEquals(actual.getInternalNetworkInputPositions(), 152); + assertThat(actual.getInternalNetworkInputDataSize()).isEqualTo(DataSize.ofBytes(142)); + assertThat(actual.getInternalNetworkInputPositions()).isEqualTo(152); - assertEquals(actual.getRawInputDataSize(), DataSize.ofBytes(14)); - assertEquals(actual.getRawInputPositions(), 15); + assertThat(actual.getRawInputDataSize()).isEqualTo(DataSize.ofBytes(14)); + assertThat(actual.getRawInputPositions()).isEqualTo(15); - assertEquals(actual.getProcessedInputDataSize(), DataSize.ofBytes(16)); - assertEquals(actual.getProcessedInputPositions(), 17); + assertThat(actual.getProcessedInputDataSize()).isEqualTo(DataSize.ofBytes(16)); + assertThat(actual.getProcessedInputPositions()).isEqualTo(17); - assertEquals(actual.getInputBlockedTime(), new Duration(101, NANOSECONDS)); + assertThat(actual.getInputBlockedTime()).isEqualTo(new Duration(101, NANOSECONDS)); - assertEquals(actual.getOutputDataSize(), DataSize.ofBytes(18)); - assertEquals(actual.getOutputPositions(), 19); + assertThat(actual.getOutputDataSize()).isEqualTo(DataSize.ofBytes(18)); + assertThat(actual.getOutputPositions()).isEqualTo(19); - assertEquals(actual.getOutputBlockedTime(), new Duration(102, NANOSECONDS)); + assertThat(actual.getOutputBlockedTime()).isEqualTo(new Duration(102, NANOSECONDS)); - assertEquals(actual.getPhysicalWrittenDataSize(), DataSize.ofBytes(20)); + assertThat(actual.getPhysicalWrittenDataSize()).isEqualTo(DataSize.ofBytes(20)); - assertEquals(actual.getOperatorSummaries().size(), 1); + assertThat(actual.getOperatorSummaries().size()).isEqualTo(1); assertExpectedOperatorStats(actual.getOperatorSummaries().get(0)); - assertEquals(actual.getDrivers().size(), 1); + assertThat(actual.getDrivers().size()).isEqualTo(1); assertExpectedDriverStats(actual.getDrivers().get(0)); } diff --git a/core/trino-main/src/test/java/io/trino/operator/TestRowNumberOperator.java b/core/trino-main/src/test/java/io/trino/operator/TestRowNumberOperator.java index 7ea9d3434213..da3c3c47f9d1 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestRowNumberOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestRowNumberOperator.java @@ -55,10 +55,9 @@ import static io.trino.testing.TestingTaskContext.createTaskContext; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -135,7 +134,7 @@ public void testRowNumberUnpartitioned() List pages = toPages(operatorFactory, driverContext, input); Block rowNumberColumn = getRowNumberColumn(pages); - assertEquals(rowNumberColumn.getPositionCount(), 10); + assertThat(rowNumberColumn.getPositionCount()).isEqualTo(10); pages = stripRowNumberColumn(pages); MaterializedResult actual = toMaterializedResult(driverContext.getSession(), ImmutableList.of(DOUBLE, BIGINT), pages); @@ -167,13 +166,13 @@ public void testMemoryReservationYield() int count = 0; for (Page page : result.getOutput()) { - assertEquals(page.getChannelCount(), 3); + assertThat(page.getChannelCount()).isEqualTo(3); for (int i = 0; i < page.getPositionCount(); i++) { - assertEquals(page.getBlock(2).getLong(i, 0), 1); + assertThat(page.getBlock(2).getLong(i, 0)).isEqualTo(1); count++; } } - assertEquals(count, 6_000 * 600); + assertThat(count).isEqualTo(6_000 * 600); } } @@ -231,7 +230,7 @@ public void testRowNumberPartitioned() List pages = toPages(operatorFactory, driverContext, input); Block rowNumberColumn = getRowNumberColumn(pages); - assertEquals(rowNumberColumn.getPositionCount(), 10); + assertThat(rowNumberColumn.getPositionCount()).isEqualTo(10); pages = stripRowNumberColumn(pages); MaterializedResult actual = toMaterializedResult(driverContext.getSession(), ImmutableList.of(DOUBLE, BIGINT), pages); @@ -239,9 +238,9 @@ public void testRowNumberPartitioned() ImmutableSet expectedPartition1Set = ImmutableSet.copyOf(expectedPartition1.getMaterializedRows()); ImmutableSet expectedPartition2Set = ImmutableSet.copyOf(expectedPartition2.getMaterializedRows()); ImmutableSet expectedPartition3Set = ImmutableSet.copyOf(expectedPartition3.getMaterializedRows()); - assertEquals(Sets.intersection(expectedPartition1Set, actualSet).size(), 4); - assertEquals(Sets.intersection(expectedPartition2Set, actualSet).size(), 4); - assertEquals(Sets.intersection(expectedPartition3Set, actualSet).size(), 2); + assertThat(Sets.intersection(expectedPartition1Set, actualSet).size()).isEqualTo(4); + assertThat(Sets.intersection(expectedPartition2Set, actualSet).size()).isEqualTo(4); + assertThat(Sets.intersection(expectedPartition3Set, actualSet).size()).isEqualTo(2); } } @@ -299,10 +298,10 @@ public void testRowNumberPartitionedLimit() List pages = toPages(operatorFactory, driverContext, input); Block rowNumberColumn = getRowNumberColumn(pages); - assertEquals(rowNumberColumn.getPositionCount(), 8); + assertThat(rowNumberColumn.getPositionCount()).isEqualTo(8); // Check that all row numbers generated are <= 3 for (int i = 0; i < rowNumberColumn.getPositionCount(); i++) { - assertTrue(rowNumberColumn.getLong(i, 0) <= 3); + assertThat(rowNumberColumn.getLong(i, 0) <= 3).isTrue(); } pages = stripRowNumberColumn(pages); @@ -311,9 +310,9 @@ public void testRowNumberPartitionedLimit() ImmutableSet expectedPartition1Set = ImmutableSet.copyOf(expectedPartition1.getMaterializedRows()); ImmutableSet expectedPartition2Set = ImmutableSet.copyOf(expectedPartition2.getMaterializedRows()); ImmutableSet expectedPartition3Set = ImmutableSet.copyOf(expectedPartition3.getMaterializedRows()); - assertEquals(Sets.intersection(expectedPartition1Set, actualSet).size(), 3); - assertEquals(Sets.intersection(expectedPartition2Set, actualSet).size(), 3); - assertEquals(Sets.intersection(expectedPartition3Set, actualSet).size(), 2); + assertThat(Sets.intersection(expectedPartition1Set, actualSet).size()).isEqualTo(3); + assertThat(Sets.intersection(expectedPartition2Set, actualSet).size()).isEqualTo(3); + assertThat(Sets.intersection(expectedPartition3Set, actualSet).size()).isEqualTo(2); } } @@ -363,14 +362,14 @@ public void testRowNumberUnpartitionedLimit() List pages = toPages(operatorFactory, driverContext, input); Block rowNumberColumn = getRowNumberColumn(pages); - assertEquals(rowNumberColumn.getPositionCount(), 3); + assertThat(rowNumberColumn.getPositionCount()).isEqualTo(3); pages = stripRowNumberColumn(pages); MaterializedResult actual = toMaterializedResult(driverContext.getSession(), ImmutableList.of(DOUBLE, BIGINT), pages); - assertEquals(actual.getMaterializedRows().size(), 3); + assertThat(actual.getMaterializedRows().size()).isEqualTo(3); ImmutableSet actualSet = ImmutableSet.copyOf(actual.getMaterializedRows()); ImmutableSet expectedRowsSet = ImmutableSet.copyOf(expectedRows.getMaterializedRows()); - assertEquals(Sets.intersection(expectedRowsSet, actualSet).size(), 3); + assertThat(Sets.intersection(expectedRowsSet, actualSet).size()).isEqualTo(3); } private static Block getRowNumberColumn(List pages) diff --git a/core/trino-main/src/test/java/io/trino/operator/TestRowReferencePageManager.java b/core/trino-main/src/test/java/io/trino/operator/TestRowReferencePageManager.java index 4b16f210e758..3ecb13083c82 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestRowReferencePageManager.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestRowReferencePageManager.java @@ -23,10 +23,8 @@ import static io.trino.spi.type.BigintType.BIGINT; import static java.lang.Math.toIntExact; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestRowReferencePageManager { @@ -36,12 +34,12 @@ public void testEmptyPage() RowReferencePageManager pageManager = new RowReferencePageManager(); Page page = createBigIntSingleBlockPage(0, 0); try (RowReferencePageManager.LoadCursor cursor = pageManager.add(page)) { - assertFalse(cursor.advance()); + assertThat(cursor.advance()).isFalse(); assertThatThrownBy(cursor::allocateRowId) .isInstanceOf(IllegalStateException.class) .hasMessage("Not yet advanced"); } - assertEquals(pageManager.getPageBytes(), 0); + assertThat(pageManager.getPageBytes()).isEqualTo(0); } @Test @@ -60,35 +58,35 @@ public void testSinglePageRowIds() long id3; Page page = createBigIntSingleBlockPage(0, 4); try (RowReferencePageManager.LoadCursor cursor = pageManager.add(page)) { - assertTrue(cursor.advance()); + assertThat(cursor.advance()).isTrue(); id0 = cursor.allocateRowId(); - assertEquals(extractValue(pageManager, id0), 0L); + assertThat(extractValue(pageManager, id0)).isEqualTo(0L); - assertTrue(cursor.advance()); - assertTrue(cursor.compareTo(strategy, id0) > 0); + assertThat(cursor.advance()).isTrue(); + assertThat(cursor.compareTo(strategy, id0) > 0).isTrue(); id1 = cursor.allocateRowId(); - assertEquals(extractValue(pageManager, id1), 1L); + assertThat(extractValue(pageManager, id1)).isEqualTo(1L); - assertTrue(cursor.advance()); - assertTrue(cursor.compareTo(strategy, id0) > 0); - assertTrue(cursor.compareTo(strategy, id1) > 0); + assertThat(cursor.advance()).isTrue(); + assertThat(cursor.compareTo(strategy, id0) > 0).isTrue(); + assertThat(cursor.compareTo(strategy, id1) > 0).isTrue(); // Skip this row by not allocating an ID - assertTrue(cursor.advance()); - assertTrue(cursor.compareTo(strategy, id0) > 0); - assertTrue(cursor.compareTo(strategy, id1) > 0); + assertThat(cursor.advance()).isTrue(); + assertThat(cursor.compareTo(strategy, id0) > 0).isTrue(); + assertThat(cursor.compareTo(strategy, id1) > 0).isTrue(); id3 = cursor.allocateRowId(); - assertEquals(extractValue(pageManager, id3), 3L); + assertThat(extractValue(pageManager, id3)).isEqualTo(3L); // Page size accounting happens after closing the cursor - assertEquals(pageManager.getPageBytes(), 0); + assertThat(pageManager.getPageBytes()).isEqualTo(0); } - assertTrue(pageManager.getPageBytes() > 0); + assertThat(pageManager.getPageBytes() > 0).isTrue(); // Should still be able to extract values for allocated IDs outside of cursor scope - assertEquals(extractValue(pageManager, id0), 0L); - assertEquals(extractValue(pageManager, id1), 1L); - assertEquals(extractValue(pageManager, id3), 3L); + assertThat(extractValue(pageManager, id0)).isEqualTo(0L); + assertThat(extractValue(pageManager, id1)).isEqualTo(1L); + assertThat(extractValue(pageManager, id3)).isEqualTo(3L); } @Test @@ -105,39 +103,39 @@ public void testMultiplePageRowIds() long id0; Page page = createBigIntSingleBlockPage(0, 1); try (RowReferencePageManager.LoadCursor cursor = pageManager.add(page)) { - assertTrue(cursor.advance()); + assertThat(cursor.advance()).isTrue(); id0 = cursor.allocateRowId(); - assertEquals(extractValue(pageManager, id0), 0L); + assertThat(extractValue(pageManager, id0)).isEqualTo(0L); - assertFalse(cursor.advance()); + assertThat(cursor.advance()).isFalse(); // Page size accounting happens after closing the cursor - assertEquals(pageManager.getPageBytes(), 0); + assertThat(pageManager.getPageBytes()).isEqualTo(0); } long pageBytes1 = pageManager.getPageBytes(); - assertTrue(pageBytes1 > 0); + assertThat(pageBytes1 > 0).isTrue(); // Should still be able to extract values for allocated IDs outside of cursor scope - assertEquals(extractValue(pageManager, id0), 0L); + assertThat(extractValue(pageManager, id0)).isEqualTo(0L); long id1; page = createBigIntSingleBlockPage(1, 2); try (RowReferencePageManager.LoadCursor cursor = pageManager.add(page)) { - assertTrue(cursor.advance()); - assertTrue(cursor.compareTo(strategy, id0) > 0); + assertThat(cursor.advance()).isTrue(); + assertThat(cursor.compareTo(strategy, id0) > 0).isTrue(); id1 = cursor.allocateRowId(); - assertEquals(extractValue(pageManager, id1), 1L); - assertFalse(cursor.advance()); + assertThat(extractValue(pageManager, id1)).isEqualTo(1L); + assertThat(cursor.advance()).isFalse(); // Page size accounting happens after closing the cursor - assertEquals(pageManager.getPageBytes(), pageBytes1); + assertThat(pageManager.getPageBytes()).isEqualTo(pageBytes1); } // Another page added, so should be larger - assertTrue(pageManager.getPageBytes() > pageBytes1); + assertThat(pageManager.getPageBytes() > pageBytes1).isTrue(); // Should still be able to extract values for allocated IDs outside of cursor scopes - assertEquals(extractValue(pageManager, id0), 0L); - assertEquals(extractValue(pageManager, id1), 1L); + assertThat(extractValue(pageManager, id0)).isEqualTo(0L); + assertThat(extractValue(pageManager, id1)).isEqualTo(1L); } @Test @@ -148,33 +146,33 @@ public void testSkipCompaction() long id0; Page page = createBigIntSingleBlockPage(0, 100); try (RowReferencePageManager.LoadCursor cursor = pageManager.add(page)) { - assertTrue(cursor.advance()); + assertThat(cursor.advance()).isTrue(); id0 = cursor.allocateRowId(); - assertEquals(extractValue(pageManager, id0), 0L); + assertThat(extractValue(pageManager, id0)).isEqualTo(0L); // No compaction candidates until after the cursor is closed - assertEquals(pageManager.getCompactionCandidateCount(), 0); + assertThat(pageManager.getCompactionCandidateCount()).isEqualTo(0); // Ignore the remaining positions, which means they should remain unreferenced } // Should still be able to extract values for allocated IDs outside of cursor scope - assertEquals(extractValue(pageManager, id0), 0L); + assertThat(extractValue(pageManager, id0)).isEqualTo(0L); // Page should have some size before compaction long pageBytesBeforeCompaction = pageManager.getPageBytes(); - assertTrue(pageBytesBeforeCompaction > 0); + assertThat(pageBytesBeforeCompaction > 0).isTrue(); // With a 1% fill, this page will certainly require compaction - assertEquals(pageManager.getCompactionCandidateCount(), 1); + assertThat(pageManager.getCompactionCandidateCount()).isEqualTo(1); pageManager.compactIfNeeded(); - assertEquals(pageManager.getCompactionCandidateCount(), 0); + assertThat(pageManager.getCompactionCandidateCount()).isEqualTo(0); // Page size should shrink after compaction - assertTrue(pageManager.getPageBytes() < pageBytesBeforeCompaction); + assertThat(pageManager.getPageBytes() < pageBytesBeforeCompaction).isTrue(); // Should still be able to extract same value after compaction - assertEquals(extractValue(pageManager, id0), 0L); + assertThat(extractValue(pageManager, id0)).isEqualTo(0L); } @Test @@ -186,9 +184,9 @@ public void testDereferenceCompaction() List rowIdsToDereference = new ArrayList<>(); Page page = createBigIntSingleBlockPage(0, 100); try (RowReferencePageManager.LoadCursor cursor = pageManager.add(page)) { - assertTrue(cursor.advance()); + assertThat(cursor.advance()).isTrue(); id0 = cursor.allocateRowId(); - assertEquals(extractValue(pageManager, id0), 0L); + assertThat(extractValue(pageManager, id0)).isEqualTo(0L); // Collect the remaining rowIds while (cursor.advance()) { @@ -197,7 +195,7 @@ public void testDereferenceCompaction() } // No compaction candidates since all rows should be referenced - assertEquals(pageManager.getCompactionCandidateCount(), 0); + assertThat(pageManager.getCompactionCandidateCount()).isEqualTo(0); // Dereference 99% of row IDs for (long rowId : rowIdsToDereference) { @@ -206,18 +204,18 @@ public void testDereferenceCompaction() // Page should have some size before compaction long pageBytesBeforeCompaction = pageManager.getPageBytes(); - assertTrue(pageBytesBeforeCompaction > 0); + assertThat(pageBytesBeforeCompaction > 0).isTrue(); // With a 1% fill, this page will certainly require compaction - assertEquals(pageManager.getCompactionCandidateCount(), 1); + assertThat(pageManager.getCompactionCandidateCount()).isEqualTo(1); pageManager.compactIfNeeded(); - assertEquals(pageManager.getCompactionCandidateCount(), 0); + assertThat(pageManager.getCompactionCandidateCount()).isEqualTo(0); // Page size should shrink after compaction - assertTrue(pageManager.getPageBytes() < pageBytesBeforeCompaction); + assertThat(pageManager.getPageBytes() < pageBytesBeforeCompaction).isTrue(); // Should still be able to extract same value after compaction - assertEquals(extractValue(pageManager, id0), 0L); + assertThat(extractValue(pageManager, id0)).isEqualTo(0L); } @Test @@ -231,10 +229,10 @@ public void testSkipFullPage() } // No compaction candidates since page is no longer needed - assertEquals(pageManager.getCompactionCandidateCount(), 0); + assertThat(pageManager.getCompactionCandidateCount()).isEqualTo(0); // Should not have any page bytes since page was skipped - assertEquals(pageManager.getPageBytes(), 0); + assertThat(pageManager.getPageBytes()).isEqualTo(0); } @Test @@ -256,10 +254,10 @@ public void testDereferenceFullPage() } // No compaction candidates since page is no longer needed - assertEquals(pageManager.getCompactionCandidateCount(), 0); + assertThat(pageManager.getCompactionCandidateCount()).isEqualTo(0); // Should not have any page bytes since page was fully dereferenced - assertEquals(pageManager.getPageBytes(), 0); + assertThat(pageManager.getPageBytes()).isEqualTo(0); } @Test @@ -275,10 +273,10 @@ public void testInlineDereferenceFullPage() } // No compaction candidates since page is no longer needed - assertEquals(pageManager.getCompactionCandidateCount(), 0); + assertThat(pageManager.getCompactionCandidateCount()).isEqualTo(0); // Should not have any page bytes since page was fully dereferenced - assertEquals(pageManager.getPageBytes(), 0); + assertThat(pageManager.getPageBytes()).isEqualTo(0); } @Test @@ -288,21 +286,21 @@ public void testRowIdRecycling() Page page = createBigIntSingleBlockPage(0, 3); try (RowReferencePageManager.LoadCursor cursor = pageManager.add(page)) { - assertTrue(cursor.advance()); + assertThat(cursor.advance()).isTrue(); long id0 = cursor.allocateRowId(); - assertEquals(extractValue(pageManager, id0), 0L); + assertThat(extractValue(pageManager, id0)).isEqualTo(0L); - assertTrue(cursor.advance()); + assertThat(cursor.advance()).isTrue(); long id1 = cursor.allocateRowId(); - assertEquals(extractValue(pageManager, id1), 1L); + assertThat(extractValue(pageManager, id1)).isEqualTo(1L); pageManager.dereference(id0); // Since id0 was dereferenced, the system can recycle that id for reuse - assertTrue(cursor.advance()); + assertThat(cursor.advance()).isTrue(); long id2 = cursor.allocateRowId(); - assertEquals(extractValue(pageManager, id2), 2L); - assertEquals(id0, id2); + assertThat(extractValue(pageManager, id2)).isEqualTo(2L); + assertThat(id0).isEqualTo(id2); } } diff --git a/core/trino-main/src/test/java/io/trino/operator/TestScanFilterAndProjectOperator.java b/core/trino-main/src/test/java/io/trino/operator/TestScanFilterAndProjectOperator.java index d1149370393d..f06361f106b6 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestScanFilterAndProjectOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestScanFilterAndProjectOperator.java @@ -79,10 +79,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -185,7 +181,7 @@ public void testPageSourceMergeOutput() operator.noMoreSplits(); List actual = toPages(operator); - assertEquals(actual.size(), 1); + assertThat(actual.size()).isEqualTo(1); List expected = rowPagesBuilder(BIGINT) .row(10L) @@ -322,15 +318,15 @@ public void testPageYield() driverContext.getYieldSignal().setWithDelay(SECONDS.toNanos(1000), driverContext.getYieldExecutor()); Page page = operator.getOutput(); if (i == totalColumns) { - assertNotNull(page); - assertEquals(page.getPositionCount(), totalRows); - assertEquals(page.getChannelCount(), totalColumns); + assertThat(page).isNotNull(); + assertThat(page.getPositionCount()).isEqualTo(totalRows); + assertThat(page.getChannelCount()).isEqualTo(totalColumns); for (int j = 0; j < totalColumns; j++) { - assertEquals(toValues(BIGINT, page.getBlock(j)), toValues(BIGINT, input.getBlock(0))); + assertThat(toValues(BIGINT, page.getBlock(j))).isEqualTo(toValues(BIGINT, input.getBlock(0))); } } else { - assertNull(page); + assertThat(page).isNull(); } driverContext.getYieldSignal().reset(); } @@ -382,7 +378,7 @@ public void testRecordCursorYield() // start driver; get null value due to yield for the first 15 times for (int i = 0; i < length; i++) { driverContext.getYieldSignal().setWithDelay(SECONDS.toNanos(1000), driverContext.getYieldExecutor()); - assertNull(operator.getOutput()); + assertThat(operator.getOutput()).isNull(); driverContext.getYieldSignal().reset(); } @@ -390,8 +386,8 @@ public void testRecordCursorYield() driverContext.getYieldSignal().setWithDelay(SECONDS.toNanos(1000), driverContext.getYieldExecutor()); Page output = operator.getOutput(); driverContext.getYieldSignal().reset(); - assertNotNull(output); - assertEquals(toValues(BIGINT, output.getBlock(0)), toValues(BIGINT, input.getBlock(0))); + assertThat(output).isNotNull(); + assertThat(toValues(BIGINT, output.getBlock(0))).isEqualTo(toValues(BIGINT, input.getBlock(0))); } private static List toPages(Operator operator) @@ -404,7 +400,9 @@ private static List toPages(Operator operator) Page outputPage = operator.getOutput(); if (outputPage == null) { // break infinite loop due to null pages - assertTrue(nullPages < 1_000_000, "Too many null pages; infinite loop?"); + assertThat(nullPages < 1_000_000) + .describedAs("Too many null pages; infinite loop?") + .isTrue(); nullPages++; } else { diff --git a/core/trino-main/src/test/java/io/trino/operator/TestSimplePagesHashStrategy.java b/core/trino-main/src/test/java/io/trino/operator/TestSimplePagesHashStrategy.java index c7b56bb9c453..379dcd0a1fcd 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestSimplePagesHashStrategy.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestSimplePagesHashStrategy.java @@ -29,10 +29,8 @@ import java.util.OptionalInt; import static io.trino.spi.type.IntegerType.INTEGER; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestSimplePagesHashStrategy { @@ -44,7 +42,7 @@ public void testHashRowWithIntegerType() Page page = new Page(block); // This works because IntegerType is comparable. - assertEquals(strategy.hashRow(0, page), -4467490526933615037L); + assertThat(strategy.hashRow(0, page)).isEqualTo(-4467490526933615037L); } @Test @@ -61,7 +59,7 @@ public void testHashRowWithMapType() Page page = new Page(block); // This works because MapType is comparable. - assertEquals(strategy.hashRow(0, page), 451258269207618863L); + assertThat(strategy.hashRow(0, page)).isEqualTo(451258269207618863L); } @Test @@ -74,8 +72,8 @@ public void testRowEqualsRowWithIntegerType() Page rightPage2 = new Page(new IntArrayBlock(1, Optional.empty(), new int[]{5678})); // This works because IntegerType is comparable. - assertTrue(strategy.rowEqualsRow(0, leftPage, 0, rightPage1)); - assertFalse(strategy.rowEqualsRow(0, leftPage, 0, rightPage2)); + assertThat(strategy.rowEqualsRow(0, leftPage, 0, rightPage1)).isTrue(); + assertThat(strategy.rowEqualsRow(0, leftPage, 0, rightPage2)).isFalse(); } @Test @@ -103,8 +101,8 @@ public void testRowEqualsRowWithMapType() new IntArrayBlock(1, Optional.empty(), new int[]{1234}))); // This works because MapType is comparable. - assertTrue(strategy.rowEqualsRow(0, leftPage, 0, rightPage1)); - assertFalse(strategy.rowEqualsRow(0, leftPage, 0, rightPage2)); + assertThat(strategy.rowEqualsRow(0, leftPage, 0, rightPage1)).isTrue(); + assertThat(strategy.rowEqualsRow(0, leftPage, 0, rightPage2)).isFalse(); } @Test @@ -114,9 +112,9 @@ public void testCompareSortChannelPositionsWithIntegerType() SimplePagesHashStrategy strategy = createSimplePagesHashStrategy(INTEGER, ImmutableList.of(block)); // This works because IntegerType is orderable. - assertEquals(strategy.compareSortChannelPositions(0, 0, 0, 1), -1); - assertEquals(strategy.compareSortChannelPositions(0, 1, 0, 0), 1); - assertEquals(strategy.compareSortChannelPositions(0, 0, 0, 2), 0); + assertThat(strategy.compareSortChannelPositions(0, 0, 0, 1)).isEqualTo(-1); + assertThat(strategy.compareSortChannelPositions(0, 1, 0, 0)).isEqualTo(1); + assertThat(strategy.compareSortChannelPositions(0, 0, 0, 2)).isEqualTo(0); } @Test diff --git a/core/trino-main/src/test/java/io/trino/operator/TestStreamingDirectExchangeBuffer.java b/core/trino-main/src/test/java/io/trino/operator/TestStreamingDirectExchangeBuffer.java index 02322e0b98cb..1f09e40fe021 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestStreamingDirectExchangeBuffer.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestStreamingDirectExchangeBuffer.java @@ -27,11 +27,8 @@ import static io.airlift.slice.Slices.utf8Slice; import static io.airlift.units.DataSize.Unit.KILOBYTE; import static io.trino.spi.StandardErrorCode.REMOTE_TASK_FAILED; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; public class TestStreamingDirectExchangeBuffer { @@ -46,63 +43,63 @@ public class TestStreamingDirectExchangeBuffer public void testHappyPath() { try (StreamingDirectExchangeBuffer buffer = new StreamingDirectExchangeBuffer(directExecutor(), DataSize.of(1, KILOBYTE))) { - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); ListenableFuture blocked = buffer.isBlocked(); - assertFalse(blocked.isDone()); - assertNull(buffer.pollPage()); + assertThat(blocked.isDone()).isFalse(); + assertThat(buffer.pollPage()).isNull(); buffer.addTask(TASK_0); - assertFalse(buffer.isFinished()); - assertFalse(blocked.isDone()); - assertNull(buffer.pollPage()); + assertThat(buffer.isFinished()).isFalse(); + assertThat(blocked.isDone()).isFalse(); + assertThat(buffer.pollPage()).isNull(); buffer.addTask(TASK_1); - assertFalse(buffer.isFinished()); - assertFalse(blocked.isDone()); - assertNull(buffer.pollPage()); + assertThat(buffer.isFinished()).isFalse(); + assertThat(blocked.isDone()).isFalse(); + assertThat(buffer.pollPage()).isNull(); buffer.noMoreTasks(); - assertFalse(buffer.isFinished()); - assertFalse(blocked.isDone()); - assertNull(buffer.pollPage()); + assertThat(buffer.isFinished()).isFalse(); + assertThat(blocked.isDone()).isFalse(); + assertThat(buffer.pollPage()).isNull(); buffer.addPages(TASK_0, ImmutableList.of(PAGE_0)); - assertEquals(buffer.getBufferedPageCount(), 1); - assertEquals(buffer.getRetainedSizeInBytes(), PAGE_0.getRetainedSize()); - assertEquals(buffer.getMaxRetainedSizeInBytes(), PAGE_0.getRetainedSize()); - assertEquals(buffer.getRemainingCapacityInBytes(), DataSize.of(1, KILOBYTE).toBytes() - PAGE_0.getRetainedSize()); - assertFalse(buffer.isFinished()); - assertTrue(blocked.isDone()); - assertEquals(buffer.pollPage(), PAGE_0); + assertThat(buffer.getBufferedPageCount()).isEqualTo(1); + assertThat(buffer.getRetainedSizeInBytes()).isEqualTo(PAGE_0.getRetainedSize()); + assertThat(buffer.getMaxRetainedSizeInBytes()).isEqualTo(PAGE_0.getRetainedSize()); + assertThat(buffer.getRemainingCapacityInBytes()).isEqualTo(DataSize.of(1, KILOBYTE).toBytes() - PAGE_0.getRetainedSize()); + assertThat(buffer.isFinished()).isFalse(); + assertThat(blocked.isDone()).isTrue(); + assertThat(buffer.pollPage()).isEqualTo(PAGE_0); blocked = buffer.isBlocked(); - assertEquals(buffer.getRetainedSizeInBytes(), 0); - assertEquals(buffer.getMaxRetainedSizeInBytes(), PAGE_0.getRetainedSize()); - assertEquals(buffer.getRemainingCapacityInBytes(), DataSize.of(1, KILOBYTE).toBytes()); - assertFalse(buffer.isFinished()); - assertFalse(blocked.isDone()); + assertThat(buffer.getRetainedSizeInBytes()).isEqualTo(0); + assertThat(buffer.getMaxRetainedSizeInBytes()).isEqualTo(PAGE_0.getRetainedSize()); + assertThat(buffer.getRemainingCapacityInBytes()).isEqualTo(DataSize.of(1, KILOBYTE).toBytes()); + assertThat(buffer.isFinished()).isFalse(); + assertThat(blocked.isDone()).isFalse(); buffer.taskFinished(TASK_0); - assertFalse(buffer.isFinished()); - assertFalse(buffer.isBlocked().isDone()); + assertThat(buffer.isFinished()).isFalse(); + assertThat(buffer.isBlocked().isDone()).isFalse(); buffer.addPages(TASK_1, ImmutableList.of(PAGE_1, PAGE_2)); - assertEquals(buffer.getBufferedPageCount(), 2); - assertEquals(buffer.getRetainedSizeInBytes(), PAGE_1.getRetainedSize() + PAGE_2.getRetainedSize()); - assertEquals(buffer.getMaxRetainedSizeInBytes(), PAGE_1.getRetainedSize() + PAGE_2.getRetainedSize()); - assertEquals(buffer.getRemainingCapacityInBytes(), DataSize.of(1, KILOBYTE).toBytes() - PAGE_1.getRetainedSize() - PAGE_2.getRetainedSize()); - assertFalse(buffer.isFinished()); - assertTrue(buffer.isBlocked().isDone()); - assertEquals(buffer.pollPage(), PAGE_1); - assertEquals(buffer.pollPage(), PAGE_2); - assertFalse(buffer.isFinished()); - assertFalse(buffer.isBlocked().isDone()); - assertEquals(buffer.getRetainedSizeInBytes(), 0); - assertEquals(buffer.getMaxRetainedSizeInBytes(), PAGE_1.getRetainedSize() + PAGE_2.getRetainedSize()); - assertEquals(buffer.getRemainingCapacityInBytes(), DataSize.of(1, KILOBYTE).toBytes()); + assertThat(buffer.getBufferedPageCount()).isEqualTo(2); + assertThat(buffer.getRetainedSizeInBytes()).isEqualTo(PAGE_1.getRetainedSize() + PAGE_2.getRetainedSize()); + assertThat(buffer.getMaxRetainedSizeInBytes()).isEqualTo(PAGE_1.getRetainedSize() + PAGE_2.getRetainedSize()); + assertThat(buffer.getRemainingCapacityInBytes()).isEqualTo(DataSize.of(1, KILOBYTE).toBytes() - PAGE_1.getRetainedSize() - PAGE_2.getRetainedSize()); + assertThat(buffer.isFinished()).isFalse(); + assertThat(buffer.isBlocked().isDone()).isTrue(); + assertThat(buffer.pollPage()).isEqualTo(PAGE_1); + assertThat(buffer.pollPage()).isEqualTo(PAGE_2); + assertThat(buffer.isFinished()).isFalse(); + assertThat(buffer.isBlocked().isDone()).isFalse(); + assertThat(buffer.getRetainedSizeInBytes()).isEqualTo(0); + assertThat(buffer.getMaxRetainedSizeInBytes()).isEqualTo(PAGE_1.getRetainedSize() + PAGE_2.getRetainedSize()); + assertThat(buffer.getRemainingCapacityInBytes()).isEqualTo(DataSize.of(1, KILOBYTE).toBytes()); buffer.taskFinished(TASK_1); - assertTrue(buffer.isFinished()); - assertTrue(blocked.isDone()); + assertThat(buffer.isFinished()).isTrue(); + assertThat(blocked.isDone()).isTrue(); } } @@ -113,15 +110,15 @@ public void testClose() buffer.addTask(TASK_0); buffer.addTask(TASK_1); - assertFalse(buffer.isFinished()); - assertFalse(buffer.isBlocked().isDone()); - assertNull(buffer.pollPage()); + assertThat(buffer.isFinished()).isFalse(); + assertThat(buffer.isBlocked().isDone()).isFalse(); + assertThat(buffer.pollPage()).isNull(); buffer.close(); - assertTrue(buffer.isFinished()); - assertTrue(buffer.isBlocked().isDone()); - assertNull(buffer.pollPage()); + assertThat(buffer.isFinished()).isTrue(); + assertThat(buffer.isBlocked().isDone()).isTrue(); + assertThat(buffer.pollPage()).isNull(); } @Test @@ -129,51 +126,51 @@ public void testIsFinished() { // 0 tasks try (StreamingDirectExchangeBuffer buffer = new StreamingDirectExchangeBuffer(directExecutor(), DataSize.of(1, KILOBYTE))) { - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); ListenableFuture blocked = buffer.isBlocked(); - assertFalse(blocked.isDone()); + assertThat(blocked.isDone()).isFalse(); buffer.noMoreTasks(); - assertTrue(buffer.isFinished()); - assertTrue(blocked.isDone()); + assertThat(buffer.isFinished()).isTrue(); + assertThat(blocked.isDone()).isTrue(); } // single task try (StreamingDirectExchangeBuffer buffer = new StreamingDirectExchangeBuffer(directExecutor(), DataSize.of(1, KILOBYTE))) { - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); ListenableFuture blocked = buffer.isBlocked(); - assertFalse(blocked.isDone()); + assertThat(blocked.isDone()).isFalse(); buffer.addTask(TASK_0); buffer.noMoreTasks(); - assertFalse(buffer.isFinished()); - assertFalse(blocked.isDone()); + assertThat(buffer.isFinished()).isFalse(); + assertThat(blocked.isDone()).isFalse(); buffer.taskFinished(TASK_0); - assertTrue(buffer.isFinished()); - assertTrue(blocked.isDone()); + assertThat(buffer.isFinished()).isTrue(); + assertThat(blocked.isDone()).isTrue(); } // single failed task try (StreamingDirectExchangeBuffer buffer = new StreamingDirectExchangeBuffer(directExecutor(), DataSize.of(1, KILOBYTE))) { - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); ListenableFuture blocked = buffer.isBlocked(); - assertFalse(blocked.isDone()); + assertThat(blocked.isDone()).isFalse(); buffer.addTask(TASK_0); - assertFalse(buffer.isFinished()); - assertFalse(blocked.isDone()); + assertThat(buffer.isFinished()).isFalse(); + assertThat(blocked.isDone()).isFalse(); RuntimeException error = new RuntimeException(); buffer.taskFailed(TASK_0, error); - assertFalse(buffer.isFinished()); - assertTrue(buffer.isFailed()); - assertTrue(blocked.isDone()); + assertThat(buffer.isFinished()).isFalse(); + assertThat(buffer.isFailed()).isTrue(); + assertThat(blocked.isDone()).isTrue(); assertThatThrownBy(buffer::pollPage).isEqualTo(error); } } @@ -182,25 +179,25 @@ public void testIsFinished() public void testFutureCancellationDoesNotAffectOtherFutures() { try (StreamingDirectExchangeBuffer buffer = new StreamingDirectExchangeBuffer(directExecutor(), DataSize.of(1, KILOBYTE))) { - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); ListenableFuture blocked1 = buffer.isBlocked(); ListenableFuture blocked2 = buffer.isBlocked(); ListenableFuture blocked3 = buffer.isBlocked(); - assertFalse(blocked1.isDone()); - assertFalse(blocked2.isDone()); - assertFalse(blocked3.isDone()); + assertThat(blocked1.isDone()).isFalse(); + assertThat(blocked2.isDone()).isFalse(); + assertThat(blocked3.isDone()).isFalse(); blocked3.cancel(true); - assertFalse(blocked1.isDone()); - assertFalse(blocked2.isDone()); + assertThat(blocked1.isDone()).isFalse(); + assertThat(blocked2.isDone()).isFalse(); buffer.noMoreTasks(); - assertTrue(buffer.isFinished()); - assertTrue(blocked1.isDone()); - assertTrue(blocked2.isDone()); + assertThat(buffer.isFinished()).isTrue(); + assertThat(blocked1.isDone()).isTrue(); + assertThat(blocked2.isDone()).isTrue(); } } @@ -213,9 +210,9 @@ public void testRemoteTaskFailedError() buffer.taskFailed(TASK_0, new TrinoException(REMOTE_TASK_FAILED, "Remote task failed")); buffer.noMoreTasks(); - assertFalse(buffer.isFinished()); - assertFalse(buffer.isFailed()); - assertNull(buffer.pollPage()); + assertThat(buffer.isFinished()).isFalse(); + assertThat(buffer.isFailed()).isFalse(); + assertThat(buffer.pollPage()).isNull(); } // fail after noMoreTasks @@ -224,9 +221,9 @@ public void testRemoteTaskFailedError() buffer.noMoreTasks(); buffer.taskFailed(TASK_0, new TrinoException(REMOTE_TASK_FAILED, "Remote task failed")); - assertFalse(buffer.isFinished()); - assertFalse(buffer.isFailed()); - assertNull(buffer.pollPage()); + assertThat(buffer.isFinished()).isFalse(); + assertThat(buffer.isFailed()).isFalse(); + assertThat(buffer.pollPage()).isNull(); } } @@ -234,24 +231,24 @@ public void testRemoteTaskFailedError() public void testSingleWakeUp() { try (StreamingDirectExchangeBuffer buffer = new StreamingDirectExchangeBuffer(directExecutor(), DataSize.of(1, KILOBYTE))) { - assertFalse(buffer.isFinished()); + assertThat(buffer.isFinished()).isFalse(); ListenableFuture blocked1 = buffer.isBlocked(); ListenableFuture blocked2 = buffer.isBlocked(); - assertFalse(blocked1.isDone()); - assertFalse(blocked2.isDone()); + assertThat(blocked1.isDone()).isFalse(); + assertThat(blocked2.isDone()).isFalse(); buffer.addTask(TASK_0); buffer.addPages(TASK_0, ImmutableList.of(PAGE_0)); buffer.pollPage(); - assertTrue(blocked1.isDone()); - assertFalse(blocked2.isDone()); + assertThat(blocked1.isDone()).isTrue(); + assertThat(blocked2.isDone()).isFalse(); buffer.addPages(TASK_0, ImmutableList.of(PAGE_0)); buffer.pollPage(); - assertTrue(blocked2.isDone()); + assertThat(blocked2.isDone()).isTrue(); } } } diff --git a/core/trino-main/src/test/java/io/trino/operator/TestTableFinishOperator.java b/core/trino-main/src/test/java/io/trino/operator/TestTableFinishOperator.java index 4cb8e2f3712c..2c9d78750fb8 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestTableFinishOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestTableFinishOperator.java @@ -60,10 +60,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -129,31 +125,45 @@ public void testStatisticsAggregation() assertThat(driverContext.getMemoryUsage()).as("memoryUsage").isGreaterThan(0); - assertTrue(operator.isBlocked().isDone(), "isBlocked should be done"); - assertTrue(operator.needsInput(), "needsInput should be true"); + assertThat(operator.isBlocked().isDone()) + .describedAs("isBlocked should be done") + .isTrue(); + assertThat(operator.needsInput()) + .describedAs("needsInput should be true") + .isTrue(); operator.finish(); - assertFalse(operator.isFinished(), "isFinished should be false"); + assertThat(operator.isFinished()) + .describedAs("isFinished should be false") + .isFalse(); - assertNull(operator.getOutput()); + assertThat(operator.getOutput()).isNull(); List outputTypes = ImmutableList.of(BIGINT); assertPageEquals(outputTypes, operator.getOutput(), rowPagesBuilder(outputTypes).row(9).build().get(0)); - assertTrue(operator.isBlocked().isDone(), "isBlocked should be done"); - assertFalse(operator.needsInput(), "needsInput should be false"); - assertTrue(operator.isFinished(), "isFinished should be true"); + assertThat(operator.isBlocked().isDone()) + .describedAs("isBlocked should be done") + .isTrue(); + assertThat(operator.needsInput()) + .describedAs("needsInput should be false") + .isFalse(); + assertThat(operator.isFinished()) + .describedAs("isFinished should be true") + .isTrue(); operator.close(); - assertEquals(tableFinisher.getFragments(), ImmutableList.of(Slices.wrappedBuffer(new byte[] {1}), Slices.wrappedBuffer(new byte[] {2}))); - assertEquals(tableFinisher.getComputedStatistics().size(), 1); - assertEquals(getOnlyElement(tableFinisher.getComputedStatistics()).getColumnStatistics().size(), 1); + assertThat(tableFinisher.getFragments()).isEqualTo(ImmutableList.of(Slices.wrappedBuffer(new byte[] {1}), Slices.wrappedBuffer(new byte[] {2}))); + assertThat(tableFinisher.getComputedStatistics().size()).isEqualTo(1); + assertThat(getOnlyElement(tableFinisher.getComputedStatistics()).getColumnStatistics().size()).isEqualTo(1); LongArrayBlockBuilder expectedStatistics = new LongArrayBlockBuilder(null, 1); BIGINT.writeLong(expectedStatistics, 7); assertBlockEquals(BIGINT, getOnlyElement(tableFinisher.getComputedStatistics()).getColumnStatistics().get(statisticMetadata), expectedStatistics.build()); - assertEquals(driverContext.getMemoryUsage(), 0, "memoryUsage"); + assertThat(driverContext.getMemoryUsage()) + .describedAs("memoryUsage") + .isEqualTo(0); } private static class TestTableFinisher diff --git a/core/trino-main/src/test/java/io/trino/operator/TestTableWriterOperator.java b/core/trino-main/src/test/java/io/trino/operator/TestTableWriterOperator.java index ca0d25f59c54..be14d2fb93b5 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestTableWriterOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestTableWriterOperator.java @@ -73,10 +73,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -107,38 +103,38 @@ public void testBlockedPageSink() Operator operator = createTableWriterOperator(blockingPageSink); // initial state validation - assertTrue(operator.isBlocked().isDone()); - assertFalse(operator.isFinished()); - assertTrue(operator.needsInput()); + assertThat(operator.isBlocked().isDone()).isTrue(); + assertThat(operator.isFinished()).isFalse(); + assertThat(operator.needsInput()).isTrue(); // blockingPageSink that will return blocked future operator.addInput(rowPagesBuilder(BIGINT).row(42).build().get(0)); - assertFalse(operator.isBlocked().isDone()); - assertFalse(operator.isFinished()); - assertFalse(operator.needsInput()); - assertNull(operator.getOutput()); + assertThat(operator.isBlocked().isDone()).isFalse(); + assertThat(operator.isFinished()).isFalse(); + assertThat(operator.needsInput()).isFalse(); + assertThat(operator.getOutput()).isNull(); // complete previously blocked future blockingPageSink.complete(); - assertTrue(operator.isBlocked().isDone()); - assertFalse(operator.isFinished()); - assertTrue(operator.needsInput()); + assertThat(operator.isBlocked().isDone()).isTrue(); + assertThat(operator.isFinished()).isFalse(); + assertThat(operator.needsInput()).isTrue(); // add second page operator.addInput(rowPagesBuilder(BIGINT).row(44).build().get(0)); - assertFalse(operator.isBlocked().isDone()); - assertFalse(operator.isFinished()); - assertFalse(operator.needsInput()); + assertThat(operator.isBlocked().isDone()).isFalse(); + assertThat(operator.isFinished()).isFalse(); + assertThat(operator.needsInput()).isFalse(); // finish operator, state hasn't changed operator.finish(); - assertFalse(operator.isBlocked().isDone()); - assertFalse(operator.isFinished()); - assertFalse(operator.needsInput()); + assertThat(operator.isBlocked().isDone()).isFalse(); + assertThat(operator.isFinished()).isFalse(); + assertThat(operator.needsInput()).isFalse(); // complete previously blocked future blockingPageSink.complete(); @@ -148,9 +144,9 @@ public void testBlockedPageSink() operator.getOutput(), rowPagesBuilder(expectedTypes).row(2, null).build().get(0)); - assertTrue(operator.isBlocked().isDone()); - assertTrue(operator.isFinished()); - assertFalse(operator.needsInput()); + assertThat(operator.isBlocked().isDone()).isTrue(); + assertThat(operator.isFinished()).isTrue(); + assertThat(operator.needsInput()).isFalse(); } @Test @@ -160,8 +156,8 @@ public void addInputFailsOnBlockedOperator() operator.addInput(rowPagesBuilder(BIGINT).row(42).build().get(0)); - assertFalse(operator.isBlocked().isDone()); - assertFalse(operator.needsInput()); + assertThat(operator.isBlocked().isDone()).isFalse(); + assertThat(operator.needsInput()).isFalse(); assertThatThrownBy(() -> operator.addInput(rowPagesBuilder(BIGINT).row(42).build().get(0))) .isInstanceOf(IllegalStateException.class) @@ -191,8 +187,8 @@ public void testTableWriterInfo() validationCpuNanos += page.getPositionCount(); tableWriterOperator.addInput(page); TableWriterInfo info = tableWriterOperator.getInfo(); - assertEquals(info.getPageSinkPeakMemoryUsage(), peakMemoryUsage); - assertEquals((long) (info.getValidationCpuTime().getValue(NANOSECONDS)), validationCpuNanos); + assertThat(info.getPageSinkPeakMemoryUsage()).isEqualTo(peakMemoryUsage); + assertThat((long) (info.getValidationCpuTime().getValue(NANOSECONDS))).isEqualTo(validationCpuNanos); } } @@ -221,13 +217,13 @@ public void testStatisticsAggregation() operator.addInput(rowPagesBuilder(BIGINT).row(42).build().get(0)); operator.addInput(rowPagesBuilder(BIGINT).row(43).build().get(0)); - assertTrue(operator.isBlocked().isDone()); - assertTrue(operator.needsInput()); + assertThat(operator.isBlocked().isDone()).isTrue(); + assertThat(operator.needsInput()).isTrue(); assertThat(driverContext.getMemoryUsage()).isGreaterThan(0); operator.finish(); - assertFalse(operator.isFinished()); + assertThat(operator.isFinished()).isFalse(); assertPageEquals(outputTypes, operator.getOutput(), rowPagesBuilder(outputTypes) @@ -237,9 +233,9 @@ public void testStatisticsAggregation() rowPagesBuilder(outputTypes) .row(2, null, null).build().get(0)); - assertTrue(operator.isBlocked().isDone()); - assertFalse(operator.needsInput()); - assertTrue(operator.isFinished()); + assertThat(operator.isBlocked().isDone()).isTrue(); + assertThat(operator.needsInput()).isFalse(); + assertThat(operator.isFinished()).isTrue(); operator.close(); assertMemoryIsReleased(operator); @@ -253,16 +249,16 @@ private void assertMemoryIsReleased(TableWriterOperator tableWriterOperator) { OperatorContext tableWriterOperatorOperatorContext = tableWriterOperator.getOperatorContext(); MemoryTrackingContext tableWriterMemoryContext = tableWriterOperatorOperatorContext.getOperatorMemoryContext(); - assertEquals(tableWriterMemoryContext.getUserMemory(), 0); - assertEquals(tableWriterMemoryContext.getRevocableMemory(), 0); + assertThat(tableWriterMemoryContext.getUserMemory()).isEqualTo(0); + assertThat(tableWriterMemoryContext.getRevocableMemory()).isEqualTo(0); Operator statisticAggregationOperator = tableWriterOperator.getStatisticAggregationOperator(); - assertTrue(statisticAggregationOperator instanceof AggregationOperator); + assertThat(statisticAggregationOperator instanceof AggregationOperator).isTrue(); AggregationOperator aggregationOperator = (AggregationOperator) statisticAggregationOperator; OperatorContext aggregationOperatorOperatorContext = aggregationOperator.getOperatorContext(); MemoryTrackingContext aggregationOperatorMemoryContext = aggregationOperatorOperatorContext.getOperatorMemoryContext(); - assertEquals(aggregationOperatorMemoryContext.getUserMemory(), 0); - assertEquals(aggregationOperatorMemoryContext.getRevocableMemory(), 0); + assertThat(aggregationOperatorMemoryContext.getUserMemory()).isEqualTo(0); + assertThat(aggregationOperatorMemoryContext.getRevocableMemory()).isEqualTo(0); } private Operator createTableWriterOperator(BlockingPageSink blockingPageSink) diff --git a/core/trino-main/src/test/java/io/trino/operator/TestTaskStats.java b/core/trino-main/src/test/java/io/trino/operator/TestTaskStats.java index a6a27d3b2a01..92b6770ee006 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestTaskStats.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestTaskStats.java @@ -25,8 +25,8 @@ import static io.trino.operator.TestPipelineStats.assertExpectedPipelineStats; import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static org.assertj.core.api.Assertions.assertThat; import static org.joda.time.DateTimeZone.UTC; -import static org.testng.Assert.assertEquals; public class TestTaskStats { @@ -102,56 +102,56 @@ public void testJson() public static void assertExpectedTaskStats(TaskStats actual) { - assertEquals(actual.getCreateTime(), new DateTime(1, UTC)); - assertEquals(actual.getFirstStartTime(), new DateTime(2, UTC)); - assertEquals(actual.getLastStartTime(), new DateTime(100, UTC)); - assertEquals(actual.getTerminatingStartTime(), new DateTime(102, UTC)); - assertEquals(actual.getLastEndTime(), new DateTime(101, UTC)); - assertEquals(actual.getEndTime(), new DateTime(3, UTC)); - assertEquals(actual.getElapsedTime(), new Duration(4, NANOSECONDS)); - assertEquals(actual.getQueuedTime(), new Duration(5, NANOSECONDS)); - - assertEquals(actual.getTotalDrivers(), 6); - assertEquals(actual.getQueuedDrivers(), 7); - assertEquals(actual.getQueuedPartitionedDrivers(), 5); - assertEquals(actual.getQueuedPartitionedSplitsWeight(), 28L); - assertEquals(actual.getRunningDrivers(), 8); - assertEquals(actual.getRunningPartitionedDrivers(), 6); - assertEquals(actual.getRunningPartitionedSplitsWeight(), 29L); - assertEquals(actual.getBlockedDrivers(), 24); - assertEquals(actual.getCompletedDrivers(), 10); - - assertEquals(actual.getCumulativeUserMemory(), 11.0); - assertEquals(actual.getUserMemoryReservation(), DataSize.ofBytes(12)); - assertEquals(actual.getPeakUserMemoryReservation(), DataSize.ofBytes(120)); - assertEquals(actual.getRevocableMemoryReservation(), DataSize.ofBytes(13)); - - assertEquals(actual.getTotalScheduledTime(), new Duration(15, NANOSECONDS)); - assertEquals(actual.getTotalCpuTime(), new Duration(16, NANOSECONDS)); - assertEquals(actual.getTotalBlockedTime(), new Duration(18, NANOSECONDS)); - - assertEquals(actual.getPhysicalInputDataSize(), DataSize.ofBytes(191)); - assertEquals(actual.getPhysicalInputPositions(), 201); - assertEquals(actual.getPhysicalInputReadTime(), new Duration(15, NANOSECONDS)); - assertEquals(actual.getInternalNetworkInputDataSize(), DataSize.ofBytes(192)); - assertEquals(actual.getInternalNetworkInputPositions(), 202); - - assertEquals(actual.getRawInputDataSize(), DataSize.ofBytes(19)); - assertEquals(actual.getRawInputPositions(), 20); - - assertEquals(actual.getProcessedInputDataSize(), DataSize.ofBytes(21)); - assertEquals(actual.getProcessedInputPositions(), 22); - - assertEquals(actual.getInputBlockedTime(), new Duration(271, NANOSECONDS)); - - assertEquals(actual.getOutputDataSize(), DataSize.ofBytes(23)); - assertEquals(actual.getOutputPositions(), 24); - - assertEquals(actual.getOutputBlockedTime(), new Duration(272, NANOSECONDS)); - - assertEquals(actual.getPhysicalWrittenDataSize(), DataSize.ofBytes(25)); - - assertEquals(actual.getPipelines().size(), 1); + assertThat(actual.getCreateTime()).isEqualTo(new DateTime(1, UTC)); + assertThat(actual.getFirstStartTime()).isEqualTo(new DateTime(2, UTC)); + assertThat(actual.getLastStartTime()).isEqualTo(new DateTime(100, UTC)); + assertThat(actual.getTerminatingStartTime()).isEqualTo(new DateTime(102, UTC)); + assertThat(actual.getLastEndTime()).isEqualTo(new DateTime(101, UTC)); + assertThat(actual.getEndTime()).isEqualTo(new DateTime(3, UTC)); + assertThat(actual.getElapsedTime()).isEqualTo(new Duration(4, NANOSECONDS)); + assertThat(actual.getQueuedTime()).isEqualTo(new Duration(5, NANOSECONDS)); + + assertThat(actual.getTotalDrivers()).isEqualTo(6); + assertThat(actual.getQueuedDrivers()).isEqualTo(7); + assertThat(actual.getQueuedPartitionedDrivers()).isEqualTo(5); + assertThat(actual.getQueuedPartitionedSplitsWeight()).isEqualTo(28L); + assertThat(actual.getRunningDrivers()).isEqualTo(8); + assertThat(actual.getRunningPartitionedDrivers()).isEqualTo(6); + assertThat(actual.getRunningPartitionedSplitsWeight()).isEqualTo(29L); + assertThat(actual.getBlockedDrivers()).isEqualTo(24); + assertThat(actual.getCompletedDrivers()).isEqualTo(10); + + assertThat(actual.getCumulativeUserMemory()).isEqualTo(11.0); + assertThat(actual.getUserMemoryReservation()).isEqualTo(DataSize.ofBytes(12)); + assertThat(actual.getPeakUserMemoryReservation()).isEqualTo(DataSize.ofBytes(120)); + assertThat(actual.getRevocableMemoryReservation()).isEqualTo(DataSize.ofBytes(13)); + + assertThat(actual.getTotalScheduledTime()).isEqualTo(new Duration(15, NANOSECONDS)); + assertThat(actual.getTotalCpuTime()).isEqualTo(new Duration(16, NANOSECONDS)); + assertThat(actual.getTotalBlockedTime()).isEqualTo(new Duration(18, NANOSECONDS)); + + assertThat(actual.getPhysicalInputDataSize()).isEqualTo(DataSize.ofBytes(191)); + assertThat(actual.getPhysicalInputPositions()).isEqualTo(201); + assertThat(actual.getPhysicalInputReadTime()).isEqualTo(new Duration(15, NANOSECONDS)); + assertThat(actual.getInternalNetworkInputDataSize()).isEqualTo(DataSize.ofBytes(192)); + assertThat(actual.getInternalNetworkInputPositions()).isEqualTo(202); + + assertThat(actual.getRawInputDataSize()).isEqualTo(DataSize.ofBytes(19)); + assertThat(actual.getRawInputPositions()).isEqualTo(20); + + assertThat(actual.getProcessedInputDataSize()).isEqualTo(DataSize.ofBytes(21)); + assertThat(actual.getProcessedInputPositions()).isEqualTo(22); + + assertThat(actual.getInputBlockedTime()).isEqualTo(new Duration(271, NANOSECONDS)); + + assertThat(actual.getOutputDataSize()).isEqualTo(DataSize.ofBytes(23)); + assertThat(actual.getOutputPositions()).isEqualTo(24); + + assertThat(actual.getOutputBlockedTime()).isEqualTo(new Duration(272, NANOSECONDS)); + + assertThat(actual.getPhysicalWrittenDataSize()).isEqualTo(DataSize.ofBytes(25)); + + assertThat(actual.getPipelines().size()).isEqualTo(1); assertExpectedPipelineStats(actual.getPipelines().get(0)); } } diff --git a/core/trino-main/src/test/java/io/trino/operator/TestTopNOperator.java b/core/trino-main/src/test/java/io/trino/operator/TestTopNOperator.java index ed6cf3efc30a..5ca535088280 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestTopNOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestTopNOperator.java @@ -44,11 +44,9 @@ import static io.trino.testing.TestingTaskContext.createTaskContext; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_METHOD) public class TestTopNOperator @@ -176,10 +174,10 @@ public void testLimitZero() ImmutableList.of(DESC_NULLS_LAST)); try (Operator operator = factory.createOperator(driverContext)) { - assertNull(operator.getOutput()); - assertTrue(operator.isFinished()); - assertFalse(operator.needsInput()); - assertNull(operator.getOutput()); + assertThat(operator.getOutput()).isNull(); + assertThat(operator.isFinished()).isTrue(); + assertThat(operator.needsInput()).isFalse(); + assertThat(operator.getOutput()).isNull(); } } diff --git a/core/trino-main/src/test/java/io/trino/operator/TestTopNPeerGroupLookup.java b/core/trino-main/src/test/java/io/trino/operator/TestTopNPeerGroupLookup.java index 80ce068a094c..d88a5cb00c7a 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestTopNPeerGroupLookup.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestTopNPeerGroupLookup.java @@ -20,9 +20,7 @@ import java.util.List; import static com.google.common.collect.Lists.cartesianProduct; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestTopNPeerGroupLookup { @@ -68,25 +66,25 @@ public void testCombinations(int expectedSize, float fillFactor, long totalGroup { TopNPeerGroupLookup lookup = new TopNPeerGroupLookup(expectedSize, fillFactor, HASH_STRATEGY, UNMAPPED_GROUP_ID, DEFAULT_RETURN_VALUE); - assertEquals(lookup.size(), 0); - assertTrue(lookup.isEmpty()); + assertThat(lookup.size()).isEqualTo(0); + assertThat(lookup.isEmpty()).isTrue(); // Put values int count = 0; for (int groupId = 0; groupId < totalGroupIds; groupId++) { for (int rowId = 0; rowId < totalRowIds; rowId++) { // Value should not exist yet - assertEquals(lookup.get(groupId, rowId), DEFAULT_RETURN_VALUE); - assertEquals(lookup.get(groupId, toRowReference(rowId)), DEFAULT_RETURN_VALUE); - assertEquals(lookup.remove(groupId, rowId), DEFAULT_RETURN_VALUE); + assertThat(lookup.get(groupId, rowId)).isEqualTo(DEFAULT_RETURN_VALUE); + assertThat(lookup.get(groupId, toRowReference(rowId))).isEqualTo(DEFAULT_RETURN_VALUE); + assertThat(lookup.remove(groupId, rowId)).isEqualTo(DEFAULT_RETURN_VALUE); // Insert the value - assertEquals(lookup.put(groupId, rowId, count), DEFAULT_RETURN_VALUE); + assertThat(lookup.put(groupId, rowId, count)).isEqualTo(DEFAULT_RETURN_VALUE); count++; - assertFalse(lookup.isEmpty()); - assertEquals(lookup.size(), count); + assertThat(lookup.isEmpty()).isFalse(); + assertThat(lookup.size()).isEqualTo(count); } } @@ -95,12 +93,12 @@ public void testCombinations(int expectedSize, float fillFactor, long totalGroup long totalEntries = totalGroupIds * totalRowIds; for (int groupId = 0; groupId < totalGroupIds; groupId++) { for (int rowId = 0; rowId < totalRowIds; rowId++) { - assertEquals(lookup.get(groupId, rowId), count); - assertEquals(lookup.get(groupId, toRowReference(rowId)), count); + assertThat(lookup.get(groupId, rowId)).isEqualTo(count); + assertThat(lookup.get(groupId, toRowReference(rowId))).isEqualTo(count); count++; - assertFalse(lookup.isEmpty()); - assertEquals(lookup.size(), totalEntries); + assertThat(lookup.isEmpty()).isFalse(); + assertThat(lookup.size()).isEqualTo(totalEntries); } } @@ -108,11 +106,11 @@ public void testCombinations(int expectedSize, float fillFactor, long totalGroup count = 0; for (int groupId = 0; groupId < totalGroupIds; groupId++) { for (int rowId = 0; rowId < totalRowIds; rowId++) { - assertEquals(lookup.put(groupId, rowId, count + 1), count); + assertThat(lookup.put(groupId, rowId, count + 1)).isEqualTo(count); count++; - assertFalse(lookup.isEmpty()); - assertEquals(lookup.size(), totalEntries); + assertThat(lookup.isEmpty()).isFalse(); + assertThat(lookup.size()).isEqualTo(totalEntries); } } @@ -120,16 +118,16 @@ public void testCombinations(int expectedSize, float fillFactor, long totalGroup count = 0; for (int groupId = 0; groupId < totalGroupIds; groupId++) { for (int rowId = 0; rowId < totalRowIds; rowId++) { - assertFalse(lookup.isEmpty()); - assertEquals(lookup.size(), totalEntries - count); + assertThat(lookup.isEmpty()).isFalse(); + assertThat(lookup.size()).isEqualTo(totalEntries - count); // Removed value should be the overwritten value - assertEquals(lookup.remove(groupId, rowId), count + 1); + assertThat(lookup.remove(groupId, rowId)).isEqualTo(count + 1); count++; } } - assertTrue(lookup.isEmpty()); - assertEquals(lookup.size(), 0); + assertThat(lookup.isEmpty()).isTrue(); + assertThat(lookup.size()).isEqualTo(0); } private static RowReference toRowReference(long rowId) diff --git a/core/trino-main/src/test/java/io/trino/operator/TestTopNRankingOperator.java b/core/trino-main/src/test/java/io/trino/operator/TestTopNRankingOperator.java index 6e1b68a2ccce..a1e91a7b53a4 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestTopNRankingOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestTopNRankingOperator.java @@ -54,14 +54,10 @@ import static io.trino.testing.TestingTaskContext.createTaskContext; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -248,16 +244,16 @@ public void testPartialFlush() for (Page inputPage : input) { operator.addInput(inputPage); if (partial) { - assertFalse(operator.needsInput()); // full - assertNotNull(operator.getOutput()); // partial flush - assertFalse(operator.isFinished()); // not finished. just partial flushing. + assertThat(operator.needsInput()).isFalse(); // full + assertThat(operator.getOutput()).isNotNull(); // partial flush + assertThat(operator.isFinished()).isFalse(); // not finished. just partial flushing. assertThatThrownBy(() -> operator.addInput(inputPage)).isInstanceOf(IllegalStateException.class); // while flushing - assertNull(operator.getOutput()); // clear flushing - assertTrue(operator.needsInput()); // flushing done + assertThat(operator.getOutput()).isNull(); // clear flushing + assertThat(operator.needsInput()).isTrue(); // flushing done } else { - assertTrue(operator.needsInput()); - assertNull(operator.getOutput()); + assertThat(operator.needsInput()).isTrue(); + assertThat(operator.getOutput()).isNull(); } } } @@ -300,13 +296,13 @@ public void testMemoryReservationYield() int count = 0; for (Page page : result.getOutput()) { - assertEquals(page.getChannelCount(), 2); + assertThat(page.getChannelCount()).isEqualTo(2); for (int i = 0; i < page.getPositionCount(); i++) { - assertEquals(page.getBlock(1).getByte(i, 0), 1); + assertThat(page.getBlock(1).getByte(i, 0)).isEqualTo((byte) 1); count++; } } - assertEquals(count, 1_000 * 500); + assertThat(count).isEqualTo(1_000 * 500); } @Test diff --git a/core/trino-main/src/test/java/io/trino/operator/TestTypeSignature.java b/core/trino-main/src/test/java/io/trino/operator/TestTypeSignature.java index a064cc7d759c..a31db515c866 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestTypeSignature.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestTypeSignature.java @@ -42,11 +42,8 @@ import static io.trino.spi.type.VarcharType.createVarcharType; import static io.trino.sql.analyzer.TypeSignatureTranslator.parseTypeSignature; import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotEquals; -import static org.testng.Assert.assertTrue; public class TestTypeSignature { @@ -54,9 +51,9 @@ public class TestTypeSignature public void parseSignatureWithLiterals() { TypeSignature result = new TypeSignature("decimal", typeVariable("X"), numericParameter(42)); - assertEquals(result.getParameters().size(), 2); - assertEquals(result.getParameters().get(0).isVariable(), true); - assertEquals(result.getParameters().get(1).isLongLiteral(), true); + assertThat(result.getParameters().size()).isEqualTo(2); + assertThat(result.getParameters().get(0).isVariable()).isEqualTo(true); + assertThat(result.getParameters().get(1).isLongLiteral()).isEqualTo(true); } @Test @@ -267,25 +264,26 @@ public void parseWithLiteralParameters() @Test public void testVarchar() { - assertEquals(VARCHAR.getTypeSignature().toString(), "varchar"); - assertEquals(createVarcharType(42).getTypeSignature().toString(), "varchar(42)"); - assertEquals(VARCHAR.getTypeSignature(), createUnboundedVarcharType().getTypeSignature()); - assertEquals(createUnboundedVarcharType().getTypeSignature(), VARCHAR.getTypeSignature()); - assertEquals(VARCHAR.getTypeSignature().hashCode(), createUnboundedVarcharType().getTypeSignature().hashCode()); - assertNotEquals(createUnboundedVarcharType().getTypeSignature(), createVarcharType(10).getTypeSignature()); + assertThat(VARCHAR.getTypeSignature().toString()).isEqualTo("varchar"); + assertThat(createVarcharType(42).getTypeSignature().toString()).isEqualTo("varchar(42)"); + assertThat(VARCHAR.getTypeSignature()).isEqualTo(createUnboundedVarcharType().getTypeSignature()); + assertThat(createUnboundedVarcharType().getTypeSignature()).isEqualTo(VARCHAR.getTypeSignature()); + assertThat(VARCHAR.getTypeSignature().hashCode()).isEqualTo(createUnboundedVarcharType().getTypeSignature().hashCode()); + assertThat(createUnboundedVarcharType().getTypeSignature()) + .isNotEqualTo(createVarcharType(10).getTypeSignature()); } @Test public void testIsCalculated() { - assertFalse(BIGINT.getTypeSignature().isCalculated()); - assertTrue(new TypeSignature("decimal", typeVariable("p"), typeVariable("s")).isCalculated()); - assertFalse(createDecimalType(2, 1).getTypeSignature().isCalculated()); - assertTrue(arrayType(new TypeSignature("decimal", typeVariable("p"), typeVariable("s"))).isCalculated()); - assertFalse(arrayType(createDecimalType(2, 1).getTypeSignature()).isCalculated()); - assertTrue(mapType(new TypeSignature("decimal", typeVariable("p1"), typeVariable("s1")), new TypeSignature("decimal", typeVariable("p2"), typeVariable("s2"))).isCalculated()); - assertFalse(mapType(createDecimalType(2, 1).getTypeSignature(), createDecimalType(3, 1).getTypeSignature()).isCalculated()); - assertTrue(rowType(namedField("a", new TypeSignature("decimal", typeVariable("p1"), typeVariable("s1"))), namedField("b", new TypeSignature("decimal", typeVariable("p2"), typeVariable("s2")))).isCalculated()); + assertThat(BIGINT.getTypeSignature().isCalculated()).isFalse(); + assertThat(new TypeSignature("decimal", typeVariable("p"), typeVariable("s")).isCalculated()).isTrue(); + assertThat(createDecimalType(2, 1).getTypeSignature().isCalculated()).isFalse(); + assertThat(arrayType(new TypeSignature("decimal", typeVariable("p"), typeVariable("s"))).isCalculated()).isTrue(); + assertThat(arrayType(createDecimalType(2, 1).getTypeSignature()).isCalculated()).isFalse(); + assertThat(mapType(new TypeSignature("decimal", typeVariable("p1"), typeVariable("s1")), new TypeSignature("decimal", typeVariable("p2"), typeVariable("s2"))).isCalculated()).isTrue(); + assertThat(mapType(createDecimalType(2, 1).getTypeSignature(), createDecimalType(3, 1).getTypeSignature()).isCalculated()).isFalse(); + assertThat(rowType(namedField("a", new TypeSignature("decimal", typeVariable("p1"), typeVariable("s1"))), namedField("b", new TypeSignature("decimal", typeVariable("p2"), typeVariable("s2")))).isCalculated()).isTrue(); } private static void assertRowSignature( @@ -294,7 +292,7 @@ private static void assertRowSignature( TypeSignature expectedSignature) { TypeSignature signature = parseTypeSignature(typeName, literalParameters); - assertEquals(signature, expectedSignature); + assertThat(signature).isEqualTo(expectedSignature); } private static void assertRowSignature( @@ -316,12 +314,12 @@ private static void assertSignature( String expectedTypeName) { TypeSignature signature = parseTypeSignature(typeName, ImmutableSet.of()); - assertEquals(signature.getBase(), base); - assertEquals(signature.getParameters().size(), parameters.size()); + assertThat(signature.getBase()).isEqualTo(base); + assertThat(signature.getParameters().size()).isEqualTo(parameters.size()); for (int i = 0; i < signature.getParameters().size(); i++) { - assertEquals(signature.getParameters().get(i).toString(), parameters.get(i)); + assertThat(signature.getParameters().get(i).toString()).isEqualTo(parameters.get(i)); } - assertEquals(signature.toString(), expectedTypeName); + assertThat(signature.toString()).isEqualTo(expectedTypeName); } private void assertSignatureFail(String typeName) diff --git a/core/trino-main/src/test/java/io/trino/operator/TestWindowOperator.java b/core/trino-main/src/test/java/io/trino/operator/TestWindowOperator.java index 88adf84a5d48..f9c3e5f6f4aa 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestWindowOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestWindowOperator.java @@ -71,9 +71,7 @@ import static java.lang.String.format; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; @Test(singleThreaded = true) public class TestWindowOperator @@ -162,9 +160,11 @@ public void testMultipleOutputPages(boolean spillEnabled, boolean revokeMemoryWh assertGreaterThan(pages.size(), 1, "Expected more than one output page"); MaterializedResult actual = toMaterializedResult(driverContext.getSession(), expected.getTypes(), pages); - assertEquals(actual.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(actual.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); - assertTrue(spillEnabled == (spillerFactory.getSpillsCount() > 0), format("Spill state mismatch. Expected spill: %s, spill count: %s", spillEnabled, spillerFactory.getSpillsCount())); + assertThat(spillEnabled == (spillerFactory.getSpillsCount() > 0)) + .describedAs(format("Spill state mismatch. Expected spill: %s, spill count: %s", spillEnabled, spillerFactory.getSpillsCount())) + .isTrue(); } @Test(dataProvider = "spillEnabled") @@ -459,8 +459,8 @@ public void testClose() DriverContext driverContext = createDriverContext(1000); Operator operator = operatorFactory.createOperator(driverContext); operatorFactory.noMoreOperators(); - assertFalse(operator.isFinished()); - assertTrue(operator.needsInput()); + assertThat(operator.isFinished()).isFalse(); + assertThat(operator.needsInput()).isTrue(); operator.addInput(input.get(0)); operator.finish(); operator.getOutput(); @@ -834,7 +834,7 @@ public void testFindEndPosition() private static void assertFindEndPosition(String values, int expected) { char[] array = values.toCharArray(); - assertEquals(findEndPosition(0, array.length, (first, second) -> array[first] == array[second]), expected); + assertThat(findEndPosition(0, array.length, (first, second) -> array[first] == array[second])).isEqualTo(expected); } private WindowOperatorFactory createFactoryUnbounded( diff --git a/core/trino-main/src/test/java/io/trino/operator/TestWorkProcessor.java b/core/trino-main/src/test/java/io/trino/operator/TestWorkProcessor.java index 29fecad7d132..0f8fa8f5a3c5 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestWorkProcessor.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestWorkProcessor.java @@ -39,10 +39,8 @@ import static io.trino.operator.WorkProcessorAssertion.assertYields; import static io.trino.operator.WorkProcessorAssertion.processorFrom; import static io.trino.operator.WorkProcessorAssertion.transformationFrom; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestWorkProcessor { @@ -55,11 +53,11 @@ public void testIterator() ProcessState.finished())); Iterator iterator = processor.iterator(); - assertTrue(iterator.hasNext()); - assertEquals(iterator.next(), (Integer) 1); - assertTrue(iterator.hasNext()); - assertEquals(iterator.next(), (Integer) 2); - assertFalse(iterator.hasNext()); + assertThat(iterator.hasNext()).isTrue(); + assertThat(iterator.next()).isEqualTo((Integer) 1); + assertThat(iterator.hasNext()).isTrue(); + assertThat(iterator.next()).isEqualTo((Integer) 2); + assertThat(iterator.hasNext()).isFalse(); } @Test @@ -155,8 +153,8 @@ public void testMergeSortedEmptyStreams() ImmutableList.of(processorFrom(firstStream), processorFrom(secondStream)), Comparator.comparingInt(firstInteger -> firstInteger)); - assertFalse(mergedStream.isBlocked()); - assertFalse(mergedStream.isFinished()); + assertThat(mergedStream.isBlocked()).isFalse(); + assertThat(mergedStream.isFinished()).isFalse(); // first stream blocked assertBlocks(mergedStream); @@ -192,8 +190,8 @@ public void testMergeSortedEmptyStreamsWithFinishedOnly() Comparator.comparingInt(firstInteger -> firstInteger)); // before - assertFalse(mergedStream.isBlocked()); - assertFalse(mergedStream.isFinished()); + assertThat(mergedStream.isBlocked()).isFalse(); + assertThat(mergedStream.isFinished()).isFalse(); assertFinishes(mergedStream); } @@ -298,7 +296,7 @@ public void testProcessStateMonitor() assertUnblocks(processor, future); assertFinishes(processor); - assertEquals(actions.build(), ImmutableList.of(RESULT, YIELD, BLOCKED, FINISHED)); + assertThat(actions.build()).isEqualTo(ImmutableList.of(RESULT, YIELD, BLOCKED, FINISHED)); } @Test @@ -480,8 +478,8 @@ public void testTransform() .transform(transformationFrom(transformationScenario)); // before - assertFalse(processor.isBlocked()); - assertFalse(processor.isFinished()); + assertThat(processor.isBlocked()).isFalse(); + assertThat(processor.isFinished()).isFalse(); // base.yield assertYields(processor); @@ -532,8 +530,8 @@ public void testCreateFrom() WorkProcessor processor = processorFrom(scenario); // before - assertFalse(processor.isBlocked()); - assertFalse(processor.isFinished()); + assertThat(processor.isBlocked()).isFalse(); + assertThat(processor.isFinished()).isFalse(); assertYields(processor); assertResult(processor, 1); diff --git a/core/trino-main/src/test/java/io/trino/operator/TestingExchangeHttpClientHandler.java b/core/trino-main/src/test/java/io/trino/operator/TestingExchangeHttpClientHandler.java index 0095f3e0325b..2543238d4842 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestingExchangeHttpClientHandler.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestingExchangeHttpClientHandler.java @@ -40,7 +40,7 @@ import static io.trino.server.PagesResponseWriter.SERIALIZED_PAGES_MAGIC; import static jakarta.ws.rs.core.HttpHeaders.CONTENT_TYPE; import static java.util.Objects.requireNonNull; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestingExchangeHttpClientHandler implements TestingHttpClient.Processor @@ -59,11 +59,11 @@ public Response handle(Request request) { ImmutableList parts = ImmutableList.copyOf(Splitter.on("/").omitEmptyStrings().split(request.getUri().getPath())); if (request.getMethod().equals("DELETE")) { - assertEquals(parts.size(), 1); + assertThat(parts.size()).isEqualTo(1); return new TestingResponse(HttpStatus.NO_CONTENT, ImmutableListMultimap.of(), new byte[0]); } - assertEquals(parts.size(), 2); + assertThat(parts.size()).isEqualTo(2); TaskId taskId = TaskId.valueOf(parts.get(0)); int pageToken = Integer.parseInt(parts.get(1)); diff --git a/core/trino-main/src/test/java/io/trino/operator/WorkProcessorAssertion.java b/core/trino-main/src/test/java/io/trino/operator/WorkProcessorAssertion.java index f30fa82e0eb7..053dfa5c3168 100644 --- a/core/trino-main/src/test/java/io/trino/operator/WorkProcessorAssertion.java +++ b/core/trino-main/src/test/java/io/trino/operator/WorkProcessorAssertion.java @@ -26,9 +26,7 @@ import static java.lang.String.format; import static java.util.Objects.requireNonNull; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public final class WorkProcessorAssertion { @@ -36,44 +34,44 @@ private WorkProcessorAssertion() {} public static void assertBlocks(WorkProcessor processor) { - assertFalse(processor.process()); - assertTrue(processor.isBlocked()); - assertFalse(processor.isFinished()); - assertFalse(processor.process()); + assertThat(processor.process()).isFalse(); + assertThat(processor.isBlocked()).isTrue(); + assertThat(processor.isFinished()).isFalse(); + assertThat(processor.process()).isFalse(); } public static void assertUnblocks(WorkProcessor processor, SettableFuture future) { future.set(null); - assertFalse(processor.isBlocked()); + assertThat(processor.isBlocked()).isFalse(); } public static void assertYields(WorkProcessor processor) { - assertFalse(processor.process()); - assertFalse(processor.isBlocked()); - assertFalse(processor.isFinished()); + assertThat(processor.process()).isFalse(); + assertThat(processor.isBlocked()).isFalse(); + assertThat(processor.isFinished()).isFalse(); } public static void assertResult(WorkProcessor processor, T result) { - validateResult(processor, actualResult -> assertEquals(processor.getResult(), result)); + validateResult(processor, actualResult -> assertThat(processor.getResult()).isEqualTo(result)); } public static void validateResult(WorkProcessor processor, Consumer validator) { - assertTrue(processor.process()); - assertFalse(processor.isBlocked()); - assertFalse(processor.isFinished()); + assertThat(processor.process()).isTrue(); + assertThat(processor.isBlocked()).isFalse(); + assertThat(processor.isFinished()).isFalse(); validator.accept(processor.getResult()); } public static void assertFinishes(WorkProcessor processor) { - assertTrue(processor.process()); - assertFalse(processor.isBlocked()); - assertTrue(processor.isFinished()); - assertTrue(processor.process()); + assertThat(processor.process()).isTrue(); + assertThat(processor.isBlocked()).isFalse(); + assertThat(processor.isFinished()).isTrue(); + assertThat(processor.process()).isTrue(); } public static WorkProcessor.Transformation transformationFrom(List> transformations) @@ -85,7 +83,7 @@ public static WorkProcessor.Transformation transformationFrom(List< { Iterator> iterator = transformations.iterator(); return element -> { - assertTrue(iterator.hasNext()); + assertThat(iterator.hasNext()).isTrue(); return iterator.next().transform( Optional.ofNullable(element), (left, right) -> left.isPresent() == right.isPresent() @@ -97,7 +95,7 @@ public static WorkProcessor processorFrom(List> states) { Iterator> iterator = states.iterator(); return WorkProcessorUtils.create(() -> { - assertTrue(iterator.hasNext()); + assertThat(iterator.hasNext()).isTrue(); return iterator.next(); }); } @@ -120,7 +118,9 @@ private Transform(Optional from, TransformationState to) private TransformationState transform(Optional from, BiPredicate, Optional> equalsPredicate) { - assertTrue(equalsPredicate.test(from, this.from), format("Expected %s to be equal to %s", from, this.from)); + assertThat(equalsPredicate.test(from, this.from)) + .describedAs(format("Expected %s to be equal to %s", from, this.from)) + .isTrue(); return to; } } diff --git a/core/trino-main/src/test/java/io/trino/operator/aggregation/AbstractTestApproximateCountDistinct.java b/core/trino-main/src/test/java/io/trino/operator/aggregation/AbstractTestApproximateCountDistinct.java index 98634bc52845..a697a552f7e5 100644 --- a/core/trino-main/src/test/java/io/trino/operator/aggregation/AbstractTestApproximateCountDistinct.java +++ b/core/trino-main/src/test/java/io/trino/operator/aggregation/AbstractTestApproximateCountDistinct.java @@ -37,7 +37,7 @@ import static io.airlift.testing.Assertions.assertLessThan; import static io.trino.spi.type.DoubleType.DOUBLE; import static io.trino.sql.analyzer.TypeSignatureProvider.fromTypes; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public abstract class AbstractTestApproximateCountDistinct { @@ -122,17 +122,17 @@ public void testMultiplePositionsPartial(double maxStandardError) for (int i = 0; i < 100; ++i) { int uniques = ThreadLocalRandom.current().nextInt(getUniqueValuesCount()) + 1; List values = createRandomSample(uniques, (int) (uniques * 1.5)); - assertEquals(estimateCountPartial(values, maxStandardError), estimateGroupByCount(values, maxStandardError)); + assertThat(estimateCountPartial(values, maxStandardError)).isEqualTo(estimateGroupByCount(values, maxStandardError)); } } protected void assertCount(List values, double maxStandardError, long expectedCount) { if (!values.isEmpty()) { - assertEquals(estimateGroupByCount(values, maxStandardError), expectedCount); + assertThat(estimateGroupByCount(values, maxStandardError)).isEqualTo(expectedCount); } - assertEquals(estimateCount(values, maxStandardError), expectedCount); - assertEquals(estimateCountPartial(values, maxStandardError), expectedCount); + assertThat(estimateCount(values, maxStandardError)).isEqualTo(expectedCount); + assertThat(estimateCountPartial(values, maxStandardError)).isEqualTo(expectedCount); } private long estimateGroupByCount(List values, double maxStandardError) diff --git a/core/trino-main/src/test/java/io/trino/operator/aggregation/AbstractTestApproximateSetGeneric.java b/core/trino-main/src/test/java/io/trino/operator/aggregation/AbstractTestApproximateSetGeneric.java index 673eb6c03e96..589e45d89cb3 100644 --- a/core/trino-main/src/test/java/io/trino/operator/aggregation/AbstractTestApproximateSetGeneric.java +++ b/core/trino-main/src/test/java/io/trino/operator/aggregation/AbstractTestApproximateSetGeneric.java @@ -40,8 +40,7 @@ import static io.airlift.testing.Assertions.assertLessThan; import static io.trino.sql.analyzer.TypeSignatureProvider.fromTypes; import static java.util.Collections.shuffle; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; +import static org.assertj.core.api.Assertions.assertThat; public abstract class AbstractTestApproximateSetGeneric { @@ -61,8 +60,8 @@ protected int getUniqueValuesCount() @Test public void testNoPositions() { - assertNull(estimateSet(ImmutableList.of())); - assertNull(estimateSetPartial(ImmutableList.of())); + assertThat(estimateSet(ImmutableList.of())).isNull(); + assertThat(estimateSetPartial(ImmutableList.of())).isNull(); } @Test @@ -75,9 +74,9 @@ public void testSinglePosition() public void testAllPositionsNull() { List justNulls = Collections.nCopies(100, null); - assertNull(estimateSet(justNulls)); - assertNull(estimateSetPartial(justNulls)); - assertNull(estimateSetGrouped(justNulls)); + assertThat(estimateSet(justNulls)).isNull(); + assertThat(estimateSetPartial(justNulls)).isNull(); + assertThat(estimateSetGrouped(justNulls)).isNull(); } @Test @@ -122,7 +121,7 @@ public void testMultiplePositionsPartial() for (int i = 0; i < 100; ++i) { int uniques = ThreadLocalRandom.current().nextInt(getUniqueValuesCount()) + 1; List values = createRandomSample(uniques, (int) (uniques * 1.5)); - assertEquals(estimateSetPartial(values).cardinality(), estimateSetGrouped(values).cardinality()); + assertThat(estimateSetPartial(values).cardinality()).isEqualTo(estimateSetGrouped(values).cardinality()); } } @@ -132,9 +131,9 @@ public void testResultStability() for (int i = 0; i < 10; ++i) { List sample = new ArrayList<>(getResultStabilityTestSample()); shuffle(sample); - assertEquals(base16().encode(estimateSet(sample).serialize().getBytes()), getResultStabilityExpected()); - assertEquals(base16().encode(estimateSetPartial(sample).serialize().getBytes()), getResultStabilityExpected()); - assertEquals(base16().encode(estimateSetGrouped(sample).serialize().getBytes()), getResultStabilityExpected()); + assertThat(base16().encode(estimateSet(sample).serialize().getBytes())).isEqualTo(getResultStabilityExpected()); + assertThat(base16().encode(estimateSetPartial(sample).serialize().getBytes())).isEqualTo(getResultStabilityExpected()); + assertThat(base16().encode(estimateSetGrouped(sample).serialize().getBytes())).isEqualTo(getResultStabilityExpected()); } } @@ -146,10 +145,10 @@ protected void assertCount(List values, long expectedCount) { if (!values.isEmpty()) { HyperLogLog actualSet = estimateSetGrouped(values); - assertEquals(actualSet.cardinality(), expectedCount); + assertThat(actualSet.cardinality()).isEqualTo(expectedCount); } - assertEquals(estimateSet(values).cardinality(), expectedCount); - assertEquals(estimateSetPartial(values).cardinality(), expectedCount); + assertThat(estimateSet(values).cardinality()).isEqualTo(expectedCount); + assertThat(estimateSetPartial(values).cardinality()).isEqualTo(expectedCount); } private HyperLogLog estimateSetGrouped(List values) diff --git a/core/trino-main/src/test/java/io/trino/operator/aggregation/AggregationTestUtils.java b/core/trino-main/src/test/java/io/trino/operator/aggregation/AggregationTestUtils.java index d40d0e3c9443..c70805dd551d 100644 --- a/core/trino-main/src/test/java/io/trino/operator/aggregation/AggregationTestUtils.java +++ b/core/trino-main/src/test/java/io/trino/operator/aggregation/AggregationTestUtils.java @@ -39,8 +39,8 @@ import static io.trino.sql.planner.plan.AggregationNode.Step.PARTIAL; import static io.trino.sql.planner.plan.AggregationNode.Step.SINGLE; import static java.lang.String.format; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Fail.fail; public final class AggregationTestUtils { @@ -75,7 +75,9 @@ public static void assertAggregation(TestingFunctionResolution functionResolutio int positions = page.getPositionCount(); for (int i = 1; i < page.getChannelCount(); i++) { - assertEquals(positions, page.getBlock(i).getPositionCount(), "input blocks provided are not equal in position count"); + assertThat(positions) + .describedAs("input blocks provided are not equal in position count") + .isEqualTo(page.getBlock(i).getPositionCount()); } if (positions == 0) { assertAggregationInternal(function, equalAssertion, testDescription, expectedValue); @@ -155,14 +157,18 @@ private static Object distinctAggregation(TestingAggregationFunction function, P // Execute with masked pages and assure equal to normal execution Object aggregationWithDupes = aggregation(function, createArgs(parameterCount), maskChannel, dupedPages); - assertEquals(aggregationWithDupes, aggregation, "Inconsistent results with mask"); + assertThat(aggregationWithDupes) + .describedAs("Inconsistent results with mask") + .isEqualTo(aggregation); // Re-run the duplicated inputs with RLE masks System.arraycopy(maskPagesWithRle(true, pages), 0, dupedPages, 0, pages.length); System.arraycopy(maskPagesWithRle(false, pages), 0, dupedPages, pages.length, pages.length); Object aggregationWithRleMasks = aggregation(function, createArgs(parameterCount), maskChannel, dupedPages); - assertEquals(aggregationWithRleMasks, aggregation, "Inconsistent results with RLE mask"); + assertThat(aggregationWithRleMasks) + .describedAs("Inconsistent results with RLE mask") + .isEqualTo(aggregation); return aggregation; } @@ -203,12 +209,16 @@ public static Object aggregation(TestingAggregationFunction function, Page... pa // execute with args in reverse order: arg2, arg1, arg0 if (parameterCount > 1) { Object aggregationWithOffset = aggregation(function, reverseArgs(parameterCount), OptionalInt.empty(), reverseColumns(pages)); - assertEquals(aggregationWithOffset, aggregation, "Inconsistent results with reversed channels"); + assertThat(aggregationWithOffset) + .describedAs("Inconsistent results with reversed channels") + .isEqualTo(aggregation); } // execute with args at an offset (and possibly reversed): null, null, null, arg2, arg1, arg0 Object aggregationWithOffset = aggregation(function, offsetArgs(parameterCount, 3), OptionalInt.empty(), offsetColumns(pages, 3)); - assertEquals(aggregationWithOffset, aggregation, "Inconsistent results with channel offset"); + assertThat(aggregationWithOffset) + .describedAs("Inconsistent results with channel offset") + .isEqualTo(aggregation); return aggregation; } @@ -235,12 +245,16 @@ public static Object partialAggregation(TestingAggregationFunction function, Pag // execute with args in reverse order: arg2, arg1, arg0 if (parameterCount > 1) { Object aggregationWithOffset = partialAggregation(function, reverseArgs(parameterCount), reverseColumns(pages)); - assertEquals(aggregationWithOffset, aggregation, "Inconsistent results with reversed channels"); + assertThat(aggregationWithOffset) + .describedAs("Inconsistent results with reversed channels") + .isEqualTo(aggregation); } // execute with args at an offset (and possibly reversed): null, null, null, arg2, arg1, arg0 Object aggregationWithOffset = partialAggregation(function, offsetArgs(parameterCount, 3), offsetColumns(pages, 3)); - assertEquals(aggregationWithOffset, aggregation, "Inconsistent results with channel offset"); + assertThat(aggregationWithOffset) + .describedAs("Inconsistent results with channel offset") + .isEqualTo(aggregation); return aggregation; } @@ -307,7 +321,9 @@ public static Object groupedAggregation(TestingAggregationFunction function, int groupedAggregator.processPage(4000, createGroupByIdBlock(4000, page.getPositionCount()), page); } Object largeGroupValue = getGroupValue(function.getFinalType(), groupedAggregator, 4000); - assertEquals(largeGroupValue, groupValue, "Inconsistent results with large group id"); + assertThat(largeGroupValue) + .describedAs("Inconsistent results with large group id") + .isEqualTo(groupValue); return groupValue; } diff --git a/core/trino-main/src/test/java/io/trino/operator/aggregation/BenchmarkDecimalAggregation.java b/core/trino-main/src/test/java/io/trino/operator/aggregation/BenchmarkDecimalAggregation.java index a5512eba0ec5..d966431e3377 100644 --- a/core/trino-main/src/test/java/io/trino/operator/aggregation/BenchmarkDecimalAggregation.java +++ b/core/trino-main/src/test/java/io/trino/operator/aggregation/BenchmarkDecimalAggregation.java @@ -45,7 +45,7 @@ import static io.trino.sql.analyzer.TypeSignatureProvider.fromTypes; import static io.trino.sql.planner.plan.AggregationNode.Step.FINAL; import static io.trino.sql.planner.plan.AggregationNode.Step.PARTIAL; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; @State(Scope.Thread) @OutputTimeUnit(TimeUnit.NANOSECONDS) @@ -197,7 +197,7 @@ public void verify() BenchmarkData data = new BenchmarkData(); data.setup(); - assertEquals(data.getGroupIds().length, data.getValues().getPositionCount()); + assertThat(data.getGroupIds().length).isEqualTo(data.getValues().getPositionCount()); new BenchmarkDecimalAggregation().benchmark(data); } diff --git a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestAnyValueAggregation.java b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestAnyValueAggregation.java index 4724d8b6ec93..fb69af18e918 100644 --- a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestAnyValueAggregation.java +++ b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestAnyValueAggregation.java @@ -38,7 +38,7 @@ import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.sql.analyzer.TypeSignatureProvider.fromTypes; -import static org.testng.Assert.assertNotNull; +import static org.assertj.core.api.Assertions.assertThat; public class TestAnyValueAggregation { @@ -49,7 +49,7 @@ public void testAllRegistered() { Collection standardTypes = new TypeRegistry(new TypeOperators(), new FeaturesConfig()).getTypes(); for (Type valueType : standardTypes) { - assertNotNull(FUNCTION_RESOLUTION.getAggregateFunction("any_value", fromTypes(valueType))); + assertThat(FUNCTION_RESOLUTION.getAggregateFunction("any_value", fromTypes(valueType))).isNotNull(); } } diff --git a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestApproximateCountDistinctAggregations.java b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestApproximateCountDistinctAggregations.java index d57cc4d9898c..53f6c2a50b30 100644 --- a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestApproximateCountDistinctAggregations.java +++ b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestApproximateCountDistinctAggregations.java @@ -18,33 +18,33 @@ import static io.trino.operator.aggregation.ApproximateCountDistinctAggregation.standardErrorToBuckets; import static io.trino.spi.StandardErrorCode.INVALID_FUNCTION_ARGUMENT; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestApproximateCountDistinctAggregations { @Test public void testStandardErrorToBuckets() { - assertEquals(standardErrorToBuckets(0.0326), 1024); - assertEquals(standardErrorToBuckets(0.0325), 1024); - assertEquals(standardErrorToBuckets(0.0324), 2048); - assertEquals(standardErrorToBuckets(0.0231), 2048); - assertEquals(standardErrorToBuckets(0.0230), 2048); - assertEquals(standardErrorToBuckets(0.0229), 4096); - assertEquals(standardErrorToBuckets(0.0164), 4096); - assertEquals(standardErrorToBuckets(0.0163), 4096); - assertEquals(standardErrorToBuckets(0.0162), 8192); - assertEquals(standardErrorToBuckets(0.0116), 8192); - assertEquals(standardErrorToBuckets(0.0115), 8192); - assertEquals(standardErrorToBuckets(0.0114), 16384); - assertEquals(standardErrorToBuckets(0.008126), 16384); - assertEquals(standardErrorToBuckets(0.008125), 16384); - assertEquals(standardErrorToBuckets(0.008124), 32768); - assertEquals(standardErrorToBuckets(0.00576), 32768); - assertEquals(standardErrorToBuckets(0.00575), 32768); - assertEquals(standardErrorToBuckets(0.00574), 65536); - assertEquals(standardErrorToBuckets(0.0040626), 65536); - assertEquals(standardErrorToBuckets(0.0040625), 65536); + assertThat(standardErrorToBuckets(0.0326)).isEqualTo(1024); + assertThat(standardErrorToBuckets(0.0325)).isEqualTo(1024); + assertThat(standardErrorToBuckets(0.0324)).isEqualTo(2048); + assertThat(standardErrorToBuckets(0.0231)).isEqualTo(2048); + assertThat(standardErrorToBuckets(0.0230)).isEqualTo(2048); + assertThat(standardErrorToBuckets(0.0229)).isEqualTo(4096); + assertThat(standardErrorToBuckets(0.0164)).isEqualTo(4096); + assertThat(standardErrorToBuckets(0.0163)).isEqualTo(4096); + assertThat(standardErrorToBuckets(0.0162)).isEqualTo(8192); + assertThat(standardErrorToBuckets(0.0116)).isEqualTo(8192); + assertThat(standardErrorToBuckets(0.0115)).isEqualTo(8192); + assertThat(standardErrorToBuckets(0.0114)).isEqualTo(16384); + assertThat(standardErrorToBuckets(0.008126)).isEqualTo(16384); + assertThat(standardErrorToBuckets(0.008125)).isEqualTo(16384); + assertThat(standardErrorToBuckets(0.008124)).isEqualTo(32768); + assertThat(standardErrorToBuckets(0.00576)).isEqualTo(32768); + assertThat(standardErrorToBuckets(0.00575)).isEqualTo(32768); + assertThat(standardErrorToBuckets(0.00574)).isEqualTo(65536); + assertThat(standardErrorToBuckets(0.0040626)).isEqualTo(65536); + assertThat(standardErrorToBuckets(0.0040625)).isEqualTo(65536); } @Test diff --git a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestApproximateMostFrequentHistogram.java b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestApproximateMostFrequentHistogram.java index 2e0819cff544..54a98b565b0c 100644 --- a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestApproximateMostFrequentHistogram.java +++ b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestApproximateMostFrequentHistogram.java @@ -20,7 +20,7 @@ import java.util.Map; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestApproximateMostFrequentHistogram { @@ -37,8 +37,8 @@ public void testLongHistogram() Map buckets = histogram.getBuckets(); - assertEquals(buckets.size(), 3); - assertEquals(buckets, ImmutableMap.of(1L, 2L, 2L, 1L, 3L, 1L)); + assertThat(buckets.size()).isEqualTo(3); + assertThat(buckets).isEqualTo(ImmutableMap.of(1L, 2L, 2L, 1L, 3L, 1L)); } @Test @@ -56,7 +56,7 @@ public void testLongRoundtrip() ApproximateMostFrequentHistogram deserialized = new ApproximateMostFrequentHistogram(serialized, LongApproximateMostFrequentStateSerializer::serializeBucket, LongApproximateMostFrequentStateSerializer::deserializeBucket); - assertEquals(deserialized.getBuckets(), original.getBuckets()); + assertThat(deserialized.getBuckets()).isEqualTo(original.getBuckets()); } @Test @@ -75,8 +75,8 @@ public void testMerge() histogram1.merge(histogram2); Map buckets = histogram1.getBuckets(); - assertEquals(buckets.size(), 3); - assertEquals(buckets, ImmutableMap.of(1L, 2L, 2L, 1L, 3L, 1L)); + assertThat(buckets.size()).isEqualTo(3); + assertThat(buckets).isEqualTo(ImmutableMap.of(1L, 2L, 2L, 1L, 3L, 1L)); } @Test @@ -105,8 +105,8 @@ public void testLongMergeOverMaxbuckets() histogram1.merge(histogram2); Map buckets = histogram1.getBuckets(); - assertEquals(buckets.size(), 3); - assertEquals(buckets, ImmutableMap.of(1L, 4L, 2L, 4L, 3L, 4L)); + assertThat(buckets.size()).isEqualTo(3); + assertThat(buckets).isEqualTo(ImmutableMap.of(1L, 4L, 2L, 4L, 3L, 4L)); } @Test @@ -122,8 +122,8 @@ public void testStringHistogram() Map buckets = histogram.getBuckets(); - assertEquals(buckets.size(), 3); - assertEquals(buckets, ImmutableMap.of(Slices.utf8Slice("A"), 2L, Slices.utf8Slice("B"), 1L, Slices.utf8Slice("C"), 1L)); + assertThat(buckets.size()).isEqualTo(3); + assertThat(buckets).isEqualTo(ImmutableMap.of(Slices.utf8Slice("A"), 2L, Slices.utf8Slice("B"), 1L, Slices.utf8Slice("C"), 1L)); } @Test @@ -141,7 +141,7 @@ public void testStringRoundtrip() ApproximateMostFrequentHistogram deserialized = new ApproximateMostFrequentHistogram(serialized, StringApproximateMostFrequentStateSerializer::serializeBucket, StringApproximateMostFrequentStateSerializer::deserializeBucket); - assertEquals(deserialized.getBuckets(), original.getBuckets()); + assertThat(deserialized.getBuckets()).isEqualTo(original.getBuckets()); } @Test @@ -170,7 +170,7 @@ public void testStringMergeOverMaxbuckets() histogram1.merge(histogram2); Map buckets = histogram1.getBuckets(); - assertEquals(buckets.size(), 3); - assertEquals(buckets, ImmutableMap.of(Slices.utf8Slice("A"), 4L, Slices.utf8Slice("B"), 4L, Slices.utf8Slice("C"), 4L)); + assertThat(buckets.size()).isEqualTo(3); + assertThat(buckets).isEqualTo(ImmutableMap.of(Slices.utf8Slice("A"), 4L, Slices.utf8Slice("B"), 4L, Slices.utf8Slice("C"), 4L)); } } diff --git a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestArbitraryAggregation.java b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestArbitraryAggregation.java index 2a7b5f83a4de..2c30f46ad9f6 100644 --- a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestArbitraryAggregation.java +++ b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestArbitraryAggregation.java @@ -38,7 +38,7 @@ import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.sql.analyzer.TypeSignatureProvider.fromTypes; -import static org.testng.Assert.assertNotNull; +import static org.assertj.core.api.Assertions.assertThat; public class TestArbitraryAggregation { @@ -49,7 +49,7 @@ public void testAllRegistered() { Collection standardTypes = new TypeRegistry(new TypeOperators(), new FeaturesConfig()).getTypes(); for (Type valueType : standardTypes) { - assertNotNull(FUNCTION_RESOLUTION.getAggregateFunction("arbitrary", fromTypes(valueType))); + assertThat(FUNCTION_RESOLUTION.getAggregateFunction("arbitrary", fromTypes(valueType))).isNotNull(); } } diff --git a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestArrayAggregation.java b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestArrayAggregation.java index b5f937984ab5..d07923de8669 100644 --- a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestArrayAggregation.java +++ b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestArrayAggregation.java @@ -42,7 +42,7 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.sql.analyzer.TypeSignatureProvider.fromTypes; import static io.trino.sql.planner.plan.AggregationNode.Step.SINGLE; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestArrayAggregation { @@ -145,7 +145,7 @@ public void testEmptyStateOutputsNull() BlockBuilder blockBuilder = bigIntAgg.getFinalType().createBlockBuilder(null, 1000); groupedAggregator.evaluate(0, blockBuilder); - assertTrue(blockBuilder.build().isNull(0)); + assertThat(blockBuilder.build().isNull(0)).isTrue(); } @Test diff --git a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestDecimalAverageAggregation.java b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestDecimalAverageAggregation.java index 4554352ed605..a81785db935c 100644 --- a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestDecimalAverageAggregation.java +++ b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestDecimalAverageAggregation.java @@ -36,7 +36,7 @@ import static java.math.BigInteger.TEN; import static java.math.BigInteger.ZERO; import static java.math.RoundingMode.HALF_UP; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; @Test(singleThreaded = true) public class TestDecimalAverageAggregation @@ -59,15 +59,15 @@ public void testOverflow() { addToState(state, TWO.pow(126)); - assertEquals(state.getLong(), 1); - assertEquals(state.getOverflow(), 0); - assertEquals(getDecimal(state), Int128.valueOf(TWO.pow(126))); + assertThat(state.getLong()).isEqualTo(1); + assertThat(state.getOverflow()).isEqualTo(0); + assertThat(getDecimal(state)).isEqualTo(Int128.valueOf(TWO.pow(126))); addToState(state, TWO.pow(126)); - assertEquals(state.getLong(), 2); - assertEquals(state.getOverflow(), 1); - assertEquals(getDecimal(state), Int128.valueOf(1L << 63, 0)); + assertThat(state.getLong()).isEqualTo(2); + assertThat(state.getOverflow()).isEqualTo(1); + assertThat(getDecimal(state)).isEqualTo(Int128.valueOf(1L << 63, 0)); assertAverageEquals(TWO.pow(126)); } @@ -77,15 +77,15 @@ public void testUnderflow() { addToState(state, Decimals.MIN_UNSCALED_DECIMAL.toBigInteger()); - assertEquals(state.getLong(), 1); - assertEquals(state.getOverflow(), 0); - assertEquals(getDecimal(state), Decimals.MIN_UNSCALED_DECIMAL); + assertThat(state.getLong()).isEqualTo(1); + assertThat(state.getOverflow()).isEqualTo(0); + assertThat(getDecimal(state)).isEqualTo(Decimals.MIN_UNSCALED_DECIMAL); addToState(state, Decimals.MIN_UNSCALED_DECIMAL.toBigInteger()); - assertEquals(state.getLong(), 2); - assertEquals(state.getOverflow(), -1); - assertEquals(getDecimal(state), Int128.valueOf(0x698966AF4AF2770BL, 0xECEBBB8000000002L)); + assertThat(state.getLong()).isEqualTo(2); + assertThat(state.getOverflow()).isEqualTo(-1); + assertThat(getDecimal(state)).isEqualTo(Int128.valueOf(0x698966AF4AF2770BL, 0xECEBBB8000000002L)); assertAverageEquals(Decimals.MIN_UNSCALED_DECIMAL.toBigInteger()); } @@ -97,15 +97,15 @@ public void testUnderflowAfterOverflow() addToState(state, TWO.pow(126)); addToState(state, TWO.pow(125)); - assertEquals(state.getOverflow(), 1); - assertEquals(getDecimal(state), Int128.valueOf((1L << 63) | (1L << 61), 0)); + assertThat(state.getOverflow()).isEqualTo(1); + assertThat(getDecimal(state)).isEqualTo(Int128.valueOf((1L << 63) | (1L << 61), 0)); addToState(state, TWO.pow(126).negate()); addToState(state, TWO.pow(126).negate()); addToState(state, TWO.pow(126).negate()); - assertEquals(state.getOverflow(), 0); - assertEquals(getDecimal(state), Int128.valueOf(TWO.pow(125).negate())); + assertThat(state.getOverflow()).isEqualTo(0); + assertThat(getDecimal(state)).isEqualTo(Int128.valueOf(TWO.pow(125).negate())); assertAverageEquals(TWO.pow(125).negate().divide(BigInteger.valueOf(6))); } @@ -122,9 +122,9 @@ public void testCombineOverflow() addToState(otherState, TWO.pow(126)); DecimalAverageAggregation.combine(state, otherState); - assertEquals(state.getLong(), 4); - assertEquals(state.getOverflow(), 1); - assertEquals(getDecimal(state), Int128.ZERO); + assertThat(state.getLong()).isEqualTo(4); + assertThat(state.getOverflow()).isEqualTo(1); + assertThat(getDecimal(state)).isEqualTo(Int128.ZERO); BigInteger expectedAverage = BigInteger.ZERO .add(TWO.pow(126)) @@ -148,9 +148,9 @@ public void testCombineUnderflow() addToState(otherState, TWO.pow(126).negate()); DecimalAverageAggregation.combine(state, otherState); - assertEquals(state.getLong(), 4); - assertEquals(state.getOverflow(), -1); - assertEquals(getDecimal(state), Int128.valueOf(1L << 62, 0)); + assertThat(state.getLong()).isEqualTo(4); + assertThat(state.getOverflow()).isEqualTo(-1); + assertThat(getDecimal(state)).isEqualTo(Int128.valueOf(1L << 62, 0)); BigInteger expectedAverage = BigInteger.ZERO .add(TWO.pow(126)) @@ -177,12 +177,12 @@ private void testNoOverflow(DecimalType type, List numbers) addToState(type, state, number); } - assertEquals(state.getOverflow(), 0); + assertThat(state.getOverflow()).isEqualTo(0); BigInteger sum = numbers.stream().reduce(BigInteger.ZERO, BigInteger::add); - assertEquals(getDecimal(state), Int128.valueOf(sum)); + assertThat(getDecimal(state)).isEqualTo(Int128.valueOf(sum)); BigDecimal expectedAverage = new BigDecimal(sum, type.getScale()).divide(BigDecimal.valueOf(numbers.size()), type.getScale(), HALF_UP); - assertEquals(decodeBigDecimal(type, average(state, type)), expectedAverage); + assertThat(decodeBigDecimal(type, average(state, type))).isEqualTo(expectedAverage); } @DataProvider @@ -218,7 +218,7 @@ private void assertAverageEquals(BigInteger expectedAverage) private void assertAverageEquals(BigInteger expectedAverage, DecimalType type) { - assertEquals(average(state, type).toBigInteger(), expectedAverage); + assertThat(average(state, type).toBigInteger()).isEqualTo(expectedAverage); } private static void addToState(LongDecimalWithOverflowAndLongState state, BigInteger value) diff --git a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestDecimalSumAggregation.java b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestDecimalSumAggregation.java index 66ead07005fc..5e115fe7fdd3 100644 --- a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestDecimalSumAggregation.java +++ b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestDecimalSumAggregation.java @@ -25,8 +25,8 @@ import java.math.BigInteger; import static io.trino.spi.type.DecimalType.createDecimalType; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestDecimalSumAggregation { @@ -40,13 +40,13 @@ public void testOverflow() addToState(state, TWO.pow(126)); - assertEquals(state.getOverflow(), 0); - assertEquals(getDecimal(state), Int128.valueOf(TWO.pow(126))); + assertThat(state.getOverflow()).isEqualTo(0); + assertThat(getDecimal(state)).isEqualTo(Int128.valueOf(TWO.pow(126))); addToState(state, TWO.pow(126)); - assertEquals(state.getOverflow(), 1); - assertEquals(getDecimal(state), Int128.valueOf(1L << 63, 0)); + assertThat(state.getOverflow()).isEqualTo(1); + assertThat(getDecimal(state)).isEqualTo(Int128.valueOf(1L << 63, 0)); } @Test @@ -56,13 +56,13 @@ public void testUnderflow() addToState(state, TWO.pow(126).negate()); - assertEquals(state.getOverflow(), 0); - assertEquals(getDecimal(state), Int128.valueOf(TWO.pow(126).negate())); + assertThat(state.getOverflow()).isEqualTo(0); + assertThat(getDecimal(state)).isEqualTo(Int128.valueOf(TWO.pow(126).negate())); addToState(state, TWO.pow(126).negate()); - assertEquals(state.getOverflow(), 0); - assertEquals(getDecimal(state), Int128.valueOf(0x8000000000000000L, 0)); + assertThat(state.getOverflow()).isEqualTo(0); + assertThat(getDecimal(state)).isEqualTo(Int128.valueOf(0x8000000000000000L, 0)); } @Test @@ -74,15 +74,15 @@ public void testUnderflowAfterOverflow() addToState(state, TWO.pow(126)); addToState(state, TWO.pow(125)); - assertEquals(state.getOverflow(), 1); - assertEquals(getDecimal(state), Int128.valueOf((1L << 63) | (1L << 61), 0)); + assertThat(state.getOverflow()).isEqualTo(1); + assertThat(getDecimal(state)).isEqualTo(Int128.valueOf((1L << 63) | (1L << 61), 0)); addToState(state, TWO.pow(126).negate()); addToState(state, TWO.pow(126).negate()); addToState(state, TWO.pow(126).negate()); - assertEquals(state.getOverflow(), 0); - assertEquals(getDecimal(state), Int128.valueOf(TWO.pow(125).negate())); + assertThat(state.getOverflow()).isEqualTo(0); + assertThat(getDecimal(state)).isEqualTo(Int128.valueOf(TWO.pow(125).negate())); } @Test @@ -99,8 +99,8 @@ public void testCombineOverflow() addToState(otherState, TWO.pow(126)); DecimalSumAggregation.combine(state, otherState); - assertEquals(state.getOverflow(), 1); - assertEquals(getDecimal(state), Int128.valueOf(0xC000000000000000L, 0)); + assertThat(state.getOverflow()).isEqualTo(1); + assertThat(getDecimal(state)).isEqualTo(Int128.valueOf(0xC000000000000000L, 0)); } @Test @@ -117,8 +117,8 @@ public void testCombineUnderflow() addToState(otherState, TWO.pow(126).negate()); DecimalSumAggregation.combine(state, otherState); - assertEquals(state.getOverflow(), -1); - assertEquals(getDecimal(state), Int128.valueOf(0x4000000000000000L, 0)); + assertThat(state.getOverflow()).isEqualTo(-1); + assertThat(getDecimal(state)).isEqualTo(Int128.valueOf(0x4000000000000000L, 0)); } @Test @@ -129,7 +129,7 @@ public void testOverflowOnOutput() addToState(state, TWO.pow(126)); addToState(state, TWO.pow(126)); - assertEquals(state.getOverflow(), 1); + assertThat(state.getOverflow()).isEqualTo(1); assertThatThrownBy(() -> DecimalSumAggregation.outputDecimal(state, new VariableWidthBlockBuilder(null, 10, 100))) .isInstanceOf(ArithmeticException.class) .hasMessage("Decimal overflow"); diff --git a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestDoubleHistogramAggregation.java b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestDoubleHistogramAggregation.java index 6c6840d4c590..e47a98ba8cf5 100644 --- a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestDoubleHistogramAggregation.java +++ b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestDoubleHistogramAggregation.java @@ -37,9 +37,8 @@ import static io.trino.sql.planner.plan.AggregationNode.Step.PARTIAL; import static io.trino.sql.planner.plan.AggregationNode.Step.SINGLE; import static io.trino.util.StructuralTestUtil.mapType; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; public class TestDoubleHistogramAggregation { @@ -71,7 +70,7 @@ public void test() finalStep.processPage(new Page(partialBlock)); Block actual = getFinalBlock(finalType, finalStep); - assertEquals(extractSingleValue(actual), extractSingleValue(expected)); + assertThat(extractSingleValue(actual)).isEqualTo(extractSingleValue(expected)); } @Test @@ -93,7 +92,7 @@ public void testMerge() Map expected = Maps.transformValues(extractSingleValue(singleStepResult), value -> value * 2); - assertEquals(extractSingleValue(actual), expected); + assertThat(extractSingleValue(actual)).isEqualTo(expected); } @Test @@ -102,8 +101,8 @@ public void testNull() Aggregator aggregator = getAggregator(SINGLE); Block result = getFinalBlock(finalType, aggregator); - assertTrue(result.getPositionCount() == 1); - assertTrue(result.isNull(0)); + assertThat(result.getPositionCount() == 1).isTrue(); + assertThat(result.isNull(0)).isTrue(); } @Test diff --git a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestHistogram.java b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestHistogram.java index 4fa033da877e..04989c974320 100644 --- a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestHistogram.java +++ b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestHistogram.java @@ -62,7 +62,7 @@ import static io.trino.util.DateTimeZoneIndex.getDateTimeZone; import static io.trino.util.StructuralTestUtil.mapType; import static io.trino.util.StructuralTestUtil.sqlMapOf; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestHistogram { @@ -240,7 +240,7 @@ public void testEmptyHistogramOutputsNull() BlockBuilder blockBuilder = function.getFinalType().createBlockBuilder(null, 1000); groupedAggregator.evaluate(0, blockBuilder); - assertTrue(blockBuilder.build().isNull(0)); + assertThat(blockBuilder.build().isNull(0)).isTrue(); } @Test diff --git a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestMinMaxByAggregation.java b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestMinMaxByAggregation.java index 019627a68a78..9cd17b7d8450 100644 --- a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestMinMaxByAggregation.java +++ b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestMinMaxByAggregation.java @@ -50,7 +50,7 @@ import static io.trino.sql.analyzer.TypeSignatureProvider.fromTypes; import static io.trino.type.UnknownType.UNKNOWN; import static java.util.Arrays.asList; -import static org.testng.Assert.assertNotNull; +import static org.assertj.core.api.Assertions.assertThat; public class TestMinMaxByAggregation { @@ -66,8 +66,8 @@ public void testAllRegistered() for (Type keyType : orderableTypes) { for (Type valueType : getTypes()) { - assertNotNull(FUNCTION_RESOLUTION.getAggregateFunction("min_by", fromTypes(valueType, keyType))); - assertNotNull(FUNCTION_RESOLUTION.getAggregateFunction("max_by", fromTypes(valueType, keyType))); + assertThat(FUNCTION_RESOLUTION.getAggregateFunction("min_by", fromTypes(valueType, keyType))).isNotNull(); + assertThat(FUNCTION_RESOLUTION.getAggregateFunction("max_by", fromTypes(valueType, keyType))).isNotNull(); } } } diff --git a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestMultimapAggAggregation.java b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestMultimapAggAggregation.java index 771e1e2d230c..301f97d7a3c1 100644 --- a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestMultimapAggAggregation.java +++ b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestMultimapAggAggregation.java @@ -45,7 +45,7 @@ import static io.trino.sql.analyzer.TypeSignatureProvider.fromTypes; import static io.trino.sql.planner.plan.AggregationNode.Step.SINGLE; import static io.trino.util.StructuralTestUtil.mapType; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestMultimapAggAggregation { @@ -174,7 +174,7 @@ public void testEmptyStateOutputIsNull() GroupedAggregator groupedAggregator = aggregationFunction.createAggregatorFactory(SINGLE, Ints.asList(), OptionalInt.empty()).createGroupedAggregator(); BlockBuilder blockBuilder = aggregationFunction.getFinalType().createBlockBuilder(null, 1); groupedAggregator.evaluate(0, blockBuilder); - assertTrue(blockBuilder.build().isNull(0)); + assertThat(blockBuilder.build().isNull(0)).isTrue(); } private static TestingAggregationFunction getAggregationFunction(Type keyType, Type valueType) diff --git a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestNumericHistogram.java b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestNumericHistogram.java index b3f5854b24ca..45ff235ca3b1 100644 --- a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestNumericHistogram.java +++ b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestNumericHistogram.java @@ -22,7 +22,7 @@ import java.util.Map; import java.util.Set; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestNumericHistogram { @@ -61,7 +61,7 @@ public void testBasic() .put(4.233333333333333, 3.0) .buildOrThrow(); - assertEquals(histogram.getBuckets(), expected); + assertThat(histogram.getBuckets()).isEqualTo(expected); } @Test @@ -79,7 +79,7 @@ public void testSameValues() .put(2.0, 500.0) .buildOrThrow(); - assertEquals(histogram.getBuckets(), expected); + assertThat(histogram.getBuckets()).isEqualTo(expected); } @Test @@ -93,7 +93,7 @@ public void testRoundtrip() Slice serialized = histogram.serialize(); NumericHistogram deserialized = new NumericHistogram(serialized, 20); - assertEquals(deserialized.getBuckets(), histogram.getBuckets()); + assertThat(deserialized.getBuckets()).isEqualTo(histogram.getBuckets()); } @Test @@ -108,7 +108,7 @@ public void testMergeSame() histogram.mergeWith(histogram); - assertEquals(histogram.getBuckets(), expected); + assertThat(histogram.getBuckets()).isEqualTo(expected); } @Test @@ -131,6 +131,6 @@ public void testMergeDifferent() expected.compact(); histogram1.mergeWith(histogram2); - assertEquals(histogram1.getBuckets(), expected.getBuckets()); + assertThat(histogram1.getBuckets()).isEqualTo(expected.getBuckets()); } } diff --git a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestRealHistogramAggregation.java b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestRealHistogramAggregation.java index 5bef8be7da9e..0e890ebe6028 100644 --- a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestRealHistogramAggregation.java +++ b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestRealHistogramAggregation.java @@ -37,9 +37,8 @@ import static io.trino.sql.planner.plan.AggregationNode.Step.PARTIAL; import static io.trino.sql.planner.plan.AggregationNode.Step.SINGLE; import static io.trino.util.StructuralTestUtil.mapType; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; public class TestRealHistogramAggregation { @@ -68,7 +67,7 @@ public void test() finalStep.processPage(new Page(partialBlock)); Block actual = getFinalBlock(function.getFinalType(), finalStep); - assertEquals(extractSingleValue(actual), extractSingleValue(expected)); + assertThat(extractSingleValue(actual)).isEqualTo(extractSingleValue(expected)); } @Test @@ -90,7 +89,7 @@ public void testMerge() Map expected = Maps.transformValues(extractSingleValue(singleStepResult), value -> value * 2); - assertEquals(extractSingleValue(actual), expected); + assertThat(extractSingleValue(actual)).isEqualTo(expected); } private Aggregator createAggregator(Step step) @@ -104,8 +103,8 @@ public void testNull() Aggregator aggregator = createAggregator(SINGLE); Block result = getFinalBlock(function.getFinalType(), aggregator); - assertEquals(result.getPositionCount(), 1); - assertTrue(result.isNull(0)); + assertThat(result.getPositionCount()).isEqualTo(1); + assertThat(result.isNull(0)).isTrue(); } @Test diff --git a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestTDigestAggregationFunction.java b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestTDigestAggregationFunction.java index 031972d832d5..d9b7200bc7b7 100644 --- a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestTDigestAggregationFunction.java +++ b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestTDigestAggregationFunction.java @@ -37,7 +37,7 @@ import static java.lang.Math.abs; import static java.util.Collections.nCopies; import static java.util.Objects.requireNonNull; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestTDigestAggregationFunction { @@ -140,7 +140,9 @@ private void testAggregation(BiFunction equalAssertion, private Object getExpectedValue(List weights, double... values) { - assertEquals(weights.size(), values.length, "mismatched weights and values"); + assertThat(weights.size()) + .describedAs("mismatched weights and values") + .isEqualTo(values.length); if (values.length == 0) { return null; } diff --git a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestTypedHistogram.java b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestTypedHistogram.java index dcb81d328e2f..75f441e42c5e 100644 --- a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestTypedHistogram.java +++ b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestTypedHistogram.java @@ -38,7 +38,6 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.util.StructuralTestUtil.mapType; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestTypedHistogram { @@ -92,7 +91,7 @@ private static void testMassive(boolean grouped, Type type, ObjIntConsumer createEqualAssertion(Object expectedValue, long groupId) { - BiConsumer equalAssertion = (actual, expected) -> assertEquals(actual, expected, format("failure on group %s", groupId)); + BiConsumer equalAssertion = (actual, expected) -> assertThat(actual) + .describedAs(format("failure on group %s", groupId)) + .isEqualTo(expected); if (expectedValue instanceof Double && !expectedValue.equals(Double.NaN)) { - equalAssertion = (actual, expected) -> assertEquals((double) actual, (double) expected, 1e-10); + equalAssertion = (actual, expected) -> assertThat((double) actual).isCloseTo((double) expected, offset(1e-10)); } if (expectedValue instanceof Float && !expectedValue.equals(Float.NaN)) { - equalAssertion = (actual, expected) -> assertEquals((float) actual, (float) expected, 1e-10f); + equalAssertion = (actual, expected) -> assertThat((float) actual).isCloseTo((float) expected, offset(1e-10f)); } return equalAssertion; } diff --git a/core/trino-main/src/test/java/io/trino/operator/aggregation/groupby/GroupByAggregationTestUtils.java b/core/trino-main/src/test/java/io/trino/operator/aggregation/groupby/GroupByAggregationTestUtils.java index 1a4a9503d67c..8c09c7e78c68 100644 --- a/core/trino-main/src/test/java/io/trino/operator/aggregation/groupby/GroupByAggregationTestUtils.java +++ b/core/trino-main/src/test/java/io/trino/operator/aggregation/groupby/GroupByAggregationTestUtils.java @@ -17,7 +17,7 @@ import io.trino.spi.Page; import io.trino.spi.block.Block; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public final class GroupByAggregationTestUtils { @@ -30,7 +30,9 @@ public static Page[] createPages(Block[] blocks) { int positions = blocks[0].getPositionCount(); for (int i = 1; i < blocks.length; i++) { - assertEquals(positions, blocks[i].getPositionCount(), "input blocks provided are not equal in position count"); + assertThat(positions) + .describedAs("input blocks provided are not equal in position count") + .isEqualTo(blocks[i].getPositionCount()); } if (positions == 0) { return new Page[] {}; diff --git a/core/trino-main/src/test/java/io/trino/operator/aggregation/listagg/TestListaggAggregationFunction.java b/core/trino-main/src/test/java/io/trino/operator/aggregation/listagg/TestListaggAggregationFunction.java index 5cc028271641..a6e1fc29dcd8 100644 --- a/core/trino-main/src/test/java/io/trino/operator/aggregation/listagg/TestListaggAggregationFunction.java +++ b/core/trino-main/src/test/java/io/trino/operator/aggregation/listagg/TestListaggAggregationFunction.java @@ -33,8 +33,8 @@ import static io.trino.spi.type.BooleanType.BOOLEAN; import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.sql.analyzer.TypeSignatureProvider.fromTypes; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestListaggAggregationFunction { @@ -61,7 +61,7 @@ public void testInputEmptyState() VariableWidthBlockBuilder blockBuilder = VARCHAR.createBlockBuilder(null, 1); state.write(blockBuilder); String result = VARCHAR.getSlice(blockBuilder.build(), 0).toString(StandardCharsets.UTF_8); - assertEquals(result, s); + assertThat(result).isEqualTo(s); } @Test @@ -88,7 +88,7 @@ public void testOutputStateSingleValue() { SingleListaggAggregationState state = createListaggAggregationState(",", true, "...", false, "value1"); - assertEquals(getOutputStateOnlyValue(state, 1024), "value1"); + assertThat(getOutputStateOnlyValue(state, 1024)).isEqualTo("value1"); } @Test @@ -108,7 +108,7 @@ public void testOutputStateWithEmptyValues() { SingleListaggAggregationState state = createListaggAggregationState(",", true, "...", false, "trino", "", "", "", ""); - assertEquals(getOutputStateOnlyValue(state, 12), "trino,,,,"); + assertThat(getOutputStateOnlyValue(state, 12)).isEqualTo("trino,,,,"); } @Test @@ -116,7 +116,7 @@ public void testOutputStateWithEmptyDelimiter() { SingleListaggAggregationState state = createListaggAggregationState("", true, "...", false, "value1", "value2"); - assertEquals(getOutputStateOnlyValue(state, 12), "value1value2"); + assertThat(getOutputStateOnlyValue(state, 12)).isEqualTo("value1value2"); } @Test @@ -124,7 +124,7 @@ public void testOutputStateWithSeparatorSpecialUnicodeCharacter() { SingleListaggAggregationState state = createListaggAggregationState("♥", true, "...", false, "Trino", "SQL", "on", "everything"); - assertEquals(getOutputStateOnlyValue(state, 29), "Trino♥SQL♥on♥everything"); + assertThat(getOutputStateOnlyValue(state, 29)).isEqualTo("Trino♥SQL♥on♥everything"); } @Test @@ -133,7 +133,7 @@ public void testOutputTruncatedStateFirstValueTooBigWithoutIndicationCount() SingleListaggAggregationState state = createListaggAggregationState(",", false, "...", false, "value1", "value2"); - assertEquals(getOutputStateOnlyValue(state, 5), "..."); + assertThat(getOutputStateOnlyValue(state, 5)).isEqualTo("..."); } @Test @@ -141,11 +141,11 @@ public void testOutputTruncatedStateLastDelimiterOmitted() { SingleListaggAggregationState state = createListaggAggregationState("###", false, "...", false, "value1", "value2", "value3"); - assertEquals(getOutputStateOnlyValue(state, 18), "value1###value2###..."); - assertEquals(getOutputStateOnlyValue(state, 19), "value1###value2###..."); - assertEquals(getOutputStateOnlyValue(state, 20), "value1###value2###..."); - assertEquals(getOutputStateOnlyValue(state, 21), "value1###value2###..."); - assertEquals(getOutputStateOnlyValue(state, 22), "value1###value2###..."); + assertThat(getOutputStateOnlyValue(state, 18)).isEqualTo("value1###value2###..."); + assertThat(getOutputStateOnlyValue(state, 19)).isEqualTo("value1###value2###..."); + assertThat(getOutputStateOnlyValue(state, 20)).isEqualTo("value1###value2###..."); + assertThat(getOutputStateOnlyValue(state, 21)).isEqualTo("value1###value2###..."); + assertThat(getOutputStateOnlyValue(state, 22)).isEqualTo("value1###value2###..."); } @Test @@ -153,11 +153,11 @@ public void testOutputTruncatedStateWithoutIndicationCount() { SingleListaggAggregationState state = createListaggAggregationState(",", false, "...", false, "value1", "value2"); - assertEquals(getOutputStateOnlyValue(state, 9), "value1,..."); - assertEquals(getOutputStateOnlyValue(state, 10), "value1,..."); - assertEquals(getOutputStateOnlyValue(state, 11), "value1,..."); - assertEquals(getOutputStateOnlyValue(state, 12), "value1,..."); - assertEquals(getOutputStateOnlyValue(state, 13), "value1,value2"); + assertThat(getOutputStateOnlyValue(state, 9)).isEqualTo("value1,..."); + assertThat(getOutputStateOnlyValue(state, 10)).isEqualTo("value1,..."); + assertThat(getOutputStateOnlyValue(state, 11)).isEqualTo("value1,..."); + assertThat(getOutputStateOnlyValue(state, 12)).isEqualTo("value1,..."); + assertThat(getOutputStateOnlyValue(state, 13)).isEqualTo("value1,value2"); } @Test @@ -165,9 +165,9 @@ public void testOutputTruncatedStateWithIndicationCount() { SingleListaggAggregationState state = createListaggAggregationState(",", false, "...", true, "string1", "string2"); - assertEquals(getOutputStateOnlyValue(state, 12), "string1,...(1)"); - assertEquals(getOutputStateOnlyValue(state, 13), "string1,...(1)"); - assertEquals(getOutputStateOnlyValue(state, 14), "string1,...(1)"); + assertThat(getOutputStateOnlyValue(state, 12)).isEqualTo("string1,...(1)"); + assertThat(getOutputStateOnlyValue(state, 13)).isEqualTo("string1,...(1)"); + assertThat(getOutputStateOnlyValue(state, 14)).isEqualTo("string1,...(1)"); } @Test @@ -175,15 +175,15 @@ public void testOutputTruncatedStateWithIndicationCountAlphabet() { SingleListaggAggregationState state = createListaggAggregationState(",", false, "...", true, "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "x", "y", "z"); - assertEquals(getOutputStateOnlyValue(state, 13), "a,b,c,d,e,f,g,...(18)"); - assertEquals(getOutputStateOnlyValue(state, 14), "a,b,c,d,e,f,g,...(18)"); - assertEquals(getOutputStateOnlyValue(state, 15), "a,b,c,d,e,f,g,h,...(17)"); - assertEquals(getOutputStateOnlyValue(state, 16), "a,b,c,d,e,f,g,h,...(17)"); - assertEquals(getOutputStateOnlyValue(state, 17), "a,b,c,d,e,f,g,h,i,...(16)"); - assertEquals(getOutputStateOnlyValue(state, 18), "a,b,c,d,e,f,g,h,i,...(16)"); - assertEquals(getOutputStateOnlyValue(state, 19), "a,b,c,d,e,f,g,h,i,j,...(15)"); - assertEquals(getOutputStateOnlyValue(state, 20), "a,b,c,d,e,f,g,h,i,j,...(15)"); - assertEquals(getOutputStateOnlyValue(state, 21), "a,b,c,d,e,f,g,h,i,j,k,...(14)"); + assertThat(getOutputStateOnlyValue(state, 13)).isEqualTo("a,b,c,d,e,f,g,...(18)"); + assertThat(getOutputStateOnlyValue(state, 14)).isEqualTo("a,b,c,d,e,f,g,...(18)"); + assertThat(getOutputStateOnlyValue(state, 15)).isEqualTo("a,b,c,d,e,f,g,h,...(17)"); + assertThat(getOutputStateOnlyValue(state, 16)).isEqualTo("a,b,c,d,e,f,g,h,...(17)"); + assertThat(getOutputStateOnlyValue(state, 17)).isEqualTo("a,b,c,d,e,f,g,h,i,...(16)"); + assertThat(getOutputStateOnlyValue(state, 18)).isEqualTo("a,b,c,d,e,f,g,h,i,...(16)"); + assertThat(getOutputStateOnlyValue(state, 19)).isEqualTo("a,b,c,d,e,f,g,h,i,j,...(15)"); + assertThat(getOutputStateOnlyValue(state, 20)).isEqualTo("a,b,c,d,e,f,g,h,i,j,...(15)"); + assertThat(getOutputStateOnlyValue(state, 21)).isEqualTo("a,b,c,d,e,f,g,h,i,j,k,...(14)"); } @Test @@ -192,14 +192,14 @@ public void testOutputTruncatedStateWithIndicationCountComplexSeparator() SingleListaggAggregationState state = createListaggAggregationState("###", false, "...", true, "a", "b", "c", "dd", "e", "f", "g", "h", "i", "j", "k", "l"); - assertEquals(getOutputStateOnlyValue(state, 100), "a###b###c###dd###e###f###g###h###i###j###k###l"); - assertEquals(getOutputStateOnlyValue(state, 15), "a###b###c###dd###...(8)"); - assertEquals(getOutputStateOnlyValue(state, 16), "a###b###c###dd###...(8)"); - assertEquals(getOutputStateOnlyValue(state, 17), "a###b###c###dd###...(8)"); - assertEquals(getOutputStateOnlyValue(state, 18), "a###b###c###dd###e###...(7)"); - assertEquals(getOutputStateOnlyValue(state, 19), "a###b###c###dd###e###...(7)"); - assertEquals(getOutputStateOnlyValue(state, 20), "a###b###c###dd###e###...(7)"); - assertEquals(getOutputStateOnlyValue(state, 21), "a###b###c###dd###e###...(7)"); + assertThat(getOutputStateOnlyValue(state, 100)).isEqualTo("a###b###c###dd###e###f###g###h###i###j###k###l"); + assertThat(getOutputStateOnlyValue(state, 15)).isEqualTo("a###b###c###dd###...(8)"); + assertThat(getOutputStateOnlyValue(state, 16)).isEqualTo("a###b###c###dd###...(8)"); + assertThat(getOutputStateOnlyValue(state, 17)).isEqualTo("a###b###c###dd###...(8)"); + assertThat(getOutputStateOnlyValue(state, 18)).isEqualTo("a###b###c###dd###e###...(7)"); + assertThat(getOutputStateOnlyValue(state, 19)).isEqualTo("a###b###c###dd###e###...(7)"); + assertThat(getOutputStateOnlyValue(state, 20)).isEqualTo("a###b###c###dd###e###...(7)"); + assertThat(getOutputStateOnlyValue(state, 21)).isEqualTo("a###b###c###dd###e###...(7)"); } @Test diff --git a/core/trino-main/src/test/java/io/trino/operator/aggregation/minmaxbyn/TestMinMaxByNAggregation.java b/core/trino-main/src/test/java/io/trino/operator/aggregation/minmaxbyn/TestMinMaxByNAggregation.java index ac7d1deba429..8d97dc3cf7ae 100644 --- a/core/trino-main/src/test/java/io/trino/operator/aggregation/minmaxbyn/TestMinMaxByNAggregation.java +++ b/core/trino-main/src/test/java/io/trino/operator/aggregation/minmaxbyn/TestMinMaxByNAggregation.java @@ -38,7 +38,7 @@ import static io.trino.spi.type.RealType.REAL; import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.sql.analyzer.TypeSignatureProvider.fromTypes; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestMinMaxByNAggregation { @@ -589,7 +589,7 @@ public void testOutOfBound() groupedAggregation(function, new Page(createStringsBlock("z"), createLongsBlock(0), createLongsBlock(10001))); } catch (TrinoException e) { - assertEquals(e.getMessage(), "third argument of max_by must be less than or equal to 10000; found 10001"); + assertThat(e.getMessage()).isEqualTo("third argument of max_by must be less than or equal to 10000; found 10001"); } } } diff --git a/core/trino-main/src/test/java/io/trino/operator/aggregation/state/TestLongDecimalWithOverflowAndLongStateSerializer.java b/core/trino-main/src/test/java/io/trino/operator/aggregation/state/TestLongDecimalWithOverflowAndLongStateSerializer.java index 1422d41d6cdc..257d63587ba7 100644 --- a/core/trino-main/src/test/java/io/trino/operator/aggregation/state/TestLongDecimalWithOverflowAndLongStateSerializer.java +++ b/core/trino-main/src/test/java/io/trino/operator/aggregation/state/TestLongDecimalWithOverflowAndLongStateSerializer.java @@ -19,7 +19,7 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestLongDecimalWithOverflowAndLongStateSerializer { @@ -36,10 +36,10 @@ public void testSerde(long low, long high, long overflow, long count, int expect LongDecimalWithOverflowAndLongState outState = roundTrip(state, expectedLength); - assertEquals(outState.getDecimalArray()[0], high); - assertEquals(outState.getDecimalArray()[1], low); - assertEquals(outState.getOverflow(), overflow); - assertEquals(outState.getLong(), count); + assertThat(outState.getDecimalArray()[0]).isEqualTo(high); + assertThat(outState.getDecimalArray()[1]).isEqualTo(low); + assertThat(outState.getOverflow()).isEqualTo(overflow); + assertThat(outState.getLong()).isEqualTo(count); } @Test @@ -50,7 +50,7 @@ public void testNullSerde() LongDecimalWithOverflowAndLongState outState = roundTrip(state, 0); - assertEquals(outState.getLong(), 0); + assertThat(outState.getLong()).isEqualTo(0); } private LongDecimalWithOverflowAndLongState roundTrip(LongDecimalWithOverflowAndLongState state, int expectedLength) @@ -61,7 +61,7 @@ private LongDecimalWithOverflowAndLongState roundTrip(LongDecimalWithOverflowAnd serializer.serialize(state, out); Block serialized = out.build(); - assertEquals(serialized.getSliceLength(0), expectedLength * Long.BYTES); + assertThat(serialized.getSliceLength(0)).isEqualTo(expectedLength * Long.BYTES); LongDecimalWithOverflowAndLongState outState = STATE_FACTORY.createSingleState(); serializer.deserialize(serialized, 0, outState); return outState; diff --git a/core/trino-main/src/test/java/io/trino/operator/aggregation/state/TestLongDecimalWithOverflowStateSerializer.java b/core/trino-main/src/test/java/io/trino/operator/aggregation/state/TestLongDecimalWithOverflowStateSerializer.java index 5fcd95891a02..57638bd7bfd7 100644 --- a/core/trino-main/src/test/java/io/trino/operator/aggregation/state/TestLongDecimalWithOverflowStateSerializer.java +++ b/core/trino-main/src/test/java/io/trino/operator/aggregation/state/TestLongDecimalWithOverflowStateSerializer.java @@ -19,9 +19,7 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestLongDecimalWithOverflowStateSerializer { @@ -38,10 +36,10 @@ public void testSerde(long low, long high, long overflow, int expectedLength) LongDecimalWithOverflowState outState = roundTrip(state, expectedLength); - assertTrue(outState.isNotNull()); - assertEquals(outState.getDecimalArray()[0], high); - assertEquals(outState.getDecimalArray()[1], low); - assertEquals(outState.getOverflow(), overflow); + assertThat(outState.isNotNull()).isTrue(); + assertThat(outState.getDecimalArray()[0]).isEqualTo(high); + assertThat(outState.getDecimalArray()[1]).isEqualTo(low); + assertThat(outState.getOverflow()).isEqualTo(overflow); } @Test @@ -52,7 +50,7 @@ public void testNullSerde() LongDecimalWithOverflowState outState = roundTrip(state, 0); - assertFalse(outState.isNotNull()); + assertThat(outState.isNotNull()).isFalse(); } private LongDecimalWithOverflowState roundTrip(LongDecimalWithOverflowState state, int expectedLength) @@ -63,7 +61,7 @@ private LongDecimalWithOverflowState roundTrip(LongDecimalWithOverflowState stat serializer.serialize(state, out); Block serialized = out.build(); - assertEquals(serialized.getSliceLength(0), expectedLength * Long.BYTES); + assertThat(serialized.getSliceLength(0)).isEqualTo(expectedLength * Long.BYTES); LongDecimalWithOverflowState outState = STATE_FACTORY.createSingleState(); serializer.deserialize(serialized, 0, outState); return outState; diff --git a/core/trino-main/src/test/java/io/trino/operator/aggregation/state/TestStateCompiler.java b/core/trino-main/src/test/java/io/trino/operator/aggregation/state/TestStateCompiler.java index 58616b35b273..995b0beae15d 100644 --- a/core/trino-main/src/test/java/io/trino/operator/aggregation/state/TestStateCompiler.java +++ b/core/trino-main/src/test/java/io/trino/operator/aggregation/state/TestStateCompiler.java @@ -58,9 +58,7 @@ import static io.trino.util.StructuralTestUtil.mapType; import static io.trino.util.StructuralTestUtil.sqlMapOf; import static io.trino.util.StructuralTestUtil.sqlRowOf; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestStateCompiler { @@ -82,12 +80,12 @@ public void testPrimitiveNullableLongSerialization() Block block = builder.build(); - assertFalse(block.isNull(0)); - assertEquals(BIGINT.getLong(block, 0), state.getValue()); + assertThat(block.isNull(0)).isFalse(); + assertThat(BIGINT.getLong(block, 0)).isEqualTo(state.getValue()); serializer.deserialize(block, 0, deserializedState); - assertEquals(deserializedState.getValue(), state.getValue()); + assertThat(deserializedState.getValue()).isEqualTo(state.getValue()); - assertTrue(block.isNull(1)); + assertThat(block.isNull(1)).isTrue(); } @Test @@ -105,16 +103,16 @@ public void testPrimitiveLongSerialization() Block block = builder.build(); - assertEquals(BIGINT.getLong(block, 0), state.getValue()); + assertThat(BIGINT.getLong(block, 0)).isEqualTo(state.getValue()); serializer.deserialize(block, 0, deserializedState); - assertEquals(deserializedState.getValue(), state.getValue()); + assertThat(deserializedState.getValue()).isEqualTo(state.getValue()); } @Test public void testGetSerializedType() { AccumulatorStateSerializer serializer = StateCompiler.generateStateSerializer(LongState.class); - assertEquals(serializer.getSerializedType(), BIGINT); + assertThat(serializer.getSerializedType()).isEqualTo(BIGINT); } @Test @@ -132,7 +130,7 @@ public void testPrimitiveBooleanSerialization() Block block = builder.build(); serializer.deserialize(block, 0, deserializedState); - assertEquals(deserializedState.isBoolean(), state.isBoolean()); + assertThat(deserializedState.isBoolean()).isEqualTo(state.isBoolean()); } @Test @@ -150,7 +148,7 @@ public void testPrimitiveByteSerialization() Block block = builder.build(); serializer.deserialize(block, 0, deserializedState); - assertEquals(deserializedState.getByte(), state.getByte()); + assertThat(deserializedState.getByte()).isEqualTo(state.getByte()); } @Test @@ -166,14 +164,14 @@ public void testNonPrimitiveSerialization() serializer.serialize(state, nullBlockBuilder); Block nullBlock = nullBlockBuilder.build(); serializer.deserialize(nullBlock, 0, deserializedState); - assertEquals(deserializedState.getSlice(), state.getSlice()); + assertThat(deserializedState.getSlice()).isEqualTo(state.getSlice()); state.setSlice(utf8Slice("test")); BlockBuilder builder = VARCHAR.createBlockBuilder(null, 1); serializer.serialize(state, builder); Block block = builder.build(); serializer.deserialize(block, 0, deserializedState); - assertEquals(deserializedState.getSlice(), state.getSlice()); + assertThat(deserializedState.getSlice()).isEqualTo(state.getSlice()); } @Test @@ -194,9 +192,9 @@ public void testVarianceStateSerialization() Block block = builder.build(); serializer.deserialize(block, 0, deserializedState); - assertEquals(deserializedState.getCount(), singleState.getCount()); - assertEquals(deserializedState.getMean(), singleState.getMean()); - assertEquals(deserializedState.getM2(), singleState.getM2()); + assertThat(deserializedState.getCount()).isEqualTo(singleState.getCount()); + assertThat(deserializedState.getMean()).isEqualTo(singleState.getMean()); + assertThat(deserializedState.getM2()).isEqualTo(singleState.getM2()); } @Test @@ -230,31 +228,26 @@ public void testComplexSerialization() Block block = builder.build(); serializer.deserialize(block, 0, deserializedState); - assertEquals(deserializedState.getBoolean(), singleState.getBoolean()); - assertEquals(deserializedState.getLong(), singleState.getLong()); - assertEquals(deserializedState.getDouble(), singleState.getDouble()); - assertEquals(deserializedState.getByte(), singleState.getByte()); - assertEquals(deserializedState.getInt(), singleState.getInt()); - assertEquals(deserializedState.getSlice(), singleState.getSlice()); - assertEquals(deserializedState.getAnotherSlice(), singleState.getAnotherSlice()); - assertEquals(deserializedState.getYetAnotherSlice(), singleState.getYetAnotherSlice()); - assertEquals(deserializedState.getBlock().getLong(0, 0), singleState.getBlock().getLong(0, 0)); + assertThat(deserializedState.getBoolean()).isEqualTo(singleState.getBoolean()); + assertThat(deserializedState.getLong()).isEqualTo(singleState.getLong()); + assertThat(deserializedState.getDouble()).isEqualTo(singleState.getDouble()); + assertThat(deserializedState.getByte()).isEqualTo(singleState.getByte()); + assertThat(deserializedState.getInt()).isEqualTo(singleState.getInt()); + assertThat(deserializedState.getSlice()).isEqualTo(singleState.getSlice()); + assertThat(deserializedState.getAnotherSlice()).isEqualTo(singleState.getAnotherSlice()); + assertThat(deserializedState.getYetAnotherSlice()).isEqualTo(singleState.getYetAnotherSlice()); + assertThat(deserializedState.getBlock().getLong(0, 0)).isEqualTo(singleState.getBlock().getLong(0, 0)); SqlMap deserializedMap = deserializedState.getSqlMap(); SqlMap expectedMap = singleState.getSqlMap(); - assertEquals(deserializedMap.getRawKeyBlock().getLong(deserializedMap.getRawOffset(), 0), - expectedMap.getRawKeyBlock().getLong(expectedMap.getRawOffset(), 0)); - assertEquals(deserializedMap.getRawValueBlock().getSlice(deserializedMap.getRawOffset(), 0, 9), - expectedMap.getRawValueBlock().getSlice(expectedMap.getRawOffset(), 0, 9)); + assertThat(deserializedMap.getRawKeyBlock().getLong(deserializedMap.getRawOffset(), 0)).isEqualTo(expectedMap.getRawKeyBlock().getLong(expectedMap.getRawOffset(), 0)); + assertThat(deserializedMap.getRawValueBlock().getSlice(deserializedMap.getRawOffset(), 0, 9)).isEqualTo(expectedMap.getRawValueBlock().getSlice(expectedMap.getRawOffset(), 0, 9)); SqlRow sqlRow = deserializedState.getSqlRow(); SqlRow expectedSqlRow = singleState.getSqlRow(); - assertEquals(VARCHAR.getSlice(sqlRow.getRawFieldBlock(0), sqlRow.getRawIndex()), - VARCHAR.getSlice(expectedSqlRow.getRawFieldBlock(0), expectedSqlRow.getRawIndex())); - assertEquals(BIGINT.getLong(sqlRow.getRawFieldBlock(1), sqlRow.getRawIndex()), - BIGINT.getLong(expectedSqlRow.getRawFieldBlock(1), expectedSqlRow.getRawIndex())); - assertEquals(VARCHAR.getSlice(sqlRow.getRawFieldBlock(2), sqlRow.getRawIndex()), - VARCHAR.getSlice(expectedSqlRow.getRawFieldBlock(2), expectedSqlRow.getRawIndex())); + assertThat(VARCHAR.getSlice(sqlRow.getRawFieldBlock(0), sqlRow.getRawIndex())).isEqualTo(VARCHAR.getSlice(expectedSqlRow.getRawFieldBlock(0), expectedSqlRow.getRawIndex())); + assertThat(BIGINT.getLong(sqlRow.getRawFieldBlock(1), sqlRow.getRawIndex())).isEqualTo(BIGINT.getLong(expectedSqlRow.getRawFieldBlock(1), expectedSqlRow.getRawIndex())); + assertThat(VARCHAR.getSlice(sqlRow.getRawFieldBlock(2), sqlRow.getRawIndex())).isEqualTo(VARCHAR.getSlice(expectedSqlRow.getRawFieldBlock(2), expectedSqlRow.getRawIndex())); } private static long getComplexStateRetainedSize(TestComplexState state) @@ -315,7 +308,7 @@ public void testComplexStateEstimatedSize() TestComplexState groupedState = factory.createGroupedState(); long initialRetainedSize = getComplexStateRetainedSize(groupedState); - assertEquals(groupedState.getEstimatedSize(), initialRetainedSize); + assertThat(groupedState.getEstimatedSize()).isEqualTo(initialRetainedSize); // BlockBigArray or SliceBigArray has an internal map that can grow in size when getting more blocks // need to handle the map overhead separately initialRetainedSize -= getReferenceCountMapOverhead(groupedState); @@ -346,7 +339,7 @@ public void testComplexStateEstimatedSize() SqlRow sqlRow = sqlRowOf(RowType.anonymousRow(VARCHAR, BIGINT, VARCHAR), "a", 777, "b"); retainedSize += sqlRow.getRetainedSizeInBytes(); groupedState.setSqlRow(sqlRow); - assertEquals(groupedState.getEstimatedSize(), initialRetainedSize + retainedSize * (i + 1) + getReferenceCountMapOverhead(groupedState)); + assertThat(groupedState.getEstimatedSize()).isEqualTo(initialRetainedSize + retainedSize * (i + 1) + getReferenceCountMapOverhead(groupedState)); } for (int i = 0; i < 1000; i++) { @@ -376,7 +369,7 @@ public void testComplexStateEstimatedSize() SqlRow sqlRow = sqlRowOf(RowType.anonymousRow(VARCHAR, BIGINT, VARCHAR), "a", 777, "b"); retainedSize += sqlRow.getRetainedSizeInBytes(); groupedState.setSqlRow(sqlRow); - assertEquals(groupedState.getEstimatedSize(), initialRetainedSize + retainedSize * 1000 + getReferenceCountMapOverhead(groupedState)); + assertThat(groupedState.getEstimatedSize()).isEqualTo(initialRetainedSize + retainedSize * 1000 + getReferenceCountMapOverhead(groupedState)); } } diff --git a/core/trino-main/src/test/java/io/trino/operator/exchange/TestLocalExchange.java b/core/trino-main/src/test/java/io/trino/operator/exchange/TestLocalExchange.java index c1bbbcad7711..92deb657a8fe 100644 --- a/core/trino-main/src/test/java/io/trino/operator/exchange/TestLocalExchange.java +++ b/core/trino-main/src/test/java/io/trino/operator/exchange/TestLocalExchange.java @@ -72,11 +72,7 @@ import static io.trino.testing.TestingHandles.TEST_CATALOG_HANDLE; import static io.trino.testing.TestingSession.testSessionBuilder; import static java.util.stream.IntStream.range; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; @Test(singleThreaded = true) public class TestLocalExchange @@ -127,7 +123,7 @@ public void testGatherSingleWriter() TOTAL_MEMORY_USED); run(localExchange, exchange -> { - assertEquals(exchange.getBufferCount(), 1); + assertThat(exchange.getBufferCount()).isEqualTo(1); assertExchangeTotalBufferedBytes(exchange, 0); LocalExchangeSinkFactory sinkFactory = exchange.createSinkFactory(); @@ -144,9 +140,9 @@ public void testGatherSingleWriter() // add the first page which should cause the reader to unblock ListenableFuture readFuture = source.waitForReading(); - assertFalse(readFuture.isDone()); + assertThat(readFuture.isDone()).isFalse(); sink.addPage(createPage(0)); - assertTrue(readFuture.isDone()); + assertThat(readFuture.isDone()).isTrue(); assertExchangeTotalBufferedBytes(exchange, 1); assertSource(source, 1); @@ -201,7 +197,7 @@ public void testRandom() TOTAL_MEMORY_USED); run(localExchange, exchange -> { - assertEquals(exchange.getBufferCount(), 2); + assertThat(exchange.getBufferCount()).isEqualTo(2); assertExchangeTotalBufferedBytes(exchange, 0); LocalExchangeSinkFactory sinkFactory = exchange.createSinkFactory(); @@ -223,13 +219,13 @@ public void testRandom() LocalExchangeBufferInfo bufferInfoA = sourceA.getBufferInfo(); LocalExchangeBufferInfo bufferInfoB = sourceB.getBufferInfo(); - assertEquals(bufferInfoA.getBufferedBytes() + bufferInfoB.getBufferedBytes(), retainedSizeOfPages(i + 1)); - assertEquals(bufferInfoA.getBufferedPages() + bufferInfoB.getBufferedPages(), i + 1); + assertThat(bufferInfoA.getBufferedBytes() + bufferInfoB.getBufferedBytes()).isEqualTo(retainedSizeOfPages(i + 1)); + assertThat(bufferInfoA.getBufferedPages() + bufferInfoB.getBufferedPages()).isEqualTo(i + 1); } // we should get ~50 pages per source, but we should get at least some pages in each buffer - assertTrue(sourceA.getBufferInfo().getBufferedPages() > 0); - assertTrue(sourceB.getBufferInfo().getBufferedPages() > 0); + assertThat(sourceA.getBufferInfo().getBufferedPages() > 0).isTrue(); + assertThat(sourceB.getBufferInfo().getBufferedPages() > 0).isTrue(); assertExchangeTotalBufferedBytes(exchange, 100); }); } @@ -251,7 +247,7 @@ public void testScaleWriter() TOTAL_MEMORY_USED); run(localExchange, exchange -> { - assertEquals(exchange.getBufferCount(), 3); + assertThat(exchange.getBufferCount()).isEqualTo(3); assertExchangeTotalBufferedBytes(exchange, 0); LocalExchangeSinkFactory sinkFactory = exchange.createSinkFactory(); @@ -271,15 +267,15 @@ public void testScaleWriter() sink.addPage(createPage(0)); sink.addPage(createPage(0)); - assertEquals(sourceA.getBufferInfo().getBufferedPages(), 2); - assertEquals(sourceB.getBufferInfo().getBufferedPages(), 0); - assertEquals(sourceC.getBufferInfo().getBufferedPages(), 0); + assertThat(sourceA.getBufferInfo().getBufferedPages()).isEqualTo(2); + assertThat(sourceB.getBufferInfo().getBufferedPages()).isEqualTo(0); + assertThat(sourceC.getBufferInfo().getBufferedPages()).isEqualTo(0); // writer min output size and buffered data size limits are exceeded, so we should see pages in sourceB sink.addPage(createPage(0)); - assertEquals(sourceA.getBufferInfo().getBufferedPages(), 2); - assertEquals(sourceB.getBufferInfo().getBufferedPages(), 1); - assertEquals(sourceC.getBufferInfo().getBufferedPages(), 0); + assertThat(sourceA.getBufferInfo().getBufferedPages()).isEqualTo(2); + assertThat(sourceB.getBufferInfo().getBufferedPages()).isEqualTo(1); + assertThat(sourceC.getBufferInfo().getBufferedPages()).isEqualTo(0); assertRemovePage(sourceA, createPage(0)); assertRemovePage(sourceA, createPage(0)); @@ -288,9 +284,9 @@ public void testScaleWriter() sink.addPage(createPage(0)); sink.addPage(createPage(0)); sink.addPage(createPage(0)); - assertEquals(sourceA.getBufferInfo().getBufferedPages(), 1); - assertEquals(sourceB.getBufferInfo().getBufferedPages(), 2); - assertEquals(sourceC.getBufferInfo().getBufferedPages(), 1); + assertThat(sourceA.getBufferInfo().getBufferedPages()).isEqualTo(1); + assertThat(sourceB.getBufferInfo().getBufferedPages()).isEqualTo(2); + assertThat(sourceC.getBufferInfo().getBufferedPages()).isEqualTo(1); }); } @@ -311,7 +307,7 @@ public void testNoWriterScalingWhenOnlyBufferSizeLimitIsExceeded() TOTAL_MEMORY_USED); run(localExchange, exchange -> { - assertEquals(exchange.getBufferCount(), 3); + assertThat(exchange.getBufferCount()).isEqualTo(3); assertExchangeTotalBufferedBytes(exchange, 0); LocalExchangeSinkFactory sinkFactory = exchange.createSinkFactory(); @@ -330,9 +326,9 @@ public void testNoWriterScalingWhenOnlyBufferSizeLimitIsExceeded() assertSource(sourceC, 0); range(0, 6).forEach(i -> sink.addPage(createPage(0))); - assertEquals(sourceA.getBufferInfo().getBufferedPages(), 6); - assertEquals(sourceB.getBufferInfo().getBufferedPages(), 0); - assertEquals(sourceC.getBufferInfo().getBufferedPages(), 0); + assertThat(sourceA.getBufferInfo().getBufferedPages()).isEqualTo(6); + assertThat(sourceB.getBufferInfo().getBufferedPages()).isEqualTo(0); + assertThat(sourceC.getBufferInfo().getBufferedPages()).isEqualTo(0); }); } @@ -356,7 +352,7 @@ public void testScaledWriterRoundRobinExchangerWhenTotalMemoryUsedIsGreaterThanL totalMemoryUsed::get); run(localExchange, exchange -> { - assertEquals(exchange.getBufferCount(), 3); + assertThat(exchange.getBufferCount()).isEqualTo(3); assertExchangeTotalBufferedBytes(exchange, 0); LocalExchangeSinkFactory sinkFactory = exchange.createSinkFactory(); @@ -377,9 +373,9 @@ public void testScaledWriterRoundRobinExchangerWhenTotalMemoryUsedIsGreaterThanL totalMemoryUsed.set(DataSize.of(11, MEGABYTE).toBytes()); range(0, 6).forEach(i -> sink.addPage(createPage(0))); - assertEquals(sourceA.getBufferInfo().getBufferedPages(), 6); - assertEquals(sourceB.getBufferInfo().getBufferedPages(), 0); - assertEquals(sourceC.getBufferInfo().getBufferedPages(), 0); + assertThat(sourceA.getBufferInfo().getBufferedPages()).isEqualTo(6); + assertThat(sourceB.getBufferInfo().getBufferedPages()).isEqualTo(0); + assertThat(sourceC.getBufferInfo().getBufferedPages()).isEqualTo(0); }); } @@ -400,7 +396,7 @@ public void testNoWriterScalingWhenOnlyWriterScalingMinDataProcessedLimitIsExcee TOTAL_MEMORY_USED); run(localExchange, exchange -> { - assertEquals(exchange.getBufferCount(), 3); + assertThat(exchange.getBufferCount()).isEqualTo(3); assertExchangeTotalBufferedBytes(exchange, 0); LocalExchangeSinkFactory sinkFactory = exchange.createSinkFactory(); @@ -422,9 +418,9 @@ public void testNoWriterScalingWhenOnlyWriterScalingMinDataProcessedLimitIsExcee range(0, 8).forEach(i -> sink.addPage(createPage(0))); physicalWrittenBytesA.set(retainedSizeOfPages(8)); sink.addPage(createPage(0)); - assertEquals(sourceA.getBufferInfo().getBufferedPages(), 9); - assertEquals(sourceB.getBufferInfo().getBufferedPages(), 0); - assertEquals(sourceC.getBufferInfo().getBufferedPages(), 0); + assertThat(sourceA.getBufferInfo().getBufferedPages()).isEqualTo(9); + assertThat(sourceB.getBufferInfo().getBufferedPages()).isEqualTo(0); + assertThat(sourceC.getBufferInfo().getBufferedPages()).isEqualTo(0); }); } @@ -447,7 +443,7 @@ public void testScalingForSkewedWriters(PartitioningHandle partitioningHandle) TOTAL_MEMORY_USED); run(localExchange, exchange -> { - assertEquals(exchange.getBufferCount(), 4); + assertThat(exchange.getBufferCount()).isEqualTo(4); assertExchangeTotalBufferedBytes(exchange, 0); LocalExchangeSinkFactory sinkFactory = exchange.createSinkFactory(); @@ -537,7 +533,7 @@ public void testNoScalingWhenDataWrittenIsLessThanMinFileSize(PartitioningHandle TOTAL_MEMORY_USED); run(localExchange, exchange -> { - assertEquals(exchange.getBufferCount(), 4); + assertThat(exchange.getBufferCount()).isEqualTo(4); assertExchangeTotalBufferedBytes(exchange, 0); LocalExchangeSinkFactory sinkFactory = exchange.createSinkFactory(); @@ -601,7 +597,7 @@ public void testNoScalingWhenBufferUtilizationIsLessThanLimit(PartitioningHandle TOTAL_MEMORY_USED); run(localExchange, exchange -> { - assertEquals(exchange.getBufferCount(), 4); + assertThat(exchange.getBufferCount()).isEqualTo(4); assertExchangeTotalBufferedBytes(exchange, 0); LocalExchangeSinkFactory sinkFactory = exchange.createSinkFactory(); @@ -667,7 +663,7 @@ public void testNoScalingWhenTotalMemoryUsedIsGreaterThanLimit(PartitioningHandl totalMemoryUsed::get); run(localExchange, exchange -> { - assertEquals(exchange.getBufferCount(), 4); + assertThat(exchange.getBufferCount()).isEqualTo(4); assertExchangeTotalBufferedBytes(exchange, 0); LocalExchangeSinkFactory sinkFactory = exchange.createSinkFactory(); @@ -747,7 +743,7 @@ public void testNoScalingWhenMaxScaledPartitionsPerTaskIsSmall(PartitioningHandl TOTAL_MEMORY_USED); run(localExchange, exchange -> { - assertEquals(exchange.getBufferCount(), 4); + assertThat(exchange.getBufferCount()).isEqualTo(4); assertExchangeTotalBufferedBytes(exchange, 0); LocalExchangeSinkFactory sinkFactory = exchange.createSinkFactory(); @@ -855,7 +851,7 @@ public void testNoScalingWhenNoWriterSkewness() TOTAL_MEMORY_USED); run(localExchange, exchange -> { - assertEquals(exchange.getBufferCount(), 2); + assertThat(exchange.getBufferCount()).isEqualTo(2); assertExchangeTotalBufferedBytes(exchange, 0); LocalExchangeSinkFactory sinkFactory = exchange.createSinkFactory(); @@ -903,7 +899,7 @@ public void testPassthrough() TOTAL_MEMORY_USED); run(localExchange, exchange -> { - assertEquals(exchange.getBufferCount(), 2); + assertThat(exchange.getBufferCount()).isEqualTo(2); assertExchangeTotalBufferedBytes(exchange, 0); LocalExchangeSinkFactory sinkFactory = exchange.createSinkFactory(); @@ -971,7 +967,7 @@ public void testPartition() TOTAL_MEMORY_USED); run(localExchange, exchange -> { - assertEquals(exchange.getBufferCount(), 2); + assertThat(exchange.getBufferCount()).isEqualTo(2); assertExchangeTotalBufferedBytes(exchange, 0); LocalExchangeSinkFactory sinkFactory = exchange.createSinkFactory(); @@ -990,13 +986,13 @@ public void testPartition() assertSource(sourceA, 1); assertSource(sourceB, 1); - assertTrue(sourceA.getBufferInfo().getBufferedBytes() + sourceB.getBufferInfo().getBufferedBytes() >= retainedSizeOfPages(1)); + assertThat(sourceA.getBufferInfo().getBufferedBytes() + sourceB.getBufferInfo().getBufferedBytes() >= retainedSizeOfPages(1)).isTrue(); sink.addPage(createPage(0)); assertSource(sourceA, 2); assertSource(sourceB, 2); - assertTrue(sourceA.getBufferInfo().getBufferedBytes() + sourceB.getBufferInfo().getBufferedBytes() >= retainedSizeOfPages(2)); + assertThat(sourceA.getBufferInfo().getBufferedBytes() + sourceB.getBufferInfo().getBufferedBytes() >= retainedSizeOfPages(2)).isTrue(); assertPartitionedRemovePage(sourceA, 0, 2); assertSource(sourceA, 1); @@ -1068,7 +1064,7 @@ public BucketFunction getBucketFunction(ConnectorTransactionHandle transactionHa TOTAL_MEMORY_USED); run(localExchange, exchange -> { - assertEquals(exchange.getBufferCount(), 2); + assertThat(exchange.getBufferCount()).isEqualTo(2); assertExchangeTotalBufferedBytes(exchange, 0); LocalExchangeSinkFactory sinkFactory = exchange.createSinkFactory(); @@ -1120,7 +1116,7 @@ public void writeUnblockWhenAllReadersFinish() TOTAL_MEMORY_USED); run(localExchange, exchange -> { - assertEquals(exchange.getBufferCount(), 2); + assertThat(exchange.getBufferCount()).isEqualTo(2); assertExchangeTotalBufferedBytes(exchange, 0); LocalExchangeSinkFactory sinkFactory = exchange.createSinkFactory(); @@ -1168,7 +1164,7 @@ public void writeUnblockWhenAllReadersFinishAndPagesConsumed() TOTAL_MEMORY_USED); run(localExchange, exchange -> { - assertEquals(exchange.getBufferCount(), 2); + assertThat(exchange.getBufferCount()).isEqualTo(2); assertExchangeTotalBufferedBytes(exchange, 0); LocalExchangeSinkFactory sinkFactory = exchange.createSinkFactory(); @@ -1176,12 +1172,12 @@ public void writeUnblockWhenAllReadersFinishAndPagesConsumed() LocalExchangeSink sinkA = sinkFactory.createSink(); assertSinkCanWrite(sinkA); ListenableFuture sinkAFinished = sinkA.isFinished(); - assertFalse(sinkAFinished.isDone()); + assertThat(sinkAFinished.isDone()).isFalse(); LocalExchangeSink sinkB = sinkFactory.createSink(); assertSinkCanWrite(sinkB); ListenableFuture sinkBFinished = sinkB.isFinished(); - assertFalse(sinkBFinished.isDone()); + assertThat(sinkBFinished.isDone()).isFalse(); sinkFactory.close(); @@ -1215,10 +1211,10 @@ public void writeUnblockWhenAllReadersFinishAndPagesConsumed() assertSourceFinished(sourceB); assertExchangeTotalBufferedBytes(exchange, 0); - assertTrue(sinkAFuture.isDone()); - assertTrue(sinkBFuture.isDone()); - assertTrue(sinkAFinished.isDone()); - assertTrue(sinkBFinished.isDone()); + assertThat(sinkAFuture.isDone()).isTrue(); + assertThat(sinkBFuture.isDone()).isTrue(); + assertThat(sinkAFinished.isDone()).isTrue(); + assertThat(sinkBFinished.isDone()).isTrue(); assertSinkFinished(sinkA); assertSinkFinished(sinkB); @@ -1272,33 +1268,33 @@ private void run(LocalExchange localExchange, Consumer test) private static void assertSource(LocalExchangeSource source, int pageCount) { LocalExchangeBufferInfo bufferInfo = source.getBufferInfo(); - assertEquals(bufferInfo.getBufferedPages(), pageCount); - assertFalse(source.isFinished()); + assertThat(bufferInfo.getBufferedPages()).isEqualTo(pageCount); + assertThat(source.isFinished()).isFalse(); if (pageCount == 0) { - assertFalse(source.waitForReading().isDone()); - assertNull(source.removePage()); - assertFalse(source.waitForReading().isDone()); - assertFalse(source.isFinished()); - assertEquals(bufferInfo.getBufferedBytes(), 0); + assertThat(source.waitForReading().isDone()).isFalse(); + assertThat(source.removePage()).isNull(); + assertThat(source.waitForReading().isDone()).isFalse(); + assertThat(source.isFinished()).isFalse(); + assertThat(bufferInfo.getBufferedBytes()).isEqualTo(0); } else { - assertTrue(source.waitForReading().isDone()); - assertTrue(bufferInfo.getBufferedBytes() > 0); + assertThat(source.waitForReading().isDone()).isTrue(); + assertThat(bufferInfo.getBufferedBytes() > 0).isTrue(); } } private static void assertSourceFinished(LocalExchangeSource source) { - assertTrue(source.isFinished()); + assertThat(source.isFinished()).isTrue(); LocalExchangeBufferInfo bufferInfo = source.getBufferInfo(); - assertEquals(bufferInfo.getBufferedPages(), 0); - assertEquals(bufferInfo.getBufferedBytes(), 0); + assertThat(bufferInfo.getBufferedPages()).isEqualTo(0); + assertThat(bufferInfo.getBufferedBytes()).isEqualTo(0); - assertTrue(source.waitForReading().isDone()); - assertNull(source.removePage()); - assertTrue(source.waitForReading().isDone()); + assertThat(source.waitForReading().isDone()).isTrue(); + assertThat(source.removePage()).isNull(); + assertThat(source.waitForReading().isDone()).isTrue(); - assertTrue(source.isFinished()); + assertThat(source.isFinished()).isTrue(); } private static void assertRemovePage(LocalExchangeSource source, Page expectedPage) @@ -1308,49 +1304,49 @@ private static void assertRemovePage(LocalExchangeSource source, Page expectedPa private static void assertRemovePage(List types, LocalExchangeSource source, Page expectedPage) { - assertTrue(source.waitForReading().isDone()); + assertThat(source.waitForReading().isDone()).isTrue(); Page actualPage = source.removePage(); - assertNotNull(actualPage); + assertThat(actualPage).isNotNull(); - assertEquals(actualPage.getChannelCount(), expectedPage.getChannelCount()); + assertThat(actualPage.getChannelCount()).isEqualTo(expectedPage.getChannelCount()); PageAssertions.assertPageEquals(types, actualPage, expectedPage); } private static void assertPartitionedRemovePage(LocalExchangeSource source, int partition, int partitionCount) { - assertTrue(source.waitForReading().isDone()); + assertThat(source.waitForReading().isDone()).isTrue(); Page page = source.removePage(); - assertNotNull(page); + assertThat(page).isNotNull(); LocalPartitionGenerator partitionGenerator = new LocalPartitionGenerator(createChannelsHashGenerator(TYPES, new int[]{0}, TYPE_OPERATORS), partitionCount); for (int position = 0; position < page.getPositionCount(); position++) { - assertEquals(partitionGenerator.getPartition(page, position), partition); + assertThat(partitionGenerator.getPartition(page, position)).isEqualTo(partition); } } private static void assertSinkCanWrite(LocalExchangeSink sink) { - assertFalse(sink.isFinished().isDone()); - assertTrue(sink.waitForWriting().isDone()); + assertThat(sink.isFinished().isDone()).isFalse(); + assertThat(sink.waitForWriting().isDone()).isTrue(); } private static ListenableFuture assertSinkWriteBlocked(LocalExchangeSink sink) { - assertFalse(sink.isFinished().isDone()); + assertThat(sink.isFinished().isDone()).isFalse(); ListenableFuture writeFuture = sink.waitForWriting(); - assertFalse(writeFuture.isDone()); + assertThat(writeFuture.isDone()).isFalse(); return writeFuture; } private static void assertSinkFinished(LocalExchangeSink sink) { - assertTrue(sink.isFinished().isDone()); - assertTrue(sink.waitForWriting().isDone()); + assertThat(sink.isFinished().isDone()).isTrue(); + assertThat(sink.waitForWriting().isDone()).isTrue(); // this will be ignored sink.addPage(createPage(0)); - assertTrue(sink.isFinished().isDone()); - assertTrue(sink.waitForWriting().isDone()); + assertThat(sink.isFinished().isDone()).isTrue(); + assertThat(sink.waitForWriting().isDone()).isTrue(); } private static void assertExchangeTotalBufferedBytes(LocalExchange exchange, int pageCount) @@ -1359,7 +1355,7 @@ private static void assertExchangeTotalBufferedBytes(LocalExchange exchange, int for (int i = 0; i < exchange.getBufferCount(); i++) { bufferedBytes += exchange.getSource(i).getBufferInfo().getBufferedBytes(); } - assertEquals(bufferedBytes, retainedSizeOfPages(pageCount)); + assertThat(bufferedBytes).isEqualTo(retainedSizeOfPages(pageCount)); } private static Page createPage(int i) diff --git a/core/trino-main/src/test/java/io/trino/operator/index/TestFieldSetFilteringRecordSet.java b/core/trino-main/src/test/java/io/trino/operator/index/TestFieldSetFilteringRecordSet.java index 45f95366f22f..98f0080364a4 100644 --- a/core/trino-main/src/test/java/io/trino/operator/index/TestFieldSetFilteringRecordSet.java +++ b/core/trino-main/src/test/java/io/trino/operator/index/TestFieldSetFilteringRecordSet.java @@ -26,7 +26,7 @@ import static io.trino.spi.type.TimeZoneKey.getTimeZoneKeyForOffset; import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MILLIS; import static io.trino.util.StructuralTestUtil.arrayBlockOf; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestFieldSetFilteringRecordSet { @@ -50,6 +50,6 @@ public void test() arrayBlockOf(BIGINT, 12, 34, 56)))), ImmutableList.of(ImmutableSet.of(0, 1), ImmutableSet.of(2, 3), ImmutableSet.of(4, 5))); RecordCursor recordCursor = fieldSetFilteringRecordSet.cursor(); - assertTrue(recordCursor.advanceNextPosition()); + assertThat(recordCursor.advanceNextPosition()).isTrue(); } } diff --git a/core/trino-main/src/test/java/io/trino/operator/join/TestHashJoinOperator.java b/core/trino-main/src/test/java/io/trino/operator/join/TestHashJoinOperator.java index b36d6ce7c809..b5d13371690e 100644 --- a/core/trino-main/src/test/java/io/trino/operator/join/TestHashJoinOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/join/TestHashJoinOperator.java @@ -115,12 +115,8 @@ import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; @Test(singleThreaded = true) public class TestHashJoinOperator @@ -300,12 +296,12 @@ public void testUnwrapsLazyBlocks() instantiateBuildDrivers(buildSideSetup, taskContext); buildLookupSource(executor, buildSideSetup); Operator operator = joinOperatorFactory.createOperator(driverContext); - assertTrue(operator.needsInput()); + assertThat(operator.needsInput()).isTrue(); operator.addInput(probeInput.get(0)); operator.finish(); Page output = operator.getOutput(); - assertFalse(output.getBlock(1) instanceof LazyBlock); + assertThat(output.getBlock(1) instanceof LazyBlock).isFalse(); } @Test @@ -353,7 +349,7 @@ public void testYield() instantiateBuildDrivers(buildSideSetup, taskContext); buildLookupSource(executor, buildSideSetup); Operator operator = joinOperatorFactory.createOperator(driverContext); - assertTrue(operator.needsInput()); + assertThat(operator.needsInput()).isTrue(); operator.addInput(probeInput.get(0)); operator.finish(); @@ -361,8 +357,10 @@ public void testYield() for (int i = 0; i < entries; i++) { driverContext.getYieldSignal().setWithDelay(5 * SECONDS.toNanos(1), driverContext.getYieldExecutor()); filterFunctionCalls.set(0); - assertNull(operator.getOutput()); - assertEquals(filterFunctionCalls.get(), 1, "Expected join to stop processing (yield) after calling filter function once"); + assertThat(operator.getOutput()).isNull(); + assertThat(filterFunctionCalls.get()) + .describedAs("Expected join to stop processing (yield) after calling filter function once") + .isEqualTo(1); driverContext.getYieldSignal().reset(); } // delayed yield is not going to prevent operator from producing a page now (yield won't be forced because filter function won't be called anymore) @@ -372,11 +370,11 @@ public void testYield() for (int i = 0; output == null && i < 5; i++) { output = operator.getOutput(); } - assertNotNull(output); + assertThat(output).isNotNull(); driverContext.getYieldSignal().reset(); // make sure we have all 4 entries - assertEquals(output.getPositionCount(), entries); + assertThat(output.getPositionCount()).isEqualTo(entries); } private enum WhenSpill @@ -530,7 +528,9 @@ private void innerJoinWithSpill(boolean probeHashEnabled, List whenSp } } getFutureValue(lookupSourceProvider).close(); - assertEquals(revoked, whenSpill.stream().map(WhenSpill.DURING_BUILD::equals).collect(toImmutableList()), "Some operators not spilled before LookupSource built"); + assertThat(revoked) + .describedAs("Some operators not spilled before LookupSource built") + .isEqualTo(whenSpill.stream().map(WhenSpill.DURING_BUILD::equals).collect(toImmutableList())); for (int i = 0; i < buildOperatorCount; i++) { if (whenSpill.get(i) == WhenSpill.AFTER_BUILD) { @@ -676,7 +676,7 @@ public void testBuildGracefulSpill() hashBuilderOperator.isBlocked().get(); lookupSourceFactory.destroy(); - assertTrue(hashBuilderOperator.isFinished()); + assertThat(hashBuilderOperator.isFinished()).isTrue(); } @Test(dataProvider = "hashJoinTestValues") @@ -1243,7 +1243,7 @@ public void testInnerJoinWithEmptyLookupSource(boolean parallelBuild, boolean pr List pages = probePages.row("test").build(); operator.addInput(pages.get(0)); Page outputPage = operator.getOutput(); - assertNull(outputPage); + assertThat(outputPage).isNull(); } @Test(dataProvider = "hashJoinTestValues") @@ -1282,7 +1282,7 @@ public void testLookupOuterJoinWithEmptyLookupSource(boolean parallelBuild, bool List pages = probePages.row("test").build(); operator.addInput(pages.get(0)); Page outputPage = operator.getOutput(); - assertNull(outputPage); + assertThat(outputPage).isNull(); } @Test(dataProvider = "hashJoinTestValues") @@ -1433,13 +1433,13 @@ public void testInnerJoinWithBlockingLookupSourceAndEmptyProbe(boolean parallelB DriverContext driverContext = taskContext.addPipelineContext(0, true, true, false).addDriverContext(); try (Operator joinOperator = joinOperatorFactory.createOperator(driverContext)) { joinOperatorFactory.noMoreOperators(); - assertFalse(joinOperator.needsInput()); + assertThat(joinOperator.needsInput()).isFalse(); joinOperator.finish(); - assertNull(joinOperator.getOutput()); + assertThat(joinOperator.getOutput()).isNull(); // lookup join operator got blocked waiting for build side - assertFalse(joinOperator.isBlocked().isDone()); - assertFalse(joinOperator.isFinished()); + assertThat(joinOperator.isBlocked().isDone()).isFalse(); + assertThat(joinOperator.isFinished()).isFalse(); } // join that doesn't wait for build side to be collected @@ -1448,14 +1448,14 @@ public void testInnerJoinWithBlockingLookupSourceAndEmptyProbe(boolean parallelB driverContext = taskContext.addPipelineContext(0, true, true, false).addDriverContext(); try (Operator joinOperator = joinOperatorFactory.createOperator(driverContext)) { joinOperatorFactory.noMoreOperators(); - assertTrue(joinOperator.needsInput()); + assertThat(joinOperator.needsInput()).isTrue(); joinOperator.finish(); - assertNull(joinOperator.getOutput()); + assertThat(joinOperator.getOutput()).isNull(); // lookup join operator will yield once before finishing - assertNull(joinOperator.getOutput()); - assertTrue(joinOperator.isBlocked().isDone()); - assertTrue(joinOperator.isFinished()); + assertThat(joinOperator.getOutput()).isNull(); + assertThat(joinOperator.isBlocked().isDone()).isTrue(); + assertThat(joinOperator.isFinished()).isTrue(); } } @@ -1472,12 +1472,12 @@ public void testInnerJoinWithBlockingLookupSource(boolean parallelBuild, boolean DriverContext driverContext = taskContext.addPipelineContext(0, true, true, false).addDriverContext(); try (Operator joinOperator = joinOperatorFactory.createOperator(driverContext)) { joinOperatorFactory.noMoreOperators(); - assertFalse(joinOperator.needsInput()); - assertNull(joinOperator.getOutput()); + assertThat(joinOperator.needsInput()).isFalse(); + assertThat(joinOperator.getOutput()).isNull(); // lookup join operator got blocked waiting for build side - assertFalse(joinOperator.isBlocked().isDone()); - assertFalse(joinOperator.isFinished()); + assertThat(joinOperator.isBlocked().isDone()).isFalse(); + assertThat(joinOperator.isFinished()).isFalse(); } // join that doesn't wait for build side to be collected @@ -1486,18 +1486,18 @@ public void testInnerJoinWithBlockingLookupSource(boolean parallelBuild, boolean driverContext = taskContext.addPipelineContext(0, true, true, false).addDriverContext(); try (Operator joinOperator = joinOperatorFactory.createOperator(driverContext)) { joinOperatorFactory.noMoreOperators(); - assertTrue(joinOperator.needsInput()); - assertNull(joinOperator.getOutput()); + assertThat(joinOperator.needsInput()).isTrue(); + assertThat(joinOperator.getOutput()).isNull(); // join needs input page - assertTrue(joinOperator.isBlocked().isDone()); - assertFalse(joinOperator.isFinished()); + assertThat(joinOperator.isBlocked().isDone()).isTrue(); + assertThat(joinOperator.isFinished()).isFalse(); joinOperator.addInput(probePage); - assertNull(joinOperator.getOutput()); + assertThat(joinOperator.getOutput()).isNull(); // lookup join operator got blocked waiting for build side - assertFalse(joinOperator.isBlocked().isDone()); - assertFalse(joinOperator.isFinished()); + assertThat(joinOperator.isBlocked().isDone()).isFalse(); + assertThat(joinOperator.isFinished()).isFalse(); } } @@ -1544,7 +1544,7 @@ public void testInnerJoinLoadsPagesInOrder() 1, () -> { // when loaded this block should be the latest one - assertEquals(probePageNumber, totalProbePages.get()); + assertThat(probePageNumber).isEqualTo(totalProbePages.get()); return probePage.getBlock(2); }))); }); @@ -1569,7 +1569,7 @@ public void testInnerJoinLoadsPagesInOrder() Page page = outputPages.getResult(); totalOutputPages++; - assertFalse(page.getBlock(1).isLoaded()); + assertThat(page.getBlock(1).isLoaded()).isFalse(); page.getBlock(2).getLoadedBlock(); // yield to enforce more complex execution @@ -1577,8 +1577,8 @@ public void testInnerJoinLoadsPagesInOrder() } // make sure that multiple pages were produced for some probe pages - assertTrue(totalOutputPages > totalProbePages.get()); - assertTrue(outputPages.isFinished()); + assertThat(totalOutputPages > totalProbePages.get()).isTrue(); + assertThat(outputPages.isFinished()).isTrue(); } private OperatorFactory createJoinOperatorFactoryWithBlockingLookupSource(TaskContext taskContext, boolean parallelBuild, boolean probeHashEnabled, boolean buildHashEnabled, boolean waitForBuild) diff --git a/core/trino-main/src/test/java/io/trino/operator/join/TestJoinOperatorInfo.java b/core/trino-main/src/test/java/io/trino/operator/join/TestJoinOperatorInfo.java index 2733131a41e9..834235b9f651 100644 --- a/core/trino-main/src/test/java/io/trino/operator/join/TestJoinOperatorInfo.java +++ b/core/trino-main/src/test/java/io/trino/operator/join/TestJoinOperatorInfo.java @@ -19,7 +19,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static io.trino.operator.join.LookupJoinOperatorFactory.JoinType.INNER; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestJoinOperatorInfo { @@ -42,11 +42,11 @@ public void testMerge() 7); JoinOperatorInfo merged = base.mergeWith(other); - assertEquals(makeHistogramArray(21, 41, 61, 81, 101, 121, 141, 161), merged.getLogHistogramProbes()); - assertEquals(makeHistogramArray(27, 47, 67, 87, 107, 127, 147, 167), merged.getLogHistogramOutput()); - assertEquals(merged.getLookupSourcePositions(), Optional.of(3L)); - assertEquals(merged.getRleProbes(), 6); - assertEquals(merged.getTotalProbes(), 10); + assertThat(makeHistogramArray(21, 41, 61, 81, 101, 121, 141, 161)).isEqualTo(merged.getLogHistogramProbes()); + assertThat(makeHistogramArray(27, 47, 67, 87, 107, 127, 147, 167)).isEqualTo(merged.getLogHistogramOutput()); + assertThat(merged.getLookupSourcePositions()).isEqualTo(Optional.of(3L)); + assertThat(merged.getRleProbes()).isEqualTo(6); + assertThat(merged.getTotalProbes()).isEqualTo(10); } private long[] makeHistogramArray(long... longArray) diff --git a/core/trino-main/src/test/java/io/trino/operator/join/TestJoinStatisticsCounter.java b/core/trino-main/src/test/java/io/trino/operator/join/TestJoinStatisticsCounter.java index 8bfdcb6c20f1..14c0f4bc2c5d 100644 --- a/core/trino-main/src/test/java/io/trino/operator/join/TestJoinStatisticsCounter.java +++ b/core/trino-main/src/test/java/io/trino/operator/join/TestJoinStatisticsCounter.java @@ -17,7 +17,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static io.trino.operator.join.LookupJoinOperatorFactory.JoinType.INNER; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestJoinStatisticsCounter { @@ -26,92 +26,92 @@ public void testRecord() { JoinStatisticsCounter counter = new JoinStatisticsCounter(INNER); JoinOperatorInfo info = counter.get(); - assertEquals(makeHistogramArray(0, 0, 0, 0, 0, 0, 0, 0), info.getLogHistogramProbes()); - assertEquals(makeHistogramArray(0, 0, 0, 0, 0, 0, 0, 0), info.getLogHistogramOutput()); + assertThat(makeHistogramArray(0, 0, 0, 0, 0, 0, 0, 0)).isEqualTo(info.getLogHistogramProbes()); + assertThat(makeHistogramArray(0, 0, 0, 0, 0, 0, 0, 0)).isEqualTo(info.getLogHistogramOutput()); // 0 to 4 buckets counter.recordProbe(0); info = counter.get(); - assertEquals(makeHistogramArray(1, 0, 0, 0, 0, 0, 0, 0), info.getLogHistogramProbes()); - assertEquals(makeHistogramArray(0, 0, 0, 0, 0, 0, 0, 0), info.getLogHistogramOutput()); + assertThat(makeHistogramArray(1, 0, 0, 0, 0, 0, 0, 0)).isEqualTo(info.getLogHistogramProbes()); + assertThat(makeHistogramArray(0, 0, 0, 0, 0, 0, 0, 0)).isEqualTo(info.getLogHistogramOutput()); counter.recordProbe(0); info = counter.get(); - assertEquals(makeHistogramArray(2, 0, 0, 0, 0, 0, 0, 0), info.getLogHistogramProbes()); - assertEquals(makeHistogramArray(0, 0, 0, 0, 0, 0, 0, 0), info.getLogHistogramOutput()); + assertThat(makeHistogramArray(2, 0, 0, 0, 0, 0, 0, 0)).isEqualTo(info.getLogHistogramProbes()); + assertThat(makeHistogramArray(0, 0, 0, 0, 0, 0, 0, 0)).isEqualTo(info.getLogHistogramOutput()); counter.recordProbe(1); info = counter.get(); - assertEquals(makeHistogramArray(2, 1, 0, 0, 0, 0, 0, 0), info.getLogHistogramProbes()); - assertEquals(makeHistogramArray(0, 1, 0, 0, 0, 0, 0, 0), info.getLogHistogramOutput()); + assertThat(makeHistogramArray(2, 1, 0, 0, 0, 0, 0, 0)).isEqualTo(info.getLogHistogramProbes()); + assertThat(makeHistogramArray(0, 1, 0, 0, 0, 0, 0, 0)).isEqualTo(info.getLogHistogramOutput()); counter.recordProbe(1); info = counter.get(); - assertEquals(makeHistogramArray(2, 2, 0, 0, 0, 0, 0, 0), info.getLogHistogramProbes()); - assertEquals(makeHistogramArray(0, 2, 0, 0, 0, 0, 0, 0), info.getLogHistogramOutput()); + assertThat(makeHistogramArray(2, 2, 0, 0, 0, 0, 0, 0)).isEqualTo(info.getLogHistogramProbes()); + assertThat(makeHistogramArray(0, 2, 0, 0, 0, 0, 0, 0)).isEqualTo(info.getLogHistogramOutput()); counter.recordProbe(2); info = counter.get(); - assertEquals(makeHistogramArray(2, 2, 1, 0, 0, 0, 0, 0), info.getLogHistogramProbes()); - assertEquals(makeHistogramArray(0, 2, 2, 0, 0, 0, 0, 0), info.getLogHistogramOutput()); + assertThat(makeHistogramArray(2, 2, 1, 0, 0, 0, 0, 0)).isEqualTo(info.getLogHistogramProbes()); + assertThat(makeHistogramArray(0, 2, 2, 0, 0, 0, 0, 0)).isEqualTo(info.getLogHistogramOutput()); counter.recordProbe(2); info = counter.get(); - assertEquals(makeHistogramArray(2, 2, 2, 0, 0, 0, 0, 0), info.getLogHistogramProbes()); - assertEquals(makeHistogramArray(0, 2, 4, 0, 0, 0, 0, 0), info.getLogHistogramOutput()); + assertThat(makeHistogramArray(2, 2, 2, 0, 0, 0, 0, 0)).isEqualTo(info.getLogHistogramProbes()); + assertThat(makeHistogramArray(0, 2, 4, 0, 0, 0, 0, 0)).isEqualTo(info.getLogHistogramOutput()); counter.recordProbe(3); info = counter.get(); - assertEquals(makeHistogramArray(2, 2, 2, 1, 0, 0, 0, 0), info.getLogHistogramProbes()); - assertEquals(makeHistogramArray(0, 2, 4, 3, 0, 0, 0, 0), info.getLogHistogramOutput()); + assertThat(makeHistogramArray(2, 2, 2, 1, 0, 0, 0, 0)).isEqualTo(info.getLogHistogramProbes()); + assertThat(makeHistogramArray(0, 2, 4, 3, 0, 0, 0, 0)).isEqualTo(info.getLogHistogramOutput()); counter.recordProbe(3); info = counter.get(); - assertEquals(makeHistogramArray(2, 2, 2, 2, 0, 0, 0, 0), info.getLogHistogramProbes()); - assertEquals(makeHistogramArray(0, 2, 4, 6, 0, 0, 0, 0), info.getLogHistogramOutput()); + assertThat(makeHistogramArray(2, 2, 2, 2, 0, 0, 0, 0)).isEqualTo(info.getLogHistogramProbes()); + assertThat(makeHistogramArray(0, 2, 4, 6, 0, 0, 0, 0)).isEqualTo(info.getLogHistogramOutput()); counter.recordProbe(4); info = counter.get(); - assertEquals(makeHistogramArray(2, 2, 2, 2, 1, 0, 0, 0), info.getLogHistogramProbes()); - assertEquals(makeHistogramArray(0, 2, 4, 6, 4, 0, 0, 0), info.getLogHistogramOutput()); + assertThat(makeHistogramArray(2, 2, 2, 2, 1, 0, 0, 0)).isEqualTo(info.getLogHistogramProbes()); + assertThat(makeHistogramArray(0, 2, 4, 6, 4, 0, 0, 0)).isEqualTo(info.getLogHistogramOutput()); counter.recordProbe(4); info = counter.get(); - assertEquals(makeHistogramArray(2, 2, 2, 2, 2, 0, 0, 0), info.getLogHistogramProbes()); - assertEquals(makeHistogramArray(0, 2, 4, 6, 8, 0, 0, 0), info.getLogHistogramOutput()); + assertThat(makeHistogramArray(2, 2, 2, 2, 2, 0, 0, 0)).isEqualTo(info.getLogHistogramProbes()); + assertThat(makeHistogramArray(0, 2, 4, 6, 8, 0, 0, 0)).isEqualTo(info.getLogHistogramOutput()); // 5 to 10 counter.recordProbe(5); info = counter.get(); - assertEquals(makeHistogramArray(2, 2, 2, 2, 2, 1, 0, 0), info.getLogHistogramProbes()); - assertEquals(makeHistogramArray(0, 2, 4, 6, 8, 5, 0, 0), info.getLogHistogramOutput()); + assertThat(makeHistogramArray(2, 2, 2, 2, 2, 1, 0, 0)).isEqualTo(info.getLogHistogramProbes()); + assertThat(makeHistogramArray(0, 2, 4, 6, 8, 5, 0, 0)).isEqualTo(info.getLogHistogramOutput()); counter.recordProbe(6); info = counter.get(); - assertEquals(makeHistogramArray(2, 2, 2, 2, 2, 2, 0, 0), info.getLogHistogramProbes()); - assertEquals(makeHistogramArray(0, 2, 4, 6, 8, 11, 0, 0), info.getLogHistogramOutput()); + assertThat(makeHistogramArray(2, 2, 2, 2, 2, 2, 0, 0)).isEqualTo(info.getLogHistogramProbes()); + assertThat(makeHistogramArray(0, 2, 4, 6, 8, 11, 0, 0)).isEqualTo(info.getLogHistogramOutput()); counter.recordProbe(10); info = counter.get(); - assertEquals(makeHistogramArray(2, 2, 2, 2, 2, 3, 0, 0), info.getLogHistogramProbes()); - assertEquals(makeHistogramArray(0, 2, 4, 6, 8, 21, 0, 0), info.getLogHistogramOutput()); + assertThat(makeHistogramArray(2, 2, 2, 2, 2, 3, 0, 0)).isEqualTo(info.getLogHistogramProbes()); + assertThat(makeHistogramArray(0, 2, 4, 6, 8, 21, 0, 0)).isEqualTo(info.getLogHistogramOutput()); // 11 to 100 counter.recordProbe(11); info = counter.get(); - assertEquals(makeHistogramArray(2, 2, 2, 2, 2, 3, 1, 0), info.getLogHistogramProbes()); - assertEquals(makeHistogramArray(0, 2, 4, 6, 8, 21, 11, 0), info.getLogHistogramOutput()); + assertThat(makeHistogramArray(2, 2, 2, 2, 2, 3, 1, 0)).isEqualTo(info.getLogHistogramProbes()); + assertThat(makeHistogramArray(0, 2, 4, 6, 8, 21, 11, 0)).isEqualTo(info.getLogHistogramOutput()); counter.recordProbe(100); info = counter.get(); - assertEquals(makeHistogramArray(2, 2, 2, 2, 2, 3, 2, 0), info.getLogHistogramProbes()); - assertEquals(makeHistogramArray(0, 2, 4, 6, 8, 21, 111, 0), info.getLogHistogramOutput()); + assertThat(makeHistogramArray(2, 2, 2, 2, 2, 3, 2, 0)).isEqualTo(info.getLogHistogramProbes()); + assertThat(makeHistogramArray(0, 2, 4, 6, 8, 21, 111, 0)).isEqualTo(info.getLogHistogramOutput()); // 101 and more counter.recordProbe(101); info = counter.get(); - assertEquals(makeHistogramArray(2, 2, 2, 2, 2, 3, 2, 1), info.getLogHistogramProbes()); - assertEquals(makeHistogramArray(0, 2, 4, 6, 8, 21, 111, 101), info.getLogHistogramOutput()); + assertThat(makeHistogramArray(2, 2, 2, 2, 2, 3, 2, 1)).isEqualTo(info.getLogHistogramProbes()); + assertThat(makeHistogramArray(0, 2, 4, 6, 8, 21, 111, 101)).isEqualTo(info.getLogHistogramOutput()); counter.recordProbe(1000); info = counter.get(); - assertEquals(makeHistogramArray(2, 2, 2, 2, 2, 3, 2, 2), info.getLogHistogramProbes()); - assertEquals(makeHistogramArray(0, 2, 4, 6, 8, 21, 111, 1101), info.getLogHistogramOutput()); + assertThat(makeHistogramArray(2, 2, 2, 2, 2, 3, 2, 2)).isEqualTo(info.getLogHistogramProbes()); + assertThat(makeHistogramArray(0, 2, 4, 6, 8, 21, 111, 1101)).isEqualTo(info.getLogHistogramOutput()); counter.recordProbe(1000000); info = counter.get(); - assertEquals(makeHistogramArray(2, 2, 2, 2, 2, 3, 2, 3), info.getLogHistogramProbes()); - assertEquals(makeHistogramArray(0, 2, 4, 6, 8, 21, 111, 1001101), info.getLogHistogramOutput()); + assertThat(makeHistogramArray(2, 2, 2, 2, 2, 3, 2, 3)).isEqualTo(info.getLogHistogramProbes()); + assertThat(makeHistogramArray(0, 2, 4, 6, 8, 21, 111, 1001101)).isEqualTo(info.getLogHistogramOutput()); } private long[] makeHistogramArray(long... longArray) diff --git a/core/trino-main/src/test/java/io/trino/operator/join/TestLookupJoinPageBuilder.java b/core/trino-main/src/test/java/io/trino/operator/join/TestLookupJoinPageBuilder.java index 95247484c0f8..02472c33a669 100644 --- a/core/trino-main/src/test/java/io/trino/operator/join/TestLookupJoinPageBuilder.java +++ b/core/trino-main/src/test/java/io/trino/operator/join/TestLookupJoinPageBuilder.java @@ -28,9 +28,7 @@ import java.util.OptionalInt; import static io.trino.spi.type.BigintType.BIGINT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestLookupJoinPageBuilder { @@ -55,32 +53,32 @@ public void testPageBuilder() lookupJoinPageBuilder.appendRow(probe, lookupSource, joinPosition++); lookupJoinPageBuilder.appendNullForBuild(probe); } - assertFalse(lookupJoinPageBuilder.isEmpty()); + assertThat(lookupJoinPageBuilder.isEmpty()).isFalse(); Page output = lookupJoinPageBuilder.build(probe); - assertEquals(output.getChannelCount(), 4); - assertTrue(output.getBlock(0) instanceof DictionaryBlock); - assertTrue(output.getBlock(1) instanceof DictionaryBlock); + assertThat(output.getChannelCount()).isEqualTo(4); + assertThat(output.getBlock(0) instanceof DictionaryBlock).isTrue(); + assertThat(output.getBlock(1) instanceof DictionaryBlock).isTrue(); for (int i = 0; i < output.getPositionCount(); i++) { - assertFalse(output.getBlock(0).isNull(i)); - assertFalse(output.getBlock(1).isNull(i)); - assertEquals(output.getBlock(0).getLong(i, 0), i / 2); - assertEquals(output.getBlock(1).getLong(i, 0), i / 2); + assertThat(output.getBlock(0).isNull(i)).isFalse(); + assertThat(output.getBlock(1).isNull(i)).isFalse(); + assertThat(output.getBlock(0).getLong(i, 0)).isEqualTo(i / 2); + assertThat(output.getBlock(1).getLong(i, 0)).isEqualTo(i / 2); if (i % 2 == 0) { - assertFalse(output.getBlock(2).isNull(i)); - assertFalse(output.getBlock(3).isNull(i)); - assertEquals(output.getBlock(2).getLong(i, 0), i / 2); - assertEquals(output.getBlock(3).getLong(i, 0), i / 2); + assertThat(output.getBlock(2).isNull(i)).isFalse(); + assertThat(output.getBlock(3).isNull(i)).isFalse(); + assertThat(output.getBlock(2).getLong(i, 0)).isEqualTo(i / 2); + assertThat(output.getBlock(3).getLong(i, 0)).isEqualTo(i / 2); } else { - assertTrue(output.getBlock(2).isNull(i)); - assertTrue(output.getBlock(3).isNull(i)); + assertThat(output.getBlock(2).isNull(i)).isTrue(); + assertThat(output.getBlock(3).isNull(i)).isTrue(); } } - assertTrue(lookupJoinPageBuilder.toString().contains("positionCount=" + output.getPositionCount())); + assertThat(lookupJoinPageBuilder.toString().contains("positionCount=" + output.getPositionCount())).isTrue(); lookupJoinPageBuilder.reset(); - assertTrue(lookupJoinPageBuilder.isEmpty()); + assertThat(lookupJoinPageBuilder.isEmpty()).isTrue(); } @Test @@ -100,9 +98,9 @@ public void testDifferentPositions() // empty JoinProbe probe = joinProbeFactory.createJoinProbe(page); Page output = lookupJoinPageBuilder.build(probe); - assertEquals(output.getChannelCount(), 2); - assertTrue(output.getBlock(0) instanceof LongArrayBlock); - assertEquals(output.getPositionCount(), 0); + assertThat(output.getChannelCount()).isEqualTo(2); + assertThat(output.getBlock(0) instanceof LongArrayBlock).isTrue(); + assertThat(output.getPositionCount()).isEqualTo(0); lookupJoinPageBuilder.reset(); // the probe covers non-sequential positions @@ -114,12 +112,12 @@ public void testDifferentPositions() lookupJoinPageBuilder.appendRow(probe, lookupSource, joinPosition); } output = lookupJoinPageBuilder.build(probe); - assertEquals(output.getChannelCount(), 2); - assertTrue(output.getBlock(0) instanceof DictionaryBlock); - assertEquals(output.getPositionCount(), entries / 2); + assertThat(output.getChannelCount()).isEqualTo(2); + assertThat(output.getBlock(0) instanceof DictionaryBlock).isTrue(); + assertThat(output.getPositionCount()).isEqualTo(entries / 2); for (int i = 0; i < entries / 2; i++) { - assertEquals(output.getBlock(0).getLong(i, 0), i * 2L); - assertEquals(output.getBlock(1).getLong(i, 0), i * 2L); + assertThat(output.getBlock(0).getLong(i, 0)).isEqualTo(i * 2L); + assertThat(output.getBlock(1).getLong(i, 0)).isEqualTo(i * 2L); } lookupJoinPageBuilder.reset(); @@ -129,12 +127,12 @@ public void testDifferentPositions() lookupJoinPageBuilder.appendRow(probe, lookupSource, joinPosition); } output = lookupJoinPageBuilder.build(probe); - assertEquals(output.getChannelCount(), 2); - assertFalse(output.getBlock(0) instanceof DictionaryBlock); - assertEquals(output.getPositionCount(), entries); + assertThat(output.getChannelCount()).isEqualTo(2); + assertThat(output.getBlock(0) instanceof DictionaryBlock).isFalse(); + assertThat(output.getPositionCount()).isEqualTo(entries); for (int i = 0; i < entries; i++) { - assertEquals(output.getBlock(0).getLong(i, 0), i); - assertEquals(output.getBlock(1).getLong(i, 0), i); + assertThat(output.getBlock(0).getLong(i, 0)).isEqualTo(i); + assertThat(output.getBlock(1).getLong(i, 0)).isEqualTo(i); } lookupJoinPageBuilder.reset(); @@ -147,12 +145,12 @@ public void testDifferentPositions() lookupJoinPageBuilder.appendRow(probe, lookupSource, joinPosition); } output = lookupJoinPageBuilder.build(probe); - assertEquals(output.getChannelCount(), 2); - assertFalse(output.getBlock(0) instanceof DictionaryBlock); - assertEquals(output.getPositionCount(), 40); + assertThat(output.getChannelCount()).isEqualTo(2); + assertThat(output.getBlock(0) instanceof DictionaryBlock).isFalse(); + assertThat(output.getPositionCount()).isEqualTo(40); for (int i = 10; i < 50; i++) { - assertEquals(output.getBlock(0).getLong(i - 10, 0), i); - assertEquals(output.getBlock(1).getLong(i - 10, 0), i); + assertThat(output.getBlock(0).getLong(i - 10, 0)).isEqualTo(i); + assertThat(output.getBlock(1).getLong(i - 10, 0)).isEqualTo(i); } } @@ -173,7 +171,7 @@ public void testCrossJoinWithEmptyBuild() for (int i = 0; i < 300_000; i++) { lookupJoinPageBuilder.appendRow(probe, lookupSource, 0); } - assertTrue(lookupJoinPageBuilder.isFull()); + assertThat(lookupJoinPageBuilder.isFull()).isTrue(); } private static final class TestLookupSource diff --git a/core/trino-main/src/test/java/io/trino/operator/join/TestNestedLoopBuildOperator.java b/core/trino-main/src/test/java/io/trino/operator/join/TestNestedLoopBuildOperator.java index 6f96ec32c251..1ba4ee974906 100644 --- a/core/trino-main/src/test/java/io/trino/operator/join/TestNestedLoopBuildOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/join/TestNestedLoopBuildOperator.java @@ -38,11 +38,9 @@ import static io.trino.spi.type.BigintType.BIGINT; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -80,7 +78,7 @@ public void testNestedLoopBuild() NestedLoopBuildOperator nestedLoopBuildOperator = (NestedLoopBuildOperator) nestedLoopBuildOperatorFactory.createOperator(driverContext); NestedLoopJoinBridge nestedLoopJoinBridge = nestedLoopJoinBridgeManager.getJoinBridge(); - assertFalse(nestedLoopJoinBridge.getPagesFuture().isDone()); + assertThat(nestedLoopJoinBridge.getPagesFuture().isDone()).isFalse(); // build pages Page buildPage1 = new Page(3, createLongSequenceBlock(11, 14)); @@ -92,12 +90,12 @@ public void testNestedLoopBuild() nestedLoopBuildOperator.addInput(buildPage2); nestedLoopBuildOperator.finish(); - assertTrue(nestedLoopJoinBridge.getPagesFuture().isDone()); + assertThat(nestedLoopJoinBridge.getPagesFuture().isDone()).isTrue(); List buildPages = nestedLoopJoinBridge.getPagesFuture().get().getPages(); - assertEquals(buildPages.get(0), buildPage1); - assertEquals(buildPages.get(1), buildPage2); - assertEquals(buildPages.size(), 2); + assertThat(buildPages.get(0)).isEqualTo(buildPage1); + assertThat(buildPages.get(1)).isEqualTo(buildPage2); + assertThat(buildPages.size()).isEqualTo(2); } @Test @@ -115,7 +113,7 @@ public void testNestedLoopBuildNoBlock() NestedLoopBuildOperator nestedLoopBuildOperator = (NestedLoopBuildOperator) nestedLoopBuildOperatorFactory.createOperator(driverContext); NestedLoopJoinBridge nestedLoopJoinBridge = nestedLoopJoinBridgeManager.getJoinBridge(); - assertFalse(nestedLoopJoinBridge.getPagesFuture().isDone()); + assertThat(nestedLoopJoinBridge.getPagesFuture().isDone()).isFalse(); // build pages Page buildPage1 = new Page(3); @@ -127,11 +125,11 @@ public void testNestedLoopBuildNoBlock() nestedLoopBuildOperator.addInput(buildPage2); nestedLoopBuildOperator.finish(); - assertTrue(nestedLoopJoinBridge.getPagesFuture().isDone()); + assertThat(nestedLoopJoinBridge.getPagesFuture().isDone()).isTrue(); List buildPages = nestedLoopJoinBridge.getPagesFuture().get().getPages(); - assertEquals(buildPages.size(), 1); - assertEquals(buildPages.get(0).getPositionCount(), 3003); + assertThat(buildPages.size()).isEqualTo(1); + assertThat(buildPages.get(0).getPositionCount()).isEqualTo(3003); } @Test @@ -149,7 +147,7 @@ public void testNestedLoopNoBlocksMaxSizeLimit() NestedLoopBuildOperator nestedLoopBuildOperator = (NestedLoopBuildOperator) nestedLoopBuildOperatorFactory.createOperator(driverContext); NestedLoopJoinBridge nestedLoopJoinBridge = nestedLoopJoinBridgeManager.getJoinBridge(); - assertFalse(nestedLoopJoinBridge.getPagesFuture().isDone()); + assertThat(nestedLoopJoinBridge.getPagesFuture().isDone()).isFalse(); // build pages Page massivePage = new Page(PageProcessor.MAX_BATCH_SIZE + 100); @@ -157,11 +155,11 @@ public void testNestedLoopNoBlocksMaxSizeLimit() nestedLoopBuildOperator.addInput(massivePage); nestedLoopBuildOperator.finish(); - assertTrue(nestedLoopJoinBridge.getPagesFuture().isDone()); + assertThat(nestedLoopJoinBridge.getPagesFuture().isDone()).isTrue(); List buildPages = nestedLoopJoinBridge.getPagesFuture().get().getPages(); - assertEquals(buildPages.size(), 2); - assertEquals(buildPages.get(0).getPositionCount(), PageProcessor.MAX_BATCH_SIZE); - assertEquals(buildPages.get(1).getPositionCount(), 100); + assertThat(buildPages.size()).isEqualTo(2); + assertThat(buildPages.get(0).getPositionCount()).isEqualTo(PageProcessor.MAX_BATCH_SIZE); + assertThat(buildPages.get(1).getPositionCount()).isEqualTo(100); } private TaskContext createTaskContext() diff --git a/core/trino-main/src/test/java/io/trino/operator/join/TestNestedLoopJoinOperator.java b/core/trino-main/src/test/java/io/trino/operator/join/TestNestedLoopJoinOperator.java index ca9a9573b561..54f041ac472b 100644 --- a/core/trino-main/src/test/java/io/trino/operator/join/TestNestedLoopJoinOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/join/TestNestedLoopJoinOperator.java @@ -48,10 +48,9 @@ import static io.trino.testing.MaterializedResult.resultBuilder; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -484,13 +483,15 @@ public void testCount() Page probePage = new Page(45); NestedLoopOutputIterator resultPageBuilder = NestedLoopJoinOperator.createNestedLoopOutputIterator(probePage, buildPage, new int[0], new int[0]); - assertTrue(resultPageBuilder.hasNext(), "There should be at least one page."); + assertThat(resultPageBuilder.hasNext()) + .describedAs("There should be at least one page.") + .isTrue(); long result = 0; while (resultPageBuilder.hasNext()) { result += resultPageBuilder.next().getPositionCount(); } - assertEquals(result, 4500); + assertThat(result).isEqualTo(4500); // force the product to be bigger than Integer.MAX_VALUE buildPage = new Page(Integer.MAX_VALUE - 10); @@ -500,7 +501,7 @@ public void testCount() while (resultPageBuilder.hasNext()) { result += resultPageBuilder.next().getPositionCount(); } - assertEquals((Integer.MAX_VALUE - 10) * 45L, result); + assertThat((Integer.MAX_VALUE - 10) * 45L).isEqualTo(result); } private TaskContext createTaskContext() diff --git a/core/trino-main/src/test/java/io/trino/operator/join/TestPositionLinks.java b/core/trino-main/src/test/java/io/trino/operator/join/TestPositionLinks.java index d2625febbea4..3c95d22a8ee2 100644 --- a/core/trino-main/src/test/java/io/trino/operator/join/TestPositionLinks.java +++ b/core/trino-main/src/test/java/io/trino/operator/join/TestPositionLinks.java @@ -30,7 +30,7 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static io.trino.operator.SyntheticAddress.encodeSyntheticAddress; import static io.trino.spi.type.BigintType.BIGINT; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestPositionLinks { @@ -41,26 +41,26 @@ public void testArrayPositionLinks() { PositionLinks.FactoryBuilder factoryBuilder = ArrayPositionLinks.builder(1000); - assertEquals(factoryBuilder.link(1, 0), 1); - assertEquals(factoryBuilder.link(2, 1), 2); - assertEquals(factoryBuilder.link(3, 2), 3); + assertThat(factoryBuilder.link(1, 0)).isEqualTo(1); + assertThat(factoryBuilder.link(2, 1)).isEqualTo(2); + assertThat(factoryBuilder.link(3, 2)).isEqualTo(3); - assertEquals(factoryBuilder.link(11, 10), 11); - assertEquals(factoryBuilder.link(12, 11), 12); + assertThat(factoryBuilder.link(11, 10)).isEqualTo(11); + assertThat(factoryBuilder.link(12, 11)).isEqualTo(12); PositionLinks positionLinks = factoryBuilder.build().create(ImmutableList.of()); - assertEquals(positionLinks.start(3, 0, TEST_PAGE), 3); - assertEquals(positionLinks.next(3, 0, TEST_PAGE), 2); - assertEquals(positionLinks.next(2, 0, TEST_PAGE), 1); - assertEquals(positionLinks.next(1, 0, TEST_PAGE), 0); + assertThat(positionLinks.start(3, 0, TEST_PAGE)).isEqualTo(3); + assertThat(positionLinks.next(3, 0, TEST_PAGE)).isEqualTo(2); + assertThat(positionLinks.next(2, 0, TEST_PAGE)).isEqualTo(1); + assertThat(positionLinks.next(1, 0, TEST_PAGE)).isEqualTo(0); - assertEquals(positionLinks.start(4, 0, TEST_PAGE), 4); - assertEquals(positionLinks.next(4, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(4, 0, TEST_PAGE)).isEqualTo(4); + assertThat(positionLinks.next(4, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(12, 0, TEST_PAGE), 12); - assertEquals(positionLinks.next(12, 0, TEST_PAGE), 11); - assertEquals(positionLinks.next(11, 0, TEST_PAGE), 10); + assertThat(positionLinks.start(12, 0, TEST_PAGE)).isEqualTo(12); + assertThat(positionLinks.next(12, 0, TEST_PAGE)).isEqualTo(11); + assertThat(positionLinks.next(11, 0, TEST_PAGE)).isEqualTo(10); } @Test @@ -72,26 +72,26 @@ public void testSortedPositionLinks() PositionLinks.FactoryBuilder factoryBuilder = buildSortedPositionLinks(); PositionLinks positionLinks = factoryBuilder.build().create(ImmutableList.of(filterFunction)); - assertEquals(positionLinks.start(0, 0, TEST_PAGE), 5); - assertEquals(positionLinks.next(5, 0, TEST_PAGE), 6); - assertEquals(positionLinks.next(6, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(0, 0, TEST_PAGE)).isEqualTo(5); + assertThat(positionLinks.next(5, 0, TEST_PAGE)).isEqualTo(6); + assertThat(positionLinks.next(6, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(7, 0, TEST_PAGE), 7); - assertEquals(positionLinks.next(7, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(7, 0, TEST_PAGE)).isEqualTo(7); + assertThat(positionLinks.next(7, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(8, 0, TEST_PAGE), 8); - assertEquals(positionLinks.next(8, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(8, 0, TEST_PAGE)).isEqualTo(8); + assertThat(positionLinks.next(8, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(9, 0, TEST_PAGE), 9); - assertEquals(positionLinks.next(9, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(9, 0, TEST_PAGE)).isEqualTo(9); + assertThat(positionLinks.next(9, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(10, 0, TEST_PAGE), 10); - assertEquals(positionLinks.next(10, 0, TEST_PAGE), 11); - assertEquals(positionLinks.next(11, 0, TEST_PAGE), 12); - assertEquals(positionLinks.next(12, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(10, 0, TEST_PAGE)).isEqualTo(10); + assertThat(positionLinks.next(10, 0, TEST_PAGE)).isEqualTo(11); + assertThat(positionLinks.next(11, 0, TEST_PAGE)).isEqualTo(12); + assertThat(positionLinks.next(12, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(13, 0, TEST_PAGE), 13); - assertEquals(positionLinks.next(13, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(13, 0, TEST_PAGE)).isEqualTo(13); + assertThat(positionLinks.next(13, 0, TEST_PAGE)).isEqualTo(-1); } @Test @@ -103,31 +103,31 @@ public void testSortedPositionLinksAllMatch() PositionLinks.FactoryBuilder factoryBuilder = buildSortedPositionLinks(); PositionLinks positionLinks = factoryBuilder.build().create(ImmutableList.of(filterFunction)); - assertEquals(positionLinks.start(0, 0, TEST_PAGE), 0); - assertEquals(positionLinks.next(0, 0, TEST_PAGE), 1); - assertEquals(positionLinks.next(1, 0, TEST_PAGE), 2); - assertEquals(positionLinks.next(2, 0, TEST_PAGE), 3); - assertEquals(positionLinks.next(3, 0, TEST_PAGE), 4); - assertEquals(positionLinks.next(4, 0, TEST_PAGE), 5); - assertEquals(positionLinks.next(5, 0, TEST_PAGE), 6); - assertEquals(positionLinks.next(6, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(0, 0, TEST_PAGE)).isEqualTo(0); + assertThat(positionLinks.next(0, 0, TEST_PAGE)).isEqualTo(1); + assertThat(positionLinks.next(1, 0, TEST_PAGE)).isEqualTo(2); + assertThat(positionLinks.next(2, 0, TEST_PAGE)).isEqualTo(3); + assertThat(positionLinks.next(3, 0, TEST_PAGE)).isEqualTo(4); + assertThat(positionLinks.next(4, 0, TEST_PAGE)).isEqualTo(5); + assertThat(positionLinks.next(5, 0, TEST_PAGE)).isEqualTo(6); + assertThat(positionLinks.next(6, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(7, 0, TEST_PAGE), 7); - assertEquals(positionLinks.next(7, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(7, 0, TEST_PAGE)).isEqualTo(7); + assertThat(positionLinks.next(7, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(8, 0, TEST_PAGE), 8); - assertEquals(positionLinks.next(8, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(8, 0, TEST_PAGE)).isEqualTo(8); + assertThat(positionLinks.next(8, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(9, 0, TEST_PAGE), 9); - assertEquals(positionLinks.next(9, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(9, 0, TEST_PAGE)).isEqualTo(9); + assertThat(positionLinks.next(9, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(10, 0, TEST_PAGE), 10); - assertEquals(positionLinks.next(10, 0, TEST_PAGE), 11); - assertEquals(positionLinks.next(11, 0, TEST_PAGE), 12); - assertEquals(positionLinks.next(12, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(10, 0, TEST_PAGE)).isEqualTo(10); + assertThat(positionLinks.next(10, 0, TEST_PAGE)).isEqualTo(11); + assertThat(positionLinks.next(11, 0, TEST_PAGE)).isEqualTo(12); + assertThat(positionLinks.next(12, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(13, 0, TEST_PAGE), 13); - assertEquals(positionLinks.next(13, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(13, 0, TEST_PAGE)).isEqualTo(13); + assertThat(positionLinks.next(13, 0, TEST_PAGE)).isEqualTo(-1); } @Test @@ -140,25 +140,25 @@ public void testSortedPositionLinksForRangePredicates() PositionLinks.FactoryBuilder factoryBuilder = buildSortedPositionLinks(); PositionLinks positionLinks = factoryBuilder.build().create(ImmutableList.of(filterFunctionOne, filterFunctionTwo)); - assertEquals(positionLinks.start(0, 0, TEST_PAGE), 5); - assertEquals(positionLinks.next(4, 0, TEST_PAGE), 5); - assertEquals(positionLinks.next(5, 0, TEST_PAGE), 6); - assertEquals(positionLinks.next(6, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(0, 0, TEST_PAGE)).isEqualTo(5); + assertThat(positionLinks.next(4, 0, TEST_PAGE)).isEqualTo(5); + assertThat(positionLinks.next(5, 0, TEST_PAGE)).isEqualTo(6); + assertThat(positionLinks.next(6, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(7, 0, TEST_PAGE), 7); - assertEquals(positionLinks.next(7, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(7, 0, TEST_PAGE)).isEqualTo(7); + assertThat(positionLinks.next(7, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(8, 0, TEST_PAGE), 8); - assertEquals(positionLinks.next(8, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(8, 0, TEST_PAGE)).isEqualTo(8); + assertThat(positionLinks.next(8, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(9, 0, TEST_PAGE), 9); - assertEquals(positionLinks.next(9, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(9, 0, TEST_PAGE)).isEqualTo(9); + assertThat(positionLinks.next(9, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(10, 0, TEST_PAGE), 10); - assertEquals(positionLinks.next(10, 0, TEST_PAGE), 11); - assertEquals(positionLinks.next(11, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(10, 0, TEST_PAGE)).isEqualTo(10); + assertThat(positionLinks.next(10, 0, TEST_PAGE)).isEqualTo(11); + assertThat(positionLinks.next(11, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(13, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(13, 0, TEST_PAGE)).isEqualTo(-1); } @Test @@ -171,29 +171,29 @@ public void testSortedPositionLinksForRangePredicatesPrefixMatch() PositionLinks.FactoryBuilder factoryBuilder = buildSortedPositionLinks(); PositionLinks positionLinks = factoryBuilder.build().create(ImmutableList.of(filterFunctionOne, filterFunctionTwo)); - assertEquals(positionLinks.start(0, 0, TEST_PAGE), 0); - assertEquals(positionLinks.next(0, 0, TEST_PAGE), 1); - assertEquals(positionLinks.next(1, 0, TEST_PAGE), 2); - assertEquals(positionLinks.next(2, 0, TEST_PAGE), 3); - assertEquals(positionLinks.next(3, 0, TEST_PAGE), 4); - assertEquals(positionLinks.next(4, 0, TEST_PAGE), 5); - assertEquals(positionLinks.next(5, 0, TEST_PAGE), 6); - assertEquals(positionLinks.next(6, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(0, 0, TEST_PAGE)).isEqualTo(0); + assertThat(positionLinks.next(0, 0, TEST_PAGE)).isEqualTo(1); + assertThat(positionLinks.next(1, 0, TEST_PAGE)).isEqualTo(2); + assertThat(positionLinks.next(2, 0, TEST_PAGE)).isEqualTo(3); + assertThat(positionLinks.next(3, 0, TEST_PAGE)).isEqualTo(4); + assertThat(positionLinks.next(4, 0, TEST_PAGE)).isEqualTo(5); + assertThat(positionLinks.next(5, 0, TEST_PAGE)).isEqualTo(6); + assertThat(positionLinks.next(6, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(7, 0, TEST_PAGE), 7); - assertEquals(positionLinks.next(7, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(7, 0, TEST_PAGE)).isEqualTo(7); + assertThat(positionLinks.next(7, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(8, 0, TEST_PAGE), 8); - assertEquals(positionLinks.next(8, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(8, 0, TEST_PAGE)).isEqualTo(8); + assertThat(positionLinks.next(8, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(9, 0, TEST_PAGE), 9); - assertEquals(positionLinks.next(9, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(9, 0, TEST_PAGE)).isEqualTo(9); + assertThat(positionLinks.next(9, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(10, 0, TEST_PAGE), 10); - assertEquals(positionLinks.next(10, 0, TEST_PAGE), 11); - assertEquals(positionLinks.next(11, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(10, 0, TEST_PAGE)).isEqualTo(10); + assertThat(positionLinks.next(10, 0, TEST_PAGE)).isEqualTo(11); + assertThat(positionLinks.next(11, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(13, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(13, 0, TEST_PAGE)).isEqualTo(-1); } @Test @@ -206,27 +206,27 @@ public void testSortedPositionLinksForRangePredicatesSuffixMatch() PositionLinks.FactoryBuilder factoryBuilder = buildSortedPositionLinks(); PositionLinks positionLinks = factoryBuilder.build().create(ImmutableList.of(filterFunctionOne, filterFunctionTwo)); - assertEquals(positionLinks.start(0, 0, TEST_PAGE), 5); - assertEquals(positionLinks.next(4, 0, TEST_PAGE), 5); - assertEquals(positionLinks.next(5, 0, TEST_PAGE), 6); - assertEquals(positionLinks.next(6, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(0, 0, TEST_PAGE)).isEqualTo(5); + assertThat(positionLinks.next(4, 0, TEST_PAGE)).isEqualTo(5); + assertThat(positionLinks.next(5, 0, TEST_PAGE)).isEqualTo(6); + assertThat(positionLinks.next(6, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(7, 0, TEST_PAGE), 7); - assertEquals(positionLinks.next(7, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(7, 0, TEST_PAGE)).isEqualTo(7); + assertThat(positionLinks.next(7, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(8, 0, TEST_PAGE), 8); - assertEquals(positionLinks.next(8, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(8, 0, TEST_PAGE)).isEqualTo(8); + assertThat(positionLinks.next(8, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(9, 0, TEST_PAGE), 9); - assertEquals(positionLinks.next(9, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(9, 0, TEST_PAGE)).isEqualTo(9); + assertThat(positionLinks.next(9, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(10, 0, TEST_PAGE), 10); - assertEquals(positionLinks.next(10, 0, TEST_PAGE), 11); - assertEquals(positionLinks.next(11, 0, TEST_PAGE), 12); - assertEquals(positionLinks.next(12, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(10, 0, TEST_PAGE)).isEqualTo(10); + assertThat(positionLinks.next(10, 0, TEST_PAGE)).isEqualTo(11); + assertThat(positionLinks.next(11, 0, TEST_PAGE)).isEqualTo(12); + assertThat(positionLinks.next(12, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(13, 0, TEST_PAGE), 13); - assertEquals(positionLinks.next(13, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(13, 0, TEST_PAGE)).isEqualTo(13); + assertThat(positionLinks.next(13, 0, TEST_PAGE)).isEqualTo(-1); } @Test @@ -238,13 +238,13 @@ public void testReverseSortedPositionLinks() PositionLinks.FactoryBuilder factoryBuilder = buildSortedPositionLinks(); PositionLinks positionLinks = factoryBuilder.build().create(ImmutableList.of(filterFunction)); - assertEquals(positionLinks.start(0, 0, TEST_PAGE), 0); - assertEquals(positionLinks.next(0, 0, TEST_PAGE), 1); - assertEquals(positionLinks.next(1, 0, TEST_PAGE), 2); - assertEquals(positionLinks.next(2, 0, TEST_PAGE), 3); - assertEquals(positionLinks.next(3, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(0, 0, TEST_PAGE)).isEqualTo(0); + assertThat(positionLinks.next(0, 0, TEST_PAGE)).isEqualTo(1); + assertThat(positionLinks.next(1, 0, TEST_PAGE)).isEqualTo(2); + assertThat(positionLinks.next(2, 0, TEST_PAGE)).isEqualTo(3); + assertThat(positionLinks.next(3, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(10, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(10, 0, TEST_PAGE)).isEqualTo(-1); } @Test @@ -256,30 +256,30 @@ public void testReverseSortedPositionLinksAllMatch() PositionLinks.FactoryBuilder factoryBuilder = buildSortedPositionLinks(); PositionLinks positionLinks = factoryBuilder.build().create(ImmutableList.of(filterFunction)); - assertEquals(positionLinks.start(0, 0, TEST_PAGE), 0); - assertEquals(positionLinks.next(0, 0, TEST_PAGE), 1); - assertEquals(positionLinks.next(1, 0, TEST_PAGE), 2); - assertEquals(positionLinks.next(2, 0, TEST_PAGE), 3); - assertEquals(positionLinks.next(3, 0, TEST_PAGE), 4); - assertEquals(positionLinks.next(4, 0, TEST_PAGE), 5); - assertEquals(positionLinks.next(5, 0, TEST_PAGE), 6); - assertEquals(positionLinks.next(6, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(0, 0, TEST_PAGE)).isEqualTo(0); + assertThat(positionLinks.next(0, 0, TEST_PAGE)).isEqualTo(1); + assertThat(positionLinks.next(1, 0, TEST_PAGE)).isEqualTo(2); + assertThat(positionLinks.next(2, 0, TEST_PAGE)).isEqualTo(3); + assertThat(positionLinks.next(3, 0, TEST_PAGE)).isEqualTo(4); + assertThat(positionLinks.next(4, 0, TEST_PAGE)).isEqualTo(5); + assertThat(positionLinks.next(5, 0, TEST_PAGE)).isEqualTo(6); + assertThat(positionLinks.next(6, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(7, 0, TEST_PAGE), 7); - assertEquals(positionLinks.next(7, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(7, 0, TEST_PAGE)).isEqualTo(7); + assertThat(positionLinks.next(7, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(8, 0, TEST_PAGE), 8); - assertEquals(positionLinks.next(8, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(8, 0, TEST_PAGE)).isEqualTo(8); + assertThat(positionLinks.next(8, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(9, 0, TEST_PAGE), 9); - assertEquals(positionLinks.next(9, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(9, 0, TEST_PAGE)).isEqualTo(9); + assertThat(positionLinks.next(9, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(10, 0, TEST_PAGE), 10); - assertEquals(positionLinks.next(10, 0, TEST_PAGE), 11); - assertEquals(positionLinks.next(11, 0, TEST_PAGE), 12); - assertEquals(positionLinks.next(12, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(10, 0, TEST_PAGE)).isEqualTo(10); + assertThat(positionLinks.next(10, 0, TEST_PAGE)).isEqualTo(11); + assertThat(positionLinks.next(11, 0, TEST_PAGE)).isEqualTo(12); + assertThat(positionLinks.next(12, 0, TEST_PAGE)).isEqualTo(-1); - assertEquals(positionLinks.start(13, 0, TEST_PAGE), -1); + assertThat(positionLinks.start(13, 0, TEST_PAGE)).isEqualTo(-1); } private static PositionLinks.FactoryBuilder buildSortedPositionLinks() @@ -296,15 +296,15 @@ private static PositionLinks.FactoryBuilder buildSortedPositionLinks() * [10] -> [11,12] */ - assertEquals(builder.link(4, 5), 4); - assertEquals(builder.link(6, 4), 4); - assertEquals(builder.link(2, 4), 2); - assertEquals(builder.link(3, 2), 2); - assertEquals(builder.link(0, 2), 0); - assertEquals(builder.link(1, 0), 0); + assertThat(builder.link(4, 5)).isEqualTo(4); + assertThat(builder.link(6, 4)).isEqualTo(4); + assertThat(builder.link(2, 4)).isEqualTo(2); + assertThat(builder.link(3, 2)).isEqualTo(2); + assertThat(builder.link(0, 2)).isEqualTo(0); + assertThat(builder.link(1, 0)).isEqualTo(0); - assertEquals(builder.link(10, 11), 10); - assertEquals(builder.link(12, 10), 10); + assertThat(builder.link(10, 11)).isEqualTo(10); + assertThat(builder.link(12, 10)).isEqualTo(10); return builder; } diff --git a/core/trino-main/src/test/java/io/trino/operator/join/unspilled/TestHashBuilderOperator.java b/core/trino-main/src/test/java/io/trino/operator/join/unspilled/TestHashBuilderOperator.java index d2562aca388e..ea6d523cc530 100644 --- a/core/trino-main/src/test/java/io/trino/operator/join/unspilled/TestHashBuilderOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/join/unspilled/TestHashBuilderOperator.java @@ -52,9 +52,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -118,27 +115,27 @@ public void test() 10_000, new PagesIndex.TestingFactory(false), defaultHashArraySizeSupplier())) { - assertEquals(operator.getState(), CONSUMING_INPUT); + assertThat(operator.getState()).isEqualTo(CONSUMING_INPUT); ListenableFuture whenBuildFinishes = lookupSourceFactory.whenBuildFinishes(); assertThat(whenBuildFinishes).isNotDone(); for (int i = 0; i < 100; i++) { assertThat(operator.isBlocked()).isDone(); - assertTrue(operator.needsInput()); + assertThat(operator.needsInput()).isTrue(); operator.addInput(somePage(types)); } - assertFalse(operator.isFinished()); - assertEquals(operator.getState(), CONSUMING_INPUT); + assertThat(operator.isFinished()).isFalse(); + assertThat(operator.getState()).isEqualTo(CONSUMING_INPUT); anotherOperatorContext.getOperatorMemoryContext().localUserMemoryContext().setBytes(memoryPoolSizeInBytes); operator.finish(); // not enough memory to create lookup source - assertEquals(operator.getState(), CONSUMING_INPUT); - assertFalse(operator.isFinished()); + assertThat(operator.getState()).isEqualTo(CONSUMING_INPUT); + assertThat(operator.isFinished()).isFalse(); assertThat(whenBuildFinishes).isNotDone(); assertThat(operatorContext.isWaitingForMemory()).isNotDone(); @@ -146,20 +143,20 @@ public void test() operator.finish(); - assertEquals(operator.getState(), LOOKUP_SOURCE_BUILT); - assertFalse(operator.isFinished()); + assertThat(operator.getState()).isEqualTo(LOOKUP_SOURCE_BUILT); + assertThat(operator.isFinished()).isFalse(); assertThat(whenBuildFinishes).isDone(); assertThat(operator.isBlocked()).isNotDone(); lookupSourceFactory.destroy(); assertThat(operator.isBlocked()).isDone(); - assertEquals(operator.getState(), LOOKUP_SOURCE_BUILT); + assertThat(operator.getState()).isEqualTo(LOOKUP_SOURCE_BUILT); operator.finish(); - assertEquals(operator.getState(), CLOSED); - assertTrue(operator.isFinished()); + assertThat(operator.getState()).isEqualTo(CLOSED); + assertThat(operator.isFinished()).isTrue(); } finally { operatorContext.destroy(); diff --git a/core/trino-main/src/test/java/io/trino/operator/join/unspilled/TestHashJoinOperator.java b/core/trino-main/src/test/java/io/trino/operator/join/unspilled/TestHashJoinOperator.java index 0e45e952d0f2..c51cc67701d7 100644 --- a/core/trino-main/src/test/java/io/trino/operator/join/unspilled/TestHashJoinOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/join/unspilled/TestHashJoinOperator.java @@ -98,11 +98,6 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; @Test(singleThreaded = true) public class TestHashJoinOperator @@ -301,12 +296,12 @@ public void testUnwrapsLazyBlocks(boolean singleBigintLookupSource) instantiateBuildDrivers(buildSideSetup, taskContext); buildLookupSource(executor, buildSideSetup); Operator operator = joinOperatorFactory.createOperator(driverContext); - assertTrue(operator.needsInput()); + assertThat(operator.needsInput()).isTrue(); operator.addInput(probeInput.get(0)); operator.finish(); Page output = operator.getOutput(); - assertFalse(output.getBlock(1) instanceof LazyBlock); + assertThat(output.getBlock(1) instanceof LazyBlock).isFalse(); } @Test(dataProvider = "singleBigintLookupSourceProvider") @@ -352,7 +347,7 @@ public void testYield(boolean singleBigintLookupSource) instantiateBuildDrivers(buildSideSetup, taskContext); buildLookupSource(executor, buildSideSetup); Operator operator = joinOperatorFactory.createOperator(driverContext); - assertTrue(operator.needsInput()); + assertThat(operator.needsInput()).isTrue(); operator.addInput(probeInput.get(0)); operator.finish(); @@ -360,8 +355,10 @@ public void testYield(boolean singleBigintLookupSource) for (int i = 0; i < entries; i++) { driverContext.getYieldSignal().setWithDelay(5 * SECONDS.toNanos(1), driverContext.getYieldExecutor()); filterFunctionCalls.set(0); - assertNull(operator.getOutput()); - assertEquals(filterFunctionCalls.get(), 1, "Expected join to stop processing (yield) after calling filter function once"); + assertThat(operator.getOutput()).isNull(); + assertThat(filterFunctionCalls.get()) + .describedAs("Expected join to stop processing (yield) after calling filter function once") + .isEqualTo(1); driverContext.getYieldSignal().reset(); } // delayed yield is not going to prevent operator from producing a page now (yield won't be forced because filter function won't be called anymore) @@ -371,11 +368,11 @@ public void testYield(boolean singleBigintLookupSource) for (int i = 0; output == null && i < 5; i++) { output = operator.getOutput(); } - assertNotNull(output); + assertThat(output).isNotNull(); driverContext.getYieldSignal().reset(); // make sure we have all 4 entries - assertEquals(output.getPositionCount(), entries); + assertThat(output.getPositionCount()).isEqualTo(entries); } @Test(dataProvider = "hashJoinTestValuesAndsingleBigintLookupSourceProvider") @@ -940,7 +937,7 @@ public void testInnerJoinWithEmptyLookupSource(boolean parallelBuild, boolean pr List pages = probePages.row(1L).build(); operator.addInput(pages.get(0)); Page outputPage = operator.getOutput(); - assertNull(outputPage); + assertThat(outputPage).isNull(); } @Test(dataProvider = "hashJoinTestValuesAndsingleBigintLookupSourceProvider") @@ -977,7 +974,7 @@ public void testLookupOuterJoinWithEmptyLookupSource(boolean parallelBuild, bool List pages = probePages.row(1L).build(); operator.addInput(pages.get(0)); Page outputPage = operator.getOutput(); - assertNull(outputPage); + assertThat(outputPage).isNull(); } @Test(dataProvider = "hashJoinTestValuesAndsingleBigintLookupSourceProvider") @@ -1122,13 +1119,13 @@ public void testInnerJoinWithBlockingLookupSourceAndEmptyProbe(boolean parallelB DriverContext driverContext = taskContext.addPipelineContext(0, true, true, false).addDriverContext(); try (Operator joinOperator = joinOperatorFactory.createOperator(driverContext)) { joinOperatorFactory.noMoreOperators(); - assertFalse(joinOperator.needsInput()); + assertThat(joinOperator.needsInput()).isFalse(); joinOperator.finish(); - assertNull(joinOperator.getOutput()); + assertThat(joinOperator.getOutput()).isNull(); // lookup join operator got blocked waiting for build side - assertFalse(joinOperator.isBlocked().isDone()); - assertFalse(joinOperator.isFinished()); + assertThat(joinOperator.isBlocked().isDone()).isFalse(); + assertThat(joinOperator.isFinished()).isFalse(); } // join that doesn't wait for build side to be collected @@ -1137,14 +1134,14 @@ public void testInnerJoinWithBlockingLookupSourceAndEmptyProbe(boolean parallelB driverContext = taskContext.addPipelineContext(0, true, true, false).addDriverContext(); try (Operator joinOperator = joinOperatorFactory.createOperator(driverContext)) { joinOperatorFactory.noMoreOperators(); - assertTrue(joinOperator.needsInput()); + assertThat(joinOperator.needsInput()).isTrue(); joinOperator.finish(); - assertNull(joinOperator.getOutput()); + assertThat(joinOperator.getOutput()).isNull(); // lookup join operator will yield once before finishing - assertNull(joinOperator.getOutput()); - assertTrue(joinOperator.isBlocked().isDone()); - assertTrue(joinOperator.isFinished()); + assertThat(joinOperator.getOutput()).isNull(); + assertThat(joinOperator.isBlocked().isDone()).isTrue(); + assertThat(joinOperator.isFinished()).isTrue(); } } @@ -1161,12 +1158,12 @@ public void testInnerJoinWithBlockingLookupSource(boolean parallelBuild, boolean DriverContext driverContext = taskContext.addPipelineContext(0, true, true, false).addDriverContext(); try (Operator joinOperator = joinOperatorFactory.createOperator(driverContext)) { joinOperatorFactory.noMoreOperators(); - assertFalse(joinOperator.needsInput()); - assertNull(joinOperator.getOutput()); + assertThat(joinOperator.needsInput()).isFalse(); + assertThat(joinOperator.getOutput()).isNull(); // lookup join operator got blocked waiting for build side - assertFalse(joinOperator.isBlocked().isDone()); - assertFalse(joinOperator.isFinished()); + assertThat(joinOperator.isBlocked().isDone()).isFalse(); + assertThat(joinOperator.isFinished()).isFalse(); } // join that doesn't wait for build side to be collected @@ -1175,18 +1172,18 @@ public void testInnerJoinWithBlockingLookupSource(boolean parallelBuild, boolean driverContext = taskContext.addPipelineContext(0, true, true, false).addDriverContext(); try (Operator joinOperator = joinOperatorFactory.createOperator(driverContext)) { joinOperatorFactory.noMoreOperators(); - assertTrue(joinOperator.needsInput()); - assertNull(joinOperator.getOutput()); + assertThat(joinOperator.needsInput()).isTrue(); + assertThat(joinOperator.getOutput()).isNull(); // join needs input page - assertTrue(joinOperator.isBlocked().isDone()); - assertFalse(joinOperator.isFinished()); + assertThat(joinOperator.isBlocked().isDone()).isTrue(); + assertThat(joinOperator.isFinished()).isFalse(); joinOperator.addInput(probePage); - assertNull(joinOperator.getOutput()); + assertThat(joinOperator.getOutput()).isNull(); // lookup join operator got blocked waiting for build side - assertFalse(joinOperator.isBlocked().isDone()); - assertFalse(joinOperator.isFinished()); + assertThat(joinOperator.isBlocked().isDone()).isFalse(); + assertThat(joinOperator.isFinished()).isFalse(); } } @@ -1233,7 +1230,7 @@ public void testInnerJoinLoadsPagesInOrder() 1, () -> { // when loaded this block should be the latest one - assertEquals(probePageNumber, totalProbePages.get()); + assertThat(probePageNumber).isEqualTo(totalProbePages.get()); return probePage.getBlock(2); }))); }); @@ -1258,7 +1255,7 @@ public void testInnerJoinLoadsPagesInOrder() Page page = outputPages.getResult(); totalOutputPages++; - assertFalse(page.getBlock(1).isLoaded()); + assertThat(page.getBlock(1).isLoaded()).isFalse(); page.getBlock(2).getLoadedBlock(); // yield to enforce more complex execution @@ -1266,8 +1263,8 @@ public void testInnerJoinLoadsPagesInOrder() } // make sure that multiple pages were produced for some probe pages - assertTrue(totalOutputPages > totalProbePages.get()); - assertTrue(outputPages.isFinished()); + assertThat(totalOutputPages > totalProbePages.get()).isTrue(); + assertThat(outputPages.isFinished()).isTrue(); } private OperatorFactory createJoinOperatorFactoryWithBlockingLookupSource(TaskContext taskContext, boolean parallelBuild, boolean probeHashEnabled, boolean buildHashEnabled, boolean waitForBuild) diff --git a/core/trino-main/src/test/java/io/trino/operator/join/unspilled/TestLookupJoinPageBuilder.java b/core/trino-main/src/test/java/io/trino/operator/join/unspilled/TestLookupJoinPageBuilder.java index d2214565dc7b..a6e9fcaa2304 100644 --- a/core/trino-main/src/test/java/io/trino/operator/join/unspilled/TestLookupJoinPageBuilder.java +++ b/core/trino-main/src/test/java/io/trino/operator/join/unspilled/TestLookupJoinPageBuilder.java @@ -29,9 +29,7 @@ import java.util.OptionalInt; import static io.trino.spi.type.BigintType.BIGINT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestLookupJoinPageBuilder { @@ -56,32 +54,32 @@ public void testPageBuilder() lookupJoinPageBuilder.appendRow(probe, lookupSource, joinPosition++); lookupJoinPageBuilder.appendNullForBuild(probe); } - assertFalse(lookupJoinPageBuilder.isEmpty()); + assertThat(lookupJoinPageBuilder.isEmpty()).isFalse(); Page output = lookupJoinPageBuilder.build(probe); - assertEquals(output.getChannelCount(), 4); - assertTrue(output.getBlock(0) instanceof DictionaryBlock); - assertTrue(output.getBlock(1) instanceof DictionaryBlock); + assertThat(output.getChannelCount()).isEqualTo(4); + assertThat(output.getBlock(0) instanceof DictionaryBlock).isTrue(); + assertThat(output.getBlock(1) instanceof DictionaryBlock).isTrue(); for (int i = 0; i < output.getPositionCount(); i++) { - assertFalse(output.getBlock(0).isNull(i)); - assertFalse(output.getBlock(1).isNull(i)); - assertEquals(output.getBlock(0).getLong(i, 0), i / 2); - assertEquals(output.getBlock(1).getLong(i, 0), i / 2); + assertThat(output.getBlock(0).isNull(i)).isFalse(); + assertThat(output.getBlock(1).isNull(i)).isFalse(); + assertThat(output.getBlock(0).getLong(i, 0)).isEqualTo(i / 2); + assertThat(output.getBlock(1).getLong(i, 0)).isEqualTo(i / 2); if (i % 2 == 0) { - assertFalse(output.getBlock(2).isNull(i)); - assertFalse(output.getBlock(3).isNull(i)); - assertEquals(output.getBlock(2).getLong(i, 0), i / 2); - assertEquals(output.getBlock(3).getLong(i, 0), i / 2); + assertThat(output.getBlock(2).isNull(i)).isFalse(); + assertThat(output.getBlock(3).isNull(i)).isFalse(); + assertThat(output.getBlock(2).getLong(i, 0)).isEqualTo(i / 2); + assertThat(output.getBlock(3).getLong(i, 0)).isEqualTo(i / 2); } else { - assertTrue(output.getBlock(2).isNull(i)); - assertTrue(output.getBlock(3).isNull(i)); + assertThat(output.getBlock(2).isNull(i)).isTrue(); + assertThat(output.getBlock(3).isNull(i)).isTrue(); } } - assertTrue(lookupJoinPageBuilder.toString().contains("positionCount=" + output.getPositionCount())); + assertThat(lookupJoinPageBuilder.toString().contains("positionCount=" + output.getPositionCount())).isTrue(); lookupJoinPageBuilder.reset(); - assertTrue(lookupJoinPageBuilder.isEmpty()); + assertThat(lookupJoinPageBuilder.isEmpty()).isTrue(); } @Test @@ -101,9 +99,9 @@ public void testDifferentPositions() // empty JoinProbe probe = joinProbeFactory.createJoinProbe(page, lookupSource); Page output = lookupJoinPageBuilder.build(probe); - assertEquals(output.getChannelCount(), 2); - assertTrue(output.getBlock(0) instanceof LongArrayBlock); - assertEquals(output.getPositionCount(), 0); + assertThat(output.getChannelCount()).isEqualTo(2); + assertThat(output.getBlock(0) instanceof LongArrayBlock).isTrue(); + assertThat(output.getPositionCount()).isEqualTo(0); lookupJoinPageBuilder.reset(); // the probe covers non-sequential positions @@ -115,12 +113,12 @@ public void testDifferentPositions() lookupJoinPageBuilder.appendRow(probe, lookupSource, joinPosition); } output = lookupJoinPageBuilder.build(probe); - assertEquals(output.getChannelCount(), 2); - assertTrue(output.getBlock(0) instanceof DictionaryBlock); - assertEquals(output.getPositionCount(), entries / 2); + assertThat(output.getChannelCount()).isEqualTo(2); + assertThat(output.getBlock(0) instanceof DictionaryBlock).isTrue(); + assertThat(output.getPositionCount()).isEqualTo(entries / 2); for (int i = 0; i < entries / 2; i++) { - assertEquals(output.getBlock(0).getLong(i, 0), i * 2L); - assertEquals(output.getBlock(1).getLong(i, 0), i * 2L); + assertThat(output.getBlock(0).getLong(i, 0)).isEqualTo(i * 2L); + assertThat(output.getBlock(1).getLong(i, 0)).isEqualTo(i * 2L); } lookupJoinPageBuilder.reset(); @@ -130,12 +128,12 @@ public void testDifferentPositions() lookupJoinPageBuilder.appendRow(probe, lookupSource, joinPosition); } output = lookupJoinPageBuilder.build(probe); - assertEquals(output.getChannelCount(), 2); - assertFalse(output.getBlock(0) instanceof DictionaryBlock); - assertEquals(output.getPositionCount(), entries); + assertThat(output.getChannelCount()).isEqualTo(2); + assertThat(output.getBlock(0) instanceof DictionaryBlock).isFalse(); + assertThat(output.getPositionCount()).isEqualTo(entries); for (int i = 0; i < entries; i++) { - assertEquals(output.getBlock(0).getLong(i, 0), i); - assertEquals(output.getBlock(1).getLong(i, 0), i); + assertThat(output.getBlock(0).getLong(i, 0)).isEqualTo(i); + assertThat(output.getBlock(1).getLong(i, 0)).isEqualTo(i); } lookupJoinPageBuilder.reset(); @@ -148,12 +146,12 @@ public void testDifferentPositions() lookupJoinPageBuilder.appendRow(probe, lookupSource, joinPosition); } output = lookupJoinPageBuilder.build(probe); - assertEquals(output.getChannelCount(), 2); - assertFalse(output.getBlock(0) instanceof DictionaryBlock); - assertEquals(output.getPositionCount(), 40); + assertThat(output.getChannelCount()).isEqualTo(2); + assertThat(output.getBlock(0) instanceof DictionaryBlock).isFalse(); + assertThat(output.getPositionCount()).isEqualTo(40); for (int i = 10; i < 50; i++) { - assertEquals(output.getBlock(0).getLong(i - 10, 0), i); - assertEquals(output.getBlock(1).getLong(i - 10, 0), i); + assertThat(output.getBlock(0).getLong(i - 10, 0)).isEqualTo(i); + assertThat(output.getBlock(1).getLong(i - 10, 0)).isEqualTo(i); } } @@ -174,7 +172,7 @@ public void testCrossJoinWithEmptyBuild() for (int i = 0; i < 300_000; i++) { lookupJoinPageBuilder.appendRow(probe, lookupSource, 0); } - assertTrue(lookupJoinPageBuilder.isFull()); + assertThat(lookupJoinPageBuilder.isFull()).isTrue(); } private static final class TestLookupSource diff --git a/core/trino-main/src/test/java/io/trino/operator/output/TestPagePartitioner.java b/core/trino-main/src/test/java/io/trino/operator/output/TestPagePartitioner.java index f8205128dff9..444e02965633 100644 --- a/core/trino-main/src/test/java/io/trino/operator/output/TestPagePartitioner.java +++ b/core/trino-main/src/test/java/io/trino/operator/output/TestPagePartitioner.java @@ -98,7 +98,6 @@ import static java.util.concurrent.Executors.newScheduledThreadPool; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; @Test(singleThreaded = true) public class TestPagePartitioner @@ -384,7 +383,7 @@ public void testMemoryReleased(PartitioningMode partitioningMode) processPages(pagePartitioner, partitioningMode, page); - assertEquals(memoryContext.getBytes(), 0); + assertThat(memoryContext.getBytes()).isEqualTo(0); } @Test(dataProvider = "partitioningMode") @@ -399,7 +398,7 @@ public void testMemoryReleasedOnFailure(PartitioningMode partitioningMode) partitioningMode.partitionPage(pagePartitioner, page); assertThatThrownBy(pagePartitioner::close).isEqualTo(exception); - assertEquals(memoryContext.getBytes(), 0); + assertThat(memoryContext.getBytes()).isEqualTo(0); } private void testOutputEqualsInput(Type type, PartitioningMode mode1, PartitioningMode mode2) diff --git a/core/trino-main/src/test/java/io/trino/operator/output/TestPagePartitionerPool.java b/core/trino-main/src/test/java/io/trino/operator/output/TestPagePartitionerPool.java index 4c3c542cf60e..1a9878780769 100644 --- a/core/trino-main/src/test/java/io/trino/operator/output/TestPagePartitionerPool.java +++ b/core/trino-main/src/test/java/io/trino/operator/output/TestPagePartitionerPool.java @@ -62,7 +62,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -93,51 +92,51 @@ public void testBuffersReusedAcrossSplits() AggregatedMemoryContext memoryContext = newSimpleAggregatedMemoryContext(); PartitionedOutputOperatorFactory factory = createFactory(maxPagePartitioningBufferSize, outputBuffer, memoryContext); - assertEquals(memoryContext.getBytes(), 0); + assertThat(memoryContext.getBytes()).isEqualTo(0); // first split, too small for a flush long initialRetainedBytesOneOperator = processSplitsConcurrently(factory, memoryContext, split); - assertEquals(outputBuffer.totalEnqueuedPageCount(), 0); + assertThat(outputBuffer.totalEnqueuedPageCount()).isEqualTo(0); assertThat(memoryContext.getBytes()).isGreaterThanOrEqualTo(initialRetainedBytesOneOperator + split.getSizeInBytes()); // second split makes the split partitioner buffer full so the split is flushed processSplitsConcurrently(factory, memoryContext, split); - assertEquals(outputBuffer.totalEnqueuedPageCount(), 1); - assertEquals(memoryContext.getBytes(), initialRetainedBytesOneOperator); + assertThat(outputBuffer.totalEnqueuedPageCount()).isEqualTo(1); + assertThat(memoryContext.getBytes()).isEqualTo(initialRetainedBytesOneOperator); // two splits are processed at once so the use different buffers and do not flush for single split per buffer long initialRetainedBytesTwoOperators = processSplitsConcurrently(factory, memoryContext, split, split); - assertEquals(outputBuffer.totalEnqueuedPageCount(), 1); + assertThat(outputBuffer.totalEnqueuedPageCount()).isEqualTo(1); assertThat(memoryContext.getBytes()).isGreaterThanOrEqualTo(initialRetainedBytesTwoOperators + 2 * split.getSizeInBytes()); // another pair of splits should flush both buffers processSplitsConcurrently(factory, memoryContext, split, split); - assertEquals(outputBuffer.totalEnqueuedPageCount(), 3); - assertEquals(memoryContext.getBytes(), initialRetainedBytesTwoOperators); + assertThat(outputBuffer.totalEnqueuedPageCount()).isEqualTo(3); + assertThat(memoryContext.getBytes()).isEqualTo(initialRetainedBytesTwoOperators); // max free buffers is set to 2 so 2 buffers are going to be retained but 2 will be flushed and released processSplitsConcurrently(factory, memoryContext, split, split, split, split); - assertEquals(outputBuffer.totalEnqueuedPageCount(), 5); + assertThat(outputBuffer.totalEnqueuedPageCount()).isEqualTo(5); assertThat(memoryContext.getBytes()).isGreaterThanOrEqualTo(initialRetainedBytesTwoOperators + 2 * split.getSizeInBytes()); // another pair of splits should flush remaining buffers processSplitsConcurrently(factory, memoryContext, split, split); - assertEquals(outputBuffer.totalEnqueuedPageCount(), 7); - assertEquals(memoryContext.getBytes(), initialRetainedBytesTwoOperators); + assertThat(outputBuffer.totalEnqueuedPageCount()).isEqualTo(7); + assertThat(memoryContext.getBytes()).isEqualTo(initialRetainedBytesTwoOperators); // noMoreOperators forces buffers to be flushed even though they are not full processSplitsConcurrently(factory, memoryContext, split); assertThat(memoryContext.getBytes()).isGreaterThanOrEqualTo(initialRetainedBytesTwoOperators + split.getSizeInBytes()); Operator operator = factory.createOperator(driverContext()); factory.noMoreOperators(); - assertEquals(outputBuffer.totalEnqueuedPageCount(), 8); - assertEquals(memoryContext.getBytes(), initialRetainedBytesOneOperator); + assertThat(outputBuffer.totalEnqueuedPageCount()).isEqualTo(8); + assertThat(memoryContext.getBytes()).isEqualTo(initialRetainedBytesOneOperator); // noMoreOperators was called already so new split are flushed even though they are not full operator.addInput(split); operator.finish(); - assertEquals(outputBuffer.totalEnqueuedPageCount(), 9); + assertThat(outputBuffer.totalEnqueuedPageCount()).isEqualTo(9); // pool is closed, all operators are finished/flushed, the retained memory should be 0 - assertEquals(memoryContext.getBytes(), 0); + assertThat(memoryContext.getBytes()).isEqualTo(0); } @Test @@ -161,7 +160,7 @@ public void enqueue(int partition, List pages) assertThat(memoryContext.getBytes()).isGreaterThanOrEqualTo(initialRetainedBytesOneOperator + split.getSizeInBytes()); assertThatThrownBy(factory::noMoreOperators).isEqualTo(exception); - assertEquals(memoryContext.getBytes(), 0); + assertThat(memoryContext.getBytes()).isEqualTo(0); } private static PartitionedOutputOperatorFactory createFactory(DataSize maxPagePartitioningBufferSize, OutputBufferMock outputBuffer, AggregatedMemoryContext memoryContext) diff --git a/core/trino-main/src/test/java/io/trino/operator/output/TestPartitionedOutputOperator.java b/core/trino-main/src/test/java/io/trino/operator/output/TestPartitionedOutputOperator.java index bc29137f38c1..a8adb0ddbab2 100644 --- a/core/trino-main/src/test/java/io/trino/operator/output/TestPartitionedOutputOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/output/TestPartitionedOutputOperator.java @@ -31,9 +31,9 @@ import static io.trino.spi.type.BigintType.BIGINT; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -68,7 +68,7 @@ public void testOperatorContextStats() partitionedOutputOperator.addInput(page); OperatorContext operatorContext = partitionedOutputOperator.getOperatorContext(); - assertEquals(operatorContext.getOutputDataSize().getTotalCount(), page.getSizeInBytes()); - assertEquals(operatorContext.getOutputPositions().getTotalCount(), page.getPositionCount()); + assertThat(operatorContext.getOutputDataSize().getTotalCount()).isEqualTo(page.getSizeInBytes()); + assertThat(operatorContext.getOutputPositions().getTotalCount()).isEqualTo(page.getPositionCount()); } } diff --git a/core/trino-main/src/test/java/io/trino/operator/output/TestPositionsAppender.java b/core/trino-main/src/test/java/io/trino/operator/output/TestPositionsAppender.java index 01953f0ff5d2..a41bd26a5334 100644 --- a/core/trino-main/src/test/java/io/trino/operator/output/TestPositionsAppender.java +++ b/core/trino-main/src/test/java/io/trino/operator/output/TestPositionsAppender.java @@ -77,9 +77,7 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; import static java.util.Objects.requireNonNull; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestPositionsAppender { @@ -190,7 +188,7 @@ public void testMultipleRleWithTheSameValueProduceRle(TestType type) positionsAppender.append(allPositions(2), rleBlock(value, 2)); Block actual = positionsAppender.build(); - assertEquals(actual.getPositionCount(), 5); + assertThat(actual.getPositionCount()).isEqualTo(5); assertInstanceOf(actual, RunLengthEncodedBlock.class); } @@ -204,7 +202,7 @@ public void testRleAppendForComplexTypeWithNullElement(TestType type, Block valu positionsAppender.append(allPositions(2), rleBlock(value, 2)); Block actual = positionsAppender.build(); - assertEquals(actual.getPositionCount(), 5); + assertThat(actual.getPositionCount()).isEqualTo(5); assertInstanceOf(actual, RunLengthEncodedBlock.class); assertBlockEquals(type.getType(), actual, RunLengthEncodedBlock.create(value, 5)); } @@ -220,8 +218,10 @@ public void testRleAppendedWithSinglePositionDoesNotProduceRle(TestType type) positionsAppender.append(0, rleBlock(value, 2)); Block actual = positionsAppender.build(); - assertEquals(actual.getPositionCount(), 6); - assertFalse(actual instanceof RunLengthEncodedBlock, actual.getClass().getSimpleName()); + assertThat(actual.getPositionCount()).isEqualTo(6); + assertThat(actual instanceof RunLengthEncodedBlock) + .describedAs(actual.getClass().getSimpleName()) + .isFalse(); } @Test(dataProvider = "types") @@ -241,9 +241,9 @@ private static void testMultipleTheSameDictionariesProduceDictionary(TestType ty positionsAppender.append(allPositions(2), createRandomDictionaryBlock(dictionary, 2)); Block actual = positionsAppender.build(); - assertEquals(actual.getPositionCount(), 5); + assertThat(actual.getPositionCount()).isEqualTo(5); assertInstanceOf(actual, DictionaryBlock.class); - assertEquals(((DictionaryBlock) actual).getDictionary(), dictionary); + assertThat(((DictionaryBlock) actual).getDictionary()).isEqualTo(dictionary); } @Test(dataProvider = "types") @@ -294,15 +294,15 @@ public void testConsecutiveBuilds(TestType type) // empty block positionsAppender.append(positions(), emptyBlock(type)); - assertEquals(positionsAppender.build().getPositionCount(), 0); + assertThat(positionsAppender.build().getPositionCount()).isEqualTo(0); Block block = createRandomBlockForType(type, 2, 0.5f); // append only null position int nullPosition = block.isNull(0) ? 0 : 1; positionsAppender.append(positions(nullPosition), block); Block actualNullBlock = positionsAppender.build(); - assertEquals(actualNullBlock.getPositionCount(), 1); - assertTrue(actualNullBlock.isNull(0)); + assertThat(actualNullBlock.getPositionCount()).isEqualTo(1); + assertThat(actualNullBlock.isNull(0)).isTrue(); // append null and not null position positionsAppender.append(allPositions(2), block); @@ -324,7 +324,7 @@ public void testConsecutiveBuilds(TestType type) assertBlockEquals(type.getType(), positionsAppender.build(), dictionaryBlock); // just build to confirm appender was reset - assertEquals(positionsAppender.build().getPositionCount(), 0); + assertThat(positionsAppender.build().getPositionCount()).isEqualTo(0); } // testcase for jit bug described https://github.com/trinodb/trino/issues/12821. @@ -489,8 +489,8 @@ private static void testNullRle(Type type, Block source) positionsAppender.append(positions, source); positionsAppender.append(positions, source); Block actual = positionsAppender.build(); - assertTrue(actual.isNull(0)); - assertEquals(actual.getPositionCount(), positions.size() * 2); + assertThat(actual.isNull(0)).isTrue(); + assertThat(actual.getPositionCount()).isEqualTo(positions.size() * 2); assertInstanceOf(actual, RunLengthEncodedBlock.class); } @@ -517,10 +517,10 @@ private static void assertBuildResult(TestType type, List inputs, Unn assertBlockIsValid(actual, sizeInBytes, type.getType(), inputs); // verify positionsAppender reset - assertEquals(positionsAppender.getSizeInBytes(), 0); - assertEquals(positionsAppender.getRetainedSizeInBytes(), initialRetainedSize); + assertThat(positionsAppender.getSizeInBytes()).isEqualTo(0); + assertThat(positionsAppender.getRetainedSizeInBytes()).isEqualTo(initialRetainedSize); Block secondBlock = positionsAppender.build(); - assertEquals(secondBlock.getPositionCount(), 0); + assertThat(secondBlock.getPositionCount()).isEqualTo(0); } private static void testAppendSingle(TestType type, List inputs) @@ -535,10 +535,10 @@ private static void testAppendSingle(TestType type, List inputs) assertBlockIsValid(actual, sizeInBytes, type.getType(), inputs); // verify positionsAppender reset - assertEquals(positionsAppender.getSizeInBytes(), 0); - assertEquals(positionsAppender.getRetainedSizeInBytes(), initialRetainedSize); + assertThat(positionsAppender.getSizeInBytes()).isEqualTo(0); + assertThat(positionsAppender.getRetainedSizeInBytes()).isEqualTo(initialRetainedSize); Block secondBlock = positionsAppender.build(); - assertEquals(secondBlock.getPositionCount(), 0); + assertThat(secondBlock.getPositionCount()).isEqualTo(0); } private static void assertBlockIsValid(Block actual, long sizeInBytes, Type type, List inputs) @@ -548,7 +548,7 @@ private static void assertBlockIsValid(Block actual, long sizeInBytes, Type type Block expected = buildBlock(type, inputs, blockBuilderStatus); assertBlockEquals(type, actual, expected); - assertEquals(sizeInBytes, pageBuilderStatus.getSizeInBytes()); + assertThat(sizeInBytes).isEqualTo(pageBuilderStatus.getSizeInBytes()); } private static Block buildBlock(Type type, List inputs, BlockBuilderStatus blockBuilderStatus) diff --git a/core/trino-main/src/test/java/io/trino/operator/project/TestDictionaryAwarePageFilter.java b/core/trino-main/src/test/java/io/trino/operator/project/TestDictionaryAwarePageFilter.java index ef81dc5d9489..cf4a1e168fb4 100644 --- a/core/trino-main/src/test/java/io/trino/operator/project/TestDictionaryAwarePageFilter.java +++ b/core/trino-main/src/test/java/io/trino/operator/project/TestDictionaryAwarePageFilter.java @@ -32,8 +32,8 @@ import static io.airlift.testing.Assertions.assertInstanceOf; import static io.trino.block.BlockAssertions.createLongSequenceBlock; import static io.trino.block.BlockAssertions.createLongsBlock; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestDictionaryAwarePageFilter { @@ -41,8 +41,8 @@ public class TestDictionaryAwarePageFilter public void testDelegateMethods() { DictionaryAwarePageFilter filter = new DictionaryAwarePageFilter(new TestDictionaryFilter(true)); - assertEquals(filter.isDeterministic(), true); - assertEquals(filter.getInputChannels().getInputChannels(), ImmutableList.of(3)); + assertThat(filter.isDeterministic()).isEqualTo(true); + assertThat(filter.getInputChannels().getInputChannels()).isEqualTo(ImmutableList.of(3)); } @Test @@ -200,7 +200,7 @@ private static void testFilter(DictionaryAwarePageFilter filter, Block block, bo expectedSelectedPositions.add(position); } } - assertEquals(actualSelectedPositions, expectedSelectedPositions); + assertThat(actualSelectedPositions).isEqualTo(expectedSelectedPositions); } private static IntSet toSet(SelectedPositions selectedPositions) @@ -274,7 +274,7 @@ public InputChannels getInputChannels() @Override public SelectedPositions filter(ConnectorSession session, Page page) { - assertEquals(page.getChannelCount(), 1); + assertThat(page.getChannelCount()).isEqualTo(1); Block block = page.getBlock(0); boolean sequential = true; diff --git a/core/trino-main/src/test/java/io/trino/operator/project/TestDictionaryAwarePageProjection.java b/core/trino-main/src/test/java/io/trino/operator/project/TestDictionaryAwarePageProjection.java index 293fa9fcb078..bdcc3daca7a7 100644 --- a/core/trino-main/src/test/java/io/trino/operator/project/TestDictionaryAwarePageProjection.java +++ b/core/trino-main/src/test/java/io/trino/operator/project/TestDictionaryAwarePageProjection.java @@ -41,15 +41,9 @@ import static io.trino.spi.block.DictionaryId.randomDictionaryId; import static io.trino.spi.type.BigintType.BIGINT; import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNotSame; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertSame; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Fail.fail; public class TestDictionaryAwarePageProjection { @@ -74,9 +68,9 @@ public void tearDown() public void testDelegateMethods() { DictionaryAwarePageProjection projection = createProjection(false); - assertEquals(projection.isDeterministic(), true); - assertEquals(projection.getInputChannels().getInputChannels(), ImmutableList.of(3)); - assertEquals(projection.getType(), BIGINT); + assertThat(projection.isDeterministic()).isEqualTo(true); + assertThat(projection.getInputChannels().getInputChannels()).isEqualTo(ImmutableList.of(3)); + assertThat(projection.getType()).isEqualTo(BIGINT); } @Test(dataProvider = "forceYield") @@ -192,21 +186,21 @@ public void testPreservesDictionaryInstance() DriverYieldSignal yieldSignal = new DriverYieldSignal(); Work firstWork = projection.project(null, yieldSignal, new Page(firstDictionaryBlock), SelectedPositions.positionsList(new int[] {0, 1}, 0, 2)); - assertTrue(firstWork.process()); + assertThat(firstWork.process()).isTrue(); Block firstOutputBlock = firstWork.getResult(); assertInstanceOf(firstOutputBlock, DictionaryBlock.class); Work secondWork = projection.project(null, yieldSignal, new Page(secondDictionaryBlock), SelectedPositions.positionsList(new int[] {0, 1}, 0, 2)); - assertTrue(secondWork.process()); + assertThat(secondWork.process()).isTrue(); Block secondOutputBlock = secondWork.getResult(); assertInstanceOf(secondOutputBlock, DictionaryBlock.class); - assertNotSame(firstOutputBlock, secondOutputBlock); + assertThat(firstOutputBlock).isNotSameAs(secondOutputBlock); Block firstDictionary = ((DictionaryBlock) firstOutputBlock).getDictionary(); Block secondDictionary = ((DictionaryBlock) secondOutputBlock).getDictionary(); - assertSame(firstDictionary, secondDictionary); - assertSame(firstDictionary, dictionary); + assertThat(firstDictionary).isSameAs(secondDictionary); + assertThat(firstDictionary).isSameAs(dictionary); } private static Block createDictionaryBlock(int dictionarySize, int blockSize) @@ -288,14 +282,14 @@ private static void testProjectRange(Block block, Class expecte result = projectWithYield(work, yieldSignal); } else { - assertTrue(work.process()); + assertThat(work.process()).isTrue(); result = work.getResult(); } if (produceLazyBlock) { assertInstanceOf(result, LazyBlock.class); - assertFalse(result.isLoaded()); - assertFalse(block.isLoaded()); + assertThat(result.isLoaded()).isFalse(); + assertThat(block.isLoaded()).isFalse(); result = result.getLoadedBlock(); } @@ -320,14 +314,14 @@ private static void testProjectList(Block block, Class expected result = projectWithYield(work, yieldSignal); } else { - assertTrue(work.process()); + assertThat(work.process()).isTrue(); result = work.getResult(); } if (produceLazyBlock) { assertInstanceOf(result, LazyBlock.class); - assertFalse(result.isLoaded()); - assertFalse(block.isLoaded()); + assertThat(result.isLoaded()).isFalse(); + assertThat(block.isLoaded()).isFalse(); result = result.getLoadedBlock(); } @@ -350,14 +344,14 @@ private static void testProjectFastReturnIgnoreYield(Block block, DictionaryAwar yieldSignal.forceYieldForTesting(); // yield signal is ignored given the block has already been loaded - assertTrue(work.process()); + assertThat(work.process()).isTrue(); Block result = work.getResult(); yieldSignal.reset(); if (produceLazyBlock) { assertInstanceOf(result, LazyBlock.class); - assertFalse(result.isLoaded()); - assertFalse(block.isLoaded()); + assertThat(result.isLoaded()).isFalse(); + assertThat(block.isLoaded()).isFalse(); result = result.getLoadedBlock(); } @@ -430,7 +424,7 @@ public TestPageProjectionWork(DriverYieldSignal yieldSignal, Page page, Selected @Override public boolean process() { - assertNull(result); + assertThat(result).isNull(); if (selectedPositions.isList()) { int offset = selectedPositions.getOffset(); int[] positions = selectedPositions.getPositions(); @@ -460,7 +454,7 @@ public boolean process() @Override public Block getResult() { - assertNotNull(result); + assertThat(result).isNotNull(); return result; } } diff --git a/core/trino-main/src/test/java/io/trino/operator/project/TestInputPageProjection.java b/core/trino-main/src/test/java/io/trino/operator/project/TestInputPageProjection.java index c3dac9685829..bd73fcedd349 100644 --- a/core/trino-main/src/test/java/io/trino/operator/project/TestInputPageProjection.java +++ b/core/trino-main/src/test/java/io/trino/operator/project/TestInputPageProjection.java @@ -22,8 +22,7 @@ import static io.trino.block.BlockAssertions.createLongSequenceBlock; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.testing.TestingConnectorSession.SESSION; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestInputPageProjection { @@ -33,12 +32,12 @@ public void testLazyInputPage() InputPageProjection projection = new InputPageProjection(0, BIGINT); Block block = createLongSequenceBlock(0, 100); Block result = projection.project(SESSION, new DriverYieldSignal(), new Page(block), SelectedPositions.positionsRange(0, 100)).getResult(); - assertFalse(result instanceof LazyBlock); + assertThat(result instanceof LazyBlock).isFalse(); block = lazyWrapper(block); result = projection.project(SESSION, new DriverYieldSignal(), new Page(block), SelectedPositions.positionsRange(0, 100)).getResult(); - assertTrue(result instanceof LazyBlock); - assertFalse(result.isLoaded()); + assertThat(result instanceof LazyBlock).isTrue(); + assertThat(result.isLoaded()).isFalse(); } private static LazyBlock lazyWrapper(Block block) diff --git a/core/trino-main/src/test/java/io/trino/operator/project/TestMergePages.java b/core/trino-main/src/test/java/io/trino/operator/project/TestMergePages.java index 033bbaf8e4ed..a37a5961c6c8 100644 --- a/core/trino-main/src/test/java/io/trino/operator/project/TestMergePages.java +++ b/core/trino-main/src/test/java/io/trino/operator/project/TestMergePages.java @@ -34,8 +34,7 @@ import static io.trino.spi.type.DoubleType.DOUBLE; import static io.trino.spi.type.RealType.REAL; import static java.lang.Math.toIntExact; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestMergePages { @@ -93,13 +92,13 @@ public void testMinRowCountThresholdWithLazyPages() pagesSource(page), newSimpleAggregatedMemoryContext()); - assertTrue(mergePages.process()); - assertFalse(mergePages.isFinished()); + assertThat(mergePages.process()).isTrue(); + assertThat(mergePages.isFinished()).isFalse(); Page result = mergePages.getResult(); - assertFalse(channel1.isLoaded()); - assertFalse(channel2.isLoaded()); - assertFalse(channel3.isLoaded()); + assertThat(channel1.isLoaded()).isFalse(); + assertThat(channel2.isLoaded()).isFalse(); + assertThat(channel3.isLoaded()).isFalse(); assertPageEquals(TYPES, result, page); } diff --git a/core/trino-main/src/test/java/io/trino/operator/project/TestPageProcessor.java b/core/trino-main/src/test/java/io/trino/operator/project/TestPageProcessor.java index 9a814cee30e9..f15aabd905d8 100644 --- a/core/trino-main/src/test/java/io/trino/operator/project/TestPageProcessor.java +++ b/core/trino-main/src/test/java/io/trino/operator/project/TestPageProcessor.java @@ -72,13 +72,9 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -102,10 +98,10 @@ public void testProjectNoColumns() Iterator> output = processAndAssertRetainedPageSize(pageProcessor, inputPage); List> outputPages = ImmutableList.copyOf(output); - assertEquals(outputPages.size(), 1); + assertThat(outputPages.size()).isEqualTo(1); Page outputPage = outputPages.get(0).orElse(null); - assertEquals(outputPage.getChannelCount(), 0); - assertEquals(outputPage.getPositionCount(), inputPage.getPositionCount()); + assertThat(outputPage.getChannelCount()).isEqualTo(0); + assertThat(outputPage.getPositionCount()).isEqualTo(inputPage.getPositionCount()); } @Test @@ -117,13 +113,13 @@ public void testFilterNoColumns() LocalMemoryContext memoryContext = newSimpleAggregatedMemoryContext().newLocalMemoryContext(PageProcessor.class.getSimpleName()); Iterator> output = pageProcessor.process(SESSION, new DriverYieldSignal(), memoryContext, inputPage); - assertEquals(memoryContext.getBytes(), 0); + assertThat(memoryContext.getBytes()).isEqualTo(0); List> outputPages = ImmutableList.copyOf(output); - assertEquals(outputPages.size(), 1); + assertThat(outputPages.size()).isEqualTo(1); Page outputPage = outputPages.get(0).orElse(null); - assertEquals(outputPage.getChannelCount(), 0); - assertEquals(outputPage.getPositionCount(), 50); + assertThat(outputPage.getChannelCount()).isEqualTo(0); + assertThat(outputPage.getPositionCount()).isEqualTo(50); } @Test @@ -139,7 +135,7 @@ public void testPartialFilter() Iterator> output = processAndAssertRetainedPageSize(pageProcessor, inputPage); List> outputPages = ImmutableList.copyOf(output); - assertEquals(outputPages.size(), 1); + assertThat(outputPages.size()).isEqualTo(1); assertPageEquals(ImmutableList.of(BIGINT), outputPages.get(0).orElse(null), new Page(createLongSequenceBlock(25, 75))); } @@ -153,7 +149,7 @@ public void testSelectAllFilter() Iterator> output = processAndAssertRetainedPageSize(pageProcessor, inputPage); List> outputPages = ImmutableList.copyOf(output); - assertEquals(outputPages.size(), 1); + assertThat(outputPages.size()).isEqualTo(1); assertPageEquals(ImmutableList.of(BIGINT), outputPages.get(0).orElse(null), new Page(createLongSequenceBlock(0, 100))); } @@ -166,10 +162,10 @@ public void testSelectNoneFilter() LocalMemoryContext memoryContext = newSimpleAggregatedMemoryContext().newLocalMemoryContext(PageProcessor.class.getSimpleName()); Iterator> output = pageProcessor.process(SESSION, new DriverYieldSignal(), memoryContext, inputPage); - assertEquals(memoryContext.getBytes(), 0); + assertThat(memoryContext.getBytes()).isEqualTo(0); List> outputPages = ImmutableList.copyOf(output); - assertEquals(outputPages.size(), 0); + assertThat(outputPages.size()).isEqualTo(0); } @Test @@ -181,11 +177,11 @@ public void testProjectEmptyPage() LocalMemoryContext memoryContext = newSimpleAggregatedMemoryContext().newLocalMemoryContext(PageProcessor.class.getSimpleName()); Iterator> output = pageProcessor.process(SESSION, new DriverYieldSignal(), memoryContext, inputPage); - assertEquals(memoryContext.getBytes(), 0); + assertThat(memoryContext.getBytes()).isEqualTo(0); // output should be one page containing no columns (only a count) List> outputPages = ImmutableList.copyOf(output); - assertEquals(outputPages.size(), 0); + assertThat(outputPages.size()).isEqualTo(0); } @Test @@ -200,9 +196,9 @@ public void testSelectNoneFilterLazyLoad() LocalMemoryContext memoryContext = newSimpleAggregatedMemoryContext().newLocalMemoryContext(PageProcessor.class.getSimpleName()); Iterator> output = pageProcessor.process(SESSION, new DriverYieldSignal(), memoryContext, inputPage); - assertEquals(memoryContext.getBytes(), 0); + assertThat(memoryContext.getBytes()).isEqualTo(0); List> outputPages = ImmutableList.copyOf(output); - assertEquals(outputPages.size(), 0); + assertThat(outputPages.size()).isEqualTo(0); } @Test @@ -219,7 +215,7 @@ public void testProjectLazyLoad() Iterator> output = pageProcessor.process(SESSION, new DriverYieldSignal(), memoryContext, inputPage); List> outputPages = ImmutableList.copyOf(output); - assertEquals(outputPages.size(), 1); + assertThat(outputPages.size()).isEqualTo(1); assertPageEquals(ImmutableList.of(BIGINT), outputPages.get(0).orElse(null), new Page(createLongSequenceBlock(0, 100))); } @@ -233,7 +229,7 @@ public void testBatchedOutput() Iterator> output = processAndAssertRetainedPageSize(pageProcessor, inputPage); List> outputPages = ImmutableList.copyOf(output); - assertEquals(outputPages.size(), 3); + assertThat(outputPages.size()).isEqualTo(3); for (int i = 0; i < outputPages.size(); i++) { Page actualPage = outputPages.get(i).orElse(null); int offset = i * MAX_BATCH_SIZE; @@ -316,11 +312,11 @@ public void testOptimisticProcessing() } } // second project is invoked once per output page - assertEquals(secondProjection.getInvocationCount(), pageCount); + assertThat(secondProjection.getInvocationCount()).isEqualTo(pageCount); // the page processor saves the results when the page size is exceeded, so the first projection // will be invoked less times - assertTrue(firstProjection.getInvocationCount() < secondProjection.getInvocationCount()); + assertThat(firstProjection.getInvocationCount() < secondProjection.getInvocationCount()).isTrue(); } @Test @@ -342,11 +338,11 @@ public void testRetainedSize() // force a compute // one block of previouslyComputedResults will be saved given the first column is with 8MB - assertTrue(output.hasNext()); + assertThat(output.hasNext()).isTrue(); // verify we do not count block sizes twice // comparing with the input page, the output page also contains an extra instance size for previouslyComputedResults - assertEquals(memoryContext.getBytes() - instanceSize(VariableWidthBlock.class), inputPage.getRetainedSizeInBytes()); + assertThat(memoryContext.getBytes() - instanceSize(VariableWidthBlock.class)).isEqualTo(inputPage.getRetainedSizeInBytes()); } @Test @@ -374,22 +370,22 @@ public void testYieldProjection() // So we would like to set yield signal when the column has just finished processing in order to let page processor capture the yield signal when the block is returned. // Also, we would like to reset the yield signal before starting to process the next column in order NOT to yield per position inside the column. for (int i = 0; i < columns - 1; i++) { - assertTrue(output.hasNext()); - assertNull(output.next().orElse(null)); - assertTrue(yieldSignal.isSet()); + assertThat(output.hasNext()).isTrue(); + assertThat(output.next().orElse(null)).isNull(); + assertThat(yieldSignal.isSet()).isTrue(); yieldSignal.reset(); } - assertTrue(output.hasNext()); + assertThat(output.hasNext()).isTrue(); Page actualPage = output.next().orElse(null); - assertNotNull(actualPage); - assertTrue(yieldSignal.isSet()); + assertThat(actualPage).isNotNull(); + assertThat(yieldSignal.isSet()).isTrue(); yieldSignal.reset(); Block[] blocks = new Block[columns]; Arrays.fill(blocks, createSlicesBlock(Arrays.copyOfRange(slices, 0, rows))); Page expectedPage = new Page(blocks); assertPageEquals(Collections.nCopies(columns, VARCHAR), actualPage, expectedPage); - assertFalse(output.hasNext()); + assertThat(output.hasNext()).isFalse(); } @Test @@ -414,12 +410,12 @@ public void testExpressionProfiler() // increment the ticker with a large value to mark the expression as expensive testingTicker.increment(10, SECONDS); profiler.stop(page.getPositionCount()); - assertTrue(profiler.isExpressionExpensive()); + assertThat(profiler.isExpressionExpensive()).isTrue(); } else { testingTicker.increment(0, NANOSECONDS); profiler.stop(page.getPositionCount()); - assertFalse(profiler.isExpressionExpensive()); + assertThat(profiler.isExpressionExpensive()).isFalse(); } work.process(); } @@ -448,12 +444,12 @@ public void testIncreasingBatchSize() long totalPositionCount = 0; while (totalPositionCount < rows) { Optional page = output.next(); - assertTrue(page.isPresent()); + assertThat(page.isPresent()).isTrue(); long positionCount = page.get().getPositionCount(); totalPositionCount += positionCount; // skip the first read && skip the last read, which can be a partial page if (positionCount > 1 && totalPositionCount != rows) { - assertEquals(positionCount, previousPositionCount * 2); + assertThat(positionCount).isEqualTo(previousPositionCount * 2); } previousPositionCount = positionCount; } @@ -482,12 +478,12 @@ public void testDecreasingBatchSize() long totalPositionCount = 0; while (totalPositionCount < rows) { Optional page = output.next(); - assertTrue(page.isPresent()); + assertThat(page.isPresent()).isTrue(); long positionCount = page.get().getPositionCount(); totalPositionCount += positionCount; // the batch size doesn't get smaller than 1 if (positionCount > 1 && previousPositionCount != 1) { - assertEquals(positionCount, previousPositionCount / 2); + assertThat(positionCount).isEqualTo(previousPositionCount / 2); } previousPositionCount = positionCount; } @@ -510,7 +506,7 @@ private Iterator> processAndAssertRetainedPageSize(PageProcessor yieldSignal, memoryContext.newLocalMemoryContext(PageProcessor.class.getSimpleName()), inputPage); - assertEquals(memoryContext.getBytes(), 0); + assertThat(memoryContext.getBytes()).isEqualTo(0); return output; } @@ -595,7 +591,7 @@ public YieldPageProjectionWork(ConnectorSession session, DriverYieldSignal yield @Override public boolean process() { - assertTrue(work.process()); + assertThat(work.process()).isTrue(); yieldSignal.setWithDelay(1, executor); yieldSignal.forceYieldForTesting(); return true; diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestBlockAndPositionNullConvention.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestBlockAndPositionNullConvention.java index 367c1fbdb67e..37dfe0175a3a 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestBlockAndPositionNullConvention.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestBlockAndPositionNullConvention.java @@ -40,7 +40,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -70,20 +69,20 @@ public void testBlockPosition() { assertThat(assertions.function("test_block_position", "BIGINT '1234'")) .isEqualTo(1234L); - assertTrue(FunctionWithBlockAndPositionConvention.hitBlockPositionBigint.get()); + assertThat(FunctionWithBlockAndPositionConvention.hitBlockPositionBigint.get()).isTrue(); assertThat(assertions.function("test_block_position", "12.34e0")) .isEqualTo(12.34); - assertTrue(FunctionWithBlockAndPositionConvention.hitBlockPositionDouble.get()); + assertThat(FunctionWithBlockAndPositionConvention.hitBlockPositionDouble.get()).isTrue(); assertThat(assertions.function("test_block_position", "'hello'")) .hasType(createVarcharType(5)) .isEqualTo("hello"); - assertTrue(FunctionWithBlockAndPositionConvention.hitBlockPositionSlice.get()); + assertThat(FunctionWithBlockAndPositionConvention.hitBlockPositionSlice.get()).isTrue(); assertThat(assertions.function("test_block_position", "true")) .isEqualTo(true); - assertTrue(FunctionWithBlockAndPositionConvention.hitBlockPositionBoolean.get()); + assertThat(FunctionWithBlockAndPositionConvention.hitBlockPositionBoolean.get()).isTrue(); } @ScalarFunction("test_block_position") @@ -189,20 +188,20 @@ public void testValueBlockPosition() { assertThat(assertions.function("test_value_block_position", "BIGINT '1234'")) .isEqualTo(1234L); - assertTrue(FunctionWithValueBlockAndPositionConvention.hitBlockPositionBigint.get()); + assertThat(FunctionWithValueBlockAndPositionConvention.hitBlockPositionBigint.get()).isTrue(); assertThat(assertions.function("test_value_block_position", "12.34e0")) .isEqualTo(12.34); - assertTrue(FunctionWithValueBlockAndPositionConvention.hitBlockPositionDouble.get()); + assertThat(FunctionWithValueBlockAndPositionConvention.hitBlockPositionDouble.get()).isTrue(); assertThat(assertions.function("test_value_block_position", "'hello'")) .hasType(createVarcharType(5)) .isEqualTo("hello"); - assertTrue(FunctionWithValueBlockAndPositionConvention.hitBlockPositionSlice.get()); + assertThat(FunctionWithValueBlockAndPositionConvention.hitBlockPositionSlice.get()).isTrue(); assertThat(assertions.function("test_value_block_position", "true")) .isEqualTo(true); - assertTrue(FunctionWithValueBlockAndPositionConvention.hitBlockPositionBoolean.get()); + assertThat(FunctionWithValueBlockAndPositionConvention.hitBlockPositionBoolean.get()).isTrue(); } @ScalarFunction("test_value_block_position") diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestBlockSet.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestBlockSet.java index c7d92e4522ce..5dac85be23b2 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestBlockSet.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestBlockSet.java @@ -37,9 +37,6 @@ import static java.util.Collections.nCopies; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestBlockSet { @@ -75,10 +72,10 @@ public void testGetElementPosition() blockSet.add(block, i); } - assertEquals(blockSet.size(), elementCount); + assertThat(blockSet.size()).isEqualTo(elementCount); for (int j = 0; j < block.getPositionCount(); j++) { - assertEquals(blockSet.positionOf(block, j), j); + assertThat(blockSet.positionOf(block, j)).isEqualTo(j); } } @@ -104,17 +101,17 @@ public void testGetElementPositionWithNull() // The internal elementBlock and hashtable of the blockSet should contain // all distinct non-null elements plus one null - assertEquals(blockSet.size(), elementCount - elementCount / 10 + 1); + assertThat(blockSet.size()).isEqualTo(elementCount - elementCount / 10 + 1); int nullCount = 0; for (int j = 0; j < block.getPositionCount(); j++) { // The null is only added to blockSet once, so the internal elementBlock subscript is shifted by nullCountMinusOne if (!block.isNull(j)) { - assertEquals(blockSet.positionOf(block, j), j - nullCount + 1); + assertThat(blockSet.positionOf(block, j)).isEqualTo(j - nullCount + 1); } else { // The first null added to blockSet is at position 0 - assertEquals(blockSet.positionOf(block, j), 0); + assertThat(blockSet.positionOf(block, j)).isEqualTo(0); nullCount++; } } @@ -182,14 +179,14 @@ public void testGetElementPositionRandom() valuesBuilder.appendNull(); Block values = valuesBuilder.build(); - assertEquals(set.positionOf(values, 4), -1); - assertEquals(set.positionOf(values, 2), 0); - assertEquals(set.positionOf(values, 1), 2); - assertEquals(set.positionOf(values, 0), 1); - assertFalse(set.contains(values, 3)); + assertThat(set.positionOf(values, 4)).isEqualTo(-1); + assertThat(set.positionOf(values, 2)).isEqualTo(0); + assertThat(set.positionOf(values, 1)).isEqualTo(2); + assertThat(set.positionOf(values, 0)).isEqualTo(1); + assertThat(set.contains(values, 3)).isFalse(); set.add(values, 4); - assertTrue(set.contains(values, 4)); + assertThat(set.contains(values, 4)).isTrue(); } @Test @@ -215,14 +212,14 @@ private static void testBigint(Block longBlock) Set set = new HashSet<>(); for (int blockPosition = 0; blockPosition < longBlock.getPositionCount(); blockPosition++) { long number = BIGINT.getLong(longBlock, blockPosition); - assertEquals(blockSet.contains(longBlock, blockPosition), set.contains(number)); - assertEquals(blockSet.size(), set.size()); + assertThat(blockSet.contains(longBlock, blockPosition)).isEqualTo(set.contains(number)); + assertThat(blockSet.size()).isEqualTo(set.size()); set.add(number); blockSet.add(longBlock, blockPosition); - assertEquals(blockSet.contains(longBlock, blockPosition), set.contains(number)); - assertEquals(blockSet.size(), set.size()); + assertThat(blockSet.contains(longBlock, blockPosition)).isEqualTo(set.contains(number)); + assertThat(blockSet.size()).isEqualTo(set.size()); } } diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestColorFunctions.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestColorFunctions.java index bef069727cba..269187d50fd9 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestColorFunctions.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestColorFunctions.java @@ -28,129 +28,125 @@ import static io.trino.spi.function.OperatorType.INDETERMINATE; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestColorFunctions { @Test public void testParseRgb() { - assertEquals(parseRgb(utf8Slice("#000")), 0x00_00_00); - assertEquals(parseRgb(utf8Slice("#FFF")), 0xFF_FF_FF); - assertEquals(parseRgb(utf8Slice("#F00")), 0xFF_00_00); - assertEquals(parseRgb(utf8Slice("#0F0")), 0x00_FF_00); - assertEquals(parseRgb(utf8Slice("#00F")), 0x00_00_FF); - assertEquals(parseRgb(utf8Slice("#700")), 0x77_00_00); - assertEquals(parseRgb(utf8Slice("#070")), 0x00_77_00); - assertEquals(parseRgb(utf8Slice("#007")), 0x00_00_77); - - assertEquals(parseRgb(utf8Slice("#cde")), 0xCC_DD_EE); + assertThat(parseRgb(utf8Slice("#000"))).isEqualTo(0x00_00_00); + assertThat(parseRgb(utf8Slice("#FFF"))).isEqualTo(0xFF_FF_FF); + assertThat(parseRgb(utf8Slice("#F00"))).isEqualTo(0xFF_00_00); + assertThat(parseRgb(utf8Slice("#0F0"))).isEqualTo(0x00_FF_00); + assertThat(parseRgb(utf8Slice("#00F"))).isEqualTo(0x00_00_FF); + assertThat(parseRgb(utf8Slice("#700"))).isEqualTo(0x77_00_00); + assertThat(parseRgb(utf8Slice("#070"))).isEqualTo(0x00_77_00); + assertThat(parseRgb(utf8Slice("#007"))).isEqualTo(0x00_00_77); + + assertThat(parseRgb(utf8Slice("#cde"))).isEqualTo(0xCC_DD_EE); } @Test public void testGetComponent() { - assertEquals(getRed(parseRgb(utf8Slice("#789"))), 0x77); - assertEquals(getGreen(parseRgb(utf8Slice("#789"))), 0x88); - assertEquals(getBlue(parseRgb(utf8Slice("#789"))), 0x99); + assertThat(getRed(parseRgb(utf8Slice("#789")))).isEqualTo(0x77); + assertThat(getGreen(parseRgb(utf8Slice("#789")))).isEqualTo(0x88); + assertThat(getBlue(parseRgb(utf8Slice("#789")))).isEqualTo(0x99); } @Test public void testToRgb() { - assertEquals(rgb(0xFF, 0, 0), 0xFF_00_00); - assertEquals(rgb(0, 0xFF, 0), 0x00_FF_00); - assertEquals(rgb(0, 0, 0xFF), 0x00_00_FF); + assertThat(rgb(0xFF, 0, 0)).isEqualTo(0xFF_00_00); + assertThat(rgb(0, 0xFF, 0)).isEqualTo(0x00_FF_00); + assertThat(rgb(0, 0, 0xFF)).isEqualTo(0x00_00_FF); } @Test public void testColor() { - assertEquals(color(utf8Slice("black")), -1); - assertEquals(color(utf8Slice("red")), -2); - assertEquals(color(utf8Slice("green")), -3); - assertEquals(color(utf8Slice("yellow")), -4); - assertEquals(color(utf8Slice("blue")), -5); - assertEquals(color(utf8Slice("magenta")), -6); - assertEquals(color(utf8Slice("cyan")), -7); - assertEquals(color(utf8Slice("white")), -8); - - assertEquals(color(utf8Slice("#f00")), 0xFF_00_00); - assertEquals(color(utf8Slice("#0f0")), 0x00_FF_00); - assertEquals(color(utf8Slice("#00f")), 0x00_00_FF); + assertThat(color(utf8Slice("black"))).isEqualTo(-1); + assertThat(color(utf8Slice("red"))).isEqualTo(-2); + assertThat(color(utf8Slice("green"))).isEqualTo(-3); + assertThat(color(utf8Slice("yellow"))).isEqualTo(-4); + assertThat(color(utf8Slice("blue"))).isEqualTo(-5); + assertThat(color(utf8Slice("magenta"))).isEqualTo(-6); + assertThat(color(utf8Slice("cyan"))).isEqualTo(-7); + assertThat(color(utf8Slice("white"))).isEqualTo(-8); + + assertThat(color(utf8Slice("#f00"))).isEqualTo(0xFF_00_00); + assertThat(color(utf8Slice("#0f0"))).isEqualTo(0x00_FF_00); + assertThat(color(utf8Slice("#00f"))).isEqualTo(0x00_00_FF); } @Test public void testBar() { - assertEquals(bar(0.6, 5, color(utf8Slice("#f0f")), color(utf8Slice("#00f"))), - utf8Slice("\u001B[38;5;201m\u2588\u001B[38;5;165m\u2588\u001B[38;5;129m\u2588\u001B[0m ")); + assertThat(bar(0.6, 5, color(utf8Slice("#f0f")), color(utf8Slice("#00f")))).isEqualTo(utf8Slice("\u001B[38;5;201m\u2588\u001B[38;5;165m\u2588\u001B[38;5;129m\u2588\u001B[0m ")); - assertEquals(bar(1, 10, color(utf8Slice("#f00")), color(utf8Slice("#0f0"))), - utf8Slice("\u001B[38;5;196m\u2588\u001B[38;5;202m\u2588\u001B[38;5;208m\u2588\u001B[38;5;214m\u2588\u001B[38;5;226m\u2588\u001B[38;5;226m\u2588\u001B[38;5;154m\u2588\u001B[38;5;118m\u2588\u001B[38;5;82m\u2588\u001B[38;5;46m\u2588\u001B[0m")); + assertThat(bar(1, 10, color(utf8Slice("#f00")), color(utf8Slice("#0f0")))).isEqualTo(utf8Slice("\u001B[38;5;196m\u2588\u001B[38;5;202m\u2588\u001B[38;5;208m\u2588\u001B[38;5;214m\u2588\u001B[38;5;226m\u2588\u001B[38;5;226m\u2588\u001B[38;5;154m\u2588\u001B[38;5;118m\u2588\u001B[38;5;82m\u2588\u001B[38;5;46m\u2588\u001B[0m")); - assertEquals(bar(0.6, 5, color(utf8Slice("#f0f")), color(utf8Slice("#00f"))), - utf8Slice("\u001B[38;5;201m\u2588\u001B[38;5;165m\u2588\u001B[38;5;129m\u2588\u001B[0m ")); + assertThat(bar(0.6, 5, color(utf8Slice("#f0f")), color(utf8Slice("#00f")))).isEqualTo(utf8Slice("\u001B[38;5;201m\u2588\u001B[38;5;165m\u2588\u001B[38;5;129m\u2588\u001B[0m ")); } @Test public void testRenderBoolean() { - assertEquals(render(true), utf8Slice("\u001b[38;5;2m✓\u001b[0m")); - assertEquals(render(false), utf8Slice("\u001b[38;5;1m✗\u001b[0m")); + assertThat(render(true)).isEqualTo(utf8Slice("\u001b[38;5;2m✓\u001b[0m")); + assertThat(render(false)).isEqualTo(utf8Slice("\u001b[38;5;1m✗\u001b[0m")); } @Test public void testRenderString() { - assertEquals(render(utf8Slice("hello"), color(utf8Slice("red"))), utf8Slice("\u001b[38;5;1mhello\u001b[0m")); + assertThat(render(utf8Slice("hello"), color(utf8Slice("red")))).isEqualTo(utf8Slice("\u001b[38;5;1mhello\u001b[0m")); - assertEquals(render(utf8Slice("hello"), color(utf8Slice("#f00"))), utf8Slice("\u001b[38;5;196mhello\u001b[0m")); - assertEquals(render(utf8Slice("hello"), color(utf8Slice("#0f0"))), utf8Slice("\u001b[38;5;46mhello\u001b[0m")); - assertEquals(render(utf8Slice("hello"), color(utf8Slice("#00f"))), utf8Slice("\u001b[38;5;21mhello\u001b[0m")); + assertThat(render(utf8Slice("hello"), color(utf8Slice("#f00")))).isEqualTo(utf8Slice("\u001b[38;5;196mhello\u001b[0m")); + assertThat(render(utf8Slice("hello"), color(utf8Slice("#0f0")))).isEqualTo(utf8Slice("\u001b[38;5;46mhello\u001b[0m")); + assertThat(render(utf8Slice("hello"), color(utf8Slice("#00f")))).isEqualTo(utf8Slice("\u001b[38;5;21mhello\u001b[0m")); } @Test public void testRenderLong() { - assertEquals(render(1234, color(utf8Slice("red"))), utf8Slice("\u001b[38;5;1m1234\u001b[0m")); + assertThat(render(1234, color(utf8Slice("red")))).isEqualTo(utf8Slice("\u001b[38;5;1m1234\u001b[0m")); - assertEquals(render(1234, color(utf8Slice("#f00"))), utf8Slice("\u001b[38;5;196m1234\u001b[0m")); - assertEquals(render(1234, color(utf8Slice("#0f0"))), utf8Slice("\u001b[38;5;46m1234\u001b[0m")); - assertEquals(render(1234, color(utf8Slice("#00f"))), utf8Slice("\u001b[38;5;21m1234\u001b[0m")); + assertThat(render(1234, color(utf8Slice("#f00")))).isEqualTo(utf8Slice("\u001b[38;5;196m1234\u001b[0m")); + assertThat(render(1234, color(utf8Slice("#0f0")))).isEqualTo(utf8Slice("\u001b[38;5;46m1234\u001b[0m")); + assertThat(render(1234, color(utf8Slice("#00f")))).isEqualTo(utf8Slice("\u001b[38;5;21m1234\u001b[0m")); } @Test public void testRenderDouble() { - assertEquals(render(1234.5678, color(utf8Slice("red"))), utf8Slice("\u001b[38;5;1m1234.5678\u001b[0m")); - assertEquals(render(1234.5678f, color(utf8Slice("red"))), utf8Slice(format("\u001b[38;5;1m%s\u001b[0m", (double) 1234.5678f))); + assertThat(render(1234.5678, color(utf8Slice("red")))).isEqualTo(utf8Slice("\u001b[38;5;1m1234.5678\u001b[0m")); + assertThat(render(1234.5678f, color(utf8Slice("red")))).isEqualTo(utf8Slice(format("\u001b[38;5;1m%s\u001b[0m", (double) 1234.5678f))); - assertEquals(render(1234.5678, color(utf8Slice("#f00"))), utf8Slice("\u001b[38;5;196m1234.5678\u001b[0m")); - assertEquals(render(1234.5678, color(utf8Slice("#0f0"))), utf8Slice("\u001b[38;5;46m1234.5678\u001b[0m")); - assertEquals(render(1234.5678, color(utf8Slice("#00f"))), utf8Slice("\u001b[38;5;21m1234.5678\u001b[0m")); + assertThat(render(1234.5678, color(utf8Slice("#f00")))).isEqualTo(utf8Slice("\u001b[38;5;196m1234.5678\u001b[0m")); + assertThat(render(1234.5678, color(utf8Slice("#0f0")))).isEqualTo(utf8Slice("\u001b[38;5;46m1234.5678\u001b[0m")); + assertThat(render(1234.5678, color(utf8Slice("#00f")))).isEqualTo(utf8Slice("\u001b[38;5;21m1234.5678\u001b[0m")); } @Test public void testInterpolate() { - assertEquals(color(0, 0, 255, color(utf8Slice("#000")), color(utf8Slice("#fff"))), 0x00_00_00); - assertEquals(color(0.0f, 0.0f, 255.0f, color(utf8Slice("#000")), color(utf8Slice("#fff"))), 0x00_00_00); - assertEquals(color(128, 0, 255, color(utf8Slice("#000")), color(utf8Slice("#fff"))), 0x80_80_80); - assertEquals(color(255, 0, 255, color(utf8Slice("#000")), color(utf8Slice("#fff"))), 0xFF_FF_FF); - - assertEquals(color(-1, 42, 52, rgb(0xFF, 0, 0), rgb(0xFF, 0xFF, 0)), 0xFF_00_00); - assertEquals(color(47, 42, 52, rgb(0xFF, 0, 0), rgb(0xFF, 0xFF, 0)), 0xFF_80_00); - assertEquals(color(142, 42, 52, rgb(0xFF, 0, 0), rgb(0xFF, 0xFF, 0)), 0xFF_FF_00); - - assertEquals(color(-42, color(utf8Slice("#000")), color(utf8Slice("#fff"))), 0x00_00_00); - assertEquals(color(0.0, color(utf8Slice("#000")), color(utf8Slice("#fff"))), 0x00_00_00); - assertEquals(color(0.5, color(utf8Slice("#000")), color(utf8Slice("#fff"))), 0x80_80_80); - assertEquals(color(1.0, color(utf8Slice("#000")), color(utf8Slice("#fff"))), 0xFF_FF_FF); - assertEquals(color(42, color(utf8Slice("#000")), color(utf8Slice("#fff"))), 0xFF_FF_FF); - assertEquals(color(1.0f, color(utf8Slice("#000")), color(utf8Slice("#fff"))), 0xFF_FF_FF); - assertEquals(color(-0.0f, color(utf8Slice("#000")), color(utf8Slice("#fff"))), 0x00_00_00); - assertEquals(color(0.0f, color(utf8Slice("#000")), color(utf8Slice("#fff"))), 0x00_00_00); + assertThat(color(0, 0, 255, color(utf8Slice("#000")), color(utf8Slice("#fff")))).isEqualTo(0x00_00_00); + assertThat(color(0.0f, 0.0f, 255.0f, color(utf8Slice("#000")), color(utf8Slice("#fff")))).isEqualTo(0x00_00_00); + assertThat(color(128, 0, 255, color(utf8Slice("#000")), color(utf8Slice("#fff")))).isEqualTo(0x80_80_80); + assertThat(color(255, 0, 255, color(utf8Slice("#000")), color(utf8Slice("#fff")))).isEqualTo(0xFF_FF_FF); + + assertThat(color(-1, 42, 52, rgb(0xFF, 0, 0), rgb(0xFF, 0xFF, 0))).isEqualTo(0xFF_00_00); + assertThat(color(47, 42, 52, rgb(0xFF, 0, 0), rgb(0xFF, 0xFF, 0))).isEqualTo(0xFF_80_00); + assertThat(color(142, 42, 52, rgb(0xFF, 0, 0), rgb(0xFF, 0xFF, 0))).isEqualTo(0xFF_FF_00); + + assertThat(color(-42, color(utf8Slice("#000")), color(utf8Slice("#fff")))).isEqualTo(0x00_00_00); + assertThat(color(0.0, color(utf8Slice("#000")), color(utf8Slice("#fff")))).isEqualTo(0x00_00_00); + assertThat(color(0.5, color(utf8Slice("#000")), color(utf8Slice("#fff")))).isEqualTo(0x80_80_80); + assertThat(color(1.0, color(utf8Slice("#000")), color(utf8Slice("#fff")))).isEqualTo(0xFF_FF_FF); + assertThat(color(42, color(utf8Slice("#000")), color(utf8Slice("#fff")))).isEqualTo(0xFF_FF_FF); + assertThat(color(1.0f, color(utf8Slice("#000")), color(utf8Slice("#fff")))).isEqualTo(0xFF_FF_FF); + assertThat(color(-0.0f, color(utf8Slice("#000")), color(utf8Slice("#fff")))).isEqualTo(0x00_00_00); + assertThat(color(0.0f, color(utf8Slice("#000")), color(utf8Slice("#fff")))).isEqualTo(0x00_00_00); } @Test diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestDateTimeFunctions.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestDateTimeFunctions.java index 3a2df5e644db..0e7fd5af9971 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestDateTimeFunctions.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestDateTimeFunctions.java @@ -47,7 +47,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -115,7 +114,7 @@ private void assertCurrentDateAtInstant(TimeZoneKey timeZoneKey, Instant instant .setTimeZoneKey(timeZoneKey) .build(); long dateTimeCalculation = currentDate(connectorSession); - assertEquals(dateTimeCalculation, expectedDays); + assertThat(dateTimeCalculation).isEqualTo(expectedDays); } private static long epochDaysInZone(TimeZoneKey timeZoneKey, Instant instant) diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestJoniRegexpFunctions.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestJoniRegexpFunctions.java index 67abef112d1b..976d3e3c6c0b 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestJoniRegexpFunctions.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestJoniRegexpFunctions.java @@ -26,8 +26,7 @@ import static io.trino.spi.StandardErrorCode.GENERIC_USER_ERROR; import static io.trino.sql.analyzer.RegexLibrary.JONI; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; +import static org.assertj.core.api.Assertions.assertThat; public class TestJoniRegexpFunctions extends AbstractTestRegexpFunctions @@ -68,7 +67,7 @@ private static void testJoniRegexpFunctionsInterruptible(Runnable joniRegexpRunn // wait for child thread to get in to terminated state searchChildThread.join(); - assertNotNull(trinoException.get()); - assertEquals(trinoException.get().getErrorCode(), GENERIC_USER_ERROR.toErrorCode()); + assertThat(trinoException.get()).isNotNull(); + assertThat(trinoException.get().getErrorCode()).isEqualTo(GENERIC_USER_ERROR.toErrorCode()); } } diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestJsonExtract.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestJsonExtract.java index 323e8016a904..ee66743cb516 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestJsonExtract.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestJsonExtract.java @@ -39,8 +39,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -49,22 +47,22 @@ public class TestJsonExtract @Test public void testJsonTokenizer() { - assertEquals(tokenizePath("$"), ImmutableList.of()); - assertEquals(tokenizePath("$"), ImmutableList.of()); - assertEquals(tokenizePath("$.foo"), ImmutableList.of("foo")); - assertEquals(tokenizePath("$[\"foo\"]"), ImmutableList.of("foo")); - assertEquals(tokenizePath("$[\"foo.bar\"]"), ImmutableList.of("foo.bar")); - assertEquals(tokenizePath("$[42]"), ImmutableList.of("42")); - assertEquals(tokenizePath("$.42"), ImmutableList.of("42")); - assertEquals(tokenizePath("$.42.63"), ImmutableList.of("42", "63")); - assertEquals(tokenizePath("$.foo.42.bar.63"), ImmutableList.of("foo", "42", "bar", "63")); - assertEquals(tokenizePath("$.x.foo"), ImmutableList.of("x", "foo")); - assertEquals(tokenizePath("$.x[\"foo\"]"), ImmutableList.of("x", "foo")); - assertEquals(tokenizePath("$.x[42]"), ImmutableList.of("x", "42")); - assertEquals(tokenizePath("$.foo_42._bar63"), ImmutableList.of("foo_42", "_bar63")); - assertEquals(tokenizePath("$[foo_42][_bar63]"), ImmutableList.of("foo_42", "_bar63")); - assertEquals(tokenizePath("$.foo:42.:bar63"), ImmutableList.of("foo:42", ":bar63")); - assertEquals(tokenizePath("$[\"foo:42\"][\":bar63\"]"), ImmutableList.of("foo:42", ":bar63")); + assertThat(tokenizePath("$")).isEqualTo(ImmutableList.of()); + assertThat(tokenizePath("$")).isEqualTo(ImmutableList.of()); + assertThat(tokenizePath("$.foo")).isEqualTo(ImmutableList.of("foo")); + assertThat(tokenizePath("$[\"foo\"]")).isEqualTo(ImmutableList.of("foo")); + assertThat(tokenizePath("$[\"foo.bar\"]")).isEqualTo(ImmutableList.of("foo.bar")); + assertThat(tokenizePath("$[42]")).isEqualTo(ImmutableList.of("42")); + assertThat(tokenizePath("$.42")).isEqualTo(ImmutableList.of("42")); + assertThat(tokenizePath("$.42.63")).isEqualTo(ImmutableList.of("42", "63")); + assertThat(tokenizePath("$.foo.42.bar.63")).isEqualTo(ImmutableList.of("foo", "42", "bar", "63")); + assertThat(tokenizePath("$.x.foo")).isEqualTo(ImmutableList.of("x", "foo")); + assertThat(tokenizePath("$.x[\"foo\"]")).isEqualTo(ImmutableList.of("x", "foo")); + assertThat(tokenizePath("$.x[42]")).isEqualTo(ImmutableList.of("x", "42")); + assertThat(tokenizePath("$.foo_42._bar63")).isEqualTo(ImmutableList.of("foo_42", "_bar63")); + assertThat(tokenizePath("$[foo_42][_bar63]")).isEqualTo(ImmutableList.of("foo_42", "_bar63")); + assertThat(tokenizePath("$.foo:42.:bar63")).isEqualTo(ImmutableList.of("foo:42", ":bar63")); + assertThat(tokenizePath("$[\"foo:42\"][\":bar63\"]")).isEqualTo(ImmutableList.of("foo:42", ":bar63")); assertPathToken("foo"); @@ -102,9 +100,9 @@ public void testJsonTokenizer() private static void assertPathToken(String fieldName) { - assertTrue(fieldName.indexOf('"') < 0); - assertEquals(tokenizePath("$." + fieldName), ImmutableList.of(fieldName)); - assertEquals(tokenizePath("$.foo." + fieldName + ".bar"), ImmutableList.of("foo", fieldName, "bar")); + assertThat(fieldName.indexOf('"') < 0).isTrue(); + assertThat(tokenizePath("$." + fieldName)).isEqualTo(ImmutableList.of(fieldName)); + assertThat(tokenizePath("$.foo." + fieldName + ".bar")).isEqualTo(ImmutableList.of("foo", fieldName, "bar")); assertPathTokenQuoting(fieldName); } @@ -127,8 +125,8 @@ private static void assertPathTokenQuoting(String fieldName) private static void assertPathTokenQuoting(String fieldName, String expectedTokenizedField) { - assertEquals(tokenizePath("$[\"" + fieldName + "\"]"), ImmutableList.of(expectedTokenizedField)); - assertEquals(tokenizePath("$.foo[\"" + fieldName + "\"].bar"), ImmutableList.of("foo", expectedTokenizedField, "bar")); + assertThat(tokenizePath("$[\"" + fieldName + "\"]")).isEqualTo(ImmutableList.of(expectedTokenizedField)); + assertThat(tokenizePath("$.foo[\"" + fieldName + "\"].bar")).isEqualTo(ImmutableList.of("foo", expectedTokenizedField, "bar")); } public static void assertInvalidPath(String path) @@ -144,20 +142,20 @@ public void testScalarValueJsonExtractor() ScalarValueJsonExtractor extractor = new ScalarValueJsonExtractor(); // Check scalar values - assertEquals(doExtract(extractor, "123"), "123"); - assertEquals(doExtract(extractor, "-1"), "-1"); - assertEquals(doExtract(extractor, "0.01"), "0.01"); - assertEquals(doExtract(extractor, "\"abc\""), "abc"); - assertEquals(doExtract(extractor, "\"\""), ""); - assertEquals(doExtract(extractor, "null"), null); + assertThat(doExtract(extractor, "123")).isEqualTo("123"); + assertThat(doExtract(extractor, "-1")).isEqualTo("-1"); + assertThat(doExtract(extractor, "0.01")).isEqualTo("0.01"); + assertThat(doExtract(extractor, "\"abc\"")).isEqualTo("abc"); + assertThat(doExtract(extractor, "\"\"")).isEqualTo(""); + assertThat(doExtract(extractor, "null")).isEqualTo(null); // Test character escaped values - assertEquals(doExtract(extractor, "\"ab\\u0001c\""), "ab\001c"); - assertEquals(doExtract(extractor, "\"ab\\u0002c\""), "ab\002c"); + assertThat(doExtract(extractor, "\"ab\\u0001c\"")).isEqualTo("ab\001c"); + assertThat(doExtract(extractor, "\"ab\\u0002c\"")).isEqualTo("ab\002c"); // Complex types should return null - assertEquals(doExtract(extractor, "[1, 2, 3]"), null); - assertEquals(doExtract(extractor, "{\"a\": 1}"), null); + assertThat(doExtract(extractor, "[1, 2, 3]")).isEqualTo(null); + assertThat(doExtract(extractor, "{\"a\": 1}")).isEqualTo(null); } @Test @@ -167,20 +165,20 @@ public void testJsonValueJsonExtractor() JsonValueJsonExtractor extractor = new JsonValueJsonExtractor(); // Check scalar values - assertEquals(doExtract(extractor, "123"), "123"); - assertEquals(doExtract(extractor, "-1"), "-1"); - assertEquals(doExtract(extractor, "0.01"), "0.01"); - assertEquals(doExtract(extractor, "\"abc\""), "\"abc\""); - assertEquals(doExtract(extractor, "\"\""), "\"\""); - assertEquals(doExtract(extractor, "null"), "null"); + assertThat(doExtract(extractor, "123")).isEqualTo("123"); + assertThat(doExtract(extractor, "-1")).isEqualTo("-1"); + assertThat(doExtract(extractor, "0.01")).isEqualTo("0.01"); + assertThat(doExtract(extractor, "\"abc\"")).isEqualTo("\"abc\""); + assertThat(doExtract(extractor, "\"\"")).isEqualTo("\"\""); + assertThat(doExtract(extractor, "null")).isEqualTo("null"); // Test character escaped values - assertEquals(doExtract(extractor, "\"ab\\u0001c\""), "\"ab\\u0001c\""); - assertEquals(doExtract(extractor, "\"ab\\u0002c\""), "\"ab\\u0002c\""); + assertThat(doExtract(extractor, "\"ab\\u0001c\"")).isEqualTo("\"ab\\u0001c\""); + assertThat(doExtract(extractor, "\"ab\\u0002c\"")).isEqualTo("\"ab\\u0002c\""); // Complex types should return json values - assertEquals(doExtract(extractor, "[1, 2, 3]"), "[1,2,3]"); - assertEquals(doExtract(extractor, "{\"a\": 1}"), "{\"a\":1}"); + assertThat(doExtract(extractor, "[1, 2, 3]")).isEqualTo("[1,2,3]"); + assertThat(doExtract(extractor, "{\"a\": 1}")).isEqualTo("{\"a\":1}"); } @Test @@ -190,14 +188,14 @@ public void testArrayElementJsonExtractor() ObjectFieldJsonExtractor firstExtractor = new ObjectFieldJsonExtractor<>("0", new ScalarValueJsonExtractor()); ObjectFieldJsonExtractor secondExtractor = new ObjectFieldJsonExtractor<>("1", new ScalarValueJsonExtractor()); - assertEquals(doExtract(firstExtractor, "[]"), null); - assertEquals(doExtract(firstExtractor, "[1, 2, 3]"), "1"); - assertEquals(doExtract(secondExtractor, "[1, 2]"), "2"); - assertEquals(doExtract(secondExtractor, "[1, null]"), null); + assertThat(doExtract(firstExtractor, "[]")).isEqualTo(null); + assertThat(doExtract(firstExtractor, "[1, 2, 3]")).isEqualTo("1"); + assertThat(doExtract(secondExtractor, "[1, 2]")).isEqualTo("2"); + assertThat(doExtract(secondExtractor, "[1, null]")).isEqualTo(null); // Out of bounds - assertEquals(doExtract(secondExtractor, "[1]"), null); + assertThat(doExtract(secondExtractor, "[1]")).isEqualTo(null); // Check skipping complex structures - assertEquals(doExtract(secondExtractor, "[{\"a\": 1}, 2, 3]"), "2"); + assertThat(doExtract(secondExtractor, "[{\"a\": 1}, 2, 3]")).isEqualTo("2"); } @Test @@ -206,118 +204,118 @@ public void testObjectFieldJsonExtractor() { ObjectFieldJsonExtractor extractor = new ObjectFieldJsonExtractor<>("fuu", new ScalarValueJsonExtractor()); - assertEquals(doExtract(extractor, "{}"), null); - assertEquals(doExtract(extractor, "{\"a\": 1}"), null); - assertEquals(doExtract(extractor, "{\"fuu\": 1}"), "1"); - assertEquals(doExtract(extractor, "{\"a\": 0, \"fuu\": 1}"), "1"); + assertThat(doExtract(extractor, "{}")).isEqualTo(null); + assertThat(doExtract(extractor, "{\"a\": 1}")).isEqualTo(null); + assertThat(doExtract(extractor, "{\"fuu\": 1}")).isEqualTo("1"); + assertThat(doExtract(extractor, "{\"a\": 0, \"fuu\": 1}")).isEqualTo("1"); // Check skipping complex structures - assertEquals(doExtract(extractor, "{\"a\": [1, 2, 3], \"fuu\": 1}"), "1"); + assertThat(doExtract(extractor, "{\"a\": [1, 2, 3], \"fuu\": 1}")).isEqualTo("1"); } @Test public void testFullScalarExtract() { - assertEquals(doScalarExtract("{}", "$"), null); - assertEquals(doScalarExtract("{\"fuu\": {\"bar\": 1}}", "$.fuu"), null); // Null b/c value is complex type - assertEquals(doScalarExtract("{\"fuu\": 1}", "$.fuu"), "1"); - assertEquals(doScalarExtract("{\"fuu\": 1}", "$[fuu]"), "1"); - assertEquals(doScalarExtract("{\"fuu\": 1}", "$[\"fuu\"]"), "1"); - assertEquals(doScalarExtract("{\"fuu\": null}", "$.fuu"), null); - assertEquals(doScalarExtract("{\"fuu\": 1}", "$.bar"), null); - assertEquals(doScalarExtract("{\"fuu\": [\"\\u0001\"]}", "$.fuu[0]"), "\001"); // Test escaped characters - assertEquals(doScalarExtract("{\"fuu\": 1, \"bar\": \"abc\"}", "$.bar"), "abc"); - assertEquals(doScalarExtract("{\"fuu\": [0.1, 1, 2]}", "$.fuu[0]"), "0.1"); - assertEquals(doScalarExtract("{\"fuu\": [0, [100, 101], 2]}", "$.fuu[1]"), null); // Null b/c value is complex type - assertEquals(doScalarExtract("{\"fuu\": [0, [100, 101], 2]}", "$.fuu[1][1]"), "101"); - assertEquals(doScalarExtract("{\"fuu\": [0, {\"bar\": {\"key\" : [\"value\"]}}, 2]}", "$.fuu[1].bar.key[0]"), "value"); + assertThat(doScalarExtract("{}", "$")).isEqualTo(null); + assertThat(doScalarExtract("{\"fuu\": {\"bar\": 1}}", "$.fuu")).isEqualTo(null); // Null b/c value is complex type + assertThat(doScalarExtract("{\"fuu\": 1}", "$.fuu")).isEqualTo("1"); + assertThat(doScalarExtract("{\"fuu\": 1}", "$[fuu]")).isEqualTo("1"); + assertThat(doScalarExtract("{\"fuu\": 1}", "$[\"fuu\"]")).isEqualTo("1"); + assertThat(doScalarExtract("{\"fuu\": null}", "$.fuu")).isEqualTo(null); + assertThat(doScalarExtract("{\"fuu\": 1}", "$.bar")).isEqualTo(null); + assertThat(doScalarExtract("{\"fuu\": [\"\\u0001\"]}", "$.fuu[0]")).isEqualTo("\001"); // Test escaped characters + assertThat(doScalarExtract("{\"fuu\": 1, \"bar\": \"abc\"}", "$.bar")).isEqualTo("abc"); + assertThat(doScalarExtract("{\"fuu\": [0.1, 1, 2]}", "$.fuu[0]")).isEqualTo("0.1"); + assertThat(doScalarExtract("{\"fuu\": [0, [100, 101], 2]}", "$.fuu[1]")).isEqualTo(null); // Null b/c value is complex type + assertThat(doScalarExtract("{\"fuu\": [0, [100, 101], 2]}", "$.fuu[1][1]")).isEqualTo("101"); + assertThat(doScalarExtract("{\"fuu\": [0, {\"bar\": {\"key\" : [\"value\"]}}, 2]}", "$.fuu[1].bar.key[0]")).isEqualTo("value"); // Test non-object extraction - assertEquals(doScalarExtract("[0, 1, 2]", "$[0]"), "0"); - assertEquals(doScalarExtract("\"abc\"", "$"), "abc"); - assertEquals(doScalarExtract("123", "$"), "123"); - assertEquals(doScalarExtract("null", "$"), null); + assertThat(doScalarExtract("[0, 1, 2]", "$[0]")).isEqualTo("0"); + assertThat(doScalarExtract("\"abc\"", "$")).isEqualTo("abc"); + assertThat(doScalarExtract("123", "$")).isEqualTo("123"); + assertThat(doScalarExtract("null", "$")).isEqualTo(null); // Test numeric path expression matches arrays and objects - assertEquals(doScalarExtract("[0, 1, 2]", "$.1"), "1"); - assertEquals(doScalarExtract("[0, 1, 2]", "$[1]"), "1"); - assertEquals(doScalarExtract("[0, 1, 2]", "$[\"1\"]"), "1"); - assertEquals(doScalarExtract("{\"0\" : 0, \"1\" : 1, \"2\" : 2, }", "$.1"), "1"); - assertEquals(doScalarExtract("{\"0\" : 0, \"1\" : 1, \"2\" : 2, }", "$[1]"), "1"); - assertEquals(doScalarExtract("{\"0\" : 0, \"1\" : 1, \"2\" : 2, }", "$[\"1\"]"), "1"); + assertThat(doScalarExtract("[0, 1, 2]", "$.1")).isEqualTo("1"); + assertThat(doScalarExtract("[0, 1, 2]", "$[1]")).isEqualTo("1"); + assertThat(doScalarExtract("[0, 1, 2]", "$[\"1\"]")).isEqualTo("1"); + assertThat(doScalarExtract("{\"0\" : 0, \"1\" : 1, \"2\" : 2, }", "$.1")).isEqualTo("1"); + assertThat(doScalarExtract("{\"0\" : 0, \"1\" : 1, \"2\" : 2, }", "$[1]")).isEqualTo("1"); + assertThat(doScalarExtract("{\"0\" : 0, \"1\" : 1, \"2\" : 2, }", "$[\"1\"]")).isEqualTo("1"); // Test fields starting with a digit - assertEquals(doScalarExtract("{\"15day\" : 0, \"30day\" : 1, \"90day\" : 2, }", "$.30day"), "1"); - assertEquals(doScalarExtract("{\"15day\" : 0, \"30day\" : 1, \"90day\" : 2, }", "$[30day]"), "1"); - assertEquals(doScalarExtract("{\"15day\" : 0, \"30day\" : 1, \"90day\" : 2, }", "$[\"30day\"]"), "1"); + assertThat(doScalarExtract("{\"15day\" : 0, \"30day\" : 1, \"90day\" : 2, }", "$.30day")).isEqualTo("1"); + assertThat(doScalarExtract("{\"15day\" : 0, \"30day\" : 1, \"90day\" : 2, }", "$[30day]")).isEqualTo("1"); + assertThat(doScalarExtract("{\"15day\" : 0, \"30day\" : 1, \"90day\" : 2, }", "$[\"30day\"]")).isEqualTo("1"); } @Test public void testFullJsonExtract() { - assertEquals(doJsonExtract("{}", "$"), "{}"); - assertEquals(doJsonExtract("{\"fuu\": {\"bar\": 1}}", "$.fuu"), "{\"bar\":1}"); - assertEquals(doJsonExtract("{\"fuu\": 1}", "$.fuu"), "1"); - assertEquals(doJsonExtract("{\"fuu\": 1}", "$[fuu]"), "1"); - assertEquals(doJsonExtract("{\"fuu\": 1}", "$[\"fuu\"]"), "1"); - assertEquals(doJsonExtract("{\"fuu\": null}", "$.fuu"), "null"); - assertEquals(doJsonExtract("{\"fuu\": 1}", "$.bar"), null); - assertEquals(doJsonExtract("{\"fuu\": [\"\\u0001\"]}", "$.fuu[0]"), "\"\\u0001\""); // Test escaped characters - assertEquals(doJsonExtract("{\"fuu\": 1, \"bar\": \"abc\"}", "$.bar"), "\"abc\""); - assertEquals(doJsonExtract("{\"fuu\": [0.1, 1, 2]}", "$.fuu[0]"), "0.1"); - assertEquals(doJsonExtract("{\"fuu\": [0, [100, 101], 2]}", "$.fuu[1]"), "[100,101]"); - assertEquals(doJsonExtract("{\"fuu\": [0, [100, 101], 2]}", "$.fuu[1][1]"), "101"); + assertThat(doJsonExtract("{}", "$")).isEqualTo("{}"); + assertThat(doJsonExtract("{\"fuu\": {\"bar\": 1}}", "$.fuu")).isEqualTo("{\"bar\":1}"); + assertThat(doJsonExtract("{\"fuu\": 1}", "$.fuu")).isEqualTo("1"); + assertThat(doJsonExtract("{\"fuu\": 1}", "$[fuu]")).isEqualTo("1"); + assertThat(doJsonExtract("{\"fuu\": 1}", "$[\"fuu\"]")).isEqualTo("1"); + assertThat(doJsonExtract("{\"fuu\": null}", "$.fuu")).isEqualTo("null"); + assertThat(doJsonExtract("{\"fuu\": 1}", "$.bar")).isEqualTo(null); + assertThat(doJsonExtract("{\"fuu\": [\"\\u0001\"]}", "$.fuu[0]")).isEqualTo("\"\\u0001\""); // Test escaped characters + assertThat(doJsonExtract("{\"fuu\": 1, \"bar\": \"abc\"}", "$.bar")).isEqualTo("\"abc\""); + assertThat(doJsonExtract("{\"fuu\": [0.1, 1, 2]}", "$.fuu[0]")).isEqualTo("0.1"); + assertThat(doJsonExtract("{\"fuu\": [0, [100, 101], 2]}", "$.fuu[1]")).isEqualTo("[100,101]"); + assertThat(doJsonExtract("{\"fuu\": [0, [100, 101], 2]}", "$.fuu[1][1]")).isEqualTo("101"); // Test non-object extraction - assertEquals(doJsonExtract("[0, 1, 2]", "$[0]"), "0"); - assertEquals(doJsonExtract("\"abc\"", "$"), "\"abc\""); - assertEquals(doJsonExtract("123", "$"), "123"); - assertEquals(doJsonExtract("null", "$"), "null"); + assertThat(doJsonExtract("[0, 1, 2]", "$[0]")).isEqualTo("0"); + assertThat(doJsonExtract("\"abc\"", "$")).isEqualTo("\"abc\""); + assertThat(doJsonExtract("123", "$")).isEqualTo("123"); + assertThat(doJsonExtract("null", "$")).isEqualTo("null"); // Test extraction using bracket json path - assertEquals(doJsonExtract("{\"fuu\": {\"bar\": 1}}", "$[\"fuu\"]"), "{\"bar\":1}"); - assertEquals(doJsonExtract("{\"fuu\": {\"bar\": 1}}", "$[\"fuu\"][\"bar\"]"), "1"); - assertEquals(doJsonExtract("{\"fuu\": 1}", "$[\"fuu\"]"), "1"); - assertEquals(doJsonExtract("{\"fuu\": null}", "$[\"fuu\"]"), "null"); - assertEquals(doJsonExtract("{\"fuu\": 1}", "$[\"bar\"]"), null); - assertEquals(doJsonExtract("{\"fuu\": [\"\\u0001\"]}", "$[\"fuu\"][0]"), "\"\\u0001\""); // Test escaped characters - assertEquals(doJsonExtract("{\"fuu\": 1, \"bar\": \"abc\"}", "$[\"bar\"]"), "\"abc\""); - assertEquals(doJsonExtract("{\"fuu\": [0.1, 1, 2]}", "$[\"fuu\"][0]"), "0.1"); - assertEquals(doJsonExtract("{\"fuu\": [0, [100, 101], 2]}", "$[\"fuu\"][1]"), "[100,101]"); - assertEquals(doJsonExtract("{\"fuu\": [0, [100, 101], 2]}", "$[\"fuu\"][1][1]"), "101"); + assertThat(doJsonExtract("{\"fuu\": {\"bar\": 1}}", "$[\"fuu\"]")).isEqualTo("{\"bar\":1}"); + assertThat(doJsonExtract("{\"fuu\": {\"bar\": 1}}", "$[\"fuu\"][\"bar\"]")).isEqualTo("1"); + assertThat(doJsonExtract("{\"fuu\": 1}", "$[\"fuu\"]")).isEqualTo("1"); + assertThat(doJsonExtract("{\"fuu\": null}", "$[\"fuu\"]")).isEqualTo("null"); + assertThat(doJsonExtract("{\"fuu\": 1}", "$[\"bar\"]")).isEqualTo(null); + assertThat(doJsonExtract("{\"fuu\": [\"\\u0001\"]}", "$[\"fuu\"][0]")).isEqualTo("\"\\u0001\""); // Test escaped characters + assertThat(doJsonExtract("{\"fuu\": 1, \"bar\": \"abc\"}", "$[\"bar\"]")).isEqualTo("\"abc\""); + assertThat(doJsonExtract("{\"fuu\": [0.1, 1, 2]}", "$[\"fuu\"][0]")).isEqualTo("0.1"); + assertThat(doJsonExtract("{\"fuu\": [0, [100, 101], 2]}", "$[\"fuu\"][1]")).isEqualTo("[100,101]"); + assertThat(doJsonExtract("{\"fuu\": [0, [100, 101], 2]}", "$[\"fuu\"][1][1]")).isEqualTo("101"); // Test extraction using bracket json path with special json characters in path - assertEquals(doJsonExtract("{\"@$fuu\": {\".b.ar\": 1}}", "$[\"@$fuu\"]"), "{\".b.ar\":1}"); - assertEquals(doJsonExtract("{\"fuu..\": 1}", "$[\"fuu..\"]"), "1"); - assertEquals(doJsonExtract("{\"fu*u\": null}", "$[\"fu*u\"]"), "null"); - assertEquals(doJsonExtract("{\",fuu\": 1}", "$[\"bar\"]"), null); - assertEquals(doJsonExtract("{\",fuu\": [\"\\u0001\"]}", "$[\",fuu\"][0]"), "\"\\u0001\""); // Test escaped characters - assertEquals(doJsonExtract("{\":fu:u:\": 1, \":b:ar:\": \"abc\"}", "$[\":b:ar:\"]"), "\"abc\""); - assertEquals(doJsonExtract("{\"?()fuu\": [0.1, 1, 2]}", "$[\"?()fuu\"][0]"), "0.1"); - assertEquals(doJsonExtract("{\"f?uu\": [0, [100, 101], 2]}", "$[\"f?uu\"][1]"), "[100,101]"); - assertEquals(doJsonExtract("{\"fuu()\": [0, [100, 101], 2]}", "$[\"fuu()\"][1][1]"), "101"); + assertThat(doJsonExtract("{\"@$fuu\": {\".b.ar\": 1}}", "$[\"@$fuu\"]")).isEqualTo("{\".b.ar\":1}"); + assertThat(doJsonExtract("{\"fuu..\": 1}", "$[\"fuu..\"]")).isEqualTo("1"); + assertThat(doJsonExtract("{\"fu*u\": null}", "$[\"fu*u\"]")).isEqualTo("null"); + assertThat(doJsonExtract("{\",fuu\": 1}", "$[\"bar\"]")).isEqualTo(null); + assertThat(doJsonExtract("{\",fuu\": [\"\\u0001\"]}", "$[\",fuu\"][0]")).isEqualTo("\"\\u0001\""); // Test escaped characters + assertThat(doJsonExtract("{\":fu:u:\": 1, \":b:ar:\": \"abc\"}", "$[\":b:ar:\"]")).isEqualTo("\"abc\""); + assertThat(doJsonExtract("{\"?()fuu\": [0.1, 1, 2]}", "$[\"?()fuu\"][0]")).isEqualTo("0.1"); + assertThat(doJsonExtract("{\"f?uu\": [0, [100, 101], 2]}", "$[\"f?uu\"][1]")).isEqualTo("[100,101]"); + assertThat(doJsonExtract("{\"fuu()\": [0, [100, 101], 2]}", "$[\"fuu()\"][1][1]")).isEqualTo("101"); // Test extraction using mix of bracket and dot notation json path - assertEquals(doJsonExtract("{\"fuu\": {\"bar\": 1}}", "$[\"fuu\"].bar"), "1"); - assertEquals(doJsonExtract("{\"fuu\": {\"bar\": 1}}", "$.fuu[\"bar\"]"), "1"); - assertEquals(doJsonExtract("{\"fuu\": [\"\\u0001\"]}", "$[\"fuu\"][0]"), "\"\\u0001\""); // Test escaped characters - assertEquals(doJsonExtract("{\"fuu\": [\"\\u0001\"]}", "$.fuu[0]"), "\"\\u0001\""); // Test escaped characters + assertThat(doJsonExtract("{\"fuu\": {\"bar\": 1}}", "$[\"fuu\"].bar")).isEqualTo("1"); + assertThat(doJsonExtract("{\"fuu\": {\"bar\": 1}}", "$.fuu[\"bar\"]")).isEqualTo("1"); + assertThat(doJsonExtract("{\"fuu\": [\"\\u0001\"]}", "$[\"fuu\"][0]")).isEqualTo("\"\\u0001\""); // Test escaped characters + assertThat(doJsonExtract("{\"fuu\": [\"\\u0001\"]}", "$.fuu[0]")).isEqualTo("\"\\u0001\""); // Test escaped characters // Test extraction using mix of bracket and dot notation json path with special json characters in path - assertEquals(doJsonExtract("{\"@$fuu\": {\"bar\": 1}}", "$[\"@$fuu\"].bar"), "1"); - assertEquals(doJsonExtract("{\",fuu\": {\"bar\": [\"\\u0001\"]}}", "$[\",fuu\"].bar[0]"), "\"\\u0001\""); // Test escaped characters + assertThat(doJsonExtract("{\"@$fuu\": {\"bar\": 1}}", "$[\"@$fuu\"].bar")).isEqualTo("1"); + assertThat(doJsonExtract("{\",fuu\": {\"bar\": [\"\\u0001\"]}}", "$[\",fuu\"].bar[0]")).isEqualTo("\"\\u0001\""); // Test escaped characters // Test numeric path expression matches arrays and objects - assertEquals(doJsonExtract("[0, 1, 2]", "$.1"), "1"); - assertEquals(doJsonExtract("[0, 1, 2]", "$[1]"), "1"); - assertEquals(doJsonExtract("[0, 1, 2]", "$[\"1\"]"), "1"); - assertEquals(doJsonExtract("{\"0\" : 0, \"1\" : 1, \"2\" : 2, }", "$.1"), "1"); - assertEquals(doJsonExtract("{\"0\" : 0, \"1\" : 1, \"2\" : 2, }", "$[1]"), "1"); - assertEquals(doJsonExtract("{\"0\" : 0, \"1\" : 1, \"2\" : 2, }", "$[\"1\"]"), "1"); + assertThat(doJsonExtract("[0, 1, 2]", "$.1")).isEqualTo("1"); + assertThat(doJsonExtract("[0, 1, 2]", "$[1]")).isEqualTo("1"); + assertThat(doJsonExtract("[0, 1, 2]", "$[\"1\"]")).isEqualTo("1"); + assertThat(doJsonExtract("{\"0\" : 0, \"1\" : 1, \"2\" : 2, }", "$.1")).isEqualTo("1"); + assertThat(doJsonExtract("{\"0\" : 0, \"1\" : 1, \"2\" : 2, }", "$[1]")).isEqualTo("1"); + assertThat(doJsonExtract("{\"0\" : 0, \"1\" : 1, \"2\" : 2, }", "$[\"1\"]")).isEqualTo("1"); // Test fields starting with a digit - assertEquals(doJsonExtract("{\"15day\" : 0, \"30day\" : 1, \"90day\" : 2, }", "$.30day"), "1"); - assertEquals(doJsonExtract("{\"15day\" : 0, \"30day\" : 1, \"90day\" : 2, }", "$[30day]"), "1"); - assertEquals(doJsonExtract("{\"15day\" : 0, \"30day\" : 1, \"90day\" : 2, }", "$[\"30day\"]"), "1"); + assertThat(doJsonExtract("{\"15day\" : 0, \"30day\" : 1, \"90day\" : 2, }", "$.30day")).isEqualTo("1"); + assertThat(doJsonExtract("{\"15day\" : 0, \"30day\" : 1, \"90day\" : 2, }", "$[30day]")).isEqualTo("1"); + assertThat(doJsonExtract("{\"15day\" : 0, \"30day\" : 1, \"90day\" : 2, }", "$[\"30day\"]")).isEqualTo("1"); } @Test @@ -337,7 +335,7 @@ public void testInvalidExtracts() public void testExtractLongString() { String longString = "a".repeat(StreamReadConstraints.DEFAULT_MAX_STRING_LEN + 1); - assertEquals(doJsonExtract("{\"key\": \"" + longString + "\"}", "$.key"), '"' + longString + '"'); + assertThat(doJsonExtract("{\"key\": \"" + longString + "\"}", "$.key")).isEqualTo('"' + longString + '"'); } @Test diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestLikeFunctions.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestLikeFunctions.java index 9cb5f2fa88f2..65de8aee5e31 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestLikeFunctions.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestLikeFunctions.java @@ -37,9 +37,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -72,8 +69,8 @@ private static Slice offsetHeapSlice(String value) public void testLikeBasic() { LikePattern matcher = LikePattern.compile(utf8Slice("f%b__").toStringUtf8(), Optional.empty()); - assertTrue(likeVarchar(utf8Slice("foobar"), matcher)); - assertTrue(likeVarchar(offsetHeapSlice("foobar"), matcher)); + assertThat(likeVarchar(utf8Slice("foobar"), matcher)).isTrue(); + assertThat(likeVarchar(offsetHeapSlice("foobar"), matcher)).isTrue(); assertThat(assertions.expression("a LIKE 'f%b__'") .binding("a", "'foob'")) @@ -112,12 +109,12 @@ public void testLikeBasic() public void testLikeChar() { LikePattern matcher = LikePattern.compile(utf8Slice("f%b__").toStringUtf8(), Optional.empty()); - assertTrue(likeChar(6L, utf8Slice("foobar"), matcher)); - assertTrue(likeChar(6L, offsetHeapSlice("foobar"), matcher)); - assertTrue(likeChar(6L, utf8Slice("foob"), matcher)); - assertTrue(likeChar(6L, offsetHeapSlice("foob"), matcher)); - assertFalse(likeChar(7L, utf8Slice("foob"), matcher)); - assertFalse(likeChar(7L, offsetHeapSlice("foob"), matcher)); + assertThat(likeChar(6L, utf8Slice("foobar"), matcher)).isTrue(); + assertThat(likeChar(6L, offsetHeapSlice("foobar"), matcher)).isTrue(); + assertThat(likeChar(6L, utf8Slice("foob"), matcher)).isTrue(); + assertThat(likeChar(6L, offsetHeapSlice("foob"), matcher)).isTrue(); + assertThat(likeChar(7L, utf8Slice("foob"), matcher)).isFalse(); + assertThat(likeChar(7L, offsetHeapSlice("foob"), matcher)).isFalse(); // pattern shorter than value length assertThat(assertions.expression("a LIKE 'foo'") @@ -205,36 +202,36 @@ public void testLikeChar() public void testLikeSpacesInPattern() { LikePattern matcher = LikePattern.compile(utf8Slice("ala ").toStringUtf8(), Optional.empty()); - assertTrue(likeVarchar(utf8Slice("ala "), matcher)); - assertFalse(likeVarchar(utf8Slice("ala"), matcher)); + assertThat(likeVarchar(utf8Slice("ala "), matcher)).isTrue(); + assertThat(likeVarchar(utf8Slice("ala"), matcher)).isFalse(); } @Test public void testLikeNewlineInPattern() { LikePattern matcher = LikePattern.compile(utf8Slice("%o\nbar").toStringUtf8(), Optional.empty()); - assertTrue(likeVarchar(utf8Slice("foo\nbar"), matcher)); + assertThat(likeVarchar(utf8Slice("foo\nbar"), matcher)).isTrue(); } @Test public void testLikeNewlineBeforeMatch() { LikePattern matcher = LikePattern.compile(utf8Slice("%b%").toStringUtf8(), Optional.empty()); - assertTrue(likeVarchar(utf8Slice("foo\nbar"), matcher)); + assertThat(likeVarchar(utf8Slice("foo\nbar"), matcher)).isTrue(); } @Test public void testLikeNewlineInMatch() { LikePattern matcher = LikePattern.compile(utf8Slice("f%b%").toStringUtf8(), Optional.empty()); - assertTrue(likeVarchar(utf8Slice("foo\nbar"), matcher)); + assertThat(likeVarchar(utf8Slice("foo\nbar"), matcher)).isTrue(); } @Test public void testLikeUtf8Pattern() { LikePattern matcher = likePattern(utf8Slice("%\u540d\u8a89%"), utf8Slice("\\")); - assertFalse(likeVarchar(utf8Slice("foo"), matcher)); + assertThat(likeVarchar(utf8Slice("foo"), matcher)).isFalse(); } @Test @@ -242,28 +239,28 @@ public void testLikeInvalidUtf8Value() { Slice value = Slices.wrappedBuffer(new byte[] {'a', 'b', 'c', (byte) 0xFF, 'x', 'y'}); LikePattern matcher = likePattern(utf8Slice("%b%"), utf8Slice("\\")); - assertTrue(likeVarchar(value, matcher)); + assertThat(likeVarchar(value, matcher)).isTrue(); } @Test public void testBackslashesNoSpecialTreatment() { LikePattern matcher = LikePattern.compile(utf8Slice("\\abc\\/\\\\").toStringUtf8(), Optional.empty()); - assertTrue(likeVarchar(utf8Slice("\\abc\\/\\\\"), matcher)); + assertThat(likeVarchar(utf8Slice("\\abc\\/\\\\"), matcher)).isTrue(); } @Test public void testSelfEscaping() { LikePattern matcher = likePattern(utf8Slice("\\\\abc\\%"), utf8Slice("\\")); - assertTrue(likeVarchar(utf8Slice("\\abc%"), matcher)); + assertThat(likeVarchar(utf8Slice("\\abc%"), matcher)).isTrue(); } @Test public void testAlternateEscapedCharacters() { LikePattern matcher = likePattern(utf8Slice("xxx%x_abcxx"), utf8Slice("x")); - assertTrue(likeVarchar(utf8Slice("x%_abcx"), matcher)); + assertThat(likeVarchar(utf8Slice("x%_abcx"), matcher)).isTrue(); } @Test @@ -283,14 +280,14 @@ public void testInvalidLikePattern() @Test public void testIsLikePattern() { - assertFalse(isLikePattern(utf8Slice("abc"), Optional.empty())); - assertFalse(isLikePattern(utf8Slice("abc#_def"), Optional.of(utf8Slice("#")))); - assertFalse(isLikePattern(utf8Slice("abc##def"), Optional.of(utf8Slice("#")))); - assertFalse(isLikePattern(utf8Slice("abc#%def"), Optional.of(utf8Slice("#")))); - assertTrue(isLikePattern(utf8Slice("abc%def"), Optional.empty())); - assertTrue(isLikePattern(utf8Slice("abcdef_"), Optional.empty())); - assertTrue(isLikePattern(utf8Slice("abcdef##_"), Optional.of(utf8Slice("#")))); - assertTrue(isLikePattern(utf8Slice("%abcdef#_"), Optional.of(utf8Slice("#")))); + assertThat(isLikePattern(utf8Slice("abc"), Optional.empty())).isFalse(); + assertThat(isLikePattern(utf8Slice("abc#_def"), Optional.of(utf8Slice("#")))).isFalse(); + assertThat(isLikePattern(utf8Slice("abc##def"), Optional.of(utf8Slice("#")))).isFalse(); + assertThat(isLikePattern(utf8Slice("abc#%def"), Optional.of(utf8Slice("#")))).isFalse(); + assertThat(isLikePattern(utf8Slice("abc%def"), Optional.empty())).isTrue(); + assertThat(isLikePattern(utf8Slice("abcdef_"), Optional.empty())).isTrue(); + assertThat(isLikePattern(utf8Slice("abcdef##_"), Optional.of(utf8Slice("#")))).isTrue(); + assertThat(isLikePattern(utf8Slice("%abcdef#_"), Optional.of(utf8Slice("#")))).isTrue(); assertThatThrownBy(() -> isLikePattern(utf8Slice("#"), Optional.of(utf8Slice("#")))) .isInstanceOf(TrinoException.class) .hasMessage("Escape character must be followed by '%', '_' or the escape character itself"); @@ -305,14 +302,14 @@ public void testIsLikePattern() @Test public void testPatternConstantPrefixBytes() { - assertEquals(patternConstantPrefixBytes(utf8Slice("abc"), Optional.empty()), 3); - assertEquals(patternConstantPrefixBytes(utf8Slice("abc#_def"), Optional.of(utf8Slice("#"))), 8); - assertEquals(patternConstantPrefixBytes(utf8Slice("abc##def"), Optional.of(utf8Slice("#"))), 8); - assertEquals(patternConstantPrefixBytes(utf8Slice("abc#%def"), Optional.of(utf8Slice("#"))), 8); - assertEquals(patternConstantPrefixBytes(utf8Slice("abc%def"), Optional.empty()), 3); - assertEquals(patternConstantPrefixBytes(utf8Slice("abcdef_"), Optional.empty()), 6); - assertEquals(patternConstantPrefixBytes(utf8Slice("abcdef##_"), Optional.of(utf8Slice("#"))), 8); - assertEquals(patternConstantPrefixBytes(utf8Slice("%abcdef#_"), Optional.of(utf8Slice("#"))), 0); + assertThat(patternConstantPrefixBytes(utf8Slice("abc"), Optional.empty())).isEqualTo(3); + assertThat(patternConstantPrefixBytes(utf8Slice("abc#_def"), Optional.of(utf8Slice("#")))).isEqualTo(8); + assertThat(patternConstantPrefixBytes(utf8Slice("abc##def"), Optional.of(utf8Slice("#")))).isEqualTo(8); + assertThat(patternConstantPrefixBytes(utf8Slice("abc#%def"), Optional.of(utf8Slice("#")))).isEqualTo(8); + assertThat(patternConstantPrefixBytes(utf8Slice("abc%def"), Optional.empty())).isEqualTo(3); + assertThat(patternConstantPrefixBytes(utf8Slice("abcdef_"), Optional.empty())).isEqualTo(6); + assertThat(patternConstantPrefixBytes(utf8Slice("abcdef##_"), Optional.of(utf8Slice("#")))).isEqualTo(8); + assertThat(patternConstantPrefixBytes(utf8Slice("%abcdef#_"), Optional.of(utf8Slice("#")))).isEqualTo(0); assertThatThrownBy(() -> patternConstantPrefixBytes(utf8Slice("#"), Optional.of(utf8Slice("#")))) .isInstanceOf(TrinoException.class) .hasMessage("Escape character must be followed by '%', '_' or the escape character itself"); @@ -327,10 +324,10 @@ public void testPatternConstantPrefixBytes() @Test public void testUnescapeValidLikePattern() { - assertEquals(unescapeLiteralLikePattern(utf8Slice("abc"), Optional.empty()), utf8Slice("abc")); - assertEquals(unescapeLiteralLikePattern(utf8Slice("abc#_"), Optional.of(utf8Slice("#"))), utf8Slice("abc_")); - assertEquals(unescapeLiteralLikePattern(utf8Slice("a##bc#_"), Optional.of(utf8Slice("#"))), utf8Slice("a#bc_")); - assertEquals(unescapeLiteralLikePattern(utf8Slice("a###_bc"), Optional.of(utf8Slice("#"))), utf8Slice("a#_bc")); + assertThat(unescapeLiteralLikePattern(utf8Slice("abc"), Optional.empty())).isEqualTo(utf8Slice("abc")); + assertThat(unescapeLiteralLikePattern(utf8Slice("abc#_"), Optional.of(utf8Slice("#")))).isEqualTo(utf8Slice("abc_")); + assertThat(unescapeLiteralLikePattern(utf8Slice("a##bc#_"), Optional.of(utf8Slice("#")))).isEqualTo(utf8Slice("a#bc_")); + assertThat(unescapeLiteralLikePattern(utf8Slice("a###_bc"), Optional.of(utf8Slice("#")))).isEqualTo(utf8Slice("a#_bc")); } @Test diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestPageProcessorCompiler.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestPageProcessorCompiler.java index b088d7572d8d..7969bd2d71e7 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestPageProcessorCompiler.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestPageProcessorCompiler.java @@ -48,9 +48,7 @@ import static io.trino.sql.relational.Expressions.constant; import static io.trino.sql.relational.Expressions.field; import static java.util.Collections.singletonList; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestPageProcessorCompiler { @@ -68,7 +66,7 @@ public void testNoCaching() ImmutableList projections = projectionsBuilder.build(); PageProcessor pageProcessor = compiler.compilePageProcessor(Optional.empty(), projections).get(); PageProcessor pageProcessor2 = compiler.compilePageProcessor(Optional.empty(), projections).get(); - assertTrue(pageProcessor != pageProcessor2); + assertThat(pageProcessor != pageProcessor2).isTrue(); } @Test @@ -86,15 +84,15 @@ public void testSanityRLE() page)) .orElseThrow(() -> new AssertionError("page is not present")); - assertEquals(outputPage.getPositionCount(), 100); - assertTrue(outputPage.getBlock(0) instanceof RunLengthEncodedBlock); - assertTrue(outputPage.getBlock(1) instanceof RunLengthEncodedBlock); + assertThat(outputPage.getPositionCount()).isEqualTo(100); + assertThat(outputPage.getBlock(0) instanceof RunLengthEncodedBlock).isTrue(); + assertThat(outputPage.getBlock(1) instanceof RunLengthEncodedBlock).isTrue(); RunLengthEncodedBlock rleBlock = (RunLengthEncodedBlock) outputPage.getBlock(0); - assertEquals(BIGINT.getLong(rleBlock.getValue(), 0), 123L); + assertThat(BIGINT.getLong(rleBlock.getValue(), 0)).isEqualTo(123L); RunLengthEncodedBlock rleBlock1 = (RunLengthEncodedBlock) outputPage.getBlock(1); - assertEquals(VARCHAR.getSlice(rleBlock1.getValue(), 0), varcharValue); + assertThat(VARCHAR.getSlice(rleBlock1.getValue(), 0)).isEqualTo(varcharValue); } @Test @@ -117,11 +115,11 @@ public void testSanityFilterOnDictionary() page)) .orElseThrow(() -> new AssertionError("page is not present")); - assertEquals(outputPage.getPositionCount(), 100); - assertTrue(outputPage.getBlock(0) instanceof DictionaryBlock); + assertThat(outputPage.getPositionCount()).isEqualTo(100); + assertThat(outputPage.getBlock(0) instanceof DictionaryBlock).isTrue(); DictionaryBlock dictionaryBlock = (DictionaryBlock) outputPage.getBlock(0); - assertEquals(dictionaryBlock.getDictionary().getPositionCount(), 10); + assertThat(dictionaryBlock.getDictionary().getPositionCount()).isEqualTo(10); // test filter caching Page outputPage2 = getOnlyElement( @@ -130,12 +128,12 @@ public void testSanityFilterOnDictionary() new DriverYieldSignal(), newSimpleAggregatedMemoryContext().newLocalMemoryContext(PageProcessor.class.getSimpleName()), page)).orElseThrow(() -> new AssertionError("page is not present")); - assertEquals(outputPage2.getPositionCount(), 100); - assertTrue(outputPage2.getBlock(0) instanceof DictionaryBlock); + assertThat(outputPage2.getPositionCount()).isEqualTo(100); + assertThat(outputPage2.getBlock(0) instanceof DictionaryBlock).isTrue(); DictionaryBlock dictionaryBlock2 = (DictionaryBlock) outputPage2.getBlock(0); // both output pages must have the same dictionary - assertEquals(dictionaryBlock2.getDictionary(), dictionaryBlock.getDictionary()); + assertThat(dictionaryBlock2.getDictionary()).isEqualTo(dictionaryBlock.getDictionary()); } @Test @@ -155,11 +153,11 @@ public void testSanityFilterOnRLE() page)) .orElseThrow(() -> new AssertionError("page is not present")); - assertEquals(outputPage.getPositionCount(), 100); - assertTrue(outputPage.getBlock(0) instanceof RunLengthEncodedBlock); + assertThat(outputPage.getPositionCount()).isEqualTo(100); + assertThat(outputPage.getBlock(0) instanceof RunLengthEncodedBlock).isTrue(); RunLengthEncodedBlock rle = (RunLengthEncodedBlock) outputPage.getBlock(0); - assertEquals(BIGINT.getLong(rle.getValue(), 0), 5L); + assertThat(BIGINT.getLong(rle.getValue(), 0)).isEqualTo(5L); } @Test @@ -176,11 +174,11 @@ public void testSanityColumnarDictionary() page)) .orElseThrow(() -> new AssertionError("page is not present")); - assertEquals(outputPage.getPositionCount(), 100); - assertTrue(outputPage.getBlock(0) instanceof DictionaryBlock); + assertThat(outputPage.getPositionCount()).isEqualTo(100); + assertThat(outputPage.getBlock(0) instanceof DictionaryBlock).isTrue(); DictionaryBlock dictionaryBlock = (DictionaryBlock) outputPage.getBlock(0); - assertEquals(dictionaryBlock.getDictionary().getPositionCount(), 10); + assertThat(dictionaryBlock.getDictionary().getPositionCount()).isEqualTo(10); } @Test @@ -195,7 +193,7 @@ public void testNonDeterministicProject() PageProcessor processor = compiler.compilePageProcessor(Optional.empty(), ImmutableList.of(lessThanRandomExpression), MAX_BATCH_SIZE).get(); - assertFalse(isDeterministic(lessThanRandomExpression)); + assertThat(isDeterministic(lessThanRandomExpression)).isFalse(); Page page = new Page(createLongDictionaryBlock(1, 100)); Page outputPage = getOnlyElement( @@ -205,7 +203,7 @@ public void testNonDeterministicProject() newSimpleAggregatedMemoryContext().newLocalMemoryContext(PageProcessor.class.getSimpleName()), page)) .orElseThrow(() -> new AssertionError("page is not present")); - assertFalse(outputPage.getBlock(0) instanceof DictionaryBlock); + assertThat(outputPage.getBlock(0) instanceof DictionaryBlock).isFalse(); } private static Block createDictionaryBlock(Slice[] expectedValues, int positionCount) diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestParametricScalarFunctionImplementationValidation.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestParametricScalarFunctionImplementationValidation.java index b4ef15c1fe59..4a8e28b023a5 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestParametricScalarFunctionImplementationValidation.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestParametricScalarFunctionImplementationValidation.java @@ -26,8 +26,8 @@ import static io.trino.spi.function.InvocationConvention.InvocationReturnConvention.FAIL_ON_NULL; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.util.Reflection.methodHandle; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestParametricScalarFunctionImplementationValidation { @@ -44,7 +44,7 @@ public void testConnectorSessionPosition() FAIL_ON_NULL, ImmutableList.of(NEVER_NULL, NEVER_NULL), validFunctionMethodHandle); - assertEquals(validFunction.getChoices().get(0).getMethodHandle(), validFunctionMethodHandle); + assertThat(validFunction.getChoices().get(0).getMethodHandle()).isEqualTo(validFunctionMethodHandle); assertThatThrownBy(() -> new ChoicesSpecializedSqlScalarFunction( signature, @@ -62,7 +62,7 @@ public void testConnectorSessionPosition() ImmutableList.of(NEVER_NULL, NEVER_NULL), validFunctionWithInstanceFactoryMethodHandle, Optional.of(STATE_FACTORY)); - assertEquals(validFunctionWithInstanceFactory.getChoices().get(0).getMethodHandle(), validFunctionWithInstanceFactoryMethodHandle); + assertThat(validFunctionWithInstanceFactory.getChoices().get(0).getMethodHandle()).isEqualTo(validFunctionWithInstanceFactoryMethodHandle); assertThatThrownBy(() -> new ChoicesSpecializedSqlScalarFunction( signature, diff --git a/core/trino-main/src/test/java/io/trino/operator/unnest/BenchmarkUnnestOperator.java b/core/trino-main/src/test/java/io/trino/operator/unnest/BenchmarkUnnestOperator.java index abac31c7aa10..987930b226d8 100644 --- a/core/trino-main/src/test/java/io/trino/operator/unnest/BenchmarkUnnestOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/unnest/BenchmarkUnnestOperator.java @@ -63,7 +63,7 @@ import static io.trino.util.StructuralTestUtil.mapType; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; @State(Scope.Thread) @OutputTimeUnit(TimeUnit.MICROSECONDS) @@ -248,16 +248,16 @@ else if (!finishing) { public void testBlocks() { Block block = produceBlock(new ArrayType(VARCHAR), 100, 0.1f, 50); - assertEquals(block.getPositionCount(), 100); + assertThat(block.getPositionCount()).isEqualTo(100); block = produceBlock(mapType(VARCHAR, INTEGER), 100, 0.1f, 50); - assertEquals(block.getPositionCount(), 100); + assertThat(block.getPositionCount()).isEqualTo(100); block = produceBlock(RowType.anonymous(Arrays.asList(VARCHAR, VARCHAR)), 100, 0.1f, 50); - assertEquals(block.getPositionCount(), 100); + assertThat(block.getPositionCount()).isEqualTo(100); block = produceBlock(new ArrayType(RowType.anonymous(Arrays.asList(VARCHAR, VARCHAR, VARCHAR))), 100, 0.1f, 50); - assertEquals(block.getPositionCount(), 100); + assertThat(block.getPositionCount()).isEqualTo(100); } public static void main(String[] args) diff --git a/core/trino-main/src/test/java/io/trino/operator/unnest/TestUnnestOperator.java b/core/trino-main/src/test/java/io/trino/operator/unnest/TestUnnestOperator.java index 657b44229be5..3351a1ceb794 100644 --- a/core/trino-main/src/test/java/io/trino/operator/unnest/TestUnnestOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/unnest/TestUnnestOperator.java @@ -76,8 +76,8 @@ import static java.lang.Double.POSITIVE_INFINITY; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; -import static org.testng.Assert.assertTrue; @TestInstance(PER_METHOD) public class TestUnnestOperator @@ -535,7 +535,7 @@ private void testUnnest(List inputPages, List replicatedTypes, List< break; } - assertTrue(outputPage.getPositionCount() <= 1000); + assertThat(outputPage.getPositionCount() <= 1000).isTrue(); outputPages.add(outputPage); } diff --git a/core/trino-main/src/test/java/io/trino/operator/unnest/TestingUnnesterUtil.java b/core/trino-main/src/test/java/io/trino/operator/unnest/TestingUnnesterUtil.java index 9825ee8f016c..00c87f6e97d4 100644 --- a/core/trino-main/src/test/java/io/trino/operator/unnest/TestingUnnesterUtil.java +++ b/core/trino-main/src/test/java/io/trino/operator/unnest/TestingUnnesterUtil.java @@ -40,8 +40,7 @@ import static java.lang.Math.max; import static java.lang.String.format; import static java.util.Objects.requireNonNull; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public final class TestingUnnesterUtil { @@ -195,12 +194,12 @@ public static void validateTestInput(int[] requiredOutputCounts, int[] unnestedL // verify lengths int positionCount = slices.length; - assertEquals(requiredOutputCounts.length, positionCount); - assertEquals(unnestedLengths.length, positionCount); + assertThat(requiredOutputCounts.length).isEqualTo(positionCount); + assertThat(unnestedLengths.length).isEqualTo(positionCount); // Unnested array lengths must be <= required output count for (int i = 0; i < requiredOutputCounts.length; i++) { - assertTrue(unnestedLengths[i] <= requiredOutputCounts[i]); + assertThat(unnestedLengths[i] <= requiredOutputCounts[i]).isTrue(); } // Elements should have the right shape for every field @@ -208,12 +207,12 @@ public static void validateTestInput(int[] requiredOutputCounts, int[] unnestedL Slice[][] entry = slices[index]; int entryLength = entry != null ? entry.length : 0; - assertEquals(entryLength, unnestedLengths[index]); + assertThat(entryLength).isEqualTo(unnestedLengths[index]); // Verify number of fields for (int i = 0; i < entryLength; i++) { if (entry[i] != null) { - assertEquals(entry[i].length, fieldCount); + assertThat(entry[i].length).isEqualTo(fieldCount); } } } @@ -221,7 +220,7 @@ public static void validateTestInput(int[] requiredOutputCounts, int[] unnestedL public static Slice[] createReplicatedOutputSlice(Slice[] input, int[] counts) { - assertEquals(input.length, counts.length); + assertThat(input.length).isEqualTo(counts.length); int outputLength = 0; for (int i = 0; i < input.length; i++) { @@ -272,7 +271,7 @@ static UnnestedLengths calculateMaxCardinalities(Page page, List replicate for (int i = 0; i < unnestChannelCount; i++) { Type type = unnestTypes.get(i); Block block = page.getBlock(replicatedChannelCount + i); - assertTrue(type instanceof ArrayType || type instanceof MapType); + assertThat(type instanceof ArrayType || type instanceof MapType).isTrue(); if (type instanceof ArrayType) { ColumnarArray columnarArray = ColumnarArray.toColumnarArray(block); @@ -318,7 +317,7 @@ static Page buildExpectedPage( int[] maxCardinalities = unnestedLengths.getMaxCardinalities(); int channelCount = page.getChannelCount(); - assertTrue(channelCount > 1); + assertThat(channelCount > 1).isTrue(); Block[] outputBlocks = new Block[outputTypes.size()]; diff --git a/core/trino-main/src/test/java/io/trino/operator/window/TestRowNumberFunction.java b/core/trino-main/src/test/java/io/trino/operator/window/TestRowNumberFunction.java index dd0361d8434f..a91581261776 100644 --- a/core/trino-main/src/test/java/io/trino/operator/window/TestRowNumberFunction.java +++ b/core/trino-main/src/test/java/io/trino/operator/window/TestRowNumberFunction.java @@ -24,7 +24,7 @@ import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.testing.MaterializedResult.resultBuilder; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestRowNumberFunction extends AbstractTestWindowFunction @@ -60,10 +60,9 @@ public void testRowNumber() assertWindowQuery("row_number() OVER ()", expected); assertWindowQuery("row_number() OVER (ORDER BY orderkey)", expected); - assertEquals(executeWindowQueryWithNulls("row_number() OVER ()").getMaterializedRows().stream() - .map(row -> row.getField(2)) - .collect(Collectors.toList()), - ImmutableList.of(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L)); + assertThat(executeWindowQueryWithNulls("row_number() OVER ()").getMaterializedRows().stream() + .map(row -> row.getField(2)) + .collect(Collectors.toList())).isEqualTo(ImmutableList.of(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L)); assertWindowQueryWithNulls("row_number() OVER (ORDER BY orderkey, orderstatus)", expectedWithNulls); } } diff --git a/core/trino-main/src/test/java/io/trino/operator/window/matcher/TestIrRowPatternToProgramRewriter.java b/core/trino-main/src/test/java/io/trino/operator/window/matcher/TestIrRowPatternToProgramRewriter.java index 8d7bbc6acbb2..36414b5c762b 100644 --- a/core/trino-main/src/test/java/io/trino/operator/window/matcher/TestIrRowPatternToProgramRewriter.java +++ b/core/trino-main/src/test/java/io/trino/operator/window/matcher/TestIrRowPatternToProgramRewriter.java @@ -35,7 +35,7 @@ import static io.trino.sql.planner.rowpattern.Patterns.rangeQuantified; import static io.trino.sql.planner.rowpattern.Patterns.starQuantified; import static io.trino.sql.planner.rowpattern.Patterns.start; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestIrRowPatternToProgramRewriter { @@ -288,6 +288,6 @@ private void assertRewritten(IrRowPattern pattern, List expected) { Program program = IrRowPatternToProgramRewriter.rewrite(pattern, LABEL_MAPPING); - assertEquals(program.getInstructions(), expected); + assertThat(program.getInstructions()).isEqualTo(expected); } } diff --git a/core/trino-main/src/test/java/io/trino/security/TestAccessControlManager.java b/core/trino-main/src/test/java/io/trino/security/TestAccessControlManager.java index dad20b7a6465..7545f5d5227e 100644 --- a/core/trino-main/src/test/java/io/trino/security/TestAccessControlManager.java +++ b/core/trino-main/src/test/java/io/trino/security/TestAccessControlManager.java @@ -76,7 +76,6 @@ import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestAccessControlManager { @@ -124,11 +123,11 @@ public void testReadOnlySystemAccessControl() accessControlManager.checkCanSelectFromColumns(context, tableName, ImmutableSet.of("column")); accessControlManager.checkCanCreateViewWithSelectFromColumns(context, tableName, ImmutableSet.of("column")); Set catalogs = ImmutableSet.of(TEST_CATALOG_NAME); - assertEquals(accessControlManager.filterCatalogs(context, catalogs), catalogs); + assertThat(accessControlManager.filterCatalogs(context, catalogs)).isEqualTo(catalogs); Set schemas = ImmutableSet.of("schema"); - assertEquals(accessControlManager.filterSchemas(context, TEST_CATALOG_NAME, schemas), schemas); + assertThat(accessControlManager.filterSchemas(context, TEST_CATALOG_NAME, schemas)).isEqualTo(schemas); Set tableNames = ImmutableSet.of(new SchemaTableName("schema", "table")); - assertEquals(accessControlManager.filterTables(context, TEST_CATALOG_NAME, tableNames), tableNames); + assertThat(accessControlManager.filterTables(context, TEST_CATALOG_NAME, tableNames)).isEqualTo(tableNames); }); assertThatThrownBy(() -> transaction(transactionManager, metadata, accessControlManager) @@ -149,8 +148,8 @@ public void testSetAccessControl() accessControlManager.loadSystemAccessControl("test", ImmutableMap.of()); accessControlManager.checkCanSetUser(Optional.of(PRINCIPAL), USER_NAME); - assertEquals(accessControlFactory.getCheckedUserName(), USER_NAME); - assertEquals(accessControlFactory.getCheckedPrincipal(), Optional.of(PRINCIPAL)); + assertThat(accessControlFactory.getCheckedUserName()).isEqualTo(USER_NAME); + assertThat(accessControlFactory.getCheckedPrincipal()).isEqualTo(Optional.of(PRINCIPAL)); } @Test diff --git a/core/trino-main/src/test/java/io/trino/security/TestFileBasedSystemAccessControl.java b/core/trino-main/src/test/java/io/trino/security/TestFileBasedSystemAccessControl.java index f905dd2ffb8b..010c930212bf 100644 --- a/core/trino-main/src/test/java/io/trino/security/TestFileBasedSystemAccessControl.java +++ b/core/trino-main/src/test/java/io/trino/security/TestFileBasedSystemAccessControl.java @@ -53,9 +53,9 @@ import static io.trino.transaction.InMemoryTransactionManager.createTestTransactionManager; import static io.trino.transaction.TransactionBuilder.transaction; import static java.lang.Thread.sleep; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.util.Files.newTemporaryFile; -import static org.testng.Assert.assertEquals; public class TestFileBasedSystemAccessControl { @@ -226,13 +226,13 @@ public void testCatalogOperations() transaction(transactionManager, metadata, accessControlManager) .execute(transactionId -> { - assertEquals(accessControlManager.filterCatalogs(new SecurityContext(transactionId, admin, queryId, queryStart), allCatalogs), allCatalogs); + assertThat(accessControlManager.filterCatalogs(new SecurityContext(transactionId, admin, queryId, queryStart), allCatalogs)).isEqualTo(allCatalogs); Set aliceCatalogs = ImmutableSet.of("open-to-all", "alice-catalog", "all-allowed", "staff-catalog"); - assertEquals(accessControlManager.filterCatalogs(new SecurityContext(transactionId, alice, queryId, queryStart), allCatalogs), aliceCatalogs); + assertThat(accessControlManager.filterCatalogs(new SecurityContext(transactionId, alice, queryId, queryStart), allCatalogs)).isEqualTo(aliceCatalogs); Set bobCatalogs = ImmutableSet.of("open-to-all", "all-allowed", "staff-catalog"); - assertEquals(accessControlManager.filterCatalogs(new SecurityContext(transactionId, bob, queryId, queryStart), allCatalogs), bobCatalogs); + assertThat(accessControlManager.filterCatalogs(new SecurityContext(transactionId, bob, queryId, queryStart), allCatalogs)).isEqualTo(bobCatalogs); Set nonAsciiUserCatalogs = ImmutableSet.of("open-to-all", "all-allowed", "\u0200\u0200\u0200"); - assertEquals(accessControlManager.filterCatalogs(new SecurityContext(transactionId, nonAsciiUser, queryId, queryStart), allCatalogs), nonAsciiUserCatalogs); + assertThat(accessControlManager.filterCatalogs(new SecurityContext(transactionId, nonAsciiUser, queryId, queryStart), allCatalogs)).isEqualTo(nonAsciiUserCatalogs); }); } @@ -245,13 +245,13 @@ public void testCatalogOperationsReadOnly() transaction(transactionManager, metadata, accessControlManager) .execute(transactionId -> { - assertEquals(accessControlManager.filterCatalogs(new SecurityContext(transactionId, admin, queryId, queryStart), allCatalogs), allCatalogs); + assertThat(accessControlManager.filterCatalogs(new SecurityContext(transactionId, admin, queryId, queryStart), allCatalogs)).isEqualTo(allCatalogs); Set aliceCatalogs = ImmutableSet.of("open-to-all", "alice-catalog", "all-allowed"); - assertEquals(accessControlManager.filterCatalogs(new SecurityContext(transactionId, alice, queryId, queryStart), allCatalogs), aliceCatalogs); + assertThat(accessControlManager.filterCatalogs(new SecurityContext(transactionId, alice, queryId, queryStart), allCatalogs)).isEqualTo(aliceCatalogs); Set bobCatalogs = ImmutableSet.of("open-to-all", "all-allowed"); - assertEquals(accessControlManager.filterCatalogs(new SecurityContext(transactionId, bob, queryId, queryStart), allCatalogs), bobCatalogs); + assertThat(accessControlManager.filterCatalogs(new SecurityContext(transactionId, bob, queryId, queryStart), allCatalogs)).isEqualTo(bobCatalogs); Set nonAsciiUserCatalogs = ImmutableSet.of("open-to-all", "all-allowed", "\u0200\u0200\u0200"); - assertEquals(accessControlManager.filterCatalogs(new SecurityContext(transactionId, nonAsciiUser, queryId, queryStart), allCatalogs), nonAsciiUserCatalogs); + assertThat(accessControlManager.filterCatalogs(new SecurityContext(transactionId, nonAsciiUser, queryId, queryStart), allCatalogs)).isEqualTo(nonAsciiUserCatalogs); }); } @@ -265,8 +265,8 @@ public void testSchemaOperations() transaction(transactionManager, metadata, accessControlManager) .execute(transactionId -> { Set aliceSchemas = ImmutableSet.of("schema"); - assertEquals(accessControlManager.filterSchemas(new SecurityContext(transactionId, alice, queryId, queryStart), "alice-catalog", aliceSchemas), aliceSchemas); - assertEquals(accessControlManager.filterSchemas(new SecurityContext(transactionId, bob, queryId, queryStart), "alice-catalog", aliceSchemas), ImmutableSet.of()); + assertThat(accessControlManager.filterSchemas(new SecurityContext(transactionId, alice, queryId, queryStart), "alice-catalog", aliceSchemas)).isEqualTo(aliceSchemas); + assertThat(accessControlManager.filterSchemas(new SecurityContext(transactionId, bob, queryId, queryStart), "alice-catalog", aliceSchemas)).isEqualTo(ImmutableSet.of()); accessControlManager.checkCanCreateSchema(new SecurityContext(transactionId, alice, queryId, queryStart), aliceSchema, ImmutableMap.of()); accessControlManager.checkCanDropSchema(new SecurityContext(transactionId, alice, queryId, queryStart), aliceSchema); @@ -289,8 +289,8 @@ public void testSchemaOperationsReadOnly() transaction(transactionManager, metadata, accessControlManager) .execute(transactionId -> { Set aliceSchemas = ImmutableSet.of("schema"); - assertEquals(accessControlManager.filterSchemas(new SecurityContext(transactionId, alice, queryId, queryStart), "alice-catalog", aliceSchemas), aliceSchemas); - assertEquals(accessControlManager.filterSchemas(new SecurityContext(transactionId, bob, queryId, queryStart), "alice-catalog", aliceSchemas), ImmutableSet.of()); + assertThat(accessControlManager.filterSchemas(new SecurityContext(transactionId, alice, queryId, queryStart), "alice-catalog", aliceSchemas)).isEqualTo(aliceSchemas); + assertThat(accessControlManager.filterSchemas(new SecurityContext(transactionId, bob, queryId, queryStart), "alice-catalog", aliceSchemas)).isEqualTo(ImmutableSet.of()); accessControlManager.checkCanShowSchemas(new SecurityContext(transactionId, alice, queryId, queryStart), "alice-catalog"); }); @@ -330,12 +330,12 @@ public void testTableOperations() SecurityContext bobContext = new SecurityContext(transactionId, bob, queryId, queryStart); SecurityContext nonAsciiContext = new SecurityContext(transactionId, nonAsciiUser, queryId, queryStart); - assertEquals(accessControlManager.filterTables(aliceContext, "alice-catalog", aliceTables), aliceTables); - assertEquals(accessControlManager.filterTables(aliceContext, "staff-catalog", aliceTables), aliceTables); - assertEquals(accessControlManager.filterTables(bobContext, "alice-catalog", aliceTables), ImmutableSet.of()); - assertEquals(accessControlManager.filterTables(bobContext, "staff-catalog", aliceTables), aliceTables); - assertEquals(accessControlManager.filterTables(nonAsciiContext, "alice-catalog", aliceTables), ImmutableSet.of()); - assertEquals(accessControlManager.filterTables(nonAsciiContext, "staff-catalog", aliceTables), ImmutableSet.of()); + assertThat(accessControlManager.filterTables(aliceContext, "alice-catalog", aliceTables)).isEqualTo(aliceTables); + assertThat(accessControlManager.filterTables(aliceContext, "staff-catalog", aliceTables)).isEqualTo(aliceTables); + assertThat(accessControlManager.filterTables(bobContext, "alice-catalog", aliceTables)).isEqualTo(ImmutableSet.of()); + assertThat(accessControlManager.filterTables(bobContext, "staff-catalog", aliceTables)).isEqualTo(aliceTables); + assertThat(accessControlManager.filterTables(nonAsciiContext, "alice-catalog", aliceTables)).isEqualTo(ImmutableSet.of()); + assertThat(accessControlManager.filterTables(nonAsciiContext, "staff-catalog", aliceTables)).isEqualTo(ImmutableSet.of()); accessControlManager.checkCanCreateTable(aliceContext, aliceTable, Map.of()); accessControlManager.checkCanDropTable(aliceContext, aliceTable); @@ -475,8 +475,8 @@ public void testTableOperationsReadOnly() transaction(transactionManager, metadata, accessControlManager) .execute(transactionId -> { Set aliceTables = ImmutableSet.of(new SchemaTableName("schema", "table")); - assertEquals(accessControlManager.filterTables(new SecurityContext(transactionId, alice, queryId, queryStart), "alice-catalog", aliceTables), aliceTables); - assertEquals(accessControlManager.filterTables(new SecurityContext(transactionId, bob, queryId, queryStart), "alice-catalog", aliceTables), ImmutableSet.of()); + assertThat(accessControlManager.filterTables(new SecurityContext(transactionId, alice, queryId, queryStart), "alice-catalog", aliceTables)).isEqualTo(aliceTables); + assertThat(accessControlManager.filterTables(new SecurityContext(transactionId, bob, queryId, queryStart), "alice-catalog", aliceTables)).isEqualTo(ImmutableSet.of()); accessControlManager.checkCanSelectFromColumns(new SecurityContext(transactionId, alice, queryId, queryStart), aliceTable, ImmutableSet.of()); }); diff --git a/core/trino-main/src/test/java/io/trino/security/TestGroupProviderManager.java b/core/trino-main/src/test/java/io/trino/security/TestGroupProviderManager.java index f8844edff1d0..6173db8196fa 100644 --- a/core/trino-main/src/test/java/io/trino/security/TestGroupProviderManager.java +++ b/core/trino-main/src/test/java/io/trino/security/TestGroupProviderManager.java @@ -24,7 +24,7 @@ import java.util.Map; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestGroupProviderManager { @@ -53,8 +53,8 @@ public void testGroupProviderIsLoaded() GroupProviderManager groupProviderManager = new GroupProviderManager(); groupProviderManager.addGroupProviderFactory(TEST_GROUP_PROVIDER_FACTORY); groupProviderManager.loadConfiguredGroupProvider(tempFile.file()); - assertEquals(groupProviderManager.getGroups("alice"), ImmutableSet.of("test", "alice")); - assertEquals(groupProviderManager.getGroups("bob"), ImmutableSet.of("test", "bob")); + assertThat(groupProviderManager.getGroups("alice")).isEqualTo(ImmutableSet.of("test", "alice")); + assertThat(groupProviderManager.getGroups("bob")).isEqualTo(ImmutableSet.of("test", "bob")); } } } diff --git a/core/trino-main/src/test/java/io/trino/server/TestBasicQueryInfo.java b/core/trino-main/src/test/java/io/trino/server/TestBasicQueryInfo.java index 34cecfbf5b2d..be5cbfc1eec2 100644 --- a/core/trino-main/src/test/java/io/trino/server/TestBasicQueryInfo.java +++ b/core/trino-main/src/test/java/io/trino/server/TestBasicQueryInfo.java @@ -39,8 +39,7 @@ import static io.trino.server.DynamicFilterService.DynamicFiltersStats; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestBasicQueryInfo { @@ -167,39 +166,39 @@ public void testConstructor() false, new NodeVersion("test"))); - assertEquals(basicInfo.getQueryId().getId(), "0"); - assertEquals(basicInfo.getState(), RUNNING); - assertTrue(basicInfo.isScheduled()); // from query stats - assertEquals(basicInfo.getQuery(), "SELECT 4"); - assertEquals(basicInfo.getQueryType().get(), QueryType.SELECT); + assertThat(basicInfo.getQueryId().getId()).isEqualTo("0"); + assertThat(basicInfo.getState()).isEqualTo(RUNNING); + assertThat(basicInfo.isScheduled()).isTrue(); // from query stats + assertThat(basicInfo.getQuery()).isEqualTo("SELECT 4"); + assertThat(basicInfo.getQueryType().get()).isEqualTo(QueryType.SELECT); - assertEquals(basicInfo.getQueryStats().getCreateTime(), DateTime.parse("1991-09-06T05:00-05:30")); - assertEquals(basicInfo.getQueryStats().getEndTime(), DateTime.parse("1991-09-06T06:00-05:30")); - assertEquals(basicInfo.getQueryStats().getElapsedTime(), new Duration(8, MINUTES)); - assertEquals(basicInfo.getQueryStats().getExecutionTime(), new Duration(44, MINUTES)); + assertThat(basicInfo.getQueryStats().getCreateTime()).isEqualTo(DateTime.parse("1991-09-06T05:00-05:30")); + assertThat(basicInfo.getQueryStats().getEndTime()).isEqualTo(DateTime.parse("1991-09-06T06:00-05:30")); + assertThat(basicInfo.getQueryStats().getElapsedTime()).isEqualTo(new Duration(8, MINUTES)); + assertThat(basicInfo.getQueryStats().getExecutionTime()).isEqualTo(new Duration(44, MINUTES)); - assertEquals(basicInfo.getQueryStats().getTotalDrivers(), 17); - assertEquals(basicInfo.getQueryStats().getQueuedDrivers(), 18); - assertEquals(basicInfo.getQueryStats().getRunningDrivers(), 19); - assertEquals(basicInfo.getQueryStats().getCompletedDrivers(), 20); + assertThat(basicInfo.getQueryStats().getTotalDrivers()).isEqualTo(17); + assertThat(basicInfo.getQueryStats().getQueuedDrivers()).isEqualTo(18); + assertThat(basicInfo.getQueryStats().getRunningDrivers()).isEqualTo(19); + assertThat(basicInfo.getQueryStats().getCompletedDrivers()).isEqualTo(20); - assertEquals(basicInfo.getQueryStats().getCumulativeUserMemory(), 21.0); - assertEquals(basicInfo.getQueryStats().getFailedCumulativeUserMemory(), 22.0); - assertEquals(basicInfo.getQueryStats().getUserMemoryReservation(), DataSize.valueOf("23GB")); - assertEquals(basicInfo.getQueryStats().getTotalMemoryReservation(), DataSize.valueOf("25GB")); - assertEquals(basicInfo.getQueryStats().getPeakUserMemoryReservation(), DataSize.valueOf("26GB")); - assertEquals(basicInfo.getQueryStats().getTotalScheduledTime(), new Duration(32, MINUTES)); - assertEquals(basicInfo.getQueryStats().getFailedScheduledTime(), new Duration(33, MINUTES)); - assertEquals(basicInfo.getQueryStats().getTotalCpuTime(), new Duration(34, MINUTES)); - assertEquals(basicInfo.getQueryStats().getFailedCpuTime(), new Duration(35, MINUTES)); + assertThat(basicInfo.getQueryStats().getCumulativeUserMemory()).isEqualTo(21.0); + assertThat(basicInfo.getQueryStats().getFailedCumulativeUserMemory()).isEqualTo(22.0); + assertThat(basicInfo.getQueryStats().getUserMemoryReservation()).isEqualTo(DataSize.valueOf("23GB")); + assertThat(basicInfo.getQueryStats().getTotalMemoryReservation()).isEqualTo(DataSize.valueOf("25GB")); + assertThat(basicInfo.getQueryStats().getPeakUserMemoryReservation()).isEqualTo(DataSize.valueOf("26GB")); + assertThat(basicInfo.getQueryStats().getTotalScheduledTime()).isEqualTo(new Duration(32, MINUTES)); + assertThat(basicInfo.getQueryStats().getFailedScheduledTime()).isEqualTo(new Duration(33, MINUTES)); + assertThat(basicInfo.getQueryStats().getTotalCpuTime()).isEqualTo(new Duration(34, MINUTES)); + assertThat(basicInfo.getQueryStats().getFailedCpuTime()).isEqualTo(new Duration(35, MINUTES)); - assertEquals(basicInfo.getQueryStats().isFullyBlocked(), true); - assertEquals(basicInfo.getQueryStats().getBlockedReasons(), ImmutableSet.of(BlockedReason.WAITING_FOR_MEMORY)); + assertThat(basicInfo.getQueryStats().isFullyBlocked()).isEqualTo(true); + assertThat(basicInfo.getQueryStats().getBlockedReasons()).isEqualTo(ImmutableSet.of(BlockedReason.WAITING_FOR_MEMORY)); - assertEquals(basicInfo.getQueryStats().getProgressPercentage(), OptionalDouble.of(100)); - assertEquals(basicInfo.getQueryStats().getRunningPercentage(), OptionalDouble.of(0)); + assertThat(basicInfo.getQueryStats().getProgressPercentage()).isEqualTo(OptionalDouble.of(100)); + assertThat(basicInfo.getQueryStats().getRunningPercentage()).isEqualTo(OptionalDouble.of(0)); - assertEquals(basicInfo.getErrorCode(), StandardErrorCode.ABANDONED_QUERY.toErrorCode()); - assertEquals(basicInfo.getErrorType(), StandardErrorCode.ABANDONED_QUERY.toErrorCode().getType()); + assertThat(basicInfo.getErrorCode()).isEqualTo(StandardErrorCode.ABANDONED_QUERY.toErrorCode()); + assertThat(basicInfo.getErrorType()).isEqualTo(StandardErrorCode.ABANDONED_QUERY.toErrorCode().getType()); } } diff --git a/core/trino-main/src/test/java/io/trino/server/TestDynamicFilterService.java b/core/trino-main/src/test/java/io/trino/server/TestDynamicFilterService.java index d07d913d4747..e8c83eeaeedd 100644 --- a/core/trino-main/src/test/java/io/trino/server/TestDynamicFilterService.java +++ b/core/trino-main/src/test/java/io/trino/server/TestDynamicFilterService.java @@ -89,10 +89,6 @@ import static io.trino.util.DynamicFiltersTestUtil.getSimplifiedDomainString; import static java.util.stream.Collectors.joining; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; public class TestDynamicFilterService { @@ -108,47 +104,45 @@ public void testDynamicFilterSummaryCompletion() dynamicFilterService.registerQuery(queryId, session, ImmutableSet.of(filterId), ImmutableSet.of(filterId), ImmutableSet.of()); dynamicFilterService.stageCannotScheduleMoreTasks(stageId, 0, 3); - assertFalse(dynamicFilterService.getSummary(queryId, filterId).isPresent()); + assertThat(dynamicFilterService.getSummary(queryId, filterId).isPresent()).isFalse(); // assert initial dynamic filtering stats DynamicFiltersStats stats = dynamicFilterService.getDynamicFilteringStats(queryId, session); - assertEquals(stats.getTotalDynamicFilters(), 1); - assertEquals(stats.getDynamicFiltersCompleted(), 0); - assertEquals(stats.getLazyDynamicFilters(), 1); - assertEquals(stats.getReplicatedDynamicFilters(), 0); + assertThat(stats.getTotalDynamicFilters()).isEqualTo(1); + assertThat(stats.getDynamicFiltersCompleted()).isEqualTo(0); + assertThat(stats.getLazyDynamicFilters()).isEqualTo(1); + assertThat(stats.getReplicatedDynamicFilters()).isEqualTo(0); dynamicFilterService.addTaskDynamicFilters( new TaskId(stageId, 0, 0), ImmutableMap.of(filterId, singleValue(INTEGER, 1L))); - assertFalse(dynamicFilterService.getSummary(queryId, filterId).isPresent()); + assertThat(dynamicFilterService.getSummary(queryId, filterId).isPresent()).isFalse(); stats = dynamicFilterService.getDynamicFilteringStats(queryId, session); - assertEquals(stats.getDynamicFiltersCompleted(), 0); + assertThat(stats.getDynamicFiltersCompleted()).isEqualTo(0); dynamicFilterService.addTaskDynamicFilters( new TaskId(stageId, 1, 0), ImmutableMap.of(filterId, singleValue(INTEGER, 2L))); - assertFalse(dynamicFilterService.getSummary(queryId, filterId).isPresent()); + assertThat(dynamicFilterService.getSummary(queryId, filterId).isPresent()).isFalse(); stats = dynamicFilterService.getDynamicFilteringStats(queryId, session); - assertEquals(stats.getDynamicFiltersCompleted(), 0); + assertThat(stats.getDynamicFiltersCompleted()).isEqualTo(0); dynamicFilterService.addTaskDynamicFilters( new TaskId(stageId, 2, 0), ImmutableMap.of(filterId, singleValue(INTEGER, 3L))); Optional summary = dynamicFilterService.getSummary(queryId, filterId); - assertTrue(summary.isPresent()); - assertEquals(summary.get(), multipleValues(INTEGER, ImmutableList.of(1L, 2L, 3L))); + assertThat(summary.isPresent()).isTrue(); + assertThat(summary.get()).isEqualTo(multipleValues(INTEGER, ImmutableList.of(1L, 2L, 3L))); stats = dynamicFilterService.getDynamicFilteringStats(queryId, session); - assertEquals(stats.getDynamicFiltersCompleted(), 1); - assertEquals(stats.getLazyDynamicFilters(), 1); - assertEquals(stats.getReplicatedDynamicFilters(), 0); - assertEquals( - stats.getDynamicFilterDomainStats(), - ImmutableList.of(new DynamicFilterDomainStats( - filterId, - getSimplifiedDomainString(1L, 3L, 3, INTEGER)))); + assertThat(stats.getDynamicFiltersCompleted()).isEqualTo(1); + assertThat(stats.getLazyDynamicFilters()).isEqualTo(1); + assertThat(stats.getReplicatedDynamicFilters()).isEqualTo(0); + assertThat(stats.getDynamicFilterDomainStats()).isEqualTo(ImmutableList.of(new DynamicFilterDomainStats( + filterId, + getSimplifiedDomainString(1L, 3L, 3, INTEGER)))); } @Test @@ -192,86 +186,88 @@ symbol2, new TestingColumnHandle("probeColumnA"), symbol3, new TestingColumnHandle("probeColumnB")), symbolAllocator.getTypes()); - assertEquals(dynamicFilter.getColumnsCovered(), Set.of(new TestingColumnHandle("probeColumnA"), new TestingColumnHandle("probeColumnB")), "columns covered"); - assertTrue(dynamicFilter.getCurrentPredicate().isAll()); - assertFalse(dynamicFilter.isComplete()); - assertTrue(dynamicFilter.isAwaitable()); + assertThat(dynamicFilter.getColumnsCovered()) + .describedAs("columns covered") + .isEqualTo(Set.of(new TestingColumnHandle("probeColumnA"), new TestingColumnHandle("probeColumnB"))); + assertThat(dynamicFilter.getCurrentPredicate().isAll()).isTrue(); + assertThat(dynamicFilter.isComplete()).isFalse(); + assertThat(dynamicFilter.isAwaitable()).isTrue(); // assert initial dynamic filtering stats DynamicFiltersStats stats = dynamicFilterService.getDynamicFilteringStats(queryId, session); - assertEquals(stats.getTotalDynamicFilters(), 3); - assertEquals(stats.getDynamicFiltersCompleted(), 0); - assertEquals(stats.getLazyDynamicFilters(), 3); - assertEquals(stats.getReplicatedDynamicFilters(), 0); + assertThat(stats.getTotalDynamicFilters()).isEqualTo(3); + assertThat(stats.getDynamicFiltersCompleted()).isEqualTo(0); + assertThat(stats.getLazyDynamicFilters()).isEqualTo(3); + assertThat(stats.getReplicatedDynamicFilters()).isEqualTo(0); // dynamic filter should be blocked waiting for tuple domain to be provided CompletableFuture blockedFuture = dynamicFilter.isBlocked(); - assertFalse(blockedFuture.isDone()); + assertThat(blockedFuture.isDone()).isFalse(); dynamicFilterService.addTaskDynamicFilters( new TaskId(stageId1, 0, 0), ImmutableMap.of(filterId1, singleValue(INTEGER, 1L))); // tuple domain from two tasks are needed for dynamic filter to be narrowed down - assertTrue(dynamicFilter.getCurrentPredicate().isAll()); - assertFalse(dynamicFilter.isComplete()); - assertTrue(dynamicFilter.isAwaitable()); - assertFalse(blockedFuture.isDone()); + assertThat(dynamicFilter.getCurrentPredicate().isAll()).isTrue(); + assertThat(dynamicFilter.isComplete()).isFalse(); + assertThat(dynamicFilter.isAwaitable()).isTrue(); + assertThat(blockedFuture.isDone()).isFalse(); dynamicFilterService.addTaskDynamicFilters( new TaskId(stageId1, 1, 0), ImmutableMap.of(filterId1, singleValue(INTEGER, 2L))); // dynamic filter (id1) has been collected as tuple domains from two tasks have been provided - assertEquals(dynamicFilter.getCurrentPredicate(), TupleDomain.withColumnDomains(ImmutableMap.of( + assertThat(dynamicFilter.getCurrentPredicate()).isEqualTo(TupleDomain.withColumnDomains(ImmutableMap.of( new TestingColumnHandle("probeColumnA"), multipleValues(INTEGER, ImmutableList.of(1L, 2L))))); - assertTrue(blockedFuture.isDone()); - assertFalse(blockedFuture.isCompletedExceptionally()); + assertThat(blockedFuture.isDone()).isTrue(); + assertThat(blockedFuture.isCompletedExceptionally()).isFalse(); stats = dynamicFilterService.getDynamicFilteringStats(queryId, session); - assertEquals(stats.getDynamicFiltersCompleted(), 1); + assertThat(stats.getDynamicFiltersCompleted()).isEqualTo(1); // there are still more dynamic filters to be collected - assertFalse(dynamicFilter.isComplete()); - assertTrue(dynamicFilter.isAwaitable()); + assertThat(dynamicFilter.isComplete()).isFalse(); + assertThat(dynamicFilter.isAwaitable()).isTrue(); blockedFuture = dynamicFilter.isBlocked(); - assertFalse(blockedFuture.isDone()); + assertThat(blockedFuture.isDone()).isFalse(); dynamicFilterService.addTaskDynamicFilters( new TaskId(stageId2, 0, 0), ImmutableMap.of(filterId2, singleValue(INTEGER, 2L))); // tuple domain from two tasks (stage 2) are needed for dynamic filter to be narrowed down - assertEquals(dynamicFilter.getCurrentPredicate(), TupleDomain.withColumnDomains(ImmutableMap.of( + assertThat(dynamicFilter.getCurrentPredicate()).isEqualTo(TupleDomain.withColumnDomains(ImmutableMap.of( new TestingColumnHandle("probeColumnA"), multipleValues(INTEGER, ImmutableList.of(1L, 2L))))); - assertFalse(dynamicFilter.isComplete()); - assertTrue(dynamicFilter.isAwaitable()); - assertFalse(blockedFuture.isDone()); + assertThat(dynamicFilter.isComplete()).isFalse(); + assertThat(dynamicFilter.isAwaitable()).isTrue(); + assertThat(blockedFuture.isDone()).isFalse(); stats = dynamicFilterService.getDynamicFilteringStats(queryId, session); - assertEquals(stats.getDynamicFiltersCompleted(), 1); + assertThat(stats.getDynamicFiltersCompleted()).isEqualTo(1); dynamicFilterService.addTaskDynamicFilters( new TaskId(stageId2, 1, 0), ImmutableMap.of(filterId2, singleValue(INTEGER, 3L))); // dynamic filter (id2) has been collected as tuple domains from two tasks have been provided - assertEquals(dynamicFilter.getCurrentPredicate(), TupleDomain.withColumnDomains(ImmutableMap.of( + assertThat(dynamicFilter.getCurrentPredicate()).isEqualTo(TupleDomain.withColumnDomains(ImmutableMap.of( new TestingColumnHandle("probeColumnA"), singleValue(INTEGER, 2L)))); - assertTrue(blockedFuture.isDone()); - assertFalse(blockedFuture.isCompletedExceptionally()); + assertThat(blockedFuture.isDone()).isTrue(); + assertThat(blockedFuture.isCompletedExceptionally()).isFalse(); stats = dynamicFilterService.getDynamicFilteringStats(queryId, session); - assertEquals(stats.getDynamicFiltersCompleted(), 2); + assertThat(stats.getDynamicFiltersCompleted()).isEqualTo(2); // there are still more dynamic filters to be collected for columns A and B - assertFalse(dynamicFilter.isComplete()); - assertTrue(dynamicFilter.isAwaitable()); + assertThat(dynamicFilter.isComplete()).isFalse(); + assertThat(dynamicFilter.isAwaitable()).isTrue(); blockedFuture = dynamicFilter.isBlocked(); - assertFalse(blockedFuture.isDone()); + assertThat(blockedFuture.isDone()).isFalse(); // make sure dynamic filter on just column A is now completed DynamicFilter dynamicFilterColumnA = dynamicFilterService.createDynamicFilter( @@ -284,11 +280,13 @@ symbol1, new TestingColumnHandle("probeColumnA"), symbol2, new TestingColumnHandle("probeColumnA")), symbolAllocator.getTypes()); - assertEquals(dynamicFilterColumnA.getColumnsCovered(), Set.of(new TestingColumnHandle("probeColumnA")), "columns covered"); - assertTrue(dynamicFilterColumnA.isComplete()); - assertFalse(dynamicFilterColumnA.isAwaitable()); - assertTrue(dynamicFilterColumnA.isBlocked().isDone()); - assertEquals(dynamicFilterColumnA.getCurrentPredicate(), TupleDomain.withColumnDomains(ImmutableMap.of( + assertThat(dynamicFilterColumnA.getColumnsCovered()) + .describedAs("columns covered") + .isEqualTo(Set.of(new TestingColumnHandle("probeColumnA"))); + assertThat(dynamicFilterColumnA.isComplete()).isTrue(); + assertThat(dynamicFilterColumnA.isAwaitable()).isFalse(); + assertThat(dynamicFilterColumnA.isBlocked().isDone()).isTrue(); + assertThat(dynamicFilterColumnA.getCurrentPredicate()).isEqualTo(TupleDomain.withColumnDomains(ImmutableMap.of( new TestingColumnHandle("probeColumnA"), singleValue(INTEGER, 2L)))); @@ -297,38 +295,38 @@ symbol2, new TestingColumnHandle("probeColumnA")), ImmutableMap.of(filterId3, none(INTEGER))); // tuple domain from two tasks (stage 3) are needed for dynamic filter to be narrowed down - assertEquals(dynamicFilter.getCurrentPredicate(), TupleDomain.withColumnDomains(ImmutableMap.of( + assertThat(dynamicFilter.getCurrentPredicate()).isEqualTo(TupleDomain.withColumnDomains(ImmutableMap.of( new TestingColumnHandle("probeColumnA"), singleValue(INTEGER, 2L)))); - assertFalse(dynamicFilter.isComplete()); - assertTrue(dynamicFilter.isAwaitable()); - assertFalse(blockedFuture.isDone()); + assertThat(dynamicFilter.isComplete()).isFalse(); + assertThat(dynamicFilter.isAwaitable()).isTrue(); + assertThat(blockedFuture.isDone()).isFalse(); stats = dynamicFilterService.getDynamicFilteringStats(queryId, session); - assertEquals(stats.getDynamicFiltersCompleted(), 2); + assertThat(stats.getDynamicFiltersCompleted()).isEqualTo(2); dynamicFilterService.addTaskDynamicFilters( new TaskId(stageId3, 1, 0), ImmutableMap.of(filterId3, none(INTEGER))); // "none" dynamic filter (id3) has been collected for column B as tuple domains from two tasks have been provided - assertEquals(dynamicFilter.getCurrentPredicate(), TupleDomain.none()); - assertTrue(blockedFuture.isDone()); - assertFalse(blockedFuture.isCompletedExceptionally()); + assertThat(dynamicFilter.getCurrentPredicate()).isEqualTo(TupleDomain.none()); + assertThat(blockedFuture.isDone()).isTrue(); + assertThat(blockedFuture.isCompletedExceptionally()).isFalse(); stats = dynamicFilterService.getDynamicFilteringStats(queryId, session); - assertEquals(stats.getDynamicFiltersCompleted(), 3); - assertEquals(stats.getLazyDynamicFilters(), 3); - assertEquals(stats.getReplicatedDynamicFilters(), 0); - assertEquals(ImmutableSet.copyOf(stats.getDynamicFilterDomainStats()), ImmutableSet.of( + assertThat(stats.getDynamicFiltersCompleted()).isEqualTo(3); + assertThat(stats.getLazyDynamicFilters()).isEqualTo(3); + assertThat(stats.getReplicatedDynamicFilters()).isEqualTo(0); + assertThat(ImmutableSet.copyOf(stats.getDynamicFilterDomainStats())).isEqualTo(ImmutableSet.of( new DynamicFilterDomainStats(filterId1, getSimplifiedDomainString(1L, 2L, 2, INTEGER)), new DynamicFilterDomainStats(filterId2, getSimplifiedDomainString(2L, 3L, 2, INTEGER)), - new DynamicFilterDomainStats(filterId3, Domain.none(INTEGER).toString(session.toConnectorSession())))); + new DynamicFilterDomainStats(filterId3, none(INTEGER).toString(session.toConnectorSession())))); // all dynamic filters have been collected, no need for more requests - assertTrue(dynamicFilter.isComplete()); - assertFalse(dynamicFilter.isAwaitable()); - assertTrue(dynamicFilter.isBlocked().isDone()); + assertThat(dynamicFilter.isComplete()).isTrue(); + assertThat(dynamicFilter.isAwaitable()).isFalse(); + assertThat(dynamicFilter.isBlocked().isDone()).isTrue(); } @Test @@ -360,18 +358,18 @@ symbol1, new TestingColumnHandle("probeColumnA")), symbolAllocator.getTypes()); // dynamic filter is initially blocked - assertTrue(dynamicFilter.getCurrentPredicate().isAll()); - assertFalse(dynamicFilter.isComplete()); - assertFalse(dynamicFilter.isBlocked().isDone()); + assertThat(dynamicFilter.getCurrentPredicate().isAll()).isTrue(); + assertThat(dynamicFilter.isComplete()).isFalse(); + assertThat(dynamicFilter.isBlocked().isDone()).isFalse(); dynamicFilterService.addTaskDynamicFilters( new TaskId(stageId1, 1, 0), ImmutableMap.of(filterId1, Domain.all(INTEGER))); // dynamic filter should be unblocked and completed - assertTrue(dynamicFilter.getCurrentPredicate().isAll()); - assertTrue(dynamicFilter.isComplete()); - assertTrue(dynamicFilter.isBlocked().isDone()); + assertThat(dynamicFilter.getCurrentPredicate().isAll()).isTrue(); + assertThat(dynamicFilter.isComplete()).isTrue(); + assertThat(dynamicFilter.isBlocked().isDone()).isTrue(); } @Test @@ -400,15 +398,17 @@ public void testDynamicFilterCoercion() ImmutableMap.of(symbol1, new TestingColumnHandle("probeColumnA")), symbolAllocator.getTypes()); - assertEquals(dynamicFilter.getColumnsCovered(), Set.of(new TestingColumnHandle("probeColumnA")), "columns covered"); - assertFalse(dynamicFilter.isComplete()); - assertTrue(dynamicFilter.getCurrentPredicate().isAll()); + assertThat(dynamicFilter.getColumnsCovered()) + .describedAs("columns covered") + .isEqualTo(Set.of(new TestingColumnHandle("probeColumnA"))); + assertThat(dynamicFilter.isComplete()).isFalse(); + assertThat(dynamicFilter.getCurrentPredicate().isAll()).isTrue(); dynamicFilterService.addTaskDynamicFilters( new TaskId(stageId1, 0, 0), ImmutableMap.of(filterId1, multipleValues(BIGINT, ImmutableList.of(1L, 2L, 3L)))); - assertTrue(dynamicFilter.isComplete()); - assertEquals(dynamicFilter.getCurrentPredicate(), TupleDomain.withColumnDomains(ImmutableMap.of( + assertThat(dynamicFilter.isComplete()).isTrue(); + assertThat(dynamicFilter.getCurrentPredicate()).isEqualTo(TupleDomain.withColumnDomains(ImmutableMap.of( new TestingColumnHandle("probeColumnA"), multipleValues(INTEGER, ImmutableList.of(1L, 2L, 3L))))); } @@ -438,43 +438,43 @@ public void testReplicatedDynamicFilter() symbol1, new TestingColumnHandle("probeColumnA")), symbolAllocator.getTypes()); - assertEquals(dynamicFilter.getColumnsCovered(), Set.of(new TestingColumnHandle("probeColumnA")), "columns covered"); - assertTrue(dynamicFilter.getCurrentPredicate().isAll()); + assertThat(dynamicFilter.getColumnsCovered()) + .describedAs("columns covered") + .isEqualTo(Set.of(new TestingColumnHandle("probeColumnA"))); + assertThat(dynamicFilter.getCurrentPredicate().isAll()).isTrue(); // assert initial dynamic filtering stats DynamicFiltersStats stats = dynamicFilterService.getDynamicFilteringStats(queryId, session); - assertEquals(stats.getTotalDynamicFilters(), 1); - assertEquals(stats.getDynamicFiltersCompleted(), 0); - assertEquals(stats.getReplicatedDynamicFilters(), 1); - assertEquals(stats.getLazyDynamicFilters(), 0); + assertThat(stats.getTotalDynamicFilters()).isEqualTo(1); + assertThat(stats.getDynamicFiltersCompleted()).isEqualTo(0); + assertThat(stats.getReplicatedDynamicFilters()).isEqualTo(1); + assertThat(stats.getLazyDynamicFilters()).isEqualTo(0); // filterId1 wasn't marked as lazy - assertFalse(dynamicFilter.isComplete()); - assertFalse(dynamicFilter.isAwaitable()); - assertTrue(dynamicFilter.isBlocked().isDone()); + assertThat(dynamicFilter.isComplete()).isFalse(); + assertThat(dynamicFilter.isAwaitable()).isFalse(); + assertThat(dynamicFilter.isBlocked().isDone()).isTrue(); dynamicFilterService.addTaskDynamicFilters( new TaskId(stageId1, 0, 0), ImmutableMap.of(filterId1, singleValue(INTEGER, 1L))); // tuple domain from single broadcast join task is sufficient - assertEquals(dynamicFilter.getCurrentPredicate(), TupleDomain.withColumnDomains(ImmutableMap.of( + assertThat(dynamicFilter.getCurrentPredicate()).isEqualTo(TupleDomain.withColumnDomains(ImmutableMap.of( new TestingColumnHandle("probeColumnA"), singleValue(INTEGER, 1L)))); - assertTrue(dynamicFilter.isComplete()); - assertFalse(dynamicFilter.isAwaitable()); + assertThat(dynamicFilter.isComplete()).isTrue(); + assertThat(dynamicFilter.isAwaitable()).isFalse(); stats = dynamicFilterService.getDynamicFilteringStats(queryId, session); - assertEquals(stats.getTotalDynamicFilters(), 1); - assertEquals(stats.getDynamicFiltersCompleted(), 1); - assertEquals(stats.getReplicatedDynamicFilters(), 1); - assertEquals(stats.getLazyDynamicFilters(), 0); - assertEquals( - stats.getDynamicFilterDomainStats(), - ImmutableList.of( - new DynamicFilterDomainStats( - filterId1, - Domain.singleValue(INTEGER, 1L).toString(session.toConnectorSession())))); + assertThat(stats.getTotalDynamicFilters()).isEqualTo(1); + assertThat(stats.getDynamicFiltersCompleted()).isEqualTo(1); + assertThat(stats.getReplicatedDynamicFilters()).isEqualTo(1); + assertThat(stats.getLazyDynamicFilters()).isEqualTo(0); + assertThat(stats.getDynamicFilterDomainStats()).isEqualTo(ImmutableList.of( + new DynamicFilterDomainStats( + filterId1, + singleValue(INTEGER, 1L).toString(session.toConnectorSession())))); } @Test @@ -500,29 +500,29 @@ public void testStageCannotScheduleMoreTasks() ImmutableList.of(new DynamicFilters.Descriptor(filterId1, df1)), ImmutableMap.of(symbol1, new TestingColumnHandle("probeColumnA")), symbolAllocator.getTypes()); - assertTrue(dynamicFilter.getCurrentPredicate().isAll()); - assertFalse(dynamicFilter.isComplete()); + assertThat(dynamicFilter.getCurrentPredicate().isAll()).isTrue(); + assertThat(dynamicFilter.isComplete()).isFalse(); CompletableFuture blockedFuture = dynamicFilter.isBlocked(); - assertFalse(blockedFuture.isDone()); + assertThat(blockedFuture.isDone()).isFalse(); // adding task dynamic filters shouldn't complete dynamic filter dynamicFilterService.addTaskDynamicFilters( new TaskId(stageId1, 0, 0), ImmutableMap.of(filterId1, singleValue(INTEGER, 1L))); - assertTrue(dynamicFilter.getCurrentPredicate().isAll()); - assertFalse(dynamicFilter.isComplete()); - assertFalse(blockedFuture.isDone()); + assertThat(dynamicFilter.getCurrentPredicate().isAll()).isTrue(); + assertThat(dynamicFilter.isComplete()).isFalse(); + assertThat(blockedFuture.isDone()).isFalse(); dynamicFilterService.stageCannotScheduleMoreTasks(stageId1, 0, 1); // dynamic filter should be completed when stage won't have more tasks - assertEquals(dynamicFilter.getCurrentPredicate(), TupleDomain.withColumnDomains(ImmutableMap.of( + assertThat(dynamicFilter.getCurrentPredicate()).isEqualTo(TupleDomain.withColumnDomains(ImmutableMap.of( new TestingColumnHandle("probeColumnA"), singleValue(INTEGER, 1L)))); - assertTrue(dynamicFilter.isComplete()); - assertTrue(blockedFuture.isDone()); - assertFalse(blockedFuture.isCompletedExceptionally()); + assertThat(dynamicFilter.isComplete()).isTrue(); + assertThat(blockedFuture.isDone()).isTrue(); + assertThat(blockedFuture.isCompletedExceptionally()).isFalse(); } @Test @@ -545,28 +545,28 @@ public void testDynamicFilterCancellation() ImmutableList.of(new DynamicFilters.Descriptor(filterId, df1)), ImmutableMap.of(symbol1, column), symbolAllocator.getTypes()); - assertFalse(dynamicFilter.isBlocked().isDone()); - assertFalse(dynamicFilter.isComplete()); - assertEquals(dynamicFilter.getCurrentPredicate(), TupleDomain.all()); + assertThat(dynamicFilter.isBlocked().isDone()).isFalse(); + assertThat(dynamicFilter.isComplete()).isFalse(); + assertThat(dynamicFilter.getCurrentPredicate()).isEqualTo(TupleDomain.all()); dynamicFilterService.addTaskDynamicFilters( new TaskId(stageId, 0, 0), ImmutableMap.of(filterId, singleValue(INTEGER, 1L))); - assertEquals(dynamicFilter.getCurrentPredicate(), TupleDomain.all()); + assertThat(dynamicFilter.getCurrentPredicate()).isEqualTo(TupleDomain.all()); // DynamicFilter future cancellation should not affect DynamicFilterService CompletableFuture isBlocked = dynamicFilter.isBlocked(); - assertFalse(isBlocked.isDone()); - assertFalse(isBlocked.cancel(false)); - assertFalse(dynamicFilter.isBlocked().isDone()); - assertFalse(dynamicFilter.isComplete()); + assertThat(isBlocked.isDone()).isFalse(); + assertThat(isBlocked.cancel(false)).isFalse(); + assertThat(dynamicFilter.isBlocked().isDone()).isFalse(); + assertThat(dynamicFilter.isComplete()).isFalse(); dynamicFilterService.addTaskDynamicFilters( new TaskId(stageId, 1, 0), ImmutableMap.of(filterId, singleValue(INTEGER, 2L))); - assertTrue(isBlocked.isDone()); - assertTrue(dynamicFilter.isComplete()); - assertEquals(dynamicFilter.getCurrentPredicate(), TupleDomain.withColumnDomains( + assertThat(isBlocked.isDone()).isTrue(); + assertThat(dynamicFilter.isComplete()).isTrue(); + assertThat(dynamicFilter.getCurrentPredicate()).isEqualTo(TupleDomain.withColumnDomains( ImmutableMap.of(column, multipleValues(INTEGER, ImmutableList.of(1L, 2L))))); } @@ -600,9 +600,9 @@ public void testIsAwaitable() ImmutableMap.of(symbol, handle), symbolAllocator.getTypes()); - assertTrue(dynamicFilter1.isAwaitable()); + assertThat(dynamicFilter1.isAwaitable()).isTrue(); // non lazy dynamic filters are marked as non-awaitable - assertFalse(dynamicFilter2.isAwaitable()); + assertThat(dynamicFilter2.isAwaitable()).isFalse(); } @Test @@ -639,18 +639,18 @@ public void testMultipleColumnMapping() symbol2, column2), symbolAllocator.getTypes()); - assertEquals(dynamicFilter.getColumnsCovered(), Set.of(column1, column2), "columns covered"); + assertThat(dynamicFilter.getColumnsCovered()) + .describedAs("columns covered") + .isEqualTo(Set.of(column1, column2)); Domain domain = singleValue(INTEGER, 1L); dynamicFilterService.addTaskDynamicFilters( new TaskId(stageId1, 0, 0), ImmutableMap.of(filterId1, domain)); - assertEquals( - dynamicFilter.getCurrentPredicate(), - TupleDomain.withColumnDomains(ImmutableMap.of( - column1, domain, - column2, domain))); + assertThat(dynamicFilter.getCurrentPredicate()).isEqualTo(TupleDomain.withColumnDomains(ImmutableMap.of( + column1, domain, + column2, domain))); } @Test @@ -671,13 +671,13 @@ public void testDynamicFilterConsumer() queryId, 0, dynamicFilters, - domains -> domains.forEach((filter, domain) -> assertNull(consumerCollectedFilters.put(filter, domain)))); - assertTrue(consumerCollectedFilters.isEmpty()); + domains -> domains.forEach((filter, domain) -> assertThat(consumerCollectedFilters.put(filter, domain)).isNull())); + assertThat(consumerCollectedFilters.isEmpty()).isTrue(); dynamicFilterService.addTaskDynamicFilters( new TaskId(stageId, 0, 0), ImmutableMap.of(filterId1, singleValue(INTEGER, 1L))); - assertTrue(consumerCollectedFilters.isEmpty()); + assertThat(consumerCollectedFilters.isEmpty()).isTrue(); // complete only filterId1 dynamicFilterService.addTaskDynamicFilters( @@ -685,7 +685,7 @@ public void testDynamicFilterConsumer() ImmutableMap.of( filterId1, singleValue(INTEGER, 3L), filterId2, singleValue(INTEGER, 2L))); - assertEquals(consumerCollectedFilters, ImmutableMap.of(filterId1, multipleValues(INTEGER, ImmutableList.of(1L, 3L)))); + assertThat(consumerCollectedFilters).isEqualTo(ImmutableMap.of(filterId1, multipleValues(INTEGER, ImmutableList.of(1L, 3L)))); // register another consumer only for filterId1 after completion of filterId1 Map secondConsumerCollectedFilters = new HashMap<>(); @@ -693,21 +693,17 @@ filterId1, singleValue(INTEGER, 3L), queryId, 0, ImmutableSet.of(filterId1), - domains -> domains.forEach((filter, domain) -> assertNull(secondConsumerCollectedFilters.put(filter, domain)))); - assertEquals(secondConsumerCollectedFilters, ImmutableMap.of(filterId1, multipleValues(INTEGER, ImmutableList.of(1L, 3L)))); + domains -> domains.forEach((filter, domain) -> assertThat(secondConsumerCollectedFilters.put(filter, domain)).isNull())); + assertThat(secondConsumerCollectedFilters).isEqualTo(ImmutableMap.of(filterId1, multipleValues(INTEGER, ImmutableList.of(1L, 3L)))); // complete filterId2 dynamicFilterService.addTaskDynamicFilters( new TaskId(stageId, 0, 0), ImmutableMap.of(filterId2, singleValue(INTEGER, 4L))); - assertEquals( - consumerCollectedFilters, - ImmutableMap.of( - filterId1, multipleValues(INTEGER, ImmutableList.of(1L, 3L)), - filterId2, multipleValues(INTEGER, ImmutableList.of(2L, 4L)))); - assertEquals( - secondConsumerCollectedFilters, - ImmutableMap.of(filterId1, multipleValues(INTEGER, ImmutableList.of(1L, 3L)))); + assertThat(consumerCollectedFilters).isEqualTo(ImmutableMap.of( + filterId1, multipleValues(INTEGER, ImmutableList.of(1L, 3L)), + filterId2, multipleValues(INTEGER, ImmutableList.of(2L, 4L)))); + assertThat(secondConsumerCollectedFilters).isEqualTo(ImmutableMap.of(filterId1, multipleValues(INTEGER, ImmutableList.of(1L, 3L)))); } @Test @@ -731,16 +727,16 @@ public void testDynamicFilterConsumerCallbackCount() dynamicFilters, domains -> { callbackCount.getAndIncrement(); - domains.forEach((filter, domain) -> assertNull(consumerCollectedFilters.put(filter, domain))); + domains.forEach((filter, domain) -> assertThat(consumerCollectedFilters.put(filter, domain)).isNull()); }); - assertTrue(consumerCollectedFilters.isEmpty()); + assertThat(consumerCollectedFilters.isEmpty()).isTrue(); dynamicFilterService.addTaskDynamicFilters( new TaskId(stageId, 0, 0), ImmutableMap.of( filterId1, singleValue(INTEGER, 1L), filterId2, singleValue(INTEGER, 2L))); - assertTrue(consumerCollectedFilters.isEmpty()); + assertThat(consumerCollectedFilters.isEmpty()).isTrue(); // complete both filterId1 and filterId2 dynamicFilterService.addTaskDynamicFilters( @@ -748,13 +744,11 @@ filterId1, singleValue(INTEGER, 1L), ImmutableMap.of( filterId1, singleValue(INTEGER, 3L), filterId2, singleValue(INTEGER, 4L))); - assertEquals( - consumerCollectedFilters, - ImmutableMap.of( - filterId1, multipleValues(INTEGER, ImmutableList.of(1L, 3L)), - filterId2, multipleValues(INTEGER, ImmutableList.of(2L, 4L)))); + assertThat(consumerCollectedFilters).isEqualTo(ImmutableMap.of( + filterId1, multipleValues(INTEGER, ImmutableList.of(1L, 3L)), + filterId2, multipleValues(INTEGER, ImmutableList.of(2L, 4L)))); - assertEquals(callbackCount.get(), 2); + assertThat(callbackCount.get()).isEqualTo(2); // register another consumer after both filters have been collected Map secondConsumerCollectedFilters = new HashMap<>(); @@ -765,25 +759,23 @@ filterId1, multipleValues(INTEGER, ImmutableList.of(1L, 3L)), dynamicFilters, domains -> { secondCallbackCount.getAndIncrement(); - domains.forEach((filter, domain) -> assertNull(secondConsumerCollectedFilters.put(filter, domain))); + domains.forEach((filter, domain) -> assertThat(secondConsumerCollectedFilters.put(filter, domain)).isNull()); }); - assertEquals( - secondConsumerCollectedFilters, - ImmutableMap.of( - filterId1, multipleValues(INTEGER, ImmutableList.of(1L, 3L)), - filterId2, multipleValues(INTEGER, ImmutableList.of(2L, 4L)))); - assertEquals(secondCallbackCount.get(), 2); + assertThat(secondConsumerCollectedFilters).isEqualTo(ImmutableMap.of( + filterId1, multipleValues(INTEGER, ImmutableList.of(1L, 3L)), + filterId2, multipleValues(INTEGER, ImmutableList.of(2L, 4L)))); + assertThat(secondCallbackCount.get()).isEqualTo(2); // first consumer should not receive callback again since it already got the completed filter - assertEquals(callbackCount.get(), 2); + assertThat(callbackCount.get()).isEqualTo(2); } @Test public void testSourceStageInnerLazyDynamicFilters() { DynamicFilterId dynamicFilterId = new DynamicFilterId("filterId"); - assertEquals(getSourceStageInnerLazyDynamicFilters(createPlan(dynamicFilterId, SOURCE_DISTRIBUTION, REPLICATE)), ImmutableSet.of(dynamicFilterId)); - assertEquals(getSourceStageInnerLazyDynamicFilters(createPlan(dynamicFilterId, FIXED_HASH_DISTRIBUTION, REPLICATE)), ImmutableSet.of()); - assertEquals(getSourceStageInnerLazyDynamicFilters(createPlan(dynamicFilterId, SOURCE_DISTRIBUTION, REPARTITION)), ImmutableSet.of()); + assertThat(getSourceStageInnerLazyDynamicFilters(createPlan(dynamicFilterId, SOURCE_DISTRIBUTION, REPLICATE))).isEqualTo(ImmutableSet.of(dynamicFilterId)); + assertThat(getSourceStageInnerLazyDynamicFilters(createPlan(dynamicFilterId, FIXED_HASH_DISTRIBUTION, REPLICATE))).isEqualTo(ImmutableSet.of()); + assertThat(getSourceStageInnerLazyDynamicFilters(createPlan(dynamicFilterId, SOURCE_DISTRIBUTION, REPARTITION))).isEqualTo(ImmutableSet.of()); } @Test @@ -791,10 +783,10 @@ public void testOutboundDynamicFilters() { DynamicFilterId filterId1 = new DynamicFilterId("filterId1"); DynamicFilterId filterId2 = new DynamicFilterId("filterId2"); - assertEquals(getOutboundDynamicFilters(createPlan(filterId1, filterId1, FIXED_HASH_DISTRIBUTION, REPLICATE)), ImmutableSet.of()); - assertEquals(getOutboundDynamicFilters(createPlan(filterId1, filterId1, FIXED_HASH_DISTRIBUTION, REPARTITION)), ImmutableSet.of()); - assertEquals(getOutboundDynamicFilters(createPlan(filterId1, filterId2, FIXED_HASH_DISTRIBUTION, REPLICATE)), ImmutableSet.of(filterId1)); - assertEquals(getOutboundDynamicFilters(createPlan(filterId1, filterId2, FIXED_HASH_DISTRIBUTION, REPARTITION)), ImmutableSet.of(filterId1)); + assertThat(getOutboundDynamicFilters(createPlan(filterId1, filterId1, FIXED_HASH_DISTRIBUTION, REPLICATE))).isEqualTo(ImmutableSet.of()); + assertThat(getOutboundDynamicFilters(createPlan(filterId1, filterId1, FIXED_HASH_DISTRIBUTION, REPARTITION))).isEqualTo(ImmutableSet.of()); + assertThat(getOutboundDynamicFilters(createPlan(filterId1, filterId2, FIXED_HASH_DISTRIBUTION, REPLICATE))).isEqualTo(ImmutableSet.of(filterId1)); + assertThat(getOutboundDynamicFilters(createPlan(filterId1, filterId2, FIXED_HASH_DISTRIBUTION, REPARTITION))).isEqualTo(ImmutableSet.of(filterId1)); } @Test @@ -807,7 +799,7 @@ public void testMultipleQueryAttempts() dynamicFilterService.registerQuery(queryId, session, ImmutableSet.of(filterId), ImmutableSet.of(filterId), ImmutableSet.of()); dynamicFilterService.stageCannotScheduleMoreTasks(stageId, 0, 3); - assertFalse(dynamicFilterService.getSummary(queryId, filterId).isPresent()); + assertThat(dynamicFilterService.getSummary(queryId, filterId).isPresent()).isFalse(); // Collect DF from 2 tasks dynamicFilterService.addTaskDynamicFilters( @@ -816,7 +808,7 @@ public void testMultipleQueryAttempts() dynamicFilterService.addTaskDynamicFilters( new TaskId(stageId, 1, 0), ImmutableMap.of(filterId, singleValue(INTEGER, 2L))); - assertFalse(dynamicFilterService.getSummary(queryId, filterId).isPresent()); + assertThat(dynamicFilterService.getSummary(queryId, filterId).isPresent()).isFalse(); // Register query retry dynamicFilterService.registerQueryRetry(queryId, 1); @@ -826,7 +818,7 @@ public void testMultipleQueryAttempts() dynamicFilterService.addTaskDynamicFilters( new TaskId(stageId, 2, 0), ImmutableMap.of(filterId, singleValue(INTEGER, 3L))); - assertFalse(dynamicFilterService.getSummary(queryId, filterId).isPresent()); + assertThat(dynamicFilterService.getSummary(queryId, filterId).isPresent()).isFalse(); // Collect DF from 3 tasks in new attempt dynamicFilterService.addTaskDynamicFilters( @@ -838,19 +830,15 @@ public void testMultipleQueryAttempts() dynamicFilterService.addTaskDynamicFilters( new TaskId(stageId, 2, 1), ImmutableMap.of(filterId, singleValue(INTEGER, 6L))); - assertEquals( - dynamicFilterService.getSummary(queryId, filterId), - Optional.of(multipleValues(INTEGER, ImmutableList.of(4L, 5L, 6L)))); + assertThat(dynamicFilterService.getSummary(queryId, filterId)).isEqualTo(Optional.of(multipleValues(INTEGER, ImmutableList.of(4L, 5L, 6L)))); DynamicFiltersStats stats = dynamicFilterService.getDynamicFilteringStats(queryId, session); - assertEquals(stats.getDynamicFiltersCompleted(), 1); - assertEquals(stats.getLazyDynamicFilters(), 1); - assertEquals(stats.getReplicatedDynamicFilters(), 0); - assertEquals( - stats.getDynamicFilterDomainStats(), - ImmutableList.of(new DynamicFilterDomainStats( - filterId, - getSimplifiedDomainString(4L, 6L, 3, INTEGER)))); + assertThat(stats.getDynamicFiltersCompleted()).isEqualTo(1); + assertThat(stats.getLazyDynamicFilters()).isEqualTo(1); + assertThat(stats.getReplicatedDynamicFilters()).isEqualTo(0); + assertThat(stats.getDynamicFilterDomainStats()).isEqualTo(ImmutableList.of(new DynamicFilterDomainStats( + filterId, + getSimplifiedDomainString(4L, 6L, 3, INTEGER)))); } @Test @@ -908,7 +896,7 @@ public void testSizeLimit() dynamicFilterService.stageCannotScheduleMoreTasks(stage1, 0, 2); assertThat(dynamicFilterService.getSummary(queryId, compactFilter)).isPresent(); Domain compactFilterSummary = dynamicFilterService.getSummary(queryId, compactFilter).get(); - assertEquals(compactFilterSummary.getValues(), ValueSet.ofRanges(range(VARCHAR, utf8Slice("value0"), true, utf8Slice("value9"), true))); + assertThat(compactFilterSummary.getValues()).isEqualTo(ValueSet.ofRanges(range(VARCHAR, utf8Slice("value0"), true, utf8Slice("value9"), true))); // test size limit exceeded after compaction dynamicFilterService.addTaskDynamicFilters( @@ -923,23 +911,21 @@ public void testSizeLimit() new TaskId(stage2, 2, 0), ImmutableMap.of(largeFilter, domain3)); assertThat(dynamicFilterService.getSummary(queryId, largeFilter)).isPresent(); - assertEquals(dynamicFilterService.getSummary(queryId, largeFilter).get(), Domain.all(VARCHAR)); + assertThat(dynamicFilterService.getSummary(queryId, largeFilter).get()).isEqualTo(Domain.all(VARCHAR)); // test compaction for replicated filter dynamicFilterService.addTaskDynamicFilters( new TaskId(stage3, 0, 0), ImmutableMap.of(replicatedFilter1, domain1.union(domain2))); assertThat(dynamicFilterService.getSummary(queryId, replicatedFilter1)).isPresent(); - assertEquals( - dynamicFilterService.getSummary(queryId, replicatedFilter1).get().getValues(), - ValueSet.ofRanges(range(VARCHAR, utf8Slice("value0"), true, utf8Slice("value9"), true))); + assertThat(dynamicFilterService.getSummary(queryId, replicatedFilter1).get().getValues()).isEqualTo(ValueSet.ofRanges(range(VARCHAR, utf8Slice("value0"), true, utf8Slice("value9"), true))); // test size limit exceeded for replicated filter dynamicFilterService.addTaskDynamicFilters( new TaskId(stage4, 0, 0), ImmutableMap.of(replicatedFilter2, domain1.union(domain2).union(domain3))); assertThat(dynamicFilterService.getSummary(queryId, replicatedFilter2)).isPresent(); - assertEquals(dynamicFilterService.getSummary(queryId, replicatedFilter2).get(), Domain.all(VARCHAR)); + assertThat(dynamicFilterService.getSummary(queryId, replicatedFilter2).get()).isEqualTo(Domain.all(VARCHAR)); } @Test @@ -975,7 +961,7 @@ public void testCollectMoreThanOnceForTheSameTask() new TaskId(stage, 1, 0), ImmutableMap.of(filter, domain3)); assertThat(dynamicFilterService.getSummary(query, filter)).isPresent(); - assertEquals(dynamicFilterService.getSummary(query, filter).get(), domain1.union(domain3)); + assertThat(dynamicFilterService.getSummary(query, filter).get()).isEqualTo(domain1.union(domain3)); } @Test @@ -991,7 +977,7 @@ public void testMultipleTaskAttempts() .build(); dynamicFilterService.registerQuery(queryId, taskRetriesEnabled, ImmutableSet.of(filterId), ImmutableSet.of(filterId), ImmutableSet.of()); dynamicFilterService.stageCannotScheduleMoreTasks(stageId, 0, 3); - assertFalse(dynamicFilterService.getSummary(queryId, filterId).isPresent()); + assertThat(dynamicFilterService.getSummary(queryId, filterId).isPresent()).isFalse(); // Collect DF from 2 tasks dynamicFilterService.addTaskDynamicFilters( @@ -1000,32 +986,28 @@ public void testMultipleTaskAttempts() dynamicFilterService.addTaskDynamicFilters( new TaskId(stageId, 1, 0), ImmutableMap.of(filterId, singleValue(INTEGER, 2L))); - assertFalse(dynamicFilterService.getSummary(queryId, filterId).isPresent()); + assertThat(dynamicFilterService.getSummary(queryId, filterId).isPresent()).isFalse(); // Collect DF from task retry of partitionId 0 dynamicFilterService.addTaskDynamicFilters( new TaskId(stageId, 0, 1), ImmutableMap.of(filterId, singleValue(INTEGER, 0L))); - assertFalse(dynamicFilterService.getSummary(queryId, filterId).isPresent()); + assertThat(dynamicFilterService.getSummary(queryId, filterId).isPresent()).isFalse(); // Collect DF from 3rd partition dynamicFilterService.addTaskDynamicFilters( new TaskId(stageId, 2, 0), ImmutableMap.of(filterId, singleValue(INTEGER, 6L))); // DF from task retry of partitionId 0 is ignored and the collected value from first successful attempt is kept - assertEquals( - dynamicFilterService.getSummary(queryId, filterId), - Optional.of(multipleValues(INTEGER, ImmutableList.of(1L, 2L, 6L)))); + assertThat(dynamicFilterService.getSummary(queryId, filterId)).isEqualTo(Optional.of(multipleValues(INTEGER, ImmutableList.of(1L, 2L, 6L)))); DynamicFiltersStats stats = dynamicFilterService.getDynamicFilteringStats(queryId, session); - assertEquals(stats.getDynamicFiltersCompleted(), 1); - assertEquals(stats.getLazyDynamicFilters(), 1); - assertEquals(stats.getReplicatedDynamicFilters(), 0); - assertEquals( - stats.getDynamicFilterDomainStats(), - ImmutableList.of(new DynamicFilterDomainStats( - filterId, - getSimplifiedDomainString(1L, 6L, 3, INTEGER)))); + assertThat(stats.getDynamicFiltersCompleted()).isEqualTo(1); + assertThat(stats.getLazyDynamicFilters()).isEqualTo(1); + assertThat(stats.getReplicatedDynamicFilters()).isEqualTo(0); + assertThat(stats.getDynamicFilterDomainStats()).isEqualTo(ImmutableList.of(new DynamicFilterDomainStats( + filterId, + getSimplifiedDomainString(1L, 6L, 3, INTEGER)))); } private static DynamicFilterService createDynamicFilterService() diff --git a/core/trino-main/src/test/java/io/trino/server/TestGenerateTokenFilter.java b/core/trino-main/src/test/java/io/trino/server/TestGenerateTokenFilter.java index fa9e2e4a10ce..be71fa55a885 100644 --- a/core/trino-main/src/test/java/io/trino/server/TestGenerateTokenFilter.java +++ b/core/trino-main/src/test/java/io/trino/server/TestGenerateTokenFilter.java @@ -48,9 +48,9 @@ import static io.trino.server.security.ResourceSecurity.AccessType.PUBLIC; import static jakarta.servlet.http.HttpServletResponse.SC_OK; import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -70,7 +70,7 @@ public void setup() // extract the filter List filters = httpClient.getRequestFilters(); - assertEquals(filters.size(), 2); + assertThat(filters.size()).isEqualTo(2); assertInstanceOf(filters.get(1), GenerateTraceTokenRequestFilter.class); filter = (GenerateTraceTokenRequestFilter) filters.get(1); } @@ -89,8 +89,8 @@ public void testTraceToken() { Request request = prepareGet().setUri(server.getBaseUrl().resolve("/testing/echo_token")).build(); StringResponse response = httpClient.execute(request, createStringResponseHandler()); - assertEquals(response.getStatusCode(), SC_OK); - assertEquals(response.getBody(), filter.getLastToken()); + assertThat(response.getStatusCode()).isEqualTo(SC_OK); + assertThat(response.getBody()).isEqualTo(filter.getLastToken()); } @Retention(RUNTIME) diff --git a/core/trino-main/src/test/java/io/trino/server/TestHttpRequestSessionContextFactory.java b/core/trino-main/src/test/java/io/trino/server/TestHttpRequestSessionContextFactory.java index f894211a53a3..6e6295154818 100644 --- a/core/trino-main/src/test/java/io/trino/server/TestHttpRequestSessionContextFactory.java +++ b/core/trino-main/src/test/java/io/trino/server/TestHttpRequestSessionContextFactory.java @@ -35,8 +35,8 @@ import static io.trino.client.ProtocolHeaders.TRINO_HEADERS; import static io.trino.client.ProtocolHeaders.createProtocolHeaders; import static io.trino.metadata.MetadataManager.createTestMetadataManager; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestHttpRequestSessionContextFactory { @@ -81,11 +81,11 @@ private static void assertSessionContext(ProtocolHeaders protocolHeaders) Optional.of(protocolHeaders.getProtocolName()), Optional.of("testRemote"), Optional.empty()); - assertEquals(context.getSource().orElse(null), "testSource"); - assertEquals(context.getCatalog().orElse(null), "testCatalog"); - assertEquals(context.getSchema().orElse(null), "testSchema"); - assertEquals(context.getPath().orElse(null), "testPath"); - assertEquals(context.getIdentity(), Identity.forUser("testUser") + assertThat(context.getSource().orElse(null)).isEqualTo("testSource"); + assertThat(context.getCatalog().orElse(null)).isEqualTo("testCatalog"); + assertThat(context.getSchema().orElse(null)).isEqualTo("testSchema"); + assertThat(context.getPath().orElse(null)).isEqualTo("testPath"); + assertThat(context.getIdentity()).isEqualTo(Identity.forUser("testUser") .withGroups(ImmutableSet.of("testUser")) .withConnectorRoles(ImmutableMap.of( "foo_connector", new SelectedRole(SelectedRole.Type.ALL, Optional.empty()), @@ -93,17 +93,17 @@ private static void assertSessionContext(ProtocolHeaders protocolHeaders) "foobar_connector", new SelectedRole(SelectedRole.Type.ROLE, Optional.of("catalog-role")))) .withEnabledRoles(ImmutableSet.of("system-role")) .build()); - assertEquals(context.getClientInfo().orElse(null), "client-info"); - assertEquals(context.getLanguage().orElse(null), "zh-TW"); - assertEquals(context.getTimeZoneId().orElse(null), "Asia/Taipei"); - assertEquals(context.getSystemProperties(), ImmutableMap.of( + assertThat(context.getClientInfo().orElse(null)).isEqualTo("client-info"); + assertThat(context.getLanguage().orElse(null)).isEqualTo("zh-TW"); + assertThat(context.getTimeZoneId().orElse(null)).isEqualTo("Asia/Taipei"); + assertThat(context.getSystemProperties()).isEqualTo(ImmutableMap.of( QUERY_MAX_MEMORY, "1GB", JOIN_DISTRIBUTION_TYPE, "partitioned", MAX_HASH_PARTITION_COUNT, "43", "some_session_property", "some value with , comma")); - assertEquals(context.getPreparedStatements(), ImmutableMap.of("query1", "select * from foo", "query2", "select * from bar")); - assertEquals(context.getSelectedRole(), new SelectedRole(SelectedRole.Type.ROLE, Optional.of("system-role"))); - assertEquals(context.getIdentity().getExtraCredentials(), ImmutableMap.of("test.token.foo", "bar", "test.token.abc", "xyz")); + assertThat(context.getPreparedStatements()).isEqualTo(ImmutableMap.of("query1", "select * from foo", "query2", "select * from bar")); + assertThat(context.getSelectedRole()).isEqualTo(new SelectedRole(SelectedRole.Type.ROLE, Optional.of("system-role"))); + assertThat(context.getIdentity().getExtraCredentials()).isEqualTo(ImmutableMap.of("test.token.foo", "bar", "test.token.abc", "xyz")); } @Test @@ -123,21 +123,21 @@ private static void assertMappedUser(ProtocolHeaders protocolHeaders) Optional.of(protocolHeaders.getProtocolName()), Optional.of("testRemote"), Optional.empty()); - assertEquals(context.getIdentity(), Identity.forUser("testUser").withGroups(ImmutableSet.of("testUser")).build()); + assertThat(context.getIdentity()).isEqualTo(Identity.forUser("testUser").withGroups(ImmutableSet.of("testUser")).build()); context = SESSION_CONTEXT_FACTORY.createSessionContext( emptyHeaders, Optional.of(protocolHeaders.getProtocolName()), Optional.of("testRemote"), Optional.of(Identity.forUser("mappedUser").withGroups(ImmutableSet.of("test")).build())); - assertEquals(context.getIdentity(), Identity.forUser("mappedUser").withGroups(ImmutableSet.of("test", "mappedUser")).build()); + assertThat(context.getIdentity()).isEqualTo(Identity.forUser("mappedUser").withGroups(ImmutableSet.of("test", "mappedUser")).build()); context = SESSION_CONTEXT_FACTORY.createSessionContext( userHeaders, Optional.of(protocolHeaders.getProtocolName()), Optional.of("testRemote"), Optional.of(Identity.ofUser("mappedUser"))); - assertEquals(context.getIdentity(), Identity.forUser("testUser").withGroups(ImmutableSet.of("testUser")).build()); + assertThat(context.getIdentity()).isEqualTo(Identity.forUser("testUser").withGroups(ImmutableSet.of("testUser")).build()); assertThatThrownBy( () -> SESSION_CONTEXT_FACTORY.createSessionContext( diff --git a/core/trino-main/src/test/java/io/trino/server/TestNodeResource.java b/core/trino-main/src/test/java/io/trino/server/TestNodeResource.java index 4cec434a5524..3e4ee2e86e2a 100644 --- a/core/trino-main/src/test/java/io/trino/server/TestNodeResource.java +++ b/core/trino-main/src/test/java/io/trino/server/TestNodeResource.java @@ -30,9 +30,9 @@ import static io.airlift.testing.Closeables.closeAll; import static io.trino.client.ProtocolHeaders.TRINO_HEADERS; import static io.trino.failuredetector.HeartbeatFailureDetector.Stats; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -68,7 +68,7 @@ public void testGetAllNodes() createJsonResponseHandler(listJsonCodec(Stats.class))); // we only have one node and the list never contains the current node - assertTrue(nodes.isEmpty()); + assertThat(nodes.isEmpty()).isTrue(); } @Test @@ -81,6 +81,6 @@ public void testGetFailedNodes() .build(), createJsonResponseHandler(listJsonCodec(Stats.class))); - assertTrue(nodes.isEmpty()); + assertThat(nodes.isEmpty()).isTrue(); } } diff --git a/core/trino-main/src/test/java/io/trino/server/TestQueryProgressStats.java b/core/trino-main/src/test/java/io/trino/server/TestQueryProgressStats.java index 77c591f51a86..b75c48b011cd 100644 --- a/core/trino-main/src/test/java/io/trino/server/TestQueryProgressStats.java +++ b/core/trino-main/src/test/java/io/trino/server/TestQueryProgressStats.java @@ -18,8 +18,7 @@ import java.util.OptionalDouble; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; +import static org.assertj.core.api.Assertions.assertThat; public class TestQueryProgressStats { @@ -42,15 +41,15 @@ public void testJson() String json = codec.toJson(expected); QueryProgressStats actual = codec.fromJson(json); - assertEquals(actual.getElapsedTimeMillis(), 123456); - assertEquals(actual.getQueuedTimeMillis(), 1111); - assertEquals(actual.getCpuTimeMillis(), 22222); - assertEquals(actual.getScheduledTimeMillis(), 3333); - assertEquals(actual.getCurrentMemoryBytes(), 100000); - assertEquals(actual.getPeakMemoryBytes(), 34230492); - assertEquals(actual.getInputRows(), 1000); - assertEquals(actual.getInputBytes(), 100000); - assertFalse(actual.isBlocked()); - assertEquals(actual.getProgressPercentage(), OptionalDouble.of(33.33)); + assertThat(actual.getElapsedTimeMillis()).isEqualTo(123456); + assertThat(actual.getQueuedTimeMillis()).isEqualTo(1111); + assertThat(actual.getCpuTimeMillis()).isEqualTo(22222); + assertThat(actual.getScheduledTimeMillis()).isEqualTo(3333); + assertThat(actual.getCurrentMemoryBytes()).isEqualTo(100000); + assertThat(actual.getPeakMemoryBytes()).isEqualTo(34230492); + assertThat(actual.getInputRows()).isEqualTo(1000); + assertThat(actual.getInputBytes()).isEqualTo(100000); + assertThat(actual.isBlocked()).isFalse(); + assertThat(actual.getProgressPercentage()).isEqualTo(OptionalDouble.of(33.33)); } } diff --git a/core/trino-main/src/test/java/io/trino/server/TestQueryResource.java b/core/trino-main/src/test/java/io/trino/server/TestQueryResource.java index b3fa0e4414af..a796e1e7d567 100644 --- a/core/trino-main/src/test/java/io/trino/server/TestQueryResource.java +++ b/core/trino-main/src/test/java/io/trino/server/TestQueryResource.java @@ -63,12 +63,8 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Fail.fail; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; @TestInstance(PER_METHOD) public class TestQueryResource @@ -142,27 +138,27 @@ public void testGetQueryInfos() runToCompletion("SELECT x FROM y"); List infos = getQueryInfos("/v1/query"); - assertEquals(infos.size(), 3); + assertThat(infos.size()).isEqualTo(3); assertStateCounts(infos, 2, 1, 0); infos = getQueryInfos("/v1/query?state=finished"); - assertEquals(infos.size(), 2); + assertThat(infos.size()).isEqualTo(2); assertStateCounts(infos, 2, 0, 0); infos = getQueryInfos("/v1/query?state=failed"); - assertEquals(infos.size(), 1); + assertThat(infos.size()).isEqualTo(1); assertStateCounts(infos, 0, 1, 0); infos = getQueryInfos("/v1/query?state=running"); - assertEquals(infos.size(), 0); + assertThat(infos.size()).isEqualTo(0); assertStateCounts(infos, 0, 0, 0); server.getAccessControl().deny(privilege("query", VIEW_QUERY)); try { - assertTrue(getQueryInfos("/v1/query").isEmpty()); - assertTrue(getQueryInfos("/v1/query?state=finished").isEmpty()); - assertTrue(getQueryInfos("/v1/query?state=failed").isEmpty()); - assertTrue(getQueryInfos("/v1/query?state=running").isEmpty()); + assertThat(getQueryInfos("/v1/query").isEmpty()).isTrue(); + assertThat(getQueryInfos("/v1/query?state=finished").isEmpty()).isTrue(); + assertThat(getQueryInfos("/v1/query?state=failed").isEmpty()).isTrue(); + assertThat(getQueryInfos("/v1/query?state=running").isEmpty()).isTrue(); } finally { server.getAccessControl().reset(); @@ -174,9 +170,9 @@ public void testGetQueryInfoDispatchFailure() { String queryId = runToCompletion("SELECT"); QueryInfo info = getQueryInfo(queryId); - assertFalse(info.isScheduled()); - assertNotNull(info.getFailureInfo()); - assertEquals(info.getFailureInfo().getErrorCode(), SYNTAX_ERROR.toErrorCode()); + assertThat(info.isScheduled()).isFalse(); + assertThat(info.getFailureInfo()).isNotNull(); + assertThat(info.getFailureInfo().getErrorCode()).isEqualTo(SYNTAX_ERROR.toErrorCode()); server.getAccessControl().deny(privilege("query", VIEW_QUERY)); try { @@ -194,9 +190,9 @@ public void testGetQueryInfoExecutionFailure() { String queryId = runToCompletion("SELECT cast(rand() AS integer) / 0"); QueryInfo info = getQueryInfo(queryId); - assertTrue(info.isScheduled()); - assertNotNull(info.getFailureInfo()); - assertEquals(info.getFailureInfo().getErrorCode(), DIVISION_BY_ZERO.toErrorCode()); + assertThat(info.isScheduled()).isTrue(); + assertThat(info.getFailureInfo()).isNotNull(); + assertThat(info.getFailureInfo().getErrorCode()).isEqualTo(DIVISION_BY_ZERO.toErrorCode()); } @Test @@ -206,17 +202,17 @@ public void testCancel() server.getAccessControl().deny(privilege("query", KILL_QUERY)); try { - assertEquals(cancelQueryInfo(queryId), 403); + assertThat(cancelQueryInfo(queryId)).isEqualTo(403); } finally { server.getAccessControl().reset(); } - assertEquals(cancelQueryInfo(queryId), 204); - assertEquals(cancelQueryInfo(queryId), 204); + assertThat(cancelQueryInfo(queryId)).isEqualTo(204); + assertThat(cancelQueryInfo(queryId)).isEqualTo(204); BasicQueryInfo queryInfo = server.getDispatchManager().getQueryInfo(new QueryId(queryId)); - assertEquals(queryInfo.getState(), FAILED); - assertEquals(queryInfo.getErrorCode(), USER_CANCELED.toErrorCode()); + assertThat(queryInfo.getState()).isEqualTo(FAILED); + assertThat(queryInfo.getErrorCode()).isEqualTo(USER_CANCELED.toErrorCode()); } @Test @@ -237,21 +233,21 @@ private void testKilled(String killType) server.getAccessControl().deny(privilege("query", KILL_QUERY)); try { - assertEquals(killQueryInfo(queryId, killType), 403); + assertThat(killQueryInfo(queryId, killType)).isEqualTo(403); } finally { server.getAccessControl().reset(); } - assertEquals(killQueryInfo(queryId, killType), 202); - assertEquals(killQueryInfo(queryId, killType), 409); + assertThat(killQueryInfo(queryId, killType)).isEqualTo(202); + assertThat(killQueryInfo(queryId, killType)).isEqualTo(409); BasicQueryInfo queryInfo = server.getDispatchManager().getQueryInfo(new QueryId(queryId)); - assertEquals(queryInfo.getState(), FAILED); + assertThat(queryInfo.getState()).isEqualTo(FAILED); if (killType.equals("killed")) { - assertEquals(queryInfo.getErrorCode(), ADMINISTRATIVELY_KILLED.toErrorCode()); + assertThat(queryInfo.getErrorCode()).isEqualTo(ADMINISTRATIVELY_KILLED.toErrorCode()); } else { - assertEquals(queryInfo.getErrorCode(), ADMINISTRATIVELY_PREEMPTED.toErrorCode()); + assertThat(queryInfo.getErrorCode()).isEqualTo(ADMINISTRATIVELY_PREEMPTED.toErrorCode()); } } @@ -322,9 +318,9 @@ private static void assertStateCounts(Iterable infos, int expect fail("Unexpected query state " + info.getState()); } } - assertEquals(failed, expectedFailed); - assertEquals(finished, expectedFinished); - assertEquals(running, expectedRunning); + assertThat(failed).isEqualTo(expectedFailed); + assertThat(finished).isEqualTo(expectedFinished); + assertThat(running).isEqualTo(expectedRunning); } private QueryInfo getQueryInfo(String queryId) diff --git a/core/trino-main/src/test/java/io/trino/server/TestQuerySessionSupplier.java b/core/trino-main/src/test/java/io/trino/server/TestQuerySessionSupplier.java index 46bf67debe13..7cb8c3fd41bc 100644 --- a/core/trino-main/src/test/java/io/trino/server/TestQuerySessionSupplier.java +++ b/core/trino-main/src/test/java/io/trino/server/TestQuerySessionSupplier.java @@ -50,7 +50,6 @@ import static io.trino.transaction.InMemoryTransactionManager.createTestTransactionManager; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestQuerySessionSupplier { @@ -81,23 +80,23 @@ public void testCreateSession() QuerySessionSupplier sessionSupplier = createSessionSupplier(new SqlEnvironmentConfig()); Session session = sessionSupplier.createSession(new QueryId("test_query_id"), Span.getInvalid(), context); - assertEquals(session.getQueryId(), new QueryId("test_query_id")); - assertEquals(session.getUser(), "testUser"); - assertEquals(session.getSource().get(), "testSource"); - assertEquals(session.getCatalog().get(), "testCatalog"); - assertEquals(session.getSchema().get(), "testSchema"); - assertEquals(session.getPath().getRawPath(), "testPath"); - assertEquals(session.getLocale(), Locale.TAIWAN); - assertEquals(session.getTimeZoneKey(), getTimeZoneKey("Asia/Taipei")); - assertEquals(session.getRemoteUserAddress().get(), "testRemote"); - assertEquals(session.getClientInfo().get(), "client-info"); - assertEquals(session.getClientTags(), ImmutableSet.of("tag1", "tag2", "tag3")); - assertEquals(session.getSystemProperties(), ImmutableMap.builder() + assertThat(session.getQueryId()).isEqualTo(new QueryId("test_query_id")); + assertThat(session.getUser()).isEqualTo("testUser"); + assertThat(session.getSource().get()).isEqualTo("testSource"); + assertThat(session.getCatalog().get()).isEqualTo("testCatalog"); + assertThat(session.getSchema().get()).isEqualTo("testSchema"); + assertThat(session.getPath().getRawPath()).isEqualTo("testPath"); + assertThat(session.getLocale()).isEqualTo(Locale.TAIWAN); + assertThat(session.getTimeZoneKey()).isEqualTo(getTimeZoneKey("Asia/Taipei")); + assertThat(session.getRemoteUserAddress().get()).isEqualTo("testRemote"); + assertThat(session.getClientInfo().get()).isEqualTo("client-info"); + assertThat(session.getClientTags()).isEqualTo(ImmutableSet.of("tag1", "tag2", "tag3")); + assertThat(session.getSystemProperties()).isEqualTo(ImmutableMap.builder() .put(QUERY_MAX_MEMORY, "1GB") .put(JOIN_DISTRIBUTION_TYPE, "partitioned") .put(MAX_HASH_PARTITION_COUNT, "43") .buildOrThrow()); - assertEquals(session.getPreparedStatements(), ImmutableMap.builder() + assertThat(session.getPreparedStatements()).isEqualTo(ImmutableMap.builder() .put("query1", "select * from foo") .put("query2", "select * from bar") .buildOrThrow()); @@ -108,14 +107,14 @@ public void testEmptyClientTags() { MultivaluedMap headers1 = new GuavaMultivaluedMap<>(ImmutableListMultimap.of(TRINO_HEADERS.requestUser(), "testUser")); SessionContext context1 = SESSION_CONTEXT_FACTORY.createSessionContext(headers1, Optional.empty(), Optional.of("remoteAddress"), Optional.empty()); - assertEquals(context1.getClientTags(), ImmutableSet.of()); + assertThat(context1.getClientTags()).isEqualTo(ImmutableSet.of()); MultivaluedMap headers2 = new GuavaMultivaluedMap<>(ImmutableListMultimap.builder() .put(TRINO_HEADERS.requestUser(), "testUser") .put(TRINO_HEADERS.requestClientTags(), "") .build()); SessionContext context2 = SESSION_CONTEXT_FACTORY.createSessionContext(headers2, Optional.empty(), Optional.of("remoteAddress"), Optional.empty()); - assertEquals(context2.getClientTags(), ImmutableSet.of()); + assertThat(context2.getClientTags()).isEqualTo(ImmutableSet.of()); } @Test @@ -126,11 +125,11 @@ public void testClientCapabilities() .put(TRINO_HEADERS.requestClientCapabilities(), "foo, bar") .build()); SessionContext context1 = SESSION_CONTEXT_FACTORY.createSessionContext(headers1, Optional.empty(), Optional.of("remoteAddress"), Optional.empty()); - assertEquals(context1.getClientCapabilities(), ImmutableSet.of("foo", "bar")); + assertThat(context1.getClientCapabilities()).isEqualTo(ImmutableSet.of("foo", "bar")); MultivaluedMap headers2 = new GuavaMultivaluedMap<>(ImmutableListMultimap.of(TRINO_HEADERS.requestUser(), "testUser")); SessionContext context2 = SESSION_CONTEXT_FACTORY.createSessionContext(headers2, Optional.empty(), Optional.of("remoteAddress"), Optional.empty()); - assertEquals(context2.getClientCapabilities(), ImmutableSet.of()); + assertThat(context2.getClientCapabilities()).isEqualTo(ImmutableSet.of()); } @Test @@ -158,18 +157,16 @@ public void testSqlPathCreation() rawPath, Optional.empty()); - assertEquals( - path.getPath(), - ImmutableList.builder() - .add(new CatalogSchemaName(GlobalSystemConnector.NAME, QUERY_LOCAL_SCHEMA)) - .add(new CatalogSchemaName(GlobalSystemConnector.NAME, BUILTIN_SCHEMA)) - .add(new CatalogSchemaName("normal", "schema")) - .add(new CatalogSchemaName("who.uses.periods", "in.schema.names")) - .add(new CatalogSchemaName("same,deal", "with,commas")) - .add(new CatalogSchemaName("aterrible", "thing!@#$%^&*()")) - .build()); - - assertEquals(path.toString(), rawPath); + assertThat(path.getPath()).isEqualTo(ImmutableList.builder() + .add(new CatalogSchemaName(GlobalSystemConnector.NAME, QUERY_LOCAL_SCHEMA)) + .add(new CatalogSchemaName(GlobalSystemConnector.NAME, BUILTIN_SCHEMA)) + .add(new CatalogSchemaName("normal", "schema")) + .add(new CatalogSchemaName("who.uses.periods", "in.schema.names")) + .add(new CatalogSchemaName("same,deal", "with,commas")) + .add(new CatalogSchemaName("aterrible", "thing!@#$%^&*()")) + .build()); + + assertThat(path.toString()).isEqualTo(rawPath); } @Test diff --git a/core/trino-main/src/test/java/io/trino/server/TestQueryStateInfo.java b/core/trino-main/src/test/java/io/trino/server/TestQueryStateInfo.java index 107369fbd4e5..0b59f1f682c5 100644 --- a/core/trino-main/src/test/java/io/trino/server/TestQueryStateInfo.java +++ b/core/trino-main/src/test/java/io/trino/server/TestQueryStateInfo.java @@ -44,7 +44,7 @@ import static io.trino.spi.resourcegroups.SchedulingPolicy.WEIGHTED; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestQueryStateInfo { @@ -73,28 +73,28 @@ public void testQueryStateInfo() Optional.of(rootAX.getId()), Optional.of(ImmutableList.of(rootAX.getInfo(), rootA.getInfo(), root.getInfo()))); - assertEquals(query.getQuery(), "SELECT 1"); - assertEquals(query.getQueryId().toString(), "query_root_a_x"); - assertEquals(query.getQueryState(), QUEUED); - assertEquals(query.getProgress(), Optional.empty()); + assertThat(query.getQuery()).isEqualTo("SELECT 1"); + assertThat(query.getQueryId().toString()).isEqualTo("query_root_a_x"); + assertThat(query.getQueryState()).isEqualTo(QUEUED); + assertThat(query.getProgress()).isEqualTo(Optional.empty()); List chainInfo = query.getPathToRoot().get(); - assertEquals(chainInfo.size(), 3); + assertThat(chainInfo.size()).isEqualTo(3); ResourceGroupInfo rootAInfo = chainInfo.get(1); ResourceGroupInfo expectedRootAInfo = rootA.getInfo(); - assertEquals(rootAInfo.getId(), expectedRootAInfo.getId()); - assertEquals(rootAInfo.getState(), expectedRootAInfo.getState()); - assertEquals(rootAInfo.getNumRunningQueries(), expectedRootAInfo.getNumRunningQueries()); - assertEquals(rootAInfo.getNumQueuedQueries(), expectedRootAInfo.getNumQueuedQueries()); + assertThat(rootAInfo.getId()).isEqualTo(expectedRootAInfo.getId()); + assertThat(rootAInfo.getState()).isEqualTo(expectedRootAInfo.getState()); + assertThat(rootAInfo.getNumRunningQueries()).isEqualTo(expectedRootAInfo.getNumRunningQueries()); + assertThat(rootAInfo.getNumQueuedQueries()).isEqualTo(expectedRootAInfo.getNumQueuedQueries()); ResourceGroupInfo actualRootInfo = chainInfo.get(2); ResourceGroupInfo expectedRootInfo = root.getInfo(); - assertEquals(actualRootInfo.getId(), expectedRootInfo.getId()); - assertEquals(actualRootInfo.getState(), expectedRootInfo.getState()); - assertEquals(actualRootInfo.getNumRunningQueries(), expectedRootInfo.getNumRunningQueries()); - assertEquals(actualRootInfo.getNumQueuedQueries(), expectedRootInfo.getNumQueuedQueries()); + assertThat(actualRootInfo.getId()).isEqualTo(expectedRootInfo.getId()); + assertThat(actualRootInfo.getState()).isEqualTo(expectedRootInfo.getState()); + assertThat(actualRootInfo.getNumRunningQueries()).isEqualTo(expectedRootInfo.getNumRunningQueries()); + assertThat(actualRootInfo.getNumQueuedQueries()).isEqualTo(expectedRootInfo.getNumQueuedQueries()); } private QueryInfo createQueryInfo(String queryId, QueryState state, String query) diff --git a/core/trino-main/src/test/java/io/trino/server/TestQueryStateInfoResource.java b/core/trino-main/src/test/java/io/trino/server/TestQueryStateInfoResource.java index cb63b79800fa..41bb35e62103 100644 --- a/core/trino-main/src/test/java/io/trino/server/TestQueryStateInfoResource.java +++ b/core/trino-main/src/test/java/io/trino/server/TestQueryStateInfoResource.java @@ -49,13 +49,11 @@ import static io.trino.testing.TestingAccessControlManager.privilege; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Fail.fail; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -139,7 +137,7 @@ public void testGetAllQueryStateInfos() .build(), createJsonResponseHandler(listJsonCodec(QueryStateInfo.class))); - assertEquals(infos.size(), 2); + assertThat(infos.size()).isEqualTo(2); } @Test @@ -152,7 +150,7 @@ public void testGetQueryStateInfosForUser() .build(), createJsonResponseHandler(listJsonCodec(QueryStateInfo.class))); - assertEquals(infos.size(), 1); + assertThat(infos.size()).isEqualTo(1); } @Test @@ -165,7 +163,7 @@ public void testGetQueryStateInfosForUserNoResult() .build(), createJsonResponseHandler(listJsonCodec(QueryStateInfo.class))); - assertTrue(infos.isEmpty()); + assertThat(infos.isEmpty()).isTrue(); } @Test @@ -178,7 +176,7 @@ public void testGetQueryStateInfo() .build(), createJsonResponseHandler(jsonCodec(QueryStateInfo.class))); - assertNotNull(info); + assertThat(info).isNotNull(); } @Test @@ -190,7 +188,7 @@ public void testGetAllQueryStateInfosDenied() .setHeader(TRINO_HEADERS.requestUser(), "any-other-user") .build(), createJsonResponseHandler(listJsonCodec(QueryStateInfo.class))); - assertEquals(infos.size(), 2); + assertThat(infos.size()).isEqualTo(2); testGetAllQueryStateInfosDenied("user1", 1); testGetAllQueryStateInfosDenied("any-other-user", 0); @@ -207,7 +205,7 @@ private void testGetAllQueryStateInfosDenied(String executionUser, int expectedC .build(), createJsonResponseHandler(listJsonCodec(QueryStateInfo.class))); - assertEquals(infos.size(), expectedCount); + assertThat(infos.size()).isEqualTo(expectedCount); } finally { server.getAccessControl().reset(); diff --git a/core/trino-main/src/test/java/io/trino/server/TestSessionPropertyDefaults.java b/core/trino-main/src/test/java/io/trino/server/TestSessionPropertyDefaults.java index b27eea657310..a945b0e35ff3 100644 --- a/core/trino-main/src/test/java/io/trino/server/TestSessionPropertyDefaults.java +++ b/core/trino-main/src/test/java/io/trino/server/TestSessionPropertyDefaults.java @@ -39,7 +39,7 @@ import static io.trino.SystemSessionProperties.QUERY_MAX_TOTAL_MEMORY; import static io.trino.testing.TestingHandles.TEST_CATALOG_HANDLE; import static io.trino.testing.TestingHandles.TEST_CATALOG_NAME; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestSessionPropertyDefaults { @@ -82,32 +82,28 @@ public void testApplyDefaultProperties() .setCatalogSessionProperty(TEST_CATALOG_NAME, "explicit_set", "explicit_set") // Override this default catalog property .build(); - assertEquals(session.getSystemProperties(), ImmutableMap.builder() + assertThat(session.getSystemProperties()).isEqualTo(ImmutableMap.builder() .put(QUERY_MAX_MEMORY, "1GB") .put(JOIN_DISTRIBUTION_TYPE, "partitioned") .put(MAX_HASH_PARTITION_COUNT, "43") .buildOrThrow()); - assertEquals( - session.getCatalogProperties(), - ImmutableMap.of( - TEST_CATALOG_NAME, - ImmutableMap.of("explicit_set", "explicit_set"))); + assertThat(session.getCatalogProperties()).isEqualTo(ImmutableMap.of( + TEST_CATALOG_NAME, + ImmutableMap.of("explicit_set", "explicit_set"))); session = sessionPropertyDefaults.newSessionWithDefaultProperties(session, Optional.empty(), TEST_RESOURCE_GROUP_ID); - assertEquals(session.getSystemProperties(), ImmutableMap.builder() + assertThat(session.getSystemProperties()).isEqualTo(ImmutableMap.builder() .put(QUERY_MAX_MEMORY, "1GB") // User provided value overrides default value .put(JOIN_DISTRIBUTION_TYPE, "partitioned") // User provided value is used .put(MAX_HASH_PARTITION_COUNT, "43") // User provided value is used .put(QUERY_MAX_TOTAL_MEMORY, "2GB") // Default value is used .buildOrThrow()); - assertEquals( - session.getCatalogProperties(), - ImmutableMap.of( - TEST_CATALOG_NAME, - ImmutableMap.builder() - .put("explicit_set", "explicit_set") // User provided value overrides default value - .put("catalog_default", "catalog_default") // Default value is used - .buildOrThrow())); + assertThat(session.getCatalogProperties()).isEqualTo(ImmutableMap.of( + TEST_CATALOG_NAME, + ImmutableMap.builder() + .put("explicit_set", "explicit_set") // User provided value overrides default value + .put("catalog_default", "catalog_default") // Default value is used + .buildOrThrow())); } } diff --git a/core/trino-main/src/test/java/io/trino/server/TestSliceSerialization.java b/core/trino-main/src/test/java/io/trino/server/TestSliceSerialization.java index b68693f0ff35..36196b2e3910 100644 --- a/core/trino-main/src/test/java/io/trino/server/TestSliceSerialization.java +++ b/core/trino-main/src/test/java/io/trino/server/TestSliceSerialization.java @@ -32,9 +32,9 @@ import static com.google.common.base.MoreObjects.toStringHelper; import static java.util.Objects.requireNonNull; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -90,7 +90,7 @@ private void testRoundTrip(Slice slice) Container expected = new Container(slice); String json = objectMapper.writeValueAsString(expected); Container actual = objectMapper.readValue(json, Container.class); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } public static class Container diff --git a/core/trino-main/src/test/java/io/trino/server/protocol/TestQueryResultRows.java b/core/trino-main/src/test/java/io/trino/server/protocol/TestQueryResultRows.java index 341592e3261c..7580e1293419 100644 --- a/core/trino-main/src/test/java/io/trino/server/protocol/TestQueryResultRows.java +++ b/core/trino-main/src/test/java/io/trino/server/protocol/TestQueryResultRows.java @@ -56,8 +56,6 @@ import static java.util.Collections.singletonMap; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestQueryResultRows { @@ -191,10 +189,12 @@ public void shouldOmitBadRows() .addPages(pages) .build(); - assertFalse(rows.isEmpty(), "rows are empty"); + assertThat(rows.isEmpty()) + .describedAs("rows are empty") + .isFalse(); assertThat(rows.getTotalRowsCount()).isEqualTo(5); assertThat(rows.getColumns()).isEqualTo(Optional.of(columns)); - assertTrue(rows.getUpdateCount().isEmpty()); + assertThat(rows.getUpdateCount().isEmpty()).isTrue(); assertThat(getAllValues(rows)) .containsExactly(ImmutableList.of(0, 0)); @@ -249,7 +249,9 @@ public void shouldHandleNullValues() .addPages(pages) .build(); - assertFalse(rows.isEmpty(), "rows are empty"); + assertThat(rows.isEmpty()) + .describedAs("rows are empty") + .isFalse(); assertThat(rows.getTotalRowsCount()).isEqualTo(3); assertThat(getAllValues(rows)) @@ -277,7 +279,9 @@ public void shouldHandleNullTimestamps() .build(); assertThat(exceptionConsumer.getExceptions()).isEmpty(); - assertFalse(rows.isEmpty(), "rows are empty"); + assertThat(rows.isEmpty()) + .describedAs("rows are empty") + .isFalse(); assertThat(rows.getTotalRowsCount()).isEqualTo(1); assertThat(getAllValues(rows)) @@ -303,7 +307,9 @@ public void shouldHandleNullValuesInArray() .build(); assertThat(exceptionConsumer.getExceptions()).isEmpty(); - assertFalse(rows.isEmpty(), "rows are empty"); + assertThat(rows.isEmpty()) + .describedAs("rows are empty") + .isFalse(); assertThat(rows.getTotalRowsCount()).isEqualTo(1); assertThat(getAllValues(rows)) @@ -331,7 +337,9 @@ public void shouldHandleNullValuesInMap() .build(); assertThat(exceptionConsumer.getExceptions()).isEmpty(); - assertFalse(rows.isEmpty(), "rows are empty"); + assertThat(rows.isEmpty()) + .describedAs("rows are empty") + .isFalse(); assertThat(rows.getTotalRowsCount()).isEqualTo(1); assertThat(getAllValues(rows)) @@ -363,7 +371,9 @@ public void shouldHandleNullValuesInRow() .build(); assertThat(exceptionConsumer.getExceptions()).isEmpty(); - assertFalse(rows.isEmpty(), "rows are empty"); + assertThat(rows.isEmpty()) + .describedAs("rows are empty") + .isFalse(); assertThat(rows.getTotalRowsCount()).isEqualTo(1); List> allValues = getAllValues(rows); diff --git a/core/trino-main/src/test/java/io/trino/server/remotetask/TestBackoff.java b/core/trino-main/src/test/java/io/trino/server/remotetask/TestBackoff.java index 6bf65b41edc1..dfba427882be 100644 --- a/core/trino-main/src/test/java/io/trino/server/remotetask/TestBackoff.java +++ b/core/trino-main/src/test/java/io/trino/server/remotetask/TestBackoff.java @@ -22,9 +22,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestBackoff { @@ -38,27 +36,27 @@ public void testFailureInterval() ticker.increment(10, MICROSECONDS); // verify initial state - assertEquals(backoff.getFailureCount(), 0); - assertEquals(backoff.getFailureDuration().roundTo(SECONDS), 0); + assertThat(backoff.getFailureCount()).isEqualTo(0); + assertThat(backoff.getFailureDuration().roundTo(SECONDS)).isEqualTo(0); // first failure, should never fail - assertFalse(backoff.failure()); - assertEquals(backoff.getFailureCount(), 1); - assertEquals(backoff.getFailureDuration().roundTo(SECONDS), 0); + assertThat(backoff.failure()).isFalse(); + assertThat(backoff.getFailureCount()).isEqualTo(1); + assertThat(backoff.getFailureDuration().roundTo(SECONDS)).isEqualTo(0); ticker.increment(14, SECONDS); // second failure within the limit, should not fail - assertFalse(backoff.failure()); - assertEquals(backoff.getFailureCount(), 2); - assertEquals(backoff.getFailureDuration().roundTo(SECONDS), 14); + assertThat(backoff.failure()).isFalse(); + assertThat(backoff.getFailureCount()).isEqualTo(2); + assertThat(backoff.getFailureDuration().roundTo(SECONDS)).isEqualTo(14); ticker.increment(1, SECONDS); // final failure after the limit causes failure - assertTrue(backoff.failure()); - assertEquals(backoff.getFailureCount(), 3); - assertEquals(backoff.getFailureDuration().roundTo(SECONDS), 15); + assertThat(backoff.failure()).isTrue(); + assertThat(backoff.getFailureCount()).isEqualTo(3); + assertThat(backoff.getFailureDuration().roundTo(SECONDS)).isEqualTo(15); } @Test @@ -71,27 +69,27 @@ public void testMinTries() ticker.increment(10, MICROSECONDS); // verify initial state - assertEquals(backoff.getFailureCount(), 0); - assertEquals(backoff.getFailureDuration().roundTo(SECONDS), 0); + assertThat(backoff.getFailureCount()).isEqualTo(0); + assertThat(backoff.getFailureDuration().roundTo(SECONDS)).isEqualTo(0); // first failure, should never fail - assertFalse(backoff.failure()); - assertEquals(backoff.getFailureCount(), 1); - assertEquals(backoff.getFailureDuration().roundTo(SECONDS), 0); + assertThat(backoff.failure()).isFalse(); + assertThat(backoff.getFailureCount()).isEqualTo(1); + assertThat(backoff.getFailureDuration().roundTo(SECONDS)).isEqualTo(0); ticker.increment(14, SECONDS); // second failure under min failures, should not fail - assertFalse(backoff.failure()); - assertEquals(backoff.getFailureCount(), 2); - assertEquals(backoff.getFailureDuration().roundTo(SECONDS), 14); + assertThat(backoff.failure()).isFalse(); + assertThat(backoff.getFailureCount()).isEqualTo(2); + assertThat(backoff.getFailureDuration().roundTo(SECONDS)).isEqualTo(14); ticker.increment(1, SECONDS); // last try failed - assertTrue(backoff.failure()); - assertEquals(backoff.getFailureCount(), 3); - assertEquals(backoff.getFailureDuration().roundTo(SECONDS), 15); + assertThat(backoff.failure()).isTrue(); + assertThat(backoff.getFailureCount()).isEqualTo(3); + assertThat(backoff.getFailureDuration().roundTo(SECONDS)).isEqualTo(15); } @Test @@ -103,30 +101,30 @@ public void testStartRequest() Backoff backoff = new Backoff(1, new Duration(15, SECONDS), ticker, ImmutableList.of(new Duration(10, MILLISECONDS))); ticker.increment(10, MICROSECONDS); - assertFalse(backoff.failure()); - assertEquals(backoff.getFailureCount(), 1); - assertEquals(backoff.getFailureDuration().roundTo(SECONDS), 0); - assertEquals(backoff.getFailureRequestTimeTotal().roundTo(SECONDS), 0); + assertThat(backoff.failure()).isFalse(); + assertThat(backoff.getFailureCount()).isEqualTo(1); + assertThat(backoff.getFailureDuration().roundTo(SECONDS)).isEqualTo(0); + assertThat(backoff.getFailureRequestTimeTotal().roundTo(SECONDS)).isEqualTo(0); ticker.increment(7, SECONDS); backoff.startRequest(); ticker.increment(7, SECONDS); - assertFalse(backoff.failure()); - assertEquals(backoff.getFailureCount(), 2); - assertEquals(backoff.getFailureDuration().roundTo(SECONDS), 14); + assertThat(backoff.failure()).isFalse(); + assertThat(backoff.getFailureCount()).isEqualTo(2); + assertThat(backoff.getFailureDuration().roundTo(SECONDS)).isEqualTo(14); // failed request took 7 seconds. - assertEquals(backoff.getFailureRequestTimeTotal().roundTo(SECONDS), 7); + assertThat(backoff.getFailureRequestTimeTotal().roundTo(SECONDS)).isEqualTo(7); ticker.increment(1, SECONDS); backoff.startRequest(); ticker.increment(1, SECONDS); - assertTrue(backoff.failure()); - assertEquals(backoff.getFailureCount(), 3); - assertEquals(backoff.getFailureDuration().roundTo(SECONDS), 16); + assertThat(backoff.failure()).isTrue(); + assertThat(backoff.getFailureCount()).isEqualTo(3); + assertThat(backoff.getFailureDuration().roundTo(SECONDS)).isEqualTo(16); // failed requests took 7+1 seconds. - assertEquals(backoff.getFailureRequestTimeTotal().roundTo(SECONDS), 8); + assertThat(backoff.getFailureRequestTimeTotal().roundTo(SECONDS)).isEqualTo(8); } @Test @@ -143,53 +141,53 @@ public void testDelay() new Duration(4, SECONDS), new Duration(8, SECONDS))); - assertEquals(backoff.getFailureCount(), 0); - assertEquals(backoff.getFailureDuration().roundTo(SECONDS), 0); + assertThat(backoff.getFailureCount()).isEqualTo(0); + assertThat(backoff.getFailureDuration().roundTo(SECONDS)).isEqualTo(0); - assertFalse(backoff.failure()); - assertEquals(backoff.getFailureCount(), 1); - assertEquals(backoff.getFailureDuration().roundTo(SECONDS), 0); + assertThat(backoff.failure()).isFalse(); + assertThat(backoff.getFailureCount()).isEqualTo(1); + assertThat(backoff.getFailureDuration().roundTo(SECONDS)).isEqualTo(0); long backoffDelay = backoff.getBackoffDelayNanos(); - assertEquals(NANOSECONDS.toSeconds(backoffDelay), 0); + assertThat(NANOSECONDS.toSeconds(backoffDelay)).isEqualTo(0); ticker.increment(backoffDelay, NANOSECONDS); - assertFalse(backoff.failure()); - assertEquals(backoff.getFailureCount(), 2); - assertEquals(backoff.getFailureDuration().roundTo(SECONDS), 0); + assertThat(backoff.failure()).isFalse(); + assertThat(backoff.getFailureCount()).isEqualTo(2); + assertThat(backoff.getFailureDuration().roundTo(SECONDS)).isEqualTo(0); backoffDelay = backoff.getBackoffDelayNanos(); - assertEquals(NANOSECONDS.toSeconds(backoffDelay), 1); + assertThat(NANOSECONDS.toSeconds(backoffDelay)).isEqualTo(1); ticker.increment(backoffDelay, NANOSECONDS); - assertFalse(backoff.failure()); - assertEquals(backoff.getFailureCount(), 3); - assertEquals(backoff.getFailureDuration().roundTo(SECONDS), 1); + assertThat(backoff.failure()).isFalse(); + assertThat(backoff.getFailureCount()).isEqualTo(3); + assertThat(backoff.getFailureDuration().roundTo(SECONDS)).isEqualTo(1); backoffDelay = backoff.getBackoffDelayNanos(); - assertEquals(NANOSECONDS.toSeconds(backoffDelay), 2); + assertThat(NANOSECONDS.toSeconds(backoffDelay)).isEqualTo(2); ticker.increment(backoffDelay, NANOSECONDS); - assertFalse(backoff.failure()); - assertEquals(backoff.getFailureCount(), 4); - assertEquals(backoff.getFailureDuration().roundTo(SECONDS), 3); + assertThat(backoff.failure()).isFalse(); + assertThat(backoff.getFailureCount()).isEqualTo(4); + assertThat(backoff.getFailureDuration().roundTo(SECONDS)).isEqualTo(3); backoffDelay = backoff.getBackoffDelayNanos(); - assertEquals(NANOSECONDS.toSeconds(backoffDelay), 4); + assertThat(NANOSECONDS.toSeconds(backoffDelay)).isEqualTo(4); ticker.increment(backoffDelay, NANOSECONDS); - assertFalse(backoff.failure()); - assertEquals(backoff.getFailureCount(), 5); - assertEquals(backoff.getFailureDuration().roundTo(SECONDS), 7); + assertThat(backoff.failure()).isFalse(); + assertThat(backoff.getFailureCount()).isEqualTo(5); + assertThat(backoff.getFailureDuration().roundTo(SECONDS)).isEqualTo(7); backoffDelay = backoff.getBackoffDelayNanos(); - assertEquals(NANOSECONDS.toSeconds(backoffDelay), 8); + assertThat(NANOSECONDS.toSeconds(backoffDelay)).isEqualTo(8); ticker.increment(backoffDelay, NANOSECONDS); - assertTrue(backoff.failure()); - assertEquals(backoff.getFailureCount(), 6); - assertEquals(backoff.getFailureDuration().roundTo(SECONDS), 15); + assertThat(backoff.failure()).isTrue(); + assertThat(backoff.getFailureCount()).isEqualTo(6); + assertThat(backoff.getFailureDuration().roundTo(SECONDS)).isEqualTo(15); backoffDelay = backoff.getBackoffDelayNanos(); - assertEquals(NANOSECONDS.toSeconds(backoffDelay), 8); + assertThat(NANOSECONDS.toSeconds(backoffDelay)).isEqualTo(8); } } diff --git a/core/trino-main/src/test/java/io/trino/server/remotetask/TestHttpRemoteTask.java b/core/trino-main/src/test/java/io/trino/server/remotetask/TestHttpRemoteTask.java index c0a8fc0f1180..f176c998379e 100644 --- a/core/trino-main/src/test/java/io/trino/server/remotetask/TestHttpRemoteTask.java +++ b/core/trino-main/src/test/java/io/trino/server/remotetask/TestHttpRemoteTask.java @@ -151,9 +151,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestHttpRemoteTask { @@ -274,29 +272,29 @@ public void testDynamicFilters() remoteTask.start(); future.get(); - assertEquals( - dynamicFilter.getCurrentPredicate(), - TupleDomain.withColumnDomains(ImmutableMap.of( - handle1, Domain.singleValue(BIGINT, 1L)))); - assertEquals(testingTaskResource.getDynamicFiltersFetchCounter(), 1); + assertThat(dynamicFilter.getCurrentPredicate()).isEqualTo(TupleDomain.withColumnDomains(ImmutableMap.of( + handle1, Domain.singleValue(BIGINT, 1L)))); + assertThat(testingTaskResource.getDynamicFiltersFetchCounter()).isEqualTo(1); // make sure dynamic filters are not collected for every status update assertEventually( new Duration(15, SECONDS), () -> assertGreaterThanOrEqual(testingTaskResource.getStatusFetchCounter(), 3L)); - assertEquals(testingTaskResource.getDynamicFiltersFetchCounter(), 1L, testingTaskResource.getDynamicFiltersFetchRequests().toString()); + assertThat(testingTaskResource.getDynamicFiltersFetchCounter()) + .describedAs(testingTaskResource.getDynamicFiltersFetchRequests().toString()) + .isEqualTo(1L); future = dynamicFilter.isBlocked(); testingTaskResource.setDynamicFilterDomains(new VersionedDynamicFilterDomains( 2L, ImmutableMap.of(filterId2, Domain.singleValue(BIGINT, 2L)))); future.get(); - assertEquals( - dynamicFilter.getCurrentPredicate(), - TupleDomain.withColumnDomains(ImmutableMap.of( - handle1, Domain.singleValue(BIGINT, 1L), - handle2, Domain.singleValue(BIGINT, 2L)))); - assertEquals(testingTaskResource.getDynamicFiltersFetchCounter(), 2L, testingTaskResource.getDynamicFiltersFetchRequests().toString()); + assertThat(dynamicFilter.getCurrentPredicate()).isEqualTo(TupleDomain.withColumnDomains(ImmutableMap.of( + handle1, Domain.singleValue(BIGINT, 1L), + handle2, Domain.singleValue(BIGINT, 2L)))); + assertThat(testingTaskResource.getDynamicFiltersFetchCounter()) + .describedAs(testingTaskResource.getDynamicFiltersFetchRequests().toString()) + .isEqualTo(2L); assertGreaterThanOrEqual(testingTaskResource.getStatusFetchCounter(), 4L); httpRemoteTaskFactory.stop(); @@ -348,10 +346,8 @@ public void testOutboundDynamicFilters() new TaskId(new StageId(queryId.getId(), 1), 1, 0), ImmutableMap.of(filterId1, Domain.singleValue(BIGINT, 1L))); future.get(); - assertEquals( - dynamicFilter.getCurrentPredicate(), - TupleDomain.withColumnDomains(ImmutableMap.of( - handle1, Domain.singleValue(BIGINT, 1L)))); + assertThat(dynamicFilter.getCurrentPredicate()).isEqualTo(TupleDomain.withColumnDomains(ImmutableMap.of( + handle1, Domain.singleValue(BIGINT, 1L)))); // Create remote task after dynamic filter is created to simulate new nodes joining HttpRemoteTaskFactory httpRemoteTaskFactory = createHttpRemoteTaskFactory(testingTaskResource, dynamicFilterService); @@ -360,39 +356,33 @@ public void testOutboundDynamicFilters() remoteTask.start(); assertEventually( new Duration(10, SECONDS), - () -> assertEquals(testingTaskResource.getDynamicFiltersSentCounter(), 1L)); - assertEquals(testingTaskResource.getCreateOrUpdateCounter(), 1L); + () -> assertThat(testingTaskResource.getDynamicFiltersSentCounter()).isEqualTo(1L)); + assertThat(testingTaskResource.getCreateOrUpdateCounter()).isEqualTo(1L); // schedule a couple of splits to trigger task updates addSplit(remoteTask, testingTaskResource, 1); addSplit(remoteTask, testingTaskResource, 2); // make sure dynamic filter was sent in task updates only once - assertEquals(testingTaskResource.getDynamicFiltersSentCounter(), 1L); - assertEquals(testingTaskResource.getCreateOrUpdateCounter(), 3L); - assertEquals( - testingTaskResource.getLatestDynamicFilterFromCoordinator(), - ImmutableMap.of(filterId1, Domain.singleValue(BIGINT, 1L))); + assertThat(testingTaskResource.getDynamicFiltersSentCounter()).isEqualTo(1L); + assertThat(testingTaskResource.getCreateOrUpdateCounter()).isEqualTo(3L); + assertThat(testingTaskResource.getLatestDynamicFilterFromCoordinator()).isEqualTo(ImmutableMap.of(filterId1, Domain.singleValue(BIGINT, 1L))); future = dynamicFilter.isBlocked(); dynamicFilterService.addTaskDynamicFilters( new TaskId(new StageId(queryId.getId(), 1), 1, 0), ImmutableMap.of(filterId2, Domain.singleValue(BIGINT, 2L))); future.get(); - assertEquals( - dynamicFilter.getCurrentPredicate(), - TupleDomain.withColumnDomains(ImmutableMap.of( - handle1, Domain.singleValue(BIGINT, 1L), - handle2, Domain.singleValue(BIGINT, 2L)))); + assertThat(dynamicFilter.getCurrentPredicate()).isEqualTo(TupleDomain.withColumnDomains(ImmutableMap.of( + handle1, Domain.singleValue(BIGINT, 1L), + handle2, Domain.singleValue(BIGINT, 2L)))); // dynamic filter should be sent even though there were no further splits scheduled assertEventually( new Duration(10, SECONDS), - () -> assertEquals(testingTaskResource.getDynamicFiltersSentCounter(), 2L)); - assertEquals(testingTaskResource.getCreateOrUpdateCounter(), 4L); + () -> assertThat(testingTaskResource.getDynamicFiltersSentCounter()).isEqualTo(2L)); + assertThat(testingTaskResource.getCreateOrUpdateCounter()).isEqualTo(4L); // previously sent dynamic filter should not be repeated - assertEquals( - testingTaskResource.getLatestDynamicFilterFromCoordinator(), - ImmutableMap.of(filterId2, Domain.singleValue(BIGINT, 2L))); + assertThat(testingTaskResource.getLatestDynamicFilterFromCoordinator()).isEqualTo(ImmutableMap.of(filterId2, Domain.singleValue(BIGINT, 2L))); httpRemoteTaskFactory.stop(); } @@ -429,7 +419,7 @@ public void testAdaptiveRemoteTaskRequestSize() poll(() -> testingTaskResource.getTaskSplitAssignment(TABLE_SCAN_NODE_ID) != null); poll(() -> testingTaskResource.getTaskSplitAssignment(TABLE_SCAN_NODE_ID).getSplits().size() == 100); // to check whether all the splits are sent or not - assertTrue(testingTaskResource.getCreateOrUpdateCounter() > 1); // to check whether the splits are divided or not + assertThat(testingTaskResource.getCreateOrUpdateCounter() > 1).isTrue(); // to check whether the splits are divided or not remoteTask.noMoreSplits(TABLE_SCAN_NODE_ID); poll(() -> testingTaskResource.getTaskSplitAssignment(TABLE_SCAN_NODE_ID).isNoMoreSplits()); @@ -467,11 +457,11 @@ public void testAdjustSplitBatchSize() } // decrease splitBatchSize - assertTrue(((HttpRemoteTask) remoteTask).adjustSplitBatchSize(ImmutableList.of(new SplitAssignment(TABLE_SCAN_NODE_ID, splits, true)), 1000000, 500)); + assertThat(((HttpRemoteTask) remoteTask).adjustSplitBatchSize(ImmutableList.of(new SplitAssignment(TABLE_SCAN_NODE_ID, splits, true)), 1000000, 500)).isTrue(); assertLessThan(((HttpRemoteTask) remoteTask).splitBatchSize.get(), 250); // increase splitBatchSize - assertFalse(((HttpRemoteTask) remoteTask).adjustSplitBatchSize(ImmutableList.of(new SplitAssignment(TABLE_SCAN_NODE_ID, splits, true)), 1000, 100)); + assertThat(((HttpRemoteTask) remoteTask).adjustSplitBatchSize(ImmutableList.of(new SplitAssignment(TABLE_SCAN_NODE_ID, splits, true)), 1000, 100)).isFalse(); assertGreaterThan(((HttpRemoteTask) remoteTask).splitBatchSize.get(), 250); } @@ -490,18 +480,22 @@ private void runTest(FailureScenario failureScenario) waitUntilIdle(lastActivityNanos); httpRemoteTaskFactory.stop(); - assertTrue(remoteTask.getTaskStatus().getState().isDone(), format("TaskStatus is not in a done state: %s", remoteTask.getTaskStatus())); + assertThat(remoteTask.getTaskStatus().getState().isDone()) + .describedAs(format("TaskStatus is not in a done state: %s", remoteTask.getTaskStatus())) + .isTrue(); ErrorCode actualErrorCode = getOnlyElement(remoteTask.getTaskStatus().getFailures()).getErrorCode(); switch (failureScenario) { case TASK_MISMATCH: case TASK_MISMATCH_WHEN_VERSION_IS_HIGH: - assertTrue(remoteTask.getTaskInfo().getTaskStatus().getState().isDone(), format("TaskInfo is not in a done state: %s", remoteTask.getTaskInfo())); - assertEquals(actualErrorCode, REMOTE_TASK_MISMATCH.toErrorCode()); + assertThat(remoteTask.getTaskInfo().getTaskStatus().getState().isDone()) + .describedAs(format("TaskInfo is not in a done state: %s", remoteTask.getTaskInfo())) + .isTrue(); + assertThat(actualErrorCode).isEqualTo(REMOTE_TASK_MISMATCH.toErrorCode()); break; case REJECTED_EXECUTION: // for a rejection to occur, the http client must be shutdown, which means we will not be able to ge the final task info - assertEquals(actualErrorCode, REMOTE_TASK_ERROR.toErrorCode()); + assertThat(actualErrorCode).isEqualTo(REMOTE_TASK_ERROR.toErrorCode()); break; default: throw new UnsupportedOperationException(); diff --git a/core/trino-main/src/test/java/io/trino/server/security/TestResourceSecurity.java b/core/trino-main/src/test/java/io/trino/server/security/TestResourceSecurity.java index 526d54e51f39..c1d7139e5e02 100644 --- a/core/trino-main/src/test/java/io/trino/server/security/TestResourceSecurity.java +++ b/core/trino-main/src/test/java/io/trino/server/security/TestResourceSecurity.java @@ -122,9 +122,6 @@ import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.MINUTES; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; public class TestResourceSecurity { @@ -324,9 +321,9 @@ public void testPasswordAuthenticatorUserMapping() .addHeader("X-Trino-User", TEST_USER_LOGIN) .build(); try (Response response = client.newCall(request).execute()) { - assertEquals(response.code(), SC_OK); - assertEquals(response.header("user"), TEST_USER); - assertEquals(response.header("principal"), TEST_USER_LOGIN); + assertThat(response.code()).isEqualTo(SC_OK); + assertThat(response.header("user")).isEqualTo(TEST_USER); + assertThat(response.header("principal")).isEqualTo(TEST_USER_LOGIN); } } } @@ -675,34 +672,36 @@ private void verifyOAuth2Authenticator(boolean webUiEnabled, boolean refreshToke if (refreshTokensEnabled) { TokenPairSerializer serializer = server.getInstance(Key.get(TokenPairSerializer.class)); TokenPair tokenPair = serializer.deserialize(getOauthToken(client, bearer.getTokenServer())); - assertEquals(tokenPair.accessToken(), tokenServer.getAccessToken()); - assertEquals(tokenPair.refreshToken(), Optional.of(tokenServer.getRefreshToken())); + assertThat(tokenPair.accessToken()).isEqualTo(tokenServer.getAccessToken()); + assertThat(tokenPair.refreshToken()).isEqualTo(Optional.of(tokenServer.getRefreshToken())); } else { - assertEquals(getOauthToken(client, bearer.getTokenServer()), tokenServer.getAccessToken()); + assertThat(getOauthToken(client, bearer.getTokenServer())).isEqualTo(tokenServer.getAccessToken()); } // if Web UI is using oauth so we should get a cookie if (webUiEnabled) { HttpCookie cookie = getCookie(cookieManager, OAUTH2_COOKIE); - assertEquals(cookie.getValue(), tokenServer.getAccessToken()); - assertEquals(cookie.getPath(), "/ui/"); - assertEquals(cookie.getDomain(), baseUri.getHost()); - assertTrue(cookie.getMaxAge() > 0 && cookie.getMaxAge() < MINUTES.toSeconds(5)); - assertTrue(cookie.isHttpOnly()); + assertThat(cookie.getValue()).isEqualTo(tokenServer.getAccessToken()); + assertThat(cookie.getPath()).isEqualTo("/ui/"); + assertThat(cookie.getDomain()).isEqualTo(baseUri.getHost()); + assertThat(cookie.getMaxAge() > 0 && cookie.getMaxAge() < MINUTES.toSeconds(5)).isTrue(); + assertThat(cookie.isHttpOnly()).isTrue(); HttpCookie idTokenCookie = getCookie(cookieManager, ID_TOKEN_COOKIE); - assertEquals(idTokenCookie.getValue(), tokenServer.issueIdToken(Optional.of(hashNonce(bearer.getNonceCookie().getValue())))); - assertEquals(idTokenCookie.getPath(), "/ui/"); - assertEquals(idTokenCookie.getDomain(), baseUri.getHost()); - assertTrue(idTokenCookie.getMaxAge() > 0 && cookie.getMaxAge() < MINUTES.toSeconds(5)); - assertTrue(idTokenCookie.isHttpOnly()); + assertThat(idTokenCookie.getValue()).isEqualTo(tokenServer.issueIdToken(Optional.of(hashNonce(bearer.getNonceCookie().getValue())))); + assertThat(idTokenCookie.getPath()).isEqualTo("/ui/"); + assertThat(idTokenCookie.getDomain()).isEqualTo(baseUri.getHost()); + assertThat(idTokenCookie.getMaxAge() > 0 && cookie.getMaxAge() < MINUTES.toSeconds(5)).isTrue(); + assertThat(idTokenCookie.isHttpOnly()).isTrue(); cookieManager.getCookieStore().removeAll(); } else { List cookies = cookieManager.getCookieStore().getCookies(); - assertTrue(cookies.isEmpty(), "Expected no cookies when webUi is not enabled, but got: " + cookies); + assertThat(cookies.isEmpty()) + .describedAs("Expected no cookies when webUi is not enabled, but got: " + cookies) + .isTrue(); } OkHttpClient clientWithOAuthToken = client.newBuilder() @@ -723,12 +722,16 @@ private static OAuthBearer assertAuthenticateOAuth2Bearer(OkHttpClient client, S String redirectTo; String tokenServer; try (Response response = client.newCall(request).execute()) { - assertEquals(response.code(), SC_UNAUTHORIZED, url); + assertThat(response.code()) + .describedAs(url) + .isEqualTo(SC_UNAUTHORIZED); String authenticateHeader = response.header(WWW_AUTHENTICATE); - assertNotNull(authenticateHeader); + assertThat(authenticateHeader).isNotNull(); Pattern oauth2BearerPattern = Pattern.compile("Bearer x_redirect_server=\"(https://127.0.0.1:[0-9]+/oauth2/token/initiate/.+)\", x_token_server=\"(https://127.0.0.1:[0-9]+/oauth2/token/.+)\""); Matcher matcher = oauth2BearerPattern.matcher(authenticateHeader); - assertTrue(matcher.matches(), format("Invalid authentication header.\nExpected: %s\nPattern: %s", authenticateHeader, oauth2BearerPattern)); + assertThat(matcher.matches()) + .describedAs(format("Invalid authentication header.\nExpected: %s\nPattern: %s", authenticateHeader, oauth2BearerPattern)) + .isTrue(); redirectTo = matcher.group(1); tokenServer = matcher.group(2); } @@ -737,12 +740,14 @@ private static OAuthBearer assertAuthenticateOAuth2Bearer(OkHttpClient client, S .url(redirectTo) .build(); try (Response response = client.newCall(request).execute()) { - assertEquals(response.code(), SC_SEE_OTHER); + assertThat(response.code()).isEqualTo(SC_SEE_OTHER); String locationHeader = response.header(LOCATION); - assertNotNull(locationHeader); + assertThat(locationHeader).isNotNull(); Pattern locationPattern = Pattern.compile(format("%s\\?(.+)", expectedRedirect)); Matcher matcher = locationPattern.matcher(locationHeader); - assertTrue(matcher.matches(), format("Invalid location header.\nExpected: %s\nPattern: %s", expectedRedirect, locationPattern)); + assertThat(matcher.matches()) + .describedAs(format("Invalid location header.\nExpected: %s\nPattern: %s", expectedRedirect, locationPattern)) + .isTrue(); HttpCookie nonceCookie = HttpCookie.parse(requireNonNull(response.header(SET_COOKIE))).get(0); nonceCookie.setDomain(request.url().host()); @@ -810,10 +815,10 @@ public void testOAuth2Groups(Optional> groups) .url(getLocation(httpServerInfo.getHttpsUri(), "/protocol/identity")) .build()) .execute()) { - assertEquals(response.code(), SC_OK); - assertEquals(response.header("user"), TEST_USER); - assertEquals(response.header("principal"), TEST_USER); - assertEquals(response.header("groups"), groups.map(TestResource::toHeader).orElse("")); + assertThat(response.code()).isEqualTo(SC_OK); + assertThat(response.header("user")).isEqualTo(TEST_USER); + assertThat(response.header("principal")).isEqualTo(TEST_USER); + assertThat(response.header("groups")).isEqualTo(groups.map(TestResource::toHeader).orElse("")); } OkHttpClient clientWithOAuthCookie = client.newBuilder() @@ -842,10 +847,10 @@ public List loadForRequest(HttpUrl url) .url(getLocation(httpServerInfo.getHttpsUri(), "/ui/api/identity")) .build()) .execute()) { - assertEquals(response.code(), SC_OK); - assertEquals(response.header("user"), TEST_USER); - assertEquals(response.header("principal"), TEST_USER); - assertEquals(response.header("groups"), groups.map(TestResource::toHeader).orElse("")); + assertThat(response.code()).isEqualTo(SC_OK); + assertThat(response.header("user")).isEqualTo(TEST_USER); + assertThat(response.header("principal")).isEqualTo(TEST_USER); + assertThat(response.header("groups")).isEqualTo(groups.map(TestResource::toHeader).orElse("")); } } } @@ -985,9 +990,9 @@ public void testResourceSecurityImpersonation() .addHeader("X-Trino-User", "impersonated-user") .build(); try (Response response = client.newCall(request).execute()) { - assertEquals(response.code(), SC_OK); - assertEquals(response.header("user"), "impersonated-user"); - assertEquals(response.header("principal"), TEST_USER_LOGIN); + assertThat(response.code()).isEqualTo(SC_OK); + assertThat(response.header("user")).isEqualTo("impersonated-user"); + assertThat(response.header("principal")).isEqualTo(TEST_USER_LOGIN); } } } @@ -1359,7 +1364,9 @@ private static void assertResponseCode(OkHttpClient client, .headers(headers) .build(); try (Response response = client.newCall(request).execute()) { - assertEquals(response.code(), expectedCode, url); + assertThat(response.code()) + .describedAs(url) + .isEqualTo(expectedCode); } } diff --git a/core/trino-main/src/test/java/io/trino/server/security/TestUserMapping.java b/core/trino-main/src/test/java/io/trino/server/security/TestUserMapping.java index 3773976a8410..9998d83515b7 100644 --- a/core/trino-main/src/test/java/io/trino/server/security/TestUserMapping.java +++ b/core/trino-main/src/test/java/io/trino/server/security/TestUserMapping.java @@ -24,8 +24,8 @@ import static io.trino.server.security.UserMapping.Case.KEEP; import static io.trino.server.security.UserMapping.createUserMapping; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestUserMapping { @@ -42,14 +42,14 @@ public void testStaticFactory() throws Exception { UserMapping defaultUserMapping = createUserMapping(Optional.empty(), Optional.empty()); - assertEquals(defaultUserMapping.mapUser("test@example.com"), "test@example.com"); + assertThat(defaultUserMapping.mapUser("test@example.com")).isEqualTo("test@example.com"); UserMapping singlePatternUserMapping = createUserMapping(Optional.of("(.*?)@.*"), Optional.empty()); - assertEquals(singlePatternUserMapping.mapUser("test@example.com"), "test"); + assertThat(singlePatternUserMapping.mapUser("test@example.com")).isEqualTo("test"); UserMapping fileUserMapping = createUserMapping(Optional.empty(), Optional.of(testFile)); - assertEquals(fileUserMapping.mapUser("test@example.com"), "test_file"); - assertEquals(fileUserMapping.mapUser("user"), "user"); + assertThat(fileUserMapping.mapUser("test@example.com")).isEqualTo("test_file"); + assertThat(fileUserMapping.mapUser("user")).isEqualTo("user"); assertThatThrownBy(() -> fileUserMapping.mapUser("test")) .isInstanceOf(UserMappingException.class) .hasMessage("Principal is not allowed"); @@ -64,7 +64,7 @@ public void testSimplePatternRule() throws Exception { UserMapping userMapping = new UserMapping(ImmutableList.of(new Rule("(.*?)@.*"))); - assertEquals(userMapping.mapUser("test@example.com"), "test"); + assertThat(userMapping.mapUser("test@example.com")).isEqualTo("test"); assertThatThrownBy(() -> userMapping.mapUser("no at sign")) .isInstanceOf(UserMappingException.class) .hasMessage("No user mapping patterns match the principal"); @@ -78,7 +78,7 @@ public void testReplacePatternRule() throws Exception { UserMapping userMapping = new UserMapping(ImmutableList.of(new Rule("(.*?)@.*", "$1 ^ $1", true, KEEP))); - assertEquals(userMapping.mapUser("test@example.com"), "test ^ test"); + assertThat(userMapping.mapUser("test@example.com")).isEqualTo("test ^ test"); assertThatThrownBy(() -> userMapping.mapUser("no at sign")) .isInstanceOf(UserMappingException.class) .hasMessage("No user mapping patterns match the principal"); @@ -108,7 +108,7 @@ public void testMultipleRule() throws Exception { UserMapping userMapping = new UserMapping(ImmutableList.of(new Rule("test@example.com", "", false, KEEP), new Rule("(.*?)@example.com"))); - assertEquals(userMapping.mapUser("apple@example.com"), "apple"); + assertThat(userMapping.mapUser("apple@example.com")).isEqualTo("apple"); assertThatThrownBy(() -> userMapping.mapUser("test@example.com")) .isInstanceOf(UserMappingException.class) .hasMessage("Principal is not allowed"); @@ -122,7 +122,7 @@ public void testLowercaseUsernameRule() throws UserMappingException { UserMapping userMapping = new UserMapping(ImmutableList.of(new Rule("(.*)@EXAMPLE\\.COM", "$1", true, UserMapping.Case.LOWER))); - assertEquals(userMapping.mapUser("TEST@EXAMPLE.COM"), "test"); + assertThat(userMapping.mapUser("TEST@EXAMPLE.COM")).isEqualTo("test"); } @Test @@ -130,7 +130,7 @@ public void testUppercaseUsernameRule() throws UserMappingException { UserMapping userMapping = new UserMapping(ImmutableList.of(new Rule("(.*)@example\\.com", "$1", true, UserMapping.Case.UPPER))); - assertEquals(userMapping.mapUser("test@example.com"), "TEST"); + assertThat(userMapping.mapUser("test@example.com")).isEqualTo("TEST"); } @Test @@ -141,15 +141,15 @@ public void testDocsExample() File docExample = new File("../../docs/src/main/sphinx/security/user-mapping.json"); UserMapping userMapping = createUserMapping(Optional.empty(), Optional.of(docExample)); - assertEquals(userMapping.mapUser("apple@example.com"), "apple"); - assertEquals(userMapping.mapUser("apple@uk.example.com"), "apple_uk"); - assertEquals(userMapping.mapUser("apple@de.example.com"), "apple_de"); + assertThat(userMapping.mapUser("apple@example.com")).isEqualTo("apple"); + assertThat(userMapping.mapUser("apple@uk.example.com")).isEqualTo("apple_uk"); + assertThat(userMapping.mapUser("apple@de.example.com")).isEqualTo("apple_de"); assertThatThrownBy(() -> userMapping.mapUser("apple@unknown.com")) .isInstanceOf(UserMappingException.class) .hasMessage("No user mapping patterns match the principal"); assertThatThrownBy(() -> userMapping.mapUser("test@example.com")) .isInstanceOf(UserMappingException.class) .hasMessage("Principal is not allowed"); - assertEquals(userMapping.mapUser("test@uppercase.com"), "TEST"); + assertThat(userMapping.mapUser("test@uppercase.com")).isEqualTo("TEST"); } } diff --git a/core/trino-main/src/test/java/io/trino/server/security/jwt/TestJwkDecoder.java b/core/trino-main/src/test/java/io/trino/server/security/jwt/TestJwkDecoder.java index 166324ea0287..742b2de5784f 100644 --- a/core/trino-main/src/test/java/io/trino/server/security/jwt/TestJwkDecoder.java +++ b/core/trino-main/src/test/java/io/trino/server/security/jwt/TestJwkDecoder.java @@ -39,10 +39,7 @@ import static io.trino.server.security.jwt.JwtUtil.newJwtBuilder; import static io.trino.server.security.jwt.JwtUtil.newJwtParserBuilder; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertSame; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestJwkDecoder { @@ -71,9 +68,9 @@ public void testReadRsaKeys() " }\n" + " ]\n" + "}"); - assertEquals(keys.size(), 2); - assertTrue(keys.get("example-rsa") instanceof JwkRsaPublicKey); - assertTrue(keys.get("example-ec") instanceof JwkEcPublicKey); + assertThat(keys.size()).isEqualTo(2); + assertThat(keys.get("example-rsa") instanceof JwkRsaPublicKey).isTrue(); + assertThat(keys.get("example-ec") instanceof JwkEcPublicKey).isTrue(); } @Test @@ -99,7 +96,7 @@ public void testNoKeyId() " }\n" + " ]\n" + "}"); - assertEquals(keys.size(), 0); + assertThat(keys.size()).isEqualTo(0); } @Test @@ -117,7 +114,7 @@ public void testRsaNoModulus() " }\n" + " ]\n" + "}"); - assertEquals(keys.size(), 0); + assertThat(keys.size()).isEqualTo(0); } @Test @@ -135,7 +132,7 @@ public void testRsaNoExponent() " }\n" + " ]\n" + "}"); - assertEquals(keys.size(), 0); + assertThat(keys.size()).isEqualTo(0); } @Test @@ -154,7 +151,7 @@ public void testRsaInvalidModulus() " }\n" + " ]\n" + "}"); - assertEquals(keys.size(), 0); + assertThat(keys.size()).isEqualTo(0); } @Test @@ -173,7 +170,7 @@ public void testRsaInvalidExponent() " }\n" + " ]\n" + "}"); - assertEquals(keys.size(), 0); + assertThat(keys.size()).isEqualTo(0); } @Test @@ -184,11 +181,11 @@ public void testJwtRsa() Map keys = decodeKeys(jwkKeys); RSAPublicKey publicKey = (RSAPublicKey) keys.get("test-rsa"); - assertNotNull(publicKey); + assertThat(publicKey).isNotNull(); RSAPublicKey expectedPublicKey = (RSAPublicKey) PemReader.loadPublicKey(new File(Resources.getResource("jwk/jwk-rsa-public.pem").toURI())); - assertEquals(publicKey.getPublicExponent(), expectedPublicKey.getPublicExponent()); - assertEquals(publicKey.getModulus(), expectedPublicKey.getModulus()); + assertThat(publicKey.getPublicExponent()).isEqualTo(expectedPublicKey.getPublicExponent()); + assertThat(publicKey.getModulus()).isEqualTo(expectedPublicKey.getModulus()); PrivateKey privateKey = PemReader.loadPrivateKey(new File(Resources.getResource("jwk/jwk-rsa-private.pem").toURI()), Optional.empty()); String jwt = newJwtBuilder() @@ -216,14 +213,14 @@ public Key resolveSigningKey(JwsHeader header, byte[] plaintext) private Key getKey(JwsHeader header) { String keyId = header.getKeyId(); - assertEquals(keyId, "test-rsa"); + assertThat(keyId).isEqualTo("test-rsa"); return publicKey; } }) .build() .parseClaimsJws(jwt); - assertEquals(claimsJws.getBody().getSubject(), "test-user"); + assertThat(claimsJws.getBody().getSubject()).isEqualTo("test-user"); } @Test @@ -241,8 +238,8 @@ public void testEcKey() " }\n" + " ]\n" + "}"); - assertEquals(keys.size(), 1); - assertTrue(keys.get("test-ec") instanceof JwkEcPublicKey); + assertThat(keys.size()).isEqualTo(1); + assertThat(keys.get("test-ec") instanceof JwkEcPublicKey).isTrue(); } @Test @@ -260,7 +257,7 @@ public void testEcInvalidCurve() " }\n" + " ]\n" + "}"); - assertEquals(keys.size(), 0); + assertThat(keys.size()).isEqualTo(0); } @Test @@ -278,7 +275,7 @@ public void testEcInvalidX() " }\n" + " ]\n" + "}"); - assertEquals(keys.size(), 0); + assertThat(keys.size()).isEqualTo(0); } @Test @@ -296,7 +293,7 @@ public void testEcInvalidY() " }\n" + " ]\n" + "}"); - assertEquals(keys.size(), 0); + assertThat(keys.size()).isEqualTo(0); } @Test @@ -315,16 +312,16 @@ private static void assertJwtEc(String keyName, ECParameterSpec expectedSpec) Map keys = decodeKeys(jwkKeys); ECPublicKey publicKey = (ECPublicKey) keys.get(keyName); - assertNotNull(publicKey); + assertThat(publicKey).isNotNull(); - assertSame(publicKey.getParams(), expectedSpec); + assertThat(publicKey.getParams()).isSameAs(expectedSpec); ECPublicKey expectedPublicKey = (ECPublicKey) PemReader.loadPublicKey(new File(Resources.getResource("jwk/" + keyName + "-public.pem").toURI())); - assertEquals(publicKey.getW(), expectedPublicKey.getW()); - assertEquals(publicKey.getParams().getCurve(), expectedPublicKey.getParams().getCurve()); - assertEquals(publicKey.getParams().getGenerator(), expectedPublicKey.getParams().getGenerator()); - assertEquals(publicKey.getParams().getOrder(), expectedPublicKey.getParams().getOrder()); - assertEquals(publicKey.getParams().getCofactor(), expectedPublicKey.getParams().getCofactor()); + assertThat(publicKey.getW()).isEqualTo(expectedPublicKey.getW()); + assertThat(publicKey.getParams().getCurve()).isEqualTo(expectedPublicKey.getParams().getCurve()); + assertThat(publicKey.getParams().getGenerator()).isEqualTo(expectedPublicKey.getParams().getGenerator()); + assertThat(publicKey.getParams().getOrder()).isEqualTo(expectedPublicKey.getParams().getOrder()); + assertThat(publicKey.getParams().getCofactor()).isEqualTo(expectedPublicKey.getParams().getCofactor()); PrivateKey privateKey = PemReader.loadPrivateKey(new File(Resources.getResource("jwk/" + keyName + "-private.pem").toURI()), Optional.empty()); String jwt = newJwtBuilder() @@ -352,13 +349,13 @@ public Key resolveSigningKey(JwsHeader header, byte[] plaintext) private Key getKey(JwsHeader header) { String keyId = header.getKeyId(); - assertEquals(keyId, keyName); + assertThat(keyId).isEqualTo(keyName); return publicKey; } }) .build() .parseClaimsJws(jwt); - assertEquals(claimsJws.getBody().getSubject(), "test-user"); + assertThat(claimsJws.getBody().getSubject()).isEqualTo("test-user"); } } diff --git a/core/trino-main/src/test/java/io/trino/server/security/jwt/TestJwkService.java b/core/trino-main/src/test/java/io/trino/server/security/jwt/TestJwkService.java index a6c806c67f2a..d1e405ddf84e 100644 --- a/core/trino-main/src/test/java/io/trino/server/security/jwt/TestJwkService.java +++ b/core/trino-main/src/test/java/io/trino/server/security/jwt/TestJwkService.java @@ -30,9 +30,8 @@ import static io.airlift.http.client.testing.TestingResponse.mockResponse; import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; public class TestJwkService { @@ -191,15 +190,15 @@ public void testBadResponse() private static void assertEmptyKeys(JwkService service) { - assertEquals(service.getKeys().size(), 0); + assertThat(service.getKeys().size()).isEqualTo(0); } private static void assertTestKeys(JwkService service) { Map keys = service.getKeys(); - assertEquals(keys.size(), 3); - assertTrue(keys.containsKey("test-rsa")); - assertTrue(keys.containsKey("test-ec")); - assertTrue(keys.containsKey("test-certificate-chain")); + assertThat(keys.size()).isEqualTo(3); + assertThat(keys.containsKey("test-rsa")).isTrue(); + assertThat(keys.containsKey("test-ec")).isTrue(); + assertThat(keys.containsKey("test-certificate-chain")).isTrue(); } } diff --git a/core/trino-main/src/test/java/io/trino/server/security/oauth2/BaseOAuth2WebUiAuthenticationFilterTest.java b/core/trino-main/src/test/java/io/trino/server/security/oauth2/BaseOAuth2WebUiAuthenticationFilterTest.java index d2f8060c531b..96dba8c260ce 100644 --- a/core/trino-main/src/test/java/io/trino/server/security/oauth2/BaseOAuth2WebUiAuthenticationFilterTest.java +++ b/core/trino-main/src/test/java/io/trino/server/security/oauth2/BaseOAuth2WebUiAuthenticationFilterTest.java @@ -74,7 +74,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -277,8 +276,8 @@ public void testSuccessfulFlow() .get() .build()) .execute()) { - assertEquals(response.code(), SC_OK); - assertEquals(response.request().url().toString(), uiUri.toString()); + assertThat(response.code()).isEqualTo(SC_OK); + assertThat(response.request().url().toString()).isEqualTo(uiUri.toString()); } Optional oauth2Cookie = cookieStore.get(uiUri) .stream() @@ -301,8 +300,8 @@ public void testSuccessfulFlow() .get() .build()) .execute()) { - assertEquals(response.code(), SC_OK); - assertEquals(response.request().url().toString(), uiUri.resolve("logout/logout.html").toString()); + assertThat(response.code()).isEqualTo(SC_OK); + assertThat(response.request().url().toString()).isEqualTo(uiUri.resolve("logout/logout.html").toString()); } assertThat(cookieStore.get(uiUri)).isEmpty(); } diff --git a/core/trino-main/src/test/java/io/trino/server/security/oauth2/TestOAuth2WebUiAuthenticationFilterWithRefreshTokens.java b/core/trino-main/src/test/java/io/trino/server/security/oauth2/TestOAuth2WebUiAuthenticationFilterWithRefreshTokens.java index badcd56931c2..9509a220445a 100644 --- a/core/trino-main/src/test/java/io/trino/server/security/oauth2/TestOAuth2WebUiAuthenticationFilterWithRefreshTokens.java +++ b/core/trino-main/src/test/java/io/trino/server/security/oauth2/TestOAuth2WebUiAuthenticationFilterWithRefreshTokens.java @@ -48,7 +48,6 @@ import static jakarta.servlet.http.HttpServletResponse.SC_OK; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestOAuth2WebUiAuthenticationFilterWithRefreshTokens { @@ -190,8 +189,8 @@ public void testSuccessfulFlowWithRefreshedAccessToken() .get() .build()) .execute()) { - assertEquals(response.code(), SC_OK); - assertEquals(response.request().url().toString(), uiUri.resolve("logout/logout.html").toString()); + assertThat(response.code()).isEqualTo(SC_OK); + assertThat(response.request().url().toString()).isEqualTo(uiUri.resolve("logout/logout.html").toString()); } assertThat(cookieStore.get(uiUri)).isEmpty(); } @@ -217,8 +216,8 @@ private void accessUi(OkHttpClient httpClient) .get() .build()) .execute()) { - assertEquals(response.code(), SC_OK); - assertEquals(response.request().url().toString(), uiUri.toString()); + assertThat(response.code()).isEqualTo(SC_OK); + assertThat(response.request().url().toString()).isEqualTo(uiUri.toString()); } } } diff --git a/core/trino-main/src/test/java/io/trino/server/ui/TestWebUi.java b/core/trino-main/src/test/java/io/trino/server/ui/TestWebUi.java index c2478ca8cc7f..838fd26d7f95 100644 --- a/core/trino-main/src/test/java/io/trino/server/ui/TestWebUi.java +++ b/core/trino-main/src/test/java/io/trino/server/ui/TestWebUi.java @@ -126,9 +126,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -322,10 +319,10 @@ private void testLogIn(URI baseUri, String username, String password, boolean se logIn(baseUri, client, username, password, sendPassword); HttpCookie cookie = getOnlyElement(cookieManager.getCookieStore().getCookies()); - assertEquals(cookie.getPath(), "/ui"); - assertEquals(cookie.getDomain(), baseUri.getHost()); - assertEquals(cookie.getMaxAge(), -1); - assertTrue(cookie.isHttpOnly()); + assertThat(cookie.getPath()).isEqualTo("/ui"); + assertThat(cookie.getDomain()).isEqualTo(baseUri.getHost()); + assertThat(cookie.getMaxAge()).isEqualTo(-1); + assertThat(cookie.isHttpOnly()).isTrue(); assertOk(client, getUiLocation(baseUri)); @@ -373,9 +370,9 @@ private void testFailedLogin(URI httpsUrl, Optional username, Optional 0 && cookie.getMaxAge() < MINUTES.toSeconds(5)); - assertTrue(idTokenCookie.isHttpOnly()); + assertThat(idTokenCookie.getValue()).isEqualTo(idToken.get()); + assertThat(idTokenCookie.getPath()).isEqualTo("/ui/"); + assertThat(idTokenCookie.getDomain()).isEqualTo(baseUri.getHost()); + assertThat(idTokenCookie.getMaxAge() > 0 && cookie.getMaxAge() < MINUTES.toSeconds(5)).isTrue(); + assertThat(idTokenCookie.isHttpOnly()).isTrue(); } else { assertThatThrownBy(() -> getCookie(cookieManager, ID_TOKEN_COOKIE)) @@ -943,10 +940,10 @@ private void assertAuth2Authentication(TestingTrinoServer server, String accessT expectedRedirect = uriBuilder.build().toString(); } - assertEquals(response.code(), SC_SEE_OTHER); + assertThat(response.code()).isEqualTo(SC_SEE_OTHER); String locationHeader = response.header(HttpHeaders.LOCATION); - assertNotNull(locationHeader); - assertEquals(locationHeader, expectedRedirect); + assertThat(locationHeader).isNotNull(); + assertThat(locationHeader).isEqualTo(expectedRedirect); assertThat(cookieManager.getCookieStore().getCookies()).isEmpty(); } assertRedirect(client, getUiLocation(baseUri), "http://example.com/authorize", false); @@ -975,8 +972,8 @@ private static void assertCookieWithRefreshToken(TestingTrinoServer server, Http { TokenPairSerializer tokenPairSerializer = server.getInstance(Key.get(TokenPairSerializer.class)); TokenPair deserialize = tokenPairSerializer.deserialize(authCookie.getValue()); - assertEquals(deserialize.accessToken(), accessToken); - assertEquals(deserialize.refreshToken(), Optional.of(REFRESH_TOKEN)); + assertThat(deserialize.accessToken()).isEqualTo(accessToken); + assertThat(deserialize.refreshToken()).isEqualTo(Optional.of(REFRESH_TOKEN)); assertThat(authCookie.getMaxAge()).isGreaterThan(0).isLessThan(REFRESH_TOKEN_TIMEOUT.getSeconds()); } @@ -1040,8 +1037,8 @@ private static void assertRedirect(OkHttpClient client, String url, String redir request = request.newBuilder().post(formBody).build(); } try (Response response = client.newCall(request).execute()) { - assertEquals(response.code(), SC_SEE_OTHER); - assertEquals(response.header(LOCATION), redirectLocation); + assertThat(response.code()).isEqualTo(SC_SEE_OTHER); + assertThat(response.header(LOCATION)).isEqualTo(redirectLocation); } if (testProxy) { @@ -1052,14 +1049,12 @@ private static void assertRedirect(OkHttpClient client, String url, String redir .header(X_FORWARDED_PORT, "123") .build(); try (Response response = client.newCall(request).execute()) { - assertEquals(response.code(), SC_SEE_OTHER); - assertEquals( - response.header(LOCATION), - uriBuilderFrom(URI.create(redirectLocation)) - .scheme("test") - .host("my-load-balancer.local") - .port(123) - .toString()); + assertThat(response.code()).isEqualTo(SC_SEE_OTHER); + assertThat(response.header(LOCATION)).isEqualTo(uriBuilderFrom(URI.create(redirectLocation)) + .scheme("test") + .host("my-load-balancer.local") + .port(123) + .toString()); } request = new Request.Builder() @@ -1068,14 +1063,12 @@ private static void assertRedirect(OkHttpClient client, String url, String redir .header(X_FORWARDED_HOST, "my-load-balancer.local:123") .build(); try (Response response = client.newCall(request).execute()) { - assertEquals(response.code(), SC_SEE_OTHER); - assertEquals( - response.header(LOCATION), - uriBuilderFrom(URI.create(redirectLocation)) - .scheme("test") - .host("my-load-balancer.local") - .port(123) - .toString()); + assertThat(response.code()).isEqualTo(SC_SEE_OTHER); + assertThat(response.header(LOCATION)).isEqualTo(uriBuilderFrom(URI.create(redirectLocation)) + .scheme("test") + .host("my-load-balancer.local") + .port(123) + .toString()); } request = new Request.Builder() @@ -1084,13 +1077,11 @@ private static void assertRedirect(OkHttpClient client, String url, String redir .header(X_FORWARDED_PORT, "123") .build(); try (Response response = client.newCall(request).execute()) { - assertEquals(response.code(), SC_SEE_OTHER); - assertEquals( - response.header(LOCATION), - uriBuilderFrom(URI.create(redirectLocation)) - .scheme("test") - .port(123) - .toString()); + assertThat(response.code()).isEqualTo(SC_SEE_OTHER); + assertThat(response.header(LOCATION)).isEqualTo(uriBuilderFrom(URI.create(redirectLocation)) + .scheme("test") + .port(123) + .toString()); } request = new Request.Builder() @@ -1098,12 +1089,10 @@ private static void assertRedirect(OkHttpClient client, String url, String redir .header(X_FORWARDED_PROTO, "test") .build(); try (Response response = client.newCall(request).execute()) { - assertEquals(response.code(), SC_SEE_OTHER); - assertEquals( - response.header(LOCATION), - uriBuilderFrom(URI.create(redirectLocation)) - .scheme("test") - .toString()); + assertThat(response.code()).isEqualTo(SC_SEE_OTHER); + assertThat(response.header(LOCATION)).isEqualTo(uriBuilderFrom(URI.create(redirectLocation)) + .scheme("test") + .toString()); } request = new Request.Builder() .url(url) @@ -1111,13 +1100,11 @@ private static void assertRedirect(OkHttpClient client, String url, String redir .header(X_FORWARDED_PORT, "123") .build(); try (Response response = client.newCall(request).execute()) { - assertEquals(response.code(), SC_SEE_OTHER); - assertEquals( - response.header(LOCATION), - uriBuilderFrom(URI.create(redirectLocation)) - .host("my-load-balancer.local") - .port(123) - .toString()); + assertThat(response.code()).isEqualTo(SC_SEE_OTHER); + assertThat(response.header(LOCATION)).isEqualTo(uriBuilderFrom(URI.create(redirectLocation)) + .host("my-load-balancer.local") + .port(123) + .toString()); } request = new Request.Builder() @@ -1125,13 +1112,11 @@ private static void assertRedirect(OkHttpClient client, String url, String redir .header(X_FORWARDED_HOST, "my-load-balancer.local:123") .build(); try (Response response = client.newCall(request).execute()) { - assertEquals(response.code(), SC_SEE_OTHER); - assertEquals( - response.header(LOCATION), - uriBuilderFrom(URI.create(redirectLocation)) - .host("my-load-balancer.local") - .port(123) - .toString()); + assertThat(response.code()).isEqualTo(SC_SEE_OTHER); + assertThat(response.header(LOCATION)).isEqualTo(uriBuilderFrom(URI.create(redirectLocation)) + .host("my-load-balancer.local") + .port(123) + .toString()); } request = new Request.Builder() @@ -1139,13 +1124,11 @@ private static void assertRedirect(OkHttpClient client, String url, String redir .header(X_FORWARDED_HOST, "my-load-balancer.local") .build(); try (Response response = client.newCall(request).execute()) { - assertEquals(response.code(), SC_SEE_OTHER); - assertEquals( - response.header(LOCATION), - uriBuilderFrom(URI.create(redirectLocation)) - .host("my-load-balancer.local") - .defaultPort() - .toString()); + assertThat(response.code()).isEqualTo(SC_SEE_OTHER); + assertThat(response.header(LOCATION)).isEqualTo(uriBuilderFrom(URI.create(redirectLocation)) + .host("my-load-balancer.local") + .defaultPort() + .toString()); } } } @@ -1173,7 +1156,9 @@ private static Optional assertResponseCode(OkHttpClient client, request = request.newBuilder().post(formBody).build(); } try (Response response = client.newCall(request).execute()) { - assertEquals(response.code(), expectedCode, url); + assertThat(response.code()) + .describedAs(url) + .isEqualTo(expectedCode); return Optional.ofNullable(response.body()) .map(responseBody -> { try { diff --git a/core/trino-main/src/test/java/io/trino/spiller/TestBinaryFileSpiller.java b/core/trino-main/src/test/java/io/trino/spiller/TestBinaryFileSpiller.java index c130b4d324f8..bab545a51306 100644 --- a/core/trino-main/src/test/java/io/trino/spiller/TestBinaryFileSpiller.java +++ b/core/trino-main/src/test/java/io/trino/spiller/TestBinaryFileSpiller.java @@ -46,7 +46,7 @@ import static io.trino.spi.type.DoubleType.DOUBLE; import static io.trino.spi.type.VarbinaryType.VARBINARY; import static io.trino.spi.type.VarcharType.VARCHAR; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; @Test(singleThreaded = true) public class TestBinaryFileSpiller @@ -146,31 +146,31 @@ private void testSpiller(List types, Spiller spiller, List... spills long spilledBytesBefore = spillerStats.getTotalSpilledBytes(); long spilledBytes = 0; - assertEquals(memoryContext.getBytes(), 0); + assertThat(memoryContext.getBytes()).isEqualTo(0); for (List spill : spills) { spilledBytes += spill.stream() .mapToLong(page -> serializer.serialize(page).length()) .sum(); spiller.spill(spill.iterator()).get(); } - assertEquals(spillerStats.getTotalSpilledBytes() - spilledBytesBefore, spilledBytes); + assertThat(spillerStats.getTotalSpilledBytes() - spilledBytesBefore).isEqualTo(spilledBytes); // At this point, the buffers should still be accounted for in the memory context, because // the spiller (FileSingleStreamSpiller) doesn't release its memory reservation until it's closed. - assertEquals(memoryContext.getBytes(), (long) spills.length * FileSingleStreamSpiller.BUFFER_SIZE); + assertThat(memoryContext.getBytes()).isEqualTo((long) spills.length * FileSingleStreamSpiller.BUFFER_SIZE); List> actualSpills = spiller.getSpills(); - assertEquals(actualSpills.size(), spills.length); + assertThat(actualSpills.size()).isEqualTo(spills.length); for (int i = 0; i < actualSpills.size(); i++) { List actualSpill = ImmutableList.copyOf(actualSpills.get(i)); List expectedSpill = spills[i]; - assertEquals(actualSpill.size(), expectedSpill.size()); + assertThat(actualSpill.size()).isEqualTo(expectedSpill.size()); for (int j = 0; j < actualSpill.size(); j++) { assertPageEquals(types, actualSpill.get(j), expectedSpill.get(j)); } } spiller.close(); - assertEquals(memoryContext.getBytes(), 0); + assertThat(memoryContext.getBytes()).isEqualTo(0); } } diff --git a/core/trino-main/src/test/java/io/trino/spiller/TestFileSingleStreamSpiller.java b/core/trino-main/src/test/java/io/trino/spiller/TestFileSingleStreamSpiller.java index a66e66e5a95b..97379246db10 100644 --- a/core/trino-main/src/test/java/io/trino/spiller/TestFileSingleStreamSpiller.java +++ b/core/trino-main/src/test/java/io/trino/spiller/TestFileSingleStreamSpiller.java @@ -50,11 +50,10 @@ import static io.trino.spi.type.VarbinaryType.VARBINARY; import static java.nio.file.Files.newInputStream; import static java.util.concurrent.Executors.newCachedThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -121,24 +120,26 @@ private void assertSpill(boolean compression, boolean encryption) encryption); LocalMemoryContext memoryContext = newSimpleAggregatedMemoryContext().newLocalMemoryContext("test"); SingleStreamSpiller singleStreamSpiller = spillerFactory.create(TYPES, bytes -> {}, memoryContext); - assertTrue(singleStreamSpiller instanceof FileSingleStreamSpiller); + assertThat(singleStreamSpiller instanceof FileSingleStreamSpiller).isTrue(); FileSingleStreamSpiller spiller = (FileSingleStreamSpiller) singleStreamSpiller; Page page = buildPage(); // The spillers will reserve memory in their constructors - assertEquals(memoryContext.getBytes(), 4096); + assertThat(memoryContext.getBytes()).isEqualTo(4096); spiller.spill(page).get(); spiller.spill(Iterators.forArray(page, page, page)).get(); - assertEquals(listFiles(spillPath.toPath()).size(), 1); + assertThat(listFiles(spillPath.toPath()).size()).isEqualTo(1); // Assert the spill codec flags match the expected configuration try (InputStream is = newInputStream(listFiles(spillPath.toPath()).get(0))) { Iterator serializedPages = PagesSerdeUtil.readSerializedPages(is); - assertTrue(serializedPages.hasNext(), "at least one page should be successfully read back"); + assertThat(serializedPages.hasNext()) + .describedAs("at least one page should be successfully read back") + .isTrue(); Slice serializedPage = serializedPages.next(); - assertEquals(isSerializedPageCompressed(serializedPage), compression); - assertEquals(isSerializedPageEncrypted(serializedPage), encryption); + assertThat(isSerializedPageCompressed(serializedPage)).isEqualTo(compression); + assertThat(isSerializedPageEncrypted(serializedPage)).isEqualTo(encryption); } // The spillers release their memory reservations when they are closed, therefore at this point @@ -146,13 +147,13 @@ private void assertSpill(boolean compression, boolean encryption) // assertEquals(memoryContext.getBytes(), 0); Iterator spilledPagesIterator = spiller.getSpilledPages(); - assertEquals(memoryContext.getBytes(), FileSingleStreamSpiller.BUFFER_SIZE); + assertThat(memoryContext.getBytes()).isEqualTo(FileSingleStreamSpiller.BUFFER_SIZE); ImmutableList spilledPages = ImmutableList.copyOf(spilledPagesIterator); // The spillers release their memory reservations when they are closed, therefore at this point // they will have non-zero memory reservation. // assertEquals(memoryContext.getBytes(), 0); - assertEquals(4, spilledPages.size()); + assertThat(4).isEqualTo(spilledPages.size()); for (int i = 0; i < 4; ++i) { PageAssertions.assertPageEquals(TYPES, page, spilledPages.get(i)); } @@ -163,8 +164,8 @@ private void assertSpill(boolean compression, boolean encryption) .hasMessage("Repeated reads are disallowed to prevent potential resource leaks"); spiller.close(); - assertEquals(listFiles(spillPath.toPath()).size(), 0); - assertEquals(memoryContext.getBytes(), 0); + assertThat(listFiles(spillPath.toPath()).size()).isEqualTo(0); + assertThat(memoryContext.getBytes()).isEqualTo(0); } private Page buildPage() diff --git a/core/trino-main/src/test/java/io/trino/spiller/TestFileSingleStreamSpillerFactory.java b/core/trino-main/src/test/java/io/trino/spiller/TestFileSingleStreamSpillerFactory.java index f1e603ec1421..c53d8bfcf75d 100644 --- a/core/trino-main/src/test/java/io/trino/spiller/TestFileSingleStreamSpillerFactory.java +++ b/core/trino-main/src/test/java/io/trino/spiller/TestFileSingleStreamSpillerFactory.java @@ -47,9 +47,9 @@ import static io.trino.spiller.FileSingleStreamSpillerFactory.SPILL_FILE_SUFFIX; import static java.nio.file.Files.setPosixFilePermissions; import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; -import static org.testng.Assert.assertEquals; @TestInstance(PER_METHOD) public class TestFileSingleStreamSpillerFactory @@ -89,8 +89,8 @@ public void testDistributesSpillOverPaths() List spillPaths = ImmutableList.of(spillPath1.toPath(), spillPath2.toPath()); FileSingleStreamSpillerFactory spillerFactory = spillerFactoryFactory(spillPaths); - assertEquals(listFiles(spillPath1.toPath()).size(), 0); - assertEquals(listFiles(spillPath2.toPath()).size(), 0); + assertThat(listFiles(spillPath1.toPath()).size()).isEqualTo(0); + assertThat(listFiles(spillPath2.toPath()).size()).isEqualTo(0); Page page = buildPage(); List spillers = new ArrayList<>(); @@ -99,12 +99,12 @@ public void testDistributesSpillOverPaths() getUnchecked(singleStreamSpiller.spill(page)); spillers.add(singleStreamSpiller); } - assertEquals(listFiles(spillPath1.toPath()).size(), 5); - assertEquals(listFiles(spillPath2.toPath()).size(), 5); + assertThat(listFiles(spillPath1.toPath()).size()).isEqualTo(5); + assertThat(listFiles(spillPath2.toPath()).size()).isEqualTo(5); spillers.forEach(SingleStreamSpiller::close); - assertEquals(listFiles(spillPath1.toPath()).size(), 0); - assertEquals(listFiles(spillPath2.toPath()).size(), 0); + assertThat(listFiles(spillPath1.toPath()).size()).isEqualTo(0); + assertThat(listFiles(spillPath2.toPath()).size()).isEqualTo(0); } @Test @@ -115,8 +115,8 @@ public void testDistributesSpillOverPathsBadDisk() List spillPaths = ImmutableList.of(spillPath1.toPath(), spillPath2.toPath()); FileSingleStreamSpillerFactory spillerFactory = spillerFactoryFactory(spillPaths); - assertEquals(listFiles(spillPath1.toPath()).size(), 0); - assertEquals(listFiles(spillPath2.toPath()).size(), 0); + assertThat(listFiles(spillPath1.toPath()).size()).isEqualTo(0); + assertThat(listFiles(spillPath2.toPath()).size()).isEqualTo(0); // Set first spiller path to read-only after initialization to emulate a disk failing during runtime setPosixFilePermissions(spillPath1.toPath(), ImmutableSet.of(PosixFilePermission.OWNER_READ)); @@ -131,12 +131,12 @@ public void testDistributesSpillOverPathsBadDisk() } // bad disk should receive no spills, with the good disk taking the remainder - assertEquals(listFiles(spillPath1.toPath()).size(), 0); - assertEquals(listFiles(spillPath2.toPath()).size(), numberOfSpills); + assertThat(listFiles(spillPath1.toPath()).size()).isEqualTo(0); + assertThat(listFiles(spillPath2.toPath()).size()).isEqualTo(numberOfSpills); spillers.forEach(SingleStreamSpiller::close); - assertEquals(listFiles(spillPath1.toPath()).size(), 0); - assertEquals(listFiles(spillPath2.toPath()).size(), 0); + assertThat(listFiles(spillPath1.toPath()).size()).isEqualTo(0); + assertThat(listFiles(spillPath2.toPath()).size()).isEqualTo(0); } private Page buildPage() @@ -185,14 +185,14 @@ public void testCleanupOldSpillFiles() java.nio.file.Files.createTempFile(spillPath2.toPath(), "blah", SPILL_FILE_SUFFIX); java.nio.file.Files.createTempFile(spillPath2.toPath(), "blah", "blah"); - assertEquals(listFiles(spillPath1.toPath()).size(), 3); - assertEquals(listFiles(spillPath2.toPath()).size(), 3); + assertThat(listFiles(spillPath1.toPath()).size()).isEqualTo(3); + assertThat(listFiles(spillPath2.toPath()).size()).isEqualTo(3); FileSingleStreamSpillerFactory spillerFactory = spillerFactoryFactory(spillPaths); spillerFactory.cleanupOldSpillFiles(); - assertEquals(listFiles(spillPath1.toPath()).size(), 1); - assertEquals(listFiles(spillPath2.toPath()).size(), 2); + assertThat(listFiles(spillPath1.toPath()).size()).isEqualTo(1); + assertThat(listFiles(spillPath2.toPath()).size()).isEqualTo(2); } @Test @@ -204,8 +204,8 @@ public void testCacheInvalidatedOnBadDisk() FileSingleStreamSpillerFactory spillerFactory = spillerFactoryFactory(spillPaths); - assertEquals(listFiles(spillPath1.toPath()).size(), 0); - assertEquals(listFiles(spillPath2.toPath()).size(), 0); + assertThat(listFiles(spillPath1.toPath()).size()).isEqualTo(0); + assertThat(listFiles(spillPath2.toPath()).size()).isEqualTo(0); Page page = buildPage(); List spillers = new ArrayList<>(); @@ -223,13 +223,15 @@ public void testCacheInvalidatedOnBadDisk() .hasMessageContaining("Failed to spill pages"); spillers.add(singleStreamSpiller2); - assertEquals(spillerFactory.getSpillPathCacheSize(), 0, "cache still contains entries"); + assertThat(spillerFactory.getSpillPathCacheSize()) + .describedAs("cache still contains entries") + .isEqualTo(0); // restore permissions to allow cleanup setPosixFilePermissions(spillPath2.toPath(), ImmutableSet.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE)); spillers.forEach(SingleStreamSpiller::close); - assertEquals(listFiles(spillPath1.toPath()).size(), 0); - assertEquals(listFiles(spillPath2.toPath()).size(), 0); + assertThat(listFiles(spillPath1.toPath()).size()).isEqualTo(0); + assertThat(listFiles(spillPath2.toPath()).size()).isEqualTo(0); } @Test @@ -241,8 +243,8 @@ public void testCacheFull() FileSingleStreamSpillerFactory spillerFactory = spillerFactoryFactory(spillPaths); - assertEquals(listFiles(spillPath1.toPath()).size(), 0); - assertEquals(listFiles(spillPath2.toPath()).size(), 0); + assertThat(listFiles(spillPath1.toPath()).size()).isEqualTo(0); + assertThat(listFiles(spillPath2.toPath()).size()).isEqualTo(0); Page page = buildPage(); List spillers = new ArrayList<>(); @@ -255,11 +257,13 @@ public void testCacheFull() getUnchecked(singleStreamSpiller2.spill(page)); spillers.add(singleStreamSpiller2); - assertEquals(spillerFactory.getSpillPathCacheSize(), 2, "cache contains no entries"); + assertThat(spillerFactory.getSpillPathCacheSize()) + .describedAs("cache contains no entries") + .isEqualTo(2); spillers.forEach(SingleStreamSpiller::close); - assertEquals(listFiles(spillPath1.toPath()).size(), 0); - assertEquals(listFiles(spillPath2.toPath()).size(), 0); + assertThat(listFiles(spillPath1.toPath()).size()).isEqualTo(0); + assertThat(listFiles(spillPath2.toPath()).size()).isEqualTo(0); } private FileSingleStreamSpillerFactory spillerFactoryFactory(List paths) diff --git a/core/trino-main/src/test/java/io/trino/spiller/TestGenericPartitioningSpiller.java b/core/trino-main/src/test/java/io/trino/spiller/TestGenericPartitioningSpiller.java index 2392cb133e8c..7abb2089a6cf 100644 --- a/core/trino-main/src/test/java/io/trino/spiller/TestGenericPartitioningSpiller.java +++ b/core/trino-main/src/test/java/io/trino/spiller/TestGenericPartitioningSpiller.java @@ -52,10 +52,10 @@ import static java.lang.Math.toIntExact; import static java.nio.file.Files.createTempDirectory; import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -124,19 +124,19 @@ public void testFileSpiller() IntPredicate spillPartitionMask = ImmutableSet.of(1, 2)::contains; PartitioningSpillResult result = spiller.partitionAndSpill(firstSpill.get(0), spillPartitionMask); result.getSpillingFuture().get(); - assertEquals(result.getRetained().getPositionCount(), 0); + assertThat(result.getRetained().getPositionCount()).isEqualTo(0); result = spiller.partitionAndSpill(firstSpill.get(1), spillPartitionMask); result.getSpillingFuture().get(); - assertEquals(result.getRetained().getPositionCount(), 10); + assertThat(result.getRetained().getPositionCount()).isEqualTo(10); result = spiller.partitionAndSpill(secondSpill.get(0), spillPartitionMask); result.getSpillingFuture().get(); - assertEquals(result.getRetained().getPositionCount(), 0); + assertThat(result.getRetained().getPositionCount()).isEqualTo(0); result = spiller.partitionAndSpill(secondSpill.get(1), spillPartitionMask); result.getSpillingFuture().get(); - assertEquals(result.getRetained().getPositionCount(), 10); + assertThat(result.getRetained().getPositionCount()).isEqualTo(10); builder = RowPagesBuilder.rowPagesBuilder(TYPES); builder.addSequencePage(10, SECOND_PARTITION_START, 5, 10, 15); @@ -167,7 +167,7 @@ public void testCloseDuringReading() mockMemoryContext(scheduledExecutor))) { Page page = SequencePageBuilder.createSequencePage(TYPES, 10, FIRST_PARTITION_START, 5, 10, 15); PartitioningSpillResult spillResult = spiller.partitionAndSpill(page, partition -> true); - assertEquals(spillResult.getRetained().getPositionCount(), 0); + assertThat(spillResult.getRetained().getPositionCount()).isEqualTo(0); getFutureValue(spillResult.getSpillingFuture()); // We get the iterator but we do not exhaust it, so that close happens during reading @@ -195,12 +195,14 @@ public void testWriteManyPartitions() for (int i = 0; i < 50_000; i++) { Page page = SequencePageBuilder.createSequencePage(types, partitionCount, 0); PartitioningSpillResult spillResult = spiller.partitionAndSpill(page, partition -> true); - assertEquals(spillResult.getRetained().getPositionCount(), 0); + assertThat(spillResult.getRetained().getPositionCount()).isEqualTo(0); getFutureValue(spillResult.getSpillingFuture()); getFutureValue(spiller.flush()); } } - assertEquals(memoryContext.getBytes(), 0, "Reserved bytes should be zeroed after spiller is closed"); + assertThat(memoryContext.getBytes()) + .describedAs("Reserved bytes should be zeroed after spiller is closed") + .isEqualTo(0); } private void assertSpilledPages( @@ -212,7 +214,7 @@ private void assertSpilledPages( List actualSpill = ImmutableList.copyOf(spiller.getSpilledPages(partition)); List expectedSpill = expectedPartitions.get(partition); - assertEquals(actualSpill.size(), expectedSpill.size()); + assertThat(actualSpill.size()).isEqualTo(expectedSpill.size()); for (int j = 0; j < actualSpill.size(); j++) { assertPageEquals(types, actualSpill.get(j), expectedSpill.get(j)); } diff --git a/core/trino-main/src/test/java/io/trino/spiller/TestSpillSpaceTracker.java b/core/trino-main/src/test/java/io/trino/spiller/TestSpillSpaceTracker.java index 145a4413ccab..fe92bbdd81eb 100644 --- a/core/trino-main/src/test/java/io/trino/spiller/TestSpillSpaceTracker.java +++ b/core/trino-main/src/test/java/io/trino/spiller/TestSpillSpaceTracker.java @@ -18,8 +18,8 @@ import org.junit.jupiter.api.Test; import static io.airlift.units.DataSize.Unit.MEGABYTE; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestSpillSpaceTracker { @@ -30,25 +30,25 @@ public void testSpillSpaceTracker() { SpillSpaceTracker spillSpaceTracker = new SpillSpaceTracker(MAX_DATA_SIZE); - assertEquals(spillSpaceTracker.getCurrentBytes(), 0); - assertEquals(spillSpaceTracker.getMaxBytes(), MAX_DATA_SIZE.toBytes()); + assertThat(spillSpaceTracker.getCurrentBytes()).isEqualTo(0); + assertThat(spillSpaceTracker.getMaxBytes()).isEqualTo(MAX_DATA_SIZE.toBytes()); long reservedBytes = DataSize.of(5, MEGABYTE).toBytes(); spillSpaceTracker.reserve(reservedBytes); - assertEquals(spillSpaceTracker.getCurrentBytes(), reservedBytes); + assertThat(spillSpaceTracker.getCurrentBytes()).isEqualTo(reservedBytes); long otherReservedBytes = DataSize.of(2, MEGABYTE).toBytes(); spillSpaceTracker.reserve(otherReservedBytes); - assertEquals(spillSpaceTracker.getCurrentBytes(), (reservedBytes + otherReservedBytes)); + assertThat(spillSpaceTracker.getCurrentBytes()).isEqualTo((reservedBytes + otherReservedBytes)); spillSpaceTracker.reserve(otherReservedBytes); - assertEquals(spillSpaceTracker.getCurrentBytes(), (reservedBytes + 2 * otherReservedBytes)); + assertThat(spillSpaceTracker.getCurrentBytes()).isEqualTo((reservedBytes + 2 * otherReservedBytes)); spillSpaceTracker.free(otherReservedBytes); spillSpaceTracker.free(otherReservedBytes); - assertEquals(spillSpaceTracker.getCurrentBytes(), reservedBytes); + assertThat(spillSpaceTracker.getCurrentBytes()).isEqualTo(reservedBytes); spillSpaceTracker.free(reservedBytes); - assertEquals(spillSpaceTracker.getCurrentBytes(), 0); + assertThat(spillSpaceTracker.getCurrentBytes()).isEqualTo(0); } @Test @@ -56,7 +56,7 @@ public void testSpillOutOfSpace() { SpillSpaceTracker spillSpaceTracker = new SpillSpaceTracker(MAX_DATA_SIZE); - assertEquals(spillSpaceTracker.getCurrentBytes(), 0); + assertThat(spillSpaceTracker.getCurrentBytes()).isEqualTo(0); assertThatThrownBy(() -> spillSpaceTracker.reserve(MAX_DATA_SIZE.toBytes() + 1)) .isInstanceOf(ExceededSpillLimitException.class) .hasMessageMatching("Query exceeded local spill limit of.*"); @@ -67,7 +67,7 @@ public void testFreeToMuch() { SpillSpaceTracker spillSpaceTracker = new SpillSpaceTracker(MAX_DATA_SIZE); - assertEquals(spillSpaceTracker.getCurrentBytes(), 0); + assertThat(spillSpaceTracker.getCurrentBytes()).isEqualTo(0); spillSpaceTracker.reserve(1000); assertThatThrownBy(() -> spillSpaceTracker.free(1001)) .isInstanceOf(IllegalArgumentException.class) diff --git a/core/trino-main/src/test/java/io/trino/split/TestPageSinkId.java b/core/trino-main/src/test/java/io/trino/split/TestPageSinkId.java index 736113e05cb7..81cd9369304a 100644 --- a/core/trino-main/src/test/java/io/trino/split/TestPageSinkId.java +++ b/core/trino-main/src/test/java/io/trino/split/TestPageSinkId.java @@ -18,8 +18,8 @@ import io.trino.spi.QueryId; import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestPageSinkId { @@ -33,7 +33,7 @@ public void testFromTaskId() { PageSinkId pageSinkId = fromTaskId(1, 2, 3); long expected = (1L << 32) + (2L << 8) + 3L; - assertEquals(pageSinkId.getId(), expected); + assertThat(pageSinkId.getId()).isEqualTo(expected); } @Test diff --git a/core/trino-main/src/test/java/io/trino/sql/BenchmarkExpressionInterpreter.java b/core/trino-main/src/test/java/io/trino/sql/BenchmarkExpressionInterpreter.java index 9bb0b72f7572..3ec49a7e824d 100644 --- a/core/trino-main/src/test/java/io/trino/sql/BenchmarkExpressionInterpreter.java +++ b/core/trino-main/src/test/java/io/trino/sql/BenchmarkExpressionInterpreter.java @@ -37,7 +37,7 @@ import static io.trino.jmh.Benchmarks.benchmark; import static io.trino.sql.TestExpressionInterpreter.planExpression; import static java.util.stream.Collectors.joining; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; @SuppressWarnings("MethodMayBeStatic") @State(Scope.Benchmark) @@ -91,7 +91,7 @@ public void verify() BenchmarkData data = new BenchmarkData(); data.setup(); BenchmarkExpressionInterpreter benchmark = new BenchmarkExpressionInterpreter(); - assertEquals(benchmark.optimize(data).size(), data.expressions.size()); + assertThat(benchmark.optimize(data).size()).isEqualTo(data.expressions.size()); } public static void main(String[] args) diff --git a/core/trino-main/src/test/java/io/trino/sql/TestExpressionInterpreter.java b/core/trino-main/src/test/java/io/trino/sql/TestExpressionInterpreter.java index ef02a2b5fb03..70fa383e6af3 100644 --- a/core/trino-main/src/test/java/io/trino/sql/TestExpressionInterpreter.java +++ b/core/trino-main/src/test/java/io/trino/sql/TestExpressionInterpreter.java @@ -78,9 +78,8 @@ import static java.lang.String.format; import static java.util.Locale.ENGLISH; import static java.util.function.Function.identity; +import static org.assertj.core.api.Assertions.assertThat; import static org.joda.time.DateTimeZone.UTC; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; public class TestExpressionInterpreter { @@ -405,9 +404,9 @@ public void testNonDeterministicFunctionCall() // evaluate should execute Object value = evaluate("random()"); - assertTrue(value instanceof Double); + assertThat(value instanceof Double).isTrue(); double randomValue = (double) value; - assertTrue(0 <= randomValue && randomValue < 1); + assertThat(0 <= randomValue && randomValue < 1).isTrue(); } @Test @@ -1896,7 +1895,7 @@ public void testLiterals() optimize("INTERVAL '3' DAY * unbound_long"); optimize("INTERVAL '3' YEAR * unbound_long"); - assertEquals(optimize("X'1234'"), Slices.wrappedBuffer((byte) 0x12, (byte) 0x34)); + assertThat(optimize("X'1234'")).isEqualTo(Slices.wrappedBuffer((byte) 0x12, (byte) 0x34)); } private static void assertLike(byte[] value, String pattern, boolean expected) @@ -1905,7 +1904,7 @@ private static void assertLike(byte[] value, String pattern, boolean expected) rawStringLiteral(Slices.wrappedBuffer(value)), new StringLiteral(pattern), Optional.empty()); - assertEquals(evaluate(predicate), expected); + assertThat(evaluate(predicate)).isEqualTo(expected); } private static StringLiteral rawStringLiteral(Slice slice) @@ -1915,7 +1914,7 @@ private static StringLiteral rawStringLiteral(Slice slice) private static void assertOptimizedEquals(@Language("SQL") String actual, @Language("SQL") String expected) { - assertEquals(optimize(actual), optimize(expected)); + assertThat(optimize(actual)).isEqualTo(optimize(expected)); } private static void assertOptimizedMatches(@Language("SQL") String actual, @Language("SQL") String expected) @@ -1966,7 +1965,7 @@ static Expression planExpression(@Language("SQL") String expression) private static void assertEvaluatedEquals(@Language("SQL") String actual, @Language("SQL") String expected) { - assertEquals(evaluate(actual), evaluate(expected)); + assertThat(evaluate(actual)).isEqualTo(evaluate(expected)); } private static Object evaluate(String expression) @@ -1982,7 +1981,7 @@ private static void assertRoundTrip(String expression) { Expression parsed = SQL_PARSER.createExpression(expression); String formatted = formatExpression(parsed); - assertEquals(parsed, SQL_PARSER.createExpression(formatted)); + assertThat(parsed).isEqualTo(SQL_PARSER.createExpression(formatted)); } private static Object evaluate(Expression expression) diff --git a/core/trino-main/src/test/java/io/trino/sql/TestExpressionOptimizer.java b/core/trino-main/src/test/java/io/trino/sql/TestExpressionOptimizer.java index f44bc8dda428..badb321aae9c 100644 --- a/core/trino-main/src/test/java/io/trino/sql/TestExpressionOptimizer.java +++ b/core/trino-main/src/test/java/io/trino/sql/TestExpressionOptimizer.java @@ -49,7 +49,7 @@ import static io.trino.sql.relational.SpecialForm.Form.IF; import static io.trino.type.JsonType.JSON; import static io.trino.util.StructuralTestUtil.mapType; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestExpressionOptimizer { @@ -75,14 +75,14 @@ public void testPossibleExponentialOptimizationTime() @Test public void testIfConstantOptimization() { - assertEquals(optimizer.optimize(ifExpression(constant(true, BOOLEAN), 1L, 2L)), constant(1L, BIGINT)); - assertEquals(optimizer.optimize(ifExpression(constant(false, BOOLEAN), 1L, 2L)), constant(2L, BIGINT)); - assertEquals(optimizer.optimize(ifExpression(constant(null, BOOLEAN), 1L, 2L)), constant(2L, BIGINT)); + assertThat(optimizer.optimize(ifExpression(constant(true, BOOLEAN), 1L, 2L))).isEqualTo(constant(1L, BIGINT)); + assertThat(optimizer.optimize(ifExpression(constant(false, BOOLEAN), 1L, 2L))).isEqualTo(constant(2L, BIGINT)); + assertThat(optimizer.optimize(ifExpression(constant(null, BOOLEAN), 1L, 2L))).isEqualTo(constant(2L, BIGINT)); RowExpression condition = new CallExpression( functionResolution.resolveOperator(EQUAL, ImmutableList.of(BIGINT, BIGINT)), ImmutableList.of(constant(3L, BIGINT), constant(3L, BIGINT))); - assertEquals(optimizer.optimize(ifExpression(condition, 1L, 2L)), constant(1L, BIGINT)); + assertThat(optimizer.optimize(ifExpression(condition, 1L, 2L))).isEqualTo(constant(1L, BIGINT)); } @Test @@ -97,7 +97,7 @@ public void testCastWithJsonParseOptimization() assertInstanceOf(resultExpression, ConstantExpression.class); Object resultValue = ((ConstantExpression) resultExpression).getValue(); assertInstanceOf(resultValue, IntArrayBlock.class); - assertEquals(toValues(INTEGER, (IntArrayBlock) resultValue), ImmutableList.of(1, 2)); + assertThat(toValues(INTEGER, (IntArrayBlock) resultValue)).isEqualTo(ImmutableList.of(1, 2)); // varchar to array testCastWithJsonParseOptimization(jsonParseFunction, new ArrayType(VARCHAR), JSON_STRING_TO_ARRAY_NAME); @@ -114,11 +114,9 @@ private void testCastWithJsonParseOptimization(ResolvedFunction jsonParseFunctio ResolvedFunction jsonCastFunction = functionResolution.getCoercion(JSON, targetType); RowExpression jsonCastExpression = new CallExpression(jsonCastFunction, ImmutableList.of(call(jsonParseFunction, field(1, VARCHAR)))); RowExpression resultExpression = optimizer.optimize(jsonCastExpression); - assertEquals( - resultExpression, - call( - functionResolution.getCoercion(builtinFunctionName(jsonStringToRowName), VARCHAR, targetType), - field(1, VARCHAR))); + assertThat(resultExpression).isEqualTo(call( + functionResolution.getCoercion(builtinFunctionName(jsonStringToRowName), VARCHAR, targetType), + field(1, VARCHAR))); } private static RowExpression ifExpression(RowExpression condition, long trueValue, long falseValue) diff --git a/core/trino-main/src/test/java/io/trino/sql/TestExpressionUtils.java b/core/trino-main/src/test/java/io/trino/sql/TestExpressionUtils.java index 9d16d3cb342d..566196d9fbc5 100644 --- a/core/trino-main/src/test/java/io/trino/sql/TestExpressionUtils.java +++ b/core/trino-main/src/test/java/io/trino/sql/TestExpressionUtils.java @@ -22,7 +22,7 @@ import static io.trino.metadata.MetadataManager.createTestMetadataManager; import static io.trino.sql.tree.LogicalExpression.Operator.AND; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestExpressionUtils { @@ -37,13 +37,9 @@ public void testAnd() Expression d = name("d"); Expression e = name("e"); - assertEquals( - ExpressionUtils.and(a, b, c, d, e), - new LogicalExpression(AND, ImmutableList.of(a, b, c, d, e))); + assertThat(ExpressionUtils.and(a, b, c, d, e)).isEqualTo(new LogicalExpression(AND, ImmutableList.of(a, b, c, d, e))); - assertEquals( - ExpressionUtils.combineConjuncts(metadata, a, b, a, c, d, c, e), - new LogicalExpression(AND, ImmutableList.of(a, b, c, d, e))); + assertThat(ExpressionUtils.combineConjuncts(metadata, a, b, a, c, d, c, e)).isEqualTo(new LogicalExpression(AND, ImmutableList.of(a, b, c, d, e))); } private static Identifier name(String name) diff --git a/core/trino-main/src/test/java/io/trino/sql/TestSqlToRowExpressionTranslator.java b/core/trino-main/src/test/java/io/trino/sql/TestSqlToRowExpressionTranslator.java index c3f34626ff43..91088f1130af 100644 --- a/core/trino-main/src/test/java/io/trino/sql/TestSqlToRowExpressionTranslator.java +++ b/core/trino-main/src/test/java/io/trino/sql/TestSqlToRowExpressionTranslator.java @@ -38,7 +38,7 @@ import static io.trino.sql.planner.TestingPlannerContext.PLANNER_CONTEXT; import static io.trino.sql.planner.iterative.rule.test.PlanBuilder.expression; import static io.trino.sql.relational.Expressions.constant; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestSqlToRowExpressionTranslator { @@ -62,22 +62,16 @@ public void testPossibleExponentialOptimizationTime() public void testOptimizeDecimalLiteral() { // Short decimal - assertEquals(translateAndOptimize(expression("CAST(NULL AS DECIMAL(7,2))")), constant(null, createDecimalType(7, 2))); - assertEquals(translateAndOptimize(expression("DECIMAL '42'")), constant(42L, createDecimalType(2, 0))); - assertEquals(translateAndOptimize(expression("CAST(42 AS DECIMAL(7,2))")), constant(4200L, createDecimalType(7, 2))); - assertEquals(translateAndOptimize(simplifyExpression(expression("CAST(42 AS DECIMAL(7,2))"))), constant(4200L, createDecimalType(7, 2))); + assertThat(translateAndOptimize(expression("CAST(NULL AS DECIMAL(7,2))"))).isEqualTo(constant(null, createDecimalType(7, 2))); + assertThat(translateAndOptimize(expression("DECIMAL '42'"))).isEqualTo(constant(42L, createDecimalType(2, 0))); + assertThat(translateAndOptimize(expression("CAST(42 AS DECIMAL(7,2))"))).isEqualTo(constant(4200L, createDecimalType(7, 2))); + assertThat(translateAndOptimize(simplifyExpression(expression("CAST(42 AS DECIMAL(7,2))")))).isEqualTo(constant(4200L, createDecimalType(7, 2))); // Long decimal - assertEquals(translateAndOptimize(expression("CAST(NULL AS DECIMAL(35,2))")), constant(null, createDecimalType(35, 2))); - assertEquals( - translateAndOptimize(expression("DECIMAL '123456789012345678901234567890'")), - constant(Decimals.valueOf(new BigDecimal("123456789012345678901234567890")), createDecimalType(30, 0))); - assertEquals( - translateAndOptimize(expression("CAST(DECIMAL '123456789012345678901234567890' AS DECIMAL(35,2))")), - constant(Decimals.valueOf(new BigDecimal("123456789012345678901234567890.00")), createDecimalType(35, 2))); - assertEquals( - translateAndOptimize(simplifyExpression(expression("CAST(DECIMAL '123456789012345678901234567890' AS DECIMAL(35,2))"))), - constant(Decimals.valueOf(new BigDecimal("123456789012345678901234567890.00")), createDecimalType(35, 2))); + assertThat(translateAndOptimize(expression("CAST(NULL AS DECIMAL(35,2))"))).isEqualTo(constant(null, createDecimalType(35, 2))); + assertThat(translateAndOptimize(expression("DECIMAL '123456789012345678901234567890'"))).isEqualTo(constant(Decimals.valueOf(new BigDecimal("123456789012345678901234567890")), createDecimalType(30, 0))); + assertThat(translateAndOptimize(expression("CAST(DECIMAL '123456789012345678901234567890' AS DECIMAL(35,2))"))).isEqualTo(constant(Decimals.valueOf(new BigDecimal("123456789012345678901234567890.00")), createDecimalType(35, 2))); + assertThat(translateAndOptimize(simplifyExpression(expression("CAST(DECIMAL '123456789012345678901234567890' AS DECIMAL(35,2))")))).isEqualTo(constant(Decimals.valueOf(new BigDecimal("123456789012345678901234567890.00")), createDecimalType(35, 2))); } private RowExpression translateAndOptimize(Expression expression) diff --git a/core/trino-main/src/test/java/io/trino/sql/analyzer/TestOutput.java b/core/trino-main/src/test/java/io/trino/sql/analyzer/TestOutput.java index 6032c863a2a4..a708fbea93a4 100644 --- a/core/trino-main/src/test/java/io/trino/sql/analyzer/TestOutput.java +++ b/core/trino-main/src/test/java/io/trino/sql/analyzer/TestOutput.java @@ -24,7 +24,7 @@ import java.util.Optional; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestOutput { @@ -48,7 +48,7 @@ public void testRoundTrip() String json = codec.toJson(expected); Output actual = codec.fromJson(json); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } @Test @@ -69,6 +69,6 @@ public void testRoundWithComplexIdentifiers() String json = codec.toJson(expected); Output actual = codec.fromJson(json); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } } diff --git a/core/trino-main/src/test/java/io/trino/sql/analyzer/TestScope.java b/core/trino-main/src/test/java/io/trino/sql/analyzer/TestScope.java index b10861e27319..6c2562e2f4d5 100644 --- a/core/trino-main/src/test/java/io/trino/sql/analyzer/TestScope.java +++ b/core/trino-main/src/test/java/io/trino/sql/analyzer/TestScope.java @@ -21,9 +21,7 @@ import java.util.Optional; import static io.trino.spi.type.BigintType.BIGINT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestScope { @@ -45,35 +43,35 @@ public void test() Expression c3 = name("c3"); Expression c4 = name("c4"); - assertFalse(root.tryResolveField(c1).isPresent()); + assertThat(root.tryResolveField(c1).isPresent()).isFalse(); - assertTrue(outer.tryResolveField(c1).isPresent()); - assertEquals(outer.tryResolveField(c1).get().getField(), outerColumn1); - assertEquals(outer.tryResolveField(c1).get().isLocal(), true); - assertEquals(outer.tryResolveField(c1).get().getHierarchyFieldIndex(), 0); - assertTrue(outer.tryResolveField(c2).isPresent()); - assertEquals(outer.tryResolveField(c2).get().getField(), outerColumn2); - assertEquals(outer.tryResolveField(c2).get().isLocal(), true); - assertEquals(outer.tryResolveField(c2).get().getHierarchyFieldIndex(), 1); - assertFalse(outer.tryResolveField(c3).isPresent()); - assertFalse(outer.tryResolveField(c4).isPresent()); + assertThat(outer.tryResolveField(c1).isPresent()).isTrue(); + assertThat(outer.tryResolveField(c1).get().getField()).isEqualTo(outerColumn1); + assertThat(outer.tryResolveField(c1).get().isLocal()).isEqualTo(true); + assertThat(outer.tryResolveField(c1).get().getHierarchyFieldIndex()).isEqualTo(0); + assertThat(outer.tryResolveField(c2).isPresent()).isTrue(); + assertThat(outer.tryResolveField(c2).get().getField()).isEqualTo(outerColumn2); + assertThat(outer.tryResolveField(c2).get().isLocal()).isEqualTo(true); + assertThat(outer.tryResolveField(c2).get().getHierarchyFieldIndex()).isEqualTo(1); + assertThat(outer.tryResolveField(c3).isPresent()).isFalse(); + assertThat(outer.tryResolveField(c4).isPresent()).isFalse(); - assertTrue(inner.tryResolveField(c1).isPresent()); - assertEquals(inner.tryResolveField(c1).get().getField(), outerColumn1); - assertEquals(inner.tryResolveField(c1).get().isLocal(), false); - assertEquals(inner.tryResolveField(c1).get().getHierarchyFieldIndex(), 0); - assertEquals(inner.tryResolveField(c1).get().getRelationFieldIndex(), 0); - assertTrue(inner.tryResolveField(c2).isPresent()); - assertEquals(inner.tryResolveField(c2).get().getField(), innerColumn2); - assertEquals(inner.tryResolveField(c2).get().isLocal(), true); - assertEquals(inner.tryResolveField(c2).get().getHierarchyFieldIndex(), 0); - assertTrue(inner.tryResolveField(c2).isPresent()); - assertEquals(inner.tryResolveField(c3).get().getField(), innerColumn3); - assertEquals(inner.tryResolveField(c3).get().isLocal(), true); - assertEquals(inner.tryResolveField(c3).get().getHierarchyFieldIndex(), 1); - assertFalse(inner.tryResolveField(c4).isPresent()); + assertThat(inner.tryResolveField(c1).isPresent()).isTrue(); + assertThat(inner.tryResolveField(c1).get().getField()).isEqualTo(outerColumn1); + assertThat(inner.tryResolveField(c1).get().isLocal()).isEqualTo(false); + assertThat(inner.tryResolveField(c1).get().getHierarchyFieldIndex()).isEqualTo(0); + assertThat(inner.tryResolveField(c1).get().getRelationFieldIndex()).isEqualTo(0); + assertThat(inner.tryResolveField(c2).isPresent()).isTrue(); + assertThat(inner.tryResolveField(c2).get().getField()).isEqualTo(innerColumn2); + assertThat(inner.tryResolveField(c2).get().isLocal()).isEqualTo(true); + assertThat(inner.tryResolveField(c2).get().getHierarchyFieldIndex()).isEqualTo(0); + assertThat(inner.tryResolveField(c2).isPresent()).isTrue(); + assertThat(inner.tryResolveField(c3).get().getField()).isEqualTo(innerColumn3); + assertThat(inner.tryResolveField(c3).get().isLocal()).isEqualTo(true); + assertThat(inner.tryResolveField(c3).get().getHierarchyFieldIndex()).isEqualTo(1); + assertThat(inner.tryResolveField(c4).isPresent()).isFalse(); - assertEquals(inner.getOuterQueryParent(), Optional.of(outer)); + assertThat(inner.getOuterQueryParent()).isEqualTo(Optional.of(outer)); } private static Expression name(String first, String... parts) diff --git a/core/trino-main/src/test/java/io/trino/sql/gen/TestInCodeGenerator.java b/core/trino-main/src/test/java/io/trino/sql/gen/TestInCodeGenerator.java index efe119e44ed0..2fcaa99882b5 100644 --- a/core/trino-main/src/test/java/io/trino/sql/gen/TestInCodeGenerator.java +++ b/core/trino-main/src/test/java/io/trino/sql/gen/TestInCodeGenerator.java @@ -33,7 +33,7 @@ import static io.trino.sql.gen.InCodeGenerator.SwitchGenerationCase.SET_CONTAINS; import static io.trino.sql.gen.InCodeGenerator.checkSwitchGenerationCase; import static io.trino.sql.relational.Expressions.constant; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestInCodeGenerator { @@ -46,21 +46,21 @@ public void testInteger() values.add(constant(Integer.MIN_VALUE, INTEGER)); values.add(constant(Integer.MAX_VALUE, INTEGER)); values.add(constant(3, INTEGER)); - assertEquals(checkSwitchGenerationCase(INTEGER, values), DIRECT_SWITCH); + assertThat(checkSwitchGenerationCase(INTEGER, values)).isEqualTo(DIRECT_SWITCH); values.add(constant(null, INTEGER)); - assertEquals(checkSwitchGenerationCase(INTEGER, values), DIRECT_SWITCH); + assertThat(checkSwitchGenerationCase(INTEGER, values)).isEqualTo(DIRECT_SWITCH); values.add(new CallExpression( functionResolution.getCoercion(DOUBLE, INTEGER), Collections.singletonList(constant(12345678901234.0, DOUBLE)))); - assertEquals(checkSwitchGenerationCase(INTEGER, values), DIRECT_SWITCH); + assertThat(checkSwitchGenerationCase(INTEGER, values)).isEqualTo(DIRECT_SWITCH); values.add(constant(6, BIGINT)); values.add(constant(7, BIGINT)); - assertEquals(checkSwitchGenerationCase(INTEGER, values), DIRECT_SWITCH); + assertThat(checkSwitchGenerationCase(INTEGER, values)).isEqualTo(DIRECT_SWITCH); values.add(constant(8, INTEGER)); - assertEquals(checkSwitchGenerationCase(INTEGER, values), SET_CONTAINS); + assertThat(checkSwitchGenerationCase(INTEGER, values)).isEqualTo(SET_CONTAINS); } @Test @@ -70,21 +70,21 @@ public void testBigint() values.add(constant(Integer.MAX_VALUE + 1L, BIGINT)); values.add(constant(Integer.MIN_VALUE - 1L, BIGINT)); values.add(constant(3L, BIGINT)); - assertEquals(checkSwitchGenerationCase(BIGINT, values), HASH_SWITCH); + assertThat(checkSwitchGenerationCase(BIGINT, values)).isEqualTo(HASH_SWITCH); values.add(constant(null, BIGINT)); - assertEquals(checkSwitchGenerationCase(BIGINT, values), HASH_SWITCH); + assertThat(checkSwitchGenerationCase(BIGINT, values)).isEqualTo(HASH_SWITCH); values.add(new CallExpression( functionResolution.getCoercion(DOUBLE, BIGINT), Collections.singletonList(constant(12345678901234.0, DOUBLE)))); - assertEquals(checkSwitchGenerationCase(BIGINT, values), HASH_SWITCH); + assertThat(checkSwitchGenerationCase(BIGINT, values)).isEqualTo(HASH_SWITCH); values.add(constant(6L, BIGINT)); values.add(constant(7L, BIGINT)); - assertEquals(checkSwitchGenerationCase(BIGINT, values), HASH_SWITCH); + assertThat(checkSwitchGenerationCase(BIGINT, values)).isEqualTo(HASH_SWITCH); values.add(constant(8L, BIGINT)); - assertEquals(checkSwitchGenerationCase(BIGINT, values), SET_CONTAINS); + assertThat(checkSwitchGenerationCase(BIGINT, values)).isEqualTo(SET_CONTAINS); } @Test @@ -94,15 +94,15 @@ public void testDate() values.add(constant(1L, DATE)); values.add(constant(2L, DATE)); values.add(constant(3L, DATE)); - assertEquals(checkSwitchGenerationCase(DATE, values), DIRECT_SWITCH); + assertThat(checkSwitchGenerationCase(DATE, values)).isEqualTo(DIRECT_SWITCH); for (long i = 4; i <= 7; ++i) { values.add(constant(i, DATE)); } - assertEquals(checkSwitchGenerationCase(DATE, values), DIRECT_SWITCH); + assertThat(checkSwitchGenerationCase(DATE, values)).isEqualTo(DIRECT_SWITCH); values.add(constant(33L, DATE)); - assertEquals(checkSwitchGenerationCase(DATE, values), SET_CONTAINS); + assertThat(checkSwitchGenerationCase(DATE, values)).isEqualTo(SET_CONTAINS); } @Test @@ -112,18 +112,18 @@ public void testDouble() values.add(constant(1.5, DOUBLE)); values.add(constant(2.5, DOUBLE)); values.add(constant(3.5, DOUBLE)); - assertEquals(checkSwitchGenerationCase(DOUBLE, values), HASH_SWITCH); + assertThat(checkSwitchGenerationCase(DOUBLE, values)).isEqualTo(HASH_SWITCH); values.add(constant(null, DOUBLE)); - assertEquals(checkSwitchGenerationCase(DOUBLE, values), HASH_SWITCH); + assertThat(checkSwitchGenerationCase(DOUBLE, values)).isEqualTo(HASH_SWITCH); for (int i = 5; i <= 7; ++i) { values.add(constant(i + 0.5, DOUBLE)); } - assertEquals(checkSwitchGenerationCase(DOUBLE, values), HASH_SWITCH); + assertThat(checkSwitchGenerationCase(DOUBLE, values)).isEqualTo(HASH_SWITCH); values.add(constant(8.5, DOUBLE)); - assertEquals(checkSwitchGenerationCase(DOUBLE, values), SET_CONTAINS); + assertThat(checkSwitchGenerationCase(DOUBLE, values)).isEqualTo(SET_CONTAINS); } @Test @@ -133,17 +133,17 @@ public void testVarchar() values.add(constant(Slices.utf8Slice("1"), DOUBLE)); values.add(constant(Slices.utf8Slice("2"), DOUBLE)); values.add(constant(Slices.utf8Slice("3"), DOUBLE)); - assertEquals(checkSwitchGenerationCase(VARCHAR, values), HASH_SWITCH); + assertThat(checkSwitchGenerationCase(VARCHAR, values)).isEqualTo(HASH_SWITCH); values.add(constant(null, VARCHAR)); - assertEquals(checkSwitchGenerationCase(VARCHAR, values), HASH_SWITCH); + assertThat(checkSwitchGenerationCase(VARCHAR, values)).isEqualTo(HASH_SWITCH); for (int i = 5; i <= 7; ++i) { values.add(constant(Slices.utf8Slice(String.valueOf(i)), VARCHAR)); } - assertEquals(checkSwitchGenerationCase(VARCHAR, values), HASH_SWITCH); + assertThat(checkSwitchGenerationCase(VARCHAR, values)).isEqualTo(HASH_SWITCH); values.add(constant(Slices.utf8Slice("8"), VARCHAR)); - assertEquals(checkSwitchGenerationCase(VARCHAR, values), SET_CONTAINS); + assertThat(checkSwitchGenerationCase(VARCHAR, values)).isEqualTo(SET_CONTAINS); } } diff --git a/core/trino-main/src/test/java/io/trino/sql/gen/TestJoinCompiler.java b/core/trino-main/src/test/java/io/trino/sql/gen/TestJoinCompiler.java index a5d6d0604c07..3504b39ce46b 100644 --- a/core/trino-main/src/test/java/io/trino/sql/gen/TestJoinCompiler.java +++ b/core/trino-main/src/test/java/io/trino/sql/gen/TestJoinCompiler.java @@ -45,8 +45,7 @@ import static io.trino.spi.type.BooleanType.BOOLEAN; import static io.trino.spi.type.DoubleType.DOUBLE; import static io.trino.spi.type.VarcharType.VARCHAR; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestJoinCompiler { @@ -83,7 +82,7 @@ public void testSingleChannel() PagesHashStrategy hashStrategy = pagesHashStrategyFactory.createPagesHashStrategy(channels, hashChannel); // verify channel count - assertEquals(hashStrategy.getChannelCount(), 1); + assertThat(hashStrategy.getChannelCount()).isEqualTo(1); BlockTypeOperators blockTypeOperators = new BlockTypeOperators(); BlockPositionEqual equalOperator = blockTypeOperators.getEqualOperator(VARCHAR); @@ -98,10 +97,10 @@ public void testSingleChannel() for (int leftBlockPosition = 0; leftBlockPosition < leftBlock.getPositionCount(); leftBlockPosition++) { // hash code of position must match block hash - assertEquals(hashStrategy.hashPosition(leftBlockIndex, leftBlockPosition), hashCodeOperator.hashCodeNullSafe(leftBlock, leftBlockPosition)); + assertThat(hashStrategy.hashPosition(leftBlockIndex, leftBlockPosition)).isEqualTo(hashCodeOperator.hashCodeNullSafe(leftBlock, leftBlockPosition)); // position must be equal to itself - assertTrue(hashStrategy.positionEqualsPositionIgnoreNulls(leftBlockIndex, leftBlockPosition, leftBlockIndex, leftBlockPosition)); + assertThat(hashStrategy.positionEqualsPositionIgnoreNulls(leftBlockIndex, leftBlockPosition, leftBlockIndex, leftBlockPosition)).isTrue(); // check equality of every position against every other position in the block for (int rightBlockIndex = 0; rightBlockIndex < channel.size(); rightBlockIndex++) { @@ -109,14 +108,14 @@ public void testSingleChannel() for (int rightBlockPosition = 0; rightBlockPosition < rightBlock.getPositionCount(); rightBlockPosition++) { boolean expected = equalOperator.equalNullSafe(leftBlock, leftBlockPosition, rightBlock, rightBlockPosition); boolean expectedNotDistinct = !distinctFromOperator.isDistinctFrom(leftBlock, leftBlockPosition, rightBlock, rightBlockPosition); - assertEquals(hashStrategy.positionEqualsRow(leftBlockIndex, leftBlockPosition, rightBlockPosition, new Page(rightBlock)), expected); - assertEquals(hashStrategy.positionNotDistinctFromRow(leftBlockIndex, leftBlockPosition, rightBlockPosition, new Page(rightBlock)), expectedNotDistinct); - assertEquals(hashStrategy.rowEqualsRow(leftBlockPosition, new Page(leftBlock), rightBlockPosition, new Page(rightBlock)), expected); - assertEquals(hashStrategy.rowNotDistinctFromRow(leftBlockPosition, new Page(leftBlock), rightBlockPosition, new Page(rightBlock)), expectedNotDistinct); - assertEquals(hashStrategy.positionEqualsRowIgnoreNulls(leftBlockIndex, leftBlockPosition, rightBlockPosition, new Page(rightBlock)), expected); - assertEquals(hashStrategy.positionEqualsPositionIgnoreNulls(leftBlockIndex, leftBlockPosition, rightBlockIndex, rightBlockPosition), expected); - assertEquals(hashStrategy.positionEqualsPosition(leftBlockIndex, leftBlockPosition, rightBlockIndex, rightBlockPosition), expected); - assertEquals(hashStrategy.positionNotDistinctFromPosition(leftBlockIndex, leftBlockPosition, rightBlockIndex, rightBlockPosition), expectedNotDistinct); + assertThat(hashStrategy.positionEqualsRow(leftBlockIndex, leftBlockPosition, rightBlockPosition, new Page(rightBlock))).isEqualTo(expected); + assertThat(hashStrategy.positionNotDistinctFromRow(leftBlockIndex, leftBlockPosition, rightBlockPosition, new Page(rightBlock))).isEqualTo(expectedNotDistinct); + assertThat(hashStrategy.rowEqualsRow(leftBlockPosition, new Page(leftBlock), rightBlockPosition, new Page(rightBlock))).isEqualTo(expected); + assertThat(hashStrategy.rowNotDistinctFromRow(leftBlockPosition, new Page(leftBlock), rightBlockPosition, new Page(rightBlock))).isEqualTo(expectedNotDistinct); + assertThat(hashStrategy.positionEqualsRowIgnoreNulls(leftBlockIndex, leftBlockPosition, rightBlockPosition, new Page(rightBlock))).isEqualTo(expected); + assertThat(hashStrategy.positionEqualsPositionIgnoreNulls(leftBlockIndex, leftBlockPosition, rightBlockIndex, rightBlockPosition)).isEqualTo(expected); + assertThat(hashStrategy.positionEqualsPosition(leftBlockIndex, leftBlockPosition, rightBlockIndex, rightBlockPosition)).isEqualTo(expected); + assertThat(hashStrategy.positionNotDistinctFromPosition(leftBlockIndex, leftBlockPosition, rightBlockIndex, rightBlockPosition)).isEqualTo(expectedNotDistinct); } } @@ -126,14 +125,14 @@ public void testSingleChannel() for (int rightBlockPosition = 0; rightBlockPosition < rightBlock.getPositionCount(); rightBlockPosition++) { boolean expected = equalOperator.equalNullSafe(leftBlock, leftBlockPosition, rightBlock, rightBlockPosition); boolean expectedNotDistinct = !distinctFromOperator.isDistinctFrom(leftBlock, leftBlockPosition, rightBlock, rightBlockPosition); - assertEquals(hashStrategy.positionEqualsRow(leftBlockIndex, leftBlockPosition, rightBlockPosition, new Page(rightBlock)), expected); - assertEquals(hashStrategy.positionNotDistinctFromRow(leftBlockIndex, leftBlockPosition, rightBlockPosition, new Page(rightBlock)), expectedNotDistinct); - assertEquals(hashStrategy.rowEqualsRow(leftBlockPosition, new Page(leftBlock), rightBlockPosition, new Page(rightBlock)), expected); - assertEquals(hashStrategy.rowNotDistinctFromRow(leftBlockPosition, new Page(leftBlock), rightBlockPosition, new Page(rightBlock)), expectedNotDistinct); - assertEquals(hashStrategy.positionEqualsRowIgnoreNulls(leftBlockIndex, leftBlockPosition, rightBlockPosition, new Page(rightBlock)), expected); - assertEquals(hashStrategy.positionEqualsPositionIgnoreNulls(leftBlockIndex, leftBlockPosition, rightBlockIndex, rightBlockPosition), expected); - assertEquals(hashStrategy.positionEqualsPosition(leftBlockIndex, leftBlockPosition, rightBlockIndex, rightBlockPosition), expected); - assertEquals(hashStrategy.positionNotDistinctFromPosition(leftBlockIndex, leftBlockPosition, rightBlockIndex, rightBlockPosition), expectedNotDistinct); + assertThat(hashStrategy.positionEqualsRow(leftBlockIndex, leftBlockPosition, rightBlockPosition, new Page(rightBlock))).isEqualTo(expected); + assertThat(hashStrategy.positionNotDistinctFromRow(leftBlockIndex, leftBlockPosition, rightBlockPosition, new Page(rightBlock))).isEqualTo(expectedNotDistinct); + assertThat(hashStrategy.rowEqualsRow(leftBlockPosition, new Page(leftBlock), rightBlockPosition, new Page(rightBlock))).isEqualTo(expected); + assertThat(hashStrategy.rowNotDistinctFromRow(leftBlockPosition, new Page(leftBlock), rightBlockPosition, new Page(rightBlock))).isEqualTo(expectedNotDistinct); + assertThat(hashStrategy.positionEqualsRowIgnoreNulls(leftBlockIndex, leftBlockPosition, rightBlockPosition, new Page(rightBlock))).isEqualTo(expected); + assertThat(hashStrategy.positionEqualsPositionIgnoreNulls(leftBlockIndex, leftBlockPosition, rightBlockIndex, rightBlockPosition)).isEqualTo(expected); + assertThat(hashStrategy.positionEqualsPosition(leftBlockIndex, leftBlockPosition, rightBlockIndex, rightBlockPosition)).isEqualTo(expected); + assertThat(hashStrategy.positionNotDistinctFromPosition(leftBlockIndex, leftBlockPosition, rightBlockIndex, rightBlockPosition)).isEqualTo(expectedNotDistinct); } } @@ -205,7 +204,7 @@ public void testMultiChannel() PagesHashStrategy expectedHashStrategy = new SimplePagesHashStrategy(types, outputChannels, channels, joinChannels, hashChannel, Optional.empty(), blockTypeOperators); // verify channel count - assertEquals(hashStrategy.getChannelCount(), outputChannels.size()); + assertThat(hashStrategy.getChannelCount()).isEqualTo(outputChannels.size()); // verify size int instanceSize = instanceSize(hashStrategy.getClass()); long sizeInBytes = instanceSize + @@ -214,7 +213,7 @@ public void testMultiChannel() .flatMap(List::stream) .mapToLong(Block::getRetainedSizeInBytes) .sum(); - assertEquals(hashStrategy.getSizeInBytes(), sizeInBytes); + assertThat(hashStrategy.getSizeInBytes()).isEqualTo(sizeInBytes); // verify hashStrategy is consistent with equals and hash code from block for (int leftBlockIndex = 0; leftBlockIndex < varcharChannel.size(); leftBlockIndex++) { @@ -229,28 +228,20 @@ public void testMultiChannel() int leftPositionCount = varcharChannel.get(leftBlockIndex).getPositionCount(); for (int leftBlockPosition = 0; leftBlockPosition < leftPositionCount; leftBlockPosition++) { // hash code of position must match block hash - assertEquals( - hashStrategy.hashPosition(leftBlockIndex, leftBlockPosition), - expectedHashStrategy.hashPosition(leftBlockIndex, leftBlockPosition)); + assertThat(hashStrategy.hashPosition(leftBlockIndex, leftBlockPosition)).isEqualTo(expectedHashStrategy.hashPosition(leftBlockIndex, leftBlockPosition)); // position must be equal to itself - assertTrue(hashStrategy.positionEqualsPositionIgnoreNulls(leftBlockIndex, leftBlockPosition, leftBlockIndex, leftBlockPosition)); - assertTrue(hashStrategy.positionEqualsPosition(leftBlockIndex, leftBlockPosition, leftBlockIndex, leftBlockPosition)); - assertTrue(hashStrategy.positionNotDistinctFromPosition(leftBlockIndex, leftBlockPosition, leftBlockIndex, leftBlockPosition)); + assertThat(hashStrategy.positionEqualsPositionIgnoreNulls(leftBlockIndex, leftBlockPosition, leftBlockIndex, leftBlockPosition)).isTrue(); + assertThat(hashStrategy.positionEqualsPosition(leftBlockIndex, leftBlockPosition, leftBlockIndex, leftBlockPosition)).isTrue(); + assertThat(hashStrategy.positionNotDistinctFromPosition(leftBlockIndex, leftBlockPosition, leftBlockIndex, leftBlockPosition)).isTrue(); // check equality of every position against every other position in the block for (int rightBlockIndex = 0; rightBlockIndex < varcharChannel.size(); rightBlockIndex++) { Block rightBlock = varcharChannel.get(rightBlockIndex); for (int rightBlockPosition = 0; rightBlockPosition < rightBlock.getPositionCount(); rightBlockPosition++) { - assertEquals( - hashStrategy.positionEqualsPositionIgnoreNulls(leftBlockIndex, leftBlockPosition, rightBlockIndex, rightBlockPosition), - expectedHashStrategy.positionEqualsPositionIgnoreNulls(leftBlockIndex, leftBlockPosition, rightBlockIndex, rightBlockPosition)); - assertEquals( - hashStrategy.positionEqualsPosition(leftBlockIndex, leftBlockPosition, rightBlockIndex, rightBlockPosition), - expectedHashStrategy.positionEqualsPosition(leftBlockIndex, leftBlockPosition, rightBlockIndex, rightBlockPosition)); - assertEquals( - hashStrategy.positionNotDistinctFromPosition(leftBlockIndex, leftBlockPosition, rightBlockIndex, rightBlockPosition), - expectedHashStrategy.positionNotDistinctFromPosition(leftBlockIndex, leftBlockPosition, rightBlockIndex, rightBlockPosition)); + assertThat(hashStrategy.positionEqualsPositionIgnoreNulls(leftBlockIndex, leftBlockPosition, rightBlockIndex, rightBlockPosition)).isEqualTo(expectedHashStrategy.positionEqualsPositionIgnoreNulls(leftBlockIndex, leftBlockPosition, rightBlockIndex, rightBlockPosition)); + assertThat(hashStrategy.positionEqualsPosition(leftBlockIndex, leftBlockPosition, rightBlockIndex, rightBlockPosition)).isEqualTo(expectedHashStrategy.positionEqualsPosition(leftBlockIndex, leftBlockPosition, rightBlockIndex, rightBlockPosition)); + assertThat(hashStrategy.positionNotDistinctFromPosition(leftBlockIndex, leftBlockPosition, rightBlockIndex, rightBlockPosition)).isEqualTo(expectedHashStrategy.positionNotDistinctFromPosition(leftBlockIndex, leftBlockPosition, rightBlockIndex, rightBlockPosition)); } } @@ -267,11 +258,11 @@ public void testMultiChannel() boolean expected = expectedHashStrategy.positionEqualsRow(leftBlockIndex, leftBlockPosition, rightPosition, new Page(rightBlocks)); boolean expectedNotDistinct = expectedHashStrategy.positionNotDistinctFromRow(leftBlockIndex, leftBlockPosition, rightPosition, new Page(rightBlocks)); - assertEquals(hashStrategy.positionEqualsRow(leftBlockIndex, leftBlockPosition, rightPosition, new Page(rightBlocks)), expected); - assertEquals(hashStrategy.positionNotDistinctFromRow(leftBlockIndex, leftBlockPosition, rightPosition, new Page(rightBlocks)), expectedNotDistinct); - assertEquals(hashStrategy.rowEqualsRow(leftBlockPosition, new Page(leftBlocks), rightPosition, new Page(rightBlocks)), expected); - assertEquals(hashStrategy.rowNotDistinctFromRow(leftBlockPosition, new Page(leftBlocks), rightPosition, new Page(rightBlocks)), expectedNotDistinct); - assertEquals(hashStrategy.positionEqualsRowIgnoreNulls(leftBlockIndex, leftBlockPosition, rightPosition, new Page(rightBlocks)), expected); + assertThat(hashStrategy.positionEqualsRow(leftBlockIndex, leftBlockPosition, rightPosition, new Page(rightBlocks))).isEqualTo(expected); + assertThat(hashStrategy.positionNotDistinctFromRow(leftBlockIndex, leftBlockPosition, rightPosition, new Page(rightBlocks))).isEqualTo(expectedNotDistinct); + assertThat(hashStrategy.rowEqualsRow(leftBlockPosition, new Page(leftBlocks), rightPosition, new Page(rightBlocks))).isEqualTo(expected); + assertThat(hashStrategy.rowNotDistinctFromRow(leftBlockPosition, new Page(leftBlocks), rightPosition, new Page(rightBlocks))).isEqualTo(expectedNotDistinct); + assertThat(hashStrategy.positionEqualsRowIgnoreNulls(leftBlockIndex, leftBlockPosition, rightPosition, new Page(rightBlocks))).isEqualTo(expected); } } @@ -322,7 +313,7 @@ public void testDistinctFrom() PagesHashStrategy hashStrategy = pagesHashStrategyFactory.createPagesHashStrategy(ImmutableList.of(channel), OptionalInt.empty()); // verify channel count - assertEquals(hashStrategy.getChannelCount(), 1); + assertThat(hashStrategy.getChannelCount()).isEqualTo(1); BlockTypeOperators blockTypeOperators = new BlockTypeOperators(); BlockPositionIsDistinctFrom distinctFromOperator = blockTypeOperators.getDistinctFromOperator(DOUBLE); @@ -333,16 +324,16 @@ public void testDistinctFrom() for (int leftBlockPosition = 0; leftBlockPosition < leftBlock.getPositionCount(); leftBlockPosition++) { // position must not be distinct from itself - assertTrue(hashStrategy.positionNotDistinctFromPosition(leftBlockIndex, leftBlockPosition, leftBlockIndex, leftBlockPosition)); + assertThat(hashStrategy.positionNotDistinctFromPosition(leftBlockIndex, leftBlockPosition, leftBlockIndex, leftBlockPosition)).isTrue(); // check distinctiveness of every position against every other position in the block for (int rightBlockIndex = 0; rightBlockIndex < channel.size(); rightBlockIndex++) { Block rightBlock = channel.get(rightBlockIndex); for (int rightBlockPosition = 0; rightBlockPosition < rightBlock.getPositionCount(); rightBlockPosition++) { boolean expectedNotDistinct = !distinctFromOperator.isDistinctFrom(leftBlock, leftBlockPosition, rightBlock, rightBlockPosition); - assertEquals(hashStrategy.positionNotDistinctFromRow(leftBlockIndex, leftBlockPosition, rightBlockPosition, new Page(rightBlock)), expectedNotDistinct); - assertEquals(hashStrategy.rowNotDistinctFromRow(leftBlockPosition, new Page(leftBlock), rightBlockPosition, new Page(rightBlock)), expectedNotDistinct); - assertEquals(hashStrategy.positionNotDistinctFromPosition(leftBlockIndex, leftBlockPosition, rightBlockIndex, rightBlockPosition), expectedNotDistinct); + assertThat(hashStrategy.positionNotDistinctFromRow(leftBlockIndex, leftBlockPosition, rightBlockPosition, new Page(rightBlock))).isEqualTo(expectedNotDistinct); + assertThat(hashStrategy.rowNotDistinctFromRow(leftBlockPosition, new Page(leftBlock), rightBlockPosition, new Page(rightBlock))).isEqualTo(expectedNotDistinct); + assertThat(hashStrategy.positionNotDistinctFromPosition(leftBlockIndex, leftBlockPosition, rightBlockIndex, rightBlockPosition)).isEqualTo(expectedNotDistinct); } } @@ -351,9 +342,9 @@ public void testDistinctFrom() Block rightBlock = channel.get(rightBlockIndex); for (int rightBlockPosition = 0; rightBlockPosition < rightBlock.getPositionCount(); rightBlockPosition++) { boolean expectedNotDistinct = !distinctFromOperator.isDistinctFrom(leftBlock, leftBlockPosition, rightBlock, rightBlockPosition); - assertEquals(hashStrategy.positionNotDistinctFromRow(leftBlockIndex, leftBlockPosition, rightBlockPosition, new Page(rightBlock)), expectedNotDistinct); - assertEquals(hashStrategy.rowNotDistinctFromRow(leftBlockPosition, new Page(leftBlock), rightBlockPosition, new Page(rightBlock)), expectedNotDistinct); - assertEquals(hashStrategy.positionNotDistinctFromPosition(leftBlockIndex, leftBlockPosition, rightBlockIndex, rightBlockPosition), expectedNotDistinct); + assertThat(hashStrategy.positionNotDistinctFromRow(leftBlockIndex, leftBlockPosition, rightBlockPosition, new Page(rightBlock))).isEqualTo(expectedNotDistinct); + assertThat(hashStrategy.rowNotDistinctFromRow(leftBlockPosition, new Page(leftBlock), rightBlockPosition, new Page(rightBlock))).isEqualTo(expectedNotDistinct); + assertThat(hashStrategy.positionNotDistinctFromPosition(leftBlockIndex, leftBlockPosition, rightBlockIndex, rightBlockPosition)).isEqualTo(expectedNotDistinct); } } } diff --git a/core/trino-main/src/test/java/io/trino/sql/gen/TestPageFunctionCompiler.java b/core/trino-main/src/test/java/io/trino/sql/gen/TestPageFunctionCompiler.java index 9a6136b4d8f6..9651ed0ed24c 100644 --- a/core/trino-main/src/test/java/io/trino/sql/gen/TestPageFunctionCompiler.java +++ b/core/trino-main/src/test/java/io/trino/sql/gen/TestPageFunctionCompiler.java @@ -36,10 +36,7 @@ import static io.trino.sql.relational.Expressions.field; import static io.trino.testing.TestingConnectorSession.SESSION; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotSame; -import static org.testng.Assert.assertSame; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestPageFunctionCompiler { @@ -60,7 +57,7 @@ public void testFailureDoesNotCorruptFutureResults() // process good page and verify we got the expected number of result rows Page goodPage = createLongBlockPage(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); Block goodResult = project(projection, goodPage, SelectedPositions.positionsRange(0, goodPage.getPositionCount())); - assertEquals(goodPage.getPositionCount(), goodResult.getPositionCount()); + assertThat(goodPage.getPositionCount()).isEqualTo(goodResult.getPositionCount()); // addition will throw due to integer overflow Page badPage = createLongBlockPage(0, 1, 2, 3, 4, Long.MAX_VALUE); @@ -70,7 +67,7 @@ public void testFailureDoesNotCorruptFutureResults() // running the good page should still work // if block builder in generated code was not reset properly, we could get junk results after the failure goodResult = project(projection, goodPage, SelectedPositions.positionsRange(0, goodPage.getPositionCount())); - assertEquals(goodPage.getPositionCount(), goodResult.getPositionCount()); + assertThat(goodPage.getPositionCount()).isEqualTo(goodResult.getPositionCount()); } @Test @@ -85,45 +82,29 @@ public void testGeneratedClassName() PageProjection projection = projectionSupplier.get(); Work work = projection.project(SESSION, new DriverYieldSignal(), createLongBlockPage(0), SelectedPositions.positionsRange(0, 1)); // class name should look like PageProjectionOutput_20170707_223500_67496_zguwn_2_7_XX - assertTrue(work.getClass().getSimpleName().startsWith("PageProjectionWork_" + stageId.replace('.', '_') + "_" + planNodeId)); + assertThat(work.getClass().getSimpleName().startsWith("PageProjectionWork_" + stageId.replace('.', '_') + "_" + planNodeId)).isTrue(); } @Test public void testCache() { PageFunctionCompiler cacheCompiler = FUNCTION_RESOLUTION.getPageFunctionCompiler(100); - assertSame( - cacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.empty()), - cacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.empty())); - assertSame( - cacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.of("hint")), - cacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.of("hint"))); - assertSame( - cacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.of("hint")), - cacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.of("hint2"))); - assertSame( - cacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.empty()), - cacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.of("hint2"))); + assertThat(cacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.empty())).isSameAs(cacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.empty())); + assertThat(cacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.of("hint"))).isSameAs(cacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.of("hint"))); + assertThat(cacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.of("hint"))).isSameAs(cacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.of("hint2"))); + assertThat(cacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.empty())).isSameAs(cacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.of("hint2"))); PageFunctionCompiler noCacheCompiler = FUNCTION_RESOLUTION.getPageFunctionCompiler(); - assertNotSame( - noCacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.empty()), - noCacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.empty())); - assertNotSame( - noCacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.of("hint")), - noCacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.of("hint"))); - assertNotSame( - noCacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.of("hint")), - noCacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.of("hint2"))); - assertNotSame( - noCacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.empty()), - noCacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.of("hint2"))); + assertThat(noCacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.empty())).isNotSameAs(noCacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.empty())); + assertThat(noCacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.of("hint"))).isNotSameAs(noCacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.of("hint"))); + assertThat(noCacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.of("hint"))).isNotSameAs(noCacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.of("hint2"))); + assertThat(noCacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.empty())).isNotSameAs(noCacheCompiler.compileProjection(ADD_10_EXPRESSION, Optional.of("hint2"))); } private Block project(PageProjection projection, Page page, SelectedPositions selectedPositions) { Work work = projection.project(SESSION, new DriverYieldSignal(), page, selectedPositions); - assertTrue(work.process()); + assertThat(work.process()).isTrue(); return work.getResult(); } diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/BenchmarkPlanner.java b/core/trino-main/src/test/java/io/trino/sql/planner/BenchmarkPlanner.java index 59e473c4658f..cae6ef950d75 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/BenchmarkPlanner.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/BenchmarkPlanner.java @@ -63,7 +63,7 @@ import static java.lang.String.format; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.joining; -import static org.testng.Assert.assertNotNull; +import static org.assertj.core.api.Assertions.assertThat; @SuppressWarnings("MethodMayBeStatic") @State(Scope.Benchmark) @@ -141,7 +141,7 @@ public void verify() BenchmarkData data = new BenchmarkData(); data.queries = queries; data.setup(); - assertNotNull(benchmark.plan(data)); + assertThat(benchmark.plan(data)).isNotNull(); } } diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestConnectorExpressionTranslator.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestConnectorExpressionTranslator.java index db4725cf2a10..ecec430e74c9 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestConnectorExpressionTranslator.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestConnectorExpressionTranslator.java @@ -91,7 +91,7 @@ import static io.trino.type.LikeFunctions.likePattern; import static io.trino.type.LikePatternType.LIKE_PATTERN; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestConnectorExpressionTranslator { @@ -501,13 +501,13 @@ private void assertTranslationToConnectorExpression(Session session, Expression private void assertTranslationToConnectorExpression(Session session, Expression expression, Optional connectorExpression) { Optional translation = translate(session, expression, TYPE_PROVIDER, PLANNER_CONTEXT, TYPE_ANALYZER); - assertEquals(connectorExpression.isPresent(), translation.isPresent()); - translation.ifPresent(value -> assertEquals(value, connectorExpression.get())); + assertThat(connectorExpression.isPresent()).isEqualTo(translation.isPresent()); + translation.ifPresent(value -> assertThat(value).isEqualTo(connectorExpression.get())); } private void assertTranslationFromConnectorExpression(Session session, ConnectorExpression connectorExpression, Expression expected) { Expression translation = ConnectorExpressionTranslator.translate(session, connectorExpression, PLANNER_CONTEXT, variableMappings, LITERAL_ENCODER); - assertEquals(translation, expected); + assertThat(translation).isEqualTo(expected); } } diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestDeterminismEvaluator.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestDeterminismEvaluator.java index 8448f2ffbaa3..60f5876d40b0 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestDeterminismEvaluator.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestDeterminismEvaluator.java @@ -36,8 +36,7 @@ import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.sql.tree.ComparisonExpression.Operator.GREATER_THAN; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestDeterminismEvaluator { @@ -47,31 +46,31 @@ public class TestDeterminismEvaluator public void testSanity() { Metadata metadata = functionResolution.getMetadata(); - assertFalse(DeterminismEvaluator.isDeterministic(function("rand"), metadata)); - assertFalse(DeterminismEvaluator.isDeterministic(function("random"), metadata)); - assertFalse(DeterminismEvaluator.isDeterministic(function("shuffle", ImmutableList.of(new ArrayType(VARCHAR)), ImmutableList.of(new NullLiteral())), - metadata)); - assertFalse(DeterminismEvaluator.isDeterministic(function("uuid"), metadata)); - assertTrue(DeterminismEvaluator.isDeterministic(function("abs", ImmutableList.of(DOUBLE), ImmutableList.of(input("symbol"))), metadata)); - assertFalse(DeterminismEvaluator.isDeterministic(function("abs", ImmutableList.of(DOUBLE), ImmutableList.of(function("rand"))), metadata)); - assertTrue(DeterminismEvaluator.isDeterministic( + assertThat(DeterminismEvaluator.isDeterministic(function("rand"), metadata)).isFalse(); + assertThat(DeterminismEvaluator.isDeterministic(function("random"), metadata)).isFalse(); + assertThat(DeterminismEvaluator.isDeterministic(function("shuffle", ImmutableList.of(new ArrayType(VARCHAR)), ImmutableList.of(new NullLiteral())), + metadata)).isFalse(); + assertThat(DeterminismEvaluator.isDeterministic(function("uuid"), metadata)).isFalse(); + assertThat(DeterminismEvaluator.isDeterministic(function("abs", ImmutableList.of(DOUBLE), ImmutableList.of(input("symbol"))), metadata)).isTrue(); + assertThat(DeterminismEvaluator.isDeterministic(function("abs", ImmutableList.of(DOUBLE), ImmutableList.of(function("rand"))), metadata)).isFalse(); + assertThat(DeterminismEvaluator.isDeterministic( function( "abs", ImmutableList.of(DOUBLE), ImmutableList.of(function("abs", ImmutableList.of(DOUBLE), ImmutableList.of(input("symbol"))))), - metadata)); - assertTrue(DeterminismEvaluator.isDeterministic( + metadata)).isTrue(); + assertThat(DeterminismEvaluator.isDeterministic( function( "filter", ImmutableList.of(new ArrayType(INTEGER), new FunctionType(ImmutableList.of(INTEGER), BOOLEAN)), ImmutableList.of(lambda("a", comparison(GREATER_THAN, input("a"), new LongLiteral("0"))))), - metadata)); - assertFalse(DeterminismEvaluator.isDeterministic( + metadata)).isTrue(); + assertThat(DeterminismEvaluator.isDeterministic( function( "filter", ImmutableList.of(new ArrayType(INTEGER), new FunctionType(ImmutableList.of(INTEGER), BOOLEAN)), ImmutableList.of(lambda("a", comparison(GREATER_THAN, function("rand", ImmutableList.of(INTEGER), ImmutableList.of(input("a"))), new LongLiteral("0"))))), - metadata)); + metadata)).isFalse(); } private FunctionCall function(String name) diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestDomainCoercer.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestDomainCoercer.java index da851c86811e..c62d597a9bc6 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestDomainCoercer.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestDomainCoercer.java @@ -34,35 +34,33 @@ import static io.trino.spi.type.SmallintType.SMALLINT; import static io.trino.sql.planner.TestingPlannerContext.PLANNER_CONTEXT; import static java.lang.Float.floatToIntBits; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestDomainCoercer { @Test public void testNone() { - assertEquals(applySaturatedCasts(Domain.none(BIGINT), INTEGER), Domain.none(INTEGER)); + assertThat(applySaturatedCasts(Domain.none(BIGINT), INTEGER)).isEqualTo(Domain.none(INTEGER)); } @Test public void testAll() { - assertEquals(applySaturatedCasts(Domain.all(BIGINT), INTEGER), Domain.all(INTEGER)); + assertThat(applySaturatedCasts(Domain.all(BIGINT), INTEGER)).isEqualTo(Domain.all(INTEGER)); } @Test public void testOnlyNull() { - assertEquals(applySaturatedCasts(Domain.onlyNull(BIGINT), INTEGER), Domain.onlyNull(INTEGER)); + assertThat(applySaturatedCasts(Domain.onlyNull(BIGINT), INTEGER)).isEqualTo(Domain.onlyNull(INTEGER)); } @Test public void testCoercedValueSameAsOriginal() { - assertEquals( - applySaturatedCasts(multipleValues(BIGINT, ImmutableList.of(1L, 10000L, -2000L)), SMALLINT), - multipleValues(SMALLINT, ImmutableList.of(1L, 10000L, -2000L))); + assertThat(applySaturatedCasts(multipleValues(BIGINT, ImmutableList.of(1L, 10000L, -2000L)), SMALLINT)).isEqualTo(multipleValues(SMALLINT, ImmutableList.of(1L, 10000L, -2000L))); Domain original = Domain.create( ValueSet.ofRanges( @@ -71,137 +69,111 @@ public void testCoercedValueSameAsOriginal() range(DOUBLE, 2.0, true, 3.0, true), greaterThan(DOUBLE, 4.0)), true); - assertEquals( - applySaturatedCasts(original, REAL), - Domain.create( - ValueSet.ofRanges( - lessThan(REAL, (long) floatToIntBits(0.0f)), - range(REAL, (long) floatToIntBits(0.0f), false, (long) floatToIntBits(1.0f), false), - range(REAL, (long) floatToIntBits(2.0f), true, (long) floatToIntBits(3.0f), true), - greaterThan(REAL, (long) floatToIntBits(4.0f))), - true)); + assertThat(applySaturatedCasts(original, REAL)).isEqualTo(Domain.create( + ValueSet.ofRanges( + lessThan(REAL, (long) floatToIntBits(0.0f)), + range(REAL, (long) floatToIntBits(0.0f), false, (long) floatToIntBits(1.0f), false), + range(REAL, (long) floatToIntBits(2.0f), true, (long) floatToIntBits(3.0f), true), + greaterThan(REAL, (long) floatToIntBits(4.0f))), + true)); } @Test public void testOutsideTargetTypeRange() { - assertEquals( - applySaturatedCasts(multipleValues(BIGINT, ImmutableList.of(1L, 10000000000L, -2000L)), SMALLINT), - multipleValues(SMALLINT, ImmutableList.of(1L, -2000L))); - - assertEquals( - applySaturatedCasts( - Domain.create( - ValueSet.ofRanges(range(DOUBLE, 0.0, true, ((double) Float.MAX_VALUE) * 10, true)), - true), - REAL), + assertThat(applySaturatedCasts(multipleValues(BIGINT, ImmutableList.of(1L, 10000000000L, -2000L)), SMALLINT)).isEqualTo(multipleValues(SMALLINT, ImmutableList.of(1L, -2000L))); + + assertThat(applySaturatedCasts( Domain.create( - ValueSet.ofRanges((range(REAL, (long) floatToIntBits(0.0f), true, (long) floatToIntBits(Float.MAX_VALUE), true))), - true)); + ValueSet.ofRanges(range(DOUBLE, 0.0, true, ((double) Float.MAX_VALUE) * 10, true)), + true), + REAL)).isEqualTo(Domain.create( + ValueSet.ofRanges((range(REAL, (long) floatToIntBits(0.0f), true, (long) floatToIntBits(Float.MAX_VALUE), true))), + true)); // low below and high above target type range - assertEquals( - applySaturatedCasts( - Domain.create( - ValueSet.ofRanges( - range(DOUBLE, ((double) Float.MAX_VALUE) * -2, true, ((double) Float.MAX_VALUE) * 10, true)), - true), - REAL), - Domain.create(ValueSet.ofRanges(lessThanOrEqual(REAL, (long) floatToIntBits(Float.MAX_VALUE))), true)); - - assertEquals( - applySaturatedCasts( - Domain.create( - ValueSet.ofRanges( - range(DOUBLE, Double.NEGATIVE_INFINITY, true, Double.POSITIVE_INFINITY, true)), - true), - REAL), + assertThat(applySaturatedCasts( + Domain.create( + ValueSet.ofRanges( + range(DOUBLE, ((double) Float.MAX_VALUE) * -2, true, ((double) Float.MAX_VALUE) * 10, true)), + true), + REAL)).isEqualTo(Domain.create(ValueSet.ofRanges(lessThanOrEqual(REAL, (long) floatToIntBits(Float.MAX_VALUE))), true)); + + assertThat(applySaturatedCasts( + Domain.create( + ValueSet.ofRanges( + range(DOUBLE, Double.NEGATIVE_INFINITY, true, Double.POSITIVE_INFINITY, true)), + true), + REAL)).isEqualTo(Domain.create( + ValueSet.ofRanges( + lessThanOrEqual(REAL, (long) floatToIntBits(Float.MAX_VALUE))), + true)); + + assertThat(applySaturatedCasts( + Domain.create( + ValueSet.ofRanges( + range(BIGINT, ((long) Integer.MAX_VALUE) * -2, false, ((long) Integer.MAX_VALUE) * 10, false)), + true), + INTEGER)).isEqualTo(Domain.create(ValueSet.ofRanges(lessThanOrEqual(INTEGER, (long) Integer.MAX_VALUE)), true)); + + assertThat(applySaturatedCasts( Domain.create( ValueSet.ofRanges( - lessThanOrEqual(REAL, (long) floatToIntBits(Float.MAX_VALUE))), - true)); - - assertEquals( - applySaturatedCasts( - Domain.create( - ValueSet.ofRanges( - range(BIGINT, ((long) Integer.MAX_VALUE) * -2, false, ((long) Integer.MAX_VALUE) * 10, false)), - true), - INTEGER), - Domain.create(ValueSet.ofRanges(lessThanOrEqual(INTEGER, (long) Integer.MAX_VALUE)), true)); - - assertEquals( - applySaturatedCasts( - Domain.create( - ValueSet.ofRanges( - range(DOUBLE, Double.NEGATIVE_INFINITY, true, Double.POSITIVE_INFINITY, true)), - true), - INTEGER), - Domain.create(ValueSet.ofRanges(lessThanOrEqual(INTEGER, (long) Integer.MAX_VALUE)), true)); + range(DOUBLE, Double.NEGATIVE_INFINITY, true, Double.POSITIVE_INFINITY, true)), + true), + INTEGER)).isEqualTo(Domain.create(ValueSet.ofRanges(lessThanOrEqual(INTEGER, (long) Integer.MAX_VALUE)), true)); // Low and high below target type range - assertEquals( - applySaturatedCasts( - Domain.create( - ValueSet.ofRanges( - range(BIGINT, ((long) Integer.MAX_VALUE) * -4, false, ((long) Integer.MAX_VALUE) * -2, false)), - false), - INTEGER), - Domain.none(INTEGER)); - - assertEquals( - applySaturatedCasts( - Domain.create( - ValueSet.ofRanges( - range(DOUBLE, ((double) Float.MAX_VALUE) * -4, true, ((double) Float.MAX_VALUE) * -2, true)), - true), - REAL), - Domain.onlyNull(REAL)); + assertThat(applySaturatedCasts( + Domain.create( + ValueSet.ofRanges( + range(BIGINT, ((long) Integer.MAX_VALUE) * -4, false, ((long) Integer.MAX_VALUE) * -2, false)), + false), + INTEGER)).isEqualTo(Domain.none(INTEGER)); + + assertThat(applySaturatedCasts( + Domain.create( + ValueSet.ofRanges( + range(DOUBLE, ((double) Float.MAX_VALUE) * -4, true, ((double) Float.MAX_VALUE) * -2, true)), + true), + REAL)).isEqualTo(Domain.onlyNull(REAL)); // Low and high above target type range - assertEquals( - applySaturatedCasts( - Domain.create( - ValueSet.ofRanges( - range(BIGINT, ((long) Integer.MAX_VALUE) * 2, false, ((long) Integer.MAX_VALUE) * 4, false)), - false), - INTEGER), - Domain.none(INTEGER)); - - assertEquals( - applySaturatedCasts( - Domain.create( - ValueSet.ofRanges( - range(DOUBLE, ((double) Float.MAX_VALUE) * 2, true, ((double) Float.MAX_VALUE) * 4, true)), - true), - REAL), - Domain.onlyNull(REAL)); + assertThat(applySaturatedCasts( + Domain.create( + ValueSet.ofRanges( + range(BIGINT, ((long) Integer.MAX_VALUE) * 2, false, ((long) Integer.MAX_VALUE) * 4, false)), + false), + INTEGER)).isEqualTo(Domain.none(INTEGER)); + + assertThat(applySaturatedCasts( + Domain.create( + ValueSet.ofRanges( + range(DOUBLE, ((double) Float.MAX_VALUE) * 2, true, ((double) Float.MAX_VALUE) * 4, true)), + true), + REAL)).isEqualTo(Domain.onlyNull(REAL)); // all short-circuit - assertEquals( - applySaturatedCasts( - Domain.create( - ValueSet.ofRanges( - greaterThanOrEqual(DOUBLE, ((double) Float.MAX_VALUE) * -4), - range(DOUBLE, 0.0, true, 1.0, true)), - true), - REAL), - Domain.all(REAL)); + assertThat(applySaturatedCasts( + Domain.create( + ValueSet.ofRanges( + greaterThanOrEqual(DOUBLE, ((double) Float.MAX_VALUE) * -4), + range(DOUBLE, 0.0, true, 1.0, true)), + true), + REAL)).isEqualTo(Domain.all(REAL)); } @Test public void testTruncatedCoercedValue() { - assertEquals( - applySaturatedCasts( - Domain.create( - ValueSet.ofRanges( - range(createDecimalType(6, 3), 123456L, true, 234567L, false)), - true), - createDecimalType(6, 1)), + assertThat(applySaturatedCasts( Domain.create( - ValueSet.ofRanges(range(createDecimalType(6, 1), 1234L, false, 2345L, true)), - true)); + ValueSet.ofRanges( + range(createDecimalType(6, 3), 123456L, true, 234567L, false)), + true), + createDecimalType(6, 1))).isEqualTo(Domain.create( + ValueSet.ofRanges(range(createDecimalType(6, 1), 1234L, false, 2345L, true)), + true)); } @Test diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestDomainTranslator.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestDomainTranslator.java index 0ab1f562896a..4f5d036289c2 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestDomainTranslator.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestDomainTranslator.java @@ -106,12 +106,10 @@ import static java.lang.String.format; import static java.util.Collections.nCopies; import static java.util.Objects.requireNonNull; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Fail.fail; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotEquals; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -209,8 +207,8 @@ public void testNoneRoundTrip() { TupleDomain tupleDomain = TupleDomain.none(); ExtractionResult result = fromPredicate(toPredicate(tupleDomain)); - assertEquals(result.getRemainingExpression(), TRUE_LITERAL); - assertEquals(result.getTupleDomain(), tupleDomain); + assertThat(result.getRemainingExpression()).isEqualTo(TRUE_LITERAL); + assertThat(result.getTupleDomain()).isEqualTo(tupleDomain); } @Test @@ -218,8 +216,8 @@ public void testAllRoundTrip() { TupleDomain tupleDomain = TupleDomain.all(); ExtractionResult result = fromPredicate(toPredicate(tupleDomain)); - assertEquals(result.getRemainingExpression(), TRUE_LITERAL); - assertEquals(result.getTupleDomain(), tupleDomain); + assertThat(result.getRemainingExpression()).isEqualTo(TRUE_LITERAL); + assertThat(result.getTupleDomain()).isEqualTo(tupleDomain); } @Test @@ -251,7 +249,7 @@ public void testInOptimization() Range.equal(BIGINT, 1L), Range.equal(BIGINT, 2L), Range.equal(BIGINT, 3L))), false); TupleDomain tupleDomain = tupleDomain(C_BIGINT, testDomain); - assertEquals(toPredicate(tupleDomain), not(in(C_BIGINT, ImmutableList.of(1L, 2L, 3L)))); + assertThat(toPredicate(tupleDomain)).isEqualTo(not(in(C_BIGINT, ImmutableList.of(1L, 2L, 3L)))); testDomain = Domain.create( ValueSet.ofRanges( @@ -260,7 +258,7 @@ public void testInOptimization() .subtract(ValueSet.ofRanges(Range.equal(BIGINT, 1L), Range.equal(BIGINT, 2L), Range.equal(BIGINT, 3L)))), false); tupleDomain = tupleDomain(C_BIGINT, testDomain); - assertEquals(toPredicate(tupleDomain), and(lessThan(C_BIGINT, bigintLiteral(4L)), not(in(C_BIGINT, ImmutableList.of(1L, 2L, 3L))))); + assertThat(toPredicate(tupleDomain)).isEqualTo(and(lessThan(C_BIGINT, bigintLiteral(4L)), not(in(C_BIGINT, ImmutableList.of(1L, 2L, 3L))))); testDomain = Domain.create(ValueSet.ofRanges( Range.range(BIGINT, 1L, true, 3L, true), @@ -269,8 +267,7 @@ public void testInOptimization() false); tupleDomain = tupleDomain(C_BIGINT, testDomain); - assertEquals(toPredicate(tupleDomain), - or(between(C_BIGINT, bigintLiteral(1L), bigintLiteral(3L)), between(C_BIGINT, bigintLiteral(5L), bigintLiteral(7L)), between(C_BIGINT, bigintLiteral(9L), bigintLiteral(11L)))); + assertThat(toPredicate(tupleDomain)).isEqualTo(or(between(C_BIGINT, bigintLiteral(1L), bigintLiteral(3L)), between(C_BIGINT, bigintLiteral(5L), bigintLiteral(7L)), between(C_BIGINT, bigintLiteral(9L), bigintLiteral(11L)))); testDomain = Domain.create( ValueSet.ofRanges( @@ -280,7 +277,7 @@ public void testInOptimization() .union(ValueSet.ofRanges(Range.range(BIGINT, 7L, true, 9L, true))), false); tupleDomain = tupleDomain(C_BIGINT, testDomain); - assertEquals(toPredicate(tupleDomain), or(and(lessThan(C_BIGINT, bigintLiteral(4L)), not(in(C_BIGINT, ImmutableList.of(1L, 2L, 3L)))), between(C_BIGINT, bigintLiteral(7L), bigintLiteral(9L)))); + assertThat(toPredicate(tupleDomain)).isEqualTo(or(and(lessThan(C_BIGINT, bigintLiteral(4L)), not(in(C_BIGINT, ImmutableList.of(1L, 2L, 3L)))), between(C_BIGINT, bigintLiteral(7L), bigintLiteral(9L)))); testDomain = Domain.create( ValueSet.ofRanges(Range.lessThan(BIGINT, 4L)) @@ -289,7 +286,7 @@ public void testInOptimization() .union(ValueSet.ofRanges(Range.range(BIGINT, 7L, false, 9L, false), Range.range(BIGINT, 11L, false, 13L, false))), false); tupleDomain = tupleDomain(C_BIGINT, testDomain); - assertEquals(toPredicate(tupleDomain), or( + assertThat(toPredicate(tupleDomain)).isEqualTo(or( and(lessThan(C_BIGINT, bigintLiteral(4L)), not(in(C_BIGINT, ImmutableList.of(1L, 2L, 3L)))), and(greaterThan(C_BIGINT, bigintLiteral(7L)), lessThan(C_BIGINT, bigintLiteral(9L))), and(greaterThan(C_BIGINT, bigintLiteral(11L)), lessThan(C_BIGINT, bigintLiteral(13L))))); @@ -305,7 +302,7 @@ public void testToPredicateNone() .put(C_BOOLEAN, Domain.none(BOOLEAN)) .buildOrThrow()); - assertEquals(toPredicate(tupleDomain), FALSE_LITERAL); + assertThat(toPredicate(tupleDomain)).isEqualTo(FALSE_LITERAL); } @Test @@ -319,8 +316,8 @@ public void testToPredicateAllIgnored() .buildOrThrow()); ExtractionResult result = fromPredicate(toPredicate(tupleDomain)); - assertEquals(result.getRemainingExpression(), TRUE_LITERAL); - assertEquals(result.getTupleDomain(), tupleDomain(ImmutableMap.builder() + assertThat(result.getRemainingExpression()).isEqualTo(TRUE_LITERAL); + assertThat(result.getTupleDomain()).isEqualTo(tupleDomain(ImmutableMap.builder() .put(C_BIGINT, Domain.singleValue(BIGINT, 1L)) .put(C_DOUBLE, Domain.onlyNull(DOUBLE)) .put(C_VARCHAR, Domain.notNull(VARCHAR)) @@ -333,52 +330,52 @@ public void testToPredicate() TupleDomain tupleDomain; tupleDomain = tupleDomain(C_BIGINT, Domain.notNull(BIGINT)); - assertEquals(toPredicate(tupleDomain), isNotNull(C_BIGINT)); + assertThat(toPredicate(tupleDomain)).isEqualTo(isNotNull(C_BIGINT)); tupleDomain = tupleDomain(C_BIGINT, Domain.onlyNull(BIGINT)); - assertEquals(toPredicate(tupleDomain), isNull(C_BIGINT)); + assertThat(toPredicate(tupleDomain)).isEqualTo(isNull(C_BIGINT)); tupleDomain = tupleDomain(C_BIGINT, Domain.none(BIGINT)); - assertEquals(toPredicate(tupleDomain), FALSE_LITERAL); + assertThat(toPredicate(tupleDomain)).isEqualTo(FALSE_LITERAL); tupleDomain = tupleDomain(C_BIGINT, Domain.all(BIGINT)); - assertEquals(toPredicate(tupleDomain), TRUE_LITERAL); + assertThat(toPredicate(tupleDomain)).isEqualTo(TRUE_LITERAL); tupleDomain = tupleDomain(C_BIGINT, Domain.create(ValueSet.ofRanges(Range.greaterThan(BIGINT, 1L)), false)); - assertEquals(toPredicate(tupleDomain), greaterThan(C_BIGINT, bigintLiteral(1L))); + assertThat(toPredicate(tupleDomain)).isEqualTo(greaterThan(C_BIGINT, bigintLiteral(1L))); tupleDomain = tupleDomain(C_BIGINT, Domain.create(ValueSet.ofRanges(Range.greaterThanOrEqual(BIGINT, 1L)), false)); - assertEquals(toPredicate(tupleDomain), greaterThanOrEqual(C_BIGINT, bigintLiteral(1L))); + assertThat(toPredicate(tupleDomain)).isEqualTo(greaterThanOrEqual(C_BIGINT, bigintLiteral(1L))); tupleDomain = tupleDomain(C_BIGINT, Domain.create(ValueSet.ofRanges(Range.lessThan(BIGINT, 1L)), false)); - assertEquals(toPredicate(tupleDomain), lessThan(C_BIGINT, bigintLiteral(1L))); + assertThat(toPredicate(tupleDomain)).isEqualTo(lessThan(C_BIGINT, bigintLiteral(1L))); tupleDomain = tupleDomain(C_BIGINT, Domain.create(ValueSet.ofRanges(Range.range(BIGINT, 0L, false, 1L, true)), false)); - assertEquals(toPredicate(tupleDomain), and(greaterThan(C_BIGINT, bigintLiteral(0L)), lessThanOrEqual(C_BIGINT, bigintLiteral(1L)))); + assertThat(toPredicate(tupleDomain)).isEqualTo(and(greaterThan(C_BIGINT, bigintLiteral(0L)), lessThanOrEqual(C_BIGINT, bigintLiteral(1L)))); tupleDomain = tupleDomain(C_BIGINT, Domain.create(ValueSet.ofRanges(Range.lessThanOrEqual(BIGINT, 1L)), false)); - assertEquals(toPredicate(tupleDomain), lessThanOrEqual(C_BIGINT, bigintLiteral(1L))); + assertThat(toPredicate(tupleDomain)).isEqualTo(lessThanOrEqual(C_BIGINT, bigintLiteral(1L))); tupleDomain = tupleDomain(C_BIGINT, Domain.singleValue(BIGINT, 1L)); - assertEquals(toPredicate(tupleDomain), equal(C_BIGINT, bigintLiteral(1L))); + assertThat(toPredicate(tupleDomain)).isEqualTo(equal(C_BIGINT, bigintLiteral(1L))); tupleDomain = tupleDomain(C_BIGINT, Domain.create(ValueSet.ofRanges(Range.equal(BIGINT, 1L), Range.equal(BIGINT, 2L)), false)); - assertEquals(toPredicate(tupleDomain), in(C_BIGINT, ImmutableList.of(1L, 2L))); + assertThat(toPredicate(tupleDomain)).isEqualTo(in(C_BIGINT, ImmutableList.of(1L, 2L))); tupleDomain = tupleDomain(C_BIGINT, Domain.create(ValueSet.ofRanges(Range.lessThan(BIGINT, 1L)), true)); - assertEquals(toPredicate(tupleDomain), or(lessThan(C_BIGINT, bigintLiteral(1L)), isNull(C_BIGINT))); + assertThat(toPredicate(tupleDomain)).isEqualTo(or(lessThan(C_BIGINT, bigintLiteral(1L)), isNull(C_BIGINT))); tupleDomain = tupleDomain(C_COLOR, Domain.create(ValueSet.of(COLOR, COLOR_VALUE_1), true)); - assertEquals(toPredicate(tupleDomain), or(equal(C_COLOR, colorLiteral(COLOR_VALUE_1)), isNull(C_COLOR))); + assertThat(toPredicate(tupleDomain)).isEqualTo(or(equal(C_COLOR, colorLiteral(COLOR_VALUE_1)), isNull(C_COLOR))); tupleDomain = tupleDomain(C_COLOR, Domain.create(ValueSet.of(COLOR, COLOR_VALUE_1).complement(), true)); - assertEquals(toPredicate(tupleDomain), or(not(equal(C_COLOR, colorLiteral(COLOR_VALUE_1))), isNull(C_COLOR))); + assertThat(toPredicate(tupleDomain)).isEqualTo(or(not(equal(C_COLOR, colorLiteral(COLOR_VALUE_1))), isNull(C_COLOR))); tupleDomain = tupleDomain(C_HYPER_LOG_LOG, Domain.onlyNull(HYPER_LOG_LOG)); - assertEquals(toPredicate(tupleDomain), isNull(C_HYPER_LOG_LOG)); + assertThat(toPredicate(tupleDomain)).isEqualTo(isNull(C_HYPER_LOG_LOG)); tupleDomain = tupleDomain(C_HYPER_LOG_LOG, Domain.notNull(HYPER_LOG_LOG)); - assertEquals(toPredicate(tupleDomain), isNotNull(C_HYPER_LOG_LOG)); + assertThat(toPredicate(tupleDomain)).isEqualTo(isNotNull(C_HYPER_LOG_LOG)); } @Test @@ -387,7 +384,7 @@ public void testToPredicateWithRangeOptimisation() TupleDomain tupleDomain; tupleDomain = tupleDomain(C_BIGINT, Domain.create(ValueSet.ofRanges(Range.greaterThan(BIGINT, 1L), Range.lessThan(BIGINT, 1L)), false)); - assertEquals(toPredicate(tupleDomain), notEqual(C_BIGINT, bigintLiteral(1L))); + assertThat(toPredicate(tupleDomain)).isEqualTo(notEqual(C_BIGINT, bigintLiteral(1L))); tupleDomain = tupleDomain(C_BIGINT, Domain.create( ValueSet.ofRanges( @@ -395,7 +392,7 @@ public void testToPredicateWithRangeOptimisation() Range.range(BIGINT, 0L, false, 1L, false), Range.greaterThan(BIGINT, 1L)), false)); - assertEquals(toPredicate(tupleDomain), not(in(C_BIGINT, ImmutableList.of(0L, 1L)))); + assertThat(toPredicate(tupleDomain)).isEqualTo(not(in(C_BIGINT, ImmutableList.of(0L, 1L)))); tupleDomain = tupleDomain(C_BIGINT, Domain.create( ValueSet.ofRanges( @@ -403,11 +400,11 @@ public void testToPredicateWithRangeOptimisation() Range.range(BIGINT, 0L, false, 1L, false), Range.greaterThan(BIGINT, 2L)), false)); - assertEquals(toPredicate(tupleDomain), or(and(lessThan(C_BIGINT, bigintLiteral(1L)), notEqual(C_BIGINT, bigintLiteral(0L))), greaterThan(C_BIGINT, bigintLiteral(2L)))); + assertThat(toPredicate(tupleDomain)).isEqualTo(or(and(lessThan(C_BIGINT, bigintLiteral(1L)), notEqual(C_BIGINT, bigintLiteral(0L))), greaterThan(C_BIGINT, bigintLiteral(2L)))); // floating point types: do not coalesce ranges when range "all" would be introduced tupleDomain = tupleDomain(C_REAL, Domain.create(ValueSet.ofRanges(Range.greaterThan(REAL, 0L), Range.lessThan(REAL, 0L)), false)); - assertEquals(toPredicate(tupleDomain), or(lessThan(C_REAL, realLiteral("0.0")), greaterThan(C_REAL, realLiteral("0.0")))); + assertThat(toPredicate(tupleDomain)).isEqualTo(or(lessThan(C_REAL, realLiteral("0.0")), greaterThan(C_REAL, realLiteral("0.0")))); tupleDomain = tupleDomain(C_REAL, Domain.create( ValueSet.ofRanges( @@ -415,7 +412,7 @@ public void testToPredicateWithRangeOptimisation() Range.range(REAL, 0L, false, (long) Float.floatToIntBits(1F), false), Range.greaterThan(REAL, (long) Float.floatToIntBits(1F))), false)); - assertEquals(toPredicate(tupleDomain), or( + assertThat(toPredicate(tupleDomain)).isEqualTo(or( lessThan(C_REAL, realLiteral("0.0")), and(greaterThan(C_REAL, realLiteral("0.0")), lessThan(C_REAL, realLiteral("1.0"))), greaterThan(C_REAL, realLiteral("1.0")))); @@ -426,7 +423,7 @@ public void testToPredicateWithRangeOptimisation() Range.range(REAL, 0L, false, (long) Float.floatToIntBits(1F), false), Range.greaterThan(REAL, (long) Float.floatToIntBits(2F))), false)); - assertEquals(toPredicate(tupleDomain), or(and(lessThan(C_REAL, realLiteral("1.0")), notEqual(C_REAL, realLiteral("0.0"))), greaterThan(C_REAL, realLiteral("2.0")))); + assertThat(toPredicate(tupleDomain)).isEqualTo(or(and(lessThan(C_REAL, realLiteral("1.0")), notEqual(C_REAL, realLiteral("0.0"))), greaterThan(C_REAL, realLiteral("2.0")))); tupleDomain = tupleDomain(C_DOUBLE, Domain.create( ValueSet.ofRanges( @@ -435,11 +432,9 @@ public void testToPredicateWithRangeOptimisation() Range.range(DOUBLE, 2.0, false, 3.0, false), Range.greaterThan(DOUBLE, 3.0)), false)); - assertEquals( - toPredicate(tupleDomain), - or( - and(lessThan(C_DOUBLE, doubleLiteral(1)), notEqual(C_DOUBLE, doubleLiteral(0))), - and(greaterThan(C_DOUBLE, doubleLiteral(2)), notEqual(C_DOUBLE, doubleLiteral(3))))); + assertThat(toPredicate(tupleDomain)).isEqualTo(or( + and(lessThan(C_DOUBLE, doubleLiteral(1)), notEqual(C_DOUBLE, doubleLiteral(0))), + and(greaterThan(C_DOUBLE, doubleLiteral(2)), notEqual(C_DOUBLE, doubleLiteral(3))))); } @Test @@ -456,8 +451,8 @@ public void testFromAndPredicate() and(greaterThan(C_BIGINT, bigintLiteral(1L)), unprocessableExpression1(C_BIGINT)), and(lessThan(C_BIGINT, bigintLiteral(5L)), unprocessableExpression2(C_BIGINT))); ExtractionResult result = fromPredicate(originalPredicate); - assertEquals(result.getRemainingExpression(), and(unprocessableExpression1(C_BIGINT), unprocessableExpression2(C_BIGINT))); - assertEquals(result.getTupleDomain(), tupleDomain(C_BIGINT, Domain.create(ValueSet.ofRanges(Range.range(BIGINT, 1L, false, 5L, false)), false))); + assertThat(result.getRemainingExpression()).isEqualTo(and(unprocessableExpression1(C_BIGINT), unprocessableExpression2(C_BIGINT))); + assertThat(result.getTupleDomain()).isEqualTo(tupleDomain(C_BIGINT, Domain.create(ValueSet.ofRanges(Range.range(BIGINT, 1L, false, 5L, false)), false))); // Test complements assertUnsupportedPredicate(not(and( @@ -468,8 +463,8 @@ public void testFromAndPredicate() not(and(greaterThan(C_BIGINT, bigintLiteral(1L)), unprocessableExpression1(C_BIGINT))), not(and(lessThan(C_BIGINT, bigintLiteral(5L)), unprocessableExpression2(C_BIGINT))))); result = fromPredicate(originalPredicate); - assertEquals(result.getRemainingExpression(), originalPredicate); - assertEquals(result.getTupleDomain(), tupleDomain(C_BIGINT, Domain.notNull(BIGINT))); + assertThat(result.getRemainingExpression()).isEqualTo(originalPredicate); + assertThat(result.getTupleDomain()).isEqualTo(tupleDomain(C_BIGINT, Domain.notNull(BIGINT))); } @Test @@ -479,22 +474,22 @@ public void testFromOrPredicate() and(greaterThan(C_BIGINT, bigintLiteral(1L)), unprocessableExpression1(C_BIGINT)), and(lessThan(C_BIGINT, bigintLiteral(5L)), unprocessableExpression2(C_BIGINT))); ExtractionResult result = fromPredicate(originalPredicate); - assertEquals(result.getRemainingExpression(), originalPredicate); - assertEquals(result.getTupleDomain(), tupleDomain(C_BIGINT, Domain.notNull(BIGINT))); + assertThat(result.getRemainingExpression()).isEqualTo(originalPredicate); + assertThat(result.getTupleDomain()).isEqualTo(tupleDomain(C_BIGINT, Domain.notNull(BIGINT))); originalPredicate = or( and(equal(C_BIGINT, bigintLiteral(1L)), unprocessableExpression1(C_BIGINT)), and(equal(C_BIGINT, bigintLiteral(2L)), unprocessableExpression2(C_BIGINT))); result = fromPredicate(originalPredicate); - assertEquals(result.getRemainingExpression(), originalPredicate); - assertEquals(result.getTupleDomain(), tupleDomain(C_BIGINT, Domain.create(ValueSet.ofRanges(Range.equal(BIGINT, 1L), Range.equal(BIGINT, 2L)), false))); + assertThat(result.getRemainingExpression()).isEqualTo(originalPredicate); + assertThat(result.getTupleDomain()).isEqualTo(tupleDomain(C_BIGINT, Domain.create(ValueSet.ofRanges(Range.equal(BIGINT, 1L), Range.equal(BIGINT, 2L)), false))); originalPredicate = or( and(lessThan(C_BIGINT, bigintLiteral(20L)), unprocessableExpression1(C_BIGINT)), and(greaterThan(C_BIGINT, bigintLiteral(10L)), unprocessableExpression2(C_BIGINT))); result = fromPredicate(originalPredicate); - assertEquals(result.getRemainingExpression(), originalPredicate); - assertEquals(result.getTupleDomain(), tupleDomain(C_BIGINT, Domain.create(ValueSet.all(BIGINT), false))); + assertThat(result.getRemainingExpression()).isEqualTo(originalPredicate); + assertThat(result.getTupleDomain()).isEqualTo(tupleDomain(C_BIGINT, Domain.create(ValueSet.all(BIGINT), false))); // Same unprocessableExpression means that we can do more extraction // If both sides are operating on the same single symbol @@ -502,8 +497,8 @@ public void testFromOrPredicate() and(equal(C_BIGINT, bigintLiteral(1L)), unprocessableExpression1(C_BIGINT)), and(equal(C_BIGINT, bigintLiteral(2L)), unprocessableExpression1(C_BIGINT))); result = fromPredicate(originalPredicate); - assertEquals(result.getRemainingExpression(), unprocessableExpression1(C_BIGINT)); - assertEquals(result.getTupleDomain(), tupleDomain(C_BIGINT, Domain.create(ValueSet.ofRanges(Range.equal(BIGINT, 1L), Range.equal(BIGINT, 2L)), false))); + assertThat(result.getRemainingExpression()).isEqualTo(unprocessableExpression1(C_BIGINT)); + assertThat(result.getTupleDomain()).isEqualTo(tupleDomain(C_BIGINT, Domain.create(ValueSet.ofRanges(Range.equal(BIGINT, 1L), Range.equal(BIGINT, 2L)), false))); // And not if they have different symbols assertUnsupportedPredicate(or( @@ -517,38 +512,38 @@ public void testFromOrPredicate() greaterThan(C_DOUBLE, doubleLiteral(2.0)), lessThan(C_DOUBLE, doubleLiteral(5.0))); result = fromPredicate(originalPredicate); - assertEquals(result.getRemainingExpression(), originalPredicate); - assertEquals(result.getTupleDomain(), tupleDomain(C_DOUBLE, Domain.notNull(DOUBLE))); + assertThat(result.getRemainingExpression()).isEqualTo(originalPredicate); + assertThat(result.getTupleDomain()).isEqualTo(tupleDomain(C_DOUBLE, Domain.notNull(DOUBLE))); originalPredicate = or( greaterThan(C_REAL, realLiteral("2.0")), lessThan(C_REAL, realLiteral("5.0")), isNull(C_REAL)); result = fromPredicate(originalPredicate); - assertEquals(result.getRemainingExpression(), originalPredicate); - assertEquals(result.getTupleDomain(), TupleDomain.all()); + assertThat(result.getRemainingExpression()).isEqualTo(originalPredicate); + assertThat(result.getTupleDomain()).isEqualTo(TupleDomain.all()); originalPredicate = or( and(greaterThan(C_DOUBLE, doubleLiteral(2.0)), unprocessableExpression1(C_DOUBLE)), and(lessThan(C_DOUBLE, doubleLiteral(5.0)), unprocessableExpression1(C_DOUBLE))); result = fromPredicate(originalPredicate); - assertEquals(result.getRemainingExpression(), originalPredicate); - assertEquals(result.getTupleDomain(), tupleDomain(C_DOUBLE, Domain.notNull(DOUBLE))); + assertThat(result.getRemainingExpression()).isEqualTo(originalPredicate); + assertThat(result.getTupleDomain()).isEqualTo(tupleDomain(C_DOUBLE, Domain.notNull(DOUBLE))); originalPredicate = or( and(greaterThan(C_REAL, realLiteral("2.0")), unprocessableExpression1(C_REAL)), and(lessThan(C_REAL, realLiteral("5.0")), unprocessableExpression1(C_REAL))); result = fromPredicate(originalPredicate); - assertEquals(result.getRemainingExpression(), originalPredicate); - assertEquals(result.getTupleDomain(), tupleDomain(C_REAL, Domain.notNull(REAL))); + assertThat(result.getRemainingExpression()).isEqualTo(originalPredicate); + assertThat(result.getTupleDomain()).isEqualTo(tupleDomain(C_REAL, Domain.notNull(REAL))); // We can make another optimization if one side is the super set of the other side originalPredicate = or( and(greaterThan(C_BIGINT, bigintLiteral(1L)), greaterThan(C_DOUBLE, doubleLiteral(1.0)), unprocessableExpression1(C_BIGINT)), and(greaterThan(C_BIGINT, bigintLiteral(2L)), greaterThan(C_DOUBLE, doubleLiteral(2.0)), unprocessableExpression1(C_BIGINT))); result = fromPredicate(originalPredicate); - assertEquals(result.getRemainingExpression(), unprocessableExpression1(C_BIGINT)); - assertEquals(result.getTupleDomain(), tupleDomain( + assertThat(result.getRemainingExpression()).isEqualTo(unprocessableExpression1(C_BIGINT)); + assertThat(result.getTupleDomain()).isEqualTo(tupleDomain( C_BIGINT, Domain.create(ValueSet.ofRanges(Range.greaterThan(BIGINT, 1L)), false), C_DOUBLE, Domain.create(ValueSet.ofRanges(Range.greaterThan(DOUBLE, 1.0)), false))); @@ -557,25 +552,25 @@ public void testFromOrPredicate() and(equal(C_BIGINT, bigintLiteral(1L)), randPredicate(C_BIGINT, BIGINT)), and(equal(C_BIGINT, bigintLiteral(2L)), randPredicate(C_BIGINT, BIGINT))); result = fromPredicate(originalPredicate); - assertEquals(result.getRemainingExpression(), originalPredicate); - assertEquals(result.getTupleDomain(), tupleDomain(C_BIGINT, Domain.create(ValueSet.ofRanges(Range.equal(BIGINT, 1L), Range.equal(BIGINT, 2L)), false))); + assertThat(result.getRemainingExpression()).isEqualTo(originalPredicate); + assertThat(result.getTupleDomain()).isEqualTo(tupleDomain(C_BIGINT, Domain.create(ValueSet.ofRanges(Range.equal(BIGINT, 1L), Range.equal(BIGINT, 2L)), false))); // Test complements originalPredicate = not(or( and(greaterThan(C_BIGINT, bigintLiteral(1L)), unprocessableExpression1(C_BIGINT)), and(lessThan(C_BIGINT, bigintLiteral(5L)), unprocessableExpression2(C_BIGINT)))); result = fromPredicate(originalPredicate); - assertEquals(result.getRemainingExpression(), and( + assertThat(result.getRemainingExpression()).isEqualTo(and( not(and(greaterThan(C_BIGINT, bigintLiteral(1L)), unprocessableExpression1(C_BIGINT))), not(and(lessThan(C_BIGINT, bigintLiteral(5L)), unprocessableExpression2(C_BIGINT))))); - assertTrue(result.getTupleDomain().isAll()); + assertThat(result.getTupleDomain().isAll()).isTrue(); originalPredicate = not(or( not(and(greaterThan(C_BIGINT, bigintLiteral(1L)), unprocessableExpression1(C_BIGINT))), not(and(lessThan(C_BIGINT, bigintLiteral(5L)), unprocessableExpression2(C_BIGINT))))); result = fromPredicate(originalPredicate); - assertEquals(result.getRemainingExpression(), and(unprocessableExpression1(C_BIGINT), unprocessableExpression2(C_BIGINT))); - assertEquals(result.getTupleDomain(), tupleDomain(C_BIGINT, Domain.create(ValueSet.ofRanges(Range.range(BIGINT, 1L, false, 5L, false)), false))); + assertThat(result.getRemainingExpression()).isEqualTo(and(unprocessableExpression1(C_BIGINT), unprocessableExpression2(C_BIGINT))); + assertThat(result.getTupleDomain()).isEqualTo(tupleDomain(C_BIGINT, Domain.create(ValueSet.ofRanges(Range.range(BIGINT, 1L, false, 5L, false)), false))); } @Test @@ -583,29 +578,29 @@ public void testFromSingleBooleanReference() { Expression originalPredicate = C_BOOLEAN.toSymbolReference(); ExtractionResult result = fromPredicate(originalPredicate); - assertEquals(result.getTupleDomain(), tupleDomain(C_BOOLEAN, Domain.create(ValueSet.ofRanges(Range.equal(BOOLEAN, true)), false))); - assertEquals(result.getRemainingExpression(), TRUE_LITERAL); + assertThat(result.getTupleDomain()).isEqualTo(tupleDomain(C_BOOLEAN, Domain.create(ValueSet.ofRanges(Range.equal(BOOLEAN, true)), false))); + assertThat(result.getRemainingExpression()).isEqualTo(TRUE_LITERAL); originalPredicate = not(C_BOOLEAN.toSymbolReference()); result = fromPredicate(originalPredicate); - assertEquals(result.getTupleDomain(), tupleDomain(C_BOOLEAN, Domain.create(ValueSet.ofRanges(Range.equal(BOOLEAN, true)).complement(), false))); - assertEquals(result.getRemainingExpression(), TRUE_LITERAL); + assertThat(result.getTupleDomain()).isEqualTo(tupleDomain(C_BOOLEAN, Domain.create(ValueSet.ofRanges(Range.equal(BOOLEAN, true)).complement(), false))); + assertThat(result.getRemainingExpression()).isEqualTo(TRUE_LITERAL); originalPredicate = and(C_BOOLEAN.toSymbolReference(), C_BOOLEAN_1.toSymbolReference()); result = fromPredicate(originalPredicate); Domain domain = Domain.create(ValueSet.ofRanges(Range.equal(BOOLEAN, true)), false); - assertEquals(result.getTupleDomain(), tupleDomain(C_BOOLEAN, domain, C_BOOLEAN_1, domain)); - assertEquals(result.getRemainingExpression(), TRUE_LITERAL); + assertThat(result.getTupleDomain()).isEqualTo(tupleDomain(C_BOOLEAN, domain, C_BOOLEAN_1, domain)); + assertThat(result.getRemainingExpression()).isEqualTo(TRUE_LITERAL); originalPredicate = or(C_BOOLEAN.toSymbolReference(), C_BOOLEAN_1.toSymbolReference()); result = fromPredicate(originalPredicate); - assertEquals(result.getTupleDomain(), TupleDomain.all()); - assertEquals(result.getRemainingExpression(), originalPredicate); + assertThat(result.getTupleDomain()).isEqualTo(TupleDomain.all()); + assertThat(result.getRemainingExpression()).isEqualTo(originalPredicate); originalPredicate = not(and(C_BOOLEAN.toSymbolReference(), C_BOOLEAN_1.toSymbolReference())); result = fromPredicate(originalPredicate); - assertEquals(result.getTupleDomain(), TupleDomain.all()); - assertEquals(result.getRemainingExpression(), originalPredicate); + assertThat(result.getTupleDomain()).isEqualTo(TupleDomain.all()); + assertThat(result.getRemainingExpression()).isEqualTo(originalPredicate); } @Test @@ -1540,12 +1535,12 @@ public void testExpressionConstantFolding() .build(); Expression originalExpression = comparison(GREATER_THAN, C_VARBINARY.toSymbolReference(), fromHex); ExtractionResult result = fromPredicate(originalExpression); - assertEquals(result.getRemainingExpression(), TRUE_LITERAL); + assertThat(result.getRemainingExpression()).isEqualTo(TRUE_LITERAL); Slice value = Slices.wrappedBuffer(BaseEncoding.base16().decode("123456")); - assertEquals(result.getTupleDomain(), tupleDomain(C_VARBINARY, Domain.create(ValueSet.ofRanges(Range.greaterThan(VARBINARY, value)), false))); + assertThat(result.getTupleDomain()).isEqualTo(tupleDomain(C_VARBINARY, Domain.create(ValueSet.ofRanges(Range.greaterThan(VARBINARY, value)), false))); Expression expression = toPredicate(result.getTupleDomain()); - assertEquals(expression, comparison(GREATER_THAN, C_VARBINARY.toSymbolReference(), varbinaryLiteral(value))); + assertThat(expression).isEqualTo(comparison(GREATER_THAN, C_VARBINARY.toSymbolReference(), varbinaryLiteral(value))); } @Test @@ -1560,11 +1555,9 @@ public void testConjunctExpression() C_DOUBLE, Domain.create(ValueSet.ofRanges(Range.greaterThan(DOUBLE, .0)), false), C_BIGINT, Domain.create(ValueSet.ofRanges(Range.greaterThan(BIGINT, 0L)), false))); - assertEquals( - toPredicate(fromPredicate(expression).getTupleDomain()), - and( - comparison(GREATER_THAN, C_DOUBLE.toSymbolReference(), doubleLiteral(0)), - comparison(GREATER_THAN, C_BIGINT.toSymbolReference(), bigintLiteral(0)))); + assertThat(toPredicate(fromPredicate(expression).getTupleDomain())).isEqualTo(and( + comparison(GREATER_THAN, C_DOUBLE.toSymbolReference(), doubleLiteral(0)), + comparison(GREATER_THAN, C_BIGINT.toSymbolReference(), bigintLiteral(0)))); } @Test @@ -2065,14 +2058,15 @@ private void assertPredicateDerives(Expression expression, TupleDomain t private void assertPredicateTranslates(Expression expression, TupleDomain tupleDomain, Expression remainingExpression) { ExtractionResult result = fromPredicate(expression); - assertEquals(result.getTupleDomain(), tupleDomain); - assertEquals(result.getRemainingExpression(), remainingExpression); + assertThat(result.getTupleDomain()).isEqualTo(tupleDomain); + assertThat(result.getRemainingExpression()).isEqualTo(remainingExpression); } private void assertNoFullPushdown(Expression expression) { ExtractionResult result = fromPredicate(expression); - assertNotEquals(result.getRemainingExpression(), TRUE_LITERAL); + assertThat(result.getRemainingExpression()) + .isNotEqualTo(TRUE_LITERAL); } private ExtractionResult fromPredicate(Expression originalPredicate) @@ -2348,7 +2342,7 @@ private void testSimpleComparison(Expression expression, Symbol symbol, Domain e private void testSimpleComparison(Expression expression, Symbol symbol, Expression expectedRemainingExpression, Domain expectedDomain) { ExtractionResult result = fromPredicate(expression); - assertEquals(result.getRemainingExpression(), expectedRemainingExpression); + assertThat(result.getRemainingExpression()).isEqualTo(expectedRemainingExpression); TupleDomain actual = result.getTupleDomain(); TupleDomain expected = tupleDomain(symbol, expectedDomain); if (!actual.equals(expected)) { diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestEffectivePredicateExtractor.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestEffectivePredicateExtractor.java index 8b01be00a3e8..32a3e4bc0321 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestEffectivePredicateExtractor.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestEffectivePredicateExtractor.java @@ -58,7 +58,6 @@ import io.trino.sql.planner.plan.ValuesNode; import io.trino.sql.planner.plan.WindowNode; import io.trino.sql.tree.BetweenPredicate; -import io.trino.sql.tree.BooleanLiteral; import io.trino.sql.tree.Cast; import io.trino.sql.tree.ComparisonExpression; import io.trino.sql.tree.DoubleLiteral; @@ -115,7 +114,7 @@ import static io.trino.tests.BogusType.BOGUS; import static io.trino.transaction.TransactionBuilder.transaction; import static io.trino.type.UnknownType.UNKNOWN; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; @Test(singleThreaded = true) public class TestEffectivePredicateExtractor @@ -237,13 +236,11 @@ D, new Aggregation( Expression effectivePredicate = effectivePredicateExtractor.extract(SESSION, node, TypeProvider.empty(), typeAnalyzer); // Rewrite in terms of group by symbols - assertEquals( - normalizeConjuncts(effectivePredicate), - normalizeConjuncts( - lessThan(AE, bigintLiteral(10)), - lessThan(BE, AE), - greaterThan(AE, bigintLiteral(2)), - equals(BE, CE))); + assertThat(normalizeConjuncts(effectivePredicate)).isEqualTo(normalizeConjuncts( + lessThan(AE, bigintLiteral(10)), + lessThan(BE, AE), + greaterThan(AE, bigintLiteral(2)), + equals(BE, CE))); } @Test @@ -261,7 +258,7 @@ public void testGroupByEmpty() Expression effectivePredicate = effectivePredicateExtractor.extract(SESSION, node, TypeProvider.empty(), typeAnalyzer); - assertEquals(effectivePredicate, TRUE_LITERAL); + assertThat(effectivePredicate).isEqualTo(TRUE_LITERAL); } @Test @@ -280,9 +277,7 @@ public void testFilter() Expression effectivePredicate = effectivePredicateExtractor.extract(SESSION, node, TypeProvider.empty(), typeAnalyzer); // Non-deterministic functions should be purged - assertEquals( - normalizeConjuncts(effectivePredicate), - normalizeConjuncts(lessThan(BE, bigintLiteral(10)))); + assertThat(normalizeConjuncts(effectivePredicate)).isEqualTo(normalizeConjuncts(lessThan(BE, bigintLiteral(10)))); } @Test @@ -301,11 +296,9 @@ public void testProject() Expression effectivePredicate = effectivePredicateExtractor.extract(SESSION, node, TypeProvider.empty(), typeAnalyzer); // Rewrite in terms of project output symbols - assertEquals( - normalizeConjuncts(effectivePredicate), - normalizeConjuncts( - lessThan(DE, bigintLiteral(10)), - equals(DE, EE))); + assertThat(normalizeConjuncts(effectivePredicate)).isEqualTo(normalizeConjuncts( + lessThan(DE, bigintLiteral(10)), + equals(DE, EE))); } @Test @@ -325,9 +318,7 @@ public void testProjectWithSymbolReuse() Expression effectivePredicateWhenBReused = effectivePredicateExtractor.extract(SESSION, projectReusingB, TypeProvider.empty(), typeAnalyzer); - assertEquals( - normalizeConjuncts(effectivePredicateWhenBReused), - normalizeConjuncts(lessThan(BE, bigintLiteral(10)))); + assertThat(normalizeConjuncts(effectivePredicateWhenBReused)).isEqualTo(normalizeConjuncts(lessThan(BE, bigintLiteral(10)))); // symbol C is reused so underlying predicates involving CE are invalid after Projection // and will not be included in the resulting predicate @@ -348,9 +339,7 @@ public void testProjectWithSymbolReuse() Expression effectivePredicateWhenCReused = effectivePredicateExtractor.extract(SESSION, projectReusingC, TypeProvider.empty(), typeAnalyzer); - assertEquals( - normalizeConjuncts(effectivePredicateWhenCReused), - normalizeConjuncts(normalizeConjuncts(equals(CE, FE)))); + assertThat(normalizeConjuncts(effectivePredicateWhenCReused)).isEqualTo(normalizeConjuncts(normalizeConjuncts(equals(CE, FE)))); } @Test @@ -370,12 +359,10 @@ public void testTopN() Expression effectivePredicate = effectivePredicateExtractor.extract(SESSION, node, TypeProvider.empty(), typeAnalyzer); // Pass through - assertEquals( - normalizeConjuncts(effectivePredicate), - normalizeConjuncts( - equals(AE, BE), - equals(BE, CE), - lessThan(CE, bigintLiteral(10)))); + assertThat(normalizeConjuncts(effectivePredicate)).isEqualTo(normalizeConjuncts( + equals(AE, BE), + equals(BE, CE), + lessThan(CE, bigintLiteral(10)))); } @Test @@ -395,12 +382,10 @@ public void testLimit() Expression effectivePredicate = effectivePredicateExtractor.extract(SESSION, node, TypeProvider.empty(), typeAnalyzer); // Pass through - assertEquals( - normalizeConjuncts(effectivePredicate), - normalizeConjuncts( - equals(AE, BE), - equals(BE, CE), - lessThan(CE, bigintLiteral(10)))); + assertThat(normalizeConjuncts(effectivePredicate)).isEqualTo(normalizeConjuncts( + equals(AE, BE), + equals(BE, CE), + lessThan(CE, bigintLiteral(10)))); } @Test @@ -420,12 +405,10 @@ public void testSort() Expression effectivePredicate = effectivePredicateExtractor.extract(SESSION, node, TypeProvider.empty(), typeAnalyzer); // Pass through - assertEquals( - normalizeConjuncts(effectivePredicate), - normalizeConjuncts( - equals(AE, BE), - equals(BE, CE), - lessThan(CE, bigintLiteral(10)))); + assertThat(normalizeConjuncts(effectivePredicate)).isEqualTo(normalizeConjuncts( + equals(AE, BE), + equals(BE, CE), + lessThan(CE, bigintLiteral(10)))); } @Test @@ -452,12 +435,10 @@ public void testWindow() Expression effectivePredicate = effectivePredicateExtractor.extract(SESSION, node, TypeProvider.empty(), typeAnalyzer); // Pass through - assertEquals( - normalizeConjuncts(effectivePredicate), - normalizeConjuncts( - equals(AE, BE), - equals(BE, CE), - lessThan(CE, bigintLiteral(10)))); + assertThat(normalizeConjuncts(effectivePredicate)).isEqualTo(normalizeConjuncts( + equals(AE, BE), + equals(BE, CE), + lessThan(CE, bigintLiteral(10)))); } @Test @@ -473,7 +454,7 @@ public void testTableScan() false, Optional.empty()); Expression effectivePredicate = effectivePredicateExtractor.extract(SESSION, node, TypeProvider.empty(), typeAnalyzer); - assertEquals(effectivePredicate, BooleanLiteral.TRUE_LITERAL); + assertThat(effectivePredicate).isEqualTo(TRUE_LITERAL); node = new TableScanNode( newId(), @@ -485,7 +466,7 @@ public void testTableScan() false, Optional.empty()); effectivePredicate = effectivePredicateExtractor.extract(SESSION, node, TypeProvider.empty(), typeAnalyzer); - assertEquals(effectivePredicate, FALSE_LITERAL); + assertThat(effectivePredicate).isEqualTo(FALSE_LITERAL); TupleDomain predicate = TupleDomain.withColumnDomains(ImmutableMap.of(scanAssignments.get(A), Domain.singleValue(BIGINT, 1L))); node = new TableScanNode( @@ -498,7 +479,7 @@ public void testTableScan() false, Optional.empty()); effectivePredicate = effectivePredicateExtractor.extract(SESSION, node, TypeProvider.empty(), typeAnalyzer); - assertEquals(normalizeConjuncts(effectivePredicate), normalizeConjuncts(equals(bigintLiteral(1L), AE))); + assertThat(normalizeConjuncts(effectivePredicate)).isEqualTo(normalizeConjuncts(equals(bigintLiteral(1L), AE))); predicate = TupleDomain.withColumnDomains(ImmutableMap.of( scanAssignments.get(A), Domain.singleValue(BIGINT, 1L), @@ -513,7 +494,7 @@ public void testTableScan() false, Optional.empty()); effectivePredicate = effectivePredicateExtractorWithoutTableProperties.extract(SESSION, node, TypeProvider.empty(), typeAnalyzer); - assertEquals(normalizeConjuncts(effectivePredicate), normalizeConjuncts(equals(bigintLiteral(2L), BE), equals(bigintLiteral(1L), AE))); + assertThat(normalizeConjuncts(effectivePredicate)).isEqualTo(normalizeConjuncts(equals(bigintLiteral(2L), BE), equals(bigintLiteral(1L), AE))); node = new TableScanNode( newId(), @@ -525,7 +506,7 @@ public void testTableScan() false, Optional.empty()); effectivePredicate = effectivePredicateExtractor.extract(SESSION, node, TypeProvider.empty(), typeAnalyzer); - assertEquals(effectivePredicate, and(equals(AE, bigintLiteral(1)), equals(BE, bigintLiteral(2)))); + assertThat(effectivePredicate).isEqualTo(and(equals(AE, bigintLiteral(1)), equals(BE, bigintLiteral(2)))); node = new TableScanNode( newId(), @@ -539,7 +520,7 @@ public void testTableScan() false, Optional.empty()); effectivePredicate = effectivePredicateExtractor.extract(SESSION, node, TypeProvider.empty(), typeAnalyzer); - assertEquals(normalizeConjuncts(effectivePredicate), normalizeConjuncts(equals(bigintLiteral(2L), BE), equals(bigintLiteral(1L), AE))); + assertThat(normalizeConjuncts(effectivePredicate)).isEqualTo(normalizeConjuncts(equals(bigintLiteral(2L), BE), equals(bigintLiteral(1L), AE))); node = new TableScanNode( newId(), @@ -551,7 +532,7 @@ public void testTableScan() false, Optional.empty()); effectivePredicate = effectivePredicateExtractor.extract(SESSION, node, TypeProvider.empty(), typeAnalyzer); - assertEquals(effectivePredicate, BooleanLiteral.TRUE_LITERAL); + assertThat(effectivePredicate).isEqualTo(TRUE_LITERAL); } @Test @@ -566,59 +547,54 @@ public void testValues() .buildOrThrow()); // one column - assertEquals( - effectivePredicateExtractor.extract( - SESSION, - new ValuesNode( - newId(), - ImmutableList.of(A), - ImmutableList.of( - new Row(ImmutableList.of(bigintLiteral(1))), - new Row(ImmutableList.of(bigintLiteral(2))))), - types, - typeAnalyzer), - new InPredicate(AE, new InListExpression(ImmutableList.of(bigintLiteral(1), bigintLiteral(2))))); + assertThat(effectivePredicateExtractor.extract( + SESSION, + new ValuesNode( + newId(), + ImmutableList.of(A), + ImmutableList.of( + new Row(ImmutableList.of(bigintLiteral(1))), + new Row(ImmutableList.of(bigintLiteral(2))))), + types, + typeAnalyzer)).isEqualTo(new InPredicate(AE, new InListExpression(ImmutableList.of(bigintLiteral(1), bigintLiteral(2))))); // one column with null - assertEquals( - effectivePredicateExtractor.extract( - SESSION, - new ValuesNode( - newId(), - ImmutableList.of(A), - ImmutableList.of( - new Row(ImmutableList.of(bigintLiteral(1))), - new Row(ImmutableList.of(bigintLiteral(2))), - new Row(ImmutableList.of(new Cast(new NullLiteral(), toSqlType(BIGINT)))))), - types, - typeAnalyzer), - or( + assertThat(effectivePredicateExtractor.extract( + SESSION, + new ValuesNode( + newId(), + ImmutableList.of(A), + ImmutableList.of( + new Row(ImmutableList.of(bigintLiteral(1))), + new Row(ImmutableList.of(bigintLiteral(2))), + new Row(ImmutableList.of(new Cast(new NullLiteral(), toSqlType(BIGINT)))))), + types, + typeAnalyzer)) + .isEqualTo(or( new InPredicate(AE, new InListExpression(ImmutableList.of(bigintLiteral(1), bigintLiteral(2)))), new IsNullPredicate(AE))); // all nulls - assertEquals( - effectivePredicateExtractor.extract( - SESSION, - new ValuesNode( - newId(), - ImmutableList.of(A), - ImmutableList.of(new Row(ImmutableList.of(new Cast(new NullLiteral(), toSqlType(BIGINT)))))), - types, - typeAnalyzer), - new IsNullPredicate(AE)); + assertThat(effectivePredicateExtractor.extract( + SESSION, + new ValuesNode( + newId(), + ImmutableList.of(A), + ImmutableList.of(new Row(ImmutableList.of(new Cast(new NullLiteral(), toSqlType(BIGINT)))))), + types, + typeAnalyzer)) + .isEqualTo(new IsNullPredicate(AE)); // nested row - assertEquals( - effectivePredicateExtractor.extract( - SESSION, - new ValuesNode( - newId(), - ImmutableList.of(R), - ImmutableList.of(new Row(ImmutableList.of(new Row(ImmutableList.of(bigintLiteral(1), new NullLiteral())))))), - types, - typeAnalyzer), - TRUE_LITERAL); + assertThat(effectivePredicateExtractor.extract( + SESSION, + new ValuesNode( + newId(), + ImmutableList.of(R), + ImmutableList.of(new Row(ImmutableList.of(new Row(ImmutableList.of(bigintLiteral(1), new NullLiteral())))))), + types, + typeAnalyzer)) + .isEqualTo(TRUE_LITERAL); // many rows List rows = IntStream.range(0, 500) @@ -626,100 +602,87 @@ public void testValues() .map(ImmutableList::of) .map(Row::new) .collect(toImmutableList()); - assertEquals( - effectivePredicateExtractor.extract( - SESSION, - new ValuesNode( - newId(), - ImmutableList.of(A), - rows), - types, - typeAnalyzer), - new BetweenPredicate(AE, bigintLiteral(0), bigintLiteral(499))); + assertThat(effectivePredicateExtractor.extract( + SESSION, + new ValuesNode( + newId(), + ImmutableList.of(A), + rows), + types, + typeAnalyzer)).isEqualTo(new BetweenPredicate(AE, bigintLiteral(0), bigintLiteral(499))); // NaN - assertEquals( - effectivePredicateExtractor.extract( - SESSION, - new ValuesNode( - newId(), - ImmutableList.of(D), - ImmutableList.of(new Row(ImmutableList.of(doubleLiteral(Double.NaN))))), - types, - typeAnalyzer), - new NotExpression(new IsNullPredicate(DE))); + assertThat(effectivePredicateExtractor.extract( + SESSION, + new ValuesNode( + newId(), + ImmutableList.of(D), + ImmutableList.of(new Row(ImmutableList.of(doubleLiteral(Double.NaN))))), + types, + typeAnalyzer)).isEqualTo(new NotExpression(new IsNullPredicate(DE))); // NaN and NULL - assertEquals( - effectivePredicateExtractor.extract( - SESSION, - new ValuesNode( - newId(), - ImmutableList.of(D), - ImmutableList.of( - new Row(ImmutableList.of(new Cast(new NullLiteral(), toSqlType(DOUBLE)))), - new Row(ImmutableList.of(doubleLiteral(Double.NaN))))), - types, - typeAnalyzer), - TRUE_LITERAL); + assertThat(effectivePredicateExtractor.extract( + SESSION, + new ValuesNode( + newId(), + ImmutableList.of(D), + ImmutableList.of( + new Row(ImmutableList.of(new Cast(new NullLiteral(), toSqlType(DOUBLE)))), + new Row(ImmutableList.of(doubleLiteral(Double.NaN))))), + types, + typeAnalyzer)).isEqualTo(TRUE_LITERAL); // NaN and value - assertEquals( - effectivePredicateExtractor.extract( - SESSION, - new ValuesNode( - newId(), - ImmutableList.of(D), - ImmutableList.of( - new Row(ImmutableList.of(doubleLiteral(42.))), - new Row(ImmutableList.of(doubleLiteral(Double.NaN))))), - types, - typeAnalyzer), - new NotExpression(new IsNullPredicate(DE))); + assertThat(effectivePredicateExtractor.extract( + SESSION, + new ValuesNode( + newId(), + ImmutableList.of(D), + ImmutableList.of( + new Row(ImmutableList.of(doubleLiteral(42.))), + new Row(ImmutableList.of(doubleLiteral(Double.NaN))))), + types, + typeAnalyzer)).isEqualTo(new NotExpression(new IsNullPredicate(DE))); // Real NaN - assertEquals( - effectivePredicateExtractor.extract( - SESSION, - new ValuesNode( - newId(), - ImmutableList.of(D), - ImmutableList.of(new Row(ImmutableList.of(new Cast(doubleLiteral(Double.NaN), toSqlType(REAL)))))), - TypeProvider.copyOf(ImmutableMap.of(D, REAL)), - typeAnalyzer), - new NotExpression(new IsNullPredicate(DE))); + assertThat(effectivePredicateExtractor.extract( + SESSION, + new ValuesNode( + newId(), + ImmutableList.of(D), + ImmutableList.of(new Row(ImmutableList.of(new Cast(doubleLiteral(Double.NaN), toSqlType(REAL)))))), + TypeProvider.copyOf(ImmutableMap.of(D, REAL)), + typeAnalyzer)).isEqualTo(new NotExpression(new IsNullPredicate(DE))); // multiple columns - assertEquals( - effectivePredicateExtractor.extract( - SESSION, - new ValuesNode( - newId(), - ImmutableList.of(A, B), - ImmutableList.of( - new Row(ImmutableList.of(bigintLiteral(1), bigintLiteral(100))), - new Row(ImmutableList.of(bigintLiteral(2), bigintLiteral(200))))), - types, - typeAnalyzer), - and( + assertThat(effectivePredicateExtractor.extract( + SESSION, + new ValuesNode( + newId(), + ImmutableList.of(A, B), + ImmutableList.of( + new Row(ImmutableList.of(bigintLiteral(1), bigintLiteral(100))), + new Row(ImmutableList.of(bigintLiteral(2), bigintLiteral(200))))), + types, + typeAnalyzer)) + .isEqualTo(and( new InPredicate(AE, new InListExpression(ImmutableList.of(bigintLiteral(1), bigintLiteral(2)))), new InPredicate(BE, new InListExpression(ImmutableList.of(bigintLiteral(100), bigintLiteral(200)))))); // multiple columns with null - assertEquals( - effectivePredicateExtractor.extract( - SESSION, - new ValuesNode( - newId(), - ImmutableList.of(A, B), - ImmutableList.of( - new Row(ImmutableList.of(bigintLiteral(1), new Cast(new NullLiteral(), toSqlType(BIGINT)))), - new Row(ImmutableList.of(new Cast(new NullLiteral(), toSqlType(BIGINT)), bigintLiteral(200))))), - types, - typeAnalyzer), - and( - or(new ComparisonExpression(EQUAL, AE, bigintLiteral(1)), new IsNullPredicate(AE)), - or(new ComparisonExpression(EQUAL, BE, bigintLiteral(200)), new IsNullPredicate(BE)))); + assertThat(effectivePredicateExtractor.extract( + SESSION, + new ValuesNode( + newId(), + ImmutableList.of(A, B), + ImmutableList.of( + new Row(ImmutableList.of(bigintLiteral(1), new Cast(new NullLiteral(), toSqlType(BIGINT)))), + new Row(ImmutableList.of(new Cast(new NullLiteral(), toSqlType(BIGINT)), bigintLiteral(200))))), + types, + typeAnalyzer)).isEqualTo(and( + or(new ComparisonExpression(EQUAL, AE, bigintLiteral(1)), new IsNullPredicate(AE)), + or(new ComparisonExpression(EQUAL, BE, bigintLiteral(200)), new IsNullPredicate(BE)))); // non-deterministic ResolvedFunction rand = functionResolution.resolveFunction("rand", ImmutableList.of()); @@ -727,35 +690,31 @@ public void testValues() newId(), ImmutableList.of(A, B), ImmutableList.of(new Row(ImmutableList.of(bigintLiteral(1), new FunctionCall(rand.toQualifiedName(), ImmutableList.of()))))); - assertEquals(extract(types, node), new ComparisonExpression(EQUAL, AE, bigintLiteral(1))); + assertThat(extract(types, node)).isEqualTo(new ComparisonExpression(EQUAL, AE, bigintLiteral(1))); // non-constant - assertEquals( - effectivePredicateExtractor.extract( - SESSION, - new ValuesNode( - newId(), - ImmutableList.of(A), - ImmutableList.of( - new Row(ImmutableList.of(bigintLiteral(1))), - new Row(ImmutableList.of(BE)))), - types, - typeAnalyzer), - TRUE_LITERAL); + assertThat(effectivePredicateExtractor.extract( + SESSION, + new ValuesNode( + newId(), + ImmutableList.of(A), + ImmutableList.of( + new Row(ImmutableList.of(bigintLiteral(1))), + new Row(ImmutableList.of(BE)))), + types, + typeAnalyzer)).isEqualTo(TRUE_LITERAL); // non-comparable and non-orderable - assertEquals( - effectivePredicateExtractor.extract( - SESSION, - new ValuesNode( - newId(), - ImmutableList.of(G), - ImmutableList.of( - new Row(ImmutableList.of(bigintLiteral(1))), - new Row(ImmutableList.of(bigintLiteral(2))))), - types, - typeAnalyzer), - TRUE_LITERAL); + assertThat(effectivePredicateExtractor.extract( + SESSION, + new ValuesNode( + newId(), + ImmutableList.of(G), + ImmutableList.of( + new Row(ImmutableList.of(bigintLiteral(1))), + new Row(ImmutableList.of(bigintLiteral(2))))), + types, + typeAnalyzer)).isEqualTo(TRUE_LITERAL); } private Expression extract(TypeProvider types, PlanNode node) @@ -783,9 +742,7 @@ public void testUnion() Expression effectivePredicate = effectivePredicateExtractor.extract(SESSION, node, TypeProvider.empty(), typeAnalyzer); // Only the common conjuncts can be inferred through a Union - assertEquals( - normalizeConjuncts(effectivePredicate), - normalizeConjuncts(greaterThan(AE, bigintLiteral(10)))); + assertThat(normalizeConjuncts(effectivePredicate)).isEqualTo(normalizeConjuncts(greaterThan(AE, bigintLiteral(10)))); } @Test @@ -834,16 +791,14 @@ public void testInnerJoin() Expression effectivePredicate = effectivePredicateExtractor.extract(SESSION, node, TypeProvider.empty(), typeAnalyzer); // All predicates having output symbol should be carried through - assertEquals( - normalizeConjuncts(effectivePredicate), - normalizeConjuncts( - lessThan(BE, AE), - lessThan(CE, bigintLiteral(10)), - equals(DE, EE), - lessThan(FE, bigintLiteral(100)), - equals(AE, DE), - equals(BE, EE), - lessThanOrEqual(BE, EE))); + assertThat(normalizeConjuncts(effectivePredicate)).isEqualTo(normalizeConjuncts( + lessThan(BE, AE), + lessThan(CE, bigintLiteral(10)), + equals(DE, EE), + lessThan(FE, bigintLiteral(100)), + equals(AE, DE), + equals(BE, EE), + lessThanOrEqual(BE, EE))); } @Test @@ -877,9 +832,7 @@ public void testInnerJoinPropagatesPredicatesViaEquiConditions() Expression effectivePredicate = effectivePredicateExtractor.extract(SESSION, node, TypeProvider.empty(), typeAnalyzer); - assertEquals( - normalizeConjuncts(effectivePredicate), - normalizeConjuncts(equals(DE, bigintLiteral(10)))); + assertThat(normalizeConjuncts(effectivePredicate)).isEqualTo(normalizeConjuncts(equals(DE, bigintLiteral(10)))); } @Test @@ -910,7 +863,7 @@ public void testInnerJoinWithFalseFilter() Expression effectivePredicate = effectivePredicateExtractor.extract(SESSION, node, TypeProvider.empty(), typeAnalyzer); - assertEquals(effectivePredicate, FALSE_LITERAL); + assertThat(effectivePredicate).isEqualTo(FALSE_LITERAL); } @Test @@ -958,15 +911,13 @@ public void testLeftJoin() Expression effectivePredicate = effectivePredicateExtractor.extract(SESSION, node, TypeProvider.empty(), typeAnalyzer); // All right side symbols having output symbols should be checked against NULL - assertEquals( - normalizeConjuncts(effectivePredicate), - normalizeConjuncts( - lessThan(BE, AE), - lessThan(CE, bigintLiteral(10)), - or(equals(DE, EE), and(isNull(DE), isNull(EE))), - or(lessThan(FE, bigintLiteral(100)), isNull(FE)), - or(equals(AE, DE), isNull(DE)), - or(equals(BE, EE), isNull(EE)))); + assertThat(normalizeConjuncts(effectivePredicate)).isEqualTo(normalizeConjuncts( + lessThan(BE, AE), + lessThan(CE, bigintLiteral(10)), + or(equals(DE, EE), and(isNull(DE), isNull(EE))), + or(lessThan(FE, bigintLiteral(100)), isNull(FE)), + or(equals(AE, DE), isNull(DE)), + or(equals(BE, EE), isNull(EE)))); } @Test @@ -1007,12 +958,10 @@ public void testLeftJoinWithFalseInner() Expression effectivePredicate = effectivePredicateExtractor.extract(SESSION, node, TypeProvider.empty(), typeAnalyzer); // False literal on the right side should be ignored - assertEquals( - normalizeConjuncts(effectivePredicate), - normalizeConjuncts( - lessThan(BE, AE), - lessThan(CE, bigintLiteral(10)), - or(equals(AE, DE), isNull(DE)))); + assertThat(normalizeConjuncts(effectivePredicate)).isEqualTo(normalizeConjuncts( + lessThan(BE, AE), + lessThan(CE, bigintLiteral(10)), + or(equals(AE, DE), isNull(DE)))); } @Test @@ -1060,15 +1009,13 @@ public void testRightJoin() Expression effectivePredicate = effectivePredicateExtractor.extract(SESSION, node, TypeProvider.empty(), typeAnalyzer); // All left side symbols should be checked against NULL - assertEquals( - normalizeConjuncts(effectivePredicate), - normalizeConjuncts( - or(lessThan(BE, AE), and(isNull(BE), isNull(AE))), - or(lessThan(CE, bigintLiteral(10)), isNull(CE)), - equals(DE, EE), - lessThan(FE, bigintLiteral(100)), - or(equals(AE, DE), isNull(AE)), - or(equals(BE, EE), isNull(BE)))); + assertThat(normalizeConjuncts(effectivePredicate)).isEqualTo(normalizeConjuncts( + or(lessThan(BE, AE), and(isNull(BE), isNull(AE))), + or(lessThan(CE, bigintLiteral(10)), isNull(CE)), + equals(DE, EE), + lessThan(FE, bigintLiteral(100)), + or(equals(AE, DE), isNull(AE)), + or(equals(BE, EE), isNull(BE)))); } @Test @@ -1108,12 +1055,10 @@ public void testRightJoinWithFalseInner() Expression effectivePredicate = effectivePredicateExtractor.extract(SESSION, node, TypeProvider.empty(), typeAnalyzer); // False literal on the left side should be ignored - assertEquals( - normalizeConjuncts(effectivePredicate), - normalizeConjuncts( - equals(DE, EE), - lessThan(FE, bigintLiteral(100)), - or(equals(AE, DE), isNull(AE)))); + assertThat(normalizeConjuncts(effectivePredicate)).isEqualTo(normalizeConjuncts( + equals(DE, EE), + lessThan(FE, bigintLiteral(100)), + or(equals(AE, DE), isNull(AE)))); } @Test @@ -1132,9 +1077,7 @@ public void testSemiJoin() Expression effectivePredicate = effectivePredicateExtractor.extract(SESSION, node, TypeProvider.empty(), typeAnalyzer); // Currently, only pull predicates through the source plan - assertEquals( - normalizeConjuncts(effectivePredicate), - normalizeConjuncts(and(greaterThan(AE, bigintLiteral(10)), lessThan(AE, bigintLiteral(100))))); + assertThat(normalizeConjuncts(effectivePredicate)).isEqualTo(normalizeConjuncts(and(greaterThan(AE, bigintLiteral(10)), lessThan(AE, bigintLiteral(100))))); } private static TableScanNode tableScanNode(Map scanAssignments) diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestEqualityInference.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestEqualityInference.java index 57358be9fae8..b3174c7a48a7 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestEqualityInference.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestEqualityInference.java @@ -58,10 +58,7 @@ import static io.trino.sql.planner.EqualityInference.isInferenceCandidate; import static io.trino.sql.tree.ComparisonExpression.Operator.EQUAL; import static io.trino.sql.tree.ComparisonExpression.Operator.GREATER_THAN; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestEqualityInference { @@ -82,28 +79,18 @@ public void testTransitivity() equals("d2", "b2"), equals("c2", "d2")); - assertEquals( - inference.rewrite(someExpression("a1", "a2"), symbols("d1", "d2")), - someExpression("d1", "d2")); + assertThat(inference.rewrite(someExpression("a1", "a2"), symbols("d1", "d2"))).isEqualTo(someExpression("d1", "d2")); - assertEquals( - inference.rewrite(someExpression("a1", "c1"), symbols("b1")), - someExpression("b1", "b1")); + assertThat(inference.rewrite(someExpression("a1", "c1"), symbols("b1"))).isEqualTo(someExpression("b1", "b1")); - assertEquals( - inference.rewrite(someExpression("a1", "a2"), symbols("b1", "d2", "c3")), - someExpression("b1", "d2")); + assertThat(inference.rewrite(someExpression("a1", "a2"), symbols("b1", "d2", "c3"))).isEqualTo(someExpression("b1", "d2")); // Both starting expressions should canonicalize to the same expression - assertEquals( - inference.getScopedCanonical(nameReference("a2"), matchesSymbols("c2", "d2")), - inference.getScopedCanonical(nameReference("b2"), matchesSymbols("c2", "d2"))); + assertThat(inference.getScopedCanonical(nameReference("a2"), matchesSymbols("c2", "d2"))).isEqualTo(inference.getScopedCanonical(nameReference("b2"), matchesSymbols("c2", "d2"))); Expression canonical = inference.getScopedCanonical(nameReference("a2"), matchesSymbols("c2", "d2")); // Given multiple translatable candidates, should choose the canonical - assertEquals( - inference.rewrite(someExpression("a2", "b2"), symbols("c2", "d2")), - someExpression(canonical, canonical)); + assertThat(inference.rewrite(someExpression("a2", "b2"), symbols("c2", "d2"))).isEqualTo(someExpression(canonical, canonical)); } @Test @@ -112,7 +99,7 @@ public void testTriviallyRewritable() Expression expression = new EqualityInference(metadata) .rewrite(someExpression("a1", "a2"), symbols("a1", "a2")); - assertEquals(expression, someExpression("a1", "a2")); + assertThat(expression).isEqualTo(someExpression("a1", "a2")); } @Test @@ -123,8 +110,8 @@ public void testUnrewritable() equals("a1", "b1"), equals("a2", "b2")); - assertNull(inference.rewrite(someExpression("a1", "a2"), symbols("b1", "c1"))); - assertNull(inference.rewrite(someExpression("c1", "c2"), symbols("a1", "a2"))); + assertThat(inference.rewrite(someExpression("a1", "a2"), symbols("b1", "c1"))).isNull(); + assertThat(inference.rewrite(someExpression("c1", "c2"), symbols("a1", "a2"))).isNull(); } @Test @@ -137,7 +124,7 @@ public void testParseEqualityExpression() equals("c1", "a1")); Expression expression = inference.rewrite(someExpression("a1", "b1"), symbols("c1")); - assertEquals(expression, someExpression("c1", "c1")); + assertThat(expression).isEqualTo(someExpression("c1", "c1")); } @Test @@ -148,10 +135,10 @@ public void testExtractInferrableEqualities() ExpressionUtils.and(equals("a1", "b1"), equals("b1", "c1"), someExpression("c1", "d1"))); // Able to rewrite to c1 due to equalities - assertEquals(nameReference("c1"), inference.rewrite(nameReference("a1"), symbols("c1"))); + assertThat(nameReference("c1")).isEqualTo(inference.rewrite(nameReference("a1"), symbols("c1"))); // But not be able to rewrite to d1 which is not connected via equality - assertNull(inference.rewrite(nameReference("a1"), symbols("d1"))); + assertThat(inference.rewrite(nameReference("a1"), symbols("d1"))).isNull(); } @Test @@ -167,28 +154,28 @@ public void testEqualityPartitionGeneration() EqualityInference.EqualityPartition emptyScopePartition = inference.generateEqualitiesPartitionedBy(ImmutableSet.of()); // Cannot generate any scope equalities with no matching symbols - assertTrue(emptyScopePartition.getScopeEqualities().isEmpty()); + assertThat(emptyScopePartition.getScopeEqualities().isEmpty()).isTrue(); // All equalities should be represented in the inverse scope - assertFalse(emptyScopePartition.getScopeComplementEqualities().isEmpty()); + assertThat(emptyScopePartition.getScopeComplementEqualities().isEmpty()).isFalse(); // There should be no equalities straddling the scope - assertTrue(emptyScopePartition.getScopeStraddlingEqualities().isEmpty()); + assertThat(emptyScopePartition.getScopeStraddlingEqualities().isEmpty()).isTrue(); EqualityInference.EqualityPartition equalityPartition = inference.generateEqualitiesPartitionedBy(symbols("c1")); // There should be equalities in the scope, that only use c1 and are all inferrable equalities - assertFalse(equalityPartition.getScopeEqualities().isEmpty()); - assertTrue(Iterables.all(equalityPartition.getScopeEqualities(), matchesSymbolScope(matchesSymbols("c1")))); - assertTrue(Iterables.all(equalityPartition.getScopeEqualities(), expression -> isInferenceCandidate(metadata, expression))); + assertThat(equalityPartition.getScopeEqualities().isEmpty()).isFalse(); + assertThat(Iterables.all(equalityPartition.getScopeEqualities(), matchesSymbolScope(matchesSymbols("c1")))).isTrue(); + assertThat(Iterables.all(equalityPartition.getScopeEqualities(), expression -> isInferenceCandidate(metadata, expression))).isTrue(); // There should be equalities in the inverse scope, that never use c1 and are all inferrable equalities - assertFalse(equalityPartition.getScopeComplementEqualities().isEmpty()); - assertTrue(Iterables.all(equalityPartition.getScopeComplementEqualities(), matchesSymbolScope(not(matchesSymbols("c1"))))); - assertTrue(Iterables.all(equalityPartition.getScopeComplementEqualities(), expression -> isInferenceCandidate(metadata, expression))); + assertThat(equalityPartition.getScopeComplementEqualities().isEmpty()).isFalse(); + assertThat(Iterables.all(equalityPartition.getScopeComplementEqualities(), matchesSymbolScope(not(matchesSymbols("c1"))))).isTrue(); + assertThat(Iterables.all(equalityPartition.getScopeComplementEqualities(), expression -> isInferenceCandidate(metadata, expression))).isTrue(); // There should be equalities in the straddling scope, that should use both c1 and not c1 symbols - assertFalse(equalityPartition.getScopeStraddlingEqualities().isEmpty()); - assertTrue(Iterables.any(equalityPartition.getScopeStraddlingEqualities(), matchesStraddlingScope(matchesSymbols("c1")))); - assertTrue(Iterables.all(equalityPartition.getScopeStraddlingEqualities(), expression -> isInferenceCandidate(metadata, expression))); + assertThat(equalityPartition.getScopeStraddlingEqualities().isEmpty()).isFalse(); + assertThat(Iterables.any(equalityPartition.getScopeStraddlingEqualities(), matchesStraddlingScope(matchesSymbols("c1")))).isTrue(); + assertThat(Iterables.all(equalityPartition.getScopeStraddlingEqualities(), expression -> isInferenceCandidate(metadata, expression))).isTrue(); // There should be a "full cover" of all of the equalities used // THUS, we should be able to plug the generated equalities back in and get an equivalent set of equalities back the next time around @@ -202,9 +189,9 @@ public void testEqualityPartitionGeneration() EqualityInference.EqualityPartition newEqualityPartition = newInference.generateEqualitiesPartitionedBy(symbols("c1")); - assertEquals(setCopy(equalityPartition.getScopeEqualities()), setCopy(newEqualityPartition.getScopeEqualities())); - assertEquals(setCopy(equalityPartition.getScopeComplementEqualities()), setCopy(newEqualityPartition.getScopeComplementEqualities())); - assertEquals(setCopy(equalityPartition.getScopeStraddlingEqualities()), setCopy(newEqualityPartition.getScopeStraddlingEqualities())); + assertThat(setCopy(equalityPartition.getScopeEqualities())).isEqualTo(setCopy(newEqualityPartition.getScopeEqualities())); + assertThat(setCopy(equalityPartition.getScopeComplementEqualities())).isEqualTo(setCopy(newEqualityPartition.getScopeComplementEqualities())); + assertThat(setCopy(equalityPartition.getScopeStraddlingEqualities())).isEqualTo(setCopy(newEqualityPartition.getScopeStraddlingEqualities())); } @Test @@ -223,19 +210,19 @@ public void testMultipleEqualitySetsPredicateGeneration() EqualityInference.EqualityPartition equalityPartition = inference.generateEqualitiesPartitionedBy(symbols("a1", "a2", "b1", "b2")); // There should be equalities in the scope, that only use a* and b* symbols and are all inferrable equalities - assertFalse(equalityPartition.getScopeEqualities().isEmpty()); - assertTrue(Iterables.all(equalityPartition.getScopeEqualities(), matchesSymbolScope(symbolBeginsWith("a", "b")))); - assertTrue(Iterables.all(equalityPartition.getScopeEqualities(), expression -> isInferenceCandidate(metadata, expression))); + assertThat(equalityPartition.getScopeEqualities().isEmpty()).isFalse(); + assertThat(Iterables.all(equalityPartition.getScopeEqualities(), matchesSymbolScope(symbolBeginsWith("a", "b")))).isTrue(); + assertThat(Iterables.all(equalityPartition.getScopeEqualities(), expression -> isInferenceCandidate(metadata, expression))).isTrue(); // There should be equalities in the inverse scope, that never use a* and b* symbols and are all inferrable equalities - assertFalse(equalityPartition.getScopeComplementEqualities().isEmpty()); - assertTrue(Iterables.all(equalityPartition.getScopeComplementEqualities(), matchesSymbolScope(not(symbolBeginsWith("a", "b"))))); - assertTrue(Iterables.all(equalityPartition.getScopeComplementEqualities(), expression -> isInferenceCandidate(metadata, expression))); + assertThat(equalityPartition.getScopeComplementEqualities().isEmpty()).isFalse(); + assertThat(Iterables.all(equalityPartition.getScopeComplementEqualities(), matchesSymbolScope(not(symbolBeginsWith("a", "b"))))).isTrue(); + assertThat(Iterables.all(equalityPartition.getScopeComplementEqualities(), expression -> isInferenceCandidate(metadata, expression))).isTrue(); // There should be equalities in the straddling scope, that should use both c1 and not c1 symbols - assertFalse(equalityPartition.getScopeStraddlingEqualities().isEmpty()); - assertTrue(Iterables.any(equalityPartition.getScopeStraddlingEqualities(), matchesStraddlingScope(symbolBeginsWith("a", "b")))); - assertTrue(Iterables.all(equalityPartition.getScopeStraddlingEqualities(), expression -> isInferenceCandidate(metadata, expression))); + assertThat(equalityPartition.getScopeStraddlingEqualities().isEmpty()).isFalse(); + assertThat(Iterables.any(equalityPartition.getScopeStraddlingEqualities(), matchesStraddlingScope(symbolBeginsWith("a", "b")))).isTrue(); + assertThat(Iterables.all(equalityPartition.getScopeStraddlingEqualities(), expression -> isInferenceCandidate(metadata, expression))).isTrue(); // Again, there should be a "full cover" of all of the equalities used // THUS, we should be able to plug the generated equalities back in and get an equivalent set of equalities back the next time around @@ -249,9 +236,9 @@ public void testMultipleEqualitySetsPredicateGeneration() EqualityInference.EqualityPartition newEqualityPartition = newInference.generateEqualitiesPartitionedBy(symbols("a1", "a2", "b1", "b2")); - assertEquals(setCopy(equalityPartition.getScopeEqualities()), setCopy(newEqualityPartition.getScopeEqualities())); - assertEquals(setCopy(equalityPartition.getScopeComplementEqualities()), setCopy(newEqualityPartition.getScopeComplementEqualities())); - assertEquals(setCopy(equalityPartition.getScopeStraddlingEqualities()), setCopy(newEqualityPartition.getScopeStraddlingEqualities())); + assertThat(setCopy(equalityPartition.getScopeEqualities())).isEqualTo(setCopy(newEqualityPartition.getScopeEqualities())); + assertThat(setCopy(equalityPartition.getScopeComplementEqualities())).isEqualTo(setCopy(newEqualityPartition.getScopeComplementEqualities())); + assertThat(setCopy(equalityPartition.getScopeStraddlingEqualities())).isEqualTo(setCopy(newEqualityPartition.getScopeStraddlingEqualities())); } @Test @@ -264,13 +251,13 @@ public void testSubExpressionRewrites() equals(nameReference("a3"), multiply(nameReference("a1"), add("b", "c")))); // a3 = a1 * (b + c) // Expression (b + c) should get entirely rewritten as a1 - assertEquals(inference.rewrite(add("b", "c"), symbols("a1", "a2")), nameReference("a1")); + assertThat(inference.rewrite(add("b", "c"), symbols("a1", "a2"))).isEqualTo(nameReference("a1")); // Only the sub-expression (b + c) should get rewritten in terms of a* - assertEquals(inference.rewrite(multiply(nameReference("ax"), add("b", "c")), symbols("ax", "a1", "a2", "a3")), multiply(nameReference("ax"), nameReference("a1"))); + assertThat(inference.rewrite(multiply(nameReference("ax"), add("b", "c")), symbols("ax", "a1", "a2", "a3"))).isEqualTo(multiply(nameReference("ax"), nameReference("a1"))); // To be compliant, could rewrite either the whole expression, or just the sub-expression. Rewriting larger expressions are preferred - assertEquals(inference.rewrite(multiply(nameReference("a1"), add("b", "c")), symbols("a1", "a2", "a3")), nameReference("a3")); + assertThat(inference.rewrite(multiply(nameReference("a1"), add("b", "c")), symbols("a1", "a2", "a3"))).isEqualTo(nameReference("a3")); } @Test @@ -283,17 +270,15 @@ public void testConstantEqualities() equals(nameReference("c1"), number(1))); // Should always prefer a constant if available (constant is part of all scopes) - assertEquals(inference.rewrite(nameReference("a1"), symbols("a1", "b1")), number(1)); + assertThat(inference.rewrite(nameReference("a1"), symbols("a1", "b1"))).isEqualTo(number(1)); // All scope equalities should utilize the constant if possible EqualityInference.EqualityPartition equalityPartition = inference.generateEqualitiesPartitionedBy(symbols("a1", "b1")); - assertEquals(equalitiesAsSets(equalityPartition.getScopeEqualities()), - set(set(nameReference("a1"), number(1)), set(nameReference("b1"), number(1)))); - assertEquals(equalitiesAsSets(equalityPartition.getScopeComplementEqualities()), - set(set(nameReference("c1"), number(1)))); + assertThat(equalitiesAsSets(equalityPartition.getScopeEqualities())).isEqualTo(set(set(nameReference("a1"), number(1)), set(nameReference("b1"), number(1)))); + assertThat(equalitiesAsSets(equalityPartition.getScopeComplementEqualities())).isEqualTo(set(set(nameReference("c1"), number(1)))); // There should be no scope straddling equalities as the full set of equalities should be already represented by the scope and inverse scope - assertTrue(equalityPartition.getScopeStraddlingEqualities().isEmpty()); + assertThat(equalityPartition.getScopeStraddlingEqualities().isEmpty()).isTrue(); } @Test @@ -306,7 +291,7 @@ public void testEqualityGeneration() equals("c", "d")); Expression scopedCanonical = inference.getScopedCanonical(nameReference("e1"), symbolBeginsWith("a")); - assertEquals(scopedCanonical, nameReference("a1")); + assertThat(scopedCanonical).isEqualTo(nameReference("a1")); } @Test @@ -332,8 +317,8 @@ public void testExpressionsThatMayReturnNullOnNonNullInput() equals(nameReference("a"), candidate)); List equalities = inference.generateEqualitiesPartitionedBy(symbols("b")).getScopeStraddlingEqualities(); - assertEquals(equalities.size(), 1); - assertTrue(equalities.get(0).equals(equals(nameReference("x"), nameReference("b"))) || equalities.get(0).equals(equals(nameReference("b"), nameReference("x")))); + assertThat(equalities.size()).isEqualTo(1); + assertThat(equalities.get(0).equals(equals(nameReference("x"), nameReference("b"))) || equalities.get(0).equals(equals(nameReference("b"), nameReference("x")))).isTrue(); } } diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestGroupingOperationRewriter.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestGroupingOperationRewriter.java index 7162e821f20b..f22ebce804c0 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestGroupingOperationRewriter.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestGroupingOperationRewriter.java @@ -21,7 +21,7 @@ import java.util.Set; import static io.trino.sql.planner.GroupingOperationRewriter.calculateGrouping; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestGroupingOperationRewriter { @@ -37,7 +37,7 @@ public void testGroupingOperationAllBitsSet() List> groupingSetOrdinals = ImmutableList.of(ImmutableSet.of(1), ImmutableSet.of(7, 3, 1), ImmutableSet.of(9, 1)); for (Set groupingSet : groupingSetOrdinals) { - assertEquals(calculateGrouping(groupingSet, groupingOrdinals), 7L); + assertThat(calculateGrouping(groupingSet, groupingOrdinals)).isEqualTo(7L); } } @@ -48,7 +48,7 @@ public void testGroupingOperationNoBitsSet() List> groupingSetOrdinals = ImmutableList.of(ImmutableSet.of(4, 6)); for (Set groupingSet : groupingSetOrdinals) { - assertEquals(calculateGrouping(groupingSet, groupingOrdinals), 0L); + assertThat(calculateGrouping(groupingSet, groupingOrdinals)).isEqualTo(0L); } } @@ -61,7 +61,7 @@ public void testGroupingOperationSomeBitsSet() for (int groupId = 0; groupId < groupingSetOrdinals.size(); groupId++) { Set groupingSet = groupingSetOrdinals.get(groupId); - assertEquals(Long.valueOf(calculateGrouping(groupingSet, groupingOrdinals)), expectedResults.get(groupId)); + assertThat(Long.valueOf(calculateGrouping(groupingSet, groupingOrdinals))).isEqualTo(expectedResults.get(groupId)); } } @@ -73,7 +73,7 @@ public void testMoreThanThirtyTwoArguments() for (int groupId = 0; groupId < groupingSetOrdinals.size(); groupId++) { Set groupingSet = groupingSetOrdinals.get(groupId); - assertEquals(Long.valueOf(calculateGrouping(groupingSet, fortyIntegers)), expectedResults.get(groupId)); + assertThat(Long.valueOf(calculateGrouping(groupingSet, fortyIntegers))).isEqualTo(expectedResults.get(groupId)); } } } diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestLiteralEncoder.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestLiteralEncoder.java index 1607fd46a0f5..05ce1f93e817 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestLiteralEncoder.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestLiteralEncoder.java @@ -82,8 +82,7 @@ import static io.trino.type.UnknownType.UNKNOWN; import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestLiteralEncoder { @@ -284,9 +283,9 @@ public void testEncodeCodePoints() private void assertEncode(Object value, Type type, String expected) { Expression expression = encoder.toExpression(value, type); - assertEquals(getExpressionType(expression), type); - assertEquals(getExpressionValue(expression), value); - assertEquals(formatSql(expression), expected); + assertThat(getExpressionType(expression)).isEqualTo(type); + assertThat(getExpressionValue(expression)).isEqualTo(value); + assertThat(formatSql(expression)).isEqualTo(expected); } /** @@ -296,20 +295,24 @@ private void assertEncode(Object value, Type type, String expected) private void assertEncodeCaseInsensitively(Object value, Type type, String expected) { Expression expression = encoder.toExpression(value, type); - assertTrue(isEffectivelyLiteral(PLANNER_CONTEXT, TEST_SESSION, expression), "isEffectivelyLiteral returned false for: " + expression); - assertEquals(getExpressionType(expression), type); - assertEquals(getExpressionValue(expression), value); + assertThat(isEffectivelyLiteral(PLANNER_CONTEXT, TEST_SESSION, expression)) + .describedAs("isEffectivelyLiteral returned false for: " + expression) + .isTrue(); + assertThat(getExpressionType(expression)).isEqualTo(type); + assertThat(getExpressionValue(expression)).isEqualTo(value); assertEqualsIgnoreCase(formatSql(expression), expected); } private void assertRoundTrip(T value, Type type, BiPredicate predicate) { Expression expression = encoder.toExpression(value, type); - assertTrue(isEffectivelyLiteral(PLANNER_CONTEXT, TEST_SESSION, expression), "isEffectivelyLiteral returned false for: " + expression); - assertEquals(getExpressionType(expression), type); + assertThat(isEffectivelyLiteral(PLANNER_CONTEXT, TEST_SESSION, expression)) + .describedAs("isEffectivelyLiteral returned false for: " + expression) + .isTrue(); + assertThat(getExpressionType(expression)).isEqualTo(type); @SuppressWarnings("unchecked") T decodedValue = (T) getExpressionValue(expression); - assertTrue(predicate.test(value, decodedValue)); + assertThat(predicate.test(value, decodedValue)).isTrue(); } private Object getExpressionValue(Expression expression) diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestLocalDynamicFilterConsumer.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestLocalDynamicFilterConsumer.java index fb4417e3d7d8..787903a6cd95 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestLocalDynamicFilterConsumer.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestLocalDynamicFilterConsumer.java @@ -52,9 +52,6 @@ import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.joining; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestLocalDynamicFilterConsumer extends BasePlanTest @@ -77,12 +74,12 @@ public void testSimple() ImmutableList.of(collector), DataSize.of(100, KILOBYTE)); filter.setPartitionCount(1); - assertEquals(filter.getBuildChannels(), ImmutableMap.of(new DynamicFilterId("123"), 0)); - assertFalse(collector.isCollectionComplete()); + assertThat(filter.getBuildChannels()).isEqualTo(ImmutableMap.of(new DynamicFilterId("123"), 0)); + assertThat(collector.isCollectionComplete()).isFalse(); filter.addPartition(TupleDomain.withColumnDomains(ImmutableMap.of( new DynamicFilterId("123"), Domain.singleValue(INTEGER, 7L)))); - assertEquals(collector.getCollectedDomains(), ImmutableMap.of( + assertThat(collector.getCollectedDomains()).isEqualTo(ImmutableMap.of( new DynamicFilterId("123"), Domain.singleValue(INTEGER, 7L))); } @@ -96,17 +93,17 @@ public void testShortCircuitOnAllTupleDomain() ImmutableList.of(collector), DataSize.of(100, KILOBYTE)); - assertFalse(collector.isCollectionComplete()); + assertThat(collector.isCollectionComplete()).isFalse(); filter.addPartition(TupleDomain.withColumnDomains(ImmutableMap.of( new DynamicFilterId("123"), Domain.all(INTEGER)))); - assertEquals(collector.getCollectedDomains(), ImmutableMap.of(new DynamicFilterId("123"), Domain.all(INTEGER))); + assertThat(collector.getCollectedDomains()).isEqualTo(ImmutableMap.of(new DynamicFilterId("123"), Domain.all(INTEGER))); filter.setPartitionCount(2); // adding another partition domain won't change final domain filter.addPartition(TupleDomain.withColumnDomains(ImmutableMap.of( new DynamicFilterId("123"), Domain.singleValue(INTEGER, 1L)))); - assertEquals(collector.getCollectedDomains(), ImmutableMap.of(new DynamicFilterId("123"), Domain.all(INTEGER))); + assertThat(collector.getCollectedDomains()).isEqualTo(ImmutableMap.of(new DynamicFilterId("123"), Domain.all(INTEGER))); } @Test @@ -118,19 +115,19 @@ public void testMultiplePartitions() ImmutableMap.of(new DynamicFilterId("123"), INTEGER), ImmutableList.of(collector), DataSize.of(100, KILOBYTE)); - assertEquals(filter.getBuildChannels(), ImmutableMap.of(new DynamicFilterId("123"), 0)); + assertThat(filter.getBuildChannels()).isEqualTo(ImmutableMap.of(new DynamicFilterId("123"), 0)); - assertFalse(collector.isCollectionComplete()); + assertThat(collector.isCollectionComplete()).isFalse(); filter.addPartition(TupleDomain.withColumnDomains(ImmutableMap.of( new DynamicFilterId("123"), Domain.singleValue(INTEGER, 10L)))); - assertFalse(collector.isCollectionComplete()); + assertThat(collector.isCollectionComplete()).isFalse(); filter.addPartition(TupleDomain.withColumnDomains(ImmutableMap.of( new DynamicFilterId("123"), Domain.singleValue(INTEGER, 20L)))); - assertFalse(collector.isCollectionComplete()); + assertThat(collector.isCollectionComplete()).isFalse(); filter.setPartitionCount(2); - assertEquals(collector.getCollectedDomains(), ImmutableMap.of( + assertThat(collector.getCollectedDomains()).isEqualTo(ImmutableMap.of( new DynamicFilterId("123"), Domain.multipleValues(INTEGER, ImmutableList.of(10L, 20L)))); } @@ -151,12 +148,12 @@ public void testAllDomain() DataSize.of(100, KILOBYTE)); filter.setPartitionCount(1); - assertFalse(collector.isCollectionComplete()); + assertThat(collector.isCollectionComplete()).isFalse(); filter.addPartition(TupleDomain.withColumnDomains(ImmutableMap.of( filter1, Domain.all(INTEGER), filter2, Domain.singleValue(INTEGER, 1L)))); - assertEquals(collector.getCollectedDomains(), ImmutableMap.of(filter1, Domain.all(INTEGER), filter2, Domain.singleValue(INTEGER, 1L))); + assertThat(collector.getCollectedDomains()).isEqualTo(ImmutableMap.of(filter1, Domain.all(INTEGER), filter2, Domain.singleValue(INTEGER, 1L))); } @Test @@ -169,12 +166,12 @@ public void testNone() ImmutableList.of(collector), DataSize.of(100, KILOBYTE)); filter.setPartitionCount(1); - assertEquals(filter.getBuildChannels(), ImmutableMap.of(new DynamicFilterId("123"), 0)); + assertThat(filter.getBuildChannels()).isEqualTo(ImmutableMap.of(new DynamicFilterId("123"), 0)); - assertFalse(collector.isCollectionComplete()); + assertThat(collector.isCollectionComplete()).isFalse(); filter.addPartition(TupleDomain.none()); - assertEquals(collector.getCollectedDomains(), ImmutableMap.of( + assertThat(collector.getCollectedDomains()).isEqualTo(ImmutableMap.of( new DynamicFilterId("123"), Domain.none(INTEGER))); } @@ -188,13 +185,13 @@ public void testMultipleColumns() ImmutableList.of(collector), DataSize.of(100, KILOBYTE)); filter.setPartitionCount(1); - assertEquals(filter.getBuildChannels(), ImmutableMap.of(new DynamicFilterId("123"), 0, new DynamicFilterId("456"), 1)); - assertFalse(collector.isCollectionComplete()); + assertThat(filter.getBuildChannels()).isEqualTo(ImmutableMap.of(new DynamicFilterId("123"), 0, new DynamicFilterId("456"), 1)); + assertThat(collector.isCollectionComplete()).isFalse(); filter.addPartition(TupleDomain.withColumnDomains(ImmutableMap.of( new DynamicFilterId("123"), Domain.singleValue(INTEGER, 10L), new DynamicFilterId("456"), Domain.singleValue(INTEGER, 20L)))); - assertEquals(collector.getCollectedDomains(), ImmutableMap.of( + assertThat(collector.getCollectedDomains()).isEqualTo(ImmutableMap.of( new DynamicFilterId("123"), Domain.singleValue(INTEGER, 10L), new DynamicFilterId("456"), Domain.singleValue(INTEGER, 20L))); } @@ -209,19 +206,19 @@ public void testMultiplePartitionsAndColumns() ImmutableList.of(collector), DataSize.of(100, KILOBYTE)); filter.setPartitionCount(2); - assertEquals(filter.getBuildChannels(), ImmutableMap.of(new DynamicFilterId("123"), 0, new DynamicFilterId("456"), 1)); + assertThat(filter.getBuildChannels()).isEqualTo(ImmutableMap.of(new DynamicFilterId("123"), 0, new DynamicFilterId("456"), 1)); - assertFalse(collector.isCollectionComplete()); + assertThat(collector.isCollectionComplete()).isFalse(); filter.addPartition(TupleDomain.withColumnDomains(ImmutableMap.of( new DynamicFilterId("123"), Domain.singleValue(INTEGER, 10L), new DynamicFilterId("456"), Domain.singleValue(BIGINT, 100L)))); - assertFalse(collector.isCollectionComplete()); + assertThat(collector.isCollectionComplete()).isFalse(); filter.addPartition(TupleDomain.withColumnDomains(ImmutableMap.of( new DynamicFilterId("123"), Domain.singleValue(INTEGER, 20L), new DynamicFilterId("456"), Domain.singleValue(BIGINT, 200L)))); - assertEquals(collector.getCollectedDomains(), ImmutableMap.of( + assertThat(collector.getCollectedDomains()).isEqualTo(ImmutableMap.of( new DynamicFilterId("123"), Domain.multipleValues(INTEGER, ImmutableList.of(10L, 20L)), new DynamicFilterId("456"), Domain.multipleValues(BIGINT, ImmutableList.of(100L, 200L)))); } @@ -260,16 +257,14 @@ public void testDynamicFilterPruning() ImmutableSet.of(filter1, filter3), ImmutableList.of(collector), DataSize.of(100, KILOBYTE)); - assertEquals(consumer.getBuildChannels(), ImmutableMap.of(filter1, 0, filter3, 2)); + assertThat(consumer.getBuildChannels()).isEqualTo(ImmutableMap.of(filter1, 0, filter3, 2)); // make sure domain types got propagated correctly - assertFalse(collector.isCollectionComplete()); + assertThat(collector.isCollectionComplete()).isFalse(); consumer.addPartition(TupleDomain.none()); - assertFalse(collector.isCollectionComplete()); + assertThat(collector.isCollectionComplete()).isFalse(); consumer.setPartitionCount(1); - assertEquals( - collector.getCollectedDomains(), - ImmutableMap.of(filter1, Domain.none(BIGINT), filter3, Domain.none(SMALLINT))); + assertThat(collector.getCollectedDomains()).isEqualTo(ImmutableMap.of(filter1, Domain.none(BIGINT), filter3, Domain.none(SMALLINT))); } @Test @@ -283,7 +278,7 @@ public void testCompactionOnSizeLimitExceeded() ImmutableMap.of(filterId, VARCHAR), ImmutableList.of(collector), sizeLimit); - assertFalse(collector.isCollectionComplete()); + assertThat(collector.isCollectionComplete()).isFalse(); Domain domain1 = Domain.multipleValues(VARCHAR, LongStream.range(0, 5) .mapToObj(i -> utf8Slice("value" + i)) @@ -295,15 +290,15 @@ public void testCompactionOnSizeLimitExceeded() assertThat(domain1.union(domain2).getRetainedSizeInBytes()).isGreaterThanOrEqualTo(sizeLimit.toBytes()); filter.addPartition(TupleDomain.withColumnDomains(ImmutableMap.of(filterId, domain1))); - assertFalse(collector.isCollectionComplete()); + assertThat(collector.isCollectionComplete()).isFalse(); filter.addPartition(TupleDomain.withColumnDomains(ImmutableMap.of(filterId, domain2))); - assertFalse(collector.isCollectionComplete()); + assertThat(collector.isCollectionComplete()).isFalse(); filter.setPartitionCount(2); - assertTrue(collector.isCollectionComplete()); + assertThat(collector.isCollectionComplete()).isTrue(); Domain collectedDomain = collector.getCollectedDomains().get(filterId); - assertEquals(collectedDomain.getValues(), ValueSet.ofRanges(range(VARCHAR, utf8Slice("value0"), true, utf8Slice("value9"), true))); + assertThat(collectedDomain.getValues()).isEqualTo(ValueSet.ofRanges(range(VARCHAR, utf8Slice("value0"), true, utf8Slice("value9"), true))); } @Test @@ -317,7 +312,7 @@ public void testSizeLimitExceededAfterCompaction() ImmutableMap.of(filterId, VARCHAR), ImmutableList.of(collector), sizeLimit); - assertFalse(collector.isCollectionComplete()); + assertThat(collector.isCollectionComplete()).isFalse(); Domain domain1 = Domain.multipleValues(VARCHAR, LongStream.range(0, 5) .mapToObj(i -> utf8Slice("value" + i)) @@ -329,10 +324,10 @@ public void testSizeLimitExceededAfterCompaction() assertThat(domain1.union(domain2).simplify(1).getRetainedSizeInBytes()).isLessThanOrEqualTo(sizeLimit.toBytes()); filter.addPartition(TupleDomain.withColumnDomains(ImmutableMap.of(filterId, domain1))); - assertFalse(collector.isCollectionComplete()); + assertThat(collector.isCollectionComplete()).isFalse(); filter.addPartition(TupleDomain.withColumnDomains(ImmutableMap.of(filterId, domain2))); - assertFalse(collector.isCollectionComplete()); + assertThat(collector.isCollectionComplete()).isFalse(); Domain domain3 = Domain.singleValue(VARCHAR, utf8Slice(IntStream.range(0, 800) .mapToObj(i -> "x") @@ -342,9 +337,9 @@ public void testSizeLimitExceededAfterCompaction() .isGreaterThanOrEqualTo(sizeLimit.toBytes()); filter.addPartition(TupleDomain.withColumnDomains(ImmutableMap.of(filterId, domain3))); - assertTrue(collector.isCollectionComplete()); + assertThat(collector.isCollectionComplete()).isTrue(); - assertEquals(collector.getCollectedDomains(), ImmutableMap.of(filterId, Domain.all(VARCHAR))); + assertThat(collector.getCollectedDomains()).isEqualTo(ImmutableMap.of(filterId, Domain.all(VARCHAR))); } private static class TestingDynamicFilterCollector diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestLocalDynamicFiltersCollector.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestLocalDynamicFiltersCollector.java index 0770c5b3a5f5..8d11e7e4e190 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestLocalDynamicFiltersCollector.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestLocalDynamicFiltersCollector.java @@ -42,9 +42,7 @@ import static io.trino.sql.tree.ComparisonExpression.Operator.EQUAL; import static io.trino.sql.tree.ComparisonExpression.Operator.GREATER_THAN; import static io.trino.sql.tree.ComparisonExpression.Operator.LESS_THAN; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestLocalDynamicFiltersCollector { @@ -64,23 +62,25 @@ public void testSingleEquality() ImmutableMap.of(symbol, column), symbolAllocator.getTypes()); - assertEquals(filter.getColumnsCovered(), Set.of(column), "columns covered"); + assertThat(filter.getColumnsCovered()) + .describedAs("columns covered") + .isEqualTo(Set.of(column)); // Filter is blocked and not completed. CompletableFuture isBlocked = filter.isBlocked(); - assertFalse(filter.isComplete()); - assertTrue(filter.isAwaitable()); - assertFalse(isBlocked.isDone()); - assertEquals(filter.getCurrentPredicate(), TupleDomain.all()); + assertThat(filter.isComplete()).isFalse(); + assertThat(filter.isAwaitable()).isTrue(); + assertThat(isBlocked.isDone()).isFalse(); + assertThat(filter.getCurrentPredicate()).isEqualTo(TupleDomain.all()); Domain domain = Domain.singleValue(BIGINT, 7L); collector.collectDynamicFilterDomains(ImmutableMap.of(filterId, domain)); // Unblocked and completed. - assertTrue(filter.isComplete()); - assertFalse(filter.isAwaitable()); - assertTrue(isBlocked.isDone()); - assertEquals(filter.getCurrentPredicate(), TupleDomain.withColumnDomains(ImmutableMap.of(column, domain))); + assertThat(filter.isComplete()).isTrue(); + assertThat(filter.isAwaitable()).isFalse(); + assertThat(isBlocked.isDone()).isTrue(); + assertThat(filter.getCurrentPredicate()).isEqualTo(TupleDomain.withColumnDomains(ImmutableMap.of(column, domain))); } @Test @@ -99,23 +99,25 @@ public void testDynamicFilterCoercion() ImmutableMap.of(symbol, column), symbolAllocator.getTypes()); - assertEquals(filter.getColumnsCovered(), Set.of(column), "columns covered"); + assertThat(filter.getColumnsCovered()) + .describedAs("columns covered") + .isEqualTo(Set.of(column)); // Filter is blocked and not completed. CompletableFuture isBlocked = filter.isBlocked(); - assertFalse(filter.isComplete()); - assertTrue(filter.isAwaitable()); - assertFalse(isBlocked.isDone()); - assertEquals(filter.getCurrentPredicate(), TupleDomain.all()); + assertThat(filter.isComplete()).isFalse(); + assertThat(filter.isAwaitable()).isTrue(); + assertThat(isBlocked.isDone()).isFalse(); + assertThat(filter.getCurrentPredicate()).isEqualTo(TupleDomain.all()); Domain domain = Domain.singleValue(BIGINT, 7L); collector.collectDynamicFilterDomains(ImmutableMap.of(filterId, domain)); // Unblocked and completed. - assertTrue(filter.isComplete()); - assertFalse(filter.isAwaitable()); - assertTrue(isBlocked.isDone()); - assertEquals(filter.getCurrentPredicate(), TupleDomain.withColumnDomains(ImmutableMap.of(column, Domain.singleValue(INTEGER, 7L)))); + assertThat(filter.isComplete()).isTrue(); + assertThat(filter.isAwaitable()).isFalse(); + assertThat(isBlocked.isDone()).isTrue(); + assertThat(filter.getCurrentPredicate()).isEqualTo(TupleDomain.withColumnDomains(ImmutableMap.of(column, Domain.singleValue(INTEGER, 7L)))); } @Test @@ -136,21 +138,21 @@ public void testDynamicFilterCancellation() // Filter is blocked and not completed. CompletableFuture isBlocked = filter.isBlocked(); - assertFalse(filter.isComplete()); - assertFalse(isBlocked.isDone()); - assertEquals(filter.getCurrentPredicate(), TupleDomain.all()); + assertThat(filter.isComplete()).isFalse(); + assertThat(isBlocked.isDone()).isFalse(); + assertThat(filter.getCurrentPredicate()).isEqualTo(TupleDomain.all()); // DynamicFilter future cancellation should not affect LocalDynamicFiltersCollector - assertFalse(isBlocked.cancel(false)); - assertFalse(isBlocked.isDone()); - assertFalse(filter.isComplete()); + assertThat(isBlocked.cancel(false)).isFalse(); + assertThat(isBlocked.isDone()).isFalse(); + assertThat(filter.isComplete()).isFalse(); Domain domain = Domain.singleValue(BIGINT, 7L); collector.collectDynamicFilterDomains(ImmutableMap.of(filterId, domain)); // Unblocked and completed. - assertTrue(filter.isComplete()); - assertTrue(isBlocked.isDone()); - assertEquals(filter.getCurrentPredicate(), TupleDomain.withColumnDomains(ImmutableMap.of(column, domain))); + assertThat(filter.isComplete()).isTrue(); + assertThat(isBlocked.isDone()).isTrue(); + assertThat(filter.getCurrentPredicate()).isEqualTo(TupleDomain.withColumnDomains(ImmutableMap.of(column, domain))); } @Test @@ -174,23 +176,25 @@ public void testMultipleProbeColumns() ImmutableMap.of(symbol1, column1, symbol2, column2), symbolAllocator.getTypes()); - assertEquals(filter.getColumnsCovered(), Set.of(column1, column2), "columns covered"); + assertThat(filter.getColumnsCovered()) + .describedAs("columns covered") + .isEqualTo(Set.of(column1, column2)); // Filter is blocked and not completed. CompletableFuture isBlocked = filter.isBlocked(); - assertFalse(filter.isComplete()); - assertTrue(filter.isAwaitable()); - assertFalse(isBlocked.isDone()); - assertEquals(filter.getCurrentPredicate(), TupleDomain.all()); + assertThat(filter.isComplete()).isFalse(); + assertThat(filter.isAwaitable()).isTrue(); + assertThat(isBlocked.isDone()).isFalse(); + assertThat(filter.getCurrentPredicate()).isEqualTo(TupleDomain.all()); Domain domain = Domain.singleValue(BIGINT, 7L); collector.collectDynamicFilterDomains(ImmutableMap.of(filterId, domain)); // Unblocked and completed. - assertTrue(filter.isComplete()); - assertFalse(filter.isAwaitable()); - assertTrue(isBlocked.isDone()); - assertEquals(filter.getCurrentPredicate(), TupleDomain.withColumnDomains(ImmutableMap.of(column1, domain, column2, domain))); + assertThat(filter.isComplete()).isTrue(); + assertThat(filter.isAwaitable()).isFalse(); + assertThat(isBlocked.isDone()).isTrue(); + assertThat(filter.getCurrentPredicate()).isEqualTo(TupleDomain.withColumnDomains(ImmutableMap.of(column1, domain, column2, domain))); } @Test @@ -212,22 +216,24 @@ public void testComparison() ImmutableMap.of(symbol, column), symbolAllocator.getTypes()); - assertEquals(filter.getColumnsCovered(), Set.of(column), "columns covered"); + assertThat(filter.getColumnsCovered()) + .describedAs("columns covered") + .isEqualTo(Set.of(column)); // Filter is blocked and not completed. CompletableFuture isBlocked = filter.isBlocked(); - assertFalse(filter.isComplete()); - assertFalse(isBlocked.isDone()); - assertEquals(filter.getCurrentPredicate(), TupleDomain.all()); + assertThat(filter.isComplete()).isFalse(); + assertThat(isBlocked.isDone()).isFalse(); + assertThat(filter.getCurrentPredicate()).isEqualTo(TupleDomain.all()); collector.collectDynamicFilterDomains(ImmutableMap.of( filterId1, Domain.multipleValues(BIGINT, ImmutableList.of(1L, 2L, 3L)), filterId2, Domain.multipleValues(BIGINT, ImmutableList.of(4L, 5L, 6L)))); // Unblocked and completed. - assertTrue(filter.isComplete()); - assertTrue(isBlocked.isDone()); - assertEquals(filter.getCurrentPredicate(), TupleDomain.withColumnDomains(ImmutableMap.of( + assertThat(filter.isComplete()).isTrue(); + assertThat(isBlocked.isDone()).isTrue(); + assertThat(filter.getCurrentPredicate()).isEqualTo(TupleDomain.withColumnDomains(ImmutableMap.of( column, Domain.create(ValueSet.ofRanges(Range.range(BIGINT, 1L, false, 6L, false)), false)))); } @@ -253,22 +259,24 @@ public void testIsNotDistinctFrom() ImmutableMap.of(symbol1, column1, symbol2, column2), symbolAllocator.getTypes()); - assertEquals(filter.getColumnsCovered(), Set.of(column1, column2), "columns covered"); + assertThat(filter.getColumnsCovered()) + .describedAs("columns covered") + .isEqualTo(Set.of(column1, column2)); // Filter is blocked and not completed. CompletableFuture isBlocked = filter.isBlocked(); - assertFalse(filter.isComplete()); - assertFalse(isBlocked.isDone()); - assertEquals(filter.getCurrentPredicate(), TupleDomain.all()); + assertThat(filter.isComplete()).isFalse(); + assertThat(isBlocked.isDone()).isFalse(); + assertThat(filter.getCurrentPredicate()).isEqualTo(TupleDomain.all()); collector.collectDynamicFilterDomains(ImmutableMap.of( filterId1, Domain.multipleValues(BIGINT, ImmutableList.of(4L, 5L, 6L)), filterId2, Domain.none(BIGINT))); // Unblocked and completed. - assertTrue(filter.isComplete()); - assertTrue(isBlocked.isDone()); - assertEquals(filter.getCurrentPredicate(), TupleDomain.withColumnDomains(ImmutableMap.of( + assertThat(filter.isComplete()).isTrue(); + assertThat(isBlocked.isDone()).isTrue(); + assertThat(filter.getCurrentPredicate()).isEqualTo(TupleDomain.withColumnDomains(ImmutableMap.of( column1, Domain.create(ValueSet.of(BIGINT, 4L, 5L, 6L), true), column2, Domain.onlyNull(BIGINT)))); } @@ -294,39 +302,41 @@ public void testMultipleBuildColumnsSingleProbeColumn() ImmutableMap.of(symbol, column), symbolAllocator.getTypes()); - assertEquals(filter.getColumnsCovered(), Set.of(column), "columns covered"); + assertThat(filter.getColumnsCovered()) + .describedAs("columns covered") + .isEqualTo(Set.of(column)); // Filter is blocking and not completed. CompletableFuture isBlocked = filter.isBlocked(); - assertFalse(filter.isComplete()); - assertTrue(filter.isAwaitable()); - assertFalse(isBlocked.isDone()); - assertEquals(filter.getCurrentPredicate(), TupleDomain.all()); + assertThat(filter.isComplete()).isFalse(); + assertThat(filter.isAwaitable()).isTrue(); + assertThat(isBlocked.isDone()).isFalse(); + assertThat(filter.getCurrentPredicate()).isEqualTo(TupleDomain.all()); collector.collectDynamicFilterDomains( ImmutableMap.of(filter1, Domain.multipleValues(BIGINT, ImmutableList.of(1L, 2L, 3L)))); // Unblocked, but not completed. - assertFalse(filter.isComplete()); - assertTrue(filter.isAwaitable()); - assertTrue(isBlocked.isDone()); - assertEquals(filter.getCurrentPredicate(), TupleDomain.withColumnDomains( + assertThat(filter.isComplete()).isFalse(); + assertThat(filter.isAwaitable()).isTrue(); + assertThat(isBlocked.isDone()).isTrue(); + assertThat(filter.getCurrentPredicate()).isEqualTo(TupleDomain.withColumnDomains( ImmutableMap.of(column, Domain.multipleValues(BIGINT, ImmutableList.of(1L, 2L, 3L))))); // Create a new blocking future, waiting for next completion. isBlocked = filter.isBlocked(); - assertFalse(isBlocked.isDone()); - assertFalse(filter.isComplete()); - assertTrue(filter.isAwaitable()); + assertThat(isBlocked.isDone()).isFalse(); + assertThat(filter.isComplete()).isFalse(); + assertThat(filter.isAwaitable()).isTrue(); collector.collectDynamicFilterDomains( ImmutableMap.of(filter2, Domain.multipleValues(BIGINT, ImmutableList.of(2L, 3L, 4L)))); // Unblocked and completed. - assertTrue(filter.isComplete()); - assertFalse(filter.isAwaitable()); - assertTrue(isBlocked.isDone()); - assertEquals(filter.getCurrentPredicate(), TupleDomain.withColumnDomains( + assertThat(filter.isComplete()).isTrue(); + assertThat(filter.isAwaitable()).isFalse(); + assertThat(isBlocked.isDone()).isTrue(); + assertThat(filter.getCurrentPredicate()).isEqualTo(TupleDomain.withColumnDomains( ImmutableMap.of(column, Domain.multipleValues(BIGINT, ImmutableList.of(2L, 3L))))); } @@ -351,25 +361,25 @@ public void testUnusedDynamicFilter() // Filter is blocking and not completed. CompletableFuture isBlocked = filter.isBlocked(); - assertFalse(filter.isComplete()); - assertTrue(filter.isAwaitable()); - assertFalse(isBlocked.isDone()); - assertEquals(filter.getCurrentPredicate(), TupleDomain.all()); + assertThat(filter.isComplete()).isFalse(); + assertThat(filter.isAwaitable()).isTrue(); + assertThat(isBlocked.isDone()).isFalse(); + assertThat(filter.getCurrentPredicate()).isEqualTo(TupleDomain.all()); collector.collectDynamicFilterDomains(ImmutableMap.of(unusedFilterId, Domain.singleValue(BIGINT, 1L))); // This dynamic filter is unused here - has no effect on blocking/completion of the above future. - assertFalse(filter.isComplete()); - assertFalse(isBlocked.isDone()); - assertEquals(filter.getCurrentPredicate(), TupleDomain.all()); + assertThat(filter.isComplete()).isFalse(); + assertThat(isBlocked.isDone()).isFalse(); + assertThat(filter.getCurrentPredicate()).isEqualTo(TupleDomain.all()); collector.collectDynamicFilterDomains(ImmutableMap.of(usedFilterId, Domain.singleValue(BIGINT, 2L))); // Unblocked and completed. - assertTrue(filter.isComplete()); - assertFalse(filter.isAwaitable()); - assertTrue(isBlocked.isDone()); - assertEquals(filter.getCurrentPredicate(), TupleDomain.withColumnDomains(ImmutableMap.of(usedColumn, Domain.singleValue(BIGINT, 2L)))); + assertThat(filter.isComplete()).isTrue(); + assertThat(filter.isAwaitable()).isFalse(); + assertThat(isBlocked.isDone()).isTrue(); + assertThat(filter.getCurrentPredicate()).isEqualTo(TupleDomain.withColumnDomains(ImmutableMap.of(usedColumn, Domain.singleValue(BIGINT, 2L)))); } @Test @@ -396,18 +406,18 @@ public void testUnregisteredDynamicFilter() // Filter is blocked and not completed. CompletableFuture isBlocked = filter.isBlocked(); - assertFalse(filter.isComplete()); - assertTrue(filter.isAwaitable()); - assertFalse(isBlocked.isDone()); - assertEquals(filter.getCurrentPredicate(), TupleDomain.all()); + assertThat(filter.isComplete()).isFalse(); + assertThat(filter.isAwaitable()).isTrue(); + assertThat(isBlocked.isDone()).isFalse(); + assertThat(filter.getCurrentPredicate()).isEqualTo(TupleDomain.all()); collector.collectDynamicFilterDomains(ImmutableMap.of(registeredFilterId, Domain.singleValue(BIGINT, 2L))); // Unblocked and completed (don't wait for filter2) - assertTrue(filter.isComplete()); - assertFalse(filter.isAwaitable()); - assertTrue(isBlocked.isDone()); - assertEquals(filter.getCurrentPredicate(), TupleDomain.withColumnDomains(ImmutableMap.of(registeredColumn, Domain.singleValue(BIGINT, 2L)))); + assertThat(filter.isComplete()).isTrue(); + assertThat(filter.isAwaitable()).isFalse(); + assertThat(isBlocked.isDone()).isTrue(); + assertThat(filter.getCurrentPredicate()).isEqualTo(TupleDomain.withColumnDomains(ImmutableMap.of(registeredColumn, Domain.singleValue(BIGINT, 2L)))); } private DynamicFilter createDynamicFilter( diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestLogicalPlanner.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestLogicalPlanner.java index 165e571fcafe..bf20f8dc6edf 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestLogicalPlanner.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestLogicalPlanner.java @@ -162,8 +162,7 @@ import static io.trino.sql.tree.WindowFrame.Type.ROWS; import static io.trino.tests.QueryTemplate.queryTemplate; import static java.lang.String.format; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; +import static org.assertj.core.api.Assertions.assertThat; public class TestLogicalPlanner extends BasePlanTest @@ -199,7 +198,7 @@ public void testLikePredicate() .collect(toOptional()) .orElseThrow(() -> new AssertionError("No domain for 'type'")); - assertEquals(domain, Domain.multipleValues( + assertThat(domain).isEqualTo(multipleValues( createVarcharType(25), ImmutableList.of("LARGE PLATED BRASS", "LARGE PLATED COPPER", "LARGE PLATED NICKEL", "LARGE PLATED STEEL", "LARGE PLATED TIN").stream() .map(Slices::utf8Slice) @@ -582,62 +581,50 @@ public void testPushDownJoinConditionConjunctsToInnerSideBasedOnInheritedPredica public void testSameScalarSubqueryIsAppliedOnlyOnce() { // three subqueries with two duplicates (coerced to two different types), only two scalar joins should be in plan - assertEquals( - countOfMatchingNodes( - plan("SELECT * " + - "FROM orders " + - "WHERE CAST(orderkey AS INTEGER) = (SELECT 1 FROM orders LIMIT 1) " + - "AND custkey = (SELECT 2 FROM orders LIMIT 1) " + - "AND CAST(custkey as REAL) != (SELECT 1 FROM orders LIMIT 1)"), - TableScanNode.class::isInstance), - 3); + assertThat(countOfMatchingNodes( + plan("SELECT * " + + "FROM orders " + + "WHERE CAST(orderkey AS INTEGER) = (SELECT 1 FROM orders LIMIT 1) " + + "AND custkey = (SELECT 2 FROM orders LIMIT 1) " + + "AND CAST(custkey as REAL) != (SELECT 1 FROM orders LIMIT 1)"), + TableScanNode.class::isInstance)).isEqualTo(3); // same query used for left, right and complex join condition - assertEquals( - countOfMatchingNodes( - plan("SELECT * " + - "FROM orders o1 " + - "JOIN orders o2 ON " + - " o1.orderkey = (SELECT 1 FROM orders LIMIT 1) " + - " AND o2.orderkey = (SELECT 1 FROM orders LIMIT 1) " + - " AND o1.orderkey + o2.orderkey > (SELECT 1 FROM orders LIMIT 1)"), - TableScanNode.class::isInstance), - 3); + assertThat(countOfMatchingNodes( + plan("SELECT * " + + "FROM orders o1 " + + "JOIN orders o2 ON " + + " o1.orderkey = (SELECT 1 FROM orders LIMIT 1) " + + " AND o2.orderkey = (SELECT 1 FROM orders LIMIT 1) " + + " AND o1.orderkey + o2.orderkey > (SELECT 1 FROM orders LIMIT 1)"), + TableScanNode.class::isInstance)).isEqualTo(3); } @Test public void testSameInSubqueryIsAppliedOnlyOnce() { // same IN query used for left, right and complex condition - assertEquals( - countOfMatchingNodes( - plan("SELECT * FROM orders o1 JOIN orders o2 ON o1.orderkey NOT IN (SELECT 1) AND (o1.orderkey NOT IN (SELECT 1) OR o1.orderkey NOT IN (SELECT 1))"), - SemiJoinNode.class::isInstance), - 1); + assertThat(countOfMatchingNodes( + plan("SELECT * FROM orders o1 JOIN orders o2 ON o1.orderkey NOT IN (SELECT 1) AND (o1.orderkey NOT IN (SELECT 1) OR o1.orderkey NOT IN (SELECT 1))"), + SemiJoinNode.class::isInstance)).isEqualTo(1); // one subquery used for "1 IN (SELECT 1)", one subquery used for "2 IN (SELECT 1)" - assertEquals( - countOfMatchingNodes( - plan("SELECT 1 NOT IN (SELECT 1), 2 NOT IN (SELECT 1) WHERE 1 NOT IN (SELECT 1)"), - SemiJoinNode.class::isInstance), - 2); + assertThat(countOfMatchingNodes( + plan("SELECT 1 NOT IN (SELECT 1), 2 NOT IN (SELECT 1) WHERE 1 NOT IN (SELECT 1)"), + SemiJoinNode.class::isInstance)).isEqualTo(2); } @Test public void testSameQualifiedSubqueryIsAppliedOnlyOnce() { // same ALL query used for left, right and complex condition - assertEquals( - countOfMatchingNodes( - plan("SELECT * FROM orders o1 JOIN orders o2 ON o1.orderkey <= ALL(SELECT 1) AND (o1.orderkey <= ALL(SELECT 1) OR o1.orderkey <= ALL(SELECT 1))"), - AggregationNode.class::isInstance), - 1); + assertThat(countOfMatchingNodes( + plan("SELECT * FROM orders o1 JOIN orders o2 ON o1.orderkey <= ALL(SELECT 1) AND (o1.orderkey <= ALL(SELECT 1) OR o1.orderkey <= ALL(SELECT 1))"), + AggregationNode.class::isInstance)).isEqualTo(1); // one subquery used for "1 <= ALL(SELECT 1)", one subquery used for "2 <= ALL(SELECT 1)" - assertEquals( - countOfMatchingNodes( - plan("SELECT 1 <= ALL(SELECT 1), 2 <= ALL(SELECT 1) WHERE 1 <= ALL(SELECT 1)"), - AggregationNode.class::isInstance), - 2); + assertThat(countOfMatchingNodes( + plan("SELECT 1 <= ALL(SELECT 1), 2 <= ALL(SELECT 1) WHERE 1 <= ALL(SELECT 1)"), + AggregationNode.class::isInstance)).isEqualTo(2); } @Test @@ -653,18 +640,16 @@ public void testSameExistsAppliedOnlyOnce() @Test public void testReferenceToSameFieldAppliedOnlyOnce() { - assertEquals( - countOfMatchingNodes( - plan( - "SELECT " + - "(SELECT 1 FROM orders WHERE orderkey = x) + " + - "(SELECT 1 FROM orders WHERE orderkey = t.x) + " + - "(SELECT 1 FROM orders WHERE orderkey = T.x) + " + - "(SELECT 1 FROM orders WHERE orderkey = t.X) + " + - "(SELECT 1 FROM orders WHERE orderkey = T.X)" + - "FROM (VALUES 1, 2) t(x)"), - JoinNode.class::isInstance), - 1); + assertThat(countOfMatchingNodes( + plan( + "SELECT " + + "(SELECT 1 FROM orders WHERE orderkey = x) + " + + "(SELECT 1 FROM orders WHERE orderkey = t.x) + " + + "(SELECT 1 FROM orders WHERE orderkey = T.x) + " + + "(SELECT 1 FROM orders WHERE orderkey = t.X) + " + + "(SELECT 1 FROM orders WHERE orderkey = T.X)" + + "FROM (VALUES 1, 2) t(x)"), + JoinNode.class::isInstance)).isEqualTo(1); } private static int countOfMatchingNodes(Plan plan, Predicate predicate) @@ -722,11 +707,11 @@ private void assertPlanContainsNoApplyOrAnyJoin(String sql) @SafeVarargs private void assertPlanDoesNotContain(String sql, Class... classes) { - assertFalse( - searchFrom(plan(sql, OPTIMIZED).getRoot()) - .whereIsInstanceOfAny(classes) - .matches(), - "Unexpected node for query: " + sql); + assertThat(searchFrom(plan(sql, OPTIMIZED).getRoot()) + .whereIsInstanceOfAny(classes) + .matches()) + .describedAs("Unexpected node for query: " + sql) + .isFalse(); } @Test @@ -1275,19 +1260,15 @@ public void testBroadcastCorrelatedSubqueryAvoidsRemoteExchangeBeforeAggregation node(TableScanNode.class)))))); // validates that there exists only one remote exchange - Consumer validateSingleRemoteExchange = plan -> assertEquals( - countOfMatchingNodes( - plan, - node -> node instanceof ExchangeNode && ((ExchangeNode) node).getScope() == REMOTE), - 1); - - Consumer validateSingleStreamingAggregation = plan -> assertEquals( - countOfMatchingNodes( - plan, - node -> node instanceof AggregationNode - && ((AggregationNode) node).getGroupingKeys().contains(new Symbol("unique")) - && ((AggregationNode) node).isStreamable()), - 1); + Consumer validateSingleRemoteExchange = plan -> assertThat(countOfMatchingNodes( + plan, + node -> node instanceof ExchangeNode && ((ExchangeNode) node).getScope() == REMOTE)).isEqualTo(1); + + Consumer validateSingleStreamingAggregation = plan -> assertThat(countOfMatchingNodes( + plan, + node -> node instanceof AggregationNode + && ((AggregationNode) node).getGroupingKeys().contains(new Symbol("unique")) + && ((AggregationNode) node).isStreamable())).isEqualTo(1); // region is unpartitioned, AssignUniqueId should provide satisfying partitioning for count(*) after LEFT JOIN assertPlanWithSession( @@ -1336,11 +1317,9 @@ public void testUsesDistributedJoinIfNaturallyPartitionedOnProbeSymbols() exchange(REMOTE, REPARTITION, tableScan("region", ImmutableMap.of("RIGHT_REGIONKEY", "regionkey"))))))), plan -> // make sure there are only two remote exchanges (one in probe and one in build side) - assertEquals( - countOfMatchingNodes( - plan, - node -> node instanceof ExchangeNode && ((ExchangeNode) node).getScope() == REMOTE), - 2)); + assertThat(countOfMatchingNodes( + plan, + node -> node instanceof ExchangeNode && ((ExchangeNode) node).getScope() == REMOTE)).isEqualTo(2)); // replicated join is preserved if probe side is single node assertPlanWithSession( @@ -1688,11 +1667,11 @@ public void testWithTies() public void testRedundantLimitNodeRemoval() { String query = "SELECT count(*) FROM orders LIMIT 10"; - assertFalse( - searchFrom(plan(query, OPTIMIZED).getRoot()) - .where(LimitNode.class::isInstance) - .matches(), - format("Unexpected limit node for query: '%s'", query)); + assertThat(searchFrom(plan(query, OPTIMIZED).getRoot()) + .where(LimitNode.class::isInstance) + .matches()) + .describedAs(format("Unexpected limit node for query: '%s'", query)) + .isFalse(); assertPlan( "SELECT orderkey, count(*) FROM orders GROUP BY orderkey LIMIT 10", @@ -1740,11 +1719,11 @@ SELECT col FROM ( public void testRemoveSingleRowSort() { String query = "SELECT count(*) FROM orders ORDER BY 1"; - assertFalse( - searchFrom(plan(query, OPTIMIZED).getRoot()) - .whereIsInstanceOfAny(SortNode.class) - .matches(), - format("Unexpected sort node for query: '%s'", query)); + assertThat(searchFrom(plan(query, OPTIMIZED).getRoot()) + .whereIsInstanceOfAny(SortNode.class) + .matches()) + .describedAs(format("Unexpected sort node for query: '%s'", query)) + .isFalse(); assertPlan( "SELECT orderkey, count(*) FROM orders GROUP BY orderkey ORDER BY 1", @@ -1758,11 +1737,11 @@ public void testRemoveSingleRowSort() public void testRedundantTopNNodeRemoval() { String query = "SELECT count(*) FROM orders ORDER BY 1 LIMIT 10"; - assertFalse( - searchFrom(plan(query, OPTIMIZED).getRoot()) - .whereIsInstanceOfAny(TopNNode.class, SortNode.class) - .matches(), - format("Unexpected TopN node for query: '%s'", query)); + assertThat(searchFrom(plan(query, OPTIMIZED).getRoot()) + .whereIsInstanceOfAny(TopNNode.class, SortNode.class) + .matches()) + .describedAs(format("Unexpected TopN node for query: '%s'", query)) + .isFalse(); assertPlan( "SELECT orderkey, count(*) FROM orders GROUP BY orderkey ORDER BY 1 LIMIT 10", @@ -1789,11 +1768,11 @@ public void testRedundantTopNNodeRemoval() public void testRedundantDistinctLimitNodeRemoval() { String query = "SELECT distinct(c) FROM (SELECT count(*) as c FROM orders) LIMIT 10"; - assertFalse( - searchFrom(plan(query, OPTIMIZED).getRoot()) - .whereIsInstanceOfAny(DistinctLimitNode.class) - .matches(), - format("Unexpected DistinctLimit node for query: '%s'", query)); + assertThat(searchFrom(plan(query, OPTIMIZED).getRoot()) + .whereIsInstanceOfAny(DistinctLimitNode.class) + .matches()) + .describedAs(format("Unexpected DistinctLimit node for query: '%s'", query)) + .isFalse(); assertPlan( "SELECT distinct(c) FROM (SELECT count(*) as c FROM orders GROUP BY orderkey) LIMIT 10", diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestPartialTranslator.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestPartialTranslator.java index 74b13fee45c0..d38efafaf9b7 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestPartialTranslator.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestPartialTranslator.java @@ -51,7 +51,7 @@ import static io.trino.sql.planner.TypeAnalyzer.createTestingTypeAnalyzer; import static io.trino.sql.tree.ArithmeticBinaryExpression.Operator.ADD; import static io.trino.testing.TestingSession.testSessionBuilder; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestPartialTranslator { @@ -101,16 +101,16 @@ public void testPartialTranslator() private void assertPartialTranslation(Expression expression, List subexpressions) { Map, ConnectorExpression> translation = extractPartialTranslations(expression, TEST_SESSION, TYPE_ANALYZER, TYPE_PROVIDER, PLANNER_CONTEXT); - assertEquals(subexpressions.size(), translation.size()); + assertThat(subexpressions.size()).isEqualTo(translation.size()); for (Expression subexpression : subexpressions) { - assertEquals(translation.get(NodeRef.of(subexpression)), translate(TEST_SESSION, subexpression, TYPE_PROVIDER, PLANNER_CONTEXT, TYPE_ANALYZER).get()); + assertThat(translation.get(NodeRef.of(subexpression))).isEqualTo(translate(TEST_SESSION, subexpression, TYPE_PROVIDER, PLANNER_CONTEXT, TYPE_ANALYZER).get()); } } private void assertFullTranslation(Expression expression) { Map, ConnectorExpression> translation = extractPartialTranslations(expression, TEST_SESSION, TYPE_ANALYZER, TYPE_PROVIDER, PLANNER_CONTEXT); - assertEquals(getOnlyElement(translation.keySet()), NodeRef.of(expression)); - assertEquals(getOnlyElement(translation.values()), translate(TEST_SESSION, expression, TYPE_PROVIDER, PLANNER_CONTEXT, TYPE_ANALYZER).get()); + assertThat(getOnlyElement(translation.keySet())).isEqualTo(NodeRef.of(expression)); + assertThat(getOnlyElement(translation.values())).isEqualTo(translate(TEST_SESSION, expression, TYPE_PROVIDER, PLANNER_CONTEXT, TYPE_ANALYZER).get()); } } diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestSchedulingOrderVisitor.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestSchedulingOrderVisitor.java index 8b7c3fd1876c..071e52e09917 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestSchedulingOrderVisitor.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestSchedulingOrderVisitor.java @@ -32,7 +32,7 @@ import static io.trino.sql.planner.TestingPlannerContext.PLANNER_CONTEXT; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestSchedulingOrderVisitor { @@ -43,7 +43,7 @@ public void testJoinOrder() TableScanNode a = planBuilder.tableScan(emptyList(), emptyMap()); TableScanNode b = planBuilder.tableScan(emptyList(), emptyMap()); List order = scheduleOrder(planBuilder.join(JoinNode.Type.INNER, a, b)); - assertEquals(order, ImmutableList.of(b.getId(), a.getId())); + assertThat(order).isEqualTo(ImmutableList.of(b.getId(), a.getId())); } @Test @@ -53,7 +53,7 @@ public void testIndexJoinOrder() TableScanNode a = planBuilder.tableScan(emptyList(), emptyMap()); TableScanNode b = planBuilder.tableScan(emptyList(), emptyMap()); List order = scheduleOrder(planBuilder.indexJoin(IndexJoinNode.Type.INNER, a, b)); - assertEquals(order, ImmutableList.of(b.getId(), a.getId())); + assertThat(order).isEqualTo(ImmutableList.of(b.getId(), a.getId())); } @Test @@ -72,6 +72,6 @@ public void testSemiJoinOrder() Optional.empty(), a, b)); - assertEquals(order, ImmutableList.of(b.getId(), a.getId())); + assertThat(order).isEqualTo(ImmutableList.of(b.getId(), a.getId())); } } diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestSortExpressionExtractor.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestSortExpressionExtractor.java index 3c1f87b49b95..24e7aedd1679 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestSortExpressionExtractor.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestSortExpressionExtractor.java @@ -35,7 +35,7 @@ import static io.trino.spi.type.DoubleType.DOUBLE; import static io.trino.sql.ExpressionUtils.extractConjuncts; import static io.trino.sql.planner.TestingPlannerContext.plannerContextBuilder; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestSortExpressionExtractor { @@ -106,7 +106,7 @@ private void assertNoSortExpression(String expression) private void assertNoSortExpression(Expression expression) { Optional actual = SortExpressionExtractor.extractSortExpression(PLANNER_CONTEXT.getMetadata(), BUILD_SYMBOLS, expression); - assertEquals(actual, Optional.empty()); + assertThat(actual).isEqualTo(Optional.empty()); } private void assertGetSortExpression(String expression, String expectedSymbol) @@ -137,6 +137,6 @@ private void assertGetSortExpression(Expression expression, String expectedSymbo { Optional expected = Optional.of(new SortExpressionContext(new SymbolReference(expectedSymbol), searchExpressions)); Optional actual = SortExpressionExtractor.extractSortExpression(PLANNER_CONTEXT.getMetadata(), BUILD_SYMBOLS, expression); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } } diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestSymbolAllocator.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestSymbolAllocator.java index d1592c4a1e4b..645c9e07c100 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestSymbolAllocator.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestSymbolAllocator.java @@ -19,7 +19,7 @@ import java.util.Set; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestSymbolAllocator { @@ -34,6 +34,6 @@ public void testUnique() .add(allocator.newSymbol("foo", BigintType.BIGINT)) .build(); - assertEquals(symbols.size(), 4); + assertThat(symbols.size()).isEqualTo(4); } } diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestUnwrapYearInComparison.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestUnwrapYearInComparison.java index 409e634cd610..1e10645c5298 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestUnwrapYearInComparison.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestUnwrapYearInComparison.java @@ -36,7 +36,7 @@ import static java.lang.String.format; import static java.time.ZoneOffset.UTC; import static java.util.Arrays.asList; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestUnwrapYearInComparison extends BasePlanTest @@ -288,39 +288,19 @@ public void testLeapYear() @Test public void testCalculateRangeEndInclusive() { - assertEquals(calculateRangeEndInclusive(1960, DATE), LocalDate.of(1960, 12, 31).toEpochDay()); - assertEquals(calculateRangeEndInclusive(2024, DATE), LocalDate.of(2024, 12, 31).toEpochDay()); - - assertEquals( - calculateRangeEndInclusive(1960, TIMESTAMP_SECONDS), - toEpochMicros(LocalDateTime.of(1960, 12, 31, 23, 59, 59))); - assertEquals( - calculateRangeEndInclusive(1960, TIMESTAMP_MILLIS), - toEpochMicros(LocalDateTime.of(1960, 12, 31, 23, 59, 59, 999_000_000))); - assertEquals( - calculateRangeEndInclusive(1960, TIMESTAMP_MICROS), - toEpochMicros(LocalDateTime.of(1960, 12, 31, 23, 59, 59, 999_999_000))); - assertEquals( - calculateRangeEndInclusive(1960, TIMESTAMP_NANOS), - new LongTimestamp(toEpochMicros(LocalDateTime.of(1960, 12, 31, 23, 59, 59, 999_999_000)), 999_000)); - assertEquals( - calculateRangeEndInclusive(1960, TIMESTAMP_PICOS), - new LongTimestamp(toEpochMicros(LocalDateTime.of(1960, 12, 31, 23, 59, 59, 999_999_000)), 999_999)); - assertEquals( - calculateRangeEndInclusive(2024, TIMESTAMP_SECONDS), - toEpochMicros(LocalDateTime.of(2024, 12, 31, 23, 59, 59))); - assertEquals( - calculateRangeEndInclusive(2024, TIMESTAMP_MILLIS), - toEpochMicros(LocalDateTime.of(2024, 12, 31, 23, 59, 59, 999_000_000))); - assertEquals( - calculateRangeEndInclusive(2024, TIMESTAMP_MICROS), - toEpochMicros(LocalDateTime.of(2024, 12, 31, 23, 59, 59, 999_999_000))); - assertEquals( - calculateRangeEndInclusive(2024, TIMESTAMP_NANOS), - new LongTimestamp(toEpochMicros(LocalDateTime.of(2024, 12, 31, 23, 59, 59, 999_999_000)), 999_000)); - assertEquals( - calculateRangeEndInclusive(2024, TIMESTAMP_PICOS), - new LongTimestamp(toEpochMicros(LocalDateTime.of(2024, 12, 31, 23, 59, 59, 999_999_000)), 999_999)); + assertThat(calculateRangeEndInclusive(1960, DATE)).isEqualTo(LocalDate.of(1960, 12, 31).toEpochDay()); + assertThat(calculateRangeEndInclusive(2024, DATE)).isEqualTo(LocalDate.of(2024, 12, 31).toEpochDay()); + + assertThat(calculateRangeEndInclusive(1960, TIMESTAMP_SECONDS)).isEqualTo(toEpochMicros(LocalDateTime.of(1960, 12, 31, 23, 59, 59))); + assertThat(calculateRangeEndInclusive(1960, TIMESTAMP_MILLIS)).isEqualTo(toEpochMicros(LocalDateTime.of(1960, 12, 31, 23, 59, 59, 999_000_000))); + assertThat(calculateRangeEndInclusive(1960, TIMESTAMP_MICROS)).isEqualTo(toEpochMicros(LocalDateTime.of(1960, 12, 31, 23, 59, 59, 999_999_000))); + assertThat(calculateRangeEndInclusive(1960, TIMESTAMP_NANOS)).isEqualTo(new LongTimestamp(toEpochMicros(LocalDateTime.of(1960, 12, 31, 23, 59, 59, 999_999_000)), 999_000)); + assertThat(calculateRangeEndInclusive(1960, TIMESTAMP_PICOS)).isEqualTo(new LongTimestamp(toEpochMicros(LocalDateTime.of(1960, 12, 31, 23, 59, 59, 999_999_000)), 999_999)); + assertThat(calculateRangeEndInclusive(2024, TIMESTAMP_SECONDS)).isEqualTo(toEpochMicros(LocalDateTime.of(2024, 12, 31, 23, 59, 59))); + assertThat(calculateRangeEndInclusive(2024, TIMESTAMP_MILLIS)).isEqualTo(toEpochMicros(LocalDateTime.of(2024, 12, 31, 23, 59, 59, 999_000_000))); + assertThat(calculateRangeEndInclusive(2024, TIMESTAMP_MICROS)).isEqualTo(toEpochMicros(LocalDateTime.of(2024, 12, 31, 23, 59, 59, 999_999_000))); + assertThat(calculateRangeEndInclusive(2024, TIMESTAMP_NANOS)).isEqualTo(new LongTimestamp(toEpochMicros(LocalDateTime.of(2024, 12, 31, 23, 59, 59, 999_999_000)), 999_000)); + assertThat(calculateRangeEndInclusive(2024, TIMESTAMP_PICOS)).isEqualTo(new LongTimestamp(toEpochMicros(LocalDateTime.of(2024, 12, 31, 23, 59, 59, 999_999_000)), 999_999)); } private static long toEpochMicros(LocalDateTime localDateTime) diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/assertions/TestExpressionVerifier.java b/core/trino-main/src/test/java/io/trino/sql/planner/assertions/TestExpressionVerifier.java index 3b1fb518b9a4..e6f6685d6429 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/assertions/TestExpressionVerifier.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/assertions/TestExpressionVerifier.java @@ -19,9 +19,8 @@ import org.junit.jupiter.api.Test; import static io.trino.sql.ExpressionUtils.rewriteIdentifiersToSymbolReferences; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestExpressionVerifier { @@ -39,11 +38,11 @@ public void test() ExpressionVerifier verifier = new ExpressionVerifier(symbolAliases); - assertTrue(verifier.process(actual, expression("NOT(X = 3 AND Y = 3 AND X < 10)"))); + assertThat(verifier.process(actual, expression("NOT(X = 3 AND Y = 3 AND X < 10)"))).isTrue(); assertThatThrownBy(() -> verifier.process(actual, expression("NOT(X = 3 AND Y = 3 AND Z < 10)"))) .isInstanceOf(IllegalStateException.class) .hasMessage("missing expression for alias Z"); - assertFalse(verifier.process(actual, expression("NOT(X = 3 AND X = 3 AND X < 10)"))); + assertThat(verifier.process(actual, expression("NOT(X = 3 AND X = 3 AND X < 10)"))).isFalse(); } @Test @@ -54,9 +53,9 @@ public void testCast() .build(); ExpressionVerifier verifier = new ExpressionVerifier(aliases); - assertTrue(verifier.process(expression("VARCHAR '2'"), expression("VARCHAR '2'"))); - assertFalse(verifier.process(expression("VARCHAR '2'"), expression("CAST('2' AS bigint)"))); - assertTrue(verifier.process(expression("CAST(orderkey AS varchar)"), expression("CAST(X AS varchar)"))); + assertThat(verifier.process(expression("VARCHAR '2'"), expression("VARCHAR '2'"))).isTrue(); + assertThat(verifier.process(expression("VARCHAR '2'"), expression("CAST('2' AS bigint)"))).isFalse(); + assertThat(verifier.process(expression("CAST(orderkey AS varchar)"), expression("CAST(X AS varchar)"))).isTrue(); } @Test @@ -69,14 +68,14 @@ public void testBetween() ExpressionVerifier verifier = new ExpressionVerifier(symbolAliases); // Complete match - assertTrue(verifier.process(expression("orderkey BETWEEN 1 AND 2"), expression("X BETWEEN 1 AND 2"))); + assertThat(verifier.process(expression("orderkey BETWEEN 1 AND 2"), expression("X BETWEEN 1 AND 2"))).isTrue(); // Different value - assertFalse(verifier.process(expression("orderkey BETWEEN 1 AND 2"), expression("Y BETWEEN 1 AND 2"))); - assertFalse(verifier.process(expression("custkey BETWEEN 1 AND 2"), expression("X BETWEEN 1 AND 2"))); + assertThat(verifier.process(expression("orderkey BETWEEN 1 AND 2"), expression("Y BETWEEN 1 AND 2"))).isFalse(); + assertThat(verifier.process(expression("custkey BETWEEN 1 AND 2"), expression("X BETWEEN 1 AND 2"))).isFalse(); // Different min or max - assertFalse(verifier.process(expression("orderkey BETWEEN 2 AND 4"), expression("X BETWEEN 1 AND 2"))); - assertFalse(verifier.process(expression("orderkey BETWEEN 1 AND 2"), expression("X BETWEEN '1' AND '2'"))); - assertFalse(verifier.process(expression("orderkey BETWEEN 1 AND 2"), expression("X BETWEEN 4 AND 7"))); + assertThat(verifier.process(expression("orderkey BETWEEN 2 AND 4"), expression("X BETWEEN 1 AND 2"))).isFalse(); + assertThat(verifier.process(expression("orderkey BETWEEN 1 AND 2"), expression("X BETWEEN '1' AND '2'"))).isFalse(); + assertThat(verifier.process(expression("orderkey BETWEEN 1 AND 2"), expression("X BETWEEN 4 AND 7"))).isFalse(); } @Test @@ -89,39 +88,39 @@ public void testSymmetry() ExpressionVerifier verifier = new ExpressionVerifier(symbolAliases); - assertTrue(verifier.process(expression("x > y"), expression("a > b"))); - assertTrue(verifier.process(expression("x > y"), expression("b < a"))); - assertTrue(verifier.process(expression("y < x"), expression("a > b"))); - assertTrue(verifier.process(expression("y < x"), expression("b < a"))); - - assertFalse(verifier.process(expression("x < y"), expression("a > b"))); - assertFalse(verifier.process(expression("x < y"), expression("b < a"))); - assertFalse(verifier.process(expression("y > x"), expression("a > b"))); - assertFalse(verifier.process(expression("y > x"), expression("b < a"))); - - assertTrue(verifier.process(expression("x >= y"), expression("a >= b"))); - assertTrue(verifier.process(expression("x >= y"), expression("b <= a"))); - assertTrue(verifier.process(expression("y <= x"), expression("a >= b"))); - assertTrue(verifier.process(expression("y <= x"), expression("b <= a"))); - - assertFalse(verifier.process(expression("x <= y"), expression("a >= b"))); - assertFalse(verifier.process(expression("x <= y"), expression("b <= a"))); - assertFalse(verifier.process(expression("y >= x"), expression("a >= b"))); - assertFalse(verifier.process(expression("y >= x"), expression("b <= a"))); - - assertTrue(verifier.process(expression("x = y"), expression("a = b"))); - assertTrue(verifier.process(expression("x = y"), expression("b = a"))); - assertTrue(verifier.process(expression("y = x"), expression("a = b"))); - assertTrue(verifier.process(expression("y = x"), expression("b = a"))); - assertTrue(verifier.process(expression("x <> y"), expression("a <> b"))); - assertTrue(verifier.process(expression("x <> y"), expression("b <> a"))); - assertTrue(verifier.process(expression("y <> x"), expression("a <> b"))); - assertTrue(verifier.process(expression("y <> x"), expression("b <> a"))); - - assertTrue(verifier.process(expression("x IS DISTINCT FROM y"), expression("a IS DISTINCT FROM b"))); - assertTrue(verifier.process(expression("x IS DISTINCT FROM y"), expression("b IS DISTINCT FROM a"))); - assertTrue(verifier.process(expression("y IS DISTINCT FROM x"), expression("a IS DISTINCT FROM b"))); - assertTrue(verifier.process(expression("y IS DISTINCT FROM x"), expression("b IS DISTINCT FROM a"))); + assertThat(verifier.process(expression("x > y"), expression("a > b"))).isTrue(); + assertThat(verifier.process(expression("x > y"), expression("b < a"))).isTrue(); + assertThat(verifier.process(expression("y < x"), expression("a > b"))).isTrue(); + assertThat(verifier.process(expression("y < x"), expression("b < a"))).isTrue(); + + assertThat(verifier.process(expression("x < y"), expression("a > b"))).isFalse(); + assertThat(verifier.process(expression("x < y"), expression("b < a"))).isFalse(); + assertThat(verifier.process(expression("y > x"), expression("a > b"))).isFalse(); + assertThat(verifier.process(expression("y > x"), expression("b < a"))).isFalse(); + + assertThat(verifier.process(expression("x >= y"), expression("a >= b"))).isTrue(); + assertThat(verifier.process(expression("x >= y"), expression("b <= a"))).isTrue(); + assertThat(verifier.process(expression("y <= x"), expression("a >= b"))).isTrue(); + assertThat(verifier.process(expression("y <= x"), expression("b <= a"))).isTrue(); + + assertThat(verifier.process(expression("x <= y"), expression("a >= b"))).isFalse(); + assertThat(verifier.process(expression("x <= y"), expression("b <= a"))).isFalse(); + assertThat(verifier.process(expression("y >= x"), expression("a >= b"))).isFalse(); + assertThat(verifier.process(expression("y >= x"), expression("b <= a"))).isFalse(); + + assertThat(verifier.process(expression("x = y"), expression("a = b"))).isTrue(); + assertThat(verifier.process(expression("x = y"), expression("b = a"))).isTrue(); + assertThat(verifier.process(expression("y = x"), expression("a = b"))).isTrue(); + assertThat(verifier.process(expression("y = x"), expression("b = a"))).isTrue(); + assertThat(verifier.process(expression("x <> y"), expression("a <> b"))).isTrue(); + assertThat(verifier.process(expression("x <> y"), expression("b <> a"))).isTrue(); + assertThat(verifier.process(expression("y <> x"), expression("a <> b"))).isTrue(); + assertThat(verifier.process(expression("y <> x"), expression("b <> a"))).isTrue(); + + assertThat(verifier.process(expression("x IS DISTINCT FROM y"), expression("a IS DISTINCT FROM b"))).isTrue(); + assertThat(verifier.process(expression("x IS DISTINCT FROM y"), expression("b IS DISTINCT FROM a"))).isTrue(); + assertThat(verifier.process(expression("y IS DISTINCT FROM x"), expression("a IS DISTINCT FROM b"))).isTrue(); + assertThat(verifier.process(expression("y IS DISTINCT FROM x"), expression("b IS DISTINCT FROM a"))).isTrue(); } private Expression expression(String sql) diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/TestIterativeOptimizer.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/TestIterativeOptimizer.java index 4eefe4e2da99..a62e09c2d6d2 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/TestIterativeOptimizer.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/TestIterativeOptimizer.java @@ -44,10 +44,9 @@ import static io.trino.testing.TestingHandles.TEST_CATALOG_NAME; import static io.trino.testing.TestingSession.testSessionBuilder; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -73,12 +72,12 @@ public void optimizerQueryRulesStatsCollect() queryRunner.createPlan(transactionSession, "SELECT 1", ImmutableList.of(optimizer), OPTIMIZED_AND_VALIDATED, NOOP, planOptimizersStatsCollector)); Optional queryRuleStats = planOptimizersStatsCollector.getTopRuleStats().stream().findFirst(); - assertTrue(queryRuleStats.isPresent()); + assertThat(queryRuleStats.isPresent()).isTrue(); QueryPlanOptimizerStatistics queryRuleStat = queryRuleStats.get(); - assertEquals(queryRuleStat.rule(), RemoveRedundantIdentityProjections.class.getCanonicalName()); - assertEquals(queryRuleStat.invocations(), 4); - assertEquals(queryRuleStat.applied(), 3); - assertEquals(queryRuleStat.failures(), 0); + assertThat(queryRuleStat.rule()).isEqualTo(RemoveRedundantIdentityProjections.class.getCanonicalName()); + assertThat(queryRuleStat.invocations()).isEqualTo(4); + assertThat(queryRuleStat.applied()).isEqualTo(3); + assertThat(queryRuleStat.failures()).isEqualTo(0); } } diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/TestMemo.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/TestMemo.java index 1b41aa97122f..460391517227 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/TestMemo.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/TestMemo.java @@ -26,7 +26,7 @@ import java.util.Optional; import static com.google.common.collect.Iterables.getOnlyElement; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestMemo { @@ -38,7 +38,7 @@ public void testInitialization() PlanNode plan = node(node()); Memo memo = new Memo(idAllocator, plan); - assertEquals(memo.getGroupCount(), 2); + assertThat(memo.getGroupCount()).isEqualTo(2); assertMatchesStructure(plan, memo.extract()); } @@ -52,12 +52,12 @@ public void testReplaceSubtree() PlanNode plan = node(node(node())); Memo memo = new Memo(idAllocator, plan); - assertEquals(memo.getGroupCount(), 3); + assertThat(memo.getGroupCount()).isEqualTo(3); // replace child of root node with subtree PlanNode transformed = node(node()); memo.replace(getChildGroup(memo, memo.getRootGroup()), transformed, "rule"); - assertEquals(memo.getGroupCount(), 3); + assertThat(memo.getGroupCount()).isEqualTo(3); assertMatchesStructure(memo.extract(), node(plan.getId(), transformed)); } @@ -73,14 +73,14 @@ public void testReplaceNode() PlanNode x = node(y); Memo memo = new Memo(idAllocator, x); - assertEquals(memo.getGroupCount(), 3); + assertThat(memo.getGroupCount()).isEqualTo(3); // replace child of root node with another node, retaining child's child int yGroup = getChildGroup(memo, memo.getRootGroup()); GroupReference zRef = (GroupReference) getOnlyElement(memo.getNode(yGroup).getSources()); PlanNode transformed = node(zRef); memo.replace(yGroup, transformed, "rule"); - assertEquals(memo.getGroupCount(), 3); + assertThat(memo.getGroupCount()).isEqualTo(3); assertMatchesStructure(memo.extract(), node(x.getId(), node(transformed.getId(), z))); } @@ -98,7 +98,7 @@ public void testReplaceNonLeafSubtree() Memo memo = new Memo(idAllocator, x); - assertEquals(memo.getGroupCount(), 4); + assertThat(memo.getGroupCount()).isEqualTo(4); int yGroup = getChildGroup(memo, memo.getRootGroup()); int zGroup = getChildGroup(memo, yGroup); @@ -110,7 +110,7 @@ public void testReplaceNonLeafSubtree() memo.replace(yGroup, newY, "rule"); - assertEquals(memo.getGroupCount(), 4); + assertThat(memo.getGroupCount()).isEqualTo(4); assertMatchesStructure( memo.extract(), @@ -133,12 +133,12 @@ public void testRemoveNode() Memo memo = new Memo(idAllocator, x); - assertEquals(memo.getGroupCount(), 3); + assertThat(memo.getGroupCount()).isEqualTo(3); int yGroup = getChildGroup(memo, memo.getRootGroup()); memo.replace(yGroup, memo.getNode(yGroup).getSources().get(0), "rule"); - assertEquals(memo.getGroupCount(), 2); + assertThat(memo.getGroupCount()).isEqualTo(2); assertMatchesStructure( memo.extract(), @@ -158,13 +158,13 @@ public void testInsertNode() Memo memo = new Memo(idAllocator, x); - assertEquals(memo.getGroupCount(), 2); + assertThat(memo.getGroupCount()).isEqualTo(2); int zGroup = getChildGroup(memo, memo.getRootGroup()); PlanNode y = node(memo.getNode(zGroup)); memo.replace(zGroup, y, "rule"); - assertEquals(memo.getGroupCount(), 3); + assertThat(memo.getGroupCount()).isEqualTo(3); assertMatchesStructure( memo.extract(), @@ -186,7 +186,7 @@ public void testMultipleReferences() PlanNode x = node(y); Memo memo = new Memo(idAllocator, x); - assertEquals(memo.getGroupCount(), 3); + assertThat(memo.getGroupCount()).isEqualTo(3); int yGroup = getChildGroup(memo, memo.getRootGroup()); @@ -196,7 +196,7 @@ public void testMultipleReferences() PlanNode newX = node(y1, y2); memo.replace(memo.getRootGroup(), newX, "rule"); - assertEquals(memo.getGroupCount(), 4); + assertThat(memo.getGroupCount()).isEqualTo(4); assertMatchesStructure( memo.extract(), @@ -220,13 +220,13 @@ public void testEvictStatsOnReplace() memo.storeStats(yGroup, yStats); memo.storeStats(xGroup, xStats); - assertEquals(memo.getStats(yGroup), Optional.of(yStats)); - assertEquals(memo.getStats(xGroup), Optional.of(xStats)); + assertThat(memo.getStats(yGroup)).isEqualTo(Optional.of(yStats)); + assertThat(memo.getStats(xGroup)).isEqualTo(Optional.of(xStats)); memo.replace(yGroup, node(), "rule"); - assertEquals(memo.getStats(yGroup), Optional.empty()); - assertEquals(memo.getStats(xGroup), Optional.empty()); + assertThat(memo.getStats(yGroup)).isEqualTo(Optional.empty()); + assertThat(memo.getStats(xGroup)).isEqualTo(Optional.empty()); } @Test @@ -244,20 +244,20 @@ public void testEvictCostOnReplace() memo.storeCost(yGroup, yCost); memo.storeCost(xGroup, xCost); - assertEquals(memo.getCost(yGroup), Optional.of(yCost)); - assertEquals(memo.getCost(xGroup), Optional.of(xCost)); + assertThat(memo.getCost(yGroup)).isEqualTo(Optional.of(yCost)); + assertThat(memo.getCost(xGroup)).isEqualTo(Optional.of(xCost)); memo.replace(yGroup, node(), "rule"); - assertEquals(memo.getCost(yGroup), Optional.empty()); - assertEquals(memo.getCost(xGroup), Optional.empty()); + assertThat(memo.getCost(yGroup)).isEqualTo(Optional.empty()); + assertThat(memo.getCost(xGroup)).isEqualTo(Optional.empty()); } private static void assertMatchesStructure(PlanNode actual, PlanNode expected) { - assertEquals(actual.getClass(), expected.getClass()); - assertEquals(actual.getId(), expected.getId()); - assertEquals(actual.getSources().size(), expected.getSources().size()); + assertThat(actual.getClass()).isEqualTo(expected.getClass()); + assertThat(actual.getId()).isEqualTo(expected.getId()); + assertThat(actual.getSources().size()).isEqualTo(expected.getSources().size()); for (int i = 0; i < actual.getSources().size(); i++) { assertMatchesStructure(actual.getSources().get(i), expected.getSources().get(i)); diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/TestRuleIndex.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/TestRuleIndex.java index 3124c7ac2a2f..703a2699690e 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/TestRuleIndex.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/TestRuleIndex.java @@ -30,7 +30,7 @@ import static io.trino.SessionTestUtils.TEST_SESSION; import static io.trino.sql.planner.TestingPlannerContext.PLANNER_CONTEXT; import static java.util.stream.Collectors.toSet; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestRuleIndex { @@ -55,15 +55,9 @@ public void testWithPlanNodeHierarchy() FilterNode filterNode = planBuilder.filter(BooleanLiteral.TRUE_LITERAL, planBuilder.values()); ValuesNode valuesNode = planBuilder.values(); - assertEquals( - ruleIndex.getCandidates(projectNode).collect(toSet()), - ImmutableSet.of(projectRule1, projectRule2, anyRule)); - assertEquals( - ruleIndex.getCandidates(filterNode).collect(toSet()), - ImmutableSet.of(filterRule, anyRule)); - assertEquals( - ruleIndex.getCandidates(valuesNode).collect(toSet()), - ImmutableSet.of(anyRule)); + assertThat(ruleIndex.getCandidates(projectNode).collect(toSet())).isEqualTo(ImmutableSet.of(projectRule1, projectRule2, anyRule)); + assertThat(ruleIndex.getCandidates(filterNode).collect(toSet())).isEqualTo(ImmutableSet.of(filterRule, anyRule)); + assertThat(ruleIndex.getCandidates(valuesNode).collect(toSet())).isEqualTo(ImmutableSet.of(anyRule)); } @Test @@ -79,15 +73,9 @@ public void testInterfacesHierarchy() .register(ab) .build(); - assertEquals( - ruleIndex.getCandidates(new A() {}).collect(toSet()), - ImmutableSet.of(a)); - assertEquals( - ruleIndex.getCandidates(new B() {}).collect(toSet()), - ImmutableSet.of(b)); - assertEquals( - ruleIndex.getCandidates(new AB()).collect(toSet()), - ImmutableSet.of(ab, a, b)); + assertThat(ruleIndex.getCandidates(new A() { }).collect(toSet())).isEqualTo(ImmutableSet.of(a)); + assertThat(ruleIndex.getCandidates(new B() { }).collect(toSet())).isEqualTo(ImmutableSet.of(b)); + assertThat(ruleIndex.getCandidates(new AB()).collect(toSet())).isEqualTo(ImmutableSet.of(ab, a, b)); } private static class NoOpRule diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestDetermineJoinDistributionType.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestDetermineJoinDistributionType.java index bca8c37d7fb5..daf1387bd505 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestDetermineJoinDistributionType.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestDetermineJoinDistributionType.java @@ -65,10 +65,9 @@ import static io.trino.sql.planner.plan.JoinNode.Type.LEFT; import static io.trino.sql.planner.plan.JoinNode.Type.RIGHT; import static io.trino.sql.tree.BooleanLiteral.TRUE_LITERAL; -import static java.lang.Double.NaN; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -906,13 +905,11 @@ public void testGetSourceTablesSizeInBytes() Symbol sourceSymbol2 = planBuilder.symbol("soruce2"); // missing source stats - assertEquals( - getSourceTablesSizeInBytes( - planBuilder.values(symbol), - noLookup(), - node -> PlanNodeStatsEstimate.unknown(), - planBuilder.getTypes()), - NaN); + assertThat(getSourceTablesSizeInBytes( + planBuilder.values(symbol), + noLookup(), + node -> PlanNodeStatsEstimate.unknown(), + planBuilder.getTypes())).isNaN(); // two source plan nodes PlanNodeStatsEstimate sourceStatsEstimate1 = PlanNodeStatsEstimate.builder() @@ -921,56 +918,50 @@ public void testGetSourceTablesSizeInBytes() PlanNodeStatsEstimate sourceStatsEstimate2 = PlanNodeStatsEstimate.builder() .setOutputRowCount(20) .build(); - assertEquals( - getSourceTablesSizeInBytes( - planBuilder.union( - ImmutableListMultimap.builder() - .put(symbol, sourceSymbol1) - .put(symbol, sourceSymbol2) - .build(), - ImmutableList.of( - planBuilder.tableScan( - ImmutableList.of(sourceSymbol1), - ImmutableMap.of(sourceSymbol1, new TestingColumnHandle("col"))), - planBuilder.values(new PlanNodeId("valuesNode"), sourceSymbol2))), - noLookup(), - node -> { - if (node instanceof TableScanNode) { - return sourceStatsEstimate1; - } + assertThat(getSourceTablesSizeInBytes( + planBuilder.union( + ImmutableListMultimap.builder() + .put(symbol, sourceSymbol1) + .put(symbol, sourceSymbol2) + .build(), + ImmutableList.of( + planBuilder.tableScan( + ImmutableList.of(sourceSymbol1), + ImmutableMap.of(sourceSymbol1, new TestingColumnHandle("col"))), + planBuilder.values(new PlanNodeId("valuesNode"), sourceSymbol2))), + noLookup(), + node -> { + if (node instanceof TableScanNode) { + return sourceStatsEstimate1; + } - if (node instanceof ValuesNode) { - return sourceStatsEstimate2; - } + if (node instanceof ValuesNode) { + return sourceStatsEstimate2; + } - return PlanNodeStatsEstimate.unknown(); - }, - planBuilder.getTypes()), - 270.0); + return PlanNodeStatsEstimate.unknown(); + }, + planBuilder.getTypes())).isEqualTo(270.0); // join node - assertEquals( - getSourceTablesSizeInBytes( - planBuilder.join( - INNER, - planBuilder.values(sourceSymbol1), - planBuilder.values(sourceSymbol2)), - noLookup(), - node -> sourceStatsEstimate1, - planBuilder.getTypes()), - NaN); + assertThat(getSourceTablesSizeInBytes( + planBuilder.join( + INNER, + planBuilder.values(sourceSymbol1), + planBuilder.values(sourceSymbol2)), + noLookup(), + node -> sourceStatsEstimate1, + planBuilder.getTypes())).isNaN(); // unnest node - assertEquals( - getSourceTablesSizeInBytes( - planBuilder.unnest( - ImmutableList.of(), - ImmutableList.of(new UnnestNode.Mapping(sourceSymbol1, ImmutableList.of(sourceSymbol1))), - planBuilder.values(sourceSymbol1)), - noLookup(), - node -> sourceStatsEstimate1, - planBuilder.getTypes()), - NaN); + assertThat(getSourceTablesSizeInBytes( + planBuilder.unnest( + ImmutableList.of(), + ImmutableList.of(new UnnestNode.Mapping(sourceSymbol1, ImmutableList.of(sourceSymbol1))), + planBuilder.values(sourceSymbol1)), + noLookup(), + node -> sourceStatsEstimate1, + planBuilder.getTypes())).isNaN(); } @Test @@ -982,13 +973,11 @@ public void testGetApproximateSourceSizeInBytes() Symbol sourceSymbol2 = planBuilder.symbol("source2"); // missing source stats - assertEquals( - getFirstKnownOutputSizeInBytes( - planBuilder.values(symbol), - noLookup(), - node -> PlanNodeStatsEstimate.unknown(), - planBuilder.getTypes()), - NaN); + assertThat(getFirstKnownOutputSizeInBytes( + planBuilder.values(symbol), + noLookup(), + node -> PlanNodeStatsEstimate.unknown(), + planBuilder.getTypes())).isNaN(); // two source plan nodes PlanNodeStatsEstimate sourceStatsEstimate1 = PlanNodeStatsEstimate.builder() @@ -1007,127 +996,117 @@ public void testGetApproximateSourceSizeInBytes() double unionInputRowCount = filterStatsEstimate.getOutputRowCount() + limitStatsEstimate.getOutputRowCount(); double sourceSizeInBytes = sourceRowCount + sourceRowCount * BIGINT.getFixedSize(); // un-estimated union with non-expanding source - assertEquals( - getFirstKnownOutputSizeInBytes( - planBuilder.union( - ImmutableListMultimap.builder() - .put(symbol, sourceSymbol1) - .put(symbol, sourceSymbol2) - .build(), - ImmutableList.of( - planBuilder.filter( - TRUE_LITERAL, - planBuilder.tableScan( - ImmutableList.of(sourceSymbol1), - ImmutableMap.of(sourceSymbol1, new TestingColumnHandle("col")))), - planBuilder.limit(20, planBuilder.values(sourceSymbol2)))), - noLookup(), - node -> { - if (node instanceof TableScanNode) { - return sourceStatsEstimate1; - } - if (node instanceof FilterNode) { - return filterStatsEstimate; - } - if (node instanceof ValuesNode) { - return sourceStatsEstimate2; - } - if (node instanceof LimitNode) { - return limitStatsEstimate; - } + assertThat(getFirstKnownOutputSizeInBytes( + planBuilder.union( + ImmutableListMultimap.builder() + .put(symbol, sourceSymbol1) + .put(symbol, sourceSymbol2) + .build(), + ImmutableList.of( + planBuilder.filter( + TRUE_LITERAL, + planBuilder.tableScan( + ImmutableList.of(sourceSymbol1), + ImmutableMap.of(sourceSymbol1, new TestingColumnHandle("col")))), + planBuilder.limit(20, planBuilder.values(sourceSymbol2)))), + noLookup(), + node -> { + if (node instanceof TableScanNode) { + return sourceStatsEstimate1; + } + if (node instanceof FilterNode) { + return filterStatsEstimate; + } + if (node instanceof ValuesNode) { + return sourceStatsEstimate2; + } + if (node instanceof LimitNode) { + return limitStatsEstimate; + } - return PlanNodeStatsEstimate.unknown(); - }, - planBuilder.getTypes()), - (unionInputRowCount / sourceRowCount) * sourceSizeInBytes); + return PlanNodeStatsEstimate.unknown(); + }, + planBuilder.getTypes())).isEqualTo((unionInputRowCount / sourceRowCount) * sourceSizeInBytes); // join node with known estimate - assertEquals( - getFirstKnownOutputSizeInBytes( - planBuilder.join( - INNER, - planBuilder.values(sourceSymbol1), - planBuilder.values(sourceSymbol2)), - noLookup(), - node -> sourceStatsEstimate1, - planBuilder.getTypes()), - sourceStatsEstimate1.getOutputRowCount() * 2 * (BIGINT.getFixedSize() + 1)); + assertThat(getFirstKnownOutputSizeInBytes( + planBuilder.join( + INNER, + planBuilder.values(sourceSymbol1), + planBuilder.values(sourceSymbol2)), + noLookup(), + node -> sourceStatsEstimate1, + planBuilder.getTypes())).isEqualTo(sourceStatsEstimate1.getOutputRowCount() * 2 * (BIGINT.getFixedSize() + 1)); // un-estimated join with non-expanding source - assertEquals( - getFirstKnownOutputSizeInBytes( - planBuilder.join( - INNER, - planBuilder.tableScan( - ImmutableList.of(sourceSymbol1), - ImmutableMap.of(sourceSymbol1, new TestingColumnHandle("col"))), - planBuilder.values(sourceSymbol2)), - noLookup(), - node -> { - if (node instanceof TableScanNode) { - return sourceStatsEstimate1; - } - if (node instanceof ValuesNode) { - return sourceStatsEstimate2; - } + assertThat(getFirstKnownOutputSizeInBytes( + planBuilder.join( + INNER, + planBuilder.tableScan( + ImmutableList.of(sourceSymbol1), + ImmutableMap.of(sourceSymbol1, new TestingColumnHandle("col"))), + planBuilder.values(sourceSymbol2)), + noLookup(), + node -> { + if (node instanceof TableScanNode) { + return sourceStatsEstimate1; + } + if (node instanceof ValuesNode) { + return sourceStatsEstimate2; + } - return PlanNodeStatsEstimate.unknown(); - }, - planBuilder.getTypes()), - NaN); + return PlanNodeStatsEstimate.unknown(); + }, + planBuilder.getTypes())).isNaN(); // un-estimated union with estimated expanding source - assertEquals( - getFirstKnownOutputSizeInBytes( - planBuilder.union( - ImmutableListMultimap.builder() - .put(symbol, sourceSymbol1) - .put(symbol, sourceSymbol2) - .build(), - ImmutableList.of( - planBuilder.unnest( - ImmutableList.of(), - ImmutableList.of(new UnnestNode.Mapping(sourceSymbol1, ImmutableList.of(sourceSymbol1))), - planBuilder.values(sourceSymbol1)), - planBuilder.values(sourceSymbol2))), - noLookup(), - node -> { - if (node instanceof UnnestNode) { - return sourceStatsEstimate1; - } - if (node instanceof ValuesNode) { - return sourceStatsEstimate2; - } + assertThat(getFirstKnownOutputSizeInBytes( + planBuilder.union( + ImmutableListMultimap.builder() + .put(symbol, sourceSymbol1) + .put(symbol, sourceSymbol2) + .build(), + ImmutableList.of( + planBuilder.unnest( + ImmutableList.of(), + ImmutableList.of(new UnnestNode.Mapping(sourceSymbol1, ImmutableList.of(sourceSymbol1))), + planBuilder.values(sourceSymbol1)), + planBuilder.values(sourceSymbol2))), + noLookup(), + node -> { + if (node instanceof UnnestNode) { + return sourceStatsEstimate1; + } + if (node instanceof ValuesNode) { + return sourceStatsEstimate2; + } - return PlanNodeStatsEstimate.unknown(); - }, - planBuilder.getTypes()), - sourceSizeInBytes); + return PlanNodeStatsEstimate.unknown(); + }, + planBuilder.getTypes())).isEqualTo(sourceSizeInBytes); // un-estimated union with un-estimated expanding source - assertEquals( - getFirstKnownOutputSizeInBytes( - planBuilder.union( - ImmutableListMultimap.builder() - .put(symbol, sourceSymbol1) - .put(symbol, sourceSymbol2) - .build(), - ImmutableList.of( - planBuilder.unnest( - ImmutableList.of(), - ImmutableList.of(new UnnestNode.Mapping(sourceSymbol1, ImmutableList.of(sourceSymbol1))), - planBuilder.values(sourceSymbol1)), - planBuilder.values(sourceSymbol2))), - noLookup(), - node -> { - if (node instanceof ValuesNode) { - return sourceStatsEstimate2; - } + assertThat(getFirstKnownOutputSizeInBytes( + planBuilder.union( + ImmutableListMultimap.builder() + .put(symbol, sourceSymbol1) + .put(symbol, sourceSymbol2) + .build(), + ImmutableList.of( + planBuilder.unnest( + ImmutableList.of(), + ImmutableList.of(new UnnestNode.Mapping(sourceSymbol1, ImmutableList.of(sourceSymbol1))), + planBuilder.values(sourceSymbol1)), + planBuilder.values(sourceSymbol2))), + noLookup(), + node -> { + if (node instanceof ValuesNode) { + return sourceStatsEstimate2; + } - return PlanNodeStatsEstimate.unknown(); - }, - planBuilder.getTypes()), - NaN); + return PlanNodeStatsEstimate.unknown(); + }, + planBuilder.getTypes())).isNaN(); } private RuleBuilder assertDetermineJoinDistributionType() diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestEliminateCrossJoins.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestEliminateCrossJoins.java index bc81568fc566..ba3b354b8b56 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestEliminateCrossJoins.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestEliminateCrossJoins.java @@ -56,9 +56,7 @@ import static io.trino.sql.tree.ArithmeticBinaryExpression.Operator.ADD; import static io.trino.sql.tree.ArithmeticUnaryExpression.Sign.MINUS; import static io.trino.testing.TestingSession.testSessionBuilder; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestEliminateCrossJoins extends BaseRuleTest @@ -108,8 +106,8 @@ public void testDoNotReorderOuterJoin() @Test public void testIsOriginalOrder() { - assertTrue(isOriginalOrder(ImmutableList.of(0, 1, 2, 3, 4))); - assertFalse(isOriginalOrder(ImmutableList.of(0, 2, 1, 3, 4))); + assertThat(isOriginalOrder(ImmutableList.of(0, 1, 2, 3, 4))).isTrue(); + assertThat(isOriginalOrder(ImmutableList.of(0, 2, 1, 3, 4))).isFalse(); } @Test @@ -128,9 +126,7 @@ public void testJoinOrder() JoinGraph joinGraph = JoinGraph.buildFrom(tester().getPlannerContext(), plan, noLookup(), new PlanNodeIdAllocator(), session, createTestingTypeAnalyzer(tester().getPlannerContext()), TypeProvider.empty()); - assertEquals( - getJoinOrder(joinGraph), - ImmutableList.of(0, 2, 1)); + assertThat(getJoinOrder(joinGraph)).isEqualTo(ImmutableList.of(0, 2, 1)); } @Test @@ -160,9 +156,7 @@ public void testJoinOrderWithRealCrossJoin() JoinGraph joinGraph = JoinGraph.buildFrom(tester().getPlannerContext(), plan, noLookup(), new PlanNodeIdAllocator(), session, createTestingTypeAnalyzer(tester().getPlannerContext()), TypeProvider.empty()); - assertEquals( - getJoinOrder(joinGraph), - ImmutableList.of(0, 2, 1, 3, 5, 4)); + assertThat(getJoinOrder(joinGraph)).isEqualTo(ImmutableList.of(0, 2, 1, 3, 5, 4)); } @Test @@ -182,9 +176,7 @@ public void testJoinOrderWithMultipleEdgesBetweenNodes() JoinGraph joinGraph = JoinGraph.buildFrom(tester().getPlannerContext(), plan, noLookup(), new PlanNodeIdAllocator(), session, createTestingTypeAnalyzer(tester().getPlannerContext()), TypeProvider.empty()); - assertEquals( - getJoinOrder(joinGraph), - ImmutableList.of(0, 2, 1)); + assertThat(getJoinOrder(joinGraph)).isEqualTo(ImmutableList.of(0, 2, 1)); } @Test @@ -203,9 +195,7 @@ public void testDoesNotChangeOrderWithoutCrossJoin() JoinGraph joinGraph = JoinGraph.buildFrom(tester().getPlannerContext(), plan, noLookup(), new PlanNodeIdAllocator(), session, createTestingTypeAnalyzer(tester().getPlannerContext()), TypeProvider.empty()); - assertEquals( - getJoinOrder(joinGraph), - ImmutableList.of(0, 1, 2)); + assertThat(getJoinOrder(joinGraph)).isEqualTo(ImmutableList.of(0, 1, 2)); } @Test @@ -223,9 +213,7 @@ public void testDoNotReorderCrossJoins() JoinGraph joinGraph = JoinGraph.buildFrom(tester().getPlannerContext(), plan, noLookup(), new PlanNodeIdAllocator(), session, createTestingTypeAnalyzer(tester().getPlannerContext()), TypeProvider.empty()); - assertEquals( - getJoinOrder(joinGraph), - ImmutableList.of(0, 1, 2)); + assertThat(getJoinOrder(joinGraph)).isEqualTo(ImmutableList.of(0, 1, 2)); } @Test @@ -310,7 +298,7 @@ public void testGiveUpOnComplexProjections() "a2", "c", "b", "c"); - assertEquals(JoinGraph.buildFrom(tester().getPlannerContext(), plan, noLookup(), new PlanNodeIdAllocator(), session, createTestingTypeAnalyzer(tester().getPlannerContext()), TypeProvider.empty()).size(), 2); + assertThat(JoinGraph.buildFrom(tester().getPlannerContext(), plan, noLookup(), new PlanNodeIdAllocator(), session, createTestingTypeAnalyzer(tester().getPlannerContext()), TypeProvider.empty()).size()).isEqualTo(2); } private Function crossJoinAndJoin(JoinNode.Type secondJoinType) diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestGetSourceTablesRowCount.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestGetSourceTablesRowCount.java index 51862641dd67..ac6c54d5a04a 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestGetSourceTablesRowCount.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestGetSourceTablesRowCount.java @@ -35,8 +35,7 @@ import static io.trino.sql.planner.iterative.Lookup.noLookup; import static io.trino.sql.planner.plan.JoinNode.Type.INNER; import static io.trino.testing.TestingSession.testSessionBuilder; -import static java.lang.Double.NaN; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestGetSourceTablesRowCount { @@ -46,14 +45,12 @@ public void testMissingSourceStats() PlanBuilder planBuilder = planBuilder(); Symbol symbol = planBuilder.symbol("col"); - assertEquals( - getSourceTablesRowCount( - planBuilder.tableScan( - tableScan -> tableScan - .setSymbols(ImmutableList.of(symbol)) - .setAssignments(ImmutableMap.of(symbol, new TestingColumnHandle("col"))) - .setStatistics(Optional.of(unknown())))), - NaN); + assertThat(getSourceTablesRowCount( + planBuilder.tableScan( + tableScan -> tableScan + .setSymbols(ImmutableList.of(symbol)) + .setAssignments(ImmutableMap.of(symbol, new TestingColumnHandle("col"))) + .setStatistics(Optional.of(unknown()))))).isNaN(); } @Test @@ -64,21 +61,19 @@ public void testTwoSourcePlanNodes() Symbol sourceSymbol1 = planBuilder.symbol("source1"); Symbol sourceSymbol2 = planBuilder.symbol("soruce2"); - assertEquals( - getSourceTablesRowCount( - planBuilder.union( - ImmutableListMultimap.builder() - .put(symbol, sourceSymbol1) - .put(symbol, sourceSymbol2) - .build(), - ImmutableList.of( - planBuilder.tableScan( - tableScan -> tableScan - .setSymbols(ImmutableList.of(sourceSymbol1)) - .setAssignments(ImmutableMap.of(sourceSymbol1, new TestingColumnHandle("col"))) - .setStatistics(Optional.of(stats(10)))), - planBuilder.values(new PlanNodeId("valuesNode"), 20, sourceSymbol2)))), - 30.0); + assertThat(getSourceTablesRowCount( + planBuilder.union( + ImmutableListMultimap.builder() + .put(symbol, sourceSymbol1) + .put(symbol, sourceSymbol2) + .build(), + ImmutableList.of( + planBuilder.tableScan( + tableScan -> tableScan + .setSymbols(ImmutableList.of(sourceSymbol1)) + .setAssignments(ImmutableMap.of(sourceSymbol1, new TestingColumnHandle("col"))) + .setStatistics(Optional.of(stats(10)))), + planBuilder.values(new PlanNodeId("valuesNode"), 20, sourceSymbol2))))).isEqualTo(30.0); } @Test @@ -88,13 +83,11 @@ public void testJoinNode() Symbol sourceSymbol1 = planBuilder.symbol("source1"); Symbol sourceSymbol2 = planBuilder.symbol("soruce2"); - assertEquals( - getSourceTablesRowCount( - planBuilder.join( - INNER, - planBuilder.values(sourceSymbol1), - planBuilder.values(sourceSymbol2))), - NaN); + assertThat(getSourceTablesRowCount( + planBuilder.join( + INNER, + planBuilder.values(sourceSymbol1), + planBuilder.values(sourceSymbol2)))).isNaN(); } private double getSourceTablesRowCount(PlanNode planNode) diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestJoinEnumerator.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestJoinEnumerator.java index 7aaff34c6a79..3ace9a010d2a 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestJoinEnumerator.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestJoinEnumerator.java @@ -49,10 +49,9 @@ import static io.trino.sql.planner.iterative.rule.ReorderJoins.JoinEnumerator.generatePartitions; import static io.trino.sql.tree.BooleanLiteral.TRUE_LITERAL; import static io.trino.testing.TestingSession.testSessionBuilder; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -76,21 +75,19 @@ public void tearDown() @Test public void testGeneratePartitions() { - assertEquals(generatePartitions(4), - ImmutableSet.of( - ImmutableSet.of(0), - ImmutableSet.of(0, 1), - ImmutableSet.of(0, 2), - ImmutableSet.of(0, 3), - ImmutableSet.of(0, 1, 2), - ImmutableSet.of(0, 1, 3), - ImmutableSet.of(0, 2, 3))); - - assertEquals(generatePartitions(3), - ImmutableSet.of( - ImmutableSet.of(0), - ImmutableSet.of(0, 1), - ImmutableSet.of(0, 2))); + assertThat(generatePartitions(4)).isEqualTo(ImmutableSet.of( + ImmutableSet.of(0), + ImmutableSet.of(0, 1), + ImmutableSet.of(0, 2), + ImmutableSet.of(0, 3), + ImmutableSet.of(0, 1, 2), + ImmutableSet.of(0, 1, 3), + ImmutableSet.of(0, 2, 3))); + + assertThat(generatePartitions(3)).isEqualTo(ImmutableSet.of( + ImmutableSet.of(0), + ImmutableSet.of(0, 1), + ImmutableSet.of(0, 2))); } @Test @@ -111,8 +108,8 @@ public void testDoesNotCreateJoinWhenPartitionedOnCrossJoin() multiJoinNode.getFilter(), createContext()); JoinEnumerationResult actual = joinEnumerator.createJoinAccordingToPartitioning(multiJoinNode.getSources(), multiJoinNode.getOutputSymbols(), ImmutableSet.of(0)); - assertFalse(actual.getPlanNode().isPresent()); - assertEquals(actual.getCost(), PlanCostEstimate.infinite()); + assertThat(actual.getPlanNode().isPresent()).isFalse(); + assertThat(actual.getCost()).isEqualTo(PlanCostEstimate.infinite()); } private Rule.Context createContext() diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestJoinNodeFlattener.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestJoinNodeFlattener.java index 88b936f1507d..2e0ecd5faa20 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestJoinNodeFlattener.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestJoinNodeFlattener.java @@ -66,12 +66,10 @@ import static io.trino.sql.tree.ComparisonExpression.Operator.LESS_THAN; import static io.trino.sql.tree.ComparisonExpression.Operator.NOT_EQUAL; import static io.trino.testing.TestingSession.testSessionBuilder; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -144,9 +142,7 @@ public void testDoesNotConvertNestedOuterJoins() .setSources(leftJoin, valuesC).setFilter(createEqualsExpression(a1, c1)) .setOutputSymbols(a1, b1, c1) .build(); - assertEquals( - toMultiJoinNode(queryRunner.getPlannerContext(), joinNode, noLookup(), planNodeIdAllocator, DEFAULT_JOIN_LIMIT, false, testSessionBuilder().build(), createTestingTypeAnalyzer(queryRunner.getPlannerContext()), p.getTypes()), - expected); + assertThat(toMultiJoinNode(queryRunner.getPlannerContext(), joinNode, noLookup(), planNodeIdAllocator, DEFAULT_JOIN_LIMIT, false, testSessionBuilder().build(), createTestingTypeAnalyzer(queryRunner.getPlannerContext()), p.getTypes())).isEqualTo(expected); } @Test @@ -173,9 +169,9 @@ public void testPushesProjectionsThroughJoin() valuesC, equiJoinClause(d, c)); MultiJoinNode actual = toMultiJoinNode(queryRunner.getPlannerContext(), joinNode, noLookup(), planNodeIdAllocator, DEFAULT_JOIN_LIMIT, true, testSessionBuilder().build(), createTestingTypeAnalyzer(queryRunner.getPlannerContext()), p.getTypes()); - assertEquals(actual.getOutputSymbols(), ImmutableList.of(d, c)); - assertEquals(actual.getFilter(), and(createEqualsExpression(a, b), createEqualsExpression(d, c))); - assertTrue(actual.isPushedProjectionThroughJoin()); + assertThat(actual.getOutputSymbols()).isEqualTo(ImmutableList.of(d, c)); + assertThat(actual.getFilter()).isEqualTo(and(createEqualsExpression(a, b), createEqualsExpression(d, c))); + assertThat(actual.isPushedProjectionThroughJoin()).isTrue(); List actualSources = ImmutableList.copyOf(actual.getSources()); assertPlan( @@ -219,9 +215,9 @@ public void testDoesNotPushStraddlingProjection() valuesC, equiJoinClause(d, c)); MultiJoinNode actual = toMultiJoinNode(queryRunner.getPlannerContext(), joinNode, noLookup(), planNodeIdAllocator, DEFAULT_JOIN_LIMIT, true, testSessionBuilder().build(), createTestingTypeAnalyzer(queryRunner.getPlannerContext()), p.getTypes()); - assertEquals(actual.getOutputSymbols(), ImmutableList.of(d, c)); - assertEquals(actual.getFilter(), createEqualsExpression(d, c)); - assertFalse(actual.isPushedProjectionThroughJoin()); + assertThat(actual.getOutputSymbols()).isEqualTo(ImmutableList.of(d, c)); + assertThat(actual.getFilter()).isEqualTo(createEqualsExpression(d, c)); + assertThat(actual.isPushedProjectionThroughJoin()).isFalse(); List actualSources = ImmutableList.copyOf(actual.getSources()); assertPlan( @@ -269,9 +265,7 @@ public void testRetainsOutputSymbols() .setFilter(and(createEqualsExpression(b1, c1), createEqualsExpression(a1, b1))) .setOutputSymbols(a1, b1) .build(); - assertEquals( - toMultiJoinNode(queryRunner.getPlannerContext(), joinNode, noLookup(), planNodeIdAllocator, DEFAULT_JOIN_LIMIT, false, testSessionBuilder().build(), createTestingTypeAnalyzer(queryRunner.getPlannerContext()), p.getTypes()), - expected); + assertThat(toMultiJoinNode(queryRunner.getPlannerContext(), joinNode, noLookup(), planNodeIdAllocator, DEFAULT_JOIN_LIMIT, false, testSessionBuilder().build(), createTestingTypeAnalyzer(queryRunner.getPlannerContext()), p.getTypes())).isEqualTo(expected); } @Test @@ -315,9 +309,7 @@ public void testCombinesCriteriaAndFilters() and(new ComparisonExpression(EQUAL, b1.toSymbolReference(), c1.toSymbolReference()), new ComparisonExpression(EQUAL, a1.toSymbolReference(), b1.toSymbolReference()), bcFilter, abcFilter), ImmutableList.of(a1, b1, b2, c1, c2), false); - assertEquals( - toMultiJoinNode(queryRunner.getPlannerContext(), joinNode, noLookup(), planNodeIdAllocator, DEFAULT_JOIN_LIMIT, false, testSessionBuilder().build(), createTestingTypeAnalyzer(queryRunner.getPlannerContext()), p.getTypes()), - expected); + assertThat(toMultiJoinNode(queryRunner.getPlannerContext(), joinNode, noLookup(), planNodeIdAllocator, DEFAULT_JOIN_LIMIT, false, testSessionBuilder().build(), createTestingTypeAnalyzer(queryRunner.getPlannerContext()), p.getTypes())).isEqualTo(expected); } @Test @@ -373,9 +365,7 @@ public void testConvertsBushyTrees() .setFilter(and(createEqualsExpression(a1, b1), createEqualsExpression(a1, c1), createEqualsExpression(d1, e1), createEqualsExpression(d2, e2), createEqualsExpression(b1, e1))) .setOutputSymbols(a1, b1, c1, d1, d2, e1, e2) .build(); - assertEquals( - toMultiJoinNode(queryRunner.getPlannerContext(), joinNode, noLookup(), planNodeIdAllocator, 5, false, testSessionBuilder().build(), createTestingTypeAnalyzer(queryRunner.getPlannerContext()), p.getTypes()), - expected); + assertThat(toMultiJoinNode(queryRunner.getPlannerContext(), joinNode, noLookup(), planNodeIdAllocator, 5, false, testSessionBuilder().build(), createTestingTypeAnalyzer(queryRunner.getPlannerContext()), p.getTypes())).isEqualTo(expected); } @Test @@ -433,9 +423,7 @@ public void testMoreThanJoinLimit() .setFilter(and(createEqualsExpression(a1, c1), createEqualsExpression(b1, e1))) .setOutputSymbols(a1, b1, c1, d1, d2, e1, e2) .build(); - assertEquals( - toMultiJoinNode(queryRunner.getPlannerContext(), joinNode, noLookup(), planNodeIdAllocator, 2, false, testSessionBuilder().build(), createTestingTypeAnalyzer(queryRunner.getPlannerContext()), p.getTypes()), - expected); + assertThat(toMultiJoinNode(queryRunner.getPlannerContext(), joinNode, noLookup(), planNodeIdAllocator, 2, false, testSessionBuilder().build(), createTestingTypeAnalyzer(queryRunner.getPlannerContext()), p.getTypes())).isEqualTo(expected); } private ComparisonExpression createEqualsExpression(Symbol left, Symbol right) diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestLambdaCaptureDesugaringRewriter.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestLambdaCaptureDesugaringRewriter.java index 1c01174f959f..42c69ac4497b 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestLambdaCaptureDesugaringRewriter.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestLambdaCaptureDesugaringRewriter.java @@ -31,7 +31,7 @@ import static io.trino.sql.planner.iterative.rule.LambdaCaptureDesugaringRewriter.rewrite; import static io.trino.sql.planner.iterative.rule.test.PlanBuilder.expression; import static java.util.stream.Collectors.toList; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestLambdaCaptureDesugaringRewriter { @@ -41,14 +41,13 @@ public void testRewriteBasicLambda() Map symbols = ImmutableMap.of(new Symbol("a"), BigintType.BIGINT); SymbolAllocator allocator = new SymbolAllocator(symbols); - assertEquals(rewrite(expression("x -> a + x"), allocator.getTypes(), allocator), - new BindExpression( - ImmutableList.of(expression("a")), - new LambdaExpression( - Stream.of("a_0", "x") - .map(Identifier::new) - .map(LambdaArgumentDeclaration::new) - .collect(toList()), - expression("a_0 + x")))); + assertThat(rewrite(expression("x -> a + x"), allocator.getTypes(), allocator)).isEqualTo(new BindExpression( + ImmutableList.of(expression("a")), + new LambdaExpression( + Stream.of("a_0", "x") + .map(Identifier::new) + .map(LambdaArgumentDeclaration::new) + .collect(toList()), + expression("a_0 + x")))); } } diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestPushProjectionThroughJoin.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestPushProjectionThroughJoin.java index afddc5089651..4dea6618f04c 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestPushProjectionThroughJoin.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestPushProjectionThroughJoin.java @@ -51,7 +51,6 @@ import static io.trino.sql.tree.ArithmeticUnaryExpression.Sign.PLUS; import static io.trino.testing.TestingSession.testSessionBuilder; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertTrue; public class TestPushProjectionThroughJoin { @@ -91,7 +90,7 @@ a2, new ArithmeticUnaryExpression(PLUS, a0.toSymbolReference()), Session session = testSessionBuilder().build(); Optional rewritten = pushProjectionThroughJoin(PLANNER_CONTEXT, planNode, noLookup(), idAllocator, session, createTestingTypeAnalyzer( PLANNER_CONTEXT), p.getTypes()); - assertTrue(rewritten.isPresent()); + assertThat(rewritten.isPresent()).isTrue(); assertPlan( session, dummyMetadata(), diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestSimplifyExpressions.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestSimplifyExpressions.java index 127cb83de946..723add1a3f41 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestSimplifyExpressions.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestSimplifyExpressions.java @@ -51,7 +51,7 @@ import static io.trino.sql.planner.TypeAnalyzer.createTestingTypeAnalyzer; import static io.trino.sql.planner.iterative.rule.SimplifyExpressions.rewrite; import static java.util.stream.Collectors.toList; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestSimplifyExpressions { @@ -315,9 +315,7 @@ private static void assertSimplifies(@Language("SQL") String expression, @Langua private static void assertSimplifies(@Language("SQL") String expression, @Language("SQL") String expected, Map symbolTypes) { Expression expectedExpression = normalize(rewriteIdentifiersToSymbolReferences(SQL_PARSER.createExpression(expected))); - assertEquals( - simplify(expression, symbolTypes), - expectedExpression); + assertThat(simplify(expression, symbolTypes)).isEqualTo(expectedExpression); } private static Expression simplify(@Language("SQL") String expression, Map symbolTypes) @@ -407,9 +405,7 @@ private static void assertSimplifiesNumericTypes(String expression, String expec Expression actualExpression = rewriteIdentifiersToSymbolReferences(SQL_PARSER.createExpression(expression)); Expression expectedExpression = rewriteIdentifiersToSymbolReferences(SQL_PARSER.createExpression(expected)); Expression rewritten = rewrite(actualExpression, TEST_SESSION, new SymbolAllocator(numericAndBooleanSymbolTypeMapFor(actualExpression)), PLANNER_CONTEXT, createTestingTypeAnalyzer(PLANNER_CONTEXT)); - assertEquals( - normalize(rewritten), - normalize(expectedExpression)); + assertThat(normalize(rewritten)).isEqualTo(normalize(expectedExpression)); } private static Map numericAndBooleanSymbolTypeMapFor(Expression expression) diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/test/RuleAssert.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/test/RuleAssert.java index 485e80eb2acf..72e3f9657e62 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/test/RuleAssert.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/test/RuleAssert.java @@ -47,7 +47,7 @@ import static io.trino.sql.planner.planprinter.PlanPrinter.textLogicalPlan; import static java.lang.String.format; import static java.util.Objects.requireNonNull; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Fail.fail; public class RuleAssert { diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestCardinalityExtractorPlanVisitor.java b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestCardinalityExtractorPlanVisitor.java index 3340a17718d4..bd56e98c314d 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestCardinalityExtractorPlanVisitor.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestCardinalityExtractorPlanVisitor.java @@ -29,7 +29,7 @@ import static io.trino.sql.planner.TestingPlannerContext.PLANNER_CONTEXT; import static io.trino.sql.planner.optimizations.QueryCardinalityUtil.extractCardinality; import static java.util.Collections.emptyList; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestCardinalityExtractorPlanVisitor { @@ -38,13 +38,9 @@ public void testLimitOnTopOfValues() { PlanBuilder planBuilder = new PlanBuilder(new PlanNodeIdAllocator(), PLANNER_CONTEXT, TEST_SESSION); - assertEquals( - extractCardinality(planBuilder.limit(3, planBuilder.values(emptyList(), ImmutableList.of(emptyList())))), - new Cardinality(Range.singleton(1L))); + assertThat(extractCardinality(planBuilder.limit(3, planBuilder.values(emptyList(), ImmutableList.of(emptyList()))))).isEqualTo(new Cardinality(Range.singleton(1L))); - assertEquals( - extractCardinality(planBuilder.limit(3, planBuilder.values(emptyList(), ImmutableList.of(emptyList(), emptyList(), emptyList(), emptyList())))), - new Cardinality(Range.singleton(3L))); + assertThat(extractCardinality(planBuilder.limit(3, planBuilder.values(emptyList(), ImmutableList.of(emptyList(), emptyList(), emptyList(), emptyList()))))).isEqualTo(new Cardinality(Range.singleton(3L))); } @Test @@ -55,61 +51,54 @@ public void testAggregation() ColumnHandle columnHandle = new TestingColumnHandle("column"); // single default aggregation - assertEquals(extractCardinality( - planBuilder.aggregation(builder -> builder - .singleGroupingSet() - .source(planBuilder.values(10)))), - new Cardinality(Range.singleton(1L))); + assertThat(extractCardinality( + planBuilder.aggregation(builder -> builder + .singleGroupingSet() + .source(planBuilder.values(10))))).isEqualTo(new Cardinality(Range.singleton(1L))); // multiple grouping sets with default aggregation with source that produces arbitrary number of rows - assertEquals(extractCardinality( - planBuilder.aggregation(builder -> builder - .groupingSets(AggregationNode.groupingSets( - ImmutableList.of(symbol), - 2, - ImmutableSet.of(0))) - .source(planBuilder.tableScan(ImmutableList.of(symbol), ImmutableMap.of(symbol, columnHandle))))), - new Cardinality(Range.atLeast(1L))); + assertThat(extractCardinality( + planBuilder.aggregation(builder -> builder + .groupingSets(AggregationNode.groupingSets( + ImmutableList.of(symbol), + 2, + ImmutableSet.of(0))) + .source(planBuilder.tableScan(ImmutableList.of(symbol), ImmutableMap.of(symbol, columnHandle)))))).isEqualTo(new Cardinality(Range.atLeast(1L))); // multiple grouping sets with default aggregation with source that produces exact number of rows - assertEquals(extractCardinality( - planBuilder.aggregation(builder -> builder - .groupingSets(AggregationNode.groupingSets( - ImmutableList.of(symbol), - 2, - ImmutableSet.of(0))) - .source(planBuilder.values(10, symbol)))), - new Cardinality(Range.closed(1L, 10L))); + assertThat(extractCardinality( + planBuilder.aggregation(builder -> builder + .groupingSets(AggregationNode.groupingSets( + ImmutableList.of(symbol), + 2, + ImmutableSet.of(0))) + .source(planBuilder.values(10, symbol))))).isEqualTo(new Cardinality(Range.closed(1L, 10L))); // multiple grouping sets with default aggregation with source that produces no rows - assertEquals(extractCardinality( - planBuilder.aggregation(builder -> builder - .groupingSets(AggregationNode.groupingSets( - ImmutableList.of(symbol), - 2, - ImmutableSet.of(0))) - .source(planBuilder.values(0, symbol)))), - new Cardinality(Range.singleton(1L))); + assertThat(extractCardinality( + planBuilder.aggregation(builder -> builder + .groupingSets(AggregationNode.groupingSets( + ImmutableList.of(symbol), + 2, + ImmutableSet.of(0))) + .source(planBuilder.values(0, symbol))))).isEqualTo(new Cardinality(Range.singleton(1L))); // single non-default aggregation with source that produces arbitrary number of rows - assertEquals(extractCardinality( - planBuilder.aggregation(builder -> builder - .singleGroupingSet(symbol) - .source(planBuilder.tableScan(ImmutableList.of(symbol), ImmutableMap.of(symbol, columnHandle))))), - new Cardinality(Range.atLeast(0L))); + assertThat(extractCardinality( + planBuilder.aggregation(builder -> builder + .singleGroupingSet(symbol) + .source(planBuilder.tableScan(ImmutableList.of(symbol), ImmutableMap.of(symbol, columnHandle)))))).isEqualTo(new Cardinality(Range.atLeast(0L))); // single non-default aggregation with source that produces at least single row - assertEquals(extractCardinality( - planBuilder.aggregation(builder -> builder - .singleGroupingSet(symbol) - .source(planBuilder.values(10, symbol)))), - new Cardinality(Range.closed(1L, 10L))); + assertThat(extractCardinality( + planBuilder.aggregation(builder -> builder + .singleGroupingSet(symbol) + .source(planBuilder.values(10, symbol))))).isEqualTo(new Cardinality(Range.closed(1L, 10L))); // single non-default aggregation with source that produces no rows - assertEquals(extractCardinality( - planBuilder.aggregation(builder -> builder - .singleGroupingSet(symbol) - .source(planBuilder.values(0, symbol)))), - new Cardinality(Range.singleton(0L))); + assertThat(extractCardinality( + planBuilder.aggregation(builder -> builder + .singleGroupingSet(symbol) + .source(planBuilder.values(0, symbol))))).isEqualTo(new Cardinality(Range.singleton(0L))); } } diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestExpressionEquivalence.java b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestExpressionEquivalence.java index 8a3db4a77480..cdc9179eab7b 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestExpressionEquivalence.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestExpressionEquivalence.java @@ -45,8 +45,7 @@ import static java.lang.String.format; import static java.util.function.Function.identity; import static java.util.stream.Collectors.toMap; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestExpressionEquivalence { @@ -143,12 +142,12 @@ private static void assertEquivalent(@Language("SQL") String left, @Language("SQ TypeProvider types = TypeProvider.copyOf(symbols.stream() .collect(toMap(identity(), TestExpressionEquivalence::generateType))); - assertTrue( - areExpressionEquivalent(leftExpression, rightExpression, types), - format("Expected (%s) and (%s) to be equivalent", left, right)); - assertTrue( - areExpressionEquivalent(rightExpression, leftExpression, types), - format("Expected (%s) and (%s) to be equivalent", right, left)); + assertThat(areExpressionEquivalent(leftExpression, rightExpression, types)) + .describedAs(format("Expected (%s) and (%s) to be equivalent", left, right)) + .isTrue(); + assertThat(areExpressionEquivalent(rightExpression, leftExpression, types)) + .describedAs(format("Expected (%s) and (%s) to be equivalent", right, left)) + .isTrue(); } @Test @@ -216,12 +215,12 @@ private static void assertNotEquivalent(@Language("SQL") String left, @Language( TypeProvider types = TypeProvider.copyOf(symbols.stream() .collect(toMap(identity(), TestExpressionEquivalence::generateType))); - assertFalse( - areExpressionEquivalent(leftExpression, rightExpression, types), - format("Expected (%s) and (%s) to not be equivalent", left, right)); - assertFalse( - areExpressionEquivalent(rightExpression, leftExpression, types), - format("Expected (%s) and (%s) to not be equivalent", right, left)); + assertThat(areExpressionEquivalent(leftExpression, rightExpression, types)) + .describedAs(format("Expected (%s) and (%s) to not be equivalent", left, right)) + .isFalse(); + assertThat(areExpressionEquivalent(rightExpression, leftExpression, types)) + .describedAs(format("Expected (%s) and (%s) to not be equivalent", right, left)) + .isFalse(); } private static boolean areExpressionEquivalent(Expression leftExpression, Expression rightExpression, TypeProvider types) diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestLocalProperties.java b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestLocalProperties.java index 5fe70c5cbd71..f45a49dd5ea9 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestLocalProperties.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestLocalProperties.java @@ -42,39 +42,39 @@ import static io.trino.sql.planner.optimizations.LocalProperties.extractLeadingConstants; import static io.trino.sql.planner.optimizations.LocalProperties.stripLeadingConstants; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestLocalProperties { @Test public void testConstantProcessing() { - assertEquals(stripLeadingConstants(ImmutableList.of()), ImmutableList.of()); - assertEquals(extractLeadingConstants(ImmutableList.of()), ImmutableSet.of()); + assertThat(stripLeadingConstants(ImmutableList.of())).isEqualTo(ImmutableList.of()); + assertThat(extractLeadingConstants(ImmutableList.of())).isEqualTo(ImmutableSet.of()); List> input = ImmutableList.of(grouped("a")); - assertEquals(stripLeadingConstants(input), ImmutableList.of(grouped("a"))); - assertEquals(extractLeadingConstants(input), ImmutableSet.of()); + assertThat(stripLeadingConstants(input)).isEqualTo(ImmutableList.of(grouped("a"))); + assertThat(extractLeadingConstants(input)).isEqualTo(ImmutableSet.of()); input = ImmutableList.of(constant("b"), grouped("a")); - assertEquals(stripLeadingConstants(input), ImmutableList.of(grouped("a"))); - assertEquals(extractLeadingConstants(input), ImmutableSet.of("b")); + assertThat(stripLeadingConstants(input)).isEqualTo(ImmutableList.of(grouped("a"))); + assertThat(extractLeadingConstants(input)).isEqualTo(ImmutableSet.of("b")); input = ImmutableList.of(constant("a"), grouped("a")); - assertEquals(stripLeadingConstants(input), ImmutableList.of(grouped("a"))); - assertEquals(extractLeadingConstants(input), ImmutableSet.of("a")); + assertThat(stripLeadingConstants(input)).isEqualTo(ImmutableList.of(grouped("a"))); + assertThat(extractLeadingConstants(input)).isEqualTo(ImmutableSet.of("a")); input = ImmutableList.of(grouped("a"), constant("b")); - assertEquals(stripLeadingConstants(input), input); - assertEquals(extractLeadingConstants(input), ImmutableSet.of()); + assertThat(stripLeadingConstants(input)).isEqualTo(input); + assertThat(extractLeadingConstants(input)).isEqualTo(ImmutableSet.of()); input = ImmutableList.of(constant("a")); - assertEquals(stripLeadingConstants(input), ImmutableList.of()); - assertEquals(extractLeadingConstants(input), ImmutableSet.of("a")); + assertThat(stripLeadingConstants(input)).isEqualTo(ImmutableList.of()); + assertThat(extractLeadingConstants(input)).isEqualTo(ImmutableSet.of("a")); input = ImmutableList.of(constant("a"), constant("b")); - assertEquals(stripLeadingConstants(input), ImmutableList.of()); - assertEquals(extractLeadingConstants(input), ImmutableSet.of("a", "b")); + assertThat(stripLeadingConstants(input)).isEqualTo(ImmutableList.of()); + assertThat(extractLeadingConstants(input)).isEqualTo(ImmutableSet.of("a", "b")); } @Test @@ -82,59 +82,59 @@ public void testTranslate() { Map map = ImmutableMap.of(); List> input = ImmutableList.of(); - assertEquals(LocalProperties.translate(input, translateWithMap(map)), ImmutableList.of()); + assertThat(LocalProperties.translate(input, translateWithMap(map))).isEqualTo(ImmutableList.of()); map = ImmutableMap.of(); input = ImmutableList.of(grouped("a")); - assertEquals(LocalProperties.translate(input, translateWithMap(map)), ImmutableList.of()); + assertThat(LocalProperties.translate(input, translateWithMap(map))).isEqualTo(ImmutableList.of()); map = ImmutableMap.of("a", "a1"); input = ImmutableList.of(grouped("a")); - assertEquals(LocalProperties.translate(input, translateWithMap(map)), ImmutableList.of(grouped("a1"))); + assertThat(LocalProperties.translate(input, translateWithMap(map))).isEqualTo(ImmutableList.of(grouped("a1"))); map = ImmutableMap.of(); input = ImmutableList.of(constant("a")); - assertEquals(LocalProperties.translate(input, translateWithMap(map)), ImmutableList.of()); + assertThat(LocalProperties.translate(input, translateWithMap(map))).isEqualTo(ImmutableList.of()); map = ImmutableMap.of(); input = ImmutableList.of(constant("a"), grouped("b")); - assertEquals(LocalProperties.translate(input, translateWithMap(map)), ImmutableList.of()); + assertThat(LocalProperties.translate(input, translateWithMap(map))).isEqualTo(ImmutableList.of()); map = ImmutableMap.of("b", "b1"); input = ImmutableList.of(constant("a"), grouped("b")); - assertEquals(LocalProperties.translate(input, translateWithMap(map)), ImmutableList.of(grouped("b1"))); + assertThat(LocalProperties.translate(input, translateWithMap(map))).isEqualTo(ImmutableList.of(grouped("b1"))); map = ImmutableMap.of("a", "a1", "b", "b1"); input = ImmutableList.of(constant("a"), grouped("b")); - assertEquals(LocalProperties.translate(input, translateWithMap(map)), ImmutableList.of(constant("a1"), grouped("b1"))); + assertThat(LocalProperties.translate(input, translateWithMap(map))).isEqualTo(ImmutableList.of(constant("a1"), grouped("b1"))); map = ImmutableMap.of("a", "a1", "b", "b1"); input = ImmutableList.of(grouped("a", "b")); - assertEquals(LocalProperties.translate(input, translateWithMap(map)), ImmutableList.of(grouped("a1", "b1"))); + assertThat(LocalProperties.translate(input, translateWithMap(map))).isEqualTo(ImmutableList.of(grouped("a1", "b1"))); map = ImmutableMap.of("a", "a1", "c", "c1"); input = ImmutableList.of(constant("a"), grouped("b"), grouped("c")); - assertEquals(LocalProperties.translate(input, translateWithMap(map)), ImmutableList.of(constant("a1"))); + assertThat(LocalProperties.translate(input, translateWithMap(map))).isEqualTo(ImmutableList.of(constant("a1"))); map = ImmutableMap.of("a", "a1", "c", "c1"); input = ImmutableList.of(grouped("a", "b"), grouped("c")); - assertEquals(LocalProperties.translate(input, translateWithMap(map)), ImmutableList.of()); + assertThat(LocalProperties.translate(input, translateWithMap(map))).isEqualTo(ImmutableList.of()); map = ImmutableMap.of("a", "a1", "c", "c1"); input = ImmutableList.of(grouped("a"), grouped("b"), grouped("c")); - assertEquals(LocalProperties.translate(input, translateWithMap(map)), ImmutableList.of(grouped("a1"))); + assertThat(LocalProperties.translate(input, translateWithMap(map))).isEqualTo(ImmutableList.of(grouped("a1"))); map = ImmutableMap.of("a", "a1", "c", "c1"); input = ImmutableList.of(constant("b"), grouped("a", "b"), grouped("c")); // Because b is constant, we can rewrite (a, b) - assertEquals(LocalProperties.translate(input, translateWithMap(map)), ImmutableList.of(grouped("a1"), grouped("c1"))); + assertThat(LocalProperties.translate(input, translateWithMap(map))).isEqualTo(ImmutableList.of(grouped("a1"), grouped("c1"))); map = ImmutableMap.of("a", "a1", "c", "c1"); input = ImmutableList.of(grouped("a"), constant("b"), grouped("c")); // Don't fail c translation due to a failed constant translation - assertEquals(LocalProperties.translate(input, translateWithMap(map)), ImmutableList.of(grouped("a1"), grouped("c1"))); + assertThat(LocalProperties.translate(input, translateWithMap(map))).isEqualTo(ImmutableList.of(grouped("a1"), grouped("c1"))); map = ImmutableMap.of("a", "a1", "b", "b1", "c", "c1"); input = ImmutableList.of(grouped("a"), constant("b"), grouped("c")); - assertEquals(LocalProperties.translate(input, translateWithMap(map)), ImmutableList.of(grouped("a1"), constant("b1"), grouped("c1"))); + assertThat(LocalProperties.translate(input, translateWithMap(map))).isEqualTo(ImmutableList.of(grouped("a1"), constant("b1"), grouped("c1"))); } private static Function> translateWithMap(Map translateMap) @@ -712,31 +712,31 @@ public ColumnHandle deserialize(JsonParser jsonParser, DeserializationContext de TestingColumnHandle columnHandle = new TestingColumnHandle("a"); LocalProperty property1 = new ConstantProperty<>(columnHandle); - assertEquals(property1, mapper.readValue(mapper.writeValueAsString(property1), new TypeReference>() {})); + assertThat(property1).isEqualTo(mapper.readValue(mapper.writeValueAsString(property1), new TypeReference>() { })); LocalProperty property2 = new SortingProperty<>(columnHandle, SortOrder.ASC_NULLS_FIRST); - assertEquals(property2, mapper.readValue(mapper.writeValueAsString(property2), new TypeReference>() {})); + assertThat(property2).isEqualTo(mapper.readValue(mapper.writeValueAsString(property2), new TypeReference>() { })); LocalProperty property3 = new GroupingProperty<>(ImmutableList.of(columnHandle)); - assertEquals(property3, mapper.readValue(mapper.writeValueAsString(property3), new TypeReference>() {})); + assertThat(property3).isEqualTo(mapper.readValue(mapper.writeValueAsString(property3), new TypeReference>() { })); } @SafeVarargs private static void assertMatch(List> actual, List> wanted, Optional>... match) { - assertEquals(LocalProperties.match(actual, wanted), Arrays.asList(match)); + assertThat(LocalProperties.match(actual, wanted)).isEqualTo(Arrays.asList(match)); } @SafeVarargs private static void assertNormalize(List> localProperties, Optional>... normalized) { - assertEquals(LocalProperties.normalize(localProperties), Arrays.asList(normalized)); + assertThat(LocalProperties.normalize(localProperties)).isEqualTo(Arrays.asList(normalized)); } @SafeVarargs private static void assertNormalizeAndFlatten(List> localProperties, LocalProperty... normalized) { - assertEquals(LocalProperties.normalizeAndPrune(localProperties), Arrays.asList(normalized)); + assertThat(LocalProperties.normalizeAndPrune(localProperties)).isEqualTo(Arrays.asList(normalized)); } private static ConstantProperty constant(String column) diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestPlanNodeSearcher.java b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestPlanNodeSearcher.java index 9b1dfe9e9173..f3b9e987c65e 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestPlanNodeSearcher.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestPlanNodeSearcher.java @@ -31,7 +31,7 @@ import static io.trino.SessionTestUtils.TEST_SESSION; import static io.trino.sql.planner.TestingPlannerContext.PLANNER_CONTEXT; import static io.trino.sql.planner.plan.JoinNode.Type.INNER; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestPlanNodeSearcher { @@ -60,7 +60,7 @@ public void testFindAll() .map(PlanNode::getId) .collect(toImmutableList()); - assertEquals(rootToBottomIds, findAllResult); + assertThat(rootToBottomIds).isEqualTo(findAllResult); } @Test @@ -84,7 +84,7 @@ public void testFindAllMultipleSources() .map(PlanNode::getId) .collect(toImmutableList()); - assertEquals(idsInPreOrder.build(), findAllResult); + assertThat(idsInPreOrder.build()).isEqualTo(findAllResult); } /** diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestUnion.java b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestUnion.java index b0d15c67e3b2..e839ca3c7c9d 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestUnion.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestUnion.java @@ -32,9 +32,7 @@ import static io.trino.sql.planner.plan.ExchangeNode.Type.GATHER; import static io.trino.sql.planner.plan.ExchangeNode.Type.REPARTITION; import static java.util.stream.Collectors.toList; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestUnion extends BasePlanTest @@ -61,8 +59,10 @@ public void testSimpleUnion() .where(TestUnion::isRemoteExchange) .findAll(); - assertEquals(remotes.size(), 1, "There should be exactly one RemoteExchange"); - assertEquals(((ExchangeNode) Iterables.getOnlyElement(remotes)).getType(), GATHER); + assertThat(remotes.size()) + .describedAs("There should be exactly one RemoteExchange") + .isEqualTo(1); + assertThat(((ExchangeNode) Iterables.getOnlyElement(remotes)).getType()).isEqualTo(GATHER); assertPlanIsFullyDistributed(plan); } @@ -83,13 +83,17 @@ public void testUnionUnderTopN() .where(TestUnion::isRemoteExchange) .findAll(); - assertEquals(remotes.size(), 1, "There should be exactly one RemoteExchange"); - assertEquals(((ExchangeNode) Iterables.getOnlyElement(remotes)).getType(), GATHER); + assertThat(remotes.size()) + .describedAs("There should be exactly one RemoteExchange") + .isEqualTo(1); + assertThat(((ExchangeNode) Iterables.getOnlyElement(remotes)).getType()).isEqualTo(GATHER); int numberOfpartialTopN = searchFrom(plan.getRoot()) .where(planNode -> planNode instanceof TopNNode && ((TopNNode) planNode).getStep() == TopNNode.Step.PARTIAL) .count(); - assertEquals(numberOfpartialTopN, 2, "There should be exactly two partial TopN nodes"); + assertThat(numberOfpartialTopN) + .describedAs("There should be exactly two partial TopN nodes") + .isEqualTo(2); assertPlanIsFullyDistributed(plan); } @@ -110,9 +114,11 @@ public void testUnionOverSingleNodeAggregationAndUnion() .where(TestUnion::isRemoteExchange) .findAll(); - assertEquals(remotes.size(), 2, "There should be exactly two RemoteExchanges"); - assertEquals(((ExchangeNode) remotes.get(0)).getType(), GATHER); - assertEquals(((ExchangeNode) remotes.get(1)).getType(), REPARTITION); + assertThat(remotes.size()) + .describedAs("There should be exactly two RemoteExchanges") + .isEqualTo(2); + assertThat(((ExchangeNode) remotes.get(0)).getType()).isEqualTo(GATHER); + assertThat(((ExchangeNode) remotes.get(1)).getType()).isEqualTo(REPARTITION); } @Test @@ -172,15 +178,17 @@ private void assertPlanIsFullyDistributed(Plan plan) return; } - assertTrue( - searchFrom(plan.getRoot()) - .recurseOnlyWhen(TestUnion::isNotRemoteGatheringExchange) - .findAll() - .stream() - .noneMatch(this::shouldBeDistributed), - "There is a node that should be distributed between output and first REMOTE GATHER ExchangeNode"); + assertThat(searchFrom(plan.getRoot()) + .recurseOnlyWhen(TestUnion::isNotRemoteGatheringExchange) + .findAll() + .stream() + .noneMatch(this::shouldBeDistributed)) + .describedAs("There is a node that should be distributed between output and first REMOTE GATHER ExchangeNode") + .isTrue(); - assertEquals(numberOfGathers, 1, "Only a single REMOTE GATHER was expected"); + assertThat(numberOfGathers) + .describedAs("Only a single REMOTE GATHER was expected") + .isEqualTo(1); } private boolean shouldBeDistributed(PlanNode planNode) @@ -213,7 +221,9 @@ private static void assertAtMostOneAggregationBetweenRemoteExchanges(Plan plan) .recurseOnlyWhen(TestUnion::isNotRemoteExchange) .findAll(); - assertFalse(aggregations.size() > 1, "More than a single AggregationNode between remote exchanges"); + assertThat(aggregations.size() > 1) + .describedAs("More than a single AggregationNode between remote exchanges") + .isFalse(); } } diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/plan/TestPatternRecognitionNodeSerialization.java b/core/trino-main/src/test/java/io/trino/sql/planner/plan/TestPatternRecognitionNodeSerialization.java index 0d0fe06874dd..36987786e5cb 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/plan/TestPatternRecognitionNodeSerialization.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/plan/TestPatternRecognitionNodeSerialization.java @@ -63,7 +63,7 @@ import static io.trino.sql.tree.SkipTo.Position.LAST; import static io.trino.sql.tree.WindowFrame.Type.ROWS; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestPatternRecognitionNodeSerialization { @@ -224,20 +224,20 @@ public void testPatternRecognitionNodeRoundtrip() PatternRecognitionNode roundtripNode = codec.fromJson(codec.toJson(node)); - assertEquals(roundtripNode.getMeasures(), node.getMeasures()); - assertEquals(roundtripNode.getRowsPerMatch(), node.getRowsPerMatch()); - assertEquals(roundtripNode.getSkipToLabel(), node.getSkipToLabel()); - assertEquals(roundtripNode.getSkipToPosition(), node.getSkipToPosition()); - assertEquals(roundtripNode.isInitial(), node.isInitial()); - assertEquals(roundtripNode.getPattern(), node.getPattern()); - assertEquals(roundtripNode.getSubsets(), node.getSubsets()); - assertEquals(roundtripNode.getVariableDefinitions(), node.getVariableDefinitions()); + assertThat(roundtripNode.getMeasures()).isEqualTo(node.getMeasures()); + assertThat(roundtripNode.getRowsPerMatch()).isEqualTo(node.getRowsPerMatch()); + assertThat(roundtripNode.getSkipToLabel()).isEqualTo(node.getSkipToLabel()); + assertThat(roundtripNode.getSkipToPosition()).isEqualTo(node.getSkipToPosition()); + assertThat(roundtripNode.isInitial()).isEqualTo(node.isInitial()); + assertThat(roundtripNode.getPattern()).isEqualTo(node.getPattern()); + assertThat(roundtripNode.getSubsets()).isEqualTo(node.getSubsets()); + assertThat(roundtripNode.getVariableDefinitions()).isEqualTo(node.getVariableDefinitions()); } public static void assertJsonRoundTrip(JsonCodec codec, T object) { String json = codec.toJson(object); T copy = codec.fromJson(json); - assertEquals(copy, object); + assertThat(copy).isEqualTo(object); } } diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/plan/TestStatisticAggregationsDescriptor.java b/core/trino-main/src/test/java/io/trino/sql/planner/plan/TestStatisticAggregationsDescriptor.java index 463f1f6ba56b..92ba530c17b2 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/plan/TestStatisticAggregationsDescriptor.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/plan/TestStatisticAggregationsDescriptor.java @@ -25,7 +25,7 @@ import static io.trino.spi.statistics.TableStatisticType.ROW_COUNT; import static io.trino.spi.type.BigintType.BIGINT; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestStatisticAggregationsDescriptor { @@ -41,7 +41,7 @@ public void testSerializationRoundTrip() private static void assertSerializationRoundTrip(JsonCodec> codec, StatisticAggregationsDescriptor descriptor) { - assertEquals(codec.fromJson(codec.toJson(descriptor)), descriptor); + assertThat(codec.fromJson(codec.toJson(descriptor))).isEqualTo(descriptor); } private static StatisticAggregationsDescriptor createTestDescriptor() diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/plan/TestWindowNode.java b/core/trino-main/src/test/java/io/trino/sql/planner/plan/TestWindowNode.java index a8b191cf46ab..e33da6cefd5d 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/plan/TestWindowNode.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/plan/TestWindowNode.java @@ -44,7 +44,7 @@ import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.sql.analyzer.TypeSignatureProvider.fromTypes; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestWindowNode { @@ -118,13 +118,13 @@ public void testSerializationRoundtrip() WindowNode actualNode = objectMapper.readValue(json, WindowNode.class); - assertEquals(actualNode.getId(), windowNode.getId()); - assertEquals(actualNode.getSpecification(), windowNode.getSpecification()); - assertEquals(actualNode.getWindowFunctions(), windowNode.getWindowFunctions()); - assertEquals(actualNode.getFrames(), windowNode.getFrames()); - assertEquals(actualNode.getHashSymbol(), windowNode.getHashSymbol()); - assertEquals(actualNode.getPrePartitionedInputs(), windowNode.getPrePartitionedInputs()); - assertEquals(actualNode.getPreSortedOrderPrefix(), windowNode.getPreSortedOrderPrefix()); + assertThat(actualNode.getId()).isEqualTo(windowNode.getId()); + assertThat(actualNode.getSpecification()).isEqualTo(windowNode.getSpecification()); + assertThat(actualNode.getWindowFunctions()).isEqualTo(windowNode.getWindowFunctions()); + assertThat(actualNode.getFrames()).isEqualTo(windowNode.getFrames()); + assertThat(actualNode.getHashSymbol()).isEqualTo(windowNode.getHashSymbol()); + assertThat(actualNode.getPrePartitionedInputs()).isEqualTo(windowNode.getPrePartitionedInputs()); + assertThat(actualNode.getPreSortedOrderPrefix()).isEqualTo(windowNode.getPreSortedOrderPrefix()); } private static PlanNodeId newId() diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/rowpattern/TestIrRowPatternOptimization.java b/core/trino-main/src/test/java/io/trino/sql/planner/rowpattern/TestIrRowPatternOptimization.java index 6757118da945..01097558b775 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/rowpattern/TestIrRowPatternOptimization.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/rowpattern/TestIrRowPatternOptimization.java @@ -28,7 +28,7 @@ import static io.trino.sql.planner.rowpattern.Patterns.questionMarkQuantified; import static io.trino.sql.planner.rowpattern.Patterns.rangeQuantified; import static io.trino.sql.planner.rowpattern.Patterns.starQuantified; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestIrRowPatternOptimization { @@ -229,19 +229,19 @@ public void testFlattenQuantifiedEmptyPattern() private void assertFlattened(IrRowPattern pattern, IrRowPattern expected) { IrRowPattern flattened = IrRowPatternFlattener.optimize(pattern); - assertEquals(flattened, expected); + assertThat(flattened).isEqualTo(expected); } private void assertOptimized(IrRowPattern pattern, IrRowPattern expected) { IrRowPattern optimized = IrPatternAlternationOptimizer.optimize(pattern); - assertEquals(optimized, expected); + assertThat(optimized).isEqualTo(expected); } private void assertFlattenedOptimized(IrRowPattern pattern, IrRowPattern expected) { IrRowPattern flattened = IrRowPatternFlattener.optimize(pattern); IrRowPattern optimized = IrPatternAlternationOptimizer.optimize(flattened); - assertEquals(optimized, expected); + assertThat(optimized).isEqualTo(expected); } } diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/rowpattern/TestRowPatternSerialization.java b/core/trino-main/src/test/java/io/trino/sql/planner/rowpattern/TestRowPatternSerialization.java index 9feb01d060a0..ac1cd60f14dd 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/rowpattern/TestRowPatternSerialization.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/rowpattern/TestRowPatternSerialization.java @@ -38,7 +38,7 @@ import static io.trino.sql.planner.rowpattern.ir.IrQuantifier.range; import static io.trino.sql.planner.rowpattern.ir.IrQuantifier.zeroOrMore; import static io.trino.sql.planner.rowpattern.ir.IrQuantifier.zeroOrOne; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestRowPatternSerialization { @@ -111,6 +111,6 @@ public static void assertJsonRoundTrip(JsonCodec codec, T object) { String json = codec.toJson(object); T copy = codec.fromJson(json); - assertEquals(copy, object); + assertThat(copy).isEqualTo(object); } } diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestFilteredAggregations.java b/core/trino-main/src/test/java/io/trino/sql/query/TestFilteredAggregations.java index 5f1eeb0a1d35..6630accea986 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestFilteredAggregations.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestFilteredAggregations.java @@ -25,7 +25,6 @@ import static io.trino.sql.planner.assertions.PlanMatchPattern.tableScan; import static io.trino.sql.planner.optimizations.PlanNodeSearcher.searchFrom; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertFalse; public class TestFilteredAggregations extends BasePlanTest @@ -144,10 +143,10 @@ public void testNoFilterAddedForConstantValueFilters() private void assertPlanContainsNoFilter(String sql) { - assertFalse( - searchFrom(plan(sql, OPTIMIZED).getRoot()) - .whereIsInstanceOfAny(FilterNode.class) - .matches(), - "Unexpected node for query: " + sql); + assertThat(searchFrom(plan(sql, OPTIMIZED).getRoot()) + .whereIsInstanceOfAny(FilterNode.class) + .matches()) + .describedAs("Unexpected node for query: " + sql) + .isFalse(); } } diff --git a/core/trino-main/src/test/java/io/trino/sql/relational/TestDeterminismEvaluator.java b/core/trino-main/src/test/java/io/trino/sql/relational/TestDeterminismEvaluator.java index debd8b2db151..35fda226a955 100644 --- a/core/trino-main/src/test/java/io/trino/sql/relational/TestDeterminismEvaluator.java +++ b/core/trino-main/src/test/java/io/trino/sql/relational/TestDeterminismEvaluator.java @@ -25,8 +25,7 @@ import static io.trino.sql.relational.Expressions.constant; import static io.trino.sql.relational.Expressions.field; import static java.util.Collections.singletonList; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestDeterminismEvaluator { @@ -38,15 +37,15 @@ public void testDeterminismEvaluator() CallExpression random = new CallExpression( functionResolution.resolveFunction("random", fromTypes(BIGINT)), singletonList(constant(10L, BIGINT))); - assertFalse(isDeterministic(random)); + assertThat(isDeterministic(random)).isFalse(); InputReferenceExpression col0 = field(0, BIGINT); ResolvedFunction lessThan = functionResolution.resolveOperator(LESS_THAN, ImmutableList.of(BIGINT, BIGINT)); CallExpression lessThanExpression = new CallExpression(lessThan, ImmutableList.of(col0, constant(10L, BIGINT))); - assertTrue(isDeterministic(lessThanExpression)); + assertThat(isDeterministic(lessThanExpression)).isTrue(); CallExpression lessThanRandomExpression = new CallExpression(lessThan, ImmutableList.of(col0, random)); - assertFalse(isDeterministic(lessThanRandomExpression)); + assertThat(isDeterministic(lessThanRandomExpression)).isFalse(); } } diff --git a/core/trino-main/src/test/java/io/trino/testing/TestBytes.java b/core/trino-main/src/test/java/io/trino/testing/TestBytes.java index 1f9c33c0b325..8a06c707a68f 100644 --- a/core/trino-main/src/test/java/io/trino/testing/TestBytes.java +++ b/core/trino-main/src/test/java/io/trino/testing/TestBytes.java @@ -15,13 +15,13 @@ import org.junit.jupiter.api.Test; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestBytes { @Test public void testToString() { - assertEquals(Bytes.fromBytes(new byte[] {1, 2, 22, -22}).toString(), "X'01 02 16 ea'"); + assertThat(Bytes.fromBytes(new byte[] {1, 2, 22, -22}).toString()).isEqualTo("X'01 02 16 ea'"); } } diff --git a/core/trino-main/src/test/java/io/trino/tests/TestVerifyTrinoMainTestSetup.java b/core/trino-main/src/test/java/io/trino/tests/TestVerifyTrinoMainTestSetup.java index 44b107491ab3..ea2d754efd0b 100644 --- a/core/trino-main/src/test/java/io/trino/tests/TestVerifyTrinoMainTestSetup.java +++ b/core/trino-main/src/test/java/io/trino/tests/TestVerifyTrinoMainTestSetup.java @@ -17,7 +17,7 @@ import java.time.ZoneId; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestVerifyTrinoMainTestSetup { @@ -25,6 +25,6 @@ public class TestVerifyTrinoMainTestSetup public void testJvmZone() { // Ensure that the zone defined in the POM is correctly set in the test JVM - assertEquals(ZoneId.systemDefault().getId(), "America/Bahia_Banderas"); + assertThat(ZoneId.systemDefault().getId()).isEqualTo("America/Bahia_Banderas"); } } diff --git a/core/trino-main/src/test/java/io/trino/transaction/TestTransactionManager.java b/core/trino-main/src/test/java/io/trino/transaction/TestTransactionManager.java index 1ea3c19ac3a5..21b841247102 100644 --- a/core/trino-main/src/test/java/io/trino/transaction/TestTransactionManager.java +++ b/core/trino-main/src/test/java/io/trino/transaction/TestTransactionManager.java @@ -41,11 +41,9 @@ import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -69,21 +67,21 @@ public void testTransactionWorkflow() TransactionId transactionId = transactionManager.beginTransaction(false); - assertEquals(transactionManager.getAllTransactionInfos().size(), 1); + assertThat(transactionManager.getAllTransactionInfos().size()).isEqualTo(1); TransactionInfo transactionInfo = transactionManager.getTransactionInfo(transactionId); - assertFalse(transactionInfo.isAutoCommitContext()); - assertTrue(transactionInfo.getCatalogNames().isEmpty()); - assertFalse(transactionInfo.getWrittenCatalogName().isPresent()); + assertThat(transactionInfo.isAutoCommitContext()).isFalse(); + assertThat(transactionInfo.getCatalogNames().isEmpty()).isTrue(); + assertThat(transactionInfo.getWrittenCatalogName().isPresent()).isFalse(); ConnectorMetadata metadata = transactionManager.getOptionalCatalogMetadata(transactionId, TEST_CATALOG_NAME).get().getMetadata(TEST_SESSION); metadata.listSchemaNames(TEST_SESSION.toConnectorSession(TEST_CATALOG_HANDLE)); transactionInfo = transactionManager.getTransactionInfo(transactionId); - assertEquals(transactionInfo.getCatalogNames(), ImmutableList.of(TEST_CATALOG_NAME)); - assertFalse(transactionInfo.getWrittenCatalogName().isPresent()); + assertThat(transactionInfo.getCatalogNames()).isEqualTo(ImmutableList.of(TEST_CATALOG_NAME)); + assertThat(transactionInfo.getWrittenCatalogName().isPresent()).isFalse(); getFutureValue(transactionManager.asyncCommit(transactionId)); - assertTrue(transactionManager.getAllTransactionInfos().isEmpty()); + assertThat(transactionManager.getAllTransactionInfos().isEmpty()).isTrue(); } } @@ -97,21 +95,21 @@ public void testAbortedTransactionWorkflow() TransactionId transactionId = transactionManager.beginTransaction(false); - assertEquals(transactionManager.getAllTransactionInfos().size(), 1); + assertThat(transactionManager.getAllTransactionInfos().size()).isEqualTo(1); TransactionInfo transactionInfo = transactionManager.getTransactionInfo(transactionId); - assertFalse(transactionInfo.isAutoCommitContext()); - assertTrue(transactionInfo.getCatalogNames().isEmpty()); - assertFalse(transactionInfo.getWrittenCatalogName().isPresent()); + assertThat(transactionInfo.isAutoCommitContext()).isFalse(); + assertThat(transactionInfo.getCatalogNames().isEmpty()).isTrue(); + assertThat(transactionInfo.getWrittenCatalogName().isPresent()).isFalse(); ConnectorMetadata metadata = transactionManager.getOptionalCatalogMetadata(transactionId, TEST_CATALOG_NAME).get().getMetadata(TEST_SESSION); metadata.listSchemaNames(TEST_SESSION.toConnectorSession(TEST_CATALOG_HANDLE)); transactionInfo = transactionManager.getTransactionInfo(transactionId); - assertEquals(transactionInfo.getCatalogNames(), ImmutableList.of(TEST_CATALOG_NAME)); - assertFalse(transactionInfo.getWrittenCatalogName().isPresent()); + assertThat(transactionInfo.getCatalogNames()).isEqualTo(ImmutableList.of(TEST_CATALOG_NAME)); + assertThat(transactionInfo.getWrittenCatalogName().isPresent()).isFalse(); getFutureValue(transactionManager.asyncAbort(transactionId)); - assertTrue(transactionManager.getAllTransactionInfos().isEmpty()); + assertThat(transactionManager.getAllTransactionInfos().isEmpty()).isTrue(); } } @@ -125,29 +123,29 @@ public void testFailedTransactionWorkflow() TransactionId transactionId = transactionManager.beginTransaction(false); - assertEquals(transactionManager.getAllTransactionInfos().size(), 1); + assertThat(transactionManager.getAllTransactionInfos().size()).isEqualTo(1); TransactionInfo transactionInfo = transactionManager.getTransactionInfo(transactionId); - assertFalse(transactionInfo.isAutoCommitContext()); - assertTrue(transactionInfo.getCatalogNames().isEmpty()); - assertFalse(transactionInfo.getWrittenCatalogName().isPresent()); + assertThat(transactionInfo.isAutoCommitContext()).isFalse(); + assertThat(transactionInfo.getCatalogNames().isEmpty()).isTrue(); + assertThat(transactionInfo.getWrittenCatalogName().isPresent()).isFalse(); ConnectorMetadata metadata = transactionManager.getOptionalCatalogMetadata(transactionId, TEST_CATALOG_NAME).get().getMetadata(TEST_SESSION); metadata.listSchemaNames(TEST_SESSION.toConnectorSession(TEST_CATALOG_HANDLE)); transactionInfo = transactionManager.getTransactionInfo(transactionId); - assertEquals(transactionInfo.getCatalogNames(), ImmutableList.of(TEST_CATALOG_NAME)); - assertFalse(transactionInfo.getWrittenCatalogName().isPresent()); + assertThat(transactionInfo.getCatalogNames()).isEqualTo(ImmutableList.of(TEST_CATALOG_NAME)); + assertThat(transactionInfo.getWrittenCatalogName().isPresent()).isFalse(); transactionManager.fail(transactionId); - assertEquals(transactionManager.getAllTransactionInfos().size(), 1); + assertThat(transactionManager.getAllTransactionInfos().size()).isEqualTo(1); assertTrinoExceptionThrownBy(() -> transactionManager.getCatalogMetadata(transactionId, TEST_CATALOG_HANDLE)) .hasErrorCode(TRANSACTION_ALREADY_ABORTED); - assertEquals(transactionManager.getAllTransactionInfos().size(), 1); + assertThat(transactionManager.getAllTransactionInfos().size()).isEqualTo(1); getFutureValue(transactionManager.asyncAbort(transactionId)); - assertTrue(transactionManager.getAllTransactionInfos().isEmpty()); + assertThat(transactionManager.getAllTransactionInfos().isEmpty()).isTrue(); } } @@ -165,14 +163,14 @@ public void testExpiration() TransactionId transactionId = transactionManager.beginTransaction(false); - assertEquals(transactionManager.getAllTransactionInfos().size(), 1); + assertThat(transactionManager.getAllTransactionInfos().size()).isEqualTo(1); TransactionInfo transactionInfo = transactionManager.getTransactionInfo(transactionId); - assertFalse(transactionInfo.isAutoCommitContext()); - assertTrue(transactionInfo.getCatalogNames().isEmpty()); - assertFalse(transactionInfo.getWrittenCatalogName().isPresent()); + assertThat(transactionInfo.isAutoCommitContext()).isFalse(); + assertThat(transactionInfo.getCatalogNames().isEmpty()).isTrue(); + assertThat(transactionInfo.getWrittenCatalogName().isPresent()).isFalse(); transactionManager.trySetInactive(transactionId); - assertEventually(new Duration(10, SECONDS), () -> assertTrue(transactionManager.getAllTransactionInfos().isEmpty())); + assertEventually(new Duration(10, SECONDS), () -> assertThat(transactionManager.getAllTransactionInfos().isEmpty()).isTrue()); } } diff --git a/core/trino-main/src/test/java/io/trino/type/AbstractTestType.java b/core/trino-main/src/test/java/io/trino/type/AbstractTestType.java index 19d8600f960d..9beb1f2c6239 100644 --- a/core/trino-main/src/test/java/io/trino/type/AbstractTestType.java +++ b/core/trino-main/src/test/java/io/trino/type/AbstractTestType.java @@ -81,10 +81,7 @@ import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Fail.fail; public abstract class AbstractTestType { @@ -301,25 +298,25 @@ private void assertFlat(byte[] fixed, int fixedOffset, byte[] variable) Object expectedStackValue = expectedStackValues.get(i); int elementFixedOffset = fixedOffset + (i * flatFixedSize); if (type.getJavaType() == boolean.class) { - assertEquals((boolean) readFlatMethod.invokeExact(fixed, elementFixedOffset, variable), expectedStackValue); + assertThat((boolean) readFlatMethod.invokeExact(fixed, elementFixedOffset, variable)).isEqualTo(expectedStackValue); } else if (type.getJavaType() == long.class) { - assertEquals((long) readFlatMethod.invokeExact(fixed, elementFixedOffset, variable), expectedStackValue); + assertThat((long) readFlatMethod.invokeExact(fixed, elementFixedOffset, variable)).isEqualTo(expectedStackValue); } else if (type.getJavaType() == double.class) { - assertEquals((double) readFlatMethod.invokeExact(fixed, elementFixedOffset, variable), expectedStackValue); + assertThat((double) readFlatMethod.invokeExact(fixed, elementFixedOffset, variable)).isEqualTo(expectedStackValue); } else if (type.getJavaType() == Slice.class) { - assertEquals((Slice) readFlatMethod.invokeExact(fixed, elementFixedOffset, variable), expectedStackValue); + assertThat((Slice) readFlatMethod.invokeExact(fixed, elementFixedOffset, variable)).isEqualTo(expectedStackValue); } else if (type.getJavaType() == Block.class) { assertBlockEquals((Block) readFlatMethod.invokeExact(fixed, elementFixedOffset, variable), (Block) expectedStackValue); } else if (stackStackEqualOperator != null) { - assertTrue((Boolean) stackStackEqualOperator.invoke(readFlatMethod.invoke(fixed, elementFixedOffset, variable), expectedStackValue)); + assertThat((Boolean) stackStackEqualOperator.invoke(readFlatMethod.invoke(fixed, elementFixedOffset, variable), expectedStackValue)).isTrue(); } else { - assertEquals(readFlatMethod.invoke(fixed, elementFixedOffset, variable), expectedStackValue); + assertThat(readFlatMethod.invoke(fixed, elementFixedOffset, variable)).isEqualTo(expectedStackValue); } BlockBuilder blockBuilder = type.createBlockBuilder(null, 1); @@ -327,21 +324,21 @@ else if (stackStackEqualOperator != null) { assertPositionEquals(testBlock, i, expectedStackValue, expectedObjectValues.get(i)); if (type.isComparable()) { - assertTrue((Boolean) flatFlatEqualOperator.invokeExact(fixed, elementFixedOffset, variable, fixed, elementFixedOffset, variable)); - assertTrue((Boolean) flatBlockPositionEqualOperator.invokeExact(fixed, elementFixedOffset, variable, testBlock, i)); - assertTrue((Boolean) blockPositionFlatEqualOperator.invokeExact(testBlock, i, fixed, elementFixedOffset, variable)); + assertThat((Boolean) flatFlatEqualOperator.invokeExact(fixed, elementFixedOffset, variable, fixed, elementFixedOffset, variable)).isTrue(); + assertThat((Boolean) flatBlockPositionEqualOperator.invokeExact(fixed, elementFixedOffset, variable, testBlock, i)).isTrue(); + assertThat((Boolean) blockPositionFlatEqualOperator.invokeExact(testBlock, i, fixed, elementFixedOffset, variable)).isTrue(); - assertEquals((long) flatHashCodeOperator.invokeExact(fixed, elementFixedOffset, variable), hashCodeOperator.hashCodeNullSafe(testBlock, i)); + assertThat((long) flatHashCodeOperator.invokeExact(fixed, elementFixedOffset, variable)).isEqualTo(hashCodeOperator.hashCodeNullSafe(testBlock, i)); - assertEquals((long) flatXxHash64Operator.invokeExact(fixed, elementFixedOffset, variable), xxHash64Operator.xxHash64(testBlock, i)); + assertThat((long) flatXxHash64Operator.invokeExact(fixed, elementFixedOffset, variable)).isEqualTo(xxHash64Operator.xxHash64(testBlock, i)); - assertFalse((boolean) flatFlatDistinctFromOperator.invokeExact(fixed, elementFixedOffset, variable, fixed, elementFixedOffset, variable)); - assertFalse((boolean) flatBlockPositionDistinctFromOperator.invokeExact(fixed, elementFixedOffset, variable, testBlock, i)); - assertFalse((boolean) blockPositionFlatDistinctFromOperator.invokeExact(testBlock, i, fixed, elementFixedOffset, variable)); + assertThat((boolean) flatFlatDistinctFromOperator.invokeExact(fixed, elementFixedOffset, variable, fixed, elementFixedOffset, variable)).isFalse(); + assertThat((boolean) flatBlockPositionDistinctFromOperator.invokeExact(fixed, elementFixedOffset, variable, testBlock, i)).isFalse(); + assertThat((boolean) blockPositionFlatDistinctFromOperator.invokeExact(testBlock, i, fixed, elementFixedOffset, variable)).isFalse(); ValueBlock nullValue = type.createBlockBuilder(null, 1).appendNull().buildValueBlock(); - assertTrue((boolean) flatBlockPositionDistinctFromOperator.invokeExact(fixed, elementFixedOffset, variable, nullValue, 0)); - assertTrue((boolean) blockPositionFlatDistinctFromOperator.invokeExact(nullValue, 0, fixed, elementFixedOffset, variable)); + assertThat((boolean) flatBlockPositionDistinctFromOperator.invokeExact(fixed, elementFixedOffset, variable, nullValue, 0)).isTrue(); + assertThat((boolean) blockPositionFlatDistinctFromOperator.invokeExact(nullValue, 0, fixed, elementFixedOffset, variable)).isTrue(); } } } @@ -378,23 +375,23 @@ protected void assertPositionEquals(ValueBlock block, int position, Object expec private void assertPositionValue(ValueBlock block, int position, Object expectedStackValue, long expectedHash, Object expectedObjectValue) throws Throwable { - assertEquals(block.isNull(position), expectedStackValue == null); + assertThat(block.isNull(position)).isEqualTo(expectedStackValue == null); Object objectValue = type.getObjectValue(SESSION, block, position); - assertEquals(objectValue, expectedObjectValue); + assertThat(objectValue).isEqualTo(expectedObjectValue); if (objectValue != null) { assertInstanceOf(objectValue, objectValueType); } Block expectedBlock = createBlock(type, expectedStackValue); if (type.isComparable()) { - assertTrue(equalOperator.equalNullSafe(block, position, block, position)); - assertTrue(equalOperator.equalNullSafe(block, position, expectedBlock, 0)); - assertTrue(equalOperator.equalNullSafe(expectedBlock, 0, block, position)); - assertEquals(hashCodeOperator.hashCodeNullSafe(block, position), expectedHash); - assertFalse(distinctFromOperator.isDistinctFrom(block, position, block, position)); - assertFalse(distinctFromOperator.isDistinctFrom(block, position, expectedBlock, 0)); - assertFalse(distinctFromOperator.isDistinctFrom(expectedBlock, 0, block, position)); + assertThat(equalOperator.equalNullSafe(block, position, block, position)).isTrue(); + assertThat(equalOperator.equalNullSafe(block, position, expectedBlock, 0)).isTrue(); + assertThat(equalOperator.equalNullSafe(expectedBlock, 0, block, position)).isTrue(); + assertThat(hashCodeOperator.hashCodeNullSafe(block, position)).isEqualTo(expectedHash); + assertThat(distinctFromOperator.isDistinctFrom(block, position, block, position)).isFalse(); + assertThat(distinctFromOperator.isDistinctFrom(block, position, expectedBlock, 0)).isFalse(); + assertThat(distinctFromOperator.isDistinctFrom(expectedBlock, 0, block, position)).isFalse(); } else { assertThatThrownBy(() -> typeOperators.getHashCodeOperator(type, simpleConvention(FAIL_ON_NULL, BLOCK_POSITION))) @@ -414,13 +411,13 @@ private void assertPositionValue(ValueBlock block, int position, Object expected .hasMessageContaining("is not comparable"); } - assertEquals(block.isNull(position), expectedStackValue == null); + assertThat(block.isNull(position)).isEqualTo(expectedStackValue == null); if (type.isOrderable()) { - assertTrue(blockTypeOperators.generateBlockPositionOrdering(type, ASC_NULLS_FIRST).order(block, position, expectedBlock, 0) == 0); - assertTrue(blockTypeOperators.generateBlockPositionOrdering(type, ASC_NULLS_LAST).order(block, position, expectedBlock, 0) == 0); - assertTrue(blockTypeOperators.generateBlockPositionOrdering(type, DESC_NULLS_FIRST).order(block, position, expectedBlock, 0) == 0); - assertTrue(blockTypeOperators.generateBlockPositionOrdering(type, DESC_NULLS_LAST).order(block, position, expectedBlock, 0) == 0); + assertThat(blockTypeOperators.generateBlockPositionOrdering(type, ASC_NULLS_FIRST).order(block, position, expectedBlock, 0) == 0).isTrue(); + assertThat(blockTypeOperators.generateBlockPositionOrdering(type, ASC_NULLS_LAST).order(block, position, expectedBlock, 0) == 0).isTrue(); + assertThat(blockTypeOperators.generateBlockPositionOrdering(type, DESC_NULLS_FIRST).order(block, position, expectedBlock, 0) == 0).isTrue(); + assertThat(blockTypeOperators.generateBlockPositionOrdering(type, DESC_NULLS_LAST).order(block, position, expectedBlock, 0) == 0).isTrue(); } else { assertThatThrownBy(() -> typeOperators.getComparisonUnorderedLastOperator(type, simpleConvention(FAIL_ON_NULL, NEVER_NULL, NEVER_NULL))) @@ -436,50 +433,50 @@ private void assertPositionValue(ValueBlock block, int position, Object expected if (block.isNull(position)) { if (type.isOrderable() && !(type instanceof UnknownType)) { Block nonNullValue = toBlock(getNonNullValue()); - assertTrue(blockTypeOperators.generateBlockPositionOrdering(type, ASC_NULLS_FIRST).order(block, position, nonNullValue, 0) < 0); - assertTrue(blockTypeOperators.generateBlockPositionOrdering(type, ASC_NULLS_LAST).order(block, position, nonNullValue, 0) > 0); - assertTrue(blockTypeOperators.generateBlockPositionOrdering(type, DESC_NULLS_FIRST).order(block, position, nonNullValue, 0) < 0); - assertTrue(blockTypeOperators.generateBlockPositionOrdering(type, DESC_NULLS_LAST).order(block, position, nonNullValue, 0) > 0); + assertThat(blockTypeOperators.generateBlockPositionOrdering(type, ASC_NULLS_FIRST).order(block, position, nonNullValue, 0) < 0).isTrue(); + assertThat(blockTypeOperators.generateBlockPositionOrdering(type, ASC_NULLS_LAST).order(block, position, nonNullValue, 0) > 0).isTrue(); + assertThat(blockTypeOperators.generateBlockPositionOrdering(type, DESC_NULLS_FIRST).order(block, position, nonNullValue, 0) < 0).isTrue(); + assertThat(blockTypeOperators.generateBlockPositionOrdering(type, DESC_NULLS_LAST).order(block, position, nonNullValue, 0) > 0).isTrue(); } return; } if (type.isOrderable() && expectedStackValue != Boolean.TRUE) { Block greaterValue = toBlock(getGreaterValue(expectedStackValue)); - assertTrue(blockTypeOperators.generateBlockPositionOrdering(type, ASC_NULLS_FIRST).order(block, position, greaterValue, 0) < 0); - assertTrue(blockTypeOperators.generateBlockPositionOrdering(type, ASC_NULLS_LAST).order(block, position, greaterValue, 0) < 0); - assertTrue(blockTypeOperators.generateBlockPositionOrdering(type, DESC_NULLS_FIRST).order(block, position, greaterValue, 0) > 0); - assertTrue(blockTypeOperators.generateBlockPositionOrdering(type, DESC_NULLS_LAST).order(block, position, greaterValue, 0) > 0); + assertThat(blockTypeOperators.generateBlockPositionOrdering(type, ASC_NULLS_FIRST).order(block, position, greaterValue, 0) < 0).isTrue(); + assertThat(blockTypeOperators.generateBlockPositionOrdering(type, ASC_NULLS_LAST).order(block, position, greaterValue, 0) < 0).isTrue(); + assertThat(blockTypeOperators.generateBlockPositionOrdering(type, DESC_NULLS_FIRST).order(block, position, greaterValue, 0) > 0).isTrue(); + assertThat(blockTypeOperators.generateBlockPositionOrdering(type, DESC_NULLS_LAST).order(block, position, greaterValue, 0) > 0).isTrue(); } if (type.getJavaType() == boolean.class) { - assertEquals(type.getBoolean(block, position), expectedStackValue); + assertThat(type.getBoolean(block, position)).isEqualTo(expectedStackValue); assertThatThrownBy(() -> type.getLong(block, position)).isInstanceOf(UnsupportedOperationException.class); assertThatThrownBy(() -> type.getDouble(block, position)).isInstanceOf(UnsupportedOperationException.class); assertThatThrownBy(() -> type.getObject(block, position)).isInstanceOf(UnsupportedOperationException.class); - assertEquals((boolean) readBlockMethod.invokeExact(block, position), expectedStackValue); + assertThat((boolean) readBlockMethod.invokeExact(block, position)).isEqualTo(expectedStackValue); } else if (type.getJavaType() == long.class) { - assertEquals(type.getLong(block, position), expectedStackValue); + assertThat(type.getLong(block, position)).isEqualTo(expectedStackValue); assertThatThrownBy(() -> type.getBoolean(block, position)).isInstanceOf(UnsupportedOperationException.class); assertThatThrownBy(() -> type.getDouble(block, position)).isInstanceOf(UnsupportedOperationException.class); assertThatThrownBy(() -> type.getObject(block, position)).isInstanceOf(UnsupportedOperationException.class); - assertEquals((long) readBlockMethod.invokeExact(block, position), expectedStackValue); + assertThat((long) readBlockMethod.invokeExact(block, position)).isEqualTo(expectedStackValue); } else if (type.getJavaType() == double.class) { - assertEquals(type.getDouble(block, position), expectedStackValue); + assertThat(type.getDouble(block, position)).isEqualTo(expectedStackValue); assertThatThrownBy(() -> type.getBoolean(block, position)).isInstanceOf(UnsupportedOperationException.class); assertThatThrownBy(() -> type.getLong(block, position)).isInstanceOf(UnsupportedOperationException.class); assertThatThrownBy(() -> type.getObject(block, position)).isInstanceOf(UnsupportedOperationException.class); - assertEquals((double) readBlockMethod.invokeExact(block, position), expectedStackValue); + assertThat((double) readBlockMethod.invokeExact(block, position)).isEqualTo(expectedStackValue); } else if (type.getJavaType() == Slice.class) { - assertEquals(type.getSlice(block, position), expectedStackValue); - assertEquals(type.getObject(block, position), expectedStackValue); + assertThat(type.getSlice(block, position)).isEqualTo(expectedStackValue); + assertThat(type.getObject(block, position)).isEqualTo(expectedStackValue); assertThatThrownBy(() -> type.getBoolean(block, position)).isInstanceOf(UnsupportedOperationException.class); assertThatThrownBy(() -> type.getLong(block, position)).isInstanceOf(UnsupportedOperationException.class); assertThatThrownBy(() -> type.getDouble(block, position)).isInstanceOf(UnsupportedOperationException.class); - assertEquals((Slice) readBlockMethod.invokeExact(block, position), expectedStackValue); + assertThat((Slice) readBlockMethod.invokeExact(block, position)).isEqualTo(expectedStackValue); } else if (type.getJavaType() == Block.class) { assertBlockEquals((Block) type.getObject(block, position), (Block) expectedStackValue); @@ -491,19 +488,19 @@ else if (type.getJavaType() == Block.class) { } else { if (stackStackEqualOperator != null) { - assertTrue((Boolean) stackStackEqualOperator.invoke(type.getObject(block, position), expectedStackValue)); + assertThat((Boolean) stackStackEqualOperator.invoke(type.getObject(block, position), expectedStackValue)).isTrue(); } else { - assertEquals(type.getObject(block, position), expectedStackValue); + assertThat(type.getObject(block, position)).isEqualTo(expectedStackValue); } assertThatThrownBy(() -> type.getBoolean(block, position)).isInstanceOf(UnsupportedOperationException.class); assertThatThrownBy(() -> type.getLong(block, position)).isInstanceOf(UnsupportedOperationException.class); assertThatThrownBy(() -> type.getDouble(block, position)).isInstanceOf(UnsupportedOperationException.class); if (stackStackEqualOperator != null) { - assertTrue((Boolean) stackStackEqualOperator.invoke(readBlockMethod.invoke(block, position), expectedStackValue)); + assertThat((Boolean) stackStackEqualOperator.invoke(readBlockMethod.invoke(block, position), expectedStackValue)).isTrue(); } else { - assertEquals(readBlockMethod.invoke(block, position), expectedStackValue); + assertThat(readBlockMethod.invoke(block, position)).isEqualTo(expectedStackValue); } } } @@ -514,7 +511,7 @@ private void assertBlockEquals(Block actualValue, Block expectedValue) writeBlock(blockEncodingSerde, actualSliceOutput, actualValue); SliceOutput expectedSliceOutput = new DynamicSliceOutput(actualSliceOutput.size()); writeBlock(blockEncodingSerde, expectedSliceOutput, expectedValue); - assertEquals(actualSliceOutput.slice(), expectedSliceOutput.slice()); + assertThat(actualSliceOutput.slice()).isEqualTo(expectedSliceOutput.slice()); } private void verifyInvalidPositionHandling(Block block) diff --git a/core/trino-main/src/test/java/io/trino/type/TestArrayOperators.java b/core/trino-main/src/test/java/io/trino/type/TestArrayOperators.java index 8442f7ac0173..8249c4601580 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestArrayOperators.java +++ b/core/trino-main/src/test/java/io/trino/type/TestArrayOperators.java @@ -82,7 +82,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -133,7 +132,7 @@ public void testStackRepresentation() DynamicSliceOutput expectedSliceOutput = new DynamicSliceOutput(100); writeBlock(((LocalQueryRunner) assertions.getQueryRunner()).getPlannerContext().getBlockEncodingSerde(), expectedSliceOutput, expectedBlock); - assertEquals(actualSliceOutput.slice(), expectedSliceOutput.slice()); + assertThat(actualSliceOutput.slice()).isEqualTo(expectedSliceOutput.slice()); } @Test diff --git a/core/trino-main/src/test/java/io/trino/type/TestBigintType.java b/core/trino-main/src/test/java/io/trino/type/TestBigintType.java index c22a8800f2ad..0419b993c0d2 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestBigintType.java +++ b/core/trino-main/src/test/java/io/trino/type/TestBigintType.java @@ -22,7 +22,6 @@ import static io.trino.spi.type.BigintType.BIGINT; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestBigintType extends AbstractTestType @@ -59,8 +58,8 @@ protected Object getGreaterValue(Object value) public void testRange() { Range range = type.getRange().orElseThrow(); - assertEquals(range.getMin(), Long.MIN_VALUE); - assertEquals(range.getMax(), Long.MAX_VALUE); + assertThat(range.getMin()).isEqualTo(Long.MIN_VALUE); + assertThat(range.getMax()).isEqualTo(Long.MAX_VALUE); } @Test diff --git a/core/trino-main/src/test/java/io/trino/type/TestBooleanType.java b/core/trino-main/src/test/java/io/trino/type/TestBooleanType.java index b267d02f8c9c..bb894d53ffca 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestBooleanType.java +++ b/core/trino-main/src/test/java/io/trino/type/TestBooleanType.java @@ -23,8 +23,6 @@ import static io.trino.block.BlockAssertions.assertBlockEquals; import static io.trino.spi.type.BooleanType.BOOLEAN; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestBooleanType extends AbstractTestType @@ -47,24 +45,24 @@ public void testBooleanBlockWithoutNullsFromByteArray() Block wrappedBlock = BooleanType.wrapByteArrayAsBooleanBlockWithoutNulls(booleanBytes); Block builderBlock = builder.build(); // wrapped instances have no nulls - assertFalse(wrappedBlock.mayHaveNull()); + assertThat(wrappedBlock.mayHaveNull()).isFalse(); // wrapped byte array instances and builder based instances both produce ByteArrayBlock - assertTrue(wrappedBlock instanceof ByteArrayBlock); - assertTrue(builderBlock instanceof ByteArrayBlock); + assertThat(wrappedBlock instanceof ByteArrayBlock).isTrue(); + assertThat(builderBlock instanceof ByteArrayBlock).isTrue(); assertBlockEquals(BOOLEAN, wrappedBlock, builderBlock); // the wrapping instance does not copy the byte array defensively - assertTrue(BOOLEAN.getBoolean(wrappedBlock, 0)); + assertThat(BOOLEAN.getBoolean(wrappedBlock, 0)).isTrue(); booleanBytes[0] = 0; - assertFalse(BOOLEAN.getBoolean(wrappedBlock, 0)); + assertThat(BOOLEAN.getBoolean(wrappedBlock, 0)).isFalse(); } @Test public void testBooleanBlockWithSingleNonNullValue() { - assertTrue(BooleanType.createBlockForSingleNonNullValue(true) instanceof ByteArrayBlock); - assertTrue(BOOLEAN.getBoolean(BooleanType.createBlockForSingleNonNullValue(true), 0)); - assertFalse(BOOLEAN.getBoolean(BooleanType.createBlockForSingleNonNullValue(false), 0)); - assertFalse(BooleanType.createBlockForSingleNonNullValue(false).mayHaveNull()); + assertThat(BooleanType.createBlockForSingleNonNullValue(true) instanceof ByteArrayBlock).isTrue(); + assertThat(BOOLEAN.getBoolean(BooleanType.createBlockForSingleNonNullValue(true), 0)).isTrue(); + assertThat(BOOLEAN.getBoolean(BooleanType.createBlockForSingleNonNullValue(false), 0)).isFalse(); + assertThat(BooleanType.createBlockForSingleNonNullValue(false).mayHaveNull()).isFalse(); } public static ValueBlock createTestBlock() diff --git a/core/trino-main/src/test/java/io/trino/type/TestBoundedVarcharType.java b/core/trino-main/src/test/java/io/trino/type/TestBoundedVarcharType.java index 7874cea36276..94f2479ce734 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestBoundedVarcharType.java +++ b/core/trino-main/src/test/java/io/trino/type/TestBoundedVarcharType.java @@ -24,7 +24,6 @@ import static io.trino.spi.type.VarcharType.createVarcharType; import static java.lang.Character.MAX_CODE_POINT; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestBoundedVarcharType extends AbstractTestType @@ -61,8 +60,8 @@ protected Object getGreaterValue(Object value) public void testRange() { Type.Range range = type.getRange().orElseThrow(); - assertEquals(range.getMin(), Slices.utf8Slice("")); - assertEquals(range.getMax(), Slices.utf8Slice(Character.toString(MAX_CODE_POINT).repeat(((VarcharType) type).getBoundedLength()))); + assertThat(range.getMin()).isEqualTo(Slices.utf8Slice("")); + assertThat(range.getMax()).isEqualTo(Slices.utf8Slice(Character.toString(MAX_CODE_POINT).repeat(((VarcharType) type).getBoundedLength()))); } @Test diff --git a/core/trino-main/src/test/java/io/trino/type/TestCharType.java b/core/trino-main/src/test/java/io/trino/type/TestCharType.java index 333fa0086fb3..f309c0e79cee 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestCharType.java +++ b/core/trino-main/src/test/java/io/trino/type/TestCharType.java @@ -33,8 +33,6 @@ import static java.lang.Character.MIN_SUPPLEMENTARY_CODE_POINT; import static java.lang.Character.isSupplementaryCodePoint; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; public class TestCharType extends AbstractTestType @@ -82,11 +80,15 @@ public void testGetObjectValue() int codePointLengthInUtf16 = isSupplementaryCodePoint(codePoint) ? 2 : 1; String objectValue = (String) charType.getObjectValue(SESSION, block, 0); - assertNotNull(objectValue); - assertEquals(objectValue.codePointAt(0), codePoint, "first code point"); - assertEquals(objectValue.length(), codePointLengthInUtf16 + 2, "size"); + assertThat(objectValue).isNotNull(); + assertThat(objectValue.codePointAt(0)) + .describedAs("first code point") + .isEqualTo(codePoint); + assertThat(objectValue.length()) + .describedAs("size") + .isEqualTo(codePointLengthInUtf16 + 2); for (int i = codePointLengthInUtf16; i < objectValue.length(); i++) { - assertEquals(objectValue.codePointAt(i), ' '); + assertThat(objectValue.codePointAt(i)).isEqualTo(' '); } } } @@ -95,8 +97,8 @@ public void testGetObjectValue() public void testRange() { Type.Range range = type.getRange().orElseThrow(); - assertEquals(range.getMin(), Slices.utf8Slice(Character.toString(MIN_CODE_POINT).repeat(((CharType) type).getLength()))); - assertEquals(range.getMax(), Slices.utf8Slice(Character.toString(MAX_CODE_POINT).repeat(((CharType) type).getLength()))); + assertThat(range.getMin()).isEqualTo(Slices.utf8Slice(Character.toString(MIN_CODE_POINT).repeat(((CharType) type).getLength()))); + assertThat(range.getMax()).isEqualTo(Slices.utf8Slice(Character.toString(MAX_CODE_POINT).repeat(((CharType) type).getLength()))); } @Test diff --git a/core/trino-main/src/test/java/io/trino/type/TestCharacterStringCasts.java b/core/trino-main/src/test/java/io/trino/type/TestCharacterStringCasts.java index 8b1a6e45f4be..953d8bb15bf4 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestCharacterStringCasts.java +++ b/core/trino-main/src/test/java/io/trino/type/TestCharacterStringCasts.java @@ -29,7 +29,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -141,86 +140,69 @@ public void testVarcharToCharSaturatedFloorCast() String maxCodePoint = new String(Character.toChars(Character.MAX_CODE_POINT)); String codePointBeforeSpace = new String(Character.toChars(' ' - 1)); - assertEquals(varcharToCharSaturatedFloorCast( + assertThat(varcharToCharSaturatedFloorCast( 5L, - utf8Slice("123" + new String(Character.toChars(0xE000)))), - utf8Slice("123" + new String(Character.toChars(0xD7FF)) + maxCodePoint)); + utf8Slice("123" + new String(Character.toChars(0xE000))))).isEqualTo(utf8Slice("123" + new String(Character.toChars(0xD7FF)) + maxCodePoint)); // Truncation - assertEquals(varcharToCharSaturatedFloorCast( + assertThat(varcharToCharSaturatedFloorCast( 4L, - utf8Slice("12345")), - utf8Slice("1234")); + utf8Slice("12345"))).isEqualTo(utf8Slice("1234")); // Size fits, preserved - assertEquals(varcharToCharSaturatedFloorCast( + assertThat(varcharToCharSaturatedFloorCast( 4L, - utf8Slice("1234")), - utf8Slice("1234")); - assertEquals(varcharToCharSaturatedFloorCast( + utf8Slice("1234"))).isEqualTo(utf8Slice("1234")); + assertThat(varcharToCharSaturatedFloorCast( 4L, - utf8Slice("123" + NON_BMP_CHARACTER)), - utf8Slice("123" + NON_BMP_CHARACTER)); - assertEquals(varcharToCharSaturatedFloorCast( + utf8Slice("123" + NON_BMP_CHARACTER))).isEqualTo(utf8Slice("123" + NON_BMP_CHARACTER)); + assertThat(varcharToCharSaturatedFloorCast( 4L, - utf8Slice("12" + NON_BMP_CHARACTER + "3")), - utf8Slice("12" + NON_BMP_CHARACTER + "3")); + utf8Slice("12" + NON_BMP_CHARACTER + "3"))).isEqualTo(utf8Slice("12" + NON_BMP_CHARACTER + "3")); // Size fits, preserved except char(4) representation has trailing spaces removed - assertEquals(varcharToCharSaturatedFloorCast( + assertThat(varcharToCharSaturatedFloorCast( 4L, - utf8Slice("123 ")), - utf8Slice("123")); + utf8Slice("123 "))).isEqualTo(utf8Slice("123")); // Too short, casted back would be padded with ' ' and thus made greater (VarcharOperators.lessThan), so last character needs decrementing - assertEquals(varcharToCharSaturatedFloorCast( + assertThat(varcharToCharSaturatedFloorCast( 4L, - utf8Slice("123")), - utf8Slice("122" + maxCodePoint)); - assertEquals(varcharToCharSaturatedFloorCast( + utf8Slice("123"))).isEqualTo(utf8Slice("122" + maxCodePoint)); + assertThat(varcharToCharSaturatedFloorCast( 4L, - utf8Slice("12 ")), - utf8Slice("12" + codePointBeforeSpace + maxCodePoint)); - assertEquals(varcharToCharSaturatedFloorCast( + utf8Slice("12 "))).isEqualTo(utf8Slice("12" + codePointBeforeSpace + maxCodePoint)); + assertThat(varcharToCharSaturatedFloorCast( 4L, - utf8Slice("1 ")), - utf8Slice("1 " + codePointBeforeSpace + maxCodePoint)); - assertEquals(varcharToCharSaturatedFloorCast( + utf8Slice("1 "))).isEqualTo(utf8Slice("1 " + codePointBeforeSpace + maxCodePoint)); + assertThat(varcharToCharSaturatedFloorCast( 4L, - utf8Slice(" ")), - utf8Slice(codePointBeforeSpace + maxCodePoint + maxCodePoint + maxCodePoint)); - assertEquals(varcharToCharSaturatedFloorCast( + utf8Slice(" "))).isEqualTo(utf8Slice(codePointBeforeSpace + maxCodePoint + maxCodePoint + maxCodePoint)); + assertThat(varcharToCharSaturatedFloorCast( 4L, - utf8Slice("12" + NON_BMP_CHARACTER)), - utf8Slice("12" + nonBmpCharacterMinusOne + maxCodePoint)); - assertEquals(varcharToCharSaturatedFloorCast( + utf8Slice("12" + NON_BMP_CHARACTER))).isEqualTo(utf8Slice("12" + nonBmpCharacterMinusOne + maxCodePoint)); + assertThat(varcharToCharSaturatedFloorCast( 4L, - utf8Slice("1" + NON_BMP_CHARACTER + "3")), - utf8Slice("1" + NON_BMP_CHARACTER + "2" + maxCodePoint)); + utf8Slice("1" + NON_BMP_CHARACTER + "3"))).isEqualTo(utf8Slice("1" + NON_BMP_CHARACTER + "2" + maxCodePoint)); // Too short, casted back would be padded with ' ' and thus made greater (VarcharOperators.lessThan), previous to last needs decrementing since last is \0 - assertEquals(varcharToCharSaturatedFloorCast( + assertThat(varcharToCharSaturatedFloorCast( 4L, - utf8Slice("12\0")), - utf8Slice("11" + maxCodePoint + maxCodePoint)); - assertEquals(varcharToCharSaturatedFloorCast( + utf8Slice("12\0"))).isEqualTo(utf8Slice("11" + maxCodePoint + maxCodePoint)); + assertThat(varcharToCharSaturatedFloorCast( 4L, - utf8Slice("1\0")), - utf8Slice("0" + maxCodePoint + maxCodePoint + maxCodePoint)); + utf8Slice("1\0"))).isEqualTo(utf8Slice("0" + maxCodePoint + maxCodePoint + maxCodePoint)); // Smaller than any char(4) casted back to varchar, so the result is lowest char(4) possible - assertEquals(varcharToCharSaturatedFloorCast( + assertThat(varcharToCharSaturatedFloorCast( 4L, - utf8Slice("\0")), - utf8Slice("\0\0\0\0")); - assertEquals(varcharToCharSaturatedFloorCast( + utf8Slice("\0"))).isEqualTo(utf8Slice("\0\0\0\0")); + assertThat(varcharToCharSaturatedFloorCast( 4L, - utf8Slice("\0\0")), - utf8Slice("\0\0\0\0")); - assertEquals(varcharToCharSaturatedFloorCast( + utf8Slice("\0\0"))).isEqualTo(utf8Slice("\0\0\0\0")); + assertThat(varcharToCharSaturatedFloorCast( 4L, - utf8Slice("")), - utf8Slice("\0\0\0\0")); + utf8Slice(""))).isEqualTo(utf8Slice("\0\0\0\0")); } @Test diff --git a/core/trino-main/src/test/java/io/trino/type/TestColorType.java b/core/trino-main/src/test/java/io/trino/type/TestColorType.java index 3d640f0d3f80..2bccda243d5f 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestColorType.java +++ b/core/trino-main/src/test/java/io/trino/type/TestColorType.java @@ -23,7 +23,6 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestColorType extends AbstractTestType @@ -49,9 +48,7 @@ public void testGetObjectValue() Block block = builder.build(); for (int position = 0; position < block.getPositionCount(); position++) { int value = block.getInt(position, 0); - assertEquals( - COLOR.getObjectValue(null, block, position), - format("#%02x%02x%02x", (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF)); + assertThat(COLOR.getObjectValue(null, block, position)).isEqualTo(format("#%02x%02x%02x", (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF)); } } diff --git a/core/trino-main/src/test/java/io/trino/type/TestDateType.java b/core/trino-main/src/test/java/io/trino/type/TestDateType.java index 9e3565cfcb86..c76b91831dc5 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestDateType.java +++ b/core/trino-main/src/test/java/io/trino/type/TestDateType.java @@ -23,7 +23,6 @@ import static io.trino.spi.type.DateType.DATE; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestDateType extends AbstractTestType @@ -60,8 +59,8 @@ protected Object getGreaterValue(Object value) public void testRange() { Range range = type.getRange().orElseThrow(); - assertEquals(range.getMin(), (long) Integer.MIN_VALUE); - assertEquals(range.getMax(), (long) Integer.MAX_VALUE); + assertThat(range.getMin()).isEqualTo((long) Integer.MIN_VALUE); + assertThat(range.getMax()).isEqualTo((long) Integer.MAX_VALUE); } @Test diff --git a/core/trino-main/src/test/java/io/trino/type/TestDoubleOperators.java b/core/trino-main/src/test/java/io/trino/type/TestDoubleOperators.java index 85a10b313519..faf6dcf3eafd 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestDoubleOperators.java +++ b/core/trino-main/src/test/java/io/trino/type/TestDoubleOperators.java @@ -48,8 +48,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -837,13 +835,13 @@ public void testNanHash() { long[] nanRepresentations = new long[] {doubleToLongBits(Double.NaN), 0xfff8000000000000L, 0x7ff8123412341234L, 0xfff8123412341234L}; for (long nanRepresentation : nanRepresentations) { - assertTrue(isNaN(longBitsToDouble(nanRepresentation))); + assertThat(isNaN(longBitsToDouble(nanRepresentation))).isTrue(); // longBitsToDouble() keeps the bitwise difference in NaN - assertTrue(nanRepresentation == nanRepresentations[0] - || doubleToRawLongBits(longBitsToDouble(nanRepresentation)) != doubleToRawLongBits(longBitsToDouble(nanRepresentations[0]))); + assertThat(nanRepresentation == nanRepresentations[0] + || doubleToRawLongBits(longBitsToDouble(nanRepresentation)) != doubleToRawLongBits(longBitsToDouble(nanRepresentations[0]))).isTrue(); - assertEquals(executeHashOperator(longBitsToDouble(nanRepresentation)), executeHashOperator(longBitsToDouble(nanRepresentations[0]))); - assertEquals(executeXxHash64Operator(longBitsToDouble(nanRepresentation)), executeXxHash64Operator(longBitsToDouble(nanRepresentations[0]))); + assertThat(executeHashOperator(longBitsToDouble(nanRepresentation))).isEqualTo(executeHashOperator(longBitsToDouble(nanRepresentations[0]))); + assertThat(executeXxHash64Operator(longBitsToDouble(nanRepresentation))).isEqualTo(executeXxHash64Operator(longBitsToDouble(nanRepresentations[0]))); } } @@ -854,9 +852,9 @@ public void testZeroHash() double[] zeroes = {0.0, -0.0}; for (double zero : zeroes) { //noinspection SimplifiedTestNGAssertion - assertTrue(zero == 0); - assertEquals(executeHashOperator(zero), executeHashOperator(zeroes[0])); - assertEquals(executeXxHash64Operator(zero), executeXxHash64Operator(zeroes[0])); + assertThat(zero == 0).isTrue(); + assertThat(executeHashOperator(zero)).isEqualTo(executeHashOperator(zeroes[0])); + assertThat(executeXxHash64Operator(zero)).isEqualTo(executeXxHash64Operator(zeroes[0])); } } diff --git a/core/trino-main/src/test/java/io/trino/type/TestDoubleType.java b/core/trino-main/src/test/java/io/trino/type/TestDoubleType.java index d13a3eee46c1..c52822ea96c1 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestDoubleType.java +++ b/core/trino-main/src/test/java/io/trino/type/TestDoubleType.java @@ -25,7 +25,6 @@ import static java.lang.Double.doubleToLongBits; import static java.lang.Double.doubleToRawLongBits; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestDoubleType extends AbstractTestType @@ -71,16 +70,16 @@ public void testNaNHash() Block block = blockBuilder.build(); BlockPositionHashCode hashCodeOperator = blockTypeOperators.getHashCodeOperator(DOUBLE); - assertEquals(hashCodeOperator.hashCode(block, 0), hashCodeOperator.hashCode(block, 1)); - assertEquals(hashCodeOperator.hashCode(block, 0), hashCodeOperator.hashCode(block, 2)); - assertEquals(hashCodeOperator.hashCode(block, 0), hashCodeOperator.hashCode(block, 3)); - assertEquals(hashCodeOperator.hashCode(block, 0), hashCodeOperator.hashCode(block, 4)); + assertThat(hashCodeOperator.hashCode(block, 0)).isEqualTo(hashCodeOperator.hashCode(block, 1)); + assertThat(hashCodeOperator.hashCode(block, 0)).isEqualTo(hashCodeOperator.hashCode(block, 2)); + assertThat(hashCodeOperator.hashCode(block, 0)).isEqualTo(hashCodeOperator.hashCode(block, 3)); + assertThat(hashCodeOperator.hashCode(block, 0)).isEqualTo(hashCodeOperator.hashCode(block, 4)); BlockPositionXxHash64 xxHash64Operator = blockTypeOperators.getXxHash64Operator(DOUBLE); - assertEquals(xxHash64Operator.xxHash64(block, 0), xxHash64Operator.xxHash64(block, 1)); - assertEquals(xxHash64Operator.xxHash64(block, 0), xxHash64Operator.xxHash64(block, 2)); - assertEquals(xxHash64Operator.xxHash64(block, 0), xxHash64Operator.xxHash64(block, 3)); - assertEquals(xxHash64Operator.xxHash64(block, 0), xxHash64Operator.xxHash64(block, 4)); + assertThat(xxHash64Operator.xxHash64(block, 0)).isEqualTo(xxHash64Operator.xxHash64(block, 1)); + assertThat(xxHash64Operator.xxHash64(block, 0)).isEqualTo(xxHash64Operator.xxHash64(block, 2)); + assertThat(xxHash64Operator.xxHash64(block, 0)).isEqualTo(xxHash64Operator.xxHash64(block, 3)); + assertThat(xxHash64Operator.xxHash64(block, 0)).isEqualTo(xxHash64Operator.xxHash64(block, 4)); } @Test diff --git a/core/trino-main/src/test/java/io/trino/type/TestFunctionType.java b/core/trino-main/src/test/java/io/trino/type/TestFunctionType.java index 6b4db788a624..38e001fbf69a 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestFunctionType.java +++ b/core/trino-main/src/test/java/io/trino/type/TestFunctionType.java @@ -21,7 +21,7 @@ import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.DoubleType.DOUBLE; import static io.trino.spi.type.RowType.field; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestFunctionType { @@ -32,6 +32,6 @@ public void testDisplayName() ImmutableList.of(RowType.from(ImmutableList.of(field("field", DOUBLE)))), BIGINT); - assertEquals(function.getDisplayName(), "function(row(field double),bigint)"); + assertThat(function.getDisplayName()).isEqualTo("function(row(field double),bigint)"); } } diff --git a/core/trino-main/src/test/java/io/trino/type/TestIntegerType.java b/core/trino-main/src/test/java/io/trino/type/TestIntegerType.java index 2a76b84a3c3a..624b12291ec7 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestIntegerType.java +++ b/core/trino-main/src/test/java/io/trino/type/TestIntegerType.java @@ -22,7 +22,6 @@ import static io.trino.spi.type.IntegerType.INTEGER; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestIntegerType extends AbstractTestType @@ -59,8 +58,8 @@ protected Object getGreaterValue(Object value) public void testRange() { Range range = type.getRange().orElseThrow(); - assertEquals(range.getMin(), (long) Integer.MIN_VALUE); - assertEquals(range.getMax(), (long) Integer.MAX_VALUE); + assertThat(range.getMin()).isEqualTo((long) Integer.MIN_VALUE); + assertThat(range.getMax()).isEqualTo((long) Integer.MAX_VALUE); } @Test diff --git a/core/trino-main/src/test/java/io/trino/type/TestIntervalDayTime.java b/core/trino-main/src/test/java/io/trino/type/TestIntervalDayTime.java index 7f54877fed69..815cc6190a25 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestIntervalDayTime.java +++ b/core/trino-main/src/test/java/io/trino/type/TestIntervalDayTime.java @@ -36,7 +36,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -60,14 +59,14 @@ public void teardown() @Test public void testObject() { - assertEquals(new SqlIntervalDayTime(12, 10, 45, 32, 123), new SqlIntervalDayTime(1_075_532_123)); - assertEquals(new SqlIntervalDayTime(-12, -10, -45, -32, -123), new SqlIntervalDayTime(-1_075_532_123)); + assertThat(new SqlIntervalDayTime(12, 10, 45, 32, 123)).isEqualTo(new SqlIntervalDayTime(1_075_532_123)); + assertThat(new SqlIntervalDayTime(-12, -10, -45, -32, -123)).isEqualTo(new SqlIntervalDayTime(-1_075_532_123)); - assertEquals(new SqlIntervalDayTime(30, 0, 0, 0, 0), new SqlIntervalDayTime(DAYS.toMillis(30))); - assertEquals(new SqlIntervalDayTime(-30, 0, 0, 0, 0), new SqlIntervalDayTime(-DAYS.toMillis(30))); + assertThat(new SqlIntervalDayTime(30, 0, 0, 0, 0)).isEqualTo(new SqlIntervalDayTime(DAYS.toMillis(30))); + assertThat(new SqlIntervalDayTime(-30, 0, 0, 0, 0)).isEqualTo(new SqlIntervalDayTime(-DAYS.toMillis(30))); - assertEquals(new SqlIntervalDayTime(90, 0, 0, 0, 0), new SqlIntervalDayTime(DAYS.toMillis(90))); - assertEquals(new SqlIntervalDayTime(-90, 0, 0, 0, 0), new SqlIntervalDayTime(-DAYS.toMillis(90))); + assertThat(new SqlIntervalDayTime(90, 0, 0, 0, 0)).isEqualTo(new SqlIntervalDayTime(DAYS.toMillis(90))); + assertThat(new SqlIntervalDayTime(-90, 0, 0, 0, 0)).isEqualTo(new SqlIntervalDayTime(-DAYS.toMillis(90))); } @Test diff --git a/core/trino-main/src/test/java/io/trino/type/TestIntervalYearMonth.java b/core/trino-main/src/test/java/io/trino/type/TestIntervalYearMonth.java index e17324c209ea..5cbe976c4fa8 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestIntervalYearMonth.java +++ b/core/trino-main/src/test/java/io/trino/type/TestIntervalYearMonth.java @@ -35,7 +35,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -61,14 +60,14 @@ public void teardown() @Test public void testObject() { - assertEquals(new SqlIntervalYearMonth(3, 11), new SqlIntervalYearMonth(47)); - assertEquals(new SqlIntervalYearMonth(-3, -11), new SqlIntervalYearMonth(-47)); + assertThat(new SqlIntervalYearMonth(3, 11)).isEqualTo(new SqlIntervalYearMonth(47)); + assertThat(new SqlIntervalYearMonth(-3, -11)).isEqualTo(new SqlIntervalYearMonth(-47)); - assertEquals(new SqlIntervalYearMonth(MAX_SHORT, 0), new SqlIntervalYearMonth(393_204)); - assertEquals(new SqlIntervalYearMonth(MAX_SHORT, MAX_SHORT), new SqlIntervalYearMonth(425_971)); + assertThat(new SqlIntervalYearMonth(MAX_SHORT, 0)).isEqualTo(new SqlIntervalYearMonth(393_204)); + assertThat(new SqlIntervalYearMonth(MAX_SHORT, MAX_SHORT)).isEqualTo(new SqlIntervalYearMonth(425_971)); - assertEquals(new SqlIntervalYearMonth(-MAX_SHORT, 0), new SqlIntervalYearMonth(-393_204)); - assertEquals(new SqlIntervalYearMonth(-MAX_SHORT, -MAX_SHORT), new SqlIntervalYearMonth(-425_971)); + assertThat(new SqlIntervalYearMonth(-MAX_SHORT, 0)).isEqualTo(new SqlIntervalYearMonth(-393_204)); + assertThat(new SqlIntervalYearMonth(-MAX_SHORT, -MAX_SHORT)).isEqualTo(new SqlIntervalYearMonth(-425_971)); } @Test diff --git a/core/trino-main/src/test/java/io/trino/type/TestIpAddressType.java b/core/trino-main/src/test/java/io/trino/type/TestIpAddressType.java index 6c4a4c7d42ee..8c172e041d5d 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestIpAddressType.java +++ b/core/trino-main/src/test/java/io/trino/type/TestIpAddressType.java @@ -23,7 +23,6 @@ import static com.google.common.base.Preconditions.checkState; import static io.trino.type.IpAddressType.IPADDRESS; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestIpAddressType extends AbstractTestType @@ -66,7 +65,7 @@ protected Object getNonNullValue() @Test public void testDisplayName() { - assertEquals((IPADDRESS).getDisplayName(), "ipaddress"); + assertThat((IPADDRESS).getDisplayName()).isEqualTo("ipaddress"); } private static Slice getSliceForAddress(String address) diff --git a/core/trino-main/src/test/java/io/trino/type/TestLongTimestampType.java b/core/trino-main/src/test/java/io/trino/type/TestLongTimestampType.java index 6e194fafc0fc..abc7432afa6d 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestLongTimestampType.java +++ b/core/trino-main/src/test/java/io/trino/type/TestLongTimestampType.java @@ -26,7 +26,6 @@ import static io.trino.spi.type.TimestampType.TIMESTAMP_NANOS; import static io.trino.spi.type.TimestampType.createTimestampType; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestLongTimestampType extends AbstractTestType @@ -64,8 +63,8 @@ protected Object getGreaterValue(Object value) public void testRange() { Range range = type.getRange().orElseThrow(); - assertEquals(range.getMin(), new LongTimestamp(Long.MIN_VALUE, 0)); - assertEquals(range.getMax(), new LongTimestamp(Long.MAX_VALUE, 999_000)); + assertThat(range.getMin()).isEqualTo(new LongTimestamp(Long.MIN_VALUE, 0)); + assertThat(range.getMax()).isEqualTo(new LongTimestamp(Long.MAX_VALUE, 999_000)); } @Test @@ -73,8 +72,8 @@ public void testRangeEveryPrecision() { for (MaxPrecision entry : maxPrecisions()) { Range range = createTimestampType(entry.precision()).getRange().orElseThrow(); - assertEquals(range.getMin(), new LongTimestamp(Long.MIN_VALUE, 0)); - assertEquals(range.getMax(), entry.expectedMax()); + assertThat(range.getMin()).isEqualTo(new LongTimestamp(Long.MIN_VALUE, 0)); + assertThat(range.getMax()).isEqualTo(entry.expectedMax()); } } diff --git a/core/trino-main/src/test/java/io/trino/type/TestRealOperators.java b/core/trino-main/src/test/java/io/trino/type/TestRealOperators.java index bbf8e2a0bd3a..1974b546f3f2 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestRealOperators.java +++ b/core/trino-main/src/test/java/io/trino/type/TestRealOperators.java @@ -47,8 +47,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -794,9 +792,9 @@ public void testNanHash() { int[] nanRepresentations = {floatToIntBits(Float.NaN), 0xffc00000, 0x7fc00000, 0x7fc01234, 0xffc01234}; for (int nanRepresentation : nanRepresentations) { - assertTrue(isNaN(intBitsToFloat(nanRepresentation))); - assertEquals(executeHashOperator(nanRepresentation), executeHashOperator(nanRepresentations[0])); - assertEquals(executeXxHas64hOperator(nanRepresentation), executeXxHas64hOperator(nanRepresentations[0])); + assertThat(isNaN(intBitsToFloat(nanRepresentation))).isTrue(); + assertThat(executeHashOperator(nanRepresentation)).isEqualTo(executeHashOperator(nanRepresentations[0])); + assertThat(executeXxHas64hOperator(nanRepresentation)).isEqualTo(executeXxHas64hOperator(nanRepresentations[0])); } } @@ -807,9 +805,9 @@ public void testZeroHash() int[] zeroes = {floatToIntBits(0.0f), floatToIntBits(-0.0f)}; for (int zero : zeroes) { //noinspection SimplifiedTestNGAssertion - assertTrue(intBitsToFloat(zero) == 0f); - assertEquals(executeHashOperator(zero), executeHashOperator(zeroes[0])); - assertEquals(executeXxHas64hOperator(zero), executeXxHas64hOperator(zeroes[0])); + assertThat(intBitsToFloat(zero) == 0f).isTrue(); + assertThat(executeHashOperator(zero)).isEqualTo(executeHashOperator(zeroes[0])); + assertThat(executeXxHas64hOperator(zero)).isEqualTo(executeXxHas64hOperator(zeroes[0])); } } diff --git a/core/trino-main/src/test/java/io/trino/type/TestRealType.java b/core/trino-main/src/test/java/io/trino/type/TestRealType.java index 7fbfa28d67c1..0dfc38e59773 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestRealType.java +++ b/core/trino-main/src/test/java/io/trino/type/TestRealType.java @@ -25,7 +25,6 @@ import static java.lang.Float.floatToRawIntBits; import static java.lang.Float.intBitsToFloat; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestRealType extends AbstractTestType @@ -73,10 +72,10 @@ public void testNaNHash() Block block = blockBuilder.build(); BlockPositionHashCode hashCodeOperator = blockTypeOperators.getHashCodeOperator(REAL); - assertEquals(hashCodeOperator.hashCode(block, 0), hashCodeOperator.hashCode(block, 1)); - assertEquals(hashCodeOperator.hashCode(block, 0), hashCodeOperator.hashCode(block, 2)); - assertEquals(hashCodeOperator.hashCode(block, 0), hashCodeOperator.hashCode(block, 3)); - assertEquals(hashCodeOperator.hashCode(block, 0), hashCodeOperator.hashCode(block, 4)); + assertThat(hashCodeOperator.hashCode(block, 0)).isEqualTo(hashCodeOperator.hashCode(block, 1)); + assertThat(hashCodeOperator.hashCode(block, 0)).isEqualTo(hashCodeOperator.hashCode(block, 2)); + assertThat(hashCodeOperator.hashCode(block, 0)).isEqualTo(hashCodeOperator.hashCode(block, 3)); + assertThat(hashCodeOperator.hashCode(block, 0)).isEqualTo(hashCodeOperator.hashCode(block, 4)); } @Test diff --git a/core/trino-main/src/test/java/io/trino/type/TestRowOperators.java b/core/trino-main/src/test/java/io/trino/type/TestRowOperators.java index afed7687dd03..fd5d52eb2456 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestRowOperators.java +++ b/core/trino-main/src/test/java/io/trino/type/TestRowOperators.java @@ -79,7 +79,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -117,8 +116,8 @@ public void testRowTypeLookup() { TypeSignature signature = RowType.from(ImmutableList.of(field("b", BIGINT))).getTypeSignature(); Type type = ((LocalQueryRunner) assertions.getQueryRunner()).getPlannerContext().getTypeManager().getType(signature); - assertEquals(type.getTypeSignature().getParameters().size(), 1); - assertEquals(type.getTypeSignature().getParameters().get(0).getNamedTypeSignature().getName().get(), "b"); + assertThat(type.getTypeSignature().getParameters().size()).isEqualTo(1); + assertThat(type.getTypeSignature().getParameters().get(0).getNamedTypeSignature().getName().get()).isEqualTo("b"); } @Test diff --git a/core/trino-main/src/test/java/io/trino/type/TestRowParametricType.java b/core/trino-main/src/test/java/io/trino/type/TestRowParametricType.java index fda1b96e61e6..ce578ee9dc2a 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestRowParametricType.java +++ b/core/trino-main/src/test/java/io/trino/type/TestRowParametricType.java @@ -29,7 +29,7 @@ import static io.trino.spi.type.DoubleType.DOUBLE; import static io.trino.spi.type.StandardTypes.ROW; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestRowParametricType { @@ -45,6 +45,6 @@ public void testTypeSignatureRoundTrip() .collect(Collectors.toList()); Type rowType = RowParametricType.ROW.createType(TESTING_TYPE_MANAGER, parameters); - assertEquals(rowType.getTypeSignature(), typeSignature); + assertThat(rowType.getTypeSignature()).isEqualTo(typeSignature); } } diff --git a/core/trino-main/src/test/java/io/trino/type/TestShortTimestampType.java b/core/trino-main/src/test/java/io/trino/type/TestShortTimestampType.java index a80089c14bca..2dd2839e31a7 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestShortTimestampType.java +++ b/core/trino-main/src/test/java/io/trino/type/TestShortTimestampType.java @@ -29,7 +29,6 @@ import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; import static io.trino.spi.type.TimestampType.createTimestampType; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestShortTimestampType extends AbstractTestType @@ -66,8 +65,8 @@ protected Object getGreaterValue(Object value) public void testRange() { Range range = type.getRange().orElseThrow(); - assertEquals(range.getMin(), Long.MIN_VALUE + 808); - assertEquals(range.getMax(), Long.MAX_VALUE - 807); + assertThat(range.getMin()).isEqualTo(Long.MIN_VALUE + 808); + assertThat(range.getMax()).isEqualTo(Long.MAX_VALUE - 807); } @ParameterizedTest @@ -75,8 +74,8 @@ public void testRange() public void testRangeEveryPrecision(int precision, long expectedMin, long expectedMax) { Range range = createTimestampType(precision).getRange().orElseThrow(); - assertEquals(range.getMin(), expectedMin); - assertEquals(range.getMax(), expectedMax); + assertThat(range.getMin()).isEqualTo(expectedMin); + assertThat(range.getMax()).isEqualTo(expectedMax); } public static Stream testRangeEveryPrecisionDataProvider() diff --git a/core/trino-main/src/test/java/io/trino/type/TestSingleAccessMethodCompiler.java b/core/trino-main/src/test/java/io/trino/type/TestSingleAccessMethodCompiler.java index a74973e9bb07..204d0c0658b4 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestSingleAccessMethodCompiler.java +++ b/core/trino-main/src/test/java/io/trino/type/TestSingleAccessMethodCompiler.java @@ -22,9 +22,8 @@ import static io.trino.util.SingleAccessMethodCompiler.compileSingleAccessMethod; import static java.lang.invoke.MethodHandles.lookup; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; public class TestSingleAccessMethodCompiler { @@ -36,7 +35,7 @@ public void testBasic() "AddOne", LongUnaryOperator.class, lookup().findStatic(TestSingleAccessMethodCompiler.class, "increment", MethodType.methodType(long.class, long.class))); - assertEquals(addOne.applyAsLong(1), 2L); + assertThat(addOne.applyAsLong(1)).isEqualTo(2L); } private static long increment(long x) @@ -55,7 +54,7 @@ public void testBasicWithClassNameTooLong() builder.append("NameThatIsLongerThanTheAllowedSymbolTableUTF8ConstantSize"); } String suggestedName = builder.toString(); - assertEquals(suggestedName.length(), overflowingNameLength); + assertThat(suggestedName.length()).isEqualTo(overflowingNameLength); // Ensure that symbol table entries are still limited to 65535 bytes assertThatThrownBy(() -> new ByteVector().putUTF8(suggestedName)) @@ -67,8 +66,10 @@ public void testBasicWithClassNameTooLong() suggestedName, LongUnaryOperator.class, lookup().findStatic(TestSingleAccessMethodCompiler.class, "increment", MethodType.methodType(long.class, long.class))); - assertEquals(addOne.applyAsLong(1), 2L); - assertTrue(addOne.getClass().getName().length() < symbolTableSizeLimit, "class name should be truncated with extra room to spare"); + assertThat(addOne.applyAsLong(1)).isEqualTo(2L); + assertThat(addOne.getClass().getName().length() < symbolTableSizeLimit) + .describedAs("class name should be truncated with extra room to spare") + .isTrue(); } @Test @@ -80,7 +81,7 @@ public void testGeneric() "Print", LongFunction.class, lookup().findStatic(TestSingleAccessMethodCompiler.class, "incrementAndPrint", MethodType.methodType(String.class, long.class))); - assertEquals(print.apply(1), "2"); + assertThat(print.apply(1)).isEqualTo("2"); } private static String incrementAndPrint(long x) diff --git a/core/trino-main/src/test/java/io/trino/type/TestSmallintType.java b/core/trino-main/src/test/java/io/trino/type/TestSmallintType.java index 46a163aa9d2b..9bc3e95355a4 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestSmallintType.java +++ b/core/trino-main/src/test/java/io/trino/type/TestSmallintType.java @@ -22,7 +22,6 @@ import static io.trino.spi.type.SmallintType.SMALLINT; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestSmallintType extends AbstractTestType @@ -59,8 +58,8 @@ protected Object getGreaterValue(Object value) public void testRange() { Range range = type.getRange().orElseThrow(); - assertEquals(range.getMin(), (long) Short.MIN_VALUE); - assertEquals(range.getMax(), (long) Short.MAX_VALUE); + assertThat(range.getMin()).isEqualTo((long) Short.MIN_VALUE); + assertThat(range.getMax()).isEqualTo((long) Short.MAX_VALUE); } @Test diff --git a/core/trino-main/src/test/java/io/trino/type/TestTinyintType.java b/core/trino-main/src/test/java/io/trino/type/TestTinyintType.java index c4987a648156..3624977d8afb 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestTinyintType.java +++ b/core/trino-main/src/test/java/io/trino/type/TestTinyintType.java @@ -22,7 +22,6 @@ import static io.trino.spi.type.TinyintType.TINYINT; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestTinyintType extends AbstractTestType @@ -59,8 +58,8 @@ protected Object getGreaterValue(Object value) public void testRange() { Range range = type.getRange().orElseThrow(); - assertEquals(range.getMin(), (long) Byte.MIN_VALUE); - assertEquals(range.getMax(), (long) Byte.MAX_VALUE); + assertThat(range.getMin()).isEqualTo((long) Byte.MIN_VALUE); + assertThat(range.getMax()).isEqualTo((long) Byte.MAX_VALUE); } @Test diff --git a/core/trino-main/src/test/java/io/trino/type/TestTypeCoercion.java b/core/trino-main/src/test/java/io/trino/type/TestTypeCoercion.java index 360f38327553..4a527917c57e 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestTypeCoercion.java +++ b/core/trino-main/src/test/java/io/trino/type/TestTypeCoercion.java @@ -22,6 +22,7 @@ import io.trino.spi.type.TypeManager; import io.trino.spi.type.TypeOperators; import io.trino.spi.type.TypeSignature; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import java.util.Collection; @@ -58,10 +59,7 @@ import static io.trino.type.UnknownType.UNKNOWN; import static java.lang.String.format; import static java.util.Objects.requireNonNull; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Fail.fail; public class TestTypeCoercion { @@ -74,45 +72,45 @@ public class TestTypeCoercion @Test public void testIsTypeOnlyCoercion() { - assertTrue(typeCoercion.isTypeOnlyCoercion(BIGINT, BIGINT)); - assertTrue(typeCoercion.isTypeOnlyCoercion(createVarcharType(42), createVarcharType(44))); - assertFalse(typeCoercion.isTypeOnlyCoercion(createVarcharType(44), createVarcharType(42))); + Assertions.assertThat(typeCoercion.isTypeOnlyCoercion(BIGINT, BIGINT)).isTrue(); + Assertions.assertThat(typeCoercion.isTypeOnlyCoercion(createVarcharType(42), createVarcharType(44))).isTrue(); + Assertions.assertThat(typeCoercion.isTypeOnlyCoercion(createVarcharType(44), createVarcharType(42))).isFalse(); - assertFalse(typeCoercion.isTypeOnlyCoercion(createCharType(42), createVarcharType(42))); + Assertions.assertThat(typeCoercion.isTypeOnlyCoercion(createCharType(42), createVarcharType(42))).isFalse(); - assertTrue(typeCoercion.isTypeOnlyCoercion(new ArrayType(createVarcharType(42)), new ArrayType(createVarcharType(44)))); - assertFalse(typeCoercion.isTypeOnlyCoercion(new ArrayType(createVarcharType(44)), new ArrayType(createVarcharType(42)))); + Assertions.assertThat(typeCoercion.isTypeOnlyCoercion(new ArrayType(createVarcharType(42)), new ArrayType(createVarcharType(44)))).isTrue(); + Assertions.assertThat(typeCoercion.isTypeOnlyCoercion(new ArrayType(createVarcharType(44)), new ArrayType(createVarcharType(42)))).isFalse(); - assertTrue(typeCoercion.isTypeOnlyCoercion(createDecimalType(22, 1), createDecimalType(23, 1))); - assertTrue(typeCoercion.isTypeOnlyCoercion(createDecimalType(2, 1), createDecimalType(3, 1))); - assertFalse(typeCoercion.isTypeOnlyCoercion(createDecimalType(23, 1), createDecimalType(22, 1))); - assertFalse(typeCoercion.isTypeOnlyCoercion(createDecimalType(3, 1), createDecimalType(2, 1))); - assertFalse(typeCoercion.isTypeOnlyCoercion(createDecimalType(3, 1), createDecimalType(22, 1))); + Assertions.assertThat(typeCoercion.isTypeOnlyCoercion(createDecimalType(22, 1), createDecimalType(23, 1))).isTrue(); + Assertions.assertThat(typeCoercion.isTypeOnlyCoercion(createDecimalType(2, 1), createDecimalType(3, 1))).isTrue(); + Assertions.assertThat(typeCoercion.isTypeOnlyCoercion(createDecimalType(23, 1), createDecimalType(22, 1))).isFalse(); + Assertions.assertThat(typeCoercion.isTypeOnlyCoercion(createDecimalType(3, 1), createDecimalType(2, 1))).isFalse(); + Assertions.assertThat(typeCoercion.isTypeOnlyCoercion(createDecimalType(3, 1), createDecimalType(22, 1))).isFalse(); - assertTrue(typeCoercion.isTypeOnlyCoercion(new ArrayType(createDecimalType(22, 1)), new ArrayType(createDecimalType(23, 1)))); - assertTrue(typeCoercion.isTypeOnlyCoercion(new ArrayType(createDecimalType(2, 1)), new ArrayType(createDecimalType(3, 1)))); - assertFalse(typeCoercion.isTypeOnlyCoercion(new ArrayType(createDecimalType(23, 1)), new ArrayType(createDecimalType(22, 1)))); - assertFalse(typeCoercion.isTypeOnlyCoercion(new ArrayType(createDecimalType(3, 1)), new ArrayType(createDecimalType(2, 1)))); + Assertions.assertThat(typeCoercion.isTypeOnlyCoercion(new ArrayType(createDecimalType(22, 1)), new ArrayType(createDecimalType(23, 1)))).isTrue(); + Assertions.assertThat(typeCoercion.isTypeOnlyCoercion(new ArrayType(createDecimalType(2, 1)), new ArrayType(createDecimalType(3, 1)))).isTrue(); + Assertions.assertThat(typeCoercion.isTypeOnlyCoercion(new ArrayType(createDecimalType(23, 1)), new ArrayType(createDecimalType(22, 1)))).isFalse(); + Assertions.assertThat(typeCoercion.isTypeOnlyCoercion(new ArrayType(createDecimalType(3, 1)), new ArrayType(createDecimalType(2, 1)))).isFalse(); - assertTrue(typeCoercion.isTypeOnlyCoercion( + Assertions.assertThat(typeCoercion.isTypeOnlyCoercion( mapType(createDecimalType(2, 1), createDecimalType(2, 1)), - mapType(createDecimalType(2, 1), createDecimalType(3, 1)))); + mapType(createDecimalType(2, 1), createDecimalType(3, 1)))).isTrue(); - assertFalse(typeCoercion.isTypeOnlyCoercion( + Assertions.assertThat(typeCoercion.isTypeOnlyCoercion( mapType(createDecimalType(2, 1), createDecimalType(2, 1)), - mapType(createDecimalType(2, 1), createDecimalType(23, 1)))); + mapType(createDecimalType(2, 1), createDecimalType(23, 1)))).isFalse(); - assertFalse(typeCoercion.isTypeOnlyCoercion( + Assertions.assertThat(typeCoercion.isTypeOnlyCoercion( mapType(createDecimalType(2, 1), createDecimalType(2, 1)), - mapType(createDecimalType(2, 1), createDecimalType(3, 2)))); + mapType(createDecimalType(2, 1), createDecimalType(3, 2)))).isFalse(); - assertTrue(typeCoercion.isTypeOnlyCoercion( + Assertions.assertThat(typeCoercion.isTypeOnlyCoercion( mapType(createDecimalType(2, 1), createDecimalType(2, 1)), - mapType(createDecimalType(3, 1), createDecimalType(3, 1)))); + mapType(createDecimalType(3, 1), createDecimalType(3, 1)))).isTrue(); - assertFalse(typeCoercion.isTypeOnlyCoercion( + Assertions.assertThat(typeCoercion.isTypeOnlyCoercion( mapType(createDecimalType(3, 1), createDecimalType(3, 1)), - mapType(createDecimalType(2, 1), createDecimalType(2, 1)))); + mapType(createDecimalType(2, 1), createDecimalType(2, 1)))).isFalse(); } private Type mapType(Type keyType, Type valueType) @@ -297,11 +295,11 @@ public void testTypeCompatibility() @Test public void testCoerceTypeBase() { - assertEquals(typeCoercion.coerceTypeBase(createDecimalType(21, 1), "decimal"), Optional.of(createDecimalType(21, 1))); - assertEquals(typeCoercion.coerceTypeBase(BIGINT, "decimal"), Optional.of(createDecimalType(19, 0))); - assertEquals(typeCoercion.coerceTypeBase(INTEGER, "decimal"), Optional.of(createDecimalType(10, 0))); - assertEquals(typeCoercion.coerceTypeBase(TINYINT, "decimal"), Optional.of(createDecimalType(3, 0))); - assertEquals(typeCoercion.coerceTypeBase(SMALLINT, "decimal"), Optional.of(createDecimalType(5, 0))); + Assertions.assertThat(typeCoercion.coerceTypeBase(createDecimalType(21, 1), "decimal")).isEqualTo(Optional.of(createDecimalType(21, 1))); + Assertions.assertThat(typeCoercion.coerceTypeBase(BIGINT, "decimal")).isEqualTo(Optional.of(createDecimalType(19, 0))); + Assertions.assertThat(typeCoercion.coerceTypeBase(INTEGER, "decimal")).isEqualTo(Optional.of(createDecimalType(10, 0))); + Assertions.assertThat(typeCoercion.coerceTypeBase(TINYINT, "decimal")).isEqualTo(Optional.of(createDecimalType(3, 0))); + Assertions.assertThat(typeCoercion.coerceTypeBase(SMALLINT, "decimal")).isEqualTo(Optional.of(createDecimalType(5, 0))); } @Test @@ -364,7 +362,9 @@ private CompatibilityAssertion assertThat(Type firstType, Type secondType) { Optional commonSuperType1 = typeCoercion.getCommonSuperType(firstType, secondType); Optional commonSuperType2 = typeCoercion.getCommonSuperType(secondType, firstType); - assertEquals(commonSuperType1, commonSuperType2, "Expected getCommonSuperType to return the same result when invoked in either order"); + Assertions.assertThat(commonSuperType1) + .describedAs("Expected getCommonSuperType to return the same result when invoked in either order") + .isEqualTo(commonSuperType2); boolean canCoerceFirstToSecond = typeCoercion.canCoerce(firstType, secondType); boolean canCoerceSecondToFirst = typeCoercion.canCoerce(secondType, firstType); return new CompatibilityAssertion(commonSuperType1, canCoerceFirstToSecond, canCoerceSecondToFirst); @@ -381,48 +381,72 @@ public CompatibilityAssertion(Optional commonSuperType, boolean canCoerceF this.commonSuperType = requireNonNull(commonSuperType, "commonSuperType is null"); // Assert that: (canFirstCoerceToSecond || canSecondCoerceToFirst) => commonSuperType.isPresent - assertTrue(!(canCoerceFirstToSecond || canCoerceSecondToFirst) || commonSuperType.isPresent(), "Expected canCoercion to be false when there is no commonSuperType"); + Assertions.assertThat(!(canCoerceFirstToSecond || canCoerceSecondToFirst) || commonSuperType.isPresent()) + .describedAs("Expected canCoercion to be false when there is no commonSuperType") + .isTrue(); this.canCoerceFirstToSecond = canCoerceFirstToSecond; this.canCoerceSecondToFirst = canCoerceSecondToFirst; } public void isIncompatible() { - assertTrue(commonSuperType.isEmpty(), "Expected to be incompatible"); + Assertions.assertThat(commonSuperType.isEmpty()) + .describedAs("Expected to be incompatible") + .isTrue(); } public CompatibilityAssertion hasCommonSuperType(Type expected) { - assertTrue(commonSuperType.isPresent(), "Expected commonSuperType to be present"); - assertEquals(commonSuperType.get(), expected, "commonSuperType"); + Assertions.assertThat(commonSuperType.isPresent()) + .describedAs("Expected commonSuperType to be present") + .isTrue(); + Assertions.assertThat(commonSuperType.get()) + .describedAs("commonSuperType") + .isEqualTo(expected); return this; } public CompatibilityAssertion canCoerceToEachOther() { - assertTrue(canCoerceFirstToSecond, "Expected first be coercible to second"); - assertTrue(canCoerceSecondToFirst, "Expected second be coercible to first"); + Assertions.assertThat(canCoerceFirstToSecond) + .describedAs("Expected first be coercible to second") + .isTrue(); + Assertions.assertThat(canCoerceSecondToFirst) + .describedAs("Expected second be coercible to first") + .isTrue(); return this; } public CompatibilityAssertion canCoerceFirstToSecondOnly() { - assertTrue(canCoerceFirstToSecond, "Expected first be coercible to second"); - assertFalse(canCoerceSecondToFirst, "Expected second NOT be coercible to first"); + Assertions.assertThat(canCoerceFirstToSecond) + .describedAs("Expected first be coercible to second") + .isTrue(); + Assertions.assertThat(canCoerceSecondToFirst) + .describedAs("Expected second NOT be coercible to first") + .isFalse(); return this; } public CompatibilityAssertion canCoerceSecondToFirstOnly() { - assertFalse(canCoerceFirstToSecond, "Expected first NOT be coercible to second"); - assertTrue(canCoerceSecondToFirst, "Expected second be coercible to first"); + Assertions.assertThat(canCoerceFirstToSecond) + .describedAs("Expected first NOT be coercible to second") + .isFalse(); + Assertions.assertThat(canCoerceSecondToFirst) + .describedAs("Expected second be coercible to first") + .isTrue(); return this; } public CompatibilityAssertion cannotCoerceToEachOther() { - assertFalse(canCoerceFirstToSecond, "Expected first NOT be coercible to second"); - assertFalse(canCoerceSecondToFirst, "Expected second NOT be coercible to first"); + Assertions.assertThat(canCoerceFirstToSecond) + .describedAs("Expected first NOT be coercible to second") + .isFalse(); + Assertions.assertThat(canCoerceSecondToFirst) + .describedAs("Expected second NOT be coercible to first") + .isFalse(); return this; } } diff --git a/core/trino-main/src/test/java/io/trino/type/TestUuidType.java b/core/trino-main/src/test/java/io/trino/type/TestUuidType.java index c10478f35c53..0ee5a55a40b7 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestUuidType.java +++ b/core/trino-main/src/test/java/io/trino/type/TestUuidType.java @@ -34,7 +34,6 @@ import static io.trino.type.UuidOperators.castFromVarcharToUuid; import static java.lang.Long.reverseBytes; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestUuidType extends AbstractTestType @@ -73,7 +72,7 @@ protected Object getNonNullValue() @Test public void testDisplayName() { - assertEquals(UUID.getDisplayName(), "uuid"); + assertThat(UUID.getDisplayName()).isEqualTo("uuid"); } @Test diff --git a/core/trino-main/src/test/java/io/trino/type/setdigest/TestSetDigest.java b/core/trino-main/src/test/java/io/trino/type/setdigest/TestSetDigest.java index 30ced6f675f0..b03a4c75ae64 100644 --- a/core/trino-main/src/test/java/io/trino/type/setdigest/TestSetDigest.java +++ b/core/trino-main/src/test/java/io/trino/type/setdigest/TestSetDigest.java @@ -35,8 +35,7 @@ import static io.trino.type.setdigest.SetDigestFunctions.hashCounts; import static io.trino.type.setdigest.SetDigestFunctions.intersectionCardinality; import static java.lang.String.format; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestSetDigest { @@ -84,8 +83,9 @@ private static void testIntersectionCardinality(int maxHashes1, int numBuckets1, } long estimatedCardinality = intersectionCardinality(digest1.serialize(), digest2.serialize()); - assertTrue(Math.abs(expectedCardinality - estimatedCardinality) / (double) expectedCardinality < 0.10, - format("Expected intersection cardinality %d +/- 10%%, got %d, for set of size %d", expectedCardinality, estimatedCardinality, size)); + assertThat(Math.abs(expectedCardinality - estimatedCardinality) / (double) expectedCardinality < 0.10) + .describedAs(format("Expected intersection cardinality %d +/- 10%%, got %d, for set of size %d", expectedCardinality, estimatedCardinality, size)) + .isTrue(); } } @@ -110,7 +110,7 @@ public void testHashCounts() blockValues.add(sqlMap.getRawValueBlock().getShort(sqlMap.getRawOffset() + i, 0)); } Set expected = ImmutableSet.of((short) 1, (short) 2); - assertEquals(blockValues, expected); + assertThat(blockValues).isEqualTo(expected); digest1.mergeWith(digest2); sqlMap = hashCounts(mapType, digest1.serialize()); @@ -119,7 +119,7 @@ public void testHashCounts() for (int i = 0; i < sqlMap.getSize(); i++) { blockValues.add(sqlMap.getRawValueBlock().getShort(sqlMap.getRawOffset() + i, 0)); } - assertEquals(blockValues, expected); + assertThat(blockValues).isEqualTo(expected); } @Test @@ -163,10 +163,10 @@ public void testSmallLargeIntersections() long estIntersectionCardinality = intersectionCardinality(digest1.serialize(), digest2.serialize()); double size2 = digest2.cardinality(); - assertTrue(estIntersectionCardinality <= size2); + assertThat(estIntersectionCardinality <= size2).isTrue(); int expectedCardinality = pair.getValue(); - assertTrue(Math.abs(expectedCardinality - estIntersectionCardinality) / - (double) size1 < 0.05); + assertThat(Math.abs(expectedCardinality - estIntersectionCardinality) / + (double) size1 < 0.05).isTrue(); } } } diff --git a/core/trino-main/src/test/java/io/trino/util/BenchmarkPagesSort.java b/core/trino-main/src/test/java/io/trino/util/BenchmarkPagesSort.java index fde9badf63e7..4af65429e429 100644 --- a/core/trino-main/src/test/java/io/trino/util/BenchmarkPagesSort.java +++ b/core/trino-main/src/test/java/io/trino/util/BenchmarkPagesSort.java @@ -51,9 +51,9 @@ import static io.trino.util.MergeSortedPages.mergeSortedPages; import static java.util.Collections.nCopies; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.assertj.core.api.Assertions.assertThat; import static org.openjdk.jmh.annotations.Mode.AverageTime; import static org.openjdk.jmh.annotations.Scope.Thread; -import static org.testng.Assert.assertEquals; @State(Thread) @OutputTimeUnit(MILLISECONDS) @@ -90,7 +90,7 @@ public void verifyPagesIndexSortBenchmark() int positionCount = pages.stream() .mapToInt(Page::getPositionCount) .sum(); - assertEquals(positionCount, state.getTotalPositions()); + assertThat(positionCount).isEqualTo(state.getTotalPositions()); } @State(Thread) @@ -151,7 +151,7 @@ public void verifyPagesMergeSortBenchmark() int positionCount = pages.stream() .mapToInt(Page::getPositionCount) .sum(); - assertEquals(positionCount, state.getTotalPositions()); + assertThat(positionCount).isEqualTo(state.getTotalPositions()); } @State(Thread) diff --git a/core/trino-main/src/test/java/io/trino/util/TestAutoCloseableCloser.java b/core/trino-main/src/test/java/io/trino/util/TestAutoCloseableCloser.java index 2a428def57cf..95ae48fd139c 100644 --- a/core/trino-main/src/test/java/io/trino/util/TestAutoCloseableCloser.java +++ b/core/trino-main/src/test/java/io/trino/util/TestAutoCloseableCloser.java @@ -16,9 +16,8 @@ import org.junit.jupiter.api.Test; import static com.google.common.base.Throwables.propagateIfPossible; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertSame; -import static org.testng.Assert.assertTrue; public class TestAutoCloseableCloser { @@ -59,11 +58,11 @@ public void testSuppressedException() assertThatThrownBy(closer::close) .isInstanceOfSatisfying(Exception.class, t -> { - assertSame(t, runtimeException); - assertSame(t.getSuppressed()[0], exception); - assertSame(t.getSuppressed()[1], exception); - assertSame(t.getSuppressed()[2], error); - assertSame(t.getSuppressed()[3], error); + assertThat(t).isSameAs(runtimeException); + assertThat(t.getSuppressed()[0]).isSameAs(exception); + assertThat(t.getSuppressed()[1]).isSameAs(exception); + assertThat(t.getSuppressed()[2]).isSameAs(error); + assertThat(t.getSuppressed()[3]).isSameAs(error); }); } @@ -79,7 +78,7 @@ private static void assertAllClosed(TestAutoCloseable... closeables) catch (Throwable ignored) { } for (TestAutoCloseable closeable : closeables) { - assertTrue(closeable.isClosed()); + assertThat(closeable.isClosed()).isTrue(); } } diff --git a/core/trino-main/src/test/java/io/trino/util/TestDateTimeUtils.java b/core/trino-main/src/test/java/io/trino/util/TestDateTimeUtils.java index edfc909f4146..3391ec335c74 100644 --- a/core/trino-main/src/test/java/io/trino/util/TestDateTimeUtils.java +++ b/core/trino-main/src/test/java/io/trino/util/TestDateTimeUtils.java @@ -20,7 +20,6 @@ import static io.trino.util.DateTimeUtils.parseIfIso8601DateFormat; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestDateTimeUtils { @@ -29,12 +28,24 @@ public class TestDateTimeUtils public void testParseIfIso8601DateFormat() { // valid dates - assertEquals(0, parseIfIso8601DateFormat("1970-01-01").getAsInt(), "1970-01-01"); - assertEquals(31, parseIfIso8601DateFormat("1970-02-01").getAsInt(), "1970-02-01"); - assertEquals(-31, parseIfIso8601DateFormat("1969-12-01").getAsInt(), "1969-12-01"); - assertEquals(19051, parseIfIso8601DateFormat("2022-02-28").getAsInt(), "2022-02-28"); - assertEquals(-719528, parseIfIso8601DateFormat("0000-01-01").getAsInt(), "0000-01-01"); - assertEquals(2932896, parseIfIso8601DateFormat("9999-12-31").getAsInt(), "9999-12-31"); + assertThat(0) + .describedAs("1970-01-01") + .isEqualTo(parseIfIso8601DateFormat("1970-01-01").getAsInt()); + assertThat(31) + .describedAs("1970-02-01") + .isEqualTo(parseIfIso8601DateFormat("1970-02-01").getAsInt()); + assertThat(-31) + .describedAs("1969-12-01") + .isEqualTo(parseIfIso8601DateFormat("1969-12-01").getAsInt()); + assertThat(19051) + .describedAs("2022-02-28") + .isEqualTo(parseIfIso8601DateFormat("2022-02-28").getAsInt()); + assertThat(-719528) + .describedAs("0000-01-01") + .isEqualTo(parseIfIso8601DateFormat("0000-01-01").getAsInt()); + assertThat(2932896) + .describedAs("9999-12-31") + .isEqualTo(parseIfIso8601DateFormat("9999-12-31").getAsInt()); // format invalid // invalid length diff --git a/core/trino-main/src/test/java/io/trino/util/TestDisjointSet.java b/core/trino-main/src/test/java/io/trino/util/TestDisjointSet.java index 91d78b97ffc6..2ab1d8167cc1 100644 --- a/core/trino-main/src/test/java/io/trino/util/TestDisjointSet.java +++ b/core/trino-main/src/test/java/io/trino/util/TestDisjointSet.java @@ -24,10 +24,7 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestDisjointSet { @@ -38,9 +35,9 @@ public void testInitial() // assert that every node is considered its own group for (int i = 0; i < 100; i++) { - assertEquals(disjoint.find(i).intValue(), i); + assertThat(disjoint.find(i).intValue()).isEqualTo(i); } - assertEquals(disjoint.getEquivalentClasses().size(), 100); + assertThat(disjoint.getEquivalentClasses().size()).isEqualTo(100); } @Test @@ -50,24 +47,25 @@ public void testMergeAllSequentially() // insert pair (i, i+1); assert all inserts are considered new for (int i = 0; i < 100; i++) { - assertTrue(disjoint.findAndUnion(i, i + 1)); + assertThat(disjoint.findAndUnion(i, i + 1)).isTrue(); if (i != 0) { - assertEquals(disjoint.find(i - 1), disjoint.find(i)); + assertThat(disjoint.find(i - 1)).isEqualTo(disjoint.find(i)); } if (i != 99) { - assertNotEquals(disjoint.find(i + 1), disjoint.find(i + 2)); + assertThat(disjoint.find(i + 1)) + .isNotEqualTo(disjoint.find(i + 2)); } } // assert every pair (i, j) is in the same set for (int i = 0; i <= 100; i++) { for (int j = 0; j <= 100; j++) { - assertEquals(disjoint.find(i), disjoint.find(j)); - assertFalse(disjoint.findAndUnion(i, j)); + assertThat(disjoint.find(i)).isEqualTo(disjoint.find(j)); + assertThat(disjoint.findAndUnion(i, j)).isFalse(); } } Collection> equivalentClasses = disjoint.getEquivalentClasses(); - assertEquals(equivalentClasses.size(), 1); - assertEquals(Iterables.getOnlyElement(equivalentClasses).size(), 101); + assertThat(equivalentClasses.size()).isEqualTo(1); + assertThat(Iterables.getOnlyElement(equivalentClasses).size()).isEqualTo(101); } @Test @@ -77,24 +75,25 @@ public void testMergeAllBackwardsSequentially() // insert pair (i, i+1); assert all inserts are considered new for (int i = 100; i > 0; i--) { - assertTrue(disjoint.findAndUnion(i, i - 1)); + assertThat(disjoint.findAndUnion(i, i - 1)).isTrue(); if (i != 100) { - assertEquals(disjoint.find(i + 1), disjoint.find(i)); + assertThat(disjoint.find(i + 1)).isEqualTo(disjoint.find(i)); } if (i != 1) { - assertNotEquals(disjoint.find(i - 1), disjoint.find(i - 2)); + assertThat(disjoint.find(i - 1)) + .isNotEqualTo(disjoint.find(i - 2)); } } // assert every pair (i, j) is in the same set for (int i = 0; i <= 100; i++) { for (int j = 0; j <= 100; j++) { - assertEquals(disjoint.find(i), disjoint.find(j)); - assertFalse(disjoint.findAndUnion(i, j)); + assertThat(disjoint.find(i)).isEqualTo(disjoint.find(j)); + assertThat(disjoint.findAndUnion(i, j)).isFalse(); } } Collection> equivalentClasses = disjoint.getEquivalentClasses(); - assertEquals(equivalentClasses.size(), 1); - assertEquals(Iterables.getOnlyElement(equivalentClasses).size(), 101); + assertThat(equivalentClasses.size()).isEqualTo(1); + assertThat(Iterables.getOnlyElement(equivalentClasses).size()).isEqualTo(101); } @Test @@ -106,23 +105,24 @@ public void testMergeFourGroups() List inputs = IntStream.range(0, 96).boxed().collect(Collectors.toList()); Collections.shuffle(inputs); for (int i : inputs) { - assertTrue(disjoint.findAndUnion(i, i + 4)); + assertThat(disjoint.findAndUnion(i, i + 4)).isTrue(); } // assert every pair (i, j) is in the same set for (int i = 0; i < 100; i++) { for (int j = 0; j < 100; j++) { if ((i - j) % 4 == 0) { - assertEquals(disjoint.find(i), disjoint.find(j)); - assertFalse(disjoint.findAndUnion(i, j)); + assertThat(disjoint.find(i)).isEqualTo(disjoint.find(j)); + assertThat(disjoint.findAndUnion(i, j)).isFalse(); } else { - assertNotEquals(disjoint.find(i), disjoint.find(j)); + assertThat(disjoint.find(i)) + .isNotEqualTo(disjoint.find(j)); } } } Collection> equivalentClasses = disjoint.getEquivalentClasses(); - assertEquals(equivalentClasses.size(), 4); - equivalentClasses.forEach(equivalentClass -> assertEquals(equivalentClass.size(), 25)); + assertThat(equivalentClasses.size()).isEqualTo(4); + equivalentClasses.forEach(equivalentClass -> assertThat(equivalentClass.size()).isEqualTo(25)); } @Test @@ -145,7 +145,7 @@ public void testMergeRandomly() if (newEquivalence) { groupCount--; } - assertEquals(disjoint.getEquivalentClasses().size(), groupCount); + assertThat(disjoint.getEquivalentClasses().size()).isEqualTo(groupCount); } } } diff --git a/core/trino-main/src/test/java/io/trino/util/TestFailures.java b/core/trino-main/src/test/java/io/trino/util/TestFailures.java index 41058a43b53c..0fb2898fdee8 100644 --- a/core/trino-main/src/test/java/io/trino/util/TestFailures.java +++ b/core/trino-main/src/test/java/io/trino/util/TestFailures.java @@ -20,9 +20,7 @@ import static io.trino.spi.StandardErrorCode.GENERIC_INTERNAL_ERROR; import static io.trino.spi.StandardErrorCode.TOO_MANY_REQUESTS_FAILED; import static io.trino.util.Failures.toFailure; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; +import static org.assertj.core.api.Assertions.assertThat; public class TestFailures { @@ -35,19 +33,19 @@ public void testToFailureLoop() // add exception 1 --> add suppress (exception 2) --> add cause (exception 1) ExecutionFailureInfo failure = toFailure(exception1); - assertEquals(failure.getMessage(), "fake exception 1"); - assertNull(failure.getCause()); - assertEquals(failure.getSuppressed().size(), 1); - assertEquals(failure.getSuppressed().get(0).getMessage(), "fake exception 2"); - assertEquals(failure.getErrorCode(), TOO_MANY_REQUESTS_FAILED.toErrorCode()); + assertThat(failure.getMessage()).isEqualTo("fake exception 1"); + assertThat(failure.getCause()).isNull(); + assertThat(failure.getSuppressed().size()).isEqualTo(1); + assertThat(failure.getSuppressed().get(0).getMessage()).isEqualTo("fake exception 2"); + assertThat(failure.getErrorCode()).isEqualTo(TOO_MANY_REQUESTS_FAILED.toErrorCode()); // add exception 2 --> add cause (exception 2) --> add suppress (exception 1) failure = toFailure(exception2); - assertEquals(failure.getMessage(), "fake exception 2"); - assertNotNull(failure.getCause()); - assertEquals(failure.getCause().getMessage(), "fake exception 1"); - assertEquals(failure.getSuppressed().size(), 0); - assertEquals(failure.getErrorCode(), TOO_MANY_REQUESTS_FAILED.toErrorCode()); + assertThat(failure.getMessage()).isEqualTo("fake exception 2"); + assertThat(failure.getCause()).isNotNull(); + assertThat(failure.getCause().getMessage()).isEqualTo("fake exception 1"); + assertThat(failure.getSuppressed().size()).isEqualTo(0); + assertThat(failure.getErrorCode()).isEqualTo(TOO_MANY_REQUESTS_FAILED.toErrorCode()); // add exception 1 --> add suppress (exception 2) --> add suppress (exception 1) exception1 = new TrinoException(TOO_MANY_REQUESTS_FAILED, "fake exception 1"); @@ -55,21 +53,21 @@ public void testToFailureLoop() exception1.addSuppressed(exception2); exception2.addSuppressed(exception1); failure = toFailure(exception1); - assertEquals(failure.getMessage(), "fake exception 1"); - assertNull(failure.getCause()); - assertEquals(failure.getSuppressed().size(), 1); - assertEquals(failure.getSuppressed().get(0).getMessage(), "fake exception 2"); - assertEquals(failure.getErrorCode(), TOO_MANY_REQUESTS_FAILED.toErrorCode()); + assertThat(failure.getMessage()).isEqualTo("fake exception 1"); + assertThat(failure.getCause()).isNull(); + assertThat(failure.getSuppressed().size()).isEqualTo(1); + assertThat(failure.getSuppressed().get(0).getMessage()).isEqualTo("fake exception 2"); + assertThat(failure.getErrorCode()).isEqualTo(TOO_MANY_REQUESTS_FAILED.toErrorCode()); // add exception 2 --> add cause (exception 1) --> add cause (exception 2) exception1 = new RuntimeException("fake exception 1"); exception2 = new RuntimeException("fake exception 2", exception1); exception1.initCause(exception2); failure = toFailure(exception2); - assertEquals(failure.getMessage(), "fake exception 2"); - assertNotNull(failure.getCause()); - assertEquals(failure.getCause().getMessage(), "fake exception 1"); - assertEquals(failure.getSuppressed().size(), 0); - assertEquals(failure.getErrorCode(), GENERIC_INTERNAL_ERROR.toErrorCode()); + assertThat(failure.getMessage()).isEqualTo("fake exception 2"); + assertThat(failure.getCause()).isNotNull(); + assertThat(failure.getCause().getMessage()).isEqualTo("fake exception 1"); + assertThat(failure.getSuppressed().size()).isEqualTo(0); + assertThat(failure.getErrorCode()).isEqualTo(GENERIC_INTERNAL_ERROR.toErrorCode()); } } diff --git a/core/trino-main/src/test/java/io/trino/util/TestHeapTraversal.java b/core/trino-main/src/test/java/io/trino/util/TestHeapTraversal.java index a2833d9bac9a..6d4faefaf90c 100644 --- a/core/trino-main/src/test/java/io/trino/util/TestHeapTraversal.java +++ b/core/trino-main/src/test/java/io/trino/util/TestHeapTraversal.java @@ -15,9 +15,7 @@ import org.junit.jupiter.api.Test; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestHeapTraversal { @@ -36,37 +34,37 @@ public void testTraversal() */ heapTraversal.resetWithPathTo(1); - assertTrue(heapTraversal.isTarget()); + assertThat(heapTraversal.isTarget()).isTrue(); heapTraversal.resetWithPathTo(2); - assertFalse(heapTraversal.isTarget()); - assertEquals(heapTraversal.nextChild(), HeapTraversal.Child.LEFT); - assertTrue(heapTraversal.isTarget()); + assertThat(heapTraversal.isTarget()).isFalse(); + assertThat(heapTraversal.nextChild()).isEqualTo(HeapTraversal.Child.LEFT); + assertThat(heapTraversal.isTarget()).isTrue(); heapTraversal.resetWithPathTo(3); - assertFalse(heapTraversal.isTarget()); - assertEquals(heapTraversal.nextChild(), HeapTraversal.Child.RIGHT); - assertTrue(heapTraversal.isTarget()); + assertThat(heapTraversal.isTarget()).isFalse(); + assertThat(heapTraversal.nextChild()).isEqualTo(HeapTraversal.Child.RIGHT); + assertThat(heapTraversal.isTarget()).isTrue(); heapTraversal.resetWithPathTo(4); - assertFalse(heapTraversal.isTarget()); - assertEquals(heapTraversal.nextChild(), HeapTraversal.Child.LEFT); - assertFalse(heapTraversal.isTarget()); - assertEquals(heapTraversal.nextChild(), HeapTraversal.Child.LEFT); - assertTrue(heapTraversal.isTarget()); + assertThat(heapTraversal.isTarget()).isFalse(); + assertThat(heapTraversal.nextChild()).isEqualTo(HeapTraversal.Child.LEFT); + assertThat(heapTraversal.isTarget()).isFalse(); + assertThat(heapTraversal.nextChild()).isEqualTo(HeapTraversal.Child.LEFT); + assertThat(heapTraversal.isTarget()).isTrue(); heapTraversal.resetWithPathTo(5); - assertFalse(heapTraversal.isTarget()); - assertEquals(heapTraversal.nextChild(), HeapTraversal.Child.LEFT); - assertFalse(heapTraversal.isTarget()); - assertEquals(heapTraversal.nextChild(), HeapTraversal.Child.RIGHT); - assertTrue(heapTraversal.isTarget()); + assertThat(heapTraversal.isTarget()).isFalse(); + assertThat(heapTraversal.nextChild()).isEqualTo(HeapTraversal.Child.LEFT); + assertThat(heapTraversal.isTarget()).isFalse(); + assertThat(heapTraversal.nextChild()).isEqualTo(HeapTraversal.Child.RIGHT); + assertThat(heapTraversal.isTarget()).isTrue(); heapTraversal.resetWithPathTo(6); - assertFalse(heapTraversal.isTarget()); - assertEquals(heapTraversal.nextChild(), HeapTraversal.Child.RIGHT); - assertFalse(heapTraversal.isTarget()); - assertEquals(heapTraversal.nextChild(), HeapTraversal.Child.LEFT); - assertTrue(heapTraversal.isTarget()); + assertThat(heapTraversal.isTarget()).isFalse(); + assertThat(heapTraversal.nextChild()).isEqualTo(HeapTraversal.Child.RIGHT); + assertThat(heapTraversal.isTarget()).isFalse(); + assertThat(heapTraversal.nextChild()).isEqualTo(HeapTraversal.Child.LEFT); + assertThat(heapTraversal.isTarget()).isTrue(); } } diff --git a/core/trino-main/src/test/java/io/trino/util/TestLong2LongOpenBigHashMap.java b/core/trino-main/src/test/java/io/trino/util/TestLong2LongOpenBigHashMap.java index bc246d6633de..7a38e5c90f1e 100644 --- a/core/trino-main/src/test/java/io/trino/util/TestLong2LongOpenBigHashMap.java +++ b/core/trino-main/src/test/java/io/trino/util/TestLong2LongOpenBigHashMap.java @@ -18,9 +18,7 @@ import java.util.Arrays; import java.util.List; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestLong2LongOpenBigHashMap { @@ -31,10 +29,10 @@ public void testBasicOps() Long2LongOpenBigHashMap map = new Long2LongOpenBigHashMap(expected); map.defaultReturnValue(-1); - assertTrue(map.isEmpty()); - assertEquals(map.size(), 0); - assertEquals(map.get(0), -1); - assertEquals(map.get(1), -1); + assertThat(map.isEmpty()).isTrue(); + assertThat(map.size()).isEqualTo(0); + assertThat(map.get(0)).isEqualTo(-1); + assertThat(map.get(1)).isEqualTo(-1); List values = Arrays.asList(Long.MIN_VALUE, -10L, 0L, 10L, Long.MAX_VALUE); @@ -42,34 +40,34 @@ public void testBasicOps() int count = 0; for (long key : values) { count++; - assertEquals(map.put(key, count - 1), -1); - assertFalse(map.isEmpty()); - assertEquals(map.size(), count); + assertThat(map.put(key, count - 1)).isEqualTo(-1); + assertThat(map.isEmpty()).isFalse(); + assertThat(map.size()).isEqualTo(count); } // Replace count = 0; for (long key : values) { count++; - assertTrue(map.replace(key, count - 1, count)); - assertFalse(map.isEmpty()); - assertEquals(map.size(), values.size()); + assertThat(map.replace(key, count - 1, count)).isTrue(); + assertThat(map.isEmpty()).isFalse(); + assertThat(map.size()).isEqualTo(values.size()); } // Get count = 0; for (long key : values) { count++; - assertTrue(map.containsKey(key)); - assertTrue(map.containsValue(count)); - assertEquals(map.get(key), count); + assertThat(map.containsKey(key)).isTrue(); + assertThat(map.containsValue(count)).isTrue(); + assertThat(map.get(key)).isEqualTo(count); } // Remove count = 0; for (long key : values) { count++; - assertEquals(map.remove(key), count); + assertThat(map.remove(key)).isEqualTo(count); } } @@ -83,11 +81,11 @@ public void testRehash() // Inserting 1M elements should be enough to trigger some rehashes given an initial capacity of 1. for (long key = 0; key < 1_000_000; key++) { - assertEquals(map.put(key, key + 1), -1); + assertThat(map.put(key, key + 1)).isEqualTo(-1); } for (long key = 0; key < 1_000_000; key++) { - assertEquals(map.get(key), key + 1); + assertThat(map.get(key)).isEqualTo(key + 1); } // Remove most of the elements and force a trim() @@ -97,6 +95,6 @@ public void testRehash() map.trim(); // Make sure we can still fetch the remaining key - assertEquals(map.get(0), 1); + assertThat(map.get(0)).isEqualTo(1); } } diff --git a/core/trino-main/src/test/java/io/trino/util/TestLongBigArrayFIFOQueue.java b/core/trino-main/src/test/java/io/trino/util/TestLongBigArrayFIFOQueue.java index 277cae70cfd5..4cb1b21b782f 100644 --- a/core/trino-main/src/test/java/io/trino/util/TestLongBigArrayFIFOQueue.java +++ b/core/trino-main/src/test/java/io/trino/util/TestLongBigArrayFIFOQueue.java @@ -15,9 +15,7 @@ import org.junit.jupiter.api.Test; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestLongBigArrayFIFOQueue { @@ -25,64 +23,64 @@ public class TestLongBigArrayFIFOQueue public void testEnqueueDequeue() { LongBigArrayFIFOQueue queue = new LongBigArrayFIFOQueue(); - assertTrue(queue.isEmpty()); - assertEquals(queue.size(), 0); - assertEquals(queue.longSize(), 0L); + assertThat(queue.isEmpty()).isTrue(); + assertThat(queue.size()).isEqualTo(0); + assertThat(queue.longSize()).isEqualTo(0L); // Enqueue elements for (long i = 1; i <= 100; i++) { queue.enqueue(i); - assertFalse(queue.isEmpty()); - assertEquals(queue.lastLong(), i); - assertEquals(queue.size(), i); - assertEquals(queue.longSize(), i); + assertThat(queue.isEmpty()).isFalse(); + assertThat(queue.lastLong()).isEqualTo(i); + assertThat(queue.size()).isEqualTo(i); + assertThat(queue.longSize()).isEqualTo(i); } - assertEquals(queue.firstLong(), 1); - assertEquals(queue.lastLong(), 100); + assertThat(queue.firstLong()).isEqualTo(1); + assertThat(queue.lastLong()).isEqualTo(100); // Dequeue elements for (long i = 1; i <= 100; i++) { - assertEquals(queue.size(), 100 - i + 1); - assertEquals(queue.longSize(), 100 - i + 1); - assertEquals(queue.dequeueLong(), i); + assertThat(queue.size()).isEqualTo(100 - i + 1); + assertThat(queue.longSize()).isEqualTo(100 - i + 1); + assertThat(queue.dequeueLong()).isEqualTo(i); } - assertTrue(queue.isEmpty()); - assertEquals(queue.size(), 0); - assertEquals(queue.longSize(), 0L); + assertThat(queue.isEmpty()).isTrue(); + assertThat(queue.size()).isEqualTo(0); + assertThat(queue.longSize()).isEqualTo(0L); } @Test public void testReverseEnqueueDequeue() { LongBigArrayFIFOQueue queue = new LongBigArrayFIFOQueue(); - assertTrue(queue.isEmpty()); - assertEquals(queue.size(), 0); - assertEquals(queue.longSize(), 0L); + assertThat(queue.isEmpty()).isTrue(); + assertThat(queue.size()).isEqualTo(0); + assertThat(queue.longSize()).isEqualTo(0L); // Enqueue elements for (long i = 1; i <= 100; i++) { queue.enqueueFirst(i); - assertFalse(queue.isEmpty()); - assertEquals(queue.firstLong(), i); - assertEquals(queue.size(), i); - assertEquals(queue.longSize(), i); + assertThat(queue.isEmpty()).isFalse(); + assertThat(queue.firstLong()).isEqualTo(i); + assertThat(queue.size()).isEqualTo(i); + assertThat(queue.longSize()).isEqualTo(i); } - assertEquals(queue.firstLong(), 100); - assertEquals(queue.lastLong(), 1); + assertThat(queue.firstLong()).isEqualTo(100); + assertThat(queue.lastLong()).isEqualTo(1); // Dequeue elements for (long i = 1; i <= 100; i++) { - assertEquals(queue.size(), 100 - i + 1); - assertEquals(queue.longSize(), 100 - i + 1); - assertEquals(queue.dequeueLastLong(), i); + assertThat(queue.size()).isEqualTo(100 - i + 1); + assertThat(queue.longSize()).isEqualTo(100 - i + 1); + assertThat(queue.dequeueLastLong()).isEqualTo(i); } - assertTrue(queue.isEmpty()); - assertEquals(queue.size(), 0); - assertEquals(queue.longSize(), 0L); + assertThat(queue.isEmpty()).isTrue(); + assertThat(queue.size()).isEqualTo(0); + assertThat(queue.longSize()).isEqualTo(0L); } @Test @@ -97,14 +95,14 @@ public void testResize() queue.enqueue(i); } for (long i = 0; i < 1_000_000; i++) { - assertEquals(queue.dequeueLong(), i); + assertThat(queue.dequeueLong()).isEqualTo(i); } queue.trim(); for (long i = 0; i < 1_000_000; i++) { queue.enqueue(i); } for (long i = 0; i < 1_000_000; i++) { - assertEquals(queue.dequeueLong(), i); + assertThat(queue.dequeueLong()).isEqualTo(i); } } } diff --git a/core/trino-main/src/test/java/io/trino/util/TestLongLong2LongOpenCustomBigHashMap.java b/core/trino-main/src/test/java/io/trino/util/TestLongLong2LongOpenCustomBigHashMap.java index f5d3884b8086..e51ccfa23b87 100644 --- a/core/trino-main/src/test/java/io/trino/util/TestLongLong2LongOpenCustomBigHashMap.java +++ b/core/trino-main/src/test/java/io/trino/util/TestLongLong2LongOpenCustomBigHashMap.java @@ -19,9 +19,7 @@ import java.util.Arrays; import java.util.List; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestLongLong2LongOpenCustomBigHashMap { @@ -53,10 +51,10 @@ public void testBasicOps(long nullKey1, long nullKey2) LongLong2LongOpenCustomBigHashMap map = new LongLong2LongOpenCustomBigHashMap(expected, DEFAULT_STRATEGY, nullKey1, nullKey2); map.defaultReturnValue(-1); - assertTrue(map.isEmpty()); - assertEquals(map.size(), 0); - assertEquals(map.get(0, 0), -1); - assertEquals(map.get(1, -1), -1); + assertThat(map.isEmpty()).isTrue(); + assertThat(map.size()).isEqualTo(0); + assertThat(map.get(0, 0)).isEqualTo(-1); + assertThat(map.get(1, -1)).isEqualTo(-1); List values = Arrays.asList(Long.MIN_VALUE, -10L, 0L, 10L, Long.MAX_VALUE); @@ -65,9 +63,9 @@ public void testBasicOps(long nullKey1, long nullKey2) for (long key1 : values) { for (long key2 : values) { count++; - assertEquals(map.put(key1, key2, count - 1), -1); - assertFalse(map.isEmpty()); - assertEquals(map.size(), count); + assertThat(map.put(key1, key2, count - 1)).isEqualTo(-1); + assertThat(map.isEmpty()).isFalse(); + assertThat(map.size()).isEqualTo(count); } } @@ -76,9 +74,9 @@ public void testBasicOps(long nullKey1, long nullKey2) for (long key1 : values) { for (long key2 : values) { count++; - assertTrue(map.replace(key1, key2, count - 1, count)); - assertFalse(map.isEmpty()); - assertEquals(map.size(), (long) values.size() * values.size()); + assertThat(map.replace(key1, key2, count - 1, count)).isTrue(); + assertThat(map.isEmpty()).isFalse(); + assertThat(map.size()).isEqualTo((long) values.size() * values.size()); } } @@ -87,9 +85,9 @@ public void testBasicOps(long nullKey1, long nullKey2) for (long key1 : values) { for (long key2 : values) { count++; - assertTrue(map.containsKey(key1, key2)); - assertTrue(map.containsValue(count)); - assertEquals(map.get(key1, key2), count); + assertThat(map.containsKey(key1, key2)).isTrue(); + assertThat(map.containsValue(count)).isTrue(); + assertThat(map.get(key1, key2)).isEqualTo(count); } } @@ -98,7 +96,7 @@ public void testBasicOps(long nullKey1, long nullKey2) for (long key1 : values) { for (long key2 : values) { count++; - assertEquals(map.remove(key1, key2), count); + assertThat(map.remove(key1, key2)).isEqualTo(count); } } } @@ -132,9 +130,9 @@ public boolean equals(long a1, long a2, long b1, long b2) for (long key1 : values) { for (long key2 : values) { count++; - assertEquals(map.put(key1, key2, count - 1), -1); - assertFalse(map.isEmpty()); - assertEquals(map.size(), count); + assertThat(map.put(key1, key2, count - 1)).isEqualTo(-1); + assertThat(map.isEmpty()).isFalse(); + assertThat(map.size()).isEqualTo(count); } } @@ -143,9 +141,9 @@ public boolean equals(long a1, long a2, long b1, long b2) for (long key1 : values) { for (long key2 : values) { count++; - assertTrue(map.replace(key1, key2, count - 1, count)); - assertFalse(map.isEmpty()); - assertEquals(map.size(), (long) values.size() * values.size()); + assertThat(map.replace(key1, key2, count - 1, count)).isTrue(); + assertThat(map.isEmpty()).isFalse(); + assertThat(map.size()).isEqualTo((long) values.size() * values.size()); } } @@ -154,9 +152,9 @@ public boolean equals(long a1, long a2, long b1, long b2) for (long key1 : values) { for (long key2 : values) { count++; - assertTrue(map.containsKey(key1, key2)); - assertTrue(map.containsValue(count)); - assertEquals(map.get(key1, key2), count); + assertThat(map.containsKey(key1, key2)).isTrue(); + assertThat(map.containsValue(count)).isTrue(); + assertThat(map.get(key1, key2)).isEqualTo(count); } } @@ -165,7 +163,7 @@ public boolean equals(long a1, long a2, long b1, long b2) for (long key1 : values) { for (long key2 : values) { count++; - assertEquals(map.remove(key1, key2), count); + assertThat(map.remove(key1, key2)).isEqualTo(count); } } } @@ -183,7 +181,7 @@ public void testRehash(long nullKey1, long nullKey2) for (long key1 = 0; key1 < 1000; key1++) { for (long key2 = 0; key2 < 1000; key2++) { count++; - assertEquals(map.put(key1, key2, count), -1); + assertThat(map.put(key1, key2, count)).isEqualTo(-1); } } @@ -191,7 +189,7 @@ public void testRehash(long nullKey1, long nullKey2) for (long key1 = 0; key1 < 1000; key1++) { for (long key2 = 0; key2 < 1000; key2++) { count++; - assertEquals(map.get(key1, key2), count); + assertThat(map.get(key1, key2)).isEqualTo(count); } } @@ -207,7 +205,7 @@ public void testRehash(long nullKey1, long nullKey2) count = 0; for (long key2 = 0; key2 < 1000; key2++) { count++; - assertEquals(map.get(0, key2), count); + assertThat(map.get(0, key2)).isEqualTo(count); } } } diff --git a/core/trino-main/src/test/java/io/trino/util/TestMergeSortedPages.java b/core/trino-main/src/test/java/io/trino/util/TestMergeSortedPages.java index 9b8979f8806f..08eacaaf60a6 100644 --- a/core/trino-main/src/test/java/io/trino/util/TestMergeSortedPages.java +++ b/core/trino-main/src/test/java/io/trino/util/TestMergeSortedPages.java @@ -42,9 +42,6 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.testing.MaterializedResult.resultBuilder; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestMergeSortedPages { @@ -335,12 +332,12 @@ public void testSortingYields() yieldSignal); // yield signal is on - assertFalse(mergedPages.process()); + assertThat(mergedPages.process()).isFalse(); yieldSignal.resetYieldForTesting(); // page is produced - assertTrue(mergedPages.process()); - assertFalse(mergedPages.isFinished()); + assertThat(mergedPages.process()).isTrue(); + assertThat(mergedPages.isFinished()).isFalse(); Page page = mergedPages.getResult(); assertThat(toMaterializedResult(TEST_SESSION, types, ImmutableList.of(page))) @@ -349,8 +346,8 @@ public void testSortingYields() .build()); // merge source finished - assertTrue(mergedPages.process()); - assertTrue(mergedPages.isFinished()); + assertThat(mergedPages.process()).isTrue(); + assertThat(mergedPages.isFinished()).isTrue(); } @Test @@ -369,10 +366,10 @@ public void testMergeSortYieldingProgresses() newSimpleAggregatedMemoryContext().newAggregatedMemoryContext(), yieldSignal); // yield signal is on - assertFalse(mergedPages.process()); + assertThat(mergedPages.process()).isFalse(); // processor finishes computations (yield signal is still on, but previous process() call yielded) - assertTrue(mergedPages.process()); - assertTrue(mergedPages.isFinished()); + assertThat(mergedPages.process()).isTrue(); + assertThat(mergedPages.isFinished()).isTrue(); } private static MaterializedResult mergeSortedPages( @@ -394,16 +391,16 @@ private static MaterializedResult mergeSortedPages( memoryContext, new DriverYieldSignal()); - assertTrue(mergedPages.process()); + assertThat(mergedPages.process()).isTrue(); if (mergedPages.isFinished()) { return toMaterializedResult(TEST_SESSION, types, ImmutableList.of()); } Page page = mergedPages.getResult(); - assertTrue(mergedPages.process()); - assertTrue(mergedPages.isFinished()); - assertEquals(memoryContext.getBytes(), 0L); + assertThat(mergedPages.process()).isTrue(); + assertThat(mergedPages.isFinished()).isTrue(); + assertThat(memoryContext.getBytes()).isEqualTo(0L); return toMaterializedResult(TEST_SESSION, types, ImmutableList.of(page)); } diff --git a/core/trino-main/src/test/java/io/trino/util/TestTimeZoneUtils.java b/core/trino-main/src/test/java/io/trino/util/TestTimeZoneUtils.java index b10c231d2351..260e5f053b1d 100644 --- a/core/trino-main/src/test/java/io/trino/util/TestTimeZoneUtils.java +++ b/core/trino-main/src/test/java/io/trino/util/TestTimeZoneUtils.java @@ -29,8 +29,8 @@ import static io.trino.util.DateTimeZoneIndex.packDateTimeWithZone; import static io.trino.util.DateTimeZoneIndex.unpackDateTimeZone; import static java.lang.String.format; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Fail.fail; public class TestTimeZoneUtils { @@ -92,6 +92,6 @@ public static void assertDateTimeZoneEquals(String zoneId, DateTimeZone actualTi expectedDateTimeZone = DateTimeZone.forID(zoneId); } - assertEquals(actualTimeZone, expectedDateTimeZone); + assertThat(actualTimeZone).isEqualTo(expectedDateTimeZone); } } diff --git a/core/trino-main/src/test/java/io/trino/version/TestEmbedVersion.java b/core/trino-main/src/test/java/io/trino/version/TestEmbedVersion.java index 78d3ba20446e..678ccd9faf6d 100644 --- a/core/trino-main/src/test/java/io/trino/version/TestEmbedVersion.java +++ b/core/trino-main/src/test/java/io/trino/version/TestEmbedVersion.java @@ -19,8 +19,8 @@ import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicInteger; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestEmbedVersion { @@ -31,7 +31,7 @@ public void testEmbedVersionInRunnable() { AtomicInteger counter = new AtomicInteger(); embedVersion.embedVersion((Runnable) counter::incrementAndGet).run(); - assertEquals(counter.get(), 1); + assertThat(counter.get()).isEqualTo(1); assertThatThrownBy(() -> embedVersion.embedVersion((Runnable) () -> { @@ -50,8 +50,8 @@ public void testEmbedVersionInCallable() String value = embedVersion.embedVersion(() -> { return "abc" + counter.incrementAndGet(); }).call(); - assertEquals(value, "abc1"); - assertEquals(counter.get(), 1); + assertThat(value).isEqualTo("abc1"); + assertThat(counter.get()).isEqualTo(1); assertThatThrownBy(() -> embedVersion.embedVersion((Callable) () -> { diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFSDataInputStreamTail.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFSDataInputStreamTail.java index 73a7b8832ede..a95aca9882a3 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFSDataInputStreamTail.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFSDataInputStreamTail.java @@ -34,12 +34,10 @@ import java.util.Arrays; import static io.airlift.testing.Closeables.closeAll; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertSame; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(SAME_THREAD) // e.g. test methods operate on shared, mutated tempFile @@ -83,9 +81,9 @@ public void testEmptyFileReadTail() { try (FSDataInputStream is = fs.open(tempFile)) { FSDataInputStreamTail tail = FSDataInputStreamTail.readTail(tempFile.toString(), FSDataInputStreamTail.MAX_SUPPORTED_PADDING_BYTES, is, FSDataInputStreamTail.MAX_SUPPORTED_PADDING_BYTES); - assertEquals(tail.getFileSize(), 0); - assertEquals(tail.getTailSlice().length(), 0); - assertSame(tail.getTailSlice(), Slices.EMPTY_SLICE); + assertThat(tail.getFileSize()).isEqualTo(0); + assertThat(tail.getTailSlice().length()).isEqualTo(0); + assertThat(tail.getTailSlice()).isSameAs(Slices.EMPTY_SLICE); } } @@ -116,10 +114,10 @@ private void testReadTailForFileSize(int fileSize, int paddedFileSize) } } - assertEquals(fs.getFileLinkStatus(tempFile).getLen(), fileSize); + assertThat(fs.getFileLinkStatus(tempFile).getLen()).isEqualTo(fileSize); try (FSDataInputStream is = fs.open(tempFile)) { - assertEquals(FSDataInputStreamTail.readTailForFileSize(tempFile.toString(), paddedFileSize, is), fileSize); + assertThat(FSDataInputStreamTail.readTailForFileSize(tempFile.toString(), paddedFileSize, is)).isEqualTo(fileSize); } } @@ -150,15 +148,15 @@ private void testReadTailCompletely(int fileSize, int paddedFileSize) } } - assertEquals(fs.getFileLinkStatus(tempFile).getLen(), fileSize); + assertThat(fs.getFileLinkStatus(tempFile).getLen()).isEqualTo(fileSize); try (FSDataInputStream is = fs.open(tempFile)) { FSDataInputStreamTail tail = FSDataInputStreamTail.readTail(tempFile.toString(), paddedFileSize, is, fileSize); - assertEquals(tail.getFileSize(), fileSize); + assertThat(tail.getFileSize()).isEqualTo(fileSize); Slice tailSlice = tail.getTailSlice(); - assertEquals(tailSlice.length(), fileSize); + assertThat(tailSlice.length()).isEqualTo(fileSize); byte[] tailContents = tailSlice.getBytes(); - assertEquals(tailContents, countingTestFileContentsWithLength(tailContents.length)); + assertThat(tailContents).isEqualTo(countingTestFileContentsWithLength(tailContents.length)); } } @@ -171,15 +169,15 @@ public void testReadTailPartial() os.write(contents); } - assertEquals(fs.getFileLinkStatus(tempFile).getLen(), contents.length); + assertThat(fs.getFileLinkStatus(tempFile).getLen()).isEqualTo(contents.length); try (FSDataInputStream is = fs.open(tempFile)) { FSDataInputStreamTail tail = FSDataInputStreamTail.readTail(tempFile.toString(), contents.length + 32, is, 16); - assertEquals(tail.getFileSize(), contents.length); + assertThat(tail.getFileSize()).isEqualTo(contents.length); Slice tailSlice = tail.getTailSlice(); - assertTrue(tailSlice.length() < contents.length); - assertEquals(tailSlice.getBytes(), Arrays.copyOfRange(contents, contents.length - tailSlice.length(), contents.length)); + assertThat(tailSlice.length() < contents.length).isTrue(); + assertThat(tailSlice.getBytes()).isEqualTo(Arrays.copyOfRange(contents, contents.length - tailSlice.length(), contents.length)); } } @@ -192,7 +190,7 @@ public void testReadTailNoEndOfFileFound() os.write(contents); } - assertEquals(fs.getFileLinkStatus(tempFile).getLen(), contents.length); + assertThat(fs.getFileLinkStatus(tempFile).getLen()).isEqualTo(contents.length); try (FSDataInputStream is = fs.open(tempFile)) { assertThatThrownBy(() -> FSDataInputStreamTail.readTail(tempFile.toString(), 128, is, 16)) @@ -210,7 +208,7 @@ public void testReadTailForFileSizeNoEndOfFileFound() os.write(contents); } - assertEquals(fs.getFileLinkStatus(tempFile).getLen(), contents.length); + assertThat(fs.getFileLinkStatus(tempFile).getLen()).isEqualTo(contents.length); try (FSDataInputStream is = fs.open(tempFile)) { assertThatThrownBy(() -> FSDataInputStreamTail.readTailForFileSize(tempFile.toString(), 128, is)) diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFileSystemCache.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFileSystemCache.java index 83c4496735ce..8ef40d3df41d 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFileSystemCache.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestFileSystemCache.java @@ -39,12 +39,10 @@ import static io.trino.plugin.base.security.UserNameProvider.SIMPLE_USER_NAME_PROVIDER; import static java.util.Objects.requireNonNull; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotSame; -import static org.testng.Assert.assertSame; @TestInstance(PER_CLASS) @Execution(SAME_THREAD) @@ -70,18 +68,18 @@ public void testFileSystemCache() ConnectorIdentity otherUserId = ConnectorIdentity.ofUser("other_user"); FileSystem fs1 = getFileSystem(environment, userId); FileSystem fs2 = getFileSystem(environment, userId); - assertSame(fs1, fs2); + assertThat(fs1).isSameAs(fs2); FileSystem fs3 = getFileSystem(environment, otherUserId); - assertNotSame(fs1, fs3); + assertThat(fs1).isNotSameAs(fs3); FileSystem fs4 = getFileSystem(environment, otherUserId); - assertSame(fs3, fs4); + assertThat(fs3).isSameAs(fs4); FileSystem.closeAll(); FileSystem fs5 = getFileSystem(environment, userId); - assertNotSame(fs5, fs1); + assertThat(fs5).isNotSameAs(fs1); } @Test @@ -95,10 +93,10 @@ public void testFileSystemCacheException() int maxCacheSize = 1000; for (int i = 0; i < maxCacheSize; i++) { - assertEquals(TrinoFileSystemCacheStats.instance().getCacheSize(), i); + assertThat(TrinoFileSystemCacheStats.instance().getCacheSize()).isEqualTo(i); getFileSystem(environment, ConnectorIdentity.ofUser("user" + i)); } - assertEquals(TrinoFileSystemCacheStats.instance().getCacheSize(), maxCacheSize); + assertThat(TrinoFileSystemCacheStats.instance().getCacheSize()).isEqualTo(maxCacheSize); assertThatThrownBy(() -> getFileSystem(environment, ConnectorIdentity.ofUser("user" + maxCacheSize))) .isInstanceOf(IOException.class) .hasMessage("FileSystem max cache size has been reached: " + maxCacheSize); @@ -120,10 +118,12 @@ public void testFileSystemCacheConcurrency() } ExecutorService executor = Executors.newFixedThreadPool(numThreads); - assertEquals(TrinoFileSystemCacheStats.instance().getCacheSize(), 0); + assertThat(TrinoFileSystemCacheStats.instance().getCacheSize()).isEqualTo(0); executor.invokeAll(callableTasks).forEach(MoreFutures::getFutureValue); executor.shutdown(); - assertEquals(TrinoFileSystemCacheStats.instance().getCacheSize(), 0, "Cache size is non zero"); + assertThat(TrinoFileSystemCacheStats.instance().getCacheSize()) + .describedAs("Cache size is non zero") + .isEqualTo(0); } private static FileSystem getFileSystem(HdfsEnvironment environment, ConnectorIdentity identity) diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestHdfsConfig.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestHdfsConfig.java index b341c7a503c9..55459f9f900a 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestHdfsConfig.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestHdfsConfig.java @@ -29,7 +29,7 @@ import static io.airlift.configuration.testing.ConfigAssertions.assertFullMapping; import static io.airlift.configuration.testing.ConfigAssertions.assertRecordedDefaults; import static io.airlift.configuration.testing.ConfigAssertions.recordDefaults; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestHdfsConfig { @@ -104,7 +104,7 @@ public void testNewDirectoryPermissionsMapping() HdfsConfig expected = new HdfsConfig() .setNewDirectoryPermissions("skip"); - assertEquals(properties.get("hive.fs.new-directory-permissions"), expected.getNewDirectoryPermissions()); - assertEquals(Optional.empty(), expected.getNewDirectoryFsPermissions()); + assertThat(properties.get("hive.fs.new-directory-permissions")).isEqualTo(expected.getNewDirectoryPermissions()); + assertThat(Optional.empty()).isEqualTo(expected.getNewDirectoryFsPermissions()); } } diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestTrinoFileSystemCacheStats.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestTrinoFileSystemCacheStats.java index 95a5b307cf88..3e8b0f874d29 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestTrinoFileSystemCacheStats.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestTrinoFileSystemCacheStats.java @@ -19,8 +19,8 @@ import java.net.URI; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestTrinoFileSystemCacheStats { @@ -30,35 +30,35 @@ public void testCacheSizeIsCorrect() { TrinoFileSystemCache trinoFileSystemCache = new TrinoFileSystemCache(); TrinoFileSystemCacheStats trinoFileSystemCacheStats = trinoFileSystemCache.getStats(); - assertEquals(trinoFileSystemCacheStats.getCacheSize(), 0); - assertEquals(trinoFileSystemCache.getCacheSize(), 0); + assertThat(trinoFileSystemCacheStats.getCacheSize()).isEqualTo(0); + assertThat(trinoFileSystemCache.getCacheSize()).isEqualTo(0); Configuration configuration = new Configuration(false); trinoFileSystemCache.get(new URI("file:///tmp/path/"), configuration); - assertEquals(trinoFileSystemCacheStats.getGetCalls().getTotalCount(), 1); - assertEquals(trinoFileSystemCacheStats.getCacheSize(), 1); - assertEquals(trinoFileSystemCache.getCacheSize(), 1); + assertThat(trinoFileSystemCacheStats.getGetCalls().getTotalCount()).isEqualTo(1); + assertThat(trinoFileSystemCacheStats.getCacheSize()).isEqualTo(1); + assertThat(trinoFileSystemCache.getCacheSize()).isEqualTo(1); trinoFileSystemCache.get(new URI("file:///tmp/path1/"), configuration); - assertEquals(trinoFileSystemCacheStats.getGetCalls().getTotalCount(), 2); - assertEquals(trinoFileSystemCacheStats.getCacheSize(), 1); - assertEquals(trinoFileSystemCache.getCacheSize(), 1); + assertThat(trinoFileSystemCacheStats.getGetCalls().getTotalCount()).isEqualTo(2); + assertThat(trinoFileSystemCacheStats.getCacheSize()).isEqualTo(1); + assertThat(trinoFileSystemCache.getCacheSize()).isEqualTo(1); // use getUnique to ensure cache size is increased FileSystem fileSystem = trinoFileSystemCache.getUnique(new URI("file:///tmp/path2/"), configuration); - assertEquals(trinoFileSystemCacheStats.getGetCalls().getTotalCount(), 2); - assertEquals(trinoFileSystemCacheStats.getGetUniqueCalls().getTotalCount(), 1); - assertEquals(trinoFileSystemCacheStats.getCacheSize(), 2); - assertEquals(trinoFileSystemCache.getCacheSize(), 2); + assertThat(trinoFileSystemCacheStats.getGetCalls().getTotalCount()).isEqualTo(2); + assertThat(trinoFileSystemCacheStats.getGetUniqueCalls().getTotalCount()).isEqualTo(1); + assertThat(trinoFileSystemCacheStats.getCacheSize()).isEqualTo(2); + assertThat(trinoFileSystemCache.getCacheSize()).isEqualTo(2); trinoFileSystemCache.remove(fileSystem); - assertEquals(trinoFileSystemCacheStats.getRemoveCalls().getTotalCount(), 1); - assertEquals(trinoFileSystemCacheStats.getCacheSize(), 1); - assertEquals(trinoFileSystemCache.getCacheSize(), 1); + assertThat(trinoFileSystemCacheStats.getRemoveCalls().getTotalCount()).isEqualTo(1); + assertThat(trinoFileSystemCacheStats.getCacheSize()).isEqualTo(1); + assertThat(trinoFileSystemCache.getCacheSize()).isEqualTo(1); trinoFileSystemCache.closeAll(); - assertEquals(trinoFileSystemCacheStats.getCacheSize(), 0); - assertEquals(trinoFileSystemCache.getCacheSize(), 0); + assertThat(trinoFileSystemCacheStats.getCacheSize()).isEqualTo(0); + assertThat(trinoFileSystemCache.getCacheSize()).isEqualTo(0); } @Test @@ -70,9 +70,9 @@ public void testFailedCallsCountIsCorrect() configuration.setInt("fs.cache.max-size", 0); assertThatThrownBy(() -> trinoFileSystemCache.get(new URI("file:///tmp/path/"), configuration)) .hasMessageMatching("FileSystem max cache size has been reached: 0"); - assertEquals(trinoFileSystemCacheStats.getGetCallsFailed().getTotalCount(), 1); - assertEquals(trinoFileSystemCacheStats.getGetCalls().getTotalCount(), 1); - assertEquals(trinoFileSystemCacheStats.getCacheSize(), 0); - assertEquals(trinoFileSystemCache.getCacheSize(), 0); + assertThat(trinoFileSystemCacheStats.getGetCallsFailed().getTotalCount()).isEqualTo(1); + assertThat(trinoFileSystemCacheStats.getGetCalls().getTotalCount()).isEqualTo(1); + assertThat(trinoFileSystemCacheStats.getCacheSize()).isEqualTo(0); + assertThat(trinoFileSystemCache.getCacheSize()).isEqualTo(0); } } diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/cos/TestHiveCosServiceConfigurationProvider.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/cos/TestHiveCosServiceConfigurationProvider.java index 357c646f055a..411764851796 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/cos/TestHiveCosServiceConfigurationProvider.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/cos/TestHiveCosServiceConfigurationProvider.java @@ -36,7 +36,7 @@ import static io.airlift.testing.Assertions.assertInstanceOf; import static io.trino.hdfs.s3.TestTrinoS3FileSystem.getAwsCredentialsProvider; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestHiveCosServiceConfigurationProvider { @@ -91,10 +91,10 @@ private static void verifyStaticCredentials(HdfsConfiguration hiveHdfsConfigurat HdfsContext hdfsContext = new HdfsContext(ConnectorIdentity.forUser("test").build()); Configuration configuration = hiveHdfsConfiguration.getConfiguration(hdfsContext, URI.create(uri)); fileSystem.initialize(URI.create(uri), configuration); - assertEquals(fileSystem.getBucketName(URI.create(uri)), expectedBucket); + assertThat(fileSystem.getBucketName(URI.create(uri))).isEqualTo(expectedBucket); AWSCredentialsProvider awsCredentialsProvider = getAwsCredentialsProvider(fileSystem); assertInstanceOf(awsCredentialsProvider, AWSStaticCredentialsProvider.class); - assertEquals(awsCredentialsProvider.getCredentials().getAWSAccessKeyId(), expectedAccessKey); - assertEquals(awsCredentialsProvider.getCredentials().getAWSSecretKey(), expectedSecretKey); + assertThat(awsCredentialsProvider.getCredentials().getAWSAccessKeyId()).isEqualTo(expectedAccessKey); + assertThat(awsCredentialsProvider.getCredentials().getAWSSecretKey()).isEqualTo(expectedSecretKey); } } diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/cos/TestServiceConfig.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/cos/TestServiceConfig.java index babccca8dd66..905a50b79241 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/cos/TestServiceConfig.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/cos/TestServiceConfig.java @@ -24,9 +24,8 @@ import java.util.Optional; import java.util.Properties; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; public class TestServiceConfig { @@ -49,9 +48,9 @@ private static void assertConfig( String secretValue, Optional endpointValue) { - assertEquals(serviceConfig.getAccessKey(), accessValue); - assertEquals(serviceConfig.getSecretKey(), secretValue); - assertEquals(serviceConfig.getEndpoint(), endpointValue); + assertThat(serviceConfig.getAccessKey()).isEqualTo(accessValue); + assertThat(serviceConfig.getSecretKey()).isEqualTo(secretValue); + assertThat(serviceConfig.getEndpoint()).isEqualTo(endpointValue); } @Test @@ -59,7 +58,7 @@ public void testLoad() throws IOException { try (TempFile tempFile = new TempFile()) { - assertTrue(ServiceConfig.loadServiceConfigs(tempFile.file()).isEmpty()); + assertThat(ServiceConfig.loadServiceConfigs(tempFile.file()).isEmpty()).isTrue(); writeProperties(tempFile, ImmutableMap.builder() .put("a.access-key", "a-accessValue") @@ -75,7 +74,7 @@ public void testLoad() .buildOrThrow()); Map bucketConfigs = ServiceConfig.loadServiceConfigs(tempFile.file()); - assertEquals(bucketConfigs.keySet(), ImmutableSet.of("a", "b", "c")); + assertThat(bucketConfigs.keySet()).isEqualTo(ImmutableSet.of("a", "b", "c")); assertConfig(bucketConfigs.get("a"), "a-accessValue", "a-secretValue", Optional.of("a-endpointValue")); assertConfig(bucketConfigs.get("b"), "b-accessValue", "b-secretValue", Optional.empty()); diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/rubix/TestRubixCaching.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/rubix/TestRubixCaching.java index 8acfa153871c..22b733b9d8c5 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/rubix/TestRubixCaching.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/rubix/TestRubixCaching.java @@ -95,7 +95,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) @Execution(SAME_THREAD) @@ -331,11 +330,11 @@ public void testGetBlockLocations() BlockLocation[] file1Locations = cachingFileSystem.getFileBlockLocations(file1, 0, 3); BlockLocation[] file2Locations = cachingFileSystem.getFileBlockLocations(file2, 0, 3); - assertEquals(file1Locations.length, 1); - assertEquals(file2Locations.length, 1); + assertThat(file1Locations.length).isEqualTo(1); + assertThat(file2Locations.length).isEqualTo(1); - assertEquals(file1Locations[0].getHosts()[0], "127.0.0.3"); - assertEquals(file2Locations[0].getHosts()[0], "127.0.0.2"); + assertThat(file1Locations[0].getHosts()[0]).isEqualTo("127.0.0.3"); + assertThat(file2Locations[0].getHosts()[0]).isEqualTo("127.0.0.2"); } @Test @@ -363,7 +362,7 @@ public void testCacheRead() // wait for async Rubix requests to complete assertEventually( new Duration(10, SECONDS), - () -> assertEquals(getAsyncDownloadedMb(ASYNC), beforeAsyncDownloadedMb + 1)); + () -> assertThat(getAsyncDownloadedMb(ASYNC)).isEqualTo(beforeAsyncDownloadedMb + 1)); } // stats are propagated asynchronously @@ -372,7 +371,7 @@ public void testCacheRead() () -> { // data should be read from remote source only assertGreaterThan(getRemoteReadsCount(), beforeRemoteReadsCount); - assertEquals(getCachedReadsCount(), beforeCachedReadsCount); + assertThat(getCachedReadsCount()).isEqualTo(beforeCachedReadsCount); }); // ensure that subsequent read uses cache exclusively @@ -382,7 +381,7 @@ public void testCacheRead() long remoteReadsCount = getRemoteReadsCount(); assertFileContents(cachingFileSystem, file, randomData); assertGreaterThan(getCachedReadsCount(), beforeCachedReadsCount); - assertEquals(getRemoteReadsCount(), remoteReadsCount); + assertThat(getRemoteReadsCount()).isEqualTo(remoteReadsCount); }); closeRubix(); @@ -431,7 +430,7 @@ public void testLargeFile() // wait for async Rubix requests to complete assertEventually( new Duration(10, SECONDS), - () -> assertEquals(getAsyncDownloadedMb(ASYNC), beforeAsyncDownloadedMb + 100)); + () -> assertThat(getAsyncDownloadedMb(ASYNC)).isEqualTo(beforeAsyncDownloadedMb + 100)); } // stats are propagated asynchronously @@ -449,7 +448,7 @@ public void testLargeFile() long remoteReadsCount = getRemoteReadsCount(); assertFileContents(cachingFileSystem, file, randomData); assertGreaterThan(getCachedReadsCount(), beforeCachedReadsCount); - assertEquals(getRemoteReadsCount(), remoteReadsCount); + assertThat(getRemoteReadsCount()).isEqualTo(remoteReadsCount); }); long secondCachedReadsCount = getCachedReadsCount(); long secondRemoteReadsCount = getRemoteReadsCount(); @@ -480,7 +479,7 @@ public void testLargeFile() () -> { // data should be read from cache only assertGreaterThan(getCachedReadsCount(), secondCachedReadsCount); - assertEquals(getRemoteReadsCount(), secondRemoteReadsCount); + assertThat(getRemoteReadsCount()).isEqualTo(secondRemoteReadsCount); }); closeRubix(); @@ -560,7 +559,9 @@ public boolean processBytes(byte[] buf, int off, int len) @Override public Void getResult() { - assertEquals(readOffset, expected.length, "Read different amount of data"); + assertThat(readOffset) + .describedAs("Read different amount of data") + .isEqualTo(expected.length); return null; } }); diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/AbstractTestTrinoS3FileSystem.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/AbstractTestTrinoS3FileSystem.java index 1919ed6a1b81..4f41ada007dc 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/AbstractTestTrinoS3FileSystem.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/AbstractTestTrinoS3FileSystem.java @@ -35,7 +35,6 @@ import static io.trino.testing.TestingNames.randomNameSuffix; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertTrue; public abstract class AbstractTestTrinoS3FileSystem { @@ -56,7 +55,7 @@ public void testDeleteRecursivelyMissingObjectPath() try (TrinoS3FileSystem fs = createFileSystem()) { // Follow Amazon S3 behavior if attempting to delete an object that does not exist // and return a success message - assertTrue(fs.delete(new Path("s3://%s/%s".formatted(getBucketName(), prefix)), true)); + assertThat(fs.delete(new Path("s3://%s/%s".formatted(getBucketName(), prefix)), true)).isTrue(); } } @@ -69,7 +68,7 @@ public void testDeleteNonRecursivelyMissingObjectPath() try (TrinoS3FileSystem fs = createFileSystem()) { // Follow Amazon S3 behavior if attempting to delete an object that does not exist // and return a success message - assertTrue(fs.delete(new Path("s3://%s/%s".formatted(getBucketName(), prefix)), false)); + assertThat(fs.delete(new Path("s3://%s/%s".formatted(getBucketName(), prefix)), false)).isTrue(); } } @@ -89,7 +88,7 @@ public void testDeleteRecursivelyObjectPath() List paths = listPaths(fs.getS3Client(), getBucketName(), prefix, true); assertThat(paths).containsOnly(fileKey); - assertTrue(fs.delete(new Path(filePath), true)); + assertThat(fs.delete(new Path(filePath), true)).isTrue(); assertThat(listPaths(fs.getS3Client(), getBucketName(), prefix, true)).isEmpty(); } @@ -115,7 +114,7 @@ public void testDeleteNonRecursivelyObjectPath() List paths = listPaths(fs.getS3Client(), getBucketName(), prefix, true); assertThat(paths).containsOnly(fileKey); - assertTrue(fs.delete(new Path(filePath), false)); + assertThat(fs.delete(new Path(filePath), false)).isTrue(); assertThat(listPaths(fs.getS3Client(), getBucketName(), prefix, true)).isEmpty(); } @@ -141,7 +140,7 @@ public void testDeleteNonRecursivelyObjectNamePrefixingAnotherObjectName() "%s/foo".formatted(prefix), "%s/foobar".formatted(prefix)); - assertTrue(fs.delete(new Path("s3://%s/%s/foo".formatted(getBucketName(), prefix)), false)); + assertThat(fs.delete(new Path("s3://%s/%s/foo".formatted(getBucketName(), prefix)), false)).isTrue(); paths = listPaths(fs.getS3Client(), getBucketName(), prefix, true); assertThat(paths).containsOnly( @@ -169,7 +168,7 @@ public void testDeleteNonRecursivelyDirectoryNamePrefixingAnotherDirectoryName() "%s/foo/".formatted(prefix), "%s/foobar/".formatted(prefix)); - assertTrue(fs.delete(new Path("s3://%s/%s/foo".formatted(getBucketName(), prefix)), true)); + assertThat(fs.delete(new Path("s3://%s/%s/foo".formatted(getBucketName(), prefix)), true)).isTrue(); paths = listPaths(fs.getS3Client(), getBucketName(), prefix, true); assertThat(paths).containsOnly( @@ -194,7 +193,7 @@ public void testDeleteNonRecursivelyEmptyDirectory() List paths = listPaths(fs.getS3Client(), getBucketName(), prefix, false); assertThat(paths).containsOnly(prefix + PATH_SEPARATOR); - assertTrue(fs.delete(new Path(prefixPath), false)); + assertThat(fs.delete(new Path(prefixPath), false)).isTrue(); assertThat(listPaths(fs.getS3Client(), getBucketName(), prefix, true)).isEmpty(); } @@ -220,7 +219,7 @@ public void testDeleteNonRecursivelyEmptyDirectoryWithAdditionalDirectorySuffixP directoryName + PATH_SEPARATOR, directoryName + DIRECTORY_SUFFIX); - assertTrue(fs.delete(new Path(directoryPath), false)); + assertThat(fs.delete(new Path(directoryPath), false)).isTrue(); assertThat(listPaths(fs.getS3Client(), getBucketName(), directoryName, true)).isEmpty(); } @@ -246,7 +245,7 @@ public void testDeleteRecursivelyObjectNamePrefixingAnotherObjectName() "%s/foo".formatted(prefix), "%s/foobar".formatted(prefix)); - assertTrue(fs.delete(new Path("s3://%s/%s/foo".formatted(getBucketName(), prefix)), true)); + assertThat(fs.delete(new Path("s3://%s/%s/foo".formatted(getBucketName(), prefix)), true)).isTrue(); paths = listPaths(fs.getS3Client(), getBucketName(), prefix, true); assertThat(paths).containsOnly( @@ -274,7 +273,7 @@ public void testDeleteRecursivelyDirectoryNamePrefixingAnotherDirectoryName() "%s/foo/".formatted(prefix), "%s/foobar/".formatted(prefix)); - assertTrue(fs.delete(new Path("s3://%s/%s/foo".formatted(getBucketName(), prefix)), true)); + assertThat(fs.delete(new Path("s3://%s/%s/foo".formatted(getBucketName(), prefix)), true)).isTrue(); paths = listPaths(fs.getS3Client(), getBucketName(), prefix, true); assertThat(paths).containsOnly("%s/foobar/".formatted(prefix)); @@ -303,7 +302,7 @@ public void testDeleteRecursivelyPrefixContainingMultipleObjectsPlain() "%s/%s".formatted(prefix, filename1), "%s/%s".formatted(prefix, filename2)); - assertTrue(fs.delete(new Path(prefixPath), true)); + assertThat(fs.delete(new Path(prefixPath), true)).isTrue(); assertThat(listPaths(fs.getS3Client(), getBucketName(), prefix, true)).isEmpty(); } @@ -331,7 +330,7 @@ public void testDeleteRecursivelyPrefixWithSpecialCharacters() "%s/%s".formatted(prefix, filename1), "%s/%s".formatted(prefix, filename2)); - assertTrue(fs.delete(new Path(prefixPath), true)); + assertThat(fs.delete(new Path(prefixPath), true)).isTrue(); assertThat(listPaths(fs.getS3Client(), getBucketName(), prefix, true)).isEmpty(); } @@ -362,7 +361,7 @@ public void testDeleteRecursivelyDirectoryWithDeepHierarchy() fs.createNewFile(new Path(directoryPath + "/dir3", filename3)); createDirectory(fs.getS3Client(), getBucketName(), directoryKey + "/dir4"); - assertTrue(fs.delete(new Path(directoryPath), true)); + assertThat(fs.delete(new Path(directoryPath), true)).isTrue(); assertThat(listPaths(fs.getS3Client(), getBucketName(), prefix, true)).isEmpty(); } @@ -389,7 +388,7 @@ public void testDeleteRecursivelyEmptyDirectory() directoryKey + PATH_SEPARATOR, directoryKey + DIRECTORY_SUFFIX); - assertTrue(fs.delete(new Path(prefixPath + "/directory"), true)); + assertThat(fs.delete(new Path(prefixPath + "/directory"), true)).isTrue(); assertThat(listPaths(fs.getS3Client(), getBucketName(), prefix, true)).isEmpty(); } @@ -423,7 +422,7 @@ public void testDeleteRecursivelyDirectoryWithObjectsAndDirectorySuffixPlacehold createDirectory(fs.getS3Client(), getBucketName(), directoryKey + "/dir4"); fs.createNewFile(new Path(directoryPath + "/dir4" + DIRECTORY_SUFFIX)); - assertTrue(fs.delete(new Path(directoryPath), true)); + assertThat(fs.delete(new Path(directoryPath), true)).isTrue(); assertThat(listPaths(fs.getS3Client(), getBucketName(), prefix, true)).isEmpty(); } @@ -454,7 +453,7 @@ public void testDeleteRecursivelyPrefixContainingDeepHierarchy() "%s/dir2/dir22/%s".formatted(prefix, filename2), "%s/dir3/dir33/dir333/%s".formatted(prefix, filename3)); - assertTrue(fs.delete(new Path(prefixPath), true)); + assertThat(fs.delete(new Path(prefixPath), true)).isTrue(); assertThat(listPaths(fs.getS3Client(), getBucketName(), prefix, true)).isEmpty(); } diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestS3SecurityMapping.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestS3SecurityMapping.java index e1684fb41166..c67af1732bdd 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestS3SecurityMapping.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestS3SecurityMapping.java @@ -43,8 +43,6 @@ import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; public class TestS3SecurityMapping { @@ -336,19 +334,19 @@ private static void assertMapping(DynamicConfigurationProvider provider, Mapping { Configuration configuration = new Configuration(false); - assertNull(configuration.get(S3_ACCESS_KEY)); - assertNull(configuration.get(S3_SECRET_KEY)); - assertNull(configuration.get(S3_IAM_ROLE)); - assertNull(configuration.get(S3_KMS_KEY_ID)); + assertThat(configuration.get(S3_ACCESS_KEY)).isNull(); + assertThat(configuration.get(S3_SECRET_KEY)).isNull(); + assertThat(configuration.get(S3_IAM_ROLE)).isNull(); + assertThat(configuration.get(S3_KMS_KEY_ID)).isNull(); applyMapping(provider, selector, configuration); - assertEquals(configuration.get(S3_ACCESS_KEY), mappingResult.getAccessKey().orElse(null)); - assertEquals(configuration.get(S3_SECRET_KEY), mappingResult.getSecretKey().orElse(null)); - assertEquals(configuration.get(S3_IAM_ROLE), mappingResult.getRole().orElse(null)); - assertEquals(configuration.get(S3_KMS_KEY_ID), mappingResult.getKmsKeyId().orElse(null)); - assertEquals(configuration.get(S3_ENDPOINT), mappingResult.getEndpoint().orElse(null)); - assertEquals(configuration.get(S3_ROLE_SESSION_NAME), mappingResult.getRoleSessionName().orElse(null)); + assertThat(configuration.get(S3_ACCESS_KEY)).isEqualTo(mappingResult.getAccessKey().orElse(null)); + assertThat(configuration.get(S3_SECRET_KEY)).isEqualTo(mappingResult.getSecretKey().orElse(null)); + assertThat(configuration.get(S3_IAM_ROLE)).isEqualTo(mappingResult.getRole().orElse(null)); + assertThat(configuration.get(S3_KMS_KEY_ID)).isEqualTo(mappingResult.getKmsKeyId().orElse(null)); + assertThat(configuration.get(S3_ENDPOINT)).isEqualTo(mappingResult.getEndpoint().orElse(null)); + assertThat(configuration.get(S3_ROLE_SESSION_NAME)).isEqualTo(mappingResult.getRoleSessionName().orElse(null)); } private static void assertMappingFails(DynamicConfigurationProvider provider, MappingSelector selector, String message) diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestS3SecurityMappingsParser.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestS3SecurityMappingsParser.java index 92b24b72cfe4..5d07aeaeda5b 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestS3SecurityMappingsParser.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestS3SecurityMappingsParser.java @@ -19,7 +19,7 @@ import java.net.URI; import java.util.Optional; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestS3SecurityMappingsParser { @@ -34,7 +34,7 @@ public void testParse() provider.parseJSONString("{\"data\": {\"mappings\": [{\"iamRole\":\"arn:aws:iam::test\",\"user\":\"test\"}]}, \"time\": \"30s\"}"); Optional mapping = mappings.getMapping(ConnectorIdentity.ofUser("test"), URI.create("http://trino")); - assertTrue(mapping.isPresent()); + assertThat(mapping.isPresent()).isTrue(); } @Test @@ -47,6 +47,6 @@ public void testParseDefault() provider.parseJSONString("{\"mappings\": [{\"iamRole\":\"arn:aws:iam::test\",\"user\":\"test\"}]}"); Optional mapping = mappings.getMapping(ConnectorIdentity.ofUser("test"), URI.create("http://trino")); - assertTrue(mapping.isPresent()); + assertThat(mapping.isPresent()).isTrue(); } } diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestTrinoS3FileSystem.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestTrinoS3FileSystem.java index 24945b453405..2482e93dbf38 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestTrinoS3FileSystem.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestTrinoS3FileSystem.java @@ -110,10 +110,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assumptions.abort; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; public class TestTrinoS3FileSystem { @@ -127,8 +123,8 @@ public void testEmbeddedCredentials() Configuration config = new Configuration(false); try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { AWSCredentials credentials = getStaticCredentials(config, fs, "s3n://testAccess:testSecret@test-bucket/"); - assertEquals(credentials.getAWSAccessKeyId(), "testAccess"); - assertEquals(credentials.getAWSSecretKey(), "testSecret"); + assertThat(credentials.getAWSAccessKeyId()).isEqualTo("testAccess"); + assertThat(credentials.getAWSSecretKey()).isEqualTo("testSecret"); assertThat(credentials).isNotInstanceOf(AWSSessionCredentials.class); } } @@ -143,18 +139,18 @@ public void testStaticCredentials() try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { AWSCredentials credentials = getStaticCredentials(config, fs, "s3n://test-bucket/"); - assertEquals(credentials.getAWSAccessKeyId(), "test_access_key"); - assertEquals(credentials.getAWSSecretKey(), "test_secret_key"); + assertThat(credentials.getAWSAccessKeyId()).isEqualTo("test_access_key"); + assertThat(credentials.getAWSSecretKey()).isEqualTo("test_secret_key"); assertThat(credentials).isNotInstanceOf(AWSSessionCredentials.class); } config.set(S3_SESSION_TOKEN, "test_token"); try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { AWSCredentials credentials = getStaticCredentials(config, fs, "s3n://test-bucket/"); - assertEquals(credentials.getAWSAccessKeyId(), "test_access_key"); - assertEquals(credentials.getAWSSecretKey(), "test_secret_key"); + assertThat(credentials.getAWSAccessKeyId()).isEqualTo("test_access_key"); + assertThat(credentials.getAWSSecretKey()).isEqualTo("test_secret_key"); assertThat(credentials).isInstanceOfSatisfying(AWSSessionCredentials.class, sessionCredentials -> - assertEquals(sessionCredentials.getSessionToken(), "test_token")); + assertThat(sessionCredentials.getSessionToken()).isEqualTo("test_token")); } } @@ -239,8 +235,8 @@ public void testAssumeRoleStaticCredentials() assertInstanceOf(tokenService, AWSStaticCredentialsProvider.class); AWSCredentials credentials = tokenService.getCredentials(); - assertEquals(credentials.getAWSAccessKeyId(), "test_access_key"); - assertEquals(credentials.getAWSSecretKey(), "test_secret_key"); + assertThat(credentials.getAWSAccessKeyId()).isEqualTo("test_access_key"); + assertThat(credentials.getAWSSecretKey()).isEqualTo("test_secret_key"); } } @@ -249,7 +245,7 @@ private static AWSCredentialsProvider getStsCredentialsProvider(TrinoS3FileSyste AWSCredentialsProvider awsCredentialsProvider = getAwsCredentialsProvider(fs); assertInstanceOf(awsCredentialsProvider, STSAssumeRoleSessionCredentialsProvider.class); - assertEquals(getFieldValue(awsCredentialsProvider, "roleArn", String.class), expectedRole); + assertThat(getFieldValue(awsCredentialsProvider, "roleArn", String.class)).isEqualTo(expectedRole); AWSSecurityTokenService tokenService = getFieldValue(awsCredentialsProvider, "securityTokenService", AWSSecurityTokenService.class); assertInstanceOf(tokenService, AWSSecurityTokenServiceClient.class); @@ -268,8 +264,8 @@ public void testAssumeRoleCredentialsWithExternalId() fs.initialize(new URI("s3n://test-bucket/"), config); AWSCredentialsProvider awsCredentialsProvider = getAwsCredentialsProvider(fs); assertInstanceOf(awsCredentialsProvider, STSAssumeRoleSessionCredentialsProvider.class); - assertEquals(getFieldValue(awsCredentialsProvider, "roleArn", String.class), "role"); - assertEquals(getFieldValue(awsCredentialsProvider, "roleExternalId", String.class), "externalId"); + assertThat(getFieldValue(awsCredentialsProvider, "roleArn", String.class)).isEqualTo("role"); + assertThat(getFieldValue(awsCredentialsProvider, "roleExternalId", String.class)).isEqualTo("externalId"); } } @@ -295,7 +291,7 @@ public void testPathStyleAccess() try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { fs.initialize(new URI("s3n://test-bucket/"), config); S3ClientOptions clientOptions = getFieldValue(fs.getS3Client(), AmazonS3Client.class, "clientOptions", S3ClientOptions.class); - assertTrue(clientOptions.isPathStyleAccess()); + assertThat(clientOptions.isPathStyleAccess()).isTrue(); } } @@ -310,11 +306,11 @@ public void testUnderscoreBucket() MockAmazonS3 s3 = new MockAmazonS3(); String expectedBucketName = "test-bucket_underscore"; URI uri = new URI("s3n://" + expectedBucketName + "/"); - assertEquals(fs.getBucketName(uri), expectedBucketName); + assertThat(fs.getBucketName(uri)).isEqualTo(expectedBucketName); fs.initialize(uri, config); fs.setS3Client(s3); fs.getS3ObjectMetadata(new Path("/test/path")); - assertEquals(expectedBucketName, s3.getGetObjectMetadataRequest().getBucketName()); + assertThat(expectedBucketName).isEqualTo(s3.getGetObjectMetadataRequest().getBucketName()); } } @@ -338,9 +334,9 @@ public void testReadRetryCounters() } catch (Throwable expected) { assertInstanceOf(expected, AmazonS3Exception.class); - assertEquals(((AmazonS3Exception) expected).getStatusCode(), HTTP_INTERNAL_ERROR); - assertEquals(TrinoS3FileSystem.getFileSystemStats().getReadRetries().getTotalCount(), maxRetries); - assertEquals(TrinoS3FileSystem.getFileSystemStats().getGetObjectRetries().getTotalCount(), (maxRetries + 1L) * maxRetries); + assertThat(((AmazonS3Exception) expected).getStatusCode()).isEqualTo(HTTP_INTERNAL_ERROR); + assertThat(TrinoS3FileSystem.getFileSystemStats().getReadRetries().getTotalCount()).isEqualTo(maxRetries); + assertThat(TrinoS3FileSystem.getFileSystemStats().getGetObjectRetries().getTotalCount()).isEqualTo((maxRetries + 1L) * maxRetries); } } } @@ -363,8 +359,8 @@ public void testGetMetadataRetryCounter() } catch (Throwable expected) { assertInstanceOf(expected, AmazonS3Exception.class); - assertEquals(((AmazonS3Exception) expected).getStatusCode(), HTTP_INTERNAL_ERROR); - assertEquals(TrinoS3FileSystem.getFileSystemStats().getGetMetadataRetries().getTotalCount(), maxRetries); + assertThat(((AmazonS3Exception) expected).getStatusCode()).isEqualTo(HTTP_INTERNAL_ERROR); + assertThat(TrinoS3FileSystem.getFileSystemStats().getGetMetadataRetries().getTotalCount()).isEqualTo(maxRetries); } } @@ -432,7 +428,7 @@ public void testCreateWithNonexistentStagingDirectory() fs.setS3Client(s3); FSDataOutputStream stream = fs.create(new Path("s3n://test-bucket/test")); stream.close(); - assertTrue(Files.exists(staging)); + assertThat(Files.exists(staging)).isTrue(); } finally { deleteRecursively(stagingParent, ALLOW_INSECURE); @@ -487,7 +483,7 @@ public void testCreateWithStagingDirectorySymlink() fs.setS3Client(s3); FSDataOutputStream stream = fs.create(new Path("s3n://test-bucket/test")); stream.close(); - assertTrue(Files.exists(link)); + assertThat(Files.exists(link)).isTrue(); } } finally { @@ -506,7 +502,7 @@ public void testReadRequestRangeNotSatisfiable() fs.initialize(new URI("s3n://test-bucket/"), new Configuration(false)); fs.setS3Client(s3); try (FSDataInputStream inputStream = fs.open(new Path("s3n://test-bucket/test"))) { - assertEquals(inputStream.read(), -1); + assertThat(inputStream.read()).isEqualTo(-1); } } } @@ -535,7 +531,7 @@ public void testGetMetadataNotFound() s3.setGetObjectMetadataHttpCode(HTTP_NOT_FOUND); fs.initialize(new URI("s3n://test-bucket/"), new Configuration(false)); fs.setS3Client(s3); - assertNull(fs.getS3ObjectMetadata(new Path("s3n://test-bucket/test"))); + assertThat(fs.getS3ObjectMetadata(new Path("s3n://test-bucket/test"))).isNull(); } } @@ -610,8 +606,8 @@ public void testUserAgentPrefix() try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { fs.initialize(new URI("s3n://test-bucket/"), config); ClientConfiguration clientConfig = getFieldValue(fs.getS3Client(), AmazonWebServiceClient.class, "clientConfiguration", ClientConfiguration.class); - assertEquals(clientConfig.getUserAgentSuffix(), "Trino"); - assertEquals(clientConfig.getUserAgentPrefix(), userAgentPrefix); + assertThat(clientConfig.getUserAgentSuffix()).isEqualTo("Trino"); + assertThat(clientConfig.getUserAgentPrefix()).isEqualTo(userAgentPrefix); } } @@ -623,12 +619,12 @@ public void testDefaultS3ClientConfiguration() try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { fs.initialize(new URI("s3n://test-bucket/"), new Configuration(false)); ClientConfiguration config = getFieldValue(fs.getS3Client(), AmazonWebServiceClient.class, "clientConfiguration", ClientConfiguration.class); - assertEquals(config.getMaxErrorRetry(), defaults.getS3MaxErrorRetries()); - assertEquals(config.getConnectionTimeout(), defaults.getS3ConnectTimeout().toMillis()); - assertEquals(config.getSocketTimeout(), defaults.getS3SocketTimeout().toMillis()); - assertEquals(config.getMaxConnections(), defaults.getS3MaxConnections()); - assertEquals(config.getUserAgentSuffix(), "Trino"); - assertEquals(config.getUserAgentPrefix(), ""); + assertThat(config.getMaxErrorRetry()).isEqualTo(defaults.getS3MaxErrorRetries()); + assertThat(config.getConnectionTimeout()).isEqualTo(defaults.getS3ConnectTimeout().toMillis()); + assertThat(config.getSocketTimeout()).isEqualTo(defaults.getS3SocketTimeout().toMillis()); + assertThat(config.getMaxConnections()).isEqualTo(defaults.getS3MaxConnections()); + assertThat(config.getUserAgentSuffix()).isEqualTo("Trino"); + assertThat(config.getUserAgentPrefix()).isEqualTo(""); } } @@ -653,13 +649,13 @@ public void testProxyDefaultsS3ClientConfiguration() try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { fs.initialize(new URI("s3n://test-bucket/"), trinoFsConfiguration); ClientConfiguration config = getFieldValue(fs.getS3Client(), AmazonWebServiceClient.class, "clientConfiguration", ClientConfiguration.class); - assertNull(config.getProxyHost()); - assertEquals(config.getProxyPort(), -1); - assertEquals(config.getProxyProtocol(), Protocol.HTTP); - assertEquals(config.getNonProxyHosts(), System.getProperty("http.nonProxyHosts")); - assertNull(config.getProxyUsername()); - assertNull(config.getProxyPassword()); - assertFalse(config.isPreemptiveBasicProxyAuth()); + assertThat(config.getProxyHost()).isNull(); + assertThat(config.getProxyPort()).isEqualTo(-1); + assertThat(config.getProxyProtocol()).isEqualTo(Protocol.HTTP); + assertThat(config.getNonProxyHosts()).isEqualTo(System.getProperty("http.nonProxyHosts")); + assertThat(config.getProxyUsername()).isNull(); + assertThat(config.getProxyPassword()).isNull(); + assertThat(config.isPreemptiveBasicProxyAuth()).isFalse(); } } @@ -683,13 +679,13 @@ public void testOnNoHostProxyDefaultsS3ClientConfiguration() try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { fs.initialize(new URI("s3n://test-bucket/"), trinoFsConfiguration); ClientConfiguration config = getFieldValue(fs.getS3Client(), AmazonWebServiceClient.class, "clientConfiguration", ClientConfiguration.class); - assertNull(config.getProxyHost()); - assertEquals(config.getProxyPort(), -1); - assertEquals(config.getProxyProtocol(), Protocol.HTTP); - assertEquals(config.getNonProxyHosts(), System.getProperty("http.nonProxyHosts")); - assertNull(config.getProxyUsername()); - assertNull(config.getProxyPassword()); - assertFalse(config.isPreemptiveBasicProxyAuth()); + assertThat(config.getProxyHost()).isNull(); + assertThat(config.getProxyPort()).isEqualTo(-1); + assertThat(config.getProxyProtocol()).isEqualTo(Protocol.HTTP); + assertThat(config.getNonProxyHosts()).isEqualTo(System.getProperty("http.nonProxyHosts")); + assertThat(config.getProxyUsername()).isNull(); + assertThat(config.getProxyPassword()).isNull(); + assertThat(config.isPreemptiveBasicProxyAuth()).isFalse(); } } @@ -713,13 +709,13 @@ public void testExplicitProxyS3ClientConfiguration() try (TrinoS3FileSystem fs = new TrinoS3FileSystem()) { fs.initialize(new URI("s3n://test-bucket/"), trinoFsConfiguration); ClientConfiguration config = getFieldValue(fs.getS3Client(), AmazonWebServiceClient.class, "clientConfiguration", ClientConfiguration.class); - assertEquals(config.getProxyHost(), "dummy.com"); - assertEquals(config.getProxyPort(), 40000); - assertEquals(config.getProxyProtocol(), Protocol.HTTPS); - assertEquals(config.getNonProxyHosts(), "firsthost.com|secondhost.com"); - assertEquals(config.getProxyUsername(), "dummy_username"); - assertEquals(config.getProxyPassword(), "dummy_password"); - assertTrue(config.isPreemptiveBasicProxyAuth()); + assertThat(config.getProxyHost()).isEqualTo("dummy.com"); + assertThat(config.getProxyPort()).isEqualTo(40000); + assertThat(config.getProxyProtocol()).isEqualTo(Protocol.HTTPS); + assertThat(config.getNonProxyHosts()).isEqualTo("firsthost.com|secondhost.com"); + assertThat(config.getProxyUsername()).isEqualTo("dummy_username"); + assertThat(config.getProxyPassword()).isEqualTo("dummy_password"); + assertThat(config.isPreemptiveBasicProxyAuth()).isTrue(); } } @@ -735,7 +731,7 @@ private static void assertSkipGlacierObjects(boolean skipGlacierObjects) fs.initialize(new URI("s3n://test-bucket/"), config); fs.setS3Client(s3); FileStatus[] statuses = fs.listStatus(new Path("s3n://test-bucket/test")); - assertEquals(statuses.length, skipGlacierObjects ? 2 : 4); + assertThat(statuses.length).isEqualTo(skipGlacierObjects ? 2 : 4); } } @@ -751,7 +747,7 @@ public void testSkipHadoopFolderMarkerObjectsEnabled() fs.initialize(new URI("s3n://test-bucket/"), config); fs.setS3Client(s3); FileStatus[] statuses = fs.listStatus(new Path("s3n://test-bucket/test")); - assertEquals(statuses.length, 2); + assertThat(statuses.length).isEqualTo(2); } } @@ -837,7 +833,7 @@ public void testDefaultAcl() try (FSDataOutputStream stream = fs.create(new Path("s3n://test-bucket/test"))) { // initiate an upload by creating a stream & closing it immediately } - assertEquals(CannedAccessControlList.Private, s3.getAcl()); + assertThat(CannedAccessControlList.Private).isEqualTo(s3.getAcl()); } } @@ -856,7 +852,7 @@ public void testFullBucketOwnerControlAcl() try (FSDataOutputStream stream = fs.create(new Path("s3n://test-bucket/test"))) { // initiate an upload by creating a stream & closing it immediately } - assertEquals(CannedAccessControlList.BucketOwnerFullControl, s3.getAcl()); + assertThat(CannedAccessControlList.BucketOwnerFullControl).isEqualTo(s3.getAcl()); } } @@ -888,7 +884,7 @@ public void testStreamingUpload() .map(UploadPartRequest::getInputStream) .reduce(new ByteArrayInputStream(new byte[0]), SequenceInputStream::new); String data = new String(toByteArray(concatInputStream), US_ASCII); - assertEquals(data, "a" + "foo".repeat(21) + "bar".repeat(44) + "orange".repeat(22)); + assertThat(data).isEqualTo("a" + "foo".repeat(21) + "bar".repeat(44) + "orange".repeat(22)); } } @@ -914,10 +910,10 @@ public ObjectMetadata getObjectMetadata(GetObjectMetadataRequest getObjectMetada fs.setS3Client(s3); FileStatus fileStatus = fs.getFileStatus(new Path("s3n://test-bucket/empty-dir/")); - assertTrue(fileStatus.isDirectory()); + assertThat(fileStatus.isDirectory()).isTrue(); fileStatus = fs.getFileStatus(new Path("s3n://test-bucket/empty-dir")); - assertTrue(fileStatus.isDirectory()); + assertThat(fileStatus.isDirectory()).isTrue(); } } @@ -958,23 +954,23 @@ public ListObjectsV2Result listObjectsV2(ListObjectsV2Request listObjectsV2Reque fs.setS3Client(s3); List shallowAll = remoteIteratorToList(fs.listLocatedStatus(rootPath)); - assertEquals(shallowAll.size(), 2); - assertTrue(shallowAll.get(0).isDirectory()); - assertFalse(shallowAll.get(1).isDirectory()); - assertEquals(shallowAll.get(0).getPath(), new Path(rootPath, "prefix")); - assertEquals(shallowAll.get(1).getPath(), new Path(rootPath, rootObject.getKey())); + assertThat(shallowAll.size()).isEqualTo(2); + assertThat(shallowAll.get(0).isDirectory()).isTrue(); + assertThat(shallowAll.get(1).isDirectory()).isFalse(); + assertThat(shallowAll.get(0).getPath()).isEqualTo(new Path(rootPath, "prefix")); + assertThat(shallowAll.get(1).getPath()).isEqualTo(new Path(rootPath, rootObject.getKey())); List shallowFiles = remoteIteratorToList(fs.listFiles(rootPath, false)); - assertEquals(shallowFiles.size(), 1); - assertFalse(shallowFiles.get(0).isDirectory()); - assertEquals(shallowFiles.get(0).getPath(), new Path(rootPath, rootObject.getKey())); + assertThat(shallowFiles.size()).isEqualTo(1); + assertThat(shallowFiles.get(0).isDirectory()).isFalse(); + assertThat(shallowFiles.get(0).getPath()).isEqualTo(new Path(rootPath, rootObject.getKey())); List recursiveFiles = remoteIteratorToList(fs.listFiles(rootPath, true)); - assertEquals(recursiveFiles.size(), 2); - assertFalse(recursiveFiles.get(0).isDirectory()); - assertFalse(recursiveFiles.get(1).isDirectory()); - assertEquals(recursiveFiles.get(0).getPath(), new Path(rootPath, childObject.getKey())); - assertEquals(recursiveFiles.get(1).getPath(), new Path(rootPath, rootObject.getKey())); + assertThat(recursiveFiles.size()).isEqualTo(2); + assertThat(recursiveFiles.get(0).isDirectory()).isFalse(); + assertThat(recursiveFiles.get(1).isDirectory()).isFalse(); + assertThat(recursiveFiles.get(0).getPath()).isEqualTo(new Path(rootPath, childObject.getKey())); + assertThat(recursiveFiles.get(1).getPath()).isEqualTo(new Path(rootPath, rootObject.getKey())); } } diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestTrinoS3FileSystemMinio.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestTrinoS3FileSystemMinio.java index d5549d2065bf..2f8559fcb4de 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestTrinoS3FileSystemMinio.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestTrinoS3FileSystemMinio.java @@ -31,7 +31,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) public class TestTrinoS3FileSystemMinio @@ -147,7 +146,7 @@ public void testDeleteRecursivelyBucketRoot() assertThat(listPaths(s3, testBucketName, "", true)) .containsOnly("file1.txt", "directory2/", "directory2/file2.txt"); - assertTrue(fs.delete(new Path(testBucketPath + Path.SEPARATOR), true)); + assertThat(fs.delete(new Path(testBucketPath + Path.SEPARATOR), true)).isTrue(); assertThat(listPaths(s3, testBucketName, "", true)).isEmpty(); } diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestUriBasedS3SecurityMappingsProvider.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestUriBasedS3SecurityMappingsProvider.java index 935054b2af12..5a2b0282933e 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestUriBasedS3SecurityMappingsProvider.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestUriBasedS3SecurityMappingsProvider.java @@ -20,7 +20,7 @@ import static com.google.common.net.MediaType.JSON_UTF_8; import static io.airlift.http.client.testing.TestingResponse.mockResponse; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestUriBasedS3SecurityMappingsProvider { @@ -34,6 +34,6 @@ public void testGetRawJSON() S3SecurityMappingConfig conf = new S3SecurityMappingConfig().setConfigFilePath("http://test:1234/api/endpoint"); UriBasedS3SecurityMappingsProvider provider = new UriBasedS3SecurityMappingsProvider(conf, new TestingHttpClient(request -> response)); String result = provider.getRawJsonString(); - assertEquals(result, MOCK_MAPPINGS_RESPONSE); + assertThat(result).isEqualTo(MOCK_MAPPINGS_RESPONSE); } } diff --git a/lib/trino-matching/pom.xml b/lib/trino-matching/pom.xml index 1eb145ae2dce..d0cccac41602 100644 --- a/lib/trino-matching/pom.xml +++ b/lib/trino-matching/pom.xml @@ -28,6 +28,12 @@ test + + org.assertj + assertj-core + test + + org.junit.jupiter junit-jupiter-api diff --git a/lib/trino-matching/src/test/java/io/trino/matching/TestMatcher.java b/lib/trino-matching/src/test/java/io/trino/matching/TestMatcher.java index 09ec670cee4c..debdc9099987 100644 --- a/lib/trino-matching/src/test/java/io/trino/matching/TestMatcher.java +++ b/lib/trino-matching/src/test/java/io/trino/matching/TestMatcher.java @@ -36,11 +36,8 @@ import static io.trino.matching.example.rel.Patterns.scan; import static io.trino.matching.example.rel.Patterns.source; import static io.trino.matching.example.rel.Patterns.tableName; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.expectThrows; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; public class TestMatcher { @@ -146,9 +143,9 @@ public void capturingMatchesInATypesafeManner() Match match = assertMatch(pattern, tree); //notice the concrete type despite no casts: FilterNode capturedFilter = match.capture(filter); - assertEquals(tree.getSource(), capturedFilter); - assertEquals(((FilterNode) tree.getSource()).getSource(), match.capture(scan)); - assertEquals("orders", match.capture(name)); + assertThat(tree.getSource()).isEqualTo(capturedFilter); + assertThat(((FilterNode) tree.getSource()).getSource()).isEqualTo(match.capture(scan)); + assertThat("orders").isEqualTo(match.capture(name)); } @Test @@ -160,7 +157,7 @@ public void noMatch() Optional match = pattern.match(42) .collect(toOptional()); - assertFalse(match.isPresent()); + assertThat(match.isPresent()).isFalse(); } @Test @@ -172,8 +169,9 @@ public void unknownCaptureIsAnError() Match match = pattern.match(42) .collect(onlyElement()); - Throwable throwable = expectThrows(NoSuchElementException.class, () -> match.capture(unknownCapture)); - assertTrue(throwable.getMessage().contains("unknown Capture")); + assertThatThrownBy(() -> match.capture(unknownCapture)) + .isInstanceOf(NoSuchElementException.class) + .hasMessageContaining("unknown Capture"); } @Test @@ -195,9 +193,9 @@ public void contextIsPassedToPropertyFunction() }).equalTo(true)); AtomicBoolean wasContextUsed = new AtomicBoolean(); - assertNotNull(pattern.match("object", wasContextUsed) - .collect(onlyElement())); - assertEquals(wasContextUsed.get(), true); + assertThat(pattern.match("object", wasContextUsed) + .collect(onlyElement())).isNotNull(); + assertThat(wasContextUsed.get()).isEqualTo(true); } @Test @@ -210,9 +208,9 @@ public void contextIsPassedToPredicate() }); AtomicBoolean wasContextUsed = new AtomicBoolean(); - assertNotNull(pattern.match("object", wasContextUsed) - .collect(onlyElement())); - assertEquals(wasContextUsed.get(), true); + assertThat(pattern.match("object", wasContextUsed) + .collect(onlyElement())).isNotNull(); + assertThat(wasContextUsed.get()).isEqualTo(true); } private Match assertMatch(Pattern pattern, T object) @@ -225,6 +223,6 @@ private void assertNoMatch(Pattern pattern, Object expectedNoMatch) { Optional match = pattern.match(expectedNoMatch) .collect(toOptional()); - assertFalse(match.isPresent()); + assertThat(match.isPresent()).isFalse(); } } diff --git a/lib/trino-orc/src/test/java/io/trino/orc/AbstractTestOrcReader.java b/lib/trino-orc/src/test/java/io/trino/orc/AbstractTestOrcReader.java index 54202aa652aa..2870472966d8 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/AbstractTestOrcReader.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/AbstractTestOrcReader.java @@ -70,7 +70,7 @@ import static java.util.Collections.nCopies; import static java.util.UUID.randomUUID; import static java.util.stream.Collectors.toList; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public abstract class AbstractTestOrcReader { @@ -90,7 +90,7 @@ public AbstractTestOrcReader(OrcTester tester) { this.tester = tester; - assertEquals(DateTimeZone.getDefault(), HIVE_STORAGE_TIME_ZONE); + assertThat(DateTimeZone.getDefault()).isEqualTo(HIVE_STORAGE_TIME_ZONE); } @Test @@ -309,7 +309,7 @@ public void testTimestampMillis() .put("2001-09-10 12:04:16.789", SqlTimestamp.fromMillis(3, 1000123456789L)) .put("2019-12-05 13:41:39.564", SqlTimestamp.fromMillis(3, 1575553299564L)) .buildOrThrow(); - map.forEach((expected, value) -> assertEquals(value.toString(), expected)); + map.forEach((expected, value) -> assertThat(value.toString()).isEqualTo(expected)); tester.testRoundTrip(TIMESTAMP_MILLIS, newArrayList(limit(cycle(map.values()), 30_000))); } @@ -328,7 +328,7 @@ public void testTimeMicros() .put("20:43:39.822000", SqlTime.newInstance(6, 74619822000000000L)) .put("23:59:59.999000", SqlTime.newInstance(6, 86399999000000000L)) .buildOrThrow(); - map.forEach((expected, value) -> assertEquals(value.toString(), expected)); + map.forEach((expected, value) -> assertThat(value.toString()).isEqualTo(expected)); tester.testRoundTrip(TIME_MICROS, newArrayList(limit(cycle(map.values()), 30_000))); } @@ -345,7 +345,7 @@ public void testTimestampMicros() .put("2001-09-10 12:04:16.789123", SqlTimestamp.newInstance(6, 1000123456789123L, 0)) .put("2019-12-05 13:41:39.564321", SqlTimestamp.newInstance(6, 1575553299564321L, 0)) .buildOrThrow(); - map.forEach((expected, value) -> assertEquals(value.toString(), expected)); + map.forEach((expected, value) -> assertThat(value.toString()).isEqualTo(expected)); tester.testRoundTrip(TIMESTAMP_MICROS, newArrayList(limit(cycle(map.values()), 30_000))); } @@ -362,7 +362,7 @@ public void testTimestampNanos() .put("2001-09-10 12:04:16.789123456", SqlTimestamp.newInstance(9, 1000123456789123L, 456_000)) .put("2019-12-05 13:41:39.564321789", SqlTimestamp.newInstance(9, 1575553299564321L, 789_000)) .buildOrThrow(); - map.forEach((expected, value) -> assertEquals(value.toString(), expected)); + map.forEach((expected, value) -> assertThat(value.toString()).isEqualTo(expected)); tester.testRoundTrip(TIMESTAMP_NANOS, newArrayList(limit(cycle(map.values()), 30_000))); } @@ -379,7 +379,7 @@ public void testInstantMillis() .put("2001-09-10 12:04:16.789 UTC", SqlTimestampWithTimeZone.newInstance(3, 1000123456789L, 0, UTC_KEY)) .put("2019-12-05 13:41:39.564 UTC", SqlTimestampWithTimeZone.newInstance(3, 1575553299564L, 0, UTC_KEY)) .buildOrThrow(); - map.forEach((expected, value) -> assertEquals(value.toString(), expected)); + map.forEach((expected, value) -> assertThat(value.toString()).isEqualTo(expected)); tester.testRoundTrip(TIMESTAMP_TZ_MILLIS, newArrayList(limit(cycle(map.values()), 30_000))); } @@ -396,7 +396,7 @@ public void testInstantMicros() .put("2001-09-10 12:04:16.789123 UTC", SqlTimestampWithTimeZone.newInstance(6, 1000123456789L, 123_000_000, UTC_KEY)) .put("2019-12-05 13:41:39.564321 UTC", SqlTimestampWithTimeZone.newInstance(6, 1575553299564L, 321_000_000, UTC_KEY)) .buildOrThrow(); - map.forEach((expected, value) -> assertEquals(value.toString(), expected)); + map.forEach((expected, value) -> assertThat(value.toString()).isEqualTo(expected)); tester.testRoundTrip(TIMESTAMP_TZ_MICROS, newArrayList(limit(cycle(map.values()), 30_000))); } @@ -413,7 +413,7 @@ public void testInstantNanos() .put("2001-09-10 12:04:16.789123456 UTC", SqlTimestampWithTimeZone.newInstance(9, 1000123456789L, 123_456_000, UTC_KEY)) .put("2019-12-05 13:41:39.564321789 UTC", SqlTimestampWithTimeZone.newInstance(9, 1575553299564L, 321_789_000, UTC_KEY)) .buildOrThrow(); - map.forEach((expected, value) -> assertEquals(value.toString(), expected)); + map.forEach((expected, value) -> assertThat(value.toString()).isEqualTo(expected)); tester.testRoundTrip(TIMESTAMP_TZ_NANOS, newArrayList(limit(cycle(map.values()), 30_000))); } diff --git a/lib/trino-orc/src/test/java/io/trino/orc/OrcTester.java b/lib/trino-orc/src/test/java/io/trino/orc/OrcTester.java index bee0267389c2..5803bdd684f8 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/OrcTester.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/OrcTester.java @@ -197,10 +197,8 @@ import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaTimestampObjectInspector; import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaTimestampTZObjectInspector; import static org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory.getCharTypeInfo; +import static org.assertj.core.api.Assertions.assertThat; import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; public class OrcTester { @@ -482,7 +480,7 @@ private void assertRoundTrip(Type writeType, Type readType, List writeValues, } } - assertEquals(stats.getWriterSizeInBytes(), 0); + assertThat(stats.getWriterSizeInBytes()).isEqualTo(0); } private static void assertFileContentsTrino( @@ -494,8 +492,8 @@ private static void assertFileContentsTrino( throws IOException { try (OrcRecordReader recordReader = createCustomOrcRecordReader(tempFile, createOrcPredicate(type, expectedValues), type, MAX_BATCH_SIZE)) { - assertEquals(recordReader.getReaderPosition(), 0); - assertEquals(recordReader.getFilePosition(), 0); + assertThat(recordReader.getReaderPosition()).isEqualTo(0); + assertThat(recordReader.getFilePosition()).isEqualTo(0); boolean isFirst = true; int rowsProcessed = 0; @@ -503,10 +501,10 @@ private static void assertFileContentsTrino( for (Page page = recordReader.nextPage(); page != null; page = recordReader.nextPage()) { int batchSize = page.getPositionCount(); if (skipStripe && rowsProcessed < 10000) { - assertEquals(advance(iterator, batchSize), batchSize); + assertThat(advance(iterator, batchSize)).isEqualTo(batchSize); } else if (skipFirstBatch && isFirst) { - assertEquals(advance(iterator, batchSize), batchSize); + assertThat(advance(iterator, batchSize)).isEqualTo(batchSize); isFirst = false; } else { @@ -518,34 +516,34 @@ else if (skipFirstBatch && isFirst) { } for (int i = 0; i < batchSize; i++) { - assertTrue(iterator.hasNext()); + assertThat(iterator.hasNext()).isTrue(); Object expected = iterator.next(); Object actual = data.get(i); assertColumnValueEquals(type, actual, expected); } } - assertEquals(recordReader.getReaderPosition(), rowsProcessed); - assertEquals(recordReader.getFilePosition(), rowsProcessed); + assertThat(recordReader.getReaderPosition()).isEqualTo(rowsProcessed); + assertThat(recordReader.getFilePosition()).isEqualTo(rowsProcessed); rowsProcessed += batchSize; } - assertFalse(iterator.hasNext()); - assertNull(recordReader.nextPage()); + assertThat(iterator.hasNext()).isFalse(); + assertThat(recordReader.nextPage()).isNull(); - assertEquals(recordReader.getReaderPosition(), rowsProcessed); - assertEquals(recordReader.getFilePosition(), rowsProcessed); + assertThat(recordReader.getReaderPosition()).isEqualTo(rowsProcessed); + assertThat(recordReader.getFilePosition()).isEqualTo(rowsProcessed); } } private static void assertColumnValueEquals(Type type, Object actual, Object expected) { if (actual == null) { - assertNull(expected); + assertThat(expected).isNull(); return; } if (type instanceof ArrayType) { List actualArray = (List) actual; List expectedArray = (List) expected; - assertEquals(actualArray.size(), expectedArray.size()); + assertThat(actualArray.size()).isEqualTo(expectedArray.size()); Type elementType = type.getTypeParameters().get(0); for (int i = 0; i < actualArray.size(); i++) { @@ -557,7 +555,7 @@ private static void assertColumnValueEquals(Type type, Object actual, Object exp else if (type instanceof MapType) { Map actualMap = (Map) actual; Map expectedMap = (Map) expected; - assertEquals(actualMap.size(), expectedMap.size()); + assertThat(actualMap.size()).isEqualTo(expectedMap.size()); Type keyType = type.getTypeParameters().get(0); Type valueType = type.getTypeParameters().get(1); @@ -576,15 +574,17 @@ else if (type instanceof MapType) { } } } - assertTrue(expectedEntries.isEmpty(), "Unmatched entries " + expectedEntries); + assertThat(expectedEntries.isEmpty()) + .describedAs("Unmatched entries " + expectedEntries) + .isTrue(); } else if (type instanceof RowType) { List fieldTypes = type.getTypeParameters(); List actualRow = (List) actual; List expectedRow = (List) expected; - assertEquals(actualRow.size(), fieldTypes.size()); - assertEquals(actualRow.size(), expectedRow.size()); + assertThat(actualRow.size()).isEqualTo(fieldTypes.size()); + assertThat(actualRow.size()).isEqualTo(expectedRow.size()); for (int fieldId = 0; fieldId < actualRow.size(); fieldId++) { Type fieldType = fieldTypes.get(fieldId); @@ -597,7 +597,9 @@ else if (type.equals(DOUBLE)) { Double actualDouble = (Double) actual; Double expectedDouble = (Double) expected; if (actualDouble.isNaN()) { - assertTrue(expectedDouble.isNaN(), "expected double to be NaN"); + assertThat(expectedDouble.isNaN()) + .describedAs("expected double to be NaN") + .isTrue(); } else { assertEquals(actualDouble, expectedDouble, 0.001); @@ -605,10 +607,10 @@ else if (type.equals(DOUBLE)) { } else if (type.equals(UUID)) { UUID actualUUID = java.util.UUID.fromString((String) actual); - assertEquals(actualUUID, expected); + assertThat(actualUUID).isEqualTo(expected); } else if (!Objects.equals(actual, expected)) { - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } } @@ -619,8 +621,8 @@ static OrcRecordReader createCustomOrcRecordReader(TempFile tempFile, OrcPredica OrcReader orcReader = OrcReader.createOrcReader(orcDataSource, READER_OPTIONS) .orElseThrow(() -> new RuntimeException("File is empty")); - assertEquals(orcReader.getColumnNames(), ImmutableList.of("test")); - assertEquals(orcReader.getFooter().getRowsInRowGroup().orElse(0), 10_000); + assertThat(orcReader.getColumnNames()).isEqualTo(ImmutableList.of("test")); + assertThat(orcReader.getFooter().getRowsInRowGroup().orElse(0)).isEqualTo(10_000); return orcReader.createRecordReader( orcReader.getRootColumn().getNestedColumns(), @@ -855,7 +857,7 @@ private static void assertFileContentsOrcHive( actualValue = decodeRecordReaderValue(type, actualValue); assertColumnValueEquals(type, actualValue, expectedValue); } - assertFalse(iterator.hasNext()); + assertThat(iterator.hasNext()).isFalse(); } private static Object decodeRecordReaderValue(Type type, Object actualValue) diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestCachingOrcDataSource.java b/lib/trino-orc/src/test/java/io/trino/orc/TestCachingOrcDataSource.java index 246b49dcd841..15b17b8c3e76 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestCachingOrcDataSource.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestCachingOrcDataSource.java @@ -47,11 +47,10 @@ import static io.trino.orc.metadata.CompressionKind.ZLIB; import static io.trino.spi.type.VarcharType.VARCHAR; import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Fail.fail; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.fail; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -140,9 +139,9 @@ public void testTinyStripesReadCacheAt() maxMergeDistance, tinyStripeThreshold)); cachingOrcDataSource.readCacheAt(3); - assertEquals(testingOrcDataSource.getLastReadRanges(), ImmutableList.of(new DiskRange(3, 60))); + assertThat(testingOrcDataSource.getLastReadRanges()).isEqualTo(ImmutableList.of(new DiskRange(3, 60))); cachingOrcDataSource.readCacheAt(63); - assertEquals(testingOrcDataSource.getLastReadRanges(), ImmutableList.of(new DiskRange(63, 8 * 1048576))); + assertThat(testingOrcDataSource.getLastReadRanges()).isEqualTo(ImmutableList.of(new DiskRange(63, 8 * 1048576))); testingOrcDataSource = new TestingOrcDataSource(FakeOrcDataSource.INSTANCE); cachingOrcDataSource = new CachingOrcDataSource( @@ -152,9 +151,9 @@ public void testTinyStripesReadCacheAt() maxMergeDistance, tinyStripeThreshold)); cachingOrcDataSource.readCacheAt(62); // read at the end of a stripe - assertEquals(testingOrcDataSource.getLastReadRanges(), ImmutableList.of(new DiskRange(3, 60))); + assertThat(testingOrcDataSource.getLastReadRanges()).isEqualTo(ImmutableList.of(new DiskRange(3, 60))); cachingOrcDataSource.readCacheAt(63); - assertEquals(testingOrcDataSource.getLastReadRanges(), ImmutableList.of(new DiskRange(63, 8 * 1048576))); + assertThat(testingOrcDataSource.getLastReadRanges()).isEqualTo(ImmutableList.of(new DiskRange(63, 8 * 1048576))); testingOrcDataSource = new TestingOrcDataSource(FakeOrcDataSource.INSTANCE); cachingOrcDataSource = new CachingOrcDataSource( @@ -164,9 +163,9 @@ public void testTinyStripesReadCacheAt() maxMergeDistance, tinyStripeThreshold)); cachingOrcDataSource.readCacheAt(3); - assertEquals(testingOrcDataSource.getLastReadRanges(), ImmutableList.of(new DiskRange(3, 1 + 1048576 * 5))); + assertThat(testingOrcDataSource.getLastReadRanges()).isEqualTo(ImmutableList.of(new DiskRange(3, 1 + 1048576 * 5))); cachingOrcDataSource.readCacheAt(4 + 1048576 * 5); - assertEquals(testingOrcDataSource.getLastReadRanges(), ImmutableList.of(new DiskRange(4 + 1048576 * 5, 3 * 1048576))); + assertThat(testingOrcDataSource.getLastReadRanges()).isEqualTo(ImmutableList.of(new DiskRange(4 + 1048576 * 5, 3 * 1048576))); } @Test @@ -176,12 +175,12 @@ public void testIntegration() // tiny file TestingOrcDataSource orcDataSource = new TestingOrcDataSource(new FileOrcDataSource(tempFile.getFile(), READER_OPTIONS)); doIntegration(orcDataSource, DataSize.of(1, Unit.MEGABYTE), DataSize.of(1, Unit.MEGABYTE)); - assertEquals(orcDataSource.getReadCount(), 1); // read entire file at once + assertThat(orcDataSource.getReadCount()).isEqualTo(1); // read entire file at once // tiny stripes orcDataSource = new TestingOrcDataSource(new FileOrcDataSource(tempFile.getFile(), READER_OPTIONS)); doIntegration(orcDataSource, DataSize.of(400, Unit.KILOBYTE), DataSize.of(400, Unit.KILOBYTE)); - assertEquals(orcDataSource.getReadCount(), 3); // footer, first few stripes, last few stripes + assertThat(orcDataSource.getReadCount()).isEqualTo(3); // footer, first few stripes, last few stripes } private void doIntegration(TestingOrcDataSource orcDataSource, DataSize maxMergeDistance, DataSize tinyStripeThreshold) @@ -194,7 +193,7 @@ private void doIntegration(TestingOrcDataSource orcDataSource, DataSize maxMerge OrcReader orcReader = OrcReader.createOrcReader(orcDataSource, options) .orElseThrow(() -> new RuntimeException("File is empty")); // 1 for reading file footer - assertEquals(orcDataSource.getReadCount(), 1); + assertThat(orcDataSource.getReadCount()).isEqualTo(1); List stripes = orcReader.getFooter().getStripes(); // Sanity check number of stripes. This can be three or higher because of orc writer low memory mode. assertGreaterThanOrEqual(stripes.size(), 3); @@ -219,13 +218,17 @@ private void doIntegration(TestingOrcDataSource orcDataSource, DataSize maxMerge Block block = page.getBlock(0); positionCount += block.getPositionCount(); } - assertEquals(positionCount, POSITION_COUNT); + assertThat(positionCount).isEqualTo(POSITION_COUNT); } private static void assertNotInstanceOf(T actual, Class expectedType) { - assertNotNull(actual, "actual is null"); - assertNotNull(expectedType, "expectedType is null"); + assertThat(actual) + .describedAs("actual is null") + .isNotNull(); + assertThat(expectedType) + .describedAs("expectedType is null") + .isNotNull(); if (expectedType.isInstance(actual)) { fail(format("expected:<%s> to not be an instance of <%s>", actual, expectedType.getName())); } diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestDictionaryBuilder.java b/lib/trino-orc/src/test/java/io/trino/orc/TestDictionaryBuilder.java index c732e1db7e25..586e44ccaaa9 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestDictionaryBuilder.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestDictionaryBuilder.java @@ -23,7 +23,7 @@ import java.util.Set; import static io.airlift.slice.Slices.wrappedBuffer; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestDictionaryBuilder { @@ -36,6 +36,6 @@ public void testSkipReservedSlots() positions.add(dictionaryBuilder.putIfAbsent(new VariableWidthBlock(1, wrappedBuffer(new byte[] {1}), new int[] {0, 1}, Optional.of(new boolean[] {false})), 0)); positions.add(dictionaryBuilder.putIfAbsent(new VariableWidthBlock(1, wrappedBuffer(new byte[] {2}), new int[] {0, 1}, Optional.of(new boolean[] {false})), 0)); } - assertEquals(positions, ImmutableSet.of(1, 2)); + assertThat(positions).isEqualTo(ImmutableSet.of(1, 2)); } } diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestDictionaryCompressionOptimizer.java b/lib/trino-orc/src/test/java/io/trino/orc/TestDictionaryCompressionOptimizer.java index 695047b05247..cd1eec66f12b 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestDictionaryCompressionOptimizer.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestDictionaryCompressionOptimizer.java @@ -32,9 +32,7 @@ import static java.lang.Math.min; import static java.lang.Math.toIntExact; import static java.util.Objects.requireNonNull; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestDictionaryCompressionOptimizer { @@ -47,20 +45,20 @@ public void testNoDictionariesBytesLimit() DataSimulator simulator = new DataSimulator(0, stripeMaxBytes, expectedMaxRowCount * 2, megabytes(16), bytesPerRow); for (int loop = 0; loop < 3; loop++) { - assertFalse(simulator.isDictionaryMemoryFull()); - assertEquals(simulator.getRowCount(), 0); - assertEquals(simulator.getBufferedBytes(), 0); + assertThat(simulator.isDictionaryMemoryFull()).isFalse(); + assertThat(simulator.getRowCount()).isEqualTo(0); + assertThat(simulator.getBufferedBytes()).isEqualTo(0); simulator.advanceToNextStateChange(); // since there are no dictionary columns, the simulator should advance until the strip is full - assertFalse(simulator.isDictionaryMemoryFull()); + assertThat(simulator.isDictionaryMemoryFull()).isFalse(); assertGreaterThanOrEqual(simulator.getBufferedBytes(), (long) stripeMaxBytes); assertGreaterThanOrEqual(simulator.getRowCount(), expectedMaxRowCount); simulator.finalOptimize(); - assertFalse(simulator.isDictionaryMemoryFull()); + assertThat(simulator.isDictionaryMemoryFull()).isFalse(); assertGreaterThanOrEqual(simulator.getBufferedBytes(), (long) stripeMaxBytes); assertGreaterThanOrEqual(simulator.getRowCount(), expectedMaxRowCount); @@ -80,23 +78,23 @@ public void testSingleDictionaryColumnRowLimit() DataSimulator simulator = new DataSimulator(0, stripeMaxBytes, expectedMaxRowCount, megabytes(16), 0, column); for (int loop = 0; loop < 3; loop++) { - assertFalse(simulator.isDictionaryMemoryFull()); - assertFalse(column.isDirect()); - assertEquals(simulator.getRowCount(), 0); - assertEquals(simulator.getBufferedBytes(), 0); + assertThat(simulator.isDictionaryMemoryFull()).isFalse(); + assertThat(column.isDirect()).isFalse(); + assertThat(simulator.getRowCount()).isEqualTo(0); + assertThat(simulator.getBufferedBytes()).isEqualTo(0); simulator.advanceToNextStateChange(); // since there dictionary columns is only 1 MB, the simulator should advance until the strip is full - assertFalse(simulator.isDictionaryMemoryFull()); - assertFalse(column.isDirect()); + assertThat(simulator.isDictionaryMemoryFull()).isFalse(); + assertThat(column.isDirect()).isFalse(); assertLessThan(simulator.getBufferedBytes(), (long) stripeMaxBytes); assertGreaterThanOrEqual(simulator.getRowCount(), expectedMaxRowCount); simulator.finalOptimize(); - assertFalse(simulator.isDictionaryMemoryFull()); - assertFalse(column.isDirect()); + assertThat(simulator.isDictionaryMemoryFull()).isFalse(); + assertThat(column.isDirect()).isFalse(); assertLessThan(simulator.getBufferedBytes(), (long) stripeMaxBytes); assertGreaterThanOrEqual(simulator.getRowCount(), expectedMaxRowCount); @@ -118,23 +116,23 @@ public void testSingleDictionaryColumnByteLimit() DataSimulator simulator = new DataSimulator(0, stripeMaxBytes, expectedMaxRowCount * 10, megabytes(16), 0, column); for (int loop = 0; loop < 3; loop++) { - assertFalse(simulator.isDictionaryMemoryFull()); - assertFalse(column.isDirect()); - assertEquals(simulator.getRowCount(), 0); - assertEquals(simulator.getBufferedBytes(), 0); + assertThat(simulator.isDictionaryMemoryFull()).isFalse(); + assertThat(column.isDirect()).isFalse(); + assertThat(simulator.getRowCount()).isEqualTo(0); + assertThat(simulator.getBufferedBytes()).isEqualTo(0); simulator.advanceToNextStateChange(); // since there dictionary columns is only 1 MB, the simulator should advance until the strip is full - assertFalse(simulator.isDictionaryMemoryFull()); - assertFalse(column.isDirect()); + assertThat(simulator.isDictionaryMemoryFull()).isFalse(); + assertThat(column.isDirect()).isFalse(); assertGreaterThanOrEqual(simulator.getBufferedBytes(), (long) stripeMaxBytes); assertLessThan(simulator.getRowCount(), expectedMaxRowCount); simulator.finalOptimize(); - assertFalse(simulator.isDictionaryMemoryFull()); - assertFalse(column.isDirect()); + assertThat(simulator.isDictionaryMemoryFull()).isFalse(); + assertThat(column.isDirect()).isFalse(); assertGreaterThanOrEqual(simulator.getBufferedBytes(), (long) stripeMaxBytes); assertLessThan(simulator.getRowCount(), expectedMaxRowCount); @@ -157,23 +155,23 @@ public void testSingleDictionaryColumnMemoryLimit() DataSimulator simulator = new DataSimulator(0, stripeMaxBytes, expectedMaxRowCount * 2, dictionaryMaxMemoryBytes, 0, column); for (int loop = 0; loop < 3; loop++) { - assertFalse(simulator.isDictionaryMemoryFull()); - assertFalse(column.isDirect()); - assertEquals(simulator.getRowCount(), 0); - assertEquals(simulator.getBufferedBytes(), 0); + assertThat(simulator.isDictionaryMemoryFull()).isFalse(); + assertThat(column.isDirect()).isFalse(); + assertThat(simulator.getRowCount()).isEqualTo(0); + assertThat(simulator.getBufferedBytes()).isEqualTo(0); simulator.advanceToNextStateChange(); // the simulator should advance until memory is full - assertTrue(simulator.isDictionaryMemoryFull()); - assertFalse(column.isDirect()); + assertThat(simulator.isDictionaryMemoryFull()).isTrue(); + assertThat(column.isDirect()).isFalse(); assertLessThan(simulator.getBufferedBytes(), (long) stripeMaxBytes); assertGreaterThanOrEqual(simulator.getRowCount(), expectedMaxRowCount); simulator.finalOptimize(); - assertTrue(simulator.isDictionaryMemoryFull()); - assertFalse(column.isDirect()); + assertThat(simulator.isDictionaryMemoryFull()).isTrue(); + assertThat(column.isDirect()).isFalse(); assertLessThan(simulator.getBufferedBytes(), (long) stripeMaxBytes); assertGreaterThanOrEqual(simulator.getRowCount(), expectedMaxRowCount); @@ -196,31 +194,31 @@ public void testDirectConversionOnDictionaryFull() DataSimulator simulator = new DataSimulator(stripeMaxBytes / 2, stripeMaxBytes, expectedMaxRowCountAtFull * 2, dictionaryMaxMemoryBytes, 0, column); for (int loop = 0; loop < 3; loop++) { - assertFalse(simulator.isDictionaryMemoryFull()); - assertFalse(column.isDirect()); - assertEquals(simulator.getRowCount(), 0); - assertEquals(simulator.getBufferedBytes(), 0); + assertThat(simulator.isDictionaryMemoryFull()).isFalse(); + assertThat(column.isDirect()).isFalse(); + assertThat(simulator.getRowCount()).isEqualTo(0); + assertThat(simulator.getBufferedBytes()).isEqualTo(0); simulator.advanceToNextStateChange(); // the simulator should advance until the dictionary column is converted to direct - assertFalse(simulator.isDictionaryMemoryFull()); - assertTrue(column.isDirect()); + assertThat(simulator.isDictionaryMemoryFull()).isFalse(); + assertThat(column.isDirect()).isTrue(); assertLessThan(simulator.getBufferedBytes(), (long) stripeMaxBytes); assertGreaterThanOrEqual(simulator.getRowCount(), expectedRowCountAtFlip); simulator.advanceToNextStateChange(); // the simulator should advance until the stripe is full - assertFalse(simulator.isDictionaryMemoryFull()); - assertTrue(column.isDirect()); + assertThat(simulator.isDictionaryMemoryFull()).isFalse(); + assertThat(column.isDirect()).isTrue(); assertGreaterThanOrEqual(simulator.getBufferedBytes(), (long) stripeMaxBytes); assertGreaterThanOrEqual(simulator.getRowCount(), expectedMaxRowCountAtFull); simulator.finalOptimize(); - assertFalse(simulator.isDictionaryMemoryFull()); - assertTrue(column.isDirect()); + assertThat(simulator.isDictionaryMemoryFull()).isFalse(); + assertThat(column.isDirect()).isTrue(); assertGreaterThanOrEqual(simulator.getBufferedBytes(), (long) stripeMaxBytes); assertGreaterThanOrEqual(simulator.getRowCount(), expectedMaxRowCountAtFull); @@ -243,23 +241,23 @@ public void testNotDirectConversionOnDictionaryFull() DataSimulator simulator = new DataSimulator(stripeMaxBytes / 2, stripeMaxBytes, expectedMaxRowCount * 2, dictionaryMaxMemoryBytes, 0, column); for (int loop = 0; loop < 3; loop++) { - assertFalse(simulator.isDictionaryMemoryFull()); - assertFalse(column.isDirect()); - assertEquals(simulator.getRowCount(), 0); - assertEquals(simulator.getBufferedBytes(), 0); + assertThat(simulator.isDictionaryMemoryFull()).isFalse(); + assertThat(column.isDirect()).isFalse(); + assertThat(simulator.getRowCount()).isEqualTo(0); + assertThat(simulator.getBufferedBytes()).isEqualTo(0); simulator.advanceToNextStateChange(); // the simulator should advance until memory is full - assertTrue(simulator.isDictionaryMemoryFull()); - assertFalse(column.isDirect()); + assertThat(simulator.isDictionaryMemoryFull()).isTrue(); + assertThat(column.isDirect()).isFalse(); assertLessThan(simulator.getBufferedBytes(), (long) stripeMaxBytes); assertGreaterThanOrEqual(simulator.getRowCount(), expectedMaxRowCount); simulator.finalOptimize(); - assertTrue(simulator.isDictionaryMemoryFull()); - assertFalse(column.isDirect()); + assertThat(simulator.isDictionaryMemoryFull()).isTrue(); + assertThat(column.isDirect()).isFalse(); assertLessThan(simulator.getBufferedBytes(), (long) stripeMaxBytes); assertGreaterThanOrEqual(simulator.getRowCount(), expectedMaxRowCount); @@ -281,31 +279,31 @@ public void testSingleDirectBytesLimit() DataSimulator simulator = new DataSimulator(0, stripeMaxBytes, expectedMaxRowCountAtFull, dictionaryMaxMemoryBytes, 0, column); for (int loop = 0; loop < 3; loop++) { - assertFalse(simulator.isDictionaryMemoryFull()); - assertFalse(column.isDirect()); - assertEquals(simulator.getRowCount(), 0); - assertEquals(simulator.getBufferedBytes(), 0); + assertThat(simulator.isDictionaryMemoryFull()).isFalse(); + assertThat(column.isDirect()).isFalse(); + assertThat(simulator.getRowCount()).isEqualTo(0); + assertThat(simulator.getBufferedBytes()).isEqualTo(0); simulator.advanceToNextStateChange(); // the simulator should advance until the dictionary column is flipped - assertFalse(simulator.isDictionaryMemoryFull()); - assertTrue(column.isDirect()); + assertThat(simulator.isDictionaryMemoryFull()).isFalse(); + assertThat(column.isDirect()).isTrue(); assertLessThan(simulator.getBufferedBytes(), (long) stripeMaxBytes); assertGreaterThanOrEqual(simulator.getRowCount(), expectedRowCountAtFlip); simulator.advanceToNextStateChange(); // the simulator should advance until the stripe is full - assertFalse(simulator.isDictionaryMemoryFull()); - assertTrue(column.isDirect()); + assertThat(simulator.isDictionaryMemoryFull()).isFalse(); + assertThat(column.isDirect()).isTrue(); assertGreaterThanOrEqual(simulator.getBufferedBytes(), (long) stripeMaxBytes); assertGreaterThanOrEqual(simulator.getRowCount(), expectedMaxRowCountAtFull); simulator.finalOptimize(); - assertFalse(simulator.isDictionaryMemoryFull()); - assertTrue(column.isDirect()); + assertThat(simulator.isDictionaryMemoryFull()).isFalse(); + assertThat(column.isDirect()).isTrue(); assertGreaterThanOrEqual(simulator.getBufferedBytes(), (long) stripeMaxBytes); assertGreaterThanOrEqual(simulator.getRowCount(), expectedMaxRowCountAtFull); @@ -329,35 +327,35 @@ public void testDictionaryAndDirectBytesLimit() DataSimulator simulator = new DataSimulator(stripeMaxBytes / 2, stripeMaxBytes, expectedMaxRowCountAtFull * 2, dictionaryMaxMemoryBytes, 0, directColumn, dictionaryColumn); for (int loop = 0; loop < 3; loop++) { - assertFalse(simulator.isDictionaryMemoryFull()); - assertFalse(directColumn.isDirect()); - assertFalse(dictionaryColumn.isDirect()); - assertEquals(simulator.getRowCount(), 0); - assertEquals(simulator.getBufferedBytes(), 0); + assertThat(simulator.isDictionaryMemoryFull()).isFalse(); + assertThat(directColumn.isDirect()).isFalse(); + assertThat(dictionaryColumn.isDirect()).isFalse(); + assertThat(simulator.getRowCount()).isEqualTo(0); + assertThat(simulator.getBufferedBytes()).isEqualTo(0); simulator.advanceToNextStateChange(); // the simulator should advance until the dictionary column is flipped - assertFalse(simulator.isDictionaryMemoryFull()); - assertTrue(directColumn.isDirect()); - assertFalse(dictionaryColumn.isDirect()); + assertThat(simulator.isDictionaryMemoryFull()).isFalse(); + assertThat(directColumn.isDirect()).isTrue(); + assertThat(dictionaryColumn.isDirect()).isFalse(); assertLessThan(simulator.getBufferedBytes(), (long) stripeMaxBytes); assertGreaterThanOrEqual(simulator.getRowCount(), expectedRowCountAtFlip); simulator.advanceToNextStateChange(); // the simulator should advance until the stripe is full - assertFalse(simulator.isDictionaryMemoryFull()); - assertTrue(directColumn.isDirect()); - assertFalse(dictionaryColumn.isDirect()); + assertThat(simulator.isDictionaryMemoryFull()).isFalse(); + assertThat(directColumn.isDirect()).isTrue(); + assertThat(dictionaryColumn.isDirect()).isFalse(); assertGreaterThanOrEqual(simulator.getBufferedBytes(), (long) stripeMaxBytes); assertGreaterThanOrEqual(simulator.getRowCount(), expectedMaxRowCountAtFull); simulator.finalOptimize(); - assertFalse(simulator.isDictionaryMemoryFull()); - assertTrue(directColumn.isDirect()); - assertFalse(dictionaryColumn.isDirect()); + assertThat(simulator.isDictionaryMemoryFull()).isFalse(); + assertThat(directColumn.isDirect()).isTrue(); + assertThat(dictionaryColumn.isDirect()).isFalse(); assertGreaterThanOrEqual(simulator.getBufferedBytes(), (long) stripeMaxBytes); assertGreaterThanOrEqual(simulator.getRowCount(), expectedMaxRowCountAtFull); @@ -385,35 +383,35 @@ public void testWideDictionaryAndNarrowDirectBytesLimit() DataSimulator simulator = new DataSimulator(0, stripeMaxBytes, maxRowCount, dictionaryMaxMemoryBytes, 0, directColumn, dictionaryColumn); for (int loop = 0; loop < 3; loop++) { - assertFalse(simulator.isDictionaryMemoryFull()); - assertFalse(directColumn.isDirect()); - assertFalse(dictionaryColumn.isDirect()); - assertEquals(simulator.getRowCount(), 0); - assertEquals(simulator.getBufferedBytes(), 0); + assertThat(simulator.isDictionaryMemoryFull()).isFalse(); + assertThat(directColumn.isDirect()).isFalse(); + assertThat(dictionaryColumn.isDirect()).isFalse(); + assertThat(simulator.getRowCount()).isEqualTo(0); + assertThat(simulator.getBufferedBytes()).isEqualTo(0); simulator.advanceToNextStateChange(); // the simulator should advance until the dictionary column is flipped - assertFalse(simulator.isDictionaryMemoryFull()); - assertTrue(directColumn.isDirect()); - assertFalse(dictionaryColumn.isDirect()); + assertThat(simulator.isDictionaryMemoryFull()).isFalse(); + assertThat(directColumn.isDirect()).isTrue(); + assertThat(dictionaryColumn.isDirect()).isFalse(); assertLessThan(simulator.getBufferedBytes(), (long) stripeMaxBytes); assertGreaterThanOrEqual(simulator.getRowCount(), expectedRowCountAtFlip); simulator.advanceToNextStateChange(); // the simulator should advance until the stripe is full - assertFalse(simulator.isDictionaryMemoryFull()); - assertTrue(directColumn.isDirect()); - assertFalse(dictionaryColumn.isDirect()); + assertThat(simulator.isDictionaryMemoryFull()).isFalse(); + assertThat(directColumn.isDirect()).isTrue(); + assertThat(dictionaryColumn.isDirect()).isFalse(); assertLessThan(simulator.getBufferedBytes(), (long) stripeMaxBytes); assertGreaterThanOrEqual(simulator.getRowCount(), maxRowCount); simulator.finalOptimize(); - assertFalse(simulator.isDictionaryMemoryFull()); - assertTrue(directColumn.isDirect()); - assertFalse(dictionaryColumn.isDirect()); + assertThat(simulator.isDictionaryMemoryFull()).isFalse(); + assertThat(directColumn.isDirect()).isTrue(); + assertThat(dictionaryColumn.isDirect()).isFalse(); assertLessThan(simulator.getBufferedBytes(), (long) stripeMaxBytes); assertGreaterThanOrEqual(simulator.getRowCount(), maxRowCount); @@ -561,7 +559,7 @@ public void reset() public void advanceTo(int rowCount) { - assertTrue(rowCount >= this.rowCount); + assertThat(rowCount >= this.rowCount).isTrue(); this.rowCount = rowCount; } @@ -616,7 +614,7 @@ public int getIndexBytes() @Override public OptionalInt tryConvertToDirect(int maxDirectBytes) { - assertFalse(direct); + assertThat(direct).isFalse(); long directBytes = (long) (rowCount * valuesPerRow * bytesPerEntry); if (directBytes <= maxDirectBytes) { direct = true; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcBloomFilters.java b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcBloomFilters.java index 2649043b2c0d..6d47671b7e8d 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcBloomFilters.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcBloomFilters.java @@ -61,10 +61,8 @@ import static java.lang.Float.floatToIntBits; import static java.lang.Float.intBitsToFloat; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Fail.fail; public class TestOrcBloomFilters { @@ -92,28 +90,28 @@ public void testHiveBloomFilterSerde() // String bloomFilter.add(TEST_STRING); - assertTrue(bloomFilter.test(TEST_STRING)); - assertTrue(bloomFilter.testSlice(wrappedBuffer(TEST_STRING))); - assertFalse(bloomFilter.test(TEST_STRING_NOT_WRITTEN)); - assertFalse(bloomFilter.testSlice(wrappedBuffer(TEST_STRING_NOT_WRITTEN))); + assertThat(bloomFilter.test(TEST_STRING)).isTrue(); + assertThat(bloomFilter.testSlice(wrappedBuffer(TEST_STRING))).isTrue(); + assertThat(bloomFilter.test(TEST_STRING_NOT_WRITTEN)).isFalse(); + assertThat(bloomFilter.testSlice(wrappedBuffer(TEST_STRING_NOT_WRITTEN))).isFalse(); // Integer bloomFilter.addLong(TEST_INTEGER); - assertTrue(bloomFilter.testLong(TEST_INTEGER)); - assertFalse(bloomFilter.testLong(TEST_INTEGER + 1)); + assertThat(bloomFilter.testLong(TEST_INTEGER)).isTrue(); + assertThat(bloomFilter.testLong(TEST_INTEGER + 1)).isFalse(); // Re-construct BloomFilter newBloomFilter = new BloomFilter(bloomFilter.getBitSet(), bloomFilter.getNumHashFunctions()); // String - assertTrue(newBloomFilter.test(TEST_STRING)); - assertTrue(newBloomFilter.testSlice(wrappedBuffer(TEST_STRING))); - assertFalse(newBloomFilter.test(TEST_STRING_NOT_WRITTEN)); - assertFalse(newBloomFilter.testSlice(wrappedBuffer(TEST_STRING_NOT_WRITTEN))); + assertThat(newBloomFilter.test(TEST_STRING)).isTrue(); + assertThat(newBloomFilter.testSlice(wrappedBuffer(TEST_STRING))).isTrue(); + assertThat(newBloomFilter.test(TEST_STRING_NOT_WRITTEN)).isFalse(); + assertThat(newBloomFilter.testSlice(wrappedBuffer(TEST_STRING_NOT_WRITTEN))).isFalse(); // Integer - assertTrue(newBloomFilter.testLong(TEST_INTEGER)); - assertFalse(newBloomFilter.testLong(TEST_INTEGER + 1)); + assertThat(newBloomFilter.testLong(TEST_INTEGER)).isTrue(); + assertThat(newBloomFilter.testLong(TEST_INTEGER + 1)).isFalse(); } @Test @@ -123,8 +121,8 @@ public void testOrcHiveBloomFilterSerde() BloomFilter bloomFilterWrite = new BloomFilter(1000L, 0.05); bloomFilterWrite.add(TEST_STRING); - assertTrue(bloomFilterWrite.test(TEST_STRING)); - assertTrue(bloomFilterWrite.testSlice(wrappedBuffer(TEST_STRING))); + assertThat(bloomFilterWrite.test(TEST_STRING)).isTrue(); + assertThat(bloomFilterWrite.testSlice(wrappedBuffer(TEST_STRING))).isTrue(); Slice bloomFilterBytes = new CompressedMetadataWriter(new OrcMetadataWriter(WriterIdentification.TRINO), CompressionKind.NONE, 1024) .writeBloomFilters(ImmutableList.of(bloomFilterWrite)); @@ -134,35 +132,35 @@ public void testOrcHiveBloomFilterSerde() OrcMetadataReader metadataReader = new OrcMetadataReader(new OrcReaderOptions()); List bloomFilters = metadataReader.readBloomFilterIndexes(inputStream); - assertEquals(bloomFilters.size(), 1); + assertThat(bloomFilters.size()).isEqualTo(1); - assertTrue(bloomFilters.get(0).test(TEST_STRING)); - assertTrue(bloomFilters.get(0).testSlice(wrappedBuffer(TEST_STRING))); - assertFalse(bloomFilters.get(0).test(TEST_STRING_NOT_WRITTEN)); - assertFalse(bloomFilters.get(0).testSlice(wrappedBuffer(TEST_STRING_NOT_WRITTEN))); + assertThat(bloomFilters.get(0).test(TEST_STRING)).isTrue(); + assertThat(bloomFilters.get(0).testSlice(wrappedBuffer(TEST_STRING))).isTrue(); + assertThat(bloomFilters.get(0).test(TEST_STRING_NOT_WRITTEN)).isFalse(); + assertThat(bloomFilters.get(0).testSlice(wrappedBuffer(TEST_STRING_NOT_WRITTEN))).isFalse(); - assertEquals(bloomFilterWrite.getNumBits(), bloomFilters.get(0).getNumBits()); - assertEquals(bloomFilterWrite.getNumHashFunctions(), bloomFilters.get(0).getNumHashFunctions()); + assertThat(bloomFilterWrite.getNumBits()).isEqualTo(bloomFilters.get(0).getNumBits()); + assertThat(bloomFilterWrite.getNumHashFunctions()).isEqualTo(bloomFilters.get(0).getNumHashFunctions()); // Validate bit set - assertTrue(Arrays.equals(bloomFilters.get(0).getBitSet(), bloomFilterWrite.getBitSet())); + assertThat(Arrays.equals(bloomFilters.get(0).getBitSet(), bloomFilterWrite.getBitSet())).isTrue(); // Read directly: allows better inspection of the bit sets (helped to fix a lot of bugs) CodedInputStream input = CodedInputStream.newInstance(bloomFilterBytes.getBytes()); OrcProto.BloomFilterIndex deserializedBloomFilterIndex = OrcProto.BloomFilterIndex.parseFrom(input); List bloomFilterList = deserializedBloomFilterIndex.getBloomFilterList(); - assertEquals(bloomFilterList.size(), 1); + assertThat(bloomFilterList.size()).isEqualTo(1); OrcProto.BloomFilter bloomFilterRead = bloomFilterList.get(0); // Validate contents of ORC bloom filter bit set - assertTrue(Arrays.equals(Longs.toArray(bloomFilterRead.getBitsetList()), bloomFilterWrite.getBitSet())); + assertThat(Arrays.equals(Longs.toArray(bloomFilterRead.getBitsetList()), bloomFilterWrite.getBitSet())).isTrue(); // hash functions - assertEquals(bloomFilterWrite.getNumHashFunctions(), bloomFilterRead.getNumHashFunctions()); + assertThat(bloomFilterWrite.getNumHashFunctions()).isEqualTo(bloomFilterRead.getNumHashFunctions()); // bit size - assertEquals(bloomFilterWrite.getBitSet().length, bloomFilterRead.getBitsetCount()); + assertThat(bloomFilterWrite.getBitSet().length).isEqualTo(bloomFilterRead.getBitsetCount()); } @Test @@ -205,7 +203,9 @@ else if (o instanceof Double) { for (Map.Entry testValue : TEST_VALUES.entrySet()) { boolean matched = checkInBloomFilter(bloomFilter, testValue.getKey(), testValue.getValue()); - assertTrue(matched, "type " + testValue.getClass()); + assertThat(matched) + .describedAs("type " + testValue.getClass()) + .isTrue(); } } @@ -216,7 +216,9 @@ public void testBloomFilterPredicateValuesNonExisting() for (Map.Entry testValue : TEST_VALUES.entrySet()) { boolean matched = checkInBloomFilter(bloomFilter, testValue.getKey(), testValue.getValue()); - assertFalse(matched, "type " + testValue.getKey().getClass()); + assertThat(matched) + .describedAs("type " + testValue.getKey().getClass()) + .isFalse(); } } @@ -275,10 +277,10 @@ public void testMatches() null, null))); - assertTrue(predicate.matches(1L, matchingStatisticsByColumnIndex)); - assertTrue(predicate.matches(1L, withoutBloomFilterStatisticsByColumnIndex)); - assertFalse(predicate.matches(1L, nonMatchingStatisticsByColumnIndex)); - assertTrue(emptyPredicate.matches(1L, matchingStatisticsByColumnIndex)); + assertThat(predicate.matches(1L, matchingStatisticsByColumnIndex)).isTrue(); + assertThat(predicate.matches(1L, withoutBloomFilterStatisticsByColumnIndex)).isTrue(); + assertThat(predicate.matches(1L, nonMatchingStatisticsByColumnIndex)).isFalse(); + assertThat(emptyPredicate.matches(1L, matchingStatisticsByColumnIndex)).isTrue(); } @Test @@ -323,8 +325,8 @@ public void testMatchesExpandedRange() .addLong(9876L) .buildBloomFilter()))); - assertTrue(predicate.matches(1L, matchingStatisticsByColumnIndex)); - assertFalse(predicate.matches(1L, nonMatchingStatisticsByColumnIndex)); + assertThat(predicate.matches(1L, matchingStatisticsByColumnIndex)).isTrue(); + assertThat(predicate.matches(1L, nonMatchingStatisticsByColumnIndex)).isFalse(); } @Test @@ -352,8 +354,8 @@ public void testMatchesNonExpandedRange() .addColumn(ROOT_COLUMN, Domain.create(ValueSet.ofRanges(range), false)); // Domain expansion doesn't take place -> no bloom filtering -> ranges overlap - assertTrue(builder.setDomainCompactionThreshold(1).build().matches(1L, matchingStatisticsByColumnIndex)); - assertFalse(builder.setDomainCompactionThreshold(100).build().matches(1L, matchingStatisticsByColumnIndex)); + assertThat(builder.setDomainCompactionThreshold(1).build().matches(1L, matchingStatisticsByColumnIndex)).isTrue(); + assertThat(builder.setDomainCompactionThreshold(100).build().matches(1L, matchingStatisticsByColumnIndex)).isFalse(); } @Test @@ -367,8 +369,8 @@ public void testBloomFilterCompatibility() BloomFilter actual = new BloomFilter(size, fpp); io.trino.hive.orc.util.BloomFilter expected = new io.trino.hive.orc.util.BloomFilter(size, fpp); - assertFalse(actual.test(null)); - assertFalse(expected.test(null)); + assertThat(actual.test(null)).isFalse(); + assertThat(expected.test(null)).isFalse(); byte[][] binaryValue = new byte[entries][]; long[] longValue = new long[entries]; @@ -383,16 +385,16 @@ public void testBloomFilterCompatibility() } for (int i = 0; i < entries; i++) { - assertFalse(actual.test(binaryValue[i])); - assertFalse(actual.testSlice(wrappedBuffer(binaryValue[i]))); - assertFalse(actual.testLong(longValue[i])); - assertFalse(actual.testDouble(doubleValue[i])); - assertFalse(actual.testFloat(floatValue[i])); - - assertFalse(expected.test(binaryValue[i])); - assertFalse(expected.testLong(longValue[i])); - assertFalse(expected.testDouble(doubleValue[i])); - assertFalse(expected.testDouble(floatValue[i])); + assertThat(actual.test(binaryValue[i])).isFalse(); + assertThat(actual.testSlice(wrappedBuffer(binaryValue[i]))).isFalse(); + assertThat(actual.testLong(longValue[i])).isFalse(); + assertThat(actual.testDouble(doubleValue[i])).isFalse(); + assertThat(actual.testFloat(floatValue[i])).isFalse(); + + assertThat(expected.test(binaryValue[i])).isFalse(); + assertThat(expected.testLong(longValue[i])).isFalse(); + assertThat(expected.testDouble(doubleValue[i])).isFalse(); + assertThat(expected.testDouble(floatValue[i])).isFalse(); } for (int i = 0; i < entries; i++) { @@ -408,26 +410,26 @@ public void testBloomFilterCompatibility() } for (int i = 0; i < entries; i++) { - assertTrue(actual.test(binaryValue[i])); - assertTrue(actual.testSlice(wrappedBuffer(binaryValue[i]))); - assertTrue(actual.testLong(longValue[i])); - assertTrue(actual.testDouble(doubleValue[i])); - assertTrue(actual.testFloat(floatValue[i])); - - assertTrue(expected.test(binaryValue[i])); - assertTrue(expected.testLong(longValue[i])); - assertTrue(expected.testDouble(doubleValue[i])); - assertTrue(expected.testDouble(floatValue[i])); + assertThat(actual.test(binaryValue[i])).isTrue(); + assertThat(actual.testSlice(wrappedBuffer(binaryValue[i]))).isTrue(); + assertThat(actual.testLong(longValue[i])).isTrue(); + assertThat(actual.testDouble(doubleValue[i])).isTrue(); + assertThat(actual.testFloat(floatValue[i])).isTrue(); + + assertThat(expected.test(binaryValue[i])).isTrue(); + assertThat(expected.testLong(longValue[i])).isTrue(); + assertThat(expected.testDouble(doubleValue[i])).isTrue(); + assertThat(expected.testDouble(floatValue[i])).isTrue(); } actual.add((byte[]) null); expected.add(null); - assertTrue(actual.test(null)); - assertTrue(actual.testSlice(null)); - assertTrue(expected.test(null)); + assertThat(actual.test(null)).isTrue(); + assertThat(actual.testSlice(null)).isTrue(); + assertThat(expected.test(null)).isTrue(); - assertEquals(actual.getBitSet(), expected.getBitSet()); + assertThat(actual.getBitSet()).isEqualTo(expected.getBitSet()); } } @@ -437,7 +439,7 @@ public void testHashCompatibility() for (int length = 0; length < 1000; length++) { for (int i = 0; i < 100; i++) { byte[] bytes = randomBytes(length); - assertEquals(BloomFilter.OrcMurmur3.hash64(bytes), Murmur3.hash64(bytes)); + assertThat(BloomFilter.OrcMurmur3.hash64(bytes)).isEqualTo(Murmur3.hash64(bytes)); } } } diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcDataSourceUtils.java b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcDataSourceUtils.java index d7a41b9d41b6..d7407e3b5ee7 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcDataSourceUtils.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcDataSourceUtils.java @@ -21,7 +21,7 @@ import static io.airlift.units.DataSize.Unit.GIGABYTE; import static io.trino.orc.OrcDataSourceUtils.mergeAdjacentDiskRanges; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestOrcDataSourceUtils { @@ -32,7 +32,7 @@ public void testMergeSingle() ImmutableList.of(new DiskRange(100, 100)), DataSize.ofBytes(0), DataSize.ofBytes(0)); - assertEquals(diskRanges, ImmutableList.of(new DiskRange(100, 100))); + assertThat(diskRanges).isEqualTo(ImmutableList.of(new DiskRange(100, 100))); } @Test @@ -42,43 +42,39 @@ public void testMergeAdjacent() ImmutableList.of(new DiskRange(100, 100), new DiskRange(200, 100), new DiskRange(300, 100)), DataSize.ofBytes(0), DataSize.of(1, GIGABYTE)); - assertEquals(diskRanges, ImmutableList.of(new DiskRange(100, 300))); + assertThat(diskRanges).isEqualTo(ImmutableList.of(new DiskRange(100, 300))); } @Test public void testMergeGap() { List consistent10ByteGap = ImmutableList.of(new DiskRange(100, 90), new DiskRange(200, 90), new DiskRange(300, 90)); - assertEquals(mergeAdjacentDiskRanges(consistent10ByteGap, DataSize.ofBytes(0), DataSize.of(1, GIGABYTE)), consistent10ByteGap); - assertEquals(mergeAdjacentDiskRanges(consistent10ByteGap, DataSize.ofBytes(9), DataSize.of(1, GIGABYTE)), consistent10ByteGap); - assertEquals(mergeAdjacentDiskRanges(consistent10ByteGap, DataSize.ofBytes(10), DataSize.of(1, GIGABYTE)), ImmutableList.of(new DiskRange(100, 290))); - assertEquals(mergeAdjacentDiskRanges(consistent10ByteGap, DataSize.ofBytes(100), DataSize.of(1, GIGABYTE)), ImmutableList.of(new DiskRange(100, 290))); + assertThat(mergeAdjacentDiskRanges(consistent10ByteGap, DataSize.ofBytes(0), DataSize.of(1, GIGABYTE))).isEqualTo(consistent10ByteGap); + assertThat(mergeAdjacentDiskRanges(consistent10ByteGap, DataSize.ofBytes(9), DataSize.of(1, GIGABYTE))).isEqualTo(consistent10ByteGap); + assertThat(mergeAdjacentDiskRanges(consistent10ByteGap, DataSize.ofBytes(10), DataSize.of(1, GIGABYTE))).isEqualTo(ImmutableList.of(new DiskRange(100, 290))); + assertThat(mergeAdjacentDiskRanges(consistent10ByteGap, DataSize.ofBytes(100), DataSize.of(1, GIGABYTE))).isEqualTo(ImmutableList.of(new DiskRange(100, 290))); List middle10ByteGap = ImmutableList.of(new DiskRange(100, 80), new DiskRange(200, 90), new DiskRange(300, 80), new DiskRange(400, 90)); - assertEquals(mergeAdjacentDiskRanges(middle10ByteGap, DataSize.ofBytes(0), DataSize.of(1, GIGABYTE)), middle10ByteGap); - assertEquals(mergeAdjacentDiskRanges(middle10ByteGap, DataSize.ofBytes(9), DataSize.of(1, GIGABYTE)), middle10ByteGap); - assertEquals(mergeAdjacentDiskRanges(middle10ByteGap, DataSize.ofBytes(10), DataSize.of(1, GIGABYTE)), - ImmutableList.of(new DiskRange(100, 80), new DiskRange(200, 180), new DiskRange(400, 90))); - assertEquals(mergeAdjacentDiskRanges(middle10ByteGap, DataSize.ofBytes(100), DataSize.of(1, GIGABYTE)), ImmutableList.of(new DiskRange(100, 390))); + assertThat(mergeAdjacentDiskRanges(middle10ByteGap, DataSize.ofBytes(0), DataSize.of(1, GIGABYTE))).isEqualTo(middle10ByteGap); + assertThat(mergeAdjacentDiskRanges(middle10ByteGap, DataSize.ofBytes(9), DataSize.of(1, GIGABYTE))).isEqualTo(middle10ByteGap); + assertThat(mergeAdjacentDiskRanges(middle10ByteGap, DataSize.ofBytes(10), DataSize.of(1, GIGABYTE))).isEqualTo(ImmutableList.of(new DiskRange(100, 80), new DiskRange(200, 180), new DiskRange(400, 90))); + assertThat(mergeAdjacentDiskRanges(middle10ByteGap, DataSize.ofBytes(100), DataSize.of(1, GIGABYTE))).isEqualTo(ImmutableList.of(new DiskRange(100, 390))); } @Test public void testMergeMaxSize() { List consistent10ByteGap = ImmutableList.of(new DiskRange(100, 90), new DiskRange(200, 90), new DiskRange(300, 90)); - assertEquals(mergeAdjacentDiskRanges(consistent10ByteGap, DataSize.ofBytes(10), DataSize.ofBytes(0)), consistent10ByteGap); - assertEquals(mergeAdjacentDiskRanges(consistent10ByteGap, DataSize.ofBytes(10), DataSize.ofBytes(100)), consistent10ByteGap); - assertEquals(mergeAdjacentDiskRanges(consistent10ByteGap, DataSize.ofBytes(10), DataSize.ofBytes(190)), - ImmutableList.of(new DiskRange(100, 190), new DiskRange(300, 90))); - assertEquals(mergeAdjacentDiskRanges(consistent10ByteGap, DataSize.ofBytes(10), DataSize.ofBytes(200)), - ImmutableList.of(new DiskRange(100, 190), new DiskRange(300, 90))); - assertEquals(mergeAdjacentDiskRanges(consistent10ByteGap, DataSize.ofBytes(10), DataSize.ofBytes(290)), ImmutableList.of(new DiskRange(100, 290))); + assertThat(mergeAdjacentDiskRanges(consistent10ByteGap, DataSize.ofBytes(10), DataSize.ofBytes(0))).isEqualTo(consistent10ByteGap); + assertThat(mergeAdjacentDiskRanges(consistent10ByteGap, DataSize.ofBytes(10), DataSize.ofBytes(100))).isEqualTo(consistent10ByteGap); + assertThat(mergeAdjacentDiskRanges(consistent10ByteGap, DataSize.ofBytes(10), DataSize.ofBytes(190))).isEqualTo(ImmutableList.of(new DiskRange(100, 190), new DiskRange(300, 90))); + assertThat(mergeAdjacentDiskRanges(consistent10ByteGap, DataSize.ofBytes(10), DataSize.ofBytes(200))).isEqualTo(ImmutableList.of(new DiskRange(100, 190), new DiskRange(300, 90))); + assertThat(mergeAdjacentDiskRanges(consistent10ByteGap, DataSize.ofBytes(10), DataSize.ofBytes(290))).isEqualTo(ImmutableList.of(new DiskRange(100, 290))); List middle10ByteGap = ImmutableList.of(new DiskRange(100, 80), new DiskRange(200, 90), new DiskRange(300, 80), new DiskRange(400, 90)); - assertEquals(mergeAdjacentDiskRanges(middle10ByteGap, DataSize.ofBytes(0), DataSize.of(1, GIGABYTE)), middle10ByteGap); - assertEquals(mergeAdjacentDiskRanges(middle10ByteGap, DataSize.ofBytes(9), DataSize.of(1, GIGABYTE)), middle10ByteGap); - assertEquals(mergeAdjacentDiskRanges(middle10ByteGap, DataSize.ofBytes(10), DataSize.of(1, GIGABYTE)), - ImmutableList.of(new DiskRange(100, 80), new DiskRange(200, 180), new DiskRange(400, 90))); - assertEquals(mergeAdjacentDiskRanges(middle10ByteGap, DataSize.ofBytes(100), DataSize.of(1, GIGABYTE)), ImmutableList.of(new DiskRange(100, 390))); + assertThat(mergeAdjacentDiskRanges(middle10ByteGap, DataSize.ofBytes(0), DataSize.of(1, GIGABYTE))).isEqualTo(middle10ByteGap); + assertThat(mergeAdjacentDiskRanges(middle10ByteGap, DataSize.ofBytes(9), DataSize.of(1, GIGABYTE))).isEqualTo(middle10ByteGap); + assertThat(mergeAdjacentDiskRanges(middle10ByteGap, DataSize.ofBytes(10), DataSize.of(1, GIGABYTE))).isEqualTo(ImmutableList.of(new DiskRange(100, 80), new DiskRange(200, 180), new DiskRange(400, 90))); + assertThat(mergeAdjacentDiskRanges(middle10ByteGap, DataSize.ofBytes(100), DataSize.of(1, GIGABYTE))).isEqualTo(ImmutableList.of(new DiskRange(100, 390))); } } diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcLz4.java b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcLz4.java index 830d83ad9df1..2848b7dce103 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcLz4.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcLz4.java @@ -34,7 +34,7 @@ import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.IntegerType.INTEGER; import static java.nio.file.Files.readAllBytes; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestOrcLz4 { @@ -54,8 +54,8 @@ private void testReadLz4(byte[] data) OrcReader orcReader = OrcReader.createOrcReader(new MemoryOrcDataSource(new OrcDataSourceId("memory"), Slices.wrappedBuffer(data)), new OrcReaderOptions()) .orElseThrow(() -> new RuntimeException("File is empty")); - assertEquals(orcReader.getCompressionKind(), LZ4); - assertEquals(orcReader.getFooter().getNumberOfRows(), POSITION_COUNT); + assertThat(orcReader.getCompressionKind()).isEqualTo(LZ4); + assertThat(orcReader.getFooter().getNumberOfRows()).isEqualTo(POSITION_COUNT); try (OrcRecordReader reader = orcReader.createRecordReader( orcReader.getRootColumn().getNestedColumns(), @@ -85,7 +85,7 @@ private void testReadLz4(byte[] data) } } - assertEquals(rows, reader.getFileRowCount()); + assertThat(rows).isEqualTo(reader.getFileRowCount()); } } diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcOutputBuffer.java b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcOutputBuffer.java index eda0a882705f..bd57557c802d 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcOutputBuffer.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcOutputBuffer.java @@ -20,7 +20,7 @@ import java.util.Arrays; import static io.airlift.slice.Slices.wrappedBuffer; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestOrcOutputBuffer { @@ -34,14 +34,14 @@ public void testWriteHugeByteChucks() DynamicSliceOutput output = new DynamicSliceOutput(size); sliceOutput.writeBytes(largeByteArray, 10, size - 10); - assertEquals(sliceOutput.writeDataTo(output), size - 10); - assertEquals(output.slice(), wrappedBuffer(largeByteArray, 10, size - 10)); + assertThat(sliceOutput.writeDataTo(output)).isEqualTo(size - 10); + assertThat(output.slice()).isEqualTo(wrappedBuffer(largeByteArray, 10, size - 10)); sliceOutput.reset(); output.reset(); sliceOutput.writeBytes(wrappedBuffer(largeByteArray), 100, size - 100); - assertEquals(sliceOutput.writeDataTo(output), size - 100); - assertEquals(output.slice(), wrappedBuffer(largeByteArray, 100, size - 100)); + assertThat(sliceOutput.writeDataTo(output)).isEqualTo(size - 100); + assertThat(output.slice()).isEqualTo(wrappedBuffer(largeByteArray, 100, size - 100)); } @Test @@ -52,27 +52,27 @@ public void testGrowCapacity() // write some data that can fit the initial capacity = 256 sliceOutput.writeBytes(largeByteArray, 0, 200); - assertEquals(sliceOutput.getBufferCapacity(), 256); + assertThat(sliceOutput.getBufferCapacity()).isEqualTo(256); // write some more data to exceed the capacity = 256; the capacity will double sliceOutput.writeBytes(largeByteArray, 0, 200); - assertEquals(sliceOutput.getBufferCapacity(), 512); + assertThat(sliceOutput.getBufferCapacity()).isEqualTo(512); // write a lot more data to exceed twice the capacity = 512 X 2; the capacity will be the required data size sliceOutput.writeBytes(largeByteArray, 0, 1200); - assertEquals(sliceOutput.getBufferCapacity(), 1200); + assertThat(sliceOutput.getBufferCapacity()).isEqualTo(1200); // write some more data to double the capacity again sliceOutput.writeBytes(largeByteArray, 0, 2000); - assertEquals(sliceOutput.getBufferCapacity(), 2400); + assertThat(sliceOutput.getBufferCapacity()).isEqualTo(2400); // make the buffer to reach the max buffer capacity sliceOutput.writeBytes(largeByteArray, 0, 2500); - assertEquals(sliceOutput.getBufferCapacity(), 3000); + assertThat(sliceOutput.getBufferCapacity()).isEqualTo(3000); // make sure we didn't miss anything DynamicSliceOutput output = new DynamicSliceOutput(6000); sliceOutput.close(); - assertEquals(sliceOutput.writeDataTo(output), 200 + 200 + 1200 + 2000 + 2500); + assertThat(sliceOutput.writeDataTo(output)).isEqualTo(200 + 200 + 1200 + 2000 + 2500); } } diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcReaderMemoryUsage.java b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcReaderMemoryUsage.java index 1e7453407a71..a7f5d3b85fd5 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcReaderMemoryUsage.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcReaderMemoryUsage.java @@ -42,7 +42,7 @@ import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestOrcReaderMemoryUsage { @@ -122,7 +122,7 @@ public void testBigIntTypeWithNulls() // StripeReader memory should increase after reading a block. assertGreaterThan(reader.getCurrentStripeRetainedSizeInBytes(), stripeReaderRetainedSize); // There are no local buffers needed. - assertEquals(reader.getStreamReaderRetainedSizeInBytes() - streamReaderRetainedSize, 0L); + assertThat(reader.getStreamReaderRetainedSizeInBytes() - streamReaderRetainedSize).isEqualTo(0L); // The total retained size and memory usage should be strictly larger than 0L because of the instance sizes. assertGreaterThan(reader.getRetainedSizeInBytes() - readerRetainedSize, 0L); assertGreaterThan(reader.getMemoryUsage() - readerMemoryUsage, 0L); @@ -169,7 +169,7 @@ public void testMapTypeWithNulls() // StripeReader memory should increase after reading a block. assertGreaterThan(reader.getCurrentStripeRetainedSizeInBytes(), stripeReaderRetainedSize); // There are no local buffers needed. - assertEquals(reader.getStreamReaderRetainedSizeInBytes() - streamReaderRetainedSize, 0L); + assertThat(reader.getStreamReaderRetainedSizeInBytes() - streamReaderRetainedSize).isEqualTo(0L); // The total retained size and memory usage should be strictly larger than 0L because of the instance sizes. assertGreaterThan(reader.getRetainedSizeInBytes() - readerRetainedSize, 0L); assertGreaterThan(reader.getMemoryUsage() - readerMemoryUsage, 0L); @@ -271,23 +271,23 @@ private static TempFile createSingleColumnMapFileWithNullValues(Type mapType, in private static void assertInitialRetainedSizes(OrcRecordReader reader, int rows) { - assertEquals(reader.getReaderRowCount(), rows); - assertEquals(reader.getReaderPosition(), 0); - assertEquals(reader.getCurrentStripeRetainedSizeInBytes(), 0); + assertThat(reader.getReaderRowCount()).isEqualTo(rows); + assertThat(reader.getReaderPosition()).isEqualTo(0); + assertThat(reader.getCurrentStripeRetainedSizeInBytes()).isEqualTo(0); // there will be object overheads assertGreaterThan(reader.getStreamReaderRetainedSizeInBytes(), 0L); // there will be object overheads assertGreaterThan(reader.getRetainedSizeInBytes(), 0L); - assertEquals(reader.getMemoryUsage(), 0); + assertThat(reader.getMemoryUsage()).isEqualTo(0); } private static void assertClosedRetainedSizes(OrcRecordReader reader) { - assertEquals(reader.getCurrentStripeRetainedSizeInBytes(), 0); + assertThat(reader.getCurrentStripeRetainedSizeInBytes()).isEqualTo(0); // after close() we still account for the StreamReader instance sizes. assertGreaterThan(reader.getStreamReaderRetainedSizeInBytes(), 0L); // after close() we still account for the StreamReader instance sizes. assertGreaterThan(reader.getRetainedSizeInBytes(), 0L); - assertEquals(reader.getMemoryUsage(), 0); + assertThat(reader.getMemoryUsage()).isEqualTo(0); } } diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcReaderPositions.java b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcReaderPositions.java index afa7f0f27891..7c0ae4650258 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcReaderPositions.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcReaderPositions.java @@ -56,10 +56,8 @@ import static java.lang.Math.min; import static java.nio.charset.StandardCharsets.UTF_8; import static org.apache.hadoop.hive.ql.io.orc.CompressionKind.SNAPPY; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Fail.fail; public class TestOrcReaderPositions { @@ -71,22 +69,22 @@ public void testEntireFile() createMultiStripeFile(tempFile.getFile()); try (OrcRecordReader reader = createCustomOrcRecordReader(tempFile, OrcPredicate.TRUE, BIGINT, MAX_BATCH_SIZE)) { - assertEquals(reader.getReaderRowCount(), 100); - assertEquals(reader.getReaderPosition(), 0); - assertEquals(reader.getFileRowCount(), reader.getReaderRowCount()); - assertEquals(reader.getFilePosition(), reader.getReaderPosition()); + assertThat(reader.getReaderRowCount()).isEqualTo(100); + assertThat(reader.getReaderPosition()).isEqualTo(0); + assertThat(reader.getFileRowCount()).isEqualTo(reader.getReaderRowCount()); + assertThat(reader.getFilePosition()).isEqualTo(reader.getReaderPosition()); for (int i = 0; i < 5; i++) { Page page = reader.nextPage().getLoadedPage(); - assertEquals(page.getPositionCount(), 20); - assertEquals(reader.getReaderPosition(), i * 20L); - assertEquals(reader.getFilePosition(), reader.getReaderPosition()); + assertThat(page.getPositionCount()).isEqualTo(20); + assertThat(reader.getReaderPosition()).isEqualTo(i * 20L); + assertThat(reader.getFilePosition()).isEqualTo(reader.getReaderPosition()); assertCurrentBatch(page, i); } - assertNull(reader.nextPage()); - assertEquals(reader.getReaderPosition(), 100); - assertEquals(reader.getFilePosition(), reader.getReaderPosition()); + assertThat(reader.nextPage()).isNull(); + assertThat(reader.getReaderPosition()).isEqualTo(100); + assertThat(reader.getFilePosition()).isEqualTo(reader.getReaderPosition()); } } } @@ -109,29 +107,29 @@ public void testStripeSkipping() }; try (OrcRecordReader reader = createCustomOrcRecordReader(tempFile, predicate, BIGINT, MAX_BATCH_SIZE)) { - assertEquals(reader.getFileRowCount(), 100); - assertEquals(reader.getReaderRowCount(), 40); - assertEquals(reader.getFilePosition(), 0); - assertEquals(reader.getReaderPosition(), 0); + assertThat(reader.getFileRowCount()).isEqualTo(100); + assertThat(reader.getReaderRowCount()).isEqualTo(40); + assertThat(reader.getFilePosition()).isEqualTo(0); + assertThat(reader.getReaderPosition()).isEqualTo(0); // second stripe Page page = reader.nextPage().getLoadedPage(); - assertEquals(page.getPositionCount(), 20); - assertEquals(reader.getReaderPosition(), 0); - assertEquals(reader.getFilePosition(), 20); + assertThat(page.getPositionCount()).isEqualTo(20); + assertThat(reader.getReaderPosition()).isEqualTo(0); + assertThat(reader.getFilePosition()).isEqualTo(20); assertCurrentBatch(page, 1); // fourth stripe page = reader.nextPage().getLoadedPage(); - assertEquals(page.getPositionCount(), 20); - assertEquals(reader.getReaderPosition(), 20); - assertEquals(reader.getFilePosition(), 60); + assertThat(page.getPositionCount()).isEqualTo(20); + assertThat(reader.getReaderPosition()).isEqualTo(20); + assertThat(reader.getFilePosition()).isEqualTo(60); assertCurrentBatch(page, 3); page = reader.nextPage(); - assertNull(page); - assertEquals(reader.getReaderPosition(), 40); - assertEquals(reader.getFilePosition(), 100); + assertThat(page).isNull(); + assertThat(reader.getReaderPosition()).isEqualTo(40); + assertThat(reader.getFilePosition()).isEqualTo(100); } } } @@ -155,10 +153,10 @@ public void testRowGroupSkipping() }; try (OrcRecordReader reader = createCustomOrcRecordReader(tempFile, predicate, BIGINT, MAX_BATCH_SIZE)) { - assertEquals(reader.getFileRowCount(), rowCount); - assertEquals(reader.getReaderRowCount(), rowCount); - assertEquals(reader.getFilePosition(), 0); - assertEquals(reader.getReaderPosition(), 0); + assertThat(reader.getFileRowCount()).isEqualTo(rowCount); + assertThat(reader.getReaderRowCount()).isEqualTo(rowCount); + assertThat(reader.getFilePosition()).isEqualTo(0); + assertThat(reader.getReaderPosition()).isEqualTo(0); long position = 50_000; while (true) { @@ -170,17 +168,17 @@ public void testRowGroupSkipping() Block block = page.getBlock(0); for (int i = 0; i < block.getPositionCount(); i++) { - assertEquals(BIGINT.getLong(block, i), position + i); + assertThat(BIGINT.getLong(block, i)).isEqualTo(position + i); } - assertEquals(reader.getFilePosition(), position); - assertEquals(reader.getReaderPosition(), position); + assertThat(reader.getFilePosition()).isEqualTo(position); + assertThat(reader.getReaderPosition()).isEqualTo(position); position += page.getPositionCount(); } - assertEquals(position, 70_000); - assertEquals(reader.getFilePosition(), rowCount); - assertEquals(reader.getReaderPosition(), rowCount); + assertThat(position).isEqualTo(70_000); + assertThat(reader.getFilePosition()).isEqualTo(rowCount); + assertThat(reader.getReaderPosition()).isEqualTo(rowCount); } } } @@ -205,10 +203,10 @@ public void testBatchSizesForVariableWidth() createGrowingSequentialFile(tempFile.getFile(), rowCount, rowsInRowGroup, baseStringBytes); try (OrcRecordReader reader = createCustomOrcRecordReader(tempFile, OrcPredicate.TRUE, VARCHAR, MAX_BATCH_SIZE)) { - assertEquals(reader.getFileRowCount(), rowCount); - assertEquals(reader.getReaderRowCount(), rowCount); - assertEquals(reader.getFilePosition(), 0); - assertEquals(reader.getReaderPosition(), 0); + assertThat(reader.getFileRowCount()).isEqualTo(rowCount); + assertThat(reader.getReaderRowCount()).isEqualTo(rowCount); + assertThat(reader.getFilePosition()).isEqualTo(0); + assertThat(reader.getReaderPosition()).isEqualTo(0); // each value's length = original value length + 4 bytes to denote offset + 1 byte to denote if null int currentStringBytes = baseStringBytes + Integer.BYTES + Byte.BYTES; @@ -227,13 +225,13 @@ public void testBatchSizesForVariableWidth() // Either we are bounded by 1024 rows per batch, or it is the last batch in the row group // For the first 3 row groups, the strings are of length 300, 600, and 900 respectively // So the loaded data is bounded by MAX_BATCH_SIZE - assertTrue(block.getPositionCount() == MAX_BATCH_SIZE || rowCountsInCurrentRowGroup == rowsInRowGroup); + assertThat(block.getPositionCount() == MAX_BATCH_SIZE || rowCountsInCurrentRowGroup == rowsInRowGroup).isTrue(); } else { // Either we are bounded by 1MB per batch, or it is the last batch in the row group // From the 4th row group, the strings are have length > 1200 // So the loaded data is bounded by MAX_BLOCK_SIZE - assertTrue(block.getPositionCount() == READER_OPTIONS.getMaxBlockSize().toBytes() / currentStringBytes || rowCountsInCurrentRowGroup == rowsInRowGroup); + assertThat(block.getPositionCount() == READER_OPTIONS.getMaxBlockSize().toBytes() / currentStringBytes || rowCountsInCurrentRowGroup == rowsInRowGroup).isTrue(); } if (rowCountsInCurrentRowGroup == rowsInRowGroup) { @@ -263,10 +261,10 @@ public void testBatchSizesForFixedWidth() createSequentialFile(tempFile.getFile(), rowCount); try (OrcRecordReader reader = createCustomOrcRecordReader(tempFile, OrcPredicate.TRUE, BIGINT, MAX_BATCH_SIZE)) { - assertEquals(reader.getFileRowCount(), rowCount); - assertEquals(reader.getReaderRowCount(), rowCount); - assertEquals(reader.getFilePosition(), 0); - assertEquals(reader.getReaderPosition(), 0); + assertThat(reader.getFileRowCount()).isEqualTo(rowCount); + assertThat(reader.getReaderRowCount()).isEqualTo(rowCount); + assertThat(reader.getFilePosition()).isEqualTo(0); + assertThat(reader.getReaderPosition()).isEqualTo(0); int rowCountsInCurrentRowGroup = 0; while (true) { @@ -279,7 +277,7 @@ public void testBatchSizesForFixedWidth() Block block = page.getBlock(0); // 8 bytes per row; 1024 row at most given 1024 X 8B < 1MB - assertTrue(block.getPositionCount() == MAX_BATCH_SIZE || rowCountsInCurrentRowGroup == rowsInRowGroup); + assertThat(block.getPositionCount() == MAX_BATCH_SIZE || rowCountsInCurrentRowGroup == rowsInRowGroup).isTrue(); if (rowCountsInCurrentRowGroup == rowsInRowGroup) { rowCountsInCurrentRowGroup = 0; @@ -308,7 +306,7 @@ public void testReadUserMetadata() .orElseThrow(() -> new RuntimeException("File is empty")); Footer footer = orcReader.getFooter(); Map readMetadata = Maps.transformValues(footer.getUserMetadata(), Slice::toStringAscii); - assertEquals(readMetadata, metadata); + assertThat(readMetadata).isEqualTo(metadata); } } @@ -321,10 +319,10 @@ public void testBatchSizeGrowth() createMultiStripeFile(tempFile.getFile()); try (OrcRecordReader reader = createCustomOrcRecordReader(tempFile, OrcPredicate.TRUE, BIGINT, INITIAL_BATCH_SIZE)) { - assertEquals(reader.getReaderRowCount(), 100); - assertEquals(reader.getReaderPosition(), 0); - assertEquals(reader.getFileRowCount(), reader.getReaderRowCount()); - assertEquals(reader.getFilePosition(), reader.getReaderPosition()); + assertThat(reader.getReaderRowCount()).isEqualTo(100); + assertThat(reader.getReaderPosition()).isEqualTo(0); + assertThat(reader.getFileRowCount()).isEqualTo(reader.getReaderRowCount()); + assertThat(reader.getFilePosition()).isEqualTo(reader.getReaderPosition()); // The batch size should start from INITIAL_BATCH_SIZE and grow by BATCH_SIZE_GROWTH_FACTOR. // For INITIAL_BATCH_SIZE = 1 and BATCH_SIZE_GROWTH_FACTOR = 2, the batchSize sequence should be @@ -340,9 +338,9 @@ public void testBatchSizeGrowth() } page = page.getLoadedPage(); - assertEquals(page.getPositionCount(), expectedBatchSize); - assertEquals(reader.getReaderPosition(), totalReadRows); - assertEquals(reader.getFilePosition(), reader.getReaderPosition()); + assertThat(page.getPositionCount()).isEqualTo(expectedBatchSize); + assertThat(reader.getReaderPosition()).isEqualTo(totalReadRows); + assertThat(reader.getFilePosition()).isEqualTo(reader.getReaderPosition()); assertCurrentBatch(page, (int) reader.getReaderPosition(), page.getPositionCount()); if (nextBatchSize > 20 - rowCountsInCurrentRowGroup) { @@ -363,8 +361,8 @@ else if (rowCountsInCurrentRowGroup > 20) { expectedBatchSize = min(min(nextBatchSize, MAX_BATCH_SIZE), 20 - rowCountsInCurrentRowGroup); } - assertEquals(reader.getReaderPosition(), 100); - assertEquals(reader.getFilePosition(), reader.getReaderPosition()); + assertThat(reader.getReaderPosition()).isEqualTo(100); + assertThat(reader.getFilePosition()).isEqualTo(reader.getReaderPosition()); } } } @@ -373,7 +371,7 @@ private static void assertCurrentBatch(Page page, int rowIndex, int batchSize) { Block block = page.getBlock(0); for (int i = 0; i < batchSize; i++) { - assertEquals(BIGINT.getLong(block, i), (rowIndex + i) * 3L); + assertThat(BIGINT.getLong(block, i)).isEqualTo((rowIndex + i) * 3L); } } @@ -381,7 +379,7 @@ private static void assertCurrentBatch(Page page, int stripe) { Block block = page.getBlock(0); for (int i = 0; i < 20; i++) { - assertEquals(BIGINT.getLong(block, i), ((stripe * 20L) + i) * 3); + assertThat(BIGINT.getLong(block, i)).isEqualTo(((stripe * 20L) + i) * 3); } } diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcWithoutRowGroupInfo.java b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcWithoutRowGroupInfo.java index ebaaaab28f1e..e43eeb70356b 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcWithoutRowGroupInfo.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcWithoutRowGroupInfo.java @@ -32,7 +32,7 @@ import static io.trino.orc.OrcReader.createOrcReader; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.IntegerType.INTEGER; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestOrcWithoutRowGroupInfo { @@ -60,8 +60,8 @@ private void testAndVerifyResults(OrcPredicate orcPredicate) OrcReader orcReader = createOrcReader(new FileOrcDataSource(file, new OrcReaderOptions()), new OrcReaderOptions()).orElseThrow(); - assertEquals(orcReader.getFooter().getNumberOfRows(), 2); - assertEquals(orcReader.getFooter().getRowsInRowGroup(), OptionalInt.empty()); + assertThat(orcReader.getFooter().getNumberOfRows()).isEqualTo(2); + assertThat(orcReader.getFooter().getRowsInRowGroup()).isEqualTo(OptionalInt.empty()); RowType rowType = RowType.from(ImmutableList.of(RowType.field("a", BIGINT))); OrcRecordReader reader = orcReader.createRecordReader( @@ -90,6 +90,6 @@ private void testAndVerifyResults(OrcPredicate orcPredicate) } } - assertEquals(rows, reader.getFileRowCount()); + assertThat(rows).isEqualTo(reader.getFileRowCount()); } } diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcWriter.java b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcWriter.java index acfe70c95a2f..642ea510cc9b 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestOrcWriter.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestOrcWriter.java @@ -52,7 +52,7 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static java.lang.Math.toIntExact; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.testng.Assert.assertFalse; +import static org.assertj.core.api.Assertions.assertThat; public class TestOrcWriter { @@ -122,7 +122,7 @@ public void testWriteOutputStreamsInOrder() boolean dataStreamStarted = false; for (Stream stream : stripeFooter.getStreams()) { if (isIndexStream(stream)) { - assertFalse(dataStreamStarted); + assertThat(dataStreamStarted).isFalse(); continue; } dataStreamStarted = true; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestReadBloomFilter.java b/lib/trino-orc/src/test/java/io/trino/orc/TestReadBloomFilter.java index 049aeed87c06..c2aae84f0ab3 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestReadBloomFilter.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestReadBloomFilter.java @@ -49,8 +49,7 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static java.lang.Float.floatToIntBits; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; +import static org.assertj.core.api.Assertions.assertThat; public class TestReadBloomFilter { @@ -89,7 +88,7 @@ private static void testType(Type type, List uniqueValues, T inBloomFilte // without predicate a normal block will be created try (OrcRecordReader recordReader = createCustomOrcRecordReader(tempFile, OrcPredicate.TRUE, type, MAX_BATCH_SIZE)) { - assertEquals(recordReader.nextPage().getLoadedPage().getPositionCount(), MAX_BATCH_SIZE); + assertThat(recordReader.nextPage().getLoadedPage().getPositionCount()).isEqualTo(MAX_BATCH_SIZE); } // predicate for specific value within the min/max range without bloom filter being enabled @@ -98,7 +97,7 @@ private static void testType(Type type, List uniqueValues, T inBloomFilte .build(); try (OrcRecordReader recordReader = createCustomOrcRecordReader(tempFile, noBloomFilterPredicate, type, MAX_BATCH_SIZE)) { - assertEquals(recordReader.nextPage().getLoadedPage().getPositionCount(), MAX_BATCH_SIZE); + assertThat(recordReader.nextPage().getLoadedPage().getPositionCount()).isEqualTo(MAX_BATCH_SIZE); } // predicate for specific value within the min/max range with bloom filter enabled, but a value not in the bloom filter @@ -108,7 +107,7 @@ private static void testType(Type type, List uniqueValues, T inBloomFilte .build(); try (OrcRecordReader recordReader = createCustomOrcRecordReader(tempFile, notMatchBloomFilterPredicate, type, MAX_BATCH_SIZE)) { - assertNull(recordReader.nextPage()); + assertThat(recordReader.nextPage()).isNull(); } // predicate for specific value within the min/max range with bloom filter enabled, and a value in the bloom filter @@ -118,7 +117,7 @@ private static void testType(Type type, List uniqueValues, T inBloomFilte .build(); try (OrcRecordReader recordReader = createCustomOrcRecordReader(tempFile, matchBloomFilterPredicate, type, MAX_BATCH_SIZE)) { - assertEquals(recordReader.nextPage().getLoadedPage().getPositionCount(), MAX_BATCH_SIZE); + assertThat(recordReader.nextPage().getLoadedPage().getPositionCount()).isEqualTo(MAX_BATCH_SIZE); } } } @@ -130,8 +129,8 @@ private static OrcRecordReader createCustomOrcRecordReader(TempFile tempFile, Or OrcReader orcReader = OrcReader.createOrcReader(orcDataSource, READER_OPTIONS) .orElseThrow(() -> new RuntimeException("File is empty")); - assertEquals(orcReader.getColumnNames(), ImmutableList.of("test")); - assertEquals(orcReader.getFooter().getRowsInRowGroup().orElse(0), 10_000); + assertThat(orcReader.getColumnNames()).isEqualTo(ImmutableList.of("test")); + assertThat(orcReader.getFooter().getRowsInRowGroup().orElse(0)).isEqualTo(10_000); return orcReader.createRecordReader( orcReader.getRootColumn().getNestedColumns(), diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestSliceDictionaryColumnReader.java b/lib/trino-orc/src/test/java/io/trino/orc/TestSliceDictionaryColumnReader.java index d0374a72f4e7..2722912a4fb6 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestSliceDictionaryColumnReader.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestSliceDictionaryColumnReader.java @@ -37,8 +37,7 @@ import static java.nio.file.Files.readAllBytes; import static java.time.ZoneOffset.UTC; import static java.util.UUID.randomUUID; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestSliceDictionaryColumnReader { @@ -62,7 +61,7 @@ public void testDictionaryReaderUpdatesRetainedSize() .orElseThrow(() -> new RuntimeException("File is empty")); Footer footer = orcReader.getFooter(); List columns = orcReader.getRootColumn().getNestedColumns(); - assertTrue(columns.size() == 1); + assertThat(columns.size() == 1).isTrue(); StripeReader stripeReader = new StripeReader( dataSource, UTC, @@ -88,12 +87,12 @@ public void testDictionaryReaderUpdatesRetainedSize() columnReader.prepareNextRead(1000); columnReader.readBlock(); // memory usage check - assertEquals(memoryContext.getBytes(), columnReader.getRetainedSizeInBytes()); + assertThat(memoryContext.getBytes()).isEqualTo(columnReader.getRetainedSizeInBytes()); } } columnReader.close(); - assertTrue(memoryContext.getBytes() == 0); + assertThat(memoryContext.getBytes() == 0).isTrue(); } private List createValues() diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestSliceDictionaryColumnWriter.java b/lib/trino-orc/src/test/java/io/trino/orc/TestSliceDictionaryColumnWriter.java index 3d277cdcc37e..c00d117fe2e8 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestSliceDictionaryColumnWriter.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestSliceDictionaryColumnWriter.java @@ -51,8 +51,6 @@ import static java.lang.Math.toIntExact; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestSliceDictionaryColumnWriter { @@ -75,7 +73,7 @@ public void testDirectConversion() writer.writeBlock(data); writer.finishRowGroup(); - assertFalse(writer.tryConvertToDirect(megabytes(64)).isPresent()); + assertThat(writer.tryConvertToDirect(megabytes(64)).isPresent()).isFalse(); } @Test @@ -120,7 +118,7 @@ public void testBloomFiltersAfterConvertToDirect() boolean contains = metadataWriter.getBloomFilters().stream().anyMatch(filter -> filter.testSlice(testValue)); if (i % 9 == 0) { // No false negatives - assertTrue(contains); + assertThat(contains).isTrue(); } hits += contains ? 1 : 0; } diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestStructColumnReader.java b/lib/trino-orc/src/test/java/io/trino/orc/TestStructColumnReader.java index 0b5f52e8235b..9a42f19d8e66 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestStructColumnReader.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestStructColumnReader.java @@ -52,12 +52,11 @@ import static io.trino.orc.metadata.CompressionKind.NONE; import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.joda.time.DateTimeZone.UTC; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; @TestInstance(PER_METHOD) @Execution(SAME_THREAD) @@ -99,10 +98,10 @@ public void testValuesAreReadInCorrectly() RowBlock readBlock = read(tempFile, readerType); List actual = (List) readerType.getObjectValue(TestingConnectorSession.SESSION, readBlock, 0); - assertEquals(actual.size(), readerFields.size()); - assertEquals(actual.get(0), "field_a_value"); - assertEquals(actual.get(1), "field_b_value"); - assertEquals(actual.get(2), "field_c_value"); + assertThat(actual.size()).isEqualTo(readerFields.size()); + assertThat(actual.get(0)).isEqualTo("field_a_value"); + assertThat(actual.get(1)).isEqualTo("field_b_value"); + assertThat(actual.get(2)).isEqualTo("field_c_value"); } /** @@ -122,10 +121,10 @@ public void testReaderLowerCasesFieldNamesFromStream() RowBlock readBlock = read(tempFile, readerType); List actual = (List) readerType.getObjectValue(TestingConnectorSession.SESSION, readBlock, 0); - assertEquals(actual.size(), readerFields.size()); - assertEquals(actual.get(0), "fieldAValue"); - assertEquals(actual.get(1), "fieldBValue"); - assertEquals(actual.get(2), "fieldCValue"); + assertThat(actual.size()).isEqualTo(readerFields.size()); + assertThat(actual.get(0)).isEqualTo("fieldAValue"); + assertThat(actual.get(1)).isEqualTo("fieldBValue"); + assertThat(actual.get(2)).isEqualTo("fieldCValue"); } /** @@ -145,10 +144,10 @@ public void testReaderLowerCasesFieldNamesFromType() RowBlock readBlock = read(tempFile, readerType); List actual = (List) readerType.getObjectValue(TestingConnectorSession.SESSION, readBlock, 0); - assertEquals(actual.size(), readerFields.size()); - assertEquals(actual.get(0), "fieldAValue"); - assertEquals(actual.get(1), "fieldBValue"); - assertEquals(actual.get(2), "fieldCValue"); + assertThat(actual.size()).isEqualTo(readerFields.size()); + assertThat(actual.get(0)).isEqualTo("fieldAValue"); + assertThat(actual.get(1)).isEqualTo("fieldBValue"); + assertThat(actual.get(2)).isEqualTo("fieldCValue"); } @Test @@ -187,10 +186,10 @@ public void testExtraFieldsInReader() RowBlock readBlock = read(tempFile, readerType); List actual = (List) readerType.getObjectValue(TestingConnectorSession.SESSION, readBlock, 0); - assertEquals(actual.size(), readerFields.size()); - assertEquals(actual.get(0), "field_a_value"); - assertNull(actual.get(1)); - assertEquals(actual.get(2), "field_c_value"); + assertThat(actual.size()).isEqualTo(readerFields.size()); + assertThat(actual.get(0)).isEqualTo("field_a_value"); + assertThat(actual.get(1)).isNull(); + assertThat(actual.get(2)).isEqualTo("field_c_value"); } /** @@ -211,9 +210,9 @@ public void testExtraFieldsInWriter() RowBlock readBlock = read(tempFile, readerType); List actual = (List) readerType.getObjectValue(TestingConnectorSession.SESSION, readBlock, 0); - assertEquals(actual.size(), readerFields.size()); - assertEquals(actual.get(0), "field_a_value"); - assertEquals(actual.get(1), "field_c_value"); + assertThat(actual.size()).isEqualTo(readerFields.size()); + assertThat(actual.get(0)).isEqualTo("field_a_value"); + assertThat(actual.get(1)).isEqualTo("field_c_value"); } private void write(TempFile tempFile, Type writerType, List data) diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestTupleDomainOrcPredicate.java b/lib/trino-orc/src/test/java/io/trino/orc/TestTupleDomainOrcPredicate.java index 632f27757f25..a5eff1a3956e 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestTupleDomainOrcPredicate.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestTupleDomainOrcPredicate.java @@ -65,7 +65,6 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static java.lang.Float.floatToRawIntBits; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestTupleDomainOrcPredicate { @@ -76,23 +75,23 @@ public class TestTupleDomainOrcPredicate @Test public void testBoolean() { - assertEquals(getDomain(BOOLEAN, 0, null), none(BOOLEAN)); - assertEquals(getDomain(BOOLEAN, 10, null), all(BOOLEAN)); + assertThat(getDomain(BOOLEAN, 0, null)).isEqualTo(none(BOOLEAN)); + assertThat(getDomain(BOOLEAN, 10, null)).isEqualTo(all(BOOLEAN)); - assertEquals(getDomain(BOOLEAN, 0, booleanColumnStats(null, null)), none(BOOLEAN)); - assertEquals(getDomain(BOOLEAN, 0, booleanColumnStats(0L, null)), none(BOOLEAN)); - assertEquals(getDomain(BOOLEAN, 0, booleanColumnStats(0L, 0L)), none(BOOLEAN)); + assertThat(getDomain(BOOLEAN, 0, booleanColumnStats(null, null))).isEqualTo(none(BOOLEAN)); + assertThat(getDomain(BOOLEAN, 0, booleanColumnStats(0L, null))).isEqualTo(none(BOOLEAN)); + assertThat(getDomain(BOOLEAN, 0, booleanColumnStats(0L, 0L))).isEqualTo(none(BOOLEAN)); - assertEquals(getDomain(BOOLEAN, 10, booleanColumnStats(0L, 0L)), onlyNull(BOOLEAN)); - assertEquals(getDomain(BOOLEAN, 10, booleanColumnStats(10L, null)), notNull(BOOLEAN)); + assertThat(getDomain(BOOLEAN, 10, booleanColumnStats(0L, 0L))).isEqualTo(onlyNull(BOOLEAN)); + assertThat(getDomain(BOOLEAN, 10, booleanColumnStats(10L, null))).isEqualTo(notNull(BOOLEAN)); - assertEquals(getDomain(BOOLEAN, 10, booleanColumnStats(10L, 10L)), singleValue(BOOLEAN, true)); - assertEquals(getDomain(BOOLEAN, 10, booleanColumnStats(10L, 0L)), singleValue(BOOLEAN, false)); + assertThat(getDomain(BOOLEAN, 10, booleanColumnStats(10L, 10L))).isEqualTo(singleValue(BOOLEAN, true)); + assertThat(getDomain(BOOLEAN, 10, booleanColumnStats(10L, 0L))).isEqualTo(singleValue(BOOLEAN, false)); - assertEquals(getDomain(BOOLEAN, 20, booleanColumnStats(10L, 5L)), all(BOOLEAN)); + assertThat(getDomain(BOOLEAN, 20, booleanColumnStats(10L, 5L))).isEqualTo(all(BOOLEAN)); - assertEquals(getDomain(BOOLEAN, 20, booleanColumnStats(10L, 10L)), create(ValueSet.ofRanges(Range.equal(BOOLEAN, true)), true)); - assertEquals(getDomain(BOOLEAN, 20, booleanColumnStats(10L, 0L)), create(ValueSet.ofRanges(Range.equal(BOOLEAN, false)), true)); + assertThat(getDomain(BOOLEAN, 20, booleanColumnStats(10L, 10L))).isEqualTo(create(ValueSet.ofRanges(Range.equal(BOOLEAN, true)), true)); + assertThat(getDomain(BOOLEAN, 20, booleanColumnStats(10L, 0L))).isEqualTo(create(ValueSet.ofRanges(Range.equal(BOOLEAN, false)), true)); } private static ColumnStatistics booleanColumnStats(Long numberOfValues, Long trueValueCount) @@ -107,25 +106,25 @@ private static ColumnStatistics booleanColumnStats(Long numberOfValues, Long tru @Test public void testBigint() { - assertEquals(getDomain(BIGINT, 0, null), none(BIGINT)); - assertEquals(getDomain(BIGINT, 10, null), all(BIGINT)); + assertThat(getDomain(BIGINT, 0, null)).isEqualTo(none(BIGINT)); + assertThat(getDomain(BIGINT, 10, null)).isEqualTo(all(BIGINT)); - assertEquals(getDomain(BIGINT, 0, integerColumnStats(null, null, null)), none(BIGINT)); - assertEquals(getDomain(BIGINT, 0, integerColumnStats(0L, null, null)), none(BIGINT)); - assertEquals(getDomain(BIGINT, 0, integerColumnStats(0L, 100L, 100L)), none(BIGINT)); + assertThat(getDomain(BIGINT, 0, integerColumnStats(null, null, null))).isEqualTo(none(BIGINT)); + assertThat(getDomain(BIGINT, 0, integerColumnStats(0L, null, null))).isEqualTo(none(BIGINT)); + assertThat(getDomain(BIGINT, 0, integerColumnStats(0L, 100L, 100L))).isEqualTo(none(BIGINT)); - assertEquals(getDomain(BIGINT, 10, integerColumnStats(0L, null, null)), onlyNull(BIGINT)); - assertEquals(getDomain(BIGINT, 10, integerColumnStats(10L, null, null)), notNull(BIGINT)); + assertThat(getDomain(BIGINT, 10, integerColumnStats(0L, null, null))).isEqualTo(onlyNull(BIGINT)); + assertThat(getDomain(BIGINT, 10, integerColumnStats(10L, null, null))).isEqualTo(notNull(BIGINT)); - assertEquals(getDomain(BIGINT, 10, integerColumnStats(10L, 100L, 100L)), singleValue(BIGINT, 100L)); + assertThat(getDomain(BIGINT, 10, integerColumnStats(10L, 100L, 100L))).isEqualTo(singleValue(BIGINT, 100L)); - assertEquals(getDomain(BIGINT, 10, integerColumnStats(10L, 0L, 100L)), create(ValueSet.ofRanges(range(BIGINT, 0L, true, 100L, true)), false)); - assertEquals(getDomain(BIGINT, 10, integerColumnStats(10L, null, 100L)), create(ValueSet.ofRanges(lessThanOrEqual(BIGINT, 100L)), false)); - assertEquals(getDomain(BIGINT, 10, integerColumnStats(10L, 0L, null)), create(ValueSet.ofRanges(greaterThanOrEqual(BIGINT, 0L)), false)); + assertThat(getDomain(BIGINT, 10, integerColumnStats(10L, 0L, 100L))).isEqualTo(create(ValueSet.ofRanges(range(BIGINT, 0L, true, 100L, true)), false)); + assertThat(getDomain(BIGINT, 10, integerColumnStats(10L, null, 100L))).isEqualTo(create(ValueSet.ofRanges(lessThanOrEqual(BIGINT, 100L)), false)); + assertThat(getDomain(BIGINT, 10, integerColumnStats(10L, 0L, null))).isEqualTo(create(ValueSet.ofRanges(greaterThanOrEqual(BIGINT, 0L)), false)); - assertEquals(getDomain(BIGINT, 10, integerColumnStats(5L, 0L, 100L)), create(ValueSet.ofRanges(range(BIGINT, 0L, true, 100L, true)), true)); - assertEquals(getDomain(BIGINT, 10, integerColumnStats(5L, null, 100L)), create(ValueSet.ofRanges(lessThanOrEqual(BIGINT, 100L)), true)); - assertEquals(getDomain(BIGINT, 10, integerColumnStats(5L, 0L, null)), create(ValueSet.ofRanges(greaterThanOrEqual(BIGINT, 0L)), true)); + assertThat(getDomain(BIGINT, 10, integerColumnStats(5L, 0L, 100L))).isEqualTo(create(ValueSet.ofRanges(range(BIGINT, 0L, true, 100L, true)), true)); + assertThat(getDomain(BIGINT, 10, integerColumnStats(5L, null, 100L))).isEqualTo(create(ValueSet.ofRanges(lessThanOrEqual(BIGINT, 100L)), true)); + assertThat(getDomain(BIGINT, 10, integerColumnStats(5L, 0L, null))).isEqualTo(create(ValueSet.ofRanges(greaterThanOrEqual(BIGINT, 0L)), true)); } private static ColumnStatistics integerColumnStats(Long numberOfValues, Long minimum, Long maximum) @@ -136,25 +135,25 @@ private static ColumnStatistics integerColumnStats(Long numberOfValues, Long min @Test public void testDouble() { - assertEquals(getDomain(DOUBLE, 0, null), none(DOUBLE)); - assertEquals(getDomain(DOUBLE, 10, null), all(DOUBLE)); + assertThat(getDomain(DOUBLE, 0, null)).isEqualTo(none(DOUBLE)); + assertThat(getDomain(DOUBLE, 10, null)).isEqualTo(all(DOUBLE)); - assertEquals(getDomain(DOUBLE, 0, doubleColumnStats(null, null, null)), none(DOUBLE)); - assertEquals(getDomain(DOUBLE, 0, doubleColumnStats(0L, null, null)), none(DOUBLE)); - assertEquals(getDomain(DOUBLE, 0, doubleColumnStats(0L, 42.24, 42.24)), none(DOUBLE)); + assertThat(getDomain(DOUBLE, 0, doubleColumnStats(null, null, null))).isEqualTo(none(DOUBLE)); + assertThat(getDomain(DOUBLE, 0, doubleColumnStats(0L, null, null))).isEqualTo(none(DOUBLE)); + assertThat(getDomain(DOUBLE, 0, doubleColumnStats(0L, 42.24, 42.24))).isEqualTo(none(DOUBLE)); - assertEquals(getDomain(DOUBLE, 10, doubleColumnStats(0L, null, null)), onlyNull(DOUBLE)); - assertEquals(getDomain(DOUBLE, 10, doubleColumnStats(10L, null, null)), notNull(DOUBLE)); + assertThat(getDomain(DOUBLE, 10, doubleColumnStats(0L, null, null))).isEqualTo(onlyNull(DOUBLE)); + assertThat(getDomain(DOUBLE, 10, doubleColumnStats(10L, null, null))).isEqualTo(notNull(DOUBLE)); - assertEquals(getDomain(DOUBLE, 10, doubleColumnStats(10L, 42.24, 42.24)), singleValue(DOUBLE, 42.24)); + assertThat(getDomain(DOUBLE, 10, doubleColumnStats(10L, 42.24, 42.24))).isEqualTo(singleValue(DOUBLE, 42.24)); - assertEquals(getDomain(DOUBLE, 10, doubleColumnStats(10L, 3.3, 42.24)), create(ValueSet.ofRanges(range(DOUBLE, 3.3, true, 42.24, true)), false)); - assertEquals(getDomain(DOUBLE, 10, doubleColumnStats(10L, null, 42.24)), create(ValueSet.ofRanges(lessThanOrEqual(DOUBLE, 42.24)), false)); - assertEquals(getDomain(DOUBLE, 10, doubleColumnStats(10L, 3.3, null)), create(ValueSet.ofRanges(greaterThanOrEqual(DOUBLE, 3.3)), false)); + assertThat(getDomain(DOUBLE, 10, doubleColumnStats(10L, 3.3, 42.24))).isEqualTo(create(ValueSet.ofRanges(range(DOUBLE, 3.3, true, 42.24, true)), false)); + assertThat(getDomain(DOUBLE, 10, doubleColumnStats(10L, null, 42.24))).isEqualTo(create(ValueSet.ofRanges(lessThanOrEqual(DOUBLE, 42.24)), false)); + assertThat(getDomain(DOUBLE, 10, doubleColumnStats(10L, 3.3, null))).isEqualTo(create(ValueSet.ofRanges(greaterThanOrEqual(DOUBLE, 3.3)), false)); - assertEquals(getDomain(DOUBLE, 10, doubleColumnStats(5L, 3.3, 42.24)), create(ValueSet.ofRanges(range(DOUBLE, 3.3, true, 42.24, true)), true)); - assertEquals(getDomain(DOUBLE, 10, doubleColumnStats(5L, null, 42.24)), create(ValueSet.ofRanges(lessThanOrEqual(DOUBLE, 42.24)), true)); - assertEquals(getDomain(DOUBLE, 10, doubleColumnStats(5L, 3.3, null)), create(ValueSet.ofRanges(greaterThanOrEqual(DOUBLE, 3.3)), true)); + assertThat(getDomain(DOUBLE, 10, doubleColumnStats(5L, 3.3, 42.24))).isEqualTo(create(ValueSet.ofRanges(range(DOUBLE, 3.3, true, 42.24, true)), true)); + assertThat(getDomain(DOUBLE, 10, doubleColumnStats(5L, null, 42.24))).isEqualTo(create(ValueSet.ofRanges(lessThanOrEqual(DOUBLE, 42.24)), true)); + assertThat(getDomain(DOUBLE, 10, doubleColumnStats(5L, 3.3, null))).isEqualTo(create(ValueSet.ofRanges(greaterThanOrEqual(DOUBLE, 3.3)), true)); } private static ColumnStatistics doubleColumnStats(Long numberOfValues, Double minimum, Double maximum) @@ -165,83 +164,83 @@ private static ColumnStatistics doubleColumnStats(Long numberOfValues, Double mi @Test public void testFloat() { - assertEquals(getDomain(REAL, 0, null), none(REAL)); - assertEquals(getDomain(REAL, 10, null), all(REAL)); + assertThat(getDomain(REAL, 0, null)).isEqualTo(none(REAL)); + assertThat(getDomain(REAL, 10, null)).isEqualTo(all(REAL)); - assertEquals(getDomain(REAL, 0, doubleColumnStats(null, null, null)), none(REAL)); - assertEquals(getDomain(REAL, 0, doubleColumnStats(0L, null, null)), none(REAL)); - assertEquals(getDomain(REAL, 0, doubleColumnStats(0L, (double) 42.24f, (double) 42.24f)), none(REAL)); + assertThat(getDomain(REAL, 0, doubleColumnStats(null, null, null))).isEqualTo(none(REAL)); + assertThat(getDomain(REAL, 0, doubleColumnStats(0L, null, null))).isEqualTo(none(REAL)); + assertThat(getDomain(REAL, 0, doubleColumnStats(0L, (double) 42.24f, (double) 42.24f))).isEqualTo(none(REAL)); - assertEquals(getDomain(REAL, 10, doubleColumnStats(0L, null, null)), onlyNull(REAL)); - assertEquals(getDomain(REAL, 10, doubleColumnStats(10L, null, null)), notNull(REAL)); + assertThat(getDomain(REAL, 10, doubleColumnStats(0L, null, null))).isEqualTo(onlyNull(REAL)); + assertThat(getDomain(REAL, 10, doubleColumnStats(10L, null, null))).isEqualTo(notNull(REAL)); - assertEquals(getDomain(REAL, 10, doubleColumnStats(10L, (double) 42.24f, (double) 42.24f)), singleValue(REAL, (long) floatToRawIntBits(42.24f))); + assertThat(getDomain(REAL, 10, doubleColumnStats(10L, (double) 42.24f, (double) 42.24f))).isEqualTo(singleValue(REAL, (long) floatToRawIntBits(42.24f))); - assertEquals(getDomain(REAL, 10, doubleColumnStats(10L, 3.3, (double) 42.24f)), create(ValueSet.ofRanges(range(REAL, (long) floatToRawIntBits(3.3f), true, (long) floatToRawIntBits(42.24f), true)), false)); - assertEquals(getDomain(REAL, 10, doubleColumnStats(10L, null, (double) 42.24f)), create(ValueSet.ofRanges(lessThanOrEqual(REAL, (long) floatToRawIntBits(42.24f))), false)); - assertEquals(getDomain(REAL, 10, doubleColumnStats(10L, 3.3, null)), create(ValueSet.ofRanges(greaterThanOrEqual(REAL, (long) floatToRawIntBits(3.3f))), false)); + assertThat(getDomain(REAL, 10, doubleColumnStats(10L, 3.3, (double) 42.24f))).isEqualTo(create(ValueSet.ofRanges(range(REAL, (long) floatToRawIntBits(3.3f), true, (long) floatToRawIntBits(42.24f), true)), false)); + assertThat(getDomain(REAL, 10, doubleColumnStats(10L, null, (double) 42.24f))).isEqualTo(create(ValueSet.ofRanges(lessThanOrEqual(REAL, (long) floatToRawIntBits(42.24f))), false)); + assertThat(getDomain(REAL, 10, doubleColumnStats(10L, 3.3, null))).isEqualTo(create(ValueSet.ofRanges(greaterThanOrEqual(REAL, (long) floatToRawIntBits(3.3f))), false)); - assertEquals(getDomain(REAL, 10, doubleColumnStats(5L, 3.3, (double) 42.24f)), create(ValueSet.ofRanges(range(REAL, (long) floatToRawIntBits(3.3f), true, (long) floatToRawIntBits(42.24f), true)), true)); - assertEquals(getDomain(REAL, 10, doubleColumnStats(5L, null, (double) 42.24f)), create(ValueSet.ofRanges(lessThanOrEqual(REAL, (long) floatToRawIntBits(42.24f))), true)); - assertEquals(getDomain(REAL, 10, doubleColumnStats(5L, 3.3, null)), create(ValueSet.ofRanges(greaterThanOrEqual(REAL, (long) floatToRawIntBits(3.3f))), true)); + assertThat(getDomain(REAL, 10, doubleColumnStats(5L, 3.3, (double) 42.24f))).isEqualTo(create(ValueSet.ofRanges(range(REAL, (long) floatToRawIntBits(3.3f), true, (long) floatToRawIntBits(42.24f), true)), true)); + assertThat(getDomain(REAL, 10, doubleColumnStats(5L, null, (double) 42.24f))).isEqualTo(create(ValueSet.ofRanges(lessThanOrEqual(REAL, (long) floatToRawIntBits(42.24f))), true)); + assertThat(getDomain(REAL, 10, doubleColumnStats(5L, 3.3, null))).isEqualTo(create(ValueSet.ofRanges(greaterThanOrEqual(REAL, (long) floatToRawIntBits(3.3f))), true)); } @Test public void testString() { - assertEquals(getDomain(VARCHAR, 0, null), none(VARCHAR)); - assertEquals(getDomain(VARCHAR, 10, null), all(VARCHAR)); + assertThat(getDomain(VARCHAR, 0, null)).isEqualTo(none(VARCHAR)); + assertThat(getDomain(VARCHAR, 10, null)).isEqualTo(all(VARCHAR)); - assertEquals(getDomain(VARCHAR, 0, stringColumnStats(null, null, null)), none(VARCHAR)); - assertEquals(getDomain(VARCHAR, 0, stringColumnStats(0L, null, null)), none(VARCHAR)); - assertEquals(getDomain(VARCHAR, 0, stringColumnStats(0L, "taco", "taco")), none(VARCHAR)); + assertThat(getDomain(VARCHAR, 0, stringColumnStats(null, null, null))).isEqualTo(none(VARCHAR)); + assertThat(getDomain(VARCHAR, 0, stringColumnStats(0L, null, null))).isEqualTo(none(VARCHAR)); + assertThat(getDomain(VARCHAR, 0, stringColumnStats(0L, "taco", "taco"))).isEqualTo(none(VARCHAR)); - assertEquals(getDomain(VARCHAR, 10, stringColumnStats(0L, null, null)), onlyNull(VARCHAR)); - assertEquals(getDomain(VARCHAR, 10, stringColumnStats(10L, null, null)), notNull(VARCHAR)); + assertThat(getDomain(VARCHAR, 10, stringColumnStats(0L, null, null))).isEqualTo(onlyNull(VARCHAR)); + assertThat(getDomain(VARCHAR, 10, stringColumnStats(10L, null, null))).isEqualTo(notNull(VARCHAR)); - assertEquals(getDomain(VARCHAR, 10, stringColumnStats(10L, "taco", "taco")), singleValue(VARCHAR, utf8Slice("taco"))); + assertThat(getDomain(VARCHAR, 10, stringColumnStats(10L, "taco", "taco"))).isEqualTo(singleValue(VARCHAR, utf8Slice("taco"))); - assertEquals(getDomain(VARCHAR, 10, stringColumnStats(10L, "apple", "taco")), create(ValueSet.ofRanges(range(VARCHAR, utf8Slice("apple"), true, utf8Slice("taco"), true)), false)); - assertEquals(getDomain(VARCHAR, 10, stringColumnStats(10L, null, "taco")), create(ValueSet.ofRanges(lessThanOrEqual(VARCHAR, utf8Slice("taco"))), false)); - assertEquals(getDomain(VARCHAR, 10, stringColumnStats(10L, "apple", null)), create(ValueSet.ofRanges(greaterThanOrEqual(VARCHAR, utf8Slice("apple"))), false)); + assertThat(getDomain(VARCHAR, 10, stringColumnStats(10L, "apple", "taco"))).isEqualTo(create(ValueSet.ofRanges(range(VARCHAR, utf8Slice("apple"), true, utf8Slice("taco"), true)), false)); + assertThat(getDomain(VARCHAR, 10, stringColumnStats(10L, null, "taco"))).isEqualTo(create(ValueSet.ofRanges(lessThanOrEqual(VARCHAR, utf8Slice("taco"))), false)); + assertThat(getDomain(VARCHAR, 10, stringColumnStats(10L, "apple", null))).isEqualTo(create(ValueSet.ofRanges(greaterThanOrEqual(VARCHAR, utf8Slice("apple"))), false)); - assertEquals(getDomain(VARCHAR, 10, stringColumnStats(5L, "apple", "taco")), create(ValueSet.ofRanges(range(VARCHAR, utf8Slice("apple"), true, utf8Slice("taco"), true)), true)); - assertEquals(getDomain(VARCHAR, 10, stringColumnStats(5L, null, "taco")), create(ValueSet.ofRanges(lessThanOrEqual(VARCHAR, utf8Slice("taco"))), true)); - assertEquals(getDomain(VARCHAR, 10, stringColumnStats(5L, "apple", null)), create(ValueSet.ofRanges(greaterThanOrEqual(VARCHAR, utf8Slice("apple"))), true)); + assertThat(getDomain(VARCHAR, 10, stringColumnStats(5L, "apple", "taco"))).isEqualTo(create(ValueSet.ofRanges(range(VARCHAR, utf8Slice("apple"), true, utf8Slice("taco"), true)), true)); + assertThat(getDomain(VARCHAR, 10, stringColumnStats(5L, null, "taco"))).isEqualTo(create(ValueSet.ofRanges(lessThanOrEqual(VARCHAR, utf8Slice("taco"))), true)); + assertThat(getDomain(VARCHAR, 10, stringColumnStats(5L, "apple", null))).isEqualTo(create(ValueSet.ofRanges(greaterThanOrEqual(VARCHAR, utf8Slice("apple"))), true)); } @Test public void testChar() { - assertEquals(getDomain(CHAR, 0, null), none(CHAR)); - assertEquals(getDomain(CHAR, 10, null), all(CHAR)); - - assertEquals(getDomain(CHAR, 0, stringColumnStats(null, null, null)), none(CHAR)); - assertEquals(getDomain(CHAR, 0, stringColumnStats(0L, null, null)), none(CHAR)); - assertEquals(getDomain(CHAR, 0, stringColumnStats(0L, "taco ", "taco ")), none(CHAR)); - assertEquals(getDomain(CHAR, 0, stringColumnStats(0L, "taco", "taco ")), none(CHAR)); - - assertEquals(getDomain(CHAR, 10, stringColumnStats(0L, null, null)), onlyNull(CHAR)); - assertEquals(getDomain(CHAR, 10, stringColumnStats(10L, null, null)), notNull(CHAR)); - - assertEquals(getDomain(CHAR, 10, stringColumnStats(10L, "taco ", "taco ")), singleValue(CHAR, utf8Slice("taco"))); - assertEquals(getDomain(CHAR, 10, stringColumnStats(10L, "taco", "taco ")), singleValue(CHAR, utf8Slice("taco"))); - - assertEquals(getDomain(CHAR, 10, stringColumnStats(10L, "apple ", "taco ")), create(ValueSet.ofRanges(range(CHAR, utf8Slice("apple"), true, utf8Slice("taco"), true)), false)); - assertEquals(getDomain(CHAR, 10, stringColumnStats(10L, "apple ", "taco")), create(ValueSet.ofRanges(range(CHAR, utf8Slice("apple"), true, utf8Slice("taco"), true)), false)); - assertEquals(getDomain(CHAR, 10, stringColumnStats(10L, null, "taco ")), create(ValueSet.ofRanges(lessThanOrEqual(CHAR, utf8Slice("taco"))), false)); - assertEquals(getDomain(CHAR, 10, stringColumnStats(10L, null, "taco")), create(ValueSet.ofRanges(lessThanOrEqual(CHAR, utf8Slice("taco"))), false)); - assertEquals(getDomain(CHAR, 10, stringColumnStats(10L, "apple ", null)), create(ValueSet.ofRanges(greaterThanOrEqual(CHAR, utf8Slice("apple"))), false)); - assertEquals(getDomain(CHAR, 10, stringColumnStats(10L, "apple", null)), create(ValueSet.ofRanges(greaterThanOrEqual(CHAR, utf8Slice("apple"))), false)); - - assertEquals(getDomain(CHAR, 10, stringColumnStats(5L, "apple ", "taco ")), create(ValueSet.ofRanges(range(CHAR, utf8Slice("apple"), true, utf8Slice("taco"), true)), true)); - assertEquals(getDomain(CHAR, 10, stringColumnStats(5L, "apple ", "taco")), create(ValueSet.ofRanges(range(CHAR, utf8Slice("apple"), true, utf8Slice("taco"), true)), true)); - assertEquals(getDomain(CHAR, 10, stringColumnStats(5L, null, "taco ")), create(ValueSet.ofRanges(lessThanOrEqual(CHAR, utf8Slice("taco"))), true)); - assertEquals(getDomain(CHAR, 10, stringColumnStats(5L, null, "taco")), create(ValueSet.ofRanges(lessThanOrEqual(CHAR, utf8Slice("taco"))), true)); - assertEquals(getDomain(CHAR, 10, stringColumnStats(5L, "apple ", null)), create(ValueSet.ofRanges(greaterThanOrEqual(CHAR, utf8Slice("apple"))), true)); - assertEquals(getDomain(CHAR, 10, stringColumnStats(5L, "apple", null)), create(ValueSet.ofRanges(greaterThanOrEqual(CHAR, utf8Slice("apple"))), true)); - - assertEquals(getDomain(CHAR, 10, stringColumnStats(10L, "\0 ", " ")), create(ValueSet.ofRanges(range(CHAR, utf8Slice("\0"), true, utf8Slice(""), true)), false)); + assertThat(getDomain(CHAR, 0, null)).isEqualTo(none(CHAR)); + assertThat(getDomain(CHAR, 10, null)).isEqualTo(all(CHAR)); + + assertThat(getDomain(CHAR, 0, stringColumnStats(null, null, null))).isEqualTo(none(CHAR)); + assertThat(getDomain(CHAR, 0, stringColumnStats(0L, null, null))).isEqualTo(none(CHAR)); + assertThat(getDomain(CHAR, 0, stringColumnStats(0L, "taco ", "taco "))).isEqualTo(none(CHAR)); + assertThat(getDomain(CHAR, 0, stringColumnStats(0L, "taco", "taco "))).isEqualTo(none(CHAR)); + + assertThat(getDomain(CHAR, 10, stringColumnStats(0L, null, null))).isEqualTo(onlyNull(CHAR)); + assertThat(getDomain(CHAR, 10, stringColumnStats(10L, null, null))).isEqualTo(notNull(CHAR)); + + assertThat(getDomain(CHAR, 10, stringColumnStats(10L, "taco ", "taco "))).isEqualTo(singleValue(CHAR, utf8Slice("taco"))); + assertThat(getDomain(CHAR, 10, stringColumnStats(10L, "taco", "taco "))).isEqualTo(singleValue(CHAR, utf8Slice("taco"))); + + assertThat(getDomain(CHAR, 10, stringColumnStats(10L, "apple ", "taco "))).isEqualTo(create(ValueSet.ofRanges(range(CHAR, utf8Slice("apple"), true, utf8Slice("taco"), true)), false)); + assertThat(getDomain(CHAR, 10, stringColumnStats(10L, "apple ", "taco"))).isEqualTo(create(ValueSet.ofRanges(range(CHAR, utf8Slice("apple"), true, utf8Slice("taco"), true)), false)); + assertThat(getDomain(CHAR, 10, stringColumnStats(10L, null, "taco "))).isEqualTo(create(ValueSet.ofRanges(lessThanOrEqual(CHAR, utf8Slice("taco"))), false)); + assertThat(getDomain(CHAR, 10, stringColumnStats(10L, null, "taco"))).isEqualTo(create(ValueSet.ofRanges(lessThanOrEqual(CHAR, utf8Slice("taco"))), false)); + assertThat(getDomain(CHAR, 10, stringColumnStats(10L, "apple ", null))).isEqualTo(create(ValueSet.ofRanges(greaterThanOrEqual(CHAR, utf8Slice("apple"))), false)); + assertThat(getDomain(CHAR, 10, stringColumnStats(10L, "apple", null))).isEqualTo(create(ValueSet.ofRanges(greaterThanOrEqual(CHAR, utf8Slice("apple"))), false)); + + assertThat(getDomain(CHAR, 10, stringColumnStats(5L, "apple ", "taco "))).isEqualTo(create(ValueSet.ofRanges(range(CHAR, utf8Slice("apple"), true, utf8Slice("taco"), true)), true)); + assertThat(getDomain(CHAR, 10, stringColumnStats(5L, "apple ", "taco"))).isEqualTo(create(ValueSet.ofRanges(range(CHAR, utf8Slice("apple"), true, utf8Slice("taco"), true)), true)); + assertThat(getDomain(CHAR, 10, stringColumnStats(5L, null, "taco "))).isEqualTo(create(ValueSet.ofRanges(lessThanOrEqual(CHAR, utf8Slice("taco"))), true)); + assertThat(getDomain(CHAR, 10, stringColumnStats(5L, null, "taco"))).isEqualTo(create(ValueSet.ofRanges(lessThanOrEqual(CHAR, utf8Slice("taco"))), true)); + assertThat(getDomain(CHAR, 10, stringColumnStats(5L, "apple ", null))).isEqualTo(create(ValueSet.ofRanges(greaterThanOrEqual(CHAR, utf8Slice("apple"))), true)); + assertThat(getDomain(CHAR, 10, stringColumnStats(5L, "apple", null))).isEqualTo(create(ValueSet.ofRanges(greaterThanOrEqual(CHAR, utf8Slice("apple"))), true)); + + assertThat(getDomain(CHAR, 10, stringColumnStats(10L, "\0 ", " "))).isEqualTo(create(ValueSet.ofRanges(range(CHAR, utf8Slice("\0"), true, utf8Slice(""), true)), false)); } private static ColumnStatistics stringColumnStats(Long numberOfValues, String minimum, String maximum) @@ -255,25 +254,25 @@ private static ColumnStatistics stringColumnStats(Long numberOfValues, String mi @Test public void testDate() { - assertEquals(getDomain(DATE, 0, null), none(DATE)); - assertEquals(getDomain(DATE, 10, null), all(DATE)); + assertThat(getDomain(DATE, 0, null)).isEqualTo(none(DATE)); + assertThat(getDomain(DATE, 10, null)).isEqualTo(all(DATE)); - assertEquals(getDomain(DATE, 0, dateColumnStats(null, null, null)), none(DATE)); - assertEquals(getDomain(DATE, 0, dateColumnStats(0L, null, null)), none(DATE)); - assertEquals(getDomain(DATE, 0, dateColumnStats(0L, 100, 100)), none(DATE)); + assertThat(getDomain(DATE, 0, dateColumnStats(null, null, null))).isEqualTo(none(DATE)); + assertThat(getDomain(DATE, 0, dateColumnStats(0L, null, null))).isEqualTo(none(DATE)); + assertThat(getDomain(DATE, 0, dateColumnStats(0L, 100, 100))).isEqualTo(none(DATE)); - assertEquals(getDomain(DATE, 10, dateColumnStats(0L, null, null)), onlyNull(DATE)); - assertEquals(getDomain(DATE, 10, dateColumnStats(10L, null, null)), notNull(DATE)); + assertThat(getDomain(DATE, 10, dateColumnStats(0L, null, null))).isEqualTo(onlyNull(DATE)); + assertThat(getDomain(DATE, 10, dateColumnStats(10L, null, null))).isEqualTo(notNull(DATE)); - assertEquals(getDomain(DATE, 10, dateColumnStats(10L, 100, 100)), singleValue(DATE, 100L)); + assertThat(getDomain(DATE, 10, dateColumnStats(10L, 100, 100))).isEqualTo(singleValue(DATE, 100L)); - assertEquals(getDomain(DATE, 10, dateColumnStats(10L, 0, 100)), create(ValueSet.ofRanges(range(DATE, 0L, true, 100L, true)), false)); - assertEquals(getDomain(DATE, 10, dateColumnStats(10L, null, 100)), create(ValueSet.ofRanges(lessThanOrEqual(DATE, 100L)), false)); - assertEquals(getDomain(DATE, 10, dateColumnStats(10L, 0, null)), create(ValueSet.ofRanges(greaterThanOrEqual(DATE, 0L)), false)); + assertThat(getDomain(DATE, 10, dateColumnStats(10L, 0, 100))).isEqualTo(create(ValueSet.ofRanges(range(DATE, 0L, true, 100L, true)), false)); + assertThat(getDomain(DATE, 10, dateColumnStats(10L, null, 100))).isEqualTo(create(ValueSet.ofRanges(lessThanOrEqual(DATE, 100L)), false)); + assertThat(getDomain(DATE, 10, dateColumnStats(10L, 0, null))).isEqualTo(create(ValueSet.ofRanges(greaterThanOrEqual(DATE, 0L)), false)); - assertEquals(getDomain(DATE, 10, dateColumnStats(5L, 0, 100)), create(ValueSet.ofRanges(range(DATE, 0L, true, 100L, true)), true)); - assertEquals(getDomain(DATE, 10, dateColumnStats(5L, null, 100)), create(ValueSet.ofRanges(lessThanOrEqual(DATE, 100L)), true)); - assertEquals(getDomain(DATE, 10, dateColumnStats(5L, 0, null)), create(ValueSet.ofRanges(greaterThanOrEqual(DATE, 0L)), true)); + assertThat(getDomain(DATE, 10, dateColumnStats(5L, 0, 100))).isEqualTo(create(ValueSet.ofRanges(range(DATE, 0L, true, 100L, true)), true)); + assertThat(getDomain(DATE, 10, dateColumnStats(5L, null, 100))).isEqualTo(create(ValueSet.ofRanges(lessThanOrEqual(DATE, 100L)), true)); + assertThat(getDomain(DATE, 10, dateColumnStats(5L, 0, null))).isEqualTo(create(ValueSet.ofRanges(greaterThanOrEqual(DATE, 0L)), true)); } private static ColumnStatistics dateColumnStats(Long numberOfValues, Integer minimum, Integer maximum) @@ -536,50 +535,36 @@ private static ColumnStatistics timeStampColumnStats(Long numberOfValues, Long m @Test public void testDecimal() { - assertEquals(getDomain(SHORT_DECIMAL, 0, null), none(SHORT_DECIMAL)); - assertEquals(getDomain(LONG_DECIMAL, 10, null), all(LONG_DECIMAL)); - - assertEquals(getDomain(SHORT_DECIMAL, 0, decimalColumnStats(null, null, null)), none(SHORT_DECIMAL)); - assertEquals(getDomain(LONG_DECIMAL, 0, decimalColumnStats(0L, null, null)), none(LONG_DECIMAL)); - assertEquals(getDomain(SHORT_DECIMAL, 0, decimalColumnStats(0L, "-999.99", "999.99")), none(SHORT_DECIMAL)); - - assertEquals(getDomain(LONG_DECIMAL, 10, decimalColumnStats(0L, null, null)), onlyNull(LONG_DECIMAL)); - assertEquals(getDomain(SHORT_DECIMAL, 10, decimalColumnStats(10L, null, null)), notNull(SHORT_DECIMAL)); - - assertEquals(getDomain(SHORT_DECIMAL, 10, decimalColumnStats(10L, "999.99", "999.99")), singleValue(SHORT_DECIMAL, shortDecimal("999.99"))); - assertEquals(getDomain(SHORT_DECIMAL, 10, decimalColumnStats(10L, "999.9", "999.9")), singleValue(SHORT_DECIMAL, shortDecimal("999.90"))); - assertEquals(getDomain(LONG_DECIMAL, 10, decimalColumnStats(10L, "1234567890.0987654321", "1234567890.0987654321")), - singleValue(LONG_DECIMAL, longDecimal("1234567890.0987654321"))); - - assertEquals(getDomain(SHORT_DECIMAL, 10, decimalColumnStats(10L, "-999.99", "999.99")), - create(ValueSet.ofRanges(range(SHORT_DECIMAL, shortDecimal("-999.99"), true, shortDecimal("999.99"), true)), false)); - assertEquals(getDomain(SHORT_DECIMAL, 10, decimalColumnStats(10L, "10.5", "20")), - create(ValueSet.ofRanges(range(SHORT_DECIMAL, shortDecimal("10.50"), true, shortDecimal("20.00"), true)), false)); - assertEquals(getDomain(SHORT_DECIMAL, 10, decimalColumnStats(10L, null, "999.99")), - create(ValueSet.ofRanges(lessThanOrEqual(SHORT_DECIMAL, shortDecimal("999.99"))), false)); - assertEquals(getDomain(SHORT_DECIMAL, 10, decimalColumnStats(10L, "-999.99", null)), - create(ValueSet.ofRanges(greaterThanOrEqual(SHORT_DECIMAL, shortDecimal("-999.99"))), false)); - - assertEquals(getDomain(LONG_DECIMAL, 10, decimalColumnStats(10L, "-1234567890.0987654321", "1234567890.0987654321")), - create(ValueSet.ofRanges(range(LONG_DECIMAL, longDecimal("-1234567890.0987654321"), true, longDecimal("1234567890.0987654321"), true)), false)); - assertEquals(getDomain(LONG_DECIMAL, 10, decimalColumnStats(10L, null, "1234567890.0987654321")), - create(ValueSet.ofRanges(lessThanOrEqual(LONG_DECIMAL, longDecimal("1234567890.0987654321"))), false)); - assertEquals(getDomain(LONG_DECIMAL, 10, decimalColumnStats(10L, "-1234567890.0987654321", null)), - create(ValueSet.ofRanges(greaterThanOrEqual(LONG_DECIMAL, longDecimal("-1234567890.0987654321"))), false)); - - assertEquals(getDomain(SHORT_DECIMAL, 10, decimalColumnStats(5L, "-999.99", "999.99")), - create(ValueSet.ofRanges(range(SHORT_DECIMAL, shortDecimal("-999.99"), true, shortDecimal("999.99"), true)), true)); - assertEquals(getDomain(SHORT_DECIMAL, 10, decimalColumnStats(5L, null, "999.99")), - create(ValueSet.ofRanges(lessThanOrEqual(SHORT_DECIMAL, shortDecimal("999.99"))), true)); - assertEquals(getDomain(SHORT_DECIMAL, 10, decimalColumnStats(5L, "-999.99", null)), - create(ValueSet.ofRanges(greaterThanOrEqual(SHORT_DECIMAL, shortDecimal("-999.99"))), true)); - - assertEquals(getDomain(LONG_DECIMAL, 10, decimalColumnStats(5L, "-1234567890.0987654321", "1234567890.0987654321")), - create(ValueSet.ofRanges(range(LONG_DECIMAL, longDecimal("-1234567890.0987654321"), true, longDecimal("1234567890.0987654321"), true)), true)); - assertEquals(getDomain(LONG_DECIMAL, 10, decimalColumnStats(5L, null, "1234567890.0987654321")), - create(ValueSet.ofRanges(lessThanOrEqual(LONG_DECIMAL, longDecimal("1234567890.0987654321"))), true)); - assertEquals(getDomain(LONG_DECIMAL, 10, decimalColumnStats(5L, "-1234567890.0987654321", null)), - create(ValueSet.ofRanges(greaterThanOrEqual(LONG_DECIMAL, longDecimal("-1234567890.0987654321"))), true)); + assertThat(getDomain(SHORT_DECIMAL, 0, null)).isEqualTo(none(SHORT_DECIMAL)); + assertThat(getDomain(LONG_DECIMAL, 10, null)).isEqualTo(all(LONG_DECIMAL)); + + assertThat(getDomain(SHORT_DECIMAL, 0, decimalColumnStats(null, null, null))).isEqualTo(none(SHORT_DECIMAL)); + assertThat(getDomain(LONG_DECIMAL, 0, decimalColumnStats(0L, null, null))).isEqualTo(none(LONG_DECIMAL)); + assertThat(getDomain(SHORT_DECIMAL, 0, decimalColumnStats(0L, "-999.99", "999.99"))).isEqualTo(none(SHORT_DECIMAL)); + + assertThat(getDomain(LONG_DECIMAL, 10, decimalColumnStats(0L, null, null))).isEqualTo(onlyNull(LONG_DECIMAL)); + assertThat(getDomain(SHORT_DECIMAL, 10, decimalColumnStats(10L, null, null))).isEqualTo(notNull(SHORT_DECIMAL)); + + assertThat(getDomain(SHORT_DECIMAL, 10, decimalColumnStats(10L, "999.99", "999.99"))).isEqualTo(singleValue(SHORT_DECIMAL, shortDecimal("999.99"))); + assertThat(getDomain(SHORT_DECIMAL, 10, decimalColumnStats(10L, "999.9", "999.9"))).isEqualTo(singleValue(SHORT_DECIMAL, shortDecimal("999.90"))); + assertThat(getDomain(LONG_DECIMAL, 10, decimalColumnStats(10L, "1234567890.0987654321", "1234567890.0987654321"))).isEqualTo(singleValue(LONG_DECIMAL, longDecimal("1234567890.0987654321"))); + + assertThat(getDomain(SHORT_DECIMAL, 10, decimalColumnStats(10L, "-999.99", "999.99"))).isEqualTo(create(ValueSet.ofRanges(range(SHORT_DECIMAL, shortDecimal("-999.99"), true, shortDecimal("999.99"), true)), false)); + assertThat(getDomain(SHORT_DECIMAL, 10, decimalColumnStats(10L, "10.5", "20"))).isEqualTo(create(ValueSet.ofRanges(range(SHORT_DECIMAL, shortDecimal("10.50"), true, shortDecimal("20.00"), true)), false)); + assertThat(getDomain(SHORT_DECIMAL, 10, decimalColumnStats(10L, null, "999.99"))).isEqualTo(create(ValueSet.ofRanges(lessThanOrEqual(SHORT_DECIMAL, shortDecimal("999.99"))), false)); + assertThat(getDomain(SHORT_DECIMAL, 10, decimalColumnStats(10L, "-999.99", null))).isEqualTo(create(ValueSet.ofRanges(greaterThanOrEqual(SHORT_DECIMAL, shortDecimal("-999.99"))), false)); + + assertThat(getDomain(LONG_DECIMAL, 10, decimalColumnStats(10L, "-1234567890.0987654321", "1234567890.0987654321"))).isEqualTo(create(ValueSet.ofRanges(range(LONG_DECIMAL, longDecimal("-1234567890.0987654321"), true, longDecimal("1234567890.0987654321"), true)), false)); + assertThat(getDomain(LONG_DECIMAL, 10, decimalColumnStats(10L, null, "1234567890.0987654321"))).isEqualTo(create(ValueSet.ofRanges(lessThanOrEqual(LONG_DECIMAL, longDecimal("1234567890.0987654321"))), false)); + assertThat(getDomain(LONG_DECIMAL, 10, decimalColumnStats(10L, "-1234567890.0987654321", null))).isEqualTo(create(ValueSet.ofRanges(greaterThanOrEqual(LONG_DECIMAL, longDecimal("-1234567890.0987654321"))), false)); + + assertThat(getDomain(SHORT_DECIMAL, 10, decimalColumnStats(5L, "-999.99", "999.99"))).isEqualTo(create(ValueSet.ofRanges(range(SHORT_DECIMAL, shortDecimal("-999.99"), true, shortDecimal("999.99"), true)), true)); + assertThat(getDomain(SHORT_DECIMAL, 10, decimalColumnStats(5L, null, "999.99"))).isEqualTo(create(ValueSet.ofRanges(lessThanOrEqual(SHORT_DECIMAL, shortDecimal("999.99"))), true)); + assertThat(getDomain(SHORT_DECIMAL, 10, decimalColumnStats(5L, "-999.99", null))).isEqualTo(create(ValueSet.ofRanges(greaterThanOrEqual(SHORT_DECIMAL, shortDecimal("-999.99"))), true)); + + assertThat(getDomain(LONG_DECIMAL, 10, decimalColumnStats(5L, "-1234567890.0987654321", "1234567890.0987654321"))).isEqualTo(create(ValueSet.ofRanges(range(LONG_DECIMAL, longDecimal("-1234567890.0987654321"), true, longDecimal("1234567890.0987654321"), true)), true)); + assertThat(getDomain(LONG_DECIMAL, 10, decimalColumnStats(5L, null, "1234567890.0987654321"))).isEqualTo(create(ValueSet.ofRanges(lessThanOrEqual(LONG_DECIMAL, longDecimal("1234567890.0987654321"))), true)); + assertThat(getDomain(LONG_DECIMAL, 10, decimalColumnStats(5L, "-1234567890.0987654321", null))).isEqualTo(create(ValueSet.ofRanges(greaterThanOrEqual(LONG_DECIMAL, longDecimal("-1234567890.0987654321"))), true)); } private static ColumnStatistics decimalColumnStats(Long numberOfValues, String minimum, String maximum) @@ -592,17 +577,17 @@ private static ColumnStatistics decimalColumnStats(Long numberOfValues, String m @Test public void testBinary() { - assertEquals(getDomain(VARBINARY, 0, null), none(VARBINARY)); - assertEquals(getDomain(VARBINARY, 10, null), all(VARBINARY)); + assertThat(getDomain(VARBINARY, 0, null)).isEqualTo(none(VARBINARY)); + assertThat(getDomain(VARBINARY, 10, null)).isEqualTo(all(VARBINARY)); - assertEquals(getDomain(VARBINARY, 0, binaryColumnStats(null)), none(VARBINARY)); - assertEquals(getDomain(VARBINARY, 0, binaryColumnStats(0L)), none(VARBINARY)); - assertEquals(getDomain(VARBINARY, 0, binaryColumnStats(0L)), none(VARBINARY)); + assertThat(getDomain(VARBINARY, 0, binaryColumnStats(null))).isEqualTo(none(VARBINARY)); + assertThat(getDomain(VARBINARY, 0, binaryColumnStats(0L))).isEqualTo(none(VARBINARY)); + assertThat(getDomain(VARBINARY, 0, binaryColumnStats(0L))).isEqualTo(none(VARBINARY)); - assertEquals(getDomain(VARBINARY, 10, binaryColumnStats(0L)), onlyNull(VARBINARY)); - assertEquals(getDomain(VARBINARY, 10, binaryColumnStats(10L)), notNull(VARBINARY)); + assertThat(getDomain(VARBINARY, 10, binaryColumnStats(0L))).isEqualTo(onlyNull(VARBINARY)); + assertThat(getDomain(VARBINARY, 10, binaryColumnStats(10L))).isEqualTo(notNull(VARBINARY)); - assertEquals(getDomain(VARBINARY, 20, binaryColumnStats(10L)), all(VARBINARY)); + assertThat(getDomain(VARBINARY, 20, binaryColumnStats(10L))).isEqualTo(all(VARBINARY)); } private static ColumnStatistics binaryColumnStats(Long numberOfValues) diff --git a/lib/trino-orc/src/test/java/io/trino/orc/TestingOrcPredicate.java b/lib/trino-orc/src/test/java/io/trino/orc/TestingOrcPredicate.java index 05777fa6b9be..1e1246b0960f 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/TestingOrcPredicate.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/TestingOrcPredicate.java @@ -64,9 +64,8 @@ import static io.trino.spi.type.TinyintType.TINYINT; import static io.trino.spi.type.UuidType.UUID; import static java.util.stream.Collectors.toList; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Fail.fail; public final class TestingOrcPredicate { @@ -167,7 +166,7 @@ public BasicOrcPredicate(Iterable expectedValues, Class type) public boolean matches(long numberOfRows, ColumnMetadata allColumnStatistics) { ColumnStatistics columnStatistics = allColumnStatistics.get(new OrcColumnId(1)); - assertTrue(columnStatistics.hasNumberOfValues()); + assertThat(columnStatistics.hasNumberOfValues()).isTrue(); if (numberOfRows == expectedValues.size()) { // whole file @@ -206,7 +205,7 @@ private void matchMiddleSection(ColumnStatistics columnStatistics, int size) private void assertChunkStats(List chunk, ColumnStatistics columnStatistics) { - assertTrue(chunkMatchesStats(chunk, columnStatistics)); + assertThat(chunkMatchesStats(chunk, columnStatistics)).isTrue(); } protected boolean chunkMatchesStats(List chunk, ColumnStatistics columnStatistics) @@ -227,10 +226,10 @@ public BooleanOrcPredicate(Iterable expectedValues) @Override protected boolean chunkMatchesStats(List chunk, ColumnStatistics columnStatistics) { - assertNull(columnStatistics.getIntegerStatistics()); - assertNull(columnStatistics.getDoubleStatistics()); - assertNull(columnStatistics.getStringStatistics()); - assertNull(columnStatistics.getDateStatistics()); + assertThat(columnStatistics.getIntegerStatistics()).isNull(); + assertThat(columnStatistics.getDoubleStatistics()).isNull(); + assertThat(columnStatistics.getStringStatistics()).isNull(); + assertThat(columnStatistics.getDateStatistics()).isNull(); // check basic statistics if (!super.chunkMatchesStats(chunk, columnStatistics)) { @@ -258,10 +257,10 @@ public DoubleOrcPredicate(Iterable expectedValues) @Override protected boolean chunkMatchesStats(List chunk, ColumnStatistics columnStatistics) { - assertNull(columnStatistics.getBooleanStatistics()); - assertNull(columnStatistics.getIntegerStatistics()); - assertNull(columnStatistics.getStringStatistics()); - assertNull(columnStatistics.getDateStatistics()); + assertThat(columnStatistics.getBooleanStatistics()).isNull(); + assertThat(columnStatistics.getIntegerStatistics()).isNull(); + assertThat(columnStatistics.getStringStatistics()).isNull(); + assertThat(columnStatistics.getDateStatistics()).isNull(); // check basic statistics if (!super.chunkMatchesStats(chunk, columnStatistics)) { @@ -323,10 +322,10 @@ public LongOrcPredicate(boolean testBloomFilter, Iterable expectedValues) @Override protected boolean chunkMatchesStats(List chunk, ColumnStatistics columnStatistics) { - assertNull(columnStatistics.getBooleanStatistics()); - assertNull(columnStatistics.getDoubleStatistics()); - assertNull(columnStatistics.getStringStatistics()); - assertNull(columnStatistics.getDateStatistics()); + assertThat(columnStatistics.getBooleanStatistics()).isNull(); + assertThat(columnStatistics.getDoubleStatistics()).isNull(); + assertThat(columnStatistics.getStringStatistics()).isNull(); + assertThat(columnStatistics.getDateStatistics()).isNull(); // check basic statistics if (!super.chunkMatchesStats(chunk, columnStatistics)) { @@ -384,10 +383,10 @@ public StringOrcPredicate(Iterable expectedValues) @Override protected boolean chunkMatchesStats(List chunk, ColumnStatistics columnStatistics) { - assertNull(columnStatistics.getBooleanStatistics()); - assertNull(columnStatistics.getIntegerStatistics()); - assertNull(columnStatistics.getDoubleStatistics()); - assertNull(columnStatistics.getDateStatistics()); + assertThat(columnStatistics.getBooleanStatistics()).isNull(); + assertThat(columnStatistics.getIntegerStatistics()).isNull(); + assertThat(columnStatistics.getDoubleStatistics()).isNull(); + assertThat(columnStatistics.getDateStatistics()).isNull(); // check basic statistics if (!super.chunkMatchesStats(chunk, columnStatistics)) { @@ -449,14 +448,14 @@ public CharOrcPredicate(Iterable expectedValues) @Override protected boolean chunkMatchesStats(List chunk, ColumnStatistics columnStatistics) { - assertNull(columnStatistics.getBooleanStatistics()); - assertNull(columnStatistics.getIntegerStatistics()); - assertNull(columnStatistics.getDoubleStatistics()); - assertNull(columnStatistics.getDateStatistics()); + assertThat(columnStatistics.getBooleanStatistics()).isNull(); + assertThat(columnStatistics.getIntegerStatistics()).isNull(); + assertThat(columnStatistics.getDoubleStatistics()).isNull(); + assertThat(columnStatistics.getDateStatistics()).isNull(); // bloom filter for char type in ORC require padded values (padded according to the type in the footer) // this is difficult to support so we skip for now - assertNull(columnStatistics.getBloomFilter()); + assertThat(columnStatistics.getBloomFilter()).isNull(); // check basic statistics if (!super.chunkMatchesStats(chunk, columnStatistics)) { @@ -505,10 +504,10 @@ public DateOrcPredicate(Iterable expectedValues) @Override protected boolean chunkMatchesStats(List chunk, ColumnStatistics columnStatistics) { - assertNull(columnStatistics.getBooleanStatistics()); - assertNull(columnStatistics.getIntegerStatistics()); - assertNull(columnStatistics.getDoubleStatistics()); - assertNull(columnStatistics.getStringStatistics()); + assertThat(columnStatistics.getBooleanStatistics()).isNull(); + assertThat(columnStatistics.getIntegerStatistics()).isNull(); + assertThat(columnStatistics.getDoubleStatistics()).isNull(); + assertThat(columnStatistics.getStringStatistics()).isNull(); // check basic statistics if (!super.chunkMatchesStats(chunk, columnStatistics)) { diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/TestOrcMetadataReader.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/TestOrcMetadataReader.java index 5a66a395b878..fc8fed2e6c0b 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/TestOrcMetadataReader.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/TestOrcMetadataReader.java @@ -40,8 +40,7 @@ import static java.lang.Character.MIN_CODE_POINT; import static java.lang.Character.MIN_SUPPLEMENTARY_CODE_POINT; import static java.lang.Character.MIN_SURROGATE; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; +import static org.assertj.core.api.Assertions.assertThat; public class TestOrcMetadataReader { @@ -58,10 +57,10 @@ public void testGetMinSlice() } Slice value = codePointToUtf8(codePoint); if (findStringStatisticTruncationPositionForOriginalOrcWriter(value) == value.length()) { - assertEquals(minStringTruncateToValidRange(value, ORIGINAL), value); + assertThat(minStringTruncateToValidRange(value, ORIGINAL)).isEqualTo(value); } else { - assertEquals(minStringTruncateToValidRange(value, ORIGINAL), minSlice); + assertThat(minStringTruncateToValidRange(value, ORIGINAL)).isEqualTo(minSlice); } } @@ -73,10 +72,10 @@ public void testGetMinSlice() } Slice value = concatSlice(prefix, codePointToUtf8(codePoint)); if (findStringStatisticTruncationPositionForOriginalOrcWriter(value) == value.length()) { - assertEquals(minStringTruncateToValidRange(value, ORIGINAL), value); + assertThat(minStringTruncateToValidRange(value, ORIGINAL)).isEqualTo(value); } else { - assertEquals(minStringTruncateToValidRange(value, ORIGINAL), prefix); + assertThat(minStringTruncateToValidRange(value, ORIGINAL)).isEqualTo(prefix); } } } @@ -94,10 +93,10 @@ public void testGetMaxSlice() } Slice value = codePointToUtf8(codePoint); if (findStringStatisticTruncationPositionForOriginalOrcWriter(value) == value.length()) { - assertEquals(maxStringTruncateToValidRange(value, ORIGINAL), value); + assertThat(maxStringTruncateToValidRange(value, ORIGINAL)).isEqualTo(value); } else { - assertEquals(maxStringTruncateToValidRange(value, ORIGINAL), maxByte); + assertThat(maxStringTruncateToValidRange(value, ORIGINAL)).isEqualTo(maxByte); } } @@ -110,10 +109,10 @@ public void testGetMaxSlice() } Slice value = concatSlice(prefix, codePointToUtf8(codePoint)); if (findStringStatisticTruncationPositionForOriginalOrcWriter(value) == value.length()) { - assertEquals(maxStringTruncateToValidRange(value, ORIGINAL), value); + assertThat(maxStringTruncateToValidRange(value, ORIGINAL)).isEqualTo(value); } else { - assertEquals(maxStringTruncateToValidRange(value, ORIGINAL), maxSlice); + assertThat(maxStringTruncateToValidRange(value, ORIGINAL)).isEqualTo(maxSlice); } } } @@ -176,65 +175,55 @@ public void testGetMaxSlice() public void testToStringStatistics() { // ORIGINAL version only produces stats at the row group level - assertNull(OrcMetadataReader.toStringStatistics( + assertThat(OrcMetadataReader.toStringStatistics( ORIGINAL, OrcProto.StringStatistics.newBuilder() .setMinimum("ant") .setMaximum("cat") .setSum(44) .build(), - false)); + false)).isNull(); // having only sum should work for current version for (boolean isRowGroup : ImmutableList.of(true, false)) { - assertEquals( - OrcMetadataReader.toStringStatistics( - ORC_HIVE_8732, - OrcProto.StringStatistics.newBuilder() - .setSum(45) - .build(), - isRowGroup), - new StringStatistics(null, null, 45)); + assertThat(OrcMetadataReader.toStringStatistics( + ORC_HIVE_8732, + OrcProto.StringStatistics.newBuilder() + .setSum(45) + .build(), + isRowGroup)).isEqualTo(new StringStatistics(null, null, 45)); } // and the ORIGINAL version row group stats (but not rolled up stats) - assertEquals( - OrcMetadataReader.toStringStatistics( - ORIGINAL, - OrcProto.StringStatistics.newBuilder() - .setSum(45) - .build(), - true), - new StringStatistics(null, null, 45)); + assertThat(OrcMetadataReader.toStringStatistics( + ORIGINAL, + OrcProto.StringStatistics.newBuilder() + .setSum(45) + .build(), + true)).isEqualTo(new StringStatistics(null, null, 45)); // having only a min or max should work - assertEquals( - OrcMetadataReader.toStringStatistics( - ORC_HIVE_8732, - OrcProto.StringStatistics.newBuilder() - .setMinimum("ant") - .build(), - true), - new StringStatistics(utf8Slice("ant"), null, 0)); - assertEquals( - OrcMetadataReader.toStringStatistics( - ORC_HIVE_8732, - OrcProto.StringStatistics.newBuilder() - .setMaximum("cat") - .build(), - true), - new StringStatistics(null, utf8Slice("cat"), 0)); + assertThat(OrcMetadataReader.toStringStatistics( + ORC_HIVE_8732, + OrcProto.StringStatistics.newBuilder() + .setMinimum("ant") + .build(), + true)).isEqualTo(new StringStatistics(utf8Slice("ant"), null, 0)); + assertThat(OrcMetadataReader.toStringStatistics( + ORC_HIVE_8732, + OrcProto.StringStatistics.newBuilder() + .setMaximum("cat") + .build(), + true)).isEqualTo(new StringStatistics(null, utf8Slice("cat"), 0)); // normal full stat - assertEquals( - OrcMetadataReader.toStringStatistics( - ORC_HIVE_8732, - OrcProto.StringStatistics.newBuilder() - .setMinimum("ant") - .setMaximum("cat") - .setSum(79) - .build(), - true), - new StringStatistics(utf8Slice("ant"), utf8Slice("cat"), 79)); + assertThat(OrcMetadataReader.toStringStatistics( + ORC_HIVE_8732, + OrcProto.StringStatistics.newBuilder() + .setMinimum("ant") + .setMaximum("cat") + .setSum(79) + .build(), + true)).isEqualTo(new StringStatistics(utf8Slice("ant"), utf8Slice("cat"), 79)); for (Slice prefix : ALL_UTF8_SEQUENCES) { for (int testCodePoint : TEST_CODE_POINTS) { @@ -250,34 +239,28 @@ public void testToStringStatistics() private static void testStringStatisticsTruncation(Slice testValue, HiveWriterVersion version) { - assertEquals( - OrcMetadataReader.toStringStatistics( - version, - OrcProto.StringStatistics.newBuilder() - .setMinimumBytes(ByteString.copyFrom(testValue.getBytes())) - .setMaximumBytes(ByteString.copyFrom(testValue.getBytes())) - .setSum(79) - .build(), - true), - createExpectedStringStatistics(version, testValue, testValue, 79)); - assertEquals( - OrcMetadataReader.toStringStatistics( - version, - OrcProto.StringStatistics.newBuilder() - .setMinimumBytes(ByteString.copyFrom(testValue.getBytes())) - .setSum(79) - .build(), - true), - createExpectedStringStatistics(version, testValue, null, 79)); - assertEquals( - OrcMetadataReader.toStringStatistics( - version, - OrcProto.StringStatistics.newBuilder() - .setMaximumBytes(ByteString.copyFrom(testValue.getBytes())) - .setSum(79) - .build(), - true), - createExpectedStringStatistics(version, null, testValue, 79)); + assertThat(OrcMetadataReader.toStringStatistics( + version, + OrcProto.StringStatistics.newBuilder() + .setMinimumBytes(ByteString.copyFrom(testValue.getBytes())) + .setMaximumBytes(ByteString.copyFrom(testValue.getBytes())) + .setSum(79) + .build(), + true)).isEqualTo(createExpectedStringStatistics(version, testValue, testValue, 79)); + assertThat(OrcMetadataReader.toStringStatistics( + version, + OrcProto.StringStatistics.newBuilder() + .setMinimumBytes(ByteString.copyFrom(testValue.getBytes())) + .setSum(79) + .build(), + true)).isEqualTo(createExpectedStringStatistics(version, testValue, null, 79)); + assertThat(OrcMetadataReader.toStringStatistics( + version, + OrcProto.StringStatistics.newBuilder() + .setMaximumBytes(ByteString.copyFrom(testValue.getBytes())) + .setSum(79) + .build(), + true)).isEqualTo(createExpectedStringStatistics(version, null, testValue, 79)); } private static StringStatistics createExpectedStringStatistics(HiveWriterVersion version, Slice min, Slice max, int sum) @@ -304,17 +287,17 @@ private static void testMinStringTruncateAtFirstReplacementCharacter(Slice prefi Slice codePoint = codePointToUtf8(testCodePoint); Slice value = concatSlice(prefix, codePoint, suffix); - assertEquals(minStringTruncateToValidRange(value, ORC_HIVE_8732), value); + assertThat(minStringTruncateToValidRange(value, ORC_HIVE_8732)).isEqualTo(value); // For ORIGINAL, skip prefixes that truncate if (prefix.equals(minStringTruncateToValidRange(prefix, ORIGINAL))) { if (testCodePoint == REPLACEMENT_CHARACTER_CODE_POINT || testCodePoint >= MIN_SUPPLEMENTARY_CODE_POINT) { // truncate at test code point - assertEquals(minStringTruncateToValidRange(value, ORIGINAL), prefix); + assertThat(minStringTruncateToValidRange(value, ORIGINAL)).isEqualTo(prefix); } else { // truncate in suffix (if at all) - assertEquals(minStringTruncateToValidRange(value, ORIGINAL), concatSlice(prefix, codePoint, minStringTruncateToValidRange(suffix, ORIGINAL))); + assertThat(minStringTruncateToValidRange(value, ORIGINAL)).isEqualTo(concatSlice(prefix, codePoint, minStringTruncateToValidRange(suffix, ORIGINAL))); } } } @@ -336,17 +319,17 @@ private static void testMaxStringTruncateAtFirstReplacementCharacter(Slice prefi Slice codePoint = codePointToUtf8(testCodePoint); Slice value = concatSlice(prefix, codePoint, suffix); - assertEquals(maxStringTruncateToValidRange(value, ORC_HIVE_8732), value); + assertThat(maxStringTruncateToValidRange(value, ORC_HIVE_8732)).isEqualTo(value); // For ORIGINAL, skip prefixes that truncate if (prefix.equals(maxStringTruncateToValidRange(prefix, ORIGINAL))) { if (testCodePoint == REPLACEMENT_CHARACTER_CODE_POINT || testCodePoint >= MIN_SUPPLEMENTARY_CODE_POINT) { // truncate at test code point - assertEquals(maxStringTruncateToValidRange(value, ORIGINAL), concatSlice(prefix, wrappedBuffer((byte) 0xFF))); + assertThat(maxStringTruncateToValidRange(value, ORIGINAL)).isEqualTo(concatSlice(prefix, wrappedBuffer((byte) 0xFF))); } else { // truncate in suffix (if at all) - assertEquals(maxStringTruncateToValidRange(value, ORIGINAL), concatSlice(prefix, codePoint, maxStringTruncateToValidRange(suffix, ORIGINAL))); + assertThat(maxStringTruncateToValidRange(value, ORIGINAL)).isEqualTo(concatSlice(prefix, codePoint, maxStringTruncateToValidRange(suffix, ORIGINAL))); } } } diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/AbstractRangeStatisticsTest.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/AbstractRangeStatisticsTest.java index a41393f73089..33be6f706e67 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/AbstractRangeStatisticsTest.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/AbstractRangeStatisticsTest.java @@ -13,8 +13,8 @@ */ package io.trino.orc.metadata.statistics; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public abstract class AbstractRangeStatisticsTest, T> { @@ -37,16 +37,16 @@ protected void assertMinMax(T min, T max) void assertRetainedSize(T min, T max, long expectedSizeInBytes) { - assertEquals(getCreateStatistics(min, max).getRetainedSizeInBytes(), expectedSizeInBytes); + assertThat(getCreateStatistics(min, max).getRetainedSizeInBytes()).isEqualTo(expectedSizeInBytes); } private void assertMinMaxStatistics(T min, T max) { R statistics = getCreateStatistics(min, max); - assertEquals(statistics.getMin(), min); - assertEquals(statistics.getMax(), max); + assertThat(statistics.getMin()).isEqualTo(min); + assertThat(statistics.getMax()).isEqualTo(max); - assertEquals(statistics, statistics); - assertEquals(statistics.hashCode(), statistics.hashCode()); + assertThat(statistics).isEqualTo(statistics); + assertThat(statistics.hashCode()).isEqualTo(statistics.hashCode()); } } diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/AbstractStatisticsBuilderTest.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/AbstractStatisticsBuilderTest.java index cd3b016eb19e..8e81fb5d4a07 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/AbstractStatisticsBuilderTest.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/AbstractStatisticsBuilderTest.java @@ -27,9 +27,7 @@ import static io.trino.orc.metadata.statistics.ColumnStatistics.mergeColumnStatistics; import static java.lang.Math.min; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; +import static org.assertj.core.api.Assertions.assertThat; public abstract class AbstractStatisticsBuilderTest { @@ -75,7 +73,7 @@ protected void assertMinAverageValueBytes(long expectedAverageValueBytes, List values) protected static void assertNoColumnStatistics(ColumnStatistics columnStatistics, long expectedNumberOfValues) { - assertEquals(columnStatistics.getNumberOfValues(), expectedNumberOfValues); - assertNull(columnStatistics.getBooleanStatistics()); - assertNull(columnStatistics.getIntegerStatistics()); - assertNull(columnStatistics.getDoubleStatistics()); - assertNull(columnStatistics.getStringStatistics()); - assertNull(columnStatistics.getDateStatistics()); - assertNull(columnStatistics.getDecimalStatistics()); - assertNull(columnStatistics.getBloomFilter()); + assertThat(columnStatistics.getNumberOfValues()).isEqualTo(expectedNumberOfValues); + assertThat(columnStatistics.getBooleanStatistics()).isNull(); + assertThat(columnStatistics.getIntegerStatistics()).isNull(); + assertThat(columnStatistics.getDoubleStatistics()).isNull(); + assertThat(columnStatistics.getStringStatistics()).isNull(); + assertThat(columnStatistics.getDateStatistics()).isNull(); + assertThat(columnStatistics.getDecimalStatistics()).isNull(); + assertThat(columnStatistics.getBloomFilter()).isNull(); } protected static void assertNoColumnStatistics(ColumnStatistics columnStatistics, int expectedNumberOfValues, int expectedNumberOfNanValues) { assertNoColumnStatistics(columnStatistics, expectedNumberOfValues); - assertEquals(columnStatistics.getNumberOfNanValues(), expectedNumberOfNanValues); + assertThat(columnStatistics.getNumberOfNanValues()).isEqualTo(expectedNumberOfNanValues); } private void assertColumnStatistics( @@ -183,58 +181,58 @@ static List insertEmptyColumnStatisticsAt(List 0) { - assertNotNull(columnStatistics.getBooleanStatistics()); + assertThat(columnStatistics.getBooleanStatistics()).isNotNull(); } else { - assertNull(columnStatistics.getBooleanStatistics()); + assertThat(columnStatistics.getBooleanStatistics()).isNull(); } if (statisticsType == StatisticsType.INTEGER && expectedNumberOfValues > 0) { assertRangeStatistics(columnStatistics.getIntegerStatistics(), expectedMin, expectedMax); } else { - assertNull(columnStatistics.getIntegerStatistics()); + assertThat(columnStatistics.getIntegerStatistics()).isNull(); } if (statisticsType == StatisticsType.DOUBLE && expectedNumberOfValues > 0) { assertRangeStatistics(columnStatistics.getDoubleStatistics(), expectedMin, expectedMax); } else { - assertNull(columnStatistics.getDoubleStatistics()); + assertThat(columnStatistics.getDoubleStatistics()).isNull(); } if (statisticsType == StatisticsType.STRING && expectedNumberOfValues > 0) { assertRangeStatistics(columnStatistics.getStringStatistics(), expectedMin, expectedMax); } else { - assertNull(columnStatistics.getStringStatistics()); + assertThat(columnStatistics.getStringStatistics()).isNull(); } if (statisticsType == StatisticsType.DATE && expectedNumberOfValues > 0) { assertRangeStatistics(columnStatistics.getDateStatistics(), expectedMin, expectedMax); } else { - assertNull(columnStatistics.getDateStatistics()); + assertThat(columnStatistics.getDateStatistics()).isNull(); } if (statisticsType == StatisticsType.DECIMAL && expectedNumberOfValues > 0) { assertRangeStatistics(columnStatistics.getDecimalStatistics(), expectedMin, expectedMax); } else { - assertNull(columnStatistics.getDecimalStatistics()); + assertThat(columnStatistics.getDecimalStatistics()).isNull(); } - assertNull(columnStatistics.getBloomFilter()); + assertThat(columnStatistics.getBloomFilter()).isNull(); } void assertRangeStatistics(RangeStatistics rangeStatistics, T expectedMin, T expectedMax) { - assertNotNull(rangeStatistics); - assertEquals(rangeStatistics.getMin(), expectedMin); - assertEquals(rangeStatistics.getMax(), expectedMax); + assertThat(rangeStatistics).isNotNull(); + assertThat(rangeStatistics.getMin()).isEqualTo(expectedMin); + assertThat(rangeStatistics.getMax()).isEqualTo(expectedMax); } public static class AggregateColumnStatistics diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestBinaryStatisticsBuilder.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestBinaryStatisticsBuilder.java index 3268d4dbb480..6982ac477f11 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestBinaryStatisticsBuilder.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestBinaryStatisticsBuilder.java @@ -25,8 +25,7 @@ import static io.trino.orc.metadata.statistics.AbstractStatisticsBuilderTest.StatisticsType.NONE; import static io.trino.orc.metadata.statistics.BinaryStatistics.BINARY_VALUE_BYTES_OVERHEAD; import static io.trino.orc.metadata.statistics.ColumnStatistics.mergeColumnStatistics; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; +import static org.assertj.core.api.Assertions.assertThat; public class TestBinaryStatisticsBuilder extends AbstractStatisticsBuilderTest @@ -101,12 +100,12 @@ private void assertMergedBinaryStatistics(List statisticsList, private void assertBinaryStatistics(ColumnStatistics columnStatistics, int expectedNumberOfValues, long expectedSum) { if (expectedNumberOfValues > 0) { - assertEquals(columnStatistics.getNumberOfValues(), expectedNumberOfValues); - assertEquals(columnStatistics.getBinaryStatistics().getSum(), expectedSum); + assertThat(columnStatistics.getNumberOfValues()).isEqualTo(expectedNumberOfValues); + assertThat(columnStatistics.getBinaryStatistics().getSum()).isEqualTo(expectedSum); } else { - assertNull(columnStatistics.getBinaryStatistics()); - assertEquals(columnStatistics.getNumberOfValues(), 0); + assertThat(columnStatistics.getBinaryStatistics()).isNull(); + assertThat(columnStatistics.getNumberOfValues()).isEqualTo(0); } } } diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestBooleanStatisticsBuilder.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestBooleanStatisticsBuilder.java index 5e6a1755f2d7..7b73da88209b 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestBooleanStatisticsBuilder.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestBooleanStatisticsBuilder.java @@ -22,7 +22,7 @@ import static io.trino.orc.metadata.statistics.AbstractStatisticsBuilderTest.StatisticsType.BOOLEAN; import static io.trino.orc.metadata.statistics.BooleanStatistics.BOOLEAN_VALUE_BYTES; import static io.trino.orc.metadata.statistics.ColumnStatistics.mergeColumnStatistics; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestBooleanStatisticsBuilder extends AbstractStatisticsBuilderTest @@ -116,7 +116,7 @@ private void assertBooleanStatistics(ColumnStatistics columnStatistics, int expe { if (expectedNumberOfValues > 0) { assertColumnStatistics(columnStatistics, expectedNumberOfValues, null, null); - assertEquals(columnStatistics.getBooleanStatistics().getTrueValueCount(), trueValueCount); + assertThat(columnStatistics.getBooleanStatistics().getTrueValueCount()).isEqualTo(trueValueCount); } } } diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestDateStatisticsBuilder.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestDateStatisticsBuilder.java index fe9a6edc6088..11f37a759f76 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestDateStatisticsBuilder.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestDateStatisticsBuilder.java @@ -23,10 +23,8 @@ import static io.trino.orc.metadata.statistics.DateStatistics.DATE_VALUE_BYTES; import static java.lang.Integer.MAX_VALUE; import static java.lang.Integer.MIN_VALUE; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; public class TestDateStatisticsBuilder extends AbstractStatisticsBuilderTest @@ -86,10 +84,10 @@ public void testBloomFilter() statisticsBuilder.addValue(1011); statisticsBuilder.addValue(4242); BloomFilter bloomFilter = statisticsBuilder.buildColumnStatistics().getBloomFilter(); - assertNotNull(bloomFilter); - assertTrue(bloomFilter.testLong(314)); - assertTrue(bloomFilter.testLong(1011)); - assertTrue(bloomFilter.testLong(4242)); - assertFalse(bloomFilter.testLong(100)); + assertThat(bloomFilter).isNotNull(); + assertThat(bloomFilter.testLong(314)).isTrue(); + assertThat(bloomFilter.testLong(1011)).isTrue(); + assertThat(bloomFilter.testLong(4242)).isTrue(); + assertThat(bloomFilter.testLong(100)).isFalse(); } } diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestDoubleStatisticsBuilder.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestDoubleStatisticsBuilder.java index 13898f084057..18b4e6358c00 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestDoubleStatisticsBuilder.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestDoubleStatisticsBuilder.java @@ -26,10 +26,7 @@ import static java.lang.Double.NEGATIVE_INFINITY; import static java.lang.Double.NaN; import static java.lang.Double.POSITIVE_INFINITY; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestDoubleStatisticsBuilder extends AbstractStatisticsBuilderTest @@ -82,7 +79,7 @@ public void testNanValue() statisticsBuilder.addValue(42.42); ColumnStatistics columnStatistics = statisticsBuilder.buildColumnStatistics(); assertColumnStatistics(columnStatistics, 1, 42.42, 42.42); - assertEquals(columnStatistics.getNumberOfNanValues(), 0); + assertThat(columnStatistics.getNumberOfNanValues()).isEqualTo(0); statisticsBuilder.addValue(NaN); assertNoColumnStatistics(statisticsBuilder.buildColumnStatistics(), 2, 1); statisticsBuilder.addValue(42.42); @@ -106,10 +103,10 @@ public void testBloomFilter() statisticsBuilder.addValue(10.11); statisticsBuilder.addValue(42.42); BloomFilter bloomFilter = statisticsBuilder.buildColumnStatistics().getBloomFilter(); - assertNotNull(bloomFilter); - assertTrue(bloomFilter.testDouble(3.14)); - assertTrue(bloomFilter.testDouble(10.11)); - assertTrue(bloomFilter.testDouble(42.42)); - assertFalse(bloomFilter.testDouble(100)); + assertThat(bloomFilter).isNotNull(); + assertThat(bloomFilter.testDouble(3.14)).isTrue(); + assertThat(bloomFilter.testDouble(10.11)).isTrue(); + assertThat(bloomFilter.testDouble(42.42)).isTrue(); + assertThat(bloomFilter.testDouble(100)).isFalse(); } } diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestIntegerStatisticsBuilder.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestIntegerStatisticsBuilder.java index 6bb7471ac208..06c7b1e9c4e6 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestIntegerStatisticsBuilder.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestIntegerStatisticsBuilder.java @@ -27,11 +27,7 @@ import static io.trino.orc.metadata.statistics.IntegerStatistics.INTEGER_VALUE_BYTES; import static java.lang.Long.MAX_VALUE; import static java.lang.Long.MIN_VALUE; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestIntegerStatisticsBuilder extends AbstractStatisticsBuilderTest @@ -169,12 +165,12 @@ private static void assertMergedIntegerStatistics(List statist private static void assertIntegerStatistics(ColumnStatistics columnStatistics, int expectedNumberOfValues, Long expectedSum) { if (expectedNumberOfValues > 0) { - assertEquals(columnStatistics.getNumberOfValues(), expectedNumberOfValues); - assertEquals(columnStatistics.getIntegerStatistics().getSum(), expectedSum); + assertThat(columnStatistics.getNumberOfValues()).isEqualTo(expectedNumberOfValues); + assertThat(columnStatistics.getIntegerStatistics().getSum()).isEqualTo(expectedSum); } else { - assertNull(columnStatistics.getIntegerStatistics()); - assertEquals(columnStatistics.getNumberOfValues(), 0); + assertThat(columnStatistics.getIntegerStatistics()).isNull(); + assertThat(columnStatistics.getNumberOfValues()).isEqualTo(0); } } @@ -186,10 +182,10 @@ public void testBloomFilter() statisticsBuilder.addValue(1011); statisticsBuilder.addValue(4242); BloomFilter bloomFilter = statisticsBuilder.buildColumnStatistics().getBloomFilter(); - assertNotNull(bloomFilter); - assertTrue(bloomFilter.testLong(314)); - assertTrue(bloomFilter.testLong(1011)); - assertTrue(bloomFilter.testLong(4242)); - assertFalse(bloomFilter.testLong(100)); + assertThat(bloomFilter).isNotNull(); + assertThat(bloomFilter.testLong(314)).isTrue(); + assertThat(bloomFilter.testLong(1011)).isTrue(); + assertThat(bloomFilter.testLong(4242)).isTrue(); + assertThat(bloomFilter.testLong(100)).isFalse(); } } diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestShortDecimalStatisticsBuilder.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestShortDecimalStatisticsBuilder.java index 3109953cb166..15252da314cd 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestShortDecimalStatisticsBuilder.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestShortDecimalStatisticsBuilder.java @@ -27,8 +27,7 @@ import static io.trino.orc.metadata.statistics.ShortDecimalStatisticsBuilder.SHORT_DECIMAL_VALUE_BYTES; import static java.lang.Long.MAX_VALUE; import static java.lang.Long.MIN_VALUE; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; +import static org.assertj.core.api.Assertions.assertThat; public class TestShortDecimalStatisticsBuilder extends AbstractStatisticsBuilderTest @@ -74,8 +73,8 @@ public void testMinAverageValueBytes() @Override void assertRangeStatistics(RangeStatistics rangeStatistics, Long expectedMin, Long expectedMax) { - assertNotNull(rangeStatistics); - assertEquals(rangeStatistics.getMin(), new BigDecimal(BigInteger.valueOf(expectedMin), SCALE)); - assertEquals(rangeStatistics.getMax(), new BigDecimal(BigInteger.valueOf(expectedMax), SCALE)); + assertThat(rangeStatistics).isNotNull(); + assertThat(rangeStatistics.getMin()).isEqualTo(new BigDecimal(BigInteger.valueOf(expectedMin), SCALE)); + assertThat(rangeStatistics.getMax()).isEqualTo(new BigDecimal(BigInteger.valueOf(expectedMax), SCALE)); } } diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestStringStatisticsBuilder.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestStringStatisticsBuilder.java index 46f985c0e03d..9b7b7d2012be 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestStringStatisticsBuilder.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestStringStatisticsBuilder.java @@ -29,11 +29,6 @@ import static io.trino.orc.metadata.statistics.StringStatistics.STRING_VALUE_BYTES_OVERHEAD; import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; public class TestStringStatisticsBuilder extends AbstractStatisticsBuilderTest @@ -247,8 +242,8 @@ public void testCopyStatsToSaveMemory() Slice stats = statisticsBuilder.buildColumnStatistics().getStringStatistics().getMax(); // assert we only spend 1 byte for stats - assertNotNull(stats); - assertEquals(stats.getRetainedSize(), Slices.wrappedBuffer(new byte[1]).getRetainedSize()); + assertThat(stats).isNotNull(); + assertThat(stats.getRetainedSize()).isEqualTo(Slices.wrappedBuffer(new byte[1]).getRetainedSize()); } @Test @@ -282,13 +277,13 @@ private static void assertMinMaxValuesWithLimit(Slice expectedMin, Slice expecte private static void assertMinMax(StringStatistics actualStringStatistics, Slice expectedMin, Slice expectedMax) { if (expectedMax == null && expectedMin == null) { - assertNull(actualStringStatistics); + assertThat(actualStringStatistics).isNull(); return; } - assertNotNull(actualStringStatistics); - assertEquals(actualStringStatistics.getMin(), expectedMin); - assertEquals(actualStringStatistics.getMax(), expectedMax); + assertThat(actualStringStatistics).isNotNull(); + assertThat(actualStringStatistics.getMin()).isEqualTo(expectedMin); + assertThat(actualStringStatistics.getMax()).isEqualTo(expectedMax); } private static ColumnStatistics stringColumnStatistics(Slice minimum, Slice maximum) @@ -311,12 +306,12 @@ private static ColumnStatistics stringColumnStatistics(Slice minimum, Slice maxi private void assertStringStatistics(ColumnStatistics columnStatistics, int expectedNumberOfValues, long expectedSum) { if (expectedNumberOfValues > 0) { - assertEquals(columnStatistics.getNumberOfValues(), expectedNumberOfValues); - assertEquals(columnStatistics.getStringStatistics().getSum(), expectedSum); + assertThat(columnStatistics.getNumberOfValues()).isEqualTo(expectedNumberOfValues); + assertThat(columnStatistics.getStringStatistics().getSum()).isEqualTo(expectedSum); } else { - assertNull(columnStatistics.getStringStatistics()); - assertEquals(columnStatistics.getNumberOfValues(), 0); + assertThat(columnStatistics.getStringStatistics()).isNull(); + assertThat(columnStatistics.getNumberOfValues()).isEqualTo(0); } } @@ -328,11 +323,11 @@ public void testBloomFilter() statisticsBuilder.addValue(MEDIUM_BOTTOM_VALUE); statisticsBuilder.addValue(MEDIUM_BOTTOM_VALUE); BloomFilter bloomFilter = statisticsBuilder.buildColumnStatistics().getBloomFilter(); - assertNotNull(bloomFilter); - assertTrue(bloomFilter.testSlice(LOW_BOTTOM_VALUE)); - assertTrue(bloomFilter.testSlice(MEDIUM_BOTTOM_VALUE)); - assertTrue(bloomFilter.testSlice(MEDIUM_BOTTOM_VALUE)); - assertFalse(bloomFilter.testSlice(LOW_TOP_VALUE)); + assertThat(bloomFilter).isNotNull(); + assertThat(bloomFilter.testSlice(LOW_BOTTOM_VALUE)).isTrue(); + assertThat(bloomFilter.testSlice(MEDIUM_BOTTOM_VALUE)).isTrue(); + assertThat(bloomFilter.testSlice(MEDIUM_BOTTOM_VALUE)).isTrue(); + assertThat(bloomFilter.testSlice(LOW_TOP_VALUE)).isFalse(); } @Test diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestTimestampStatisticsBuilder.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestTimestampStatisticsBuilder.java index 9645ea7d24b8..26b227bde41f 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestTimestampStatisticsBuilder.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestTimestampStatisticsBuilder.java @@ -23,9 +23,7 @@ import static io.trino.orc.metadata.statistics.TimestampStatistics.TIMESTAMP_VALUE_BYTES; import static java.lang.Long.MAX_VALUE; import static java.lang.Long.MIN_VALUE; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestTimestampStatisticsBuilder extends AbstractStatisticsBuilderTest @@ -73,10 +71,10 @@ public void testBloomFilter() statisticsBuilder.addValue(1011L); statisticsBuilder.addValue(4242L); BloomFilter bloomFilter = statisticsBuilder.buildColumnStatistics().getBloomFilter(); - assertNotNull(bloomFilter); - assertTrue(bloomFilter.testLong(314L)); - assertTrue(bloomFilter.testLong(1011L)); - assertTrue(bloomFilter.testLong(4242L)); - assertFalse(bloomFilter.testLong(100L)); + assertThat(bloomFilter).isNotNull(); + assertThat(bloomFilter.testLong(314L)).isTrue(); + assertThat(bloomFilter.testLong(1011L)).isTrue(); + assertThat(bloomFilter.testLong(4242L)).isTrue(); + assertThat(bloomFilter.testLong(100L)).isFalse(); } } diff --git a/lib/trino-orc/src/test/java/io/trino/orc/stream/AbstractTestValueStream.java b/lib/trino-orc/src/test/java/io/trino/orc/stream/AbstractTestValueStream.java index 4f6a13f598e5..f7e0d88599b6 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/stream/AbstractTestValueStream.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/stream/AbstractTestValueStream.java @@ -25,8 +25,7 @@ import java.io.IOException; import java.util.List; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public abstract class AbstractTestValueStream, R extends ValueInputStream> { @@ -44,7 +43,7 @@ protected void testWriteValue(List> groups) outputStream.recordCheckpoint(); group.forEach(value -> writeValue(outputStream, value)); - assertTrue(outputStream.getRetainedBytes() >= retainedBytes); + assertThat(outputStream.getRetainedBytes() >= retainedBytes).isTrue(); retainedBytes = outputStream.getRetainedBytes(); } outputStream.close(); @@ -53,12 +52,12 @@ protected void testWriteValue(List> groups) StreamDataOutput streamDataOutput = outputStream.getStreamDataOutput(new OrcColumnId(33)); streamDataOutput.writeData(sliceOutput); Stream stream = streamDataOutput.getStream(); - assertEquals(stream.getStreamKind(), StreamKind.DATA); - assertEquals(stream.getColumnId(), new OrcColumnId(33)); - assertEquals(stream.getLength(), sliceOutput.size()); + assertThat(stream.getStreamKind()).isEqualTo(StreamKind.DATA); + assertThat(stream.getColumnId()).isEqualTo(new OrcColumnId(33)); + assertThat(stream.getLength()).isEqualTo(sliceOutput.size()); List checkpoints = outputStream.getCheckpoints(); - assertEquals(checkpoints.size(), groups.size()); + assertThat(checkpoints.size()).isEqualTo(groups.size()); R valueStream = createValueStream(sliceOutput.slice()); for (List group : groups) { @@ -67,7 +66,9 @@ protected void testWriteValue(List> groups) index++; T actualValue = readValue(valueStream); if (!actualValue.equals(expectedValue)) { - assertEquals(actualValue, expectedValue, "index=" + index); + assertThat(actualValue) + .describedAs("index=" + index) + .isEqualTo(expectedValue); } } } @@ -76,7 +77,7 @@ protected void testWriteValue(List> groups) for (T expectedValue : groups.get(groupIndex)) { T actualValue = readValue(valueStream); if (!actualValue.equals(expectedValue)) { - assertEquals(actualValue, expectedValue); + assertThat(actualValue).isEqualTo(expectedValue); } } } diff --git a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestBooleanOutputStream.java b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestBooleanOutputStream.java index d3edb8559d39..d7d9b1d73fbe 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestBooleanOutputStream.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestBooleanOutputStream.java @@ -24,9 +24,7 @@ import java.util.List; import static io.trino.orc.metadata.CompressionKind.NONE; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestBooleanOutputStream { @@ -71,26 +69,26 @@ public void testWriteBoolean() buffer.writeDataTo(slice); Slice singleWriteBuffer = slice.slice(); - assertEquals(batchWriteCheckpoints.size(), singleWriteCheckpoints.size()); + assertThat(batchWriteCheckpoints.size()).isEqualTo(singleWriteCheckpoints.size()); for (int i = 0; i < batchWriteCheckpoints.size(); i++) { - assertTrue(checkpointsEqual(batchWriteCheckpoints.get(i), singleWriteCheckpoints.get(i))); + assertThat(checkpointsEqual(batchWriteCheckpoints.get(i), singleWriteCheckpoints.get(i))).isTrue(); } - assertEquals(batchWriteBuffer, singleWriteBuffer); + assertThat(batchWriteBuffer).isEqualTo(singleWriteBuffer); } } private static boolean checkpointsEqual(BooleanStreamCheckpoint left, BooleanStreamCheckpoint right) { - assertNotNull(left); - assertNotNull(right); + assertThat(left).isNotNull(); + assertThat(right).isNotNull(); if (left.getOffset() != right.getOffset()) { return false; } ByteStreamCheckpoint leftCheckpoint = left.getByteStreamCheckpoint(); ByteStreamCheckpoint rightCheckpoint = right.getByteStreamCheckpoint(); - assertNotNull(leftCheckpoint); - assertNotNull(rightCheckpoint); + assertThat(leftCheckpoint).isNotNull(); + assertThat(rightCheckpoint).isNotNull(); return leftCheckpoint.getInputStreamCheckpoint() == rightCheckpoint.getInputStreamCheckpoint() && leftCheckpoint.getOffset() == rightCheckpoint.getOffset(); diff --git a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestBooleanStream.java b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestBooleanStream.java index ea5a8971c63b..9a48bdec0f98 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestBooleanStream.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestBooleanStream.java @@ -34,7 +34,7 @@ import static io.trino.memory.context.AggregatedMemoryContext.newSimpleAggregatedMemoryContext; import static io.trino.orc.OrcDecompressor.createOrcDecompressor; import static io.trino.orc.metadata.CompressionKind.SNAPPY; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestBooleanStream extends AbstractTestValueStream @@ -94,15 +94,15 @@ public void testWriteMultiple() StreamDataOutput streamDataOutput = outputStream.getStreamDataOutput(new OrcColumnId(33)); streamDataOutput.writeData(sliceOutput); Stream stream = streamDataOutput.getStream(); - assertEquals(stream.getStreamKind(), StreamKind.DATA); - assertEquals(stream.getColumnId(), new OrcColumnId(33)); - assertEquals(stream.getLength(), sliceOutput.size()); + assertThat(stream.getStreamKind()).isEqualTo(StreamKind.DATA); + assertThat(stream.getColumnId()).isEqualTo(new OrcColumnId(33)); + assertThat(stream.getLength()).isEqualTo(sliceOutput.size()); BooleanInputStream valueStream = createValueStream(sliceOutput.slice()); for (int index = 0; index < expectedValues.size(); index++) { boolean expectedValue = expectedValues.getBoolean(index); boolean actualValue = readValue(valueStream); - assertEquals(actualValue, expectedValue); + assertThat(actualValue).isEqualTo(expectedValue); } } } diff --git a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestDecimalStream.java b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestDecimalStream.java index b5fb0d2e8e9c..a1f05ed01044 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestDecimalStream.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestDecimalStream.java @@ -34,8 +34,8 @@ import static io.trino.spi.type.Decimals.MAX_UNSCALED_DECIMAL; import static io.trino.spi.type.Decimals.MIN_UNSCALED_DECIMAL; import static java.math.BigInteger.ONE; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestDecimalStream { @@ -102,7 +102,7 @@ public void testSkipsValue() DecimalInputStream stream = new DecimalInputStream(chunkLoader); stream.skip(1); - assertEquals(nextShortDecimalValue(stream), Long.MIN_VALUE); + assertThat(nextShortDecimalValue(stream)).isEqualTo(Long.MIN_VALUE); } @Test @@ -118,7 +118,7 @@ public void testSkipToEdgeOfChunkShort() DecimalInputStream stream = new DecimalInputStream(loader); stream.skip(1); - assertEquals(nextShortDecimalValue(stream), Long.MAX_VALUE); + assertThat(nextShortDecimalValue(stream)).isEqualTo(Long.MAX_VALUE); } @Test @@ -133,8 +133,8 @@ public void testReadToEdgeOfChunkShort() DecimalInputStream stream = new DecimalInputStream(loader); - assertEquals(nextShortDecimalValue(stream), Long.MAX_VALUE); - assertEquals(nextShortDecimalValue(stream), Long.MAX_VALUE); + assertThat(nextShortDecimalValue(stream)).isEqualTo(Long.MAX_VALUE); + assertThat(nextShortDecimalValue(stream)).isEqualTo(Long.MAX_VALUE); } @Test @@ -150,7 +150,7 @@ public void testSkipToEdgeOfChunkLong() DecimalInputStream stream = new DecimalInputStream(loader); stream.skip(1); - assertEquals(nextLongDecimalValue(stream), BigInteger.valueOf(Long.MAX_VALUE)); + assertThat(nextLongDecimalValue(stream)).isEqualTo(BigInteger.valueOf(Long.MAX_VALUE)); } @Test @@ -165,8 +165,8 @@ public void testReadToEdgeOfChunkLong() DecimalInputStream stream = new DecimalInputStream(loader); - assertEquals(nextLongDecimalValue(stream), BigInteger.valueOf(Long.MAX_VALUE)); - assertEquals(nextLongDecimalValue(stream), BigInteger.valueOf(Long.MAX_VALUE)); + assertThat(nextLongDecimalValue(stream)).isEqualTo(BigInteger.valueOf(Long.MAX_VALUE)); + assertThat(nextLongDecimalValue(stream)).isEqualTo(BigInteger.valueOf(Long.MAX_VALUE)); } private static Slice encodeValues(List values) @@ -184,14 +184,14 @@ private static void assertReadsShortValue(long value) throws IOException { DecimalInputStream stream = new DecimalInputStream(decimalChunkLoader(BigInteger.valueOf(value))); - assertEquals(nextShortDecimalValue(stream), value); + assertThat(nextShortDecimalValue(stream)).isEqualTo(value); } private static void assertReadsLongValue(BigInteger value) throws IOException { DecimalInputStream stream = new DecimalInputStream(decimalChunkLoader(value)); - assertEquals(nextLongDecimalValue(stream), value); + assertThat(nextLongDecimalValue(stream)).isEqualTo(value); } private static void assertShortValueReadFails(BigInteger value) diff --git a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongBitPacker.java b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongBitPacker.java index 5d3f1c3c0f46..12abb8f21709 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongBitPacker.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongBitPacker.java @@ -21,7 +21,7 @@ import static io.trino.orc.stream.TestingBitPackingUtils.unpackGeneric; import static java.lang.String.format; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestLongBitPacker { @@ -51,9 +51,13 @@ private static void assertUnpacking(LongBitPacker packer, int length) RandomByteInputStream actualInput = new RandomByteInputStream(); packer.unpack(actual, offset, length, width, actualInput); for (int i = offset; i < length + offset; i++) { - assertEquals(actual[i], expected[i], format("index = %s, length = %s, width = %s, offset = %s", i, length, width, offset)); + assertThat(actual[i]) + .describedAs(format("index = %s, length = %s, width = %s, offset = %s", i, length, width, offset)) + .isEqualTo(expected[i]); } - assertEquals(actualInput.getReadBytes(), expectedInput.getReadBytes(), format("Wrong number of bytes read for length = %s, width = %s, offset = %s", length, width, offset)); + assertThat(actualInput.getReadBytes()) + .describedAs(format("Wrong number of bytes read for length = %s, width = %s, offset = %s", length, width, offset)) + .isEqualTo(expectedInput.getReadBytes()); } } } diff --git a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongDecode.java b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongDecode.java index f8f731a0627b..844133c7d659 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongDecode.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongDecode.java @@ -28,7 +28,7 @@ import static io.trino.memory.context.AggregatedMemoryContext.newSimpleAggregatedMemoryContext; import static io.trino.orc.stream.LongDecode.readVInt; import static io.trino.orc.stream.LongDecode.writeVLong; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestLongDecode { @@ -82,22 +82,22 @@ private static void assertVIntRoundTrip(SliceOutput output, long value, boolean writeVLong(output, value, signed); Slice trinoBytes = output.slice().copy(); if (!trinoBytes.equals(hiveBytes)) { - assertEquals(trinoBytes, hiveBytes); + assertThat(trinoBytes).isEqualTo(hiveBytes); } // read using Hive's code if (signed) { long readValueOld = readVslong(hiveBytes.getInput()); - assertEquals(readValueOld, value); + assertThat(readValueOld).isEqualTo(value); } else { long readValueOld = readVulong(hiveBytes.getInput()); - assertEquals(readValueOld, value); + assertThat(readValueOld).isEqualTo(value); } // read using Trino's code long readValueNew = readVInt(signed, new OrcInputStream(OrcChunkLoader.create(new OrcDataSourceId("test"), hiveBytes, Optional.empty(), newSimpleAggregatedMemoryContext()))); - assertEquals(readValueNew, value); + assertThat(readValueNew).isEqualTo(value); } // diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/TestParquetTimestampUtils.java b/lib/trino-parquet/src/test/java/io/trino/parquet/TestParquetTimestampUtils.java index d38de5fffc4f..45d735eea125 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/TestParquetTimestampUtils.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/TestParquetTimestampUtils.java @@ -25,7 +25,7 @@ import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; import static java.time.ZoneOffset.UTC; import static org.apache.hadoop.hive.ql.io.parquet.timestamp.NanoTimeUtils.getNanoTime; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestParquetTimestampUtils { @@ -45,8 +45,8 @@ public void testInvalidBinaryLength() decodeInt96Timestamp(Binary.fromConstantByteArray(invalidLengthBinaryTimestamp)); } catch (TrinoException e) { - assertEquals(e.getErrorCode(), NOT_SUPPORTED.toErrorCode()); - assertEquals(e.getMessage(), "Parquet timestamp must be 12 bytes, actual 8"); + assertThat(e.getErrorCode()).isEqualTo(NOT_SUPPORTED.toErrorCode()); + assertThat(e.getMessage()).isEqualTo("Parquet timestamp must be 12 bytes, actual 8"); } } @@ -55,7 +55,7 @@ private static void assertTimestampCorrect(LocalDateTime dateTime) Timestamp timestamp = Timestamp.ofEpochSecond(dateTime.toEpochSecond(UTC), dateTime.getNano()); Binary timestampBytes = getNanoTime(timestamp, false).toBinary(); DecodedTimestamp decodedTimestamp = decodeInt96Timestamp(timestampBytes); - assertEquals(decodedTimestamp.epochSeconds(), dateTime.toEpochSecond(UTC)); - assertEquals(decodedTimestamp.nanosOfSecond(), dateTime.getNano()); + assertThat(decodedTimestamp.epochSeconds()).isEqualTo(dateTime.toEpochSecond(UTC)); + assertThat(decodedTimestamp.nanosOfSecond()).isEqualTo(dateTime.getNano()); } } diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/TestTupleDomainParquetPredicate.java b/lib/trino-parquet/src/test/java/io/trino/parquet/TestTupleDomainParquetPredicate.java index 43bd3959a87a..206c6161249f 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/TestTupleDomainParquetPredicate.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/TestTupleDomainParquetPredicate.java @@ -113,9 +113,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.joda.time.DateTimeZone.UTC; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestTupleDomainParquetPredicate { @@ -126,12 +123,12 @@ public void testBoolean() throws ParquetCorruptionException { ColumnDescriptor columnDescriptor = createColumnDescriptor(PrimitiveTypeName.BOOLEAN, "BooleanColumn"); - assertEquals(getDomain(columnDescriptor, BOOLEAN, 0L, null, ID, UTC), all(BOOLEAN)); + assertThat(getDomain(columnDescriptor, BOOLEAN, 0L, null, ID, UTC)).isEqualTo(all(BOOLEAN)); - assertEquals(getDomain(columnDescriptor, BOOLEAN, 10, booleanColumnStats(true, true), ID, UTC), singleValue(BOOLEAN, true)); - assertEquals(getDomain(columnDescriptor, BOOLEAN, 10, booleanColumnStats(false, false), ID, UTC), singleValue(BOOLEAN, false)); + assertThat(getDomain(columnDescriptor, BOOLEAN, 10, booleanColumnStats(true, true), ID, UTC)).isEqualTo(singleValue(BOOLEAN, true)); + assertThat(getDomain(columnDescriptor, BOOLEAN, 10, booleanColumnStats(false, false), ID, UTC)).isEqualTo(singleValue(BOOLEAN, false)); - assertEquals(getDomain(columnDescriptor, BOOLEAN, 20, booleanColumnStats(false, true), ID, UTC), all(BOOLEAN)); + assertThat(getDomain(columnDescriptor, BOOLEAN, 20, booleanColumnStats(false, true), ID, UTC)).isEqualTo(all(BOOLEAN)); } private static BooleanStatistics booleanColumnStats(boolean minimum, boolean maximum) @@ -148,14 +145,14 @@ public void testBigint() throws ParquetCorruptionException { ColumnDescriptor columnDescriptor = createColumnDescriptor(INT64, "BigintColumn"); - assertEquals(getDomain(columnDescriptor, BIGINT, 0, null, ID, UTC), all(BIGINT)); + assertThat(getDomain(columnDescriptor, BIGINT, 0, null, ID, UTC)).isEqualTo(all(BIGINT)); - assertEquals(getDomain(columnDescriptor, BIGINT, 10, longColumnStats(100L, 100L), ID, UTC), singleValue(BIGINT, 100L)); + assertThat(getDomain(columnDescriptor, BIGINT, 10, longColumnStats(100L, 100L), ID, UTC)).isEqualTo(singleValue(BIGINT, 100L)); - assertEquals(getDomain(columnDescriptor, BIGINT, 10, longColumnStats(0L, 100L), ID, UTC), create(ValueSet.ofRanges(range(BIGINT, 0L, true, 100L, true)), false)); + assertThat(getDomain(columnDescriptor, BIGINT, 10, longColumnStats(0L, 100L), ID, UTC)).isEqualTo(create(ValueSet.ofRanges(range(BIGINT, 0L, true, 100L, true)), false)); - assertEquals(getDomain(columnDescriptor, BIGINT, 20, longOnlyNullsStats(10), ID, UTC), Domain.all(BIGINT)); - assertEquals(getDomain(columnDescriptor, BIGINT, 20, longOnlyNullsStats(20), ID, UTC), Domain.onlyNull(BIGINT)); + assertThat(getDomain(columnDescriptor, BIGINT, 20, longOnlyNullsStats(10), ID, UTC)).isEqualTo(all(BIGINT)); + assertThat(getDomain(columnDescriptor, BIGINT, 20, longOnlyNullsStats(20), ID, UTC)).isEqualTo(onlyNull(BIGINT)); // fail on corrupted statistics assertThatExceptionOfType(ParquetCorruptionException.class) .isThrownBy(() -> getDomain(columnDescriptor, BIGINT, 10, longColumnStats(100L, 10L), ID, UTC)) @@ -167,15 +164,15 @@ public void testInteger() throws ParquetCorruptionException { ColumnDescriptor columnDescriptor = createColumnDescriptor(INT32, "IntegerColumn"); - assertEquals(getDomain(columnDescriptor, INTEGER, 0, null, ID, UTC), all(INTEGER)); + assertThat(getDomain(columnDescriptor, INTEGER, 0, null, ID, UTC)).isEqualTo(all(INTEGER)); - assertEquals(getDomain(columnDescriptor, INTEGER, 10, longColumnStats(100, 100), ID, UTC), singleValue(INTEGER, 100L)); + assertThat(getDomain(columnDescriptor, INTEGER, 10, longColumnStats(100, 100), ID, UTC)).isEqualTo(singleValue(INTEGER, 100L)); - assertEquals(getDomain(columnDescriptor, INTEGER, 10, longColumnStats(0, 100), ID, UTC), create(ValueSet.ofRanges(range(INTEGER, 0L, true, 100L, true)), false)); + assertThat(getDomain(columnDescriptor, INTEGER, 10, longColumnStats(0, 100), ID, UTC)).isEqualTo(create(ValueSet.ofRanges(range(INTEGER, 0L, true, 100L, true)), false)); - assertEquals(getDomain(columnDescriptor, INTEGER, 20, longColumnStats(0, 2147483648L), ID, UTC), notNull(INTEGER)); + assertThat(getDomain(columnDescriptor, INTEGER, 20, longColumnStats(0, 2147483648L), ID, UTC)).isEqualTo(notNull(INTEGER)); - assertEquals(getDomain(columnDescriptor, INTEGER, 20, longOnlyNullsStats(10), ID, UTC), create(ValueSet.all(INTEGER), true)); + assertThat(getDomain(columnDescriptor, INTEGER, 20, longOnlyNullsStats(10), ID, UTC)).isEqualTo(create(ValueSet.all(INTEGER), true)); // fail on corrupted statistics assertThatExceptionOfType(ParquetCorruptionException.class) .isThrownBy(() -> getDomain(columnDescriptor, INTEGER, 10, longColumnStats(2147483648L, 10), ID, UTC)) @@ -187,15 +184,15 @@ public void testSmallint() throws ParquetCorruptionException { ColumnDescriptor columnDescriptor = createColumnDescriptor(INT32, "SmallintColumn"); - assertEquals(getDomain(columnDescriptor, SMALLINT, 0, null, ID, UTC), all(SMALLINT)); + assertThat(getDomain(columnDescriptor, SMALLINT, 0, null, ID, UTC)).isEqualTo(all(SMALLINT)); - assertEquals(getDomain(columnDescriptor, SMALLINT, 10, longColumnStats(100, 100), ID, UTC), singleValue(SMALLINT, 100L)); + assertThat(getDomain(columnDescriptor, SMALLINT, 10, longColumnStats(100, 100), ID, UTC)).isEqualTo(singleValue(SMALLINT, 100L)); - assertEquals(getDomain(columnDescriptor, SMALLINT, 10, longColumnStats(0, 100), ID, UTC), create(ValueSet.ofRanges(range(SMALLINT, 0L, true, 100L, true)), false)); + assertThat(getDomain(columnDescriptor, SMALLINT, 10, longColumnStats(0, 100), ID, UTC)).isEqualTo(create(ValueSet.ofRanges(range(SMALLINT, 0L, true, 100L, true)), false)); - assertEquals(getDomain(columnDescriptor, SMALLINT, 20, longColumnStats(0, 2147483648L), ID, UTC), notNull(SMALLINT)); + assertThat(getDomain(columnDescriptor, SMALLINT, 20, longColumnStats(0, 2147483648L), ID, UTC)).isEqualTo(notNull(SMALLINT)); - assertEquals(getDomain(columnDescriptor, SMALLINT, 20, longOnlyNullsStats(10), ID, UTC), create(ValueSet.all(SMALLINT), true)); + assertThat(getDomain(columnDescriptor, SMALLINT, 20, longOnlyNullsStats(10), ID, UTC)).isEqualTo(create(ValueSet.all(SMALLINT), true)); // fail on corrupted statistics assertThatExceptionOfType(ParquetCorruptionException.class) .isThrownBy(() -> getDomain(columnDescriptor, SMALLINT, 10, longColumnStats(2147483648L, 10), ID, UTC)) @@ -207,15 +204,15 @@ public void testTinyint() throws ParquetCorruptionException { ColumnDescriptor columnDescriptor = createColumnDescriptor(INT32, "TinyintColumn"); - assertEquals(getDomain(columnDescriptor, TINYINT, 0, null, ID, UTC), all(TINYINT)); + assertThat(getDomain(columnDescriptor, TINYINT, 0, null, ID, UTC)).isEqualTo(all(TINYINT)); - assertEquals(getDomain(columnDescriptor, TINYINT, 10, longColumnStats(100, 100), ID, UTC), singleValue(TINYINT, 100L)); + assertThat(getDomain(columnDescriptor, TINYINT, 10, longColumnStats(100, 100), ID, UTC)).isEqualTo(singleValue(TINYINT, 100L)); - assertEquals(getDomain(columnDescriptor, TINYINT, 10, longColumnStats(0, 100), ID, UTC), create(ValueSet.ofRanges(range(TINYINT, 0L, true, 100L, true)), false)); + assertThat(getDomain(columnDescriptor, TINYINT, 10, longColumnStats(0, 100), ID, UTC)).isEqualTo(create(ValueSet.ofRanges(range(TINYINT, 0L, true, 100L, true)), false)); - assertEquals(getDomain(columnDescriptor, TINYINT, 20, longColumnStats(0, 2147483648L), ID, UTC), notNull(TINYINT)); + assertThat(getDomain(columnDescriptor, TINYINT, 20, longColumnStats(0, 2147483648L), ID, UTC)).isEqualTo(notNull(TINYINT)); - assertEquals(getDomain(columnDescriptor, TINYINT, 20, longOnlyNullsStats(10), ID, UTC), create(ValueSet.all(TINYINT), true)); + assertThat(getDomain(columnDescriptor, TINYINT, 20, longOnlyNullsStats(10), ID, UTC)).isEqualTo(create(ValueSet.all(TINYINT), true)); // fail on corrupted statistics assertThatExceptionOfType(ParquetCorruptionException.class) .isThrownBy(() -> getDomain(columnDescriptor, TINYINT, 10, longColumnStats(2147483648L, 10), ID, UTC)) @@ -228,13 +225,13 @@ public void testShortDecimal() { ColumnDescriptor columnDescriptor = createColumnDescriptor(INT32, "ShortDecimalColumn"); Type type = createDecimalType(5, 2); - assertEquals(getDomain(columnDescriptor, type, 0, null, ID, UTC), all(type)); + assertThat(getDomain(columnDescriptor, type, 0, null, ID, UTC)).isEqualTo(all(type)); - assertEquals(getDomain(columnDescriptor, type, 10, longColumnStats(10012L, 10012L), ID, UTC), singleValue(type, 10012L)); + assertThat(getDomain(columnDescriptor, type, 10, longColumnStats(10012L, 10012L), ID, UTC)).isEqualTo(singleValue(type, 10012L)); // Test that statistics overflowing the size of the type are not used - assertEquals(getDomain(columnDescriptor, type, 10, longColumnStats(100012L, 100012L), ID, UTC), notNull(type)); + assertThat(getDomain(columnDescriptor, type, 10, longColumnStats(100012L, 100012L), ID, UTC)).isEqualTo(notNull(type)); - assertEquals(getDomain(columnDescriptor, type, 10, longColumnStats(0L, 100L), ID, UTC), create(ValueSet.ofRanges(range(type, 0L, true, 100L, true)), false)); + assertThat(getDomain(columnDescriptor, type, 10, longColumnStats(0L, 100L), ID, UTC)).isEqualTo(create(ValueSet.ofRanges(range(type, 0L, true, 100L, true)), false)); // fail on corrupted statistics assertThatExceptionOfType(ParquetCorruptionException.class) .isThrownBy(() -> getDomain(columnDescriptor, type, 10, longColumnStats(100L, 10L), ID, UTC)) @@ -247,13 +244,13 @@ public void testShortDecimalWithNoScale() { ColumnDescriptor columnDescriptor = createColumnDescriptor(INT32, "ShortDecimalColumnWithNoScale"); Type type = createDecimalType(5, 0); - assertEquals(getDomain(columnDescriptor, type, 0, null, ID, UTC), all(type)); + assertThat(getDomain(columnDescriptor, type, 0, null, ID, UTC)).isEqualTo(all(type)); - assertEquals(getDomain(columnDescriptor, type, 10, longColumnStats(100L, 100L), ID, UTC), singleValue(type, 100L)); + assertThat(getDomain(columnDescriptor, type, 10, longColumnStats(100L, 100L), ID, UTC)).isEqualTo(singleValue(type, 100L)); // Test that statistics overflowing the size of the type are not used - assertEquals(getDomain(columnDescriptor, type, 10, longColumnStats(123456L, 123456), ID, UTC), notNull(type)); + assertThat(getDomain(columnDescriptor, type, 10, longColumnStats(123456L, 123456), ID, UTC)).isEqualTo(notNull(type)); - assertEquals(getDomain(columnDescriptor, type, 10, longColumnStats(0L, 100L), ID, UTC), create(ValueSet.ofRanges(range(type, 0L, true, 100L, true)), false)); + assertThat(getDomain(columnDescriptor, type, 10, longColumnStats(0L, 100L), ID, UTC)).isEqualTo(create(ValueSet.ofRanges(range(type, 0L, true, 100L, true)), false)); // fail on corrupted statistics assertThatExceptionOfType(ParquetCorruptionException.class) .isThrownBy(() -> getDomain(columnDescriptor, type, 10, longColumnStats(100L, 10L), ID, UTC)) @@ -272,12 +269,10 @@ public void testLongDecimal() Int128 hundred = Int128.valueOf(100L); Int128 max = Int128.valueOf(maximum); - assertEquals(getDomain(columnDescriptor, type, 0, null, ID, UTC), all(type)); - assertEquals(getDomain(columnDescriptor, type, 10, binaryColumnStats(maximum, maximum), ID, UTC), singleValue(type, max)); + assertThat(getDomain(columnDescriptor, type, 0, null, ID, UTC)).isEqualTo(all(type)); + assertThat(getDomain(columnDescriptor, type, 10, binaryColumnStats(maximum, maximum), ID, UTC)).isEqualTo(singleValue(type, max)); - assertEquals( - getDomain(columnDescriptor, type, 10, binaryColumnStats(0L, 100L), ID, UTC), - create(ValueSet.ofRanges(range(type, zero, true, hundred, true)), false)); + assertThat(getDomain(columnDescriptor, type, 10, binaryColumnStats(0L, 100L), ID, UTC)).isEqualTo(create(ValueSet.ofRanges(range(type, zero, true, hundred, true)), false)); // fail on corrupted statistics assertThatExceptionOfType(ParquetCorruptionException.class) .isThrownBy(() -> getDomain(columnDescriptor, type, 10, binaryColumnStats(100L, 10L), ID, UTC)) @@ -292,11 +287,11 @@ public void testLongDecimalWithNoScale() DecimalType type = createDecimalType(20, 0); Int128 zero = Int128.ZERO; Int128 hundred = Int128.valueOf(100L); - assertEquals(getDomain(columnDescriptor, type, 0, null, ID, UTC), all(type)); + assertThat(getDomain(columnDescriptor, type, 0, null, ID, UTC)).isEqualTo(all(type)); - assertEquals(getDomain(columnDescriptor, type, 10, binaryColumnStats(100L, 100L), ID, UTC), singleValue(type, hundred)); + assertThat(getDomain(columnDescriptor, type, 10, binaryColumnStats(100L, 100L), ID, UTC)).isEqualTo(singleValue(type, hundred)); - assertEquals(getDomain(columnDescriptor, type, 10, binaryColumnStats(0L, 100L), ID, UTC), create(ValueSet.ofRanges(range(type, zero, true, hundred, true)), false)); + assertThat(getDomain(columnDescriptor, type, 10, binaryColumnStats(0L, 100L), ID, UTC)).isEqualTo(create(ValueSet.ofRanges(range(type, zero, true, hundred, true)), false)); // fail on corrupted statistics assertThatExceptionOfType(ParquetCorruptionException.class) .isThrownBy(() -> getDomain(columnDescriptor, type, 10, binaryColumnStats(100L, 10L), ID, UTC)) @@ -308,23 +303,23 @@ public void testDouble() throws Exception { ColumnDescriptor columnDescriptor = createColumnDescriptor(PrimitiveTypeName.DOUBLE, "DoubleColumn"); - assertEquals(getDomain(columnDescriptor, DOUBLE, 0, null, ID, UTC), all(DOUBLE)); + assertThat(getDomain(columnDescriptor, DOUBLE, 0, null, ID, UTC)).isEqualTo(all(DOUBLE)); - assertEquals(getDomain(columnDescriptor, DOUBLE, 10, doubleColumnStats(42.24, 42.24), ID, UTC), singleValue(DOUBLE, 42.24)); + assertThat(getDomain(columnDescriptor, DOUBLE, 10, doubleColumnStats(42.24, 42.24), ID, UTC)).isEqualTo(singleValue(DOUBLE, 42.24)); - assertEquals(getDomain(columnDescriptor, DOUBLE, 10, doubleColumnStats(3.3, 42.24), ID, UTC), create(ValueSet.ofRanges(range(DOUBLE, 3.3, true, 42.24, true)), false)); + assertThat(getDomain(columnDescriptor, DOUBLE, 10, doubleColumnStats(3.3, 42.24), ID, UTC)).isEqualTo(create(ValueSet.ofRanges(range(DOUBLE, 3.3, true, 42.24, true)), false)); - assertEquals(getDomain(columnDescriptor, DOUBLE, 10, doubleColumnStats(NaN, NaN), ID, UTC), Domain.notNull(DOUBLE)); + assertThat(getDomain(columnDescriptor, DOUBLE, 10, doubleColumnStats(NaN, NaN), ID, UTC)).isEqualTo(notNull(DOUBLE)); - assertEquals(getDomain(columnDescriptor, DOUBLE, 10, doubleColumnStats(NaN, NaN, true), ID, UTC), Domain.all(DOUBLE)); + assertThat(getDomain(columnDescriptor, DOUBLE, 10, doubleColumnStats(NaN, NaN, true), ID, UTC)).isEqualTo(all(DOUBLE)); - assertEquals(getDomain(columnDescriptor, DOUBLE, 10, doubleColumnStats(3.3, NaN), ID, UTC), Domain.notNull(DOUBLE)); + assertThat(getDomain(columnDescriptor, DOUBLE, 10, doubleColumnStats(3.3, NaN), ID, UTC)).isEqualTo(notNull(DOUBLE)); - assertEquals(getDomain(columnDescriptor, DOUBLE, 10, doubleColumnStats(3.3, NaN, true), ID, UTC), Domain.all(DOUBLE)); + assertThat(getDomain(columnDescriptor, DOUBLE, 10, doubleColumnStats(3.3, NaN, true), ID, UTC)).isEqualTo(all(DOUBLE)); - assertEquals(getDomain(DOUBLE, doubleDictionaryDescriptor(NaN)), Domain.all(DOUBLE)); + assertThat(getDomain(DOUBLE, doubleDictionaryDescriptor(NaN))).isEqualTo(all(DOUBLE)); - assertEquals(getDomain(DOUBLE, doubleDictionaryDescriptor(3.3, NaN)), Domain.all(DOUBLE)); + assertThat(getDomain(DOUBLE, doubleDictionaryDescriptor(3.3, NaN))).isEqualTo(all(DOUBLE)); // fail on corrupted statistics assertThatExceptionOfType(ParquetCorruptionException.class) @@ -337,13 +332,13 @@ public void testString() throws ParquetCorruptionException { ColumnDescriptor columnDescriptor = createColumnDescriptor(BINARY, "StringColumn"); - assertEquals(getDomain(columnDescriptor, createUnboundedVarcharType(), 0, null, ID, UTC), all(createUnboundedVarcharType())); + assertThat(getDomain(columnDescriptor, createUnboundedVarcharType(), 0, null, ID, UTC)).isEqualTo(all(createUnboundedVarcharType())); - assertEquals(getDomain(columnDescriptor, createUnboundedVarcharType(), 10, stringColumnStats("taco", "taco"), ID, UTC), singleValue(createUnboundedVarcharType(), utf8Slice("taco"))); + assertThat(getDomain(columnDescriptor, createUnboundedVarcharType(), 10, stringColumnStats("taco", "taco"), ID, UTC)).isEqualTo(singleValue(createUnboundedVarcharType(), utf8Slice("taco"))); - assertEquals(getDomain(columnDescriptor, createUnboundedVarcharType(), 10, stringColumnStats("apple", "taco"), ID, UTC), create(ValueSet.ofRanges(range(createUnboundedVarcharType(), utf8Slice("apple"), true, utf8Slice("taco"), true)), false)); + assertThat(getDomain(columnDescriptor, createUnboundedVarcharType(), 10, stringColumnStats("apple", "taco"), ID, UTC)).isEqualTo(create(ValueSet.ofRanges(range(createUnboundedVarcharType(), utf8Slice("apple"), true, utf8Slice("taco"), true)), false)); - assertEquals(getDomain(columnDescriptor, createUnboundedVarcharType(), 10, stringColumnStats("中国", "美利坚"), ID, UTC), create(ValueSet.ofRanges(range(createUnboundedVarcharType(), utf8Slice("中国"), true, utf8Slice("美利坚"), true)), false)); + assertThat(getDomain(columnDescriptor, createUnboundedVarcharType(), 10, stringColumnStats("中国", "美利坚"), ID, UTC)).isEqualTo(create(ValueSet.ofRanges(range(createUnboundedVarcharType(), utf8Slice("中国"), true, utf8Slice("美利坚"), true)), false)); // fail on corrupted statistics assertThatExceptionOfType(ParquetCorruptionException.class) @@ -365,28 +360,26 @@ public void testFloat() throws Exception { ColumnDescriptor columnDescriptor = createColumnDescriptor(FLOAT, "FloatColumn"); - assertEquals(getDomain(columnDescriptor, REAL, 0, null, ID, UTC), all(REAL)); + assertThat(getDomain(columnDescriptor, REAL, 0, null, ID, UTC)).isEqualTo(all(REAL)); float minimum = 4.3f; float maximum = 40.3f; - assertEquals(getDomain(columnDescriptor, REAL, 10, floatColumnStats(minimum, minimum), ID, UTC), singleValue(REAL, (long) floatToRawIntBits(minimum))); + assertThat(getDomain(columnDescriptor, REAL, 10, floatColumnStats(minimum, minimum), ID, UTC)).isEqualTo(singleValue(REAL, (long) floatToRawIntBits(minimum))); - assertEquals( - getDomain(columnDescriptor, REAL, 10, floatColumnStats(minimum, maximum), ID, UTC), - create(ValueSet.ofRanges(range(REAL, (long) floatToRawIntBits(minimum), true, (long) floatToRawIntBits(maximum), true)), false)); + assertThat(getDomain(columnDescriptor, REAL, 10, floatColumnStats(minimum, maximum), ID, UTC)).isEqualTo(create(ValueSet.ofRanges(range(REAL, (long) floatToRawIntBits(minimum), true, (long) floatToRawIntBits(maximum), true)), false)); - assertEquals(getDomain(columnDescriptor, REAL, 10, floatColumnStats(NaN, NaN), ID, UTC), Domain.notNull(REAL)); + assertThat(getDomain(columnDescriptor, REAL, 10, floatColumnStats(NaN, NaN), ID, UTC)).isEqualTo(notNull(REAL)); - assertEquals(getDomain(columnDescriptor, REAL, 10, floatColumnStats(NaN, NaN, true), ID, UTC), Domain.all(REAL)); + assertThat(getDomain(columnDescriptor, REAL, 10, floatColumnStats(NaN, NaN, true), ID, UTC)).isEqualTo(all(REAL)); - assertEquals(getDomain(columnDescriptor, REAL, 10, floatColumnStats(minimum, NaN), ID, UTC), Domain.notNull(REAL)); + assertThat(getDomain(columnDescriptor, REAL, 10, floatColumnStats(minimum, NaN), ID, UTC)).isEqualTo(notNull(REAL)); - assertEquals(getDomain(columnDescriptor, REAL, 10, floatColumnStats(minimum, NaN, true), ID, UTC), Domain.all(REAL)); + assertThat(getDomain(columnDescriptor, REAL, 10, floatColumnStats(minimum, NaN, true), ID, UTC)).isEqualTo(all(REAL)); - assertEquals(getDomain(REAL, floatDictionaryDescriptor(NaN)), Domain.all(REAL)); + assertThat(getDomain(REAL, floatDictionaryDescriptor(NaN))).isEqualTo(all(REAL)); - assertEquals(getDomain(REAL, floatDictionaryDescriptor(minimum, NaN)), Domain.all(REAL)); + assertThat(getDomain(REAL, floatDictionaryDescriptor(minimum, NaN))).isEqualTo(all(REAL)); // fail on corrupted statistics assertThatExceptionOfType(ParquetCorruptionException.class) @@ -399,9 +392,9 @@ public void testDate() throws ParquetCorruptionException { ColumnDescriptor columnDescriptor = createColumnDescriptor(INT32, "DateColumn"); - assertEquals(getDomain(columnDescriptor, DATE, 0, null, ID, UTC), all(DATE)); - assertEquals(getDomain(columnDescriptor, DATE, 10, intColumnStats(100, 100), ID, UTC), singleValue(DATE, 100L)); - assertEquals(getDomain(columnDescriptor, DATE, 10, intColumnStats(0, 100), ID, UTC), create(ValueSet.ofRanges(range(DATE, 0L, true, 100L, true)), false)); + assertThat(getDomain(columnDescriptor, DATE, 0, null, ID, UTC)).isEqualTo(all(DATE)); + assertThat(getDomain(columnDescriptor, DATE, 10, intColumnStats(100, 100), ID, UTC)).isEqualTo(singleValue(DATE, 100L)); + assertThat(getDomain(columnDescriptor, DATE, 10, intColumnStats(0, 100), ID, UTC)).isEqualTo(create(ValueSet.ofRanges(range(DATE, 0L, true, 100L, true)), false)); // fail on corrupted statistics assertThatExceptionOfType(ParquetCorruptionException.class) @@ -427,12 +420,10 @@ public void testTimestampInt96(int precision, LocalDateTime baseTime, Object bas { ColumnDescriptor columnDescriptor = createColumnDescriptor(INT96, "TimestampColumn"); TimestampType timestampType = createTimestampType(precision); - assertEquals(getDomain(columnDescriptor, timestampType, 0, null, ID, UTC), all(timestampType)); - assertEquals(getDomain(columnDescriptor, timestampType, 10, timestampColumnStats(baseTime, baseTime), ID, UTC), singleValue(timestampType, baseDomainValue)); + assertThat(getDomain(columnDescriptor, timestampType, 0, null, ID, UTC)).isEqualTo(all(timestampType)); + assertThat(getDomain(columnDescriptor, timestampType, 10, timestampColumnStats(baseTime, baseTime), ID, UTC)).isEqualTo(singleValue(timestampType, baseDomainValue)); // INT96 binary ranges ignored when min <> max - assertEquals( - getDomain(columnDescriptor, timestampType, 10, timestampColumnStats(baseTime.minusSeconds(10), baseTime), ID, UTC), - create(ValueSet.all(timestampType), false)); + assertThat(getDomain(columnDescriptor, timestampType, 10, timestampColumnStats(baseTime.minusSeconds(10), baseTime), ID, UTC)).isEqualTo(create(ValueSet.all(timestampType), false)); } @Test(dataProvider = "testTimestampInt64DataProvider") @@ -460,7 +451,7 @@ public void testTimestampInt64(TimeUnit timeUnit, int precision, LocalDateTime b ColumnDescriptor columnDescriptor = new ColumnDescriptor(new String[]{}, type, 0, 0); TimestampType timestampType = createTimestampType(precision); - assertEquals(getDomain(columnDescriptor, timestampType, 0, null, ID, UTC), all(timestampType)); + assertThat(getDomain(columnDescriptor, timestampType, 0, null, ID, UTC)).isEqualTo(all(timestampType)); LocalDateTime maxTime = baseTime.plus(Duration.ofMillis(50)); Object maxDomainValue; @@ -476,9 +467,8 @@ else if (baseDomainValue instanceof LongTimestamp longTimestamp) { long minValue = toEpochWithPrecision(baseTime, parquetPrecision); long maxValue = toEpochWithPrecision(maxTime, parquetPrecision); - assertEquals(getDomain(columnDescriptor, timestampType, 10, longColumnStats(minValue, minValue), ID, UTC), singleValue(timestampType, baseDomainValue)); - assertEquals(getDomain(columnDescriptor, timestampType, 10, longColumnStats(minValue, maxValue), ID, UTC), - Domain.create(ValueSet.ofRanges(range(timestampType, baseDomainValue, true, maxDomainValue, true)), false)); + assertThat(getDomain(columnDescriptor, timestampType, 10, longColumnStats(minValue, minValue), ID, UTC)).isEqualTo(singleValue(timestampType, baseDomainValue)); + assertThat(getDomain(columnDescriptor, timestampType, 10, longColumnStats(minValue, maxValue), ID, UTC)).isEqualTo(create(ValueSet.ofRanges(range(timestampType, baseDomainValue, true, maxDomainValue, true)), false)); } @DataProvider @@ -609,15 +599,15 @@ public void testVarcharMatchesWithDictionaryDescriptor() TupleDomain effectivePredicate = getEffectivePredicate(column, createVarcharType(255), EMPTY_SLICE); TupleDomainParquetPredicate parquetPredicate = new TupleDomainParquetPredicate(effectivePredicate, singletonList(column), UTC); DictionaryPage page = new DictionaryPage(Slices.wrappedBuffer(new byte[] {0, 0, 0, 0}), 1, PLAIN_DICTIONARY); - assertTrue(parquetPredicate.matches(new DictionaryDescriptor(column, true, Optional.of(page)))); - assertTrue(parquetPredicate.matches(new DictionaryDescriptor(column, false, Optional.of(page)))); + assertThat(parquetPredicate.matches(new DictionaryDescriptor(column, true, Optional.of(page)))).isTrue(); + assertThat(parquetPredicate.matches(new DictionaryDescriptor(column, false, Optional.of(page)))).isTrue(); effectivePredicate = withColumnDomains(ImmutableMap.of( column, singleValue(createVarcharType(255), Slices.utf8Slice("abc"), true))); parquetPredicate = new TupleDomainParquetPredicate(effectivePredicate, singletonList(column), UTC); - assertTrue(parquetPredicate.matches(new DictionaryDescriptor(column, true, Optional.of(page)))); - assertFalse(parquetPredicate.matches(new DictionaryDescriptor(column, false, Optional.of(page)))); + assertThat(parquetPredicate.matches(new DictionaryDescriptor(column, true, Optional.of(page)))).isTrue(); + assertThat(parquetPredicate.matches(new DictionaryDescriptor(column, false, Optional.of(page)))).isFalse(); } @Test @@ -635,24 +625,24 @@ public void testEmptyDictionary() withColumnDomains(singletonMap(descriptor, notNull(type))), singletonList(column), UTC); - assertFalse(predicate.matches(new DictionaryDescriptor(column, true, Optional.of(dictionary)))); - assertFalse(predicate.matches(new DictionaryDescriptor(column, false, Optional.of(dictionary)))); + assertThat(predicate.matches(new DictionaryDescriptor(column, true, Optional.of(dictionary)))).isFalse(); + assertThat(predicate.matches(new DictionaryDescriptor(column, false, Optional.of(dictionary)))).isFalse(); // only nulls allowed predicate = new TupleDomainParquetPredicate( withColumnDomains(singletonMap(descriptor, onlyNull(type))), singletonList(column), UTC); - assertTrue(predicate.matches(new DictionaryDescriptor(column, true, Optional.of(dictionary)))); - assertFalse(predicate.matches(new DictionaryDescriptor(column, false, Optional.of(dictionary)))); + assertThat(predicate.matches(new DictionaryDescriptor(column, true, Optional.of(dictionary)))).isTrue(); + assertThat(predicate.matches(new DictionaryDescriptor(column, false, Optional.of(dictionary)))).isFalse(); // mixed non-nulls and nulls allowed predicate = new TupleDomainParquetPredicate( withColumnDomains(singletonMap(descriptor, singleValue(type, EMPTY_SLICE, true))), singletonList(column), UTC); - assertTrue(predicate.matches(new DictionaryDescriptor(column, true, Optional.of(dictionary)))); - assertFalse(predicate.matches(new DictionaryDescriptor(column, false, Optional.of(dictionary)))); + assertThat(predicate.matches(new DictionaryDescriptor(column, true, Optional.of(dictionary)))).isTrue(); + assertThat(predicate.matches(new DictionaryDescriptor(column, false, Optional.of(dictionary)))).isFalse(); } @Test diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/predicate/TestPredicateUtils.java b/lib/trino-parquet/src/test/java/io/trino/parquet/predicate/TestPredicateUtils.java index f70716d7fa6d..d5eb7faba743 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/predicate/TestPredicateUtils.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/predicate/TestPredicateUtils.java @@ -40,33 +40,32 @@ import static org.apache.parquet.hadoop.metadata.ColumnPath.fromDotString; import static org.apache.parquet.hadoop.metadata.CompressionCodecName.UNCOMPRESSED; import static org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName.BINARY; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestPredicateUtils { @Test public void testIsStatisticsOverflow() { - assertFalse(isStatisticsOverflow(TINYINT, -10L, 10L)); - assertTrue(isStatisticsOverflow(TINYINT, -129L, 10L)); - assertTrue(isStatisticsOverflow(TINYINT, -10L, 129L)); + assertThat(isStatisticsOverflow(TINYINT, -10L, 10L)).isFalse(); + assertThat(isStatisticsOverflow(TINYINT, -129L, 10L)).isTrue(); + assertThat(isStatisticsOverflow(TINYINT, -10L, 129L)).isTrue(); - assertFalse(isStatisticsOverflow(SMALLINT, -32_000L, 32_000L)); - assertTrue(isStatisticsOverflow(SMALLINT, -100_000L, 32_000L)); - assertTrue(isStatisticsOverflow(SMALLINT, -32_000L, 100_000L)); + assertThat(isStatisticsOverflow(SMALLINT, -32_000L, 32_000L)).isFalse(); + assertThat(isStatisticsOverflow(SMALLINT, -100_000L, 32_000L)).isTrue(); + assertThat(isStatisticsOverflow(SMALLINT, -32_000L, 100_000L)).isTrue(); - assertFalse(isStatisticsOverflow(INTEGER, -2_000_000_000L, 2_000_000_000L)); - assertTrue(isStatisticsOverflow(INTEGER, -3_000_000_000L, 2_000_000_000L)); - assertTrue(isStatisticsOverflow(INTEGER, -2_000_000_000L, 3_000_000_000L)); + assertThat(isStatisticsOverflow(INTEGER, -2_000_000_000L, 2_000_000_000L)).isFalse(); + assertThat(isStatisticsOverflow(INTEGER, -3_000_000_000L, 2_000_000_000L)).isTrue(); + assertThat(isStatisticsOverflow(INTEGER, -2_000_000_000L, 3_000_000_000L)).isTrue(); // short decimal - assertFalse(isStatisticsOverflow(createDecimalType(5, 0), -10_000L, 10_000L)); - assertTrue(isStatisticsOverflow(createDecimalType(5, 0), -100_000L, 10_000L)); - assertTrue(isStatisticsOverflow(createDecimalType(5, 0), -10_000L, 100_000L)); + assertThat(isStatisticsOverflow(createDecimalType(5, 0), -10_000L, 10_000L)).isFalse(); + assertThat(isStatisticsOverflow(createDecimalType(5, 0), -100_000L, 10_000L)).isTrue(); + assertThat(isStatisticsOverflow(createDecimalType(5, 0), -10_000L, 100_000L)).isTrue(); // long decimal - assertFalse(isStatisticsOverflow(createDecimalType(19, 0), -1_000_000_000_000_000_000L, 1_000_000_000_000_000_000L)); + assertThat(isStatisticsOverflow(createDecimalType(19, 0), -1_000_000_000_000_000_000L, 1_000_000_000_000_000_000L)).isFalse(); } @Test @@ -81,27 +80,45 @@ public void testDictionaryEncodingV1() Set mixedDictionary = ImmutableSet.of(PLAIN_DICTIONARY, PLAIN); Set dictionary = ImmutableSet.of(PLAIN_DICTIONARY); - assertFalse(isOnlyDictionaryEncodingPages(createColumnMetaDataV1(union(required, notDictionary))), "required notDictionary"); - assertFalse(isOnlyDictionaryEncodingPages(createColumnMetaDataV1(union(optional, notDictionary))), "optional notDictionary"); - assertFalse(isOnlyDictionaryEncodingPages(createColumnMetaDataV1(union(repeated, notDictionary))), "repeated notDictionary"); - assertFalse(isOnlyDictionaryEncodingPages(createColumnMetaDataV1(union(required, mixedDictionary))), "required mixedDictionary"); - assertFalse(isOnlyDictionaryEncodingPages(createColumnMetaDataV1(union(optional, mixedDictionary))), "optional mixedDictionary"); - assertFalse(isOnlyDictionaryEncodingPages(createColumnMetaDataV1(union(repeated, mixedDictionary))), "repeated mixedDictionary"); - assertTrue(isOnlyDictionaryEncodingPages(createColumnMetaDataV1(union(required, dictionary))), "required dictionary"); - assertTrue(isOnlyDictionaryEncodingPages(createColumnMetaDataV1(union(optional, dictionary))), "optional dictionary"); - assertTrue(isOnlyDictionaryEncodingPages(createColumnMetaDataV1(union(repeated, dictionary))), "repeated dictionary"); + assertThat(isOnlyDictionaryEncodingPages(createColumnMetaDataV1(union(required, notDictionary)))) + .describedAs("required notDictionary") + .isFalse(); + assertThat(isOnlyDictionaryEncodingPages(createColumnMetaDataV1(union(optional, notDictionary)))) + .describedAs("optional notDictionary") + .isFalse(); + assertThat(isOnlyDictionaryEncodingPages(createColumnMetaDataV1(union(repeated, notDictionary)))) + .describedAs("repeated notDictionary") + .isFalse(); + assertThat(isOnlyDictionaryEncodingPages(createColumnMetaDataV1(union(required, mixedDictionary)))) + .describedAs("required mixedDictionary") + .isFalse(); + assertThat(isOnlyDictionaryEncodingPages(createColumnMetaDataV1(union(optional, mixedDictionary)))) + .describedAs("optional mixedDictionary") + .isFalse(); + assertThat(isOnlyDictionaryEncodingPages(createColumnMetaDataV1(union(repeated, mixedDictionary)))) + .describedAs("repeated mixedDictionary") + .isFalse(); + assertThat(isOnlyDictionaryEncodingPages(createColumnMetaDataV1(union(required, dictionary)))) + .describedAs("required dictionary") + .isTrue(); + assertThat(isOnlyDictionaryEncodingPages(createColumnMetaDataV1(union(optional, dictionary)))) + .describedAs("optional dictionary") + .isTrue(); + assertThat(isOnlyDictionaryEncodingPages(createColumnMetaDataV1(union(repeated, dictionary)))) + .describedAs("repeated dictionary") + .isTrue(); } @Test @SuppressWarnings("deprecation") public void testDictionaryEncodingV2() { - assertTrue(isOnlyDictionaryEncodingPages(createColumnMetaDataV2(RLE_DICTIONARY))); - assertTrue(isOnlyDictionaryEncodingPages(createColumnMetaDataV2(PLAIN_DICTIONARY))); - assertFalse(isOnlyDictionaryEncodingPages(createColumnMetaDataV2(PLAIN))); + assertThat(isOnlyDictionaryEncodingPages(createColumnMetaDataV2(RLE_DICTIONARY))).isTrue(); + assertThat(isOnlyDictionaryEncodingPages(createColumnMetaDataV2(PLAIN_DICTIONARY))).isTrue(); + assertThat(isOnlyDictionaryEncodingPages(createColumnMetaDataV2(PLAIN))).isFalse(); // Simulate fallback to plain encoding e.g. too many unique entries - assertFalse(isOnlyDictionaryEncodingPages(createColumnMetaDataV2(RLE_DICTIONARY, PLAIN))); + assertThat(isOnlyDictionaryEncodingPages(createColumnMetaDataV2(RLE_DICTIONARY, PLAIN))).isFalse(); } private ColumnChunkMetaData createColumnMetaDataV2(Encoding... dataEncodings) diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestChunkedInputStream.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestChunkedInputStream.java index 1153e759c931..e9cc7a5f08f2 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestChunkedInputStream.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestChunkedInputStream.java @@ -26,9 +26,8 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static io.airlift.slice.Slices.EMPTY_SLICE; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; public class TestChunkedInputStream { @@ -51,43 +50,43 @@ public void testInput(List chunks) byte[] buffer = new byte[expectedBytes.length + 1]; List slices = chunks.stream().map(Slices::wrappedBuffer).collect(toImmutableList()); - assertEquals(input(slices).readAllBytes(), expectedBytes); - assertEquals(input(slices).getSlice(expectedBytes.length).getBytes(), expectedBytes); - assertEquals(readAll(input(slices)), expectedBytes); + assertThat(input(slices).readAllBytes()).isEqualTo(expectedBytes); + assertThat(input(slices).getSlice(expectedBytes.length).getBytes()).isEqualTo(expectedBytes); + assertThat(readAll(input(slices))).isEqualTo(expectedBytes); - assertEquals(input(slices).readNBytes(expectedBytes.length), expectedBytes); + assertThat(input(slices).readNBytes(expectedBytes.length)).isEqualTo(expectedBytes); - assertEquals(input(slices).read(buffer, 0, 0), 0); - assertEquals(input(slices).getSlice(0), EMPTY_SLICE); + assertThat(input(slices).read(buffer, 0, 0)).isEqualTo(0); + assertThat(input(slices).getSlice(0)).isEqualTo(EMPTY_SLICE); if (expectedBytes.length > 0) { // read from one chunk only - assertEquals(input(slices).read(buffer, 0, 1), 1); - assertEquals(buffer[0], expectedBytes[0]); + assertThat(input(slices).read(buffer, 0, 1)).isEqualTo(1); + assertThat(buffer[0]).isEqualTo(expectedBytes[0]); } // rad more than total length ChunkedInputStream input = input(slices); int bytesRead = ByteStreams.read(input, buffer, 0, buffer.length); - assertEquals(bytesRead, expectedBytes.length > 0 ? expectedBytes.length : -1); + assertThat(bytesRead).isEqualTo(expectedBytes.length > 0 ? expectedBytes.length : -1); // read after input is done returns -1 - assertEquals(input.read(), -1); + assertThat(input.read()).isEqualTo(-1); // getSlice(0) after input is done returns empty slice - assertEquals(input.getSlice(0), EMPTY_SLICE); + assertThat(input.getSlice(0)).isEqualTo(EMPTY_SLICE); assertThatThrownBy(() -> input.getSlice(1)).isInstanceOf(IllegalArgumentException.class); - assertEquals(input.read(buffer, 0, 1), -1); + assertThat(input.read(buffer, 0, 1)).isEqualTo(-1); // verify available ChunkedInputStream availableInput = input(slices); // nothing is read initially - assertEquals(availableInput.available(), 0); + assertThat(availableInput.available()).isEqualTo(0); for (byte[] chunk : chunks) { availableInput.read(); - assertEquals(availableInput.available(), chunk.length - 1); + assertThat(availableInput.available()).isEqualTo(chunk.length - 1); availableInput.skipNBytes(chunk.length - 1); - assertEquals(availableInput.available(), 0); + assertThat(availableInput.available()).isEqualTo(0); } } @@ -102,7 +101,7 @@ public void testClose(List chunks) ChunkedInputStream input = new ChunkedInputStream(chunksReaders); input.close(); for (TestingChunkReader chunksReader : chunksReaders) { - assertTrue(chunksReader.isFreed()); + assertThat(chunksReader.isFreed()).isTrue(); } // close partially read input @@ -111,7 +110,7 @@ public void testClose(List chunks) input.readNBytes(chunks.get(0).length); input.close(); for (TestingChunkReader chunksReader : chunksReaders) { - assertTrue(chunksReader.isFreed()); + assertThat(chunksReader.isFreed()).isTrue(); } // close fully read input @@ -120,7 +119,7 @@ public void testClose(List chunks) input.readNBytes(chunks.stream().mapToInt(chunk -> chunk.length).sum()); input.close(); for (TestingChunkReader chunksReader : chunksReaders) { - assertTrue(chunksReader.isFreed()); + assertThat(chunksReader.isFreed()).isTrue(); } } diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestColumnIndexBuilder.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestColumnIndexBuilder.java index 73fff71fd453..44535cd9609a 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestColumnIndexBuilder.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestColumnIndexBuilder.java @@ -32,7 +32,6 @@ import java.math.BigDecimal; import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.PrimitiveIterator; @@ -58,12 +57,8 @@ import static org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName.FLOAT; import static org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName.INT32; import static org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName.INT64; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; -import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Fail.fail; // import static org.hamcrest.CoreMatchers.instanceOf; // import static org.junit.Assert.assertThat; @@ -266,7 +261,7 @@ public void testBuildBinaryDecimal() PrimitiveType type = Types.required(BINARY).as(LogicalTypeAnnotation.decimalType(2, 12)).named("test_binary_decimal"); ColumnIndexBuilder builder = ColumnIndexBuilder.getBuilder(type, Integer.MAX_VALUE); //assertThat(builder, instanceOf(BinaryColumnIndexBuilder.class)); - assertNull(builder.build()); + assertThat(builder.build()).isNull(); Operators.BinaryColumn col = binaryColumn("test_col"); StatsBuilder sb = new StatsBuilder(); @@ -278,10 +273,10 @@ public void testBuildBinaryDecimal() builder.add(sb.stats(type, null, null, null, null)); builder.add(sb.stats(type, null, null)); builder.add(sb.stats(type, decimalBinary("87656273"))); - assertEquals(8, builder.getPageCount()); - assertEquals(sb.getMinMaxSize(), builder.getMinMaxSize()); + assertThat(8).isEqualTo(builder.getPageCount()); + assertThat(sb.getMinMaxSize()).isEqualTo(builder.getMinMaxSize()); ColumnIndex columnIndex = builder.build(); - assertEquals(BoundaryOrder.UNORDERED, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.UNORDERED).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 2, 0, 3, 3, 0, 4, 2, 0); assertCorrectNullPages(columnIndex, true, false, false, true, false, true, true, false); assertCorrectValues(columnIndex.getMaxValues(), @@ -323,10 +318,10 @@ public void testBuildBinaryDecimal() builder.add(sb.stats(type, null, null)); builder.add(sb.stats(type, decimalBinary("1234567890.12"), null, null, null)); builder.add(sb.stats(type, null, null, null)); - assertEquals(8, builder.getPageCount()); - assertEquals(sb.getMinMaxSize(), builder.getMinMaxSize()); + assertThat(8).isEqualTo(builder.getPageCount()); + assertThat(sb.getMinMaxSize()).isEqualTo(builder.getMinMaxSize()); columnIndex = builder.build(); - assertEquals(BoundaryOrder.ASCENDING, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.ASCENDING).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 4, 0, 0, 2, 0, 2, 3, 3); assertCorrectNullPages(columnIndex, true, false, false, true, false, true, false, true); assertCorrectValues(columnIndex.getMaxValues(), @@ -368,10 +363,10 @@ public void testBuildBinaryDecimal() builder.add(sb.stats(type, decimalBinary("987656273"), decimalBinary("-0.17"))); builder.add(sb.stats(type, null, null)); builder.add(sb.stats(type, decimalBinary("-234.23"), decimalBinary("-9999293.23"))); - assertEquals(8, builder.getPageCount()); - assertEquals(sb.getMinMaxSize(), builder.getMinMaxSize()); + assertThat(8).isEqualTo(builder.getPageCount()); + assertThat(sb.getMinMaxSize()).isEqualTo(builder.getMinMaxSize()); columnIndex = builder.build(); - assertEquals(BoundaryOrder.DESCENDING, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.DESCENDING).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 3, 2, 3, 4, 0, 0, 2, 0); assertCorrectNullPages(columnIndex, true, true, false, true, false, false, true, false); assertCorrectValues(columnIndex.getMaxValues(), @@ -410,7 +405,7 @@ public void testBuildBinaryUtf8() PrimitiveType type = Types.required(BINARY).as(LogicalTypeAnnotation.stringType()).named("test_binary_utf8"); ColumnIndexBuilder builder = ColumnIndexBuilder.getBuilder(type, Integer.MAX_VALUE); //assertThat(builder, instanceOf(BinaryColumnIndexBuilder.class)); - assertNull(builder.build()); + assertThat(builder.build()).isNull(); Operators.BinaryColumn col = binaryColumn("test_col"); StatsBuilder sb = new StatsBuilder(); @@ -422,10 +417,10 @@ public void testBuildBinaryUtf8() builder.add(sb.stats(type, stringBinary("Dent"), stringBinary("Trilian"), null)); builder.add(sb.stats(type, stringBinary("Beeblebrox"))); builder.add(sb.stats(type, null, null)); - assertEquals(8, builder.getPageCount()); - assertEquals(sb.getMinMaxSize(), builder.getMinMaxSize()); + assertThat(8).isEqualTo(builder.getPageCount()); + assertThat(sb.getMinMaxSize()).isEqualTo(builder.getMinMaxSize()); ColumnIndex columnIndex = builder.build(); - assertEquals(BoundaryOrder.UNORDERED, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.UNORDERED).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 2, 2, 5, 2, 0, 1, 0, 2); assertCorrectNullPages(columnIndex, true, false, true, true, false, false, false, true); assertCorrectValues(columnIndex.getMaxValues(), @@ -467,10 +462,10 @@ public void testBuildBinaryUtf8() builder.add(sb.stats(type, null, null)); builder.add(sb.stats(type, stringBinary("Slartibartfast"))); builder.add(sb.stats(type, null, null)); - assertEquals(8, builder.getPageCount()); - assertEquals(sb.getMinMaxSize(), builder.getMinMaxSize()); + assertThat(8).isEqualTo(builder.getPageCount()); + assertThat(sb.getMinMaxSize()).isEqualTo(builder.getMinMaxSize()); columnIndex = builder.build(); - assertEquals(BoundaryOrder.ASCENDING, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.ASCENDING).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 2, 2, 5, 0, 1, 2, 0, 2); assertCorrectNullPages(columnIndex, false, true, true, false, false, true, false, true); assertCorrectValues(columnIndex.getMaxValues(), @@ -512,10 +507,10 @@ public void testBuildBinaryUtf8() builder.add(sb.stats(type, null, null)); builder.add(sb.stats(type, null, null)); builder.add(sb.stats(type, stringBinary("Dent"), stringBinary("Beeblebrox"), null, null)); - assertEquals(8, builder.getPageCount()); - assertEquals(sb.getMinMaxSize(), builder.getMinMaxSize()); + assertThat(8).isEqualTo(builder.getPageCount()); + assertThat(sb.getMinMaxSize()).isEqualTo(builder.getMinMaxSize()); columnIndex = builder.build(); - assertEquals(BoundaryOrder.DESCENDING, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.DESCENDING).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 2, 0, 5, 1, 0, 2, 2, 2); assertCorrectNullPages(columnIndex, true, false, true, false, false, true, true, false); assertCorrectValues(columnIndex.getMaxValues(), @@ -574,7 +569,7 @@ public void testStaticBuildBinary() stringBinary("Prefect"), null, stringBinary("Slartibartfast"))); - assertEquals(BoundaryOrder.ASCENDING, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.ASCENDING).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 1, 2, 3, 4, 5, 6, 7, 8); assertCorrectNullPages(columnIndex, true, true, false, false, true, false, true, false); assertCorrectValues(columnIndex.getMaxValues(), @@ -623,8 +618,8 @@ public void testFilterWithoutNullCounts() stringBinary("Prefect"), null, stringBinary("Slartibartfast"))); - assertEquals(BoundaryOrder.ASCENDING, columnIndex.getBoundaryOrder()); - assertNull(columnIndex.getNullCounts()); + assertThat(BoundaryOrder.ASCENDING).isEqualTo(columnIndex.getBoundaryOrder()); + assertThat(columnIndex.getNullCounts()).isNull(); assertCorrectNullPages(columnIndex, true, true, false, false, true, false, true, false); assertCorrectValues(columnIndex.getMaxValues(), null, @@ -660,7 +655,7 @@ public void testBuildBoolean() PrimitiveType type = Types.required(BOOLEAN).named("test_boolean"); ColumnIndexBuilder builder = ColumnIndexBuilder.getBuilder(type, Integer.MAX_VALUE); //assertThat(builder, instanceOf(BooleanColumnIndexBuilder.class)); - assertNull(builder.build()); + assertThat(builder.build()).isNull(); Operators.BooleanColumn col = booleanColumn("test_col"); builder = ColumnIndexBuilder.getBuilder(type, Integer.MAX_VALUE); @@ -670,10 +665,10 @@ public void testBuildBoolean() builder.add(sb.stats(type, true, true, null, null)); builder.add(sb.stats(type, null, null, null)); builder.add(sb.stats(type, false, false)); - assertEquals(5, builder.getPageCount()); - assertEquals(sb.getMinMaxSize(), builder.getMinMaxSize()); + assertThat(5).isEqualTo(builder.getPageCount()); + assertThat(sb.getMinMaxSize()).isEqualTo(builder.getMinMaxSize()); ColumnIndex columnIndex = builder.build(); - assertEquals(BoundaryOrder.UNORDERED, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.UNORDERED).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 0, 1, 2, 3, 0); assertCorrectNullPages(columnIndex, false, false, false, true, false); assertCorrectValues(columnIndex.getMaxValues(), true, true, true, null, false); @@ -694,10 +689,10 @@ public void testBuildBoolean() builder.add(sb.stats(type, false, true, null)); builder.add(sb.stats(type, false, true, null, null)); builder.add(sb.stats(type, null, null, null)); - assertEquals(7, builder.getPageCount()); - assertEquals(sb.getMinMaxSize(), builder.getMinMaxSize()); + assertThat(7).isEqualTo(builder.getPageCount()); + assertThat(sb.getMinMaxSize()).isEqualTo(builder.getMinMaxSize()); columnIndex = builder.build(); - assertEquals(BoundaryOrder.ASCENDING, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.ASCENDING).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 2, 0, 3, 4, 1, 2, 3); assertCorrectNullPages(columnIndex, true, false, true, true, false, false, true); assertCorrectValues(columnIndex.getMaxValues(), null, false, null, null, true, true, null); @@ -718,10 +713,10 @@ public void testBuildBoolean() builder.add(sb.stats(type, true, false, null)); builder.add(sb.stats(type, false, false, null, null)); builder.add(sb.stats(type, null, null, null)); - assertEquals(7, builder.getPageCount()); - assertEquals(sb.getMinMaxSize(), builder.getMinMaxSize()); + assertThat(7).isEqualTo(builder.getPageCount()); + assertThat(sb.getMinMaxSize()).isEqualTo(builder.getMinMaxSize()); columnIndex = builder.build(); - assertEquals(BoundaryOrder.DESCENDING, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.DESCENDING).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 2, 0, 3, 4, 1, 2, 3); assertCorrectNullPages(columnIndex, true, false, true, true, false, false, true); assertCorrectValues(columnIndex.getMaxValues(), null, true, null, null, true, false, null); @@ -744,7 +739,7 @@ public void testStaticBuildBoolean() asList(9L, 8L, 7L, 6L, 5L, 0L), toBBList(false, null, false, null, true, null), toBBList(true, null, false, null, true, null)); - assertEquals(BoundaryOrder.DESCENDING, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.DESCENDING).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 9, 8, 7, 6, 5, 0); assertCorrectNullPages(columnIndex, false, true, false, true, false, true); assertCorrectValues(columnIndex.getMaxValues(), true, null, false, null, true, null); @@ -757,7 +752,7 @@ public void testBuildDouble() PrimitiveType type = Types.required(DOUBLE).named("test_double"); ColumnIndexBuilder builder = ColumnIndexBuilder.getBuilder(type, Integer.MAX_VALUE); //assertThat(builder, instanceOf(DoubleColumnIndexBuilder.class)); - assertNull(builder.build()); + assertThat(builder.build()).isNull(); Operators.DoubleColumn col = doubleColumn("test_col"); StatsBuilder sb = new StatsBuilder(); @@ -767,10 +762,10 @@ public void testBuildDouble() builder.add(sb.stats(type, null, null, null)); builder.add(sb.stats(type, 1.9, 2.32)); builder.add(sb.stats(type, -21.0, 8.1)); - assertEquals(6, builder.getPageCount()); - assertEquals(sb.getMinMaxSize(), builder.getMinMaxSize()); + assertThat(6).isEqualTo(builder.getPageCount()); + assertThat(sb.getMinMaxSize()).isEqualTo(builder.getMinMaxSize()); ColumnIndex columnIndex = builder.build(); - assertEquals(BoundaryOrder.UNORDERED, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.UNORDERED).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 0, 1, 2, 3, 0, 0); assertCorrectNullPages(columnIndex, false, false, false, true, false, false); assertCorrectValues(columnIndex.getMaxValues(), -4.1, 7.0, 2.2, null, 2.32, 8.1); @@ -797,10 +792,10 @@ public void testBuildDouble() builder.add(sb.stats(type, null, null)); builder.add(sb.stats(type, 3.0, 42.83)); builder.add(sb.stats(type, null, null)); - assertEquals(9, builder.getPageCount()); - assertEquals(sb.getMinMaxSize(), builder.getMinMaxSize()); + assertThat(9).isEqualTo(builder.getPageCount()); + assertThat(sb.getMinMaxSize()).isEqualTo(builder.getMinMaxSize()); columnIndex = builder.build(); - assertEquals(BoundaryOrder.ASCENDING, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.ASCENDING).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 2, 2, 1, 2, 3, 0, 2, 0, 2); assertCorrectNullPages(columnIndex, true, false, false, true, true, false, true, false, true); assertCorrectValues(columnIndex.getMaxValues(), null, -345.2, -234.6, null, null, 2.99999, null, 42.83, null); @@ -827,10 +822,10 @@ public void testBuildDouble() builder.add(sb.stats(type, null, null)); builder.add(sb.stats(type, null, null)); builder.add(sb.stats(type, -3.0, -42.83)); - assertEquals(9, builder.getPageCount()); - assertEquals(sb.getMinMaxSize(), builder.getMinMaxSize()); + assertThat(9).isEqualTo(builder.getPageCount()); + assertThat(sb.getMinMaxSize()).isEqualTo(builder.getMinMaxSize()); columnIndex = builder.build(); - assertEquals(BoundaryOrder.DESCENDING, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.DESCENDING).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 5, 0, 3, 1, 2, 0, 2, 2, 0); assertCorrectNullPages(columnIndex, true, false, true, false, true, false, true, true, false); assertCorrectValues(columnIndex.getMaxValues(), null, 532.3, null, 234.7, null, 234.69, null, null, -3.0); @@ -864,7 +859,7 @@ public void testBuildDoubleZeroNaN() builder.add(sb.stats(type, -1.0, -0.0)); builder.add(sb.stats(type, 0.0, Double.NaN)); builder.add(sb.stats(type, 1.0, 100.0)); - assertNull(builder.build()); + assertThat(builder.build()).isNull(); } @Test @@ -877,7 +872,7 @@ public void testStaticBuildDouble() asList(0L, 1L, 2L, 3L, 4L, 5L), toBBList(-1.0, -2.0, -3.0, -4.0, -5.0, -6.0), toBBList(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)); - assertEquals(BoundaryOrder.UNORDERED, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.UNORDERED).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 0, 1, 2, 3, 4, 5); assertCorrectNullPages(columnIndex, false, false, false, false, false, false); assertCorrectValues(columnIndex.getMaxValues(), 1.0, 2.0, 3.0, 4.0, 5.0, 6.0); @@ -890,7 +885,7 @@ public void testBuildFloat() PrimitiveType type = Types.required(FLOAT).named("test_float"); ColumnIndexBuilder builder = ColumnIndexBuilder.getBuilder(type, Integer.MAX_VALUE); //assertThat(builder, instanceOf(FloatColumnIndexBuilder.class)); - assertNull(builder.build()); + assertThat(builder.build()).isNull(); Operators.FloatColumn col = floatColumn("test_col"); StatsBuilder sb = new StatsBuilder(); @@ -900,10 +895,10 @@ public void testBuildFloat() builder.add(sb.stats(type, null, null, null)); builder.add(sb.stats(type, 1.9f, 2.32f)); builder.add(sb.stats(type, -21.0f, 8.1f)); - assertEquals(6, builder.getPageCount()); - assertEquals(sb.getMinMaxSize(), builder.getMinMaxSize()); + assertThat(6).isEqualTo(builder.getPageCount()); + assertThat(sb.getMinMaxSize()).isEqualTo(builder.getMinMaxSize()); ColumnIndex columnIndex = builder.build(); - assertEquals(BoundaryOrder.UNORDERED, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.UNORDERED).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 0, 1, 2, 3, 0, 0); assertCorrectNullPages(columnIndex, false, false, false, true, false, false); assertCorrectValues(columnIndex.getMaxValues(), -4.1f, 7.0f, 2.2f, null, 2.32f, 8.1f); @@ -930,10 +925,10 @@ public void testBuildFloat() builder.add(sb.stats(type, null, null)); builder.add(sb.stats(type, 3.0f, 42.83f)); builder.add(sb.stats(type, null, null)); - assertEquals(9, builder.getPageCount()); - assertEquals(sb.getMinMaxSize(), builder.getMinMaxSize()); + assertThat(9).isEqualTo(builder.getPageCount()); + assertThat(sb.getMinMaxSize()).isEqualTo(builder.getMinMaxSize()); columnIndex = builder.build(); - assertEquals(BoundaryOrder.ASCENDING, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.ASCENDING).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 2, 2, 1, 2, 3, 0, 2, 0, 2); assertCorrectNullPages(columnIndex, true, false, false, true, true, false, true, false, true); assertCorrectValues(columnIndex.getMaxValues(), null, -345.2f, -234.7f, null, null, 2.99999f, null, 42.83f, null); @@ -960,10 +955,10 @@ public void testBuildFloat() builder.add(sb.stats(type, null, null)); builder.add(sb.stats(type, null, null)); builder.add(sb.stats(type, -3.0f, -42.83f)); - assertEquals(9, builder.getPageCount()); - assertEquals(sb.getMinMaxSize(), builder.getMinMaxSize()); + assertThat(9).isEqualTo(builder.getPageCount()); + assertThat(sb.getMinMaxSize()).isEqualTo(builder.getMinMaxSize()); columnIndex = builder.build(); - assertEquals(BoundaryOrder.DESCENDING, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.DESCENDING).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 5, 0, 3, 1, 2, 0, 2, 2, 0); assertCorrectNullPages(columnIndex, true, false, true, false, true, false, true, true, false); assertCorrectValues(columnIndex.getMaxValues(), null, 532.3f, null, 234.7f, null, 234.6f, null, null, -3.0f); @@ -997,7 +992,7 @@ public void testBuildFloatZeroNaN() builder.add(sb.stats(type, -1.0f, -0.0f)); builder.add(sb.stats(type, 0.0f, Float.NaN)); builder.add(sb.stats(type, 1.0f, 100.0f)); - assertNull(builder.build()); + assertThat(builder.build()).isNull(); } @Test @@ -1010,7 +1005,7 @@ public void testStaticBuildFloat() asList(9L, 8L, 7L, 6L, 0L, 0L), toBBList(null, null, null, -3.0f, -2.0f, 0.1f), toBBList(null, null, null, -2.0f, 0.0f, 6.0f)); - assertEquals(BoundaryOrder.ASCENDING, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.ASCENDING).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 9, 8, 7, 6, 0, 0); assertCorrectNullPages(columnIndex, true, true, true, false, false, false); assertCorrectValues(columnIndex.getMaxValues(), null, null, null, -2.0f, 0.0f, 6.0f); @@ -1023,7 +1018,7 @@ public void testBuildInt32() PrimitiveType type = Types.required(INT32).named("test_int32"); ColumnIndexBuilder builder = ColumnIndexBuilder.getBuilder(type, Integer.MAX_VALUE); //assertThat(builder, instanceOf(IntColumnIndexBuilder.class)); - assertNull(builder.build()); + assertThat(builder.build()).isNull(); Operators.IntColumn col = intColumn("test_col"); StatsBuilder sb = new StatsBuilder(); @@ -1033,10 +1028,10 @@ public void testBuildInt32() builder.add(sb.stats(type, null, null, null)); builder.add(sb.stats(type, 1, 2)); builder.add(sb.stats(type, -21, 8)); - assertEquals(6, builder.getPageCount()); - assertEquals(sb.getMinMaxSize(), builder.getMinMaxSize()); + assertThat(6).isEqualTo(builder.getPageCount()); + assertThat(sb.getMinMaxSize()).isEqualTo(builder.getMinMaxSize()); ColumnIndex columnIndex = builder.build(); - assertEquals(BoundaryOrder.UNORDERED, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.UNORDERED).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 0, 1, 2, 3, 0, 0); assertCorrectNullPages(columnIndex, false, false, false, true, false, false); assertCorrectValues(columnIndex.getMaxValues(), 10, 7, 2, null, 2, 8); @@ -1063,10 +1058,10 @@ public void testBuildInt32() builder.add(sb.stats(type, null, null)); builder.add(sb.stats(type, 3, 42)); builder.add(sb.stats(type, null, null)); - assertEquals(9, builder.getPageCount()); - assertEquals(sb.getMinMaxSize(), builder.getMinMaxSize()); + assertThat(9).isEqualTo(builder.getPageCount()); + assertThat(sb.getMinMaxSize()).isEqualTo(builder.getMinMaxSize()); columnIndex = builder.build(); - assertEquals(BoundaryOrder.ASCENDING, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.ASCENDING).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 2, 2, 1, 2, 3, 0, 2, 0, 2); assertCorrectNullPages(columnIndex, true, false, false, true, true, false, true, false, true); assertCorrectValues(columnIndex.getMaxValues(), null, -345, -42, null, null, 2, null, 42, null); @@ -1094,10 +1089,10 @@ public void testBuildInt32() builder.add(sb.stats(type, null, null)); builder.add(sb.stats(type, null, null)); builder.add(sb.stats(type, -3, -42)); - assertEquals(9, builder.getPageCount()); - assertEquals(sb.getMinMaxSize(), builder.getMinMaxSize()); + assertThat(9).isEqualTo(builder.getPageCount()); + assertThat(sb.getMinMaxSize()).isEqualTo(builder.getMinMaxSize()); columnIndex = builder.build(); - assertEquals(BoundaryOrder.DESCENDING, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.DESCENDING).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 5, 0, 3, 1, 2, 0, 2, 2, 0); assertCorrectNullPages(columnIndex, true, false, true, false, true, false, true, true, false); assertCorrectValues(columnIndex.getMaxValues(), null, 532, null, 234, null, 42, null, null, -3); @@ -1125,7 +1120,7 @@ public void testStaticBuildInt32() asList(0L, 10L, 0L, 3L, 5L, 7L), toBBList(10, 8, 6, null, null, null), toBBList(9, 7, 5, null, null, null)); - assertEquals(BoundaryOrder.DESCENDING, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.DESCENDING).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 0, 10, 0, 3, 5, 7); assertCorrectNullPages(columnIndex, false, false, false, true, true, true); assertCorrectValues(columnIndex.getMaxValues(), 9, 7, 5, null, null, null); @@ -1138,7 +1133,7 @@ public void testBuildUInt8() PrimitiveType type = Types.required(INT32).as(LogicalTypeAnnotation.intType(8, false)).named("test_uint8"); ColumnIndexBuilder builder = ColumnIndexBuilder.getBuilder(type, Integer.MAX_VALUE); //assertThat(builder, instanceOf(IntColumnIndexBuilder.class)); - assertNull(builder.build()); + assertThat(builder.build()).isNull(); Operators.IntColumn col = intColumn("test_col"); StatsBuilder sb = new StatsBuilder(); @@ -1148,10 +1143,10 @@ public void testBuildUInt8() builder.add(sb.stats(type, null, null, null)); builder.add(sb.stats(type, 1, 0xFF)); builder.add(sb.stats(type, 0xEF, 0xFA)); - assertEquals(6, builder.getPageCount()); - assertEquals(sb.getMinMaxSize(), builder.getMinMaxSize()); + assertThat(6).isEqualTo(builder.getPageCount()); + assertThat(sb.getMinMaxSize()).isEqualTo(builder.getMinMaxSize()); ColumnIndex columnIndex = builder.build(); - assertEquals(BoundaryOrder.UNORDERED, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.UNORDERED).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 0, 1, 2, 3, 0, 0); assertCorrectNullPages(columnIndex, false, false, false, true, false, false); assertCorrectValues(columnIndex.getMaxValues(), 10, 17, 2, null, 0xFF, 0xFA); @@ -1178,10 +1173,10 @@ public void testBuildUInt8() builder.add(sb.stats(type, null, null)); builder.add(sb.stats(type, 0xEF, 0xFF)); builder.add(sb.stats(type, null, null)); - assertEquals(9, builder.getPageCount()); - assertEquals(sb.getMinMaxSize(), builder.getMinMaxSize()); + assertThat(9).isEqualTo(builder.getPageCount()); + assertThat(sb.getMinMaxSize()).isEqualTo(builder.getMinMaxSize()); columnIndex = builder.build(); - assertEquals(BoundaryOrder.ASCENDING, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.ASCENDING).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 2, 2, 1, 2, 3, 0, 2, 0, 2); assertCorrectNullPages(columnIndex, true, false, false, true, true, false, true, false, true); assertCorrectValues(columnIndex.getMaxValues(), null, 0, 42, null, null, 0xEE, null, 0xFF, null); @@ -1209,10 +1204,10 @@ public void testBuildUInt8() builder.add(sb.stats(type, null, null)); builder.add(sb.stats(type, null, null)); builder.add(sb.stats(type, 41, 0)); - assertEquals(9, builder.getPageCount()); - assertEquals(sb.getMinMaxSize(), builder.getMinMaxSize()); + assertThat(9).isEqualTo(builder.getPageCount()); + assertThat(sb.getMinMaxSize()).isEqualTo(builder.getMinMaxSize()); columnIndex = builder.build(); - assertEquals(BoundaryOrder.DESCENDING, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.DESCENDING).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 5, 0, 3, 1, 2, 0, 2, 2, 0); assertCorrectNullPages(columnIndex, true, false, true, false, true, false, true, true, false); assertCorrectValues(columnIndex.getMaxValues(), null, 0xFF, null, 0xEF, null, 0xEE, null, null, 41); @@ -1236,7 +1231,7 @@ public void testBuildInt64() PrimitiveType type = Types.required(INT64).named("test_int64"); ColumnIndexBuilder builder = ColumnIndexBuilder.getBuilder(type, Integer.MAX_VALUE); //assertThat(builder, instanceOf(LongColumnIndexBuilder.class)); - assertNull(builder.build()); + assertThat(builder.build()).isNull(); Operators.LongColumn col = longColumn("test_col"); StatsBuilder sb = new StatsBuilder(); @@ -1246,10 +1241,10 @@ public void testBuildInt64() builder.add(sb.stats(type, null, null, null)); builder.add(sb.stats(type, 1L, 2L)); builder.add(sb.stats(type, -21L, 8L)); - assertEquals(6, builder.getPageCount()); - assertEquals(sb.getMinMaxSize(), builder.getMinMaxSize()); + assertThat(6).isEqualTo(builder.getPageCount()); + assertThat(sb.getMinMaxSize()).isEqualTo(builder.getMinMaxSize()); ColumnIndex columnIndex = builder.build(); - assertEquals(BoundaryOrder.UNORDERED, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.UNORDERED).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 0L, 1L, 2L, 3L, 0L, 0L); assertCorrectNullPages(columnIndex, false, false, false, true, false, false); assertCorrectValues(columnIndex.getMaxValues(), 10L, 7L, 2L, null, 2L, 8L); @@ -1276,10 +1271,10 @@ public void testBuildInt64() builder.add(sb.stats(type, null, null)); builder.add(sb.stats(type, -3L, 42L)); builder.add(sb.stats(type, null, null)); - assertEquals(9, builder.getPageCount()); - assertEquals(sb.getMinMaxSize(), builder.getMinMaxSize()); + assertThat(9).isEqualTo(builder.getPageCount()); + assertThat(sb.getMinMaxSize()).isEqualTo(builder.getMinMaxSize()); columnIndex = builder.build(); - assertEquals(BoundaryOrder.ASCENDING, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.ASCENDING).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 2, 2, 1, 2, 3, 0, 2, 0, 2); assertCorrectNullPages(columnIndex, true, false, false, true, true, false, true, false, true); assertCorrectValues(columnIndex.getMaxValues(), null, -345L, -42L, null, null, 2L, null, 42L, null); @@ -1307,10 +1302,10 @@ public void testBuildInt64() builder.add(sb.stats(type, null, null)); builder.add(sb.stats(type, null, null)); builder.add(sb.stats(type, -3L, -42L)); - assertEquals(9, builder.getPageCount()); - assertEquals(sb.getMinMaxSize(), builder.getMinMaxSize()); + assertThat(9).isEqualTo(builder.getPageCount()); + assertThat(sb.getMinMaxSize()).isEqualTo(builder.getMinMaxSize()); columnIndex = builder.build(); - assertEquals(BoundaryOrder.DESCENDING, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.DESCENDING).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 5, 0, 3, 1, 2, 0, 2, 2, 0); assertCorrectNullPages(columnIndex, true, false, true, false, true, false, true, true, false); assertCorrectValues(columnIndex.getMaxValues(), null, 532L, null, 234L, null, 42L, null, null, -3L); @@ -1338,7 +1333,7 @@ public void testStaticBuildInt64() asList(1L, 2L, 3L, 4L, 5L, 6L), toBBList(null, 2L, null, 4L, null, 9L), toBBList(null, 3L, null, 15L, null, 10L)); - assertEquals(BoundaryOrder.UNORDERED, columnIndex.getBoundaryOrder()); + assertThat(BoundaryOrder.UNORDERED).isEqualTo(columnIndex.getBoundaryOrder()); assertCorrectNullCounts(columnIndex, 1, 2, 3, 4, 5, 6); assertCorrectNullPages(columnIndex, true, false, true, false, true, false); assertCorrectValues(columnIndex.getMaxValues(), null, 3L, null, 15L, null, 10L); @@ -1356,9 +1351,9 @@ public void testNoOpBuilder() builder.add(sb.stats(Types.required(DOUBLE).named("test_double"), null, null, null)); builder.add(sb.stats(Types.required(INT32).named("test_int32"), null, null)); builder.add(sb.stats(Types.required(INT64).named("test_int64"), -234L, -42L, null)); - assertEquals(0, builder.getPageCount()); - assertEquals(0, builder.getMinMaxSize()); - assertNull(builder.build()); + assertThat(0).isEqualTo(builder.getPageCount()); + assertThat(0).isEqualTo(builder.getMinMaxSize()); + assertThat(builder.build()).isNull(); } private static List toBBList(Binary... values) @@ -1457,95 +1452,129 @@ private static Binary stringBinary(String str) private static void assertCorrectValues(List values, Binary... expectedValues) { - assertEquals(expectedValues.length, values.size()); + assertThat(expectedValues.length).isEqualTo(values.size()); for (int i = 0; i < expectedValues.length; ++i) { Binary expectedValue = expectedValues[i]; ByteBuffer value = values.get(i); if (expectedValue == null) { - assertFalse(value.hasRemaining(), "The byte buffer should be empty for null pages"); + assertThat(value.hasRemaining()) + .describedAs("The byte buffer should be empty for null pages") + .isFalse(); } else { - assertArrayEquals("Invalid value for page " + i, expectedValue.getBytesUnsafe(), value.array()); + assertThat(value.array()) + .describedAs("Invalid value for page " + i) + .isEqualTo(expectedValue.getBytesUnsafe()); } } } private static void assertCorrectValues(List values, Boolean... expectedValues) { - assertEquals(expectedValues.length, values.size()); + assertThat(expectedValues.length).isEqualTo(values.size()); for (int i = 0; i < expectedValues.length; ++i) { Boolean expectedValue = expectedValues[i]; ByteBuffer value = values.get(i); if (expectedValue == null) { - assertFalse(value.hasRemaining(), "The byte buffer should be empty for null pages"); + assertThat(value.hasRemaining()) + .describedAs("The byte buffer should be empty for null pages") + .isFalse(); } else { - assertEquals(1, value.remaining(), "The byte buffer should be 1 byte long for boolean"); - assertEquals(expectedValue.booleanValue(), value.get(0) != 0, "Invalid value for page " + i); + assertThat(1) + .describedAs("The byte buffer should be 1 byte long for boolean") + .isEqualTo(value.remaining()); + assertThat(expectedValue.booleanValue()) + .describedAs("Invalid value for page " + i) + .isEqualTo(value.get(0) != 0); } } } private static void assertCorrectValues(List values, Double... expectedValues) { - assertEquals(expectedValues.length, values.size()); + assertThat(expectedValues.length).isEqualTo(values.size()); for (int i = 0; i < expectedValues.length; ++i) { Double expectedValue = expectedValues[i]; ByteBuffer value = values.get(i); if (expectedValue == null) { - assertFalse(value.hasRemaining(), "The byte buffer should be empty for null pages"); + assertThat(value.hasRemaining()) + .describedAs("The byte buffer should be empty for null pages") + .isFalse(); } else { - assertEquals(8, value.remaining(), "The byte buffer should be 8 bytes long for double"); - assertTrue(Double.compare(expectedValue.doubleValue(), value.getDouble(0)) == 0, "Invalid value for page " + i); + assertThat(8) + .describedAs("The byte buffer should be 8 bytes long for double") + .isEqualTo(value.remaining()); + assertThat(Double.compare(expectedValue.doubleValue(), value.getDouble(0)) == 0) + .describedAs("Invalid value for page " + i) + .isTrue(); } } } private static void assertCorrectValues(List values, Float... expectedValues) { - assertEquals(expectedValues.length, values.size()); + assertThat(expectedValues.length).isEqualTo(values.size()); for (int i = 0; i < expectedValues.length; ++i) { Float expectedValue = expectedValues[i]; ByteBuffer value = values.get(i); if (expectedValue == null) { - assertFalse(value.hasRemaining(), "The byte buffer should be empty for null pages"); + assertThat(value.hasRemaining()) + .describedAs("The byte buffer should be empty for null pages") + .isFalse(); } else { - assertEquals(4, value.remaining(), "The byte buffer should be 4 bytes long for double"); - assertTrue(Float.compare(expectedValue.floatValue(), value.getFloat(0)) == 0, "Invalid value for page " + i); + assertThat(4) + .describedAs("The byte buffer should be 4 bytes long for double") + .isEqualTo(value.remaining()); + assertThat(Float.compare(expectedValue.floatValue(), value.getFloat(0)) == 0) + .describedAs("Invalid value for page " + i) + .isTrue(); } } } private static void assertCorrectValues(List values, Integer... expectedValues) { - assertEquals(expectedValues.length, values.size()); + assertThat(expectedValues.length).isEqualTo(values.size()); for (int i = 0; i < expectedValues.length; ++i) { Integer expectedValue = expectedValues[i]; ByteBuffer value = values.get(i); if (expectedValue == null) { - assertFalse(value.hasRemaining(), "The byte buffer should be empty for null pages"); + assertThat(value.hasRemaining()) + .describedAs("The byte buffer should be empty for null pages") + .isFalse(); } else { - assertEquals(4, value.remaining(), "The byte buffer should be 4 bytes long for int32"); - assertEquals(expectedValue.intValue(), value.getInt(0), "Invalid value for page " + i); + assertThat(4) + .describedAs("The byte buffer should be 4 bytes long for int32") + .isEqualTo(value.remaining()); + assertThat(expectedValue.intValue()) + .describedAs("Invalid value for page " + i) + .isEqualTo(value.getInt(0)); } } } private static void assertCorrectValues(List values, Long... expectedValues) { - assertEquals(expectedValues.length, values.size()); + assertThat(expectedValues.length).isEqualTo(values.size()); for (int i = 0; i < expectedValues.length; ++i) { Long expectedValue = expectedValues[i]; ByteBuffer value = values.get(i); if (expectedValue == null) { - assertFalse(value.hasRemaining(), "The byte buffer should be empty for null pages"); + assertThat(value.hasRemaining()) + .describedAs("The byte buffer should be empty for null pages") + .isFalse(); } else { - assertEquals(8, value.remaining(), "The byte buffer should be 8 bytes long for int64"); - assertEquals(expectedValue.intValue(), value.getLong(0), "Invalid value for page " + i); + assertThat(8) + .describedAs("The byte buffer should be 8 bytes long for int64") + .isEqualTo(value.remaining()); + assertThat(expectedValue.intValue()) + .describedAs("Invalid value for page " + i) + .isEqualTo(value.getLong(0)); } } } @@ -1553,18 +1582,22 @@ private static void assertCorrectValues(List values, Long... expecte private static void assertCorrectNullCounts(ColumnIndex columnIndex, long... expectedNullCounts) { List nullCounts = columnIndex.getNullCounts(); - assertEquals(expectedNullCounts.length, nullCounts.size()); + assertThat(expectedNullCounts.length).isEqualTo(nullCounts.size()); for (int i = 0; i < expectedNullCounts.length; ++i) { - assertEquals(expectedNullCounts[i], nullCounts.get(i).longValue(), "Invalid null count at page " + i); + assertThat(expectedNullCounts[i]) + .describedAs("Invalid null count at page " + i) + .isEqualTo(nullCounts.get(i).longValue()); } } private static void assertCorrectNullPages(ColumnIndex columnIndex, boolean... expectedNullPages) { List nullPages = columnIndex.getNullPages(); - assertEquals(expectedNullPages.length, nullPages.size()); + assertThat(expectedNullPages.length).isEqualTo(nullPages.size()); for (int i = 0; i < expectedNullPages.length; ++i) { - assertEquals(expectedNullPages[i], nullPages.get(i).booleanValue(), "Invalid null pages at page " + i); + assertThat(expectedNullPages[i]) + .describedAs("Invalid null pages at page " + i) + .isEqualTo(nullPages.get(i).booleanValue()); } } @@ -1628,8 +1661,6 @@ static void checkEquals(PrimitiveIterator.OfInt actualIt, int... expectedValues) IntList actualList = new IntArrayList(); actualIt.forEachRemaining((int value) -> actualList.add(value)); int[] actualValues = actualList.toIntArray(); - assertArrayEquals( - "ExpectedValues: " + Arrays.toString(expectedValues) + " ActualValues: " + Arrays.toString(actualValues), - expectedValues, actualValues); + assertThat(actualValues).isEqualTo(expectedValues); } } diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestColumnIndexFilter.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestColumnIndexFilter.java index ec77c71ed73d..50d114542a50 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestColumnIndexFilter.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestColumnIndexFilter.java @@ -33,7 +33,6 @@ import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -66,7 +65,7 @@ import static org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName.INT32; import static org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName.INT64; import static org.apache.parquet.schema.Types.optional; -import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals; +import static org.assertj.core.api.Assertions.assertThat; /** * Unit tests of {@link ColumnIndexFilter} @@ -356,14 +355,14 @@ private static void assertAllRows(RowRanges ranges, long rowCount) ranges.iterator().forEachRemaining((long value) -> actualList.add(value)); LongList expectedList = new LongArrayList(); LongStream.range(0, rowCount).forEach(expectedList::add); - assertArrayEquals(expectedList + " != " + actualList, expectedList.toLongArray(), actualList.toLongArray()); + assertThat(actualList.toLongArray()).isEqualTo(expectedList.toLongArray()); } private static void assertRows(RowRanges ranges, long... expectedRows) { LongList actualList = new LongArrayList(); ranges.iterator().forEachRemaining((long value) -> actualList.add(value)); - assertArrayEquals(Arrays.toString(expectedRows) + " != " + actualList, expectedRows, actualList.toLongArray()); + assertThat(actualList.toLongArray()).isEqualTo(expectedRows); } @Test diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestMetadataReader.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestMetadataReader.java index ffe73d3b0ab0..65d5ee5b1545 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestMetadataReader.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestMetadataReader.java @@ -36,10 +36,6 @@ import static org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName.INT64; import static org.apache.parquet.schema.Type.Repetition.OPTIONAL; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; public class TestMetadataReader { @@ -66,15 +62,15 @@ public void testReadStatsInt32(Optional fileCreatedBy) statistics.setMax(fromHex("3AA40000")); assertThat(MetadataReader.readStats(fileCreatedBy, Optional.of(statistics), new PrimitiveType(OPTIONAL, INT32, "Test column"))) .isInstanceOfSatisfying(IntStatistics.class, columnStatistics -> { - assertFalse(columnStatistics.isEmpty()); + assertThat(columnStatistics.isEmpty()).isFalse(); - assertTrue(columnStatistics.isNumNullsSet()); - assertEquals(columnStatistics.getNumNulls(), 13); + assertThat(columnStatistics.isNumNullsSet()).isTrue(); + assertThat(columnStatistics.getNumNulls()).isEqualTo(13); - assertEquals(columnStatistics.getMin(), -10); - assertEquals(columnStatistics.getMax(), 42042); - assertEquals(columnStatistics.genericGetMin(), (Integer) (int) -10); - assertEquals(columnStatistics.genericGetMax(), (Integer) 42042); + assertThat(columnStatistics.getMin()).isEqualTo(-10); + assertThat(columnStatistics.getMax()).isEqualTo(42042); + assertThat(columnStatistics.genericGetMin()).isEqualTo((Integer) (int) -10); + assertThat(columnStatistics.genericGetMax()).isEqualTo((Integer) 42042); }); } @@ -87,15 +83,15 @@ public void testReadStatsInt64(Optional fileCreatedBy) statistics.setMax(fromHex("3AA4000000000000")); assertThat(MetadataReader.readStats(fileCreatedBy, Optional.of(statistics), new PrimitiveType(OPTIONAL, INT64, "Test column"))) .isInstanceOfSatisfying(LongStatistics.class, columnStatistics -> { - assertFalse(columnStatistics.isEmpty()); + assertThat(columnStatistics.isEmpty()).isFalse(); - assertTrue(columnStatistics.isNumNullsSet()); - assertEquals(columnStatistics.getNumNulls(), 13); + assertThat(columnStatistics.isNumNullsSet()).isTrue(); + assertThat(columnStatistics.getNumNulls()).isEqualTo(13); - assertEquals(columnStatistics.getMin(), -10); - assertEquals(columnStatistics.getMax(), 42042); - assertEquals(columnStatistics.genericGetMin(), (Long) (long) -10L); - assertEquals(columnStatistics.genericGetMax(), (Long) 42042L); + assertThat(columnStatistics.getMin()).isEqualTo(-10); + assertThat(columnStatistics.getMax()).isEqualTo(42042); + assertThat(columnStatistics.genericGetMin()).isEqualTo((Long) (long) -10L); + assertThat(columnStatistics.genericGetMax()).isEqualTo((Long) 42042L); }); } @@ -108,15 +104,15 @@ public void testReadStatsFloat(Optional fileCreatedBy) statistics.setMax(fromHex("12340000")); assertThat(MetadataReader.readStats(fileCreatedBy, Optional.of(statistics), new PrimitiveType(OPTIONAL, FLOAT, "Test column"))) .isInstanceOfSatisfying(FloatStatistics.class, columnStatistics -> { - assertFalse(columnStatistics.isEmpty()); + assertThat(columnStatistics.isEmpty()).isFalse(); - assertTrue(columnStatistics.isNumNullsSet()); - assertEquals(columnStatistics.getNumNulls(), 13); + assertThat(columnStatistics.isNumNullsSet()).isTrue(); + assertThat(columnStatistics.getNumNulls()).isEqualTo(13); - assertEquals(columnStatistics.getMin(), -3.59039552E8f); - assertEquals(columnStatistics.getMax(), 1.868E-41f); - assertEquals(columnStatistics.genericGetMin(), -3.59039552E8f); - assertEquals(columnStatistics.genericGetMax(), 1.868E-41f); + assertThat(columnStatistics.getMin()).isEqualTo(-3.59039552E8f); + assertThat(columnStatistics.getMax()).isEqualTo(1.868E-41f); + assertThat(columnStatistics.genericGetMin()).isEqualTo(-3.59039552E8f); + assertThat(columnStatistics.genericGetMax()).isEqualTo(1.868E-41f); }); } @@ -129,15 +125,15 @@ public void testReadStatsDouble(Optional fileCreatedBy) statistics.setMax(fromHex("000000000000E043")); assertThat(MetadataReader.readStats(fileCreatedBy, Optional.of(statistics), new PrimitiveType(OPTIONAL, DOUBLE, "Test column"))) .isInstanceOfSatisfying(DoubleStatistics.class, columnStatistics -> { - assertFalse(columnStatistics.isEmpty()); + assertThat(columnStatistics.isEmpty()).isFalse(); - assertTrue(columnStatistics.isNumNullsSet()); - assertEquals(columnStatistics.getNumNulls(), 13); + assertThat(columnStatistics.isNumNullsSet()).isTrue(); + assertThat(columnStatistics.getNumNulls()).isEqualTo(13); - assertEquals(columnStatistics.getMin(), 4.36428250013E-312); - assertEquals(columnStatistics.getMax(), 9.223372036854776E18); - assertEquals(columnStatistics.genericGetMin(), 4.36428250013E-312); - assertEquals(columnStatistics.genericGetMax(), 9.223372036854776E18); + assertThat(columnStatistics.getMin()).isEqualTo(4.36428250013E-312); + assertThat(columnStatistics.getMax()).isEqualTo(9.223372036854776E18); + assertThat(columnStatistics.genericGetMin()).isEqualTo(4.36428250013E-312); + assertThat(columnStatistics.genericGetMax()).isEqualTo(9.223372036854776E18); }); } @@ -149,15 +145,15 @@ public void testReadStatsInt64WithoutNullCount(Optional fileCreatedBy) statistics.setMax(fromHex("3AA4000000000000")); assertThat(MetadataReader.readStats(fileCreatedBy, Optional.of(statistics), new PrimitiveType(OPTIONAL, INT64, "Test column"))) .isInstanceOfSatisfying(LongStatistics.class, columnStatistics -> { - assertFalse(columnStatistics.isEmpty()); + assertThat(columnStatistics.isEmpty()).isFalse(); - assertFalse(columnStatistics.isNumNullsSet()); - assertEquals(columnStatistics.getNumNulls(), -1); + assertThat(columnStatistics.isNumNullsSet()).isFalse(); + assertThat(columnStatistics.getNumNulls()).isEqualTo(-1); - assertEquals(columnStatistics.getMin(), -10); - assertEquals(columnStatistics.getMax(), 42042); - assertEquals(columnStatistics.genericGetMin(), (Long) (long) -10L); - assertEquals(columnStatistics.genericGetMax(), (Long) 42042L); + assertThat(columnStatistics.getMin()).isEqualTo(-10); + assertThat(columnStatistics.getMax()).isEqualTo(42042); + assertThat(columnStatistics.genericGetMin()).isEqualTo((Long) (long) -10L); + assertThat(columnStatistics.genericGetMax()).isEqualTo((Long) 42042L); }); } @@ -169,15 +165,15 @@ public void testReadStatsInt64WithoutMin(Optional fileCreatedBy) statistics.setMax(fromHex("3AA4000000000000")); assertThat(MetadataReader.readStats(fileCreatedBy, Optional.of(statistics), new PrimitiveType(OPTIONAL, INT64, "Test column"))) .isInstanceOfSatisfying(LongStatistics.class, columnStatistics -> { - assertFalse(columnStatistics.isEmpty()); + assertThat(columnStatistics.isEmpty()).isFalse(); - assertTrue(columnStatistics.isNumNullsSet()); - assertEquals(columnStatistics.getNumNulls(), 13); + assertThat(columnStatistics.isNumNullsSet()).isTrue(); + assertThat(columnStatistics.getNumNulls()).isEqualTo(13); - assertEquals(columnStatistics.getMin(), 0); - assertEquals(columnStatistics.getMax(), 0); // file statistics indicate 42042 - assertEquals(columnStatistics.genericGetMin(), (Long) 0L); - assertEquals(columnStatistics.genericGetMax(), (Long) 0L); // file statistics indicate 42042 + assertThat(columnStatistics.getMin()).isEqualTo(0); + assertThat(columnStatistics.getMax()).isEqualTo(0); // file statistics indicate 42042 + assertThat(columnStatistics.genericGetMin()).isEqualTo((Long) 0L); + assertThat(columnStatistics.genericGetMax()).isEqualTo((Long) 0L); // file statistics indicate 42042 }); } @@ -189,15 +185,15 @@ public void testReadStatsInt64WithoutMax(Optional fileCreatedBy) statistics.setMin(fromHex("F6FFFFFFFFFFFFFF")); assertThat(MetadataReader.readStats(fileCreatedBy, Optional.of(statistics), new PrimitiveType(OPTIONAL, INT64, "Test column"))) .isInstanceOfSatisfying(LongStatistics.class, columnStatistics -> { - assertFalse(columnStatistics.isEmpty()); + assertThat(columnStatistics.isEmpty()).isFalse(); - assertTrue(columnStatistics.isNumNullsSet()); - assertEquals(columnStatistics.getNumNulls(), 13); + assertThat(columnStatistics.isNumNullsSet()).isTrue(); + assertThat(columnStatistics.getNumNulls()).isEqualTo(13); - assertEquals(columnStatistics.getMin(), 0); // file statistics indicate -10 - assertEquals(columnStatistics.getMax(), 0); - assertEquals(columnStatistics.genericGetMin(), (Long) 0L); // file statistics indicate -10 - assertEquals(columnStatistics.genericGetMax(), (Long) 0L); + assertThat(columnStatistics.getMin()).isEqualTo(0); // file statistics indicate -10 + assertThat(columnStatistics.getMax()).isEqualTo(0); + assertThat(columnStatistics.genericGetMin()).isEqualTo((Long) 0L); // file statistics indicate -10 + assertThat(columnStatistics.genericGetMax()).isEqualTo((Long) 0L); }); } @@ -209,15 +205,15 @@ public void testReadStatsFloatWithoutMin(Optional fileCreatedBy) statistics.setMax(fromHex("12340000")); assertThat(MetadataReader.readStats(fileCreatedBy, Optional.of(statistics), new PrimitiveType(OPTIONAL, FLOAT, "Test column"))) .isInstanceOfSatisfying(FloatStatistics.class, columnStatistics -> { - assertFalse(columnStatistics.isEmpty()); + assertThat(columnStatistics.isEmpty()).isFalse(); - assertTrue(columnStatistics.isNumNullsSet()); - assertEquals(columnStatistics.getNumNulls(), 13); + assertThat(columnStatistics.isNumNullsSet()).isTrue(); + assertThat(columnStatistics.getNumNulls()).isEqualTo(13); - assertEquals(columnStatistics.getMin(), 0f); - assertEquals(columnStatistics.getMax(), 0f); - assertEquals(columnStatistics.genericGetMin(), 0f); - assertEquals(columnStatistics.genericGetMax(), 0f); + assertThat(columnStatistics.getMin()).isEqualTo(0f); + assertThat(columnStatistics.getMax()).isEqualTo(0f); + assertThat(columnStatistics.genericGetMin()).isEqualTo(0f); + assertThat(columnStatistics.genericGetMax()).isEqualTo(0f); }); } @@ -229,15 +225,15 @@ public void testReadStatsFloatWithoutMax(Optional fileCreatedBy) statistics.setMin(fromHex("1234ABCD")); assertThat(MetadataReader.readStats(fileCreatedBy, Optional.of(statistics), new PrimitiveType(OPTIONAL, FLOAT, "Test column"))) .isInstanceOfSatisfying(FloatStatistics.class, columnStatistics -> { - assertFalse(columnStatistics.isEmpty()); + assertThat(columnStatistics.isEmpty()).isFalse(); - assertTrue(columnStatistics.isNumNullsSet()); - assertEquals(columnStatistics.getNumNulls(), 13); + assertThat(columnStatistics.isNumNullsSet()).isTrue(); + assertThat(columnStatistics.getNumNulls()).isEqualTo(13); - assertEquals(columnStatistics.getMin(), 0f); - assertEquals(columnStatistics.getMax(), 0f); - assertEquals(columnStatistics.genericGetMin(), 0f); - assertEquals(columnStatistics.genericGetMax(), 0f); + assertThat(columnStatistics.getMin()).isEqualTo(0f); + assertThat(columnStatistics.getMax()).isEqualTo(0f); + assertThat(columnStatistics.genericGetMin()).isEqualTo(0f); + assertThat(columnStatistics.genericGetMax()).isEqualTo(0f); }); } @@ -249,15 +245,15 @@ public void testReadStatsDoubleWithoutMin(Optional fileCreatedBy) statistics.setMax(fromHex("3AA4000000000000")); assertThat(MetadataReader.readStats(fileCreatedBy, Optional.of(statistics), new PrimitiveType(OPTIONAL, DOUBLE, "Test column"))) .isInstanceOfSatisfying(DoubleStatistics.class, columnStatistics -> { - assertFalse(columnStatistics.isEmpty()); + assertThat(columnStatistics.isEmpty()).isFalse(); - assertTrue(columnStatistics.isNumNullsSet()); - assertEquals(columnStatistics.getNumNulls(), 13); + assertThat(columnStatistics.isNumNullsSet()).isTrue(); + assertThat(columnStatistics.getNumNulls()).isEqualTo(13); - assertEquals(columnStatistics.getMin(), 0d); - assertEquals(columnStatistics.getMax(), 0d); - assertEquals(columnStatistics.genericGetMin(), 0d); - assertEquals(columnStatistics.genericGetMax(), 0d); + assertThat(columnStatistics.getMin()).isEqualTo(0d); + assertThat(columnStatistics.getMax()).isEqualTo(0d); + assertThat(columnStatistics.genericGetMin()).isEqualTo(0d); + assertThat(columnStatistics.genericGetMax()).isEqualTo(0d); }); } @@ -269,15 +265,15 @@ public void testReadStatsDoubleWithoutMax(Optional fileCreatedBy) statistics.setMin(fromHex("F6FFFFFFFFFFFFFF")); assertThat(MetadataReader.readStats(fileCreatedBy, Optional.of(statistics), new PrimitiveType(OPTIONAL, DOUBLE, "Test column"))) .isInstanceOfSatisfying(DoubleStatistics.class, columnStatistics -> { - assertFalse(columnStatistics.isEmpty()); + assertThat(columnStatistics.isEmpty()).isFalse(); - assertTrue(columnStatistics.isNumNullsSet()); - assertEquals(columnStatistics.getNumNulls(), 13); + assertThat(columnStatistics.isNumNullsSet()).isTrue(); + assertThat(columnStatistics.getNumNulls()).isEqualTo(13); - assertEquals(columnStatistics.getMin(), 0d); - assertEquals(columnStatistics.getMax(), 0d); - assertEquals(columnStatistics.genericGetMin(), 0d); - assertEquals(columnStatistics.genericGetMax(), 0d); + assertThat(columnStatistics.getMin()).isEqualTo(0d); + assertThat(columnStatistics.getMax()).isEqualTo(0d); + assertThat(columnStatistics.genericGetMin()).isEqualTo(0d); + assertThat(columnStatistics.genericGetMax()).isEqualTo(0d); }); } @@ -293,13 +289,13 @@ public void testReadStatsBinary(Optional fileCreatedBy) assertThat(MetadataReader.readStats(fileCreatedBy, Optional.of(statistics), varbinary)) .isInstanceOfSatisfying(BinaryStatistics.class, columnStatistics -> { // Stats ignored because we did not provide original type and provided min/max for BINARY - assertEquals(columnStatistics.getNumNulls(), 13); - assertNull(columnStatistics.getMin()); - assertNull(columnStatistics.getMax()); - assertNull(columnStatistics.getMinBytes()); - assertNull(columnStatistics.getMaxBytes()); - assertNull(columnStatistics.genericGetMin()); - assertNull(columnStatistics.genericGetMax()); + assertThat(columnStatistics.getNumNulls()).isEqualTo(13); + assertThat(columnStatistics.getMin()).isNull(); + assertThat(columnStatistics.getMax()).isNull(); + assertThat(columnStatistics.getMinBytes()).isNull(); + assertThat(columnStatistics.getMaxBytes()).isNull(); + assertThat(columnStatistics.genericGetMin()).isNull(); + assertThat(columnStatistics.genericGetMax()).isNull(); }); // Stats written by Parquet after https://issues.apache.org/jira/browse/PARQUET-1025 @@ -309,17 +305,17 @@ public void testReadStatsBinary(Optional fileCreatedBy) statistics.setMax_value("é".getBytes(UTF_8)); assertThat(MetadataReader.readStats(fileCreatedBy, Optional.of(statistics), varbinary)) .isInstanceOfSatisfying(BinaryStatistics.class, columnStatistics -> { - assertFalse(columnStatistics.isEmpty()); + assertThat(columnStatistics.isEmpty()).isFalse(); - assertTrue(columnStatistics.isNumNullsSet()); - assertEquals(columnStatistics.getNumNulls(), 13); + assertThat(columnStatistics.isNumNullsSet()).isTrue(); + assertThat(columnStatistics.getNumNulls()).isEqualTo(13); - assertEquals(columnStatistics.getMin().getBytes(), new byte[] {'a'}); - assertEquals(columnStatistics.getMax().getBytes(), new byte[] {(byte) 0xC3, (byte) 0xA9}); - assertEquals(columnStatistics.getMinBytes(), new byte[] {'a'}); - assertEquals(columnStatistics.getMaxBytes(), new byte[] {(byte) 0xC3, (byte) 0xA9}); - assertEquals(columnStatistics.genericGetMin().getBytes(), new byte[] {'a'}); - assertEquals(columnStatistics.genericGetMax().getBytes(), new byte[] {(byte) 0xC3, (byte) 0xA9}); + assertThat(columnStatistics.getMin().getBytes()).isEqualTo(new byte[] {'a'}); + assertThat(columnStatistics.getMax().getBytes()).isEqualTo(new byte[] {(byte) 0xC3, (byte) 0xA9}); + assertThat(columnStatistics.getMinBytes()).isEqualTo(new byte[] {'a'}); + assertThat(columnStatistics.getMaxBytes()).isEqualTo(new byte[] {(byte) 0xC3, (byte) 0xA9}); + assertThat(columnStatistics.genericGetMin().getBytes()).isEqualTo(new byte[] {'a'}); + assertThat(columnStatistics.genericGetMax().getBytes()).isEqualTo(new byte[] {(byte) 0xC3, (byte) 0xA9}); }); } @@ -406,10 +402,10 @@ private void testReadStatsBinaryUtf8OldWriter(Optional fileCreatedBy, St } assertThat(MetadataReader.readStats(fileCreatedBy, Optional.of(statistics), Types.optional(BINARY).as(LogicalTypeAnnotation.stringType()).named("Test column"))) .isInstanceOfSatisfying(BinaryStatistics.class, columnStatistics -> { - assertFalse(columnStatistics.isEmpty()); + assertThat(columnStatistics.isEmpty()).isFalse(); - assertTrue(columnStatistics.isNumNullsSet()); - assertEquals(columnStatistics.getNumNulls(), 13); + assertThat(columnStatistics.isNumNullsSet()).isTrue(); + assertThat(columnStatistics.getNumNulls()).isEqualTo(13); byte[] expectedMinBytes = expectedMin != null ? expectedMin.getBytes(UTF_8) : null; assertThat(columnStatistics.getMinBytes()).isEqualTo(expectedMinBytes); @@ -418,8 +414,8 @@ private void testReadStatsBinaryUtf8OldWriter(Optional fileCreatedBy, St assertThat(columnStatistics.genericGetMin().getBytes()).isEqualTo(expectedMinBytes); } else { - assertNull(columnStatistics.getMin()); - assertNull(columnStatistics.genericGetMin()); + assertThat(columnStatistics.getMin()).isNull(); + assertThat(columnStatistics.genericGetMin()).isNull(); } byte[] expectedMaxBytes = expectedMax != null ? expectedMax.getBytes(UTF_8) : null; @@ -429,8 +425,8 @@ private void testReadStatsBinaryUtf8OldWriter(Optional fileCreatedBy, St assertThat(columnStatistics.genericGetMax().getBytes()).isEqualTo(expectedMaxBytes); } else { - assertNull(columnStatistics.getMax()); - assertNull(columnStatistics.genericGetMax()); + assertThat(columnStatistics.getMax()).isNull(); + assertThat(columnStatistics.genericGetMax()).isNull(); } }); } @@ -448,17 +444,17 @@ public void testReadStatsBinaryUtf8(Optional fileCreatedBy) statistics.setMax_value("é".getBytes(UTF_8)); assertThat(MetadataReader.readStats(fileCreatedBy, Optional.of(statistics), varchar)) .isInstanceOfSatisfying(BinaryStatistics.class, columnStatistics -> { - assertFalse(columnStatistics.isEmpty()); + assertThat(columnStatistics.isEmpty()).isFalse(); - assertTrue(columnStatistics.isNumNullsSet()); - assertEquals(columnStatistics.getNumNulls(), 13); + assertThat(columnStatistics.isNumNullsSet()).isTrue(); + assertThat(columnStatistics.getNumNulls()).isEqualTo(13); - assertEquals(columnStatistics.getMin().getBytes(), new byte[] {'a'}); - assertEquals(columnStatistics.getMax().getBytes(), new byte[] {(byte) 0xC3, (byte) 0xA9}); - assertEquals(columnStatistics.getMinBytes(), new byte[] {'a'}); - assertEquals(columnStatistics.getMaxBytes(), new byte[] {(byte) 0xC3, (byte) 0xA9}); - assertEquals(columnStatistics.genericGetMin().getBytes(), new byte[] {'a'}); - assertEquals(columnStatistics.genericGetMax().getBytes(), new byte[] {(byte) 0xC3, (byte) 0xA9}); + assertThat(columnStatistics.getMin().getBytes()).isEqualTo(new byte[] {'a'}); + assertThat(columnStatistics.getMax().getBytes()).isEqualTo(new byte[] {(byte) 0xC3, (byte) 0xA9}); + assertThat(columnStatistics.getMinBytes()).isEqualTo(new byte[] {'a'}); + assertThat(columnStatistics.getMaxBytes()).isEqualTo(new byte[] {(byte) 0xC3, (byte) 0xA9}); + assertThat(columnStatistics.genericGetMin().getBytes()).isEqualTo(new byte[] {'a'}); + assertThat(columnStatistics.genericGetMax().getBytes()).isEqualTo(new byte[] {(byte) 0xC3, (byte) 0xA9}); }); } @@ -469,25 +465,25 @@ public void testReadNullStats(Optional fileCreatedBy) assertThat(MetadataReader.readStats(fileCreatedBy, Optional.empty(), new PrimitiveType(OPTIONAL, INT32, "Test column"))) .isInstanceOfSatisfying( IntStatistics.class, - columnStatistics -> assertTrue(columnStatistics.isEmpty())); + columnStatistics -> assertThat(columnStatistics.isEmpty()).isTrue()); // bigint assertThat(MetadataReader.readStats(fileCreatedBy, Optional.empty(), new PrimitiveType(OPTIONAL, INT64, "Test column"))) .isInstanceOfSatisfying( LongStatistics.class, - columnStatistics -> assertTrue(columnStatistics.isEmpty())); + columnStatistics -> assertThat(columnStatistics.isEmpty()).isTrue()); // varchar assertThat(MetadataReader.readStats(fileCreatedBy, Optional.empty(), Types.optional(BINARY).as(LogicalTypeAnnotation.stringType()).named("Test column"))) .isInstanceOfSatisfying( BinaryStatistics.class, - columnStatistics -> assertTrue(columnStatistics.isEmpty())); + columnStatistics -> assertThat(columnStatistics.isEmpty()).isTrue()); // varbinary assertThat(MetadataReader.readStats(fileCreatedBy, Optional.empty(), new PrimitiveType(OPTIONAL, BINARY, "Test column"))) .isInstanceOfSatisfying( BinaryStatistics.class, - columnStatistics -> assertTrue(columnStatistics.isEmpty())); + columnStatistics -> assertThat(columnStatistics.isEmpty()).isTrue()); } @DataProvider diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestPageReader.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestPageReader.java index 329969053298..132bc97ffc0a 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestPageReader.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestPageReader.java @@ -64,11 +64,6 @@ import static org.apache.parquet.schema.Type.Repetition.REQUIRED; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; public class TestPageReader { @@ -306,16 +301,16 @@ private static void assertPages( { PageReader pageReader = createPageReader(valueCount, compressionCodec, hasDictionary, slices); DictionaryPage dictionaryPage = pageReader.readDictionaryPage(); - assertEquals(dictionaryPage != null, hasDictionary); + assertThat(dictionaryPage != null).isEqualTo(hasDictionary); for (int i = 0; i < pageCount; i++) { - assertTrue(pageReader.hasNext()); + assertThat(pageReader.hasNext()).isTrue(); DataPage decompressedPage = pageReader.readPage(); - assertNotNull(decompressedPage); + assertThat(decompressedPage).isNotNull(); assertDataPageEquals(pageHeader, DATA_PAGE, compressedDataPage, decompressedPage); } - assertFalse(pageReader.hasNext()); - assertNull(pageReader.readPage()); + assertThat(pageReader.hasNext()).isFalse(); + assertThat(pageReader.readPage()).isNull(); } @DataProvider diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestParquetDataSource.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestParquetDataSource.java index ff80346453f8..bc24c4a1030b 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestParquetDataSource.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/TestParquetDataSource.java @@ -34,7 +34,6 @@ import static io.trino.memory.context.AggregatedMemoryContext.newSimpleAggregatedMemoryContext; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestParquetDataSource { @@ -88,25 +87,25 @@ public void testMemoryAccounting() ChunkReader firstReader = Iterables.getOnlyElement(chunkReaders.get("1")); ChunkReader secondReader = Iterables.getOnlyElement(chunkReaders.get("2")); ChunkReader thirdReader = Iterables.getOnlyElement(chunkReaders.get("3")); - assertEquals(memoryContext.getBytes(), 0); + assertThat(memoryContext.getBytes()).isEqualTo(0); firstReader.read(); // first and second range are merged - assertEquals(memoryContext.getBytes(), 500); + assertThat(memoryContext.getBytes()).isEqualTo(500); firstReader.free(); // since the second reader is not freed, the memory is still retained - assertEquals(memoryContext.getBytes(), 500); + assertThat(memoryContext.getBytes()).isEqualTo(500); thirdReader.read(); // third reader is standalone so only retains its size - assertEquals(memoryContext.getBytes(), 700); + assertThat(memoryContext.getBytes()).isEqualTo(700); thirdReader.free(); // third reader is standalone, free releases the memory - assertEquals(memoryContext.getBytes(), 500); + assertThat(memoryContext.getBytes()).isEqualTo(500); secondReader.read(); // second reader is merged with the first, read only accesses already cached data - assertEquals(memoryContext.getBytes(), 500); + assertThat(memoryContext.getBytes()).isEqualTo(500); secondReader.free(); // both readers using merged reader are freed, all memory is released - assertEquals(memoryContext.getBytes(), 0); + assertThat(memoryContext.getBytes()).isEqualTo(0); } @Test diff --git a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/flat/TestBinaryBuffer.java b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/flat/TestBinaryBuffer.java index 12acf25b6f12..b187f8f1a04d 100644 --- a/lib/trino-parquet/src/test/java/io/trino/parquet/reader/flat/TestBinaryBuffer.java +++ b/lib/trino-parquet/src/test/java/io/trino/parquet/reader/flat/TestBinaryBuffer.java @@ -18,7 +18,6 @@ import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertSame; public class TestBinaryBuffer { @@ -31,14 +30,14 @@ public void testAsSlice() buffer.add(a, 0); Slice result = buffer.asSlice(); - assertSame(a, result); + assertThat(a).isSameAs(result); buffer.add(b, 1); result = buffer.asSlice(); Slice secondInvocation = buffer.asSlice(); - assertSame(secondInvocation, result); + assertThat(secondInvocation).isSameAs(result); assertThat(result.getBytes()).isEqualTo(new byte[] {0, 1}); } } diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/classloader/TestClassLoaderSafeWrappers.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/classloader/TestClassLoaderSafeWrappers.java index b7274558b7c2..6e9bb520cf34 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/classloader/TestClassLoaderSafeWrappers.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/classloader/TestClassLoaderSafeWrappers.java @@ -36,7 +36,7 @@ import static io.trino.spi.testing.InterfaceTestUtils.assertAllMethodsOverridden; import static java.lang.String.format; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Fail.fail; public class TestClassLoaderSafeWrappers { diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/io/TestByteBuffers.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/io/TestByteBuffers.java index 2b769545cdaa..304a53cd8f34 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/io/TestByteBuffers.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/io/TestByteBuffers.java @@ -18,7 +18,7 @@ import java.nio.ByteBuffer; import static io.trino.plugin.base.io.ByteBuffers.getWrappedBytes; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestByteBuffers { @@ -26,10 +26,16 @@ public class TestByteBuffers public void testGetWrappedBytes() { ByteBuffer buffer = ByteBuffer.wrap(new byte[] {0, 1, 2, 3}); - assertEquals(getWrappedBytes(buffer), new byte[] {0, 1, 2, 3}, "getWrappedBytes"); + assertThat(getWrappedBytes(buffer)) + .describedAs("getWrappedBytes") + .isEqualTo(new byte[] {0, 1, 2, 3}); // Assert the buffer position hasn't changed - assertEquals(buffer.position(), 0, "position"); - assertEquals(getWrappedBytes(buffer), new byte[] {0, 1, 2, 3}, "getWrappedBytes again"); + assertThat(buffer.position()) + .describedAs("position") + .isEqualTo(0); + assertThat(getWrappedBytes(buffer)) + .describedAs("getWrappedBytes again") + .isEqualTo(new byte[] {0, 1, 2, 3}); } } diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/logging/TestFormatInterpolator.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/logging/TestFormatInterpolator.java index 4fb7a245243c..e4278c902115 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/logging/TestFormatInterpolator.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/logging/TestFormatInterpolator.java @@ -16,8 +16,6 @@ import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestFormatInterpolator { @@ -52,11 +50,11 @@ public void testUnknownValueInterpolation() @Test public void testValidation() { - assertFalse(FormatInterpolator.hasValidPlaceholders("$UNKNOWN_VALUE", MultipleTestValues.values())); - assertTrue(FormatInterpolator.hasValidPlaceholders("$TEST_VALUE", MultipleTestValues.values())); - assertFalse(FormatInterpolator.hasValidPlaceholders("$TEST_VALUE and $UNKNOWN_VALUE", MultipleTestValues.values())); - assertTrue(FormatInterpolator.hasValidPlaceholders("$TEST_VALUE and $ANOTHER_VALUE", MultipleTestValues.values())); - assertTrue(FormatInterpolator.hasValidPlaceholders("$TEST_VALUE and $TEST_VALUE", MultipleTestValues.values())); + assertThat(FormatInterpolator.hasValidPlaceholders("$UNKNOWN_VALUE", MultipleTestValues.values())).isFalse(); + assertThat(FormatInterpolator.hasValidPlaceholders("$TEST_VALUE", MultipleTestValues.values())).isTrue(); + assertThat(FormatInterpolator.hasValidPlaceholders("$TEST_VALUE and $UNKNOWN_VALUE", MultipleTestValues.values())).isFalse(); + assertThat(FormatInterpolator.hasValidPlaceholders("$TEST_VALUE and $ANOTHER_VALUE", MultipleTestValues.values())).isTrue(); + assertThat(FormatInterpolator.hasValidPlaceholders("$TEST_VALUE and $TEST_VALUE", MultipleTestValues.values())).isTrue(); } public enum SingleTestValue diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/projection/TestApplyProjectionUtil.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/projection/TestApplyProjectionUtil.java index 5ae338c3c504..691a3a810b53 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/projection/TestApplyProjectionUtil.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/projection/TestApplyProjectionUtil.java @@ -26,9 +26,7 @@ import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.spi.type.RowType.field; import static io.trino.spi.type.RowType.rowType; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestApplyProjectionUtil { @@ -72,40 +70,40 @@ public class TestApplyProjectionUtil @Test public void testIsProjectionSupported() { - assertTrue(isPushdownSupported(ONE_LEVEL_DEREFERENCE, connectorExpression -> true)); - assertTrue(isPushdownSupported(TWO_LEVEL_DEREFERENCE, connectorExpression -> true)); - assertTrue(isPushdownSupported(INT_VARIABLE, connectorExpression -> true)); - assertFalse(isPushdownSupported(CONSTANT, connectorExpression -> true)); - - assertFalse(isPushdownSupported(ONE_LEVEL_DEREFERENCE, connectorExpression -> false)); - assertFalse(isPushdownSupported(TWO_LEVEL_DEREFERENCE, connectorExpression -> false)); - assertFalse(isPushdownSupported(INT_VARIABLE, connectorExpression -> false)); - assertFalse(isPushdownSupported(CONSTANT, connectorExpression -> false)); - - assertTrue(isPushdownSupported(LEAF_DOTTED_ONE_LEVEL_DEREFERENCE, this::isSupportedForPushDown)); - assertFalse(isPushdownSupported(LEAF_DOTTED_TWO_LEVEL_DEREFERENCE, this::isSupportedForPushDown)); - assertFalse(isPushdownSupported(MID_DOTTED_ONE_LEVEL_DEREFERENCE, this::isSupportedForPushDown)); - assertFalse(isPushdownSupported(MID_DOTTED_TWO_LEVEL_DEREFERENCE, this::isSupportedForPushDown)); + assertThat(isPushdownSupported(ONE_LEVEL_DEREFERENCE, connectorExpression -> true)).isTrue(); + assertThat(isPushdownSupported(TWO_LEVEL_DEREFERENCE, connectorExpression -> true)).isTrue(); + assertThat(isPushdownSupported(INT_VARIABLE, connectorExpression -> true)).isTrue(); + assertThat(isPushdownSupported(CONSTANT, connectorExpression -> true)).isFalse(); + + assertThat(isPushdownSupported(ONE_LEVEL_DEREFERENCE, connectorExpression -> false)).isFalse(); + assertThat(isPushdownSupported(TWO_LEVEL_DEREFERENCE, connectorExpression -> false)).isFalse(); + assertThat(isPushdownSupported(INT_VARIABLE, connectorExpression -> false)).isFalse(); + assertThat(isPushdownSupported(CONSTANT, connectorExpression -> false)).isFalse(); + + assertThat(isPushdownSupported(LEAF_DOTTED_ONE_LEVEL_DEREFERENCE, this::isSupportedForPushDown)).isTrue(); + assertThat(isPushdownSupported(LEAF_DOTTED_TWO_LEVEL_DEREFERENCE, this::isSupportedForPushDown)).isFalse(); + assertThat(isPushdownSupported(MID_DOTTED_ONE_LEVEL_DEREFERENCE, this::isSupportedForPushDown)).isFalse(); + assertThat(isPushdownSupported(MID_DOTTED_TWO_LEVEL_DEREFERENCE, this::isSupportedForPushDown)).isFalse(); } @Test public void testExtractSupportedProjectionColumns() { - assertEquals(extractSupportedProjectedColumns(ONE_LEVEL_DEREFERENCE), ImmutableList.of(ONE_LEVEL_DEREFERENCE)); - assertEquals(extractSupportedProjectedColumns(TWO_LEVEL_DEREFERENCE), ImmutableList.of(TWO_LEVEL_DEREFERENCE)); - assertEquals(extractSupportedProjectedColumns(INT_VARIABLE), ImmutableList.of(INT_VARIABLE)); - assertEquals(extractSupportedProjectedColumns(CONSTANT), ImmutableList.of()); + assertThat(extractSupportedProjectedColumns(ONE_LEVEL_DEREFERENCE)).isEqualTo(ImmutableList.of(ONE_LEVEL_DEREFERENCE)); + assertThat(extractSupportedProjectedColumns(TWO_LEVEL_DEREFERENCE)).isEqualTo(ImmutableList.of(TWO_LEVEL_DEREFERENCE)); + assertThat(extractSupportedProjectedColumns(INT_VARIABLE)).isEqualTo(ImmutableList.of(INT_VARIABLE)); + assertThat(extractSupportedProjectedColumns(CONSTANT)).isEqualTo(ImmutableList.of()); - assertEquals(extractSupportedProjectedColumns(ONE_LEVEL_DEREFERENCE, connectorExpression -> false), ImmutableList.of()); - assertEquals(extractSupportedProjectedColumns(TWO_LEVEL_DEREFERENCE, connectorExpression -> false), ImmutableList.of()); - assertEquals(extractSupportedProjectedColumns(INT_VARIABLE, connectorExpression -> false), ImmutableList.of()); - assertEquals(extractSupportedProjectedColumns(CONSTANT, connectorExpression -> false), ImmutableList.of()); + assertThat(extractSupportedProjectedColumns(ONE_LEVEL_DEREFERENCE, connectorExpression -> false)).isEqualTo(ImmutableList.of()); + assertThat(extractSupportedProjectedColumns(TWO_LEVEL_DEREFERENCE, connectorExpression -> false)).isEqualTo(ImmutableList.of()); + assertThat(extractSupportedProjectedColumns(INT_VARIABLE, connectorExpression -> false)).isEqualTo(ImmutableList.of()); + assertThat(extractSupportedProjectedColumns(CONSTANT, connectorExpression -> false)).isEqualTo(ImmutableList.of()); // Partial supported projection - assertEquals(extractSupportedProjectedColumns(LEAF_DOTTED_ONE_LEVEL_DEREFERENCE, this::isSupportedForPushDown), ImmutableList.of(LEAF_DOTTED_ONE_LEVEL_DEREFERENCE)); - assertEquals(extractSupportedProjectedColumns(LEAF_DOTTED_TWO_LEVEL_DEREFERENCE, this::isSupportedForPushDown), ImmutableList.of(LEAF_DOTTED_ONE_LEVEL_DEREFERENCE)); - assertEquals(extractSupportedProjectedColumns(MID_DOTTED_ONE_LEVEL_DEREFERENCE, this::isSupportedForPushDown), ImmutableList.of(MID_DOTTED_ROW_OF_ROW_VARIABLE)); - assertEquals(extractSupportedProjectedColumns(MID_DOTTED_TWO_LEVEL_DEREFERENCE, this::isSupportedForPushDown), ImmutableList.of(MID_DOTTED_ROW_OF_ROW_VARIABLE)); + assertThat(extractSupportedProjectedColumns(LEAF_DOTTED_ONE_LEVEL_DEREFERENCE, this::isSupportedForPushDown)).isEqualTo(ImmutableList.of(LEAF_DOTTED_ONE_LEVEL_DEREFERENCE)); + assertThat(extractSupportedProjectedColumns(LEAF_DOTTED_TWO_LEVEL_DEREFERENCE, this::isSupportedForPushDown)).isEqualTo(ImmutableList.of(LEAF_DOTTED_ONE_LEVEL_DEREFERENCE)); + assertThat(extractSupportedProjectedColumns(MID_DOTTED_ONE_LEVEL_DEREFERENCE, this::isSupportedForPushDown)).isEqualTo(ImmutableList.of(MID_DOTTED_ROW_OF_ROW_VARIABLE)); + assertThat(extractSupportedProjectedColumns(MID_DOTTED_TWO_LEVEL_DEREFERENCE, this::isSupportedForPushDown)).isEqualTo(ImmutableList.of(MID_DOTTED_ROW_OF_ROW_VARIABLE)); } /** diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedConnectorAccessControlTest.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedConnectorAccessControlTest.java index dd5cb9fb8e16..7dc2e76eae3b 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedConnectorAccessControlTest.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedConnectorAccessControlTest.java @@ -32,7 +32,6 @@ import io.trino.spi.security.TrinoPrincipal; import io.trino.spi.security.ViewExpression; import org.junit.jupiter.api.Test; -import org.testng.Assert.ThrowingRunnable; import java.io.File; import java.util.List; @@ -51,7 +50,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.util.Files.newTemporaryFile; -import static org.testng.Assert.assertEquals; public abstract class BaseFileBasedConnectorAccessControlTest { @@ -94,7 +92,7 @@ public void testEmptyFile() .add(new SchemaTableName("secret", "any")) .add(new SchemaTableName("any", "any")) .build(); - assertEquals(accessControl.filterTables(UNKNOWN, tables), tables); + assertThat(accessControl.filterTables(UNKNOWN, tables)).isEqualTo(tables); // permissions management APIs are hard coded to deny TrinoPrincipal someUser = new TrinoPrincipal(USER, "some_user"); @@ -293,14 +291,12 @@ public void testTableRules() accessControl.checkCanSelectFromColumns(ALICE, bobTable, ImmutableSet.of("bobcolumn")); accessControl.checkCanShowColumns(ALICE, bobTable); - assertEquals( - accessControl.filterColumns(ALICE, Map.of(bobTable, ImmutableSet.of("a"))), - Map.of(bobTable, ImmutableSet.of("a"))); + assertThat(accessControl.filterColumns(ALICE, Map.of(bobTable, ImmutableSet.of("a")))) + .isEqualTo(Map.of(bobTable, ImmutableSet.of("a"))); accessControl.checkCanSelectFromColumns(BOB, bobTable, ImmutableSet.of()); accessControl.checkCanShowColumns(BOB, bobTable); - assertEquals( - accessControl.filterColumns(BOB, Map.of(bobTable, ImmutableSet.of("a"))), - Map.of(bobTable, ImmutableSet.of("a"))); + assertThat(accessControl.filterColumns(BOB, Map.of(bobTable, ImmutableSet.of("a")))) + .isEqualTo(Map.of(bobTable, ImmutableSet.of("a"))); accessControl.checkCanInsertIntoTable(BOB, bobTable); accessControl.checkCanDeleteFromTable(BOB, bobTable); @@ -388,12 +384,8 @@ public void testTableRulesForMixedGroupUsers() accessControl.checkCanDeleteFromTable(userGroup1Group2, myTable); accessControl.checkCanDropTable(userGroup1Group2, myTable); accessControl.checkCanSelectFromColumns(userGroup1Group2, myTable, ImmutableSet.of()); - assertEquals( - accessControl.getColumnMask(userGroup1Group2, myTable, "col_a", VARCHAR), - Optional.empty()); - assertEquals( - accessControl.getRowFilters(userGroup1Group2, myTable), - ImmutableList.of()); + assertThat(accessControl.getColumnMask(userGroup1Group2, myTable, "col_a", VARCHAR)).isEqualTo(Optional.empty()); + assertThat(accessControl.getRowFilters(userGroup1Group2, myTable)).isEqualTo(ImmutableList.of()); assertDenied(() -> accessControl.checkCanCreateTable(userGroup2, myTable, Map.of())); assertDenied(() -> accessControl.checkCanInsertIntoTable(userGroup2, myTable)); @@ -407,9 +399,7 @@ public void testTableRulesForMixedGroupUsers() .schema("my_schema") .expression("'mask_a'") .build()); - assertEquals( - accessControl.getRowFilters(userGroup2, myTable), - ImmutableList.of()); + assertThat(accessControl.getRowFilters(userGroup2, myTable)).isEqualTo(ImmutableList.of()); ConnectorSecurityContext userGroup1Group3 = user("user_1_3", ImmutableSet.of("group1", "group3")); ConnectorSecurityContext userGroup3 = user("user_3", ImmutableSet.of("group3")); @@ -419,9 +409,7 @@ public void testTableRulesForMixedGroupUsers() accessControl.checkCanDeleteFromTable(userGroup1Group3, myTable); accessControl.checkCanDropTable(userGroup1Group3, myTable); accessControl.checkCanSelectFromColumns(userGroup1Group3, myTable, ImmutableSet.of()); - assertEquals( - accessControl.getColumnMask(userGroup1Group3, myTable, "col_a", VARCHAR), - Optional.empty()); + assertThat(accessControl.getColumnMask(userGroup1Group3, myTable, "col_a", VARCHAR)).isEqualTo(Optional.empty()); assertDenied(() -> accessControl.checkCanCreateTable(userGroup3, myTable, Map.of())); assertDenied(() -> accessControl.checkCanInsertIntoTable(userGroup3, myTable)); @@ -437,7 +425,7 @@ public void testTableRulesForMixedGroupUsers() .build()); List rowFilters = accessControl.getRowFilters(userGroup3, myTable); - assertEquals(rowFilters.size(), 1); + assertThat(rowFilters.size()).isEqualTo(1); assertViewExpressionEquals( rowFilters.get(0), ViewExpression.builder() @@ -449,11 +437,21 @@ public void testTableRulesForMixedGroupUsers() private static void assertViewExpressionEquals(ViewExpression actual, ViewExpression expected) { - assertEquals(actual.getSecurityIdentity(), expected.getSecurityIdentity(), "Identity"); - assertEquals(actual.getCatalog(), expected.getCatalog(), "Catalog"); - assertEquals(actual.getSchema(), expected.getSchema(), "Schema"); - assertEquals(actual.getExpression(), expected.getExpression(), "Expression"); - assertEquals(actual.getPath(), expected.getPath(), "Path"); + assertThat(actual.getSecurityIdentity()) + .describedAs("Identity") + .isEqualTo(expected.getSecurityIdentity()); + assertThat(actual.getCatalog()) + .describedAs("Catalog") + .isEqualTo(expected.getCatalog()); + assertThat(actual.getSchema()) + .describedAs("Schema") + .isEqualTo(expected.getSchema()); + assertThat(actual.getExpression()) + .describedAs("Expression") + .isEqualTo(expected.getExpression()); + assertThat(actual.getPath()) + .describedAs("Path") + .isEqualTo(expected.getPath()); } @Test @@ -469,15 +467,15 @@ public void testTableFilter() .add(new SchemaTableName("bobschema", "any")) .add(new SchemaTableName("any", "any")) .build(); - assertEquals(accessControl.filterTables(ALICE, tables), ImmutableSet.builder() + assertThat(accessControl.filterTables(ALICE, tables)).isEqualTo(ImmutableSet.builder() .add(new SchemaTableName("aliceschema", "any")) .add(new SchemaTableName("aliceschema", "bobtable")) .build()); - assertEquals(accessControl.filterTables(BOB, tables), ImmutableSet.builder() + assertThat(accessControl.filterTables(BOB, tables)).isEqualTo(ImmutableSet.builder() .add(new SchemaTableName("aliceschema", "bobtable")) .add(new SchemaTableName("bobschema", "bob_any")) .build()); - assertEquals(accessControl.filterTables(ADMIN, tables), ImmutableSet.builder() + assertThat(accessControl.filterTables(ADMIN, tables)).isEqualTo(ImmutableSet.builder() .add(new SchemaTableName("secret", "any")) .add(new SchemaTableName("aliceschema", "any")) .add(new SchemaTableName("aliceschema", "bobtable")) @@ -494,17 +492,16 @@ public void testNoTableRules() SchemaTableName bobTable = new SchemaTableName("bobschema", "bobtable"); assertDenied(() -> accessControl.checkCanShowColumns(BOB, bobTable)); assertDenied(() -> accessControl.checkCanShowTables(BOB, "bobschema")); - assertEquals( - accessControl.filterColumns(BOB, Map.of(bobTable, ImmutableSet.of("a"))), - Map.of(bobTable, ImmutableSet.of())); + assertThat(accessControl.filterColumns(BOB, Map.of(bobTable, ImmutableSet.of("a")))) + .isEqualTo(Map.of(bobTable, ImmutableSet.of())); Set tables = ImmutableSet.builder() .add(new SchemaTableName("restricted", "any")) .add(new SchemaTableName("secret", "any")) .add(new SchemaTableName("any", "any")) .build(); - assertEquals(accessControl.filterTables(ALICE, tables), ImmutableSet.of()); - assertEquals(accessControl.filterTables(BOB, tables), ImmutableSet.of()); + assertThat(accessControl.filterTables(ALICE, tables)).isEqualTo(ImmutableSet.of()); + assertThat(accessControl.filterTables(BOB, tables)).isEqualTo(ImmutableSet.of()); } @Test @@ -526,8 +523,8 @@ public void testNoFunctionRules() .add(new SchemaFunctionName("secret", "any")) .add(new SchemaFunctionName("any", "any")) .build(); - assertEquals(accessControl.filterFunctions(ALICE, functions), ImmutableSet.of()); - assertEquals(accessControl.filterFunctions(BOB, functions), ImmutableSet.of()); + assertThat(accessControl.filterFunctions(ALICE, functions)).isEqualTo(ImmutableSet.of()); + assertThat(accessControl.filterFunctions(BOB, functions)).isEqualTo(ImmutableSet.of()); } @Test @@ -557,10 +554,10 @@ public void testFilterSchemas() private static void assertFilterSchemas(ConnectorAccessControl accessControl) { ImmutableSet allSchemas = ImmutableSet.of("specific-schema", "alice-schema", "bob-schema", "unknown", "ptf_schema", "procedure-schema"); - assertEquals(accessControl.filterSchemas(ADMIN, allSchemas), allSchemas); - assertEquals(accessControl.filterSchemas(ALICE, allSchemas), ImmutableSet.of("specific-schema", "alice-schema", "ptf_schema")); - assertEquals(accessControl.filterSchemas(BOB, allSchemas), ImmutableSet.of("specific-schema", "bob-schema", "procedure-schema")); - assertEquals(accessControl.filterSchemas(CHARLIE, allSchemas), ImmutableSet.of("specific-schema")); + assertThat(accessControl.filterSchemas(ADMIN, allSchemas)).isEqualTo(allSchemas); + assertThat(accessControl.filterSchemas(ALICE, allSchemas)).isEqualTo(ImmutableSet.of("specific-schema", "alice-schema", "ptf_schema")); + assertThat(accessControl.filterSchemas(BOB, allSchemas)).isEqualTo(ImmutableSet.of("specific-schema", "bob-schema", "procedure-schema")); + assertThat(accessControl.filterSchemas(CHARLIE, allSchemas)).isEqualTo(ImmutableSet.of("specific-schema")); } @Test @@ -755,15 +752,15 @@ public void testFunctionFilter() .add(new SchemaFunctionName("bobschema", "any")) .add(new SchemaFunctionName("any", "any")) .build(); - assertEquals(accessControl.filterFunctions(ALICE, functions), ImmutableSet.builder() + assertThat(accessControl.filterFunctions(ALICE, functions)).isEqualTo(ImmutableSet.builder() .add(new SchemaFunctionName("aliceschema", "any")) .add(new SchemaFunctionName("aliceschema", "bobfunction")) .build()); - assertEquals(accessControl.filterFunctions(BOB, functions), ImmutableSet.builder() + assertThat(accessControl.filterFunctions(BOB, functions)).isEqualTo(ImmutableSet.builder() .add(new SchemaFunctionName("aliceschema", "bobfunction")) .add(new SchemaFunctionName("bobschema", "bob_any")) .build()); - assertEquals(accessControl.filterFunctions(ADMIN, functions), ImmutableSet.builder() + assertThat(accessControl.filterFunctions(ADMIN, functions)).isEqualTo(ImmutableSet.builder() .add(new SchemaFunctionName("secret", "any")) .add(new SchemaFunctionName("aliceschema", "any")) .add(new SchemaFunctionName("aliceschema", "bobfunction")) @@ -850,4 +847,10 @@ private static void assertDenied(ThrowingRunnable runnable) // TODO test expected message precisely, as in TestFileBasedSystemAccessControl .hasMessageStartingWith("Access Denied"); } + + interface ThrowingRunnable + { + void run() + throws Exception; + } } diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedSystemAccessControlTest.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedSystemAccessControlTest.java index d9faa620cf24..040b5e226332 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedSystemAccessControlTest.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/BaseFileBasedSystemAccessControlTest.java @@ -55,7 +55,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.util.Files.newTemporaryFile; -import static org.testng.Assert.assertEquals; public abstract class BaseFileBasedSystemAccessControlTest { @@ -513,24 +512,18 @@ public void testTableRulesForFilterColumns() { SystemAccessControl accessControl = newFileBasedSystemAccessControl("file-based-system-access-table.json"); - assertEquals( - accessControl.filterColumns( - ALICE, - new CatalogSchemaTableName("some-catalog", "bobschema", "bobcolumns"), - ImmutableSet.of("private", "a", "restricted", "b")), - ImmutableSet.of("private", "a", "restricted", "b")); - assertEquals( - accessControl.filterColumns( - BOB, - new CatalogSchemaTableName("some-catalog", "bobschema", "bobcolumns"), - ImmutableSet.of("private", "a", "restricted", "b")), - ImmutableSet.of("private", "a", "restricted", "b")); - assertEquals( - accessControl.filterColumns( - CHARLIE, - new CatalogSchemaTableName("some-catalog", "bobschema", "bobcolumns"), - ImmutableSet.of("private", "a", "restricted", "b")), - ImmutableSet.of("a", "b")); + assertThat(accessControl.filterColumns( + ALICE, + new CatalogSchemaTableName("some-catalog", "bobschema", "bobcolumns"), + ImmutableSet.of("private", "a", "restricted", "b"))).isEqualTo(ImmutableSet.of("private", "a", "restricted", "b")); + assertThat(accessControl.filterColumns( + BOB, + new CatalogSchemaTableName("some-catalog", "bobschema", "bobcolumns"), + ImmutableSet.of("private", "a", "restricted", "b"))).isEqualTo(ImmutableSet.of("private", "a", "restricted", "b")); + assertThat(accessControl.filterColumns( + CHARLIE, + new CatalogSchemaTableName("some-catalog", "bobschema", "bobcolumns"), + ImmutableSet.of("private", "a", "restricted", "b"))).isEqualTo(ImmutableSet.of("a", "b")); } @Test @@ -546,15 +539,15 @@ public void testTableFilter() .add(new SchemaTableName("bobschema", "any")) .add(new SchemaTableName("any", "any")) .build(); - assertEquals(accessControl.filterTables(ALICE, "any", tables), ImmutableSet.builder() + assertThat(accessControl.filterTables(ALICE, "any", tables)).isEqualTo(ImmutableSet.builder() .add(new SchemaTableName("aliceschema", "any")) .add(new SchemaTableName("aliceschema", "bobtable")) .build()); - assertEquals(accessControl.filterTables(BOB, "any", tables), ImmutableSet.builder() + assertThat(accessControl.filterTables(BOB, "any", tables)).isEqualTo(ImmutableSet.builder() .add(new SchemaTableName("aliceschema", "bobtable")) .add(new SchemaTableName("bobschema", "bob_any")) .build()); - assertEquals(accessControl.filterTables(ADMIN, "any", tables), ImmutableSet.builder() + assertThat(accessControl.filterTables(ADMIN, "any", tables)).isEqualTo(ImmutableSet.builder() .add(new SchemaTableName("secret", "any")) .add(new SchemaTableName("aliceschema", "any")) .add(new SchemaTableName("aliceschema", "bobtable")) @@ -574,17 +567,15 @@ public void testTableFilterNoAccess() .add(new SchemaTableName("secret", "any")) .add(new SchemaTableName("any", "any")) .build(); - assertEquals(accessControl.filterTables(ALICE, "any", tables), ImmutableSet.of()); - assertEquals(accessControl.filterTables(BOB, "any", tables), ImmutableSet.of()); + assertThat(accessControl.filterTables(ALICE, "any", tables)).isEqualTo(ImmutableSet.of()); + assertThat(accessControl.filterTables(BOB, "any", tables)).isEqualTo(ImmutableSet.of()); } @Test public void testTableRulesForFilterColumnsWithNoAccess() { SystemAccessControl accessControl = newFileBasedSystemAccessControl("file-based-system-no-access.json"); - assertEquals( - accessControl.filterColumns(BOB, new CatalogSchemaTableName("some-catalog", "bobschema", "bobtable"), ImmutableSet.of("a")), - ImmutableSet.of()); + assertThat(accessControl.filterColumns(BOB, new CatalogSchemaTableName("some-catalog", "bobschema", "bobtable"), ImmutableSet.of("a"))).isEqualTo(ImmutableSet.of()); } @Test @@ -756,13 +747,11 @@ public void testTableRulesForMixedGroupUsers() SystemSecurityContext userGroup2 = new SystemSecurityContext(Identity.forUser("user_2") .withGroups(ImmutableSet.of("group2")).build(), queryId, queryStart); - assertEquals( - accessControl.getColumnMask( - userGroup1Group2, - new CatalogSchemaTableName("some-catalog", "my_schema", "my_table"), - "col_a", - VARCHAR), - Optional.empty()); + assertThat(accessControl.getColumnMask( + userGroup1Group2, + new CatalogSchemaTableName("some-catalog", "my_schema", "my_table"), + "col_a", + VARCHAR)).isEqualTo(Optional.empty()); assertViewExpressionEquals( accessControl.getColumnMask( @@ -781,16 +770,14 @@ public void testTableRulesForMixedGroupUsers() SystemSecurityContext userGroup3 = new SystemSecurityContext(Identity.forUser("user_3") .withGroups(ImmutableSet.of("group3")).build(), queryId, queryStart); - assertEquals( - accessControl.getRowFilters( - userGroup1Group3, - new CatalogSchemaTableName("some-catalog", "my_schema", "my_table")), - ImmutableList.of()); + assertThat(accessControl.getRowFilters( + userGroup1Group3, + new CatalogSchemaTableName("some-catalog", "my_schema", "my_table"))).isEqualTo(ImmutableList.of()); List rowFilters = accessControl.getRowFilters( userGroup3, new CatalogSchemaTableName("some-catalog", "my_schema", "my_table")); - assertEquals(rowFilters.size(), 1); + assertThat(rowFilters.size()).isEqualTo(1); assertViewExpressionEquals( rowFilters.get(0), ViewExpression.builder() @@ -867,12 +854,12 @@ public void testQuery() accessControlManager.checkCanExecuteQuery(admin); accessControlManager.checkCanViewQueryOwnedBy(admin, any); - assertEquals(accessControlManager.filterViewQueryOwnedBy(admin, ImmutableSet.of(Identity.ofUser("a"), Identity.ofUser("b"))), ImmutableSet.of(Identity.ofUser("a"), Identity.ofUser("b"))); + assertThat(accessControlManager.filterViewQueryOwnedBy(admin, ImmutableSet.of(Identity.ofUser("a"), Identity.ofUser("b")))).isEqualTo(ImmutableSet.of(Identity.ofUser("a"), Identity.ofUser("b"))); accessControlManager.checkCanKillQueryOwnedBy(admin, any); accessControlManager.checkCanExecuteQuery(alice); accessControlManager.checkCanViewQueryOwnedBy(alice, any); - assertEquals(accessControlManager.filterViewQueryOwnedBy(alice, ImmutableSet.of(Identity.ofUser("a"), Identity.ofUser("b"))), ImmutableSet.of(Identity.ofUser("a"), Identity.ofUser("b"))); + assertThat(accessControlManager.filterViewQueryOwnedBy(alice, ImmutableSet.of(Identity.ofUser("a"), Identity.ofUser("b")))).isEqualTo(ImmutableSet.of(Identity.ofUser("a"), Identity.ofUser("b"))); assertAccessDenied( () -> accessControlManager.checkCanKillQueryOwnedBy(alice, any), "Cannot view query"); @@ -883,14 +870,13 @@ public void testQuery() assertAccessDenied( () -> accessControlManager.checkCanViewQueryOwnedBy(bob, any), "Cannot view query"); - assertEquals(accessControlManager.filterViewQueryOwnedBy(bob, ImmutableSet.of(Identity.ofUser("a"), Identity.ofUser("b"))), ImmutableSet.of()); + assertThat(accessControlManager.filterViewQueryOwnedBy(bob, ImmutableSet.of(Identity.ofUser("a"), Identity.ofUser("b")))).isEqualTo(ImmutableSet.of()); accessControlManager.checkCanKillQueryOwnedBy(bob, any); accessControlManager.checkCanExecuteQuery(dave); accessControlManager.checkCanViewQueryOwnedBy(dave, alice); accessControlManager.checkCanViewQueryOwnedBy(dave, dave); - assertEquals(accessControlManager.filterViewQueryOwnedBy(dave, ImmutableSet.of(Identity.ofUser("alice"), Identity.ofUser("bob"), Identity.ofUser("dave"), Identity.ofUser("admin"))), - ImmutableSet.of(Identity.ofUser("alice"), Identity.ofUser("dave"))); + assertThat(accessControlManager.filterViewQueryOwnedBy(dave, ImmutableSet.of(Identity.ofUser("alice"), Identity.ofUser("bob"), Identity.ofUser("dave"), Identity.ofUser("admin")))).isEqualTo(ImmutableSet.of(Identity.ofUser("alice"), Identity.ofUser("dave"))); assertAccessDenied( () -> accessControlManager.checkCanKillQueryOwnedBy(dave, alice), "Cannot view query"); @@ -913,7 +899,7 @@ public void testQuery() accessControlManager.checkCanExecuteQuery(nonAsciiUser); accessControlManager.checkCanViewQueryOwnedBy(nonAsciiUser, any); - assertEquals(accessControlManager.filterViewQueryOwnedBy(nonAsciiUser, ImmutableSet.of(Identity.ofUser("a"), Identity.ofUser("b"))), ImmutableSet.of(Identity.ofUser("a"), Identity.ofUser("b"))); + assertThat(accessControlManager.filterViewQueryOwnedBy(nonAsciiUser, ImmutableSet.of(Identity.ofUser("a"), Identity.ofUser("b")))).isEqualTo(ImmutableSet.of(Identity.ofUser("a"), Identity.ofUser("b"))); accessControlManager.checkCanKillQueryOwnedBy(nonAsciiUser, any); } @@ -931,7 +917,7 @@ public void testQueryNotSet() accessControlManager.checkCanExecuteQuery(bob); accessControlManager.checkCanViewQueryOwnedBy(bob, any); - assertEquals(accessControlManager.filterViewQueryOwnedBy(bob, ImmutableSet.of(Identity.ofUser("a"), Identity.ofUser("b"))), ImmutableSet.of(Identity.ofUser("a"), Identity.ofUser("b"))); + assertThat(accessControlManager.filterViewQueryOwnedBy(bob, ImmutableSet.of(Identity.ofUser("a"), Identity.ofUser("b")))).isEqualTo(ImmutableSet.of(Identity.ofUser("a"), Identity.ofUser("b"))); accessControlManager.checkCanKillQueryOwnedBy(bob, any); } @@ -943,21 +929,21 @@ public void testQueryDocsExample() accessControlManager.checkCanExecuteQuery(admin); accessControlManager.checkCanViewQueryOwnedBy(admin, any); - assertEquals(accessControlManager.filterViewQueryOwnedBy(admin, ImmutableSet.of(Identity.ofUser("a"), Identity.ofUser("b"))), ImmutableSet.of(Identity.ofUser("a"), Identity.ofUser("b"))); + assertThat(accessControlManager.filterViewQueryOwnedBy(admin, ImmutableSet.of(Identity.ofUser("a"), Identity.ofUser("b")))).isEqualTo(ImmutableSet.of(Identity.ofUser("a"), Identity.ofUser("b"))); accessControlManager.checkCanKillQueryOwnedBy(admin, any); accessControlManager.checkCanExecuteQuery(alice); assertAccessDenied( () -> accessControlManager.checkCanViewQueryOwnedBy(alice, any), "Cannot view query"); - assertEquals(accessControlManager.filterViewQueryOwnedBy(alice, ImmutableSet.of(Identity.ofUser("a"), Identity.ofUser("b"))), ImmutableSet.of()); + assertThat(accessControlManager.filterViewQueryOwnedBy(alice, ImmutableSet.of(Identity.ofUser("a"), Identity.ofUser("b")))).isEqualTo(ImmutableSet.of()); accessControlManager.checkCanKillQueryOwnedBy(alice, any); accessControlManager.checkCanExecuteQuery(alice); assertAccessDenied( () -> accessControlManager.checkCanViewQueryOwnedBy(bob, any), "Cannot view query"); - assertEquals(accessControlManager.filterViewQueryOwnedBy(bob, ImmutableSet.of(Identity.ofUser("a"), Identity.ofUser("b"))), ImmutableSet.of()); + assertThat(accessControlManager.filterViewQueryOwnedBy(bob, ImmutableSet.of(Identity.ofUser("a"), Identity.ofUser("b")))).isEqualTo(ImmutableSet.of()); assertAccessDenied( () -> accessControlManager.checkCanKillQueryOwnedBy(bob, any), "Cannot view query"); @@ -965,8 +951,7 @@ public void testQueryDocsExample() accessControlManager.checkCanExecuteQuery(dave); accessControlManager.checkCanViewQueryOwnedBy(dave, alice); accessControlManager.checkCanViewQueryOwnedBy(dave, dave); - assertEquals(accessControlManager.filterViewQueryOwnedBy(dave, ImmutableSet.of(Identity.ofUser("alice"), Identity.ofUser("bob"), Identity.ofUser("dave"), Identity.ofUser("admin"))), - ImmutableSet.of(Identity.ofUser("alice"), Identity.ofUser("dave"))); + assertThat(accessControlManager.filterViewQueryOwnedBy(dave, ImmutableSet.of(Identity.ofUser("alice"), Identity.ofUser("bob"), Identity.ofUser("dave"), Identity.ofUser("admin")))).isEqualTo(ImmutableSet.of(Identity.ofUser("alice"), Identity.ofUser("dave"))); assertAccessDenied( () -> accessControlManager.checkCanKillQueryOwnedBy(dave, alice), "Cannot view query"); @@ -1121,13 +1106,13 @@ public void testFilterCatalogs() "unknown", "ptf-catalog"); - assertEquals(accessControl.filterCatalogs(ADMIN, allCatalogs), Sets.difference(allCatalogs, ImmutableSet.of("blocked-catalog"))); + assertThat(accessControl.filterCatalogs(ADMIN, allCatalogs)).isEqualTo(Sets.difference(allCatalogs, ImmutableSet.of("blocked-catalog"))); Set aliceCatalogs = ImmutableSet.of("specific-catalog", "alice-catalog", "ptf-catalog"); - assertEquals(accessControl.filterCatalogs(ALICE, allCatalogs), aliceCatalogs); + assertThat(accessControl.filterCatalogs(ALICE, allCatalogs)).isEqualTo(aliceCatalogs); Set bobCatalogs = ImmutableSet.of("specific-catalog", "alice-catalog", "bob-catalog"); - assertEquals(accessControl.filterCatalogs(BOB, allCatalogs), bobCatalogs); + assertThat(accessControl.filterCatalogs(BOB, allCatalogs)).isEqualTo(bobCatalogs); Set charlieCatalogs = ImmutableSet.of("specific-catalog"); - assertEquals(accessControl.filterCatalogs(CHARLIE, allCatalogs), charlieCatalogs); + assertThat(accessControl.filterCatalogs(CHARLIE, allCatalogs)).isEqualTo(charlieCatalogs); } @Test @@ -1185,55 +1170,55 @@ public void testFilterSchemas() { SystemAccessControl accessControl = newFileBasedSystemAccessControl("file-based-system-access-visibility.json"); - assertEquals(accessControl.filterSchemas(ADMIN, "specific-catalog", ImmutableSet.of("specific-schema", "unknown")), ImmutableSet.of("specific-schema", "unknown")); - assertEquals(accessControl.filterSchemas(ALICE, "specific-catalog", ImmutableSet.of("specific-schema", "unknown")), ImmutableSet.of("specific-schema")); - assertEquals(accessControl.filterSchemas(BOB, "specific-catalog", ImmutableSet.of("specific-schema")), ImmutableSet.of("specific-schema")); - assertEquals(accessControl.filterSchemas(CHARLIE, "specific-catalog", ImmutableSet.of("specific-schema", "unknown")), ImmutableSet.of("specific-schema")); + assertThat(accessControl.filterSchemas(ADMIN, "specific-catalog", ImmutableSet.of("specific-schema", "unknown"))).isEqualTo(ImmutableSet.of("specific-schema", "unknown")); + assertThat(accessControl.filterSchemas(ALICE, "specific-catalog", ImmutableSet.of("specific-schema", "unknown"))).isEqualTo(ImmutableSet.of("specific-schema")); + assertThat(accessControl.filterSchemas(BOB, "specific-catalog", ImmutableSet.of("specific-schema"))).isEqualTo(ImmutableSet.of("specific-schema")); + assertThat(accessControl.filterSchemas(CHARLIE, "specific-catalog", ImmutableSet.of("specific-schema", "unknown"))).isEqualTo(ImmutableSet.of("specific-schema")); - assertEquals(accessControl.filterSchemas(ADMIN, "alice-catalog", ImmutableSet.of("alice-schema", "bob-schema", "unknown")), ImmutableSet.of("alice-schema", "bob-schema", "unknown")); - assertEquals(accessControl.filterSchemas(ALICE, "alice-catalog", ImmutableSet.of("alice-schema", "bob-schema", "unknown")), ImmutableSet.of("alice-schema")); - assertEquals(accessControl.filterSchemas(BOB, "alice-catalog", ImmutableSet.of("alice-schema", "bob-schema", "unknown")), ImmutableSet.of("bob-schema")); - assertEquals(accessControl.filterSchemas(CHARLIE, "alice-catalog", ImmutableSet.of("alice-schema", "bob-schema", "unknown")), ImmutableSet.of()); + assertThat(accessControl.filterSchemas(ADMIN, "alice-catalog", ImmutableSet.of("alice-schema", "bob-schema", "unknown"))).isEqualTo(ImmutableSet.of("alice-schema", "bob-schema", "unknown")); + assertThat(accessControl.filterSchemas(ALICE, "alice-catalog", ImmutableSet.of("alice-schema", "bob-schema", "unknown"))).isEqualTo(ImmutableSet.of("alice-schema")); + assertThat(accessControl.filterSchemas(BOB, "alice-catalog", ImmutableSet.of("alice-schema", "bob-schema", "unknown"))).isEqualTo(ImmutableSet.of("bob-schema")); + assertThat(accessControl.filterSchemas(CHARLIE, "alice-catalog", ImmutableSet.of("alice-schema", "bob-schema", "unknown"))).isEqualTo(ImmutableSet.of()); - assertEquals(accessControl.filterSchemas(ADMIN, "bob-catalog", ImmutableSet.of("bob-schema", "unknown")), ImmutableSet.of("bob-schema", "unknown")); - assertEquals(accessControl.filterSchemas(ALICE, "bob-catalog", ImmutableSet.of("bob-schema", "unknown")), ImmutableSet.of()); - assertEquals(accessControl.filterSchemas(BOB, "bob-catalog", ImmutableSet.of("bob-schema", "unknown")), ImmutableSet.of("bob-schema")); - assertEquals(accessControl.filterSchemas(CHARLIE, "bob-catalog", ImmutableSet.of("bob-schema", "unknown")), ImmutableSet.of()); + assertThat(accessControl.filterSchemas(ADMIN, "bob-catalog", ImmutableSet.of("bob-schema", "unknown"))).isEqualTo(ImmutableSet.of("bob-schema", "unknown")); + assertThat(accessControl.filterSchemas(ALICE, "bob-catalog", ImmutableSet.of("bob-schema", "unknown"))).isEqualTo(ImmutableSet.of()); + assertThat(accessControl.filterSchemas(BOB, "bob-catalog", ImmutableSet.of("bob-schema", "unknown"))).isEqualTo(ImmutableSet.of("bob-schema")); + assertThat(accessControl.filterSchemas(CHARLIE, "bob-catalog", ImmutableSet.of("bob-schema", "unknown"))).isEqualTo(ImmutableSet.of()); - assertEquals(accessControl.filterSchemas(ADMIN, "secret", ImmutableSet.of("unknown")), ImmutableSet.of("unknown")); - assertEquals(accessControl.filterSchemas(ALICE, "secret", ImmutableSet.of("unknown")), ImmutableSet.of()); - assertEquals(accessControl.filterSchemas(BOB, "secret", ImmutableSet.of("unknown")), ImmutableSet.of()); - assertEquals(accessControl.filterSchemas(CHARLIE, "secret", ImmutableSet.of("unknown")), ImmutableSet.of()); + assertThat(accessControl.filterSchemas(ADMIN, "secret", ImmutableSet.of("unknown"))).isEqualTo(ImmutableSet.of("unknown")); + assertThat(accessControl.filterSchemas(ALICE, "secret", ImmutableSet.of("unknown"))).isEqualTo(ImmutableSet.of()); + assertThat(accessControl.filterSchemas(BOB, "secret", ImmutableSet.of("unknown"))).isEqualTo(ImmutableSet.of()); + assertThat(accessControl.filterSchemas(CHARLIE, "secret", ImmutableSet.of("unknown"))).isEqualTo(ImmutableSet.of()); - assertEquals(accessControl.filterSchemas(ADMIN, "hidden", ImmutableSet.of("unknown")), ImmutableSet.of("unknown")); - assertEquals(accessControl.filterSchemas(ALICE, "hidden", ImmutableSet.of("unknown")), ImmutableSet.of()); - assertEquals(accessControl.filterSchemas(BOB, "hidden", ImmutableSet.of("unknown")), ImmutableSet.of()); - assertEquals(accessControl.filterSchemas(CHARLIE, "hidden", ImmutableSet.of("unknown")), ImmutableSet.of()); + assertThat(accessControl.filterSchemas(ADMIN, "hidden", ImmutableSet.of("unknown"))).isEqualTo(ImmutableSet.of("unknown")); + assertThat(accessControl.filterSchemas(ALICE, "hidden", ImmutableSet.of("unknown"))).isEqualTo(ImmutableSet.of()); + assertThat(accessControl.filterSchemas(BOB, "hidden", ImmutableSet.of("unknown"))).isEqualTo(ImmutableSet.of()); + assertThat(accessControl.filterSchemas(CHARLIE, "hidden", ImmutableSet.of("unknown"))).isEqualTo(ImmutableSet.of()); - assertEquals(accessControl.filterSchemas(ADMIN, "open-to-all", ImmutableSet.of("unknown")), ImmutableSet.of("unknown")); - assertEquals(accessControl.filterSchemas(ALICE, "open-to-all", ImmutableSet.of("unknown")), ImmutableSet.of()); - assertEquals(accessControl.filterSchemas(BOB, "open-to-all", ImmutableSet.of("unknown")), ImmutableSet.of()); - assertEquals(accessControl.filterSchemas(CHARLIE, "open-to-all", ImmutableSet.of("unknown")), ImmutableSet.of()); + assertThat(accessControl.filterSchemas(ADMIN, "open-to-all", ImmutableSet.of("unknown"))).isEqualTo(ImmutableSet.of("unknown")); + assertThat(accessControl.filterSchemas(ALICE, "open-to-all", ImmutableSet.of("unknown"))).isEqualTo(ImmutableSet.of()); + assertThat(accessControl.filterSchemas(BOB, "open-to-all", ImmutableSet.of("unknown"))).isEqualTo(ImmutableSet.of()); + assertThat(accessControl.filterSchemas(CHARLIE, "open-to-all", ImmutableSet.of("unknown"))).isEqualTo(ImmutableSet.of()); - assertEquals(accessControl.filterSchemas(ADMIN, "blocked-catalog", ImmutableSet.of("unknown")), ImmutableSet.of()); - assertEquals(accessControl.filterSchemas(ALICE, "blocked-catalog", ImmutableSet.of("unknown")), ImmutableSet.of()); - assertEquals(accessControl.filterSchemas(BOB, "blocked-catalog", ImmutableSet.of("unknown")), ImmutableSet.of()); - assertEquals(accessControl.filterSchemas(CHARLIE, "blocked-catalog", ImmutableSet.of("unknown")), ImmutableSet.of()); + assertThat(accessControl.filterSchemas(ADMIN, "blocked-catalog", ImmutableSet.of("unknown"))).isEqualTo(ImmutableSet.of()); + assertThat(accessControl.filterSchemas(ALICE, "blocked-catalog", ImmutableSet.of("unknown"))).isEqualTo(ImmutableSet.of()); + assertThat(accessControl.filterSchemas(BOB, "blocked-catalog", ImmutableSet.of("unknown"))).isEqualTo(ImmutableSet.of()); + assertThat(accessControl.filterSchemas(CHARLIE, "blocked-catalog", ImmutableSet.of("unknown"))).isEqualTo(ImmutableSet.of()); - assertEquals(accessControl.filterSchemas(ADMIN, "unknown", ImmutableSet.of("unknown")), ImmutableSet.of("unknown")); - assertEquals(accessControl.filterSchemas(ALICE, "unknown", ImmutableSet.of("unknown")), ImmutableSet.of()); - assertEquals(accessControl.filterSchemas(BOB, "unknown", ImmutableSet.of("unknown")), ImmutableSet.of()); - assertEquals(accessControl.filterSchemas(CHARLIE, "unknown", ImmutableSet.of("unknown")), ImmutableSet.of()); + assertThat(accessControl.filterSchemas(ADMIN, "unknown", ImmutableSet.of("unknown"))).isEqualTo(ImmutableSet.of("unknown")); + assertThat(accessControl.filterSchemas(ALICE, "unknown", ImmutableSet.of("unknown"))).isEqualTo(ImmutableSet.of()); + assertThat(accessControl.filterSchemas(BOB, "unknown", ImmutableSet.of("unknown"))).isEqualTo(ImmutableSet.of()); + assertThat(accessControl.filterSchemas(CHARLIE, "unknown", ImmutableSet.of("unknown"))).isEqualTo(ImmutableSet.of()); - assertEquals(accessControl.filterSchemas(ADMIN, "session-catalog", ImmutableSet.of("session-schema", "unknown")), ImmutableSet.of("session-schema", "unknown")); - assertEquals(accessControl.filterSchemas(ALICE, "session-catalog", ImmutableSet.of("session-schema", "unknown")), ImmutableSet.of()); - assertEquals(accessControl.filterSchemas(BOB, "session-catalog", ImmutableSet.of("session-schema", "unknown")), ImmutableSet.of()); - assertEquals(accessControl.filterSchemas(CHARLIE, "session-catalog", ImmutableSet.of("session-schema", "unknown")), ImmutableSet.of()); + assertThat(accessControl.filterSchemas(ADMIN, "session-catalog", ImmutableSet.of("session-schema", "unknown"))).isEqualTo(ImmutableSet.of("session-schema", "unknown")); + assertThat(accessControl.filterSchemas(ALICE, "session-catalog", ImmutableSet.of("session-schema", "unknown"))).isEqualTo(ImmutableSet.of()); + assertThat(accessControl.filterSchemas(BOB, "session-catalog", ImmutableSet.of("session-schema", "unknown"))).isEqualTo(ImmutableSet.of()); + assertThat(accessControl.filterSchemas(CHARLIE, "session-catalog", ImmutableSet.of("session-schema", "unknown"))).isEqualTo(ImmutableSet.of()); - assertEquals(accessControl.filterSchemas(ADMIN, "ptf-catalog", ImmutableSet.of("ptf_schema")), ImmutableSet.of("ptf_schema")); - assertEquals(accessControl.filterSchemas(ALICE, "ptf-catalog", ImmutableSet.of("ptf_schema")), ImmutableSet.of("ptf_schema")); - assertEquals(accessControl.filterSchemas(BOB, "ptf-catalog", ImmutableSet.of("ptf_schema")), ImmutableSet.of()); - assertEquals(accessControl.filterSchemas(CHARLIE, "ptf-catalog", ImmutableSet.of("ptf_schema")), ImmutableSet.of()); + assertThat(accessControl.filterSchemas(ADMIN, "ptf-catalog", ImmutableSet.of("ptf_schema"))).isEqualTo(ImmutableSet.of("ptf_schema")); + assertThat(accessControl.filterSchemas(ALICE, "ptf-catalog", ImmutableSet.of("ptf_schema"))).isEqualTo(ImmutableSet.of("ptf_schema")); + assertThat(accessControl.filterSchemas(BOB, "ptf-catalog", ImmutableSet.of("ptf_schema"))).isEqualTo(ImmutableSet.of()); + assertThat(accessControl.filterSchemas(CHARLIE, "ptf-catalog", ImmutableSet.of("ptf_schema"))).isEqualTo(ImmutableSet.of()); } @Test @@ -1339,13 +1324,11 @@ public void testGetColumnMask() { SystemAccessControl accessControl = newFileBasedSystemAccessControl("file-based-system-access-table.json"); - assertEquals( - accessControl.getColumnMask( - ALICE, - new CatalogSchemaTableName("some-catalog", "bobschema", "bobcolumns"), - "masked", - VARCHAR), - Optional.empty()); + assertThat(accessControl.getColumnMask( + ALICE, + new CatalogSchemaTableName("some-catalog", "bobschema", "bobcolumns"), + "masked", + VARCHAR)).isEqualTo(Optional.empty()); assertViewExpressionEquals( accessControl.getColumnMask( @@ -1378,12 +1361,10 @@ public void testGetRowFilter() { SystemAccessControl accessControl = newFileBasedSystemAccessControl("file-based-system-access-table.json"); - assertEquals( - accessControl.getRowFilters(ALICE, new CatalogSchemaTableName("some-catalog", "bobschema", "bobcolumns")), - ImmutableList.of()); + assertThat(accessControl.getRowFilters(ALICE, new CatalogSchemaTableName("some-catalog", "bobschema", "bobcolumns"))).isEqualTo(ImmutableList.of()); List rowFilters = accessControl.getRowFilters(CHARLIE, new CatalogSchemaTableName("some-catalog", "bobschema", "bobcolumns")); - assertEquals(rowFilters.size(), 1); + assertThat(rowFilters.size()).isEqualTo(1); assertViewExpressionEquals( rowFilters.get(0), ViewExpression.builder() @@ -1393,7 +1374,7 @@ public void testGetRowFilter() .build()); rowFilters = accessControl.getRowFilters(CHARLIE, new CatalogSchemaTableName("some-catalog", "bobschema", "bobcolumns_with_grant")); - assertEquals(rowFilters.size(), 1); + assertThat(rowFilters.size()).isEqualTo(1); assertViewExpressionEquals( rowFilters.get(0), ViewExpression.builder() @@ -1406,11 +1387,21 @@ public void testGetRowFilter() private static void assertViewExpressionEquals(ViewExpression actual, ViewExpression expected) { - assertEquals(actual.getSecurityIdentity(), expected.getSecurityIdentity(), "Identity"); - assertEquals(actual.getCatalog(), expected.getCatalog(), "Catalog"); - assertEquals(actual.getSchema(), expected.getSchema(), "Schema"); - assertEquals(actual.getExpression(), expected.getExpression(), "Expression"); - assertEquals(actual.getPath(), expected.getPath(), "Path"); + assertThat(actual.getSecurityIdentity()) + .describedAs("Identity") + .isEqualTo(expected.getSecurityIdentity()); + assertThat(actual.getCatalog()) + .describedAs("Catalog") + .isEqualTo(expected.getCatalog()); + assertThat(actual.getSchema()) + .describedAs("Schema") + .isEqualTo(expected.getSchema()); + assertThat(actual.getExpression()) + .describedAs("Expression") + .isEqualTo(expected.getExpression()); + assertThat(actual.getPath()) + .describedAs("Path") + .isEqualTo(expected.getPath()); } @Test @@ -1675,15 +1666,15 @@ public void testFunctionsFilter() .add(new SchemaFunctionName("bobschema", "any")) .add(new SchemaFunctionName("any", "any")) .build(); - assertEquals(accessControl.filterFunctions(ALICE, "any", functions), ImmutableSet.builder() + assertThat(accessControl.filterFunctions(ALICE, "any", functions)).isEqualTo(ImmutableSet.builder() .add(new SchemaFunctionName("aliceschema", "any")) .add(new SchemaFunctionName("aliceschema", "bobfunction")) .build()); - assertEquals(accessControl.filterFunctions(BOB, "any", functions), ImmutableSet.builder() + assertThat(accessControl.filterFunctions(BOB, "any", functions)).isEqualTo(ImmutableSet.builder() .add(new SchemaFunctionName("aliceschema", "bobfunction")) .add(new SchemaFunctionName("bobschema", "bob_any")) .build()); - assertEquals(accessControl.filterFunctions(ADMIN, "any", functions), ImmutableSet.builder() + assertThat(accessControl.filterFunctions(ADMIN, "any", functions)).isEqualTo(ImmutableSet.builder() .add(new SchemaFunctionName("secret", "any")) .add(new SchemaFunctionName("aliceschema", "any")) .add(new SchemaFunctionName("aliceschema", "bobfunction")) @@ -1703,8 +1694,8 @@ public void testFunctionsFilterNoAccess() .add(new SchemaFunctionName("secret", "any")) .add(new SchemaFunctionName("any", "any")) .build(); - assertEquals(accessControl.filterFunctions(ALICE, "any", functions), ImmutableSet.of()); - assertEquals(accessControl.filterFunctions(BOB, "any", functions), ImmutableSet.of()); + assertThat(accessControl.filterFunctions(ALICE, "any", functions)).isEqualTo(ImmutableSet.of()); + assertThat(accessControl.filterFunctions(BOB, "any", functions)).isEqualTo(ImmutableSet.of()); } @Test diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestFileBasedAccessControlConfig.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestFileBasedAccessControlConfig.java index 425f587c29e7..c9eadee791ab 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestFileBasedAccessControlConfig.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestFileBasedAccessControlConfig.java @@ -33,7 +33,7 @@ import static io.airlift.testing.ValidationAssertions.assertValidates; import static io.trino.plugin.base.security.FileBasedAccessControlConfig.SECURITY_CONFIG_FILE; import static io.trino.plugin.base.security.FileBasedAccessControlConfig.SECURITY_REFRESH_PERIOD; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestFileBasedAccessControlConfig { @@ -171,14 +171,14 @@ public void testConfigSource() { FileBasedAccessControlConfig fileBasedAccessControlConfig = new FileBasedAccessControlConfig() .setConfigFile("/etc/access-control"); - assertEquals(fileBasedAccessControlConfig.isHttp(), false); + assertThat(fileBasedAccessControlConfig.isHttp()).isEqualTo(false); fileBasedAccessControlConfig = new FileBasedAccessControlConfig() .setConfigFile("http://trino.example/config"); - assertEquals(fileBasedAccessControlConfig.isHttp(), true); + assertThat(fileBasedAccessControlConfig.isHttp()).isEqualTo(true); fileBasedAccessControlConfig = new FileBasedAccessControlConfig() .setConfigFile("https://trino.example/config"); - assertEquals(fileBasedAccessControlConfig.isHttp(), true); + assertThat(fileBasedAccessControlConfig.isHttp()).isEqualTo(true); } } diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/util/TestClosables.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/util/TestClosables.java index 2ba41e59907e..9331ae002a27 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/util/TestClosables.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/util/TestClosables.java @@ -20,7 +20,6 @@ import static io.trino.plugin.base.util.Closables.closeAllSuppress; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertTrue; public class TestClosables { @@ -30,7 +29,7 @@ public void testCloseAllSuppressNonThrowing() RuntimeException rootException = new RuntimeException("root"); TestClosable closable = new TestClosable(Optional.empty()); closeAllSuppress(rootException, closable); - assertTrue(closable.isClosed()); + assertThat(closable.isClosed()).isTrue(); assertThat(rootException.getSuppressed()).isEmpty(); } @@ -41,7 +40,7 @@ public void testCloseAllSuppressThrowingOther() RuntimeException closeException = new RuntimeException("close"); TestClosable closable = new TestClosable(Optional.of(closeException)); closeAllSuppress(rootException, closable); - assertTrue(closable.isClosed()); + assertThat(closable.isClosed()).isTrue(); assertThat(rootException.getSuppressed()).containsExactly(closeException); } @@ -51,7 +50,7 @@ public void testCloseAllSuppressThrowingRoot() RuntimeException rootException = new RuntimeException("root"); TestClosable closable = new TestClosable(Optional.of(rootException)); closeAllSuppress(rootException, closable); - assertTrue(closable.isClosed()); + assertThat(closable.isClosed()).isTrue(); assertThat(rootException.getSuppressed()).isEmpty(); } @@ -74,10 +73,10 @@ public void testCloseAllSuppressMultipleClosables() TestClosable closable3 = new TestClosable(Optional.empty()); // non throwing TestClosable closable4 = new TestClosable(Optional.of(rootException)); // throwing root closeAllSuppress(rootException, closable1, closable2, closable3, closable4, null); - assertTrue(closable1.isClosed()); - assertTrue(closable2.isClosed()); - assertTrue(closable3.isClosed()); - assertTrue(closable4.isClosed()); + assertThat(closable1.isClosed()).isTrue(); + assertThat(closable2.isClosed()).isTrue(); + assertThat(closable3.isClosed()).isTrue(); + assertThat(closable4.isClosed()).isTrue(); assertThat(rootException.getSuppressed()).containsExactly(closeException1, closeException2); } diff --git a/lib/trino-record-decoder/src/test/java/io/trino/decoder/avro/AvroDecoderTestUtil.java b/lib/trino-record-decoder/src/test/java/io/trino/decoder/avro/AvroDecoderTestUtil.java index 2ec07462a90d..20c17acefb21 100644 --- a/lib/trino-record-decoder/src/test/java/io/trino/decoder/avro/AvroDecoderTestUtil.java +++ b/lib/trino-record-decoder/src/test/java/io/trino/decoder/avro/AvroDecoderTestUtil.java @@ -33,12 +33,8 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.testing.TestingConnectorSession.SESSION; import static java.lang.String.format; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Fail.fail; public final class AvroDecoderTestUtil { @@ -47,32 +43,32 @@ private AvroDecoderTestUtil() {} private static void checkPrimitiveValue(Object actual, Object expected) { if (actual == null || expected == null) { - assertNull(expected); - assertNull(actual); + assertThat(expected).isNull(); + assertThat(actual).isNull(); } else if (actual instanceof CharSequence) { - assertTrue(expected instanceof CharSequence || expected instanceof GenericEnumSymbol); - assertEquals(actual.toString(), expected.toString()); + assertThat(expected instanceof CharSequence || expected instanceof GenericEnumSymbol).isTrue(); + assertThat(actual.toString()).isEqualTo(expected.toString()); } else if (actual instanceof SqlVarbinary) { if (expected instanceof GenericFixed) { - assertEquals(((SqlVarbinary) actual).getBytes(), ((GenericFixed) expected).bytes()); + assertThat(((SqlVarbinary) actual).getBytes()).isEqualTo(((GenericFixed) expected).bytes()); } else if (expected instanceof ByteBuffer) { - assertEquals(((SqlVarbinary) actual).getBytes(), ((ByteBuffer) expected).array()); + assertThat(((SqlVarbinary) actual).getBytes()).isEqualTo(((ByteBuffer) expected).array()); } else { fail(format("Unexpected value type %s", actual.getClass())); } } else if (isIntegralType(actual) && isIntegralType(expected)) { - assertEquals(((Number) actual).longValue(), ((Number) expected).longValue()); + assertThat(((Number) actual).longValue()).isEqualTo(((Number) expected).longValue()); } else if (isRealType(actual) && isRealType(expected)) { - assertEquals(((Number) actual).doubleValue(), ((Number) expected).doubleValue()); + assertThat(((Number) actual).doubleValue()).isEqualTo(((Number) expected).doubleValue()); } else { - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } } @@ -91,18 +87,26 @@ private static boolean isRealType(Object value) public static void checkArrayValues(Block block, Type type, Object value) { - assertNotNull(type, "Type is null"); - assertTrue(type instanceof ArrayType, "Unexpected type"); - assertNotNull(block, "Block is null"); - assertNotNull(value, "Value is null"); + assertThat(type) + .describedAs("Type is null") + .isNotNull(); + assertThat(type instanceof ArrayType) + .describedAs("Unexpected type") + .isTrue(); + assertThat(block) + .describedAs("Block is null") + .isNotNull(); + assertThat(value) + .describedAs("Value is null") + .isNotNull(); List list = (List) value; - assertEquals(block.getPositionCount(), list.size()); + assertThat(block.getPositionCount()).isEqualTo(list.size()); Type elementType = ((ArrayType) type).getElementType(); for (int index = 0; index < block.getPositionCount(); index++) { if (block.isNull(index)) { - assertNull(list.get(index)); + assertThat(list.get(index)).isNull(); continue; } if (elementType instanceof ArrayType arrayType) { @@ -122,11 +126,21 @@ else if (elementType instanceof RowType rowType) { public static void checkMapValues(SqlMap sqlMap, Type type, Object value) { - assertNotNull(type, "Type is null"); - assertTrue(type instanceof MapType, "Unexpected type"); - assertTrue(((MapType) type).getKeyType() instanceof VarcharType, "Unexpected key type"); - assertNotNull(sqlMap, "sqlMap is null"); - assertNotNull(value, "Value is null"); + assertThat(type) + .describedAs("Type is null") + .isNotNull(); + assertThat(type instanceof MapType) + .describedAs("Unexpected type") + .isTrue(); + assertThat(((MapType) type).getKeyType() instanceof VarcharType) + .describedAs("Unexpected key type") + .isTrue(); + assertThat(sqlMap) + .describedAs("sqlMap is null") + .isNotNull(); + assertThat(value) + .describedAs("Value is null") + .isNotNull(); Map expected = (Map) value; @@ -134,13 +148,15 @@ public static void checkMapValues(SqlMap sqlMap, Type type, Object value) Block rawKeyBlock = sqlMap.getRawKeyBlock(); Block rawValueBlock = sqlMap.getRawValueBlock(); - assertEquals(sqlMap.getSize(), expected.size()); + assertThat(sqlMap.getSize()).isEqualTo(expected.size()); Type valueType = ((MapType) type).getValueType(); for (int index = 0; index < sqlMap.getSize(); index++) { String actualKey = VARCHAR.getSlice(rawKeyBlock, rawOffset + index).toStringUtf8(); - assertTrue(expected.containsKey(actualKey), "Key not found: %s".formatted(actualKey)); + assertThat(expected.containsKey(actualKey)) + .describedAs("Key not found: %s".formatted(actualKey)) + .isTrue(); if (rawValueBlock.isNull(rawOffset + index)) { - assertNull(expected.get(actualKey)); + assertThat(expected.get(actualKey)).isNull(); continue; } if (valueType instanceof ArrayType arrayType) { @@ -160,22 +176,34 @@ else if (valueType instanceof RowType rowType) { public static void checkRowValues(SqlRow sqlRow, Type type, Object value) { - assertNotNull(type, "Type is null"); - assertTrue(type instanceof RowType, "Unexpected type"); - assertNotNull(sqlRow, "sqlRow is null"); - assertNotNull(value, "Value is null"); + assertThat(type) + .describedAs("Type is null") + .isNotNull(); + assertThat(type instanceof RowType) + .describedAs("Unexpected type") + .isTrue(); + assertThat(sqlRow) + .describedAs("sqlRow is null") + .isNotNull(); + assertThat(value) + .describedAs("Value is null") + .isNotNull(); GenericRecord record = (GenericRecord) value; RowType rowType = (RowType) type; - assertEquals(record.getSchema().getFields().size(), rowType.getFields().size(), "Avro field size mismatch"); - assertEquals(sqlRow.getFieldCount(), rowType.getFields().size(), "Trino type field size mismatch"); + assertThat(record.getSchema().getFields().size()) + .describedAs("Avro field size mismatch") + .isEqualTo(rowType.getFields().size()); + assertThat(sqlRow.getFieldCount()) + .describedAs("Trino type field size mismatch") + .isEqualTo(rowType.getFields().size()); int rawIndex = sqlRow.getRawIndex(); for (int fieldIndex = 0; fieldIndex < rowType.getFields().size(); fieldIndex++) { RowType.Field rowField = rowType.getFields().get(fieldIndex); Object expectedValue = record.get(rowField.getName().orElseThrow()); Block fieldBlock = sqlRow.getRawFieldBlock(fieldIndex); if (fieldBlock.isNull(rawIndex)) { - assertNull(expectedValue); + assertThat(expectedValue).isNull(); continue; } checkField(fieldBlock, rowField.getType(), rawIndex, expectedValue); @@ -184,10 +212,16 @@ public static void checkRowValues(SqlRow sqlRow, Type type, Object value) private static void checkField(Block actualBlock, Type type, int position, Object expectedValue) { - assertNotNull(type, "Type is null"); - assertNotNull(actualBlock, "actualBlock is null"); - assertFalse(actualBlock.isNull(position)); - assertNotNull(expectedValue, "expectedValue is null"); + assertThat(type) + .describedAs("Type is null") + .isNotNull(); + assertThat(actualBlock) + .describedAs("actualBlock is null") + .isNotNull(); + assertThat(actualBlock.isNull(position)).isFalse(); + assertThat(expectedValue) + .describedAs("expectedValue is null") + .isNotNull(); if (type instanceof ArrayType arrayType) { checkArrayValues(arrayType.getObject(actualBlock, position), type, expectedValue); diff --git a/lib/trino-record-decoder/src/test/java/io/trino/decoder/avro/TestAvroDecoder.java b/lib/trino-record-decoder/src/test/java/io/trino/decoder/avro/TestAvroDecoder.java index 1593330c4245..0ad33ba2cec0 100644 --- a/lib/trino-record-decoder/src/test/java/io/trino/decoder/avro/TestAvroDecoder.java +++ b/lib/trino-record-decoder/src/test/java/io/trino/decoder/avro/TestAvroDecoder.java @@ -81,10 +81,8 @@ import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.lang.Float.floatToIntBits; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; public class TestAvroDecoder { @@ -161,7 +159,7 @@ private Map buildAndDecodeColumn(Decode ImmutableMap.of(columnName, columnType), ImmutableMap.of(columnName, actualValue)); - assertEquals(decodedRow.size(), 1); + assertThat(decodedRow.size()).isEqualTo(1); return decodedRow; } @@ -186,7 +184,7 @@ private static byte[] buildAvroData(Schema schema, Map values) private static Map buildMapFromKeysAndValues(List keys, List values) { - assertEquals(keys.size(), values.size()); + assertThat(keys.size()).isEqualTo(values.size()); Map map = new HashMap<>(); for (int i = 0; i < keys.size(); i++) { map.put(keys.get(i), values.get(i)); @@ -256,7 +254,7 @@ public void testSchemaEvolutionAddingColumn() ImmutableSet.of(originalColumn, newlyAddedColumn), ImmutableMap.of(DATA_SCHEMA, addedColumnSchema)); - assertEquals(decodedRow.size(), 2); + assertThat(decodedRow.size()).isEqualTo(2); checkValue(decodedRow, originalColumn, "string_field_value"); checkIsNull(decodedRow, newlyAddedColumn); } @@ -279,7 +277,7 @@ public void testSchemaEvolutionRenamingColumn() ImmutableSet.of(renamedColumn), ImmutableMap.of(DATA_SCHEMA, renamedColumnSchema)); - assertEquals(decodedEvolvedRow.size(), 1); + assertThat(decodedEvolvedRow.size()).isEqualTo(1); checkIsNull(decodedEvolvedRow, renamedColumn); } @@ -304,7 +302,7 @@ public void testSchemaEvolutionRemovingColumn() ImmutableSet.of(evolvedColumn), ImmutableMap.of(DATA_SCHEMA, removedColumnSchema)); - assertEquals(decodedEvolvedRow.size(), 1); + assertThat(decodedEvolvedRow.size()).isEqualTo(1); checkValue(decodedEvolvedRow, evolvedColumn, "string_field_value"); } @@ -326,7 +324,7 @@ public void testSchemaEvolutionIntToLong() ImmutableSet.of(longColumnReadingIntData), ImmutableMap.of(DATA_SCHEMA, changedTypeSchema)); - assertEquals(decodedEvolvedRow.size(), 1); + assertThat(decodedEvolvedRow.size()).isEqualTo(1); checkValue(decodedEvolvedRow, longColumnReadingIntData, 100); } @@ -348,7 +346,7 @@ public void testSchemaEvolutionIntToDouble() ImmutableSet.of(doubleColumnReadingIntData), ImmutableMap.of(DATA_SCHEMA, changedTypeSchema)); - assertEquals(decodedEvolvedRow.size(), 1); + assertThat(decodedEvolvedRow.size()).isEqualTo(1); checkValue(decodedEvolvedRow, doubleColumnReadingIntData, 100.0); } @@ -505,7 +503,7 @@ public void testNestedRecord() ImmutableSet.of(row), ImmutableMap.of(DATA_SCHEMA, schema)); - assertEquals(decodedRow.size(), 1); + assertThat(decodedRow.size()).isEqualTo(1); checkValue(decodedRow, row, 98247748); } @@ -851,7 +849,7 @@ public void testMapOfArrayOfMapsWithDifferentKeys() Map decodedRow = buildAndDecodeColumn(row, "map_field", schema.toString(), data); assertThatThrownBy(() -> checkMapValue(decodedRow, row, mismatchedData)) .isInstanceOf(AssertionError.class) - .hasMessageStartingWith("Key not found: sk3"); + .hasMessageContaining("Key not found: sk3"); } @Test @@ -875,8 +873,7 @@ public void testMapOfArrayOfMapsWithDifferentValues() DecoderTestColumnHandle row = new DecoderTestColumnHandle(0, "row", MAP_OF_ARRAY_OF_MAP_TYPE, "map_field", null, null, false, false, false); Map decodedRow = buildAndDecodeColumn(row, "map_field", schema.toString(), data); assertThatThrownBy(() -> checkMapValue(decodedRow, row, mismatchedData)) - .isInstanceOf(AssertionError.class) - .hasMessageMatching("expected \\[-2\\..*] but found \\[2\\..*]"); + .isInstanceOf(AssertionError.class); } @Test @@ -931,7 +928,7 @@ public void testMapWithDifferentKeys() "key4", "def", "key3", "zyx"))) .isInstanceOf(AssertionError.class) - .hasMessageStartingWith("Key not found: key2"); + .hasMessageContaining("Key not found: key2"); } @Test @@ -947,8 +944,7 @@ public void testMapWithDifferentValues() "key1", "abc", "key2", "fed", "key3", "zyx"))) - .isInstanceOf(AssertionError.class) - .hasMessage("expected [fed] but found [def]"); + .isInstanceOf(AssertionError.class); } @Test @@ -1151,11 +1147,11 @@ private static void checkMapValue(Map d private static void checkArrayItemIsNull(Map decodedRow, DecoderColumnHandle handle, long[] expected) { Block actualBlock = (Block) getObject(decodedRow, handle); - assertEquals(actualBlock.getPositionCount(), expected.length); + assertThat(actualBlock.getPositionCount()).isEqualTo(expected.length); for (int i = 0; i < actualBlock.getPositionCount(); i++) { - assertTrue(actualBlock.isNull(i)); - assertEquals(BIGINT.getLong(actualBlock, i), expected[i]); + assertThat(actualBlock.isNull(i)).isTrue(); + assertThat(BIGINT.getLong(actualBlock, i)).isEqualTo(expected[i]); } } @@ -1167,7 +1163,7 @@ private static void checkMapValue(Map d private static Object getObject(Map decodedRow, DecoderColumnHandle handle) { FieldValueProvider provider = decodedRow.get(handle); - assertNotNull(provider); + assertThat(provider).isNotNull(); return provider.getObject(); } diff --git a/lib/trino-record-decoder/src/test/java/io/trino/decoder/csv/TestCsvDecoder.java b/lib/trino-record-decoder/src/test/java/io/trino/decoder/csv/TestCsvDecoder.java index 0d01d5638d8b..951442c1946c 100644 --- a/lib/trino-record-decoder/src/test/java/io/trino/decoder/csv/TestCsvDecoder.java +++ b/lib/trino-record-decoder/src/test/java/io/trino/decoder/csv/TestCsvDecoder.java @@ -43,8 +43,8 @@ import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; import static io.trino.spi.type.VarcharType.createVarcharType; import static java.util.Collections.emptyMap; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestCsvDecoder { @@ -69,7 +69,7 @@ public void testSimple() Map decodedRow = rowDecoder.decodeRow(csv.getBytes(StandardCharsets.UTF_8)) .orElseThrow(AssertionError::new); - assertEquals(decodedRow.size(), columns.size()); + assertThat(decodedRow.size()).isEqualTo(columns.size()); checkValue(decodedRow, row1, "ro"); checkValue(decodedRow, row2, "row2"); @@ -100,7 +100,7 @@ public void testBoolean() Map decodedRow = rowDecoder.decodeRow(csv.getBytes(StandardCharsets.UTF_8)) .orElseThrow(AssertionError::new); - assertEquals(decodedRow.size(), columns.size()); + assertThat(decodedRow.size()).isEqualTo(columns.size()); checkValue(decodedRow, row1, true); checkValue(decodedRow, row2, false); @@ -128,7 +128,7 @@ public void testNulls() Map decodedRow = rowDecoder.decodeRow(csv.getBytes(StandardCharsets.UTF_8)) .orElseThrow(AssertionError::new); - assertEquals(decodedRow.size(), columns.size()); + assertThat(decodedRow.size()).isEqualTo(columns.size()); checkIsNull(decodedRow, row1); checkIsNull(decodedRow, row2); @@ -154,7 +154,7 @@ public void testLessTokensThanColumns() Map decodedRow = rowDecoder.decodeRow(csv.getBytes(StandardCharsets.UTF_8)) .orElseThrow(AssertionError::new); - assertEquals(decodedRow.size(), columns.size()); + assertThat(decodedRow.size()).isEqualTo(columns.size()); checkValue(decodedRow, column1, "ala"); checkValue(decodedRow, column2, 10); diff --git a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/JsonFieldDecoderTester.java b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/JsonFieldDecoderTester.java index 738ef4a12a80..72e2a3e52d43 100644 --- a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/JsonFieldDecoderTester.java +++ b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/JsonFieldDecoderTester.java @@ -32,11 +32,9 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Collections.emptyMap; import static java.util.Objects.requireNonNull; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.util.Preconditions.checkArgument; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class JsonFieldDecoderTester { @@ -70,44 +68,56 @@ public void assertDecodedAs(String jsonValue, Type type, long expectedValue) { checkArgument(type.getJavaType() == long.class, "Wrong (not long based) Trino type '%s'", type); FieldValueProvider decodedValue = decode(Optional.of(jsonValue), type); - assertFalse(decodedValue.isNull(), format("expected non null when decoding %s as %s", jsonValue, type)); - assertEquals(decodedValue.getLong(), expectedValue); + assertThat(decodedValue.isNull()) + .describedAs(format("expected non null when decoding %s as %s", jsonValue, type)) + .isFalse(); + assertThat(decodedValue.getLong()).isEqualTo(expectedValue); } public void assertDecodedAs(String jsonValue, Type type, double expectedValue) { checkArgument(type.getJavaType() == double.class, "Wrong (not double based) Trino type '%s'", type); FieldValueProvider decodedValue = decode(Optional.of(jsonValue), type); - assertFalse(decodedValue.isNull(), format("expected non null when decoding %s as %s", jsonValue, type)); - assertEquals(decodedValue.getDouble(), expectedValue); + assertThat(decodedValue.isNull()) + .describedAs(format("expected non null when decoding %s as %s", jsonValue, type)) + .isFalse(); + assertThat(decodedValue.getDouble()).isEqualTo(expectedValue); } public void assertDecodedAs(String jsonValue, Type type, Slice expectedValue) { checkArgument(type.getJavaType() == Slice.class, "Wrong (not Slice based) Trino type '%s'", type); FieldValueProvider decodedValue = decode(Optional.of(jsonValue), type); - assertFalse(decodedValue.isNull(), format("expected non null when decoding %s as %s", jsonValue, type)); - assertEquals(decodedValue.getSlice(), expectedValue); + assertThat(decodedValue.isNull()) + .describedAs(format("expected non null when decoding %s as %s", jsonValue, type)) + .isFalse(); + assertThat(decodedValue.getSlice()).isEqualTo(expectedValue); } public void assertDecodedAs(String jsonValue, Type type, boolean expectedValue) { checkArgument(type.getJavaType() == boolean.class, "Wrong (not boolean based) Trino type '%s'", type); FieldValueProvider decodedValue = decode(Optional.of(jsonValue), type); - assertFalse(decodedValue.isNull(), format("expected non null when decoding %s as %s", jsonValue, type)); - assertEquals(decodedValue.getBoolean(), expectedValue); + assertThat(decodedValue.isNull()) + .describedAs(format("expected non null when decoding %s as %s", jsonValue, type)) + .isFalse(); + assertThat(decodedValue.getBoolean()).isEqualTo(expectedValue); } public void assertDecodedAsNull(String jsonValue, Type type) { FieldValueProvider decodedValue = decode(Optional.of(jsonValue), type); - assertTrue(decodedValue.isNull(), format("expected null when decoding %s as %s", jsonValue, type)); + assertThat(decodedValue.isNull()) + .describedAs(format("expected null when decoding %s as %s", jsonValue, type)) + .isTrue(); } public void assertMissingDecodedAsNull(Type type) { FieldValueProvider decodedValue = decode(Optional.empty(), type); - assertTrue(decodedValue.isNull(), format("expected null when decoding missing field as %s", type)); + assertThat(decodedValue.isNull()) + .describedAs(format("expected null when decoding missing field as %s", type)) + .isTrue(); } public void assertInvalidInput(String jsonValue, Type type, String exceptionRegex) @@ -135,7 +145,9 @@ private FieldValueProvider decode(Optional jsonValue, Type type) RowDecoder rowDecoder = DECODER_FACTORY.create(TESTING_SESSION, new RowDecoderSpec(JsonRowDecoder.NAME, emptyMap(), ImmutableSet.of(columnHandle))); Map decodedRow = rowDecoder.decodeRow(json.getBytes(UTF_8)) .orElseThrow(AssertionError::new); - assertTrue(decodedRow.containsKey(columnHandle), format("column '%s' not found in decoded row", columnHandle.getName())); + assertThat(decodedRow.containsKey(columnHandle)) + .describedAs(format("column '%s' not found in decoded row", columnHandle.getName())) + .isTrue(); return decodedRow.get(columnHandle); } } diff --git a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestJsonDecoder.java b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestJsonDecoder.java index f1466a43229b..77be5b03b134 100644 --- a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestJsonDecoder.java +++ b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestJsonDecoder.java @@ -52,9 +52,8 @@ import static io.trino.spi.type.VarcharType.createVarcharType; import static java.util.Arrays.asList; import static java.util.Collections.emptyMap; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; public class TestJsonDecoder { @@ -78,7 +77,7 @@ public void testSimple() Map decodedRow = rowDecoder.decodeRow(json) .orElseThrow(AssertionError::new); - assertEquals(decodedRow.size(), columns.size()); + assertThat(decodedRow.size()).isEqualTo(columns.size()); checkValue(decodedRow, column1, "twitterfeed"); checkValue(decodedRow, column2, "EKentuckyN"); @@ -103,7 +102,7 @@ public void testNonExistent() Map decodedRow = rowDecoder.decodeRow(json) .orElseThrow(AssertionError::new); - assertEquals(decodedRow.size(), columns.size()); + assertThat(decodedRow.size()).isEqualTo(columns.size()); checkIsNull(decodedRow, column1); checkIsNull(decodedRow, column2); @@ -125,9 +124,9 @@ public void testStringNumber() RowDecoder rowDecoder = DECODER_FACTORY.create(TESTING_SESSION, new RowDecoderSpec(JsonRowDecoder.NAME, emptyMap(), columns)); Optional> decodedRow = rowDecoder.decodeRow(json); - assertTrue(decodedRow.isPresent()); + assertThat(decodedRow.isPresent()).isTrue(); - assertEquals(decodedRow.get().size(), columns.size()); + assertThat(decodedRow.get().size()).isEqualTo(columns.size()); checkValue(decodedRow.get(), column1, "481516"); checkValue(decodedRow.get(), column2, 481516); diff --git a/lib/trino-record-decoder/src/test/java/io/trino/decoder/protobuf/TestProtobufDecoder.java b/lib/trino-record-decoder/src/test/java/io/trino/decoder/protobuf/TestProtobufDecoder.java index 3f3936117c72..ae5374bcdbde 100644 --- a/lib/trino-record-decoder/src/test/java/io/trino/decoder/protobuf/TestProtobufDecoder.java +++ b/lib/trino-record-decoder/src/test/java/io/trino/decoder/protobuf/TestProtobufDecoder.java @@ -76,10 +76,8 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.stream.Collectors.joining; import static java.util.stream.IntStream.range; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; public class TestProtobufDecoder { @@ -156,7 +154,7 @@ private void testAllDataTypes(String stringData, Integer integerData, Long longD .decodeRow(messageBuilder.build().toByteArray()) .orElseThrow(AssertionError::new); - assertEquals(decodedRow.size(), 9); + assertThat(decodedRow.size()).isEqualTo(9); checkValue(decodedRow, stringColumn, stringData); checkValue(decodedRow, integerColumn, integerData); @@ -189,7 +187,7 @@ public void testOneofFixedSchemaProvider() // Uses the file-based schema parser which generates a Descriptor that does not have any oneof fields -- all are null Descriptor descriptor = getDescriptor("test_oneof.proto"); for (String oneofColumnName : oneofColumnNames) { - assertNull(descriptor.findFieldByName(oneofColumnName)); + assertThat(descriptor.findFieldByName(oneofColumnName)).isNull(); } } @@ -325,13 +323,13 @@ private void assertOneof(DynamicMessage.Builder messageBuilder, .decodeRow(message.toByteArray()) .orElseThrow(AssertionError::new); - assertEquals(decodedRow.size(), 2); + assertThat(decodedRow.size()).isEqualTo(2); final var obj = new ObjectMapper(); final var expected = obj.writeValueAsString(setValue); - assertEquals(decodedRow.get(testColumnHandle).getSlice().toStringUtf8(), "value"); - assertEquals(decodedRow.get(testOneofColumn).getSlice().toStringUtf8(), expected); + assertThat(decodedRow.get(testColumnHandle).getSlice().toStringUtf8()).isEqualTo("value"); + assertThat(decodedRow.get(testOneofColumn).getSlice().toStringUtf8()).isEqualTo(expected); } @Test @@ -357,7 +355,7 @@ public void testAnyTypeWithDummyDescriptor() .decodeRow(testAny.toByteArray()) .orElseThrow(AssertionError::new); - assertTrue(decodedRow.get(testAnyColumn).isNull()); + assertThat(decodedRow.get(testAnyColumn).isNull()).isTrue(); } @Test @@ -403,16 +401,16 @@ public void testAnyTypeWithFileDescriptor() .orElseThrow(AssertionError::new); JsonNode actual = new ObjectMapper().readTree(decodedRow.get(testOneOfColumn).getSlice().toStringUtf8()); - assertTrue(actual.get("@type").textValue().contains("schema")); - assertEquals(actual.get("stringColumn").textValue(), stringData); - assertEquals(actual.get("integerColumn").intValue(), integerData); - assertEquals(actual.get("longColumn").textValue(), Long.toString(longData)); - assertEquals(actual.get("doubleColumn").doubleValue(), doubleData); - assertEquals(actual.get("floatColumn").floatValue(), floatData); - assertEquals(actual.get("booleanColumn").booleanValue(), booleanData); - assertEquals(actual.get("numberColumn").textValue(), enumData); - assertEquals(actual.get("timestampColumn").textValue(), "2020-12-12T15:35:45.923Z"); - assertEquals(actual.get("bytesColumn").binaryValue(), bytesData); + assertThat(actual.get("@type").textValue().contains("schema")).isTrue(); + assertThat(actual.get("stringColumn").textValue()).isEqualTo(stringData); + assertThat(actual.get("integerColumn").intValue()).isEqualTo(integerData); + assertThat(actual.get("longColumn").textValue()).isEqualTo(Long.toString(longData)); + assertThat(actual.get("doubleColumn").doubleValue()).isEqualTo(doubleData); + assertThat(actual.get("floatColumn").floatValue()).isEqualTo(floatData); + assertThat(actual.get("booleanColumn").booleanValue()).isEqualTo(booleanData); + assertThat(actual.get("numberColumn").textValue()).isEqualTo(enumData); + assertThat(actual.get("timestampColumn").textValue()).isEqualTo("2020-12-12T15:35:45.923Z"); + assertThat(actual.get("bytesColumn").binaryValue()).isEqualTo(bytesData); } @Test @@ -510,27 +508,27 @@ private void testStructuralDataTypes(String stringData, Integer integerData, Lon .decodeRow(messageBuilder.build().toByteArray()) .orElseThrow(AssertionError::new); - assertEquals(decodedRow.size(), 3); + assertThat(decodedRow.size()).isEqualTo(3); Block listBlock = (Block) decodedRow.get(listColumn).getObject(); - assertEquals(VARCHAR.getSlice(listBlock, 0).toStringUtf8(), "Presto"); + assertThat(VARCHAR.getSlice(listBlock, 0).toStringUtf8()).isEqualTo("Presto"); SqlMap sqlMap = (SqlMap) decodedRow.get(mapColumn).getObject(); - assertEquals(VARCHAR.getSlice(sqlMap.getRawKeyBlock(), sqlMap.getRawOffset()).toStringUtf8(), "Key"); - assertEquals(VARCHAR.getSlice(sqlMap.getRawValueBlock(), sqlMap.getRawOffset()).toStringUtf8(), "Value"); + assertThat(VARCHAR.getSlice(sqlMap.getRawKeyBlock(), sqlMap.getRawOffset()).toStringUtf8()).isEqualTo("Key"); + assertThat(VARCHAR.getSlice(sqlMap.getRawValueBlock(), sqlMap.getRawOffset()).toStringUtf8()).isEqualTo("Value"); SqlRow sqlRow = (SqlRow) decodedRow.get(rowColumn).getObject(); int rawIndex = sqlRow.getRawIndex(); ConnectorSession session = TestingSession.testSessionBuilder().build().toConnectorSession(); - assertEquals(VARCHAR.getObjectValue(session, sqlRow.getRawFieldBlock(0), rawIndex), stringData); - assertEquals(INTEGER.getObjectValue(session, sqlRow.getRawFieldBlock(1), rawIndex), integerData); - assertEquals(BIGINT.getObjectValue(session, sqlRow.getRawFieldBlock(2), rawIndex), longData); - assertEquals(DOUBLE.getObjectValue(session, sqlRow.getRawFieldBlock(3), rawIndex), doubleData); - assertEquals(REAL.getObjectValue(session, sqlRow.getRawFieldBlock(4), rawIndex), floatData); - assertEquals(BOOLEAN.getObjectValue(session, sqlRow.getRawFieldBlock(5), rawIndex), booleanData); - assertEquals(VARCHAR.getObjectValue(session, sqlRow.getRawFieldBlock(6), rawIndex), enumData); - assertEquals(TIMESTAMP_MICROS.getObjectValue(session, sqlRow.getRawFieldBlock(7), rawIndex), sqlTimestamp.roundTo(6)); - assertEquals(VARBINARY.getObjectValue(session, sqlRow.getRawFieldBlock(8), rawIndex), new SqlVarbinary(bytesData)); + assertThat(VARCHAR.getObjectValue(session, sqlRow.getRawFieldBlock(0), rawIndex)).isEqualTo(stringData); + assertThat(INTEGER.getObjectValue(session, sqlRow.getRawFieldBlock(1), rawIndex)).isEqualTo(integerData); + assertThat(BIGINT.getObjectValue(session, sqlRow.getRawFieldBlock(2), rawIndex)).isEqualTo(longData); + assertThat(DOUBLE.getObjectValue(session, sqlRow.getRawFieldBlock(3), rawIndex)).isEqualTo(doubleData); + assertThat(REAL.getObjectValue(session, sqlRow.getRawFieldBlock(4), rawIndex)).isEqualTo(floatData); + assertThat(BOOLEAN.getObjectValue(session, sqlRow.getRawFieldBlock(5), rawIndex)).isEqualTo(booleanData); + assertThat(VARCHAR.getObjectValue(session, sqlRow.getRawFieldBlock(6), rawIndex)).isEqualTo(enumData); + assertThat(TIMESTAMP_MICROS.getObjectValue(session, sqlRow.getRawFieldBlock(7), rawIndex)).isEqualTo(sqlTimestamp.roundTo(6)); + assertThat(VARBINARY.getObjectValue(session, sqlRow.getRawFieldBlock(8), rawIndex)).isEqualTo(new SqlVarbinary(bytesData)); } @Test @@ -639,7 +637,7 @@ private void testRowFlattening(String stringData, Integer integerData, Long long .decodeRow(messageBuilder.build().toByteArray()) .orElseThrow(AssertionError::new); - assertEquals(decodedRow.size(), 9); + assertThat(decodedRow.size()).isEqualTo(9); checkValue(decodedRow, stringColumn, stringData); checkValue(decodedRow, integerColumn, integerData); diff --git a/lib/trino-record-decoder/src/test/java/io/trino/decoder/raw/TestRawDecoder.java b/lib/trino-record-decoder/src/test/java/io/trino/decoder/raw/TestRawDecoder.java index 93378de5edf3..67b3e4f814ae 100644 --- a/lib/trino-record-decoder/src/test/java/io/trino/decoder/raw/TestRawDecoder.java +++ b/lib/trino-record-decoder/src/test/java/io/trino/decoder/raw/TestRawDecoder.java @@ -46,9 +46,8 @@ import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; import static io.trino.spi.type.VarcharType.createVarcharType; import static java.util.Collections.emptyMap; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; public class TestRawDecoder { @@ -93,7 +92,7 @@ public void testSimple() Map decodedRow = rowDecoder.decodeRow(row) .orElseThrow(AssertionError::new); - assertEquals(decodedRow.size(), columns.size()); + assertThat(decodedRow.size()).isEqualTo(columns.size()); checkValue(decodedRow, row1, 4815162342L); checkValue(decodedRow, row2, 12345678); @@ -119,7 +118,7 @@ public void testFixedWithString() Map decodedRow = rowDecoder.decodeRow(row) .orElseThrow(AssertionError::new); - assertEquals(decodedRow.size(), columns.size()); + assertThat(decodedRow.size()).isEqualTo(columns.size()); checkValue(decodedRow, row1, str); checkValue(decodedRow, row2, str); @@ -149,7 +148,7 @@ public void testFloatStuff() Map decodedRow = rowDecoder.decodeRow(row) .orElseThrow(AssertionError::new); - assertEquals(decodedRow.size(), columns.size()); + assertThat(decodedRow.size()).isEqualTo(columns.size()); checkValue(decodedRow, row1, Math.PI); checkValue(decodedRow, row2, Math.E); @@ -223,7 +222,7 @@ public void testBooleanStuff() Map decodedRow = rowDecoder.decodeRow(row) .orElseThrow(AssertionError::new); - assertEquals(decodedRow.size(), columns.size()); + assertThat(decodedRow.size()).isEqualTo(columns.size()); checkValue(decodedRow, row01, 127); checkValue(decodedRow, row02, false); @@ -432,7 +431,7 @@ public void testGetValueTwice() Map decodedRow = rowDecoder.decodeRow(row) .orElseThrow(AssertionError::new); - assertEquals(decodedRow.size(), columns.size()); + assertThat(decodedRow.size()).isEqualTo(columns.size()); for (DecoderColumnHandle handle : columns) { checkTwice(decodedRow, handle); @@ -442,19 +441,19 @@ public void testGetValueTwice() private void checkTwice(Map decodedRow, DecoderColumnHandle handle) { FieldValueProvider provider = decodedRow.get(handle); - assertNotNull(provider); + assertThat(provider).isNotNull(); Type type = handle.getType(); if (type == BigintType.BIGINT) { - assertEquals(provider.getLong(), provider.getLong()); + assertThat(provider.getLong()).isEqualTo(provider.getLong()); } else if (type == BooleanType.BOOLEAN) { - assertEquals(provider.getBoolean(), provider.getBoolean()); + assertThat(provider.getBoolean()).isEqualTo(provider.getBoolean()); } else if (type == DoubleType.DOUBLE) { - assertEquals(provider.getDouble(), provider.getDouble()); + assertThat(provider.getDouble()).isEqualTo(provider.getDouble()); } else if (type == VarcharType.VARCHAR) { - assertEquals(provider.getSlice(), provider.getSlice()); + assertThat(provider.getSlice()).isEqualTo(provider.getSlice()); } } diff --git a/lib/trino-record-decoder/src/test/java/io/trino/decoder/util/DecoderTestUtil.java b/lib/trino-record-decoder/src/test/java/io/trino/decoder/util/DecoderTestUtil.java index 1437b1a7b839..31b3a13dedea 100644 --- a/lib/trino-record-decoder/src/test/java/io/trino/decoder/util/DecoderTestUtil.java +++ b/lib/trino-record-decoder/src/test/java/io/trino/decoder/util/DecoderTestUtil.java @@ -21,9 +21,8 @@ import java.util.Map; +import static org.assertj.core.api.Assertions.assertThat; import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; public final class DecoderTestUtil { @@ -34,42 +33,42 @@ private DecoderTestUtil() {} public static void checkValue(Map decodedRow, DecoderColumnHandle handle, Slice value) { FieldValueProvider provider = decodedRow.get(handle); - assertNotNull(provider); - assertEquals(provider.getSlice(), value); + assertThat(provider).isNotNull(); + assertThat(provider.getSlice()).isEqualTo(value); } public static void checkValue(Map decodedRow, DecoderColumnHandle handle, String value) { FieldValueProvider provider = decodedRow.get(handle); - assertNotNull(provider); - assertEquals(provider.getSlice().toStringUtf8(), value); + assertThat(provider).isNotNull(); + assertThat(provider.getSlice().toStringUtf8()).isEqualTo(value); } public static void checkValue(Map decodedRow, DecoderColumnHandle handle, long value) { FieldValueProvider provider = decodedRow.get(handle); - assertNotNull(provider); - assertEquals(provider.getLong(), value); + assertThat(provider).isNotNull(); + assertThat(provider.getLong()).isEqualTo(value); } public static void checkValue(Map decodedRow, DecoderColumnHandle handle, double value) { FieldValueProvider provider = decodedRow.get(handle); - assertNotNull(provider); + assertThat(provider).isNotNull(); assertEquals(provider.getDouble(), value, 0.0001); } public static void checkValue(Map decodedRow, DecoderColumnHandle handle, boolean value) { FieldValueProvider provider = decodedRow.get(handle); - assertNotNull(provider); - assertEquals(provider.getBoolean(), value); + assertThat(provider).isNotNull(); + assertThat(provider.getBoolean()).isEqualTo(value); } public static void checkIsNull(Map decodedRow, DecoderColumnHandle handle) { FieldValueProvider provider = decodedRow.get(handle); - assertNotNull(provider); - assertTrue(provider.isNull()); + assertThat(provider).isNotNull(); + assertThat(provider.isNull()).isTrue(); } } diff --git a/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/TestAccumuloClient.java b/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/TestAccumuloClient.java index 472b9711d10b..b33b67e27ac1 100644 --- a/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/TestAccumuloClient.java +++ b/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/TestAccumuloClient.java @@ -35,8 +35,8 @@ import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertNotNull; @TestInstance(PER_CLASS) public class TestAccumuloClient @@ -83,7 +83,7 @@ public void testCreateTableEmptyAccumuloColumn() properties.put("external", true); properties.put("column_mapping", "a:a:a,b::b,c:c:,d::"); client.createTable(new ConnectorTableMetadata(tableName, columns, properties)); - assertNotNull(client.getTable(tableName)); + assertThat(client.getTable(tableName)).isNotNull(); } finally { AccumuloTable table = zooKeeperMetadataManager.getTable(tableName); diff --git a/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/TestAccumuloConnectorTest.java b/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/TestAccumuloConnectorTest.java index 1f89baca5457..7effb2a909c3 100644 --- a/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/TestAccumuloConnectorTest.java +++ b/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/TestAccumuloConnectorTest.java @@ -27,11 +27,9 @@ import static io.trino.plugin.accumulo.AccumuloQueryRunner.createAccumuloQueryRunner; import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.testing.MaterializedResult.resultBuilder; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assumptions.abort; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; /** * Accumulo requires a unique identifier for the rows. @@ -91,15 +89,15 @@ public void testCreateTableAsSelect() // TODO some test cases from overridden method succeed to create table, but with wrong number or rows. assertUpdate("CREATE TABLE test_create_table_as_if_not_exists (a bigint, b double)"); - assertTrue(getQueryRunner().tableExists(getSession(), "test_create_table_as_if_not_exists")); + assertThat(getQueryRunner().tableExists(getSession(), "test_create_table_as_if_not_exists")).isTrue(); assertTableColumnNames("test_create_table_as_if_not_exists", "a", "b"); assertUpdate("CREATE TABLE IF NOT EXISTS test_create_table_as_if_not_exists AS SELECT cast(uuid() AS uuid) AS uuid, orderkey, discount FROM lineitem", 0); - assertTrue(getQueryRunner().tableExists(getSession(), "test_create_table_as_if_not_exists")); + assertThat(getQueryRunner().tableExists(getSession(), "test_create_table_as_if_not_exists")).isTrue(); assertTableColumnNames("test_create_table_as_if_not_exists", "a", "b"); assertUpdate("DROP TABLE test_create_table_as_if_not_exists"); - assertFalse(getQueryRunner().tableExists(getSession(), "test_create_table_as_if_not_exists")); + assertThat(getQueryRunner().tableExists(getSession(), "test_create_table_as_if_not_exists")).isFalse(); this.assertCreateTableAsSelect( "SELECT orderstatus, sum(totalprice) x FROM orders GROUP BY orderstatus", @@ -192,24 +190,24 @@ public void testShowColumns() // Override base class because table descriptions for Accumulo connector include extra info MaterializedResult actual = computeActual("SHOW COLUMNS FROM orders"); - assertEquals(actual.getMaterializedRows().get(0).getField(0), "orderkey"); - assertEquals(actual.getMaterializedRows().get(0).getField(1), "bigint"); - assertEquals(actual.getMaterializedRows().get(1).getField(0), "custkey"); - assertEquals(actual.getMaterializedRows().get(1).getField(1), "bigint"); - assertEquals(actual.getMaterializedRows().get(2).getField(0), "orderstatus"); - assertEquals(actual.getMaterializedRows().get(2).getField(1), "varchar(1)"); - assertEquals(actual.getMaterializedRows().get(3).getField(0), "totalprice"); - assertEquals(actual.getMaterializedRows().get(3).getField(1), "double"); - assertEquals(actual.getMaterializedRows().get(4).getField(0), "orderdate"); - assertEquals(actual.getMaterializedRows().get(4).getField(1), "date"); - assertEquals(actual.getMaterializedRows().get(5).getField(0), "orderpriority"); - assertEquals(actual.getMaterializedRows().get(5).getField(1), "varchar(15)"); - assertEquals(actual.getMaterializedRows().get(6).getField(0), "clerk"); - assertEquals(actual.getMaterializedRows().get(6).getField(1), "varchar(15)"); - assertEquals(actual.getMaterializedRows().get(7).getField(0), "shippriority"); - assertEquals(actual.getMaterializedRows().get(7).getField(1), "integer"); - assertEquals(actual.getMaterializedRows().get(8).getField(0), "comment"); - assertEquals(actual.getMaterializedRows().get(8).getField(1), "varchar(79)"); + assertThat(actual.getMaterializedRows().get(0).getField(0)).isEqualTo("orderkey"); + assertThat(actual.getMaterializedRows().get(0).getField(1)).isEqualTo("bigint"); + assertThat(actual.getMaterializedRows().get(1).getField(0)).isEqualTo("custkey"); + assertThat(actual.getMaterializedRows().get(1).getField(1)).isEqualTo("bigint"); + assertThat(actual.getMaterializedRows().get(2).getField(0)).isEqualTo("orderstatus"); + assertThat(actual.getMaterializedRows().get(2).getField(1)).isEqualTo("varchar(1)"); + assertThat(actual.getMaterializedRows().get(3).getField(0)).isEqualTo("totalprice"); + assertThat(actual.getMaterializedRows().get(3).getField(1)).isEqualTo("double"); + assertThat(actual.getMaterializedRows().get(4).getField(0)).isEqualTo("orderdate"); + assertThat(actual.getMaterializedRows().get(4).getField(1)).isEqualTo("date"); + assertThat(actual.getMaterializedRows().get(5).getField(0)).isEqualTo("orderpriority"); + assertThat(actual.getMaterializedRows().get(5).getField(1)).isEqualTo("varchar(15)"); + assertThat(actual.getMaterializedRows().get(6).getField(0)).isEqualTo("clerk"); + assertThat(actual.getMaterializedRows().get(6).getField(1)).isEqualTo("varchar(15)"); + assertThat(actual.getMaterializedRows().get(7).getField(0)).isEqualTo("shippriority"); + assertThat(actual.getMaterializedRows().get(7).getField(1)).isEqualTo("integer"); + assertThat(actual.getMaterializedRows().get(8).getField(0)).isEqualTo("comment"); + assertThat(actual.getMaterializedRows().get(8).getField(1)).isEqualTo("varchar(79)"); } @Test diff --git a/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/index/TestIndexer.java b/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/index/TestIndexer.java index 26fd8c0a569e..807d11c99140 100644 --- a/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/index/TestIndexer.java +++ b/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/index/TestIndexer.java @@ -45,10 +45,9 @@ import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.VarcharType.VARCHAR; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -141,7 +140,7 @@ public void testMutationIndex() assertKeyValuePair(iter.next(), M1_FNAME_VALUE, "cf_firstname", "row1", ""); assertKeyValuePair(iter.next(), bytes("def"), "cf_arr", "row1", ""); assertKeyValuePair(iter.next(), bytes("ghi"), "cf_arr", "row1", ""); - assertFalse(iter.hasNext()); + assertThat(iter.hasNext()).isFalse(); scan.close(); @@ -157,7 +156,7 @@ public void testMutationIndex() assertKeyValuePair(iter.next(), M1_FNAME_VALUE, "cf_firstname", "___card___", "1"); assertKeyValuePair(iter.next(), bytes("def"), "cf_arr", "___card___", "1"); assertKeyValuePair(iter.next(), bytes("ghi"), "cf_arr", "___card___", "1"); - assertFalse(iter.hasNext()); + assertThat(iter.hasNext()).isFalse(); scan.close(); @@ -177,7 +176,7 @@ public void testMutationIndex() assertKeyValuePair(iter.next(), bytes("ghi"), "cf_arr", "row1", ""); assertKeyValuePair(iter.next(), bytes("ghi"), "cf_arr", "row2", ""); assertKeyValuePair(iter.next(), bytes("mno"), "cf_arr", "row2", ""); - assertFalse(iter.hasNext()); + assertThat(iter.hasNext()).isFalse(); scan.close(); @@ -195,7 +194,7 @@ public void testMutationIndex() assertKeyValuePair(iter.next(), bytes("def"), "cf_arr", "___card___", "1"); assertKeyValuePair(iter.next(), bytes("ghi"), "cf_arr", "___card___", "2"); assertKeyValuePair(iter.next(), bytes("mno"), "cf_arr", "___card___", "1"); - assertFalse(iter.hasNext()); + assertThat(iter.hasNext()).isFalse(); scan.close(); } @@ -233,7 +232,7 @@ public void testMutationIndexWithVisibilities() assertKeyValuePair(iter.next(), bytes("def"), "cf_arr", "row1", "moreprivate", ""); assertKeyValuePair(iter.next(), bytes("ghi"), "cf_arr", "row1", ""); assertKeyValuePair(iter.next(), bytes("ghi"), "cf_arr", "row1", "moreprivate", ""); - assertFalse(iter.hasNext()); + assertThat(iter.hasNext()).isFalse(); scan.close(); @@ -254,7 +253,7 @@ public void testMutationIndexWithVisibilities() assertKeyValuePair(iter.next(), bytes("def"), "cf_arr", "___card___", "moreprivate", "1"); assertKeyValuePair(iter.next(), bytes("ghi"), "cf_arr", "___card___", "1"); assertKeyValuePair(iter.next(), bytes("ghi"), "cf_arr", "___card___", "moreprivate", "1"); - assertFalse(iter.hasNext()); + assertThat(iter.hasNext()).isFalse(); scan.close(); @@ -285,7 +284,7 @@ public void testMutationIndexWithVisibilities() assertKeyValuePair(iter.next(), bytes("ghi"), "cf_arr", "row2", "moreprivate", ""); assertKeyValuePair(iter.next(), bytes("mno"), "cf_arr", "row2", ""); assertKeyValuePair(iter.next(), bytes("mno"), "cf_arr", "row2", "moreprivate", ""); - assertFalse(iter.hasNext()); + assertThat(iter.hasNext()).isFalse(); scan.close(); @@ -310,26 +309,26 @@ public void testMutationIndexWithVisibilities() assertKeyValuePair(iter.next(), bytes("ghi"), "cf_arr", "___card___", "moreprivate", "2"); assertKeyValuePair(iter.next(), bytes("mno"), "cf_arr", "___card___", "1"); assertKeyValuePair(iter.next(), bytes("mno"), "cf_arr", "___card___", "moreprivate", "1"); - assertFalse(iter.hasNext()); + assertThat(iter.hasNext()).isFalse(); scan.close(); } private static void assertKeyValuePair(Entry e, byte[] row, String cf, String cq, String value) { - assertEquals(row, e.getKey().getRow().copyBytes()); - assertEquals(cf, e.getKey().getColumnFamily().toString()); - assertEquals(cq, e.getKey().getColumnQualifier().toString()); - assertEquals(value, e.getValue().toString()); + assertThat(row).isEqualTo(e.getKey().getRow().copyBytes()); + assertThat(cf).isEqualTo(e.getKey().getColumnFamily().toString()); + assertThat(cq).isEqualTo(e.getKey().getColumnQualifier().toString()); + assertThat(value).isEqualTo(e.getValue().toString()); } private static void assertKeyValuePair(Entry e, byte[] row, String cf, String cq, String cv, String value) { - assertEquals(row, e.getKey().getRow().copyBytes()); - assertEquals(cf, e.getKey().getColumnFamily().toString()); - assertEquals(cq, e.getKey().getColumnQualifier().toString()); - assertEquals(cv, e.getKey().getColumnVisibility().toString()); - assertEquals(value, e.getValue().toString()); + assertThat(row).isEqualTo(e.getKey().getRow().copyBytes()); + assertThat(cf).isEqualTo(e.getKey().getColumnFamily().toString()); + assertThat(cq).isEqualTo(e.getKey().getColumnQualifier().toString()); + assertThat(cv).isEqualTo(e.getKey().getColumnVisibility().toString()); + assertThat(value).isEqualTo(e.getValue().toString()); } private static byte[] bytes(String s) diff --git a/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/model/TestAccumuloSplit.java b/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/model/TestAccumuloSplit.java index 6335c4c5f790..926f775cce6a 100644 --- a/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/model/TestAccumuloSplit.java +++ b/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/model/TestAccumuloSplit.java @@ -21,7 +21,7 @@ import java.util.Optional; import java.util.stream.Collectors; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestAccumuloSplit { @@ -53,8 +53,8 @@ public void testJsonRoundTripEmptyThings() private static void assertSplit(AccumuloSplit actual, AccumuloSplit expected) { - assertEquals(actual.getAddresses(), expected.getAddresses()); - assertEquals(actual.getHostPort(), expected.getHostPort()); - assertEquals(actual.getRanges(), expected.getRanges()); + assertThat(actual.getAddresses()).isEqualTo(expected.getAddresses()); + assertThat(actual.getHostPort()).isEqualTo(expected.getHostPort()); + assertThat(actual.getRanges()).isEqualTo(expected.getRanges()); } } diff --git a/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/model/TestField.java b/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/model/TestField.java index 9edc7755aaec..d22ea29149ed 100644 --- a/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/model/TestField.java +++ b/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/model/TestField.java @@ -46,8 +46,8 @@ import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.lang.Float.floatToIntBits; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestField { @@ -65,9 +65,9 @@ public void testArray() Type type = new ArrayType(VARCHAR); Block expected = AccumuloRowSerializer.getBlockFromArray(VARCHAR, ImmutableList.of("a", "b", "c")); Field f1 = new Field(expected, type); - assertEquals(f1.getArray(), expected); - assertEquals(f1.getObject(), expected); - assertEquals(f1.getType(), type); + assertThat(f1.getArray()).isEqualTo(expected); + assertThat(f1.getObject()).isEqualTo(expected); + assertThat(f1.getType()).isEqualTo(type); } @Test @@ -75,14 +75,14 @@ public void testBoolean() { Type type = BOOLEAN; Field f1 = new Field(true, type); - assertEquals(f1.getBoolean().booleanValue(), true); - assertEquals(f1.getObject(), true); - assertEquals(f1.getType(), type); + assertThat(f1.getBoolean().booleanValue()).isEqualTo(true); + assertThat(f1.getObject()).isEqualTo(true); + assertThat(f1.getType()).isEqualTo(type); f1 = new Field(false, type); - assertEquals(f1.getBoolean().booleanValue(), false); - assertEquals(f1.getObject(), false); - assertEquals(f1.getType(), type); + assertThat(f1.getBoolean().booleanValue()).isEqualTo(false); + assertThat(f1.getObject()).isEqualTo(false); + assertThat(f1.getType()).isEqualTo(type); } @Test @@ -91,9 +91,9 @@ public void testDate() Type type = DATE; long expected = LocalDate.parse("1999-01-01").toEpochDay(); Field f1 = new Field(10592L, type); - assertEquals(f1.getDate(), expected); - assertEquals(f1.getObject(), expected); - assertEquals(f1.getType(), type); + assertThat(f1.getDate()).isEqualTo(expected); + assertThat(f1.getObject()).isEqualTo(expected); + assertThat(f1.getType()).isEqualTo(type); } @Test @@ -102,9 +102,9 @@ public void testDouble() Type type = DOUBLE; Double expected = 123.45678; Field f1 = new Field(expected, type); - assertEquals(f1.getDouble(), expected); - assertEquals(f1.getObject(), expected); - assertEquals(f1.getType(), type); + assertThat(f1.getDouble()).isEqualTo(expected); + assertThat(f1.getObject()).isEqualTo(expected); + assertThat(f1.getType()).isEqualTo(type); } @Test @@ -113,9 +113,9 @@ public void testFloat() Type type = REAL; Float expected = 123.45678f; Field f1 = new Field((long) floatToIntBits(expected), type); - assertEquals(f1.getFloat(), expected); - assertEquals(f1.getObject(), expected); - assertEquals(f1.getType(), type); + assertThat(f1.getFloat()).isEqualTo(expected); + assertThat(f1.getObject()).isEqualTo(expected); + assertThat(f1.getType()).isEqualTo(type); } @Test @@ -124,9 +124,9 @@ public void testInt() Type type = INTEGER; Integer expected = 12345678; Field f1 = new Field((long) expected, type); - assertEquals(f1.getInt(), expected); - assertEquals(f1.getObject(), expected); - assertEquals(f1.getType(), type); + assertThat(f1.getInt()).isEqualTo(expected); + assertThat(f1.getObject()).isEqualTo(expected); + assertThat(f1.getType()).isEqualTo(type); } @Test @@ -135,9 +135,9 @@ public void testLong() Type type = BIGINT; Long expected = 12345678L; Field f1 = new Field(expected, type); - assertEquals(f1.getLong(), expected); - assertEquals(f1.getObject(), expected); - assertEquals(f1.getType(), type); + assertThat(f1.getLong()).isEqualTo(expected); + assertThat(f1.getObject()).isEqualTo(expected); + assertThat(f1.getType()).isEqualTo(type); } @Test @@ -148,9 +148,9 @@ public void testMap() TypeSignatureParameter.typeParameter(BIGINT.getTypeSignature()))); SqlMap expected = AccumuloRowSerializer.getSqlMapFromMap(type, ImmutableMap.of("a", 1L, "b", 2L, "c", 3L)); Field f1 = new Field(expected, type); - assertEquals(f1.getMap(), expected); - assertEquals(f1.getObject(), expected); - assertEquals(f1.getType(), type); + assertThat(f1.getMap()).isEqualTo(expected); + assertThat(f1.getObject()).isEqualTo(expected); + assertThat(f1.getType()).isEqualTo(type); } @Test @@ -159,9 +159,9 @@ public void testSmallInt() Type type = SMALLINT; Short expected = 12345; Field f1 = new Field((long) expected, type); - assertEquals(f1.getShort(), expected); - assertEquals(f1.getObject(), expected); - assertEquals(f1.getType(), type); + assertThat(f1.getShort()).isEqualTo(expected); + assertThat(f1.getObject()).isEqualTo(expected); + assertThat(f1.getType()).isEqualTo(type); } @Test @@ -170,9 +170,9 @@ public void testTime() Type type = TIME_MILLIS; Time expected = new Time(new GregorianCalendar(1970, 0, 1, 12, 30, 0).getTime().getTime()); Field f1 = new Field(70200000L, type); - assertEquals(f1.getTime(), expected); - assertEquals(f1.getObject(), expected); - assertEquals(f1.getType(), type); + assertThat(f1.getTime()).isEqualTo(expected); + assertThat(f1.getObject()).isEqualTo(expected); + assertThat(f1.getType()).isEqualTo(type); } @Test @@ -181,9 +181,9 @@ public void testTimestamp() Type type = TIMESTAMP_MILLIS; Timestamp expected = new Timestamp(new GregorianCalendar(1999, 0, 1, 12, 30, 0).getTime().getTime()); Field f1 = new Field(915_219_000_000_000L, type); - assertEquals(f1.getTimestamp(), expected); - assertEquals(f1.getObject(), expected); - assertEquals(f1.getType(), type); + assertThat(f1.getTimestamp()).isEqualTo(expected); + assertThat(f1.getObject()).isEqualTo(expected); + assertThat(f1.getType()).isEqualTo(type); } @Test @@ -192,9 +192,9 @@ public void testTinyInt() Type type = TINYINT; Byte expected = 123; Field f1 = new Field((long) expected, type); - assertEquals(f1.getByte(), expected); - assertEquals(f1.getObject(), expected); - assertEquals(f1.getType(), type); + assertThat(f1.getByte()).isEqualTo(expected); + assertThat(f1.getObject()).isEqualTo(expected); + assertThat(f1.getType()).isEqualTo(type); } @Test @@ -203,9 +203,9 @@ public void testVarbinary() Type type = VARBINARY; byte[] expected = "O'Leary".getBytes(UTF_8); Field f1 = new Field(Slices.wrappedBuffer(expected.clone()), type); - assertEquals(f1.getVarbinary(), expected); - assertEquals(f1.getObject(), expected); - assertEquals(f1.getType(), type); + assertThat(f1.getVarbinary()).isEqualTo(expected); + assertThat(f1.getObject()).isEqualTo(expected); + assertThat(f1.getType()).isEqualTo(type); } @Test @@ -214,8 +214,8 @@ public void testVarchar() Type type = VARCHAR; String expected = "O'Leary"; Field f1 = new Field(utf8Slice(expected), type); - assertEquals(f1.getVarchar(), expected); - assertEquals(f1.getObject(), expected); - assertEquals(f1.getType(), type); + assertThat(f1.getVarchar()).isEqualTo(expected); + assertThat(f1.getObject()).isEqualTo(expected); + assertThat(f1.getType()).isEqualTo(type); } } diff --git a/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/model/TestRow.java b/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/model/TestRow.java index 67de0e6a892d..2d7d182e8315 100644 --- a/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/model/TestRow.java +++ b/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/model/TestRow.java @@ -38,8 +38,8 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static java.lang.Float.floatToIntBits; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestRow { @@ -62,7 +62,7 @@ public void testRow() r1.addField(utf8Slice("O'Leary"), VARCHAR); r1.addField(null, VARCHAR); - assertEquals(r1.length(), 14); + assertThat(r1.length()).isEqualTo(14); } @Test diff --git a/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/model/TestSerializedRange.java b/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/model/TestSerializedRange.java index 7d6a4608d90b..dcb3804e9376 100644 --- a/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/model/TestSerializedRange.java +++ b/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/model/TestSerializedRange.java @@ -16,7 +16,7 @@ import org.apache.accumulo.core.data.Range; import org.junit.jupiter.api.Test; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestSerializedRange { @@ -27,8 +27,8 @@ public void testJsonRoundTrip() Range range = new Range("bar", "foo"); Range exclusiveRange = new Range("asiago", false, "bagel", false); - assertEquals(SerializedRange.serialize(exact).deserialize(), exact); - assertEquals(SerializedRange.serialize(range).deserialize(), range); - assertEquals(SerializedRange.serialize(exclusiveRange).deserialize(), exclusiveRange); + assertThat(SerializedRange.serialize(exact).deserialize()).isEqualTo(exact); + assertThat(SerializedRange.serialize(range).deserialize()).isEqualTo(range); + assertThat(SerializedRange.serialize(exclusiveRange).deserialize()).isEqualTo(exclusiveRange); } } diff --git a/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/serializers/AbstractTestAccumuloRowSerializer.java b/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/serializers/AbstractTestAccumuloRowSerializer.java index 4cb85021afff..5f7877b5555d 100644 --- a/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/serializers/AbstractTestAccumuloRowSerializer.java +++ b/plugin/trino-accumulo/src/test/java/io/trino/plugin/accumulo/serializers/AbstractTestAccumuloRowSerializer.java @@ -46,7 +46,7 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public abstract class AbstractTestAccumuloRowSerializer { @@ -67,11 +67,11 @@ public void testArray() List expected = ImmutableList.of("a", "b", "c"); byte[] data = serializer.encode(type, AccumuloRowSerializer.getBlockFromArray(VARCHAR, expected)); List actual = serializer.decode(type, data); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); deserializeData(serializer, data); actual = AccumuloRowSerializer.getArrayFromBlock(VARCHAR, serializer.getArray(COLUMN_NAME, type)); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } @Test @@ -82,19 +82,19 @@ public void testBoolean() Type type = BOOLEAN; byte[] data = serializer.encode(type, true); boolean actual = serializer.decode(type, data); - assertEquals(actual, true); + assertThat(actual).isEqualTo(true); deserializeData(serializer, data); actual = serializer.getBoolean(COLUMN_NAME); - assertEquals(actual, true); + assertThat(actual).isEqualTo(true); data = serializer.encode(type, false); actual = serializer.decode(type, data); - assertEquals(actual, false); + assertThat(actual).isEqualTo(false); deserializeData(serializer, data); actual = serializer.getBoolean(COLUMN_NAME); - assertEquals(actual, false); + assertThat(actual).isEqualTo(false); } @Test @@ -108,7 +108,7 @@ public void testDate() deserializeData(serializer, data); long actual = serializer.getDate(COLUMN_NAME); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } @Test @@ -120,11 +120,11 @@ public void testDouble() Double expected = 123.45678; byte[] data = serializer.encode(type, expected); Double actual = serializer.decode(type, data); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); deserializeData(serializer, data); actual = serializer.getDouble(COLUMN_NAME); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } @Test @@ -136,11 +136,11 @@ public void testFloat() Float expected = 123.45678f; byte[] data = serializer.encode(type, expected); Float actual = ((Double) serializer.decode(type, data)).floatValue(); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); deserializeData(serializer, data); actual = serializer.getFloat(COLUMN_NAME); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } @Test @@ -153,11 +153,11 @@ public void testInt() byte[] data = serializer.encode(type, expected); @SuppressWarnings("unchecked") Integer actual = ((Long) serializer.decode(type, data)).intValue(); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); deserializeData(serializer, data); actual = serializer.getInt(COLUMN_NAME); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } @Test @@ -169,11 +169,11 @@ public void testLong() Long expected = 123456L; byte[] data = serializer.encode(type, expected); Long actual = serializer.decode(type, data); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); deserializeData(serializer, data); actual = serializer.getLong(COLUMN_NAME); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } @Test @@ -187,11 +187,11 @@ public void testMap() Map expected = ImmutableMap.of("a", 1L, "b", 2L, "3", 3L); byte[] data = serializer.encode(type, AccumuloRowSerializer.getSqlMapFromMap(type, expected)); Map actual = serializer.decode(type, data); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); deserializeData(serializer, data); actual = AccumuloRowSerializer.getMapFromSqlMap(type, serializer.getMap(COLUMN_NAME, type)); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } @Test @@ -203,11 +203,11 @@ public void testSmallInt() Short expected = 12345; byte[] data = serializer.encode(type, expected); Short actual = ((Long) serializer.decode(type, data)).shortValue(); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); deserializeData(serializer, data); actual = serializer.getShort(COLUMN_NAME); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } @Test @@ -219,11 +219,11 @@ public void testTime() Time expected = new Time(new java.util.Date().getTime()); byte[] data = serializer.encode(type, expected); Time actual = new Time(serializer.decode(type, data)); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); deserializeData(serializer, data); actual = serializer.getTime(COLUMN_NAME); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } @Test @@ -235,11 +235,11 @@ public void testTimestamp() Timestamp expected = new Timestamp(new java.util.Date().getTime()); byte[] data = serializer.encode(type, expected); Timestamp actual = new Timestamp(serializer.decode(type, data)); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); deserializeData(serializer, data); actual = serializer.getTimestamp(COLUMN_NAME); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } @Test @@ -251,11 +251,11 @@ public void testTinyInt() Byte expected = 123; byte[] data = serializer.encode(type, expected); Byte actual = ((Long) serializer.decode(type, data)).byteValue(); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); deserializeData(serializer, data); actual = serializer.getByte(COLUMN_NAME); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } @Test @@ -267,11 +267,11 @@ public void testVarbinary() byte[] expected = b(UUID.randomUUID().toString()); byte[] data = serializer.encode(type, expected); byte[] actual = serializer.decode(type, data); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); deserializeData(serializer, data); actual = serializer.getVarbinary(COLUMN_NAME); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } @Test @@ -283,11 +283,11 @@ public void testVarchar() String expected = UUID.randomUUID().toString(); byte[] data = serializer.encode(type, expected); String actual = serializer.decode(type, data); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); deserializeData(serializer, data); actual = serializer.getVarchar(COLUMN_NAME); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } protected void deserializeData(AccumuloRowSerializer serializer, byte[] data) diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java index cb92f5e55d5a..9136cc411886 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java @@ -46,8 +46,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assumptions.abort; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; @TestInstance(PER_CLASS) public abstract class BaseBigQueryConnectorTest @@ -178,9 +176,7 @@ public void testCreateTableSupportedType() private void testCreateTableSupportedType(String createType, String expectedType) { try (TestTable table = new TestTable(getQueryRunner()::execute, "test_create_table_supported_type_" + createType.replaceAll("[^a-zA-Z0-9]", ""), format("(col1 %s)", createType))) { - assertEquals( - computeScalar("SELECT data_type FROM information_schema.columns WHERE table_name = '" + table.getName() + "' AND column_name = 'col1'"), - expectedType); + assertThat(computeScalar("SELECT data_type FROM information_schema.columns WHERE table_name = '" + table.getName() + "' AND column_name = 'col1'")).isEqualTo(expectedType); } } @@ -547,9 +543,7 @@ public void testViewDefinitionSystemTable() onBigQuery(format("CREATE TABLE %s.%s (a INT64, b INT64, c INT64)", schemaName, tableName)); onBigQuery(format("CREATE VIEW %s.%s AS SELECT * FROM %s.%s", schemaName, viewName, schemaName, tableName)); - assertEquals( - computeScalar(format("SELECT * FROM %s.\"%s$view_definition\"", schemaName, viewName)), - format("SELECT * FROM %s.%s", schemaName, tableName)); + assertThat(computeScalar(format("SELECT * FROM %s.\"%s$view_definition\"", schemaName, viewName))).isEqualTo(format("SELECT * FROM %s.%s", schemaName, tableName)); assertQueryFails( format("SELECT * FROM %s.\"%s$view_definition\"", schemaName, tableName), @@ -942,17 +936,17 @@ public void testNativeQuerySelectUnsupportedType() public void testNativeQueryCreateStatement() { String tableName = "test_create" + randomNameSuffix(); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); assertThatThrownBy(() -> query("SELECT * FROM TABLE(bigquery.system.query(query => 'CREATE TABLE test." + tableName + "(n INTEGER)'))")) .hasMessage("Unsupported statement type: CREATE_TABLE"); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); } @Test public void testNativeQueryInsertStatementTableDoesNotExist() { String tableName = "test_insert" + randomNameSuffix(); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); assertThatThrownBy(() -> query("SELECT * FROM TABLE(bigquery.system.query(query => 'INSERT INTO test." + tableName + " VALUES (1)'))")) .hasMessageContaining("Failed to get schema for query") .hasStackTraceContaining("%s was not found", tableName); diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryCaseInsensitiveMapping.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryCaseInsensitiveMapping.java index 32b10c7a6289..46be937556f1 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryCaseInsensitiveMapping.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryCaseInsensitiveMapping.java @@ -30,7 +30,6 @@ import static java.lang.String.format; import static java.util.Locale.ENGLISH; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; // With case-insensitive-name-matching enabled colliding schema/table names are considered as errors. // Some tests here create colliding names which can cause any other concurrent test to fail. @@ -84,11 +83,9 @@ public void testNonLowerCaseTableName() AutoCloseable ignore2 = withTable( bigQuerySchema + ".NonLowerCaseTable", "AS SELECT 'a' AS lower_case_name, 'b' AS Mixed_Case_Name, 'c' AS UPPER_CASE_NAME")) { assertThat(computeActual("SHOW TABLES FROM " + trinoSchema).getOnlyColumn()).contains("nonlowercasetable"); - assertEquals( - computeActual("SHOW COLUMNS FROM " + trinoSchema + ".nonlowercasetable").getMaterializedRows().stream() - .map(row -> row.getField(0)) - .collect(toImmutableSet()), - ImmutableSet.of("lower_case_name", "mixed_case_name", "upper_case_name")); + assertThat(computeActual("SHOW COLUMNS FROM " + trinoSchema + ".nonlowercasetable").getMaterializedRows().stream() + .map(row -> row.getField(0)) + .collect(toImmutableSet())).isEqualTo(ImmutableSet.of("lower_case_name", "mixed_case_name", "upper_case_name")); assertQuery("SELECT table_name FROM information_schema.tables WHERE table_schema = '" + trinoSchema + "'", "VALUES 'nonlowercasetable'"); assertQuery( @@ -129,11 +126,9 @@ public void testNonLowerCaseViewName() TestView view = new TestView(bigQuerySqlExecutor, namePrefix, "SELECT 'a' AS lower_case_name, 'b' AS Mixed_Case_Name, 'c' AS UPPER_CASE_NAME")) { String viewName = view.getName().substring(bigQuerySchema.length() + 1).toLowerCase(ENGLISH); assertThat(computeActual("SHOW TABLES FROM " + trinoSchema).getOnlyColumn()).contains(viewName); - assertEquals( - computeActual("SHOW COLUMNS FROM " + trinoSchema + "." + viewName).getMaterializedRows().stream() - .map(row -> row.getField(0)) - .collect(toImmutableSet()), - ImmutableSet.of("lower_case_name", "mixed_case_name", "upper_case_name")); + assertThat(computeActual("SHOW COLUMNS FROM " + trinoSchema + "." + viewName).getMaterializedRows().stream() + .map(row -> row.getField(0)) + .collect(toImmutableSet())).isEqualTo(ImmutableSet.of("lower_case_name", "mixed_case_name", "upper_case_name")); assertQuery( format("SELECT table_name FROM information_schema.tables WHERE table_schema = '%s'", trinoSchema), diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryMetadataCaching.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryMetadataCaching.java index dc674d61a553..84a1d346fd5f 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryMetadataCaching.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestBigQueryMetadataCaching.java @@ -21,7 +21,7 @@ import static io.trino.plugin.bigquery.BigQueryQueryRunner.BigQuerySqlExecutor; import static io.trino.testing.TestingNames.randomNameSuffix; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestBigQueryMetadataCaching extends AbstractTestQueryFramework @@ -45,14 +45,14 @@ public void testMetadataCaching() String schema = "test_metadata_caching_" + randomNameSuffix(); try { getQueryRunner().execute("CREATE SCHEMA " + schema); - assertEquals(getQueryRunner().execute("SHOW SCHEMAS IN bigquery LIKE '" + schema + "'").getOnlyValue(), schema); + assertThat(getQueryRunner().execute("SHOW SCHEMAS IN bigquery LIKE '" + schema + "'").getOnlyValue()).isEqualTo(schema); String schemaTableName = schema + ".test_metadata_caching"; getQueryRunner().execute("CREATE TABLE " + schemaTableName + " AS SELECT * FROM tpch.tiny.region"); - assertEquals(getQueryRunner().execute("SELECT * FROM " + schemaTableName).getRowCount(), 5); + assertThat(getQueryRunner().execute("SELECT * FROM " + schemaTableName).getRowCount()).isEqualTo(5); bigQuerySqlExecutor.execute("DROP SCHEMA " + schema + " CASCADE"); - assertEquals(getQueryRunner().execute("SHOW SCHEMAS IN bigquery LIKE '" + schema + "'").getOnlyValue(), schema); + assertThat(getQueryRunner().execute("SHOW SCHEMAS IN bigquery LIKE '" + schema + "'").getOnlyValue()).isEqualTo(schema); assertQueryFails("SELECT * FROM " + schemaTableName, ".*Schema '.+' does not exist.*"); } diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestStaticCredentialsConfig.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestStaticCredentialsConfig.java index 83f494eb5145..d3dd59fdc569 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestStaticCredentialsConfig.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/TestStaticCredentialsConfig.java @@ -26,9 +26,9 @@ import static io.airlift.configuration.testing.ConfigAssertions.assertRecordedDefaults; import static io.airlift.configuration.testing.ConfigAssertions.recordDefaults; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Fail.fail; public class TestStaticCredentialsConfig { @@ -48,8 +48,8 @@ public void testExplicitPropertyMappingsWithCredentialsKey() ConfigurationFactory configurationFactory = new ConfigurationFactory(properties); StaticCredentialsConfig config = configurationFactory.build(StaticCredentialsConfig.class); - assertEquals(config.getCredentialsKey(), Optional.of("key")); - assertEquals(config.getCredentialsFile(), Optional.empty()); + assertThat(config.getCredentialsKey()).isEqualTo(Optional.of("key")); + assertThat(config.getCredentialsFile()).isEqualTo(Optional.empty()); } @Test @@ -63,8 +63,8 @@ public void testExplicitPropertyMappingsWithCredentialsFile() ConfigurationFactory configurationFactory = new ConfigurationFactory(properties); StaticCredentialsConfig config = configurationFactory.build(StaticCredentialsConfig.class); - assertEquals(config.getCredentialsKey(), Optional.empty()); - assertEquals(config.getCredentialsFile(), Optional.of(file.toString())); + assertThat(config.getCredentialsKey()).isEqualTo(Optional.empty()); + assertThat(config.getCredentialsFile()).isEqualTo(Optional.of(file.toString())); } catch (IOException e) { fail(e.getMessage()); diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/CassandraServer.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/CassandraServer.java index a6960e7b1a73..7c2ee1b906be 100644 --- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/CassandraServer.java +++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/CassandraServer.java @@ -52,8 +52,8 @@ import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; import static org.testcontainers.utility.MountableFile.forHostPath; -import static org.testng.Assert.assertEquals; public class CassandraServer implements Closeable @@ -159,7 +159,7 @@ private static void checkConnectivity(CassandraSession session) { ResultSet result = session.execute("SELECT release_version FROM system.local"); List rows = result.all(); - assertEquals(rows.size(), 1); + assertThat(rows.size()).isEqualTo(1); String version = rows.get(0).getString(0); log.debug("Cassandra version: %s", version); } diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/CassandraTestingUtils.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/CassandraTestingUtils.java index fbad4da7ed52..dc9b0a9b4e62 100644 --- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/CassandraTestingUtils.java +++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/CassandraTestingUtils.java @@ -34,7 +34,7 @@ import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.lang.String.format; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public final class CassandraTestingUtils { @@ -101,7 +101,7 @@ public static void createTableTupleType(CassandraSession session, SchemaTableNam session.execute(format("INSERT INTO %s (key, typetuple) VALUES (1, (1, 'text-1', 1.11))", table)); session.execute(format("INSERT INTO %s (key, typetuple) VALUES (2, (2, 'text-2', 2.22))", table)); - assertEquals(session.execute("SELECT COUNT(*) FROM " + table).all().get(0).getLong(0), 2); + assertThat(session.execute("SELECT COUNT(*) FROM " + table).all().get(0).getLong(0)).isEqualTo(2); } private static void createTableUserDefinedType(CassandraSession session, SchemaTableName table) @@ -167,7 +167,7 @@ private static void createTableUserDefinedType(CassandraSession session, SchemaT "}" + ");", table)); - assertEquals(session.execute("SELECT COUNT(*) FROM " + table).all().get(0).getLong(0), 1); + assertThat(session.execute("SELECT COUNT(*) FROM " + table).all().get(0).getLong(0)).isEqualTo(1); } private static void insertTestData(CassandraSession session, SchemaTableName table, Date date, int rowsCount) @@ -199,7 +199,7 @@ private static void insertTestData(CassandraSession session, SchemaTableName tab session.execute(insert); } - assertEquals(session.execute("SELECT COUNT(*) FROM " + table).all().get(0).getLong(0), rowsCount); + assertThat(session.execute("SELECT COUNT(*) FROM " + table).all().get(0).getLong(0)).isEqualTo(rowsCount); } private static void createTableDeleteData(CassandraSession session, SchemaTableName table) @@ -259,6 +259,6 @@ private static void insertIntoTableDeleteData(CassandraSession session, SchemaTa session.execute(QueryBuilder.insertInto(table.getSchemaName(), table.getTableName()) .value("partition_one", literal(2L)).value("partition_two", literal(2)).value("clust_one", literal("clust_one_" + 1)).build()); - assertEquals(session.execute("SELECT COUNT(*) FROM " + table).all().get(0).getLong(0), 15); + assertThat(session.execute("SELECT COUNT(*) FROM " + table).all().get(0).getLong(0)).isEqualTo(15); } } diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraColumnHandle.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraColumnHandle.java index 983e554841f1..f30e9dc5c146 100644 --- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraColumnHandle.java +++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraColumnHandle.java @@ -22,7 +22,7 @@ import org.junit.jupiter.api.Test; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestCassandraColumnHandle { @@ -43,11 +43,11 @@ public void testRoundTrip() String json = codec.toJson(expected); CassandraColumnHandle actual = codec.fromJson(json); - assertEquals(actual.getName(), expected.getName()); - assertEquals(actual.getOrdinalPosition(), expected.getOrdinalPosition()); - assertEquals(actual.getCassandraType(), expected.getCassandraType()); - assertEquals(actual.isPartitionKey(), expected.isPartitionKey()); - assertEquals(actual.isClusteringKey(), expected.isClusteringKey()); + assertThat(actual.getName()).isEqualTo(expected.getName()); + assertThat(actual.getOrdinalPosition()).isEqualTo(expected.getOrdinalPosition()); + assertThat(actual.getCassandraType()).isEqualTo(expected.getCassandraType()); + assertThat(actual.isPartitionKey()).isEqualTo(expected.isPartitionKey()); + assertThat(actual.isClusteringKey()).isEqualTo(expected.isClusteringKey()); } @Test @@ -65,10 +65,10 @@ public void testRoundTrip2() String json = codec.toJson(expected); CassandraColumnHandle actual = codec.fromJson(json); - assertEquals(actual.getName(), expected.getName()); - assertEquals(actual.getOrdinalPosition(), expected.getOrdinalPosition()); - assertEquals(actual.getCassandraType(), expected.getCassandraType()); - assertEquals(actual.isPartitionKey(), expected.isPartitionKey()); - assertEquals(actual.isClusteringKey(), expected.isClusteringKey()); + assertThat(actual.getName()).isEqualTo(expected.getName()); + assertThat(actual.getOrdinalPosition()).isEqualTo(expected.getOrdinalPosition()); + assertThat(actual.getCassandraType()).isEqualTo(expected.getCassandraType()); + assertThat(actual.isPartitionKey()).isEqualTo(expected.isPartitionKey()); + assertThat(actual.isClusteringKey()).isEqualTo(expected.isClusteringKey()); } } diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnector.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnector.java index 1eb5bc82075c..75b30ffd0135 100644 --- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnector.java +++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnector.java @@ -88,11 +88,8 @@ import static java.lang.String.format; import static java.util.Locale.ENGLISH; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Fail.fail; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; @TestInstance(PER_CLASS) @Isolated @@ -158,14 +155,14 @@ public void tearDown() public void testGetDatabaseNames() { List databases = metadata.listSchemaNames(SESSION); - assertTrue(databases.contains(database.toLowerCase(ENGLISH))); + assertThat(databases.contains(database.toLowerCase(ENGLISH))).isTrue(); } @Test public void testGetTableNames() { List tables = metadata.listTables(SESSION, Optional.of(database)); - assertTrue(tables.contains(table)); + assertThat(tables.contains(table)).isTrue(); } @Test @@ -178,9 +175,9 @@ public void testGetTableNamesException() @Test public void testListUnknownSchema() { - assertNull(metadata.getTableHandle(SESSION, new SchemaTableName("totally_invalid_database_name", "dual"))); - assertEquals(metadata.listTables(SESSION, Optional.of("totally_invalid_database_name")), ImmutableList.of()); - assertEquals(metadata.listTableColumns(SESSION, new SchemaTablePrefix("totally_invalid_database_name", "dual")), ImmutableMap.of()); + assertThat(metadata.getTableHandle(SESSION, new SchemaTableName("totally_invalid_database_name", "dual"))).isNull(); + assertThat(metadata.listTables(SESSION, Optional.of("totally_invalid_database_name"))).isEqualTo(ImmutableList.of()); + assertThat(metadata.listTableColumns(SESSION, new SchemaTablePrefix("totally_invalid_database_name", "dual"))).isEqualTo(ImmutableMap.of()); } @Test @@ -214,29 +211,29 @@ public void testGetRecords() rowNumber++; String keyValue = cursor.getSlice(columnIndex.get("key")).toStringUtf8(); - assertTrue(keyValue.startsWith("key ")); + assertThat(keyValue.startsWith("key ")).isTrue(); int rowId = Integer.parseInt(keyValue.substring(4)); - assertEquals(keyValue, "key " + rowId); + assertThat(keyValue).isEqualTo("key " + rowId); - assertEquals(Bytes.toHexString(cursor.getSlice(columnIndex.get("typebytes")).getBytes()), format("0x%08X", rowId)); + assertThat(Bytes.toHexString(cursor.getSlice(columnIndex.get("typebytes")).getBytes())).isEqualTo(format("0x%08X", rowId)); // VARINT is returned as a string - assertEquals(cursor.getSlice(columnIndex.get("typeinteger")).toStringUtf8(), String.valueOf(rowId)); + assertThat(cursor.getSlice(columnIndex.get("typeinteger")).toStringUtf8()).isEqualTo(String.valueOf(rowId)); - assertEquals(cursor.getLong(columnIndex.get("typelong")), 1000 + rowId); + assertThat(cursor.getLong(columnIndex.get("typelong"))).isEqualTo(1000 + rowId); - assertEquals(trinoUuidToJavaUuid(cursor.getSlice(columnIndex.get("typeuuid"))).toString(), format("00000000-0000-0000-0000-%012d", rowId)); + assertThat(trinoUuidToJavaUuid(cursor.getSlice(columnIndex.get("typeuuid"))).toString()).isEqualTo(format("00000000-0000-0000-0000-%012d", rowId)); - assertEquals(cursor.getLong(columnIndex.get("typetimestamp")), packDateTimeWithZone(DATE.getTime(), UTC_KEY)); + assertThat(cursor.getLong(columnIndex.get("typetimestamp"))).isEqualTo(packDateTimeWithZone(DATE.getTime(), UTC_KEY)); long newCompletedBytes = cursor.getCompletedBytes(); - assertTrue(newCompletedBytes >= completedBytes); + assertThat(newCompletedBytes >= completedBytes).isTrue(); completedBytes = newCompletedBytes; } } } - assertEquals(rowNumber, 9); + assertThat(rowNumber).isEqualTo(9); } @Test @@ -286,7 +283,7 @@ public void testGetTupleType() rowNumber++; String keyValue = cursor.getSlice(columnIndex.get("key")).toStringUtf8(); - assertEquals(keyValue, Long.toString(rowNumber)); + assertThat(keyValue).isEqualTo(Long.toString(rowNumber)); SqlRow tupleValueBlock = (SqlRow) cursor.getObject(columnIndex.get("typetuple")); assertThat(tupleValueBlock.getFieldCount()).isEqualTo(3); @@ -299,12 +296,12 @@ public void testGetTupleType() assertThat(tupleArgumentTypes.get(2).getTrinoType().getLong(tupleValueBlock.getRawFieldBlock(2), rawIndex)).isEqualTo(Float.floatToRawIntBits(1.11f * rowNumber)); long newCompletedBytes = cursor.getCompletedBytes(); - assertTrue(newCompletedBytes >= completedBytes); + assertThat(newCompletedBytes >= completedBytes).isTrue(); completedBytes = newCompletedBytes; } } } - assertEquals(rowNumber, 2); + assertThat(rowNumber).isEqualTo(2); } @Test @@ -342,25 +339,25 @@ public void testGetUserDefinedType() SqlRow value = (SqlRow) cursor.getObject(columnIndex.get("typeudt")); int valueRawIndex = value.getRawIndex(); - assertEquals(key, "key"); - assertEquals(VARCHAR.getSlice(value.getRawFieldBlock(0), valueRawIndex).toStringUtf8(), "text"); - assertEquals(trinoUuidToJavaUuid(UUID.getSlice(value.getRawFieldBlock(1), valueRawIndex)).toString(), "01234567-0123-0123-0123-0123456789ab"); - assertEquals(INTEGER.getInt(value.getRawFieldBlock(2), valueRawIndex), -2147483648); - assertEquals(BIGINT.getLong(value.getRawFieldBlock(3), valueRawIndex), -9223372036854775808L); - assertEquals(VARBINARY.getSlice(value.getRawFieldBlock(4), valueRawIndex).toStringUtf8(), "01234"); - assertEquals(TIMESTAMP_MILLIS.getLong(value.getRawFieldBlock(5), valueRawIndex), 117964800000L); - assertEquals(VARCHAR.getSlice(value.getRawFieldBlock(6), valueRawIndex).toStringUtf8(), "ansi"); - assertTrue(BOOLEAN.getBoolean(value.getRawFieldBlock(7), valueRawIndex)); - assertEquals(DOUBLE.getDouble(value.getRawFieldBlock(8), valueRawIndex), 99999999999999997748809823456034029568D); - assertEquals(DOUBLE.getDouble(value.getRawFieldBlock(9), valueRawIndex), 4.9407e-324); - assertEquals(REAL.getObjectValue(SESSION, value.getRawFieldBlock(10), valueRawIndex), 1.4E-45f); - assertEquals(InetAddresses.toAddrString(InetAddress.getByAddress(IpAddressType.IPADDRESS.getSlice(value.getRawFieldBlock(11), valueRawIndex).getBytes())), "0.0.0.0"); - assertEquals(VARCHAR.getSlice(value.getRawFieldBlock(12), valueRawIndex).toStringUtf8(), "varchar"); - assertEquals(VARCHAR.getSlice(value.getRawFieldBlock(13), valueRawIndex).toStringUtf8(), "-9223372036854775808"); - assertEquals(trinoUuidToJavaUuid(UUID.getSlice(value.getRawFieldBlock(14), valueRawIndex)).toString(), "d2177dd0-eaa2-11de-a572-001b779c76e3"); - assertEquals(VARCHAR.getSlice(value.getRawFieldBlock(15), valueRawIndex).toStringUtf8(), "[\"list\"]"); - assertEquals(VARCHAR.getSlice(value.getRawFieldBlock(16), valueRawIndex).toStringUtf8(), "{\"map\":1}"); - assertEquals(VARCHAR.getSlice(value.getRawFieldBlock(17), valueRawIndex).toStringUtf8(), "[true]"); + assertThat(key).isEqualTo("key"); + assertThat(VARCHAR.getSlice(value.getRawFieldBlock(0), valueRawIndex).toStringUtf8()).isEqualTo("text"); + assertThat(trinoUuidToJavaUuid(UUID.getSlice(value.getRawFieldBlock(1), valueRawIndex)).toString()).isEqualTo("01234567-0123-0123-0123-0123456789ab"); + assertThat(INTEGER.getInt(value.getRawFieldBlock(2), valueRawIndex)).isEqualTo(-2147483648); + assertThat(BIGINT.getLong(value.getRawFieldBlock(3), valueRawIndex)).isEqualTo(-9223372036854775808L); + assertThat(VARBINARY.getSlice(value.getRawFieldBlock(4), valueRawIndex).toStringUtf8()).isEqualTo("01234"); + assertThat(TIMESTAMP_MILLIS.getLong(value.getRawFieldBlock(5), valueRawIndex)).isEqualTo(117964800000L); + assertThat(VARCHAR.getSlice(value.getRawFieldBlock(6), valueRawIndex).toStringUtf8()).isEqualTo("ansi"); + assertThat(BOOLEAN.getBoolean(value.getRawFieldBlock(7), valueRawIndex)).isTrue(); + assertThat(DOUBLE.getDouble(value.getRawFieldBlock(8), valueRawIndex)).isEqualTo(99999999999999997748809823456034029568D); + assertThat(DOUBLE.getDouble(value.getRawFieldBlock(9), valueRawIndex)).isEqualTo(4.9407e-324); + assertThat(REAL.getObjectValue(SESSION, value.getRawFieldBlock(10), valueRawIndex)).isEqualTo(1.4E-45f); + assertThat(InetAddresses.toAddrString(InetAddress.getByAddress(IpAddressType.IPADDRESS.getSlice(value.getRawFieldBlock(11), valueRawIndex).getBytes()))).isEqualTo("0.0.0.0"); + assertThat(VARCHAR.getSlice(value.getRawFieldBlock(12), valueRawIndex).toStringUtf8()).isEqualTo("varchar"); + assertThat(VARCHAR.getSlice(value.getRawFieldBlock(13), valueRawIndex).toStringUtf8()).isEqualTo("-9223372036854775808"); + assertThat(trinoUuidToJavaUuid(UUID.getSlice(value.getRawFieldBlock(14), valueRawIndex)).toString()).isEqualTo("d2177dd0-eaa2-11de-a572-001b779c76e3"); + assertThat(VARCHAR.getSlice(value.getRawFieldBlock(15), valueRawIndex).toStringUtf8()).isEqualTo("[\"list\"]"); + assertThat(VARCHAR.getSlice(value.getRawFieldBlock(16), valueRawIndex).toStringUtf8()).isEqualTo("{\"map\":1}"); + assertThat(VARCHAR.getSlice(value.getRawFieldBlock(17), valueRawIndex).toStringUtf8()).isEqualTo("[true]"); SqlRow tupleValue = value.getRawFieldBlock(18).getObject(valueRawIndex, SqlRow.class); assertThat(tupleValue.getFieldCount()).isEqualTo(1); assertThat(INTEGER.getInt(tupleValue.getRawFieldBlock(0), tupleValue.getRawIndex())).isEqualTo(123); @@ -369,12 +366,12 @@ public void testGetUserDefinedType() assertThat(INTEGER.getInt(udtValue.getRawFieldBlock(0), tupleValue.getRawIndex())).isEqualTo(999); long newCompletedBytes = cursor.getCompletedBytes(); - assertTrue(newCompletedBytes >= completedBytes); + assertThat(newCompletedBytes >= completedBytes).isTrue(); completedBytes = newCompletedBytes; } } } - assertEquals(rowNumber, 1); + assertThat(rowNumber).isEqualTo(1); } @SuppressWarnings({"ResultOfMethodCallIgnored", "CheckReturnValue"}) // we only check if the values are valid, we don't need them otherwise @@ -484,6 +481,6 @@ private CassandraPartition createPartition(long value1, long value2) private void assertNumberOfRows(SchemaTableName tableName, int rowsCount) { CassandraSession session = server.getSession(); - assertEquals(session.execute("SELECT COUNT(*) FROM " + tableName).all().get(0).getLong(0), rowsCount); + assertThat(session.execute("SELECT COUNT(*) FROM " + tableName).all().get(0).getLong(0)).isEqualTo(rowsCount); } } diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnectorTest.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnectorTest.java index aea76d462d85..892b7b9ad14b 100644 --- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnectorTest.java +++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnectorTest.java @@ -74,8 +74,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assumptions.abort; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; @TestInstance(PER_CLASS) public class TestCassandraConnectorTest @@ -308,7 +306,7 @@ public void testPushdownAllTypesPartitionKeyPredicate() ""; MaterializedResult result = execute(sql); - assertEquals(result.getRowCount(), 1); + assertThat(result.getRowCount()).isEqualTo(1); } } @@ -400,7 +398,7 @@ public void testPartitionKeyPredicate() ""; MaterializedResult result = execute(sql); - assertEquals(result.getRowCount(), 1); + assertThat(result.getRowCount()).isEqualTo(1); } } @@ -417,7 +415,7 @@ public void testTimestampPartitionKey() "WHERE c1 = TIMESTAMP '2017-04-01 11:21:59.001 UTC'", testCassandraTable.getTableName()); MaterializedResult result = execute(sql); - assertEquals(result.getRowCount(), 1); + assertThat(result.getRowCount()).isEqualTo(1); } } @@ -624,23 +622,23 @@ public void testClusteringPredicates() rowNumber -> format("'clust_three_%d'", rowNumber), rowNumber -> "null")))) { String sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE key='key_1' AND clust_one='clust_one'"; - assertEquals(execute(sql).getRowCount(), 1); + assertThat(execute(sql).getRowCount()).isEqualTo(1); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE key IN ('key_1','key_2') AND clust_one='clust_one'"; - assertEquals(execute(sql).getRowCount(), 2); + assertThat(execute(sql).getRowCount()).isEqualTo(2); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE key='key_1' AND clust_one!='clust_one'"; - assertEquals(execute(sql).getRowCount(), 0); + assertThat(execute(sql).getRowCount()).isEqualTo(0); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE key IN ('key_1','key_2','key_3','key_4') AND clust_one='clust_one' AND clust_two>'clust_two_1'"; - assertEquals(execute(sql).getRowCount(), 3); + assertThat(execute(sql).getRowCount()).isEqualTo(3); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE key IN ('key_1','key_2') AND clust_one='clust_one' AND " + "((clust_two='clust_two_1') OR (clust_two='clust_two_2'))"; - assertEquals(execute(sql).getRowCount(), 2); + assertThat(execute(sql).getRowCount()).isEqualTo(2); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE key IN ('key_1','key_2') AND clust_one='clust_one' AND " + "((clust_two='clust_two_1' AND clust_three='clust_three_1') OR (clust_two='clust_two_2' AND clust_three='clust_three_2'))"; - assertEquals(execute(sql).getRowCount(), 2); + assertThat(execute(sql).getRowCount()).isEqualTo(2); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE key IN ('key_1','key_2') AND clust_one='clust_one' AND clust_three='clust_three_1'"; - assertEquals(execute(sql).getRowCount(), 1); + assertThat(execute(sql).getRowCount()).isEqualTo(1); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE key IN ('key_1','key_2') AND clust_one='clust_one' AND clust_two IN ('clust_two_1','clust_two_2')"; - assertEquals(execute(sql).getRowCount(), 2); + assertThat(execute(sql).getRowCount()).isEqualTo(2); } } @@ -665,26 +663,26 @@ public void testMultiplePartitionClusteringPredicates() rowNumber -> "null")))) { String partitionInPredicates = " partition_one IN ('partition_one_1','partition_one_2') AND partition_two IN ('partition_two_1','partition_two_2') "; String sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE partition_one='partition_one_1' AND partition_two='partition_two_1' AND clust_one='clust_one'"; - assertEquals(execute(sql).getRowCount(), 1); + assertThat(execute(sql).getRowCount()).isEqualTo(1); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE " + partitionInPredicates + " AND clust_one='clust_one'"; - assertEquals(execute(sql).getRowCount(), 2); + assertThat(execute(sql).getRowCount()).isEqualTo(2); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE partition_one='partition_one_1' AND partition_two='partition_two_1' AND clust_one!='clust_one'"; - assertEquals(execute(sql).getRowCount(), 0); + assertThat(execute(sql).getRowCount()).isEqualTo(0); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE " + "partition_one IN ('partition_one_1','partition_one_2','partition_one_3','partition_one_4') AND " + "partition_two IN ('partition_two_1','partition_two_2','partition_two_3','partition_two_4') AND " + "clust_one='clust_one' AND clust_two>'clust_two_1'"; - assertEquals(execute(sql).getRowCount(), 3); + assertThat(execute(sql).getRowCount()).isEqualTo(3); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE " + partitionInPredicates + " AND clust_one='clust_one' AND " + "((clust_two='clust_two_1') OR (clust_two='clust_two_2'))"; - assertEquals(execute(sql).getRowCount(), 2); + assertThat(execute(sql).getRowCount()).isEqualTo(2); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE " + partitionInPredicates + " AND clust_one='clust_one' AND " + "((clust_two='clust_two_1' AND clust_three='clust_three_1') OR (clust_two='clust_two_2' AND clust_three='clust_three_2'))"; - assertEquals(execute(sql).getRowCount(), 2); + assertThat(execute(sql).getRowCount()).isEqualTo(2); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE " + partitionInPredicates + " AND clust_one='clust_one' AND clust_three='clust_three_1'"; - assertEquals(execute(sql).getRowCount(), 1); + assertThat(execute(sql).getRowCount()).isEqualTo(1); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE " + partitionInPredicates + " AND clust_one='clust_one' AND clust_two IN ('clust_two_1','clust_two_2')"; - assertEquals(execute(sql).getRowCount(), 2); + assertThat(execute(sql).getRowCount()).isEqualTo(2); } } @@ -706,11 +704,11 @@ public void testClusteringKeyOnlyPushdown() rowNumber -> format("'clust_three_%d'", rowNumber), rowNumber -> "null")))) { String sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE clust_one='clust_one'"; - assertEquals(execute(sql).getRowCount(), 9); + assertThat(execute(sql).getRowCount()).isEqualTo(9); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE clust_one='clust_one' AND clust_two='clust_two_2'"; - assertEquals(execute(sql).getRowCount(), 1); + assertThat(execute(sql).getRowCount()).isEqualTo(1); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE clust_one='clust_one' AND clust_two='clust_two_2' AND clust_three='clust_three_2'"; - assertEquals(execute(sql).getRowCount(), 1); + assertThat(execute(sql).getRowCount()).isEqualTo(1); } try (TestCassandraTable testCassandraTable = testTable( @@ -731,29 +729,29 @@ public void testClusteringKeyOnlyPushdown() // for the smaller table (<200 partitions by default) connector fetches all the partitions id // and the partitioned patch is being followed String sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE clust_one='clust_one' AND clust_two='clust_two_2'"; - assertEquals(execute(sql).getRowCount(), 1); + assertThat(execute(sql).getRowCount()).isEqualTo(1); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE clust_one='clust_one' AND clust_two='clust_two_2' AND clust_three='clust_three_2'"; - assertEquals(execute(sql).getRowCount(), 1); + assertThat(execute(sql).getRowCount()).isEqualTo(1); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE clust_one='clust_one' AND clust_two='clust_two_2' AND clust_three IN ('clust_three_1', 'clust_three_2', 'clust_three_3')"; - assertEquals(execute(sql).getRowCount(), 1); + assertThat(execute(sql).getRowCount()).isEqualTo(1); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE clust_one='clust_one' AND clust_two IN ('clust_two_1','clust_two_2') AND clust_three IN ('clust_three_1', 'clust_three_2', 'clust_three_3')"; - assertEquals(execute(sql).getRowCount(), 2); + assertThat(execute(sql).getRowCount()).isEqualTo(2); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE clust_one='clust_one' AND clust_two > 'clust_two_998'"; - assertEquals(execute(sql).getRowCount(), 1); + assertThat(execute(sql).getRowCount()).isEqualTo(1); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE clust_one='clust_one' AND clust_two > 'clust_two_997' AND clust_two < 'clust_two_999'"; - assertEquals(execute(sql).getRowCount(), 1); + assertThat(execute(sql).getRowCount()).isEqualTo(1); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE clust_one='clust_one' AND clust_two IN ('clust_two_1','clust_two_2') AND clust_three > 'clust_three_998'"; - assertEquals(execute(sql).getRowCount(), 0); + assertThat(execute(sql).getRowCount()).isEqualTo(0); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE clust_one='clust_one' AND clust_two IN ('clust_two_1','clust_two_2') AND clust_three < 'clust_three_3'"; - assertEquals(execute(sql).getRowCount(), 2); + assertThat(execute(sql).getRowCount()).isEqualTo(2); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE clust_one='clust_one' AND clust_two IN ('clust_two_1','clust_two_2') AND clust_three > 'clust_three_1' AND clust_three < 'clust_three_3'"; - assertEquals(execute(sql).getRowCount(), 1); + assertThat(execute(sql).getRowCount()).isEqualTo(1); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE clust_one='clust_one' AND clust_two IN ('clust_two_1','clust_two_2','clust_two_3') AND clust_two < 'clust_two_2'"; - assertEquals(execute(sql).getRowCount(), 1); + assertThat(execute(sql).getRowCount()).isEqualTo(1); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE clust_one='clust_one' AND clust_two IN ('clust_two_997','clust_two_998','clust_two_999') AND clust_two > 'clust_two_998'"; - assertEquals(execute(sql).getRowCount(), 1); + assertThat(execute(sql).getRowCount()).isEqualTo(1); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE clust_one='clust_one' AND clust_two IN ('clust_two_1','clust_two_2','clust_two_3') AND clust_two = 'clust_two_2'"; - assertEquals(execute(sql).getRowCount(), 1); + assertThat(execute(sql).getRowCount()).isEqualTo(1); } } @@ -775,13 +773,13 @@ public void testNotEqualPredicateOnClusteringColumn() rowNumber -> format("%d", Timestamp.from(TIMESTAMP_VALUE.toInstant()).getTime() + rowNumber * 10), rowNumber -> "null")))) { String sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE key='key_1' AND clust_one != 'clust_one'"; - assertEquals(execute(sql).getRowCount(), 0); + assertThat(execute(sql).getRowCount()).isEqualTo(0); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE key='key_1' AND clust_one='clust_one' AND clust_two != 2"; - assertEquals(execute(sql).getRowCount(), 3); + assertThat(execute(sql).getRowCount()).isEqualTo(3); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE key='key_1' AND clust_one='clust_one' AND clust_two >= 2 AND clust_two != 3"; - assertEquals(execute(sql).getRowCount(), 2); + assertThat(execute(sql).getRowCount()).isEqualTo(2); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE key='key_1' AND clust_one='clust_one' AND clust_two > 2 AND clust_two != 3"; - assertEquals(execute(sql).getRowCount(), 1); + assertThat(execute(sql).getRowCount()).isEqualTo(1); } } @@ -803,27 +801,27 @@ public void testClusteringKeyPushdownInequality() rowNumber -> format("%d", Timestamp.from(TIMESTAMP_VALUE.toInstant()).getTime() + rowNumber * 10), rowNumber -> "null")))) { String sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE key='key_1' AND clust_one='clust_one'"; - assertEquals(execute(sql).getRowCount(), 4); + assertThat(execute(sql).getRowCount()).isEqualTo(4); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE key='key_1' AND clust_one='clust_one' AND clust_two=2"; - assertEquals(execute(sql).getRowCount(), 1); + assertThat(execute(sql).getRowCount()).isEqualTo(1); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE key='key_1' AND clust_one='clust_one' AND clust_two=2 AND clust_three = timestamp '1970-01-01 03:04:05.020Z'"; - assertEquals(execute(sql).getRowCount(), 1); + assertThat(execute(sql).getRowCount()).isEqualTo(1); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE key='key_1' AND clust_one='clust_one' AND clust_two=2 AND clust_three = timestamp '1970-01-01 03:04:05.010Z'"; - assertEquals(execute(sql).getRowCount(), 0); + assertThat(execute(sql).getRowCount()).isEqualTo(0); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE key='key_1' AND clust_one='clust_one' AND clust_two IN (1,2)"; - assertEquals(execute(sql).getRowCount(), 2); + assertThat(execute(sql).getRowCount()).isEqualTo(2); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE key='key_1' AND clust_one='clust_one' AND clust_two > 1 AND clust_two < 3"; - assertEquals(execute(sql).getRowCount(), 1); + assertThat(execute(sql).getRowCount()).isEqualTo(1); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE key='key_1' AND clust_one='clust_one' AND clust_two=2 AND clust_three >= timestamp '1970-01-01 03:04:05.010Z' AND clust_three <= timestamp '1970-01-01 03:04:05.020Z'"; - assertEquals(execute(sql).getRowCount(), 1); + assertThat(execute(sql).getRowCount()).isEqualTo(1); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE key='key_1' AND clust_one='clust_one' AND clust_two IN (1,2) AND clust_three >= timestamp '1970-01-01 03:04:05.010Z' AND clust_three <= timestamp '1970-01-01 03:04:05.020Z'"; - assertEquals(execute(sql).getRowCount(), 2); + assertThat(execute(sql).getRowCount()).isEqualTo(2); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE key='key_1' AND clust_one='clust_one' AND clust_two IN (1,2,3) AND clust_two < 2"; - assertEquals(execute(sql).getRowCount(), 1); + assertThat(execute(sql).getRowCount()).isEqualTo(1); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE key='key_1' AND clust_one='clust_one' AND clust_two IN (1,2,3) AND clust_two > 2"; - assertEquals(execute(sql).getRowCount(), 1); + assertThat(execute(sql).getRowCount()).isEqualTo(1); sql = "SELECT * FROM " + testCassandraTable.getTableName() + " WHERE key='key_1' AND clust_one='clust_one' AND clust_two IN (1,2,3) AND clust_two = 2"; - assertEquals(execute(sql).getRowCount(), 1); + assertThat(execute(sql).getRowCount()).isEqualTo(1); } } @@ -850,7 +848,7 @@ public void testUpperCaseNameUnescapedInCassandra() execute("INSERT INTO keyspace_1.table_1 (column_1) VALUES (1)"); - assertEquals(execute("SELECT column_1 FROM cassandra.keyspace_1.table_1").getRowCount(), 1); + assertThat(execute("SELECT column_1 FROM cassandra.keyspace_1.table_1").getRowCount()).isEqualTo(1); assertUpdate("DROP TABLE cassandra.keyspace_1.table_1"); // when an identifier is unquoted the lowercase and uppercase spelling may be used interchangeable @@ -880,7 +878,7 @@ public void testUppercaseNameEscaped() execute("INSERT INTO \"KEYSPACE_2\".\"TABLE_2\" (\"COLUMN_2\") VALUES (1)"); - assertEquals(execute("SELECT column_2 FROM cassandra.keyspace_2.table_2").getRowCount(), 1); + assertThat(execute("SELECT column_2 FROM cassandra.keyspace_2.table_2").getRowCount()).isEqualTo(1); assertUpdate("DROP TABLE cassandra.keyspace_2.table_2"); // when an identifier is unquoted the lowercase and uppercase spelling may be used interchangeable @@ -1092,16 +1090,13 @@ public void testNestedCollectionType() .build(), new Duration(1, MINUTES)); session.execute("INSERT INTO keyspace_test_nested_collection.table_set (column_5, nested_collection) VALUES (1, {{1, 2, 3}})"); - assertEquals(execute("SELECT nested_collection FROM cassandra.keyspace_test_nested_collection.table_set").getMaterializedRows().get(0), - new MaterializedRow(DEFAULT_PRECISION, "[[1,2,3]]")); + assertThat(execute("SELECT nested_collection FROM cassandra.keyspace_test_nested_collection.table_set").getMaterializedRows().get(0)).isEqualTo(new MaterializedRow(DEFAULT_PRECISION, "[[1,2,3]]")); session.execute("INSERT INTO keyspace_test_nested_collection.table_list (column_5, nested_collection) VALUES (1, [[4, 5, 6]])"); - assertEquals(execute("SELECT nested_collection FROM cassandra.keyspace_test_nested_collection.table_list").getMaterializedRows().get(0), - new MaterializedRow(DEFAULT_PRECISION, "[[4,5,6]]")); + assertThat(execute("SELECT nested_collection FROM cassandra.keyspace_test_nested_collection.table_list").getMaterializedRows().get(0)).isEqualTo(new MaterializedRow(DEFAULT_PRECISION, "[[4,5,6]]")); session.execute("INSERT INTO keyspace_test_nested_collection.table_map (column_5, nested_collection) VALUES (1, {7:{8:9}})"); - assertEquals(execute("SELECT nested_collection FROM cassandra.keyspace_test_nested_collection.table_map").getMaterializedRows().get(0), - new MaterializedRow(DEFAULT_PRECISION, "{7:{8:9}}")); + assertThat(execute("SELECT nested_collection FROM cassandra.keyspace_test_nested_collection.table_map").getMaterializedRows().get(0)).isEqualTo(new MaterializedRow(DEFAULT_PRECISION, "{7:{8:9}}")); session.execute("DROP KEYSPACE keyspace_test_nested_collection"); } @@ -1137,7 +1132,7 @@ public void testAllTypesInsert() String sql = "SELECT key, typeuuid, typeinteger, typelong, typebytes, typetimestamp, typeansi, typeboolean, typedecimal, " + "typedouble, typefloat, typeinet, typevarchar, typevarint, typetimeuuid, typelist, typemap, typeset" + " FROM " + testCassandraTable.getTableName(); - assertEquals(execute(sql).getRowCount(), 0); + assertThat(execute(sql).getRowCount()).isEqualTo(0); // TODO Following types are not supported now. We need to change null into the value after fixing it // blob, frozen>, list, map, set, decimal, varint @@ -1184,8 +1179,8 @@ public void testAllTypesInsert() MaterializedResult result = execute(sql); int rowCount = result.getRowCount(); - assertEquals(rowCount, 1); - assertEquals(result.getMaterializedRows().get(0), new MaterializedRow(DEFAULT_PRECISION, + assertThat(rowCount).isEqualTo(1); + assertThat(result.getMaterializedRows().get(0)).isEqualTo(new MaterializedRow(DEFAULT_PRECISION, "key1", java.util.UUID.fromString("12151fd2-7586-11e9-8f9e-2a86e4085a59"), 1, @@ -1217,8 +1212,8 @@ public void testAllTypesInsert() " FROM " + testCassandraTable.getTableName() + " WHERE key = 'key2'"; result = execute(sql); rowCount = result.getRowCount(); - assertEquals(rowCount, 1); - assertEquals(result.getMaterializedRows().get(0), new MaterializedRow(DEFAULT_PRECISION, + assertThat(rowCount).isEqualTo(1); + assertThat(result.getMaterializedRows().get(0)).isEqualTo(new MaterializedRow(DEFAULT_PRECISION, "key2", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null)); // insert into only a subset of columns @@ -1230,8 +1225,8 @@ public void testAllTypesInsert() " FROM " + testCassandraTable.getTableName() + " WHERE key = 'key3'"; result = execute(sql); rowCount = result.getRowCount(); - assertEquals(rowCount, 1); - assertEquals(result.getMaterializedRows().get(0), new MaterializedRow(DEFAULT_PRECISION, + assertThat(rowCount).isEqualTo(1); + assertThat(result.getMaterializedRows().get(0)).isEqualTo(new MaterializedRow(DEFAULT_PRECISION, "key3", null, 999, null, null, null, "ansi", false, null, null, null, null, null, null, null, null, null, null)); } } @@ -1264,40 +1259,40 @@ public void testDelete() "1, 2, 'clust_one_3', null", "2, 2, 'clust_one_1', null"))) { String keyspaceAndTable = testCassandraTable.getTableName(); - assertEquals(execute("SELECT * FROM " + keyspaceAndTable).getRowCount(), 15); + assertThat(execute("SELECT * FROM " + keyspaceAndTable).getRowCount()).isEqualTo(15); // error assertThatThrownBy(() -> execute("DELETE FROM " + keyspaceAndTable)) .isInstanceOf(RuntimeException.class); - assertEquals(execute("SELECT * FROM " + keyspaceAndTable).getRowCount(), 15); + assertThat(execute("SELECT * FROM " + keyspaceAndTable).getRowCount()).isEqualTo(15); String whereClusteringKeyOnly = " WHERE clust_one='clust_one_2'"; assertThatThrownBy(() -> execute("DELETE FROM " + keyspaceAndTable + whereClusteringKeyOnly)) .isInstanceOf(RuntimeException.class); - assertEquals(execute("SELECT * FROM " + keyspaceAndTable).getRowCount(), 15); + assertThat(execute("SELECT * FROM " + keyspaceAndTable).getRowCount()).isEqualTo(15); String whereMultiplePartitionKeyWithClusteringKey = " WHERE " + " (partition_one=1 AND partition_two=1 AND clust_one='clust_one_1') OR " + " (partition_one=1 AND partition_two=2 AND clust_one='clust_one_2') "; assertThatThrownBy(() -> execute("DELETE FROM " + keyspaceAndTable + whereMultiplePartitionKeyWithClusteringKey)) .isInstanceOf(RuntimeException.class); - assertEquals(execute("SELECT * FROM " + keyspaceAndTable).getRowCount(), 15); + assertThat(execute("SELECT * FROM " + keyspaceAndTable).getRowCount()).isEqualTo(15); // success String wherePrimaryKey = " WHERE partition_one=3 AND partition_two=3 AND clust_one='clust_one_3'"; execute("DELETE FROM " + keyspaceAndTable + wherePrimaryKey); - assertEquals(execute("SELECT * FROM " + keyspaceAndTable).getRowCount(), 14); - assertEquals(execute("SELECT * FROM " + keyspaceAndTable + wherePrimaryKey).getRowCount(), 0); + assertThat(execute("SELECT * FROM " + keyspaceAndTable).getRowCount()).isEqualTo(14); + assertThat(execute("SELECT * FROM " + keyspaceAndTable + wherePrimaryKey).getRowCount()).isEqualTo(0); String wherePartitionKey = " WHERE partition_one=2 AND partition_two=2"; execute("DELETE FROM " + keyspaceAndTable + wherePartitionKey); - assertEquals(execute("SELECT * FROM " + keyspaceAndTable).getRowCount(), 12); - assertEquals(execute("SELECT * FROM " + keyspaceAndTable + wherePartitionKey).getRowCount(), 0); + assertThat(execute("SELECT * FROM " + keyspaceAndTable).getRowCount()).isEqualTo(12); + assertThat(execute("SELECT * FROM " + keyspaceAndTable + wherePartitionKey).getRowCount()).isEqualTo(0); String whereMultiplePartitionKey = " WHERE (partition_one=1 AND partition_two=1) OR (partition_one=1 AND partition_two=2)"; execute("DELETE FROM " + keyspaceAndTable + whereMultiplePartitionKey); - assertEquals(execute("SELECT * FROM " + keyspaceAndTable).getRowCount(), 6); - assertEquals(execute("SELECT * FROM " + keyspaceAndTable + whereMultiplePartitionKey).getRowCount(), 0); + assertThat(execute("SELECT * FROM " + keyspaceAndTable).getRowCount()).isEqualTo(6); + assertThat(execute("SELECT * FROM " + keyspaceAndTable + whereMultiplePartitionKey).getRowCount()).isEqualTo(0); } } @@ -1439,17 +1434,17 @@ public void testNativeQueryCaseSensitivity() public void testNativeQueryCreateTableFailure() { String tableName = "test_create" + randomNameSuffix(); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); assertThatThrownBy(() -> query("SELECT * FROM TABLE(cassandra.system.query(query => 'CREATE TABLE tpch." + tableName + "(col INT PRIMARY KEY)'))")) .hasMessage("Handle doesn't have columns info"); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); } @Test public void testNativeQueryPreparingStatementFailure() { String tableName = "test_insert" + randomNameSuffix(); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); assertThatThrownBy(() -> query("SELECT * FROM TABLE(cassandra.system.query(query => 'INSERT INTO tpch." + tableName + "(col) VALUES (1)'))")) .hasMessage("Cannot get column definition") .hasStackTraceContaining("unconfigured table"); @@ -1533,8 +1528,8 @@ private void assertSelect(String tableName) MaterializedResult result = execute(sql); int rowCount = result.getRowCount(); - assertEquals(rowCount, 9); - assertEquals(result.getTypes(), ImmutableList.of( + assertThat(rowCount).isEqualTo(9); + assertThat(result.getTypes()).isEqualTo(ImmutableList.of( createUnboundedVarcharType(), UUID, INTEGER, @@ -1559,7 +1554,7 @@ private void assertSelect(String tableName) .collect(toList()); for (int rowNumber = 1; rowNumber <= rowCount; rowNumber++) { - assertEquals(sortedRows.get(rowNumber - 1), new MaterializedRow(DEFAULT_PRECISION, + assertThat(sortedRows.get(rowNumber - 1)).isEqualTo(new MaterializedRow(DEFAULT_PRECISION, "key " + rowNumber, java.util.UUID.fromString(format("00000000-0000-0000-0000-%012d", rowNumber)), rowNumber, diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraSplit.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraSplit.java index 8245e79dcc0b..5319148f4b19 100644 --- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraSplit.java +++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraSplit.java @@ -18,7 +18,7 @@ import io.trino.spi.HostAddress; import org.junit.jupiter.api.Test; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestCassandraSplit { @@ -36,8 +36,8 @@ public void testJsonRoundTrip() String json = codec.toJson(expected); CassandraSplit actual = codec.fromJson(json); - assertEquals(actual.getSplitCondition(), expected.getSplitCondition()); - assertEquals(actual.getAddresses(), expected.getAddresses()); + assertThat(actual.getSplitCondition()).isEqualTo(expected.getSplitCondition()); + assertThat(actual.getAddresses()).isEqualTo(expected.getAddresses()); } @Test @@ -48,12 +48,12 @@ public void testWhereClause() CassandraPartition.UNPARTITIONED_ID, "token(k) >= 0 AND token(k) <= 2", addresses); - assertEquals(split.getWhereClause(), " WHERE token(k) >= 0 AND token(k) <= 2"); + assertThat(split.getWhereClause()).isEqualTo(" WHERE token(k) >= 0 AND token(k) <= 2"); split = new CassandraSplit( "key = 123", null, addresses); - assertEquals(split.getWhereClause(), " WHERE key = 123"); + assertThat(split.getWhereClause()).isEqualTo(" WHERE key = 123"); } } diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraTableHandle.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraTableHandle.java index bc7bf9c8b085..25c9df7eb67b 100644 --- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraTableHandle.java +++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraTableHandle.java @@ -16,7 +16,7 @@ import io.airlift.json.JsonCodec; import org.junit.jupiter.api.Test; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestCassandraTableHandle { @@ -30,7 +30,7 @@ public void testRoundTripNamedRelationHandle() String json = codec.toJson(expected); CassandraTableHandle actual = codec.fromJson(json); - assertEquals(actual.getRequiredNamedRelation().getSchemaTableName(), expected.getRequiredNamedRelation().getSchemaTableName()); + assertThat(actual.getRequiredNamedRelation().getSchemaTableName()).isEqualTo(expected.getRequiredNamedRelation().getSchemaTableName()); } @Test @@ -41,6 +41,6 @@ public void testRoundTripQueryRelationHandle() String json = codec.toJson(expected); CassandraTableHandle actual = codec.fromJson(json); - assertEquals(actual.getRelationHandle(), new CassandraQueryRelationHandle("SELECT * FROM tpch.region")); + assertThat(actual.getRelationHandle()).isEqualTo(new CassandraQueryRelationHandle("SELECT * FROM tpch.region")); } } diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraTokenSplitManager.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraTokenSplitManager.java index 549fe159f184..ecbc83ad3952 100644 --- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraTokenSplitManager.java +++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraTokenSplitManager.java @@ -28,7 +28,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -70,16 +69,16 @@ public void testPartitionCountOverride() server.refreshSizeEstimates(KEYSPACE, tableName); CassandraTokenSplitManager onlyConfigSplitsPerNode = new CassandraTokenSplitManager(session, SPLIT_SIZE, Optional.of(12_345L)); - assertEquals(onlyConfigSplitsPerNode.getTotalPartitionsCount(KEYSPACE, tableName, Optional.empty()), 12_345L); + assertThat(onlyConfigSplitsPerNode.getTotalPartitionsCount(KEYSPACE, tableName, Optional.empty())).isEqualTo(12_345L); CassandraTokenSplitManager onlySessionSplitsPerNode = new CassandraTokenSplitManager(session, SPLIT_SIZE, Optional.empty()); - assertEquals(onlySessionSplitsPerNode.getTotalPartitionsCount(KEYSPACE, tableName, Optional.of(67_890L)), 67_890L); + assertThat(onlySessionSplitsPerNode.getTotalPartitionsCount(KEYSPACE, tableName, Optional.of(67_890L))).isEqualTo(67_890L); CassandraTokenSplitManager sessionOverrideConfig = new CassandraTokenSplitManager(session, SPLIT_SIZE, Optional.of(12_345L)); - assertEquals(sessionOverrideConfig.getTotalPartitionsCount(KEYSPACE, tableName, Optional.of(67_890L)), 67_890L); + assertThat(sessionOverrideConfig.getTotalPartitionsCount(KEYSPACE, tableName, Optional.of(67_890L))).isEqualTo(67_890L); CassandraTokenSplitManager defaultSplitManager = new CassandraTokenSplitManager(session, SPLIT_SIZE, Optional.empty()); - assertEquals(defaultSplitManager.getTotalPartitionsCount(KEYSPACE, tableName, Optional.empty()), 0); + assertThat(defaultSplitManager.getTotalPartitionsCount(KEYSPACE, tableName, Optional.empty())).isEqualTo(0); } @Test diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraTypeManager.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraTypeManager.java index 7ddb7f30b68e..115c8903e73c 100644 --- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraTypeManager.java +++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraTypeManager.java @@ -23,20 +23,20 @@ import java.io.IOException; import static io.trino.plugin.cassandra.CassandraTestingUtils.CASSANDRA_TYPE_MANAGER; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestCassandraTypeManager { @Test public void testJsonArrayEncoding() { - assertTrue(isValidJson(CASSANDRA_TYPE_MANAGER.buildArrayValue(Lists.newArrayList("one", "two", "three\""), DataTypes.TEXT))); - assertTrue(isValidJson(CASSANDRA_TYPE_MANAGER.buildArrayValue(Lists.newArrayList(1, 2, 3), DataTypes.INT))); - assertTrue(isValidJson(CASSANDRA_TYPE_MANAGER.buildArrayValue(Lists.newArrayList(100000L, 200000000L, 3000000000L), DataTypes.BIGINT))); - assertTrue(isValidJson(CASSANDRA_TYPE_MANAGER.buildArrayValue(Lists.newArrayList(1.0, 2.0, 3.0), DataTypes.DOUBLE))); - assertTrue(isValidJson(CASSANDRA_TYPE_MANAGER.buildArrayValue(Lists.newArrayList((short) -32768, (short) 0, (short) 32767), DataTypes.SMALLINT))); - assertTrue(isValidJson(CASSANDRA_TYPE_MANAGER.buildArrayValue(Lists.newArrayList((byte) -128, (byte) 0, (byte) 127), DataTypes.TINYINT))); - assertTrue(isValidJson(CASSANDRA_TYPE_MANAGER.buildArrayValue(Lists.newArrayList("1970-01-01", "5555-06-15", "9999-12-31"), DataTypes.DATE))); + assertThat(isValidJson(CASSANDRA_TYPE_MANAGER.buildArrayValue(Lists.newArrayList("one", "two", "three\""), DataTypes.TEXT))).isTrue(); + assertThat(isValidJson(CASSANDRA_TYPE_MANAGER.buildArrayValue(Lists.newArrayList(1, 2, 3), DataTypes.INT))).isTrue(); + assertThat(isValidJson(CASSANDRA_TYPE_MANAGER.buildArrayValue(Lists.newArrayList(100000L, 200000000L, 3000000000L), DataTypes.BIGINT))).isTrue(); + assertThat(isValidJson(CASSANDRA_TYPE_MANAGER.buildArrayValue(Lists.newArrayList(1.0, 2.0, 3.0), DataTypes.DOUBLE))).isTrue(); + assertThat(isValidJson(CASSANDRA_TYPE_MANAGER.buildArrayValue(Lists.newArrayList((short) -32768, (short) 0, (short) 32767), DataTypes.SMALLINT))).isTrue(); + assertThat(isValidJson(CASSANDRA_TYPE_MANAGER.buildArrayValue(Lists.newArrayList((byte) -128, (byte) 0, (byte) 127), DataTypes.TINYINT))).isTrue(); + assertThat(isValidJson(CASSANDRA_TYPE_MANAGER.buildArrayValue(Lists.newArrayList("1970-01-01", "5555-06-15", "9999-12-31"), DataTypes.DATE))).isTrue(); } private static void continueWhileNotNull(JsonParser parser, JsonToken token) diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestJsonCassandraHandles.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestJsonCassandraHandles.java index 9bcc9d9e85de..a4c430a0b505 100644 --- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestJsonCassandraHandles.java +++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestJsonCassandraHandles.java @@ -31,8 +31,7 @@ import static io.trino.testing.QueryAssertions.assertEqualsIgnoreOrder; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestJsonCassandraHandles { @@ -103,7 +102,7 @@ public void testTableHandleSerialize() { CassandraTableHandle tableHandle = new CassandraTableHandle(new CassandraNamedRelationHandle("cassandra_schema", "cassandra_table")); - assertTrue(OBJECT_MAPPER.canSerialize(CassandraTableHandle.class)); + assertThat(OBJECT_MAPPER.canSerialize(CassandraTableHandle.class)).isTrue(); String json = OBJECT_MAPPER.writeValueAsString(tableHandle); testJsonEquals(json, TABLE_HANDLE_AS_MAP); } @@ -113,7 +112,7 @@ public void testTable2HandleSerialize() throws Exception { CassandraTableHandle tableHandle = new CassandraTableHandle(new CassandraNamedRelationHandle("cassandra_schema", "cassandra_table", PARTITIONS, "clusteringKey1 = 33")); - assertTrue(OBJECT_MAPPER.canSerialize(CassandraTableHandle.class)); + assertThat(OBJECT_MAPPER.canSerialize(CassandraTableHandle.class)).isTrue(); String json = OBJECT_MAPPER.writeValueAsString(tableHandle); testJsonEquals(json, TABLE2_HANDLE_AS_MAP); } @@ -126,10 +125,10 @@ public void testTableHandleDeserialize() CassandraNamedRelationHandle tableHandle = (OBJECT_MAPPER.readValue(json, CassandraTableHandle.class)).getRequiredNamedRelation(); - assertEquals(tableHandle.getSchemaName(), "cassandra_schema"); - assertEquals(tableHandle.getTableName(), "cassandra_table"); - assertEquals(tableHandle.getSchemaTableName(), new SchemaTableName("cassandra_schema", "cassandra_table")); - assertEquals(tableHandle.getClusteringKeyPredicates(), ""); + assertThat(tableHandle.getSchemaName()).isEqualTo("cassandra_schema"); + assertThat(tableHandle.getTableName()).isEqualTo("cassandra_table"); + assertThat(tableHandle.getSchemaTableName()).isEqualTo(new SchemaTableName("cassandra_schema", "cassandra_table")); + assertThat(tableHandle.getClusteringKeyPredicates()).isEqualTo(""); } @Test @@ -140,11 +139,11 @@ public void testTable2HandleDeserialize() CassandraNamedRelationHandle tableHandle = (OBJECT_MAPPER.readValue(json, CassandraTableHandle.class)).getRequiredNamedRelation(); - assertEquals(tableHandle.getSchemaName(), "cassandra_schema"); - assertEquals(tableHandle.getTableName(), "cassandra_table"); - assertEquals(tableHandle.getSchemaTableName(), new SchemaTableName("cassandra_schema", "cassandra_table")); - assertEquals(tableHandle.getPartitions(), PARTITIONS); - assertEquals(tableHandle.getClusteringKeyPredicates(), "clusteringKey1 = 33"); + assertThat(tableHandle.getSchemaName()).isEqualTo("cassandra_schema"); + assertThat(tableHandle.getTableName()).isEqualTo("cassandra_table"); + assertThat(tableHandle.getSchemaTableName()).isEqualTo(new SchemaTableName("cassandra_schema", "cassandra_table")); + assertThat(tableHandle.getPartitions()).isEqualTo(PARTITIONS); + assertThat(tableHandle.getClusteringKeyPredicates()).isEqualTo("clusteringKey1 = 33"); } @Test @@ -153,7 +152,7 @@ public void testColumnHandleSerialize() { CassandraColumnHandle columnHandle = new CassandraColumnHandle("column", 42, CassandraTypes.BIGINT, false, true, false, false); - assertTrue(OBJECT_MAPPER.canSerialize(CassandraColumnHandle.class)); + assertThat(OBJECT_MAPPER.canSerialize(CassandraColumnHandle.class)).isTrue(); String json = OBJECT_MAPPER.writeValueAsString(columnHandle); testJsonEquals(json, COLUMN_HANDLE_AS_MAP); } @@ -171,7 +170,7 @@ public void testColumn2HandleSerialize() false, false); - assertTrue(OBJECT_MAPPER.canSerialize(CassandraColumnHandle.class)); + assertThat(OBJECT_MAPPER.canSerialize(CassandraColumnHandle.class)).isTrue(); String json = OBJECT_MAPPER.writeValueAsString(columnHandle); testJsonEquals(json, COLUMN2_HANDLE_AS_MAP); } @@ -184,11 +183,11 @@ public void testColumnHandleDeserialize() CassandraColumnHandle columnHandle = OBJECT_MAPPER.readValue(json, CassandraColumnHandle.class); - assertEquals(columnHandle.getName(), "column"); - assertEquals(columnHandle.getOrdinalPosition(), 42); - assertEquals(columnHandle.getCassandraType(), CassandraTypes.BIGINT); - assertEquals(columnHandle.isPartitionKey(), false); - assertEquals(columnHandle.isClusteringKey(), true); + assertThat(columnHandle.getName()).isEqualTo("column"); + assertThat(columnHandle.getOrdinalPosition()).isEqualTo(42); + assertThat(columnHandle.getCassandraType()).isEqualTo(CassandraTypes.BIGINT); + assertThat(columnHandle.isPartitionKey()).isEqualTo(false); + assertThat(columnHandle.isClusteringKey()).isEqualTo(true); } @Test @@ -199,11 +198,11 @@ public void testColumn2HandleDeserialize() CassandraColumnHandle columnHandle = OBJECT_MAPPER.readValue(json, CassandraColumnHandle.class); - assertEquals(columnHandle.getName(), "column2"); - assertEquals(columnHandle.getOrdinalPosition(), 0); - assertEquals(columnHandle.getCassandraType(), CassandraTypes.SET); - assertEquals(columnHandle.isPartitionKey(), false); - assertEquals(columnHandle.isClusteringKey(), false); + assertThat(columnHandle.getName()).isEqualTo("column2"); + assertThat(columnHandle.getOrdinalPosition()).isEqualTo(0); + assertThat(columnHandle.getCassandraType()).isEqualTo(CassandraTypes.SET); + assertThat(columnHandle.isPartitionKey()).isEqualTo(false); + assertThat(columnHandle.isClusteringKey()).isEqualTo(false); } private void testJsonEquals(String json, Map expectedMap) diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestMurmur3PartitionerTokenRing.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestMurmur3PartitionerTokenRing.java index c70e9d7759de..fe326dc9cbe4 100644 --- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestMurmur3PartitionerTokenRing.java +++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestMurmur3PartitionerTokenRing.java @@ -20,7 +20,8 @@ import static java.math.BigInteger.ONE; import static java.math.BigInteger.ZERO; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.offset; public class TestMurmur3PartitionerTokenRing { @@ -29,24 +30,24 @@ public class TestMurmur3PartitionerTokenRing @Test public void testGetTokenCountInRange() { - assertEquals(tokenRing.getTokenCountInRange(new Murmur3Token(0), new Murmur3Token(1)), ONE); - assertEquals(tokenRing.getTokenCountInRange(new Murmur3Token(-1), new Murmur3Token(1)), new BigInteger("2")); - assertEquals(tokenRing.getTokenCountInRange(new Murmur3Token(-100), new Murmur3Token(100)), new BigInteger("200")); - assertEquals(tokenRing.getTokenCountInRange(new Murmur3Token(0), new Murmur3Token(10)), new BigInteger("10")); - assertEquals(tokenRing.getTokenCountInRange(new Murmur3Token(1), new Murmur3Token(11)), new BigInteger("10")); - assertEquals(tokenRing.getTokenCountInRange(new Murmur3Token(0), new Murmur3Token(0)), ZERO); - assertEquals(tokenRing.getTokenCountInRange(new Murmur3Token(1), new Murmur3Token(1)), ZERO); - assertEquals(tokenRing.getTokenCountInRange(new Murmur3Token(Long.MIN_VALUE), new Murmur3Token(Long.MIN_VALUE)), BigInteger.valueOf(2).pow(64).subtract(ONE)); - assertEquals(tokenRing.getTokenCountInRange(new Murmur3Token(1), new Murmur3Token(0)), BigInteger.valueOf(2).pow(64).subtract(BigInteger.valueOf(2))); + assertThat(tokenRing.getTokenCountInRange(new Murmur3Token(0), new Murmur3Token(1))).isEqualTo(ONE); + assertThat(tokenRing.getTokenCountInRange(new Murmur3Token(-1), new Murmur3Token(1))).isEqualTo(new BigInteger("2")); + assertThat(tokenRing.getTokenCountInRange(new Murmur3Token(-100), new Murmur3Token(100))).isEqualTo(new BigInteger("200")); + assertThat(tokenRing.getTokenCountInRange(new Murmur3Token(0), new Murmur3Token(10))).isEqualTo(new BigInteger("10")); + assertThat(tokenRing.getTokenCountInRange(new Murmur3Token(1), new Murmur3Token(11))).isEqualTo(new BigInteger("10")); + assertThat(tokenRing.getTokenCountInRange(new Murmur3Token(0), new Murmur3Token(0))).isEqualTo(ZERO); + assertThat(tokenRing.getTokenCountInRange(new Murmur3Token(1), new Murmur3Token(1))).isEqualTo(ZERO); + assertThat(tokenRing.getTokenCountInRange(new Murmur3Token(Long.MIN_VALUE), new Murmur3Token(Long.MIN_VALUE))).isEqualTo(BigInteger.valueOf(2).pow(64).subtract(ONE)); + assertThat(tokenRing.getTokenCountInRange(new Murmur3Token(1), new Murmur3Token(0))).isEqualTo(BigInteger.valueOf(2).pow(64).subtract(BigInteger.valueOf(2))); } @Test public void testGetRingFraction() { - assertEquals(tokenRing.getRingFraction(new Murmur3Token(1), new Murmur3Token(1)), 0.0, 0.001); - assertEquals(tokenRing.getRingFraction(new Murmur3Token(1), new Murmur3Token(0)), 1.0, 0.001); - assertEquals(tokenRing.getRingFraction(new Murmur3Token(0), new Murmur3Token(Long.MAX_VALUE)), 0.5, 0.001); - assertEquals(tokenRing.getRingFraction(new Murmur3Token(Long.MIN_VALUE), new Murmur3Token(Long.MAX_VALUE)), 1.0, 0.001); - assertEquals(tokenRing.getRingFraction(new Murmur3Token(Long.MIN_VALUE), new Murmur3Token(Long.MIN_VALUE)), 1.0, 0.001); + assertThat(tokenRing.getRingFraction(new Murmur3Token(1), new Murmur3Token(1))).isCloseTo(0.0, offset(0.001)); + assertThat(tokenRing.getRingFraction(new Murmur3Token(1), new Murmur3Token(0))).isCloseTo(1.0, offset(0.001)); + assertThat(tokenRing.getRingFraction(new Murmur3Token(0), new Murmur3Token(Long.MAX_VALUE))).isCloseTo(0.5, offset(0.001)); + assertThat(tokenRing.getRingFraction(new Murmur3Token(Long.MIN_VALUE), new Murmur3Token(Long.MAX_VALUE))).isCloseTo(1.0, offset(0.001)); + assertThat(tokenRing.getRingFraction(new Murmur3Token(Long.MIN_VALUE), new Murmur3Token(Long.MIN_VALUE))).isCloseTo(1.0, offset(0.001)); } } diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestRandomPartitionerTokenRing.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestRandomPartitionerTokenRing.java index ee2ae94a2c2c..8055dbb50c92 100644 --- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestRandomPartitionerTokenRing.java +++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestRandomPartitionerTokenRing.java @@ -20,7 +20,8 @@ import static java.math.BigInteger.ONE; import static java.math.BigInteger.ZERO; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.offset; public class TestRandomPartitionerTokenRing { @@ -29,25 +30,25 @@ public class TestRandomPartitionerTokenRing @Test public void testGetRingFraction() { - assertEquals(tokenRing.getTokenCountInRange(randomToken(0), randomToken(1)), ONE); - assertEquals(tokenRing.getTokenCountInRange(randomToken(0), randomToken(200)), new BigInteger("200")); - assertEquals(tokenRing.getTokenCountInRange(randomToken(0), randomToken(10)), new BigInteger("10")); - assertEquals(tokenRing.getTokenCountInRange(randomToken(1), randomToken(11)), new BigInteger("10")); - assertEquals(tokenRing.getTokenCountInRange(randomToken(0), randomToken(0)), ZERO); - assertEquals(tokenRing.getTokenCountInRange(randomToken(-1), randomToken(-1)), BigInteger.valueOf(2).pow(127).add(ONE)); - assertEquals(tokenRing.getTokenCountInRange(randomToken(1), randomToken(0)), BigInteger.valueOf(2).pow(127)); + assertThat(tokenRing.getTokenCountInRange(randomToken(0), randomToken(1))).isEqualTo(ONE); + assertThat(tokenRing.getTokenCountInRange(randomToken(0), randomToken(200))).isEqualTo(new BigInteger("200")); + assertThat(tokenRing.getTokenCountInRange(randomToken(0), randomToken(10))).isEqualTo(new BigInteger("10")); + assertThat(tokenRing.getTokenCountInRange(randomToken(1), randomToken(11))).isEqualTo(new BigInteger("10")); + assertThat(tokenRing.getTokenCountInRange(randomToken(0), randomToken(0))).isEqualTo(ZERO); + assertThat(tokenRing.getTokenCountInRange(randomToken(-1), randomToken(-1))).isEqualTo(BigInteger.valueOf(2).pow(127).add(ONE)); + assertThat(tokenRing.getTokenCountInRange(randomToken(1), randomToken(0))).isEqualTo(BigInteger.valueOf(2).pow(127)); } @Test public void testGetTokenCountInRange() { - assertEquals(tokenRing.getRingFraction(randomToken(0), randomToken(0)), 0.0, 0.001); - assertEquals(tokenRing.getRingFraction(randomToken(1), randomToken(0)), 1.0, 0.001); - assertEquals(tokenRing.getRingFraction(randomToken(-1), randomToken(-1)), 1.0, 0.001); - assertEquals(tokenRing.getRingFraction(randomToken(0), randomToken(BigInteger.valueOf(2).pow(126))), 0.5, 0.001); - assertEquals(tokenRing.getRingFraction(randomToken(BigInteger.valueOf(2).pow(126)), randomToken(BigInteger.valueOf(2).pow(127))), 0.5, 0.001); - assertEquals(tokenRing.getRingFraction(randomToken(0), randomToken(BigInteger.valueOf(2).pow(126))), 0.5, 0.001); - assertEquals(tokenRing.getRingFraction(randomToken(0), randomToken(BigInteger.valueOf(2).pow(127))), 1.0, 0.001); + assertThat(tokenRing.getRingFraction(randomToken(0), randomToken(0))).isCloseTo(0.0, offset(0.001)); + assertThat(tokenRing.getRingFraction(randomToken(1), randomToken(0))).isCloseTo(1.0, offset(0.001)); + assertThat(tokenRing.getRingFraction(randomToken(-1), randomToken(-1))).isCloseTo(1.0, offset(0.001)); + assertThat(tokenRing.getRingFraction(randomToken(0), randomToken(BigInteger.valueOf(2).pow(126)))).isCloseTo(0.5, offset(0.001)); + assertThat(tokenRing.getRingFraction(randomToken(BigInteger.valueOf(2).pow(126)), randomToken(BigInteger.valueOf(2).pow(127)))).isCloseTo(0.5, offset(0.001)); + assertThat(tokenRing.getRingFraction(randomToken(0), randomToken(BigInteger.valueOf(2).pow(126)))).isCloseTo(0.5, offset(0.001)); + assertThat(tokenRing.getRingFraction(randomToken(0), randomToken(BigInteger.valueOf(2).pow(127)))).isCloseTo(1.0, offset(0.001)); } private static RandomToken randomToken(long value) diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/util/TestCassandraClusteringPredicatesExtractor.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/util/TestCassandraClusteringPredicatesExtractor.java index 44833901f10b..fd59960ef7e1 100644 --- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/util/TestCassandraClusteringPredicatesExtractor.java +++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/util/TestCassandraClusteringPredicatesExtractor.java @@ -28,7 +28,7 @@ import static io.trino.plugin.cassandra.CassandraTestingUtils.CASSANDRA_TYPE_MANAGER; import static io.trino.spi.type.BigintType.BIGINT; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestCassandraClusteringPredicatesExtractor { @@ -61,7 +61,7 @@ public void testBuildClusteringPredicate() col4, Domain.singleValue(BIGINT, 26L))); CassandraClusteringPredicatesExtractor predicatesExtractor = new CassandraClusteringPredicatesExtractor(CASSANDRA_TYPE_MANAGER, cassandraTable.getClusteringKeyColumns(), tupleDomain, cassandraVersion); String predicate = predicatesExtractor.getClusteringKeyPredicates(); - assertEquals(predicate, "\"clusteringKey1\" = 34"); + assertThat(predicate).isEqualTo("\"clusteringKey1\" = 34"); } @Test @@ -73,6 +73,6 @@ public void testGetUnenforcedPredicates() col4, Domain.singleValue(BIGINT, 26L))); CassandraClusteringPredicatesExtractor predicatesExtractor = new CassandraClusteringPredicatesExtractor(CASSANDRA_TYPE_MANAGER, cassandraTable.getClusteringKeyColumns(), tupleDomain, cassandraVersion); TupleDomain unenforcedPredicates = TupleDomain.withColumnDomains(ImmutableMap.of(col4, Domain.singleValue(BIGINT, 26L))); - assertEquals(predicatesExtractor.getUnenforcedConstraints(), unenforcedPredicates); + assertThat(predicatesExtractor.getUnenforcedConstraints()).isEqualTo(unenforcedPredicates); } } diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/util/TestHostAddressFactory.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/util/TestHostAddressFactory.java index 41ff67cd1b6e..d4bbbaaf6113 100644 --- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/util/TestHostAddressFactory.java +++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/util/TestHostAddressFactory.java @@ -29,7 +29,7 @@ import java.util.List; import java.util.Set; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestHostAddressFactory { @@ -58,6 +58,6 @@ public void testToHostAddressList() HostAddressFactory hostAddressFactory = new HostAddressFactory(); List list = hostAddressFactory.toHostAddressList(nodes); - assertEquals(list.toString(), "[[102:304:506:708:90a:b0c:d0e:f10], 1.2.3.4]"); + assertThat(list.toString()).isEqualTo("[[102:304:506:708:90a:b0c:d0e:f10], 1.2.3.4]"); } } diff --git a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveFileSystemAdl.java b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveFileSystemAdl.java index 88758304df51..df69bddd44be 100644 --- a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveFileSystemAdl.java +++ b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveFileSystemAdl.java @@ -32,10 +32,9 @@ import static com.google.common.base.Preconditions.checkArgument; import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; import static org.testng.util.Strings.isNullOrEmpty; @TestInstance(PER_CLASS) @@ -99,19 +98,19 @@ public void testRename() { Path basePath = new Path(getBasePath(), UUID.randomUUID().toString()); FileSystem fs = hdfsEnvironment.getFileSystem(TESTING_CONTEXT, basePath); - assertFalse(fs.exists(basePath)); + assertThat(fs.exists(basePath)).isFalse(); // create file foo.txt Path path = new Path(basePath, "foo.txt"); - assertTrue(fs.createNewFile(path)); - assertTrue(fs.exists(path)); + assertThat(fs.createNewFile(path)).isTrue(); + assertThat(fs.exists(path)).isTrue(); // rename foo.txt to bar.txt when bar does not exist Path newPath = new Path(basePath, "bar.txt"); - assertFalse(fs.exists(newPath)); - assertTrue(fs.rename(path, newPath)); - assertFalse(fs.exists(path)); - assertTrue(fs.exists(newPath)); + assertThat(fs.exists(newPath)).isFalse(); + assertThat(fs.rename(path, newPath)).isTrue(); + assertThat(fs.exists(path)).isFalse(); + assertThat(fs.exists(newPath)).isTrue(); // rename foo.txt to foo.txt when foo.txt does not exist // This fails with error no such file in ADLFileSystem @@ -119,43 +118,43 @@ public void testRename() .isInstanceOf(FileNotFoundException.class); // create file foo.txt and rename to existing bar.txt - assertTrue(fs.createNewFile(path)); - assertFalse(fs.rename(path, newPath)); + assertThat(fs.createNewFile(path)).isTrue(); + assertThat(fs.rename(path, newPath)).isFalse(); // rename foo.txt to foo.txt when foo.txt exists // This returns true in ADLFileSystem - assertTrue(fs.rename(path, path)); + assertThat(fs.rename(path, path)).isTrue(); // delete foo.txt - assertTrue(fs.delete(path, false)); - assertFalse(fs.exists(path)); + assertThat(fs.delete(path, false)).isTrue(); + assertThat(fs.exists(path)).isFalse(); // create directory source with file Path source = new Path(basePath, "source"); - assertTrue(fs.createNewFile(new Path(source, "test.txt"))); + assertThat(fs.createNewFile(new Path(source, "test.txt"))).isTrue(); // rename source to non-existing target Path target = new Path(basePath, "target"); - assertFalse(fs.exists(target)); - assertTrue(fs.rename(source, target)); - assertFalse(fs.exists(source)); - assertTrue(fs.exists(target)); + assertThat(fs.exists(target)).isFalse(); + assertThat(fs.rename(source, target)).isTrue(); + assertThat(fs.exists(source)).isFalse(); + assertThat(fs.exists(target)).isTrue(); // create directory source with file - assertTrue(fs.createNewFile(new Path(source, "test.txt"))); + assertThat(fs.createNewFile(new Path(source, "test.txt"))).isTrue(); // rename source to existing target - assertTrue(fs.rename(source, target)); - assertFalse(fs.exists(source)); + assertThat(fs.rename(source, target)).isTrue(); + assertThat(fs.exists(source)).isFalse(); target = new Path(target, "source"); - assertTrue(fs.exists(target)); - assertTrue(fs.exists(new Path(target, "test.txt"))); + assertThat(fs.exists(target)).isTrue(); + assertThat(fs.exists(new Path(target, "test.txt"))).isTrue(); // delete target target = new Path(basePath, "target"); - assertTrue(fs.exists(target)); - assertTrue(fs.delete(target, true)); - assertFalse(fs.exists(target)); + assertThat(fs.exists(target)).isTrue(); + assertThat(fs.delete(target, true)).isTrue(); + assertThat(fs.exists(target)).isFalse(); // cleanup fs.delete(basePath, true); diff --git a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveFileSystemS3.java b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveFileSystemS3.java index 449799b1aa61..ae5c1833a8d5 100644 --- a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveFileSystemS3.java +++ b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveFileSystemS3.java @@ -59,7 +59,6 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertFalse; import static org.testng.util.Strings.isNullOrEmpty; @TestInstance(PER_CLASS) @@ -132,7 +131,7 @@ public void testIgnoreHadoopFolderMarker() Path filePath = new Path(basePath, markerFileName); fs.create(filePath).close(); - assertFalse(Arrays.stream(fs.listStatus(basePath)).anyMatch(file -> file.getPath().getName().equalsIgnoreCase(markerFileName))); + assertThat(Arrays.stream(fs.listStatus(basePath)).anyMatch(file -> file.getPath().getName().equalsIgnoreCase(markerFileName))).isFalse(); } /** diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java index 122de9327503..97809bfcac4a 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java @@ -324,15 +324,11 @@ import static org.apache.hadoop.hive.common.FileUtils.makePartName; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Fail.fail; +import static org.assertj.core.data.Offset.offset; import static org.joda.time.DateTimeZone.UTC; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; @TestInstance(PER_CLASS) @Execution(SAME_THREAD) // staging directory is shared mutable state @@ -1057,7 +1053,7 @@ public void testGetDatabaseNames() try (Transaction transaction = newTransaction()) { ConnectorMetadata metadata = transaction.getMetadata(); List databases = metadata.listSchemaNames(newSession()); - assertTrue(databases.contains(database)); + assertThat(databases.contains(database)).isTrue(); } } @@ -1067,8 +1063,8 @@ public void testGetTableNames() try (Transaction transaction = newTransaction()) { ConnectorMetadata metadata = transaction.getMetadata(); List tables = metadata.listTables(newSession(), Optional.of(database)); - assertTrue(tables.contains(tablePartitionFormat)); - assertTrue(tables.contains(tableUnpartitioned)); + assertThat(tables.contains(tablePartitionFormat)).isTrue(); + assertThat(tables.contains(tableUnpartitioned)).isTrue(); } } @@ -1078,8 +1074,8 @@ public void testGetAllTableNames() try (Transaction transaction = newTransaction()) { ConnectorMetadata metadata = transaction.getMetadata(); List tables = metadata.listTables(newSession(), Optional.empty()); - assertTrue(tables.contains(tablePartitionFormat)); - assertTrue(tables.contains(tableUnpartitioned)); + assertThat(tables.contains(tablePartitionFormat)).isTrue(); + assertThat(tables.contains(tableUnpartitioned)).isTrue(); } } @@ -1089,8 +1085,8 @@ public void testGetAllTableColumns() try (Transaction transaction = newTransaction()) { ConnectorMetadata metadata = transaction.getMetadata(); Map> allColumns = listTableColumns(metadata, newSession(), new SchemaTablePrefix()); - assertTrue(allColumns.containsKey(tablePartitionFormat)); - assertTrue(allColumns.containsKey(tableUnpartitioned)); + assertThat(allColumns.containsKey(tablePartitionFormat)).isTrue(); + assertThat(allColumns.containsKey(tableUnpartitioned)).isTrue(); } } @@ -1100,8 +1096,8 @@ public void testGetAllTableColumnsInSchema() try (Transaction transaction = newTransaction()) { ConnectorMetadata metadata = transaction.getMetadata(); Map> allColumns = listTableColumns(metadata, newSession(), new SchemaTablePrefix(database)); - assertTrue(allColumns.containsKey(tablePartitionFormat)); - assertTrue(allColumns.containsKey(tableUnpartitioned)); + assertThat(allColumns.containsKey(tablePartitionFormat)).isTrue(); + assertThat(allColumns.containsKey(tableUnpartitioned)).isTrue(); } } @@ -1111,12 +1107,12 @@ public void testListUnknownSchema() try (Transaction transaction = newTransaction()) { ConnectorMetadata metadata = transaction.getMetadata(); ConnectorSession session = newSession(); - assertNull(metadata.getTableHandle(session, new SchemaTableName(INVALID_DATABASE, INVALID_TABLE))); - assertEquals(metadata.listTables(session, Optional.of(INVALID_DATABASE)), ImmutableList.of()); - assertEquals(listTableColumns(metadata, session, new SchemaTablePrefix(INVALID_DATABASE, INVALID_TABLE)), ImmutableMap.of()); - assertEquals(metadata.listViews(session, Optional.of(INVALID_DATABASE)), ImmutableList.of()); - assertEquals(metadata.getViews(session, Optional.of(INVALID_DATABASE)), ImmutableMap.of()); - assertEquals(metadata.getView(session, new SchemaTableName(INVALID_DATABASE, INVALID_TABLE)), Optional.empty()); + assertThat(metadata.getTableHandle(session, new SchemaTableName(INVALID_DATABASE, INVALID_TABLE))).isNull(); + assertThat(metadata.listTables(session, Optional.of(INVALID_DATABASE))).isEqualTo(ImmutableList.of()); + assertThat(listTableColumns(metadata, session, new SchemaTablePrefix(INVALID_DATABASE, INVALID_TABLE))).isEqualTo(ImmutableMap.of()); + assertThat(metadata.listViews(session, Optional.of(INVALID_DATABASE))).isEqualTo(ImmutableList.of()); + assertThat(metadata.getViews(session, Optional.of(INVALID_DATABASE))).isEqualTo(ImmutableMap.of()); + assertThat(metadata.getView(session, new SchemaTableName(INVALID_DATABASE, INVALID_TABLE))).isEqualTo(Optional.empty()); } } @@ -1345,20 +1341,20 @@ protected void doTestMismatchSchemaTable( } catch (TrinoException e) { // expected - assertEquals(e.getErrorCode(), HIVE_PARTITION_SCHEMA_MISMATCH.toErrorCode()); + assertThat(e.getErrorCode()).isEqualTo(HIVE_PARTITION_SCHEMA_MISMATCH.toErrorCode()); } } protected void assertExpectedTableProperties(ConnectorTableProperties actualProperties, ConnectorTableProperties expectedProperties) { - assertEquals(actualProperties.getPredicate(), expectedProperties.getPredicate()); - assertEquals(actualProperties.getDiscretePredicates().isPresent(), expectedProperties.getDiscretePredicates().isPresent()); + assertThat(actualProperties.getPredicate()).isEqualTo(expectedProperties.getPredicate()); + assertThat(actualProperties.getDiscretePredicates().isPresent()).isEqualTo(expectedProperties.getDiscretePredicates().isPresent()); actualProperties.getDiscretePredicates().ifPresent(actual -> { DiscretePredicates expected = expectedProperties.getDiscretePredicates().get(); - assertEquals(actual.getColumns(), expected.getColumns()); + assertThat(actual.getColumns()).isEqualTo(expected.getColumns()); assertEqualsIgnoreOrder(actual.getPredicates(), expected.getPredicates()); }); - assertEquals(actualProperties.getLocalProperties(), expectedProperties.getLocalProperties()); + assertThat(actualProperties.getLocalProperties()).isEqualTo(expectedProperties.getLocalProperties()); } protected void assertExpectedPartitions(ConnectorTableHandle table, Iterable expectedPartitions) @@ -1373,9 +1369,9 @@ protected void assertExpectedPartitions(ConnectorTableHandle table, Iterable expected : expectedById.entrySet()) { HivePartition actualPartition = actualById.get(expected.getKey()); HivePartition expectedPartition = expected.getValue(); - assertEquals(actualPartition.getPartitionId(), expectedPartition.getPartitionId()); - assertEquals(actualPartition.getKeys(), expectedPartition.getKeys()); - assertEquals(actualPartition.getTableName(), expectedPartition.getTableName()); + assertThat(actualPartition.getPartitionId()).isEqualTo(expectedPartition.getPartitionId()); + assertThat(actualPartition.getKeys()).isEqualTo(expectedPartition.getKeys()); + assertThat(actualPartition.getTableName()).isEqualTo(expectedPartition.getTableName()); } } @@ -1434,7 +1430,7 @@ public void testGetTableSchemaOffline() try (Transaction transaction = newTransaction()) { ConnectorMetadata metadata = transaction.getMetadata(); Map> columns = listTableColumns(metadata, newSession(), tableOffline.toSchemaTablePrefix()); - assertEquals(columns.size(), 1); + assertThat(columns.size()).isEqualTo(1); Map map = uniqueIndex(getOnlyElement(columns.values()), ColumnMetadata::getName); assertPrimitiveField(map, "t_string", createUnboundedVarcharType(), false); @@ -1459,7 +1455,7 @@ public void testGetTableSchemaException() { try (Transaction transaction = newTransaction()) { ConnectorMetadata metadata = transaction.getMetadata(); - assertNull(metadata.getTableHandle(newSession(), invalidTable)); + assertThat(metadata.getTableHandle(newSession(), invalidTable)).isNull(); } } @@ -1526,7 +1522,9 @@ private static void verifyTableStatisticsWithColumns( { TableStatistics tableStatistics = metadata.getTableStatistics(session, tableHandle); - assertFalse(tableStatistics.getRowCount().isUnknown(), "row count is unknown"); + assertThat(tableStatistics.getRowCount().isUnknown()) + .describedAs("row count is unknown") + .isFalse(); Map columnsStatistics = tableStatistics .getColumnStatistics() @@ -1537,30 +1535,32 @@ private static void verifyTableStatisticsWithColumns( entry -> ((HiveColumnHandle) entry.getKey()).getName(), Map.Entry::getValue)); - assertEquals(columnsStatistics.keySet(), expectedColumnStatsColumns, "columns with statistics"); + assertThat(columnsStatistics.keySet()) + .describedAs("columns with statistics") + .isEqualTo(expectedColumnStatsColumns); Map columnHandles = metadata.getColumnHandles(session, tableHandle); columnsStatistics.forEach((columnName, columnStatistics) -> { ColumnHandle columnHandle = columnHandles.get(columnName); Type columnType = metadata.getColumnMetadata(session, tableHandle, columnHandle).getType(); - assertFalse( - columnStatistics.getNullsFraction().isUnknown(), - "unknown nulls fraction for " + columnName); + assertThat(columnStatistics.getNullsFraction().isUnknown()) + .describedAs("unknown nulls fraction for " + columnName) + .isFalse(); - assertFalse( - columnStatistics.getDistinctValuesCount().isUnknown(), - "unknown distinct values count for " + columnName); + assertThat(columnStatistics.getDistinctValuesCount().isUnknown()) + .describedAs("unknown distinct values count for " + columnName) + .isFalse(); if (columnType instanceof VarcharType) { - assertFalse( - columnStatistics.getDataSize().isUnknown(), - "unknown data size for " + columnName); + assertThat(columnStatistics.getDataSize().isUnknown()) + .describedAs("unknown data size for " + columnName) + .isFalse(); } else { - assertTrue( - columnStatistics.getDataSize().isUnknown(), - "unknown data size for" + columnName); + assertThat(columnStatistics.getDataSize().isUnknown()) + .describedAs("unknown data size for" + columnName) + .isTrue(); } }); } @@ -1576,7 +1576,7 @@ public void testGetPartitionSplitsBatch() ConnectorTableHandle tableHandle = getTableHandle(metadata, tablePartitionFormat); ConnectorSplitSource splitSource = getSplits(splitManager, transaction, session, tableHandle); - assertEquals(getSplitCount(splitSource), tablePartitionFormatPartitions.size()); + assertThat(getSplitCount(splitSource)).isEqualTo(tablePartitionFormatPartitions.size()); } } @@ -1591,7 +1591,7 @@ public void testGetPartitionSplitsBatchUnpartitioned() ConnectorTableHandle tableHandle = getTableHandle(metadata, tableUnpartitioned); ConnectorSplitSource splitSource = getSplits(splitManager, transaction, session, tableHandle); - assertEquals(getSplitCount(splitSource), 1); + assertThat(getSplitCount(splitSource)).isEqualTo(1); } } @@ -1613,11 +1613,11 @@ public void testPerTransactionDirectoryListerCache() getAllSplits(getSplits(splitManager, transaction, session, tableHandle)); // directory should be listed initially - assertEquals(countingDirectoryLister.getListCount(), initListCount + 1); + assertThat(countingDirectoryLister.getListCount()).isEqualTo(initListCount + 1); // directory content should be cached getAllSplits(getSplits(splitManager, transaction, session, tableHandle)); - assertEquals(countingDirectoryLister.getListCount(), initListCount + 1); + assertThat(countingDirectoryLister.getListCount()).isEqualTo(initListCount + 1); } try (Transaction transaction = newTransaction()) { @@ -1629,7 +1629,7 @@ public void testPerTransactionDirectoryListerCache() getAllSplits(getSplits(splitManager, transaction, session, tableHandle)); // directory should be listed again in new transaction - assertEquals(countingDirectoryLister.getListCount(), initListCount + 2); + assertThat(countingDirectoryLister.getListCount()).isEqualTo(initListCount + 2); } } finally { @@ -1657,7 +1657,7 @@ public void testGetPartitionTableOffline() fail("expected TableOfflineException"); } catch (TableOfflineException e) { - assertEquals(e.getTableName(), tableOffline); + assertThat(e.getTableName()).isEqualTo(tableOffline); } } } @@ -1670,7 +1670,7 @@ public void testGetPartitionSplitsTableNotReadablePartition() ConnectorSession session = newSession(); ConnectorTableHandle tableHandle = getTableHandle(metadata, tableNotReadable); - assertNotNull(tableHandle); + assertThat(tableHandle).isNotNull(); try { getSplitCount(getSplits(splitManager, transaction, session, tableHandle)); @@ -1678,8 +1678,8 @@ public void testGetPartitionSplitsTableNotReadablePartition() } catch (HiveNotReadableException e) { assertThat(e).hasMessageMatching("Table '.*\\.trino_test_not_readable' is not readable: reason for not readable"); - assertEquals(e.getTableName(), tableNotReadable); - assertEquals(e.getPartition(), Optional.empty()); + assertThat(e.getTableName()).isEqualTo(tableNotReadable); + assertThat(e.getPartition()).isEqualTo(Optional.empty()); } } } @@ -1720,7 +1720,7 @@ public void testBucketedTableStringInt() rowFound = true; } } - assertTrue(rowFound); + assertThat(rowFound).isTrue(); } } @@ -1743,8 +1743,8 @@ public void testBucketedTableBigintBoolean() newSession(ImmutableMap.of("propagate_table_scan_sorting_properties", true)), tableHandle); // trino_test_bucketed_by_bigint_boolean does not define sorting, therefore local properties is empty - assertTrue(properties.getLocalProperties().isEmpty()); - assertTrue(metadata.getTableProperties(newSession(), tableHandle).getLocalProperties().isEmpty()); + assertThat(properties.getLocalProperties().isEmpty()).isTrue(); + assertThat(metadata.getTableProperties(newSession(), tableHandle).getLocalProperties().isEmpty()).isTrue(); String testString = "test"; Long testBigint = 89L; @@ -1767,7 +1767,7 @@ public void testBucketedTableBigintBoolean() break; } } - assertTrue(rowFound); + assertThat(rowFound).isTrue(); } } @@ -1850,7 +1850,7 @@ private void doTestBucketedTableEvolutionWithDifferentReadCount(HiveStorageForma List columnHandles = ImmutableList.copyOf(metadata.getColumnHandles(session, tableHandle).values()); List splits = getAllSplits(getSplits(splitManager, transaction, session, tableHandle)); - assertEquals(splits.size(), 16); + assertThat(splits.size()).isEqualTo(16); ImmutableList.Builder allRows = ImmutableList.builder(); for (ConnectorSplit split : splits) { @@ -1861,7 +1861,7 @@ private void doTestBucketedTableEvolutionWithDifferentReadCount(HiveStorageForma } result = new MaterializedResult(allRows.build(), getTypes(columnHandles)); - assertEquals(result.getRowCount(), rowCount); + assertThat(result.getRowCount()).isEqualTo(rowCount); Map columnIndex = indexColumns(columnHandles); int nameColumnIndex = columnIndex.get("name"); @@ -1870,7 +1870,7 @@ private void doTestBucketedTableEvolutionWithDifferentReadCount(HiveStorageForma String name = (String) row.getField(nameColumnIndex); int bucket = (int) row.getField(bucketColumnIndex); - assertEquals(bucket, Integer.parseInt(name) % bucketCount); + assertThat(bucket).isEqualTo(Integer.parseInt(name) % bucketCount); } } } @@ -1986,18 +1986,16 @@ private static void assertBucketTableEvolutionResult(MaterializedResult result, String name = (String) row.getField(nameColumnIndex); int bucket = (int) row.getField(bucketColumnIndex); idCount.compute(Long.parseLong(name), (key, oldValue) -> oldValue == null ? 1 : oldValue + 1); - assertEquals(bucket, Integer.parseInt(name) % bucketCount); + assertThat(bucket).isEqualTo(Integer.parseInt(name) % bucketCount); if (idColumnIndex.isPresent()) { long id = (long) row.getField(idColumnIndex.getAsInt()); - assertEquals(Integer.parseInt(name), id); + assertThat(Integer.parseInt(name)).isEqualTo(id); } } - assertEquals( - (int) idCount.values().stream() - .distinct() - .collect(onlyElement()), - 3); - assertEquals(idCount.keySet(), expectedIds); + assertThat((int) idCount.values().stream() + .distinct() + .collect(onlyElement())).isEqualTo(3); + assertThat(idCount.keySet()).isEqualTo(expectedIds); } @Test @@ -2065,7 +2063,7 @@ private void doTestBucketedSortedTableEvolution(SchemaTableName tableName) // read entire table List columnHandles = ImmutableList.copyOf(metadata.getColumnHandles(session, tableHandle).values()); MaterializedResult result = readTable(transaction, tableHandle, columnHandles, session, TupleDomain.all(), OptionalInt.empty(), Optional.empty()); - assertEquals(result.getRowCount(), 300); + assertThat(result.getRowCount()).isEqualTo(300); } try (Transaction transaction = newTransaction()) { @@ -2075,7 +2073,7 @@ private void doTestBucketedSortedTableEvolution(SchemaTableName tableName) Map columnHandles = metadata.getColumnHandles(session, tableHandle); // verify local sorting property ConnectorTableProperties properties = metadata.getTableProperties(session, tableHandle); - assertEquals(properties.getLocalProperties(), ImmutableList.of( + assertThat(properties.getLocalProperties()).isEqualTo(ImmutableList.of( new SortingProperty<>(columnHandles.get("id"), ASC_NULLS_FIRST))); // read on a entire table should fail with exception @@ -2095,7 +2093,7 @@ private void doTestBucketedSortedTableEvolution(SchemaTableName tableName) Domain.create(ValueSet.of(VARCHAR, utf8Slice("sorted_by_id_name"), utf8Slice("sorted_by_id")), false))), OptionalInt.empty(), Optional.empty()); - assertEquals(result.getRowCount(), 200); + assertThat(result.getRowCount()).isEqualTo(200); } } @@ -2127,7 +2125,7 @@ private void doTestBucketedTableValidation(HiveStorageFormat storageFormat, Sche ConnectorTableHandle tableHandle = getTableHandle(metadata, tableName); List columnHandles = filterNonHiddenColumnHandles(metadata.getColumnHandles(session, tableHandle).values()); MaterializedResult result = readTable(transaction, tableHandle, columnHandles, session, TupleDomain.all(), OptionalInt.empty(), Optional.of(storageFormat)); - assertEquals(result.getRowCount(), 87); // fewer rows due to deleted file + assertThat(result.getRowCount()).isEqualTo(87); // fewer rows due to deleted file } // read fails due to validation failure @@ -2197,7 +2195,7 @@ private void assertTableIsBucketed(ConnectorTableHandle tableHandle, Transaction // verify all paths are unique Set paths = new HashSet<>(); for (ConnectorSplit split : splits) { - assertTrue(paths.add(((HiveSplit) split).getPath())); + assertThat(paths.add(((HiveSplit) split).getPath())).isTrue(); } } @@ -2216,7 +2214,7 @@ public void testGetRecords() Map columnIndex = indexColumns(columnHandles); List splits = getAllSplits(tableHandle, transaction, session); - assertEquals(splits.size(), tablePartitionFormatPartitions.size()); + assertThat(splits.size()).isEqualTo(tablePartitionFormatPartitions.size()); for (ConnectorSplit split : splits) { HiveSplit hiveSplit = (HiveSplit) split; @@ -2247,48 +2245,48 @@ public void testGetRecords() value = row.getField(columnIndex.get("t_string")); if (rowNumber % 19 == 0) { - assertNull(value); + assertThat(value).isNull(); } else if (rowNumber % 19 == 1) { - assertEquals(value, ""); + assertThat(value).isEqualTo(""); } else { - assertEquals(value, "test"); + assertThat(value).isEqualTo("test"); } - assertEquals(row.getField(columnIndex.get("t_tinyint")), (byte) (1 + rowNumber)); - assertEquals(row.getField(columnIndex.get("t_smallint")), (short) (2 + rowNumber)); - assertEquals(row.getField(columnIndex.get("t_int")), 3 + (int) rowNumber); + assertThat(row.getField(columnIndex.get("t_tinyint"))).isEqualTo((byte) (1 + rowNumber)); + assertThat(row.getField(columnIndex.get("t_smallint"))).isEqualTo((short) (2 + rowNumber)); + assertThat(row.getField(columnIndex.get("t_int"))).isEqualTo(3 + (int) rowNumber); if (rowNumber % 13 == 0) { - assertNull(row.getField(columnIndex.get("t_bigint"))); + assertThat(row.getField(columnIndex.get("t_bigint"))).isNull(); } else { - assertEquals(row.getField(columnIndex.get("t_bigint")), 4 + rowNumber); + assertThat(row.getField(columnIndex.get("t_bigint"))).isEqualTo(4 + rowNumber); } - assertEquals((Float) row.getField(columnIndex.get("t_float")), 5.1f + rowNumber, 0.001); - assertEquals(row.getField(columnIndex.get("t_double")), 6.2 + rowNumber); + assertThat((Float) row.getField(columnIndex.get("t_float"))).isCloseTo(5.1f + rowNumber, offset(0.001f)); + assertThat(row.getField(columnIndex.get("t_double"))).isEqualTo(6.2 + rowNumber); if (rowNumber % 3 == 2) { - assertNull(row.getField(columnIndex.get("t_boolean"))); + assertThat(row.getField(columnIndex.get("t_boolean"))).isNull(); } else { - assertEquals(row.getField(columnIndex.get("t_boolean")), rowNumber % 3 != 0); + assertThat(row.getField(columnIndex.get("t_boolean"))).isEqualTo(rowNumber % 3 != 0); } - assertEquals(row.getField(columnIndex.get("ds")), ds); - assertEquals(row.getField(columnIndex.get("file_format")), fileFormat); - assertEquals(row.getField(columnIndex.get("dummy")), dummyPartition); + assertThat(row.getField(columnIndex.get("ds"))).isEqualTo(ds); + assertThat(row.getField(columnIndex.get("file_format"))).isEqualTo(fileFormat); + assertThat(row.getField(columnIndex.get("dummy"))).isEqualTo(dummyPartition); long newCompletedBytes = pageSource.getCompletedBytes(); - assertTrue(newCompletedBytes >= completedBytes); - assertTrue(newCompletedBytes <= hiveSplit.getLength()); + assertThat(newCompletedBytes >= completedBytes).isTrue(); + assertThat(newCompletedBytes <= hiveSplit.getLength()).isTrue(); completedBytes = newCompletedBytes; } - assertTrue(completedBytes <= hiveSplit.getLength()); - assertEquals(rowNumber, 100); + assertThat(completedBytes <= hiveSplit.getLength()).isTrue(); + assertThat(rowNumber).isEqualTo(100); } } } @@ -2308,7 +2306,7 @@ public void testGetPartialRecords() Map columnIndex = indexColumns(columnHandles); List splits = getAllSplits(tableHandle, transaction, session); - assertEquals(splits.size(), tablePartitionFormatPartitions.size()); + assertThat(splits.size()).isEqualTo(tablePartitionFormatPartitions.size()); for (ConnectorSplit split : splits) { HiveSplit hiveSplit = (HiveSplit) split; @@ -2326,13 +2324,13 @@ public void testGetPartialRecords() for (MaterializedRow row : result) { rowNumber++; - assertEquals(row.getField(columnIndex.get("t_double")), 6.2 + rowNumber); - assertEquals(row.getField(columnIndex.get("ds")), ds); - assertEquals(row.getField(columnIndex.get("file_format")), fileFormat); - assertEquals(row.getField(columnIndex.get("dummy")), dummyPartition); + assertThat(row.getField(columnIndex.get("t_double"))).isEqualTo(6.2 + rowNumber); + assertThat(row.getField(columnIndex.get("ds"))).isEqualTo(ds); + assertThat(row.getField(columnIndex.get("file_format"))).isEqualTo(fileFormat); + assertThat(row.getField(columnIndex.get("dummy"))).isEqualTo(dummyPartition); } } - assertEquals(rowNumber, 100); + assertThat(rowNumber).isEqualTo(100); } } } @@ -2356,7 +2354,7 @@ public void testGetRecordsUnpartitioned() for (ConnectorSplit split : splits) { HiveSplit hiveSplit = (HiveSplit) split; - assertEquals(hiveSplit.getPartitionKeys(), ImmutableList.of()); + assertThat(hiveSplit.getPartitionKeys()).isEqualTo(ImmutableList.of()); long rowNumber = 0; try (ConnectorPageSource pageSource = pageSourceProvider.createPageSource(transaction.getTransactionHandle(), session, split, tableHandle, columnHandles, DynamicFilter.EMPTY)) { @@ -2367,19 +2365,19 @@ public void testGetRecordsUnpartitioned() rowNumber++; if (rowNumber % 19 == 0) { - assertNull(row.getField(columnIndex.get("t_string"))); + assertThat(row.getField(columnIndex.get("t_string"))).isNull(); } else if (rowNumber % 19 == 1) { - assertEquals(row.getField(columnIndex.get("t_string")), ""); + assertThat(row.getField(columnIndex.get("t_string"))).isEqualTo(""); } else { - assertEquals(row.getField(columnIndex.get("t_string")), "unpartitioned"); + assertThat(row.getField(columnIndex.get("t_string"))).isEqualTo("unpartitioned"); } - assertEquals(row.getField(columnIndex.get("t_tinyint")), (byte) (1 + rowNumber)); + assertThat(row.getField(columnIndex.get("t_tinyint"))).isEqualTo((byte) (1 + rowNumber)); } } - assertEquals(rowNumber, 100); + assertThat(rowNumber).isEqualTo(100); } } } @@ -2416,7 +2414,7 @@ public void testPartitionSchemaNonCanonical() Constraint constraint = new Constraint(TupleDomain.fromFixedValues(ImmutableMap.of(column, NullableValue.of(BOOLEAN, false)))); table = applyFilter(metadata, table, constraint); HivePartition partition = getOnlyElement(((HiveTableHandle) table).getPartitions().orElseThrow(AssertionError::new)); - assertEquals(getPartitionId(partition), "t_boolean=0"); + assertThat(getPartitionId(partition)).isEqualTo("t_boolean=0"); ConnectorSplitSource splitSource = getSplits(splitManager, transaction, session, table); ConnectorSplit split = getOnlyElement(getAllSplits(splitSource)); @@ -2426,7 +2424,7 @@ public void testPartitionSchemaNonCanonical() fail("expected exception"); } catch (TrinoException e) { - assertEquals(e.getErrorCode(), HIVE_INVALID_PARTITION_VALUE.toErrorCode()); + assertThat(e.getErrorCode()).isEqualTo(HIVE_INVALID_PARTITION_VALUE.toErrorCode()); } } } @@ -2531,19 +2529,19 @@ private void assertEmptyFile(HiveStorageFormat format) // verify directory is empty HdfsContext context = new HdfsContext(session); Path location = new Path(table.getStorage().getLocation()); - assertTrue(listDirectory(context, location).isEmpty()); + assertThat(listDirectory(context, location).isEmpty()).isTrue(); // read table with empty directory readTable(transaction, tableHandle, columnHandles, session, TupleDomain.all(), OptionalInt.of(0), Optional.of(ORC)); // create empty file FileSystem fileSystem = hdfsEnvironment.getFileSystem(context, location); - assertTrue(fileSystem.createNewFile(new Path(location, "empty-file"))); - assertEquals(listDirectory(context, location), ImmutableList.of("empty-file")); + assertThat(fileSystem.createNewFile(new Path(location, "empty-file"))).isTrue(); + assertThat(listDirectory(context, location)).isEqualTo(ImmutableList.of("empty-file")); // read table with empty file MaterializedResult result = readTable(transaction, tableHandle, columnHandles, session, TupleDomain.all(), OptionalInt.of(0), Optional.empty()); - assertEquals(result.getRowCount(), 0); + assertThat(result.getRowCount()).isEqualTo(0); } } finally { @@ -2571,8 +2569,8 @@ public void testRenameTable() ConnectorSession session = newSession(); ConnectorMetadata metadata = transaction.getMetadata(); - assertNull(metadata.getTableHandle(session, temporaryRenameTableOld)); - assertNotNull(metadata.getTableHandle(session, temporaryRenameTableNew)); + assertThat(metadata.getTableHandle(session, temporaryRenameTableOld)).isNull(); + assertThat(metadata.getTableHandle(session, temporaryRenameTableNew)).isNotNull(); } } finally { @@ -2684,7 +2682,7 @@ public void testTableCreationRollback() // verify we have data files stagingPathRoot = getStagingPathRoot(outputHandle); HdfsContext context = new HdfsContext(session); - assertFalse(listAllDataFiles(context, stagingPathRoot).isEmpty()); + assertThat(listAllDataFiles(context, stagingPathRoot).isEmpty()).isFalse(); // rollback the table transaction.rollback(); @@ -2692,13 +2690,13 @@ public void testTableCreationRollback() // verify all files have been deleted HdfsContext context = new HdfsContext(newSession()); - assertTrue(listAllDataFiles(context, stagingPathRoot).isEmpty()); + assertThat(listAllDataFiles(context, stagingPathRoot).isEmpty()).isTrue(); // verify table is not in the metastore try (Transaction transaction = newTransaction()) { ConnectorSession session = newSession(); ConnectorMetadata metadata = transaction.getMetadata(); - assertNull(metadata.getTableHandle(session, temporaryCreateRollbackTable)); + assertThat(metadata.getTableHandle(session, temporaryCreateRollbackTable)).isNull(); } } finally { @@ -2724,7 +2722,7 @@ public void testTableCreationIgnoreExisting() transaction.getMetastore() .createTable(session, table, privileges, Optional.empty(), Optional.empty(), false, ZERO_TABLE_STATISTICS, false); Optional
    tableHandle = transaction.getMetastore().getTable(schemaName, tableName); - assertTrue(tableHandle.isPresent()); + assertThat(tableHandle.isPresent()).isTrue(); transaction.commit(); } @@ -2758,8 +2756,8 @@ public void testTableCreationIgnoreExisting() fail("Expected exception"); } catch (TrinoException e) { - assertEquals(e.getErrorCode(), TRANSACTION_CONFLICT.toErrorCode()); - assertEquals(e.getMessage(), format("Table already exists with a different schema: '%s'", schemaTableName.getTableName())); + assertThat(e.getErrorCode()).isEqualTo(TRANSACTION_CONFLICT.toErrorCode()); + assertThat(e.getMessage()).isEqualTo(format("Table already exists with a different schema: '%s'", schemaTableName.getTableName())); } } finally { @@ -2897,7 +2895,7 @@ private void doTestBucketSortedTables(SchemaTableName table) "bucket_execution_enabled", false)), tableHandle); Map columnIndex = indexColumns(columnHandles); - assertEquals(properties.getLocalProperties(), ImmutableList.of( + assertThat(properties.getLocalProperties()).isEqualTo(ImmutableList.of( new SortingProperty<>(columnHandles.get(columnIndex.get("value_asc")), ASC_NULLS_FIRST), new SortingProperty<>(columnHandles.get(columnIndex.get("value_desc")), DESC_NULLS_LAST))); assertThat(metadata.getTableProperties(newSession(), tableHandle).getLocalProperties()).isEmpty(); @@ -2919,8 +2917,8 @@ private void doTestBucketSortedTables(SchemaTableName table) for (int i = 0; i < page.getPositionCount(); i++) { Block blockAsc = page.getBlock(1); Block blockDesc = page.getBlock(2); - assertFalse(blockAsc.isNull(i)); - assertFalse(blockDesc.isNull(i)); + assertThat(blockAsc.isNull(i)).isFalse(); + assertThat(blockDesc.isNull(i)).isFalse(); String valueAsc = VARCHAR.getSlice(blockAsc, i).toStringUtf8(); if (lastValueAsc != null) { @@ -3108,7 +3106,9 @@ public void testCreateEmptyTableShouldNotCreateStagingDirectory() HdfsContext context = new HdfsContext(session); Path temporaryRoot = new Path(targetPath.toString(), temporaryStagingPrefix); FileSystem fileSystem = hdfsEnvironment.getFileSystem(context, temporaryRoot); - assertFalse(fileSystem.exists(temporaryRoot), format("Temporary staging directory %s is created.", temporaryRoot)); + assertThat(fileSystem.exists(temporaryRoot)) + .describedAs(format("Temporary staging directory %s is created.", temporaryRoot)) + .isFalse(); } } finally { @@ -3149,7 +3149,7 @@ public void testCreateTableUnsupportedType() fail("create table with unsupported type should fail for storage format " + storageFormat); } catch (TrinoException e) { - assertEquals(e.getErrorCode(), NOT_SUPPORTED.toErrorCode()); + assertThat(e.getErrorCode()).isEqualTo(NOT_SUPPORTED.toErrorCode()); } } } @@ -3573,7 +3573,7 @@ public void testIllegalStorageFormatDuringTableScan() try (Transaction transaction = newTransaction()) { ConnectorMetadata metadata = transaction.getMetadata(); Map> allColumns = listTableColumns(metadata, newSession(), new SchemaTablePrefix(schemaTableName.getSchemaName())); - assertTrue(allColumns.containsKey(schemaTableName)); + assertThat(allColumns.containsKey(schemaTableName)).isTrue(); } finally { dropTable(schemaTableName); @@ -3711,9 +3711,7 @@ protected void testStorePartitionWithStatistics( // create partition with stats for all columns metastoreClient.addPartitions(tableName.getSchemaName(), tableName.getTableName(), ImmutableList.of(new PartitionWithStatistics(partition, partitionName, statsForAllColumns1))); - assertEquals( - metastoreClient.getPartition(tableName.getSchemaName(), tableName.getTableName(), partitionValues).get().getStorage().getStorageFormat(), - fromHiveStorageFormat(ORC)); + assertThat(metastoreClient.getPartition(tableName.getSchemaName(), tableName.getTableName(), partitionValues).get().getStorage().getStorageFormat()).isEqualTo(fromHiveStorageFormat(ORC)); assertThat(metastoreClient.getPartitionStatistics(tableName.getSchemaName(), tableName.getTableName(), ImmutableSet.of(partitionName))) .isEqualTo(ImmutableMap.of(partitionName, statsForAllColumns1)); @@ -3724,9 +3722,7 @@ protected void testStorePartitionWithStatistics( .setLocation(partitionTargetPath(tableName, partitionName))) .build(); metastoreClient.alterPartition(tableName.getSchemaName(), tableName.getTableName(), new PartitionWithStatistics(modifiedPartition, partitionName, statsForAllColumns2)); - assertEquals( - metastoreClient.getPartition(tableName.getSchemaName(), tableName.getTableName(), partitionValues).get().getStorage().getStorageFormat(), - fromHiveStorageFormat(RCBINARY)); + assertThat(metastoreClient.getPartition(tableName.getSchemaName(), tableName.getTableName(), partitionValues).get().getStorage().getStorageFormat()).isEqualTo(fromHiveStorageFormat(RCBINARY)); assertThat(metastoreClient.getPartitionStatistics(tableName.getSchemaName(), tableName.getTableName(), ImmutableSet.of(partitionName))) .isEqualTo(ImmutableMap.of(partitionName, statsForAllColumns2)); @@ -3812,7 +3808,7 @@ protected void testPartitionStatisticsSampling(List columns, Par ConnectorTableHandle tableHandle = metadata.getTableHandle(session, tableName); TableStatistics unsampledStatistics = metadata.getTableStatistics(sampleSize(2), tableHandle); TableStatistics sampledStatistics = metadata.getTableStatistics(sampleSize(1), tableHandle); - assertEquals(sampledStatistics, unsampledStatistics); + assertThat(sampledStatistics).isEqualTo(unsampledStatistics); } } finally { @@ -3846,7 +3842,7 @@ public void testApplyProjection() List columnHandles = metadata.getColumnHandles(session, tableHandle).values().stream() .filter(columnHandle -> !((HiveColumnHandle) columnHandle).isHidden()) .collect(toList()); - assertEquals(columnHandles.size(), columnsForApplyProjectionTest.size()); + assertThat(columnHandles.size()).isEqualTo(columnsForApplyProjectionTest.size()); Map columnHandleMap = columnHandles.stream() .collect(toImmutableMap(handle -> ((HiveColumnHandle) handle).getBaseColumnName(), Function.identity())); @@ -3951,16 +3947,20 @@ private static Map getColumnHandlesFor(Map> projectionResult, boolean shouldBeEmpty, List expectedProjections, Map expectedAssignments) { if (shouldBeEmpty) { - assertTrue(projectionResult.isEmpty(), "expected projectionResult to be empty"); + assertThat(projectionResult.isEmpty()) + .describedAs("expected projectionResult to be empty") + .isTrue(); return; } - assertTrue(projectionResult.isPresent(), "expected non-empty projection result"); + assertThat(projectionResult.isPresent()) + .describedAs("expected non-empty projection result") + .isTrue(); ProjectionApplicationResult result = projectionResult.get(); // Verify projections - assertEquals(expectedProjections, result.getProjections()); + assertThat(expectedProjections).isEqualTo(result.getProjections()); // Verify assignments List assignments = result.getAssignments(); @@ -3968,15 +3968,13 @@ private static void assertProjectionResult(Optional views = metadata.getViews(newSession(), Optional.of(viewName.getSchemaName())); - assertEquals(views.size(), 1); - assertEquals(views.get(viewName).getOriginalSql(), definition.getOriginalSql()); + assertThat(views.size()).isEqualTo(1); + assertThat(views.get(viewName).getOriginalSql()).isEqualTo(definition.getOriginalSql()); - assertTrue(metadata.listViews(newSession(), Optional.of(viewName.getSchemaName())).contains(viewName)); + assertThat(metadata.listViews(newSession(), Optional.of(viewName.getSchemaName())).contains(viewName)).isTrue(); } } @@ -4212,7 +4210,7 @@ protected void doCreateTable(SchemaTableName tableName, HiveStorageFormat storag // verify the metadata ConnectorTableMetadata tableMetadata = metadata.getTableMetadata(session, getTableHandle(metadata, tableName)); - assertEquals(filterNonHiddenColumnMetadata(tableMetadata.getColumns()), CREATE_TABLE_COLUMNS); + assertThat(filterNonHiddenColumnMetadata(tableMetadata.getColumns())).isEqualTo(CREATE_TABLE_COLUMNS); // verify the data MaterializedResult result = readTable(transaction, tableHandle, columnHandles, session, TupleDomain.all(), OptionalInt.empty(), Optional.of(storageFormat)); @@ -4220,13 +4218,13 @@ protected void doCreateTable(SchemaTableName tableName, HiveStorageFormat storag // verify the node version and query ID in table Table table = getMetastoreClient().getTable(tableName.getSchemaName(), tableName.getTableName()).get(); - assertEquals(table.getParameters().get(PRESTO_VERSION_NAME), TEST_SERVER_VERSION); - assertEquals(table.getParameters().get(PRESTO_QUERY_ID_NAME), queryId); + assertThat(table.getParameters().get(PRESTO_VERSION_NAME)).isEqualTo(TEST_SERVER_VERSION); + assertThat(table.getParameters().get(PRESTO_QUERY_ID_NAME)).isEqualTo(queryId); // verify basic statistics HiveBasicStatistics statistics = getBasicStatisticsForTable(transaction, tableName); - assertEquals(statistics.getRowCount().getAsLong(), CREATE_TABLE_DATA.getRowCount()); - assertEquals(statistics.getFileCount().getAsLong(), 1L); + assertThat(statistics.getRowCount().getAsLong()).isEqualTo(CREATE_TABLE_DATA.getRowCount()); + assertThat(statistics.getFileCount().getAsLong()).isEqualTo(1L); assertGreaterThan(statistics.getInMemoryDataSizeInBytes().getAsLong(), 0L); assertGreaterThan(statistics.getOnDiskDataSizeInBytes().getAsLong(), 0L); } @@ -4277,28 +4275,28 @@ protected void doCreateEmptyTable(SchemaTableName tableName, HiveStorageFormat s .setExtraInfo(Optional.ofNullable(columnExtraInfo(partitionedBy.contains(column.getName())))) .build()) .collect(toList()); - assertEquals(filterNonHiddenColumnMetadata(tableMetadata.getColumns()), expectedColumns); + assertThat(filterNonHiddenColumnMetadata(tableMetadata.getColumns())).isEqualTo(expectedColumns); // verify table format Table table = transaction.getMetastore().getTable(tableName.getSchemaName(), tableName.getTableName()).get(); - assertEquals(table.getStorage().getStorageFormat().getInputFormat(), storageFormat.getInputFormat()); + assertThat(table.getStorage().getStorageFormat().getInputFormat()).isEqualTo(storageFormat.getInputFormat()); // verify the node version and query ID - assertEquals(table.getParameters().get(PRESTO_VERSION_NAME), TEST_SERVER_VERSION); - assertEquals(table.getParameters().get(PRESTO_QUERY_ID_NAME), queryId); + assertThat(table.getParameters().get(PRESTO_VERSION_NAME)).isEqualTo(TEST_SERVER_VERSION); + assertThat(table.getParameters().get(PRESTO_QUERY_ID_NAME)).isEqualTo(queryId); // verify the table is empty List columnHandles = filterNonHiddenColumnHandles(metadata.getColumnHandles(session, tableHandle).values()); MaterializedResult result = readTable(transaction, tableHandle, columnHandles, session, TupleDomain.all(), OptionalInt.empty(), Optional.of(storageFormat)); - assertEquals(result.getRowCount(), 0); + assertThat(result.getRowCount()).isEqualTo(0); // verify basic statistics if (partitionedBy.isEmpty()) { HiveBasicStatistics statistics = getBasicStatisticsForTable(transaction, tableName); - assertEquals(statistics.getRowCount().getAsLong(), 0L); - assertEquals(statistics.getFileCount().getAsLong(), 0L); - assertEquals(statistics.getInMemoryDataSizeInBytes().getAsLong(), 0L); - assertEquals(statistics.getOnDiskDataSizeInBytes().getAsLong(), 0L); + assertThat(statistics.getRowCount().getAsLong()).isEqualTo(0L); + assertThat(statistics.getFileCount().getAsLong()).isEqualTo(0L); + assertThat(statistics.getInMemoryDataSizeInBytes().getAsLong()).isEqualTo(0L); + assertThat(statistics.getOnDiskDataSizeInBytes().getAsLong()).isEqualTo(0L); } } } @@ -4324,7 +4322,7 @@ private void doInsert(HiveStorageFormat storageFormat, SchemaTableName tableName // verify the metadata ConnectorTableMetadata tableMetadata = metadata.getTableMetadata(session, getTableHandle(metadata, tableName)); - assertEquals(filterNonHiddenColumnMetadata(tableMetadata.getColumns()), CREATE_TABLE_COLUMNS); + assertThat(filterNonHiddenColumnMetadata(tableMetadata.getColumns())).isEqualTo(CREATE_TABLE_COLUMNS); // verify the data resultBuilder.rows(CREATE_TABLE_DATA.getMaterializedRows()); @@ -4333,8 +4331,8 @@ private void doInsert(HiveStorageFormat storageFormat, SchemaTableName tableName // statistics HiveBasicStatistics tableStatistics = getBasicStatisticsForTable(transaction, tableName); - assertEquals(tableStatistics.getRowCount().orElse(0), CREATE_TABLE_DATA.getRowCount() * (i + 1L)); - assertEquals(tableStatistics.getFileCount().getAsLong(), i + 1L); + assertThat(tableStatistics.getRowCount().orElse(0)).isEqualTo(CREATE_TABLE_DATA.getRowCount() * (i + 1L)); + assertThat(tableStatistics.getFileCount().getAsLong()).isEqualTo(i + 1L); assertGreaterThan(tableStatistics.getInMemoryDataSizeInBytes().getAsLong(), 0L); assertGreaterThan(tableStatistics.getOnDiskDataSizeInBytes().getAsLong(), 0L); } @@ -4344,7 +4342,7 @@ private void doInsert(HiveStorageFormat storageFormat, SchemaTableName tableName Set existingFiles; try (Transaction transaction = newTransaction()) { existingFiles = listAllDataFiles(transaction, tableName.getSchemaName(), tableName.getTableName()); - assertFalse(existingFiles.isEmpty()); + assertThat(existingFiles.isEmpty()).isFalse(); } Location stagingPathRoot; @@ -4364,22 +4362,22 @@ private void doInsert(HiveStorageFormat storageFormat, SchemaTableName tableName // statistics, visible from within transaction HiveBasicStatistics tableStatistics = getBasicStatisticsForTable(transaction, tableName); - assertEquals(tableStatistics.getRowCount().getAsLong(), CREATE_TABLE_DATA.getRowCount() * 5L); + assertThat(tableStatistics.getRowCount().getAsLong()).isEqualTo(CREATE_TABLE_DATA.getRowCount() * 5L); try (Transaction otherTransaction = newTransaction()) { // statistics, not visible from outside transaction HiveBasicStatistics otherTableStatistics = getBasicStatisticsForTable(otherTransaction, tableName); - assertEquals(otherTableStatistics.getRowCount().getAsLong(), CREATE_TABLE_DATA.getRowCount() * 3L); + assertThat(otherTableStatistics.getRowCount().getAsLong()).isEqualTo(CREATE_TABLE_DATA.getRowCount() * 3L); } // verify we did not modify the table directory - assertEquals(listAllDataFiles(transaction, tableName.getSchemaName(), tableName.getTableName()), existingFiles); + assertThat(listAllDataFiles(transaction, tableName.getSchemaName(), tableName.getTableName())).isEqualTo(existingFiles); // verify all temp files start with the unique prefix stagingPathRoot = getStagingPathRoot(insertTableHandle); HdfsContext context = new HdfsContext(session); Set tempFiles = listAllDataFiles(context, stagingPathRoot); - assertTrue(!tempFiles.isEmpty()); + assertThat(!tempFiles.isEmpty()).isTrue(); for (String filePath : tempFiles) { assertThat(new Path(filePath).getName()).startsWith(session.getQueryId()); } @@ -4390,7 +4388,7 @@ private void doInsert(HiveStorageFormat storageFormat, SchemaTableName tableName // verify temp directory is empty HdfsContext context = new HdfsContext(newSession()); - assertTrue(listAllDataFiles(context, stagingPathRoot).isEmpty()); + assertThat(listAllDataFiles(context, stagingPathRoot).isEmpty()).isTrue(); // verify the data is unchanged try (Transaction transaction = newTransaction()) { @@ -4404,14 +4402,14 @@ private void doInsert(HiveStorageFormat storageFormat, SchemaTableName tableName assertEqualsIgnoreOrder(result.getMaterializedRows(), resultBuilder.build().getMaterializedRows()); // verify we did not modify the table directory - assertEquals(listAllDataFiles(transaction, tableName.getSchemaName(), tableName.getTableName()), existingFiles); + assertThat(listAllDataFiles(transaction, tableName.getSchemaName(), tableName.getTableName())).isEqualTo(existingFiles); } // verify statistics unchanged try (Transaction transaction = newTransaction()) { HiveBasicStatistics statistics = getBasicStatisticsForTable(transaction, tableName); - assertEquals(statistics.getRowCount().getAsLong(), CREATE_TABLE_DATA.getRowCount() * 3L); - assertEquals(statistics.getFileCount().getAsLong(), 3L); + assertThat(statistics.getRowCount().getAsLong()).isEqualTo(CREATE_TABLE_DATA.getRowCount() * 3L); + assertThat(statistics.getFileCount().getAsLong()).isEqualTo(3L); } } @@ -4446,7 +4444,7 @@ private void doInsertOverwriteUnpartitioned(SchemaTableName tableName) // verify the metadata ConnectorTableMetadata tableMetadata = metadata.getTableMetadata(session, getTableHandle(metadata, tableName)); - assertEquals(filterNonHiddenColumnMetadata(tableMetadata.getColumns()), CREATE_TABLE_COLUMNS); + assertThat(filterNonHiddenColumnMetadata(tableMetadata.getColumns())).isEqualTo(CREATE_TABLE_COLUMNS); // verify the data MaterializedResult result = readTable(transaction, tableHandle, columnHandles, session, TupleDomain.all(), OptionalInt.empty(), Optional.empty()); @@ -4454,8 +4452,8 @@ private void doInsertOverwriteUnpartitioned(SchemaTableName tableName) // statistics HiveBasicStatistics tableStatistics = getBasicStatisticsForTable(transaction, tableName); - assertEquals(tableStatistics.getRowCount().getAsLong(), overwriteData.getRowCount()); - assertEquals(tableStatistics.getFileCount().getAsLong(), 1L); + assertThat(tableStatistics.getRowCount().getAsLong()).isEqualTo(overwriteData.getRowCount()); + assertThat(tableStatistics.getFileCount().getAsLong()).isEqualTo(1L); assertGreaterThan(tableStatistics.getInMemoryDataSizeInBytes().getAsLong(), 0L); assertGreaterThan(tableStatistics.getOnDiskDataSizeInBytes().getAsLong(), 0L); } @@ -4465,7 +4463,7 @@ private void doInsertOverwriteUnpartitioned(SchemaTableName tableName) Set existingFiles; try (Transaction transaction = newTransaction()) { existingFiles = listAllDataFiles(transaction, tableName.getSchemaName(), tableName.getTableName()); - assertFalse(existingFiles.isEmpty()); + assertThat(existingFiles.isEmpty()).isFalse(); } Location stagingPathRoot; @@ -4486,22 +4484,22 @@ private void doInsertOverwriteUnpartitioned(SchemaTableName tableName) // statistics, visible from within transaction HiveBasicStatistics tableStatistics = getBasicStatisticsForTable(transaction, tableName); - assertEquals(tableStatistics.getRowCount().getAsLong(), overwriteData.getRowCount() * 4L); + assertThat(tableStatistics.getRowCount().getAsLong()).isEqualTo(overwriteData.getRowCount() * 4L); try (Transaction otherTransaction = newTransaction()) { // statistics, not visible from outside transaction HiveBasicStatistics otherTableStatistics = getBasicStatisticsForTable(otherTransaction, tableName); - assertEquals(otherTableStatistics.getRowCount().getAsLong(), overwriteData.getRowCount()); + assertThat(otherTableStatistics.getRowCount().getAsLong()).isEqualTo(overwriteData.getRowCount()); } // verify we did not modify the table directory - assertEquals(listAllDataFiles(transaction, tableName.getSchemaName(), tableName.getTableName()), existingFiles); + assertThat(listAllDataFiles(transaction, tableName.getSchemaName(), tableName.getTableName())).isEqualTo(existingFiles); // verify all temp files start with the unique prefix stagingPathRoot = getStagingPathRoot(insertTableHandle); HdfsContext context = new HdfsContext(session); Set tempFiles = listAllDataFiles(context, stagingPathRoot); - assertTrue(!tempFiles.isEmpty()); + assertThat(!tempFiles.isEmpty()).isTrue(); for (String filePath : tempFiles) { assertThat(new Path(filePath).getName()).startsWith(session.getQueryId()); } @@ -4512,7 +4510,7 @@ private void doInsertOverwriteUnpartitioned(SchemaTableName tableName) // verify temp directory is empty HdfsContext context = new HdfsContext(newSession()); - assertTrue(listAllDataFiles(context, stagingPathRoot).isEmpty()); + assertThat(listAllDataFiles(context, stagingPathRoot).isEmpty()).isTrue(); // verify the data is unchanged try (Transaction transaction = newTransaction()) { @@ -4526,14 +4524,14 @@ private void doInsertOverwriteUnpartitioned(SchemaTableName tableName) assertEqualsIgnoreOrder(result.getMaterializedRows(), overwriteData.getMaterializedRows()); // verify we did not modify the table directory - assertEquals(listAllDataFiles(transaction, tableName.getSchemaName(), tableName.getTableName()), existingFiles); + assertThat(listAllDataFiles(transaction, tableName.getSchemaName(), tableName.getTableName())).isEqualTo(existingFiles); } // verify statistics unchanged try (Transaction transaction = newTransaction()) { HiveBasicStatistics statistics = getBasicStatisticsForTable(transaction, tableName); - assertEquals(statistics.getRowCount().getAsLong(), overwriteData.getRowCount()); - assertEquals(statistics.getFileCount().getAsLong(), 1L); + assertThat(statistics.getRowCount().getAsLong()).isEqualTo(overwriteData.getRowCount()); + assertThat(statistics.getFileCount().getAsLong()).isEqualTo(1L); } } @@ -4642,11 +4640,11 @@ private void doInsertIntoNewPartition(HiveStorageFormat storageFormat, SchemaTab // verify the node versions in partitions Map> partitions = getMetastoreClient().getPartitionsByNames(table, partitionNames); - assertEquals(partitions.size(), partitionNames.size()); + assertThat(partitions.size()).isEqualTo(partitionNames.size()); for (String partitionName : partitionNames) { Partition partition = partitions.get(partitionName).get(); - assertEquals(partition.getParameters().get(PRESTO_VERSION_NAME), TEST_SERVER_VERSION); - assertEquals(partition.getParameters().get(PRESTO_QUERY_ID_NAME), queryId); + assertThat(partition.getParameters().get(PRESTO_VERSION_NAME)).isEqualTo(TEST_SERVER_VERSION); + assertThat(partition.getParameters().get(PRESTO_QUERY_ID_NAME)).isEqualTo(queryId); } // load the new table @@ -4662,13 +4660,13 @@ private void doInsertIntoNewPartition(HiveStorageFormat storageFormat, SchemaTab // test rollback existingFiles = listAllDataFiles(transaction, tableName.getSchemaName(), tableName.getTableName()); - assertFalse(existingFiles.isEmpty()); + assertThat(existingFiles.isEmpty()).isFalse(); // test statistics for (String partitionName : partitionNames) { HiveBasicStatistics partitionStatistics = getBasicStatisticsForPartition(transaction, tableName, COLUMN_NAMES_PARTITIONED, partitionName); - assertEquals(partitionStatistics.getRowCount().getAsLong(), 1L); - assertEquals(partitionStatistics.getFileCount().getAsLong(), 1L); + assertThat(partitionStatistics.getRowCount().getAsLong()).isEqualTo(1L); + assertThat(partitionStatistics.getFileCount().getAsLong()).isEqualTo(1L); assertGreaterThan(partitionStatistics.getInMemoryDataSizeInBytes().getAsLong(), 0L); assertGreaterThan(partitionStatistics.getOnDiskDataSizeInBytes().getAsLong(), 0L); } @@ -4691,7 +4689,7 @@ private void doInsertIntoNewPartition(HiveStorageFormat storageFormat, SchemaTab // verify all temp files start with the unique prefix HdfsContext context = new HdfsContext(session); Set tempFiles = listAllDataFiles(context, getStagingPathRoot(insertTableHandle)); - assertTrue(!tempFiles.isEmpty()); + assertThat(!tempFiles.isEmpty()).isTrue(); for (String filePath : tempFiles) { assertThat(new Path(filePath).getName()).startsWith(session.getQueryId()); } @@ -4712,11 +4710,11 @@ private void doInsertIntoNewPartition(HiveStorageFormat storageFormat, SchemaTab assertEqualsIgnoreOrder(result.getMaterializedRows(), CREATE_TABLE_PARTITIONED_DATA.getMaterializedRows()); // verify we did not modify the table directory - assertEquals(listAllDataFiles(transaction, tableName.getSchemaName(), tableName.getTableName()), existingFiles); + assertThat(listAllDataFiles(transaction, tableName.getSchemaName(), tableName.getTableName())).isEqualTo(existingFiles); // verify temp directory is empty HdfsContext context = new HdfsContext(session); - assertTrue(listAllDataFiles(context, stagingPathRoot).isEmpty()); + assertThat(listAllDataFiles(context, stagingPathRoot).isEmpty()).isTrue(); } } @@ -4776,8 +4774,8 @@ private void doInsertIntoExistingPartition(HiveStorageFormat storageFormat, Sche // test statistics for (String partitionName : partitionNames) { HiveBasicStatistics statistics = getBasicStatisticsForPartition(transaction, tableName, COLUMN_NAMES_PARTITIONED, partitionName); - assertEquals(statistics.getRowCount().getAsLong(), i + 1L); - assertEquals(statistics.getFileCount().getAsLong(), i + 1L); + assertThat(statistics.getRowCount().getAsLong()).isEqualTo(i + 1L); + assertThat(statistics.getFileCount().getAsLong()).isEqualTo(i + 1L); assertGreaterThan(statistics.getInMemoryDataSizeInBytes().getAsLong(), 0L); assertGreaterThan(statistics.getOnDiskDataSizeInBytes().getAsLong(), 0L); } @@ -4792,7 +4790,7 @@ private void doInsertIntoExistingPartition(HiveStorageFormat storageFormat, Sche ConnectorSession session = newSession(); existingFiles = listAllDataFiles(transaction, tableName.getSchemaName(), tableName.getTableName()); - assertFalse(existingFiles.isEmpty()); + assertThat(existingFiles.isEmpty()).isFalse(); ConnectorTableHandle tableHandle = getTableHandle(metadata, tableName); @@ -4808,7 +4806,7 @@ private void doInsertIntoExistingPartition(HiveStorageFormat storageFormat, Sche // verify all temp files start with the unique prefix HdfsContext context = new HdfsContext(session); Set tempFiles = listAllDataFiles(context, getStagingPathRoot(insertTableHandle)); - assertTrue(!tempFiles.isEmpty()); + assertThat(!tempFiles.isEmpty()).isTrue(); for (String filePath : tempFiles) { assertThat(new Path(filePath).getName()).startsWith(session.getQueryId()); } @@ -4818,7 +4816,7 @@ private void doInsertIntoExistingPartition(HiveStorageFormat storageFormat, Sche .orElseThrow(() -> new AssertionError("Table does not exist: " + tableName)); for (String partitionName : partitionNames) { HiveBasicStatistics partitionStatistics = getBasicStatisticsForPartition(transaction, tableName, COLUMN_NAMES_PARTITIONED, partitionName); - assertEquals(partitionStatistics.getRowCount().getAsLong(), 5L); + assertThat(partitionStatistics.getRowCount().getAsLong()).isEqualTo(5L); } // rollback insert @@ -4837,18 +4835,18 @@ private void doInsertIntoExistingPartition(HiveStorageFormat storageFormat, Sche assertEqualsIgnoreOrder(result.getMaterializedRows(), resultBuilder.build().getMaterializedRows()); // verify we did not modify the table directory - assertEquals(listAllDataFiles(transaction, tableName.getSchemaName(), tableName.getTableName()), existingFiles); + assertThat(listAllDataFiles(transaction, tableName.getSchemaName(), tableName.getTableName())).isEqualTo(existingFiles); // verify temp directory is empty HdfsContext hdfsContext = new HdfsContext(session); - assertTrue(listAllDataFiles(hdfsContext, stagingPathRoot).isEmpty()); + assertThat(listAllDataFiles(hdfsContext, stagingPathRoot).isEmpty()).isTrue(); // verify statistics have been rolled back List partitionNames = transaction.getMetastore().getPartitionNames(tableName.getSchemaName(), tableName.getTableName()) .orElseThrow(() -> new AssertionError("Table does not exist: " + tableName)); for (String partitionName : partitionNames) { HiveBasicStatistics partitionStatistics = getBasicStatisticsForPartition(transaction, tableName, COLUMN_NAMES_PARTITIONED, partitionName); - assertEquals(partitionStatistics.getRowCount().getAsLong(), 3L); + assertThat(partitionStatistics.getRowCount().getAsLong()).isEqualTo(3L); } } } @@ -4960,7 +4958,7 @@ private String insertData(SchemaTableName tableName, MaterializedResult data, Ma if (!writePath.equals(targetPath)) { HdfsContext context = new HdfsContext(newSession()); FileSystem fileSystem = hdfsEnvironment.getFileSystem(context, new Path(writePath.toString())); - assertFalse(fileSystem.exists(new Path(writePath.toString()))); + assertThat(fileSystem.exists(new Path(writePath.toString()))).isFalse(); } return queryId; @@ -4991,7 +4989,7 @@ private void doTestMetadataDelete(HiveStorageFormat storageFormat, SchemaTableNa // verify table directory is not empty Set filesAfterInsert = listAllDataFiles(transaction, tableName.getSchemaName(), tableName.getTableName()); - assertFalse(filesAfterInsert.isEmpty()); + assertThat(filesAfterInsert.isEmpty()).isFalse(); // verify the data ConnectorTableHandle tableHandle = getTableHandle(metadata, tableName); @@ -5067,7 +5065,7 @@ private void doTestMetadataDelete(HiveStorageFormat storageFormat, SchemaTableNa // verify table directory is empty Set filesAfterDelete = listAllDataFiles(transaction, tableName.getSchemaName(), tableName.getTableName()); - assertTrue(filesAfterDelete.isEmpty()); + assertThat(filesAfterDelete.isEmpty()).isTrue(); } } @@ -5093,7 +5091,7 @@ protected void assertGetRecords(String tableName, HiveStorageFormat hiveStorageF protected HiveSplit getHiveSplit(ConnectorTableHandle tableHandle, Transaction transaction, ConnectorSession session) { List splits = getAllSplits(tableHandle, transaction, session); - assertEquals(splits.size(), 1); + assertThat(splits.size()).isEqualTo(1); return (HiveSplit) getOnlyElement(splits); } @@ -5130,49 +5128,49 @@ protected void assertGetRecords( index = columnIndex.get("t_string"); value = row.getField(index); if (rowNumber % 19 == 0) { - assertNull(value); + assertThat(value).isNull(); } else if (rowNumber % 19 == 1) { - assertEquals(value, ""); + assertThat(value).isEqualTo(""); } else { - assertEquals(value, "test"); + assertThat(value).isEqualTo("test"); } // NUMBERS - assertEquals(row.getField(columnIndex.get("t_tinyint")), (byte) (1 + rowNumber)); - assertEquals(row.getField(columnIndex.get("t_smallint")), (short) (2 + rowNumber)); - assertEquals(row.getField(columnIndex.get("t_int")), (int) (3 + rowNumber)); + assertThat(row.getField(columnIndex.get("t_tinyint"))).isEqualTo((byte) (1 + rowNumber)); + assertThat(row.getField(columnIndex.get("t_smallint"))).isEqualTo((short) (2 + rowNumber)); + assertThat(row.getField(columnIndex.get("t_int"))).isEqualTo((int) (3 + rowNumber)); index = columnIndex.get("t_bigint"); if ((rowNumber % 13) == 0) { - assertNull(row.getField(index)); + assertThat(row.getField(index)).isNull(); } else { - assertEquals(row.getField(index), 4 + rowNumber); + assertThat(row.getField(index)).isEqualTo(4 + rowNumber); } - assertEquals((Float) row.getField(columnIndex.get("t_float")), 5.1f + rowNumber, 0.001); - assertEquals(row.getField(columnIndex.get("t_double")), 6.2 + rowNumber); + assertThat((Float) row.getField(columnIndex.get("t_float"))).isCloseTo(5.1f + rowNumber, offset(0.001f)); + assertThat(row.getField(columnIndex.get("t_double"))).isEqualTo(6.2 + rowNumber); // BOOLEAN index = columnIndex.get("t_boolean"); if ((rowNumber % 3) == 2) { - assertNull(row.getField(index)); + assertThat(row.getField(index)).isNull(); } else { - assertEquals(row.getField(index), (rowNumber % 3) != 0); + assertThat(row.getField(index)).isEqualTo((rowNumber % 3) != 0); } // TIMESTAMP index = columnIndex.get("t_timestamp"); if (index != null) { if ((rowNumber % 17) == 0) { - assertNull(row.getField(index)); + assertThat(row.getField(index)).isNull(); } else { SqlTimestamp expected = sqlTimestampOf(3, 2011, 5, 6, 7, 8, 9, 123); - assertEquals(row.getField(index), expected); + assertThat(row.getField(index)).isEqualTo(expected); } } @@ -5180,10 +5178,10 @@ else if (rowNumber % 19 == 1) { index = columnIndex.get("t_binary"); if (index != null) { if ((rowNumber % 23) == 0) { - assertNull(row.getField(index)); + assertThat(row.getField(index)).isNull(); } else { - assertEquals(row.getField(index), new SqlVarbinary("test binary".getBytes(UTF_8))); + assertThat(row.getField(index)).isEqualTo(new SqlVarbinary("test binary".getBytes(UTF_8))); } } @@ -5191,11 +5189,11 @@ else if (rowNumber % 19 == 1) { index = columnIndex.get("t_date"); if (index != null) { if ((rowNumber % 37) == 0) { - assertNull(row.getField(index)); + assertThat(row.getField(index)).isNull(); } else { SqlDate expected = new SqlDate(toIntExact(MILLISECONDS.toDays(new DateTime(2013, 8, 9, 0, 0, 0, UTC).getMillis()))); - assertEquals(row.getField(index), expected); + assertThat(row.getField(index)).isEqualTo(expected); } } @@ -5204,20 +5202,20 @@ else if (rowNumber % 19 == 1) { if (index != null) { value = row.getField(index); if (rowNumber % 39 == 0) { - assertNull(value); + assertThat(value).isNull(); } else if (rowNumber % 39 == 1) { // https://issues.apache.org/jira/browse/HIVE-13289 // RCBINARY reads empty VARCHAR as null if (hiveStorageFormat == RCBINARY) { - assertNull(value); + assertThat(value).isNull(); } else { - assertEquals(value, ""); + assertThat(value).isEqualTo(""); } } else { - assertEquals(value, "test varchar"); + assertThat(value).isEqualTo("test varchar"); } } @@ -5226,10 +5224,10 @@ else if (rowNumber % 39 == 1) { if (index != null) { value = row.getField(index); if ((rowNumber % 41) == 0) { - assertNull(value); + assertThat(value).isNull(); } else { - assertEquals(value, (rowNumber % 41) == 1 ? " " : "test char "); + assertThat(value).isEqualTo((rowNumber % 41) == 1 ? " " : "test char "); } } @@ -5237,10 +5235,10 @@ else if (rowNumber % 39 == 1) { index = columnIndex.get("t_map"); if (index != null) { if ((rowNumber % 27) == 0) { - assertNull(row.getField(index)); + assertThat(row.getField(index)).isNull(); } else { - assertEquals(row.getField(index), ImmutableMap.of("test key", "test value")); + assertThat(row.getField(index)).isEqualTo(ImmutableMap.of("test key", "test value")); } } @@ -5248,10 +5246,10 @@ else if (rowNumber % 39 == 1) { index = columnIndex.get("t_array_string"); if (index != null) { if ((rowNumber % 29) == 0) { - assertNull(row.getField(index)); + assertThat(row.getField(index)).isNull(); } else { - assertEquals(row.getField(index), ImmutableList.of("abc", "xyz", "data")); + assertThat(row.getField(index)).isEqualTo(ImmutableList.of("abc", "xyz", "data")); } } @@ -5259,11 +5257,11 @@ else if (rowNumber % 39 == 1) { index = columnIndex.get("t_array_timestamp"); if (index != null) { if ((rowNumber % 43) == 0) { - assertNull(row.getField(index)); + assertThat(row.getField(index)).isNull(); } else { SqlTimestamp expected = sqlTimestampOf(3, LocalDateTime.of(2011, 5, 6, 7, 8, 9, 123_000_000)); - assertEquals(row.getField(index), ImmutableList.of(expected)); + assertThat(row.getField(index)).isEqualTo(ImmutableList.of(expected)); } } @@ -5271,12 +5269,12 @@ else if (rowNumber % 39 == 1) { index = columnIndex.get("t_array_struct"); if (index != null) { if ((rowNumber % 31) == 0) { - assertNull(row.getField(index)); + assertThat(row.getField(index)).isNull(); } else { List expected1 = ImmutableList.of("test abc", 0.1); List expected2 = ImmutableList.of("test xyz", 0.2); - assertEquals(row.getField(index), ImmutableList.of(expected1, expected2)); + assertThat(row.getField(index)).isEqualTo(ImmutableList.of(expected1, expected2)); } } @@ -5284,14 +5282,14 @@ else if (rowNumber % 39 == 1) { index = columnIndex.get("t_struct"); if (index != null) { if ((rowNumber % 31) == 0) { - assertNull(row.getField(index)); + assertThat(row.getField(index)).isNull(); } else { - assertTrue(row.getField(index) instanceof List); + assertThat(row.getField(index) instanceof List).isTrue(); List values = (List) row.getField(index); - assertEquals(values.size(), 2); - assertEquals(values.get(0), "test abc"); - assertEquals(values.get(1), 0.1); + assertThat(values.size()).isEqualTo(2); + assertThat(values.get(0)).isEqualTo("test abc"); + assertThat(values.get(1)).isEqualTo(0.1); } } @@ -5299,27 +5297,27 @@ else if (rowNumber % 39 == 1) { index = columnIndex.get("t_complex"); if (index != null) { if ((rowNumber % 33) == 0) { - assertNull(row.getField(index)); + assertThat(row.getField(index)).isNull(); } else { List expected1 = ImmutableList.of("test abc", 0.1); List expected2 = ImmutableList.of("test xyz", 0.2); - assertEquals(row.getField(index), ImmutableMap.of(1, ImmutableList.of(expected1, expected2))); + assertThat(row.getField(index)).isEqualTo(ImmutableMap.of(1, ImmutableList.of(expected1, expected2))); } } // NEW COLUMN - assertNull(row.getField(columnIndex.get("new_column"))); + assertThat(row.getField(columnIndex.get("new_column"))).isNull(); long newCompletedBytes = pageSource.getCompletedBytes(); - assertTrue(newCompletedBytes >= completedBytes); + assertThat(newCompletedBytes >= completedBytes).isTrue(); // some formats (e.g., parquet) over read the data by a bit assertLessThanOrEqual(newCompletedBytes, hiveSplit.getLength() + (100 * 1024)); completedBytes = newCompletedBytes; } assertLessThanOrEqual(completedBytes, hiveSplit.getLength() + (100 * 1024)); - assertEquals(rowNumber, 100); + assertThat(rowNumber).isEqualTo(100); } finally { pageSource.close(); @@ -5378,7 +5376,7 @@ protected MaterializedResult readTable( tableHandle = applyFilter(transaction.getMetadata(), tableHandle, new Constraint(tupleDomain)); List splits = getAllSplits(getSplits(splitManager, transaction, session, tableHandle)); if (expectedSplitCount.isPresent()) { - assertEquals(splits.size(), expectedSplitCount.getAsInt()); + assertThat(splits.size()).isEqualTo(expectedSplitCount.getAsInt()); } ImmutableList.Builder allRows = ImmutableList.builder(); @@ -5521,10 +5519,12 @@ else if (column.getType() instanceof MapType) { private static void assertPrimitiveField(Map map, String name, Type type, boolean partitionKey) { - assertTrue(map.containsKey(name)); + assertThat(map.containsKey(name)).isTrue(); ColumnMetadata column = map.get(name); - assertEquals(column.getType(), type, name); - assertEquals(column.getExtraInfo(), columnExtraInfo(partitionKey)); + assertThat(column.getType()) + .describedAs(name) + .isEqualTo(type); + assertThat(column.getExtraInfo()).isEqualTo(columnExtraInfo(partitionKey)); } protected static ImmutableMap indexColumns(List columnHandles) @@ -5658,7 +5658,7 @@ protected void createEmptyTable( HdfsContext context = new HdfsContext(newSession()); List targetDirectoryList = listDirectory(context, targetPath); - assertEquals(targetDirectoryList, ImmutableList.of()); + assertThat(targetDirectoryList).isEqualTo(ImmutableList.of()); } private void alterBucketProperty(SchemaTableName schemaTableName, Optional bucketProperty) @@ -5748,9 +5748,9 @@ public void testPreferredInsertLayout() ConnectorSession session = newSession(); ConnectorTableHandle tableHandle = getTableHandle(metadata, tableName); Optional insertLayout = metadata.getInsertLayout(session, tableHandle); - assertTrue(insertLayout.isPresent()); - assertFalse(insertLayout.get().getPartitioning().isPresent()); - assertEquals(insertLayout.get().getPartitionColumns(), ImmutableList.of(partitioningColumn.getName())); + assertThat(insertLayout.isPresent()).isTrue(); + assertThat(insertLayout.get().getPartitioning().isPresent()).isFalse(); + assertThat(insertLayout.get().getPartitionColumns()).isEqualTo(ImmutableList.of(partitioningColumn.getName())); } } finally { @@ -5788,18 +5788,18 @@ protected void insertBucketedTableLayout(boolean transactional) ConnectorSession session = newSession(); ConnectorTableHandle tableHandle = getTableHandle(metadata, tableName); Optional insertLayout = metadata.getInsertLayout(session, tableHandle); - assertTrue(insertLayout.isPresent()); + assertThat(insertLayout.isPresent()).isTrue(); ConnectorPartitioningHandle partitioningHandle = new HivePartitioningHandle( bucketProperty.getBucketingVersion(), bucketProperty.getBucketCount(), ImmutableList.of(HIVE_STRING), OptionalInt.empty(), false); - assertEquals(insertLayout.get().getPartitioning(), Optional.of(partitioningHandle)); - assertEquals(insertLayout.get().getPartitionColumns(), ImmutableList.of("column1")); + assertThat(insertLayout.get().getPartitioning()).isEqualTo(Optional.of(partitioningHandle)); + assertThat(insertLayout.get().getPartitionColumns()).isEqualTo(ImmutableList.of("column1")); ConnectorBucketNodeMap connectorBucketNodeMap = nodePartitioningProvider.getBucketNodeMapping(transaction.getTransactionHandle(), session, partitioningHandle).orElseThrow(); - assertEquals(connectorBucketNodeMap.getBucketCount(), 4); - assertFalse(connectorBucketNodeMap.hasFixedMapping()); + assertThat(connectorBucketNodeMap.getBucketCount()).isEqualTo(4); + assertThat(connectorBucketNodeMap.hasFixedMapping()).isFalse(); } } finally { @@ -5838,18 +5838,18 @@ protected void insertPartitionedBucketedTableLayout(boolean transactional) ConnectorSession session = newSession(); ConnectorTableHandle tableHandle = getTableHandle(metadata, tableName); Optional insertLayout = metadata.getInsertLayout(session, tableHandle); - assertTrue(insertLayout.isPresent()); + assertThat(insertLayout.isPresent()).isTrue(); ConnectorPartitioningHandle partitioningHandle = new HivePartitioningHandle( bucketProperty.getBucketingVersion(), bucketProperty.getBucketCount(), ImmutableList.of(HIVE_STRING), OptionalInt.empty(), true); - assertEquals(insertLayout.get().getPartitioning(), Optional.of(partitioningHandle)); - assertEquals(insertLayout.get().getPartitionColumns(), ImmutableList.of("column1", "column2")); + assertThat(insertLayout.get().getPartitioning()).isEqualTo(Optional.of(partitioningHandle)); + assertThat(insertLayout.get().getPartitionColumns()).isEqualTo(ImmutableList.of("column1", "column2")); ConnectorBucketNodeMap connectorBucketNodeMap = nodePartitioningProvider.getBucketNodeMapping(transaction.getTransactionHandle(), session, partitioningHandle).orElseThrow(); - assertEquals(connectorBucketNodeMap.getBucketCount(), 32); - assertFalse(connectorBucketNodeMap.hasFixedMapping()); + assertThat(connectorBucketNodeMap.getBucketCount()).isEqualTo(32); + assertThat(connectorBucketNodeMap.hasFixedMapping()).isFalse(); } } finally { @@ -5875,9 +5875,9 @@ public void testPreferredCreateTableLayout() BUCKETED_BY_PROPERTY, ImmutableList.of(), BUCKET_COUNT_PROPERTY, 0, SORTED_BY_PROPERTY, ImmutableList.of()))); - assertTrue(newTableLayout.isPresent()); - assertFalse(newTableLayout.get().getPartitioning().isPresent()); - assertEquals(newTableLayout.get().getPartitionColumns(), ImmutableList.of("column2")); + assertThat(newTableLayout.isPresent()).isTrue(); + assertThat(newTableLayout.get().getPartitioning().isPresent()).isFalse(); + assertThat(newTableLayout.get().getPartitionColumns()).isEqualTo(ImmutableList.of("column2")); } } @@ -5899,18 +5899,18 @@ public void testCreateBucketedTableLayout() BUCKETED_BY_PROPERTY, ImmutableList.of("column1"), BUCKET_COUNT_PROPERTY, 10, SORTED_BY_PROPERTY, ImmutableList.of()))); - assertTrue(newTableLayout.isPresent()); + assertThat(newTableLayout.isPresent()).isTrue(); ConnectorPartitioningHandle partitioningHandle = new HivePartitioningHandle( BUCKETING_V1, 10, ImmutableList.of(HIVE_LONG), OptionalInt.empty(), false); - assertEquals(newTableLayout.get().getPartitioning(), Optional.of(partitioningHandle)); - assertEquals(newTableLayout.get().getPartitionColumns(), ImmutableList.of("column1")); + assertThat(newTableLayout.get().getPartitioning()).isEqualTo(Optional.of(partitioningHandle)); + assertThat(newTableLayout.get().getPartitionColumns()).isEqualTo(ImmutableList.of("column1")); ConnectorBucketNodeMap connectorBucketNodeMap = nodePartitioningProvider.getBucketNodeMapping(transaction.getTransactionHandle(), session, partitioningHandle).orElseThrow(); - assertEquals(connectorBucketNodeMap.getBucketCount(), 10); - assertFalse(connectorBucketNodeMap.hasFixedMapping()); + assertThat(connectorBucketNodeMap.getBucketCount()).isEqualTo(10); + assertThat(connectorBucketNodeMap.hasFixedMapping()).isFalse(); } } @@ -5932,18 +5932,18 @@ public void testCreatePartitionedBucketedTableLayout() BUCKETED_BY_PROPERTY, ImmutableList.of("column1"), BUCKET_COUNT_PROPERTY, 10, SORTED_BY_PROPERTY, ImmutableList.of()))); - assertTrue(newTableLayout.isPresent()); + assertThat(newTableLayout.isPresent()).isTrue(); ConnectorPartitioningHandle partitioningHandle = new HivePartitioningHandle( BUCKETING_V1, 10, ImmutableList.of(HIVE_LONG), OptionalInt.empty(), true); - assertEquals(newTableLayout.get().getPartitioning(), Optional.of(partitioningHandle)); - assertEquals(newTableLayout.get().getPartitionColumns(), ImmutableList.of("column1", "column2")); + assertThat(newTableLayout.get().getPartitioning()).isEqualTo(Optional.of(partitioningHandle)); + assertThat(newTableLayout.get().getPartitionColumns()).isEqualTo(ImmutableList.of("column1", "column2")); ConnectorBucketNodeMap connectorBucketNodeMap = nodePartitioningProvider.getBucketNodeMapping(transaction.getTransactionHandle(), session, partitioningHandle).orElseThrow(); - assertEquals(connectorBucketNodeMap.getBucketCount(), 32); - assertFalse(connectorBucketNodeMap.hasFixedMapping()); + assertThat(connectorBucketNodeMap.getBucketCount()).isEqualTo(32); + assertThat(connectorBucketNodeMap.hasFixedMapping()).isFalse(); } } @@ -6072,7 +6072,7 @@ private void doTestTransactionDeleteInsert( metadata.finishInsert(session, insertTableHandle, fragments, ImmutableList.of()); rollbackIfEquals(tag, ROLLBACK_AFTER_FINISH_INSERT); - assertEquals(tag, COMMIT); + assertThat(tag).isEqualTo(COMMIT); if (conflictTrigger.isPresent()) { JsonCodec partitionUpdateCodec = JsonCodec.jsonCodec(PartitionUpdate.class); @@ -6084,7 +6084,7 @@ private void doTestTransactionDeleteInsert( } transaction.commit(); if (conflictTrigger.isPresent()) { - assertTrue(expectQuerySucceed); + assertThat(expectQuerySucceed).isTrue(); conflictTrigger.get().verifyAndCleanup(session, tableName); } } @@ -6092,7 +6092,7 @@ private void doTestTransactionDeleteInsert( transaction.rollback(); } catch (TrinoException e) { - assertFalse(expectQuerySucceed); + assertThat(expectQuerySucceed).isFalse(); if (conflictTrigger.isPresent()) { conflictTrigger.get().verifyAndCleanup(newSession(), tableName); } @@ -6103,7 +6103,7 @@ private void doTestTransactionDeleteInsert( if (writePath != null && !writePath.equals(targetPath)) { HdfsContext context = new HdfsContext(newSession()); FileSystem fileSystem = hdfsEnvironment.getFileSystem(context, new Path(writePath.toString())); - assertFalse(fileSystem.exists(new Path(writePath.toString()))); + assertThat(fileSystem.exists(new Path(writePath.toString()))).isFalse(); } try (Transaction transaction = newTransaction()) { @@ -6246,7 +6246,7 @@ public void verifyAndCleanup(ConnectorSession session, SchemaTableName tableName Optional actualPartition = metastoreClient.getPartition(table, toPartitionValues(partitionNameToConflict)); // Make sure the partition inserted to trigger conflict was not overwritten // Checking storage location is sufficient because implement never uses .../pk1=a/pk2=a2 as the directory for partition [b, b2]. - assertEquals(actualPartition.get().getStorage().getLocation(), conflictPartition.getStorage().getLocation()); + assertThat(actualPartition.get().getStorage().getLocation()).isEqualTo(conflictPartition.getStorage().getLocation()); metastoreClient.dropPartition(tableName.getSchemaName(), tableName.getTableName(), conflictPartition.getValues(), false); } } @@ -6299,7 +6299,7 @@ public void triggerConflict(ConnectorSession session, SchemaTableName tableName, public void verifyAndCleanup(ConnectorSession session, SchemaTableName tableName) throws IOException { - assertEquals(listDirectory(context, path), ImmutableList.of()); + assertThat(listDirectory(context, path)).isEqualTo(ImmutableList.of()); hdfsEnvironment.getFileSystem(context, path).delete(path, false); } } @@ -6320,7 +6320,7 @@ public void triggerConflict(ConnectorSession session, SchemaTableName tableName, break; } } - assertNotNull(path); + assertThat(path).isNotNull(); context = new HdfsContext(session); FileSystem fileSystem = hdfsEnvironment.getFileSystem(context, path); @@ -6333,7 +6333,7 @@ public void verifyAndCleanup(ConnectorSession session, SchemaTableName tableName { // The file we added to trigger a conflict was cleaned up because it matches the query prefix. // Consider this the same as a network failure that caused the successful creation of file not reported to the caller. - assertFalse(hdfsEnvironment.getFileSystem(context, path).exists(path)); + assertThat(hdfsEnvironment.getFileSystem(context, path).exists(path)).isFalse(); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java index 2ead1071c9fe..ac0f980f7788 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java @@ -134,9 +134,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -348,12 +345,24 @@ public void testGetFileStatus() Path filePath = new Path(tablePath, "test_table.csv"); FileSystem fs = hdfsEnvironment.getFileSystem(TESTING_CONTEXT, basePath); - assertTrue(fs.getFileStatus(basePath).isDirectory(), "basePath should be considered a directory"); - assertTrue(fs.getFileStatus(tablePath).isDirectory(), "tablePath should be considered a directory"); - assertTrue(fs.getFileStatus(filePath).isFile(), "filePath should be considered a file"); - assertFalse(fs.getFileStatus(filePath).isDirectory(), "filePath should not be considered a directory"); - assertFalse(fs.exists(new Path(basePath, "foo-" + randomUUID())), "foo-random path should be found not to exist"); - assertFalse(fs.exists(new Path(basePath, "foo")), "foo path should be found not to exist"); + assertThat(fs.getFileStatus(basePath).isDirectory()) + .describedAs("basePath should be considered a directory") + .isTrue(); + assertThat(fs.getFileStatus(tablePath).isDirectory()) + .describedAs("tablePath should be considered a directory") + .isTrue(); + assertThat(fs.getFileStatus(filePath).isFile()) + .describedAs("filePath should be considered a file") + .isTrue(); + assertThat(fs.getFileStatus(filePath).isDirectory()) + .describedAs("filePath should not be considered a directory") + .isFalse(); + assertThat(fs.exists(new Path(basePath, "foo-" + randomUUID()))) + .describedAs("foo-random path should be found not to exist") + .isFalse(); + assertThat(fs.exists(new Path(basePath, "foo"))) + .describedAs("foo path should be found not to exist") + .isFalse(); } @Test @@ -362,60 +371,60 @@ public void testRename() { Path basePath = new Path(getBasePath(), randomUUID().toString()); FileSystem fs = hdfsEnvironment.getFileSystem(TESTING_CONTEXT, basePath); - assertFalse(fs.exists(basePath)); + assertThat(fs.exists(basePath)).isFalse(); // create file foo.txt Path path = new Path(basePath, "foo.txt"); - assertTrue(fs.createNewFile(path)); - assertTrue(fs.exists(path)); + assertThat(fs.createNewFile(path)).isTrue(); + assertThat(fs.exists(path)).isTrue(); // rename foo.txt to bar.txt when bar does not exist Path newPath = new Path(basePath, "bar.txt"); - assertFalse(fs.exists(newPath)); - assertTrue(fs.rename(path, newPath)); - assertFalse(fs.exists(path)); - assertTrue(fs.exists(newPath)); + assertThat(fs.exists(newPath)).isFalse(); + assertThat(fs.rename(path, newPath)).isTrue(); + assertThat(fs.exists(path)).isFalse(); + assertThat(fs.exists(newPath)).isTrue(); // rename foo.txt to foo.txt when foo.txt does not exist - assertFalse(fs.rename(path, path)); + assertThat(fs.rename(path, path)).isFalse(); // create file foo.txt and rename to existing bar.txt - assertTrue(fs.createNewFile(path)); - assertFalse(fs.rename(path, newPath)); + assertThat(fs.createNewFile(path)).isTrue(); + assertThat(fs.rename(path, newPath)).isFalse(); // rename foo.txt to foo.txt when foo.txt exists - assertEquals(fs.rename(path, path), getRawFileSystem(fs) instanceof AzureBlobFileSystem); + assertThat(fs.rename(path, path)).isEqualTo(getRawFileSystem(fs) instanceof AzureBlobFileSystem); // delete foo.txt - assertTrue(fs.delete(path, false)); - assertFalse(fs.exists(path)); + assertThat(fs.delete(path, false)).isTrue(); + assertThat(fs.exists(path)).isFalse(); // create directory source with file Path source = new Path(basePath, "source"); - assertTrue(fs.createNewFile(new Path(source, "test.txt"))); + assertThat(fs.createNewFile(new Path(source, "test.txt"))).isTrue(); // rename source to non-existing target Path target = new Path(basePath, "target"); - assertFalse(fs.exists(target)); - assertTrue(fs.rename(source, target)); - assertFalse(fs.exists(source)); - assertTrue(fs.exists(target)); + assertThat(fs.exists(target)).isFalse(); + assertThat(fs.rename(source, target)).isTrue(); + assertThat(fs.exists(source)).isFalse(); + assertThat(fs.exists(target)).isTrue(); // create directory source with file - assertTrue(fs.createNewFile(new Path(source, "test.txt"))); + assertThat(fs.createNewFile(new Path(source, "test.txt"))).isTrue(); // rename source to existing target - assertTrue(fs.rename(source, target)); - assertFalse(fs.exists(source)); + assertThat(fs.rename(source, target)).isTrue(); + assertThat(fs.exists(source)).isFalse(); target = new Path(target, "source"); - assertTrue(fs.exists(target)); - assertTrue(fs.exists(new Path(target, "test.txt"))); + assertThat(fs.exists(target)).isTrue(); + assertThat(fs.exists(new Path(target, "test.txt"))).isTrue(); // delete target target = new Path(basePath, "target"); - assertTrue(fs.exists(target)); - assertTrue(fs.delete(target, true)); - assertFalse(fs.exists(target)); + assertThat(fs.exists(target)).isTrue(); + assertThat(fs.delete(target, true)).isTrue(); + assertThat(fs.exists(target)).isFalse(); // cleanup fs.delete(basePath, true); @@ -627,17 +636,17 @@ public void testDirectoryWithTrailingSpace() { Path basePath = new Path(getBasePath(), randomUUID().toString()); FileSystem fs = hdfsEnvironment.getFileSystem(TESTING_CONTEXT, basePath); - assertFalse(fs.exists(basePath)); + assertThat(fs.exists(basePath)).isFalse(); Path path = new Path(new Path(basePath, "dir_with_space "), "foo.txt"); try (OutputStream outputStream = fs.create(path)) { outputStream.write("test".getBytes(UTF_8)); } - assertTrue(fs.exists(path)); + assertThat(fs.exists(path)).isTrue(); try (InputStream inputStream = fs.open(path)) { String content = new BufferedReader(new InputStreamReader(inputStream, UTF_8)).readLine(); - assertEquals(content, "test"); + assertThat(content).isEqualTo("test"); } fs.delete(basePath, true); @@ -727,7 +736,7 @@ private void createTable(SchemaTableName tableName, HiveStorageFormat storageFor // verify the metadata ConnectorTableMetadata tableMetadata = metadata.getTableMetadata(session, getTableHandle(metadata, tableName)); - assertEquals(filterNonHiddenColumnMetadata(tableMetadata.getColumns()), columns); + assertThat(filterNonHiddenColumnMetadata(tableMetadata.getColumns())).isEqualTo(columns); // verify the data metadata.beginQuery(session); @@ -804,8 +813,8 @@ private void createExternalTableOnNonExistingPath(SchemaTableName tableName, Hiv // verify the metadata ConnectorTableMetadata tableMetadata = metadata.getTableMetadata(session, getTableHandle(metadata, tableName)); - assertEquals(filterNonHiddenColumnMetadata(tableMetadata.getColumns()), columns); - assertEquals(tableMetadata.getProperties().get("external_location"), externalLocation); + assertThat(filterNonHiddenColumnMetadata(tableMetadata.getColumns())).isEqualTo(columns); + assertThat(tableMetadata.getProperties().get("external_location")).isEqualTo(externalLocation); // verify the data metadata.beginQuery(session); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveLocal.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveLocal.java index 715a7035dd11..e6dfd137f58a 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveLocal.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveLocal.java @@ -68,9 +68,9 @@ import static io.trino.plugin.hive.util.HiveUtil.SPARK_TABLE_PROVIDER_KEY; import static java.nio.file.Files.copy; import static java.util.Objects.requireNonNull; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assumptions.abort; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) public abstract class AbstractTestHiveLocal @@ -277,7 +277,7 @@ private void assertReadReturnsRowCount(HiveStorageFormat storageFormat, SchemaTa ConnectorTableHandle tableHandle = getTableHandle(metadata, tableName); List columnHandles = filterNonHiddenColumnHandles(metadata.getColumnHandles(session, tableHandle).values()); MaterializedResult result = readTable(transaction, tableHandle, columnHandles, session, TupleDomain.all(), OptionalInt.empty(), Optional.of(storageFormat)); - assertEquals(result.getRowCount(), rowCount); + assertThat(result.getRowCount()).isEqualTo(rowCount); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveRoles.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveRoles.java index c19e3b7fe5d6..b637059880b4 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveRoles.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveRoles.java @@ -36,8 +36,8 @@ import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; import static io.trino.testing.QueryAssertions.assertContains; import static io.trino.testing.QueryAssertions.assertEqualsIgnoreOrder; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; @Execution(SAME_THREAD) abstract class AbstractTestHiveRoles @@ -70,8 +70,8 @@ private void cleanup() public void testCreateRole() { executeFromAdmin(createRoleSql("role1")); - assertEquals(listRoles(), ImmutableSet.of("role1", "admin")); - assertEquals(listRoles(), ImmutableSet.of("role1", "admin")); + assertThat(listRoles()).isEqualTo(ImmutableSet.of("role1", "admin")); + assertThat(listRoles()).isEqualTo(ImmutableSet.of("role1", "admin")); cleanup(); } @@ -111,9 +111,9 @@ public void testCreateRoleByNonAdminUser() public void testDropRole() { executeFromAdmin(createRoleSql("role1")); - assertEquals(listRoles(), ImmutableSet.of("role1", "admin")); + assertThat(listRoles()).isEqualTo(ImmutableSet.of("role1", "admin")); executeFromAdmin(dropRoleSql("role1")); - assertEquals(listRoles(), ImmutableSet.of("admin")); + assertThat(listRoles()).isEqualTo(ImmutableSet.of("admin")); cleanup(); } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java index 113f1d3cff9f..f06cbc899925 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java @@ -44,7 +44,6 @@ import io.trino.spi.type.DateType; import io.trino.spi.type.TimestampType; import io.trino.spi.type.Type; -import io.trino.spi.type.VarcharType; import io.trino.sql.planner.Plan; import io.trino.sql.planner.plan.ExchangeNode; import io.trino.sql.planner.planprinter.IoPlanPrinter; @@ -188,15 +187,9 @@ import static java.util.stream.Collectors.toSet; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Fail.fail; import static org.assertj.core.data.Offset.offset; import static org.junit.jupiter.api.Assumptions.abort; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; -import static org.testng.FileAssert.assertFile; public abstract class BaseHiveConnectorTest extends BaseConnectorTest @@ -1171,48 +1164,46 @@ public void testIoExplain() EstimatedStatsAndCost estimate = new EstimatedStatsAndCost(2.0, 40.0, 40.0, 0.0, 0.0); MaterializedResult result = computeActual("EXPLAIN (TYPE IO, FORMAT JSON) INSERT INTO test_io_explain SELECT custkey, orderkey, processing FROM test_io_explain WHERE custkey <= 10"); - assertEquals( - getIoPlanCodec().fromJson((String) getOnlyElement(result.getOnlyColumnAsSet())), - new IoPlan( - ImmutableSet.of( - new TableColumnInfo( - new CatalogSchemaTableName(catalog, "tpch", "test_io_explain"), - new IoPlanPrinter.Constraint( - false, - ImmutableSet.of( - new ColumnConstraint( - "orderkey", - BIGINT, - new FormattedDomain( - false, - ImmutableSet.of( - new FormattedRange( - new FormattedMarker(Optional.of("1"), EXACTLY), - new FormattedMarker(Optional.of("1"), EXACTLY)), - new FormattedRange( - new FormattedMarker(Optional.of("2"), EXACTLY), - new FormattedMarker(Optional.of("2"), EXACTLY))))), - new ColumnConstraint( - "processing", - BOOLEAN, - new FormattedDomain( - false, - ImmutableSet.of( - new FormattedRange( - new FormattedMarker(Optional.of("false"), EXACTLY), - new FormattedMarker(Optional.of("false"), EXACTLY))))), - new ColumnConstraint( - "custkey", - BIGINT, - new FormattedDomain( - false, - ImmutableSet.of( - new FormattedRange( - new FormattedMarker(Optional.empty(), ABOVE), - new FormattedMarker(Optional.of("10"), EXACTLY))))))), - estimate)), - Optional.of(new CatalogSchemaTableName(catalog, "tpch", "test_io_explain")), - estimate)); + assertThat(getIoPlanCodec().fromJson((String) getOnlyElement(result.getOnlyColumnAsSet()))).isEqualTo(new IoPlan( + ImmutableSet.of( + new TableColumnInfo( + new CatalogSchemaTableName(catalog, "tpch", "test_io_explain"), + new IoPlanPrinter.Constraint( + false, + ImmutableSet.of( + new ColumnConstraint( + "orderkey", + BIGINT, + new FormattedDomain( + false, + ImmutableSet.of( + new FormattedRange( + new FormattedMarker(Optional.of("1"), EXACTLY), + new FormattedMarker(Optional.of("1"), EXACTLY)), + new FormattedRange( + new FormattedMarker(Optional.of("2"), EXACTLY), + new FormattedMarker(Optional.of("2"), EXACTLY))))), + new ColumnConstraint( + "processing", + BOOLEAN, + new FormattedDomain( + false, + ImmutableSet.of( + new FormattedRange( + new FormattedMarker(Optional.of("false"), EXACTLY), + new FormattedMarker(Optional.of("false"), EXACTLY))))), + new ColumnConstraint( + "custkey", + BIGINT, + new FormattedDomain( + false, + ImmutableSet.of( + new FormattedRange( + new FormattedMarker(Optional.empty(), ABOVE), + new FormattedMarker(Optional.of("10"), EXACTLY))))))), + estimate)), + Optional.of(new CatalogSchemaTableName(catalog, "tpch", "test_io_explain")), + estimate)); assertUpdate("DROP TABLE test_io_explain"); @@ -1221,61 +1212,57 @@ public void testIoExplain() estimate = new EstimatedStatsAndCost(55.0, 990.0, 990.0, 0.0, 0.0); result = computeActual("EXPLAIN (TYPE IO, FORMAT JSON) INSERT INTO test_io_explain SELECT custkey, orderkey + 10 FROM test_io_explain WHERE custkey <= 10"); - assertEquals( - getIoPlanCodec().fromJson((String) getOnlyElement(result.getOnlyColumnAsSet())), - new IoPlan( - ImmutableSet.of( - new TableColumnInfo( - new CatalogSchemaTableName(catalog, "tpch", "test_io_explain"), - new IoPlanPrinter.Constraint( - false, - ImmutableSet.of( - new ColumnConstraint( - "orderkey", - BIGINT, - new FormattedDomain( - false, - ImmutableSet.of( - new FormattedRange( - new FormattedMarker(Optional.of("1"), EXACTLY), - new FormattedMarker(Optional.of("199"), EXACTLY))))), - new ColumnConstraint( - "custkey", - BIGINT, - new FormattedDomain( - false, - ImmutableSet.of( - new FormattedRange( - new FormattedMarker(Optional.empty(), ABOVE), - new FormattedMarker(Optional.of("10"), EXACTLY))))))), - estimate)), - Optional.of(new CatalogSchemaTableName(catalog, "tpch", "test_io_explain")), - estimate)); + assertThat(getIoPlanCodec().fromJson((String) getOnlyElement(result.getOnlyColumnAsSet()))).isEqualTo(new IoPlan( + ImmutableSet.of( + new TableColumnInfo( + new CatalogSchemaTableName(catalog, "tpch", "test_io_explain"), + new IoPlanPrinter.Constraint( + false, + ImmutableSet.of( + new ColumnConstraint( + "orderkey", + BIGINT, + new FormattedDomain( + false, + ImmutableSet.of( + new FormattedRange( + new FormattedMarker(Optional.of("1"), EXACTLY), + new FormattedMarker(Optional.of("199"), EXACTLY))))), + new ColumnConstraint( + "custkey", + BIGINT, + new FormattedDomain( + false, + ImmutableSet.of( + new FormattedRange( + new FormattedMarker(Optional.empty(), ABOVE), + new FormattedMarker(Optional.of("10"), EXACTLY))))))), + estimate)), + Optional.of(new CatalogSchemaTableName(catalog, "tpch", "test_io_explain")), + estimate)); EstimatedStatsAndCost finalEstimate = new EstimatedStatsAndCost(Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN); estimate = new EstimatedStatsAndCost(1.0, 18.0, 18, 0.0, 0.0); result = computeActual("EXPLAIN (TYPE IO, FORMAT JSON) INSERT INTO test_io_explain SELECT custkey, orderkey FROM test_io_explain WHERE orderkey = 100"); - assertEquals( - getIoPlanCodec().fromJson((String) getOnlyElement(result.getOnlyColumnAsSet())), - new IoPlan( - ImmutableSet.of( - new TableColumnInfo( - new CatalogSchemaTableName(catalog, "tpch", "test_io_explain"), - new IoPlanPrinter.Constraint( - false, - ImmutableSet.of( - new ColumnConstraint( - "orderkey", - BIGINT, - new FormattedDomain( - false, - ImmutableSet.of( - new FormattedRange( - new FormattedMarker(Optional.of("100"), EXACTLY), - new FormattedMarker(Optional.of("100"), EXACTLY))))))), - estimate)), - Optional.of(new CatalogSchemaTableName(catalog, "tpch", "test_io_explain")), - finalEstimate)); + assertThat(getIoPlanCodec().fromJson((String) getOnlyElement(result.getOnlyColumnAsSet()))).isEqualTo(new IoPlan( + ImmutableSet.of( + new TableColumnInfo( + new CatalogSchemaTableName(catalog, "tpch", "test_io_explain"), + new IoPlanPrinter.Constraint( + false, + ImmutableSet.of( + new ColumnConstraint( + "orderkey", + BIGINT, + new FormattedDomain( + false, + ImmutableSet.of( + new FormattedRange( + new FormattedMarker(Optional.of("100"), EXACTLY), + new FormattedMarker(Optional.of("100"), EXACTLY))))))), + estimate)), + Optional.of(new CatalogSchemaTableName(catalog, "tpch", "test_io_explain")), + finalEstimate)); assertUpdate("DROP TABLE test_io_explain"); } @@ -1289,128 +1276,122 @@ public void testIoExplainColumnFilters() EstimatedStatsAndCost estimate = new EstimatedStatsAndCost(2.0, 48.0, 48.0, 0.0, 0.0); EstimatedStatsAndCost finalEstimate = new EstimatedStatsAndCost(0.0, 0.0, 96.0, 0.0, 0.0); MaterializedResult result = computeActual("EXPLAIN (TYPE IO, FORMAT JSON) SELECT custkey, orderkey, orderstatus FROM test_io_explain_column_filters WHERE custkey <= 10 and orderstatus='P'"); - assertEquals( - getIoPlanCodec().fromJson((String) getOnlyElement(result.getOnlyColumnAsSet())), - new IoPlan( - ImmutableSet.of( - new TableColumnInfo( - new CatalogSchemaTableName(catalog, "tpch", "test_io_explain_column_filters"), - new IoPlanPrinter.Constraint( - false, - ImmutableSet.of( - new ColumnConstraint( - "orderkey", - BIGINT, - new FormattedDomain( - false, - ImmutableSet.of( - new FormattedRange( - new FormattedMarker(Optional.of("1"), EXACTLY), - new FormattedMarker(Optional.of("1"), EXACTLY)), - new FormattedRange( - new FormattedMarker(Optional.of("2"), EXACTLY), - new FormattedMarker(Optional.of("2"), EXACTLY))))), - new ColumnConstraint( - "custkey", - BIGINT, - new FormattedDomain( - false, - ImmutableSet.of( - new FormattedRange( - new FormattedMarker(Optional.empty(), ABOVE), - new FormattedMarker(Optional.of("10"), EXACTLY))))), - new ColumnConstraint( - "orderstatus", - VarcharType.createVarcharType(1), - new FormattedDomain( - false, - ImmutableSet.of( - new FormattedRange( - new FormattedMarker(Optional.of("P"), EXACTLY), - new FormattedMarker(Optional.of("P"), EXACTLY))))))), - estimate)), - Optional.empty(), - finalEstimate)); + assertThat(getIoPlanCodec().fromJson((String) getOnlyElement(result.getOnlyColumnAsSet()))).isEqualTo(new IoPlan( + ImmutableSet.of( + new TableColumnInfo( + new CatalogSchemaTableName(catalog, "tpch", "test_io_explain_column_filters"), + new IoPlanPrinter.Constraint( + false, + ImmutableSet.of( + new ColumnConstraint( + "orderkey", + BIGINT, + new FormattedDomain( + false, + ImmutableSet.of( + new FormattedRange( + new FormattedMarker(Optional.of("1"), EXACTLY), + new FormattedMarker(Optional.of("1"), EXACTLY)), + new FormattedRange( + new FormattedMarker(Optional.of("2"), EXACTLY), + new FormattedMarker(Optional.of("2"), EXACTLY))))), + new ColumnConstraint( + "custkey", + BIGINT, + new FormattedDomain( + false, + ImmutableSet.of( + new FormattedRange( + new FormattedMarker(Optional.empty(), ABOVE), + new FormattedMarker(Optional.of("10"), EXACTLY))))), + new ColumnConstraint( + "orderstatus", + createVarcharType(1), + new FormattedDomain( + false, + ImmutableSet.of( + new FormattedRange( + new FormattedMarker(Optional.of("P"), EXACTLY), + new FormattedMarker(Optional.of("P"), EXACTLY))))))), + estimate)), + Optional.empty(), + finalEstimate)); result = computeActual("EXPLAIN (TYPE IO, FORMAT JSON) SELECT custkey, orderkey, orderstatus FROM test_io_explain_column_filters WHERE custkey <= 10 and (orderstatus='P' or orderstatus='S')"); - assertEquals( - getIoPlanCodec().fromJson((String) getOnlyElement(result.getOnlyColumnAsSet())), - new IoPlan( - ImmutableSet.of( - new TableColumnInfo( - new CatalogSchemaTableName(catalog, "tpch", "test_io_explain_column_filters"), - new IoPlanPrinter.Constraint( - false, - ImmutableSet.of( - new ColumnConstraint( - "orderkey", - BIGINT, - new FormattedDomain( - false, - ImmutableSet.of( - new FormattedRange( - new FormattedMarker(Optional.of("1"), EXACTLY), - new FormattedMarker(Optional.of("1"), EXACTLY)), - new FormattedRange( - new FormattedMarker(Optional.of("2"), EXACTLY), - new FormattedMarker(Optional.of("2"), EXACTLY))))), - new ColumnConstraint( - "orderstatus", - VarcharType.createVarcharType(1), - new FormattedDomain( - false, - ImmutableSet.of( - new FormattedRange( - new FormattedMarker(Optional.of("P"), EXACTLY), - new FormattedMarker(Optional.of("P"), EXACTLY)), - new FormattedRange( - new FormattedMarker(Optional.of("S"), EXACTLY), - new FormattedMarker(Optional.of("S"), EXACTLY))))), - new ColumnConstraint( - "custkey", - BIGINT, - new FormattedDomain( - false, - ImmutableSet.of( - new FormattedRange( - new FormattedMarker(Optional.empty(), ABOVE), - new FormattedMarker(Optional.of("10"), EXACTLY))))))), - estimate)), - Optional.empty(), - finalEstimate)); + assertThat(getIoPlanCodec().fromJson((String) getOnlyElement(result.getOnlyColumnAsSet()))).isEqualTo(new IoPlan( + ImmutableSet.of( + new TableColumnInfo( + new CatalogSchemaTableName(catalog, "tpch", "test_io_explain_column_filters"), + new IoPlanPrinter.Constraint( + false, + ImmutableSet.of( + new ColumnConstraint( + "orderkey", + BIGINT, + new FormattedDomain( + false, + ImmutableSet.of( + new FormattedRange( + new FormattedMarker(Optional.of("1"), EXACTLY), + new FormattedMarker(Optional.of("1"), EXACTLY)), + new FormattedRange( + new FormattedMarker(Optional.of("2"), EXACTLY), + new FormattedMarker(Optional.of("2"), EXACTLY))))), + new ColumnConstraint( + "orderstatus", + createVarcharType(1), + new FormattedDomain( + false, + ImmutableSet.of( + new FormattedRange( + new FormattedMarker(Optional.of("P"), EXACTLY), + new FormattedMarker(Optional.of("P"), EXACTLY)), + new FormattedRange( + new FormattedMarker(Optional.of("S"), EXACTLY), + new FormattedMarker(Optional.of("S"), EXACTLY))))), + new ColumnConstraint( + "custkey", + BIGINT, + new FormattedDomain( + false, + ImmutableSet.of( + new FormattedRange( + new FormattedMarker(Optional.empty(), ABOVE), + new FormattedMarker(Optional.of("10"), EXACTLY))))))), + estimate)), + Optional.empty(), + finalEstimate)); result = computeActual("EXPLAIN (TYPE IO, FORMAT JSON) SELECT custkey, orderkey, orderstatus FROM test_io_explain_column_filters WHERE custkey <= 10 and cast(orderstatus as integer) = 5"); - assertEquals( - getIoPlanCodec().fromJson((String) getOnlyElement(result.getOnlyColumnAsSet())), - new IoPlan( - ImmutableSet.of( - new TableColumnInfo( - new CatalogSchemaTableName(catalog, "tpch", "test_io_explain_column_filters"), - new IoPlanPrinter.Constraint( - false, - ImmutableSet.of( - new ColumnConstraint( - "orderkey", - BIGINT, - new FormattedDomain( - false, - ImmutableSet.of( - new FormattedRange( - new FormattedMarker(Optional.of("1"), EXACTLY), - new FormattedMarker(Optional.of("1"), EXACTLY)), - new FormattedRange( - new FormattedMarker(Optional.of("2"), EXACTLY), - new FormattedMarker(Optional.of("2"), EXACTLY))))), - new ColumnConstraint( - "custkey", - BIGINT, - new FormattedDomain( - false, - ImmutableSet.of( - new FormattedRange( - new FormattedMarker(Optional.empty(), ABOVE), - new FormattedMarker(Optional.of("10"), EXACTLY))))))), - estimate)), - Optional.empty(), - finalEstimate)); + assertThat(getIoPlanCodec().fromJson((String) getOnlyElement(result.getOnlyColumnAsSet()))).isEqualTo(new IoPlan( + ImmutableSet.of( + new TableColumnInfo( + new CatalogSchemaTableName(catalog, "tpch", "test_io_explain_column_filters"), + new IoPlanPrinter.Constraint( + false, + ImmutableSet.of( + new ColumnConstraint( + "orderkey", + BIGINT, + new FormattedDomain( + false, + ImmutableSet.of( + new FormattedRange( + new FormattedMarker(Optional.of("1"), EXACTLY), + new FormattedMarker(Optional.of("1"), EXACTLY)), + new FormattedRange( + new FormattedMarker(Optional.of("2"), EXACTLY), + new FormattedMarker(Optional.of("2"), EXACTLY))))), + new ColumnConstraint( + "custkey", + BIGINT, + new FormattedDomain( + false, + ImmutableSet.of( + new FormattedRange( + new FormattedMarker(Optional.empty(), ABOVE), + new FormattedMarker(Optional.of("10"), EXACTLY))))))), + estimate)), + Optional.empty(), + finalEstimate)); assertUpdate("DROP TABLE test_io_explain_column_filters"); } @@ -1423,16 +1404,14 @@ public void testIoExplainWithEmptyPartitionedTable() EstimatedStatsAndCost estimate = new EstimatedStatsAndCost(0.0, 0.0, 0.0, 0.0, 0.0); MaterializedResult result = computeActual("EXPLAIN (TYPE IO, FORMAT JSON) SELECT custkey, orderkey FROM test_io_explain_with_empty_partitioned_table"); - assertEquals( - getIoPlanCodec().fromJson((String) getOnlyElement(result.getOnlyColumnAsSet())), - new IoPlan( - ImmutableSet.of( - new TableColumnInfo( - new CatalogSchemaTableName(catalog, "tpch", "test_io_explain_with_empty_partitioned_table"), - new IoPlanPrinter.Constraint(true, ImmutableSet.of()), - estimate)), - Optional.empty(), - estimate)); + assertThat(getIoPlanCodec().fromJson((String) getOnlyElement(result.getOnlyColumnAsSet()))).isEqualTo(new IoPlan( + ImmutableSet.of( + new TableColumnInfo( + new CatalogSchemaTableName(catalog, "tpch", "test_io_explain_with_empty_partitioned_table"), + new IoPlanPrinter.Constraint(true, ImmutableSet.of()), + estimate)), + Optional.empty(), + estimate)); assertUpdate("DROP TABLE test_io_explain_with_empty_partitioned_table"); } @@ -1460,27 +1439,25 @@ public void testIoExplainNoFilter() EstimatedStatsAndCost finalEstimate = new EstimatedStatsAndCost(1.0, 22.0, 22.0, 0.0, 22.0); MaterializedResult result = computeActual("EXPLAIN (TYPE IO, FORMAT JSON) SELECT * FROM io_explain_test_no_filter"); - assertEquals( - getIoPlanCodec().fromJson((String) getOnlyElement(result.getOnlyColumnAsSet())), - new IoPlan( - ImmutableSet.of( - new TableColumnInfo( - new CatalogSchemaTableName(catalog, "tpch", "io_explain_test_no_filter"), - new IoPlanPrinter.Constraint( - false, - ImmutableSet.of( - new ColumnConstraint( - "ds", - VARCHAR, - new FormattedDomain( - false, - ImmutableSet.of( - new FormattedRange( - new FormattedMarker(Optional.of("a"), EXACTLY), - new FormattedMarker(Optional.of("a"), EXACTLY))))))), - estimate)), - Optional.empty(), - finalEstimate)); + assertThat(getIoPlanCodec().fromJson((String) getOnlyElement(result.getOnlyColumnAsSet()))).isEqualTo(new IoPlan( + ImmutableSet.of( + new TableColumnInfo( + new CatalogSchemaTableName(catalog, "tpch", "io_explain_test_no_filter"), + new IoPlanPrinter.Constraint( + false, + ImmutableSet.of( + new ColumnConstraint( + "ds", + VARCHAR, + new FormattedDomain( + false, + ImmutableSet.of( + new FormattedRange( + new FormattedMarker(Optional.of("a"), EXACTLY), + new FormattedMarker(Optional.of("a"), EXACTLY))))))), + estimate)), + Optional.empty(), + finalEstimate)); assertUpdate("DROP TABLE io_explain_test_no_filter"); } @@ -1507,36 +1484,34 @@ public void testIoExplainFilterOnAgg() EstimatedStatsAndCost finalEstimate = new EstimatedStatsAndCost(Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN); MaterializedResult result = computeActual("EXPLAIN (TYPE IO, FORMAT JSON) SELECT * FROM (SELECT COUNT(*) cnt FROM io_explain_test_filter_on_agg WHERE b = 'b') WHERE cnt > 0"); - assertEquals( - getIoPlanCodec().fromJson((String) getOnlyElement(result.getOnlyColumnAsSet())), - new IoPlan( - ImmutableSet.of( - new TableColumnInfo( - new CatalogSchemaTableName(catalog, "tpch", "io_explain_test_filter_on_agg"), - new IoPlanPrinter.Constraint( - false, - ImmutableSet.of( - new ColumnConstraint( - "ds", - VARCHAR, - new FormattedDomain( - false, - ImmutableSet.of( - new FormattedRange( - new FormattedMarker(Optional.of("a"), EXACTLY), - new FormattedMarker(Optional.of("a"), EXACTLY))))), - new ColumnConstraint( - "b", - VARCHAR, - new FormattedDomain( - false, - ImmutableSet.of( - new FormattedRange( - new FormattedMarker(Optional.of("b"), EXACTLY), - new FormattedMarker(Optional.of("b"), EXACTLY))))))), - estimate)), - Optional.empty(), - finalEstimate)); + assertThat(getIoPlanCodec().fromJson((String) getOnlyElement(result.getOnlyColumnAsSet()))).isEqualTo(new IoPlan( + ImmutableSet.of( + new TableColumnInfo( + new CatalogSchemaTableName(catalog, "tpch", "io_explain_test_filter_on_agg"), + new IoPlanPrinter.Constraint( + false, + ImmutableSet.of( + new ColumnConstraint( + "ds", + VARCHAR, + new FormattedDomain( + false, + ImmutableSet.of( + new FormattedRange( + new FormattedMarker(Optional.of("a"), EXACTLY), + new FormattedMarker(Optional.of("a"), EXACTLY))))), + new ColumnConstraint( + "b", + VARCHAR, + new FormattedDomain( + false, + ImmutableSet.of( + new FormattedRange( + new FormattedMarker(Optional.of("b"), EXACTLY), + new FormattedMarker(Optional.of("b"), EXACTLY))))))), + estimate)), + Optional.empty(), + finalEstimate)); assertUpdate("DROP TABLE io_explain_test_filter_on_agg"); } @@ -1570,9 +1545,9 @@ public void testIoExplainWithPrimitiveTypes() assertUpdate(query, 1); - assertEquals( - getIoPlanCodec().fromJson((String) getOnlyElement(computeActual("EXPLAIN (TYPE IO, FORMAT JSON) SELECT * FROM test_types_table").getOnlyColumnAsSet())), - new IoPlan( + assertThat(getIoPlanCodec().fromJson((String) getOnlyElement(computeActual("EXPLAIN (TYPE IO, FORMAT JSON) SELECT * FROM test_types_table").getOnlyColumnAsSet()))) + .describedAs(format("%d) Type %s ", index, type)) + .isEqualTo(new IoPlan( ImmutableSet.of(new TableColumnInfo( new CatalogSchemaTableName(catalog, "tpch", "test_types_table"), new IoPlanPrinter.Constraint( @@ -1589,8 +1564,7 @@ public void testIoExplainWithPrimitiveTypes() new FormattedMarker(Optional.of(entry.getKey().toString()), EXACTLY))))))), estimate)), Optional.empty(), - estimate), - format("%d) Type %s ", index, type)); + estimate)); assertUpdate("DROP TABLE test_types_table"); } @@ -1630,22 +1604,22 @@ public void createTableWithEveryType() assertUpdate(query, 1); MaterializedResult results = getQueryRunner().execute(getSession(), "SELECT * FROM test_types_table").toTestTypes(); - assertEquals(results.getRowCount(), 1); + assertThat(results.getRowCount()).isEqualTo(1); MaterializedRow row = results.getMaterializedRows().get(0); - assertEquals(row.getField(0), "foo"); - assertEquals(row.getField(1), "bar".getBytes(UTF_8)); - assertEquals(row.getField(2), 1L); - assertEquals(row.getField(3), 2); - assertEquals(row.getField(4), 3.14); - assertEquals(row.getField(5), true); - assertEquals(row.getField(6), LocalDate.of(1980, 5, 7)); - assertEquals(row.getField(7), LocalDateTime.of(1980, 5, 7, 11, 22, 33, 456_000_000)); - assertEquals(row.getField(8), new BigDecimal("3.14")); - assertEquals(row.getField(9), new BigDecimal("12345678901234567890.0123456789")); - assertEquals(row.getField(10), "bar "); + assertThat(row.getField(0)).isEqualTo("foo"); + assertThat(row.getField(1)).isEqualTo("bar".getBytes(UTF_8)); + assertThat(row.getField(2)).isEqualTo(1L); + assertThat(row.getField(3)).isEqualTo(2); + assertThat(row.getField(4)).isEqualTo(3.14); + assertThat(row.getField(5)).isEqualTo(true); + assertThat(row.getField(6)).isEqualTo(LocalDate.of(1980, 5, 7)); + assertThat(row.getField(7)).isEqualTo(LocalDateTime.of(1980, 5, 7, 11, 22, 33, 456_000_000)); + assertThat(row.getField(8)).isEqualTo(new BigDecimal("3.14")); + assertThat(row.getField(9)).isEqualTo(new BigDecimal("12345678901234567890.0123456789")); + assertThat(row.getField(10)).isEqualTo("bar "); assertUpdate("DROP TABLE test_types_table"); - assertFalse(getQueryRunner().tableExists(getSession(), "test_types_table")); + assertThat(getQueryRunner().tableExists(getSession(), "test_types_table")).isFalse(); } @Test @@ -1696,7 +1670,7 @@ private void testCreatePartitionedTable(Session session, HiveStorageFormat stora assertUpdate(session, createTable); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, "test_partitioned_table"); - assertEquals(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY), storageFormat); + assertThat(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY)).isEqualTo(storageFormat); List partitionedBy = ImmutableList.of( "_partition_string", @@ -1711,10 +1685,10 @@ private void testCreatePartitionedTable(Session session, HiveStorageFormat stora "_partition_decimal_long", "_partition_date", "_partition_timestamp"); - assertEquals(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY), partitionedBy); + assertThat(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY)).isEqualTo(partitionedBy); for (ColumnMetadata columnMetadata : tableMetadata.getColumns()) { boolean partitionKey = partitionedBy.contains(columnMetadata.getName()); - assertEquals(columnMetadata.getExtraInfo(), columnExtraInfo(partitionKey)); + assertThat(columnMetadata.getExtraInfo()).isEqualTo(columnExtraInfo(partitionKey)); } assertColumnType(tableMetadata, "_string", createUnboundedVarcharType()); @@ -1724,7 +1698,7 @@ private void testCreatePartitionedTable(Session session, HiveStorageFormat stora assertColumnType(tableMetadata, "_partition_varchar", createVarcharType(65535)); MaterializedResult result = computeActual("SELECT * FROM test_partitioned_table"); - assertEquals(result.getRowCount(), 0); + assertThat(result.getRowCount()).isEqualTo(0); @Language("SQL") String select = "" + "SELECT" + @@ -1778,7 +1752,7 @@ private void testCreatePartitionedTable(Session session, HiveStorageFormat stora assertUpdate(session, "DROP TABLE test_partitioned_table"); - assertFalse(getQueryRunner().tableExists(session, "test_partitioned_table")); + assertThat(getQueryRunner().tableExists(session, "test_partitioned_table")).isFalse(); } @Test @@ -1822,7 +1796,7 @@ private void createTableLike(String likeSuffix, boolean hasPartition) // Verify the partition keys are correctly created List partitionedBy = ImmutableList.of("partition_bigint", "partition_decimal_long"); - assertEquals(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY), partitionedBy); + assertThat(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY)).isEqualTo(partitionedBy); // Verify the column types assertColumnType(tableMetadata, "string_col", createUnboundedVarcharType()); @@ -1923,7 +1897,7 @@ private void testCreateTableAs(Session session, HiveStorageFormat storageFormat) assertUpdate(session, createTableAs, 1); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, "test_format_table"); - assertEquals(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY), storageFormat); + assertThat(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY)).isEqualTo(storageFormat); assertColumnType(tableMetadata, "_varchar", createVarcharType(3)); assertColumnType(tableMetadata, "_char", createCharType(10)); @@ -1935,7 +1909,7 @@ private void testCreateTableAs(Session session, HiveStorageFormat storageFormat) assertUpdate(session, "DROP TABLE test_format_table"); - assertFalse(getQueryRunner().tableExists(session, "test_format_table")); + assertThat(getQueryRunner().tableExists(session, "test_format_table")).isFalse(); } @Test @@ -1959,17 +1933,17 @@ private void testCreatePartitionedTableAs(Session session, HiveStorageFormat sto assertUpdate(session, createTable, "SELECT count(*) FROM orders"); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, "test_create_partitioned_table_as"); - assertEquals(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY), storageFormat); - assertEquals(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY), ImmutableList.of("ship_priority", "order_status")); + assertThat(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY)).isEqualTo(storageFormat); + assertThat(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY)).isEqualTo(ImmutableList.of("ship_priority", "order_status")); List partitions = getPartitions("test_create_partitioned_table_as"); - assertEquals(partitions.size(), 3); + assertThat(partitions.size()).isEqualTo(3); assertQuery(session, "SELECT * FROM test_create_partitioned_table_as", "SELECT orderkey, shippriority, orderstatus FROM orders"); assertUpdate(session, "DROP TABLE test_create_partitioned_table_as"); - assertFalse(getQueryRunner().tableExists(session, "test_create_partitioned_table_as")); + assertThat(getQueryRunner().tableExists(session, "test_create_partitioned_table_as")).isFalse(); } @Test @@ -2197,13 +2171,13 @@ private void testEmptyBucketedTable(Session baseSession, HiveStorageFormat stora assertUpdate(createTable); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, tableName); - assertEquals(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY), storageFormat); + assertThat(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY)).isEqualTo(storageFormat); - assertNull(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY)); - assertEquals(tableMetadata.getMetadata().getProperties().get(BUCKETED_BY_PROPERTY), ImmutableList.of("bucket_key")); - assertEquals(tableMetadata.getMetadata().getProperties().get(BUCKET_COUNT_PROPERTY), 11); + assertThat(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY)).isNull(); + assertThat(tableMetadata.getMetadata().getProperties().get(BUCKETED_BY_PROPERTY)).isEqualTo(ImmutableList.of("bucket_key")); + assertThat(tableMetadata.getMetadata().getProperties().get(BUCKET_COUNT_PROPERTY)).isEqualTo(11); - assertEquals(computeActual("SELECT * from " + tableName).getRowCount(), 0); + assertThat(computeActual("SELECT * from " + tableName).getRowCount()).isEqualTo(0); // make sure that we will get one file per bucket regardless of writer count configured Session session = Session.builder(baseSession) @@ -2216,7 +2190,7 @@ private void testEmptyBucketedTable(Session baseSession, HiveStorageFormat stora assertQuery("SELECT * from " + tableName, "VALUES ('a0', 'b0', 'c0'), ('a1', 'b1', 'c1')"); assertUpdate(session, "DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(session, tableName)); + assertThat(getQueryRunner().tableExists(session, tableName)).isFalse(); } @Test @@ -2259,11 +2233,11 @@ private void testBucketedTable(Session session, HiveStorageFormat storageFormat, assertUpdate(parallelWriter, createTable, 3); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, tableName); - assertEquals(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY), storageFormat); + assertThat(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY)).isEqualTo(storageFormat); - assertNull(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY)); - assertEquals(tableMetadata.getMetadata().getProperties().get(BUCKETED_BY_PROPERTY), ImmutableList.of("bucket_key")); - assertEquals(tableMetadata.getMetadata().getProperties().get(BUCKET_COUNT_PROPERTY), 11); + assertThat(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY)).isNull(); + assertThat(tableMetadata.getMetadata().getProperties().get(BUCKETED_BY_PROPERTY)).isEqualTo(ImmutableList.of("bucket_key")); + assertThat(tableMetadata.getMetadata().getProperties().get(BUCKET_COUNT_PROPERTY)).isEqualTo(11); assertQuery("SELECT * from " + tableName, "VALUES ('a', 'b', 'c'), ('aa', 'bb', 'cc'), ('aaa', 'bbb', 'ccc')"); @@ -2278,7 +2252,7 @@ private void testBucketedTable(Session session, HiveStorageFormat storageFormat, assertQuery("SELECT * from " + tableName, "VALUES ('a', 'b', 'c'), ('aa', 'bb', 'cc'), ('aaa', 'bbb', 'ccc'), ('a0', 'b0', 'c0'), ('a1', 'b1', 'c1')"); assertUpdate(session, "DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(session, tableName)); + assertThat(getQueryRunner().tableExists(session, tableName)).isFalse(); } @Test @@ -2501,7 +2475,7 @@ private void testCreatePartitionedBucketedTableAsFewRows(Session session, HiveSt verifyPartitionedBucketedTableAsFewRows(storageFormat, tableName); assertUpdate(session, "DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(session, tableName)); + assertThat(getQueryRunner().tableExists(session, tableName)).isFalse(); } @Test @@ -2535,7 +2509,7 @@ private void testCreatePartitionedBucketedTableAs(HiveStorageFormat storageForma verifyPartitionedBucketedTable(storageFormat, tableName); assertUpdate("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); } @Test @@ -2589,7 +2563,7 @@ private void testCreatePartitionedBucketedTableWithNullsAs(HiveStorageFormat sto assertQuery("SELECT * FROM " + tableName, "SELECT custkey, orderkey, comment, nullif(orderpriority, '1-URGENT') orderpriority_nulls, orderstatus FROM orders"); assertUpdate("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); } @Test @@ -2710,20 +2684,20 @@ private void testCreatePartitionedBucketedTableAsWithUnionAll(HiveStorageFormat verifyPartitionedBucketedTable(storageFormat, tableName); assertUpdate("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); } private void verifyPartitionedBucketedTable(HiveStorageFormat storageFormat, String tableName) { TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, tableName); - assertEquals(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY), storageFormat); + assertThat(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY)).isEqualTo(storageFormat); - assertEquals(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY), ImmutableList.of("orderstatus")); - assertEquals(tableMetadata.getMetadata().getProperties().get(BUCKETED_BY_PROPERTY), ImmutableList.of("custkey", "custkey2")); - assertEquals(tableMetadata.getMetadata().getProperties().get(BUCKET_COUNT_PROPERTY), 11); + assertThat(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY)).isEqualTo(ImmutableList.of("orderstatus")); + assertThat(tableMetadata.getMetadata().getProperties().get(BUCKETED_BY_PROPERTY)).isEqualTo(ImmutableList.of("custkey", "custkey2")); + assertThat(tableMetadata.getMetadata().getProperties().get(BUCKET_COUNT_PROPERTY)).isEqualTo(11); List partitions = getPartitions(tableName); - assertEquals(partitions.size(), 3); + assertThat(partitions.size()).isEqualTo(3); // verify that we create bucket_count files in each partition assertEqualsIgnoreOrder( @@ -2832,7 +2806,7 @@ private void testCreateInvalidBucketedTable(HiveStorageFormat storageFormat) "FROM tpch.tiny.orders")) .hasMessage("Sorting columns [custkey3] not present in schema"); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); } @Test @@ -2878,20 +2852,20 @@ private void testInsertPartitionedBucketedTableFewRows(Session session, HiveStor verifyPartitionedBucketedTableAsFewRows(storageFormat, tableName); assertUpdate(session, "DROP TABLE test_insert_partitioned_bucketed_table_few_rows"); - assertFalse(getQueryRunner().tableExists(session, tableName)); + assertThat(getQueryRunner().tableExists(session, tableName)).isFalse(); } private void verifyPartitionedBucketedTableAsFewRows(HiveStorageFormat storageFormat, String tableName) { TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, tableName); - assertEquals(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY), storageFormat); + assertThat(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY)).isEqualTo(storageFormat); - assertEquals(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY), ImmutableList.of("partition_key")); - assertEquals(tableMetadata.getMetadata().getProperties().get(BUCKETED_BY_PROPERTY), ImmutableList.of("bucket_key")); - assertEquals(tableMetadata.getMetadata().getProperties().get(BUCKET_COUNT_PROPERTY), 11); + assertThat(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY)).isEqualTo(ImmutableList.of("partition_key")); + assertThat(tableMetadata.getMetadata().getProperties().get(BUCKETED_BY_PROPERTY)).isEqualTo(ImmutableList.of("bucket_key")); + assertThat(tableMetadata.getMetadata().getProperties().get(BUCKET_COUNT_PROPERTY)).isEqualTo(11); List partitions = getPartitions(tableName); - assertEquals(partitions.size(), 3); + assertThat(partitions.size()).isEqualTo(3); MaterializedResult actual = computeActual("SELECT * FROM " + tableName); MaterializedResult expected = resultBuilder(getSession(), canonicalizeType(createUnboundedVarcharType()), canonicalizeType(createUnboundedVarcharType()), canonicalizeType(createUnboundedVarcharType())) @@ -2963,7 +2937,7 @@ public void testUnregisterRegisterPartition() assertUpdate(format("INSERT INTO %s (dummy_col, part) VALUES (1, 'first'), (2, 'second'), (3, 'third')", tableName), 3); List paths = getQueryRunner().execute(getSession(), "SELECT \"$path\" FROM " + tableName + " ORDER BY \"$path\" ASC").toTestTypes().getMaterializedRows(); - assertEquals(paths.size(), 3); + assertThat(paths.size()).isEqualTo(3); String firstPartition = Location.of((String) paths.get(0).getField(0)).parentDirectory().toString(); @@ -3033,7 +3007,7 @@ private void testCreateEmptyBucketedPartition(Session session, HiveStorageFormat } assertUpdate(session, "DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(session, tableName)); + assertThat(getQueryRunner().tableExists(session, tableName)).isFalse(); } @Test @@ -3082,7 +3056,7 @@ private void testInsertPartitionedBucketedTable(HiveStorageFormat storageFormat) verifyPartitionedBucketedTable(storageFormat, tableName); assertUpdate("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); } private void createPartitionedBucketedTable(Session session, String tableName, HiveStorageFormat storageFormat) @@ -3145,7 +3119,7 @@ private void testInsertPartitionedBucketedTableWithUnionAll(HiveStorageFormat st verifyPartitionedBucketedTable(storageFormat, tableName); assertUpdate("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); } @Test @@ -3204,7 +3178,7 @@ private void testInsert(Session session, HiveStorageFormat storageFormat) assertUpdate(session, createTable); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, "test_insert_format_table"); - assertEquals(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY), storageFormat); + assertThat(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY)).isEqualTo(storageFormat); assertColumnType(tableMetadata, "_string", createUnboundedVarcharType()); assertColumnType(tableMetadata, "_varchar", createVarcharType(65535)); @@ -3249,7 +3223,7 @@ private void testInsert(Session session, HiveStorageFormat storageFormat) assertUpdate(session, "DROP TABLE test_insert_format_table"); - assertFalse(getQueryRunner().tableExists(session, "test_insert_format_table")); + assertThat(getQueryRunner().tableExists(session, "test_insert_format_table")).isFalse(); } @Test @@ -3275,8 +3249,8 @@ private void testInsertPartitionedTable(Session session, HiveStorageFormat stora assertUpdate(session, createTable); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, "test_insert_partitioned_table"); - assertEquals(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY), storageFormat); - assertEquals(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY), ImmutableList.of("ship_priority", "order_status")); + assertThat(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY)).isEqualTo(storageFormat); + assertThat(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY)).isEqualTo(ImmutableList.of("ship_priority", "order_status")); String partitionsTable = "\"test_insert_partitioned_table$partitions\""; @@ -3296,7 +3270,7 @@ private void testInsertPartitionedTable(Session session, HiveStorageFormat stora // verify the partitions List partitions = getPartitions("test_insert_partitioned_table"); - assertEquals(partitions.size(), 3); + assertThat(partitions.size()).isEqualTo(3); assertQuery(session, "SELECT * FROM test_insert_partitioned_table", "SELECT orderkey, shippriority, orderstatus FROM orders"); @@ -3320,7 +3294,7 @@ private void testInsertPartitionedTable(Session session, HiveStorageFormat stora assertUpdate(session, "DROP TABLE test_insert_partitioned_table"); - assertFalse(getQueryRunner().tableExists(session, "test_insert_partitioned_table")); + assertThat(getQueryRunner().tableExists(session, "test_insert_partitioned_table")).isFalse(); } @Test @@ -3348,8 +3322,8 @@ private void testInsertPartitionedTableExistingPartition(Session session, HiveSt assertUpdate(session, createTable); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, tableName); - assertEquals(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY), storageFormat); - assertEquals(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY), ImmutableList.of("order_status")); + assertThat(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY)).isEqualTo(storageFormat); + assertThat(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY)).isEqualTo(ImmutableList.of("order_status")); for (int i = 0; i < 3; i++) { assertUpdate( @@ -3365,7 +3339,7 @@ private void testInsertPartitionedTableExistingPartition(Session session, HiveSt // verify the partitions List partitions = getPartitions(tableName); - assertEquals(partitions.size(), 3); + assertThat(partitions.size()).isEqualTo(3); assertQuery( session, @@ -3374,7 +3348,7 @@ private void testInsertPartitionedTableExistingPartition(Session session, HiveSt assertUpdate(session, "DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(session, tableName)); + assertThat(getQueryRunner().tableExists(session, tableName)).isFalse(); } @Test @@ -3406,8 +3380,8 @@ private void testInsertPartitionedTableOverwriteExistingPartition(Session sessio assertUpdate(session, createTable); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, tableName); - assertEquals(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY), storageFormat); - assertEquals(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY), ImmutableList.of("order_status")); + assertThat(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY)).isEqualTo(storageFormat); + assertThat(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY)).isEqualTo(ImmutableList.of("order_status")); for (int i = 0; i < 3; i++) { assertUpdate( @@ -3422,7 +3396,7 @@ private void testInsertPartitionedTableOverwriteExistingPartition(Session sessio // verify the partitions List partitions = getPartitions(tableName); - assertEquals(partitions.size(), 3); + assertThat(partitions.size()).isEqualTo(3); assertQuery( session, @@ -3431,7 +3405,7 @@ private void testInsertPartitionedTableOverwriteExistingPartition(Session sessio } assertUpdate(session, "DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(session, tableName)); + assertThat(getQueryRunner().tableExists(session, tableName)).isFalse(); } @Test @@ -3521,7 +3495,7 @@ public void testPartitionPerScanLimit() assertUpdate(createTable); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, tableName); - assertEquals(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY), ImmutableList.of("part")); + assertThat(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY)).isEqualTo(ImmutableList.of("part")); // insert 1200 partitions for (int i = 0; i < 12; i++) { @@ -3723,7 +3697,7 @@ private void testInsertUnpartitionedTable(Session session, HiveStorageFormat sto assertUpdate(session, createTable); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, tableName); - assertEquals(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY), storageFormat); + assertThat(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY)).isEqualTo(storageFormat); for (int i = 0; i < 3; i++) { assertUpdate( @@ -3744,7 +3718,7 @@ private void testInsertUnpartitionedTable(Session session, HiveStorageFormat sto assertUpdate(session, "DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(session, tableName)); + assertThat(getQueryRunner().tableExists(session, tableName)).isFalse(); } @Test @@ -3755,11 +3729,11 @@ public void testDeleteFromUnpartitionedTable() assertUpdate("DELETE FROM test_delete_unpartitioned"); MaterializedResult result = computeActual("SELECT * FROM test_delete_unpartitioned"); - assertEquals(result.getRowCount(), 0); + assertThat(result.getRowCount()).isEqualTo(0); assertUpdate("DROP TABLE test_delete_unpartitioned"); - assertFalse(getQueryRunner().tableExists(getSession(), "test_delete_unpartitioned")); + assertThat(getQueryRunner().tableExists(getSession(), "test_delete_unpartitioned")).isFalse(); } @Test @@ -3802,7 +3776,7 @@ public void testMetadataDelete() assertUpdate("DROP TABLE test_metadata_delete"); - assertFalse(getQueryRunner().tableExists(getSession(), "test_metadata_delete")); + assertThat(getQueryRunner().tableExists(getSession(), "test_metadata_delete")).isFalse(); } private TableMetadata getTableMetadata(String catalog, String schema, String tableName) @@ -3814,7 +3788,7 @@ private TableMetadata getTableMetadata(String catalog, String schema, String tab .readOnly() .execute(session, transactionSession -> { Optional tableHandle = metadata.getTableHandle(transactionSession, new QualifiedObjectName(catalog, schema, tableName)); - assertTrue(tableHandle.isPresent()); + assertThat(tableHandle.isPresent()).isTrue(); return metadata.getTableMetadata(transactionSession, tableHandle.get()); }); } @@ -4083,12 +4057,12 @@ public void testBucketedCatalog() String bucketedSchema = bucketedSession.getSchema().get(); TableMetadata ordersTableMetadata = getTableMetadata(bucketedCatalog, bucketedSchema, "orders"); - assertEquals(ordersTableMetadata.getMetadata().getProperties().get(BUCKETED_BY_PROPERTY), ImmutableList.of("custkey")); - assertEquals(ordersTableMetadata.getMetadata().getProperties().get(BUCKET_COUNT_PROPERTY), 11); + assertThat(ordersTableMetadata.getMetadata().getProperties().get(BUCKETED_BY_PROPERTY)).isEqualTo(ImmutableList.of("custkey")); + assertThat(ordersTableMetadata.getMetadata().getProperties().get(BUCKET_COUNT_PROPERTY)).isEqualTo(11); TableMetadata customerTableMetadata = getTableMetadata(bucketedCatalog, bucketedSchema, "customer"); - assertEquals(customerTableMetadata.getMetadata().getProperties().get(BUCKETED_BY_PROPERTY), ImmutableList.of("custkey")); - assertEquals(customerTableMetadata.getMetadata().getProperties().get(BUCKET_COUNT_PROPERTY), 11); + assertThat(customerTableMetadata.getMetadata().getProperties().get(BUCKETED_BY_PROPERTY)).isEqualTo(ImmutableList.of("custkey")); + assertThat(customerTableMetadata.getMetadata().getProperties().get(BUCKET_COUNT_PROPERTY)).isEqualTo(11); } @Test @@ -4119,7 +4093,7 @@ public void testSingleWriter() createTableSql, (long) computeActual("SELECT count(*) FROM tpch.tiny.orders").getOnlyValue()); - assertEquals(computeActual("SELECT count(DISTINCT \"$path\") FROM scale_writers_small").getOnlyValue(), 1L); + assertThat(computeActual("SELECT count(DISTINCT \"$path\") FROM scale_writers_small").getOnlyValue()).isEqualTo(1L); } finally { assertUpdate("DROP TABLE IF EXISTS scale_writers_small"); @@ -4267,7 +4241,7 @@ private void testLimitWriterTasks(int maxWriterTasks, int expectedFilesCount, bo try { assertUpdate(session, createTableSql, (long) computeActual("SELECT count(*) FROM tpch.sf2.orders").getOnlyValue()); long files = (long) computeScalar("SELECT count(DISTINCT \"$path\") FROM %s".formatted(tableName)); - assertEquals(files, expectedFilesCount); + assertThat(files).isEqualTo(expectedFilesCount); } finally { assertUpdate("DROP TABLE IF EXISTS %s".formatted(tableName)); @@ -4364,7 +4338,7 @@ public void testShowCreateTable() assertUpdate(createTableSql); MaterializedResult actualResult = computeActual("SHOW CREATE TABLE test_show_create_table"); - assertEquals(getOnlyElement(actualResult.getOnlyColumnAsSet()), createTableSql); + assertThat(getOnlyElement(actualResult.getOnlyColumnAsSet())).isEqualTo(createTableSql); createTableSql = format("" + "CREATE TABLE %s.%s.%s (\n" + @@ -4389,7 +4363,7 @@ public void testShowCreateTable() "\"test_show_create_table'2\""); assertUpdate(createTableSql); actualResult = computeActual("SHOW CREATE TABLE \"test_show_create_table'2\""); - assertEquals(getOnlyElement(actualResult.getOnlyColumnAsSet()), createTableSql); + assertThat(getOnlyElement(actualResult.getOnlyColumnAsSet())).isEqualTo(createTableSql); createTableSql = format("" + "CREATE TABLE %s.%s.%s (\n" + @@ -4402,7 +4376,7 @@ public void testShowCreateTable() "test_show_create_table_with_special_characters"); assertUpdate(createTableSql); actualResult = computeActual("SHOW CREATE TABLE test_show_create_table_with_special_characters"); - assertEquals(getOnlyElement(actualResult.getOnlyColumnAsSet()), createTableSql); + assertThat(getOnlyElement(actualResult.getOnlyColumnAsSet())).isEqualTo(createTableSql); } @Test @@ -4417,18 +4391,16 @@ public void testShowCreateTableWithColumnProperties() " partitioned_by = ARRAY['b']," + " partition_projection_location_template = 's3://example/${b}')")) { String result = (String) computeScalar("SHOW CREATE TABLE " + table.getName()); - assertEquals( - result, - "CREATE TABLE hive.tpch." + table.getName() + " (\n" + - " a integer,\n" + - " b integer WITH ( partition_projection_range = ARRAY['0','10'], partition_projection_type = 'INTEGER' )\n" + - ")\n" + - "WITH (\n" + - " format = 'ORC',\n" + - " partition_projection_enabled = true,\n" + - " partition_projection_location_template = 's3://example/${b}',\n" + - " partitioned_by = ARRAY['b']\n" + - ")"); + assertThat(result).isEqualTo("CREATE TABLE hive.tpch." + table.getName() + " (\n" + + " a integer,\n" + + " b integer WITH ( partition_projection_range = ARRAY['0','10'], partition_projection_type = 'INTEGER' )\n" + + ")\n" + + "WITH (\n" + + " format = 'ORC',\n" + + " partition_projection_enabled = true,\n" + + " partition_projection_location_template = 's3://example/${b}',\n" + + " partitioned_by = ARRAY['b']\n" + + ")"); } } @@ -4464,11 +4436,11 @@ private void testCreateExternalTable( assertUpdate(createTableSql); MaterializedResult actual = computeActual(format("SHOW CREATE TABLE %s", tableName)); - assertEquals(actual.getOnlyValue(), createTableSql); + assertThat(actual.getOnlyValue()).isEqualTo(createTableSql); assertQuery(format("SELECT col1, col2 from %s", tableName), expectedResults); assertUpdate(format("DROP TABLE %s", tableName)); - assertFile(dataFile); // file should still exist after drop + assertThat(dataFile).exists(); // file should still exist after drop deleteRecursively(tempDir, ALLOW_INSECURE); } @@ -4564,7 +4536,7 @@ private void testCreateTableWithHeaderAndFooter(String format) assertUpdate(createTableSql); MaterializedResult actual = computeActual(format("SHOW CREATE TABLE %s_table_skip_header", format)); - assertEquals(actual.getOnlyValue(), createTableSql); + assertThat(actual.getOnlyValue()).isEqualTo(createTableSql); assertUpdate(format("DROP TABLE %s_table_skip_header", format)); createTableSql = format("" + @@ -4580,7 +4552,7 @@ private void testCreateTableWithHeaderAndFooter(String format) assertUpdate(createTableSql); actual = computeActual(format("SHOW CREATE TABLE %s_table_skip_footer", format)); - assertEquals(actual.getOnlyValue(), createTableSql); + assertThat(actual.getOnlyValue()).isEqualTo(createTableSql); assertUpdate(format("DROP TABLE %s_table_skip_footer", format)); createTableSql = format("" + @@ -4597,7 +4569,7 @@ private void testCreateTableWithHeaderAndFooter(String format) assertUpdate(createTableSql); actual = computeActual(format("SHOW CREATE TABLE %s_table_skip_header_footer", format)); - assertEquals(actual.getOnlyValue(), createTableSql); + assertThat(actual.getOnlyValue()).isEqualTo(createTableSql); assertUpdate(format("DROP TABLE %s_table_skip_header_footer", format)); createTableSql = format("" + @@ -4752,23 +4724,23 @@ private void testPathHiddenColumn(Session session, HiveStorageFormat storageForm "(2, 2), (5, 2) " + " ) t(col0, col1) "; assertUpdate(session, createTable, 8); - assertTrue(getQueryRunner().tableExists(getSession(), "test_path")); + assertThat(getQueryRunner().tableExists(getSession(), "test_path")).isTrue(); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, "test_path"); - assertEquals(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY), storageFormat); + assertThat(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY)).isEqualTo(storageFormat); List columnNames = ImmutableList.of("col0", "col1", PATH_COLUMN_NAME, FILE_SIZE_COLUMN_NAME, FILE_MODIFIED_TIME_COLUMN_NAME, PARTITION_COLUMN_NAME); List columnMetadatas = tableMetadata.getColumns(); - assertEquals(columnMetadatas.size(), columnNames.size()); + assertThat(columnMetadatas.size()).isEqualTo(columnNames.size()); for (int i = 0; i < columnMetadatas.size(); i++) { ColumnMetadata columnMetadata = columnMetadatas.get(i); - assertEquals(columnMetadata.getName(), columnNames.get(i)); + assertThat(columnMetadata.getName()).isEqualTo(columnNames.get(i)); if (columnMetadata.getName().equals(PATH_COLUMN_NAME)) { // $path should be hidden column - assertTrue(columnMetadata.isHidden()); + assertThat(columnMetadata.isHidden()).isTrue(); } } - assertEquals(getPartitions("test_path").size(), 3); + assertThat(getPartitions("test_path").size()).isEqualTo(3); MaterializedResult results = computeActual(session, format("SELECT *, \"%s\" FROM test_path", PATH_COLUMN_NAME)); Map partitionPathMap = new HashMap<>(); @@ -4779,20 +4751,20 @@ private void testPathHiddenColumn(Session session, HiveStorageFormat storageForm String pathName = (String) row.getField(2); String parentDirectory = Location.of(pathName).parentDirectory().toString(); - assertTrue(pathName.length() > 0); - assertEquals(col0 % 3, col1); + assertThat(pathName.length() > 0).isTrue(); + assertThat(col0 % 3).isEqualTo(col1); if (partitionPathMap.containsKey(col1)) { // the rows in the same partition should be in the same partition directory - assertEquals(partitionPathMap.get(col1), parentDirectory); + assertThat(partitionPathMap.get(col1)).isEqualTo(parentDirectory); } else { partitionPathMap.put(col1, parentDirectory); } } - assertEquals(partitionPathMap.size(), 3); + assertThat(partitionPathMap.size()).isEqualTo(3); assertUpdate(session, "DROP TABLE test_path"); - assertFalse(getQueryRunner().tableExists(session, "test_path")); + assertThat(getQueryRunner().tableExists(session, "test_path")).isFalse(); } @Test @@ -4809,24 +4781,24 @@ public void testBucketHiddenColumn() "(6, 17), (7, 18), (8, 19)" + " ) t (col0, col1) "; assertUpdate(createTable, 9); - assertTrue(getQueryRunner().tableExists(getSession(), "test_bucket_hidden_column")); + assertThat(getQueryRunner().tableExists(getSession(), "test_bucket_hidden_column")).isTrue(); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, "test_bucket_hidden_column"); - assertEquals(tableMetadata.getMetadata().getProperties().get(BUCKETED_BY_PROPERTY), ImmutableList.of("col0")); - assertEquals(tableMetadata.getMetadata().getProperties().get(BUCKET_COUNT_PROPERTY), 2); + assertThat(tableMetadata.getMetadata().getProperties().get(BUCKETED_BY_PROPERTY)).isEqualTo(ImmutableList.of("col0")); + assertThat(tableMetadata.getMetadata().getProperties().get(BUCKET_COUNT_PROPERTY)).isEqualTo(2); List columnNames = ImmutableList.of("col0", "col1", PATH_COLUMN_NAME, BUCKET_COLUMN_NAME, FILE_SIZE_COLUMN_NAME, FILE_MODIFIED_TIME_COLUMN_NAME); List columnMetadatas = tableMetadata.getColumns(); - assertEquals(columnMetadatas.size(), columnNames.size()); + assertThat(columnMetadatas.size()).isEqualTo(columnNames.size()); for (int i = 0; i < columnMetadatas.size(); i++) { ColumnMetadata columnMetadata = columnMetadatas.get(i); - assertEquals(columnMetadata.getName(), columnNames.get(i)); + assertThat(columnMetadata.getName()).isEqualTo(columnNames.get(i)); if (columnMetadata.getName().equals(BUCKET_COLUMN_NAME)) { // $bucket_number should be hidden column - assertTrue(columnMetadata.isHidden()); + assertThat(columnMetadata.isHidden()).isTrue(); } } - assertEquals(getBucketCount("test_bucket_hidden_column"), 2); + assertThat(getBucketCount("test_bucket_hidden_column")).isEqualTo(2); MaterializedResult results = computeActual(format("SELECT *, \"%1$s\" FROM test_bucket_hidden_column WHERE \"%1$s\" = 1", BUCKET_COLUMN_NAME)); @@ -4836,16 +4808,16 @@ public void testBucketHiddenColumn() int col1 = (int) row.getField(1); int bucket = (int) row.getField(2); - assertEquals(col1, col0 + 11); - assertTrue(col1 % 2 == 0); + assertThat(col1).isEqualTo(col0 + 11); + assertThat(col1 % 2 == 0).isTrue(); // Because Hive's hash function for integer n is h(n) = n. - assertEquals(bucket, col0 % 2); + assertThat(bucket).isEqualTo(col0 % 2); } - assertEquals(results.getRowCount(), 4); + assertThat(results.getRowCount()).isEqualTo(4); assertUpdate("DROP TABLE test_bucket_hidden_column"); - assertFalse(getQueryRunner().tableExists(getSession(), "test_bucket_hidden_column")); + assertThat(getQueryRunner().tableExists(getSession(), "test_bucket_hidden_column")).isFalse(); } @Test @@ -4861,21 +4833,21 @@ public void testFileSizeHiddenColumn() "(2, 2), (5, 2) " + " ) t(col0, col1) "; assertUpdate(createTable, 8); - assertTrue(getQueryRunner().tableExists(getSession(), "test_file_size")); + assertThat(getQueryRunner().tableExists(getSession(), "test_file_size")).isTrue(); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, "test_file_size"); List columnNames = ImmutableList.of("col0", "col1", PATH_COLUMN_NAME, FILE_SIZE_COLUMN_NAME, FILE_MODIFIED_TIME_COLUMN_NAME, PARTITION_COLUMN_NAME); List columnMetadatas = tableMetadata.getColumns(); - assertEquals(columnMetadatas.size(), columnNames.size()); + assertThat(columnMetadatas.size()).isEqualTo(columnNames.size()); for (int i = 0; i < columnMetadatas.size(); i++) { ColumnMetadata columnMetadata = columnMetadatas.get(i); - assertEquals(columnMetadata.getName(), columnNames.get(i)); + assertThat(columnMetadata.getName()).isEqualTo(columnNames.get(i)); if (columnMetadata.getName().equals(FILE_SIZE_COLUMN_NAME)) { - assertTrue(columnMetadata.isHidden()); + assertThat(columnMetadata.isHidden()).isTrue(); } } - assertEquals(getPartitions("test_file_size").size(), 3); + assertThat(getPartitions("test_file_size").size()).isEqualTo(3); MaterializedResult results = computeActual(format("SELECT *, \"%s\" FROM test_file_size", FILE_SIZE_COLUMN_NAME)); Map fileSizeMap = new HashMap<>(); @@ -4885,16 +4857,16 @@ public void testFileSizeHiddenColumn() int col1 = (int) row.getField(1); long fileSize = (Long) row.getField(2); - assertTrue(fileSize > 0); - assertEquals(col0 % 3, col1); + assertThat(fileSize > 0).isTrue(); + assertThat(col0 % 3).isEqualTo(col1); if (fileSizeMap.containsKey(col1)) { - assertEquals(fileSizeMap.get(col1).longValue(), fileSize); + assertThat(fileSizeMap.get(col1).longValue()).isEqualTo(fileSize); } else { fileSizeMap.put(col1, fileSize); } } - assertEquals(fileSizeMap.size(), 3); + assertThat(fileSizeMap.size()).isEqualTo(3); assertUpdate("DROP TABLE test_file_size"); } @@ -4921,21 +4893,21 @@ private void testFileModifiedTimeHiddenColumn(HiveTimestampPrecision precision) "(2, 2), (5, 2) " + " ) t(col0, col1) "; assertUpdate(createTable, 8); - assertTrue(getQueryRunner().tableExists(getSession(), "test_file_modified_time")); + assertThat(getQueryRunner().tableExists(getSession(), "test_file_modified_time")).isTrue(); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, "test_file_modified_time"); List columnNames = ImmutableList.of("col0", "col1", PATH_COLUMN_NAME, FILE_SIZE_COLUMN_NAME, FILE_MODIFIED_TIME_COLUMN_NAME, PARTITION_COLUMN_NAME); List columnMetadatas = tableMetadata.getColumns(); - assertEquals(columnMetadatas.size(), columnNames.size()); + assertThat(columnMetadatas.size()).isEqualTo(columnNames.size()); for (int i = 0; i < columnMetadatas.size(); i++) { ColumnMetadata columnMetadata = columnMetadatas.get(i); - assertEquals(columnMetadata.getName(), columnNames.get(i)); + assertThat(columnMetadata.getName()).isEqualTo(columnNames.get(i)); if (columnMetadata.getName().equals(FILE_MODIFIED_TIME_COLUMN_NAME)) { - assertTrue(columnMetadata.isHidden()); + assertThat(columnMetadata.isHidden()).isTrue(); } } - assertEquals(getPartitions("test_file_modified_time").size(), 3); + assertThat(getPartitions("test_file_modified_time").size()).isEqualTo(3); Session sessionWithTimestampPrecision = withTimestampPrecision(getSession(), precision); MaterializedResult results = computeActual( @@ -4949,15 +4921,15 @@ private void testFileModifiedTimeHiddenColumn(HiveTimestampPrecision precision) Instant fileModifiedTime = ((ZonedDateTime) row.getField(2)).toInstant(); assertThat(fileModifiedTime.toEpochMilli()).isCloseTo(testStartTime, offset(2000L)); - assertEquals(col0 % 3, col1); + assertThat(col0 % 3).isEqualTo(col1); if (fileModifiedTimeMap.containsKey(col1)) { - assertEquals(fileModifiedTimeMap.get(col1), fileModifiedTime); + assertThat(fileModifiedTimeMap.get(col1)).isEqualTo(fileModifiedTime); } else { fileModifiedTimeMap.put(col1, fileModifiedTime); } } - assertEquals(fileModifiedTimeMap.size(), 3); + assertThat(fileModifiedTimeMap.size()).isEqualTo(3); assertUpdate("DROP TABLE test_file_modified_time"); } @@ -4975,30 +4947,30 @@ public void testPartitionHiddenColumn() "(6, 17, 27), (7, 18, 28), (8, 19, 29)" + " ) t (col0, col1, col2) "; assertUpdate(createTable, 9); - assertTrue(getQueryRunner().tableExists(getSession(), "test_partition_hidden_column")); + assertThat(getQueryRunner().tableExists(getSession(), "test_partition_hidden_column")).isTrue(); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, "test_partition_hidden_column"); - assertEquals(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY), ImmutableList.of("col1", "col2")); + assertThat(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY)).isEqualTo(ImmutableList.of("col1", "col2")); List columnNames = ImmutableList.of("col0", "col1", "col2", PATH_COLUMN_NAME, FILE_SIZE_COLUMN_NAME, FILE_MODIFIED_TIME_COLUMN_NAME, PARTITION_COLUMN_NAME); List columnMetadatas = tableMetadata.getColumns(); - assertEquals(columnMetadatas.size(), columnNames.size()); + assertThat(columnMetadatas.size()).isEqualTo(columnNames.size()); for (int i = 0; i < columnMetadatas.size(); i++) { ColumnMetadata columnMetadata = columnMetadatas.get(i); - assertEquals(columnMetadata.getName(), columnNames.get(i)); + assertThat(columnMetadata.getName()).isEqualTo(columnNames.get(i)); if (columnMetadata.getName().equals(PARTITION_COLUMN_NAME)) { - assertTrue(columnMetadata.isHidden()); + assertThat(columnMetadata.isHidden()).isTrue(); } } - assertEquals(getPartitions("test_partition_hidden_column").size(), 9); + assertThat(getPartitions("test_partition_hidden_column").size()).isEqualTo(9); MaterializedResult results = computeActual(format("SELECT *, \"%s\" FROM test_partition_hidden_column", PARTITION_COLUMN_NAME)); for (MaterializedRow row : results.getMaterializedRows()) { String actualPartition = (String) row.getField(3); String expectedPartition = format("col1=%s/col2=%s", row.getField(1), row.getField(2)); - assertEquals(actualPartition, expectedPartition); + assertThat(actualPartition).isEqualTo(expectedPartition); } - assertEquals(results.getRowCount(), 9); + assertThat(results.getRowCount()).isEqualTo(9); assertUpdate("DROP TABLE test_partition_hidden_column"); } @@ -5343,12 +5315,12 @@ private void testParquetTimestampPredicatePushdown(HiveTimestampPrecision timest MaterializedResultWithQueryId queryResult = queryRunner.executeWithQueryId( session, format("SELECT * FROM %s WHERE t < %s", tableName, formatTimestamp(value))); - assertEquals(getQueryInfo(queryRunner, queryResult).getQueryStats().getProcessedInputDataSize().toBytes(), 0); + assertThat(getQueryInfo(queryRunner, queryResult).getQueryStats().getProcessedInputDataSize().toBytes()).isEqualTo(0); queryResult = queryRunner.executeWithQueryId( session, format("SELECT * FROM %s WHERE t > %s", tableName, formatTimestamp(value))); - assertEquals(getQueryInfo(queryRunner, queryResult).getQueryStats().getProcessedInputDataSize().toBytes(), 0); + assertThat(getQueryInfo(queryRunner, queryResult).getQueryStats().getProcessedInputDataSize().toBytes()).isEqualTo(0); assertQueryStats( session, @@ -5388,12 +5360,12 @@ private void testOrcTimestampPredicatePushdown(HiveTimestampPrecision timestampP MaterializedResultWithQueryId queryResult = queryRunner.executeWithQueryId( session, format("SELECT * FROM test_orc_timestamp_predicate_pushdown WHERE t < %s", formatTimestamp(value.minusNanos(MILLISECONDS.toNanos(1))))); - assertEquals(getQueryInfo(queryRunner, queryResult).getQueryStats().getProcessedInputDataSize().toBytes(), 0); + assertThat(getQueryInfo(queryRunner, queryResult).getQueryStats().getProcessedInputDataSize().toBytes()).isEqualTo(0); queryResult = queryRunner.executeWithQueryId( session, format("SELECT * FROM test_orc_timestamp_predicate_pushdown WHERE t > %s", formatTimestamp(value.plusNanos(MILLISECONDS.toNanos(1))))); - assertEquals(getQueryInfo(queryRunner, queryResult).getQueryStats().getProcessedInputDataSize().toBytes(), 0); + assertThat(getQueryInfo(queryRunner, queryResult).getQueryStats().getProcessedInputDataSize().toBytes()).isEqualTo(0); assertQuery(session, "SELECT * FROM test_orc_timestamp_predicate_pushdown WHERE t < " + formatTimestamp(value.plusNanos(1)), format("VALUES (%s)", formatTimestamp(value))); @@ -5961,8 +5933,8 @@ public void testNestedColumnWithDuplicateName() assertQuery("SELECT foo FROM " + tableName + " WHERE root.foo = 'b'", "VALUES ('a')"); assertQuery("SELECT foo FROM " + tableName + " WHERE foo = 'a' AND root.foo = 'b'", "VALUES ('a')"); - assertTrue(computeActual("SELECT foo FROM " + tableName + " WHERE foo = 'a' AND root.foo = 'a'").getMaterializedRows().isEmpty()); - assertTrue(computeActual("SELECT foo FROM " + tableName + " WHERE foo = 'b' AND root.foo = 'b'").getMaterializedRows().isEmpty()); + assertThat(computeActual("SELECT foo FROM " + tableName + " WHERE foo = 'a' AND root.foo = 'a'").getMaterializedRows().isEmpty()).isTrue(); + assertThat(computeActual("SELECT foo FROM " + tableName + " WHERE foo = 'b' AND root.foo = 'b'").getMaterializedRows().isEmpty()).isTrue(); assertUpdate("DROP TABLE " + tableName); } @@ -6371,7 +6343,7 @@ public void testShowColumnMetadata() "SELECT lower(column_name) " + "FROM information_schema.columns " + "WHERE table_name = '" + tableName + "'"; - assertEquals(computeActual(getColumnsSql).getOnlyColumnAsSet(), ImmutableSet.of("a", "b", "c")); + assertThat(computeActual(getColumnsSql).getOnlyColumnAsSet()).isEqualTo(ImmutableSet.of("a", "b", "c")); // verify with no SELECT privileges on table, querying information_schema will return empty columns executeExclusively(() -> { @@ -7797,7 +7769,7 @@ public void testCreateAvroTableWithSchemaUrl() try { MaterializedResult actual = computeActual("SHOW CREATE TABLE " + tableName); - assertEquals(actual.getOnlyValue(), expectedShowCreateTable); + assertThat(actual.getOnlyValue()).isEqualTo(expectedShowCreateTable); } finally { assertUpdate("DROP TABLE " + tableName); @@ -8113,7 +8085,7 @@ private void testSelectWithNoColumns(Session session, HiveStorageFormat storageF tableName, storageFormat); assertUpdate(session, createTable, 3); - assertTrue(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isTrue(); assertQuery("SELECT 1 FROM " + tableName, "VALUES 1, 1, 1"); assertQuery("SELECT count(*) FROM " + tableName, "SELECT 3"); @@ -8250,9 +8222,9 @@ public void testCoercingVarchar0ToVarchar1() getQueryRunner()::execute, "test_coercion_create_table_varchar", "(var_column_0 varchar(0), var_column_1 varchar(1), var_column_10 varchar(10))")) { - assertEquals(getColumnType(testTable.getName(), "var_column_0"), "varchar(1)"); - assertEquals(getColumnType(testTable.getName(), "var_column_1"), "varchar(1)"); - assertEquals(getColumnType(testTable.getName(), "var_column_10"), "varchar(10)"); + assertThat(getColumnType(testTable.getName(), "var_column_0")).isEqualTo("varchar(1)"); + assertThat(getColumnType(testTable.getName(), "var_column_1")).isEqualTo("varchar(1)"); + assertThat(getColumnType(testTable.getName(), "var_column_10")).isEqualTo("varchar(10)"); } } @@ -8263,7 +8235,7 @@ public void testCoercingVarchar0ToVarchar1WithCTAS() getQueryRunner()::execute, "test_coercion_ctas_varchar", "AS SELECT '' AS var_column")) { - assertEquals(getColumnType(testTable.getName(), "var_column"), "varchar(1)"); + assertThat(getColumnType(testTable.getName(), "var_column")).isEqualTo("varchar(1)"); } } @@ -8274,7 +8246,7 @@ public void testCoercingVarchar0ToVarchar1WithCTASNoData() getQueryRunner()::execute, "test_coercion_ctas_nd_varchar", "AS SELECT '' AS var_column WITH NO DATA")) { - assertEquals(getColumnType(testTable.getName(), "var_column"), "varchar(1)"); + assertThat(getColumnType(testTable.getName(), "var_column")).isEqualTo("varchar(1)"); } } @@ -8810,7 +8782,7 @@ public void testExplainOfCreateTableAs() { String query = "CREATE TABLE copy_orders AS SELECT * FROM orders"; MaterializedResult result = computeActual("EXPLAIN " + query); - assertEquals(getOnlyElement(result.getOnlyColumnAsSet()), getExplainPlan(query, DISTRIBUTED)); + assertThat(getOnlyElement(result.getOnlyColumnAsSet())).isEqualTo(getExplainPlan(query, DISTRIBUTED)); } @Test @@ -8825,7 +8797,7 @@ public void testAutoPurgeProperty() assertUpdate(createTableSql, 1500L); TableMetadata tableMetadataDefaults = getTableMetadata(catalog, TPCH_SCHEMA, tableName); - assertEquals(tableMetadataDefaults.getMetadata().getProperties().get(AUTO_PURGE), null); + assertThat(tableMetadataDefaults.getMetadata().getProperties().get(AUTO_PURGE)).isEqualTo(null); assertUpdate("DROP TABLE " + tableName); @@ -8839,7 +8811,7 @@ public void testAutoPurgeProperty() assertUpdate(createTableSqlWithAutoPurge, 1500L); TableMetadata tableMetadataWithPurge = getTableMetadata(catalog, TPCH_SCHEMA, tableName); - assertEquals(tableMetadataWithPurge.getMetadata().getProperties().get(AUTO_PURGE), true); + assertThat(tableMetadataWithPurge.getMetadata().getProperties().get(AUTO_PURGE)).isEqualTo(true); assertUpdate("DROP TABLE " + tableName); } @@ -9026,9 +8998,9 @@ private void assertOneNotNullResult(@Language("SQL") String query) private void assertOneNotNullResult(Session session, @Language("SQL") String query) { MaterializedResult results = getQueryRunner().execute(session, query).toTestTypes(); - assertEquals(results.getRowCount(), 1); - assertEquals(results.getMaterializedRows().get(0).getFieldCount(), 1); - assertNotNull(results.getMaterializedRows().get(0).getField(0)); + assertThat(results.getRowCount()).isEqualTo(1); + assertThat(results.getMaterializedRows().get(0).getFieldCount()).isEqualTo(1); + assertThat(results.getMaterializedRows().get(0).getField(0)).isNotNull(); } private Type canonicalizeType(Type type) @@ -9038,7 +9010,7 @@ private Type canonicalizeType(Type type) private void assertColumnType(TableMetadata tableMetadata, String columnName, Type expectedType) { - assertEquals(tableMetadata.getColumn(columnName).getType(), canonicalizeType(expectedType)); + assertThat(tableMetadata.getColumn(columnName).getType()).isEqualTo(canonicalizeType(expectedType)); } private void assertConstraints(@Language("SQL") String query, Set expected) @@ -9050,21 +9022,21 @@ private void assertConstraints(@Language("SQL") String query, Set partitionKeys) { Object partitionByProperty = tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY); if (hasPartition) { - assertEquals(partitionByProperty, partitionKeys); + assertThat(partitionByProperty).isEqualTo(partitionKeys); for (ColumnMetadata columnMetadata : tableMetadata.getColumns()) { boolean partitionKey = partitionKeys.contains(columnMetadata.getName()); - assertEquals(columnMetadata.getExtraInfo(), columnExtraInfo(partitionKey)); + assertThat(columnMetadata.getExtraInfo()).isEqualTo(columnExtraInfo(partitionKey)); } } else { - assertNull(partitionByProperty); + assertThat(partitionByProperty).isNull(); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/HiveQueryRunner.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/HiveQueryRunner.java index 19fa8bb0cd6e..93f9ff35d614 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/HiveQueryRunner.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/HiveQueryRunner.java @@ -64,7 +64,7 @@ import static java.util.Locale.ENGLISH; import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public final class HiveQueryRunner { @@ -257,7 +257,9 @@ public DistributedQueryRunner build() Map hiveProperties = new HashMap<>(); if (!skipTimezoneSetup) { - assertEquals(DateTimeZone.getDefault(), TIME_ZONE, "Timezone not configured correctly. Add -Duser.timezone=America/Bahia_Banderas to your JVM arguments"); + assertThat(DateTimeZone.getDefault()) + .describedAs("Timezone not configured correctly. Add -Duser.timezone=America/Bahia_Banderas to your JVM arguments") + .isEqualTo(TIME_ZONE); hiveProperties.put("hive.rcfile.time-zone", TIME_ZONE.getID()); hiveProperties.put("hive.parquet.time-zone", TIME_ZONE.getID()); } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java index 64a89f0076fc..555ce79943bf 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestBackgroundHiveSplitLoader.java @@ -118,9 +118,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -159,7 +156,7 @@ public void testNoPathFilter() HiveSplitSource hiveSplitSource = hiveSplitSource(backgroundHiveSplitLoader); backgroundHiveSplitLoader.start(hiveSplitSource); - assertEquals(drain(hiveSplitSource).size(), 2); + assertThat(drain(hiveSplitSource).size()).isEqualTo(2); } @Test @@ -196,7 +193,7 @@ private void assertCsvSplitCount(FileEntry file, Map tableProper HiveSplitSource hiveSplitSource = hiveSplitSource(backgroundHiveSplitLoader); backgroundHiveSplitLoader.start(hiveSplitSource); - assertEquals(drainSplits(hiveSplitSource).size(), expectedSplitCount); + assertThat(drainSplits(hiveSplitSource).size()).isEqualTo(expectedSplitCount); } @Test @@ -210,8 +207,8 @@ public void testPathFilter() HiveSplitSource hiveSplitSource = hiveSplitSource(backgroundHiveSplitLoader); backgroundHiveSplitLoader.start(hiveSplitSource); List paths = drain(hiveSplitSource); - assertEquals(paths.size(), 1); - assertEquals(paths.get(0), LOCATION.toString()); + assertThat(paths.size()).isEqualTo(1); + assertThat(paths.get(0)).isEqualTo(LOCATION.toString()); } @Test @@ -228,8 +225,8 @@ public void testPathFilterOneBucketMatchPartitionedTable() HiveSplitSource hiveSplitSource = hiveSplitSource(backgroundHiveSplitLoader); backgroundHiveSplitLoader.start(hiveSplitSource); List paths = drain(hiveSplitSource); - assertEquals(paths.size(), 1); - assertEquals(paths.get(0), LOCATION.toString()); + assertThat(paths.size()).isEqualTo(1); + assertThat(paths.get(0)).isEqualTo(LOCATION.toString()); } @Test @@ -252,8 +249,8 @@ public void testPathFilterBucketedPartitionedTable() HiveSplitSource hiveSplitSource = hiveSplitSource(backgroundHiveSplitLoader); backgroundHiveSplitLoader.start(hiveSplitSource); List paths = drain(hiveSplitSource); - assertEquals(paths.size(), 1); - assertEquals(paths.get(0), LOCATION.toString()); + assertThat(paths.size()).isEqualTo(1); + assertThat(paths.get(0)).isEqualTo(LOCATION.toString()); } @Test @@ -276,7 +273,7 @@ public void testEmptyFileWithNoBlocks() backgroundHiveSplitLoader.start(hiveSplitSource); List splits = drainSplits(hiveSplitSource); - assertEquals(splits.size(), 0); + assertThat(splits.size()).isEqualTo(0); } @Test @@ -344,8 +341,8 @@ public TupleDomain getCurrentPredicate() HiveSplitSource hiveSplitSource = hiveSplitSource(backgroundHiveSplitLoader); backgroundHiveSplitLoader.start(hiveSplitSource); - assertEquals(drain(hiveSplitSource).size(), 2); - assertTrue(hiveSplitSource.isFinished()); + assertThat(drain(hiveSplitSource).size()).isEqualTo(2); + assertThat(hiveSplitSource.isFinished()).isTrue(); } @Test @@ -353,7 +350,7 @@ public void testCachedDirectoryLister() throws Exception { CachingDirectoryLister cachingDirectoryLister = new CachingDirectoryLister(new Duration(5, TimeUnit.MINUTES), DataSize.of(100, KILOBYTE), List.of("test_dbname.test_table")); - assertEquals(cachingDirectoryLister.getRequestCount(), 0); + assertThat(cachingDirectoryLister.getRequestCount()).isEqualTo(0); int totalCount = 100; CountDownLatch firstVisit = new CountDownLatch(1); @@ -382,51 +379,51 @@ public void testCachedDirectoryLister() } for (Future> future : futures) { - assertEquals(future.get().size(), TEST_LOCATIONS.size()); + assertThat(future.get().size()).isEqualTo(TEST_LOCATIONS.size()); } - assertEquals(cachingDirectoryLister.getRequestCount(), totalCount); - assertEquals(cachingDirectoryLister.getHitCount(), totalCount - 1); - assertEquals(cachingDirectoryLister.getMissCount(), 1); + assertThat(cachingDirectoryLister.getRequestCount()).isEqualTo(totalCount); + assertThat(cachingDirectoryLister.getHitCount()).isEqualTo(totalCount - 1); + assertThat(cachingDirectoryLister.getMissCount()).isEqualTo(1); } @Test public void testGetBucketNumber() { // legacy Presto naming pattern - assertEquals(getBucketNumber("20190526_072952_00009_fn7s5_bucket-00234"), OptionalInt.of(234)); - assertEquals(getBucketNumber("20190526_072952_00009_fn7s5_bucket-00234.txt"), OptionalInt.of(234)); - assertEquals(getBucketNumber("20190526_235847_87654_fn7s5_bucket-56789"), OptionalInt.of(56789)); + assertThat(getBucketNumber("20190526_072952_00009_fn7s5_bucket-00234")).isEqualTo(OptionalInt.of(234)); + assertThat(getBucketNumber("20190526_072952_00009_fn7s5_bucket-00234.txt")).isEqualTo(OptionalInt.of(234)); + assertThat(getBucketNumber("20190526_235847_87654_fn7s5_bucket-56789")).isEqualTo(OptionalInt.of(56789)); // Hive - assertEquals(getBucketNumber("0234_0"), OptionalInt.of(234)); - assertEquals(getBucketNumber("000234_0"), OptionalInt.of(234)); - assertEquals(getBucketNumber("0234_99"), OptionalInt.of(234)); - assertEquals(getBucketNumber("0234_0.txt"), OptionalInt.of(234)); - assertEquals(getBucketNumber("0234_0_copy_1"), OptionalInt.of(234)); + assertThat(getBucketNumber("0234_0")).isEqualTo(OptionalInt.of(234)); + assertThat(getBucketNumber("000234_0")).isEqualTo(OptionalInt.of(234)); + assertThat(getBucketNumber("0234_99")).isEqualTo(OptionalInt.of(234)); + assertThat(getBucketNumber("0234_0.txt")).isEqualTo(OptionalInt.of(234)); + assertThat(getBucketNumber("0234_0_copy_1")).isEqualTo(OptionalInt.of(234)); // starts with non-zero - assertEquals(getBucketNumber("234_99"), OptionalInt.of(234)); - assertEquals(getBucketNumber("1234_0_copy_1"), OptionalInt.of(1234)); + assertThat(getBucketNumber("234_99")).isEqualTo(OptionalInt.of(234)); + assertThat(getBucketNumber("1234_0_copy_1")).isEqualTo(OptionalInt.of(1234)); // Hive ACID - assertEquals(getBucketNumber("bucket_1234"), OptionalInt.of(1234)); - assertEquals(getBucketNumber("bucket_01234"), OptionalInt.of(1234)); + assertThat(getBucketNumber("bucket_1234")).isEqualTo(OptionalInt.of(1234)); + assertThat(getBucketNumber("bucket_01234")).isEqualTo(OptionalInt.of(1234)); // not matching - assertEquals(getBucketNumber("0234.txt"), OptionalInt.empty()); - assertEquals(getBucketNumber("0234.txt"), OptionalInt.empty()); + assertThat(getBucketNumber("0234.txt")).isEqualTo(OptionalInt.empty()); + assertThat(getBucketNumber("0234.txt")).isEqualTo(OptionalInt.empty()); } @Test public void testGetAttemptId() { - assertFalse(hasAttemptId("bucket_00000")); - assertTrue(hasAttemptId("bucket_00000_0")); - assertTrue(hasAttemptId("bucket_00000_10")); - assertTrue(hasAttemptId("bucket_00000_1000")); - assertFalse(hasAttemptId("bucket_00000__1000")); - assertFalse(hasAttemptId("bucket_00000_a")); - assertFalse(hasAttemptId("bucket_00000_ad")); - assertFalse(hasAttemptId("base_00000_00")); + assertThat(hasAttemptId("bucket_00000")).isFalse(); + assertThat(hasAttemptId("bucket_00000_0")).isTrue(); + assertThat(hasAttemptId("bucket_00000_10")).isTrue(); + assertThat(hasAttemptId("bucket_00000_1000")).isTrue(); + assertThat(hasAttemptId("bucket_00000__1000")).isFalse(); + assertThat(hasAttemptId("bucket_00000_a")).isFalse(); + assertThat(hasAttemptId("bucket_00000_ad")).isFalse(); + assertThat(hasAttemptId("base_00000_00")).isFalse(); } @Test @@ -498,7 +495,7 @@ public HivePartitionMetadata next() .hasMessageEndingWith("loading error occurred"); if (threads == 1) { - assertFalse(iteratorUsedAfterException.get()); + assertThat(iteratorUsedAfterException.get()).isFalse(); } } @@ -518,7 +515,7 @@ public void testMultipleSplitsPerBucket() HiveSplitSource hiveSplitSource = hiveSplitSource(backgroundHiveSplitLoader); backgroundHiveSplitLoader.start(hiveSplitSource); - assertEquals(drainSplits(hiveSplitSource).size(), 17); + assertThat(drainSplits(hiveSplitSource).size()).isEqualTo(17); } @Test @@ -811,9 +808,9 @@ public void testBuildManifestFileIterator() locations, true); List splits = ImmutableList.copyOf(splitIterator); - assertEquals(splits.size(), 2); - assertEquals(splits.get(0).getPath(), firstFilePath.toString()); - assertEquals(splits.get(1).getPath(), secondFilePath.toString()); + assertThat(splits.size()).isEqualTo(2); + assertThat(splits.get(0).getPath()).isEqualTo(firstFilePath.toString()); + assertThat(splits.get(1).getPath()).isEqualTo(secondFilePath.toString()); } @Test @@ -853,9 +850,9 @@ public void testBuildManifestFileIteratorNestedDirectory() locations, false); List splits = ImmutableList.copyOf(splitIterator); - assertEquals(splits.size(), 2); - assertEquals(splits.get(0).getPath(), filePath.toString()); - assertEquals(splits.get(1).getPath(), directoryPath.toString()); + assertThat(splits.size()).isEqualTo(2); + assertThat(splits.get(0).getPath()).isEqualTo(filePath.toString()); + assertThat(splits.get(1).getPath()).isEqualTo(directoryPath.toString()); } @Test diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveAnalyzeCorruptStatistics.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveAnalyzeCorruptStatistics.java index 1b803b5a11e6..bb70593a2558 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveAnalyzeCorruptStatistics.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveAnalyzeCorruptStatistics.java @@ -23,8 +23,8 @@ import static io.trino.testing.TestingNames.randomNameSuffix; import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestHiveAnalyzeCorruptStatistics extends AbstractTestQueryFramework @@ -71,7 +71,7 @@ private void prepareBrokenColumnStatisticsTable(String tableName) assertUpdate("CREATE TABLE " + tableName + " AS SELECT 1 col", 1); // Insert duplicated row to simulate broken column statistics status https://github.com/trinodb/trino/issues/13787 - assertEquals(onMetastore("SELECT COUNT(1) FROM TAB_COL_STATS WHERE db_name = 'tpch' AND table_name = '" + tableName + "'"), "1"); + assertThat(onMetastore("SELECT COUNT(1) FROM TAB_COL_STATS WHERE db_name = 'tpch' AND table_name = '" + tableName + "'")).isEqualTo("1"); onMetastore(""" INSERT INTO TAB_COL_STATS SELECT @@ -99,7 +99,7 @@ private void prepareBrokenColumnStatisticsTable(String tableName) FROM TAB_COL_STATS WHERE db_name = 'tpch' AND table_name = '%s' """.formatted(tableName)); - assertEquals(onMetastore("SELECT COUNT(1) FROM TAB_COL_STATS WHERE db_name = 'tpch' AND table_name = '" + tableName + "'"), "2"); + assertThat(onMetastore("SELECT COUNT(1) FROM TAB_COL_STATS WHERE db_name = 'tpch' AND table_name = '" + tableName + "'")).isEqualTo("2"); } @Test @@ -126,7 +126,7 @@ private void prepareBrokenPartitionStatisticsTable(String tableName) assertUpdate("CREATE TABLE " + tableName + " WITH(partitioned_by = ARRAY['part']) AS SELECT 1 col, 'test_partition' part", 1); // Insert duplicated row to simulate broken partition statistics status https://github.com/trinodb/trino/issues/13787 - assertEquals(onMetastore("SELECT COUNT(1) FROM PART_COL_STATS WHERE db_name = 'tpch' AND table_name = '" + tableName + "'"), "1"); + assertThat(onMetastore("SELECT COUNT(1) FROM PART_COL_STATS WHERE db_name = 'tpch' AND table_name = '" + tableName + "'")).isEqualTo("1"); onMetastore(""" INSERT INTO PART_COL_STATS SELECT @@ -155,7 +155,7 @@ private void prepareBrokenPartitionStatisticsTable(String tableName) FROM PART_COL_STATS WHERE db_name = 'tpch' AND table_name = '%s' """.formatted(tableName)); - assertEquals(onMetastore("SELECT COUNT(1) FROM PART_COL_STATS WHERE db_name = 'tpch' AND table_name = '" + tableName + "'"), "2"); + assertThat(onMetastore("SELECT COUNT(1) FROM PART_COL_STATS WHERE db_name = 'tpch' AND table_name = '" + tableName + "'")).isEqualTo("2"); } private String onMetastore(@Language("SQL") String sql) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveBooleanParser.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveBooleanParser.java index 53aa16a0701e..d5b6fdef6a96 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveBooleanParser.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveBooleanParser.java @@ -17,31 +17,29 @@ import static io.trino.plugin.hive.HiveBooleanParser.parseHiveBoolean; import static java.nio.charset.StandardCharsets.US_ASCII; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestHiveBooleanParser { @Test public void testParse() { - assertTrue(parseBoolean("true")); - assertTrue(parseBoolean("TRUE")); - assertTrue(parseBoolean("tRuE")); + assertThat(parseBoolean("true")).isTrue(); + assertThat(parseBoolean("TRUE")).isTrue(); + assertThat(parseBoolean("tRuE")).isTrue(); - assertFalse(parseBoolean("false")); - assertFalse(parseBoolean("FALSE")); - assertFalse(parseBoolean("fAlSe")); + assertThat(parseBoolean("false")).isFalse(); + assertThat(parseBoolean("FALSE")).isFalse(); + assertThat(parseBoolean("fAlSe")).isFalse(); - assertNull(parseBoolean("true ")); - assertNull(parseBoolean(" true")); - assertNull(parseBoolean("false ")); - assertNull(parseBoolean(" false")); - assertNull(parseBoolean("t")); - assertNull(parseBoolean("f")); - assertNull(parseBoolean("")); - assertNull(parseBoolean("blah")); + assertThat(parseBoolean("true ")).isNull(); + assertThat(parseBoolean(" true")).isNull(); + assertThat(parseBoolean("false ")).isNull(); + assertThat(parseBoolean(" false")).isNull(); + assertThat(parseBoolean("t")).isNull(); + assertThat(parseBoolean("f")).isNull(); + assertThat(parseBoolean("")).isNull(); + assertThat(parseBoolean("blah")).isNull(); } private static Boolean parseBoolean(String s) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveColumnHandle.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveColumnHandle.java index 2c5887fcb75d..684b305b437b 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveColumnHandle.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveColumnHandle.java @@ -35,7 +35,7 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.util.Arrays.asList; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestHiveColumnHandle { @@ -93,16 +93,16 @@ private void testRoundTrip(HiveColumnHandle expected) String json = codec.toJson(expected); HiveColumnHandle actual = codec.fromJson(json); - assertEquals(actual.getBaseColumnName(), expected.getBaseColumnName()); - assertEquals(actual.getBaseHiveColumnIndex(), expected.getBaseHiveColumnIndex()); - assertEquals(actual.getBaseType(), expected.getBaseType()); - assertEquals(actual.getBaseHiveType(), expected.getBaseHiveType()); + assertThat(actual.getBaseColumnName()).isEqualTo(expected.getBaseColumnName()); + assertThat(actual.getBaseHiveColumnIndex()).isEqualTo(expected.getBaseHiveColumnIndex()); + assertThat(actual.getBaseType()).isEqualTo(expected.getBaseType()); + assertThat(actual.getBaseHiveType()).isEqualTo(expected.getBaseHiveType()); - assertEquals(actual.getName(), expected.getName()); - assertEquals(actual.getType(), expected.getType()); - assertEquals(actual.getHiveType(), expected.getHiveType()); + assertThat(actual.getName()).isEqualTo(expected.getName()); + assertThat(actual.getType()).isEqualTo(expected.getType()); + assertThat(actual.getHiveType()).isEqualTo(expected.getHiveType()); - assertEquals(actual.getHiveColumnProjectionInfo(), expected.getHiveColumnProjectionInfo()); - assertEquals(actual.isPartitionKey(), expected.isPartitionKey()); + assertThat(actual.getHiveColumnProjectionInfo()).isEqualTo(expected.getHiveColumnProjectionInfo()); + assertThat(actual.isPartitionKey()).isEqualTo(expected.isPartitionKey()); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveCreateExternalTable.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveCreateExternalTable.java index 2ae6e3304a36..6d0831f018b0 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveCreateExternalTable.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveCreateExternalTable.java @@ -34,7 +34,6 @@ import static java.lang.String.format; import static java.nio.file.Files.createTempDirectory; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestHiveCreateExternalTable extends AbstractTestQueryFramework @@ -116,7 +115,7 @@ public void testCreateExternalTableOnNonExistingPath() assertUpdate(createTableSql); String actual = (String) computeScalar("SHOW CREATE TABLE " + tableName); - assertEquals(actual, createTableSql); + assertThat(actual).isEqualTo(createTableSql); assertUpdate("DROP TABLE " + tableName); deleteRecursively(tempDir, ALLOW_INSECURE); } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveDecimalParser.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveDecimalParser.java index 36b2a1fb9cc8..80707d3854ee 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveDecimalParser.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveDecimalParser.java @@ -19,7 +19,7 @@ import java.math.BigDecimal; import static java.nio.charset.StandardCharsets.US_ASCII; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestHiveDecimalParser { @@ -54,6 +54,6 @@ private void checkParseDecimal(String input, int precision, int scale, BigDecima { byte[] bytes = input.getBytes(US_ASCII); BigDecimal parsed = HiveDecimalParser.parseHiveDecimal(bytes, 0, bytes.length, DecimalType.createDecimalType(precision, scale)); - assertEquals(parsed, expected); + assertThat(parsed).isEqualTo(expected); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveDistributedJoinQueries.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveDistributedJoinQueries.java index 35a8da72e26c..0fdb2687ce87 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveDistributedJoinQueries.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveDistributedJoinQueries.java @@ -23,7 +23,7 @@ import static com.google.common.base.Verify.verify; import static io.trino.SystemSessionProperties.JOIN_DISTRIBUTION_TYPE; import static io.trino.sql.planner.OptimizerConfig.JoinDistributionType.BROADCAST; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; /** * @see TestHiveDistributedJoinQueriesWithoutDynamicFiltering for tests with dynamic filtering disabled @@ -60,6 +60,6 @@ public void testJoinWithEmptyBuildSide() MaterializedResultWithQueryId result = getDistributedQueryRunner().executeWithQueryId( session, "SELECT * FROM lineitem JOIN orders ON lineitem.orderkey = orders.orderkey AND orders.totalprice = 123.4567"); - assertEquals(result.getResult().getRowCount(), 0); + assertThat(result.getResult().getRowCount()).isEqualTo(0); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java index a598b26c166c..ac702ba3b9f1 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveFileFormats.java @@ -211,10 +211,9 @@ import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaStringObjectInspector; import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaTimestampObjectInspector; import static org.apache.hadoop.io.SequenceFile.CompressionType.BLOCK; +import static org.assertj.core.api.Assertions.assertThat; import static org.joda.time.DateTimeZone.UTC; import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; // Failing on multiple threads because of org.apache.hadoop.hive.ql.io.parquet.write.ParquetRecordWriterWrapper // uses a single record writer across all threads. @@ -245,9 +244,9 @@ public static Object[][] validFileSizePaddingProvider() public void setUp() { // ensure the expected timezone is configured for this VM - assertEquals(TimeZone.getDefault().getID(), - "America/Bahia_Banderas", - "Timezone not configured correctly. Add -Duser.timezone=America/Bahia_Banderas to your JVM arguments"); + assertThat(TimeZone.getDefault().getID()) + .describedAs("Timezone not configured correctly. Add -Duser.timezone=America/Bahia_Banderas to your JVM arguments") + .isEqualTo("America/Bahia_Banderas"); } @Test(dataProvider = "validRowAndFileSizePadding") @@ -293,7 +292,7 @@ public void testCsvFile(int rowCount, long fileSizePadding) .filter(column -> column.partitionKey() || (column.type() instanceof VarcharType varcharType && varcharType.isUnbounded() && !column.name().contains("_null_"))) .collect(toImmutableList()); - assertTrue(testColumns.size() > 5); + assertThat(testColumns.size() > 5).isTrue(); assertThatFileFormat(CSV) .withColumns(testColumns) @@ -1027,7 +1026,7 @@ private static void checkPageSource(ConnectorPageSource pageSource, List= 0, "rowsCount must be non-negative"); + assertThat(storageFormat) + .describedAs("storageFormat must be specified") + .isNotNull(); + assertThat(writeColumns) + .describedAs("writeColumns must be specified") + .isNotNull(); + assertThat(readColumns) + .describedAs("readColumns must be specified") + .isNotNull(); + assertThat(session) + .describedAs("session must be specified") + .isNotNull(); + assertThat(rowsCount >= 0) + .describedAs("rowsCount must be non-negative") + .isTrue(); String compressionSuffix = compressionCodec.getHiveCompressionKind() .map(CompressionKind::getFileExtension) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveLocationService.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveLocationService.java index d87d1275a43f..c98aa7835142 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveLocationService.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveLocationService.java @@ -15,6 +15,7 @@ import io.trino.filesystem.Location; import io.trino.plugin.hive.LocationService.WriteInfo; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; @@ -22,7 +23,6 @@ import static io.trino.plugin.hive.LocationHandle.WriteMode.DIRECT_TO_TARGET_NEW_DIRECTORY; import static io.trino.plugin.hive.LocationHandle.WriteMode.STAGE_AND_MOVE_TO_TARGET_DIRECTORY; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; -import static org.testng.Assert.assertEquals; public class TestHiveLocationService { @@ -86,9 +86,9 @@ public Assertion(LocationHandle locationHandle, boolean overwrite) public void producesWriteInfo(WriteInfo expected) { - assertEquals(actual.writePath(), expected.writePath()); - assertEquals(actual.targetPath(), expected.targetPath()); - assertEquals(actual.writeMode(), expected.writeMode()); + Assertions.assertThat(actual.writePath()).isEqualTo(expected.writePath()); + Assertions.assertThat(actual.targetPath()).isEqualTo(expected.targetPath()); + Assertions.assertThat(actual.writeMode()).isEqualTo(expected.writeMode()); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveMetadata.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveMetadata.java index 5fa38f5d0bef..b8154bda685a 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveMetadata.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveMetadata.java @@ -31,8 +31,7 @@ import static io.trino.plugin.hive.HiveMetadata.createPredicate; import static io.trino.spi.type.DoubleType.DOUBLE; import static io.trino.spi.type.VarcharType.VARCHAR; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; +import static org.assertj.core.api.Assertions.assertThat; public class TestHiveMetadata { @@ -67,7 +66,7 @@ public void testCreatePredicate() Domain domain = createPredicate(ImmutableList.of(TEST_COLUMN_HANDLE), partitions.build()) .getDomains().orElseThrow().get(TEST_COLUMN_HANDLE); - assertEquals(domain, Domain.create( + assertThat(domain).isEqualTo(Domain.create( ValueSet.copyOf(VARCHAR, IntStream.range(0, 5_000) .mapToObj(i -> utf8Slice(Integer.toString(i))) @@ -89,7 +88,7 @@ public void testCreateOnlyNullsPredicate() Domain domain = createPredicate(ImmutableList.of(TEST_COLUMN_HANDLE), partitions.build()) .getDomains().orElseThrow().get(TEST_COLUMN_HANDLE); - assertEquals(domain, Domain.onlyNull(VARCHAR)); + assertThat(domain).isEqualTo(Domain.onlyNull(VARCHAR)); } @Test @@ -110,7 +109,7 @@ public void testCreatePredicateWithNaN() Domain domain = createPredicate(ImmutableList.of(columnHandle), partitions.build()) .getDomains().orElseThrow().get(columnHandle); - assertEquals(domain, Domain.notNull(DOUBLE)); + assertThat(domain).isEqualTo(Domain.notNull(DOUBLE)); } @Test @@ -136,7 +135,7 @@ public void testCreatePredicateWithNaNAndNull() Domain domain = createPredicate(ImmutableList.of(columnHandle), partitions.build()) .getDomains().orElseThrow().get(columnHandle); - assertNull(domain); + assertThat(domain).isNull(); } @Test @@ -158,6 +157,6 @@ public void testCreateMixedPredicate() Domain domain = createPredicate(ImmutableList.of(TEST_COLUMN_HANDLE), partitions.build()) .getDomains().orElseThrow().get(TEST_COLUMN_HANDLE); - assertEquals(domain, Domain.create(ValueSet.of(VARCHAR, utf8Slice("0"), utf8Slice("1"), utf8Slice("2"), utf8Slice("3"), utf8Slice("4")), true)); + assertThat(domain).isEqualTo(Domain.create(ValueSet.of(VARCHAR, utf8Slice("0"), utf8Slice("1"), utf8Slice("2"), utf8Slice("3"), utf8Slice("4")), true)); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java index 09974bb0654b..d4e5bce1c78c 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSink.java @@ -91,8 +91,6 @@ import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; public class TestHivePageSink { @@ -140,7 +138,9 @@ void testAllFormats() } long length = writeTestFile(fileSystemFactory, config, sortingFileWriterConfig, metastore, makeFileName(config)); - assertTrue(uncompressedLength > length, format("%s with %s compressed to %s which is not less than %s", format, codec, length, uncompressedLength)); + assertThat(uncompressedLength > length) + .describedAs(format("%s with %s compressed to %s which is not less than %s", format, codec, length, uncompressedLength)) + .isTrue(); } } } @@ -224,7 +224,7 @@ private static long writeTestFile(TrinoFileSystemFactory fileSystemFactory, Hive MaterializedResult expectedResults = toMaterializedResult(getHiveSession(config), columnTypes, ImmutableList.of(page)); MaterializedResult results = toMaterializedResult(getHiveSession(config), columnTypes, pages); assertThat(results).containsExactlyElementsOf(expectedResults); - assertEquals(round(stats.getInputPageSizeInBytes().getAllTime().getMax()), page.getRetainedSizeInBytes()); + assertThat(round(stats.getInputPageSizeInBytes().getAllTime().getMax())).isEqualTo(page.getRetainedSizeInBytes()); return fileEntry.length(); } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePartitionedBucketFunction.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePartitionedBucketFunction.java index 1b904d69de25..77f847cb7633 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePartitionedBucketFunction.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePartitionedBucketFunction.java @@ -43,7 +43,7 @@ import static io.trino.spi.type.BigintType.BIGINT; import static java.util.Collections.max; import static java.util.Collections.min; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestHivePartitionedBucketFunction { @@ -153,17 +153,15 @@ public void testConsecutiveBucketsWithinPartition(BucketingVersion hiveBucketing int maxPosition = max(positions); // assert that every bucket number was generated - assertEquals(maxPosition - minPosition + 1, 10); + assertThat(maxPosition - minPosition + 1).isEqualTo(10); } private static void assertBucketCount(BucketFunction bucketFunction, Page page, Collection positions, int bucketCount) { - assertEquals( - positions.stream() - .map(position -> bucketFunction.getBucket(page, position)) - .distinct() - .count(), - bucketCount); + assertThat(positions.stream() + .map(position -> bucketFunction.getBucket(page, position)) + .distinct() + .count()).isEqualTo(bucketCount); } private static Block createLongSequenceBlockWithNull(int numValues) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveQlTranslation.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveQlTranslation.java index 63d126a4f417..79a129cdcf4f 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveQlTranslation.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveQlTranslation.java @@ -33,7 +33,7 @@ import static java.lang.String.format; import static java.lang.String.join; import static java.util.Collections.nCopies; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestHiveQlTranslation { @@ -256,7 +256,7 @@ public void testStringEscapes() private void assertTranslation(String hiveSql, String expectedTrinoSql) { String actualTrinoSql = translateHiveViewToTrino(hiveSql); - assertEquals(actualTrinoSql, expectedTrinoSql); + assertThat(actualTrinoSql).isEqualTo(expectedTrinoSql); assertTrinoSqlIsParsable(expectedTrinoSql); assertTrinoSqlIsParsable(actualTrinoSql); } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveSplit.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveSplit.java index 068aec23941b..2195a07d35cf 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveSplit.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveSplit.java @@ -36,7 +36,7 @@ import static io.trino.plugin.hive.HiveType.HIVE_LONG; import static io.trino.plugin.hive.util.HiveBucketing.BucketingVersion.BUCKETING_V1; import static io.trino.spi.type.BigintType.BIGINT; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestHiveSplit { @@ -86,18 +86,18 @@ public void testJsonRoundTrip() String json = codec.toJson(expected); HiveSplit actual = codec.fromJson(json); - assertEquals(actual.getPartitionName(), expected.getPartitionName()); - assertEquals(actual.getPath(), expected.getPath()); - assertEquals(actual.getStart(), expected.getStart()); - assertEquals(actual.getLength(), expected.getLength()); - assertEquals(actual.getEstimatedFileSize(), expected.getEstimatedFileSize()); - assertEquals(actual.getSchema(), expected.getSchema()); - assertEquals(actual.getPartitionKeys(), expected.getPartitionKeys()); - assertEquals(actual.getTableToPartitionMapping().getPartitionColumnCoercions(), expected.getTableToPartitionMapping().getPartitionColumnCoercions()); - assertEquals(actual.getTableToPartitionMapping().getTableToPartitionColumns(), expected.getTableToPartitionMapping().getTableToPartitionColumns()); - assertEquals(actual.getBucketConversion(), expected.getBucketConversion()); - assertEquals(actual.isForceLocalScheduling(), expected.isForceLocalScheduling()); - assertEquals(actual.getAcidInfo().get(), expected.getAcidInfo().get()); - assertEquals(actual.getSplitWeight(), expected.getSplitWeight()); + assertThat(actual.getPartitionName()).isEqualTo(expected.getPartitionName()); + assertThat(actual.getPath()).isEqualTo(expected.getPath()); + assertThat(actual.getStart()).isEqualTo(expected.getStart()); + assertThat(actual.getLength()).isEqualTo(expected.getLength()); + assertThat(actual.getEstimatedFileSize()).isEqualTo(expected.getEstimatedFileSize()); + assertThat(actual.getSchema()).isEqualTo(expected.getSchema()); + assertThat(actual.getPartitionKeys()).isEqualTo(expected.getPartitionKeys()); + assertThat(actual.getTableToPartitionMapping().getPartitionColumnCoercions()).isEqualTo(expected.getTableToPartitionMapping().getPartitionColumnCoercions()); + assertThat(actual.getTableToPartitionMapping().getTableToPartitionColumns()).isEqualTo(expected.getTableToPartitionMapping().getTableToPartitionColumns()); + assertThat(actual.getBucketConversion()).isEqualTo(expected.getBucketConversion()); + assertThat(actual.isForceLocalScheduling()).isEqualTo(expected.isForceLocalScheduling()); + assertThat(actual.getAcidInfo().get()).isEqualTo(expected.getAcidInfo().get()); + assertThat(actual.getSplitWeight()).isEqualTo(expected.getSplitWeight()); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveSplitSource.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveSplitSource.java index 516fa6e1c5cc..553a3d20f71f 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveSplitSource.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveSplitSource.java @@ -38,9 +38,8 @@ import static io.trino.plugin.hive.HiveTestUtils.SESSION; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static java.lang.Math.toIntExact; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; public class TestHiveSplitSource { @@ -63,20 +62,20 @@ public void testOutstandingSplitCount() // add 10 splits for (int i = 0; i < 10; i++) { hiveSplitSource.addToQueue(new TestSplit(i)); - assertEquals(hiveSplitSource.getBufferedInternalSplitCount(), i + 1); + assertThat(hiveSplitSource.getBufferedInternalSplitCount()).isEqualTo(i + 1); } // remove 1 split - assertEquals(getSplits(hiveSplitSource, 1).size(), 1); - assertEquals(hiveSplitSource.getBufferedInternalSplitCount(), 9); + assertThat(getSplits(hiveSplitSource, 1).size()).isEqualTo(1); + assertThat(hiveSplitSource.getBufferedInternalSplitCount()).isEqualTo(9); // remove 4 splits - assertEquals(getSplits(hiveSplitSource, 4).size(), 4); - assertEquals(hiveSplitSource.getBufferedInternalSplitCount(), 5); + assertThat(getSplits(hiveSplitSource, 4).size()).isEqualTo(4); + assertThat(hiveSplitSource.getBufferedInternalSplitCount()).isEqualTo(5); // try to remove 20 splits, and verify we only got 5 - assertEquals(getSplits(hiveSplitSource, 20).size(), 5); - assertEquals(hiveSplitSource.getBufferedInternalSplitCount(), 0); + assertThat(getSplits(hiveSplitSource, 20).size()).isEqualTo(5); + assertThat(hiveSplitSource.getBufferedInternalSplitCount()).isEqualTo(0); } @Test @@ -98,11 +97,11 @@ public void testDynamicPartitionPruning() // add two splits, one of the splits is dynamically pruned hiveSplitSource.addToQueue(new TestSplit(0, () -> false)); hiveSplitSource.addToQueue(new TestSplit(1, () -> true)); - assertEquals(hiveSplitSource.getBufferedInternalSplitCount(), 2); + assertThat(hiveSplitSource.getBufferedInternalSplitCount()).isEqualTo(2); // try to remove 2 splits, only one should be returned - assertEquals(getSplits(hiveSplitSource, 2).size(), 1); - assertEquals(hiveSplitSource.getBufferedInternalSplitCount(), 0); + assertThat(getSplits(hiveSplitSource, 2).size()).isEqualTo(1); + assertThat(hiveSplitSource.getBufferedInternalSplitCount()).isEqualTo(0); } @Test @@ -128,10 +127,10 @@ public void testEvenlySizedSplitRemainder() hiveSplitSource.addToQueue(new TestSplit(1, OptionalInt.empty(), fileSize)); HiveSplit first = (HiveSplit) getSplits(hiveSplitSource, 1).get(0); - assertEquals(first.getLength(), halfOfSize); + assertThat(first.getLength()).isEqualTo(halfOfSize); HiveSplit second = (HiveSplit) getSplits(hiveSplitSource, 1).get(0); - assertEquals(second.getLength(), fileSize.toBytes() - halfOfSize); + assertThat(second.getLength()).isEqualTo(fileSize.toBytes() - halfOfSize); } @Test @@ -153,30 +152,30 @@ public void testFail() // add some splits for (int i = 0; i < 5; i++) { hiveSplitSource.addToQueue(new TestSplit(i)); - assertEquals(hiveSplitSource.getBufferedInternalSplitCount(), i + 1); + assertThat(hiveSplitSource.getBufferedInternalSplitCount()).isEqualTo(i + 1); } // remove a split and verify - assertEquals(getSplits(hiveSplitSource, 1).size(), 1); - assertEquals(hiveSplitSource.getBufferedInternalSplitCount(), 4); + assertThat(getSplits(hiveSplitSource, 1).size()).isEqualTo(1); + assertThat(hiveSplitSource.getBufferedInternalSplitCount()).isEqualTo(4); // fail source hiveSplitSource.fail(new RuntimeException("test")); - assertEquals(hiveSplitSource.getBufferedInternalSplitCount(), 4); + assertThat(hiveSplitSource.getBufferedInternalSplitCount()).isEqualTo(4); // try to remove a split and verify we got the expected exception assertThatThrownBy(() -> getSplits(hiveSplitSource, 1)) .isInstanceOf(RuntimeException.class) .hasMessage("test"); - assertEquals(hiveSplitSource.getBufferedInternalSplitCount(), 4); // 3 splits + poison + assertThat(hiveSplitSource.getBufferedInternalSplitCount()).isEqualTo(4); // 3 splits + poison // attempt to add another split and verify it does not work hiveSplitSource.addToQueue(new TestSplit(99)); - assertEquals(hiveSplitSource.getBufferedInternalSplitCount(), 4); // 3 splits + poison + assertThat(hiveSplitSource.getBufferedInternalSplitCount()).isEqualTo(4); // 3 splits + poison // fail source again hiveSplitSource.fail(new RuntimeException("another failure")); - assertEquals(hiveSplitSource.getBufferedInternalSplitCount(), 4); // 3 splits + poison + assertThat(hiveSplitSource.getBufferedInternalSplitCount()).isEqualTo(4); // 3 splits + poison // try to remove a split and verify we got the first exception assertThatThrownBy(() -> getSplits(hiveSplitSource, 1)) @@ -209,7 +208,7 @@ public void testReaderWaitsForSplits() try { started.countDown(); List batch = getSplits(hiveSplitSource, 1); - assertEquals(batch.size(), 1); + assertThat(batch.size()).isEqualTo(1); splits.set(batch.get(0)); } catch (Throwable e) { @@ -220,18 +219,18 @@ public void testReaderWaitsForSplits() try { // wait for the thread to be started - assertTrue(started.await(1, TimeUnit.SECONDS)); + assertThat(started.await(1, TimeUnit.SECONDS)).isTrue(); // sleep for a bit, and assure the thread is blocked TimeUnit.MILLISECONDS.sleep(200); - assertTrue(!splits.isDone()); + assertThat(!splits.isDone()).isTrue(); // add a split hiveSplitSource.addToQueue(new TestSplit(33)); // wait for thread to get the split ConnectorSplit split = splits.get(800, TimeUnit.MILLISECONDS); - assertEquals(((HiveSplit) split).getSchema().get("id"), "33"); + assertThat(((HiveSplit) split).getSchema().get("id")).isEqualTo("33"); } finally { // make sure the thread exits @@ -260,14 +259,14 @@ public void testOutstandingSplitSize() int maxSplitCount = toIntExact(maxOutstandingSplitsSize.toBytes()) / testSplitSizeInBytes; for (int i = 0; i < maxSplitCount; i++) { hiveSplitSource.addToQueue(new TestSplit(i)); - assertEquals(hiveSplitSource.getBufferedInternalSplitCount(), i + 1); + assertThat(hiveSplitSource.getBufferedInternalSplitCount()).isEqualTo(i + 1); } - assertEquals(getSplits(hiveSplitSource, maxSplitCount).size(), maxSplitCount); + assertThat(getSplits(hiveSplitSource, maxSplitCount).size()).isEqualTo(maxSplitCount); for (int i = 0; i < maxSplitCount; i++) { hiveSplitSource.addToQueue(new TestSplit(i)); - assertEquals(hiveSplitSource.getBufferedInternalSplitCount(), i + 1); + assertThat(hiveSplitSource.getBufferedInternalSplitCount()).isEqualTo(i + 1); } assertTrinoExceptionThrownBy(() -> hiveSplitSource.addToQueue(new TestSplit(0))) .hasErrorCode(HIVE_EXCEEDED_SPLIT_BUFFERING_LIMIT) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveTableHandle.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveTableHandle.java index 42a1164a08fc..3bf14adf1ab9 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveTableHandle.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveTableHandle.java @@ -20,7 +20,7 @@ import java.util.Optional; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestHiveTableHandle { @@ -34,6 +34,6 @@ public void testRoundTrip() String json = codec.toJson(expected); HiveTableHandle actual = codec.fromJson(json); - assertEquals(actual.getSchemaTableName(), expected.getSchemaTableName()); + assertThat(actual.getSchemaTableName()).isEqualTo(expected.getSchemaTableName()); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveWriterFactory.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveWriterFactory.java index 17c9bf10c245..5c8c20e6a64b 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveWriterFactory.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveWriterFactory.java @@ -24,7 +24,6 @@ import static io.trino.plugin.hive.HiveWriterFactory.setSchemeToFileIfAbsent; import static org.apache.hadoop.hive.ql.exec.Utilities.getBucketIdFromFile; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestHiveWriterFactory { @@ -33,11 +32,11 @@ public void testComputeBucketedFileName() { String name = computeNonTransactionalBucketedFilename("20180102_030405_00641_x1y2z", 1234); assertThat(name).matches("001234_0_.*_20180102_030405_00641_x1y2z"); - assertEquals(getBucketIdFromFile(name), 1234); + assertThat(getBucketIdFromFile(name)).isEqualTo(1234); name = computeTransactionalBucketedFilename(1234); - assertEquals(name, "001234_0"); - assertEquals(getBucketIdFromFile(name), 1234); + assertThat(name).isEqualTo("001234_0"); + assertThat(getBucketIdFromFile(name)).isEqualTo(1234); } @Test diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestNodeLocalDynamicSplitPruning.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestNodeLocalDynamicSplitPruning.java index 805b1217cd16..d67a1fa1b037 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestNodeLocalDynamicSplitPruning.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestNodeLocalDynamicSplitPruning.java @@ -54,7 +54,7 @@ import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.util.concurrent.CompletableFuture.completedFuture; import static org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.FILE_INPUT_FORMAT; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; class TestNodeLocalDynamicSplitPruning { @@ -86,11 +86,11 @@ void testDynamicBucketPruning() HiveConfig config = new HiveConfig(); HiveTransactionHandle transaction = new HiveTransactionHandle(false); try (ConnectorPageSource emptyPageSource = createTestingPageSource(transaction, config, getDynamicFilter(getTupleDomainForBucketSplitPruning()))) { - assertEquals(emptyPageSource.getClass(), EmptyPageSource.class); + assertThat(emptyPageSource.getClass()).isEqualTo(EmptyPageSource.class); } try (ConnectorPageSource nonEmptyPageSource = createTestingPageSource(transaction, config, getDynamicFilter(getNonSelectiveBucketTupleDomain()))) { - assertEquals(nonEmptyPageSource.getClass(), HivePageSource.class); + assertThat(nonEmptyPageSource.getClass()).isEqualTo(HivePageSource.class); } } @@ -102,11 +102,11 @@ void testDynamicPartitionPruning() HiveTransactionHandle transaction = new HiveTransactionHandle(false); try (ConnectorPageSource emptyPageSource = createTestingPageSource(transaction, config, getDynamicFilter(getTupleDomainForPartitionSplitPruning()))) { - assertEquals(emptyPageSource.getClass(), EmptyPageSource.class); + assertThat(emptyPageSource.getClass()).isEqualTo(EmptyPageSource.class); } try (ConnectorPageSource nonEmptyPageSource = createTestingPageSource(transaction, config, getDynamicFilter(getNonSelectivePartitionTupleDomain()))) { - assertEquals(nonEmptyPageSource.getClass(), HivePageSource.class); + assertThat(nonEmptyPageSource.getClass()).isEqualTo(HivePageSource.class); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOrcPageSourceMemoryTracking.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOrcPageSourceMemoryTracking.java index b9138e10f2e0..50cb6a652300 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOrcPageSourceMemoryTracking.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOrcPageSourceMemoryTracking.java @@ -130,14 +130,10 @@ import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaStringObjectInspector; import static org.apache.hadoop.mapreduce.lib.output.FileOutputFormat.COMPRESS_CODEC; import static org.apache.hadoop.mapreduce.lib.output.FileOutputFormat.COMPRESS_TYPE; +import static org.assertj.core.api.Assertions.assertThat; import static org.joda.time.DateTimeZone.UTC; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -205,15 +201,15 @@ private void testPageSource(boolean useCache) assertBetweenInclusive(pageSource.getMemoryUsage(), testPreparer.getFileSize(), testPreparer.getFileSize() + 200); } else { - assertEquals(pageSource.getMemoryUsage(), 0); + assertThat(pageSource.getMemoryUsage()).isEqualTo(0); } long memoryUsage = -1; int totalRows = 0; while (totalRows < 20000) { - assertFalse(pageSource.isFinished()); + assertThat(pageSource.isFinished()).isFalse(); Page page = pageSource.getNextPage(); - assertNotNull(page); + assertThat(page).isNotNull(); Block block = page.getBlock(1); if (memoryUsage == -1) { @@ -235,18 +231,18 @@ private void testPageSource(boolean useCache) } } else { - assertEquals(pageSource.getMemoryUsage(), memoryUsage); + assertThat(pageSource.getMemoryUsage()).isEqualTo(memoryUsage); createUnboundedVarcharType().getSlice(block, block.getPositionCount() - 1); // trigger loading for lazy block - assertEquals(pageSource.getMemoryUsage(), memoryUsage); + assertThat(pageSource.getMemoryUsage()).isEqualTo(memoryUsage); } totalRows += page.getPositionCount(); } memoryUsage = -1; while (totalRows < 40000) { - assertFalse(pageSource.isFinished()); + assertThat(pageSource.isFinished()).isFalse(); Page page = pageSource.getNextPage(); - assertNotNull(page); + assertThat(page).isNotNull(); Block block = page.getBlock(1); if (memoryUsage == -1) { @@ -268,18 +264,18 @@ private void testPageSource(boolean useCache) } } else { - assertEquals(pageSource.getMemoryUsage(), memoryUsage); + assertThat(pageSource.getMemoryUsage()).isEqualTo(memoryUsage); createUnboundedVarcharType().getSlice(block, block.getPositionCount() - 1); // trigger loading for lazy block - assertEquals(pageSource.getMemoryUsage(), memoryUsage); + assertThat(pageSource.getMemoryUsage()).isEqualTo(memoryUsage); } totalRows += page.getPositionCount(); } memoryUsage = -1; while (totalRows < NUM_ROWS) { - assertFalse(pageSource.isFinished()); + assertThat(pageSource.isFinished()).isFalse(); Page page = pageSource.getNextPage(); - assertNotNull(page); + assertThat(page).isNotNull(); Block block = page.getBlock(1); if (memoryUsage == -1) { @@ -301,22 +297,22 @@ private void testPageSource(boolean useCache) } } else { - assertEquals(pageSource.getMemoryUsage(), memoryUsage); + assertThat(pageSource.getMemoryUsage()).isEqualTo(memoryUsage); createUnboundedVarcharType().getSlice(block, block.getPositionCount() - 1); // trigger loading for lazy block - assertEquals(pageSource.getMemoryUsage(), memoryUsage); + assertThat(pageSource.getMemoryUsage()).isEqualTo(memoryUsage); } totalRows += page.getPositionCount(); } - assertFalse(pageSource.isFinished()); - assertNull(pageSource.getNextPage()); - assertTrue(pageSource.isFinished()); + assertThat(pageSource.isFinished()).isFalse(); + assertThat(pageSource.getNextPage()).isNull(); + assertThat(pageSource.isFinished()).isTrue(); if (useCache) { // file is fully cached assertBetweenInclusive(pageSource.getMemoryUsage(), testPreparer.getFileSize(), testPreparer.getFileSize() + 200); } else { - assertEquals(pageSource.getMemoryUsage(), 0); + assertThat(pageSource.getMemoryUsage()).isEqualTo(0); } pageSource.close(); } @@ -370,7 +366,7 @@ private void testMaxReadBytes(int rowCount) if (pageSource.isFinished()) { break; } - assertNotNull(page); + assertThat(page).isNotNull(); page = page.getLoadedPage(); positionCount += page.getPositionCount(); // assert upper bound is tight @@ -378,15 +374,15 @@ private void testMaxReadBytes(int rowCount) if (positionCount > MAX_BATCH_SIZE) { // either the block is bounded by maxReadBytes or we just load one single large block // an error margin MAX_BATCH_SIZE / step is needed given the block sizes are increasing - assertTrue(page.getSizeInBytes() < (long) maxReadBytes * (MAX_BATCH_SIZE / step) || 1 == page.getPositionCount()); + assertThat(page.getSizeInBytes() < (long) maxReadBytes * (MAX_BATCH_SIZE / step) || 1 == page.getPositionCount()).isTrue(); } } // verify the stats are correctly recorded Distribution distribution = stats.getMaxCombinedBytesPerRow().getAllTime(); - assertEquals((int) distribution.getCount(), 1); + assertThat((int) distribution.getCount()).isEqualTo(1); // the block is VariableWidthBlock that contains valueIsNull and offsets arrays as overhead - assertEquals((int) distribution.getMax(), Arrays.stream(dataColumns).mapToInt(GrowingTestColumn::getMaxSize).sum() + (Integer.BYTES + Byte.BYTES) * numColumns); + assertThat((int) distribution.getMax()).isEqualTo(Arrays.stream(dataColumns).mapToInt(GrowingTestColumn::getMaxSize).sum() + (Integer.BYTES + Byte.BYTES) * numColumns); pageSource.close(); } finally { @@ -403,61 +399,61 @@ public void testTableScanOperator() DriverContext driverContext = testPreparer.newDriverContext(); SourceOperator operator = testPreparer.newTableScanOperator(driverContext); - assertEquals(driverContext.getMemoryUsage(), 0); + assertThat(driverContext.getMemoryUsage()).isEqualTo(0); long memoryUsage = -1; int totalRows = 0; while (totalRows < 20000) { - assertFalse(operator.isFinished()); + assertThat(operator.isFinished()).isFalse(); Page page = operator.getOutput(); - assertNotNull(page); + assertThat(page).isNotNull(); page.getBlock(1); if (memoryUsage == -1) { memoryUsage = driverContext.getMemoryUsage(); assertBetweenInclusive(memoryUsage, 460000L, 469999L); } else { - assertEquals(driverContext.getMemoryUsage(), memoryUsage); + assertThat(driverContext.getMemoryUsage()).isEqualTo(memoryUsage); } totalRows += page.getPositionCount(); } memoryUsage = -1; while (totalRows < 40000) { - assertFalse(operator.isFinished()); + assertThat(operator.isFinished()).isFalse(); Page page = operator.getOutput(); - assertNotNull(page); + assertThat(page).isNotNull(); page.getBlock(1); if (memoryUsage == -1) { memoryUsage = driverContext.getMemoryUsage(); assertBetweenInclusive(memoryUsage, 460000L, 469999L); } else { - assertEquals(driverContext.getMemoryUsage(), memoryUsage); + assertThat(driverContext.getMemoryUsage()).isEqualTo(memoryUsage); } totalRows += page.getPositionCount(); } memoryUsage = -1; while (totalRows < NUM_ROWS) { - assertFalse(operator.isFinished()); + assertThat(operator.isFinished()).isFalse(); Page page = operator.getOutput(); - assertNotNull(page); + assertThat(page).isNotNull(); page.getBlock(1); if (memoryUsage == -1) { memoryUsage = driverContext.getMemoryUsage(); assertBetweenInclusive(memoryUsage, 360000L, 369999L); } else { - assertEquals(driverContext.getMemoryUsage(), memoryUsage); + assertThat(driverContext.getMemoryUsage()).isEqualTo(memoryUsage); } totalRows += page.getPositionCount(); } - assertFalse(operator.isFinished()); - assertNull(operator.getOutput()); - assertTrue(operator.isFinished()); - assertEquals(driverContext.getMemoryUsage(), 0); + assertThat(operator.isFinished()).isFalse(); + assertThat(operator.getOutput()).isNull(); + assertThat(operator.isFinished()).isTrue(); + assertThat(driverContext.getMemoryUsage()).isEqualTo(0); } @Test @@ -469,24 +465,26 @@ public void testScanFilterAndProjectOperator() DriverContext driverContext = testPreparer.newDriverContext(); SourceOperator operator = testPreparer.newScanFilterAndProjectOperator(driverContext); - assertEquals(driverContext.getMemoryUsage(), 0); + assertThat(driverContext.getMemoryUsage()).isEqualTo(0); int totalRows = 0; while (totalRows < NUM_ROWS) { - assertFalse(operator.isFinished()); + assertThat(operator.isFinished()).isFalse(); Page page = operator.getOutput(); - assertNotNull(page); + assertThat(page).isNotNull(); // memory usage varies depending on stripe alignment long memoryUsage = driverContext.getMemoryUsage(); - assertTrue(memoryUsage < 1000 || (memoryUsage > 150_000 && memoryUsage < 630_000), format("Memory usage (%s) outside of bounds", memoryUsage)); + assertThat(memoryUsage < 1000 || (memoryUsage > 150_000 && memoryUsage < 630_000)) + .describedAs(format("Memory usage (%s) outside of bounds", memoryUsage)) + .isTrue(); totalRows += page.getPositionCount(); } // done... in the current implementation finish is not set until output returns a null page - assertNull(operator.getOutput()); - assertTrue(operator.isFinished()); + assertThat(operator.getOutput()).isNull(); + assertThat(operator.isFinished()).isTrue(); assertBetweenInclusive(driverContext.getMemoryUsage(), 0L, 500L); } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOriginalFilesUtils.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOriginalFilesUtils.java index 8e10003d0e33..5d5f1eaa0791 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOriginalFilesUtils.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestOriginalFilesUtils.java @@ -25,7 +25,7 @@ import static io.trino.plugin.hive.AcidInfo.OriginalFileInfo; import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; import static io.trino.testing.TestingConnectorSession.SESSION; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestOriginalFilesUtils { @@ -49,7 +49,9 @@ public void testGetPrecedingRowCountSingleFile() SESSION.getIdentity(), new OrcReaderOptions(), new FileFormatDataSourceStats()); - assertEquals(rowCountResult, 0, "Original file should have 0 as the starting row count"); + assertThat(rowCountResult) + .describedAs("Original file should have 0 as the starting row count") + .isEqualTo(0); } @Test @@ -70,6 +72,8 @@ public void testGetPrecedingRowCount() new FileFormatDataSourceStats()); // Bucket-2 has original files: 000002_0, 000002_0_copy_1. Each file original file has 4 rows. // So, starting row ID of 000002_0_copy_2 = row count of original files in Bucket-2 before it in lexicographic order. - assertEquals(rowCountResult, 8, "Original file 000002_0_copy_2 should have 8 as the starting row count"); + assertThat(rowCountResult) + .describedAs("Original file 000002_0_copy_2 should have 8 as the starting row count") + .isEqualTo(8); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestPartitionOfflineException.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestPartitionOfflineException.java index e55c26560d12..58de1117edc5 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestPartitionOfflineException.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestPartitionOfflineException.java @@ -16,7 +16,7 @@ import io.trino.spi.connector.SchemaTableName; import org.junit.jupiter.api.Test; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestPartitionOfflineException { @@ -34,6 +34,6 @@ public void testMessage() private static void assertMessage(SchemaTableName tableName, String partitionName, boolean forPresto, String offlineMessage, String expectedMessage) { PartitionOfflineException tableOfflineException = new PartitionOfflineException(tableName, partitionName, forPresto, offlineMessage); - assertEquals(tableOfflineException.getMessage(), expectedMessage); + assertThat(tableOfflineException.getMessage()).isEqualTo(expectedMessage); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestPartitionUpdate.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestPartitionUpdate.java index 9fd6aaf0bba1..5081f91f326b 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestPartitionUpdate.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestPartitionUpdate.java @@ -20,7 +20,7 @@ import org.junit.jupiter.api.Test; import static io.airlift.json.JsonCodec.jsonCodec; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestPartitionUpdate { @@ -41,13 +41,13 @@ public void testRoundTrip() PartitionUpdate actual = CODEC.fromJson(CODEC.toJson(expected)); - assertEquals(actual.getName(), "test"); - assertEquals(actual.getUpdateMode(), UpdateMode.APPEND); - assertEquals(actual.getWritePath(), Location.of("/writePath")); - assertEquals(actual.getTargetPath(), Location.of("/targetPath")); - assertEquals(actual.getFileNames(), ImmutableList.of("file1", "file3")); - assertEquals(actual.getRowCount(), 123); - assertEquals(actual.getInMemoryDataSizeInBytes(), 456); - assertEquals(actual.getOnDiskDataSizeInBytes(), 789); + assertThat(actual.getName()).isEqualTo("test"); + assertThat(actual.getUpdateMode()).isEqualTo(UpdateMode.APPEND); + assertThat(actual.getWritePath()).isEqualTo(Location.of("/writePath")); + assertThat(actual.getTargetPath()).isEqualTo(Location.of("/targetPath")); + assertThat(actual.getFileNames()).isEqualTo(ImmutableList.of("file1", "file3")); + assertThat(actual.getRowCount()).isEqualTo(123); + assertThat(actual.getInMemoryDataSizeInBytes()).isEqualTo(456); + assertThat(actual.getOnDiskDataSizeInBytes()).isEqualTo(789); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestReaderColumns.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestReaderColumns.java index 2422cbc92a95..c3863d4f557e 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestReaderColumns.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestReaderColumns.java @@ -31,8 +31,7 @@ import static io.trino.plugin.hive.TestHiveReaderProjectionsUtil.createProjectedColumnHandle; import static io.trino.plugin.hive.TestHiveReaderProjectionsUtil.createTestFullColumns; import static io.trino.spi.type.BigintType.BIGINT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestReaderColumns { @@ -60,10 +59,14 @@ public void testNoProjections() Optional mapping; mapping = projectBaseColumns(columns); - assertTrue(mapping.isEmpty(), "Full columns should not require any adaptation"); + assertThat(mapping.isEmpty()) + .describedAs("Full columns should not require any adaptation") + .isTrue(); mapping = projectSufficientColumns(columns); - assertTrue(mapping.isEmpty(), "Full columns should not require any adaptation"); + assertThat(mapping.isEmpty()) + .describedAs("Full columns should not require any adaptation") + .isTrue(); } @Test @@ -77,7 +80,9 @@ public void testBaseColumnsProjection() createProjectedColumnHandle(TEST_FULL_COLUMNS.get("col_struct_of_non_primitives"), ImmutableList.of(0))); Optional mapping = projectBaseColumns(columns); - assertTrue(mapping.isPresent(), "Full columns should be created for corresponding projected columns"); + assertThat(mapping.isPresent()) + .describedAs("Full columns should be created for corresponding projected columns") + .isTrue(); List readerColumns = mapping.get().get().stream() .map(HiveColumnHandle.class::cast) @@ -87,8 +92,8 @@ public void testBaseColumnsProjection() HiveColumnHandle column = columns.get(i); int readerIndex = mapping.get().getPositionForColumnAt(i); HiveColumnHandle readerColumn = (HiveColumnHandle) mapping.get().getForColumnAt(i); - assertEquals(column.getBaseColumn(), readerColumn); - assertEquals(readerColumns.get(readerIndex), readerColumn); + assertThat(column.getBaseColumn()).isEqualTo(readerColumn); + assertThat(readerColumns.get(readerIndex)).isEqualTo(readerColumn); } } @@ -103,26 +108,28 @@ public void testProjectSufficientColumns() createProjectedColumnHandle(TEST_FULL_COLUMNS.get("col_struct_of_non_primitives"), ImmutableList.of(0))); Optional readerProjections = projectSufficientColumns(columns); - assertTrue(readerProjections.isPresent(), "expected readerProjections to be present"); - - assertEquals(readerProjections.get().getForColumnAt(0), columns.get(0)); - assertEquals(readerProjections.get().getForColumnAt(1), columns.get(1)); - assertEquals(readerProjections.get().getForColumnAt(2), columns.get(2)); - assertEquals(readerProjections.get().getForColumnAt(3), columns.get(4)); - assertEquals(readerProjections.get().getForColumnAt(4), columns.get(4)); - - assertEquals(readerProjections.get().getPositionForColumnAt(0), 0); - assertEquals(readerProjections.get().getPositionForColumnAt(1), 1); - assertEquals(readerProjections.get().getPositionForColumnAt(2), 2); - assertEquals(readerProjections.get().getPositionForColumnAt(3), 3); - assertEquals(readerProjections.get().getPositionForColumnAt(4), 3); + assertThat(readerProjections.isPresent()) + .describedAs("expected readerProjections to be present") + .isTrue(); + + assertThat(readerProjections.get().getForColumnAt(0)).isEqualTo(columns.get(0)); + assertThat(readerProjections.get().getForColumnAt(1)).isEqualTo(columns.get(1)); + assertThat(readerProjections.get().getForColumnAt(2)).isEqualTo(columns.get(2)); + assertThat(readerProjections.get().getForColumnAt(3)).isEqualTo(columns.get(4)); + assertThat(readerProjections.get().getForColumnAt(4)).isEqualTo(columns.get(4)); + + assertThat(readerProjections.get().getPositionForColumnAt(0)).isEqualTo(0); + assertThat(readerProjections.get().getPositionForColumnAt(1)).isEqualTo(1); + assertThat(readerProjections.get().getPositionForColumnAt(2)).isEqualTo(2); + assertThat(readerProjections.get().getPositionForColumnAt(3)).isEqualTo(3); + assertThat(readerProjections.get().getPositionForColumnAt(4)).isEqualTo(3); List readerColumns = readerProjections.get().get().stream() .map(HiveColumnHandle.class::cast) .collect(toImmutableList()); - assertEquals(readerColumns.get(0), columns.get(0)); - assertEquals(readerColumns.get(1), columns.get(1)); - assertEquals(readerColumns.get(2), columns.get(2)); - assertEquals(readerColumns.get(3), columns.get(4)); + assertThat(readerColumns.get(0)).isEqualTo(columns.get(0)); + assertThat(readerColumns.get(1)).isEqualTo(columns.get(1)); + assertThat(readerColumns.get(2)).isEqualTo(columns.get(2)); + assertThat(readerColumns.get(3)).isEqualTo(columns.get(4)); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestReaderProjectionsAdapter.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestReaderProjectionsAdapter.java index 6a0b33e2a4fe..e4bd2959a706 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestReaderProjectionsAdapter.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestReaderProjectionsAdapter.java @@ -43,8 +43,6 @@ import static io.trino.spi.type.BigintType.BIGINT; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestReaderProjectionsAdapter { @@ -103,22 +101,22 @@ public void testLazyDereferenceProjectionLoading() // Assertion for "col" Block lazyBlockLevel1 = inputPage.getBlock(0); - assertTrue(lazyBlockLevel1 instanceof LazyBlock); - assertFalse(lazyBlockLevel1.isLoaded()); + assertThat(lazyBlockLevel1 instanceof LazyBlock).isTrue(); + assertThat(lazyBlockLevel1.isLoaded()).isFalse(); RowBlock rowBlockLevel1 = ((RowBlock) (((LazyBlock) lazyBlockLevel1).getBlock())); - assertFalse(rowBlockLevel1.isLoaded()); + assertThat(rowBlockLevel1.isLoaded()).isFalse(); // Assertion for "col.f_row_0" and col.f_bigint_0" - assertFalse(rowBlockLevel1.getFieldBlock(0).isLoaded()); - assertFalse(rowBlockLevel1.getFieldBlock(1).isLoaded()); + assertThat(rowBlockLevel1.getFieldBlock(0).isLoaded()).isFalse(); + assertThat(rowBlockLevel1.getFieldBlock(1).isLoaded()).isFalse(); Block lazyBlockLevel2 = rowBlockLevel1.getFieldBlock(0); - assertTrue(lazyBlockLevel2 instanceof LazyBlock); + assertThat(lazyBlockLevel2 instanceof LazyBlock).isTrue(); RowBlock rowBlockLevel2 = ((RowBlock) (((LazyBlock) lazyBlockLevel2).getBlock())); - assertFalse(rowBlockLevel2.isLoaded()); + assertThat(rowBlockLevel2.isLoaded()).isFalse(); // Assertion for "col.f_row_0.f_bigint_0" and "col.f_row_0.f_bigint_1" - assertTrue(rowBlockLevel2.getFieldBlock(0).isLoaded()); - assertFalse(rowBlockLevel2.getFieldBlock(1).isLoaded()); + assertThat(rowBlockLevel2.getFieldBlock(0).isLoaded()).isTrue(); + assertThat(rowBlockLevel2.getFieldBlock(1).isLoaded()).isFalse(); } private void verifyPageAdaptation(ReaderProjectionsAdapter adapter, List> inputPageData) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestTableOfflineException.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestTableOfflineException.java index 7eea0e2ad384..78c0146d72c3 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestTableOfflineException.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestTableOfflineException.java @@ -16,7 +16,7 @@ import io.trino.spi.connector.SchemaTableName; import org.junit.jupiter.api.Test; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestTableOfflineException { @@ -34,6 +34,6 @@ public void testMessage() private static void assertMessage(SchemaTableName tableName, boolean forPresto, String offlineMessage, String expectedMessage) { TableOfflineException tableOfflineException = new TableOfflineException(tableName, forPresto, offlineMessage); - assertEquals(tableOfflineException.getMessage(), expectedMessage); + assertThat(tableOfflineException.getMessage()).isEqualTo(expectedMessage); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestTableToPartitionMapping.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestTableToPartitionMapping.java index edf2ed2be7c0..5a6f169e7332 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestTableToPartitionMapping.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestTableToPartitionMapping.java @@ -17,32 +17,31 @@ import org.junit.jupiter.api.Test; import static io.trino.plugin.hive.TableToPartitionMapping.isIdentityMapping; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestTableToPartitionMapping { @Test public void testIsOneToOneMapping() { - assertTrue(isIdentityMapping(ImmutableMap.builder() + assertThat(isIdentityMapping(ImmutableMap.builder() .put(0, 0) .put(1, 1) .put(2, 2) .put(3, 3) - .buildOrThrow())); - assertFalse(isIdentityMapping(ImmutableMap.builder() + .buildOrThrow())).isTrue(); + assertThat(isIdentityMapping(ImmutableMap.builder() .put(0, 0) .put(1, 1) .put(2, 2) .put(3, 3) .put(5, 5) - .buildOrThrow())); - assertFalse(isIdentityMapping(ImmutableMap.builder() + .buildOrThrow())).isFalse(); + assertThat(isIdentityMapping(ImmutableMap.builder() .put(0, 0) .put(1, 1) .put(2, 2) .put(4, 5) - .buildOrThrow())); + .buildOrThrow())).isFalse(); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/TestCachingDirectoryListerRecursiveFilesOnly.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/TestCachingDirectoryListerRecursiveFilesOnly.java index 5cfb919a2e0a..87a982032d13 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/TestCachingDirectoryListerRecursiveFilesOnly.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/TestCachingDirectoryListerRecursiveFilesOnly.java @@ -31,9 +31,8 @@ import static io.trino.plugin.hive.HiveQueryRunner.TPCH_SCHEMA; import static io.trino.plugin.hive.metastore.PrincipalPrivileges.NO_PRIVILEGES; import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; // some tests may invalidate the whole cache affecting therefore other concurrent tests @Execution(SAME_THREAD) @@ -83,17 +82,17 @@ public void testRecursiveDirectories() assertQuery("SELECT sum(clicks) FROM recursive_directories", "VALUES (11000)"); String tableLocation = getTableLocation(TPCH_SCHEMA, "recursive_directories"); - assertTrue(isCached(tableLocation)); + assertThat(isCached(tableLocation)).isTrue(); // Insert should invalidate cache, even at the root directory path assertUpdate("INSERT INTO recursive_directories VALUES (1000)", 1); - assertFalse(isCached(tableLocation)); + assertThat(isCached(tableLocation)).isFalse(); // Results should include the new insert which is at the table location root for the unpartitioned table assertQuery("SELECT sum(clicks) FROM recursive_directories", "VALUES (12000)"); assertUpdate("DROP TABLE recursive_directories"); - assertFalse(isCached(tableLocation)); + assertThat(isCached(tableLocation)).isFalse(); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/TestHiveFileIterator.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/TestHiveFileIterator.java index d160c843b8c6..d90a2d78bce1 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/TestHiveFileIterator.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/TestHiveFileIterator.java @@ -19,47 +19,46 @@ import static io.trino.plugin.hive.fs.HiveFileIterator.containsHiddenPathPartAfterIndex; import static io.trino.plugin.hive.fs.HiveFileIterator.isHiddenFileOrDirectory; import static io.trino.plugin.hive.fs.HiveFileIterator.isHiddenOrWithinHiddenParentDirectory; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestHiveFileIterator { @Test public void testRelativeHiddenPathDetection() { - assertTrue(isHiddenOrWithinHiddenParentDirectory(Location.of("file:///root-path/.hidden/child"), Location.of("file:///root-path"))); - assertTrue(isHiddenOrWithinHiddenParentDirectory(Location.of("file:///root-path/_hidden.txt"), Location.of("file:///root-path"))); + assertThat(isHiddenOrWithinHiddenParentDirectory(Location.of("file:///root-path/.hidden/child"), Location.of("file:///root-path"))).isTrue(); + assertThat(isHiddenOrWithinHiddenParentDirectory(Location.of("file:///root-path/_hidden.txt"), Location.of("file:///root-path"))).isTrue(); // root path with trailing slash - assertTrue(isHiddenOrWithinHiddenParentDirectory(Location.of("file:///root-path/.hidden/child"), Location.of("file:///root-path/"))); - assertTrue(isHiddenOrWithinHiddenParentDirectory(Location.of("file:///root-path/_hidden.txt"), Location.of("file:///root-path/"))); + assertThat(isHiddenOrWithinHiddenParentDirectory(Location.of("file:///root-path/.hidden/child"), Location.of("file:///root-path/"))).isTrue(); + assertThat(isHiddenOrWithinHiddenParentDirectory(Location.of("file:///root-path/_hidden.txt"), Location.of("file:///root-path/"))).isTrue(); // root path containing .hidden - assertFalse(isHiddenOrWithinHiddenParentDirectory(Location.of("file:///root/.hidden/listing-root/file.txt"), Location.of("file:///root/.hidden/listing-root"))); + assertThat(isHiddenOrWithinHiddenParentDirectory(Location.of("file:///root/.hidden/listing-root/file.txt"), Location.of("file:///root/.hidden/listing-root"))).isFalse(); // root path ending with an underscore - assertFalse(isHiddenOrWithinHiddenParentDirectory(Location.of("file:///root/hidden-ending_/file.txt"), Location.of("file:///root/hidden-ending_"))); + assertThat(isHiddenOrWithinHiddenParentDirectory(Location.of("file:///root/hidden-ending_/file.txt"), Location.of("file:///root/hidden-ending_"))).isFalse(); // root path containing "arbitrary" characters - assertFalse(isHiddenOrWithinHiddenParentDirectory(Location.of("file:///root/With spaces and | pipes/.hidden/file.txt"), Location.of("file:///root/With spaces and | pipes/.hidden"))); + assertThat(isHiddenOrWithinHiddenParentDirectory(Location.of("file:///root/With spaces and | pipes/.hidden/file.txt"), Location.of("file:///root/With spaces and | pipes/.hidden"))).isFalse(); } @Test public void testHiddenFileNameDetection() { - assertFalse(isHiddenFileOrDirectory(Location.of("file:///parent/.hidden/ignore-parent-directories.txt"))); - assertTrue(isHiddenFileOrDirectory(Location.of("file:///parent/visible/_hidden-file.txt"))); + assertThat(isHiddenFileOrDirectory(Location.of("file:///parent/.hidden/ignore-parent-directories.txt"))).isFalse(); + assertThat(isHiddenFileOrDirectory(Location.of("file:///parent/visible/_hidden-file.txt"))).isTrue(); } @Test public void testHiddenDetectionEdgeCases() { - assertTrue(containsHiddenPathPartAfterIndex("/.leading-slash-hidden/file.txt", 0)); - assertTrue(containsHiddenPathPartAfterIndex("/.leading-slash-hidden-directory/", 0)); - assertTrue(containsHiddenPathPartAfterIndex("_root-hidden/file.txt", 0)); - assertTrue(containsHiddenPathPartAfterIndex("_root-hidden/directory/", 0)); - assertFalse(containsHiddenPathPartAfterIndex("root//multi-slash/", 0)); - assertTrue(containsHiddenPathPartAfterIndex("root/child/.slash-hidden/file.txt", 0)); - assertTrue(containsHiddenPathPartAfterIndex("root/child/.slash-hidden/parent/file.txt", 0)); + assertThat(containsHiddenPathPartAfterIndex("/.leading-slash-hidden/file.txt", 0)).isTrue(); + assertThat(containsHiddenPathPartAfterIndex("/.leading-slash-hidden-directory/", 0)).isTrue(); + assertThat(containsHiddenPathPartAfterIndex("_root-hidden/file.txt", 0)).isTrue(); + assertThat(containsHiddenPathPartAfterIndex("_root-hidden/directory/", 0)).isTrue(); + assertThat(containsHiddenPathPartAfterIndex("root//multi-slash/", 0)).isFalse(); + assertThat(containsHiddenPathPartAfterIndex("root/child/.slash-hidden/file.txt", 0)).isTrue(); + assertThat(containsHiddenPathPartAfterIndex("root/child/.slash-hidden/parent/file.txt", 0)).isTrue(); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestMetastoreUtil.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestMetastoreUtil.java index 07cc8fd8b7f5..05cdc7863dda 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestMetastoreUtil.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestMetastoreUtil.java @@ -50,7 +50,6 @@ import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.spi.type.VarcharType.VARCHAR; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestMetastoreUtil { @@ -167,7 +166,7 @@ public void testTableRoundTrip() { Table table = ThriftMetastoreUtil.fromMetastoreApiTable(TEST_TABLE, TEST_SCHEMA); io.trino.hive.thrift.metastore.Table metastoreApiTable = ThriftMetastoreUtil.toMetastoreApiTable(table, NO_PRIVILEGES); - assertEquals(metastoreApiTable, TEST_TABLE); + assertThat(metastoreApiTable).isEqualTo(TEST_TABLE); } @Test @@ -175,21 +174,21 @@ public void testPartitionRoundTrip() { Partition partition = ThriftMetastoreUtil.fromMetastoreApiPartition(TEST_PARTITION); io.trino.hive.thrift.metastore.Partition metastoreApiPartition = ThriftMetastoreUtil.toMetastoreApiPartition(partition); - assertEquals(metastoreApiPartition, TEST_PARTITION); + assertThat(metastoreApiPartition).isEqualTo(TEST_PARTITION); } @Test public void testHiveSchemaTable() { Map actual = MetastoreUtil.getHiveSchema(ThriftMetastoreUtil.fromMetastoreApiTable(TEST_TABLE_WITH_UNSUPPORTED_FIELDS, TEST_SCHEMA)); - assertEquals(actual, TEST_TABLE_METADATA); + assertThat(actual).isEqualTo(TEST_TABLE_METADATA); } @Test public void testHiveSchemaPartition() { Map actual = MetastoreUtil.getHiveSchema(ThriftMetastoreUtil.fromMetastoreApiPartition(TEST_PARTITION_WITH_UNSUPPORTED_FIELDS), ThriftMetastoreUtil.fromMetastoreApiTable(TEST_TABLE_WITH_UNSUPPORTED_FIELDS, TEST_SCHEMA)); - assertEquals(actual, TEST_TABLE_METADATA); + assertThat(actual).isEqualTo(TEST_TABLE_METADATA); } @Test @@ -199,10 +198,10 @@ public void testHiveSchemaCaseInsensitive() .map(fieldSchema -> new FieldSchema(fieldSchema.getName(), fieldSchema.getType().toUpperCase(Locale.ENGLISH), fieldSchema.getComment())) .toList(); Map actualTable = MetastoreUtil.getHiveSchema(ThriftMetastoreUtil.fromMetastoreApiTable(TEST_TABLE_WITH_UNSUPPORTED_FIELDS, testSchema)); - assertEquals(actualTable, TEST_TABLE_METADATA); + assertThat(actualTable).isEqualTo(TEST_TABLE_METADATA); Map actualPartition = MetastoreUtil.getHiveSchema(ThriftMetastoreUtil.fromMetastoreApiPartition(TEST_PARTITION_WITH_UNSUPPORTED_FIELDS), ThriftMetastoreUtil.fromMetastoreApiTable(TEST_TABLE_WITH_UNSUPPORTED_FIELDS, testSchema)); - assertEquals(actualPartition, TEST_TABLE_METADATA); + assertThat(actualPartition).isEqualTo(TEST_TABLE_METADATA); } @Test diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestPrincipalPrivileges.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestPrincipalPrivileges.java index 4ffacdc6efe1..24e3193642ad 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestPrincipalPrivileges.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestPrincipalPrivileges.java @@ -18,8 +18,7 @@ import org.junit.jupiter.api.Test; import static io.trino.plugin.hive.metastore.HivePrivilegeInfo.HivePrivilege.SELECT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; +import static org.assertj.core.api.Assertions.assertThat; public class TestPrincipalPrivileges { @@ -31,9 +30,9 @@ public void testGetTablePrincipalPrivileges() hivePrivilegeInfo(PrincipalType.USER, "user002"), hivePrivilegeInfo(PrincipalType.ROLE, "role001"))); - assertNotNull(principalPrivileges); - assertEquals(principalPrivileges.getUserPrivileges().size(), 2); - assertEquals(principalPrivileges.getRolePrivileges().size(), 1); + assertThat(principalPrivileges).isNotNull(); + assertThat(principalPrivileges.getUserPrivileges().size()).isEqualTo(2); + assertThat(principalPrivileges.getRolePrivileges().size()).isEqualTo(1); } private static HivePrivilegeInfo hivePrivilegeInfo(PrincipalType type, String key) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestSemiTransactionalHiveMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestSemiTransactionalHiveMetastore.java index 765619228c86..9cdd79aff461 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestSemiTransactionalHiveMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestSemiTransactionalHiveMetastore.java @@ -42,7 +42,7 @@ import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.util.concurrent.Executors.newFixedThreadPool; import static java.util.concurrent.Executors.newScheduledThreadPool; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; // countDownLatch field is shared between tests public class TestSemiTransactionalHiveMetastore @@ -182,7 +182,7 @@ private void assertCountDownLatch() { try { countDownLatch.countDown(); - assertTrue(countDownLatch.await(10, TimeUnit.SECONDS)); //all other threads launched should count down within 10 seconds + assertThat(countDownLatch.await(10, TimeUnit.SECONDS)).isTrue(); //all other threads launched should count down within 10 seconds } catch (InterruptedException ie) { Thread.currentThread().interrupt(); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestStorage.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestStorage.java index e5d2446d6721..ed78915ebbde 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestStorage.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestStorage.java @@ -17,7 +17,7 @@ import org.junit.jupiter.api.Test; import static io.airlift.json.JsonCodec.jsonCodec; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestStorage { @@ -31,6 +31,6 @@ public void testRoundTrip() .setLocation("/test") .build(); - assertEquals(CODEC.fromJson(CODEC.toJson(storage)), storage); + assertThat(CODEC.fromJson(CODEC.toJson(storage))).isEqualTo(storage); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastore.java index 117b8384dcfc..5a779e401ff0 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastore.java @@ -119,10 +119,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(SAME_THREAD) @@ -214,126 +210,126 @@ public void testCachingWithOnlyPartitionsCacheEnabled() @Test public void testGetAllDatabases() { - assertEquals(mockClient.getAccessCount(), 0); - assertEquals(metastore.getAllDatabases(), ImmutableList.of(TEST_DATABASE)); - assertEquals(mockClient.getAccessCount(), 1); - assertEquals(metastore.getAllDatabases(), ImmutableList.of(TEST_DATABASE)); - assertEquals(mockClient.getAccessCount(), 1); - assertEquals(metastore.getDatabaseNamesStats().getRequestCount(), 2); - assertEquals(metastore.getDatabaseNamesStats().getHitRate(), 0.5); + assertThat(mockClient.getAccessCount()).isEqualTo(0); + assertThat(metastore.getAllDatabases()).isEqualTo(ImmutableList.of(TEST_DATABASE)); + assertThat(mockClient.getAccessCount()).isEqualTo(1); + assertThat(metastore.getAllDatabases()).isEqualTo(ImmutableList.of(TEST_DATABASE)); + assertThat(mockClient.getAccessCount()).isEqualTo(1); + assertThat(metastore.getDatabaseNamesStats().getRequestCount()).isEqualTo(2); + assertThat(metastore.getDatabaseNamesStats().getHitRate()).isEqualTo(0.5); metastore.flushCache(); - assertEquals(metastore.getAllDatabases(), ImmutableList.of(TEST_DATABASE)); - assertEquals(mockClient.getAccessCount(), 2); - assertEquals(metastore.getDatabaseNamesStats().getRequestCount(), 3); - assertEquals(metastore.getDatabaseNamesStats().getHitRate(), 1.0 / 3); + assertThat(metastore.getAllDatabases()).isEqualTo(ImmutableList.of(TEST_DATABASE)); + assertThat(mockClient.getAccessCount()).isEqualTo(2); + assertThat(metastore.getDatabaseNamesStats().getRequestCount()).isEqualTo(3); + assertThat(metastore.getDatabaseNamesStats().getHitRate()).isEqualTo(1.0 / 3); } @Test public void testGetAllTable() { - assertEquals(mockClient.getAccessCount(), 0); - assertEquals(metastore.getAllTables(TEST_DATABASE), ImmutableList.of(TEST_TABLE)); - assertEquals(mockClient.getAccessCount(), 1); - assertEquals(metastore.getAllTables(TEST_DATABASE), ImmutableList.of(TEST_TABLE)); - assertEquals(mockClient.getAccessCount(), 1); - assertEquals(metastore.getTableNamesStats().getRequestCount(), 2); - assertEquals(metastore.getTableNamesStats().getHitRate(), 0.5); + assertThat(mockClient.getAccessCount()).isEqualTo(0); + assertThat(metastore.getAllTables(TEST_DATABASE)).isEqualTo(ImmutableList.of(TEST_TABLE)); + assertThat(mockClient.getAccessCount()).isEqualTo(1); + assertThat(metastore.getAllTables(TEST_DATABASE)).isEqualTo(ImmutableList.of(TEST_TABLE)); + assertThat(mockClient.getAccessCount()).isEqualTo(1); + assertThat(metastore.getTableNamesStats().getRequestCount()).isEqualTo(2); + assertThat(metastore.getTableNamesStats().getHitRate()).isEqualTo(0.5); metastore.flushCache(); - assertEquals(metastore.getAllTables(TEST_DATABASE), ImmutableList.of(TEST_TABLE)); - assertEquals(mockClient.getAccessCount(), 2); - assertEquals(metastore.getTableNamesStats().getRequestCount(), 3); - assertEquals(metastore.getTableNamesStats().getHitRate(), 1.0 / 3); + assertThat(metastore.getAllTables(TEST_DATABASE)).isEqualTo(ImmutableList.of(TEST_TABLE)); + assertThat(mockClient.getAccessCount()).isEqualTo(2); + assertThat(metastore.getTableNamesStats().getRequestCount()).isEqualTo(3); + assertThat(metastore.getTableNamesStats().getHitRate()).isEqualTo(1.0 / 3); } @Test public void testBatchGetAllTable() { - assertEquals(mockClient.getAccessCount(), 0); - assertEquals(metastore.getAllTables(), Optional.of(ImmutableList.of(TEST_SCHEMA_TABLE))); - assertEquals(mockClient.getAccessCount(), 1); - assertEquals(metastore.getAllTables(), Optional.of(ImmutableList.of(TEST_SCHEMA_TABLE))); - assertEquals(mockClient.getAccessCount(), 1); - assertEquals(metastore.getAllTables(TEST_DATABASE), ImmutableList.of(TEST_TABLE)); - assertEquals(mockClient.getAccessCount(), 2); - assertEquals(metastore.getAllTableNamesStats().getRequestCount(), 2); - assertEquals(metastore.getAllTableNamesStats().getHitRate(), .5); + assertThat(mockClient.getAccessCount()).isEqualTo(0); + assertThat(metastore.getAllTables()).isEqualTo(Optional.of(ImmutableList.of(TEST_SCHEMA_TABLE))); + assertThat(mockClient.getAccessCount()).isEqualTo(1); + assertThat(metastore.getAllTables()).isEqualTo(Optional.of(ImmutableList.of(TEST_SCHEMA_TABLE))); + assertThat(mockClient.getAccessCount()).isEqualTo(1); + assertThat(metastore.getAllTables(TEST_DATABASE)).isEqualTo(ImmutableList.of(TEST_TABLE)); + assertThat(mockClient.getAccessCount()).isEqualTo(2); + assertThat(metastore.getAllTableNamesStats().getRequestCount()).isEqualTo(2); + assertThat(metastore.getAllTableNamesStats().getHitRate()).isEqualTo(.5); metastore.flushCache(); - assertEquals(metastore.getAllTables(), Optional.of(ImmutableList.of(TEST_SCHEMA_TABLE))); - assertEquals(mockClient.getAccessCount(), 3); - assertEquals(metastore.getAllTableNamesStats().getRequestCount(), 3); - assertEquals(metastore.getAllTableNamesStats().getHitRate(), 1. / 3); + assertThat(metastore.getAllTables()).isEqualTo(Optional.of(ImmutableList.of(TEST_SCHEMA_TABLE))); + assertThat(mockClient.getAccessCount()).isEqualTo(3); + assertThat(metastore.getAllTableNamesStats().getRequestCount()).isEqualTo(3); + assertThat(metastore.getAllTableNamesStats().getHitRate()).isEqualTo(1. / 3); } @Test public void testInvalidDbGetAllTAbles() { - assertTrue(metastore.getAllTables(BAD_DATABASE).isEmpty()); + assertThat(metastore.getAllTables(BAD_DATABASE).isEmpty()).isTrue(); } @Test public void testGetTable() { - assertEquals(mockClient.getAccessCount(), 0); - assertNotNull(metastore.getTable(TEST_DATABASE, TEST_TABLE)); - assertEquals(mockClient.getAccessCount(), 1); - assertNotNull(metastore.getTable(TEST_DATABASE, TEST_TABLE)); - assertEquals(mockClient.getAccessCount(), 1); - assertEquals(metastore.getTableStats().getRequestCount(), 2); - assertEquals(metastore.getTableStats().getHitRate(), 0.5); + assertThat(mockClient.getAccessCount()).isEqualTo(0); + assertThat(metastore.getTable(TEST_DATABASE, TEST_TABLE)).isNotNull(); + assertThat(mockClient.getAccessCount()).isEqualTo(1); + assertThat(metastore.getTable(TEST_DATABASE, TEST_TABLE)).isNotNull(); + assertThat(mockClient.getAccessCount()).isEqualTo(1); + assertThat(metastore.getTableStats().getRequestCount()).isEqualTo(2); + assertThat(metastore.getTableStats().getHitRate()).isEqualTo(0.5); metastore.flushCache(); - assertNotNull(metastore.getTable(TEST_DATABASE, TEST_TABLE)); - assertEquals(mockClient.getAccessCount(), 2); - assertEquals(metastore.getTableStats().getRequestCount(), 3); - assertEquals(metastore.getTableStats().getHitRate(), 1.0 / 3); + assertThat(metastore.getTable(TEST_DATABASE, TEST_TABLE)).isNotNull(); + assertThat(mockClient.getAccessCount()).isEqualTo(2); + assertThat(metastore.getTableStats().getRequestCount()).isEqualTo(3); + assertThat(metastore.getTableStats().getHitRate()).isEqualTo(1.0 / 3); } @Test public void testSetTableAuthorization() { - assertEquals(mockClient.getAccessCount(), 0); - assertNotNull(metastore.getTable(TEST_DATABASE, TEST_TABLE)); - assertNotNull(metastore.getDatabase(TEST_DATABASE)); - assertEquals(mockClient.getAccessCount(), 2); + assertThat(mockClient.getAccessCount()).isEqualTo(0); + assertThat(metastore.getTable(TEST_DATABASE, TEST_TABLE)).isNotNull(); + assertThat(metastore.getDatabase(TEST_DATABASE)).isNotNull(); + assertThat(mockClient.getAccessCount()).isEqualTo(2); metastore.setTableOwner(TEST_DATABASE, TEST_TABLE, new HivePrincipal(USER, "ignore")); - assertEquals(mockClient.getAccessCount(), 3); - assertNotNull(metastore.getTable(TEST_DATABASE, TEST_TABLE)); - assertEquals(mockClient.getAccessCount(), 4); + assertThat(mockClient.getAccessCount()).isEqualTo(3); + assertThat(metastore.getTable(TEST_DATABASE, TEST_TABLE)).isNotNull(); + assertThat(mockClient.getAccessCount()).isEqualTo(4); // Assert that database cache has not been invalidated - assertNotNull(metastore.getDatabase(TEST_DATABASE)); - assertEquals(mockClient.getAccessCount(), 4); + assertThat(metastore.getDatabase(TEST_DATABASE)).isNotNull(); + assertThat(mockClient.getAccessCount()).isEqualTo(4); } @Test public void testInvalidDbGetTable() { - assertFalse(metastore.getTable(BAD_DATABASE, TEST_TABLE).isPresent()); + assertThat(metastore.getTable(BAD_DATABASE, TEST_TABLE).isPresent()).isFalse(); - assertEquals(stats.getGetTable().getThriftExceptions().getTotalCount(), 0); - assertEquals(stats.getGetTable().getTotalFailures().getTotalCount(), 0); + assertThat(stats.getGetTable().getThriftExceptions().getTotalCount()).isEqualTo(0); + assertThat(stats.getGetTable().getTotalFailures().getTotalCount()).isEqualTo(0); } @Test public void testGetPartitionNames() { ImmutableList expectedPartitions = ImmutableList.of(TEST_PARTITION1, TEST_PARTITION2, TEST_PARTITION3); - assertEquals(mockClient.getAccessCount(), 0); - assertEquals(metastore.getPartitionNamesByFilter(TEST_DATABASE, TEST_TABLE, PARTITION_COLUMN_NAMES, TupleDomain.all()).orElseThrow(), expectedPartitions); - assertEquals(mockClient.getAccessCount(), 1); - assertEquals(metastore.getPartitionNamesByFilter(TEST_DATABASE, TEST_TABLE, PARTITION_COLUMN_NAMES, TupleDomain.all()).orElseThrow(), expectedPartitions); - assertEquals(mockClient.getAccessCount(), 1); + assertThat(mockClient.getAccessCount()).isEqualTo(0); + assertThat(metastore.getPartitionNamesByFilter(TEST_DATABASE, TEST_TABLE, PARTITION_COLUMN_NAMES, TupleDomain.all()).orElseThrow()).isEqualTo(expectedPartitions); + assertThat(mockClient.getAccessCount()).isEqualTo(1); + assertThat(metastore.getPartitionNamesByFilter(TEST_DATABASE, TEST_TABLE, PARTITION_COLUMN_NAMES, TupleDomain.all()).orElseThrow()).isEqualTo(expectedPartitions); + assertThat(mockClient.getAccessCount()).isEqualTo(1); metastore.flushCache(); - assertEquals(metastore.getPartitionNamesByFilter(TEST_DATABASE, TEST_TABLE, PARTITION_COLUMN_NAMES, TupleDomain.all()).orElseThrow(), expectedPartitions); - assertEquals(mockClient.getAccessCount(), 2); + assertThat(metastore.getPartitionNamesByFilter(TEST_DATABASE, TEST_TABLE, PARTITION_COLUMN_NAMES, TupleDomain.all()).orElseThrow()).isEqualTo(expectedPartitions); + assertThat(mockClient.getAccessCount()).isEqualTo(2); } /** @@ -405,7 +401,7 @@ public void testGetPartitionThenGetPartitionsRacingWithInvalidation() @Test public void testInvalidGetPartitionNamesByFilterAll() { - assertTrue(metastore.getPartitionNamesByFilter(BAD_DATABASE, TEST_TABLE, PARTITION_COLUMN_NAMES, TupleDomain.all()).isEmpty()); + assertThat(metastore.getPartitionNamesByFilter(BAD_DATABASE, TEST_TABLE, PARTITION_COLUMN_NAMES, TupleDomain.all()).isEmpty()).isTrue(); } @Test @@ -413,20 +409,20 @@ public void testGetPartitionNamesByParts() { ImmutableList expectedPartitions = ImmutableList.of(TEST_PARTITION1, TEST_PARTITION2, TEST_PARTITION3); - assertEquals(mockClient.getAccessCount(), 0); - assertEquals(metastore.getPartitionNamesByFilter(TEST_DATABASE, TEST_TABLE, PARTITION_COLUMN_NAMES, TupleDomain.all()).orElseThrow(), expectedPartitions); - assertEquals(mockClient.getAccessCount(), 1); - assertEquals(metastore.getPartitionNamesByFilter(TEST_DATABASE, TEST_TABLE, PARTITION_COLUMN_NAMES, TupleDomain.all()).orElseThrow(), expectedPartitions); - assertEquals(mockClient.getAccessCount(), 1); - assertEquals(metastore.getPartitionFilterStats().getRequestCount(), 2); - assertEquals(metastore.getPartitionFilterStats().getHitRate(), 0.5); + assertThat(mockClient.getAccessCount()).isEqualTo(0); + assertThat(metastore.getPartitionNamesByFilter(TEST_DATABASE, TEST_TABLE, PARTITION_COLUMN_NAMES, TupleDomain.all()).orElseThrow()).isEqualTo(expectedPartitions); + assertThat(mockClient.getAccessCount()).isEqualTo(1); + assertThat(metastore.getPartitionNamesByFilter(TEST_DATABASE, TEST_TABLE, PARTITION_COLUMN_NAMES, TupleDomain.all()).orElseThrow()).isEqualTo(expectedPartitions); + assertThat(mockClient.getAccessCount()).isEqualTo(1); + assertThat(metastore.getPartitionFilterStats().getRequestCount()).isEqualTo(2); + assertThat(metastore.getPartitionFilterStats().getHitRate()).isEqualTo(0.5); metastore.flushCache(); - assertEquals(metastore.getPartitionNamesByFilter(TEST_DATABASE, TEST_TABLE, PARTITION_COLUMN_NAMES, TupleDomain.all()).orElseThrow(), expectedPartitions); - assertEquals(mockClient.getAccessCount(), 2); - assertEquals(metastore.getPartitionFilterStats().getRequestCount(), 3); - assertEquals(metastore.getPartitionFilterStats().getHitRate(), 1.0 / 3); + assertThat(metastore.getPartitionNamesByFilter(TEST_DATABASE, TEST_TABLE, PARTITION_COLUMN_NAMES, TupleDomain.all()).orElseThrow()).isEqualTo(expectedPartitions); + assertThat(mockClient.getAccessCount()).isEqualTo(2); + assertThat(metastore.getPartitionFilterStats().getRequestCount()).isEqualTo(3); + assertThat(metastore.getPartitionFilterStats().getHitRate()).isEqualTo(1.0 / 3); List partitionColumnNames = ImmutableList.of("date_key", "key"); HiveColumnHandle dateKeyColumn = createBaseColumn(partitionColumnNames.get(0), 0, HIVE_STRING, VARCHAR, PARTITION_KEY, Optional.empty()); @@ -451,108 +447,108 @@ public void testGetPartitionNamesByParts() .put(keyColumn, Domain.create(ValueSet.ofRanges(Range.range(VARCHAR, utf8Slice("val1"), true, utf8Slice("val2"), true)), false)) .buildOrThrow())); - assertEquals(stats.getGetPartitionNamesByParts().getTime().getAllTime().getCount(), 0.0); + assertThat(stats.getGetPartitionNamesByParts().getTime().getAllTime().getCount()).isEqualTo(0.0); metastore.getPartitionNamesByFilter(TEST_DATABASE, TEST_TABLE, partitionColumnNames, withNoFilter); - assertEquals(stats.getGetPartitionNamesByParts().getTime().getAllTime().getCount(), 0.0); + assertThat(stats.getGetPartitionNamesByParts().getTime().getAllTime().getCount()).isEqualTo(0.0); metastore.getPartitionNamesByFilter(TEST_DATABASE, TEST_TABLE, partitionColumnNames, withSingleValueFilter); - assertEquals(stats.getGetPartitionNamesByParts().getTime().getAllTime().getCount(), 1.0); + assertThat(stats.getGetPartitionNamesByParts().getTime().getAllTime().getCount()).isEqualTo(1.0); metastore.getPartitionNamesByFilter(TEST_DATABASE, TEST_TABLE, partitionColumnNames, withNoSingleValueFilter); - assertEquals(stats.getGetPartitionNamesByParts().getTime().getAllTime().getCount(), 2.0); + assertThat(stats.getGetPartitionNamesByParts().getTime().getAllTime().getCount()).isEqualTo(2.0); } @Test public void testInvalidGetPartitionNamesByParts() { - assertFalse(metastore.getPartitionNamesByFilter(BAD_DATABASE, TEST_TABLE, PARTITION_COLUMN_NAMES, TupleDomain.all()).isPresent()); + assertThat(metastore.getPartitionNamesByFilter(BAD_DATABASE, TEST_TABLE, PARTITION_COLUMN_NAMES, TupleDomain.all()).isPresent()).isFalse(); } @Test public void testGetPartitionsByNames() { - assertEquals(mockClient.getAccessCount(), 0); + assertThat(mockClient.getAccessCount()).isEqualTo(0); Table table = metastore.getTable(TEST_DATABASE, TEST_TABLE).orElseThrow(); - assertEquals(mockClient.getAccessCount(), 1); + assertThat(mockClient.getAccessCount()).isEqualTo(1); // Select half of the available partitions and load them into the cache - assertEquals(metastore.getPartitionsByNames(table, ImmutableList.of(TEST_PARTITION1)).size(), 1); - assertEquals(mockClient.getAccessCount(), 2); + assertThat(metastore.getPartitionsByNames(table, ImmutableList.of(TEST_PARTITION1)).size()).isEqualTo(1); + assertThat(mockClient.getAccessCount()).isEqualTo(2); // Now select all the partitions - assertEquals(metastore.getPartitionsByNames(table, ImmutableList.of(TEST_PARTITION1, TEST_PARTITION2)).size(), 2); + assertThat(metastore.getPartitionsByNames(table, ImmutableList.of(TEST_PARTITION1, TEST_PARTITION2)).size()).isEqualTo(2); // There should be one more access to fetch the remaining partition - assertEquals(mockClient.getAccessCount(), 3); + assertThat(mockClient.getAccessCount()).isEqualTo(3); // Now if we fetch any or both of them, they should not hit the client - assertEquals(metastore.getPartitionsByNames(table, ImmutableList.of(TEST_PARTITION1)).size(), 1); - assertEquals(metastore.getPartitionsByNames(table, ImmutableList.of(TEST_PARTITION2)).size(), 1); - assertEquals(metastore.getPartitionsByNames(table, ImmutableList.of(TEST_PARTITION1, TEST_PARTITION2)).size(), 2); - assertEquals(mockClient.getAccessCount(), 3); + assertThat(metastore.getPartitionsByNames(table, ImmutableList.of(TEST_PARTITION1)).size()).isEqualTo(1); + assertThat(metastore.getPartitionsByNames(table, ImmutableList.of(TEST_PARTITION2)).size()).isEqualTo(1); + assertThat(metastore.getPartitionsByNames(table, ImmutableList.of(TEST_PARTITION1, TEST_PARTITION2)).size()).isEqualTo(2); + assertThat(mockClient.getAccessCount()).isEqualTo(3); metastore.flushCache(); // Fetching both should only result in one batched access - assertEquals(metastore.getPartitionsByNames(table, ImmutableList.of(TEST_PARTITION1, TEST_PARTITION2)).size(), 2); - assertEquals(mockClient.getAccessCount(), 4); + assertThat(metastore.getPartitionsByNames(table, ImmutableList.of(TEST_PARTITION1, TEST_PARTITION2)).size()).isEqualTo(2); + assertThat(mockClient.getAccessCount()).isEqualTo(4); } @Test public void testListRoles() { - assertEquals(mockClient.getAccessCount(), 0); + assertThat(mockClient.getAccessCount()).isEqualTo(0); - assertEquals(metastore.listRoles(), TEST_ROLES); - assertEquals(mockClient.getAccessCount(), 1); + assertThat(metastore.listRoles()).containsExactlyElementsOf(TEST_ROLES); + assertThat(mockClient.getAccessCount()).isEqualTo(1); - assertEquals(metastore.listRoles(), TEST_ROLES); - assertEquals(mockClient.getAccessCount(), 1); + assertThat(metastore.listRoles()).containsExactlyElementsOf(TEST_ROLES); + assertThat(mockClient.getAccessCount()).isEqualTo(1); - assertEquals(metastore.getRolesStats().getRequestCount(), 2); - assertEquals(metastore.getRolesStats().getHitRate(), 0.5); + assertThat(metastore.getRolesStats().getRequestCount()).isEqualTo(2); + assertThat(metastore.getRolesStats().getHitRate()).isEqualTo(0.5); metastore.flushCache(); - assertEquals(metastore.listRoles(), TEST_ROLES); - assertEquals(mockClient.getAccessCount(), 2); + assertThat(metastore.listRoles()).containsExactlyElementsOf(TEST_ROLES); + assertThat(mockClient.getAccessCount()).isEqualTo(2); metastore.createRole("role", "grantor"); - assertEquals(metastore.listRoles(), TEST_ROLES); - assertEquals(mockClient.getAccessCount(), 3); + assertThat(metastore.listRoles()).containsExactlyElementsOf(TEST_ROLES); + assertThat(mockClient.getAccessCount()).isEqualTo(3); metastore.dropRole("testrole"); - assertEquals(metastore.listRoles(), TEST_ROLES); - assertEquals(mockClient.getAccessCount(), 4); + assertThat(metastore.listRoles()).containsExactlyElementsOf(TEST_ROLES); + assertThat(mockClient.getAccessCount()).isEqualTo(4); - assertEquals(metastore.getRolesStats().getRequestCount(), 5); - assertEquals(metastore.getRolesStats().getHitRate(), 0.2); + assertThat(metastore.getRolesStats().getRequestCount()).isEqualTo(5); + assertThat(metastore.getRolesStats().getHitRate()).isEqualTo(0.2); } @Test public void testGetTableStatistics() { - assertEquals(mockClient.getAccessCount(), 0); + assertThat(mockClient.getAccessCount()).isEqualTo(0); Table table = metastore.getTable(TEST_DATABASE, TEST_TABLE).orElseThrow(); - assertEquals(mockClient.getAccessCount(), 1); + assertThat(mockClient.getAccessCount()).isEqualTo(1); - assertEquals(metastore.getTableStatistics(table), TEST_STATS); - assertEquals(mockClient.getAccessCount(), 2); + assertThat(metastore.getTableStatistics(table)).isEqualTo(TEST_STATS); + assertThat(mockClient.getAccessCount()).isEqualTo(2); - assertEquals(metastore.getTableStatistics(table), TEST_STATS); - assertEquals(mockClient.getAccessCount(), 2); + assertThat(metastore.getTableStatistics(table)).isEqualTo(TEST_STATS); + assertThat(mockClient.getAccessCount()).isEqualTo(2); - assertEquals(metastore.getTableStatisticsStats().getRequestCount(), 2); - assertEquals(metastore.getTableStatisticsStats().getHitRate(), 0.5); + assertThat(metastore.getTableStatisticsStats().getRequestCount()).isEqualTo(2); + assertThat(metastore.getTableStatisticsStats().getHitRate()).isEqualTo(0.5); - assertEquals(metastore.getTableStats().getRequestCount(), 1); - assertEquals(metastore.getTableStats().getHitRate(), 0.0); + assertThat(metastore.getTableStats().getRequestCount()).isEqualTo(1); + assertThat(metastore.getTableStats().getHitRate()).isEqualTo(0.0); // check empty column list does not trigger the call Table emptyColumnListTable = Table.builder(table).setDataColumns(ImmutableList.of()).build(); assertThat(metastore.getTableStatistics(emptyColumnListTable).getBasicStatistics()).isEqualTo(TEST_STATS.getBasicStatistics()); - assertEquals(metastore.getTableStatisticsStats().getRequestCount(), 3); - assertEquals(metastore.getTableStatisticsStats().getHitRate(), 2.0 / 3); + assertThat(metastore.getTableStatisticsStats().getRequestCount()).isEqualTo(3); + assertThat(metastore.getTableStatisticsStats().getHitRate()).isEqualTo(2.0 / 3); mockClient.mockColumnStats(TEST_DATABASE, TEST_TABLE, ImmutableMap.of( "col1", ColumnStatisticsData.longStats(new LongColumnStatsData().setNumNulls(1)), @@ -570,12 +566,12 @@ public void testGetTableStatistics() .containsEntry("col3", intColumnStats(3)); metastore.getTableStatistics(table); // ensure cached - assertEquals(mockClient.getAccessCount(), 5); + assertThat(mockClient.getAccessCount()).isEqualTo(5); ColumnStatisticsData newStats = new ColumnStatisticsData(); newStats.setLongStats(new LongColumnStatsData(327843, 4324)); mockClient.mockColumnStats(TEST_DATABASE, TEST_TABLE, ImmutableMap.of(TEST_COLUMN, newStats)); metastore.invalidateTable(TEST_DATABASE, TEST_TABLE); - assertEquals(metastore.getTableStatistics(table), PartitionStatistics.builder() + assertThat(metastore.getTableStatistics(table)).isEqualTo(PartitionStatistics.builder() .setBasicStatistics(TEST_STATS.getBasicStatistics()) .setColumnStatistics(ImmutableMap.of(TEST_COLUMN, createIntegerColumnStatistics( OptionalLong.empty(), @@ -583,28 +579,28 @@ public void testGetTableStatistics() OptionalLong.of(newStats.getLongStats().getNumNulls()), OptionalLong.of(newStats.getLongStats().getNumDVs() - 1)))) .build()); - assertEquals(mockClient.getAccessCount(), 6); + assertThat(mockClient.getAccessCount()).isEqualTo(6); } @Test public void testGetTableStatisticsWithoutMetadataCache() { - assertEquals(mockClient.getAccessCount(), 0); + assertThat(mockClient.getAccessCount()).isEqualTo(0); Table table = statsOnlyCacheMetastore.getTable(TEST_DATABASE, TEST_TABLE).orElseThrow(); - assertEquals(mockClient.getAccessCount(), 1); + assertThat(mockClient.getAccessCount()).isEqualTo(1); - assertEquals(statsOnlyCacheMetastore.getTableStatistics(table), TEST_STATS); - assertEquals(mockClient.getAccessCount(), 2); + assertThat(statsOnlyCacheMetastore.getTableStatistics(table)).isEqualTo(TEST_STATS); + assertThat(mockClient.getAccessCount()).isEqualTo(2); - assertEquals(statsOnlyCacheMetastore.getTableStatistics(table), TEST_STATS); - assertEquals(mockClient.getAccessCount(), 2); + assertThat(statsOnlyCacheMetastore.getTableStatistics(table)).isEqualTo(TEST_STATS); + assertThat(mockClient.getAccessCount()).isEqualTo(2); - assertEquals(statsOnlyCacheMetastore.getTableStatisticsStats().getRequestCount(), 2); - assertEquals(statsOnlyCacheMetastore.getTableStatisticsStats().getHitRate(), 0.5); + assertThat(statsOnlyCacheMetastore.getTableStatisticsStats().getRequestCount()).isEqualTo(2); + assertThat(statsOnlyCacheMetastore.getTableStatisticsStats().getHitRate()).isEqualTo(0.5); - assertEquals(statsOnlyCacheMetastore.getTableStats().getRequestCount(), 0); - assertEquals(statsOnlyCacheMetastore.getTableStats().getHitRate(), 1.0); + assertThat(statsOnlyCacheMetastore.getTableStats().getRequestCount()).isEqualTo(0); + assertThat(statsOnlyCacheMetastore.getTableStats().getHitRate()).isEqualTo(1.0); } @Test @@ -651,12 +647,12 @@ public List getTableColumnStatistics(String databaseName, S }); // start get stats before the invalidation, it will wait until invalidation is done to finish - assertEquals(metastore.getTableStatistics(table), TEST_STATS); - assertEquals(mockClient.getAccessCount(), 2); + assertThat(metastore.getTableStatistics(table)).isEqualTo(TEST_STATS); + assertThat(mockClient.getAccessCount()).isEqualTo(2); // get stats after invalidate - assertEquals(metastore.getTableStatistics(table), TEST_STATS); + assertThat(metastore.getTableStatistics(table)).isEqualTo(TEST_STATS); // the value was not cached - assertEquals(mockClient.getAccessCount(), 3); + assertThat(mockClient.getAccessCount()).isEqualTo(3); // make sure invalidateFuture is done invalidateFuture.get(1, SECONDS); } @@ -668,10 +664,10 @@ public List getTableColumnStatistics(String databaseName, S @Test public void testGetPartitionStatistics() { - assertEquals(mockClient.getAccessCount(), 0); + assertThat(mockClient.getAccessCount()).isEqualTo(0); Table table = metastore.getTable(TEST_DATABASE, TEST_TABLE).orElseThrow(); - assertEquals(mockClient.getAccessCount(), 1); + assertThat(mockClient.getAccessCount()).isEqualTo(1); Partition partition = metastore.getPartition(table, TEST_PARTITION_VALUES1).orElseThrow(); String partitionName = makePartitionName(table, partition); @@ -679,31 +675,31 @@ public void testGetPartitionStatistics() String partition2Name = makePartitionName(table, partition2); Partition partition3 = metastore.getPartition(table, TEST_PARTITION_VALUES3).orElseThrow(); String partition3Name = makePartitionName(table, partition3); - assertEquals(mockClient.getAccessCount(), 4); + assertThat(mockClient.getAccessCount()).isEqualTo(4); - assertEquals(metastore.getPartitionStatistics(table, ImmutableList.of(partition)), ImmutableMap.of(TEST_PARTITION1, TEST_STATS)); - assertEquals(mockClient.getAccessCount(), 5); + assertThat(metastore.getPartitionStatistics(table, ImmutableList.of(partition))).isEqualTo(ImmutableMap.of(TEST_PARTITION1, TEST_STATS)); + assertThat(mockClient.getAccessCount()).isEqualTo(5); - assertEquals(metastore.getPartitionStatisticsStats().getRequestCount(), 1); - assertEquals(metastore.getPartitionStatistics(table, ImmutableList.of(partition)), ImmutableMap.of(TEST_PARTITION1, TEST_STATS)); - assertEquals(mockClient.getAccessCount(), 5); + assertThat(metastore.getPartitionStatisticsStats().getRequestCount()).isEqualTo(1); + assertThat(metastore.getPartitionStatistics(table, ImmutableList.of(partition))).isEqualTo(ImmutableMap.of(TEST_PARTITION1, TEST_STATS)); + assertThat(mockClient.getAccessCount()).isEqualTo(5); - assertEquals(metastore.getPartitionStatisticsStats().getRequestCount(), 2); - assertEquals(metastore.getPartitionStatisticsStats().getHitRate(), 1.0 / 2); + assertThat(metastore.getPartitionStatisticsStats().getRequestCount()).isEqualTo(2); + assertThat(metastore.getPartitionStatisticsStats().getHitRate()).isEqualTo(1.0 / 2); - assertEquals(metastore.getTableStats().getRequestCount(), 1); - assertEquals(metastore.getTableStats().getHitRate(), 0.0); + assertThat(metastore.getTableStats().getRequestCount()).isEqualTo(1); + assertThat(metastore.getTableStats().getHitRate()).isEqualTo(0.0); - assertEquals(metastore.getPartitionStats().getRequestCount(), 3); - assertEquals(metastore.getPartitionStats().getHitRate(), 0.0); + assertThat(metastore.getPartitionStats().getRequestCount()).isEqualTo(3); + assertThat(metastore.getPartitionStats().getHitRate()).isEqualTo(0.0); // check empty column list does not trigger the call Table emptyColumnListTable = Table.builder(table).setDataColumns(ImmutableList.of()).build(); Map partitionStatistics = metastore.getPartitionStatistics(emptyColumnListTable, ImmutableList.of(partition)); assertThat(partitionStatistics).containsOnlyKeys(TEST_PARTITION1); assertThat(partitionStatistics.get(TEST_PARTITION1).getBasicStatistics()).isEqualTo(TEST_STATS.getBasicStatistics()); - assertEquals(metastore.getPartitionStatisticsStats().getRequestCount(), 3); - assertEquals(metastore.getPartitionStatisticsStats().getHitRate(), 2.0 / 3); + assertThat(metastore.getPartitionStatisticsStats().getRequestCount()).isEqualTo(3); + assertThat(metastore.getPartitionStatisticsStats().getHitRate()).isEqualTo(2.0 / 3); mockClient.mockPartitionColumnStats(TEST_DATABASE, TEST_TABLE, TEST_PARTITION1, ImmutableMap.of( "col1", ColumnStatisticsData.longStats(new LongColumnStatsData().setNumNulls(1)), @@ -757,28 +753,28 @@ public void testGetPartitionStatistics() @Test public void testGetPartitionStatisticsWithoutMetadataCache() { - assertEquals(mockClient.getAccessCount(), 0); + assertThat(mockClient.getAccessCount()).isEqualTo(0); Table table = statsOnlyCacheMetastore.getTable(TEST_DATABASE, TEST_TABLE).orElseThrow(); - assertEquals(mockClient.getAccessCount(), 1); + assertThat(mockClient.getAccessCount()).isEqualTo(1); Partition partition = statsOnlyCacheMetastore.getPartition(table, TEST_PARTITION_VALUES1).orElseThrow(); - assertEquals(mockClient.getAccessCount(), 2); + assertThat(mockClient.getAccessCount()).isEqualTo(2); - assertEquals(statsOnlyCacheMetastore.getPartitionStatistics(table, ImmutableList.of(partition)), ImmutableMap.of(TEST_PARTITION1, TEST_STATS)); - assertEquals(mockClient.getAccessCount(), 3); + assertThat(statsOnlyCacheMetastore.getPartitionStatistics(table, ImmutableList.of(partition))).isEqualTo(ImmutableMap.of(TEST_PARTITION1, TEST_STATS)); + assertThat(mockClient.getAccessCount()).isEqualTo(3); - assertEquals(statsOnlyCacheMetastore.getPartitionStatistics(table, ImmutableList.of(partition)), ImmutableMap.of(TEST_PARTITION1, TEST_STATS)); - assertEquals(mockClient.getAccessCount(), 3); + assertThat(statsOnlyCacheMetastore.getPartitionStatistics(table, ImmutableList.of(partition))).isEqualTo(ImmutableMap.of(TEST_PARTITION1, TEST_STATS)); + assertThat(mockClient.getAccessCount()).isEqualTo(3); - assertEquals(statsOnlyCacheMetastore.getPartitionStatisticsStats().getRequestCount(), 2); - assertEquals(statsOnlyCacheMetastore.getPartitionStatisticsStats().getHitRate(), 1.0 / 2); + assertThat(statsOnlyCacheMetastore.getPartitionStatisticsStats().getRequestCount()).isEqualTo(2); + assertThat(statsOnlyCacheMetastore.getPartitionStatisticsStats().getHitRate()).isEqualTo(1.0 / 2); - assertEquals(statsOnlyCacheMetastore.getTableStats().getRequestCount(), 0); - assertEquals(statsOnlyCacheMetastore.getTableStats().getHitRate(), 1.0); + assertThat(statsOnlyCacheMetastore.getTableStats().getRequestCount()).isEqualTo(0); + assertThat(statsOnlyCacheMetastore.getTableStats().getHitRate()).isEqualTo(1.0); - assertEquals(statsOnlyCacheMetastore.getPartitionStats().getRequestCount(), 0); - assertEquals(statsOnlyCacheMetastore.getPartitionStats().getHitRate(), 1.0); + assertThat(statsOnlyCacheMetastore.getPartitionStats().getRequestCount()).isEqualTo(0); + assertThat(statsOnlyCacheMetastore.getPartitionStats().getHitRate()).isEqualTo(1.0); } @Test @@ -827,12 +823,12 @@ public Map> getPartitionColumnStatistics(Strin }); // start get stats before the invalidation, it will wait until invalidation is done to finish - assertEquals(metastore.getPartitionStatistics(table, ImmutableList.of(partition)), ImmutableMap.of(TEST_PARTITION1, TEST_STATS)); - assertEquals(mockClient.getAccessCount(), 3); + assertThat(metastore.getPartitionStatistics(table, ImmutableList.of(partition))).isEqualTo(ImmutableMap.of(TEST_PARTITION1, TEST_STATS)); + assertThat(mockClient.getAccessCount()).isEqualTo(3); // get stats after invalidate - assertEquals(metastore.getPartitionStatistics(table, ImmutableList.of(partition)), ImmutableMap.of(TEST_PARTITION1, TEST_STATS)); + assertThat(metastore.getPartitionStatistics(table, ImmutableList.of(partition))).isEqualTo(ImmutableMap.of(TEST_PARTITION1, TEST_STATS)); // the value was not cached - assertEquals(mockClient.getAccessCount(), 4); + assertThat(mockClient.getAccessCount()).isEqualTo(4); // make sure invalidateFuture is done invalidateFuture.get(1, SECONDS); } @@ -844,15 +840,15 @@ public Map> getPartitionColumnStatistics(Strin @Test public void testUpdatePartitionStatistics() { - assertEquals(mockClient.getAccessCount(), 0); + assertThat(mockClient.getAccessCount()).isEqualTo(0); HiveMetastoreClosure hiveMetastoreClosure = new HiveMetastoreClosure(metastore, TESTING_TYPE_MANAGER, false); Table table = hiveMetastoreClosure.getTable(TEST_DATABASE, TEST_TABLE).orElseThrow(); - assertEquals(mockClient.getAccessCount(), 1); + assertThat(mockClient.getAccessCount()).isEqualTo(1); hiveMetastoreClosure.updatePartitionStatistics(table.getDatabaseName(), table.getTableName(), TEST_PARTITION1, identity()); - assertEquals(mockClient.getAccessCount(), 5); + assertThat(mockClient.getAccessCount()).isEqualTo(5); } @Test @@ -860,9 +856,9 @@ public void testInvalidGetPartitionsByNames() { Table table = metastore.getTable(TEST_DATABASE, TEST_TABLE).orElseThrow(); Map> partitionsByNames = metastore.getPartitionsByNames(table, ImmutableList.of(BAD_PARTITION)); - assertEquals(partitionsByNames.size(), 1); + assertThat(partitionsByNames.size()).isEqualTo(1); Optional onlyElement = Iterables.getOnlyElement(partitionsByNames.values()); - assertFalse(onlyElement.isPresent()); + assertThat(onlyElement.isPresent()).isFalse(); } @Test @@ -875,7 +871,7 @@ public void testNoCacheExceptions() } catch (RuntimeException ignored) { } - assertEquals(mockClient.getAccessCount(), 1); + assertThat(mockClient.getAccessCount()).isEqualTo(1); // Second try should hit the client again try { @@ -883,7 +879,7 @@ public void testNoCacheExceptions() } catch (RuntimeException ignored) { } - assertEquals(mockClient.getAccessCount(), 2); + assertThat(mockClient.getAccessCount()).isEqualTo(2); } @Test @@ -892,35 +888,35 @@ public void testNoCacheMissing() CachingHiveMetastore metastore = createCachingHiveMetastore(new BridgingHiveMetastore(thriftHiveMetastore), CACHE_TTL, false, true, executor); mockClient.setReturnTable(false); - assertEquals(mockClient.getAccessCount(), 0); + assertThat(mockClient.getAccessCount()).isEqualTo(0); // First access assertThat(metastore.getTable(TEST_DATABASE, TEST_TABLE)).isEmpty(); - assertEquals(mockClient.getAccessCount(), 1); + assertThat(mockClient.getAccessCount()).isEqualTo(1); // Second access, second load assertThat(metastore.getTable(TEST_DATABASE, TEST_TABLE)).isEmpty(); - assertEquals(mockClient.getAccessCount(), 2); + assertThat(mockClient.getAccessCount()).isEqualTo(2); // Table get be accessed once it exists mockClient.setReturnTable(true); assertThat(metastore.getTable(TEST_DATABASE, TEST_TABLE)).isPresent(); - assertEquals(mockClient.getAccessCount(), 3); + assertThat(mockClient.getAccessCount()).isEqualTo(3); // Table existence is cached mockClient.setReturnTable(true); assertThat(metastore.getTable(TEST_DATABASE, TEST_TABLE)).isPresent(); - assertEquals(mockClient.getAccessCount(), 3); + assertThat(mockClient.getAccessCount()).isEqualTo(3); // Table is returned even if no longer exists mockClient.setReturnTable(false); assertThat(metastore.getTable(TEST_DATABASE, TEST_TABLE)).isPresent(); - assertEquals(mockClient.getAccessCount(), 3); + assertThat(mockClient.getAccessCount()).isEqualTo(3); // After cache invalidation, table absence is apparent metastore.invalidateTable(TEST_DATABASE, TEST_TABLE); assertThat(metastore.getTable(TEST_DATABASE, TEST_TABLE)).isEmpty(); - assertEquals(mockClient.getAccessCount(), 4); + assertThat(mockClient.getAccessCount()).isEqualTo(4); } @Test @@ -928,12 +924,12 @@ public void testCachingHiveMetastoreCreationViaMemoize() { metastore = createPerTransactionCache(new BridgingHiveMetastore(createThriftHiveMetastore()), 1000); - assertEquals(mockClient.getAccessCount(), 0); - assertEquals(metastore.getAllDatabases(), ImmutableList.of(TEST_DATABASE)); - assertEquals(mockClient.getAccessCount(), 1); - assertEquals(metastore.getAllDatabases(), ImmutableList.of(TEST_DATABASE)); - assertEquals(mockClient.getAccessCount(), 1); - assertEquals(metastore.getDatabaseNamesStats().getRequestCount(), 0); + assertThat(mockClient.getAccessCount()).isEqualTo(0); + assertThat(metastore.getAllDatabases()).isEqualTo(ImmutableList.of(TEST_DATABASE)); + assertThat(mockClient.getAccessCount()).isEqualTo(1); + assertThat(metastore.getAllDatabases()).isEqualTo(ImmutableList.of(TEST_DATABASE)); + assertThat(mockClient.getAccessCount()).isEqualTo(1); + assertThat(metastore.getDatabaseNamesStats().getRequestCount()).isEqualTo(0); } @Test @@ -1093,7 +1089,7 @@ public Map> getPartitionsByNames(Table table, List> getPartitionsByNames(Table table, List secondFuture.set(null)); secondFuture.get(); - assertEquals(callCounter.get(), 2); + assertThat(callCounter.get()).isEqualTo(2); } finally { executor.shutdownNow(); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestGlueExpressionUtil.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestGlueExpressionUtil.java index 421985ccc7c9..ef32ae21584f 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestGlueExpressionUtil.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestGlueExpressionUtil.java @@ -27,7 +27,7 @@ import static io.trino.plugin.hive.metastore.glue.GlueExpressionUtil.buildGlueExpressionForSingleDomain; import static io.trino.spi.type.BigintType.BIGINT; import static java.lang.String.format; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestGlueExpressionUtil { @@ -36,7 +36,7 @@ public void testBuildGlueExpressionDomainEqualsSingleValue() { Domain domain = Domain.singleValue(VarcharType.VARCHAR, utf8Slice("2020-01-01")); Optional foo = buildGlueExpressionForSingleDomain("foo", domain, true); - assertEquals(foo.get(), "((foo = '2020-01-01'))"); + assertThat(foo.get()).isEqualTo("((foo = '2020-01-01'))"); } @Test @@ -47,7 +47,7 @@ public void testBuildGlueExpressionTupleDomainEqualsSingleValue() .addStringValues("col2", "2020-02-20") .build(); String expression = buildGlueExpression(ImmutableList.of("col1", "col2"), filter, true); - assertEquals(expression, "((col1 = '2020-01-01')) AND ((col2 = '2020-02-20'))"); + assertThat(expression).isEqualTo("((col1 = '2020-01-01')) AND ((col2 = '2020-02-20'))"); } @Test @@ -58,7 +58,7 @@ public void testBuildGlueExpressionTupleDomainEqualsAndInClause() .addStringValues("col2", "2020-02-20", "2020-02-28") .build(); String expression = buildGlueExpression(ImmutableList.of("col1", "col2"), filter, true); - assertEquals(expression, "((col1 = '2020-01-01')) AND ((col2 in ('2020-02-20', '2020-02-28')))"); + assertThat(expression).isEqualTo("((col1 = '2020-01-01')) AND ((col2 in ('2020-02-20', '2020-02-28')))"); } @Test @@ -69,7 +69,7 @@ public void testBuildGlueExpressionTupleDomainExtraDomain() .addStringValues("col2", "2020-02-20", "2020-02-28") .build(); String expression = buildGlueExpression(ImmutableList.of("col1"), filter, true); - assertEquals(expression, "((col1 = '2020-01-01'))"); + assertThat(expression).isEqualTo("((col1 = '2020-01-01'))"); } @Test @@ -81,7 +81,7 @@ public void testBuildGlueExpressionTupleDomainRange() .addRanges("col2", Range.lessThan(BIGINT, 0L)) .build(); String expression = buildGlueExpression(ImmutableList.of("col1", "col2"), filter, true); - assertEquals(expression, "((col1 = '2020-01-01')) AND ((col2 < 0) OR (col2 > 100))"); + assertThat(expression).isEqualTo("((col1 = '2020-01-01')) AND ((col2 < 0) OR (col2 > 100))"); } @Test @@ -93,7 +93,7 @@ public void testBuildGlueExpressionTupleDomainEqualAndRangeLong() .addRanges("col1", Range.lessThan(BIGINT, 0L)) .build(); String expression = buildGlueExpression(ImmutableList.of("col1"), filter, true); - assertEquals(expression, "((col1 < 0) OR (col1 > 100) OR (col1 = 3))"); + assertThat(expression).isEqualTo("((col1 < 0) OR (col1 > 100) OR (col1 = 3))"); } @Test @@ -104,7 +104,7 @@ public void testBuildGlueExpressionTupleDomainEqualAndRangeString() .addRanges("col1", Range.range(VarcharType.VARCHAR, utf8Slice("2020-03-01"), true, utf8Slice("2020-03-31"), true)) .build(); String expression = buildGlueExpression(ImmutableList.of("col1"), filter, true); - assertEquals(expression, "((col1 >= '2020-03-01' AND col1 <= '2020-03-31') OR (col1 in ('2020-01-01', '2020-01-31')))"); + assertThat(expression).isEqualTo("((col1 >= '2020-03-01' AND col1 <= '2020-03-31') OR (col1 in ('2020-01-01', '2020-01-31')))"); } @Test @@ -114,7 +114,7 @@ public void testBuildGlueExpressionExtraColumn() .addStringValues("col1", "2020-01-01") .build(); String expression = buildGlueExpression(ImmutableList.of("col1", "col2"), filter, true); - assertEquals(expression, "((col1 = '2020-01-01'))"); + assertThat(expression).isEqualTo("((col1 = '2020-01-01'))"); } @Test @@ -124,7 +124,7 @@ public void testBuildGlueExpressionTupleDomainIsNull() .addDomain("col1", Domain.onlyNull(VarcharType.VARCHAR)) .build(); String expression = buildGlueExpression(ImmutableList.of("col1"), filter, true); - assertEquals(expression, format("(col1 = '%s')", GlueExpressionUtil.NULL_STRING)); + assertThat(expression).isEqualTo(format("(col1 = '%s')", GlueExpressionUtil.NULL_STRING)); } @Test @@ -134,7 +134,7 @@ public void testBuildGlueExpressionTupleDomainNotNull() .addDomain("col1", Domain.notNull(VarcharType.VARCHAR)) .build(); String expression = buildGlueExpression(ImmutableList.of("col1"), filter, true); - assertEquals(expression, format("(col1 <> '%s')", GlueExpressionUtil.NULL_STRING)); + assertThat(expression).isEqualTo(format("(col1 <> '%s')", GlueExpressionUtil.NULL_STRING)); } @Test @@ -145,7 +145,7 @@ public void testBuildGlueExpressionTupleDomainEqualsOrIsNull() .addDomain("col1", Domain.onlyNull(VarcharType.VARCHAR)) .build(); String expression = buildGlueExpression(ImmutableList.of("col1"), filter, true); - assertEquals(expression, format("((col1 = '2020-01-01') OR (col1 = '%s'))", GlueExpressionUtil.NULL_STRING)); + assertThat(expression).isEqualTo(format("((col1 = '2020-01-01') OR (col1 = '%s'))", GlueExpressionUtil.NULL_STRING)); } @Test @@ -156,7 +156,7 @@ public void testBuildGlueExpressionTupleDomainEqualsAndIsNotNull() .addDomain("col2", Domain.notNull(VarcharType.VARCHAR)) .build(); String expression = buildGlueExpression(ImmutableList.of("col1", "col2"), filter, true); - assertEquals(expression, format("((col1 = '2020-01-01')) AND (col2 <> '%s')", GlueExpressionUtil.NULL_STRING)); + assertThat(expression).isEqualTo(format("((col1 = '2020-01-01')) AND (col2 <> '%s')", GlueExpressionUtil.NULL_STRING)); } @Test @@ -166,7 +166,7 @@ public void testBuildGlueExpressionMaxLengthNone() .addStringValues("col1", "x".repeat(101)) .build(); String expression = buildGlueExpression(ImmutableList.of("col1"), filter, true, 100); - assertEquals(expression, ""); + assertThat(expression).isEqualTo(""); } @Test @@ -177,14 +177,14 @@ public void testBuildGlueExpressionMaxLengthOneColumn() .addStringValues("col2", "x".repeat(25)) .build(); String expression = buildGlueExpression(ImmutableList.of("col1", "col2"), filter, true, 20); - assertEquals(expression, "((col1 = 'xxxxx'))"); + assertThat(expression).isEqualTo("((col1 = 'xxxxx'))"); } @Test public void testBuildGlueExpressionTupleDomainAll() { String expression = buildGlueExpression(ImmutableList.of("col1"), TupleDomain.all(), true); - assertEquals(expression, ""); + assertThat(expression).isEqualTo(""); } @Test @@ -194,7 +194,7 @@ public void testDecimalConverstion() .addDecimalValues("col1", "10.134") .build(); String expression = buildGlueExpression(ImmutableList.of("col1"), filter, true); - assertEquals(expression, "((col1 = 10.13400))"); + assertThat(expression).isEqualTo("((col1 = 10.13400))"); } @Test @@ -204,7 +204,7 @@ public void testBigintConversion() .addBigintValues("col1", Long.MAX_VALUE) .build(); String expression = buildGlueExpression(ImmutableList.of("col1"), filter, true); - assertEquals(expression, format("((col1 = %d))", Long.MAX_VALUE)); + assertThat(expression).isEqualTo(format("((col1 = %d))", Long.MAX_VALUE)); } @Test @@ -214,7 +214,7 @@ public void testIntegerConversion() .addIntegerValues("col1", Long.valueOf(Integer.MAX_VALUE)) .build(); String expression = buildGlueExpression(ImmutableList.of("col1"), filter, true); - assertEquals(expression, format("((col1 = %d))", Integer.MAX_VALUE)); + assertThat(expression).isEqualTo(format("((col1 = %d))", Integer.MAX_VALUE)); } @Test @@ -224,7 +224,7 @@ public void testSmallintConversion() .addIntegerValues("col1", Long.valueOf(Short.MAX_VALUE)) .build(); String expression = buildGlueExpression(ImmutableList.of("col1"), filter, true); - assertEquals(expression, format("((col1 = %d))", Short.MAX_VALUE)); + assertThat(expression).isEqualTo(format("((col1 = %d))", Short.MAX_VALUE)); } @Test @@ -234,6 +234,6 @@ public void testTinyintConversion() .addIntegerValues("col1", Long.valueOf(Byte.MAX_VALUE)) .build(); String expression = buildGlueExpression(ImmutableList.of("col1"), filter, true); - assertEquals(expression, format("((col1 = %d))", Byte.MAX_VALUE)); + assertThat(expression).isEqualTo(format("((col1 = %d))", Byte.MAX_VALUE)); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestGlueInputConverter.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestGlueInputConverter.java index ffe163f866b6..30ebc7828782 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestGlueInputConverter.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestGlueInputConverter.java @@ -40,8 +40,7 @@ import static io.trino.plugin.hive.metastore.glue.TestingMetastoreObjects.getPrestoTestDatabase; import static io.trino.plugin.hive.metastore.glue.TestingMetastoreObjects.getPrestoTestPartition; import static io.trino.plugin.hive.metastore.glue.TestingMetastoreObjects.getPrestoTestTable; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; +import static org.assertj.core.api.Assertions.assertThat; public class TestGlueInputConverter { @@ -54,10 +53,10 @@ public void testConvertDatabase() { DatabaseInput dbInput = GlueInputConverter.convertDatabase(testDb); - assertEquals(dbInput.getName(), testDb.getDatabaseName()); - assertEquals(dbInput.getDescription(), testDb.getComment().get()); - assertEquals(dbInput.getLocationUri(), testDb.getLocation().get()); - assertEquals(dbInput.getParameters(), testDb.getParameters()); + assertThat(dbInput.getName()).isEqualTo(testDb.getDatabaseName()); + assertThat(dbInput.getDescription()).isEqualTo(testDb.getComment().get()); + assertThat(dbInput.getLocationUri()).isEqualTo(testDb.getLocation().get()); + assertThat(dbInput.getParameters()).isEqualTo(testDb.getParameters()); } @Test @@ -65,15 +64,15 @@ public void testConvertTable() { TableInput tblInput = GlueInputConverter.convertTable(testTbl); - assertEquals(tblInput.getName(), testTbl.getTableName()); - assertEquals(tblInput.getOwner(), testTbl.getOwner().orElse(null)); - assertEquals(tblInput.getTableType(), testTbl.getTableType()); - assertEquals(tblInput.getParameters(), testTbl.getParameters()); + assertThat(tblInput.getName()).isEqualTo(testTbl.getTableName()); + assertThat(tblInput.getOwner()).isEqualTo(testTbl.getOwner().orElse(null)); + assertThat(tblInput.getTableType()).isEqualTo(testTbl.getTableType()); + assertThat(tblInput.getParameters()).isEqualTo(testTbl.getParameters()); assertColumnList(tblInput.getStorageDescriptor().getColumns(), testTbl.getDataColumns()); assertColumnList(tblInput.getPartitionKeys(), testTbl.getPartitionColumns()); assertStorage(tblInput.getStorageDescriptor(), testTbl.getStorage()); - assertEquals(tblInput.getViewExpandedText(), testTbl.getViewExpandedText().get()); - assertEquals(tblInput.getViewOriginalText(), testTbl.getViewOriginalText().get()); + assertThat(tblInput.getViewExpandedText()).isEqualTo(testTbl.getViewExpandedText().get()); + assertThat(tblInput.getViewOriginalText()).isEqualTo(testTbl.getViewOriginalText().get()); } @Test @@ -81,9 +80,9 @@ public void testConvertPartition() { PartitionInput partitionInput = GlueInputConverter.convertPartition(testPartition); - assertEquals(partitionInput.getParameters(), testPartition.getParameters()); + assertThat(partitionInput.getParameters()).isEqualTo(testPartition.getParameters()); assertStorage(partitionInput.getStorageDescriptor(), testPartition.getStorage()); - assertEquals(partitionInput.getValues(), testPartition.getValues()); + assertThat(partitionInput.getValues()).isEqualTo(testPartition.getValues()); } @Test @@ -94,28 +93,28 @@ public void testConvertFunction() LanguageFunction expected = new LanguageFunction("(integer,bigint,varchar)", sql, List.of(), Optional.of("owner")); UserDefinedFunctionInput input = GlueInputConverter.convertFunction("test_name", expected); - assertEquals(input.getOwnerName(), expected.owner().orElseThrow()); + assertThat(input.getOwnerName()).isEqualTo(expected.owner().orElseThrow()); UserDefinedFunction function = new UserDefinedFunction() .withOwnerName(input.getOwnerName()) .withResourceUris(input.getResourceUris()); LanguageFunction actual = GlueToTrinoConverter.convertFunction(function); - assertEquals(input.getResourceUris().size(), 4); - assertEquals(actual, expected); + assertThat(input.getResourceUris().size()).isEqualTo(4); + assertThat(actual).isEqualTo(expected); // verify that the owner comes from the metastore function.setOwnerName("other"); actual = GlueToTrinoConverter.convertFunction(function); - assertEquals(actual.owner(), Optional.of("other")); + assertThat(actual.owner()).isEqualTo(Optional.of("other")); } private static void assertColumnList(List actual, List expected) { if (expected == null) { - assertNull(actual); + assertThat(actual).isNull(); } - assertEquals(actual.size(), expected.size()); + assertThat(actual.size()).isEqualTo(expected.size()); for (int i = 0; i < expected.size(); i++) { assertColumn(actual.get(i), expected.get(i)); @@ -124,22 +123,22 @@ private static void assertColumnList(List actual, List expected) { if (expected == null) { - assertNull(actual); + assertThat(actual).isNull(); } - assertEquals(actual.size(), expected.size()); + assertThat(actual.size()).isEqualTo(expected.size()); for (int i = 0; i < expected.size(); i++) { assertColumn(actual.get(i), expected.get(i)); @@ -294,21 +287,21 @@ private static void assertColumnList(List actual, List getMetastoreClient().getDatabase(null)) .isInstanceOf(TrinoException.class) .hasMessageStartingWith("Database name cannot be equal to null or empty"); - assertEquals(stats.getGetDatabase().getTotalFailures().getTotalCount(), initialFailureCount + 1); + assertThat(stats.getGetDatabase().getTotalFailures().getTotalCount()).isEqualTo(initialFailureCount + 1); } @Test @@ -1375,7 +1373,7 @@ public void testGlueObjectsWithoutStorageDescriptor() glueClient.createTable(new CreateTableRequest() .withDatabaseName(database) .withTableInput(tableInput)); - assertTrue(isIcebergTable(metastore.getTable(table.getSchemaName(), table.getTableName()).orElseThrow())); + assertThat(isIcebergTable(metastore.getTable(table.getSchemaName(), table.getTableName()).orElseThrow())).isTrue(); glueClient.deleteTable(deleteTableRequest); // Delta Lake table @@ -1383,7 +1381,7 @@ public void testGlueObjectsWithoutStorageDescriptor() glueClient.createTable(new CreateTableRequest() .withDatabaseName(database) .withTableInput(tableInput)); - assertTrue(isDeltaLakeTable(metastore.getTable(table.getSchemaName(), table.getTableName()).orElseThrow())); + assertThat(isDeltaLakeTable(metastore.getTable(table.getSchemaName(), table.getTableName()).orElseThrow())).isTrue(); glueClient.deleteTable(deleteTableRequest); // Iceberg materialized view @@ -1396,7 +1394,7 @@ public void testGlueObjectsWithoutStorageDescriptor() glueClient.createTable(new CreateTableRequest() .withDatabaseName(database) .withTableInput(tableInput)); - assertTrue(isTrinoMaterializedView(metastore.getTable(table.getSchemaName(), table.getTableName()).orElseThrow())); + assertThat(isTrinoMaterializedView(metastore.getTable(table.getSchemaName(), table.getTableName()).orElseThrow())).isTrue(); materializedViews.add(table); try (Transaction transaction = newTransaction()) { ConnectorSession session = newSession(); @@ -1529,11 +1527,10 @@ private void doGetPartitionsFilterTest( tableName.getTableName(), partitionColumnNames, filter); - assertTrue(partitionNames.isPresent()); - assertEquals( - partitionNames.get(), - expectedResults, - format("lists \nactual: %s\nexpected: %s\nmismatch for filter %s (input index %d)\n", partitionNames.get(), expectedResults, filter, i)); + assertThat(partitionNames.isPresent()).isTrue(); + assertThat(partitionNames.get()) + .describedAs(format("lists \nactual: %s\nexpected: %s\nmismatch for filter %s (input index %d)\n", partitionNames.get(), expectedResults, filter, i)) + .isEqualTo(expectedResults); } } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestCoalescingCounter.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestCoalescingCounter.java index b2c2257fa425..a3cf1651a923 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestCoalescingCounter.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestCoalescingCounter.java @@ -26,7 +26,7 @@ import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestCoalescingCounter { @@ -36,20 +36,20 @@ public void test() TestingClock clock = new TestingClock(); CoalescingCounter counter = new CoalescingCounter(clock, new Duration(1, SECONDS)); - assertEquals(counter.get(), 0); - assertEquals(counter.incrementAndGet(), 1); - assertEquals(counter.incrementAndGet(), 1); - assertEquals(counter.get(), 1); + assertThat(counter.get()).isEqualTo(0); + assertThat(counter.incrementAndGet()).isEqualTo(1); + assertThat(counter.incrementAndGet()).isEqualTo(1); + assertThat(counter.get()).isEqualTo(1); clock.increment(100, MILLISECONDS); - assertEquals(counter.incrementAndGet(), 1); - assertEquals(counter.incrementAndGet(), 1); - assertEquals(counter.get(), 1); + assertThat(counter.incrementAndGet()).isEqualTo(1); + assertThat(counter.incrementAndGet()).isEqualTo(1); + assertThat(counter.get()).isEqualTo(1); clock.increment(1000, MILLISECONDS); - assertEquals(counter.incrementAndGet(), 2); - assertEquals(counter.incrementAndGet(), 2); - assertEquals(counter.get(), 2); + assertThat(counter.incrementAndGet()).isEqualTo(2); + assertThat(counter.incrementAndGet()).isEqualTo(2); + assertThat(counter.get()).isEqualTo(2); } // TODO replace with https://github.com/airlift/airlift/pull/780 diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestStaticMetastoreConfig.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestStaticMetastoreConfig.java index 96587454dc64..9cb092b7ae71 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestStaticMetastoreConfig.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestStaticMetastoreConfig.java @@ -23,7 +23,7 @@ import static io.airlift.configuration.testing.ConfigAssertions.assertFullMapping; import static io.airlift.configuration.testing.ConfigAssertions.assertRecordedDefaults; import static io.airlift.configuration.testing.ConfigAssertions.recordDefaults; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestStaticMetastoreConfig { @@ -48,8 +48,8 @@ public void testExplicitPropertyMappingsSingleMetastore() .setMetastoreUsername("trino"); assertFullMapping(properties, expected); - assertEquals(expected.getMetastoreUris(), ImmutableList.of(URI.create("thrift://localhost:9083"))); - assertEquals(expected.getMetastoreUsername(), "trino"); + assertThat(expected.getMetastoreUris()).isEqualTo(ImmutableList.of(URI.create("thrift://localhost:9083"))); + assertThat(expected.getMetastoreUsername()).isEqualTo("trino"); } @Test @@ -65,9 +65,9 @@ public void testExplicitPropertyMappingsMultipleMetastores() .setMetastoreUsername("trino"); assertFullMapping(properties, expected); - assertEquals(expected.getMetastoreUris(), ImmutableList.of( + assertThat(expected.getMetastoreUris()).isEqualTo(ImmutableList.of( URI.create("thrift://localhost:9083"), URI.create("thrift://192.0.2.3:8932"))); - assertEquals(expected.getMetastoreUsername(), "trino"); + assertThat(expected.getMetastoreUsername()).isEqualTo("trino"); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestStaticTokenAwareMetastoreClientFactory.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestStaticTokenAwareMetastoreClientFactory.java index f586c71945b6..4eed9b49a139 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestStaticTokenAwareMetastoreClientFactory.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestStaticTokenAwareMetastoreClientFactory.java @@ -26,8 +26,8 @@ import java.util.Optional; import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestStaticTokenAwareMetastoreClientFactory { @@ -228,6 +228,6 @@ private static void assertEqualHiveClient(ThriftMetastoreClient actual, ThriftMe if (expected instanceof FailureAwareThriftMetastoreClient) { expected = ((FailureAwareThriftMetastoreClient) expected).getDelegate(); } - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestThriftMetastoreUtil.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestThriftMetastoreUtil.java index 14a8e2706802..0ca9057048e7 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestThriftMetastoreUtil.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestThriftMetastoreUtil.java @@ -67,7 +67,6 @@ import static io.trino.spi.security.PrincipalType.ROLE; import static io.trino.spi.security.PrincipalType.USER; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestThriftMetastoreUtil { @@ -82,13 +81,11 @@ public void testLongStatsToColumnStatistics() ColumnStatisticsObj columnStatisticsObj = new ColumnStatisticsObj("my_col", BIGINT_TYPE_NAME, longStats(longColumnStatsData)); HiveColumnStatistics actual = fromMetastoreApiColumnStatistics(columnStatisticsObj, OptionalLong.of(1000)); - assertEquals( - actual, - HiveColumnStatistics.builder() - .setIntegerStatistics(new IntegerStatistics(OptionalLong.of(0), OptionalLong.of(100))) - .setNullsCount(1) - .setDistinctValuesCount(19) - .build()); + assertThat(actual).isEqualTo(HiveColumnStatistics.builder() + .setIntegerStatistics(new IntegerStatistics(OptionalLong.of(0), OptionalLong.of(100))) + .setNullsCount(1) + .setDistinctValuesCount(19) + .build()); } @Test @@ -98,11 +95,9 @@ public void testEmptyLongStatsToColumnStatistics() ColumnStatisticsObj columnStatisticsObj = new ColumnStatisticsObj("my_col", BIGINT_TYPE_NAME, longStats(emptyLongColumnStatsData)); HiveColumnStatistics actual = fromMetastoreApiColumnStatistics(columnStatisticsObj, OptionalLong.empty()); - assertEquals( - actual, - HiveColumnStatistics.builder() - .setIntegerStatistics(new IntegerStatistics(OptionalLong.empty(), OptionalLong.empty())) - .build()); + assertThat(actual).isEqualTo(HiveColumnStatistics.builder() + .setIntegerStatistics(new IntegerStatistics(OptionalLong.empty(), OptionalLong.empty())) + .build()); } @Test @@ -116,13 +111,11 @@ public void testDoubleStatsToColumnStatistics() ColumnStatisticsObj columnStatisticsObj = new ColumnStatisticsObj("my_col", DOUBLE_TYPE_NAME, doubleStats(doubleColumnStatsData)); HiveColumnStatistics actual = fromMetastoreApiColumnStatistics(columnStatisticsObj, OptionalLong.of(1000)); - assertEquals( - actual, - HiveColumnStatistics.builder() - .setDoubleStatistics(new DoubleStatistics(OptionalDouble.of(0), OptionalDouble.of(100))) - .setNullsCount(1) - .setDistinctValuesCount(19) - .build()); + assertThat(actual).isEqualTo(HiveColumnStatistics.builder() + .setDoubleStatistics(new DoubleStatistics(OptionalDouble.of(0), OptionalDouble.of(100))) + .setNullsCount(1) + .setDistinctValuesCount(19) + .build()); } @Test @@ -132,11 +125,9 @@ public void testEmptyDoubleStatsToColumnStatistics() ColumnStatisticsObj columnStatisticsObj = new ColumnStatisticsObj("my_col", DOUBLE_TYPE_NAME, doubleStats(emptyDoubleColumnStatsData)); HiveColumnStatistics actual = fromMetastoreApiColumnStatistics(columnStatisticsObj, OptionalLong.empty()); - assertEquals( - actual, - HiveColumnStatistics.builder() - .setDoubleStatistics(new DoubleStatistics(OptionalDouble.empty(), OptionalDouble.empty())) - .build()); + assertThat(actual).isEqualTo(HiveColumnStatistics.builder() + .setDoubleStatistics(new DoubleStatistics(OptionalDouble.empty(), OptionalDouble.empty())) + .build()); } @Test @@ -152,13 +143,11 @@ public void testDecimalStatsToColumnStatistics() ColumnStatisticsObj columnStatisticsObj = new ColumnStatisticsObj("my_col", DECIMAL_TYPE_NAME, decimalStats(decimalColumnStatsData)); HiveColumnStatistics actual = fromMetastoreApiColumnStatistics(columnStatisticsObj, OptionalLong.of(1000)); - assertEquals( - actual, - HiveColumnStatistics.builder() - .setDecimalStatistics(new DecimalStatistics(Optional.of(low), Optional.of(high))) - .setNullsCount(1) - .setDistinctValuesCount(19) - .build()); + assertThat(actual).isEqualTo(HiveColumnStatistics.builder() + .setDecimalStatistics(new DecimalStatistics(Optional.of(low), Optional.of(high))) + .setNullsCount(1) + .setDistinctValuesCount(19) + .build()); } @Test @@ -168,11 +157,9 @@ public void testEmptyDecimalStatsToColumnStatistics() ColumnStatisticsObj columnStatisticsObj = new ColumnStatisticsObj("my_col", DECIMAL_TYPE_NAME, decimalStats(emptyDecimalColumnStatsData)); HiveColumnStatistics actual = fromMetastoreApiColumnStatistics(columnStatisticsObj, OptionalLong.empty()); - assertEquals( - actual, - HiveColumnStatistics.builder() - .setDecimalStatistics(new DecimalStatistics(Optional.empty(), Optional.empty())) - .build()); + assertThat(actual).isEqualTo(HiveColumnStatistics.builder() + .setDecimalStatistics(new DecimalStatistics(Optional.empty(), Optional.empty())) + .build()); } @Test @@ -185,12 +172,10 @@ public void testBooleanStatsToColumnStatistics() ColumnStatisticsObj columnStatisticsObj = new ColumnStatisticsObj("my_col", BOOLEAN_TYPE_NAME, booleanStats(booleanColumnStatsData)); HiveColumnStatistics actual = fromMetastoreApiColumnStatistics(columnStatisticsObj, OptionalLong.empty()); - assertEquals( - actual, - HiveColumnStatistics.builder() - .setBooleanStatistics(new BooleanStatistics(OptionalLong.of(100), OptionalLong.of(10))) - .setNullsCount(0) - .build()); + assertThat(actual).isEqualTo(HiveColumnStatistics.builder() + .setBooleanStatistics(new BooleanStatistics(OptionalLong.of(100), OptionalLong.of(10))) + .setNullsCount(0) + .build()); } @Test @@ -200,12 +185,10 @@ public void testImpalaGeneratedBooleanStatistics() ColumnStatisticsObj columnStatisticsObj = new ColumnStatisticsObj("my_col", BOOLEAN_TYPE_NAME, booleanStats(statsData)); HiveColumnStatistics actual = fromMetastoreApiColumnStatistics(columnStatisticsObj, OptionalLong.empty()); - assertEquals( - actual, - HiveColumnStatistics.builder() - .setBooleanStatistics(new BooleanStatistics(OptionalLong.empty(), OptionalLong.empty())) - .setNullsCount(2) - .build()); + assertThat(actual).isEqualTo(HiveColumnStatistics.builder() + .setBooleanStatistics(new BooleanStatistics(OptionalLong.empty(), OptionalLong.empty())) + .setNullsCount(2) + .build()); } @Test @@ -215,11 +198,9 @@ public void testEmptyBooleanStatsToColumnStatistics() ColumnStatisticsObj columnStatisticsObj = new ColumnStatisticsObj("my_col", BOOLEAN_TYPE_NAME, booleanStats(emptyBooleanColumnStatsData)); HiveColumnStatistics actual = fromMetastoreApiColumnStatistics(columnStatisticsObj, OptionalLong.empty()); - assertEquals( - actual, - HiveColumnStatistics.builder() - .setBooleanStatistics(new BooleanStatistics(OptionalLong.empty(), OptionalLong.empty())) - .build()); + assertThat(actual).isEqualTo(HiveColumnStatistics.builder() + .setBooleanStatistics(new BooleanStatistics(OptionalLong.empty(), OptionalLong.empty())) + .build()); } @Test @@ -233,13 +214,11 @@ public void testDateStatsToColumnStatistics() ColumnStatisticsObj columnStatisticsObj = new ColumnStatisticsObj("my_col", DATE_TYPE_NAME, dateStats(dateColumnStatsData)); HiveColumnStatistics actual = fromMetastoreApiColumnStatistics(columnStatisticsObj, OptionalLong.of(1000)); - assertEquals( - actual, - HiveColumnStatistics.builder() - .setDateStatistics(new DateStatistics(Optional.of(LocalDate.ofEpochDay(1000)), Optional.of(LocalDate.ofEpochDay(2000)))) - .setNullsCount(1) - .setDistinctValuesCount(19) - .build()); + assertThat(actual).isEqualTo(HiveColumnStatistics.builder() + .setDateStatistics(new DateStatistics(Optional.of(LocalDate.ofEpochDay(1000)), Optional.of(LocalDate.ofEpochDay(2000)))) + .setNullsCount(1) + .setDistinctValuesCount(19) + .build()); } @Test @@ -249,11 +228,9 @@ public void testEmptyDateStatsToColumnStatistics() ColumnStatisticsObj columnStatisticsObj = new ColumnStatisticsObj("my_col", DATE_TYPE_NAME, dateStats(emptyDateColumnStatsData)); HiveColumnStatistics actual = fromMetastoreApiColumnStatistics(columnStatisticsObj, OptionalLong.empty()); - assertEquals( - actual, - HiveColumnStatistics.builder() - .setDateStatistics(new DateStatistics(Optional.empty(), Optional.empty())) - .build()); + assertThat(actual).isEqualTo(HiveColumnStatistics.builder() + .setDateStatistics(new DateStatistics(Optional.empty(), Optional.empty())) + .build()); } @Test @@ -267,14 +244,12 @@ public void testStringStatsToColumnStatistics() ColumnStatisticsObj columnStatisticsObj = new ColumnStatisticsObj("my_col", STRING_TYPE_NAME, stringStats(stringColumnStatsData)); HiveColumnStatistics actual = fromMetastoreApiColumnStatistics(columnStatisticsObj, OptionalLong.of(2)); - assertEquals( - actual, - HiveColumnStatistics.builder() - .setMaxValueSizeInBytes(100) - .setTotalSizeInBytes(23) - .setNullsCount(1) - .setDistinctValuesCount(1) - .build()); + assertThat(actual).isEqualTo(HiveColumnStatistics.builder() + .setMaxValueSizeInBytes(100) + .setTotalSizeInBytes(23) + .setNullsCount(1) + .setDistinctValuesCount(1) + .build()); } @Test @@ -284,7 +259,7 @@ public void testEmptyStringColumnStatsData() ColumnStatisticsObj columnStatisticsObj = new ColumnStatisticsObj("my_col", STRING_TYPE_NAME, stringStats(emptyStringColumnStatsData)); HiveColumnStatistics actual = fromMetastoreApiColumnStatistics(columnStatisticsObj, OptionalLong.empty()); - assertEquals(actual, HiveColumnStatistics.builder().build()); + assertThat(actual).isEqualTo(HiveColumnStatistics.builder().build()); } @Test @@ -297,13 +272,11 @@ public void testBinaryStatsToColumnStatistics() ColumnStatisticsObj columnStatisticsObj = new ColumnStatisticsObj("my_col", BINARY_TYPE_NAME, binaryStats(binaryColumnStatsData)); HiveColumnStatistics actual = fromMetastoreApiColumnStatistics(columnStatisticsObj, OptionalLong.of(4)); - assertEquals( - actual, - HiveColumnStatistics.builder() - .setMaxValueSizeInBytes(100) - .setTotalSizeInBytes(44) - .setNullsCount(2) - .build()); + assertThat(actual).isEqualTo(HiveColumnStatistics.builder() + .setMaxValueSizeInBytes(100) + .setTotalSizeInBytes(44) + .setNullsCount(2) + .build()); } @Test @@ -313,7 +286,7 @@ public void testEmptyBinaryStatsToColumnStatistics() ColumnStatisticsObj columnStatisticsObj = new ColumnStatisticsObj("my_col", BINARY_TYPE_NAME, binaryStats(emptyBinaryColumnStatsData)); HiveColumnStatistics actual = fromMetastoreApiColumnStatistics(columnStatisticsObj, OptionalLong.empty()); - assertEquals(actual, HiveColumnStatistics.builder().build()); + assertThat(actual).isEqualTo(HiveColumnStatistics.builder().build()); } @Test @@ -325,8 +298,8 @@ public void testSingleDistinctValue() ColumnStatisticsObj columnStatisticsObj = new ColumnStatisticsObj("my_col", DOUBLE_TYPE_NAME, doubleStats(doubleColumnStatsData)); HiveColumnStatistics actual = fromMetastoreApiColumnStatistics(columnStatisticsObj, OptionalLong.of(10)); - assertEquals(actual.getNullsCount(), OptionalLong.of(10)); - assertEquals(actual.getDistinctValuesCount(), OptionalLong.of(0)); + assertThat(actual.getNullsCount()).isEqualTo(OptionalLong.of(10)); + assertThat(actual.getDistinctValuesCount()).isEqualTo(OptionalLong.of(0)); doubleColumnStatsData = new DoubleColumnStatsData(); doubleColumnStatsData.setNumNulls(10); @@ -334,8 +307,8 @@ public void testSingleDistinctValue() columnStatisticsObj = new ColumnStatisticsObj("my_col", DOUBLE_TYPE_NAME, doubleStats(doubleColumnStatsData)); actual = fromMetastoreApiColumnStatistics(columnStatisticsObj, OptionalLong.of(11)); - assertEquals(actual.getNullsCount(), OptionalLong.of(10)); - assertEquals(actual.getDistinctValuesCount(), OptionalLong.of(1)); + assertThat(actual.getNullsCount()).isEqualTo(OptionalLong.of(10)); + assertThat(actual.getDistinctValuesCount()).isEqualTo(OptionalLong.of(1)); } @Test @@ -348,9 +321,9 @@ public void testSparkFallbackGetBasicStatistics() "spark.sql.statistics.rawDataSize", "3", "spark.sql.statistics.totalSize", "4"); HiveBasicStatistics actual = getBasicStatisticsWithSparkFallback(tableParameters); - assertEquals(actual, new HiveBasicStatistics(OptionalLong.of(1), OptionalLong.of(2), OptionalLong.of(3), OptionalLong.of(4))); + assertThat(actual).isEqualTo(new HiveBasicStatistics(OptionalLong.of(1), OptionalLong.of(2), OptionalLong.of(3), OptionalLong.of(4))); actual = getHiveBasicStatistics(tableParameters); - assertEquals(actual, new HiveBasicStatistics(OptionalLong.empty(), OptionalLong.empty(), OptionalLong.empty(), OptionalLong.empty())); + assertThat(actual).isEqualTo(new HiveBasicStatistics(OptionalLong.empty(), OptionalLong.empty(), OptionalLong.empty(), OptionalLong.empty())); // empty hive and not empty spark stats tableParameters = Map.of( "numFiles", "0", @@ -362,9 +335,9 @@ public void testSparkFallbackGetBasicStatistics() "spark.sql.statistics.rawDataSize", "3", "spark.sql.statistics.totalSize", "4"); actual = getBasicStatisticsWithSparkFallback(tableParameters); - assertEquals(actual, new HiveBasicStatistics(OptionalLong.of(1), OptionalLong.of(2), OptionalLong.of(3), OptionalLong.of(4))); + assertThat(actual).isEqualTo(new HiveBasicStatistics(OptionalLong.of(1), OptionalLong.of(2), OptionalLong.of(3), OptionalLong.of(4))); actual = getHiveBasicStatistics(tableParameters); - assertEquals(actual, new HiveBasicStatistics(OptionalLong.of(0), OptionalLong.of(0), OptionalLong.of(0), OptionalLong.of(0))); + assertThat(actual).isEqualTo(new HiveBasicStatistics(OptionalLong.of(0), OptionalLong.of(0), OptionalLong.of(0), OptionalLong.of(0))); // not empty hive and not empty spark stats tableParameters = Map.of( "numFiles", "10", @@ -376,7 +349,7 @@ public void testSparkFallbackGetBasicStatistics() "spark.sql.statistics.rawDataSize", "3", "spark.sql.statistics.totalSize", "4"); actual = getBasicStatisticsWithSparkFallback(tableParameters); - assertEquals(actual, new HiveBasicStatistics(OptionalLong.of(10), OptionalLong.of(20), OptionalLong.of(30), OptionalLong.of(40))); + assertThat(actual).isEqualTo(new HiveBasicStatistics(OptionalLong.of(10), OptionalLong.of(20), OptionalLong.of(30), OptionalLong.of(40))); } @Test @@ -389,7 +362,7 @@ public void testBasicStatisticsRoundTrip() private static void testBasicStatisticsRoundTrip(HiveBasicStatistics expected) { - assertEquals(getHiveBasicStatistics(updateStatisticsParameters(ImmutableMap.of(), expected)), expected); + assertThat(getHiveBasicStatistics(updateStatisticsParameters(ImmutableMap.of(), expected))).isEqualTo(expected); } @Test diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestThriftSparkMetastoreUtil.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestThriftSparkMetastoreUtil.java index 8c58a2a9b0de..ebc67f7d3f1f 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestThriftSparkMetastoreUtil.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestThriftSparkMetastoreUtil.java @@ -37,7 +37,7 @@ import static io.trino.plugin.hive.util.SerdeConstants.DECIMAL_TYPE_NAME; import static io.trino.plugin.hive.util.SerdeConstants.DOUBLE_TYPE_NAME; import static io.trino.plugin.hive.util.SerdeConstants.STRING_TYPE_NAME; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestThriftSparkMetastoreUtil { @@ -52,20 +52,16 @@ public void testSparkLongStatsToColumnStatistics() "spark.sql.statistics.colStats.c_long.version", "2", "spark.sql.statistics.colStats.c_long.max", "4"); HiveColumnStatistics actualNotExists = fromMetastoreColumnStatistics(new FieldSchema("c_long_not_exists", BIGINT_TYPE_NAME, null), columnStatistics, 4); - assertEquals( - actualNotExists, - HiveColumnStatistics.builder() - .setIntegerStatistics(new IntegerStatistics(OptionalLong.empty(), OptionalLong.empty())) - .build()); + assertThat(actualNotExists).isEqualTo(HiveColumnStatistics.builder() + .setIntegerStatistics(new IntegerStatistics(OptionalLong.empty(), OptionalLong.empty())) + .build()); HiveColumnStatistics actual = fromMetastoreColumnStatistics(new FieldSchema("c_long", BIGINT_TYPE_NAME, null), columnStatistics, 4); - assertEquals( - actual, - HiveColumnStatistics.builder() - .setIntegerStatistics(new IntegerStatistics(OptionalLong.of(1), OptionalLong.of(4))) - .setNullsCount(0) - .setDistinctValuesCount(4) - .build()); + assertThat(actual).isEqualTo(HiveColumnStatistics.builder() + .setIntegerStatistics(new IntegerStatistics(OptionalLong.of(1), OptionalLong.of(4))) + .setNullsCount(0) + .setDistinctValuesCount(4) + .build()); } @Test @@ -79,20 +75,16 @@ public void testSparkDoubleStatsToColumnStatistics() "spark.sql.statistics.colStats.c_double.version", "2", "spark.sql.statistics.colStats.c_double.max", "3.3"); HiveColumnStatistics actualNotExists = fromMetastoreColumnStatistics(new FieldSchema("c_double_not_exists", DOUBLE_TYPE_NAME, null), columnStatistics, 10); - assertEquals( - actualNotExists, - HiveColumnStatistics.builder() - .setDoubleStatistics(new DoubleStatistics(OptionalDouble.empty(), OptionalDouble.empty())) - .build()); + assertThat(actualNotExists).isEqualTo(HiveColumnStatistics.builder() + .setDoubleStatistics(new DoubleStatistics(OptionalDouble.empty(), OptionalDouble.empty())) + .build()); HiveColumnStatistics actual = fromMetastoreColumnStatistics(new FieldSchema("c_double", DOUBLE_TYPE_NAME, null), columnStatistics, 10); - assertEquals( - actual, - HiveColumnStatistics.builder() - .setDoubleStatistics(new DoubleStatistics(OptionalDouble.of(0.3), OptionalDouble.of(3.3))) - .setNullsCount(1) - .setDistinctValuesCount(9) - .build()); + assertThat(actual).isEqualTo(HiveColumnStatistics.builder() + .setDoubleStatistics(new DoubleStatistics(OptionalDouble.of(0.3), OptionalDouble.of(3.3))) + .setNullsCount(1) + .setDistinctValuesCount(9) + .build()); } @Test @@ -106,20 +98,16 @@ public void testSparkDecimalStatsToColumnStatistics() "spark.sql.statistics.colStats.c_decimal.version", "2", "spark.sql.statistics.colStats.c_decimal.max", "3.3"); HiveColumnStatistics actualNotExists = fromMetastoreColumnStatistics(new FieldSchema("c_decimal_not_exists", DECIMAL_TYPE_NAME, null), columnStatistics, 10); - assertEquals( - actualNotExists, - HiveColumnStatistics.builder() - .setDecimalStatistics(new DecimalStatistics(Optional.empty(), Optional.empty())) - .build()); + assertThat(actualNotExists).isEqualTo(HiveColumnStatistics.builder() + .setDecimalStatistics(new DecimalStatistics(Optional.empty(), Optional.empty())) + .build()); HiveColumnStatistics actual = fromMetastoreColumnStatistics(new FieldSchema("c_decimal", DECIMAL_TYPE_NAME, null), columnStatistics, 10); - assertEquals( - actual, - HiveColumnStatistics.builder() - .setDecimalStatistics(new DecimalStatistics(Optional.of(new BigDecimal("0.3")), Optional.of(new BigDecimal("3.3")))) - .setNullsCount(1) - .setDistinctValuesCount(9) - .build()); + assertThat(actual).isEqualTo(HiveColumnStatistics.builder() + .setDecimalStatistics(new DecimalStatistics(Optional.of(new BigDecimal("0.3")), Optional.of(new BigDecimal("3.3")))) + .setNullsCount(1) + .setDistinctValuesCount(9) + .build()); } @Test @@ -133,19 +121,15 @@ public void testSparkBooleanStatsToColumnStatistics() "spark.sql.statistics.colStats.c_bool.version", "2", "spark.sql.statistics.colStats.c_bool.max", "true"); HiveColumnStatistics actualNotExists = fromMetastoreColumnStatistics(new FieldSchema("c_bool_not_exists", BOOLEAN_TYPE_NAME, null), columnStatistics, 10); - assertEquals( - actualNotExists, - HiveColumnStatistics.builder() - .setBooleanStatistics(new BooleanStatistics(OptionalLong.empty(), OptionalLong.empty())) - .build()); + assertThat(actualNotExists).isEqualTo(HiveColumnStatistics.builder() + .setBooleanStatistics(new BooleanStatistics(OptionalLong.empty(), OptionalLong.empty())) + .build()); HiveColumnStatistics actual = fromMetastoreColumnStatistics(new FieldSchema("c_bool", BOOLEAN_TYPE_NAME, null), columnStatistics, 10); - assertEquals( - actual, - HiveColumnStatistics.builder() - .setBooleanStatistics(new BooleanStatistics(OptionalLong.empty(), OptionalLong.empty())) - .setNullsCount(1) - .build()); + assertThat(actual).isEqualTo(HiveColumnStatistics.builder() + .setBooleanStatistics(new BooleanStatistics(OptionalLong.empty(), OptionalLong.empty())) + .setNullsCount(1) + .build()); } @Test @@ -159,20 +143,16 @@ public void testSparkDateStatsToColumnStatistics() "spark.sql.statistics.colStats.c_date.version", "2", "spark.sql.statistics.colStats.c_date.max", "2030-12-31"); HiveColumnStatistics actualNotExists = fromMetastoreColumnStatistics(new FieldSchema("c_date_not_exists", DATE_TYPE_NAME, null), columnStatistics, 10); - assertEquals( - actualNotExists, - HiveColumnStatistics.builder() - .setDateStatistics((new DateStatistics(Optional.empty(), Optional.empty()))) - .build()); + assertThat(actualNotExists).isEqualTo(HiveColumnStatistics.builder() + .setDateStatistics((new DateStatistics(Optional.empty(), Optional.empty()))) + .build()); HiveColumnStatistics actual = fromMetastoreColumnStatistics(new FieldSchema("c_date", DATE_TYPE_NAME, null), columnStatistics, 10); - assertEquals( - actual, - HiveColumnStatistics.builder() - .setDateStatistics((new DateStatistics(Optional.of(LocalDate.of(2000, 1, 1)), Optional.of(LocalDate.of(2030, 12, 31))))) - .setNullsCount(3) - .setDistinctValuesCount(7) - .build()); + assertThat(actual).isEqualTo(HiveColumnStatistics.builder() + .setDateStatistics((new DateStatistics(Optional.of(LocalDate.of(2000, 1, 1)), Optional.of(LocalDate.of(2030, 12, 31))))) + .setNullsCount(3) + .setDistinctValuesCount(7) + .build()); } @Test @@ -185,16 +165,14 @@ public void testSparkStringStatsToColumnStatistics() "spark.sql.statistics.colStats.c_char.nullCount", "7", "spark.sql.statistics.colStats.c_char.version", "2"); HiveColumnStatistics actualNotExists = fromMetastoreColumnStatistics(new FieldSchema("c_char_not_exists", STRING_TYPE_NAME, null), columnStatistics, 10); - assertEquals(actualNotExists, HiveColumnStatistics.builder().build()); + assertThat(actualNotExists).isEqualTo(HiveColumnStatistics.builder().build()); HiveColumnStatistics actual = fromMetastoreColumnStatistics(new FieldSchema("c_char", STRING_TYPE_NAME, null), columnStatistics, 10); - assertEquals( - actual, - HiveColumnStatistics.builder() - .setNullsCount(7) - .setDistinctValuesCount(3) - .setTotalSizeInBytes(30) - .build()); + assertThat(actual).isEqualTo(HiveColumnStatistics.builder() + .setNullsCount(7) + .setDistinctValuesCount(3) + .setTotalSizeInBytes(30) + .build()); } @Test @@ -207,15 +185,13 @@ public void testSparkBinaryStatsToColumnStatistics() "spark.sql.statistics.colStats.c_bin.nullCount", "3", "spark.sql.statistics.colStats.c_bin.version", "2"); HiveColumnStatistics actualNotExists = fromMetastoreColumnStatistics(new FieldSchema("c_bin_not_exists", BINARY_TYPE_NAME, null), columnStatistics, 10); - assertEquals(actualNotExists, HiveColumnStatistics.builder().build()); + assertThat(actualNotExists).isEqualTo(HiveColumnStatistics.builder().build()); HiveColumnStatistics actual = fromMetastoreColumnStatistics(new FieldSchema("c_bin", BINARY_TYPE_NAME, null), columnStatistics, 10); - assertEquals( - actual, - HiveColumnStatistics.builder() - .setNullsCount(3) - .setTotalSizeInBytes(70) - .setMaxValueSizeInBytes(10) - .build()); + assertThat(actual).isEqualTo(HiveColumnStatistics.builder() + .setNullsCount(3) + .setTotalSizeInBytes(70) + .setMaxValueSizeInBytes(10) + .build()); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/optimizer/TestHiveProjectionPushdownIntoTableScan.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/optimizer/TestHiveProjectionPushdownIntoTableScan.java index 679bce14b825..1de3d1f5d714 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/optimizer/TestHiveProjectionPushdownIntoTableScan.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/optimizer/TestHiveProjectionPushdownIntoTableScan.java @@ -55,7 +55,7 @@ import static io.trino.sql.planner.plan.JoinNode.Type.INNER; import static io.trino.testing.TestingSession.testSessionBuilder; import static java.lang.String.format; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestHiveProjectionPushdownIntoTableScan extends BasePushdownPlanTest @@ -131,7 +131,9 @@ public void testDereferencePushdown() Session session = getQueryRunner().getDefaultSession(); Optional tableHandle = getTableHandle(session, completeTableName); - assertTrue(tableHandle.isPresent(), "expected the table handle to be present"); + assertThat(tableHandle.isPresent()) + .describedAs("expected the table handle to be present") + .isTrue(); Map columns = getColumnHandles(session, completeTableName); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcDeleteDeltaPageSource.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcDeleteDeltaPageSource.java index 512b9b66f5a7..7fb1169ff5bc 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcDeleteDeltaPageSource.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcDeleteDeltaPageSource.java @@ -32,7 +32,7 @@ import static io.trino.plugin.hive.HiveTestUtils.SESSION; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.IntegerType.INTEGER; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestOrcDeleteDeltaPageSource { @@ -49,9 +49,9 @@ public void testReadingDeletedRows() ConnectorPageSource pageSource = pageSourceFactory.createPageSource(inputFile).orElseThrow(); MaterializedResult materializedRows = MaterializedResult.materializeSourceDataStream(SESSION, pageSource, ImmutableList.of(BIGINT, INTEGER, BIGINT)); - assertEquals(materializedRows.getRowCount(), 1); + assertThat(materializedRows.getRowCount()).isEqualTo(1); AcidOutputFormat.Options bucketOptions = new AcidOutputFormat.Options(new Configuration(false)).bucket(0); - assertEquals(materializedRows.getMaterializedRows().get(0), new MaterializedRow(5, 2L, BucketCodec.V1.encode(bucketOptions), 0L)); + assertThat(materializedRows.getMaterializedRows().get(0)).isEqualTo(new MaterializedRow(5, 2L, BucketCodec.V1.encode(bucketOptions), 0L)); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcDeletedRows.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcDeletedRows.java index 75da49ea9444..4250ed785dd6 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcDeletedRows.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcDeletedRows.java @@ -37,7 +37,7 @@ import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.testing.MaterializedResult.resultBuilder; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestOrcDeletedRows { @@ -75,13 +75,13 @@ public void testDeleteLocations() .build() .getOnlyColumnAsSet(); - assertEquals(validRows.size(), 8); - assertEquals(validRows, ImmutableSet.of(0L, 1L, 3L, 4L, 5L, 7L, 8L, 9L)); + assertThat(validRows.size()).isEqualTo(8); + assertThat(validRows).isEqualTo(ImmutableSet.of(0L, 1L, 3L, 4L, 5L, 7L, 8L, 9L)); // page with no deleted rows testPage = createTestPage(10, 20); block = deletedRows.getMaskDeletedRowsFunction(testPage, OptionalLong.empty()).apply(testPage.getBlock(1)); - assertEquals(block.getPositionCount(), 10); + assertThat(block.getPositionCount()).isEqualTo(10); } @Test @@ -104,13 +104,13 @@ public void testDeletedLocationsOriginalFiles() .build() .getOnlyColumnAsSet(); - assertEquals(validRows.size(), 7); - assertEquals(validRows, ImmutableSet.of(0L, 1L, 3L, 4L, 5L, 6L, 7L)); + assertThat(validRows.size()).isEqualTo(7); + assertThat(validRows).isEqualTo(ImmutableSet.of(0L, 1L, 3L, 4L, 5L, 6L, 7L)); // page with no deleted rows testPage = createTestPage(5, 9); block = deletedRows.getMaskDeletedRowsFunction(testPage, OptionalLong.empty()).apply(testPage.getBlock(1)); - assertEquals(block.getPositionCount(), 4); + assertThat(block.getPositionCount()).isEqualTo(4); } @Test @@ -129,13 +129,13 @@ public void testDeletedLocationsAfterMinorCompaction() .build() .getOnlyColumnAsSet(); - assertEquals(validRows.size(), 9); - assertEquals(validRows, ImmutableSet.of(0L, 1L, 3L, 4L, 5L, 6L, 7L, 8L, 9L)); + assertThat(validRows.size()).isEqualTo(9); + assertThat(validRows).isEqualTo(ImmutableSet.of(0L, 1L, 3L, 4L, 5L, 6L, 7L, 8L, 9L)); // page with no deleted rows testPage = createTestPage(10, 20); block = deletedRows.getMaskDeletedRowsFunction(testPage, OptionalLong.empty()).apply(testPage.getBlock(1)); - assertEquals(block.getPositionCount(), 10); + assertThat(block.getPositionCount()).isEqualTo(10); } private static void addDeleteDelta(AcidInfo.Builder acidInfoBuilder, long minWriteId, long maxWriteId, OptionalInt statementId, Location path) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPageSourceFactory.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPageSourceFactory.java index 2dcdd5e86dda..88521d96f1c1 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPageSourceFactory.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPageSourceFactory.java @@ -68,9 +68,8 @@ import static io.trino.tpch.NationColumn.NATION_KEY; import static io.trino.tpch.NationColumn.REGION_KEY; import static java.util.Collections.nCopies; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; public class TestOrcPageSourceFactory { @@ -143,7 +142,7 @@ public void testReadWithAcidVersionValidationHive3() .build(); List result = readFile(fileSystemFactory, Map.of(), OptionalLong.empty(), acidInfo, fileLocation, 625); - assertEquals(result.size(), 1); + assertThat(result.size()).isEqualTo(1); } @Test @@ -181,11 +180,12 @@ public void testFullFileReadOriginalFilesTable() List expected = expectedResult(OptionalLong.empty(), nationKey -> nationKey == 24, 1); List result = readFile(fileSystemFactory, ALL_COLUMNS, OptionalLong.empty(), Optional.of(acidInfo), fileLocation, 1780); - assertEquals(result.size(), expected.size()); + assertThat(result.size()).isEqualTo(expected.size()); int deletedRowKey = 24; String deletedRowNameColumn = "UNITED STATES"; - assertFalse(result.stream().anyMatch(acidNationRow -> acidNationRow.getName().equals(deletedRowNameColumn) && acidNationRow.getNationKey() == deletedRowKey), - "Deleted row shouldn't be present in the result"); + assertThat(result.stream().anyMatch(acidNationRow -> acidNationRow.getName().equals(deletedRowNameColumn) && acidNationRow.getNationKey() == deletedRowKey)) + .describedAs("Deleted row shouldn't be present in the result") + .isFalse(); } private static void assertRead(Map columns, OptionalLong nationKeyPredicate, Optional acidInfo, LongPredicate deletedRows) @@ -338,14 +338,16 @@ private static Map createSchema() private static void assertEqualsByColumns(Set columns, List actualRows, List expectedRows) { - assertEquals(actualRows.size(), expectedRows.size(), "row count"); + assertThat(actualRows.size()) + .describedAs("row count") + .isEqualTo(expectedRows.size()); for (int i = 0; i < actualRows.size(); i++) { Nation actual = actualRows.get(i); Nation expected = expectedRows.get(i); - assertEquals(actual.getNationKey(), columns.contains(NATION_KEY) ? expected.getNationKey() : -42); - assertEquals(actual.getName(), columns.contains(NAME) ? expected.getName() : ""); - assertEquals(actual.getRegionKey(), columns.contains(REGION_KEY) ? expected.getRegionKey() : -42); - assertEquals(actual.getComment(), columns.contains(COMMENT) ? expected.getComment() : ""); + assertThat(actual.getNationKey()).isEqualTo(columns.contains(NATION_KEY) ? expected.getNationKey() : -42); + assertThat(actual.getName()).isEqualTo(columns.contains(NAME) ? expected.getName() : ""); + assertThat(actual.getRegionKey()).isEqualTo(columns.contains(REGION_KEY) ? expected.getRegionKey() : -42); + assertThat(actual.getComment()).isEqualTo(columns.contains(COMMENT) ? expected.getComment() : ""); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPredicates.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPredicates.java index bf028866b7ea..e8a657ecb4e2 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPredicates.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/orc/TestOrcPredicates.java @@ -69,8 +69,8 @@ import static io.trino.spi.type.RowType.field; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; import static org.joda.time.DateTimeZone.UTC; -import static org.testng.Assert.assertEquals; class TestOrcPredicates { @@ -147,7 +147,7 @@ private static void assertFilteredRows( filteredRows += page.getPositionCount(); } } - assertEquals(filteredRows, expectedRows); + assertThat(filteredRows).isEqualTo(expectedRows); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/AbstractTestParquetReader.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/AbstractTestParquetReader.java index c8da88c636c1..21d6cb047918 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/AbstractTestParquetReader.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/AbstractTestParquetReader.java @@ -123,8 +123,8 @@ import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaStringObjectInspector; import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaTimestampObjectInspector; import static org.apache.parquet.schema.MessageTypeParser.parseMessageType; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public abstract class AbstractTestParquetReader { @@ -144,7 +144,7 @@ protected AbstractTestParquetReader(ParquetTester tester) @BeforeClass public void setUp() { - assertEquals(DateTimeZone.getDefault(), DateTimeZone.forID("America/Bahia_Banderas")); + assertThat(DateTimeZone.getDefault()).isEqualTo(DateTimeZone.forID("America/Bahia_Banderas")); // Parquet has excessive logging at INFO level parquetLogger = Logger.getLogger("org.apache.parquet.hadoop"); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetTester.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetTester.java index a21ce4788577..80de56d67d8e 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetTester.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/ParquetTester.java @@ -150,9 +150,7 @@ import static org.apache.parquet.hadoop.ParquetOutputFormat.COMPRESSION; import static org.apache.parquet.hadoop.ParquetOutputFormat.ENABLE_DICTIONARY; import static org.apache.parquet.hadoop.ParquetOutputFormat.WRITER_VERSION; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; class ParquetTester { @@ -468,7 +466,7 @@ void assertMaxReadBytes( expectedValues, pageSource, Optional.of(getParquetMaxReadBlockSize(session).toBytes())); - assertFalse(stream(expectedValues).allMatch(Iterator::hasNext)); + assertThat(stream(expectedValues).allMatch(Iterator::hasNext)).isFalse(); } } } @@ -492,7 +490,7 @@ private void assertFileContents( else { assertPageSource(columnTypes, expectedValues, pageSource); } - assertFalse(stream(expectedValues).allMatch(Iterator::hasNext)); + assertThat(stream(expectedValues).allMatch(Iterator::hasNext)).isFalse(); } } @@ -510,15 +508,15 @@ private static void assertPageSource(List types, Iterator[] valuesByFie } maxReadBlockSize.ifPresent(max -> - assertTrue(page.getPositionCount() == 1 || page.getSizeInBytes() <= max)); + assertThat(page.getPositionCount() == 1 || page.getSizeInBytes() <= max).isTrue()); for (int field = 0; field < page.getChannelCount(); field++) { Block block = page.getBlock(field); for (int i = 0; i < block.getPositionCount(); i++) { - assertTrue(valuesByField[field].hasNext()); + assertThat(valuesByField[field].hasNext()).isTrue(); Object expected = valuesByField[field].next(); Object actual = decodeObject(types.get(field), block, i); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } } } @@ -528,10 +526,10 @@ private static void assertRecordCursor(List types, Iterator[] valuesByF { while (cursor.advanceNextPosition()) { for (int field = 0; field < types.size(); field++) { - assertTrue(valuesByField[field].hasNext()); + assertThat(valuesByField[field].hasNext()).isTrue(); Object expected = valuesByField[field].next(); Object actual = getActualCursorValue(cursor, types.get(field), field); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestBloomFilterStore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestBloomFilterStore.java index c52ad23ff6e7..4c168c2b0247 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestBloomFilterStore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestBloomFilterStore.java @@ -82,9 +82,8 @@ import static org.apache.parquet.hadoop.metadata.ColumnPath.fromDotString; import static org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName.BINARY; import static org.apache.parquet.schema.Type.Repetition.REQUIRED; +import static org.assertj.core.api.Assertions.assertThat; import static org.joda.time.DateTimeZone.UTC; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestBloomFilterStore { @@ -179,20 +178,20 @@ public void testReadBloomFilter(BloomFilterTypeTestCase typeTestCase) { try (ParquetTester.TempFile tempFile = new ParquetTester.TempFile("testbloomfilter", ".parquet")) { BloomFilterStore bloomFilterEnabled = generateBloomFilterStore(tempFile, true, typeTestCase.writeValues, typeTestCase.objectInspector); - assertTrue(bloomFilterEnabled.getBloomFilter(fromDotString(COLUMN_NAME)).isPresent()); + assertThat(bloomFilterEnabled.getBloomFilter(fromDotString(COLUMN_NAME)).isPresent()).isTrue(); BloomFilter bloomFilter = bloomFilterEnabled.getBloomFilter(fromDotString(COLUMN_NAME)).get(); for (Object data : typeTestCase.matchingValues) { - assertTrue(TupleDomainParquetPredicate.checkInBloomFilter(bloomFilter, data, typeTestCase.sqlType)); + assertThat(TupleDomainParquetPredicate.checkInBloomFilter(bloomFilter, data, typeTestCase.sqlType)).isTrue(); } for (Object data : typeTestCase.nonMatchingValues) { - assertFalse(TupleDomainParquetPredicate.checkInBloomFilter(bloomFilter, data, typeTestCase.sqlType)); + assertThat(TupleDomainParquetPredicate.checkInBloomFilter(bloomFilter, data, typeTestCase.sqlType)).isFalse(); } } try (ParquetTester.TempFile tempFile = new ParquetTester.TempFile("testbloomfilter", ".parquet")) { BloomFilterStore bloomFilterNotEnabled = generateBloomFilterStore(tempFile, false, typeTestCase.writeValues, typeTestCase.objectInspector); - assertTrue(bloomFilterNotEnabled.getBloomFilter(fromDotString(COLUMN_NAME)).isEmpty()); + assertThat(bloomFilterNotEnabled.getBloomFilter(fromDotString(COLUMN_NAME)).isEmpty()).isTrue(); } } @@ -206,18 +205,18 @@ public void testMatchesWithBloomFilter(BloomFilterTypeTestCase typeTestCase) TupleDomain domain = withColumnDomains(singletonMap(columnDescriptor, multipleValues(typeTestCase.sqlType, typeTestCase.matchingValues))); TupleDomainParquetPredicate parquetPredicate = new TupleDomainParquetPredicate(domain, singletonList(columnDescriptor), UTC); // bloomfilter store has the column, and values match - assertTrue(parquetPredicate.matches(bloomFilterStore, DOMAIN_COMPACTION_THRESHOLD)); + assertThat(parquetPredicate.matches(bloomFilterStore, DOMAIN_COMPACTION_THRESHOLD)).isTrue(); TupleDomain domainWithoutMatch = withColumnDomains(singletonMap(columnDescriptor, multipleValues(typeTestCase.sqlType, typeTestCase.nonMatchingValues))); TupleDomainParquetPredicate parquetPredicateWithoutMatch = new TupleDomainParquetPredicate(domainWithoutMatch, singletonList(columnDescriptor), UTC); // bloomfilter store has the column, but values not match - assertFalse(parquetPredicateWithoutMatch.matches(bloomFilterStore, DOMAIN_COMPACTION_THRESHOLD)); + assertThat(parquetPredicateWithoutMatch.matches(bloomFilterStore, DOMAIN_COMPACTION_THRESHOLD)).isFalse(); ColumnDescriptor columnDescriptor = new ColumnDescriptor(new String[] {"non_exist_path"}, Types.optional(BINARY).named("Test column"), 0, 0); TupleDomain domainForColumnWithoutBloomFilter = withColumnDomains(singletonMap(columnDescriptor, multipleValues(typeTestCase.sqlType, typeTestCase.nonMatchingValues))); TupleDomainParquetPredicate predicateForColumnWithoutBloomFilter = new TupleDomainParquetPredicate(domainForColumnWithoutBloomFilter, singletonList(columnDescriptor), UTC); // bloomfilter store does not have the column - assertTrue(predicateForColumnWithoutBloomFilter.matches(bloomFilterStore, DOMAIN_COMPACTION_THRESHOLD)); + assertThat(predicateForColumnWithoutBloomFilter.matches(bloomFilterStore, DOMAIN_COMPACTION_THRESHOLD)).isTrue(); } } @@ -232,19 +231,19 @@ public void testMatchesWithBloomFilterExpand() TupleDomain domain = TupleDomain.withColumnDomains(singletonMap(columnDescriptor, Domain.create(SortedRangeSet.copyOf(INTEGER, ImmutableList.of(Range.range(INTEGER, 60L, true, 68L, true))), false))); TupleDomainParquetPredicate parquetPredicate = new TupleDomainParquetPredicate(domain, singletonList(columnDescriptor), UTC); - assertTrue(parquetPredicate.matches(bloomFilterStore, DOMAIN_COMPACTION_THRESHOLD)); + assertThat(parquetPredicate.matches(bloomFilterStore, DOMAIN_COMPACTION_THRESHOLD)).isTrue(); // case 2, bloomfilter store does not have the column, but ranges exceeded DOMAIN_COMPACTION_THRESHOLD domain = TupleDomain.withColumnDomains(singletonMap(columnDescriptor, Domain.create(SortedRangeSet.copyOf(INTEGER, ImmutableList.of(Range.range(INTEGER, -68L, true, 0L, true))), false))); parquetPredicate = new TupleDomainParquetPredicate(domain, singletonList(columnDescriptor), UTC); - assertTrue(parquetPredicate.matches(bloomFilterStore, DOMAIN_COMPACTION_THRESHOLD)); + assertThat(parquetPredicate.matches(bloomFilterStore, DOMAIN_COMPACTION_THRESHOLD)).isTrue(); // case 3, bloomfilter store has the column, and ranges expanded successfully but does not overlap domain = TupleDomain.withColumnDomains(singletonMap(columnDescriptor, Domain.create(SortedRangeSet.copyOf(INTEGER, ImmutableList.of(Range.range(INTEGER, -68L, true, -60L, true))), false))); parquetPredicate = new TupleDomainParquetPredicate(domain, singletonList(columnDescriptor), UTC); - assertFalse(parquetPredicate.matches(bloomFilterStore, DOMAIN_COMPACTION_THRESHOLD)); + assertThat(parquetPredicate.matches(bloomFilterStore, DOMAIN_COMPACTION_THRESHOLD)).isFalse(); } } @@ -260,13 +259,13 @@ public void testMatchesWithBloomFilterNullValues() ImmutableList.of(Range.range(INTEGER, 60L, true, 68L, true))), false))); TupleDomainParquetPredicate parquetPredicate = new TupleDomainParquetPredicate(domain, singletonList(columnDescriptor), UTC); // bloomfilter store has the column, and ranges overlap - assertTrue(parquetPredicate.matches(bloomFilterStore, DOMAIN_COMPACTION_THRESHOLD)); + assertThat(parquetPredicate.matches(bloomFilterStore, DOMAIN_COMPACTION_THRESHOLD)).isTrue(); TupleDomain domainWithoutMatch = TupleDomain.withColumnDomains(singletonMap(columnDescriptor, Domain.create(SortedRangeSet.copyOf(INTEGER, ImmutableList.of(Range.range(INTEGER, -68L, true, -60L, true))), false))); // bloomfilter store has the column, but ranges not overlap TupleDomainParquetPredicate parquetPredicateWithoutMatch = new TupleDomainParquetPredicate(domainWithoutMatch, singletonList(columnDescriptor), UTC); - assertFalse(parquetPredicateWithoutMatch.matches(bloomFilterStore, DOMAIN_COMPACTION_THRESHOLD)); + assertThat(parquetPredicateWithoutMatch.matches(bloomFilterStore, DOMAIN_COMPACTION_THRESHOLD)).isFalse(); } } @@ -281,7 +280,7 @@ public void testMatchesWithBloomFilterNullPredicate() TupleDomain domainWithoutMatch = TupleDomain.withColumnDomains(singletonMap(columnDescriptor, Domain.create(SortedRangeSet.copyOf(INTEGER, ImmutableList.of(Range.range(INTEGER, -68L, true, -60L, true))), true))); TupleDomainParquetPredicate parquetPredicateWithoutMatch = new TupleDomainParquetPredicate(domainWithoutMatch, singletonList(columnDescriptor), UTC); - assertTrue(parquetPredicateWithoutMatch.matches(bloomFilterStore, DOMAIN_COMPACTION_THRESHOLD)); + assertThat(parquetPredicateWithoutMatch.matches(bloomFilterStore, DOMAIN_COMPACTION_THRESHOLD)).isTrue(); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestParquetPageSourceFactory.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestParquetPageSourceFactory.java index 8c8b492ec843..2a24941ea5c0 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestParquetPageSourceFactory.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestParquetPageSourceFactory.java @@ -33,7 +33,7 @@ import static org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName.INT32; import static org.apache.parquet.schema.Type.Repetition.OPTIONAL; import static org.apache.parquet.schema.Type.Repetition.REQUIRED; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestParquetPageSourceFactory { @@ -64,9 +64,7 @@ public void testGetNestedMixedRepetitionColumnType(boolean useColumnNames) new GroupType(OPTIONAL, "optional_level1", new GroupType(OPTIONAL, "optional_level2", new PrimitiveType(REQUIRED, INT32, "required_level3")))); - assertEquals( - ParquetPageSourceFactory.getColumnType(columnHandle, fileSchema, useColumnNames).get(), - fileSchema.getType("optional_level1")); + assertThat(ParquetPageSourceFactory.getColumnType(columnHandle, fileSchema, useColumnNames).get()).isEqualTo(fileSchema.getType("optional_level1")); } @DataProvider diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestamp.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestamp.java index 8f7a42295e52..12fb581a0463 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestamp.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/TestTimestamp.java @@ -55,8 +55,6 @@ import static org.apache.parquet.hadoop.ParquetOutputFormat.WRITER_VERSION; import static org.apache.parquet.schema.MessageTypeParser.parseMessageType; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestTimestamp { @@ -131,7 +129,9 @@ private static void testReadingAs(Type type, ConnectorSession session, ParquetTe try (ConnectorPageSource pageSource = ParquetUtil.createPageSource(session, tempFile.getFile(), columnNames, ImmutableList.of(type))) { // skip a page to exercise the decoder's skip() logic Page firstPage = pageSource.getNextPage(); - assertTrue(firstPage.getPositionCount() > 0, "Expected first page to have at least 1 row"); + assertThat(firstPage.getPositionCount() > 0) + .describedAs("Expected first page to have at least 1 row") + .isTrue(); for (int i = 0; i < firstPage.getPositionCount(); i++) { expected.next(); @@ -155,7 +155,9 @@ private static void testReadingAs(Type type, ConnectorSession session, ParquetTe .withFailMessage("Expected more than one page but processed %s", pageCount) .isGreaterThan(1); - assertFalse(expected.hasNext(), "Read fewer values than expected"); + assertThat(expected.hasNext()) + .describedAs("Read fewer values than expected") + .isFalse(); } } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/predicate/TestParquetPredicateUtils.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/predicate/TestParquetPredicateUtils.java index 05806e27aa3b..3809418191db 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/predicate/TestParquetPredicateUtils.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/predicate/TestParquetPredicateUtils.java @@ -49,8 +49,7 @@ import static org.apache.parquet.schema.Type.Repetition.OPTIONAL; import static org.apache.parquet.schema.Type.Repetition.REPEATED; import static org.apache.parquet.schema.Type.Repetition.REQUIRED; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestParquetPredicateUtils { @@ -66,7 +65,7 @@ public void testParquetTupleDomainPrimitiveArray(boolean useColumnNames) Map, ColumnDescriptor> descriptorsByPath = getDescriptors(fileSchema, fileSchema); TupleDomain tupleDomain = getParquetTupleDomain(descriptorsByPath, domain, fileSchema, useColumnNames); - assertTrue(tupleDomain.isAll()); + assertThat(tupleDomain.isAll()).isTrue(); } @Test(dataProvider = "useColumnNames") @@ -86,7 +85,7 @@ public void testParquetTupleDomainStructArray(boolean useColumnNames) Map, ColumnDescriptor> descriptorsByPath = getDescriptors(fileSchema, fileSchema); TupleDomain tupleDomain = getParquetTupleDomain(descriptorsByPath, domain, fileSchema, useColumnNames); - assertTrue(tupleDomain.isAll()); + assertThat(tupleDomain.isAll()).isTrue(); } @Test(dataProvider = "useColumnNames") @@ -101,13 +100,13 @@ public void testParquetTupleDomainPrimitive(boolean useColumnNames) Map, ColumnDescriptor> descriptorsByPath = getDescriptors(fileSchema, fileSchema); TupleDomain tupleDomain = getParquetTupleDomain(descriptorsByPath, domain, fileSchema, useColumnNames); - assertEquals(tupleDomain.getDomains().get().size(), 1); + assertThat(tupleDomain.getDomains().get().size()).isEqualTo(1); ColumnDescriptor descriptor = tupleDomain.getDomains().get().keySet().iterator().next(); - assertEquals(descriptor.getPath().length, 1); - assertEquals(descriptor.getPath()[0], "my_primitive"); + assertThat(descriptor.getPath().length).isEqualTo(1); + assertThat(descriptor.getPath()[0]).isEqualTo("my_primitive"); Domain predicateDomain = Iterables.getOnlyElement(tupleDomain.getDomains().get().values()); - assertEquals(predicateDomain, singleValueDomain); + assertThat(predicateDomain).isEqualTo(singleValueDomain); } @Test(dataProvider = "useColumnNames") @@ -127,7 +126,7 @@ public void testParquetTupleDomainStruct(boolean useColumnNames) new PrimitiveType(OPTIONAL, INT32, "c"))); Map, ColumnDescriptor> descriptorsByPath = getDescriptors(fileSchema, fileSchema); TupleDomain tupleDomain = getParquetTupleDomain(descriptorsByPath, domain, fileSchema, useColumnNames); - assertTrue(tupleDomain.isAll()); + assertThat(tupleDomain.isAll()).isTrue(); } @Test(dataProvider = "useColumnNames") @@ -163,9 +162,9 @@ public void testParquetTupleDomainStructWithPrimitiveColumnPredicate(boolean use new PrimitiveType(OPTIONAL, INT32, "c"))); Map, ColumnDescriptor> descriptorsByPath = getDescriptors(fileSchema, fileSchema); TupleDomain calculatedTupleDomain = getParquetTupleDomain(descriptorsByPath, tupleDomain, fileSchema, useColumNames); - assertEquals(calculatedTupleDomain.getDomains().get().size(), 1); + assertThat(calculatedTupleDomain.getDomains().get().size()).isEqualTo(1); ColumnDescriptor selectedColumnDescriptor = descriptorsByPath.get(ImmutableList.of("row_field", "b")); - assertEquals(calculatedTupleDomain.getDomains().get().get(selectedColumnDescriptor), predicateDomain); + assertThat(calculatedTupleDomain.getDomains().get().get(selectedColumnDescriptor)).isEqualTo(predicateDomain); } @Test(dataProvider = "useColumnNames") @@ -208,7 +207,7 @@ public void testParquetTupleDomainStructWithComplexColumnPredicate(boolean useCo Map, ColumnDescriptor> descriptorsByPath = getDescriptors(fileSchema, fileSchema); // skip looking up predicates for complex types as Parquet only stores stats for primitives TupleDomain calculatedTupleDomain = getParquetTupleDomain(descriptorsByPath, tupleDomain, fileSchema, useColumNames); - assertTrue(calculatedTupleDomain.isAll()); + assertThat(calculatedTupleDomain.isAll()).isTrue(); } @Test(dataProvider = "useColumnNames") @@ -243,7 +242,7 @@ public void testParquetTupleDomainStructWithMissingPrimitiveColumn(boolean useCo new PrimitiveType(OPTIONAL, INT32, "b"))); Map, ColumnDescriptor> descriptorsByPath = getDescriptors(fileSchema, fileSchema); TupleDomain calculatedTupleDomain = getParquetTupleDomain(descriptorsByPath, tupleDomain, fileSchema, useColumnNames); - assertTrue(calculatedTupleDomain.isAll()); + assertThat(calculatedTupleDomain.isAll()).isTrue(); } @Test(dataProvider = "useColumnNames") @@ -263,7 +262,7 @@ public void testParquetTupleDomainMap(boolean useColumnNames) Map, ColumnDescriptor> descriptorsByPath = getDescriptors(fileSchema, fileSchema); TupleDomain tupleDomain = getParquetTupleDomain(descriptorsByPath, domain, fileSchema, useColumnNames); - assertTrue(tupleDomain.isAll()); + assertThat(tupleDomain.isAll()).isTrue(); } @DataProvider diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/statistics/TestMetastoreHiveStatisticsProvider.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/statistics/TestMetastoreHiveStatisticsProvider.java index 5896b6e7d61d..a9c984500617 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/statistics/TestMetastoreHiveStatisticsProvider.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/statistics/TestMetastoreHiveStatisticsProvider.java @@ -90,7 +90,6 @@ import static java.util.Collections.nCopies; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestMetastoreHiveStatisticsProvider { @@ -111,13 +110,13 @@ public void testGetPartitionsSample() HivePartition p4 = partition("p1=string4/p2=4567"); HivePartition p5 = partition("p1=string5/p2=5678"); - assertEquals(getPartitionsSample(ImmutableList.of(p1), 1), ImmutableList.of(p1)); - assertEquals(getPartitionsSample(ImmutableList.of(p1), 2), ImmutableList.of(p1)); - assertEquals(getPartitionsSample(ImmutableList.of(p1, p2), 2), ImmutableList.of(p1, p2)); - assertEquals(getPartitionsSample(ImmutableList.of(p1, p2, p3), 2), ImmutableList.of(p1, p3)); - assertEquals(getPartitionsSample(ImmutableList.of(p1, p2, p3, p4), 1), getPartitionsSample(ImmutableList.of(p1, p2, p3, p4), 1)); - assertEquals(getPartitionsSample(ImmutableList.of(p1, p2, p3, p4), 3), getPartitionsSample(ImmutableList.of(p1, p2, p3, p4), 3)); - assertEquals(getPartitionsSample(ImmutableList.of(p1, p2, p3, p4, p5), 3), ImmutableList.of(p1, p5, p4)); + assertThat(getPartitionsSample(ImmutableList.of(p1), 1)).isEqualTo(ImmutableList.of(p1)); + assertThat(getPartitionsSample(ImmutableList.of(p1), 2)).isEqualTo(ImmutableList.of(p1)); + assertThat(getPartitionsSample(ImmutableList.of(p1, p2), 2)).isEqualTo(ImmutableList.of(p1, p2)); + assertThat(getPartitionsSample(ImmutableList.of(p1, p2, p3), 2)).isEqualTo(ImmutableList.of(p1, p3)); + assertThat(getPartitionsSample(ImmutableList.of(p1, p2, p3, p4), 1)).isEqualTo(getPartitionsSample(ImmutableList.of(p1, p2, p3, p4), 1)); + assertThat(getPartitionsSample(ImmutableList.of(p1, p2, p3, p4), 3)).isEqualTo(getPartitionsSample(ImmutableList.of(p1, p2, p3, p4), 3)); + assertThat(getPartitionsSample(ImmutableList.of(p1, p2, p3, p4, p5), 3)).isEqualTo(ImmutableList.of(p1, p5, p4)); } @Test @@ -277,207 +276,153 @@ public void testCalculatePartitionsRowCount() @Test public void testCalculateDistinctPartitionKeys() { - assertEquals(calculateDistinctPartitionKeys(PARTITION_COLUMN_1, ImmutableList.of()), 0); - assertEquals( - calculateDistinctPartitionKeys( - PARTITION_COLUMN_1, - ImmutableList.of(partition("p1=string1/p2=1234"))), - 1); - assertEquals( - calculateDistinctPartitionKeys( - PARTITION_COLUMN_1, - ImmutableList.of(partition("p1=string1/p2=1234"), partition("p1=string2/p2=1234"))), - 2); - assertEquals( - calculateDistinctPartitionKeys( - PARTITION_COLUMN_2, - ImmutableList.of(partition("p1=string1/p2=1234"), partition("p1=string2/p2=1234"))), - 1); - assertEquals( - calculateDistinctPartitionKeys( - PARTITION_COLUMN_2, - ImmutableList.of(partition("p1=string1/p2=1234"), partition("p1=string1/p2=1235"))), - 2); - assertEquals( - calculateDistinctPartitionKeys( - PARTITION_COLUMN_1, - ImmutableList.of(partition("p1=__HIVE_DEFAULT_PARTITION__/p2=1234"), partition("p1=string1/p2=1235"))), - 1); - assertEquals( - calculateDistinctPartitionKeys( - PARTITION_COLUMN_2, - ImmutableList.of(partition("p1=123/p2=__HIVE_DEFAULT_PARTITION__"), partition("p1=string1/p2=1235"))), - 1); - assertEquals( - calculateDistinctPartitionKeys( - PARTITION_COLUMN_2, - ImmutableList.of(partition("p1=123/p2=__HIVE_DEFAULT_PARTITION__"), partition("p1=string1/p2=__HIVE_DEFAULT_PARTITION__"))), - 0); + assertThat(calculateDistinctPartitionKeys(PARTITION_COLUMN_1, ImmutableList.of())).isEqualTo(0); + assertThat(calculateDistinctPartitionKeys( + PARTITION_COLUMN_1, + ImmutableList.of(partition("p1=string1/p2=1234")))).isEqualTo(1); + assertThat(calculateDistinctPartitionKeys( + PARTITION_COLUMN_1, + ImmutableList.of(partition("p1=string1/p2=1234"), partition("p1=string2/p2=1234")))).isEqualTo(2); + assertThat(calculateDistinctPartitionKeys( + PARTITION_COLUMN_2, + ImmutableList.of(partition("p1=string1/p2=1234"), partition("p1=string2/p2=1234")))).isEqualTo(1); + assertThat(calculateDistinctPartitionKeys( + PARTITION_COLUMN_2, + ImmutableList.of(partition("p1=string1/p2=1234"), partition("p1=string1/p2=1235")))).isEqualTo(2); + assertThat(calculateDistinctPartitionKeys( + PARTITION_COLUMN_1, + ImmutableList.of(partition("p1=__HIVE_DEFAULT_PARTITION__/p2=1234"), partition("p1=string1/p2=1235")))).isEqualTo(1); + assertThat(calculateDistinctPartitionKeys( + PARTITION_COLUMN_2, + ImmutableList.of(partition("p1=123/p2=__HIVE_DEFAULT_PARTITION__"), partition("p1=string1/p2=1235")))).isEqualTo(1); + assertThat(calculateDistinctPartitionKeys( + PARTITION_COLUMN_2, + ImmutableList.of(partition("p1=123/p2=__HIVE_DEFAULT_PARTITION__"), partition("p1=string1/p2=__HIVE_DEFAULT_PARTITION__")))).isEqualTo(0); } @Test public void testCalculateNullsFractionForPartitioningKey() { - assertEquals( - calculateNullsFractionForPartitioningKey( - PARTITION_COLUMN_1, - ImmutableList.of(partition("p1=string1/p2=1234")), - ImmutableMap.of("p1=string1/p2=1234", rowsCount(1000)), - 2000, - 0), - 0.0); - assertEquals( - calculateNullsFractionForPartitioningKey( - PARTITION_COLUMN_1, - ImmutableList.of(partition("p1=string1/p2=1234")), - ImmutableMap.of("p1=string1/p2=1234", rowsCount(1000)), - 2000, - 4000), - 0.0); - assertEquals( - calculateNullsFractionForPartitioningKey( - PARTITION_COLUMN_1, - ImmutableList.of(partition("p1=__HIVE_DEFAULT_PARTITION__/p2=1234")), - ImmutableMap.of("p1=__HIVE_DEFAULT_PARTITION__/p2=1234", rowsCount(1000)), - 2000, - 4000), - 0.25); - assertEquals( - calculateNullsFractionForPartitioningKey( - PARTITION_COLUMN_1, - ImmutableList.of(partition("p1=__HIVE_DEFAULT_PARTITION__/p2=1234")), - ImmutableMap.of("p1=__HIVE_DEFAULT_PARTITION__/p2=1234", PartitionStatistics.empty()), - 2000, - 4000), - 0.5); - assertEquals( - calculateNullsFractionForPartitioningKey( - PARTITION_COLUMN_1, - ImmutableList.of(partition("p1=__HIVE_DEFAULT_PARTITION__/p2=1234")), - ImmutableMap.of(), - 2000, - 4000), - 0.5); - assertEquals( - calculateNullsFractionForPartitioningKey( - PARTITION_COLUMN_1, - ImmutableList.of(partition("p1=__HIVE_DEFAULT_PARTITION__/p2=1234"), partition("p1=__HIVE_DEFAULT_PARTITION__/p2=4321")), - ImmutableMap.of("p1=__HIVE_DEFAULT_PARTITION__/p2=1234", rowsCount(1000), "p1=__HIVE_DEFAULT_PARTITION__/p2=4321", rowsCount(2000)), - 3000, - 4000), - 0.75); - assertEquals( - calculateNullsFractionForPartitioningKey( - PARTITION_COLUMN_1, - ImmutableList.of(partition("p1=__HIVE_DEFAULT_PARTITION__/p2=1234"), partition("p1=__HIVE_DEFAULT_PARTITION__/p2=4321")), - ImmutableMap.of("p1=__HIVE_DEFAULT_PARTITION__/p2=1234", rowsCount(1000), "p1=__HIVE_DEFAULT_PARTITION__/p2=4321", PartitionStatistics.empty()), - 3000, - 4000), - 1.0); - assertEquals( - calculateNullsFractionForPartitioningKey( - PARTITION_COLUMN_1, - ImmutableList.of(partition("p1=__HIVE_DEFAULT_PARTITION__/p2=1234"), partition("p1=__HIVE_DEFAULT_PARTITION__/p2=4321")), - ImmutableMap.of("p1=__HIVE_DEFAULT_PARTITION__/p2=1234", rowsCount(1000), "p1=__HIVE_DEFAULT_PARTITION__/p2=4321", PartitionStatistics.empty()), - 4000, - 4000), - 1.0); + assertThat(calculateNullsFractionForPartitioningKey( + PARTITION_COLUMN_1, + ImmutableList.of(partition("p1=string1/p2=1234")), + ImmutableMap.of("p1=string1/p2=1234", rowsCount(1000)), + 2000, + 0)).isEqualTo(0.0); + assertThat(calculateNullsFractionForPartitioningKey( + PARTITION_COLUMN_1, + ImmutableList.of(partition("p1=string1/p2=1234")), + ImmutableMap.of("p1=string1/p2=1234", rowsCount(1000)), + 2000, + 4000)).isEqualTo(0.0); + assertThat(calculateNullsFractionForPartitioningKey( + PARTITION_COLUMN_1, + ImmutableList.of(partition("p1=__HIVE_DEFAULT_PARTITION__/p2=1234")), + ImmutableMap.of("p1=__HIVE_DEFAULT_PARTITION__/p2=1234", rowsCount(1000)), + 2000, + 4000)).isEqualTo(0.25); + assertThat(calculateNullsFractionForPartitioningKey( + PARTITION_COLUMN_1, + ImmutableList.of(partition("p1=__HIVE_DEFAULT_PARTITION__/p2=1234")), + ImmutableMap.of("p1=__HIVE_DEFAULT_PARTITION__/p2=1234", PartitionStatistics.empty()), + 2000, + 4000)).isEqualTo(0.5); + assertThat(calculateNullsFractionForPartitioningKey( + PARTITION_COLUMN_1, + ImmutableList.of(partition("p1=__HIVE_DEFAULT_PARTITION__/p2=1234")), + ImmutableMap.of(), + 2000, + 4000)).isEqualTo(0.5); + assertThat(calculateNullsFractionForPartitioningKey( + PARTITION_COLUMN_1, + ImmutableList.of(partition("p1=__HIVE_DEFAULT_PARTITION__/p2=1234"), partition("p1=__HIVE_DEFAULT_PARTITION__/p2=4321")), + ImmutableMap.of("p1=__HIVE_DEFAULT_PARTITION__/p2=1234", rowsCount(1000), "p1=__HIVE_DEFAULT_PARTITION__/p2=4321", rowsCount(2000)), + 3000, + 4000)).isEqualTo(0.75); + assertThat(calculateNullsFractionForPartitioningKey( + PARTITION_COLUMN_1, + ImmutableList.of(partition("p1=__HIVE_DEFAULT_PARTITION__/p2=1234"), partition("p1=__HIVE_DEFAULT_PARTITION__/p2=4321")), + ImmutableMap.of("p1=__HIVE_DEFAULT_PARTITION__/p2=1234", rowsCount(1000), "p1=__HIVE_DEFAULT_PARTITION__/p2=4321", PartitionStatistics.empty()), + 3000, + 4000)).isEqualTo(1.0); + assertThat(calculateNullsFractionForPartitioningKey( + PARTITION_COLUMN_1, + ImmutableList.of(partition("p1=__HIVE_DEFAULT_PARTITION__/p2=1234"), partition("p1=__HIVE_DEFAULT_PARTITION__/p2=4321")), + ImmutableMap.of("p1=__HIVE_DEFAULT_PARTITION__/p2=1234", rowsCount(1000), "p1=__HIVE_DEFAULT_PARTITION__/p2=4321", PartitionStatistics.empty()), + 4000, + 4000)).isEqualTo(1.0); } @Test public void testCalculateDataSizeForPartitioningKey() { - assertEquals( - calculateDataSizeForPartitioningKey( - PARTITION_COLUMN_2, - BIGINT, - ImmutableList.of(partition("p1=string1/p2=1234")), - ImmutableMap.of("p1=string1/p2=1234", rowsCount(1000)), - 2000), - Estimate.unknown()); - assertEquals( - calculateDataSizeForPartitioningKey( - PARTITION_COLUMN_1, - VARCHAR, - ImmutableList.of(partition("p1=string1/p2=1234")), - ImmutableMap.of("p1=string1/p2=1234", rowsCount(1000)), - 2000), - Estimate.of(7000)); - assertEquals( - calculateDataSizeForPartitioningKey( - PARTITION_COLUMN_1, - VARCHAR, - ImmutableList.of(partition("p1=string1/p2=1234")), - ImmutableMap.of("p1=string1/p2=1234", PartitionStatistics.empty()), - 2000), - Estimate.of(14000)); - assertEquals( - calculateDataSizeForPartitioningKey( - PARTITION_COLUMN_1, - VARCHAR, - ImmutableList.of(partition("p1=string1/p2=1234"), partition("p1=str2/p2=1234")), - ImmutableMap.of("p1=string1/p2=1234", rowsCount(1000), "p1=str2/p2=1234", rowsCount(2000)), - 3000), - Estimate.of(15000)); - assertEquals( - calculateDataSizeForPartitioningKey( - PARTITION_COLUMN_1, - VARCHAR, - ImmutableList.of(partition("p1=string1/p2=1234"), partition("p1=str2/p2=1234")), - ImmutableMap.of("p1=string1/p2=1234", rowsCount(1000), "p1=str2/p2=1234", PartitionStatistics.empty()), - 3000), - Estimate.of(19000)); - assertEquals( - calculateDataSizeForPartitioningKey( - PARTITION_COLUMN_1, - VARCHAR, - ImmutableList.of(partition("p1=string1/p2=1234"), partition("p1=str2/p2=1234")), - ImmutableMap.of(), - 3000), - Estimate.of(33000)); - assertEquals( - calculateDataSizeForPartitioningKey( - PARTITION_COLUMN_1, - VARCHAR, - ImmutableList.of(partition("p1=__HIVE_DEFAULT_PARTITION__/p2=1234"), partition("p1=str2/p2=1234")), - ImmutableMap.of(), - 3000), - Estimate.of(12000)); + assertThat(calculateDataSizeForPartitioningKey( + PARTITION_COLUMN_2, + BIGINT, + ImmutableList.of(partition("p1=string1/p2=1234")), + ImmutableMap.of("p1=string1/p2=1234", rowsCount(1000)), + 2000)).isEqualTo(Estimate.unknown()); + assertThat(calculateDataSizeForPartitioningKey( + PARTITION_COLUMN_1, + VARCHAR, + ImmutableList.of(partition("p1=string1/p2=1234")), + ImmutableMap.of("p1=string1/p2=1234", rowsCount(1000)), + 2000)).isEqualTo(Estimate.of(7000)); + assertThat(calculateDataSizeForPartitioningKey( + PARTITION_COLUMN_1, + VARCHAR, + ImmutableList.of(partition("p1=string1/p2=1234")), + ImmutableMap.of("p1=string1/p2=1234", PartitionStatistics.empty()), + 2000)).isEqualTo(Estimate.of(14000)); + assertThat(calculateDataSizeForPartitioningKey( + PARTITION_COLUMN_1, + VARCHAR, + ImmutableList.of(partition("p1=string1/p2=1234"), partition("p1=str2/p2=1234")), + ImmutableMap.of("p1=string1/p2=1234", rowsCount(1000), "p1=str2/p2=1234", rowsCount(2000)), + 3000)).isEqualTo(Estimate.of(15000)); + assertThat(calculateDataSizeForPartitioningKey( + PARTITION_COLUMN_1, + VARCHAR, + ImmutableList.of(partition("p1=string1/p2=1234"), partition("p1=str2/p2=1234")), + ImmutableMap.of("p1=string1/p2=1234", rowsCount(1000), "p1=str2/p2=1234", PartitionStatistics.empty()), + 3000)).isEqualTo(Estimate.of(19000)); + assertThat(calculateDataSizeForPartitioningKey( + PARTITION_COLUMN_1, + VARCHAR, + ImmutableList.of(partition("p1=string1/p2=1234"), partition("p1=str2/p2=1234")), + ImmutableMap.of(), + 3000)).isEqualTo(Estimate.of(33000)); + assertThat(calculateDataSizeForPartitioningKey( + PARTITION_COLUMN_1, + VARCHAR, + ImmutableList.of(partition("p1=__HIVE_DEFAULT_PARTITION__/p2=1234"), partition("p1=str2/p2=1234")), + ImmutableMap.of(), + 3000)).isEqualTo(Estimate.of(12000)); } @Test public void testCalculateRangeForPartitioningKey() { - assertEquals( - calculateRangeForPartitioningKey( - PARTITION_COLUMN_1, - VARCHAR, - ImmutableList.of(partition("p1=string1/p2=1234"))), - Optional.empty()); - assertEquals( - calculateRangeForPartitioningKey( - PARTITION_COLUMN_2, - BIGINT, - ImmutableList.of(partition("p1=string1/p2=__HIVE_DEFAULT_PARTITION__"))), - Optional.empty()); - assertEquals( - calculateRangeForPartitioningKey( - PARTITION_COLUMN_2, - BIGINT, - ImmutableList.of(partition("p1=string1/p2=__HIVE_DEFAULT_PARTITION__"), partition("p1=string1/p2=__HIVE_DEFAULT_PARTITION__"))), - Optional.empty()); - assertEquals( - calculateRangeForPartitioningKey( - PARTITION_COLUMN_2, - BIGINT, - ImmutableList.of(partition("p1=string1/p2=__HIVE_DEFAULT_PARTITION__"), partition("p1=string1/p2=1"))), - Optional.of(new DoubleRange(1, 1))); - assertEquals( - calculateRangeForPartitioningKey( - PARTITION_COLUMN_2, - BIGINT, - ImmutableList.of(partition("p1=string1/p2=2"), partition("p1=string1/p2=1"))), - Optional.of(new DoubleRange(1, 2))); + assertThat(calculateRangeForPartitioningKey( + PARTITION_COLUMN_1, + VARCHAR, + ImmutableList.of(partition("p1=string1/p2=1234")))).isEqualTo(Optional.empty()); + assertThat(calculateRangeForPartitioningKey( + PARTITION_COLUMN_2, + BIGINT, + ImmutableList.of(partition("p1=string1/p2=__HIVE_DEFAULT_PARTITION__")))).isEqualTo(Optional.empty()); + assertThat(calculateRangeForPartitioningKey( + PARTITION_COLUMN_2, + BIGINT, + ImmutableList.of(partition("p1=string1/p2=__HIVE_DEFAULT_PARTITION__"), partition("p1=string1/p2=__HIVE_DEFAULT_PARTITION__")))).isEqualTo(Optional.empty()); + assertThat(calculateRangeForPartitioningKey( + PARTITION_COLUMN_2, + BIGINT, + ImmutableList.of(partition("p1=string1/p2=__HIVE_DEFAULT_PARTITION__"), partition("p1=string1/p2=1")))).isEqualTo(Optional.of(new DoubleRange(1, 1))); + assertThat(calculateRangeForPartitioningKey( + PARTITION_COLUMN_2, + BIGINT, + ImmutableList.of(partition("p1=string1/p2=2"), partition("p1=string1/p2=1")))).isEqualTo(Optional.of(new DoubleRange(1, 2))); } @Test @@ -497,132 +442,122 @@ public void testConvertPartitionValueToDouble() private static void assertConvertPartitionValueToDouble(Type type, String value, double expected) { Object trinoValue = parsePartitionValue(format("p=%s", value), value, type).getValue(); - assertEquals(convertPartitionValueToDouble(type, trinoValue), OptionalDouble.of(expected)); + assertThat(convertPartitionValueToDouble(type, trinoValue)).isEqualTo(OptionalDouble.of(expected)); } @Test public void testCreateDataColumnStatistics() { - assertEquals(createDataColumnStatistics(COLUMN, BIGINT, 1000, ImmutableList.of()), ColumnStatistics.empty()); - assertEquals( - createDataColumnStatistics(COLUMN, BIGINT, 1000, ImmutableList.of(PartitionStatistics.empty(), PartitionStatistics.empty())), - ColumnStatistics.empty()); - assertEquals( - createDataColumnStatistics( - COLUMN, - BIGINT, - 1000, - ImmutableList.of(new PartitionStatistics(HiveBasicStatistics.createZeroStatistics(), ImmutableMap.of("column2", HiveColumnStatistics.empty())))), - ColumnStatistics.empty()); + assertThat(createDataColumnStatistics(COLUMN, BIGINT, 1000, ImmutableList.of())).isEqualTo(ColumnStatistics.empty()); + assertThat(createDataColumnStatistics(COLUMN, BIGINT, 1000, ImmutableList.of(PartitionStatistics.empty(), PartitionStatistics.empty()))).isEqualTo(ColumnStatistics.empty()); + assertThat(createDataColumnStatistics( + COLUMN, + BIGINT, + 1000, + ImmutableList.of(new PartitionStatistics(HiveBasicStatistics.createZeroStatistics(), ImmutableMap.of("column2", HiveColumnStatistics.empty()))))).isEqualTo(ColumnStatistics.empty()); } @Test public void testCalculateDistinctValuesCount() { - assertEquals(calculateDistinctValuesCount(ImmutableList.of()), Estimate.unknown()); - assertEquals(calculateDistinctValuesCount(ImmutableList.of(HiveColumnStatistics.empty())), Estimate.unknown()); - assertEquals(calculateDistinctValuesCount(ImmutableList.of(HiveColumnStatistics.empty(), HiveColumnStatistics.empty())), Estimate.unknown()); - assertEquals(calculateDistinctValuesCount(ImmutableList.of(distinctValuesCount(1))), Estimate.of(1)); - assertEquals(calculateDistinctValuesCount(ImmutableList.of(distinctValuesCount(1), distinctValuesCount(2))), Estimate.of(2)); - assertEquals(calculateDistinctValuesCount(ImmutableList.of(distinctValuesCount(1), HiveColumnStatistics.empty())), Estimate.of(1)); - assertEquals(calculateDistinctValuesCount(ImmutableList.of(createBooleanColumnStatistics(OptionalLong.empty(), OptionalLong.empty(), OptionalLong.empty()))), Estimate.unknown()); - assertEquals(calculateDistinctValuesCount(ImmutableList.of(createBooleanColumnStatistics(OptionalLong.of(1), OptionalLong.of(0), OptionalLong.empty()))), Estimate.of(1)); - assertEquals(calculateDistinctValuesCount(ImmutableList.of(createBooleanColumnStatistics(OptionalLong.of(10), OptionalLong.empty(), OptionalLong.empty()))), Estimate.unknown()); - assertEquals(calculateDistinctValuesCount(ImmutableList.of(createBooleanColumnStatistics(OptionalLong.of(10), OptionalLong.of(10), OptionalLong.empty()))), Estimate.of(2)); - assertEquals(calculateDistinctValuesCount(ImmutableList.of(createBooleanColumnStatistics(OptionalLong.empty(), OptionalLong.of(10), OptionalLong.empty()))), Estimate.unknown()); - assertEquals(calculateDistinctValuesCount(ImmutableList.of(createBooleanColumnStatistics(OptionalLong.of(0), OptionalLong.of(10), OptionalLong.empty()))), Estimate.of(1)); - assertEquals(calculateDistinctValuesCount(ImmutableList.of(createBooleanColumnStatistics(OptionalLong.of(0), OptionalLong.of(0), OptionalLong.empty()))), Estimate.of(0)); - assertEquals( - calculateDistinctValuesCount(ImmutableList.of( - createBooleanColumnStatistics(OptionalLong.of(0), OptionalLong.of(10), OptionalLong.empty()), - createBooleanColumnStatistics(OptionalLong.of(1), OptionalLong.of(10), OptionalLong.empty()))), - Estimate.of(2)); + assertThat(calculateDistinctValuesCount(ImmutableList.of())).isEqualTo(Estimate.unknown()); + assertThat(calculateDistinctValuesCount(ImmutableList.of(HiveColumnStatistics.empty()))).isEqualTo(Estimate.unknown()); + assertThat(calculateDistinctValuesCount(ImmutableList.of(HiveColumnStatistics.empty(), HiveColumnStatistics.empty()))).isEqualTo(Estimate.unknown()); + assertThat(calculateDistinctValuesCount(ImmutableList.of(distinctValuesCount(1)))).isEqualTo(Estimate.of(1)); + assertThat(calculateDistinctValuesCount(ImmutableList.of(distinctValuesCount(1), distinctValuesCount(2)))).isEqualTo(Estimate.of(2)); + assertThat(calculateDistinctValuesCount(ImmutableList.of(distinctValuesCount(1), HiveColumnStatistics.empty()))).isEqualTo(Estimate.of(1)); + assertThat(calculateDistinctValuesCount(ImmutableList.of(createBooleanColumnStatistics(OptionalLong.empty(), OptionalLong.empty(), OptionalLong.empty())))).isEqualTo(Estimate.unknown()); + assertThat(calculateDistinctValuesCount(ImmutableList.of(createBooleanColumnStatistics(OptionalLong.of(1), OptionalLong.of(0), OptionalLong.empty())))).isEqualTo(Estimate.of(1)); + assertThat(calculateDistinctValuesCount(ImmutableList.of(createBooleanColumnStatistics(OptionalLong.of(10), OptionalLong.empty(), OptionalLong.empty())))).isEqualTo(Estimate.unknown()); + assertThat(calculateDistinctValuesCount(ImmutableList.of(createBooleanColumnStatistics(OptionalLong.of(10), OptionalLong.of(10), OptionalLong.empty())))).isEqualTo(Estimate.of(2)); + assertThat(calculateDistinctValuesCount(ImmutableList.of(createBooleanColumnStatistics(OptionalLong.empty(), OptionalLong.of(10), OptionalLong.empty())))).isEqualTo(Estimate.unknown()); + assertThat(calculateDistinctValuesCount(ImmutableList.of(createBooleanColumnStatistics(OptionalLong.of(0), OptionalLong.of(10), OptionalLong.empty())))).isEqualTo(Estimate.of(1)); + assertThat(calculateDistinctValuesCount(ImmutableList.of(createBooleanColumnStatistics(OptionalLong.of(0), OptionalLong.of(0), OptionalLong.empty())))).isEqualTo(Estimate.of(0)); + assertThat(calculateDistinctValuesCount(ImmutableList.of( + createBooleanColumnStatistics(OptionalLong.of(0), OptionalLong.of(10), OptionalLong.empty()), + createBooleanColumnStatistics(OptionalLong.of(1), OptionalLong.of(10), OptionalLong.empty())))).isEqualTo(Estimate.of(2)); } @Test public void testCalculateNullsFraction() { - assertEquals(calculateNullsFraction(COLUMN, ImmutableList.of()), Estimate.unknown()); - assertEquals(calculateNullsFraction(COLUMN, ImmutableList.of(PartitionStatistics.empty())), Estimate.unknown()); - assertEquals(calculateNullsFraction(COLUMN, ImmutableList.of(rowsCount(1000))), Estimate.unknown()); - assertEquals(calculateNullsFraction(COLUMN, ImmutableList.of(rowsCount(1000), nullsCount(500))), Estimate.unknown()); - assertEquals(calculateNullsFraction(COLUMN, ImmutableList.of(rowsCount(1000), nullsCount(500), rowsCountAndNullsCount(1000, 500))), Estimate.of(0.5)); - assertEquals(calculateNullsFraction(COLUMN, ImmutableList.of(rowsCountAndNullsCount(2000, 200), rowsCountAndNullsCount(1000, 100))), Estimate.of(0.1)); - assertEquals(calculateNullsFraction(COLUMN, ImmutableList.of(rowsCountAndNullsCount(0, 0), rowsCountAndNullsCount(0, 0))), Estimate.of(0)); + assertThat(calculateNullsFraction(COLUMN, ImmutableList.of())).isEqualTo(Estimate.unknown()); + assertThat(calculateNullsFraction(COLUMN, ImmutableList.of(PartitionStatistics.empty()))).isEqualTo(Estimate.unknown()); + assertThat(calculateNullsFraction(COLUMN, ImmutableList.of(rowsCount(1000)))).isEqualTo(Estimate.unknown()); + assertThat(calculateNullsFraction(COLUMN, ImmutableList.of(rowsCount(1000), nullsCount(500)))).isEqualTo(Estimate.unknown()); + assertThat(calculateNullsFraction(COLUMN, ImmutableList.of(rowsCount(1000), nullsCount(500), rowsCountAndNullsCount(1000, 500)))).isEqualTo(Estimate.of(0.5)); + assertThat(calculateNullsFraction(COLUMN, ImmutableList.of(rowsCountAndNullsCount(2000, 200), rowsCountAndNullsCount(1000, 100)))).isEqualTo(Estimate.of(0.1)); + assertThat(calculateNullsFraction(COLUMN, ImmutableList.of(rowsCountAndNullsCount(0, 0), rowsCountAndNullsCount(0, 0)))).isEqualTo(Estimate.of(0)); } @Test public void testCalculateDataSize() { - assertEquals(calculateDataSize(COLUMN, ImmutableList.of(), 0), Estimate.unknown()); - assertEquals(calculateDataSize(COLUMN, ImmutableList.of(), 1000), Estimate.unknown()); - assertEquals(calculateDataSize(COLUMN, ImmutableList.of(PartitionStatistics.empty()), 1000), Estimate.unknown()); - assertEquals(calculateDataSize(COLUMN, ImmutableList.of(rowsCount(1000)), 1000), Estimate.unknown()); - assertEquals(calculateDataSize(COLUMN, ImmutableList.of(dataSize(1000)), 1000), Estimate.unknown()); - assertEquals(calculateDataSize(COLUMN, ImmutableList.of(dataSize(1000), rowsCount(1000)), 1000), Estimate.unknown()); - assertEquals(calculateDataSize(COLUMN, ImmutableList.of(rowsCountAndDataSize(500, 1000)), 2000), Estimate.of(4000)); - assertEquals(calculateDataSize(COLUMN, ImmutableList.of(rowsCountAndDataSize(0, 0)), 2000), Estimate.unknown()); - assertEquals(calculateDataSize(COLUMN, ImmutableList.of(rowsCountAndDataSize(0, 0)), 0), Estimate.zero()); - assertEquals(calculateDataSize(COLUMN, ImmutableList.of(rowsCountAndDataSize(1000, 0)), 2000), Estimate.of(0)); - assertEquals( - calculateDataSize( - COLUMN, - ImmutableList.of( - rowsCountAndDataSize(500, 1000), - rowsCountAndDataSize(1000, 5000)), - 5000), - Estimate.of(20000)); - assertEquals( - calculateDataSize( - COLUMN, - ImmutableList.of( - dataSize(1000), - rowsCountAndDataSize(500, 1000), - rowsCount(3000), - rowsCountAndDataSize(1000, 5000)), - 5000), - Estimate.of(20000)); + assertThat(calculateDataSize(COLUMN, ImmutableList.of(), 0)).isEqualTo(Estimate.unknown()); + assertThat(calculateDataSize(COLUMN, ImmutableList.of(), 1000)).isEqualTo(Estimate.unknown()); + assertThat(calculateDataSize(COLUMN, ImmutableList.of(PartitionStatistics.empty()), 1000)).isEqualTo(Estimate.unknown()); + assertThat(calculateDataSize(COLUMN, ImmutableList.of(rowsCount(1000)), 1000)).isEqualTo(Estimate.unknown()); + assertThat(calculateDataSize(COLUMN, ImmutableList.of(dataSize(1000)), 1000)).isEqualTo(Estimate.unknown()); + assertThat(calculateDataSize(COLUMN, ImmutableList.of(dataSize(1000), rowsCount(1000)), 1000)).isEqualTo(Estimate.unknown()); + assertThat(calculateDataSize(COLUMN, ImmutableList.of(rowsCountAndDataSize(500, 1000)), 2000)).isEqualTo(Estimate.of(4000)); + assertThat(calculateDataSize(COLUMN, ImmutableList.of(rowsCountAndDataSize(0, 0)), 2000)).isEqualTo(Estimate.unknown()); + assertThat(calculateDataSize(COLUMN, ImmutableList.of(rowsCountAndDataSize(0, 0)), 0)).isEqualTo(Estimate.zero()); + assertThat(calculateDataSize(COLUMN, ImmutableList.of(rowsCountAndDataSize(1000, 0)), 2000)).isEqualTo(Estimate.of(0)); + assertThat(calculateDataSize( + COLUMN, + ImmutableList.of( + rowsCountAndDataSize(500, 1000), + rowsCountAndDataSize(1000, 5000)), + 5000)).isEqualTo(Estimate.of(20000)); + assertThat(calculateDataSize( + COLUMN, + ImmutableList.of( + dataSize(1000), + rowsCountAndDataSize(500, 1000), + rowsCount(3000), + rowsCountAndDataSize(1000, 5000)), + 5000)).isEqualTo(Estimate.of(20000)); } @Test public void testCalculateRange() { - assertEquals(calculateRange(VARCHAR, ImmutableList.of()), Optional.empty()); - assertEquals(calculateRange(VARCHAR, ImmutableList.of(integerRange(OptionalLong.empty(), OptionalLong.empty()))), Optional.empty()); - assertEquals(calculateRange(VARCHAR, ImmutableList.of(integerRange(1, 2))), Optional.empty()); - assertEquals(calculateRange(BIGINT, ImmutableList.of(integerRange(1, 2))), Optional.of(new DoubleRange(1, 2))); - assertEquals(calculateRange(BIGINT, ImmutableList.of(integerRange(Long.MIN_VALUE, Long.MAX_VALUE))), Optional.of(new DoubleRange(Long.MIN_VALUE, Long.MAX_VALUE))); - assertEquals(calculateRange(INTEGER, ImmutableList.of(integerRange(Long.MIN_VALUE, Long.MAX_VALUE))), Optional.of(new DoubleRange(Integer.MIN_VALUE, Integer.MAX_VALUE))); - assertEquals(calculateRange(SMALLINT, ImmutableList.of(integerRange(Long.MIN_VALUE, Long.MAX_VALUE))), Optional.of(new DoubleRange(Short.MIN_VALUE, Short.MAX_VALUE))); - assertEquals(calculateRange(TINYINT, ImmutableList.of(integerRange(Long.MIN_VALUE, Long.MAX_VALUE))), Optional.of(new DoubleRange(Byte.MIN_VALUE, Byte.MAX_VALUE))); - assertEquals(calculateRange(BIGINT, ImmutableList.of(integerRange(1, 5), integerRange(3, 7))), Optional.of(new DoubleRange(1, 7))); - assertEquals(calculateRange(BIGINT, ImmutableList.of(integerRange(OptionalLong.empty(), OptionalLong.empty()), integerRange(3, 7))), Optional.of(new DoubleRange(3, 7))); - assertEquals(calculateRange(BIGINT, ImmutableList.of(integerRange(OptionalLong.empty(), OptionalLong.of(8)), integerRange(3, 7))), Optional.of(new DoubleRange(3, 7))); - assertEquals(calculateRange(DOUBLE, ImmutableList.of(integerRange(1, 2))), Optional.empty()); - assertEquals(calculateRange(REAL, ImmutableList.of(integerRange(1, 2))), Optional.empty()); - assertEquals(calculateRange(DOUBLE, ImmutableList.of(doubleRange(OptionalDouble.empty(), OptionalDouble.empty()))), Optional.empty()); - assertEquals(calculateRange(DOUBLE, ImmutableList.of(doubleRange(0.1, 0.2))), Optional.of(new DoubleRange(0.1, 0.2))); - assertEquals(calculateRange(BIGINT, ImmutableList.of(doubleRange(0.1, 0.2))), Optional.empty()); - assertEquals(calculateRange(DOUBLE, ImmutableList.of(doubleRange(0.1, 0.2), doubleRange(0.15, 0.25))), Optional.of(new DoubleRange(0.1, 0.25))); - assertEquals(calculateRange(REAL, ImmutableList.of(doubleRange(0.1, 0.2), doubleRange(0.15, 0.25))), Optional.of(new DoubleRange(0.1, 0.25))); - assertEquals(calculateRange(REAL, ImmutableList.of(doubleRange(OptionalDouble.empty(), OptionalDouble.of(0.2)), doubleRange(0.15, 0.25))), Optional.of(new DoubleRange(0.15, 0.25))); - assertEquals(calculateRange(DOUBLE, ImmutableList.of(doubleRange(NaN, 0.2))), Optional.empty()); - assertEquals(calculateRange(DOUBLE, ImmutableList.of(doubleRange(0.1, NaN))), Optional.empty()); - assertEquals(calculateRange(DOUBLE, ImmutableList.of(doubleRange(NaN, NaN))), Optional.empty()); - assertEquals(calculateRange(DOUBLE, ImmutableList.of(doubleRange(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY))), Optional.of(new DoubleRange(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY))); - assertEquals(calculateRange(REAL, ImmutableList.of(doubleRange(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY))), Optional.of(new DoubleRange(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY))); - assertEquals(calculateRange(DOUBLE, ImmutableList.of(doubleRange(Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY))), Optional.of(new DoubleRange(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY))); - assertEquals(calculateRange(DOUBLE, ImmutableList.of(doubleRange(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY), doubleRange(0.1, 0.2))), Optional.of(new DoubleRange(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY))); - assertEquals(calculateRange(DATE, ImmutableList.of(doubleRange(0.1, 0.2))), Optional.empty()); - assertEquals(calculateRange(DATE, ImmutableList.of(dateRange("1970-01-01", "1970-01-02"))), Optional.of(new DoubleRange(0, 1))); - assertEquals(calculateRange(DATE, ImmutableList.of(dateRange(Optional.empty(), Optional.empty()))), Optional.empty()); - assertEquals(calculateRange(DATE, ImmutableList.of(dateRange(Optional.of("1970-01-01"), Optional.empty()))), Optional.empty()); - assertEquals(calculateRange(DATE, ImmutableList.of(dateRange("1970-01-01", "1970-01-05"), dateRange("1970-01-03", "1970-01-07"))), Optional.of(new DoubleRange(0, 6))); - assertEquals(calculateRange(DECIMAL, ImmutableList.of(doubleRange(0.1, 0.2))), Optional.empty()); - assertEquals(calculateRange(DECIMAL, ImmutableList.of(decimalRange(BigDecimal.valueOf(1), BigDecimal.valueOf(5)))), Optional.of(new DoubleRange(1, 5))); - assertEquals(calculateRange(DECIMAL, ImmutableList.of(decimalRange(Optional.empty(), Optional.empty()))), Optional.empty()); - assertEquals(calculateRange(DECIMAL, ImmutableList.of(decimalRange(Optional.of(BigDecimal.valueOf(1)), Optional.empty()))), Optional.empty()); - assertEquals(calculateRange(DECIMAL, ImmutableList.of(decimalRange(BigDecimal.valueOf(1), BigDecimal.valueOf(5)), decimalRange(BigDecimal.valueOf(3), BigDecimal.valueOf(7)))), Optional.of(new DoubleRange(1, 7))); + assertThat(calculateRange(VARCHAR, ImmutableList.of())).isEqualTo(Optional.empty()); + assertThat(calculateRange(VARCHAR, ImmutableList.of(integerRange(OptionalLong.empty(), OptionalLong.empty())))).isEqualTo(Optional.empty()); + assertThat(calculateRange(VARCHAR, ImmutableList.of(integerRange(1, 2)))).isEqualTo(Optional.empty()); + assertThat(calculateRange(BIGINT, ImmutableList.of(integerRange(1, 2)))).isEqualTo(Optional.of(new DoubleRange(1, 2))); + assertThat(calculateRange(BIGINT, ImmutableList.of(integerRange(Long.MIN_VALUE, Long.MAX_VALUE)))).isEqualTo(Optional.of(new DoubleRange(Long.MIN_VALUE, Long.MAX_VALUE))); + assertThat(calculateRange(INTEGER, ImmutableList.of(integerRange(Long.MIN_VALUE, Long.MAX_VALUE)))).isEqualTo(Optional.of(new DoubleRange(Integer.MIN_VALUE, Integer.MAX_VALUE))); + assertThat(calculateRange(SMALLINT, ImmutableList.of(integerRange(Long.MIN_VALUE, Long.MAX_VALUE)))).isEqualTo(Optional.of(new DoubleRange(Short.MIN_VALUE, Short.MAX_VALUE))); + assertThat(calculateRange(TINYINT, ImmutableList.of(integerRange(Long.MIN_VALUE, Long.MAX_VALUE)))).isEqualTo(Optional.of(new DoubleRange(Byte.MIN_VALUE, Byte.MAX_VALUE))); + assertThat(calculateRange(BIGINT, ImmutableList.of(integerRange(1, 5), integerRange(3, 7)))).isEqualTo(Optional.of(new DoubleRange(1, 7))); + assertThat(calculateRange(BIGINT, ImmutableList.of(integerRange(OptionalLong.empty(), OptionalLong.empty()), integerRange(3, 7)))).isEqualTo(Optional.of(new DoubleRange(3, 7))); + assertThat(calculateRange(BIGINT, ImmutableList.of(integerRange(OptionalLong.empty(), OptionalLong.of(8)), integerRange(3, 7)))).isEqualTo(Optional.of(new DoubleRange(3, 7))); + assertThat(calculateRange(DOUBLE, ImmutableList.of(integerRange(1, 2)))).isEqualTo(Optional.empty()); + assertThat(calculateRange(REAL, ImmutableList.of(integerRange(1, 2)))).isEqualTo(Optional.empty()); + assertThat(calculateRange(DOUBLE, ImmutableList.of(doubleRange(OptionalDouble.empty(), OptionalDouble.empty())))).isEqualTo(Optional.empty()); + assertThat(calculateRange(DOUBLE, ImmutableList.of(doubleRange(0.1, 0.2)))).isEqualTo(Optional.of(new DoubleRange(0.1, 0.2))); + assertThat(calculateRange(BIGINT, ImmutableList.of(doubleRange(0.1, 0.2)))).isEqualTo(Optional.empty()); + assertThat(calculateRange(DOUBLE, ImmutableList.of(doubleRange(0.1, 0.2), doubleRange(0.15, 0.25)))).isEqualTo(Optional.of(new DoubleRange(0.1, 0.25))); + assertThat(calculateRange(REAL, ImmutableList.of(doubleRange(0.1, 0.2), doubleRange(0.15, 0.25)))).isEqualTo(Optional.of(new DoubleRange(0.1, 0.25))); + assertThat(calculateRange(REAL, ImmutableList.of(doubleRange(OptionalDouble.empty(), OptionalDouble.of(0.2)), doubleRange(0.15, 0.25)))).isEqualTo(Optional.of(new DoubleRange(0.15, 0.25))); + assertThat(calculateRange(DOUBLE, ImmutableList.of(doubleRange(NaN, 0.2)))).isEqualTo(Optional.empty()); + assertThat(calculateRange(DOUBLE, ImmutableList.of(doubleRange(0.1, NaN)))).isEqualTo(Optional.empty()); + assertThat(calculateRange(DOUBLE, ImmutableList.of(doubleRange(NaN, NaN)))).isEqualTo(Optional.empty()); + assertThat(calculateRange(DOUBLE, ImmutableList.of(doubleRange(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY)))).isEqualTo(Optional.of(new DoubleRange(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY))); + assertThat(calculateRange(REAL, ImmutableList.of(doubleRange(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY)))).isEqualTo(Optional.of(new DoubleRange(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY))); + assertThat(calculateRange(DOUBLE, ImmutableList.of(doubleRange(Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY)))).isEqualTo(Optional.of(new DoubleRange(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY))); + assertThat(calculateRange(DOUBLE, ImmutableList.of(doubleRange(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY), doubleRange(0.1, 0.2)))).isEqualTo(Optional.of(new DoubleRange(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY))); + assertThat(calculateRange(DATE, ImmutableList.of(doubleRange(0.1, 0.2)))).isEqualTo(Optional.empty()); + assertThat(calculateRange(DATE, ImmutableList.of(dateRange("1970-01-01", "1970-01-02")))).isEqualTo(Optional.of(new DoubleRange(0, 1))); + assertThat(calculateRange(DATE, ImmutableList.of(dateRange(Optional.empty(), Optional.empty())))).isEqualTo(Optional.empty()); + assertThat(calculateRange(DATE, ImmutableList.of(dateRange(Optional.of("1970-01-01"), Optional.empty())))).isEqualTo(Optional.empty()); + assertThat(calculateRange(DATE, ImmutableList.of(dateRange("1970-01-01", "1970-01-05"), dateRange("1970-01-03", "1970-01-07")))).isEqualTo(Optional.of(new DoubleRange(0, 6))); + assertThat(calculateRange(DECIMAL, ImmutableList.of(doubleRange(0.1, 0.2)))).isEqualTo(Optional.empty()); + assertThat(calculateRange(DECIMAL, ImmutableList.of(decimalRange(BigDecimal.valueOf(1), BigDecimal.valueOf(5))))).isEqualTo(Optional.of(new DoubleRange(1, 5))); + assertThat(calculateRange(DECIMAL, ImmutableList.of(decimalRange(Optional.empty(), Optional.empty())))).isEqualTo(Optional.empty()); + assertThat(calculateRange(DECIMAL, ImmutableList.of(decimalRange(Optional.of(BigDecimal.valueOf(1)), Optional.empty())))).isEqualTo(Optional.empty()); + assertThat(calculateRange(DECIMAL, ImmutableList.of(decimalRange(BigDecimal.valueOf(1), BigDecimal.valueOf(5)), decimalRange(BigDecimal.valueOf(3), BigDecimal.valueOf(7))))).isEqualTo(Optional.of(new DoubleRange(1, 7))); } @Test @@ -666,20 +601,18 @@ protected Map getPartitionsStatistics(ConnectorSess .setDistinctValuesCount(Estimate.of(300)) .build()) .build(); - assertEquals( - statisticsProvider.getTableStatistics( - SESSION, - TABLE, - ImmutableMap.of( - "p1", PARTITION_COLUMN_1, - "p2", PARTITION_COLUMN_2, - COLUMN, columnHandle), - ImmutableMap.of( - "p1", VARCHAR, - "p2", BIGINT, - COLUMN, BIGINT), - ImmutableList.of(partition(partitionName))), - expected); + assertThat(statisticsProvider.getTableStatistics( + SESSION, + TABLE, + ImmutableMap.of( + "p1", PARTITION_COLUMN_1, + "p2", PARTITION_COLUMN_2, + COLUMN, columnHandle), + ImmutableMap.of( + "p1", VARCHAR, + "p2", BIGINT, + COLUMN, BIGINT), + ImmutableList.of(partition(partitionName)))).isEqualTo(expected); } @Test @@ -710,14 +643,12 @@ protected Map getPartitionsStatistics(ConnectorSess .setDistinctValuesCount(Estimate.of(300)) .build()) .build(); - assertEquals( - statisticsProvider.getTableStatistics( - SESSION, - TABLE, - ImmutableMap.of(COLUMN, columnHandle), - ImmutableMap.of(COLUMN, BIGINT), - ImmutableList.of(new HivePartition(TABLE))), - expected); + assertThat(statisticsProvider.getTableStatistics( + SESSION, + TABLE, + ImmutableMap.of(COLUMN, columnHandle), + ImmutableMap.of(COLUMN, BIGINT), + ImmutableList.of(new HivePartition(TABLE)))).isEqualTo(expected); } @Test @@ -732,14 +663,12 @@ protected Map getPartitionsStatistics(ConnectorSess return ImmutableMap.of(partitionName, PartitionStatistics.empty()); } }; - assertEquals( - statisticsProvider.getTableStatistics( - SESSION, - TABLE, - ImmutableMap.of(), - ImmutableMap.of(), - ImmutableList.of(partition(partitionName))), - TableStatistics.empty()); + assertThat(statisticsProvider.getTableStatistics( + SESSION, + TABLE, + ImmutableMap.of(), + ImmutableMap.of(), + ImmutableList.of(partition(partitionName)))).isEqualTo(TableStatistics.empty()); } @Test @@ -749,8 +678,8 @@ public void testGetTableStatisticsSampling() @Override protected Map getPartitionsStatistics(ConnectorSession session, SchemaTableName table, List hivePartitions, Set columns) { - assertEquals(table, TABLE); - assertEquals(hivePartitions.size(), 1); + assertThat(table).isEqualTo(TABLE); + assertThat(hivePartitions.size()).isEqualTo(1); return ImmutableMap.of(); } }; @@ -787,14 +716,12 @@ protected Map getPartitionsStatistics(ConnectorSess ImmutableList.of(partition(partitionName)))) .isInstanceOf(TrinoException.class) .hasFieldOrPropertyWithValue("errorCode", HIVE_CORRUPTED_COLUMN_STATISTICS.toErrorCode()); - assertEquals( - statisticsProvider.getTableStatistics( - getHiveSession(new HiveConfig().setIgnoreCorruptedStatistics(true)), - TABLE, - ImmutableMap.of(), - ImmutableMap.of(), - ImmutableList.of(partition(partitionName))), - TableStatistics.empty()); + assertThat(statisticsProvider.getTableStatistics( + getHiveSession(new HiveConfig().setIgnoreCorruptedStatistics(true)), + TABLE, + ImmutableMap.of(), + ImmutableMap.of(), + ImmutableList.of(partition(partitionName)))).isEqualTo(TableStatistics.empty()); } @Test @@ -852,26 +779,24 @@ private void testEmptyTableStatisticsForPartitionColumns(HiveStatisticsProvider .build()) .build(); - assertEquals( - statisticsProvider.getTableStatistics( - getHiveSession(new HiveConfig().setIgnoreCorruptedStatistics(true)), - TABLE, - ImmutableMap.of( - "p1", PARTITION_COLUMN_1, - "p2", PARTITION_COLUMN_2, - COLUMN, columnHandle), - ImmutableMap.of( - "p1", VARCHAR, - "p2", BIGINT, - COLUMN, BIGINT), - ImmutableList.of( - partition(partitionName1), - partition(partitionName2), - partition(partitionName3), - partition(partitionName4), - partition(partitionName5), - partition(partitionName6))), - expected); + assertThat(statisticsProvider.getTableStatistics( + getHiveSession(new HiveConfig().setIgnoreCorruptedStatistics(true)), + TABLE, + ImmutableMap.of( + "p1", PARTITION_COLUMN_1, + "p2", PARTITION_COLUMN_2, + COLUMN, columnHandle), + ImmutableMap.of( + "p1", VARCHAR, + "p2", BIGINT, + COLUMN, BIGINT), + ImmutableList.of( + partition(partitionName1), + partition(partitionName2), + partition(partitionName3), + partition(partitionName4), + partition(partitionName5), + partition(partitionName6)))).isEqualTo(expected); } private static void assertInvalidStatistics(PartitionStatistics partitionStatistics, String expectedMessage) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestAcidBucketCodec.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestAcidBucketCodec.java index ffa16413ecd5..af733f41b524 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestAcidBucketCodec.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestAcidBucketCodec.java @@ -15,8 +15,8 @@ import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestAcidBucketCodec { @@ -24,19 +24,19 @@ public class TestAcidBucketCodec public void testGetBucketCodecVersion0() { AcidBucketCodec codec = AcidBucketCodec.forBucket(0); - assertEquals(codec, AcidBucketCodec.V0); - assertEquals(codec.decodeWriterId(7), 7); - assertEquals(codec.decodeStatementId(100), 0); - assertEquals(codec.decodeStatementId(-10), 0); + assertThat(codec).isEqualTo(AcidBucketCodec.V0); + assertThat(codec.decodeWriterId(7)).isEqualTo(7); + assertThat(codec.decodeStatementId(100)).isEqualTo(0); + assertThat(codec.decodeStatementId(-10)).isEqualTo(0); } @Test public void testGetBucketCodecVersion1() { AcidBucketCodec codec = AcidBucketCodec.forBucket(1 << 29); - assertEquals(codec, AcidBucketCodec.V1); - assertEquals(codec.decodeWriterId(0x0ABC0000), 2748); - assertEquals(codec.decodeStatementId(0x00000ABC), 2748); + assertThat(codec).isEqualTo(AcidBucketCodec.V1); + assertThat(codec.decodeWriterId(0x0ABC0000)).isEqualTo(2748); + assertThat(codec.decodeStatementId(0x00000ABC)).isEqualTo(2748); } @Test diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestAcidTables.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestAcidTables.java index f59df73221a9..8bd8af414a61 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestAcidTables.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestAcidTables.java @@ -31,7 +31,6 @@ import static io.trino.plugin.hive.util.AcidTables.parseBase; import static io.trino.plugin.hive.util.AcidTables.parseDelta; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; /** * See TestHiveAcidUtils for equivalent tests against Hive AcidUtils from the Hive codebase. @@ -44,12 +43,12 @@ public class TestAcidTables public void testParseBase() { ParsedBase base = parseBase("base_000123"); - assertEquals(base.writeId(), 123); - assertEquals(base.visibilityId(), 0); + assertThat(base.writeId()).isEqualTo(123); + assertThat(base.visibilityId()).isEqualTo(0); base = parseBase("base_123_v456"); - assertEquals(base.writeId(), 123); - assertEquals(base.visibilityId(), 456); + assertThat(base.writeId()).isEqualTo(123); + assertThat(base.visibilityId()).isEqualTo(456); } @Test @@ -57,46 +56,46 @@ public void testParseDelta() { ParsedDelta delta; delta = parseDelta("/tbl/part1/delta_12_34", "delta_", List.of()); - assertEquals(12, delta.min()); - assertEquals(34, delta.max()); - assertEquals(-1, delta.statementId()); + assertThat(12).isEqualTo(delta.min()); + assertThat(34).isEqualTo(delta.max()); + assertThat(-1).isEqualTo(delta.statementId()); delta = parseDelta("/tbl/part1/delete_delta_12_34", "delete_delta_", List.of()); - assertEquals(12, delta.min()); - assertEquals(34, delta.max()); - assertEquals(-1, delta.statementId()); + assertThat(12).isEqualTo(delta.min()); + assertThat(34).isEqualTo(delta.max()); + assertThat(-1).isEqualTo(delta.statementId()); delta = parseDelta("/tbl/part1/delta_12_34_56", "delta_", List.of()); - assertEquals(12, delta.min()); - assertEquals(34, delta.max()); - assertEquals(56, delta.statementId()); + assertThat(12).isEqualTo(delta.min()); + assertThat(34).isEqualTo(delta.max()); + assertThat(56).isEqualTo(delta.statementId()); delta = parseDelta("/tbl/part1/delete_delta_12_34_56", "delete_delta_", List.of()); - assertEquals(12, delta.min()); - assertEquals(34, delta.max()); - assertEquals(56, delta.statementId()); + assertThat(12).isEqualTo(delta.min()); + assertThat(34).isEqualTo(delta.max()); + assertThat(56).isEqualTo(delta.statementId()); // v78 visibility part should be ignored delta = parseDelta("/tbl/part1/delta_12_34_v78", "delta_", List.of()); - assertEquals(12, delta.min()); - assertEquals(34, delta.max()); - assertEquals(-1, delta.statementId()); + assertThat(12).isEqualTo(delta.min()); + assertThat(34).isEqualTo(delta.max()); + assertThat(-1).isEqualTo(delta.statementId()); delta = parseDelta("/tbl/part1/delete_delta_12_34_v78", "delete_delta_", List.of()); - assertEquals(12, delta.min()); - assertEquals(34, delta.max()); - assertEquals(-1, delta.statementId()); + assertThat(12).isEqualTo(delta.min()); + assertThat(34).isEqualTo(delta.max()); + assertThat(-1).isEqualTo(delta.statementId()); delta = parseDelta("/tbl/part1/delta_12_34_56_v78", "delta_", List.of()); - assertEquals(12, delta.min()); - assertEquals(34, delta.max()); - assertEquals(56, delta.statementId()); + assertThat(12).isEqualTo(delta.min()); + assertThat(34).isEqualTo(delta.max()); + assertThat(56).isEqualTo(delta.statementId()); delta = parseDelta("/tbl/part1/delete_delta_12_34_56_v78", "delete_delta_", List.of()); - assertEquals(12, delta.min()); - assertEquals(34, delta.max()); - assertEquals(56, delta.statementId()); + assertThat(12).isEqualTo(delta.min()); + assertThat(34).isEqualTo(delta.max()); + assertThat(56).isEqualTo(delta.statementId()); } @Test @@ -124,14 +123,14 @@ public void testOriginal() assertThat(state.deltas()).isEmpty(); List files = state.originalFiles(); - assertEquals(files.size(), 7); - assertEquals(files.get(0).location(), Location.of("memory:///tbl/part1/000000_0")); - assertEquals(files.get(1).location(), Location.of("memory:///tbl/part1/000000_0_copy_1")); - assertEquals(files.get(2).location(), Location.of("memory:///tbl/part1/000000_0_copy_2")); - assertEquals(files.get(3).location(), Location.of("memory:///tbl/part1/000001_1")); - assertEquals(files.get(4).location(), Location.of("memory:///tbl/part1/000002_0")); - assertEquals(files.get(5).location(), Location.of("memory:///tbl/part1/random")); - assertEquals(files.get(6).location(), Location.of("memory:///tbl/part1/subdir/000000_0")); + assertThat(files.size()).isEqualTo(7); + assertThat(files.get(0).location()).isEqualTo(Location.of("memory:///tbl/part1/000000_0")); + assertThat(files.get(1).location()).isEqualTo(Location.of("memory:///tbl/part1/000000_0_copy_1")); + assertThat(files.get(2).location()).isEqualTo(Location.of("memory:///tbl/part1/000000_0_copy_2")); + assertThat(files.get(3).location()).isEqualTo(Location.of("memory:///tbl/part1/000001_1")); + assertThat(files.get(4).location()).isEqualTo(Location.of("memory:///tbl/part1/000002_0")); + assertThat(files.get(5).location()).isEqualTo(Location.of("memory:///tbl/part1/random")); + assertThat(files.get(6).location()).isEqualTo(Location.of("memory:///tbl/part1/subdir/000000_0")); } @Test @@ -161,23 +160,23 @@ public void testOriginalDeltas() assertThat(state.baseDirectory()).isEmpty(); List files = state.originalFiles(); - assertEquals(files.size(), 5); - assertEquals(files.get(0).location(), Location.of("memory:///tbl/part1/000000_0")); - assertEquals(files.get(1).location(), Location.of("memory:///tbl/part1/000001_1")); - assertEquals(files.get(2).location(), Location.of("memory:///tbl/part1/000002_0")); - assertEquals(files.get(3).location(), Location.of("memory:///tbl/part1/random")); - assertEquals(files.get(4).location(), Location.of("memory:///tbl/part1/subdir/000000_0")); + assertThat(files.size()).isEqualTo(5); + assertThat(files.get(0).location()).isEqualTo(Location.of("memory:///tbl/part1/000000_0")); + assertThat(files.get(1).location()).isEqualTo(Location.of("memory:///tbl/part1/000001_1")); + assertThat(files.get(2).location()).isEqualTo(Location.of("memory:///tbl/part1/000002_0")); + assertThat(files.get(3).location()).isEqualTo(Location.of("memory:///tbl/part1/random")); + assertThat(files.get(4).location()).isEqualTo(Location.of("memory:///tbl/part1/subdir/000000_0")); List deltas = state.deltas(); - assertEquals(deltas.size(), 2); + assertThat(deltas.size()).isEqualTo(2); ParsedDelta delta = deltas.get(0); - assertEquals(delta.path(), "memory:///tbl/part1/delta_025_030"); - assertEquals(delta.min(), 25); - assertEquals(delta.max(), 30); + assertThat(delta.path()).isEqualTo("memory:///tbl/part1/delta_025_030"); + assertThat(delta.min()).isEqualTo(25); + assertThat(delta.max()).isEqualTo(30); delta = deltas.get(1); - assertEquals(delta.path(), "memory:///tbl/part1/delta_050_100"); - assertEquals(delta.min(), 50); - assertEquals(delta.max(), 100); + assertThat(delta.path()).isEqualTo("memory:///tbl/part1/delta_050_100"); + assertThat(delta.min()).isEqualTo(50); + assertThat(delta.max()).isEqualTo(100); } @Test @@ -202,14 +201,14 @@ public void testBaseDeltas() new ValidWriteIdList("tbl:100:%d:".formatted(Long.MAX_VALUE))); assertThat(dir.baseDirectory()).contains(Location.of("memory:///tbl/part1/base_49")); - assertEquals(dir.originalFiles().size(), 0); + assertThat(dir.originalFiles().size()).isEqualTo(0); List deltas = dir.deltas(); - assertEquals(deltas.size(), 1); + assertThat(deltas.size()).isEqualTo(1); ParsedDelta delta = deltas.get(0); - assertEquals(delta.path(), "memory:///tbl/part1/delta_050_105"); - assertEquals(delta.min(), 50); - assertEquals(delta.max(), 105); + assertThat(delta.path()).isEqualTo("memory:///tbl/part1/delta_050_105"); + assertThat(delta.min()).isEqualTo(50); + assertThat(delta.max()).isEqualTo(105); } @Test @@ -251,11 +250,11 @@ public void testOverlapingDelta() assertThat(state.baseDirectory()).contains(Location.of("memory:///tbl/part1/base_50")); List deltas = state.deltas(); - assertEquals(deltas.size(), 4); - assertEquals(deltas.get(0).path(), "memory:///tbl/part1/delta_40_60"); - assertEquals(deltas.get(1).path(), "memory:///tbl/part1/delta_00061_61"); - assertEquals(deltas.get(2).path(), "memory:///tbl/part1/delta_000062_62"); - assertEquals(deltas.get(3).path(), "memory:///tbl/part1/delta_0000063_63"); + assertThat(deltas.size()).isEqualTo(4); + assertThat(deltas.get(0).path()).isEqualTo("memory:///tbl/part1/delta_40_60"); + assertThat(deltas.get(1).path()).isEqualTo("memory:///tbl/part1/delta_00061_61"); + assertThat(deltas.get(2).path()).isEqualTo("memory:///tbl/part1/delta_000062_62"); + assertThat(deltas.get(3).path()).isEqualTo("memory:///tbl/part1/delta_0000063_63"); } @Test @@ -283,12 +282,12 @@ public void testOverlapingDelta2() assertThat(state.baseDirectory()).contains(Location.of("memory:///tbl/part1/base_50")); List deltas = state.deltas(); - assertEquals(deltas.size(), 5); - assertEquals(deltas.get(0).path(), "memory:///tbl/part1/delta_40_60"); - assertEquals(deltas.get(1).path(), "memory:///tbl/part1/delta_00061_61_0"); - assertEquals(deltas.get(2).path(), "memory:///tbl/part1/delta_000062_62_0"); - assertEquals(deltas.get(3).path(), "memory:///tbl/part1/delta_000062_62_3"); - assertEquals(deltas.get(4).path(), "memory:///tbl/part1/delta_0000063_63_0"); + assertThat(deltas.size()).isEqualTo(5); + assertThat(deltas.get(0).path()).isEqualTo("memory:///tbl/part1/delta_40_60"); + assertThat(deltas.get(1).path()).isEqualTo("memory:///tbl/part1/delta_00061_61_0"); + assertThat(deltas.get(2).path()).isEqualTo("memory:///tbl/part1/delta_000062_62_0"); + assertThat(deltas.get(3).path()).isEqualTo("memory:///tbl/part1/delta_000062_62_3"); + assertThat(deltas.get(4).path()).isEqualTo("memory:///tbl/part1/delta_0000063_63_0"); } @Test @@ -305,9 +304,9 @@ public void deltasWithOpenTxnInRead() new ValidWriteIdList("tbl:100:4:4")); List deltas = state.deltas(); - assertEquals(deltas.size(), 2); - assertEquals(deltas.get(0).path(), "memory:///tbl/part1/delta_1_1"); - assertEquals(deltas.get(1).path(), "memory:///tbl/part1/delta_2_5"); + assertThat(deltas.size()).isEqualTo(2); + assertThat(deltas.get(0).path()).isEqualTo("memory:///tbl/part1/delta_1_1"); + assertThat(deltas.get(1).path()).isEqualTo("memory:///tbl/part1/delta_2_5"); } @Test @@ -327,9 +326,9 @@ public void deltasWithOpenTxnInRead2() new ValidWriteIdList("tbl:100:4:4")); List deltas = state.deltas(); - assertEquals(deltas.size(), 2); - assertEquals(deltas.get(0).path(), "memory:///tbl/part1/delta_1_1"); - assertEquals(deltas.get(1).path(), "memory:///tbl/part1/delta_2_5"); + assertThat(deltas.size()).isEqualTo(2); + assertThat(deltas.get(0).path()).isEqualTo("memory:///tbl/part1/delta_1_1"); + assertThat(deltas.get(1).path()).isEqualTo("memory:///tbl/part1/delta_2_5"); } @Test @@ -358,9 +357,9 @@ public void testBaseWithDeleteDeltas() assertThat(state.originalFiles()).isEmpty(); List deltas = state.deltas(); - assertEquals(deltas.size(), 2); - assertEquals(deltas.get(0).path(), "memory:///tbl/part1/delete_delta_050_105"); - assertEquals(deltas.get(1).path(), "memory:///tbl/part1/delta_050_105"); + assertThat(deltas.size()).isEqualTo(2); + assertThat(deltas.get(0).path()).isEqualTo("memory:///tbl/part1/delete_delta_050_105"); + assertThat(deltas.get(1).path()).isEqualTo("memory:///tbl/part1/delta_050_105"); // The delete_delta_110_110 should not be read because it is greater than the high watermark. } @@ -388,13 +387,13 @@ public void testOverlapingDeltaAndDeleteDelta() assertThat(state.baseDirectory()).contains(Location.of("memory:///tbl/part1/base_50")); List deltas = state.deltas(); - assertEquals(deltas.size(), 6); - assertEquals(deltas.get(0).path(), "memory:///tbl/part1/delete_delta_40_60"); - assertEquals(deltas.get(1).path(), "memory:///tbl/part1/delta_40_60"); - assertEquals(deltas.get(2).path(), "memory:///tbl/part1/delta_00061_61"); - assertEquals(deltas.get(3).path(), "memory:///tbl/part1/delta_000062_62"); - assertEquals(deltas.get(4).path(), "memory:///tbl/part1/delta_0000063_63"); - assertEquals(deltas.get(5).path(), "memory:///tbl/part1/delete_delta_00064_64"); + assertThat(deltas.size()).isEqualTo(6); + assertThat(deltas.get(0).path()).isEqualTo("memory:///tbl/part1/delete_delta_40_60"); + assertThat(deltas.get(1).path()).isEqualTo("memory:///tbl/part1/delta_40_60"); + assertThat(deltas.get(2).path()).isEqualTo("memory:///tbl/part1/delta_00061_61"); + assertThat(deltas.get(3).path()).isEqualTo("memory:///tbl/part1/delta_000062_62"); + assertThat(deltas.get(4).path()).isEqualTo("memory:///tbl/part1/delta_0000063_63"); + assertThat(deltas.get(5).path()).isEqualTo("memory:///tbl/part1/delete_delta_00064_64"); } @Test @@ -413,8 +412,8 @@ public void testMinorCompactedDeltaMakesInBetweenDelteDeltaObsolete() new ValidWriteIdList("tbl:100:%d:".formatted(Long.MAX_VALUE))); List deltas = state.deltas(); - assertEquals(deltas.size(), 1); - assertEquals(deltas.get(0).path(), "memory:///tbl/part1/delta_40_60"); + assertThat(deltas.size()).isEqualTo(1); + assertThat(deltas.get(0).path()).isEqualTo("memory:///tbl/part1/delta_40_60"); } @Test @@ -436,10 +435,10 @@ public void deleteDeltasWithOpenTxnInRead() new ValidWriteIdList("tbl:100:4:4")); List deltas = state.deltas(); - assertEquals(deltas.size(), 3); - assertEquals(deltas.get(0).path(), "memory:///tbl/part1/delta_1_1"); - assertEquals(deltas.get(1).path(), "memory:///tbl/part1/delete_delta_2_5"); - assertEquals(deltas.get(2).path(), "memory:///tbl/part1/delta_2_5"); + assertThat(deltas.size()).isEqualTo(3); + assertThat(deltas.get(0).path()).isEqualTo("memory:///tbl/part1/delta_1_1"); + assertThat(deltas.get(1).path()).isEqualTo("memory:///tbl/part1/delete_delta_2_5"); + assertThat(deltas.get(2).path()).isEqualTo("memory:///tbl/part1/delta_2_5"); // Note that delete_delta_3_3 should not be read, when a minor compacted // [delete_]delta_2_5 is present. } @@ -448,7 +447,7 @@ public void deleteDeltasWithOpenTxnInRead() public void testDeleteDeltaSubdirPathGeneration() { String deleteDeltaSubdirPath = deleteDeltaSubdir(13, 5); - assertEquals(deleteDeltaSubdirPath, "delete_delta_0000013_0000013_0005"); + assertThat(deleteDeltaSubdirPath).isEqualTo("delete_delta_0000013_0000013_0005"); } private static void createFile(TrinoFileSystem fileSystem, String location, byte[] data) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestAsyncQueue.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestAsyncQueue.java index 41383d480af0..aa0cea6f85bf 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestAsyncQueue.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestAsyncQueue.java @@ -34,12 +34,10 @@ import java.util.concurrent.atomic.AtomicBoolean; import static io.airlift.concurrent.MoreFutures.getFutureValue; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -69,10 +67,10 @@ public void testGetPartial() queue.offer("1"); queue.offer("2"); queue.offer("3"); - assertEquals(queue.getBatchAsync(100).get(), ImmutableList.of("1", "2", "3")); + assertThat(queue.getBatchAsync(100).get()).isEqualTo(ImmutableList.of("1", "2", "3")); queue.finish(); - assertTrue(queue.isFinished()); + assertThat(queue.isFinished()).isTrue(); } @Test @@ -82,29 +80,29 @@ public void testFullQueue() { AsyncQueue queue = new AsyncQueue<>(4, executor); - assertTrue(queue.offer("1").isDone()); - assertTrue(queue.offer("2").isDone()); - assertTrue(queue.offer("3").isDone()); + assertThat(queue.offer("1").isDone()).isTrue(); + assertThat(queue.offer("2").isDone()).isTrue(); + assertThat(queue.offer("3").isDone()).isTrue(); - assertFalse(queue.offer("4").isDone()); - assertFalse(queue.offer("5").isDone()); + assertThat(queue.offer("4").isDone()).isFalse(); + assertThat(queue.offer("5").isDone()).isFalse(); ListenableFuture offerFuture = queue.offer("6"); - assertFalse(offerFuture.isDone()); + assertThat(offerFuture.isDone()).isFalse(); - assertEquals(queue.getBatchAsync(2).get(), ImmutableList.of("1", "2")); - assertFalse(offerFuture.isDone()); + assertThat(queue.getBatchAsync(2).get()).isEqualTo(ImmutableList.of("1", "2")); + assertThat(offerFuture.isDone()).isFalse(); - assertEquals(queue.getBatchAsync(1).get(), ImmutableList.of("3")); + assertThat(queue.getBatchAsync(1).get()).isEqualTo(ImmutableList.of("3")); offerFuture.get(); offerFuture = queue.offer("7"); - assertFalse(offerFuture.isDone()); + assertThat(offerFuture.isDone()).isFalse(); queue.finish(); offerFuture.get(); - assertFalse(queue.isFinished()); - assertEquals(queue.getBatchAsync(4).get(), ImmutableList.of("4", "5", "6", "7")); - assertTrue(queue.isFinished()); + assertThat(queue.isFinished()).isFalse(); + assertThat(queue.getBatchAsync(4).get()).isEqualTo(ImmutableList.of("4", "5", "6", "7")); + assertThat(queue.isFinished()).isTrue(); } @Test @@ -114,22 +112,22 @@ public void testEmptyQueue() { AsyncQueue queue = new AsyncQueue<>(4, executor); - assertTrue(queue.offer("1").isDone()); - assertTrue(queue.offer("2").isDone()); - assertTrue(queue.offer("3").isDone()); - assertEquals(queue.getBatchAsync(2).get(), ImmutableList.of("1", "2")); - assertEquals(queue.getBatchAsync(2).get(), ImmutableList.of("3")); + assertThat(queue.offer("1").isDone()).isTrue(); + assertThat(queue.offer("2").isDone()).isTrue(); + assertThat(queue.offer("3").isDone()).isTrue(); + assertThat(queue.getBatchAsync(2).get()).isEqualTo(ImmutableList.of("1", "2")); + assertThat(queue.getBatchAsync(2).get()).isEqualTo(ImmutableList.of("3")); ListenableFuture> batchFuture = queue.getBatchAsync(2); - assertFalse(batchFuture.isDone()); + assertThat(batchFuture.isDone()).isFalse(); - assertTrue(queue.offer("4").isDone()); - assertEquals(batchFuture.get(), ImmutableList.of("4")); + assertThat(queue.offer("4").isDone()).isTrue(); + assertThat(batchFuture.get()).isEqualTo(ImmutableList.of("4")); batchFuture = queue.getBatchAsync(2); - assertFalse(batchFuture.isDone()); + assertThat(batchFuture.isDone()).isFalse(); queue.finish(); batchFuture.get(); - assertTrue(queue.isFinished()); + assertThat(queue.isFinished()).isTrue(); } @Test @@ -139,19 +137,19 @@ public void testOfferAfterFinish() { AsyncQueue queue = new AsyncQueue<>(4, executor); - assertTrue(queue.offer("1").isDone()); - assertTrue(queue.offer("2").isDone()); - assertTrue(queue.offer("3").isDone()); - assertFalse(queue.offer("4").isDone()); + assertThat(queue.offer("1").isDone()).isTrue(); + assertThat(queue.offer("2").isDone()).isTrue(); + assertThat(queue.offer("3").isDone()).isTrue(); + assertThat(queue.offer("4").isDone()).isFalse(); queue.finish(); - assertTrue(queue.offer("5").isDone()); - assertTrue(queue.offer("6").isDone()); - assertTrue(queue.offer("7").isDone()); - assertFalse(queue.isFinished()); + assertThat(queue.offer("5").isDone()).isTrue(); + assertThat(queue.offer("6").isDone()).isTrue(); + assertThat(queue.offer("7").isDone()).isTrue(); + assertThat(queue.isFinished()).isFalse(); - assertEquals(queue.getBatchAsync(100).get(), ImmutableList.of("1", "2", "3", "4")); - assertTrue(queue.isFinished()); + assertThat(queue.getBatchAsync(100).get()).isEqualTo(ImmutableList.of("1", "2", "3", "4")); + assertThat(queue.isFinished()).isTrue(); } @Test @@ -185,12 +183,12 @@ public void testBorrow() future3.get(); queue.finish(); - assertFalse(queue.isFinished()); + assertThat(queue.isFinished()).isFalse(); AtomicBoolean done = new AtomicBoolean(); executor.submit(() -> { while (!done.get()) { - assertFalse(queue.isFinished() || done.get()); + assertThat(queue.isFinished() || done.get()).isFalse(); } }); @@ -202,11 +200,11 @@ public void testBorrow() future3.get(); done.set(true); - assertFalse(queue.isFinished()); + assertThat(queue.isFinished()).isFalse(); ArrayList list = new ArrayList<>(queue.getBatchAsync(100).get()); list.sort(Integer::compare); - assertEquals(list, ImmutableList.of(1, 2, 3, 4, 5)); - assertTrue(queue.isFinished()); + assertThat(list).isEqualTo(ImmutableList.of(1, 2, 3, 4, 5)); + assertThat(queue.isFinished()).isTrue(); } @Test @@ -224,7 +222,7 @@ public void testBorrowThrows() queue.offer(5); ListenableFuture future1 = queue.offer(6); - assertFalse(future1.isDone()); + assertThat(future1.isDone()).isFalse(); Runnable runnable = () -> { getFutureValue(queue.borrowBatchAsync(1, elements -> { @@ -237,23 +235,23 @@ public void testBorrowThrows() .hasMessageContaining("test fail"); ListenableFuture future2 = queue.offer(7); - assertFalse(future1.isDone()); - assertFalse(future2.isDone()); + assertThat(future1.isDone()).isFalse(); + assertThat(future2.isDone()).isFalse(); queue.finish(); future1.get(); future2.get(); - assertTrue(queue.offer(8).isDone()); + assertThat(queue.offer(8).isDone()).isTrue(); assertThatThrownBy(() -> executor.submit(runnable).get()) .isInstanceOf(ExecutionException.class) .hasMessageContaining("test fail"); - assertTrue(queue.offer(9).isDone()); + assertThat(queue.offer(9).isDone()).isTrue(); - assertFalse(queue.isFinished()); + assertThat(queue.isFinished()).isFalse(); ArrayList list = new ArrayList<>(queue.getBatchAsync(100).get()); // 1 and 2 were removed by borrow call; 8 and 9 were never inserted because insertion happened after finish. - assertEquals(list, ImmutableList.of(3, 4, 5, 6, 7)); - assertTrue(queue.isFinished()); + assertThat(list).isEqualTo(ImmutableList.of(3, 4, 5, 6, 7)); + assertThat(queue.isFinished()).isTrue(); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveAcidUtils.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveAcidUtils.java index 23395f8c219e..1832f75b22ab 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveAcidUtils.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveAcidUtils.java @@ -28,8 +28,7 @@ import java.util.List; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; +import static org.assertj.core.api.Assertions.assertThat; /** * Test the expected behavior of the Hive AcidUtils class from the Hive codebase. @@ -39,7 +38,7 @@ public class TestHiveAcidUtils @Test public void testParsing() { - assertEquals(AcidUtils.parseBase(new Path("/tmp/base_000123")), 123); + assertThat(AcidUtils.parseBase(new Path("/tmp/base_000123"))).isEqualTo(123); } @Test @@ -62,18 +61,18 @@ public void testOriginal() new MockPath(fs, "/tbl/part1"), conf, new ValidReaderWriteIdList("tbl:100:" + Long.MAX_VALUE + ":")); - assertNull(dir.getBaseDirectory()); - assertEquals(dir.getCurrentDirectories().size(), 0); - assertEquals(dir.getObsolete().size(), 0); + assertThat(dir.getBaseDirectory()).isNull(); + assertThat(dir.getCurrentDirectories().size()).isEqualTo(0); + assertThat(dir.getObsolete().size()).isEqualTo(0); List result = dir.getOriginalFiles(); - assertEquals(result.size(), 7); - assertEquals(result.get(0).getFileStatus().getPath().toString(), "mock:/tbl/part1/000000_0"); - assertEquals(result.get(1).getFileStatus().getPath().toString(), "mock:/tbl/part1/000000_0" + Utilities.COPY_KEYWORD + "1"); - assertEquals(result.get(2).getFileStatus().getPath().toString(), "mock:/tbl/part1/000000_0" + Utilities.COPY_KEYWORD + "2"); - assertEquals(result.get(3).getFileStatus().getPath().toString(), "mock:/tbl/part1/000001_1"); - assertEquals(result.get(4).getFileStatus().getPath().toString(), "mock:/tbl/part1/000002_0"); - assertEquals(result.get(5).getFileStatus().getPath().toString(), "mock:/tbl/part1/random"); - assertEquals(result.get(6).getFileStatus().getPath().toString(), "mock:/tbl/part1/subdir/000000_0"); + assertThat(result.size()).isEqualTo(7); + assertThat(result.get(0).getFileStatus().getPath().toString()).isEqualTo("mock:/tbl/part1/000000_0"); + assertThat(result.get(1).getFileStatus().getPath().toString()).isEqualTo("mock:/tbl/part1/000000_0" + Utilities.COPY_KEYWORD + "1"); + assertThat(result.get(2).getFileStatus().getPath().toString()).isEqualTo("mock:/tbl/part1/000000_0" + Utilities.COPY_KEYWORD + "2"); + assertThat(result.get(3).getFileStatus().getPath().toString()).isEqualTo("mock:/tbl/part1/000001_1"); + assertThat(result.get(4).getFileStatus().getPath().toString()).isEqualTo("mock:/tbl/part1/000002_0"); + assertThat(result.get(5).getFileStatus().getPath().toString()).isEqualTo("mock:/tbl/part1/random"); + assertThat(result.get(6).getFileStatus().getPath().toString()).isEqualTo("mock:/tbl/part1/subdir/000000_0"); } @Test @@ -99,28 +98,28 @@ public void testOriginalDeltas() new MockPath(fs, "mock:/tbl/part1"), conf, new ValidReaderWriteIdList("tbl:100:" + Long.MAX_VALUE + ":")); - assertNull(dir.getBaseDirectory()); + assertThat(dir.getBaseDirectory()).isNull(); List obsolete = dir.getObsolete(); - assertEquals(obsolete.size(), 2); - assertEquals(obsolete.get(0).getPath().toString(), "mock:/tbl/part1/delta_025_025"); - assertEquals(obsolete.get(1).getPath().toString(), "mock:/tbl/part1/delta_029_029"); + assertThat(obsolete.size()).isEqualTo(2); + assertThat(obsolete.get(0).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_025_025"); + assertThat(obsolete.get(1).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_029_029"); List result = dir.getOriginalFiles(); - assertEquals(result.size(), 5); - assertEquals(result.get(0).getFileStatus().getPath().toString(), "mock:/tbl/part1/000000_0"); - assertEquals(result.get(1).getFileStatus().getPath().toString(), "mock:/tbl/part1/000001_1"); - assertEquals(result.get(2).getFileStatus().getPath().toString(), "mock:/tbl/part1/000002_0"); - assertEquals(result.get(3).getFileStatus().getPath().toString(), "mock:/tbl/part1/random"); - assertEquals(result.get(4).getFileStatus().getPath().toString(), "mock:/tbl/part1/subdir/000000_0"); + assertThat(result.size()).isEqualTo(5); + assertThat(result.get(0).getFileStatus().getPath().toString()).isEqualTo("mock:/tbl/part1/000000_0"); + assertThat(result.get(1).getFileStatus().getPath().toString()).isEqualTo("mock:/tbl/part1/000001_1"); + assertThat(result.get(2).getFileStatus().getPath().toString()).isEqualTo("mock:/tbl/part1/000002_0"); + assertThat(result.get(3).getFileStatus().getPath().toString()).isEqualTo("mock:/tbl/part1/random"); + assertThat(result.get(4).getFileStatus().getPath().toString()).isEqualTo("mock:/tbl/part1/subdir/000000_0"); List deltas = dir.getCurrentDirectories(); - assertEquals(deltas.size(), 2); + assertThat(deltas.size()).isEqualTo(2); AcidUtils.ParsedDelta delt = deltas.get(0); - assertEquals(delt.getPath().toString(), "mock:/tbl/part1/delta_025_030"); - assertEquals(delt.getMinWriteId(), 25); - assertEquals(delt.getMaxWriteId(), 30); + assertThat(delt.getPath().toString()).isEqualTo("mock:/tbl/part1/delta_025_030"); + assertThat(delt.getMinWriteId()).isEqualTo(25); + assertThat(delt.getMaxWriteId()).isEqualTo(30); delt = deltas.get(1); - assertEquals(delt.getPath().toString(), "mock:/tbl/part1/delta_050_100"); - assertEquals(delt.getMinWriteId(), 50); - assertEquals(delt.getMaxWriteId(), 100); + assertThat(delt.getPath().toString()).isEqualTo("mock:/tbl/part1/delta_050_100"); + assertThat(delt.getMinWriteId()).isEqualTo(50); + assertThat(delt.getMaxWriteId()).isEqualTo(100); } @Test @@ -141,21 +140,21 @@ public void testBaseDeltas() new MockFile("mock:/tbl/part1/delta_90_120/bucket_0", 0, new byte[0])); MockPath part = new MockPath(fs, "mock:/tbl/part1"); AcidUtils.Directory dir = AcidUtils.getAcidState(part, conf, new ValidReaderWriteIdList("tbl:100:" + Long.MAX_VALUE + ":")); - assertEquals(dir.getBaseDirectory().toString(), "mock:/tbl/part1/base_49"); + assertThat(dir.getBaseDirectory().toString()).isEqualTo("mock:/tbl/part1/base_49"); List obsolete = dir.getObsolete(); - assertEquals(obsolete.size(), 5); - assertEquals(obsolete.get(0).getPath().toString(), "mock:/tbl/part1/base_10"); - assertEquals(obsolete.get(1).getPath().toString(), "mock:/tbl/part1/base_5"); - assertEquals(obsolete.get(2).getPath().toString(), "mock:/tbl/part1/delta_025_030"); - assertEquals(obsolete.get(3).getPath().toString(), "mock:/tbl/part1/delta_025_025"); - assertEquals(obsolete.get(4).getPath().toString(), "mock:/tbl/part1/delta_029_029"); - assertEquals(dir.getOriginalFiles().size(), 0); + assertThat(obsolete.size()).isEqualTo(5); + assertThat(obsolete.get(0).getPath().toString()).isEqualTo("mock:/tbl/part1/base_10"); + assertThat(obsolete.get(1).getPath().toString()).isEqualTo("mock:/tbl/part1/base_5"); + assertThat(obsolete.get(2).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_025_030"); + assertThat(obsolete.get(3).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_025_025"); + assertThat(obsolete.get(4).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_029_029"); + assertThat(dir.getOriginalFiles().size()).isEqualTo(0); List deltas = dir.getCurrentDirectories(); - assertEquals(deltas.size(), 1); + assertThat(deltas.size()).isEqualTo(1); AcidUtils.ParsedDelta delt = deltas.get(0); - assertEquals(delt.getPath().toString(), "mock:/tbl/part1/delta_050_105"); - assertEquals(delt.getMinWriteId(), 50); - assertEquals(delt.getMaxWriteId(), 105); + assertThat(delt.getPath().toString()).isEqualTo("mock:/tbl/part1/delta_050_105"); + assertThat(delt.getMinWriteId()).isEqualTo(50); + assertThat(delt.getMaxWriteId()).isEqualTo(105); } @Test @@ -172,9 +171,9 @@ public void testObsoleteOriginals() AcidUtils.Directory dir = AcidUtils.getAcidState(part, conf, new ValidReaderWriteIdList("tbl:150:" + Long.MAX_VALUE + ":")); // Obsolete list should include the two original bucket files, and the old base dir List obsolete = dir.getObsolete(); - assertEquals(obsolete.size(), 3); - assertEquals(obsolete.get(0).getPath().toString(), "mock:/tbl/part1/base_5"); - assertEquals(dir.getBaseDirectory().toString(), "mock:/tbl/part1/base_10"); + assertThat(obsolete.size()).isEqualTo(3); + assertThat(obsolete.get(0).getPath().toString()).isEqualTo("mock:/tbl/part1/base_5"); + assertThat(dir.getBaseDirectory().toString()).isEqualTo("mock:/tbl/part1/base_10"); } @Test @@ -192,17 +191,17 @@ public void testOverlapingDelta() new MockFile("mock:/tbl/part1/base_50/bucket_0", 500, new byte[0])); Path part = new MockPath(fs, "mock:/tbl/part1"); AcidUtils.Directory dir = AcidUtils.getAcidState(part, conf, new ValidReaderWriteIdList("tbl:100:" + Long.MAX_VALUE + ":")); - assertEquals(dir.getBaseDirectory().toString(), "mock:/tbl/part1/base_50"); + assertThat(dir.getBaseDirectory().toString()).isEqualTo("mock:/tbl/part1/base_50"); List obsolete = dir.getObsolete(); - assertEquals(obsolete.size(), 2); - assertEquals(obsolete.get(0).getPath().toString(), "mock:/tbl/part1/delta_052_55"); - assertEquals(obsolete.get(1).getPath().toString(), "mock:/tbl/part1/delta_0060_60"); + assertThat(obsolete.size()).isEqualTo(2); + assertThat(obsolete.get(0).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_052_55"); + assertThat(obsolete.get(1).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_0060_60"); List delts = dir.getCurrentDirectories(); - assertEquals(delts.size(), 4); - assertEquals(delts.get(0).getPath().toString(), "mock:/tbl/part1/delta_40_60"); - assertEquals(delts.get(1).getPath().toString(), "mock:/tbl/part1/delta_00061_61"); - assertEquals(delts.get(2).getPath().toString(), "mock:/tbl/part1/delta_000062_62"); - assertEquals(delts.get(3).getPath().toString(), "mock:/tbl/part1/delta_0000063_63"); + assertThat(delts.size()).isEqualTo(4); + assertThat(delts.get(0).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_40_60"); + assertThat(delts.get(1).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_00061_61"); + assertThat(delts.get(2).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_000062_62"); + assertThat(delts.get(3).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_0000063_63"); } @Test @@ -224,21 +223,21 @@ public void testOverlapingDelta2() new MockFile("mock:/tbl/part1/base_50/bucket_0", 500, new byte[0])); Path part = new MockPath(fs, "mock:/tbl/part1"); AcidUtils.Directory dir = AcidUtils.getAcidState(part, conf, new ValidReaderWriteIdList("tbl:100:" + Long.MAX_VALUE + ":")); - assertEquals(dir.getBaseDirectory().toString(), "mock:/tbl/part1/base_50"); + assertThat(dir.getBaseDirectory().toString()).isEqualTo("mock:/tbl/part1/base_50"); List obsolete = dir.getObsolete(); - assertEquals(obsolete.size(), 5); - assertEquals(obsolete.get(0).getPath().toString(), "mock:/tbl/part1/delta_052_55"); - assertEquals(obsolete.get(1).getPath().toString(), "mock:/tbl/part1/delta_058_58"); - assertEquals(obsolete.get(2).getPath().toString(), "mock:/tbl/part1/delta_0060_60_1"); - assertEquals(obsolete.get(3).getPath().toString(), "mock:/tbl/part1/delta_0060_60_4"); - assertEquals(obsolete.get(4).getPath().toString(), "mock:/tbl/part1/delta_0060_60_7"); + assertThat(obsolete.size()).isEqualTo(5); + assertThat(obsolete.get(0).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_052_55"); + assertThat(obsolete.get(1).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_058_58"); + assertThat(obsolete.get(2).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_0060_60_1"); + assertThat(obsolete.get(3).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_0060_60_4"); + assertThat(obsolete.get(4).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_0060_60_7"); List delts = dir.getCurrentDirectories(); - assertEquals(delts.size(), 5); - assertEquals(delts.get(0).getPath().toString(), "mock:/tbl/part1/delta_40_60"); - assertEquals(delts.get(1).getPath().toString(), "mock:/tbl/part1/delta_00061_61_0"); - assertEquals(delts.get(2).getPath().toString(), "mock:/tbl/part1/delta_000062_62_0"); - assertEquals(delts.get(3).getPath().toString(), "mock:/tbl/part1/delta_000062_62_3"); - assertEquals(delts.get(4).getPath().toString(), "mock:/tbl/part1/delta_0000063_63_0"); + assertThat(delts.size()).isEqualTo(5); + assertThat(delts.get(0).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_40_60"); + assertThat(delts.get(1).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_00061_61_0"); + assertThat(delts.get(2).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_000062_62_0"); + assertThat(delts.get(3).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_000062_62_3"); + assertThat(delts.get(4).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_0000063_63_0"); } @Test @@ -252,9 +251,9 @@ public void deltasWithOpenTxnInRead() Path part = new MockPath(fs, "mock:/tbl/part1"); AcidUtils.Directory dir = AcidUtils.getAcidState(part, conf, new ValidReaderWriteIdList("tbl:100:4:4")); List delts = dir.getCurrentDirectories(); - assertEquals(delts.size(), 2); - assertEquals(delts.get(0).getPath().toString(), "mock:/tbl/part1/delta_1_1"); - assertEquals(delts.get(1).getPath().toString(), "mock:/tbl/part1/delta_2_5"); + assertThat(delts.size()).isEqualTo(2); + assertThat(delts.get(0).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_1_1"); + assertThat(delts.get(1).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_2_5"); } @Test @@ -271,9 +270,9 @@ public void deltasWithOpenTxnInRead2() Path part = new MockPath(fs, "mock:/tbl/part1"); AcidUtils.Directory dir = AcidUtils.getAcidState(part, conf, new ValidReaderWriteIdList("tbl:100:4:4")); List delts = dir.getCurrentDirectories(); - assertEquals(delts.size(), 2); - assertEquals(delts.get(0).getPath().toString(), "mock:/tbl/part1/delta_1_1"); - assertEquals(delts.get(1).getPath().toString(), "mock:/tbl/part1/delta_2_5"); + assertThat(delts.size()).isEqualTo(2); + assertThat(delts.get(0).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_1_1"); + assertThat(delts.get(1).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_2_5"); } @Test @@ -287,8 +286,8 @@ public void deltasWithOpenTxnsNotInCompact() Path part = new MockPath(fs, "mock:/tbl/part1"); AcidUtils.Directory dir = AcidUtils.getAcidState(part, conf, new ValidCompactorWriteIdList("tbl:4:" + Long.MAX_VALUE)); List delts = dir.getCurrentDirectories(); - assertEquals(delts.size(), 1); - assertEquals(delts.get(0).getPath().toString(), "mock:/tbl/part1/delta_1_1"); + assertThat(delts.size()).isEqualTo(1); + assertThat(delts.get(0).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_1_1"); } @Test @@ -304,8 +303,8 @@ public void deltasWithOpenTxnsNotInCompact2() Path part = new MockPath(fs, "mock:/tbl/part1"); AcidUtils.Directory dir = AcidUtils.getAcidState(part, conf, new ValidCompactorWriteIdList("tbl:3:" + Long.MAX_VALUE)); List delts = dir.getCurrentDirectories(); - assertEquals(delts.size(), 1); - assertEquals(delts.get(0).getPath().toString(), "mock:/tbl/part1/delta_1_1"); + assertThat(delts.size()).isEqualTo(1); + assertThat(delts.get(0).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_1_1"); } @Test @@ -327,21 +326,21 @@ public void testBaseWithDeleteDeltas() new MockFile("mock:/tbl/part1/delete_delta_110_110/bucket_0", 0, new byte[0])); MockPath part = new MockPath(fs, "mock:/tbl/part1"); AcidUtils.Directory dir = AcidUtils.getAcidState(part, conf, new ValidReaderWriteIdList("tbl:100:" + Long.MAX_VALUE + ":")); - assertEquals(dir.getBaseDirectory().toString(), "mock:/tbl/part1/base_49"); + assertThat(dir.getBaseDirectory().toString()).isEqualTo("mock:/tbl/part1/base_49"); List obsolete = dir.getObsolete(); - assertEquals(obsolete.size(), 7); - assertEquals(obsolete.get(0).getPath().toString(), "mock:/tbl/part1/base_10"); - assertEquals(obsolete.get(1).getPath().toString(), "mock:/tbl/part1/base_5"); - assertEquals(obsolete.get(2).getPath().toString(), "mock:/tbl/part1/delete_delta_025_030"); - assertEquals(obsolete.get(3).getPath().toString(), "mock:/tbl/part1/delta_025_030"); - assertEquals(obsolete.get(4).getPath().toString(), "mock:/tbl/part1/delta_025_025"); - assertEquals(obsolete.get(5).getPath().toString(), "mock:/tbl/part1/delete_delta_029_029"); - assertEquals(obsolete.get(6).getPath().toString(), "mock:/tbl/part1/delta_029_029"); - assertEquals(dir.getOriginalFiles().size(), 0); + assertThat(obsolete.size()).isEqualTo(7); + assertThat(obsolete.get(0).getPath().toString()).isEqualTo("mock:/tbl/part1/base_10"); + assertThat(obsolete.get(1).getPath().toString()).isEqualTo("mock:/tbl/part1/base_5"); + assertThat(obsolete.get(2).getPath().toString()).isEqualTo("mock:/tbl/part1/delete_delta_025_030"); + assertThat(obsolete.get(3).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_025_030"); + assertThat(obsolete.get(4).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_025_025"); + assertThat(obsolete.get(5).getPath().toString()).isEqualTo("mock:/tbl/part1/delete_delta_029_029"); + assertThat(obsolete.get(6).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_029_029"); + assertThat(dir.getOriginalFiles().size()).isEqualTo(0); List deltas = dir.getCurrentDirectories(); - assertEquals(deltas.size(), 2); - assertEquals(deltas.get(0).getPath().toString(), "mock:/tbl/part1/delete_delta_050_105"); - assertEquals(deltas.get(1).getPath().toString(), "mock:/tbl/part1/delta_050_105"); + assertThat(deltas.size()).isEqualTo(2); + assertThat(deltas.get(0).getPath().toString()).isEqualTo("mock:/tbl/part1/delete_delta_050_105"); + assertThat(deltas.get(1).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_050_105"); // The delete_delta_110_110 should not be read because it is greater than the high watermark. } @@ -363,20 +362,20 @@ public void testOverlapingDeltaAndDeleteDelta() new MockFile("mock:/tbl/part1/base_50/bucket_0", 500, new byte[0])); Path part = new MockPath(fs, "mock:/tbl/part1"); AcidUtils.Directory dir = AcidUtils.getAcidState(part, conf, new ValidReaderWriteIdList("tbl:100:" + Long.MAX_VALUE + ":")); - assertEquals(dir.getBaseDirectory().toString(), "mock:/tbl/part1/base_50"); + assertThat(dir.getBaseDirectory().toString()).isEqualTo("mock:/tbl/part1/base_50"); List obsolete = dir.getObsolete(); - assertEquals(obsolete.size(), 3); - assertEquals(obsolete.get(0).getPath().toString(), "mock:/tbl/part1/delete_delta_052_55"); - assertEquals(obsolete.get(1).getPath().toString(), "mock:/tbl/part1/delta_052_55"); - assertEquals(obsolete.get(2).getPath().toString(), "mock:/tbl/part1/delta_0060_60"); + assertThat(obsolete.size()).isEqualTo(3); + assertThat(obsolete.get(0).getPath().toString()).isEqualTo("mock:/tbl/part1/delete_delta_052_55"); + assertThat(obsolete.get(1).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_052_55"); + assertThat(obsolete.get(2).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_0060_60"); List delts = dir.getCurrentDirectories(); - assertEquals(delts.size(), 6); - assertEquals(delts.get(0).getPath().toString(), "mock:/tbl/part1/delete_delta_40_60"); - assertEquals(delts.get(1).getPath().toString(), "mock:/tbl/part1/delta_40_60"); - assertEquals(delts.get(2).getPath().toString(), "mock:/tbl/part1/delta_00061_61"); - assertEquals(delts.get(3).getPath().toString(), "mock:/tbl/part1/delta_000062_62"); - assertEquals(delts.get(4).getPath().toString(), "mock:/tbl/part1/delta_0000063_63"); - assertEquals(delts.get(5).getPath().toString(), "mock:/tbl/part1/delete_delta_00064_64"); + assertThat(delts.size()).isEqualTo(6); + assertThat(delts.get(0).getPath().toString()).isEqualTo("mock:/tbl/part1/delete_delta_40_60"); + assertThat(delts.get(1).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_40_60"); + assertThat(delts.get(2).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_00061_61"); + assertThat(delts.get(3).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_000062_62"); + assertThat(delts.get(4).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_0000063_63"); + assertThat(delts.get(5).getPath().toString()).isEqualTo("mock:/tbl/part1/delete_delta_00064_64"); } @Test @@ -392,11 +391,11 @@ public void testMinorCompactedDeltaMakesInBetweenDelteDeltaObsolete() Path part = new MockPath(fs, "mock:/tbl/part1"); AcidUtils.Directory dir = AcidUtils.getAcidState(part, conf, new ValidReaderWriteIdList("tbl:100:" + Long.MAX_VALUE + ":")); List obsolete = dir.getObsolete(); - assertEquals(obsolete.size(), 1); - assertEquals(obsolete.get(0).getPath().toString(), "mock:/tbl/part1/delete_delta_50_50"); + assertThat(obsolete.size()).isEqualTo(1); + assertThat(obsolete.get(0).getPath().toString()).isEqualTo("mock:/tbl/part1/delete_delta_50_50"); List delts = dir.getCurrentDirectories(); - assertEquals(delts.size(), 1); - assertEquals(delts.get(0).getPath().toString(), "mock:/tbl/part1/delta_40_60"); + assertThat(delts.size()).isEqualTo(1); + assertThat(delts.get(0).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_40_60"); } @Test @@ -417,9 +416,9 @@ public void deltasAndDeleteDeltasWithOpenTxnsNotInCompact() Path part = new MockPath(fs, "mock:/tbl/part1"); AcidUtils.Directory dir = AcidUtils.getAcidState(part, conf, new ValidCompactorWriteIdList("tbl:4:" + Long.MAX_VALUE + ":")); List delts = dir.getCurrentDirectories(); - assertEquals(delts.size(), 2); - assertEquals(delts.get(0).getPath().toString(), "mock:/tbl/part1/delta_1_1"); - assertEquals(delts.get(1).getPath().toString(), "mock:/tbl/part1/delete_delta_2_2"); + assertThat(delts.size()).isEqualTo(2); + assertThat(delts.get(0).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_1_1"); + assertThat(delts.get(1).getPath().toString()).isEqualTo("mock:/tbl/part1/delete_delta_2_2"); } @Test @@ -438,10 +437,10 @@ public void deleteDeltasWithOpenTxnInRead() Path part = new MockPath(fs, "mock:/tbl/part1"); AcidUtils.Directory dir = AcidUtils.getAcidState(part, conf, new ValidReaderWriteIdList("tbl:100:4:4")); List delts = dir.getCurrentDirectories(); - assertEquals(delts.size(), 3); - assertEquals(delts.get(0).getPath().toString(), "mock:/tbl/part1/delta_1_1"); - assertEquals(delts.get(1).getPath().toString(), "mock:/tbl/part1/delete_delta_2_5"); - assertEquals(delts.get(2).getPath().toString(), "mock:/tbl/part1/delta_2_5"); + assertThat(delts.size()).isEqualTo(3); + assertThat(delts.get(0).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_1_1"); + assertThat(delts.get(1).getPath().toString()).isEqualTo("mock:/tbl/part1/delete_delta_2_5"); + assertThat(delts.get(2).getPath().toString()).isEqualTo("mock:/tbl/part1/delta_2_5"); // Note that delete_delta_3_3 should not be read, when a minor compacted // [delete_]delta_2_5 is present. } @@ -450,8 +449,8 @@ public void deleteDeltasWithOpenTxnInRead() public void testDeleteDeltaSubdirPathGeneration() { String deleteDeltaSubdirPath = AcidUtils.deleteDeltaSubdir(1, 10); - assertEquals(deleteDeltaSubdirPath, "delete_delta_0000001_0000010"); + assertThat(deleteDeltaSubdirPath).isEqualTo("delete_delta_0000001_0000010"); deleteDeltaSubdirPath = AcidUtils.deleteDeltaSubdir(1, 10, 5); - assertEquals(deleteDeltaSubdirPath, "delete_delta_0000001_0000010_0005"); + assertThat(deleteDeltaSubdirPath).isEqualTo("delete_delta_0000001_0000010_0005"); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveBucketing.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveBucketing.java index d10b715a0ea4..9cbf4f4dc465 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveBucketing.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveBucketing.java @@ -54,8 +54,8 @@ import static java.util.Map.Entry; import static org.apache.hadoop.hive.serde2.typeinfo.TypeInfoUtils.getStandardJavaObjectInspectorFromTypeInfo; import static org.apache.hadoop.hive.serde2.typeinfo.TypeInfoUtils.getTypeInfoFromTypeString; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestHiveBucketing { @@ -63,8 +63,8 @@ public class TestHiveBucketing public void testHashingCompare() { assertBucketEquals("string", "Trino rocks", 1132136730, -399107423); - assertEquals(HiveBucketing.getBucketNumber(1132136730, 4), 2); - assertEquals(HiveBucketing.getBucketNumber(-399107423, 4), 1); + assertThat(HiveBucketing.getBucketNumber(1132136730, 4)).isEqualTo(2); + assertThat(HiveBucketing.getBucketNumber(-399107423, 4)).isEqualTo(1); assertBucketEquals("boolean", null, 0, 0); assertBucketEquals("boolean", true, 1, 1); @@ -244,9 +244,9 @@ private static void assertBucketsEqual(List hiveTypeStrings, List hiveTypeStrings, List ImmutableList blockList = blockListBuilder.build(); int result1 = bucketingVersion.getBucketHashCode(hiveTypeInfos, new Page(blockList.toArray(new Block[blockList.size()])), 2); int result2 = bucketingVersion.getBucketHashCode(hiveTypeInfos, nativeContainerValues); - assertEquals(result1, result2, "overloads of getBucketHashCode produced different result"); + assertThat(result1) + .describedAs("overloads of getBucketHashCode produced different result") + .isEqualTo(result2); return result1; } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveTypeTranslator.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveTypeTranslator.java index 98be6d3c0cd0..82108a2623ca 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveTypeTranslator.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveTypeTranslator.java @@ -44,8 +44,8 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.spi.type.VarcharType.createVarcharType; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Fail.fail; public class TestHiveTypeTranslator { @@ -79,7 +79,7 @@ public void testTypeTranslator() private static void assertTypeTranslation(Type type, HiveType hiveType) { - assertEquals(toHiveType(type), hiveType); + assertThat(toHiveType(type)).isEqualTo(hiveType); } private static void assertInvalidTypeTranslation(Type type, ErrorCode errorCode, String message) @@ -90,7 +90,7 @@ private static void assertInvalidTypeTranslation(Type type, ErrorCode errorCode, } catch (TrinoException e) { try { - assertEquals(e.getErrorCode(), errorCode); + assertThat(e.getErrorCode()).isEqualTo(errorCode); assertContains(e.getMessage(), message); } catch (Throwable failure) { diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveUtil.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveUtil.java index a91240b269e5..647dd54a36aa 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveUtil.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestHiveUtil.java @@ -32,7 +32,6 @@ import static io.trino.type.DateTimes.MICROSECONDS_PER_MILLISECOND; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestHiveUtil { @@ -40,11 +39,11 @@ public class TestHiveUtil public void testParseHiveTimestamp() { DateTime time = new DateTime(2011, 5, 6, 7, 8, 9, 123, DateTimeZone.UTC); - assertEquals(parse(time, "yyyy-MM-dd HH:mm:ss"), unixTime(time, 0)); - assertEquals(parse(time, "yyyy-MM-dd HH:mm:ss.S"), unixTime(time, 1)); - assertEquals(parse(time, "yyyy-MM-dd HH:mm:ss.SSS"), unixTime(time, 3)); - assertEquals(parse(time, "yyyy-MM-dd HH:mm:ss.SSSSSSS"), unixTime(time, 6)); - assertEquals(parse(time, "yyyy-MM-dd HH:mm:ss.SSSSSSSSS"), unixTime(time, 7)); + assertThat(parse(time, "yyyy-MM-dd HH:mm:ss")).isEqualTo(unixTime(time, 0)); + assertThat(parse(time, "yyyy-MM-dd HH:mm:ss.S")).isEqualTo(unixTime(time, 1)); + assertThat(parse(time, "yyyy-MM-dd HH:mm:ss.SSS")).isEqualTo(unixTime(time, 3)); + assertThat(parse(time, "yyyy-MM-dd HH:mm:ss.SSSSSSS")).isEqualTo(unixTime(time, 6)); + assertThat(parse(time, "yyyy-MM-dd HH:mm:ss.SSSSSSSSS")).isEqualTo(unixTime(time, 7)); } @Test @@ -155,7 +154,7 @@ private static void assertToPartitionValues(String partitionName) AbstractList expected = new ArrayList<>(); actual.forEach(s -> expected.add(null)); Warehouse.makeValsFromName(partitionName, expected); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } private static long parse(DateTime time, String pattern) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestMergingPageIterator.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestMergingPageIterator.java index 8582dd6fcbee..fb72bf958c28 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestMergingPageIterator.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestMergingPageIterator.java @@ -32,9 +32,6 @@ import static java.util.Comparator.nullsFirst; import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestMergingPageIterator { @@ -72,7 +69,7 @@ public void testMerging() pageBuilder.reset(); } pageLists.add(pages); - assertFalse(values.hasNext()); + assertThat(values.hasNext()).isFalse(); } List> pages = pageLists.stream() @@ -85,13 +82,13 @@ public void testMerging() Page page = iterator.next(); for (int i = 0; i < page.getPositionCount(); i++) { if (page.getBlock(0).isNull(i)) { - assertTrue(page.getBlock(1).isNull(i)); + assertThat(page.getBlock(1).isNull(i)).isTrue(); values.add(null); } else { int x = INTEGER.getInt(page.getBlock(0), i); int y = INTEGER.getInt(page.getBlock(1), i); - assertEquals(y, x * 22); + assertThat(y).isEqualTo(x * 22); values.add(x); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestSizeBasedSplitWeightProvider.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestSizeBasedSplitWeightProvider.java index 63ad5b2ec746..961c66894f8a 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestSizeBasedSplitWeightProvider.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestSizeBasedSplitWeightProvider.java @@ -18,8 +18,8 @@ import org.junit.jupiter.api.Test; import static io.airlift.units.DataSize.Unit.MEGABYTE; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestSizeBasedSplitWeightProvider { @@ -27,9 +27,9 @@ public class TestSizeBasedSplitWeightProvider public void testSimpleProportions() { SizeBasedSplitWeightProvider provider = new SizeBasedSplitWeightProvider(0.01, DataSize.of(64, MEGABYTE)); - assertEquals(provider.weightForSplitSizeInBytes(DataSize.of(64, MEGABYTE).toBytes()), SplitWeight.fromProportion(1)); - assertEquals(provider.weightForSplitSizeInBytes(DataSize.of(32, MEGABYTE).toBytes()), SplitWeight.fromProportion(0.5)); - assertEquals(provider.weightForSplitSizeInBytes(DataSize.of(16, MEGABYTE).toBytes()), SplitWeight.fromProportion(0.25)); + assertThat(provider.weightForSplitSizeInBytes(DataSize.of(64, MEGABYTE).toBytes())).isEqualTo(SplitWeight.fromProportion(1)); + assertThat(provider.weightForSplitSizeInBytes(DataSize.of(32, MEGABYTE).toBytes())).isEqualTo(SplitWeight.fromProportion(0.5)); + assertThat(provider.weightForSplitSizeInBytes(DataSize.of(16, MEGABYTE).toBytes())).isEqualTo(SplitWeight.fromProportion(0.25)); } @Test @@ -38,10 +38,10 @@ public void testMinimumAndMaximumSplitWeightHandling() double minimumWeight = 0.05; DataSize targetSplitSize = DataSize.of(64, MEGABYTE); SizeBasedSplitWeightProvider provider = new SizeBasedSplitWeightProvider(minimumWeight, targetSplitSize); - assertEquals(provider.weightForSplitSizeInBytes(1), SplitWeight.fromProportion(minimumWeight)); + assertThat(provider.weightForSplitSizeInBytes(1)).isEqualTo(SplitWeight.fromProportion(minimumWeight)); DataSize largerThanTarget = DataSize.of(128, MEGABYTE); - assertEquals(provider.weightForSplitSizeInBytes(largerThanTarget.toBytes()), SplitWeight.standard()); + assertThat(provider.weightForSplitSizeInBytes(largerThanTarget.toBytes())).isEqualTo(SplitWeight.standard()); } @Test diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestThrottledAsyncQueue.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestThrottledAsyncQueue.java index ec17417e5e67..64a73e6683ed 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestThrottledAsyncQueue.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestThrottledAsyncQueue.java @@ -30,12 +30,10 @@ import static io.airlift.concurrent.MoreFutures.getFutureValue; import static io.airlift.concurrent.Threads.daemonThreadsNamed; import static java.util.concurrent.Executors.newCachedThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -63,31 +61,31 @@ public void testThrottle() // Make sure that the dequeuing is throttled even if we have enough elements in the queue ThrottledAsyncQueue queue = new ThrottledAsyncQueue<>(3, 10, executor); - assertTrue(queue.offer(1).isDone()); - assertTrue(queue.offer(2).isDone()); - assertTrue(queue.offer(3).isDone()); - assertTrue(queue.offer(4).isDone()); - assertTrue(queue.offer(5).isDone()); - assertTrue(queue.offer(6).isDone()); + assertThat(queue.offer(1).isDone()).isTrue(); + assertThat(queue.offer(2).isDone()).isTrue(); + assertThat(queue.offer(3).isDone()).isTrue(); + assertThat(queue.offer(4).isDone()).isTrue(); + assertThat(queue.offer(5).isDone()).isTrue(); + assertThat(queue.offer(6).isDone()).isTrue(); queue.finish(); // no throttling, enough elements in the queue ListenableFuture> future1 = queue.getBatchAsync(2); - assertTrue(future1.isDone()); - assertEquals(getFutureValue(future1), ImmutableList.of(1, 2)); - assertFalse(queue.isFinished()); + assertThat(future1.isDone()).isTrue(); + assertThat(getFutureValue(future1)).isEqualTo(ImmutableList.of(1, 2)); + assertThat(queue.isFinished()).isFalse(); // we can only dequeue one more element before being throttled ListenableFuture> future2 = queue.getBatchAsync(2); - assertFalse(future2.isDone()); - assertEquals(getFutureValue(future2), ImmutableList.of(3, 4)); - assertFalse(queue.isFinished()); + assertThat(future2.isDone()).isFalse(); + assertThat(getFutureValue(future2)).isEqualTo(ImmutableList.of(3, 4)); + assertThat(queue.isFinished()).isFalse(); // we are now throttled, this future will not be immediate ListenableFuture> future3 = queue.getBatchAsync(2); - assertFalse(future3.isDone()); - assertEquals(getFutureValue(future3), ImmutableList.of(5, 6)); - assertTrue(queue.isFinished()); + assertThat(future3.isDone()).isFalse(); + assertThat(getFutureValue(future3)).isEqualTo(ImmutableList.of(5, 6)); + assertThat(queue.isFinished()).isTrue(); } @Test @@ -99,30 +97,30 @@ public void testThrottleEmptyQueue() // The future should only complete once the queue becomes non-empty again. ThrottledAsyncQueue queue = new ThrottledAsyncQueue<>(2, 10, executor); - assertTrue(queue.offer(1).isDone()); - assertTrue(queue.offer(2).isDone()); + assertThat(queue.offer(1).isDone()).isTrue(); + assertThat(queue.offer(2).isDone()).isTrue(); // no throttling, enough elements in the queue ListenableFuture> future1 = queue.getBatchAsync(2); - assertTrue(future1.isDone()); - assertEquals(getFutureValue(future1), ImmutableList.of(1, 2)); - assertFalse(queue.isFinished()); + assertThat(future1.isDone()).isTrue(); + assertThat(getFutureValue(future1)).isEqualTo(ImmutableList.of(1, 2)); + assertThat(queue.isFinished()).isFalse(); // we are now throttled and the queue is empty ListenableFuture> future2 = queue.getBatchAsync(2); - assertFalse(future2.isDone()); + assertThat(future2.isDone()).isFalse(); Thread.sleep(1000L); // wait one second, after which we should not be throttled any more // no batch is ready at that point as the queue is still empty - assertFalse(future2.isDone()); + assertThat(future2.isDone()).isFalse(); - assertTrue(queue.offer(3).isDone()); + assertThat(queue.offer(3).isDone()).isTrue(); queue.finish(); - assertEquals(getFutureValue(future2), ImmutableList.of(3)); + assertThat(getFutureValue(future2)).isEqualTo(ImmutableList.of(3)); - assertTrue(queue.isFinished()); + assertThat(queue.isFinished()).isTrue(); } @Test @@ -141,7 +139,7 @@ public void testBorrowThrows() queue.offer(5); ListenableFuture future1 = queue.offer(6); - assertFalse(future1.isDone()); + assertThat(future1.isDone()).isFalse(); Runnable runnable = () -> { getFutureValue(queue.borrowBatchAsync(1, elements -> { @@ -154,23 +152,23 @@ public void testBorrowThrows() .hasMessageContaining("test fail"); ListenableFuture future2 = queue.offer(7); - assertFalse(future1.isDone()); - assertFalse(future2.isDone()); + assertThat(future1.isDone()).isFalse(); + assertThat(future2.isDone()).isFalse(); queue.finish(); future1.get(); future2.get(); - assertTrue(queue.offer(8).isDone()); + assertThat(queue.offer(8).isDone()).isTrue(); assertThatThrownBy(() -> executor.submit(runnable).get()) .isInstanceOf(ExecutionException.class) .hasMessageContaining("test fail"); - assertTrue(queue.offer(9).isDone()); + assertThat(queue.offer(9).isDone()).isTrue(); - assertFalse(queue.isFinished()); + assertThat(queue.isFinished()).isFalse(); // 1 and 2 were removed by borrow call; 8 and 9 were never inserted because insertion happened after finish. - assertEquals(queue.getBatchAsync(100).get(), ImmutableList.of(3, 4, 5, 6, 7)); - assertTrue(queue.isFinished()); + assertThat(queue.getBatchAsync(100).get()).isEqualTo(ImmutableList.of(3, 4, 5, 6, 7)); + assertThat(queue.isFinished()).isTrue(); } @Test @@ -183,10 +181,10 @@ public void testGetPartial() queue.offer("1"); queue.offer("2"); queue.offer("3"); - assertEquals(queue.getBatchAsync(100).get(), ImmutableList.of("1", "2", "3")); + assertThat(queue.getBatchAsync(100).get()).isEqualTo(ImmutableList.of("1", "2", "3")); queue.finish(); - assertTrue(queue.isFinished()); + assertThat(queue.isFinished()).isTrue(); } @Test @@ -196,29 +194,29 @@ public void testFullQueue() { AsyncQueue queue = new ThrottledAsyncQueue<>(100, 4, executor); - assertTrue(queue.offer("1").isDone()); - assertTrue(queue.offer("2").isDone()); - assertTrue(queue.offer("3").isDone()); + assertThat(queue.offer("1").isDone()).isTrue(); + assertThat(queue.offer("2").isDone()).isTrue(); + assertThat(queue.offer("3").isDone()).isTrue(); - assertFalse(queue.offer("4").isDone()); - assertFalse(queue.offer("5").isDone()); + assertThat(queue.offer("4").isDone()).isFalse(); + assertThat(queue.offer("5").isDone()).isFalse(); ListenableFuture offerFuture = queue.offer("6"); - assertFalse(offerFuture.isDone()); + assertThat(offerFuture.isDone()).isFalse(); - assertEquals(queue.getBatchAsync(2).get(), ImmutableList.of("1", "2")); - assertFalse(offerFuture.isDone()); + assertThat(queue.getBatchAsync(2).get()).isEqualTo(ImmutableList.of("1", "2")); + assertThat(offerFuture.isDone()).isFalse(); - assertEquals(queue.getBatchAsync(1).get(), ImmutableList.of("3")); + assertThat(queue.getBatchAsync(1).get()).isEqualTo(ImmutableList.of("3")); offerFuture.get(); offerFuture = queue.offer("7"); - assertFalse(offerFuture.isDone()); + assertThat(offerFuture.isDone()).isFalse(); queue.finish(); offerFuture.get(); - assertFalse(queue.isFinished()); - assertEquals(queue.getBatchAsync(4).get(), ImmutableList.of("4", "5", "6", "7")); - assertTrue(queue.isFinished()); + assertThat(queue.isFinished()).isFalse(); + assertThat(queue.getBatchAsync(4).get()).isEqualTo(ImmutableList.of("4", "5", "6", "7")); + assertThat(queue.isFinished()).isTrue(); } @Test @@ -228,22 +226,22 @@ public void testEmptyQueue() { AsyncQueue queue = new ThrottledAsyncQueue<>(100, 4, executor); - assertTrue(queue.offer("1").isDone()); - assertTrue(queue.offer("2").isDone()); - assertTrue(queue.offer("3").isDone()); - assertEquals(queue.getBatchAsync(2).get(), ImmutableList.of("1", "2")); - assertEquals(queue.getBatchAsync(2).get(), ImmutableList.of("3")); + assertThat(queue.offer("1").isDone()).isTrue(); + assertThat(queue.offer("2").isDone()).isTrue(); + assertThat(queue.offer("3").isDone()).isTrue(); + assertThat(queue.getBatchAsync(2).get()).isEqualTo(ImmutableList.of("1", "2")); + assertThat(queue.getBatchAsync(2).get()).isEqualTo(ImmutableList.of("3")); ListenableFuture> batchFuture = queue.getBatchAsync(2); - assertFalse(batchFuture.isDone()); + assertThat(batchFuture.isDone()).isFalse(); - assertTrue(queue.offer("4").isDone()); - assertEquals(batchFuture.get(), ImmutableList.of("4")); + assertThat(queue.offer("4").isDone()).isTrue(); + assertThat(batchFuture.get()).isEqualTo(ImmutableList.of("4")); batchFuture = queue.getBatchAsync(2); - assertFalse(batchFuture.isDone()); + assertThat(batchFuture.isDone()).isFalse(); queue.finish(); batchFuture.get(); - assertTrue(queue.isFinished()); + assertThat(queue.isFinished()).isTrue(); } @Test @@ -253,18 +251,18 @@ public void testOfferAfterFinish() { AsyncQueue queue = new ThrottledAsyncQueue<>(100, 4, executor); - assertTrue(queue.offer("1").isDone()); - assertTrue(queue.offer("2").isDone()); - assertTrue(queue.offer("3").isDone()); - assertFalse(queue.offer("4").isDone()); + assertThat(queue.offer("1").isDone()).isTrue(); + assertThat(queue.offer("2").isDone()).isTrue(); + assertThat(queue.offer("3").isDone()).isTrue(); + assertThat(queue.offer("4").isDone()).isFalse(); queue.finish(); - assertTrue(queue.offer("5").isDone()); - assertTrue(queue.offer("6").isDone()); - assertTrue(queue.offer("7").isDone()); - assertFalse(queue.isFinished()); + assertThat(queue.offer("5").isDone()).isTrue(); + assertThat(queue.offer("6").isDone()).isTrue(); + assertThat(queue.offer("7").isDone()).isTrue(); + assertThat(queue.isFinished()).isFalse(); - assertEquals(queue.getBatchAsync(100).get(), ImmutableList.of("1", "2", "3", "4")); - assertTrue(queue.isFinished()); + assertThat(queue.getBatchAsync(100).get()).isEqualTo(ImmutableList.of("1", "2", "3", "4")); + assertThat(queue.isFinished()).isTrue(); } } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorSmokeTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorSmokeTest.java index 16605509d5f3..b499a7309f15 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorSmokeTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorSmokeTest.java @@ -59,8 +59,6 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) public abstract class BaseIcebergConnectorSmokeTest @@ -166,7 +164,7 @@ public void testDeleteRowsConcurrently() } finally { executor.shutdownNow(); - assertTrue(executor.awaitTermination(10, SECONDS)); + assertThat(executor.awaitTermination(10, SECONDS)).isTrue(); } } @@ -524,7 +522,7 @@ public void testSortedNationTable() "WITH (sorted_by = ARRAY['comment'], format = '" + format.name() + "') AS SELECT * FROM nation WITH NO DATA")) { assertUpdate(withSmallRowGroups, "INSERT INTO " + table.getName() + " SELECT * FROM nation", 25); for (Object filePath : computeActual("SELECT file_path from \"" + table.getName() + "$files\"").getOnlyColumnAsSet()) { - assertTrue(isFileSorted(Location.of((String) filePath), "comment")); + assertThat(isFileSorted(Location.of((String) filePath), "comment")).isTrue(); } assertQuery("SELECT * FROM " + table.getName(), "SELECT * FROM nation"); } @@ -544,7 +542,7 @@ public void testFileSortingWithLargerTable() "INSERT INTO " + table.getName() + " TABLE tpch.tiny.lineitem", "VALUES 60175"); for (Object filePath : computeActual("SELECT file_path from \"" + table.getName() + "$files\"").getOnlyColumnAsSet()) { - assertTrue(isFileSorted(Location.of((String) filePath), "comment")); + assertThat(isFileSorted(Location.of((String) filePath), "comment")).isTrue(); } assertQuery("SELECT * FROM " + table.getName(), "SELECT * FROM lineitem"); } @@ -562,12 +560,16 @@ public void testDropTableWithMissingMetadataFile() // Delete current metadata file fileSystem.deleteFile(metadataLocation); - assertFalse(fileSystem.newInputFile(metadataLocation).exists(), "Current metadata file should not exist"); + assertThat(fileSystem.newInputFile(metadataLocation).exists()) + .describedAs("Current metadata file should not exist") + .isFalse(); // try to drop table assertUpdate("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); - assertFalse(fileSystem.listFiles(tableLocation).hasNext(), "Table location should not exist"); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); + assertThat(fileSystem.listFiles(tableLocation).hasNext()) + .describedAs("Table location should not exist") + .isFalse(); } @Test @@ -584,12 +586,16 @@ public void testDropTableWithMissingSnapshotFile() // Delete current snapshot file fileSystem.deleteFile(currentSnapshotFile); - assertFalse(fileSystem.newInputFile(currentSnapshotFile).exists(), "Current snapshot file should not exist"); + assertThat(fileSystem.newInputFile(currentSnapshotFile).exists()) + .describedAs("Current snapshot file should not exist") + .isFalse(); // try to drop table assertUpdate("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); - assertFalse(fileSystem.listFiles(tableLocation).hasNext(), "Table location should not exist"); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); + assertThat(fileSystem.listFiles(tableLocation).hasNext()) + .describedAs("Table location should not exist") + .isFalse(); } @Test @@ -607,12 +613,16 @@ public void testDropTableWithMissingManifestListFile() // Delete Manifest List file fileSystem.deleteFile(manifestListFile); - assertFalse(fileSystem.newInputFile(manifestListFile).exists(), "Manifest list file should not exist"); + assertThat(fileSystem.newInputFile(manifestListFile).exists()) + .describedAs("Manifest list file should not exist") + .isFalse(); // try to drop table assertUpdate("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); - assertFalse(fileSystem.listFiles(tableLocation).hasNext(), "Table location should not exist"); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); + assertThat(fileSystem.listFiles(tableLocation).hasNext()) + .describedAs("Table location should not exist") + .isFalse(); } @Test @@ -626,17 +636,21 @@ public void testDropTableWithMissingDataFile() Location tableLocation = Location.of(getTableLocation(tableName)); Location tableDataPath = tableLocation.appendPath("data"); FileIterator fileIterator = fileSystem.listFiles(tableDataPath); - assertTrue(fileIterator.hasNext()); + assertThat(fileIterator.hasNext()).isTrue(); Location dataFile = fileIterator.next().location(); // Delete data file fileSystem.deleteFile(dataFile); - assertFalse(fileSystem.newInputFile(dataFile).exists(), "Data file should not exist"); + assertThat(fileSystem.newInputFile(dataFile).exists()) + .describedAs("Data file should not exist") + .isFalse(); // try to drop table assertUpdate("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); - assertFalse(fileSystem.listFiles(tableLocation).hasNext(), "Table location should not exist"); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); + assertThat(fileSystem.listFiles(tableLocation).hasNext()) + .describedAs("Table location should not exist") + .isFalse(); } @Test @@ -651,11 +665,13 @@ public void testDropTableWithNonExistentTableLocation() // Delete table location fileSystem.deleteDirectory(tableLocation); - assertFalse(fileSystem.listFiles(tableLocation).hasNext(), "Table location should not exist"); + assertThat(fileSystem.listFiles(tableLocation).hasNext()) + .describedAs("Table location should not exist") + .isFalse(); // try to drop table assertUpdate("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); } // Verify the accuracy of Trino metadata tables while retrieving Iceberg table metadata from the underlying `TrinoCatalog` implementation diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java index 92bfa3778f10..95929f03fd16 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java @@ -150,13 +150,9 @@ import static java.util.stream.IntStream.range; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.offset; import static org.junit.jupiter.api.Assumptions.abort; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotEquals; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) public abstract class BaseIcebergConnectorTest @@ -234,13 +230,13 @@ public void testAddRowFieldCaseInsensitivity() try (TestTable table = new TestTable(getQueryRunner()::execute, "test_add_row_field_case_insensitivity_", "AS SELECT CAST(row(row(2)) AS row(\"CHILD\" row(grandchild_1 integer))) AS col")) { - assertEquals(getColumnType(table.getName(), "col"), "row(CHILD row(grandchild_1 integer))"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(CHILD row(grandchild_1 integer))"); assertUpdate("ALTER TABLE " + table.getName() + " ADD COLUMN col.child.grandchild_2 integer"); - assertEquals(getColumnType(table.getName(), "col"), "row(CHILD row(grandchild_1 integer, grandchild_2 integer))"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(CHILD row(grandchild_1 integer, grandchild_2 integer))"); assertUpdate("ALTER TABLE " + table.getName() + " ADD COLUMN col.CHILD.grandchild_3 integer"); - assertEquals(getColumnType(table.getName(), "col"), "row(CHILD row(grandchild_1 integer, grandchild_2 integer, grandchild_3 integer))"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(CHILD row(grandchild_1 integer, grandchild_2 integer, grandchild_3 integer))"); } } @@ -1064,25 +1060,23 @@ public void testCreatePartitionedTableAs() "FROM tpch.tiny.orders", "SELECT count(*) from orders"); - assertEquals( - computeScalar("SHOW CREATE TABLE test_create_partitioned_table_as"), - format( - "CREATE TABLE %s.%s.%s (\n" + - " \"order key\" bigint,\n" + - " ship_priority integer,\n" + - " order_status varchar\n" + - ")\n" + - "WITH (\n" + - " format = '%s',\n" + - " format_version = 2,\n" + - " location = '%s',\n" + - " partitioning = ARRAY['order_status','ship_priority','bucket(\"order key\", 9)']\n" + - ")", - getSession().getCatalog().orElseThrow(), - getSession().getSchema().orElseThrow(), - "test_create_partitioned_table_as", - format, - tempDirPath)); + assertThat(computeScalar("SHOW CREATE TABLE test_create_partitioned_table_as")).isEqualTo(format( + "CREATE TABLE %s.%s.%s (\n" + + " \"order key\" bigint,\n" + + " ship_priority integer,\n" + + " order_status varchar\n" + + ")\n" + + "WITH (\n" + + " format = '%s',\n" + + " format_version = 2,\n" + + " location = '%s',\n" + + " partitioning = ARRAY['order_status','ship_priority','bucket(\"order key\", 9)']\n" + + ")", + getSession().getCatalog().orElseThrow(), + getSession().getSchema().orElseThrow(), + "test_create_partitioned_table_as", + format, + tempDirPath)); assertQuery("SELECT * from test_create_partitioned_table_as", "SELECT orderkey, shippriority, orderstatus FROM orders"); @@ -1303,10 +1297,10 @@ public void testSortOrderChange() for (Object filePath : computeActual("SELECT file_path from \"" + table.getName() + "$files\"").getOnlyColumnAsSet()) { String path = (String) filePath; if (sortedByComment.contains(path)) { - assertTrue(isFileSorted(path, "comment")); + assertThat(isFileSorted(path, "comment")).isTrue(); } else { - assertTrue(isFileSorted(path, "name")); + assertThat(isFileSorted(path, "name")).isTrue(); } } assertQuery("SELECT * FROM " + table.getName(), "SELECT * FROM nation UNION ALL SELECT * FROM nation"); @@ -1325,7 +1319,7 @@ public void testSortingDisabled() "WITH (sorted_by = ARRAY['comment']) AS SELECT * FROM nation WITH NO DATA")) { assertUpdate(withSortingDisabled, "INSERT INTO " + table.getName() + " SELECT * FROM nation", 25); for (Object filePath : computeActual("SELECT file_path from \"" + table.getName() + "$files\"").getOnlyColumnAsSet()) { - assertFalse(isFileSorted((String) filePath, "comment")); + assertThat(isFileSorted((String) filePath, "comment")).isFalse(); } assertQuery("SELECT * FROM " + table.getName(), "SELECT * FROM nation"); } @@ -1347,7 +1341,7 @@ public void testOptimizeWithSortOrder() assertUpdate(withSingleWriterPerTask(withSmallRowGroups), "ALTER TABLE " + table.getName() + " EXECUTE optimize"); for (Object filePath : computeActual("SELECT file_path from \"" + table.getName() + "$files\"").getOnlyColumnAsSet()) { - assertTrue(isFileSorted((String) filePath, "comment")); + assertThat(isFileSorted((String) filePath, "comment")).isTrue(); } assertQuery("SELECT * FROM " + table.getName(), "SELECT * FROM nation"); } @@ -1372,7 +1366,7 @@ public void testUpdateWithSortOrder() "SELECT orderkey, partkey, suppkey, linenumber, quantity, extendedprice, discount, tax, returnflag, linestatus, shipdate, " + "commitdate, receiptdate, shipinstruct, shipmode, substring(comment, 2) FROM lineitem"); for (Object filePath : computeActual("SELECT file_path from \"" + table.getName() + "$files\"").getOnlyColumnAsSet()) { - assertTrue(isFileSorted((String) filePath, "comment")); + assertThat(isFileSorted((String) filePath, "comment")).isTrue(); } } } @@ -1434,17 +1428,17 @@ public void testTableComments() ")"; String createTableSql = format(createTableTemplate, "test table comment", format); assertUpdate(createTableSql); - assertEquals(computeScalar("SHOW CREATE TABLE test_table_comments"), createTableSql); + assertThat(computeScalar("SHOW CREATE TABLE test_table_comments")).isEqualTo(createTableSql); assertUpdate("COMMENT ON TABLE test_table_comments IS 'different test table comment'"); - assertEquals(computeScalar("SHOW CREATE TABLE test_table_comments"), format(createTableTemplate, "different test table comment", format)); + assertThat(computeScalar("SHOW CREATE TABLE test_table_comments")).isEqualTo(format(createTableTemplate, "different test table comment", format)); assertUpdate("COMMENT ON TABLE test_table_comments IS NULL"); - assertEquals(computeScalar("SHOW CREATE TABLE test_table_comments"), createTableWithoutComment); + assertThat(computeScalar("SHOW CREATE TABLE test_table_comments")).isEqualTo(createTableWithoutComment); dropTable("iceberg.tpch.test_table_comments"); assertUpdate(createTableWithoutComment); - assertEquals(computeScalar("SHOW CREATE TABLE test_table_comments"), createTableWithoutComment); + assertThat(computeScalar("SHOW CREATE TABLE test_table_comments")).isEqualTo(createTableWithoutComment); dropTable("iceberg.tpch.test_table_comments"); } @@ -1470,7 +1464,7 @@ public void testRollbackSnapshot() assertQuery("SELECT * FROM test_rollback ORDER BY col0", "VALUES (123, CAST(987 AS BIGINT))"); assertUpdate(format("CALL system.rollback_to_snapshot('tpch', 'test_rollback', %s)", afterCreateTableId)); - assertEquals((long) computeActual("SELECT COUNT(*) FROM test_rollback").getOnlyValue(), 0); + assertThat((long) computeActual("SELECT COUNT(*) FROM test_rollback").getOnlyValue()).isEqualTo(0); assertUpdate("INSERT INTO test_rollback (col0, col1) VALUES (789, CAST(987 AS BIGINT))", 1); long afterSecondInsertId = getCurrentSnapshotId("test_rollback"); @@ -1668,48 +1662,42 @@ private void testCreateTableLikeForFormat(IcebergFileFormat otherFormat) // For this reason the source and the copied table will share the same directory. // This test does not drop intentionally the created tables to avoid affecting the source table or the information_schema. assertUpdate(format("CREATE TABLE test_create_table_like_original (col1 INTEGER, aDate DATE) WITH(format = '%s', location = '%s', partitioning = ARRAY['aDate'])", format, tempDirPath)); - assertEquals( - getTablePropertiesString("test_create_table_like_original"), - format( - """ - WITH ( - format = '%s', - format_version = 2, - location = '%s', - partitioning = ARRAY['adate'] - )""", - format, - tempDirPath)); + assertThat(getTablePropertiesString("test_create_table_like_original")).isEqualTo(format( + """ + WITH ( + format = '%s', + format_version = 2, + location = '%s', + partitioning = ARRAY['adate'] + )""", + format, + tempDirPath)); assertUpdate("CREATE TABLE test_create_table_like_copy0 (LIKE test_create_table_like_original, col2 INTEGER)"); assertUpdate("INSERT INTO test_create_table_like_copy0 (col1, aDate, col2) VALUES (1, CAST('1950-06-28' AS DATE), 3)", 1); assertQuery("SELECT * from test_create_table_like_copy0", "VALUES(1, CAST('1950-06-28' AS DATE), 3)"); assertUpdate("CREATE TABLE test_create_table_like_copy1 (LIKE test_create_table_like_original)"); - assertEquals( - getTablePropertiesString("test_create_table_like_copy1"), - format( - """ - WITH ( - format = '%s', - format_version = 2, - location = '%s' - )""", - format, - getTableLocation("test_create_table_like_copy1"))); + assertThat(getTablePropertiesString("test_create_table_like_copy1")).isEqualTo(format( + """ + WITH ( + format = '%s', + format_version = 2, + location = '%s' + )""", + format, + getTableLocation("test_create_table_like_copy1"))); assertUpdate("CREATE TABLE test_create_table_like_copy2 (LIKE test_create_table_like_original EXCLUDING PROPERTIES)"); - assertEquals( - getTablePropertiesString("test_create_table_like_copy2"), - format( - """ - WITH ( - format = '%s', - format_version = 2, - location = '%s' - )""", - format, - getTableLocation("test_create_table_like_copy2"))); + assertThat(getTablePropertiesString("test_create_table_like_copy2")).isEqualTo(format( + """ + WITH ( + format = '%s', + format_version = 2, + location = '%s' + )""", + format, + getTableLocation("test_create_table_like_copy2"))); dropTable("test_create_table_like_copy2"); assertQueryFails("CREATE TABLE test_create_table_like_copy3 (LIKE test_create_table_like_original INCLUDING PROPERTIES)", @@ -3648,34 +3636,34 @@ public void testPartitionedTableStatistics() assertUpdate("INSERT INTO test_partitioned_table_statistics VALUES (100, 10)", 1); MaterializedResult result = computeActual("SHOW STATS FOR iceberg.tpch.test_partitioned_table_statistics"); - assertEquals(result.getRowCount(), 3); + assertThat(result.getRowCount()).isEqualTo(3); MaterializedRow row0 = result.getMaterializedRows().get(0); - assertEquals(row0.getField(0), "col1"); - assertEquals(row0.getField(3), 0.0); + assertThat(row0.getField(0)).isEqualTo("col1"); + assertThat(row0.getField(3)).isEqualTo(0.0); if (format != AVRO) { - assertEquals(row0.getField(5), "-10.0"); - assertEquals(row0.getField(6), "100.0"); + assertThat(row0.getField(5)).isEqualTo("-10.0"); + assertThat(row0.getField(6)).isEqualTo("100.0"); } else { - assertNull(row0.getField(5)); - assertNull(row0.getField(6)); + assertThat(row0.getField(5)).isNull(); + assertThat(row0.getField(6)).isNull(); } MaterializedRow row1 = result.getMaterializedRows().get(1); - assertEquals(row1.getField(0), "col2"); - assertEquals(row1.getField(3), 0.0); + assertThat(row1.getField(0)).isEqualTo("col2"); + assertThat(row1.getField(3)).isEqualTo(0.0); if (format != AVRO) { - assertEquals(row1.getField(5), "-1"); - assertEquals(row1.getField(6), "10"); + assertThat(row1.getField(5)).isEqualTo("-1"); + assertThat(row1.getField(6)).isEqualTo("10"); } else { - assertNull(row0.getField(5)); - assertNull(row0.getField(6)); + assertThat(row0.getField(5)).isNull(); + assertThat(row0.getField(6)).isNull(); } MaterializedRow row2 = result.getMaterializedRows().get(2); - assertEquals(row2.getField(4), 2.0); + assertThat(row2.getField(4)).isEqualTo(2.0); assertUpdate("INSERT INTO test_partitioned_table_statistics VALUES " + IntStream.rangeClosed(1, 5) .mapToObj(i -> format("(%d, 10)", i + 100)) @@ -3686,35 +3674,35 @@ public void testPartitionedTableStatistics() .collect(joining(", ")), 5); result = computeActual("SHOW STATS FOR iceberg.tpch.test_partitioned_table_statistics"); - assertEquals(result.getRowCount(), 3); + assertThat(result.getRowCount()).isEqualTo(3); row0 = result.getMaterializedRows().get(0); - assertEquals(row0.getField(0), "col1"); + assertThat(row0.getField(0)).isEqualTo("col1"); if (format != AVRO) { - assertEquals((double) row0.getField(3), 5.0 / 12.0, 1e-10); - assertEquals(row0.getField(5), "-10.0"); - assertEquals(row0.getField(6), "105.0"); + assertThat((double) row0.getField(3)).isCloseTo(5.0 / 12.0, offset(1e-10)); + assertThat(row0.getField(5)).isEqualTo("-10.0"); + assertThat(row0.getField(6)).isEqualTo("105.0"); } else { - assertEquals(row0.getField(3), 0.1); - assertNull(row0.getField(5)); - assertNull(row0.getField(6)); + assertThat(row0.getField(3)).isEqualTo(0.1); + assertThat(row0.getField(5)).isNull(); + assertThat(row0.getField(6)).isNull(); } row1 = result.getMaterializedRows().get(1); - assertEquals(row1.getField(0), "col2"); + assertThat(row1.getField(0)).isEqualTo("col2"); if (format != AVRO) { - assertEquals(row1.getField(3), 0.0); - assertEquals(row1.getField(5), "-1"); - assertEquals(row1.getField(6), "10"); + assertThat(row1.getField(3)).isEqualTo(0.0); + assertThat(row1.getField(5)).isEqualTo("-1"); + assertThat(row1.getField(6)).isEqualTo("10"); } else { - assertEquals(row0.getField(3), 0.1); - assertNull(row0.getField(5)); - assertNull(row0.getField(6)); + assertThat(row0.getField(3)).isEqualTo(0.1); + assertThat(row0.getField(5)).isNull(); + assertThat(row0.getField(6)).isNull(); } row2 = result.getMaterializedRows().get(2); - assertEquals(row2.getField(4), 12.0); + assertThat(row2.getField(4)).isEqualTo(12.0); assertUpdate("INSERT INTO test_partitioned_table_statistics VALUES " + IntStream.rangeClosed(6, 10) .mapToObj(i -> "(100, NULL)") @@ -3722,33 +3710,33 @@ public void testPartitionedTableStatistics() result = computeActual("SHOW STATS FOR iceberg.tpch.test_partitioned_table_statistics"); row0 = result.getMaterializedRows().get(0); - assertEquals(row0.getField(0), "col1"); + assertThat(row0.getField(0)).isEqualTo("col1"); if (format != AVRO) { - assertEquals(row0.getField(3), 5.0 / 17.0); - assertEquals(row0.getField(5), "-10.0"); - assertEquals(row0.getField(6), "105.0"); + assertThat(row0.getField(3)).isEqualTo(5.0 / 17.0); + assertThat(row0.getField(5)).isEqualTo("-10.0"); + assertThat(row0.getField(6)).isEqualTo("105.0"); } else { - assertEquals(row0.getField(3), 0.1); - assertNull(row0.getField(5)); - assertNull(row0.getField(6)); + assertThat(row0.getField(3)).isEqualTo(0.1); + assertThat(row0.getField(5)).isNull(); + assertThat(row0.getField(6)).isNull(); } row1 = result.getMaterializedRows().get(1); - assertEquals(row1.getField(0), "col2"); + assertThat(row1.getField(0)).isEqualTo("col2"); if (format != AVRO) { - assertEquals(row1.getField(3), 5.0 / 17.0); - assertEquals(row1.getField(5), "-1"); - assertEquals(row1.getField(6), "10"); + assertThat(row1.getField(3)).isEqualTo(5.0 / 17.0); + assertThat(row1.getField(5)).isEqualTo("-1"); + assertThat(row1.getField(6)).isEqualTo("10"); } else { - assertEquals(row0.getField(3), 0.1); - assertNull(row0.getField(5)); - assertNull(row0.getField(6)); + assertThat(row0.getField(3)).isEqualTo(0.1); + assertThat(row0.getField(5)).isNull(); + assertThat(row0.getField(6)).isNull(); } row2 = result.getMaterializedRows().get(2); - assertEquals(row2.getField(4), 17.0); + assertThat(row2.getField(4)).isEqualTo(17.0); dropTable("iceberg.tpch.test_partitioned_table_statistics"); } @@ -3915,20 +3903,16 @@ private void assertFilterPushdown( Optional> result = metadata.applyFilter(session, table, new Constraint(domains)); - assertEquals((expectedUnenforcedPredicate == null && expectedEnforcedPredicate == null), result.isEmpty()); + assertThat((expectedUnenforcedPredicate == null && expectedEnforcedPredicate == null)).isEqualTo(result.isEmpty()); if (result.isPresent()) { IcebergTableHandle newTable = (IcebergTableHandle) result.get().getHandle().getConnectorHandle(); - assertEquals( - newTable.getEnforcedPredicate(), - TupleDomain.withColumnDomains(expectedEnforcedPredicate.entrySet().stream() - .collect(toImmutableMap(entry -> columns.get(entry.getKey()), Map.Entry::getValue)))); + assertThat(newTable.getEnforcedPredicate()).isEqualTo(TupleDomain.withColumnDomains(expectedEnforcedPredicate.entrySet().stream() + .collect(toImmutableMap(entry -> columns.get(entry.getKey()), Map.Entry::getValue)))); - assertEquals( - newTable.getUnenforcedPredicate(), - TupleDomain.withColumnDomains(expectedUnenforcedPredicate.entrySet().stream() - .collect(toImmutableMap(entry -> columns.get(entry.getKey()), Map.Entry::getValue)))); + assertThat(newTable.getUnenforcedPredicate()).isEqualTo(TupleDomain.withColumnDomains(expectedUnenforcedPredicate.entrySet().stream() + .collect(toImmutableMap(entry -> columns.get(entry.getKey()), Map.Entry::getValue)))); } }); } @@ -3963,7 +3947,7 @@ public void testCreateNestedPartitionedTable() " (CAST(ROW(null, 'this is a random value') AS ROW(int, varchar))), " + " DATE '2021-07-24'", 1); - assertEquals(computeActual("SELECT * from test_nested_table_1").getRowCount(), 1); + assertThat(computeActual("SELECT * from test_nested_table_1").getRowCount()).isEqualTo(1); if (format != AVRO) { assertThat(query("SHOW STATS FOR test_nested_table_1")) @@ -4027,7 +4011,7 @@ public void testCreateNestedPartitionedTable() "map(array[1,2], array[array['ek', 'one'], array['don', 'do', 'two']]), CAST(1.0 as DECIMAL(5,2)), " + "CAST(ROW(1, 'this is a random value', null) AS ROW(int, varchar, array(int))), 'one'", 1); - assertEquals(computeActual("SELECT * from test_nested_table_2").getRowCount(), 1); + assertThat(computeActual("SELECT * from test_nested_table_2").getRowCount()).isEqualTo(1); if (format != AVRO) { assertThat(query("SHOW STATS FOR test_nested_table_2")) @@ -4062,7 +4046,7 @@ public void testCreateNestedPartitionedTable() assertUpdate("CREATE TABLE test_nested_table_3 WITH (partitioning = ARRAY['int']) AS SELECT * FROM test_nested_table_2", 1); - assertEquals(computeActual("SELECT * FROM test_nested_table_3").getRowCount(), 1); + assertThat(computeActual("SELECT * FROM test_nested_table_3").getRowCount()).isEqualTo(1); assertThat(query("SHOW STATS FOR test_nested_table_3")) .matches("SHOW STATS FOR test_nested_table_2"); @@ -4102,7 +4086,7 @@ private void dropTable(String table) { Session session = getSession(); assertUpdate(session, "DROP TABLE " + table); - assertFalse(getQueryRunner().tableExists(session, table)); + assertThat(getQueryRunner().tableExists(session, table)).isFalse(); } @Test @@ -4171,7 +4155,7 @@ public void testIncorrectIcebergFileSizes() // Get manifest file MaterializedResult result = computeActual("SELECT path FROM \"test_iceberg_file_size$manifests\""); - assertEquals(result.getRowCount(), 1); + assertThat(result.getRowCount()).isEqualTo(1); String manifestFile = (String) result.getOnlyValue(); // Read manifest file @@ -4184,13 +4168,14 @@ public void testIncorrectIcebergFileSizes() entry = dataFileReader.next(); recordCount++; } - assertEquals(recordCount, 1); + assertThat(recordCount).isEqualTo(1); } // Alter data file entry to store incorrect file size GenericData.Record dataFile = (GenericData.Record) entry.get("data_file"); long alteredValue = 50L; - assertNotEquals(dataFile.get("file_size_in_bytes"), alteredValue); + assertThat(dataFile.get("file_size_in_bytes")) + .isNotEqualTo(alteredValue); dataFile.put("file_size_in_bytes", alteredValue); // Write altered metadata @@ -5471,7 +5456,8 @@ public void testFileModifiedTimeHiddenColumn() storageTimePrecision.sleep(1); assertUpdate("INSERT INTO " + table.getName() + " VALUES (2)", 1); ZonedDateTime anotherFileModifiedTime = (ZonedDateTime) computeScalar("SELECT max(\"$file_modified_time\") FROM " + table.getName()); - assertNotEquals(fileModifiedTime, anotherFileModifiedTime); + assertThat(fileModifiedTime) + .isNotEqualTo(anotherFileModifiedTime); assertThat(anotherFileModifiedTime).isAfter(fileModifiedTime); // to detect potential clock backward adjustment assertThat(query("SELECT col FROM " + table.getName() + " WHERE \"$file_modified_time\" = from_iso8601_timestamp('" + fileModifiedTime.format(ISO_OFFSET_DATE_TIME) + "')")) @@ -6766,12 +6752,16 @@ public void testDropTableWithMissingMetadataFile() // Delete current metadata file fileSystem.deleteFile(metadataLocation); - assertFalse(fileSystem.newInputFile(metadataLocation).exists(), "Current metadata file should not exist"); + assertThat(fileSystem.newInputFile(metadataLocation).exists()) + .describedAs("Current metadata file should not exist") + .isFalse(); // try to drop table assertUpdate("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); - assertFalse(fileSystem.listFiles(Location.of(tableLocation)).hasNext(), "Table location should not exist"); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); + assertThat(fileSystem.listFiles(Location.of(tableLocation)).hasNext()) + .describedAs("Table location should not exist") + .isFalse(); } @Test @@ -6788,12 +6778,16 @@ public void testDropTableWithMissingSnapshotFile() // Delete current snapshot file fileSystem.deleteFile(currentSnapshotFile); - assertFalse(fileSystem.newInputFile(currentSnapshotFile).exists(), "Current snapshot file should not exist"); + assertThat(fileSystem.newInputFile(currentSnapshotFile).exists()) + .describedAs("Current snapshot file should not exist") + .isFalse(); // try to drop table assertUpdate("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); - assertFalse(fileSystem.listFiles(Location.of(tableLocation)).hasNext(), "Table location should not exist"); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); + assertThat(fileSystem.listFiles(Location.of(tableLocation)).hasNext()) + .describedAs("Table location should not exist") + .isFalse(); } @Test @@ -6811,12 +6805,16 @@ public void testDropTableWithMissingManifestListFile() // Delete Manifest List file fileSystem.deleteFile(manifestListFile); - assertFalse(fileSystem.newInputFile(manifestListFile).exists(), "Manifest list file should not exist"); + assertThat(fileSystem.newInputFile(manifestListFile).exists()) + .describedAs("Manifest list file should not exist") + .isFalse(); // try to drop table assertUpdate("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); - assertFalse(fileSystem.listFiles(Location.of(tableLocation)).hasNext(), "Table location should not exist"); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); + assertThat(fileSystem.listFiles(Location.of(tableLocation)).hasNext()) + .describedAs("Table location should not exist") + .isFalse(); } @Test @@ -6830,17 +6828,21 @@ public void testDropTableWithMissingDataFile() Location tableLocation = Location.of(getTableLocation(tableName)); Location tableDataPath = tableLocation.appendPath("data"); FileIterator fileIterator = fileSystem.listFiles(tableDataPath); - assertTrue(fileIterator.hasNext()); + assertThat(fileIterator.hasNext()).isTrue(); Location dataFile = fileIterator.next().location(); // Delete data file fileSystem.deleteFile(dataFile); - assertFalse(fileSystem.newInputFile(dataFile).exists(), "Data file should not exist"); + assertThat(fileSystem.newInputFile(dataFile).exists()) + .describedAs("Data file should not exist") + .isFalse(); // try to drop table assertUpdate("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); - assertFalse(fileSystem.listFiles(tableLocation).hasNext(), "Table location should not exist"); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); + assertThat(fileSystem.listFiles(tableLocation).hasNext()) + .describedAs("Table location should not exist") + .isFalse(); } @Test @@ -6855,11 +6857,13 @@ public void testDropTableWithNonExistentTableLocation() // Delete table location fileSystem.deleteDirectory(tableLocation); - assertFalse(fileSystem.listFiles(tableLocation).hasNext(), "Table location should not exist"); + assertThat(fileSystem.listFiles(tableLocation).hasNext()) + .describedAs("Table location should not exist") + .isFalse(); // try to drop table assertUpdate("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); } @Test @@ -6876,7 +6880,9 @@ public void testCorruptedTableLocation() // break the table by deleting all metadata files fileSystem.deleteDirectory(metadataLocation); - assertFalse(fileSystem.listFiles(metadataLocation).hasNext(), "Metadata location should not exist"); + assertThat(fileSystem.listFiles(metadataLocation).hasNext()) + .describedAs("Metadata location should not exist") + .isFalse(); // Assert queries fail cleanly assertQueryFails("TABLE " + tableName, "Metadata not found in metadata location for table " + schemaTableName); @@ -6914,8 +6920,10 @@ public void testCorruptedTableLocation() // DROP TABLE should succeed so that users can remove their corrupted table assertQuerySucceeds("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); - assertFalse(fileSystem.listFiles(tableLocation).hasNext(), "Table location should not exist"); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); + assertThat(fileSystem.listFiles(tableLocation).hasNext()) + .describedAs("Table location should not exist") + .isFalse(); } @Test @@ -6965,12 +6973,16 @@ public void testDropCorruptedTableWithHiveRedirection() // break the table by deleting all metadata files fileSystem.deleteDirectory(metadataLocation); - assertFalse(fileSystem.listFiles(metadataLocation).hasNext(), "Metadata location should not exist"); + assertThat(fileSystem.listFiles(metadataLocation).hasNext()) + .describedAs("Metadata location should not exist") + .isFalse(); // DROP TABLE should succeed using hive redirection queryRunner.execute("DROP TABLE " + hiveTableName); - assertFalse(queryRunner.tableExists(getSession(), icebergTableName)); - assertFalse(fileSystem.listFiles(tableLocation).hasNext(), "Table location should not exist"); + assertThat(queryRunner.tableExists(getSession(), icebergTableName)).isFalse(); + assertThat(fileSystem.listFiles(tableLocation).hasNext()) + .describedAs("Table location should not exist") + .isFalse(); } @Test @@ -7301,7 +7313,7 @@ public void testTimestampPrecisionOnCreateTableAsSelect() getQueryRunner()::execute, "test_coercion_show_create_table", format("AS SELECT %s a", setup.sourceValueLiteral))) { - assertEquals(getColumnType(testTable.getName(), "a"), setup.newColumnType); + assertThat(getColumnType(testTable.getName(), "a")).isEqualTo(setup.newColumnType); assertQuery( format("SELECT * FROM %s", testTable.getName()), format("VALUES (%s)", setup.newValueLiteral)); @@ -7317,7 +7329,7 @@ public void testTimestampPrecisionOnCreateTableAsSelectWithNoData() getQueryRunner()::execute, "test_coercion_show_create_table", format("AS SELECT %s a WITH NO DATA", setup.sourceValueLiteral))) { - assertEquals(getColumnType(testTable.getName(), "a"), setup.newColumnType); + assertThat(getColumnType(testTable.getName(), "a")).isEqualTo(setup.newColumnType); } } } @@ -7417,7 +7429,7 @@ private void testTimePrecisionOnCreateTableAsSelect(String inputType, String tab getQueryRunner()::execute, "test_coercion_show_create_table", format("AS SELECT %s a", inputType))) { - assertEquals(getColumnType(testTable.getName(), "a"), tableType); + assertThat(getColumnType(testTable.getName(), "a")).isEqualTo(tableType); assertQuery( format("SELECT * FROM %s", testTable.getName()), format("VALUES (%s)", tableValue)); @@ -7460,7 +7472,7 @@ private void testTimePrecisionOnCreateTableAsSelectWithNoData(String inputType, getQueryRunner()::execute, "test_coercion_show_create_table", format("AS SELECT %s a WITH NO DATA", inputType))) { - assertEquals(getColumnType(testTable.getName(), "a"), tableType); + assertThat(getColumnType(testTable.getName(), "a")).isEqualTo(tableType); } } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergMaterializedViewTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergMaterializedViewTest.java index 45b0e5ed2246..9f3820d21410 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergMaterializedViewTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergMaterializedViewTest.java @@ -50,7 +50,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -281,40 +280,40 @@ public void testCreateRefreshSelect() // 4. Select the data from refreshed materialized view, verify the number of rows in the result // 5. Ensure that the plan uses the storage table // 6. In some cases validate the result data - assertEquals(computeActual("SELECT * FROM materialized_view_no_part").getRowCount(), 6); + assertThat(computeActual("SELECT * FROM materialized_view_no_part").getRowCount()).isEqualTo(6); assertThat(getExplainPlan("SELECT * FROM materialized_view_no_part", ExplainType.Type.IO)) .contains("base_table1"); assertUpdate("REFRESH MATERIALIZED VIEW materialized_view_no_part", 6); - assertEquals(computeActual("SELECT * FROM materialized_view_no_part").getRowCount(), 6); + assertThat(computeActual("SELECT * FROM materialized_view_no_part").getRowCount()).isEqualTo(6); assertThat(getExplainPlan("SELECT * FROM materialized_view_no_part", ExplainType.Type.IO)).doesNotContain("base_table1"); - assertEquals(computeActual("SELECT * FROM materialized_view_agg").getRowCount(), 3); + assertThat(computeActual("SELECT * FROM materialized_view_agg").getRowCount()).isEqualTo(3); assertThat(getExplainPlan("SELECT * FROM materialized_view_agg", ExplainType.Type.IO)) .contains("base_table1"); assertUpdate("REFRESH MATERIALIZED VIEW materialized_view_agg", 3); - assertEquals(computeActual("SELECT * FROM materialized_view_agg").getRowCount(), 3); + assertThat(computeActual("SELECT * FROM materialized_view_agg").getRowCount()).isEqualTo(3); assertThat(getExplainPlan("SELECT * FROM materialized_view_agg", ExplainType.Type.IO)) .doesNotContain("base_table1"); assertQuery(session, "SELECT * FROM materialized_view_agg", "VALUES (DATE '2019-09-10', 2)," + "(DATE '2019-09-08', 1), (DATE '2019-09-09', 3)"); - assertEquals(computeActual("SELECT * FROM materialized_view_part").getRowCount(), 3); + assertThat(computeActual("SELECT * FROM materialized_view_part").getRowCount()).isEqualTo(3); assertThat(getExplainPlan("SELECT * FROM materialized_view_part", ExplainType.Type.IO)) .contains("base_table1"); assertUpdate("REFRESH MATERIALIZED VIEW materialized_view_part", 3); - assertEquals(computeActual("SELECT * FROM materialized_view_part").getRowCount(), 3); + assertThat(computeActual("SELECT * FROM materialized_view_part").getRowCount()).isEqualTo(3); assertThat(getExplainPlan("SELECT * FROM materialized_view_part", ExplainType.Type.IO)).doesNotContain("base_table1"); - assertEquals(computeActual("SELECT * FROM materialized_view_join").getRowCount(), 5); + assertThat(computeActual("SELECT * FROM materialized_view_join").getRowCount()).isEqualTo(5); assertThat(getExplainPlan("SELECT * FROM materialized_view_join", ExplainType.Type.IO)).contains("base_table1", "base_table2"); assertUpdate("REFRESH MATERIALIZED VIEW materialized_view_join", 5); - assertEquals(computeActual("SELECT * FROM materialized_view_join").getRowCount(), 5); + assertThat(computeActual("SELECT * FROM materialized_view_join").getRowCount()).isEqualTo(5); assertThat(getExplainPlan("SELECT * FROM materialized_view_join", ExplainType.Type.IO)).doesNotContain("base_table1", "base_table2"); - assertEquals(computeActual("SELECT * FROM materialized_view_join_part").getRowCount(), 4); + assertThat(computeActual("SELECT * FROM materialized_view_join_part").getRowCount()).isEqualTo(4); assertThat(getExplainPlan("SELECT * FROM materialized_view_join_part", ExplainType.Type.IO)).contains("base_table1", "base_table2"); assertUpdate("REFRESH MATERIALIZED VIEW materialized_view_join_part", 4); - assertEquals(computeActual("SELECT * FROM materialized_view_join_part").getRowCount(), 4); + assertThat(computeActual("SELECT * FROM materialized_view_join_part").getRowCount()).isEqualTo(4); assertThat(getExplainPlan("SELECT * FROM materialized_view_join_part", ExplainType.Type.IO)).doesNotContain("base_table1", "base_table2"); assertQuery(session, "SELECT * FROM materialized_view_join_part", "VALUES (2, 'a', DATE '2019-09-09', 1), " + "(0, 'a', DATE '2019-09-08', 2), (3, 'a', DATE '2019-09-09', 1), (1, 'a', DATE '2019-09-09', 1)"); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergSystemTables.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergSystemTables.java index c64668c8b50a..a970c4611d36 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergSystemTables.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergSystemTables.java @@ -33,7 +33,6 @@ import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) public abstract class BaseIcebergSystemTables @@ -116,20 +115,20 @@ public void testPartitionTable() "('data', 'row(_bigint row(min bigint, max bigint, null_count bigint, nan_count bigint))', '', '')"); MaterializedResult result = computeActual("SELECT * from test_schema.\"test_table$partitions\""); - assertEquals(result.getRowCount(), 3); + assertThat(result.getRowCount()).isEqualTo(3); Map rowsByPartition = result.getMaterializedRows().stream() .collect(toImmutableMap(row -> ((LocalDate) ((MaterializedRow) row.getField(0)).getField(0)), Function.identity())); // Test if row counts are computed correctly - assertEquals(rowsByPartition.get(LocalDate.parse("2019-09-08")).getField(1), 1L); - assertEquals(rowsByPartition.get(LocalDate.parse("2019-09-09")).getField(1), 3L); - assertEquals(rowsByPartition.get(LocalDate.parse("2019-09-10")).getField(1), 2L); + assertThat(rowsByPartition.get(LocalDate.parse("2019-09-08")).getField(1)).isEqualTo(1L); + assertThat(rowsByPartition.get(LocalDate.parse("2019-09-09")).getField(1)).isEqualTo(3L); + assertThat(rowsByPartition.get(LocalDate.parse("2019-09-10")).getField(1)).isEqualTo(2L); // Test if min/max values, null value count and nan value count are computed correctly. - assertEquals(rowsByPartition.get(LocalDate.parse("2019-09-08")).getField(4), new MaterializedRow(DEFAULT_PRECISION, new MaterializedRow(DEFAULT_PRECISION, 0L, 0L, 0L, null))); - assertEquals(rowsByPartition.get(LocalDate.parse("2019-09-09")).getField(4), new MaterializedRow(DEFAULT_PRECISION, new MaterializedRow(DEFAULT_PRECISION, 1L, 3L, 0L, null))); - assertEquals(rowsByPartition.get(LocalDate.parse("2019-09-10")).getField(4), new MaterializedRow(DEFAULT_PRECISION, new MaterializedRow(DEFAULT_PRECISION, 4L, 5L, 0L, null))); + assertThat(rowsByPartition.get(LocalDate.parse("2019-09-08")).getField(4)).isEqualTo(new MaterializedRow(DEFAULT_PRECISION, new MaterializedRow(DEFAULT_PRECISION, 0L, 0L, 0L, null))); + assertThat(rowsByPartition.get(LocalDate.parse("2019-09-09")).getField(4)).isEqualTo(new MaterializedRow(DEFAULT_PRECISION, new MaterializedRow(DEFAULT_PRECISION, 1L, 3L, 0L, null))); + assertThat(rowsByPartition.get(LocalDate.parse("2019-09-10")).getField(4)).isEqualTo(new MaterializedRow(DEFAULT_PRECISION, new MaterializedRow(DEFAULT_PRECISION, 4L, 5L, 0L, null))); } @Test @@ -138,52 +137,44 @@ public void testPartitionTableWithNan() assertQuery("SELECT count(*) FROM test_schema.test_table_nan", "VALUES 6"); MaterializedResult result = computeActual("SELECT * from test_schema.\"test_table_nan$partitions\""); - assertEquals(result.getRowCount(), 4); + assertThat(result.getRowCount()).isEqualTo(4); Map rowsByPartition = result.getMaterializedRows().stream() .collect(toImmutableMap(row -> ((LocalDate) ((MaterializedRow) row.getField(0)).getField(0)), Function.identity())); // Test if row counts are computed correctly - assertEquals(rowsByPartition.get(LocalDate.parse("2022-01-01")).getField(1), 1L); - assertEquals(rowsByPartition.get(LocalDate.parse("2022-01-02")).getField(1), 1L); - assertEquals(rowsByPartition.get(LocalDate.parse("2022-01-03")).getField(1), 1L); - assertEquals(rowsByPartition.get(LocalDate.parse("2022-01-04")).getField(1), 3L); + assertThat(rowsByPartition.get(LocalDate.parse("2022-01-01")).getField(1)).isEqualTo(1L); + assertThat(rowsByPartition.get(LocalDate.parse("2022-01-02")).getField(1)).isEqualTo(1L); + assertThat(rowsByPartition.get(LocalDate.parse("2022-01-03")).getField(1)).isEqualTo(1L); + assertThat(rowsByPartition.get(LocalDate.parse("2022-01-04")).getField(1)).isEqualTo(3L); // Test if min/max values, null value count and nan value count are computed correctly. - assertEquals( - rowsByPartition.get(LocalDate.parse("2022-01-01")).getField(4), - new MaterializedRow(DEFAULT_PRECISION, - new MaterializedRow(DEFAULT_PRECISION, 1L, 1L, 0L, null), - new MaterializedRow(DEFAULT_PRECISION, 1.1d, 1.1d, 0L, null), - new MaterializedRow(DEFAULT_PRECISION, 1.2f, 1.2f, 0L, null))); - assertEquals( - rowsByPartition.get(LocalDate.parse("2022-01-02")).getField(4), - new MaterializedRow(DEFAULT_PRECISION, - new MaterializedRow(DEFAULT_PRECISION, 2L, 2L, 0L, null), - new MaterializedRow(DEFAULT_PRECISION, null, null, 0L, nanCount(1L)), - new MaterializedRow(DEFAULT_PRECISION, 2.2f, 2.2f, 0L, null))); - assertEquals( - rowsByPartition.get(LocalDate.parse("2022-01-03")).getField(4), - new MaterializedRow(DEFAULT_PRECISION, - new MaterializedRow(DEFAULT_PRECISION, 3L, 3L, 0L, null), - new MaterializedRow(DEFAULT_PRECISION, 3.3, 3.3d, 0L, null), - new MaterializedRow(DEFAULT_PRECISION, null, null, 0L, nanCount(1L)))); - assertEquals( - rowsByPartition.get(LocalDate.parse("2022-01-04")).getField(4), - new MaterializedRow(DEFAULT_PRECISION, - new MaterializedRow(DEFAULT_PRECISION, 4L, 6L, 0L, null), - new MaterializedRow(DEFAULT_PRECISION, null, null, 0L, nanCount(2L)), - new MaterializedRow(DEFAULT_PRECISION, null, null, 0L, nanCount(2L)))); + assertThat(rowsByPartition.get(LocalDate.parse("2022-01-01")).getField(4)).isEqualTo(new MaterializedRow(DEFAULT_PRECISION, + new MaterializedRow(DEFAULT_PRECISION, 1L, 1L, 0L, null), + new MaterializedRow(DEFAULT_PRECISION, 1.1d, 1.1d, 0L, null), + new MaterializedRow(DEFAULT_PRECISION, 1.2f, 1.2f, 0L, null))); + assertThat(rowsByPartition.get(LocalDate.parse("2022-01-02")).getField(4)).isEqualTo(new MaterializedRow(DEFAULT_PRECISION, + new MaterializedRow(DEFAULT_PRECISION, 2L, 2L, 0L, null), + new MaterializedRow(DEFAULT_PRECISION, null, null, 0L, nanCount(1L)), + new MaterializedRow(DEFAULT_PRECISION, 2.2f, 2.2f, 0L, null))); + assertThat(rowsByPartition.get(LocalDate.parse("2022-01-03")).getField(4)).isEqualTo(new MaterializedRow(DEFAULT_PRECISION, + new MaterializedRow(DEFAULT_PRECISION, 3L, 3L, 0L, null), + new MaterializedRow(DEFAULT_PRECISION, 3.3, 3.3d, 0L, null), + new MaterializedRow(DEFAULT_PRECISION, null, null, 0L, nanCount(1L)))); + assertThat(rowsByPartition.get(LocalDate.parse("2022-01-04")).getField(4)).isEqualTo(new MaterializedRow(DEFAULT_PRECISION, + new MaterializedRow(DEFAULT_PRECISION, 4L, 6L, 0L, null), + new MaterializedRow(DEFAULT_PRECISION, null, null, 0L, nanCount(2L)), + new MaterializedRow(DEFAULT_PRECISION, null, null, 0L, nanCount(2L)))); } @Test public void testPartitionTableOnDropColumn() { MaterializedResult resultAfterDrop = computeActual("SELECT * from test_schema.\"test_table_drop_column$partitions\""); - assertEquals(resultAfterDrop.getRowCount(), 3); + assertThat(resultAfterDrop.getRowCount()).isEqualTo(3); Map rowsByPartitionAfterDrop = resultAfterDrop.getMaterializedRows().stream() .collect(toImmutableMap(row -> ((LocalDate) ((MaterializedRow) row.getField(0)).getField(0)), Function.identity())); - assertEquals(rowsByPartitionAfterDrop.get(LocalDate.parse("2019-09-08")).getField(4), new MaterializedRow(DEFAULT_PRECISION, + assertThat(rowsByPartitionAfterDrop.get(LocalDate.parse("2019-09-08")).getField(4)).isEqualTo(new MaterializedRow(DEFAULT_PRECISION, new MaterializedRow(DEFAULT_PRECISION, 0L, 0L, 0L, null))); } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseSharedMetastoreTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseSharedMetastoreTest.java index 8a867611dd08..5616040e974e 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseSharedMetastoreTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseSharedMetastoreTest.java @@ -25,7 +25,6 @@ import static java.time.ZoneOffset.UTC; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public abstract class BaseSharedMetastoreTest extends AbstractTestQueryFramework @@ -118,21 +117,13 @@ public void testShowSchemas() .containsAll("VALUES '" + schema + "'"); String showCreateHiveSchema = (String) computeActual("SHOW CREATE SCHEMA hive." + schema).getOnlyValue(); - assertEquals( - showCreateHiveSchema, - getExpectedHiveCreateSchema("hive")); + assertThat(showCreateHiveSchema).isEqualTo(getExpectedHiveCreateSchema("hive")); String showCreateIcebergSchema = (String) computeActual("SHOW CREATE SCHEMA iceberg." + schema).getOnlyValue(); - assertEquals( - showCreateIcebergSchema, - getExpectedIcebergCreateSchema("iceberg")); + assertThat(showCreateIcebergSchema).isEqualTo(getExpectedIcebergCreateSchema("iceberg")); String showCreateHiveWithRedirectionsSchema = (String) computeActual("SHOW CREATE SCHEMA hive_with_redirections." + schema).getOnlyValue(); - assertEquals( - showCreateHiveWithRedirectionsSchema, - getExpectedHiveCreateSchema("hive_with_redirections")); + assertThat(showCreateHiveWithRedirectionsSchema).isEqualTo(getExpectedHiveCreateSchema("hive_with_redirections")); String showCreateIcebergWithRedirectionsSchema = (String) computeActual("SHOW CREATE SCHEMA iceberg_with_redirections." + schema).getOnlyValue(); - assertEquals( - showCreateIcebergWithRedirectionsSchema, - getExpectedIcebergCreateSchema("iceberg_with_redirections")); + assertThat(showCreateIcebergWithRedirectionsSchema).isEqualTo(getExpectedIcebergCreateSchema("iceberg_with_redirections")); } @Test diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/DataFileRecord.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/DataFileRecord.java index 249f5e740dba..0157745ae39e 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/DataFileRecord.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/DataFileRecord.java @@ -18,7 +18,7 @@ import java.util.Map; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class DataFileRecord { @@ -37,7 +37,7 @@ public class DataFileRecord @SuppressWarnings("unchecked") public static DataFileRecord toDataFileRecord(MaterializedRow row) { - assertEquals(row.getFieldCount(), 14); + assertThat(row.getFieldCount()).isEqualTo(14); return new DataFileRecord( (int) row.getField(0), (String) row.getField(1), diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergBucketing.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergBucketing.java index fc51c86be43a..f3082b48dc3c 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergBucketing.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergBucketing.java @@ -70,8 +70,8 @@ import static java.lang.String.format; import static java.time.ZoneOffset.UTC; import static org.apache.iceberg.types.Type.TypeID.DECIMAL; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestIcebergBucketing { @@ -238,10 +238,9 @@ private void assertBucketNumberEquals(Type icebergType, Object icebergValue, int Integer icebergBucket = computeIcebergBucket(icebergType, icebergValue, bucketCount); Integer trinoBucket = computeTrinoBucket(icebergType, icebergValue, bucketCount); - assertEquals( - trinoBucket, - icebergBucket, - format("icebergType=%s, bucketCount=%s, icebergBucket=%d, trinoBucket=%d;", icebergType, bucketCount, icebergBucket, trinoBucket)); + assertThat(trinoBucket) + .describedAs(format("icebergType=%s, bucketCount=%s, icebergBucket=%d, trinoBucket=%d;", icebergType, bucketCount, icebergBucket, trinoBucket)) + .isEqualTo(icebergBucket); } private void assertHashEquals(Type icebergType, Object icebergValue, Integer expectedHash) @@ -253,16 +252,14 @@ private void assertHashEquals(Type icebergType, Object icebergValue, Integer exp Integer trinoBucketHash = computeTrinoBucket(icebergType, icebergValue, Integer.MAX_VALUE); // Ensure hash is stable and does not change - assertEquals( - icebergBucketHash, - expectedHash, - format("expected Iceberg %s(%s) bucket with %sd buckets to be %d, got %d", icebergType, icebergValue, Integer.MAX_VALUE, expectedHash, icebergBucketHash)); + assertThat(icebergBucketHash) + .describedAs(format("expected Iceberg %s(%s) bucket with %sd buckets to be %d, got %d", icebergType, icebergValue, Integer.MAX_VALUE, expectedHash, icebergBucketHash)) + .isEqualTo(expectedHash); // Ensure hash is stable and does not change - assertEquals( - trinoBucketHash, - expectedHash, - format("expected Trino %s(%s) bucket with %sd buckets to be %d, got %d", icebergType, icebergValue, Integer.MAX_VALUE, expectedHash, trinoBucketHash)); + assertThat(trinoBucketHash) + .describedAs(format("expected Trino %s(%s) bucket with %sd buckets to be %d, got %d", icebergType, icebergValue, Integer.MAX_VALUE, expectedHash, trinoBucketHash)) + .isEqualTo(expectedHash); } private Integer computeIcebergBucket(Type type, Object icebergValue, int bucketCount) @@ -290,7 +287,7 @@ private Integer computeTrinoBucket(Type icebergType, Object icebergValue, int bu Long trinoBucketWithValue = (Long) transform.getValueTransform().apply(block, 0); Integer trinoBucketWithValueAsInteger = trinoBucketWithValue == null ? null : toIntExact(trinoBucketWithValue); - assertEquals(trinoBucketWithValueAsInteger, trinoBucketWithBlock); + assertThat(trinoBucketWithValueAsInteger).isEqualTo(trinoBucketWithBlock); return trinoBucketWithBlock; } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergColumnHandle.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergColumnHandle.java index b5a49bb851b2..f25ea910e3aa 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergColumnHandle.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergColumnHandle.java @@ -32,7 +32,7 @@ import static io.trino.plugin.iceberg.ColumnIdentity.primitiveColumnIdentity; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestIcebergColumnHandle { @@ -82,14 +82,14 @@ private void testRoundTrip(IcebergColumnHandle expected) String json = codec.toJson(expected); IcebergColumnHandle actual = codec.fromJson(json); - assertEquals(actual, expected); - assertEquals(actual.getBaseColumnIdentity(), expected.getBaseColumnIdentity()); - assertEquals(actual.getBaseType(), expected.getBaseType()); - assertEquals(actual.getQualifiedName(), expected.getQualifiedName()); - assertEquals(actual.getName(), expected.getName()); - assertEquals(actual.getColumnIdentity(), expected.getColumnIdentity()); - assertEquals(actual.getId(), actual.getId()); - assertEquals(actual.getType(), expected.getType()); - assertEquals(actual.getComment(), expected.getComment()); + assertThat(actual).isEqualTo(expected); + assertThat(actual.getBaseColumnIdentity()).isEqualTo(expected.getBaseColumnIdentity()); + assertThat(actual.getBaseType()).isEqualTo(expected.getBaseType()); + assertThat(actual.getQualifiedName()).isEqualTo(expected.getQualifiedName()); + assertThat(actual.getName()).isEqualTo(expected.getName()); + assertThat(actual.getColumnIdentity()).isEqualTo(expected.getColumnIdentity()); + assertThat(actual.getId()).isEqualTo(actual.getId()); + assertThat(actual.getType()).isEqualTo(expected.getType()); + assertThat(actual.getComment()).isEqualTo(expected.getComment()); } } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMergeAppend.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMergeAppend.java index 0997b3768eff..33e15cedb9a8 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMergeAppend.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMergeAppend.java @@ -36,7 +36,7 @@ import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.createPerTransactionCache; import static io.trino.plugin.hive.metastore.file.TestingFileHiveMetastore.createTestingFileHiveMetastore; import static io.trino.plugin.iceberg.IcebergTestUtils.getFileSystemFactory; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestIcebergMergeAppend extends AbstractTestQueryFramework @@ -79,9 +79,9 @@ public void testInsertWithAppend() .commit(); assertUpdate("INSERT INTO table_to_insert VALUES (1, 'a'), (2, 'b'), (3, 'c')", 3); MaterializedResult result = computeActual("select * from \"table_to_insert$manifests\""); - assertEquals(result.getRowCount(), 1); + assertThat(result.getRowCount()).isEqualTo(1); assertUpdate("INSERT INTO table_to_insert VALUES (4, 'd')", 1); result = computeActual("select * from \"table_to_insert$manifests\""); - assertEquals(result.getRowCount(), 1); + assertThat(result.getRowCount()).isEqualTo(1); } } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMigrateProcedure.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMigrateProcedure.java index 10dcb3c5193c..126b116dcb49 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMigrateProcedure.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMigrateProcedure.java @@ -33,7 +33,6 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestIcebergMigrateProcedure extends AbstractTestQueryFramework @@ -286,8 +285,8 @@ public void testMigrateTablePreserveComments() assertUpdate("CREATE TABLE " + hiveTableName + "(col int COMMENT 'column comment') COMMENT 'table comment'"); assertUpdate("CALL iceberg.system.migrate('tpch', '" + tableName + "')"); - assertEquals(getTableComment(tableName), "table comment"); - assertEquals(getColumnComment(tableName, "col"), "column comment"); + assertThat(getTableComment(tableName)).isEqualTo("table comment"); + assertThat(getColumnComment(tableName, "col")).isEqualTo("column comment"); assertUpdate("DROP TABLE " + tableName); } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergNodeLocalDynamicSplitPruning.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergNodeLocalDynamicSplitPruning.java index 581c374e8eb3..ebcaa3dbaa2b 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergNodeLocalDynamicSplitPruning.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergNodeLocalDynamicSplitPruning.java @@ -76,9 +76,7 @@ import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.util.concurrent.CompletableFuture.completedFuture; import static org.apache.iceberg.types.Types.NestedField.optional; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; +import static org.assertj.core.api.Assertions.assertThat; public class TestIcebergNodeLocalDynamicSplitPruning { @@ -114,16 +112,16 @@ public void testDynamicSplitPruning() writeOrcContent(outputFile); try (ConnectorPageSource emptyPageSource = createTestingPageSource(transaction, icebergConfig, inputFile, getDynamicFilter(getTupleDomainForSplitPruning()))) { - assertNull(emptyPageSource.getNextPage()); + assertThat(emptyPageSource.getNextPage()).isNull(); } try (ConnectorPageSource nonEmptyPageSource = createTestingPageSource(transaction, icebergConfig, inputFile, getDynamicFilter(getNonSelectiveTupleDomain()))) { Page page = nonEmptyPageSource.getNextPage(); - assertNotNull(page); - assertEquals(page.getBlock(0).getPositionCount(), 1); - assertEquals(page.getBlock(0).getInt(0, 0), KEY_COLUMN_VALUE); - assertEquals(page.getBlock(1).getPositionCount(), 1); - assertEquals(page.getBlock(1).getSlice(0, 0, page.getBlock(1).getSliceLength(0)).toStringUtf8(), DATA_COLUMN_VALUE); + assertThat(page).isNotNull(); + assertThat(page.getBlock(0).getPositionCount()).isEqualTo(1); + assertThat(page.getBlock(0).getInt(0, 0)).isEqualTo(KEY_COLUMN_VALUE); + assertThat(page.getBlock(1).getPositionCount()).isEqualTo(1); + assertThat(page.getBlock(1).getSlice(0, 0, page.getBlock(1).getSliceLength(0)).toStringUtf8()).isEqualTo(DATA_COLUMN_VALUE); } } } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergOrcMetricsCollection.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergOrcMetricsCollection.java index c19c1349713c..dec3bd643dbd 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergOrcMetricsCollection.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergOrcMetricsCollection.java @@ -54,8 +54,7 @@ import static io.trino.plugin.iceberg.IcebergQueryRunner.ICEBERG_CATALOG; import static io.trino.plugin.iceberg.IcebergTestUtils.getFileSystemFactory; import static io.trino.testing.TestingSession.testSessionBuilder; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; +import static org.assertj.core.api.Assertions.assertThat; public class TestIcebergOrcMetricsCollection extends AbstractTestQueryFramework @@ -121,12 +120,12 @@ public void testMetrics() assertUpdate("insert into no_metrics values ('abcd', 'a')", 1); List materializedRows = computeActual("select * from \"no_metrics$files\"").getMaterializedRows(); DataFileRecord datafile = toDataFileRecord(materializedRows.get(0)); - assertEquals(datafile.getRecordCount(), 1); - assertNull(datafile.getValueCounts()); - assertNull(datafile.getNullValueCounts()); - assertNull(datafile.getUpperBounds()); - assertNull(datafile.getLowerBounds()); - assertNull(datafile.getColumnSizes()); + assertThat(datafile.getRecordCount()).isEqualTo(1); + assertThat(datafile.getValueCounts()).isNull(); + assertThat(datafile.getNullValueCounts()).isNull(); + assertThat(datafile.getUpperBounds()).isNull(); + assertThat(datafile.getLowerBounds()).isNull(); + assertThat(datafile.getColumnSizes()).isNull(); // keep c1 metrics assertUpdate("create table c1_metrics (c1 varchar, c2 varchar)"); @@ -140,11 +139,11 @@ public void testMetrics() assertUpdate("insert into c1_metrics values ('b', 'a')", 1); materializedRows = computeActual("select * from \"c1_metrics$files\"").getMaterializedRows(); datafile = toDataFileRecord(materializedRows.get(0)); - assertEquals(datafile.getRecordCount(), 1); - assertEquals(datafile.getValueCounts().size(), 1); - assertEquals(datafile.getNullValueCounts().size(), 1); - assertEquals(datafile.getUpperBounds().size(), 1); - assertEquals(datafile.getLowerBounds().size(), 1); + assertThat(datafile.getRecordCount()).isEqualTo(1); + assertThat(datafile.getValueCounts().size()).isEqualTo(1); + assertThat(datafile.getNullValueCounts().size()).isEqualTo(1); + assertThat(datafile.getUpperBounds().size()).isEqualTo(1); + assertThat(datafile.getLowerBounds().size()).isEqualTo(1); // set c1 metrics mode to count assertUpdate("create table c1_metrics_count (c1 varchar, c2 varchar)"); @@ -158,11 +157,11 @@ public void testMetrics() assertUpdate("insert into c1_metrics_count values ('b', 'a')", 1); materializedRows = computeActual("select * from \"c1_metrics_count$files\"").getMaterializedRows(); datafile = toDataFileRecord(materializedRows.get(0)); - assertEquals(datafile.getRecordCount(), 1); - assertEquals(datafile.getValueCounts().size(), 1); - assertEquals(datafile.getNullValueCounts().size(), 1); - assertNull(datafile.getUpperBounds()); - assertNull(datafile.getLowerBounds()); + assertThat(datafile.getRecordCount()).isEqualTo(1); + assertThat(datafile.getValueCounts().size()).isEqualTo(1); + assertThat(datafile.getNullValueCounts().size()).isEqualTo(1); + assertThat(datafile.getUpperBounds()).isNull(); + assertThat(datafile.getLowerBounds()).isNull(); // set c1 metrics mode to truncate(10) assertUpdate("create table c1_metrics_truncate (c1 varchar, c2 varchar)"); @@ -176,13 +175,13 @@ public void testMetrics() assertUpdate("insert into c1_metrics_truncate values ('abcaabcaabcaabca', 'a')", 1); materializedRows = computeActual("select * from \"c1_metrics_truncate$files\"").getMaterializedRows(); datafile = toDataFileRecord(materializedRows.get(0)); - assertEquals(datafile.getRecordCount(), 1); - assertEquals(datafile.getValueCounts().size(), 1); - assertEquals(datafile.getNullValueCounts().size(), 1); + assertThat(datafile.getRecordCount()).isEqualTo(1); + assertThat(datafile.getValueCounts().size()).isEqualTo(1); + assertThat(datafile.getNullValueCounts().size()).isEqualTo(1); datafile.getUpperBounds().forEach((k, v) -> { - assertEquals(v.length(), 10); }); + assertThat(v.length()).isEqualTo(10); }); datafile.getLowerBounds().forEach((k, v) -> { - assertEquals(v.length(), 10); }); + assertThat(v.length()).isEqualTo(10); }); // keep both c1 and c2 metrics assertUpdate("create table c_metrics (c1 varchar, c2 varchar)"); @@ -195,11 +194,11 @@ public void testMetrics() assertUpdate("insert into c_metrics values ('b', 'a')", 1); materializedRows = computeActual("select * from \"c_metrics$files\"").getMaterializedRows(); datafile = toDataFileRecord(materializedRows.get(0)); - assertEquals(datafile.getRecordCount(), 1); - assertEquals(datafile.getValueCounts().size(), 2); - assertEquals(datafile.getNullValueCounts().size(), 2); - assertEquals(datafile.getUpperBounds().size(), 2); - assertEquals(datafile.getLowerBounds().size(), 2); + assertThat(datafile.getRecordCount()).isEqualTo(1); + assertThat(datafile.getValueCounts().size()).isEqualTo(2); + assertThat(datafile.getNullValueCounts().size()).isEqualTo(2); + assertThat(datafile.getUpperBounds().size()).isEqualTo(2); + assertThat(datafile.getLowerBounds().size()).isEqualTo(2); // keep all metrics assertUpdate("create table metrics (c1 varchar, c2 varchar)"); @@ -211,11 +210,11 @@ public void testMetrics() assertUpdate("insert into metrics values ('b', 'a')", 1); materializedRows = computeActual("select * from \"metrics$files\"").getMaterializedRows(); datafile = toDataFileRecord(materializedRows.get(0)); - assertEquals(datafile.getRecordCount(), 1); - assertEquals(datafile.getValueCounts().size(), 2); - assertEquals(datafile.getNullValueCounts().size(), 2); - assertEquals(datafile.getUpperBounds().size(), 2); - assertEquals(datafile.getLowerBounds().size(), 2); + assertThat(datafile.getRecordCount()).isEqualTo(1); + assertThat(datafile.getValueCounts().size()).isEqualTo(2); + assertThat(datafile.getNullValueCounts().size()).isEqualTo(2); + assertThat(datafile.getUpperBounds().size()).isEqualTo(2); + assertThat(datafile.getLowerBounds().size()).isEqualTo(2); } @Test @@ -223,51 +222,51 @@ public void testBasic() { assertUpdate("CREATE TABLE orders WITH (format = 'ORC') AS SELECT * FROM tpch.tiny.orders", 15000); MaterializedResult materializedResult = computeActual("SELECT * FROM \"orders$files\""); - assertEquals(materializedResult.getRowCount(), 1); + assertThat(materializedResult.getRowCount()).isEqualTo(1); DataFileRecord datafile = toDataFileRecord(materializedResult.getMaterializedRows().get(0)); // check content - assertEquals(datafile.getContent(), FileContent.DATA.id()); + assertThat(datafile.getContent()).isEqualTo(FileContent.DATA.id()); // Check file format - assertEquals(datafile.getFileFormat(), "ORC"); + assertThat(datafile.getFileFormat()).isEqualTo("ORC"); // Check file row count - assertEquals(datafile.getRecordCount(), 15000L); + assertThat(datafile.getRecordCount()).isEqualTo(15000L); // Check per-column value count - datafile.getValueCounts().values().forEach(valueCount -> assertEquals(valueCount, (Long) 15000L)); + datafile.getValueCounts().values().forEach(valueCount -> assertThat(valueCount).isEqualTo((Long) 15000L)); // Check per-column null value count - datafile.getNullValueCounts().values().forEach(nullValueCount -> assertEquals(nullValueCount, (Long) 0L)); + datafile.getNullValueCounts().values().forEach(nullValueCount -> assertThat(nullValueCount).isEqualTo((Long) 0L)); // Check NaN value count // TODO: add more checks after NaN info is collected - assertNull(datafile.getNanValueCounts()); + assertThat(datafile.getNanValueCounts()).isNull(); // Check per-column lower bound Map lowerBounds = datafile.getLowerBounds(); - assertEquals(lowerBounds.get(1), "1"); - assertEquals(lowerBounds.get(2), "1"); - assertEquals(lowerBounds.get(3), "F"); - assertEquals(lowerBounds.get(4), "874.89"); - assertEquals(lowerBounds.get(5), "1992-01-01"); - assertEquals(lowerBounds.get(6), "1-URGENT"); - assertEquals(lowerBounds.get(7), "Clerk#000000001"); - assertEquals(lowerBounds.get(8), "0"); - assertEquals(lowerBounds.get(9), " about the accou"); + assertThat(lowerBounds.get(1)).isEqualTo("1"); + assertThat(lowerBounds.get(2)).isEqualTo("1"); + assertThat(lowerBounds.get(3)).isEqualTo("F"); + assertThat(lowerBounds.get(4)).isEqualTo("874.89"); + assertThat(lowerBounds.get(5)).isEqualTo("1992-01-01"); + assertThat(lowerBounds.get(6)).isEqualTo("1-URGENT"); + assertThat(lowerBounds.get(7)).isEqualTo("Clerk#000000001"); + assertThat(lowerBounds.get(8)).isEqualTo("0"); + assertThat(lowerBounds.get(9)).isEqualTo(" about the accou"); // Check per-column upper bound Map upperBounds = datafile.getUpperBounds(); - assertEquals(upperBounds.get(1), "60000"); - assertEquals(upperBounds.get(2), "1499"); - assertEquals(upperBounds.get(3), "P"); - assertEquals(upperBounds.get(4), "466001.28"); - assertEquals(upperBounds.get(5), "1998-08-02"); - assertEquals(upperBounds.get(6), "5-LOW"); - assertEquals(upperBounds.get(7), "Clerk#000001000"); - assertEquals(upperBounds.get(8), "0"); - assertEquals(upperBounds.get(9), "zzle. carefully!"); + assertThat(upperBounds.get(1)).isEqualTo("60000"); + assertThat(upperBounds.get(2)).isEqualTo("1499"); + assertThat(upperBounds.get(3)).isEqualTo("P"); + assertThat(upperBounds.get(4)).isEqualTo("466001.28"); + assertThat(upperBounds.get(5)).isEqualTo("1998-08-02"); + assertThat(upperBounds.get(6)).isEqualTo("5-LOW"); + assertThat(upperBounds.get(7)).isEqualTo("Clerk#000001000"); + assertThat(upperBounds.get(8)).isEqualTo("0"); + assertThat(upperBounds.get(9)).isEqualTo("zzle. carefully!"); assertUpdate("DROP TABLE orders"); } @@ -282,41 +281,41 @@ public void testWithNulls() "(4, null, 'ccc', null)," + "(null, null, 'ddd', null)", 4); MaterializedResult materializedResult = computeActual("SELECT * FROM \"test_with_nulls$files\""); - assertEquals(materializedResult.getRowCount(), 1); + assertThat(materializedResult.getRowCount()).isEqualTo(1); DataFileRecord datafile = toDataFileRecord(materializedResult.getMaterializedRows().get(0)); // Check per-column value count - datafile.getValueCounts().values().forEach(valueCount -> assertEquals(valueCount, (Long) 4L)); + datafile.getValueCounts().values().forEach(valueCount -> assertThat(valueCount).isEqualTo((Long) 4L)); // Check per-column null value count - assertEquals(datafile.getNullValueCounts().get(1), (Long) 1L); - assertEquals(datafile.getNullValueCounts().get(2), (Long) 2L); - assertEquals(datafile.getNullValueCounts().get(3), (Long) 0L); - assertEquals(datafile.getNullValueCounts().get(4), (Long) 2L); + assertThat(datafile.getNullValueCounts().get(1)).isEqualTo((Long) 1L); + assertThat(datafile.getNullValueCounts().get(2)).isEqualTo((Long) 2L); + assertThat(datafile.getNullValueCounts().get(3)).isEqualTo((Long) 0L); + assertThat(datafile.getNullValueCounts().get(4)).isEqualTo((Long) 2L); // Check per-column lower bound - assertEquals(datafile.getLowerBounds().get(1), "3"); - assertEquals(datafile.getLowerBounds().get(2), "3.4"); - assertEquals(datafile.getLowerBounds().get(3), "aaa"); - assertEquals(datafile.getLowerBounds().get(4), "2020-01-01T00:00:00.123"); + assertThat(datafile.getLowerBounds().get(1)).isEqualTo("3"); + assertThat(datafile.getLowerBounds().get(2)).isEqualTo("3.4"); + assertThat(datafile.getLowerBounds().get(3)).isEqualTo("aaa"); + assertThat(datafile.getLowerBounds().get(4)).isEqualTo("2020-01-01T00:00:00.123"); assertUpdate("DROP TABLE test_with_nulls"); assertUpdate("CREATE TABLE test_all_nulls (_integer INTEGER)"); assertUpdate("INSERT INTO test_all_nulls VALUES null, null, null", 3); materializedResult = computeActual("SELECT * FROM \"test_all_nulls$files\""); - assertEquals(materializedResult.getRowCount(), 1); + assertThat(materializedResult.getRowCount()).isEqualTo(1); datafile = toDataFileRecord(materializedResult.getMaterializedRows().get(0)); // Check per-column value count - assertEquals(datafile.getValueCounts().get(1), (Long) 3L); + assertThat(datafile.getValueCounts().get(1)).isEqualTo((Long) 3L); // Check per-column null value count - assertEquals(datafile.getNullValueCounts().get(1), (Long) 3L); + assertThat(datafile.getNullValueCounts().get(1)).isEqualTo((Long) 3L); // Check that lower bounds and upper bounds are nulls. (There's no non-null record) - assertNull(datafile.getLowerBounds()); - assertNull(datafile.getUpperBounds()); + assertThat(datafile.getLowerBounds()).isNull(); + assertThat(datafile.getUpperBounds()).isNull(); assertUpdate("DROP TABLE test_all_nulls"); } @@ -327,21 +326,21 @@ public void testWithNaNs() assertUpdate("CREATE TABLE test_with_nans (_int INTEGER, _real REAL, _double DOUBLE)"); assertUpdate("INSERT INTO test_with_nans VALUES (1, 1.1, 1.1), (2, nan(), 4.5), (3, 4.6, -nan())", 3); MaterializedResult materializedResult = computeActual("SELECT * FROM \"test_with_nans$files\""); - assertEquals(materializedResult.getRowCount(), 1); + assertThat(materializedResult.getRowCount()).isEqualTo(1); DataFileRecord datafile = toDataFileRecord(materializedResult.getMaterializedRows().get(0)); // Check per-column value count - datafile.getValueCounts().values().forEach(valueCount -> assertEquals(valueCount, (Long) 3L)); + datafile.getValueCounts().values().forEach(valueCount -> assertThat(valueCount).isEqualTo((Long) 3L)); // Check per-column nan value count - assertEquals(datafile.getNanValueCounts().size(), 2); - assertEquals(datafile.getNanValueCounts().get(2), (Long) 1L); - assertEquals(datafile.getNanValueCounts().get(3), (Long) 1L); + assertThat(datafile.getNanValueCounts().size()).isEqualTo(2); + assertThat(datafile.getNanValueCounts().get(2)).isEqualTo((Long) 1L); + assertThat(datafile.getNanValueCounts().get(3)).isEqualTo((Long) 1L); - assertNull(datafile.getLowerBounds().get(2)); - assertNull(datafile.getLowerBounds().get(3)); - assertNull(datafile.getUpperBounds().get(2)); - assertNull(datafile.getUpperBounds().get(3)); + assertThat(datafile.getLowerBounds().get(2)).isNull(); + assertThat(datafile.getLowerBounds().get(3)).isNull(); + assertThat(datafile.getUpperBounds().get(2)).isNull(); + assertThat(datafile.getUpperBounds().get(3)).isNull(); assertUpdate("DROP TABLE test_with_nans"); } @@ -356,7 +355,7 @@ public void testNestedTypes() "(8, ROW(0, ARRAY[14, 17, 21], 3.9)), " + "(3, ROW(10, ARRAY[15, 18, 22], 4.9))", 4); MaterializedResult materializedResult = computeActual("SELECT * FROM \"test_nested_types$files\""); - assertEquals(materializedResult.getRowCount(), 1); + assertThat(materializedResult.getRowCount()).isEqualTo(1); DataFileRecord datafile = toDataFileRecord(materializedResult.getMaterializedRows().get(0)); Map lowerBounds = datafile.getLowerBounds(); @@ -366,20 +365,20 @@ public void testNestedTypes() // 1. top-level primitive columns // 2. and nested primitive fields that are not descendants of LISTs or MAPs // should appear in lowerBounds or UpperBounds - assertEquals(lowerBounds.size(), 3); - assertEquals(upperBounds.size(), 3); + assertThat(lowerBounds.size()).isEqualTo(3); + assertThat(upperBounds.size()).isEqualTo(3); // col1 - assertEquals(lowerBounds.get(1), "-9"); - assertEquals(upperBounds.get(1), "8"); + assertThat(lowerBounds.get(1)).isEqualTo("-9"); + assertThat(upperBounds.get(1)).isEqualTo("8"); // col2.f1 (key in lowerBounds/upperBounds is Iceberg ID) - assertEquals(lowerBounds.get(3), "0"); - assertEquals(upperBounds.get(3), "10"); + assertThat(lowerBounds.get(3)).isEqualTo("0"); + assertThat(upperBounds.get(3)).isEqualTo("10"); // col2.f3 (key in lowerBounds/upperBounds is Iceberg ID) - assertEquals(lowerBounds.get(5), "-2.9"); - assertEquals(upperBounds.get(5), "4.9"); + assertThat(lowerBounds.get(5)).isEqualTo("-2.9"); + assertThat(upperBounds.get(5)).isEqualTo("4.9"); assertUpdate("DROP TABLE test_nested_types"); } @@ -393,27 +392,27 @@ public void testWithTimestamps() "(TIMESTAMP '2021-01-01 00:00:00.222222'), " + "(TIMESTAMP '2021-01-31 00:00:00.333333')", 3); MaterializedResult materializedResult = computeActual("SELECT * FROM \"test_timestamp$files\""); - assertEquals(materializedResult.getRowCount(), 1); + assertThat(materializedResult.getRowCount()).isEqualTo(1); DataFileRecord datafile = toDataFileRecord(materializedResult.getMaterializedRows().get(0)); // Check file format - assertEquals(datafile.getFileFormat(), "ORC"); + assertThat(datafile.getFileFormat()).isEqualTo("ORC"); // Check file row count - assertEquals(datafile.getRecordCount(), 3L); + assertThat(datafile.getRecordCount()).isEqualTo(3L); // Check per-column value count - datafile.getValueCounts().values().forEach(valueCount -> assertEquals(valueCount, (Long) 3L)); + datafile.getValueCounts().values().forEach(valueCount -> assertThat(valueCount).isEqualTo((Long) 3L)); // Check per-column null value count - datafile.getNullValueCounts().values().forEach(nullValueCount -> assertEquals(nullValueCount, (Long) 0L)); + datafile.getNullValueCounts().values().forEach(nullValueCount -> assertThat(nullValueCount).isEqualTo((Long) 0L)); // Check column lower bound. Min timestamp doesn't rely on file-level statistics and will not be truncated to milliseconds. - assertEquals(datafile.getLowerBounds().get(1), "2021-01-01T00:00:00.111"); + assertThat(datafile.getLowerBounds().get(1)).isEqualTo("2021-01-01T00:00:00.111"); assertQuery("SELECT min(_timestamp) FROM test_timestamp", "VALUES '2021-01-01 00:00:00.111111'"); // Check column upper bound. Max timestamp doesn't rely on file-level statistics and will not be truncated to milliseconds. - assertEquals(datafile.getUpperBounds().get(1), "2021-01-31T00:00:00.333999"); + assertThat(datafile.getUpperBounds().get(1)).isEqualTo("2021-01-31T00:00:00.333999"); assertQuery("SELECT max(_timestamp) FROM test_timestamp", "VALUES '2021-01-31 00:00:00.333333'"); assertUpdate("DROP TABLE test_timestamp"); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergParquetConnectorTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergParquetConnectorTest.java index 7f41b139e425..f2830b0408c1 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergParquetConnectorTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergParquetConnectorTest.java @@ -24,8 +24,8 @@ import static io.trino.plugin.iceberg.IcebergFileFormat.PARQUET; import static io.trino.plugin.iceberg.IcebergTestUtils.checkParquetFileSorting; import static io.trino.plugin.iceberg.IcebergTestUtils.withSmallRowGroups; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestIcebergParquetConnectorTest extends BaseIcebergConnectorTest @@ -65,7 +65,7 @@ public void testRowGroupResetDictionary() assertUpdate(withSmallRowGroups(getSession()), "INSERT INTO " + tableName + " VALUES " + values, 100); MaterializedResult result = getDistributedQueryRunner().execute(String.format("SELECT * FROM %s", tableName)); - assertEquals(result.getRowCount(), 100); + assertThat(result.getRowCount()).isEqualTo(100); } } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergPartitionEvolution.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergPartitionEvolution.java index 1659ea0c1afd..8d8c1313d6cd 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergPartitionEvolution.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergPartitionEvolution.java @@ -26,7 +26,6 @@ import static io.trino.testing.TestingNames.randomNameSuffix; import static java.lang.Math.toIntExact; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestIcebergPartitionEvolution extends AbstractTestQueryFramework @@ -59,10 +58,10 @@ public void testRemovePartitioning() int expectedFileCount = computeActual("SELECT DISTINCT regionkey, substring(name, 1, 1) FROM nation WHERE nationkey < 10").getRowCount(); assertThat(partitionedFiles).hasSize(expectedFileCount); - assertEquals(partitionedFiles.stream().mapToLong(row -> (long) row.getField(1)).sum(), 10L); + assertThat(partitionedFiles.stream().mapToLong(row -> (long) row.getField(1)).sum()).isEqualTo(10L); assertThat(unpartitionedFiles).hasSize(1); - assertEquals((long) unpartitionedFiles.get(0).getField(1), 15); + assertThat((long) unpartitionedFiles.get(0).getField(1)).isEqualTo(15); assertQuery("SELECT * FROM " + tableName, "SELECT * FROM nation"); // Most partitions have one record each. regionkey=2, trunc_name=I has two records, and 15 records are unpartitioned @@ -90,11 +89,11 @@ public void testAddPartitionColumn() int expectedInitialFiles = toIntExact((long) computeActual("SELECT count(distinct regionkey) FROM nation WHERE nationkey < 10").getOnlyValue()); assertThat(initialFiles).hasSize(expectedInitialFiles); - assertEquals(initialFiles.stream().mapToLong(row -> (long) row.getField(1)).sum(), 10L); + assertThat(initialFiles.stream().mapToLong(row -> (long) row.getField(1)).sum()).isEqualTo(10L); int expectedFinalFileCount = computeActual("SELECT DISTINCT regionkey, substring(name, 1, 1) FROM nation WHERE nationkey >= 10").getRowCount(); assertThat(partitionedFiles).hasSize(expectedFinalFileCount); - assertEquals(partitionedFiles.stream().mapToLong(row -> (long) row.getField(1)).sum(), 15L); + assertThat(partitionedFiles.stream().mapToLong(row -> (long) row.getField(1)).sum()).isEqualTo(15L); assertQuery("SELECT * FROM " + tableName, "SELECT * FROM nation"); assertUpdate("DROP TABLE " + tableName); @@ -115,11 +114,11 @@ public void testAddPartitionColumn() expectedInitialFiles = computeActual("SELECT DISTINCT substring(name, 1, 1) FROM nation WHERE nationkey < 10").getRowCount(); assertThat(initialFiles).hasSize(expectedInitialFiles); - assertEquals(initialFiles.stream().mapToLong(row -> (long) row.getField(1)).sum(), 10L); + assertThat(initialFiles.stream().mapToLong(row -> (long) row.getField(1)).sum()).isEqualTo(10L); expectedFinalFileCount = computeActual("SELECT DISTINCT regionkey, substring(name, 1, 1) FROM nation WHERE nationkey >= 10").getRowCount(); assertThat(partitionedFiles).hasSize(expectedFinalFileCount); - assertEquals(partitionedFiles.stream().mapToLong(row -> (long) row.getField(1)).sum(), 15L); + assertThat(partitionedFiles.stream().mapToLong(row -> (long) row.getField(1)).sum()).isEqualTo(15L); assertQuery("SELECT * FROM " + tableName, "SELECT * FROM nation"); assertUpdate("DROP TABLE " + tableName); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergSplitSource.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergSplitSource.java index 2e7812aacda5..1de4b30b4420 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergSplitSource.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergSplitSource.java @@ -77,8 +77,6 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) public class TestIcebergSplitSource @@ -223,7 +221,7 @@ public TupleDomain getCurrentPredicate() .forEach(splits::add); } assertThat(splits.build().size()).isGreaterThan(0); - assertTrue(splitSource.isFinished()); + assertThat(splitSource.isFinished()).isTrue(); assertThat(System.currentTimeMillis() - startMillis) .as("IcebergSplitSource failed to wait for dynamicFilteringWaitTimeout") .isGreaterThanOrEqualTo(2000); @@ -239,18 +237,18 @@ public void testBigintPartitionPruning() ImmutableList.of(), BIGINT, Optional.empty()); - assertFalse(IcebergSplitSource.partitionMatchesPredicate( + assertThat(IcebergSplitSource.partitionMatchesPredicate( ImmutableSet.of(bigintColumn), () -> ImmutableMap.of(bigintColumn, NullableValue.of(BIGINT, 1000L)), - TupleDomain.fromFixedValues(ImmutableMap.of(bigintColumn, NullableValue.of(BIGINT, 100L))))); - assertTrue(IcebergSplitSource.partitionMatchesPredicate( + TupleDomain.fromFixedValues(ImmutableMap.of(bigintColumn, NullableValue.of(BIGINT, 100L))))).isFalse(); + assertThat(IcebergSplitSource.partitionMatchesPredicate( ImmutableSet.of(bigintColumn), () -> ImmutableMap.of(bigintColumn, NullableValue.of(BIGINT, 1000L)), - TupleDomain.fromFixedValues(ImmutableMap.of(bigintColumn, NullableValue.of(BIGINT, 1000L))))); - assertFalse(IcebergSplitSource.partitionMatchesPredicate( + TupleDomain.fromFixedValues(ImmutableMap.of(bigintColumn, NullableValue.of(BIGINT, 1000L))))).isTrue(); + assertThat(IcebergSplitSource.partitionMatchesPredicate( ImmutableSet.of(bigintColumn), () -> ImmutableMap.of(bigintColumn, NullableValue.of(BIGINT, 1000L)), - TupleDomain.fromFixedValues(ImmutableMap.of(bigintColumn, NullableValue.asNull(BIGINT))))); + TupleDomain.fromFixedValues(ImmutableMap.of(bigintColumn, NullableValue.asNull(BIGINT))))).isFalse(); } @Test @@ -266,92 +264,92 @@ public void testBigintStatisticsPruning() Map lowerBound = ImmutableMap.of(1, Conversions.toByteBuffer(Types.LongType.get(), 1000L)); Map upperBound = ImmutableMap.of(1, Conversions.toByteBuffer(Types.LongType.get(), 2000L)); - assertFalse(IcebergSplitSource.fileMatchesPredicate( + assertThat(IcebergSplitSource.fileMatchesPredicate( primitiveTypes, TupleDomain.fromFixedValues(ImmutableMap.of(bigintColumn, NullableValue.of(BIGINT, 0L))), lowerBound, upperBound, - ImmutableMap.of(1, 0L))); - assertTrue(IcebergSplitSource.fileMatchesPredicate( + ImmutableMap.of(1, 0L))).isFalse(); + assertThat(IcebergSplitSource.fileMatchesPredicate( primitiveTypes, TupleDomain.fromFixedValues(ImmutableMap.of(bigintColumn, NullableValue.of(BIGINT, 1000L))), lowerBound, upperBound, - ImmutableMap.of(1, 0L))); - assertTrue(IcebergSplitSource.fileMatchesPredicate( + ImmutableMap.of(1, 0L))).isTrue(); + assertThat(IcebergSplitSource.fileMatchesPredicate( primitiveTypes, TupleDomain.fromFixedValues(ImmutableMap.of(bigintColumn, NullableValue.of(BIGINT, 1500L))), lowerBound, upperBound, - ImmutableMap.of(1, 0L))); - assertTrue(IcebergSplitSource.fileMatchesPredicate( + ImmutableMap.of(1, 0L))).isTrue(); + assertThat(IcebergSplitSource.fileMatchesPredicate( primitiveTypes, TupleDomain.fromFixedValues(ImmutableMap.of(bigintColumn, NullableValue.of(BIGINT, 2000L))), lowerBound, upperBound, - ImmutableMap.of(1, 0L))); - assertFalse(IcebergSplitSource.fileMatchesPredicate( + ImmutableMap.of(1, 0L))).isTrue(); + assertThat(IcebergSplitSource.fileMatchesPredicate( primitiveTypes, TupleDomain.fromFixedValues(ImmutableMap.of(bigintColumn, NullableValue.of(BIGINT, 3000L))), lowerBound, upperBound, - ImmutableMap.of(1, 0L))); + ImmutableMap.of(1, 0L))).isFalse(); Domain outsideStatisticsRangeAllowNulls = Domain.create(ValueSet.ofRanges(Range.range(BIGINT, 0L, true, 100L, true)), true); - assertFalse(IcebergSplitSource.fileMatchesPredicate( + assertThat(IcebergSplitSource.fileMatchesPredicate( primitiveTypes, TupleDomain.withColumnDomains(ImmutableMap.of(bigintColumn, outsideStatisticsRangeAllowNulls)), lowerBound, upperBound, - ImmutableMap.of(1, 0L))); - assertTrue(IcebergSplitSource.fileMatchesPredicate( + ImmutableMap.of(1, 0L))).isFalse(); + assertThat(IcebergSplitSource.fileMatchesPredicate( primitiveTypes, TupleDomain.withColumnDomains(ImmutableMap.of(bigintColumn, outsideStatisticsRangeAllowNulls)), lowerBound, upperBound, - ImmutableMap.of(1, 1L))); + ImmutableMap.of(1, 1L))).isTrue(); Domain outsideStatisticsRangeNoNulls = Domain.create(ValueSet.ofRanges(Range.range(BIGINT, 0L, true, 100L, true)), false); - assertFalse(IcebergSplitSource.fileMatchesPredicate( + assertThat(IcebergSplitSource.fileMatchesPredicate( primitiveTypes, TupleDomain.withColumnDomains(ImmutableMap.of(bigintColumn, outsideStatisticsRangeNoNulls)), lowerBound, upperBound, - ImmutableMap.of(1, 0L))); - assertFalse(IcebergSplitSource.fileMatchesPredicate( + ImmutableMap.of(1, 0L))).isFalse(); + assertThat(IcebergSplitSource.fileMatchesPredicate( primitiveTypes, TupleDomain.withColumnDomains(ImmutableMap.of(bigintColumn, outsideStatisticsRangeNoNulls)), lowerBound, upperBound, - ImmutableMap.of(1, 1L))); + ImmutableMap.of(1, 1L))).isFalse(); Domain insideStatisticsRange = Domain.create(ValueSet.ofRanges(Range.range(BIGINT, 1001L, true, 1002L, true)), false); - assertTrue(IcebergSplitSource.fileMatchesPredicate( + assertThat(IcebergSplitSource.fileMatchesPredicate( primitiveTypes, TupleDomain.withColumnDomains(ImmutableMap.of(bigintColumn, insideStatisticsRange)), lowerBound, upperBound, - ImmutableMap.of(1, 0L))); - assertTrue(IcebergSplitSource.fileMatchesPredicate( + ImmutableMap.of(1, 0L))).isTrue(); + assertThat(IcebergSplitSource.fileMatchesPredicate( primitiveTypes, TupleDomain.withColumnDomains(ImmutableMap.of(bigintColumn, insideStatisticsRange)), lowerBound, upperBound, - ImmutableMap.of(1, 1L))); + ImmutableMap.of(1, 1L))).isTrue(); Domain overlappingStatisticsRange = Domain.create(ValueSet.ofRanges(Range.range(BIGINT, 990L, true, 1010L, true)), false); - assertTrue(IcebergSplitSource.fileMatchesPredicate( + assertThat(IcebergSplitSource.fileMatchesPredicate( primitiveTypes, TupleDomain.withColumnDomains(ImmutableMap.of(bigintColumn, overlappingStatisticsRange)), lowerBound, upperBound, - ImmutableMap.of(1, 0L))); - assertTrue(IcebergSplitSource.fileMatchesPredicate( + ImmutableMap.of(1, 0L))).isTrue(); + assertThat(IcebergSplitSource.fileMatchesPredicate( primitiveTypes, TupleDomain.withColumnDomains(ImmutableMap.of(bigintColumn, overlappingStatisticsRange)), lowerBound, upperBound, - ImmutableMap.of(1, 1L))); + ImmutableMap.of(1, 1L))).isTrue(); } @Test @@ -368,44 +366,44 @@ public void testNullStatisticsMaps() Map upperBound = ImmutableMap.of(1, Conversions.toByteBuffer(Types.LongType.get(), 2000L)); TupleDomain domainOfZero = TupleDomain.fromFixedValues(ImmutableMap.of(bigintColumn, NullableValue.of(BIGINT, 0L))); - assertTrue(IcebergSplitSource.fileMatchesPredicate( + assertThat(IcebergSplitSource.fileMatchesPredicate( primitiveTypes, domainOfZero, null, upperBound, - ImmutableMap.of(1, 0L))); - assertTrue(IcebergSplitSource.fileMatchesPredicate( + ImmutableMap.of(1, 0L))).isTrue(); + assertThat(IcebergSplitSource.fileMatchesPredicate( primitiveTypes, domainOfZero, ImmutableMap.of(), upperBound, - ImmutableMap.of(1, 0L))); + ImmutableMap.of(1, 0L))).isTrue(); - assertTrue(IcebergSplitSource.fileMatchesPredicate( + assertThat(IcebergSplitSource.fileMatchesPredicate( primitiveTypes, domainOfZero, lowerBound, null, - ImmutableMap.of(1, 0L))); - assertTrue(IcebergSplitSource.fileMatchesPredicate( + ImmutableMap.of(1, 0L))).isTrue(); + assertThat(IcebergSplitSource.fileMatchesPredicate( primitiveTypes, domainOfZero, lowerBound, ImmutableMap.of(), - ImmutableMap.of(1, 0L))); + ImmutableMap.of(1, 0L))).isTrue(); TupleDomain onlyNull = TupleDomain.withColumnDomains(ImmutableMap.of(bigintColumn, Domain.onlyNull(BIGINT))); - assertTrue(IcebergSplitSource.fileMatchesPredicate( + assertThat(IcebergSplitSource.fileMatchesPredicate( primitiveTypes, onlyNull, ImmutableMap.of(), ImmutableMap.of(), - null)); - assertTrue(IcebergSplitSource.fileMatchesPredicate( + null)).isTrue(); + assertThat(IcebergSplitSource.fileMatchesPredicate( primitiveTypes, onlyNull, ImmutableMap.of(), ImmutableMap.of(), - ImmutableMap.of())); + ImmutableMap.of())).isTrue(); } } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergStatistics.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergStatistics.java index b3b96f258f89..dc86ff1beb92 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergStatistics.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergStatistics.java @@ -39,8 +39,6 @@ import static java.util.stream.Collectors.joining; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotEquals; public class TestIcebergStatistics extends AbstractTestQueryFramework @@ -723,10 +721,11 @@ public void testAnalyzeAndRollbackToSnapshot() assertUpdate("ANALYZE " + tableName); long analyzeSnapshot = getCurrentSnapshotId(tableName); // ANALYZE currently does not create a new snapshot - assertEquals(analyzeSnapshot, createSnapshot); + assertThat(analyzeSnapshot).isEqualTo(createSnapshot); assertUpdate("INSERT INTO " + tableName + " SELECT * FROM tpch.sf1.nation WHERE nationkey = 1", 1); - assertNotEquals(getCurrentSnapshotId(tableName), createSnapshot); + assertThat(getCurrentSnapshotId(tableName)) + .isNotEqualTo(createSnapshot); // NDV information present after INSERT assertQuery( "SHOW STATS FOR " + tableName, diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergTableName.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergTableName.java index b741444730e5..5758307909a3 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergTableName.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergTableName.java @@ -20,9 +20,6 @@ import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestIcebergTableName { @@ -48,37 +45,37 @@ public void testParse() @Test public void testIsDataTable() { - assertTrue(IcebergTableName.isDataTable("abc")); + assertThat(IcebergTableName.isDataTable("abc")).isTrue(); - assertFalse(IcebergTableName.isDataTable("abc$data")); // it's invalid - assertFalse(IcebergTableName.isDataTable("abc$history")); - assertFalse(IcebergTableName.isDataTable("abc$invalid")); + assertThat(IcebergTableName.isDataTable("abc$data")).isFalse(); // it's invalid + assertThat(IcebergTableName.isDataTable("abc$history")).isFalse(); + assertThat(IcebergTableName.isDataTable("abc$invalid")).isFalse(); } @Test public void testTableNameFrom() { - assertEquals(IcebergTableName.tableNameFrom("abc"), "abc"); - assertEquals(IcebergTableName.tableNameFrom("abc$data"), "abc"); - assertEquals(IcebergTableName.tableNameFrom("abc$history"), "abc"); - assertEquals(IcebergTableName.tableNameFrom("abc$invalid"), "abc"); + assertThat(IcebergTableName.tableNameFrom("abc")).isEqualTo("abc"); + assertThat(IcebergTableName.tableNameFrom("abc$data")).isEqualTo("abc"); + assertThat(IcebergTableName.tableNameFrom("abc$history")).isEqualTo("abc"); + assertThat(IcebergTableName.tableNameFrom("abc$invalid")).isEqualTo("abc"); } @Test public void testTableTypeFrom() { - assertEquals(IcebergTableName.tableTypeFrom("abc"), Optional.of(TableType.DATA)); - assertEquals(IcebergTableName.tableTypeFrom("abc$data"), Optional.empty()); // it's invalid - assertEquals(IcebergTableName.tableTypeFrom("abc$history"), Optional.of(TableType.HISTORY)); + assertThat(IcebergTableName.tableTypeFrom("abc")).isEqualTo(Optional.of(TableType.DATA)); + assertThat(IcebergTableName.tableTypeFrom("abc$data")).isEqualTo(Optional.empty()); // it's invalid + assertThat(IcebergTableName.tableTypeFrom("abc$history")).isEqualTo(Optional.of(TableType.HISTORY)); - assertEquals(IcebergTableName.tableTypeFrom("abc$invalid"), Optional.empty()); + assertThat(IcebergTableName.tableTypeFrom("abc$invalid")).isEqualTo(Optional.empty()); } @Test public void testTableNameWithType() { - assertEquals(IcebergTableName.tableNameWithType("abc", TableType.DATA), "abc$data"); - assertEquals(IcebergTableName.tableNameWithType("abc", TableType.HISTORY), "abc$history"); + assertThat(IcebergTableName.tableNameWithType("abc", TableType.DATA)).isEqualTo("abc$data"); + assertThat(IcebergTableName.tableNameWithType("abc", TableType.HISTORY)).isEqualTo("abc$history"); } private static void assertInvalid(String inputName, String message) @@ -96,7 +93,7 @@ private static void assertNoValidTableType(String inputName) private static void assertParseNameAndType(String inputName, String tableName, TableType tableType) { - assertEquals(IcebergTableName.tableNameFrom(inputName), tableName); - assertEquals(IcebergTableName.tableTypeFrom(inputName), Optional.of(tableType)); + assertThat(IcebergTableName.tableNameFrom(inputName)).isEqualTo(tableName); + assertThat(IcebergTableName.tableTypeFrom(inputName)).isEqualTo(Optional.of(tableType)); } } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergTableWithCustomLocation.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergTableWithCustomLocation.java index 6f6df2a679b2..eb2cf82fe9e9 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergTableWithCustomLocation.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergTableWithCustomLocation.java @@ -39,10 +39,6 @@ import static io.trino.testing.TestingConnectorSession.SESSION; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotEquals; -import static org.testng.Assert.assertTrue; public class TestIcebergTableWithCustomLocation extends AbstractTestQueryFramework @@ -98,18 +94,28 @@ public void testCreateAndDrop() assertThat(table.getTableType()).isEqualTo(EXTERNAL_TABLE.name()); Location tableLocation = Location.of(table.getStorage().getLocation()); - assertTrue(fileSystem.newInputFile(tableLocation).exists(), "The directory corresponding to the table storage location should exist"); + assertThat(fileSystem.newInputFile(tableLocation).exists()) + .describedAs("The directory corresponding to the table storage location should exist") + .isTrue(); MaterializedResult materializedResult = computeActual("SELECT * FROM \"test_create_and_drop$files\""); - assertEquals(materializedResult.getRowCount(), 1); + assertThat(materializedResult.getRowCount()).isEqualTo(1); DataFileRecord dataFile = toDataFileRecord(materializedResult.getMaterializedRows().get(0)); Location dataFileLocation = Location.of(dataFile.getFilePath()); - assertTrue(fileSystem.newInputFile(dataFileLocation).exists(), "The data file should exist"); + assertThat(fileSystem.newInputFile(dataFileLocation).exists()) + .describedAs("The data file should exist") + .isTrue(); assertQuerySucceeds(format("DROP TABLE %s", tableName)); - assertFalse(metastore.getTable("tpch", tableName).isPresent(), "Table should be dropped"); - assertFalse(fileSystem.newInputFile(dataFileLocation).exists(), "The data file should have been removed"); - assertFalse(fileSystem.newInputFile(tableLocation).exists(), "The directory corresponding to the dropped Iceberg table should not be removed because it may be shared with other tables"); + assertThat(metastore.getTable("tpch", tableName).isPresent()) + .describedAs("Table should be dropped") + .isFalse(); + assertThat(fileSystem.newInputFile(dataFileLocation).exists()) + .describedAs("The data file should have been removed") + .isFalse(); + assertThat(fileSystem.newInputFile(tableLocation).exists()) + .describedAs("The directory corresponding to the dropped Iceberg table should not be removed because it may be shared with other tables") + .isFalse(); } @Test @@ -126,7 +132,9 @@ public void testCreateRenameDrop() Optional
    renamedTable = metastore.getTable("tpch", renamedName); assertThat(renamedTable).as("Table should exist").isPresent(); String renamedTableLocation = renamedTable.get().getStorage().getLocation(); - assertEquals(renamedTableLocation, tableInitialLocation, "Location should not be changed"); + assertThat(renamedTableLocation) + .describedAs("Location should not be changed") + .isEqualTo(tableInitialLocation); assertQuerySucceeds(format("DROP TABLE %s", renamedName)); assertThat(metastore.getTable("tpch", tableName)).as("Initial table should not exist").isEmpty(); @@ -147,12 +155,16 @@ public void testCreateRenameCreate() Optional
    renamedTable = metastore.getTable("tpch", renamedName); assertThat(renamedTable).as("Table should exist").isPresent(); String renamedTableLocation = renamedTable.get().getStorage().getLocation(); - assertEquals(renamedTableLocation, tableInitialLocation, "Location should not be changed"); + assertThat(renamedTableLocation) + .describedAs("Location should not be changed") + .isEqualTo(tableInitialLocation); assertQuerySucceeds(format("CREATE TABLE %s as select 1 as val", tableName)); Optional
    recreatedTableWithInitialName = metastore.getTable("tpch", tableName); assertThat(recreatedTableWithInitialName).as("Table should exist").isPresent(); String recreatedTableLocation = recreatedTableWithInitialName.get().getStorage().getLocation(); - assertNotEquals(tableInitialLocation, recreatedTableLocation, "Location should be different"); + assertThat(tableInitialLocation) + .describedAs("Location should be different") + .isNotEqualTo(recreatedTableLocation); } } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergTableWithExternalLocation.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergTableWithExternalLocation.java index 85f480efb0c0..a8d8a88cf42b 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergTableWithExternalLocation.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergTableWithExternalLocation.java @@ -40,9 +40,6 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) public class TestIcebergTableWithExternalLocation @@ -90,16 +87,24 @@ public void testCreateAndDrop() Table table = metastore.getTable("tpch", tableName).orElseThrow(); assertThat(table.getTableType()).isEqualTo(EXTERNAL_TABLE.name()); Location tableLocation = Location.of(table.getStorage().getLocation()); - assertTrue(fileSystem.newInputFile(tableLocation).exists(), "The directory corresponding to the table storage location should exist"); + assertThat(fileSystem.newInputFile(tableLocation).exists()) + .describedAs("The directory corresponding to the table storage location should exist") + .isTrue(); MaterializedResult materializedResult = computeActual("SELECT * FROM \"test_table_external_create_and_drop$files\""); - assertEquals(materializedResult.getRowCount(), 1); + assertThat(materializedResult.getRowCount()).isEqualTo(1); DataFileRecord dataFile = toDataFileRecord(materializedResult.getMaterializedRows().get(0)); Location dataFileLocation = Location.of(dataFile.getFilePath()); - assertTrue(fileSystem.newInputFile(dataFileLocation).exists(), "The data file should exist"); + assertThat(fileSystem.newInputFile(dataFileLocation).exists()) + .describedAs("The data file should exist") + .isTrue(); assertQuerySucceeds(format("DROP TABLE %s", tableName)); assertThat(metastore.getTable("tpch", tableName)).as("Table should be dropped").isEmpty(); - assertFalse(fileSystem.newInputFile(dataFileLocation).exists(), "The data file should have been removed"); - assertFalse(fileSystem.newInputFile(tableLocation).exists(), "The directory corresponding to the dropped Iceberg table should be removed as we don't allow shared locations."); + assertThat(fileSystem.newInputFile(dataFileLocation).exists()) + .describedAs("The data file should have been removed") + .isFalse(); + assertThat(fileSystem.newInputFile(tableLocation).exists()) + .describedAs("The directory corresponding to the dropped Iceberg table should be removed as we don't allow shared locations.") + .isFalse(); } } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergUtil.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergUtil.java index d9078a581c42..00cd2d84f599 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergUtil.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergUtil.java @@ -16,23 +16,23 @@ import org.junit.jupiter.api.Test; import static io.trino.plugin.iceberg.IcebergUtil.parseVersion; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestIcebergUtil { @Test public void testParseVersion() { - assertEquals(parseVersion("00000-409702ba-4735-4645-8f14-09537cc0b2c8.metadata.json"), 0); - assertEquals(parseVersion("99999-409702ba-4735-4645-8f14-09537cc0b2c8.metadata.json"), 99999); - assertEquals(parseVersion("00010-409702ba-4735-4645-8f14-09537cc0b2c8.metadata.json"), 10); - assertEquals(parseVersion("00011-409702ba-4735-4645-8f14-09537cc0b2c8.metadata.json"), 11); - assertEquals(parseVersion("v0.metadata.json"), 0); - assertEquals(parseVersion("v10.metadata.json"), 10); - assertEquals(parseVersion("v99999.metadata.json"), 99999); - assertEquals(parseVersion("v0.gz.metadata.json"), 0); - assertEquals(parseVersion("v0.metadata.json.gz"), 0); + assertThat(parseVersion("00000-409702ba-4735-4645-8f14-09537cc0b2c8.metadata.json")).isEqualTo(0); + assertThat(parseVersion("99999-409702ba-4735-4645-8f14-09537cc0b2c8.metadata.json")).isEqualTo(99999); + assertThat(parseVersion("00010-409702ba-4735-4645-8f14-09537cc0b2c8.metadata.json")).isEqualTo(10); + assertThat(parseVersion("00011-409702ba-4735-4645-8f14-09537cc0b2c8.metadata.json")).isEqualTo(11); + assertThat(parseVersion("v0.metadata.json")).isEqualTo(0); + assertThat(parseVersion("v10.metadata.json")).isEqualTo(10); + assertThat(parseVersion("v99999.metadata.json")).isEqualTo(99999); + assertThat(parseVersion("v0.gz.metadata.json")).isEqualTo(0); + assertThat(parseVersion("v0.metadata.json.gz")).isEqualTo(0); assertThatThrownBy(() -> parseVersion("hdfs://hadoop-master:9000/user/hive/warehouse/orders_5-581fad8517934af6be1857a903559d44/metadata/00000-409702ba-4735-4645-8f14-09537cc0b2c8.metadata.json")) .hasMessageMatching("Not a file name: .*"); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergV2.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergV2.java index f8433034db31..a3821447ad03 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergV2.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergV2.java @@ -106,8 +106,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) public class TestIcebergV2 @@ -586,9 +584,9 @@ public void testUpgradeTableToV2FromTrino() { String tableName = "test_upgrade_table_to_v2_from_trino_" + randomNameSuffix(); assertUpdate("CREATE TABLE " + tableName + " WITH (format_version = 1) AS SELECT * FROM tpch.tiny.nation", 25); - assertEquals(loadTable(tableName).operations().current().formatVersion(), 1); + assertThat(loadTable(tableName).operations().current().formatVersion()).isEqualTo(1); assertUpdate("ALTER TABLE " + tableName + " SET PROPERTIES format_version = 2"); - assertEquals(loadTable(tableName).operations().current().formatVersion(), 2); + assertThat(loadTable(tableName).operations().current().formatVersion()).isEqualTo(2); assertQuery("SELECT * FROM " + tableName, "SELECT * FROM nation"); } @@ -597,7 +595,7 @@ public void testDowngradingV2TableToV1Fails() { String tableName = "test_downgrading_v2_table_to_v1_fails_" + randomNameSuffix(); assertUpdate("CREATE TABLE " + tableName + " WITH (format_version = 2) AS SELECT * FROM tpch.tiny.nation", 25); - assertEquals(loadTable(tableName).operations().current().formatVersion(), 2); + assertThat(loadTable(tableName).operations().current().formatVersion()).isEqualTo(2); assertThatThrownBy(() -> query("ALTER TABLE " + tableName + " SET PROPERTIES format_version = 1")) .hasMessage("Failed to set new property values") .rootCause() @@ -609,7 +607,7 @@ public void testUpgradingToInvalidVersionFails() { String tableName = "test_upgrading_to_invalid_version_fails_" + randomNameSuffix(); assertUpdate("CREATE TABLE " + tableName + " WITH (format_version = 2) AS SELECT * FROM tpch.tiny.nation", 25); - assertEquals(loadTable(tableName).operations().current().formatVersion(), 2); + assertThat(loadTable(tableName).operations().current().formatVersion()).isEqualTo(2); assertThatThrownBy(() -> query("ALTER TABLE " + tableName + " SET PROPERTIES format_version = 42")) .hasMessage("Unable to set catalog 'iceberg' table property 'format_version' to [42]: format_version must be between 1 and 2"); } @@ -620,23 +618,23 @@ public void testUpdatingAllTableProperties() String tableName = "test_updating_all_table_properties_" + randomNameSuffix(); assertUpdate("CREATE TABLE " + tableName + " WITH (format_version = 1, format = 'ORC') AS SELECT * FROM tpch.tiny.nation", 25); BaseTable table = loadTable(tableName); - assertEquals(table.operations().current().formatVersion(), 1); - assertTrue(table.properties().get(TableProperties.DEFAULT_FILE_FORMAT).equalsIgnoreCase("ORC")); - assertTrue(table.spec().isUnpartitioned()); + assertThat(table.operations().current().formatVersion()).isEqualTo(1); + assertThat(table.properties().get(TableProperties.DEFAULT_FILE_FORMAT).equalsIgnoreCase("ORC")).isTrue(); + assertThat(table.spec().isUnpartitioned()).isTrue(); assertUpdate("ALTER TABLE " + tableName + " SET PROPERTIES format_version = 2, partitioning = ARRAY['regionkey'], format = 'PARQUET', sorted_by = ARRAY['comment']"); table = loadTable(tableName); - assertEquals(table.operations().current().formatVersion(), 2); - assertTrue(table.properties().get(TableProperties.DEFAULT_FILE_FORMAT).equalsIgnoreCase("PARQUET")); - assertTrue(table.spec().isPartitioned()); + assertThat(table.operations().current().formatVersion()).isEqualTo(2); + assertThat(table.properties().get(TableProperties.DEFAULT_FILE_FORMAT).equalsIgnoreCase("PARQUET")).isTrue(); + assertThat(table.spec().isPartitioned()).isTrue(); List partitionFields = table.spec().fields(); assertThat(partitionFields).hasSize(1); - assertEquals(partitionFields.get(0).name(), "regionkey"); - assertTrue(partitionFields.get(0).transform().isIdentity()); - assertTrue(table.sortOrder().isSorted()); + assertThat(partitionFields.get(0).name()).isEqualTo("regionkey"); + assertThat(partitionFields.get(0).transform().isIdentity()).isTrue(); + assertThat(table.sortOrder().isSorted()).isTrue(); List sortFields = table.sortOrder().fields(); - assertEquals(sortFields.size(), 1); - assertEquals(getOnlyElement(sortFields).sourceId(), table.schema().findField("comment").fieldId()); + assertThat(sortFields.size()).isEqualTo(1); + assertThat(getOnlyElement(sortFields).sourceId()).isEqualTo(table.schema().findField("comment").fieldId()); assertQuery("SELECT * FROM " + tableName, "SELECT * FROM nation"); } @@ -647,20 +645,20 @@ public void testUnsettingAllTableProperties() assertUpdate("CREATE TABLE " + tableName + " WITH (format_version = 1, format = 'PARQUET', partitioning = ARRAY['regionkey'], sorted_by = ARRAY['comment']) " + "AS SELECT * FROM tpch.tiny.nation", 25); BaseTable table = loadTable(tableName); - assertEquals(table.operations().current().formatVersion(), 1); - assertTrue(table.properties().get(TableProperties.DEFAULT_FILE_FORMAT).equalsIgnoreCase("PARQUET")); - assertTrue(table.spec().isPartitioned()); + assertThat(table.operations().current().formatVersion()).isEqualTo(1); + assertThat(table.properties().get(TableProperties.DEFAULT_FILE_FORMAT).equalsIgnoreCase("PARQUET")).isTrue(); + assertThat(table.spec().isPartitioned()).isTrue(); List partitionFields = table.spec().fields(); assertThat(partitionFields).hasSize(1); - assertEquals(partitionFields.get(0).name(), "regionkey"); - assertTrue(partitionFields.get(0).transform().isIdentity()); + assertThat(partitionFields.get(0).name()).isEqualTo("regionkey"); + assertThat(partitionFields.get(0).transform().isIdentity()).isTrue(); assertUpdate("ALTER TABLE " + tableName + " SET PROPERTIES format_version = DEFAULT, format = DEFAULT, partitioning = DEFAULT, sorted_by = DEFAULT"); table = loadTable(tableName); - assertEquals(table.operations().current().formatVersion(), 2); - assertTrue(table.properties().get(TableProperties.DEFAULT_FILE_FORMAT).equalsIgnoreCase("PARQUET")); - assertTrue(table.spec().isUnpartitioned()); - assertTrue(table.sortOrder().isUnsorted()); + assertThat(table.operations().current().formatVersion()).isEqualTo(2); + assertThat(table.properties().get(TableProperties.DEFAULT_FILE_FORMAT).equalsIgnoreCase("PARQUET")).isTrue(); + assertThat(table.spec().isUnpartitioned()).isTrue(); + assertThat(table.sortOrder().isUnsorted()).isTrue(); assertQuery("SELECT * FROM " + tableName, "SELECT * FROM nation"); } @@ -721,7 +719,7 @@ public void testDeletingEntireFileWithMultipleSplits() long initialSnapshotId = (long) computeScalar("SELECT snapshot_id FROM \"" + tableName + "$snapshots\" ORDER BY committed_at DESC FETCH FIRST 1 ROW WITH TIES"); assertUpdate("DELETE FROM " + tableName + " WHERE regionkey < 10", 25); long parentSnapshotId = (long) computeScalar("SELECT parent_id FROM \"" + tableName + "$snapshots\" ORDER BY committed_at DESC FETCH FIRST 1 ROW WITH TIES"); - assertEquals(initialSnapshotId, parentSnapshotId); + assertThat(initialSnapshotId).isEqualTo(parentSnapshotId); assertThat(query("SELECT * FROM " + tableName)).returnsEmptyResult(); assertThat(this.loadTable(tableName).newScan().planFiles()).hasSize(1); } @@ -737,7 +735,7 @@ public void testMultipleDeletes() long initialSnapshotId = (long) computeScalar("SELECT snapshot_id FROM \"" + tableName + "$snapshots\" ORDER BY committed_at DESC FETCH FIRST 1 ROW WITH TIES"); assertUpdate("DELETE FROM " + tableName + " WHERE regionkey % 2 = 1", "SELECT count(*) FROM nation WHERE regionkey % 2 = 1"); long parentSnapshotId = (long) computeScalar("SELECT parent_id FROM \"" + tableName + "$snapshots\" ORDER BY committed_at DESC FETCH FIRST 1 ROW WITH TIES"); - assertEquals(initialSnapshotId, parentSnapshotId); + assertThat(initialSnapshotId).isEqualTo(parentSnapshotId); assertUpdate("DELETE FROM " + tableName + " WHERE regionkey % 2 = 0", "SELECT count(*) FROM nation WHERE regionkey % 2 = 0"); assertThat(query("SELECT * FROM " + tableName)).returnsEmptyResult(); @@ -842,7 +840,7 @@ public void testStatsFilePruning() TypeManager typeManager = new TestingTypeManager(); Table table = loadTable(testTable.getName()); TableStatistics withNoFilter = TableStatisticsReader.makeTableStatistics(typeManager, table, snapshotId, TupleDomain.all(), TupleDomain.all(), true); - assertEquals(withNoFilter.getRowCount().getValue(), 4.0); + assertThat(withNoFilter.getRowCount().getValue()).isEqualTo(4.0); TableStatistics withPartitionFilter = TableStatisticsReader.makeTableStatistics( typeManager, @@ -853,7 +851,7 @@ public void testStatsFilePruning() Domain.singleValue(INTEGER, 10L))), TupleDomain.all(), true); - assertEquals(withPartitionFilter.getRowCount().getValue(), 3.0); + assertThat(withPartitionFilter.getRowCount().getValue()).isEqualTo(3.0); TableStatistics withUnenforcedFilter = TableStatisticsReader.makeTableStatistics( typeManager, @@ -864,7 +862,7 @@ public void testStatsFilePruning() new IcebergColumnHandle(ColumnIdentity.primitiveColumnIdentity(0, "a"), INTEGER, ImmutableList.of(), INTEGER, Optional.empty()), Domain.create(ValueSet.ofRanges(Range.greaterThan(INTEGER, 100L)), true))), true); - assertEquals(withUnenforcedFilter.getRowCount().getValue(), 2.0); + assertThat(withUnenforcedFilter.getRowCount().getValue()).isEqualTo(2.0); } } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestMetricsWrapper.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestMetricsWrapper.java index 198d9674a40c..cf42535ae7c6 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestMetricsWrapper.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestMetricsWrapper.java @@ -30,7 +30,6 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet; import static io.airlift.json.JsonCodec.jsonCodec; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestMetricsWrapper { @@ -51,13 +50,13 @@ public void testRoundTrip() Metrics actual = CODEC.fromJson(CODEC.toJson(new MetricsWrapper(expected))).metrics(); - assertEquals(actual.recordCount(), recordCount); - assertEquals(actual.columnSizes(), columnSizes); - assertEquals(actual.valueCounts(), valueCounts); - assertEquals(actual.nullValueCounts(), nullValueCounts); - assertEquals(actual.nanValueCounts(), nanValueCounts); - assertEquals(actual.lowerBounds(), lowerBounds); - assertEquals(actual.upperBounds(), upperBounds); + assertThat(actual.recordCount()).isEqualTo(recordCount); + assertThat(actual.columnSizes()).isEqualTo(columnSizes); + assertThat(actual.valueCounts()).isEqualTo(valueCounts); + assertThat(actual.nullValueCounts()).isEqualTo(nullValueCounts); + assertThat(actual.nanValueCounts()).isEqualTo(nanValueCounts); + assertThat(actual.lowerBounds()).isEqualTo(lowerBounds); + assertThat(actual.upperBounds()).isEqualTo(upperBounds); } @Test diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestPartitionFields.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestPartitionFields.java index 3cd07dd337da..77ee270160a4 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestPartitionFields.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestPartitionFields.java @@ -29,8 +29,8 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static io.trino.plugin.iceberg.PartitionFields.parsePartitionField; import static io.trino.plugin.iceberg.PartitionFields.toPartitionFields; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestPartitionFields { @@ -87,9 +87,9 @@ public void testParse() private static void assertParse(String value, PartitionSpec expected, String canonicalRepresentation) { - assertEquals(expected.fields().size(), 1); - assertEquals(parseField(value), expected); - assertEquals(getOnlyElement(toPartitionFields(expected)), canonicalRepresentation); + assertThat(expected.fields().size()).isEqualTo(1); + assertThat(parseField(value)).isEqualTo(expected); + assertThat(getOnlyElement(toPartitionFields(expected))).isEqualTo(canonicalRepresentation); } private static void assertParse(String value, PartitionSpec expected) diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestPartitionTransforms.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestPartitionTransforms.java index 6006f370cb1b..affeeb9b0890 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestPartitionTransforms.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestPartitionTransforms.java @@ -29,7 +29,7 @@ import static io.trino.plugin.iceberg.PartitionTransforms.epochYear; import static java.lang.Math.toIntExact; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestPartitionTransforms { @@ -39,13 +39,13 @@ public class TestPartitionTransforms @Test public void testToStringMatchesSpecification() { - assertEquals(Transforms.identity().toString(), "identity"); - assertEquals(Transforms.bucket(13).bind(StringType.get()).toString(), "bucket[13]"); - assertEquals(Transforms.truncate(19).bind(StringType.get()).toString(), "truncate[19]"); - assertEquals(Transforms.year().toString(), "year"); - assertEquals(Transforms.month().toString(), "month"); - assertEquals(Transforms.day().toString(), "day"); - assertEquals(Transforms.hour().toString(), "hour"); + assertThat(Transforms.identity().toString()).isEqualTo("identity"); + assertThat(Transforms.bucket(13).bind(StringType.get()).toString()).isEqualTo("bucket[13]"); + assertThat(Transforms.truncate(19).bind(StringType.get()).toString()).isEqualTo("truncate[19]"); + assertThat(Transforms.year().toString()).isEqualTo("year"); + assertThat(Transforms.month().toString()).isEqualTo("month"); + assertThat(Transforms.day().toString()).isEqualTo("day"); + assertThat(Transforms.hour().toString()).isEqualTo("hour"); } @Test @@ -65,16 +65,30 @@ public void testEpochTransforms() if (time.toLocalTime().equals(LocalTime.MIDNIGHT)) { int epochDay = toIntExact(time.toLocalDate().toEpochDay()); - assertEquals(actualYear, (int) Transforms.year().bind(ICEBERG_DATE).apply(epochDay), time.toString()); - assertEquals(actualMonth, (int) Transforms.month().bind(ICEBERG_DATE).apply(epochDay), time.toString()); - assertEquals(actualDay, (int) Transforms.day().bind(ICEBERG_DATE).apply(epochDay), time.toString()); + assertThat(actualYear) + .describedAs(time.toString()) + .isEqualTo((int) Transforms.year().bind(ICEBERG_DATE).apply(epochDay)); + assertThat(actualMonth) + .describedAs(time.toString()) + .isEqualTo((int) Transforms.month().bind(ICEBERG_DATE).apply(epochDay)); + assertThat(actualDay) + .describedAs(time.toString()) + .isEqualTo((int) Transforms.day().bind(ICEBERG_DATE).apply(epochDay)); } long epochMicro = SECONDS.toMicros(epochSecond); - assertEquals(actualYear, (int) Transforms.year().bind(ICEBERG_TIMESTAMP).apply(epochMicro), time.toString()); - assertEquals(actualMonth, (int) Transforms.month().bind(ICEBERG_TIMESTAMP).apply(epochMicro), time.toString()); - assertEquals(actualDay, (int) Transforms.day().bind(ICEBERG_TIMESTAMP).apply(epochMicro), time.toString()); - assertEquals(actualHour, (int) Transforms.hour().bind(ICEBERG_TIMESTAMP).apply(epochMicro), time.toString()); + assertThat(actualYear) + .describedAs(time.toString()) + .isEqualTo((int) Transforms.year().bind(ICEBERG_TIMESTAMP).apply(epochMicro)); + assertThat(actualMonth) + .describedAs(time.toString()) + .isEqualTo((int) Transforms.month().bind(ICEBERG_TIMESTAMP).apply(epochMicro)); + assertThat(actualDay) + .describedAs(time.toString()) + .isEqualTo((int) Transforms.day().bind(ICEBERG_TIMESTAMP).apply(epochMicro)); + assertThat(actualHour) + .describedAs(time.toString()) + .isEqualTo((int) Transforms.hour().bind(ICEBERG_TIMESTAMP).apply(epochMicro)); } } } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestSortFieldUtils.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestSortFieldUtils.java index c57f7e1a8aeb..ff00cef248be 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestSortFieldUtils.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestSortFieldUtils.java @@ -24,8 +24,8 @@ import java.util.function.Consumer; import static io.trino.plugin.iceberg.SortFieldUtils.parseSortFields; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestSortFieldUtils { @@ -100,8 +100,8 @@ public void testParse() private static void assertParse(@Language("SQL") String value, SortOrder expected) { - assertEquals(expected.fields().size(), 1); - assertEquals(parseField(value), expected); + assertThat(expected.fields().size()).isEqualTo(1); + assertThat(parseField(value)).isEqualTo(expected); } private static void assertDoesNotParse(@Language("SQL") String value) diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/BaseTrinoCatalogTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/BaseTrinoCatalogTest.java index 01a9dfae53ef..36b15e394218 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/BaseTrinoCatalogTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/BaseTrinoCatalogTest.java @@ -55,8 +55,6 @@ import static io.trino.testing.TestingNames.randomNameSuffix; import static java.util.Locale.ENGLISH; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotEquals; public abstract class BaseTrinoCatalogTest { @@ -79,8 +77,8 @@ public void testCreateNamespaceWithLocation() namespaceProperties = ImmutableMap.copyOf(namespaceProperties); catalog.createNamespace(SESSION, namespace, namespaceProperties, new TrinoPrincipal(PrincipalType.USER, SESSION.getUser())); assertThat(catalog.listNamespaces(SESSION)).contains(namespace); - assertEquals(catalog.loadNamespaceMetadata(SESSION, namespace), namespaceProperties); - assertEquals(catalog.defaultTableLocation(SESSION, new SchemaTableName(namespace, "table")), namespaceLocation.replaceAll("/$", "") + "/table"); + assertThat(catalog.loadNamespaceMetadata(SESSION, namespace)).isEqualTo(namespaceProperties); + assertThat(catalog.defaultTableLocation(SESSION, new SchemaTableName(namespace, "table"))).isEqualTo(namespaceLocation.replaceAll("/$", "") + "/table"); catalog.dropNamespace(SESSION, namespace); assertThat(catalog.listNamespaces(SESSION)).doesNotContain(namespace); } @@ -154,12 +152,12 @@ public void testCreateTable() assertThat(catalog.listTables(SESSION, Optional.empty())).contains(schemaTableName); Table icebergTable = catalog.loadTable(SESSION, schemaTableName); - assertEquals(icebergTable.name(), quotedTableName(schemaTableName)); - assertEquals(icebergTable.schema().columns().size(), 1); - assertEquals(icebergTable.schema().columns().get(0).name(), "col1"); - assertEquals(icebergTable.schema().columns().get(0).type(), Types.LongType.get()); - assertEquals(icebergTable.location(), tableLocation); - assertEquals(icebergTable.sortOrder().isUnsorted(), true); + assertThat(icebergTable.name()).isEqualTo(quotedTableName(schemaTableName)); + assertThat(icebergTable.schema().columns().size()).isEqualTo(1); + assertThat(icebergTable.schema().columns().get(0).name()).isEqualTo("col1"); + assertThat(icebergTable.schema().columns().get(0).type()).isEqualTo(Types.LongType.get()); + assertThat(icebergTable.location()).isEqualTo(tableLocation); + assertThat(icebergTable.sortOrder().isUnsorted()).isEqualTo(true); assertThat(icebergTable.properties()).containsAllEntriesOf(tableProperties); catalog.dropTable(SESSION, schemaTableName); @@ -215,19 +213,19 @@ public void testCreateWithSortTable() assertThat(catalog.listTables(SESSION, Optional.empty())).contains(schemaTableName); Table icebergTable = catalog.loadTable(SESSION, schemaTableName); - assertEquals(icebergTable.name(), quotedTableName(schemaTableName)); - assertEquals(icebergTable.schema().columns().size(), 4); - assertEquals(icebergTable.schema().columns().get(0).name(), "col1"); - assertEquals(icebergTable.schema().columns().get(0).type(), Types.LongType.get()); - assertEquals(icebergTable.schema().columns().get(1).name(), "col2"); - assertEquals(icebergTable.schema().columns().get(1).type(), Types.StringType.get()); - assertEquals(icebergTable.location(), tableLocation); - assertEquals(icebergTable.schema().columns().get(2).name(), "col3"); - assertEquals(icebergTable.schema().columns().get(2).type(), Types.TimestampType.withZone()); - assertEquals(icebergTable.schema().columns().get(3).name(), "col4"); - assertEquals(icebergTable.schema().columns().get(3).type(), Types.StringType.get()); - assertEquals(icebergTable.location(), tableLocation); - assertEquals(icebergTable.sortOrder(), sortOrder); + assertThat(icebergTable.name()).isEqualTo(quotedTableName(schemaTableName)); + assertThat(icebergTable.schema().columns().size()).isEqualTo(4); + assertThat(icebergTable.schema().columns().get(0).name()).isEqualTo("col1"); + assertThat(icebergTable.schema().columns().get(0).type()).isEqualTo(Types.LongType.get()); + assertThat(icebergTable.schema().columns().get(1).name()).isEqualTo("col2"); + assertThat(icebergTable.schema().columns().get(1).type()).isEqualTo(Types.StringType.get()); + assertThat(icebergTable.location()).isEqualTo(tableLocation); + assertThat(icebergTable.schema().columns().get(2).name()).isEqualTo("col3"); + assertThat(icebergTable.schema().columns().get(2).type()).isEqualTo(Types.TimestampType.withZone()); + assertThat(icebergTable.schema().columns().get(3).name()).isEqualTo("col4"); + assertThat(icebergTable.schema().columns().get(3).type()).isEqualTo(Types.StringType.get()); + assertThat(icebergTable.location()).isEqualTo(tableLocation); + assertThat(icebergTable.sortOrder()).isEqualTo(sortOrder); catalog.dropTable(SESSION, schemaTableName); } @@ -317,7 +315,8 @@ public void testUseUniqueTableLocations() try { String location1 = catalog.defaultTableLocation(SESSION, schemaTableName); String location2 = catalog.defaultTableLocation(SESSION, schemaTableName); - assertNotEquals(location1, location2); + assertThat(location1) + .isNotEqualTo(location2); assertThat(location1) .startsWith(namespaceLocation + "/"); @@ -366,7 +365,7 @@ public void testView() assertThat(catalog.listViews(SESSION, Optional.of(namespace))).contains(schemaTableName); Map views = catalog.getViews(SESSION, Optional.of(schemaTableName.getSchemaName())); - assertEquals(views.size(), 1); + assertThat(views.size()).isEqualTo(1); assertViewDefinition(views.get(schemaTableName), viewDefinition); assertViewDefinition(catalog.getView(SESSION, schemaTableName).orElseThrow(), viewDefinition); @@ -374,7 +373,7 @@ public void testView() assertThat(catalog.listTables(SESSION, Optional.of(namespace))).doesNotContain(schemaTableName); assertThat(catalog.listViews(SESSION, Optional.of(namespace))).doesNotContain(schemaTableName); views = catalog.getViews(SESSION, Optional.of(schemaTableName.getSchemaName())); - assertEquals(views.size(), 1); + assertThat(views.size()).isEqualTo(1); assertViewDefinition(views.get(renamedSchemaTableName), viewDefinition); assertViewDefinition(catalog.getView(SESSION, renamedSchemaTableName).orElseThrow(), viewDefinition); assertThat(catalog.getView(SESSION, schemaTableName)).isEmpty(); @@ -411,20 +410,20 @@ private String arbitraryTableLocation(TrinoCatalog catalog, ConnectorSession ses private void assertViewDefinition(ConnectorViewDefinition actualView, ConnectorViewDefinition expectedView) { - assertEquals(actualView.getOriginalSql(), expectedView.getOriginalSql()); - assertEquals(actualView.getCatalog(), expectedView.getCatalog()); - assertEquals(actualView.getSchema(), expectedView.getSchema()); - assertEquals(actualView.getColumns().size(), expectedView.getColumns().size()); + assertThat(actualView.getOriginalSql()).isEqualTo(expectedView.getOriginalSql()); + assertThat(actualView.getCatalog()).isEqualTo(expectedView.getCatalog()); + assertThat(actualView.getSchema()).isEqualTo(expectedView.getSchema()); + assertThat(actualView.getColumns().size()).isEqualTo(expectedView.getColumns().size()); for (int i = 0; i < actualView.getColumns().size(); i++) { assertViewColumnDefinition(actualView.getColumns().get(i), expectedView.getColumns().get(i)); } - assertEquals(actualView.getOwner(), expectedView.getOwner()); - assertEquals(actualView.isRunAsInvoker(), expectedView.isRunAsInvoker()); + assertThat(actualView.getOwner()).isEqualTo(expectedView.getOwner()); + assertThat(actualView.isRunAsInvoker()).isEqualTo(expectedView.isRunAsInvoker()); } private void assertViewColumnDefinition(ConnectorViewDefinition.ViewColumn actualViewColumn, ConnectorViewDefinition.ViewColumn expectedViewColumn) { - assertEquals(actualViewColumn.getName(), expectedViewColumn.getName()); - assertEquals(actualViewColumn.getType(), expectedViewColumn.getType()); + assertThat(actualViewColumn.getName()).isEqualTo(expectedViewColumn.getName()); + assertThat(actualViewColumn.getType()).isEqualTo(expectedViewColumn.getType()); } } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestTrinoGlueCatalog.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestTrinoGlueCatalog.java index c44dc03ee138..3bb113369859 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestTrinoGlueCatalog.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestTrinoGlueCatalog.java @@ -52,7 +52,6 @@ import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.util.Locale.ENGLISH; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestTrinoGlueCatalog extends BaseTrinoCatalogTest @@ -169,7 +168,7 @@ public void testDefaultLocation() try { File expectedSchemaDirectory = new File(tmpDirectory.toFile(), namespace + ".db"); File expectedTableDirectory = new File(expectedSchemaDirectory, schemaTableName.getTableName()); - assertEquals(catalogWithDefaultLocation.defaultTableLocation(SESSION, schemaTableName), expectedTableDirectory.toPath().toAbsolutePath().toString()); + assertThat(catalogWithDefaultLocation.defaultTableLocation(SESSION, schemaTableName)).isEqualTo(expectedTableDirectory.toPath().toAbsolutePath().toString()); } finally { try { diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestIcebergTrinoRestCatalogConnectorSmokeTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestIcebergTrinoRestCatalogConnectorSmokeTest.java index 7cc4994a426a..6a1f7559c199 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestIcebergTrinoRestCatalogConnectorSmokeTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestIcebergTrinoRestCatalogConnectorSmokeTest.java @@ -183,7 +183,7 @@ public void testDropTableWithMissingSnapshotFile() public void testDropTableWithMissingManifestListFile() { assertThatThrownBy(super::testDropTableWithMissingManifestListFile) - .hasMessageContaining("Table location should not exist expected [false] but found [true]"); + .hasMessageContaining("Table location should not exist"); } @Test @@ -191,7 +191,7 @@ public void testDropTableWithMissingManifestListFile() public void testDropTableWithMissingDataFile() { assertThatThrownBy(super::testDropTableWithMissingDataFile) - .hasMessageContaining("Table location should not exist expected [false] but found [true]"); + .hasMessageContaining("Table location should not exist"); } @Test diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestOAuth2SecurityConfig.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestOAuth2SecurityConfig.java index 368f74920f2f..ca43a8052d2e 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestOAuth2SecurityConfig.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/rest/TestOAuth2SecurityConfig.java @@ -21,7 +21,7 @@ import static io.airlift.configuration.testing.ConfigAssertions.assertFullMapping; import static io.airlift.configuration.testing.ConfigAssertions.assertRecordedDefaults; import static io.airlift.configuration.testing.ConfigAssertions.recordDefaults; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestOAuth2SecurityConfig { @@ -44,7 +44,7 @@ public void testExplicitPropertyMappings() OAuth2SecurityConfig expected = new OAuth2SecurityConfig() .setCredential("credential") .setToken("token"); - assertTrue(expected.credentialOrTokenPresent()); + assertThat(expected.credentialOrTokenPresent()).isTrue(); assertFullMapping(properties, expected); } } diff --git a/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/BaseKuduWithStandardInferSchemaConnectorSmokeTest.java b/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/BaseKuduWithStandardInferSchemaConnectorSmokeTest.java index bf1e61458d13..5768ef80816b 100644 --- a/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/BaseKuduWithStandardInferSchemaConnectorSmokeTest.java +++ b/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/BaseKuduWithStandardInferSchemaConnectorSmokeTest.java @@ -17,7 +17,7 @@ import java.util.Optional; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public abstract class BaseKuduWithStandardInferSchemaConnectorSmokeTest extends BaseKuduConnectorSmokeTest @@ -33,6 +33,6 @@ public void testListingOfTableForDefaultSchema() { // The special $schemas table is created when listing schema names with schema emulation enabled // Depending on test ordering, this table may or may not be created when this test runs, so filter it out - assertEquals(computeActual("SHOW TABLES FROM default LIKE '%$schemas'").getRowCount(), 0); + assertThat(computeActual("SHOW TABLES FROM default LIKE '%$schemas'").getRowCount()).isEqualTo(0); } } diff --git a/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduConnectorTest.java b/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduConnectorTest.java index 29677ab90a77..15cf25573b63 100644 --- a/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduConnectorTest.java +++ b/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduConnectorTest.java @@ -38,10 +38,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assumptions.abort; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; public class TestKuduConnectorTest extends BaseConnectorTest @@ -356,26 +352,26 @@ public void testCreateTable() "id INT WITH (primary_key=true)," + "a bigint, b double, c varchar(50))" + "WITH (partition_by_hash_columns = ARRAY['id'], partition_by_hash_buckets = 2)"); - assertTrue(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isTrue(); assertTableColumnNames(tableName, "id", "a", "b", "c"); - assertNull(getTableComment(getSession().getCatalog().orElseThrow(), getSession().getSchema().orElseThrow(), tableName)); + assertThat(getTableComment(getSession().getCatalog().orElseThrow(), getSession().getSchema().orElseThrow(), tableName)).isNull(); assertUpdate("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); assertQueryFails("CREATE TABLE " + tableName + " (" + "id INT WITH (primary_key=true)," + "a bad_type, b double, c varchar(50))" + "WITH (partition_by_hash_columns = ARRAY['id'], partition_by_hash_buckets = 2)", ".* Unknown type 'bad_type' for column 'a'"); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); tableName = "test_create_if_not_exists_" + randomNameSuffix(); assertUpdate("CREATE TABLE " + tableName + " (" + "id INT WITH (primary_key=true)," + "a bigint, b varchar(50), c double)" + "WITH (partition_by_hash_columns = ARRAY['id'], partition_by_hash_buckets = 2)"); - assertTrue(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isTrue(); assertTableColumnNames(tableName, "id", "a", "b", "c"); assertUpdate( @@ -383,11 +379,11 @@ public void testCreateTable() "id INT WITH (primary_key=true)," + "d bigint, e varchar(50))" + "WITH (partition_by_hash_columns = ARRAY['id'], partition_by_hash_buckets = 2)"); - assertTrue(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isTrue(); assertTableColumnNames(tableName, "id", "a", "b", "c"); assertUpdate("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); // Test CREATE TABLE LIKE tableName = "test_create_origin_" + randomNameSuffix(); @@ -395,7 +391,7 @@ public void testCreateTable() "id INT WITH (primary_key=true)," + "a bigint, b double, c varchar(50))" + "WITH (partition_by_hash_columns = ARRAY['id'], partition_by_hash_buckets = 2)"); - assertTrue(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isTrue(); assertTableColumnNames(tableName, "id", "a", "b", "c"); // TODO: remove assertThatThrownBy and uncomment the commented lines @@ -412,7 +408,7 @@ public void testCreateTable() //assertTableColumnNames(tableNameLike, "a", "b", "c", "d", "e"); assertUpdate("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); //assertUpdate("DROP TABLE " + tableNameLike); //assertFalse(getQueryRunner().tableExists(getSession(), tableNameLike)); @@ -430,7 +426,7 @@ public void testCreateTableWithLongTableName() "id INT WITH (primary_key=true)," + "a VARCHAR)" + "WITH (partition_by_hash_columns = ARRAY['id'], partition_by_hash_buckets = 2)"); - assertTrue(getQueryRunner().tableExists(getSession(), validTableName)); + assertThat(getQueryRunner().tableExists(getSession(), validTableName)).isTrue(); assertUpdate("DROP TABLE " + validTableName); String invalidTableName = baseTableName + "z".repeat(256 - baseTableName.length() + 1); @@ -439,7 +435,7 @@ public void testCreateTableWithLongTableName() "a VARCHAR)" + "WITH (partition_by_hash_columns = ARRAY['id'], partition_by_hash_buckets = 2)")) .hasMessageContaining("invalid table name"); - assertFalse(getQueryRunner().tableExists(getSession(), validTableName)); + assertThat(getQueryRunner().tableExists(getSession(), validTableName)).isFalse(); } @Test @@ -457,7 +453,7 @@ public void testCreateTableWithLongColumnName() "id INT WITH (primary_key=true)," + validColumnName + " bigint)" + "WITH (partition_by_hash_columns = ARRAY['id'], partition_by_hash_buckets = 2)"); - assertTrue(columnExists(tableName, validColumnName)); + assertThat(columnExists(tableName, validColumnName)).isTrue(); assertUpdate("DROP TABLE " + tableName); String invalidColumnName = validColumnName + "z"; @@ -466,7 +462,7 @@ public void testCreateTableWithLongColumnName() invalidColumnName + " bigint)" + "WITH (partition_by_hash_columns = ARRAY['id'], partition_by_hash_buckets = 2)")) .satisfies(this::verifyColumnNameLengthFailurePermissible); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); } @Test @@ -497,10 +493,10 @@ public void testDropTable() "id INT WITH (primary_key=true)," + "col bigint)" + "WITH (partition_by_hash_columns = ARRAY['id'], partition_by_hash_buckets = 2)"); - assertTrue(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isTrue(); assertUpdate("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); } @Override @@ -537,7 +533,7 @@ public void testAddColumnWithCommentSpecialCharacter(String comment) "test_add_col_", "(id INT WITH (primary_key=true), a_varchar varchar) WITH (partition_by_hash_columns = ARRAY['id'], partition_by_hash_buckets = 2)")) { assertUpdate("ALTER TABLE " + table.getName() + " ADD COLUMN b_varchar varchar COMMENT " + varcharLiteral(comment)); - assertEquals(getColumnComment(table.getName(), "b_varchar"), comment); + assertThat(getColumnComment(table.getName(), "b_varchar")).isEqualTo(comment); } } @@ -1012,7 +1008,7 @@ public void testCreateTableWithTableComment() assertUpdate("CREATE TABLE " + tableName + " (a bigint WITH (primary_key=true)) COMMENT 'test comment' " + "WITH (partition_by_hash_columns = ARRAY['a'], partition_by_hash_buckets = 2)"); - assertEquals(getTableComment("kudu", "default", tableName), "test comment"); + assertThat(getTableComment("kudu", "default", tableName)).isEqualTo("test comment"); assertUpdate("DROP TABLE " + tableName); } @@ -1025,7 +1021,7 @@ protected void testCreateTableWithTableCommentSpecialCharacter(String comment) "test_create_", "(a bigint WITH (primary_key=true)) COMMENT " + varcharLiteral(comment) + "WITH (partition_by_hash_columns = ARRAY['a'], partition_by_hash_buckets = 2)")) { - assertEquals(getTableComment("kudu", "default", table.getName()), comment); + assertThat(getTableComment("kudu", "default", table.getName())).isEqualTo(comment); } } @@ -1097,8 +1093,9 @@ protected void verifyColumnNameLengthFailurePermissible(Throwable e) private void assertTableProperty(String tableProperties, String key, String regexValue) { - assertTrue(Pattern.compile(key + "\\s*=\\s*" + regexValue + ",?\\s+").matcher(tableProperties).find(), - "Not found: " + key + " = " + regexValue + " in " + tableProperties); + assertThat(Pattern.compile(key + "\\s*=\\s*" + regexValue + ",?\\s+").matcher(tableProperties).find()) + .describedAs("Not found: " + key + " = " + regexValue + " in " + tableProperties) + .isTrue(); } private void withTableName(String prefix, Consumer consumer) diff --git a/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduIntegrationDecimalColumns.java b/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduIntegrationDecimalColumns.java index cfa2593177c7..c2420d76be86 100644 --- a/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduIntegrationDecimalColumns.java +++ b/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduIntegrationDecimalColumns.java @@ -24,9 +24,9 @@ import static io.trino.plugin.kudu.KuduQueryRunnerFactory.createKuduQueryRunner; import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.offset; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) public class TestKuduIntegrationDecimalColumns @@ -114,11 +114,13 @@ private void doTestCreateTableWithDecimalColumn(TestDecimal decimal) assertUpdate(format("INSERT INTO %s VALUES(1, DECIMAL '%s')", testTable.getName(), insertValue), 1); MaterializedResult result = computeActual(format("SELECT id, CAST((dec - (DECIMAL '%s')) as DOUBLE) FROM %s", insertValue, testTable.getName())); - assertEquals(result.getRowCount(), 1); + assertThat(result.getRowCount()).isEqualTo(1); Object obj = result.getMaterializedRows().get(0).getField(1); - assertTrue(obj instanceof Double); + assertThat(obj instanceof Double).isTrue(); Double actual = (Double) obj; - assertEquals(0, actual, 0.3 * Math.pow(0.1, decimal.scale), "p=" + decimal.precision + ",s=" + decimal.scale + " => " + actual + ",insert = " + insertValue); + assertThat(actual) + .describedAs("p=" + decimal.precision + ",s=" + decimal.scale + " => " + actual + ",insert = " + insertValue) + .isCloseTo(0, offset(0.3 * Math.pow(0.1, decimal.scale))); } } diff --git a/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduIntegrationDynamicFilter.java b/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduIntegrationDynamicFilter.java index 553fbc81a203..a6b0c6be87c0 100644 --- a/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduIntegrationDynamicFilter.java +++ b/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduIntegrationDynamicFilter.java @@ -54,9 +54,7 @@ import static io.trino.spi.connector.Constraint.alwaysTrue; import static io.trino.sql.planner.OptimizerConfig.JoinDistributionType.BROADCAST; import static io.trino.sql.planner.OptimizerConfig.JoinReorderingStrategy.NONE; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestKuduIntegrationDynamicFilter extends AbstractTestQueryFramework @@ -89,7 +87,7 @@ public void testIncompleteDynamicFilterTimeout() .beginTransactionId(transactionId, transactionManager, new AllowAllAccessControl()); QualifiedObjectName tableName = new QualifiedObjectName("kudu", "tpch", "orders"); Optional tableHandle = runner.getMetadata().getTableHandle(session, tableName); - assertTrue(tableHandle.isPresent()); + assertThat(tableHandle.isPresent()).isTrue(); SplitSource splitSource = runner.getSplitManager() .getSplits(session, Span.getInvalid(), tableHandle.get(), new IncompleteDynamicFilter(), alwaysTrue()); List splits = new ArrayList<>(); @@ -97,7 +95,7 @@ public void testIncompleteDynamicFilterTimeout() splits.addAll(splitSource.getNextBatch(1000).get().getSplits()); } splitSource.close(); - assertFalse(splits.isEmpty()); + assertThat(splits.isEmpty()).isFalse(); } private static class IncompleteDynamicFilter @@ -171,8 +169,8 @@ private void assertDynamicFiltering(@Language("SQL") String selectQuery, Session DistributedQueryRunner runner = getDistributedQueryRunner(); MaterializedResultWithQueryId result = runner.executeWithQueryId(session, selectQuery); - assertEquals(result.getResult().getRowCount(), expectedRowCount); - assertEquals(getOperatorRowsRead(runner, result.getQueryId()), Ints.asList(expectedOperatorRowsRead)); + assertThat(result.getResult().getRowCount()).isEqualTo(expectedRowCount); + assertThat(getOperatorRowsRead(runner, result.getQueryId())).isEqualTo(Ints.asList(expectedOperatorRowsRead)); } private Session withBroadcastJoin() diff --git a/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduIntegrationHashPartitioning.java b/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduIntegrationHashPartitioning.java index 09db254e5513..3c0cb1b917ae 100644 --- a/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduIntegrationHashPartitioning.java +++ b/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduIntegrationHashPartitioning.java @@ -20,7 +20,7 @@ import org.junit.jupiter.api.Test; import static io.trino.plugin.kudu.KuduQueryRunnerFactory.createKuduQueryRunner; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestKuduIntegrationHashPartitioning extends AbstractTestQueryFramework @@ -78,6 +78,6 @@ private void doTestCreateTable(String tableName, @Language("SQL") String createT assertUpdate(insert, 1); MaterializedResult result = computeActual("SELECT id FROM " + tableName); - assertEquals(result.getRowCount(), 1); + assertThat(result.getRowCount()).isEqualTo(1); } } diff --git a/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduIntegrationIntegerColumns.java b/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduIntegrationIntegerColumns.java index b44f0c130951..c20bddf65646 100644 --- a/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduIntegrationIntegerColumns.java +++ b/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduIntegrationIntegerColumns.java @@ -19,9 +19,8 @@ import org.junit.jupiter.api.Test; import static io.trino.plugin.kudu.KuduQueryRunnerFactory.createKuduQueryRunner; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Fail.fail; public class TestKuduIntegrationIntegerColumns extends AbstractTestQueryFramework @@ -68,24 +67,24 @@ private void doTestCreateTableWithIntegerColumn(TestInt test) assertUpdate("INSERT INTO test_int VALUES(1, CAST(" + casted + " AS " + test.type + "))", 1); MaterializedResult result = computeActual("SELECT id, intcol FROM test_int"); - assertEquals(result.getRowCount(), 1); + assertThat(result.getRowCount()).isEqualTo(1); Object obj = result.getMaterializedRows().get(0).getField(1); switch (test.bits) { case 64: - assertTrue(obj instanceof Long); - assertEquals(((Long) obj).longValue(), casted); + assertThat(obj instanceof Long).isTrue(); + assertThat(((Long) obj).longValue()).isEqualTo(casted); break; case 32: - assertTrue(obj instanceof Integer); - assertEquals(((Integer) obj).longValue(), casted); + assertThat(obj instanceof Integer).isTrue(); + assertThat(((Integer) obj).longValue()).isEqualTo(casted); break; case 16: - assertTrue(obj instanceof Short); - assertEquals(((Short) obj).longValue(), casted); + assertThat(obj instanceof Short).isTrue(); + assertThat(((Short) obj).longValue()).isEqualTo(casted); break; case 8: - assertTrue(obj instanceof Byte); - assertEquals(((Byte) obj).longValue(), casted); + assertThat(obj instanceof Byte).isTrue(); + assertThat(((Byte) obj).longValue()).isEqualTo(casted); break; default: fail("Unexpected bits: " + test.bits); diff --git a/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduIntegrationRangePartitioning.java b/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduIntegrationRangePartitioning.java index 4dc7ec1fd34d..0e2975b1d0e0 100644 --- a/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduIntegrationRangePartitioning.java +++ b/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduIntegrationRangePartitioning.java @@ -20,8 +20,7 @@ import static io.trino.plugin.kudu.KuduQueryRunnerFactory.createKuduQueryRunner; import static java.lang.String.join; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestKuduIntegrationRangePartitioning extends AbstractTestQueryFramework @@ -133,12 +132,14 @@ private void doTestCreateAndChangeTableWithRangePartition(TestRanges ranges) assertUpdate(dropPartition3); MaterializedResult result = computeActual("SHOW CREATE TABLE " + tableName); - assertEquals(result.getRowCount(), 1); + assertThat(result.getRowCount()).isEqualTo(1); String createSQL = result.getMaterializedRows().get(0).getField(0).toString(); String rangesArray = "'[" + ranges.cmp1 + "," + ranges.cmp2 + "," + ranges.cmp4 + "]'"; rangesArray = rangesArray.replaceAll("\\s+", ""); String expectedRanges = "range_partitions = " + rangesArray; - assertTrue(createSQL.contains(expectedRanges), createSQL + "\ncontains\n" + expectedRanges); + assertThat(createSQL.contains(expectedRanges)) + .describedAs(createSQL + "\ncontains\n" + expectedRanges) + .isTrue(); } static class TestRanges diff --git a/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/properties/TestRangePartitionSerialization.java b/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/properties/TestRangePartitionSerialization.java index e61eeead07d7..271d7989c862 100644 --- a/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/properties/TestRangePartitionSerialization.java +++ b/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/properties/TestRangePartitionSerialization.java @@ -18,7 +18,7 @@ import java.io.IOException; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestRangePartitionSerialization { @@ -41,7 +41,7 @@ public void testDeserializationSerialization() RangePartition partition = mapper.readValue(input, RangePartition.class); String serialized = mapper.writeValueAsString(partition); - assertEquals(serialized, input); + assertThat(serialized).isEqualTo(input); } } } diff --git a/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/schema/TestSchemaEmulation.java b/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/schema/TestSchemaEmulation.java index 96f5f07a8db3..dd8e506de801 100644 --- a/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/schema/TestSchemaEmulation.java +++ b/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/schema/TestSchemaEmulation.java @@ -16,7 +16,7 @@ import io.trino.spi.connector.SchemaTableName; import org.junit.jupiter.api.Test; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestSchemaEmulation { @@ -64,11 +64,11 @@ public void testFromRawToRaw() SchemaEmulation emulation = input.tableNamePrefix != null ? new SchemaEmulationByTableNameConvention(input.tableNamePrefix) : new NoSchemaEmulation(); SchemaTableName schemaTableName = emulation.fromRawName(input.kuduTableName); - assertEquals(input.valid, schemaTableName != null); + assertThat(input.valid).isEqualTo(schemaTableName != null); if (input.valid) { - assertEquals(schemaTableName, new SchemaTableName(input.prestoSchema, input.prestoTable)); + assertThat(schemaTableName).isEqualTo(new SchemaTableName(input.prestoSchema, input.prestoTable)); String raw = emulation.toRawName(schemaTableName); - assertEquals(raw, input.kuduTableName); + assertThat(raw).isEqualTo(input.kuduTableName); } } } diff --git a/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryConnectorTest.java b/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryConnectorTest.java index 50fb43f610b2..b8284237037e 100644 --- a/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryConnectorTest.java +++ b/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryConnectorTest.java @@ -45,9 +45,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assumptions.abort; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestMemoryConnectorTest extends BaseConnectorTest @@ -196,12 +193,12 @@ public void testPhysicalInputPositions() getSession(), "SELECT * FROM lineitem JOIN tpch.tiny.supplier ON lineitem.suppkey = supplier.suppkey " + "AND supplier.name = 'Supplier#000000001'"); - assertEquals(result.getResult().getRowCount(), 615); + assertThat(result.getResult().getRowCount()).isEqualTo(615); OperatorStats probeStats = getScanOperatorStats(getDistributedQueryRunner(), result.getQueryId()).stream() .findFirst().orElseThrow(); // there should be two: one for lineitem and one for supplier - assertEquals(probeStats.getInputPositions(), 615); - assertEquals(probeStats.getPhysicalInputPositions(), LINEITEM_COUNT); + assertThat(probeStats.getInputPositions()).isEqualTo(615); + assertThat(probeStats.getPhysicalInputPositions()).isEqualTo(LINEITEM_COUNT); } @Test @@ -474,8 +471,8 @@ private void assertDynamicFiltering(@Language("SQL") String selectQuery, Session { MaterializedResultWithQueryId result = getDistributedQueryRunner().executeWithQueryId(session, selectQuery); - assertEquals(result.getResult().getRowCount(), expectedRowCount); - assertEquals(getOperatorRowsRead(getDistributedQueryRunner(), result.getQueryId()), Ints.asList(expectedOperatorRowsRead)); + assertThat(result.getResult().getRowCount()).isEqualTo(expectedRowCount); + assertThat(getOperatorRowsRead(getDistributedQueryRunner(), result.getQueryId())).isEqualTo(Ints.asList(expectedOperatorRowsRead)); } private Session withLargeDynamicFilters(JoinDistributionType joinDistributionType) @@ -560,11 +557,11 @@ public void testCreateTableInNonDefaultSchema() public void testCreateTableAndViewInNotExistSchema() { assertQueryFails("CREATE TABLE schema3.test_table3 (x date)", "Schema schema3 not found"); - assertFalse(getQueryRunner().tableExists(getSession(), "schema3.test_table3")); + assertThat(getQueryRunner().tableExists(getSession(), "schema3.test_table3")).isFalse(); assertQueryFails("CREATE VIEW schema4.test_view4 AS SELECT 123 x", "Schema schema4 not found"); - assertFalse(getQueryRunner().tableExists(getSession(), "schema4.test_view4")); + assertThat(getQueryRunner().tableExists(getSession(), "schema4.test_view4")).isFalse(); assertQueryFails("CREATE OR REPLACE VIEW schema5.test_view5 AS SELECT 123 x", "Schema schema5 not found"); - assertFalse(getQueryRunner().tableExists(getSession(), "schema5.test_view5")); + assertThat(getQueryRunner().tableExists(getSession(), "schema5.test_view5")).isFalse(); } @Test @@ -580,7 +577,7 @@ public void testViews() assertQuery("SELECT * FROM test_view", query); - assertTrue(computeActual("SHOW TABLES").getOnlyColumnAsSet().contains("test_view")); + assertThat(computeActual("SHOW TABLES").getOnlyColumnAsSet().contains("test_view")).isTrue(); assertUpdate("DROP VIEW test_view"); assertQueryFails("DROP VIEW test_view", "line 1:1: View 'memory.default.test_view' does not exist"); diff --git a/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryMetadata.java b/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryMetadata.java index 15a65ed02dc6..d429b45e4961 100644 --- a/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryMetadata.java +++ b/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryMetadata.java @@ -42,12 +42,7 @@ import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotEquals; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.expectThrows; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Fail.fail; public class TestMemoryMetadata { @@ -68,8 +63,12 @@ public void tableIsCreatedAfterCommits() metadata.finishCreateTable(SESSION, table, ImmutableList.of(), ImmutableList.of()); List tables = metadata.listTables(SESSION, Optional.empty()); - assertEquals(tables.size(), 1, "Expected only one table"); - assertEquals(tables.get(0).getTableName(), "temp_table", "Expected table with name 'temp_table'"); + assertThat(tables.size()) + .describedAs("Expected only one table") + .isEqualTo(1); + assertThat(tables.get(0).getTableName()) + .describedAs("Expected table with name 'temp_table'") + .isEqualTo("temp_table"); } @Test @@ -106,7 +105,7 @@ public void testActiveTableIds() MemoryTableHandle firstTableHandle = (MemoryTableHandle) metadata.getTableHandle(SESSION, firstTableName, Optional.empty(), Optional.empty()); long firstTableId = firstTableHandle.getId(); - assertTrue(metadata.beginInsert(SESSION, firstTableHandle, ImmutableList.of(), NO_RETRIES).getActiveTableIds().contains(firstTableId)); + assertThat(metadata.beginInsert(SESSION, firstTableHandle, ImmutableList.of(), NO_RETRIES).getActiveTableIds().contains(firstTableId)).isTrue(); SchemaTableName secondTableName = new SchemaTableName("default", "second_table"); metadata.createTable(SESSION, new ConnectorTableMetadata(secondTableName, ImmutableList.of(), ImmutableMap.of()), false); @@ -114,9 +113,10 @@ public void testActiveTableIds() MemoryTableHandle secondTableHandle = (MemoryTableHandle) metadata.getTableHandle(SESSION, secondTableName, Optional.empty(), Optional.empty()); long secondTableId = secondTableHandle.getId(); - assertNotEquals(firstTableId, secondTableId); - assertTrue(metadata.beginInsert(SESSION, secondTableHandle, ImmutableList.of(), NO_RETRIES).getActiveTableIds().contains(firstTableId)); - assertTrue(metadata.beginInsert(SESSION, secondTableHandle, ImmutableList.of(), NO_RETRIES).getActiveTableIds().contains(secondTableId)); + assertThat(firstTableId) + .isNotEqualTo(secondTableId); + assertThat(metadata.beginInsert(SESSION, secondTableHandle, ImmutableList.of(), NO_RETRIES).getActiveTableIds().contains(firstTableId)).isTrue(); + assertThat(metadata.beginInsert(SESSION, secondTableHandle, ImmutableList.of(), NO_RETRIES).getActiveTableIds().contains(secondTableId)).isTrue(); } @Test @@ -134,7 +134,9 @@ public void testReadTableBeforeCreationCompleted() NO_RETRIES); List tableNames = metadata.listTables(SESSION, Optional.empty()); - assertEquals(tableNames.size(), 1, "Expected exactly one table"); + assertThat(tableNames.size()) + .describedAs("Expected exactly one table") + .isEqualTo(1); metadata.finishCreateTable(SESSION, table, ImmutableList.of(), ImmutableList.of()); } @@ -143,10 +145,10 @@ public void testReadTableBeforeCreationCompleted() public void testCreateSchema() { MemoryMetadata metadata = createMetadata(); - assertEquals(metadata.listSchemaNames(SESSION), ImmutableList.of("default")); + assertThat(metadata.listSchemaNames(SESSION)).isEqualTo(ImmutableList.of("default")); metadata.createSchema(SESSION, "test", ImmutableMap.of(), new TrinoPrincipal(USER, SESSION.getUser())); - assertEquals(metadata.listSchemaNames(SESSION), ImmutableList.of("default", "test")); - assertEquals(metadata.listTables(SESSION, Optional.of("test")), ImmutableList.of()); + assertThat(metadata.listSchemaNames(SESSION)).isEqualTo(ImmutableList.of("default", "test")); + assertThat(metadata.listTables(SESSION, Optional.of("test"))).isEqualTo(ImmutableList.of()); SchemaTableName tableName = new SchemaTableName("test", "first_table"); metadata.createTable( @@ -157,9 +159,9 @@ public void testCreateSchema() ImmutableMap.of()), false); - assertEquals(metadata.listTables(SESSION, Optional.empty()), ImmutableList.of(tableName)); - assertEquals(metadata.listTables(SESSION, Optional.of("test")), ImmutableList.of(tableName)); - assertEquals(metadata.listTables(SESSION, Optional.of("default")), ImmutableList.of()); + assertThat(metadata.listTables(SESSION, Optional.empty())).isEqualTo(ImmutableList.of(tableName)); + assertThat(metadata.listTables(SESSION, Optional.of("test"))).isEqualTo(ImmutableList.of(tableName)); + assertThat(metadata.listTables(SESSION, Optional.of("default"))).isEqualTo(ImmutableList.of()); } @Test @@ -229,9 +231,9 @@ public void testViews() // verify getting data Map views = metadata.getViews(SESSION, Optional.of("test")); - assertEquals(views.keySet(), ImmutableSet.of(test1, test2)); - assertEquals(views.get(test1).getOriginalSql(), "test1"); - assertEquals(views.get(test2).getOriginalSql(), "test2"); + assertThat(views.keySet()).isEqualTo(ImmutableSet.of(test1, test2)); + assertThat(views.get(test1).getOriginalSql()).isEqualTo("test1"); + assertThat(views.get(test2).getOriginalSql()).isEqualTo("test2"); // all schemas assertThat(metadata.getViews(SESSION, Optional.empty())) @@ -277,7 +279,7 @@ public void testViews() public void testCreateTableAndViewInNotExistSchema() { MemoryMetadata metadata = createMetadata(); - assertEquals(metadata.listSchemaNames(SESSION), ImmutableList.of("default")); + assertThat(metadata.listSchemaNames(SESSION)).isEqualTo(ImmutableList.of("default")); SchemaTableName table1 = new SchemaTableName("test1", "test_schema_table1"); assertTrinoExceptionThrownBy(() -> metadata.beginCreateTable( @@ -287,21 +289,21 @@ public void testCreateTableAndViewInNotExistSchema() NO_RETRIES)) .hasErrorCode(NOT_FOUND) .hasMessage("Schema test1 not found"); - assertNull(metadata.getTableHandle(SESSION, table1, Optional.empty(), Optional.empty())); + assertThat(metadata.getTableHandle(SESSION, table1, Optional.empty(), Optional.empty())).isNull(); SchemaTableName view2 = new SchemaTableName("test2", "test_schema_view2"); assertTrinoExceptionThrownBy(() -> metadata.createView(SESSION, view2, testingViewDefinition("aaa"), false)) .hasErrorCode(NOT_FOUND) .hasMessage("Schema test2 not found"); - assertNull(metadata.getTableHandle(SESSION, view2, Optional.empty(), Optional.empty())); + assertThat(metadata.getTableHandle(SESSION, view2, Optional.empty(), Optional.empty())).isNull(); SchemaTableName view3 = new SchemaTableName("test3", "test_schema_view3"); assertTrinoExceptionThrownBy(() -> metadata.createView(SESSION, view3, testingViewDefinition("bbb"), true)) .hasErrorCode(NOT_FOUND) .hasMessage("Schema test3 not found"); - assertNull(metadata.getTableHandle(SESSION, view3, Optional.empty(), Optional.empty())); + assertThat(metadata.getTableHandle(SESSION, view3, Optional.empty(), Optional.empty())).isNull(); - assertEquals(metadata.listSchemaNames(SESSION), ImmutableList.of("default")); + assertThat(metadata.listSchemaNames(SESSION)).isEqualTo(ImmutableList.of("default")); } @Test @@ -320,25 +322,28 @@ public void testRenameTable() // rename table to schema which does not exist SchemaTableName invalidSchemaTableName = new SchemaTableName("test_schema_not_exist", "test_table_renamed"); ConnectorTableHandle tableHandle = metadata.getTableHandle(SESSION, tableName, Optional.empty(), Optional.empty()); - Throwable throwable = expectThrows(SchemaNotFoundException.class, () -> metadata.renameTable(SESSION, tableHandle, invalidSchemaTableName)); - assertEquals(throwable.getMessage(), "Schema test_schema_not_exist not found"); + assertThatThrownBy(() -> metadata.renameTable(SESSION, tableHandle, invalidSchemaTableName)) + .isInstanceOf(SchemaNotFoundException.class) + .hasMessage("Schema test_schema_not_exist not found"); // rename table to same schema SchemaTableName sameSchemaTableName = new SchemaTableName("test_schema", "test_renamed"); metadata.renameTable(SESSION, metadata.getTableHandle(SESSION, tableName, Optional.empty(), Optional.empty()), sameSchemaTableName); - assertEquals(metadata.listTables(SESSION, Optional.of("test_schema")), ImmutableList.of(sameSchemaTableName)); + assertThat(metadata.listTables(SESSION, Optional.of("test_schema"))).isEqualTo(ImmutableList.of(sameSchemaTableName)); // rename table to different schema metadata.createSchema(SESSION, "test_different_schema", ImmutableMap.of(), new TrinoPrincipal(USER, SESSION.getUser())); SchemaTableName differentSchemaTableName = new SchemaTableName("test_different_schema", "test_renamed"); metadata.renameTable(SESSION, metadata.getTableHandle(SESSION, sameSchemaTableName, Optional.empty(), Optional.empty()), differentSchemaTableName); - assertEquals(metadata.listTables(SESSION, Optional.of("test_schema")), ImmutableList.of()); - assertEquals(metadata.listTables(SESSION, Optional.of("test_different_schema")), ImmutableList.of(differentSchemaTableName)); + assertThat(metadata.listTables(SESSION, Optional.of("test_schema"))).isEqualTo(ImmutableList.of()); + assertThat(metadata.listTables(SESSION, Optional.of("test_different_schema"))).isEqualTo(ImmutableList.of(differentSchemaTableName)); } private static void assertNoTables(MemoryMetadata metadata) { - assertEquals(metadata.listTables(SESSION, Optional.empty()), ImmutableList.of(), "No table was expected"); + assertThat(metadata.listTables(SESSION, Optional.empty())) + .describedAs("No table was expected") + .isEqualTo(ImmutableList.of()); } private static ConnectorViewDefinition testingViewDefinition(String sql) diff --git a/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryPagesStore.java b/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryPagesStore.java index f24852a6594a..93d6644b0283 100644 --- a/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryPagesStore.java +++ b/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryPagesStore.java @@ -34,12 +34,10 @@ import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.testing.TestingConnectorSession.SESSION; import static io.trino.testing.TestingPageSinkId.TESTING_PAGE_SINK_ID; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_METHOD) @Execution(SAME_THREAD) @@ -61,7 +59,7 @@ public void setUp() public void testCreateEmptyTable() { createTable(0L, 0L); - assertEquals(pagesStore.getPages(0L, 0, 1, new int[] {0}, 0, OptionalLong.empty(), OptionalDouble.empty()), ImmutableList.of()); + assertThat(pagesStore.getPages(0L, 0, 1, new int[] {0}, 0, OptionalLong.empty(), OptionalDouble.empty())).isEqualTo(ImmutableList.of()); } @Test @@ -69,14 +67,14 @@ public void testInsertPage() { createTable(0L, 0L); insertToTable(0L, 0L); - assertEquals(pagesStore.getPages(0L, 0, 1, new int[] {0}, POSITIONS_PER_PAGE, OptionalLong.empty(), OptionalDouble.empty()).size(), 1); + assertThat(pagesStore.getPages(0L, 0, 1, new int[] {0}, POSITIONS_PER_PAGE, OptionalLong.empty(), OptionalDouble.empty()).size()).isEqualTo(1); } @Test public void testInsertPageWithoutCreate() { insertToTable(0L, 0L); - assertEquals(pagesStore.getPages(0L, 0, 1, new int[] {0}, POSITIONS_PER_PAGE, OptionalLong.empty(), OptionalDouble.empty()).size(), 1); + assertThat(pagesStore.getPages(0L, 0, 1, new int[] {0}, POSITIONS_PER_PAGE, OptionalLong.empty(), OptionalDouble.empty()).size()).isEqualTo(1); } @Test @@ -92,7 +90,7 @@ public void testReadFromUnknownTable() public void testTryToReadFromEmptyTable() { createTable(0L, 0L); - assertEquals(pagesStore.getPages(0L, 0, 1, new int[] {0}, 0, OptionalLong.empty(), OptionalDouble.empty()), ImmutableList.of()); + assertThat(pagesStore.getPages(0L, 0, 1, new int[] {0}, 0, OptionalLong.empty(), OptionalDouble.empty())).isEqualTo(ImmutableList.of()); assertThatThrownBy(() -> pagesStore.getPages(0L, 0, 1, new int[] {0}, 42, OptionalLong.empty(), OptionalDouble.empty())) .isInstanceOf(TrinoException.class) .hasMessageMatching("Expected to find.*"); @@ -105,21 +103,21 @@ public void testCleanUp() createTable(1L, 0L, 1L); createTable(2L, 0L, 1L, 2L); - assertTrue(pagesStore.contains(0L)); - assertTrue(pagesStore.contains(1L)); - assertTrue(pagesStore.contains(2L)); + assertThat(pagesStore.contains(0L)).isTrue(); + assertThat(pagesStore.contains(1L)).isTrue(); + assertThat(pagesStore.contains(2L)).isTrue(); insertToTable(1L, 0L, 1L); - assertTrue(pagesStore.contains(0L)); - assertTrue(pagesStore.contains(1L)); - assertTrue(pagesStore.contains(2L)); + assertThat(pagesStore.contains(0L)).isTrue(); + assertThat(pagesStore.contains(1L)).isTrue(); + assertThat(pagesStore.contains(2L)).isTrue(); insertToTable(2L, 0L, 2L); - assertTrue(pagesStore.contains(0L)); - assertFalse(pagesStore.contains(1L)); - assertTrue(pagesStore.contains(2L)); + assertThat(pagesStore.contains(0L)).isTrue(); + assertThat(pagesStore.contains(1L)).isFalse(); + assertThat(pagesStore.contains(2L)).isTrue(); } @Test diff --git a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestEncryptionUtil.java b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestEncryptionUtil.java index d711e9df5b01..8ea0dec6b5a6 100644 --- a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestEncryptionUtil.java +++ b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestEncryptionUtil.java @@ -18,8 +18,8 @@ import static io.trino.plugin.password.file.EncryptionUtil.getHashingAlgorithm; import static io.trino.plugin.password.file.HashingAlgorithm.BCRYPT; import static io.trino.plugin.password.file.HashingAlgorithm.PBKDF2; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestEncryptionUtil { @@ -28,14 +28,14 @@ public class TestEncryptionUtil public void testHashingAlgorithmBCrypt() { String password = "$2y$10$BqTb8hScP5DfcpmHo5PeyugxHz5Ky/qf3wrpD7SNm8sWuA3VlGqsa"; - assertEquals(getHashingAlgorithm(password), BCRYPT); + assertThat(getHashingAlgorithm(password)).isEqualTo(BCRYPT); } @Test public void testHashingAlgorithmPBKDF2() { String password = "1000:5b4240333032306164:f38d165fce8ce42f59d366139ef5d9e1ca1247f0e06e503ee1a611dd9ec40876bb5edb8409f5abe5504aab6628e70cfb3d3a18e99d70357d295002c3d0a308a0"; - assertEquals(getHashingAlgorithm(password), PBKDF2); + assertThat(getHashingAlgorithm(password)).isEqualTo(PBKDF2); } @Test diff --git a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestFileGroupProvider.java b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestFileGroupProvider.java index 893341ba18c4..d73f33ab7497 100644 --- a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestFileGroupProvider.java +++ b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestFileGroupProvider.java @@ -21,7 +21,7 @@ import java.io.File; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestFileGroupProvider { @@ -46,9 +46,9 @@ public void testGroupProviderFactory() private static void assertGroupProvider(GroupProvider groupProvider) { - assertEquals(groupProvider.getGroups("user_1"), ImmutableSet.of("group_a", "group_b")); - assertEquals(groupProvider.getGroups("user_2"), ImmutableSet.of("group_a")); - assertEquals(groupProvider.getGroups("user_3"), ImmutableSet.of("group_b")); - assertEquals(groupProvider.getGroups("unknown"), ImmutableSet.of()); + assertThat(groupProvider.getGroups("user_1")).isEqualTo(ImmutableSet.of("group_a", "group_b")); + assertThat(groupProvider.getGroups("user_2")).isEqualTo(ImmutableSet.of("group_a")); + assertThat(groupProvider.getGroups("user_3")).isEqualTo(ImmutableSet.of("group_b")); + assertThat(groupProvider.getGroups("unknown")).isEqualTo(ImmutableSet.of()); } } diff --git a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestPasswordStore.java b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestPasswordStore.java index 77dcd8009ecc..3c66504299d5 100644 --- a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestPasswordStore.java +++ b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/file/TestPasswordStore.java @@ -16,9 +16,8 @@ import com.google.common.collect.ImmutableList; import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestPasswordStore { @@ -30,16 +29,16 @@ public void testAuthenticate() { PasswordStore store = createStore("userbcrypt:" + BCRYPT_PASSWORD, "userpbkdf2:" + PBKDF2_PASSWORD); - assertTrue(store.authenticate("userbcrypt", "user123")); - assertFalse(store.authenticate("userbcrypt", "user999")); - assertFalse(store.authenticate("userbcrypt", "password")); + assertThat(store.authenticate("userbcrypt", "user123")).isTrue(); + assertThat(store.authenticate("userbcrypt", "user999")).isFalse(); + assertThat(store.authenticate("userbcrypt", "password")).isFalse(); - assertTrue(store.authenticate("userpbkdf2", "password")); - assertFalse(store.authenticate("userpbkdf2", "password999")); - assertFalse(store.authenticate("userpbkdf2", "user123")); + assertThat(store.authenticate("userpbkdf2", "password")).isTrue(); + assertThat(store.authenticate("userpbkdf2", "password999")).isFalse(); + assertThat(store.authenticate("userpbkdf2", "user123")).isFalse(); - assertFalse(store.authenticate("baduser", "user123")); - assertFalse(store.authenticate("baduser", "password")); + assertThat(store.authenticate("baduser", "user123")).isFalse(); + assertThat(store.authenticate("baduser", "password")).isFalse(); } @Test diff --git a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/ldap/TestLdapAuthenticator.java b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/ldap/TestLdapAuthenticator.java index 4ea836bf3e37..5049505b8d1d 100644 --- a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/ldap/TestLdapAuthenticator.java +++ b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/ldap/TestLdapAuthenticator.java @@ -30,7 +30,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -79,7 +78,7 @@ public void testSingleBindPattern() assertThatThrownBy(() -> ldapAuthenticator.createAuthenticatedPrincipal("unknown", "alice-pass")) .isInstanceOf(RuntimeException.class) .hasMessageMatching("Access Denied: Invalid credentials"); - assertEquals(ldapAuthenticator.createAuthenticatedPrincipal("alice", "alice-pass"), new BasicPrincipal("alice")); + assertThat(ldapAuthenticator.createAuthenticatedPrincipal("alice", "alice-pass")).isEqualTo(new BasicPrincipal("alice")); } } @@ -97,15 +96,15 @@ public void testMultipleBindPattern() new LdapAuthenticatorConfig() .setUserBindSearchPatterns(format("uid=${USER},%s:uid=${USER},%s", organization.getDistinguishedName(), alternativeOrganization.getDistinguishedName()))); - assertEquals(ldapAuthenticator.createAuthenticatedPrincipal("alice", "alice-pass"), new BasicPrincipal("alice")); + assertThat(ldapAuthenticator.createAuthenticatedPrincipal("alice", "alice-pass")).isEqualTo(new BasicPrincipal("alice")); ldapAuthenticator.invalidateCache(); - assertEquals(ldapAuthenticator.createAuthenticatedPrincipal("bob", "bob-pass"), new BasicPrincipal("bob")); + assertThat(ldapAuthenticator.createAuthenticatedPrincipal("bob", "bob-pass")).isEqualTo(new BasicPrincipal("bob")); ldapAuthenticator.invalidateCache(); - assertEquals(ldapAuthenticator.createAuthenticatedPrincipal("alice", "alt-alice-pass"), new BasicPrincipal("alice")); + assertThat(ldapAuthenticator.createAuthenticatedPrincipal("alice", "alt-alice-pass")).isEqualTo(new BasicPrincipal("alice")); ldapAuthenticator.invalidateCache(); - assertEquals(ldapAuthenticator.createAuthenticatedPrincipal("alice", "alice-pass"), new BasicPrincipal("alice")); + assertThat(ldapAuthenticator.createAuthenticatedPrincipal("alice", "alice-pass")).isEqualTo(new BasicPrincipal("alice")); ldapAuthenticator.invalidateCache(); } } @@ -138,7 +137,7 @@ public void testGroupMembership() .hasMessageMatching("Access Denied: User \\[bob] not a member of an authorized group"); openLdapServer.addUserToGroup(alice, group); - assertEquals(ldapAuthenticator.createAuthenticatedPrincipal("alice", "alice-pass"), new BasicPrincipal("alice")); + assertThat(ldapAuthenticator.createAuthenticatedPrincipal("alice", "alice-pass")).isEqualTo(new BasicPrincipal("alice")); } } @@ -197,7 +196,7 @@ public void testDistinguishedNameLookup() ldapAuthenticator.invalidateCache(); openLdapServer.addUserToGroup(alice, group); - assertEquals(ldapAuthenticator.createAuthenticatedPrincipal("alice", "alice-pass"), new BasicPrincipal("alice")); + assertThat(ldapAuthenticator.createAuthenticatedPrincipal("alice", "alice-pass")).isEqualTo(new BasicPrincipal("alice")); ldapAuthenticator.invalidateCache(); assertThatThrownBy(() -> ldapAuthenticator.createAuthenticatedPrincipal("alice", "invalid")) diff --git a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/ldap/TestLdapAuthenticatorWithTimeouts.java b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/ldap/TestLdapAuthenticatorWithTimeouts.java index 1d78d76a88d8..dff776a1d272 100644 --- a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/ldap/TestLdapAuthenticatorWithTimeouts.java +++ b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/ldap/TestLdapAuthenticatorWithTimeouts.java @@ -32,9 +32,9 @@ import static io.trino.plugin.password.ldap.TestingOpenLdapServer.LDAP_PORT; import static java.lang.String.format; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) public class TestLdapAuthenticatorWithTimeouts @@ -93,10 +93,8 @@ public void testConnectTimeout() .hasMessageMatching(".*Authentication error.*"); LdapClientConfig withIncreasedTimeout = ldapConfig.setLdapConnectionTimeout(new Duration(30, SECONDS)); - assertEquals( - new LdapAuthenticator(new LdapAuthenticatorClient(new JdkLdapClient(withIncreasedTimeout)), ldapAuthenticatorConfig) - .createAuthenticatedPrincipal("alice", "alice-pass"), - new BasicPrincipal("alice")); + assertThat(new LdapAuthenticator(new LdapAuthenticatorClient(new JdkLdapClient(withIncreasedTimeout)), ldapAuthenticatorConfig) + .createAuthenticatedPrincipal("alice", "alice-pass")).isEqualTo(new BasicPrincipal("alice")); } } @@ -127,13 +125,11 @@ public void testReadTimeout() .hasMessageMatching(".*Authentication error.*"); LdapClientConfig withIncreasedTimeout = ldapConfig.setLdapReadTimeout(new Duration(30, SECONDS)); - assertEquals( - new LdapAuthenticator( - new LdapAuthenticatorClient( - new JdkLdapClient(withIncreasedTimeout)), - ldapAuthenticatorConfig) - .createAuthenticatedPrincipal("alice", "alice-pass"), - new BasicPrincipal("alice")); + assertThat(new LdapAuthenticator( + new LdapAuthenticatorClient( + new JdkLdapClient(withIncreasedTimeout)), + ldapAuthenticatorConfig) + .createAuthenticatedPrincipal("alice", "alice-pass")).isEqualTo(new BasicPrincipal("alice")); } } } diff --git a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/salesforce/TestSalesforceBasicAuthenticator.java b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/salesforce/TestSalesforceBasicAuthenticator.java index 932aea459187..254592148cf2 100644 --- a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/salesforce/TestSalesforceBasicAuthenticator.java +++ b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/salesforce/TestSalesforceBasicAuthenticator.java @@ -29,10 +29,10 @@ import static io.airlift.http.client.HttpStatus.OK; import static io.airlift.http.client.testing.TestingResponse.mockResponse; import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Fail.fail; import static org.junit.jupiter.api.Assumptions.abort; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.fail; public class TestSalesforceBasicAuthenticator { @@ -65,14 +65,20 @@ public void createAuthenticatedPrincipalSuccess() SalesforceBasicAuthenticator authenticator = new SalesforceBasicAuthenticator(config, testHttpClient); Principal principal = authenticator.createAuthenticatedPrincipal(username, password); - assertEquals(principal.getName(), username, "Test principal name."); + assertThat(principal.getName()) + .describedAs("Test principal name.") + .isEqualTo(username); principal = authenticator.createAuthenticatedPrincipal(username, password); - assertEquals(principal.getName(), username, "Test principal name from cache."); + assertThat(principal.getName()) + .describedAs("Test principal name from cache.") + .isEqualTo(username); Thread.sleep(2000L); principal = authenticator.createAuthenticatedPrincipal(username, password); - assertEquals(principal.getName(), username, "Test principal name from expired cache."); + assertThat(principal.getName()) + .describedAs("Test principal name from expired cache.") + .isEqualTo(username); } @Test @@ -129,7 +135,9 @@ public void createAuthenticatedPrincipalAllOrgs() SalesforceBasicAuthenticator authenticator = new SalesforceBasicAuthenticator(config, testHttpClient); Principal principal = authenticator.createAuthenticatedPrincipal(username, password); - assertEquals(principal.getName(), username, "Test allowing all orgs."); + assertThat(principal.getName()) + .describedAs("Test allowing all orgs.") + .isEqualTo(username); } @Test @@ -148,7 +156,9 @@ public void createAuthenticatedPrincipalFewOrgs() SalesforceBasicAuthenticator authenticator = new SalesforceBasicAuthenticator(config, testHttpClient); Principal principal = authenticator.createAuthenticatedPrincipal(username, password); - assertEquals(principal.getName(), username, "Test allowing a few orgs."); + assertThat(principal.getName()) + .describedAs("Test allowing a few orgs.") + .isEqualTo(username); } /* @@ -188,7 +198,9 @@ public void createAuthenticatedPrincipalRealSuccess() SalesforceBasicAuthenticator authenticator = new SalesforceBasicAuthenticator(config, testHttpClient); Principal principal = authenticator.createAuthenticatedPrincipal(username, password); - assertEquals(principal.getName(), username, "Test principal name for real, yo!"); + assertThat(principal.getName()) + .describedAs("Test principal name for real, yo!") + .isEqualTo(username); } // Test a real login for a different org. @@ -239,7 +251,9 @@ public void createAuthenticatedPrincipalRealAllOrgs() SalesforceBasicAuthenticator authenticator = new SalesforceBasicAuthenticator(config, testHttpClient); Principal principal = authenticator.createAuthenticatedPrincipal(username, password); - assertEquals(principal.getName(), username, "Test no org check for real, yo!"); + assertThat(principal.getName()) + .describedAs("Test no org check for real, yo!") + .isEqualTo(username); } // Test a login with a bad password. diff --git a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/salesforce/TestSalesforceConfig.java b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/salesforce/TestSalesforceConfig.java index 98b708319e32..513315f712fb 100644 --- a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/salesforce/TestSalesforceConfig.java +++ b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/salesforce/TestSalesforceConfig.java @@ -23,7 +23,7 @@ import static io.airlift.configuration.testing.ConfigAssertions.assertRecordedDefaults; import static io.airlift.configuration.testing.ConfigAssertions.recordDefaults; import static java.util.concurrent.TimeUnit.MINUTES; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestSalesforceConfig { @@ -67,6 +67,6 @@ public void testGetOrgSet() int expected = (int) orgs.chars().filter(sep -> sep == ',').count() + 1; int actual = (new SalesforceConfig() .setAllowedOrganizations(orgs)).getOrgSet().size(); - assertEquals(expected, actual); + assertThat(expected).isEqualTo(actual); } } diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/BasePinotConnectorSmokeTest.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/BasePinotConnectorSmokeTest.java index d06887539ce2..515de1a97be1 100644 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/BasePinotConnectorSmokeTest.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/BasePinotConnectorSmokeTest.java @@ -97,7 +97,6 @@ import static org.apache.pinot.spi.utils.JsonUtils.inputStreamToObject; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.testng.Assert.assertEquals; public abstract class BasePinotConnectorSmokeTest extends BaseConnectorSmokeTest @@ -1016,8 +1015,8 @@ public void testJoin() public void testRealType() { MaterializedResult result = computeActual("SELECT price FROM " + JSON_TABLE + " WHERE vendor = 'vendor1'"); - assertEquals(getOnlyElement(result.getTypes()), REAL); - assertEquals(result.getOnlyValue(), 3.5F); + assertThat(getOnlyElement(result.getTypes())).isEqualTo(REAL); + assertThat(result.getOnlyValue()).isEqualTo(3.5F); } @Test @@ -1093,8 +1092,8 @@ public void testBrokerQueriesWithAvg() " ('Los Angeles', 7.75, 6.25, 10000.0)"); MaterializedResult result = computeActual("SELECT \"avg(lucky_number)\"" + " FROM \"SELECT AVG(lucky_number) FROM my_table WHERE vendor in ('vendor2', 'vendor4')\""); - assertEquals(getOnlyElement(result.getTypes()), DOUBLE); - assertEquals(result.getOnlyValue(), 7.0); + assertThat(getOnlyElement(result.getTypes())).isEqualTo(DOUBLE); + assertThat(result.getOnlyValue()).isEqualTo(7.0); } @Test @@ -1192,11 +1191,9 @@ public void testNonLowerTable() assertQuery( "SELECT column_name FROM information_schema.columns WHERE table_name = 'mixedcase'", "VALUES 'stringcol', 'updatedatseconds', 'longcol'"); - assertEquals( - computeActual("SHOW COLUMNS FROM default.mixedcase").getMaterializedRows().stream() - .map(row -> row.getField(0)) - .collect(toImmutableSet()), - ImmutableSet.of("stringcol", "updatedatseconds", "longcol")); + assertThat(computeActual("SHOW COLUMNS FROM default.mixedcase").getMaterializedRows().stream() + .map(row -> row.getField(0)) + .collect(toImmutableSet())).isEqualTo(ImmutableSet.of("stringcol", "updatedatseconds", "longcol")); } @Test @@ -1331,7 +1328,7 @@ public void testCount() assertQuery("SELECT \"count(*)\" FROM \"SELECT COUNT(*) FROM " + ALL_TYPES_TABLE + "\"", "VALUES " + MAX_ROWS_PER_SPLIT_FOR_SEGMENT_QUERIES); // If no limit is supplied to a broker query, 10 arbitrary rows will be returned. Verify this behavior: MaterializedResult result = computeActual("SELECT * FROM \"SELECT bool_col FROM " + ALL_TYPES_TABLE + "\""); - assertEquals(result.getRowCount(), DEFAULT_PINOT_LIMIT_FOR_BROKER_QUERIES); + assertThat(result.getRowCount()).isEqualTo(DEFAULT_PINOT_LIMIT_FOR_BROKER_QUERIES); } @Test diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestBrokerQueries.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestBrokerQueries.java index 4f43d5456509..320dc9160399 100644 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestBrokerQueries.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestBrokerQueries.java @@ -36,9 +36,8 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static org.apache.pinot.common.utils.DataSchema.ColumnDataType.LONG; import static org.apache.pinot.common.utils.DataSchema.ColumnDataType.STRING; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; public class TestBrokerQueries extends TestPinotQueryBase @@ -77,11 +76,13 @@ public void testBrokerColumnMapping() .add(new PinotColumnHandle("col_2", BIGINT)) .build(); ResultsIterator resultIterator = fromResultTable(RESPONSE, columnHandles, 0); - assertTrue(resultIterator.hasNext(), "resultIterator is empty"); + assertThat(resultIterator.hasNext()) + .describedAs("resultIterator is empty") + .isTrue(); BrokerResultRow row = resultIterator.next(); - assertEquals(row.getField(0), "col_3_data"); - assertEquals(row.getField(1), "col_1_data"); - assertEquals(row.getField(2), 2L); + assertThat(row.getField(0)).isEqualTo("col_3_data"); + assertThat(row.getField(1)).isEqualTo("col_1_data"); + assertThat(row.getField(2)).isEqualTo(2L); } @Test @@ -92,10 +93,12 @@ public void testBrokerColumnMappingWithSubset() .add(new PinotColumnHandle("col_1", VARCHAR)) .build(); ResultsIterator resultIterator = fromResultTable(RESPONSE, columnHandles, 0); - assertTrue(resultIterator.hasNext(), "resultIterator is empty"); + assertThat(resultIterator.hasNext()) + .describedAs("resultIterator is empty") + .isTrue(); BrokerResultRow row = resultIterator.next(); - assertEquals(row.getField(0), "col_3_data"); - assertEquals(row.getField(1), "col_1_data"); + assertThat(row.getField(0)).isEqualTo("col_3_data"); + assertThat(row.getField(1)).isEqualTo("col_1_data"); } @Test @@ -113,16 +116,16 @@ public void testBrokerQuery() LIMIT_FOR_BROKER_QUERIES); Page page = pageSource.getNextPage(); - assertEquals(page.getChannelCount(), columnHandles.size()); - assertEquals(page.getPositionCount(), RESPONSE.getResultTable().getRows().size()); + assertThat(page.getChannelCount()).isEqualTo(columnHandles.size()); + assertThat(page.getPositionCount()).isEqualTo(RESPONSE.getResultTable().getRows().size()); Block block = page.getBlock(0); String value = block.getSlice(0, 0, block.getSliceLength(0)).toStringUtf8(); - assertEquals(value, getOnlyElement(RESPONSE.getResultTable().getRows())[0]); + assertThat(value).isEqualTo(getOnlyElement(RESPONSE.getResultTable().getRows())[0]); block = page.getBlock(1); - assertEquals(block.getLong(0, 0), (long) getOnlyElement(RESPONSE.getResultTable().getRows())[1]); + assertThat(block.getLong(0, 0)).isEqualTo((long) getOnlyElement(RESPONSE.getResultTable().getRows())[1]); block = page.getBlock(2); value = block.getSlice(0, 0, block.getSliceLength(0)).toStringUtf8(); - assertEquals(value, getOnlyElement(RESPONSE.getResultTable().getRows())[2]); + assertThat(value).isEqualTo(getOnlyElement(RESPONSE.getResultTable().getRows())[2]); } @Test @@ -134,8 +137,8 @@ public void testCountStarBrokerQuery() testingPinotClient, LIMIT_FOR_BROKER_QUERIES); Page page = pageSource.getNextPage(); - assertEquals(page.getPositionCount(), RESPONSE.getResultTable().getRows().size()); - assertEquals(page.getChannelCount(), 0); + assertThat(page.getPositionCount()).isEqualTo(RESPONSE.getResultTable().getRows().size()); + assertThat(page.getChannelCount()).isEqualTo(0); } @Test diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestDynamicTable.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestDynamicTable.java index 00fdba82ba5f..b8ccf3419d76 100755 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestDynamicTable.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestDynamicTable.java @@ -38,7 +38,7 @@ import static java.util.Locale.ENGLISH; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestDynamicTable extends TestPinotQueryBase @@ -61,13 +61,12 @@ public void testSelectNoFilter() .collect(joining(", ")), limit); DynamicTable dynamicTable = buildFromPql(pinotMetadata, new SchemaTableName("default", query), mockClusterInfoFetcher, TESTING_TYPE_CONVERTER); - assertEquals(dynamicTable.getProjections().stream() + assertThat(dynamicTable.getProjections().stream() .map(PinotColumnHandle::getColumnName) - .collect(toImmutableList()), - columnNames); + .collect(toImmutableList())).isEqualTo(columnNames); orderByExpressions.add(new OrderByExpression(quoteIdentifier(orderByColumns.get(4)), false)); - assertEquals(dynamicTable.getOrderBy(), orderByExpressions); - assertEquals(dynamicTable.getLimit().getAsLong(), limit); + assertThat(dynamicTable.getOrderBy()).isEqualTo(orderByExpressions); + assertThat(dynamicTable.getLimit().getAsLong()).isEqualTo(limit); } @Test @@ -77,14 +76,13 @@ public void testGroupBy() long limit = 25; String query = "SELECT Origin, AirlineID, max(CarrierDelay), avg(CarrierDelay) FROM %s GROUP BY Origin, AirlineID LIMIT %s".formatted(tableName, limit); DynamicTable dynamicTable = buildFromPql(pinotMetadata, new SchemaTableName("default", query), mockClusterInfoFetcher, TESTING_TYPE_CONVERTER); - assertEquals(dynamicTable.getGroupingColumns().stream() - .map(PinotColumnHandle::getColumnName) - .collect(toImmutableList()), - ImmutableList.builder() - .add("Origin") - .add("AirlineID") - .build()); - assertEquals(dynamicTable.getLimit().getAsLong(), limit); + assertThat(dynamicTable.getGroupingColumns().stream() + .map(PinotColumnHandle::getColumnName) + .collect(toImmutableList())).isEqualTo(ImmutableList.builder() + .add("Origin") + .add("AirlineID") + .build()); + assertThat(dynamicTable.getLimit().getAsLong()).isEqualTo(limit); } @Test @@ -108,7 +106,7 @@ WHERE OR(AND("CancellationCode" IN ('strike', 'weather', 'pilot_bac'), ("Origin" AND(("DepDelayMinutes") < '10', ("Distance") >= '3', ("ArrDelay") > '4', ("SecurityDelay") < '5', ("LateAircraftDelay") <= '7'))\ LIMIT 60""".formatted(tableName); DynamicTable dynamicTable = buildFromPql(pinotMetadata, new SchemaTableName("default", query), mockClusterInfoFetcher, TESTING_TYPE_CONVERTER); - assertEquals(extractPql(dynamicTable, TupleDomain.all()), expected); + assertThat(extractPql(dynamicTable, TupleDomain.all())).isEqualTo(expected); } @Test @@ -132,7 +130,7 @@ WHERE AND(("string_col") = 'string', ("long_col") = '12345678901',\ ("int_col") = '123456789', ("double_col") = '3.56', ("float_col") = '3.56', ("bytes_col") = 'abcd')\ LIMIT 60"""; DynamicTable dynamicTable = buildFromPql(pinotMetadata, new SchemaTableName("default", query), mockClusterInfoFetcher, TESTING_TYPE_CONVERTER); - assertEquals(extractPql(dynamicTable, TupleDomain.all()), expected); + assertThat(extractPql(dynamicTable, TupleDomain.all())).isEqualTo(expected); } @Test @@ -143,7 +141,7 @@ public void testDoubleWithScientificNotation() String expected = """ SELECT "string_col" FROM primitive_types_table WHERE ("double_col") = '350000.0' LIMIT 10"""; DynamicTable dynamicTable = buildFromPql(pinotMetadata, new SchemaTableName("default", query), mockClusterInfoFetcher, TESTING_TYPE_CONVERTER); - assertEquals(extractPql(dynamicTable, TupleDomain.all()), expected); + assertThat(extractPql(dynamicTable, TupleDomain.all())).isEqualTo(expected); } @Test @@ -159,7 +157,7 @@ public void testFilterWithCast() WHERE AND(("string_col") = '123', ("long_col") = '123')\ LIMIT 60"""; DynamicTable dynamicTable = buildFromPql(pinotMetadata, new SchemaTableName("default", query), mockClusterInfoFetcher, TESTING_TYPE_CONVERTER); - assertEquals(extractPql(dynamicTable, TupleDomain.all()), expected); + assertThat(extractPql(dynamicTable, TupleDomain.all())).isEqualTo(expected); } @Test @@ -180,7 +178,7 @@ WHERE AND((CASE WHEN equals("CancellationCode", 'strike')\ THEN 'pizza' WHEN equals("OriginCityName", 'la') THEN 'burrito' WHEN equals("OriginCityName", 'boston')\ THEN 'clam chowder' ELSE 'burger' END) != 'salad') LIMIT 10""".formatted(tableName); DynamicTable dynamicTable = buildFromPql(pinotMetadata, new SchemaTableName("default", query), mockClusterInfoFetcher, TESTING_TYPE_CONVERTER); - assertEquals(extractPql(dynamicTable, TupleDomain.all()), expected); + assertThat(extractPql(dynamicTable, TupleDomain.all())).isEqualTo(expected); } @Test @@ -198,7 +196,7 @@ public void testFilterWithPushdownConstraint() FROM realtimeOnly\ WHERE ("OriginCityName" = 'Catfish Paradise')\ LIMIT 60"""; - assertEquals(extractPql(dynamicTable, tupleDomain), expectedPql); + assertThat(extractPql(dynamicTable, tupleDomain)).isEqualTo(expectedPql); } @Test @@ -214,7 +212,7 @@ public void testFilterWithUdf() FROM realtimeOnly\ WHERE AND(("DivLongestGTimes") = '9.0', (exp("CarrierDelay")) > '5')\ LIMIT 60"""; - assertEquals(extractPql(dynamicTable, TupleDomain.all()), expectedPql); + assertThat(extractPql(dynamicTable, TupleDomain.all())).isEqualTo(expectedPql); } @Test @@ -224,7 +222,7 @@ public void testSelectStarDynamicTable() String query = "SELECT * FROM %s LIMIT 70".formatted(tableName.toLowerCase(ENGLISH)); DynamicTable dynamicTable = buildFromPql(pinotMetadata, new SchemaTableName("default", query), mockClusterInfoFetcher, TESTING_TYPE_CONVERTER); String expectedPql = "SELECT %s FROM %s LIMIT 70".formatted(getColumnNames(tableName).stream().map(TestDynamicTable::quoteIdentifier).collect(joining(", ")), tableName); - assertEquals(extractPql(dynamicTable, TupleDomain.all()), expectedPql); + assertThat(extractPql(dynamicTable, TupleDomain.all())).isEqualTo(expectedPql); } @Test @@ -235,8 +233,8 @@ public void testOfflineDynamicTable() String query = "SELECT * FROM %s LIMIT 70".formatted(tableNameWithSuffix); DynamicTable dynamicTable = buildFromPql(pinotMetadata, new SchemaTableName("default", query), mockClusterInfoFetcher, TESTING_TYPE_CONVERTER); String expectedPql = "SELECT %s FROM %s LIMIT 70".formatted(getColumnNames(tableName).stream().map(TestDynamicTable::quoteIdentifier).collect(joining(", ")), tableNameWithSuffix); - assertEquals(extractPql(dynamicTable, TupleDomain.all()), expectedPql); - assertEquals(dynamicTable.getTableName(), tableName); + assertThat(extractPql(dynamicTable, TupleDomain.all())).isEqualTo(expectedPql); + assertThat(dynamicTable.getTableName()).isEqualTo(tableName); } @Test @@ -247,8 +245,8 @@ public void testRealtimeOnlyDynamicTable() String query = "SELECT * FROM %s LIMIT 70".formatted(tableNameWithSuffix); DynamicTable dynamicTable = buildFromPql(pinotMetadata, new SchemaTableName("default", query), mockClusterInfoFetcher, TESTING_TYPE_CONVERTER); String expectedPql = "SELECT %s FROM %s LIMIT 70".formatted(getColumnNames(tableName).stream().map(TestDynamicTable::quoteIdentifier).collect(joining(", ")), tableNameWithSuffix); - assertEquals(extractPql(dynamicTable, TupleDomain.all()), expectedPql); - assertEquals(dynamicTable.getTableName(), tableName); + assertThat(extractPql(dynamicTable, TupleDomain.all())).isEqualTo(expectedPql); + assertThat(dynamicTable.getTableName()).isEqualTo(tableName); } @Test @@ -259,8 +257,8 @@ public void testLimitAndOffset() String query = "SELECT * FROM %s LIMIT 70, 40".formatted(tableNameWithSuffix); DynamicTable dynamicTable = buildFromPql(pinotMetadata, new SchemaTableName("default", query), mockClusterInfoFetcher, TESTING_TYPE_CONVERTER); String expectedPql = "SELECT %s FROM %s LIMIT 70, 40".formatted(getColumnNames(tableName).stream().map(TestDynamicTable::quoteIdentifier).collect(joining(", ")), tableNameWithSuffix); - assertEquals(extractPql(dynamicTable, TupleDomain.all()), expectedPql); - assertEquals(dynamicTable.getTableName(), tableName); + assertThat(extractPql(dynamicTable, TupleDomain.all())).isEqualTo(expectedPql); + assertThat(dynamicTable.getTableName()).isEqualTo(tableName); } private static String quoteIdentifier(String identifier) @@ -277,8 +275,8 @@ public void testRegexpLike() DynamicTable dynamicTable = buildFromPql(pinotMetadata, new SchemaTableName("default", query), mockClusterInfoFetcher, TESTING_TYPE_CONVERTER); String expectedPql = """ SELECT "OriginCityName" FROM %s WHERE regexp_like("OriginCityName", '.*york.*') LIMIT 70""".formatted(tableNameWithSuffix); - assertEquals(extractPql(dynamicTable, TupleDomain.all()), expectedPql); - assertEquals(dynamicTable.getTableName(), tableName); + assertThat(extractPql(dynamicTable, TupleDomain.all())).isEqualTo(expectedPql); + assertThat(dynamicTable.getTableName()).isEqualTo(tableName); } @Test @@ -290,8 +288,8 @@ public void testTextMatch() DynamicTable dynamicTable = buildFromPql(pinotMetadata, new SchemaTableName("default", query), mockClusterInfoFetcher, TESTING_TYPE_CONVERTER); String expectedPql = """ SELECT "OriginCityName" FROM %s WHERE text_match("OriginCityName", 'new and york') LIMIT 70""".formatted(tableNameWithSuffix); - assertEquals(extractPql(dynamicTable, TupleDomain.all()), expectedPql); - assertEquals(dynamicTable.getTableName(), tableName); + assertThat(extractPql(dynamicTable, TupleDomain.all())).isEqualTo(expectedPql); + assertThat(dynamicTable.getTableName()).isEqualTo(tableName); } @Test @@ -304,8 +302,8 @@ public void testJsonMatch() DynamicTable dynamicTable = buildFromPql(pinotMetadata, new SchemaTableName("default", query), mockClusterInfoFetcher, TESTING_TYPE_CONVERTER); String expectedPql = """ SELECT "OriginCityName" FROM %s WHERE json_match("OriginCityName", '"$.name"=''new york''') LIMIT 70""".formatted(tableNameWithSuffix); - assertEquals(extractPql(dynamicTable, TupleDomain.all()), expectedPql); - assertEquals(dynamicTable.getTableName(), tableName); + assertThat(extractPql(dynamicTable, TupleDomain.all())).isEqualTo(expectedPql); + assertThat(dynamicTable.getTableName()).isEqualTo(tableName); } @Test @@ -328,8 +326,8 @@ SELECT datetimeconvert("DaysSinceEpoch", '1:SECONDS:EPOCH', '1:MILLISECONDS:EPOC timeconvert("DaysSinceEpoch", 'SECONDS', 'MINUTES') AS "foo"\ FROM %s\ LIMIT 70""".formatted(tableNameWithSuffix); - assertEquals(extractPql(dynamicTable, TupleDomain.all()), expectedPql); - assertEquals(dynamicTable.getTableName(), tableName); + assertThat(extractPql(dynamicTable, TupleDomain.all())).isEqualTo(expectedPql); + assertThat(dynamicTable.getTableName()).isEqualTo(tableName); } @Test @@ -361,8 +359,8 @@ GROUP BY datetimeconvert("DaysSinceEpoch", '1:SECONDS:EPOCH', '1:MILLISECONDS:EP not_equals(CASE WHEN equals("OriginCityName", 'nyc') THEN 'pizza' WHEN equals("OriginCityName", 'la') THEN 'burrito' WHEN equals("OriginCityName", 'boston') THEN 'clam chowder' ELSE 'burger' END, 'salad'),\ timeconvert("DaysSinceEpoch", 'SECONDS', 'MINUTES')\ LIMIT 70""".formatted(tableNameWithSuffix); - assertEquals(extractPql(dynamicTable, TupleDomain.all()), expectedPql); - assertEquals(dynamicTable.getTableName(), tableName); + assertThat(extractPql(dynamicTable, TupleDomain.all())).isEqualTo(expectedPql); + assertThat(dynamicTable.getTableName()).isEqualTo(tableName); } @Test @@ -377,8 +375,8 @@ SELECT plus("ArrDelay", '34') - "DaysSinceEpoch", "FlightNum"\ FROM %s\ ORDER BY "ArrDelay", "DaysSinceEpoch" DESC\ LIMIT 10""".formatted(tableNameWithSuffix); - assertEquals(extractPql(dynamicTable, TupleDomain.all()), expectedPql); - assertEquals(dynamicTable.getTableName(), tableName); + assertThat(extractPql(dynamicTable, TupleDomain.all())).isEqualTo(expectedPql); + assertThat(dynamicTable.getTableName()).isEqualTo(tableName); } @Test @@ -393,8 +391,8 @@ SELECT count(*)\ FROM %s\ ORDER BY count(*)\ LIMIT 10""".formatted(tableNameWithSuffix); - assertEquals(extractPql(dynamicTable, TupleDomain.all()), expectedPql); - assertEquals(dynamicTable.getTableName(), tableName); + assertThat(extractPql(dynamicTable, TupleDomain.all())).isEqualTo(expectedPql); + assertThat(dynamicTable.getTableName()).isEqualTo(tableName); } @Test @@ -409,8 +407,8 @@ SELECT plus("ArrDelay", '34') - "DaysSinceEpoch", "FlightNum"\ FROM %s\ ORDER BY plus("ArrDelay", '34') - "DaysSinceEpoch" DESC\ LIMIT 10""".formatted(tableNameWithSuffix); - assertEquals(extractPql(dynamicTable, TupleDomain.all()), expectedPql); - assertEquals(dynamicTable.getTableName(), tableName); + assertThat(extractPql(dynamicTable, TupleDomain.all())).isEqualTo(expectedPql); + assertThat(dynamicTable.getTableName()).isEqualTo(tableName); } @Test @@ -427,8 +425,8 @@ public void testQuotesInAlias() SELECT "non_quoted" AS "non""quoted"\ FROM %s\ LIMIT 50""".formatted(tableNameWithSuffix); - assertEquals(extractPql(dynamicTable, TupleDomain.all()), expectedPql); - assertEquals(dynamicTable.getTableName(), tableName); + assertThat(extractPql(dynamicTable, TupleDomain.all())).isEqualTo(expectedPql); + assertThat(dynamicTable.getTableName()).isEqualTo(tableName); } @Test @@ -445,8 +443,8 @@ public void testQuotesInColumnName() SELECT "qu""ot""ed"\ FROM %s\ LIMIT 50""".formatted(tableNameWithSuffix); - assertEquals(extractPql(dynamicTable, TupleDomain.all()), expectedPql); - assertEquals(dynamicTable.getTableName(), tableName); + assertThat(extractPql(dynamicTable, TupleDomain.all())).isEqualTo(expectedPql); + assertThat(dynamicTable.getTableName()).isEqualTo(tableName); } @Test @@ -468,7 +466,7 @@ public void testQueryOptions() SELECT "FlightNum" \ FROM %s \ LIMIT 50""".formatted(tableNameWithSuffix); - assertEquals(extractPql(dynamicTable, TupleDomain.all()), expectedPql); - assertEquals(dynamicTable.getTableName(), tableName); + assertThat(extractPql(dynamicTable, TupleDomain.all())).isEqualTo(expectedPql); + assertThat(dynamicTable.getTableName()).isEqualTo(tableName); } } diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestInstance.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestInstance.java index 610d8cce0580..a5a9510952e9 100644 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestInstance.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestInstance.java @@ -17,7 +17,7 @@ import org.apache.pinot.core.transport.ServerInstance; import org.junit.jupiter.api.Test; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestInstance { @@ -26,7 +26,7 @@ public void test() { InstanceConfig instanceConfig = new InstanceConfig("Server_pinot-server-0.svc.cluster.local_9000"); ServerInstance serverInstance = new ServerInstance(instanceConfig); - assertEquals(serverInstance.getHostname(), "pinot-server-0.svc.cluster.local"); - assertEquals(serverInstance.getPort(), 9000); + assertThat(serverInstance.getHostname()).isEqualTo("pinot-server-0.svc.cluster.local"); + assertThat(serverInstance.getPort()).isEqualTo(9000); } } diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotColumnHandle.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotColumnHandle.java index f2056d4d493f..768ec70bfcb0 100755 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotColumnHandle.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotColumnHandle.java @@ -19,7 +19,7 @@ import static io.trino.plugin.pinot.MetadataUtil.COLUMN_CODEC; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.VarcharType.VARCHAR; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestPinotColumnHandle { @@ -30,7 +30,7 @@ public void testJsonRoundTrip() { String json = COLUMN_CODEC.toJson(columnHandle); PinotColumnHandle copy = COLUMN_CODEC.fromJson(json); - assertEquals(copy, columnHandle); + assertThat(copy).isEqualTo(columnHandle); } @Test diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotConfig.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotConfig.java index 92ac07b06e20..fdb603715869 100755 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotConfig.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotConfig.java @@ -23,9 +23,8 @@ import java.util.concurrent.TimeUnit; import static io.airlift.units.DataSize.Unit.MEGABYTE; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestPinotConfig { @@ -108,20 +107,20 @@ public void testControllerUrls() { PinotConfig config = new PinotConfig(); config.setControllerUrls("my-controller-1:8443,my-controller-2:8443"); - assertTrue(config.allUrlSchemesEqual()); - assertFalse(config.isTlsEnabled()); + assertThat(config.allUrlSchemesEqual()).isTrue(); + assertThat(config.isTlsEnabled()).isFalse(); config.setControllerUrls("http://my-controller-1:9000,http://my-controller-2:9000"); - assertTrue(config.allUrlSchemesEqual()); - assertFalse(config.isTlsEnabled()); + assertThat(config.allUrlSchemesEqual()).isTrue(); + assertThat(config.isTlsEnabled()).isFalse(); config.setControllerUrls("https://my-controller-1:8443,https://my-controller-2:8443"); - assertTrue(config.allUrlSchemesEqual()); - assertTrue(config.isTlsEnabled()); + assertThat(config.allUrlSchemesEqual()).isTrue(); + assertThat(config.isTlsEnabled()).isTrue(); config.setControllerUrls("my-controller-1:8443,http://my-controller-2:8443"); - assertTrue(config.allUrlSchemesEqual()); - assertFalse(config.isTlsEnabled()); + assertThat(config.allUrlSchemesEqual()).isTrue(); + assertThat(config.isTlsEnabled()).isFalse(); config.setControllerUrls("http://my-controller-1:8443,https://my-controller-2:8443"); - assertFalse(config.allUrlSchemesEqual()); + assertThat(config.allUrlSchemesEqual()).isFalse(); config.setControllerUrls("my-controller-1:8443,https://my-controller-2:8443"); - assertFalse(config.allUrlSchemesEqual()); + assertThat(config.allUrlSchemesEqual()).isFalse(); } } diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotMetadata.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotMetadata.java index cf9f4fc2feb2..cba44c329ce0 100755 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotMetadata.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotMetadata.java @@ -25,7 +25,7 @@ import java.util.concurrent.Executors; import static io.trino.plugin.pinot.MetadataUtil.TEST_TABLE; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestPinotMetadata { @@ -37,19 +37,18 @@ public void testTables() { ConnectorSession session = TestPinotSplitManager.createSessionWithNumSplits(1, false, pinotConfig); List schemaTableNames = metadata.listTables(session, Optional.empty()); - assertEquals(ImmutableSet.copyOf(schemaTableNames), - ImmutableSet.builder() - .add(new SchemaTableName("default", TestPinotSplitManager.realtimeOnlyTable.getTableName())) - .add(new SchemaTableName("default", TestPinotSplitManager.hybridTable.getTableName())) - .add(new SchemaTableName("default", TEST_TABLE)) - .build()); + assertThat(ImmutableSet.copyOf(schemaTableNames)).isEqualTo(ImmutableSet.builder() + .add(new SchemaTableName("default", TestPinotSplitManager.realtimeOnlyTable.getTableName())) + .add(new SchemaTableName("default", TestPinotSplitManager.hybridTable.getTableName())) + .add(new SchemaTableName("default", TEST_TABLE)) + .build()); List schemas = metadata.listSchemaNames(session); - assertEquals(ImmutableList.copyOf(schemas), ImmutableList.of("default")); + assertThat(ImmutableList.copyOf(schemas)).isEqualTo(ImmutableList.of("default")); PinotTableHandle withWeirdSchema = metadata.getTableHandle(session, new SchemaTableName("foo", TestPinotSplitManager.realtimeOnlyTable.getTableName())); - assertEquals(withWeirdSchema.getTableName(), TestPinotSplitManager.realtimeOnlyTable.getTableName()); + assertThat(withWeirdSchema.getTableName()).isEqualTo(TestPinotSplitManager.realtimeOnlyTable.getTableName()); PinotTableHandle withAnotherSchema = metadata.getTableHandle(session, new SchemaTableName(TestPinotSplitManager.realtimeOnlyTable.getTableName(), TestPinotSplitManager.realtimeOnlyTable.getTableName())); - assertEquals(withAnotherSchema.getTableName(), TestPinotSplitManager.realtimeOnlyTable.getTableName()); + assertThat(withAnotherSchema.getTableName()).isEqualTo(TestPinotSplitManager.realtimeOnlyTable.getTableName()); PinotTableHandle withUppercaseTable = metadata.getTableHandle(session, new SchemaTableName("default", TEST_TABLE)); - assertEquals(withUppercaseTable.getTableName(), "airlineStats"); + assertThat(withUppercaseTable.getTableName()).isEqualTo("airlineStats"); } } diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotSessionProperties.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotSessionProperties.java index c19af44912df..3a0f89793fb2 100755 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotSessionProperties.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotSessionProperties.java @@ -20,8 +20,8 @@ import java.util.concurrent.TimeUnit; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestPinotSessionProperties { @@ -42,6 +42,6 @@ public void testConnectionTimeoutParsedProperly() ConnectorSession session = TestingConnectorSession.builder() .setPropertyMetadata(pinotSessionProperties.getSessionProperties()) .build(); - assertEquals(PinotSessionProperties.getConnectionTimeout(session), new Duration(0.25, TimeUnit.MINUTES)); + assertThat(PinotSessionProperties.getConnectionTimeout(session)).isEqualTo(new Duration(0.25, TimeUnit.MINUTES)); } } diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotSplitManager.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotSplitManager.java index 69849d1f1199..61932d278ca9 100755 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotSplitManager.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotSplitManager.java @@ -37,10 +37,8 @@ import static io.trino.spi.type.TimeZoneKey.UTC_KEY; import static java.lang.String.format; import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestPinotSplitManager extends TestPinotQueryBase @@ -101,15 +99,15 @@ public void testHybridSegmentSplitsOneSegmentPerServer() private void assertSplits(List splits, int numSplitsExpected, PinotSplit.SplitType splitType) { - assertEquals(splits.size(), numSplitsExpected); - splits.forEach(s -> assertEquals(s.getSplitType(), splitType)); + assertThat(splits.size()).isEqualTo(numSplitsExpected); + splits.forEach(s -> assertThat(s.getSplitType()).isEqualTo(splitType)); } private void assertSegmentSplitWellFormed(PinotSplit split) { - assertEquals(split.getSplitType(), SEGMENT); - assertTrue(split.getSegmentHost().isPresent()); - assertFalse(split.getSegments().isEmpty()); + assertThat(split.getSplitType()).isEqualTo(SEGMENT); + assertThat(split.getSegmentHost().isPresent()).isTrue(); + assertThat(split.getSegments().isEmpty()).isFalse(); } public static ConnectorSession createSessionWithNumSplits(int numSegmentsPerSplit, boolean forbidSegmentQueries, PinotConfig pinotConfig) diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotTableHandle.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotTableHandle.java index b80370f85552..ae3bddf53209 100755 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotTableHandle.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/TestPinotTableHandle.java @@ -18,7 +18,7 @@ import org.junit.jupiter.api.Test; import static io.airlift.json.JsonCodec.jsonCodec; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestPinotTableHandle { @@ -30,7 +30,7 @@ public void testJsonRoundTrip() JsonCodec codec = jsonCodec(PinotTableHandle.class); String json = codec.toJson(tableHandle); PinotTableHandle copy = codec.fromJson(json); - assertEquals(copy, tableHandle); + assertThat(copy).isEqualTo(tableHandle); } @Test diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/none/TestPinotEmptyAuthenticationProvider.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/none/TestPinotEmptyAuthenticationProvider.java index 35f4923b9fe3..d6517af81a1f 100644 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/none/TestPinotEmptyAuthenticationProvider.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/none/TestPinotEmptyAuthenticationProvider.java @@ -17,7 +17,7 @@ import java.util.Optional; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestPinotEmptyAuthenticationProvider { @@ -25,6 +25,6 @@ public class TestPinotEmptyAuthenticationProvider public void testAuthenticationToken() { PinotEmptyAuthenticationProvider underTest = PinotEmptyAuthenticationProvider.instance(); - assertEquals(underTest.getAuthenticationToken(), Optional.empty()); + assertThat(underTest.getAuthenticationToken()).isEqualTo(Optional.empty()); } } diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/TestPinotPasswordAuthenticationProvider.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/TestPinotPasswordAuthenticationProvider.java index 4d52831515ed..9e976eeedd0d 100644 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/TestPinotPasswordAuthenticationProvider.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/auth/password/TestPinotPasswordAuthenticationProvider.java @@ -17,7 +17,7 @@ import java.util.Optional; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestPinotPasswordAuthenticationProvider { @@ -25,6 +25,6 @@ public class TestPinotPasswordAuthenticationProvider public void testAuthenticationToken() { PinotPasswordAuthenticationProvider underTest = new PinotPasswordAuthenticationProvider("admin", "verysecret"); - assertEquals(underTest.getAuthenticationToken(), Optional.of("Basic YWRtaW46dmVyeXNlY3JldA==")); + assertThat(underTest.getAuthenticationToken()).isEqualTo(Optional.of("Basic YWRtaW46dmVyeXNlY3JldA==")); } } diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusCaseInsensitiveNameMatching.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusCaseInsensitiveNameMatching.java index bad892836dfd..6cf39173bdad 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusCaseInsensitiveNameMatching.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusCaseInsensitiveNameMatching.java @@ -29,10 +29,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -59,14 +55,14 @@ public void testCaseInsensitiveNameMatchingFalse() Set tableNames = client.getTableNames(DEFAULT_SCHEMA); assertThat(tableNames).hasSize(1); - assertTrue(tableNames.contains(UPPER_CASE_METRIC)); + assertThat(tableNames.contains(UPPER_CASE_METRIC)).isTrue(); PrometheusMetadata metadata = new PrometheusMetadata(client); List tables = metadata.listTables(null, Optional.of(DEFAULT_SCHEMA)); assertThat(tableNames).hasSize(1); - assertEquals(UPPER_CASE_METRIC.toLowerCase(ENGLISH), tables.get(0).getTableName()); + assertThat(UPPER_CASE_METRIC.toLowerCase(ENGLISH)).isEqualTo(tables.get(0).getTableName()); - assertNull(client.getTable(DEFAULT_SCHEMA, tables.get(0).getTableName())); + assertThat(client.getTable(DEFAULT_SCHEMA, tables.get(0).getTableName())).isNull(); } @Test @@ -80,15 +76,15 @@ public void testCaseInsensitiveNameMatchingTrue() Set tableNames = client.getTableNames(DEFAULT_SCHEMA); assertThat(tableNames).hasSize(1); - assertTrue(tableNames.contains(UPPER_CASE_METRIC)); + assertThat(tableNames.contains(UPPER_CASE_METRIC)).isTrue(); PrometheusMetadata metadata = new PrometheusMetadata(client); List tables = metadata.listTables(null, Optional.of(DEFAULT_SCHEMA)); assertThat(tableNames).hasSize(1); SchemaTableName table = tables.get(0); - assertEquals(UPPER_CASE_METRIC.toLowerCase(ENGLISH), table.getTableName()); + assertThat(UPPER_CASE_METRIC.toLowerCase(ENGLISH)).isEqualTo(table.getTableName()); - assertNotNull(client.getTable(DEFAULT_SCHEMA, tables.get(0).getTableName())); - assertEquals(UPPER_CASE_METRIC, client.getTable(DEFAULT_SCHEMA, tables.get(0).getTableName()).getName()); + assertThat(client.getTable(DEFAULT_SCHEMA, tables.get(0).getTableName())).isNotNull(); + assertThat(UPPER_CASE_METRIC).isEqualTo(client.getTable(DEFAULT_SCHEMA, tables.get(0).getTableName()).getName()); } } diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusColumnHandle.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusColumnHandle.java index 766a386adaab..e644a33208dd 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusColumnHandle.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusColumnHandle.java @@ -19,7 +19,7 @@ import static io.trino.plugin.prometheus.MetadataUtil.COLUMN_CODEC; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestPrometheusColumnHandle { @@ -30,7 +30,7 @@ public void testJsonRoundTrip() { String json = COLUMN_CODEC.toJson(columnHandle); PrometheusColumnHandle copy = COLUMN_CODEC.fromJson(json); - assertEquals(copy, columnHandle); + assertThat(copy).isEqualTo(columnHandle); } @Test diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryMatrixResponseParse.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryMatrixResponseParse.java index 01d11ee9e1be..10acfe9f7e32 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryMatrixResponseParse.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryMatrixResponseParse.java @@ -27,12 +27,10 @@ import java.util.List; import static java.time.Instant.ofEpochMilli; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_METHOD) @Execution(SAME_THREAD) @@ -45,7 +43,7 @@ public class TestPrometheusQueryMatrixResponseParse public void trueStatusOnSuccessResponse() throws IOException { - assertTrue(new PrometheusQueryResponseParse(promMatrixResponse).getStatus()); + assertThat(new PrometheusQueryResponseParse(promMatrixResponse).getStatus()).isTrue(); } @Test @@ -53,7 +51,7 @@ public void verifyMetricPropertiesResponse() throws IOException { List results = new PrometheusQueryResponseParse(promMatrixResponse).getResults(); - assertEquals(results.get(0).getMetricHeader().get("__name__"), "up"); + assertThat(results.get(0).getMetricHeader().get("__name__")).isEqualTo("up"); } @Test @@ -61,7 +59,7 @@ public void verifyMetricTimestampResponse() throws IOException { List results = new PrometheusQueryResponseParse(promMatrixResponse).getResults(); - assertEquals(results.get(0).getTimeSeriesValues().getValues().get(0).getTimestamp(), ofEpochMilli(1565962969044L)); + assertThat(results.get(0).getTimeSeriesValues().getValues().get(0).getTimestamp()).isEqualTo(ofEpochMilli(1565962969044L)); } @Test @@ -69,7 +67,7 @@ public void verifyMetricValueResponse() throws IOException { List results = new PrometheusQueryResponseParse(promMatrixResponse).getResults(); - assertEquals(results.get(0).getTimeSeriesValues().getValues().get(0).getValue(), "1"); + assertThat(results.get(0).getTimeSeriesValues().getValues().get(0).getValue()).isEqualTo("1"); } @Test @@ -85,11 +83,15 @@ public void setUp() throws Exception { URL promMatrixResponse = Resources.getResource(getClass(), "/prometheus-data/up_matrix_response.json"); - assertNotNull(promMatrixResponse, "metadataUrl is null"); + assertThat(promMatrixResponse) + .describedAs("metadataUrl is null") + .isNotNull(); this.promMatrixResponse = promMatrixResponse.openStream(); URL promErrorResponse = Resources.getResource(getClass(), "/prometheus-data/prom_error_response.json"); - assertNotNull(promMatrixResponse, "metadataUrl is null"); + assertThat(promMatrixResponse) + .describedAs("metadataUrl is null") + .isNotNull(); this.promErrorResponse = promErrorResponse.openStream(); } diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryScalarResponseParse.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryScalarResponseParse.java index 74d2a57b871c..45703c7da9ba 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryScalarResponseParse.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryScalarResponseParse.java @@ -22,9 +22,7 @@ import java.util.List; import static java.time.Instant.ofEpochMilli; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestPrometheusQueryScalarResponseParse { @@ -33,7 +31,7 @@ public void trueStatusOnSuccessResponse() throws IOException { try (InputStream promVectorResponse = openStream()) { - assertTrue(new PrometheusQueryResponseParse(promVectorResponse).getStatus()); + assertThat(new PrometheusQueryResponseParse(promVectorResponse).getStatus()).isTrue(); } } @@ -43,7 +41,7 @@ public void verifyMetricPropertiesResponse() { try (InputStream promVectorResponse = openStream()) { List results = new PrometheusQueryResponseParse(promVectorResponse).getResults(); - assertEquals(results.get(0).getMetricHeader().get("__name__"), "scalar"); + assertThat(results.get(0).getMetricHeader().get("__name__")).isEqualTo("scalar"); } } @@ -53,7 +51,7 @@ public void verifyMetricTimestampResponse() { try (InputStream promVectorResponse = openStream()) { List results = new PrometheusQueryResponseParse(promVectorResponse).getResults(); - assertEquals(results.get(0).getTimeSeriesValues().getValues().get(0).getTimestamp(), ofEpochMilli(1566306405073L)); + assertThat(results.get(0).getTimeSeriesValues().getValues().get(0).getTimestamp()).isEqualTo(ofEpochMilli(1566306405073L)); } } @@ -63,7 +61,7 @@ public void verifyMetricValueResponse() { try (InputStream promVectorResponse = openStream()) { List results = new PrometheusQueryResponseParse(promVectorResponse).getResults(); - assertEquals(results.get(0).getTimeSeriesValues().getValues().get(0).getValue(), "1"); + assertThat(results.get(0).getTimeSeriesValues().getValues().get(0).getValue()).isEqualTo("1"); } } @@ -71,7 +69,9 @@ private static InputStream openStream() throws IOException { URL promMatrixResponse = Resources.getResource(TestPrometheusQueryScalarResponseParse.class, "/prometheus-data/up_scalar_response.json"); - assertNotNull(promMatrixResponse, "metadataUrl is null"); + assertThat(promMatrixResponse) + .describedAs("metadataUrl is null") + .isNotNull(); return promMatrixResponse.openStream(); } } diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryVectorResponseParse.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryVectorResponseParse.java index 0be201d67ae0..58169b0fe91e 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryVectorResponseParse.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryVectorResponseParse.java @@ -22,9 +22,7 @@ import java.util.List; import static java.time.Instant.ofEpochMilli; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestPrometheusQueryVectorResponseParse { @@ -33,7 +31,7 @@ public void trueStatusOnSuccessResponse() throws IOException { try (InputStream promVectorResponse = openStream()) { - assertTrue(new PrometheusQueryResponseParse(promVectorResponse).getStatus()); + assertThat(new PrometheusQueryResponseParse(promVectorResponse).getStatus()).isTrue(); } } @@ -43,7 +41,7 @@ public void verifyMetricPropertiesResponse() { try (InputStream promVectorResponse = openStream()) { List results = new PrometheusQueryResponseParse(promVectorResponse).getResults(); - assertEquals(results.get(0).getMetricHeader().get("__name__"), "up"); + assertThat(results.get(0).getMetricHeader().get("__name__")).isEqualTo("up"); } } @@ -53,7 +51,7 @@ public void verifyMetricTimestampResponse() { try (InputStream promVectorResponse = openStream()) { List results = new PrometheusQueryResponseParse(promVectorResponse).getResults(); - assertEquals(results.get(0).getTimeSeriesValues().getValues().get(0).getTimestamp(), ofEpochMilli(1565889995668L)); + assertThat(results.get(0).getTimeSeriesValues().getValues().get(0).getTimestamp()).isEqualTo(ofEpochMilli(1565889995668L)); } } @@ -63,7 +61,7 @@ public void verifyMetricValueResponse() { try (InputStream promVectorResponse = openStream()) { List results = new PrometheusQueryResponseParse(promVectorResponse).getResults(); - assertEquals(results.get(0).getTimeSeriesValues().getValues().get(0).getValue(), "1"); + assertThat(results.get(0).getTimeSeriesValues().getValues().get(0).getValue()).isEqualTo("1"); } } @@ -71,7 +69,9 @@ private static InputStream openStream() throws IOException { URL promMatrixResponse = Resources.getResource(TestPrometheusQueryVectorResponseParse.class, "/prometheus-data/up_vector_response.json"); - assertNotNull(promMatrixResponse, "metadataUrl is null"); + assertThat(promMatrixResponse) + .describedAs("metadataUrl is null") + .isNotNull(); return promMatrixResponse.openStream(); } } diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusRecordSet.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusRecordSet.java index fa0f052199a7..dc087e392424 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusRecordSet.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusRecordSet.java @@ -39,8 +39,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -61,9 +59,9 @@ public void testCursorSimple() new PrometheusColumnHandle("value", DoubleType.DOUBLE, 2))); RecordCursor cursor = recordSet.cursor(); - assertEquals(cursor.getType(0), varcharMapType); - assertEquals(cursor.getType(1), TIMESTAMP_COLUMN_TYPE); - assertEquals(cursor.getType(2), DoubleType.DOUBLE); + assertThat(cursor.getType(0)).isEqualTo(varcharMapType); + assertThat(cursor.getType(1)).isEqualTo(TIMESTAMP_COLUMN_TYPE); + assertThat(cursor.getType(2)).isEqualTo(DoubleType.DOUBLE); List actual = new ArrayList<>(); while (cursor.advanceNextPosition()) { @@ -72,9 +70,9 @@ public void testCursorSimple() .collect(toImmutableMap(entry -> (String) entry.getKey(), entry -> (String) entry.getValue())), (Instant) cursor.getObject(1), cursor.getDouble(2))); - assertFalse(cursor.isNull(0)); - assertFalse(cursor.isNull(1)); - assertFalse(cursor.isNull(2)); + assertThat(cursor.isNull(0)).isFalse(); + assertThat(cursor.isNull(1)).isFalse(); + assertThat(cursor.isNull(2)).isFalse(); } List expected = ImmutableList.builder() .add(new PrometheusStandardizedRow( @@ -92,9 +90,9 @@ public void testCursorSimple() for (int i = 0; i < actual.size(); i++) { PrometheusStandardizedRow actualRow = actual.get(i); PrometheusStandardizedRow expectedRow = expected.get(i); - assertEquals(getMapFromSqlMap(varcharMapType, getSqlMapFromMap(varcharMapType, actualRow.getLabels())), getMapFromSqlMap(varcharMapType, getSqlMapFromMap(varcharMapType, expectedRow.getLabels()))); - assertEquals(actualRow.getTimestamp(), expectedRow.getTimestamp()); - assertEquals(actualRow.getValue(), expectedRow.getValue()); + assertThat(getMapFromSqlMap(varcharMapType, getSqlMapFromMap(varcharMapType, actualRow.getLabels()))).isEqualTo(getMapFromSqlMap(varcharMapType, getSqlMapFromMap(varcharMapType, expectedRow.getLabels()))); + assertThat(actualRow.getTimestamp()).isEqualTo(expectedRow.getTimestamp()); + assertThat(actualRow.getValue()).isEqualTo(expectedRow.getValue()); } } diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusRecordSetProvider.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusRecordSetProvider.java index 44ea49952aaf..6ca479c8852e 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusRecordSetProvider.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusRecordSetProvider.java @@ -36,10 +36,9 @@ import static io.trino.testing.TestingConnectorSession.SESSION; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.time.Instant.ofEpochMilli; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -65,10 +64,14 @@ public void testGetRecordSet() new PrometheusColumnHandle("labels", varcharMapType, 0), new PrometheusColumnHandle("timestamp", TIMESTAMP_COLUMN_TYPE, 1), new PrometheusColumnHandle("value", DoubleType.DOUBLE, 2))); - assertNotNull(recordSet, "recordSet is null"); + assertThat(recordSet) + .describedAs("recordSet is null") + .isNotNull(); RecordCursor cursor = recordSet.cursor(); - assertNotNull(cursor, "cursor is null"); + assertThat(cursor) + .describedAs("cursor is null") + .isNotNull(); Map> actual = new LinkedHashMap<>(); while (cursor.advanceNextPosition()) { @@ -88,6 +91,6 @@ public void testGetRecordSet() "localhost:9090", "__name__", "up", "job", "prometheus")) .buildOrThrow(); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } } diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusSplit.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusSplit.java index 11476f1078b6..ff591793543d 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusSplit.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusSplit.java @@ -61,11 +61,10 @@ import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.apache.http.client.utils.URLEncodedUtils.parse; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotEquals; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -86,23 +85,23 @@ public void testAddresses() { // http split with default port PrometheusSplit httpSplit = new PrometheusSplit("http://prometheus.com/prometheus"); - assertEquals(httpSplit.getAddresses(), ImmutableList.of(HostAddress.fromString("prometheus.com"))); - assertTrue(httpSplit.isRemotelyAccessible()); + assertThat(httpSplit.getAddresses()).isEqualTo(ImmutableList.of(HostAddress.fromString("prometheus.com"))); + assertThat(httpSplit.isRemotelyAccessible()).isTrue(); // http split with custom port httpSplit = new PrometheusSplit("http://prometheus.com:8080/prometheus"); - assertEquals(httpSplit.getAddresses(), ImmutableList.of(HostAddress.fromParts("prometheus.com", 8080))); - assertTrue(httpSplit.isRemotelyAccessible()); + assertThat(httpSplit.getAddresses()).isEqualTo(ImmutableList.of(HostAddress.fromParts("prometheus.com", 8080))); + assertThat(httpSplit.isRemotelyAccessible()).isTrue(); // http split with default port PrometheusSplit httpsSplit = new PrometheusSplit("https://prometheus.com/prometheus"); - assertEquals(httpsSplit.getAddresses(), ImmutableList.of(HostAddress.fromString("prometheus.com"))); - assertTrue(httpsSplit.isRemotelyAccessible()); + assertThat(httpsSplit.getAddresses()).isEqualTo(ImmutableList.of(HostAddress.fromString("prometheus.com"))); + assertThat(httpsSplit.isRemotelyAccessible()).isTrue(); // http split with custom port httpsSplit = new PrometheusSplit("https://prometheus.com:8443/prometheus"); - assertEquals(httpsSplit.getAddresses(), ImmutableList.of(HostAddress.fromParts("prometheus.com", 8443))); - assertTrue(httpsSplit.isRemotelyAccessible()); + assertThat(httpsSplit.getAddresses()).isEqualTo(ImmutableList.of(HostAddress.fromParts("prometheus.com", 8443))); + assertThat(httpsSplit.isRemotelyAccessible()).isTrue(); } @Test @@ -111,10 +110,10 @@ public void testJsonRoundTrip() JsonCodec codec = jsonCodec(PrometheusSplit.class); String json = codec.toJson(split); PrometheusSplit copy = codec.fromJson(json); - assertEquals(copy.getUri(), split.getUri()); + assertThat(copy.getUri()).isEqualTo(split.getUri()); - assertEquals(copy.getAddresses(), ImmutableList.of(HostAddress.fromString("127.0.0.1"))); - assertTrue(copy.isRemotelyAccessible()); + assertThat(copy.getAddresses()).isEqualTo(ImmutableList.of(HostAddress.fromString("127.0.0.1"))); + assertThat(copy.isRemotelyAccessible()).isTrue(); } @Test @@ -137,9 +136,8 @@ public void testQueryWithTableNameNeedingURLEncodeInSplits() config.getMaxQueryRangeDuration().toMillis() + config.getQueryChunkSizeDuration().toMillis() - OFFSET_MILLIS * 20); - assertEquals(queryInSplit, - URI.create("http://doesnotmatter:9090/api/v1/query?query=up%20now[" + getQueryChunkSizeDurationAsPrometheusCompatibleDurationString(config) + "]" + "&time=" + - timeShouldBe).getQuery()); + assertThat(queryInSplit).isEqualTo(URI.create("http://doesnotmatter:9090/api/v1/query?query=up%20now[" + getQueryChunkSizeDurationAsPrometheusCompatibleDurationString(config) + "]" + "&time=" + + timeShouldBe).getQuery()); } @Test @@ -162,9 +160,8 @@ public void testQueryDividedIntoSplitsFirstSplitHasRightTime() config.getMaxQueryRangeDuration().toMillis() + config.getQueryChunkSizeDuration().toMillis() - OFFSET_MILLIS * 20); - assertEquals(queryInSplit, - URI.create("http://doesnotmatter:9090/api/v1/query?query=up[" + getQueryChunkSizeDurationAsPrometheusCompatibleDurationString(config) + "]" + "&time=" + - timeShouldBe).getQuery()); + assertThat(queryInSplit).isEqualTo(URI.create("http://doesnotmatter:9090/api/v1/query?query=up[" + getQueryChunkSizeDurationAsPrometheusCompatibleDurationString(config) + "]" + "&time=" + + timeShouldBe).getQuery()); } @Test @@ -189,7 +186,7 @@ public void testQueryDividedIntoSplitsLastSplitHasRightTime() URI uriAsFormed = URI.create("http://doesnotmatter:9090/api/v1/query?query=up[" + getQueryChunkSizeDurationAsPrometheusCompatibleDurationString(config) + "]" + "&time=" + timeShouldBe); - assertEquals(queryInSplit, uriAsFormed.getQuery()); + assertThat(queryInSplit).isEqualTo(uriAsFormed.getQuery()); } @Test @@ -210,8 +207,8 @@ public void testQueryDividedIntoSplitsShouldHaveCorrectSpacingBetweenTimes() Map paramsMap1 = parse(URI.create(split1.getUri()), StandardCharsets.UTF_8).stream().collect(Collectors.toMap(NameValuePair::getName, NameValuePair::getValue)); PrometheusSplit split2 = (PrometheusSplit) splits.getNextBatch(1).getNow(null).getSplits().get(0); Map paramsMap2 = parse(URI.create(split2.getUri()), StandardCharsets.UTF_8).stream().collect(Collectors.toMap(NameValuePair::getName, NameValuePair::getValue)); - assertEquals(paramsMap1.get("query"), "up[1d]"); - assertEquals(paramsMap2.get("query"), "up[1d]"); + assertThat(paramsMap1.get("query")).isEqualTo("up[1d]"); + assertThat(paramsMap2.get("query")).isEqualTo("up[1d]"); long diff = Double.valueOf(paramsMap2.get("time")).longValue() - Double.valueOf(paramsMap1.get("time")).longValue(); assertEquals(config.getQueryChunkSizeDuration().getValue(TimeUnit.SECONDS), diff, 0.0001); } @@ -229,7 +226,7 @@ public void testSplitTimesCorrect() maxQueryRangeDuration, queryChunkSizeDuration, prometheusTableHandle); List expectedSplitTimes = ImmutableList.of( "827199.998", "913599.999", "1000000"); - assertEquals(splitTimes, expectedSplitTimes); + assertThat(splitTimes).isEqualTo(expectedSplitTimes); } @Test @@ -243,7 +240,7 @@ public void testSplitTimesCorrectNonModuloZeroDurationToChunk() List splitTimes = PrometheusSplitManager.generateTimesForSplits(now, maxQueryRangeDuration, queryChunkSizeDuration, prometheusTableHandle); List expectedSplitTimes = ImmutableList.of( "827199.999", "1000000"); - assertEquals(splitTimes, expectedSplitTimes); + assertThat(splitTimes).isEqualTo(expectedSplitTimes); } @Test @@ -256,7 +253,7 @@ public void testSplitTimesCorrectVersusMock() PrometheusTableHandle prometheusTableHandle = new PrometheusTableHandle("schemaName", "tableName"); List splitTimes = PrometheusSplitManager.generateTimesForSplits(now, maxQueryRangeDuration, queryChunkSizeDuration, prometheusTableHandle); List promTimesReturned = mockPrometheusResponseToChunkedQueries(queryChunkSizeDuration, splitTimes); - assertEquals(promTimesReturned, convertMockTimesToStrings(promTimeValuesMock)); + assertThat(promTimesReturned).isEqualTo(convertMockTimesToStrings(promTimeValuesMock)); } @Test @@ -269,28 +266,28 @@ public void testSplitTimesAreTimesNearBoundaryNotMissing() PrometheusTableHandle prometheusTableHandle = new PrometheusTableHandle("schemaName", "tableName"); List splitTimes = PrometheusSplitManager.generateTimesForSplits(now, maxQueryRangeDuration, queryChunkSizeDuration, prometheusTableHandle); List promTimesReturned = mockPrometheusResponseToChunkedQueries(queryChunkSizeDuration, splitTimes); - assertEquals(promTimesReturned, convertMockTimesToStrings(promTimeValuesMock)); + assertThat(promTimesReturned).isEqualTo(convertMockTimesToStrings(promTimeValuesMock)); } @Test public void testMockPrometheusResponseShouldBeCorrectWhenUpperBoundaryAlignsWithData() { List expectedResponse = ImmutableList.of(1568638142.0, 1568638157.0, 1568638171.999); - assertEquals(mockPrometheusResponseToQuery(new io.airlift.units.Duration(30, TimeUnit.SECONDS), "1568638171.999"), expectedResponse); + assertThat(mockPrometheusResponseToQuery(new io.airlift.units.Duration(30, SECONDS), "1568638171.999")).isEqualTo(expectedResponse); } @Test public void testMockPrometheusResponseShouldBeCorrectWhenLowerBoundaryAlignsWithData() { List expectedResponse = ImmutableList.of(1568638142.0, 1568638157.0, 1568638171.999); - assertEquals(mockPrometheusResponseToQuery(new io.airlift.units.Duration(30, TimeUnit.SECONDS), "1568638172."), expectedResponse); + assertThat(mockPrometheusResponseToQuery(new io.airlift.units.Duration(30, SECONDS), "1568638172.")).isEqualTo(expectedResponse); } @Test public void testMockPrometheusResponseShouldBeCorrectWhenLowerBoundaryLaterThanData() { List expectedResponse = ImmutableList.of(1568638157.0, 1568638171.999); - assertEquals(mockPrometheusResponseToQuery(new io.airlift.units.Duration(30, TimeUnit.SECONDS), "1568638172.001"), expectedResponse); + assertThat(mockPrometheusResponseToQuery(new io.airlift.units.Duration(30, SECONDS), "1568638172.001")).isEqualTo(expectedResponse); } @Test @@ -298,7 +295,7 @@ public void testMockPrometheusResponseWithSeveralChunksShouldBeCorrect() { List expectedResponse = ImmutableList.of("1568638112", "1568638126.997", "1568638142", "1568638157", "1568638171.999"); List splitTimes = ImmutableList.of("1568638141.999", "1568638172."); - assertEquals(mockPrometheusResponseToChunkedQueries(new io.airlift.units.Duration(30, TimeUnit.SECONDS), splitTimes), expectedResponse); + assertThat(mockPrometheusResponseToChunkedQueries(new io.airlift.units.Duration(30, SECONDS), splitTimes)).isEqualTo(expectedResponse); } @Test @@ -311,7 +308,7 @@ public void testPredicatePushDownLowerBoundDirect() new PrometheusColumnHandle("timestamp", TIMESTAMP_COLUMN_TYPE, 2), testDomain)); PrometheusPredicateTimeInfo predicateTimes = PrometheusSplitManager.determinePredicateTimes(testTupleDomain).orElseThrow(); Instant expected = ofEpochMilli(1570460709643L); - assertEquals(predicateTimes.getPredicateLowerTimeBound().orElseThrow(), expected); + assertThat(predicateTimes.getPredicateLowerTimeBound().orElseThrow()).isEqualTo(expected); } @Test @@ -336,8 +333,9 @@ public void testPredicatePushDownSetsLowerBoundOnly() Instant earliestSplitAsTime = ofEpochMilli(longFromDecimalSecondString(earliestSplit)); TemporalAmount queryChunkAsTime = java.time.Duration.ofMillis(queryChunkSizeDuration.toMillis()); Instant startOfQuery = earliestSplitAsTime.minus(queryChunkAsTime); - assertNotEquals(startOfQuery, now.minus(maxQueryAsTime).minus(java.time.Duration.ofMillis((splitTimes.size() - 1) * OFFSET_MILLIS))); - assertEquals(startOfQuery.toEpochMilli(), ofEpochMilli(predicateLowValue).toEpochMilli() - ((splitTimes.size() - 1) * OFFSET_MILLIS)); + assertThat(startOfQuery) + .isNotEqualTo(now.minus(maxQueryAsTime).minus(Duration.ofMillis((splitTimes.size() - 1) * OFFSET_MILLIS))); + assertThat(startOfQuery.toEpochMilli()).isEqualTo(ofEpochMilli(predicateLowValue).toEpochMilli() - ((splitTimes.size() - 1) * OFFSET_MILLIS)); } @Test @@ -368,8 +366,8 @@ public void testPredicatePushDownSetsUpperBoundOnly() java.time.Duration actualMaxDuration = Duration.between(earliestSplitAsTime .minus(queryChunkAsTime), lastSplitAsTime); - assertEquals(lastSplitAsTime.toEpochMilli(), 1568638171999L); - assertEquals(actualMaxDuration, expectedMaxQueryAsTime); + assertThat(lastSplitAsTime.toEpochMilli()).isEqualTo(1568638171999L); + assertThat(actualMaxDuration).isEqualTo(expectedMaxQueryAsTime); } @Test @@ -402,8 +400,8 @@ public void testPredicatePushDownSetsUpperAndLowerBound() java.time.Duration actualMaxDuration = Duration.between(earliestSplitAsTime .minus(queryChunkAsTime), lastSplitAsTime); - assertEquals(lastSplitAsTime.toEpochMilli(), 1568638171999L); - assertEquals(actualMaxDuration, expectedMaxQueryAsTime); + assertThat(lastSplitAsTime.toEpochMilli()).isEqualTo(1568638171999L); + assertThat(actualMaxDuration).isEqualTo(expectedMaxQueryAsTime); } @Test @@ -421,8 +419,9 @@ public void testEmptyPredicatePredicatePushDown() Instant earliestSplitAsTime = ofEpochMilli(longFromDecimalSecondString(earliestSplit)); TemporalAmount queryChunkAsTime = java.time.Duration.ofMillis(queryChunkSizeDuration.toMillis()); Instant startOfQuery = earliestSplitAsTime.minus(queryChunkAsTime); - assertEquals(startOfQuery, now.minus(maxQueryAsTime).minus(java.time.Duration.ofMillis((splitTimes.size() - 1) * OFFSET_MILLIS))); - assertNotEquals(startOfQuery.toEpochMilli(), ofEpochMilli(predicateLowValue).toEpochMilli()); + assertThat(startOfQuery).isEqualTo(now.minus(maxQueryAsTime).minus(Duration.ofMillis((splitTimes.size() - 1) * OFFSET_MILLIS))); + assertThat(startOfQuery.toEpochMilli()) + .isNotEqualTo(ofEpochMilli(predicateLowValue).toEpochMilli()); } /** diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusTable.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusTable.java index 69d490c2b40a..7f821aecadbc 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusTable.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusTable.java @@ -21,7 +21,7 @@ import static io.trino.plugin.prometheus.MetadataUtil.TABLE_CODEC; import static io.trino.plugin.prometheus.MetadataUtil.varcharMapType; import static io.trino.plugin.prometheus.PrometheusClient.TIMESTAMP_COLUMN_TYPE; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestPrometheusTable { @@ -34,7 +34,7 @@ public class TestPrometheusTable @Test public void testColumnMetadata() { - assertEquals(prometheusTable.getColumnsMetadata(), ImmutableList.of( + assertThat(prometheusTable.getColumnsMetadata()).isEqualTo(ImmutableList.of( new ColumnMetadata("labels", varcharMapType), new ColumnMetadata("timestamp", TIMESTAMP_COLUMN_TYPE), new ColumnMetadata("value", DoubleType.DOUBLE))); @@ -46,7 +46,7 @@ public void testRoundTrip() String json = TABLE_CODEC.toJson(prometheusTable); PrometheusTable prometheusTableCopy = TABLE_CODEC.fromJson(json); - assertEquals(prometheusTableCopy.getName(), prometheusTable.getName()); - assertEquals(prometheusTableCopy.getColumns(), prometheusTable.getColumns()); + assertThat(prometheusTableCopy.getName()).isEqualTo(prometheusTable.getName()); + assertThat(prometheusTableCopy.getColumns()).isEqualTo(prometheusTable.getColumns()); } } diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusTableHandle.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusTableHandle.java index 64401552011e..d550bcda0392 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusTableHandle.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusTableHandle.java @@ -18,7 +18,7 @@ import org.junit.jupiter.api.Test; import static io.airlift.json.JsonCodec.jsonCodec; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestPrometheusTableHandle { @@ -30,7 +30,7 @@ public void testJsonRoundTrip() JsonCodec codec = jsonCodec(PrometheusTableHandle.class); String json = codec.toJson(tableHandle); PrometheusTableHandle copy = codec.fromJson(json); - assertEquals(copy, tableHandle); + assertThat(copy).isEqualTo(tableHandle); } @Test diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusTimestampDeserializer.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusTimestampDeserializer.java index 1ecdb62192ba..cafd21564e1f 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusTimestampDeserializer.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusTimestampDeserializer.java @@ -18,7 +18,7 @@ import java.time.Instant; import static java.time.temporal.ChronoUnit.MILLIS; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestPrometheusTimestampDeserializer { @@ -29,6 +29,6 @@ public void testDeserializeTimestamp() long nowEpochMillis = now.toEpochMilli(); String nowTimeStr = PrometheusSplitManager.decimalSecondString(nowEpochMillis); Instant nowTimestampActual = PrometheusTimestampDeserializer.decimalEpochTimestampToSQLTimestamp(nowTimeStr); - assertEquals(nowTimestampActual, now.truncatedTo(MILLIS)); + assertThat(nowTimestampActual).isEqualTo(now.truncatedTo(MILLIS)); } } diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/BaseRaptorConnectorTest.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/BaseRaptorConnectorTest.java index fab62710dcc6..effb15e3b1a7 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/BaseRaptorConnectorTest.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/BaseRaptorConnectorTest.java @@ -59,10 +59,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assumptions.abort; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotEquals; -import static org.testng.Assert.assertNotNull; public abstract class BaseRaptorConnectorTest extends BaseConnectorTest @@ -120,13 +116,13 @@ public void testRenameTableAcrossSchema() String renamedTable = "test_rename_new_" + randomNameSuffix(); assertUpdate("ALTER TABLE " + tableName + " RENAME TO " + schemaName + "." + renamedTable); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); assertQuery("SELECT x FROM " + schemaName + "." + renamedTable, "VALUES 123"); assertUpdate("DROP TABLE " + schemaName + "." + renamedTable); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); - assertFalse(getQueryRunner().tableExists(Session.builder(getSession()).setSchema(schemaName).build(), renamedTable)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); + assertThat(getQueryRunner().tableExists(Session.builder(getSession()).setSchema(schemaName).build(), renamedTable)).isFalse(); } @Override @@ -224,19 +220,20 @@ public void testShardUuidHiddenColumn() assertUpdate("CREATE TABLE test_shard_uuid AS SELECT orderdate, orderkey FROM orders", "SELECT count(*) FROM orders"); MaterializedResult actualResults = computeActual("SELECT *, \"$shard_uuid\" FROM test_shard_uuid"); - assertEquals(actualResults.getTypes(), ImmutableList.of(DATE, BIGINT, SHARD_UUID_COLUMN_TYPE)); + assertThat(actualResults.getTypes()).isEqualTo(ImmutableList.of(DATE, BIGINT, SHARD_UUID_COLUMN_TYPE)); UUID arbitraryUuid = null; for (MaterializedRow row : actualResults.getMaterializedRows()) { Object uuid = row.getField(2); assertInstanceOf(uuid, String.class); arbitraryUuid = UUID.fromString((String) uuid); } - assertNotNull(arbitraryUuid); + assertThat(arbitraryUuid).isNotNull(); actualResults = computeActual(format("SELECT * FROM test_shard_uuid where \"$shard_uuid\" = '%s'", arbitraryUuid)); - assertNotEquals(actualResults.getMaterializedRows().size(), 0); + assertThat(actualResults.getMaterializedRows().size()) + .isNotEqualTo(0); actualResults = computeActual("SELECT * FROM test_shard_uuid where \"$shard_uuid\" = 'foo'"); - assertEquals(actualResults.getMaterializedRows().size(), 0); + assertThat(actualResults.getMaterializedRows().size()).isEqualTo(0); } @Test @@ -249,11 +246,11 @@ public void testBucketNumberHiddenColumn() "SELECT count(*) FROM orders"); MaterializedResult actualResults = computeActual("SELECT DISTINCT \"$bucket_number\" FROM test_bucket_number"); - assertEquals(actualResults.getTypes(), ImmutableList.of(INTEGER)); + assertThat(actualResults.getTypes()).isEqualTo(ImmutableList.of(INTEGER)); Set actual = actualResults.getMaterializedRows().stream() .map(row -> row.getField(0)) .collect(toSet()); - assertEquals(actual, IntStream.range(0, 50).boxed().collect(toSet())); + assertThat(actual).isEqualTo(IntStream.range(0, 50).boxed().collect(toSet())); } @Test @@ -271,7 +268,7 @@ public void testNoBucketNumberHiddenColumn() public void testShardingByTemporalDateColumn() { // Make sure we have at least 2 different orderdate. - assertEquals(computeActual("SELECT count(DISTINCT orderdate) >= 2 FROM orders WHERE orderdate < date '1992-02-08'").getOnlyValue(), true); + assertThat(computeActual("SELECT count(DISTINCT orderdate) >= 2 FROM orders WHERE orderdate < date '1992-02-08'").getOnlyValue()).isEqualTo(true); assertUpdate("CREATE TABLE test_shard_temporal_date " + "WITH (temporal_column = 'orderdate') AS " + @@ -291,7 +288,7 @@ public void testShardingByTemporalDateColumn() } for (Collection dates : shardDateMap.asMap().values()) { - assertEquals(dates.size(), 1); + assertThat(dates.size()).isEqualTo(1); } // Make sure we have all the rows @@ -303,7 +300,7 @@ public void testShardingByTemporalDateColumn() public void testShardingByTemporalDateColumnBucketed() { // Make sure we have at least 2 different orderdate. - assertEquals(computeActual("SELECT count(DISTINCT orderdate) >= 2 FROM orders WHERE orderdate < date '1992-02-08'").getOnlyValue(), true); + assertThat(computeActual("SELECT count(DISTINCT orderdate) >= 2 FROM orders WHERE orderdate < date '1992-02-08'").getOnlyValue()).isEqualTo(true); assertUpdate("CREATE TABLE test_shard_temporal_date_bucketed " + "WITH (temporal_column = 'orderdate', bucket_count = 10, bucketed_on = ARRAY ['orderkey']) AS " + @@ -323,7 +320,7 @@ public void testShardingByTemporalDateColumnBucketed() } for (Collection dates : shardDateMap.asMap().values()) { - assertEquals(dates.size(), 1); + assertThat(dates.size()).isEqualTo(1); } // Make sure we have all the rows @@ -345,7 +342,7 @@ public void testShardingByTemporalTimestampColumn() assertUpdate(joiner.toString(), format("VALUES(%s)", rows)); MaterializedResult results = computeActual("SELECT cast(cast(col2 as DATE) as VARCHAR), \"$shard_uuid\" FROM test_shard_temporal_timestamp"); - assertEquals(results.getRowCount(), rows); + assertThat(results.getRowCount()).isEqualTo(rows); // Each shard will only contain data of one date. SetMultimap shardDateMap = HashMultimap.create(); @@ -354,7 +351,7 @@ public void testShardingByTemporalTimestampColumn() } for (Collection dates : shardDateMap.asMap().values()) { - assertEquals(dates.size(), 1); + assertThat(dates.size()).isEqualTo(1); } // Ensure one shard can contain different timestamps from the same day @@ -380,7 +377,7 @@ public void testShardingByTemporalTimestampColumnBucketed() "SELECT cast(cast(col2 as DATE) as VARCHAR), \"$shard_uuid\" " + "FROM test_shard_temporal_timestamp_bucketed"); - assertEquals(results.getRowCount(), rows); + assertThat(results.getRowCount()).isEqualTo(rows); // Each shard will only contain data of one date. SetMultimap shardDateMap = HashMultimap.create(); @@ -389,7 +386,7 @@ public void testShardingByTemporalTimestampColumnBucketed() } for (Collection dates : shardDateMap.asMap().values()) { - assertEquals(dates.size(), 1); + assertThat(dates.size()).isEqualTo(1); } // Ensure one shard can contain different timestamps from the same day @@ -422,7 +419,7 @@ public void testShardsSystemTable() public void testShardsSystemTableWithTemporalColumn() { // Make sure we have rows in the selected range - assertEquals(computeActual("SELECT count(*) >= 1 FROM orders WHERE orderdate BETWEEN date '1992-01-01' AND date '1992-02-08'").getOnlyValue(), true); + assertThat(computeActual("SELECT count(*) >= 1 FROM orders WHERE orderdate BETWEEN date '1992-01-01' AND date '1992-02-08'").getOnlyValue()).isEqualTo(true); // Create a table that has DATE type temporal column assertUpdate("CREATE TABLE test_shards_system_table_date_temporal\n" + @@ -444,24 +441,24 @@ public void testShardsSystemTableWithTemporalColumn() "WHERE orderdate BETWEEN date '1992-01-01' AND date '1992-02-08'"); // For table with DATE type temporal column, min/max_timestamp columns must be null while min/max_date columns must not be null - assertEquals(computeActual("" + + assertThat(computeActual("" + "SELECT count(*)\n" + "FROM system.shards\n" + "WHERE table_schema = 'tpch'\n" + "AND table_name = 'test_shards_system_table_date_temporal'\n" + "AND NOT \n" + "(min_timestamp IS NULL AND max_timestamp IS NULL\n" + - "AND min_date IS NOT NULL AND max_date IS NOT NULL)").getOnlyValue(), 0L); + "AND min_date IS NOT NULL AND max_date IS NOT NULL)").getOnlyValue()).isEqualTo(0L); // For table with TIMESTAMP type temporal column, min/max_date columns must be null while min/max_timestamp columns must not be null - assertEquals(computeActual("" + + assertThat(computeActual("" + "SELECT count(*)\n" + "FROM system.shards\n" + "WHERE table_schema = 'tpch'\n" + "AND table_name = 'test_shards_system_table_timestamp_temporal'\n" + "AND NOT\n" + "(min_date IS NULL AND max_date IS NULL\n" + - "AND min_timestamp IS NOT NULL AND max_timestamp IS NOT NULL)").getOnlyValue(), 0L); + "AND min_timestamp IS NOT NULL AND max_timestamp IS NOT NULL)").getOnlyValue()).isEqualTo(0L); // Test date predicates in table with DATE temporal column assertQuery("" + @@ -629,13 +626,13 @@ public void testShowCreateTable() assertUpdate(createTableSql); MaterializedResult actualResult = computeActual("SHOW CREATE TABLE test_show_create_table"); - assertEquals(getOnlyElement(actualResult.getOnlyColumnAsSet()), createTableSql); + assertThat(getOnlyElement(actualResult.getOnlyColumnAsSet())).isEqualTo(createTableSql); actualResult = computeActual("SHOW CREATE TABLE " + getSession().getSchema().get() + ".test_show_create_table"); - assertEquals(getOnlyElement(actualResult.getOnlyColumnAsSet()), createTableSql); + assertThat(getOnlyElement(actualResult.getOnlyColumnAsSet())).isEqualTo(createTableSql); actualResult = computeActual("SHOW CREATE TABLE " + getSession().getCatalog().get() + "." + getSession().getSchema().get() + ".test_show_create_table"); - assertEquals(getOnlyElement(actualResult.getOnlyColumnAsSet()), createTableSql); + assertThat(getOnlyElement(actualResult.getOnlyColumnAsSet())).isEqualTo(createTableSql); // With organization enabled createTableSql = format("" + @@ -658,13 +655,13 @@ public void testShowCreateTable() assertUpdate(createTableSql); actualResult = computeActual("SHOW CREATE TABLE test_show_create_table_organized"); - assertEquals(getOnlyElement(actualResult.getOnlyColumnAsSet()), createTableSql); + assertThat(getOnlyElement(actualResult.getOnlyColumnAsSet())).isEqualTo(createTableSql); actualResult = computeActual("SHOW CREATE TABLE " + getSession().getSchema().get() + ".test_show_create_table_organized"); - assertEquals(getOnlyElement(actualResult.getOnlyColumnAsSet()), createTableSql); + assertThat(getOnlyElement(actualResult.getOnlyColumnAsSet())).isEqualTo(createTableSql); actualResult = computeActual("SHOW CREATE TABLE " + getSession().getCatalog().get() + "." + getSession().getSchema().get() + ".test_show_create_table_organized"); - assertEquals(getOnlyElement(actualResult.getOnlyColumnAsSet()), createTableSql); + assertThat(getOnlyElement(actualResult.getOnlyColumnAsSet())).isEqualTo(createTableSql); createTableSql = format("" + "CREATE TABLE %s.%s.%s (\n" + @@ -678,7 +675,7 @@ public void testShowCreateTable() assertUpdate(createTableSql); actualResult = computeActual("SHOW CREATE TABLE \"test_show_create_table\"\"2\""); - assertEquals(getOnlyElement(actualResult.getOnlyColumnAsSet()), createTableSql); + assertThat(getOnlyElement(actualResult.getOnlyColumnAsSet())).isEqualTo(createTableSql); } @Test @@ -721,59 +718,45 @@ public void testTablesSystemTable() "WITH (ordering = ARRAY['c51', 'c52'], distribution_name = 'test_distribution', bucket_count = 50, bucketed_on = ARRAY ['c53', 'c54'], organized = true)"); MaterializedResult actualResults = computeActual("SELECT * FROM system.tables"); - assertEquals( - actualResults.getTypes(), - ImmutableList.builder() - .add(VARCHAR) // table_schema - .add(VARCHAR) // table_name - .add(VARCHAR) // temporal_column - .add(new ArrayType(VARCHAR)) // ordering_columns - .add(VARCHAR) // distribution_name - .add(BIGINT) // bucket_count - .add(new ArrayType(VARCHAR)) // bucket_columns - .add(BOOLEAN) // organized - .build()); + assertThat(actualResults.getTypes()).isEqualTo(ImmutableList.builder() + .add(VARCHAR) // table_schema + .add(VARCHAR) // table_name + .add(VARCHAR) // temporal_column + .add(new ArrayType(VARCHAR)) // ordering_columns + .add(VARCHAR) // distribution_name + .add(BIGINT) // bucket_count + .add(new ArrayType(VARCHAR)) // bucket_columns + .add(BOOLEAN) // organized + .build()); Map map = actualResults.getMaterializedRows().stream() .filter(row -> ((String) row.getField(1)).startsWith("system_tables_test")) .collect(toImmutableMap(row -> ((String) row.getField(1)), identity())); - assertEquals(map.size(), 6); - assertEquals( - map.get("system_tables_test0").getFields(), - asList("tpch", "system_tables_test0", null, null, null, null, null, Boolean.FALSE)); - assertEquals( - map.get("system_tables_test1").getFields(), - asList("tpch", "system_tables_test1", "c10", null, null, null, null, Boolean.FALSE)); - assertEquals( - map.get("system_tables_test2").getFields(), - asList("tpch", "system_tables_test2", "c20", ImmutableList.of("c22", "c21"), null, null, null, Boolean.FALSE)); - assertEquals( - map.get("system_tables_test3").getFields(), - asList("tpch", "system_tables_test3", "c30", null, null, 40L, ImmutableList.of("c34", "c33"), Boolean.FALSE)); - assertEquals( - map.get("system_tables_test4").getFields(), - asList("tpch", "system_tables_test4", "c40", ImmutableList.of("c41", "c42"), "test_distribution", 50L, ImmutableList.of("c43", "c44"), Boolean.FALSE)); - assertEquals( - map.get("system_tables_test5").getFields(), - asList("tpch", "system_tables_test5", null, ImmutableList.of("c51", "c52"), "test_distribution", 50L, ImmutableList.of("c53", "c54"), Boolean.TRUE)); + assertThat(map.size()).isEqualTo(6); + assertThat(map.get("system_tables_test0").getFields()).isEqualTo(asList("tpch", "system_tables_test0", null, null, null, null, null, Boolean.FALSE)); + assertThat(map.get("system_tables_test1").getFields()).isEqualTo(asList("tpch", "system_tables_test1", "c10", null, null, null, null, Boolean.FALSE)); + assertThat(map.get("system_tables_test2").getFields()).isEqualTo(asList("tpch", "system_tables_test2", "c20", ImmutableList.of("c22", "c21"), null, null, null, Boolean.FALSE)); + assertThat(map.get("system_tables_test3").getFields()).isEqualTo(asList("tpch", "system_tables_test3", "c30", null, null, 40L, ImmutableList.of("c34", "c33"), Boolean.FALSE)); + assertThat(map.get("system_tables_test4").getFields()).isEqualTo(asList("tpch", "system_tables_test4", "c40", ImmutableList.of("c41", "c42"), "test_distribution", 50L, ImmutableList.of("c43", "c44"), Boolean.FALSE)); + assertThat(map.get("system_tables_test5").getFields()).isEqualTo(asList("tpch", "system_tables_test5", null, ImmutableList.of("c51", "c52"), "test_distribution", 50L, ImmutableList.of("c53", "c54"), Boolean.TRUE)); actualResults = computeActual("SELECT * FROM system.tables WHERE table_schema = 'tpch'"); long actualRowCount = actualResults.getMaterializedRows().stream() .filter(row -> ((String) row.getField(1)).startsWith("system_tables_test")) .count(); - assertEquals(actualRowCount, 6); + assertThat(actualRowCount).isEqualTo(6); actualResults = computeActual("SELECT * FROM system.tables WHERE table_name = 'system_tables_test3'"); - assertEquals(actualResults.getMaterializedRows().size(), 1); + assertThat(actualResults.getMaterializedRows().size()).isEqualTo(1); actualResults = computeActual("SELECT * FROM system.tables WHERE table_schema = 'tpch' and table_name = 'system_tables_test3'"); - assertEquals(actualResults.getMaterializedRows().size(), 1); + assertThat(actualResults.getMaterializedRows().size()).isEqualTo(1); actualResults = computeActual("" + "SELECT distribution_name, bucket_count, bucketing_columns, ordering_columns, temporal_column, organized " + "FROM system.tables " + "WHERE table_schema = 'tpch' and table_name = 'system_tables_test3'"); - assertEquals(actualResults.getTypes(), ImmutableList.of(VARCHAR, BIGINT, new ArrayType(VARCHAR), new ArrayType(VARCHAR), VARCHAR, BOOLEAN)); - assertEquals(actualResults.getMaterializedRows().size(), 1); + assertThat(actualResults.getTypes()).isEqualTo(ImmutableList.of(VARCHAR, BIGINT, new ArrayType(VARCHAR), new ArrayType(VARCHAR), VARCHAR, BOOLEAN)); + assertThat(actualResults.getMaterializedRows().size()).isEqualTo(1); assertUpdate("DROP TABLE system_tables_test0"); assertUpdate("DROP TABLE system_tables_test1"); @@ -782,7 +765,7 @@ public void testTablesSystemTable() assertUpdate("DROP TABLE system_tables_test4"); assertUpdate("DROP TABLE system_tables_test5"); - assertEquals(computeActual("SELECT * FROM system.tables WHERE table_schema IN ('foo', 'bar')").getRowCount(), 0); + assertThat(computeActual("SELECT * FROM system.tables WHERE table_schema IN ('foo', 'bar')").getRowCount()).isEqualTo(0); } @Test @@ -822,24 +805,24 @@ public void testTableStatsSystemTable() LocalDateTime createTime = (LocalDateTime) row.getField(0); LocalDateTime updateTime1 = (LocalDateTime) row.getField(1); - assertEquals(createTime, updateTime1); + assertThat(createTime).isEqualTo(updateTime1); - assertEquals(row.getField(2), 1L); // table_version - assertEquals(row.getField(3), 0L); // shard_count - assertEquals(row.getField(4), 0L); // row_count + assertThat(row.getField(2)).isEqualTo(1L); // table_version + assertThat(row.getField(3)).isEqualTo(0L); // shard_count + assertThat(row.getField(4)).isEqualTo(0L); // row_count long size1 = (long) row.getField(5); // uncompressed_size // insert assertUpdate("INSERT INTO test_table_stats VALUES (1), (2), (3), (4)", 4); row = getOnlyElement(computeActual(sql).getMaterializedRows()); - assertEquals(row.getField(0), createTime); + assertThat(row.getField(0)).isEqualTo(createTime); LocalDateTime updateTime2 = (LocalDateTime) row.getField(1); assertLessThan(updateTime1, updateTime2); - assertEquals(row.getField(2), 2L); // table_version + assertThat(row.getField(2)).isEqualTo(2L); // table_version assertGreaterThanOrEqual((Long) row.getField(3), 1L); // shard_count - assertEquals(row.getField(4), 4L); // row_count + assertThat(row.getField(4)).isEqualTo(4L); // row_count long size2 = (long) row.getField(5); // uncompressed_size assertGreaterThan(size2, size1); @@ -847,13 +830,13 @@ public void testTableStatsSystemTable() assertUpdate("DELETE FROM test_table_stats WHERE x IN (2, 4)", 2); row = getOnlyElement(computeActual(sql).getMaterializedRows()); - assertEquals(row.getField(0), createTime); + assertThat(row.getField(0)).isEqualTo(createTime); LocalDateTime updateTime3 = (LocalDateTime) row.getField(1); assertLessThan(updateTime2, updateTime3); - assertEquals(row.getField(2), 3L); // table_version + assertThat(row.getField(2)).isEqualTo(3L); // table_version assertGreaterThanOrEqual((Long) row.getField(3), 1L); // shard_count - assertEquals(row.getField(4), 2L); // row_count + assertThat(row.getField(4)).isEqualTo(2L); // row_count long size3 = (long) row.getField(5); // uncompressed_Size assertLessThan(size3, size2); @@ -861,12 +844,12 @@ public void testTableStatsSystemTable() assertUpdate("ALTER TABLE test_table_stats ADD COLUMN y bigint"); row = getOnlyElement(computeActual(sql).getMaterializedRows()); - assertEquals(row.getField(0), createTime); + assertThat(row.getField(0)).isEqualTo(createTime); assertLessThan(updateTime3, (LocalDateTime) row.getField(1)); - assertEquals(row.getField(2), 4L); // table_version - assertEquals(row.getField(4), 2L); // row_count - assertEquals(row.getField(5), size3); // uncompressed_size + assertThat(row.getField(2)).isEqualTo(4L); // table_version + assertThat(row.getField(4)).isEqualTo(2L); // row_count + assertThat(row.getField(5)).isEqualTo(size3); // uncompressed_size // cleanup assertUpdate("DROP TABLE test_table_stats"); diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorBucketFunction.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorBucketFunction.java index 16342723cd68..1e389c2662fe 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorBucketFunction.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorBucketFunction.java @@ -26,7 +26,7 @@ import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestRaptorBucketFunction { @@ -34,49 +34,49 @@ public class TestRaptorBucketFunction public void testBigint() { BucketFunction function = bucketFunction(50, BIGINT); - assertEquals(getBucket(function, createLongsBlock(123456789012L)), 12); - assertEquals(getBucket(function, createLongsBlock(454345325)), 16); - assertEquals(getBucket(function, createLongsBlock(365363)), 42); - assertEquals(getBucket(function, createLongsBlock(45645747)), 41); - assertEquals(getBucket(function, createLongsBlock(3244)), 29); + assertThat(getBucket(function, createLongsBlock(123456789012L))).isEqualTo(12); + assertThat(getBucket(function, createLongsBlock(454345325))).isEqualTo(16); + assertThat(getBucket(function, createLongsBlock(365363))).isEqualTo(42); + assertThat(getBucket(function, createLongsBlock(45645747))).isEqualTo(41); + assertThat(getBucket(function, createLongsBlock(3244))).isEqualTo(29); function = bucketFunction(2, BIGINT); - assertEquals(getBucket(function, createLongsBlock(123456789012L)), 0); - assertEquals(getBucket(function, createLongsBlock(454345325)), 0); - assertEquals(getBucket(function, createLongsBlock(365363)), 0); - assertEquals(getBucket(function, createLongsBlock(45645747)), 1); - assertEquals(getBucket(function, createLongsBlock(3244)), 1); + assertThat(getBucket(function, createLongsBlock(123456789012L))).isEqualTo(0); + assertThat(getBucket(function, createLongsBlock(454345325))).isEqualTo(0); + assertThat(getBucket(function, createLongsBlock(365363))).isEqualTo(0); + assertThat(getBucket(function, createLongsBlock(45645747))).isEqualTo(1); + assertThat(getBucket(function, createLongsBlock(3244))).isEqualTo(1); } @Test public void testInteger() { BucketFunction function = bucketFunction(50, INTEGER); - assertEquals(getBucket(function, createIntsBlock(454345325)), 16); - assertEquals(getBucket(function, createIntsBlock(365363)), 42); - assertEquals(getBucket(function, createIntsBlock(45645747)), 41); - assertEquals(getBucket(function, createIntsBlock(3244)), 29); + assertThat(getBucket(function, createIntsBlock(454345325))).isEqualTo(16); + assertThat(getBucket(function, createIntsBlock(365363))).isEqualTo(42); + assertThat(getBucket(function, createIntsBlock(45645747))).isEqualTo(41); + assertThat(getBucket(function, createIntsBlock(3244))).isEqualTo(29); } @Test public void testVarchar() { BucketFunction function = bucketFunction(50, createUnboundedVarcharType()); - assertEquals(getBucket(function, createStringsBlock("lorem ipsum")), 2); - assertEquals(getBucket(function, createStringsBlock("lorem")), 26); - assertEquals(getBucket(function, createStringsBlock("ipsum")), 3); - assertEquals(getBucket(function, createStringsBlock("hello")), 19); + assertThat(getBucket(function, createStringsBlock("lorem ipsum"))).isEqualTo(2); + assertThat(getBucket(function, createStringsBlock("lorem"))).isEqualTo(26); + assertThat(getBucket(function, createStringsBlock("ipsum"))).isEqualTo(3); + assertThat(getBucket(function, createStringsBlock("hello"))).isEqualTo(19); } @Test public void testVarcharBigint() { BucketFunction function = bucketFunction(50, createUnboundedVarcharType(), BIGINT); - assertEquals(getBucket(function, createStringsBlock("lorem ipsum"), createLongsBlock(123456789012L)), 24); - assertEquals(getBucket(function, createStringsBlock("lorem"), createLongsBlock(454345325)), 32); - assertEquals(getBucket(function, createStringsBlock("ipsum"), createLongsBlock(365363)), 21); - assertEquals(getBucket(function, createStringsBlock("hello"), createLongsBlock(45645747)), 34); - assertEquals(getBucket(function, createStringsBlock("world"), createLongsBlock(3244)), 4); + assertThat(getBucket(function, createStringsBlock("lorem ipsum"), createLongsBlock(123456789012L))).isEqualTo(24); + assertThat(getBucket(function, createStringsBlock("lorem"), createLongsBlock(454345325))).isEqualTo(32); + assertThat(getBucket(function, createStringsBlock("ipsum"), createLongsBlock(365363))).isEqualTo(21); + assertThat(getBucket(function, createStringsBlock("hello"), createLongsBlock(45645747))).isEqualTo(34); + assertThat(getBucket(function, createStringsBlock("world"), createLongsBlock(3244))).isEqualTo(4); } private static int getBucket(BucketFunction function, Block... blocks) diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorConnector.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorConnector.java index 8e68b678b727..2e73a6855f51 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorConnector.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorConnector.java @@ -71,11 +71,9 @@ import static io.trino.testing.TestingPageSinkId.TESTING_PAGE_SINK_ID; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static io.trino.util.DateTimeUtils.parseDate; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_METHOD) @Execution(SAME_THREAD) @@ -138,50 +136,50 @@ public void testMaintenanceBlocked() long tableId1 = createTable("test1"); long tableId2 = createTable("test2"); - assertFalse(metadataDao.isMaintenanceBlockedLocked(tableId1)); - assertFalse(metadataDao.isMaintenanceBlockedLocked(tableId2)); + assertThat(metadataDao.isMaintenanceBlockedLocked(tableId1)).isFalse(); + assertThat(metadataDao.isMaintenanceBlockedLocked(tableId2)).isFalse(); // begin delete for table1 ConnectorTransactionHandle txn1 = beginTransaction(); ConnectorTableHandle handle1 = getTableHandle(connector.getMetadata(SESSION, txn1), "test1"); connector.getMetadata(SESSION, txn1).beginMerge(SESSION, handle1, NO_RETRIES); - assertTrue(metadataDao.isMaintenanceBlockedLocked(tableId1)); - assertFalse(metadataDao.isMaintenanceBlockedLocked(tableId2)); + assertThat(metadataDao.isMaintenanceBlockedLocked(tableId1)).isTrue(); + assertThat(metadataDao.isMaintenanceBlockedLocked(tableId2)).isFalse(); // begin delete for table2 ConnectorTransactionHandle txn2 = beginTransaction(); ConnectorTableHandle handle2 = getTableHandle(connector.getMetadata(SESSION, txn2), "test2"); connector.getMetadata(SESSION, txn2).beginMerge(SESSION, handle2, NO_RETRIES); - assertTrue(metadataDao.isMaintenanceBlockedLocked(tableId1)); - assertTrue(metadataDao.isMaintenanceBlockedLocked(tableId2)); + assertThat(metadataDao.isMaintenanceBlockedLocked(tableId1)).isTrue(); + assertThat(metadataDao.isMaintenanceBlockedLocked(tableId2)).isTrue(); // begin another delete for table1 ConnectorTransactionHandle txn3 = beginTransaction(); ConnectorTableHandle handle3 = getTableHandle(connector.getMetadata(SESSION, txn3), "test1"); connector.getMetadata(SESSION, txn3).beginMerge(SESSION, handle3, NO_RETRIES); - assertTrue(metadataDao.isMaintenanceBlockedLocked(tableId1)); - assertTrue(metadataDao.isMaintenanceBlockedLocked(tableId2)); + assertThat(metadataDao.isMaintenanceBlockedLocked(tableId1)).isTrue(); + assertThat(metadataDao.isMaintenanceBlockedLocked(tableId2)).isTrue(); // commit first delete for table1 connector.commit(txn1); - assertTrue(metadataDao.isMaintenanceBlockedLocked(tableId1)); - assertTrue(metadataDao.isMaintenanceBlockedLocked(tableId2)); + assertThat(metadataDao.isMaintenanceBlockedLocked(tableId1)).isTrue(); + assertThat(metadataDao.isMaintenanceBlockedLocked(tableId2)).isTrue(); // rollback second delete for table1 connector.rollback(txn3); - assertFalse(metadataDao.isMaintenanceBlockedLocked(tableId1)); - assertTrue(metadataDao.isMaintenanceBlockedLocked(tableId2)); + assertThat(metadataDao.isMaintenanceBlockedLocked(tableId1)).isFalse(); + assertThat(metadataDao.isMaintenanceBlockedLocked(tableId2)).isTrue(); // commit delete for table2 connector.commit(txn2); - assertFalse(metadataDao.isMaintenanceBlockedLocked(tableId1)); - assertFalse(metadataDao.isMaintenanceBlockedLocked(tableId2)); + assertThat(metadataDao.isMaintenanceBlockedLocked(tableId1)).isFalse(); + assertThat(metadataDao.isMaintenanceBlockedLocked(tableId2)).isFalse(); } @Test @@ -189,13 +187,13 @@ public void testMaintenanceUnblockedOnStart() { long tableId = createTable("test"); - assertFalse(metadataDao.isMaintenanceBlockedLocked(tableId)); + assertThat(metadataDao.isMaintenanceBlockedLocked(tableId)).isFalse(); metadataDao.blockMaintenance(tableId); - assertTrue(metadataDao.isMaintenanceBlockedLocked(tableId)); + assertThat(metadataDao.isMaintenanceBlockedLocked(tableId)).isTrue(); connector.start(); - assertFalse(metadataDao.isMaintenanceBlockedLocked(tableId)); + assertThat(metadataDao.isMaintenanceBlockedLocked(tableId)).isFalse(); } @Test @@ -257,7 +255,7 @@ else if (temporalType.equals(DATE)) { raptorPageSink.appendPage(inputPage); Collection shards = raptorPageSink.finish().get(); - assertEquals(shards.size(), expectedSplits); + assertThat(shards.size()).isEqualTo(expectedSplits); connector.getMetadata(session, txn1).dropTable(session, handle1); connector.commit(txn1); } diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/AbstractTestBackupStore.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/AbstractTestBackupStore.java index 57f45c085991..948d4ee61ea7 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/AbstractTestBackupStore.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/AbstractTestBackupStore.java @@ -22,9 +22,7 @@ import static java.nio.file.Files.readAllBytes; import static java.nio.file.Files.writeString; import static java.util.UUID.randomUUID; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public abstract class AbstractTestBackupStore { @@ -40,41 +38,41 @@ public void testBackupStore() writeString(file1.toPath(), "hello world"); UUID uuid1 = randomUUID(); - assertFalse(store.shardExists(uuid1)); + assertThat(store.shardExists(uuid1)).isFalse(); store.backupShard(uuid1, file1); - assertTrue(store.shardExists(uuid1)); + assertThat(store.shardExists(uuid1)).isTrue(); // backup second file File file2 = temporary.resolve("file2").toFile(); writeString(file2.toPath(), "bye bye"); UUID uuid2 = randomUUID(); - assertFalse(store.shardExists(uuid2)); + assertThat(store.shardExists(uuid2)).isFalse(); store.backupShard(uuid2, file2); - assertTrue(store.shardExists(uuid2)); + assertThat(store.shardExists(uuid2)).isTrue(); // verify first file File restore1 = temporary.resolve("restore1").toFile(); store.restoreShard(uuid1, restore1); - assertEquals(readAllBytes(file1.toPath()), readAllBytes(restore1.toPath())); + assertThat(readAllBytes(file1.toPath())).isEqualTo(readAllBytes(restore1.toPath())); // verify second file File restore2 = temporary.resolve("restore2").toFile(); store.restoreShard(uuid2, restore2); - assertEquals(readAllBytes(file2.toPath()), readAllBytes(restore2.toPath())); + assertThat(readAllBytes(file2.toPath())).isEqualTo(readAllBytes(restore2.toPath())); // verify random UUID does not exist - assertFalse(store.shardExists(randomUUID())); + assertThat(store.shardExists(randomUUID())).isFalse(); // delete first file - assertTrue(store.shardExists(uuid1)); - assertTrue(store.shardExists(uuid2)); + assertThat(store.shardExists(uuid1)).isTrue(); + assertThat(store.shardExists(uuid2)).isTrue(); store.deleteShard(uuid1); store.deleteShard(uuid1); - assertFalse(store.shardExists(uuid1)); - assertTrue(store.shardExists(uuid2)); + assertThat(store.shardExists(uuid1)).isFalse(); + assertThat(store.shardExists(uuid2)).isTrue(); // delete random UUID store.deleteShard(randomUUID()); diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestBackupManager.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestBackupManager.java index 7520db6e929e..a93596ab649f 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestBackupManager.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestBackupManager.java @@ -43,12 +43,10 @@ import static java.util.Objects.requireNonNull; import static java.util.UUID.randomUUID; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; -import static org.testng.FileAssert.assertFile; @TestInstance(PER_METHOD) @Execution(SAME_THREAD) @@ -104,7 +102,7 @@ public void testSimple() } futures.forEach(CompletableFuture::join); for (UUID uuid : uuids) { - assertTrue(backupStore.shardExists(uuid)); + assertThat(backupStore.shardExists(uuid)).isTrue(); } assertBackupStats(5, 0, 0); @@ -124,8 +122,8 @@ public void testFailure() assertThatThrownBy(() -> backupManager.submit(FAILURE_UUID, file).get(10, SECONDS)) .isInstanceOfSatisfying(ExecutionException.class, wrapper -> { TrinoException e = (TrinoException) wrapper.getCause(); - assertEquals(e.getErrorCode(), RAPTOR_BACKUP_ERROR.toErrorCode()); - assertEquals(e.getMessage(), "Backup failed for testing"); + assertThat(e.getErrorCode()).isEqualTo(RAPTOR_BACKUP_ERROR.toErrorCode()); + assertThat(e.getMessage()).isEqualTo("Backup failed for testing"); }); assertBackupStats(0, 1, 0); @@ -145,13 +143,13 @@ public void testCorruption() assertThatThrownBy(() -> backupManager.submit(CORRUPTION_UUID, file).get(10, SECONDS)) .isInstanceOfSatisfying(ExecutionException.class, wrapper -> { TrinoException e = (TrinoException) wrapper.getCause(); - assertEquals(e.getErrorCode(), RAPTOR_BACKUP_CORRUPTION.toErrorCode()); - assertEquals(e.getMessage(), "Backup is corrupt after write: " + CORRUPTION_UUID); + assertThat(e.getErrorCode()).isEqualTo(RAPTOR_BACKUP_CORRUPTION.toErrorCode()); + assertThat(e.getMessage()).isEqualTo("Backup is corrupt after write: " + CORRUPTION_UUID); }); File quarantineBase = storageService.getQuarantineFile(CORRUPTION_UUID); - assertFile(new File(quarantineBase.getPath() + ".original")); - assertFile(new File(quarantineBase.getPath() + ".restored")); + assertThat(new File(quarantineBase.getPath() + ".original")).isFile(); + assertThat(new File(quarantineBase.getPath() + ".restored")).isFile(); assertBackupStats(0, 1, 1); assertEmptyStagingDirectory(); @@ -160,15 +158,15 @@ public void testCorruption() private void assertEmptyStagingDirectory() { File staging = storageService.getStagingFile(randomUUID()).getParentFile(); - assertEquals(staging.list(), new String[] {}); + assertThat(staging.list()).isEqualTo(new String[] {}); } private void assertBackupStats(int successCount, int failureCount, int corruptionCount) { BackupStats stats = backupManager.getStats(); - assertEquals(stats.getBackupSuccess().getTotalCount(), successCount); - assertEquals(stats.getBackupFailure().getTotalCount(), failureCount); - assertEquals(stats.getBackupCorruption().getTotalCount(), corruptionCount); + assertThat(stats.getBackupSuccess().getTotalCount()).isEqualTo(successCount); + assertThat(stats.getBackupFailure().getTotalCount()).isEqualTo(failureCount); + assertThat(stats.getBackupCorruption().getTotalCount()).isEqualTo(corruptionCount); } private static class TestingBackupStore diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestFileBackupStore.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestFileBackupStore.java index 66bd6d445abc..6b0060f75160 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestFileBackupStore.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/backup/TestFileBackupStore.java @@ -26,8 +26,8 @@ import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; import static java.lang.String.format; import static java.nio.file.Files.createTempDirectory; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) public class TestFileBackupStore @@ -54,6 +54,6 @@ public void testFilePaths() { UUID uuid = UUID.fromString("701e1a79-74f7-4f56-b438-b41e8e7d019d"); File expected = temporary.resolve("backup").resolve("70").resolve("1e").resolve(format("%s.orc", uuid)).toFile(); - assertEquals(store.getBackupFile(uuid), expected); + assertThat(store.getBackupFile(uuid)).isEqualTo(expected); } } diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestDatabaseShardManager.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestDatabaseShardManager.java index 64a512fbf559..3e4c7341886a 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestDatabaseShardManager.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestDatabaseShardManager.java @@ -92,10 +92,9 @@ import static java.time.ZoneOffset.UTC; import static java.util.concurrent.TimeUnit.DAYS; import static java.util.stream.Collectors.toSet; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; @TestInstance(PER_METHOD) @Execution(SAME_THREAD) @@ -145,7 +144,7 @@ public void testCommit() shardManager.commitShards(transactionId, tableId, columns, shards, Optional.empty(), 0); Set actual = getShardNodes(tableId, TupleDomain.all()); - assertEquals(actual, toShardNodes(shards)); + assertThat(actual).isEqualTo(toShardNodes(shards)); } @Test @@ -179,7 +178,7 @@ public void testAssignShard() shardManager.commitShards(transactionId, tableId, columns, shardNodes, Optional.empty(), 0); ShardNodes actual = getOnlyElement(getShardNodes(tableId, TupleDomain.all())); - assertEquals(actual, new ShardNodes(shard, ImmutableSet.of("node1"))); + assertThat(actual).isEqualTo(new ShardNodes(shard, ImmutableSet.of("node1"))); assertTrinoExceptionThrownBy(() -> shardManager.replaceShardAssignment(tableId, shard, "node2", true)) .hasErrorCode(SERVER_STARTING_UP) @@ -189,13 +188,13 @@ public void testAssignShard() shardManager.replaceShardAssignment(tableId, shard, "node2", false); actual = getOnlyElement(getShardNodes(tableId, TupleDomain.all())); - assertEquals(actual, new ShardNodes(shard, ImmutableSet.of("node2"))); + assertThat(actual).isEqualTo(new ShardNodes(shard, ImmutableSet.of("node2"))); // replacing shard assignment should be idempotent shardManager.replaceShardAssignment(tableId, shard, "node2", false); actual = getOnlyElement(getShardNodes(tableId, TupleDomain.all())); - assertEquals(actual, new ShardNodes(shard, ImmutableSet.of("node2"))); + assertThat(actual).isEqualTo(new ShardNodes(shard, ImmutableSet.of("node2"))); } @Test @@ -216,19 +215,19 @@ public void testGetNodeBytes() long transactionId = shardManager.beginTransaction(); shardManager.commitShards(transactionId, tableId, columns, shardNodes, Optional.empty(), 0); - assertEquals(getShardNodes(tableId, TupleDomain.all()), ImmutableSet.of( + assertThat(getShardNodes(tableId, TupleDomain.all())).isEqualTo(ImmutableSet.of( new ShardNodes(shard1, ImmutableSet.of("node1")), new ShardNodes(shard2, ImmutableSet.of("node1")))); - assertEquals(shardManager.getNodeBytes(), ImmutableMap.of("node1", 88L)); + assertThat(shardManager.getNodeBytes()).isEqualTo(ImmutableMap.of("node1", 88L)); shardManager.replaceShardAssignment(tableId, shard1, "node2", false); - assertEquals(getShardNodes(tableId, TupleDomain.all()), ImmutableSet.of( + assertThat(getShardNodes(tableId, TupleDomain.all())).isEqualTo(ImmutableSet.of( new ShardNodes(shard1, ImmutableSet.of("node2")), new ShardNodes(shard2, ImmutableSet.of("node1")))); - assertEquals(shardManager.getNodeBytes(), ImmutableMap.of("node1", 55L, "node2", 33L)); + assertThat(shardManager.getNodeBytes()).isEqualTo(ImmutableMap.of("node1", 55L, "node2", 33L)); } @Test @@ -255,7 +254,7 @@ public void testGetNodeTableShards() Set shardMetadata = shardManager.getNodeShards(node); Set expectedUuids = ImmutableSet.copyOf(nodeShardMap.get(node)); Set actualUuids = shardMetadata.stream().map(ShardMetadata::getShardUuid).collect(toSet()); - assertEquals(actualUuids, expectedUuids); + assertThat(actualUuids).isEqualTo(expectedUuids); } } @@ -274,7 +273,7 @@ public void testGetExistingShards() shardManager.commitShards(transactionId, tableId, columns, shardNodes, Optional.empty(), 0); Set actual = shardManager.getExistingShardUuids(tableId, ImmutableSet.of(shard1, shard2, UUID.randomUUID())); Set expected = ImmutableSet.of(shard1, shard2); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } @Test @@ -310,7 +309,7 @@ public void testReplaceShardUuids() shardMetadata = shardManager.getNodeShards(nodes.get(0)); Set actualUuids = shardMetadata.stream().map(ShardMetadata::getShardUuid).collect(toSet()); - assertEquals(actualUuids, ImmutableSet.copyOf(expectedUuids)); + assertThat(actualUuids).isEqualTo(ImmutableSet.copyOf(expectedUuids)); // Compute expected all uuids for this table Set expectedAllUuids = new HashSet<>(originalUuids); @@ -324,7 +323,7 @@ public void testReplaceShardUuids() .flatMap(Collection::stream) .map(ShardNodes::getShardUuid) .collect(toSet()); - assertEquals(actualAllUuids, expectedAllUuids); + assertThat(actualAllUuids).isEqualTo(expectedAllUuids); // verify that conflicting updates are handled newShards = ImmutableList.of(shardInfo(UUID.randomUUID(), nodes.get(0))); @@ -376,8 +375,8 @@ public void testBucketAssignments() shardManager.createBuckets(distributionId, bucketCount); List assignments = shardManager.getBucketAssignments(distributionId); - assertEquals(assignments.size(), bucketCount); - assertEquals(ImmutableSet.copyOf(assignments), nodeIds(originalNodes)); + assertThat(assignments.size()).isEqualTo(bucketCount); + assertThat(ImmutableSet.copyOf(assignments)).isEqualTo(nodeIds(originalNodes)); Set newNodes = ImmutableSet.of(node1, node3); shardManager = createShardManager(dbi, () -> newNodes, ticker); @@ -389,15 +388,15 @@ public void testBucketAssignments() ticker.increment(2, DAYS); assignments = shardManager.getBucketAssignments(distributionId); - assertEquals(assignments.size(), bucketCount); - assertEquals(ImmutableSet.copyOf(assignments), nodeIds(newNodes)); + assertThat(assignments.size()).isEqualTo(bucketCount); + assertThat(ImmutableSet.copyOf(assignments)).isEqualTo(nodeIds(newNodes)); Set singleNode = ImmutableSet.of(node1); shardManager = createShardManager(dbi, () -> singleNode, ticker); ticker.increment(2, DAYS); assignments = shardManager.getBucketAssignments(distributionId); - assertEquals(assignments.size(), bucketCount); - assertEquals(ImmutableSet.copyOf(assignments), nodeIds(singleNode)); + assertThat(assignments.size()).isEqualTo(bucketCount); + assertThat(ImmutableSet.copyOf(assignments)).isEqualTo(nodeIds(singleNode)); } @Test @@ -408,7 +407,7 @@ public void testEmptyTable() shardManager.createTable(tableId, columns, false, OptionalLong.empty()); try (ResultIterator iterator = shardManager.getShardNodes(tableId, TupleDomain.all())) { - assertFalse(iterator.hasNext()); + assertThat(iterator.hasNext()).isFalse(); } } @@ -420,7 +419,7 @@ public void testEmptyTableBucketed() shardManager.createTable(tableId, columns, true, OptionalLong.empty()); try (ResultIterator iterator = shardManager.getShardNodesBucketed(tableId, true, ImmutableList.of(), TupleDomain.all())) { - assertFalse(iterator.hasNext()); + assertThat(iterator.hasNext()).isFalse(); } } @@ -654,7 +653,7 @@ public void testAddNewColumn() shardManager.addColumn(tableId, newColumn); int after = columnCount(tableId); // should be 2 more: min and max columns - assertEquals(after, before + 2); + assertThat(after).isEqualTo(before + 2); } @Test @@ -669,7 +668,7 @@ public void testAddDuplicateColumn() shardManager.addColumn(tableId, columns.get(0)); int after = columnCount(tableId); // no error, no columns added - assertEquals(after, before); + assertThat(after).isEqualTo(before); } @Test @@ -783,7 +782,7 @@ public void expected(List shards) { TupleDomain predicate = TupleDomain.withColumnDomains(domains); Set actual = getShardNodes(tableId, predicate); - assertEquals(actual, toShardNodes(shards)); + assertThat(actual).isEqualTo(toShardNodes(shards)); } } diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestMetadataDao.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestMetadataDao.java index 3745933aae04..8e6ed4e46048 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestMetadataDao.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestMetadataDao.java @@ -27,11 +27,9 @@ import static io.trino.plugin.raptor.legacy.DatabaseTesting.createTestingJdbi; import static io.trino.plugin.raptor.legacy.metadata.SchemaDaoUtil.createTablesWithRetry; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; @TestInstance(PER_METHOD) @Execution(SAME_THREAD) @@ -63,16 +61,16 @@ public void testTemporalColumn() long tableId = dao.insertTable("schema1", "table1", true, false, null, 0); dao.insertColumn(tableId, columnId, "col1", 1, "bigint", null, null); Long temporalColumnId = dao.getTemporalColumnId(tableId); - assertNull(temporalColumnId); + assertThat(temporalColumnId).isNull(); dao.updateTemporalColumnId(tableId, columnId); temporalColumnId = dao.getTemporalColumnId(tableId); - assertNotNull(temporalColumnId); - assertEquals(temporalColumnId, columnId); + assertThat(temporalColumnId).isNotNull(); + assertThat(temporalColumnId).isEqualTo(columnId); long tableId2 = dao.insertTable("schema1", "table2", true, false, null, 0); Long columnId2 = dao.getTemporalColumnId(tableId2); - assertNull(columnId2); + assertThat(columnId2).isNull(); } @Test @@ -91,10 +89,10 @@ public void testGetTableInformation() private static void assertTable(Table info, long tableId) { - assertEquals(info.getTableId(), tableId); - assertEquals(info.getDistributionId(), Optional.empty()); - assertEquals(info.getDistributionName(), Optional.empty()); - assertEquals(info.getBucketCount(), OptionalInt.empty()); - assertEquals(info.getTemporalColumnId(), OptionalLong.empty()); + assertThat(info.getTableId()).isEqualTo(tableId); + assertThat(info.getDistributionId()).isEqualTo(Optional.empty()); + assertThat(info.getDistributionName()).isEqualTo(Optional.empty()); + assertThat(info.getBucketCount()).isEqualTo(OptionalInt.empty()); + assertThat(info.getTemporalColumnId()).isEqualTo(OptionalLong.empty()); } } diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestRaptorMetadata.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestRaptorMetadata.java index b7c4515aacda..7adb9a996e0b 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestRaptorMetadata.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestRaptorMetadata.java @@ -77,11 +77,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_METHOD) @Execution(SAME_THREAD) @@ -121,7 +116,7 @@ public void cleanupDatabase() @Test public void testRenameColumn() { - assertNull(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)); + assertThat(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)).isNull(); metadata.createTable(SESSION, getOrdersTable(), false); ConnectorTableHandle tableHandle = metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS); assertInstanceOf(tableHandle, RaptorTableHandle.class); @@ -131,14 +126,14 @@ public void testRenameColumn() metadata.renameColumn(SESSION, raptorTableHandle, columnHandle, "orderkey_renamed"); - assertNull(metadata.getColumnHandles(SESSION, tableHandle).get("orderkey")); - assertNotNull(metadata.getColumnHandles(SESSION, tableHandle).get("orderkey_renamed")); + assertThat(metadata.getColumnHandles(SESSION, tableHandle).get("orderkey")).isNull(); + assertThat(metadata.getColumnHandles(SESSION, tableHandle).get("orderkey_renamed")).isNotNull(); } @Test public void testAddColumn() { - assertNull(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)); + assertThat(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)).isNull(); metadata.createTable(SESSION, buildTable(ImmutableMap.of(), tableMetadataBuilder(DEFAULT_TEST_ORDERS) .column("orderkey", BIGINT) .column("price", BIGINT)), @@ -149,13 +144,13 @@ public void testAddColumn() RaptorTableHandle raptorTableHandle = (RaptorTableHandle) tableHandle; metadata.addColumn(SESSION, raptorTableHandle, new ColumnMetadata("new_col", BIGINT)); - assertNotNull(metadata.getColumnHandles(SESSION, raptorTableHandle).get("new_col")); + assertThat(metadata.getColumnHandles(SESSION, raptorTableHandle).get("new_col")).isNotNull(); } @Test public void testDropColumn() { - assertNull(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)); + assertThat(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)).isNull(); metadata.createTable(SESSION, buildTable(ImmutableMap.of(), tableMetadataBuilder(DEFAULT_TEST_ORDERS) .column("orderkey", BIGINT) .column("price", BIGINT)), @@ -167,13 +162,13 @@ public void testDropColumn() ColumnHandle lastColumn = metadata.getColumnHandles(SESSION, tableHandle).get("orderkey"); metadata.dropColumn(SESSION, raptorTableHandle, lastColumn); - assertNull(metadata.getColumnHandles(SESSION, tableHandle).get("orderkey")); + assertThat(metadata.getColumnHandles(SESSION, tableHandle).get("orderkey")).isNull(); } @Test public void testAddColumnAfterDropColumn() { - assertNull(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)); + assertThat(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)).isNull(); metadata.createTable(SESSION, buildTable(ImmutableMap.of(), tableMetadataBuilder(DEFAULT_TEST_ORDERS) .column("orderkey", BIGINT) .column("price", BIGINT)), @@ -186,14 +181,14 @@ public void testAddColumnAfterDropColumn() metadata.dropColumn(SESSION, raptorTableHandle, column); metadata.addColumn(SESSION, raptorTableHandle, new ColumnMetadata("new_col", BIGINT)); - assertNull(metadata.getColumnHandles(SESSION, tableHandle).get("orderkey")); - assertNotNull(metadata.getColumnHandles(SESSION, raptorTableHandle).get("new_col")); + assertThat(metadata.getColumnHandles(SESSION, tableHandle).get("orderkey")).isNull(); + assertThat(metadata.getColumnHandles(SESSION, raptorTableHandle).get("new_col")).isNotNull(); } @Test public void testDropColumnDisallowed() { - assertNull(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)); + assertThat(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)).isNull(); Map properties = ImmutableMap.of( BUCKET_COUNT_PROPERTY, 16, BUCKETED_ON_PROPERTY, ImmutableList.of("orderkey"), @@ -209,7 +204,7 @@ public void testDropColumnDisallowed() ConnectorTableHandle ordersTableHandle = metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS); assertInstanceOf(ordersTableHandle, RaptorTableHandle.class); RaptorTableHandle ordersRaptorTableHandle = (RaptorTableHandle) ordersTableHandle; - assertEquals(ordersRaptorTableHandle.getTableId(), 1); + assertThat(ordersRaptorTableHandle.getTableId()).isEqualTo(1); assertInstanceOf(ordersRaptorTableHandle, RaptorTableHandle.class); @@ -238,7 +233,7 @@ public void testDropColumnDisallowed() @Test public void testRenameTable() { - assertNull(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)); + assertThat(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)).isNull(); metadata.createTable(SESSION, getOrdersTable(), false); ConnectorTableHandle tableHandle = metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS); assertInstanceOf(tableHandle, RaptorTableHandle.class); @@ -247,40 +242,40 @@ public void testRenameTable() SchemaTableName renamedTable = new SchemaTableName(raptorTableHandle.getSchemaName(), "orders_renamed"); metadata.renameTable(SESSION, raptorTableHandle, renamedTable); - assertNull(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)); + assertThat(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)).isNull(); ConnectorTableHandle renamedTableHandle = metadata.getTableHandle(SESSION, renamedTable); - assertNotNull(renamedTableHandle); - assertEquals(((RaptorTableHandle) renamedTableHandle).getTableName(), renamedTable.getTableName()); + assertThat(renamedTableHandle).isNotNull(); + assertThat(((RaptorTableHandle) renamedTableHandle).getTableName()).isEqualTo(renamedTable.getTableName()); } @Test public void testCreateTable() { - assertNull(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)); + assertThat(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)).isNull(); metadata.createTable(SESSION, getOrdersTable(), false); ConnectorTableHandle tableHandle = metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS); assertInstanceOf(tableHandle, RaptorTableHandle.class); - assertEquals(((RaptorTableHandle) tableHandle).getTableId(), 1); + assertThat(((RaptorTableHandle) tableHandle).getTableId()).isEqualTo(1); ConnectorTableMetadata table = metadata.getTableMetadata(SESSION, tableHandle); assertTableEqual(table, getOrdersTable()); ColumnHandle columnHandle = metadata.getColumnHandles(SESSION, tableHandle).get("orderkey"); assertInstanceOf(columnHandle, RaptorColumnHandle.class); - assertEquals(((RaptorColumnHandle) columnHandle).getColumnId(), 1); + assertThat(((RaptorColumnHandle) columnHandle).getColumnId()).isEqualTo(1); ColumnMetadata columnMetadata = metadata.getColumnMetadata(SESSION, tableHandle, columnHandle); - assertNotNull(columnMetadata); - assertEquals(columnMetadata.getName(), "orderkey"); - assertEquals(columnMetadata.getType(), BIGINT); + assertThat(columnMetadata).isNotNull(); + assertThat(columnMetadata.getName()).isEqualTo("orderkey"); + assertThat(columnMetadata.getType()).isEqualTo(BIGINT); } @Test public void testTableProperties() { - assertNull(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)); + assertThat(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)).isNull(); ConnectorTableMetadata ordersTable = getOrdersTable(ImmutableMap.of( ORDERING_PROPERTY, ImmutableList.of("orderdate", "custkey"), @@ -290,7 +285,7 @@ public void testTableProperties() ConnectorTableHandle tableHandle = metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS); assertInstanceOf(tableHandle, RaptorTableHandle.class); RaptorTableHandle raptorTableHandle = (RaptorTableHandle) tableHandle; - assertEquals(raptorTableHandle.getTableId(), 1); + assertThat(raptorTableHandle.getTableId()).isEqualTo(1); long tableId = raptorTableHandle.getTableId(); MetadataDao metadataDao = dbi.onDemand(MetadataDao.class); @@ -302,10 +297,10 @@ public void testTableProperties() new TableColumn(DEFAULT_TEST_ORDERS, "custkey", BIGINT, 2, 1, OptionalInt.empty(), OptionalInt.of(1), false))); // verify temporal column - assertEquals(metadataDao.getTemporalColumnId(tableId), Long.valueOf(4)); + assertThat(metadataDao.getTemporalColumnId(tableId)).isEqualTo(Long.valueOf(4)); // verify no organization - assertFalse(metadataDao.getTableInformation(tableId).isOrganized()); + assertThat(metadataDao.getTableInformation(tableId).isOrganized()).isFalse(); metadata.dropTable(SESSION, tableHandle); } @@ -313,7 +308,7 @@ public void testTableProperties() @Test public void testTablePropertiesWithOrganization() { - assertNull(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)); + assertThat(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)).isNull(); ConnectorTableMetadata ordersTable = getOrdersTable(ImmutableMap.of( ORDERING_PROPERTY, ImmutableList.of("orderdate", "custkey"), @@ -323,7 +318,7 @@ public void testTablePropertiesWithOrganization() ConnectorTableHandle tableHandle = metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS); assertInstanceOf(tableHandle, RaptorTableHandle.class); RaptorTableHandle raptorTableHandle = (RaptorTableHandle) tableHandle; - assertEquals(raptorTableHandle.getTableId(), 1); + assertThat(raptorTableHandle.getTableId()).isEqualTo(1); long tableId = raptorTableHandle.getTableId(); MetadataDao metadataDao = dbi.onDemand(MetadataDao.class); @@ -335,7 +330,7 @@ public void testTablePropertiesWithOrganization() new TableColumn(DEFAULT_TEST_ORDERS, "custkey", BIGINT, 2, 1, OptionalInt.empty(), OptionalInt.of(1), false))); // verify organization - assertTrue(metadataDao.getTableInformation(tableId).isOrganized()); + assertThat(metadataDao.getTableInformation(tableId).isOrganized()).isTrue(); metadata.dropTable(SESSION, tableHandle); } @@ -343,7 +338,7 @@ public void testTablePropertiesWithOrganization() @Test public void testCreateBucketedTable() { - assertNull(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)); + assertThat(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)).isNull(); ConnectorTableMetadata ordersTable = getOrdersTable(ImmutableMap.of( BUCKET_COUNT_PROPERTY, 16, @@ -353,7 +348,7 @@ public void testCreateBucketedTable() ConnectorTableHandle tableHandle = metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS); assertInstanceOf(tableHandle, RaptorTableHandle.class); RaptorTableHandle raptorTableHandle = (RaptorTableHandle) tableHandle; - assertEquals(raptorTableHandle.getTableId(), 1); + assertThat(raptorTableHandle.getTableId()).isEqualTo(1); long tableId = raptorTableHandle.getTableId(); MetadataDao metadataDao = dbi.onDemand(MetadataDao.class); @@ -362,34 +357,34 @@ public void testCreateBucketedTable() new TableColumn(DEFAULT_TEST_ORDERS, "custkey", BIGINT, 2, 1, OptionalInt.of(0), OptionalInt.empty(), false), new TableColumn(DEFAULT_TEST_ORDERS, "orderkey", BIGINT, 1, 0, OptionalInt.of(1), OptionalInt.empty(), false))); - assertEquals(raptorTableHandle.getBucketCount(), OptionalInt.of(16)); + assertThat(raptorTableHandle.getBucketCount()).isEqualTo(OptionalInt.of(16)); - assertEquals(getTableDistributionId(tableId), Long.valueOf(1)); + assertThat(getTableDistributionId(tableId)).isEqualTo(Long.valueOf(1)); metadata.dropTable(SESSION, tableHandle); // create a new table and verify it has a different distribution metadata.createTable(SESSION, ordersTable, false); tableId = ((RaptorTableHandle) metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)).getTableId(); - assertEquals(tableId, 2); - assertEquals(getTableDistributionId(tableId), Long.valueOf(2)); + assertThat(tableId).isEqualTo(2); + assertThat(getTableDistributionId(tableId)).isEqualTo(Long.valueOf(2)); } @Test public void testCreateBucketedTableAsSelect() { - assertNull(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)); + assertThat(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)).isNull(); ConnectorTableMetadata ordersTable = getOrdersTable(ImmutableMap.of( BUCKET_COUNT_PROPERTY, 32, BUCKETED_ON_PROPERTY, ImmutableList.of("orderkey", "custkey"))); ConnectorTableLayout layout = metadata.getNewTableLayout(SESSION, ordersTable).get(); - assertEquals(layout.getPartitionColumns(), ImmutableList.of("orderkey", "custkey")); - assertTrue(layout.getPartitioning().isPresent()); + assertThat(layout.getPartitionColumns()).isEqualTo(ImmutableList.of("orderkey", "custkey")); + assertThat(layout.getPartitioning().isPresent()).isTrue(); assertInstanceOf(layout.getPartitioning().get(), RaptorPartitioningHandle.class); RaptorPartitioningHandle partitioning = (RaptorPartitioningHandle) layout.getPartitioning().get(); - assertEquals(partitioning.getDistributionId(), 1); + assertThat(partitioning.getDistributionId()).isEqualTo(1); ConnectorOutputTableHandle outputHandle = metadata.beginCreateTable(SESSION, ordersTable, Optional.of(layout), NO_RETRIES); metadata.finishCreateTable(SESSION, outputHandle, ImmutableList.of(), ImmutableList.of()); @@ -397,7 +392,7 @@ public void testCreateBucketedTableAsSelect() ConnectorTableHandle tableHandle = metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS); assertInstanceOf(tableHandle, RaptorTableHandle.class); RaptorTableHandle raptorTableHandle = (RaptorTableHandle) tableHandle; - assertEquals(raptorTableHandle.getTableId(), 1); + assertThat(raptorTableHandle.getTableId()).isEqualTo(1); long tableId = raptorTableHandle.getTableId(); MetadataDao metadataDao = dbi.onDemand(MetadataDao.class); @@ -406,9 +401,9 @@ public void testCreateBucketedTableAsSelect() new TableColumn(DEFAULT_TEST_ORDERS, "orderkey", BIGINT, 1, 0, OptionalInt.of(0), OptionalInt.empty(), false), new TableColumn(DEFAULT_TEST_ORDERS, "custkey", BIGINT, 2, 1, OptionalInt.of(1), OptionalInt.empty(), false))); - assertEquals(raptorTableHandle.getBucketCount(), OptionalInt.of(32)); + assertThat(raptorTableHandle.getBucketCount()).isEqualTo(OptionalInt.of(32)); - assertEquals(getTableDistributionId(tableId), Long.valueOf(1)); + assertThat(getTableDistributionId(tableId)).isEqualTo(Long.valueOf(1)); metadata.dropTable(SESSION, tableHandle); } @@ -419,7 +414,7 @@ public void testCreateBucketedTableExistingDistribution() MetadataDao metadataDao = dbi.onDemand(MetadataDao.class); // create orders table - assertNull(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)); + assertThat(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)).isNull(); ConnectorTableMetadata table = getOrdersTable(ImmutableMap.of( BUCKET_COUNT_PROPERTY, 16, @@ -432,17 +427,17 @@ public void testCreateBucketedTableExistingDistribution() RaptorTableHandle raptorTableHandle = (RaptorTableHandle) tableHandle; long tableId = raptorTableHandle.getTableId(); - assertEquals(raptorTableHandle.getTableId(), 1); + assertThat(raptorTableHandle.getTableId()).isEqualTo(1); assertTableColumnsEqual(metadataDao.listBucketColumns(tableId), ImmutableList.of( new TableColumn(DEFAULT_TEST_ORDERS, "orderkey", BIGINT, 1, 0, OptionalInt.of(0), OptionalInt.empty(), false))); - assertEquals(raptorTableHandle.getBucketCount(), OptionalInt.of(16)); + assertThat(raptorTableHandle.getBucketCount()).isEqualTo(OptionalInt.of(16)); - assertEquals(getTableDistributionId(tableId), Long.valueOf(1)); + assertThat(getTableDistributionId(tableId)).isEqualTo(Long.valueOf(1)); // create lineitems table - assertNull(metadata.getTableHandle(SESSION, DEFAULT_TEST_LINEITEMS)); + assertThat(metadata.getTableHandle(SESSION, DEFAULT_TEST_LINEITEMS)).isNull(); table = getLineItemsTable(ImmutableMap.of( BUCKET_COUNT_PROPERTY, 16, @@ -455,20 +450,20 @@ public void testCreateBucketedTableExistingDistribution() raptorTableHandle = (RaptorTableHandle) tableHandle; tableId = raptorTableHandle.getTableId(); - assertEquals(tableId, 2); + assertThat(tableId).isEqualTo(2); assertTableColumnsEqual(metadataDao.listBucketColumns(tableId), ImmutableList.of( new TableColumn(DEFAULT_TEST_LINEITEMS, "orderkey", BIGINT, 1, 0, OptionalInt.of(0), OptionalInt.empty(), false))); - assertEquals(raptorTableHandle.getBucketCount(), OptionalInt.of(16)); + assertThat(raptorTableHandle.getBucketCount()).isEqualTo(OptionalInt.of(16)); - assertEquals(getTableDistributionId(tableId), Long.valueOf(1)); + assertThat(getTableDistributionId(tableId)).isEqualTo(Long.valueOf(1)); } @Test public void testInvalidOrderingColumns() { - assertNull(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)); + assertThat(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)).isNull(); assertThatThrownBy(() -> metadata.createTable(SESSION, getOrdersTable(ImmutableMap.of(ORDERING_PROPERTY, ImmutableList.of("orderdatefoo"))), false)) .isInstanceOf(TrinoException.class) @@ -478,7 +473,7 @@ public void testInvalidOrderingColumns() @Test public void testInvalidTemporalColumn() { - assertNull(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)); + assertThat(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)).isNull(); assertThatThrownBy(() -> metadata.createTable(SESSION, getOrdersTable(ImmutableMap.of(TEMPORAL_COLUMN_PROPERTY, "foo")), false)) .isInstanceOf(TrinoException.class) @@ -488,7 +483,7 @@ public void testInvalidTemporalColumn() @Test public void testInvalidTemporalColumnType() { - assertNull(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)); + assertThat(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)).isNull(); assertThatThrownBy(() -> metadata.createTable(SESSION, getOrdersTable(ImmutableMap.of(TEMPORAL_COLUMN_PROPERTY, "orderkey")), false)) .isInstanceOf(TrinoException.class) .hasMessage("Temporal column must be of type timestamp or date: orderkey"); @@ -497,7 +492,7 @@ public void testInvalidTemporalColumnType() @Test public void testInvalidTemporalOrganization() { - assertNull(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)); + assertThat(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)).isNull(); assertThatThrownBy(() -> metadata.createTable(SESSION, getOrdersTable(ImmutableMap.of( TEMPORAL_COLUMN_PROPERTY, "orderdate", ORGANIZED_PROPERTY, true)), @@ -509,7 +504,7 @@ public void testInvalidTemporalOrganization() @Test public void testInvalidOrderingOrganization() { - assertNull(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)); + assertThat(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)).isNull(); assertThatThrownBy(() -> metadata.createTable(SESSION, getOrdersTable(ImmutableMap.of(ORGANIZED_PROPERTY, true)), false)) .isInstanceOf(TrinoException.class) .hasMessage("Table organization requires an ordering"); @@ -518,7 +513,7 @@ public void testInvalidOrderingOrganization() @Test public void testSortOrderProperty() { - assertNull(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)); + assertThat(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)).isNull(); ConnectorTableMetadata ordersTable = getOrdersTable(ImmutableMap.of(ORDERING_PROPERTY, ImmutableList.of("orderdate", "custkey"))); metadata.createTable(SESSION, ordersTable, false); @@ -526,7 +521,7 @@ public void testSortOrderProperty() ConnectorTableHandle tableHandle = metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS); assertInstanceOf(tableHandle, RaptorTableHandle.class); RaptorTableHandle raptorTableHandle = (RaptorTableHandle) tableHandle; - assertEquals(raptorTableHandle.getTableId(), 1); + assertThat(raptorTableHandle.getTableId()).isEqualTo(1); long tableId = raptorTableHandle.getTableId(); MetadataDao metadataDao = dbi.onDemand(MetadataDao.class); @@ -538,14 +533,14 @@ public void testSortOrderProperty() new TableColumn(DEFAULT_TEST_ORDERS, "custkey", BIGINT, 2, 1, OptionalInt.empty(), OptionalInt.of(1), false))); // verify temporal column is not set - assertEquals(metadataDao.getTemporalColumnId(tableId), null); + assertThat(metadataDao.getTemporalColumnId(tableId)).isEqualTo(null); metadata.dropTable(SESSION, tableHandle); } @Test public void testTemporalColumn() { - assertNull(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)); + assertThat(metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS)).isNull(); ConnectorTableMetadata ordersTable = getOrdersTable(ImmutableMap.of(TEMPORAL_COLUMN_PROPERTY, "orderdate")); metadata.createTable(SESSION, ordersTable, false); @@ -553,18 +548,18 @@ public void testTemporalColumn() ConnectorTableHandle tableHandle = metadata.getTableHandle(SESSION, DEFAULT_TEST_ORDERS); assertInstanceOf(tableHandle, RaptorTableHandle.class); RaptorTableHandle raptorTableHandle = (RaptorTableHandle) tableHandle; - assertEquals(raptorTableHandle.getTableId(), 1); + assertThat(raptorTableHandle.getTableId()).isEqualTo(1); long tableId = raptorTableHandle.getTableId(); MetadataDao metadataDao = dbi.onDemand(MetadataDao.class); // verify sort columns are not set List sortColumns = metadataDao.listSortColumns(tableId); - assertEquals(sortColumns.size(), 0); - assertEquals(sortColumns, ImmutableList.of()); + assertThat(sortColumns.size()).isEqualTo(0); + assertThat(sortColumns).isEqualTo(ImmutableList.of()); // verify temporal column is set - assertEquals(metadataDao.getTemporalColumnId(tableId), Long.valueOf(4)); + assertThat(metadataDao.getTemporalColumnId(tableId)).isEqualTo(Long.valueOf(4)); metadata.dropTable(SESSION, tableHandle); } @@ -573,7 +568,7 @@ public void testListTables() { metadata.createTable(SESSION, getOrdersTable(), false); List tables = metadata.listTables(SESSION, Optional.empty()); - assertEquals(tables, ImmutableList.of(DEFAULT_TEST_ORDERS)); + assertThat(tables).isEqualTo(ImmutableList.of(DEFAULT_TEST_ORDERS)); } @Test @@ -581,7 +576,7 @@ public void testListTableColumns() { metadata.createTable(SESSION, getOrdersTable(), false); Map> columns = metadata.listTableColumns(SESSION, new SchemaTablePrefix()); - assertEquals(columns, ImmutableMap.of(DEFAULT_TEST_ORDERS, getOrdersTable().getColumns())); + assertThat(columns).isEqualTo(ImmutableMap.of(DEFAULT_TEST_ORDERS, getOrdersTable().getColumns())); } @Test @@ -591,8 +586,8 @@ public void testListTableColumnsFiltering() Map> filterCatalog = metadata.listTableColumns(SESSION, new SchemaTablePrefix()); Map> filterSchema = metadata.listTableColumns(SESSION, new SchemaTablePrefix("test")); Map> filterTable = metadata.listTableColumns(SESSION, new SchemaTablePrefix("test", "orders")); - assertEquals(filterCatalog, filterSchema); - assertEquals(filterCatalog, filterTable); + assertThat(filterCatalog).isEqualTo(filterSchema); + assertThat(filterCatalog).isEqualTo(filterTable); } @Test @@ -611,9 +606,9 @@ public void testViews() // verify getting data Map views = metadata.getViews(SESSION, Optional.of("test")); - assertEquals(views.keySet(), ImmutableSet.of(test1, test2)); - assertEquals(views.get(test1).getOriginalSql(), "test1"); - assertEquals(views.get(test2).getOriginalSql(), "test2"); + assertThat(views.keySet()).isEqualTo(ImmutableSet.of(test1, test2)); + assertThat(views.get(test1).getOriginalSql()).isEqualTo("test1"); + assertThat(views.get(test2).getOriginalSql()).isEqualTo("test2"); // drop first view metadata.dropView(SESSION, test1); @@ -663,13 +658,13 @@ public void testTransactionTableWrite() ConnectorOutputTableHandle outputHandle = metadata.beginCreateTable(SESSION, getOrdersTable(), Optional.empty(), NO_RETRIES); // transaction is in progress - assertTrue(transactionExists(transactionId)); - assertNull(transactionSuccessful(transactionId)); + assertThat(transactionExists(transactionId)).isTrue(); + assertThat(transactionSuccessful(transactionId)).isNull(); // commit table creation metadata.finishCreateTable(SESSION, outputHandle, ImmutableList.of(), ImmutableList.of()); - assertTrue(transactionExists(transactionId)); - assertTrue(transactionSuccessful(transactionId)); + assertThat(transactionExists(transactionId)).isTrue(); + assertThat(transactionSuccessful(transactionId)).isTrue(); } @Test @@ -678,7 +673,7 @@ public void testTransactionInsert() // creating a table allocates a transaction long transactionId = 1; metadata.createTable(SESSION, getOrdersTable(), false); - assertTrue(transactionSuccessful(transactionId)); + assertThat(transactionSuccessful(transactionId)).isTrue(); // start insert transactionId++; @@ -686,13 +681,13 @@ public void testTransactionInsert() ConnectorInsertTableHandle insertHandle = metadata.beginInsert(SESSION, tableHandle, ImmutableList.of(), NO_RETRIES); // transaction is in progress - assertTrue(transactionExists(transactionId)); - assertNull(transactionSuccessful(transactionId)); + assertThat(transactionExists(transactionId)).isTrue(); + assertThat(transactionSuccessful(transactionId)).isNull(); // commit insert metadata.finishInsert(SESSION, insertHandle, ImmutableList.of(), ImmutableList.of()); - assertTrue(transactionExists(transactionId)); - assertTrue(transactionSuccessful(transactionId)); + assertThat(transactionExists(transactionId)).isTrue(); + assertThat(transactionSuccessful(transactionId)).isTrue(); } @Test @@ -703,13 +698,13 @@ public void testTransactionAbort() ConnectorOutputTableHandle outputHandle = metadata.beginCreateTable(SESSION, getOrdersTable(), Optional.empty(), NO_RETRIES); // transaction is in progress - assertTrue(transactionExists(transactionId)); - assertNull(transactionSuccessful(transactionId)); + assertThat(transactionExists(transactionId)).isTrue(); + assertThat(transactionSuccessful(transactionId)).isNull(); // force transaction to abort shardManager.rollbackTransaction(transactionId); - assertTrue(transactionExists(transactionId)); - assertFalse(transactionSuccessful(transactionId)); + assertThat(transactionExists(transactionId)).isTrue(); + assertThat(transactionSuccessful(transactionId)).isFalse(); // commit table creation assertTrinoExceptionThrownBy(() -> metadata.finishCreateTable(SESSION, outputHandle, ImmutableList.of(), ImmutableList.of())) @@ -791,38 +786,38 @@ private static ConnectorViewDefinition testingViewDefinition(String sql) private static void assertTableEqual(ConnectorTableMetadata actual, ConnectorTableMetadata expected) { - assertEquals(actual.getTable(), expected.getTable()); + assertThat(actual.getTable()).isEqualTo(expected.getTable()); List actualColumns = actual.getColumns().stream() .filter(columnMetadata -> !columnMetadata.isHidden()) .collect(Collectors.toList()); List expectedColumns = expected.getColumns(); - assertEquals(actualColumns.size(), expectedColumns.size()); + assertThat(actualColumns.size()).isEqualTo(expectedColumns.size()); for (int i = 0; i < actualColumns.size(); i++) { ColumnMetadata actualColumn = actualColumns.get(i); ColumnMetadata expectedColumn = expectedColumns.get(i); - assertEquals(actualColumn.getName(), expectedColumn.getName()); - assertEquals(actualColumn.getType(), expectedColumn.getType()); + assertThat(actualColumn.getName()).isEqualTo(expectedColumn.getName()); + assertThat(actualColumn.getType()).isEqualTo(expectedColumn.getType()); } - assertEquals(actual.getProperties(), expected.getProperties()); + assertThat(actual.getProperties()).isEqualTo(expected.getProperties()); } private static void assertTableColumnEqual(TableColumn actual, TableColumn expected) { - assertEquals(actual.getTable(), expected.getTable()); - assertEquals(actual.getColumnId(), expected.getColumnId()); - assertEquals(actual.getColumnName(), expected.getColumnName()); - assertEquals(actual.getDataType(), expected.getDataType()); - assertEquals(actual.getOrdinalPosition(), expected.getOrdinalPosition()); - assertEquals(actual.getBucketOrdinal(), expected.getBucketOrdinal()); - assertEquals(actual.getSortOrdinal(), expected.getSortOrdinal()); - assertEquals(actual.isTemporal(), expected.isTemporal()); + assertThat(actual.getTable()).isEqualTo(expected.getTable()); + assertThat(actual.getColumnId()).isEqualTo(expected.getColumnId()); + assertThat(actual.getColumnName()).isEqualTo(expected.getColumnName()); + assertThat(actual.getDataType()).isEqualTo(expected.getDataType()); + assertThat(actual.getOrdinalPosition()).isEqualTo(expected.getOrdinalPosition()); + assertThat(actual.getBucketOrdinal()).isEqualTo(expected.getBucketOrdinal()); + assertThat(actual.getSortOrdinal()).isEqualTo(expected.getSortOrdinal()); + assertThat(actual.isTemporal()).isEqualTo(expected.isTemporal()); } private static void assertTableColumnsEqual(List actual, List expected) { - assertEquals(actual.size(), expected.size()); + assertThat(actual.size()).isEqualTo(expected.size()); for (int i = 0; i < actual.size(); i++) { assertTableColumnEqual(actual.get(i), expected.get(i)); } diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestRaptorSplitManager.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestRaptorSplitManager.java index 0d038ef3e243..5c794cd998d5 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestRaptorSplitManager.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestRaptorSplitManager.java @@ -68,10 +68,10 @@ import static java.nio.file.Files.createTempDirectory; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; @TestInstance(PER_METHOD) @Execution(SAME_THREAD) @@ -150,7 +150,7 @@ public void testSanity() while (!splitSource.isFinished()) { splitCount += getSplits(splitSource, 1000).size(); } - assertEquals(splitCount, 4); + assertThat(splitCount).isEqualTo(4); } @Test @@ -180,7 +180,7 @@ public void testAssignRandomNodeWhenBackupAvailable() ConnectorSplitSource partitionSplit = getSplits(raptorSplitManagerWithBackup, tableHandle); List batch = getSplits(partitionSplit, 1); - assertEquals(getOnlyElement(getOnlyElement(batch).getAddresses()), node.getHostAndPort()); + assertThat(getOnlyElement(getOnlyElement(batch).getAddresses())).isEqualTo(node.getHostAndPort()); } @Test diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardCleaner.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardCleaner.java index e52c35c1b54c..8e174bc1b535 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardCleaner.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardCleaner.java @@ -55,11 +55,9 @@ import static java.util.UUID.randomUUID; import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_METHOD) @Execution(SAME_THREAD) @@ -133,7 +131,7 @@ public void testAbortOldTransactions() long txn3 = dao.insertTransaction(new Timestamp(now)); ShardDao shardDao = dbi.onDemand(ShardDao.class); - assertEquals(shardDao.finalizeTransaction(txn1, true), 1); + assertThat(shardDao.finalizeTransaction(txn1, true)).isEqualTo(1); assertQuery("SELECT transaction_id, successful FROM transactions", row(txn1, true), @@ -151,7 +149,7 @@ public void testAbortOldTransactions() @Test public void testDeleteOldShards() { - assertEquals(cleaner.getBackupShardsQueued().getTotalCount(), 0); + assertThat(cleaner.getBackupShardsQueued().getTotalCount()).isEqualTo(0); ShardDao dao = dbi.onDemand(ShardDao.class); @@ -161,7 +159,7 @@ public void testDeleteOldShards() // shards for failed transaction long txn1 = dao.insertTransaction(); - assertEquals(dao.finalizeTransaction(txn1, false), 1); + assertThat(dao.finalizeTransaction(txn1, false)).isEqualTo(1); dao.insertCreatedShard(shard1, txn1); dao.insertCreatedShard(shard2, txn1); @@ -182,7 +180,7 @@ public void testDeleteOldShards() // move shards for failed transaction to deleted cleaner.deleteOldShards(); - assertEquals(cleaner.getBackupShardsQueued().getTotalCount(), 2); + assertThat(cleaner.getBackupShardsQueued().getTotalCount()).isEqualTo(2); // verify database assertQuery("SELECT shard_uuid, transaction_id FROM created_shards", @@ -197,7 +195,7 @@ public void testDeleteOldShards() public void testCleanLocalShardsImmediately() throws Exception { - assertEquals(cleaner.getLocalShardsCleaned().getTotalCount(), 0); + assertThat(cleaner.getLocalShardsCleaned().getTotalCount()).isEqualTo(0); TestingShardDao shardDao = dbi.onDemand(TestingShardDao.class); MetadataDao metadataDao = dbi.onDemand(MetadataDao.class); @@ -212,7 +210,7 @@ public void testCleanLocalShardsImmediately() for (UUID shard : shards) { shardDao.insertShard(shard, tableId, null, 0, 0, 0, 0); createShardFile(shard); - assertTrue(shardFileExists(shard)); + assertThat(shardFileExists(shard)).isTrue(); } int node1 = shardDao.insertNode("node1"); @@ -229,20 +227,20 @@ public void testCleanLocalShardsImmediately() Set local = cleaner.getLocalShards(); cleaner.cleanLocalShardsImmediately(local); - assertEquals(cleaner.getLocalShardsCleaned().getTotalCount(), 2); + assertThat(cleaner.getLocalShardsCleaned().getTotalCount()).isEqualTo(2); // shards 2 and 3 should be deleted // shard 1 is referenced by this node - assertTrue(shardFileExists(shard1)); - assertFalse(shardFileExists(shard2)); - assertFalse(shardFileExists(shard3)); + assertThat(shardFileExists(shard1)).isTrue(); + assertThat(shardFileExists(shard2)).isFalse(); + assertThat(shardFileExists(shard3)).isFalse(); } @Test public void testCleanLocalShards() throws Exception { - assertEquals(cleaner.getLocalShardsCleaned().getTotalCount(), 0); + assertThat(cleaner.getLocalShardsCleaned().getTotalCount()).isEqualTo(0); TestingShardDao shardDao = dbi.onDemand(TestingShardDao.class); MetadataDao metadataDao = dbi.onDemand(MetadataDao.class); @@ -259,7 +257,7 @@ public void testCleanLocalShards() for (UUID shard : shards) { shardDao.insertShard(shard, tableId, null, 0, 0, 0, 0); createShardFile(shard); - assertTrue(shardFileExists(shard)); + assertThat(shardFileExists(shard)).isTrue(); } int node1 = shardDao.insertNode("node1"); @@ -276,11 +274,11 @@ public void testCleanLocalShards() // mark unreferenced shards cleaner.cleanLocalShards(); - assertEquals(cleaner.getLocalShardsCleaned().getTotalCount(), 0); + assertThat(cleaner.getLocalShardsCleaned().getTotalCount()).isEqualTo(0); // make sure nothing is deleted for (UUID shard : shards) { - assertTrue(shardFileExists(shard)); + assertThat(shardFileExists(shard)).isTrue(); } // add reference for shard 3 @@ -293,21 +291,21 @@ public void testCleanLocalShards() // clean shards cleaner.cleanLocalShards(); - assertEquals(cleaner.getLocalShardsCleaned().getTotalCount(), 2); + assertThat(cleaner.getLocalShardsCleaned().getTotalCount()).isEqualTo(2); // shards 2 and 4 should be deleted // shards 1 and 3 are referenced by this node - assertTrue(shardFileExists(shard1)); - assertFalse(shardFileExists(shard2)); - assertTrue(shardFileExists(shard3)); - assertFalse(shardFileExists(shard4)); + assertThat(shardFileExists(shard1)).isTrue(); + assertThat(shardFileExists(shard2)).isFalse(); + assertThat(shardFileExists(shard3)).isTrue(); + assertThat(shardFileExists(shard4)).isFalse(); } @Test public void testCleanBackupShards() throws Exception { - assertEquals(cleaner.getBackupShardsCleaned().getTotalCount(), 0); + assertThat(cleaner.getBackupShardsCleaned().getTotalCount()).isEqualTo(0); TestingDao dao = dbi.onDemand(TestingDao.class); @@ -332,11 +330,11 @@ public void testCleanBackupShards() cleaner.cleanBackupShards(); - assertEquals(cleaner.getBackupShardsCleaned().getTotalCount(), 2); + assertThat(cleaner.getBackupShardsCleaned().getTotalCount()).isEqualTo(2); - assertFalse(shardBackupExists(shard1)); - assertFalse(shardBackupExists(shard2)); - assertTrue(shardBackupExists(shard3)); + assertThat(shardBackupExists(shard1)).isFalse(); + assertThat(shardBackupExists(shard2)).isFalse(); + assertThat(shardBackupExists(shard3)).isTrue(); assertQuery("SELECT shard_uuid FROM deleted_shards", row(shard3)); @@ -360,17 +358,17 @@ public void testDeleteOldCompletedTransactions() long txn5 = dao.insertTransaction(new Timestamp(now)); long txn6 = dao.insertTransaction(new Timestamp(now)); - assertEquals(shardDao.finalizeTransaction(txn1, true), 1); - assertEquals(shardDao.finalizeTransaction(txn2, false), 1); - assertEquals(shardDao.finalizeTransaction(txn3, false), 1); - assertEquals(shardDao.finalizeTransaction(txn5, true), 1); - assertEquals(shardDao.finalizeTransaction(txn6, false), 1); + assertThat(shardDao.finalizeTransaction(txn1, true)).isEqualTo(1); + assertThat(shardDao.finalizeTransaction(txn2, false)).isEqualTo(1); + assertThat(shardDao.finalizeTransaction(txn3, false)).isEqualTo(1); + assertThat(shardDao.finalizeTransaction(txn5, true)).isEqualTo(1); + assertThat(shardDao.finalizeTransaction(txn6, false)).isEqualTo(1); - assertEquals(dao.updateTransactionEndTime(txn1, yesterdayEnd), 1); - assertEquals(dao.updateTransactionEndTime(txn2, yesterdayEnd), 1); - assertEquals(dao.updateTransactionEndTime(txn3, yesterdayEnd), 1); - assertEquals(dao.updateTransactionEndTime(txn5, todayEnd), 1); - assertEquals(dao.updateTransactionEndTime(txn6, todayEnd), 1); + assertThat(dao.updateTransactionEndTime(txn1, yesterdayEnd)).isEqualTo(1); + assertThat(dao.updateTransactionEndTime(txn2, yesterdayEnd)).isEqualTo(1); + assertThat(dao.updateTransactionEndTime(txn3, yesterdayEnd)).isEqualTo(1); + assertThat(dao.updateTransactionEndTime(txn5, todayEnd)).isEqualTo(1); + assertThat(dao.updateTransactionEndTime(txn6, todayEnd)).isEqualTo(1); shardDao.insertCreatedShard(randomUUID(), txn2); shardDao.insertCreatedShard(randomUUID(), txn2); @@ -402,7 +400,7 @@ private void createShardFile(UUID uuid) { File file = storageService.getStorageFile(uuid); storageService.createParents(file); - assertTrue(file.createNewFile()); + assertThat(file.createNewFile()).isTrue(); } private boolean shardBackupExists(UUID uuid) @@ -415,7 +413,7 @@ private void createShardBackups(UUID... uuids) { for (UUID uuid : uuids) { File file = temporary.resolve("empty-" + randomUUID()).toFile(); - assertTrue(file.createNewFile()); + assertThat(file.createNewFile()).isTrue(); backupStore.backupShard(uuid, file); } } diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardDao.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardDao.java index d5f018e943ee..7d9cdf956763 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardDao.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardDao.java @@ -33,13 +33,10 @@ import static io.airlift.testing.Assertions.assertInstanceOf; import static io.trino.plugin.raptor.legacy.DatabaseTesting.createTestingJdbi; import static io.trino.plugin.raptor.legacy.metadata.SchemaDaoUtil.createTablesWithRetry; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_METHOD) @Execution(SAME_THREAD) @@ -68,18 +65,18 @@ public void teardown() @Test public void testExternalBatches() { - assertFalse(dao.externalBatchExists("foo")); - assertFalse(dao.externalBatchExists("bar")); + assertThat(dao.externalBatchExists("foo")).isFalse(); + assertThat(dao.externalBatchExists("bar")).isFalse(); dao.insertExternalBatch("foo"); - assertTrue(dao.externalBatchExists("foo")); - assertFalse(dao.externalBatchExists("bar")); + assertThat(dao.externalBatchExists("foo")).isTrue(); + assertThat(dao.externalBatchExists("bar")).isFalse(); assertThatThrownBy(() -> dao.insertExternalBatch("foo")) .isInstanceOfSatisfying(UnableToExecuteStatementException.class, e -> { assertInstanceOf(e.getCause(), SQLException.class); - assertTrue(((SQLException) e.getCause()).getSQLState().startsWith("23")); + assertThat(((SQLException) e.getCause()).getSQLState().startsWith("23")).isTrue(); }); } @@ -101,13 +98,13 @@ public void testInsertDeletedShards() @Test public void testNodeInsert() { - assertEquals(dao.getAllNodesInUse(), ImmutableSet.of()); + assertThat(dao.getAllNodesInUse()).isEqualTo(ImmutableSet.of()); String nodeName = UUID.randomUUID().toString(); int nodeId = dao.insertNode(nodeName); - assertEquals(dao.getNodeId(nodeName), (Integer) nodeId); + assertThat(dao.getNodeId(nodeName)).isEqualTo((Integer) nodeId); - assertEquals(dao.getAllNodesInUse(), ImmutableSet.of(nodeName)); + assertThat(dao.getAllNodesInUse()).isEqualTo(ImmutableSet.of(nodeName)); } @Test @@ -118,14 +115,14 @@ public void testInsertShard() long shardId = dao.insertShard(shardUuid, tableId, null, 13, 42, 84, 1234); ShardMetadata shard = dao.getShard(shardUuid); - assertNotNull(shard); - assertEquals(shard.getTableId(), tableId); - assertEquals(shard.getShardId(), shardId); - assertEquals(shard.getShardUuid(), shardUuid); - assertEquals(shard.getRowCount(), 13); - assertEquals(shard.getCompressedSize(), 42); - assertEquals(shard.getUncompressedSize(), 84); - assertEquals(shard.getXxhash64(), OptionalLong.of(1234)); + assertThat(shard).isNotNull(); + assertThat(shard.getTableId()).isEqualTo(tableId); + assertThat(shard.getShardId()).isEqualTo(shardId); + assertThat(shard.getShardUuid()).isEqualTo(shardUuid); + assertThat(shard.getRowCount()).isEqualTo(13); + assertThat(shard.getCompressedSize()).isEqualTo(42); + assertThat(shard.getUncompressedSize()).isEqualTo(84); + assertThat(shard.getXxhash64()).isEqualTo(OptionalLong.of(1234)); } @Test @@ -139,13 +136,13 @@ public void testInsertShardNodeUsingShardUuid() dao.insertShardNode(shard, nodeId); - assertEquals(dao.getShardNodes(tableId), ImmutableList.of(new ShardNode(shard, "node"))); + assertThat(dao.getShardNodes(tableId)).containsExactlyElementsOf(ImmutableList.of(new ShardNode(shard, "node"))); } @Test public void testNodeShards() { - assertEquals(dao.getAllNodesInUse(), ImmutableSet.of()); + assertThat(dao.getAllNodesInUse()).isEqualTo(ImmutableSet.of()); String nodeName1 = UUID.randomUUID().toString(); int nodeId1 = dao.insertNode(nodeName1); @@ -153,7 +150,7 @@ public void testNodeShards() String nodeName2 = UUID.randomUUID().toString(); int nodeId2 = dao.insertNode(nodeName2); - assertEquals(dao.getAllNodesInUse(), ImmutableSet.of(nodeName1, nodeName2)); + assertThat(dao.getAllNodesInUse()).isEqualTo(ImmutableSet.of(nodeName1, nodeName2)); UUID shardUuid1 = UUID.randomUUID(); UUID shardUuid2 = UUID.randomUUID(); @@ -187,12 +184,12 @@ public void testNodeShards() ShardMetadata shard4 = new ShardMetadata(bucketedTableId, shardId4, shardUuid4, OptionalInt.of(9), 4, 44, 444, OptionalLong.of(888_444), noRange, noRange); ShardMetadata shard5 = new ShardMetadata(bucketedTableId, shardId5, shardUuid5, OptionalInt.of(7), 5, 55, 555, OptionalLong.of(888_555), noRange, noRange); - assertEquals(dao.getShards(plainTableId), ImmutableSet.of(shardUuid1, shardUuid2)); - assertEquals(dao.getShards(bucketedTableId), ImmutableSet.of(shardUuid3, shardUuid4, shardUuid5)); + assertThat(dao.getShards(plainTableId)).isEqualTo(ImmutableSet.of(shardUuid1, shardUuid2)); + assertThat(dao.getShards(bucketedTableId)).isEqualTo(ImmutableSet.of(shardUuid3, shardUuid4, shardUuid5)); - assertEquals(dao.getNodeShards(nodeName1, null), ImmutableSet.of(shard3)); - assertEquals(dao.getNodeShards(nodeName2, null), ImmutableSet.of(shard4, shard5)); - assertEquals(dao.getNodeSizes(), ImmutableSet.of( + assertThat(dao.getNodeShards(nodeName1, null)).isEqualTo(ImmutableSet.of(shard3)); + assertThat(dao.getNodeShards(nodeName2, null)).isEqualTo(ImmutableSet.of(shard4, shard5)); + assertThat(dao.getNodeSizes()).isEqualTo(ImmutableSet.of( new NodeSize(nodeName1, 33), new NodeSize(nodeName2, 44 + 55))); @@ -200,42 +197,42 @@ public void testNodeShards() dao.insertShardNode(shardId2, nodeId1); dao.insertShardNode(shardId1, nodeId2); - assertEquals(dao.getNodeShards(nodeName1, null), ImmutableSet.of(shard1, shard2, shard3)); - assertEquals(dao.getNodeShards(nodeName2, null), ImmutableSet.of(shard1, shard4, shard5)); - assertEquals(dao.getNodeSizes(), ImmutableSet.of( + assertThat(dao.getNodeShards(nodeName1, null)).isEqualTo(ImmutableSet.of(shard1, shard2, shard3)); + assertThat(dao.getNodeShards(nodeName2, null)).isEqualTo(ImmutableSet.of(shard1, shard4, shard5)); + assertThat(dao.getNodeSizes()).isEqualTo(ImmutableSet.of( new NodeSize(nodeName1, 11 + 22 + 33), new NodeSize(nodeName2, 11 + 44 + 55))); dao.dropShardNodes(plainTableId); - assertEquals(dao.getNodeShards(nodeName1, null), ImmutableSet.of(shard3)); - assertEquals(dao.getNodeShards(nodeName2, null), ImmutableSet.of(shard4, shard5)); - assertEquals(dao.getNodeSizes(), ImmutableSet.of( + assertThat(dao.getNodeShards(nodeName1, null)).isEqualTo(ImmutableSet.of(shard3)); + assertThat(dao.getNodeShards(nodeName2, null)).isEqualTo(ImmutableSet.of(shard4, shard5)); + assertThat(dao.getNodeSizes()).isEqualTo(ImmutableSet.of( new NodeSize(nodeName1, 33), new NodeSize(nodeName2, 44 + 55))); dao.dropShards(plainTableId); dao.dropShards(bucketedTableId); - assertEquals(dao.getShards(plainTableId), ImmutableSet.of()); - assertEquals(dao.getShards(bucketedTableId), ImmutableSet.of()); - assertEquals(dao.getNodeSizes(), ImmutableSet.of()); + assertThat(dao.getShards(plainTableId)).isEqualTo(ImmutableSet.of()); + assertThat(dao.getShards(bucketedTableId)).isEqualTo(ImmutableSet.of()); + assertThat(dao.getNodeSizes()).isEqualTo(ImmutableSet.of()); } @Test public void testShardSelection() { - assertEquals(dao.getAllNodesInUse(), ImmutableSet.of()); + assertThat(dao.getAllNodesInUse()).isEqualTo(ImmutableSet.of()); String nodeName1 = UUID.randomUUID().toString(); int nodeId1 = dao.insertNode(nodeName1); - assertEquals(dao.getAllNodesInUse(), ImmutableSet.of(nodeName1)); + assertThat(dao.getAllNodesInUse()).isEqualTo(ImmutableSet.of(nodeName1)); String nodeName2 = UUID.randomUUID().toString(); int nodeId2 = dao.insertNode(nodeName2); - assertEquals(dao.getAllNodesInUse(), ImmutableSet.of(nodeName1, nodeName2)); + assertThat(dao.getAllNodesInUse()).isEqualTo(ImmutableSet.of(nodeName1, nodeName2)); long tableId = createTable("test"); @@ -250,14 +247,14 @@ public void testShardSelection() long shardId4 = dao.insertShard(shardUuid4, tableId, null, 0, 0, 0, 0); Set shards = dao.getShards(tableId); - assertEquals(shards.size(), 4); + assertThat(shards.size()).isEqualTo(4); - assertTrue(shards.contains(shardUuid1)); - assertTrue(shards.contains(shardUuid2)); - assertTrue(shards.contains(shardUuid3)); - assertTrue(shards.contains(shardUuid4)); + assertThat(shards.contains(shardUuid1)).isTrue(); + assertThat(shards.contains(shardUuid2)).isTrue(); + assertThat(shards.contains(shardUuid3)).isTrue(); + assertThat(shards.contains(shardUuid4)).isTrue(); - assertEquals(dao.getShardNodes(tableId).size(), 0); + assertThat(dao.getShardNodes(tableId).size()).isEqualTo(0); dao.insertShardNode(shardId1, nodeId1); dao.insertShardNode(shardId1, nodeId2); @@ -266,10 +263,10 @@ public void testShardSelection() dao.insertShardNode(shardId4, nodeId1); dao.insertShardNode(shardId4, nodeId2); - assertEquals(dao.getShards(tableId), shards); + assertThat(dao.getShards(tableId)).isEqualTo(shards); Set shardNodes = dao.getShardNodes(tableId); - assertEquals(shardNodes.size(), 6); + assertThat(shardNodes.size()).isEqualTo(6); assertContainsShardNode(shardNodes, nodeName1, shardUuid1); assertContainsShardNode(shardNodes, nodeName2, shardUuid1); @@ -286,6 +283,6 @@ private long createTable(String name) private static void assertContainsShardNode(Set nodes, String nodeName, UUID shardUuid) { - assertTrue(nodes.contains(new ShardNode(shardUuid, nodeName))); + assertThat(nodes.contains(new ShardNode(shardUuid, nodeName))).isTrue(); } } diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardPredicate.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardPredicate.java index 109f0e49d0c8..c7cc79e6cac9 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardPredicate.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardPredicate.java @@ -35,7 +35,7 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static java.sql.JDBCType.VARBINARY; import static java.util.UUID.randomUUID; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestShardPredicate { @@ -48,9 +48,9 @@ public void testSimpleShardUuidPredicate() ShardPredicate shardPredicate = ShardPredicate.create(tupleDomain); - assertEquals(shardPredicate.getPredicate(), "shard_uuid = ?"); - assertEquals(shardPredicate.getTypes(), ImmutableList.of(VARBINARY)); - assertEquals(shardPredicate.getValues(), ImmutableList.of(uuidStringToBytes(utf8Slice(uuid)))); + assertThat(shardPredicate.getPredicate()).isEqualTo("shard_uuid = ?"); + assertThat(shardPredicate.getTypes()).isEqualTo(ImmutableList.of(VARBINARY)); + assertThat(shardPredicate.getValues()).isEqualTo(ImmutableList.of(uuidStringToBytes(utf8Slice(uuid)))); } @Test @@ -64,9 +64,9 @@ public void testDiscreteShardUuidPredicate() ShardPredicate shardPredicate = ShardPredicate.create(tupleDomain); - assertEquals(shardPredicate.getPredicate(), "shard_uuid = ? OR shard_uuid = ?"); - assertEquals(shardPredicate.getTypes(), ImmutableList.of(VARBINARY, VARBINARY)); - assertEquals(ImmutableSet.copyOf(shardPredicate.getValues()), ImmutableSet.of(uuidStringToBytes(uuid0), uuidStringToBytes(uuid1))); + assertThat(shardPredicate.getPredicate()).isEqualTo("shard_uuid = ? OR shard_uuid = ?"); + assertThat(shardPredicate.getTypes()).isEqualTo(ImmutableList.of(VARBINARY, VARBINARY)); + assertThat(ImmutableSet.copyOf(shardPredicate.getValues())).isEqualTo(ImmutableSet.of(uuidStringToBytes(uuid0), uuidStringToBytes(uuid1))); } @Test @@ -80,7 +80,7 @@ public void testInvalidUuid() ShardPredicate shardPredicate = ShardPredicate.create(tupleDomain); - assertEquals(shardPredicate.getPredicate(), "true"); + assertThat(shardPredicate.getPredicate()).isEqualTo("true"); } @Test @@ -92,7 +92,7 @@ public void testRangeShardUuidPredicate() create(SortedRangeSet.copyOf(VARCHAR, ImmutableList.of(greaterThanOrEqual(VARCHAR, uuid0))), false))); ShardPredicate shardPredicate = ShardPredicate.create(tupleDomain); - assertEquals(shardPredicate.getPredicate(), "true"); + assertThat(shardPredicate.getPredicate()).isEqualTo("true"); } @Test @@ -103,7 +103,7 @@ public void testBucketNumberSingleRange() create(SortedRangeSet.copyOf(INTEGER, ImmutableList.of(equal(INTEGER, 1L))), false))); ShardPredicate shardPredicate = ShardPredicate.create(tupleDomain); - assertEquals(shardPredicate.getPredicate(), "(((bucket_number >= ? OR bucket_number IS NULL) AND (bucket_number <= ? OR bucket_number IS NULL)))"); + assertThat(shardPredicate.getPredicate()).isEqualTo("(((bucket_number >= ? OR bucket_number IS NULL) AND (bucket_number <= ? OR bucket_number IS NULL)))"); } @Test @@ -114,9 +114,8 @@ public void testBucketNumberMultipleRanges() create(SortedRangeSet.copyOf(INTEGER, ImmutableList.of(equal(INTEGER, 1L), equal(INTEGER, 3L))), false))); ShardPredicate shardPredicate = ShardPredicate.create(tupleDomain); - assertEquals(shardPredicate.getPredicate(), - "(((bucket_number >= ? OR bucket_number IS NULL) AND (bucket_number <= ? OR bucket_number IS NULL))" + - " OR ((bucket_number >= ? OR bucket_number IS NULL) AND (bucket_number <= ? OR bucket_number IS NULL)))"); + assertThat(shardPredicate.getPredicate()).isEqualTo("(((bucket_number >= ? OR bucket_number IS NULL) AND (bucket_number <= ? OR bucket_number IS NULL))" + + " OR ((bucket_number >= ? OR bucket_number IS NULL) AND (bucket_number <= ? OR bucket_number IS NULL)))"); } @Test @@ -128,11 +127,9 @@ public void testMultipleColumnsMultipleRanges() new RaptorColumnHandle("col", 1, INTEGER), create(SortedRangeSet.copyOf(INTEGER, ImmutableList.of(equal(INTEGER, 1L), equal(INTEGER, 3L))), false))); ShardPredicate shardPredicate = ShardPredicate.create(tupleDomain); - assertEquals( - shardPredicate.getPredicate(), - "(((bucket_number >= ? OR bucket_number IS NULL) AND (bucket_number <= ? OR bucket_number IS NULL)) " + - "OR ((bucket_number >= ? OR bucket_number IS NULL) AND (bucket_number <= ? OR bucket_number IS NULL))) " + - "AND (((c1_max >= ? OR c1_max IS NULL) AND (c1_min <= ? OR c1_min IS NULL)) " + - "OR ((c1_max >= ? OR c1_max IS NULL) AND (c1_min <= ? OR c1_min IS NULL)))"); + assertThat(shardPredicate.getPredicate()).isEqualTo("(((bucket_number >= ? OR bucket_number IS NULL) AND (bucket_number <= ? OR bucket_number IS NULL)) " + + "OR ((bucket_number >= ? OR bucket_number IS NULL) AND (bucket_number <= ? OR bucket_number IS NULL))) " + + "AND (((c1_max >= ? OR c1_max IS NULL) AND (c1_min <= ? OR c1_min IS NULL)) " + + "OR ((c1_max >= ? OR c1_max IS NULL) AND (c1_min <= ? OR c1_min IS NULL)))"); } } diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/OrcTestingUtil.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/OrcTestingUtil.java index 74933d1dc82a..8a209cadd2d6 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/OrcTestingUtil.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/OrcTestingUtil.java @@ -32,7 +32,7 @@ import static io.airlift.units.DataSize.Unit.MEGABYTE; import static io.trino.memory.context.AggregatedMemoryContext.newSimpleAggregatedMemoryContext; import static io.trino.orc.OrcReader.MAX_BATCH_SIZE; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; final class OrcTestingUtil { @@ -58,7 +58,7 @@ public static OrcRecordReader createReader(OrcDataSource dataSource, List .orElseThrow(() -> new RuntimeException("File is empty")); List columnNames = orcReader.getColumnNames(); - assertEquals(columnNames.size(), columnIds.size()); + assertThat(columnNames.size()).isEqualTo(columnIds.size()); return orcReader.createRecordReader( orcReader.getRootColumn().getNestedColumns(), diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestBucketBalancer.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestBucketBalancer.java index fbaabf8961b9..ab1bc789e346 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestBucketBalancer.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestBucketBalancer.java @@ -52,9 +52,9 @@ import static io.trino.plugin.raptor.legacy.metadata.TestDatabaseShardManager.createShardManager; import static io.trino.spi.type.BigintType.BIGINT; import static java.util.concurrent.TimeUnit.DAYS; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; @TestInstance(PER_METHOD) @Execution(SAME_THREAD) @@ -192,10 +192,10 @@ public void testMultipleDistributionUnbalancedWithDiskSpace() assertBalancing(balancer, 1); - assertEquals(balancer.fetchClusterState().getAssignedBytes().values() + assertThat(balancer.fetchClusterState().getAssignedBytes().values() .stream() .distinct() - .count(), 1); + .count()).isEqualTo(1); } @Test @@ -242,7 +242,7 @@ public void testMultipleDistributionUnbalancedWorstCase() private static void assertBalancing(BucketBalancer balancer, int expectedMoves) { int actualMoves = balancer.balance(); - assertEquals(actualMoves, expectedMoves); + assertThat(actualMoves).isEqualTo(expectedMoves); // check that number of buckets per node is within bounds ClusterState clusterState = balancer.fetchClusterState(); @@ -260,7 +260,7 @@ private static void assertBalancing(BucketBalancer balancer, int expectedMoves) } // check stability - assertEquals(balancer.balance(), 0); + assertThat(balancer.balance()).isEqualTo(0); } private long createDistribution(String distributionName, int bucketCount) diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestFileStorageService.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestFileStorageService.java index c6abfcacd9f3..b1b2cb513b98 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestFileStorageService.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestFileStorageService.java @@ -32,13 +32,9 @@ import static java.lang.String.format; import static java.nio.file.Files.createTempDirectory; import static java.util.UUID.randomUUID; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; -import static org.testng.FileAssert.assertDirectory; -import static org.testng.FileAssert.assertFile; @TestInstance(PER_METHOD) @Execution(SAME_THREAD) @@ -68,7 +64,7 @@ public void testGetFileSystemPath() { UUID uuid = UUID.fromString("701e1a79-74f7-4f56-b438-b41e8e7d019d"); File expected = new File("/test", format("70/1e/%s.orc", uuid)); - assertEquals(getFileSystemPath(new File("/test"), uuid), expected); + assertThat(getFileSystemPath(new File("/test"), uuid)).isEqualTo(expected); } @Test @@ -78,9 +74,9 @@ public void testFilePaths() File staging = temporary.resolve("staging").resolve(format("%s.orc", uuid)).toFile(); File storage = temporary.resolve("storage").resolve("70").resolve("1e").resolve(format("%s.orc", uuid)).toFile(); File quarantine = temporary.resolve("quarantine").resolve(format("%s.orc", uuid)).toFile(); - assertEquals(store.getStagingFile(uuid), staging); - assertEquals(store.getStorageFile(uuid), storage); - assertEquals(store.getQuarantineFile(uuid), quarantine); + assertThat(store.getStagingFile(uuid)).isEqualTo(staging); + assertThat(store.getStorageFile(uuid)).isEqualTo(storage); + assertThat(store.getQuarantineFile(uuid)).isEqualTo(quarantine); } @Test @@ -91,22 +87,22 @@ public void testStop() File storage = temporary.resolve("storage").toFile(); File quarantine = temporary.resolve("quarantine").toFile(); - assertDirectory(staging); - assertDirectory(storage); - assertDirectory(quarantine); + assertThat(staging).isDirectory(); + assertThat(storage).isDirectory(); + assertThat(quarantine).isDirectory(); File file = store.getStagingFile(randomUUID()); store.createParents(file); - assertFalse(file.exists()); - assertTrue(file.createNewFile()); - assertFile(file); + assertThat(file.exists()).isFalse(); + assertThat(file.createNewFile()).isTrue(); + assertThat(file).isFile(); store.stop(); - assertFalse(file.exists()); - assertFalse(staging.exists()); - assertDirectory(storage); - assertDirectory(quarantine); + assertThat(file.exists()).isFalse(); + assertThat(staging.exists()).isFalse(); + assertThat(storage).isDirectory(); + assertThat(quarantine).isDirectory(); } @Test @@ -121,15 +117,15 @@ public void testGetStorageShards() for (UUID shard : shards) { File file = store.getStorageFile(shard); store.createParents(file); - assertTrue(file.createNewFile()); + assertThat(file.createNewFile()).isTrue(); } File storage = temporary.resolve("storage").toFile(); - assertTrue(new File(storage, "abc").mkdir()); - assertTrue(new File(storage, "ab/cd").mkdirs()); - assertTrue(new File(storage, format("ab/cd/%s.junk", randomUUID())).createNewFile()); - assertTrue(new File(storage, "ab/cd/junk.orc").createNewFile()); + assertThat(new File(storage, "abc").mkdir()).isTrue(); + assertThat(new File(storage, "ab/cd").mkdirs()).isTrue(); + assertThat(new File(storage, format("ab/cd/%s.junk", randomUUID())).createNewFile()).isTrue(); + assertThat(new File(storage, "ab/cd/junk.orc").createNewFile()).isTrue(); - assertEquals(store.getStorageShards(), shards); + assertThat(store.getStorageShards()).isEqualTo(shards); } } diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestMissingShardComparator.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestMissingShardComparator.java index 00dfc546ec0a..7c779032c5e9 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestMissingShardComparator.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestMissingShardComparator.java @@ -17,7 +17,7 @@ import static io.trino.plugin.raptor.legacy.storage.ShardRecoveryManager.MissingShardComparator; import static io.trino.plugin.raptor.legacy.storage.ShardRecoveryManager.MissingShardRunnable; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestMissingShardComparator { @@ -25,10 +25,10 @@ public class TestMissingShardComparator public void testOrdering() { MissingShardComparator comparator = new MissingShardComparator(); - assertEquals(comparator.compare(new DummyMissingShardRunnable(false), new DummyMissingShardRunnable(false)), 0); - assertEquals(comparator.compare(new DummyMissingShardRunnable(false), new DummyMissingShardRunnable(true)), 1); - assertEquals(comparator.compare(new DummyMissingShardRunnable(true), new DummyMissingShardRunnable(false)), -1); - assertEquals(comparator.compare(new DummyMissingShardRunnable(true), new DummyMissingShardRunnable(true)), 0); + assertThat(comparator.compare(new DummyMissingShardRunnable(false), new DummyMissingShardRunnable(false))).isEqualTo(0); + assertThat(comparator.compare(new DummyMissingShardRunnable(false), new DummyMissingShardRunnable(true))).isEqualTo(1); + assertThat(comparator.compare(new DummyMissingShardRunnable(true), new DummyMissingShardRunnable(false))).isEqualTo(-1); + assertThat(comparator.compare(new DummyMissingShardRunnable(true), new DummyMissingShardRunnable(true))).isEqualTo(0); } private static class DummyMissingShardRunnable diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestOrcFileRewriter.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestOrcFileRewriter.java index 9f9da1c14827..2b537ed5eb56 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestOrcFileRewriter.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestOrcFileRewriter.java @@ -60,12 +60,9 @@ import static java.nio.file.Files.createTempDirectory; import static java.nio.file.Files.readAllBytes; import static java.util.UUID.randomUUID; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(SAME_THREAD) @@ -117,72 +114,72 @@ public void testRewrite() try (OrcDataSource dataSource = fileOrcDataSource(file)) { OrcRecordReader reader = createReader(dataSource, columnIds, columnTypes); - assertEquals(reader.getReaderRowCount(), 5); - assertEquals(reader.getFileRowCount(), 5); - assertEquals(reader.getSplitLength(), file.length()); + assertThat(reader.getReaderRowCount()).isEqualTo(5); + assertThat(reader.getFileRowCount()).isEqualTo(5); + assertThat(reader.getSplitLength()).isEqualTo(file.length()); Page page = reader.nextPage(); - assertEquals(page.getPositionCount(), 5); + assertThat(page.getPositionCount()).isEqualTo(5); Block column0 = page.getBlock(0); - assertEquals(column0.getPositionCount(), 5); + assertThat(column0.getPositionCount()).isEqualTo(5); for (int i = 0; i < 5; i++) { - assertEquals(column0.isNull(i), false); + assertThat(column0.isNull(i)).isEqualTo(false); } - assertEquals(BIGINT.getLong(column0, 0), 123L); - assertEquals(BIGINT.getLong(column0, 1), 777L); - assertEquals(BIGINT.getLong(column0, 2), 456L); - assertEquals(BIGINT.getLong(column0, 3), 888L); - assertEquals(BIGINT.getLong(column0, 4), 999L); + assertThat(BIGINT.getLong(column0, 0)).isEqualTo(123L); + assertThat(BIGINT.getLong(column0, 1)).isEqualTo(777L); + assertThat(BIGINT.getLong(column0, 2)).isEqualTo(456L); + assertThat(BIGINT.getLong(column0, 3)).isEqualTo(888L); + assertThat(BIGINT.getLong(column0, 4)).isEqualTo(999L); Block column1 = page.getBlock(1); - assertEquals(column1.getPositionCount(), 5); + assertThat(column1.getPositionCount()).isEqualTo(5); for (int i = 0; i < 5; i++) { - assertEquals(column1.isNull(i), false); + assertThat(column1.isNull(i)).isEqualTo(false); } - assertEquals(createVarcharType(20).getSlice(column1, 0), utf8Slice("hello")); - assertEquals(createVarcharType(20).getSlice(column1, 1), utf8Slice("sky")); - assertEquals(createVarcharType(20).getSlice(column1, 2), utf8Slice("bye")); - assertEquals(createVarcharType(20).getSlice(column1, 3), utf8Slice("world")); - assertEquals(createVarcharType(20).getSlice(column1, 4), utf8Slice("done")); + assertThat(createVarcharType(20).getSlice(column1, 0)).isEqualTo(utf8Slice("hello")); + assertThat(createVarcharType(20).getSlice(column1, 1)).isEqualTo(utf8Slice("sky")); + assertThat(createVarcharType(20).getSlice(column1, 2)).isEqualTo(utf8Slice("bye")); + assertThat(createVarcharType(20).getSlice(column1, 3)).isEqualTo(utf8Slice("world")); + assertThat(createVarcharType(20).getSlice(column1, 4)).isEqualTo(utf8Slice("done")); Block column2 = page.getBlock(2); - assertEquals(column2.getPositionCount(), 5); + assertThat(column2.getPositionCount()).isEqualTo(5); for (int i = 0; i < 5; i++) { - assertEquals(column2.isNull(i), false); + assertThat(column2.isNull(i)).isEqualTo(false); } - assertTrue(arrayBlocksEqual(BIGINT, arrayType.getObject(column2, 0), arrayBlockOf(BIGINT, 1, 2))); - assertTrue(arrayBlocksEqual(BIGINT, arrayType.getObject(column2, 1), arrayBlockOf(BIGINT, 3, 4))); - assertTrue(arrayBlocksEqual(BIGINT, arrayType.getObject(column2, 2), arrayBlockOf(BIGINT, 5, 6))); - assertTrue(arrayBlocksEqual(BIGINT, arrayType.getObject(column2, 3), arrayBlockOf(BIGINT, 7, 8))); - assertTrue(arrayBlocksEqual(BIGINT, arrayType.getObject(column2, 4), arrayBlockOf(BIGINT, 9, 10))); + assertThat(arrayBlocksEqual(BIGINT, arrayType.getObject(column2, 0), arrayBlockOf(BIGINT, 1, 2))).isTrue(); + assertThat(arrayBlocksEqual(BIGINT, arrayType.getObject(column2, 1), arrayBlockOf(BIGINT, 3, 4))).isTrue(); + assertThat(arrayBlocksEqual(BIGINT, arrayType.getObject(column2, 2), arrayBlockOf(BIGINT, 5, 6))).isTrue(); + assertThat(arrayBlocksEqual(BIGINT, arrayType.getObject(column2, 3), arrayBlockOf(BIGINT, 7, 8))).isTrue(); + assertThat(arrayBlocksEqual(BIGINT, arrayType.getObject(column2, 4), arrayBlockOf(BIGINT, 9, 10))).isTrue(); Block column3 = page.getBlock(3); - assertEquals(column3.getPositionCount(), 5); + assertThat(column3.getPositionCount()).isEqualTo(5); for (int i = 0; i < 5; i++) { - assertEquals(column3.isNull(i), false); + assertThat(column3.isNull(i)).isEqualTo(false); } - assertTrue(sqlMapEqual(createVarcharType(5), BOOLEAN, (SqlMap) mapType.getObject(column3, 0), sqlMapOf(createVarcharType(5), BOOLEAN, "k1", true))); - assertTrue(sqlMapEqual(createVarcharType(5), BOOLEAN, (SqlMap) mapType.getObject(column3, 1), sqlMapOf(createVarcharType(5), BOOLEAN, "k2", false))); - assertTrue(sqlMapEqual(createVarcharType(5), BOOLEAN, (SqlMap) mapType.getObject(column3, 2), sqlMapOf(createVarcharType(5), BOOLEAN, "k3", true))); - assertTrue(sqlMapEqual(createVarcharType(5), BOOLEAN, (SqlMap) mapType.getObject(column3, 3), sqlMapOf(createVarcharType(5), BOOLEAN, "k4", true))); - assertTrue(sqlMapEqual(createVarcharType(5), BOOLEAN, (SqlMap) mapType.getObject(column3, 4), sqlMapOf(createVarcharType(5), BOOLEAN, "k5", true))); + assertThat(sqlMapEqual(createVarcharType(5), BOOLEAN, (SqlMap) mapType.getObject(column3, 0), sqlMapOf(createVarcharType(5), BOOLEAN, "k1", true))).isTrue(); + assertThat(sqlMapEqual(createVarcharType(5), BOOLEAN, (SqlMap) mapType.getObject(column3, 1), sqlMapOf(createVarcharType(5), BOOLEAN, "k2", false))).isTrue(); + assertThat(sqlMapEqual(createVarcharType(5), BOOLEAN, (SqlMap) mapType.getObject(column3, 2), sqlMapOf(createVarcharType(5), BOOLEAN, "k3", true))).isTrue(); + assertThat(sqlMapEqual(createVarcharType(5), BOOLEAN, (SqlMap) mapType.getObject(column3, 3), sqlMapOf(createVarcharType(5), BOOLEAN, "k4", true))).isTrue(); + assertThat(sqlMapEqual(createVarcharType(5), BOOLEAN, (SqlMap) mapType.getObject(column3, 4), sqlMapOf(createVarcharType(5), BOOLEAN, "k5", true))).isTrue(); Block column4 = page.getBlock(4); - assertEquals(column4.getPositionCount(), 5); + assertThat(column4.getPositionCount()).isEqualTo(5); for (int i = 0; i < 5; i++) { - assertEquals(column4.isNull(i), false); + assertThat(column4.isNull(i)).isEqualTo(false); } - assertTrue(arrayBlocksEqual(arrayType, arrayOfArrayType.getObject(column4, 0), arrayBlockOf(arrayType, arrayBlockOf(BIGINT, 5)))); - assertTrue(arrayBlocksEqual(arrayType, arrayOfArrayType.getObject(column4, 1), arrayBlockOf(arrayType, arrayBlockOf(BIGINT, 6)))); - assertTrue(arrayBlocksEqual(arrayType, arrayOfArrayType.getObject(column4, 2), arrayBlockOf(arrayType, arrayBlockOf(BIGINT, 7)))); - assertTrue(arrayBlocksEqual(arrayType, arrayOfArrayType.getObject(column4, 3), arrayBlockOf(arrayType, null, arrayBlockOf(BIGINT, 8), null))); - assertTrue(arrayBlocksEqual(arrayType, arrayOfArrayType.getObject(column4, 4), arrayBlockOf(arrayType, arrayBlockOf(BIGINT, 9, 10)))); + assertThat(arrayBlocksEqual(arrayType, arrayOfArrayType.getObject(column4, 0), arrayBlockOf(arrayType, arrayBlockOf(BIGINT, 5)))).isTrue(); + assertThat(arrayBlocksEqual(arrayType, arrayOfArrayType.getObject(column4, 1), arrayBlockOf(arrayType, arrayBlockOf(BIGINT, 6)))).isTrue(); + assertThat(arrayBlocksEqual(arrayType, arrayOfArrayType.getObject(column4, 2), arrayBlockOf(arrayType, arrayBlockOf(BIGINT, 7)))).isTrue(); + assertThat(arrayBlocksEqual(arrayType, arrayOfArrayType.getObject(column4, 3), arrayBlockOf(arrayType, null, arrayBlockOf(BIGINT, 8), null))).isTrue(); + assertThat(arrayBlocksEqual(arrayType, arrayOfArrayType.getObject(column4, 4), arrayBlockOf(arrayType, arrayBlockOf(BIGINT, 9, 10)))).isTrue(); - assertNull(reader.nextPage()); + assertThat(reader.nextPage()).isNull(); OrcFileMetadata orcFileMetadata = METADATA_CODEC.fromJson(reader.getUserMetadata().get(OrcFileMetadata.KEY).getBytes()); - assertEquals(orcFileMetadata, new OrcFileMetadata(ImmutableMap.builder() + assertThat(orcFileMetadata).isEqualTo(new OrcFileMetadata(ImmutableMap.builder() .put(3L, BIGINT.getTypeId()) .put(7L, createVarcharType(20).getTypeId()) .put(9L, arrayType.getTypeId()) @@ -199,63 +196,63 @@ public void testRewrite() File newFile = temporary.resolve(randomUUID().toString()).toFile(); OrcFileInfo info = OrcFileRewriter.rewrite(TESTING_TYPE_MANAGER, file, newFile, rowsToDelete); - assertEquals(info.getRowCount(), 2); - assertEquals(info.getUncompressedSize(), 182); + assertThat(info.getRowCount()).isEqualTo(2); + assertThat(info.getUncompressedSize()).isEqualTo(182); try (OrcDataSource dataSource = fileOrcDataSource(newFile)) { OrcRecordReader reader = createReader(dataSource, columnIds, columnTypes); - assertEquals(reader.getReaderRowCount(), 2); - assertEquals(reader.getFileRowCount(), 2); - assertEquals(reader.getSplitLength(), newFile.length()); + assertThat(reader.getReaderRowCount()).isEqualTo(2); + assertThat(reader.getFileRowCount()).isEqualTo(2); + assertThat(reader.getSplitLength()).isEqualTo(newFile.length()); Page page = reader.nextPage(); - assertEquals(page.getPositionCount(), 2); + assertThat(page.getPositionCount()).isEqualTo(2); Block column0 = page.getBlock(0); - assertEquals(column0.getPositionCount(), 2); + assertThat(column0.getPositionCount()).isEqualTo(2); for (int i = 0; i < 2; i++) { - assertEquals(column0.isNull(i), false); + assertThat(column0.isNull(i)).isEqualTo(false); } - assertEquals(BIGINT.getLong(column0, 0), 123L); - assertEquals(BIGINT.getLong(column0, 1), 456L); + assertThat(BIGINT.getLong(column0, 0)).isEqualTo(123L); + assertThat(BIGINT.getLong(column0, 1)).isEqualTo(456L); Block column1 = page.getBlock(1); - assertEquals(column1.getPositionCount(), 2); + assertThat(column1.getPositionCount()).isEqualTo(2); for (int i = 0; i < 2; i++) { - assertEquals(column1.isNull(i), false); + assertThat(column1.isNull(i)).isEqualTo(false); } - assertEquals(createVarcharType(20).getSlice(column1, 0), utf8Slice("hello")); - assertEquals(createVarcharType(20).getSlice(column1, 1), utf8Slice("bye")); + assertThat(createVarcharType(20).getSlice(column1, 0)).isEqualTo(utf8Slice("hello")); + assertThat(createVarcharType(20).getSlice(column1, 1)).isEqualTo(utf8Slice("bye")); Block column2 = page.getBlock(2); - assertEquals(column2.getPositionCount(), 2); + assertThat(column2.getPositionCount()).isEqualTo(2); for (int i = 0; i < 2; i++) { - assertEquals(column2.isNull(i), false); + assertThat(column2.isNull(i)).isEqualTo(false); } - assertTrue(arrayBlocksEqual(BIGINT, arrayType.getObject(column2, 0), arrayBlockOf(BIGINT, 1, 2))); - assertTrue(arrayBlocksEqual(BIGINT, arrayType.getObject(column2, 1), arrayBlockOf(BIGINT, 5, 6))); + assertThat(arrayBlocksEqual(BIGINT, arrayType.getObject(column2, 0), arrayBlockOf(BIGINT, 1, 2))).isTrue(); + assertThat(arrayBlocksEqual(BIGINT, arrayType.getObject(column2, 1), arrayBlockOf(BIGINT, 5, 6))).isTrue(); Block column3 = page.getBlock(3); - assertEquals(column3.getPositionCount(), 2); + assertThat(column3.getPositionCount()).isEqualTo(2); for (int i = 0; i < 2; i++) { - assertEquals(column3.isNull(i), false); + assertThat(column3.isNull(i)).isEqualTo(false); } - assertTrue(sqlMapEqual(createVarcharType(5), BOOLEAN, (SqlMap) mapType.getObject(column3, 0), sqlMapOf(createVarcharType(5), BOOLEAN, "k1", true))); - assertTrue(sqlMapEqual(createVarcharType(5), BOOLEAN, (SqlMap) mapType.getObject(column3, 1), sqlMapOf(createVarcharType(5), BOOLEAN, "k3", true))); + assertThat(sqlMapEqual(createVarcharType(5), BOOLEAN, (SqlMap) mapType.getObject(column3, 0), sqlMapOf(createVarcharType(5), BOOLEAN, "k1", true))).isTrue(); + assertThat(sqlMapEqual(createVarcharType(5), BOOLEAN, (SqlMap) mapType.getObject(column3, 1), sqlMapOf(createVarcharType(5), BOOLEAN, "k3", true))).isTrue(); Block column4 = page.getBlock(4); - assertEquals(column4.getPositionCount(), 2); + assertThat(column4.getPositionCount()).isEqualTo(2); for (int i = 0; i < 2; i++) { - assertEquals(column4.isNull(i), false); + assertThat(column4.isNull(i)).isEqualTo(false); } - assertTrue(arrayBlocksEqual(arrayType, arrayOfArrayType.getObject(column4, 0), arrayBlockOf(arrayType, arrayBlockOf(BIGINT, 5)))); - assertTrue(arrayBlocksEqual(arrayType, arrayOfArrayType.getObject(column4, 1), arrayBlockOf(arrayType, arrayBlockOf(BIGINT, 7)))); + assertThat(arrayBlocksEqual(arrayType, arrayOfArrayType.getObject(column4, 0), arrayBlockOf(arrayType, arrayBlockOf(BIGINT, 5)))).isTrue(); + assertThat(arrayBlocksEqual(arrayType, arrayOfArrayType.getObject(column4, 1), arrayBlockOf(arrayType, arrayBlockOf(BIGINT, 7)))).isTrue(); - assertEquals(reader.nextPage(), null); + assertThat(reader.nextPage()).isEqualTo(null); OrcFileMetadata orcFileMetadata = METADATA_CODEC.fromJson(reader.getUserMetadata().get(OrcFileMetadata.KEY).getBytes()); - assertEquals(orcFileMetadata, new OrcFileMetadata(ImmutableMap.builder() + assertThat(orcFileMetadata).isEqualTo(new OrcFileMetadata(ImmutableMap.builder() .put(3L, BIGINT.getTypeId()) .put(7L, createVarcharType(20).getTypeId()) .put(9L, arrayType.getTypeId()) @@ -284,10 +281,10 @@ public void testRewriteAllRowsDeleted() File newFile = temporary.resolve(randomUUID().toString()).toFile(); OrcFileInfo info = OrcFileRewriter.rewrite(TESTING_TYPE_MANAGER, file, newFile, rowsToDelete); - assertEquals(info.getRowCount(), 0); - assertEquals(info.getUncompressedSize(), 0); + assertThat(info.getRowCount()).isEqualTo(0); + assertThat(info.getUncompressedSize()).isEqualTo(0); - assertFalse(newFile.exists()); + assertThat(newFile.exists()).isFalse(); } @Test @@ -306,10 +303,10 @@ public void testRewriteNoRowsDeleted() File newFile = temporary.resolve(randomUUID().toString()).toFile(); OrcFileInfo info = OrcFileRewriter.rewrite(TESTING_TYPE_MANAGER, file, newFile, rowsToDelete); - assertEquals(info.getRowCount(), 2); - assertEquals(info.getUncompressedSize(), 18); + assertThat(info.getRowCount()).isEqualTo(2); + assertThat(info.getUncompressedSize()).isEqualTo(18); - assertEquals(readAllBytes(newFile.toPath()), readAllBytes(file.toPath())); + assertThat(readAllBytes(newFile.toPath())).isEqualTo(readAllBytes(file.toPath())); } @Test @@ -331,7 +328,7 @@ public void testUncompressedSize() File newFile = temporary.resolve(randomUUID().toString()).toFile(); OrcFileInfo info = OrcFileRewriter.rewrite(TESTING_TYPE_MANAGER, file, newFile, new BitSet()); - assertEquals(info.getRowCount(), 3); - assertEquals(info.getUncompressedSize(), 106); + assertThat(info.getRowCount()).isEqualTo(3); + assertThat(info.getUncompressedSize()).isEqualTo(106); } } diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestRaptorStorageManager.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestRaptorStorageManager.java index adce54990608..890a3248e9e3 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestRaptorStorageManager.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestRaptorStorageManager.java @@ -97,18 +97,10 @@ import static java.lang.String.format; import static java.nio.file.Files.createTempDirectory; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Fail.fail; import static org.joda.time.DateTimeZone.UTC; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; -import static org.testng.FileAssert.assertDirectory; -import static org.testng.FileAssert.assertFile; @TestInstance(PER_METHOD) @Execution(SAME_THREAD) @@ -192,40 +184,44 @@ public void testWriter() sink.appendPages(pages); // shard is not recorded until flush - assertEquals(shardRecorder.getShards().size(), 0); + assertThat(shardRecorder.getShards().size()).isEqualTo(0); sink.flush(); // shard is recorded after flush List recordedShards = shardRecorder.getShards(); - assertEquals(recordedShards.size(), 1); + assertThat(recordedShards.size()).isEqualTo(1); List shards = getFutureValue(sink.commit()); - assertEquals(shards.size(), 1); + assertThat(shards.size()).isEqualTo(1); ShardInfo shardInfo = Iterables.getOnlyElement(shards); UUID shardUuid = shardInfo.getShardUuid(); File file = storageService.getStorageFile(shardUuid); File backupFile = fileBackupStore.getBackupFile(shardUuid); - assertEquals(recordedShards.get(0).getTransactionId(), TRANSACTION_ID); - assertEquals(recordedShards.get(0).getShardUuid(), shardUuid); + assertThat(recordedShards.get(0).getTransactionId()).isEqualTo(TRANSACTION_ID); + assertThat(recordedShards.get(0).getShardUuid()).isEqualTo(shardUuid); - assertEquals(shardInfo.getRowCount(), 2); - assertEquals(shardInfo.getCompressedSize(), file.length()); - assertEquals(shardInfo.getXxhash64(), xxhash64(file)); + assertThat(shardInfo.getRowCount()).isEqualTo(2); + assertThat(shardInfo.getCompressedSize()).isEqualTo(file.length()); + assertThat(shardInfo.getXxhash64()).isEqualTo(xxhash64(file)); // verify primary and backup shard exist - assertFile(file, "primary shard"); - assertFile(backupFile, "backup shard"); + assertThat(file) + .describedAs("primary shard") + .exists(); + assertThat(backupFile) + .describedAs("backup shard") + .exists(); assertFileEquals(file, backupFile); // remove primary shard to force recovery from backup - assertTrue(file.delete()); - assertTrue(file.getParentFile().delete()); - assertFalse(file.exists()); + assertThat(file.delete()).isTrue(); + assertThat(file.getParentFile().delete()).isTrue(); + assertThat(file.exists()).isFalse(); recoveryManager.restoreFromBackup(shardUuid, shardInfo.getCompressedSize(), OptionalLong.of(shardInfo.getXxhash64())); @@ -233,19 +229,19 @@ public void testWriter() OrcRecordReader reader = createReader(dataSource, columnIds, columnTypes); Page page = reader.nextPage(); - assertEquals(page.getPositionCount(), 2); + assertThat(page.getPositionCount()).isEqualTo(2); Block column0 = page.getBlock(0); - assertEquals(column0.isNull(0), false); - assertEquals(column0.isNull(1), false); - assertEquals(BIGINT.getLong(column0, 0), 123L); - assertEquals(BIGINT.getLong(column0, 1), 456L); + assertThat(column0.isNull(0)).isEqualTo(false); + assertThat(column0.isNull(1)).isEqualTo(false); + assertThat(BIGINT.getLong(column0, 0)).isEqualTo(123L); + assertThat(BIGINT.getLong(column0, 1)).isEqualTo(456L); Block column1 = page.getBlock(1); - assertEquals(createVarcharType(10).getSlice(column1, 0), utf8Slice("hello")); - assertEquals(createVarcharType(10).getSlice(column1, 1), utf8Slice("bye")); + assertThat(createVarcharType(10).getSlice(column1, 0)).isEqualTo(utf8Slice("hello")); + assertThat(createVarcharType(10).getSlice(column1, 1)).isEqualTo(utf8Slice("bye")); - assertNull(reader.nextPage()); + assertThat(reader.nextPage()).isNull(); } } @@ -283,7 +279,7 @@ public void testReader() sink.appendPages(pages); List shards = getFutureValue(sink.commit()); - assertEquals(shards.size(), 1); + assertThat(shards.size()).isEqualTo(1); UUID uuid = Iterables.getOnlyElement(shards).getShardUuid(); MaterializedResult expected = resultBuilder(SESSION, columnTypes) @@ -298,7 +294,7 @@ public void testReader() try (ConnectorPageSource pageSource = getPageSource(manager, columnIds, columnTypes, uuid, tupleDomain)) { MaterializedResult result = materializeSourceDataStream(SESSION, pageSource, columnTypes); - assertEquals(result.getRowCount(), expected.getRowCount()); + assertThat(result.getRowCount()).isEqualTo(expected.getRowCount()); assertThat(result).containsExactlyElementsOf(expected); } @@ -307,7 +303,7 @@ public void testReader() try (ConnectorPageSource pageSource = getPageSource(manager, columnIds, columnTypes, uuid, tupleDomain)) { MaterializedResult result = materializeSourceDataStream(SESSION, pageSource, columnTypes); - assertEquals(result.getRowCount(), expected.getRowCount()); + assertThat(result.getRowCount()).isEqualTo(expected.getRowCount()); } // tuple domain outside the column range @@ -315,7 +311,7 @@ public void testReader() try (ConnectorPageSource pageSource = getPageSource(manager, columnIds, columnTypes, uuid, tupleDomain)) { MaterializedResult result = materializeSourceDataStream(SESSION, pageSource, columnTypes); - assertEquals(result.getRowCount(), 0); + assertThat(result.getRowCount()).isEqualTo(0); } } @@ -338,7 +334,7 @@ public void testRewriter() sink.appendPages(pages); List shards = getFutureValue(sink.commit()); - assertEquals(shardRecorder.getShards().size(), 1); + assertThat(shardRecorder.getShards().size()).isEqualTo(1); // delete one row BitSet rowsToDelete = new BitSet(); @@ -350,7 +346,7 @@ public void testRewriter() ShardInfo shardInfo = Iterables.getOnlyElement(shardDeltas.getNewShards()); // check that output file has one row - assertEquals(shardInfo.getRowCount(), 1); + assertThat(shardInfo.getRowCount()).isEqualTo(1); // check that storage file is same as backup file File storageFile = storageService.getStorageFile(shardInfo.getShardUuid()); @@ -359,9 +355,9 @@ public void testRewriter() // verify recorded shard List recordedShards = shardRecorder.getShards(); - assertEquals(recordedShards.size(), 2); - assertEquals(recordedShards.get(1).getTransactionId(), TRANSACTION_ID); - assertEquals(recordedShards.get(1).getShardUuid(), shardInfo.getShardUuid()); + assertThat(recordedShards.size()).isEqualTo(2); + assertThat(recordedShards.get(1).getTransactionId()).isEqualTo(TRANSACTION_ID); + assertThat(recordedShards.get(1).getShardUuid()).isEqualTo(shardInfo.getShardUuid()); } @Test @@ -369,8 +365,8 @@ public void testWriterRollback() { // verify staging directory is empty File staging = temporary.resolve("data").resolve("staging").toFile(); - assertDirectory(staging); - assertEquals(staging.list(), new String[] {}); + assertThat(staging).isDirectory(); + assertThat(staging.list()).isEqualTo(new String[] {}); // create a shard in staging RaptorStorageManager manager = createRaptorStorageManager(); @@ -389,7 +385,7 @@ public void testWriterRollback() // verify shard exists in staging String[] files = staging.list(); - assertNotNull(files); + assertThat(files).isNotNull(); String stagingFile = Arrays.stream(files) .filter(file -> file.endsWith(".orc")) .findFirst() @@ -399,8 +395,8 @@ public void testWriterRollback() sink.rollback(); files = staging.list(); - assertNotNull(files); - assertTrue(Arrays.stream(files).noneMatch(stagingFile::equals)); + assertThat(files).isNotNull(); + assertThat(Arrays.stream(files).noneMatch(stagingFile::equals)).isTrue(); } @Test @@ -521,7 +517,7 @@ public void testMaxShardRows() .row(456L, "bye") .build(); sink.appendPages(pages); - assertTrue(sink.isFull()); + assertThat(sink.isFull()).isTrue(); } @Test @@ -539,7 +535,7 @@ public void testMaxFileSize() RaptorStorageManager manager = createRaptorStorageManager(20, DataSize.ofBytes(1)); StoragePageSink sink = createStoragePageSink(manager, columnIds, columnTypes); sink.appendPages(pages); - assertTrue(sink.isFull()); + assertThat(sink.isFull()).isTrue(); } private static ConnectorPageSource getPageSource( @@ -635,8 +631,8 @@ private static void assertColumnStats(List list, long columnId, Obj { for (ColumnStats stats : list) { if (stats.getColumnId() == columnId) { - assertEquals(stats.getMin(), min); - assertEquals(stats.getMax(), max); + assertThat(stats.getMin()).isEqualTo(min); + assertThat(stats.getMax()).isEqualTo(max); return; } } @@ -646,7 +642,8 @@ private static void assertColumnStats(List list, long columnId, Obj private static void assertNoColumnStats(List list, long columnId) { for (ColumnStats stats : list) { - assertNotEquals(stats.getColumnId(), columnId); + assertThat(stats.getColumnId()) + .isNotEqualTo(columnId); } } @@ -673,7 +670,7 @@ private List columnStats(List columnTypes, Object[]... rows) sink.appendPages(rowPagesBuilder(columnTypes).rows(rows).build()); List shards = getFutureValue(sink.commit()); - assertEquals(shards.size(), 1); + assertThat(shards.size()).isEqualTo(1); return Iterables.getOnlyElement(shards).getColumnStats(); } diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestShardEjector.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestShardEjector.java index 3756fbdcc921..21511bd9616a 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestShardEjector.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestShardEjector.java @@ -57,11 +57,9 @@ import static java.util.UUID.randomUUID; import static java.util.concurrent.TimeUnit.HOURS; import static java.util.stream.Collectors.toSet; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_METHOD) @Execution(SAME_THREAD) @@ -143,7 +141,7 @@ public void testEjector() for (ShardInfo shard : shards.subList(0, 8)) { File file = storageService.getStorageFile(shard.getShardUuid()); storageService.createParents(file); - assertTrue(file.createNewFile()); + assertThat(file.createNewFile()).isTrue(); } ejector.process(); @@ -160,13 +158,13 @@ public void testEjector() Set remaining = uuids(shardManager.getNodeShards("node1")); for (UUID uuid : ejectedShards) { - assertFalse(remaining.contains(uuid)); - assertFalse(storageService.getStorageFile(uuid).exists()); + assertThat(remaining.contains(uuid)).isFalse(); + assertThat(storageService.getStorageFile(uuid).exists()).isFalse(); } - assertEquals(remaining, keptShards); + assertThat(remaining).isEqualTo(keptShards); for (UUID uuid : keptShards) { - assertTrue(storageService.getStorageFile(uuid).exists()); + assertThat(storageService.getStorageFile(uuid).exists()).isTrue(); } Set others = ImmutableSet.builder() @@ -176,7 +174,7 @@ public void testEjector() .addAll(uuids(shardManager.getNodeShards("node5"))) .build(); - assertTrue(others.containsAll(ejectedShards)); + assertThat(others.containsAll(ejectedShards)).isTrue(); } private long createTable(String name) diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestShardRecovery.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestShardRecovery.java index 739eac07cf32..af3c34262364 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestShardRecovery.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestShardRecovery.java @@ -50,14 +50,10 @@ import static java.nio.file.Files.createTempFile; import static java.nio.file.Files.writeString; import static java.util.concurrent.TimeUnit.MINUTES; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_METHOD) @Execution(SAME_THREAD) @@ -110,15 +106,15 @@ public void testShardRecovery() writeString(tempFile.toPath(), "test data"); backupStore.backupShard(shardUuid, tempFile); - assertTrue(backupStore.shardExists(shardUuid)); + assertThat(backupStore.shardExists(shardUuid)).isTrue(); File backupFile = backupStore.getBackupFile(shardUuid); - assertTrue(backupFile.exists()); - assertEquals(backupFile.length(), tempFile.length()); + assertThat(backupFile.exists()).isTrue(); + assertThat(backupFile.length()).isEqualTo(tempFile.length()); - assertFalse(file.exists()); + assertThat(file.exists()).isFalse(); recoveryManager.restoreFromBackup(shardUuid, tempFile.length(), OptionalLong.empty()); - assertTrue(file.exists()); - assertEquals(file.length(), tempFile.length()); + assertThat(file.exists()).isTrue(); + assertThat(file.length()).isEqualTo(tempFile.length()); } @Test @@ -132,10 +128,10 @@ public void testShardRecoveryExistingFileSizeMismatch() writeString(tempFile.toPath(), "test data"); backupStore.backupShard(shardUuid, tempFile); - assertTrue(backupStore.shardExists(shardUuid)); + assertThat(backupStore.shardExists(shardUuid)).isTrue(); File backupFile = backupStore.getBackupFile(shardUuid); - assertTrue(Files.equal(tempFile, backupFile)); + assertThat(Files.equal(tempFile, backupFile)).isTrue(); // write corrupt storage file with wrong length File storageFile = storageService.getStorageFile(shardUuid); @@ -143,20 +139,21 @@ public void testShardRecoveryExistingFileSizeMismatch() writeString(storageFile.toPath(), "bad data"); - assertTrue(storageFile.exists()); - assertNotEquals(storageFile.length(), tempFile.length()); - assertFalse(Files.equal(storageFile, tempFile)); + assertThat(storageFile.exists()).isTrue(); + assertThat(storageFile.length()) + .isNotEqualTo(tempFile.length()); + assertThat(Files.equal(storageFile, tempFile)).isFalse(); // restore from backup and verify recoveryManager.restoreFromBackup(shardUuid, tempFile.length(), OptionalLong.empty()); - assertTrue(storageFile.exists()); - assertTrue(Files.equal(storageFile, tempFile)); + assertThat(storageFile.exists()).isTrue(); + assertThat(Files.equal(storageFile, tempFile)).isTrue(); // verify quarantine exists List quarantined = listFiles(storageService.getQuarantineFile(shardUuid).getParentFile()); - assertEquals(quarantined.size(), 1); - assertTrue(getOnlyElement(quarantined).startsWith(shardUuid + ".orc.corrupt")); + assertThat(quarantined.size()).isEqualTo(1); + assertThat(getOnlyElement(quarantined).startsWith(shardUuid + ".orc.corrupt")).isTrue(); } @Test @@ -170,10 +167,10 @@ public void testShardRecoveryExistingFileChecksumMismatch() writeString(tempFile.toPath(), "test data"); backupStore.backupShard(shardUuid, tempFile); - assertTrue(backupStore.shardExists(shardUuid)); + assertThat(backupStore.shardExists(shardUuid)).isTrue(); File backupFile = backupStore.getBackupFile(shardUuid); - assertTrue(Files.equal(tempFile, backupFile)); + assertThat(Files.equal(tempFile, backupFile)).isTrue(); // write corrupt storage file with wrong data File storageFile = storageService.getStorageFile(shardUuid); @@ -181,20 +178,20 @@ public void testShardRecoveryExistingFileChecksumMismatch() writeString(storageFile.toPath(), "test xata"); - assertTrue(storageFile.exists()); - assertEquals(storageFile.length(), tempFile.length()); - assertFalse(Files.equal(storageFile, tempFile)); + assertThat(storageFile.exists()).isTrue(); + assertThat(storageFile.length()).isEqualTo(tempFile.length()); + assertThat(Files.equal(storageFile, tempFile)).isFalse(); // restore from backup and verify recoveryManager.restoreFromBackup(shardUuid, tempFile.length(), OptionalLong.of(xxhash64(tempFile))); - assertTrue(storageFile.exists()); - assertTrue(Files.equal(storageFile, tempFile)); + assertThat(storageFile.exists()).isTrue(); + assertThat(Files.equal(storageFile, tempFile)).isTrue(); // verify quarantine exists List quarantined = listFiles(storageService.getQuarantineFile(shardUuid).getParentFile()); - assertEquals(quarantined.size(), 1); - assertTrue(getOnlyElement(quarantined).startsWith(shardUuid + ".orc.corrupt")); + assertThat(quarantined.size()).isEqualTo(1); + assertThat(getOnlyElement(quarantined).startsWith(shardUuid + ".orc.corrupt")).isTrue(); } @Test @@ -215,20 +212,20 @@ public void testShardRecoveryBackupChecksumMismatch() // backup and verify backupStore.backupShard(shardUuid, storageFile); - assertTrue(backupStore.shardExists(shardUuid)); + assertThat(backupStore.shardExists(shardUuid)).isTrue(); File backupFile = backupStore.getBackupFile(shardUuid); - assertTrue(Files.equal(storageFile, backupFile)); + assertThat(Files.equal(storageFile, backupFile)).isTrue(); // corrupt backup file writeString(backupFile.toPath(), "test xata"); - assertTrue(backupFile.exists()); - assertEquals(storageFile.length(), backupFile.length()); - assertFalse(Files.equal(storageFile, backupFile)); + assertThat(backupFile.exists()).isTrue(); + assertThat(storageFile.length()).isEqualTo(backupFile.length()); + assertThat(Files.equal(storageFile, backupFile)).isFalse(); // delete local file to force restore - assertTrue(storageFile.delete()); - assertFalse(storageFile.exists()); + assertThat(storageFile.delete()).isTrue(); + assertThat(storageFile.exists()).isFalse(); // restore should fail assertTrinoExceptionThrownBy(() -> recoveryManager.restoreFromBackup(shardUuid, size, OptionalLong.of(xxhash64))) @@ -237,8 +234,8 @@ public void testShardRecoveryBackupChecksumMismatch() // verify quarantine exists List quarantined = listFiles(storageService.getQuarantineFile(shardUuid).getParentFile()); - assertEquals(quarantined.size(), 1); - assertTrue(getOnlyElement(quarantined).startsWith(shardUuid + ".orc.corrupt")); + assertThat(quarantined.size()).isEqualTo(1); + assertThat(getOnlyElement(quarantined).startsWith(shardUuid + ".orc.corrupt")).isTrue(); } @Test @@ -268,7 +265,7 @@ public static ShardRecoveryManager createShardRecoveryManager( private static List listFiles(File path) { String[] files = path.list(); - assertNotNull(files); + assertThat(files).isNotNull(); return ImmutableList.copyOf(files); } } diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestShardWriter.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestShardWriter.java index 3979a5c1588a..604f9ae9311a 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestShardWriter.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/TestShardWriter.java @@ -57,12 +57,9 @@ import static io.trino.testing.StructuralTestUtil.sqlMapOf; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; import static java.nio.file.Files.createTempDirectory; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -113,77 +110,77 @@ public void testWriter() try (OrcDataSource dataSource = fileOrcDataSource(file)) { OrcRecordReader reader = createReader(dataSource, columnIds, columnTypes); - assertEquals(reader.getReaderRowCount(), 3); - assertEquals(reader.getReaderPosition(), 0); - assertEquals(reader.getFileRowCount(), reader.getReaderRowCount()); - assertEquals(reader.getFilePosition(), reader.getFilePosition()); + assertThat(reader.getReaderRowCount()).isEqualTo(3); + assertThat(reader.getReaderPosition()).isEqualTo(0); + assertThat(reader.getFileRowCount()).isEqualTo(reader.getReaderRowCount()); + assertThat(reader.getFilePosition()).isEqualTo(reader.getFilePosition()); Page page = reader.nextPage(); - assertEquals(page.getPositionCount(), 3); - assertEquals(reader.getReaderPosition(), 0); - assertEquals(reader.getFilePosition(), reader.getFilePosition()); + assertThat(page.getPositionCount()).isEqualTo(3); + assertThat(reader.getReaderPosition()).isEqualTo(0); + assertThat(reader.getFilePosition()).isEqualTo(reader.getFilePosition()); Block column0 = page.getBlock(0); - assertEquals(column0.isNull(0), false); - assertEquals(column0.isNull(1), true); - assertEquals(column0.isNull(2), false); - assertEquals(BIGINT.getLong(column0, 0), 123L); - assertEquals(BIGINT.getLong(column0, 2), 456L); + assertThat(column0.isNull(0)).isEqualTo(false); + assertThat(column0.isNull(1)).isEqualTo(true); + assertThat(column0.isNull(2)).isEqualTo(false); + assertThat(BIGINT.getLong(column0, 0)).isEqualTo(123L); + assertThat(BIGINT.getLong(column0, 2)).isEqualTo(456L); Block column1 = page.getBlock(1); - assertEquals(createVarcharType(10).getSlice(column1, 0), utf8Slice("hello")); - assertEquals(createVarcharType(10).getSlice(column1, 1), utf8Slice("world")); - assertEquals(createVarcharType(10).getSlice(column1, 2), utf8Slice("bye \u2603")); + assertThat(createVarcharType(10).getSlice(column1, 0)).isEqualTo(utf8Slice("hello")); + assertThat(createVarcharType(10).getSlice(column1, 1)).isEqualTo(utf8Slice("world")); + assertThat(createVarcharType(10).getSlice(column1, 2)).isEqualTo(utf8Slice("bye \u2603")); Block column2 = page.getBlock(2); - assertEquals(VARBINARY.getSlice(column2, 0), wrappedBuffer(bytes1)); - assertEquals(column2.isNull(1), true); - assertEquals(VARBINARY.getSlice(column2, 2), wrappedBuffer(bytes3)); + assertThat(VARBINARY.getSlice(column2, 0)).isEqualTo(wrappedBuffer(bytes1)); + assertThat(column2.isNull(1)).isEqualTo(true); + assertThat(VARBINARY.getSlice(column2, 2)).isEqualTo(wrappedBuffer(bytes3)); Block column3 = page.getBlock(3); - assertEquals(column3.isNull(0), false); - assertEquals(column3.isNull(1), false); - assertEquals(column3.isNull(2), false); - assertEquals(DOUBLE.getDouble(column3, 0), 123.456); - assertEquals(DOUBLE.getDouble(column3, 1), Double.POSITIVE_INFINITY); - assertEquals(DOUBLE.getDouble(column3, 2), Double.NaN); + assertThat(column3.isNull(0)).isEqualTo(false); + assertThat(column3.isNull(1)).isEqualTo(false); + assertThat(column3.isNull(2)).isEqualTo(false); + assertThat(DOUBLE.getDouble(column3, 0)).isEqualTo(123.456); + assertThat(DOUBLE.getDouble(column3, 1)).isEqualTo(Double.POSITIVE_INFINITY); + assertThat(DOUBLE.getDouble(column3, 2)).isNaN(); Block column4 = page.getBlock(4); - assertEquals(column4.isNull(0), false); - assertEquals(column4.isNull(1), true); - assertEquals(column4.isNull(2), false); - assertEquals(BOOLEAN.getBoolean(column4, 0), true); - assertEquals(BOOLEAN.getBoolean(column4, 2), false); + assertThat(column4.isNull(0)).isEqualTo(false); + assertThat(column4.isNull(1)).isEqualTo(true); + assertThat(column4.isNull(2)).isEqualTo(false); + assertThat(BOOLEAN.getBoolean(column4, 0)).isEqualTo(true); + assertThat(BOOLEAN.getBoolean(column4, 2)).isEqualTo(false); Block column5 = page.getBlock(5); - assertEquals(column5.getPositionCount(), 3); + assertThat(column5.getPositionCount()).isEqualTo(3); - assertTrue(arrayBlocksEqual(BIGINT, arrayType.getObject(column5, 0), arrayBlockOf(BIGINT, 1, 2))); - assertTrue(arrayBlocksEqual(BIGINT, arrayType.getObject(column5, 1), arrayBlockOf(BIGINT, 3, null))); - assertTrue(arrayBlocksEqual(BIGINT, arrayType.getObject(column5, 2), arrayBlockOf(BIGINT))); + assertThat(arrayBlocksEqual(BIGINT, arrayType.getObject(column5, 0), arrayBlockOf(BIGINT, 1, 2))).isTrue(); + assertThat(arrayBlocksEqual(BIGINT, arrayType.getObject(column5, 1), arrayBlockOf(BIGINT, 3, null))).isTrue(); + assertThat(arrayBlocksEqual(BIGINT, arrayType.getObject(column5, 2), arrayBlockOf(BIGINT))).isTrue(); Block column6 = page.getBlock(6); - assertEquals(column6.getPositionCount(), 3); + assertThat(column6.getPositionCount()).isEqualTo(3); - assertTrue(sqlMapEqual(createVarcharType(5), BOOLEAN, (SqlMap) mapType.getObject(column6, 0), sqlMapOf(createVarcharType(5), BOOLEAN, "k1", true))); + assertThat(sqlMapEqual(createVarcharType(5), BOOLEAN, (SqlMap) mapType.getObject(column6, 0), sqlMapOf(createVarcharType(5), BOOLEAN, "k1", true))).isTrue(); SqlMap object = (SqlMap) mapType.getObject(column6, 1); SqlMap k2 = sqlMapOf(createVarcharType(5), BOOLEAN, "k2", null); - assertTrue(sqlMapEqual(createVarcharType(5), BOOLEAN, object, k2)); - assertTrue(sqlMapEqual(createVarcharType(5), BOOLEAN, (SqlMap) mapType.getObject(column6, 2), sqlMapOf(createVarcharType(5), BOOLEAN, "k3", false))); + assertThat(sqlMapEqual(createVarcharType(5), BOOLEAN, object, k2)).isTrue(); + assertThat(sqlMapEqual(createVarcharType(5), BOOLEAN, (SqlMap) mapType.getObject(column6, 2), sqlMapOf(createVarcharType(5), BOOLEAN, "k3", false))).isTrue(); Block column7 = page.getBlock(7); - assertEquals(column7.getPositionCount(), 3); + assertThat(column7.getPositionCount()).isEqualTo(3); - assertTrue(arrayBlocksEqual(arrayType, arrayOfArrayType.getObject(column7, 0), arrayBlockOf(arrayType, arrayBlockOf(BIGINT, 5)))); - assertTrue(arrayBlocksEqual(arrayType, arrayOfArrayType.getObject(column7, 1), arrayBlockOf(arrayType, null, arrayBlockOf(BIGINT, 6, 7)))); - assertTrue(arrayBlocksEqual(arrayType, arrayOfArrayType.getObject(column7, 2), arrayBlockOf(arrayType, arrayBlockOf(BIGINT)))); + assertThat(arrayBlocksEqual(arrayType, arrayOfArrayType.getObject(column7, 0), arrayBlockOf(arrayType, arrayBlockOf(BIGINT, 5)))).isTrue(); + assertThat(arrayBlocksEqual(arrayType, arrayOfArrayType.getObject(column7, 1), arrayBlockOf(arrayType, null, arrayBlockOf(BIGINT, 6, 7)))).isTrue(); + assertThat(arrayBlocksEqual(arrayType, arrayOfArrayType.getObject(column7, 2), arrayBlockOf(arrayType, arrayBlockOf(BIGINT)))).isTrue(); - assertNull(reader.nextPage()); - assertEquals(reader.getReaderPosition(), 3); - assertEquals(reader.getFilePosition(), reader.getFilePosition()); + assertThat(reader.nextPage()).isNull(); + assertThat(reader.getReaderPosition()).isEqualTo(3); + assertThat(reader.getFilePosition()).isEqualTo(reader.getFilePosition()); OrcFileMetadata orcFileMetadata = METADATA_CODEC.fromJson(reader.getUserMetadata().get(OrcFileMetadata.KEY).getBytes()); - assertEquals(orcFileMetadata, new OrcFileMetadata(ImmutableMap.builder() + assertThat(orcFileMetadata).isEqualTo(new OrcFileMetadata(ImmutableMap.builder() .put(1L, BIGINT.getTypeId()) .put(2L, createVarcharType(10).getTypeId()) .put(4L, VARBINARY.getTypeId()) @@ -196,7 +193,7 @@ public void testWriter() } File crcFile = new File(file.getParentFile(), "." + file.getName() + ".crc"); - assertFalse(crcFile.exists()); + assertThat(crcFile.exists()).isFalse(); } @SuppressWarnings("EmptyClass") diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestCompactionSetCreator.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestCompactionSetCreator.java index 82c6c14a95b7..62fe27c7d823 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestCompactionSetCreator.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestCompactionSetCreator.java @@ -32,8 +32,7 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static io.trino.spi.type.DateType.DATE; import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestCompactionSetCreator { @@ -55,8 +54,8 @@ public void testNonTemporalOrganizationSetSimple() shardWithSize(10, 10)); Set compactionSets = compactionSetCreator.createCompactionSets(tableInfo, inputShards); - assertEquals(compactionSets.size(), 1); - assertEquals(getOnlyElement(compactionSets).getShards(), extractIndexes(inputShards, 0, 1, 2)); + assertThat(compactionSets.size()).isEqualTo(1); + assertThat(getOnlyElement(compactionSets).getShards()).isEqualTo(extractIndexes(inputShards, 0, 1, 2)); } @Test @@ -74,7 +73,7 @@ public void testNonTemporalSizeBasedOrganizationSet() for (OrganizationSet set : compactionSets) { actual.addAll(set.getShards()); } - assertTrue(extractIndexes(inputShards, 0, 1, 2).containsAll(actual)); + assertThat(extractIndexes(inputShards, 0, 1, 2).containsAll(actual)).isTrue(); } @Test @@ -93,7 +92,7 @@ public void testNonTemporalRowCountBasedOrganizationSet() actual.addAll(set.getShards()); } - assertTrue(extractIndexes(inputShards, 0, 2, 3).containsAll(actual)); + assertThat(extractIndexes(inputShards, 0, 2, 3).containsAll(actual)).isTrue(); } @Test @@ -111,12 +110,12 @@ public void testTemporalCompactionNoCompactionAcrossDays() shardWithTemporalRange(TIMESTAMP_MILLIS, day3, day3)); Set actual = compactionSetCreator.createCompactionSets(temporalTableInfo, inputShards); - assertEquals(actual.size(), 2); + assertThat(actual.size()).isEqualTo(2); Set expected = ImmutableSet.of( new OrganizationSet(temporalTableInfo.getTableId(), extractIndexes(inputShards, 0, 3), OptionalInt.empty()), new OrganizationSet(temporalTableInfo.getTableId(), extractIndexes(inputShards, 1, 2), OptionalInt.empty())); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } @Test @@ -139,12 +138,12 @@ public void testTemporalCompactionSpanningDays() long tableId = temporalTableInfo.getTableId(); Set compactionSets = compactionSetCreator.createCompactionSets(temporalTableInfo, inputShards); - assertEquals(compactionSets.size(), 2); + assertThat(compactionSets.size()).isEqualTo(2); Set expected = ImmutableSet.of( new OrganizationSet(tableId, extractIndexes(inputShards, 0, 1, 5, 6), OptionalInt.empty()), new OrganizationSet(tableId, extractIndexes(inputShards, 2, 3, 4), OptionalInt.empty())); - assertEquals(compactionSets, expected); + assertThat(compactionSets).isEqualTo(expected); } @Test @@ -165,12 +164,12 @@ public void testTemporalCompactionDate() long tableId = temporalTableInfo.getTableId(); Set actual = compactionSetCreator.createCompactionSets(temporalTableInfo, inputShards); - assertEquals(actual.size(), 2); + assertThat(actual.size()).isEqualTo(2); Set expected = ImmutableSet.of( new OrganizationSet(tableId, extractIndexes(inputShards, 0, 3, 5), OptionalInt.empty()), new OrganizationSet(tableId, extractIndexes(inputShards, 1, 4), OptionalInt.empty())); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } @Test @@ -187,12 +186,12 @@ public void testBucketedTableCompaction() long tableId = bucketedTableInfo.getTableId(); Set actual = compactionSetCreator.createCompactionSets(bucketedTableInfo, inputShards); - assertEquals(actual.size(), 2); + assertThat(actual.size()).isEqualTo(2); Set expected = ImmutableSet.of( new OrganizationSet(tableId, extractIndexes(inputShards, 0, 3, 5), OptionalInt.of(1)), new OrganizationSet(tableId, extractIndexes(inputShards, 1, 2, 4), OptionalInt.of(2))); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } static Set extractIndexes(List inputShards, int... indexes) @@ -223,12 +222,12 @@ public void testBucketedTemporalTableCompaction() long tableId = bucketedTemporalTableInfo.getTableId(); Set actual = compactionSetCreator.createCompactionSets(bucketedTemporalTableInfo, inputShards); - assertEquals(actual.size(), 2); + assertThat(actual.size()).isEqualTo(2); Set expected = ImmutableSet.of( new OrganizationSet(tableId, extractIndexes(inputShards, 0, 2), OptionalInt.of(1)), new OrganizationSet(tableId, extractIndexes(inputShards, 1, 3), OptionalInt.of(2))); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } private static ShardIndexInfo shardWithSize(long rows, long size) diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardCompactor.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardCompactor.java index ca6a3d739575..daf49460b080 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardCompactor.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardCompactor.java @@ -71,7 +71,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; @TestInstance(PER_METHOD) @Execution(SAME_THREAD) @@ -120,7 +119,7 @@ public void testShardCompactor() List columnTypes = ImmutableList.of(BIGINT, createVarcharType(20), DOUBLE, DATE, TIMESTAMP_MILLIS); List inputShards = createShards(storageManager, columnIds, columnTypes, 3); - assertEquals(inputShards.size(), 3); + assertThat(inputShards.size()).isEqualTo(3); long totalRows = inputShards.stream() .mapToLong(ShardInfo::getRowCount) @@ -131,7 +130,7 @@ public void testShardCompactor() long transactionId = 1; List outputShards = compactor.compact(transactionId, OptionalInt.empty(), inputUuids, getColumnInfo(columnIds, columnTypes)); - assertEquals(outputShards.size(), expectedOutputShards); + assertThat(outputShards.size()).isEqualTo(expectedOutputShards); Set outputUuids = outputShards.stream().map(ShardInfo::getShardUuid).collect(toSet()); assertShardEqualsIgnoreOrder(inputUuids, outputUuids, columnIds, columnTypes); @@ -150,7 +149,7 @@ public void testShardCompactorSorted() .collect(toList()); List inputShards = createSortedShards(storageManager, columnIds, columnTypes, sortIndexes, sortOrders, 2); - assertEquals(inputShards.size(), 2); + assertThat(inputShards.size()).isEqualTo(2); long totalRows = inputShards.stream().mapToLong(ShardInfo::getRowCount).sum(); long expectedOutputShards = computeExpectedOutputShards(totalRows); @@ -162,7 +161,7 @@ public void testShardCompactorSorted() List outputUuids = outputShards.stream() .map(ShardInfo::getShardUuid) .collect(toList()); - assertEquals(outputShards.size(), expectedOutputShards); + assertThat(outputShards.size()).isEqualTo(expectedOutputShards); assertShardEqualsSorted(inputUuids, outputUuids, columnIds, columnTypes, sortIndexes, sortOrders); } diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizationManager.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizationManager.java index 17f99cf2bf36..9115d1b3b222 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizationManager.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizationManager.java @@ -49,9 +49,9 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.stream.Collectors.toSet; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; @TestInstance(PER_METHOD) @Execution(SAME_THREAD) @@ -93,7 +93,7 @@ public void testOrganizationEligibleTables() metadataDao.insertTable("schema", "table2", false, true, null, 0); metadataDao.insertTable("schema", "table3", false, false, null, 0); - assertEquals(metadataDao.getOrganizationEligibleTables(), ImmutableSet.of(table1)); + assertThat(metadataDao.getOrganizationEligibleTables()).isEqualTo(ImmutableSet.of(table1)); } @Test @@ -113,7 +113,7 @@ public void testTableDiscovery() // initializes tables Set actual = organizationManager.discoverAndInitializeTablesToOrganize(); - assertEquals(actual, ImmutableSet.of(table1, table2)); + assertThat(actual).isEqualTo(ImmutableSet.of(table1, table2)); // update the start times and test that the tables are discovered after interval seconds long updateTime = System.currentTimeMillis(); @@ -126,7 +126,7 @@ public void testTableDiscovery() nanosSince(start).toMillis() < intervalMillis + 1000) { MILLISECONDS.sleep(10); } - assertEquals(organizationManager.discoverAndInitializeTablesToOrganize(), ImmutableSet.of(table1, table2)); + assertThat(organizationManager.discoverAndInitializeTablesToOrganize()).isEqualTo(ImmutableSet.of(table1, table2)); } @Test @@ -142,9 +142,9 @@ public void testSimple() shardWithSortRange(1, ShardRange.of(new Tuple(types, 1L, "hello", day, timestamp), new Tuple(types, 5L, "hello", day, timestamp)))); Set actual = createOrganizationSets(tableInfo, shards); - assertEquals(actual.size(), 1); + assertThat(actual.size()).isEqualTo(1); // Shards 0, 1 and 2 are overlapping, so we should get an organization set with these shards - assertEquals(getOnlyElement(actual).getShards(), extractIndexes(shards, 0, 1, 2)); + assertThat(getOnlyElement(actual).getShards()).isEqualTo(extractIndexes(shards, 0, 1, 2)); } @Test @@ -170,8 +170,8 @@ public void testSimpleTemporal() .collect(toSet()); // expect 2 organization sets, of overlapping shards (0, 2) and (1, 3) - assertEquals(organizationSets.size(), 2); - assertEquals(actual, ImmutableSet.of(extractIndexes(shards, 0, 2), extractIndexes(shards, 1, 3))); + assertThat(organizationSets.size()).isEqualTo(2); + assertThat(actual).isEqualTo(ImmutableSet.of(extractIndexes(shards, 0, 2), extractIndexes(shards, 1, 3))); } private static ShardIndexInfo shardWithSortRange(int bucketNumber, ShardRange sortRange) diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizer.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizer.java index 5fff0cc87dc4..9ba90eace90f 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizer.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizer.java @@ -24,9 +24,7 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.util.concurrent.Uninterruptibles.sleepUninterruptibly; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestShardOrganizer { @@ -42,14 +40,14 @@ public void testShardOrganizerInProgress() organizer.enqueue(organizationSet); - assertTrue(organizer.inProgress(getOnlyElement(shards))); - assertEquals(organizer.getShardsInProgress(), 1); + assertThat(organizer.inProgress(getOnlyElement(shards))).isTrue(); + assertThat(organizer.getShardsInProgress()).isEqualTo(1); while (organizer.inProgress(getOnlyElement(shards))) { MILLISECONDS.sleep(10); } - assertFalse(organizer.inProgress(getOnlyElement(shards))); - assertEquals(organizer.getShardsInProgress(), 0); + assertThat(organizer.inProgress(getOnlyElement(shards))).isFalse(); + assertThat(organizer.getShardsInProgress()).isEqualTo(0); organizer.shutdown(); } diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizerUtil.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizerUtil.java index 8e197063adfb..7faed0e48c2a 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizerUtil.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizerUtil.java @@ -60,9 +60,9 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.spi.type.VarcharType.createVarcharType; import static io.trino.testing.TestingConnectorSession.SESSION; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; @TestInstance(PER_METHOD) @Execution(SAME_THREAD) @@ -175,13 +175,13 @@ public void testGetOrganizationEligibleShards() Set actual = ImmutableSet.copyOf(getOrganizationEligibleShards(dbi, metadataDao, tableInfo, shardMetadatas, false)); List expected = getShardIndexInfo(tableInfo, shards, temporalColumn, Optional.empty()); - assertEquals(actual, expected); + assertThat(actual).containsExactlyElementsOf(expected); List sortColumns = metadataDao.listSortColumns(tableInfo.getTableId()); Set actualSortRange = ImmutableSet.copyOf(getOrganizationEligibleShards(dbi, metadataDao, tableInfo, shardMetadatas, true)); List expectedSortRange = getShardIndexInfo(tableInfo, shards, temporalColumn, Optional.of(sortColumns)); - assertEquals(actualSortRange, expectedSortRange); + assertThat(actualSortRange).containsExactlyElementsOf(expectedSortRange); } private static List getShardIndexInfo(Table tableInfo, List shards, TableColumn temporalColumn, Optional> sortColumns) diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardRange.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardRange.java index 1dbcf0d1c284..a85d24c2cb23 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardRange.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardRange.java @@ -23,8 +23,7 @@ import static io.trino.spi.type.BooleanType.BOOLEAN; import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; import static io.trino.spi.type.VarcharType.VARCHAR; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestShardRange { @@ -33,7 +32,7 @@ public void testEnclosesIsSymmetric() { List types = ImmutableList.of(BIGINT, VARCHAR, BOOLEAN, TIMESTAMP_MILLIS); ShardRange range = ShardRange.of(new Tuple(types, 2L, "aaa", true, 1L), new Tuple(types, 5L, "ccc", false, 2L)); - assertTrue(range.encloses(range)); + assertThat(range.encloses(range)).isTrue(); } @Test @@ -45,17 +44,17 @@ public void testEnclosingRange() ShardRange enclosesRange1 = ShardRange.of(new Tuple(types1, 1L), new Tuple(types1, 10L)); ShardRange notEnclosesRange1 = ShardRange.of(new Tuple(types1, 1L), new Tuple(types1, 4L)); - assertTrue(enclosesRange1.encloses(range1)); - assertFalse(notEnclosesRange1.encloses(range1)); + assertThat(enclosesRange1.encloses(range1)).isTrue(); + assertThat(notEnclosesRange1.encloses(range1)).isFalse(); List types2 = ImmutableList.of(BIGINT, VARCHAR); ShardRange range2 = ShardRange.of(new Tuple(types2, 2L, "aaa"), new Tuple(types2, 5L, "ccc")); ShardRange enclosesRange2 = ShardRange.of(new Tuple(types2, 1L, "ccc"), new Tuple(types2, 10L, "ccc")); ShardRange notEnclosesRange2 = ShardRange.of(new Tuple(types2, 2L, "aaa"), new Tuple(types2, 5L, "bbb")); - assertTrue(range2.encloses(range2)); - assertTrue(enclosesRange2.encloses(range2)); - assertFalse(notEnclosesRange2.encloses(range2)); + assertThat(range2.encloses(range2)).isTrue(); + assertThat(enclosesRange2.encloses(range2)).isTrue(); + assertThat(notEnclosesRange2.encloses(range2)).isFalse(); } @Test @@ -63,7 +62,7 @@ public void testOverlapsIsSymmetric() { List types = ImmutableList.of(BIGINT, VARCHAR, BOOLEAN, TIMESTAMP_MILLIS); ShardRange range = ShardRange.of(new Tuple(types, 2L, "aaa", true, 1L), new Tuple(types, 5L, "ccc", false, 2L)); - assertTrue(range.overlaps(range)); + assertThat(range.overlaps(range)).isTrue(); } @Test @@ -76,9 +75,9 @@ public void testOverlappingRange() ShardRange overlapsRange1 = ShardRange.of(new Tuple(types1, 1L), new Tuple(types1, 4L)); ShardRange notOverlapsRange1 = ShardRange.of(new Tuple(types1, 6L), new Tuple(types1, 8L)); - assertTrue(enclosesRange1.overlaps(range1)); - assertTrue(overlapsRange1.overlaps(range1)); - assertFalse(notOverlapsRange1.overlaps(range1)); + assertThat(enclosesRange1.overlaps(range1)).isTrue(); + assertThat(overlapsRange1.overlaps(range1)).isTrue(); + assertThat(notOverlapsRange1.overlaps(range1)).isFalse(); List types2 = ImmutableList.of(BIGINT, VARCHAR); ShardRange range2 = ShardRange.of(new Tuple(types2, 2L, "aaa"), new Tuple(types2, 5L, "ccc")); @@ -86,9 +85,9 @@ public void testOverlappingRange() ShardRange overlapsRange2 = ShardRange.of(new Tuple(types2, 2L, "aaa"), new Tuple(types2, 5L, "bbb")); ShardRange notOverlapsRange2 = ShardRange.of(new Tuple(types2, 6L, "aaa"), new Tuple(types2, 8L, "bbb")); - assertTrue(enclosesRange2.encloses(range2)); - assertTrue(overlapsRange2.overlaps(range2)); - assertFalse(notOverlapsRange2.overlaps(range2)); + assertThat(enclosesRange2.encloses(range2)).isTrue(); + assertThat(overlapsRange2.overlaps(range2)).isTrue(); + assertThat(notOverlapsRange2.overlaps(range2)).isFalse(); } @Test @@ -98,10 +97,10 @@ public void testAdjacentRange() ShardRange range1 = ShardRange.of(new Tuple(types1, 2L), new Tuple(types1, 5L)); ShardRange adjacentRange1 = ShardRange.of(new Tuple(types1, 5L), new Tuple(types1, 10L)); - assertFalse(range1.adjacent(range1)); + assertThat(range1.adjacent(range1)).isFalse(); - assertTrue(adjacentRange1.adjacent(range1)); - assertTrue(range1.adjacent(adjacentRange1)); + assertThat(adjacentRange1.adjacent(range1)).isTrue(); + assertThat(range1.adjacent(adjacentRange1)).isTrue(); List types2 = ImmutableList.of(BIGINT, VARCHAR); ShardRange range2 = ShardRange.of(new Tuple(types2, 2L, "aaa"), new Tuple(types2, 5L, "ccc")); @@ -110,9 +109,9 @@ public void testAdjacentRange() ShardRange overlapsRange2 = ShardRange.of(new Tuple(types2, 3L, "aaa"), new Tuple(types2, 10L, "ccc")); ShardRange notAdjacentRange2 = ShardRange.of(new Tuple(types2, 6L, "ccc"), new Tuple(types2, 10L, "ccc")); - assertTrue(adjacentRange2.adjacent(range2)); - assertTrue(subsetAdjacentRange2.adjacent(range2)); - assertFalse(overlapsRange2.adjacent(range2)); - assertFalse(notAdjacentRange2.adjacent(range2)); + assertThat(adjacentRange2.adjacent(range2)).isTrue(); + assertThat(subsetAdjacentRange2.adjacent(range2)).isTrue(); + assertThat(overlapsRange2.adjacent(range2)).isFalse(); + assertThat(notAdjacentRange2.adjacent(range2)).isFalse(); } } diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestTemporalFunction.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestTemporalFunction.java index 2adf82c17483..e294f9f51d8a 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestTemporalFunction.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestTemporalFunction.java @@ -23,8 +23,8 @@ import static io.trino.spi.type.DateType.DATE; import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; import static io.trino.type.DateTimes.MICROSECONDS_PER_MILLISECOND; +import static org.assertj.core.api.Assertions.assertThat; import static org.joda.time.DateTimeZone.UTC; -import static org.testng.Assert.assertEquals; public class TestTemporalFunction { @@ -38,8 +38,8 @@ public void testDateBlock() DATE.writeLong(blockBuilder, 42); Block block = blockBuilder.build(); - assertEquals(TemporalFunction.getDay(DATE, block, 0), 13); - assertEquals(TemporalFunction.getDay(DATE, block, 1), 42); + assertThat(TemporalFunction.getDay(DATE, block, 0)).isEqualTo(13); + assertThat(TemporalFunction.getDay(DATE, block, 1)).isEqualTo(42); } @Test @@ -53,18 +53,18 @@ public void testTimestampBlock() Block block = blockBuilder.build(); - assertEquals(TemporalFunction.getDay(TIMESTAMP_MILLIS, block, 0), 1); - assertEquals(TemporalFunction.getDay(TIMESTAMP_MILLIS, block, 1), 1); + assertThat(TemporalFunction.getDay(TIMESTAMP_MILLIS, block, 0)).isEqualTo(1); + assertThat(TemporalFunction.getDay(TIMESTAMP_MILLIS, block, 1)).isEqualTo(1); } @Test public void testDateShardRange() { - assertEquals(TemporalFunction.getDayFromRange(dateRange(2, 2)), 2); - assertEquals(TemporalFunction.getDayFromRange(dateRange(13, 13)), 13); + assertThat(TemporalFunction.getDayFromRange(dateRange(2, 2))).isEqualTo(2); + assertThat(TemporalFunction.getDayFromRange(dateRange(13, 13))).isEqualTo(13); // date is determined from lowest shard - assertEquals(TemporalFunction.getDayFromRange(dateRange(2, 5)), 2); + assertThat(TemporalFunction.getDayFromRange(dateRange(2, 5))).isEqualTo(2); } @Test @@ -73,13 +73,13 @@ public void testTimestampShardRange() // The time frame should be look like following: // time range covers full day of day 1 - assertEquals(TemporalFunction.getDayFromRange(timeRange(DATE_TIME.getMillis(), Duration.ofDays(1))), 1); + assertThat(TemporalFunction.getDayFromRange(timeRange(DATE_TIME.getMillis(), Duration.ofDays(1)))).isEqualTo(1); // time range covers full day of day 1 and 2 - assertEquals(TemporalFunction.getDayFromRange(timeRange(DATE_TIME.getMillis(), Duration.ofDays(2))), 2); + assertThat(TemporalFunction.getDayFromRange(timeRange(DATE_TIME.getMillis(), Duration.ofDays(2)))).isEqualTo(2); // time range covers 13 hours of day 1 and 11 hours of day 2 - assertEquals(TemporalFunction.getDayFromRange(timeRange(DATE_TIME.getMillis() + Duration.ofHours(11).toMillis(), Duration.ofHours(24))), 1); + assertThat(TemporalFunction.getDayFromRange(timeRange(DATE_TIME.getMillis() + Duration.ofHours(11).toMillis(), Duration.ofHours(24)))).isEqualTo(1); // time range covers 11 hours of day 0 and 13 hours of day 1 - assertEquals(TemporalFunction.getDayFromRange(timeRange(DATE_TIME.getMillis() + Duration.ofHours(13).toMillis(), Duration.ofHours(24))), 2); + assertThat(TemporalFunction.getDayFromRange(timeRange(DATE_TIME.getMillis() + Duration.ofHours(13).toMillis(), Duration.ofHours(24)))).isEqualTo(2); } private static ShardRange dateRange(int start, int end) diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestTuple.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestTuple.java index f42eec0b460b..1fc358fb2297 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestTuple.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestTuple.java @@ -28,8 +28,8 @@ import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.spi.type.VarcharType.createVarcharType; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestTuple { @@ -43,7 +43,7 @@ public void testComparableTuple() Tuple greaterThanTuple1 = new Tuple(types, ImmutableList.of(1L, "hello", false, 1.2d, 11111, 1113)); Tuple lessThanTuple1 = new Tuple(types, ImmutableList.of(1L, "hello", false, 1.2d, 11111, 1111)); - assertEquals(tuple1.compareTo(equalToTuple1), 0); + assertThat(tuple1.compareTo(equalToTuple1)).isEqualTo(0); assertLessThan(tuple1.compareTo(greaterThanTuple1), 0); assertGreaterThan(tuple1.compareTo(lessThanTuple1), 0); } diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/systemtables/TestShardMetadataRecordCursor.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/systemtables/TestShardMetadataRecordCursor.java index 48a05cdc6e66..6cc425c25cf1 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/systemtables/TestShardMetadataRecordCursor.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/systemtables/TestShardMetadataRecordCursor.java @@ -62,9 +62,9 @@ import static io.trino.testing.MaterializedResult.DEFAULT_PRECISION; import static io.trino.testing.TestingConnectorSession.SESSION; import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; @TestInstance(PER_METHOD) @Execution(SAME_THREAD) @@ -145,14 +145,14 @@ public void testSimple() try (RecordCursor cursor = new ShardMetadataSystemTable(dbi).cursor(null, SESSION, tupleDomain)) { actual = getMaterializedResults(cursor, SHARD_METADATA.getColumns()); } - assertEquals(actual.size(), 3); + assertThat(actual.size()).isEqualTo(3); List expected = ImmutableList.of( new MaterializedRow(DEFAULT_PRECISION, schema, table, utf8Slice(uuid1.toString()), null, 100L, 10L, 1L, utf8Slice("0000000000001234"), null, null, null, null), new MaterializedRow(DEFAULT_PRECISION, schema, table, utf8Slice(uuid2.toString()), null, 200L, 20L, 2L, utf8Slice("cafebabedeadbeef"), null, null, null, null), new MaterializedRow(DEFAULT_PRECISION, schema, table, utf8Slice(uuid3.toString()), null, 300L, 30L, 3L, utf8Slice("fedcba0987654321"), null, null, null, null)); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } @Test @@ -176,7 +176,7 @@ public void testNoSchemaFilter() Set expected = ImmutableSet.of( metadataDao.getTableInformation("other", "orders").getTableId(), metadataDao.getTableInformation("test", "orders").getTableId()); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } @Test @@ -200,7 +200,7 @@ public void testNoTableFilter() Set expected = ImmutableSet.of( metadataDao.getTableInformation("test", "orders").getTableId(), metadataDao.getTableInformation("test", "orders2").getTableId()); - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); } private void createTable(ConnectorTableMetadata table) @@ -214,7 +214,7 @@ private static List getMaterializedResults(RecordCursor cursor, ImmutableList.Builder rowBuilder = ImmutableList.builder(); for (int i = 0; i < types.size(); i++) { - assertEquals(cursor.getType(i), types.get(i)); + assertThat(cursor.getType(i)).isEqualTo(types.get(i)); } while (cursor.advanceNextPosition()) { diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/util/TestPrioritizedFifoExecutor.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/util/TestPrioritizedFifoExecutor.java index 78e51c1922fa..d301e5eca48c 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/util/TestPrioritizedFifoExecutor.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/util/TestPrioritizedFifoExecutor.java @@ -31,11 +31,9 @@ import static com.google.common.util.concurrent.Uninterruptibles.awaitUninterruptibly; import static java.util.concurrent.Executors.newCachedThreadPool; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @TestInstance(PER_METHOD) @Execution(SAME_THREAD) @@ -76,8 +74,8 @@ public void testCounter() try { // wait for the go signal - assertTrue(awaitUninterruptibly(startLatch, 1, TimeUnit.MINUTES)); - assertFalse(futures.get(taskNumber).isDone()); + assertThat(awaitUninterruptibly(startLatch, 1, TimeUnit.MINUTES)).isTrue(); + assertThat(futures.get(taskNumber).isDone()).isFalse(); // intentional distinct read and write calls int initialCount = counter.get(); @@ -90,19 +88,19 @@ public void testCounter() } for (Future future : futures) { - assertFalse(future.isDone()); + assertThat(future.isDone()).isFalse(); } // signal go and wait for tasks to complete startLatch.countDown(); - assertTrue(awaitUninterruptibly(completeLatch, 1, TimeUnit.MINUTES)); + assertThat(awaitUninterruptibly(completeLatch, 1, TimeUnit.MINUTES)).isTrue(); - assertEquals(counter.get(), totalTasks); + assertThat(counter.get()).isEqualTo(totalTasks); // since this is a fifo executor with one thread and completeLatch is decremented inside the future, // the last future may not be done yet, but all the rest must be futures.get(futures.size() - 1).get(1, TimeUnit.MINUTES); for (Future future : futures) { - assertTrue(future.isDone()); + assertThat(future.isDone()).isTrue(); } } @@ -147,8 +145,8 @@ private void testBound(int maxThreads, int totalTasks) // signal go and wait for tasks to complete startLatch.countDown(); - assertTrue(awaitUninterruptibly(completeLatch, 1, TimeUnit.MINUTES)); + assertThat(awaitUninterruptibly(completeLatch, 1, TimeUnit.MINUTES)).isTrue(); - assertFalse(failed.get()); + assertThat(failed.get()).isFalse(); } } diff --git a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/TestFileResourceGroupConfigurationManager.java b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/TestFileResourceGroupConfigurationManager.java index 939c1741f03d..9c25aa411a09 100644 --- a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/TestFileResourceGroupConfigurationManager.java +++ b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/TestFileResourceGroupConfigurationManager.java @@ -39,10 +39,6 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; public class TestFileResourceGroupConfigurationManager { @@ -146,25 +142,25 @@ public void testConfiguration() ResourceGroupId globalId = new ResourceGroupId("global"); ResourceGroup global = new TestingResourceGroup(globalId); manager.configure(global, new SelectionContext<>(globalId, new ResourceGroupIdTemplate("global"))); - assertEquals(global.getSoftMemoryLimitBytes(), DataSize.of(1, MEGABYTE).toBytes()); - assertEquals(global.getSoftCpuLimit(), Duration.ofHours(1)); - assertEquals(global.getHardCpuLimit(), Duration.ofDays(1)); - assertEquals(global.getCpuQuotaGenerationMillisPerSecond(), 1000 * 24); - assertEquals(global.getMaxQueuedQueries(), 1000); - assertEquals(global.getHardConcurrencyLimit(), 100); - assertEquals(global.getSchedulingPolicy(), WEIGHTED); - assertEquals(global.getSchedulingWeight(), 0); - assertTrue(global.getJmxExport()); + assertThat(global.getSoftMemoryLimitBytes()).isEqualTo(DataSize.of(1, MEGABYTE).toBytes()); + assertThat(global.getSoftCpuLimit()).isEqualTo(Duration.ofHours(1)); + assertThat(global.getHardCpuLimit()).isEqualTo(Duration.ofDays(1)); + assertThat(global.getCpuQuotaGenerationMillisPerSecond()).isEqualTo(1000 * 24); + assertThat(global.getMaxQueuedQueries()).isEqualTo(1000); + assertThat(global.getHardConcurrencyLimit()).isEqualTo(100); + assertThat(global.getSchedulingPolicy()).isEqualTo(WEIGHTED); + assertThat(global.getSchedulingWeight()).isEqualTo(0); + assertThat(global.getJmxExport()).isTrue(); ResourceGroupId subId = new ResourceGroupId(globalId, "sub"); ResourceGroup sub = new TestingResourceGroup(subId); manager.configure(sub, new SelectionContext<>(subId, new ResourceGroupIdTemplate("global.sub"))); - assertEquals(sub.getSoftMemoryLimitBytes(), DataSize.of(2, MEGABYTE).toBytes()); - assertEquals(sub.getHardConcurrencyLimit(), 3); - assertEquals(sub.getMaxQueuedQueries(), 4); - assertNull(sub.getSchedulingPolicy()); - assertEquals(sub.getSchedulingWeight(), 5); - assertFalse(sub.getJmxExport()); + assertThat(sub.getSoftMemoryLimitBytes()).isEqualTo(DataSize.of(2, MEGABYTE).toBytes()); + assertThat(sub.getHardConcurrencyLimit()).isEqualTo(3); + assertThat(sub.getMaxQueuedQueries()).isEqualTo(4); + assertThat(sub.getSchedulingPolicy()).isNull(); + assertThat(sub.getSchedulingWeight()).isEqualTo(5); + assertThat(sub.getJmxExport()).isFalse(); } @Test @@ -173,16 +169,16 @@ public void testExtractVariableConfiguration() FileResourceGroupConfigurationManager manager = parse("resource_groups_config_extract_variable.json"); SelectionContext selectionContext = match(manager, userAndSourceSelectionCriteria("someuser@presto.io", "scheduler.us_east.12")); - assertEquals(selectionContext.getResourceGroupId().toString(), "global.presto:us_east:12"); + assertThat(selectionContext.getResourceGroupId().toString()).isEqualTo("global.presto:us_east:12"); TestingResourceGroup resourceGroup = new TestingResourceGroup(selectionContext.getResourceGroupId()); manager.configure(resourceGroup, selectionContext); - assertEquals(resourceGroup.getHardConcurrencyLimit(), 3); + assertThat(resourceGroup.getHardConcurrencyLimit()).isEqualTo(3); selectionContext = match(manager, userAndSourceSelectionCriteria("nobody", "rg-abcdefghijkl")); - assertEquals(selectionContext.getResourceGroupId().toString(), "global.abcdefghijkl"); + assertThat(selectionContext.getResourceGroupId().toString()).isEqualTo("global.abcdefghijkl"); resourceGroup = new TestingResourceGroup(selectionContext.getResourceGroupId()); manager.configure(resourceGroup, selectionContext); - assertEquals(resourceGroup.getHardConcurrencyLimit(), 115); + assertThat(resourceGroup.getHardConcurrencyLimit()).isEqualTo(115); } @Test @@ -203,12 +199,12 @@ public void testDocsExample() ImmutableSet.of("hipri"), EMPTY_RESOURCE_ESTIMATES, Optional.of("select"))); - assertEquals(selectionContext.getResourceGroupId().toString(), "global.adhoc.bi-powerfulbi.Alice"); + assertThat(selectionContext.getResourceGroupId().toString()).isEqualTo("global.adhoc.bi-powerfulbi.Alice"); TestingResourceGroup resourceGroup = new TestingResourceGroup(selectionContext.getResourceGroupId()); manager.configure(resourceGroup, selectionContext); - assertEquals(resourceGroup.getHardConcurrencyLimit(), 3); - assertEquals(resourceGroup.getMaxQueuedQueries(), 10); - assertEquals(resourceGroup.getSoftMemoryLimitBytes(), memoryPoolSize / 10); + assertThat(resourceGroup.getHardConcurrencyLimit()).isEqualTo(3); + assertThat(resourceGroup.getMaxQueuedQueries()).isEqualTo(10); + assertThat(resourceGroup.getSoftMemoryLimitBytes()).isEqualTo(memoryPoolSize / 10); } @Test @@ -218,15 +214,17 @@ public void testLegacyConfiguration() ResourceGroupId globalId = new ResourceGroupId("global"); ResourceGroup global = new TestingResourceGroup(globalId); manager.configure(global, new SelectionContext<>(globalId, new ResourceGroupIdTemplate("global"))); - assertEquals(global.getSoftMemoryLimitBytes(), DataSize.of(3, MEGABYTE).toBytes()); - assertEquals(global.getMaxQueuedQueries(), 99); - assertEquals(global.getHardConcurrencyLimit(), 42); + assertThat(global.getSoftMemoryLimitBytes()).isEqualTo(DataSize.of(3, MEGABYTE).toBytes()); + assertThat(global.getMaxQueuedQueries()).isEqualTo(99); + assertThat(global.getHardConcurrencyLimit()).isEqualTo(42); } private static void assertMatch(FileResourceGroupConfigurationManager manager, SelectionCriteria criteria, String expectedResourceGroup) { ResourceGroupId resourceGroupId = match(manager, criteria).getResourceGroupId(); - assertEquals(resourceGroupId.toString(), expectedResourceGroup, format("Expected: '%s' resource group, found: %s", expectedResourceGroup, resourceGroupId)); + assertThat(resourceGroupId.toString()) + .describedAs(format("Expected: '%s' resource group, found: %s", expectedResourceGroup, resourceGroupId)) + .isEqualTo(expectedResourceGroup); } private static SelectionContext match(FileResourceGroupConfigurationManager manager, SelectionCriteria criteria) diff --git a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/TestResourceGroupIdTemplate.java b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/TestResourceGroupIdTemplate.java index 74e038862f67..38013ee00add 100644 --- a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/TestResourceGroupIdTemplate.java +++ b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/TestResourceGroupIdTemplate.java @@ -24,9 +24,8 @@ import java.util.Optional; import java.util.regex.Pattern; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; public class TestResourceGroupIdTemplate { @@ -37,9 +36,9 @@ public void testExpansion() { ResourceGroupIdTemplate template = new ResourceGroupIdTemplate("test.${USER}.${SOURCE}"); ResourceGroupId expected = new ResourceGroupId(new ResourceGroupId(new ResourceGroupId("test"), "u"), "s"); - assertEquals(template.expandTemplate(new VariableMap(ImmutableMap.of("USER", "u", "SOURCE", "s"))), expected); + assertThat(template.expandTemplate(new VariableMap(ImmutableMap.of("USER", "u", "SOURCE", "s")))).isEqualTo(expected); template = new ResourceGroupIdTemplate("test.${USER}"); - assertEquals(template.expandTemplate(new VariableMap(ImmutableMap.of("USER", "alice.smith", "SOURCE", "s"))), new ResourceGroupId(new ResourceGroupId("test"), "alice.smith")); + assertThat(template.expandTemplate(new VariableMap(ImmutableMap.of("USER", "alice.smith", "SOURCE", "s")))).isEqualTo(new ResourceGroupId(new ResourceGroupId("test"), "alice.smith")); } @Test @@ -52,7 +51,7 @@ public void testExtraction() StaticSelector selector = new StaticSelector(Optional.empty(), Optional.empty(), Optional.of(sourcePattern), Optional.empty(), Optional.empty(), Optional.empty(), template); SelectionCriteria context = new SelectionCriteria(true, "user", ImmutableSet.of(), Optional.of("scheduler.important.testpipeline[5]"), ImmutableSet.of(), EMPTY_RESOURCE_ESTIMATES, Optional.empty()); - assertEquals(selector.match(context).map(SelectionContext::getResourceGroupId), Optional.of(expected)); + assertThat(selector.match(context).map(SelectionContext::getResourceGroupId)).isEqualTo(Optional.of(expected)); } @Test @@ -65,7 +64,7 @@ public void testNoSource() StaticSelector selector = new StaticSelector(Optional.of(userPattern), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), template); SelectionCriteria context = new SelectionCriteria(true, "scheduler.important.testpipeline[5]", ImmutableSet.of(), Optional.empty(), ImmutableSet.of(), EMPTY_RESOURCE_ESTIMATES, Optional.empty()); - assertEquals(selector.match(context).map(SelectionContext::getResourceGroupId), Optional.of(expected)); + assertThat(selector.match(context).map(SelectionContext::getResourceGroupId)).isEqualTo(Optional.of(expected)); } @Test @@ -76,7 +75,7 @@ public void testNoMatch() StaticSelector selector = new StaticSelector(Optional.empty(), Optional.empty(), Optional.of(sourcePattern), Optional.empty(), Optional.empty(), Optional.empty(), template); SelectionCriteria context = new SelectionCriteria(true, "user", ImmutableSet.of(), Optional.of("scheduler.testpipeline[5]"), ImmutableSet.of(), EMPTY_RESOURCE_ESTIMATES, Optional.empty()); - assertFalse(selector.match(context).isPresent()); + assertThat(selector.match(context).isPresent()).isFalse(); } @Test diff --git a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/TestStaticSelector.java b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/TestStaticSelector.java index 2d5bfb2100e3..bc8d70ee8e40 100644 --- a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/TestStaticSelector.java +++ b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/TestStaticSelector.java @@ -31,7 +31,7 @@ import static io.airlift.units.DataSize.Unit.MEGABYTE; import static io.airlift.units.DataSize.Unit.TERABYTE; import static java.util.concurrent.TimeUnit.MINUTES; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestStaticSelector { @@ -49,9 +49,9 @@ public void testUserRegex() Optional.empty(), Optional.empty(), new ResourceGroupIdTemplate("global.foo")); - assertEquals(selector.match(newSelectionCriteria("userA", null, ImmutableSet.of("tag1"), EMPTY_RESOURCE_ESTIMATES)).map(SelectionContext::getResourceGroupId), Optional.of(resourceGroupId)); - assertEquals(selector.match(newSelectionCriteria("userB", "source", ImmutableSet.of(), EMPTY_RESOURCE_ESTIMATES)).map(SelectionContext::getResourceGroupId), Optional.of(resourceGroupId)); - assertEquals(selector.match(newSelectionCriteria("A.user", null, ImmutableSet.of("tag1"), EMPTY_RESOURCE_ESTIMATES)), Optional.empty()); + assertThat(selector.match(newSelectionCriteria("userA", null, ImmutableSet.of("tag1"), EMPTY_RESOURCE_ESTIMATES)).map(SelectionContext::getResourceGroupId)).isEqualTo(Optional.of(resourceGroupId)); + assertThat(selector.match(newSelectionCriteria("userB", "source", ImmutableSet.of(), EMPTY_RESOURCE_ESTIMATES)).map(SelectionContext::getResourceGroupId)).isEqualTo(Optional.of(resourceGroupId)); + assertThat(selector.match(newSelectionCriteria("A.user", null, ImmutableSet.of("tag1"), EMPTY_RESOURCE_ESTIMATES))).isEqualTo(Optional.empty()); } @Test @@ -66,9 +66,9 @@ public void testSourceRegex() Optional.empty(), Optional.empty(), new ResourceGroupIdTemplate("global.foo")); - assertEquals(selector.match(newSelectionCriteria("userA", null, ImmutableSet.of("tag1"), EMPTY_RESOURCE_ESTIMATES)), Optional.empty()); - assertEquals(selector.match(newSelectionCriteria("userB", "source", ImmutableSet.of(), EMPTY_RESOURCE_ESTIMATES)).map(SelectionContext::getResourceGroupId), Optional.of(resourceGroupId)); - assertEquals(selector.match(newSelectionCriteria("A.user", "a source b", ImmutableSet.of("tag1"), EMPTY_RESOURCE_ESTIMATES)).map(SelectionContext::getResourceGroupId), Optional.of(resourceGroupId)); + assertThat(selector.match(newSelectionCriteria("userA", null, ImmutableSet.of("tag1"), EMPTY_RESOURCE_ESTIMATES))).isEqualTo(Optional.empty()); + assertThat(selector.match(newSelectionCriteria("userB", "source", ImmutableSet.of(), EMPTY_RESOURCE_ESTIMATES)).map(SelectionContext::getResourceGroupId)).isEqualTo(Optional.of(resourceGroupId)); + assertThat(selector.match(newSelectionCriteria("A.user", "a source b", ImmutableSet.of("tag1"), EMPTY_RESOURCE_ESTIMATES)).map(SelectionContext::getResourceGroupId)).isEqualTo(Optional.of(resourceGroupId)); } @Test @@ -83,10 +83,10 @@ public void testClientTags() Optional.empty(), Optional.empty(), new ResourceGroupIdTemplate("global.foo")); - assertEquals(selector.match(newSelectionCriteria("userA", null, ImmutableSet.of("tag1", "tag2"), EMPTY_RESOURCE_ESTIMATES)).map(SelectionContext::getResourceGroupId), Optional.of(resourceGroupId)); - assertEquals(selector.match(newSelectionCriteria("userB", "source", ImmutableSet.of(), EMPTY_RESOURCE_ESTIMATES)), Optional.empty()); - assertEquals(selector.match(newSelectionCriteria("A.user", "a source b", ImmutableSet.of("tag1"), EMPTY_RESOURCE_ESTIMATES)), Optional.empty()); - assertEquals(selector.match(newSelectionCriteria("A.user", "a source b", ImmutableSet.of("tag1", "tag2", "tag3"), EMPTY_RESOURCE_ESTIMATES)).map(SelectionContext::getResourceGroupId), Optional.of(resourceGroupId)); + assertThat(selector.match(newSelectionCriteria("userA", null, ImmutableSet.of("tag1", "tag2"), EMPTY_RESOURCE_ESTIMATES)).map(SelectionContext::getResourceGroupId)).isEqualTo(Optional.of(resourceGroupId)); + assertThat(selector.match(newSelectionCriteria("userB", "source", ImmutableSet.of(), EMPTY_RESOURCE_ESTIMATES))).isEqualTo(Optional.empty()); + assertThat(selector.match(newSelectionCriteria("A.user", "a source b", ImmutableSet.of("tag1"), EMPTY_RESOURCE_ESTIMATES))).isEqualTo(Optional.empty()); + assertThat(selector.match(newSelectionCriteria("A.user", "a source b", ImmutableSet.of("tag1", "tag2", "tag3"), EMPTY_RESOURCE_ESTIMATES)).map(SelectionContext::getResourceGroupId)).isEqualTo(Optional.of(resourceGroupId)); } @Test @@ -110,8 +110,7 @@ public void testSelectorResourceEstimate() Optional.empty(), new ResourceGroupIdTemplate("global.foo")); - assertEquals( - smallQuerySelector.match( + assertThat(smallQuerySelector.match( newSelectionCriteria( "userA", null, @@ -120,11 +119,9 @@ public void testSelectorResourceEstimate() Optional.of(java.time.Duration.ofMinutes(4)), Optional.empty(), Optional.of(DataSize.of(400, MEGABYTE).toBytes())))) - .map(SelectionContext::getResourceGroupId), - Optional.of(resourceGroupId)); + .map(SelectionContext::getResourceGroupId)).isEqualTo(Optional.of(resourceGroupId)); - assertEquals( - smallQuerySelector.match( + assertThat(smallQuerySelector.match( newSelectionCriteria( "A.user", "a source b", @@ -133,11 +130,9 @@ public void testSelectorResourceEstimate() Optional.of(java.time.Duration.ofMinutes(4)), Optional.empty(), Optional.of(DataSize.of(600, MEGABYTE).toBytes())))) - .map(SelectionContext::getResourceGroupId), - Optional.empty()); + .map(SelectionContext::getResourceGroupId)).isEqualTo(Optional.empty()); - assertEquals( - smallQuerySelector.match( + assertThat(smallQuerySelector.match( newSelectionCriteria( "userB", "source", @@ -146,8 +141,7 @@ public void testSelectorResourceEstimate() Optional.of(java.time.Duration.ofMinutes(4)), Optional.empty(), Optional.empty()))) - .map(SelectionContext::getResourceGroupId), - Optional.empty()); + .map(SelectionContext::getResourceGroupId)).isEqualTo(Optional.empty()); StaticSelector largeQuerySelector = new StaticSelector( Optional.empty(), @@ -163,8 +157,7 @@ public void testSelectorResourceEstimate() Optional.empty(), new ResourceGroupIdTemplate("global.foo")); - assertEquals( - largeQuerySelector.match( + assertThat(largeQuerySelector.match( newSelectionCriteria( "userA", null, @@ -173,11 +166,9 @@ public void testSelectorResourceEstimate() Optional.of(java.time.Duration.ofHours(100)), Optional.empty(), Optional.of(DataSize.of(4, TERABYTE).toBytes())))) - .map(SelectionContext::getResourceGroupId), - Optional.empty()); + .map(SelectionContext::getResourceGroupId)).isEqualTo(Optional.empty()); - assertEquals( - largeQuerySelector.match( + assertThat(largeQuerySelector.match( newSelectionCriteria( "A.user", "a source b", @@ -186,11 +177,9 @@ public void testSelectorResourceEstimate() Optional.empty(), Optional.empty(), Optional.of(DataSize.of(6, TERABYTE).toBytes())))) - .map(SelectionContext::getResourceGroupId), - Optional.of(resourceGroupId)); + .map(SelectionContext::getResourceGroupId)).isEqualTo(Optional.of(resourceGroupId)); - assertEquals( - largeQuerySelector.match( + assertThat(largeQuerySelector.match( newSelectionCriteria( "userB", "source", @@ -199,8 +188,7 @@ public void testSelectorResourceEstimate() Optional.of(java.time.Duration.ofSeconds(1)), Optional.of(java.time.Duration.ofSeconds(1)), Optional.of(DataSize.of(6, TERABYTE).toBytes())))) - .map(SelectionContext::getResourceGroupId), - Optional.of(resourceGroupId)); + .map(SelectionContext::getResourceGroupId)).isEqualTo(Optional.of(resourceGroupId)); } private SelectionCriteria newSelectionCriteria(String user, String source, Set tags, ResourceEstimates resourceEstimates) diff --git a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/BaseTestDbResourceGroupsFlywayMigration.java b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/BaseTestDbResourceGroupsFlywayMigration.java index dcd9780e5061..55450c8ac400 100644 --- a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/BaseTestDbResourceGroupsFlywayMigration.java +++ b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/BaseTestDbResourceGroupsFlywayMigration.java @@ -24,9 +24,9 @@ import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) @Execution(SAME_THREAD) @@ -92,7 +92,7 @@ private void verifyResultSetCount(String sql, long expectedCount) { List results = jdbi.withHandle(handle -> handle.createQuery(sql).mapTo(String.class).list()); - assertEquals(results.size(), expectedCount); + assertThat(results.size()).isEqualTo(expectedCount); } protected void dropAllTables() diff --git a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbResourceGroupConfig.java b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbResourceGroupConfig.java index 3fac39267b5c..bfbaa0573c77 100644 --- a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbResourceGroupConfig.java +++ b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbResourceGroupConfig.java @@ -27,7 +27,7 @@ import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestDbResourceGroupConfig { @@ -63,7 +63,7 @@ public void testExplicitPropertyMappings() .setExactMatchSelectorEnabled(true); assertFullMapping(properties, expected); - assertTrue(expected.isRefreshIntervalValid()); + assertThat(expected.isRefreshIntervalValid()).isTrue(); } @Test diff --git a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbResourceGroupConfigurationManager.java b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbResourceGroupConfigurationManager.java index 8d4c2e33520e..27b75af76a12 100644 --- a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbResourceGroupConfigurationManager.java +++ b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbResourceGroupConfigurationManager.java @@ -26,6 +26,7 @@ import io.trino.spi.resourcegroups.SelectionContext; import io.trino.spi.resourcegroups.SelectionCriteria; import io.trino.spi.session.ResourceEstimates; +import org.h2.jdbc.JdbcException; import org.jdbi.v3.core.statement.UnableToExecuteStatementException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; @@ -46,9 +47,6 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestDbResourceGroupConfigurationManager { @@ -81,25 +79,25 @@ public void testEnvironments() // check the prod configuration DbResourceGroupConfigurationManager manager = new DbResourceGroupConfigurationManager(listener -> {}, new DbResourceGroupConfig(), daoProvider.get(), prodEnvironment); List groups = manager.getRootGroups(); - assertEquals(groups.size(), 1); + assertThat(groups.size()).isEqualTo(1); InternalResourceGroup prodGlobal = new InternalResourceGroup("prod_global", (group, export) -> {}, directExecutor()); manager.configure(prodGlobal, new SelectionContext<>(prodGlobal.getId(), new ResourceGroupIdTemplate("prod_global"))); assertEqualsResourceGroup(prodGlobal, "10MB", 1000, 100, 100, WEIGHTED, DEFAULT_WEIGHT, true, Duration.ofHours(1), Duration.ofDays(1)); - assertEquals(manager.getSelectors().size(), 1); + assertThat(manager.getSelectors().size()).isEqualTo(1); ResourceGroupSelector prodSelector = manager.getSelectors().get(0); ResourceGroupId prodResourceGroupId = prodSelector.match(new SelectionCriteria(true, "prod_user", ImmutableSet.of(), Optional.empty(), ImmutableSet.of(), EMPTY_RESOURCE_ESTIMATES, Optional.empty())).get().getResourceGroupId(); - assertEquals(prodResourceGroupId.toString(), "prod_global"); + assertThat(prodResourceGroupId.toString()).isEqualTo("prod_global"); // check the dev configuration manager = new DbResourceGroupConfigurationManager(listener -> {}, new DbResourceGroupConfig(), daoProvider.get(), devEnvironment); - assertEquals(groups.size(), 1); + assertThat(groups.size()).isEqualTo(1); InternalResourceGroup devGlobal = new InternalResourceGroup("dev_global", (group, export) -> {}, directExecutor()); manager.configure(devGlobal, new SelectionContext<>(prodGlobal.getId(), new ResourceGroupIdTemplate("dev_global"))); assertEqualsResourceGroup(devGlobal, "1MB", 1000, 100, 100, WEIGHTED, DEFAULT_WEIGHT, true, Duration.ofHours(1), Duration.ofDays(1)); - assertEquals(manager.getSelectors().size(), 1); + assertThat(manager.getSelectors().size()).isEqualTo(1); ResourceGroupSelector devSelector = manager.getSelectors().get(0); ResourceGroupId devResourceGroupId = devSelector.match(new SelectionCriteria(true, "dev_user", ImmutableSet.of(), Optional.empty(), ImmutableSet.of(), EMPTY_RESOURCE_ESTIMATES, Optional.empty())).get().getResourceGroupId(); - assertEquals(devResourceGroupId.toString(), "dev_global"); + assertThat(devResourceGroupId.toString()).isEqualTo("dev_global"); } @Test @@ -134,8 +132,8 @@ public void testDuplicateRoots() dao.insertResourceGroup(1, "global", "1MB", 1000, 100, 100, null, null, null, null, null, null, ENVIRONMENT); assertThatThrownBy(() -> dao.insertResourceGroup(1, "global", "1MB", 1000, 100, 100, null, null, null, null, null, null, ENVIRONMENT)) .isInstanceOfSatisfying(UnableToExecuteStatementException.class, ex -> { - assertTrue(ex.getCause() instanceof org.h2.jdbc.JdbcException); - assertTrue(ex.getCause().getMessage().startsWith("Unique index or primary key violation")); + assertThat(ex.getCause() instanceof JdbcException).isTrue(); + assertThat(ex.getCause().getMessage().startsWith("Unique index or primary key violation")).isTrue(); }); dao.insertSelector(1, 1, null, null, null, null, null, null); } @@ -152,8 +150,8 @@ public void testDuplicateGroups() dao.insertResourceGroup(2, "sub", "1MB", 1000, 100, 100, null, null, null, null, null, 1L, ENVIRONMENT); assertThatThrownBy(() -> dao.insertResourceGroup(2, "sub", "1MB", 1000, 100, 100, null, null, null, null, null, 1L, ENVIRONMENT)) .isInstanceOfSatisfying(UnableToExecuteStatementException.class, ex -> { - assertTrue(ex.getCause() instanceof org.h2.jdbc.JdbcException); - assertTrue(ex.getCause().getMessage().startsWith("Unique index or primary key violation")); + assertThat(ex.getCause() instanceof JdbcException).isTrue(); + assertThat(ex.getCause().getMessage().startsWith("Unique index or primary key violation")).isTrue(); }); dao.insertSelector(2, 2, null, null, null, null, null, null); } @@ -233,14 +231,14 @@ public void testExactMatchSelector() config.setExactMatchSelectorEnabled(true); DbResourceGroupConfigurationManager manager = new DbResourceGroupConfigurationManager(listener -> {}, config, daoProvider.get(), ENVIRONMENT); manager.load(); - assertEquals(manager.getSelectors().size(), 2); - assertTrue(manager.getSelectors().get(0) instanceof DbSourceExactMatchSelector); + assertThat(manager.getSelectors().size()).isEqualTo(2); + assertThat(manager.getSelectors().get(0) instanceof DbSourceExactMatchSelector).isTrue(); config.setExactMatchSelectorEnabled(false); manager = new DbResourceGroupConfigurationManager(listener -> {}, config, daoProvider.get(), ENVIRONMENT); manager.load(); - assertEquals(manager.getSelectors().size(), 1); - assertFalse(manager.getSelectors().get(0) instanceof DbSourceExactMatchSelector); + assertThat(manager.getSelectors().size()).isEqualTo(1); + assertThat(manager.getSelectors().get(0) instanceof DbSourceExactMatchSelector).isFalse(); } @Test @@ -274,15 +272,15 @@ public void testSelectorPriority() manager.load(); List selectors = manager.getSelectors(); - assertEquals(selectors.size(), expectedUsers.size()); + assertThat(selectors.size()).isEqualTo(expectedUsers.size()); // when we load the selectors we expect the selector list to be ordered by priority expectedUsers.sort(Comparator.comparingInt(Integer::parseInt).reversed()); for (int i = 0; i < numberOfUsers; i++) { Optional user = ((StaticSelector) selectors.get(i)).getUserRegex(); - assertTrue(user.isPresent()); - assertEquals(user.get().pattern(), expectedUsers.get(i)); + assertThat(user.isPresent()).isTrue(); + assertThat(user.get().pattern()).isEqualTo(expectedUsers.get(i)); } } @@ -393,15 +391,15 @@ private static void assertEqualsResourceGroup( Duration softCpuLimit, Duration hardCpuLimit) { - assertEquals(group.getSoftMemoryLimitBytes(), DataSize.valueOf(softMemoryLimit).toBytes()); - assertEquals(group.getMaxQueuedQueries(), maxQueued); - assertEquals(group.getHardConcurrencyLimit(), hardConcurrencyLimit); - assertEquals(group.getSoftConcurrencyLimit(), softConcurrencyLimit); - assertEquals(group.getSchedulingPolicy(), schedulingPolicy); - assertEquals(group.getSchedulingWeight(), schedulingWeight); - assertEquals(group.getJmxExport(), jmxExport); - assertEquals(group.getSoftCpuLimit(), softCpuLimit); - assertEquals(group.getHardCpuLimit(), hardCpuLimit); + assertThat(group.getSoftMemoryLimitBytes()).isEqualTo(DataSize.valueOf(softMemoryLimit).toBytes()); + assertThat(group.getMaxQueuedQueries()).isEqualTo(maxQueued); + assertThat(group.getHardConcurrencyLimit()).isEqualTo(hardConcurrencyLimit); + assertThat(group.getSoftConcurrencyLimit()).isEqualTo(softConcurrencyLimit); + assertThat(group.getSchedulingPolicy()).isEqualTo(schedulingPolicy); + assertThat(group.getSchedulingWeight()).isEqualTo(schedulingWeight); + assertThat(group.getJmxExport()).isEqualTo(jmxExport); + assertThat(group.getSoftCpuLimit()).isEqualTo(softCpuLimit); + assertThat(group.getHardCpuLimit()).isEqualTo(hardCpuLimit); } private static SelectionCriteria userGroupsSelectionCriteria(String... groups) diff --git a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbSourceExactMatchSelector.java b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbSourceExactMatchSelector.java index e6f9c4ed08a3..2449dd180457 100644 --- a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbSourceExactMatchSelector.java +++ b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestDbSourceExactMatchSelector.java @@ -28,7 +28,7 @@ import static io.trino.spi.resourcegroups.QueryType.DELETE; import static io.trino.spi.resourcegroups.QueryType.INSERT; import static io.trino.spi.resourcegroups.QueryType.SELECT; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestDbSourceExactMatchSelector { @@ -53,21 +53,11 @@ public void testMatch() DbSourceExactMatchSelector selector = new DbSourceExactMatchSelector("test", dao); - assertEquals( - selector.match(new SelectionCriteria(true, "testuser", ImmutableSet.of(), Optional.of("@test@test_pipeline"), ImmutableSet.of("tag"), EMPTY_RESOURCE_ESTIMATES, Optional.empty())), - Optional.empty()); - assertEquals( - selector.match(new SelectionCriteria(true, "testuser", ImmutableSet.of(), Optional.of("@test@test_pipeline"), ImmutableSet.of("tag"), EMPTY_RESOURCE_ESTIMATES, Optional.of(INSERT.name()))).map(SelectionContext::getResourceGroupId), - Optional.of(resourceGroupId1)); - assertEquals( - selector.match(new SelectionCriteria(true, "testuser", ImmutableSet.of(), Optional.of("@test@test_pipeline"), ImmutableSet.of("tag"), EMPTY_RESOURCE_ESTIMATES, Optional.of(SELECT.name()))).map(SelectionContext::getResourceGroupId), - Optional.of(resourceGroupId2)); - assertEquals( - selector.match(new SelectionCriteria(true, "testuser", ImmutableSet.of(), Optional.of("@test@test_pipeline"), ImmutableSet.of("tag"), EMPTY_RESOURCE_ESTIMATES, Optional.of(DELETE.name()))), - Optional.empty()); + assertThat(selector.match(new SelectionCriteria(true, "testuser", ImmutableSet.of(), Optional.of("@test@test_pipeline"), ImmutableSet.of("tag"), EMPTY_RESOURCE_ESTIMATES, Optional.empty()))).isEqualTo(Optional.empty()); + assertThat(selector.match(new SelectionCriteria(true, "testuser", ImmutableSet.of(), Optional.of("@test@test_pipeline"), ImmutableSet.of("tag"), EMPTY_RESOURCE_ESTIMATES, Optional.of(INSERT.name()))).map(SelectionContext::getResourceGroupId)).isEqualTo(Optional.of(resourceGroupId1)); + assertThat(selector.match(new SelectionCriteria(true, "testuser", ImmutableSet.of(), Optional.of("@test@test_pipeline"), ImmutableSet.of("tag"), EMPTY_RESOURCE_ESTIMATES, Optional.of(SELECT.name()))).map(SelectionContext::getResourceGroupId)).isEqualTo(Optional.of(resourceGroupId2)); + assertThat(selector.match(new SelectionCriteria(true, "testuser", ImmutableSet.of(), Optional.of("@test@test_pipeline"), ImmutableSet.of("tag"), EMPTY_RESOURCE_ESTIMATES, Optional.of(DELETE.name())))).isEqualTo(Optional.empty()); - assertEquals( - selector.match(new SelectionCriteria(true, "testuser", ImmutableSet.of(), Optional.of("@test@test_new"), ImmutableSet.of(), EMPTY_RESOURCE_ESTIMATES, Optional.of(INSERT.name()))), - Optional.empty()); + assertThat(selector.match(new SelectionCriteria(true, "testuser", ImmutableSet.of(), Optional.of("@test@test_new"), ImmutableSet.of(), EMPTY_RESOURCE_ESTIMATES, Optional.of(INSERT.name())))).isEqualTo(Optional.empty()); } } diff --git a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestResourceGroupsDao.java b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestResourceGroupsDao.java index b0bbd708a773..c00207a6b06e 100644 --- a/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestResourceGroupsDao.java +++ b/plugin/trino-resource-group-managers/src/test/java/io/trino/plugin/resourcegroups/db/TestResourceGroupsDao.java @@ -21,6 +21,7 @@ import io.trino.plugin.resourcegroups.SelectorResourceEstimate; import io.trino.plugin.resourcegroups.SelectorResourceEstimate.Range; import io.trino.spi.resourcegroups.ResourceGroupId; +import org.h2.jdbc.JdbcException; import org.jdbi.v3.core.statement.UnableToExecuteStatementException; import org.junit.jupiter.api.Test; @@ -40,9 +41,7 @@ import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestResourceGroupsDao { @@ -83,7 +82,7 @@ private static void testResourceGroupInsert(H2ResourceGroupsDao dao, Map records = dao.getResourceGroups(ENVIRONMENT); - assertEquals(records.size(), 2); + assertThat(records.size()).isEqualTo(2); map.put(1L, new ResourceGroupSpecBuilder(1, new ResourceGroupNameTemplate("global"), "100%", 100, Optional.of(100), 100, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty())); map.put(2L, new ResourceGroupSpecBuilder(2, new ResourceGroupNameTemplate("bi"), "50%", 50, Optional.of(50), 50, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.of(1L))); compareResourceGroups(map, records); @@ -248,20 +247,20 @@ public void testGlobalResourceGroupProperties() dao.insertResourceGroupsGlobalProperties("cpu_quota_period", "1h"); ResourceGroupGlobalProperties globalProperties = new ResourceGroupGlobalProperties(Optional.of(new Duration(1, HOURS))); ResourceGroupGlobalProperties records = dao.getResourceGroupGlobalProperties().get(0); - assertEquals(globalProperties, records); + assertThat(globalProperties).isEqualTo(records); try { dao.insertResourceGroupsGlobalProperties("invalid_property", "1h"); } catch (UnableToExecuteStatementException ex) { - assertTrue(ex.getCause() instanceof org.h2.jdbc.JdbcException); - assertTrue(ex.getCause().getMessage().startsWith("Check constraint violation:")); + assertThat(ex.getCause() instanceof JdbcException).isTrue(); + assertThat(ex.getCause().getMessage().startsWith("Check constraint violation:")).isTrue(); } try { dao.updateResourceGroupsGlobalProperties("invalid_property_name"); } catch (UnableToExecuteStatementException ex) { - assertTrue(ex.getCause() instanceof org.h2.jdbc.JdbcException); - assertTrue(ex.getCause().getMessage().startsWith("Check constraint violation:")); + assertThat(ex.getCause() instanceof JdbcException).isTrue(); + assertThat(ex.getCause().getMessage().startsWith("Check constraint violation:")).isTrue(); } } @@ -277,33 +276,33 @@ public void testExactMatchSelector() dao.insertExactMatchSelector("test", "@test@test_pipeline", INSERT.name(), codec.toJson(resourceGroupId1)); dao.insertExactMatchSelector("test", "@test@test_pipeline", SELECT.name(), codec.toJson(resourceGroupId2)); - assertNull(dao.getExactMatchResourceGroup("test", "@test@test_pipeline", null)); - assertEquals(dao.getExactMatchResourceGroup("test", "@test@test_pipeline", INSERT.name()), codec.toJson(resourceGroupId1)); - assertEquals(dao.getExactMatchResourceGroup("test", "@test@test_pipeline", SELECT.name()), codec.toJson(resourceGroupId2)); - assertNull(dao.getExactMatchResourceGroup("test", "@test@test_pipeline", DELETE.name())); + assertThat(dao.getExactMatchResourceGroup("test", "@test@test_pipeline", null)).isNull(); + assertThat(dao.getExactMatchResourceGroup("test", "@test@test_pipeline", INSERT.name())).isEqualTo(codec.toJson(resourceGroupId1)); + assertThat(dao.getExactMatchResourceGroup("test", "@test@test_pipeline", SELECT.name())).isEqualTo(codec.toJson(resourceGroupId2)); + assertThat(dao.getExactMatchResourceGroup("test", "@test@test_pipeline", DELETE.name())).isNull(); - assertNull(dao.getExactMatchResourceGroup("test", "abc", INSERT.name())); - assertNull(dao.getExactMatchResourceGroup("prod", "@test@test_pipeline", INSERT.name())); + assertThat(dao.getExactMatchResourceGroup("test", "abc", INSERT.name())).isNull(); + assertThat(dao.getExactMatchResourceGroup("prod", "@test@test_pipeline", INSERT.name())).isNull(); } private static void compareResourceGroups(Map map, List records) { - assertEquals(map.size(), records.size()); + assertThat(map.size()).isEqualTo(records.size()); for (ResourceGroupSpecBuilder record : records) { ResourceGroupSpecBuilder expected = map.get(record.getId()); - assertEquals(record.build(), expected.build()); + assertThat(record.build()).isEqualTo(expected.build()); } } private static void compareSelectors(Map map, List records) { - assertEquals(map.size(), records.size()); + assertThat(map.size()).isEqualTo(records.size()); for (SelectorRecord record : records) { SelectorRecord expected = map.get(record.getResourceGroupId()); - assertEquals(record.getResourceGroupId(), expected.getResourceGroupId()); - assertEquals(record.getUserRegex().map(Pattern::pattern), expected.getUserRegex().map(Pattern::pattern)); - assertEquals(record.getSourceRegex().map(Pattern::pattern), expected.getSourceRegex().map(Pattern::pattern)); - assertEquals(record.getSelectorResourceEstimate(), expected.getSelectorResourceEstimate()); + assertThat(record.getResourceGroupId()).isEqualTo(expected.getResourceGroupId()); + assertThat(record.getUserRegex().map(Pattern::pattern)).isEqualTo(expected.getUserRegex().map(Pattern::pattern)); + assertThat(record.getSourceRegex().map(Pattern::pattern)).isEqualTo(expected.getSourceRegex().map(Pattern::pattern)); + assertThat(record.getSelectorResourceEstimate()).isEqualTo(expected.getSelectorResourceEstimate()); } } } diff --git a/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/TestReadWrite.java b/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/TestReadWrite.java index c6856609716d..2163ee7bef38 100644 --- a/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/TestReadWrite.java +++ b/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/TestReadWrite.java @@ -51,8 +51,7 @@ import static java.lang.String.format; import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; +import static org.assertj.core.api.Assertions.assertThat; public class TestReadWrite { @@ -170,8 +169,8 @@ private void testReadWrite(Random random, int records, Function, Tri Page page = batch.toPage(columns.stream().map(ColumnDefinition::getType).collect(toImmutableList())); // compare the result with original input - assertNotNull(page); - assertEquals(page.getChannelCount(), columns.size()); + assertThat(page).isNotNull(); + assertThat(page.getChannelCount()).isEqualTo(columns.size()); for (int i = 0; i < columns.size(); i++) { Block actual = page.getBlock(i); Block expected = inputBlocks.get(i); @@ -195,12 +194,12 @@ private static Block generateColumn(ColumnDefinition column, Random random, int private static void assertBlock(Block actual, Block expected, ColumnDefinition columnDefinition) { - assertEquals(actual.getPositionCount(), expected.getPositionCount()); + assertThat(actual.getPositionCount()).isEqualTo(expected.getPositionCount()); int positions = actual.getPositionCount(); for (int i = 0; i < positions; i++) { Object actualValue = columnDefinition.extractValue(actual, i); Object expectedValue = columnDefinition.extractValue(expected, i); - assertEquals(actualValue, expectedValue); + assertThat(actualValue).isEqualTo(expectedValue); } } diff --git a/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/TestTrinoThriftId.java b/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/TestTrinoThriftId.java index d66ddd76e56b..a07a17ffb2fc 100644 --- a/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/TestTrinoThriftId.java +++ b/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/TestTrinoThriftId.java @@ -17,20 +17,18 @@ import org.junit.jupiter.api.Test; import static io.trino.plugin.thrift.api.TrinoThriftId.summarize; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestTrinoThriftId { @Test public void testSummarize() { - assertEquals(summarize(bytes()), ""); - assertEquals(summarize(bytes(1)), "01"); - assertEquals(summarize(bytes(255, 254, 253, 252, 251, 250, 249)), "FFFEFDFCFBFAF9"); - assertEquals(summarize(bytes(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 249, 250, 251, 252, 253, 254, 255)), - "00010203040506070809F9FAFBFCFDFEFF"); - assertEquals(summarize(bytes(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 247, 248, 249, 250, 251, 252, 253, 254, 255)), - "0001020304050607..F8F9FAFBFCFDFEFF"); + assertThat(summarize(bytes())).isEqualTo(""); + assertThat(summarize(bytes(1))).isEqualTo("01"); + assertThat(summarize(bytes(255, 254, 253, 252, 251, 250, 249))).isEqualTo("FFFEFDFCFBFAF9"); + assertThat(summarize(bytes(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 249, 250, 251, 252, 253, 254, 255))).isEqualTo("00010203040506070809F9FAFBFCFDFEFF"); + assertThat(summarize(bytes(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 247, 248, 249, 250, 251, 252, 253, 254, 255))).isEqualTo("0001020304050607..F8F9FAFBFCFDFEFF"); } private static byte[] bytes(int... values) diff --git a/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/datatypes/TestTrinoThriftBigint.java b/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/datatypes/TestTrinoThriftBigint.java index 77bc56be58b6..0679e4da8c4e 100644 --- a/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/datatypes/TestTrinoThriftBigint.java +++ b/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/datatypes/TestTrinoThriftBigint.java @@ -27,11 +27,8 @@ import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.IntegerType.INTEGER; import static java.util.Collections.unmodifiableList; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; public class TestTrinoThriftBigint { @@ -108,11 +105,9 @@ public void testWriteBlockAlternating() { Block source = longBlock(1, null, 2, null, 3, null, 4, null, 5, null, 6, null, 7, null); TrinoThriftBlock column = fromBlock(source); - assertNotNull(column.getBigintData()); - assertEquals(column.getBigintData().getNulls(), - new boolean[] {false, true, false, true, false, true, false, true, false, true, false, true, false, true}); - assertEquals(column.getBigintData().getLongs(), - new long[] {1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0}); + assertThat(column.getBigintData()).isNotNull(); + assertThat(column.getBigintData().getNulls()).isEqualTo(new boolean[] {false, true, false, true, false, true, false, true, false, true, false, true, false, true}); + assertThat(column.getBigintData().getLongs()).isEqualTo(new long[] {1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0}); } @Test @@ -120,9 +115,9 @@ public void testWriteBlockAllNulls() { Block source = longBlock(null, null, null, null, null); TrinoThriftBlock column = fromBlock(source); - assertNotNull(column.getBigintData()); - assertEquals(column.getBigintData().getNulls(), new boolean[] {true, true, true, true, true}); - assertNull(column.getBigintData().getLongs()); + assertThat(column.getBigintData()).isNotNull(); + assertThat(column.getBigintData().getNulls()).isEqualTo(new boolean[] {true, true, true, true, true}); + assertThat(column.getBigintData().getLongs()).isNull(); } @Test @@ -130,38 +125,38 @@ public void testWriteBlockAllNonNull() { Block source = longBlock(1, 2, 3, 4, 5); TrinoThriftBlock column = fromBlock(source); - assertNotNull(column.getBigintData()); - assertNull(column.getBigintData().getNulls()); - assertEquals(column.getBigintData().getLongs(), new long[] {1, 2, 3, 4, 5}); + assertThat(column.getBigintData()).isNotNull(); + assertThat(column.getBigintData().getNulls()).isNull(); + assertThat(column.getBigintData().getLongs()).isEqualTo(new long[] {1, 2, 3, 4, 5}); } @Test public void testWriteBlockEmpty() { TrinoThriftBlock column = fromBlock(longBlock()); - assertNotNull(column.getBigintData()); - assertNull(column.getBigintData().getNulls()); - assertNull(column.getBigintData().getLongs()); + assertThat(column.getBigintData()).isNotNull(); + assertThat(column.getBigintData().getNulls()).isNull(); + assertThat(column.getBigintData().getLongs()).isNull(); } @Test public void testWriteBlockSingleValue() { TrinoThriftBlock column = fromBlock(longBlock(1)); - assertNotNull(column.getBigintData()); - assertNull(column.getBigintData().getNulls()); - assertEquals(column.getBigintData().getLongs(), new long[] {1}); + assertThat(column.getBigintData()).isNotNull(); + assertThat(column.getBigintData().getNulls()).isNull(); + assertThat(column.getBigintData().getLongs()).isEqualTo(new long[] {1}); } private void assertBlockEquals(Block block, List expected) { - assertEquals(block.getPositionCount(), expected.size()); + assertThat(block.getPositionCount()).isEqualTo(expected.size()); for (int i = 0; i < expected.size(); i++) { if (expected.get(i) == null) { - assertTrue(block.isNull(i)); + assertThat(block.isNull(i)).isTrue(); } else { - assertEquals(block.getLong(i, 0), expected.get(i).longValue()); + assertThat(block.getLong(i, 0)).isEqualTo(expected.get(i).longValue()); } } } diff --git a/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/valuesets/TestTrinoThriftAllOrNoneValueSet.java b/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/valuesets/TestTrinoThriftAllOrNoneValueSet.java index c354d574b1c2..b13b8c74aa41 100644 --- a/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/valuesets/TestTrinoThriftAllOrNoneValueSet.java +++ b/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/valuesets/TestTrinoThriftAllOrNoneValueSet.java @@ -18,9 +18,7 @@ import static io.trino.plugin.thrift.api.valuesets.TrinoThriftValueSet.fromValueSet; import static io.trino.spi.type.HyperLogLogType.HYPER_LOG_LOG; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestTrinoThriftAllOrNoneValueSet { @@ -28,15 +26,15 @@ public class TestTrinoThriftAllOrNoneValueSet public void testFromValueSetAll() { TrinoThriftValueSet thriftValueSet = fromValueSet(ValueSet.all(HYPER_LOG_LOG)); - assertNotNull(thriftValueSet.getAllOrNoneValueSet()); - assertTrue(thriftValueSet.getAllOrNoneValueSet().isAll()); + assertThat(thriftValueSet.getAllOrNoneValueSet()).isNotNull(); + assertThat(thriftValueSet.getAllOrNoneValueSet().isAll()).isTrue(); } @Test public void testFromValueSetNone() { TrinoThriftValueSet thriftValueSet = fromValueSet(ValueSet.none(HYPER_LOG_LOG)); - assertNotNull(thriftValueSet.getAllOrNoneValueSet()); - assertFalse(thriftValueSet.getAllOrNoneValueSet().isAll()); + assertThat(thriftValueSet.getAllOrNoneValueSet()).isNotNull(); + assertThat(thriftValueSet.getAllOrNoneValueSet().isAll()).isFalse(); } } diff --git a/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/valuesets/TestTrinoThriftEquatableValueSet.java b/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/valuesets/TestTrinoThriftEquatableValueSet.java index f55a244689ce..5016812eca8e 100644 --- a/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/valuesets/TestTrinoThriftEquatableValueSet.java +++ b/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/valuesets/TestTrinoThriftEquatableValueSet.java @@ -23,10 +23,7 @@ import static io.trino.plugin.thrift.api.valuesets.TrinoThriftValueSet.fromValueSet; import static io.trino.type.JsonType.JSON; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestTrinoThriftEquatableValueSet { @@ -37,27 +34,27 @@ public class TestTrinoThriftEquatableValueSet public void testFromValueSetAll() { TrinoThriftValueSet thriftValueSet = fromValueSet(ValueSet.all(JSON)); - assertNotNull(thriftValueSet.getEquatableValueSet()); - assertFalse(thriftValueSet.getEquatableValueSet().isInclusive()); - assertTrue(thriftValueSet.getEquatableValueSet().getValues().isEmpty()); + assertThat(thriftValueSet.getEquatableValueSet()).isNotNull(); + assertThat(thriftValueSet.getEquatableValueSet().isInclusive()).isFalse(); + assertThat(thriftValueSet.getEquatableValueSet().getValues().isEmpty()).isTrue(); } @Test public void testFromValueSetNone() { TrinoThriftValueSet thriftValueSet = fromValueSet(ValueSet.none(JSON)); - assertNotNull(thriftValueSet.getEquatableValueSet()); - assertTrue(thriftValueSet.getEquatableValueSet().isInclusive()); - assertTrue(thriftValueSet.getEquatableValueSet().getValues().isEmpty()); + assertThat(thriftValueSet.getEquatableValueSet()).isNotNull(); + assertThat(thriftValueSet.getEquatableValueSet().isInclusive()).isTrue(); + assertThat(thriftValueSet.getEquatableValueSet().getValues().isEmpty()).isTrue(); } @Test public void testFromValueSetOf() { TrinoThriftValueSet thriftValueSet = fromValueSet(ValueSet.of(JSON, utf8Slice(JSON1), utf8Slice(JSON2))); - assertNotNull(thriftValueSet.getEquatableValueSet()); - assertTrue(thriftValueSet.getEquatableValueSet().isInclusive()); - assertEquals(thriftValueSet.getEquatableValueSet().getValues(), ImmutableList.of( + assertThat(thriftValueSet.getEquatableValueSet()).isNotNull(); + assertThat(thriftValueSet.getEquatableValueSet().isInclusive()).isTrue(); + assertThat(thriftValueSet.getEquatableValueSet().getValues()).isEqualTo(ImmutableList.of( jsonData(new TrinoThriftJson(null, new int[] {JSON1.length()}, JSON1.getBytes(UTF_8))), jsonData(new TrinoThriftJson(null, new int[] {JSON2.length()}, JSON2.getBytes(UTF_8))))); } diff --git a/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/valuesets/TestTrinoThriftRangeValueSet.java b/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/valuesets/TestTrinoThriftRangeValueSet.java index 0d24f0446dce..53a91777564a 100644 --- a/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/valuesets/TestTrinoThriftRangeValueSet.java +++ b/plugin/trino-thrift-api/src/test/java/io/trino/plugin/thrift/api/valuesets/TestTrinoThriftRangeValueSet.java @@ -29,8 +29,7 @@ import static io.trino.plugin.thrift.api.valuesets.TrinoThriftValueSet.fromValueSet; import static io.trino.spi.predicate.Range.range; import static io.trino.spi.type.BigintType.BIGINT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; +import static org.assertj.core.api.Assertions.assertThat; public class TestTrinoThriftRangeValueSet { @@ -38,8 +37,8 @@ public class TestTrinoThriftRangeValueSet public void testFromValueSetAll() { TrinoThriftValueSet thriftValueSet = fromValueSet(ValueSet.all(BIGINT)); - assertNotNull(thriftValueSet.getRangeValueSet()); - assertEquals(thriftValueSet.getRangeValueSet().getRanges(), ImmutableList.of( + assertThat(thriftValueSet.getRangeValueSet()).isNotNull(); + assertThat(thriftValueSet.getRangeValueSet().getRanges()).isEqualTo(ImmutableList.of( new TrinoThriftRange(new TrinoThriftMarker(null, ABOVE), new TrinoThriftMarker(null, BELOW)))); } @@ -47,16 +46,16 @@ public void testFromValueSetAll() public void testFromValueSetNone() { TrinoThriftValueSet thriftValueSet = fromValueSet(ValueSet.none(BIGINT)); - assertNotNull(thriftValueSet.getRangeValueSet()); - assertEquals(thriftValueSet.getRangeValueSet().getRanges(), ImmutableList.of()); + assertThat(thriftValueSet.getRangeValueSet()).isNotNull(); + assertThat(thriftValueSet.getRangeValueSet().getRanges()).isEqualTo(ImmutableList.of()); } @Test public void testFromValueSetOf() { TrinoThriftValueSet thriftValueSet = fromValueSet(ValueSet.of(BIGINT, 1L, 2L, 3L)); - assertNotNull(thriftValueSet.getRangeValueSet()); - assertEquals(thriftValueSet.getRangeValueSet().getRanges(), ImmutableList.of( + assertThat(thriftValueSet.getRangeValueSet()).isNotNull(); + assertThat(thriftValueSet.getRangeValueSet().getRanges()).isEqualTo(ImmutableList.of( new TrinoThriftRange(new TrinoThriftMarker(longValue(1), EXACTLY), new TrinoThriftMarker(longValue(1), EXACTLY)), new TrinoThriftRange(new TrinoThriftMarker(longValue(2), EXACTLY), new TrinoThriftMarker(longValue(2), EXACTLY)), new TrinoThriftRange(new TrinoThriftMarker(longValue(3), EXACTLY), new TrinoThriftMarker(longValue(3), EXACTLY)))); @@ -66,8 +65,8 @@ public void testFromValueSetOf() public void testFromValueSetOfRangesUnbounded() { TrinoThriftValueSet thriftValueSet = fromValueSet(ValueSet.ofRanges(Range.greaterThanOrEqual(BIGINT, 0L))); - assertNotNull(thriftValueSet.getRangeValueSet()); - assertEquals(thriftValueSet.getRangeValueSet().getRanges(), ImmutableList.of( + assertThat(thriftValueSet.getRangeValueSet()).isNotNull(); + assertThat(thriftValueSet.getRangeValueSet().getRanges()).isEqualTo(ImmutableList.of( new TrinoThriftRange(new TrinoThriftMarker(longValue(0), EXACTLY), new TrinoThriftMarker(null, BELOW)))); } @@ -77,8 +76,8 @@ public void testFromValueSetOfRangesBounded() TrinoThriftValueSet thriftValueSet = fromValueSet(ValueSet.ofRanges( range(BIGINT, -10L, true, -1L, false), range(BIGINT, -1L, false, 100L, true))); - assertNotNull(thriftValueSet.getRangeValueSet()); - assertEquals(thriftValueSet.getRangeValueSet().getRanges(), ImmutableList.of( + assertThat(thriftValueSet.getRangeValueSet()).isNotNull(); + assertThat(thriftValueSet.getRangeValueSet().getRanges()).isEqualTo(ImmutableList.of( new TrinoThriftRange(new TrinoThriftMarker(longValue(-10), EXACTLY), new TrinoThriftMarker(longValue(-1), BELOW)), new TrinoThriftRange(new TrinoThriftMarker(longValue(-1), ABOVE), new TrinoThriftMarker(longValue(100), EXACTLY)))); } diff --git a/plugin/trino-thrift/pom.xml b/plugin/trino-thrift/pom.xml index 6d1795b5c2cb..c1aefe764262 100644 --- a/plugin/trino-thrift/pom.xml +++ b/plugin/trino-thrift/pom.xml @@ -224,6 +224,12 @@ test + + org.assertj + assertj-core + test + + org.junit.jupiter junit-jupiter-api diff --git a/plugin/trino-thrift/src/test/java/io/trino/plugin/thrift/TestThriftIndexPageSource.java b/plugin/trino-thrift/src/test/java/io/trino/plugin/thrift/TestThriftIndexPageSource.java index 1176b83a65e8..dfa545aad955 100644 --- a/plugin/trino-thrift/src/test/java/io/trino/plugin/thrift/TestThriftIndexPageSource.java +++ b/plugin/trino-thrift/src/test/java/io/trino/plugin/thrift/TestThriftIndexPageSource.java @@ -51,11 +51,7 @@ import static io.trino.spi.type.IntegerType.INTEGER; import static java.util.Collections.shuffle; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestThriftIndexPageSource { @@ -97,59 +93,67 @@ public ListenableFuture getRows(TrinoThriftId splitId, Li MAX_BYTES_PER_RESPONSE, lookupRequestsConcurrency); - assertNull(pageSource.getNextPage()); - assertEquals((long) stats.getIndexPageSize().getAllTime().getTotal(), 0); + assertThat(pageSource.getNextPage()).isNull(); + assertThat((long) stats.getIndexPageSize().getAllTime().getTotal()).isEqualTo(0); signals.get(0).await(1, SECONDS); signals.get(1).await(1, SECONDS); signals.get(2).await(1, SECONDS); - assertEquals(signals.get(0).getCount(), 0, "first request wasn't sent"); - assertEquals(signals.get(1).getCount(), 0, "second request wasn't sent"); - assertEquals(signals.get(2).getCount(), 1, "third request shouldn't be sent"); + assertThat(signals.get(0).getCount()) + .describedAs("first request wasn't sent") + .isEqualTo(0); + assertThat(signals.get(1).getCount()) + .describedAs("second request wasn't sent") + .isEqualTo(0); + assertThat(signals.get(2).getCount()) + .describedAs("third request shouldn't be sent") + .isEqualTo(1); // at this point first two requests were sent - assertFalse(pageSource.isFinished()); - assertNull(pageSource.getNextPage()); - assertEquals((long) stats.getIndexPageSize().getAllTime().getTotal(), 0); + assertThat(pageSource.isFinished()).isFalse(); + assertThat(pageSource.getNextPage()).isNull(); + assertThat((long) stats.getIndexPageSize().getAllTime().getTotal()).isEqualTo(0); // completing the second request futures.get(1).set(pageResult(20, null)); Page page = pageSource.getNextPage(); pageSizeReceived += page.getSizeInBytes(); - assertEquals((long) stats.getIndexPageSize().getAllTime().getTotal(), pageSizeReceived); - assertNotNull(page); - assertEquals(page.getPositionCount(), 1); - assertEquals(page.getBlock(0).getInt(0, 0), 20); + assertThat((long) stats.getIndexPageSize().getAllTime().getTotal()).isEqualTo(pageSizeReceived); + assertThat(page).isNotNull(); + assertThat(page.getPositionCount()).isEqualTo(1); + assertThat(page.getBlock(0).getInt(0, 0)).isEqualTo(20); // not complete yet - assertFalse(pageSource.isFinished()); + assertThat(pageSource.isFinished()).isFalse(); // once one of the requests completes the next one should be sent signals.get(2).await(1, SECONDS); - assertEquals(signals.get(2).getCount(), 0, "third request wasn't sent"); + assertThat(signals.get(2).getCount()) + .describedAs("third request wasn't sent") + .isEqualTo(0); // completing the first request futures.get(0).set(pageResult(10, null)); page = pageSource.getNextPage(); - assertNotNull(page); + assertThat(page).isNotNull(); pageSizeReceived += page.getSizeInBytes(); - assertEquals((long) stats.getIndexPageSize().getAllTime().getTotal(), pageSizeReceived); - assertEquals(page.getPositionCount(), 1); - assertEquals(page.getBlock(0).getInt(0, 0), 10); + assertThat((long) stats.getIndexPageSize().getAllTime().getTotal()).isEqualTo(pageSizeReceived); + assertThat(page.getPositionCount()).isEqualTo(1); + assertThat(page.getBlock(0).getInt(0, 0)).isEqualTo(10); // still not complete - assertFalse(pageSource.isFinished()); + assertThat(pageSource.isFinished()).isFalse(); // completing the third request futures.get(2).set(pageResult(30, null)); page = pageSource.getNextPage(); - assertNotNull(page); + assertThat(page).isNotNull(); pageSizeReceived += page.getSizeInBytes(); - assertEquals((long) stats.getIndexPageSize().getAllTime().getTotal(), pageSizeReceived); - assertEquals(page.getPositionCount(), 1); - assertEquals(page.getBlock(0).getInt(0, 0), 30); + assertThat((long) stats.getIndexPageSize().getAllTime().getTotal()).isEqualTo(pageSizeReceived); + assertThat(page.getPositionCount()).isEqualTo(1); + assertThat(page.getBlock(0).getInt(0, 0)).isEqualTo(30); // finished now - assertTrue(pageSource.isFinished()); + assertThat(pageSource.isFinished()).isTrue(); // after completion - assertNull(pageSource.getNextPage()); + assertThat(pageSource.getNextPage()).isNull(); pageSource.close(); } @@ -216,10 +220,10 @@ private static void runGeneralTest(int splits, int lookupRequestsConcurrency, in expected.add(split * 10 + row); } } - assertEquals(actual, expected); + assertThat(actual).isEqualTo(expected); // must be null after finish - assertNull(pageSource.getNextPage()); + assertThat(pageSource.getNextPage()).isNull(); pageSource.close(); } diff --git a/plugin/trino-thrift/src/test/java/io/trino/plugin/thrift/TestThriftPlugin.java b/plugin/trino-thrift/src/test/java/io/trino/plugin/thrift/TestThriftPlugin.java index bf7d106db935..bba60bf48711 100644 --- a/plugin/trino-thrift/src/test/java/io/trino/plugin/thrift/TestThriftPlugin.java +++ b/plugin/trino-thrift/src/test/java/io/trino/plugin/thrift/TestThriftPlugin.java @@ -24,7 +24,7 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static io.airlift.testing.Assertions.assertInstanceOf; -import static org.testng.Assert.assertNotNull; +import static org.assertj.core.api.Assertions.assertThat; public class TestThriftPlugin { @@ -40,7 +40,7 @@ public void testPlugin() "bootstrap.quiet", "true"); Connector connector = factory.create("test", config, new TestingConnectorContext()); - assertNotNull(connector); + assertThat(connector).isNotNull(); assertInstanceOf(connector, ThriftConnector.class); connector.shutdown(); } diff --git a/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/BaseFaultTolerantExecutionTest.java b/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/BaseFaultTolerantExecutionTest.java index 9a19b45249f9..731ad2ab5e9a 100644 --- a/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/BaseFaultTolerantExecutionTest.java +++ b/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/BaseFaultTolerantExecutionTest.java @@ -20,7 +20,6 @@ import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public abstract class BaseFaultTolerantExecutionTest extends AbstractTestQueryFramework @@ -47,7 +46,7 @@ CREATE TABLE test_table_writer_skew_mitigation WITH (%s = ARRAY['returnflag']) A // force single writer task to verify there is exactly one writer per task assertUpdate(withUnlimitedTargetTaskInputSize(session), createTableSql, 1000000); - assertEquals(computeActual(selectFileInfo).getRowCount(), 1); + assertThat(computeActual(selectFileInfo).getRowCount()).isEqualTo(1); assertUpdate("DROP TABLE test_table_writer_skew_mitigation"); assertUpdate(withDisabledPreferredWritePartitioning(session), createTableSql, 1000000); @@ -58,7 +57,7 @@ CREATE TABLE test_table_writer_skew_mitigation WITH (%s = ARRAY['returnflag']) A assertUpdate(withEnabledPreferredWritePartitioning(session), createTableSql, 1000000); int actualNumberOfFiles = computeActual(selectFileInfo).getRowCount(); assertUpdate("DROP TABLE test_table_writer_skew_mitigation"); - assertEquals(actualNumberOfFiles, expectedNumberOfFiles); + assertThat(actualNumberOfFiles).isEqualTo(expectedNumberOfFiles); } @Test @@ -79,7 +78,7 @@ CREATE TABLE test_execute_skew_mitigation WITH (%s = ARRAY['returnflag']) AS // force single writer task to verify there is exactly one writer per task assertUpdate(withUnlimitedTargetTaskInputSize(session), executeSql); - assertEquals(computeActual(selectFileInfo).getRowCount(), 1); + assertThat(computeActual(selectFileInfo).getRowCount()).isEqualTo(1); assertUpdate(withDisabledPreferredWritePartitioning(session), executeSql); int expectedNumberOfFiles = computeActual(selectFileInfo).getRowCount(); @@ -89,7 +88,7 @@ CREATE TABLE test_execute_skew_mitigation WITH (%s = ARRAY['returnflag']) AS assertUpdate(withEnabledPreferredWritePartitioning(session), executeSql); int actualNumberOfFiles = computeActual(selectFileInfo).getRowCount(); - assertEquals(actualNumberOfFiles, expectedNumberOfFiles); + assertThat(actualNumberOfFiles).isEqualTo(expectedNumberOfFiles); // verify no data is lost in process assertQuery("SELECT count(*) FROM test_execute_skew_mitigation", "SELECT 1000000"); diff --git a/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/TestOverridePartitionCountRecursively.java b/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/TestOverridePartitionCountRecursively.java index d5b1bb8a502e..e73ec67fafe4 100644 --- a/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/TestOverridePartitionCountRecursively.java +++ b/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/TestOverridePartitionCountRecursively.java @@ -61,8 +61,6 @@ import static io.trino.transaction.TransactionBuilder.transaction; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; public class TestOverridePartitionCountRecursively extends AbstractTestQueryFramework @@ -230,10 +228,10 @@ private void assertOverridePartitionCountRecursively( PlanFragment fragment = subPlan.getFragment(); int fragmentIdAsInt = Integer.parseInt(fragment.getId().toString()); FragmentPartitioningInfo fragmentPartitioningInfo = fragmentPartitioningInfoBefore.get(fragmentIdAsInt); - assertEquals(fragment.getPartitionCount(), fragmentPartitioningInfo.inputPartitionCount()); - assertEquals(fragment.getPartitioning(), fragmentPartitioningInfo.inputPartitioning()); - assertEquals(fragment.getOutputPartitioningScheme().getPartitionCount(), fragmentPartitioningInfo.outputPartitionCount()); - assertEquals(fragment.getOutputPartitioningScheme().getPartitioning().getHandle(), fragmentPartitioningInfo.outputPartitioning()); + assertThat(fragment.getPartitionCount()).isEqualTo(fragmentPartitioningInfo.inputPartitionCount()); + assertThat(fragment.getPartitioning()).isEqualTo(fragmentPartitioningInfo.inputPartitioning()); + assertThat(fragment.getOutputPartitioningScheme().getPartitionCount()).isEqualTo(fragmentPartitioningInfo.outputPartitionCount()); + assertThat(fragment.getOutputPartitioningScheme().getPartitioning().getHandle()).isEqualTo(fragmentPartitioningInfo.outputPartitioning()); } PlanFragmentIdAllocator planFragmentIdAllocator = new PlanFragmentIdAllocator(getMaxPlanFragmentId(planInTopologicalOrder) + 1); @@ -250,7 +248,7 @@ private void assertOverridePartitionCountRecursively( }) .max() .orElseThrow(); - assertTrue(oldPartitionCount > 0); + assertThat(oldPartitionCount > 0).isTrue(); SubPlan newPlan = overridePartitionCountRecursively( plan, @@ -265,10 +263,10 @@ private void assertOverridePartitionCountRecursively( PlanFragment fragment = subPlan.getFragment(); int fragmentIdAsInt = Integer.parseInt(fragment.getId().toString()); FragmentPartitioningInfo fragmentPartitioningInfo = fragmentPartitioningInfoAfter.get(fragmentIdAsInt); - assertEquals(fragment.getPartitionCount(), fragmentPartitioningInfo.inputPartitionCount()); - assertEquals(fragment.getPartitioning(), fragmentPartitioningInfo.inputPartitioning()); - assertEquals(fragment.getOutputPartitioningScheme().getPartitionCount(), fragmentPartitioningInfo.outputPartitionCount()); - assertEquals(fragment.getOutputPartitioningScheme().getPartitioning().getHandle(), fragmentPartitioningInfo.outputPartitioning()); + assertThat(fragment.getPartitionCount()).isEqualTo(fragmentPartitioningInfo.inputPartitionCount()); + assertThat(fragment.getPartitioning()).isEqualTo(fragmentPartitioningInfo.inputPartitioning()); + assertThat(fragment.getOutputPartitioningScheme().getPartitionCount()).isEqualTo(fragmentPartitioningInfo.outputPartitionCount()); + assertThat(fragment.getOutputPartitioningScheme().getPartitioning().getHandle()).isEqualTo(fragmentPartitioningInfo.outputPartitioning()); } } diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/TestSqlCancel.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/TestSqlCancel.java index 4ccf81292643..4e70bc950b69 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/TestSqlCancel.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/TestSqlCancel.java @@ -52,7 +52,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Fail.fail; public class TestSqlCancel extends ProductTest diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/utils/QueryAssertions.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/utils/QueryAssertions.java index 19c071be3502..f30f9c62a435 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/utils/QueryAssertions.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/utils/QueryAssertions.java @@ -21,7 +21,7 @@ import java.util.function.Supplier; import static java.lang.String.format; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Fail.fail; public final class QueryAssertions { diff --git a/testing/trino-test-jdbc-compatibility-old-server/src/test/java/io/trino/TestJdbcResultSetCompatibilityOldServer.java b/testing/trino-test-jdbc-compatibility-old-server/src/test/java/io/trino/TestJdbcResultSetCompatibilityOldServer.java index 2a851bf0c8ae..3adc54e4ced0 100644 --- a/testing/trino-test-jdbc-compatibility-old-server/src/test/java/io/trino/TestJdbcResultSetCompatibilityOldServer.java +++ b/testing/trino-test-jdbc-compatibility-old-server/src/test/java/io/trino/TestJdbcResultSetCompatibilityOldServer.java @@ -40,8 +40,7 @@ import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; @Test(singleThreaded = true) public class TestJdbcResultSetCompatibilityOldServer @@ -109,9 +108,11 @@ public void setupTrinoContainer() // verify that version reported by Trino server matches requested one. try (ConnectedStatement statementWrapper = newStatement()) { try (ResultSet rs = statementWrapper.getStatement().executeQuery("SELECT node_version FROM system.runtime.nodes")) { - assertTrue(rs.next()); + assertThat(rs.next()).isTrue(); String actualTrinoVersion = rs.getString(1); - assertEquals(actualTrinoVersion, getTestedTrinoVersion(), "Trino server version reported by container does not match expected one"); + assertThat(actualTrinoVersion) + .describedAs("Trino server version reported by container does not match expected one") + .isEqualTo(getTestedTrinoVersion()); } } catch (SQLException e) { diff --git a/testing/trino-testing-services/src/test/java/io/trino/testng/services/TestFlakyTestRetryAnalyzer.java b/testing/trino-testing-services/src/test/java/io/trino/testng/services/TestFlakyTestRetryAnalyzer.java index 9d0071be868b..fe236630f05f 100644 --- a/testing/trino-testing-services/src/test/java/io/trino/testng/services/TestFlakyTestRetryAnalyzer.java +++ b/testing/trino-testing-services/src/test/java/io/trino/testng/services/TestFlakyTestRetryAnalyzer.java @@ -17,8 +17,8 @@ import org.testng.annotations.Test; import static io.trino.testng.services.FlakyTestRetryAnalyzer.ALLOWED_RETRIES_COUNT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Fail.fail; @Test(singleThreaded = true) public class TestFlakyTestRetryAnalyzer @@ -36,14 +36,14 @@ public void testRetrying() if (testRetryingCount <= ALLOWED_RETRIES_COUNT) { fail("I am trying hard to fail!"); } - assertEquals(testRetryingCount, 3); + assertThat(testRetryingCount).isEqualTo(3); } @Test public void testNoRetrying() { testNoRetryingCount++; - assertEquals(testNoRetryingCount, 1); + assertThat(testNoRetryingCount).isEqualTo(1); } @Override @@ -62,7 +62,7 @@ public void testRetryingParametricTest(int index) if (testRetryingParametricTestCount[index] <= ALLOWED_RETRIES_COUNT) { fail("I am trying hard to fail!"); } - assertEquals(testRetryingParametricTestCount[index], 3); + assertThat(testRetryingParametricTestCount[index]).isEqualTo(3); } @DataProvider @@ -84,6 +84,6 @@ public void testRetryingOverriddenTest() if (testRetryingOverriddenTestCount <= ALLOWED_RETRIES_COUNT) { fail("I am trying hard to fail!"); } - assertEquals(testRetryingOverriddenTestCount, 3); + assertThat(testRetryingOverriddenTestCount).isEqualTo(3); } } diff --git a/testing/trino-testing-services/src/test/java/io/trino/testng/services/TestReportBadTestAnnotations.java b/testing/trino-testing-services/src/test/java/io/trino/testng/services/TestReportBadTestAnnotations.java index 161414bda6e9..743bd1d17447 100644 --- a/testing/trino-testing-services/src/test/java/io/trino/testng/services/TestReportBadTestAnnotations.java +++ b/testing/trino-testing-services/src/test/java/io/trino/testng/services/TestReportBadTestAnnotations.java @@ -26,8 +26,6 @@ import static io.trino.testng.services.ReportBadTestAnnotations.findUnannotatedTestMethods; import static io.trino.testng.services.ReportBadTestAnnotations.isTemptoClass; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public class TestReportBadTestAnnotations { @@ -62,9 +60,9 @@ public void testTemptoRequirementsProvider() @Test public void testTemptoPackage() { - assertTrue(isTemptoClass(RequirementsProvider.class)); - assertTrue(isTemptoClass(WithName.class)); - assertFalse(isTemptoClass(getClass())); + assertThat(isTemptoClass(RequirementsProvider.class)).isTrue(); + assertThat(isTemptoClass(WithName.class)).isTrue(); + assertThat(isTemptoClass(getClass())).isFalse(); } @Test diff --git a/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestAggregations.java b/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestAggregations.java index 3714a499cdc1..c1ca47508ad1 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestAggregations.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestAggregations.java @@ -24,8 +24,7 @@ import static io.trino.SystemSessionProperties.MARK_DISTINCT_STRATEGY; import static io.trino.testing.MaterializedResult.resultBuilder; import static io.trino.testing.QueryAssertions.assertEqualsIgnoreOrder; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public abstract class AbstractTestAggregations extends AbstractTestQueryFramework @@ -892,14 +891,14 @@ public void testApproximateCountDistinctGroupByWithStandardError() public void testDistinctNan() { MaterializedResult actual = computeActual("SELECT DISTINCT a/a FROM (VALUES (0.0e0), (0.0e0)) x (a)"); - assertTrue(Double.isNaN((Double) actual.getOnlyValue())); + assertThat(Double.isNaN((Double) actual.getOnlyValue())).isTrue(); } @Test public void testGroupByNan() { MaterializedResult actual = computeActual("SELECT * FROM (VALUES nan(), nan(), nan()) GROUP BY 1"); - assertTrue(Double.isNaN((Double) actual.getOnlyValue())); + assertThat(Double.isNaN((Double) actual.getOnlyValue())).isTrue(); } @Test @@ -907,10 +906,10 @@ public void testGroupByNanRow() { MaterializedResult actual = computeActual("SELECT a, b, c FROM (VALUES ROW(nan(), 1, 2), ROW(nan(), 1, 2)) t(a, b, c) GROUP BY 1, 2, 3"); List actualRows = actual.getMaterializedRows(); - assertEquals(actualRows.size(), 1); - assertTrue(Double.isNaN((Double) actualRows.get(0).getField(0))); - assertEquals(actualRows.get(0).getField(1), 1); - assertEquals(actualRows.get(0).getField(2), 2); + assertThat(actualRows.size()).isEqualTo(1); + assertThat(Double.isNaN((Double) actualRows.get(0).getField(0))).isTrue(); + assertThat(actualRows.get(0).getField(1)).isEqualTo(1); + assertThat(actualRows.get(0).getField(2)).isEqualTo(2); } @Test @@ -918,19 +917,19 @@ public void testGroupByNanArray() { MaterializedResult actual = computeActual("SELECT a FROM (VALUES (ARRAY[nan(), 2e0, 3e0]), (ARRAY[nan(), 2e0, 3e0])) t(a) GROUP BY a"); List actualRows = actual.getMaterializedRows(); - assertEquals(actualRows.size(), 1); + assertThat(actualRows.size()).isEqualTo(1); @SuppressWarnings("unchecked") List value = (List) actualRows.get(0).getField(0); - assertTrue(Double.isNaN(value.get(0))); - assertEquals(value.get(1), 2.0); - assertEquals(value.get(2), 3.0); + assertThat(Double.isNaN(value.get(0))).isTrue(); + assertThat(value.get(1)).isEqualTo(2.0); + assertThat(value.get(2)).isEqualTo(3.0); } @Test public void testGroupByNanMap() { MaterializedResult actual = computeActual("SELECT MAP_KEYS(x)[1] FROM (VALUES MAP(ARRAY[nan()], ARRAY[ARRAY[1]]), MAP(ARRAY[nan()], ARRAY[ARRAY[2]])) t(x) GROUP BY 1"); - assertTrue(Double.isNaN((Double) actual.getOnlyValue())); + assertThat(Double.isNaN((Double) actual.getOnlyValue())).isTrue(); } @Test @@ -1363,50 +1362,50 @@ public void testGroupingSetsWithDefaultValue() public void testApproxMostFrequentWithLong() { MaterializedResult actual1 = computeActual("SELECT approx_most_frequent(3, cast(x as bigint), 15) FROM (values 1, 2, 1, 3, 1, 2, 3, 4, 5) t(x)"); - assertEquals(actual1.getRowCount(), 1); - assertEquals(actual1.getMaterializedRows().get(0).getFields().get(0), ImmutableMap.of(1L, 3L, 2L, 2L, 3L, 2L)); + assertThat(actual1.getRowCount()).isEqualTo(1); + assertThat(actual1.getMaterializedRows().get(0).getFields().get(0)).isEqualTo(ImmutableMap.of(1L, 3L, 2L, 2L, 3L, 2L)); MaterializedResult actual2 = computeActual("SELECT approx_most_frequent(2, cast(x as bigint), 15) FROM (values 1, 2, 1, 3, 1, 2, 3, 4, 5) t(x)"); - assertEquals(actual2.getRowCount(), 1); - assertEquals(actual2.getMaterializedRows().get(0).getFields().get(0), ImmutableMap.of(1L, 3L, 2L, 2L)); + assertThat(actual2.getRowCount()).isEqualTo(1); + assertThat(actual2.getMaterializedRows().get(0).getFields().get(0)).isEqualTo(ImmutableMap.of(1L, 3L, 2L, 2L)); } @Test public void testApproxMostFrequentWithVarchar() { MaterializedResult actual1 = computeActual("SELECT approx_most_frequent(3, x, 15) FROM (values 'A', 'B', 'A', 'C', 'A', 'B', 'C', 'D', 'E') t(x)"); - assertEquals(actual1.getRowCount(), 1); - assertEquals(actual1.getMaterializedRows().get(0).getFields().get(0), ImmutableMap.of("A", 3L, "B", 2L, "C", 2L)); + assertThat(actual1.getRowCount()).isEqualTo(1); + assertThat(actual1.getMaterializedRows().get(0).getFields().get(0)).isEqualTo(ImmutableMap.of("A", 3L, "B", 2L, "C", 2L)); MaterializedResult actual2 = computeActual("SELECT approx_most_frequent(2, x, 15) FROM (values 'A', 'B', 'A', 'C', 'A', 'B', 'C', 'D', 'E') t(x)"); - assertEquals(actual2.getRowCount(), 1); - assertEquals(actual2.getMaterializedRows().get(0).getFields().get(0), ImmutableMap.of("A", 3L, "B", 2L)); + assertThat(actual2.getRowCount()).isEqualTo(1); + assertThat(actual2.getMaterializedRows().get(0).getFields().get(0)).isEqualTo(ImmutableMap.of("A", 3L, "B", 2L)); } @Test public void testApproxMostFrequentWithLongGroupBy() { MaterializedResult actual1 = computeActual("SELECT k, approx_most_frequent(3, cast(v as bigint), 15) FROM (values ('a', 1), ('b', 2), ('a', 1), ('c', 3), ('a', 1), ('b', 2), ('c', 3), ('a', 4), ('b', 5)) t(k, v) GROUP BY 1 ORDER BY 1"); - assertEquals(actual1.getRowCount(), 3); - assertEquals(actual1.getMaterializedRows().get(0).getFields().get(0), "a"); - assertEquals(actual1.getMaterializedRows().get(0).getFields().get(1), ImmutableMap.of(1L, 3L, 4L, 1L)); - assertEquals(actual1.getMaterializedRows().get(1).getFields().get(0), "b"); - assertEquals(actual1.getMaterializedRows().get(1).getFields().get(1), ImmutableMap.of(2L, 2L, 5L, 1L)); - assertEquals(actual1.getMaterializedRows().get(2).getFields().get(0), "c"); - assertEquals(actual1.getMaterializedRows().get(2).getFields().get(1), ImmutableMap.of(3L, 2L)); + assertThat(actual1.getRowCount()).isEqualTo(3); + assertThat(actual1.getMaterializedRows().get(0).getFields().get(0)).isEqualTo("a"); + assertThat(actual1.getMaterializedRows().get(0).getFields().get(1)).isEqualTo(ImmutableMap.of(1L, 3L, 4L, 1L)); + assertThat(actual1.getMaterializedRows().get(1).getFields().get(0)).isEqualTo("b"); + assertThat(actual1.getMaterializedRows().get(1).getFields().get(1)).isEqualTo(ImmutableMap.of(2L, 2L, 5L, 1L)); + assertThat(actual1.getMaterializedRows().get(2).getFields().get(0)).isEqualTo("c"); + assertThat(actual1.getMaterializedRows().get(2).getFields().get(1)).isEqualTo(ImmutableMap.of(3L, 2L)); } @Test public void testApproxMostFrequentWithStringGroupBy() { MaterializedResult actual1 = computeActual("SELECT k, approx_most_frequent(3, v, 15) FROM (values ('a', 'A'), ('b', 'B'), ('a', 'A'), ('c', 'C'), ('a', 'A'), ('b', 'B'), ('c', 'C'), ('a', 'D'), ('b', 'E')) t(k, v) GROUP BY 1 ORDER BY 1"); - assertEquals(actual1.getRowCount(), 3); - assertEquals(actual1.getMaterializedRows().get(0).getFields().get(0), "a"); - assertEquals(actual1.getMaterializedRows().get(0).getFields().get(1), ImmutableMap.of("A", 3L, "D", 1L)); - assertEquals(actual1.getMaterializedRows().get(1).getFields().get(0), "b"); - assertEquals(actual1.getMaterializedRows().get(1).getFields().get(1), ImmutableMap.of("B", 2L, "E", 1L)); - assertEquals(actual1.getMaterializedRows().get(2).getFields().get(0), "c"); - assertEquals(actual1.getMaterializedRows().get(2).getFields().get(1), ImmutableMap.of("C", 2L)); + assertThat(actual1.getRowCount()).isEqualTo(3); + assertThat(actual1.getMaterializedRows().get(0).getFields().get(0)).isEqualTo("a"); + assertThat(actual1.getMaterializedRows().get(0).getFields().get(1)).isEqualTo(ImmutableMap.of("A", 3L, "D", 1L)); + assertThat(actual1.getMaterializedRows().get(1).getFields().get(0)).isEqualTo("b"); + assertThat(actual1.getMaterializedRows().get(1).getFields().get(1)).isEqualTo(ImmutableMap.of("B", 2L, "E", 1L)); + assertThat(actual1.getMaterializedRows().get(2).getFields().get(0)).isEqualTo("c"); + assertThat(actual1.getMaterializedRows().get(2).getFields().get(1)).isEqualTo(ImmutableMap.of("C", 2L)); } @Test diff --git a/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestEngineOnlyQueries.java b/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestEngineOnlyQueries.java index 833639074dea..88096a00accd 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestEngineOnlyQueries.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestEngineOnlyQueries.java @@ -72,10 +72,6 @@ import static java.util.stream.IntStream.range; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotEquals; -import static org.testng.Assert.assertTrue; public abstract class AbstractTestEngineOnlyQueries extends AbstractTestQueryFramework @@ -126,7 +122,7 @@ 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(); - assertEquals(computeScalar("SELECT DATE '2013-03-22'"), LocalDate.of(2013, 3, 22)); + assertThat(computeScalar("SELECT DATE '2013-03-22'")).isEqualTo(LocalDate.of(2013, 3, 22)); assertQuery("SELECT DATE '2013-03-22'"); assertQuery(chicago, "SELECT DATE '2013-03-22'"); assertQuery(kathmandu, "SELECT DATE '2013-03-22'"); @@ -138,8 +134,8 @@ public void testTimeLiterals() Session chicago = Session.builder(getSession()).setTimeZoneKey(TimeZoneKey.getTimeZoneKey("America/Chicago")).build(); Session kathmandu = Session.builder(getSession()).setTimeZoneKey(TimeZoneKey.getTimeZoneKey("Asia/Kathmandu")).build(); - 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)); + assertThat(computeScalar("SELECT TIME '3:04:05'")).isEqualTo(LocalTime.of(3, 4, 5, 0)); + assertThat(computeScalar("SELECT TIME '3:04:05.123'")).isEqualTo(LocalTime.of(3, 4, 5, 123_000_000)); assertQuery("SELECT TIME '3:04:05'"); assertQuery("SELECT TIME '0:04:05'"); @@ -150,10 +146,10 @@ public void testTimeLiterals() @Test public void testTimeWithTimeZoneLiterals() { - assertEquals(computeScalar("SELECT TIME '01:02:03.400+00:00'"), OffsetTime.of(1, 2, 3, 400_000_000, ZoneOffset.ofHoursMinutes(0, 0))); - 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 +05:07'"), OffsetTime.of(3, 4, 5, 0, ZoneOffset.ofHoursMinutes(5, 7))); - assertEquals(computeScalar("SELECT TIME '3:04:05 +03:00'"), OffsetTime.of(3, 4, 5, 0, ZoneOffset.ofHoursMinutes(3, 0))); + assertThat(computeScalar("SELECT TIME '01:02:03.400+00:00'")).isEqualTo(OffsetTime.of(1, 2, 3, 400_000_000, ZoneOffset.ofHoursMinutes(0, 0))); + assertThat(computeScalar("SELECT TIME '3:04:05 +06:00'")).isEqualTo(OffsetTime.of(3, 4, 5, 0, ZoneOffset.ofHoursMinutes(6, 0))); + assertThat(computeScalar("SELECT TIME '3:04:05 +05:07'")).isEqualTo(OffsetTime.of(3, 4, 5, 0, ZoneOffset.ofHoursMinutes(5, 7))); + assertThat(computeScalar("SELECT TIME '3:04:05 +03:00'")).isEqualTo(OffsetTime.of(3, 4, 5, 0, ZoneOffset.ofHoursMinutes(3, 0))); } @Test @@ -162,8 +158,8 @@ public void testTimestampLiterals() Session chicago = Session.builder(getSession()).setTimeZoneKey(TimeZoneKey.getTimeZoneKey("America/Chicago")).build(); Session kathmandu = Session.builder(getSession()).setTimeZoneKey(TimeZoneKey.getTimeZoneKey("Asia/Kathmandu")).build(); - 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)); + assertThat(computeScalar("SELECT TIMESTAMP '1960-01-22 3:04:05'")).isEqualTo(LocalDateTime.of(1960, 1, 22, 3, 4, 5)); + assertThat(computeScalar("SELECT TIMESTAMP '1960-01-22 3:04:05.123'")).isEqualTo(LocalDateTime.of(1960, 1, 22, 3, 4, 5, 123_000_000)); assertQuery("SELECT TIMESTAMP '1960-01-22 3:04:05'"); assertQuery("SELECT TIMESTAMP '1960-01-22 3:04:05.123'"); assertQuery(chicago, "SELECT TIMESTAMP '1960-01-22 3:04:05.123'"); @@ -173,7 +169,7 @@ public void testTimestampLiterals() @Test public void testTimestampWithTimeZoneLiterals() { - assertEquals(computeScalar("SELECT TIMESTAMP '1960-01-22 3:04:05 +06:00'"), ZonedDateTime.of(1960, 1, 22, 3, 4, 5, 0, ZoneOffset.ofHoursMinutes(6, 0))); + assertThat(computeScalar("SELECT TIMESTAMP '1960-01-22 3:04:05 +06:00'")).isEqualTo(ZonedDateTime.of(1960, 1, 22, 3, 4, 5, 0, ZoneOffset.ofHoursMinutes(6, 0))); } @Test @@ -183,21 +179,21 @@ public void testLocallyUnrepresentableTimeLiterals() checkState(ZoneId.systemDefault().getRules().getValidOffsets(localTimeThatDidNotExist).isEmpty(), "This test assumes certain JVM time zone"); // This tests that both Trino 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.SSS''").format(localTimeThatDidNotExist); - assertEquals(computeScalar(sql), localTimeThatDidNotExist); // this tests Trino and the QueryRunner + assertThat(computeScalar(sql)).isEqualTo(localTimeThatDidNotExist); // this tests Trino 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 Trino 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); - assertEquals(computeScalar(sql), localDateThatDidNotHaveMidnight); // this tests Trino and the QueryRunner + assertThat(computeScalar(sql)).isEqualTo(localDateThatDidNotHaveMidnight); // this tests Trino and the QueryRunner assertQuery(sql); // this tests H2QueryRunner 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); - assertEquals(computeScalar(sql), localTimeThatDidNotOccurOn19700101); // this tests Trino and the QueryRunner + assertThat(computeScalar(sql)).isEqualTo(localTimeThatDidNotOccurOn19700101); // this tests Trino and the QueryRunner assertQuery(sql); // this tests H2QueryRunner } @@ -205,21 +201,21 @@ public void testLocallyUnrepresentableTimeLiterals() public void testNodeRoster() { List result = computeActual("SELECT * FROM system.runtime.nodes").getMaterializedRows(); - assertEquals(result.size(), getNodeCount()); + assertThat(result.size()).isEqualTo(getNodeCount()); } @Test public void testTransactionsTable() { List result = computeActual("SELECT * FROM system.runtime.transactions").getMaterializedRows(); - assertTrue(result.size() >= 1); // At least one row for the current transaction. + assertThat(result.size() >= 1).isTrue(); // At least one row for the current transaction. } @Test public void testCountOnInternalTables() { List rows = computeActual("SELECT count(*) FROM system.runtime.nodes").getMaterializedRows(); - assertEquals(((Long) rows.get(0).getField(0)).longValue(), getNodeCount()); + assertThat(((Long) rows.get(0).getField(0)).longValue()).isEqualTo(getNodeCount()); } @Test @@ -232,12 +228,12 @@ public void testParsingError() public void testSelectLargeInterval() { MaterializedResult result = computeActual("SELECT INTERVAL '30' DAY"); - assertEquals(result.getRowCount(), 1); - assertEquals(result.getMaterializedRows().get(0).getField(0), new SqlIntervalDayTime(30, 0, 0, 0, 0)); + assertThat(result.getRowCount()).isEqualTo(1); + assertThat(result.getMaterializedRows().get(0).getField(0)).isEqualTo(new SqlIntervalDayTime(30, 0, 0, 0, 0)); result = computeActual("SELECT INTERVAL '" + Short.MAX_VALUE + "' YEAR"); - assertEquals(result.getRowCount(), 1); - assertEquals(result.getMaterializedRows().get(0).getField(0), new SqlIntervalYearMonth(Short.MAX_VALUE, 0)); + assertThat(result.getRowCount()).isEqualTo(1); + assertThat(result.getMaterializedRows().get(0).getField(0)).isEqualTo(new SqlIntervalYearMonth(Short.MAX_VALUE, 0)); } @Test @@ -466,7 +462,7 @@ public void testIntersect() assertQuery("SELECT NULL, NULL INTERSECT SELECT NULL, NULL FROM nation"); MaterializedResult emptyResult = computeActual("SELECT 100 INTERSECT (SELECT regionkey FROM nation WHERE nationkey <10)"); - assertEquals(emptyResult.getMaterializedRows().size(), 0); + assertThat(emptyResult.getMaterializedRows().size()).isEqualTo(0); } @Test @@ -523,7 +519,7 @@ public void testExcept() "EXCEPT (SELECT * FROM (VALUES 1) EXCEPT SELECT * FROM (VALUES 1))"); MaterializedResult emptyResult = computeActual("SELECT 0 EXCEPT (SELECT regionkey FROM nation WHERE nationkey <10)"); - assertEquals(emptyResult.getMaterializedRows().size(), 0); + assertThat(emptyResult.getMaterializedRows().size()).isEqualTo(0); } @Test @@ -573,28 +569,24 @@ public void testAssignUniqueId() @Test public void testAtTimeZone() { - assertEquals(computeScalar("SELECT TIMESTAMP '2012-10-31 01:00' AT TIME ZONE INTERVAL '07:09' hour to minute"), zonedDateTime("2012-10-30 18:09:00.000 +07:09")); - assertEquals(computeScalar("SELECT TIMESTAMP '2012-10-31 01:00' AT TIME ZONE 'Asia/Oral'"), zonedDateTime("2012-10-30 16:00:00.000 Asia/Oral")); - assertEquals(computeScalar("SELECT MIN(x) AT TIME ZONE 'America/Chicago' FROM (VALUES TIMESTAMP '1970-01-01 00:01:00+00:00') t(x)"), zonedDateTime("1969-12-31 18:01:00.000 America/Chicago")); - assertEquals(computeScalar("SELECT TIMESTAMP '2012-10-31 01:00' AT TIME ZONE '+07:09'"), zonedDateTime("2012-10-30 18:09:00.000 +07:09")); - assertEquals(computeScalar("SELECT TIMESTAMP '2012-10-31 01:00 UTC' AT TIME ZONE 'America/Los_Angeles'"), zonedDateTime("2012-10-30 18:00:00.000 America/Los_Angeles")); - assertEquals(computeScalar("SELECT TIMESTAMP '2012-10-31 01:00' AT TIME ZONE 'America/Los_Angeles'"), zonedDateTime("2012-10-30 04:00:00.000 America/Los_Angeles")); - assertEquals(computeActual("SELECT x AT TIME ZONE 'America/Los_Angeles' FROM (values TIMESTAMP '1970-01-01 00:01:00+00:00', TIMESTAMP '1970-01-01 08:01:00+08:00', TIMESTAMP '1969-12-31 16:01:00-08:00') t(x)").getOnlyColumnAsSet(), - ImmutableSet.of(zonedDateTime("1969-12-31 16:01:00.000 America/Los_Angeles"))); - assertEquals(computeActual("SELECT x AT TIME ZONE 'America/Los_Angeles' FROM (values TIMESTAMP '1970-01-01 00:01:00', TIMESTAMP '1970-01-01 08:01:00', TIMESTAMP '1969-12-31 16:01:00') t(x)").getOnlyColumn().collect(toList()), - ImmutableList.of(zonedDateTime("1970-01-01 03:01:00.000 America/Los_Angeles"), zonedDateTime("1970-01-01 11:01:00.000 America/Los_Angeles"), zonedDateTime("1969-12-31 19:01:00.000 America/Los_Angeles"))); - assertEquals(computeScalar("SELECT min(x) AT TIME ZONE 'America/Los_Angeles' FROM (values TIMESTAMP '1970-01-01 00:01:00+00:00', TIMESTAMP '1970-01-01 08:01:00+08:00', TIMESTAMP '1969-12-31 16:01:00-08:00') t(x)"), - zonedDateTime("1969-12-31 16:01:00.000 America/Los_Angeles")); + assertThat(computeScalar("SELECT TIMESTAMP '2012-10-31 01:00' AT TIME ZONE INTERVAL '07:09' hour to minute")).isEqualTo(zonedDateTime("2012-10-30 18:09:00.000 +07:09")); + assertThat(computeScalar("SELECT TIMESTAMP '2012-10-31 01:00' AT TIME ZONE 'Asia/Oral'")).isEqualTo(zonedDateTime("2012-10-30 16:00:00.000 Asia/Oral")); + assertThat(computeScalar("SELECT MIN(x) AT TIME ZONE 'America/Chicago' FROM (VALUES TIMESTAMP '1970-01-01 00:01:00+00:00') t(x)")).isEqualTo(zonedDateTime("1969-12-31 18:01:00.000 America/Chicago")); + assertThat(computeScalar("SELECT TIMESTAMP '2012-10-31 01:00' AT TIME ZONE '+07:09'")).isEqualTo(zonedDateTime("2012-10-30 18:09:00.000 +07:09")); + assertThat(computeScalar("SELECT TIMESTAMP '2012-10-31 01:00 UTC' AT TIME ZONE 'America/Los_Angeles'")).isEqualTo(zonedDateTime("2012-10-30 18:00:00.000 America/Los_Angeles")); + assertThat(computeScalar("SELECT TIMESTAMP '2012-10-31 01:00' AT TIME ZONE 'America/Los_Angeles'")).isEqualTo(zonedDateTime("2012-10-30 04:00:00.000 America/Los_Angeles")); + assertThat(computeActual("SELECT x AT TIME ZONE 'America/Los_Angeles' FROM (values TIMESTAMP '1970-01-01 00:01:00+00:00', TIMESTAMP '1970-01-01 08:01:00+08:00', TIMESTAMP '1969-12-31 16:01:00-08:00') t(x)").getOnlyColumnAsSet()).isEqualTo(ImmutableSet.of(zonedDateTime("1969-12-31 16:01:00.000 America/Los_Angeles"))); + assertThat(computeActual("SELECT x AT TIME ZONE 'America/Los_Angeles' FROM (values TIMESTAMP '1970-01-01 00:01:00', TIMESTAMP '1970-01-01 08:01:00', TIMESTAMP '1969-12-31 16:01:00') t(x)").getOnlyColumn().collect(toList())).isEqualTo(ImmutableList.of(zonedDateTime("1970-01-01 03:01:00.000 America/Los_Angeles"), zonedDateTime("1970-01-01 11:01:00.000 America/Los_Angeles"), zonedDateTime("1969-12-31 19:01:00.000 America/Los_Angeles"))); + assertThat(computeScalar("SELECT min(x) AT TIME ZONE 'America/Los_Angeles' FROM (values TIMESTAMP '1970-01-01 00:01:00+00:00', TIMESTAMP '1970-01-01 08:01:00+08:00', TIMESTAMP '1969-12-31 16:01:00-08:00') t(x)")).isEqualTo(zonedDateTime("1969-12-31 16:01:00.000 America/Los_Angeles")); // with chained AT TIME ZONE - assertEquals(computeScalar("SELECT TIMESTAMP '2012-10-31 01:00' AT TIME ZONE 'America/Los_Angeles' AT TIME ZONE 'UTC'"), zonedDateTime("2012-10-30 11:00:00.000 UTC")); - assertEquals(computeScalar("SELECT TIMESTAMP '2012-10-31 01:00' AT TIME ZONE 'Asia/Tokyo' AT TIME ZONE 'America/Los_Angeles'"), zonedDateTime("2012-10-30 04:00:00.000 America/Los_Angeles")); - assertEquals(computeScalar("SELECT TIMESTAMP '2012-10-31 01:00' AT TIME ZONE 'America/Los_Angeles' AT TIME ZONE 'Asia/Shanghai'"), zonedDateTime("2012-10-30 19:00:00.000 Asia/Shanghai")); - assertEquals(computeScalar("SELECT min(x) AT TIME ZONE 'America/Los_Angeles' AT TIME ZONE 'UTC' FROM (values TIMESTAMP '1970-01-01 00:01:00+00:00', TIMESTAMP '1970-01-01 08:01:00+08:00', TIMESTAMP '1969-12-31 16:01:00-08:00') t(x)"), - zonedDateTime("1970-01-01 00:01:00.000 UTC")); + assertThat(computeScalar("SELECT TIMESTAMP '2012-10-31 01:00' AT TIME ZONE 'America/Los_Angeles' AT TIME ZONE 'UTC'")).isEqualTo(zonedDateTime("2012-10-30 11:00:00.000 UTC")); + assertThat(computeScalar("SELECT TIMESTAMP '2012-10-31 01:00' AT TIME ZONE 'Asia/Tokyo' AT TIME ZONE 'America/Los_Angeles'")).isEqualTo(zonedDateTime("2012-10-30 04:00:00.000 America/Los_Angeles")); + assertThat(computeScalar("SELECT TIMESTAMP '2012-10-31 01:00' AT TIME ZONE 'America/Los_Angeles' AT TIME ZONE 'Asia/Shanghai'")).isEqualTo(zonedDateTime("2012-10-30 19:00:00.000 Asia/Shanghai")); + assertThat(computeScalar("SELECT min(x) AT TIME ZONE 'America/Los_Angeles' AT TIME ZONE 'UTC' FROM (values TIMESTAMP '1970-01-01 00:01:00+00:00', TIMESTAMP '1970-01-01 08:01:00+08:00', TIMESTAMP '1969-12-31 16:01:00-08:00') t(x)")).isEqualTo(zonedDateTime("1970-01-01 00:01:00.000 UTC")); // with AT TIME ZONE in VALUES - assertEquals(computeScalar("SELECT * FROM (VALUES TIMESTAMP '2012-10-31 01:00' AT TIME ZONE 'Asia/Oral')"), zonedDateTime("2012-10-30 16:00:00.000 Asia/Oral")); + assertThat(computeScalar("SELECT * FROM (VALUES TIMESTAMP '2012-10-31 01:00' AT TIME ZONE 'Asia/Oral')")).isEqualTo(zonedDateTime("2012-10-30 16:00:00.000 Asia/Oral")); } @Test @@ -613,10 +605,8 @@ public void testIn() assertQuery("SELECT 1 in (1, NULL, 3)", "values true"); assertQuery("SELECT 2 in (1, NULL, 3)", "values null"); assertQuery("SELECT x FROM (values DATE '1970-01-01', DATE '1970-01-03') t(x) WHERE x IN (DATE '1970-01-01')", "values DATE '1970-01-01'"); - assertEquals( - computeActual("SELECT x FROM (values TIMESTAMP '1970-01-01 00:01:00+00:00', TIMESTAMP '1970-01-01 08:01:00+08:00', TIMESTAMP '1970-01-01 00:01:00+08:00') t(x) WHERE x IN (TIMESTAMP '1970-01-01 00:01:00+00:00')") - .getOnlyColumn().collect(toList()), - ImmutableList.of(zonedDateTime("1970-01-01 00:01:00.000 UTC"), zonedDateTime("1970-01-01 08:01:00.000 +08:00"))); + assertThat(computeActual("SELECT x FROM (values TIMESTAMP '1970-01-01 00:01:00+00:00', TIMESTAMP '1970-01-01 08:01:00+08:00', TIMESTAMP '1970-01-01 00:01:00+08:00') t(x) WHERE x IN (TIMESTAMP '1970-01-01 00:01:00+00:00')") + .getOnlyColumn().collect(toList())).isEqualTo(ImmutableList.of(zonedDateTime("1970-01-01 00:01:00.000 UTC"), zonedDateTime("1970-01-01 08:01:00.000 +08:00"))); assertQuery("SELECT COUNT(*) FROM (values 1) t(x) WHERE x IN (null, 0)", "SELECT 0"); assertQuery("SELECT d IN (DECIMAL '2.0', DECIMAL '30.0') FROM (VALUES (2.0E0)) t(d)", "SELECT true"); // coercion with type only coercion inside IN list assertQuery("SELECT REAL '-0.0' IN (VALUES REAL '1.0', REAL '0.0')", "SELECT true"); @@ -1659,7 +1649,7 @@ public void testArrayShuffle() for (int i = 0; i < 3; i++) { MaterializedResult results = computeActual(format("SELECT shuffle(ARRAY %s) FROM orders LIMIT 10", expected)); List rows = results.getMaterializedRows(); - assertEquals(rows.size(), 10); + assertThat(rows.size()).isEqualTo(10); for (MaterializedRow row : rows) { @SuppressWarnings("unchecked") @@ -1671,7 +1661,9 @@ public void testArrayShuffle() distinctResults.add(actual); } } - assertTrue(distinctResults.size() >= 24, "shuffle must produce at least 24 distinct results"); + assertThat(distinctResults.size() >= 24) + .describedAs("shuffle must produce at least 24 distinct results") + .isTrue(); } @Test @@ -1722,7 +1714,7 @@ public void testApproxSetBigint() .row(1002L) .build(); - assertEquals(actual.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(actual.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); } @Test @@ -1734,7 +1726,7 @@ public void testApproxSetVarchar() .row(1024L) .build(); - assertEquals(actual.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(actual.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); } @Test @@ -1746,7 +1738,7 @@ public void testApproxSetDouble() .row(1014L) .build(); - assertEquals(actual.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(actual.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); } @Test @@ -1809,7 +1801,7 @@ public void testApproxSetWithNulls() .row(1001L) .build(); - assertEquals(actual.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(actual.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); } @Test @@ -1821,7 +1813,7 @@ public void testApproxSetOnlyNulls() .row(new Object[] {null}) .build(); - assertEquals(actual.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(actual.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); } @Test @@ -1867,7 +1859,7 @@ public void testMergeHyperLogLog() .row(1002L) .build(); - assertEquals(actual.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(actual.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); } @Test @@ -1896,7 +1888,7 @@ public void testMergeHyperLogLogWithNulls() .row(1001L) .build(); - assertEquals(actual.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(actual.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); } @Test @@ -1925,7 +1917,7 @@ public void testMergeHyperLogLogOnlyNulls() .row(new Object[] {null}) .build(); - assertEquals(actual.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(actual.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); } @Test @@ -1935,7 +1927,7 @@ public void testEmptyApproxSet() MaterializedResult expected = resultBuilder(getSession(), BIGINT) .row(0L) .build(); - assertEquals(actual.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(actual.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); } @Test @@ -1945,7 +1937,7 @@ public void testMergeEmptyApproxSet() MaterializedResult expected = resultBuilder(getSession(), BIGINT) .row(0L) .build(); - assertEquals(actual.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(actual.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); } @Test @@ -1955,7 +1947,7 @@ public void testMergeEmptyNonEmptyApproxSet() MaterializedResult expected = resultBuilder(getSession(), BIGINT) .row(1002L) .build(); - assertEquals(actual.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(actual.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); } @Test @@ -1967,7 +1959,7 @@ public void testP4ApproxSetBigint() .row(1002L) .build(); - assertEquals(actual.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(actual.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); } @Test @@ -1979,7 +1971,7 @@ public void testP4ApproxSetVarchar() .row(1024L) .build(); - assertEquals(actual.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(actual.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); } @Test @@ -1991,7 +1983,7 @@ public void testP4ApproxSetDouble() .row(1014L) .build(); - assertEquals(actual.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(actual.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); } @Test @@ -2054,7 +2046,7 @@ public void testP4ApproxSetWithNulls() .row(1001L) .build(); - assertEquals(actual.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(actual.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); } @Test @@ -2066,7 +2058,7 @@ public void testP4ApproxSetOnlyNulls() .row(new Object[] {null}) .build(); - assertEquals(actual.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(actual.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); } @Test @@ -2154,12 +2146,12 @@ public void testValuesWithNonTrivialType() MaterializedResult actual = computeActual("VALUES (0E0/0E0, 1E0/0E0, -1E0/0E0)"); List rows = actual.getMaterializedRows(); - assertEquals(rows.size(), 1); + assertThat(rows.size()).isEqualTo(1); MaterializedRow row = rows.get(0); - assertTrue(((Double) row.getField(0)).isNaN()); - assertEquals(row.getField(1), Double.POSITIVE_INFINITY); - assertEquals(row.getField(2), Double.NEGATIVE_INFINITY); + assertThat(((Double) row.getField(0)).isNaN()).isTrue(); + assertThat(row.getField(1)).isEqualTo(Double.POSITIVE_INFINITY); + assertThat(row.getField(2)).isEqualTo(Double.NEGATIVE_INFINITY); } @Test @@ -2168,10 +2160,10 @@ public void testValuesWithTimestamp() MaterializedResult actual = computeActual("VALUES (current_timestamp, now())"); List rows = actual.getMaterializedRows(); - assertEquals(rows.size(), 1); + assertThat(rows.size()).isEqualTo(1); MaterializedRow row = rows.get(0); - assertEquals(row.getField(0), row.getField(1)); + assertThat(row.getField(0)).isEqualTo(row.getField(1)); } @Test @@ -2183,7 +2175,7 @@ public void testValuesWithUnusedColumns() .row(1) .build(); - assertEquals(actual.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(actual.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); } @Test @@ -2262,9 +2254,9 @@ public void testSpecialFloatingPointValues() { MaterializedResult actual = computeActual("SELECT nan(), infinity(), -infinity()"); MaterializedRow row = getOnlyElement(actual.getMaterializedRows()); - assertEquals(row.getField(0), Double.NaN); - assertEquals(row.getField(1), Double.POSITIVE_INFINITY); - assertEquals(row.getField(2), Double.NEGATIVE_INFINITY); + assertThat(row.getField(0)).isEqualTo(Double.NaN); + assertThat(row.getField(1)).isEqualTo(Double.POSITIVE_INFINITY); + assertThat(row.getField(2)).isEqualTo(Double.NEGATIVE_INFINITY); } @Test @@ -2423,7 +2415,7 @@ public void testRowNumberNoOptimization() " FROM orders\n" + ") WHERE NOT rn <= 10"); MaterializedResult all = computeExpected("SELECT orderkey, orderstatus FROM orders", actual.getTypes()); - assertEquals(actual.getMaterializedRows().size(), all.getMaterializedRows().size() - 10); + assertThat(actual.getMaterializedRows().size()).isEqualTo(all.getMaterializedRows().size() - 10); assertContains(all, actual); actual = computeActual("" + @@ -2432,7 +2424,7 @@ public void testRowNumberNoOptimization() " FROM orders\n" + ") WHERE rn - 5 <= 10"); all = computeExpected("SELECT orderkey, orderstatus FROM orders", actual.getTypes()); - assertEquals(actual.getMaterializedRows().size(), 15); + assertThat(actual.getMaterializedRows().size()).isEqualTo(15); assertContains(all, actual); } @@ -2443,25 +2435,25 @@ public void testRowNumberLimit() "SELECT row_number() OVER (PARTITION BY orderstatus) rn, orderstatus\n" + "FROM orders\n" + "LIMIT 10"); - assertEquals(actual.getMaterializedRows().size(), 10); + assertThat(actual.getMaterializedRows().size()).isEqualTo(10); actual = computeActual("" + "SELECT row_number() OVER (PARTITION BY orderstatus ORDER BY orderkey) rn\n" + "FROM orders\n" + "LIMIT 10"); - assertEquals(actual.getMaterializedRows().size(), 10); + assertThat(actual.getMaterializedRows().size()).isEqualTo(10); actual = computeActual("" + "SELECT row_number() OVER () rn, orderstatus\n" + "FROM orders\n" + "LIMIT 10"); - assertEquals(actual.getMaterializedRows().size(), 10); + assertThat(actual.getMaterializedRows().size()).isEqualTo(10); actual = computeActual("" + "SELECT row_number() OVER (ORDER BY orderkey) rn\n" + "FROM orders\n" + "LIMIT 10"); - assertEquals(actual.getMaterializedRows().size(), 10); + assertThat(actual.getMaterializedRows().size()).isEqualTo(10); } @Test @@ -2549,7 +2541,7 @@ public void testRowNumberFilterAndLimit() .row(2, 1L) .row(2, 2L) .build(); - assertEquals(actual.getMaterializedRows().size(), 2); + assertThat(actual.getMaterializedRows().size()).isEqualTo(2); assertContains(expected, actual); } @@ -2562,7 +2554,7 @@ public void testRowNumberUnpartitionedFilter() " FROM orders\n" + ") WHERE rn <= 5 AND orderstatus != 'Z'"); MaterializedResult all = computeExpected("SELECT orderkey, orderstatus FROM orders", actual.getTypes()); - assertEquals(actual.getMaterializedRows().size(), 5); + assertThat(actual.getMaterializedRows().size()).isEqualTo(5); assertContains(all, actual); actual = computeActual("" + @@ -2572,7 +2564,7 @@ public void testRowNumberUnpartitionedFilter() ") WHERE rn < 5"); all = computeExpected("SELECT orderkey, orderstatus FROM orders", actual.getTypes()); - assertEquals(actual.getMaterializedRows().size(), 4); + assertThat(actual.getMaterializedRows().size()).isEqualTo(4); assertContains(all, actual); actual = computeActual("" + @@ -2582,7 +2574,7 @@ public void testRowNumberUnpartitionedFilter() ") LIMIT 5"); all = computeExpected("SELECT orderkey, orderstatus FROM orders", actual.getTypes()); - assertEquals(actual.getMaterializedRows().size(), 5); + assertThat(actual.getMaterializedRows().size()).isEqualTo(5); assertContains(all, actual); } @@ -2597,7 +2589,7 @@ public void testRowNumberPartitionedFilter() MaterializedResult all = computeExpected("SELECT orderkey, orderstatus FROM orders", actual.getTypes()); // there are 3 DISTINCT orderstatus, so expect 15 rows. - assertEquals(actual.getMaterializedRows().size(), 15); + assertThat(actual.getMaterializedRows().size()).isEqualTo(15); assertContains(all, actual); // Test for unreferenced outputs @@ -2609,7 +2601,7 @@ public void testRowNumberPartitionedFilter() all = computeExpected("SELECT orderkey FROM orders", actual.getTypes()); // there are 3 distinct orderstatus, so expect 15 rows. - assertEquals(actual.getMaterializedRows().size(), 15); + assertThat(actual.getMaterializedRows().size()).isEqualTo(15); assertContains(all, actual); } @@ -3077,37 +3069,41 @@ public void testNonDeterministic() .map(row -> row.getField(0)) .distinct() .count(); - assertTrue(distinctCount >= 8, "rand() must produce different rows"); + assertThat(distinctCount >= 8) + .describedAs("rand() must produce different rows") + .isTrue(); materializedResult = computeActual("SELECT apply(1, x -> x + rand()) FROM orders LIMIT 10"); distinctCount = materializedResult.getMaterializedRows().stream() .map(row -> row.getField(0)) .distinct() .count(); - assertTrue(distinctCount >= 8, "rand() must produce different rows"); + assertThat(distinctCount >= 8) + .describedAs("rand() must produce different rows") + .isTrue(); } @Test public void testNonDeterministicFilter() { MaterializedResult materializedResult = computeActual("SELECT u FROM ( SELECT if(rand() > 0.5, 0, 1) AS u ) WHERE u <> u"); - assertEquals(materializedResult.getRowCount(), 0); + assertThat(materializedResult.getRowCount()).isEqualTo(0); materializedResult = computeActual("SELECT u, v FROM ( SELECT if(rand() > 0.5, 0, 1) AS u, 4*4 AS v ) WHERE u <> u and v > 10"); - assertEquals(materializedResult.getRowCount(), 0); + assertThat(materializedResult.getRowCount()).isEqualTo(0); materializedResult = computeActual("SELECT u, v, w FROM ( SELECT if(rand() > 0.5, 0, 1) AS u, 4*4 AS v, 'abc' AS w ) WHERE v > 10"); - assertEquals(materializedResult.getRowCount(), 1); + assertThat(materializedResult.getRowCount()).isEqualTo(1); } @Test public void testNonDeterministicProjection() { MaterializedResult materializedResult = computeActual("SELECT r, r + 1 FROM (SELECT rand(100) r FROM orders) LIMIT 10"); - assertEquals(materializedResult.getRowCount(), 10); + assertThat(materializedResult.getRowCount()).isEqualTo(10); for (MaterializedRow materializedRow : materializedResult) { - assertEquals(materializedRow.getFieldCount(), 2); - assertEquals(((Number) materializedRow.getField(0)).intValue() + 1, materializedRow.getField(1)); + assertThat(materializedRow.getFieldCount()).isEqualTo(2); + assertThat(((Number) materializedRow.getField(0)).intValue() + 1).isEqualTo(materializedRow.getField(1)); } } @@ -3522,23 +3518,23 @@ public void testApproxPercentile() List totalPrices = Ordering.natural().sortedCopy(totalPriceByStatus.get(status)); // verify real rank of returned value is within 0.05% of requested rank - assertTrue(orderKey >= orderKeys.get((int) (0.9985 * orderKeys.size()))); - assertTrue(orderKey <= orderKeys.get((int) (0.9995 * orderKeys.size()))); + assertThat(orderKey >= orderKeys.get((int) (0.9985 * orderKeys.size()))).isTrue(); + assertThat(orderKey <= orderKeys.get((int) (0.9995 * orderKeys.size()))).isTrue(); - assertTrue(orderKeyWeighted >= orderKeys.get((int) (0.9985 * orderKeys.size()))); - assertTrue(orderKeyWeighted <= orderKeys.get((int) (0.9995 * orderKeys.size()))); + assertThat(orderKeyWeighted >= orderKeys.get((int) (0.9985 * orderKeys.size()))).isTrue(); + assertThat(orderKeyWeighted <= orderKeys.get((int) (0.9995 * orderKeys.size()))).isTrue(); - assertTrue(orderKeyFractionalWeighted >= orderKeys.get((int) (0.9985 * orderKeys.size()))); - assertTrue(orderKeyFractionalWeighted <= orderKeys.get((int) (0.9995 * orderKeys.size()))); + assertThat(orderKeyFractionalWeighted >= orderKeys.get((int) (0.9985 * orderKeys.size()))).isTrue(); + assertThat(orderKeyFractionalWeighted <= orderKeys.get((int) (0.9995 * orderKeys.size()))).isTrue(); - assertTrue(totalPrice >= totalPrices.get((int) (0.9985 * totalPrices.size()))); - assertTrue(totalPrice <= totalPrices.get((int) (0.9995 * totalPrices.size()))); + assertThat(totalPrice >= totalPrices.get((int) (0.9985 * totalPrices.size()))).isTrue(); + assertThat(totalPrice <= totalPrices.get((int) (0.9995 * totalPrices.size()))).isTrue(); - assertTrue(totalPriceWeighted >= totalPrices.get((int) (0.9985 * totalPrices.size()))); - assertTrue(totalPriceWeighted <= totalPrices.get((int) (0.9995 * totalPrices.size()))); + assertThat(totalPriceWeighted >= totalPrices.get((int) (0.9985 * totalPrices.size()))).isTrue(); + assertThat(totalPriceWeighted <= totalPrices.get((int) (0.9995 * totalPrices.size()))).isTrue(); - assertTrue(totalPriceFractionalWeighted >= totalPrices.get((int) (0.9985 * totalPrices.size()))); - assertTrue(totalPriceFractionalWeighted <= totalPrices.get((int) (0.9995 * totalPrices.size()))); + assertThat(totalPriceFractionalWeighted >= totalPrices.get((int) (0.9985 * totalPrices.size()))).isTrue(); + assertThat(totalPriceFractionalWeighted <= totalPrices.get((int) (0.9995 * totalPrices.size()))).isTrue(); } } @@ -3612,8 +3608,9 @@ public void testOffset() MaterializedResult actual = computeActual("SELECT x FROM " + values + " OFFSET 2 ROWS"); MaterializedResult all = computeExpected("SELECT x FROM " + values, actual.getTypes()); - assertEquals(actual.getMaterializedRows().size(), 2); - assertNotEquals(actual.getMaterializedRows().get(0), actual.getMaterializedRows().get(1)); + assertThat(actual.getMaterializedRows().size()).isEqualTo(2); + assertThat(actual.getMaterializedRows().get(0)) + .isNotEqualTo(actual.getMaterializedRows().get(1)); assertContains(all, actual); } @@ -3625,7 +3622,7 @@ public void testOffsetWithFetch() MaterializedResult actual = computeActual("SELECT x FROM " + values + " OFFSET 2 ROWS FETCH NEXT ROW ONLY"); MaterializedResult all = computeExpected("SELECT x FROM " + values, actual.getTypes()); - assertEquals(actual.getMaterializedRows().size(), 1); + assertThat(actual.getMaterializedRows().size()).isEqualTo(1); assertContains(all, actual); } @@ -5346,16 +5343,14 @@ public void testShowSession() MaterializedResult result = computeActual(session, "SHOW SESSION"); ImmutableMap properties = Maps.uniqueIndex(result.getMaterializedRows(), input -> { - assertEquals(input.getFieldCount(), 5); + assertThat(input.getFieldCount()).isEqualTo(5); return (String) input.getField(0); }); - assertEquals(properties.get("test_string"), new MaterializedRow(1, "test_string", "foo string", "test default", "varchar", "test string property")); - assertEquals(properties.get("test_long"), new MaterializedRow(1, "test_long", "424242", "42", "bigint", "test long property")); - assertEquals(properties.get(TESTING_CATALOG + ".connector_string"), - new MaterializedRow(1, TESTING_CATALOG + ".connector_string", "bar string", "connector default", "varchar", "connector string property")); - assertEquals(properties.get(TESTING_CATALOG + ".connector_long"), - new MaterializedRow(1, TESTING_CATALOG + ".connector_long", "11", "33", "bigint", "connector long property")); + assertThat(properties.get("test_string")).isEqualTo(new MaterializedRow(1, "test_string", "foo string", "test default", "varchar", "test string property")); + assertThat(properties.get("test_long")).isEqualTo(new MaterializedRow(1, "test_long", "424242", "42", "bigint", "test long property")); + assertThat(properties.get(TESTING_CATALOG + ".connector_string")).isEqualTo(new MaterializedRow(1, TESTING_CATALOG + ".connector_string", "bar string", "connector default", "varchar", "connector string property")); + assertThat(properties.get(TESTING_CATALOG + ".connector_long")).isEqualTo(new MaterializedRow(1, TESTING_CATALOG + ".connector_long", "11", "33", "bigint", "connector long property")); } @Test @@ -5363,35 +5358,35 @@ public void testSetSession() { MaterializedResult result = computeActual("SET SESSION test_string = 'bar'"); assertNoRelationalResult(result); - assertEquals(result.getSetSessionProperties(), ImmutableMap.of("test_string", "bar")); + assertThat(result.getSetSessionProperties()).isEqualTo(ImmutableMap.of("test_string", "bar")); result = computeActual(format("SET SESSION %s.connector_long = 999", TESTING_CATALOG)); assertNoRelationalResult(result); - assertEquals(result.getSetSessionProperties(), ImmutableMap.of(TESTING_CATALOG + ".connector_long", "999")); + assertThat(result.getSetSessionProperties()).isEqualTo(ImmutableMap.of(TESTING_CATALOG + ".connector_long", "999")); result = computeActual(format("SET SESSION %s.connector_string = 'baz'", TESTING_CATALOG)); assertNoRelationalResult(result); - assertEquals(result.getSetSessionProperties(), ImmutableMap.of(TESTING_CATALOG + ".connector_string", "baz")); + assertThat(result.getSetSessionProperties()).isEqualTo(ImmutableMap.of(TESTING_CATALOG + ".connector_string", "baz")); result = computeActual(format("SET SESSION %s.connector_string = 'ban' || 'ana'", TESTING_CATALOG)); assertNoRelationalResult(result); - assertEquals(result.getSetSessionProperties(), ImmutableMap.of(TESTING_CATALOG + ".connector_string", "banana")); + assertThat(result.getSetSessionProperties()).isEqualTo(ImmutableMap.of(TESTING_CATALOG + ".connector_string", "banana")); result = computeActual(format("SET SESSION %s.connector_long = 444", TESTING_CATALOG)); assertNoRelationalResult(result); - assertEquals(result.getSetSessionProperties(), ImmutableMap.of(TESTING_CATALOG + ".connector_long", "444")); + assertThat(result.getSetSessionProperties()).isEqualTo(ImmutableMap.of(TESTING_CATALOG + ".connector_long", "444")); result = computeActual(format("SET SESSION %s.connector_long = 111 + 111", TESTING_CATALOG)); assertNoRelationalResult(result); - assertEquals(result.getSetSessionProperties(), ImmutableMap.of(TESTING_CATALOG + ".connector_long", "222")); + assertThat(result.getSetSessionProperties()).isEqualTo(ImmutableMap.of(TESTING_CATALOG + ".connector_long", "222")); result = computeActual(format("SET SESSION %s.connector_boolean = 111 < 3", TESTING_CATALOG)); assertNoRelationalResult(result); - assertEquals(result.getSetSessionProperties(), ImmutableMap.of(TESTING_CATALOG + ".connector_boolean", "false")); + assertThat(result.getSetSessionProperties()).isEqualTo(ImmutableMap.of(TESTING_CATALOG + ".connector_boolean", "false")); result = computeActual(format("SET SESSION %s.connector_double = 11.1", TESTING_CATALOG)); assertNoRelationalResult(result); - assertEquals(result.getSetSessionProperties(), ImmutableMap.of(TESTING_CATALOG + ".connector_double", "11.1")); + assertThat(result.getSetSessionProperties()).isEqualTo(ImmutableMap.of(TESTING_CATALOG + ".connector_double", "11.1")); } @Test @@ -5399,11 +5394,11 @@ public void testResetSession() { MaterializedResult result = computeActual(getSession(), "RESET SESSION test_string"); assertNoRelationalResult(result); - assertEquals(result.getResetSessionProperties(), ImmutableSet.of("test_string")); + assertThat(result.getResetSessionProperties()).isEqualTo(ImmutableSet.of("test_string")); result = computeActual(getSession(), format("RESET SESSION %s.connector_string", TESTING_CATALOG)); assertNoRelationalResult(result); - assertEquals(result.getResetSessionProperties(), ImmutableSet.of(TESTING_CATALOG + ".connector_string")); + assertThat(result.getResetSessionProperties()).isEqualTo(ImmutableSet.of(TESTING_CATALOG + ".connector_string")); } @Test @@ -5795,10 +5790,10 @@ public void testNonDeterministicTableScanPredicatePushdown() ")\n" + "WHERE rand() > 0.5"); MaterializedRow row = getOnlyElement(materializedResult.getMaterializedRows()); - assertEquals(row.getFieldCount(), 1); + assertThat(row.getFieldCount()).isEqualTo(1); long count = (Long) row.getField(0); // Technically non-deterministic unit test but has essentially a next to impossible chance of a false positive - assertTrue(count > 0 && count < 1000); + assertThat(count > 0 && count < 1000).isTrue(); } @Test @@ -5814,10 +5809,10 @@ public void testNonDeterministicAggregationPredicatePushdown() ")\n" + "WHERE rand() > 0.5"); MaterializedRow row = getOnlyElement(materializedResult.getMaterializedRows()); - assertEquals(row.getFieldCount(), 1); + assertThat(row.getFieldCount()).isEqualTo(1); long count = (Long) row.getField(0); // Technically non-deterministic unit test but has essentially a next to impossible chance of a false positive - assertTrue(count > 0 && count < 1000); + assertThat(count > 0 && count < 1000).isTrue(); } @Test @@ -5934,7 +5929,7 @@ public void testDefaultExplainTextFormat() { String query = "SELECT * FROM orders"; MaterializedResult result = computeActual("EXPLAIN " + query); - assertEquals(getOnlyElement(result.getOnlyColumnAsSet()), getExplainPlan(query, DISTRIBUTED)); + assertThat(getOnlyElement(result.getOnlyColumnAsSet())).isEqualTo(getExplainPlan(query, DISTRIBUTED)); } @Test @@ -5942,7 +5937,7 @@ public void testDefaultExplainGraphvizFormat() { String query = "SELECT * FROM orders"; MaterializedResult result = computeActual("EXPLAIN (FORMAT GRAPHVIZ) " + query); - assertEquals(getOnlyElement(result.getOnlyColumnAsSet()), getGraphvizExplainPlan(query, DISTRIBUTED)); + assertThat(getOnlyElement(result.getOnlyColumnAsSet())).isEqualTo(getGraphvizExplainPlan(query, DISTRIBUTED)); } @Test @@ -5950,7 +5945,7 @@ public void testLogicalExplain() { String query = "SELECT * FROM orders"; MaterializedResult result = computeActual("EXPLAIN (TYPE LOGICAL) " + query); - assertEquals(getOnlyElement(result.getOnlyColumnAsSet()), getExplainPlan(query, LOGICAL)); + assertThat(getOnlyElement(result.getOnlyColumnAsSet())).isEqualTo(getExplainPlan(query, LOGICAL)); } @Test @@ -5969,7 +5964,7 @@ public void testIoExplain() { String query = "SELECT * FROM orders"; MaterializedResult result = computeActual("EXPLAIN (TYPE IO) " + query); - assertEquals(getOnlyElement(result.getOnlyColumnAsSet()), getExplainPlan(query, IO)); + assertThat(getOnlyElement(result.getOnlyColumnAsSet())).isEqualTo(getExplainPlan(query, IO)); } @Test @@ -5977,7 +5972,7 @@ public void testLogicalExplainTextFormat() { String query = "SELECT * FROM orders"; MaterializedResult result = computeActual("EXPLAIN (TYPE LOGICAL, FORMAT TEXT) " + query); - assertEquals(getOnlyElement(result.getOnlyColumnAsSet()), getExplainPlan(query, LOGICAL)); + assertThat(getOnlyElement(result.getOnlyColumnAsSet())).isEqualTo(getExplainPlan(query, LOGICAL)); } @Test @@ -5985,7 +5980,7 @@ public void testLogicalExplainGraphvizFormat() { String query = "SELECT * FROM orders"; MaterializedResult result = computeActual("EXPLAIN (TYPE LOGICAL, FORMAT GRAPHVIZ) " + query); - assertEquals(getOnlyElement(result.getOnlyColumnAsSet()), getGraphvizExplainPlan(query, LOGICAL)); + assertThat(getOnlyElement(result.getOnlyColumnAsSet())).isEqualTo(getGraphvizExplainPlan(query, LOGICAL)); } @Test @@ -5993,7 +5988,7 @@ public void testDistributedExplain() { String query = "SELECT * FROM orders"; MaterializedResult result = computeActual("EXPLAIN (TYPE DISTRIBUTED) " + query); - assertEquals(getOnlyElement(result.getOnlyColumnAsSet()), getExplainPlan(query, DISTRIBUTED)); + assertThat(getOnlyElement(result.getOnlyColumnAsSet())).isEqualTo(getExplainPlan(query, DISTRIBUTED)); } @Test @@ -6001,7 +5996,7 @@ public void testDistributedExplainTextFormat() { String query = "SELECT * FROM orders"; MaterializedResult result = computeActual("EXPLAIN (TYPE DISTRIBUTED, FORMAT TEXT) " + query); - assertEquals(getOnlyElement(result.getOnlyColumnAsSet()), getExplainPlan(query, DISTRIBUTED)); + assertThat(getOnlyElement(result.getOnlyColumnAsSet())).isEqualTo(getExplainPlan(query, DISTRIBUTED)); } @Test @@ -6009,7 +6004,7 @@ public void testDistributedExplainGraphvizFormat() { String query = "SELECT * FROM orders"; MaterializedResult result = computeActual("EXPLAIN (TYPE DISTRIBUTED, FORMAT GRAPHVIZ) " + query); - assertEquals(getOnlyElement(result.getOnlyColumnAsSet()), getGraphvizExplainPlan(query, DISTRIBUTED)); + assertThat(getOnlyElement(result.getOnlyColumnAsSet())).isEqualTo(getGraphvizExplainPlan(query, DISTRIBUTED)); } @Test @@ -6017,7 +6012,7 @@ public void testExplainOfExplain() { String query = "EXPLAIN SELECT * FROM orders"; MaterializedResult result = computeActual("EXPLAIN " + query); - assertEquals(getOnlyElement(result.getOnlyColumnAsSet()), getExplainPlan(query, DISTRIBUTED)); + assertThat(getOnlyElement(result.getOnlyColumnAsSet())).isEqualTo(getExplainPlan(query, DISTRIBUTED)); } @Test @@ -6025,7 +6020,7 @@ public void testExplainOfExplainAnalyze() { String query = "EXPLAIN ANALYZE SELECT * FROM orders"; MaterializedResult result = computeActual("EXPLAIN " + query); - assertEquals(getOnlyElement(result.getOnlyColumnAsSet()), getExplainPlan(query, DISTRIBUTED)); + assertThat(getOnlyElement(result.getOnlyColumnAsSet())).isEqualTo(getExplainPlan(query, DISTRIBUTED)); } @Test @@ -6061,14 +6056,14 @@ private void assertExplainDdl(String query) private void assertExplainDdl(String query, String expected) { MaterializedResult result = computeActual("EXPLAIN " + query); - assertEquals(getOnlyElement(result.getOnlyColumnAsSet()), expected); + assertThat(getOnlyElement(result.getOnlyColumnAsSet())).isEqualTo(expected); } @Test public void testExplainValidate() { MaterializedResult result = computeActual("EXPLAIN (TYPE VALIDATE) SELECT 1"); - assertEquals(result.getOnlyValue(), true); + assertThat(result.getOnlyValue()).isEqualTo(true); } @Test @@ -6084,7 +6079,7 @@ public void testExplainExecute() .addPreparedStatement("my_query", "SELECT * FROM orders") .build(); MaterializedResult result = computeActual(session, "EXPLAIN (TYPE LOGICAL) EXECUTE my_query"); - assertEquals(getOnlyElement(result.getOnlyColumnAsSet()), getExplainPlan("SELECT * FROM orders", LOGICAL)); + assertThat(getOnlyElement(result.getOnlyColumnAsSet())).isEqualTo(getExplainPlan("SELECT * FROM orders", LOGICAL)); } @Test @@ -6094,7 +6089,7 @@ public void testExplainExecuteWithUsing() .addPreparedStatement("my_query", "SELECT * FROM orders WHERE orderkey < ?") .build(); MaterializedResult result = computeActual(session, "EXPLAIN (TYPE LOGICAL) EXECUTE my_query USING 7"); - assertEquals(getOnlyElement(result.getOnlyColumnAsSet()), getExplainPlan("SELECT * FROM orders WHERE orderkey < 7", LOGICAL)); + assertThat(getOnlyElement(result.getOnlyColumnAsSet())).isEqualTo(getExplainPlan("SELECT * FROM orders WHERE orderkey < 7", LOGICAL)); } @Test @@ -6104,10 +6099,8 @@ public void testExplainSetSessionWithUsing() .addPreparedStatement("my_query", "SET SESSION foo = ?") .build(); MaterializedResult result = computeActual(session, "EXPLAIN (TYPE LOGICAL) EXECUTE my_query USING 7"); - assertEquals( - getOnlyElement(result.getOnlyColumnAsSet()), - "SET SESSION foo = ?\n" + - "Parameters: [7]"); + assertThat(getOnlyElement(result.getOnlyColumnAsSet())).isEqualTo("SET SESSION foo = ?\n" + + "Parameters: [7]"); } @Test @@ -6118,9 +6111,10 @@ public void testShowTablesLikeWithEscape() assertQueryFails("SHOW TABLES LIKE 't$_%' ESCAPE '$$'", "Escape string must be a single character"); Set allTables = computeActual("SHOW TABLES FROM information_schema").getOnlyColumnAsSet(); - assertEquals(allTables, computeActual("SHOW TABLES FROM information_schema LIKE '%_%'").getOnlyColumnAsSet()); + assertThat(allTables).isEqualTo(computeActual("SHOW TABLES FROM information_schema LIKE '%_%'").getOnlyColumnAsSet()); Set result = computeActual("SHOW TABLES FROM information_schema LIKE '%$_%' ESCAPE '$'").getOnlyColumnAsSet(); - assertNotEquals(allTables, result); + assertThat(allTables) + .isNotEqualTo(result); assertThat(result).contains("table_privileges").allMatch(schemaName -> ((String) schemaName).contains("_")); } @@ -6128,14 +6122,14 @@ public void testShowTablesLikeWithEscape() public void testShowCatalogs() { MaterializedResult result = computeActual("SHOW CATALOGS"); - assertTrue(result.getOnlyColumnAsSet().contains(getSession().getCatalog().get())); + assertThat(result.getOnlyColumnAsSet().contains(getSession().getCatalog().get())).isTrue(); } @Test public void testShowCatalogsLike() { MaterializedResult result = computeActual(format("SHOW CATALOGS LIKE '%s'", getSession().getCatalog().get())); - assertEquals(result.getOnlyColumnAsSet(), ImmutableSet.of(getSession().getCatalog().get())); + assertThat(result.getOnlyColumnAsSet()).isEqualTo(ImmutableSet.of(getSession().getCatalog().get())); } @Test @@ -6143,48 +6137,60 @@ public void testShowFunctions() { MaterializedResult result = computeActual("SHOW FUNCTIONS"); ImmutableMultimap functions = Multimaps.index(result.getMaterializedRows(), input -> { - assertEquals(input.getFieldCount(), 6); + assertThat(input.getFieldCount()).isEqualTo(6); return (String) input.getField(0); }); - assertTrue(functions.containsKey("avg"), "Expected function names " + functions + " to contain 'avg'"); - assertEquals(functions.get("avg").asList().size(), 6); - assertEquals(functions.get("avg").asList().get(0).getField(1), "decimal(p,s)"); - assertEquals(functions.get("avg").asList().get(0).getField(2), "decimal(p,s)"); - assertEquals(functions.get("avg").asList().get(0).getField(3), "aggregate"); - assertEquals(functions.get("avg").asList().get(1).getField(1), "double"); - assertEquals(functions.get("avg").asList().get(1).getField(2), "bigint"); - assertEquals(functions.get("avg").asList().get(1).getField(3), "aggregate"); - assertEquals(functions.get("avg").asList().get(2).getField(1), "double"); - assertEquals(functions.get("avg").asList().get(2).getField(2), "double"); - assertEquals(functions.get("avg").asList().get(2).getField(3), "aggregate"); - assertEquals(functions.get("avg").asList().get(3).getField(1), "interval day to second"); - assertEquals(functions.get("avg").asList().get(3).getField(2), "interval day to second"); - assertEquals(functions.get("avg").asList().get(3).getField(3), "aggregate"); - assertEquals(functions.get("avg").asList().get(4).getField(1), "interval year to month"); - assertEquals(functions.get("avg").asList().get(4).getField(2), "interval year to month"); - assertEquals(functions.get("avg").asList().get(4).getField(3), "aggregate"); - assertEquals(functions.get("avg").asList().get(5).getField(1), "real"); - assertEquals(functions.get("avg").asList().get(5).getField(2), "real"); - assertEquals(functions.get("avg").asList().get(5).getField(3), "aggregate"); - - assertTrue(functions.containsKey("abs"), "Expected function names " + functions + " to contain 'abs'"); - assertEquals(functions.get("abs").asList().get(0).getField(3), "scalar"); - assertEquals(functions.get("abs").asList().get(0).getField(4), true); - - assertTrue(functions.containsKey("rand"), "Expected function names " + functions + " to contain 'rand'"); - assertEquals(functions.get("rand").asList().get(0).getField(3), "scalar"); - assertEquals(functions.get("rand").asList().get(0).getField(4), false); - - assertTrue(functions.containsKey("rank"), "Expected function names " + functions + " to contain 'rank'"); - assertEquals(functions.get("rank").asList().get(0).getField(3), "window"); - - assertTrue(functions.containsKey("rank"), "Expected function names " + functions + " to contain 'split_part'"); - assertEquals(functions.get("split_part").asList().get(0).getField(1), "varchar(x)"); - assertEquals(functions.get("split_part").asList().get(0).getField(2), "varchar(x), varchar(y), bigint"); - assertEquals(functions.get("split_part").asList().get(0).getField(3), "scalar"); - - assertFalse(functions.containsKey("like"), "Expected function names " + functions + " not to contain 'like'"); + assertThat(functions.containsKey("avg")) + .describedAs("Expected function names " + functions + " to contain 'avg'") + .isTrue(); + assertThat(functions.get("avg").asList().size()).isEqualTo(6); + assertThat(functions.get("avg").asList().get(0).getField(1)).isEqualTo("decimal(p,s)"); + assertThat(functions.get("avg").asList().get(0).getField(2)).isEqualTo("decimal(p,s)"); + assertThat(functions.get("avg").asList().get(0).getField(3)).isEqualTo("aggregate"); + assertThat(functions.get("avg").asList().get(1).getField(1)).isEqualTo("double"); + assertThat(functions.get("avg").asList().get(1).getField(2)).isEqualTo("bigint"); + assertThat(functions.get("avg").asList().get(1).getField(3)).isEqualTo("aggregate"); + assertThat(functions.get("avg").asList().get(2).getField(1)).isEqualTo("double"); + assertThat(functions.get("avg").asList().get(2).getField(2)).isEqualTo("double"); + assertThat(functions.get("avg").asList().get(2).getField(3)).isEqualTo("aggregate"); + assertThat(functions.get("avg").asList().get(3).getField(1)).isEqualTo("interval day to second"); + assertThat(functions.get("avg").asList().get(3).getField(2)).isEqualTo("interval day to second"); + assertThat(functions.get("avg").asList().get(3).getField(3)).isEqualTo("aggregate"); + assertThat(functions.get("avg").asList().get(4).getField(1)).isEqualTo("interval year to month"); + assertThat(functions.get("avg").asList().get(4).getField(2)).isEqualTo("interval year to month"); + assertThat(functions.get("avg").asList().get(4).getField(3)).isEqualTo("aggregate"); + assertThat(functions.get("avg").asList().get(5).getField(1)).isEqualTo("real"); + assertThat(functions.get("avg").asList().get(5).getField(2)).isEqualTo("real"); + assertThat(functions.get("avg").asList().get(5).getField(3)).isEqualTo("aggregate"); + + assertThat(functions.containsKey("abs")) + .describedAs("Expected function names " + functions + " to contain 'abs'") + .isTrue(); + assertThat(functions.get("abs").asList().get(0).getField(3)).isEqualTo("scalar"); + assertThat(functions.get("abs").asList().get(0).getField(4)).isEqualTo(true); + + assertThat(functions.containsKey("rand")) + .describedAs("Expected function names " + functions + " to contain 'rand'") + .isTrue(); + assertThat(functions.get("rand").asList().get(0).getField(3)).isEqualTo("scalar"); + assertThat(functions.get("rand").asList().get(0).getField(4)).isEqualTo(false); + + assertThat(functions.containsKey("rank")) + .describedAs("Expected function names " + functions + " to contain 'rank'") + .isTrue(); + assertThat(functions.get("rank").asList().get(0).getField(3)).isEqualTo("window"); + + assertThat(functions.containsKey("rank")) + .describedAs("Expected function names " + functions + " to contain 'split_part'") + .isTrue(); + assertThat(functions.get("split_part").asList().get(0).getField(1)).isEqualTo("varchar(x)"); + assertThat(functions.get("split_part").asList().get(0).getField(2)).isEqualTo("varchar(x), varchar(y), bigint"); + assertThat(functions.get("split_part").asList().get(0).getField(3)).isEqualTo("scalar"); + + assertThat(functions.containsKey("like")) + .describedAs("Expected function names " + functions + " not to contain 'like'") + .isFalse(); } @Test @@ -6571,28 +6577,28 @@ public void testJsonArrayFunction() public void testColumnNames() { MaterializedResult showFunctionsResult = computeActual("SHOW FUNCTIONS"); - assertEquals(showFunctionsResult.getColumnNames(), ImmutableList.of("Function", "Return Type", "Argument Types", "Function Type", "Deterministic", "Description")); + assertThat(showFunctionsResult.getColumnNames()).isEqualTo(ImmutableList.of("Function", "Return Type", "Argument Types", "Function Type", "Deterministic", "Description")); MaterializedResult showCatalogsResult = computeActual("SHOW CATALOGS"); - assertEquals(showCatalogsResult.getColumnNames(), ImmutableList.of("Catalog")); + assertThat(showCatalogsResult.getColumnNames()).isEqualTo(ImmutableList.of("Catalog")); MaterializedResult selectAllResult = computeActual("SELECT * FROM nation"); - assertEquals(selectAllResult.getColumnNames(), ImmutableList.of("nationkey", "name", "regionkey", "comment")); + assertThat(selectAllResult.getColumnNames()).isEqualTo(ImmutableList.of("nationkey", "name", "regionkey", "comment")); MaterializedResult selectResult = computeActual("SELECT nationkey, regionkey FROM nation"); - assertEquals(selectResult.getColumnNames(), ImmutableList.of("nationkey", "regionkey")); + assertThat(selectResult.getColumnNames()).isEqualTo(ImmutableList.of("nationkey", "regionkey")); MaterializedResult selectJsonArrayResult = computeActual("SELECT json_array(name, regionkey) from nation"); - assertEquals(selectJsonArrayResult.getColumnNames(), ImmutableList.of("_col0")); + assertThat(selectJsonArrayResult.getColumnNames()).isEqualTo(ImmutableList.of("_col0")); MaterializedResult selectJsonArrayAsResult = computeActual("SELECT json_array(name, regionkey) result from nation"); - assertEquals(selectJsonArrayAsResult.getColumnNames(), ImmutableList.of("result")); + assertThat(selectJsonArrayAsResult.getColumnNames()).isEqualTo(ImmutableList.of("result")); MaterializedResult showColumnResult = computeActual("SHOW COLUMNS FROM nation"); - assertEquals(showColumnResult.getColumnNames(), ImmutableList.of("Column", "Type", "Extra", "Comment")); + assertThat(showColumnResult.getColumnNames()).isEqualTo(ImmutableList.of("Column", "Type", "Extra", "Comment")); MaterializedResult showCreateTableResult = computeActual("SHOW CREATE TABLE nation"); - assertEquals(showCreateTableResult.getColumnNames(), ImmutableList.of("Create Table")); + assertThat(showCreateTableResult.getColumnNames()).isEqualTo(ImmutableList.of("Create Table")); } @Test diff --git a/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestIndexedQueries.java b/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestIndexedQueries.java index 73672ddbadc8..6eacf1bf82fa 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestIndexedQueries.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestIndexedQueries.java @@ -19,8 +19,7 @@ import io.trino.testing.tpch.TpchIndexSpec.Builder; import org.junit.jupiter.api.Test; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public abstract class AbstractTestIndexedQueries extends AbstractTestQueryFramework @@ -39,10 +38,10 @@ public void testExampleSystemTable() assertQuery("SELECT name FROM sys.example", "SELECT 'test' AS name"); MaterializedResult result = computeActual("SHOW SCHEMAS"); - assertTrue(result.getOnlyColumnAsSet().containsAll(ImmutableSet.of("sf100", "tiny", "sys"))); + assertThat(result.getOnlyColumnAsSet().containsAll(ImmutableSet.of("sf100", "tiny", "sys"))).isTrue(); result = computeActual("SHOW TABLES FROM sys"); - assertEquals(result.getOnlyColumnAsSet(), ImmutableSet.of("example")); + assertThat(result.getOnlyColumnAsSet()).isEqualTo(ImmutableSet.of("example")); } @Test diff --git a/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestJoinQueries.java b/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestJoinQueries.java index cbdbfce23ae6..5095cf747a90 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestJoinQueries.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestJoinQueries.java @@ -42,8 +42,7 @@ import static io.trino.tpch.TpchTable.PART; import static io.trino.tpch.TpchTable.REGION; import static java.lang.String.format; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public abstract class AbstractTestJoinQueries extends AbstractTestQueryFramework @@ -195,18 +194,18 @@ public void testJoinWithNonDeterministicLessThan() "SELECT count(*) FROM " + "customer c1 JOIN customer c2 ON c1.nationkey=c2.nationkey " + "WHERE c1.custkey - RANDOM(CAST(c1.custkey AS BIGINT)) < c2.custkey").getMaterializedRows()); - assertEquals(actualRow.getFieldCount(), 1); + assertThat(actualRow.getFieldCount()).isEqualTo(1); long actualCount = (Long) actualRow.getField(0); // this should be around ~69000 MaterializedRow expectedAtLeastRow = getOnlyElement(computeActual( "SELECT count(*) FROM " + "customer c1 JOIN customer c2 ON c1.nationkey=c2.nationkey " + "WHERE c1.custkey < c2.custkey").getMaterializedRows()); - assertEquals(expectedAtLeastRow.getFieldCount(), 1); + assertThat(expectedAtLeastRow.getFieldCount()).isEqualTo(1); long expectedAtLeastCount = (Long) expectedAtLeastRow.getField(0); // this is exactly 45022 // Technically non-deterministic unit test but has hopefully a next to impossible chance of a false positive - assertTrue(actualCount > expectedAtLeastCount); + assertThat(actualCount > expectedAtLeastCount).isTrue(); } @Test @@ -1220,7 +1219,7 @@ public void testOuterJoinWithCommonExpression() .row(2L, 1L) // (total rows, # of non null values) .build(); - assertEquals(actual.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(actual.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); } @Test @@ -2170,10 +2169,10 @@ public void testNonDeterministicJoinPredicatePushdown() " WHERE rand() * 1000 > table1.col1b\n" + ")"); MaterializedRow row = getOnlyElement(materializedResult.getMaterializedRows()); - assertEquals(row.getFieldCount(), 1); + assertThat(row.getFieldCount()).isEqualTo(1); long count = (Long) row.getField(0); // Technically non-deterministic unit test but has essentially a next to impossible chance of a false positive - assertTrue(count > 0 && count < 1000000); + assertThat(count > 0 && count < 1000000).isTrue(); } @Test @@ -2376,7 +2375,7 @@ private void assertJoinOutputPositions(@Language("SQL") String sql, int expected .setSystemProperty(JOIN_REORDERING_STRATEGY, "NONE") .build(), sql); - assertEquals(result.getResult().getMaterializedRows().get(0).getField(0), 0L); + assertThat(result.getResult().getMaterializedRows().get(0).getField(0)).isEqualTo(0L); QueryStats stats = getDistributedQueryRunner() .getCoordinator() .getQueryManager() @@ -2388,6 +2387,6 @@ private void assertJoinOutputPositions(@Language("SQL") String sql, int expected .map(OperatorStats::getOutputPositions) .mapToInt(Math::toIntExact) .sum(); - assertEquals(actualJoinOutputPositions, expectedJoinOutputPositions); + assertThat(actualJoinOutputPositions).isEqualTo(expectedJoinOutputPositions); } } diff --git a/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestQueries.java b/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestQueries.java index 6cb30033cddc..11687667239b 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestQueries.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestQueries.java @@ -42,8 +42,6 @@ import static java.util.stream.Collectors.joining; import static java.util.stream.IntStream.range; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; public abstract class AbstractTestQueries extends AbstractTestQueryFramework @@ -155,7 +153,7 @@ public void testLimit() { MaterializedResult actual = computeActual("SELECT orderkey FROM orders LIMIT 10"); MaterializedResult all = computeExpected("SELECT orderkey FROM orders", actual.getTypes()); - assertEquals(actual.getMaterializedRows().size(), 10); + assertThat(actual.getMaterializedRows().size()).isEqualTo(10); assertContains(all, actual); actual = computeActual( @@ -165,7 +163,7 @@ public void testLimit() "(SELECT orderkey, custkey FROM orders LIMIT 5) UNION ALL " + "SELECT orderkey, custkey FROM orders LIMIT 10"); all = computeExpected("SELECT orderkey, custkey FROM orders", actual.getTypes()); - assertEquals(actual.getMaterializedRows().size(), 10); + assertThat(actual.getMaterializedRows().size()).isEqualTo(10); assertContains(all, actual); // with ORDER BY @@ -191,7 +189,7 @@ public void testLimitWithAggregation() MaterializedResult actual = computeActual("SELECT custkey, SUM(orderkey) FROM orders GROUP BY custkey LIMIT 10"); MaterializedResult all = computeExpected("SELECT custkey, SUM(orderkey) FROM orders GROUP BY custkey", actual.getTypes()); - assertEquals(actual.getMaterializedRows().size(), 10); + assertThat(actual.getMaterializedRows().size()).isEqualTo(10); assertContains(all, actual); } @@ -201,7 +199,7 @@ public void testLimitInInlineView() MaterializedResult actual = computeActual("SELECT orderkey FROM (SELECT orderkey FROM orders LIMIT 100) T LIMIT 10"); MaterializedResult all = computeExpected("SELECT orderkey FROM orders", actual.getTypes()); - assertEquals(actual.getMaterializedRows().size(), 10); + assertThat(actual.getMaterializedRows().size()).isEqualTo(10); assertContains(all, actual); } @@ -280,7 +278,7 @@ public void testShowSchemasFrom() public void testShowSchemasLike() { MaterializedResult result = computeActual(format("SHOW SCHEMAS LIKE '%s'", getSession().getSchema().get())); - assertEquals(result.getOnlyColumnAsSet(), ImmutableSet.of(getSession().getSchema().get())); + assertThat(result.getOnlyColumnAsSet()).isEqualTo(ImmutableSet.of(getSession().getSchema().get())); } @Test @@ -292,7 +290,7 @@ public void testShowSchemasLikeWithEscape() // Using eventual assertion because set of schemas may change concurrently. assertEventually(() -> { Set allSchemas = computeActual("SHOW SCHEMAS").getOnlyColumnAsSet(); - assertEquals(allSchemas, computeActual("SHOW SCHEMAS LIKE '%_%'").getOnlyColumnAsSet()); + assertThat(allSchemas).isEqualTo(computeActual("SHOW SCHEMAS LIKE '%_%'").getOnlyColumnAsSet()); Set result = computeActual("SHOW SCHEMAS LIKE '%$_%' ESCAPE '$'").getOnlyColumnAsSet(); verify(allSchemas.stream().anyMatch(schema -> !((String) schema).contains("_")), "This test expects at least one schema without underscore in it's name. Satisfy this assumption or override the test."); @@ -352,8 +350,9 @@ public void testShowColumns() .build(); // Until we migrate all connectors to parametrized varchar we check two options - assertTrue(actual.equals(expectedParametrizedVarchar) || actual.equals(expectedUnparametrizedVarchar), - format("%s does not match neither of %s and %s", actual, expectedParametrizedVarchar, expectedUnparametrizedVarchar)); + assertThat(actual.equals(expectedParametrizedVarchar) || actual.equals(expectedUnparametrizedVarchar)) + .describedAs(format("%s does not match neither of %s and %s", actual, expectedParametrizedVarchar, expectedUnparametrizedVarchar)) + .isTrue(); } @Test @@ -464,7 +463,7 @@ public void testTableSampleBernoulliBoundaryValues() MaterializedResult all = computeExpected("SELECT orderkey FROM orders", fullSample.getTypes()); assertContains(all, fullSample); - assertEquals(emptySample.getMaterializedRows().size(), 0); + assertThat(emptySample.getMaterializedRows().size()).isEqualTo(0); } @Test @@ -477,12 +476,16 @@ public void testTableSampleBernoulli() for (int i = 0; i < 100; i++) { List values = computeActual("SELECT orderkey FROM orders TABLESAMPLE BERNOULLI (50)").getMaterializedRows(); - assertEquals(values.size(), ImmutableSet.copyOf(values).size(), "TABLESAMPLE produced duplicate rows"); + assertThat(values.size()) + .describedAs("TABLESAMPLE produced duplicate rows") + .isEqualTo(ImmutableSet.copyOf(values).size()); stats.addValue(values.size() * 1.0 / total); } double mean = stats.getGeometricMean(); - assertTrue(mean > 0.45 && mean < 0.55, format("Expected mean sampling rate to be ~0.5, but was %s", mean)); + assertThat(mean > 0.45 && mean < 0.55) + .describedAs(format("Expected mean sampling rate to be ~0.5, but was %s", mean)) + .isTrue(); } @Test diff --git a/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestQueryFramework.java b/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestQueryFramework.java index 074966f11d2a..7593b180dc6d 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestQueryFramework.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestQueryFramework.java @@ -87,9 +87,9 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Fail.fail; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.fail; @TestInstance(PER_CLASS) @Execution(CONCURRENT) diff --git a/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestWindowQueries.java b/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestWindowQueries.java index 54b4d88af37b..e4f2ce0f7ccc 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestWindowQueries.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestWindowQueries.java @@ -25,7 +25,6 @@ import static io.trino.testing.QueryAssertions.assertEqualsIgnoreOrder; import static io.trino.testing.StructuralTestUtil.mapType; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public abstract class AbstractTestWindowQueries extends AbstractTestQueryFramework @@ -91,7 +90,7 @@ public void testDistinctWindowPartitionAndPeerGroups() .row(null, null, "b", 5L) .build(); - assertEquals(actual.getMaterializedRows(), expected.getMaterializedRows()); + assertThat(actual.getMaterializedRows()).isEqualTo(expected.getMaterializedRows()); } @Test diff --git a/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java b/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java index 6f7ee9373578..31e32005c361 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java @@ -175,14 +175,10 @@ import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Fail.fail; import static org.assertj.core.api.InstanceOfAssertFactories.ZONED_DATE_TIME; import static org.junit.jupiter.api.Assumptions.abort; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; /** * Generic test for connectors. @@ -1765,11 +1761,11 @@ private void testViewMetadata(String securityClauseInCreate, String securityClau actual = computeActual("SHOW CREATE VIEW " + viewName); - assertEquals(getOnlyElement(actual.getOnlyColumnAsSet()), expectedSql); + assertThat(getOnlyElement(actual.getOnlyColumnAsSet())).isEqualTo(expectedSql); actual = computeActual(format("SHOW CREATE VIEW %s.%s.%s", getSession().getCatalog().get(), getSession().getSchema().get(), viewName)); - assertEquals(getOnlyElement(actual.getOnlyColumnAsSet()), expectedSql); + assertThat(getOnlyElement(actual.getOnlyColumnAsSet())).isEqualTo(expectedSql); assertUpdate("DROP VIEW " + viewName); } @@ -1797,7 +1793,7 @@ public void testShowCreateView() viewName); assertUpdate(ddl); - assertEquals(computeScalar("SHOW CREATE VIEW " + viewName), ddl); + assertThat(computeScalar("SHOW CREATE VIEW " + viewName)).isEqualTo(ddl); assertUpdate("DROP VIEW " + viewName); } @@ -1861,9 +1857,9 @@ public void testRenameMaterializedView() assertUpdate(session, "DROP MATERIALIZED VIEW " + uppercaseName); } - assertFalse(getQueryRunner().tableExists(session, originalMaterializedView.toString())); - assertFalse(getQueryRunner().tableExists(session, renamedMaterializedView)); - assertFalse(getQueryRunner().tableExists(session, testExistsMaterializedViewName)); + assertThat(getQueryRunner().tableExists(session, originalMaterializedView.toString())).isFalse(); + assertThat(getQueryRunner().tableExists(session, renamedMaterializedView)).isFalse(); + assertThat(getQueryRunner().tableExists(session, testExistsMaterializedViewName)).isFalse(); // rename with IF EXISTS on NOT existing materialized view assertUpdate(session, "ALTER TABLE IF EXISTS " + originalMaterializedView + " RENAME TO " + renamedMaterializedView); @@ -2012,7 +2008,7 @@ protected void testReadMetadataWithRelationsConcurrentModifications(int readIter if (hasBehavior(SUPPORTS_CREATE_MATERIALIZED_VIEW)) { readerTasks.add(queryRepeatedly(readIterations, incompleteReadTasks, "SELECT * FROM system.metadata.materialized_views WHERE catalog_name = CURRENT_CATALOG AND schema_name = CURRENT_SCHEMA")); } - assertEquals(readerTasks.size(), readerTasksCount); + assertThat(readerTasks.size()).isEqualTo(readerTasksCount); int writeTasksCount = 1 + (hasBehavior(SUPPORTS_CREATE_VIEW) ? 1 : 0) @@ -2030,7 +2026,7 @@ protected void testReadMetadataWithRelationsConcurrentModifications(int readIter if (hasBehavior(SUPPORTS_CREATE_MATERIALIZED_VIEW)) { writeTasks.add(createDropRepeatedly(writeInitialized, done, "concur_mview", "CREATE MATERIALIZED VIEW %s AS SELECT 1 a", "DROP MATERIALIZED VIEW %s")); } - assertEquals(writeTasks.size() * 2, writeTasksCount); + assertThat(writeTasks.size() * 2).isEqualTo(writeTasksCount); ExecutorService executor = newFixedThreadPool(readerTasksCount + writeTasksCount); try { @@ -2063,7 +2059,7 @@ protected void testReadMetadataWithRelationsConcurrentModifications(int readIter finally { executor.shutdownNow(); } - assertTrue(executor.awaitTermination(10, SECONDS)); + assertThat(executor.awaitTermination(10, SECONDS)).isTrue(); } @Language("SQL") @@ -2202,8 +2198,8 @@ public void testTableSampleSystem() MaterializedResult all = computeActual("SELECT orderkey FROM orders"); assertContains(all, fullSample); - assertEquals(emptySample.getMaterializedRows().size(), 0); - assertTrue(all.getMaterializedRows().size() >= randomSample.getMaterializedRows().size()); + assertThat(emptySample.getMaterializedRows().size()).isEqualTo(0); + assertThat(all.getMaterializedRows().size() >= randomSample.getMaterializedRows().size()).isTrue(); } @Test @@ -2213,10 +2209,10 @@ public void testTableSampleWithFiltering() MaterializedResult halfSample = computeActual("SELECT DISTINCT orderkey, orderdate FROM orders TABLESAMPLE SYSTEM (50) WHERE orderkey BETWEEN 0 AND 9999999999"); MaterializedResult all = computeActual("SELECT orderkey, orderdate FROM orders"); - assertEquals(emptySample.getMaterializedRows().size(), 0); + assertThat(emptySample.getMaterializedRows().size()).isEqualTo(0); // Assertions need to be loose here because SYSTEM sampling random selects data on split boundaries. In this case either all the data will be selected, or // none of it. Sampling with a 100% ratio is ignored, so that also cannot be used to guarantee results. - assertTrue(all.getMaterializedRows().size() >= halfSample.getMaterializedRows().size()); + assertThat(all.getMaterializedRows().size() >= halfSample.getMaterializedRows().size()).isTrue(); } @Test @@ -2501,10 +2497,10 @@ public void testAddColumn() "VALUES ('first', NULL, NULL, NULL), ('second', 'xxx', NULL, NULL), ('third', 'yyy', 33.3, NULL), ('fourth', 'zzz', 55.3, 'newColumn')"); } - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); assertUpdate("ALTER TABLE IF EXISTS " + tableName + " ADD COLUMN x bigint"); assertUpdate("ALTER TABLE IF EXISTS " + tableName + " ADD COLUMN IF NOT EXISTS x bigint"); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); } /** @@ -2534,7 +2530,7 @@ public void testAddColumnWithComment() assertThat(getColumnComment(tableName, "b_varchar")).isEqualTo("test new column comment"); assertUpdate("ALTER TABLE " + tableName + " ADD COLUMN empty_comment varchar COMMENT ''"); - assertEquals(getColumnComment(tableName, "empty_comment"), ""); + assertThat(getColumnComment(tableName, "empty_comment")).isEqualTo(""); } } @@ -2557,7 +2553,7 @@ public void testAddNotNullColumnToEmptyTable() } assertUpdate(addNonNullColumn); - assertFalse(columnIsNullable(tableName, "b_varchar")); + assertThat(columnIsNullable(tableName, "b_varchar")).isFalse(); assertUpdate("INSERT INTO " + tableName + " VALUES ('a', 'b')", 1); assertThat(query("TABLE " + tableName)) .skippingTypesCheck() @@ -2625,21 +2621,21 @@ public void testAddRowField() try (TestTable table = new TestTable(getQueryRunner()::execute, "test_add_field_", "AS SELECT CAST(row(1, row(10)) AS row(a integer, b row(x integer))) AS col")) { - assertEquals(getColumnType(table.getName(), "col"), "row(a integer, b row(x integer))"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(a integer, b row(x integer))"); assertUpdate("ALTER TABLE " + table.getName() + " ADD COLUMN col.c integer"); - assertEquals(getColumnType(table.getName(), "col"), "row(a integer, b row(x integer), c integer)"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(a integer, b row(x integer), c integer)"); assertThat(query("SELECT * FROM " + table.getName())).matches("SELECT CAST(row(1, row(10), NULL) AS row(a integer, b row(x integer), c integer))"); // Add a nested field assertUpdate("ALTER TABLE " + table.getName() + " ADD COLUMN col.b.y integer"); - assertEquals(getColumnType(table.getName(), "col"), "row(a integer, b row(x integer, y integer), c integer)"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(a integer, b row(x integer, y integer), c integer)"); assertThat(query("SELECT * FROM " + table.getName())).matches("SELECT CAST(row(1, row(10, NULL), NULL) AS row(a integer, b row(x integer, y integer), c integer))"); // Specify existing fields with IF NOT EXISTS option assertUpdate("ALTER TABLE " + table.getName() + " ADD COLUMN IF NOT EXISTS col.a varchar"); assertUpdate("ALTER TABLE " + table.getName() + " ADD COLUMN IF NOT EXISTS col.b.x varchar"); - assertEquals(getColumnType(table.getName(), "col"), "row(a integer, b row(x integer, y integer), c integer)"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(a integer, b row(x integer, y integer), c integer)"); // Specify existing fields without IF NOT EXISTS option assertQueryFails("ALTER TABLE " + table.getName() + " ADD COLUMN col.a varchar", ".* Field 'a' already exists"); @@ -2668,10 +2664,10 @@ public void testDropColumn() assertQueryFails("ALTER TABLE " + tableName + " DROP COLUMN a", ".* Cannot drop the only column in a table"); } - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); assertUpdate("ALTER TABLE IF EXISTS " + tableName + " DROP COLUMN notExistColumn"); assertUpdate("ALTER TABLE IF EXISTS " + tableName + " DROP COLUMN IF EXISTS notExistColumn"); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); } @Test @@ -2692,15 +2688,15 @@ public void testDropRowField() try (TestTable table = new TestTable(getQueryRunner()::execute, "test_drop_field_", "AS SELECT CAST(row(1, 2, row(10, 20)) AS row(a integer, b integer, c row(x integer, y integer))) AS col")) { - assertEquals(getColumnType(table.getName(), "col"), "row(a integer, b integer, c row(x integer, y integer))"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(a integer, b integer, c row(x integer, y integer))"); assertUpdate("ALTER TABLE " + table.getName() + " DROP COLUMN col.b"); - assertEquals(getColumnType(table.getName(), "col"), "row(a integer, c row(x integer, y integer))"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(a integer, c row(x integer, y integer))"); assertThat(query("SELECT * FROM " + table.getName())).matches("SELECT CAST(row(1, row(10, 20)) AS row(a integer, c row(x integer, y integer)))"); // Drop a nested field assertUpdate("ALTER TABLE " + table.getName() + " DROP COLUMN col.c.y"); - assertEquals(getColumnType(table.getName(), "col"), "row(a integer, c row(x integer))"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(a integer, c row(x integer))"); assertThat(query("SELECT * FROM " + table.getName())).matches("SELECT CAST(row(1, row(10)) AS row(a integer, c row(x integer)))"); // Verify failure when trying to drop unique field in nested row type @@ -2713,13 +2709,13 @@ public void testDropRowField() // Drop a row having fields assertUpdate("ALTER TABLE " + table.getName() + " DROP COLUMN col.c"); - assertEquals(getColumnType(table.getName(), "col"), "row(a integer)"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(a integer)"); assertThat(query("SELECT * FROM " + table.getName())).matches("SELECT CAST(row(1) AS row(a integer))"); // Specify non-existing fields with IF EXISTS option assertUpdate("ALTER TABLE " + table.getName() + " DROP COLUMN IF EXISTS non_existing.a"); assertUpdate("ALTER TABLE " + table.getName() + " DROP COLUMN IF EXISTS col.non_existing"); - assertEquals(getColumnType(table.getName(), "col"), "row(a integer)"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(a integer)"); } } @@ -2731,12 +2727,12 @@ public void testDropRowFieldWhenDuplicates() try (TestTable table = new TestTable(getQueryRunner()::execute, "test_drop_duplicated_field_", "AS SELECT CAST(row(1, 2, 3) AS row(a integer, a integer, b integer)) AS col")) { - assertEquals(getColumnType(table.getName(), "col"), "row(a integer, a integer, b integer)"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(a integer, a integer, b integer)"); assertQueryFails( "ALTER TABLE " + table.getName() + " DROP COLUMN col.a", "\\QField path [a] within row(a integer, a integer, b integer) is ambiguous"); - assertEquals(getColumnType(table.getName(), "col"), "row(a integer, a integer, b integer)"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(a integer, a integer, b integer)"); } } @@ -2748,7 +2744,7 @@ public void testDropRowFieldCaseSensitivity() try (TestTable table = new TestTable(getQueryRunner()::execute, "test_drop_row_field_case_sensitivity_", "AS SELECT CAST(row(1, 2) AS row(lower integer, \"UPPER\" integer)) AS col")) { - assertEquals(getColumnType(table.getName(), "col"), "row(lower integer, UPPER integer)"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(lower integer, UPPER integer)"); assertQueryFails( "ALTER TABLE " + table.getName() + " DROP COLUMN col.LOWER", @@ -2758,7 +2754,7 @@ public void testDropRowFieldCaseSensitivity() "\\Qline 1:1: Cannot resolve field 'upper' within row(lower integer, UPPER integer) type when dropping [upper] in row(lower integer, UPPER integer)"); assertUpdate("ALTER TABLE " + table.getName() + " DROP COLUMN col.\"UPPER\""); - assertEquals(getColumnType(table.getName(), "col"), "row(lower integer)"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(lower integer)"); assertThat(query("SELECT * FROM " + table.getName())).matches("SELECT CAST(row(1) AS row(lower integer))"); } } @@ -2774,16 +2770,16 @@ public void testDropAmbiguousRowFieldCaseSensitivity() AS SELECT CAST(row(1, 2, 3, 4, 5) AS row("sOME_FIELd" integer, "some_field" integer, "SomE_Field" integer, "SOME_FIELD" integer, "sOME_FieLd" integer)) AS col """)) { - assertEquals(getColumnType(table.getName(), "col"), "row(sOME_FIELd integer, some_field integer, SomE_Field integer, SOME_FIELD integer, sOME_FieLd integer)"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(sOME_FIELd integer, some_field integer, SomE_Field integer, SOME_FIELD integer, sOME_FieLd integer)"); assertUpdate("ALTER TABLE " + table.getName() + " DROP COLUMN col.some_field"); - assertEquals(getColumnType(table.getName(), "col"), "row(sOME_FIELd integer, SomE_Field integer, SOME_FIELD integer, sOME_FieLd integer)"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(sOME_FIELd integer, SomE_Field integer, SOME_FIELD integer, sOME_FieLd integer)"); assertUpdate("ALTER TABLE " + table.getName() + " DROP COLUMN col.\"SomE_Field\""); - assertEquals(getColumnType(table.getName(), "col"), "row(sOME_FIELd integer, SOME_FIELD integer, sOME_FieLd integer)"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(sOME_FIELd integer, SOME_FIELD integer, sOME_FieLd integer)"); assertUpdate("ALTER TABLE " + table.getName() + " DROP COLUMN col.\"SOME_FIELD\""); - assertEquals(getColumnType(table.getName(), "col"), "row(sOME_FIELd integer, sOME_FieLd integer)"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(sOME_FIELd integer, sOME_FieLd integer)"); assertThat(query("SELECT * FROM " + table.getName())).matches("SELECT CAST(row(1, 5) AS row(\"sOME_FIELd\" integer, \"sOME_FieLd\" integer))"); } @@ -2835,10 +2831,10 @@ public void testRenameColumn() assertQuery("SELECT * FROM " + tableName, "VALUES 'some value'"); } - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); assertUpdate("ALTER TABLE IF EXISTS " + tableName + " RENAME COLUMN columnNotExists TO y"); assertUpdate("ALTER TABLE IF EXISTS " + tableName + " RENAME COLUMN IF EXISTS columnNotExists TO y"); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); } @Test @@ -2871,21 +2867,21 @@ public void testRenameRowField() try (TestTable table = new TestTable(getQueryRunner()::execute, "test_add_field_", "AS SELECT CAST(row(1, row(10)) AS row(a integer, b row(x integer))) AS col")) { - assertEquals(getColumnType(table.getName(), "col"), "row(a integer, b row(x integer))"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(a integer, b row(x integer))"); assertUpdate("ALTER TABLE " + table.getName() + " RENAME COLUMN col.a TO a_renamed"); - assertEquals(getColumnType(table.getName(), "col"), "row(a_renamed integer, b row(x integer))"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(a_renamed integer, b row(x integer))"); assertThat(query("SELECT * FROM " + table.getName())).matches("SELECT CAST(row(1, row(10)) AS row(a_renamed integer, b row(x integer)))"); // Rename a nested field assertUpdate("ALTER TABLE " + table.getName() + " RENAME COLUMN col.b.x TO x_renamed"); - assertEquals(getColumnType(table.getName(), "col"), "row(a_renamed integer, b row(x_renamed integer))"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(a_renamed integer, b row(x_renamed integer))"); assertThat(query("SELECT * FROM " + table.getName())).matches("SELECT CAST(row(1, row(10)) AS row(a_renamed integer, b row(x_renamed integer)))"); // Specify not existing fields with IF EXISTS option assertUpdate("ALTER TABLE " + table.getName() + " RENAME COLUMN IF EXISTS col.a_missing TO a_missing_renamed"); assertUpdate("ALTER TABLE " + table.getName() + " RENAME COLUMN IF EXISTS col.b.x_missing TO x_missing_renamed"); - assertEquals(getColumnType(table.getName(), "col"), "row(a_renamed integer, b row(x_renamed integer))"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(a_renamed integer, b row(x_renamed integer))"); // Specify existing fields without IF EXISTS option assertQueryFails("ALTER TABLE " + table.getName() + " RENAME COLUMN col.a_renamed TO a_renamed", ".* Field 'a_renamed' already exists"); @@ -2900,16 +2896,16 @@ public void testRenameRowFieldCaseSensitivity() try (TestTable table = new TestTable(getQueryRunner()::execute, "test_add_row_field_case_sensitivity_", "AS SELECT CAST(row(1, 2) AS row(lower integer, \"UPPER\" integer)) AS col")) { - assertEquals(getColumnType(table.getName(), "col"), "row(lower integer, UPPER integer)"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(lower integer, UPPER integer)"); assertQueryFails("ALTER TABLE " + table.getName() + " RENAME COLUMN col.lower TO UPPER", ".* Field 'upper' already exists"); assertQueryFails("ALTER TABLE " + table.getName() + " RENAME COLUMN col.lower TO upper", ".* Field 'upper' already exists"); assertUpdate("ALTER TABLE " + table.getName() + " RENAME COLUMN col.lower TO LOWER_RENAMED"); - assertEquals(getColumnType(table.getName(), "col"), "row(lower_renamed integer, UPPER integer)"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(lower_renamed integer, UPPER integer)"); assertUpdate("ALTER TABLE " + table.getName() + " RENAME COLUMN col.\"UPPER\" TO upper_renamed"); - assertEquals(getColumnType(table.getName(), "col"), "row(lower_renamed integer, upper_renamed integer)"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(lower_renamed integer, upper_renamed integer)"); assertThat(query("SELECT * FROM " + table.getName())) .matches("SELECT CAST(row(1, 2) AS row(lower_renamed integer, upper_renamed integer))"); @@ -2929,7 +2925,7 @@ public void testSetColumnType() try (TestTable table = new TestTable(getQueryRunner()::execute, "test_set_column_type_", "AS SELECT CAST(123 AS integer) AS col")) { assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col SET DATA TYPE bigint"); - assertEquals(getColumnType(table.getName(), "col"), "bigint"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("bigint"); assertThat(query("SELECT * FROM " + table.getName())) .skippingTypesCheck() .matches("VALUES bigint '123'"); @@ -2960,7 +2956,7 @@ public void testSetColumnTypes() } setColumnType.run(); - assertEquals(getColumnType(table.getName(), "col"), setup.newColumnType); + assertThat(getColumnType(table.getName(), "col")).isEqualTo(setup.newColumnType); assertThat(query("SELECT * FROM " + table.getName())) .skippingTypesCheck() .matches("SELECT " + setup.newValueLiteral); @@ -3057,10 +3053,10 @@ public void testSetColumnTypeWithNotNull() skipTestUnless(hasBehavior(SUPPORTS_SET_COLUMN_TYPE) && hasBehavior(SUPPORTS_NOT_NULL_CONSTRAINT)); try (TestTable table = new TestTable(getQueryRunner()::execute, "test_set_column_type_null_", "(col int NOT NULL)")) { - assertFalse(columnIsNullable(table.getName(), "col")); + assertThat(columnIsNullable(table.getName(), "col")).isFalse(); assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col SET DATA TYPE bigint"); - assertFalse(columnIsNullable(table.getName(), "col")); + assertThat(columnIsNullable(table.getName(), "col")).isFalse(); } } @@ -3070,10 +3066,10 @@ public void testSetColumnTypeWithComment() skipTestUnless(hasBehavior(SUPPORTS_SET_COLUMN_TYPE) && hasBehavior(SUPPORTS_CREATE_TABLE_WITH_COLUMN_COMMENT)); try (TestTable table = new TestTable(getQueryRunner()::execute, "test_set_column_type_comment_", "(col int COMMENT 'test comment')")) { - assertEquals(getColumnComment(table.getName(), "col"), "test comment"); + assertThat(getColumnComment(table.getName(), "col")).isEqualTo("test comment"); assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col SET DATA TYPE bigint"); - assertEquals(getColumnComment(table.getName(), "col"), "test comment"); + assertThat(getColumnComment(table.getName(), "col")).isEqualTo("test comment"); } } @@ -3132,11 +3128,11 @@ public void testSetFieldType() } try (TestTable table = new TestTable(getQueryRunner()::execute, "test_set_field_type_", "AS SELECT CAST(row(123) AS row(field integer)) AS col")) { - assertEquals(getColumnType(table.getName(), "col"), "row(field integer)"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(field integer)"); assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col.field SET DATA TYPE bigint"); - assertEquals(getColumnType(table.getName(), "col"), "row(field bigint)"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(field bigint)"); assertThat(query("SELECT * FROM " + table.getName())) .skippingTypesCheck() .matches("SELECT row(bigint '123')"); @@ -3170,7 +3166,7 @@ public void testSetFieldTypes() } setFieldType.run(); - assertEquals(getColumnType(table.getName(), "col"), "row(field " + setup.newColumnType + ")"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(field " + setup.newColumnType + ")"); assertThat(query("SELECT * FROM " + table.getName())) .skippingTypesCheck() .matches("SELECT row(" + setup.newValueLiteral + ")"); @@ -3197,10 +3193,10 @@ public void testSetFieldTypeCaseSensitivity() skipTestUnless(hasBehavior(SUPPORTS_SET_FIELD_TYPE) && hasBehavior(SUPPORTS_NOT_NULL_CONSTRAINT)); try (TestTable table = new TestTable(getQueryRunner()::execute, "test_set_field_type_case_", " AS SELECT CAST(row(1) AS row(\"UPPER\" integer)) col")) { - assertEquals(getColumnType(table.getName(), "col"), "row(UPPER integer)"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(UPPER integer)"); assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col.upper SET DATA TYPE bigint"); - assertEquals(getColumnType(table.getName(), "col"), "row(UPPER bigint)"); + assertThat(getColumnType(table.getName(), "col")).isEqualTo("row(UPPER bigint)"); assertThat(query("SELECT * FROM " + table.getName())) .matches("SELECT CAST(row(1) AS row(UPPER bigint))"); } @@ -3212,10 +3208,10 @@ public void testSetFieldTypeWithNotNull() skipTestUnless(hasBehavior(SUPPORTS_SET_FIELD_TYPE) && hasBehavior(SUPPORTS_NOT_NULL_CONSTRAINT)); try (TestTable table = new TestTable(getQueryRunner()::execute, "test_set_field_type_null_", "(col row(field int) NOT NULL)")) { - assertFalse(columnIsNullable(table.getName(), "col")); + assertThat(columnIsNullable(table.getName(), "col")).isFalse(); assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col.field SET DATA TYPE bigint"); - assertFalse(columnIsNullable(table.getName(), "col")); + assertThat(columnIsNullable(table.getName(), "col")).isFalse(); } } @@ -3225,10 +3221,10 @@ public void testSetFieldTypeWithComment() skipTestUnless(hasBehavior(SUPPORTS_SET_FIELD_TYPE) && hasBehavior(SUPPORTS_CREATE_TABLE_WITH_COLUMN_COMMENT)); try (TestTable table = new TestTable(getQueryRunner()::execute, "test_set_field_type_comment_", "(col row(field int) COMMENT 'test comment')")) { - assertEquals(getColumnComment(table.getName(), "col"), "test comment"); + assertThat(getColumnComment(table.getName(), "col")).isEqualTo("test comment"); assertUpdate("ALTER TABLE " + table.getName() + " ALTER COLUMN col.field SET DATA TYPE bigint"); - assertEquals(getColumnComment(table.getName(), "col"), "test comment"); + assertThat(getColumnComment(table.getName(), "col")).isEqualTo("test comment"); } } @@ -3288,49 +3284,49 @@ public void testCreateTable() assertThat(computeActual("SHOW TABLES").getOnlyColumnAsSet()) // prime the cache, if any .doesNotContain(tableName); assertUpdate("CREATE TABLE " + tableName + " (a bigint, b double, c varchar(50))"); - assertTrue(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isTrue(); assertThat(computeActual("SHOW TABLES").getOnlyColumnAsSet()) .contains(tableName); assertTableColumnNames(tableName, "a", "b", "c"); - assertNull(getTableComment(getSession().getCatalog().orElseThrow(), getSession().getSchema().orElseThrow(), tableName)); + assertThat(getTableComment(getSession().getCatalog().orElseThrow(), getSession().getSchema().orElseThrow(), tableName)).isNull(); assertUpdate("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); assertThat(computeActual("SHOW TABLES").getOnlyColumnAsSet()) .doesNotContain(tableName); assertQueryFails("CREATE TABLE " + tableName + " (a bad_type)", ".* Unknown type 'bad_type' for column 'a'"); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); // TODO (https://github.com/trinodb/trino/issues/5901) revert to longer name when Oracle version is updated tableName = "test_cr_not_exists_" + randomNameSuffix(); assertUpdate("CREATE TABLE " + tableName + " (a bigint, b varchar(50), c double)"); - assertTrue(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isTrue(); assertTableColumnNames(tableName, "a", "b", "c"); assertUpdate("CREATE TABLE IF NOT EXISTS " + tableName + " (d bigint, e varchar(50))"); - assertTrue(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isTrue(); assertTableColumnNames(tableName, "a", "b", "c"); assertUpdate("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); // Test CREATE TABLE LIKE tableName = "test_create_orig_" + randomNameSuffix(); assertUpdate("CREATE TABLE " + tableName + " (a bigint, b double, c varchar(50))"); - assertTrue(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isTrue(); assertTableColumnNames(tableName, "a", "b", "c"); String tableNameLike = "test_create_like_" + randomNameSuffix(); assertUpdate("CREATE TABLE " + tableNameLike + " (LIKE " + tableName + ", d bigint, e varchar(50))"); - assertTrue(getQueryRunner().tableExists(getSession(), tableNameLike)); + assertThat(getQueryRunner().tableExists(getSession(), tableNameLike)).isTrue(); assertTableColumnNames(tableNameLike, "a", "b", "c", "d", "e"); assertUpdate("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); assertUpdate("DROP TABLE " + tableNameLike); - assertFalse(getQueryRunner().tableExists(getSession(), tableNameLike)); + assertThat(getQueryRunner().tableExists(getSession(), tableNameLike)).isFalse(); } @Test @@ -3534,7 +3530,7 @@ public void testCreateTableWithLongTableName() String validTableName = baseTableName + "z".repeat(maxLength - baseTableName.length()); assertUpdate("CREATE TABLE " + validTableName + " (a bigint)"); - assertTrue(getQueryRunner().tableExists(getSession(), validTableName)); + assertThat(getQueryRunner().tableExists(getSession(), validTableName)).isTrue(); assertUpdate("DROP TABLE " + validTableName); if (maxTableNameLength().isEmpty()) { @@ -3544,7 +3540,7 @@ public void testCreateTableWithLongTableName() String invalidTableName = validTableName + "z"; assertThatThrownBy(() -> assertUpdate("CREATE TABLE " + invalidTableName + " (a bigint)")) .satisfies(this::verifyTableNameLengthFailurePermissible); - assertFalse(getQueryRunner().tableExists(getSession(), validTableName)); + assertThat(getQueryRunner().tableExists(getSession(), validTableName)).isFalse(); } @Test @@ -3563,7 +3559,7 @@ public void testRenameTableToLongTableName() String validTargetTableName = baseTableName + "z".repeat(maxLength - baseTableName.length()); assertUpdate("ALTER TABLE " + sourceTableName + " RENAME TO " + validTargetTableName); - assertTrue(getQueryRunner().tableExists(getSession(), validTargetTableName)); + assertThat(getQueryRunner().tableExists(getSession(), validTargetTableName)).isTrue(); assertQuery("SELECT x FROM " + validTargetTableName, "VALUES 123"); assertUpdate("DROP TABLE " + validTargetTableName); @@ -3575,7 +3571,7 @@ public void testRenameTableToLongTableName() String invalidTargetTableName = validTargetTableName + "z"; assertThatThrownBy(() -> assertUpdate("ALTER TABLE " + sourceTableName + " RENAME TO " + invalidTargetTableName)) .satisfies(this::verifyTableNameLengthFailurePermissible); - assertFalse(getQueryRunner().tableExists(getSession(), invalidTargetTableName)); + assertThat(getQueryRunner().tableExists(getSession(), invalidTargetTableName)).isFalse(); assertUpdate("DROP TABLE " + sourceTableName); } @@ -3608,7 +3604,7 @@ public void testCreateTableWithLongColumnName() String validColumnName = baseColumnName + "z".repeat(maxLength - baseColumnName.length()); assertUpdate("CREATE TABLE " + tableName + " (" + validColumnName + " bigint)"); - assertTrue(columnExists(tableName, validColumnName)); + assertThat(columnExists(tableName, validColumnName)).isTrue(); assertUpdate("DROP TABLE " + tableName); if (maxColumnNameLength().isEmpty()) { @@ -3618,7 +3614,7 @@ public void testCreateTableWithLongColumnName() String invalidColumnName = validColumnName + "z"; assertThatThrownBy(() -> assertUpdate("CREATE TABLE " + tableName + " (" + invalidColumnName + " bigint)")) .satisfies(this::verifyColumnNameLengthFailurePermissible); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); } // TODO: Add test for CREATE TABLE AS SELECT with long column name @@ -3638,7 +3634,7 @@ public void testAlterTableAddLongColumnName() String validTargetColumnName = baseColumnName + "z".repeat(maxLength - baseColumnName.length()); assertUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + validTargetColumnName + " int"); - assertTrue(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isTrue(); assertQuery("SELECT x FROM " + tableName, "VALUES 123"); assertUpdate("DROP TABLE " + tableName); @@ -3715,7 +3711,7 @@ public void testCreateTableWithTableComment() } assertUpdate("CREATE TABLE " + tableName + " (a bigint) COMMENT 'test comment'"); - assertEquals(getTableComment(getSession().getCatalog().orElseThrow(), getSession().getSchema().orElseThrow(), tableName), "test comment"); + assertThat(getTableComment(getSession().getCatalog().orElseThrow(), getSession().getSchema().orElseThrow(), tableName)).isEqualTo("test comment"); assertUpdate("DROP TABLE " + tableName); } @@ -3733,7 +3729,7 @@ public void testCreateTableWithColumnComment() } assertUpdate("CREATE TABLE " + tableName + " (a bigint COMMENT 'test comment')"); - assertEquals(getColumnComment(tableName, "a"), "test comment"); + assertThat(getColumnComment(tableName, "a")).isEqualTo("test comment"); assertUpdate("DROP TABLE " + tableName); } @@ -3765,7 +3761,7 @@ public void testCreateTableAsSelect() } assertUpdate("CREATE TABLE IF NOT EXISTS " + tableName + " AS SELECT name, regionkey FROM nation", "SELECT count(*) FROM nation"); assertTableColumnNames(tableName, "name", "regionkey"); - assertNull(getTableComment(getSession().getCatalog().orElseThrow(), getSession().getSchema().orElseThrow(), tableName)); + assertThat(getTableComment(getSession().getCatalog().orElseThrow(), getSession().getSchema().orElseThrow(), tableName)).isNull(); assertUpdate("DROP TABLE " + tableName); // Some connectors support CREATE TABLE AS but not the ordinary CREATE TABLE. Let's test CTAS IF NOT EXISTS with a table that is guaranteed to exist. @@ -3842,7 +3838,7 @@ public void testCreateTableAsSelectWithTableComment() } assertUpdate("CREATE TABLE " + tableName + " COMMENT 'test comment' AS SELECT name FROM nation", 25); - assertEquals(getTableComment(getSession().getCatalog().orElseThrow(), getSession().getSchema().orElseThrow(), tableName), "test comment"); + assertThat(getTableComment(getSession().getCatalog().orElseThrow(), getSession().getSchema().orElseThrow(), tableName)).isEqualTo("test comment"); assertUpdate("DROP TABLE " + tableName); } @@ -3891,7 +3887,7 @@ protected void assertCreateTableAsSelect(Session session, @Language("SQL") Strin assertQuery(session, "SELECT * FROM " + table, expectedQuery); assertUpdate(session, "DROP TABLE " + table); - assertFalse(getQueryRunner().tableExists(session, table)); + assertThat(getQueryRunner().tableExists(session, table)).isFalse(); } @Test @@ -3960,12 +3956,12 @@ public void testRenameTable() assertUpdate("DROP TABLE " + uppercaseName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); - assertFalse(getQueryRunner().tableExists(getSession(), renamedTable)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); + assertThat(getQueryRunner().tableExists(getSession(), renamedTable)).isFalse(); assertUpdate("ALTER TABLE IF EXISTS " + tableName + " RENAME TO " + renamedTable); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); - assertFalse(getQueryRunner().tableExists(getSession(), renamedTable)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); + assertThat(getQueryRunner().tableExists(getSession(), renamedTable)).isFalse(); } @Test @@ -4004,14 +4000,14 @@ public void testRenameTableAcrossSchema() } } - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); assertQuery("SELECT x FROM " + schemaName + "." + renamedTable, "VALUES 123"); assertUpdate("DROP TABLE " + schemaName + "." + renamedTable); assertUpdate("DROP SCHEMA " + schemaName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); - assertFalse(getQueryRunner().tableExists(Session.builder(getSession()).setSchema(schemaName).build(), renamedTable)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); + assertThat(getQueryRunner().tableExists(Session.builder(getSession()).setSchema(schemaName).build(), renamedTable)).isFalse(); } @Test @@ -5210,18 +5206,18 @@ public void testDropTable() skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE)); String tableName = "test_drop_table_" + randomNameSuffix(); assertUpdate("CREATE TABLE " + tableName + "(col bigint)"); - assertTrue(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isTrue(); assertUpdate("DROP TABLE " + tableName); - assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(getQueryRunner().tableExists(getSession(), tableName)).isFalse(); } @Test public void testDropTableIfExists() { - assertFalse(getQueryRunner().tableExists(getSession(), "test_drop_if_exists")); + assertThat(getQueryRunner().tableExists(getSession(), "test_drop_if_exists")).isFalse(); assertUpdate("DROP TABLE IF EXISTS test_drop_if_exists"); - assertFalse(getQueryRunner().tableExists(getSession(), "test_drop_if_exists")); + assertThat(getQueryRunner().tableExists(getSession(), "test_drop_if_exists")).isFalse(); } @Test @@ -5249,13 +5245,11 @@ public void testQueryLoggingCount() executeExclusively(() -> { assertEventually( new Duration(1, MINUTES), - () -> assertEquals( - queryManager.getQueries().stream() - .map(BasicQueryInfo::getQueryId) - .map(queryManager::getFullQueryInfo) - .filter(info -> !info.isFinalQueryInfo()) - .collect(toList()), - ImmutableList.of())); + () -> assertThat(queryManager.getQueries().stream() + .map(BasicQueryInfo::getQueryId) + .map(queryManager::getFullQueryInfo) + .filter(info -> !info.isFinalQueryInfo()) + .collect(toList())).isEqualTo(ImmutableList.of())); // We cannot simply get the number of completed queries as soon as all the queries are completed, because this counter may not be up-to-date at that point. // The completed queries counter is updated in a final query info listener, which is called eventually. @@ -5273,8 +5267,8 @@ public void testQueryLoggingCount() // TODO: Figure out a better way of synchronization assertEventually( new Duration(1, MINUTES), - () -> assertEquals(dispatchManager.getStats().getCompletedQueries().getTotalCount() - beforeCompletedQueriesCount, 4)); - assertEquals(dispatchManager.getStats().getSubmittedQueries().getTotalCount() - beforeSubmittedQueriesCount, 4); + () -> assertThat(dispatchManager.getStats().getCompletedQueries().getTotalCount() - beforeCompletedQueriesCount).isEqualTo(4)); + assertThat(dispatchManager.getStats().getSubmittedQueries().getTotalCount() - beforeSubmittedQueriesCount).isEqualTo(4); }); } @@ -5306,7 +5300,7 @@ private T waitUntilStable(Supplier computation, Duration timeout) public void testShowSchemasFromOther() { MaterializedResult result = computeActual("SHOW SCHEMAS FROM tpch"); - assertTrue(result.getOnlyColumnAsSet().containsAll(ImmutableSet.of(INFORMATION_SCHEMA, "tiny", "sf1"))); + assertThat(result.getOnlyColumnAsSet().containsAll(ImmutableSet.of(INFORMATION_SCHEMA, "tiny", "sf1"))).isTrue(); } // TODO move to to engine-only @@ -5333,17 +5327,17 @@ public void testWrittenStats() MaterializedResultWithQueryId resultResultWithQueryId = getDistributedQueryRunner().executeWithQueryId(getSession(), sql); QueryInfo queryInfo = getDistributedQueryRunner().getCoordinator().getQueryManager().getFullQueryInfo(resultResultWithQueryId.getQueryId()); - assertEquals(queryInfo.getQueryStats().getOutputPositions(), 1L); - assertEquals(queryInfo.getQueryStats().getWrittenPositions(), 25L); - assertTrue(queryInfo.getQueryStats().getLogicalWrittenDataSize().toBytes() > 0L); + assertThat(queryInfo.getQueryStats().getOutputPositions()).isEqualTo(1L); + assertThat(queryInfo.getQueryStats().getWrittenPositions()).isEqualTo(25L); + assertThat(queryInfo.getQueryStats().getLogicalWrittenDataSize().toBytes() > 0L).isTrue(); sql = "INSERT INTO " + tableName + " SELECT * FROM nation LIMIT 10"; resultResultWithQueryId = getDistributedQueryRunner().executeWithQueryId(getSession(), sql); queryInfo = getDistributedQueryRunner().getCoordinator().getQueryManager().getFullQueryInfo(resultResultWithQueryId.getQueryId()); - assertEquals(queryInfo.getQueryStats().getOutputPositions(), 1L); - assertEquals(queryInfo.getQueryStats().getWrittenPositions(), 10L); - assertTrue(queryInfo.getQueryStats().getLogicalWrittenDataSize().toBytes() > 0L); + assertThat(queryInfo.getQueryStats().getOutputPositions()).isEqualTo(1L); + assertThat(queryInfo.getQueryStats().getWrittenPositions()).isEqualTo(10L); + assertThat(queryInfo.getQueryStats().getLogicalWrittenDataSize().toBytes() > 0L).isTrue(); } finally { assertUpdate("DROP TABLE IF EXISTS " + tableName); @@ -5585,7 +5579,7 @@ protected void testCreateTableWithTableCommentSpecialCharacter(String comment) skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE_WITH_TABLE_COMMENT)); try (TestTable table = new TestTable(getQueryRunner()::execute, "test_create_", "(a bigint) COMMENT " + varcharLiteral(comment))) { - assertEquals(getTableComment(getSession().getCatalog().orElseThrow(), getSession().getSchema().orElseThrow(), table.getName()), comment); + assertThat(getTableComment(getSession().getCatalog().orElseThrow(), getSession().getSchema().orElseThrow(), table.getName())).isEqualTo(comment); } } @@ -5608,7 +5602,7 @@ private void testCreateTableAsSelectWithTableCommentSpecialCharacter(String comm skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA) && hasBehavior(SUPPORTS_CREATE_TABLE_WITH_TABLE_COMMENT)); try (TestTable table = new TestTable(getQueryRunner()::execute, "test_create_", " COMMENT " + varcharLiteral(comment) + " AS SELECT 1 a")) { - assertEquals(getTableComment(getSession().getCatalog().orElseThrow(), getSession().getSchema().orElseThrow(), table.getName()), comment); + assertThat(getTableComment(getSession().getCatalog().orElseThrow(), getSession().getSchema().orElseThrow(), table.getName())).isEqualTo(comment); } } @@ -5631,7 +5625,7 @@ private void testCreateTableWithColumnCommentSpecialCharacter(String comment) skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE_WITH_COLUMN_COMMENT)); try (TestTable table = new TestTable(getQueryRunner()::execute, "test_create_", " (a bigint COMMENT " + varcharLiteral(comment) + ")")) { - assertEquals(getColumnComment(table.getName(), "a"), comment); + assertThat(getColumnComment(table.getName(), "a")).isEqualTo(comment); } } @@ -5655,7 +5649,7 @@ protected void testAddColumnWithCommentSpecialCharacter(String comment) try (TestTable table = new TestTable(getQueryRunner()::execute, "test_add_col_", "(a_varchar varchar)")) { assertUpdate("ALTER TABLE " + table.getName() + " ADD COLUMN b_varchar varchar COMMENT " + varcharLiteral(comment)); - assertEquals(getColumnComment(table.getName(), "b_varchar"), comment); + assertThat(getColumnComment(table.getName(), "b_varchar")).isEqualTo(comment); } } @@ -5679,7 +5673,7 @@ private void testCommentTableSpecialCharacter(String comment) try (TestTable table = new TestTable(getQueryRunner()::execute, "test_comment_table_", "(a integer)")) { assertUpdate("COMMENT ON TABLE " + table.getName() + " IS " + varcharLiteral(comment)); - assertEquals(getTableComment(getSession().getCatalog().orElseThrow(), getSession().getSchema().orElseThrow(), table.getName()), comment); + assertThat(getTableComment(getSession().getCatalog().orElseThrow(), getSession().getSchema().orElseThrow(), table.getName())).isEqualTo(comment); } } @@ -5703,7 +5697,7 @@ private void testCommentColumnSpecialCharacter(String comment) try (TestTable table = new TestTable(getQueryRunner()::execute, "test_comment_column_", "(a integer)")) { assertUpdate("COMMENT ON COLUMN " + table.getName() + ".a IS " + varcharLiteral(comment)); - assertEquals(getColumnComment(table.getName(), "a"), comment); + assertThat(getColumnComment(table.getName(), "a")).isEqualTo(comment); } } @@ -6836,9 +6830,9 @@ public void testProjectionPushdownReadsLessData() } assertThat(statsWithoutPushdown.getProcessedInputDataSize()).isGreaterThan(processedDataSizeWithPushdown); }, - results -> assertEquals(results.getOnlyColumnAsSet(), expectedResult.getOnlyColumnAsSet())); + results -> assertThat(results.getOnlyColumnAsSet()).isEqualTo(expectedResult.getOnlyColumnAsSet())); }, - results -> assertEquals(results.getOnlyColumnAsSet(), expectedResult.getOnlyColumnAsSet())); + results -> assertThat(results.getOnlyColumnAsSet()).isEqualTo(expectedResult.getOnlyColumnAsSet())); } } @@ -6869,9 +6863,9 @@ public void testProjectionPushdownPhysicalInputSize() assertThat(statsWithSelectLeafField.getPhysicalInputDataSize()).isEqualTo(statsWithSelectRootField.getPhysicalInputDataSize()); } }, - results -> assertEquals(results.getOnlyColumnAsSet(), computeActual("SELECT val + 1 FROM UNNEST(SEQUENCE(1, 10)) AS t(val)").getOnlyColumnAsSet())); + results -> assertThat(results.getOnlyColumnAsSet()).isEqualTo(computeActual("SELECT val + 1 FROM UNNEST(SEQUENCE(1, 10)) AS t(val)").getOnlyColumnAsSet())); }, - results -> assertEquals(results.getOnlyColumnAsSet(), computeActual("SELECT ROW(val + 1, val + 2) FROM UNNEST(SEQUENCE(1, 10)) AS t(val)").getOnlyColumnAsSet())); + results -> assertThat(results.getOnlyColumnAsSet()).isEqualTo(computeActual("SELECT ROW(val + 1, val + 2) FROM UNNEST(SEQUENCE(1, 10)) AS t(val)").getOnlyColumnAsSet())); // Verify that the physical input size is the same when reading the root field compared to reading both the root and root.leaf1 fields assertQueryStats( @@ -6886,7 +6880,7 @@ public void testProjectionPushdownPhysicalInputSize() }, results -> assertEqualsIgnoreOrder(results.getMaterializedRows(), computeActual("SELECT ROW(val + 1, val + 2), val + 1 FROM UNNEST(SEQUENCE(1, 10)) AS t(val)").getMaterializedRows())); }, - results -> assertEquals(results.getOnlyColumnAsSet(), computeActual("SELECT ROW(val + 1, val + 2) FROM UNNEST(SEQUENCE(1, 10)) AS t(val)").getOnlyColumnAsSet())); + results -> assertThat(results.getOnlyColumnAsSet()).isEqualTo(computeActual("SELECT ROW(val + 1, val + 2) FROM UNNEST(SEQUENCE(1, 10)) AS t(val)").getOnlyColumnAsSet())); } } diff --git a/testing/trino-testing/src/main/java/io/trino/testing/BaseDynamicPartitionPruningTest.java b/testing/trino-testing/src/main/java/io/trino/testing/BaseDynamicPartitionPruningTest.java index b6e9cdba6c18..fa815289b5ab 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/BaseDynamicPartitionPruningTest.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/BaseDynamicPartitionPruningTest.java @@ -55,8 +55,6 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Isolated @@ -110,14 +108,14 @@ public void testJoinWithEmptyBuildSide() assertEqualsIgnoreOrder(result.getResult(), expected); DynamicFiltersStats dynamicFiltersStats = getDynamicFilteringStats(result.getQueryId()); - assertEquals(dynamicFiltersStats.getTotalDynamicFilters(), 1L); - assertEquals(dynamicFiltersStats.getLazyDynamicFilters(), 1L); - assertEquals(dynamicFiltersStats.getReplicatedDynamicFilters(), 0L); - assertEquals(dynamicFiltersStats.getDynamicFiltersCompleted(), 1L); + assertThat(dynamicFiltersStats.getTotalDynamicFilters()).isEqualTo(1L); + assertThat(dynamicFiltersStats.getLazyDynamicFilters()).isEqualTo(1L); + assertThat(dynamicFiltersStats.getReplicatedDynamicFilters()).isEqualTo(0L); + assertThat(dynamicFiltersStats.getDynamicFiltersCompleted()).isEqualTo(1L); DynamicFilterDomainStats domainStats = getOnlyElement(dynamicFiltersStats.getDynamicFilterDomainStats()); - assertEquals(domainStats.getSimplifiedDomain(), none(BIGINT).toString(getSession().toConnectorSession())); - assertTrue(domainStats.getCollectionDuration().isPresent()); + assertThat(domainStats.getSimplifiedDomain()).isEqualTo(none(BIGINT).toString(getSession().toConnectorSession())); + assertThat(domainStats.getCollectionDuration().isPresent()).isTrue(); } @Test @@ -133,13 +131,13 @@ public void testJoinWithSelectiveBuildSide() assertEqualsIgnoreOrder(result.getResult(), expected); DynamicFiltersStats dynamicFiltersStats = getDynamicFilteringStats(result.getQueryId()); - assertEquals(dynamicFiltersStats.getTotalDynamicFilters(), 1L); - assertEquals(dynamicFiltersStats.getLazyDynamicFilters(), 1L); - assertEquals(dynamicFiltersStats.getReplicatedDynamicFilters(), 0L); - assertEquals(dynamicFiltersStats.getDynamicFiltersCompleted(), 1L); + assertThat(dynamicFiltersStats.getTotalDynamicFilters()).isEqualTo(1L); + assertThat(dynamicFiltersStats.getLazyDynamicFilters()).isEqualTo(1L); + assertThat(dynamicFiltersStats.getReplicatedDynamicFilters()).isEqualTo(0L); + assertThat(dynamicFiltersStats.getDynamicFiltersCompleted()).isEqualTo(1L); DynamicFilterDomainStats domainStats = getOnlyElement(dynamicFiltersStats.getDynamicFilterDomainStats()); - assertEquals(domainStats.getSimplifiedDomain(), singleValue(BIGINT, 1L).toString(getSession().toConnectorSession())); + assertThat(domainStats.getSimplifiedDomain()).isEqualTo(singleValue(BIGINT, 1L).toString(getSession().toConnectorSession())); } @Test @@ -154,10 +152,10 @@ public void testJoinWithNonSelectiveBuildSide() assertEqualsIgnoreOrder(result.getResult(), expected); DynamicFiltersStats dynamicFiltersStats = getDynamicFilteringStats(result.getQueryId()); - assertEquals(dynamicFiltersStats.getTotalDynamicFilters(), 1L); - assertEquals(dynamicFiltersStats.getLazyDynamicFilters(), 1L); - assertEquals(dynamicFiltersStats.getReplicatedDynamicFilters(), 0L); - assertEquals(dynamicFiltersStats.getDynamicFiltersCompleted(), 1L); + assertThat(dynamicFiltersStats.getTotalDynamicFilters()).isEqualTo(1L); + assertThat(dynamicFiltersStats.getLazyDynamicFilters()).isEqualTo(1L); + assertThat(dynamicFiltersStats.getReplicatedDynamicFilters()).isEqualTo(0L); + assertThat(dynamicFiltersStats.getDynamicFiltersCompleted()).isEqualTo(1L); DynamicFilterDomainStats domainStats = getOnlyElement(dynamicFiltersStats.getDynamicFilterDomainStats()); assertThat(domainStats.getSimplifiedDomain()) @@ -176,16 +174,14 @@ public void testJoinLargeBuildSideRangeDynamicFiltering() assertEqualsIgnoreOrder(result.getResult(), expected); DynamicFiltersStats dynamicFiltersStats = getDynamicFilteringStats(result.getQueryId()); - assertEquals(dynamicFiltersStats.getTotalDynamicFilters(), 1L); - assertEquals(dynamicFiltersStats.getLazyDynamicFilters(), 1L); - assertEquals(dynamicFiltersStats.getReplicatedDynamicFilters(), 0L); - assertEquals(dynamicFiltersStats.getDynamicFiltersCompleted(), 1L); + assertThat(dynamicFiltersStats.getTotalDynamicFilters()).isEqualTo(1L); + assertThat(dynamicFiltersStats.getLazyDynamicFilters()).isEqualTo(1L); + assertThat(dynamicFiltersStats.getReplicatedDynamicFilters()).isEqualTo(0L); + assertThat(dynamicFiltersStats.getDynamicFiltersCompleted()).isEqualTo(1L); DynamicFilterDomainStats domainStats = getOnlyElement(dynamicFiltersStats.getDynamicFilterDomainStats()); - assertEquals( - domainStats.getSimplifiedDomain(), - Domain.create(ValueSet.ofRanges(range(BIGINT, 1L, true, 60000L, true)), false) - .toString(getSession().toConnectorSession())); + assertThat(domainStats.getSimplifiedDomain()).isEqualTo(Domain.create(ValueSet.ofRanges(range(BIGINT, 1L, true, 60000L, true)), false) + .toString(getSession().toConnectorSession())); } @Test @@ -204,10 +200,10 @@ public void testJoinWithMultipleDynamicFiltersOnProbe() assertEqualsIgnoreOrder(result.getResult(), expected); DynamicFiltersStats dynamicFiltersStats = getDynamicFilteringStats(result.getQueryId()); - assertEquals(dynamicFiltersStats.getTotalDynamicFilters(), 2L); - assertEquals(dynamicFiltersStats.getLazyDynamicFilters(), 2L); - assertEquals(dynamicFiltersStats.getReplicatedDynamicFilters(), 0L); - assertEquals(dynamicFiltersStats.getDynamicFiltersCompleted(), 2); + assertThat(dynamicFiltersStats.getTotalDynamicFilters()).isEqualTo(2L); + assertThat(dynamicFiltersStats.getLazyDynamicFilters()).isEqualTo(2L); + assertThat(dynamicFiltersStats.getReplicatedDynamicFilters()).isEqualTo(0L); + assertThat(dynamicFiltersStats.getDynamicFiltersCompleted()).isEqualTo(2); List domainStats = dynamicFiltersStats.getDynamicFilterDomainStats(); assertThat(domainStats).map(DynamicFilterDomainStats::getSimplifiedDomain) @@ -235,15 +231,15 @@ public void testJoinWithImplicitCoercion() OperatorStats probeStats = searchScanFilterAndProjectOperatorStats(result.getQueryId(), getQualifiedTableName("partitioned_lineitem_int")); // Probe-side is partially scanned - assertEquals(probeStats.getInputPositions(), 615L); + assertThat(probeStats.getInputPositions()).isEqualTo(615L); DynamicFiltersStats dynamicFiltersStats = getDynamicFilteringStats(result.getQueryId()); - assertEquals(dynamicFiltersStats.getTotalDynamicFilters(), 1L); - assertEquals(dynamicFiltersStats.getLazyDynamicFilters(), 1L); - assertEquals(dynamicFiltersStats.getReplicatedDynamicFilters(), 0L); - assertEquals(dynamicFiltersStats.getDynamicFiltersCompleted(), 1L); + assertThat(dynamicFiltersStats.getTotalDynamicFilters()).isEqualTo(1L); + assertThat(dynamicFiltersStats.getLazyDynamicFilters()).isEqualTo(1L); + assertThat(dynamicFiltersStats.getReplicatedDynamicFilters()).isEqualTo(0L); + assertThat(dynamicFiltersStats.getDynamicFiltersCompleted()).isEqualTo(1L); DynamicFilterDomainStats domainStats = getOnlyElement(dynamicFiltersStats.getDynamicFilterDomainStats()); - assertEquals(domainStats.getSimplifiedDomain(), singleValue(BIGINT, 1L).toString(getSession().toConnectorSession())); + assertThat(domainStats.getSimplifiedDomain()).isEqualTo(singleValue(BIGINT, 1L).toString(getSession().toConnectorSession())); } @Test @@ -258,13 +254,13 @@ public void testSemiJoinWithEmptyBuildSide() assertEqualsIgnoreOrder(result.getResult(), expected); DynamicFiltersStats dynamicFiltersStats = getDynamicFilteringStats(result.getQueryId()); - assertEquals(dynamicFiltersStats.getTotalDynamicFilters(), 1L); - assertEquals(dynamicFiltersStats.getLazyDynamicFilters(), 1L); - assertEquals(dynamicFiltersStats.getReplicatedDynamicFilters(), 0L); - assertEquals(dynamicFiltersStats.getDynamicFiltersCompleted(), 1L); + assertThat(dynamicFiltersStats.getTotalDynamicFilters()).isEqualTo(1L); + assertThat(dynamicFiltersStats.getLazyDynamicFilters()).isEqualTo(1L); + assertThat(dynamicFiltersStats.getReplicatedDynamicFilters()).isEqualTo(0L); + assertThat(dynamicFiltersStats.getDynamicFiltersCompleted()).isEqualTo(1L); DynamicFilterDomainStats domainStats = getOnlyElement(dynamicFiltersStats.getDynamicFilterDomainStats()); - assertEquals(domainStats.getSimplifiedDomain(), none(BIGINT).toString(getSession().toConnectorSession())); + assertThat(domainStats.getSimplifiedDomain()).isEqualTo(none(BIGINT).toString(getSession().toConnectorSession())); } @Test @@ -279,13 +275,13 @@ public void testSemiJoinWithSelectiveBuildSide() assertEqualsIgnoreOrder(result.getResult(), expected); DynamicFiltersStats dynamicFiltersStats = getDynamicFilteringStats(result.getQueryId()); - assertEquals(dynamicFiltersStats.getTotalDynamicFilters(), 1L); - assertEquals(dynamicFiltersStats.getLazyDynamicFilters(), 1L); - assertEquals(dynamicFiltersStats.getReplicatedDynamicFilters(), 0L); - assertEquals(dynamicFiltersStats.getDynamicFiltersCompleted(), 1L); + assertThat(dynamicFiltersStats.getTotalDynamicFilters()).isEqualTo(1L); + assertThat(dynamicFiltersStats.getLazyDynamicFilters()).isEqualTo(1L); + assertThat(dynamicFiltersStats.getReplicatedDynamicFilters()).isEqualTo(0L); + assertThat(dynamicFiltersStats.getDynamicFiltersCompleted()).isEqualTo(1L); DynamicFilterDomainStats domainStats = getOnlyElement(dynamicFiltersStats.getDynamicFilterDomainStats()); - assertEquals(domainStats.getSimplifiedDomain(), singleValue(BIGINT, 1L).toString(getSession().toConnectorSession())); + assertThat(domainStats.getSimplifiedDomain()).isEqualTo(singleValue(BIGINT, 1L).toString(getSession().toConnectorSession())); } @Test @@ -300,10 +296,10 @@ public void testSemiJoinWithNonSelectiveBuildSide() assertEqualsIgnoreOrder(result.getResult(), expected); DynamicFiltersStats dynamicFiltersStats = getDynamicFilteringStats(result.getQueryId()); - assertEquals(dynamicFiltersStats.getTotalDynamicFilters(), 1L); - assertEquals(dynamicFiltersStats.getLazyDynamicFilters(), 1L); - assertEquals(dynamicFiltersStats.getReplicatedDynamicFilters(), 0L); - assertEquals(dynamicFiltersStats.getDynamicFiltersCompleted(), 1L); + assertThat(dynamicFiltersStats.getTotalDynamicFilters()).isEqualTo(1L); + assertThat(dynamicFiltersStats.getLazyDynamicFilters()).isEqualTo(1L); + assertThat(dynamicFiltersStats.getReplicatedDynamicFilters()).isEqualTo(0L); + assertThat(dynamicFiltersStats.getDynamicFiltersCompleted()).isEqualTo(1L); DynamicFilterDomainStats domainStats = getOnlyElement(dynamicFiltersStats.getDynamicFilterDomainStats()); assertThat(domainStats.getSimplifiedDomain()) @@ -322,16 +318,14 @@ public void testSemiJoinLargeBuildSideRangeDynamicFiltering() assertEqualsIgnoreOrder(result.getResult(), expected); DynamicFiltersStats dynamicFiltersStats = getDynamicFilteringStats(result.getQueryId()); - assertEquals(dynamicFiltersStats.getTotalDynamicFilters(), 1L); - assertEquals(dynamicFiltersStats.getLazyDynamicFilters(), 1L); - assertEquals(dynamicFiltersStats.getReplicatedDynamicFilters(), 0L); - assertEquals(dynamicFiltersStats.getDynamicFiltersCompleted(), 1L); + assertThat(dynamicFiltersStats.getTotalDynamicFilters()).isEqualTo(1L); + assertThat(dynamicFiltersStats.getLazyDynamicFilters()).isEqualTo(1L); + assertThat(dynamicFiltersStats.getReplicatedDynamicFilters()).isEqualTo(0L); + assertThat(dynamicFiltersStats.getDynamicFiltersCompleted()).isEqualTo(1L); DynamicFilterDomainStats domainStats = getOnlyElement(dynamicFiltersStats.getDynamicFilterDomainStats()); - assertEquals( - domainStats.getSimplifiedDomain(), - Domain.create(ValueSet.ofRanges(range(BIGINT, 1L, true, 60000L, true)), false) - .toString(getSession().toConnectorSession())); + assertThat(domainStats.getSimplifiedDomain()).isEqualTo(Domain.create(ValueSet.ofRanges(range(BIGINT, 1L, true, 60000L, true)), false) + .toString(getSession().toConnectorSession())); } @Test @@ -346,13 +340,13 @@ public void testRightJoinWithEmptyBuildSide() assertEqualsIgnoreOrder(result.getResult(), expected); DynamicFiltersStats dynamicFiltersStats = getDynamicFilteringStats(result.getQueryId()); - assertEquals(dynamicFiltersStats.getTotalDynamicFilters(), 1L); - assertEquals(dynamicFiltersStats.getLazyDynamicFilters(), 1L); - assertEquals(dynamicFiltersStats.getReplicatedDynamicFilters(), 0L); - assertEquals(dynamicFiltersStats.getDynamicFiltersCompleted(), 1L); + assertThat(dynamicFiltersStats.getTotalDynamicFilters()).isEqualTo(1L); + assertThat(dynamicFiltersStats.getLazyDynamicFilters()).isEqualTo(1L); + assertThat(dynamicFiltersStats.getReplicatedDynamicFilters()).isEqualTo(0L); + assertThat(dynamicFiltersStats.getDynamicFiltersCompleted()).isEqualTo(1L); DynamicFilterDomainStats domainStats = getOnlyElement(dynamicFiltersStats.getDynamicFilterDomainStats()); - assertEquals(domainStats.getSimplifiedDomain(), none(BIGINT).toString(getSession().toConnectorSession())); + assertThat(domainStats.getSimplifiedDomain()).isEqualTo(none(BIGINT).toString(getSession().toConnectorSession())); } @Test @@ -367,13 +361,13 @@ public void testRightJoinWithSelectiveBuildSide() assertEqualsIgnoreOrder(result.getResult(), expected); DynamicFiltersStats dynamicFiltersStats = getDynamicFilteringStats(result.getQueryId()); - assertEquals(dynamicFiltersStats.getTotalDynamicFilters(), 1L); - assertEquals(dynamicFiltersStats.getLazyDynamicFilters(), 1L); - assertEquals(dynamicFiltersStats.getReplicatedDynamicFilters(), 0L); - assertEquals(dynamicFiltersStats.getDynamicFiltersCompleted(), 1L); + assertThat(dynamicFiltersStats.getTotalDynamicFilters()).isEqualTo(1L); + assertThat(dynamicFiltersStats.getLazyDynamicFilters()).isEqualTo(1L); + assertThat(dynamicFiltersStats.getReplicatedDynamicFilters()).isEqualTo(0L); + assertThat(dynamicFiltersStats.getDynamicFiltersCompleted()).isEqualTo(1L); DynamicFilterDomainStats domainStats = getOnlyElement(dynamicFiltersStats.getDynamicFilterDomainStats()); - assertEquals(domainStats.getSimplifiedDomain(), singleValue(BIGINT, 1L).toString(getSession().toConnectorSession())); + assertThat(domainStats.getSimplifiedDomain()).isEqualTo(singleValue(BIGINT, 1L).toString(getSession().toConnectorSession())); } @Test @@ -388,10 +382,10 @@ public void testRightJoinWithNonSelectiveBuildSide() assertEqualsIgnoreOrder(result.getResult(), expected); DynamicFiltersStats dynamicFiltersStats = getDynamicFilteringStats(result.getQueryId()); - assertEquals(dynamicFiltersStats.getTotalDynamicFilters(), 1L); - assertEquals(dynamicFiltersStats.getLazyDynamicFilters(), 1L); - assertEquals(dynamicFiltersStats.getReplicatedDynamicFilters(), 0L); - assertEquals(dynamicFiltersStats.getDynamicFiltersCompleted(), 1L); + assertThat(dynamicFiltersStats.getTotalDynamicFilters()).isEqualTo(1L); + assertThat(dynamicFiltersStats.getLazyDynamicFilters()).isEqualTo(1L); + assertThat(dynamicFiltersStats.getReplicatedDynamicFilters()).isEqualTo(0L); + assertThat(dynamicFiltersStats.getDynamicFiltersCompleted()).isEqualTo(1L); DynamicFilterDomainStats domainStats = getOnlyElement(dynamicFiltersStats.getDynamicFilterDomainStats()); assertThat(domainStats.getSimplifiedDomain()) diff --git a/testing/trino-testing/src/main/java/io/trino/testing/BaseFailureRecoveryTest.java b/testing/trino-testing/src/main/java/io/trino/testing/BaseFailureRecoveryTest.java index 8be6eeb95e4f..fff6eae42576 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/BaseFailureRecoveryTest.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/BaseFailureRecoveryTest.java @@ -71,7 +71,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.data.Percentage.withPercentage; -import static org.testng.Assert.assertEquals; public abstract class BaseFailureRecoveryTest extends AbstractTestQueryFramework @@ -620,22 +619,30 @@ public FailureRecoveryAssert finishesSuccessfully(Consumer queryAsserti boolean isUpdate = expectedQueryResult.getUpdateCount().isPresent(); boolean isExplain = query.trim().toUpperCase(ENGLISH).startsWith("EXPLAIN"); if (isAnalyze) { - assertEquals(actualQueryResult.getUpdateCount(), expectedQueryResult.getUpdateCount()); + assertThat(actualQueryResult.getUpdateCount()).isEqualTo(expectedQueryResult.getUpdateCount()); assertThat(expected.getUpdatedTableStatistics()).isPresent(); assertThat(actual.getUpdatedTableStatistics()).isPresent(); MaterializedResult expectedUpdatedTableStatisticsResult = expected.getUpdatedTableStatistics().get(); MaterializedResult actualUpdatedTableStatisticsResult = actual.getUpdatedTableStatistics().get(); - assertEquals(actualUpdatedTableStatisticsResult.getTypes(), expectedUpdatedTableStatisticsResult.getTypes(), "Column types"); - assertEquals(actualUpdatedTableStatisticsResult.getColumnNames(), expectedUpdatedTableStatisticsResult.getColumnNames(), "Column names"); + assertThat(actualUpdatedTableStatisticsResult.getTypes()) + .describedAs("Column types") + .isEqualTo(expectedUpdatedTableStatisticsResult.getTypes()); + assertThat(actualUpdatedTableStatisticsResult.getColumnNames()) + .describedAs("Column names") + .isEqualTo(expectedUpdatedTableStatisticsResult.getColumnNames()); Map expectedUpdatedTableStatistics = expectedUpdatedTableStatisticsResult.getMaterializedRows().stream() .collect(toMap(row -> (String) row.getField(0), identity())); Map actualUpdatedTableStatistics = actualUpdatedTableStatisticsResult.getMaterializedRows().stream() .collect(toMap(row -> (String) row.getField(0), identity())); - assertEquals(actualUpdatedTableStatistics.keySet(), expectedUpdatedTableStatistics.keySet(), "Table columns"); + assertThat(actualUpdatedTableStatistics.keySet()) + .describedAs("Table columns") + .isEqualTo(expectedUpdatedTableStatistics.keySet()); expectedUpdatedTableStatistics.forEach((key, expectedRow) -> { MaterializedRow actualRow = actualUpdatedTableStatistics.get(key); - assertEquals(actualRow.getFieldCount(), expectedRow.getFieldCount(), "Unexpected layout of stats"); + assertThat(actualRow.getFieldCount()) + .describedAs("Unexpected layout of stats") + .isEqualTo(expectedRow.getFieldCount()); for (int statsColumnIndex = 0; statsColumnIndex < expectedRow.getFieldCount(); statsColumnIndex++) { String statsColumnName = actualUpdatedTableStatisticsResult.getColumnNames().get(statsColumnIndex); String testedFieldDescription = "Field %d '%s' in %s".formatted(statsColumnIndex, statsColumnName, actualRow); @@ -661,7 +668,7 @@ public FailureRecoveryAssert finishesSuccessfully(Consumer queryAsserti }); } else if (isUpdate) { - assertEquals(actualQueryResult.getUpdateCount(), expectedQueryResult.getUpdateCount()); + assertThat(actualQueryResult.getUpdateCount()).isEqualTo(expectedQueryResult.getUpdateCount()); assertThat(expected.getUpdatedTableContent()).isPresent(); assertThat(actual.getUpdatedTableContent()).isPresent(); MaterializedResult expectedUpdatedTableContent = expected.getUpdatedTableContent().get(); @@ -669,7 +676,7 @@ else if (isUpdate) { assertEqualsIgnoreOrder(actualUpdatedTableContent, expectedUpdatedTableContent, "For query: \n " + query); } else if (isExplain) { - assertEquals(actualQueryResult.getRowCount(), expectedQueryResult.getRowCount()); + assertThat(actualQueryResult.getRowCount()).isEqualTo(expectedQueryResult.getRowCount()); } else { assertEqualsIgnoreOrder(actualQueryResult, expectedQueryResult, "For query: \n " + query); diff --git a/testing/trino-testing/src/main/java/io/trino/testing/MultisetAssertions.java b/testing/trino-testing/src/main/java/io/trino/testing/MultisetAssertions.java index 1d3f9fbf1607..9505068907bb 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/MultisetAssertions.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/MultisetAssertions.java @@ -22,7 +22,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static java.lang.String.format; import static java.lang.String.join; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Fail.fail; public final class MultisetAssertions { diff --git a/testing/trino-testing/src/main/java/io/trino/testing/PlanDeterminismChecker.java b/testing/trino-testing/src/main/java/io/trino/testing/PlanDeterminismChecker.java index 9d859db9458c..4aa99eea5da0 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/PlanDeterminismChecker.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/PlanDeterminismChecker.java @@ -22,7 +22,7 @@ import static io.trino.execution.querystats.PlanOptimizersStatsCollector.createPlanOptimizersStatsCollector; import static io.trino.execution.warnings.WarningCollector.NOOP; import static io.trino.sql.planner.LogicalPlanner.Stage.OPTIMIZED_AND_VALIDATED; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class PlanDeterminismChecker { @@ -52,7 +52,7 @@ public void checkPlanIsDeterministic(Session session, String sql) String previous = planEquivalenceFunction.apply(getPlanText(session, sql)); for (int attempt = 1; attempt < MINIMUM_SUBSEQUENT_SAME_PLANS; attempt++) { String current = planEquivalenceFunction.apply(getPlanText(session, sql)); - assertEquals(previous, current); + assertThat(previous).isEqualTo(current); } } diff --git a/testing/trino-testing/src/main/java/io/trino/testing/QueryAssertions.java b/testing/trino-testing/src/main/java/io/trino/testing/QueryAssertions.java index 57ca5f2f72f2..4d4a6b546eae 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/QueryAssertions.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/QueryAssertions.java @@ -45,9 +45,7 @@ import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Fail.fail; public final class QueryAssertions { @@ -93,7 +91,9 @@ public static void assertUpdate(QueryRunner queryRunner, Session session, @Langu if (count.isEmpty()) { fail("expected no update count, but got " + results.getUpdateCount().getAsLong()); } - assertEquals(results.getUpdateCount().getAsLong(), count.getAsLong(), "update count"); + assertThat(results.getUpdateCount().getAsLong()) + .describedAs("update count") + .isEqualTo(count.getAsLong()); } else if (count.isPresent()) { fail("update count is not present"); @@ -138,7 +138,9 @@ private static void assertDistributedUpdate(DistributedQueryRunner distributedQu if (count.isEmpty()) { fail("expected no update count, but got " + results.getUpdateCount().getAsLong() + " for query " + queryId); } - assertEquals(results.getUpdateCount().getAsLong(), count.getAsLong(), "update count for query " + queryId); + assertThat(results.getUpdateCount().getAsLong()) + .describedAs("update count for query " + queryId) + .isEqualTo(count.getAsLong()); } else if (count.isPresent()) { fail("update count is not present for query " + queryId); @@ -243,16 +245,26 @@ private static void assertQuery( if (actualResults.getUpdateCount().isEmpty()) { fail("update count not present for query: \n" + actual); } - assertEquals(actualRows.size(), 1, "For query: \n " + actual + "\n:"); - assertEquals(expectedRows.size(), 1, "For query: \n " + actual + "\n:"); + assertThat(actualRows.size()) + .describedAs("For query: \n " + actual + "\n:") + .isEqualTo(1); + assertThat(expectedRows.size()) + .describedAs("For query: \n " + actual + "\n:") + .isEqualTo(1); MaterializedRow row = expectedRows.get(0); - assertEquals(row.getFieldCount(), 1, "For query: \n " + actual + "\n:"); - assertEquals(row.getField(0), actualResults.getUpdateCount().getAsLong(), "For query: \n " + actual + "\n:"); + assertThat(row.getFieldCount()) + .describedAs("For query: \n " + actual + "\n:") + .isEqualTo(1); + assertThat(row.getField(0)) + .describedAs("For query: \n " + actual + "\n:") + .isEqualTo(actualResults.getUpdateCount().getAsLong()); } if (ensureOrdering) { if (!actualRows.equals(expectedRows)) { - assertEquals(actualRows, expectedRows, "For query: \n " + actual + "\n:"); + assertThat(actualRows) + .describedAs("For query: \n " + actual + "\n:") + .isEqualTo(expectedRows); } } else { @@ -332,16 +344,26 @@ private static void assertDistributedQuery( if (actualResults.getUpdateCount().isEmpty()) { fail("update count not present for query " + queryId + ": \n" + actual); } - assertEquals(actualRows.size(), 1, "For query " + queryId + ": \n " + actual + "\n:"); - assertEquals(expectedRows.size(), 1, "For query " + queryId + ": \n " + actual + "\n:"); + assertThat(actualRows.size()) + .describedAs("For query " + queryId + ": \n " + actual + "\n:") + .isEqualTo(1); + assertThat(expectedRows.size()) + .describedAs("For query " + queryId + ": \n " + actual + "\n:") + .isEqualTo(1); MaterializedRow row = expectedRows.get(0); - assertEquals(row.getFieldCount(), 1, "For query " + queryId + ": \n " + actual + "\n:"); - assertEquals(row.getField(0), actualResults.getUpdateCount().getAsLong(), "For query " + queryId + ": \n " + actual + "\n:"); + assertThat(row.getFieldCount()) + .describedAs("For query " + queryId + ": \n " + actual + "\n:") + .isEqualTo(1); + assertThat(row.getField(0)) + .describedAs("For query " + queryId + ": \n " + actual + "\n:") + .isEqualTo(actualResults.getUpdateCount().getAsLong()); } if (ensureOrdering) { if (!actualRows.equals(expectedRows)) { - assertEquals(actualRows, expectedRows, "For query " + queryId + ": \n " + actual + "\n:"); + assertThat(actualRows) + .describedAs("For query " + queryId + ": \n " + actual + "\n:") + .isEqualTo(expectedRows); } } else { @@ -370,8 +392,12 @@ public static void assertEqualsIgnoreOrder(Iterable actual, Iterable expec public static void assertEqualsIgnoreOrder(Iterable actual, Iterable expected, String message) { - assertNotNull(actual, "actual is null"); - assertNotNull(expected, "expected is null"); + assertThat(actual) + .describedAs("actual is null") + .isNotNull(); + assertThat(expected) + .describedAs("expected is null") + .isNotNull(); ImmutableMultiset actualSet = ImmutableMultiset.copyOf(actual); ImmutableMultiset expectedSet = ImmutableMultiset.copyOf(expected); @@ -465,8 +491,8 @@ protected static void assertQueryReturnsEmptyResult(QueryRunner queryRunner, Ses else { results = queryRunner.execute(session, sql).toTestTypes(); } - assertNotNull(results); - assertEquals(results.getRowCount(), 0); + assertThat(results).isNotNull(); + assertThat(results.getRowCount()).isEqualTo(0); } catch (RuntimeException ex) { if (queryId == null) { diff --git a/testing/trino-testing/src/main/java/io/trino/testing/datatype/DataTypeTest.java b/testing/trino-testing/src/main/java/io/trino/testing/datatype/DataTypeTest.java index c52182095758..caf2cf7599b2 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/datatype/DataTypeTest.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/datatype/DataTypeTest.java @@ -31,7 +31,6 @@ import static java.util.Collections.unmodifiableList; import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class DataTypeTest { @@ -140,7 +139,9 @@ private void checkResults(List expectedTypes, List expectedResults List actualResults = getOnlyElement(materializedRows).getFields(); verify(actualResults.size() == expectedResults.size(), "lists don't have the same size"); for (int i = 0; i < expectedResults.size(); i++) { - assertEquals(actualResults.get(i), expectedResults.get(i), "Element " + i); + assertThat(actualResults.get(i)) + .describedAs("Element " + i) + .isEqualTo(expectedResults.get(i)); } } diff --git a/testing/trino-testing/src/main/java/io/trino/testing/statistics/StatisticsAssertion.java b/testing/trino-testing/src/main/java/io/trino/testing/statistics/StatisticsAssertion.java index 42c590dba309..769bec3cf407 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/statistics/StatisticsAssertion.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/statistics/StatisticsAssertion.java @@ -32,7 +32,7 @@ import static io.trino.testing.statistics.Metrics.lowValue; import static io.trino.testing.statistics.Metrics.nullsFraction; import static java.util.Objects.requireNonNull; -import static org.testng.Assert.assertSame; +import static org.assertj.core.api.Assertions.assertThat; public class StatisticsAssertion implements AutoCloseable @@ -129,7 +129,9 @@ void run(@Language("SQL") String query, QueryRunner runner) for (int i = 0; i < checks.size(); i++) { MetricsCheck check = checks.get(i); MetricComparison metricComparison = metricComparisons.get(i); - assertSame(metricComparison.result(check.strategy), check.expectedComparisonResult, "Metric doesn't match: " + metricComparison); + assertThat(metricComparison.result(check.strategy)) + .describedAs("Metric doesn't match: " + metricComparison) + .isEqualTo(check.expectedComparisonResult); } } } diff --git a/testing/trino-testing/src/test/java/io/trino/testing/TestH2QueryRunner.java b/testing/trino-testing/src/test/java/io/trino/testing/TestH2QueryRunner.java index 038ecfc88f39..9849501177e0 100644 --- a/testing/trino-testing/src/test/java/io/trino/testing/TestH2QueryRunner.java +++ b/testing/trino-testing/src/test/java/io/trino/testing/TestH2QueryRunner.java @@ -23,7 +23,7 @@ import static com.google.common.base.Preconditions.checkState; import static io.trino.SessionTestUtils.TEST_SESSION; import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestH2QueryRunner { @@ -35,13 +35,13 @@ public void testDateToTimestampCoercion() // ordinary date MaterializedResult rows = h2QueryRunner.execute(TEST_SESSION, "SELECT DATE '2018-01-13'", ImmutableList.of(TIMESTAMP_MILLIS)); - assertEquals(rows.getOnlyValue(), LocalDate.of(2018, 1, 13).atStartOfDay()); + assertThat(rows.getOnlyValue()).isEqualTo(LocalDate.of(2018, 1, 13).atStartOfDay()); // date, which midnight was skipped in JVM zone LocalDate forwardOffsetChangeAtMidnightInJvmZone = LocalDate.of(1970, 1, 1); checkState(ZoneId.systemDefault().getRules().getValidOffsets(forwardOffsetChangeAtMidnightInJvmZone.atStartOfDay()).size() == 0, "This test assumes certain JVM time zone"); rows = h2QueryRunner.execute(TEST_SESSION, DateTimeFormatter.ofPattern("'SELECT DATE '''uuuu-MM-dd''").format(forwardOffsetChangeAtMidnightInJvmZone), ImmutableList.of(TIMESTAMP_MILLIS)); - assertEquals(rows.getOnlyValue(), forwardOffsetChangeAtMidnightInJvmZone.atStartOfDay()); + assertThat(rows.getOnlyValue()).isEqualTo(forwardOffsetChangeAtMidnightInJvmZone.atStartOfDay()); } } } diff --git a/testing/trino-testing/src/test/java/io/trino/testing/TestTestingTrinoClient.java b/testing/trino-testing/src/test/java/io/trino/testing/TestTestingTrinoClient.java index b1c28bdf3fbb..ac11e8b149aa 100644 --- a/testing/trino-testing/src/test/java/io/trino/testing/TestTestingTrinoClient.java +++ b/testing/trino-testing/src/test/java/io/trino/testing/TestTestingTrinoClient.java @@ -37,10 +37,10 @@ import java.nio.file.Path; import java.security.Principal; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -100,7 +100,7 @@ public void testAuthenticationWithForwarding() try (TestingTrinoClient client = new TestingTrinoClient(server, session, httpClient)) { MaterializedResult result = client.execute("SELECT 123").getResult(); - assertEquals(result.getOnlyValue(), 123); + assertThat(result.getOnlyValue()).isEqualTo(123); } } diff --git a/testing/trino-testing/src/test/java/io/trino/testing/datatype/TestDataType.java b/testing/trino-testing/src/test/java/io/trino/testing/datatype/TestDataType.java index cea9e9e6048b..838ac432575b 100644 --- a/testing/trino-testing/src/test/java/io/trino/testing/datatype/TestDataType.java +++ b/testing/trino-testing/src/test/java/io/trino/testing/datatype/TestDataType.java @@ -18,15 +18,15 @@ import java.time.LocalTime; import static io.trino.testing.datatype.DataType.timeDataType; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestDataType { @Test public void testTimeDataType() { - assertEquals(timeDataType(0).toLiteral(LocalTime.of(23, 59, 59, 0)), "TIME '23:59:59'"); - assertEquals(timeDataType(3).toLiteral(LocalTime.of(23, 59, 59, 999_000_000)), "TIME '23:59:59.999'"); - assertEquals(timeDataType(6).toLiteral(LocalTime.of(23, 59, 59, 999_999_000)), "TIME '23:59:59.999999'"); + assertThat(timeDataType(0).toLiteral(LocalTime.of(23, 59, 59, 0))).isEqualTo("TIME '23:59:59'"); + assertThat(timeDataType(3).toLiteral(LocalTime.of(23, 59, 59, 999_000_000))).isEqualTo("TIME '23:59:59.999'"); + assertThat(timeDataType(6).toLiteral(LocalTime.of(23, 59, 59, 999_999_000))).isEqualTo("TIME '23:59:59.999999'"); } } diff --git a/testing/trino-tests/src/test/java/io/trino/connector/system/runtime/TestKillQuery.java b/testing/trino-tests/src/test/java/io/trino/connector/system/runtime/TestKillQuery.java index 0bd75bd08db0..096a98f95f83 100644 --- a/testing/trino-tests/src/test/java/io/trino/connector/system/runtime/TestKillQuery.java +++ b/testing/trino-tests/src/test/java/io/trino/connector/system/runtime/TestKillQuery.java @@ -42,9 +42,9 @@ import static io.trino.testing.TestingSession.testSessionBuilder; import static java.lang.String.format; import static java.util.UUID.randomUUID; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertFalse; @TestInstance(PER_CLASS) public class TestKillQuery @@ -101,7 +101,7 @@ private void killQuery(Function sql, String expectedKilledMessag } String queryId = queryIdValue.get().toString(); - assertFalse(queryFuture.isDone()); + assertThat(queryFuture.isDone()).isFalse(); getQueryRunner().getAccessControl().deny(privilege("query", KILL_QUERY)); try { diff --git a/testing/trino-tests/src/test/java/io/trino/connector/system/runtime/TestSystemRuntimeConnector.java b/testing/trino-tests/src/test/java/io/trino/connector/system/runtime/TestSystemRuntimeConnector.java index cc69d5b4b78c..a536672a3617 100644 --- a/testing/trino-tests/src/test/java/io/trino/connector/system/runtime/TestSystemRuntimeConnector.java +++ b/testing/trino-tests/src/test/java/io/trino/connector/system/runtime/TestSystemRuntimeConnector.java @@ -52,8 +52,6 @@ import static java.lang.String.format; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @Test(singleThreaded = true) public class TestSystemRuntimeConnector @@ -224,8 +222,8 @@ public void testQueryDuringAnalysisIsCaptured() format("SELECT state FROM system.runtime.queries WHERE query LIKE '%%%s%%' AND query NOT LIKE '%%system.runtime.queries%%'", testQueryId), "VALUES 'WAITING_FOR_RESOURCES'", new Duration(10, SECONDS)); - assertFalse(metadataFuture.isDone()); - assertFalse(queryFuture.isDone()); + assertThat(metadataFuture.isDone()).isFalse(); + assertThat(queryFuture.isDone()).isFalse(); metadataFuture.set(ImmutableList.of(new ColumnMetadata("a", BIGINT))); @@ -235,7 +233,7 @@ public void testQueryDuringAnalysisIsCaptured() "VALUES 'FINISHED'", new Duration(10, SECONDS)); // Client should receive query result immediately afterwards - assertEventually(new Duration(5, SECONDS), () -> assertTrue(queryFuture.isDone())); + assertEventually(new Duration(5, SECONDS), () -> assertThat(queryFuture.isDone()).isTrue()); } @Test(timeOut = 60_000) @@ -270,15 +268,15 @@ public void testQueryKillingDuringAnalysis() Optional queryId = computeActual(format("SELECT query_id FROM system.runtime.queries WHERE query LIKE '%%%s%%' AND query NOT LIKE '%%system.runtime.queries%%'", testQueryId)) .getOnlyColumn() .collect(toOptional()); - assertFalse(metadataFuture.isDone()); - assertFalse(queryFuture.isDone()); - assertTrue(queryId.isPresent()); + assertThat(metadataFuture.isDone()).isFalse(); + assertThat(queryFuture.isDone()).isFalse(); + assertThat(queryId.isPresent()).isTrue(); getQueryRunner().execute(format("CALL system.runtime.kill_query('%s', 'because')", queryId.get())); // Cancellation should happen within kill_query, but it still needs to be propagated to the thread performing analysis. - assertEventually(new Duration(5, SECONDS), () -> assertTrue(metadataFuture.isCancelled())); + assertEventually(new Duration(5, SECONDS), () -> assertThat(metadataFuture.isCancelled()).isTrue()); // Client should receive query result (failure) immediately afterwards - assertEventually(new Duration(5, SECONDS), () -> assertTrue(queryFuture.isDone())); + assertEventually(new Duration(5, SECONDS), () -> assertThat(queryFuture.isDone()).isTrue()); } @Test diff --git a/testing/trino-tests/src/test/java/io/trino/execution/AbstractTestCoordinatorDynamicFiltering.java b/testing/trino-tests/src/test/java/io/trino/execution/AbstractTestCoordinatorDynamicFiltering.java index ee313e8f525a..3ceec0e49c32 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/AbstractTestCoordinatorDynamicFiltering.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/AbstractTestCoordinatorDynamicFiltering.java @@ -84,9 +84,6 @@ import static java.util.Objects.requireNonNull; import static java.util.concurrent.CompletableFuture.completedFuture; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; public abstract class AbstractTestCoordinatorDynamicFiltering extends AbstractTestQueryFramework @@ -497,9 +494,13 @@ public ConnectorSplitSource getSplits( if (!isTaskRetryMode) { // In task retry mode, dynamic filter collection is done outside the join stage, // so it's not necessary that dynamicFilter will be blocked initially. - assertFalse(dynamicFilter.isBlocked().isDone(), "Dynamic filter should be initially blocked"); + assertThat(dynamicFilter.isBlocked().isDone()) + .describedAs("Dynamic filter should be initially blocked") + .isFalse(); } - assertEquals(dynamicFilter.getColumnsCovered(), expectedDynamicFilterColumnsCovered, "columns covered"); + assertThat(dynamicFilter.getColumnsCovered()) + .describedAs("columns covered") + .isEqualTo(expectedDynamicFilterColumnsCovered); AtomicBoolean splitProduced = new AtomicBoolean(); return new ConnectorSplitSource() @@ -528,13 +529,15 @@ public void close() @Override public boolean isFinished() { - assertEquals(dynamicFilter.getColumnsCovered(), expectedDynamicFilterColumnsCovered, "columns covered"); + assertThat(dynamicFilter.getColumnsCovered()) + .describedAs("columns covered") + .isEqualTo(expectedDynamicFilterColumnsCovered); if (!dynamicFilter.isComplete() || !splitProduced.get()) { return false; } - assertTrue(dynamicFilter.isBlocked().isDone()); + assertThat(dynamicFilter.isBlocked().isDone()).isTrue(); expectedCoordinatorDynamicFilterAssertion.accept(dynamicFilter.getCurrentPredicate()); return true; @@ -558,7 +561,9 @@ public ConnectorPageSource createPageSource( List columns, DynamicFilter dynamicFilter) { - assertEquals(dynamicFilter.getColumnsCovered(), expectedDynamicFilterColumnsCovered, "columns covered"); + assertThat(dynamicFilter.getColumnsCovered()) + .describedAs("columns covered") + .isEqualTo(expectedDynamicFilterColumnsCovered); return new EmptyPageSource() { @@ -571,14 +576,16 @@ public CompletableFuture isBlocked() @Override public boolean isFinished() { - assertEquals(dynamicFilter.getColumnsCovered(), expectedDynamicFilterColumnsCovered, "columns covered"); + assertThat(dynamicFilter.getColumnsCovered()) + .describedAs("columns covered") + .isEqualTo(expectedDynamicFilterColumnsCovered); if (!dynamicFilter.isComplete()) { return false; } // ConnectorPageSource is blocked until the dynamicFilter is complete - assertTrue(dynamicFilter.isBlocked().isDone()); + assertThat(dynamicFilter.isBlocked().isDone()).isTrue(); expectedTableScanDynamicFilterAssertion.accept(dynamicFilter.getCurrentPredicate()); return true; diff --git a/testing/trino-tests/src/test/java/io/trino/execution/EventsAwaitingQueries.java b/testing/trino-tests/src/test/java/io/trino/execution/EventsAwaitingQueries.java index c3463d1106c0..6b7ca2609f30 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/EventsAwaitingQueries.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/EventsAwaitingQueries.java @@ -29,7 +29,7 @@ import static java.lang.String.format; import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Fail.fail; class EventsAwaitingQueries { diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestBeginQuery.java b/testing/trino-tests/src/test/java/io/trino/execution/TestBeginQuery.java index 8ddc1b5df3ad..05d982ce03bd 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestBeginQuery.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestBeginQuery.java @@ -49,7 +49,7 @@ import static io.trino.testing.TestingSession.testSessionBuilder; import static java.util.Objects.requireNonNull; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestBeginQuery extends AbstractTestQueryFramework @@ -124,8 +124,8 @@ private void assertBeginQuery(String query) { metadata.resetCounters(); computeActual(query); - assertEquals(metadata.begin.get(), 1); - assertEquals(metadata.end.get(), 1); + assertThat(metadata.begin.get()).isEqualTo(1); + assertThat(metadata.end.get()).isEqualTo(1); metadata.resetCounters(); } diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestCompletedEventWarnings.java b/testing/trino-tests/src/test/java/io/trino/execution/TestCompletedEventWarnings.java index bd4d2cd300ba..003675efacbb 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestCompletedEventWarnings.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestCompletedEventWarnings.java @@ -37,9 +37,9 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static io.trino.SessionTestUtils.TEST_SESSION; +import static org.assertj.core.api.Fail.fail; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.fail; @TestInstance(PER_CLASS) @Execution(CONCURRENT) diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestDeprecatedFunctionWarning.java b/testing/trino-tests/src/test/java/io/trino/execution/TestDeprecatedFunctionWarning.java index 83c91e612eed..84d6d304dee2 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestDeprecatedFunctionWarning.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestDeprecatedFunctionWarning.java @@ -47,9 +47,9 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet; import static io.trino.spi.type.DoubleType.DOUBLE; import static io.trino.testing.TestingSession.testSessionBuilder; +import static org.assertj.core.api.Fail.fail; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.fail; @TestInstance(PER_CLASS) @Execution(CONCURRENT) diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java b/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java index 7f3c05ffcff7..76809bb35f9d 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java @@ -81,9 +81,6 @@ import static java.lang.String.format; import static java.util.UUID.randomUUID; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; @Test(singleThreaded = true) public class TestEventListenerBasic @@ -337,11 +334,11 @@ private void assertFailedQuery(Session session, @Language("SQL") String sql, Str QueryEvents queryEvents = queries.runQueryAndWaitForEvents(sql, session, Optional.of(expectedFailure)).getQueryEvents(); QueryCompletedEvent queryCompletedEvent = queryEvents.getQueryCompletedEvent(); - assertEquals(queryCompletedEvent.getMetadata().getQuery(), sql); + assertThat(queryCompletedEvent.getMetadata().getQuery()).isEqualTo(sql); QueryFailureInfo failureInfo = queryCompletedEvent.getFailureInfo() .orElseThrow(() -> new AssertionError("Expected query event to be failed")); - assertEquals(expectedFailure, failureInfo.getFailureMessage().orElse(null)); + assertThat(expectedFailure).isEqualTo(failureInfo.getFailureMessage().orElse(null)); } @Test @@ -353,26 +350,26 @@ public void testReferencedTablesAndRoutines() QueryCompletedEvent event = queryEvents.getQueryCompletedEvent(); List tables = event.getMetadata().getTables(); - assertEquals(tables.size(), 1); + assertThat(tables.size()).isEqualTo(1); TableInfo table = tables.get(0); - assertEquals(table.getCatalog(), "tpch"); - assertEquals(table.getSchema(), "tiny"); - assertEquals(table.getTable(), "lineitem"); - assertEquals(table.getAuthorization(), "user"); - assertTrue(table.getFilters().isEmpty()); - assertEquals(table.getColumns().size(), 1); + assertThat(table.getCatalog()).isEqualTo("tpch"); + assertThat(table.getSchema()).isEqualTo("tiny"); + assertThat(table.getTable()).isEqualTo("lineitem"); + assertThat(table.getAuthorization()).isEqualTo("user"); + assertThat(table.getFilters().isEmpty()).isTrue(); + assertThat(table.getColumns().size()).isEqualTo(1); ColumnInfo column = table.getColumns().get(0); - assertEquals(column.getColumn(), "linenumber"); - assertTrue(column.getMask().isEmpty()); + assertThat(column.getColumn()).isEqualTo("linenumber"); + assertThat(column.getMask().isEmpty()).isTrue(); List routines = event.getMetadata().getRoutines(); - assertEquals(tables.size(), 1); + assertThat(tables.size()).isEqualTo(1); RoutineInfo routine = routines.get(0); - assertEquals(routine.getRoutine(), "sum"); - assertEquals(routine.getAuthorization(), "user"); + assertThat(routine.getRoutine()).isEqualTo("sum"); + assertThat(routine.getAuthorization()).isEqualTo("user"); } @Test @@ -611,33 +608,27 @@ public void testReferencedColumns() QueryCompletedEvent event = queryEvents.getQueryCompletedEvent(); TableInfo table = getOnlyElement(event.getMetadata().getTables()); - assertEquals( - table.getColumns().stream() - .map(ColumnInfo::getColumn) - .collect(toImmutableSet()), - ImmutableSet.of("name", "nationkey")); + assertThat(table.getColumns().stream() + .map(ColumnInfo::getColumn) + .collect(toImmutableSet())).isEqualTo(ImmutableSet.of("name", "nationkey")); // assert that ColumnInfos for referenced columns are present when the table was aliased queryEvents = runQueryAndWaitForEvents("SELECT name, nationkey FROM nation n").getQueryEvents(); event = queryEvents.getQueryCompletedEvent(); table = getOnlyElement(event.getMetadata().getTables()); - assertEquals( - table.getColumns().stream() - .map(ColumnInfo::getColumn) - .collect(toImmutableSet()), - ImmutableSet.of("name", "nationkey")); + assertThat(table.getColumns().stream() + .map(ColumnInfo::getColumn) + .collect(toImmutableSet())).isEqualTo(ImmutableSet.of("name", "nationkey")); // assert that ColumnInfos for referenced columns are present when the table was aliased and its columns were aliased queryEvents = runQueryAndWaitForEvents("SELECT a, b FROM nation n(a, b, c, d)").getQueryEvents(); event = queryEvents.getQueryCompletedEvent(); table = getOnlyElement(event.getMetadata().getTables()); - assertEquals( - table.getColumns().stream() - .map(ColumnInfo::getColumn) - .collect(toImmutableSet()), - ImmutableSet.of("name", "nationkey")); + assertThat(table.getColumns().stream() + .map(ColumnInfo::getColumn) + .collect(toImmutableSet())).isEqualTo(ImmutableSet.of("name", "nationkey")); } @Test @@ -651,22 +642,22 @@ public void testPrepareAndExecute() QueryEvents queryEvents = runQueryAndWaitForEvents(prepareQuery).getQueryEvents(); QueryCreatedEvent queryCreatedEvent = queryEvents.getQueryCreatedEvent(); - assertEquals(queryCreatedEvent.getContext().getServerVersion(), "testversion"); - assertEquals(queryCreatedEvent.getContext().getServerAddress(), "127.0.0.1"); - assertEquals(queryCreatedEvent.getContext().getEnvironment(), "testing"); - assertEquals(queryCreatedEvent.getContext().getClientInfo().get(), "{\"clientVersion\":\"testVersion\"}"); - assertEquals(queryCreatedEvent.getMetadata().getQuery(), prepareQuery); - assertFalse(queryCreatedEvent.getMetadata().getPreparedQuery().isPresent()); + assertThat(queryCreatedEvent.getContext().getServerVersion()).isEqualTo("testversion"); + assertThat(queryCreatedEvent.getContext().getServerAddress()).isEqualTo("127.0.0.1"); + assertThat(queryCreatedEvent.getContext().getEnvironment()).isEqualTo("testing"); + assertThat(queryCreatedEvent.getContext().getClientInfo().get()).isEqualTo("{\"clientVersion\":\"testVersion\"}"); + assertThat(queryCreatedEvent.getMetadata().getQuery()).isEqualTo(prepareQuery); + assertThat(queryCreatedEvent.getMetadata().getPreparedQuery().isPresent()).isFalse(); QueryCompletedEvent queryCompletedEvent = queryEvents.getQueryCompletedEvent(); - assertTrue(queryCompletedEvent.getContext().getResourceGroupId().isPresent()); - assertEquals(queryCompletedEvent.getContext().getResourceGroupId().get(), createResourceGroupId("global", "user-user")); - assertEquals(queryCompletedEvent.getIoMetadata().getOutput(), Optional.empty()); - assertEquals(queryCompletedEvent.getIoMetadata().getInputs().size(), 0); // Prepare has no inputs - assertEquals(queryCompletedEvent.getContext().getClientInfo().get(), "{\"clientVersion\":\"testVersion\"}"); - assertEquals(queryCreatedEvent.getMetadata().getQueryId(), queryCompletedEvent.getMetadata().getQueryId()); - assertFalse(queryCompletedEvent.getMetadata().getPreparedQuery().isPresent()); - assertEquals(queryCompletedEvent.getStatistics().getCompletedSplits(), 0); // Prepare has no splits + assertThat(queryCompletedEvent.getContext().getResourceGroupId().isPresent()).isTrue(); + assertThat(queryCompletedEvent.getContext().getResourceGroupId().get()).isEqualTo(createResourceGroupId("global", "user-user")); + assertThat(queryCompletedEvent.getIoMetadata().getOutput()).isEqualTo(Optional.empty()); + assertThat(queryCompletedEvent.getIoMetadata().getInputs().size()).isEqualTo(0); // Prepare has no inputs + assertThat(queryCompletedEvent.getContext().getClientInfo().get()).isEqualTo("{\"clientVersion\":\"testVersion\"}"); + assertThat(queryCreatedEvent.getMetadata().getQueryId()).isEqualTo(queryCompletedEvent.getMetadata().getQueryId()); + assertThat(queryCompletedEvent.getMetadata().getPreparedQuery().isPresent()).isFalse(); + assertThat(queryCompletedEvent.getStatistics().getCompletedSplits()).isEqualTo(0); // Prepare has no splits // Add prepared statement to a new session to eliminate any impact on other tests in this suite. Session sessionWithPrepare = Session.builder(getSession()).addPreparedStatement("stmt", selectQuery).build(); @@ -674,24 +665,24 @@ public void testPrepareAndExecute() queryEvents = queries.runQueryAndWaitForEvents("EXECUTE stmt USING 'SHIP'", sessionWithPrepare).getQueryEvents(); queryCreatedEvent = queryEvents.getQueryCreatedEvent(); - assertEquals(queryCreatedEvent.getContext().getServerVersion(), "testversion"); - assertEquals(queryCreatedEvent.getContext().getServerAddress(), "127.0.0.1"); - assertEquals(queryCreatedEvent.getContext().getEnvironment(), "testing"); - assertEquals(queryCreatedEvent.getContext().getClientInfo().get(), "{\"clientVersion\":\"testVersion\"}"); - assertEquals(queryCreatedEvent.getMetadata().getQuery(), "EXECUTE stmt USING 'SHIP'"); - assertTrue(queryCreatedEvent.getMetadata().getPreparedQuery().isPresent()); - assertEquals(queryCreatedEvent.getMetadata().getPreparedQuery().get(), selectQuery); + assertThat(queryCreatedEvent.getContext().getServerVersion()).isEqualTo("testversion"); + assertThat(queryCreatedEvent.getContext().getServerAddress()).isEqualTo("127.0.0.1"); + assertThat(queryCreatedEvent.getContext().getEnvironment()).isEqualTo("testing"); + assertThat(queryCreatedEvent.getContext().getClientInfo().get()).isEqualTo("{\"clientVersion\":\"testVersion\"}"); + assertThat(queryCreatedEvent.getMetadata().getQuery()).isEqualTo("EXECUTE stmt USING 'SHIP'"); + assertThat(queryCreatedEvent.getMetadata().getPreparedQuery().isPresent()).isTrue(); + assertThat(queryCreatedEvent.getMetadata().getPreparedQuery().get()).isEqualTo(selectQuery); queryCompletedEvent = queryEvents.getQueryCompletedEvent(); - assertTrue(queryCompletedEvent.getContext().getResourceGroupId().isPresent()); - assertEquals(queryCompletedEvent.getContext().getResourceGroupId().get(), createResourceGroupId("global", "user-user")); - assertEquals(queryCompletedEvent.getIoMetadata().getOutput(), Optional.empty()); - assertEquals(queryCompletedEvent.getIoMetadata().getInputs().size(), 1); - assertEquals(queryCompletedEvent.getContext().getClientInfo().get(), "{\"clientVersion\":\"testVersion\"}"); - assertEquals(getOnlyElement(queryCompletedEvent.getIoMetadata().getInputs()).getCatalogName(), "tpch"); - assertEquals(queryCreatedEvent.getMetadata().getQueryId(), queryCompletedEvent.getMetadata().getQueryId()); - assertTrue(queryCompletedEvent.getMetadata().getPreparedQuery().isPresent()); - assertEquals(queryCompletedEvent.getMetadata().getPreparedQuery().get(), selectQuery); + assertThat(queryCompletedEvent.getContext().getResourceGroupId().isPresent()).isTrue(); + assertThat(queryCompletedEvent.getContext().getResourceGroupId().get()).isEqualTo(createResourceGroupId("global", "user-user")); + assertThat(queryCompletedEvent.getIoMetadata().getOutput()).isEqualTo(Optional.empty()); + assertThat(queryCompletedEvent.getIoMetadata().getInputs().size()).isEqualTo(1); + assertThat(queryCompletedEvent.getContext().getClientInfo().get()).isEqualTo("{\"clientVersion\":\"testVersion\"}"); + assertThat(getOnlyElement(queryCompletedEvent.getIoMetadata().getInputs()).getCatalogName()).isEqualTo("tpch"); + assertThat(queryCreatedEvent.getMetadata().getQueryId()).isEqualTo(queryCompletedEvent.getMetadata().getQueryId()); + assertThat(queryCompletedEvent.getMetadata().getPreparedQuery().isPresent()).isTrue(); + assertThat(queryCompletedEvent.getMetadata().getPreparedQuery().get()).isEqualTo(selectQuery); } @Test @@ -703,48 +694,48 @@ public void testOutputStats() QueryCompletedEvent queryCompletedEvent = result.getQueryEvents().getQueryCompletedEvent(); QueryStats queryStats = getDistributedQueryRunner().getCoordinator().getQueryManager().getFullQueryInfo(new QueryId(queryCreatedEvent.getMetadata().getQueryId())).getQueryStats(); - assertTrue(queryStats.getOutputDataSize().toBytes() > 0L); - assertTrue(queryCompletedEvent.getStatistics().getOutputBytes() > 0L); - assertEquals(result.getMaterializedResult().getRowCount(), queryStats.getOutputPositions()); - assertEquals(result.getMaterializedResult().getRowCount(), queryCompletedEvent.getStatistics().getOutputRows()); + assertThat(queryStats.getOutputDataSize().toBytes() > 0L).isTrue(); + assertThat(queryCompletedEvent.getStatistics().getOutputBytes() > 0L).isTrue(); + assertThat(result.getMaterializedResult().getRowCount()).isEqualTo(queryStats.getOutputPositions()); + assertThat(result.getMaterializedResult().getRowCount()).isEqualTo(queryCompletedEvent.getStatistics().getOutputRows()); result = runQueryAndWaitForEvents("SELECT COUNT(1) FROM lineitem"); queryCreatedEvent = result.getQueryEvents().getQueryCreatedEvent(); queryCompletedEvent = result.getQueryEvents().getQueryCompletedEvent(); queryStats = getDistributedQueryRunner().getCoordinator().getQueryManager().getFullQueryInfo(new QueryId(queryCreatedEvent.getMetadata().getQueryId())).getQueryStats(); - assertTrue(queryStats.getOutputDataSize().toBytes() > 0L); - assertTrue(queryCompletedEvent.getStatistics().getOutputBytes() > 0L); - assertEquals(1L, queryStats.getOutputPositions()); - assertEquals(1L, queryCompletedEvent.getStatistics().getOutputRows()); + assertThat(queryStats.getOutputDataSize().toBytes() > 0L).isTrue(); + assertThat(queryCompletedEvent.getStatistics().getOutputBytes() > 0L).isTrue(); + assertThat(1L).isEqualTo(queryStats.getOutputPositions()); + assertThat(1L).isEqualTo(queryCompletedEvent.getStatistics().getOutputRows()); // Ensure the proper conversion in QueryMonitor#createQueryStatistics QueryStatistics statistics = queryCompletedEvent.getStatistics(); - assertEquals(statistics.getCpuTime().toMillis(), queryStats.getTotalCpuTime().toMillis()); - assertEquals(statistics.getWallTime().toMillis(), queryStats.getElapsedTime().toMillis()); - assertEquals(statistics.getQueuedTime().toMillis(), queryStats.getQueuedTime().toMillis()); - assertEquals(statistics.getScheduledTime().get().toMillis(), queryStats.getTotalScheduledTime().toMillis()); - assertEquals(statistics.getResourceWaitingTime().get().toMillis(), queryStats.getResourceWaitingTime().toMillis()); - assertEquals(statistics.getAnalysisTime().get().toMillis(), queryStats.getAnalysisTime().toMillis()); - assertEquals(statistics.getPlanningTime().get().toMillis(), queryStats.getPlanningTime().toMillis()); - assertEquals(statistics.getExecutionTime().get().toMillis(), queryStats.getExecutionTime().toMillis()); - assertEquals(statistics.getPeakUserMemoryBytes(), queryStats.getPeakUserMemoryReservation().toBytes()); - assertEquals(statistics.getPeakTaskUserMemory(), queryStats.getPeakTaskUserMemory().toBytes()); - assertEquals(statistics.getPeakTaskTotalMemory(), queryStats.getPeakTaskTotalMemory().toBytes()); - assertEquals(statistics.getPhysicalInputBytes(), queryStats.getPhysicalInputDataSize().toBytes()); - assertEquals(statistics.getPhysicalInputRows(), queryStats.getPhysicalInputPositions()); - assertEquals(statistics.getInternalNetworkBytes(), queryStats.getInternalNetworkInputDataSize().toBytes()); - assertEquals(statistics.getInternalNetworkRows(), queryStats.getInternalNetworkInputPositions()); - assertEquals(statistics.getTotalBytes(), queryStats.getRawInputDataSize().toBytes()); - assertEquals(statistics.getTotalRows(), queryStats.getRawInputPositions()); - assertEquals(statistics.getOutputBytes(), queryStats.getOutputDataSize().toBytes()); - assertEquals(statistics.getOutputRows(), queryStats.getOutputPositions()); - assertEquals(statistics.getWrittenBytes(), queryStats.getLogicalWrittenDataSize().toBytes()); - assertEquals(statistics.getWrittenRows(), queryStats.getWrittenPositions()); - assertEquals(statistics.getSpilledBytes(), queryStats.getSpilledDataSize().toBytes()); - assertEquals(statistics.getCumulativeMemory(), queryStats.getCumulativeUserMemory()); - assertEquals(statistics.getStageGcStatistics(), queryStats.getStageGcStatistics()); - assertEquals(statistics.getCompletedSplits(), queryStats.getCompletedDrivers()); + assertThat(statistics.getCpuTime().toMillis()).isEqualTo(queryStats.getTotalCpuTime().toMillis()); + assertThat(statistics.getWallTime().toMillis()).isEqualTo(queryStats.getElapsedTime().toMillis()); + assertThat(statistics.getQueuedTime().toMillis()).isEqualTo(queryStats.getQueuedTime().toMillis()); + assertThat(statistics.getScheduledTime().get().toMillis()).isEqualTo(queryStats.getTotalScheduledTime().toMillis()); + assertThat(statistics.getResourceWaitingTime().get().toMillis()).isEqualTo(queryStats.getResourceWaitingTime().toMillis()); + assertThat(statistics.getAnalysisTime().get().toMillis()).isEqualTo(queryStats.getAnalysisTime().toMillis()); + assertThat(statistics.getPlanningTime().get().toMillis()).isEqualTo(queryStats.getPlanningTime().toMillis()); + assertThat(statistics.getExecutionTime().get().toMillis()).isEqualTo(queryStats.getExecutionTime().toMillis()); + assertThat(statistics.getPeakUserMemoryBytes()).isEqualTo(queryStats.getPeakUserMemoryReservation().toBytes()); + assertThat(statistics.getPeakTaskUserMemory()).isEqualTo(queryStats.getPeakTaskUserMemory().toBytes()); + assertThat(statistics.getPeakTaskTotalMemory()).isEqualTo(queryStats.getPeakTaskTotalMemory().toBytes()); + assertThat(statistics.getPhysicalInputBytes()).isEqualTo(queryStats.getPhysicalInputDataSize().toBytes()); + assertThat(statistics.getPhysicalInputRows()).isEqualTo(queryStats.getPhysicalInputPositions()); + assertThat(statistics.getInternalNetworkBytes()).isEqualTo(queryStats.getInternalNetworkInputDataSize().toBytes()); + assertThat(statistics.getInternalNetworkRows()).isEqualTo(queryStats.getInternalNetworkInputPositions()); + assertThat(statistics.getTotalBytes()).isEqualTo(queryStats.getRawInputDataSize().toBytes()); + assertThat(statistics.getTotalRows()).isEqualTo(queryStats.getRawInputPositions()); + assertThat(statistics.getOutputBytes()).isEqualTo(queryStats.getOutputDataSize().toBytes()); + assertThat(statistics.getOutputRows()).isEqualTo(queryStats.getOutputPositions()); + assertThat(statistics.getWrittenBytes()).isEqualTo(queryStats.getLogicalWrittenDataSize().toBytes()); + assertThat(statistics.getWrittenRows()).isEqualTo(queryStats.getWrittenPositions()); + assertThat(statistics.getSpilledBytes()).isEqualTo(queryStats.getSpilledDataSize().toBytes()); + assertThat(statistics.getCumulativeMemory()).isEqualTo(queryStats.getCumulativeUserMemory()); + assertThat(statistics.getStageGcStatistics()).isEqualTo(queryStats.getStageGcStatistics()); + assertThat(statistics.getCompletedSplits()).isEqualTo(queryStats.getCompletedDrivers()); } @Test diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerWithSplits.java b/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerWithSplits.java index f854b4998d27..956aa7f794af 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerWithSplits.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerWithSplits.java @@ -47,9 +47,7 @@ import static io.trino.testing.TestingSession.testSessionBuilder; import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.stream.Collectors.toSet; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestEventListenerWithSplits extends AbstractTestQueryFramework @@ -109,32 +107,32 @@ public void testSplitsForNormalQuery() QueryEvents queryEvents = runQueryAndWaitForEvents("SELECT sum(linenumber) FROM lineitem").getQueryEvents(); QueryCreatedEvent queryCreatedEvent = queryEvents.getQueryCreatedEvent(); - assertEquals(queryCreatedEvent.getContext().getServerVersion(), "testversion"); - assertEquals(queryCreatedEvent.getContext().getServerAddress(), "127.0.0.1"); - assertEquals(queryCreatedEvent.getContext().getEnvironment(), "testing"); - assertEquals(queryCreatedEvent.getContext().getClientInfo().get(), "{\"clientVersion\":\"testVersion\"}"); - assertEquals(queryCreatedEvent.getMetadata().getQuery(), "SELECT sum(linenumber) FROM lineitem"); - assertFalse(queryCreatedEvent.getMetadata().getPreparedQuery().isPresent()); + assertThat(queryCreatedEvent.getContext().getServerVersion()).isEqualTo("testversion"); + assertThat(queryCreatedEvent.getContext().getServerAddress()).isEqualTo("127.0.0.1"); + assertThat(queryCreatedEvent.getContext().getEnvironment()).isEqualTo("testing"); + assertThat(queryCreatedEvent.getContext().getClientInfo().get()).isEqualTo("{\"clientVersion\":\"testVersion\"}"); + assertThat(queryCreatedEvent.getMetadata().getQuery()).isEqualTo("SELECT sum(linenumber) FROM lineitem"); + assertThat(queryCreatedEvent.getMetadata().getPreparedQuery().isPresent()).isFalse(); QueryCompletedEvent queryCompletedEvent = queryEvents.getQueryCompletedEvent(); - assertTrue(queryCompletedEvent.getContext().getResourceGroupId().isPresent()); - assertEquals(queryCompletedEvent.getContext().getResourceGroupId().get(), createResourceGroupId("global", "user-user")); - assertEquals(queryCompletedEvent.getIoMetadata().getOutput(), Optional.empty()); - assertEquals(queryCompletedEvent.getIoMetadata().getInputs().size(), 1); - assertEquals(queryCompletedEvent.getContext().getClientInfo().get(), "{\"clientVersion\":\"testVersion\"}"); - assertEquals(getOnlyElement(queryCompletedEvent.getIoMetadata().getInputs()).getCatalogName(), "tpch"); - assertEquals(queryCreatedEvent.getMetadata().getQueryId(), queryCompletedEvent.getMetadata().getQueryId()); - assertFalse(queryCompletedEvent.getMetadata().getPreparedQuery().isPresent()); - assertEquals(queryCompletedEvent.getStatistics().getCompletedSplits(), SPLITS_PER_NODE + 2); + assertThat(queryCompletedEvent.getContext().getResourceGroupId().isPresent()).isTrue(); + assertThat(queryCompletedEvent.getContext().getResourceGroupId().get()).isEqualTo(createResourceGroupId("global", "user-user")); + assertThat(queryCompletedEvent.getIoMetadata().getOutput()).isEqualTo(Optional.empty()); + assertThat(queryCompletedEvent.getIoMetadata().getInputs().size()).isEqualTo(1); + assertThat(queryCompletedEvent.getContext().getClientInfo().get()).isEqualTo("{\"clientVersion\":\"testVersion\"}"); + assertThat(getOnlyElement(queryCompletedEvent.getIoMetadata().getInputs()).getCatalogName()).isEqualTo("tpch"); + assertThat(queryCreatedEvent.getMetadata().getQueryId()).isEqualTo(queryCompletedEvent.getMetadata().getQueryId()); + assertThat(queryCompletedEvent.getMetadata().getPreparedQuery().isPresent()).isFalse(); + assertThat(queryCompletedEvent.getStatistics().getCompletedSplits()).isEqualTo(SPLITS_PER_NODE + 2); List splitCompletedEvents = queryEvents.waitForSplitCompletedEvents(SPLITS_PER_NODE + 2, new Duration(30, SECONDS)); - assertEquals(splitCompletedEvents.size(), SPLITS_PER_NODE + 2); // leaf splits + aggregation split + assertThat(splitCompletedEvents.size()).isEqualTo(SPLITS_PER_NODE + 2); // leaf splits + aggregation split // All splits must have the same query ID Set actual = splitCompletedEvents.stream() .map(SplitCompletedEvent::getQueryId) .collect(toSet()); - assertEquals(actual, ImmutableSet.of(queryCompletedEvent.getMetadata().getQueryId())); + assertThat(actual).isEqualTo(ImmutableSet.of(queryCompletedEvent.getMetadata().getQueryId())); // Sum of row count processed by all leaf stages is equal to the number of rows in the table long actualCompletedPositions = splitCompletedEvents.stream() @@ -144,44 +142,44 @@ public void testSplitsForNormalQuery() MaterializedResultWithEvents result = runQueryAndWaitForEvents("SELECT count(*) FROM lineitem"); long expectedCompletedPositions = (long) result.getMaterializedResult().getMaterializedRows().get(0).getField(0); - assertEquals(actualCompletedPositions, expectedCompletedPositions); + assertThat(actualCompletedPositions).isEqualTo(expectedCompletedPositions); QueryStatistics statistics = queryCompletedEvent.getStatistics(); // Aggregation can have memory pool usage - assertTrue(statistics.getPeakUserMemoryBytes() >= 0); - assertTrue(statistics.getPeakTaskUserMemory() >= 0); - assertTrue(statistics.getPeakTaskTotalMemory() >= 0); - assertTrue(statistics.getCumulativeMemory() >= 0); + assertThat(statistics.getPeakUserMemoryBytes() >= 0).isTrue(); + assertThat(statistics.getPeakTaskUserMemory() >= 0).isTrue(); + assertThat(statistics.getPeakTaskTotalMemory() >= 0).isTrue(); + assertThat(statistics.getCumulativeMemory() >= 0).isTrue(); // Not a write query - assertEquals(statistics.getWrittenBytes(), 0); - assertEquals(statistics.getWrittenRows(), 0); - assertEquals(statistics.getStageGcStatistics().size(), 2); + assertThat(statistics.getWrittenBytes()).isEqualTo(0); + assertThat(statistics.getWrittenRows()).isEqualTo(0); + assertThat(statistics.getStageGcStatistics().size()).isEqualTo(2); // Deterministic statistics - assertEquals(statistics.getPhysicalInputBytes(), 0); - assertEquals(statistics.getPhysicalInputRows(), expectedCompletedPositions); - assertEquals(statistics.getProcessedInputBytes(), 0); - assertEquals(statistics.getProcessedInputRows(), expectedCompletedPositions); - assertEquals(statistics.getInternalNetworkBytes(), 261); - assertEquals(statistics.getInternalNetworkRows(), 3); - assertEquals(statistics.getTotalBytes(), 0); - assertEquals(statistics.getOutputBytes(), 9); - assertEquals(statistics.getOutputRows(), 1); - assertTrue(statistics.isComplete()); + assertThat(statistics.getPhysicalInputBytes()).isEqualTo(0); + assertThat(statistics.getPhysicalInputRows()).isEqualTo(expectedCompletedPositions); + assertThat(statistics.getProcessedInputBytes()).isEqualTo(0); + assertThat(statistics.getProcessedInputRows()).isEqualTo(expectedCompletedPositions); + assertThat(statistics.getInternalNetworkBytes()).isEqualTo(261); + assertThat(statistics.getInternalNetworkRows()).isEqualTo(3); + assertThat(statistics.getTotalBytes()).isEqualTo(0); + assertThat(statistics.getOutputBytes()).isEqualTo(9); + assertThat(statistics.getOutputRows()).isEqualTo(1); + assertThat(statistics.isComplete()).isTrue(); // Check only the presence because they are non-deterministic. - assertTrue(statistics.getScheduledTime().isPresent()); - assertTrue(statistics.getResourceWaitingTime().isPresent()); - assertTrue(statistics.getAnalysisTime().isPresent()); - assertTrue(statistics.getPlanningTime().isPresent()); - assertTrue(statistics.getExecutionTime().isPresent()); - assertTrue(statistics.getPlanNodeStatsAndCosts().isPresent()); - assertTrue(statistics.getCpuTime().getSeconds() >= 0); - assertTrue(statistics.getWallTime().getSeconds() >= 0); - assertTrue(statistics.getCpuTimeDistribution().size() > 0); - assertTrue(statistics.getOperatorSummaries().size() > 0); - assertTrue(statistics.getOutputBufferUtilization().size() > 0); + assertThat(statistics.getScheduledTime().isPresent()).isTrue(); + assertThat(statistics.getResourceWaitingTime().isPresent()).isTrue(); + assertThat(statistics.getAnalysisTime().isPresent()).isTrue(); + assertThat(statistics.getPlanningTime().isPresent()).isTrue(); + assertThat(statistics.getExecutionTime().isPresent()).isTrue(); + assertThat(statistics.getPlanNodeStatsAndCosts().isPresent()).isTrue(); + assertThat(statistics.getCpuTime().getSeconds() >= 0).isTrue(); + assertThat(statistics.getWallTime().getSeconds() >= 0).isTrue(); + assertThat(statistics.getCpuTimeDistribution().size() > 0).isTrue(); + assertThat(statistics.getOperatorSummaries().size() > 0).isTrue(); + assertThat(statistics.getOutputBufferUtilization().size() > 0).isTrue(); } @Test @@ -192,26 +190,26 @@ public void testSplitsForConstantQuery() QueryEvents queryEvents = runQueryAndWaitForEvents("SELECT 1").getQueryEvents(); QueryCreatedEvent queryCreatedEvent = queryEvents.getQueryCreatedEvent(); - assertEquals(queryCreatedEvent.getContext().getServerVersion(), "testversion"); - assertEquals(queryCreatedEvent.getContext().getServerAddress(), "127.0.0.1"); - assertEquals(queryCreatedEvent.getContext().getEnvironment(), "testing"); - assertEquals(queryCreatedEvent.getContext().getClientInfo().get(), "{\"clientVersion\":\"testVersion\"}"); - assertEquals(queryCreatedEvent.getContext().getQueryType().get(), QueryType.SELECT); - assertEquals(queryCreatedEvent.getMetadata().getQuery(), "SELECT 1"); - assertFalse(queryCreatedEvent.getMetadata().getPreparedQuery().isPresent()); + assertThat(queryCreatedEvent.getContext().getServerVersion()).isEqualTo("testversion"); + assertThat(queryCreatedEvent.getContext().getServerAddress()).isEqualTo("127.0.0.1"); + assertThat(queryCreatedEvent.getContext().getEnvironment()).isEqualTo("testing"); + assertThat(queryCreatedEvent.getContext().getClientInfo().get()).isEqualTo("{\"clientVersion\":\"testVersion\"}"); + assertThat(queryCreatedEvent.getContext().getQueryType().get()).isEqualTo(QueryType.SELECT); + assertThat(queryCreatedEvent.getMetadata().getQuery()).isEqualTo("SELECT 1"); + assertThat(queryCreatedEvent.getMetadata().getPreparedQuery().isPresent()).isFalse(); QueryCompletedEvent queryCompletedEvent = queryEvents.getQueryCompletedEvent(); - assertTrue(queryCompletedEvent.getContext().getResourceGroupId().isPresent()); - assertEquals(queryCompletedEvent.getContext().getResourceGroupId().get(), createResourceGroupId("global", "user-user")); - assertEquals(queryCompletedEvent.getStatistics().getTotalRows(), 0L); - assertEquals(queryCompletedEvent.getContext().getClientInfo().get(), "{\"clientVersion\":\"testVersion\"}"); - assertEquals(queryCreatedEvent.getMetadata().getQueryId(), queryCompletedEvent.getMetadata().getQueryId()); - assertFalse(queryCompletedEvent.getMetadata().getPreparedQuery().isPresent()); - assertEquals(queryCompletedEvent.getContext().getQueryType().get(), QueryType.SELECT); + assertThat(queryCompletedEvent.getContext().getResourceGroupId().isPresent()).isTrue(); + assertThat(queryCompletedEvent.getContext().getResourceGroupId().get()).isEqualTo(createResourceGroupId("global", "user-user")); + assertThat(queryCompletedEvent.getStatistics().getTotalRows()).isEqualTo(0L); + assertThat(queryCompletedEvent.getContext().getClientInfo().get()).isEqualTo("{\"clientVersion\":\"testVersion\"}"); + assertThat(queryCreatedEvent.getMetadata().getQueryId()).isEqualTo(queryCompletedEvent.getMetadata().getQueryId()); + assertThat(queryCompletedEvent.getMetadata().getPreparedQuery().isPresent()).isFalse(); + assertThat(queryCompletedEvent.getContext().getQueryType().get()).isEqualTo(QueryType.SELECT); List splitCompletedEvents = queryEvents.waitForSplitCompletedEvents(1, new Duration(30, SECONDS)); - assertEquals(splitCompletedEvents.get(0).getQueryId(), queryCompletedEvent.getMetadata().getQueryId()); - assertEquals(splitCompletedEvents.get(0).getStatistics().getCompletedPositions(), 1); + assertThat(splitCompletedEvents.get(0).getQueryId()).isEqualTo(queryCompletedEvent.getMetadata().getQueryId()); + assertThat(splitCompletedEvents.get(0).getStatistics().getCompletedPositions()).isEqualTo(1); } private MaterializedResultWithEvents runQueryAndWaitForEvents(@Language("SQL") String sql) diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestExecutionJmxMetrics.java b/testing/trino-tests/src/test/java/io/trino/execution/TestExecutionJmxMetrics.java index 37622b73c5bb..7a9cf391c1f7 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestExecutionJmxMetrics.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestExecutionJmxMetrics.java @@ -34,7 +34,7 @@ import static io.trino.execution.QueryState.QUEUED; import static io.trino.execution.QueryState.RUNNING; import static io.trino.testing.TestingSession.testSessionBuilder; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestExecutionJmxMetrics { @@ -61,21 +61,21 @@ public void testQueryStats() QueryId firstDashboardQuery = createQuery(queryRunner, dashboardSession(), LONG_RUNNING_QUERY); waitForQueryState(queryRunner, firstDashboardQuery, RUNNING); - assertEquals(getMbeanAttribute(mbeanServer, "RunningQueries"), 1); - assertEquals(getMbeanAttribute(mbeanServer, "QueuedQueries"), 0); + assertThat(getMbeanAttribute(mbeanServer, "RunningQueries")).isEqualTo(1); + assertThat(getMbeanAttribute(mbeanServer, "QueuedQueries")).isEqualTo(0); // the second "dashboard" query can't run right away because the resource group has a hardConcurrencyLimit of 1 QueryId secondDashboardQuery = createQuery(queryRunner, dashboardSession(), LONG_RUNNING_QUERY); waitForQueryState(queryRunner, secondDashboardQuery, QUEUED); - assertEquals(getMbeanAttribute(mbeanServer, "RunningQueries"), 1); - assertEquals(getMbeanAttribute(mbeanServer, "QueuedQueries"), 1); + assertThat(getMbeanAttribute(mbeanServer, "RunningQueries")).isEqualTo(1); + assertThat(getMbeanAttribute(mbeanServer, "QueuedQueries")).isEqualTo(1); cancelQuery(queryRunner, secondDashboardQuery); waitForQueryState(queryRunner, secondDashboardQuery, FAILED); - assertEquals(getMbeanAttribute(mbeanServer, "RunningQueries"), 1); - assertEquals(getMbeanAttribute(mbeanServer, "QueuedQueries"), 0); + assertThat(getMbeanAttribute(mbeanServer, "RunningQueries")).isEqualTo(1); + assertThat(getMbeanAttribute(mbeanServer, "QueuedQueries")).isEqualTo(0); // cancel the running query to avoid polluting the logs with meaningless stack traces try { diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestFinalQueryInfo.java b/testing/trino-tests/src/test/java/io/trino/execution/TestFinalQueryInfo.java index d0590d58d928..e9ba97e7e890 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestFinalQueryInfo.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestFinalQueryInfo.java @@ -35,7 +35,7 @@ import static io.trino.client.StatementClientFactory.newStatementClient; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestFinalQueryInfo { @@ -56,7 +56,7 @@ public void testFinalQueryInfoSetOnAbort() // wait for final query info QueryInfo finalQueryInfo = tryGetFutureValue(finalQueryInfoFuture, 10, SECONDS) .orElseThrow(() -> new AssertionError("Final query info never set")); - assertTrue(finalQueryInfo.isFinalQueryInfo()); + assertThat(finalQueryInfo.isFinalQueryInfo()).isTrue(); } } diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestPendingStageState.java b/testing/trino-tests/src/test/java/io/trino/execution/TestPendingStageState.java index a91d84e6dd08..c431d5e004d5 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestPendingStageState.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestPendingStageState.java @@ -32,9 +32,9 @@ import static io.trino.plugin.tpch.TpchConnectorFactory.TPCH_SPLITS_PER_NODE; import static io.trino.testing.assertions.Assert.assertEventually; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -61,18 +61,18 @@ public void testPendingState() // wait for the query to finish producing results, but don't poll them assertEventually( new Duration(10, SECONDS), - () -> assertEquals(queryRunner.getCoordinator().getFullQueryInfo(queryId).getOutputStage().get().getState(), StageState.RUNNING)); + () -> assertThat(queryRunner.getCoordinator().getFullQueryInfo(queryId).getOutputStage().get().getState()).isEqualTo(StageState.RUNNING)); // wait for the sub stages to go to pending state assertEventually( new Duration(10, SECONDS), - () -> assertEquals(queryRunner.getCoordinator().getFullQueryInfo(queryId).getOutputStage().get().getSubStages().get(0).getState(), StageState.PENDING)); + () -> assertThat(queryRunner.getCoordinator().getFullQueryInfo(queryId).getOutputStage().get().getSubStages().get(0).getState()).isEqualTo(StageState.PENDING)); QueryInfo queryInfo = queryRunner.getCoordinator().getFullQueryInfo(queryId); - assertEquals(queryInfo.getState(), RUNNING); - assertEquals(queryInfo.getOutputStage().get().getState(), StageState.RUNNING); - assertEquals(queryInfo.getOutputStage().get().getSubStages().size(), 1); - assertEquals(queryInfo.getOutputStage().get().getSubStages().get(0).getState(), StageState.PENDING); + assertThat(queryInfo.getState()).isEqualTo(RUNNING); + assertThat(queryInfo.getOutputStage().get().getState()).isEqualTo(StageState.RUNNING); + assertThat(queryInfo.getOutputStage().get().getSubStages().size()).isEqualTo(1); + assertThat(queryInfo.getOutputStage().get().getSubStages().get(0).getState()).isEqualTo(StageState.PENDING); } @AfterAll diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestQueues.java b/testing/trino-tests/src/test/java/io/trino/execution/TestQueues.java index af9dea4ef84c..9f343c2693a9 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestQueues.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestQueues.java @@ -48,8 +48,7 @@ import static java.lang.String.format; import static java.util.Arrays.asList; import static java.util.Objects.requireNonNull; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; // run single threaded to avoid creating multiple query runners at once public class TestQueues @@ -302,8 +301,12 @@ private void assertResourceGroup(DistributedQueryRunner queryRunner, Session ses QueryId queryId = createQuery(queryRunner, session, query); waitForQueryState(queryRunner, queryId, ImmutableSet.of(RUNNING, FINISHING, FINISHED)); Optional resourceGroupId = queryRunner.getCoordinator().getQueryManager().getFullQueryInfo(queryId).getResourceGroupId(); - assertTrue(resourceGroupId.isPresent(), "Query should have a resource group"); - assertEquals(resourceGroupId.get(), expectedResourceGroup, format("Expected: '%s' resource group, found: %s", expectedResourceGroup, resourceGroupId.get())); + assertThat(resourceGroupId.isPresent()) + .describedAs("Query should have a resource group") + .isTrue(); + assertThat(resourceGroupId.get()) + .describedAs(format("Expected: '%s' resource group, found: %s", expectedResourceGroup, resourceGroupId.get())) + .isEqualTo(expectedResourceGroup); } private void testRejection() @@ -316,7 +319,7 @@ private void testRejection() QueryId queryId = createQuery(queryRunner, newRejectionSession(), LONG_LASTING_QUERY); waitForQueryState(queryRunner, queryId, FAILED); DispatchManager dispatchManager = queryRunner.getCoordinator().getDispatchManager(); - assertEquals(dispatchManager.getQueryInfo(queryId).getErrorCode(), QUERY_REJECTED.toErrorCode()); + assertThat(dispatchManager.getQueryInfo(queryId).getErrorCode()).isEqualTo(QUERY_REJECTED.toErrorCode()); } } diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestSetSessionAuthorization.java b/testing/trino-tests/src/test/java/io/trino/execution/TestSetSessionAuthorization.java index 6c9698436094..257c36ad06fb 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestSetSessionAuthorization.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestSetSessionAuthorization.java @@ -38,7 +38,7 @@ import static io.trino.spi.StandardErrorCode.GENERIC_USER_ERROR; import static io.trino.spi.StandardErrorCode.PERMISSION_DENIED; import static java.util.concurrent.TimeUnit.MINUTES; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestSetSessionAuthorization extends AbstractTestQueryFramework @@ -60,12 +60,9 @@ public void testSetSessionAuthorizationToSelf() .principal(Optional.of("user")) .user(Optional.of("user")) .build(); - assertEquals(submitQuery("SET SESSION AUTHORIZATION user", clientSession).getSetAuthorizationUser().get(), - "user"); - assertEquals(submitQuery("SET SESSION AUTHORIZATION alice", clientSession).getSetAuthorizationUser().get(), - "alice"); - assertEquals(submitQuery("SET SESSION AUTHORIZATION user", clientSession).getSetAuthorizationUser().get(), - "user"); + assertThat(submitQuery("SET SESSION AUTHORIZATION user", clientSession).getSetAuthorizationUser().get()).isEqualTo("user"); + assertThat(submitQuery("SET SESSION AUTHORIZATION alice", clientSession).getSetAuthorizationUser().get()).isEqualTo("alice"); + assertThat(submitQuery("SET SESSION AUTHORIZATION user", clientSession).getSetAuthorizationUser().get()).isEqualTo("user"); } @Test @@ -75,15 +72,13 @@ public void testValidSetSessionAuthorization() .principal(Optional.of("user")) .user(Optional.of("user")) .build(); - assertEquals(submitQuery("SET SESSION AUTHORIZATION alice", clientSession).getSetAuthorizationUser().get(), - "alice"); + assertThat(submitQuery("SET SESSION AUTHORIZATION alice", clientSession).getSetAuthorizationUser().get()).isEqualTo("alice"); clientSession = defaultClientSessionBuilder() .principal(Optional.of("user2")) .user(Optional.of("user2")) .build(); - assertEquals(submitQuery("SET SESSION AUTHORIZATION bob", clientSession).getSetAuthorizationUser().get(), - "bob"); + assertThat(submitQuery("SET SESSION AUTHORIZATION bob", clientSession).getSetAuthorizationUser().get()).isEqualTo("bob"); } @Test @@ -97,7 +92,7 @@ public void testInvalidSetSessionAuthorization() PERMISSION_DENIED.toErrorCode(), "Access Denied: User user cannot impersonate user user2"); assertError(submitQuery("SET SESSION AUTHORIZATION bob", clientSession), PERMISSION_DENIED.toErrorCode(), "Access Denied: User user cannot impersonate user bob"); - assertEquals(submitQuery("SET SESSION AUTHORIZATION alice", clientSession).getSetAuthorizationUser().get(), "alice"); + assertThat(submitQuery("SET SESSION AUTHORIZATION alice", clientSession).getSetAuthorizationUser().get()).isEqualTo("alice"); assertError(submitQuery("SET SESSION AUTHORIZATION charlie", clientSession), PERMISSION_DENIED.toErrorCode(), "Access Denied: User user cannot impersonate user charlie"); StatementClient client = submitQuery("START TRANSACTION", clientSession); @@ -115,19 +110,19 @@ public void testInvalidTransitiveSetSessionAuthorization() .principal(Optional.of("user")) .user(Optional.of("user")) .build(); - assertEquals(submitQuery("SET SESSION AUTHORIZATION alice", clientSession).getSetAuthorizationUser().get(), "alice"); + assertThat(submitQuery("SET SESSION AUTHORIZATION alice", clientSession).getSetAuthorizationUser().get()).isEqualTo("alice"); clientSession = defaultClientSessionBuilder() .principal(Optional.of("alice")) .user(Optional.of("alice")) .build(); - assertEquals(submitQuery("SET SESSION AUTHORIZATION charlie", clientSession).getSetAuthorizationUser().get(), "charlie"); + assertThat(submitQuery("SET SESSION AUTHORIZATION charlie", clientSession).getSetAuthorizationUser().get()).isEqualTo("charlie"); clientSession = defaultClientSessionBuilder() .principal(Optional.of("user")) .user(Optional.of("user")) .build(); - assertEquals(submitQuery("SET SESSION AUTHORIZATION alice", clientSession).getSetAuthorizationUser().get(), "alice"); + assertThat(submitQuery("SET SESSION AUTHORIZATION alice", clientSession).getSetAuthorizationUser().get()).isEqualTo("alice"); assertError(submitQuery("SET SESSION AUTHORIZATION charlie", clientSession), PERMISSION_DENIED.toErrorCode(), "Access Denied: User user cannot impersonate user charlie"); } @@ -140,20 +135,20 @@ public void testValidSessionAuthorizationExecution() .user(Optional.of("user")) .authorizationUser(Optional.of("alice")) .build(); - assertEquals(submitQuery("SELECT 1+1", clientSession).currentStatusInfo().getError(), null); + assertThat(submitQuery("SELECT 1+1", clientSession).currentStatusInfo().getError()).isEqualTo(null); clientSession = defaultClientSessionBuilder() .principal(Optional.of("user")) .user(Optional.of("user")) .authorizationUser(Optional.of("user")) .build(); - assertEquals(submitQuery("SELECT 1+1", clientSession).currentStatusInfo().getError(), null); + assertThat(submitQuery("SELECT 1+1", clientSession).currentStatusInfo().getError()).isEqualTo(null); clientSession = defaultClientSessionBuilder() .principal(Optional.of("user")) .authorizationUser(Optional.of("alice")) .build(); - assertEquals(submitQuery("SELECT 1+1", clientSession).currentStatusInfo().getError(), null); + assertThat(submitQuery("SELECT 1+1", clientSession).currentStatusInfo().getError()).isEqualTo(null); } @Test @@ -196,7 +191,7 @@ public void testSelectCurrentUser() ImmutableList.Builder> data = ImmutableList.builder(); submitQuery("SELECT CURRENT_USER", clientSession, data); List> rows = data.build(); - assertEquals((String) rows.get(0).get(0), "alice"); + assertThat((String) rows.get(0).get(0)).isEqualTo("alice"); } @Test @@ -207,7 +202,7 @@ public void testResetSessionAuthorization() .user(Optional.of("user")) .build(); assertResetAuthorizationUser(submitQuery("RESET SESSION AUTHORIZATION", clientSession)); - assertEquals(submitQuery("SET SESSION AUTHORIZATION alice", clientSession).getSetAuthorizationUser().get(), "alice"); + assertThat(submitQuery("SET SESSION AUTHORIZATION alice", clientSession).getSetAuthorizationUser().get()).isEqualTo("alice"); assertResetAuthorizationUser(submitQuery("RESET SESSION AUTHORIZATION", clientSession)); StatementClient client = submitQuery("START TRANSACTION", clientSession); clientSession = ClientSession.builder(clientSession).transactionId(client.getStartedTransactionId()).build(); @@ -217,15 +212,15 @@ public void testResetSessionAuthorization() private void assertError(StatementClient client, ErrorCode errorCode, String errorMessage) { - assertEquals(client.getSetAuthorizationUser(), Optional.empty()); - assertEquals(client.currentStatusInfo().getError().getErrorName(), errorCode.getName()); - assertEquals(client.currentStatusInfo().getError().getMessage(), errorMessage); + assertThat(client.getSetAuthorizationUser()).isEqualTo(Optional.empty()); + assertThat(client.currentStatusInfo().getError().getErrorName()).isEqualTo(errorCode.getName()); + assertThat(client.currentStatusInfo().getError().getMessage()).isEqualTo(errorMessage); } private void assertResetAuthorizationUser(StatementClient client) { - assertEquals(client.isResetAuthorizationUser(), true); - assertEquals(client.getSetAuthorizationUser().isEmpty(), true); + assertThat(client.isResetAuthorizationUser()).isEqualTo(true); + assertThat(client.getSetAuthorizationUser().isEmpty()).isEqualTo(true); } private ClientSession.Builder defaultClientSessionBuilder() diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestStatementStats.java b/testing/trino-tests/src/test/java/io/trino/execution/TestStatementStats.java index f51dd6c53fdd..b2a4fa1861b3 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestStatementStats.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestStatementStats.java @@ -22,9 +22,7 @@ import org.junit.jupiter.api.Test; import static io.trino.testing.TestingSession.testSessionBuilder; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; public class TestStatementStats { @@ -38,21 +36,21 @@ public void testUniqueNodeCounts() .build()) { MaterializedResult result = queryRunner.execute(testSessionBuilder().setCatalog("tpch").setSchema("tiny").build(), "SELECT COUNT(*) from lineitem LIMIT 10"); - assertTrue(result.getStatementStats().isPresent()); + assertThat(result.getStatementStats().isPresent()).isTrue(); StatementStats stats = result.getStatementStats().get(); // two unique nodes across all stages - assertEquals(stats.getNodes(), 2); + assertThat(stats.getNodes()).isEqualTo(2); StageStats rootStage = stats.getRootStage(); - assertNotNull(rootStage); + assertThat(rootStage).isNotNull(); // root stage should be a single node gather - assertEquals(rootStage.getNodes(), 1); + assertThat(rootStage.getNodes()).isEqualTo(1); // one child stage - assertEquals(rootStage.getSubStages().size(), 1); + assertThat(rootStage.getSubStages().size()).isEqualTo(1); // child stage has two unique nodes - assertEquals(rootStage.getSubStages().get(0).getNodes(), 2); + assertThat(rootStage.getSubStages().get(0).getNodes()).isEqualTo(2); } } } diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestTableRedirection.java b/testing/trino-tests/src/test/java/io/trino/execution/TestTableRedirection.java index aa79d1f96388..7f795e9cac35 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestTableRedirection.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestTableRedirection.java @@ -55,7 +55,6 @@ import static java.util.Collections.emptyIterator; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; public class TestTableRedirection extends AbstractTestQueryFramework @@ -341,7 +340,7 @@ public void testShowCreate() { String showCreateValidSource = (String) computeScalar(format("SHOW CREATE TABLE %s.%s", SCHEMA_ONE, VALID_REDIRECTION_SRC)); String showCreateValidTarget = (String) computeScalar(format("SHOW CREATE TABLE %s.%s", SCHEMA_TWO, VALID_REDIRECTION_TARGET)); - assertEquals(showCreateValidTarget, showCreateValidSource.replace(SCHEMA_ONE + "." + VALID_REDIRECTION_SRC, SCHEMA_TWO + "." + VALID_REDIRECTION_TARGET)); + assertThat(showCreateValidTarget).isEqualTo(showCreateValidSource.replace(SCHEMA_ONE + "." + VALID_REDIRECTION_SRC, SCHEMA_TWO + "." + VALID_REDIRECTION_TARGET)); assertThatThrownBy(() -> query((format("SHOW CREATE TABLE %s.%s", SCHEMA_ONE, BAD_REDIRECTION_SRC)))) .hasMessageContaining( @@ -404,10 +403,8 @@ public void testInsert() .where(TableFinishNode.class::isInstance) .findOnlyElement(); TableWriterNode.InsertTarget insertTarget = ((TableWriterNode.InsertTarget) finishNode.getTarget()); - assertEquals( - ((MockConnectorInsertTableHandle) insertTarget.getHandle().getConnectorHandle()).getTableName(), - schemaTableName(SCHEMA_TWO, VALID_REDIRECTION_TARGET)); - assertEquals(insertTarget.getSchemaTableName(), schemaTableName(SCHEMA_TWO, VALID_REDIRECTION_TARGET)); + assertThat(((MockConnectorInsertTableHandle) insertTarget.getHandle().getConnectorHandle()).getTableName()).isEqualTo(schemaTableName(SCHEMA_TWO, VALID_REDIRECTION_TARGET)); + assertThat(insertTarget.getSchemaTableName()).isEqualTo(schemaTableName(SCHEMA_TWO, VALID_REDIRECTION_TARGET)); }); } @@ -424,10 +421,8 @@ public void testDelete() .where(TableFinishNode.class::isInstance) .findOnlyElement(); TableWriterNode.MergeTarget mergeTarget = (TableWriterNode.MergeTarget) finishNode.getTarget(); - assertEquals( - ((MockConnectorTableHandle) mergeTarget.getHandle().getConnectorHandle()).getTableName(), - schemaTableName(SCHEMA_TWO, VALID_REDIRECTION_TARGET)); - assertEquals(mergeTarget.getSchemaTableName(), schemaTableName(SCHEMA_TWO, VALID_REDIRECTION_TARGET)); + assertThat(((MockConnectorTableHandle) mergeTarget.getHandle().getConnectorHandle()).getTableName()).isEqualTo(schemaTableName(SCHEMA_TWO, VALID_REDIRECTION_TARGET)); + assertThat(mergeTarget.getSchemaTableName()).isEqualTo(schemaTableName(SCHEMA_TWO, VALID_REDIRECTION_TARGET)); }); } @@ -444,10 +439,8 @@ public void testUpdate() .where(TableFinishNode.class::isInstance) .findOnlyElement(); TableWriterNode.MergeTarget mergeTarget = (TableWriterNode.MergeTarget) finishNode.getTarget(); - assertEquals( - ((MockConnectorTableHandle) mergeTarget.getHandle().getConnectorHandle()).getTableName(), - schemaTableName(SCHEMA_TWO, VALID_REDIRECTION_TARGET)); - assertEquals(mergeTarget.getSchemaTableName(), schemaTableName(SCHEMA_TWO, VALID_REDIRECTION_TARGET)); + assertThat(((MockConnectorTableHandle) mergeTarget.getHandle().getConnectorHandle()).getTableName()).isEqualTo(schemaTableName(SCHEMA_TWO, VALID_REDIRECTION_TARGET)); + assertThat(mergeTarget.getSchemaTableName()).isEqualTo(schemaTableName(SCHEMA_TWO, VALID_REDIRECTION_TARGET)); }); } @@ -467,7 +460,7 @@ private Consumer verifySingleTableScan(String schemaName, String tableName .where(TableScanNode.class::isInstance) .findOnlyElement(); SchemaTableName actual = ((MockConnectorTableHandle) tableScan.getTable().getConnectorHandle()).getTableName(); - assertEquals(actual, schemaTableName(schemaName, tableName)); + assertThat(actual).isEqualTo(schemaTableName(schemaName, tableName)); }; } } diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestUserImpersonationAccessControl.java b/testing/trino-tests/src/test/java/io/trino/execution/TestUserImpersonationAccessControl.java index 89a842f6133a..25de45207dbb 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestUserImpersonationAccessControl.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestUserImpersonationAccessControl.java @@ -37,9 +37,7 @@ import static io.trino.client.StatementClientFactory.newStatementClient; import static io.trino.plugin.base.security.FileBasedAccessControlConfig.SECURITY_CONFIG_FILE; import static java.util.concurrent.TimeUnit.MINUTES; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; +import static org.assertj.core.api.Assertions.assertThat; public class TestUserImpersonationAccessControl extends AbstractTestQueryFramework @@ -65,15 +63,15 @@ protected QueryRunner createQueryRunner() public void testReadAccessControl() { QueryError aliceQueryError = trySelectQuery("alice"); - assertNull(aliceQueryError); + assertThat(aliceQueryError).isNull(); QueryError bobQueryError = trySelectQuery("bob"); - assertNotNull(bobQueryError); - assertEquals(bobQueryError.getErrorType(), "USER_ERROR"); - assertEquals(bobQueryError.getErrorName(), "PERMISSION_DENIED"); + assertThat(bobQueryError).isNotNull(); + assertThat(bobQueryError.getErrorType()).isEqualTo("USER_ERROR"); + assertThat(bobQueryError.getErrorName()).isEqualTo("PERMISSION_DENIED"); QueryError charlieQueryError = trySelectQuery("charlie"); - assertNull(charlieQueryError); + assertThat(charlieQueryError).isNull(); } @Nullable diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestWarnings.java b/testing/trino-tests/src/test/java/io/trino/execution/TestWarnings.java index 7c1e2dec3812..c0062096250b 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestWarnings.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestWarnings.java @@ -30,9 +30,9 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet; import static io.trino.spi.connector.StandardWarningCode.TOO_MANY_STAGES; +import static org.assertj.core.api.Fail.fail; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.fail; @TestInstance(PER_CLASS) @Execution(CONCURRENT) diff --git a/testing/trino-tests/src/test/java/io/trino/execution/resourcegroups/TestResourceGroupIntegration.java b/testing/trino-tests/src/test/java/io/trino/execution/resourcegroups/TestResourceGroupIntegration.java index 730297b6d24b..154d08e6fe6a 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/resourcegroups/TestResourceGroupIntegration.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/resourcegroups/TestResourceGroupIntegration.java @@ -28,7 +28,6 @@ import static io.trino.testing.TestingSession.testSessionBuilder; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestResourceGroupIntegration { @@ -59,10 +58,10 @@ public void testPathToRoot() queryRunner.execute(testSessionBuilder().setCatalog("tpch").setSchema("tiny").setSource("dashboard-foo").build(), "SELECT COUNT(*), clerk FROM orders GROUP BY clerk"); List path = manager.tryGetPathToRoot(new ResourceGroupId(new ResourceGroupId(new ResourceGroupId("global"), "user-user"), "dashboard-user")) .orElseThrow(() -> new IllegalStateException("Resource group not found")); - assertEquals(path.size(), 3); + assertThat(path.size()).isEqualTo(3); assertThat(path.get(1).getSubGroups()).isPresent(); - assertEquals(path.get(2).getId(), new ResourceGroupId("global")); - assertEquals(path.get(2).getHardConcurrencyLimit(), 100); + assertThat(path.get(2).getId()).isEqualTo(new ResourceGroupId("global")); + assertThat(path.get(2).getHardConcurrencyLimit()).isEqualTo(100); assertThat(path.get(2).getRunningQueries()).isNotPresent(); } } diff --git a/testing/trino-tests/src/test/java/io/trino/execution/resourcegroups/db/TestQueuesDb.java b/testing/trino-tests/src/test/java/io/trino/execution/resourcegroups/db/TestQueuesDb.java index 57f99705b588..d61984362bbe 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/resourcegroups/db/TestQueuesDb.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/resourcegroups/db/TestQueuesDb.java @@ -68,9 +68,8 @@ import static io.trino.testing.assertions.Assert.assertEventually; import static java.lang.String.format; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; // run single threaded to avoid creating multiple query runners at once @TestInstance(PER_METHOD) @@ -215,11 +214,11 @@ public void testRejection() QueryId queryId = createQuery(queryRunner, rejectingSession(), LONG_LASTING_QUERY); waitForQueryState(queryRunner, queryId, FAILED); DispatchManager dispatchManager = queryRunner.getCoordinator().getDispatchManager(); - assertEquals(dispatchManager.getQueryInfo(queryId).getErrorCode(), QUERY_REJECTED.toErrorCode()); + assertThat(dispatchManager.getQueryInfo(queryId).getErrorCode()).isEqualTo(QUERY_REJECTED.toErrorCode()); int selectorCount = getSelectors(queryRunner).size(); dao.insertSelector(4, 100_000, "user.*", null, "(?i).*reject.*", null, null, null); dbConfigurationManager.load(); - assertEquals(getSelectors(queryRunner).size(), selectorCount + 1); + assertThat(getSelectors(queryRunner).size()).isEqualTo(selectorCount + 1); // Verify the query can be submitted queryId = createQuery(queryRunner, rejectingSession(), LONG_LASTING_QUERY); waitForQueryState(queryRunner, queryId, RUNNING); @@ -238,7 +237,7 @@ public void testQuerySystemTableResourceGroup() QueryId firstQuery = createQuery(queryRunner, dashboardSession(), LONG_LASTING_QUERY); waitForQueryState(queryRunner, firstQuery, RUNNING); MaterializedResult result = queryRunner.execute("SELECT resource_group_id FROM system.runtime.queries WHERE source = 'dashboard'"); - assertEquals(result.getOnlyValue(), ImmutableList.of("global", "user-user", "dashboard-user")); + assertThat(result.getOnlyValue()).isEqualTo(ImmutableList.of("global", "user-user", "dashboard-user")); } @Test @@ -254,8 +253,8 @@ public void testSelectorPriority() waitForQueryState(queryRunner, firstQuery, RUNNING); Optional resourceGroup = queryManager.getFullQueryInfo(firstQuery).getResourceGroupId(); - assertTrue(resourceGroup.isPresent()); - assertEquals(resourceGroup.get().toString(), "global.user-user.dashboard-user"); + assertThat(resourceGroup.isPresent()).isTrue(); + assertThat(resourceGroup.get().toString()).isEqualTo("global.user-user.dashboard-user"); // create a new resource group that rejects all queries submitted to it dao.insertResourceGroup(8, "reject-all-queries", "1MB", 0, 0, 0, null, null, null, null, null, 3L, TEST_ENVIRONMENT); @@ -271,7 +270,7 @@ public void testSelectorPriority() DispatchManager dispatchManager = queryRunner.getCoordinator().getDispatchManager(); BasicQueryInfo basicQueryInfo = dispatchManager.getQueryInfo(secondQuery); - assertEquals(basicQueryInfo.getErrorCode(), QUERY_QUEUE_FULL.toErrorCode()); + assertThat(basicQueryInfo.getErrorCode()).isEqualTo(QUERY_QUEUE_FULL.toErrorCode()); } @Test @@ -292,7 +291,7 @@ public void testQueryExecutionTimeLimit() .build(), LONG_LASTING_QUERY); waitForQueryState(queryRunner, firstQuery, FAILED); - assertEquals(queryManager.getFullQueryInfo(firstQuery).getErrorCode(), EXCEEDED_TIME_LIMIT.toErrorCode()); + assertThat(queryManager.getFullQueryInfo(firstQuery).getErrorCode()).isEqualTo(EXCEEDED_TIME_LIMIT.toErrorCode()); assertContains(queryManager.getFullQueryInfo(firstQuery).getFailureInfo().getMessage(), "Query exceeded the maximum execution time limit of 1.00ms"); // set max running queries to 0 for the dashboard resource group so that new queries get queued immediately dao.updateResourceGroup(5, "dashboard-${USER}", "1MB", 1, null, 0, null, null, null, null, null, 3L, TEST_ENVIRONMENT); @@ -311,7 +310,7 @@ public void testQueryExecutionTimeLimit() // after a 5s wait this query should still be QUEUED, not FAILED as the max execution time should be enforced after the query starts running Thread.sleep(5_000); DispatchManager dispatchManager = queryRunner.getCoordinator().getDispatchManager(); - assertEquals(dispatchManager.getQueryInfo(secondQuery).getState(), QUEUED); + assertThat(dispatchManager.getQueryInfo(secondQuery).getState()).isEqualTo(QUEUED); // reconfigure the resource group to run the second query dao.updateResourceGroup(5, "dashboard-${USER}", "1MB", 1, null, 1, null, null, null, null, null, 3L, TEST_ENVIRONMENT); dbConfigurationManager.load(); @@ -332,8 +331,10 @@ public void testQueryTypeBasedSelection() QueryId queryId = createQuery(queryRunner, session, "EXPLAIN " + LONG_LASTING_QUERY); waitForQueryState(queryRunner, queryId, ImmutableSet.of(RUNNING, FINISHED)); Optional resourceGroupId = queryRunner.getCoordinator().getQueryManager().getFullQueryInfo(queryId).getResourceGroupId(); - assertTrue(resourceGroupId.isPresent(), "Query should have a resource group"); - assertEquals(resourceGroupId.get(), createResourceGroupId("explain")); + assertThat(resourceGroupId.isPresent()) + .describedAs("Query should have a resource group") + .isTrue(); + assertThat(resourceGroupId.get()).isEqualTo(createResourceGroupId("explain")); } @Test @@ -370,7 +371,7 @@ public void testNonLeafGroup() // Submit a query to a non-leaf resource group QueryId invalidResourceGroupQuery = createQuery(queryRunner, session, LONG_LASTING_QUERY); waitForQueryState(queryRunner, invalidResourceGroupQuery, FAILED); - assertEquals(queryRunner.getCoordinator().getDispatchManager().getQueryInfo(invalidResourceGroupQuery).getErrorCode(), INVALID_RESOURCE_GROUP.toErrorCode()); + assertThat(queryRunner.getCoordinator().getDispatchManager().getQueryInfo(invalidResourceGroupQuery).getErrorCode()).isEqualTo(INVALID_RESOURCE_GROUP.toErrorCode()); } @Test @@ -383,12 +384,10 @@ public void testUpdateSoftMemoryLimit() dao.updateResourceGroup(2, "bi-${USER}", "100%", 3, 2, 2, null, null, null, null, null, 1L, TEST_ENVIRONMENT); dbConfigurationManager.load(); - assertEquals( - manager.tryGetResourceGroupInfo(new ResourceGroupId(new ResourceGroupId("global"), "bi-user")) - .orElseThrow(() -> new IllegalStateException("Resource group not found")) - .getSoftMemoryLimit() - .toBytes(), - queryRunner.getCoordinator().getClusterMemoryManager().getClusterMemoryBytes()); + assertThat(manager.tryGetResourceGroupInfo(new ResourceGroupId(new ResourceGroupId("global"), "bi-user")) + .orElseThrow(() -> new IllegalStateException("Resource group not found")) + .getSoftMemoryLimit() + .toBytes()).isEqualTo(queryRunner.getCoordinator().getClusterMemoryManager().getClusterMemoryBytes()); dao.updateResourceGroup(2, "bi-${USER}", "123MB", 3, 2, 2, null, null, null, null, null, 1L, TEST_ENVIRONMENT); dbConfigurationManager.load(); @@ -397,11 +396,9 @@ public void testUpdateSoftMemoryLimit() assertEventually( new Duration(2, TimeUnit.SECONDS), new Duration(100, TimeUnit.MILLISECONDS), - () -> assertEquals( - manager.tryGetResourceGroupInfo(new ResourceGroupId(new ResourceGroupId("global"), "bi-user")) - .orElseThrow(() -> new IllegalStateException("Resource group not found")) - .getSoftMemoryLimit(), - DataSize.of(123, MEGABYTE))); + () -> assertThat(manager.tryGetResourceGroupInfo(new ResourceGroupId(new ResourceGroupId("global"), "bi-user")) + .orElseThrow(() -> new IllegalStateException("Resource group not found")) + .getSoftMemoryLimit()).isEqualTo(DataSize.of(123, MEGABYTE))); } private void assertResourceGroupWithClientTags(Set clientTags, ResourceGroupId expectedResourceGroup) @@ -416,7 +413,11 @@ private void assertResourceGroupWithClientTags(Set clientTags, ResourceG QueryId queryId = createQuery(queryRunner, session, LONG_LASTING_QUERY); waitForQueryState(queryRunner, queryId, ImmutableSet.of(RUNNING, FINISHED)); Optional resourceGroupId = queryRunner.getCoordinator().getQueryManager().getFullQueryInfo(queryId).getResourceGroupId(); - assertTrue(resourceGroupId.isPresent(), "Query should have a resource group"); - assertEquals(resourceGroupId.get(), expectedResourceGroup, format("Expected: '%s' resource group, found: %s", expectedResourceGroup, resourceGroupId.get())); + assertThat(resourceGroupId.isPresent()) + .describedAs("Query should have a resource group") + .isTrue(); + assertThat(resourceGroupId.get()) + .describedAs(format("Expected: '%s' resource group, found: %s", expectedResourceGroup, resourceGroupId.get())) + .isEqualTo(expectedResourceGroup); } } diff --git a/testing/trino-tests/src/test/java/io/trino/memory/TestClusterMemoryLeakDetector.java b/testing/trino-tests/src/test/java/io/trino/memory/TestClusterMemoryLeakDetector.java index 850955491a20..827e2daee544 100644 --- a/testing/trino-tests/src/test/java/io/trino/memory/TestClusterMemoryLeakDetector.java +++ b/testing/trino-tests/src/test/java/io/trino/memory/TestClusterMemoryLeakDetector.java @@ -36,7 +36,7 @@ import static io.trino.execution.QueryState.RUNNING; import static io.trino.operator.BlockedReason.WAITING_FOR_MEMORY; import static java.util.concurrent.TimeUnit.MINUTES; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestClusterMemoryLeakDetector { @@ -47,23 +47,23 @@ public void testLeakDetector() ClusterMemoryLeakDetector leakDetector = new ClusterMemoryLeakDetector(); leakDetector.checkForMemoryLeaks(ImmutableList::of, ImmutableMap.of()); - assertEquals(leakDetector.getNumberOfLeakedQueries(), 0); + assertThat(leakDetector.getNumberOfLeakedQueries()).isEqualTo(0); // the leak detector should report no leaked queries as the query is still running leakDetector.checkForMemoryLeaks(() -> ImmutableList.of(createQueryInfo(testQuery.getId(), RUNNING)), ImmutableMap.of(testQuery, 1L)); - assertEquals(leakDetector.getNumberOfLeakedQueries(), 0); + assertThat(leakDetector.getNumberOfLeakedQueries()).isEqualTo(0); // the leak detector should report exactly one leaked query since the query is finished, and its end time is way in the past leakDetector.checkForMemoryLeaks(() -> ImmutableList.of(createQueryInfo(testQuery.getId(), FINISHED)), ImmutableMap.of(testQuery, 1L)); - assertEquals(leakDetector.getNumberOfLeakedQueries(), 1); + assertThat(leakDetector.getNumberOfLeakedQueries()).isEqualTo(1); // the leak detector should report no leaked queries as the query doesn't have any memory reservation leakDetector.checkForMemoryLeaks(() -> ImmutableList.of(createQueryInfo(testQuery.getId(), FINISHED)), ImmutableMap.of(testQuery, 0L)); - assertEquals(leakDetector.getNumberOfLeakedQueries(), 0); + assertThat(leakDetector.getNumberOfLeakedQueries()).isEqualTo(0); // the leak detector should report exactly one leaked query since the coordinator doesn't know of any query leakDetector.checkForMemoryLeaks(ImmutableList::of, ImmutableMap.of(testQuery, 1L)); - assertEquals(leakDetector.getNumberOfLeakedQueries(), 1); + assertThat(leakDetector.getNumberOfLeakedQueries()).isEqualTo(1); } private static BasicQueryInfo createQueryInfo(String queryId, QueryState state) diff --git a/testing/trino-tests/src/test/java/io/trino/memory/TestMemoryManager.java b/testing/trino-tests/src/test/java/io/trino/memory/TestMemoryManager.java index 980326078adf..c9b9d54f4ade 100644 --- a/testing/trino-tests/src/test/java/io/trino/memory/TestMemoryManager.java +++ b/testing/trino-tests/src/test/java/io/trino/memory/TestMemoryManager.java @@ -58,11 +58,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -138,7 +133,7 @@ public void testOutOfMemoryKiller() TaskId fakeTaskId = new TaskId(new StageId("fake", 0), 0, 0); for (TestingTrinoServer server : queryRunner.getServers()) { MemoryPool memoryPool = server.getLocalMemoryManager().getMemoryPool(); - assertTrue(memoryPool.tryReserve(fakeTaskId, "test", memoryPool.getMaxBytes())); + assertThat(memoryPool.tryReserve(fakeTaskId, "test", memoryPool.getMaxBytes())).isTrue(); } int queries = 2; @@ -158,10 +153,10 @@ public void testOutOfMemoryKiller() for (TestingTrinoServer server : queryRunner.getServers()) { MemoryPool pool = server.getLocalMemoryManager().getMemoryPool(); - assertTrue(pool.getReservedBytes() > 0); + assertThat(pool.getReservedBytes() > 0).isTrue(); // Free up the entire pool pool.free(fakeTaskId, "test", pool.getMaxBytes()); - assertTrue(pool.getFreeBytes() > 0); + assertThat(pool.getFreeBytes() > 0).isTrue(); } assertThatThrownBy(() -> { @@ -181,10 +176,12 @@ private void waitForQueryToBeKilled(DistributedQueryRunner queryRunner) boolean hasRunningQuery = false; for (BasicQueryInfo info : queryRunner.getCoordinator().getQueryManager().getQueries()) { if (info.getState() == FAILED) { - assertEquals(info.getErrorCode(), CLUSTER_OUT_OF_MEMORY.toErrorCode()); + assertThat(info.getErrorCode()).isEqualTo(CLUSTER_OUT_OF_MEMORY.toErrorCode()); return; } - assertNull(info.getErrorCode(), "errorCode unexpectedly present for " + info); + assertThat(info.getErrorCode()) + .describedAs("errorCode unexpectedly present for " + info) + .isNull(); if (!info.getState().isDone()) { hasRunningQuery = true; } @@ -212,13 +209,13 @@ private void testNoLeak(@Language("SQL") String query) executor.submit(() -> queryRunner.execute(query)).get(); for (BasicQueryInfo info : queryRunner.getCoordinator().getQueryManager().getQueries()) { - assertEquals(info.getState(), FINISHED); + assertThat(info.getState()).isEqualTo(FINISHED); } // Make sure we didn't leak any memory on the workers for (TestingTrinoServer worker : queryRunner.getServers()) { MemoryPool pool = worker.getLocalMemoryManager().getMemoryPool(); - assertEquals(pool.getMaxBytes(), pool.getFreeBytes()); + assertThat(pool.getMaxBytes()).isEqualTo(pool.getFreeBytes()); } } } @@ -235,7 +232,7 @@ public void testClusterPools() TaskId fakeTaskId = new TaskId(new StageId("fake", 0), 0, 0); for (TestingTrinoServer server : queryRunner.getServers()) { MemoryPool pool = server.getLocalMemoryManager().getMemoryPool(); - assertTrue(pool.tryReserve(fakeTaskId, "test", pool.getMaxBytes())); + assertThat(pool.tryReserve(fakeTaskId, "test", pool.getMaxBytes())).isTrue(); } List> queryFutures = new ArrayList<>(); @@ -247,7 +244,7 @@ public void testClusterPools() ClusterMemoryManager memoryManager = queryRunner.getCoordinator().getClusterMemoryManager(); ClusterMemoryPool clusterPool = memoryManager.getPool(); - assertNotNull(clusterPool); + assertThat(clusterPool).isNotNull(); // Wait for the pools to become blocked while (clusterPool.getBlockedNodes() != 2) { @@ -263,14 +260,14 @@ public void testClusterPools() // Make sure the queries are blocked for (BasicQueryInfo info : currentQueryInfos) { - assertFalse(info.getState().isDone()); + assertThat(info.getState().isDone()).isFalse(); } while (!currentQueryInfos.stream().allMatch(TestMemoryManager::isBlockedWaitingForMemory)) { MILLISECONDS.sleep(10); currentQueryInfos = queryRunner.getCoordinator().getQueryManager().getQueries(); for (BasicQueryInfo info : currentQueryInfos) { - assertFalse(info.getState().isDone()); + assertThat(info.getState().isDone()).isFalse(); } } @@ -279,7 +276,7 @@ public void testClusterPools() MemoryPool pool = server.getLocalMemoryManager().getMemoryPool(); // Free up the entire pool pool.free(fakeTaskId, "test", pool.getMaxBytes()); - assertTrue(pool.getFreeBytes() > 0); + assertThat(pool.getFreeBytes() > 0).isTrue(); } // Make sure both queries finish now that there's memory free in the memory pool. @@ -288,13 +285,13 @@ public void testClusterPools() } for (BasicQueryInfo info : queryRunner.getCoordinator().getQueryManager().getQueries()) { - assertEquals(info.getState(), FINISHED); + assertThat(info.getState()).isEqualTo(FINISHED); } // Make sure we didn't leak any memory on the workers for (TestingTrinoServer worker : queryRunner.getServers()) { MemoryPool pool = worker.getLocalMemoryManager().getMemoryPool(); - assertEquals(pool.getMaxBytes(), pool.getFreeBytes()); + assertThat(pool.getMaxBytes()).isEqualTo(pool.getFreeBytes()); } } } diff --git a/testing/trino-tests/src/test/java/io/trino/tests/TestGracefulShutdown.java b/testing/trino-tests/src/test/java/io/trino/tests/TestGracefulShutdown.java index a807197f8ad1..9fbf1065fc93 100644 --- a/testing/trino-tests/src/test/java/io/trino/tests/TestGracefulShutdown.java +++ b/testing/trino-tests/src/test/java/io/trino/tests/TestGracefulShutdown.java @@ -42,11 +42,10 @@ import static io.trino.testing.TestingSession.testSessionBuilder; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -112,12 +111,12 @@ public void testShutdown() List queryInfos = queryRunner.getCoordinator().getQueryManager().getQueries(); for (BasicQueryInfo info : queryInfos) { - assertEquals(info.getState(), FINISHED); + assertThat(info.getState()).isEqualTo(FINISHED); } TestShutdownAction shutdownAction = (TestShutdownAction) worker.getShutdownAction(); shutdownAction.waitForShutdownComplete(SHUTDOWN_TIMEOUT_MILLIS); - assertTrue(shutdownAction.isWorkerShutdown()); + assertThat(shutdownAction.isWorkerShutdown()).isTrue(); } } diff --git a/testing/trino-tests/src/test/java/io/trino/tests/TestMetadataManager.java b/testing/trino-tests/src/test/java/io/trino/tests/TestMetadataManager.java index e536af92bfca..c40dec07c4c8 100644 --- a/testing/trino-tests/src/test/java/io/trino/tests/TestMetadataManager.java +++ b/testing/trino-tests/src/test/java/io/trino/tests/TestMetadataManager.java @@ -51,7 +51,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; /** * This is integration / unit test suite. @@ -111,7 +110,7 @@ public void testMetadataIsClearedAfterQueryFinished() @Language("SQL") String sql = "SELECT * FROM nation"; queryRunner.execute(sql); - assertEquals(metadataManager.getActiveQueryIds().size(), 0); + assertThat(metadataManager.getActiveQueryIds().size()).isEqualTo(0); } @Test @@ -122,7 +121,7 @@ public void testMetadataIsClearedAfterQueryFailed() .isInstanceOf(RuntimeException.class) .hasMessage("Division by zero"); - assertEquals(metadataManager.getActiveQueryIds().size(), 0); + assertThat(metadataManager.getActiveQueryIds().size()).isEqualTo(0); } @Test @@ -155,7 +154,7 @@ public void testMetadataIsClearedAfterQueryCanceled() while (true) { BasicQueryInfo queryInfo = dispatchManager.getQueryInfo(queryId); if (queryInfo.getState().isDone()) { - assertEquals(queryInfo.getState(), FAILED); + assertThat(queryInfo.getState()).isEqualTo(FAILED); throw dispatchManager.getDispatchInfo(queryId).get().getFailureInfo().get().toException(); } if (queryInfo.getState() == RUNNING) { @@ -166,7 +165,7 @@ public void testMetadataIsClearedAfterQueryCanceled() // cancel query dispatchManager.cancelQuery(queryId); - assertEquals(metadataManager.getActiveQueryIds().size(), 0); + assertThat(metadataManager.getActiveQueryIds().size()).isEqualTo(0); } @Test @@ -177,7 +176,7 @@ public void testUpperCaseSchemaIsChangedToLowerCase() TEST_SESSION, transactionSession -> { List expectedSchemas = ImmutableList.of("information_schema", "upper_case_schema"); - assertEquals(queryRunner.getMetadata().listSchemaNames(transactionSession, "upper_case_schema_catalog"), expectedSchemas); + assertThat(queryRunner.getMetadata().listSchemaNames(transactionSession, "upper_case_schema_catalog")).isEqualTo(expectedSchemas); return null; }); } diff --git a/testing/trino-tests/src/test/java/io/trino/tests/TestMinWorkerRequirement.java b/testing/trino-tests/src/test/java/io/trino/tests/TestMinWorkerRequirement.java index 03ab4762263c..8dc4d9add6a5 100644 --- a/testing/trino-tests/src/test/java/io/trino/tests/TestMinWorkerRequirement.java +++ b/testing/trino-tests/src/test/java/io/trino/tests/TestMinWorkerRequirement.java @@ -31,11 +31,9 @@ import static java.util.concurrent.Executors.newFixedThreadPool; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Fail.fail; // run single threaded to avoid creating multiple query runners at once @Test(singleThreaded = true) @@ -108,10 +106,10 @@ public void testInsufficientWorkerNodesAfterDrop() .setNodeCount(4) .build()) { queryRunner.execute("SELECT COUNT(*) from lineitem"); - assertEquals(queryRunner.getCoordinator().refreshNodes().getActiveNodes().size(), 4); + assertThat(queryRunner.getCoordinator().refreshNodes().getActiveNodes().size()).isEqualTo(4); queryRunner.getServers().get(0).close(); - assertEquals(queryRunner.getCoordinator().refreshNodes().getActiveNodes().size(), 3); + assertThat(queryRunner.getCoordinator().refreshNodes().getActiveNodes().size()).isEqualTo(3); assertThatThrownBy(() -> queryRunner.execute("SELECT COUNT(*) from lineitem")) .isInstanceOf(RuntimeException.class) .hasMessage("Insufficient active worker nodes. Waited 1.00ns for at least 4 workers, but only 3 workers are active"); @@ -169,7 +167,7 @@ public void testRequiredWorkerNodesSessionOverride() // After adding 2 nodes, query should run queryRunner.addServers(2); - assertEquals(queryRunner.getCoordinator().refreshNodes().getActiveNodes().size(), 6); + assertThat(queryRunner.getCoordinator().refreshNodes().getActiveNodes().size()).isEqualTo(6); queryRunner.execute(require6Workers, "SELECT COUNT(*) from lineitem"); } } @@ -199,32 +197,32 @@ public void testMultipleRequiredWorkerNodesSessionOverride() MILLISECONDS.sleep(1000); // None of the queries should run - assertFalse(queryFuture1.isDone()); - assertFalse(queryFuture2.isDone()); - assertFalse(queryFuture3.isDone()); + assertThat(queryFuture1.isDone()).isFalse(); + assertThat(queryFuture2.isDone()).isFalse(); + assertThat(queryFuture3.isDone()).isFalse(); queryRunner.addServers(1); - assertEquals(queryRunner.getCoordinator().refreshNodes().getActiveNodes().size(), 2); + assertThat(queryRunner.getCoordinator().refreshNodes().getActiveNodes().size()).isEqualTo(2); // After adding 1 node, only 1st query should run MILLISECONDS.sleep(1000); - assertTrue(queryFuture1.get().getResult().getRowCount() > 0); + assertThat(queryFuture1.get().getResult().getRowCount() > 0).isTrue(); QueryManager queryManager = queryRunner.getCoordinator().getQueryManager(); QueryInfo completedQueryInfo = queryManager.getFullQueryInfo(queryFuture1.get().getQueryId()); - assertTrue(completedQueryInfo.getQueryStats().getResourceWaitingTime().roundTo(SECONDS) >= 1); + assertThat(completedQueryInfo.getQueryStats().getResourceWaitingTime().roundTo(SECONDS) >= 1).isTrue(); - assertFalse(queryFuture2.isDone()); - assertFalse(queryFuture3.isDone()); + assertThat(queryFuture2.isDone()).isFalse(); + assertThat(queryFuture3.isDone()).isFalse(); // After adding 2 nodes, 2nd and 3rd query should also run queryRunner.addServers(2); - assertEquals(queryRunner.getCoordinator().refreshNodes().getActiveNodes().size(), 4); - assertTrue(queryFuture2.get().getResult().getRowCount() > 0); + assertThat(queryRunner.getCoordinator().refreshNodes().getActiveNodes().size()).isEqualTo(4); + assertThat(queryFuture2.get().getResult().getRowCount() > 0).isTrue(); completedQueryInfo = queryManager.getFullQueryInfo(queryFuture2.get().getQueryId()); - assertTrue(completedQueryInfo.getQueryStats().getResourceWaitingTime().roundTo(SECONDS) >= 2); + assertThat(completedQueryInfo.getQueryStats().getResourceWaitingTime().roundTo(SECONDS) >= 2).isTrue(); - assertTrue(queryFuture3.get().getResult().getRowCount() > 0); + assertThat(queryFuture3.get().getResult().getRowCount() > 0).isTrue(); completedQueryInfo = queryManager.getFullQueryInfo(queryFuture3.get().getQueryId()); - assertTrue(completedQueryInfo.getQueryStats().getResourceWaitingTime().roundTo(SECONDS) >= 2); + assertThat(completedQueryInfo.getQueryStats().getResourceWaitingTime().roundTo(SECONDS) >= 2).isTrue(); } finally { service.shutdown(); diff --git a/testing/trino-tests/src/test/java/io/trino/tests/TestProcedureCall.java b/testing/trino-tests/src/test/java/io/trino/tests/TestProcedureCall.java index 6b48e1144a15..8854489ed17c 100644 --- a/testing/trino-tests/src/test/java/io/trino/tests/TestProcedureCall.java +++ b/testing/trino-tests/src/test/java/io/trino/tests/TestProcedureCall.java @@ -33,10 +33,9 @@ import static io.trino.testing.TestingSession.testSessionBuilder; import static java.lang.String.format; import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; @TestInstance(PER_CLASS) public class TestProcedureCall @@ -203,8 +202,8 @@ private void assertCall(@Language("SQL") String sql, String name, Object... argu { tester.reset(); assertUpdate(sql); - assertEquals(tester.getCalledName(), name); - assertEquals(tester.getCalledArguments(), list(arguments)); + assertThat(tester.getCalledName()).isEqualTo(name); + assertThat(tester.getCalledArguments()).isEqualTo(list(arguments)); } private void assertCallThrows(@Language("SQL") String sql, String name, String message) @@ -212,8 +211,8 @@ private void assertCallThrows(@Language("SQL") String sql, String name, String m tester.reset(); assertThatThrownBy(() -> assertUpdate(sql)) .isInstanceOfSatisfying(RuntimeException.class, e -> { - assertEquals(tester.getCalledName(), name); - assertEquals(tester.getCalledArguments(), list()); + assertThat(tester.getCalledName()).isEqualTo(name); + assertThat(tester.getCalledArguments()).isEqualTo(list()); }) .hasMessage(message); } @@ -222,7 +221,7 @@ private void assertCallFails(@Language("SQL") String sql, String message) { tester.reset(); assertThatThrownBy(() -> assertUpdate(sql)) - .isInstanceOfSatisfying(RuntimeException.class, e -> assertFalse(tester.wasCalled())) + .isInstanceOfSatisfying(RuntimeException.class, e -> assertThat(tester.wasCalled()).isFalse()) .hasMessage(message); } diff --git a/testing/trino-tests/src/test/java/io/trino/tests/TestQueryManager.java b/testing/trino-tests/src/test/java/io/trino/tests/TestQueryManager.java index 41cbb15e88c9..091a4a951b0f 100644 --- a/testing/trino-tests/src/test/java/io/trino/tests/TestQueryManager.java +++ b/testing/trino-tests/src/test/java/io/trino/tests/TestQueryManager.java @@ -46,11 +46,10 @@ import static io.trino.spi.StandardErrorCode.GENERIC_INTERNAL_ERROR; import static io.trino.testing.TestingSession.testSessionBuilder; import static java.util.Arrays.stream; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Fail.fail; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.fail; @TestInstance(PER_CLASS) @Execution(CONCURRENT) @@ -103,10 +102,10 @@ public void testFailQuery() QueryManager queryManager = queryRunner.getCoordinator().getQueryManager(); queryManager.failQuery(queryId, new TrinoException(GENERIC_INTERNAL_ERROR, "mock exception")); QueryInfo queryInfo = queryManager.getFullQueryInfo(queryId); - assertEquals(queryInfo.getState(), FAILED); - assertEquals(queryInfo.getErrorCode(), GENERIC_INTERNAL_ERROR.toErrorCode()); - assertNotNull(queryInfo.getFailureInfo()); - assertEquals(queryInfo.getFailureInfo().getMessage(), "mock exception"); + assertThat(queryInfo.getState()).isEqualTo(FAILED); + assertThat(queryInfo.getErrorCode()).isEqualTo(GENERIC_INTERNAL_ERROR.toErrorCode()); + assertThat(queryInfo.getFailureInfo()).isNotNull(); + assertThat(queryInfo.getFailureInfo().getMessage()).isEqualTo("mock exception"); } @Test @@ -119,8 +118,8 @@ public void testQueryCpuLimit() waitForQueryState(queryRunner, queryId, FAILED); QueryManager queryManager = queryRunner.getCoordinator().getQueryManager(); BasicQueryInfo queryInfo = queryManager.getQueryInfo(queryId); - assertEquals(queryInfo.getState(), FAILED); - assertEquals(queryInfo.getErrorCode(), EXCEEDED_CPU_LIMIT.toErrorCode()); + assertThat(queryInfo.getState()).isEqualTo(FAILED); + assertThat(queryInfo.getErrorCode()).isEqualTo(EXCEEDED_CPU_LIMIT.toErrorCode()); } } @@ -134,8 +133,8 @@ public void testQueryScanExceeded() waitForQueryState(queryRunner, queryId, FAILED); QueryManager queryManager = queryRunner.getCoordinator().getQueryManager(); BasicQueryInfo queryInfo = queryManager.getQueryInfo(queryId); - assertEquals(queryInfo.getState(), FAILED); - assertEquals(queryInfo.getErrorCode(), EXCEEDED_SCAN_LIMIT.toErrorCode()); + assertThat(queryInfo.getState()).isEqualTo(FAILED); + assertThat(queryInfo.getErrorCode()).isEqualTo(EXCEEDED_SCAN_LIMIT.toErrorCode()); } } @@ -157,8 +156,8 @@ public void testQueryScanExceededSession() waitForQueryState(queryRunner, queryId, FAILED); QueryManager queryManager = queryRunner.getCoordinator().getQueryManager(); BasicQueryInfo queryInfo = queryManager.getQueryInfo(queryId); - assertEquals(queryInfo.getState(), FAILED); - assertEquals(queryInfo.getErrorCode(), EXCEEDED_SCAN_LIMIT.toErrorCode()); + assertThat(queryInfo.getState()).isEqualTo(FAILED); + assertThat(queryInfo.getErrorCode()).isEqualTo(EXCEEDED_SCAN_LIMIT.toErrorCode()); } } } diff --git a/testing/trino-tests/src/test/java/io/trino/tests/TestQuerySpillLimits.java b/testing/trino-tests/src/test/java/io/trino/tests/TestQuerySpillLimits.java index a795b7ec2cee..b56a82185651 100644 --- a/testing/trino-tests/src/test/java/io/trino/tests/TestQuerySpillLimits.java +++ b/testing/trino-tests/src/test/java/io/trino/tests/TestQuerySpillLimits.java @@ -31,6 +31,7 @@ import static com.google.common.io.MoreFiles.deleteRecursively; import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; import static io.trino.testing.TestingSession.testSessionBuilder; +import static org.assertj.core.api.Assertions.assertThatThrownBy; @Test(singleThreaded = true) public class TestQuerySpillLimits @@ -59,17 +60,25 @@ public void tearDown() @Test(timeOut = 240_000, expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = ".*Query exceeded local spill limit of 10B") public void testMaxSpillPerNodeLimit() { - try (QueryRunner queryRunner = createLocalQueryRunner(new NodeSpillConfig().setMaxSpillPerNode(DataSize.succinctBytes(10)))) { - queryRunner.execute(queryRunner.getDefaultSession(), "SELECT COUNT(DISTINCT clerk) as count, orderdate FROM orders GROUP BY orderdate ORDER BY count, orderdate"); - } + assertThatThrownBy(() -> { + try (QueryRunner queryRunner = createLocalQueryRunner(new NodeSpillConfig().setMaxSpillPerNode(DataSize.succinctBytes(10)))) { + queryRunner.execute(queryRunner.getDefaultSession(), "SELECT COUNT(DISTINCT clerk) as count, orderdate FROM orders GROUP BY orderdate ORDER BY count, orderdate"); + } + }) + .isInstanceOf(RuntimeException.class) + .hasMessage(".*Query exceeded local spill limit of 10B"); } @Test(timeOut = 240_000, expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = ".*Query exceeded per-query local spill limit of 10B") public void testQueryMaxSpillPerNodeLimit() { - try (QueryRunner queryRunner = createLocalQueryRunner(new NodeSpillConfig().setQueryMaxSpillPerNode(DataSize.succinctBytes(10)))) { - queryRunner.execute(queryRunner.getDefaultSession(), "SELECT COUNT(DISTINCT clerk) as count, orderdate FROM orders GROUP BY orderdate ORDER BY count, orderdate"); - } + assertThatThrownBy(() -> { + try (QueryRunner queryRunner = createLocalQueryRunner(new NodeSpillConfig().setQueryMaxSpillPerNode(DataSize.succinctBytes(10)))) { + queryRunner.execute(queryRunner.getDefaultSession(), "SELECT COUNT(DISTINCT clerk) as count, orderdate FROM orders GROUP BY orderdate ORDER BY count, orderdate"); + } + }) + .isInstanceOf(RuntimeException.class) + .hasMessageMatching(".*Query exceeded per-query local spill limit of 10B"); } private LocalQueryRunner createLocalQueryRunner(NodeSpillConfig nodeSpillConfig) diff --git a/testing/trino-tests/src/test/java/io/trino/tests/TestServer.java b/testing/trino-tests/src/test/java/io/trino/tests/TestServer.java index 825fc08f3587..e0dbbb86d8be 100644 --- a/testing/trino-tests/src/test/java/io/trino/tests/TestServer.java +++ b/testing/trino-tests/src/test/java/io/trino/tests/TestServer.java @@ -81,10 +81,7 @@ import static java.util.stream.Collectors.joining; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.fail; +import static org.assertj.core.api.Fail.fail; public class TestServer { @@ -129,12 +126,12 @@ public void testInvalidSessionError() .collect(last()); QueryError queryError = queryResults.getError(); - assertNotNull(queryError); + assertThat(queryError).isNotNull(); TimeZoneNotSupportedException expected = new TimeZoneNotSupportedException(invalidTimeZone); - assertEquals(queryError.getErrorCode(), expected.getErrorCode().getCode()); - assertEquals(queryError.getErrorName(), expected.getErrorCode().getName()); - assertEquals(queryError.getErrorType(), expected.getErrorCode().getType().name()); - assertEquals(queryError.getMessage(), expected.getMessage()); + assertThat(queryError.getErrorCode()).isEqualTo(expected.getErrorCode().getCode()); + assertThat(queryError.getErrorName()).isEqualTo(expected.getErrorCode().getName()); + assertThat(queryError.getErrorType()).isEqualTo(expected.getErrorCode().getType().name()); + assertThat(queryError.getMessage()).isEqualTo(expected.getMessage()); } @Test @@ -153,14 +150,14 @@ public void testFirstResponseColumns() Optional data = queryResults.stream().filter(results -> results.getData() != null).findFirst(); - assertNull(first.getColumns()); - assertEquals(first.getStats().getState(), "QUEUED"); - assertNull(first.getData()); + assertThat(first.getColumns()).isNull(); + assertThat(first.getStats().getState()).isEqualTo("QUEUED"); + assertThat(first.getData()).isNull(); assertThat(last.getColumns()).hasSize(1); assertThat(last.getColumns().get(0).getName()).isEqualTo("Catalog"); assertThat(last.getColumns().get(0).getType()).isEqualTo("varchar(6)"); - assertEquals(last.getStats().getState(), "FINISHED"); + assertThat(last.getStats().getState()).isEqualTo("FINISHED"); assertThat(data).isPresent(); @@ -175,7 +172,7 @@ public void testServerStarts() prepareGet().setUri(server.resolve("/v1/info")).build(), createStatusResponseHandler()); - assertEquals(response.getStatusCode(), OK.getStatusCode()); + assertThat(response.getStatusCode()).isEqualTo(OK.getStatusCode()); } @Test @@ -192,7 +189,7 @@ public void testQuery() .addHeader(TRINO_HEADERS.requestSession(), JOIN_DISTRIBUTION_TYPE + "=partitioned," + MAX_HASH_PARTITION_COUNT + " = 43") .addHeader(TRINO_HEADERS.requestPreparedStatement(), "foo=select * from bar")) .map(JsonResponse::getValue) - .peek(result -> assertNull(result.getError())) + .peek(result -> assertThat(result.getError()).isNull()) .peek(results -> { if (results.getData() != null) { data.addAll(results.getData()); @@ -204,20 +201,20 @@ public void testQuery() BasicQueryInfo queryInfo = server.getQueryManager().getQueryInfo(new QueryId(queryResults.getId())); // verify session properties - assertEquals(queryInfo.getSession().getSystemProperties(), ImmutableMap.builder() + assertThat(queryInfo.getSession().getSystemProperties()).isEqualTo(ImmutableMap.builder() .put(QUERY_MAX_MEMORY, "1GB") .put(JOIN_DISTRIBUTION_TYPE, "partitioned") .put(MAX_HASH_PARTITION_COUNT, "43") .buildOrThrow()); // verify client info in session - assertEquals(queryInfo.getSession().getClientInfo().get(), "{\"clientVersion\":\"testVersion\"}"); + assertThat(queryInfo.getSession().getClientInfo().get()).isEqualTo("{\"clientVersion\":\"testVersion\"}"); // verify prepared statements - assertEquals(queryInfo.getSession().getPreparedStatements(), ImmutableMap.of("foo", "select * from bar")); + assertThat(queryInfo.getSession().getPreparedStatements()).isEqualTo(ImmutableMap.of("foo", "select * from bar")); List> rows = data.build(); - assertEquals(rows, ImmutableList.of(ImmutableList.of("memory"), ImmutableList.of("system"))); + assertThat(rows).isEqualTo(ImmutableList.of(ImmutableList.of("memory"), ImmutableList.of("system"))); } @Test @@ -226,9 +223,9 @@ public void testTransactionSupport() JsonResponse queryResults = postQuery(request -> request .setBodyGenerator(createStaticBodyGenerator("start transaction", UTF_8)) .setHeader(TRINO_HEADERS.requestTransactionId(), "none")) - .peek(result -> assertNull(result.getValue().getError())) + .peek(result -> assertThat(result.getValue().getError()).isNull()) .collect(last()); - assertNotNull(queryResults.getHeader(TRINO_HEADERS.responseStartedTransactionId())); + assertThat(queryResults.getHeader(TRINO_HEADERS.responseStartedTransactionId())).isNotNull(); } @Test @@ -240,8 +237,8 @@ public void testNoTransactionSupport() .findFirst() .orElseThrow(() -> new RuntimeException("Error expected")); - assertNull(queryResults.getNextUri()); - assertEquals(queryResults.getError().getErrorCode(), INCOMPATIBLE_CLIENT.toErrorCode().getCode()); + assertThat(queryResults.getNextUri()).isNull(); + assertThat(queryResults.getError().getErrorCode()).isEqualTo(INCOMPATIBLE_CLIENT.toErrorCode().getCode()); } @Test @@ -329,7 +326,7 @@ private void checkVersionOnError(String query, @Language("RegExp") String proofO .findFirst() .orElseThrow(() -> new RuntimeException("Error expected")); - assertNull(queryResults.getNextUri()); + assertThat(queryResults.getNextUri()).isNull(); QueryError queryError = queryResults.getError(); String stackTrace = getStackTraceAsString(queryError.getFailureInfo().toException()); assertThat(stackTrace).containsPattern(proofOfOrigin); @@ -350,8 +347,12 @@ public void testStatusPing() .setFollowRedirects(false) .build(); StatusResponse response = client.execute(request, createStatusResponseHandler()); - assertEquals(response.getStatusCode(), OK.getStatusCode(), "Status code"); - assertEquals(response.getHeader(CONTENT_TYPE), APPLICATION_JSON, "Content Type"); + assertThat(response.getStatusCode()) + .describedAs("Status code") + .isEqualTo(OK.getStatusCode()); + assertThat(response.getHeader(CONTENT_TYPE)) + .describedAs("Content Type") + .isEqualTo(APPLICATION_JSON); } @Test @@ -362,8 +363,12 @@ public void testRedirectToUi() .setFollowRedirects(false) .build(); StatusResponse response = client.execute(request, createStatusResponseHandler()); - assertEquals(response.getStatusCode(), SEE_OTHER.getStatusCode(), "Status code"); - assertEquals(response.getHeader("Location"), server.getBaseUrl() + "/ui/", "Location"); + assertThat(response.getStatusCode()) + .describedAs("Status code") + .isEqualTo(SEE_OTHER.getStatusCode()); + assertThat(response.getHeader("Location")) + .describedAs("Location") + .isEqualTo(server.getBaseUrl() + "/ui/"); // behind a proxy request = prepareGet() @@ -374,8 +379,12 @@ public void testRedirectToUi() .setFollowRedirects(false) .build(); response = client.execute(request, createStatusResponseHandler()); - assertEquals(response.getStatusCode(), SEE_OTHER.getStatusCode(), "Status code"); - assertEquals(response.getHeader("Location"), "https://my-load-balancer.local/ui/", "Location"); + assertThat(response.getStatusCode()) + .describedAs("Status code") + .isEqualTo(SEE_OTHER.getStatusCode()); + assertThat(response.getHeader("Location")) + .describedAs("Location") + .isEqualTo("https://my-load-balancer.local/ui/"); } private Stream> postQuery(Function requestConfigurer) diff --git a/testing/trino-tests/src/test/java/io/trino/tests/TestVerifyTrinoTestsTestSetup.java b/testing/trino-tests/src/test/java/io/trino/tests/TestVerifyTrinoTestsTestSetup.java index 6a890243f18a..a47c3d36388e 100644 --- a/testing/trino-tests/src/test/java/io/trino/tests/TestVerifyTrinoTestsTestSetup.java +++ b/testing/trino-tests/src/test/java/io/trino/tests/TestVerifyTrinoTestsTestSetup.java @@ -18,7 +18,7 @@ import java.time.ZoneId; import java.util.Locale; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestVerifyTrinoTestsTestSetup { @@ -26,13 +26,13 @@ public class TestVerifyTrinoTestsTestSetup public void testJvmZone() { // Ensure that the zone defined in the POM is correctly set in the test JVM - assertEquals(ZoneId.systemDefault().getId(), "America/Bahia_Banderas"); + assertThat(ZoneId.systemDefault().getId()).isEqualTo("America/Bahia_Banderas"); } @Test public void testJvmLocale() { // Ensure that locale defined in the POM is correctly set in the test JVM - assertEquals(Locale.getDefault(), Locale.US); + assertThat(Locale.getDefault()).isEqualTo(Locale.US); } } diff --git a/testing/trino-tests/src/test/java/io/trino/tests/tpch/TestTpchConnectorTest.java b/testing/trino-tests/src/test/java/io/trino/tests/tpch/TestTpchConnectorTest.java index c843f28cb96f..a95de36f9b95 100644 --- a/testing/trino-tests/src/test/java/io/trino/tests/tpch/TestTpchConnectorTest.java +++ b/testing/trino-tests/src/test/java/io/trino/tests/tpch/TestTpchConnectorTest.java @@ -37,7 +37,6 @@ import static io.trino.sql.planner.planprinter.IoPlanPrinter.FormattedMarker.Bound.EXACTLY; import static io.trino.testing.TestingSession.testSessionBuilder; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; public class TestTpchConnectorTest extends BaseConnectorTest @@ -108,9 +107,7 @@ public void testIoExplain() objectMapperProvider.setJsonDeserializers(ImmutableMap.of(Type.class, new TypeDeserializer(getQueryRunner().getTypeManager()))); JsonCodec codec = new JsonCodecFactory(objectMapperProvider).jsonCodec(IoPlanPrinter.IoPlan.class); - assertEquals( - codec.fromJson((String) getOnlyElement(result.getOnlyColumnAsSet())), - new IoPlanPrinter.IoPlan(ImmutableSet.of(input), Optional.empty(), totalEstimate)); + assertThat(codec.fromJson((String) getOnlyElement(result.getOnlyColumnAsSet()))).isEqualTo(new IoPlanPrinter.IoPlan(ImmutableSet.of(input), Optional.empty(), totalEstimate)); } @Test diff --git a/testing/trino-tests/src/test/java/io/trino/tests/tpch/TestTpchTableScanRedirection.java b/testing/trino-tests/src/test/java/io/trino/tests/tpch/TestTpchTableScanRedirection.java index 7a8abdb94886..10dce9b7b70f 100644 --- a/testing/trino-tests/src/test/java/io/trino/tests/tpch/TestTpchTableScanRedirection.java +++ b/testing/trino-tests/src/test/java/io/trino/tests/tpch/TestTpchTableScanRedirection.java @@ -20,7 +20,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; public class TestTpchTableScanRedirection extends AbstractTestQueryFramework @@ -51,7 +51,7 @@ public void testTableScanRedirection() // F | 7304 assertUpdate("CREATE TABLE memory.test.orders AS SELECT * FROM tpch_data_load.tiny.orders WHERE orderstatus IN ('O', 'P')", 7696L); // row count of 7333L verifies that filter was coorectly re-materialized during redirection and that redirection has taken place - assertEquals(computeActual("SELECT * FROM tpch.tiny.orders WHERE orderstatus IN ('O', 'F')").getRowCount(), 7333L); + assertThat(computeActual("SELECT * FROM tpch.tiny.orders WHERE orderstatus IN ('O', 'F')").getRowCount()).isEqualTo(7333L); } @Test From 031bd9f6048799052eee18d328550b7a3dbff8b0 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Fri, 17 Nov 2023 14:05:08 -0800 Subject: [PATCH 341/587] Migrate tests to JUnit --- .../TestAggregationLoopBuilder.java | 12 +- ...AuthenticationFilterWithRefreshTokens.java | 16 +- .../trino/spiller/TestBinaryFileSpiller.java | 21 +- .../sql/planner/assertions/BasePlanTest.java | 4 - .../planner/optimizations/BaseTestUnion.java | 244 ++++++++++++++++++ .../sql/planner/optimizations/TestUnion.java | 230 +---------------- .../TestUnionWithReplicatedJoin.java | 2 +- lib/trino-matching/pom.xml | 6 - lib/trino-orc/pom.xml | 19 -- .../src/test/java/io/trino/orc/OrcTester.java | 4 +- .../AbstractStatisticsBuilderTest.java | 2 +- .../TestBinaryStatisticsBuilder.java | 2 +- .../TestBooleanStatisticsBuilder.java | 2 +- .../statistics/TestDateStatistics.java | 2 +- .../statistics/TestDateStatisticsBuilder.java | 2 +- .../statistics/TestDecimalStatistics.java | 2 +- .../statistics/TestDoubleStatistics.java | 2 +- .../TestDoubleStatisticsBuilder.java | 2 +- .../statistics/TestIntegerStatistics.java | 2 +- .../TestIntegerStatisticsBuilder.java | 2 +- .../TestLongDecimalStatisticsBuilder.java | 2 +- .../TestShortDecimalStatisticsBuilder.java | 2 +- .../statistics/TestStringStatistics.java | 2 +- .../statistics/TestTimestampStatistics.java | 2 +- .../TestTimestampStatisticsBuilder.java | 2 +- .../hive/AbstractTestHiveFileSystemAbfs.java | 2 +- .../plugin/hive/TestHiveFileSystemAdl.java | 2 +- .../plugin/hive/TestHiveFileSystemS3.java | 2 +- .../plugin/hive/TestHiveFileSystemWasb.java | 2 +- ...TestTrinoS3FileSystemAccessOperations.java | 13 +- plugin/trino-memory/pom.xml | 6 - ...eFaultTolerantExecutionOrderByQueries.java | 2 - ...veFaultTolerantExecutionWindowQueries.java | 2 - .../execution/TestEventListenerBasic.java | 13 +- .../io/trino/execution/TestRevokeOnTable.java | 16 +- .../tests/TestLocalEngineOnlyQueries.java | 9 +- .../trino/tests/TestMinWorkerRequirement.java | 104 ++++---- .../trino/tests/TestQueryPlanDeterminism.java | 5 +- .../tests/TestQuerySerializationFailures.java | 2 +- .../io/trino/tests/TestQuerySpillLimits.java | 24 +- 40 files changed, 417 insertions(+), 375 deletions(-) create mode 100644 core/trino-main/src/test/java/io/trino/sql/planner/optimizations/BaseTestUnion.java diff --git a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestAggregationLoopBuilder.java b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestAggregationLoopBuilder.java index 53d55a59df6d..333384277129 100644 --- a/core/trino-main/src/test/java/io/trino/operator/aggregation/TestAggregationLoopBuilder.java +++ b/core/trino-main/src/test/java/io/trino/operator/aggregation/TestAggregationLoopBuilder.java @@ -24,8 +24,10 @@ import io.trino.spi.function.BlockPosition; import io.trino.spi.function.SqlNullable; import io.trino.spi.function.SqlType; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.lang.invoke.MethodHandle; import java.util.ArrayList; @@ -36,7 +38,11 @@ import static java.lang.invoke.MethodHandles.lookup; import static java.lang.invoke.MethodType.methodType; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestAggregationLoopBuilder { private static final MethodHandle INPUT_FUNCTION; @@ -59,7 +65,7 @@ public class TestAggregationLoopBuilder private List keyBlocks; private List valueBlocks; - @BeforeClass + @BeforeAll public void setUp() throws ReflectiveOperationException { diff --git a/core/trino-main/src/test/java/io/trino/server/security/oauth2/TestOAuth2WebUiAuthenticationFilterWithRefreshTokens.java b/core/trino-main/src/test/java/io/trino/server/security/oauth2/TestOAuth2WebUiAuthenticationFilterWithRefreshTokens.java index 9509a220445a..b17eaf002226 100644 --- a/core/trino-main/src/test/java/io/trino/server/security/oauth2/TestOAuth2WebUiAuthenticationFilterWithRefreshTokens.java +++ b/core/trino-main/src/test/java/io/trino/server/security/oauth2/TestOAuth2WebUiAuthenticationFilterWithRefreshTokens.java @@ -30,9 +30,11 @@ import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.net.CookieManager; import java.net.CookieStore; @@ -48,7 +50,11 @@ import static jakarta.servlet.http.HttpServletResponse.SC_OK; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestOAuth2WebUiAuthenticationFilterWithRefreshTokens { protected static final Duration TTL_ACCESS_TOKEN_IN_SECONDS = Duration.ofSeconds(5); @@ -65,7 +71,7 @@ public class TestOAuth2WebUiAuthenticationFilterWithRefreshTokens private URI serverUri; private URI uiUri; - @BeforeClass + @BeforeAll public void setup() throws Exception { @@ -121,7 +127,7 @@ public void setup() serverUri + "/ui/logout/logout.html"); } - @AfterClass(alwaysRun = true) + @AfterAll public void tearDown() throws Exception { diff --git a/core/trino-main/src/test/java/io/trino/spiller/TestBinaryFileSpiller.java b/core/trino-main/src/test/java/io/trino/spiller/TestBinaryFileSpiller.java index bab545a51306..ca723183c382 100644 --- a/core/trino-main/src/test/java/io/trino/spiller/TestBinaryFileSpiller.java +++ b/core/trino-main/src/test/java/io/trino/spiller/TestBinaryFileSpiller.java @@ -25,10 +25,12 @@ import io.trino.spi.block.BlockEncodingSerde; import io.trino.spi.block.TestingBlockEncodingSerde; import io.trino.spi.type.Type; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.io.IOException; @@ -47,8 +49,11 @@ import static io.trino.spi.type.VarbinaryType.VARBINARY; import static io.trino.spi.type.VarcharType.VARCHAR; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -@Test(singleThreaded = true) +@TestInstance(PER_CLASS) +@Execution(SAME_THREAD) public class TestBinaryFileSpiller { private static final List TYPES = ImmutableList.of(BIGINT, VARCHAR, DOUBLE, BIGINT); @@ -60,14 +65,14 @@ public class TestBinaryFileSpiller private PageSerializer serializer; private AggregatedMemoryContext memoryContext; - @BeforeClass(alwaysRun = true) + @BeforeAll public void setUpClass() throws IOException { spillPath = Files.createTempDirectory("tmp").toFile(); } - @BeforeMethod + @BeforeEach public void setUp() { spillerStats = new SpillerStats(); @@ -83,7 +88,7 @@ public void setUp() memoryContext = newSimpleAggregatedMemoryContext(); } - @AfterMethod(alwaysRun = true) + @AfterEach public void tearDown() throws Exception { diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/assertions/BasePlanTest.java b/core/trino-main/src/test/java/io/trino/sql/planner/assertions/BasePlanTest.java index 1d86481b4487..2715d24c0819 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/assertions/BasePlanTest.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/assertions/BasePlanTest.java @@ -34,8 +34,6 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.parallel.Execution; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; import java.util.List; import java.util.Map; @@ -91,14 +89,12 @@ protected LocalQueryRunner createLocalQueryRunner() } @BeforeAll - @BeforeClass public final void initPlanTest() { this.queryRunner = createLocalQueryRunner(); } @AfterAll - @AfterClass(alwaysRun = true) public final void destroyPlanTest() { closeAllRuntimeException(queryRunner); diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/BaseTestUnion.java b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/BaseTestUnion.java new file mode 100644 index 000000000000..0dd1135c30d5 --- /dev/null +++ b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/BaseTestUnion.java @@ -0,0 +1,244 @@ +/* + * 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 io.trino.sql.planner.optimizations; + +import com.google.common.collect.Iterables; +import io.trino.sql.planner.Plan; +import io.trino.sql.planner.assertions.BasePlanTest; +import io.trino.sql.planner.plan.AggregationNode; +import io.trino.sql.planner.plan.ExchangeNode; +import io.trino.sql.planner.plan.JoinNode; +import io.trino.sql.planner.plan.PlanNode; +import io.trino.sql.planner.plan.TopNNode; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static io.trino.sql.planner.LogicalPlanner.Stage.OPTIMIZED_AND_VALIDATED; +import static io.trino.sql.planner.optimizations.PlanNodeSearcher.searchFrom; +import static io.trino.sql.planner.plan.ExchangeNode.Scope.REMOTE; +import static io.trino.sql.planner.plan.ExchangeNode.Type.GATHER; +import static io.trino.sql.planner.plan.ExchangeNode.Type.REPARTITION; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; + +abstract class BaseTestUnion + extends BasePlanTest +{ + protected BaseTestUnion(Map sessionProperties) + { + super(sessionProperties); + } + + @Test + public void testSimpleUnion() + { + Plan plan = plan( + "SELECT suppkey FROM supplier UNION ALL SELECT nationkey FROM nation", + OPTIMIZED_AND_VALIDATED, + false); + + List remotes = searchFrom(plan.getRoot()) + .where(BaseTestUnion::isRemoteExchange) + .findAll(); + + assertThat(remotes.size()) + .describedAs("There should be exactly one RemoteExchange") + .isEqualTo(1); + assertThat(((ExchangeNode) Iterables.getOnlyElement(remotes)).getType()).isEqualTo(GATHER); + assertPlanIsFullyDistributed(plan); + } + + @Test + public void testUnionUnderTopN() + { + Plan plan = plan( + "SELECT * FROM (" + + " SELECT regionkey FROM nation " + + " UNION ALL " + + " SELECT nationkey FROM nation" + + ") t(a) " + + "ORDER BY a LIMIT 1", + OPTIMIZED_AND_VALIDATED, + false); + + List remotes = searchFrom(plan.getRoot()) + .where(BaseTestUnion::isRemoteExchange) + .findAll(); + + assertThat(remotes.size()) + .describedAs("There should be exactly one RemoteExchange") + .isEqualTo(1); + assertThat(((ExchangeNode) Iterables.getOnlyElement(remotes)).getType()).isEqualTo(GATHER); + + int numberOfpartialTopN = searchFrom(plan.getRoot()) + .where(planNode -> planNode instanceof TopNNode && ((TopNNode) planNode).getStep() == TopNNode.Step.PARTIAL) + .count(); + assertThat(numberOfpartialTopN) + .describedAs("There should be exactly two partial TopN nodes") + .isEqualTo(2); + assertPlanIsFullyDistributed(plan); + } + + @Test + public void testUnionOverSingleNodeAggregationAndUnion() + { + Plan plan = plan( + "SELECT count(*) FROM (" + + "SELECT 1 FROM nation GROUP BY regionkey " + + "UNION ALL (" + + " SELECT 1 FROM nation " + + " UNION ALL " + + " SELECT 1 FROM nation))", + OPTIMIZED_AND_VALIDATED, + false); + + List remotes = searchFrom(plan.getRoot()) + .where(BaseTestUnion::isRemoteExchange) + .findAll(); + + assertThat(remotes.size()) + .describedAs("There should be exactly two RemoteExchanges") + .isEqualTo(2); + assertThat(((ExchangeNode) remotes.get(0)).getType()).isEqualTo(GATHER); + assertThat(((ExchangeNode) remotes.get(1)).getType()).isEqualTo(REPARTITION); + } + + @Test + public void testPartialAggregationsWithUnion() + { + Plan plan = plan( + "SELECT orderstatus, sum(orderkey) FROM (SELECT orderkey, orderstatus FROM orders UNION ALL SELECT orderkey, orderstatus FROM orders) x GROUP BY (orderstatus)", + OPTIMIZED_AND_VALIDATED, + false); + assertAtMostOneAggregationBetweenRemoteExchanges(plan); + assertPlanIsFullyDistributed(plan); + } + + @Test + public void testPartialRollupAggregationsWithUnion() + { + Plan plan = plan( + "SELECT orderstatus, sum(orderkey) FROM (SELECT orderkey, orderstatus FROM orders UNION ALL SELECT orderkey, orderstatus FROM orders) x GROUP BY ROLLUP (orderstatus)", + OPTIMIZED_AND_VALIDATED, + false); + assertAtMostOneAggregationBetweenRemoteExchanges(plan); + assertPlanIsFullyDistributed(plan); + } + + @Test + public void testAggregationWithUnionAndValues() + { + Plan plan = plan( + "SELECT regionkey, count(*) FROM (SELECT regionkey FROM nation UNION ALL SELECT * FROM (VALUES 2, 100) t(regionkey)) GROUP BY regionkey", + OPTIMIZED_AND_VALIDATED, + false); + assertAtMostOneAggregationBetweenRemoteExchanges(plan); + // TODO: Enable this check once distributed UNION can handle both partitioned and single node sources at the same time + //assertPlanIsFullyDistributed(plan); + } + + @Test + public void testUnionOnProbeSide() + { + Plan plan = plan( + "SELECT * FROM (SELECT * FROM nation UNION ALL SELECT * from nation) n, region r WHERE n.regionkey=r.regionkey", + OPTIMIZED_AND_VALIDATED, + false); + + assertPlanIsFullyDistributed(plan); + } + + private void assertPlanIsFullyDistributed(Plan plan) + { + int numberOfGathers = searchFrom(plan.getRoot()) + .where(BaseTestUnion::isRemoteGatheringExchange) + .findAll() + .size(); + + if (numberOfGathers == 0) { + // there are no "gather" nodes, so the plan is expected to be fully distributed + return; + } + + assertThat(searchFrom(plan.getRoot()) + .recurseOnlyWhen(BaseTestUnion::isNotRemoteGatheringExchange) + .findAll() + .stream() + .noneMatch(this::shouldBeDistributed)) + .describedAs("There is a node that should be distributed between output and first REMOTE GATHER ExchangeNode") + .isTrue(); + + assertThat(numberOfGathers) + .describedAs("Only a single REMOTE GATHER was expected") + .isEqualTo(1); + } + + private boolean shouldBeDistributed(PlanNode planNode) + { + if (planNode instanceof JoinNode) { + return true; + } + if (planNode instanceof AggregationNode) { + // TODO: differentiate aggregation with empty grouping set + return true; + } + if (planNode instanceof TopNNode) { + return ((TopNNode) planNode).getStep() == TopNNode.Step.PARTIAL; + } + return false; + } + + private static void assertAtMostOneAggregationBetweenRemoteExchanges(Plan plan) + { + List fragments = searchFrom(plan.getRoot()) + .where(BaseTestUnion::isRemoteExchange) + .findAll() + .stream() + .flatMap(exchangeNode -> exchangeNode.getSources().stream()) + .collect(toList()); + + for (PlanNode fragment : fragments) { + List aggregations = searchFrom(fragment) + .where(AggregationNode.class::isInstance) + .recurseOnlyWhen(BaseTestUnion::isNotRemoteExchange) + .findAll(); + + assertThat(aggregations.size() > 1) + .describedAs("More than a single AggregationNode between remote exchanges") + .isFalse(); + } + } + + private static boolean isNotRemoteGatheringExchange(PlanNode planNode) + { + return !isRemoteGatheringExchange(planNode); + } + + private static boolean isRemoteGatheringExchange(PlanNode planNode) + { + return isRemoteExchange(planNode) && ((ExchangeNode) planNode).getType() == GATHER; + } + + private static boolean isNotRemoteExchange(PlanNode planNode) + { + return !isRemoteExchange(planNode); + } + + private static boolean isRemoteExchange(PlanNode planNode) + { + return (planNode instanceof ExchangeNode) && ((ExchangeNode) planNode).getScope() == REMOTE; + } +} diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestUnion.java b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestUnion.java index e839ca3c7c9d..d97255b181ae 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestUnion.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestUnion.java @@ -13,237 +13,13 @@ */ package io.trino.sql.planner.optimizations; -import com.google.common.collect.Iterables; -import io.trino.sql.planner.Plan; -import io.trino.sql.planner.assertions.BasePlanTest; -import io.trino.sql.planner.plan.AggregationNode; -import io.trino.sql.planner.plan.ExchangeNode; -import io.trino.sql.planner.plan.JoinNode; -import io.trino.sql.planner.plan.PlanNode; -import io.trino.sql.planner.plan.TopNNode; -import org.testng.annotations.Test; - -import java.util.List; -import java.util.Map; - -import static io.trino.sql.planner.LogicalPlanner.Stage.OPTIMIZED_AND_VALIDATED; -import static io.trino.sql.planner.optimizations.PlanNodeSearcher.searchFrom; -import static io.trino.sql.planner.plan.ExchangeNode.Scope.REMOTE; -import static io.trino.sql.planner.plan.ExchangeNode.Type.GATHER; -import static io.trino.sql.planner.plan.ExchangeNode.Type.REPARTITION; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; +import com.google.common.collect.ImmutableMap; public class TestUnion - extends BasePlanTest + extends BaseTestUnion { public TestUnion() { - super(); - } - - public TestUnion(Map sessionProperties) - { - super(sessionProperties); - } - - @Test - public void testSimpleUnion() - { - Plan plan = plan( - "SELECT suppkey FROM supplier UNION ALL SELECT nationkey FROM nation", - OPTIMIZED_AND_VALIDATED, - false); - - List remotes = searchFrom(plan.getRoot()) - .where(TestUnion::isRemoteExchange) - .findAll(); - - assertThat(remotes.size()) - .describedAs("There should be exactly one RemoteExchange") - .isEqualTo(1); - assertThat(((ExchangeNode) Iterables.getOnlyElement(remotes)).getType()).isEqualTo(GATHER); - assertPlanIsFullyDistributed(plan); - } - - @Test - public void testUnionUnderTopN() - { - Plan plan = plan( - "SELECT * FROM (" + - " SELECT regionkey FROM nation " + - " UNION ALL " + - " SELECT nationkey FROM nation" + - ") t(a) " + - "ORDER BY a LIMIT 1", - OPTIMIZED_AND_VALIDATED, - false); - - List remotes = searchFrom(plan.getRoot()) - .where(TestUnion::isRemoteExchange) - .findAll(); - - assertThat(remotes.size()) - .describedAs("There should be exactly one RemoteExchange") - .isEqualTo(1); - assertThat(((ExchangeNode) Iterables.getOnlyElement(remotes)).getType()).isEqualTo(GATHER); - - int numberOfpartialTopN = searchFrom(plan.getRoot()) - .where(planNode -> planNode instanceof TopNNode && ((TopNNode) planNode).getStep() == TopNNode.Step.PARTIAL) - .count(); - assertThat(numberOfpartialTopN) - .describedAs("There should be exactly two partial TopN nodes") - .isEqualTo(2); - assertPlanIsFullyDistributed(plan); - } - - @Test - public void testUnionOverSingleNodeAggregationAndUnion() - { - Plan plan = plan( - "SELECT count(*) FROM (" + - "SELECT 1 FROM nation GROUP BY regionkey " + - "UNION ALL (" + - " SELECT 1 FROM nation " + - " UNION ALL " + - " SELECT 1 FROM nation))", - OPTIMIZED_AND_VALIDATED, - false); - - List remotes = searchFrom(plan.getRoot()) - .where(TestUnion::isRemoteExchange) - .findAll(); - - assertThat(remotes.size()) - .describedAs("There should be exactly two RemoteExchanges") - .isEqualTo(2); - assertThat(((ExchangeNode) remotes.get(0)).getType()).isEqualTo(GATHER); - assertThat(((ExchangeNode) remotes.get(1)).getType()).isEqualTo(REPARTITION); - } - - @Test - public void testPartialAggregationsWithUnion() - { - Plan plan = plan( - "SELECT orderstatus, sum(orderkey) FROM (SELECT orderkey, orderstatus FROM orders UNION ALL SELECT orderkey, orderstatus FROM orders) x GROUP BY (orderstatus)", - OPTIMIZED_AND_VALIDATED, - false); - assertAtMostOneAggregationBetweenRemoteExchanges(plan); - assertPlanIsFullyDistributed(plan); - } - - @Test - public void testPartialRollupAggregationsWithUnion() - { - Plan plan = plan( - "SELECT orderstatus, sum(orderkey) FROM (SELECT orderkey, orderstatus FROM orders UNION ALL SELECT orderkey, orderstatus FROM orders) x GROUP BY ROLLUP (orderstatus)", - OPTIMIZED_AND_VALIDATED, - false); - assertAtMostOneAggregationBetweenRemoteExchanges(plan); - assertPlanIsFullyDistributed(plan); - } - - @Test - public void testAggregationWithUnionAndValues() - { - Plan plan = plan( - "SELECT regionkey, count(*) FROM (SELECT regionkey FROM nation UNION ALL SELECT * FROM (VALUES 2, 100) t(regionkey)) GROUP BY regionkey", - OPTIMIZED_AND_VALIDATED, - false); - assertAtMostOneAggregationBetweenRemoteExchanges(plan); - // TODO: Enable this check once distributed UNION can handle both partitioned and single node sources at the same time - //assertPlanIsFullyDistributed(plan); - } - - @Test - public void testUnionOnProbeSide() - { - Plan plan = plan( - "SELECT * FROM (SELECT * FROM nation UNION ALL SELECT * from nation) n, region r WHERE n.regionkey=r.regionkey", - OPTIMIZED_AND_VALIDATED, - false); - - assertPlanIsFullyDistributed(plan); - } - - private void assertPlanIsFullyDistributed(Plan plan) - { - int numberOfGathers = searchFrom(plan.getRoot()) - .where(TestUnion::isRemoteGatheringExchange) - .findAll() - .size(); - - if (numberOfGathers == 0) { - // there are no "gather" nodes, so the plan is expected to be fully distributed - return; - } - - assertThat(searchFrom(plan.getRoot()) - .recurseOnlyWhen(TestUnion::isNotRemoteGatheringExchange) - .findAll() - .stream() - .noneMatch(this::shouldBeDistributed)) - .describedAs("There is a node that should be distributed between output and first REMOTE GATHER ExchangeNode") - .isTrue(); - - assertThat(numberOfGathers) - .describedAs("Only a single REMOTE GATHER was expected") - .isEqualTo(1); - } - - private boolean shouldBeDistributed(PlanNode planNode) - { - if (planNode instanceof JoinNode) { - return true; - } - if (planNode instanceof AggregationNode) { - // TODO: differentiate aggregation with empty grouping set - return true; - } - if (planNode instanceof TopNNode) { - return ((TopNNode) planNode).getStep() == TopNNode.Step.PARTIAL; - } - return false; - } - - private static void assertAtMostOneAggregationBetweenRemoteExchanges(Plan plan) - { - List fragments = searchFrom(plan.getRoot()) - .where(TestUnion::isRemoteExchange) - .findAll() - .stream() - .flatMap(exchangeNode -> exchangeNode.getSources().stream()) - .collect(toList()); - - for (PlanNode fragment : fragments) { - List aggregations = searchFrom(fragment) - .where(AggregationNode.class::isInstance) - .recurseOnlyWhen(TestUnion::isNotRemoteExchange) - .findAll(); - - assertThat(aggregations.size() > 1) - .describedAs("More than a single AggregationNode between remote exchanges") - .isFalse(); - } - } - - private static boolean isNotRemoteGatheringExchange(PlanNode planNode) - { - return !isRemoteGatheringExchange(planNode); - } - - private static boolean isRemoteGatheringExchange(PlanNode planNode) - { - return isRemoteExchange(planNode) && ((ExchangeNode) planNode).getType() == GATHER; - } - - private static boolean isNotRemoteExchange(PlanNode planNode) - { - return !isRemoteExchange(planNode); - } - - private static boolean isRemoteExchange(PlanNode planNode) - { - return (planNode instanceof ExchangeNode) && ((ExchangeNode) planNode).getScope() == REMOTE; + super(ImmutableMap.of()); } } diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestUnionWithReplicatedJoin.java b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestUnionWithReplicatedJoin.java index ecb8b74cbfae..586526932259 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestUnionWithReplicatedJoin.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestUnionWithReplicatedJoin.java @@ -19,7 +19,7 @@ import static io.trino.sql.planner.OptimizerConfig.JoinDistributionType.BROADCAST; public class TestUnionWithReplicatedJoin - extends TestUnion + extends BaseTestUnion { public TestUnionWithReplicatedJoin() { diff --git a/lib/trino-matching/pom.xml b/lib/trino-matching/pom.xml index d0cccac41602..d6c4649d9135 100644 --- a/lib/trino-matching/pom.xml +++ b/lib/trino-matching/pom.xml @@ -45,11 +45,5 @@ junit-jupiter-engine test - - - org.testng - testng - test - diff --git a/lib/trino-orc/pom.xml b/lib/trino-orc/pom.xml index 04f9293efcbf..299c25ca57d0 100644 --- a/lib/trino-orc/pom.xml +++ b/lib/trino-orc/pom.xml @@ -185,12 +185,6 @@ jmh-generator-annprocess test - - - org.testng - testng - test - @@ -205,19 +199,6 @@ **/TestFullOrcReader.java - - - - org.apache.maven.surefire - surefire-junit-platform - ${dep.plugin.surefire.version} - - - org.apache.maven.surefire - surefire-testng - ${dep.plugin.surefire.version} - - diff --git a/lib/trino-orc/src/test/java/io/trino/orc/OrcTester.java b/lib/trino-orc/src/test/java/io/trino/orc/OrcTester.java index 5803bdd684f8..8a02dcdb6709 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/OrcTester.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/OrcTester.java @@ -198,7 +198,7 @@ import static org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory.javaTimestampTZObjectInspector; import static org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory.getCharTypeInfo; import static org.assertj.core.api.Assertions.assertThat; -import static org.testng.Assert.assertEquals; +import static org.assertj.core.data.Offset.offset; public class OrcTester { @@ -602,7 +602,7 @@ else if (type.equals(DOUBLE)) { .isTrue(); } else { - assertEquals(actualDouble, expectedDouble, 0.001); + assertThat(actualDouble).isCloseTo(expectedDouble, offset(0.001)); } } else if (type.equals(UUID)) { diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/AbstractStatisticsBuilderTest.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/AbstractStatisticsBuilderTest.java index 8e81fb5d4a07..313996c19ae0 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/AbstractStatisticsBuilderTest.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/AbstractStatisticsBuilderTest.java @@ -14,7 +14,7 @@ package io.trino.orc.metadata.statistics; import com.google.common.collect.ImmutableList; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.Collections; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestBinaryStatisticsBuilder.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestBinaryStatisticsBuilder.java index 6982ac477f11..6b624015b625 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestBinaryStatisticsBuilder.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestBinaryStatisticsBuilder.java @@ -15,7 +15,7 @@ import com.google.common.collect.ImmutableList; import io.airlift.slice.Slice; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.List; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestBooleanStatisticsBuilder.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestBooleanStatisticsBuilder.java index 7b73da88209b..fa76d84a03c4 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestBooleanStatisticsBuilder.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestBooleanStatisticsBuilder.java @@ -14,7 +14,7 @@ package io.trino.orc.metadata.statistics; import com.google.common.collect.ImmutableList; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.List; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestDateStatistics.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestDateStatistics.java index d401cbad4e8f..f3a521e632ea 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestDateStatistics.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestDateStatistics.java @@ -13,7 +13,7 @@ */ package io.trino.orc.metadata.statistics; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.airlift.slice.SizeOf.instanceSize; import static java.lang.Integer.MAX_VALUE; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestDateStatisticsBuilder.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestDateStatisticsBuilder.java index 11f37a759f76..c90d6cdfac99 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestDateStatisticsBuilder.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestDateStatisticsBuilder.java @@ -17,7 +17,7 @@ import com.google.common.collect.DiscreteDomain; import com.google.common.collect.ImmutableList; import com.google.common.collect.Range; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.orc.metadata.statistics.AbstractStatisticsBuilderTest.StatisticsType.DATE; import static io.trino.orc.metadata.statistics.DateStatistics.DATE_VALUE_BYTES; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestDecimalStatistics.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestDecimalStatistics.java index 9084d7ca066b..4f3e921f2ffc 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestDecimalStatistics.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestDecimalStatistics.java @@ -13,7 +13,7 @@ */ package io.trino.orc.metadata.statistics; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.math.BigDecimal; import java.math.BigInteger; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestDoubleStatistics.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestDoubleStatistics.java index 4aaeea94c776..91174a6a4628 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestDoubleStatistics.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestDoubleStatistics.java @@ -13,7 +13,7 @@ */ package io.trino.orc.metadata.statistics; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.airlift.slice.SizeOf.instanceSize; import static java.lang.Double.NEGATIVE_INFINITY; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestDoubleStatisticsBuilder.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestDoubleStatisticsBuilder.java index 18b4e6358c00..f2a61acac277 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestDoubleStatisticsBuilder.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestDoubleStatisticsBuilder.java @@ -14,7 +14,7 @@ package io.trino.orc.metadata.statistics; import com.google.common.collect.ImmutableList; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.List; import java.util.stream.LongStream; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestIntegerStatistics.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestIntegerStatistics.java index d8f9d031a960..a1546f7cb278 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestIntegerStatistics.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestIntegerStatistics.java @@ -13,7 +13,7 @@ */ package io.trino.orc.metadata.statistics; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.airlift.slice.SizeOf.instanceSize; import static java.lang.Long.MAX_VALUE; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestIntegerStatisticsBuilder.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestIntegerStatisticsBuilder.java index 06c7b1e9c4e6..d38862a0aeac 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestIntegerStatisticsBuilder.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestIntegerStatisticsBuilder.java @@ -17,7 +17,7 @@ import com.google.common.collect.DiscreteDomain; import com.google.common.collect.ImmutableList; import com.google.common.collect.Range; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.List; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestLongDecimalStatisticsBuilder.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestLongDecimalStatisticsBuilder.java index 7d81c4a1578e..8121e751de01 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestLongDecimalStatisticsBuilder.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestLongDecimalStatisticsBuilder.java @@ -17,7 +17,7 @@ import com.google.common.collect.DiscreteDomain; import com.google.common.collect.ImmutableList; import com.google.common.collect.Range; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.math.BigDecimal; import java.util.List; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestShortDecimalStatisticsBuilder.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestShortDecimalStatisticsBuilder.java index 15252da314cd..a6e155005951 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestShortDecimalStatisticsBuilder.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestShortDecimalStatisticsBuilder.java @@ -17,7 +17,7 @@ import com.google.common.collect.DiscreteDomain; import com.google.common.collect.ImmutableList; import com.google.common.collect.Range; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.math.BigDecimal; import java.math.BigInteger; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestStringStatistics.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestStringStatistics.java index 40ba294eef1c..13c0dce8a540 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestStringStatistics.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestStringStatistics.java @@ -14,7 +14,7 @@ package io.trino.orc.metadata.statistics; import io.airlift.slice.Slice; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.airlift.slice.SizeOf.instanceSize; import static io.airlift.slice.Slices.EMPTY_SLICE; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestTimestampStatistics.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestTimestampStatistics.java index 6c4a183fbac6..818ba511742e 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestTimestampStatistics.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestTimestampStatistics.java @@ -13,7 +13,7 @@ */ package io.trino.orc.metadata.statistics; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.airlift.slice.SizeOf.instanceSize; import static java.lang.Long.MAX_VALUE; diff --git a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestTimestampStatisticsBuilder.java b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestTimestampStatisticsBuilder.java index 26b227bde41f..1a5708882fb6 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestTimestampStatisticsBuilder.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/metadata/statistics/TestTimestampStatisticsBuilder.java @@ -17,7 +17,7 @@ import com.google.common.collect.DiscreteDomain; import com.google.common.collect.ImmutableList; import com.google.common.collect.Range; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import static io.trino.orc.metadata.statistics.AbstractStatisticsBuilderTest.StatisticsType.TIMESTAMP; import static io.trino.orc.metadata.statistics.TimestampStatistics.TIMESTAMP_VALUE_BYTES; diff --git a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystemAbfs.java b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystemAbfs.java index 532323a3ffa0..8daba40b523b 100644 --- a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystemAbfs.java +++ b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystemAbfs.java @@ -42,7 +42,7 @@ import static io.trino.plugin.hive.HiveTableProperties.STORAGE_FORMAT_PROPERTY; import static io.trino.spi.type.BigintType.BIGINT; import static java.lang.String.format; -import static org.testng.util.Strings.isNullOrEmpty; +import static org.assertj.core.util.Strings.isNullOrEmpty; public abstract class AbstractTestHiveFileSystemAbfs extends AbstractTestHiveFileSystem diff --git a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveFileSystemAdl.java b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveFileSystemAdl.java index df69bddd44be..525bf5063106 100644 --- a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveFileSystemAdl.java +++ b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveFileSystemAdl.java @@ -34,8 +34,8 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.util.Strings.isNullOrEmpty; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.util.Strings.isNullOrEmpty; @TestInstance(PER_CLASS) public class TestHiveFileSystemAdl diff --git a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveFileSystemS3.java b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveFileSystemS3.java index ae5c1833a8d5..c2d29ba9730c 100644 --- a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveFileSystemS3.java +++ b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveFileSystemS3.java @@ -58,8 +58,8 @@ import static java.io.InputStream.nullInputStream; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.util.Strings.isNullOrEmpty; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.util.Strings.isNullOrEmpty; @TestInstance(PER_CLASS) public class TestHiveFileSystemS3 diff --git a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveFileSystemWasb.java b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveFileSystemWasb.java index 955675b7d1d5..08d9eda5c3e4 100644 --- a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveFileSystemWasb.java +++ b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveFileSystemWasb.java @@ -27,8 +27,8 @@ import static com.google.common.base.Preconditions.checkArgument; import static java.lang.String.format; +import static org.assertj.core.util.Strings.isNullOrEmpty; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -import static org.testng.util.Strings.isNullOrEmpty; @TestInstance(PER_CLASS) public class TestHiveFileSystemWasb diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestTrinoS3FileSystemAccessOperations.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestTrinoS3FileSystemAccessOperations.java index 960f6b54d324..32083e867c6c 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestTrinoS3FileSystemAccessOperations.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestTrinoS3FileSystemAccessOperations.java @@ -35,8 +35,11 @@ import io.trino.testing.QueryRunner; import io.trino.testing.containers.Minio; import org.intellij.lang.annotations.Language; -import org.testng.annotations.AfterClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; import java.io.File; @@ -48,8 +51,10 @@ import static io.trino.testing.containers.Minio.MINIO_ACCESS_KEY; import static io.trino.testing.containers.Minio.MINIO_SECRET_KEY; import static java.util.stream.Collectors.toCollection; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; -@Test(singleThreaded = true) // S3 request counters shares mutable state so can't be run from many threads simultaneously +@TestInstance(PER_CLASS) +@Execution(ExecutionMode.SAME_THREAD) // S3 request counters shares mutable state so can't be run from many threads simultaneously public class TestTrinoS3FileSystemAccessOperations extends AbstractTestQueryFramework { @@ -100,7 +105,7 @@ protected QueryRunner createQueryRunner() .build(); } - @AfterClass(alwaysRun = true) + @AfterAll public void tearDown() { // closed by closeAfterClass diff --git a/plugin/trino-memory/pom.xml b/plugin/trino-memory/pom.xml index 42c275e8d656..9900c9b1e51c 100644 --- a/plugin/trino-memory/pom.xml +++ b/plugin/trino-memory/pom.xml @@ -190,11 +190,5 @@ junit-jupiter-engine test - - - org.testng - testng - test - diff --git a/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/hive/TestHiveFaultTolerantExecutionOrderByQueries.java b/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/hive/TestHiveFaultTolerantExecutionOrderByQueries.java index 48f0e58d69cb..d9bd5ae28238 100644 --- a/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/hive/TestHiveFaultTolerantExecutionOrderByQueries.java +++ b/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/hive/TestHiveFaultTolerantExecutionOrderByQueries.java @@ -20,7 +20,6 @@ import io.trino.testing.QueryRunner; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; -import org.testng.annotations.AfterClass; import java.util.Map; @@ -51,7 +50,6 @@ protected QueryRunner createQueryRunner(Map extraProperties) } @AfterAll - @AfterClass(alwaysRun = true) public void destroy() throws Exception { diff --git a/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/hive/TestHiveFaultTolerantExecutionWindowQueries.java b/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/hive/TestHiveFaultTolerantExecutionWindowQueries.java index 9b61d9ee2f69..54d9ad72c195 100644 --- a/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/hive/TestHiveFaultTolerantExecutionWindowQueries.java +++ b/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/hive/TestHiveFaultTolerantExecutionWindowQueries.java @@ -20,7 +20,6 @@ import io.trino.testing.QueryRunner; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.TestInstance; -import org.testng.annotations.AfterClass; import java.util.Map; @@ -53,7 +52,6 @@ protected QueryRunner createQueryRunner(Map extraProperties) } @AfterAll - @AfterClass(alwaysRun = true) public void destroy() throws Exception { diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java b/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java index 76809bb35f9d..ade1e9fc96c6 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java @@ -50,7 +50,10 @@ import io.trino.testing.DistributedQueryRunner; import io.trino.testing.QueryRunner; import org.intellij.lang.annotations.Language; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.net.URISyntaxException; @@ -81,8 +84,11 @@ import static java.lang.String.format; import static java.util.UUID.randomUUID; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -@Test(singleThreaded = true) +@TestInstance(PER_CLASS) +@Execution(SAME_THREAD) public class TestEventListenerBasic extends AbstractTestQueryFramework { @@ -268,7 +274,8 @@ public void testAbortedWhileWaitingForResources() assertFailedQuery(mySession, "SELECT * FROM tpch.sf1.nation", "Insufficient active worker nodes. Waited 10.00ms for at least 17 workers, but only 1 workers are active"); } - @Test(timeOut = 30_000) + @Test + @Timeout(30) public void testKilledWhileWaitingForResources() throws Exception { diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestRevokeOnTable.java b/testing/trino-tests/src/test/java/io/trino/execution/TestRevokeOnTable.java index 50b1324004f8..7f19308c6172 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestRevokeOnTable.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestRevokeOnTable.java @@ -27,9 +27,11 @@ import io.trino.spi.security.TrinoPrincipal; import io.trino.sql.query.QueryAssertions; import io.trino.testing.DistributedQueryRunner; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.EnumSet; @@ -39,7 +41,11 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) public class TestRevokeOnTable { private static final Session admin = sessionOf("admin"); @@ -52,7 +58,7 @@ public class TestRevokeOnTable private DistributedQueryRunner queryRunner; private QueryAssertions assertions; - @BeforeClass + @BeforeAll public void initClass() throws Exception { @@ -78,7 +84,7 @@ public void initClass() assertions = new QueryAssertions(queryRunner); } - @AfterClass(alwaysRun = true) + @AfterAll public void teardown() { assertions.close(); diff --git a/testing/trino-tests/src/test/java/io/trino/tests/TestLocalEngineOnlyQueries.java b/testing/trino-tests/src/test/java/io/trino/tests/TestLocalEngineOnlyQueries.java index e33cea6524eb..b1acc5037d8e 100644 --- a/testing/trino-tests/src/test/java/io/trino/tests/TestLocalEngineOnlyQueries.java +++ b/testing/trino-tests/src/test/java/io/trino/tests/TestLocalEngineOnlyQueries.java @@ -18,9 +18,10 @@ import io.trino.testing.AbstractTestEngineOnlyQueries; import io.trino.testing.LocalQueryRunner; import io.trino.testing.QueryRunner; -import org.testng.SkipException; +import org.junit.jupiter.api.Test; import static io.airlift.testing.Closeables.closeAllSuppress; +import static org.junit.jupiter.api.Assumptions.abort; public class TestLocalEngineOnlyQueries extends AbstractTestEngineOnlyQueries @@ -45,15 +46,17 @@ protected QueryRunner createQueryRunner() return queryRunner; } + @Test @Override public void testSetSession() { - throw new SkipException("SET SESSION is not supported by LocalQueryRunner"); + abort("SET SESSION is not supported by LocalQueryRunner"); } + @Test @Override public void testResetSession() { - throw new SkipException("RESET SESSION is not supported by LocalQueryRunner"); + abort("RESET SESSION is not supported by LocalQueryRunner"); } } diff --git a/testing/trino-tests/src/test/java/io/trino/tests/TestMinWorkerRequirement.java b/testing/trino-tests/src/test/java/io/trino/tests/TestMinWorkerRequirement.java index 8dc4d9add6a5..27de229f8821 100644 --- a/testing/trino-tests/src/test/java/io/trino/tests/TestMinWorkerRequirement.java +++ b/testing/trino-tests/src/test/java/io/trino/tests/TestMinWorkerRequirement.java @@ -23,7 +23,9 @@ import io.trino.testing.DistributedQueryRunner; import io.trino.testing.MaterializedResultWithQueryId; import io.trino.tests.tpch.TpchQueryRunnerBuilder; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import static io.trino.SystemSessionProperties.REQUIRED_WORKERS_COUNT; import static io.trino.SystemSessionProperties.REQUIRED_WORKERS_MAX_WAIT_TIME; @@ -34,42 +36,51 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Fail.fail; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; // run single threaded to avoid creating multiple query runners at once -@Test(singleThreaded = true) +@TestInstance(PER_CLASS) +@Execution(SAME_THREAD) public class TestMinWorkerRequirement { - @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "Insufficient active worker nodes. Waited 1.00ns for at least 5 workers, but only 4 workers are active") + @Test public void testInsufficientWorkerNodes() - throws Exception { - try (DistributedQueryRunner queryRunner = TpchQueryRunnerBuilder.builder() - .setCoordinatorProperties(ImmutableMap.builder() - .put("query-manager.required-workers", "5") - .put("query-manager.required-workers-max-wait", "1ns") - .buildOrThrow()) - .setNodeCount(4) - .build()) { - queryRunner.execute("SELECT COUNT(*) from lineitem"); - fail("Expected exception due to insufficient active worker nodes"); - } + assertThatThrownBy(() -> { + try (DistributedQueryRunner queryRunner = TpchQueryRunnerBuilder.builder() + .setCoordinatorProperties(ImmutableMap.builder() + .put("query-manager.required-workers", "5") + .put("query-manager.required-workers-max-wait", "1ns") + .buildOrThrow()) + .setNodeCount(4) + .build()) { + queryRunner.execute("SELECT COUNT(*) from lineitem"); + fail("Expected exception due to insufficient active worker nodes"); + } + }) + .isInstanceOf(RuntimeException.class) + .hasMessage("Insufficient active worker nodes. Waited 1.00ns for at least 5 workers, but only 4 workers are active"); } - @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "Insufficient active worker nodes. Waited 1.00ns for at least 4 workers, but only 3 workers are active") + @Test public void testInsufficientWorkerNodesWithCoordinatorExcluded() - throws Exception { - try (DistributedQueryRunner queryRunner = TpchQueryRunnerBuilder.builder() - .setCoordinatorProperties(ImmutableMap.builder() - .put("node-scheduler.include-coordinator", "false") - .put("query-manager.required-workers", "4") - .put("query-manager.required-workers-max-wait", "1ns") - .buildOrThrow()) - .setNodeCount(4) - .build()) { - queryRunner.execute("SELECT COUNT(*) from lineitem"); - fail("Expected exception due to insufficient active worker nodes"); - } + assertThatThrownBy(() -> { + try (DistributedQueryRunner queryRunner = TpchQueryRunnerBuilder.builder() + .setCoordinatorProperties(ImmutableMap.builder() + .put("node-scheduler.include-coordinator", "false") + .put("query-manager.required-workers", "4") + .put("query-manager.required-workers-max-wait", "1ns") + .buildOrThrow()) + .setNodeCount(4) + .build()) { + queryRunner.execute("SELECT COUNT(*) from lineitem"); + fail("Expected exception due to insufficient active worker nodes"); + } + }) + .isInstanceOf(RuntimeException.class) + .hasMessage("Insufficient active worker nodes. Waited 1.00ns for at least 4 workers, but only 3 workers are active"); } @Test @@ -116,26 +127,29 @@ public void testInsufficientWorkerNodesAfterDrop() } } - @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "Insufficient active worker nodes. Waited 99.00ns for at least 3 workers, but only 2 workers are active") + @Test public void testRequiredNodesMaxWaitSessionOverride() - throws Exception { - try (DistributedQueryRunner queryRunner = TpchQueryRunnerBuilder.builder() - .setCoordinatorProperties(ImmutableMap.builder() - .put("query-manager.required-workers", "3") - .put("query-manager.required-workers-max-wait", "1ns") - .buildOrThrow()) - .setNodeCount(2) - .build()) { - Session session = testSessionBuilder() - .setSystemProperty(REQUIRED_WORKERS_COUNT, "3") - .setSystemProperty(REQUIRED_WORKERS_MAX_WAIT_TIME, "99ns") - .setCatalog("tpch") - .setSchema("tiny") - .build(); - queryRunner.execute(session, "SELECT COUNT(*) from lineitem"); - fail("Expected exception due to insufficient active worker nodes"); - } + assertThatThrownBy(() -> { + try (DistributedQueryRunner queryRunner = TpchQueryRunnerBuilder.builder() + .setCoordinatorProperties(ImmutableMap.builder() + .put("query-manager.required-workers", "3") + .put("query-manager.required-workers-max-wait", "1ns") + .buildOrThrow()) + .setNodeCount(2) + .build()) { + Session session = testSessionBuilder() + .setSystemProperty(REQUIRED_WORKERS_COUNT, "3") + .setSystemProperty(REQUIRED_WORKERS_MAX_WAIT_TIME, "99ns") + .setCatalog("tpch") + .setSchema("tiny") + .build(); + queryRunner.execute(session, "SELECT COUNT(*) from lineitem"); + fail("Expected exception due to insufficient active worker nodes"); + } + }) + .isInstanceOf(RuntimeException.class) + .hasMessage("Insufficient active worker nodes. Waited 99.00ns for at least 3 workers, but only 2 workers are active"); } @Test diff --git a/testing/trino-tests/src/test/java/io/trino/tests/TestQueryPlanDeterminism.java b/testing/trino-tests/src/test/java/io/trino/tests/TestQueryPlanDeterminism.java index 71bd2d193611..9a6bd296c921 100644 --- a/testing/trino-tests/src/test/java/io/trino/tests/TestQueryPlanDeterminism.java +++ b/testing/trino-tests/src/test/java/io/trino/tests/TestQueryPlanDeterminism.java @@ -25,10 +25,10 @@ import io.trino.testing.TestingAccessControlManager.TestingPrivilege; import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; -import org.testng.SkipException; import java.util.List; @@ -267,10 +267,11 @@ public void testTpcdsQ6deterministic() " )\n"); } + @Test @Override public void testLargeIn() { // testLargeIn is expensive - throw new SkipException("Skipping testLargeIn"); + Assumptions.abort("Skipping testLargeIn"); } } diff --git a/testing/trino-tests/src/test/java/io/trino/tests/TestQuerySerializationFailures.java b/testing/trino-tests/src/test/java/io/trino/tests/TestQuerySerializationFailures.java index b764d491e726..19cf90a7b95c 100644 --- a/testing/trino-tests/src/test/java/io/trino/tests/TestQuerySerializationFailures.java +++ b/testing/trino-tests/src/test/java/io/trino/tests/TestQuerySerializationFailures.java @@ -24,7 +24,7 @@ import io.trino.testing.DistributedQueryRunner; import io.trino.testing.QueryRunner; import io.trino.testng.services.Flaky; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.Set; diff --git a/testing/trino-tests/src/test/java/io/trino/tests/TestQuerySpillLimits.java b/testing/trino-tests/src/test/java/io/trino/tests/TestQuerySpillLimits.java index b56a82185651..9fb634752fa4 100644 --- a/testing/trino-tests/src/test/java/io/trino/tests/TestQuerySpillLimits.java +++ b/testing/trino-tests/src/test/java/io/trino/tests/TestQuerySpillLimits.java @@ -21,9 +21,12 @@ import io.trino.spiller.NodeSpillConfig; import io.trino.testing.LocalQueryRunner; import io.trino.testing.QueryRunner; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.parallel.Execution; import java.io.File; import java.nio.file.Files; @@ -32,8 +35,11 @@ import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; import static io.trino.testing.TestingSession.testSessionBuilder; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; -@Test(singleThreaded = true) +@TestInstance(PER_METHOD) +@Execution(SAME_THREAD) public class TestQuerySpillLimits { private static final Session SESSION = testSessionBuilder() @@ -43,21 +49,22 @@ public class TestQuerySpillLimits private File spillPath; - @BeforeMethod + @BeforeEach public void setUp() throws Exception { this.spillPath = Files.createTempDirectory(null).toFile(); } - @AfterMethod(alwaysRun = true) + @AfterEach public void tearDown() throws Exception { deleteRecursively(spillPath.toPath(), ALLOW_INSECURE); } - @Test(timeOut = 240_000, expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = ".*Query exceeded local spill limit of 10B") + @Test + @Timeout(240) public void testMaxSpillPerNodeLimit() { assertThatThrownBy(() -> { @@ -69,7 +76,8 @@ public void testMaxSpillPerNodeLimit() .hasMessage(".*Query exceeded local spill limit of 10B"); } - @Test(timeOut = 240_000, expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = ".*Query exceeded per-query local spill limit of 10B") + @Test + @Timeout(240) public void testQueryMaxSpillPerNodeLimit() { assertThatThrownBy(() -> { From 3a85001dba5fb32e60cc6a695126e426ec8e7d77 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Fri, 17 Nov 2023 17:39:20 +0100 Subject: [PATCH 342/587] Remove support for pre-Puffin Iceberg stats Before Iceberg library gained Puffin stats, Trino would store table stats in Iceberg table properties. This became obsoleted in Trino 405 (released in Dec 2022). Dropping support for old stats format means need to re-analyze tables that haven't been written to by Trino 405 or newer. --- .../trino/plugin/iceberg/IcebergMetadata.java | 38 ------------------- .../plugin/iceberg/TableStatisticsReader.java | 33 ---------------- 2 files changed, 71 deletions(-) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java index e90aeb4f72bf..9c3b1e34ec6e 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java @@ -251,8 +251,6 @@ import static io.trino.plugin.iceberg.PartitionFields.parsePartitionFields; import static io.trino.plugin.iceberg.PartitionFields.toPartitionFields; import static io.trino.plugin.iceberg.SortFieldUtils.parseSortFields; -import static io.trino.plugin.iceberg.TableStatisticsReader.TRINO_STATS_COLUMN_ID_PATTERN; -import static io.trino.plugin.iceberg.TableStatisticsReader.TRINO_STATS_PREFIX; import static io.trino.plugin.iceberg.TableStatisticsWriter.StatsUpdateMode.INCREMENTAL_UPDATE; import static io.trino.plugin.iceberg.TableStatisticsWriter.StatsUpdateMode.REPLACE; import static io.trino.plugin.iceberg.TableType.DATA; @@ -1499,13 +1497,6 @@ private void executeDropExtendedStats(ConnectorSession session, IcebergTableExec updateStatistics.removeStatistics(statisticsFile.snapshotId()); } updateStatistics.commit(); - UpdateProperties updateProperties = transaction.updateProperties(); - for (String key : transaction.table().properties().keySet()) { - if (key.startsWith(TRINO_STATS_PREFIX)) { - updateProperties.remove(key); - } - } - updateProperties.commit(); transaction.commitTransaction(); transaction = null; } @@ -2174,41 +2165,12 @@ public void finishStatisticsCollection(ConnectorSession session, ConnectorTableH "Unexpected computed statistics that cannot be attached to a snapshot because none exists: %s", computedStatistics); - // TODO (https://github.com/trinodb/trino/issues/15397): remove support for Trino-specific statistics properties - // Drop all stats. Empty table needs none - UpdateProperties updateProperties = transaction.updateProperties(); - table.properties().keySet().stream() - .filter(key -> key.startsWith(TRINO_STATS_PREFIX)) - .forEach(updateProperties::remove); - updateProperties.commit(); - transaction.commitTransaction(); transaction = null; return; } long snapshotId = handle.getSnapshotId().orElseThrow(); - Set columnIds = table.schema().columns().stream() - .map(Types.NestedField::fieldId) - .collect(toImmutableSet()); - - // TODO (https://github.com/trinodb/trino/issues/15397): remove support for Trino-specific statistics properties - // Drop stats for obsolete columns - UpdateProperties updateProperties = transaction.updateProperties(); - table.properties().keySet().stream() - .filter(key -> { - if (!key.startsWith(TRINO_STATS_PREFIX)) { - return false; - } - Matcher matcher = TRINO_STATS_COLUMN_ID_PATTERN.matcher(key); - if (!matcher.matches()) { - return false; - } - return !columnIds.contains(Integer.parseInt(matcher.group("columnId"))); - }) - .forEach(updateProperties::remove); - updateProperties.commit(); - CollectedStatistics collectedStatistics = processComputedTableStatistics(table, computedStatistics); StatisticsFile statisticsFile = tableStatisticsWriter.writeStatisticsFile( session, diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/TableStatisticsReader.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/TableStatisticsReader.java index 8e00d7e0c3da..0b4947959e0e 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/TableStatisticsReader.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/TableStatisticsReader.java @@ -51,8 +51,6 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import static com.google.common.base.Verify.verifyNotNull; import static com.google.common.collect.ImmutableMap.toImmutableMap; @@ -77,19 +75,6 @@ private TableStatisticsReader() {} private static final Logger log = Logger.get(TableStatisticsReader.class); - // TODO (https://github.com/trinodb/trino/issues/15397): remove support for Trino-specific statistics properties - @Deprecated - public static final String TRINO_STATS_PREFIX = "trino.stats.ndv."; - // TODO (https://github.com/trinodb/trino/issues/15397): remove support for Trino-specific statistics properties - @Deprecated - public static final String TRINO_STATS_NDV_FORMAT = TRINO_STATS_PREFIX + "%d.ndv"; - // TODO (https://github.com/trinodb/trino/issues/15397): remove support for Trino-specific statistics properties - @Deprecated - public static final Pattern TRINO_STATS_COLUMN_ID_PATTERN = Pattern.compile(Pattern.quote(TRINO_STATS_PREFIX) + "(?\\d+)\\..*"); - // TODO (https://github.com/trinodb/trino/issues/15397): remove support for Trino-specific statistics properties - @Deprecated - public static final Pattern TRINO_STATS_NDV_PATTERN = Pattern.compile(Pattern.quote(TRINO_STATS_PREFIX) + "(?\\d+)\\.ndv"); - public static final String APACHE_DATASKETCHES_THETA_V1_NDV_PROPERTY = "ndv"; public static TableStatistics getTableStatistics(TypeManager typeManager, ConnectorSession session, IcebergTableHandle tableHandle, Table icebergTable) @@ -256,24 +241,6 @@ private static Map readNdvs(Table icebergTable, long snapshotId, } }); - // TODO (https://github.com/trinodb/trino/issues/15397): remove support for Trino-specific statistics properties - Iterator> properties = icebergTable.properties().entrySet().iterator(); - while (!remainingColumnIds.isEmpty() && properties.hasNext()) { - Entry entry = properties.next(); - String key = entry.getKey(); - String value = entry.getValue(); - if (key.startsWith(TRINO_STATS_PREFIX)) { - Matcher matcher = TRINO_STATS_NDV_PATTERN.matcher(key); - if (matcher.matches()) { - int columnId = Integer.parseInt(matcher.group("columnId")); - if (remainingColumnIds.remove(columnId)) { - long ndv = parseLong(value); - ndvByColumnId.put(columnId, ndv); - } - } - } - } - return ndvByColumnId.buildOrThrow(); } From 185d0331917d15bb0568a938685afc63bdb16a89 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Fri, 17 Nov 2023 17:11:18 +0100 Subject: [PATCH 343/587] Update some occurrences of "Presto" in tests --- .../metastore/glue/TestGlueInputConverter.java | 12 ++++++------ .../metastore/glue/TestingMetastoreObjects.java | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestGlueInputConverter.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestGlueInputConverter.java index 30ebc7828782..2b64eb2d5fd0 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestGlueInputConverter.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestGlueInputConverter.java @@ -37,16 +37,16 @@ import java.util.Optional; import java.util.Random; -import static io.trino.plugin.hive.metastore.glue.TestingMetastoreObjects.getPrestoTestDatabase; -import static io.trino.plugin.hive.metastore.glue.TestingMetastoreObjects.getPrestoTestPartition; -import static io.trino.plugin.hive.metastore.glue.TestingMetastoreObjects.getPrestoTestTable; +import static io.trino.plugin.hive.metastore.glue.TestingMetastoreObjects.getTrinoTestDatabase; +import static io.trino.plugin.hive.metastore.glue.TestingMetastoreObjects.getTrinoTestPartition; +import static io.trino.plugin.hive.metastore.glue.TestingMetastoreObjects.getTrinoTestTable; import static org.assertj.core.api.Assertions.assertThat; public class TestGlueInputConverter { - private final Database testDb = getPrestoTestDatabase(); - private final Table testTbl = getPrestoTestTable(testDb.getDatabaseName()); - private final Partition testPartition = getPrestoTestPartition(testDb.getDatabaseName(), testTbl.getTableName(), ImmutableList.of("val1")); + private final Database testDb = getTrinoTestDatabase(); + private final Table testTbl = getTrinoTestTable(testDb.getDatabaseName()); + private final Partition testPartition = getTrinoTestPartition(testDb.getDatabaseName(), testTbl.getTableName(), ImmutableList.of("val1")); @Test public void testConvertDatabase() diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestingMetastoreObjects.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestingMetastoreObjects.java index cd1266e7a1a8..5fbeefa4b7be 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestingMetastoreObjects.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestingMetastoreObjects.java @@ -126,7 +126,7 @@ public static Partition getGlueTestPartition(String dbName, String tblName, List // --------------- Trino Objects --------------- - public static io.trino.plugin.hive.metastore.Database getPrestoTestDatabase() + public static io.trino.plugin.hive.metastore.Database getTrinoTestDatabase() { return io.trino.plugin.hive.metastore.Database.builder() .setDatabaseName("test-db" + generateRandom()) @@ -138,7 +138,7 @@ public static io.trino.plugin.hive.metastore.Database getPrestoTestDatabase() .build(); } - public static io.trino.plugin.hive.metastore.Table getPrestoTestTable(String dbName) + public static io.trino.plugin.hive.metastore.Table getTrinoTestTable(String dbName) { return io.trino.plugin.hive.metastore.Table.builder() .setDatabaseName(dbName) @@ -146,25 +146,25 @@ public static io.trino.plugin.hive.metastore.Table getPrestoTestTable(String dbN .setOwner(Optional.of("owner")) .setParameters(ImmutableMap.of()) .setTableType(TableType.EXTERNAL_TABLE.name()) - .setDataColumns(ImmutableList.of(getPrestoTestColumn())) - .setPartitionColumns(ImmutableList.of(getPrestoTestColumn())) + .setDataColumns(ImmutableList.of(getTrinoTestColumn())) + .setPartitionColumns(ImmutableList.of(getTrinoTestColumn())) .setViewOriginalText(Optional.of("originalText")) .setViewExpandedText(Optional.of("expandedText")) .withStorage(STORAGE_CONSUMER).build(); } - public static io.trino.plugin.hive.metastore.Partition getPrestoTestPartition(String dbName, String tblName, List values) + public static io.trino.plugin.hive.metastore.Partition getTrinoTestPartition(String dbName, String tblName, List values) { return io.trino.plugin.hive.metastore.Partition.builder() .setDatabaseName(dbName) .setTableName(tblName) .setValues(values) - .setColumns(ImmutableList.of(getPrestoTestColumn())) + .setColumns(ImmutableList.of(getTrinoTestColumn())) .setParameters(ImmutableMap.of()) .withStorage(STORAGE_CONSUMER).build(); } - public static io.trino.plugin.hive.metastore.Column getPrestoTestColumn() + public static io.trino.plugin.hive.metastore.Column getTrinoTestColumn() { return new io.trino.plugin.hive.metastore.Column("test-col" + generateRandom(), HiveType.HIVE_STRING, Optional.of("column comment")); } From 3a57cfaecfc54c09fd8f74532bf0690df9f1e2ad Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Fri, 17 Nov 2023 17:09:17 +0100 Subject: [PATCH 344/587] Remove deprecated Hive internal Column constructor --- .../plugin/deltalake/DeltaLakeMetadata.java | 2 +- .../glue/TestDeltaLakeGlueMetastore.java | 4 +- .../plugin/hive/TestHiveFileSystemS3.java | 5 +- .../trino/plugin/hive/HiveColumnHandle.java | 3 +- .../io/trino/plugin/hive/HiveMetadata.java | 6 +- .../plugin/hive/TrinoViewHiveMetastore.java | 2 +- .../io/trino/plugin/hive/ViewReaderUtil.java | 2 +- .../trino/plugin/hive/metastore/Column.java | 9 --- .../metastore/glue/GlueHiveMetastore.java | 4 +- .../metastore/thrift/ThriftMetastoreUtil.java | 2 +- .../trino/plugin/hive/AbstractTestHive.java | 68 +++++++++---------- .../hive/AbstractTestHiveFileSystem.java | 6 +- .../plugin/hive/AbstractTestHiveLocal.java | 8 +-- .../plugin/hive/BaseHiveConnectorTest.java | 2 +- ...ransactionScopeCachingDirectoryLister.java | 3 +- .../TestSemiTransactionalHiveMetastore.java | 4 +- .../cache/TestCachingHiveMetastore.java | 16 ++--- .../metastore/file/TestFileHiveMetastore.java | 3 +- .../glue/TestingMetastoreObjects.java | 3 +- ...astoreMetadataQueriesAccessOperations.java | 5 +- .../plugin/hudi/TestHudiPartitionManager.java | 5 +- .../ResourceHudiTablesInitializer.java | 12 ++-- .../testing/TpchHudiTablesInitializer.java | 15 ++-- .../AbstractIcebergTableOperations.java | 4 +- .../iceberg/catalog/hms/TrinoHiveCatalog.java | 2 +- ...stIcebergNodeLocalDynamicSplitPruning.java | 4 +- 26 files changed, 101 insertions(+), 98 deletions(-) diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java index b138e265b9ec..42f790c003eb 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java @@ -346,7 +346,7 @@ public class DeltaLakeMetadata // Matches the dummy column Databricks stores in the metastore private static final List DUMMY_DATA_COLUMNS = ImmutableList.of( - new Column("col", HiveType.toHiveType(new ArrayType(VarcharType.createUnboundedVarcharType())), Optional.empty())); + new Column("col", HiveType.toHiveType(new ArrayType(VarcharType.createUnboundedVarcharType())), Optional.empty(), Map.of())); private static final Set SUPPORTED_STATISTICS_TYPE = ImmutableSet.builder() .add(TOTAL_SIZE_IN_BYTES) .add(NUMBER_OF_DISTINCT_VALUES_SUMMARY) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeGlueMetastore.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeGlueMetastore.java index 54fa8be4597b..221dbe3f0c95 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeGlueMetastore.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeGlueMetastore.java @@ -289,7 +289,7 @@ private void createTable(SchemaTableName tableName, String tableLocation, Consum .setTableName(tableName.getTableName()) .setOwner(Optional.of(session.getUser())) .setTableType(EXTERNAL_TABLE.name()) - .setDataColumns(List.of(new Column("a_column", HIVE_STRING, Optional.empty()))); + .setDataColumns(List.of(new Column("a_column", HIVE_STRING, Optional.empty(), Map.of()))); table.getStorageBuilder() .setStorageFormat(fromHiveStorageFormat(PARQUET)) @@ -308,7 +308,7 @@ private void createView(SchemaTableName viewName, String tableLocation, Consumer .setTableName(viewName.getTableName()) .setOwner(Optional.of(session.getUser())) .setTableType(VIRTUAL_VIEW.name()) - .setDataColumns(List.of(new Column("a_column", HIVE_STRING, Optional.empty()))); + .setDataColumns(List.of(new Column("a_column", HIVE_STRING, Optional.empty(), Map.of()))); table.getStorageBuilder() .setStorageFormat(fromHiveStorageFormat(PARQUET)) diff --git a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveFileSystemS3.java b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveFileSystemS3.java index c2d29ba9730c..5ff770ac39ff 100644 --- a/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveFileSystemS3.java +++ b/plugin/trino-hive-hadoop2/src/test/java/io/trino/plugin/hive/TestHiveFileSystemS3.java @@ -49,6 +49,7 @@ import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.Optional; import static com.google.common.base.Preconditions.checkArgument; @@ -145,8 +146,8 @@ public void testFileIteratorPartitionedListingNativeS3Client() Table.Builder tableBuilder = Table.builder() .setDatabaseName(table.getSchemaName()) .setTableName(table.getTableName()) - .setDataColumns(ImmutableList.of(new Column("data", HIVE_LONG, Optional.empty()))) - .setPartitionColumns(ImmutableList.of(new Column("part", HIVE_STRING, Optional.empty()))) + .setDataColumns(ImmutableList.of(new Column("data", HIVE_LONG, Optional.empty(), Map.of()))) + .setPartitionColumns(ImmutableList.of(new Column("part", HIVE_STRING, Optional.empty(), Map.of()))) .setOwner(Optional.empty()) .setTableType("fake"); tableBuilder.getStorageBuilder() diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveColumnHandle.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveColumnHandle.java index ca27ecacf0b3..3e54e3425bb0 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveColumnHandle.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveColumnHandle.java @@ -15,6 +15,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.collect.ImmutableMap; import io.airlift.slice.SizeOf; import io.trino.plugin.hive.metastore.Column; import io.trino.spi.connector.ColumnHandle; @@ -254,7 +255,7 @@ public String toString() public Column toMetastoreColumn() { - return new Column(name, getHiveType(), comment); + return new Column(name, getHiveType(), comment, ImmutableMap.of()); } public static HiveColumnHandle mergeRowIdColumnHandle() diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java index 02f7652ad295..38fccc9b136d 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java @@ -1398,7 +1398,7 @@ private static Table buildTableObject( HiveType type = columnHandle.getHiveType(); if (!partitionColumnNames.contains(name)) { verify(!columnHandle.isPartitionKey(), "Column handles are not consistent with partitioned by property"); - columns.add(new Column(name, type, columnHandle.getComment())); + columns.add(new Column(name, type, columnHandle.getComment(), ImmutableMap.of())); } else { verify(columnHandle.isPartitionKey(), "Column handles are not consistent with partitioned by property"); @@ -2664,7 +2664,7 @@ public void createView(ConnectorSession session, SchemaTableName viewName, Conne .put(PRESTO_QUERY_ID_NAME, session.getQueryId()) .buildOrThrow(); - Column dummyColumn = new Column("dummy", HIVE_STRING, Optional.empty()); + Column dummyColumn = new Column("dummy", HIVE_STRING, Optional.empty(), ImmutableMap.of()); Table.Builder tableBuilder = Table.builder() .setDatabaseName(viewName.getSchemaName()) @@ -3671,7 +3671,7 @@ private static void validateBucketColumns(ConnectorTableMetadata tableMetadata) } List dataColumns = tableMetadata.getColumns().stream() - .map(columnMetadata -> new Column(columnMetadata.getName(), toHiveType(columnMetadata.getType()), Optional.ofNullable(columnMetadata.getComment()))) + .map(columnMetadata -> new Column(columnMetadata.getName(), toHiveType(columnMetadata.getType()), Optional.ofNullable(columnMetadata.getComment()), ImmutableMap.of())) .collect(toImmutableList()); if (!isSupportedBucketing(bucketProperty.get(), dataColumns, tableMetadata.getTable().getTableName())) { throw new TrinoException(NOT_SUPPORTED, "Cannot create a table bucketed on an unsupported type"); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/TrinoViewHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/TrinoViewHiveMetastore.java index 626bac260801..89ca55fc702f 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/TrinoViewHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/TrinoViewHiveMetastore.java @@ -73,7 +73,7 @@ public void createView(ConnectorSession session, SchemaTableName schemaViewName, .setTableName(schemaViewName.getTableName()) .setOwner(isUsingSystemSecurity ? Optional.empty() : Optional.of(session.getUser())) .setTableType(VIRTUAL_VIEW.name()) - .setDataColumns(ImmutableList.of(new Column("dummy", HIVE_STRING, Optional.empty()))) + .setDataColumns(ImmutableList.of(new Column("dummy", HIVE_STRING, Optional.empty(), Map.of()))) .setPartitionColumns(ImmutableList.of()) .setParameters(createViewProperties(session, trinoVersion, connectorName)) .setViewOriginalText(Optional.of(encodeViewData(definition))) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/ViewReaderUtil.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/ViewReaderUtil.java index 4e9cffb9f723..5bbb24834f1d 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/ViewReaderUtil.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/ViewReaderUtil.java @@ -110,7 +110,7 @@ private static CoralTableRedirectionResolver coralTableRedirectionResolver( format("%s is redirected to %s, but that relation cannot be found", schemaTableName, target))); List columns = tableSchema.getColumns().stream() .filter(columnSchema -> !columnSchema.isHidden()) - .map(columnSchema -> new Column(columnSchema.getName(), toHiveType(columnSchema.getType()), Optional.empty() /* comment */)) + .map(columnSchema -> new Column(columnSchema.getName(), toHiveType(columnSchema.getType()), Optional.empty() /* comment */, Map.of())) .collect(toImmutableList()); Table table = Table.builder() .setDatabaseName(schemaTableName.getSchemaName()) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/Column.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/Column.java index af1440c121c5..7eaaa438c0b0 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/Column.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/Column.java @@ -34,15 +34,6 @@ public class Column private final Optional comment; private final Map properties; - @Deprecated - public Column( - String name, - HiveType type, - Optional comment) - { - this(name, type, comment, ImmutableMap.of()); - } - @JsonCreator public Column( @JsonProperty("name") String name, diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java index 1991c2d869ff..fc6c078e9a65 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java @@ -778,7 +778,7 @@ public void addColumn(String databaseName, String tableName, String columnName, { Table oldTable = getExistingTable(databaseName, tableName); Table newTable = Table.builder(oldTable) - .addDataColumn(new Column(columnName, columnType, Optional.ofNullable(columnComment))) + .addDataColumn(new Column(columnName, columnType, Optional.ofNullable(columnComment), ImmutableMap.of())) .build(); replaceTable(databaseName, tableName, newTable, null); } @@ -794,7 +794,7 @@ public void renameColumn(String databaseName, String tableName, String oldColumn ImmutableList.Builder newDataColumns = ImmutableList.builder(); for (Column column : oldTable.getDataColumns()) { if (column.getName().equals(oldColumnName)) { - newDataColumns.add(new Column(newColumnName, column.getType(), column.getComment())); + newDataColumns.add(new Column(newColumnName, column.getType(), column.getComment(), column.getProperties())); } else { newDataColumns.add(column); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreUtil.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreUtil.java index 6dd725b00dc4..650ca4623595 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreUtil.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftMetastoreUtil.java @@ -686,7 +686,7 @@ public static FieldSchema toMetastoreApiFieldSchema(Column column) private static Column fromMetastoreApiFieldSchema(FieldSchema fieldSchema) { - return new Column(fieldSchema.getName(), HiveType.valueOf(fieldSchema.getType().toLowerCase(ENGLISH)), Optional.ofNullable(fieldSchema.getComment())); + return new Column(fieldSchema.getName(), HiveType.valueOf(fieldSchema.getType().toLowerCase(ENGLISH)), Optional.ofNullable(fieldSchema.getComment()), ImmutableMap.of()); } private static void fromMetastoreApiStorageDescriptor( diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java index 97809bfcac4a..c572512fb5b2 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java @@ -1296,7 +1296,7 @@ protected void doTestMismatchSchemaTable( Table oldTable = transaction.getMetastore().getTable(schemaName, tableName).get(); List dataColumns = tableAfter.stream() .filter(columnMetadata -> !columnMetadata.getName().equals("ds")) - .map(columnMetadata -> new Column(columnMetadata.getName(), toHiveType(columnMetadata.getType()), Optional.empty())) + .map(columnMetadata -> new Column(columnMetadata.getName(), toHiveType(columnMetadata.getType()), Optional.empty(), Map.of())) .collect(toList()); Table.Builder newTable = Table.builder(oldTable) .setDataColumns(dataColumns); @@ -1601,7 +1601,7 @@ public void testPerTransactionDirectoryListerCache() { long initListCount = countingDirectoryLister.getListCount(); SchemaTableName tableName = temporaryTable("per_transaction_listing_cache_test"); - List columns = ImmutableList.of(new Column("test", HIVE_STRING, Optional.empty())); + List columns = ImmutableList.of(new Column("test", HIVE_STRING, Optional.empty(), Map.of())); createEmptyTable(tableName, ORC, columns, ImmutableList.of()); try { try (Transaction transaction = newTransaction()) { @@ -1826,9 +1826,9 @@ private void doTestBucketedTableEvolutionWithDifferentReadCount(HiveStorageForma tableName, storageFormat, ImmutableList.of( - new Column("id", HIVE_LONG, Optional.empty()), - new Column("name", HIVE_STRING, Optional.empty())), - ImmutableList.of(new Column("pk", HIVE_STRING, Optional.empty())), + new Column("id", HIVE_LONG, Optional.empty(), Map.of()), + new Column("name", HIVE_STRING, Optional.empty(), Map.of())), + ImmutableList.of(new Column("pk", HIVE_STRING, Optional.empty(), Map.of())), Optional.of(new HiveBucketProperty(ImmutableList.of("id"), BUCKETING_V1, 4, ImmutableList.of()))); // write a 4-bucket partition MaterializedResult.Builder bucket8Builder = MaterializedResult.resultBuilder(SESSION, BIGINT, VARCHAR, VARCHAR); @@ -1902,9 +1902,9 @@ private void doTestBucketedTableEvolution(HiveStorageFormat storageFormat, Schem tableName, storageFormat, ImmutableList.of( - new Column("id", HIVE_LONG, Optional.empty()), - new Column("name", HIVE_STRING, Optional.empty())), - ImmutableList.of(new Column("pk", HIVE_STRING, Optional.empty())), + new Column("id", HIVE_LONG, Optional.empty(), Map.of()), + new Column("name", HIVE_STRING, Optional.empty(), Map.of())), + ImmutableList.of(new Column("pk", HIVE_STRING, Optional.empty(), Map.of())), Optional.of(new HiveBucketProperty(ImmutableList.of("id"), BUCKETING_V1, 4, ImmutableList.of()))); // write a 4-bucket partition MaterializedResult.Builder bucket4Builder = MaterializedResult.resultBuilder(SESSION, BIGINT, VARCHAR, VARCHAR); @@ -2020,9 +2020,9 @@ private void doTestBucketedSortedTableEvolution(SchemaTableName tableName) tableName, ORC, ImmutableList.of( - new Column("id", HIVE_LONG, Optional.empty()), - new Column("name", HIVE_STRING, Optional.empty())), - ImmutableList.of(new Column("pk", HIVE_STRING, Optional.empty())), + new Column("id", HIVE_LONG, Optional.empty(), Map.of()), + new Column("name", HIVE_STRING, Optional.empty(), Map.of())), + ImmutableList.of(new Column("pk", HIVE_STRING, Optional.empty(), Map.of())), Optional.of(new HiveBucketProperty( ImmutableList.of("id"), BUCKETING_V1, @@ -2139,8 +2139,8 @@ private void prepareInvalidBuckets(HiveStorageFormat storageFormat, SchemaTableN tableName, storageFormat, ImmutableList.of( - new Column("id", HIVE_LONG, Optional.empty()), - new Column("name", HIVE_STRING, Optional.empty())), + new Column("id", HIVE_LONG, Optional.empty(), Map.of()), + new Column("name", HIVE_STRING, Optional.empty(), Map.of())), ImmutableList.of(), Optional.of(new HiveBucketProperty(ImmutableList.of("id"), BUCKETING_V1, 8, ImmutableList.of()))); @@ -2511,7 +2511,7 @@ private void assertEmptyFile(HiveStorageFormat format) { SchemaTableName tableName = temporaryTable("empty_file"); try { - List columns = ImmutableList.of(new Column("test", HIVE_STRING, Optional.empty())); + List columns = ImmutableList.of(new Column("test", HIVE_STRING, Optional.empty(), Map.of())); createEmptyTable(tableName, format, columns, ImmutableList.of()); try (Transaction transaction = newTransaction()) { @@ -2707,7 +2707,7 @@ public void testTableCreationRollback() @Test public void testTableCreationIgnoreExisting() { - List columns = ImmutableList.of(new Column("dummy", HiveType.valueOf("uniontype"), Optional.empty())); + List columns = ImmutableList.of(new Column("dummy", HiveType.valueOf("uniontype"), Optional.empty(), Map.of())); SchemaTableName schemaTableName = temporaryTable("create"); ConnectorSession session = newSession(); String schemaName = schemaTableName.getSchemaName(); @@ -2747,7 +2747,7 @@ public void testTableCreationIgnoreExisting() } // at this point the table should exist, now try creating the table again with a different table definition - columns = ImmutableList.of(new Column("new_column", HiveType.valueOf("string"), Optional.empty())); + columns = ImmutableList.of(new Column("new_column", HiveType.valueOf("string"), Optional.empty(), Map.of())); try (Transaction transaction = newTransaction()) { Table table = createSimpleTable(schemaTableName, columns, session, targetPath.appendSuffix("_4"), "q4"); transaction.getMetastore() @@ -3067,7 +3067,7 @@ public void testCreateEmptyTableShouldNotCreateStagingDirectory() for (HiveStorageFormat storageFormat : createTableFormats) { SchemaTableName temporaryCreateEmptyTable = temporaryTable("create_empty"); try { - List columns = ImmutableList.of(new Column("test", HIVE_STRING, Optional.empty())); + List columns = ImmutableList.of(new Column("test", HIVE_STRING, Optional.empty(), Map.of())); try (Transaction transaction = newTransaction()) { String temporaryStagingPrefix = "hive-temporary-staging-prefix-" + UUID.randomUUID().toString().toLowerCase(ENGLISH).replace("-", ""); ConnectorSession session = newSession(); @@ -3165,8 +3165,8 @@ public void testHideDeltaLakeTables() .setTableName(tableName.getTableName()) .setOwner(Optional.of(session.getUser())) .setTableType(MANAGED_TABLE.name()) - .setPartitionColumns(List.of(new Column("a_partition_column", HIVE_INT, Optional.empty()))) - .setDataColumns(List.of(new Column("a_column", HIVE_STRING, Optional.empty()))) + .setPartitionColumns(List.of(new Column("a_partition_column", HIVE_INT, Optional.empty(), Map.of()))) + .setDataColumns(List.of(new Column("a_column", HIVE_STRING, Optional.empty(), Map.of()))) .setParameter(SPARK_TABLE_PROVIDER_KEY, DELTA_LAKE_PROVIDER); table.getStorageBuilder() .setStorageFormat(fromHiveStorageFormat(PARQUET)) @@ -3237,8 +3237,8 @@ public void testDisallowQueryingOfIcebergTables() .setTableName(tableName.getTableName()) .setOwner(Optional.of(session.getUser())) .setTableType(MANAGED_TABLE.name()) - .setPartitionColumns(List.of(new Column("a_partition_column", HIVE_INT, Optional.empty()))) - .setDataColumns(List.of(new Column("a_column", HIVE_STRING, Optional.empty()))) + .setPartitionColumns(List.of(new Column("a_partition_column", HIVE_INT, Optional.empty(), Map.of()))) + .setDataColumns(List.of(new Column("a_column", HIVE_STRING, Optional.empty(), Map.of()))) .setParameter(ICEBERG_TABLE_TYPE_NAME, ICEBERG_TABLE_TYPE_VALUE); table.getStorageBuilder() .setStorageFormat(fromHiveStorageFormat(PARQUET)) @@ -3544,7 +3544,7 @@ public void testIllegalStorageFormatDuringTableScan() SchemaTableName schemaTableName = temporaryTable("test_illegal_storage_format"); try (Transaction transaction = newTransaction()) { ConnectorSession session = newSession(); - List columns = ImmutableList.of(new Column("pk", HIVE_STRING, Optional.empty())); + List columns = ImmutableList.of(new Column("pk", HIVE_STRING, Optional.empty(), Map.of())); String tableOwner = session.getUser(); String schemaName = schemaTableName.getSchemaName(); String tableName = schemaTableName.getTableName(); @@ -4055,8 +4055,8 @@ private void assertPageSourceMetrics(SchemaTableName tableName, HiveStorageForma tableName, storageFormat, ImmutableList.of( - new Column("id", HIVE_LONG, Optional.empty()), - new Column("name", HIVE_STRING, Optional.empty())), + new Column("id", HIVE_LONG, Optional.empty(), Map.of()), + new Column("name", HIVE_STRING, Optional.empty(), Map.of())), ImmutableList.of()); MaterializedResult.Builder inputDataBuilder = MaterializedResult.resultBuilder(SESSION, BIGINT, VARCHAR); IntStream.range(0, 100).forEach(i -> inputDataBuilder.row((long) i, String.valueOf(i))); @@ -4721,8 +4721,8 @@ private void doInsertIntoNewPartition(HiveStorageFormat storageFormat, SchemaTab private void doInsertUnsupportedWriteType(HiveStorageFormat storageFormat, SchemaTableName tableName) throws Exception { - List columns = ImmutableList.of(new Column("dummy", HiveType.valueOf("uniontype"), Optional.empty())); - List partitionColumns = ImmutableList.of(new Column("name", HIVE_STRING, Optional.empty())); + List columns = ImmutableList.of(new Column("dummy", HiveType.valueOf("uniontype"), Optional.empty(), Map.of())); + List partitionColumns = ImmutableList.of(new Column("name", HIVE_STRING, Optional.empty(), Map.of())); createEmptyTable(tableName, storageFormat, columns, partitionColumns); @@ -5737,9 +5737,9 @@ public void testPreferredInsertLayout() SchemaTableName tableName = temporaryTable("empty_partitioned_table"); try { - Column partitioningColumn = new Column("column2", HIVE_STRING, Optional.empty()); + Column partitioningColumn = new Column("column2", HIVE_STRING, Optional.empty(), Map.of()); List columns = ImmutableList.of( - new Column("column1", HIVE_STRING, Optional.empty()), + new Column("column1", HIVE_STRING, Optional.empty(), Map.of()), partitioningColumn); createEmptyTable(tableName, ORC, columns, ImmutableList.of(partitioningColumn)); @@ -5778,8 +5778,8 @@ protected void insertBucketedTableLayout(boolean transactional) SchemaTableName tableName = temporaryTable("empty_bucketed_table"); try { List columns = ImmutableList.of( - new Column("column1", HIVE_STRING, Optional.empty()), - new Column("column2", HIVE_LONG, Optional.empty())); + new Column("column1", HIVE_STRING, Optional.empty(), Map.of()), + new Column("column2", HIVE_LONG, Optional.empty(), Map.of())); HiveBucketProperty bucketProperty = new HiveBucketProperty(ImmutableList.of("column1"), BUCKETING_V1, 4, ImmutableList.of()); createEmptyTable(tableName, ORC, columns, ImmutableList.of(), Optional.of(bucketProperty), transactional); @@ -5826,9 +5826,9 @@ protected void insertPartitionedBucketedTableLayout(boolean transactional) { SchemaTableName tableName = temporaryTable("empty_partitioned_table"); try { - Column partitioningColumn = new Column("column2", HIVE_LONG, Optional.empty()); + Column partitioningColumn = new Column("column2", HIVE_LONG, Optional.empty(), Map.of()); List columns = ImmutableList.of( - new Column("column1", HIVE_STRING, Optional.empty()), + new Column("column1", HIVE_STRING, Optional.empty(), Map.of()), partitioningColumn); HiveBucketProperty bucketProperty = new HiveBucketProperty(ImmutableList.of("column1"), BUCKETING_V1, 4, ImmutableList.of()); createEmptyTable(tableName, ORC, columns, ImmutableList.of(partitioningColumn), Optional.of(bucketProperty), transactional); @@ -6002,8 +6002,8 @@ protected void doTestTransactionDeleteInsert(HiveStorageFormat storageFormat, bo createEmptyTable( temporaryDeleteInsert, storageFormat, - ImmutableList.of(new Column("col1", HIVE_LONG, Optional.empty())), - ImmutableList.of(new Column("pk1", HIVE_STRING, Optional.empty()), new Column("pk2", HIVE_STRING, Optional.empty()))); + ImmutableList.of(new Column("col1", HIVE_LONG, Optional.empty(), Map.of())), + ImmutableList.of(new Column("pk1", HIVE_STRING, Optional.empty(), Map.of()), new Column("pk2", HIVE_STRING, Optional.empty(), Map.of()))); insertData(temporaryDeleteInsert, beforeData); try { doTestTransactionDeleteInsert( diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java index ac0f980f7788..f4b1dcba3919 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java @@ -437,7 +437,7 @@ public void testFileIteratorListing() Table.Builder tableBuilder = Table.builder() .setDatabaseName(table.getSchemaName()) .setTableName(table.getTableName()) - .setDataColumns(ImmutableList.of(new Column("one", HIVE_LONG, Optional.empty()))) + .setDataColumns(ImmutableList.of(new Column("one", HIVE_LONG, Optional.empty(), Map.of()))) .setPartitionColumns(ImmutableList.of()) .setOwner(Optional.empty()) .setTableType("fake"); @@ -515,8 +515,8 @@ public void testFileIteratorPartitionedListing() Table.Builder tableBuilder = Table.builder() .setDatabaseName(table.getSchemaName()) .setTableName(table.getTableName()) - .setDataColumns(ImmutableList.of(new Column("data", HIVE_LONG, Optional.empty()))) - .setPartitionColumns(ImmutableList.of(new Column("part", HIVE_STRING, Optional.empty()))) + .setDataColumns(ImmutableList.of(new Column("data", HIVE_LONG, Optional.empty(), Map.of()))) + .setPartitionColumns(ImmutableList.of(new Column("part", HIVE_STRING, Optional.empty(), Map.of()))) .setOwner(Optional.empty()) .setTableType("fake"); tableBuilder.getStorageBuilder() diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveLocal.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveLocal.java index e6dfd137f58a..ea4989f2e928 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveLocal.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveLocal.java @@ -246,10 +246,10 @@ private void doTestSparkBucketedTableValidation(SchemaTableName tableName) tableName, ORC, ImmutableList.of( - new Column("nationkey", HIVE_INT, Optional.empty()), - new Column("name", HIVE_STRING, Optional.empty()), - new Column("regionkey", HIVE_INT, Optional.empty()), - new Column("comment", HIVE_STRING, Optional.empty())), + new Column("nationkey", HIVE_INT, Optional.empty(), Map.of()), + new Column("name", HIVE_STRING, Optional.empty(), Map.of()), + new Column("regionkey", HIVE_INT, Optional.empty(), Map.of()), + new Column("comment", HIVE_STRING, Optional.empty(), Map.of())), ImmutableList.of(), Optional.of(new HiveBucketProperty( ImmutableList.of("nationkey"), diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java index f06cbc899925..2264fe4855ec 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java @@ -8642,7 +8642,7 @@ public void testTimestampWithTimeZone() Optional.empty(), false, Collections.emptyMap()), - List.of(new Column("t", HiveType.HIVE_TIMESTAMPLOCALTZ, Optional.empty())), + List.of(new Column("t", HiveType.HIVE_TIMESTAMPLOCALTZ, Optional.empty(), Map.of())), List.of(), Collections.emptyMap(), Optional.empty(), diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/TestTransactionScopeCachingDirectoryLister.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/TestTransactionScopeCachingDirectoryLister.java index bd7817e4d456..a34467a51076 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/TestTransactionScopeCachingDirectoryLister.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/TestTransactionScopeCachingDirectoryLister.java @@ -51,7 +51,8 @@ public class TestTransactionScopeCachingDirectoryLister private static final Column TABLE_COLUMN = new Column( "column", HiveType.HIVE_INT, - Optional.of("comment")); + Optional.of("comment"), + Map.of()); private static final Storage TABLE_STORAGE = new Storage( StorageFormat.create("serde", "input", "output"), Optional.of("location"), diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestSemiTransactionalHiveMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestSemiTransactionalHiveMetastore.java index 9cdd79aff461..b8eccab2fad7 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestSemiTransactionalHiveMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestSemiTransactionalHiveMetastore.java @@ -25,6 +25,7 @@ import org.junit.jupiter.api.Test; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.OptionalLong; import java.util.concurrent.CountDownLatch; @@ -50,7 +51,8 @@ public class TestSemiTransactionalHiveMetastore private static final Column TABLE_COLUMN = new Column( "column", HiveType.HIVE_INT, - Optional.of("comment")); + Optional.of("comment"), + Map.of()); private static final Storage TABLE_STORAGE = new Storage( StorageFormat.create("serde", "input", "output"), Optional.of("/test"), diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastore.java index 5a779e401ff0..dc176ec0a00a 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastore.java @@ -554,12 +554,12 @@ public void testGetTableStatistics() "col1", ColumnStatisticsData.longStats(new LongColumnStatsData().setNumNulls(1)), "col2", ColumnStatisticsData.longStats(new LongColumnStatsData().setNumNulls(2)), "col3", ColumnStatisticsData.longStats(new LongColumnStatsData().setNumNulls(3)))); - Table tableCol1 = Table.builder(table).setDataColumns(ImmutableList.of(new Column("col1", HIVE_LONG, Optional.empty()))).build(); + Table tableCol1 = Table.builder(table).setDataColumns(ImmutableList.of(new Column("col1", HIVE_LONG, Optional.empty(), Map.of()))).build(); assertThat(metastore.getTableStatistics(tableCol1).getColumnStatistics()).containsEntry("col1", intColumnStats(1)); - Table tableCol2 = Table.builder(table).setDataColumns(ImmutableList.of(new Column("col2", HIVE_LONG, Optional.empty()))).build(); + Table tableCol2 = Table.builder(table).setDataColumns(ImmutableList.of(new Column("col2", HIVE_LONG, Optional.empty(), Map.of()))).build(); assertThat(metastore.getTableStatistics(tableCol2).getColumnStatistics()).containsEntry("col2", intColumnStats(2)); Table tableCol23 = Table.builder(table) - .setDataColumns(ImmutableList.of(new Column("col2", HIVE_LONG, Optional.empty()), new Column("col3", HIVE_LONG, Optional.empty()))) + .setDataColumns(ImmutableList.of(new Column("col2", HIVE_LONG, Optional.empty(), Map.of()), new Column("col3", HIVE_LONG, Optional.empty(), Map.of()))) .build(); assertThat(metastore.getTableStatistics(tableCol23).getColumnStatistics()) .containsEntry("col2", intColumnStats(2)) @@ -706,16 +706,16 @@ public void testGetPartitionStatistics() "col2", ColumnStatisticsData.longStats(new LongColumnStatsData().setNumNulls(2)), "col3", ColumnStatisticsData.longStats(new LongColumnStatsData().setNumNulls(3)))); - Table tableCol1 = Table.builder(table).setDataColumns(ImmutableList.of(new Column("col1", HIVE_LONG, Optional.empty()))).build(); + Table tableCol1 = Table.builder(table).setDataColumns(ImmutableList.of(new Column("col1", HIVE_LONG, Optional.empty(), Map.of()))).build(); Map tableCol1PartitionStatistics = metastore.getPartitionStatistics(tableCol1, ImmutableList.of(partition)); assertThat(tableCol1PartitionStatistics).containsOnlyKeys(partitionName); assertThat(tableCol1PartitionStatistics.get(partitionName).getColumnStatistics()).containsEntry("col1", intColumnStats(1)); - Table tableCol2 = Table.builder(table).setDataColumns(ImmutableList.of(new Column("col2", HIVE_LONG, Optional.empty()))).build(); + Table tableCol2 = Table.builder(table).setDataColumns(ImmutableList.of(new Column("col2", HIVE_LONG, Optional.empty(), Map.of()))).build(); Map tableCol2PartitionStatistics = metastore.getPartitionStatistics(tableCol2, ImmutableList.of(partition)); assertThat(tableCol2PartitionStatistics).containsOnlyKeys(partitionName); assertThat(tableCol2PartitionStatistics.get(partitionName).getColumnStatistics()).containsEntry("col2", intColumnStats(2)); Table tableCol23 = Table.builder(table) - .setDataColumns(ImmutableList.of(new Column("col2", HIVE_LONG, Optional.empty()), new Column("col3", HIVE_LONG, Optional.empty()))) + .setDataColumns(ImmutableList.of(new Column("col2", HIVE_LONG, Optional.empty(), Map.of()), new Column("col3", HIVE_LONG, Optional.empty(), Map.of()))) .build(); Map tableCol23PartitionStatistics = metastore.getPartitionStatistics(tableCol23, ImmutableList.of(partition)); assertThat(tableCol23PartitionStatistics).containsOnlyKeys(partitionName); @@ -954,8 +954,8 @@ private void testLoadAfterInvalidate(boolean invalidateAll) String databaseName = "my_database"; String tableName = "my_table_name"; - tableColumns.add(new Column("value", toHiveType(VARCHAR), Optional.empty() /* comment */)); - tableColumns.add(new Column("pk", toHiveType(VARCHAR), Optional.empty() /* comment */)); + tableColumns.add(new Column("value", toHiveType(VARCHAR), Optional.empty() /* comment */, Map.of())); + tableColumns.add(new Column("pk", toHiveType(VARCHAR), Optional.empty() /* comment */, Map.of())); List partitionNames = new ArrayList<>(); for (int i = 0; i < 10; i++) { diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/file/TestFileHiveMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/file/TestFileHiveMetastore.java index 4752af151dea..590c9d411350 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/file/TestFileHiveMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/file/TestFileHiveMetastore.java @@ -28,6 +28,7 @@ import java.io.IOException; import java.nio.file.Path; +import java.util.Map; import java.util.Optional; import static com.google.common.io.MoreFiles.deleteRecursively; @@ -94,7 +95,7 @@ public void testPreserveHudiInputFormat() .setTableName("some_table_name" + randomNameSuffix()) .setTableType(EXTERNAL_TABLE.name()) .setOwner(Optional.of("public")) - .addDataColumn(new Column("foo", HIVE_INT, Optional.empty())) + .addDataColumn(new Column("foo", HIVE_INT, Optional.empty(), Map.of())) .setParameters(ImmutableMap.of("serialization.format", "1", "EXTERNAL", "TRUE")) .withStorage(storageBuilder -> storageBuilder .setStorageFormat(storageFormat) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestingMetastoreObjects.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestingMetastoreObjects.java index 5fbeefa4b7be..b09617a305ef 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestingMetastoreObjects.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestingMetastoreObjects.java @@ -28,6 +28,7 @@ import io.trino.spi.security.PrincipalType; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.concurrent.ThreadLocalRandom; import java.util.function.Consumer; @@ -166,7 +167,7 @@ public static io.trino.plugin.hive.metastore.Partition getTrinoTestPartition(Str public static io.trino.plugin.hive.metastore.Column getTrinoTestColumn() { - return new io.trino.plugin.hive.metastore.Column("test-col" + generateRandom(), HiveType.HIVE_STRING, Optional.of("column comment")); + return new io.trino.plugin.hive.metastore.Column("test-col" + generateRandom(), HiveType.HIVE_STRING, Optional.of("column comment"), Map.of()); } private static final Consumer STORAGE_CONSUMER = storage -> diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestHiveMetastoreMetadataQueriesAccessOperations.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestHiveMetastoreMetadataQueriesAccessOperations.java index 4bbae5d0f9d4..9fa42c8e0c25 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestHiveMetastoreMetadataQueriesAccessOperations.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestHiveMetastoreMetadataQueriesAccessOperations.java @@ -34,6 +34,7 @@ import org.junit.jupiter.api.parallel.Execution; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.stream.IntStream; @@ -790,8 +791,8 @@ public Optional
    getTable(String databaseName, String tableName) .setDatabaseName(databaseName) .setTableName(tableName) .setDataColumns(ImmutableList.of( - new Column("id", HiveType.HIVE_INT, Optional.empty()), - new Column("name", HiveType.HIVE_STRING, Optional.empty()))) + new Column("id", HiveType.HIVE_INT, Optional.empty(), Map.of()), + new Column("name", HiveType.HIVE_STRING, Optional.empty(), Map.of()))) .setOwner(Optional.empty()) .setTableType(MANAGED_TABLE.name()) .withStorage(storage -> diff --git a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiPartitionManager.java b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiPartitionManager.java index 41a4c566d978..fc3357e7b681 100644 --- a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiPartitionManager.java +++ b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiPartitionManager.java @@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.OptionalLong; @@ -42,8 +43,8 @@ public class TestHudiPartitionManager private static final String TABLE_NAME = "table"; private static final String USER_NAME = "user"; private static final String LOCATION = "somewhere/over/the/rainbow"; - private static final Column PARTITION_COLUMN = new Column("ds", HIVE_STRING, Optional.empty()); - private static final Column BUCKET_COLUMN = new Column("c1", HIVE_INT, Optional.empty()); + private static final Column PARTITION_COLUMN = new Column("ds", HIVE_STRING, Optional.empty(), Map.of()); + private static final Column BUCKET_COLUMN = new Column("c1", HIVE_INT, Optional.empty(), Map.of()); private static final Table TABLE = new Table( SCHEMA_NAME, TABLE_NAME, diff --git a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/testing/ResourceHudiTablesInitializer.java b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/testing/ResourceHudiTablesInitializer.java index 2e996d626b3e..3c89326d69ae 100644 --- a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/testing/ResourceHudiTablesInitializer.java +++ b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/testing/ResourceHudiTablesInitializer.java @@ -130,7 +130,7 @@ private void createTable( private static Column column(String name, HiveType type) { - return new Column(name, type, Optional.empty()); + return new Column(name, type, Optional.empty(), Map.of()); } private static void copyDir(Path srcDir, Path dstDir) @@ -161,11 +161,11 @@ public enum TestingTable /**/; private static final List HUDI_META_COLUMNS = ImmutableList.of( - new Column("_hoodie_commit_time", HIVE_STRING, Optional.empty()), - new Column("_hoodie_commit_seqno", HIVE_STRING, Optional.empty()), - new Column("_hoodie_record_key", HIVE_STRING, Optional.empty()), - new Column("_hoodie_partition_path", HIVE_STRING, Optional.empty()), - new Column("_hoodie_file_name", HIVE_STRING, Optional.empty())); + new Column("_hoodie_commit_time", HIVE_STRING, Optional.empty(), Map.of()), + new Column("_hoodie_commit_seqno", HIVE_STRING, Optional.empty(), Map.of()), + new Column("_hoodie_record_key", HIVE_STRING, Optional.empty(), Map.of()), + new Column("_hoodie_partition_path", HIVE_STRING, Optional.empty(), Map.of()), + new Column("_hoodie_file_name", HIVE_STRING, Optional.empty(), Map.of())); private final List regularColumns; private final List partitionColumns; diff --git a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/testing/TpchHudiTablesInitializer.java b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/testing/TpchHudiTablesInitializer.java index 785ae2a32e96..7807fdf12945 100644 --- a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/testing/TpchHudiTablesInitializer.java +++ b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/testing/TpchHudiTablesInitializer.java @@ -60,6 +60,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.function.Function; @@ -92,11 +93,11 @@ public class TpchHudiTablesInitializer private static final String PARTITION_PATH = ""; private static final Logger log = Logger.get(TpchHudiTablesInitializer.class); private static final List HUDI_META_COLUMNS = ImmutableList.of( - new Column("_hoodie_commit_time", HIVE_STRING, Optional.empty()), - new Column("_hoodie_commit_seqno", HIVE_STRING, Optional.empty()), - new Column("_hoodie_record_key", HIVE_STRING, Optional.empty()), - new Column("_hoodie_partition_path", HIVE_STRING, Optional.empty()), - new Column("_hoodie_file_name", HIVE_STRING, Optional.empty())); + new Column("_hoodie_commit_time", HIVE_STRING, Optional.empty(), Map.of()), + new Column("_hoodie_commit_seqno", HIVE_STRING, Optional.empty(), Map.of()), + new Column("_hoodie_record_key", HIVE_STRING, Optional.empty(), Map.of()), + new Column("_hoodie_partition_path", HIVE_STRING, Optional.empty(), Map.of()), + new Column("_hoodie_file_name", HIVE_STRING, Optional.empty(), Map.of())); private static final HdfsContext CONTEXT = new HdfsContext(SESSION); private final HoodieTableType tableType; @@ -291,9 +292,9 @@ private static List createMetastoreColumns(TpchTable table) List columns = new ArrayList<>(tpchColumns.size() + 1); for (TpchColumn c : tpchColumns) { HiveType hiveType = TpchColumnTypeAdapter.toHiveType(c.getType()); - columns.add(new Column(c.getSimplifiedColumnName(), hiveType, Optional.empty())); + columns.add(new Column(c.getSimplifiedColumnName(), hiveType, Optional.empty(), Map.of())); } - columns.add(new Column(FIELD_UUID, HIVE_STRING, Optional.empty())); + columns.add(new Column(FIELD_UUID, HIVE_STRING, Optional.empty(), Map.of())); return unmodifiableList(columns); } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractIcebergTableOperations.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractIcebergTableOperations.java index 721c4dcf44f9..5920798b8a10 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractIcebergTableOperations.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractIcebergTableOperations.java @@ -36,6 +36,7 @@ import java.io.FileNotFoundException; import java.time.Duration; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.OptionalInt; @@ -306,7 +307,8 @@ public static List toHiveColumns(List columns) .map(column -> new Column( column.name(), toHiveType(HiveSchemaUtil.convert(column.type())), - Optional.empty())) + Optional.empty(), + Map.of())) .collect(toImmutableList()); } } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java index 39ef750ec7de..a0a02eb6b1ee 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java @@ -628,7 +628,7 @@ private void createMaterializedViewWithStorageTable( // Create a view indicating the storage table Map viewProperties = createMaterializedViewProperties(session, storageTable); - Column dummyColumn = new Column("dummy", HIVE_STRING, Optional.empty()); + Column dummyColumn = new Column("dummy", HIVE_STRING, Optional.empty(), Map.of()); io.trino.plugin.hive.metastore.Table.Builder tableBuilder = io.trino.plugin.hive.metastore.Table.builder() .setDatabaseName(viewName.getSchemaName()) diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergNodeLocalDynamicSplitPruning.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergNodeLocalDynamicSplitPruning.java index ebcaa3dbaa2b..7733f28ffa62 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergNodeLocalDynamicSplitPruning.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergNodeLocalDynamicSplitPruning.java @@ -82,11 +82,11 @@ public class TestIcebergNodeLocalDynamicSplitPruning { private static final String SCHEMA_NAME = "test"; private static final String TABLE_NAME = "test"; - private static final Column KEY_COLUMN = new Column("a_integer", HIVE_INT, Optional.empty()); + private static final Column KEY_COLUMN = new Column("a_integer", HIVE_INT, Optional.empty(), Map.of()); private static final ColumnIdentity KEY_COLUMN_IDENTITY = new ColumnIdentity(1, KEY_COLUMN.getName(), PRIMITIVE, ImmutableList.of()); private static final IcebergColumnHandle KEY_ICEBERG_COLUMN_HANDLE = new IcebergColumnHandle(KEY_COLUMN_IDENTITY, INTEGER, ImmutableList.of(), INTEGER, Optional.empty()); private static final int KEY_COLUMN_VALUE = 42; - private static final Column DATA_COLUMN = new Column("a_varchar", HIVE_STRING, Optional.empty()); + private static final Column DATA_COLUMN = new Column("a_varchar", HIVE_STRING, Optional.empty(), Map.of()); private static final ColumnIdentity DATA_COLUMN_IDENTITY = new ColumnIdentity(2, DATA_COLUMN.getName(), PRIMITIVE, ImmutableList.of()); private static final IcebergColumnHandle DATA_ICEBERG_COLUMN_HANDLE = new IcebergColumnHandle(DATA_COLUMN_IDENTITY, VARCHAR, ImmutableList.of(), VARCHAR, Optional.empty()); private static final String DATA_COLUMN_VALUE = "hello world"; From b060fff75ac0003a93e65319f7e96c70ca98a6f8 Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Mon, 20 Nov 2023 09:15:28 +0900 Subject: [PATCH 345/587] Remove flaky assertion in SQL Server testCreateTableAsSelectWriteBulkiness --- .../plugin/sqlserver/TestSqlServerConnectorTest.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerConnectorTest.java b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerConnectorTest.java index 93a16229f5a9..f859fed7a306 100644 --- a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerConnectorTest.java +++ b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerConnectorTest.java @@ -62,10 +62,8 @@ protected SqlExecutor onRemoteDatabase() return sqlServer::execute; } - @Flaky(issue = "fn_dblog() returns information only about the active portion of the transaction log, therefore it is flaky", match = ".*") @Test public void testCreateTableAsSelectWriteBulkiness() - throws SQLException { testCreateTableAsSelectWriteBulkiness(true, true); testCreateTableAsSelectWriteBulkiness(true, false); @@ -74,7 +72,6 @@ public void testCreateTableAsSelectWriteBulkiness() } private void testCreateTableAsSelectWriteBulkiness(boolean bulkCopyForWrite, boolean bulkCopyLock) - throws SQLException { String table = "bulk_copy_ctas_" + randomNameSuffix(); Session session = Session.builder(getSession()) @@ -86,12 +83,6 @@ private void testCreateTableAsSelectWriteBulkiness(boolean bulkCopyForWrite, boo assertQuerySucceeds(session, format("CREATE TABLE %s as SELECT * FROM tpch.tiny.customer", table)); assertQuery("SELECT * FROM " + table, "SELECT * FROM customer"); - // check whether minimal logging was applied. - // Unlike fully logged operations, which use the transaction log to keep track of every row change, - // minimally logged operations keep track of extent allocations and meta-data changes only. - assertThat(getTableOperationsCount("LOP_INSERT_ROWS", table)) - .isEqualTo(bulkCopyForWrite && bulkCopyLock ? 0 : 1500); - // check that there are no locks remaining on the target table after bulk copy assertQuery("SELECT count(*) FROM " + table, "SELECT count(*) FROM customer"); assertUpdate(format("INSERT INTO %s SELECT * FROM tpch.tiny.customer LIMIT 1", table), 1); From 0ef6ebc0a0ee408a146ccbd2155a4d1903c1dec9 Mon Sep 17 00:00:00 2001 From: radek Date: Fri, 17 Nov 2023 10:58:07 +0100 Subject: [PATCH 346/587] Change property from max_writer_tasks_count to max_writer_task_count --- .../io/trino/SystemSessionProperties.java | 10 ++--- .../trino/execution/QueryManagerConfig.java | 10 ++--- .../execution/TestQueryManagerConfig.java | 4 +- .../TestLimitMaxWriterNodesCount.java | 40 +++++++++---------- .../admin/properties-query-management.md | 4 +- .../plugin/hive/BaseHiveConnectorTest.java | 12 +++--- ...veFaultTolerantExecutionConnectorTest.java | 6 +-- 7 files changed, 43 insertions(+), 43 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/SystemSessionProperties.java b/core/trino-main/src/main/java/io/trino/SystemSessionProperties.java index 323592aa51ed..2f56a09680cc 100644 --- a/core/trino-main/src/main/java/io/trino/SystemSessionProperties.java +++ b/core/trino-main/src/main/java/io/trino/SystemSessionProperties.java @@ -83,7 +83,7 @@ public final class SystemSessionProperties public static final String USE_PREFERRED_WRITE_PARTITIONING = "use_preferred_write_partitioning"; public static final String SCALE_WRITERS = "scale_writers"; public static final String TASK_SCALE_WRITERS_ENABLED = "task_scale_writers_enabled"; - public static final String MAX_WRITER_TASKS_COUNT = "max_writer_tasks_count"; + public static final String MAX_WRITER_TASK_COUNT = "max_writer_task_count"; public static final String WRITER_SCALING_MIN_DATA_PROCESSED = "writer_scaling_min_data_processed"; public static final String SKEWED_PARTITION_MIN_DATA_PROCESSED_REBALANCE_THRESHOLD = "skewed_partition_min_data_processed_rebalance_threshold"; public static final String MAX_MEMORY_PER_PARTITION_WRITER = "max_memory_per_partition_writer"; @@ -320,10 +320,10 @@ public SystemSessionProperties( featuresConfig.isScaleWriters(), false), integerProperty( - MAX_WRITER_TASKS_COUNT, + MAX_WRITER_TASK_COUNT, "Maximum number of tasks that will participate in writing data", - queryManagerConfig.getMaxWriterTasksCount(), - value -> validateIntegerValue(value, MAX_WRITER_TASKS_COUNT, 1, false), + queryManagerConfig.getMaxWriterTaskCount(), + value -> validateIntegerValue(value, MAX_WRITER_TASK_COUNT, 1, false), false), booleanProperty( TASK_SCALE_WRITERS_ENABLED, @@ -1149,7 +1149,7 @@ public static boolean isTaskScaleWritersEnabled(Session session) public static int getMaxWriterTaskCount(Session session) { - return session.getSystemProperty(MAX_WRITER_TASKS_COUNT, Integer.class); + return session.getSystemProperty(MAX_WRITER_TASK_COUNT, Integer.class); } public static DataSize getWriterScalingMinDataProcessed(Session session) diff --git a/core/trino-main/src/main/java/io/trino/execution/QueryManagerConfig.java b/core/trino-main/src/main/java/io/trino/execution/QueryManagerConfig.java index c48c58c58246..7aee308276f7 100644 --- a/core/trino-main/src/main/java/io/trino/execution/QueryManagerConfig.java +++ b/core/trino-main/src/main/java/io/trino/execution/QueryManagerConfig.java @@ -65,7 +65,7 @@ public class QueryManagerConfig private int maxHashPartitionCount = 100; private int minHashPartitionCount = 4; private int minHashPartitionCountForWrite = 50; - private int maxWriterTasksCount = 100; + private int maxWriterTaskCount = 100; private Duration minQueryExpireAge = new Duration(15, TimeUnit.MINUTES); private int maxQueryHistory = 100; private int maxQueryLength = 1_000_000; @@ -260,16 +260,16 @@ public QueryManagerConfig setMinHashPartitionCountForWrite(int minHashPartitionC } @Min(1) - public int getMaxWriterTasksCount() + public int getMaxWriterTaskCount() { - return maxWriterTasksCount; + return maxWriterTaskCount; } @Config("query.max-writer-task-count") @ConfigDescription("Maximum number of tasks that will participate in writing data") - public QueryManagerConfig setMaxWriterTasksCount(int maxWritersNodesCount) + public QueryManagerConfig setMaxWriterTaskCount(int maxWritersNodesCount) { - this.maxWriterTasksCount = maxWritersNodesCount; + this.maxWriterTaskCount = maxWritersNodesCount; return this; } diff --git a/core/trino-main/src/test/java/io/trino/execution/TestQueryManagerConfig.java b/core/trino-main/src/test/java/io/trino/execution/TestQueryManagerConfig.java index dc62cc9f36ee..70167a834d5e 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestQueryManagerConfig.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestQueryManagerConfig.java @@ -109,7 +109,7 @@ public void testDefaults() .setFaultTolerantExecutionSmallStageSourceSizeMultiplier(1.2) .setFaultTolerantExecutionSmallStageRequireNoMorePartitions(false) .setFaultTolerantExecutionStageEstimationForEagerParentEnabled(true) - .setMaxWriterTasksCount(100)); + .setMaxWriterTaskCount(100)); } @Test @@ -258,7 +258,7 @@ public void testExplicitPropertyMappings() .setFaultTolerantExecutionSmallStageSourceSizeMultiplier(1.6) .setFaultTolerantExecutionSmallStageRequireNoMorePartitions(true) .setFaultTolerantExecutionStageEstimationForEagerParentEnabled(false) - .setMaxWriterTasksCount(101); + .setMaxWriterTaskCount(101); assertFullMapping(properties, expected); } diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestLimitMaxWriterNodesCount.java b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestLimitMaxWriterNodesCount.java index 7e95b4f90269..e51f24a75272 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestLimitMaxWriterNodesCount.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestLimitMaxWriterNodesCount.java @@ -41,7 +41,7 @@ import java.util.Optional; import java.util.OptionalInt; -import static io.trino.SystemSessionProperties.MAX_WRITER_TASKS_COUNT; +import static io.trino.SystemSessionProperties.MAX_WRITER_TASK_COUNT; import static io.trino.SystemSessionProperties.REDISTRIBUTE_WRITES; import static io.trino.SystemSessionProperties.RETRY_POLICY; import static io.trino.SystemSessionProperties.SCALE_WRITERS; @@ -144,7 +144,7 @@ public void testPlanWhenInsertToUnpartitionedTableScaleWritersDisabled() @Language("SQL") String query = "INSERT INTO unpartitioned_target_table VALUES ('one', 'two')"; Session session = Session.builder(getQueryRunner().getDefaultSession()) - .setSystemProperty(MAX_WRITER_TASKS_COUNT, "2") + .setSystemProperty(MAX_WRITER_TASK_COUNT, "2") .setSystemProperty(SCALE_WRITERS, "false") .setCatalog(catalogName) .build(); @@ -155,7 +155,7 @@ public void testPlanWhenInsertToUnpartitionedTableScaleWritersDisabled() anyTree( node(TableWriterNode.class, exchange(LOCAL, Optional.empty(), - // partitionCount for writing stage should be set to because session variable MAX_WRITER_TASKS_COUNT is set to 2 + // partitionCount for writing stage should be set to because session variable MAX_WRITER_TASK_COUNT is set to 2 exchange(REMOTE, FIXED_ARBITRARY_DISTRIBUTION, Optional.of(2), values("column_a", "column_b")))))); } @@ -166,7 +166,7 @@ public void testPlanWhenInsertToUnpartitionedTableScaleWritersEnabled() @Language("SQL") String query = "INSERT INTO unpartitioned_target_table VALUES ('one', 'two')"; Session session = Session.builder(getQueryRunner().getDefaultSession()) - .setSystemProperty(MAX_WRITER_TASKS_COUNT, "2") + .setSystemProperty(MAX_WRITER_TASK_COUNT, "2") .setSystemProperty(SCALE_WRITERS, "true") .setCatalog(catalogName) .build(); @@ -177,7 +177,7 @@ public void testPlanWhenInsertToUnpartitionedTableScaleWritersEnabled() anyTree( node(TableWriterNode.class, exchange(LOCAL, Optional.empty(), - // partitionCount for writing stage should be set to because session variable MAX_WRITER_TASKS_COUNT is set to 2 + // partitionCount for writing stage should be set to because session variable MAX_WRITER_TASK_COUNT is set to 2 exchange(REMOTE, SystemPartitioningHandle.SCALED_WRITER_ROUND_ROBIN_DISTRIBUTION, Optional.of(2), values("column_a", "column_b")))))); } @@ -188,7 +188,7 @@ public void testPlanWhenInsertToUnpartitionedSourceDistribution() @Language("SQL") String query = "INSERT INTO unpartitioned_target_table VALUES ('one', 'two')"; Session session = Session.builder(getQueryRunner().getDefaultSession()) - .setSystemProperty(MAX_WRITER_TASKS_COUNT, "2") + .setSystemProperty(MAX_WRITER_TASK_COUNT, "2") .setSystemProperty(SCALE_WRITERS, "false") .setSystemProperty(REDISTRIBUTE_WRITES, "false") .setCatalog(catalogName) @@ -209,7 +209,7 @@ public void testPlanWhenInsertToPartitionedTablePreferredPartitioningEnabled() @Language("SQL") String query = "INSERT INTO partitioned_target_table VALUES ('one', 'two'), ('three', 'four')"; Session session = Session.builder(getQueryRunner().getDefaultSession()) - .setSystemProperty(MAX_WRITER_TASKS_COUNT, "2") + .setSystemProperty(MAX_WRITER_TASK_COUNT, "2") .setSystemProperty(USE_PREFERRED_WRITE_PARTITIONING, "true") .setCatalog(catalogName) .build(); @@ -220,7 +220,7 @@ public void testPlanWhenInsertToPartitionedTablePreferredPartitioningEnabled() anyTree( node(TableWriterNode.class, exchange(LOCAL, - // partitionCount for writing stage should be set to because session variable MAX_WRITER_TASKS_COUNT is set to 2 + // partitionCount for writing stage should be set to because session variable MAX_WRITER_TASK_COUNT is set to 2 exchange(REMOTE, SCALED_WRITER_HASH_DISTRIBUTION, Optional.of(2), values("column_a", "column_b")))))); } @@ -231,7 +231,7 @@ public void testPlanWhenInsertToPartitionedAndBucketedTable() @Language("SQL") String query = "INSERT INTO partitioned_bucketed_target_table VALUES ('one', 'two'), ('three', 'four')"; Session session = Session.builder(getQueryRunner().getDefaultSession()) - .setSystemProperty(MAX_WRITER_TASKS_COUNT, "2") + .setSystemProperty(MAX_WRITER_TASK_COUNT, "2") .setCatalog(catalogName) .build(); @@ -252,7 +252,7 @@ public void testPlanWhenMaxWriterTasksSpecified() @Language("SQL") String query = "INSERT INTO partitioned_target_table VALUES ('one', 'two'), ('three', 'four')"; Session session = Session.builder(getQueryRunner().getDefaultSession()) - .setSystemProperty(MAX_WRITER_TASKS_COUNT, "2") + .setSystemProperty(MAX_WRITER_TASK_COUNT, "2") .setSystemProperty(USE_PREFERRED_WRITE_PARTITIONING, "true") .setCatalog(catalogNameWithMaxWriterTasksSpecified) .build(); @@ -274,7 +274,7 @@ public void testPlanWhenRetryPolicyIsTask() @Language("SQL") String query = "INSERT INTO partitioned_target_table VALUES ('one', 'two'), ('three', 'four')"; Session session = Session.builder(getQueryRunner().getDefaultSession()) - .setSystemProperty(MAX_WRITER_TASKS_COUNT, "2") + .setSystemProperty(MAX_WRITER_TASK_COUNT, "2") .setSystemProperty(USE_PREFERRED_WRITE_PARTITIONING, "true") .setSystemProperty(RETRY_POLICY, "TASK") .setCatalog(catalogNameWithMaxWriterTasksSpecified) @@ -296,7 +296,7 @@ public void testPlanWhenExecuteOnUnpartitionedTableScaleWritersDisabled() @Language("SQL") String query = "ALTER TABLE unpartitioned_target_table EXECUTE optimize(file_size_threshold => '10MB')"; Session session = Session.builder(getQueryRunner().getDefaultSession()) - .setSystemProperty(MAX_WRITER_TASKS_COUNT, "2") + .setSystemProperty(MAX_WRITER_TASK_COUNT, "2") .setSystemProperty(SCALE_WRITERS, "false") .setCatalog(catalogName) .build(); @@ -307,7 +307,7 @@ public void testPlanWhenExecuteOnUnpartitionedTableScaleWritersDisabled() anyTree( node(TableExecuteNode.class, exchange(LOCAL, Optional.empty(), - // partitionCount for writing stage should be set to because session variable MAX_WRITER_TASKS_COUNT is set to 2 + // partitionCount for writing stage should be set to because session variable MAX_WRITER_TASK_COUNT is set to 2 exchange(REMOTE, FIXED_ARBITRARY_DISTRIBUTION, Optional.of(2), tableScan(unPartitionedTable)))))); } @@ -318,7 +318,7 @@ public void testPlanWhenTableExecuteToUnpartitionedTableScaleWritersEnabled() @Language("SQL") String query = "ALTER TABLE unpartitioned_target_table EXECUTE optimize(file_size_threshold => '10MB')"; Session session = Session.builder(getQueryRunner().getDefaultSession()) - .setSystemProperty(MAX_WRITER_TASKS_COUNT, "2") + .setSystemProperty(MAX_WRITER_TASK_COUNT, "2") .setSystemProperty(SCALE_WRITERS, "true") .setCatalog(catalogName) .build(); @@ -329,7 +329,7 @@ public void testPlanWhenTableExecuteToUnpartitionedTableScaleWritersEnabled() anyTree( node(TableExecuteNode.class, exchange(LOCAL, Optional.empty(), - // partitionCount for writing stage should be set to because session variable MAX_WRITER_TASKS_COUNT is set to 2 + // partitionCount for writing stage should be set to because session variable MAX_WRITER_TASK_COUNT is set to 2 exchange(REMOTE, SystemPartitioningHandle.SCALED_WRITER_ROUND_ROBIN_DISTRIBUTION, Optional.of(2), tableScan(unPartitionedTable)))))); } @@ -340,7 +340,7 @@ public void testPlanWhenTableExecuteToUnpartitionedSourceDistribution() @Language("SQL") String query = "ALTER TABLE unpartitioned_target_table EXECUTE optimize(file_size_threshold => '10MB')"; Session session = Session.builder(getQueryRunner().getDefaultSession()) - .setSystemProperty(MAX_WRITER_TASKS_COUNT, "2") + .setSystemProperty(MAX_WRITER_TASK_COUNT, "2") .setSystemProperty(SCALE_WRITERS, "false") .setSystemProperty(REDISTRIBUTE_WRITES, "false") .setCatalog(catalogName) @@ -361,7 +361,7 @@ public void testPlanWhenTableExecuteToPartitionedTablePreferredPartitioningEnabl @Language("SQL") String query = "ALTER TABLE partitioned_target_table EXECUTE optimize(file_size_threshold => '10MB')"; Session session = Session.builder(getQueryRunner().getDefaultSession()) - .setSystemProperty(MAX_WRITER_TASKS_COUNT, "2") + .setSystemProperty(MAX_WRITER_TASK_COUNT, "2") .setSystemProperty(USE_PREFERRED_WRITE_PARTITIONING, "true") .setCatalog(catalogName) .build(); @@ -372,7 +372,7 @@ public void testPlanWhenTableExecuteToPartitionedTablePreferredPartitioningEnabl anyTree( node(TableExecuteNode.class, exchange(LOCAL, - // partitionCount for writing stage should be set to because session variable MAX_WRITER_TASKS_COUNT is set to 2 + // partitionCount for writing stage should be set to because session variable MAX_WRITER_TASK_COUNT is set to 2 exchange(REMOTE, SCALED_WRITER_HASH_DISTRIBUTION, Optional.of(2), node(TableScanNode.class)))))); } @@ -383,7 +383,7 @@ public void testPlanTableExecuteWhenMaxWriterTasksSpecified() @Language("SQL") String query = "ALTER TABLE partitioned_target_table EXECUTE optimize(file_size_threshold => '10MB')"; Session session = Session.builder(getQueryRunner().getDefaultSession()) - .setSystemProperty(MAX_WRITER_TASKS_COUNT, "2") + .setSystemProperty(MAX_WRITER_TASK_COUNT, "2") .setSystemProperty(USE_PREFERRED_WRITE_PARTITIONING, "true") .setCatalog(catalogNameWithMaxWriterTasksSpecified) .build(); @@ -405,7 +405,7 @@ public void testPlanTableExecuteWhenRetryPolicyIsTask() @Language("SQL") String query = "ALTER TABLE partitioned_target_table EXECUTE optimize(file_size_threshold => '10MB')"; Session session = Session.builder(getQueryRunner().getDefaultSession()) - .setSystemProperty(MAX_WRITER_TASKS_COUNT, "2") + .setSystemProperty(MAX_WRITER_TASK_COUNT, "2") .setSystemProperty(USE_PREFERRED_WRITE_PARTITIONING, "true") .setSystemProperty(RETRY_POLICY, "TASK") .setCatalog(catalogNameWithMaxWriterTasksSpecified) diff --git a/docs/src/main/sphinx/admin/properties-query-management.md b/docs/src/main/sphinx/admin/properties-query-management.md index 6f1e95ad960f..9ca758e13e71 100644 --- a/docs/src/main/sphinx/admin/properties-query-management.md +++ b/docs/src/main/sphinx/admin/properties-query-management.md @@ -61,11 +61,11 @@ joins, aggregations, partitioned window functions and others. The minimum number of partitions to use for processing distributed operations in write queries, such as joins, aggregations, partitioned window functions and others. -## `query.max-writer-tasks-count` +## `query.max-writer-task-count` - **Type:** {ref}`prop-type-integer` - **Default value:** `100` -- **Session property:** `max_writer_tasks_count` +- **Session property:** `max_writer_task_count` The maximum number of tasks that will take part in writing data during `INSERT`, `CREATE TABLE AS SELECT` and `EXECUTE` queries. diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java index 2264fe4855ec..c323d84f5ed8 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java @@ -115,7 +115,7 @@ import static io.trino.SystemSessionProperties.FAULT_TOLERANT_EXECUTION_ARBITRARY_DISTRIBUTION_WRITE_TASK_TARGET_SIZE_MIN; import static io.trino.SystemSessionProperties.FAULT_TOLERANT_EXECUTION_HASH_DISTRIBUTION_COMPUTE_TASK_TARGET_SIZE; import static io.trino.SystemSessionProperties.FAULT_TOLERANT_EXECUTION_HASH_DISTRIBUTION_WRITE_TASK_TARGET_SIZE; -import static io.trino.SystemSessionProperties.MAX_WRITER_TASKS_COUNT; +import static io.trino.SystemSessionProperties.MAX_WRITER_TASK_COUNT; import static io.trino.SystemSessionProperties.QUERY_MAX_MEMORY_PER_NODE; import static io.trino.SystemSessionProperties.REDISTRIBUTE_WRITES; import static io.trino.SystemSessionProperties.SCALE_WRITERS; @@ -4201,7 +4201,7 @@ public void testMultipleWritersWhenTaskScaleWritersIsEnabledWithMemoryLimit() } @Test - public void testWriterTasksCountLimitUnpartitioned() + public void testWriterTaskCountLimitUnpartitioned() { testLimitWriterTasks(2, 2, true, true, false, DataSize.of(1, MEGABYTE)); testLimitWriterTasks(2, 2, false, true, false, DataSize.of(1, MEGABYTE)); @@ -4209,13 +4209,13 @@ public void testWriterTasksCountLimitUnpartitioned() } @Test - public void testWriterTasksCountLimitPartitionedScaleWritersDisabled() + public void testWriterTaskCountLimitPartitionedScaleWritersDisabled() { testLimitWriterTasks(2, 2, false, true, true, DataSize.of(1, MEGABYTE)); } @Test - public void testWriterTasksCountLimitPartitionedScaleWritersEnabled() + public void testWriterTaskCountLimitPartitionedScaleWritersEnabled() { testLimitWriterTasks(2, 4, true, true, true, DataSize.of(1, MEGABYTE)); // Since we track page size for scaling writer instead of actual compressed output file size, we need to have a @@ -4227,7 +4227,7 @@ private void testLimitWriterTasks(int maxWriterTasks, int expectedFilesCount, bo { Session session = Session.builder(getSession()) .setSystemProperty(SCALE_WRITERS, Boolean.toString(scaleWritersEnabled)) - .setSystemProperty(MAX_WRITER_TASKS_COUNT, Integer.toString(maxWriterTasks)) + .setSystemProperty(MAX_WRITER_TASK_COUNT, Integer.toString(maxWriterTasks)) .setSystemProperty(REDISTRIBUTE_WRITES, Boolean.toString(redistributeWrites)) .setSystemProperty(TASK_MIN_WRITER_COUNT, "1") .setSystemProperty(WRITER_SCALING_MIN_DATA_PROCESSED, writerScalingMinDataProcessed.toString()) @@ -8318,7 +8318,7 @@ private void testOptimizeWithWriterScaling(boolean scaleWriters, boolean taskSca .setSystemProperty("task_min_writer_count", "1"); if (!scaleWriters) { - writerScalingSessionBuilder.setSystemProperty("max_writer_tasks_count", "1"); + writerScalingSessionBuilder.setSystemProperty("max_writer_task_count", "1"); } assertUpdate(writerScalingSessionBuilder.build(), "ALTER TABLE " + tableName + " EXECUTE optimize(file_size_threshold => '10kB')"); diff --git a/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/hive/TestHiveFaultTolerantExecutionConnectorTest.java b/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/hive/TestHiveFaultTolerantExecutionConnectorTest.java index 4874d865742f..3715b9809a30 100644 --- a/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/hive/TestHiveFaultTolerantExecutionConnectorTest.java +++ b/testing/trino-faulttolerant-tests/src/test/java/io/trino/faulttolerant/hive/TestHiveFaultTolerantExecutionConnectorTest.java @@ -80,21 +80,21 @@ public void testTaskWritersDoesNotScaleWithLargeMinWriterSize() @Test @Override - public void testWriterTasksCountLimitUnpartitioned() + public void testWriterTaskCountLimitUnpartitioned() { // Not applicable for fault-tolerant mode. } @Test @Override - public void testWriterTasksCountLimitPartitionedScaleWritersDisabled() + public void testWriterTaskCountLimitPartitionedScaleWritersDisabled() { // Not applicable for fault-tolerant mode. } @Test @Override - public void testWriterTasksCountLimitPartitionedScaleWritersEnabled() + public void testWriterTaskCountLimitPartitionedScaleWritersEnabled() { // Not applicable for fault-tolerant mode. } From 14eb9d4bb402aaf746f01d15c35a6f59b7005b6b Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Sun, 19 Nov 2023 20:26:23 +0100 Subject: [PATCH 347/587] Upgrade airlift to 239 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a3ee0f4e4612..0e084685eab8 100644 --- a/pom.xml +++ b/pom.xml @@ -174,7 +174,7 @@ 2.7.7-1 1.10.2 - 238 + 239 4.13.1 14.0.0 1.11.3 From 142cc780a6ee84ced510ad73054edc120def3077 Mon Sep 17 00:00:00 2001 From: James Petty Date: Wed, 15 Nov 2023 14:19:48 -0500 Subject: [PATCH 348/587] Refactor RunLengthEncodedBlock hash combination logic --- .../operator/FlatHashStrategyCompiler.java | 27 +++++++++---------- .../operator/scalar/CombineHashFunction.java | 13 +++++++++ 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/operator/FlatHashStrategyCompiler.java b/core/trino-main/src/main/java/io/trino/operator/FlatHashStrategyCompiler.java index 499ef7125004..0101154a2038 100644 --- a/core/trino-main/src/main/java/io/trino/operator/FlatHashStrategyCompiler.java +++ b/core/trino-main/src/main/java/io/trino/operator/FlatHashStrategyCompiler.java @@ -443,16 +443,6 @@ private static MethodDefinition generateHashBlockVectorized(ClassDefinition defi block, position); - BytecodeExpression setHashExpression; - if (field.index() == 0) { - // hashes[index] = hash; - setHashExpression = hashes.setElement(index, hash); - } - else { - // hashes[index] = CombineHashFunction.getHash(hashes[index], hash); - setHashExpression = hashes.setElement(index, invokeStatic(CombineHashFunction.class, "getHash", long.class, hashes.getElement(index), hash)); - } - BytecodeBlock rleHandling = new BytecodeBlock() .append(new IfStatement("hash = block.isNull(position) ? NULL_HASH_CODE : hash(block, position)") .condition(block.invoke("isNull", boolean.class, position)) @@ -463,11 +453,18 @@ private static MethodDefinition generateHashBlockVectorized(ClassDefinition defi rleHandling.append(invokeStatic(Arrays.class, "fill", void.class, hashes, constantInt(0), length, hash)); } else { - rleHandling.append(new ForLoop("for (int index = 0; index < length; index++) { hashes[index] = CombineHashFunction.getHash(hashes[index], hash); }") - .initialize(index.set(constantInt(0))) - .condition(lessThan(index, length)) - .update(index.increment()) - .body(setHashExpression)); + // CombineHashFunction.combineAllHashesWithConstant(hashes, 0, length, hash) + rleHandling.append(invokeStatic(CombineHashFunction.class, "combineAllHashesWithConstant", void.class, hashes, constantInt(0), length, hash)); + } + + BytecodeExpression setHashExpression; + if (field.index() == 0) { + // hashes[index] = hash; + setHashExpression = hashes.setElement(index, hash); + } + else { + // hashes[index] = CombineHashFunction.getHash(hashes[index], hash); + setHashExpression = hashes.setElement(index, invokeStatic(CombineHashFunction.class, "getHash", long.class, hashes.getElement(index), hash)); } BytecodeBlock computeHashLoop = new BytecodeBlock() diff --git a/core/trino-main/src/main/java/io/trino/operator/scalar/CombineHashFunction.java b/core/trino-main/src/main/java/io/trino/operator/scalar/CombineHashFunction.java index ed08ebec9bd5..42e4ef800a35 100644 --- a/core/trino-main/src/main/java/io/trino/operator/scalar/CombineHashFunction.java +++ b/core/trino-main/src/main/java/io/trino/operator/scalar/CombineHashFunction.java @@ -13,10 +13,13 @@ */ package io.trino.operator.scalar; +import io.trino.annotation.UsedByGeneratedCode; import io.trino.spi.function.ScalarFunction; import io.trino.spi.function.SqlType; import io.trino.spi.type.StandardTypes; +import static java.util.Objects.checkFromToIndex; + public final class CombineHashFunction { private CombineHashFunction() {} @@ -27,4 +30,14 @@ public static long getHash(@SqlType(StandardTypes.BIGINT) long previousHashValue { return (31 * previousHashValue + value); } + + @UsedByGeneratedCode + public static void combineAllHashesWithConstant(long[] hashes, int fromIndex, int toIndex, long value) + { + checkFromToIndex(fromIndex, toIndex, hashes.length); + + for (int i = 0; i < toIndex; i++) { + hashes[i] = (31 * hashes[i]) + value; + } + } } From 0d2dc48f63ea7685811145b39604418d479e82f5 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Thu, 9 Nov 2023 17:26:57 +0100 Subject: [PATCH 349/587] Catch all "not found" exceptions Table/view not found can be indicated by `TrinoException` with `TABLE_NOT_FOUND` error code, but also by `TableNotFoundException` or `ViewNotFoundException` exceptions (both using `NOT_FOUND` error code). The code catching "not found" exceptions should catch all three. --- .../src/main/java/io/trino/metadata/MetadataManager.java | 2 ++ .../main/java/io/trino/spi/connector/ConnectorMetadata.java | 2 ++ .../src/main/java/io/trino/plugin/hive/HiveMetadata.java | 2 +- .../main/java/io/trino/plugin/hive/TrinoViewHiveMetastore.java | 2 +- .../io/trino/plugin/iceberg/catalog/AbstractTrinoCatalog.java | 3 ++- 5 files changed, 8 insertions(+), 3 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java b/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java index 8146c5430499..585629ef6c1f 100644 --- a/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java +++ b/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java @@ -165,6 +165,7 @@ import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; import static io.trino.spi.StandardErrorCode.SCHEMA_NOT_FOUND; import static io.trino.spi.StandardErrorCode.SYNTAX_ERROR; +import static io.trino.spi.StandardErrorCode.TABLE_NOT_FOUND; import static io.trino.spi.StandardErrorCode.TABLE_REDIRECTION_ERROR; import static io.trino.spi.StandardErrorCode.UNSUPPORTED_TABLE_TYPE; import static io.trino.spi.connector.MaterializedViewFreshness.Freshness.STALE; @@ -600,6 +601,7 @@ public List listTableColumns(Session session, QualifiedTab ErrorCode errorCode = trinoException.getErrorCode(); silent = errorCode.equals(UNSUPPORTED_TABLE_TYPE.toErrorCode()) || // e.g. table deleted concurrently + errorCode.equals(TABLE_NOT_FOUND.toErrorCode()) || errorCode.equals(NOT_FOUND.toErrorCode()) || // e.g. Iceberg/Delta table being deleted concurrently resulting in failure to load metadata from filesystem errorCode.getType() == EXTERNAL; diff --git a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMetadata.java b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMetadata.java index 8191761d9924..09842afe22c6 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMetadata.java +++ b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMetadata.java @@ -62,6 +62,7 @@ import static io.trino.spi.StandardErrorCode.GENERIC_INTERNAL_ERROR; import static io.trino.spi.StandardErrorCode.NOT_FOUND; import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; +import static io.trino.spi.StandardErrorCode.TABLE_NOT_FOUND; import static io.trino.spi.StandardErrorCode.UNSUPPORTED_TABLE_TYPE; import static io.trino.spi.connector.SaveMode.IGNORE; import static io.trino.spi.connector.SaveMode.REPLACE; @@ -409,6 +410,7 @@ default Iterator streamRelationComments(ConnectorSessio ErrorCode errorCode = trinoException.getErrorCode(); silent = errorCode.equals(UNSUPPORTED_TABLE_TYPE.toErrorCode()) || // e.g. table deleted concurrently + errorCode.equals(TABLE_NOT_FOUND.toErrorCode()) || errorCode.equals(NOT_FOUND.toErrorCode()) || // e.g. Iceberg/Delta table being deleted concurrently resulting in failure to load metadata from filesystem errorCode.getType() == EXTERNAL; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java index 38fccc9b136d..376d758689ba 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java @@ -2790,7 +2790,7 @@ public Map getViews(ConnectorSession s else if (e.getErrorCode().equals(HIVE_INVALID_VIEW_DATA.toErrorCode())) { // Ignore views that are not valid } - else if (e.getErrorCode().equals(TABLE_NOT_FOUND.toErrorCode())) { + else if (e.getErrorCode().equals(TABLE_NOT_FOUND.toErrorCode()) || e instanceof TableNotFoundException || e instanceof ViewNotFoundException) { // Ignore view that was dropped during query execution (race condition) } else { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/TrinoViewHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/TrinoViewHiveMetastore.java index 89ca55fc702f..759a512f06f3 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/TrinoViewHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/TrinoViewHiveMetastore.java @@ -143,7 +143,7 @@ public Map getViews(Optional s getView(name).ifPresent(view -> views.put(name, view)); } catch (TrinoException e) { - if (e.getErrorCode().equals(TABLE_NOT_FOUND.toErrorCode())) { + if (e.getErrorCode().equals(TABLE_NOT_FOUND.toErrorCode()) || e instanceof TableNotFoundException || e instanceof ViewNotFoundException) { // Ignore view that was dropped during query execution (race condition) } else { diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractTrinoCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractTrinoCatalog.java index 922d5293f5f3..769f1c041992 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractTrinoCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractTrinoCatalog.java @@ -35,6 +35,7 @@ import io.trino.spi.connector.ConnectorViewDefinition; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.connector.TableNotFoundException; +import io.trino.spi.connector.ViewNotFoundException; import io.trino.spi.type.ArrayType; import io.trino.spi.type.CharType; import io.trino.spi.type.MapType; @@ -169,7 +170,7 @@ public Map getViews(ConnectorSession s getView(session, name).ifPresent(view -> views.put(name, view)); } catch (TrinoException e) { - if (e.getErrorCode().equals(TABLE_NOT_FOUND.toErrorCode())) { + if (e.getErrorCode().equals(TABLE_NOT_FOUND.toErrorCode()) || e instanceof TableNotFoundException || e instanceof ViewNotFoundException) { // Ignore view that was dropped during query execution (race condition) } else { From 9c269e18ffb4857dfc9c56ef67d583cd4b94b1dd Mon Sep 17 00:00:00 2001 From: James Petty Date: Mon, 20 Nov 2023 11:56:08 -0500 Subject: [PATCH 350/587] Fix loop index range in CombineHashFunction --- .../main/java/io/trino/operator/scalar/CombineHashFunction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/trino-main/src/main/java/io/trino/operator/scalar/CombineHashFunction.java b/core/trino-main/src/main/java/io/trino/operator/scalar/CombineHashFunction.java index 42e4ef800a35..d04b2b786010 100644 --- a/core/trino-main/src/main/java/io/trino/operator/scalar/CombineHashFunction.java +++ b/core/trino-main/src/main/java/io/trino/operator/scalar/CombineHashFunction.java @@ -36,7 +36,7 @@ public static void combineAllHashesWithConstant(long[] hashes, int fromIndex, in { checkFromToIndex(fromIndex, toIndex, hashes.length); - for (int i = 0; i < toIndex; i++) { + for (int i = fromIndex; i < toIndex; i++) { hashes[i] = (31 * hashes[i]) + value; } } From add33e2f9e604ee50b2a7857b3565773f029fd98 Mon Sep 17 00:00:00 2001 From: Manfred Moser Date: Fri, 10 Nov 2023 16:00:28 -0800 Subject: [PATCH 351/587] Improve docs for MERGE --- docs/src/main/sphinx/sql/merge.md | 43 ++++++++++++++++++------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/docs/src/main/sphinx/sql/merge.md b/docs/src/main/sphinx/sql/merge.md index e7b65257763f..d2b0f3c1d1f5 100644 --- a/docs/src/main/sphinx/sql/merge.md +++ b/docs/src/main/sphinx/sql/merge.md @@ -31,28 +31,36 @@ WHEN NOT MATCHED [ AND condition ] Conditionally update and/or delete rows of a table and/or insert new rows into a table. -`MERGE` supports an arbitrary number of `WHEN` clauses with different -`MATCHED` conditions, executing the `DELETE`, `UPDATE` or `INSERT` -operation in the first `WHEN` clause selected by the `MATCHED` -state and the match condition. +`MERGE`changes data in the `target_table` based on the contents of the +`source_table`. The `search_condition` defines a condition, such as a relation +from identical columns, to associate the source and target data. -For each source row, the `WHEN` clauses are processed in order. Only -the first first matching `WHEN` clause is executed and subsequent clauses -are ignored. A `MERGE_TARGET_ROW_MULTIPLE_MATCHES` exception is -raised when a single target table row matches more than one source row. +`MERGE` supports an arbitrary number of `WHEN` clauses. `MATCHED` conditions can +execute `DELETE` or `UPDATE` operations on the target data, while `NOT MATCHED` +conditions can add data from the source to the target table with `INSERT`. +Additional conditions can narrow down the affected rows. -If a source row is not matched by any `WHEN` clause and there is no -`WHEN NOT MATCHED` clause, the source row is ignored. +For each source row, the `WHEN` clauses are processed in order. Only the first +matching `WHEN` clause is executed and subsequent clauses are ignored. The query +fails if a single target table row matches more than one source row. In `WHEN` clauses with `UPDATE` operations, the column value expressions -can depend on any field of the target or the source. In the `NOT MATCHED` +can depend on any field of the target or the source. In the `NOT MATCHED` case, the `INSERT` expressions can depend on any field of the source. +Typical usage of `MERGE` involves two tables with similar structure, containing +different data. For example, the source table is part of a transactional usage +in a production system, while the target table is located in a data warehouse +used for analytics. Periodically, `MERGE` operations are run to combine recent +production data with long-term data in the analytics warehouse. As long as you +can define a search condition between the two tables, you can also use very +different tables. + ## Examples Delete all customers mentioned in the source table: -``` +```sql MERGE INTO accounts t USING monthly_accounts_update s ON t.customer = s.customer WHEN MATCHED @@ -62,7 +70,7 @@ MERGE INTO accounts t USING monthly_accounts_update s For matching customer rows, increment the purchases, and if there is no match, insert the row from the source table: -``` +```sql MERGE INTO accounts t USING monthly_accounts_update s ON (t.customer = s.customer) WHEN MATCHED @@ -73,12 +81,11 @@ MERGE INTO accounts t USING monthly_accounts_update s ``` `MERGE` into the target table from the source table, deleting any matching -target row for which the source address is Centreville. For all other -matching rows, add the source purchases and set the address to the source -address, if there is no match in the target table, insert the source -table row: +target row for which the source address is `Centreville`. For all other matching +rows, add the source purchases and set the address to the source address. If +there is no match in the target table, insert the source table row: -``` +```sql MERGE INTO accounts t USING monthly_accounts_update s ON (t.customer = s.customer) WHEN MATCHED AND s.address = 'Centreville' From bb61e14768cdec97fb4e0310f2398fab4f958a6e Mon Sep 17 00:00:00 2001 From: Manfred Moser Date: Wed, 15 Nov 2023 17:24:57 -0800 Subject: [PATCH 352/587] Convert more docs to markdown source --- docs/src/main/sphinx/client/jdbc.md | 14 ++- docs/src/main/sphinx/conf.py | 4 - .../jdbc-common-configurations.fragment | 93 +++++++++---------- .../join-pushdown-enabled-true.fragment | 41 ++++---- .../sphinx/connector/json-decoder.fragment | 45 +++++---- docs/src/main/sphinx/connector/mongodb.md | 24 ++--- .../sphinx/connector/raw-decoder.fragment | 39 ++++---- docs/src/main/sphinx/connector/system.md | 10 +- .../sphinx/sql/reset-session-authorization.md | 17 ++++ .../sql/reset-session-authorization.rst | 22 ----- .../sphinx/sql/set-session-authorization.md | 49 ++++++++++ .../sphinx/sql/set-session-authorization.rst | 47 ---------- 12 files changed, 196 insertions(+), 209 deletions(-) create mode 100644 docs/src/main/sphinx/sql/reset-session-authorization.md delete mode 100644 docs/src/main/sphinx/sql/reset-session-authorization.rst create mode 100644 docs/src/main/sphinx/sql/set-session-authorization.md delete mode 100644 docs/src/main/sphinx/sql/set-session-authorization.rst diff --git a/docs/src/main/sphinx/client/jdbc.md b/docs/src/main/sphinx/client/jdbc.md index cf974490f9cb..8b2f8a8e04d8 100644 --- a/docs/src/main/sphinx/client/jdbc.md +++ b/docs/src/main/sphinx/client/jdbc.md @@ -23,14 +23,12 @@ Download {maven_download}`jdbc` and add it to the classpath of your Java applica The driver is also available from Maven Central: -```{eval-rst} -.. parsed-literal:: - - - io.trino - trino-jdbc - \ |version|\ - +```xml + + io.trino + trino-jdbc + |trino_version| + ``` We recommend using the latest version of the JDBC driver. A list of all diff --git a/docs/src/main/sphinx/conf.py b/docs/src/main/sphinx/conf.py index baa17cfe1cfc..4f15464425bb 100644 --- a/docs/src/main/sphinx/conf.py +++ b/docs/src/main/sphinx/conf.py @@ -102,10 +102,6 @@ def setup(app): default_role = 'backquote' -rst_epilog = """ -.. |trino_server_release| replace:: ``trino-server-{release}`` -""".replace('{release}', release) - # Any replace that is inside of a code block should be added here # https://stackoverflow.com/questions/8821511/substitutions-inside-sphinx-code-blocks-arent-replaced diff --git a/docs/src/main/sphinx/connector/jdbc-common-configurations.fragment b/docs/src/main/sphinx/connector/jdbc-common-configurations.fragment index 9846f44595e0..dc30d1a1a671 100644 --- a/docs/src/main/sphinx/connector/jdbc-common-configurations.fragment +++ b/docs/src/main/sphinx/connector/jdbc-common-configurations.fragment @@ -3,52 +3,51 @@ The following table describes general catalog configuration properties for the connector: -```{eval-rst} -.. list-table:: - :widths: 30, 40, 30 - :header-rows: 1 +:::{list-table} +:widths: 30, 40, 30 +:header-rows: 1 - * - Property name - - Description - - Default value - * - ``case-insensitive-name-matching`` - - Support case insensitive schema and table names. - - ``false`` - * - ``case-insensitive-name-matching.cache-ttl`` - - This value should be a :ref:`prop-type-duration`. - - ``1m`` - * - ``case-insensitive-name-matching.config-file`` - - Path to a name mapping configuration file in JSON format that allows - Trino to disambiguate between schemas and tables with similar names in - different cases. - - ``null`` - * - ``case-insensitive-name-matching.config-file.refresh-period`` - - Frequency with which Trino checks the name matching configuration file - for changes. This value should be a :ref:`prop-type-duration`. - - (refresh disabled) - * - ``metadata.cache-ttl`` - - :ref:`The duration ` for which metadata, including +* - Property name + - Description + - Default value +* - `case-insensitive-name-matching` + - Support case insensitive schema and table names. + - `false` +* - `case-insensitive-name-matching.cache-ttl` + - This value should be a [](prop-type-duration). + - `1m` +* - `case-insensitive-name-matching.config-file` + - Path to a name mapping configuration file in JSON format that allows + Trino to disambiguate between schemas and tables with similar names in + different cases. + - `null` +* - `case-insensitive-name-matching.config-file.refresh-period` + - Frequency with which Trino checks the name matching configuration file + for changes. This value should be a [](prop-type-duration). + - (refresh disabled) +* - `metadata.cache-ttl` + - [The duration](prop-type-duration) for which metadata, including table and column statistics, is cached. - - ``0s`` (caching disabled) - * - ``metadata.cache-missing`` - - Cache the fact that metadata, including table and column statistics, is - not available - - ``false`` - * - ``metadata.cache-maximum-size`` - - Maximum number of objects stored in the metadata cache - - ``10000`` - * - ``write.batch-size`` - - Maximum number of statements in a batched execution. - Do not change this setting from the default. Non-default values may - negatively impact performance. - - ``1000`` - * - ``dynamic-filtering.enabled`` - - Push down dynamic filters into JDBC queries - - ``true`` - * - ``dynamic-filtering.wait-timeout`` - - Maximum :ref:`prop-type-duration` for which Trino will wait for dynamic - filters to be collected from the build side of joins before starting a - JDBC query. Using a large timeout can potentially result in more detailed - dynamic filters. However, it can also increase latency for some queries. - - ``20s`` -``` + - `0s` (caching disabled) +* - `metadata.cache-missing` + - Cache the fact that metadata, including table and column statistics, is + not available + - `false` +* - `metadata.cache-maximum-size` + - Maximum number of objects stored in the metadata cache + - `10000` +* - `write.batch-size` + - Maximum number of statements in a batched execution. Do not change + this setting from the default. Non-default values may negatively + impact performance. + - `1000` +* - `dynamic-filtering.enabled` + - Push down dynamic filters into JDBC queries + - `true` +* - `dynamic-filtering.wait-timeout` + - Maximum [](prop-type-duration) for which Trino waits for dynamic + filters to be collected from the build side of joins before starting a + JDBC query. Using a large timeout can potentially result in more detailed + dynamic filters. However, it can also increase latency for some queries. + - `20s` +::: diff --git a/docs/src/main/sphinx/connector/join-pushdown-enabled-true.fragment b/docs/src/main/sphinx/connector/join-pushdown-enabled-true.fragment index abc68ba054b1..738785f2d28e 100644 --- a/docs/src/main/sphinx/connector/join-pushdown-enabled-true.fragment +++ b/docs/src/main/sphinx/connector/join-pushdown-enabled-true.fragment @@ -12,25 +12,24 @@ performance. The following table describes catalog configuration properties for join pushdown: -```{eval-rst} -.. list-table:: - :widths: 30, 40, 30 - :header-rows: 1 +:::{list-table} +:widths: 30, 40, 30 +:header-rows: 1 - * - Property name - - Description - - Default value - * - ``join-pushdown.enabled`` - - Enable :ref:`join pushdown `. Equivalent :ref:`catalog - session property ` is - ``join_pushdown_enabled``. - - ``true`` - * - ``join-pushdown.strategy`` - - Strategy used to evaluate whether join operations are pushed down. Set to - ``AUTOMATIC`` to enable cost-based join pushdown, or ``EAGER`` to - push down joins whenever possible. Note that ``EAGER`` can push down joins - even when table statistics are unavailable, which may result in degraded - query performance. Because of this, ``EAGER`` is only recommended for - testing and troubleshooting purposes. - - ``AUTOMATIC`` -``` +* - Property name + - Description + - Default value +* - `join-pushdown.enabled` + - Enable [join pushdown](join-pushdown). Equivalent [catalog + session property](session-properties-definition) is + `join_pushdown_enabled`. + - `true` +* - `join-pushdown.strategy` + - Strategy used to evaluate whether join operations are pushed down. Set to + `AUTOMATIC` to enable cost-based join pushdown, or `EAGER` to + push down joins whenever possible. Note that `EAGER` can push down joins + even when table statistics are unavailable, which may result in degraded + query performance. Because of this, `EAGER` is only recommended for + testing and troubleshooting purposes. + - `AUTOMATIC` +::: diff --git a/docs/src/main/sphinx/connector/json-decoder.fragment b/docs/src/main/sphinx/connector/json-decoder.fragment index 197be96f200d..b6ce45611452 100644 --- a/docs/src/main/sphinx/connector/json-decoder.fragment +++ b/docs/src/main/sphinx/connector/json-decoder.fragment @@ -19,30 +19,29 @@ types. The following table lists Trino data types, which can be used in `type` and matching field decoders, and specified via `dataFormat` attribute: -```{eval-rst} -.. list-table:: - :widths: 40, 60 - :header-rows: 1 +:::{list-table} +:widths: 40, 60 +:header-rows: 1 - * - Trino data type - - Allowed ``dataFormat`` values - * - ``BIGINT``, ``INTEGER``, ``SMALLINT``, ``TINYINT``, ``DOUBLE``, - ``BOOLEAN``, ``VARCHAR``, ``VARCHAR(x)`` - - Default field decoder (omitted ``dataFormat`` attribute) - * - ``DATE`` - - ``custom-date-time``, ``iso8601`` - * - ``TIME`` - - ``custom-date-time``, ``iso8601``, ``milliseconds-since-epoch``, - ``seconds-since-epoch`` - * - ``TIME WITH TIME ZONE`` - - ``custom-date-time``, ``iso8601`` - * - ``TIMESTAMP`` - - ``custom-date-time``, ``iso8601``, ``rfc2822``, - ``milliseconds-since-epoch``, ``seconds-since-epoch`` - * - ``TIMESTAMP WITH TIME ZONE`` - - ``custom-date-time``, ``iso8601``, ``rfc2822``, - ``milliseconds-since-epoch``, ``seconds-since-epoch`` -``` +* - Trino data type + - Allowed `dataFormat` values +* - `BIGINT`, `INTEGER`, `SMALLINT`, `TINYINT`, `DOUBLE`, `BOOLEAN`, + `VARCHAR`, `VARCHAR(x)` + - Default field decoder (omitted `dataFormat` attribute) +* - `DATE` + - `custom-date-time`, `iso8601` +* - `TIME` + - `custom-date-time`, `iso8601`, `milliseconds-since-epoch`, + `seconds-since-epoch` +* - `TIME WITH TIME ZONE` + - `custom-date-time`, `iso8601` +* - `TIMESTAMP` + - `custom-date-time`, `iso8601`, `rfc2822`, `milliseconds-since-epoch`, + `seconds-since-epoch` +* - `TIMESTAMP WITH TIME ZONE` + - `custom-date-time`, `iso8601`, `rfc2822`, + `milliseconds-since-epoch`, `seconds-since-epoch` +::: No other types are supported. diff --git a/docs/src/main/sphinx/connector/mongodb.md b/docs/src/main/sphinx/connector/mongodb.md index 77269717f780..ff66ceaef678 100644 --- a/docs/src/main/sphinx/connector/mongodb.md +++ b/docs/src/main/sphinx/connector/mongodb.md @@ -315,23 +315,23 @@ SELECT CAST(_id AS VARCHAR), * FROM orders WHERE _id = ObjectId('55b151633864d64 The first four bytes of each [ObjectId](https://docs.mongodb.com/manual/reference/method/ObjectId) represent an embedded timestamp of its creation time. Trino provides a couple of functions to take advantage of this MongoDB feature. -```{eval-rst} -.. function:: objectid_timestamp(ObjectId) -> timestamp +:::{function} objectid_timestamp(ObjectId) -> timestamp +Extracts the TIMESTAMP WITH TIME ZONE from a given ObjectId: - Extracts the TIMESTAMP WITH TIME ZONE from a given ObjectId:: - - SELECT objectid_timestamp(ObjectId('507f191e810c19729de860ea')); - -- 2012-10-17 20:46:22.000 UTC +```sql +SELECT objectid_timestamp(ObjectId('507f191e810c19729de860ea')); +-- 2012-10-17 20:46:22.000 UTC ``` +::: -```{eval-rst} -.. function:: timestamp_objectid(timestamp) -> ObjectId - - Creates an ObjectId from a TIMESTAMP WITH TIME ZONE:: +:::{function} timestamp_objectid(timestamp) -> ObjectId +Creates an ObjectId from a TIMESTAMP WITH TIME ZONE: - SELECT timestamp_objectid(TIMESTAMP '2021-08-07 17:51:36 +00:00'); - -- 61 0e c8 28 00 00 00 00 00 00 00 00 +```sql +SELECT timestamp_objectid(TIMESTAMP '2021-08-07 17:51:36 +00:00'); +-- 61 0e c8 28 00 00 00 00 00 00 00 00 ``` +::: In MongoDB, you can filter all the documents created after `2021-08-07 17:51:36` with a query like this: diff --git a/docs/src/main/sphinx/connector/raw-decoder.fragment b/docs/src/main/sphinx/connector/raw-decoder.fragment index 68d2de457445..12a6718ef5f5 100644 --- a/docs/src/main/sphinx/connector/raw-decoder.fragment +++ b/docs/src/main/sphinx/connector/raw-decoder.fragment @@ -28,26 +28,25 @@ The `type` attribute defines the Trino data type on which the value is mapped. Depending on the Trino type assigned to a column, different values of dataFormat can be used: -```{eval-rst} -.. list-table:: - :widths: 40, 60 - :header-rows: 1 - - * - Trino data type - - Allowed ``dataFormat`` values - * - ``BIGINT`` - - ``BYTE``, ``SHORT``, ``INT``, ``LONG`` - * - ``INTEGER`` - - ``BYTE``, ``SHORT``, ``INT`` - * - ``SMALLINT`` - - ``BYTE``, ``SHORT`` - * - ``DOUBLE`` - - ``DOUBLE``, ``FLOAT`` - * - ``BOOLEAN`` - - ``BYTE``, ``SHORT``, ``INT``, ``LONG`` - * - ``VARCHAR`` / ``VARCHAR(x)`` - - ``BYTE`` -``` +:::{list-table} +:widths: 40, 60 +:header-rows: 1 + +* - Trino data type + - Allowed `dataFormat` values +* - `BIGINT` + - `BYTE`, `SHORT`, `INT`, `LONG` +* - `INTEGER` + - `BYTE`, `SHORT`, `INT` +* - `SMALLINT` + - `BYTE`, `SHORT` +* - `DOUBLE` + - `DOUBLE`, `FLOAT` +* - `BOOLEAN` + - `BYTE`, `SHORT`, `INT`, `LONG` +* - `VARCHAR` / `VARCHAR(x)` + - `BYTE` +::: No other types are supported. diff --git a/docs/src/main/sphinx/connector/system.md b/docs/src/main/sphinx/connector/system.md index 07ad18fc87d1..a3de46eb182d 100644 --- a/docs/src/main/sphinx/connector/system.md +++ b/docs/src/main/sphinx/connector/system.md @@ -137,12 +137,12 @@ idle time, initialization parameters, and accessed catalogs. ## System connector procedures -```{eval-rst} -.. function:: runtime.kill_query(query_id, message) +:::{function} runtime.kill_query(query_id, message) - Kill the query identified by ``query_id``. The query failure message - includes the specified ``message``. ``message`` is optional. -``` +Kill the query identified by `query_id`. The query failure message includes the +specified `message`. `message` is optional. + +::: (system-type-mapping)= diff --git a/docs/src/main/sphinx/sql/reset-session-authorization.md b/docs/src/main/sphinx/sql/reset-session-authorization.md new file mode 100644 index 000000000000..6665f9f56ba1 --- /dev/null +++ b/docs/src/main/sphinx/sql/reset-session-authorization.md @@ -0,0 +1,17 @@ +# RESET SESSION AUTHORIZATION + +## Synopsis + +```text +RESET SESSION AUTHORIZATION +``` + +## Description + +Resets the current authorization user back to the original user. The original +user is usually the authenticated user (principal), or it can be the session +user when the session user is provided by the client. + +## See also + +[](set-session-authorization) diff --git a/docs/src/main/sphinx/sql/reset-session-authorization.rst b/docs/src/main/sphinx/sql/reset-session-authorization.rst deleted file mode 100644 index b1b163a5c90a..000000000000 --- a/docs/src/main/sphinx/sql/reset-session-authorization.rst +++ /dev/null @@ -1,22 +0,0 @@ -=========================== -RESET SESSION AUTHORIZATION -=========================== - -Synopsis --------- - -.. code-block:: none - - RESET SESSION AUTHORIZATION - -Description ------------ - -Resets the current authorization user back to the original user. -The original user is usually the authenticated user (principal), -or it can be the session user when the session user is provided by the client. - -See Also --------- - -:doc:`set-session-authorization` diff --git a/docs/src/main/sphinx/sql/set-session-authorization.md b/docs/src/main/sphinx/sql/set-session-authorization.md new file mode 100644 index 000000000000..719bc2506a5f --- /dev/null +++ b/docs/src/main/sphinx/sql/set-session-authorization.md @@ -0,0 +1,49 @@ +# SET SESSION AUTHORIZATION + +## Synopsis + +```text +SET SESSION AUTHORIZATION username +``` + +## Description + +Changes the current user of the session. For the `SET SESSION AUTHORIZATION +username` statement to succeed, the original user (that the client connected +with) must be able to impersonate the specified user. User impersonation can be +enabled in the system access control. + +## Examples + +In the following example, the original user when the connection to Trino is made +is Kevin. The following sets the session authorization user to John: + +```sql +SET SESSION AUTHORIZATION 'John'; +``` + +Queries will now execute as John instead of Kevin. + +All supported syntax to change the session authorization users are shown below. + +Changing the session authorization with single quotes: + +```sql +SET SESSION AUTHORIZATION 'John'; +``` + +Changing the session authorization with double quotes: + +```sql +SET SESSION AUTHORIZATION "John"; +``` + +Changing the session authorization without quotes: + +```sql +SET SESSION AUTHORIZATION John; +``` + +## See also + +[](reset-session-authorization) diff --git a/docs/src/main/sphinx/sql/set-session-authorization.rst b/docs/src/main/sphinx/sql/set-session-authorization.rst deleted file mode 100644 index 98634dd11235..000000000000 --- a/docs/src/main/sphinx/sql/set-session-authorization.rst +++ /dev/null @@ -1,47 +0,0 @@ -========================= -SET SESSION AUTHORIZATION -========================= - -Synopsis --------- - -.. code-block:: none - - SET SESSION AUTHORIZATION username - -Description ------------ - -Changes the current user of the session. -For the ``SET SESSION AUTHORIZATION username`` statement to succeed, -the the original user (that the client connected with) must be able to impersonate the specified user. -User impersonation can be enabled in the system access control. - -Examples --------- - -In the following example, the original user when the connection to Trino is made is Kevin. -The following sets the session authorization user to John:: - - SET SESSION AUTHORIZATION 'John'; - -Queries will now execute as John instead of Kevin. - -All supported syntax to change the session authorization users are shown below. - -Changing the session authorization with single quotes:: - - SET SESSION AUTHORIZATION 'John'; - -Changing the session authorization with double quotes:: - - SET SESSION AUTHORIZATION "John"; - -Changing the session authorization without quotes:: - - SET SESSION AUTHORIZATION John; - -See Also --------- - -:doc:`reset-session-authorization` From 064f01296caa5e73873ba02dfbfbd8e0792f91d7 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Fri, 17 Nov 2023 17:13:23 +0100 Subject: [PATCH 353/587] Update s3mock-testcontainers to 3.2.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0e084685eab8..020fc4dc81b9 100644 --- a/pom.xml +++ b/pom.xml @@ -298,7 +298,7 @@ com.adobe.testing s3mock-testcontainers - 3.1.0 + 3.2.0 From ed54241206f7cba8d2423d61f3278e19275b9675 Mon Sep 17 00:00:00 2001 From: Wei Zheng Date: Wed, 2 Aug 2023 17:25:03 -0700 Subject: [PATCH 354/587] Improve doc for Window functions --- docs/src/main/sphinx/functions/window.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/src/main/sphinx/functions/window.md b/docs/src/main/sphinx/functions/window.md index f5b786b6b9a6..e67017b1dae6 100644 --- a/docs/src/main/sphinx/functions/window.md +++ b/docs/src/main/sphinx/functions/window.md @@ -23,8 +23,9 @@ The window can be specified in two ways (see {ref}`window-clause`): ## Aggregate functions All {doc}`aggregate` can be used as window functions by adding the `OVER` -clause. The aggregate function is computed for each row over the rows within -the current row's window frame. +clause. The aggregate function is computed for each row over the rows within the +current row's window frame. Note that [ordering during +aggregation](aggregate-function-ordering-during-aggregation) is not supported. For example, the following query produces a rolling sum of order prices by day for each clerk: From 44e9876bd37f6b6f872d2dd4b78c33aed0f2f41e Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Mon, 20 Nov 2023 13:03:47 +0100 Subject: [PATCH 355/587] Remove redundant analyzeColumns from IcebergTableHandle The information was not correct when ANALYZE was invoked without `columns` parameter -- instead of all columns, an empty set was there. The information can be replaced with a single boolean. --- .../trino/plugin/iceberg/IcebergMetadata.java | 12 +++++----- .../plugin/iceberg/IcebergTableHandle.java | 24 +++++++++---------- ...stIcebergNodeLocalDynamicSplitPruning.java | 2 +- .../iceberg/TestIcebergSplitSource.java | 2 +- ...TestConnectorPushdownRulesWithIceberg.java | 8 +++---- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java index 9c3b1e34ec6e..822585bd6bfb 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java @@ -468,7 +468,7 @@ private IcebergTableHandle tableHandleForSnapshot( false, Optional.empty(), ImmutableSet.of(), - Optional.empty()); + Optional.of(false)); } private static long getSnapshotIdFromVersion(ConnectorSession session, Table table, ConnectorTableVersion version) @@ -713,7 +713,7 @@ public ColumnMetadata getColumnMetadata(ConnectorSession session, ConnectorTable public void validateScan(ConnectorSession session, ConnectorTableHandle handle) { IcebergTableHandle table = (IcebergTableHandle) handle; - if (isQueryPartitionFilterRequired(session) && table.getEnforcedPredicate().isAll() && table.getAnalyzeColumns().isEmpty()) { + if (isQueryPartitionFilterRequired(session) && table.getEnforcedPredicate().isAll() && !table.getForAnalyze().orElseThrow()) { Schema schema = SchemaParser.fromJson(table.getTableSchemaJson()); Optional partitionSpec = table.getPartitionSpecJson() .map(partitionSpecJson -> PartitionSpecParser.fromJson(schema, partitionSpecJson)); @@ -2105,7 +2105,7 @@ public ConnectorAnalyzeMetadata getStatisticsCollectionMetadata(ConnectorSession }); return new ConnectorAnalyzeMetadata( - handle.withAnalyzeColumns(analyzeColumnNames.or(() -> Optional.of(ImmutableSet.of()))), + handle.forAnalyze(), getStatisticsCollectionMetadata( tableMetadata, analyzeColumnNames, @@ -2461,7 +2461,7 @@ public Optional> applyLimit(Connect table.isRecordScannedFiles(), table.getMaxScannedFileSize(), table.getConstraintColumns(), - table.getAnalyzeColumns()); + table.getForAnalyze()); return Optional.of(new LimitApplicationResult<>(table, false, false)); } @@ -2562,7 +2562,7 @@ else if (isMetadataColumnId(columnHandle.getId())) { table.isRecordScannedFiles(), table.getMaxScannedFileSize(), Sets.union(table.getConstraintColumns(), newConstraintColumns), - table.getAnalyzeColumns()), + table.getForAnalyze()), remainingConstraint.transformKeys(ColumnHandle.class::cast), extractionResult.remainingExpression(), false)); @@ -2712,7 +2712,7 @@ public TableStatistics getTableStatistics(ConnectorSession session, ConnectorTab originalHandle.isRecordScannedFiles(), originalHandle.getMaxScannedFileSize(), originalHandle.getConstraintColumns(), - originalHandle.getAnalyzeColumns()), + originalHandle.getForAnalyze()), handle -> { Table icebergTable = catalog.loadTable(session, handle.getSchemaTableName()); return TableStatisticsReader.getTableStatistics(typeManager, session, handle, icebergTable); diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergTableHandle.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergTableHandle.java index b71cd5913da9..cc8a23e72a9b 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergTableHandle.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergTableHandle.java @@ -68,8 +68,8 @@ public class IcebergTableHandle private final boolean recordScannedFiles; private final Optional maxScannedFileSize; - // ANALYZE only - private final Optional> analyzeColumns; + // ANALYZE only. Coordinator-only + private final Optional forAnalyze; @JsonCreator public static IcebergTableHandle fromJsonForDeserializationOnly( @@ -130,7 +130,7 @@ public IcebergTableHandle( boolean recordScannedFiles, Optional maxScannedFileSize, Set constraintColumns, - Optional> analyzeColumns) + Optional forAnalyze) { this.catalog = requireNonNull(catalog, "catalog is null"); this.schemaName = requireNonNull(schemaName, "schemaName is null"); @@ -150,7 +150,7 @@ public IcebergTableHandle( this.recordScannedFiles = recordScannedFiles; this.maxScannedFileSize = requireNonNull(maxScannedFileSize, "maxScannedFileSize is null"); this.constraintColumns = ImmutableSet.copyOf(requireNonNull(constraintColumns, "constraintColumns is null")); - this.analyzeColumns = requireNonNull(analyzeColumns, "analyzeColumns is null"); + this.forAnalyze = requireNonNull(forAnalyze, "forAnalyze is null"); } @JsonProperty @@ -263,9 +263,9 @@ public Set getConstraintColumns() } @JsonIgnore - public Optional> getAnalyzeColumns() + public Optional getForAnalyze() { - return analyzeColumns; + return forAnalyze; } public SchemaTableName getSchemaTableName() @@ -299,10 +299,10 @@ public IcebergTableHandle withProjectedColumns(Set projecte recordScannedFiles, maxScannedFileSize, constraintColumns, - analyzeColumns); + forAnalyze); } - public IcebergTableHandle withAnalyzeColumns(Optional> analyzeColumns) + public IcebergTableHandle forAnalyze() { return new IcebergTableHandle( catalog, @@ -323,7 +323,7 @@ public IcebergTableHandle withAnalyzeColumns(Optional> analyzeColumn recordScannedFiles, maxScannedFileSize, constraintColumns, - analyzeColumns); + Optional.of(true)); } public IcebergTableHandle forOptimize(boolean recordScannedFiles, DataSize maxScannedFileSize) @@ -347,7 +347,7 @@ public IcebergTableHandle forOptimize(boolean recordScannedFiles, DataSize maxSc recordScannedFiles, Optional.of(maxScannedFileSize), constraintColumns, - analyzeColumns); + forAnalyze); } @Override @@ -379,7 +379,7 @@ public boolean equals(Object o) Objects.equals(storageProperties, that.storageProperties) && Objects.equals(maxScannedFileSize, that.maxScannedFileSize) && Objects.equals(constraintColumns, that.constraintColumns) && - Objects.equals(analyzeColumns, that.analyzeColumns); + Objects.equals(forAnalyze, that.forAnalyze); } @Override @@ -404,7 +404,7 @@ public int hashCode() recordScannedFiles, maxScannedFileSize, constraintColumns, - analyzeColumns); + forAnalyze); } @Override diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergNodeLocalDynamicSplitPruning.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergNodeLocalDynamicSplitPruning.java index 7733f28ffa62..686e76eed7b0 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergNodeLocalDynamicSplitPruning.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergNodeLocalDynamicSplitPruning.java @@ -188,7 +188,7 @@ private static ConnectorPageSource createTestingPageSource(HiveTransactionHandle false, Optional.empty(), ImmutableSet.of(), - Optional.empty()), + Optional.of(false)), transaction); FileFormatDataSourceStats stats = new FileFormatDataSourceStats(); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergSplitSource.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergSplitSource.java index 1de4b30b4420..0f54199c85e7 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergSplitSource.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergSplitSource.java @@ -160,7 +160,7 @@ public void testIncompleteDynamicFilterTimeout() false, Optional.empty(), ImmutableSet.of(), - Optional.empty()); + Optional.of(false)); try (IcebergSplitSource splitSource = new IcebergSplitSource( fileSystemFactory, diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/optimizer/TestConnectorPushdownRulesWithIceberg.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/optimizer/TestConnectorPushdownRulesWithIceberg.java index 519b80444b39..900c649508e6 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/optimizer/TestConnectorPushdownRulesWithIceberg.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/optimizer/TestConnectorPushdownRulesWithIceberg.java @@ -172,7 +172,7 @@ public void testProjectionPushdown() false, Optional.empty(), ImmutableSet.of(), - Optional.empty()); + Optional.of(false)); TableHandle table = new TableHandle(catalogHandle, icebergTable, new HiveTransactionHandle(false)); IcebergColumnHandle fullColumn = partialColumn.getBaseColumn(); @@ -256,7 +256,7 @@ public void testPredicatePushdown() false, Optional.empty(), ImmutableSet.of(), - Optional.empty()); + Optional.of(false)); TableHandle table = new TableHandle(catalogHandle, icebergTable, new HiveTransactionHandle(false)); IcebergColumnHandle column = new IcebergColumnHandle(primitiveColumnIdentity(1, "a"), INTEGER, ImmutableList.of(), INTEGER, Optional.empty()); @@ -307,7 +307,7 @@ public void testColumnPruningProjectionPushdown() false, Optional.empty(), ImmutableSet.of(), - Optional.empty()); + Optional.of(false)); TableHandle table = new TableHandle(catalogHandle, icebergTable, new HiveTransactionHandle(false)); IcebergColumnHandle columnA = new IcebergColumnHandle(primitiveColumnIdentity(0, "a"), INTEGER, ImmutableList.of(), INTEGER, Optional.empty()); @@ -369,7 +369,7 @@ public void testPushdownWithDuplicateExpressions() false, Optional.empty(), ImmutableSet.of(), - Optional.empty()); + Optional.of(false)); TableHandle table = new TableHandle(catalogHandle, icebergTable, new HiveTransactionHandle(false)); IcebergColumnHandle bigintColumn = new IcebergColumnHandle(primitiveColumnIdentity(1, "just_bigint"), BIGINT, ImmutableList.of(), BIGINT, Optional.empty()); From eeda7cd37b97ce404dc40c7138f561507dc2c450 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Mon, 20 Nov 2023 13:07:01 +0100 Subject: [PATCH 356/587] Further normalize Iceberg handle for getTableStatistics cache --- .../main/java/io/trino/plugin/iceberg/IcebergMetadata.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java index 822585bd6bfb..41cd8c7bdd93 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java @@ -2709,10 +2709,10 @@ public TableStatistics getTableStatistics(ConnectorSession session, ConnectorTab originalHandle.getNameMappingJson(), originalHandle.getTableLocation(), originalHandle.getStorageProperties(), - originalHandle.isRecordScannedFiles(), + false, // recordScannedFiles does not affect stats originalHandle.getMaxScannedFileSize(), - originalHandle.getConstraintColumns(), - originalHandle.getForAnalyze()), + ImmutableSet.of(), // constraintColumns do not affect stats + Optional.empty()), // forAnalyze does not affect stats handle -> { Table icebergTable = catalog.loadTable(session, handle.getSchemaTableName()); return TableStatisticsReader.getTableStatistics(typeManager, session, handle, icebergTable); From 50221eba3681cad19f2eea5ef3add8558e8e3e63 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Mon, 20 Nov 2023 13:29:13 +0100 Subject: [PATCH 357/587] Prevent calls to JSON-serialization-only methods --- .../main/java/io/trino/json/ir/IrLiteral.java | 3 ++- .../io/trino/sql/planner/Partitioning.java | 3 ++- .../plan/StatisticAggregationsDescriptor.java | 7 ++++--- .../trino/sql/planner/plan/TableScanNode.java | 2 +- .../sql/relational/ConstantExpression.java | 2 ++ .../main/java/io/trino/spi/SplitWeight.java | 1 + .../FunctionDependencyDeclaration.java | 2 ++ .../trino/spi/function/FunctionMetadata.java | 2 ++ .../spi/function/LongVariableConstraint.java | 9 +++------ .../java/io/trino/spi/function/Signature.java | 9 +++------ .../spi/function/TypeVariableConstraint.java | 9 +++------ .../spi/function/table/ScalarArgument.java | 2 ++ .../io/trino/spi/predicate/NullableValue.java | 3 ++- .../io/trino/spi/predicate/SortedRangeSet.java | 4 +++- .../io/trino/spi/predicate/TupleDomain.java | 15 +++++---------- .../statistics/ColumnStatisticMetadata.java | 3 ++- .../plugin/accumulo/AccumuloSplitManager.java | 18 +++++++++--------- .../io/trino/plugin/jdbc/QueryParameter.java | 2 ++ .../statistics/DeltaLakeColumnStatistics.java | 4 +++- .../hive/metastore/HivePageSinkMetadata.java | 2 ++ .../plugin/iceberg/IcebergTableHandle.java | 2 ++ .../trino/plugin/kudu/KuduClientSession.java | 8 +++----- 22 files changed, 60 insertions(+), 52 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/json/ir/IrLiteral.java b/core/trino-main/src/main/java/io/trino/json/ir/IrLiteral.java index 36b7d754fd0b..3915c17ecbf0 100644 --- a/core/trino-main/src/main/java/io/trino/json/ir/IrLiteral.java +++ b/core/trino-main/src/main/java/io/trino/json/ir/IrLiteral.java @@ -15,6 +15,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.errorprone.annotations.DoNotCall; import io.trino.spi.block.Block; import io.trino.spi.type.Type; @@ -35,8 +36,8 @@ public record IrLiteral(Optional type, Object value) requireNonNull(value, "value is null"); // (boxed) native representation. No null values allowed. } - @Deprecated // For JSON deserialization only @JsonCreator + @DoNotCall // For JSON deserialization only public static IrLiteral fromJson(@JsonProperty("type") Type type, @JsonProperty("valueAsBlock") Block value) { checkArgument(value.getPositionCount() == 1); diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/Partitioning.java b/core/trino-main/src/main/java/io/trino/sql/planner/Partitioning.java index 44bf49249a69..b89c54c9079b 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/Partitioning.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/Partitioning.java @@ -17,6 +17,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.DoNotCall; import com.google.errorprone.annotations.Immutable; import io.trino.Session; import io.trino.metadata.Metadata; @@ -59,8 +60,8 @@ public static Partitioning create(PartitioningHandle handle, List column .collect(toImmutableList())); } - // Factory method for JSON serde only! @JsonCreator + @DoNotCall // For JSON deserialization only public static Partitioning jsonCreate( @JsonProperty("handle") PartitioningHandle handle, @JsonProperty("arguments") List arguments) diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/plan/StatisticAggregationsDescriptor.java b/core/trino-main/src/main/java/io/trino/sql/planner/plan/StatisticAggregationsDescriptor.java index 2ca0511f6ac5..73a30edf0d9c 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/plan/StatisticAggregationsDescriptor.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/plan/StatisticAggregationsDescriptor.java @@ -17,6 +17,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.DoNotCall; import io.trino.spi.statistics.ColumnStatisticMetadata; import io.trino.spi.statistics.TableStatisticType; @@ -52,7 +53,7 @@ public StatisticAggregationsDescriptor( } @JsonCreator - @Deprecated // for JSON serialization only + @DoNotCall // for JSON serialization only public static StatisticAggregationsDescriptor fromJson( @JsonProperty("grouping") Map grouping, @JsonProperty("tableStatistics") Map tableStatistics, @@ -84,8 +85,8 @@ public Map getColumnStatistics() } @JsonProperty - @Deprecated // for JSON serialization only - public List> getColumnStatisticsList() + @DoNotCall // for JSON serialization only + public final List> getColumnStatisticsList() { return columnStatistics.entrySet().stream() .map(entry -> new ColumnStatisticAggregationsDescriptor(entry.getKey(), entry.getValue())) diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/plan/TableScanNode.java b/core/trino-main/src/main/java/io/trino/sql/planner/plan/TableScanNode.java index c63390929100..0fa645de6762 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/plan/TableScanNode.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/plan/TableScanNode.java @@ -79,7 +79,7 @@ public static TableScanNode newInstance( * This constructor is for JSON deserialization only. Do not use. * It's marked as @Deprecated to help avoid usage, and not because we plan to remove it. */ - @Deprecated + /* TODO @DoNotCall once it's applicable to constructors */ @JsonCreator public TableScanNode( @JsonProperty("id") PlanNodeId id, diff --git a/core/trino-main/src/main/java/io/trino/sql/relational/ConstantExpression.java b/core/trino-main/src/main/java/io/trino/sql/relational/ConstantExpression.java index 9baaf0b88748..9cbf9fd8c824 100644 --- a/core/trino-main/src/main/java/io/trino/sql/relational/ConstantExpression.java +++ b/core/trino-main/src/main/java/io/trino/sql/relational/ConstantExpression.java @@ -15,6 +15,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.errorprone.annotations.DoNotCall; import io.airlift.slice.Slice; import io.trino.spi.block.Block; import io.trino.spi.block.BlockBuilder; @@ -33,6 +34,7 @@ public final class ConstantExpression extends RowExpression { @JsonCreator + @DoNotCall // For JSON deserialization only public static ConstantExpression fromJson( @JsonProperty Block value, @JsonProperty Type type) diff --git a/core/trino-spi/src/main/java/io/trino/spi/SplitWeight.java b/core/trino-spi/src/main/java/io/trino/spi/SplitWeight.java index 7b0fe2cdfa9e..3d9c96ada266 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/SplitWeight.java +++ b/core/trino-spi/src/main/java/io/trino/spi/SplitWeight.java @@ -82,6 +82,7 @@ public String toString() * to avoid breakages that could arise if {@link SplitWeight#UNIT_VALUE} changes in the future. */ @JsonCreator + // TODO Mark with @DoNotCall public static SplitWeight fromRawValue(long value) { return value == UNIT_VALUE ? STANDARD_WEIGHT : new SplitWeight(value); diff --git a/core/trino-spi/src/main/java/io/trino/spi/function/FunctionDependencyDeclaration.java b/core/trino-spi/src/main/java/io/trino/spi/function/FunctionDependencyDeclaration.java index 382a9a8db3a6..110c6a5b2e95 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/function/FunctionDependencyDeclaration.java +++ b/core/trino-spi/src/main/java/io/trino/spi/function/FunctionDependencyDeclaration.java @@ -15,6 +15,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.errorprone.annotations.DoNotCall; import io.trino.spi.Experimental; import io.trino.spi.type.Type; import io.trino.spi.type.TypeSignature; @@ -80,6 +81,7 @@ public Set getCastDependencies() } @JsonCreator + @DoNotCall // For JSON deserialization only public static FunctionDependencyDeclaration fromJson( @JsonProperty Set typeDependencies, @JsonProperty Set functionDependencies, diff --git a/core/trino-spi/src/main/java/io/trino/spi/function/FunctionMetadata.java b/core/trino-spi/src/main/java/io/trino/spi/function/FunctionMetadata.java index 312ffc94c805..8f96946ebdfa 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/function/FunctionMetadata.java +++ b/core/trino-spi/src/main/java/io/trino/spi/function/FunctionMetadata.java @@ -15,6 +15,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.errorprone.annotations.DoNotCall; import io.trino.spi.Experimental; import java.util.ArrayList; @@ -149,6 +150,7 @@ public boolean isDeprecated() } @JsonCreator + @DoNotCall // For JSON deserialization only public static FunctionMetadata fromJson( @JsonProperty FunctionId functionId, @JsonProperty Signature signature, diff --git a/core/trino-spi/src/main/java/io/trino/spi/function/LongVariableConstraint.java b/core/trino-spi/src/main/java/io/trino/spi/function/LongVariableConstraint.java index 2557cabf708f..3cd9dc973029 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/function/LongVariableConstraint.java +++ b/core/trino-spi/src/main/java/io/trino/spi/function/LongVariableConstraint.java @@ -15,6 +15,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.errorprone.annotations.DoNotCall; import io.trino.spi.Experimental; import java.util.Objects; @@ -71,13 +72,9 @@ public int hashCode() return Objects.hash(name, expression); } - /** - * This method is only visible for JSON deserialization. - * - * @deprecated use builder - */ - @Deprecated @JsonCreator + @DoNotCall // For JSON deserialization only + @Deprecated // Discourage usages in SPI consumers public static LongVariableConstraint fromJson( @JsonProperty("name") String name, @JsonProperty("expression") String expression) diff --git a/core/trino-spi/src/main/java/io/trino/spi/function/Signature.java b/core/trino-spi/src/main/java/io/trino/spi/function/Signature.java index bd5659b1e4c4..2c0f8fb8dea8 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/function/Signature.java +++ b/core/trino-spi/src/main/java/io/trino/spi/function/Signature.java @@ -15,6 +15,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.errorprone.annotations.DoNotCall; import io.trino.spi.Experimental; import io.trino.spi.type.Type; import io.trino.spi.type.TypeSignature; @@ -239,13 +240,9 @@ public Signature build() } } - /** - * This method is only visible for JSON deserialization. - * - * @deprecated use builder - */ - @Deprecated @JsonCreator + @DoNotCall // For JSON deserialization only + @Deprecated // Discourage usages in SPI consumers public static Signature fromJson( @JsonProperty("typeVariableConstraints") List typeVariableConstraints, @JsonProperty("longVariableConstraints") List longVariableConstraints, diff --git a/core/trino-spi/src/main/java/io/trino/spi/function/TypeVariableConstraint.java b/core/trino-spi/src/main/java/io/trino/spi/function/TypeVariableConstraint.java index a5787180336e..69c36df6ae0d 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/function/TypeVariableConstraint.java +++ b/core/trino-spi/src/main/java/io/trino/spi/function/TypeVariableConstraint.java @@ -15,6 +15,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.errorprone.annotations.DoNotCall; import io.trino.spi.Experimental; import io.trino.spi.type.Type; import io.trino.spi.type.TypeSignature; @@ -208,13 +209,9 @@ public TypeVariableConstraint build() } } - /** - * This method is only visible for JSON deserialization. - * - * @deprecated use builder - */ - @Deprecated @JsonCreator + @DoNotCall // For JSON deserialization only + @Deprecated // Discourage usages in SPI consumers public static TypeVariableConstraint fromJson( @JsonProperty("name") String name, @JsonProperty("comparableRequired") boolean comparableRequired, diff --git a/core/trino-spi/src/main/java/io/trino/spi/function/table/ScalarArgument.java b/core/trino-spi/src/main/java/io/trino/spi/function/table/ScalarArgument.java index c059f1fcbca9..44ca552508a5 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/function/table/ScalarArgument.java +++ b/core/trino-spi/src/main/java/io/trino/spi/function/table/ScalarArgument.java @@ -15,6 +15,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.errorprone.annotations.DoNotCall; import io.trino.spi.Experimental; import io.trino.spi.expression.ConnectorExpression; import io.trino.spi.predicate.NullableValue; @@ -60,6 +61,7 @@ public Object getValue() // deserialization @JsonCreator + @DoNotCall // For JSON deserialization only public static ScalarArgument fromNullableValue(@JsonProperty("nullableValue") NullableValue nullableValue) { return new ScalarArgument(nullableValue.getType(), nullableValue.getValue()); diff --git a/core/trino-spi/src/main/java/io/trino/spi/predicate/NullableValue.java b/core/trino-spi/src/main/java/io/trino/spi/predicate/NullableValue.java index 32c25482fb44..6f28f8b950f5 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/predicate/NullableValue.java +++ b/core/trino-spi/src/main/java/io/trino/spi/predicate/NullableValue.java @@ -15,6 +15,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.errorprone.annotations.DoNotCall; import io.trino.spi.block.Block; import io.trino.spi.type.Type; @@ -73,8 +74,8 @@ public static NullableValue asNull(Type type) return new NullableValue(type, null); } - // Jackson deserialization only @JsonCreator + @DoNotCall // For JSON deserialization only public static NullableValue fromSerializable(@JsonProperty("serializable") Serializable serializable) { Type type = serializable.getType(); diff --git a/core/trino-spi/src/main/java/io/trino/spi/predicate/SortedRangeSet.java b/core/trino-spi/src/main/java/io/trino/spi/predicate/SortedRangeSet.java index a14ce100e5fe..e88a78511cc8 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/predicate/SortedRangeSet.java +++ b/core/trino-spi/src/main/java/io/trino/spi/predicate/SortedRangeSet.java @@ -15,6 +15,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.errorprone.annotations.DoNotCall; import io.trino.spi.block.Block; import io.trino.spi.block.BlockBuilder; import io.trino.spi.block.DictionaryBlock; @@ -136,8 +137,9 @@ static SortedRangeSet all(Type type) .build()); } - @Deprecated // For JSON deserialization only @JsonCreator + @DoNotCall // For JSON deserialization only + @Deprecated // Discourage usages in SPI consumers public static SortedRangeSet fromJson( @JsonProperty("type") Type type, @JsonProperty("inclusive") boolean[] inclusive, diff --git a/core/trino-spi/src/main/java/io/trino/spi/predicate/TupleDomain.java b/core/trino-spi/src/main/java/io/trino/spi/predicate/TupleDomain.java index c7177eda8487..7be8cb97bb5d 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/predicate/TupleDomain.java +++ b/core/trino-spi/src/main/java/io/trino/spi/predicate/TupleDomain.java @@ -16,6 +16,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.errorprone.annotations.DoNotCall; import io.trino.spi.connector.ConnectorSession; import io.trino.spi.type.Type; @@ -167,12 +168,9 @@ public static TupleDomain fromFixedValues(Map fixedValu }))); } - /* - * This method is for JSON serialization only. Do not use. - * It's marked as @Deprecated to help avoid usage, and not because we plan to remove it. - */ - @Deprecated @JsonCreator + @DoNotCall // For JSON deserialization only + @Deprecated // Discourage usages in SPI consumers public static TupleDomain fromColumnDomains(@JsonProperty("columnDomains") Optional>> columnDomains) { if (columnDomains.isEmpty()) { @@ -182,12 +180,9 @@ public static TupleDomain fromColumnDomains(@JsonProperty("columnDomains" .collect(toLinkedMap(ColumnDomain::getColumn, ColumnDomain::getDomain))); } - /* - * This method is for JSON serialization only. Do not use. - * It's marked as @Deprecated to help avoid usage, and not because we plan to remove it. - */ - @Deprecated @JsonProperty + @DoNotCall // For JSON serialization only + @Deprecated // Discourage usages in SPI consumers public Optional>> getColumnDomains() { return domains.map(map -> map.entrySet().stream() diff --git a/core/trino-spi/src/main/java/io/trino/spi/statistics/ColumnStatisticMetadata.java b/core/trino-spi/src/main/java/io/trino/spi/statistics/ColumnStatisticMetadata.java index f4a1f209f267..ac5ab127b7dd 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/statistics/ColumnStatisticMetadata.java +++ b/core/trino-spi/src/main/java/io/trino/spi/statistics/ColumnStatisticMetadata.java @@ -16,6 +16,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.errorprone.annotations.DoNotCall; import io.trino.spi.Experimental; import io.trino.spi.expression.FunctionName; @@ -71,8 +72,8 @@ private ColumnStatisticMetadata( } } - @Deprecated // For JSON deserialization only @JsonCreator + @DoNotCall // For JSON deserialization only public static ColumnStatisticMetadata fromJson( @JsonProperty("columnName") String columnName, @JsonProperty("connectorAggregationId") String connectorAggregationId, diff --git a/plugin/trino-accumulo/src/main/java/io/trino/plugin/accumulo/AccumuloSplitManager.java b/plugin/trino-accumulo/src/main/java/io/trino/plugin/accumulo/AccumuloSplitManager.java index 77ecf0ad4df9..645fbd8b7631 100644 --- a/plugin/trino-accumulo/src/main/java/io/trino/plugin/accumulo/AccumuloSplitManager.java +++ b/plugin/trino-accumulo/src/main/java/io/trino/plugin/accumulo/AccumuloSplitManager.java @@ -33,9 +33,9 @@ import io.trino.spi.connector.FixedSplitSource; import io.trino.spi.predicate.Domain; import io.trino.spi.predicate.TupleDomain; -import io.trino.spi.predicate.TupleDomain.ColumnDomain; import java.util.List; +import java.util.Map.Entry; import java.util.Optional; import java.util.stream.Collectors; @@ -89,11 +89,11 @@ public ConnectorSplitSource getSplits( private static Optional getRangeDomain(String rowIdName, TupleDomain constraint) { - if (constraint.getColumnDomains().isPresent()) { - for (ColumnDomain cd : constraint.getColumnDomains().get()) { - AccumuloColumnHandle col = (AccumuloColumnHandle) cd.getColumn(); + if (constraint.getDomains().isPresent()) { + for (Entry columnDomain : constraint.getDomains().get().entrySet()) { + AccumuloColumnHandle col = (AccumuloColumnHandle) columnDomain.getKey(); if (col.getName().equals(rowIdName)) { - return Optional.of(cd.getDomain()); + return Optional.of(columnDomain.getValue()); } } } @@ -111,8 +111,8 @@ private static Optional getRangeDomain(String rowIdName, TupleDomaingetColumnConstraints(String rowIdName, TupleDomain constraint) { ImmutableList.Builder constraintBuilder = ImmutableList.builder(); - for (ColumnDomain columnDomain : constraint.getColumnDomains().get()) { - AccumuloColumnHandle columnHandle = (AccumuloColumnHandle) columnDomain.getColumn(); + constraint.getDomains().orElseThrow().forEach((handle, domain) -> { + AccumuloColumnHandle columnHandle = (AccumuloColumnHandle) handle; if (!columnHandle.getName().equals(rowIdName)) { // Family and qualifier will exist for non-row ID columns @@ -120,10 +120,10 @@ private static List getColumnConstraints(String rowIdN columnHandle.getName(), columnHandle.getFamily().get(), columnHandle.getQualifier().get(), - Optional.of(columnDomain.getDomain()), + Optional.of(domain), columnHandle.isIndexed())); } - } + }); return constraintBuilder.build(); } diff --git a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/QueryParameter.java b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/QueryParameter.java index b2180e6eab3f..2324530d62ed 100644 --- a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/QueryParameter.java +++ b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/QueryParameter.java @@ -16,6 +16,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.errorprone.annotations.DoNotCall; import io.trino.spi.block.Block; import io.trino.spi.type.Type; @@ -51,6 +52,7 @@ private QueryParameter(Optional jdbcType, Type type, Optional jdbcType, Type type, Block valueBlock) { requireNonNull(type, "type is null"); diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/statistics/DeltaLakeColumnStatistics.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/statistics/DeltaLakeColumnStatistics.java index a7e49b2f9f0e..5ded4de91192 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/statistics/DeltaLakeColumnStatistics.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/statistics/DeltaLakeColumnStatistics.java @@ -15,6 +15,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.errorprone.annotations.DoNotCall; import io.airlift.slice.Slices; import io.airlift.stats.cardinality.HyperLogLog; @@ -29,7 +30,8 @@ public class DeltaLakeColumnStatistics private final HyperLogLog ndvSummary; @JsonCreator - public static DeltaLakeColumnStatistics create( + @DoNotCall // For JSON deserialization only + public static DeltaLakeColumnStatistics fromJson( @JsonProperty("totalSizeInBytes") OptionalLong totalSizeInBytes, @JsonProperty("ndvSummary") String ndvSummaryBase64) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HivePageSinkMetadata.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HivePageSinkMetadata.java index ac1549705e65..44f9e7001a14 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HivePageSinkMetadata.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HivePageSinkMetadata.java @@ -15,6 +15,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.errorprone.annotations.DoNotCall; import io.trino.spi.connector.SchemaTableName; import java.util.List; @@ -43,6 +44,7 @@ public HivePageSinkMetadata( } @JsonCreator + @DoNotCall // For JSON deserialization only public static HivePageSinkMetadata deserialize( @JsonProperty("schemaTableName") SchemaTableName schemaTableName, @JsonProperty("table") Optional
    table, diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergTableHandle.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergTableHandle.java index cc8a23e72a9b..78d748063374 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergTableHandle.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergTableHandle.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.DoNotCall; import io.airlift.units.DataSize; import io.trino.spi.connector.CatalogHandle; import io.trino.spi.connector.ConnectorTableHandle; @@ -72,6 +73,7 @@ public class IcebergTableHandle private final Optional forAnalyze; @JsonCreator + @DoNotCall // For JSON deserialization only public static IcebergTableHandle fromJsonForDeserializationOnly( @JsonProperty("catalog") CatalogHandle catalog, @JsonProperty("schemaName") String schemaName, diff --git a/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/KuduClientSession.java b/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/KuduClientSession.java index 452afc08e631..f3b2ae96c8e7 100644 --- a/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/KuduClientSession.java +++ b/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/KuduClientSession.java @@ -32,7 +32,6 @@ import io.trino.spi.connector.SchemaTableName; import io.trino.spi.connector.TableNotFoundException; import io.trino.spi.predicate.DiscreteValues; -import io.trino.spi.predicate.Domain; import io.trino.spi.predicate.EquatableValueSet; import io.trino.spi.predicate.Range; import io.trino.spi.predicate.Ranges; @@ -503,10 +502,9 @@ private void addConstraintPredicates(KuduTable table, KuduScanToken.KuduScanToke } Schema schema = table.getSchema(); - for (TupleDomain.ColumnDomain columnDomain : constraintSummary.getColumnDomains().get()) { - int position = ((KuduColumnHandle) columnDomain.getColumn()).getOrdinalPosition(); + constraintSummary.getDomains().orElseThrow().forEach((columnHandle, domain) -> { + int position = ((KuduColumnHandle) columnHandle).getOrdinalPosition(); ColumnSchema columnSchema = schema.getColumnByIndex(position); - Domain domain = columnDomain.getDomain(); verify(!domain.isNone(), "Domain is none"); if (domain.isAll()) { // no restriction @@ -560,7 +558,7 @@ else if (valueSet instanceof SortedRangeSet) { throw new IllegalStateException("Unexpected domain: " + domain); } } - } + }); } private KuduPredicate createInListPredicate(ColumnSchema columnSchema, DiscreteValues discreteValues) From 8354918569f61de48085caffc433d9937109bfe8 Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Tue, 21 Nov 2023 10:29:39 +0900 Subject: [PATCH 358/587] Move testNonDeltaTablesCannotBeAccessed in Delta Lake --- .../BaseDeltaLakeConnectorSmokeTest.java | 12 ------------ .../BaseDeltaLakeSharedMetastoreViewsTest.java | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeConnectorSmokeTest.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeConnectorSmokeTest.java index 07a0eb065be3..20b28c439200 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeConnectorSmokeTest.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeConnectorSmokeTest.java @@ -487,18 +487,6 @@ public void testHiveViewsCannotBeAccessed() hiveHadoop.runOnHive("DROP DATABASE " + schemaName + " CASCADE"); } - @Test - public void testNonDeltaTablesCannotBeAccessed() - { - String schemaName = "test_schema" + randomNameSuffix(); - String tableName = "hive_table"; - hiveHadoop.runOnHive("CREATE DATABASE " + schemaName); - hiveHadoop.runOnHive(format("CREATE TABLE %s.%s (id BIGINT)", schemaName, tableName)); - assertThat(computeScalar(format("SHOW TABLES FROM %s LIKE '%s'", schemaName, tableName))).isEqualTo(tableName); - assertThatThrownBy(() -> computeActual("DESCRIBE " + schemaName + "." + tableName)).hasMessageContaining(tableName + " is not a Delta Lake table"); - hiveHadoop.runOnHive("DROP DATABASE " + schemaName + " CASCADE"); - } - @Test public void testDropDatabricksTable() { diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeSharedMetastoreViewsTest.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeSharedMetastoreViewsTest.java index a6802850b378..209c8c0629ac 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeSharedMetastoreViewsTest.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeSharedMetastoreViewsTest.java @@ -35,6 +35,7 @@ import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.testing.TestingSession.testSessionBuilder; import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; /** @@ -157,6 +158,23 @@ public void testViewOnHiveTableCreatedInHiveIsReadableInDeltaLake() } } + @Test + public void testNonDeltaTablesCannotBeAccessed() + { + String schemaName = "test_schema" + randomNameSuffix(); + String tableName = "hive_table"; + + assertUpdate("CREATE SCHEMA %s.%s".formatted(HIVE_CATALOG_NAME, schemaName)); + try { + assertUpdate("CREATE TABLE %s.%s.%s(id BIGINT)".formatted(HIVE_CATALOG_NAME, schemaName, tableName)); + assertThat(computeScalar(format("SHOW TABLES FROM %s LIKE '%s'", schemaName, tableName))).isEqualTo(tableName); + assertQueryFails("DESCRIBE " + schemaName + "." + tableName, ".* is not a Delta Lake table"); + } + finally { + assertUpdate("DROP SCHEMA %s.%s CASCADE".formatted(HIVE_CATALOG_NAME, schemaName)); + } + } + @AfterAll public void cleanup() throws IOException From f7dc99fe08ed0907b7b1d43016a15a76daf371de Mon Sep 17 00:00:00 2001 From: Karol Sobczak Date: Mon, 20 Nov 2023 15:40:08 +0100 Subject: [PATCH 359/587] Fix formatting --- .../io/trino/cost/TestValuesNodeStats.java | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/core/trino-main/src/test/java/io/trino/cost/TestValuesNodeStats.java b/core/trino-main/src/test/java/io/trino/cost/TestValuesNodeStats.java index d5311555591a..6284ef233bbf 100644 --- a/core/trino-main/src/test/java/io/trino/cost/TestValuesNodeStats.java +++ b/core/trino-main/src/test/java/io/trino/cost/TestValuesNodeStats.java @@ -30,11 +30,11 @@ public class TestValuesNodeStats public void testStatsForValuesNode() { tester().assertStatsFor(pb -> pb - .values(ImmutableList.of(pb.symbol("a", BIGINT), pb.symbol("b", DOUBLE)), - ImmutableList.of( - ImmutableList.of(expression("3+3"), expression("13.5e0")), - ImmutableList.of(expression("55"), expression("null")), - ImmutableList.of(expression("6"), expression("13.5e0"))))) + .values(ImmutableList.of(pb.symbol("a", BIGINT), pb.symbol("b", DOUBLE)), + ImmutableList.of( + ImmutableList.of(expression("3+3"), expression("13.5e0")), + ImmutableList.of(expression("55"), expression("null")), + ImmutableList.of(expression("6"), expression("13.5e0"))))) .check(outputStats -> outputStats.equalTo( PlanNodeStatsEstimate.builder() .setOutputRowCount(3) @@ -57,12 +57,12 @@ public void testStatsForValuesNode() .build())); tester().assertStatsFor(pb -> pb - .values(ImmutableList.of(pb.symbol("v", createVarcharType(30))), - ImmutableList.of( - ImmutableList.of(expression("'Alice'")), - ImmutableList.of(expression("'has'")), - ImmutableList.of(expression("'a cat'")), - ImmutableList.of(expression("null"))))) + .values(ImmutableList.of(pb.symbol("v", createVarcharType(30))), + ImmutableList.of( + ImmutableList.of(expression("'Alice'")), + ImmutableList.of(expression("'has'")), + ImmutableList.of(expression("'a cat'")), + ImmutableList.of(expression("null"))))) .check(outputStats -> outputStats.equalTo( PlanNodeStatsEstimate.builder() .setOutputRowCount(4) @@ -85,21 +85,21 @@ public void testStatsForValuesNodeWithJustNulls() .build(); tester().assertStatsFor(pb -> pb - .values(ImmutableList.of(pb.symbol("a", BIGINT)), - ImmutableList.of( - ImmutableList.of(expression("3 + null"))))) + .values(ImmutableList.of(pb.symbol("a", BIGINT)), + ImmutableList.of( + ImmutableList.of(expression("3 + null"))))) .check(outputStats -> outputStats.equalTo(nullAStats)); tester().assertStatsFor(pb -> pb - .values(ImmutableList.of(pb.symbol("a", BIGINT)), - ImmutableList.of( - ImmutableList.of(expression("null"))))) + .values(ImmutableList.of(pb.symbol("a", BIGINT)), + ImmutableList.of( + ImmutableList.of(expression("null"))))) .check(outputStats -> outputStats.equalTo(nullAStats)); tester().assertStatsFor(pb -> pb - .values(ImmutableList.of(pb.symbol("a", UNKNOWN)), - ImmutableList.of( - ImmutableList.of(expression("null"))))) + .values(ImmutableList.of(pb.symbol("a", UNKNOWN)), + ImmutableList.of( + ImmutableList.of(expression("null"))))) .check(outputStats -> outputStats.equalTo(nullAStats)); } @@ -107,8 +107,8 @@ public void testStatsForValuesNodeWithJustNulls() public void testStatsForEmptyValues() { tester().assertStatsFor(pb -> pb - .values(ImmutableList.of(pb.symbol("a", BIGINT)), - ImmutableList.of())) + .values(ImmutableList.of(pb.symbol("a", BIGINT)), + ImmutableList.of())) .check(outputStats -> outputStats.equalTo( PlanNodeStatsEstimate.builder() .setOutputRowCount(0) From b99ef18dd49c6dcf7412fd50347db93d2e18f894 Mon Sep 17 00:00:00 2001 From: Karol Sobczak Date: Fri, 17 Nov 2023 13:59:18 +0100 Subject: [PATCH 360/587] Simplify NodeRepresentation#getEstimates --- .../sql/planner/planprinter/JsonRenderer.java | 2 +- .../planprinter/NodeRepresentation.java | 29 ++++++++++------ .../sql/planner/planprinter/PlanPrinter.java | 2 +- .../sql/planner/planprinter/TextRenderer.java | 2 +- .../TestAnonymizeJsonRepresentation.java | 34 +++++++++---------- .../planprinter/TestJsonRepresentation.java | 33 +++++++++--------- .../execution/TestEventListenerBasic.java | 13 +++---- 7 files changed, 63 insertions(+), 52 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/JsonRenderer.java b/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/JsonRenderer.java index bdeaf648aac2..0402f142c3f4 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/JsonRenderer.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/JsonRenderer.java @@ -51,7 +51,7 @@ protected JsonRenderedNode renderJson(PlanRepresentation plan, NodeRepresentatio node.getDescriptor(), node.getOutputs(), node.getDetails(), - node.getEstimates(plan.getTypes()), + node.getEstimates(), children); } diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/NodeRepresentation.java b/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/NodeRepresentation.java index 0ecd9fcfaf61..d3ee8289ff12 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/NodeRepresentation.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/NodeRepresentation.java @@ -13,7 +13,7 @@ */ package io.trino.sql.planner.planprinter; -import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.FormatMethod; @@ -21,6 +21,7 @@ import io.trino.cost.PlanCostEstimate; import io.trino.cost.PlanNodeStatsAndCostSummary; import io.trino.cost.PlanNodeStatsEstimate; +import io.trino.spi.type.Type; import io.trino.sql.planner.Symbol; import io.trino.sql.planner.TypeProvider; import io.trino.sql.planner.plan.PlanFragmentId; @@ -33,6 +34,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableMap.toImmutableMap; import static java.lang.String.format; import static java.util.Objects.requireNonNull; @@ -151,7 +153,7 @@ public Optional getReorderJoinStatsAndCost() return reorderJoinStatsAndCost; } - public List getEstimates(TypeProvider typeProvider) + public List getEstimates() { if (getEstimatedStats().stream().allMatch(PlanNodeStatsEstimate::isOutputRowCountUnknown) && getEstimatedCost().stream().allMatch(c -> c.getRootNodeLocalCostEstimate().equals(LocalCostEstimate.unknown()))) { @@ -159,6 +161,8 @@ public List getEstimates(TypeProvider typeProvider) } ImmutableList.Builder estimates = ImmutableList.builder(); + TypeProvider typeProvider = TypeProvider.copyOf(outputs.stream() + .collect(toImmutableMap(TypedSymbol::getSymbol, TypedSymbol::getTrinoType))); for (int i = 0; i < getEstimatedStats().size(); i++) { PlanNodeStatsEstimate stats = getEstimatedStats().get(i); LocalCostEstimate cost = getEstimatedCost().get(i).getRootNodeLocalCostEstimate(); @@ -181,13 +185,12 @@ public List getEstimates(TypeProvider typeProvider) public static class TypedSymbol { private final Symbol symbol; - private final String type; + private final Type trinoType; - @JsonCreator - public TypedSymbol(@JsonProperty("symbol") Symbol symbol, @JsonProperty("type") String type) + public TypedSymbol(Symbol symbol, Type trinoType) { this.symbol = symbol; - this.type = type; + this.trinoType = trinoType; } @JsonProperty @@ -199,10 +202,16 @@ public Symbol getSymbol() @JsonProperty public String getType() { - return type; + return trinoType.getDisplayName(); } - public static TypedSymbol typedSymbol(String symbol, String type) + @JsonIgnore + public Type getTrinoType() + { + return trinoType; + } + + public static TypedSymbol typedSymbol(String symbol, Type type) { return new TypedSymbol(new Symbol(symbol), type); } @@ -218,13 +227,13 @@ public boolean equals(Object o) } TypedSymbol that = (TypedSymbol) o; return symbol.equals(that.symbol) - && type.equals(that.type); + && trinoType.equals(that.trinoType); } @Override public int hashCode() { - return Objects.hash(symbol, type); + return Objects.hash(symbol, trinoType); } } } diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/PlanPrinter.java b/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/PlanPrinter.java index f8a070b67c93..7ac1cc004ec8 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/PlanPrinter.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/PlanPrinter.java @@ -2160,7 +2160,7 @@ public NodeRepresentation addNode( rootNode.getClass().getSimpleName(), descriptor, rootNode.getOutputSymbols().stream() - .map(s -> new TypedSymbol(new Symbol(anonymizer.anonymize(s)), types.get(s).getDisplayName())) + .map(s -> new TypedSymbol(new Symbol(anonymizer.anonymize(s)), types.get(s))) .collect(toImmutableList()), stats.map(s -> s.get(rootNode.getId())), estimatedStats, diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/TextRenderer.java b/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/TextRenderer.java index f362fa22c682..64118f8f96f1 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/TextRenderer.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/TextRenderer.java @@ -84,7 +84,7 @@ private String writeTextOutput(StringBuilder output, PlanRepresentation plan, In output.append(indentMultilineString(reorderJoinStatsAndCost, indent.detailIndent())); } - List estimates = node.getEstimates(plan.getTypes()); + List estimates = node.getEstimates(); if (!estimates.isEmpty()) { output.append(indentMultilineString(printEstimates(estimates), indent.detailIndent())); } diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/planprinter/TestAnonymizeJsonRepresentation.java b/core/trino-main/src/test/java/io/trino/sql/planner/planprinter/TestAnonymizeJsonRepresentation.java index 5a74d5e50de0..862a49e242ef 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/planprinter/TestAnonymizeJsonRepresentation.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/planprinter/TestAnonymizeJsonRepresentation.java @@ -115,17 +115,17 @@ public void testAggregationPlan() "keys", "[symbol_1, symbol_2]", "hash", "[]"), ImmutableList.of( - typedSymbol("symbol_1", "bigint"), - typedSymbol("symbol_2", "bigint"), - typedSymbol("symbol_3", "bigint")), + typedSymbol("symbol_1", BIGINT), + typedSymbol("symbol_2", BIGINT), + typedSymbol("symbol_3", BIGINT)), ImmutableList.of("symbol_3 := sum(\"symbol_4\")"), ImmutableList.of(), ImmutableList.of(valuesRepresentation( "0", ImmutableList.of( - typedSymbol("symbol_4", "bigint"), - typedSymbol("symbol_1", "bigint"), - typedSymbol("symbol_2", "bigint")))))); + typedSymbol("symbol_4", BIGINT), + typedSymbol("symbol_1", BIGINT), + typedSymbol("symbol_2", BIGINT)))))); } @Test @@ -149,20 +149,20 @@ public void testJoinPlan() ImmutableMap.of( "criteria", "(\"symbol_1\" = \"symbol_2\")", "hash", "[]"), - ImmutableList.of(typedSymbol("symbol_3", "bigint")), + ImmutableList.of(typedSymbol("symbol_3", BIGINT)), ImmutableList.of("dynamicFilterAssignments = {symbol_2 -> #DF}"), ImmutableList.of(), ImmutableList.of( valuesRepresentation( "0", ImmutableList.of( - typedSymbol("symbol_1", "bigint"), - typedSymbol("symbol_3", "bigint"))), + typedSymbol("symbol_1", BIGINT), + typedSymbol("symbol_3", BIGINT))), valuesRepresentation( "1", ImmutableList.of( - typedSymbol("symbol_4", "bigint"), - typedSymbol("symbol_2", "bigint")))))); + typedSymbol("symbol_4", BIGINT), + typedSymbol("symbol_2", BIGINT)))))); } @Test @@ -187,10 +187,10 @@ public void testTableScanPlan() ImmutableMap.of( "table", "[table = catalog_1.schema_1.table_1, connector = tpch]"), ImmutableList.of( - typedSymbol("symbol_1", "bigint"), - typedSymbol("symbol_2", "bigint"), - typedSymbol("symbol_3", "bigint"), - typedSymbol("symbol_4", "bigint")), + typedSymbol("symbol_1", BIGINT), + typedSymbol("symbol_2", BIGINT), + typedSymbol("symbol_3", BIGINT), + typedSymbol("symbol_4", BIGINT)), ImmutableList.of( "symbol_1 := column_1", " :: [[bigint_value_1]]", @@ -212,13 +212,13 @@ public void testSortPlan() "1", "Sort", ImmutableMap.of("orderBy", "[symbol_1 ASC NULLS FIRST]"), - ImmutableList.of(typedSymbol("symbol_1", "bigint")), + ImmutableList.of(typedSymbol("symbol_1", BIGINT)), ImmutableList.of(), ImmutableList.of(), ImmutableList.of( valuesRepresentation( "0", - ImmutableList.of(typedSymbol("symbol_1", "bigint")))))); + ImmutableList.of(typedSymbol("symbol_1", BIGINT)))))); } private static JsonRenderedNode valuesRepresentation(String id, List outputs) diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/planprinter/TestJsonRepresentation.java b/core/trino-main/src/test/java/io/trino/sql/planner/planprinter/TestJsonRepresentation.java index 855a8307e8d0..c66941d6abb4 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/planprinter/TestJsonRepresentation.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/planprinter/TestJsonRepresentation.java @@ -47,6 +47,7 @@ import static io.trino.operator.RetryPolicy.NONE; import static io.trino.plugin.tpch.TpchMetadata.TINY_SCHEMA_NAME; import static io.trino.spi.type.BigintType.BIGINT; +import static io.trino.spi.type.DoubleType.DOUBLE; import static io.trino.spi.type.VarcharType.createVarcharType; import static io.trino.sql.planner.iterative.rule.test.PlanBuilder.expression; import static io.trino.sql.planner.plan.AggregationNode.Step.FINAL; @@ -96,14 +97,14 @@ public void testDistributedJsonPlan() "6", "Output", ImmutableMap.of("columnNames", "[quantity]"), - ImmutableList.of(typedSymbol("quantity", "double")), + ImmutableList.of(typedSymbol("quantity", DOUBLE)), ImmutableList.of(), ImmutableList.of(new PlanNodeStatsAndCostSummary(10, 90, 0, 0, 0)), ImmutableList.of(new JsonRenderedNode( "100", "Limit", ImmutableMap.of("count", "10", "withTies", "", "inputPreSortedBy", "[]"), - ImmutableList.of(typedSymbol("quantity", "double")), + ImmutableList.of(typedSymbol("quantity", DOUBLE)), ImmutableList.of(), ImmutableList.of(new PlanNodeStatsAndCostSummary(10, 90, 90, 0, 0)), ImmutableList.of(new JsonRenderedNode( @@ -114,14 +115,14 @@ public void testDistributedJsonPlan() "isReplicateNullsAndAny", "", "hashColumn", "[]", "arguments", "[]"), - ImmutableList.of(typedSymbol("quantity", "double")), + ImmutableList.of(typedSymbol("quantity", DOUBLE)), ImmutableList.of(), ImmutableList.of(new PlanNodeStatsAndCostSummary(60175, 541575, 0, 0, 0)), ImmutableList.of(new JsonRenderedNode( "0", "TableScan", ImmutableMap.of("table", "tpch:tiny:lineitem"), - ImmutableList.of(typedSymbol("quantity", "double")), + ImmutableList.of(typedSymbol("quantity", DOUBLE)), ImmutableList.of("quantity := tpch:quantity"), ImmutableList.of(new PlanNodeStatsAndCostSummary(60175, 541575, 541575, 0, 0)), ImmutableList.of())))))))); @@ -139,14 +140,14 @@ public void testLogicalJsonPlan() "6", "Output", ImmutableMap.of("columnNames", "[quantity]"), - ImmutableList.of(typedSymbol("quantity", "double")), + ImmutableList.of(typedSymbol("quantity", DOUBLE)), ImmutableList.of(), ImmutableList.of(new PlanNodeStatsAndCostSummary(10, 90, 0, 0, 0)), ImmutableList.of(new JsonRenderedNode( "100", "Limit", ImmutableMap.of("count", "10", "withTies", "", "inputPreSortedBy", "[]"), - ImmutableList.of(typedSymbol("quantity", "double")), + ImmutableList.of(typedSymbol("quantity", DOUBLE)), ImmutableList.of(), ImmutableList.of(new PlanNodeStatsAndCostSummary(10, 90, 90, 0, 0)), ImmutableList.of(new JsonRenderedNode( @@ -157,14 +158,14 @@ public void testLogicalJsonPlan() "isReplicateNullsAndAny", "", "hashColumn", "[]", "arguments", "[]"), - ImmutableList.of(typedSymbol("quantity", "double")), + ImmutableList.of(typedSymbol("quantity", DOUBLE)), ImmutableList.of(), ImmutableList.of(new PlanNodeStatsAndCostSummary(60175, 541575, 0, 0, 0)), ImmutableList.of(new JsonRenderedNode( "0", "TableScan", ImmutableMap.of("table", "tpch:tiny:lineitem"), - ImmutableList.of(typedSymbol("quantity", "double")), + ImmutableList.of(typedSymbol("quantity", DOUBLE)), ImmutableList.of("quantity := tpch:quantity"), ImmutableList.of(new PlanNodeStatsAndCostSummary(60175, 541575, 541575, 0, 0)), ImmutableList.of()))))))); @@ -191,14 +192,14 @@ public void testAggregationPlan() "keys", "[y, z]", "hash", "[]"), ImmutableList.of( - typedSymbol("y", "bigint"), - typedSymbol("z", "bigint"), - typedSymbol("sum", "bigint")), + typedSymbol("y", BIGINT), + typedSymbol("z", BIGINT), + typedSymbol("sum", BIGINT)), ImmutableList.of("sum := sum(\"x\")"), ImmutableList.of(), ImmutableList.of(valuesRepresentation( "0", - ImmutableList.of(typedSymbol("x", "bigint"), typedSymbol("y", "bigint"), typedSymbol("z", "bigint")))))); + ImmutableList.of(typedSymbol("x", BIGINT), typedSymbol("y", BIGINT), typedSymbol("z", BIGINT)))))); } @Test @@ -220,12 +221,12 @@ public void testJoinPlan() "2", "InnerJoin", ImmutableMap.of("criteria", "(\"a\" = \"d\")", "filter", "(\"a\" < \"c\")", "hash", "[]"), - ImmutableList.of(typedSymbol("b", "bigint")), + ImmutableList.of(typedSymbol("b", BIGINT)), ImmutableList.of("dynamicFilterAssignments = {d -> #DF}"), ImmutableList.of(), ImmutableList.of( - valuesRepresentation("0", ImmutableList.of(typedSymbol("a", "bigint"), typedSymbol("b", "bigint"))), - valuesRepresentation("1", ImmutableList.of(typedSymbol("c", "bigint"), typedSymbol("d", "bigint")))))); + valuesRepresentation("0", ImmutableList.of(typedSymbol("a", BIGINT), typedSymbol("b", BIGINT))), + valuesRepresentation("1", ImmutableList.of(typedSymbol("c", BIGINT), typedSymbol("d", BIGINT)))))); } @Test @@ -244,7 +245,7 @@ public void testSourceFragmentIdsInRemoteSource() "0", "RemoteSource", ImmutableMap.of("sourceFragmentIds", "[1, 2]"), - ImmutableList.of(typedSymbol("a", "bigint"), typedSymbol("b", "bigint")), + ImmutableList.of(typedSymbol("a", BIGINT), typedSymbol("b", BIGINT)), ImmutableList.of(), ImmutableList.of(), ImmutableList.of())); diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java b/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java index ade1e9fc96c6..6a539fbc21a0 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java @@ -77,6 +77,7 @@ import static io.trino.execution.TestQueues.createResourceGroupId; import static io.trino.spi.metrics.Metrics.EMPTY; import static io.trino.spi.type.BigintType.BIGINT; +import static io.trino.spi.type.DoubleType.DOUBLE; import static io.trino.spi.type.VarcharType.createVarcharType; import static io.trino.sql.planner.planprinter.JsonRenderer.JsonRenderedNode; import static io.trino.sql.planner.planprinter.NodeRepresentation.TypedSymbol.typedSymbol; @@ -1230,14 +1231,14 @@ public void testAnonymizedJsonPlan() "6", "Output", ImmutableMap.of("columnNames", "[column_1]"), - ImmutableList.of(typedSymbol("symbol_1", "double")), + ImmutableList.of(typedSymbol("symbol_1", DOUBLE)), ImmutableList.of(), ImmutableList.of(), ImmutableList.of(new JsonRenderedNode( "100", "Limit", ImmutableMap.of("count", "10", "withTies", "", "inputPreSortedBy", "[]"), - ImmutableList.of(typedSymbol("symbol_1", "double")), + ImmutableList.of(typedSymbol("symbol_1", DOUBLE)), ImmutableList.of(), ImmutableList.of(), ImmutableList.of(new JsonRenderedNode( @@ -1248,14 +1249,14 @@ public void testAnonymizedJsonPlan() "isReplicateNullsAndAny", "", "hashColumn", "[]", "arguments", "[]"), - ImmutableList.of(typedSymbol("symbol_1", "double")), + ImmutableList.of(typedSymbol("symbol_1", DOUBLE)), ImmutableList.of(), ImmutableList.of(), ImmutableList.of(new JsonRenderedNode( "140", "RemoteSource", ImmutableMap.of("sourceFragmentIds", "[1]"), - ImmutableList.of(typedSymbol("symbol_1", "double")), + ImmutableList.of(typedSymbol("symbol_1", DOUBLE)), ImmutableList.of(), ImmutableList.of(), ImmutableList.of()))))))), @@ -1266,7 +1267,7 @@ public void testAnonymizedJsonPlan() "count", "10", "withTies", "", "inputPreSortedBy", "[]"), - ImmutableList.of(typedSymbol("symbol_1", "double")), + ImmutableList.of(typedSymbol("symbol_1", DOUBLE)), ImmutableList.of(), ImmutableList.of(), ImmutableList.of(new JsonRenderedNode( @@ -1274,7 +1275,7 @@ public void testAnonymizedJsonPlan() "TableScan", ImmutableMap.of( "table", "[table = catalog_1.schema_1.table_1, connector = tpch]"), - ImmutableList.of(typedSymbol("symbol_1", "double")), + ImmutableList.of(typedSymbol("symbol_1", DOUBLE)), ImmutableList.of("symbol_1 := column_2"), ImmutableList.of(), ImmutableList.of())))); From 889740769c680cb343ad83ef5782a08665434336 Mon Sep 17 00:00:00 2001 From: Karol Sobczak Date: Mon, 20 Nov 2023 16:42:37 +0100 Subject: [PATCH 361/587] Prevent ValuesStatsRule from causing planning failures ValuesStatsRule is calling evaluateConstantExpression which might fail for certain expressions like division by 0. --- .../java/io/trino/cost/ValuesStatsRule.java | 26 ++++++++++++------- .../io/trino/cost/TestValuesNodeStats.java | 10 +++++++ 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/cost/ValuesStatsRule.java b/core/trino-main/src/main/java/io/trino/cost/ValuesStatsRule.java index e91a88deba12..01fd6cf87d91 100644 --- a/core/trino-main/src/main/java/io/trino/cost/ValuesStatsRule.java +++ b/core/trino-main/src/main/java/io/trino/cost/ValuesStatsRule.java @@ -68,16 +68,22 @@ public Optional calculate(ValuesNode node, StatsProvider PlanNodeStatsEstimate.Builder statsBuilder = PlanNodeStatsEstimate.builder(); statsBuilder.setOutputRowCount(node.getRowCount()); - for (int symbolId = 0; symbolId < node.getOutputSymbols().size(); ++symbolId) { - Symbol symbol = node.getOutputSymbols().get(symbolId); - List symbolValues = getSymbolValues( - node, - symbolId, - session, - RowType.anonymous(node.getOutputSymbols().stream() - .map(types::get) - .collect(toImmutableList()))); - statsBuilder.addSymbolStatistics(symbol, buildSymbolStatistics(symbolValues, types.get(symbol))); + try { + for (int symbolId = 0; symbolId < node.getOutputSymbols().size(); ++symbolId) { + Symbol symbol = node.getOutputSymbols().get(symbolId); + List symbolValues = getSymbolValues( + node, + symbolId, + session, + RowType.anonymous(node.getOutputSymbols().stream() + .map(types::get) + .collect(toImmutableList()))); + statsBuilder.addSymbolStatistics(symbol, buildSymbolStatistics(symbolValues, types.get(symbol))); + } + } + catch (RuntimeException e) { + // prevent stats calculations (e.g. division by zero) from causing planning failures + return Optional.empty(); } return Optional.of(statsBuilder.build()); diff --git a/core/trino-main/src/test/java/io/trino/cost/TestValuesNodeStats.java b/core/trino-main/src/test/java/io/trino/cost/TestValuesNodeStats.java index 6284ef233bbf..0963cf12fe2f 100644 --- a/core/trino-main/src/test/java/io/trino/cost/TestValuesNodeStats.java +++ b/core/trino-main/src/test/java/io/trino/cost/TestValuesNodeStats.java @@ -17,6 +17,7 @@ import io.trino.sql.planner.Symbol; import org.junit.jupiter.api.Test; +import static io.trino.cost.PlanNodeStatsEstimate.unknown; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.DoubleType.DOUBLE; import static io.trino.spi.type.VarcharType.createVarcharType; @@ -76,6 +77,15 @@ public void testStatsForValuesNode() .build())); } + @Test + public void testDivisionByZero() + { + tester().assertStatsFor(pb -> pb + .values(ImmutableList.of(pb.symbol("a", BIGINT)), + ImmutableList.of(ImmutableList.of(expression("1 / 0"))))) + .check(outputStats -> outputStats.equalTo(unknown())); + } + @Test public void testStatsForValuesNodeWithJustNulls() { From 173087cba8d6f27dafc3cef93dfbb44703bc9898 Mon Sep 17 00:00:00 2001 From: Karol Sobczak Date: Mon, 20 Nov 2023 18:33:00 +0100 Subject: [PATCH 362/587] Remove try catch around DeterminePartitionCount stats estimation It's no longer needed as stats rules have been improved --- .../optimizations/DeterminePartitionCount.java | 13 +++---------- .../io/trino/plugin/hive/BaseHiveConnectorTest.java | 2 +- .../io/trino/plugin/hive/TestHive3OnDataLake.java | 2 +- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/DeterminePartitionCount.java b/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/DeterminePartitionCount.java index c73c9484c940..647620b04d19 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/DeterminePartitionCount.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/DeterminePartitionCount.java @@ -132,16 +132,9 @@ public PlanNode optimize( return plan; } - try { - return determinePartitionCount(plan, session, types, tableStatsProvider, isWriteQuery) - .map(partitionCount -> rewriteWith(new Rewriter(partitionCount), plan)) - .orElse(plan); - } - catch (RuntimeException e) { - log.warn(e, "Error occurred when determining hash partition count for query %s", session.getQueryId()); - } - - return plan; + return determinePartitionCount(plan, session, types, tableStatsProvider, isWriteQuery) + .map(partitionCount -> rewriteWith(new Rewriter(partitionCount), plan)) + .orElse(plan); } private Optional determinePartitionCount( diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java index c323d84f5ed8..90c761d59056 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java @@ -6731,7 +6731,7 @@ public void testInvalidAnalyzePartitionedTable() assertQueryFails(format("ANALYZE %s WITH (partitions = ARRAY[NULL])", tableName), ".*Invalid null value in analyze partitions property.*"); // Test non-existed partition - assertQueryFails(format("ANALYZE %s WITH (partitions = ARRAY[ARRAY['p4', '10']])", tableName), ".*Partition no longer exists.*"); + assertQueryFails(format("ANALYZE %s WITH (partitions = ARRAY[ARRAY['p4', '10']])", tableName), ".*Partition.*not found.*"); // Test partition schema mismatch assertQueryFails(format("ANALYZE %s WITH (partitions = ARRAY[ARRAY['p4']])", tableName), "Partition value count does not match partition column count"); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHive3OnDataLake.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHive3OnDataLake.java index 9b911653e92c..24d40204c58f 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHive3OnDataLake.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHive3OnDataLake.java @@ -1618,7 +1618,7 @@ public void testAnalyzePartitionedTableWithCanonicalization() (null, null, null, null, 5.0, null, null) """); // TODO (https://github.com/trinodb/trino/issues/15998) fix selective ANALYZE for table with non-canonical partition values - assertQueryFails("ANALYZE " + getFullyQualifiedTestTableName(externalTableName) + " WITH (partitions = ARRAY[ARRAY['4']])", "Partition no longer exists: month=4"); + assertQueryFails("ANALYZE " + getFullyQualifiedTestTableName(externalTableName) + " WITH (partitions = ARRAY[ARRAY['4']])", ".*Partition.*not found.*"); assertUpdate("DROP TABLE " + getFullyQualifiedTestTableName(externalTableName)); assertUpdate("DROP TABLE " + getFullyQualifiedTestTableName(tableName)); From 564bf727d5f0ab84b98e6be63c45968d394e6258 Mon Sep 17 00:00:00 2001 From: Karol Sobczak Date: Fri, 17 Nov 2023 14:08:53 +0100 Subject: [PATCH 363/587] Always collect statistics at the end of planning If stats collection was not requested explicitly, then use statistics that were fetched and cached during planning. --- .../trino/cost/CachingTableStatsProvider.java | 6 +++++ .../io/trino/sql/planner/LogicalPlanner.java | 24 ++++++++++++----- .../execution/TestEventListenerBasic.java | 26 +++++++++++++++---- 3 files changed, 44 insertions(+), 12 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/cost/CachingTableStatsProvider.java b/core/trino-main/src/main/java/io/trino/cost/CachingTableStatsProvider.java index 8049ee1759c1..ca7e9fd43f35 100644 --- a/core/trino-main/src/main/java/io/trino/cost/CachingTableStatsProvider.java +++ b/core/trino-main/src/main/java/io/trino/cost/CachingTableStatsProvider.java @@ -13,6 +13,7 @@ */ package io.trino.cost; +import com.google.common.collect.ImmutableMap; import io.trino.Session; import io.trino.metadata.Metadata; import io.trino.metadata.TableHandle; @@ -47,4 +48,9 @@ public TableStatistics getTableStatistics(TableHandle tableHandle) } return stats; } + + public Map getCachedTableStatistics() + { + return ImmutableMap.copyOf(cache); + } } diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/LogicalPlanner.java b/core/trino-main/src/main/java/io/trino/sql/planner/LogicalPlanner.java index 9ca8a04c0cc3..8e85a0f5dc9c 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/LogicalPlanner.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/LogicalPlanner.java @@ -48,6 +48,7 @@ import io.trino.spi.connector.ColumnMetadata; import io.trino.spi.connector.ConnectorTableMetadata; import io.trino.spi.security.AccessDeniedException; +import io.trino.spi.statistics.TableStatistics; import io.trino.spi.statistics.TableStatisticsMetadata; import io.trino.spi.type.CharType; import io.trino.spi.type.Type; @@ -261,7 +262,7 @@ public Plan plan(Analysis analysis, Stage stage, boolean collectPlanStatistics) planSanityChecker.validateIntermediatePlan(root, session, plannerContext, typeAnalyzer, symbolAllocator.getTypes(), warningCollector); } - TableStatsProvider tableStatsProvider = new CachingTableStatsProvider(metadata, session); + CachingTableStatsProvider tableStatsProvider = new CachingTableStatsProvider(metadata, session); if (stage.ordinal() >= OPTIMIZED.ordinal()) { try (var ignored = scopedSpan(plannerContext.getTracer(), "optimizer")) { @@ -280,13 +281,22 @@ public Plan plan(Analysis analysis, Stage stage, boolean collectPlanStatistics) TypeProvider types = symbolAllocator.getTypes(); - StatsAndCosts statsAndCosts = StatsAndCosts.empty(); + TableStatsProvider collectTableStatsProvider; if (collectPlanStatistics) { - StatsProvider statsProvider = new CachingStatsProvider(statsCalculator, session, types, tableStatsProvider); - CostProvider costProvider = new CachingCostProvider(costCalculator, statsProvider, Optional.empty(), session, types); - try (var ignored = scopedSpan(plannerContext.getTracer(), "plan-stats")) { - statsAndCosts = StatsAndCosts.create(root, statsProvider, costProvider); - } + collectTableStatsProvider = tableStatsProvider; + } + else { + // If stats collection was not requested explicitly, then use statistics + // that were fetched and cached during planning. + Map cachedStatistics = tableStatsProvider.getCachedTableStatistics(); + collectTableStatsProvider = handle -> cachedStatistics.getOrDefault(handle, TableStatistics.empty()); + } + + StatsAndCosts statsAndCosts; + StatsProvider statsProvider = new CachingStatsProvider(statsCalculator, session, types, collectTableStatsProvider); + CostProvider costProvider = new CachingCostProvider(costCalculator, statsProvider, Optional.empty(), session, types); + try (var ignored = scopedSpan(plannerContext.getTracer(), "plan-stats")) { + statsAndCosts = StatsAndCosts.create(root, statsProvider, costProvider); } return new Plan(root, types, statsAndCosts); } diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java b/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java index 6a539fbc21a0..ae77214a462f 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java @@ -20,6 +20,8 @@ import io.trino.Session; import io.trino.connector.MockConnectorFactory; import io.trino.connector.MockConnectorTableHandle; +import io.trino.cost.PlanNodeStatsAndCostSummary; +import io.trino.cost.StatsAndCosts; import io.trino.execution.EventsAwaitingQueries.MaterializedResultWithEvents; import io.trino.execution.EventsCollector.QueryEvents; import io.trino.execution.TestEventListenerPlugin.TestingEventListenerPlugin; @@ -71,6 +73,7 @@ import static com.google.common.collect.MoreCollectors.toOptional; import static com.google.common.io.Resources.getResource; import static com.google.common.util.concurrent.MoreExecutors.shutdownAndAwaitTermination; +import static io.airlift.json.JsonCodec.jsonCodec; import static io.airlift.json.JsonCodec.mapJsonCodec; import static io.trino.connector.MockConnectorEntities.TPCH_NATION_DATA; import static io.trino.connector.MockConnectorEntities.TPCH_NATION_SCHEMA; @@ -82,6 +85,7 @@ import static io.trino.sql.planner.planprinter.JsonRenderer.JsonRenderedNode; import static io.trino.sql.planner.planprinter.NodeRepresentation.TypedSymbol.typedSymbol; import static io.trino.testing.TestingSession.testSessionBuilder; +import static java.lang.Double.NaN; import static java.lang.String.format; import static java.util.UUID.randomUUID; import static org.assertj.core.api.Assertions.assertThat; @@ -94,6 +98,7 @@ public class TestEventListenerBasic extends AbstractTestQueryFramework { private static final JsonCodec> ANONYMIZED_PLAN_JSON_CODEC = mapJsonCodec(String.class, JsonRenderedNode.class); + private static final JsonCodec STATS_AND_COSTS_JSON_CODEC = jsonCodec(StatsAndCosts.class); private static final String IGNORE_EVENT_MARKER = " -- ignore_generated_event"; private static final String VARCHAR_TYPE = "varchar(15)"; private static final String BIGINT_TYPE = BIGINT.getDisplayName(); @@ -1220,6 +1225,17 @@ private void testOutputColumnsForSetOperations(String setOperator) new ColumnDetail("tpch", "sf1", "orders", "custkey")))); } + @Test + public void testTableStats() + throws Exception + { + QueryEvents queryEvents = queries.runQueryAndWaitForEvents("SELECT l.name FROM nation l, nation r WHERE l.nationkey = r.nationkey", getSession(), true).getQueryEvents(); + QueryCompletedEvent event = queryEvents.getQueryCompletedEvent(); + assertThat(event.getStatistics().getPlanNodeStatsAndCosts()).isPresent(); + StatsAndCosts statsAndCosts = STATS_AND_COSTS_JSON_CODEC.fromJson(event.getStatistics().getPlanNodeStatsAndCosts().get()); + assertThat(statsAndCosts.getStats().values()).allMatch(stats -> stats.getOutputRowCount() == 25.0); + } + @Test public void testAnonymizedJsonPlan() throws Exception @@ -1233,14 +1249,14 @@ public void testAnonymizedJsonPlan() ImmutableMap.of("columnNames", "[column_1]"), ImmutableList.of(typedSymbol("symbol_1", DOUBLE)), ImmutableList.of(), - ImmutableList.of(), + ImmutableList.of(new PlanNodeStatsAndCostSummary(10., 90., 0., 0., 0.)), ImmutableList.of(new JsonRenderedNode( "100", "Limit", ImmutableMap.of("count", "10", "withTies", "", "inputPreSortedBy", "[]"), ImmutableList.of(typedSymbol("symbol_1", DOUBLE)), ImmutableList.of(), - ImmutableList.of(), + ImmutableList.of(new PlanNodeStatsAndCostSummary(10., 90., 90., 0., 0.)), ImmutableList.of(new JsonRenderedNode( "173", "LocalExchange", @@ -1251,7 +1267,7 @@ public void testAnonymizedJsonPlan() "arguments", "[]"), ImmutableList.of(typedSymbol("symbol_1", DOUBLE)), ImmutableList.of(), - ImmutableList.of(), + ImmutableList.of(new PlanNodeStatsAndCostSummary(10., 90., 0., 0., 0.)), ImmutableList.of(new JsonRenderedNode( "140", "RemoteSource", @@ -1269,7 +1285,7 @@ public void testAnonymizedJsonPlan() "inputPreSortedBy", "[]"), ImmutableList.of(typedSymbol("symbol_1", DOUBLE)), ImmutableList.of(), - ImmutableList.of(), + ImmutableList.of(new PlanNodeStatsAndCostSummary(10., 90., 90., 0., 0.)), ImmutableList.of(new JsonRenderedNode( "0", "TableScan", @@ -1277,7 +1293,7 @@ public void testAnonymizedJsonPlan() "table", "[table = catalog_1.schema_1.table_1, connector = tpch]"), ImmutableList.of(typedSymbol("symbol_1", DOUBLE)), ImmutableList.of("symbol_1 := column_2"), - ImmutableList.of(), + ImmutableList.of(new PlanNodeStatsAndCostSummary(NaN, NaN, NaN, 0., 0.)), ImmutableList.of())))); assertThat(event.getMetadata().getJsonPlan()) .isEqualTo(Optional.of(ANONYMIZED_PLAN_JSON_CODEC.toJson(anonymizedPlan))); From 6fd542d520d29b45bdff99aab3274b8782388f28 Mon Sep 17 00:00:00 2001 From: radek Date: Tue, 21 Nov 2023 10:33:32 +0100 Subject: [PATCH 364/587] Change benchto-driver version to 0.26 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 020fc4dc81b9..42b48e4087ba 100644 --- a/pom.xml +++ b/pom.xml @@ -1502,7 +1502,7 @@ io.trino.benchto benchto-driver - 0.25 + 0.26 From ae40d12fb25126ae5d18a7aa5c4bdc849f60f5b1 Mon Sep 17 00:00:00 2001 From: Karol Sobczak Date: Tue, 21 Nov 2023 15:07:02 +0100 Subject: [PATCH 365/587] Prevent duplicates in TypeProvider --- .../io/trino/sql/planner/planprinter/NodeRepresentation.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/NodeRepresentation.java b/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/NodeRepresentation.java index d3ee8289ff12..d7e7faeba91e 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/NodeRepresentation.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/NodeRepresentation.java @@ -162,6 +162,7 @@ public List getEstimates() ImmutableList.Builder estimates = ImmutableList.builder(); TypeProvider typeProvider = TypeProvider.copyOf(outputs.stream() + .distinct() .collect(toImmutableMap(TypedSymbol::getSymbol, TypedSymbol::getTrinoType))); for (int i = 0; i < getEstimatedStats().size(); i++) { PlanNodeStatsEstimate stats = getEstimatedStats().get(i); From fb9b64ed0e00147f63924e7db7d249df427cee8b Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Mon, 20 Nov 2023 16:12:39 +0100 Subject: [PATCH 366/587] Remove redundant set operation from information_schema.tables When concurrency is not involved, it is not needed and when it is involved, it doesn't provide more correctness. FWIW, the `system.jdbc.tables` doesn't have similar set unioning. --- .../informationschema/InformationSchemaPageSource.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/connector/informationschema/InformationSchemaPageSource.java b/core/trino-main/src/main/java/io/trino/connector/informationschema/InformationSchemaPageSource.java index 8802461a2c86..521369d5c7cc 100644 --- a/core/trino-main/src/main/java/io/trino/connector/informationschema/InformationSchemaPageSource.java +++ b/core/trino-main/src/main/java/io/trino/connector/informationschema/InformationSchemaPageSource.java @@ -49,7 +49,6 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.collect.ImmutableSet.toImmutableSet; -import static com.google.common.collect.Sets.union; import static io.trino.SystemSessionProperties.isOmitDateTimeTypePrecision; import static io.trino.connector.informationschema.InformationSchemaMetadata.defaultPrefixes; import static io.trino.connector.informationschema.InformationSchemaMetadata.isTablesEnumeratingTable; @@ -285,7 +284,7 @@ private void addTablesRecords(QualifiedTablePrefix prefix) } // TODO (https://github.com/trinodb/trino/issues/8207) define a type for materialized views - for (SchemaTableName name : union(tables, views)) { + for (SchemaTableName name : tables) { String type = null; if (needsTableType) { // if table and view names overlap, the view wins From 9d8fbbb3acbe17353475093f4ec8323265d97d60 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Mon, 20 Nov 2023 20:55:10 +0100 Subject: [PATCH 367/587] Unify and supplement short-circuiting in MetadataManager Prevent passing impossible names down to the connectors. E.g. prevents failures if a connector chooses to convert input to something that validates name is not impossible, like `SchemaTablePrefix`. --- .../io/trino/metadata/MetadataManager.java | 49 ++++++++++++------- .../TestInformationSchemaConnector.java | 5 +- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java b/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java index 585629ef6c1f..9eb72beb91a5 100644 --- a/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java +++ b/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java @@ -271,9 +271,7 @@ public Optional getTableHandle(Session session, QualifiedObjectName public Optional getTableHandle(Session session, QualifiedObjectName table, Optional startVersion, Optional endVersion) { requireNonNull(table, "table is null"); - - if (table.getCatalogName().isEmpty() || table.getSchemaName().isEmpty() || table.getObjectName().isEmpty()) { - // Table cannot exist + if (cannotExist(table)) { return Optional.empty(); } @@ -507,6 +505,9 @@ public ColumnMetadata getColumnMetadata(Session session, TableHandle tableHandle public List listTables(Session session, QualifiedTablePrefix prefix) { requireNonNull(prefix, "prefix is null"); + if (cannotExist(prefix)) { + return ImmutableList.of(); + } Optional objectName = prefix.asQualifiedObjectName(); if (objectName.isPresent()) { @@ -563,18 +564,14 @@ private Optional isExistingRelationForListing(Session session, Qualifie public List listTableColumns(Session session, QualifiedTablePrefix prefix, UnaryOperator> relationFilter) { requireNonNull(prefix, "prefix is null"); + if (cannotExist(prefix)) { + return ImmutableList.of(); + } String catalogName = prefix.getCatalogName(); Optional schemaName = prefix.getSchemaName(); Optional relationName = prefix.getTableName(); - if (catalogName.isEmpty() || - (schemaName.isPresent() && schemaName.get().isEmpty()) || - (relationName.isPresent() && relationName.get().isEmpty())) { - // Cannot exist - return ImmutableList.of(); - } - if (relationName.isPresent()) { QualifiedObjectName objectName = new QualifiedObjectName(catalogName, schemaName.orElseThrow(), relationName.get()); SchemaTableName schemaTableName = objectName.asSchemaTableName(); @@ -700,6 +697,10 @@ private List viewColumnMetadata(String catalogName, SchemaTableN @Override public List listRelationComments(Session session, String catalogName, Optional schemaName, UnaryOperator> relationFilter) { + if (cannotExist(new QualifiedTablePrefix(catalogName, schemaName, Optional.empty()))) { + return ImmutableList.of(); + } + Optional catalog = getOptionalCatalogMetadata(session, catalogName); ImmutableList.Builder tableComments = ImmutableList.builder(); @@ -1262,6 +1263,9 @@ public List listCatalogs(Session session) public List listViews(Session session, QualifiedTablePrefix prefix) { requireNonNull(prefix, "prefix is null"); + if (cannotExist(prefix)) { + return ImmutableList.of(); + } Optional objectName = prefix.asQualifiedObjectName(); if (objectName.isPresent()) { @@ -1295,6 +1299,9 @@ public List listViews(Session session, QualifiedTablePrefix public Map getViews(Session session, QualifiedTablePrefix prefix) { requireNonNull(prefix, "prefix is null"); + if (cannotExist(prefix)) { + return ImmutableMap.of(); + } Optional catalog = getOptionalCatalogMetadata(session, prefix.getCatalogName()); @@ -1409,8 +1416,7 @@ private static ViewDefinition createViewDefinition(QualifiedObjectName viewName, private Optional getViewInternal(Session session, QualifiedObjectName viewName) { - if (viewName.getCatalogName().isEmpty() || viewName.getSchemaName().isEmpty() || viewName.getObjectName().isEmpty()) { - // View cannot exist + if (cannotExist(viewName)) { return Optional.empty(); } @@ -1631,8 +1637,7 @@ private static MaterializedViewDefinition createMaterializedViewDefinition(Conne private Optional getMaterializedViewInternal(Session session, QualifiedObjectName viewName) { - if (viewName.getCatalogName().isEmpty() || viewName.getSchemaName().isEmpty() || viewName.getObjectName().isEmpty()) { - // View cannot exist + if (cannotExist(viewName)) { return Optional.empty(); } @@ -1722,9 +1727,7 @@ private QualifiedObjectName getRedirectedTableName(Session session, QualifiedObj { requireNonNull(session, "session is null"); requireNonNull(originalTableName, "originalTableName is null"); - - if (originalTableName.getCatalogName().isEmpty() || originalTableName.getSchemaName().isEmpty() || originalTableName.getObjectName().isEmpty()) { - // table cannot exist + if (cannotExist(originalTableName)) { return originalTableName; } @@ -2628,6 +2631,18 @@ private Optional toConnectorVersion(Optionalbuilder() - .add("ConnectorMetadata.listTables(schema=)") - .add("ConnectorMetadata.listViews(schema=)") - .build()); + ImmutableMultiset.of()); // Empty table name assertMetadataCalls( From 1c833bd5ead1700a288594bdf5648eeb8e7ed387 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Mon, 20 Nov 2023 16:36:57 +0100 Subject: [PATCH 368/587] Resolve potential duplicates when listing relations in Hive `listMaterializedViews` can return names already included in metastore's relation listing. --- .../src/main/java/io/trino/plugin/hive/HiveMetadata.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java index 376d758689ba..42209e7fd9c8 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java @@ -794,15 +794,16 @@ public List listTables(ConnectorSession session, Optional> allTables = metastore.getAllTables(); if (allTables.isPresent()) { - return ImmutableList.builder() + return ImmutableSet.builder() .addAll(allTables.get().stream() .filter(table -> !isHiveSystemSchema(table.getSchemaName())) .collect(toImmutableList())) .addAll(listMaterializedViews(session, optionalSchemaName)) - .build(); + .build() + .asList(); } } - ImmutableList.Builder tableNames = ImmutableList.builder(); + ImmutableSet.Builder tableNames = ImmutableSet.builder(); for (String schemaName : listSchemas(session, optionalSchemaName)) { for (String tableName : metastore.getAllTables(schemaName)) { tableNames.add(new SchemaTableName(schemaName, tableName)); @@ -810,7 +811,7 @@ public List listTables(ConnectorSession session, Optional listSchemas(ConnectorSession session, Optional schemaName) From ee23d6d0a2cee928cf295119e6ae6eef04765169 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Mon, 20 Nov 2023 21:07:18 +0100 Subject: [PATCH 369/587] Use enhanced instanceof --- .../java/io/trino/plugin/hive/HiveMetadata.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java index 42209e7fd9c8..71bc4e010046 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java @@ -3777,8 +3777,8 @@ private static void validateTimestampColumns(List columns, HiveT private static void validateTimestampTypes(Type type, HiveTimestampPrecision precision, ColumnMetadata column) { - if (type instanceof TimestampType) { - if (((TimestampType) type).getPrecision() != precision.getPrecision()) { + if (type instanceof TimestampType timestampType) { + if (timestampType.getPrecision() != precision.getPrecision()) { throw new TrinoException(NOT_SUPPORTED, format( "Incorrect timestamp precision for %s; the configured precision is %s; column name: %s", type, @@ -3786,15 +3786,15 @@ private static void validateTimestampTypes(Type type, HiveTimestampPrecision pre column.getName())); } } - else if (type instanceof ArrayType) { - validateTimestampTypes(((ArrayType) type).getElementType(), precision, column); + else if (type instanceof ArrayType arrayType) { + validateTimestampTypes(arrayType.getElementType(), precision, column); } - else if (type instanceof MapType) { - validateTimestampTypes(((MapType) type).getKeyType(), precision, column); - validateTimestampTypes(((MapType) type).getValueType(), precision, column); + else if (type instanceof MapType mapType) { + validateTimestampTypes(mapType.getKeyType(), precision, column); + validateTimestampTypes(mapType.getValueType(), precision, column); } else if (type instanceof RowType) { - for (Type fieldType : ((RowType) type).getTypeParameters()) { + for (Type fieldType : type.getTypeParameters()) { validateTimestampTypes(fieldType, precision, column); } } From 79ae3898dc4ffafa50222408cd1bb97af7134783 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Mon, 20 Nov 2023 17:49:13 +0100 Subject: [PATCH 370/587] Improve list assertions for list.contains Use `AbstractIterableAssert#contains` instead of `assertThat(list.contains(...)).isTrue()`. The `isTrue` version produces less useful error messages. --- .../java/io/trino/jdbc/TestJdbcWarnings.java | 2 +- .../io/trino/execution/TestNodeScheduler.java | 26 +++++++++---------- .../cassandra/TestCassandraConnector.java | 4 +-- .../BaseElasticsearchConnectorTest.java | 2 +- .../trino/plugin/hive/AbstractTestHive.java | 12 ++++----- .../kafka/TestMinimalFunctionality.java | 2 +- ...chemaRegistryTableDescriptionSupplier.java | 8 +++--- .../memory/TestMemoryConnectorTest.java | 2 +- .../plugin/memory/TestMemoryMetadata.java | 6 ++--- ...PrometheusCaseInsensitiveNameMatching.java | 4 +-- .../raptor/legacy/metadata/TestShardDao.java | 10 +++---- .../AbstractTestEngineOnlyQueries.java | 2 +- 12 files changed, 40 insertions(+), 40 deletions(-) diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcWarnings.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcWarnings.java index d5aa7a6f596e..abec642369d6 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcWarnings.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestJdbcWarnings.java @@ -168,7 +168,7 @@ public void testExecuteQueryWarnings() TestingWarningCollector warningCollector = new TestingWarningCollector(new WarningCollectorConfig(), warningCollectorConfig); List expectedWarnings = warningCollector.getWarnings(); for (TrinoWarning trinoWarning : expectedWarnings) { - assertThat(currentWarnings.contains(new WarningEntry(toTrinoSqlWarning(trinoWarning)))).isTrue(); + assertThat(currentWarnings).contains(new WarningEntry(toTrinoSqlWarning(trinoWarning))); } } } diff --git a/core/trino-main/src/test/java/io/trino/execution/TestNodeScheduler.java b/core/trino-main/src/test/java/io/trino/execution/TestNodeScheduler.java index f159ee3e8248..f36ca90d175e 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestNodeScheduler.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestNodeScheduler.java @@ -311,7 +311,7 @@ public void testBasicAssignment() Multimap assignments = nodeSelector.computeAssignments(splits, ImmutableList.copyOf(taskMap.values())).getAssignments(); assertThat(assignments.entries().size()).isEqualTo(assignments.size()); for (InternalNode node : activeCatalogNodes) { - assertThat(assignments.keySet().contains(node)).isTrue(); + assertThat(assignments.keySet()).contains(node); } } @@ -370,7 +370,7 @@ public void testBasicAssignmentMaxUnacknowledgedSplitsPerTask() Multimap assignments = nodeSelector.computeAssignments(splits, ImmutableList.copyOf(taskMap.values())).getAssignments(); assertThat(assignments.entries().size()).isEqualTo(activeCatalogNodes.size()); for (InternalNode node : activeCatalogNodes) { - assertThat(assignments.keySet().contains(node)).isTrue(); + assertThat(assignments.keySet()).contains(node); } } @@ -492,7 +492,7 @@ public void testPrioritizedAssignmentOfLocalSplit() // Check that all splits are being assigned to node1 assertThat(initialAssignment.size()).isEqualTo(20); assertThat(initialAssignment.keySet().size()).isEqualTo(1); - assertThat(initialAssignment.keySet().contains(node)).isTrue(); + assertThat(initialAssignment.keySet()).contains(node); // Check for assignment of splits beyond maxSplitsPerNode (2 splits should remain unassigned) // 1 split with node1 as local node @@ -504,7 +504,7 @@ public void testPrioritizedAssignmentOfLocalSplit() // Check that only 20 splits are being assigned as there is a single task assertThat(finalAssignment.size()).isEqualTo(20); assertThat(finalAssignment.keySet().size()).isEqualTo(1); - assertThat(finalAssignment.keySet().contains(node)).isTrue(); + assertThat(finalAssignment.keySet()).contains(node); // When optimized-local-scheduling is enabled, the split with node1 as local node should be assigned long countLocalSplits = finalAssignment.values().stream() @@ -535,7 +535,7 @@ public void testAssignmentWhenMixedSplits() // Check that all splits are being assigned to node1 assertThat(initialAssignment.size()).isEqualTo(20); assertThat(initialAssignment.keySet().size()).isEqualTo(1); - assertThat(initialAssignment.keySet().contains(node)).isTrue(); + assertThat(initialAssignment.keySet()).contains(node); // Check for assignment of splits beyond maxSplitsPerNode (2 splits should remain unassigned) // 1 split with node1 as local node @@ -547,7 +547,7 @@ public void testAssignmentWhenMixedSplits() // Check that only 20 splits are being assigned as there is a single task assertThat(finalAssignment.size()).isEqualTo(20); assertThat(finalAssignment.keySet().size()).isEqualTo(1); - assertThat(finalAssignment.keySet().contains(node)).isTrue(); + assertThat(finalAssignment.keySet()).contains(node); // When optimized-local-scheduling is enabled, all 11 splits with node1 as local node should be assigned long countLocalSplits = finalAssignment.values().stream() @@ -575,8 +575,8 @@ public void testOptimizedLocalScheduling() // Check that all 20 splits are being assigned to node1 as optimized-local-scheduling is enabled assertThat(assignments1.size()).isEqualTo(20); assertThat(assignments1.keySet().size()).isEqualTo(2); - assertThat(assignments1.keySet().contains(node1)).isTrue(); - assertThat(assignments1.keySet().contains(node2)).isTrue(); + assertThat(assignments1.keySet()).contains(node1); + assertThat(assignments1.keySet()).contains(node2); // 19 splits with node2 as local node to be assigned in the first iteration of computeAssignments for (int i = 0; i < 19; i++) { @@ -586,8 +586,8 @@ public void testOptimizedLocalScheduling() // Check that all 39 splits are being assigned (20 splits assigned to node1 and 19 splits assigned to node2) assertThat(assignments2.size()).isEqualTo(39); assertThat(assignments2.keySet().size()).isEqualTo(2); - assertThat(assignments2.keySet().contains(node1)).isTrue(); - assertThat(assignments2.keySet().contains(node2)).isTrue(); + assertThat(assignments2.keySet()).contains(node1); + assertThat(assignments2.keySet()).contains(node2); long node1Splits = assignments2.values().stream() .map(Split::getConnectorSplit) @@ -610,8 +610,8 @@ public void testOptimizedLocalScheduling() // Check that only 40 splits are being assigned as there is a single task assertThat(assignments3.size()).isEqualTo(40); assertThat(assignments3.keySet().size()).isEqualTo(2); - assertThat(assignments3.keySet().contains(node1)).isTrue(); - assertThat(assignments3.keySet().contains(node2)).isTrue(); + assertThat(assignments3.keySet()).contains(node1); + assertThat(assignments3.keySet()).contains(node2); // The first 20 splits have node1 as local, the next 19 have node2 as local, the 40th split has node1 as local and the 41st has node2 as local // If optimized-local-scheduling is disabled, the 41st split will be unassigned (the last slot in node2 will be taken up by the 40th split with node1 as local) @@ -856,7 +856,7 @@ public void testMaxUnacknowledgedSplitsPerTask() // Attempt to schedule again, only the node with the unblocked task should be chosen splitPlacements = nodeSelector.computeAssignments(splits, ImmutableList.copyOf(tasks)); assertThat(splitPlacements.getAssignments().size()).isEqualTo(1); - assertThat(splitPlacements.getAssignments().keySet().contains(nodes.get(0))).isTrue(); + assertThat(splitPlacements.getAssignments().keySet()).contains(nodes.get(0)); // Make the first node appear to have no splits, unacknowledged splits alone should force the splits to be spread across nodes taskOne.clearSplits(); diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnector.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnector.java index 75b30ffd0135..478e43c9bbc1 100644 --- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnector.java +++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestCassandraConnector.java @@ -155,14 +155,14 @@ public void tearDown() public void testGetDatabaseNames() { List databases = metadata.listSchemaNames(SESSION); - assertThat(databases.contains(database.toLowerCase(ENGLISH))).isTrue(); + assertThat(databases).contains(database.toLowerCase(ENGLISH)); } @Test public void testGetTableNames() { List tables = metadata.listTables(SESSION, Optional.of(database)); - assertThat(tables.contains(table)).isTrue(); + assertThat(tables).contains(table); } @Test diff --git a/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/BaseElasticsearchConnectorTest.java b/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/BaseElasticsearchConnectorTest.java index a9700e9fa1cb..60586c95bbf5 100644 --- a/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/BaseElasticsearchConnectorTest.java +++ b/plugin/trino-elasticsearch/src/test/java/io/trino/plugin/elasticsearch/BaseElasticsearchConnectorTest.java @@ -1803,7 +1803,7 @@ public void testEmptyIndexWithMappings() createIndex(indexName, mappings); assertQuery(format("SELECT column_name FROM information_schema.columns WHERE table_name = '%s'", indexName), "VALUES ('dummy_column')"); - assertThat(computeActual("SHOW TABLES").getOnlyColumnAsSet().contains(indexName)).isTrue(); + assertThat(computeActual("SHOW TABLES").getOnlyColumnAsSet()).contains(indexName); assertQueryReturnsEmptyResult("SELECT * FROM " + indexName); } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java index c572512fb5b2..7c8e572152e8 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java @@ -1053,7 +1053,7 @@ public void testGetDatabaseNames() try (Transaction transaction = newTransaction()) { ConnectorMetadata metadata = transaction.getMetadata(); List databases = metadata.listSchemaNames(newSession()); - assertThat(databases.contains(database)).isTrue(); + assertThat(databases).contains(database); } } @@ -1063,8 +1063,8 @@ public void testGetTableNames() try (Transaction transaction = newTransaction()) { ConnectorMetadata metadata = transaction.getMetadata(); List tables = metadata.listTables(newSession(), Optional.of(database)); - assertThat(tables.contains(tablePartitionFormat)).isTrue(); - assertThat(tables.contains(tableUnpartitioned)).isTrue(); + assertThat(tables).contains(tablePartitionFormat); + assertThat(tables).contains(tableUnpartitioned); } } @@ -1074,8 +1074,8 @@ public void testGetAllTableNames() try (Transaction transaction = newTransaction()) { ConnectorMetadata metadata = transaction.getMetadata(); List tables = metadata.listTables(newSession(), Optional.empty()); - assertThat(tables.contains(tablePartitionFormat)).isTrue(); - assertThat(tables.contains(tableUnpartitioned)).isTrue(); + assertThat(tables).contains(tablePartitionFormat); + assertThat(tables).contains(tableUnpartitioned); } } @@ -4165,7 +4165,7 @@ private void doCreateView(SchemaTableName viewName, boolean replace) assertThat(views.size()).isEqualTo(1); assertThat(views.get(viewName).getOriginalSql()).isEqualTo(definition.getOriginalSql()); - assertThat(metadata.listViews(newSession(), Optional.of(viewName.getSchemaName())).contains(viewName)).isTrue(); + assertThat(metadata.listViews(newSession(), Optional.of(viewName.getSchemaName()))).contains(viewName); } } diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestMinimalFunctionality.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestMinimalFunctionality.java index 33f8dacbc65c..c11b45322c96 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestMinimalFunctionality.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestMinimalFunctionality.java @@ -54,7 +54,7 @@ protected QueryRunner createQueryRunner() @Test public void testTopicExists() { - assertThat(getQueryRunner().listTables(getSession(), "kafka", "default").contains(QualifiedObjectName.valueOf("kafka.default." + topicName))).isTrue(); + assertThat(getQueryRunner().listTables(getSession(), "kafka", "default")).contains(QualifiedObjectName.valueOf("kafka.default." + topicName)); } @Test diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestConfluentSchemaRegistryTableDescriptionSupplier.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestConfluentSchemaRegistryTableDescriptionSupplier.java index ed8cccb9c7be..b9b7ca6e6375 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestConfluentSchemaRegistryTableDescriptionSupplier.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/schema/confluent/TestConfluentSchemaRegistryTableDescriptionSupplier.java @@ -57,7 +57,7 @@ public void testTopicDescription() String subject = topicName + "-value"; SCHEMA_REGISTRY_CLIENT.register(subject, getAvroSchema(schemaTableName.getTableName(), "")); - assertThat(tableDescriptionSupplier.listTables().contains(schemaTableName)).isTrue(); + assertThat(tableDescriptionSupplier.listTables()).contains(schemaTableName); assertThat(getKafkaTopicDescription(tableDescriptionSupplier, schemaTableName)) .isEqualTo( @@ -82,7 +82,7 @@ public void testCaseInsensitiveSubjectMapping() SCHEMA_REGISTRY_CLIENT.register(subject, getAvroSchema(schemaTableName.getTableName(), "")); - assertThat(tableDescriptionSupplier.listTables().contains(schemaTableName)).isTrue(); + assertThat(tableDescriptionSupplier.listTables()).contains(schemaTableName); assertThat(getKafkaTopicDescription(tableDescriptionSupplier, schemaTableName)) .isEqualTo( @@ -107,7 +107,7 @@ public void testAmbiguousSubject() SCHEMA_REGISTRY_CLIENT.register(topicName + "-key", getAvroSchema(schemaTableName.getTableName(), "")); SCHEMA_REGISTRY_CLIENT.register(topicName.toUpperCase(ENGLISH) + "-key", getAvroSchema(schemaTableName.getTableName(), "")); - assertThat(tableDescriptionSupplier.listTables().contains(schemaTableName)).isTrue(); + assertThat(tableDescriptionSupplier.listTables()).contains(schemaTableName); assertThatThrownBy(() -> tableDescriptionSupplier.getTopicDescription( @@ -131,7 +131,7 @@ public void testOverriddenSubject() String overriddenSubject = "overriddenSubject"; SCHEMA_REGISTRY_CLIENT.register(overriddenSubject, getAvroSchema(schemaTableName.getTableName(), "overridden_")); - assertThat(tableDescriptionSupplier.listTables().contains(schemaTableName)).isTrue(); + assertThat(tableDescriptionSupplier.listTables()).contains(schemaTableName); assertThat(getKafkaTopicDescription(tableDescriptionSupplier, schemaTableName)) .isEqualTo( diff --git a/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryConnectorTest.java b/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryConnectorTest.java index b8284237037e..f90fc9ab825c 100644 --- a/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryConnectorTest.java +++ b/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryConnectorTest.java @@ -577,7 +577,7 @@ public void testViews() assertQuery("SELECT * FROM test_view", query); - assertThat(computeActual("SHOW TABLES").getOnlyColumnAsSet().contains("test_view")).isTrue(); + assertThat(computeActual("SHOW TABLES").getOnlyColumnAsSet()).contains("test_view"); assertUpdate("DROP VIEW test_view"); assertQueryFails("DROP VIEW test_view", "line 1:1: View 'memory.default.test_view' does not exist"); diff --git a/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryMetadata.java b/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryMetadata.java index d429b45e4961..e78b85ba7029 100644 --- a/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryMetadata.java +++ b/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryMetadata.java @@ -105,7 +105,7 @@ public void testActiveTableIds() MemoryTableHandle firstTableHandle = (MemoryTableHandle) metadata.getTableHandle(SESSION, firstTableName, Optional.empty(), Optional.empty()); long firstTableId = firstTableHandle.getId(); - assertThat(metadata.beginInsert(SESSION, firstTableHandle, ImmutableList.of(), NO_RETRIES).getActiveTableIds().contains(firstTableId)).isTrue(); + assertThat(metadata.beginInsert(SESSION, firstTableHandle, ImmutableList.of(), NO_RETRIES).getActiveTableIds()).contains(firstTableId); SchemaTableName secondTableName = new SchemaTableName("default", "second_table"); metadata.createTable(SESSION, new ConnectorTableMetadata(secondTableName, ImmutableList.of(), ImmutableMap.of()), false); @@ -115,8 +115,8 @@ public void testActiveTableIds() assertThat(firstTableId) .isNotEqualTo(secondTableId); - assertThat(metadata.beginInsert(SESSION, secondTableHandle, ImmutableList.of(), NO_RETRIES).getActiveTableIds().contains(firstTableId)).isTrue(); - assertThat(metadata.beginInsert(SESSION, secondTableHandle, ImmutableList.of(), NO_RETRIES).getActiveTableIds().contains(secondTableId)).isTrue(); + assertThat(metadata.beginInsert(SESSION, secondTableHandle, ImmutableList.of(), NO_RETRIES).getActiveTableIds()).contains(firstTableId); + assertThat(metadata.beginInsert(SESSION, secondTableHandle, ImmutableList.of(), NO_RETRIES).getActiveTableIds()).contains(secondTableId); } @Test diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusCaseInsensitiveNameMatching.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusCaseInsensitiveNameMatching.java index 6cf39173bdad..88fbbf069a6c 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusCaseInsensitiveNameMatching.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusCaseInsensitiveNameMatching.java @@ -55,7 +55,7 @@ public void testCaseInsensitiveNameMatchingFalse() Set tableNames = client.getTableNames(DEFAULT_SCHEMA); assertThat(tableNames).hasSize(1); - assertThat(tableNames.contains(UPPER_CASE_METRIC)).isTrue(); + assertThat(tableNames).contains(UPPER_CASE_METRIC); PrometheusMetadata metadata = new PrometheusMetadata(client); List tables = metadata.listTables(null, Optional.of(DEFAULT_SCHEMA)); @@ -76,7 +76,7 @@ public void testCaseInsensitiveNameMatchingTrue() Set tableNames = client.getTableNames(DEFAULT_SCHEMA); assertThat(tableNames).hasSize(1); - assertThat(tableNames.contains(UPPER_CASE_METRIC)).isTrue(); + assertThat(tableNames).contains(UPPER_CASE_METRIC); PrometheusMetadata metadata = new PrometheusMetadata(client); List tables = metadata.listTables(null, Optional.of(DEFAULT_SCHEMA)); diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardDao.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardDao.java index 7d9cdf956763..df9de586c3ee 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardDao.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/metadata/TestShardDao.java @@ -249,10 +249,10 @@ public void testShardSelection() Set shards = dao.getShards(tableId); assertThat(shards.size()).isEqualTo(4); - assertThat(shards.contains(shardUuid1)).isTrue(); - assertThat(shards.contains(shardUuid2)).isTrue(); - assertThat(shards.contains(shardUuid3)).isTrue(); - assertThat(shards.contains(shardUuid4)).isTrue(); + assertThat(shards).contains(shardUuid1); + assertThat(shards).contains(shardUuid2); + assertThat(shards).contains(shardUuid3); + assertThat(shards).contains(shardUuid4); assertThat(dao.getShardNodes(tableId).size()).isEqualTo(0); @@ -283,6 +283,6 @@ private long createTable(String name) private static void assertContainsShardNode(Set nodes, String nodeName, UUID shardUuid) { - assertThat(nodes.contains(new ShardNode(shardUuid, nodeName))).isTrue(); + assertThat(nodes).contains(new ShardNode(shardUuid, nodeName)); } } diff --git a/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestEngineOnlyQueries.java b/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestEngineOnlyQueries.java index 88096a00accd..7fdd84268ac7 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestEngineOnlyQueries.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestEngineOnlyQueries.java @@ -6122,7 +6122,7 @@ public void testShowTablesLikeWithEscape() public void testShowCatalogs() { MaterializedResult result = computeActual("SHOW CATALOGS"); - assertThat(result.getOnlyColumnAsSet().contains(getSession().getCatalog().get())).isTrue(); + assertThat(result.getOnlyColumnAsSet()).contains(getSession().getCatalog().get()); } @Test From ab9871a6926b10d56383d96e9038cc2586fa39c1 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Mon, 20 Nov 2023 17:49:13 +0100 Subject: [PATCH 371/587] Improve map assertions for map.containsKey Use `AbstractMapAssert#containsKey` instead of `assertThat(map.containsKey(...)).isTrue()`. The `isTrue` version produces less useful error messages. --- .../java/io/trino/block/TestMapBlock.java | 3 +-- .../server/security/jwt/TestJwkService.java | 6 +++--- .../decoder/json/JsonFieldDecoderTester.java | 4 +--- .../trino/plugin/hive/AbstractTestHive.java | 14 ++++++------- .../db/TestDbSessionPropertyManager.java | 20 +++++++++++-------- .../tpcds/TestTpcdsMetadataStatistics.java | 4 ++-- 6 files changed, 26 insertions(+), 25 deletions(-) diff --git a/core/trino-main/src/test/java/io/trino/block/TestMapBlock.java b/core/trino-main/src/test/java/io/trino/block/TestMapBlock.java index 9052e58ddacd..74faf7d13dba 100644 --- a/core/trino-main/src/test/java/io/trino/block/TestMapBlock.java +++ b/core/trino-main/src/test/java/io/trino/block/TestMapBlock.java @@ -337,8 +337,7 @@ private void assertValue(Block mapBlock, int position, Map map) else { actualValue = BIGINT.getLong(rawValueBlock, rawOffset + i); } - assertThat(map.containsKey(actualKey)).isTrue(); - assertThat(actualValue).isEqualTo(map.get(actualKey)); + assertThat(map).containsEntry(actualKey, actualValue); } } diff --git a/core/trino-main/src/test/java/io/trino/server/security/jwt/TestJwkService.java b/core/trino-main/src/test/java/io/trino/server/security/jwt/TestJwkService.java index d1e405ddf84e..3cd018eefd23 100644 --- a/core/trino-main/src/test/java/io/trino/server/security/jwt/TestJwkService.java +++ b/core/trino-main/src/test/java/io/trino/server/security/jwt/TestJwkService.java @@ -197,8 +197,8 @@ private static void assertTestKeys(JwkService service) { Map keys = service.getKeys(); assertThat(keys.size()).isEqualTo(3); - assertThat(keys.containsKey("test-rsa")).isTrue(); - assertThat(keys.containsKey("test-ec")).isTrue(); - assertThat(keys.containsKey("test-certificate-chain")).isTrue(); + assertThat(keys).containsKey("test-rsa"); + assertThat(keys).containsKey("test-ec"); + assertThat(keys).containsKey("test-certificate-chain"); } } diff --git a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/JsonFieldDecoderTester.java b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/JsonFieldDecoderTester.java index 72e2a3e52d43..6e49aa1c0f91 100644 --- a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/JsonFieldDecoderTester.java +++ b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/JsonFieldDecoderTester.java @@ -145,9 +145,7 @@ private FieldValueProvider decode(Optional jsonValue, Type type) RowDecoder rowDecoder = DECODER_FACTORY.create(TESTING_SESSION, new RowDecoderSpec(JsonRowDecoder.NAME, emptyMap(), ImmutableSet.of(columnHandle))); Map decodedRow = rowDecoder.decodeRow(json.getBytes(UTF_8)) .orElseThrow(AssertionError::new); - assertThat(decodedRow.containsKey(columnHandle)) - .describedAs(format("column '%s' not found in decoded row", columnHandle.getName())) - .isTrue(); + assertThat(decodedRow).containsKey(columnHandle); return decodedRow.get(columnHandle); } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java index 7c8e572152e8..25a7945c9eef 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java @@ -1085,8 +1085,8 @@ public void testGetAllTableColumns() try (Transaction transaction = newTransaction()) { ConnectorMetadata metadata = transaction.getMetadata(); Map> allColumns = listTableColumns(metadata, newSession(), new SchemaTablePrefix()); - assertThat(allColumns.containsKey(tablePartitionFormat)).isTrue(); - assertThat(allColumns.containsKey(tableUnpartitioned)).isTrue(); + assertThat(allColumns).containsKey(tablePartitionFormat); + assertThat(allColumns).containsKey(tableUnpartitioned); } } @@ -1096,8 +1096,8 @@ public void testGetAllTableColumnsInSchema() try (Transaction transaction = newTransaction()) { ConnectorMetadata metadata = transaction.getMetadata(); Map> allColumns = listTableColumns(metadata, newSession(), new SchemaTablePrefix(database)); - assertThat(allColumns.containsKey(tablePartitionFormat)).isTrue(); - assertThat(allColumns.containsKey(tableUnpartitioned)).isTrue(); + assertThat(allColumns).containsKey(tablePartitionFormat); + assertThat(allColumns).containsKey(tableUnpartitioned); } } @@ -3573,7 +3573,7 @@ public void testIllegalStorageFormatDuringTableScan() try (Transaction transaction = newTransaction()) { ConnectorMetadata metadata = transaction.getMetadata(); Map> allColumns = listTableColumns(metadata, newSession(), new SchemaTablePrefix(schemaTableName.getSchemaName())); - assertThat(allColumns.containsKey(schemaTableName)).isTrue(); + assertThat(allColumns).containsKey(schemaTableName); } finally { dropTable(schemaTableName); @@ -3968,7 +3968,7 @@ private static void assertProjectionResult(Optional map, String name, Type type, boolean partitionKey) { - assertThat(map.containsKey(name)).isTrue(); + assertThat(map).containsKey(name); ColumnMetadata column = map.get(name); assertThat(column.getType()) .describedAs(name) diff --git a/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/db/TestDbSessionPropertyManager.java b/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/db/TestDbSessionPropertyManager.java index 6e21e9acfded..e681d77fe328 100644 --- a/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/db/TestDbSessionPropertyManager.java +++ b/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/db/TestDbSessionPropertyManager.java @@ -137,28 +137,32 @@ public void testSessionProperties() SessionConfigurationContext context1 = new SessionConfigurationContext("foo123", Optional.of("src1"), ImmutableSet.of(), Optional.empty(), TEST_RG); Map sessionProperties1 = manager.getSystemSessionProperties(context1); - assertThat(sessionProperties1.get("prop_1")).isEqualTo("val_1"); - assertThat(sessionProperties1.containsKey("prop_2")).isFalse(); + assertThat(sessionProperties1) + .containsEntry("prop_1", "val_1") + .doesNotContainKey("prop_2"); specsProvider.refresh(); SessionConfigurationContext context2 = new SessionConfigurationContext("bar123", Optional.of("bar123"), ImmutableSet.of(), Optional.empty(), TEST_RG); Map sessionProperties2 = manager.getSystemSessionProperties(context2); - assertThat(sessionProperties2.get("prop_2")).isEqualTo("val_2"); - assertThat(sessionProperties2.containsKey("prop_1")).isFalse(); + assertThat(sessionProperties2) + .doesNotContainKey("prop_1") + .containsEntry("prop_2", "val_2"); specsProvider.refresh(); SessionConfigurationContext context3 = new SessionConfigurationContext("foo123", Optional.of("bar123"), ImmutableSet.of(), Optional.empty(), TEST_RG); Map sessionProperties3 = manager.getSystemSessionProperties(context3); - assertThat(sessionProperties3.get("prop_1")).isEqualTo("val_1"); - assertThat(sessionProperties3.get("prop_2")).isEqualTo("val_2"); + assertThat(sessionProperties3) + .containsEntry("prop_1", "val_1") + .containsEntry("prop_2", "val_2"); specsProvider.refresh(); SessionConfigurationContext context4 = new SessionConfigurationContext("abc", Optional.empty(), ImmutableSet.of(), Optional.empty(), TEST_RG); Map sessionProperties4 = manager.getSystemSessionProperties(context4); - assertThat(sessionProperties4.containsKey("prop_1")).isFalse(); - assertThat(sessionProperties4.containsKey("prop_2")).isFalse(); + assertThat(sessionProperties4) + .doesNotContainKey("prop_1") + .doesNotContainKey("prop_2"); } /** diff --git a/plugin/trino-tpcds/src/test/java/io/trino/plugin/tpcds/TestTpcdsMetadataStatistics.java b/plugin/trino-tpcds/src/test/java/io/trino/plugin/tpcds/TestTpcdsMetadataStatistics.java index c65dec50f9cb..4b2496e556cb 100644 --- a/plugin/trino-tpcds/src/test/java/io/trino/plugin/tpcds/TestTpcdsMetadataStatistics.java +++ b/plugin/trino-tpcds/src/test/java/io/trino/plugin/tpcds/TestTpcdsMetadataStatistics.java @@ -63,7 +63,7 @@ public void testTableStatsExistenceSupportedSchema() TableStatistics tableStatistics = metadata.getTableStatistics(session, tableHandle); assertThat(tableStatistics.getRowCount().isUnknown()).isFalse(); for (ColumnHandle column : metadata.getColumnHandles(session, tableHandle).values()) { - assertThat(tableStatistics.getColumnStatistics().containsKey(column)).isTrue(); + assertThat(tableStatistics.getColumnStatistics()).containsKey(column); assertThat(tableStatistics.getColumnStatistics().get(column)).isNotNull(); } })); @@ -81,7 +81,7 @@ public void testTableStatsDetails() // all columns have stats Map columnHandles = metadata.getColumnHandles(session, tableHandle); for (ColumnHandle column : columnHandles.values()) { - assertThat(tableStatistics.getColumnStatistics().containsKey(column)).isTrue(); + assertThat(tableStatistics.getColumnStatistics()).containsKey(column); assertThat(tableStatistics.getColumnStatistics().get(column)).isNotNull(); } From ccf6b62761eb5385077c1d804131e9fcb0e97c49 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Mon, 20 Nov 2023 17:49:13 +0100 Subject: [PATCH 372/587] Improve map assertions for map entries Use `AbstractMapAssert#containsEntry` instead of `assertThat(map.get(...)).isEqualTo(...)`. The `isEqualTo` version produces less useful error message, because it does not know the map key being asserted. --- .../trino/execution/TestSetTimeZoneTask.java | 10 +-- ...rtitionedPipelinedOutputBufferManager.java | 2 +- .../sql/planner/TestPartialTranslator.java | 2 +- .../java/io/trino/geospatial/TestKdbTree.java | 62 +++++++------- .../java/io/trino/hdfs/TestHdfsConfig.java | 2 +- .../deltalake/TestDeltaLakeAnalyze.java | 4 +- .../plugin/deltalake/TestDeltaLakeBasic.java | 23 +++--- .../deltalake/TestDeltaLakeColumnMapping.java | 3 +- .../trino/plugin/hive/AbstractTestHive.java | 12 +-- .../hive/AbstractTestHiveFileSystem.java | 2 +- .../plugin/hive/BaseHiveConnectorTest.java | 82 +++++++++---------- .../plugin/hive/TestHiveSplitSource.java | 2 +- .../metastore/glue/TestHiveGlueMetastore.java | 2 +- .../predicate/TestParquetPredicateUtils.java | 2 +- .../plugin/hive/util/TestStatistics.java | 6 +- .../TestIcebergOrcMetricsCollection.java | 76 ++++++++--------- .../trino/plugin/iceberg/TestIcebergV2.java | 28 +++---- .../plugin/kafka/TestKafkaSslConfig.java | 2 +- .../memory/TestMemoryConnectorTest.java | 8 +- ...estPrometheusQueryMatrixResponseParse.java | 2 +- ...estPrometheusQueryScalarResponseParse.java | 2 +- ...estPrometheusQueryVectorResponseParse.java | 2 +- .../prometheus/TestPrometheusSplit.java | 4 +- .../db/TestDbSessionPropertyManager.java | 10 +-- 24 files changed, 173 insertions(+), 177 deletions(-) diff --git a/core/trino-main/src/test/java/io/trino/execution/TestSetTimeZoneTask.java b/core/trino-main/src/test/java/io/trino/execution/TestSetTimeZoneTask.java index 17cd7ab5a62e..81afebf50c40 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestSetTimeZoneTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestSetTimeZoneTask.java @@ -103,7 +103,7 @@ public void testSetTimeZoneStringLiteral() Map setSessionProperties = stateMachine.getSetSessionProperties(); assertThat(setSessionProperties).hasSize(1); - assertThat(setSessionProperties.get(TIME_ZONE_ID)).isEqualTo("America/Los_Angeles"); + assertThat(setSessionProperties).containsEntry(TIME_ZONE_ID, "America/Los_Angeles"); } @Test @@ -129,7 +129,7 @@ public void testSetTimeZoneVarcharFunctionCall() Map setSessionProperties = stateMachine.getSetSessionProperties(); assertThat(setSessionProperties).hasSize(1); - assertThat(setSessionProperties.get(TIME_ZONE_ID)).isEqualTo("America/Los_Angeles"); + assertThat(setSessionProperties).containsEntry(TIME_ZONE_ID, "America/Los_Angeles"); } @Test @@ -170,7 +170,7 @@ public void testSetTimeZoneIntervalLiteral() Map setSessionProperties = stateMachine.getSetSessionProperties(); assertThat(setSessionProperties).hasSize(1); - assertThat(setSessionProperties.get(TIME_ZONE_ID)).isEqualTo("+10:00"); + assertThat(setSessionProperties).containsEntry(TIME_ZONE_ID, "+10:00"); } @Test @@ -190,7 +190,7 @@ public void testSetTimeZoneIntervalDayTimeTypeFunctionCall() Map setSessionProperties = stateMachine.getSetSessionProperties(); assertThat(setSessionProperties).hasSize(1); - assertThat(setSessionProperties.get(TIME_ZONE_ID)).isEqualTo("+08:00"); + assertThat(setSessionProperties).containsEntry(TIME_ZONE_ID, "+08:00"); } @Test @@ -246,7 +246,7 @@ public void testSetTimeIntervalLiteralZoneHourToMinute() Map setSessionProperties = stateMachine.getSetSessionProperties(); assertThat(setSessionProperties).hasSize(1); - assertThat(setSessionProperties.get(TIME_ZONE_ID)).isEqualTo("-08:00"); + assertThat(setSessionProperties).containsEntry(TIME_ZONE_ID, "-08:00"); } private QueryStateMachine createQueryStateMachine(String query) diff --git a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestPartitionedPipelinedOutputBufferManager.java b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestPartitionedPipelinedOutputBufferManager.java index c2ee6ae4e70f..7ef9f4c35879 100644 --- a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestPartitionedPipelinedOutputBufferManager.java +++ b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestPartitionedPipelinedOutputBufferManager.java @@ -59,7 +59,7 @@ private static void assertOutputBuffers(PipelinedOutputBuffers outputBuffers) Map buffers = outputBuffers.getBuffers(); assertThat(buffers.size()).isEqualTo(4); for (int partition = 0; partition < 4; partition++) { - assertThat(buffers.get(new OutputBufferId(partition))).isEqualTo(Integer.valueOf(partition)); + assertThat(buffers).containsEntry(new OutputBufferId(partition), Integer.valueOf(partition)); } } } diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestPartialTranslator.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestPartialTranslator.java index d38efafaf9b7..53af1ce5e0ec 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestPartialTranslator.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestPartialTranslator.java @@ -103,7 +103,7 @@ private void assertPartialTranslation(Expression expression, List su Map, ConnectorExpression> translation = extractPartialTranslations(expression, TEST_SESSION, TYPE_ANALYZER, TYPE_PROVIDER, PLANNER_CONTEXT); assertThat(subexpressions.size()).isEqualTo(translation.size()); for (Expression subexpression : subexpressions) { - assertThat(translation.get(NodeRef.of(subexpression))).isEqualTo(translate(TEST_SESSION, subexpression, TYPE_PROVIDER, PLANNER_CONTEXT, TYPE_ANALYZER).get()); + assertThat(translation).containsEntry(NodeRef.of(subexpression), translate(TEST_SESSION, subexpression, TYPE_PROVIDER, PLANNER_CONTEXT, TYPE_ANALYZER).get()); } } diff --git a/lib/trino-geospatial-toolkit/src/test/java/io/trino/geospatial/TestKdbTree.java b/lib/trino-geospatial-toolkit/src/test/java/io/trino/geospatial/TestKdbTree.java index 9a4d7ef18b30..f58b580acdca 100644 --- a/lib/trino-geospatial-toolkit/src/test/java/io/trino/geospatial/TestKdbTree.java +++ b/lib/trino-geospatial-toolkit/src/test/java/io/trino/geospatial/TestKdbTree.java @@ -96,8 +96,8 @@ private void testSplitVertically(double width, double height) Map leafNodes = treeCopy.getLeaves(); assertThat(leafNodes.size()).isEqualTo(2); assertThat(leafNodes.keySet()).isEqualTo(ImmutableSet.of(0, 1)); - assertThat(leafNodes.get(0)).isEqualTo(new Rectangle(0, 0, 4.5, 4)); - assertThat(leafNodes.get(1)).isEqualTo(new Rectangle(4.5, 0, 9, 4)); + assertThat(leafNodes).containsEntry(0, new Rectangle(0, 0, 4.5, 4)); + assertThat(leafNodes).containsEntry(1, new Rectangle(4.5, 0, 9, 4)); assertPartitions(treeCopy, new Rectangle(1, 1, 2, 2), ImmutableSet.of(0)); assertPartitions(treeCopy, new Rectangle(1, 1, 5, 2), ImmutableSet.of(0, 1)); @@ -125,8 +125,8 @@ private void testSplitHorizontally(double width, double height) Map leafNodes = tree.getLeaves(); assertThat(leafNodes.size()).isEqualTo(2); assertThat(leafNodes.keySet()).isEqualTo(ImmutableSet.of(0, 1)); - assertThat(leafNodes.get(0)).isEqualTo(new Rectangle(0, 0, 4, 4.5)); - assertThat(leafNodes.get(1)).isEqualTo(new Rectangle(0, 4.5, 4, 9)); + assertThat(leafNodes).containsEntry(0, new Rectangle(0, 0, 4, 4.5)); + assertThat(leafNodes).containsEntry(1, new Rectangle(0, 4.5, 4, 9)); // points inside and outside partitions assertPartitions(tree, new Rectangle(1, 1, 1, 1), ImmutableSet.of(0)); @@ -172,12 +172,12 @@ private void testEvenDistribution(double width, double height) Map leafNodes = tree.getLeaves(); assertThat(leafNodes.size()).isEqualTo(6); assertThat(leafNodes.keySet()).isEqualTo(ImmutableSet.of(0, 1, 2, 3, 4, 5)); - assertThat(leafNodes.get(0)).isEqualTo(new Rectangle(0, 0, 2.5, 2.5)); - assertThat(leafNodes.get(1)).isEqualTo(new Rectangle(0, 2.5, 2.5, 4)); - assertThat(leafNodes.get(2)).isEqualTo(new Rectangle(2.5, 0, 4.5, 4)); - assertThat(leafNodes.get(3)).isEqualTo(new Rectangle(4.5, 0, 7.5, 2.5)); - assertThat(leafNodes.get(4)).isEqualTo(new Rectangle(4.5, 2.5, 7.5, 4)); - assertThat(leafNodes.get(5)).isEqualTo(new Rectangle(7.5, 0, 9, 4)); + assertThat(leafNodes).containsEntry(0, new Rectangle(0, 0, 2.5, 2.5)); + assertThat(leafNodes).containsEntry(1, new Rectangle(0, 2.5, 2.5, 4)); + assertThat(leafNodes).containsEntry(2, new Rectangle(2.5, 0, 4.5, 4)); + assertThat(leafNodes).containsEntry(3, new Rectangle(4.5, 0, 7.5, 2.5)); + assertThat(leafNodes).containsEntry(4, new Rectangle(4.5, 2.5, 7.5, 4)); + assertThat(leafNodes).containsEntry(5, new Rectangle(7.5, 0, 9, 4)); } @Test @@ -208,15 +208,15 @@ private void testSkewedDistribution(double width, double height) Map leafNodes = tree.getLeaves(); assertThat(leafNodes.size()).isEqualTo(9); assertThat(leafNodes.keySet()).isEqualTo(ImmutableSet.of(0, 1, 2, 3, 4, 5, 6, 7, 8)); - assertThat(leafNodes.get(0)).isEqualTo(new Rectangle(0, 0, 1.5, 2.5)); - assertThat(leafNodes.get(1)).isEqualTo(new Rectangle(1.5, 0, 3.5, 2.5)); - assertThat(leafNodes.get(2)).isEqualTo(new Rectangle(0, 2.5, 3.5, 4)); - assertThat(leafNodes.get(3)).isEqualTo(new Rectangle(3.5, 0, 5.1, 1.75)); - assertThat(leafNodes.get(4)).isEqualTo(new Rectangle(3.5, 1.75, 5.1, 4)); - assertThat(leafNodes.get(5)).isEqualTo(new Rectangle(5.1, 0, 5.9, 1.75)); - assertThat(leafNodes.get(6)).isEqualTo(new Rectangle(5.9, 0, 9, 1.75)); - assertThat(leafNodes.get(7)).isEqualTo(new Rectangle(5.1, 1.75, 7.5, 4)); - assertThat(leafNodes.get(8)).isEqualTo(new Rectangle(7.5, 1.75, 9, 4)); + assertThat(leafNodes).containsEntry(0, new Rectangle(0, 0, 1.5, 2.5)); + assertThat(leafNodes).containsEntry(1, new Rectangle(1.5, 0, 3.5, 2.5)); + assertThat(leafNodes).containsEntry(2, new Rectangle(0, 2.5, 3.5, 4)); + assertThat(leafNodes).containsEntry(3, new Rectangle(3.5, 0, 5.1, 1.75)); + assertThat(leafNodes).containsEntry(4, new Rectangle(3.5, 1.75, 5.1, 4)); + assertThat(leafNodes).containsEntry(5, new Rectangle(5.1, 0, 5.9, 1.75)); + assertThat(leafNodes).containsEntry(6, new Rectangle(5.9, 0, 9, 1.75)); + assertThat(leafNodes).containsEntry(7, new Rectangle(5.1, 1.75, 7.5, 4)); + assertThat(leafNodes).containsEntry(8, new Rectangle(7.5, 1.75, 9, 4)); } @Test @@ -242,16 +242,16 @@ private void testCantSplitVertically(double width, double height) Map leafNodes = tree.getLeaves(); assertThat(leafNodes.size()).isEqualTo(10); assertThat(leafNodes.keySet()).isEqualTo(ImmutableSet.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)); - assertThat(leafNodes.get(0)).isEqualTo(new Rectangle(0, 0, 4.5, 0.5)); - assertThat(leafNodes.get(1)).isEqualTo(new Rectangle(0, 0.5, 4.5, 1.5)); - assertThat(leafNodes.get(2)).isEqualTo(new Rectangle(0, 1.5, 4.5, 2.5)); - assertThat(leafNodes.get(3)).isEqualTo(new Rectangle(0, 2.5, 4.5, 3.5)); - assertThat(leafNodes.get(4)).isEqualTo(new Rectangle(0, 3.5, 4.5, 4 + height)); - assertThat(leafNodes.get(5)).isEqualTo(new Rectangle(4.5, 0, 9 + width, 0.5)); - assertThat(leafNodes.get(6)).isEqualTo(new Rectangle(4.5, 0.5, 9 + width, 1.5)); - assertThat(leafNodes.get(7)).isEqualTo(new Rectangle(4.5, 1.5, 9 + width, 2.5)); - assertThat(leafNodes.get(8)).isEqualTo(new Rectangle(4.5, 2.5, 9 + width, 3.5)); - assertThat(leafNodes.get(9)).isEqualTo(new Rectangle(4.5, 3.5, 9 + width, 4 + height)); + assertThat(leafNodes).containsEntry(0, new Rectangle(0, 0, 4.5, 0.5)); + assertThat(leafNodes).containsEntry(1, new Rectangle(0, 0.5, 4.5, 1.5)); + assertThat(leafNodes).containsEntry(2, new Rectangle(0, 1.5, 4.5, 2.5)); + assertThat(leafNodes).containsEntry(3, new Rectangle(0, 2.5, 4.5, 3.5)); + assertThat(leafNodes).containsEntry(4, new Rectangle(0, 3.5, 4.5, 4 + height)); + assertThat(leafNodes).containsEntry(5, new Rectangle(4.5, 0, 9 + width, 0.5)); + assertThat(leafNodes).containsEntry(6, new Rectangle(4.5, 0.5, 9 + width, 1.5)); + assertThat(leafNodes).containsEntry(7, new Rectangle(4.5, 1.5, 9 + width, 2.5)); + assertThat(leafNodes).containsEntry(8, new Rectangle(4.5, 2.5, 9 + width, 3.5)); + assertThat(leafNodes).containsEntry(9, new Rectangle(4.5, 3.5, 9 + width, 4 + height)); } @Test @@ -277,7 +277,7 @@ private void testCantSplit(double width, double height) Map leafNodes = tree.getLeaves(); assertThat(leafNodes.size()).isEqualTo(2); assertThat(leafNodes.keySet()).isEqualTo(ImmutableSet.of(0, 1)); - assertThat(leafNodes.get(0)).isEqualTo(new Rectangle(0, 0, 4.5, 4 + height)); - assertThat(leafNodes.get(1)).isEqualTo(new Rectangle(4.5, 0, 9 + width, 4 + height)); + assertThat(leafNodes).containsEntry(0, new Rectangle(0, 0, 4.5, 4 + height)); + assertThat(leafNodes).containsEntry(1, new Rectangle(4.5, 0, 9 + width, 4 + height)); } } diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestHdfsConfig.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestHdfsConfig.java index 55459f9f900a..03bf0b6219ff 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestHdfsConfig.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/TestHdfsConfig.java @@ -104,7 +104,7 @@ public void testNewDirectoryPermissionsMapping() HdfsConfig expected = new HdfsConfig() .setNewDirectoryPermissions("skip"); - assertThat(properties.get("hive.fs.new-directory-permissions")).isEqualTo(expected.getNewDirectoryPermissions()); + assertThat(properties).containsEntry("hive.fs.new-directory-permissions", expected.getNewDirectoryPermissions()); assertThat(Optional.empty()).isEqualTo(expected.getNewDirectoryFsPermissions()); } } diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeAnalyze.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeAnalyze.java index 19fcdd41f38f..b2c749d51345 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeAnalyze.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeAnalyze.java @@ -1006,8 +1006,8 @@ public void testNoColumnStatsMixedCase() assertThat(transactionLogAfterUpdate).hasSize(2); AddFileEntry updateAddFileEntry = transactionLogAfterUpdate.get(1).getAdd(); DeltaLakeFileStatistics updateStats = updateAddFileEntry.getStats().orElseThrow(); - assertThat(updateStats.getMinValues().orElseThrow().get("c_Int")).isEqualTo(2); - assertThat(updateStats.getMaxValues().orElseThrow().get("c_Int")).isEqualTo(11); + assertThat(updateStats.getMinValues().orElseThrow()).containsEntry("c_Int", 2); + assertThat(updateStats.getMaxValues().orElseThrow()).containsEntry("c_Int", 11); assertThat(updateStats.getNullCount("c_Int").orElseThrow()).isEqualTo(1); assertThat(updateStats.getNullCount("c_Str").orElseThrow()).isEqualTo(1); diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeBasic.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeBasic.java index 501c409d6679..63083a1e4a14 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeBasic.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeBasic.java @@ -200,8 +200,9 @@ private void testAddNestedColumnWithColumnMappingMode(String columnMappingMode) assertUpdate("ALTER TABLE " + tableName + " ADD COLUMN second_col row(a array(integer), b map(integer, integer), c row(field integer))"); MetadataEntry metadata = loadMetadataEntry(1, tableLocation); - assertThat(metadata.getConfiguration().get("delta.columnMapping.maxColumnId")) - .isEqualTo("6"); // +5 comes from second_col + second_col.a + second_col.b + second_col.c + second_col.c.field + assertThat(metadata.getConfiguration()).containsEntry( + "delta.columnMapping.maxColumnId", + "6"); // +5 comes from second_col + second_col.a + second_col.b + second_col.c + second_col.c.field JsonNode schema = OBJECT_MAPPER.readTree(metadata.getSchemaString()); List fields = ImmutableList.copyOf(schema.get("fields").elements()); @@ -301,8 +302,8 @@ private void testOptimizeWithColumnMappingMode(String columnMappingMode) assertThat(transactionLog.get(4).getAdd()).isNotNull(); AddFileEntry addFileEntry = transactionLog.get(4).getAdd(); DeltaLakeFileStatistics stats = addFileEntry.getStats().orElseThrow(); - assertThat(stats.getMinValues().orElseThrow().get(physicalName)).isEqualTo(10); - assertThat(stats.getMaxValues().orElseThrow().get(physicalName)).isEqualTo(20); + assertThat(stats.getMinValues().orElseThrow()).containsEntry(physicalName, 10); + assertThat(stats.getMaxValues().orElseThrow()).containsEntry(physicalName, 20); assertThat(stats.getNullCount(physicalName).orElseThrow()).isEqualTo(1); // Verify optimized parquet file contains the expected physical id and name @@ -347,8 +348,7 @@ private void testDropColumnWithColumnMappingMode(String columnMappingMode) assertUpdate("ALTER TABLE " + tableName + " ADD COLUMN second_col row(a array(integer), b map(integer, integer), c row(field integer))"); MetadataEntry metadata = loadMetadataEntry(1, tableLocation); - assertThat(metadata.getConfiguration().get("delta.columnMapping.maxColumnId")) - .isEqualTo("6"); // +5 comes from second_col + second_col.a + second_col.b + second_col.c + second_col.c.field + assertThat(metadata.getConfiguration()).containsEntry("delta.columnMapping.maxColumnId", "6"); // +5 comes from second_col + second_col.a + second_col.b + second_col.c + second_col.c.field assertThat(metadata.getSchemaString()) .containsPattern("(delta\\.columnMapping\\.id.*?){6}") .containsPattern("(delta\\.columnMapping\\.physicalName.*?){6}"); @@ -401,8 +401,7 @@ private void testRenameColumnWithColumnMappingMode(String columnMappingMode) assertUpdate("ALTER TABLE " + tableName + " ADD COLUMN second_col row(a array(integer), b map(integer, integer), c row(field integer))"); MetadataEntry metadata = loadMetadataEntry(1, tableLocation); - assertThat(metadata.getConfiguration().get("delta.columnMapping.maxColumnId")) - .isEqualTo("6"); // +5 comes from second_col + second_col.a + second_col.b + second_col.c + second_col.c.field + assertThat(metadata.getConfiguration()).containsEntry("delta.columnMapping.maxColumnId", "6"); // +5 comes from second_col + second_col.a + second_col.b + second_col.c + second_col.c.field assertThat(metadata.getSchemaString()) .containsPattern("(delta\\.columnMapping\\.id.*?){6}") .containsPattern("(delta\\.columnMapping\\.physicalName.*?){6}"); @@ -522,8 +521,8 @@ public void testStatisticsWithColumnCaseSensitivity() assertThat(transactionLog).hasSize(2); AddFileEntry addFileEntry = transactionLog.get(1).getAdd(); DeltaLakeFileStatistics stats = addFileEntry.getStats().orElseThrow(); - assertThat(stats.getMinValues().orElseThrow().get("UPPER_CASE")).isEqualTo(10); - assertThat(stats.getMaxValues().orElseThrow().get("UPPER_CASE")).isEqualTo(20); + assertThat(stats.getMinValues().orElseThrow()).containsEntry("UPPER_CASE", 10); + assertThat(stats.getMaxValues().orElseThrow()).containsEntry("UPPER_CASE", 20); assertThat(stats.getNullCount("UPPER_CASE").orElseThrow()).isEqualTo(1); assertUpdate("UPDATE " + tableName + " SET upper_case = upper_case + 10", 3); @@ -532,8 +531,8 @@ public void testStatisticsWithColumnCaseSensitivity() assertThat(transactionLogAfterUpdate).hasSize(3); AddFileEntry updateAddFileEntry = transactionLogAfterUpdate.get(2).getAdd(); DeltaLakeFileStatistics updateStats = updateAddFileEntry.getStats().orElseThrow(); - assertThat(updateStats.getMinValues().orElseThrow().get("UPPER_CASE")).isEqualTo(20); - assertThat(updateStats.getMaxValues().orElseThrow().get("UPPER_CASE")).isEqualTo(30); + assertThat(updateStats.getMinValues().orElseThrow()).containsEntry("UPPER_CASE", 20); + assertThat(updateStats.getMaxValues().orElseThrow()).containsEntry("UPPER_CASE", 30); assertThat(updateStats.getNullCount("UPPER_CASE").orElseThrow()).isEqualTo(1); assertQuery( diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeColumnMapping.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeColumnMapping.java index 8001bb889a27..2a2f7a284a05 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeColumnMapping.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeColumnMapping.java @@ -98,8 +98,7 @@ private void testCreateTableColumnMappingMode(Consumer createTable) String tableLocation = getTableLocation(tableName); MetadataEntry metadata = loadMetadataEntry(0, Path.of(tableLocation)); - assertThat(metadata.getConfiguration().get("delta.columnMapping.maxColumnId")) - .isEqualTo("3"); // 3 comes from a_int + a_row + a_row.x + assertThat(metadata.getConfiguration()).containsEntry("delta.columnMapping.maxColumnId", "3"); // 3 comes from a_int + a_row + a_row.x JsonNode schema = OBJECT_MAPPER.readTree(metadata.getSchemaString()); List fields = ImmutableList.copyOf(schema.get("fields").elements()); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java index 25a7945c9eef..19c219dd9161 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java @@ -4218,8 +4218,8 @@ protected void doCreateTable(SchemaTableName tableName, HiveStorageFormat storag // verify the node version and query ID in table Table table = getMetastoreClient().getTable(tableName.getSchemaName(), tableName.getTableName()).get(); - assertThat(table.getParameters().get(PRESTO_VERSION_NAME)).isEqualTo(TEST_SERVER_VERSION); - assertThat(table.getParameters().get(PRESTO_QUERY_ID_NAME)).isEqualTo(queryId); + assertThat(table.getParameters()).containsEntry(PRESTO_VERSION_NAME, TEST_SERVER_VERSION); + assertThat(table.getParameters()).containsEntry(PRESTO_QUERY_ID_NAME, queryId); // verify basic statistics HiveBasicStatistics statistics = getBasicStatisticsForTable(transaction, tableName); @@ -4282,8 +4282,8 @@ protected void doCreateEmptyTable(SchemaTableName tableName, HiveStorageFormat s assertThat(table.getStorage().getStorageFormat().getInputFormat()).isEqualTo(storageFormat.getInputFormat()); // verify the node version and query ID - assertThat(table.getParameters().get(PRESTO_VERSION_NAME)).isEqualTo(TEST_SERVER_VERSION); - assertThat(table.getParameters().get(PRESTO_QUERY_ID_NAME)).isEqualTo(queryId); + assertThat(table.getParameters()).containsEntry(PRESTO_VERSION_NAME, TEST_SERVER_VERSION); + assertThat(table.getParameters()).containsEntry(PRESTO_QUERY_ID_NAME, queryId); // verify the table is empty List columnHandles = filterNonHiddenColumnHandles(metadata.getColumnHandles(session, tableHandle).values()); @@ -4643,8 +4643,8 @@ private void doInsertIntoNewPartition(HiveStorageFormat storageFormat, SchemaTab assertThat(partitions.size()).isEqualTo(partitionNames.size()); for (String partitionName : partitionNames) { Partition partition = partitions.get(partitionName).get(); - assertThat(partition.getParameters().get(PRESTO_VERSION_NAME)).isEqualTo(TEST_SERVER_VERSION); - assertThat(partition.getParameters().get(PRESTO_QUERY_ID_NAME)).isEqualTo(queryId); + assertThat(partition.getParameters()).containsEntry(PRESTO_VERSION_NAME, TEST_SERVER_VERSION); + assertThat(partition.getParameters()).containsEntry(PRESTO_QUERY_ID_NAME, queryId); } // load the new table diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java index f4b1dcba3919..1fcd34fdc8f8 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java @@ -814,7 +814,7 @@ private void createExternalTableOnNonExistingPath(SchemaTableName tableName, Hiv // verify the metadata ConnectorTableMetadata tableMetadata = metadata.getTableMetadata(session, getTableHandle(metadata, tableName)); assertThat(filterNonHiddenColumnMetadata(tableMetadata.getColumns())).isEqualTo(columns); - assertThat(tableMetadata.getProperties().get("external_location")).isEqualTo(externalLocation); + assertThat(tableMetadata.getProperties()).containsEntry("external_location", externalLocation); // verify the data metadata.beginQuery(session); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java index 90c761d59056..39d4f950067d 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java @@ -1670,7 +1670,7 @@ private void testCreatePartitionedTable(Session session, HiveStorageFormat stora assertUpdate(session, createTable); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, "test_partitioned_table"); - assertThat(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY)).isEqualTo(storageFormat); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(STORAGE_FORMAT_PROPERTY, storageFormat); List partitionedBy = ImmutableList.of( "_partition_string", @@ -1685,7 +1685,7 @@ private void testCreatePartitionedTable(Session session, HiveStorageFormat stora "_partition_decimal_long", "_partition_date", "_partition_timestamp"); - assertThat(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY)).isEqualTo(partitionedBy); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(PARTITIONED_BY_PROPERTY, partitionedBy); for (ColumnMetadata columnMetadata : tableMetadata.getColumns()) { boolean partitionKey = partitionedBy.contains(columnMetadata.getName()); assertThat(columnMetadata.getExtraInfo()).isEqualTo(columnExtraInfo(partitionKey)); @@ -1796,7 +1796,7 @@ private void createTableLike(String likeSuffix, boolean hasPartition) // Verify the partition keys are correctly created List partitionedBy = ImmutableList.of("partition_bigint", "partition_decimal_long"); - assertThat(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY)).isEqualTo(partitionedBy); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(PARTITIONED_BY_PROPERTY, partitionedBy); // Verify the column types assertColumnType(tableMetadata, "string_col", createUnboundedVarcharType()); @@ -1897,7 +1897,7 @@ private void testCreateTableAs(Session session, HiveStorageFormat storageFormat) assertUpdate(session, createTableAs, 1); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, "test_format_table"); - assertThat(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY)).isEqualTo(storageFormat); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(STORAGE_FORMAT_PROPERTY, storageFormat); assertColumnType(tableMetadata, "_varchar", createVarcharType(3)); assertColumnType(tableMetadata, "_char", createCharType(10)); @@ -1933,8 +1933,8 @@ private void testCreatePartitionedTableAs(Session session, HiveStorageFormat sto assertUpdate(session, createTable, "SELECT count(*) FROM orders"); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, "test_create_partitioned_table_as"); - assertThat(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY)).isEqualTo(storageFormat); - assertThat(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY)).isEqualTo(ImmutableList.of("ship_priority", "order_status")); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(STORAGE_FORMAT_PROPERTY, storageFormat); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(PARTITIONED_BY_PROPERTY, ImmutableList.of("ship_priority", "order_status")); List partitions = getPartitions("test_create_partitioned_table_as"); assertThat(partitions.size()).isEqualTo(3); @@ -2171,11 +2171,11 @@ private void testEmptyBucketedTable(Session baseSession, HiveStorageFormat stora assertUpdate(createTable); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, tableName); - assertThat(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY)).isEqualTo(storageFormat); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(STORAGE_FORMAT_PROPERTY, storageFormat); assertThat(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY)).isNull(); - assertThat(tableMetadata.getMetadata().getProperties().get(BUCKETED_BY_PROPERTY)).isEqualTo(ImmutableList.of("bucket_key")); - assertThat(tableMetadata.getMetadata().getProperties().get(BUCKET_COUNT_PROPERTY)).isEqualTo(11); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(BUCKETED_BY_PROPERTY, ImmutableList.of("bucket_key")); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(BUCKET_COUNT_PROPERTY, 11); assertThat(computeActual("SELECT * from " + tableName).getRowCount()).isEqualTo(0); @@ -2233,11 +2233,11 @@ private void testBucketedTable(Session session, HiveStorageFormat storageFormat, assertUpdate(parallelWriter, createTable, 3); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, tableName); - assertThat(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY)).isEqualTo(storageFormat); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(STORAGE_FORMAT_PROPERTY, storageFormat); assertThat(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY)).isNull(); - assertThat(tableMetadata.getMetadata().getProperties().get(BUCKETED_BY_PROPERTY)).isEqualTo(ImmutableList.of("bucket_key")); - assertThat(tableMetadata.getMetadata().getProperties().get(BUCKET_COUNT_PROPERTY)).isEqualTo(11); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(BUCKETED_BY_PROPERTY, ImmutableList.of("bucket_key")); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(BUCKET_COUNT_PROPERTY, 11); assertQuery("SELECT * from " + tableName, "VALUES ('a', 'b', 'c'), ('aa', 'bb', 'cc'), ('aaa', 'bbb', 'ccc')"); @@ -2690,11 +2690,11 @@ private void testCreatePartitionedBucketedTableAsWithUnionAll(HiveStorageFormat private void verifyPartitionedBucketedTable(HiveStorageFormat storageFormat, String tableName) { TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, tableName); - assertThat(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY)).isEqualTo(storageFormat); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(STORAGE_FORMAT_PROPERTY, storageFormat); - assertThat(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY)).isEqualTo(ImmutableList.of("orderstatus")); - assertThat(tableMetadata.getMetadata().getProperties().get(BUCKETED_BY_PROPERTY)).isEqualTo(ImmutableList.of("custkey", "custkey2")); - assertThat(tableMetadata.getMetadata().getProperties().get(BUCKET_COUNT_PROPERTY)).isEqualTo(11); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(PARTITIONED_BY_PROPERTY, ImmutableList.of("orderstatus")); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(BUCKETED_BY_PROPERTY, ImmutableList.of("custkey", "custkey2")); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(BUCKET_COUNT_PROPERTY, 11); List partitions = getPartitions(tableName); assertThat(partitions.size()).isEqualTo(3); @@ -2858,11 +2858,11 @@ private void testInsertPartitionedBucketedTableFewRows(Session session, HiveStor private void verifyPartitionedBucketedTableAsFewRows(HiveStorageFormat storageFormat, String tableName) { TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, tableName); - assertThat(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY)).isEqualTo(storageFormat); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(STORAGE_FORMAT_PROPERTY, storageFormat); - assertThat(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY)).isEqualTo(ImmutableList.of("partition_key")); - assertThat(tableMetadata.getMetadata().getProperties().get(BUCKETED_BY_PROPERTY)).isEqualTo(ImmutableList.of("bucket_key")); - assertThat(tableMetadata.getMetadata().getProperties().get(BUCKET_COUNT_PROPERTY)).isEqualTo(11); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(PARTITIONED_BY_PROPERTY, ImmutableList.of("partition_key")); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(BUCKETED_BY_PROPERTY, ImmutableList.of("bucket_key")); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(BUCKET_COUNT_PROPERTY, 11); List partitions = getPartitions(tableName); assertThat(partitions.size()).isEqualTo(3); @@ -3178,7 +3178,7 @@ private void testInsert(Session session, HiveStorageFormat storageFormat) assertUpdate(session, createTable); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, "test_insert_format_table"); - assertThat(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY)).isEqualTo(storageFormat); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(STORAGE_FORMAT_PROPERTY, storageFormat); assertColumnType(tableMetadata, "_string", createUnboundedVarcharType()); assertColumnType(tableMetadata, "_varchar", createVarcharType(65535)); @@ -3249,8 +3249,8 @@ private void testInsertPartitionedTable(Session session, HiveStorageFormat stora assertUpdate(session, createTable); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, "test_insert_partitioned_table"); - assertThat(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY)).isEqualTo(storageFormat); - assertThat(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY)).isEqualTo(ImmutableList.of("ship_priority", "order_status")); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(STORAGE_FORMAT_PROPERTY, storageFormat); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(PARTITIONED_BY_PROPERTY, ImmutableList.of("ship_priority", "order_status")); String partitionsTable = "\"test_insert_partitioned_table$partitions\""; @@ -3322,8 +3322,8 @@ private void testInsertPartitionedTableExistingPartition(Session session, HiveSt assertUpdate(session, createTable); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, tableName); - assertThat(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY)).isEqualTo(storageFormat); - assertThat(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY)).isEqualTo(ImmutableList.of("order_status")); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(STORAGE_FORMAT_PROPERTY, storageFormat); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(PARTITIONED_BY_PROPERTY, ImmutableList.of("order_status")); for (int i = 0; i < 3; i++) { assertUpdate( @@ -3380,8 +3380,8 @@ private void testInsertPartitionedTableOverwriteExistingPartition(Session sessio assertUpdate(session, createTable); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, tableName); - assertThat(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY)).isEqualTo(storageFormat); - assertThat(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY)).isEqualTo(ImmutableList.of("order_status")); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(STORAGE_FORMAT_PROPERTY, storageFormat); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(PARTITIONED_BY_PROPERTY, ImmutableList.of("order_status")); for (int i = 0; i < 3; i++) { assertUpdate( @@ -3495,7 +3495,7 @@ public void testPartitionPerScanLimit() assertUpdate(createTable); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, tableName); - assertThat(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY)).isEqualTo(ImmutableList.of("part")); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(PARTITIONED_BY_PROPERTY, ImmutableList.of("part")); // insert 1200 partitions for (int i = 0; i < 12; i++) { @@ -3697,7 +3697,7 @@ private void testInsertUnpartitionedTable(Session session, HiveStorageFormat sto assertUpdate(session, createTable); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, tableName); - assertThat(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY)).isEqualTo(storageFormat); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(STORAGE_FORMAT_PROPERTY, storageFormat); for (int i = 0; i < 3; i++) { assertUpdate( @@ -4057,12 +4057,12 @@ public void testBucketedCatalog() String bucketedSchema = bucketedSession.getSchema().get(); TableMetadata ordersTableMetadata = getTableMetadata(bucketedCatalog, bucketedSchema, "orders"); - assertThat(ordersTableMetadata.getMetadata().getProperties().get(BUCKETED_BY_PROPERTY)).isEqualTo(ImmutableList.of("custkey")); - assertThat(ordersTableMetadata.getMetadata().getProperties().get(BUCKET_COUNT_PROPERTY)).isEqualTo(11); + assertThat(ordersTableMetadata.getMetadata().getProperties()).containsEntry(BUCKETED_BY_PROPERTY, ImmutableList.of("custkey")); + assertThat(ordersTableMetadata.getMetadata().getProperties()).containsEntry(BUCKET_COUNT_PROPERTY, 11); TableMetadata customerTableMetadata = getTableMetadata(bucketedCatalog, bucketedSchema, "customer"); - assertThat(customerTableMetadata.getMetadata().getProperties().get(BUCKETED_BY_PROPERTY)).isEqualTo(ImmutableList.of("custkey")); - assertThat(customerTableMetadata.getMetadata().getProperties().get(BUCKET_COUNT_PROPERTY)).isEqualTo(11); + assertThat(customerTableMetadata.getMetadata().getProperties()).containsEntry(BUCKETED_BY_PROPERTY, ImmutableList.of("custkey")); + assertThat(customerTableMetadata.getMetadata().getProperties()).containsEntry(BUCKET_COUNT_PROPERTY, 11); } @Test @@ -4727,7 +4727,7 @@ private void testPathHiddenColumn(Session session, HiveStorageFormat storageForm assertThat(getQueryRunner().tableExists(getSession(), "test_path")).isTrue(); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, "test_path"); - assertThat(tableMetadata.getMetadata().getProperties().get(STORAGE_FORMAT_PROPERTY)).isEqualTo(storageFormat); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(STORAGE_FORMAT_PROPERTY, storageFormat); List columnNames = ImmutableList.of("col0", "col1", PATH_COLUMN_NAME, FILE_SIZE_COLUMN_NAME, FILE_MODIFIED_TIME_COLUMN_NAME, PARTITION_COLUMN_NAME); List columnMetadatas = tableMetadata.getColumns(); @@ -4755,7 +4755,7 @@ private void testPathHiddenColumn(Session session, HiveStorageFormat storageForm assertThat(col0 % 3).isEqualTo(col1); if (partitionPathMap.containsKey(col1)) { // the rows in the same partition should be in the same partition directory - assertThat(partitionPathMap.get(col1)).isEqualTo(parentDirectory); + assertThat(partitionPathMap).containsEntry(col1, parentDirectory); } else { partitionPathMap.put(col1, parentDirectory); @@ -4784,8 +4784,8 @@ public void testBucketHiddenColumn() assertThat(getQueryRunner().tableExists(getSession(), "test_bucket_hidden_column")).isTrue(); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, "test_bucket_hidden_column"); - assertThat(tableMetadata.getMetadata().getProperties().get(BUCKETED_BY_PROPERTY)).isEqualTo(ImmutableList.of("col0")); - assertThat(tableMetadata.getMetadata().getProperties().get(BUCKET_COUNT_PROPERTY)).isEqualTo(2); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(BUCKETED_BY_PROPERTY, ImmutableList.of("col0")); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(BUCKET_COUNT_PROPERTY, 2); List columnNames = ImmutableList.of("col0", "col1", PATH_COLUMN_NAME, BUCKET_COLUMN_NAME, FILE_SIZE_COLUMN_NAME, FILE_MODIFIED_TIME_COLUMN_NAME); List columnMetadatas = tableMetadata.getColumns(); @@ -4923,7 +4923,7 @@ private void testFileModifiedTimeHiddenColumn(HiveTimestampPrecision precision) assertThat(fileModifiedTime.toEpochMilli()).isCloseTo(testStartTime, offset(2000L)); assertThat(col0 % 3).isEqualTo(col1); if (fileModifiedTimeMap.containsKey(col1)) { - assertThat(fileModifiedTimeMap.get(col1)).isEqualTo(fileModifiedTime); + assertThat(fileModifiedTimeMap).containsEntry(col1, fileModifiedTime); } else { fileModifiedTimeMap.put(col1, fileModifiedTime); @@ -4950,7 +4950,7 @@ public void testPartitionHiddenColumn() assertThat(getQueryRunner().tableExists(getSession(), "test_partition_hidden_column")).isTrue(); TableMetadata tableMetadata = getTableMetadata(catalog, TPCH_SCHEMA, "test_partition_hidden_column"); - assertThat(tableMetadata.getMetadata().getProperties().get(PARTITIONED_BY_PROPERTY)).isEqualTo(ImmutableList.of("col1", "col2")); + assertThat(tableMetadata.getMetadata().getProperties()).containsEntry(PARTITIONED_BY_PROPERTY, ImmutableList.of("col1", "col2")); List columnNames = ImmutableList.of("col0", "col1", "col2", PATH_COLUMN_NAME, FILE_SIZE_COLUMN_NAME, FILE_MODIFIED_TIME_COLUMN_NAME, PARTITION_COLUMN_NAME); List columnMetadatas = tableMetadata.getColumns(); @@ -8797,7 +8797,7 @@ public void testAutoPurgeProperty() assertUpdate(createTableSql, 1500L); TableMetadata tableMetadataDefaults = getTableMetadata(catalog, TPCH_SCHEMA, tableName); - assertThat(tableMetadataDefaults.getMetadata().getProperties().get(AUTO_PURGE)).isEqualTo(null); + assertThat(tableMetadataDefaults.getMetadata().getProperties()).doesNotContainKey(AUTO_PURGE); assertUpdate("DROP TABLE " + tableName); @@ -8811,7 +8811,7 @@ public void testAutoPurgeProperty() assertUpdate(createTableSqlWithAutoPurge, 1500L); TableMetadata tableMetadataWithPurge = getTableMetadata(catalog, TPCH_SCHEMA, tableName); - assertThat(tableMetadataWithPurge.getMetadata().getProperties().get(AUTO_PURGE)).isEqualTo(true); + assertThat(tableMetadataWithPurge.getMetadata().getProperties()).containsEntry(AUTO_PURGE, true); assertUpdate("DROP TABLE " + tableName); } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveSplitSource.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveSplitSource.java index 553a3d20f71f..159b97792016 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveSplitSource.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveSplitSource.java @@ -230,7 +230,7 @@ public void testReaderWaitsForSplits() // wait for thread to get the split ConnectorSplit split = splits.get(800, TimeUnit.MILLISECONDS); - assertThat(((HiveSplit) split).getSchema().get("id")).isEqualTo("33"); + assertThat(((HiveSplit) split).getSchema()).containsEntry("id", "33"); } finally { // make sure the thread exits diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestHiveGlueMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestHiveGlueMetastore.java index c8c9ea9d87fc..5ef50056585c 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestHiveGlueMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestHiveGlueMetastore.java @@ -1428,7 +1428,7 @@ public void testAlterTableComment() assertThat(metastore.getTable(tableName.getSchemaName(), tableName.getTableName()).orElseThrow().getParameters()).doesNotContainKey(TABLE_COMMENT); metastore.commentTable(tableName.getSchemaName(), tableName.getTableName(), Optional.of("a table comment")); Map tableParameters = metastore.getTable(tableName.getSchemaName(), tableName.getTableName()).orElseThrow().getParameters(); - assertThat(tableParameters.get(TABLE_COMMENT)).isEqualTo("a table comment"); + assertThat(tableParameters).containsEntry(TABLE_COMMENT, "a table comment"); metastore.commentTable(tableName.getSchemaName(), tableName.getTableName(), Optional.empty()); tableParameters = metastore.getTable(tableName.getSchemaName(), tableName.getTableName()).orElseThrow().getParameters(); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/predicate/TestParquetPredicateUtils.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/predicate/TestParquetPredicateUtils.java index 3809418191db..9aec2afa6bc6 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/predicate/TestParquetPredicateUtils.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/parquet/predicate/TestParquetPredicateUtils.java @@ -164,7 +164,7 @@ public void testParquetTupleDomainStructWithPrimitiveColumnPredicate(boolean use TupleDomain calculatedTupleDomain = getParquetTupleDomain(descriptorsByPath, tupleDomain, fileSchema, useColumNames); assertThat(calculatedTupleDomain.getDomains().get().size()).isEqualTo(1); ColumnDescriptor selectedColumnDescriptor = descriptorsByPath.get(ImmutableList.of("row_field", "b")); - assertThat(calculatedTupleDomain.getDomains().get().get(selectedColumnDescriptor)).isEqualTo(predicateDomain); + assertThat(calculatedTupleDomain.getDomains().get()).containsEntry(selectedColumnDescriptor, predicateDomain); } @Test(dataProvider = "useColumnNames") diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestStatistics.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestStatistics.java index d47f123f9e1d..b40e7b05cf24 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestStatistics.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/util/TestStatistics.java @@ -332,14 +332,12 @@ public void testFromComputedStatistics() assertThat(columnStatistics).hasSize(2); assertThat(columnStatistics.keySet()).contains("a_column", "b_column"); - assertThat(columnStatistics.get("a_column")).isEqualTo( - HiveColumnStatistics.builder() + assertThat(columnStatistics).containsEntry("a_column", HiveColumnStatistics.builder() .setIntegerStatistics(new IntegerStatistics(OptionalLong.of(1), OptionalLong.of(5))) .setNullsCount(0) .setDistinctValuesCount(5) .build()); - assertThat(columnStatistics.get("b_column")).isEqualTo( - HiveColumnStatistics.builder() + assertThat(columnStatistics).containsEntry("b_column", HiveColumnStatistics.builder() .setNullsCount(1) .build()); } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergOrcMetricsCollection.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergOrcMetricsCollection.java index dec3bd643dbd..0371c613249a 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergOrcMetricsCollection.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergOrcMetricsCollection.java @@ -246,27 +246,27 @@ public void testBasic() // Check per-column lower bound Map lowerBounds = datafile.getLowerBounds(); - assertThat(lowerBounds.get(1)).isEqualTo("1"); - assertThat(lowerBounds.get(2)).isEqualTo("1"); - assertThat(lowerBounds.get(3)).isEqualTo("F"); - assertThat(lowerBounds.get(4)).isEqualTo("874.89"); - assertThat(lowerBounds.get(5)).isEqualTo("1992-01-01"); - assertThat(lowerBounds.get(6)).isEqualTo("1-URGENT"); - assertThat(lowerBounds.get(7)).isEqualTo("Clerk#000000001"); - assertThat(lowerBounds.get(8)).isEqualTo("0"); - assertThat(lowerBounds.get(9)).isEqualTo(" about the accou"); + assertThat(lowerBounds).containsEntry(1, "1"); + assertThat(lowerBounds).containsEntry(2, "1"); + assertThat(lowerBounds).containsEntry(3, "F"); + assertThat(lowerBounds).containsEntry(4, "874.89"); + assertThat(lowerBounds).containsEntry(5, "1992-01-01"); + assertThat(lowerBounds).containsEntry(6, "1-URGENT"); + assertThat(lowerBounds).containsEntry(7, "Clerk#000000001"); + assertThat(lowerBounds).containsEntry(8, "0"); + assertThat(lowerBounds).containsEntry(9, " about the accou"); // Check per-column upper bound Map upperBounds = datafile.getUpperBounds(); - assertThat(upperBounds.get(1)).isEqualTo("60000"); - assertThat(upperBounds.get(2)).isEqualTo("1499"); - assertThat(upperBounds.get(3)).isEqualTo("P"); - assertThat(upperBounds.get(4)).isEqualTo("466001.28"); - assertThat(upperBounds.get(5)).isEqualTo("1998-08-02"); - assertThat(upperBounds.get(6)).isEqualTo("5-LOW"); - assertThat(upperBounds.get(7)).isEqualTo("Clerk#000001000"); - assertThat(upperBounds.get(8)).isEqualTo("0"); - assertThat(upperBounds.get(9)).isEqualTo("zzle. carefully!"); + assertThat(upperBounds).containsEntry(1, "60000"); + assertThat(upperBounds).containsEntry(2, "1499"); + assertThat(upperBounds).containsEntry(3, "P"); + assertThat(upperBounds).containsEntry(4, "466001.28"); + assertThat(upperBounds).containsEntry(5, "1998-08-02"); + assertThat(upperBounds).containsEntry(6, "5-LOW"); + assertThat(upperBounds).containsEntry(7, "Clerk#000001000"); + assertThat(upperBounds).containsEntry(8, "0"); + assertThat(upperBounds).containsEntry(9, "zzle. carefully!"); assertUpdate("DROP TABLE orders"); } @@ -288,16 +288,16 @@ public void testWithNulls() datafile.getValueCounts().values().forEach(valueCount -> assertThat(valueCount).isEqualTo((Long) 4L)); // Check per-column null value count - assertThat(datafile.getNullValueCounts().get(1)).isEqualTo((Long) 1L); - assertThat(datafile.getNullValueCounts().get(2)).isEqualTo((Long) 2L); - assertThat(datafile.getNullValueCounts().get(3)).isEqualTo((Long) 0L); - assertThat(datafile.getNullValueCounts().get(4)).isEqualTo((Long) 2L); + assertThat(datafile.getNullValueCounts()).containsEntry(1, (Long) 1L); + assertThat(datafile.getNullValueCounts()).containsEntry(2, (Long) 2L); + assertThat(datafile.getNullValueCounts()).containsEntry(3, (Long) 0L); + assertThat(datafile.getNullValueCounts()).containsEntry(4, (Long) 2L); // Check per-column lower bound - assertThat(datafile.getLowerBounds().get(1)).isEqualTo("3"); - assertThat(datafile.getLowerBounds().get(2)).isEqualTo("3.4"); - assertThat(datafile.getLowerBounds().get(3)).isEqualTo("aaa"); - assertThat(datafile.getLowerBounds().get(4)).isEqualTo("2020-01-01T00:00:00.123"); + assertThat(datafile.getLowerBounds()).containsEntry(1, "3"); + assertThat(datafile.getLowerBounds()).containsEntry(2, "3.4"); + assertThat(datafile.getLowerBounds()).containsEntry(3, "aaa"); + assertThat(datafile.getLowerBounds()).containsEntry(4, "2020-01-01T00:00:00.123"); assertUpdate("DROP TABLE test_with_nulls"); @@ -308,10 +308,10 @@ public void testWithNulls() datafile = toDataFileRecord(materializedResult.getMaterializedRows().get(0)); // Check per-column value count - assertThat(datafile.getValueCounts().get(1)).isEqualTo((Long) 3L); + assertThat(datafile.getValueCounts()).containsEntry(1, (Long) 3L); // Check per-column null value count - assertThat(datafile.getNullValueCounts().get(1)).isEqualTo((Long) 3L); + assertThat(datafile.getNullValueCounts()).containsEntry(1, (Long) 3L); // Check that lower bounds and upper bounds are nulls. (There's no non-null record) assertThat(datafile.getLowerBounds()).isNull(); @@ -334,8 +334,8 @@ public void testWithNaNs() // Check per-column nan value count assertThat(datafile.getNanValueCounts().size()).isEqualTo(2); - assertThat(datafile.getNanValueCounts().get(2)).isEqualTo((Long) 1L); - assertThat(datafile.getNanValueCounts().get(3)).isEqualTo((Long) 1L); + assertThat(datafile.getNanValueCounts()).containsEntry(2, (Long) 1L); + assertThat(datafile.getNanValueCounts()).containsEntry(3, (Long) 1L); assertThat(datafile.getLowerBounds().get(2)).isNull(); assertThat(datafile.getLowerBounds().get(3)).isNull(); @@ -369,16 +369,16 @@ public void testNestedTypes() assertThat(upperBounds.size()).isEqualTo(3); // col1 - assertThat(lowerBounds.get(1)).isEqualTo("-9"); - assertThat(upperBounds.get(1)).isEqualTo("8"); + assertThat(lowerBounds).containsEntry(1, "-9"); + assertThat(upperBounds).containsEntry(1, "8"); // col2.f1 (key in lowerBounds/upperBounds is Iceberg ID) - assertThat(lowerBounds.get(3)).isEqualTo("0"); - assertThat(upperBounds.get(3)).isEqualTo("10"); + assertThat(lowerBounds).containsEntry(3, "0"); + assertThat(upperBounds).containsEntry(3, "10"); // col2.f3 (key in lowerBounds/upperBounds is Iceberg ID) - assertThat(lowerBounds.get(5)).isEqualTo("-2.9"); - assertThat(upperBounds.get(5)).isEqualTo("4.9"); + assertThat(lowerBounds).containsEntry(5, "-2.9"); + assertThat(upperBounds).containsEntry(5, "4.9"); assertUpdate("DROP TABLE test_nested_types"); } @@ -408,11 +408,11 @@ public void testWithTimestamps() datafile.getNullValueCounts().values().forEach(nullValueCount -> assertThat(nullValueCount).isEqualTo((Long) 0L)); // Check column lower bound. Min timestamp doesn't rely on file-level statistics and will not be truncated to milliseconds. - assertThat(datafile.getLowerBounds().get(1)).isEqualTo("2021-01-01T00:00:00.111"); + assertThat(datafile.getLowerBounds()).containsEntry(1, "2021-01-01T00:00:00.111"); assertQuery("SELECT min(_timestamp) FROM test_timestamp", "VALUES '2021-01-01 00:00:00.111111'"); // Check column upper bound. Max timestamp doesn't rely on file-level statistics and will not be truncated to milliseconds. - assertThat(datafile.getUpperBounds().get(1)).isEqualTo("2021-01-31T00:00:00.333999"); + assertThat(datafile.getUpperBounds()).containsEntry(1, "2021-01-31T00:00:00.333999"); assertQuery("SELECT max(_timestamp) FROM test_timestamp", "VALUES '2021-01-31 00:00:00.333333'"); assertUpdate("DROP TABLE test_timestamp"); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergV2.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergV2.java index a3821447ad03..89afa7897ee2 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergV2.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergV2.java @@ -265,14 +265,14 @@ public void testOptimizingV2TableRemovesEqualityDeletesWhenWholeTableIsScanned() String tableName = "test_optimize_table_cleans_equality_delete_file_when_whole_table_is_scanned" + randomNameSuffix(); assertUpdate("CREATE TABLE " + tableName + " WITH (partitioning = ARRAY['regionkey']) AS SELECT * FROM tpch.tiny.nation", 25); Table icebergTable = loadTable(tableName); - assertThat(icebergTable.currentSnapshot().summary().get("total-equality-deletes")).isEqualTo("0"); + assertThat(icebergTable.currentSnapshot().summary()).containsEntry("total-equality-deletes", "0"); writeEqualityDeleteToNationTable(icebergTable, Optional.of(icebergTable.spec()), Optional.of(new PartitionData(new Long[]{1L}))); List initialActiveFiles = getActiveFiles(tableName); query("ALTER TABLE " + tableName + " EXECUTE OPTIMIZE"); assertQuery("SELECT * FROM " + tableName, "SELECT * FROM nation WHERE regionkey != 1"); // nationkey is before the equality delete column in the table schema, comment is after assertQuery("SELECT nationkey, comment FROM " + tableName, "SELECT nationkey, comment FROM nation WHERE regionkey != 1"); - assertThat(loadTable(tableName).currentSnapshot().summary().get("total-equality-deletes")).isEqualTo("0"); + assertThat(loadTable(tableName).currentSnapshot().summary()).containsEntry("total-equality-deletes", "0"); List updatedFiles = getActiveFiles(tableName); assertThat(updatedFiles).doesNotContain(initialActiveFiles.toArray(new String[0])); } @@ -284,14 +284,14 @@ public void testOptimizingV2TableDoesntRemoveEqualityDeletesWhenOnlyPartOfTheTab String tableName = "test_optimize_table_with_equality_delete_file_for_different_partition_" + randomNameSuffix(); assertUpdate("CREATE TABLE " + tableName + " WITH (partitioning = ARRAY['regionkey']) AS SELECT * FROM tpch.tiny.nation", 25); Table icebergTable = loadTable(tableName); - assertThat(icebergTable.currentSnapshot().summary().get("total-equality-deletes")).isEqualTo("0"); + assertThat(icebergTable.currentSnapshot().summary()).containsEntry("total-equality-deletes", "0"); List initialActiveFiles = getActiveFiles(tableName); writeEqualityDeleteToNationTable(icebergTable, Optional.of(icebergTable.spec()), Optional.of(new PartitionData(new Long[]{1L}))); query("ALTER TABLE " + tableName + " EXECUTE OPTIMIZE WHERE regionkey != 1"); assertQuery("SELECT * FROM " + tableName, "SELECT * FROM nation WHERE regionkey != 1"); // nationkey is before the equality delete column in the table schema, comment is after assertQuery("SELECT nationkey, comment FROM " + tableName, "SELECT nationkey, comment FROM nation WHERE regionkey != 1"); - assertThat(loadTable(tableName).currentSnapshot().summary().get("total-equality-deletes")).isEqualTo("1"); + assertThat(loadTable(tableName).currentSnapshot().summary()).containsEntry("total-equality-deletes", "1"); List updatedFiles = getActiveFiles(tableName); assertThat(updatedFiles).doesNotContain(initialActiveFiles.stream().filter(path -> !path.contains("regionkey=1")).toArray(String[]::new)); } @@ -306,7 +306,7 @@ public void testSelectivelyOptimizingLeavesEqualityDeletes() writeEqualityDeleteToNationTable(icebergTable, Optional.of(icebergTable.spec()), Optional.of(new PartitionData(new Long[]{1L}))); query("ALTER TABLE " + tableName + " EXECUTE OPTIMIZE WHERE nationkey < 5"); assertQuery("SELECT * FROM " + tableName, "SELECT * FROM nation WHERE regionkey != 1 OR nationkey != 1"); - assertThat(loadTable(tableName).currentSnapshot().summary().get("total-equality-deletes")).isEqualTo("1"); + assertThat(loadTable(tableName).currentSnapshot().summary()).containsEntry("total-equality-deletes", "1"); } @Test @@ -316,7 +316,7 @@ public void testMultipleEqualityDeletes() String tableName = "test_multiple_equality_deletes_" + randomNameSuffix(); assertUpdate("CREATE TABLE " + tableName + " AS SELECT * FROM tpch.tiny.nation", 25); Table icebergTable = loadTable(tableName); - assertThat(icebergTable.currentSnapshot().summary().get("total-equality-deletes")).isEqualTo("0"); + assertThat(icebergTable.currentSnapshot().summary()).containsEntry("total-equality-deletes", "0"); for (int i = 1; i < 3; i++) { writeEqualityDeleteToNationTable( @@ -337,7 +337,7 @@ public void testMultipleEqualityDeletesWithEquivalentSchemas() String tableName = "test_multiple_equality_deletes_equivalent_schemas_" + randomNameSuffix(); assertUpdate("CREATE TABLE " + tableName + " AS SELECT * FROM tpch.tiny.nation", 25); Table icebergTable = loadTable(tableName); - assertThat(icebergTable.currentSnapshot().summary().get("total-equality-deletes")).isEqualTo("0"); + assertThat(icebergTable.currentSnapshot().summary()).containsEntry("total-equality-deletes", "0"); Schema deleteRowSchema = new Schema(ImmutableList.of("regionkey", "name").stream() .map(name -> icebergTable.schema().findField(name)) .collect(toImmutableList())); @@ -373,7 +373,7 @@ public void testMultipleEqualityDeletesWithDifferentSchemas() String tableName = "test_multiple_equality_deletes_different_schemas_" + randomNameSuffix(); assertUpdate("CREATE TABLE " + tableName + " AS SELECT * FROM tpch.tiny.nation", 25); Table icebergTable = loadTable(tableName); - assertThat(icebergTable.currentSnapshot().summary().get("total-equality-deletes")).isEqualTo("0"); + assertThat(icebergTable.currentSnapshot().summary()).containsEntry("total-equality-deletes", "0"); writeEqualityDeleteToNationTableWithDeleteColumns( icebergTable, Optional.empty(), @@ -407,7 +407,7 @@ public void testMultipleEqualityDeletesWithNestedFields() assertUpdate("INSERT INTO " + tableName + " VALUES (2, row(20, 200))", 1); assertUpdate("INSERT INTO " + tableName + " VALUES (2, row(20, 200))", 1); Table icebergTable = loadTable(tableName); - assertThat(icebergTable.currentSnapshot().summary().get("total-equality-deletes")).isEqualTo("0"); + assertThat(icebergTable.currentSnapshot().summary()).containsEntry("total-equality-deletes", "0"); List deleteFileColumns = ImmutableList.of("root.nested"); Schema deleteRowSchema = icebergTable.schema().select(deleteFileColumns); @@ -442,7 +442,7 @@ public void testOptimizingWholeTableRemovesEqualityDeletes() writeEqualityDeleteToNationTable(icebergTable, Optional.of(icebergTable.spec()), Optional.of(new PartitionData(new Long[]{1L}))); query("ALTER TABLE " + tableName + " EXECUTE OPTIMIZE"); assertQuery("SELECT * FROM " + tableName, "SELECT * FROM nation WHERE regionkey != 1 OR nationkey != 1"); - assertThat(loadTable(tableName).currentSnapshot().summary().get("total-equality-deletes")).isEqualTo("0"); + assertThat(loadTable(tableName).currentSnapshot().summary()).containsEntry("total-equality-deletes", "0"); } @Test @@ -452,14 +452,14 @@ public void testOptimizingV2TableWithEmptyPartitionSpec() String tableName = "test_optimize_table_with_global_equality_delete_file_" + randomNameSuffix(); assertUpdate("CREATE TABLE " + tableName + " AS SELECT * FROM tpch.tiny.nation", 25); Table icebergTable = loadTable(tableName); - assertThat(icebergTable.currentSnapshot().summary().get("total-equality-deletes")).isEqualTo("0"); + assertThat(icebergTable.currentSnapshot().summary()).containsEntry("total-equality-deletes", "0"); writeEqualityDeleteToNationTable(icebergTable); List initialActiveFiles = getActiveFiles(tableName); query("ALTER TABLE " + tableName + " EXECUTE OPTIMIZE"); assertQuery("SELECT * FROM " + tableName, "SELECT * FROM nation WHERE regionkey != 1"); // nationkey is before the equality delete column in the table schema, comment is after assertQuery("SELECT nationkey, comment FROM " + tableName, "SELECT nationkey, comment FROM nation WHERE regionkey != 1"); - assertThat(loadTable(tableName).currentSnapshot().summary().get("total-equality-deletes")).isEqualTo("0"); + assertThat(loadTable(tableName).currentSnapshot().summary()).containsEntry("total-equality-deletes", "0"); List updatedFiles = getActiveFiles(tableName); assertThat(updatedFiles).doesNotContain(initialActiveFiles.toArray(new String[0])); } @@ -471,7 +471,7 @@ public void testOptimizingPartitionsOfV2TableWithGlobalEqualityDeleteFile() String tableName = "test_optimize_partitioned_table_with_global_equality_delete_file_" + randomNameSuffix(); assertUpdate("CREATE TABLE " + tableName + " WITH (partitioning = ARRAY['regionkey']) AS SELECT * FROM tpch.tiny.nation", 25); Table icebergTable = loadTable(tableName); - assertThat(icebergTable.currentSnapshot().summary().get("total-equality-deletes")).isEqualTo("0"); + assertThat(icebergTable.currentSnapshot().summary()).containsEntry("total-equality-deletes", "0"); writeEqualityDeleteToNationTable(icebergTable, Optional.of(icebergTable.spec()), Optional.of(new PartitionData(new Long[]{1L}))); List initialActiveFiles = getActiveFiles(tableName); assertQuery("SELECT * FROM " + tableName, "SELECT * FROM nation WHERE regionkey != 1"); @@ -479,7 +479,7 @@ public void testOptimizingPartitionsOfV2TableWithGlobalEqualityDeleteFile() assertQuery("SELECT * FROM " + tableName, "SELECT * FROM nation WHERE regionkey != 1"); // nationkey is before the equality delete column in the table schema, comment is after assertQuery("SELECT nationkey, comment FROM " + tableName, "SELECT nationkey, comment FROM nation WHERE regionkey != 1"); - assertThat(loadTable(tableName).currentSnapshot().summary().get("total-equality-deletes")).isEqualTo("1"); + assertThat(loadTable(tableName).currentSnapshot().summary()).containsEntry("total-equality-deletes", "1"); List updatedFiles = getActiveFiles(tableName); assertThat(updatedFiles) .doesNotContain(initialActiveFiles.stream() diff --git a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaSslConfig.java b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaSslConfig.java index 8cb0ac969f99..ad801a437f05 100644 --- a/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaSslConfig.java +++ b/plugin/trino-kafka/src/test/java/io/trino/plugin/kafka/TestKafkaSslConfig.java @@ -134,7 +134,7 @@ public void testDisabledEndpointIdentificationAlgorithm() } Map securityProperties = config.getKafkaClientProperties(); assertThat(securityProperties).containsKey(SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG); - assertThat(securityProperties.get(SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG)).isEqualTo(""); + assertThat(securityProperties).containsEntry(SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG, ""); } @Test diff --git a/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryConnectorTest.java b/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryConnectorTest.java index f90fc9ab825c..ed70900f8e81 100644 --- a/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryConnectorTest.java +++ b/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryConnectorTest.java @@ -139,8 +139,8 @@ public void testSelect() public void testCustomMetricsScanFilter() { Metrics metrics = collectCustomMetrics("SELECT partkey FROM part WHERE partkey % 1000 > 0"); - assertThat(metrics.getMetrics().get("rows")).isEqualTo(new LongCount(PART_COUNT)); - assertThat(metrics.getMetrics().get("started")).isEqualTo(metrics.getMetrics().get("finished")); + assertThat(metrics.getMetrics()).containsEntry("rows", new LongCount(PART_COUNT)); + assertThat(metrics.getMetrics()).containsEntry("started", metrics.getMetrics().get("finished")); assertThat(((Count) metrics.getMetrics().get("finished")).getTotal()).isGreaterThan(0); } @@ -149,8 +149,8 @@ public void testCustomMetricsScanFilter() public void testCustomMetricsScanOnly() { Metrics metrics = collectCustomMetrics("SELECT partkey FROM part"); - assertThat(metrics.getMetrics().get("rows")).isEqualTo(new LongCount(PART_COUNT)); - assertThat(metrics.getMetrics().get("started")).isEqualTo(metrics.getMetrics().get("finished")); + assertThat(metrics.getMetrics()).containsEntry("rows", new LongCount(PART_COUNT)); + assertThat(metrics.getMetrics()).containsEntry("started", metrics.getMetrics().get("finished")); assertThat(((Count) metrics.getMetrics().get("finished")).getTotal()).isGreaterThan(0); } diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryMatrixResponseParse.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryMatrixResponseParse.java index 10acfe9f7e32..d32d48e2f96d 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryMatrixResponseParse.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryMatrixResponseParse.java @@ -51,7 +51,7 @@ public void verifyMetricPropertiesResponse() throws IOException { List results = new PrometheusQueryResponseParse(promMatrixResponse).getResults(); - assertThat(results.get(0).getMetricHeader().get("__name__")).isEqualTo("up"); + assertThat(results.get(0).getMetricHeader()).containsEntry("__name__", "up"); } @Test diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryScalarResponseParse.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryScalarResponseParse.java index 45703c7da9ba..1f0a41e296d8 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryScalarResponseParse.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryScalarResponseParse.java @@ -41,7 +41,7 @@ public void verifyMetricPropertiesResponse() { try (InputStream promVectorResponse = openStream()) { List results = new PrometheusQueryResponseParse(promVectorResponse).getResults(); - assertThat(results.get(0).getMetricHeader().get("__name__")).isEqualTo("scalar"); + assertThat(results.get(0).getMetricHeader()).containsEntry("__name__", "scalar"); } } diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryVectorResponseParse.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryVectorResponseParse.java index 58169b0fe91e..6589d5a560d4 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryVectorResponseParse.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusQueryVectorResponseParse.java @@ -41,7 +41,7 @@ public void verifyMetricPropertiesResponse() { try (InputStream promVectorResponse = openStream()) { List results = new PrometheusQueryResponseParse(promVectorResponse).getResults(); - assertThat(results.get(0).getMetricHeader().get("__name__")).isEqualTo("up"); + assertThat(results.get(0).getMetricHeader()).containsEntry("__name__", "up"); } } diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusSplit.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusSplit.java index ff591793543d..dc156c3fe056 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusSplit.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusSplit.java @@ -207,8 +207,8 @@ public void testQueryDividedIntoSplitsShouldHaveCorrectSpacingBetweenTimes() Map paramsMap1 = parse(URI.create(split1.getUri()), StandardCharsets.UTF_8).stream().collect(Collectors.toMap(NameValuePair::getName, NameValuePair::getValue)); PrometheusSplit split2 = (PrometheusSplit) splits.getNextBatch(1).getNow(null).getSplits().get(0); Map paramsMap2 = parse(URI.create(split2.getUri()), StandardCharsets.UTF_8).stream().collect(Collectors.toMap(NameValuePair::getName, NameValuePair::getValue)); - assertThat(paramsMap1.get("query")).isEqualTo("up[1d]"); - assertThat(paramsMap2.get("query")).isEqualTo("up[1d]"); + assertThat(paramsMap1).containsEntry("query", "up[1d]"); + assertThat(paramsMap2).containsEntry("query", "up[1d]"); long diff = Double.valueOf(paramsMap2.get("time")).longValue() - Double.valueOf(paramsMap1.get("time")).longValue(); assertEquals(config.getQueryChunkSizeDuration().getValue(TimeUnit.SECONDS), diff, 0.0001); } diff --git a/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/db/TestDbSessionPropertyManager.java b/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/db/TestDbSessionPropertyManager.java index e681d77fe328..47be530e294e 100644 --- a/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/db/TestDbSessionPropertyManager.java +++ b/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/db/TestDbSessionPropertyManager.java @@ -193,8 +193,8 @@ public void testReloads() // Failed reloading, use cached configurations assertThat(failuresAfter - failuresBefore).isEqualTo(1); Map sessionProperties1 = manager.getSystemSessionProperties(context1); - assertThat(sessionProperties1.get("prop_1")).isEqualTo("val_1"); - assertThat(sessionProperties1.get("prop_3")).isEqualTo(null); + assertThat(sessionProperties1).containsEntry("prop_1", "val_1"); + assertThat(sessionProperties1).doesNotContainKey("prop_3"); } /** @@ -219,9 +219,9 @@ public void testOrderingOfSpecs() SessionConfigurationContext context = new SessionConfigurationContext("foo", Optional.of("bar"), ImmutableSet.of(), Optional.empty(), TEST_RG); Map sessionProperties = manager.getSystemSessionProperties(context); - assertThat(sessionProperties.get("prop_1")).isEqualTo("val_1_3"); - assertThat(sessionProperties.get("prop_2")).isEqualTo("val_2_2"); - assertThat(sessionProperties.get("prop_3")).isEqualTo("val_3_1"); + assertThat(sessionProperties).containsEntry("prop_1", "val_1_3"); + assertThat(sessionProperties).containsEntry("prop_2", "val_2_2"); + assertThat(sessionProperties).containsEntry("prop_3", "val_3_1"); assertThat(sessionProperties.size()).isEqualTo(3); } From b4d15171ce720ed759a1b72e69373788aeced8ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Kokosi=C5=84ski?= Date: Tue, 21 Nov 2023 16:37:20 +0100 Subject: [PATCH 373/587] Simplify MetadataManager::getMaterializedView Mixing handling of absence of object and catalog security in one 'if' block was misleading. These two things are separate and unrelated so the best if they handled in separately. --- .../main/java/io/trino/metadata/MetadataManager.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java b/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java index 9eb72beb91a5..80ca50b6a120 100644 --- a/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java +++ b/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java @@ -1605,11 +1605,13 @@ public boolean isMaterializedView(Session session, QualifiedObjectName viewName) public Optional getMaterializedView(Session session, QualifiedObjectName viewName) { Optional connectorView = getMaterializedViewInternal(session, viewName); - if (connectorView.isEmpty() || isCatalogManagedSecurity(session, viewName.getCatalogName())) { - return connectorView.map(view -> { - String runAsUser = view.getOwner().orElseThrow(() -> new TrinoException(INVALID_VIEW, "Owner not set for a run-as invoker view: " + viewName)); - return createMaterializedViewDefinition(view, Identity.ofUser(runAsUser)); - }); + if (connectorView.isEmpty()) { + return Optional.empty(); + } + + if (isCatalogManagedSecurity(session, viewName.getCatalogName())) { + String runAsUser = connectorView.get().getOwner().orElseThrow(() -> new TrinoException(INVALID_VIEW, "Owner not set for a run-as invoker view: " + viewName)); + return Optional.of(createMaterializedViewDefinition(connectorView.get(), Identity.ofUser(runAsUser))); } Identity runAsIdentity = systemSecurityMetadata.getViewRunAsIdentity(session, viewName.asCatalogSchemaTableName()) From 0fa001b58cb99e67c08d89775ba94f250476a68d Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Tue, 21 Nov 2023 16:33:44 +0100 Subject: [PATCH 374/587] Downgrade Oracle JDBC driver to LTS 19.21.0.0 19.x series have long-term support and bugfixes which isn't the case for the 21.x being "innovative release" --- client/trino-jdbc/pom.xml | 3 +-- plugin/trino-oracle/pom.xml | 7 ++----- plugin/trino-resource-group-managers/pom.xml | 3 +-- pom.xml | 9 ++++++++- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/client/trino-jdbc/pom.xml b/client/trino-jdbc/pom.xml index 1fb6ab0bc8c5..1cfe51f6f30d 100644 --- a/client/trino-jdbc/pom.xml +++ b/client/trino-jdbc/pom.xml @@ -97,8 +97,7 @@ com.oracle.database.jdbc - ojdbc11 - ${dep.oracle.version} + ojdbc10 test diff --git a/plugin/trino-oracle/pom.xml b/plugin/trino-oracle/pom.xml index b985032e558e..c9950bfc9545 100644 --- a/plugin/trino-oracle/pom.xml +++ b/plugin/trino-oracle/pom.xml @@ -30,14 +30,12 @@ com.oracle.database.jdbc - ojdbc11 - ${dep.oracle.version} + ojdbc10 com.oracle.database.jdbc - ucp11 - ${dep.oracle.version} + ucp @@ -109,7 +107,6 @@ com.oracle.database.nls orai18n - ${dep.oracle.version} runtime diff --git a/plugin/trino-resource-group-managers/pom.xml b/plugin/trino-resource-group-managers/pom.xml index afb9ee34af36..87a56ac2f1ce 100644 --- a/plugin/trino-resource-group-managers/pom.xml +++ b/plugin/trino-resource-group-managers/pom.xml @@ -158,8 +158,7 @@ com.oracle.database.jdbc - ojdbc11 - ${dep.oracle.version} + ojdbc10 runtime diff --git a/pom.xml b/pom.xml index 42b48e4087ba..beafd96a1d2f 100644 --- a/pom.xml +++ b/pom.xml @@ -193,7 +193,6 @@ 3.6.0 1.9.20 3.6.0 - 21.11.0.0 ${dep.airlift.version} 1.13.1 3.25.1 @@ -223,6 +222,14 @@ import + + com.oracle.database.jdbc + ojdbc10-production + 19.21.0.0 + pom + import + + com.squareup.okhttp3 okhttp-bom From deb8ae0d5b15db9338490b3dc51f2324e7af22fe Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Sat, 2 Sep 2023 12:11:02 +0900 Subject: [PATCH 375/587] Use Storage write API in BigQuery connector --- plugin/trino-bigquery/pom.xml | 11 ++++ .../bigquery/BigQueryConnectorModule.java | 1 + .../plugin/bigquery/BigQueryErrorCode.java | 1 + .../BigQueryGrpcOptionsConfigurer.java | 8 +++ .../bigquery/BigQueryOptionsConfigurer.java | 3 + .../plugin/bigquery/BigQueryPageSink.java | 58 ++++++++++++++----- .../bigquery/BigQueryPageSinkProvider.java | 8 +-- .../plugin/bigquery/BigQueryTypeManager.java | 2 +- .../plugin/bigquery/BigQueryTypeUtils.java | 42 ++++++-------- .../bigquery/BigQueryWriteClientFactory.java | 52 +++++++++++++++++ .../CredentialsOptionsConfigurer.java | 9 +++ .../plugin/bigquery/RemoteTableName.java | 6 ++ .../bigquery/RetryOptionsConfigurer.java | 15 +++++ .../bigquery/BaseBigQueryConnectorTest.java | 4 +- .../bigquery/BaseBigQueryTypeMapping.java | 16 +++++ 15 files changed, 192 insertions(+), 44 deletions(-) create mode 100644 plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryWriteClientFactory.java diff --git a/plugin/trino-bigquery/pom.xml b/plugin/trino-bigquery/pom.xml index 30f6094550c4..428667093582 100644 --- a/plugin/trino-bigquery/pom.xml +++ b/plugin/trino-bigquery/pom.xml @@ -103,6 +103,11 @@ + + com.google.api.grpc + proto-google-common-protos + + com.google.auth google-auth-library-credentials @@ -304,6 +309,12 @@ httpcore + + org.json + json + 20231013 + + org.threeten threetenbp diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryConnectorModule.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryConnectorModule.java index bc6211493a05..7cd9e62aadb7 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryConnectorModule.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryConnectorModule.java @@ -68,6 +68,7 @@ protected void setup(Binder binder) { // BigQuery related binder.bind(BigQueryReadClientFactory.class).in(Scopes.SINGLETON); + binder.bind(BigQueryWriteClientFactory.class).in(Scopes.SINGLETON); binder.bind(BigQueryClientFactory.class).in(Scopes.SINGLETON); binder.bind(BigQueryTypeManager.class).in(Scopes.SINGLETON); diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryErrorCode.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryErrorCode.java index 60fa13b0274b..d42de472b776 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryErrorCode.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryErrorCode.java @@ -31,6 +31,7 @@ public enum BigQueryErrorCode BIGQUERY_UNSUPPORTED_OPERATION(5, USER_ERROR), BIGQUERY_INVALID_STATEMENT(6, USER_ERROR), BIGQUERY_PROXY_SSL_INITIALIZATION_FAILED(7, EXTERNAL), + BIGQUERY_BAD_WRITE(8, EXTERNAL), /**/; private final ErrorCode errorCode; diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryGrpcOptionsConfigurer.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryGrpcOptionsConfigurer.java index f1c7c667ccac..7fe40acafc9d 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryGrpcOptionsConfigurer.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryGrpcOptionsConfigurer.java @@ -15,6 +15,7 @@ import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider; import com.google.cloud.bigquery.storage.v1.BigQueryReadSettings; +import com.google.cloud.bigquery.storage.v1.BigQueryWriteSettings; import io.trino.spi.connector.ConnectorSession; interface BigQueryGrpcOptionsConfigurer @@ -27,5 +28,12 @@ default BigQueryReadSettings.Builder configure(BigQueryReadSettings.Builder buil return builder.setTransportChannelProvider(configure(channelBuilder, session).build()); } + @Override + default BigQueryWriteSettings.Builder configure(BigQueryWriteSettings.Builder builder, ConnectorSession session) + { + InstantiatingGrpcChannelProvider.Builder channelBuilder = ((InstantiatingGrpcChannelProvider) builder.getTransportChannelProvider()).toBuilder(); + return builder.setTransportChannelProvider(configure(channelBuilder, session).build()); + } + InstantiatingGrpcChannelProvider.Builder configure(InstantiatingGrpcChannelProvider.Builder channelBuilder, ConnectorSession session); } diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryOptionsConfigurer.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryOptionsConfigurer.java index 14f4bdcd8277..1543a2c9f535 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryOptionsConfigurer.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryOptionsConfigurer.java @@ -15,6 +15,7 @@ import com.google.cloud.bigquery.BigQueryOptions; import com.google.cloud.bigquery.storage.v1.BigQueryReadSettings; +import com.google.cloud.bigquery.storage.v1.BigQueryWriteSettings; import io.trino.spi.connector.ConnectorSession; interface BigQueryOptionsConfigurer @@ -22,4 +23,6 @@ interface BigQueryOptionsConfigurer BigQueryOptions.Builder configure(BigQueryOptions.Builder builder, ConnectorSession session); BigQueryReadSettings.Builder configure(BigQueryReadSettings.Builder builder, ConnectorSession session); + + BigQueryWriteSettings.Builder configure(BigQueryWriteSettings.Builder builder, ConnectorSession session); } diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryPageSink.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryPageSink.java index e67d13702d0f..7a969aad5547 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryPageSink.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryPageSink.java @@ -13,40 +13,49 @@ */ package io.trino.plugin.bigquery; -import com.google.cloud.bigquery.InsertAllRequest; -import com.google.cloud.bigquery.TableId; +import com.google.api.core.ApiFuture; +import com.google.cloud.bigquery.storage.v1.AppendRowsResponse; +import com.google.cloud.bigquery.storage.v1.BigQueryWriteClient; +import com.google.cloud.bigquery.storage.v1.CreateWriteStreamRequest; +import com.google.cloud.bigquery.storage.v1.JsonStreamWriter; +import com.google.cloud.bigquery.storage.v1.TableName; +import com.google.cloud.bigquery.storage.v1.WriteStream; import com.google.common.collect.ImmutableList; import io.airlift.slice.Slice; import io.airlift.slice.Slices; import io.trino.spi.Page; +import io.trino.spi.TrinoException; import io.trino.spi.connector.ConnectorPageSink; import io.trino.spi.connector.ConnectorPageSinkId; import io.trino.spi.type.Type; +import org.json.JSONArray; +import org.json.JSONObject; import java.util.Collection; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import static com.google.cloud.bigquery.storage.v1.WriteStream.Type.COMMITTED; import static com.google.common.base.Preconditions.checkArgument; +import static io.trino.plugin.bigquery.BigQueryErrorCode.BIGQUERY_BAD_WRITE; import static io.trino.plugin.bigquery.BigQueryTypeUtils.readNativeValue; +import static java.lang.String.format; import static java.util.Objects.requireNonNull; import static java.util.concurrent.CompletableFuture.completedFuture; public class BigQueryPageSink implements ConnectorPageSink { - private final BigQueryClient client; - private final TableId tableId; + private final BigQueryWriteClient client; + private final WriteStream writeStream; private final List columnNames; private final List columnTypes; private final ConnectorPageSinkId pageSinkId; private final Optional pageSinkIdColumnName; public BigQueryPageSink( - BigQueryClient client, + BigQueryWriteClient client, RemoteTableName remoteTableName, List columnNames, List columnTypes, @@ -64,28 +73,49 @@ public BigQueryPageSink( this.pageSinkIdColumnName = requireNonNull(pageSinkIdColumnName, "pageSinkIdColumnName is null"); checkArgument(temporaryTableName.isPresent() == pageSinkIdColumnName.isPresent(), "temporaryTableName.isPresent is not equal to pageSinkIdColumn.isPresent"); - this.tableId = temporaryTableName - .map(tableName -> TableId.of(remoteTableName.getProjectId(), remoteTableName.getDatasetName(), tableName)) - .orElseGet(remoteTableName::toTableId); + TableName tableName = temporaryTableName + .map(table -> TableName.of(remoteTableName.getProjectId(), remoteTableName.getDatasetName(), table)) + .orElseGet(remoteTableName::toTableName); + // TODO: Consider using PENDING mode + WriteStream stream = WriteStream.newBuilder().setType(COMMITTED).build(); + CreateWriteStreamRequest createWriteStreamRequest = CreateWriteStreamRequest.newBuilder() + .setParent(tableName.toString()) + .setWriteStream(stream) + .build(); + this.writeStream = client.createWriteStream(createWriteStreamRequest); } @Override public CompletableFuture appendPage(Page page) { - InsertAllRequest.Builder batch = InsertAllRequest.newBuilder(tableId); + JSONArray batch = new JSONArray(); for (int position = 0; position < page.getPositionCount(); position++) { - Map row = new HashMap<>(); + JSONObject row = new JSONObject(); pageSinkIdColumnName.ifPresent(column -> row.put(column, pageSinkId.getId())); for (int channel = 0; channel < page.getChannelCount(); channel++) { row.put(columnNames.get(channel), readNativeValue(columnTypes.get(channel), page.getBlock(channel), position)); } - batch.addRow(row); + batch.put(row); } - client.insert(batch.build()); + insertWithCommitted(batch); return NOT_BLOCKED; } + private void insertWithCommitted(JSONArray batch) + { + try (JsonStreamWriter writer = JsonStreamWriter.newBuilder(writeStream.getName(), writeStream.getTableSchema(), client).build()) { + ApiFuture future = writer.append(batch); + AppendRowsResponse response = future.get(); // Throw error + if (response.hasError()) { + throw new TrinoException(BIGQUERY_BAD_WRITE, format("Response has error: %s", response.getError().getMessage())); + } + } + catch (Exception e) { + throw new TrinoException(BIGQUERY_BAD_WRITE, "Failed to insert rows", e); + } + } + @Override public CompletableFuture> finish() { diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryPageSinkProvider.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryPageSinkProvider.java index a3423fa78670..dc8de166298c 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryPageSinkProvider.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryPageSinkProvider.java @@ -29,10 +29,10 @@ public class BigQueryPageSinkProvider implements ConnectorPageSinkProvider { - private final BigQueryClientFactory clientFactory; + private final BigQueryWriteClientFactory clientFactory; @Inject - public BigQueryPageSinkProvider(BigQueryClientFactory clientFactory) + public BigQueryPageSinkProvider(BigQueryWriteClientFactory clientFactory) { this.clientFactory = requireNonNull(clientFactory, "clientFactory is null"); } @@ -42,7 +42,7 @@ public ConnectorPageSink createPageSink(ConnectorTransactionHandle transactionHa { BigQueryOutputTableHandle handle = (BigQueryOutputTableHandle) outputTableHandle; return new BigQueryPageSink( - clientFactory.createBigQueryClient(session), + clientFactory.create(session), handle.getRemoteTableName(), handle.getColumnNames(), handle.getColumnTypes(), @@ -56,7 +56,7 @@ public ConnectorPageSink createPageSink(ConnectorTransactionHandle transactionHa { BigQueryInsertTableHandle handle = (BigQueryInsertTableHandle) insertTableHandle; return new BigQueryPageSink( - clientFactory.createBigQueryClient(session), + clientFactory.create(session), handle.getRemoteTableName(), handle.getColumnNames(), handle.getColumnTypes(), diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryTypeManager.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryTypeManager.java index bfd4ab41e185..28d2a3f9eb3a 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryTypeManager.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryTypeManager.java @@ -154,7 +154,7 @@ public static String dateToStringConverter(Object value) return "'" + date + "'"; } - private static String datetimeToStringConverter(Object value) + public static String datetimeToStringConverter(Object value) { long epochMicros = (long) value; long epochSeconds = floorDiv(epochMicros, MICROSECONDS_PER_SECOND); diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryTypeUtils.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryTypeUtils.java index 9c278045b395..728b6f476ee4 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryTypeUtils.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryTypeUtils.java @@ -13,7 +13,7 @@ */ package io.trino.plugin.bigquery; -import com.google.common.collect.ImmutableList; +import com.google.protobuf.ByteString; import io.trino.spi.TrinoException; import io.trino.spi.block.Block; import io.trino.spi.block.SqlRow; @@ -24,16 +24,15 @@ import io.trino.spi.type.Type; import io.trino.spi.type.VarcharType; import jakarta.annotation.Nullable; +import org.json.JSONArray; +import org.json.JSONObject; import java.time.LocalDate; import java.time.format.DateTimeFormatter; -import java.util.Base64; -import java.util.HashMap; import java.util.List; -import java.util.Map; +import static io.trino.plugin.bigquery.BigQueryTypeManager.datetimeToStringConverter; import static io.trino.plugin.bigquery.BigQueryTypeManager.timestampToStringConverter; -import static io.trino.plugin.bigquery.BigQueryTypeManager.toZonedDateTime; import static io.trino.spi.StandardErrorCode.GENERIC_INTERNAL_ERROR; import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; import static io.trino.spi.type.BigintType.BIGINT; @@ -45,19 +44,15 @@ import static io.trino.spi.type.SmallintType.SMALLINT; import static io.trino.spi.type.TimestampType.TIMESTAMP_MICROS; import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MICROS; -import static io.trino.spi.type.Timestamps.MICROSECONDS_PER_SECOND; -import static io.trino.spi.type.Timestamps.NANOSECONDS_PER_MICROSECOND; import static io.trino.spi.type.TinyintType.TINYINT; import static io.trino.spi.type.VarbinaryType.VARBINARY; -import static java.lang.Math.floorDiv; -import static java.lang.Math.floorMod; -import static java.time.ZoneOffset.UTC; -import static java.util.Collections.unmodifiableMap; public final class BigQueryTypeUtils { + private static final long MIN_SUPPORTED_DATE = LocalDate.parse("0001-01-01").toEpochDay(); + private static final long MAX_SUPPORTED_DATE = LocalDate.parse("9999-12-31").toEpochDay(); + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd"); - private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss.SSSSSS"); private BigQueryTypeUtils() {} @@ -73,10 +68,10 @@ public static Object readNativeValue(Type type, Block block, int position) return BOOLEAN.getBoolean(block, position); } if (type.equals(TINYINT)) { - return TINYINT.getByte(block, position); + return type.getLong(block, position); } if (type.equals(SMALLINT)) { - return SMALLINT.getShort(block, position); + return SMALLINT.getLong(block, position); } if (type.equals(INTEGER)) { return INTEGER.getInt(block, position); @@ -94,17 +89,18 @@ public static Object readNativeValue(Type type, Block block, int position) return varcharType.getSlice(block, position).toStringUtf8(); } if (type.equals(VARBINARY)) { - return Base64.getEncoder().encodeToString(VARBINARY.getSlice(block, position).getBytes()); + return ByteString.copyFrom(VARBINARY.getSlice(block, position).getBytes()); } if (type.equals(DATE)) { int days = DATE.getInt(block, position); + if (days < MIN_SUPPORTED_DATE || days > MAX_SUPPORTED_DATE) { + throw new TrinoException(NOT_SUPPORTED, "BigQuery supports dates between 0001-01-01 and 9999-12-31 but got " + LocalDate.ofEpochDay(days)); + } return DATE_FORMATTER.format(LocalDate.ofEpochDay(days)); } if (type.equals(TIMESTAMP_MICROS)) { long epochMicros = TIMESTAMP_MICROS.getLong(block, position); - long epochSeconds = floorDiv(epochMicros, MICROSECONDS_PER_SECOND); - int nanoAdjustment = floorMod(epochMicros, MICROSECONDS_PER_SECOND) * NANOSECONDS_PER_MICROSECOND; - return DATETIME_FORMATTER.format(toZonedDateTime(epochSeconds, nanoAdjustment, UTC)); + return datetimeToStringConverter(epochMicros); } if (type.equals(TIMESTAMP_TZ_MICROS)) { LongTimestampWithTimeZone timestamp = (LongTimestampWithTimeZone) TIMESTAMP_TZ_MICROS.getObject(block, position); @@ -112,15 +108,15 @@ public static Object readNativeValue(Type type, Block block, int position) } if (type instanceof ArrayType arrayType) { Block arrayBlock = arrayType.getObject(block, position); - ImmutableList.Builder list = ImmutableList.builderWithExpectedSize(arrayBlock.getPositionCount()); + JSONArray list = new JSONArray(); for (int i = 0; i < arrayBlock.getPositionCount(); i++) { Object element = readNativeValue(arrayType.getElementType(), arrayBlock, i); if (element == null) { throw new TrinoException(NOT_SUPPORTED, "BigQuery does not support null elements in arrays"); } - list.add(element); + list.put(element); } - return list.build(); + return list; } if (type instanceof RowType rowType) { SqlRow sqlRow = rowType.getObject(block, position); @@ -131,13 +127,13 @@ public static Object readNativeValue(Type type, Block block, int position) } int rawIndex = sqlRow.getRawIndex(); - Map rowValue = new HashMap<>(); + JSONObject rowValue = new JSONObject(); for (int fieldIndex = 0; fieldIndex < sqlRow.getFieldCount(); fieldIndex++) { String fieldName = rowType.getFields().get(fieldIndex).getName().orElseThrow(() -> new IllegalArgumentException("Field name must exist in BigQuery")); Object fieldValue = readNativeValue(fieldTypes.get(fieldIndex), sqlRow.getRawFieldBlock(fieldIndex), rawIndex); rowValue.put(fieldName, fieldValue); } - return unmodifiableMap(rowValue); + return rowValue; } throw new TrinoException(NOT_SUPPORTED, "Unsupported type: " + type); diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryWriteClientFactory.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryWriteClientFactory.java new file mode 100644 index 000000000000..39afc63c068d --- /dev/null +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryWriteClientFactory.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 io.trino.plugin.bigquery; + +import com.google.cloud.bigquery.storage.v1.BigQueryWriteClient; +import com.google.cloud.bigquery.storage.v1.BigQueryWriteSettings; +import com.google.common.collect.ImmutableSet; +import com.google.inject.Inject; +import io.trino.spi.connector.ConnectorSession; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.Set; + +import static java.util.Objects.requireNonNull; + +public class BigQueryWriteClientFactory +{ + private final Set configurers; + + @Inject + public BigQueryWriteClientFactory(Set configurers) + { + this.configurers = ImmutableSet.copyOf(requireNonNull(configurers, "configurers is null")); + } + + public BigQueryWriteClient create(ConnectorSession session) + { + BigQueryWriteSettings.Builder builder = BigQueryWriteSettings.newBuilder(); + + for (BigQueryOptionsConfigurer configurer : configurers) { + builder = configurer.configure(builder, session); + } + try { + return BigQueryWriteClient.create(builder.build()); + } + catch (IOException e) { + throw new UncheckedIOException("Error creating BigQueryWriteClient", e); + } + } +} diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/CredentialsOptionsConfigurer.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/CredentialsOptionsConfigurer.java index f44eab1a8ff1..ee771c77b8b2 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/CredentialsOptionsConfigurer.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/CredentialsOptionsConfigurer.java @@ -18,6 +18,7 @@ import com.google.auth.oauth2.ServiceAccountCredentials; import com.google.cloud.bigquery.BigQueryOptions; import com.google.cloud.bigquery.storage.v1.BigQueryReadSettings; +import com.google.cloud.bigquery.storage.v1.BigQueryWriteSettings; import com.google.common.annotations.VisibleForTesting; import com.google.inject.Inject; import io.trino.spi.connector.ConnectorSession; @@ -58,6 +59,14 @@ public BigQueryReadSettings.Builder configure(BigQueryReadSettings.Builder build return builder; } + @Override + public BigQueryWriteSettings.Builder configure(BigQueryWriteSettings.Builder builder, ConnectorSession session) + { + Optional credentials = credentialsSupplier.getCredentials(session); + credentials.ifPresent(value -> builder.setCredentialsProvider(FixedCredentialsProvider.create(value))); + return builder; + } + // Note that at this point the config has been validated, which means that option 2 or option 3 will always be valid @VisibleForTesting static String calculateBillingProjectId(Optional configParentProjectId, Optional credentials) diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/RemoteTableName.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/RemoteTableName.java index 33bdf307d6ce..1fe82e53fdcc 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/RemoteTableName.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/RemoteTableName.java @@ -16,6 +16,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.cloud.bigquery.TableId; +import com.google.cloud.bigquery.storage.v1.TableName; import java.util.Objects; @@ -46,6 +47,11 @@ public TableId toTableId() return TableId.of(projectId, datasetName, tableName); } + public TableName toTableName() + { + return TableName.of(projectId, datasetName, tableName); + } + @JsonProperty public String getProjectId() { diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/RetryOptionsConfigurer.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/RetryOptionsConfigurer.java index 56f25cf87349..94735b606c9b 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/RetryOptionsConfigurer.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/RetryOptionsConfigurer.java @@ -16,6 +16,7 @@ import com.google.api.gax.retrying.RetrySettings; import com.google.cloud.bigquery.BigQueryOptions; import com.google.cloud.bigquery.storage.v1.BigQueryReadSettings; +import com.google.cloud.bigquery.storage.v1.BigQueryWriteSettings; import com.google.inject.Inject; import io.airlift.units.Duration; import io.trino.spi.connector.ConnectorSession; @@ -62,6 +63,20 @@ public BigQueryReadSettings.Builder configure(BigQueryReadSettings.Builder build } } + @Override + public BigQueryWriteSettings.Builder configure(BigQueryWriteSettings.Builder builder, ConnectorSession session) + { + try { + return builder.applyToAllUnaryMethods(methodBuilder -> { + methodBuilder.setRetrySettings(retrySettings()); + return null; + }); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + private RetrySettings retrySettings() { long maxDelay = retryDelay.toMillis() * (long) pow(retryMultiplier, retries); diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java index 9136cc411886..3ff3ffe1a9b6 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java @@ -995,13 +995,13 @@ public void testInsertRowConcurrently() @Override protected String errorMessageForCreateTableAsSelectNegativeDate(String date) { - return format(".*Invalid date: '%s'.*", date); + return "BigQuery supports dates between 0001-01-01 and 9999-12-31 but got " + date; } @Override protected String errorMessageForInsertNegativeDate(String date) { - return format(".*Invalid date: '%s'.*", date); + return "BigQuery supports dates between 0001-01-01 and 9999-12-31 but got " + date; } @Override diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryTypeMapping.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryTypeMapping.java index c3760d95ed7a..b3374bd27f3c 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryTypeMapping.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryTypeMapping.java @@ -415,6 +415,22 @@ public void testDate() .execute(getQueryRunner(), trinoCreateAndInsert("test.date")); } + @Test + public void testBigQueryUnsupportedDate() + { + try (TestTable table = new TestTable(getBigQuerySqlExecutor(), "test.unsupported_date", "(col date)")) { + assertQueryFails( + "INSERT INTO " + table.getName() + " VALUES date '-0001-01-01'", + "BigQuery supports dates between 0001-01-01 and 9999-12-31 but got -0001-01-01"); + assertQueryFails( + "INSERT INTO " + table.getName() + " VALUES date '0000-12-31'", + "BigQuery supports dates between 0001-01-01 and 9999-12-31 but got 0000-12-31"); + assertQueryFails( + "INSERT INTO " + table.getName() + " VALUES date '10000-01-01'", + "BigQuery supports dates between 0001-01-01 and 9999-12-31 but got \\+10000-01-01"); + } + } + @Test public void testTimestamp() { From 9d3f56473d61c02c760cf528a220498edea5fdd3 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Tue, 21 Nov 2023 10:30:00 +0100 Subject: [PATCH 376/587] Fix TestShardOrganizer.testShardOrganizerInProgress flakiness There was a race, the test job was sleeping for only 10ms. However, since GC pause is possible, sleeping "long enough" should be measured in seconds. Use a latch for synchronization instead of fixed sleep time. --- .../TestShardOrganizationManager.java | 17 +++++++++++- .../organization/TestShardOrganizer.java | 26 +++++++------------ 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizationManager.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizationManager.java index 9115d1b3b222..7ee6ae0061a6 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizationManager.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizationManager.java @@ -35,13 +35,13 @@ import java.util.UUID; import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.common.util.concurrent.Uninterruptibles.sleepUninterruptibly; import static io.airlift.units.Duration.nanosSince; import static io.trino.plugin.raptor.legacy.DatabaseTesting.createTestingJdbi; import static io.trino.plugin.raptor.legacy.metadata.SchemaDaoUtil.createTablesWithRetry; import static io.trino.plugin.raptor.legacy.metadata.TestDatabaseShardManager.createShardManager; import static io.trino.plugin.raptor.legacy.storage.organization.ShardOrganizationManager.createOrganizationSets; import static io.trino.plugin.raptor.legacy.storage.organization.TestCompactionSetCreator.extractIndexes; -import static io.trino.plugin.raptor.legacy.storage.organization.TestShardOrganizer.createShardOrganizer; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.DateType.DATE; import static io.trino.spi.type.TimestampType.TIMESTAMP_MILLIS; @@ -208,4 +208,19 @@ private ShardOrganizationManager createShardOrganizationManager(long intervalMil new Duration(intervalMillis, MILLISECONDS), new Duration(5, MINUTES)); } + + private static class MockJobFactory + implements JobFactory + { + @Override + public Runnable create(OrganizationSet organizationSet) + { + return () -> sleepUninterruptibly(10, MILLISECONDS); + } + } + + private static ShardOrganizer createShardOrganizer() + { + return new ShardOrganizer(new MockJobFactory(), 1); + } } diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizer.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizer.java index 9ba90eace90f..f72f4f464a55 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizer.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/storage/organization/TestShardOrganizer.java @@ -20,10 +20,13 @@ import java.util.OptionalInt; import java.util.Set; import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Iterables.getOnlyElement; -import static com.google.common.util.concurrent.Uninterruptibles.sleepUninterruptibly; +import static com.google.common.util.concurrent.Uninterruptibles.awaitUninterruptibly; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; public class TestShardOrganizer @@ -33,7 +36,10 @@ public class TestShardOrganizer public void testShardOrganizerInProgress() throws Exception { - ShardOrganizer organizer = createShardOrganizer(); + CountDownLatch canComplete = new CountDownLatch(1); + ShardOrganizer organizer = new ShardOrganizer( + organizationSet -> () -> checkState(awaitUninterruptibly(canComplete, 10, SECONDS)), + 1); Set shards = ImmutableSet.of(UUID.randomUUID()); OrganizationSet organizationSet = new OrganizationSet(1L, shards, OptionalInt.empty()); @@ -43,6 +49,7 @@ public void testShardOrganizerInProgress() assertThat(organizer.inProgress(getOnlyElement(shards))).isTrue(); assertThat(organizer.getShardsInProgress()).isEqualTo(1); + canComplete.countDown(); while (organizer.inProgress(getOnlyElement(shards))) { MILLISECONDS.sleep(10); } @@ -50,19 +57,4 @@ public void testShardOrganizerInProgress() assertThat(organizer.getShardsInProgress()).isEqualTo(0); organizer.shutdown(); } - - private static class MockJobFactory - implements JobFactory - { - @Override - public Runnable create(OrganizationSet organizationSet) - { - return () -> sleepUninterruptibly(10, MILLISECONDS); - } - } - - static ShardOrganizer createShardOrganizer() - { - return new ShardOrganizer(new MockJobFactory(), 1); - } } From f2746d6418ab6c734c40912ffaf2b64f9a3ea9a2 Mon Sep 17 00:00:00 2001 From: Konrad Dziedzic Date: Tue, 21 Nov 2023 14:03:32 +0100 Subject: [PATCH 377/587] Change type of ConnectorTableFunction binding in HiveModule --- .../io/trino/plugin/hive/HiveConnector.java | 6 +++--- .../java/io/trino/plugin/hive/HiveModule.java | 4 +--- .../hive/InternalHiveConnectorFactory.java | 2 +- .../plugin/hive/NoopFunctionProvider.java | 20 +++++++++++++++++++ 4 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/NoopFunctionProvider.java diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveConnector.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveConnector.java index 6771a8216ade..b1c4116ad26e 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveConnector.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveConnector.java @@ -70,7 +70,7 @@ public class HiveConnector private final HiveTransactionManager transactionManager; private final Set connectorTableFunctions; - private final Optional functionProvider; + private final FunctionProvider functionProvider; private final boolean singleStatementWritesOnly; public HiveConnector( @@ -92,7 +92,7 @@ public HiveConnector( List> materializedViewProperties, Optional accessControl, Set connectorTableFunctions, - Optional functionProvider, + FunctionProvider functionProvider, boolean singleStatementWritesOnly, ClassLoader classLoader) { @@ -243,7 +243,7 @@ public final void shutdown() @Override public Optional getFunctionProvider() { - return functionProvider; + return Optional.of(functionProvider); } @Override diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveModule.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveModule.java index 2f0e6a6b97ea..878f6bf62257 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveModule.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveModule.java @@ -19,7 +19,6 @@ import com.google.inject.Provides; import com.google.inject.Scopes; import com.google.inject.Singleton; -import com.google.inject.TypeLiteral; import com.google.inject.multibindings.Multibinder; import io.airlift.event.client.EventClient; import io.trino.plugin.base.CatalogName; @@ -57,7 +56,6 @@ import io.trino.spi.function.FunctionProvider; import io.trino.spi.function.table.ConnectorTableFunction; -import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledExecutorService; @@ -149,7 +147,7 @@ public void configure(Binder binder) configBinder(binder).bindConfig(ParquetWriterConfig.class); fileWriterFactoryBinder.addBinding().to(ParquetFileWriterFactory.class).in(Scopes.SINGLETON); - newOptionalBinder(binder, new TypeLiteral>(){}).setDefault().toInstance(Optional.empty()); + newOptionalBinder(binder, FunctionProvider.class).setDefault().toInstance(new NoopFunctionProvider()); newSetBinder(binder, ConnectorTableFunction.class); } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/InternalHiveConnectorFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/InternalHiveConnectorFactory.java index 995606d56797..6dda13819bec 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/InternalHiveConnectorFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/InternalHiveConnectorFactory.java @@ -174,7 +174,7 @@ public static Connector createConnector( hiveMaterializedViewPropertiesProvider.getMaterializedViewProperties(), hiveAccessControl, injector.getInstance(Key.get(new TypeLiteral>() {})), - injector.getInstance(Key.get(new TypeLiteral>() {})), + injector.getInstance(FunctionProvider.class), injector.getInstance(HiveConfig.class).isSingleStatementWritesOnly(), classLoader); } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/NoopFunctionProvider.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/NoopFunctionProvider.java new file mode 100644 index 000000000000..f994b7a96c3f --- /dev/null +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/NoopFunctionProvider.java @@ -0,0 +1,20 @@ +/* + * 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 io.trino.plugin.hive; + +import io.trino.spi.function.FunctionProvider; + +public class NoopFunctionProvider + implements FunctionProvider +{} From 8b4e6efafda134f104888273563440b3b0c169ae Mon Sep 17 00:00:00 2001 From: Konrad Dziedzic Date: Tue, 21 Nov 2023 14:08:38 +0100 Subject: [PATCH 378/587] Fix lack of valid class loader for TableChangesSplitProcessor --- ...LoaderSafeTableFunctionSplitProcessor.java | 41 +++++++++++++++++++ .../TestClassLoaderSafeWrappers.java | 2 + .../TableChangesProcessorProvider.java | 6 ++- ...TableChangesFunctionProcessorProvider.java | 6 ++- 4 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeTableFunctionSplitProcessor.java diff --git a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeTableFunctionSplitProcessor.java b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeTableFunctionSplitProcessor.java new file mode 100644 index 000000000000..07cfe2b1ec45 --- /dev/null +++ b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeTableFunctionSplitProcessor.java @@ -0,0 +1,41 @@ +/* + * 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 io.trino.plugin.base.classloader; + +import io.trino.spi.classloader.ThreadContextClassLoader; +import io.trino.spi.function.table.TableFunctionProcessorState; +import io.trino.spi.function.table.TableFunctionSplitProcessor; + +import static java.util.Objects.requireNonNull; + +public final class ClassLoaderSafeTableFunctionSplitProcessor + implements TableFunctionSplitProcessor +{ + private final TableFunctionSplitProcessor delegate; + private final ClassLoader classLoader; + + public ClassLoaderSafeTableFunctionSplitProcessor(TableFunctionSplitProcessor delegate, ClassLoader classLoader) + { + this.delegate = requireNonNull(delegate, "delegate is null"); + this.classLoader = requireNonNull(classLoader, "classLoader is null"); + } + + @Override + public TableFunctionProcessorState process() + { + try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(classLoader)) { + return delegate.process(); + } + } +} diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/classloader/TestClassLoaderSafeWrappers.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/classloader/TestClassLoaderSafeWrappers.java index 6e9bb520cf34..8030347fed30 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/classloader/TestClassLoaderSafeWrappers.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/classloader/TestClassLoaderSafeWrappers.java @@ -27,6 +27,7 @@ import io.trino.spi.connector.SystemTable; import io.trino.spi.eventlistener.EventListener; import io.trino.spi.function.table.ConnectorTableFunction; +import io.trino.spi.function.table.TableFunctionSplitProcessor; import org.junit.jupiter.api.Test; import java.lang.reflect.Method; @@ -58,6 +59,7 @@ public void test() testClassLoaderSafe(RecordSet.class, ClassLoaderSafeRecordSet.class); testClassLoaderSafe(EventListener.class, ClassLoaderSafeEventListener.class); testClassLoaderSafe(ConnectorTableFunction.class, ClassLoaderSafeConnectorTableFunction.class); + testClassLoaderSafe(TableFunctionSplitProcessor.class, ClassLoaderSafeTableFunctionSplitProcessor.class); } private static void testClassLoaderSafe(Class iface, Class clazz) diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/functions/tablechanges/TableChangesProcessorProvider.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/functions/tablechanges/TableChangesProcessorProvider.java index 5a250e87f73e..b0a7c74b4545 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/functions/tablechanges/TableChangesProcessorProvider.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/functions/tablechanges/TableChangesProcessorProvider.java @@ -16,6 +16,7 @@ import com.google.inject.Inject; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.parquet.ParquetReaderOptions; +import io.trino.plugin.base.classloader.ClassLoaderSafeTableFunctionSplitProcessor; import io.trino.plugin.deltalake.DeltaLakeConfig; import io.trino.plugin.hive.FileFormatDataSourceStats; import io.trino.plugin.hive.parquet.ParquetReaderConfig; @@ -54,7 +55,7 @@ public TableChangesProcessorProvider( @Override public TableFunctionSplitProcessor getSplitProcessor(ConnectorSession session, ConnectorTableFunctionHandle handle, ConnectorSplit split) { - return new TableChangesFunctionProcessor( + return new ClassLoaderSafeTableFunctionSplitProcessor(new TableChangesFunctionProcessor( session, fileSystemFactory, parquetDateTimeZone, @@ -62,6 +63,7 @@ public TableFunctionSplitProcessor getSplitProcessor(ConnectorSession session, C fileFormatDataSourceStats, parquetReaderOptions, (TableChangesTableFunctionHandle) handle, - (TableChangesSplit) split); + (TableChangesSplit) split), + getClass().getClassLoader()); } } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/functions/tablechanges/TableChangesFunctionProcessorProvider.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/functions/tablechanges/TableChangesFunctionProcessorProvider.java index b052b722b44d..1121046bff33 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/functions/tablechanges/TableChangesFunctionProcessorProvider.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/functions/tablechanges/TableChangesFunctionProcessorProvider.java @@ -14,6 +14,7 @@ package io.trino.plugin.iceberg.functions.tablechanges; import com.google.inject.Inject; +import io.trino.plugin.base.classloader.ClassLoaderSafeTableFunctionSplitProcessor; import io.trino.plugin.iceberg.IcebergPageSourceProvider; import io.trino.spi.connector.ConnectorSession; import io.trino.spi.connector.ConnectorSplit; @@ -40,10 +41,11 @@ public TableFunctionSplitProcessor getSplitProcessor( ConnectorTableFunctionHandle handle, ConnectorSplit split) { - return new TableChangesFunctionProcessor( + return new ClassLoaderSafeTableFunctionSplitProcessor(new TableChangesFunctionProcessor( session, (TableChangesFunctionHandle) handle, (TableChangesSplit) split, - icebergPageSourceProvider); + icebergPageSourceProvider), + getClass().getClassLoader()); } } From 19327e9684456c9c5e11fbbaa3e42f491562a2a5 Mon Sep 17 00:00:00 2001 From: Konrad Dziedzic Date: Tue, 21 Nov 2023 14:21:03 +0100 Subject: [PATCH 379/587] Fix lack of valid class loader for TableChangesProcessorProvider --- ...derSafeTableFunctionProcessorProvider.java | 53 +++++++++++++++++++ .../TestClassLoaderSafeWrappers.java | 2 + .../deltalake/DeltaLakeFunctionProvider.java | 3 +- .../functions/IcebergFunctionProvider.java | 3 +- 4 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeTableFunctionProcessorProvider.java diff --git a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeTableFunctionProcessorProvider.java b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeTableFunctionProcessorProvider.java new file mode 100644 index 000000000000..51f54e99ea67 --- /dev/null +++ b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeTableFunctionProcessorProvider.java @@ -0,0 +1,53 @@ +/* + * 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 io.trino.plugin.base.classloader; + +import io.trino.spi.classloader.ThreadContextClassLoader; +import io.trino.spi.connector.ConnectorSession; +import io.trino.spi.connector.ConnectorSplit; +import io.trino.spi.function.table.ConnectorTableFunctionHandle; +import io.trino.spi.function.table.TableFunctionDataProcessor; +import io.trino.spi.function.table.TableFunctionProcessorProvider; +import io.trino.spi.function.table.TableFunctionSplitProcessor; + +import static java.util.Objects.requireNonNull; + +public final class ClassLoaderSafeTableFunctionProcessorProvider + implements TableFunctionProcessorProvider +{ + private final TableFunctionProcessorProvider delegate; + private final ClassLoader classLoader; + + public ClassLoaderSafeTableFunctionProcessorProvider(TableFunctionProcessorProvider delegate, ClassLoader classLoader) + { + this.delegate = requireNonNull(delegate, "delegate is null"); + this.classLoader = requireNonNull(classLoader, "classLoader is null"); + } + + @Override + public TableFunctionDataProcessor getDataProcessor(ConnectorTableFunctionHandle handle) + { + try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(classLoader)) { + return delegate.getDataProcessor(handle); + } + } + + @Override + public TableFunctionSplitProcessor getSplitProcessor(ConnectorSession session, ConnectorTableFunctionHandle handle, ConnectorSplit split) + { + try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(classLoader)) { + return delegate.getSplitProcessor(session, handle, split); + } + } +} diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/classloader/TestClassLoaderSafeWrappers.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/classloader/TestClassLoaderSafeWrappers.java index 8030347fed30..d0834ec00fa6 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/classloader/TestClassLoaderSafeWrappers.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/classloader/TestClassLoaderSafeWrappers.java @@ -27,6 +27,7 @@ import io.trino.spi.connector.SystemTable; import io.trino.spi.eventlistener.EventListener; import io.trino.spi.function.table.ConnectorTableFunction; +import io.trino.spi.function.table.TableFunctionProcessorProvider; import io.trino.spi.function.table.TableFunctionSplitProcessor; import org.junit.jupiter.api.Test; @@ -60,6 +61,7 @@ public void test() testClassLoaderSafe(EventListener.class, ClassLoaderSafeEventListener.class); testClassLoaderSafe(ConnectorTableFunction.class, ClassLoaderSafeConnectorTableFunction.class); testClassLoaderSafe(TableFunctionSplitProcessor.class, ClassLoaderSafeTableFunctionSplitProcessor.class); + testClassLoaderSafe(TableFunctionProcessorProvider.class, ClassLoaderSafeTableFunctionProcessorProvider.class); } private static void testClassLoaderSafe(Class iface, Class clazz) diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeFunctionProvider.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeFunctionProvider.java index de4732d88d34..4e541200b7e1 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeFunctionProvider.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeFunctionProvider.java @@ -14,6 +14,7 @@ package io.trino.plugin.deltalake; import com.google.inject.Inject; +import io.trino.plugin.base.classloader.ClassLoaderSafeTableFunctionProcessorProvider; import io.trino.plugin.deltalake.functions.tablechanges.TableChangesProcessorProvider; import io.trino.plugin.deltalake.functions.tablechanges.TableChangesTableFunctionHandle; import io.trino.spi.function.FunctionProvider; @@ -37,7 +38,7 @@ public DeltaLakeFunctionProvider(TableChangesProcessorProvider tableChangesProce public TableFunctionProcessorProvider getTableFunctionProcessorProvider(ConnectorTableFunctionHandle functionHandle) { if (functionHandle instanceof TableChangesTableFunctionHandle) { - return tableChangesProcessorProvider; + return new ClassLoaderSafeTableFunctionProcessorProvider(tableChangesProcessorProvider, getClass().getClassLoader()); } throw new UnsupportedOperationException("Unsupported function: " + functionHandle); } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/functions/IcebergFunctionProvider.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/functions/IcebergFunctionProvider.java index 57bbea9479df..dac202aacf5d 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/functions/IcebergFunctionProvider.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/functions/IcebergFunctionProvider.java @@ -14,6 +14,7 @@ package io.trino.plugin.iceberg.functions; import com.google.inject.Inject; +import io.trino.plugin.base.classloader.ClassLoaderSafeTableFunctionProcessorProvider; import io.trino.plugin.iceberg.functions.tablechanges.TableChangesFunctionHandle; import io.trino.plugin.iceberg.functions.tablechanges.TableChangesFunctionProcessorProvider; import io.trino.spi.function.FunctionProvider; @@ -37,7 +38,7 @@ public IcebergFunctionProvider(TableChangesFunctionProcessorProvider tableChange public TableFunctionProcessorProvider getTableFunctionProcessorProvider(ConnectorTableFunctionHandle functionHandle) { if (functionHandle instanceof TableChangesFunctionHandle) { - return tableChangesFunctionProcessorProvider; + return new ClassLoaderSafeTableFunctionProcessorProvider(tableChangesFunctionProcessorProvider, getClass().getClassLoader()); } throw new UnsupportedOperationException("Unsupported function: " + functionHandle); From 83ab1e1f94ebabcee595b2dae35a85a79899f8f8 Mon Sep 17 00:00:00 2001 From: Marius Grama Date: Fri, 17 Nov 2023 11:42:02 +0100 Subject: [PATCH 380/587] Fix listing views in Delta with Glue --- .../glue/TestDeltaLakeViewsGlueMetastore.java | 2 ++ .../glue/converter/GlueInputConverter.java | 15 +++++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeViewsGlueMetastore.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeViewsGlueMetastore.java index f7ddf7a81b11..cce31746b617 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeViewsGlueMetastore.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeViewsGlueMetastore.java @@ -83,6 +83,8 @@ public void testCreateView() TestView view = new TestView(getQueryRunner()::execute, viewName, "SELECT * FROM " + table.getName())) { assertQuery(format("SELECT * FROM %s", view.getName()), "VALUES 'test'"); assertQuery(format("SELECT table_type FROM information_schema.tables WHERE table_name = '%s' AND table_schema='%s'", view.getName(), SCHEMA), "VALUES 'VIEW'"); + // Ensure all relations are being listed + assertQuery(format("SELECT table_type FROM information_schema.tables WHERE table_name LIKE '%%%s' AND table_schema='%s'", view.getName(), SCHEMA), "VALUES 'VIEW'"); } } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/converter/GlueInputConverter.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/converter/GlueInputConverter.java index 994466d64f89..9c5a7367c6e4 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/converter/GlueInputConverter.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/converter/GlueInputConverter.java @@ -37,11 +37,14 @@ import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableMap.toImmutableMap; import static io.trino.plugin.hive.HiveMetadata.TABLE_COMMENT; +import static io.trino.plugin.hive.ViewReaderUtil.isTrinoMaterializedView; +import static io.trino.plugin.hive.ViewReaderUtil.isTrinoView; import static io.trino.plugin.hive.metastore.thrift.ThriftMetastoreUtil.metastoreFunctionName; import static io.trino.plugin.hive.metastore.thrift.ThriftMetastoreUtil.toResourceUris; import static io.trino.plugin.hive.metastore.thrift.ThriftMetastoreUtil.updateStatisticsParameters; @@ -64,10 +67,14 @@ public static DatabaseInput convertDatabase(Database database) public static TableInput convertTable(Table table) { - Optional comment = Optional.ofNullable(table.getParameters().get(TABLE_COMMENT)); - Map tableParameters = table.getParameters().entrySet().stream() - .filter(entry -> !entry.getKey().equals(TABLE_COMMENT)) - .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)); + Map tableParameters = table.getParameters(); + Optional comment = Optional.empty(); + if (!isTrinoView(table) && !isTrinoMaterializedView(table)) { + tableParameters = tableParameters.entrySet().stream() + .filter(entry -> !entry.getKey().equals(TABLE_COMMENT)) + .collect(toImmutableMap(Entry::getKey, Entry::getValue)); + comment = Optional.ofNullable(table.getParameters().get(TABLE_COMMENT)); + } TableInput input = new TableInput(); input.setName(table.getTableName()); From 98f82c2d08631cf04cd0c77f32db0d9b092c3d8e Mon Sep 17 00:00:00 2001 From: Marius Grama Date: Tue, 21 Nov 2023 14:55:16 +0100 Subject: [PATCH 381/587] empty --- .../hive/metastore/glue/converter/GlueInputConverter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/converter/GlueInputConverter.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/converter/GlueInputConverter.java index 9c5a7367c6e4..11defde147e1 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/converter/GlueInputConverter.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/converter/GlueInputConverter.java @@ -70,10 +70,10 @@ public static TableInput convertTable(Table table) Map tableParameters = table.getParameters(); Optional comment = Optional.empty(); if (!isTrinoView(table) && !isTrinoMaterializedView(table)) { + comment = Optional.ofNullable(tableParameters.get(TABLE_COMMENT)); tableParameters = tableParameters.entrySet().stream() .filter(entry -> !entry.getKey().equals(TABLE_COMMENT)) .collect(toImmutableMap(Entry::getKey, Entry::getValue)); - comment = Optional.ofNullable(table.getParameters().get(TABLE_COMMENT)); } TableInput input = new TableInput(); From c2fb0c6bff36ac6a8a2e4f31e50044a6ab41574f Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Fri, 17 Nov 2023 18:17:20 +0900 Subject: [PATCH 382/587] Change latest connector test to smoke test in MariaDB Also, update the docker image versions. --- docs/src/main/sphinx/connector/mariadb.md | 2 +- .../mariadb/TestMariaDbConnectorTest.java | 28 ------------------- ... TestMariaDbLatestConnectorSmokeTest.java} | 22 +++++++++------ .../plugin/mariadb/TestingMariaDbServer.java | 4 +-- 4 files changed, 16 insertions(+), 40 deletions(-) rename plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/{TestMariaDbLatestConnectorTest.java => TestMariaDbLatestConnectorSmokeTest.java} (66%) diff --git a/docs/src/main/sphinx/connector/mariadb.md b/docs/src/main/sphinx/connector/mariadb.md index 700843215c71..5eacada0501e 100644 --- a/docs/src/main/sphinx/connector/mariadb.md +++ b/docs/src/main/sphinx/connector/mariadb.md @@ -17,7 +17,7 @@ database. To connect to MariaDB, you need: -- MariaDB version 10.2 or higher. +- MariaDB version 10.10 or higher. - Network access from the Trino coordinator and workers to MariaDB. Port 3306 is the default port. diff --git a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbConnectorTest.java b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbConnectorTest.java index 42a7bf7432e8..639bead397da 100644 --- a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbConnectorTest.java +++ b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbConnectorTest.java @@ -16,10 +16,8 @@ import com.google.common.collect.ImmutableMap; import io.trino.testing.QueryRunner; import io.trino.testing.sql.SqlExecutor; -import org.junit.jupiter.api.Test; import static io.trino.plugin.mariadb.MariaDbQueryRunner.createMariaDbQueryRunner; -import static org.assertj.core.api.Assertions.assertThatThrownBy; public class TestMariaDbConnectorTest extends BaseMariaDbConnectorTest @@ -37,30 +35,4 @@ protected SqlExecutor onRemoteDatabase() { return server::execute; } - - @Test - @Override - public void testRenameColumn() - { - assertThatThrownBy(super::testRenameColumn) - .hasMessageContaining("Rename column not supported for the MariaDB server version"); - } - - @Test - @Override - public void testRenameColumnName() - { - for (String columnName : testColumnNameDataProvider()) { - assertThatThrownBy(() -> testRenameColumnName(columnName, requiresDelimiting(columnName))) - .hasMessageContaining("Rename column not supported for the MariaDB server version"); - } - } - - @Test - @Override - public void testAlterTableRenameColumnToLongName() - { - assertThatThrownBy(super::testAlterTableRenameColumnToLongName) - .hasMessageContaining("Rename column not supported for the MariaDB server version"); - } } diff --git a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbLatestConnectorTest.java b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbLatestConnectorSmokeTest.java similarity index 66% rename from plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbLatestConnectorTest.java rename to plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbLatestConnectorSmokeTest.java index 7664ef9e0cb3..186eeb250cc6 100644 --- a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbLatestConnectorTest.java +++ b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestMariaDbLatestConnectorSmokeTest.java @@ -14,26 +14,30 @@ package io.trino.plugin.mariadb; import com.google.common.collect.ImmutableMap; +import io.trino.plugin.jdbc.BaseJdbcConnectorSmokeTest; import io.trino.testing.QueryRunner; -import io.trino.testing.sql.SqlExecutor; +import io.trino.testing.TestingConnectorBehavior; import static io.trino.plugin.mariadb.MariaDbQueryRunner.createMariaDbQueryRunner; import static io.trino.plugin.mariadb.TestingMariaDbServer.LATEST_VERSION; -public class TestMariaDbLatestConnectorTest - extends BaseMariaDbConnectorTest +public class TestMariaDbLatestConnectorSmokeTest + extends BaseJdbcConnectorSmokeTest { @Override - protected QueryRunner createQueryRunner() - throws Exception + protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) { - server = closeAfterClass(new TestingMariaDbServer(LATEST_VERSION)); - return createMariaDbQueryRunner(server, ImmutableMap.of(), ImmutableMap.of(), REQUIRED_TPCH_TABLES); + return switch (connectorBehavior) { + case SUPPORTS_RENAME_SCHEMA -> false; + default -> super.hasBehavior(connectorBehavior); + }; } @Override - protected SqlExecutor onRemoteDatabase() + protected QueryRunner createQueryRunner() + throws Exception { - return server::execute; + TestingMariaDbServer server = closeAfterClass(new TestingMariaDbServer(LATEST_VERSION)); + return createMariaDbQueryRunner(server, ImmutableMap.of(), ImmutableMap.of(), REQUIRED_TPCH_TABLES); } } diff --git a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestingMariaDbServer.java b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestingMariaDbServer.java index c9796befa167..4d2a6c280172 100644 --- a/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestingMariaDbServer.java +++ b/plugin/trino-mariadb/src/test/java/io/trino/plugin/mariadb/TestingMariaDbServer.java @@ -27,8 +27,8 @@ public class TestingMariaDbServer implements AutoCloseable { - public static final String LATEST_VERSION = "10.7.1"; - public static final String DEFAULT_VERSION = "10.2"; + public static final String LATEST_VERSION = "11.1.3"; + public static final String DEFAULT_VERSION = "10.10"; private static final int MARIADB_PORT = 3306; private final MariaDBContainer container; From ecc95ea2bf6695751853650707ea8effe8ed054c Mon Sep 17 00:00:00 2001 From: James Petty Date: Wed, 15 Nov 2023 15:42:56 -0500 Subject: [PATCH 383/587] Flush partitioned page builders more eagerly Flushes PositionsAppenderPageBuilder after reaching 4x the PageProcessor.MAX_BATCH_SIZE positions. The previous limit of Integer.MAX_VALUE positions could easily overflow since append operations can insert more than a single row and isFull() is only checked after inserting an entire page into the builder. Additionally, buffering too many RLE rows in the builder can prevent it from reaching the size limit and starve downstream tasks from receiving any input at all. --- .../output/PositionsAppenderPageBuilder.java | 7 ++- .../TestPositionsAppenderPageBuilder.java | 57 +++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 core/trino-main/src/test/java/io/trino/operator/output/TestPositionsAppenderPageBuilder.java diff --git a/core/trino-main/src/main/java/io/trino/operator/output/PositionsAppenderPageBuilder.java b/core/trino-main/src/main/java/io/trino/operator/output/PositionsAppenderPageBuilder.java index 7b113d87d429..69cd1b7f1d85 100644 --- a/core/trino-main/src/main/java/io/trino/operator/output/PositionsAppenderPageBuilder.java +++ b/core/trino-main/src/main/java/io/trino/operator/output/PositionsAppenderPageBuilder.java @@ -13,6 +13,8 @@ */ package io.trino.operator.output; +import com.google.common.annotations.VisibleForTesting; +import io.trino.operator.project.PageProcessor; import io.trino.spi.Page; import io.trino.spi.block.Block; import io.trino.spi.type.Type; @@ -26,6 +28,9 @@ public class PositionsAppenderPageBuilder { private static final int DEFAULT_INITIAL_EXPECTED_ENTRIES = 8; + @VisibleForTesting + static final int MAX_POSITION_COUNT = PageProcessor.MAX_BATCH_SIZE * 4; + private final UnnestingPositionsAppender[] channelAppenders; private final int maxPageSizeInBytes; private int declaredPositions; @@ -98,7 +103,7 @@ private void declarePositions(int positions) public boolean isFull() { - return declaredPositions == Integer.MAX_VALUE || getSizeInBytes() >= maxPageSizeInBytes; + return declaredPositions >= MAX_POSITION_COUNT || getSizeInBytes() >= maxPageSizeInBytes; } public boolean isEmpty() diff --git a/core/trino-main/src/test/java/io/trino/operator/output/TestPositionsAppenderPageBuilder.java b/core/trino-main/src/test/java/io/trino/operator/output/TestPositionsAppenderPageBuilder.java new file mode 100644 index 000000000000..4eec501719cf --- /dev/null +++ b/core/trino-main/src/test/java/io/trino/operator/output/TestPositionsAppenderPageBuilder.java @@ -0,0 +1,57 @@ +/* + * 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 io.trino.operator.output; + +import io.airlift.slice.Slices; +import io.trino.spi.Page; +import io.trino.spi.block.Block; +import io.trino.spi.block.RunLengthEncodedBlock; +import io.trino.type.BlockTypeOperators; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static io.trino.spi.type.VarcharType.VARCHAR; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TestPositionsAppenderPageBuilder +{ + @Test + public void testFullOnPositionCountLimit() + { + int maxPageBytes = 1024 * 1024; + PositionsAppenderPageBuilder pageBuilder = PositionsAppenderPageBuilder.withMaxPageSize( + maxPageBytes, + List.of(VARCHAR), + new PositionsAppenderFactory(new BlockTypeOperators())); + + Block rleBlock = RunLengthEncodedBlock.create(VARCHAR, Slices.utf8Slice("test"), 10); + Page inputPage = new Page(rleBlock); + + IntArrayList positions = IntArrayList.wrap(new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); + // Append 32760 positions, just less than MAX_POSITION_COUNT + assertEquals(32768, PositionsAppenderPageBuilder.MAX_POSITION_COUNT, "expected MAX_POSITION_COUNT to be 32768"); + for (int i = 0; i < 3276; i++) { + pageBuilder.appendToOutputPartition(inputPage, positions); + } + assertFalse(pageBuilder.isFull(), "pageBuilder should still not be full"); + // Append 10 more positions, crossing the threshold on position count + pageBuilder.appendToOutputPartition(inputPage, positions); + assertTrue(pageBuilder.isFull(), "pageBuilder should be full"); + assertEquals(rleBlock.getSizeInBytes(), pageBuilder.getSizeInBytes()); + } +} From cb685ff76b81d722e34a5bc93593dfa926c3f770 Mon Sep 17 00:00:00 2001 From: James Petty Date: Wed, 15 Nov 2023 16:13:28 -0500 Subject: [PATCH 384/587] Track dictionary ids size when partitioning Although the total exact size in bytes of UnnestingPositionAppender dictionaries is expensive to track, we should at least account for the dictionary ids size. Otherwise, repeated dictionary insertions don't increase the reported size at all. --- .../io/trino/operator/output/UnnestingPositionsAppender.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/trino-main/src/main/java/io/trino/operator/output/UnnestingPositionsAppender.java b/core/trino-main/src/main/java/io/trino/operator/output/UnnestingPositionsAppender.java index 63c1485fcc08..d818b385278f 100644 --- a/core/trino-main/src/main/java/io/trino/operator/output/UnnestingPositionsAppender.java +++ b/core/trino-main/src/main/java/io/trino/operator/output/UnnestingPositionsAppender.java @@ -206,7 +206,8 @@ public long getRetainedSizeInBytes() public long getSizeInBytes() { return delegate.getSizeInBytes() + - // dictionary size is not included due to the expense of the calculation + // dictionary size is not included due to the expense of the calculation, but we can account for the ids size + (dictionaryIdsBuilder.size() * (long) Integer.BYTES) + (rleValue != null ? rleValue.getSizeInBytes() : 0); } From ddae2ecc015436e8eacd1b430b065ee5fb93d885 Mon Sep 17 00:00:00 2001 From: James Petty Date: Wed, 15 Nov 2023 15:59:34 -0500 Subject: [PATCH 385/587] Limit RLE to direct expansion when partitioning Flushes PositionsAppenderPageBuilder entries if the cost of converting all RLE channels into direct channels would exceed the maximum page size by a factor of 8x. Previously, page builders could buffer a very large number of RLE positions without being considered full and then suddenly expand to huge sizes when forced to transition to a direct representation as a result of the RLE input value changing across one or more columns. In particular, this is can easily happen when pages are produced from CROSS JOIN UNNEST operations. --- .../output/PositionsAppenderPageBuilder.java | 37 +++++++++++++- .../PositionsAppenderSizeAccumulator.java | 42 ++++++++++++++++ .../output/UnnestingPositionsAppender.java | 8 +++ .../TestPositionsAppenderPageBuilder.java | 50 ++++++++++++++++++- 4 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 core/trino-main/src/main/java/io/trino/operator/output/PositionsAppenderSizeAccumulator.java diff --git a/core/trino-main/src/main/java/io/trino/operator/output/PositionsAppenderPageBuilder.java b/core/trino-main/src/main/java/io/trino/operator/output/PositionsAppenderPageBuilder.java index 69cd1b7f1d85..4ba6fd3361df 100644 --- a/core/trino-main/src/main/java/io/trino/operator/output/PositionsAppenderPageBuilder.java +++ b/core/trino-main/src/main/java/io/trino/operator/output/PositionsAppenderPageBuilder.java @@ -22,6 +22,7 @@ import java.util.List; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static java.util.Objects.requireNonNull; @@ -30,26 +31,41 @@ public class PositionsAppenderPageBuilder private static final int DEFAULT_INITIAL_EXPECTED_ENTRIES = 8; @VisibleForTesting static final int MAX_POSITION_COUNT = PageProcessor.MAX_BATCH_SIZE * 4; + // Maximum page size before being considered full based on current direct appender size and if RLE channels were converted to direct. Currently, + // dictionary mode appenders still under-report because computing their equivalent size if converted to direct is prohibitively expensive. + private static final int MAXIMUM_DIRECT_SIZE_MULTIPLIER = 8; private final UnnestingPositionsAppender[] channelAppenders; private final int maxPageSizeInBytes; + private final int maxDirectPageSizeInBytes; private int declaredPositions; public static PositionsAppenderPageBuilder withMaxPageSize(int maxPageBytes, List sourceTypes, PositionsAppenderFactory positionsAppenderFactory) { - return new PositionsAppenderPageBuilder(DEFAULT_INITIAL_EXPECTED_ENTRIES, maxPageBytes, sourceTypes, positionsAppenderFactory); + return withMaxPageSize(maxPageBytes, maxPageBytes * MAXIMUM_DIRECT_SIZE_MULTIPLIER, sourceTypes, positionsAppenderFactory); + } + + @VisibleForTesting + static PositionsAppenderPageBuilder withMaxPageSize(int maxPageBytes, int maxDirectSizeInBytes, List sourceTypes, PositionsAppenderFactory positionsAppenderFactory) + { + return new PositionsAppenderPageBuilder(DEFAULT_INITIAL_EXPECTED_ENTRIES, maxPageBytes, maxDirectSizeInBytes, sourceTypes, positionsAppenderFactory); } private PositionsAppenderPageBuilder( int initialExpectedEntries, int maxPageSizeInBytes, + int maxDirectPageSizeInBytes, List types, PositionsAppenderFactory positionsAppenderFactory) { requireNonNull(types, "types is null"); requireNonNull(positionsAppenderFactory, "positionsAppenderFactory is null"); + checkArgument(maxPageSizeInBytes > 0, "maxPageSizeInBytes is negative: %s", maxPageSizeInBytes); + checkArgument(maxDirectPageSizeInBytes > 0, "maxDirectPageSizeInBytes is negative: %s", maxDirectPageSizeInBytes); + checkArgument(maxDirectPageSizeInBytes >= maxPageSizeInBytes, "maxDirectPageSizeInBytes (%s) must be >= maxPageSizeInBytes (%s)", maxDirectPageSizeInBytes, maxPageSizeInBytes); this.maxPageSizeInBytes = maxPageSizeInBytes; + this.maxDirectPageSizeInBytes = maxDirectPageSizeInBytes; channelAppenders = new UnnestingPositionsAppender[types.size()]; for (int i = 0; i < channelAppenders.length; i++) { channelAppenders[i] = positionsAppenderFactory.create(types.get(i), initialExpectedEntries, maxPageSizeInBytes); @@ -103,7 +119,24 @@ private void declarePositions(int positions) public boolean isFull() { - return declaredPositions >= MAX_POSITION_COUNT || getSizeInBytes() >= maxPageSizeInBytes; + if (declaredPositions == 0) { + return false; + } + if (declaredPositions >= MAX_POSITION_COUNT) { + return true; + } + PositionsAppenderSizeAccumulator accumulator = computeAppenderSizes(); + return accumulator.getSizeInBytes() >= maxPageSizeInBytes || accumulator.getDirectSizeInBytes() >= maxDirectPageSizeInBytes; + } + + @VisibleForTesting + PositionsAppenderSizeAccumulator computeAppenderSizes() + { + PositionsAppenderSizeAccumulator accumulator = new PositionsAppenderSizeAccumulator(); + for (UnnestingPositionsAppender positionsAppender : channelAppenders) { + positionsAppender.addSizesToAccumulator(accumulator); + } + return accumulator; } public boolean isEmpty() diff --git a/core/trino-main/src/main/java/io/trino/operator/output/PositionsAppenderSizeAccumulator.java b/core/trino-main/src/main/java/io/trino/operator/output/PositionsAppenderSizeAccumulator.java new file mode 100644 index 000000000000..f0f43953e700 --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/operator/output/PositionsAppenderSizeAccumulator.java @@ -0,0 +1,42 @@ +/* + * 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 io.trino.operator.output; + +final class PositionsAppenderSizeAccumulator +{ + private long sizeInBytes; + private long directSizeInBytes; + + public long getSizeInBytes() + { + return sizeInBytes; + } + + public long getDirectSizeInBytes() + { + return directSizeInBytes; + } + + public void accumulate(long sizeInBytes, long directSizeInBytes) + { + this.sizeInBytes += sizeInBytes; + this.directSizeInBytes += directSizeInBytes; + } + + @Override + public String toString() + { + return "PositionsAppenderSizeAccumulator{sizeInBytes=" + sizeInBytes + " directSizeInBytes=" + directSizeInBytes + "}"; + } +} diff --git a/core/trino-main/src/main/java/io/trino/operator/output/UnnestingPositionsAppender.java b/core/trino-main/src/main/java/io/trino/operator/output/UnnestingPositionsAppender.java index d818b385278f..23d2c1147861 100644 --- a/core/trino-main/src/main/java/io/trino/operator/output/UnnestingPositionsAppender.java +++ b/core/trino-main/src/main/java/io/trino/operator/output/UnnestingPositionsAppender.java @@ -211,6 +211,14 @@ public long getSizeInBytes() (rleValue != null ? rleValue.getSizeInBytes() : 0); } + void addSizesToAccumulator(PositionsAppenderSizeAccumulator accumulator) + { + long sizeInBytes = getSizeInBytes(); + // dictionary size is not included due to the expense of the calculation, so this will under-report for dictionaries + long directSizeInBytes = (rleValue == null) ? sizeInBytes : (rleValue.getSizeInBytes() * rlePositionCount); + accumulator.accumulate(sizeInBytes, directSizeInBytes); + } + private static class DictionaryIdsBuilder { private static final int INSTANCE_SIZE = instanceSize(DictionaryIdsBuilder.class); diff --git a/core/trino-main/src/test/java/io/trino/operator/output/TestPositionsAppenderPageBuilder.java b/core/trino-main/src/test/java/io/trino/operator/output/TestPositionsAppenderPageBuilder.java index 4eec501719cf..ca4cf5f5125f 100644 --- a/core/trino-main/src/test/java/io/trino/operator/output/TestPositionsAppenderPageBuilder.java +++ b/core/trino-main/src/test/java/io/trino/operator/output/TestPositionsAppenderPageBuilder.java @@ -34,8 +34,10 @@ public class TestPositionsAppenderPageBuilder public void testFullOnPositionCountLimit() { int maxPageBytes = 1024 * 1024; + int maxDirectSize = maxPageBytes * 10; PositionsAppenderPageBuilder pageBuilder = PositionsAppenderPageBuilder.withMaxPageSize( maxPageBytes, + maxDirectSize, List.of(VARCHAR), new PositionsAppenderFactory(new BlockTypeOperators())); @@ -52,6 +54,52 @@ public void testFullOnPositionCountLimit() // Append 10 more positions, crossing the threshold on position count pageBuilder.appendToOutputPartition(inputPage, positions); assertTrue(pageBuilder.isFull(), "pageBuilder should be full"); - assertEquals(rleBlock.getSizeInBytes(), pageBuilder.getSizeInBytes()); + PositionsAppenderSizeAccumulator sizeAccumulator = pageBuilder.computeAppenderSizes(); + assertEquals(rleBlock.getSizeInBytes(), sizeAccumulator.getSizeInBytes()); + assertTrue(sizeAccumulator.getDirectSizeInBytes() < maxDirectSize, "direct size should still be below threshold"); + assertEquals(sizeAccumulator.getSizeInBytes(), pageBuilder.getSizeInBytes(), "pageBuilder sizeInBytes must match sizeAccumulator value"); + } + + @Test + public void testFullOnDirectSizeInBytes() + { + int maxPageBytes = 100; + int maxDirectSize = 1000; + PositionsAppenderPageBuilder pageBuilder = PositionsAppenderPageBuilder.withMaxPageSize( + maxPageBytes, + maxDirectSize, + List.of(VARCHAR), + new PositionsAppenderFactory(new BlockTypeOperators())); + + PositionsAppenderSizeAccumulator sizeAccumulator = pageBuilder.computeAppenderSizes(); + assertEquals(0L, sizeAccumulator.getSizeInBytes()); + assertEquals(0L, sizeAccumulator.getDirectSizeInBytes()); + assertFalse(pageBuilder.isFull()); + + Block rleBlock = RunLengthEncodedBlock.create(VARCHAR, Slices.utf8Slice("test"), 10); + Page inputPage = new Page(rleBlock); + + IntArrayList positions = IntArrayList.wrap(new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); + pageBuilder.appendToOutputPartition(inputPage, positions); + // 10 positions inserted, size in bytes is still the same since we're in RLE mode but direct size is 10x + sizeAccumulator = pageBuilder.computeAppenderSizes(); + assertEquals(rleBlock.getSizeInBytes(), sizeAccumulator.getSizeInBytes()); + assertEquals(sizeAccumulator.getSizeInBytes(), pageBuilder.getSizeInBytes(), "pageBuilder sizeInBytes must match sizeAccumulator value"); + assertEquals(rleBlock.getSizeInBytes() * 10, sizeAccumulator.getDirectSizeInBytes()); + assertFalse(pageBuilder.isFull()); + + // Keep inserting until the direct size limit is reached + while (pageBuilder.computeAppenderSizes().getDirectSizeInBytes() < maxDirectSize) { + pageBuilder.appendToOutputPartition(inputPage, positions); + } + // size in bytes is unchanged + sizeAccumulator = pageBuilder.computeAppenderSizes(); + assertEquals(rleBlock.getSizeInBytes(), sizeAccumulator.getSizeInBytes(), "sizeInBytes must still report the RLE block size only"); + assertEquals(sizeAccumulator.getSizeInBytes(), pageBuilder.getSizeInBytes(), "pageBuilder sizeInBytes must match sizeAccumulator value"); + // builder reports full due to maximum size in bytes reached + assertTrue(pageBuilder.isFull()); + Page result = pageBuilder.build(); + assertEquals(120, result.getPositionCount(), "result positions should be below the 8192 maximum"); + assertTrue(result.getBlock(0) instanceof RunLengthEncodedBlock, "result block is RLE encoded"); } } From 0ea303b33d846a2b911af6d5223fc386bd482695 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Wed, 22 Nov 2023 09:33:13 +0100 Subject: [PATCH 386/587] Allow specifying custom JDK version for docker image build --- core/docker/build.sh | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/core/docker/build.sh b/core/docker/build.sh index 8d1e1f236516..a41106bf1a3e 100755 --- a/core/docker/build.sh +++ b/core/docker/build.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -set -euo pipefail +set -xeuo pipefail usage() { cat <&2 @@ -10,13 +10,21 @@ Builds the Trino Docker image -h Display help -a Build the specified comma-separated architectures, defaults to amd64,arm64,ppc64le -r Build the specified Trino release version, downloads all required artifacts +-j Build the Trino release with specified Temurin JDK release EOF } +# Retrieve the script directory. +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" +cd "${SCRIPT_DIR}" || exit 2 + +SOURCE_DIR="${SCRIPT_DIR}/../.." + ARCHITECTURES=(amd64 arm64 ppc64le) TRINO_VERSION= +JDK_VERSION=$(cat "${SOURCE_DIR}/.java-version") -while getopts ":a:h:r:" o; do +while getopts ":a:h:r:j:" o; do case "${o}" in a) IFS=, read -ra ARCHITECTURES <<< "$OPTARG" @@ -28,6 +36,9 @@ while getopts ":a:h:r:" o; do usage exit 0 ;; + j) + JDK_VERSION="${OPTARG}" + ;; *) usage exit 1 @@ -36,13 +47,11 @@ while getopts ":a:h:r:" o; do done shift $((OPTIND - 1)) -SOURCE_DIR="../.." - function temurin_jdk_link() { local JDK_VERSION="${1}" local ARCH="${2}" - versionsUrl="https://api.adoptium.net/v3/info/release_names?heap_size=normal&image_type=jdk<s=true&os=linux&page=0&page_size=20&project=jdk&release_type=ga&semver=false&sort_method=DEFAULT&sort_order=ASC&vendor=eclipse&version=%28${JDK_VERSION}%2C%5D" + versionsUrl="https://api.adoptium.net/v3/info/release_names?heap_size=normal&image_type=jdk&os=linux&page=0&page_size=20&project=jdk&release_type=ga&semver=false&sort_method=DEFAULT&sort_order=ASC&vendor=eclipse&version=%28${JDK_VERSION}%2C%5D" if ! result=$(curl -fLs "$versionsUrl" -H 'accept: application/json'); then echo >&2 "Failed to fetch release names for JDK version [${JDK_VERSION}, ) from Temurin API : $result" exit 1 @@ -70,10 +79,6 @@ function temurin_jdk_link() { esac } -# Retrieve the script directory. -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" -cd "${SCRIPT_DIR}" || exit 2 - if [ -n "$TRINO_VERSION" ]; then echo "🎣 Downloading server and client artifacts for release version ${TRINO_VERSION}" for artifactId in io.trino:trino-server:"${TRINO_VERSION}":tar.gz io.trino:trino-cli:"${TRINO_VERSION}":jar:executable; do @@ -100,7 +105,6 @@ cp -R bin "${WORK_DIR}/trino-server-${TRINO_VERSION}" cp -R default "${WORK_DIR}/" TAG_PREFIX="trino:${TRINO_VERSION}" -JDK_VERSION=$(cat "${SOURCE_DIR}/.java-version") for arch in "${ARCHITECTURES[@]}"; do echo "🫙 Building the image for $arch with JDK ${JDK_VERSION}" From 4f7782ad1708d0f7a1af4983b25e4ea3ae0a8237 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Tue, 21 Nov 2023 15:13:41 -0800 Subject: [PATCH 387/587] Fix invalid boolean expression in ScalarFunctionAdapter.adaptParameter --- .../main/java/io/trino/spi/function/ScalarFunctionAdapter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/trino-spi/src/main/java/io/trino/spi/function/ScalarFunctionAdapter.java b/core/trino-spi/src/main/java/io/trino/spi/function/ScalarFunctionAdapter.java index 27b3e9385181..fdd5e9268c90 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/function/ScalarFunctionAdapter.java +++ b/core/trino-spi/src/main/java/io/trino/spi/function/ScalarFunctionAdapter.java @@ -302,7 +302,7 @@ private static MethodHandle adaptParameter( InvocationReturnConvention returnConvention) { // For value block, cast specialized parameter to ValueBlock - if (actualArgumentConvention == VALUE_BLOCK_POSITION || actualArgumentConvention == VALUE_BLOCK_POSITION_NOT_NULL && methodHandle.type().parameterType(parameterIndex) != ValueBlock.class) { + if ((actualArgumentConvention == VALUE_BLOCK_POSITION || actualArgumentConvention == VALUE_BLOCK_POSITION_NOT_NULL) && methodHandle.type().parameterType(parameterIndex) != ValueBlock.class) { methodHandle = methodHandle.asType(methodHandle.type().changeParameterType(parameterIndex, ValueBlock.class)); } From 9626ec9bafa4f59645eaa543c648f40e8e1b48d3 Mon Sep 17 00:00:00 2001 From: Charles Morgan <44453535+charlesjmorgan@users.noreply.github.com> Date: Wed, 22 Nov 2023 14:42:51 -0800 Subject: [PATCH 388/587] Fix typos in SQL routine example docs --- docs/src/main/sphinx/routines/examples.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/main/sphinx/routines/examples.md b/docs/src/main/sphinx/routines/examples.md index 0d3ed0726153..7ed95d24d798 100644 --- a/docs/src/main/sphinx/routines/examples.md +++ b/docs/src/main/sphinx/routines/examples.md @@ -201,14 +201,14 @@ SELECT simple_case(null,null); -- null .. but really?? ## Fibonacci example This routine calculates the `n`-th value in the Fibonacci series, in which each -number is the sum of the two preceding ones. The two initial values are are set +number is the sum of the two preceding ones. The two initial values are set to `1` as the defaults for `a` and `b`. The routine uses an `IF` statement condition to return `1` for all input values of `2` or less. The `WHILE` block then starts to calculate each number in the series, starting with `a=1` and `b=1` and iterates until it reaches the `n`-th position. In each iteration is sets `a` and `b` for the preceding to values, so it can calculate the sum, and finally return it. Note that processing the routine takes longer and longer with -higher `n` values, and the result it deterministic. +higher `n` values, and the result is deterministic. ```sql FUNCTION fib(n bigint) @@ -246,7 +246,7 @@ SELECT fib(8); -- 21 ## Labels and loops -This routing uses the `top` label to name the `WHILE` block, and then controls +This routine uses the `top` label to name the `WHILE` block, and then controls the flow with conditional statements, `ITERATE`, and `LEAVE`. For the values of `a=1` and `a=2` in the first two iterations of the loop the `ITERATE` call moves the flow up to `top` before `b` is ever increased. Then `b` is increased for the From d3a832411b0cee10b4a76ac34deddea027915349 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Fri, 17 Nov 2023 17:15:24 +0100 Subject: [PATCH 389/587] Update testcontainers to 1.19.3 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index beafd96a1d2f..fd4924d7c4f4 100644 --- a/pom.xml +++ b/pom.xml @@ -289,7 +289,7 @@ org.testcontainers testcontainers-bom - 1.19.1 + 1.19.3 pom import @@ -478,7 +478,7 @@ com.github.docker-java docker-java-api - 3.3.3 + 3.3.4 From 39b04491c05b5abc6545e9113508b9c57dc29d26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Osipiuk?= Date: Tue, 21 Nov 2023 23:39:21 +0100 Subject: [PATCH 390/587] Close taskSource when all task partitions complete --- .../faulttolerant/EventDrivenFaultTolerantQueryScheduler.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java index c6d92ba5b5c5..41016b2bc76c 100644 --- a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java +++ b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java @@ -1989,6 +1989,7 @@ public void taskFinished(TaskId taskId, TaskStatus taskStatus) finalSinkOutputSelector = sinkOutputSelectorBuilder.build(); sinkOutputSelectorBuilder = null; stage.finish(); + taskSource.close(); } } From 32dcd2b789e5b61ea85c734c7dc87d1f316f360c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Osipiuk?= Date: Tue, 21 Nov 2023 23:40:16 +0100 Subject: [PATCH 391/587] Properly finish stage on noMorePartitions signal Theoretically it is possible that all task partitions for a stage complete before we get noMorePartitions signal from SplitAssigner. In such case we should execute same finish routie as we do when last task partition completes and noMorePartitions is already set. This is cleanup, not a bugfix as currently no SplitAssigner (even though it is not guaranteed by interface) would seal all of the task partitions without returning noMorePartitions flag in the same AssignmentResult object. --- ...ventDrivenFaultTolerantQueryScheduler.java | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java index 41016b2bc76c..4af6fe5d9f18 100644 --- a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java +++ b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java @@ -1747,9 +1747,7 @@ public void noMorePartitions() } if (remainingPartitions.isEmpty()) { - stage.finish(); - // TODO close exchange early - taskSource.close(); + finish(); } } @@ -1980,19 +1978,24 @@ public void taskFinished(TaskId taskId, TaskStatus taskStatus) sinkOutputSelectorBuilder.include(exchange.getId(), taskId.getPartitionId(), taskId.getAttemptId()); if (noMorePartitions && remainingPartitions.isEmpty() && !stage.getState().isDone()) { - dynamicFilterService.stageCannotScheduleMoreTasks(stage.getStageId(), 0, partitions.size()); - exchange.noMoreSinks(); - exchange.allRequiredSinksFinished(); - verify(finalSinkOutputSelector == null, "finalOutputSelector is already set"); - sinkOutputSelectorBuilder.setPartitionCount(exchange.getId(), partitions.size()); - sinkOutputSelectorBuilder.setFinal(); - finalSinkOutputSelector = sinkOutputSelectorBuilder.build(); - sinkOutputSelectorBuilder = null; - stage.finish(); - taskSource.close(); + finish(); } } + private void finish() + { + dynamicFilterService.stageCannotScheduleMoreTasks(stage.getStageId(), 0, partitions.size()); + exchange.noMoreSinks(); + exchange.allRequiredSinksFinished(); + verify(finalSinkOutputSelector == null, "finalOutputSelector is already set"); + sinkOutputSelectorBuilder.setPartitionCount(exchange.getId(), partitions.size()); + sinkOutputSelectorBuilder.setFinal(); + finalSinkOutputSelector = sinkOutputSelectorBuilder.build(); + sinkOutputSelectorBuilder = null; + stage.finish(); + taskSource.close(); + } + private void updateOutputSize(SpoolingOutputStats.Snapshot taskOutputStats) { for (int partitionId = 0; partitionId < sinkPartitioningScheme.getPartitionCount(); partitionId++) { From 44be8f8285933b2467c7203551876205bf84a7c5 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 22 Nov 2023 11:19:21 +0100 Subject: [PATCH 392/587] Code cleanup - static fields before instance fields - private where appropriate - remove special values - fix code indentation - use explicit `Duration` constructor instead of approximating `Duration.succinctDuration` --- .../io/trino/plugin/jdbc/BaseJdbcConfig.java | 27 +++++++++---------- .../trino/plugin/jdbc/CachingJdbcClient.java | 3 +-- .../jdbc/DefaultJdbcMetadataFactory.java | 12 ++++----- .../trino/plugin/jdbc/TestBaseJdbcConfig.java | 19 ++++++------- .../plugin/jdbc/TestCachingJdbcClient.java | 9 +++---- 5 files changed, 34 insertions(+), 36 deletions(-) diff --git a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/BaseJdbcConfig.java b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/BaseJdbcConfig.java index be5106dcf54a..68e363c3ff42 100644 --- a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/BaseJdbcConfig.java +++ b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/BaseJdbcConfig.java @@ -29,24 +29,23 @@ import static com.google.common.base.Strings.nullToEmpty; import static jakarta.validation.constraints.Pattern.Flag.CASE_INSENSITIVE; -import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; public class BaseJdbcConfig { - public static final String METADATA_CACHE_TTL = "metadata.cache-ttl"; - public static final String METADATA_SCHEMAS_CACHE_TTL = "metadata.schemas.cache-ttl"; - public static final String METADATA_TABLES_CACHE_TTL = "metadata.tables.cache-ttl"; - public static final String METADATA_CACHE_MAXIMUM_SIZE = "metadata.cache-maximum-size"; + private static final String METADATA_CACHE_TTL = "metadata.cache-ttl"; + private static final String METADATA_SCHEMAS_CACHE_TTL = "metadata.schemas.cache-ttl"; + private static final String METADATA_TABLES_CACHE_TTL = "metadata.tables.cache-ttl"; + private static final String METADATA_CACHE_MAXIMUM_SIZE = "metadata.cache-maximum-size"; + private static final long DEFAULT_METADATA_CACHE_SIZE = 10000; private String connectionUrl; private Set jdbcTypesMappedToVarchar = ImmutableSet.of(); - public static final Duration CACHING_DISABLED = new Duration(0, MILLISECONDS); - private Duration metadataCacheTtl = CACHING_DISABLED; + private Duration metadataCacheTtl = new Duration(0, SECONDS); private Optional schemaNamesCacheTtl = Optional.empty(); private Optional tableNamesCacheTtl = Optional.empty(); private boolean cacheMissing; - public static final long DEFAULT_METADATA_CACHE_SIZE = 10000; - private long cacheMaximumSize = DEFAULT_METADATA_CACHE_SIZE; + private Optional cacheMaximumSize = Optional.empty(); @NotNull // Some drivers match case insensitive in Driver.acceptURL @@ -134,32 +133,32 @@ public BaseJdbcConfig setCacheMissing(boolean cacheMissing) @Min(1) public long getCacheMaximumSize() { - return cacheMaximumSize; + return cacheMaximumSize.orElse(DEFAULT_METADATA_CACHE_SIZE); } @Config(METADATA_CACHE_MAXIMUM_SIZE) @ConfigDescription("Maximum number of objects stored in the metadata cache") public BaseJdbcConfig setCacheMaximumSize(long cacheMaximumSize) { - this.cacheMaximumSize = cacheMaximumSize; + this.cacheMaximumSize = Optional.of(cacheMaximumSize); return this; } @AssertTrue(message = METADATA_CACHE_TTL + " must be set to a non-zero value when " + METADATA_CACHE_MAXIMUM_SIZE + " is set") public boolean isCacheMaximumSizeConsistent() { - return !metadataCacheTtl.equals(CACHING_DISABLED) || cacheMaximumSize == BaseJdbcConfig.DEFAULT_METADATA_CACHE_SIZE; + return !metadataCacheTtl.isZero() || cacheMaximumSize.isEmpty(); } @AssertTrue(message = METADATA_SCHEMAS_CACHE_TTL + " must not be set when " + METADATA_CACHE_TTL + " is not set") public boolean isSchemaNamesCacheTtlConsistent() { - return !metadataCacheTtl.equals(CACHING_DISABLED) || schemaNamesCacheTtl.isEmpty(); + return !metadataCacheTtl.isZero() || schemaNamesCacheTtl.isEmpty(); } @AssertTrue(message = METADATA_TABLES_CACHE_TTL + " must not be set when " + METADATA_CACHE_TTL + " is not set") public boolean isTableNamesCacheTtlConsistent() { - return !metadataCacheTtl.equals(CACHING_DISABLED) || tableNamesCacheTtl.isEmpty(); + return !metadataCacheTtl.isZero() || tableNamesCacheTtl.isEmpty(); } } diff --git a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/CachingJdbcClient.java b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/CachingJdbcClient.java index 232632c7dd5b..3f20ce049e76 100644 --- a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/CachingJdbcClient.java +++ b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/CachingJdbcClient.java @@ -66,7 +66,6 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableMap.toImmutableMap; import static io.trino.cache.CacheUtils.invalidateAllIf; -import static io.trino.plugin.jdbc.BaseJdbcConfig.CACHING_DISABLED; import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -142,7 +141,7 @@ public CachingJdbcClient( this.cacheMissing = cacheMissing; this.identityMapping = requireNonNull(identityMapping, "identityMapping is null"); - long cacheSize = metadataCachingTtl.equals(CACHING_DISABLED) + long cacheSize = metadataCachingTtl.isZero() // Disables the cache entirely ? 0 : cacheMaximumSize; diff --git a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadataFactory.java b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadataFactory.java index 76b1a91dc9dc..dce6420786cb 100644 --- a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadataFactory.java +++ b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadataFactory.java @@ -41,12 +41,12 @@ public JdbcMetadata create(JdbcTransactionHandle transaction) // Session stays the same per transaction, therefore session properties don't need to // be a part of cache keys in CachingJdbcClient. return create(new CachingJdbcClient( - jdbcClient, - Set.of(), - new SingletonIdentityCacheMapping(), - new Duration(1, DAYS), - true, - Integer.MAX_VALUE)); + jdbcClient, + Set.of(), + new SingletonIdentityCacheMapping(), + new Duration(1, DAYS), + true, + Integer.MAX_VALUE)); } protected JdbcMetadata create(JdbcClient transactionCachingJdbcClient) diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestBaseJdbcConfig.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestBaseJdbcConfig.java index 3f5d200bf650..55f9bc2fba3b 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestBaseJdbcConfig.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestBaseJdbcConfig.java @@ -26,15 +26,13 @@ import static io.airlift.configuration.testing.ConfigAssertions.recordDefaults; import static io.airlift.testing.ValidationAssertions.assertFailsValidation; import static io.airlift.testing.ValidationAssertions.assertValidates; -import static java.util.concurrent.TimeUnit.MINUTES; +import static io.airlift.units.Duration.ZERO; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; public class TestBaseJdbcConfig { - private static final Duration ZERO = Duration.succinctDuration(0, MINUTES); - @Test public void testDefaults() { @@ -100,20 +98,23 @@ public void testCacheConfigValidation() .setConnectionUrl("jdbc:h2:mem:config") .setMetadataCacheTtl(new Duration(1, SECONDS))); - assertFailsValidation(new BaseJdbcConfig() - .setCacheMaximumSize(5000), + assertFailsValidation( + new BaseJdbcConfig() + .setCacheMaximumSize(5000), "cacheMaximumSizeConsistent", "metadata.cache-ttl must be set to a non-zero value when metadata.cache-maximum-size is set", AssertTrue.class); - assertFailsValidation(new BaseJdbcConfig() - .setSchemaNamesCacheTtl(new Duration(1, SECONDS)), + assertFailsValidation( + new BaseJdbcConfig() + .setSchemaNamesCacheTtl(new Duration(1, SECONDS)), "schemaNamesCacheTtlConsistent", "metadata.schemas.cache-ttl must not be set when metadata.cache-ttl is not set", AssertTrue.class); - assertFailsValidation(new BaseJdbcConfig() - .setTableNamesCacheTtl(new Duration(1, SECONDS)), + assertFailsValidation( + new BaseJdbcConfig() + .setTableNamesCacheTtl(new Duration(1, SECONDS)), "tableNamesCacheTtlConsistent", "metadata.tables.cache-ttl must not be set when metadata.cache-ttl is not set", AssertTrue.class); diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java index 75b628f241d2..8f336e4b25ed 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java @@ -61,6 +61,7 @@ import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.collect.MoreCollectors.onlyElement; import static io.airlift.concurrent.Threads.daemonThreadsNamed; +import static io.airlift.units.Duration.ZERO; import static io.trino.spi.session.PropertyMetadata.stringProperty; import static io.trino.spi.testing.InterfaceTestUtils.assertAllMethodsOverridden; import static io.trino.spi.type.IntegerType.INTEGER; @@ -71,7 +72,6 @@ import static java.util.Objects.requireNonNull; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.TimeUnit.DAYS; -import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.function.Function.identity; import static org.assertj.core.api.Assertions.assertThat; @@ -81,8 +81,7 @@ @TestInstance(PER_METHOD) public class TestCachingJdbcClient { - private static final Duration FOREVER = Duration.succinctDuration(1, DAYS); - private static final Duration ZERO = Duration.succinctDuration(0, MILLISECONDS); + private static final Duration FOREVER = new Duration(1, DAYS); private static final ImmutableList> PROPERTY_METADATA = ImmutableList.of( stringProperty( @@ -943,8 +942,8 @@ public void testSpecificSchemaAndTableCaches() { CachingJdbcClient cachingJdbcClient = createCachingJdbcClient( FOREVER, - Duration.succinctDuration(3, SECONDS), - Duration.succinctDuration(2, SECONDS), + new Duration(3, SECONDS), + new Duration(2, SECONDS), false, // decreased ttl for schema and table names mostly makes sense with cacheMissing == false 10000); String secondSchema = schema + "_two"; From dbf9d4bb30b547eadcb25e4d87c982f15f6e32ee Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 22 Nov 2023 11:25:44 +0100 Subject: [PATCH 393/587] Remove CachingJdbcClient overload Inline in usages to simplify CachingJdbcClient construction. --- .../trino/plugin/jdbc/CachingJdbcClient.java | 18 ------------------ .../jdbc/DefaultJdbcMetadataFactory.java | 2 ++ .../plugin/jdbc/TestCachingJdbcClient.java | 14 +++++++++++++- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/CachingJdbcClient.java b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/CachingJdbcClient.java index 3f20ce049e76..e0f4d3d72f25 100644 --- a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/CachingJdbcClient.java +++ b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/CachingJdbcClient.java @@ -106,24 +106,6 @@ public CachingJdbcClient( config.getCacheMaximumSize()); } - public CachingJdbcClient( - JdbcClient delegate, - Set sessionPropertiesProviders, - IdentityCacheMapping identityMapping, - Duration metadataCachingTtl, - boolean cacheMissing, - long cacheMaximumSize) - { - this(delegate, - sessionPropertiesProviders, - identityMapping, - metadataCachingTtl, - metadataCachingTtl, - metadataCachingTtl, - cacheMissing, - cacheMaximumSize); - } - public CachingJdbcClient( JdbcClient delegate, Set sessionPropertiesProviders, diff --git a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadataFactory.java b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadataFactory.java index dce6420786cb..3dbafaa7cc5e 100644 --- a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadataFactory.java +++ b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadataFactory.java @@ -45,6 +45,8 @@ public JdbcMetadata create(JdbcTransactionHandle transaction) Set.of(), new SingletonIdentityCacheMapping(), new Duration(1, DAYS), + new Duration(1, DAYS), + new Duration(1, DAYS), true, Integer.MAX_VALUE)); } diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java index 8f336e4b25ed..d0880ab5957e 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java @@ -736,7 +736,15 @@ public TableStatistics getTableStatistics(ConnectorSession session, JdbcTableHan return NON_EMPTY_STATS; } }; - return new CachingJdbcClient(statsAwareJdbcClient, SESSION_PROPERTIES_PROVIDERS, new SingletonIdentityCacheMapping(), duration, cacheMissing, cacheMaximumSize); + return new CachingJdbcClient( + statsAwareJdbcClient, + SESSION_PROPERTIES_PROVIDERS, + new SingletonIdentityCacheMapping(), + duration, + duration, + duration, + cacheMissing, + cacheMaximumSize); } @Test @@ -787,6 +795,8 @@ public void testDifferentIdentityKeys() .setUserCredentialName("user") .setPasswordCredentialName("password")), FOREVER, + FOREVER, + FOREVER, true, 10000); ConnectorSession alice = createUserSession("alice"); @@ -906,6 +916,8 @@ public Optional getTableHandle(ConnectorSession session, Schema new SingletonIdentityCacheMapping(), // ttl is 0, cache is disabled new Duration(0, DAYS), + new Duration(0, DAYS), + new Duration(0, DAYS), true, 10); From 03c566d20d1453770ed94f72f0820dc1a57d9042 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 22 Nov 2023 11:53:04 +0100 Subject: [PATCH 394/587] By default test defaults in TestCachingJdbcClient Previously the test used cacheMissing=true by default, despite this being false by default in the config. --- .../trino/plugin/jdbc/TestCachingJdbcClient.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java index d0880ab5957e..e4e5ee05f140 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java @@ -111,7 +111,7 @@ public void setUp() throws Exception { database = new TestingDatabase(); - cachingJdbcClient = createCachingJdbcClient(true, 10000); + cachingJdbcClient = createCachingJdbcClient(new BaseJdbcConfig().isCacheMissing(), new BaseJdbcConfig().getCacheMaximumSize()); jdbcClient = database.getJdbcClient(); schema = jdbcClient.getSchemaNames(SESSION).iterator().next(); executor = newCachedThreadPool(daemonThreadsNamed("TestCachingJdbcClient-%s")); @@ -219,6 +219,7 @@ public void testTableHandleCached() public void testTableHandleOfQueryCached() throws Exception { + CachingJdbcClient cachingJdbcClient = cachingStatisticsAwareJdbcClient(FOREVER, new BaseJdbcConfig().isCacheMissing(), new BaseJdbcConfig().getCacheMaximumSize()); SchemaTableName phantomTable = new SchemaTableName(schema, "phantom_table"); createTable(phantomTable); @@ -378,6 +379,7 @@ private void assertTableHandlesByNameCacheIsInvalidated(JdbcTableHandle table) @Test public void testEmptyTableHandleIsCachedWhenCacheMissingIsTrue() { + CachingJdbcClient cachingJdbcClient = createCachingJdbcClient(true, 10000); SchemaTableName phantomTable = new SchemaTableName(schema, "phantom_table"); assertThat(cachingJdbcClient.getTableHandle(SESSION, phantomTable)).isEmpty(); @@ -592,7 +594,7 @@ public void testColumnsNotCachedWhenCacheDisabled() @Test public void testGetTableStatistics() { - CachingJdbcClient cachingJdbcClient = cachingStatisticsAwareJdbcClient(FOREVER, true, 10000); + CachingJdbcClient cachingJdbcClient = cachingStatisticsAwareJdbcClient(FOREVER, new BaseJdbcConfig().isCacheMissing(), new BaseJdbcConfig().getCacheMaximumSize()); ConnectorSession session = createSession("first"); JdbcTableHandle first = createTable(new SchemaTableName(schema, "first")); @@ -640,7 +642,7 @@ public void testGetTableStatistics() @Test public void testCacheGetTableStatisticsWithQueryRelationHandle() { - CachingJdbcClient cachingJdbcClient = cachingStatisticsAwareJdbcClient(FOREVER, true, 10000); + CachingJdbcClient cachingJdbcClient = cachingStatisticsAwareJdbcClient(FOREVER, new BaseJdbcConfig().isCacheMissing(), new BaseJdbcConfig().getCacheMaximumSize()); ConnectorSession session = createSession("some test session name"); JdbcTableHandle first = createTable(new SchemaTableName(schema, "first")); @@ -687,7 +689,7 @@ public void testCacheGetTableStatisticsWithQueryRelationHandle() @Test public void testTruncateTable() { - CachingJdbcClient cachingJdbcClient = cachingStatisticsAwareJdbcClient(FOREVER, true, 10000); + CachingJdbcClient cachingJdbcClient = cachingStatisticsAwareJdbcClient(FOREVER, new BaseJdbcConfig().isCacheMissing(), new BaseJdbcConfig().getCacheMaximumSize()); ConnectorSession session = createSession("table"); JdbcTableHandle table = createTable(new SchemaTableName(schema, "table")); @@ -797,7 +799,7 @@ public void testDifferentIdentityKeys() FOREVER, FOREVER, FOREVER, - true, + new BaseJdbcConfig().isCacheMissing(), 10000); ConnectorSession alice = createUserSession("alice"); ConnectorSession bob = createUserSession("bob"); @@ -821,7 +823,7 @@ public void testDifferentIdentityKeys() @Test public void testFlushCache() { - CachingJdbcClient cachingJdbcClient = cachingStatisticsAwareJdbcClient(FOREVER, true, 10000); + CachingJdbcClient cachingJdbcClient = cachingStatisticsAwareJdbcClient(FOREVER, new BaseJdbcConfig().isCacheMissing(), new BaseJdbcConfig().getCacheMaximumSize()); ConnectorSession session = createSession("asession"); JdbcTableHandle first = createTable(new SchemaTableName(schema, "atable")); @@ -858,7 +860,7 @@ public void testFlushCache() @Timeout(60) public void testConcurrentSchemaCreateAndDrop() { - CachingJdbcClient cachingJdbcClient = cachingStatisticsAwareJdbcClient(FOREVER, true, 10000); + CachingJdbcClient cachingJdbcClient = cachingStatisticsAwareJdbcClient(FOREVER, new BaseJdbcConfig().isCacheMissing(), new BaseJdbcConfig().getCacheMaximumSize()); ConnectorSession session = createSession("asession"); List> futures = new ArrayList<>(); for (int i = 0; i < 5; i++) { From ae57465335a6e95010a134097976942a9731facd Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 22 Nov 2023 11:48:43 +0100 Subject: [PATCH 395/587] Construct CachingJdbcClient using builder and config in test Using builder avoids maintaining overloads of various "create some CachingJdbcClient" methods. Using config avoids passing 3 Duration parameters positionally to the constructor. The config is equivalent of named parameters. This also moves helper methods below tests. This is code cleanup and preparatory change before adding yet another Duration parameter. --- .../plugin/jdbc/TestCachingJdbcClient.java | 230 ++++++++++-------- 1 file changed, 133 insertions(+), 97 deletions(-) diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java index e4e5ee05f140..a79c885432dc 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java @@ -111,43 +111,12 @@ public void setUp() throws Exception { database = new TestingDatabase(); - cachingJdbcClient = createCachingJdbcClient(new BaseJdbcConfig().isCacheMissing(), new BaseJdbcConfig().getCacheMaximumSize()); + cachingJdbcClient = createCachingJdbcClient(); jdbcClient = database.getJdbcClient(); schema = jdbcClient.getSchemaNames(SESSION).iterator().next(); executor = newCachedThreadPool(daemonThreadsNamed("TestCachingJdbcClient-%s")); } - private CachingJdbcClient createCachingJdbcClient( - Duration cacheTtl, - boolean cacheMissing, - long cacheMaximumSize) - { - return createCachingJdbcClient(cacheTtl, cacheTtl, cacheTtl, cacheMissing, cacheMaximumSize); - } - - private CachingJdbcClient createCachingJdbcClient( - Duration cacheTtl, - Duration schemasCacheTtl, - Duration tablesCacheTtl, - boolean cacheMissing, - long cacheMaximumSize) - { - return new CachingJdbcClient( - database.getJdbcClient(), - SESSION_PROPERTIES_PROVIDERS, - new SingletonIdentityCacheMapping(), - cacheTtl, - schemasCacheTtl, - tablesCacheTtl, - cacheMissing, - cacheMaximumSize); - } - - private CachingJdbcClient createCachingJdbcClient(boolean cacheMissing, long cacheMaximumSize) - { - return createCachingJdbcClient(FOREVER, cacheMissing, cacheMaximumSize); - } - @AfterEach public void tearDown() throws Exception @@ -219,7 +188,9 @@ public void testTableHandleCached() public void testTableHandleOfQueryCached() throws Exception { - CachingJdbcClient cachingJdbcClient = cachingStatisticsAwareJdbcClient(FOREVER, new BaseJdbcConfig().isCacheMissing(), new BaseJdbcConfig().getCacheMaximumSize()); + CachingJdbcClient cachingJdbcClient = cachingClientBuilder() + .delegate(jdbcClientWithTableStats()) + .build(); SchemaTableName phantomTable = new SchemaTableName(schema, "phantom_table"); createTable(phantomTable); @@ -379,7 +350,9 @@ private void assertTableHandlesByNameCacheIsInvalidated(JdbcTableHandle table) @Test public void testEmptyTableHandleIsCachedWhenCacheMissingIsTrue() { - CachingJdbcClient cachingJdbcClient = createCachingJdbcClient(true, 10000); + CachingJdbcClient cachingJdbcClient = cachingClientBuilder() + .config(enableCache().setCacheMissing(true)) + .build(); SchemaTableName phantomTable = new SchemaTableName(schema, "phantom_table"); assertThat(cachingJdbcClient.getTableHandle(SESSION, phantomTable)).isEmpty(); @@ -392,7 +365,9 @@ public void testEmptyTableHandleIsCachedWhenCacheMissingIsTrue() @Test public void testEmptyTableHandleNotCachedWhenCacheMissingIsFalse() { - CachingJdbcClient cachingJdbcClient = createCachingJdbcClient(false, 10000); + CachingJdbcClient cachingJdbcClient = cachingClientBuilder() + .config(enableCache().setCacheMissing(false)) + .build(); SchemaTableName phantomTable = new SchemaTableName(schema, "phantom_table"); assertThat(cachingJdbcClient.getTableHandle(SESSION, phantomTable)).isEmpty(); @@ -557,7 +532,9 @@ public void testColumnsCacheInvalidationOnTableDrop() @Test public void testColumnsNotCachedWhenCacheDisabled() { - CachingJdbcClient cachingJdbcClient = createCachingJdbcClient(ZERO, true, 10000); + CachingJdbcClient cachingJdbcClient = cachingClientBuilder() + .config(new BaseJdbcConfig().setMetadataCacheTtl(ZERO)) + .build(); ConnectorSession firstSession = createSession("first"); ConnectorSession secondSession = createSession("second"); @@ -594,7 +571,9 @@ public void testColumnsNotCachedWhenCacheDisabled() @Test public void testGetTableStatistics() { - CachingJdbcClient cachingJdbcClient = cachingStatisticsAwareJdbcClient(FOREVER, new BaseJdbcConfig().isCacheMissing(), new BaseJdbcConfig().getCacheMaximumSize()); + CachingJdbcClient cachingJdbcClient = cachingClientBuilder() + .delegate(jdbcClientWithTableStats()) + .build(); ConnectorSession session = createSession("first"); JdbcTableHandle first = createTable(new SchemaTableName(schema, "first")); @@ -642,7 +621,9 @@ public void testGetTableStatistics() @Test public void testCacheGetTableStatisticsWithQueryRelationHandle() { - CachingJdbcClient cachingJdbcClient = cachingStatisticsAwareJdbcClient(FOREVER, new BaseJdbcConfig().isCacheMissing(), new BaseJdbcConfig().getCacheMaximumSize()); + CachingJdbcClient cachingJdbcClient = cachingClientBuilder() + .delegate(jdbcClientWithTableStats()) + .build(); ConnectorSession session = createSession("some test session name"); JdbcTableHandle first = createTable(new SchemaTableName(schema, "first")); @@ -689,7 +670,9 @@ public void testCacheGetTableStatisticsWithQueryRelationHandle() @Test public void testTruncateTable() { - CachingJdbcClient cachingJdbcClient = cachingStatisticsAwareJdbcClient(FOREVER, new BaseJdbcConfig().isCacheMissing(), new BaseJdbcConfig().getCacheMaximumSize()); + CachingJdbcClient cachingJdbcClient = cachingClientBuilder() + .delegate(jdbcClientWithTableStats()) + .build(); ConnectorSession session = createSession("table"); JdbcTableHandle table = createTable(new SchemaTableName(schema, "table")); @@ -721,38 +704,12 @@ public void testTruncateTable() this.jdbcClient.dropTable(SESSION, table); } - private CachingJdbcClient cachingStatisticsAwareJdbcClient(Duration duration, boolean cacheMissing, long cacheMaximumSize) - { - JdbcClient jdbcClient = database.getJdbcClient(); - JdbcClient statsAwareJdbcClient = new ForwardingJdbcClient() - { - @Override - protected JdbcClient delegate() - { - return jdbcClient; - } - - @Override - public TableStatistics getTableStatistics(ConnectorSession session, JdbcTableHandle handle) - { - return NON_EMPTY_STATS; - } - }; - return new CachingJdbcClient( - statsAwareJdbcClient, - SESSION_PROPERTIES_PROVIDERS, - new SingletonIdentityCacheMapping(), - duration, - duration, - duration, - cacheMissing, - cacheMaximumSize); - } - @Test public void testCacheEmptyStatistics() { - CachingJdbcClient cachingJdbcClient = createCachingJdbcClient(FOREVER, true, 10000); + CachingJdbcClient cachingJdbcClient = cachingClientBuilder() + .config(enableCache().setCacheMissing(true)) + .build(); ConnectorSession session = createSession("table"); JdbcTableHandle table = createTable(new SchemaTableName(schema, "table")); @@ -771,7 +728,9 @@ public void testCacheEmptyStatistics() @Test public void testGetTableStatisticsDoNotCacheEmptyWhenCachingMissingIsDisabled() { - CachingJdbcClient cachingJdbcClient = createCachingJdbcClient(FOREVER, false, 10000); + CachingJdbcClient cachingJdbcClient = cachingClientBuilder() + .config(enableCache().setCacheMissing(false)) + .build(); ConnectorSession session = createSession("table"); JdbcTableHandle table = createTable(new SchemaTableName(schema, "table")); @@ -790,17 +749,11 @@ public void testGetTableStatisticsDoNotCacheEmptyWhenCachingMissingIsDisabled() @Test public void testDifferentIdentityKeys() { - CachingJdbcClient cachingJdbcClient = new CachingJdbcClient( - database.getJdbcClient(), - SESSION_PROPERTIES_PROVIDERS, - new ExtraCredentialsBasedIdentityCacheMapping(new ExtraCredentialConfig() + CachingJdbcClient cachingJdbcClient = cachingClientBuilder() + .identityCacheMapping(new ExtraCredentialsBasedIdentityCacheMapping(new ExtraCredentialConfig() .setUserCredentialName("user") - .setPasswordCredentialName("password")), - FOREVER, - FOREVER, - FOREVER, - new BaseJdbcConfig().isCacheMissing(), - 10000); + .setPasswordCredentialName("password"))) + .build(); ConnectorSession alice = createUserSession("alice"); ConnectorSession bob = createUserSession("bob"); @@ -823,7 +776,9 @@ public void testDifferentIdentityKeys() @Test public void testFlushCache() { - CachingJdbcClient cachingJdbcClient = cachingStatisticsAwareJdbcClient(FOREVER, new BaseJdbcConfig().isCacheMissing(), new BaseJdbcConfig().getCacheMaximumSize()); + CachingJdbcClient cachingJdbcClient = cachingClientBuilder() + .delegate(jdbcClientWithTableStats()) + .build(); ConnectorSession session = createSession("asession"); JdbcTableHandle first = createTable(new SchemaTableName(schema, "atable")); @@ -860,7 +815,9 @@ public void testFlushCache() @Timeout(60) public void testConcurrentSchemaCreateAndDrop() { - CachingJdbcClient cachingJdbcClient = cachingStatisticsAwareJdbcClient(FOREVER, new BaseJdbcConfig().isCacheMissing(), new BaseJdbcConfig().getCacheMaximumSize()); + CachingJdbcClient cachingJdbcClient = cachingClientBuilder() + .delegate(jdbcClientWithTableStats()) + .build(); ConnectorSession session = createSession("asession"); List> futures = new ArrayList<>(); for (int i = 0; i < 5; i++) { @@ -886,8 +843,8 @@ public void testLoadFailureNotSharedWhenDisabled() AtomicBoolean first = new AtomicBoolean(true); CyclicBarrier barrier = new CyclicBarrier(2); - CachingJdbcClient cachingJdbcClient = new CachingJdbcClient( - new ForwardingJdbcClient() + CachingJdbcClient cachingJdbcClient = cachingClientBuilder() + .delegate(new ForwardingJdbcClient() { private final JdbcClient delegate = database.getJdbcClient(); @@ -913,15 +870,10 @@ public Optional getTableHandle(ConnectorSession session, Schema } return super.getTableHandle(session, schemaTableName); } - }, - SESSION_PROPERTIES_PROVIDERS, - new SingletonIdentityCacheMapping(), + }) // ttl is 0, cache is disabled - new Duration(0, DAYS), - new Duration(0, DAYS), - new Duration(0, DAYS), - true, - 10); + .config(new BaseJdbcConfig().setMetadataCacheTtl(ZERO)) + .build(); SchemaTableName tableName = new SchemaTableName(schema, "test_load_failure_not_shared"); createTable(tableName); @@ -954,12 +906,14 @@ public Optional getTableHandle(ConnectorSession session, Schema @Test public void testSpecificSchemaAndTableCaches() { - CachingJdbcClient cachingJdbcClient = createCachingJdbcClient( - FOREVER, - new Duration(3, SECONDS), - new Duration(2, SECONDS), - false, // decreased ttl for schema and table names mostly makes sense with cacheMissing == false - 10000); + CachingJdbcClient cachingJdbcClient = cachingClientBuilder() + .config(new BaseJdbcConfig() + .setMetadataCacheTtl(FOREVER) + .setSchemaNamesCacheTtl(new Duration(3, SECONDS)) + .setTableNamesCacheTtl(new Duration(2, SECONDS)) + // decreased ttl for schema and table names mostly makes sense with cacheMissing == false + .setCacheMissing(false)) + .build(); String secondSchema = schema + "_two"; SchemaTableName firstName = new SchemaTableName(schema, "first_table"); SchemaTableName secondName = new SchemaTableName(secondSchema, "second_table"); @@ -1101,6 +1055,88 @@ public void testEverythingImplemented() assertAllMethodsOverridden(JdbcClient.class, CachingJdbcClient.class); } + private CachingJdbcClient createCachingJdbcClient() + { + return cachingClientBuilder().build(); + } + + private CachingJdbcClientBuilder cachingClientBuilder() + { + return new CachingJdbcClientBuilder() + .delegate(database.getJdbcClient()) + .sessionPropertiesProviders(SESSION_PROPERTIES_PROVIDERS) + .identityCacheMapping(new SingletonIdentityCacheMapping()) + .config(enableCache()); + } + + private static class CachingJdbcClientBuilder + { + private JdbcClient delegate; + private Set sessionPropertiesProviders; + private IdentityCacheMapping identityCacheMapping; + private BaseJdbcConfig config; + + public CachingJdbcClientBuilder delegate(JdbcClient delegate) + { + this.delegate = delegate; + return this; + } + + public CachingJdbcClientBuilder sessionPropertiesProviders(Set sessionPropertiesProviders) + { + this.sessionPropertiesProviders = sessionPropertiesProviders; + return this; + } + + public CachingJdbcClientBuilder identityCacheMapping(IdentityCacheMapping identityCacheMapping) + { + this.identityCacheMapping = identityCacheMapping; + return this; + } + + public CachingJdbcClientBuilder config(BaseJdbcConfig config) + { + this.config = config; + return this; + } + + CachingJdbcClient build() + { + return new CachingJdbcClient( + delegate, + sessionPropertiesProviders, + identityCacheMapping, + config.getMetadataCacheTtl(), + config.getSchemaNamesCacheTtl(), + config.getTableNamesCacheTtl(), + config.isCacheMissing(), + config.getCacheMaximumSize()); + } + } + + private JdbcClient jdbcClientWithTableStats() + { + return new ForwardingJdbcClient() + { + @Override + protected JdbcClient delegate() + { + return database.getJdbcClient(); + } + + @Override + public TableStatistics getTableStatistics(ConnectorSession session, JdbcTableHandle handle) + { + return NON_EMPTY_STATS; + } + }; + } + + private static BaseJdbcConfig enableCache() + { + return new BaseJdbcConfig().setMetadataCacheTtl(FOREVER); + } + private static SingleJdbcCacheStatsAssertions assertSchemaNamesCache(CachingJdbcClient client) { return assertCacheStats(client, CachingJdbcCache.SCHEMA_NAMES_CACHE); From 54948d994f3b4e402fc357b02835f2541d27f8b7 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 22 Nov 2023 12:12:29 +0100 Subject: [PATCH 396/587] Remove default CachingJdbcClient in test Many TestCachingJdbcClient's tests construct their own instance, so providing a default instance in the `@Before` creates duplicated state and confusion. --- .../plugin/jdbc/TestCachingJdbcClient.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java index a79c885432dc..607356a6a68c 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java @@ -101,7 +101,6 @@ public class TestCachingJdbcClient .build(); private TestingDatabase database; - private CachingJdbcClient cachingJdbcClient; private JdbcClient jdbcClient; private String schema; private ExecutorService executor; @@ -111,7 +110,6 @@ public void setUp() throws Exception { database = new TestingDatabase(); - cachingJdbcClient = createCachingJdbcClient(); jdbcClient = database.getJdbcClient(); schema = jdbcClient.getSchemaNames(SESSION).iterator().next(); executor = newCachedThreadPool(daemonThreadsNamed("TestCachingJdbcClient-%s")); @@ -130,6 +128,7 @@ public void tearDown() @Test public void testSchemaNamesCached() { + CachingJdbcClient cachingJdbcClient = createCachingJdbcClient(); String phantomSchema = "phantom_schema"; jdbcClient.createSchema(SESSION, phantomSchema); @@ -152,6 +151,7 @@ public void testSchemaNamesCached() @Test public void testTableNamesCached() { + CachingJdbcClient cachingJdbcClient = createCachingJdbcClient(); SchemaTableName phantomTable = new SchemaTableName(schema, "phantom_table"); createTable(phantomTable); @@ -174,6 +174,7 @@ public void testTableNamesCached() @Test public void testTableHandleCached() { + CachingJdbcClient cachingJdbcClient = createCachingJdbcClient(); SchemaTableName phantomTable = new SchemaTableName(schema, "phantom_table"); createTable(phantomTable); @@ -289,6 +290,7 @@ public void testTableHandleOfQueryCached() public void testProcedureHandleCached() throws Exception { + CachingJdbcClient cachingJdbcClient = createCachingJdbcClient(); SchemaTableName phantomTable = new SchemaTableName(schema, "phantom_table"); createTable(phantomTable); @@ -318,24 +320,25 @@ public void testProcedureHandleCached() @Test public void testTableHandleInvalidatedOnColumnsModifications() { + CachingJdbcClient cachingJdbcClient = createCachingJdbcClient(); JdbcTableHandle table = createTable(new SchemaTableName(schema, "a_table")); JdbcColumnHandle existingColumn = addColumn(table, "a_column"); // warm-up cache - assertTableHandlesByNameCacheIsInvalidated(table); + assertTableHandlesByNameCacheIsInvalidated(cachingJdbcClient, table); JdbcColumnHandle newColumn = addColumn(cachingJdbcClient, table, "new_column"); - assertTableHandlesByNameCacheIsInvalidated(table); + assertTableHandlesByNameCacheIsInvalidated(cachingJdbcClient, table); cachingJdbcClient.setColumnComment(SESSION, table, newColumn, Optional.empty()); - assertTableHandlesByNameCacheIsInvalidated(table); + assertTableHandlesByNameCacheIsInvalidated(cachingJdbcClient, table); cachingJdbcClient.renameColumn(SESSION, table, newColumn, "new_column_name"); - assertTableHandlesByNameCacheIsInvalidated(table); + assertTableHandlesByNameCacheIsInvalidated(cachingJdbcClient, table); cachingJdbcClient.dropColumn(SESSION, table, existingColumn); - assertTableHandlesByNameCacheIsInvalidated(table); + assertTableHandlesByNameCacheIsInvalidated(cachingJdbcClient, table); dropTable(table); } - private void assertTableHandlesByNameCacheIsInvalidated(JdbcTableHandle table) + private void assertTableHandlesByNameCacheIsInvalidated(CachingJdbcClient cachingJdbcClient, JdbcTableHandle table) { SchemaTableName tableName = table.asPlainTable().getSchemaTableName(); @@ -420,6 +423,7 @@ private void dropTable(SchemaTableName phantomTable) @Test public void testColumnsCached() { + CachingJdbcClient cachingJdbcClient = createCachingJdbcClient(); JdbcTableHandle table = getAnyTable(schema); JdbcColumnHandle phantomColumn = addColumn(table); @@ -441,6 +445,7 @@ public void testColumnsCached() @Test public void testColumnsCachedPerSession() { + CachingJdbcClient cachingJdbcClient = createCachingJdbcClient(); ConnectorSession firstSession = createSession("first"); ConnectorSession secondSession = createSession("second"); JdbcTableHandle table = getAnyTable(schema); @@ -481,6 +486,7 @@ public void testColumnsCachedPerSession() @Test public void testColumnsCacheInvalidationOnTableDrop() { + CachingJdbcClient cachingJdbcClient = createCachingJdbcClient(); ConnectorSession firstSession = createSession("first"); ConnectorSession secondSession = createSession("second"); JdbcTableHandle firstTable = createTable(new SchemaTableName(schema, "first_table")); From 4bac759b5de438f9e417d2be0b5a778d700cec0e Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 22 Nov 2023 11:31:43 +0100 Subject: [PATCH 397/587] Remove unnecessary logic from CachingJdbcClient This logically reverts commit b25c90b44fc47daa3522111ccce2c9f89d76ce4b. The class uses `EvictableCacheBuilder` and the `io.trino.cache.EvictableCacheBuilder#cacheDisabled` considers the cache to be disabled when TTL is 0 or cache size is 0, so no need to set cache size to 0 if TTL is known to be 0. --- .../trino/plugin/jdbc/CachingJdbcClient.java | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/CachingJdbcClient.java b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/CachingJdbcClient.java index e0f4d3d72f25..1444fbf07889 100644 --- a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/CachingJdbcClient.java +++ b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/CachingJdbcClient.java @@ -123,18 +123,13 @@ public CachingJdbcClient( this.cacheMissing = cacheMissing; this.identityMapping = requireNonNull(identityMapping, "identityMapping is null"); - long cacheSize = metadataCachingTtl.isZero() - // Disables the cache entirely - ? 0 - : cacheMaximumSize; - - schemaNamesCache = buildCache(cacheSize, schemaNamesCachingTtl); - tableNamesCache = buildCache(cacheSize, tableNamesCachingTtl); - tableHandlesByNameCache = buildCache(cacheSize, metadataCachingTtl); - tableHandlesByQueryCache = buildCache(cacheSize, metadataCachingTtl); - procedureHandlesByQueryCache = buildCache(cacheSize, metadataCachingTtl); - columnsCache = buildCache(cacheSize, metadataCachingTtl); - statisticsCache = buildCache(cacheSize, metadataCachingTtl); + schemaNamesCache = buildCache(cacheMaximumSize, schemaNamesCachingTtl); + tableNamesCache = buildCache(cacheMaximumSize, tableNamesCachingTtl); + tableHandlesByNameCache = buildCache(cacheMaximumSize, metadataCachingTtl); + tableHandlesByQueryCache = buildCache(cacheMaximumSize, metadataCachingTtl); + procedureHandlesByQueryCache = buildCache(cacheMaximumSize, metadataCachingTtl); + columnsCache = buildCache(cacheMaximumSize, metadataCachingTtl); + statisticsCache = buildCache(cacheMaximumSize, metadataCachingTtl); } private static Cache buildCache(long cacheSize, Duration cachingTtl) From 288d4e68af79cbf089304a687bd481d9c13774d7 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 22 Nov 2023 11:19:17 +0100 Subject: [PATCH 398/587] Allow caching stats in JDBC but not metadata Apply `CachingHiveMetastore` capabilities in JDBC. Allow turning on statistics caching without having to use caching for all of the metadata. In common case caching statistics has much fewer side effects than caching other stuff. --- .../io/trino/plugin/jdbc/BaseJdbcConfig.java | 22 ++++++++++- .../trino/plugin/jdbc/CachingJdbcClient.java | 4 +- .../jdbc/DefaultJdbcMetadataFactory.java | 1 + .../trino/plugin/jdbc/TestBaseJdbcConfig.java | 10 ++++- .../plugin/jdbc/TestCachingJdbcClient.java | 39 +++++++++++++++++++ 5 files changed, 72 insertions(+), 4 deletions(-) diff --git a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/BaseJdbcConfig.java b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/BaseJdbcConfig.java index 68e363c3ff42..44efaa48390d 100644 --- a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/BaseJdbcConfig.java +++ b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/BaseJdbcConfig.java @@ -36,6 +36,7 @@ public class BaseJdbcConfig private static final String METADATA_CACHE_TTL = "metadata.cache-ttl"; private static final String METADATA_SCHEMAS_CACHE_TTL = "metadata.schemas.cache-ttl"; private static final String METADATA_TABLES_CACHE_TTL = "metadata.tables.cache-ttl"; + private static final String METADATA_STATISTICS_CACHE_TTL = "metadata.statistics.cache-ttl"; private static final String METADATA_CACHE_MAXIMUM_SIZE = "metadata.cache-maximum-size"; private static final long DEFAULT_METADATA_CACHE_SIZE = 10000; @@ -44,6 +45,7 @@ public class BaseJdbcConfig private Duration metadataCacheTtl = new Duration(0, SECONDS); private Optional schemaNamesCacheTtl = Optional.empty(); private Optional tableNamesCacheTtl = Optional.empty(); + private Optional statisticsCacheTtl = Optional.empty(); private boolean cacheMissing; private Optional cacheMaximumSize = Optional.empty(); @@ -117,6 +119,20 @@ public BaseJdbcConfig setTableNamesCacheTtl(Duration tableNamesCacheTtl) return this; } + @NotNull + public Duration getStatisticsCacheTtl() + { + return statisticsCacheTtl.orElse(metadataCacheTtl); + } + + @Config(METADATA_STATISTICS_CACHE_TTL) + @ConfigDescription("Determines how long table statistics information will be cached") + public BaseJdbcConfig setStatisticsCacheTtl(Duration statisticsCacheTtl) + { + this.statisticsCacheTtl = Optional.ofNullable(statisticsCacheTtl); + return this; + } + public boolean isCacheMissing() { return cacheMissing; @@ -144,10 +160,12 @@ public BaseJdbcConfig setCacheMaximumSize(long cacheMaximumSize) return this; } - @AssertTrue(message = METADATA_CACHE_TTL + " must be set to a non-zero value when " + METADATA_CACHE_MAXIMUM_SIZE + " is set") + @AssertTrue(message = METADATA_CACHE_TTL + " or " + METADATA_STATISTICS_CACHE_TTL + " must be set to a non-zero value when " + METADATA_CACHE_MAXIMUM_SIZE + " is set") public boolean isCacheMaximumSizeConsistent() { - return !metadataCacheTtl.isZero() || cacheMaximumSize.isEmpty(); + return !metadataCacheTtl.isZero() || + (statisticsCacheTtl.isPresent() && !statisticsCacheTtl.get().isZero()) || + cacheMaximumSize.isEmpty(); } @AssertTrue(message = METADATA_SCHEMAS_CACHE_TTL + " must not be set when " + METADATA_CACHE_TTL + " is not set") diff --git a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/CachingJdbcClient.java b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/CachingJdbcClient.java index 1444fbf07889..0d83ab3398ef 100644 --- a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/CachingJdbcClient.java +++ b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/CachingJdbcClient.java @@ -102,6 +102,7 @@ public CachingJdbcClient( config.getMetadataCacheTtl(), config.getSchemaNamesCacheTtl(), config.getTableNamesCacheTtl(), + config.getStatisticsCacheTtl(), config.isCacheMissing(), config.getCacheMaximumSize()); } @@ -113,6 +114,7 @@ public CachingJdbcClient( Duration metadataCachingTtl, Duration schemaNamesCachingTtl, Duration tableNamesCachingTtl, + Duration statisticsCachingTtl, boolean cacheMissing, long cacheMaximumSize) { @@ -129,7 +131,7 @@ public CachingJdbcClient( tableHandlesByQueryCache = buildCache(cacheMaximumSize, metadataCachingTtl); procedureHandlesByQueryCache = buildCache(cacheMaximumSize, metadataCachingTtl); columnsCache = buildCache(cacheMaximumSize, metadataCachingTtl); - statisticsCache = buildCache(cacheMaximumSize, metadataCachingTtl); + statisticsCache = buildCache(cacheMaximumSize, statisticsCachingTtl); } private static Cache buildCache(long cacheSize, Duration cachingTtl) diff --git a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadataFactory.java b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadataFactory.java index 3dbafaa7cc5e..b4fba299e632 100644 --- a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadataFactory.java +++ b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadataFactory.java @@ -47,6 +47,7 @@ public JdbcMetadata create(JdbcTransactionHandle transaction) new Duration(1, DAYS), new Duration(1, DAYS), new Duration(1, DAYS), + new Duration(1, DAYS), true, Integer.MAX_VALUE)); } diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestBaseJdbcConfig.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestBaseJdbcConfig.java index 55f9bc2fba3b..a8dfaba46f99 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestBaseJdbcConfig.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestBaseJdbcConfig.java @@ -42,6 +42,7 @@ public void testDefaults() .setMetadataCacheTtl(ZERO) .setSchemaNamesCacheTtl(null) .setTableNamesCacheTtl(null) + .setStatisticsCacheTtl(null) .setCacheMissing(false) .setCacheMaximumSize(10000)); } @@ -55,6 +56,7 @@ public void testExplicitPropertyMappings() .put("metadata.cache-ttl", "1s") .put("metadata.schemas.cache-ttl", "2s") .put("metadata.tables.cache-ttl", "3s") + .put("metadata.statistics.cache-ttl", "7s") .put("metadata.cache-missing", "true") .put("metadata.cache-maximum-size", "5000") .buildOrThrow(); @@ -65,6 +67,7 @@ public void testExplicitPropertyMappings() .setMetadataCacheTtl(new Duration(1, SECONDS)) .setSchemaNamesCacheTtl(new Duration(2, SECONDS)) .setTableNamesCacheTtl(new Duration(3, SECONDS)) + .setStatisticsCacheTtl(new Duration(7, SECONDS)) .setCacheMissing(true) .setCacheMaximumSize(5000); @@ -94,6 +97,11 @@ public void testCacheConfigValidation() .setTableNamesCacheTtl(new Duration(3, SECONDS)) .setCacheMaximumSize(5000)); + assertValidates(new BaseJdbcConfig() + .setConnectionUrl("jdbc:h2:mem:config") + .setStatisticsCacheTtl(new Duration(7, SECONDS)) + .setCacheMaximumSize(5000)); + assertValidates(new BaseJdbcConfig() .setConnectionUrl("jdbc:h2:mem:config") .setMetadataCacheTtl(new Duration(1, SECONDS))); @@ -102,7 +110,7 @@ public void testCacheConfigValidation() new BaseJdbcConfig() .setCacheMaximumSize(5000), "cacheMaximumSizeConsistent", - "metadata.cache-ttl must be set to a non-zero value when metadata.cache-maximum-size is set", + "metadata.cache-ttl or metadata.statistics.cache-ttl must be set to a non-zero value when metadata.cache-maximum-size is set", AssertTrue.class); assertFailsValidation( diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java index 607356a6a68c..a2eebce8acd7 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java @@ -1008,6 +1008,44 @@ public void testSpecificSchemaAndTableCaches() jdbcClient.dropSchema(SESSION, secondSchema, false); } + @Test + public void testCacheOnlyStatistics() + throws Exception + { + CachingJdbcClient cachingJdbcClient = cachingClientBuilder() + .delegate(jdbcClientWithTableStats()) + .config(new BaseJdbcConfig() + .setMetadataCacheTtl(ZERO) + .setStatisticsCacheTtl(FOREVER)) + .build(); + SchemaTableName phantomTable = new SchemaTableName(schema, "phantom_table"); + + createTable(phantomTable); + + JdbcTableHandle firstHandle = assertTableHandleByNameCache(cachingJdbcClient) + .misses(2) + .loads(1) + .calling(() -> cachingJdbcClient.getTableHandle(SESSION, phantomTable)).orElseThrow(); + + assertStatisticsCacheStats(cachingJdbcClient) + .misses(1) + .loads(1) + .calling(() -> cachingJdbcClient.getTableStatistics(SESSION, firstHandle)); + + // Handle is afresh, not cached + JdbcTableHandle secondHandle = assertTableHandleByNameCache(cachingJdbcClient) + .misses(2) + .loads(1) + .calling(() -> cachingJdbcClient.getTableHandle(SESSION, phantomTable)).orElseThrow(); + + // Stats come from the cache + assertStatisticsCacheStats(cachingJdbcClient) + .hits(1) + .calling(() -> cachingJdbcClient.getTableStatistics(SESSION, secondHandle)); + + dropTable(phantomTable); + } + private JdbcTableHandle getAnyTable(String schema) { SchemaTableName tableName = jdbcClient.getTableNames(SESSION, Optional.of(schema)) @@ -1115,6 +1153,7 @@ CachingJdbcClient build() config.getMetadataCacheTtl(), config.getSchemaNamesCacheTtl(), config.getTableNamesCacheTtl(), + config.getStatisticsCacheTtl(), config.isCacheMissing(), config.getCacheMaximumSize()); } From 8137b67847bc3f68637ce828811abd34f658cb6a Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 22 Nov 2023 13:07:06 +0100 Subject: [PATCH 399/587] Make testSpecificSchemaAndTableCaches faster The test used a masked Thread.sleep to await for cache expiration, and thus lasted at least 6 seconds. Now the whole test class runs about 1 second. --- .../trino/plugin/jdbc/CachingJdbcClient.java | 20 +++-- .../jdbc/DefaultJdbcMetadataFactory.java | 2 + .../plugin/jdbc/TestCachingJdbcClient.java | 78 +++++++++++-------- 3 files changed, 58 insertions(+), 42 deletions(-) diff --git a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/CachingJdbcClient.java b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/CachingJdbcClient.java index 0d83ab3398ef..1a2ed089609f 100644 --- a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/CachingJdbcClient.java +++ b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/CachingJdbcClient.java @@ -14,6 +14,7 @@ package io.trino.plugin.jdbc; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Ticker; import com.google.common.cache.Cache; import com.google.common.cache.CacheStats; import com.google.common.collect.ImmutableMap; @@ -96,6 +97,7 @@ public CachingJdbcClient( BaseJdbcConfig config) { this( + Ticker.systemTicker(), delegate, sessionPropertiesProviders, identityMapping, @@ -108,6 +110,7 @@ public CachingJdbcClient( } public CachingJdbcClient( + Ticker ticker, JdbcClient delegate, Set sessionPropertiesProviders, IdentityCacheMapping identityMapping, @@ -125,18 +128,19 @@ public CachingJdbcClient( this.cacheMissing = cacheMissing; this.identityMapping = requireNonNull(identityMapping, "identityMapping is null"); - schemaNamesCache = buildCache(cacheMaximumSize, schemaNamesCachingTtl); - tableNamesCache = buildCache(cacheMaximumSize, tableNamesCachingTtl); - tableHandlesByNameCache = buildCache(cacheMaximumSize, metadataCachingTtl); - tableHandlesByQueryCache = buildCache(cacheMaximumSize, metadataCachingTtl); - procedureHandlesByQueryCache = buildCache(cacheMaximumSize, metadataCachingTtl); - columnsCache = buildCache(cacheMaximumSize, metadataCachingTtl); - statisticsCache = buildCache(cacheMaximumSize, statisticsCachingTtl); + schemaNamesCache = buildCache(ticker, cacheMaximumSize, schemaNamesCachingTtl); + tableNamesCache = buildCache(ticker, cacheMaximumSize, tableNamesCachingTtl); + tableHandlesByNameCache = buildCache(ticker, cacheMaximumSize, metadataCachingTtl); + tableHandlesByQueryCache = buildCache(ticker, cacheMaximumSize, metadataCachingTtl); + procedureHandlesByQueryCache = buildCache(ticker, cacheMaximumSize, metadataCachingTtl); + columnsCache = buildCache(ticker, cacheMaximumSize, metadataCachingTtl); + statisticsCache = buildCache(ticker, cacheMaximumSize, statisticsCachingTtl); } - private static Cache buildCache(long cacheSize, Duration cachingTtl) + private static Cache buildCache(Ticker ticker, long cacheSize, Duration cachingTtl) { return EvictableCacheBuilder.newBuilder() + .ticker(ticker) .maximumSize(cacheSize) .expireAfterWrite(cachingTtl.toMillis(), MILLISECONDS) .shareNothingWhenDisabled() diff --git a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadataFactory.java b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadataFactory.java index b4fba299e632..29078ca73bf2 100644 --- a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadataFactory.java +++ b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadataFactory.java @@ -13,6 +13,7 @@ */ package io.trino.plugin.jdbc; +import com.google.common.base.Ticker; import com.google.common.collect.ImmutableSet; import com.google.inject.Inject; import io.airlift.units.Duration; @@ -41,6 +42,7 @@ public JdbcMetadata create(JdbcTransactionHandle transaction) // Session stays the same per transaction, therefore session properties don't need to // be a part of cache keys in CachingJdbcClient. return create(new CachingJdbcClient( + Ticker.systemTicker(), jdbcClient, Set.of(), new SingletonIdentityCacheMapping(), diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java index a2eebce8acd7..691b058ed337 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestCachingJdbcClient.java @@ -13,10 +13,12 @@ */ package io.trino.plugin.jdbc; +import com.google.common.base.Ticker; import com.google.common.cache.CacheStats; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.Futures; +import io.airlift.testing.TestingTicker; import io.airlift.units.Duration; import io.trino.plugin.base.session.SessionPropertiesProvider; import io.trino.plugin.jdbc.JdbcProcedureHandle.ProcedureQuery; @@ -66,7 +68,6 @@ import static io.trino.spi.testing.InterfaceTestUtils.assertAllMethodsOverridden; import static io.trino.spi.type.IntegerType.INTEGER; import static io.trino.testing.TestingNames.randomNameSuffix; -import static io.trino.testing.assertions.Assert.assertEventually; import static java.lang.String.format; import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNull; @@ -912,11 +913,13 @@ public Optional getTableHandle(ConnectorSession session, Schema @Test public void testSpecificSchemaAndTableCaches() { + TestingTicker ticker = new TestingTicker(); CachingJdbcClient cachingJdbcClient = cachingClientBuilder() + .ticker(ticker) .config(new BaseJdbcConfig() .setMetadataCacheTtl(FOREVER) - .setSchemaNamesCacheTtl(new Duration(3, SECONDS)) - .setTableNamesCacheTtl(new Duration(2, SECONDS)) + .setSchemaNamesCacheTtl(new Duration(30, SECONDS)) + .setTableNamesCacheTtl(new Duration(20, SECONDS)) // decreased ttl for schema and table names mostly makes sense with cacheMissing == false .setCacheMissing(false)) .build(); @@ -967,40 +970,38 @@ public void testSpecificSchemaAndTableCaches() }); // reloads table names, retains schema names and table handles - assertEventually(Duration.succinctDuration(10, SECONDS), () -> { - assertSchemaNamesCache(cachingJdbcClient).hits(1).afterRunning(() -> { - assertThat(cachingJdbcClient.getSchemaNames(session)) - .contains(schema) - .doesNotContain(secondSchema); - }); - assertTableNamesCache(cachingJdbcClient).loads(1).misses(1).afterRunning(() -> { - assertThat(cachingJdbcClient.getTableNames(session, Optional.empty())) - .contains(firstName, secondName); - }); - assertTableHandleByNameCache(cachingJdbcClient).hits(1).afterRunning(() -> { - assertThat(cachingJdbcClient.getTableHandle(session, firstName)).isNotEmpty(); - }); - assertTableHandleByNameCache(cachingJdbcClient).hits(1).afterRunning(() -> { - assertThat(cachingJdbcClient.getTableHandle(session, secondName)).isNotEmpty(); - }); + ticker.increment(25, SECONDS); + assertSchemaNamesCache(cachingJdbcClient).hits(1).afterRunning(() -> { + assertThat(cachingJdbcClient.getSchemaNames(session)) + .contains(schema) + .doesNotContain(secondSchema); + }); + assertTableNamesCache(cachingJdbcClient).loads(1).misses(1).afterRunning(() -> { + assertThat(cachingJdbcClient.getTableNames(session, Optional.empty())) + .contains(firstName, secondName); + }); + assertTableHandleByNameCache(cachingJdbcClient).hits(1).afterRunning(() -> { + assertThat(cachingJdbcClient.getTableHandle(session, firstName)).isNotEmpty(); + }); + assertTableHandleByNameCache(cachingJdbcClient).hits(1).afterRunning(() -> { + assertThat(cachingJdbcClient.getTableHandle(session, secondName)).isNotEmpty(); }); // reloads tables names and schema names, but retains table handles - assertEventually(Duration.succinctDuration(10, SECONDS), () -> { - assertSchemaNamesCache(cachingJdbcClient).loads(1).misses(1).afterRunning(() -> { - assertThat(cachingJdbcClient.getSchemaNames(session)) - .contains(schema, secondSchema); - }); - assertTableNamesCache(cachingJdbcClient).loads(1).misses(1).afterRunning(() -> { - assertThat(cachingJdbcClient.getTableNames(session, Optional.empty())) - .contains(firstName, secondName); - }); - assertTableHandleByNameCache(cachingJdbcClient).hits(1).afterRunning(() -> { - assertThat(cachingJdbcClient.getTableHandle(session, firstName)).isNotEmpty(); - }); - assertTableHandleByNameCache(cachingJdbcClient).hits(1).afterRunning(() -> { - assertThat(cachingJdbcClient.getTableHandle(session, secondName)).isNotEmpty(); - }); + ticker.increment(35, SECONDS); + assertSchemaNamesCache(cachingJdbcClient).loads(1).misses(1).afterRunning(() -> { + assertThat(cachingJdbcClient.getSchemaNames(session)) + .contains(schema, secondSchema); + }); + assertTableNamesCache(cachingJdbcClient).loads(1).misses(1).afterRunning(() -> { + assertThat(cachingJdbcClient.getTableNames(session, Optional.empty())) + .contains(firstName, secondName); + }); + assertTableHandleByNameCache(cachingJdbcClient).hits(1).afterRunning(() -> { + assertThat(cachingJdbcClient.getTableHandle(session, firstName)).isNotEmpty(); + }); + assertTableHandleByNameCache(cachingJdbcClient).hits(1).afterRunning(() -> { + assertThat(cachingJdbcClient.getTableHandle(session, secondName)).isNotEmpty(); }); jdbcClient.dropTable(SESSION, first); @@ -1107,6 +1108,7 @@ private CachingJdbcClient createCachingJdbcClient() private CachingJdbcClientBuilder cachingClientBuilder() { return new CachingJdbcClientBuilder() + .ticker(Ticker.systemTicker()) .delegate(database.getJdbcClient()) .sessionPropertiesProviders(SESSION_PROPERTIES_PROVIDERS) .identityCacheMapping(new SingletonIdentityCacheMapping()) @@ -1115,11 +1117,18 @@ private CachingJdbcClientBuilder cachingClientBuilder() private static class CachingJdbcClientBuilder { + private Ticker ticker; private JdbcClient delegate; private Set sessionPropertiesProviders; private IdentityCacheMapping identityCacheMapping; private BaseJdbcConfig config; + public CachingJdbcClientBuilder ticker(Ticker ticker) + { + this.ticker = ticker; + return this; + } + public CachingJdbcClientBuilder delegate(JdbcClient delegate) { this.delegate = delegate; @@ -1147,6 +1156,7 @@ public CachingJdbcClientBuilder config(BaseJdbcConfig config) CachingJdbcClient build() { return new CachingJdbcClient( + ticker, delegate, sessionPropertiesProviders, identityCacheMapping, From 3ddc70218ec8c3c2f1e7cbd683e5e56a0e8d2ece Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 22 Nov 2023 14:26:28 +0100 Subject: [PATCH 400/587] Import time units statically --- .../cache/CachingHiveMetastoreConfig.java | 8 +++++--- .../cache/TestCachingHiveMetastoreConfig.java | 14 ++++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastoreConfig.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastoreConfig.java index eaae97eed9b5..dc60bc9897e0 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastoreConfig.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastoreConfig.java @@ -20,16 +20,18 @@ import jakarta.validation.constraints.NotNull; import java.util.Optional; -import java.util.concurrent.TimeUnit; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.SECONDS; public class CachingHiveMetastoreConfig { - private Duration metastoreCacheTtl = new Duration(0, TimeUnit.SECONDS); + private Duration metastoreCacheTtl = new Duration(0, SECONDS); // Use 5 mins for stats cache TTL by default. 5 mins will be sufficient to help // significantly when there is high number of concurrent queries. // 5 mins will also prevent stats from being stalled for a long time since // time window where table data can be altered is limited. - private Duration statsCacheTtl = new Duration(5, TimeUnit.MINUTES); + private Duration statsCacheTtl = new Duration(5, MINUTES); private Optional metastoreRefreshInterval = Optional.empty(); private long metastoreCacheMaximumSize = 10000; private int maxMetastoreRefreshThreads = 10; diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastoreConfig.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastoreConfig.java index 05df0080aca3..83a84a5ff80d 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastoreConfig.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastoreConfig.java @@ -18,11 +18,13 @@ import org.junit.jupiter.api.Test; import java.util.Map; -import java.util.concurrent.TimeUnit; import static io.airlift.configuration.testing.ConfigAssertions.assertFullMapping; import static io.airlift.configuration.testing.ConfigAssertions.assertRecordedDefaults; import static io.airlift.configuration.testing.ConfigAssertions.recordDefaults; +import static java.util.concurrent.TimeUnit.HOURS; +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.SECONDS; public class TestCachingHiveMetastoreConfig { @@ -30,8 +32,8 @@ public class TestCachingHiveMetastoreConfig public void testDefaults() { assertRecordedDefaults(recordDefaults(CachingHiveMetastoreConfig.class) - .setMetastoreCacheTtl(new Duration(0, TimeUnit.SECONDS)) - .setStatsCacheTtl(new Duration(5, TimeUnit.MINUTES)) + .setMetastoreCacheTtl(new Duration(0, SECONDS)) + .setStatsCacheTtl(new Duration(5, MINUTES)) .setMetastoreRefreshInterval(null) .setMetastoreCacheMaximumSize(10000) .setMaxMetastoreRefreshThreads(10) @@ -53,9 +55,9 @@ public void testExplicitPropertyMappings() .buildOrThrow(); CachingHiveMetastoreConfig expected = new CachingHiveMetastoreConfig() - .setMetastoreCacheTtl(new Duration(2, TimeUnit.HOURS)) - .setStatsCacheTtl(new Duration(10, TimeUnit.MINUTES)) - .setMetastoreRefreshInterval(new Duration(30, TimeUnit.MINUTES)) + .setMetastoreCacheTtl(new Duration(2, HOURS)) + .setStatsCacheTtl(new Duration(10, MINUTES)) + .setMetastoreRefreshInterval(new Duration(30, MINUTES)) .setMetastoreCacheMaximumSize(5000) .setMaxMetastoreRefreshThreads(2500) .setCacheMissing(false) From 193a8ab675d9b6efced3d7659aa8d20f5774686e Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 22 Nov 2023 14:23:57 +0100 Subject: [PATCH 401/587] Respect hive.metastore-stats-cache-ttl is explicitly set Remove the logic that would ignore `hive.metastore-stats-cache-ttl` set by the user, if the chosen value was less than the `hive.metastore-cache-ttl` value. The logic's purpose was considered necessary to enable statistics cache by default, but the same goal can be achieved differently, by applying default value (fixed duration or `hive.metastore-cache-ttl` whichever is greater) only if the parameter was not set explicitly. --- .../cache/CachingHiveMetastoreConfig.java | 11 +++++--- .../cache/SharedHiveMetastoreCache.java | 8 +----- .../cache/TestCachingHiveMetastoreConfig.java | 25 +++++++++++++++++++ 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastoreConfig.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastoreConfig.java index dc60bc9897e0..9995e2eacf1d 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastoreConfig.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastoreConfig.java @@ -21,17 +21,20 @@ import java.util.Optional; +import static com.google.common.collect.Comparators.max; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.SECONDS; public class CachingHiveMetastoreConfig { - private Duration metastoreCacheTtl = new Duration(0, SECONDS); // Use 5 mins for stats cache TTL by default. 5 mins will be sufficient to help // significantly when there is high number of concurrent queries. // 5 mins will also prevent stats from being stalled for a long time since // time window where table data can be altered is limited. - private Duration statsCacheTtl = new Duration(5, MINUTES); + public static final Duration DEFAULT_STATS_CACHE_TTL = new Duration(5, MINUTES); + + private Duration metastoreCacheTtl = new Duration(0, SECONDS); + private Optional statsCacheTtl = Optional.empty(); private Optional metastoreRefreshInterval = Optional.empty(); private long metastoreCacheMaximumSize = 10000; private int maxMetastoreRefreshThreads = 10; @@ -54,13 +57,13 @@ public CachingHiveMetastoreConfig setMetastoreCacheTtl(Duration metastoreCacheTt @NotNull public Duration getStatsCacheTtl() { - return statsCacheTtl; + return statsCacheTtl.orElseGet(() -> max(metastoreCacheTtl, DEFAULT_STATS_CACHE_TTL)); } @Config("hive.metastore-stats-cache-ttl") public CachingHiveMetastoreConfig setStatsCacheTtl(Duration statsCacheTtl) { - this.statsCacheTtl = statsCacheTtl; + this.statsCacheTtl = Optional.of(statsCacheTtl); return this; } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/SharedHiveMetastoreCache.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/SharedHiveMetastoreCache.java index aeeefbbf6033..0ac7025a4a92 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/SharedHiveMetastoreCache.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/SharedHiveMetastoreCache.java @@ -78,13 +78,7 @@ public SharedHiveMetastoreCache( this.catalogName = catalogName; metadataCacheTtl = config.getMetastoreCacheTtl(); - if (metadataCacheTtl.compareTo(config.getStatsCacheTtl()) > 0) { - statsCacheTtl = metadataCacheTtl; - } - else { - statsCacheTtl = config.getStatsCacheTtl(); - } - + statsCacheTtl = config.getStatsCacheTtl(); maxMetastoreRefreshThreads = config.getMaxMetastoreRefreshThreads(); metastoreRefreshInterval = config.getMetastoreRefreshInterval(); metastoreCacheMaximumSize = config.getMetastoreCacheMaximumSize(); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastoreConfig.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastoreConfig.java index 83a84a5ff80d..1f0e196bd05a 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastoreConfig.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/cache/TestCachingHiveMetastoreConfig.java @@ -22,9 +22,13 @@ import static io.airlift.configuration.testing.ConfigAssertions.assertFullMapping; import static io.airlift.configuration.testing.ConfigAssertions.assertRecordedDefaults; import static io.airlift.configuration.testing.ConfigAssertions.recordDefaults; +import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastoreConfig.DEFAULT_STATS_CACHE_TTL; +import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.HOURS; +import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; public class TestCachingHiveMetastoreConfig { @@ -65,4 +69,25 @@ public void testExplicitPropertyMappings() assertFullMapping(properties, expected); } + + @Test + public void testStatsCacheTtl() + { + // enabled by default + assertThat(new CachingHiveMetastoreConfig().getStatsCacheTtl()).isEqualTo(DEFAULT_STATS_CACHE_TTL); + + // takes higher of the DEFAULT_STATS_CACHE_TTL or metastoreTtl if not set explicitly + assertThat(new CachingHiveMetastoreConfig() + .setMetastoreCacheTtl(new Duration(1, SECONDS)) + .getStatsCacheTtl()).isEqualTo(DEFAULT_STATS_CACHE_TTL); + assertThat(new CachingHiveMetastoreConfig() + .setMetastoreCacheTtl(new Duration(1111, DAYS)) + .getStatsCacheTtl()).isEqualTo(new Duration(1111, DAYS)); + + // explicit configuration is honored + assertThat(new CachingHiveMetastoreConfig() + .setStatsCacheTtl(new Duration(135, MILLISECONDS)) + .setMetastoreCacheTtl(new Duration(1111, DAYS)) + .getStatsCacheTtl()).isEqualTo(new Duration(135, MILLISECONDS)); + } } From 26b6d6921447e7e2913113b8871a0979ae247826 Mon Sep 17 00:00:00 2001 From: Raunaq Morarka Date: Thu, 9 Nov 2023 19:47:59 +0530 Subject: [PATCH 402/587] Move getColumnIndexStore to TrinoColumnIndexStore This method should be in parquet reader package rather than in a connector package --- .../parquet/reader/TrinoColumnIndexStore.java | 44 +++++++++++++++++++ plugin/trino-hive/pom.xml | 11 ++--- .../parquet/ParquetPageSourceFactory.java | 43 +----------------- .../plugin/hudi/HudiPageSourceProvider.java | 2 +- 4 files changed, 52 insertions(+), 48 deletions(-) diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/TrinoColumnIndexStore.java b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/TrinoColumnIndexStore.java index 041c4e1250ae..94c6f753637d 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/reader/TrinoColumnIndexStore.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/reader/TrinoColumnIndexStore.java @@ -18,7 +18,11 @@ import com.google.common.collect.ListMultimap; import io.trino.parquet.DiskRange; import io.trino.parquet.ParquetDataSource; +import io.trino.parquet.ParquetReaderOptions; +import io.trino.spi.predicate.Domain; +import io.trino.spi.predicate.TupleDomain; import jakarta.annotation.Nullable; +import org.apache.parquet.column.ColumnDescriptor; import org.apache.parquet.format.Util; import org.apache.parquet.format.converter.ParquetMetadataConverter; import org.apache.parquet.hadoop.metadata.BlockMetaData; @@ -32,12 +36,15 @@ import java.io.IOException; import java.io.InputStream; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.function.BiFunction; import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static com.google.common.collect.ImmutableSet.toImmutableSet; import static io.trino.memory.context.AggregatedMemoryContext.newSimpleAggregatedMemoryContext; import static java.util.Objects.requireNonNull; @@ -129,6 +136,43 @@ public OffsetIndex getOffsetIndex(ColumnPath column) return offsetIndexStore.get(column); } + public static Optional getColumnIndexStore( + ParquetDataSource dataSource, + BlockMetaData blockMetadata, + Map, ColumnDescriptor> descriptorsByPath, + TupleDomain parquetTupleDomain, + ParquetReaderOptions options) + { + if (!options.isUseColumnIndex() || parquetTupleDomain.isAll() || parquetTupleDomain.isNone()) { + return Optional.empty(); + } + + boolean hasColumnIndex = false; + for (ColumnChunkMetaData column : blockMetadata.getColumns()) { + if (column.getColumnIndexReference() != null && column.getOffsetIndexReference() != null) { + hasColumnIndex = true; + break; + } + } + + if (!hasColumnIndex) { + return Optional.empty(); + } + + Set columnsReadPaths = new HashSet<>(descriptorsByPath.size()); + for (List path : descriptorsByPath.keySet()) { + columnsReadPaths.add(ColumnPath.get(path.toArray(new String[0]))); + } + + Map parquetDomains = parquetTupleDomain.getDomains() + .orElseThrow(() -> new IllegalStateException("Predicate other than none should have domains")); + Set columnsFilteredPaths = parquetDomains.keySet().stream() + .map(column -> ColumnPath.get(column.getPath())) + .collect(toImmutableSet()); + + return Optional.of(new TrinoColumnIndexStore(dataSource, blockMetadata, columnsReadPaths, columnsFilteredPaths)); + } + private static Map loadIndexes( ParquetDataSource dataSource, List indexMetadata, diff --git a/plugin/trino-hive/pom.xml b/plugin/trino-hive/pom.xml index f844ae68cb26..3d37acc7756a 100644 --- a/plugin/trino-hive/pom.xml +++ b/plugin/trino-hive/pom.xml @@ -221,11 +221,6 @@ parquet-column - - org.apache.parquet - parquet-common - - org.apache.parquet parquet-format-structures @@ -299,6 +294,12 @@ runtime + + org.apache.parquet + parquet-common + runtime + + io.airlift junit-extensions diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/parquet/ParquetPageSourceFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/parquet/ParquetPageSourceFactory.java index f7919c4da1bf..4482eb3cd090 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/parquet/ParquetPageSourceFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/parquet/ParquetPageSourceFactory.java @@ -33,7 +33,6 @@ import io.trino.parquet.predicate.TupleDomainParquetPredicate; import io.trino.parquet.reader.MetadataReader; import io.trino.parquet.reader.ParquetReader; -import io.trino.parquet.reader.TrinoColumnIndexStore; import io.trino.plugin.hive.AcidInfo; import io.trino.plugin.hive.FileFormatDataSourceStats; import io.trino.plugin.hive.HiveColumnHandle; @@ -51,8 +50,6 @@ import io.trino.spi.predicate.TupleDomain; import org.apache.parquet.column.ColumnDescriptor; import org.apache.parquet.hadoop.metadata.BlockMetaData; -import org.apache.parquet.hadoop.metadata.ColumnChunkMetaData; -import org.apache.parquet.hadoop.metadata.ColumnPath; import org.apache.parquet.hadoop.metadata.FileMetaData; import org.apache.parquet.hadoop.metadata.ParquetMetadata; import org.apache.parquet.internal.filter2.columnindex.ColumnIndexStore; @@ -63,7 +60,6 @@ import org.joda.time.DateTimeZone; import java.io.IOException; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -74,7 +70,6 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.collect.ImmutableSet.toImmutableSet; import static io.trino.memory.context.AggregatedMemoryContext.newSimpleAggregatedMemoryContext; import static io.trino.parquet.BloomFilterStore.getBloomFilterStore; import static io.trino.parquet.ParquetTypeUtils.constructField; @@ -84,6 +79,7 @@ import static io.trino.parquet.ParquetTypeUtils.lookupColumnByName; import static io.trino.parquet.predicate.PredicateUtils.buildPredicate; import static io.trino.parquet.predicate.PredicateUtils.predicateMatches; +import static io.trino.parquet.reader.TrinoColumnIndexStore.getColumnIndexStore; import static io.trino.plugin.hive.HiveColumnHandle.ColumnType.REGULAR; import static io.trino.plugin.hive.HiveErrorCode.HIVE_BAD_DATA; import static io.trino.plugin.hive.HiveErrorCode.HIVE_CANNOT_OPEN_SPLIT; @@ -383,43 +379,6 @@ public static Optional getColumnType(HiveColumnH return Optional.of(new GroupType(baseType.getRepetition(), baseType.getName(), ImmutableList.of(type))); } - public static Optional getColumnIndexStore( - ParquetDataSource dataSource, - BlockMetaData blockMetadata, - Map, ColumnDescriptor> descriptorsByPath, - TupleDomain parquetTupleDomain, - ParquetReaderOptions options) - { - if (!options.isUseColumnIndex() || parquetTupleDomain.isAll() || parquetTupleDomain.isNone()) { - return Optional.empty(); - } - - boolean hasColumnIndex = false; - for (ColumnChunkMetaData column : blockMetadata.getColumns()) { - if (column.getColumnIndexReference() != null && column.getOffsetIndexReference() != null) { - hasColumnIndex = true; - break; - } - } - - if (!hasColumnIndex) { - return Optional.empty(); - } - - Set columnsReadPaths = new HashSet<>(descriptorsByPath.size()); - for (List path : descriptorsByPath.keySet()) { - columnsReadPaths.add(ColumnPath.get(path.toArray(new String[0]))); - } - - Map parquetDomains = parquetTupleDomain.getDomains() - .orElseThrow(() -> new IllegalStateException("Predicate other than none should have domains")); - Set columnsFilteredPaths = parquetDomains.keySet().stream() - .map(column -> ColumnPath.get(column.getPath())) - .collect(toImmutableSet()); - - return Optional.of(new TrinoColumnIndexStore(dataSource, blockMetadata, columnsReadPaths, columnsFilteredPaths)); - } - public static TupleDomain getParquetTupleDomain( Map, ColumnDescriptor> descriptorsByPath, TupleDomain effectivePredicate, diff --git a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiPageSourceProvider.java b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiPageSourceProvider.java index 93b0e3c5af02..9c29179861c7 100644 --- a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiPageSourceProvider.java +++ b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiPageSourceProvider.java @@ -75,11 +75,11 @@ import static io.trino.parquet.ParquetTypeUtils.getDescriptors; import static io.trino.parquet.predicate.PredicateUtils.buildPredicate; import static io.trino.parquet.predicate.PredicateUtils.predicateMatches; +import static io.trino.parquet.reader.TrinoColumnIndexStore.getColumnIndexStore; import static io.trino.plugin.hive.HivePageSourceProvider.projectBaseColumns; import static io.trino.plugin.hive.parquet.ParquetPageSourceFactory.ParquetReaderProvider; import static io.trino.plugin.hive.parquet.ParquetPageSourceFactory.createDataSource; import static io.trino.plugin.hive.parquet.ParquetPageSourceFactory.createParquetPageSource; -import static io.trino.plugin.hive.parquet.ParquetPageSourceFactory.getColumnIndexStore; import static io.trino.plugin.hive.parquet.ParquetPageSourceFactory.getParquetMessageType; import static io.trino.plugin.hive.parquet.ParquetPageSourceFactory.getParquetTupleDomain; import static io.trino.plugin.hive.util.HiveUtil.makePartName; From fea2b9a98f58ddfeefab177afab518f969169d8b Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Thu, 23 Nov 2023 10:05:28 +0100 Subject: [PATCH 403/587] Remove testReadMetadataWithRelationsConcurrentModifications test This test covers an important aspect of Trino connectors -- metadata listing should work also while tables/views/materialized views are being created or dropped. It helped discover real problems, e.g. - in Iceberg, that's why it was created - in Kudu: https://github.com/trinodb/trino/issues/12974, - in SQL Server https://github.com/trinodb/trino/blob/8edaa86c6b43e5f5fe03bb9427d3f26439a60d15/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerConnectorTest.java#L151-L152 However, this turned out to be hard to test. The existing test is pretty complicated, takes a lot of time, has high timeout value and remains one of the more flaky tests. The test author does not know how to make it robust and perhaps does not want to remain as the author of the top flaky test in the project. --- .../plugin/jdbc/TestJdbcConnectorTest.java | 9 - .../bigquery/BaseBigQueryConnectorTest.java | 8 - .../plugin/kudu/TestKuduConnectorTest.java | 24 --- .../redshift/TestRedshiftConnectorTest.java | 7 - .../sqlserver/BaseSqlServerConnectorTest.java | 21 -- .../io/trino/testing/BaseConnectorTest.java | 203 ------------------ 6 files changed, 272 deletions(-) diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestJdbcConnectorTest.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestJdbcConnectorTest.java index a5e2fcbad224..6751c1160dc0 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestJdbcConnectorTest.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestJdbcConnectorTest.java @@ -134,15 +134,6 @@ public void testDeleteWithLike() .hasStackTraceContaining("TrinoException: " + MODIFYING_ROWS_MESSAGE); } - @Test - @Override - public void testReadMetadataWithRelationsConcurrentModifications() - { - // Under concurrently, H2 sometimes returns null table name in DatabaseMetaData.getTables's ResultSet - // See https://github.com/trinodb/trino/issues/16658 for more information - abort("Skipped due to H2 problems"); - } - @Test public void testUnknownTypeAsIgnored() { diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java index 3ff3ffe1a9b6..e0d11eecf419 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java @@ -571,14 +571,6 @@ public void testShowCreateTable() ")"); } - @Test - @Override - public void testReadMetadataWithRelationsConcurrentModifications() - { - // TODO: Enable this test after fixing "Task did not completed before timeout" (https://github.com/trinodb/trino/issues/14230) - abort("Test fails with a timeout sometimes and is flaky"); - } - @Test public void testSkipUnsupportedType() { diff --git a/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduConnectorTest.java b/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduConnectorTest.java index 15cf25573b63..48363c5d2f45 100644 --- a/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduConnectorTest.java +++ b/plugin/trino-kudu/src/test/java/io/trino/plugin/kudu/TestKuduConnectorTest.java @@ -731,30 +731,6 @@ public void testWrittenStats() // TODO Kudu connector supports CTAS and inserts, but the test would fail } - @Test - @Override - public void testReadMetadataWithRelationsConcurrentModifications() - { - try { - super.testReadMetadataWithRelationsConcurrentModifications(); - } - catch (Exception expected) { - // The test failure is not guaranteed - // TODO (https://github.com/trinodb/trino/issues/12974): shouldn't fail - assertThat(expected) - .hasMessageMatching(".* table .* was deleted: Table deleted at .* UTC"); - abort("to be fixed"); - } - } - - @Override - protected String createTableSqlTemplateForConcurrentModifications() - { - // TODO Remove this overriding method once kudu connector can create tables with default partitions - return "CREATE TABLE %s(a integer WITH (primary_key=true)) " + - "WITH (partition_by_hash_columns = ARRAY['a'], partition_by_hash_buckets = 2)"; - } - @Test @Override public void testCreateTableAsSelectNegativeDate() diff --git a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftConnectorTest.java b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftConnectorTest.java index 3c51094ab3eb..cebbc170e7d6 100644 --- a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftConnectorTest.java +++ b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftConnectorTest.java @@ -602,13 +602,6 @@ public void testDecimalAvgPushdownFoShortDecimalScale() } } - @Test - @Override - public void testReadMetadataWithRelationsConcurrentModifications() - { - abort("Test fails with a timeout sometimes and is flaky"); - } - @Test @Override public void testInsertRowConcurrently() diff --git a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerConnectorTest.java b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerConnectorTest.java index 237dd55659cf..be0109175c48 100644 --- a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerConnectorTest.java +++ b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerConnectorTest.java @@ -41,7 +41,6 @@ import static java.util.stream.Collectors.joining; import static java.util.stream.IntStream.range; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assumptions.abort; public abstract class BaseSqlServerConnectorTest extends BaseJdbcConnectorTest @@ -134,26 +133,6 @@ public void testSelectInformationSchemaColumns() super.testSelectInformationSchemaColumns(); } - @Test - @Override - public void testReadMetadataWithRelationsConcurrentModifications() - { - try { - super.testReadMetadataWithRelationsConcurrentModifications(); - } - catch (Exception expected) { - // The test failure is not guaranteed - assertThat(expected) - .hasMessageMatching("(?s).*(" + - "No task completed before timeout|" + - "was deadlocked on lock resources with another process and has been chosen as the deadlock victim|" + - "Lock request time out period exceeded|" + - // E.g. system.metadata.table_comments can return empty results, when underlying metadata list tables call fails - "Expecting actual not to be empty).*"); - abort("to be fixed"); - } - } - @Override protected void verifyAddNotNullColumnToNonEmptyTableFailurePermissible(Throwable e) { diff --git a/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java b/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java index 31e32005c361..dd00a554460a 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java @@ -13,7 +13,6 @@ */ package io.trino.testing; -import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.Futures; @@ -57,24 +56,16 @@ import java.time.Instant; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; -import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Deque; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.OptionalInt; -import java.util.concurrent.Callable; -import java.util.concurrent.CompletionService; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; -import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Function; @@ -1966,200 +1957,6 @@ public void testViewAndMaterializedViewTogether() assertUpdate("DROP MATERIALIZED VIEW " + materializedViewName); } - /** - * Test that reading table, column metadata, like {@code SHOW TABLES} or reading from {@code information_schema.views} - * does not fail when relations are concurrently created or dropped. - */ - @Test - @Timeout(180) - public void testReadMetadataWithRelationsConcurrentModifications() - throws Exception - { - if (!hasBehavior(SUPPORTS_CREATE_TABLE) && !hasBehavior(SUPPORTS_CREATE_VIEW) && !hasBehavior(SUPPORTS_CREATE_MATERIALIZED_VIEW)) { - abort("Cannot test"); - } - - int readIterations = 5; - // generous timeout as this is a generic test; typically should be faster - int testTimeoutSeconds = 150; - - testReadMetadataWithRelationsConcurrentModifications(readIterations, testTimeoutSeconds); - } - - protected void testReadMetadataWithRelationsConcurrentModifications(int readIterations, int testTimeoutSeconds) - throws Exception - { - Stopwatch testWatch = Stopwatch.createStarted(); - - int readerTasksCount = 6 - + (hasBehavior(SUPPORTS_CREATE_VIEW) ? 1 : 0) - + (hasBehavior(SUPPORTS_CREATE_MATERIALIZED_VIEW) ? 1 : 0); - AtomicInteger incompleteReadTasks = new AtomicInteger(readerTasksCount); - List> readerTasks = new ArrayList<>(); - readerTasks.add(queryRepeatedly(readIterations, incompleteReadTasks, "SHOW TABLES")); - readerTasks.add(queryRepeatedly(readIterations, incompleteReadTasks, "SELECT * FROM information_schema.tables WHERE table_schema = CURRENT_SCHEMA")); - readerTasks.add(queryRepeatedly(readIterations, incompleteReadTasks, "SELECT * FROM information_schema.columns WHERE table_schema = CURRENT_SCHEMA")); - readerTasks.add(queryRepeatedly(readIterations, incompleteReadTasks, "SELECT * FROM system.jdbc.tables WHERE table_cat = CURRENT_CATALOG AND table_schem = CURRENT_SCHEMA")); - readerTasks.add(queryRepeatedly(readIterations, incompleteReadTasks, "SELECT * FROM system.jdbc.columns WHERE table_cat = CURRENT_CATALOG AND table_schem = CURRENT_SCHEMA")); - readerTasks.add(queryRepeatedly(readIterations, incompleteReadTasks, "SELECT * FROM system.metadata.table_comments WHERE catalog_name = CURRENT_CATALOG AND schema_name = CURRENT_SCHEMA")); - if (hasBehavior(SUPPORTS_CREATE_VIEW)) { - readerTasks.add(queryRepeatedly(readIterations, incompleteReadTasks, "SELECT * FROM information_schema.views WHERE table_schema = CURRENT_SCHEMA")); - } - if (hasBehavior(SUPPORTS_CREATE_MATERIALIZED_VIEW)) { - readerTasks.add(queryRepeatedly(readIterations, incompleteReadTasks, "SELECT * FROM system.metadata.materialized_views WHERE catalog_name = CURRENT_CATALOG AND schema_name = CURRENT_SCHEMA")); - } - assertThat(readerTasks.size()).isEqualTo(readerTasksCount); - - int writeTasksCount = 1 - + (hasBehavior(SUPPORTS_CREATE_VIEW) ? 1 : 0) - + (hasBehavior(SUPPORTS_CREATE_MATERIALIZED_VIEW) ? 1 : 0); - writeTasksCount = 2 * writeTasksCount; // writes are scheduled twice - CountDownLatch writeTasksInitialized = new CountDownLatch(writeTasksCount); - Runnable writeInitialized = writeTasksInitialized::countDown; - AtomicBoolean aborted = new AtomicBoolean(); - Supplier done = () -> aborted.get() || incompleteReadTasks.get() == 0; - List> writeTasks = new ArrayList<>(); - writeTasks.add(createDropRepeatedly(writeInitialized, done, "concur_table", createTableSqlTemplateForConcurrentModifications(), "DROP TABLE %s")); - if (hasBehavior(SUPPORTS_CREATE_VIEW)) { - writeTasks.add(createDropRepeatedly(writeInitialized, done, "concur_view", "CREATE VIEW %s AS SELECT 1 a", "DROP VIEW %s")); - } - if (hasBehavior(SUPPORTS_CREATE_MATERIALIZED_VIEW)) { - writeTasks.add(createDropRepeatedly(writeInitialized, done, "concur_mview", "CREATE MATERIALIZED VIEW %s AS SELECT 1 a", "DROP MATERIALIZED VIEW %s")); - } - assertThat(writeTasks.size() * 2).isEqualTo(writeTasksCount); - - ExecutorService executor = newFixedThreadPool(readerTasksCount + writeTasksCount); - try { - CompletionService completionService = new ExecutorCompletionService<>(executor); - submitTasks(writeTasks, completionService); - submitTasks(writeTasks, completionService); // twice to increase chances of catching problems - if (!writeTasksInitialized.await(testTimeoutSeconds, SECONDS)) { - Future someFailure = completionService.poll(); - if (someFailure != null) { - someFailure.get(); // non-blocking - } - fail("Setup failed"); - } - submitTasks(readerTasks, completionService); - for (int i = 0; i < readerTasksCount + writeTasksCount; i++) { - long remainingTimeSeconds = testTimeoutSeconds - testWatch.elapsed(SECONDS); - Future future = completionService.poll(remainingTimeSeconds, SECONDS); - verifyNotNull(future, "Task did not completed before timeout; completed tasks: %s, current poll timeout: %s s", i, remainingTimeSeconds); - future.get(); // non-blocking - } - } - catch (Throwable failure) { - aborted.set(true); - executor.shutdownNow(); - if (!executor.awaitTermination(10, SECONDS)) { - throw new AssertionError("Test threads did not complete. Leaving test threads behind may violate AbstractTestQueryFramework.checkQueryInfosFinal", failure); - } - throw failure; - } - finally { - executor.shutdownNow(); - } - assertThat(executor.awaitTermination(10, SECONDS)).isTrue(); - } - - @Language("SQL") - protected String createTableSqlTemplateForConcurrentModifications() - { - return "CREATE TABLE %s(a integer)"; - } - - /** - * Run {@code sql} query at least {@code minIterations} times and keep running until other tasks complete. - * {@code incompleteReadTasks} is used for orchestrating end of execution. - */ - protected Callable queryRepeatedly(int minIterations, AtomicInteger incompleteReadTasks, @Language("SQL") String sql) - { - return new Callable<>() - { - @Override - public Void call() - { - boolean alwaysEmpty = true; - for (int i = 0; i < minIterations; i++) { - MaterializedResult result = computeActual(sql); - alwaysEmpty &= result.getRowCount() == 0; - } - if (alwaysEmpty) { - fail(format("The results of [%s] are always empty after %s iterations, this may indicate test misconfiguration or broken connector behavior", sql, minIterations)); - } - assertThat(incompleteReadTasks.decrementAndGet()).as("incompleteReadTasks").isGreaterThanOrEqualTo(0); - // Keep running so that faster test queries have same length of exposure in wall time - while (incompleteReadTasks.get() != 0) { - computeActual(sql); - } - return null; - } - - @Override - public String toString() - { - return format("Query(%s)", sql); - } - }; - } - - protected Callable createDropRepeatedly(Runnable initReady, Supplier done, String namePrefix, String createTemplate, String dropTemplate) - { - return new Callable<>() - { - @Override - public Void call() - { - int objectsToKeep = 3; - Deque liveObjects = new ArrayDeque<>(objectsToKeep); - for (int i = 0; i < objectsToKeep; i++) { - String name = namePrefix + "_" + randomNameSuffix(); - assertUpdate(format(createTemplate, name)); - liveObjects.addLast(name); - } - initReady.run(); - while (!done.get()) { - assertUpdate(format(dropTemplate, liveObjects.removeFirst())); - String name = namePrefix + "_" + randomNameSuffix(); - assertUpdate(format(createTemplate, name)); - liveObjects.addLast(name); - } - while (!liveObjects.isEmpty()) { - assertUpdate(format(dropTemplate, liveObjects.removeFirst())); - } - return null; - } - - @Override - public String toString() - { - return format("Repeat (%s) and (%s)", createTemplate, dropTemplate); - } - }; - } - - protected void submitTasks(List> callables, CompletionService completionService) - { - for (Callable callable : callables) { - String taskDescription = callable.toString(); - completionService.submit(new Callable() - { - @Override - public T call() - throws Exception - { - try { - return callable.call(); - } - catch (Throwable e) { - e.addSuppressed(new Exception("Task: " + taskDescription)); - throw e; - } - } - }); - } - } - @Test public void testExplainAnalyze() { From c4c510c541c819744ad1eb1c353382b9897044c7 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Tue, 21 Nov 2023 16:34:58 +0100 Subject: [PATCH 404/587] Unimplement DirectExchangeBuffer for TASK retries The class is not used for TASK-level retries. --- .../DeduplicatingDirectExchangeBuffer.java | 77 ++------ .../operator/DirectExchangeClientFactory.java | 1 + ...TestDeduplicatingDirectExchangeBuffer.java | 170 ------------------ 3 files changed, 16 insertions(+), 232 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/operator/DeduplicatingDirectExchangeBuffer.java b/core/trino-main/src/main/java/io/trino/operator/DeduplicatingDirectExchangeBuffer.java index becd8242c239..b6bb2033649d 100644 --- a/core/trino-main/src/main/java/io/trino/operator/DeduplicatingDirectExchangeBuffer.java +++ b/core/trino-main/src/main/java/io/trino/operator/DeduplicatingDirectExchangeBuffer.java @@ -75,7 +75,6 @@ import static io.airlift.concurrent.MoreFutures.getFutureValue; import static io.airlift.concurrent.MoreFutures.toListenableFuture; import static io.trino.execution.scheduler.Exchanges.getAllSourceHandles; -import static io.trino.operator.RetryPolicy.NONE; import static io.trino.operator.RetryPolicy.QUERY; import static io.trino.spi.StandardErrorCode.REMOTE_TASK_FAILED; import static io.trino.spi.block.PageBuilderStatus.DEFAULT_MAX_PAGE_SIZE_IN_BYTES; @@ -92,7 +91,6 @@ public class DeduplicatingDirectExchangeBuffer private static final Duration SINK_INSTANCE_HANDLE_GET_TIMEOUT = Duration.succinctDuration(60, SECONDS); private final Executor executor; - private final RetryPolicy retryPolicy; @GuardedBy("this") private final Set allTasks = new HashSet<>(); @@ -130,9 +128,7 @@ public DeduplicatingDirectExchangeBuffer( ExchangeId exchangeId) { this.executor = requireNonNull(executor, "executor is null"); - requireNonNull(retryPolicy, "retryPolicy is null"); - checkArgument(retryPolicy != NONE, "retries should be enabled"); - this.retryPolicy = retryPolicy; + checkArgument(retryPolicy == QUERY, "the class is used for query level retries only, got: %s", retryPolicy); this.pageBuffer = new PageBuffer( exchangeManagerRegistry, queryId, @@ -195,10 +191,8 @@ public synchronized void addTask(TaskId taskId) if (taskId.getAttemptId() > maxAttemptId) { maxAttemptId = taskId.getAttemptId(); - if (retryPolicy == QUERY) { - pageBuffer.removePagesForPreviousAttempts(maxAttemptId); - updateMaxRetainedSize(); - } + pageBuffer.removePagesForPreviousAttempts(maxAttemptId); + updateMaxRetainedSize(); } } @@ -217,7 +211,7 @@ public synchronized void addPages(TaskId taskId, List pages) checkState(!successfulTasks.contains(taskId), "task is finished: %s", taskId); checkState(!failedTasks.containsKey(taskId), "task is failed: %s", taskId); - if (retryPolicy == QUERY && taskId.getAttemptId() < maxAttemptId) { + if (taskId.getAttemptId() < maxAttemptId) { return; } @@ -283,61 +277,20 @@ private void checkInputFinished() return; } - Map failures; - switch (retryPolicy) { - case TASK -> { - Set allPartitions = allTasks.stream() - .map(TaskId::getPartitionId) - .collect(toImmutableSet()); - - Set successfulPartitions = successfulTasks.stream() - .map(TaskId::getPartitionId) - .collect(toImmutableSet()); - - if (successfulPartitions.containsAll(allPartitions)) { - Map partitionToTaskMap = new HashMap<>(); - for (TaskId successfulTaskId : successfulTasks) { - Integer partitionId = successfulTaskId.getPartitionId(); - TaskId existing = partitionToTaskMap.get(partitionId); - if (existing == null || existing.getAttemptId() > successfulTaskId.getAttemptId()) { - partitionToTaskMap.put(partitionId, successfulTaskId); - } - } - - outputSource = pageBuffer.createOutputSource(ImmutableSet.copyOf(partitionToTaskMap.values())); - unblock(outputReady); - return; - } - - Set runningPartitions = allTasks.stream() - .filter(taskId -> !successfulTasks.contains(taskId)) - .filter(taskId -> !failedTasks.containsKey(taskId)) - .map(TaskId::getPartitionId) - .collect(toImmutableSet()); + Set latestAttemptTasks = allTasks.stream() + .filter(taskId -> taskId.getAttemptId() == maxAttemptId) + .collect(toImmutableSet()); - failures = failedTasks.entrySet().stream() - .filter(entry -> !successfulPartitions.contains(entry.getKey().getPartitionId())) - .filter(entry -> !runningPartitions.contains(entry.getKey().getPartitionId())) - .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)); - } - case QUERY -> { - Set latestAttemptTasks = allTasks.stream() - .filter(taskId -> taskId.getAttemptId() == maxAttemptId) - .collect(toImmutableSet()); - - if (successfulTasks.containsAll(latestAttemptTasks)) { - outputSource = pageBuffer.createOutputSource(latestAttemptTasks); - unblock(outputReady); - return; - } - - failures = failedTasks.entrySet().stream() - .filter(entry -> entry.getKey().getAttemptId() == maxAttemptId) - .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)); - } - default -> throw new UnsupportedOperationException("unexpected retry policy: " + retryPolicy); + if (successfulTasks.containsAll(latestAttemptTasks)) { + outputSource = pageBuffer.createOutputSource(latestAttemptTasks); + unblock(outputReady); + return; } + Map failures = failedTasks.entrySet().stream() + .filter(entry -> entry.getKey().getAttemptId() == maxAttemptId) + .collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)); + Throwable failure = null; for (Map.Entry entry : failures.entrySet()) { TaskId taskId = entry.getKey(); diff --git a/core/trino-main/src/main/java/io/trino/operator/DirectExchangeClientFactory.java b/core/trino-main/src/main/java/io/trino/operator/DirectExchangeClientFactory.java index ec629a531dcb..9ad7022bd6c3 100644 --- a/core/trino-main/src/main/java/io/trino/operator/DirectExchangeClientFactory.java +++ b/core/trino-main/src/main/java/io/trino/operator/DirectExchangeClientFactory.java @@ -144,6 +144,7 @@ public DirectExchangeClient get( DirectExchangeBuffer buffer; switch (retryPolicy) { case TASK: + throw new UnsupportedOperationException(); case QUERY: buffer = new DeduplicatingDirectExchangeBuffer(scheduler, deduplicationBufferSize, retryPolicy, exchangeManagerRegistry, queryId, exchangeId); break; diff --git a/core/trino-main/src/test/java/io/trino/operator/TestDeduplicatingDirectExchangeBuffer.java b/core/trino-main/src/test/java/io/trino/operator/TestDeduplicatingDirectExchangeBuffer.java index 2ec80f350157..28df3edb36d5 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestDeduplicatingDirectExchangeBuffer.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestDeduplicatingDirectExchangeBuffer.java @@ -342,175 +342,6 @@ public void testPollPagesQueryLevelRetry() error); } - @Test - public void testPollPagesTaskLevelRetry() - { - // 0 pages - testPollPages( - RetryPolicy.TASK, - ImmutableListMultimap.of(), - ImmutableMap.of(), - DEFAULT_BUFFER_CAPACITY, - 0, - ImmutableList.of()); - - // single page, no spilling - testPollPages( - RetryPolicy.TASK, - ImmutableListMultimap.of(createTaskId(0, 0), createPage("p0a0v0", DataSize.of(10, BYTE))), - ImmutableMap.of(), - DataSize.of(1, KILOBYTE), - 0, - ImmutableList.of(createPage("p0a0v0", DataSize.of(10, BYTE)))); - - // single page, with spilling - testPollPages( - RetryPolicy.TASK, - ImmutableListMultimap.of(createTaskId(0, 0), createPage("p0a0v0", DataSize.of(2, KILOBYTE))), - ImmutableMap.of(), - DataSize.of(1, KILOBYTE), - 1, - ImmutableList.of(createPage("p0a0v0", DataSize.of(2, KILOBYTE)))); - - // discard single page, with no spilling - testPollPages( - RetryPolicy.TASK, - ImmutableListMultimap.builder() - .put(createTaskId(0, 0), createPage("p0a0v0", DataSize.of(6, KILOBYTE))) - .put(createTaskId(0, 1), createPage("p0a1v0", DataSize.of(3, KILOBYTE))) - .build(), - ImmutableMap.of(), - DataSize.of(10, KILOBYTE), - 0, - ImmutableList.of( - createPage("p0a0v0", DataSize.of(6, KILOBYTE)))); - - // discard single page, with spilling - testPollPages( - RetryPolicy.TASK, - ImmutableListMultimap.builder() - .put(createTaskId(0, 0), createPage("p0a0v0", DataSize.of(6, KILOBYTE))) - .put(createTaskId(0, 1), createPage("p0a1v0", DataSize.of(3, KILOBYTE))) - .build(), - ImmutableMap.of(), - DataSize.of(5, KILOBYTE), - 2, - ImmutableList.of( - createPage("p0a0v0", DataSize.of(6, KILOBYTE)))); - - // multiple pages, no spilling - testPollPages( - RetryPolicy.TASK, - ImmutableListMultimap.builder() - .put(createTaskId(0, 0), createPage("p0a0v0", DataSize.of(1, KILOBYTE))) - .put(createTaskId(1, 0), createPage("p1a0v0", DataSize.of(1, KILOBYTE))) - .put(createTaskId(0, 1), createPage("p0a1v0", DataSize.of(1, KILOBYTE))) - .build(), - ImmutableMap.of(), - DataSize.of(5, KILOBYTE), - 0, - ImmutableList.of( - createPage("p0a0v0", DataSize.of(1, KILOBYTE)), - createPage("p1a0v0", DataSize.of(1, KILOBYTE)))); - - // multiple pages, with spilling - testPollPages( - RetryPolicy.TASK, - ImmutableListMultimap.builder() - .put(createTaskId(0, 0), createPage("p0a0v0", DataSize.of(1, KILOBYTE))) - .put(createTaskId(0, 1), createPage("p0a1v0", DataSize.of(2, KILOBYTE))) - .put(createTaskId(1, 0), createPage("p1a0v0", DataSize.of(1, KILOBYTE))) - .build(), - ImmutableMap.of(), - DataSize.of(2, KILOBYTE), - 3, - ImmutableList.of( - createPage("p0a0v0", DataSize.of(1, KILOBYTE)), - createPage("p1a0v0", DataSize.of(1, KILOBYTE)))); - - // failure in a task that produced no pages, no spilling - testPollPages( - RetryPolicy.TASK, - ImmutableListMultimap.builder() - .put(createTaskId(0, 0), createPage("p0a0v0", DataSize.of(1, KILOBYTE))) - .put(createTaskId(0, 1), createPage("p0a1v0", DataSize.of(2, KILOBYTE))) - .put(createTaskId(1, 1), createPage("p1a1v0", DataSize.of(1, KILOBYTE))) - .build(), - ImmutableMap.of(createTaskId(1, 0), new RuntimeException("error")), - DataSize.of(10, KILOBYTE), - 0, - ImmutableList.of( - createPage("p0a0v0", DataSize.of(1, KILOBYTE)), - createPage("p1a1v0", DataSize.of(1, KILOBYTE)))); - - // failure in a task that produced no pages, with spilling - testPollPages( - RetryPolicy.TASK, - ImmutableListMultimap.builder() - .put(createTaskId(0, 0), createPage("p0a0v0", DataSize.of(1, KILOBYTE))) - .put(createTaskId(0, 1), createPage("p0a1v0", DataSize.of(2, KILOBYTE))) - .put(createTaskId(1, 1), createPage("p1a1v0", DataSize.of(1, KILOBYTE))) - .build(), - ImmutableMap.of(createTaskId(1, 0), new RuntimeException("error")), - DataSize.of(2, KILOBYTE), - 3, - ImmutableList.of( - createPage("p0a0v0", DataSize.of(1, KILOBYTE)), - createPage("p1a1v0", DataSize.of(1, KILOBYTE)))); - - RuntimeException error = new RuntimeException("error"); - - // buffer failure in a task that produced no pages, no spilling - testPollPagesFailure( - RetryPolicy.TASK, - ImmutableListMultimap.builder() - .put(createTaskId(0, 0), createPage("p0a0v0", DataSize.of(1, KILOBYTE))) - .put(createTaskId(0, 1), createPage("p0a1v0", DataSize.of(1, KILOBYTE))) - .put(createTaskId(1, 0), createPage("p1a0v0", DataSize.of(1, KILOBYTE))) - .build(), - ImmutableMap.of(createTaskId(2, 2), error), - DataSize.of(5, KILOBYTE), - 0, - error); - - // buffer failure in a task that produced some pages, no spilling - testPollPagesFailure( - RetryPolicy.TASK, - ImmutableListMultimap.builder() - .put(createTaskId(0, 1), createPage("p0a1v0", DataSize.of(1, KILOBYTE))) - .put(createTaskId(1, 0), createPage("p1a0v0", DataSize.of(1, KILOBYTE))) - .build(), - ImmutableMap.of(createTaskId(0, 1), error), - DataSize.of(5, KILOBYTE), - 0, - error); - - // buffer failure in a task that produced no pages, with spilling - testPollPagesFailure( - RetryPolicy.TASK, - ImmutableListMultimap.builder() - .put(createTaskId(0, 0), createPage("p0a0v0", DataSize.of(1, KILOBYTE))) - .put(createTaskId(0, 1), createPage("p0a1v0", DataSize.of(2, KILOBYTE))) - .put(createTaskId(1, 0), createPage("p1a0v0", DataSize.of(1, KILOBYTE))) - .build(), - ImmutableMap.of(createTaskId(2, 2), error), - DataSize.of(2, KILOBYTE), - 3, - error); - - // buffer failure in a task that produced some pages, with spilling - testPollPagesFailure( - RetryPolicy.TASK, - ImmutableListMultimap.builder() - .put(createTaskId(0, 1), createPage("p0a1v0", DataSize.of(1, KILOBYTE))) - .put(createTaskId(1, 0), createPage("p1a0v0", DataSize.of(1, KILOBYTE))) - .build(), - ImmutableMap.of(createTaskId(0, 1), error), - DataSize.of(1, KILOBYTE), - 2, - error); - } - private void testPollPages( RetryPolicy retryPolicy, Multimap pages, @@ -820,7 +651,6 @@ public void testRemainingBufferCapacity() public void testRemoteTaskFailedError() { testRemoteTaskFailedError(RetryPolicy.QUERY); - testRemoteTaskFailedError(RetryPolicy.TASK); } private void testRemoteTaskFailedError(RetryPolicy retryPolicy) From 766f7b257c727ed54ae774a029429b9910899d0c Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Tue, 21 Nov 2023 17:21:41 +0100 Subject: [PATCH 405/587] Use switch expression --- .../operator/DirectExchangeClientFactory.java | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/operator/DirectExchangeClientFactory.java b/core/trino-main/src/main/java/io/trino/operator/DirectExchangeClientFactory.java index 9ad7022bd6c3..737029857ee8 100644 --- a/core/trino-main/src/main/java/io/trino/operator/DirectExchangeClientFactory.java +++ b/core/trino-main/src/main/java/io/trino/operator/DirectExchangeClientFactory.java @@ -141,19 +141,12 @@ public DirectExchangeClient get( TaskFailureListener taskFailureListener, RetryPolicy retryPolicy) { - DirectExchangeBuffer buffer; - switch (retryPolicy) { - case TASK: - throw new UnsupportedOperationException(); - case QUERY: - buffer = new DeduplicatingDirectExchangeBuffer(scheduler, deduplicationBufferSize, retryPolicy, exchangeManagerRegistry, queryId, exchangeId); - break; - case NONE: - buffer = new StreamingDirectExchangeBuffer(scheduler, maxBufferedBytes); - break; - default: - throw new IllegalArgumentException("unexpected retry policy: " + retryPolicy); - } + @SuppressWarnings("resource") + DirectExchangeBuffer buffer = switch (retryPolicy) { + case TASK -> throw new UnsupportedOperationException(); + case QUERY -> new DeduplicatingDirectExchangeBuffer(scheduler, deduplicationBufferSize, retryPolicy, exchangeManagerRegistry, queryId, exchangeId); + case NONE -> new StreamingDirectExchangeBuffer(scheduler, maxBufferedBytes); + }; return new DirectExchangeClient( nodeInfo.getExternalAddress(), From 4cb3072a2aee3cc2fba08b9f70ffcba0f9190361 Mon Sep 17 00:00:00 2001 From: Jinyang Li Date: Wed, 15 Nov 2023 11:35:12 -0800 Subject: [PATCH 406/587] Fix ArrayIndexOutOfBoundsException when statistics are ignored --- .../parquet/ParquetPageSourceFactory.java | 6 +-- .../plugin/hive/BaseHiveConnectorTest.java | 37 +++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/parquet/ParquetPageSourceFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/parquet/ParquetPageSourceFactory.java index 4482eb3cd090..ef443a65e98d 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/parquet/ParquetPageSourceFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/parquet/ParquetPageSourceFactory.java @@ -234,8 +234,8 @@ public static ReaderPageSource createPageSource( List> parquetTupleDomains; List parquetPredicates; if (options.isIgnoreStatistics()) { - parquetTupleDomains = ImmutableList.of(); - parquetPredicates = ImmutableList.of(); + parquetTupleDomains = ImmutableList.of(TupleDomain.all()); + parquetPredicates = ImmutableList.of(buildPredicate(requestedSchema, TupleDomain.all(), descriptorsByPath, timeZone)); } else { ImmutableList.Builder> parquetTupleDomainsBuilder = ImmutableList.builderWithExpectedSize(disjunctTupleDomains.size()); @@ -255,7 +255,7 @@ public static ReaderPageSource createPageSource( ImmutableList.Builder> columnIndexes = ImmutableList.builder(); for (BlockMetaData block : parquetMetadata.getBlocks()) { long firstDataPage = block.getColumns().get(0).getFirstDataPageOffset(); - for (int i = 0; i < disjunctTupleDomains.size(); i++) { + for (int i = 0; i < parquetTupleDomains.size(); i++) { TupleDomain parquetTupleDomain = parquetTupleDomains.get(i); TupleDomainParquetPredicate parquetPredicate = parquetPredicates.get(i); Optional columnIndex = getColumnIndexStore(dataSource, block, descriptorsByPath, parquetTupleDomain, options); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java index 39d4f950067d..324e496a2303 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java @@ -5913,6 +5913,43 @@ public void testParquetWithMissingNestedColumns() assertUpdate(sessionUsingColumnIndex, "DROP TABLE " + missingNestedArrayTableName); } + @Test + public void testParquetIgnoreStatistics() + { + for (boolean ignoreStatistics : ImmutableList.of(true, false)) { + Session session = Session.builder(getSession()) + .setCatalogSessionProperty(catalog, "parquet_ignore_statistics", String.valueOf(ignoreStatistics)) + .build(); + + String tableName = "test_parquet_ignore_statistics"; + + assertUpdate(session, format( + "CREATE TABLE %s(" + + " a varchar, " + + " b varchar) " + + "WITH (format='PARQUET', partitioned_by=ARRAY['b'])", + tableName)); + assertUpdate(session, "INSERT INTO " + tableName + " VALUES ('a', 'b')", 1); + + assertQuery( + session, + "SELECT a, b FROM " + tableName, + "VALUES ('a', 'b')"); + + assertQuery( + session, + "SELECT a, b FROM " + tableName + " WHERE b = 'b'", + "VALUES ('a', 'b')"); + + assertQuery( + session, + "SELECT a, b FROM " + tableName + " WHERE a = 'a'", + "VALUES ('a', 'b')"); + + assertUpdate(session, "DROP TABLE " + tableName); + } + } + @Test public void testNestedColumnWithDuplicateName() { From 257c8ad0352f42465052a4f87efc3674cfcbd031 Mon Sep 17 00:00:00 2001 From: Tony Baeg Date: Thu, 16 Nov 2023 11:54:36 -0500 Subject: [PATCH 407/587] Update TableFunctionProcessorProvider.getDataProcessor to include ConnectorSession parameter --- .../trino/operator/TableFunctionOperator.java | 20 +++++++-- .../trino/operator/table/ExcludeColumns.java | 2 +- .../sql/planner/LocalExecutionPlanner.java | 1 + .../connector/TestingTableFunctions.java | 18 ++++---- .../table/TableFunctionProcessorProvider.java | 2 +- ...sLoaderSafeTableFunctionDataProcessor.java | 43 +++++++++++++++++++ ...derSafeTableFunctionProcessorProvider.java | 4 +- .../TestClassLoaderSafeWrappers.java | 2 + 8 files changed, 75 insertions(+), 17 deletions(-) create mode 100644 lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeTableFunctionDataProcessor.java diff --git a/core/trino-main/src/main/java/io/trino/operator/TableFunctionOperator.java b/core/trino-main/src/main/java/io/trino/operator/TableFunctionOperator.java index 7172673412a6..f309a6d145c5 100644 --- a/core/trino-main/src/main/java/io/trino/operator/TableFunctionOperator.java +++ b/core/trino-main/src/main/java/io/trino/operator/TableFunctionOperator.java @@ -22,6 +22,8 @@ import io.trino.memory.context.LocalMemoryContext; import io.trino.operator.RegularTableFunctionPartition.PassThroughColumnSpecification; import io.trino.spi.Page; +import io.trino.spi.connector.CatalogHandle; +import io.trino.spi.connector.ConnectorSession; import io.trino.spi.connector.SortOrder; import io.trino.spi.function.table.ConnectorTableFunctionHandle; import io.trino.spi.function.table.TableFunctionProcessorProvider; @@ -54,6 +56,7 @@ public static class TableFunctionOperatorFactory // a provider of table function processor to be called once per partition private final TableFunctionProcessorProvider tableFunctionProvider; + private final CatalogHandle catalogHandle; // all information necessary to execute the table function collected during analysis private final ConnectorTableFunctionHandle functionHandle; @@ -106,6 +109,7 @@ public TableFunctionOperatorFactory( int operatorId, PlanNodeId planNodeId, TableFunctionProcessorProvider tableFunctionProvider, + CatalogHandle catalogHandle, ConnectorTableFunctionHandle functionHandle, int properChannelsCount, int passThroughSourcesCount, @@ -124,6 +128,7 @@ public TableFunctionOperatorFactory( { requireNonNull(planNodeId, "planNodeId is null"); requireNonNull(tableFunctionProvider, "tableFunctionProvider is null"); + requireNonNull(catalogHandle, "catalogHandle is null"); requireNonNull(functionHandle, "functionHandle is null"); requireNonNull(requiredChannels, "requiredChannels is null"); requireNonNull(markerChannels, "markerChannels is null"); @@ -142,6 +147,7 @@ public TableFunctionOperatorFactory( this.operatorId = operatorId; this.planNodeId = planNodeId; this.tableFunctionProvider = tableFunctionProvider; + this.catalogHandle = catalogHandle; this.functionHandle = functionHandle; this.properChannelsCount = properChannelsCount; this.passThroughSourcesCount = passThroughSourcesCount; @@ -170,6 +176,7 @@ public Operator createOperator(DriverContext driverContext) return new TableFunctionOperator( operatorContext, tableFunctionProvider, + catalogHandle, functionHandle, properChannelsCount, passThroughSourcesCount, @@ -200,6 +207,7 @@ public OperatorFactory duplicate() operatorId, planNodeId, tableFunctionProvider, + catalogHandle, functionHandle, properChannelsCount, passThroughSourcesCount, @@ -219,7 +227,7 @@ public OperatorFactory duplicate() } private final OperatorContext operatorContext; - + private final ConnectorSession session; private final PageBuffer pageBuffer = new PageBuffer(); private final WorkProcessor outputPages; private final boolean processEmptyInput; @@ -227,6 +235,7 @@ public OperatorFactory duplicate() public TableFunctionOperator( OperatorContext operatorContext, TableFunctionProcessorProvider tableFunctionProvider, + CatalogHandle catalogHandle, ConnectorTableFunctionHandle functionHandle, int properChannelsCount, int passThroughSourcesCount, @@ -245,6 +254,7 @@ public TableFunctionOperator( { requireNonNull(operatorContext, "operatorContext is null"); requireNonNull(tableFunctionProvider, "tableFunctionProvider is null"); + requireNonNull(catalogHandle, "catalogHandle is null"); requireNonNull(functionHandle, "functionHandle is null"); requireNonNull(requiredChannels, "requiredChannels is null"); requireNonNull(markerChannels, "markerChannels is null"); @@ -261,7 +271,7 @@ public TableFunctionOperator( requireNonNull(pagesIndexFactory, "pagesIndexFactory is null"); this.operatorContext = operatorContext; - + this.session = operatorContext.getSession().toConnectorSession(catalogHandle); this.processEmptyInput = !pruneWhenEmpty; PagesIndex pagesIndex = pagesIndexFactory.newPagesIndex(sourceTypes, expectedPositions); @@ -273,6 +283,7 @@ public TableFunctionOperator( groupPagesIndex, hashStrategies, tableFunctionProvider, + session, functionHandle, properChannelsCount, passThroughSourcesCount, @@ -517,6 +528,7 @@ private WorkProcessor pagesIndexToTableFunctionPartition PagesIndex pagesIndex, HashStrategies hashStrategies, TableFunctionProcessorProvider tableFunctionProvider, + ConnectorSession session, ConnectorTableFunctionHandle functionHandle, int properChannelsCount, int passThroughSourcesCount, @@ -542,7 +554,7 @@ public WorkProcessor.ProcessState process() // empty PagesIndex can only be passed once as the result of PartitionAndSort. Neither this nor any future instance of Process will ever get an empty PagesIndex again. processEmpty = false; return WorkProcessor.ProcessState.ofResult(new EmptyTableFunctionPartition( - tableFunctionProvider.getDataProcessor(functionHandle), + tableFunctionProvider.getDataProcessor(session, functionHandle), properChannelsCount, passThroughSourcesCount, passThroughSpecifications.stream() @@ -562,7 +574,7 @@ public WorkProcessor.ProcessState process() pagesIndex, partitionStart, partitionEnd, - tableFunctionProvider.getDataProcessor(functionHandle), + tableFunctionProvider.getDataProcessor(session, functionHandle), properChannelsCount, passThroughSourcesCount, requiredChannels, diff --git a/core/trino-main/src/main/java/io/trino/operator/table/ExcludeColumns.java b/core/trino-main/src/main/java/io/trino/operator/table/ExcludeColumns.java index 2ef635f3a946..d650c97b2b98 100644 --- a/core/trino-main/src/main/java/io/trino/operator/table/ExcludeColumns.java +++ b/core/trino-main/src/main/java/io/trino/operator/table/ExcludeColumns.java @@ -154,7 +154,7 @@ public static TableFunctionProcessorProvider getExcludeColumnsFunctionProcessorP return new TableFunctionProcessorProvider() { @Override - public TableFunctionDataProcessor getDataProcessor(ConnectorTableFunctionHandle handle) + public TableFunctionDataProcessor getDataProcessor(ConnectorSession session, ConnectorTableFunctionHandle handle) { return input -> { if (input == null) { diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/LocalExecutionPlanner.java b/core/trino-main/src/main/java/io/trino/sql/planner/LocalExecutionPlanner.java index b00cc38fab58..a4d67d8e88d4 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/LocalExecutionPlanner.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/LocalExecutionPlanner.java @@ -1723,6 +1723,7 @@ public PhysicalOperation visitTableFunctionProcessor(TableFunctionProcessorNode context.getNextOperatorId(), node.getId(), processorProvider, + node.getHandle().getCatalogHandle(), node.getHandle().getFunctionHandle(), properChannelsCount, toIntExact(passThroughSourcesCount), diff --git a/core/trino-main/src/test/java/io/trino/connector/TestingTableFunctions.java b/core/trino-main/src/test/java/io/trino/connector/TestingTableFunctions.java index 8929d8758d4f..81ddc2f40a4e 100644 --- a/core/trino-main/src/test/java/io/trino/connector/TestingTableFunctions.java +++ b/core/trino-main/src/test/java/io/trino/connector/TestingTableFunctions.java @@ -610,7 +610,7 @@ public static class IdentityFunctionProcessorProvider implements TableFunctionProcessorProvider { @Override - public TableFunctionDataProcessor getDataProcessor(ConnectorTableFunctionHandle handle) + public TableFunctionDataProcessor getDataProcessor(ConnectorSession session, ConnectorTableFunctionHandle handle) { return input -> { if (input == null) { @@ -659,7 +659,7 @@ public static class IdentityPassThroughFunctionProcessorProvider implements TableFunctionProcessorProvider { @Override - public TableFunctionDataProcessor getDataProcessor(ConnectorTableFunctionHandle handle) + public TableFunctionDataProcessor getDataProcessor(ConnectorSession session, ConnectorTableFunctionHandle handle) { return new IdentityPassThroughFunctionProcessor(); } @@ -750,7 +750,7 @@ public static class RepeatFunctionProcessorProvider implements TableFunctionProcessorProvider { @Override - public TableFunctionDataProcessor getDataProcessor(ConnectorTableFunctionHandle handle) + public TableFunctionDataProcessor getDataProcessor(ConnectorSession session, ConnectorTableFunctionHandle handle) { return new RepeatFunctionProcessor(((RepeatFunctionHandle) handle).getCount()); } @@ -848,7 +848,7 @@ public static class EmptyOutputProcessorProvider implements TableFunctionProcessorProvider { @Override - public TableFunctionDataProcessor getDataProcessor(ConnectorTableFunctionHandle handle) + public TableFunctionDataProcessor getDataProcessor(ConnectorSession session, ConnectorTableFunctionHandle handle) { return new EmptyOutputProcessor(); } @@ -906,7 +906,7 @@ public static class EmptyOutputWithPassThroughProcessorProvider implements TableFunctionProcessorProvider { @Override - public TableFunctionDataProcessor getDataProcessor(ConnectorTableFunctionHandle handle) + public TableFunctionDataProcessor getDataProcessor(ConnectorSession session, ConnectorTableFunctionHandle handle) { return new EmptyOutputWithPassThroughProcessor(); } @@ -982,7 +982,7 @@ public static class TestInputsFunctionProcessorProvider implements TableFunctionProcessorProvider { @Override - public TableFunctionDataProcessor getDataProcessor(ConnectorTableFunctionHandle handle) + public TableFunctionDataProcessor getDataProcessor(ConnectorSession session, ConnectorTableFunctionHandle handle) { BlockBuilder resultBuilder = BOOLEAN.createBlockBuilder(null, 1); BOOLEAN.writeBoolean(resultBuilder, true); @@ -1043,7 +1043,7 @@ public static class PassThroughInputProcessorProvider implements TableFunctionProcessorProvider { @Override - public TableFunctionDataProcessor getDataProcessor(ConnectorTableFunctionHandle handle) + public TableFunctionDataProcessor getDataProcessor(ConnectorSession session, ConnectorTableFunctionHandle handle) { return new PassThroughInputProcessor(); } @@ -1142,7 +1142,7 @@ public static class TestInputProcessorProvider implements TableFunctionProcessorProvider { @Override - public TableFunctionDataProcessor getDataProcessor(ConnectorTableFunctionHandle handle) + public TableFunctionDataProcessor getDataProcessor(ConnectorSession session, ConnectorTableFunctionHandle handle) { return new TestInputProcessor(); } @@ -1206,7 +1206,7 @@ public static class TestSingleInputFunctionProcessorProvider implements TableFunctionProcessorProvider { @Override - public TableFunctionDataProcessor getDataProcessor(ConnectorTableFunctionHandle handle) + public TableFunctionDataProcessor getDataProcessor(ConnectorSession session, ConnectorTableFunctionHandle handle) { BlockBuilder builder = BOOLEAN.createBlockBuilder(null, 1); BOOLEAN.writeBoolean(builder, true); diff --git a/core/trino-spi/src/main/java/io/trino/spi/function/table/TableFunctionProcessorProvider.java b/core/trino-spi/src/main/java/io/trino/spi/function/table/TableFunctionProcessorProvider.java index 0be4cd2ed585..d0d70448ead0 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/function/table/TableFunctionProcessorProvider.java +++ b/core/trino-spi/src/main/java/io/trino/spi/function/table/TableFunctionProcessorProvider.java @@ -24,7 +24,7 @@ public interface TableFunctionProcessorProvider * This method returns a {@code TableFunctionDataProcessor}. All the necessary information collected during analysis is available * in the form of {@link ConnectorTableFunctionHandle}. It is called once per each partition processed by the table function. */ - default TableFunctionDataProcessor getDataProcessor(ConnectorTableFunctionHandle handle) + default TableFunctionDataProcessor getDataProcessor(ConnectorSession session, ConnectorTableFunctionHandle handle) { throw new UnsupportedOperationException("this table function does not process input data"); } diff --git a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeTableFunctionDataProcessor.java b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeTableFunctionDataProcessor.java new file mode 100644 index 000000000000..ccf073d671f3 --- /dev/null +++ b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeTableFunctionDataProcessor.java @@ -0,0 +1,43 @@ +/* + * 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 io.trino.plugin.base.classloader; + +import io.trino.spi.Page; +import io.trino.spi.classloader.ThreadContextClassLoader; +import io.trino.spi.function.table.TableFunctionDataProcessor; +import io.trino.spi.function.table.TableFunctionProcessorState; + +import java.util.List; +import java.util.Optional; + +public class ClassLoaderSafeTableFunctionDataProcessor + implements TableFunctionDataProcessor +{ + private final TableFunctionDataProcessor delegate; + private final ClassLoader classLoader; + + public ClassLoaderSafeTableFunctionDataProcessor(TableFunctionDataProcessor delegate, ClassLoader classLoader) + { + this.delegate = delegate; + this.classLoader = classLoader; + } + + @Override + public TableFunctionProcessorState process(List> input) + { + try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(classLoader)) { + return delegate.process(input); + } + } +} diff --git a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeTableFunctionProcessorProvider.java b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeTableFunctionProcessorProvider.java index 51f54e99ea67..c95483774291 100644 --- a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeTableFunctionProcessorProvider.java +++ b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeTableFunctionProcessorProvider.java @@ -36,10 +36,10 @@ public ClassLoaderSafeTableFunctionProcessorProvider(TableFunctionProcessorProvi } @Override - public TableFunctionDataProcessor getDataProcessor(ConnectorTableFunctionHandle handle) + public TableFunctionDataProcessor getDataProcessor(ConnectorSession session, ConnectorTableFunctionHandle handle) { try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(classLoader)) { - return delegate.getDataProcessor(handle); + return delegate.getDataProcessor(session, handle); } } diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/classloader/TestClassLoaderSafeWrappers.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/classloader/TestClassLoaderSafeWrappers.java index d0834ec00fa6..66a1412e61a5 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/classloader/TestClassLoaderSafeWrappers.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/classloader/TestClassLoaderSafeWrappers.java @@ -27,6 +27,7 @@ import io.trino.spi.connector.SystemTable; import io.trino.spi.eventlistener.EventListener; import io.trino.spi.function.table.ConnectorTableFunction; +import io.trino.spi.function.table.TableFunctionDataProcessor; import io.trino.spi.function.table.TableFunctionProcessorProvider; import io.trino.spi.function.table.TableFunctionSplitProcessor; import org.junit.jupiter.api.Test; @@ -61,6 +62,7 @@ public void test() testClassLoaderSafe(EventListener.class, ClassLoaderSafeEventListener.class); testClassLoaderSafe(ConnectorTableFunction.class, ClassLoaderSafeConnectorTableFunction.class); testClassLoaderSafe(TableFunctionSplitProcessor.class, ClassLoaderSafeTableFunctionSplitProcessor.class); + testClassLoaderSafe(TableFunctionDataProcessor.class, ClassLoaderSafeTableFunctionDataProcessor.class); testClassLoaderSafe(TableFunctionProcessorProvider.class, ClassLoaderSafeTableFunctionProcessorProvider.class); } From 93c0040ccde79e576d507818a573b374326a19f2 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Thu, 23 Nov 2023 00:22:25 -0800 Subject: [PATCH 408/587] Add support for FILTER clause in LISTAGG --- .../src/main/antlr4/io/trino/grammar/sql/SqlBase.g4 | 3 ++- .../test/java/io/trino/sql/query/TestListagg.java | 13 +++++++++++++ .../main/java/io/trino/sql/ExpressionFormatter.java | 4 ++++ .../main/java/io/trino/sql/parser/AstBuilder.java | 2 +- 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/core/trino-grammar/src/main/antlr4/io/trino/grammar/sql/SqlBase.g4 b/core/trino-grammar/src/main/antlr4/io/trino/grammar/sql/SqlBase.g4 index 0f01927a5023..c13530d62a80 100644 --- a/core/trino-grammar/src/main/antlr4/io/trino/grammar/sql/SqlBase.g4 +++ b/core/trino-grammar/src/main/antlr4/io/trino/grammar/sql/SqlBase.g4 @@ -566,7 +566,8 @@ primaryExpression | ROW '(' expression (',' expression)* ')' #rowConstructor | name=LISTAGG '(' setQuantifier? expression (',' string)? (ON OVERFLOW listAggOverflowBehavior)? ')' - (WITHIN GROUP '(' ORDER BY sortItem (',' sortItem)* ')') #listagg + (WITHIN GROUP '(' ORDER BY sortItem (',' sortItem)* ')') + filter? #listagg | processingMode? qualifiedName '(' (label=identifier '.')? ASTERISK ')' filter? over? #functionCall | processingMode? qualifiedName '(' (setQuantifier? expression (',' expression)*)? diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestListagg.java b/core/trino-main/src/test/java/io/trino/sql/query/TestListagg.java index 725a3b21f9f6..4c2e6ca37294 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestListagg.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestListagg.java @@ -391,4 +391,17 @@ public void testListaggQueryGroupingOverflowTruncateWithCountAndWithOverflowFill " (1, VARCHAR '" + largeValue + ",everything,.....(2)')," + " (2, VARCHAR 'listagg,string joiner')"); } + + @Test + void testFilter() + { + assertThat(assertions.query( + """ + SELECT listagg(value, ',') WITHIN GROUP (ORDER BY id) FILTER (WHERE id % 2 = 0) + FROM ( + VALUES (1, 'a'), (2, 'b'), (3, 'c'), (4, 'd') + ) t(id, value) + """)) + .matches("VALUES VARCHAR 'b,d'"); + } } diff --git a/core/trino-parser/src/main/java/io/trino/sql/ExpressionFormatter.java b/core/trino-parser/src/main/java/io/trino/sql/ExpressionFormatter.java index 743d4a181568..ad0b99952256 100644 --- a/core/trino-parser/src/main/java/io/trino/sql/ExpressionFormatter.java +++ b/core/trino-parser/src/main/java/io/trino/sql/ExpressionFormatter.java @@ -1023,6 +1023,10 @@ private String visitListagg(FunctionCall node) .append(')'); } + if (node.getFilter().isPresent()) { + builder.append(" FILTER ").append(visitFilter(node.getFilter().get(), null)); + } + return builder.toString(); } } diff --git a/core/trino-parser/src/main/java/io/trino/sql/parser/AstBuilder.java b/core/trino-parser/src/main/java/io/trino/sql/parser/AstBuilder.java index b2581d57cfb8..fd66be00a9ff 100644 --- a/core/trino-parser/src/main/java/io/trino/sql/parser/AstBuilder.java +++ b/core/trino-parser/src/main/java/io/trino/sql/parser/AstBuilder.java @@ -2457,7 +2457,7 @@ else if (listaggCountIndicationContext.WITHOUT() != null) { Optional.of(getLocation(context)), QualifiedName.of("LISTAGG"), window, - Optional.empty(), + visitIfPresent(context.filter(), Expression.class), Optional.of(orderBy), distinct, Optional.empty(), From ee27de460faba5239c521cb7ecb387aaa86731a4 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Wed, 22 Nov 2023 09:45:52 -0800 Subject: [PATCH 409/587] Fix incorrect comparison for negative zero --- .../main/java/io/trino/spi/type/DoubleType.java | 14 ++++++++++++-- .../src/main/java/io/trino/spi/type/RealType.java | 13 +++++++++++-- .../io/trino/spi/predicate/TestTupleDomain.java | 15 +++++++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/core/trino-spi/src/main/java/io/trino/spi/type/DoubleType.java b/core/trino-spi/src/main/java/io/trino/spi/type/DoubleType.java index 4a9175875e70..1a6a3c37016c 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/type/DoubleType.java +++ b/core/trino-spi/src/main/java/io/trino/spi/type/DoubleType.java @@ -242,7 +242,7 @@ private static boolean distinctFromOperator(double left, @IsNull boolean leftNul @ScalarOperator(COMPARISON_UNORDERED_LAST) private static long comparisonUnorderedLastOperator(double left, double right) { - return Double.compare(left, right); + return compare(left, right); } @ScalarOperator(COMPARISON_UNORDERED_FIRST) @@ -258,7 +258,8 @@ private static long comparisonUnorderedFirstOperator(double left, double right) if (Double.isNaN(right)) { return 1; } - return Double.compare(left, right); + + return compare(left, right); } @ScalarOperator(LESS_THAN) @@ -272,4 +273,13 @@ private static boolean lessThanOrEqualOperator(double left, double right) { return left <= right; } + + private static int compare(double left, double right) + { + if (left == right) { // Double.compare considers 0.0 and -0.0 different from each other + return 0; + } + + return Double.compare(left, right); + } } diff --git a/core/trino-spi/src/main/java/io/trino/spi/type/RealType.java b/core/trino-spi/src/main/java/io/trino/spi/type/RealType.java index da26b556cefd..f7b6eea4f7cf 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/type/RealType.java +++ b/core/trino-spi/src/main/java/io/trino/spi/type/RealType.java @@ -183,7 +183,7 @@ private static boolean distinctFromOperator(long left, @IsNull boolean leftNull, @ScalarOperator(COMPARISON_UNORDERED_LAST) private static long comparisonUnorderedLastOperator(long left, long right) { - return Float.compare(intBitsToFloat((int) left), intBitsToFloat((int) right)); + return compare(intBitsToFloat((int) left), intBitsToFloat((int) right)); } @ScalarOperator(COMPARISON_UNORDERED_FIRST) @@ -201,7 +201,7 @@ private static long comparisonUnorderedFirstOperator(long leftBits, long rightBi if (Float.isNaN(right)) { return 1; } - return Float.compare(left, right); + return compare(left, right); } @ScalarOperator(LESS_THAN) @@ -215,4 +215,13 @@ private static boolean lessThanOrEqualOperator(long left, long right) { return intBitsToFloat((int) left) <= intBitsToFloat((int) right); } + + private static int compare(float left, float right) + { + if (left == right) { // Float.compare considers 0.0 and -0.0 different from each other + return 0; + } + + return Float.compare(left, right); + } } diff --git a/core/trino-spi/src/test/java/io/trino/spi/predicate/TestTupleDomain.java b/core/trino-spi/src/test/java/io/trino/spi/predicate/TestTupleDomain.java index 613607f853d2..16ab08700e75 100644 --- a/core/trino-spi/src/test/java/io/trino/spi/predicate/TestTupleDomain.java +++ b/core/trino-spi/src/test/java/io/trino/spi/predicate/TestTupleDomain.java @@ -462,6 +462,16 @@ public void testContains() ImmutableMap.of( A, Domain.singleValue(BIGINT, 0L), B, Domain.none(VARCHAR)))).isTrue(); + + assertThat(contains( + ImmutableMap.of(A, Domain.singleValue(DOUBLE, 0.0)), + ImmutableMap.of(A, Domain.singleValue(DOUBLE, -0.0)))) + .isTrue(); + + assertThat(contains( + ImmutableMap.of(A, Domain.singleValue(DOUBLE, -0.0)), + ImmutableMap.of(A, Domain.singleValue(DOUBLE, 0.0)))) + .isTrue(); } @Test @@ -575,6 +585,11 @@ public void testEquals() ImmutableMap.of( A, Domain.singleValue(BIGINT, 0L), C, Domain.singleValue(DOUBLE, 0.0)))).isFalse(); + + assertThat(equals( + ImmutableMap.of(A, Domain.singleValue(DOUBLE, 0.0)), + ImmutableMap.of(A, Domain.singleValue(DOUBLE, -0.0)))) + .isTrue(); } @Test From 08baad3eaba0e62dbc8bb6a4e8b8ec7f3d7c7480 Mon Sep 17 00:00:00 2001 From: Raunaq Morarka Date: Thu, 23 Nov 2023 23:51:55 +0530 Subject: [PATCH 410/587] Fix failure from reading parquet column index for INT96 org.apache.parquet.format.converter.ParquetMetadataConverter#fromParquetColumnIndex returns null if the parquet primitive type for a column does not support min/max stats. Fixed query failure by avoiding reading column index for such cases. --- .../TupleDomainParquetPredicate.java | 16 ++++++++++++ .../plugin/hive/TestParquetPageSkipping.java | 24 ++++++++++++++++++ .../issue-16801.parquet | Bin 0 -> 989 bytes 3 files changed, 40 insertions(+) create mode 100644 plugin/trino-hive/src/test/resources/parquet_page_skipping/unsupported_column_index/issue-16801.parquet diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/predicate/TupleDomainParquetPredicate.java b/lib/trino-parquet/src/main/java/io/trino/parquet/predicate/TupleDomainParquetPredicate.java index 7f23d4a0fa7b..6dcc6b0743ff 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/predicate/TupleDomainParquetPredicate.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/predicate/TupleDomainParquetPredicate.java @@ -47,6 +47,7 @@ import org.apache.parquet.internal.filter2.columnindex.ColumnIndexStore; import org.apache.parquet.io.ParquetDecodingException; import org.apache.parquet.io.api.Binary; +import org.apache.parquet.schema.ColumnOrder; import org.apache.parquet.schema.LogicalTypeAnnotation; import org.apache.parquet.schema.LogicalTypeAnnotation.TimestampLogicalTypeAnnotation; import org.apache.parquet.schema.PrimitiveType; @@ -207,6 +208,10 @@ public boolean matches(Map valueCounts, ColumnIndexStore continue; } + // ParquetMetadataConverter#fromParquetColumnIndex returns null if the parquet primitive type does not support min/max stats + if (!isColumnIndexStatsSupported(column.getPrimitiveType())) { + continue; + } ColumnIndex columnIndex = columnIndexStore.getColumnIndex(ColumnPath.get(column.getPath())); if (columnIndex == null) { continue; @@ -685,6 +690,11 @@ private FilterPredicate convertToParquetFilter(DateTimeZone timeZone) continue; } + // ParquetMetadataConverter#fromParquetColumnIndex returns null if the parquet primitive type does not support min/max stats + if (!isColumnIndexStatsSupported(column.getPrimitiveType())) { + continue; + } + FilterPredicate columnFilter = FilterApi.userDefined( new TrinoIntColumn(ColumnPath.get(column.getPath())), new DomainUserDefinedPredicate<>(column, domain, timeZone)); @@ -808,4 +818,10 @@ private static final class TrinoIntColumn super(columnPath, Integer.class); } } + + // Copy of org.apache.parquet.format.converter.ParquetMetadataConverter#isMinMaxStatsSupported + private static boolean isColumnIndexStatsSupported(PrimitiveType type) + { + return type.columnOrder().getColumnOrderName() == ColumnOrder.ColumnOrderName.TYPE_DEFINED_ORDER; + } } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestParquetPageSkipping.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestParquetPageSkipping.java index 0ffa7cd64f7f..c3e41ac1881d 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestParquetPageSkipping.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestParquetPageSkipping.java @@ -126,6 +126,30 @@ public void testFilteringOnColumnNameWithDot() assertUpdate("DROP TABLE " + tableName); } + @Test + public void testUnsupportedColumnIndex() + throws URISyntaxException + { + String tableName = "test_unsupported_column_index_" + randomNameSuffix(); + + // Test for https://github.com/trinodb/trino/issues/16801 + File parquetFile = new File(Resources.getResource("parquet_page_skipping/unsupported_column_index").toURI()); + assertUpdate(format( + "CREATE TABLE %s (stime timestamp(3), btime timestamp(3), detail varchar) WITH (format = 'PARQUET', external_location = '%s')", + tableName, + parquetFile.getAbsolutePath())); + + assertQuery( + "SELECT * FROM " + tableName + " WHERE btime >= timestamp '2023-03-27 13:30:00'", + "VALUES ('2023-03-31 18:00:00.000', '2023-03-31 18:00:00.000', 'record_1')"); + + assertQuery( + "SELECT * FROM " + tableName + " WHERE detail = 'record_2'", + "VALUES ('2023-03-31 18:00:00.000', null, 'record_2')"); + + assertUpdate("DROP TABLE " + tableName); + } + @Test public void testPageSkipping() { diff --git a/plugin/trino-hive/src/test/resources/parquet_page_skipping/unsupported_column_index/issue-16801.parquet b/plugin/trino-hive/src/test/resources/parquet_page_skipping/unsupported_column_index/issue-16801.parquet new file mode 100644 index 0000000000000000000000000000000000000000..6e69c4e945df6ea5c91e02f1bd3cda36eedc0b76 GIT binary patch literal 989 zcmb_bJxc>Y5S`m>&J(UtoaGj_xF$yg4R>e03JF*QETV|Eg51Xj50WT}T3Uz*wqk8* zW#w**0w#QXrbsJK?-0lLB7r}OMrm$$d*(F zMICj3S(~V9s%aFQ6M;G}QJq`G(NWTuNEl^ZHgH;wA?i>zzcN`95%7$Yq!lQpoc~u* z&@=+hS>aPgI3eSSIM+nf$^V78SnZ^52C#MwrEMsqgJHbqeZl_k))a_yR}^Sw-srJB z3X Date: Thu, 23 Nov 2023 11:23:31 +0100 Subject: [PATCH 411/587] Test Iceberg information_schema.tables cost with Glue --- .../TestIcebergGlueCatalogAccessOperations.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogAccessOperations.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogAccessOperations.java index 42ff3e2a441f..ec25c1ef46fb 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogAccessOperations.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogAccessOperations.java @@ -458,7 +458,7 @@ public void testSelectSystemTable() } @Test - public void testInformationSchemaColumns() + public void testInformationSchemaTableAndColumns() { String schemaName = "test_i_s_columns_schema" + randomNameSuffix(); assertUpdate("CREATE SCHEMA " + schemaName); @@ -483,7 +483,7 @@ public void testInformationSchemaColumns() assertUpdate(session, "CREATE TABLE test_other_select_i_s_columns" + i + "(id varchar, age integer)"); // won't match the filter } - // Bulk retrieval + // Bulk columns retrieval assertInvocations( session, "SELECT * FROM information_schema.columns WHERE table_schema = CURRENT_SCHEMA AND table_name LIKE 'test_select_i_s_columns%'", @@ -493,7 +493,16 @@ public void testInformationSchemaColumns() ImmutableMultiset.of()); } - // Pointed lookup + // Tables listing + assertInvocations( + session, + "SELECT * FROM information_schema.tables WHERE table_schema = CURRENT_SCHEMA", + ImmutableMultiset.builder() + .addCopies(GET_TABLES, 2) + .build(), + ImmutableMultiset.of()); + + // Pointed columns lookup assertInvocations( session, "SELECT * FROM information_schema.columns WHERE table_schema = CURRENT_SCHEMA AND table_name = 'test_select_i_s_columns0'", @@ -504,7 +513,7 @@ public void testInformationSchemaColumns() .add(new FileOperation(METADATA_JSON, INPUT_FILE_NEW_STREAM)) .build()); - // Pointed lookup via DESCRIBE (which does some additional things before delegating to information_schema.columns) + // Pointed columns lookup via DESCRIBE (which does some additional things before delegating to information_schema.columns) assertInvocations( session, "DESCRIBE test_select_i_s_columns0", From faa6054f994cf65541cc358ae8de9d542f74c64d Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Mon, 20 Nov 2023 15:52:01 +0100 Subject: [PATCH 412/587] Add getRelationTypes for listing relations with their types --- .../trino/jdbc/TestTrinoDatabaseMetaData.java | 35 ++++----- .../InformationSchemaPageSource.java | 26 +++++-- .../connector/system/jdbc/TableJdbcTable.java | 12 ++- .../main/java/io/trino/metadata/Metadata.java | 7 ++ .../io/trino/metadata/MetadataListing.java | 25 ++++-- .../io/trino/metadata/MetadataManager.java | 58 +++++++++++--- .../tracing/TracingConnectorMetadata.java | 10 +++ .../io/trino/tracing/TracingMetadata.java | 10 +++ .../trino/metadata/AbstractMockMetadata.java | 7 ++ .../spi/connector/ConnectorMetadata.java | 22 ++++++ .../io/trino/spi/connector/RelationType.java | 21 +++++ .../ClassLoaderSafeConnectorMetadata.java | 9 +++ ...estIcebergGlueCatalogAccessOperations.java | 2 +- .../TestInformationSchemaConnector.java | 78 ++++++++----------- 14 files changed, 223 insertions(+), 99 deletions(-) create mode 100644 core/trino-spi/src/main/java/io/trino/spi/connector/RelationType.java diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDatabaseMetaData.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDatabaseMetaData.java index 7ce476f211a2..584fe873db70 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDatabaseMetaData.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDatabaseMetaData.java @@ -1116,8 +1116,7 @@ public void testGetTablesMetadataCalls() databaseMetaData -> databaseMetaData.getTables(null, null, null, null), list("TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", "TABLE_TYPE")), ImmutableMultiset.builder() - .add("ConnectorMetadata.listViews") - .add("ConnectorMetadata.listTables") + .add("ConnectorMetadata.getRelationTypes") .build()); // Equality predicate on catalog name @@ -1127,8 +1126,7 @@ public void testGetTablesMetadataCalls() databaseMetaData -> databaseMetaData.getTables(COUNTING_CATALOG, null, null, null), list("TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", "TABLE_TYPE")), ImmutableMultiset.builder() - .add("ConnectorMetadata.listViews") - .add("ConnectorMetadata.listTables") + .add("ConnectorMetadata.getRelationTypes") .build()); // Equality predicate on schema name @@ -1142,8 +1140,7 @@ public void testGetTablesMetadataCalls() .map(schemaTableName -> list(COUNTING_CATALOG, schemaTableName.getSchemaName(), schemaTableName.getTableName(), "TABLE")) .collect(toImmutableList()), ImmutableMultiset.builder() - .add("ConnectorMetadata.listViews(schema=test_schema1)") - .add("ConnectorMetadata.listTables(schema=test_schema1)") + .add("ConnectorMetadata.getRelationTypes(schema=test_schema1)") .build()); // LIKE predicate on schema name @@ -1157,8 +1154,7 @@ public void testGetTablesMetadataCalls() .map(schemaTableName -> list(COUNTING_CATALOG, schemaTableName.getSchemaName(), schemaTableName.getTableName(), "TABLE")) .collect(toImmutableList()), ImmutableMultiset.builder() - .add("ConnectorMetadata.listViews") - .add("ConnectorMetadata.listTables") + .add("ConnectorMetadata.getRelationTypes") .build()); // Equality predicate on table name @@ -1171,8 +1167,7 @@ public void testGetTablesMetadataCalls() list(COUNTING_CATALOG, "test_schema1", "test_table1", "TABLE"), list(COUNTING_CATALOG, "test_schema2", "test_table1", "TABLE")), ImmutableMultiset.builder() - .add("ConnectorMetadata.listViews") - .add("ConnectorMetadata.listTables") + .add("ConnectorMetadata.getRelationTypes") .build()); // LIKE predicate on table name @@ -1185,8 +1180,7 @@ public void testGetTablesMetadataCalls() list(COUNTING_CATALOG, "test_schema1", "test_table1", "TABLE"), list(COUNTING_CATALOG, "test_schema2", "test_table1", "TABLE")), ImmutableMultiset.builder() - .add("ConnectorMetadata.listViews") - .add("ConnectorMetadata.listTables") + .add("ConnectorMetadata.getRelationTypes") .build()); // Equality predicate on schema name and table name @@ -1197,8 +1191,8 @@ public void testGetTablesMetadataCalls() list("TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", "TABLE_TYPE")), list(list(COUNTING_CATALOG, "test_schema1", "test_table1", "TABLE")), ImmutableMultiset.builder() - .addCopies("ConnectorMetadata.getSystemTable(schema=test_schema1, table=test_table1)", 5) - .addCopies("ConnectorMetadata.getView(schema=test_schema1, table=test_table1)", 2) + .addCopies("ConnectorMetadata.getSystemTable(schema=test_schema1, table=test_table1)", 4) + .add("ConnectorMetadata.getView(schema=test_schema1, table=test_table1)") .add("ConnectorMetadata.getMaterializedView(schema=test_schema1, table=test_table1)") .add("ConnectorMetadata.redirectTable(schema=test_schema1, table=test_table1)") .add("ConnectorMetadata.getTableHandle(schema=test_schema1, table=test_table1)") @@ -1212,8 +1206,7 @@ public void testGetTablesMetadataCalls() list("TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", "TABLE_TYPE")), list(list(COUNTING_CATALOG, "test_schema1", "test_table1", "TABLE")), ImmutableMultiset.builder() - .add("ConnectorMetadata.listViews") - .add("ConnectorMetadata.listTables") + .add("ConnectorMetadata.getRelationTypes") .build()); // catalog does not exist @@ -1530,8 +1523,7 @@ private void testAssumeLiteralMetadataCalls(String escapeLiteralParameter) .map(schemaTableName -> list(COUNTING_CATALOG, schemaTableName.getSchemaName(), schemaTableName.getTableName(), "TABLE")) .collect(toImmutableList()), ImmutableMultiset.builder() - .add("ConnectorMetadata.listViews(schema=test_schema1)") - .add("ConnectorMetadata.listTables(schema=test_schema1)") + .add("ConnectorMetadata.getRelationTypes(schema=test_schema1)") .build()); // getTables's schema and table name patterns treated as literals @@ -1542,8 +1534,8 @@ private void testAssumeLiteralMetadataCalls(String escapeLiteralParameter) list("TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", "TABLE_TYPE")), list(list(COUNTING_CATALOG, "test_schema1", "test_table1", "TABLE")), ImmutableMultiset.builder() - .addCopies("ConnectorMetadata.getSystemTable(schema=test_schema1, table=test_table1)", 5) - .addCopies("ConnectorMetadata.getView(schema=test_schema1, table=test_table1)", 2) + .addCopies("ConnectorMetadata.getSystemTable(schema=test_schema1, table=test_table1)", 4) + .add("ConnectorMetadata.getView(schema=test_schema1, table=test_table1)") .add("ConnectorMetadata.getMaterializedView(schema=test_schema1, table=test_table1)") .add("ConnectorMetadata.redirectTable(schema=test_schema1, table=test_table1)") .add("ConnectorMetadata.getTableHandle(schema=test_schema1, table=test_table1)") @@ -1557,8 +1549,7 @@ private void testAssumeLiteralMetadataCalls(String escapeLiteralParameter) list("TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", "TABLE_TYPE")), list(), ImmutableMultiset.builder() - .add("ConnectorMetadata.listViews(schema=test_schema_)") - .add("ConnectorMetadata.listTables(schema=test_schema_)") + .add("ConnectorMetadata.getRelationTypes(schema=test_schema_)") .build()); // getColumns's schema and table name patterns treated as literals diff --git a/core/trino-main/src/main/java/io/trino/connector/informationschema/InformationSchemaPageSource.java b/core/trino-main/src/main/java/io/trino/connector/informationschema/InformationSchemaPageSource.java index 521369d5c7cc..143cc6493c63 100644 --- a/core/trino-main/src/main/java/io/trino/connector/informationschema/InformationSchemaPageSource.java +++ b/core/trino-main/src/main/java/io/trino/connector/informationschema/InformationSchemaPageSource.java @@ -26,6 +26,7 @@ import io.trino.spi.connector.ColumnHandle; import io.trino.spi.connector.ColumnMetadata; import io.trino.spi.connector.ConnectorPageSource; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.security.AccessDeniedException; import io.trino.spi.security.GrantInfo; @@ -37,6 +38,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; import java.util.OptionalLong; import java.util.Queue; @@ -52,12 +54,12 @@ import static io.trino.SystemSessionProperties.isOmitDateTimeTypePrecision; import static io.trino.connector.informationschema.InformationSchemaMetadata.defaultPrefixes; import static io.trino.connector.informationschema.InformationSchemaMetadata.isTablesEnumeratingTable; +import static io.trino.metadata.MetadataListing.getRelationTypes; import static io.trino.metadata.MetadataListing.getViews; import static io.trino.metadata.MetadataListing.listSchemas; import static io.trino.metadata.MetadataListing.listTableColumns; import static io.trino.metadata.MetadataListing.listTablePrivileges; import static io.trino.metadata.MetadataListing.listTables; -import static io.trino.metadata.MetadataListing.listViews; import static io.trino.spi.security.PrincipalType.USER; import static io.trino.spi.type.TypeUtils.writeNativeValue; import static io.trino.type.TypeUtils.getDisplayLabel; @@ -245,7 +247,7 @@ private void buildPages() private void addColumnsRecords(QualifiedTablePrefix prefix) { - for (Map.Entry> entry : listTableColumns(session, metadata, accessControl, prefix).entrySet()) { + for (Entry> entry : listTableColumns(session, metadata, accessControl, prefix).entrySet()) { SchemaTableName tableName = entry.getKey(); long ordinalPosition = 1; @@ -275,16 +277,24 @@ private void addColumnsRecords(QualifiedTablePrefix prefix) private void addTablesRecords(QualifiedTablePrefix prefix) { - Set tables = listTables(session, metadata, accessControl, prefix); boolean needsTableType = requiredColumns.contains("table_type"); - Set views = Set.of(); + Set relations; + Set views; if (needsTableType) { - // TODO introduce a dedicated method for getting relations with their type from the connector, instead of calling (potentially much more expensive) getViews - views = listViews(session, metadata, accessControl, prefix); + Map relationTypes = getRelationTypes(session, metadata, accessControl, prefix); + relations = relationTypes.keySet(); + views = relationTypes.entrySet().stream() + .filter(entry -> entry.getValue() == RelationType.VIEW) + .map(Entry::getKey) + .collect(toImmutableSet()); + } + else { + relations = listTables(session, metadata, accessControl, prefix); + views = Set.of(); } // TODO (https://github.com/trinodb/trino/issues/8207) define a type for materialized views - for (SchemaTableName name : tables) { + for (SchemaTableName name : relations) { String type = null; if (needsTableType) { // if table and view names overlap, the view wins @@ -304,7 +314,7 @@ private void addTablesRecords(QualifiedTablePrefix prefix) private void addViewsRecords(QualifiedTablePrefix prefix) { - for (Map.Entry entry : getViews(session, metadata, accessControl, prefix).entrySet()) { + for (Entry entry : getViews(session, metadata, accessControl, prefix).entrySet()) { addRecord( prefix.getCatalogName(), entry.getKey().getSchemaName(), diff --git a/core/trino-main/src/main/java/io/trino/connector/system/jdbc/TableJdbcTable.java b/core/trino-main/src/main/java/io/trino/connector/system/jdbc/TableJdbcTable.java index 320172490d1f..45413ef690b5 100644 --- a/core/trino-main/src/main/java/io/trino/connector/system/jdbc/TableJdbcTable.java +++ b/core/trino-main/src/main/java/io/trino/connector/system/jdbc/TableJdbcTable.java @@ -26,19 +26,18 @@ import io.trino.spi.connector.InMemoryRecordSet; import io.trino.spi.connector.InMemoryRecordSet.Builder; import io.trino.spi.connector.RecordCursor; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.predicate.Domain; import io.trino.spi.predicate.TupleDomain; import java.util.Optional; -import java.util.Set; import static io.trino.connector.system.jdbc.FilterUtil.isImpossibleObjectName; import static io.trino.connector.system.jdbc.FilterUtil.tablePrefix; import static io.trino.connector.system.jdbc.FilterUtil.tryGetSingleVarcharValue; +import static io.trino.metadata.MetadataListing.getRelationTypes; import static io.trino.metadata.MetadataListing.listCatalogNames; -import static io.trino.metadata.MetadataListing.listTables; -import static io.trino.metadata.MetadataListing.listViews; import static io.trino.metadata.MetadataUtil.TableMetadataBuilder.tableMetadataBuilder; import static io.trino.spi.type.VarcharType.VARCHAR; import static java.util.Objects.requireNonNull; @@ -104,13 +103,12 @@ public RecordCursor cursor(ConnectorTransactionHandle transactionHandle, Connect for (String catalog : listCatalogNames(session, metadata, accessControl, catalogDomain)) { QualifiedTablePrefix prefix = tablePrefix(catalog, schemaFilter, tableFilter); - Set views = listViews(session, metadata, accessControl, prefix); - for (SchemaTableName name : listTables(session, metadata, accessControl, prefix)) { - boolean isView = views.contains(name); + getRelationTypes(session, metadata, accessControl, prefix).forEach((name, type) -> { + boolean isView = type == RelationType.VIEW; if ((includeTables && !isView) || (includeViews && isView)) { table.addRow(tableRow(catalog, name, isView ? "VIEW" : "TABLE")); } - } + }); } return table.build().cursor(); } diff --git a/core/trino-main/src/main/java/io/trino/metadata/Metadata.java b/core/trino-main/src/main/java/io/trino/metadata/Metadata.java index e55b75efa63a..bb2cad83e056 100644 --- a/core/trino-main/src/main/java/io/trino/metadata/Metadata.java +++ b/core/trino-main/src/main/java/io/trino/metadata/Metadata.java @@ -37,6 +37,7 @@ import io.trino.spi.connector.MaterializedViewFreshness; import io.trino.spi.connector.ProjectionApplicationResult; import io.trino.spi.connector.RelationCommentMetadata; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.RowChangeParadigm; import io.trino.spi.connector.SampleApplicationResult; import io.trino.spi.connector.SampleType; @@ -164,6 +165,12 @@ Optional getTableHandleForExecute( */ List listTables(Session session, QualifiedTablePrefix prefix); + /** + * Get the relation names that match the specified table prefix (never null). + * This includes all relations (e.g. tables, views, materialized views). + */ + Map getRelationTypes(Session session, QualifiedTablePrefix prefix); + /** * Gets all of the columns on the specified table, or an empty map if the columns cannot be enumerated. * diff --git a/core/trino-main/src/main/java/io/trino/metadata/MetadataListing.java b/core/trino-main/src/main/java/io/trino/metadata/MetadataListing.java index f72cb857704c..8088d145a927 100644 --- a/core/trino-main/src/main/java/io/trino/metadata/MetadataListing.java +++ b/core/trino-main/src/main/java/io/trino/metadata/MetadataListing.java @@ -22,6 +22,7 @@ import io.trino.spi.TrinoException; import io.trino.spi.connector.CatalogHandle; import io.trino.spi.connector.ColumnMetadata; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.connector.TableColumnsMetadata; import io.trino.spi.predicate.Domain; @@ -46,6 +47,7 @@ import static io.trino.connector.system.jdbc.FilterUtil.tryGetSingleVarcharValue; import static io.trino.spi.StandardErrorCode.GENERIC_INTERNAL_ERROR; import static io.trino.spi.StandardErrorCode.TABLE_REDIRECTION_ERROR; +import static java.util.function.Function.identity; public final class MetadataListing { @@ -133,22 +135,29 @@ private static Set doListTables(Session session, Metadata metad return accessControl.filterTables(session.toSecurityContext(), prefix.getCatalogName(), tableNames); } - public static Set listViews(Session session, Metadata metadata, AccessControl accessControl, QualifiedTablePrefix prefix) + public static Map getRelationTypes(Session session, Metadata metadata, AccessControl accessControl, QualifiedTablePrefix prefix) { try { - return doListViews(session, metadata, accessControl, prefix); + return doGetRelationTypes(session, metadata, accessControl, prefix); } catch (RuntimeException exception) { - throw handleListingException(exception, "views", prefix.getCatalogName()); + throw handleListingException(exception, "tables", prefix.getCatalogName()); } } - private static Set doListViews(Session session, Metadata metadata, AccessControl accessControl, QualifiedTablePrefix prefix) + private static Map doGetRelationTypes(Session session, Metadata metadata, AccessControl accessControl, QualifiedTablePrefix prefix) { - Set tableNames = metadata.listViews(session, prefix).stream() - .map(QualifiedObjectName::asSchemaTableName) - .collect(toImmutableSet()); - return accessControl.filterTables(session.toSecurityContext(), prefix.getCatalogName(), tableNames); + Map relationTypes = metadata.getRelationTypes(session, prefix); + + // Table listing operation only involves getting table names, but not any metadata. So redirected tables are not + // handled any differently. The target table or catalog are not involved. Thus the following filter is only called + // for the source catalog on source table names. + Set accessibleNames = accessControl.filterTables(session.toSecurityContext(), prefix.getCatalogName(), relationTypes.keySet()); + if (accessibleNames.equals(relationTypes.keySet())) { + return relationTypes; + } + return accessibleNames.stream() + .collect(toImmutableMap(identity(), relationTypes::get)); } public static Map getViews(Session session, Metadata metadata, AccessControl accessControl, QualifiedTablePrefix prefix) diff --git a/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java b/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java index 80ca50b6a120..1adeb8d71a4a 100644 --- a/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java +++ b/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java @@ -69,6 +69,7 @@ import io.trino.spi.connector.ProjectionApplicationResult; import io.trino.spi.connector.RelationColumnsMetadata; import io.trino.spi.connector.RelationCommentMetadata; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.RowChangeParadigm; import io.trino.spi.connector.SampleApplicationResult; import io.trino.spi.connector.SampleType; @@ -511,10 +512,11 @@ public List listTables(Session session, QualifiedTablePrefi Optional objectName = prefix.asQualifiedObjectName(); if (objectName.isPresent()) { - Optional exists = isExistingRelationForListing(session, objectName.get()); - if (exists.isPresent()) { - return exists.get() ? ImmutableList.of(objectName.get()) : ImmutableList.of(); + Optional relationType = getRelationTypeIfExists(session, objectName.get()); + if (relationType.isPresent()) { + return ImmutableList.of(objectName.get()); } + // TODO we can probably return empty lit here } Optional catalog = getOptionalCatalogMetadata(session, prefix.getCatalogName()); @@ -537,25 +539,63 @@ public List listTables(Session session, QualifiedTablePrefi return ImmutableList.copyOf(tables); } - private Optional isExistingRelationForListing(Session session, QualifiedObjectName name) + @Override + public Map getRelationTypes(Session session, QualifiedTablePrefix prefix) + { + requireNonNull(prefix, "prefix is null"); + if (cannotExist(prefix)) { + return ImmutableMap.of(); + } + + Optional objectName = prefix.asQualifiedObjectName(); + if (objectName.isPresent()) { + Optional relationType = getRelationTypeIfExists(session, objectName.get()); + if (relationType.isPresent()) { + return ImmutableMap.of(objectName.get().asSchemaTableName(), relationType.get()); + } + return ImmutableMap.of(); + } + + Optional catalog = getOptionalCatalogMetadata(session, prefix.getCatalogName()); + Map relationTypes = new LinkedHashMap<>(); + if (catalog.isPresent()) { + CatalogMetadata catalogMetadata = catalog.get(); + + for (CatalogHandle catalogHandle : catalogMetadata.listCatalogHandles()) { + ConnectorMetadata metadata = catalogMetadata.getMetadataFor(session, catalogHandle); + ConnectorSession connectorSession = session.toConnectorSession(catalogHandle); + if (isExternalInformationSchema(catalogHandle, prefix.getSchemaName())) { + continue; + } + metadata.getRelationTypes(connectorSession, prefix.getSchemaName()).entrySet().stream() + .filter(entry -> !isExternalInformationSchema(catalogHandle, entry.getKey().getSchemaName())) + .forEach(entry -> relationTypes.put(entry.getKey(), entry.getValue())); + } + } + return ImmutableMap.copyOf(relationTypes); + } + + private Optional getRelationTypeIfExists(Session session, QualifiedObjectName name) { if (isMaterializedView(session, name)) { - return Optional.of(true); + return Optional.of(RelationType.MATERIALIZED_VIEW); } if (isView(session, name)) { - return Optional.of(true); + return Optional.of(RelationType.VIEW); } // TODO: consider a better way to resolve relation names: https://github.com/trinodb/trino/issues/9400 try { - return Optional.of(getRedirectionAwareTableHandle(session, name).tableHandle().isPresent()); + if (getRedirectionAwareTableHandle(session, name).tableHandle().isPresent()) { + return Optional.of(RelationType.TABLE); + } + return Optional.empty(); } catch (TrinoException e) { // ignore redirection errors for consistency with listing if (e.getErrorCode().equals(TABLE_REDIRECTION_ERROR.toErrorCode())) { - return Optional.of(true); + return Optional.of(RelationType.TABLE); } - // we don't know if it exists or not return Optional.empty(); } } diff --git a/core/trino-main/src/main/java/io/trino/tracing/TracingConnectorMetadata.java b/core/trino-main/src/main/java/io/trino/tracing/TracingConnectorMetadata.java index df84f0eec8bd..26cfe77b0373 100644 --- a/core/trino-main/src/main/java/io/trino/tracing/TracingConnectorMetadata.java +++ b/core/trino-main/src/main/java/io/trino/tracing/TracingConnectorMetadata.java @@ -51,6 +51,7 @@ import io.trino.spi.connector.ProjectionApplicationResult; import io.trino.spi.connector.RelationColumnsMetadata; import io.trino.spi.connector.RelationCommentMetadata; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.RetryMode; import io.trino.spi.connector.RowChangeParadigm; import io.trino.spi.connector.SampleApplicationResult; @@ -267,6 +268,15 @@ public List listTables(ConnectorSession session, Optional getRelationTypes(ConnectorSession session, Optional schemaName) + { + Span span = startSpan("getRelationTypes", schemaName); + try (var ignored = scopedSpan(span)) { + return delegate.getRelationTypes(session, schemaName); + } + } + @Override public Map getColumnHandles(ConnectorSession session, ConnectorTableHandle tableHandle) { diff --git a/core/trino-main/src/main/java/io/trino/tracing/TracingMetadata.java b/core/trino-main/src/main/java/io/trino/tracing/TracingMetadata.java index e629d9524267..6d669bd5e7a9 100644 --- a/core/trino-main/src/main/java/io/trino/tracing/TracingMetadata.java +++ b/core/trino-main/src/main/java/io/trino/tracing/TracingMetadata.java @@ -65,6 +65,7 @@ import io.trino.spi.connector.MaterializedViewFreshness; import io.trino.spi.connector.ProjectionApplicationResult; import io.trino.spi.connector.RelationCommentMetadata; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.RowChangeParadigm; import io.trino.spi.connector.SampleApplicationResult; import io.trino.spi.connector.SampleType; @@ -317,6 +318,15 @@ public List listTables(Session session, QualifiedTablePrefi } } + @Override + public Map getRelationTypes(Session session, QualifiedTablePrefix prefix) + { + Span span = startSpan("getRelationTypes", prefix); + try (var ignored = scopedSpan(span)) { + return delegate.getRelationTypes(session, prefix); + } + } + @Override public Map getColumnHandles(Session session, TableHandle tableHandle) { diff --git a/core/trino-main/src/test/java/io/trino/metadata/AbstractMockMetadata.java b/core/trino-main/src/test/java/io/trino/metadata/AbstractMockMetadata.java index 5d9d9f27d32e..2611b045a74e 100644 --- a/core/trino-main/src/test/java/io/trino/metadata/AbstractMockMetadata.java +++ b/core/trino-main/src/test/java/io/trino/metadata/AbstractMockMetadata.java @@ -43,6 +43,7 @@ import io.trino.spi.connector.MaterializedViewFreshness; import io.trino.spi.connector.ProjectionApplicationResult; import io.trino.spi.connector.RelationCommentMetadata; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.RowChangeParadigm; import io.trino.spi.connector.SampleApplicationResult; import io.trino.spi.connector.SampleType; @@ -232,6 +233,12 @@ public List listTables(Session session, QualifiedTablePrefi throw new UnsupportedOperationException(); } + @Override + public Map getRelationTypes(Session session, QualifiedTablePrefix prefix) + { + throw new UnsupportedOperationException(); + } + @Override public Map getColumnHandles(Session session, TableHandle tableHandle) { diff --git a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMetadata.java b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMetadata.java index 09842afe22c6..3a006f8a0a84 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMetadata.java +++ b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMetadata.java @@ -283,6 +283,28 @@ default List listTables(ConnectorSession session, Optional getRelationTypes(ConnectorSession session, Optional schemaName) + { + Set materializedViews = Set.copyOf(listMaterializedViews(session, schemaName)); + Set views = Set.copyOf(listViews(session, schemaName)); + + return listTables(session, schemaName).stream() + .collect(toMap( + identity(), + relation -> { + if (materializedViews.contains(relation)) { + return RelationType.MATERIALIZED_VIEW; + } + if (views.contains(relation)) { + return RelationType.VIEW; + } + return RelationType.TABLE; + })); + } + /** * Gets all of the columns on the specified table, or an empty map if the columns cannot be enumerated. * diff --git a/core/trino-spi/src/main/java/io/trino/spi/connector/RelationType.java b/core/trino-spi/src/main/java/io/trino/spi/connector/RelationType.java new file mode 100644 index 000000000000..d038a1de8bc3 --- /dev/null +++ b/core/trino-spi/src/main/java/io/trino/spi/connector/RelationType.java @@ -0,0 +1,21 @@ +/* + * 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 io.trino.spi.connector; + +public enum RelationType +{ + TABLE, + VIEW, + MATERIALIZED_VIEW, +} diff --git a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeConnectorMetadata.java b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeConnectorMetadata.java index adc7a7be7b7e..dfbed636f83b 100644 --- a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeConnectorMetadata.java +++ b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeConnectorMetadata.java @@ -51,6 +51,7 @@ import io.trino.spi.connector.ProjectionApplicationResult; import io.trino.spi.connector.RelationColumnsMetadata; import io.trino.spi.connector.RelationCommentMetadata; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.RetryMode; import io.trino.spi.connector.RowChangeParadigm; import io.trino.spi.connector.SampleApplicationResult; @@ -294,6 +295,14 @@ public List listTables(ConnectorSession session, Optional getRelationTypes(ConnectorSession session, Optional schemaName) + { + try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(classLoader)) { + return delegate.getRelationTypes(session, schemaName); + } + } + @Override public Map getColumnHandles(ConnectorSession session, ConnectorTableHandle tableHandle) { diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogAccessOperations.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogAccessOperations.java index ec25c1ef46fb..4d87b0f51a5b 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogAccessOperations.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogAccessOperations.java @@ -498,7 +498,7 @@ public void testInformationSchemaTableAndColumns() session, "SELECT * FROM information_schema.tables WHERE table_schema = CURRENT_SCHEMA", ImmutableMultiset.builder() - .addCopies(GET_TABLES, 2) + .addCopies(GET_TABLES, 3) // TODO regression, it used to be 2 .build(), ImmutableMultiset.of()); diff --git a/testing/trino-tests/src/test/java/io/trino/connector/informationschema/TestInformationSchemaConnector.java b/testing/trino-tests/src/test/java/io/trino/connector/informationschema/TestInformationSchemaConnector.java index 167dbe40d7b5..18e23395d0c0 100644 --- a/testing/trino-tests/src/test/java/io/trino/connector/informationschema/TestInformationSchemaConnector.java +++ b/testing/trino-tests/src/test/java/io/trino/connector/informationschema/TestInformationSchemaConnector.java @@ -175,32 +175,27 @@ public void testMetadataCalls() "SELECT count(table_name), count(table_type) from test_catalog.information_schema.tables", "VALUES (3008, 3008)", ImmutableMultiset.builder() - .add("ConnectorMetadata.listTables") - .add("ConnectorMetadata.listViews") + .add("ConnectorMetadata.getRelationTypes") .build()); assertMetadataCalls( "SELECT count(table_name), count(table_type) from test_catalog.information_schema.tables WHERE table_schema = 'test_schema1'", "VALUES (1000, 1000)", ImmutableMultiset.builder() - .add("ConnectorMetadata.listTables(schema=test_schema1)") - .add("ConnectorMetadata.listViews(schema=test_schema1)") + .add("ConnectorMetadata.getRelationTypes(schema=test_schema1)") .build()); assertMetadataCalls( "SELECT count(table_name), count(table_type) from test_catalog.information_schema.tables WHERE table_schema LIKE 'test_sch_ma1'", "VALUES (1000, 1000)", ImmutableMultiset.builder() .add("ConnectorMetadata.listSchemaNames") - .add("ConnectorMetadata.listTables(schema=test_schema1)") - .add("ConnectorMetadata.listViews(schema=test_schema1)") + .add("ConnectorMetadata.getRelationTypes(schema=test_schema1)") .build()); assertMetadataCalls( "SELECT count(table_name), count(table_type) from test_catalog.information_schema.tables WHERE table_schema LIKE 'test_sch_ma1' AND table_schema IN ('test_schema1', 'test_schema2')", "VALUES (1000, 1000)", ImmutableMultiset.builder() - .add("ConnectorMetadata.listTables(schema=test_schema1)") - .add("ConnectorMetadata.listViews(schema=test_schema1)") - .add("ConnectorMetadata.listTables(schema=test_schema2)") - .add("ConnectorMetadata.listViews(schema=test_schema2)") + .add("ConnectorMetadata.getRelationTypes(schema=test_schema1)") + .add("ConnectorMetadata.getRelationTypes(schema=test_schema2)") .build()); assertMetadataCalls( "SELECT count(table_name), count(table_type) from test_catalog.information_schema.tables WHERE table_schema IN " + @@ -212,26 +207,25 @@ public void testMetadataCalls() .collect(joining(",", "(", ")")), "VALUES (3000, 3000)", ImmutableMultiset.builder() - .add("ConnectorMetadata.listTables") - .add("ConnectorMetadata.listViews") + .add("ConnectorMetadata.getRelationTypes") .build()); assertMetadataCalls( "SELECT count(table_name), count(table_type) from test_catalog.information_schema.tables WHERE table_name = 'test_table1'", "VALUES (2, 2)", ImmutableMultiset.builder() .add("ConnectorMetadata.listSchemaNames") - .addCopies("ConnectorMetadata.getSystemTable(schema=test_schema1, table=test_table1)", 5) - .addCopies("ConnectorMetadata.getSystemTable(schema=test_schema2, table=test_table1)", 5) - .addCopies("ConnectorMetadata.getSystemTable(schema=test_schema3_empty, table=test_table1)", 5) - .addCopies("ConnectorMetadata.getSystemTable(schema=test_schema4_empty, table=test_table1)", 5) + .addCopies("ConnectorMetadata.getSystemTable(schema=test_schema1, table=test_table1)", 4) + .addCopies("ConnectorMetadata.getSystemTable(schema=test_schema2, table=test_table1)", 4) + .addCopies("ConnectorMetadata.getSystemTable(schema=test_schema3_empty, table=test_table1)", 4) + .addCopies("ConnectorMetadata.getSystemTable(schema=test_schema4_empty, table=test_table1)", 4) .add("ConnectorMetadata.getMaterializedView(schema=test_schema1, table=test_table1)") .add("ConnectorMetadata.getMaterializedView(schema=test_schema2, table=test_table1)") .add("ConnectorMetadata.getMaterializedView(schema=test_schema3_empty, table=test_table1)") .add("ConnectorMetadata.getMaterializedView(schema=test_schema4_empty, table=test_table1)") - .addCopies("ConnectorMetadata.getView(schema=test_schema1, table=test_table1)", 2) - .addCopies("ConnectorMetadata.getView(schema=test_schema2, table=test_table1)", 2) - .addCopies("ConnectorMetadata.getView(schema=test_schema3_empty, table=test_table1)", 2) - .addCopies("ConnectorMetadata.getView(schema=test_schema4_empty, table=test_table1)", 2) + .add("ConnectorMetadata.getView(schema=test_schema1, table=test_table1)") + .add("ConnectorMetadata.getView(schema=test_schema2, table=test_table1)") + .add("ConnectorMetadata.getView(schema=test_schema3_empty, table=test_table1)") + .add("ConnectorMetadata.getView(schema=test_schema4_empty, table=test_table1)") .add("ConnectorMetadata.redirectTable(schema=test_schema1, table=test_table1)") .add("ConnectorMetadata.redirectTable(schema=test_schema2, table=test_table1)") .add("ConnectorMetadata.redirectTable(schema=test_schema3_empty, table=test_table1)") @@ -246,28 +240,24 @@ public void testMetadataCalls() "VALUES (2, 2)", ImmutableMultiset.builder() .add("ConnectorMetadata.listSchemaNames") - .add("ConnectorMetadata.listTables(schema=test_schema1)") - .add("ConnectorMetadata.listTables(schema=test_schema2)") - .add("ConnectorMetadata.listTables(schema=test_schema3_empty)") - .add("ConnectorMetadata.listTables(schema=test_schema4_empty)") - .add("ConnectorMetadata.listViews(schema=test_schema1)") - .add("ConnectorMetadata.listViews(schema=test_schema2)") - .add("ConnectorMetadata.listViews(schema=test_schema3_empty)") - .add("ConnectorMetadata.listViews(schema=test_schema4_empty)") + .add("ConnectorMetadata.getRelationTypes(schema=test_schema1)") + .add("ConnectorMetadata.getRelationTypes(schema=test_schema2)") + .add("ConnectorMetadata.getRelationTypes(schema=test_schema3_empty)") + .add("ConnectorMetadata.getRelationTypes(schema=test_schema4_empty)") .build()); assertMetadataCalls( "SELECT count(table_name), count(table_type) from test_catalog.information_schema.tables WHERE table_name LIKE 'test_t_ble1' AND table_name IN ('test_table1', 'test_table2')", "VALUES (2, 2)", ImmutableMultiset.builder() .add("ConnectorMetadata.listSchemaNames") - .addCopies("ConnectorMetadata.getSystemTable(schema=test_schema1, table=test_table1)", 5) - .addCopies("ConnectorMetadata.getSystemTable(schema=test_schema1, table=test_table2)", 5) - .addCopies("ConnectorMetadata.getSystemTable(schema=test_schema2, table=test_table1)", 5) - .addCopies("ConnectorMetadata.getSystemTable(schema=test_schema2, table=test_table2)", 5) - .addCopies("ConnectorMetadata.getSystemTable(schema=test_schema3_empty, table=test_table1)", 5) - .addCopies("ConnectorMetadata.getSystemTable(schema=test_schema3_empty, table=test_table2)", 5) - .addCopies("ConnectorMetadata.getSystemTable(schema=test_schema4_empty, table=test_table1)", 5) - .addCopies("ConnectorMetadata.getSystemTable(schema=test_schema4_empty, table=test_table2)", 5) + .addCopies("ConnectorMetadata.getSystemTable(schema=test_schema1, table=test_table1)", 4) + .addCopies("ConnectorMetadata.getSystemTable(schema=test_schema1, table=test_table2)", 4) + .addCopies("ConnectorMetadata.getSystemTable(schema=test_schema2, table=test_table1)", 4) + .addCopies("ConnectorMetadata.getSystemTable(schema=test_schema2, table=test_table2)", 4) + .addCopies("ConnectorMetadata.getSystemTable(schema=test_schema3_empty, table=test_table1)", 4) + .addCopies("ConnectorMetadata.getSystemTable(schema=test_schema3_empty, table=test_table2)", 4) + .addCopies("ConnectorMetadata.getSystemTable(schema=test_schema4_empty, table=test_table1)", 4) + .addCopies("ConnectorMetadata.getSystemTable(schema=test_schema4_empty, table=test_table2)", 4) .add("ConnectorMetadata.getMaterializedView(schema=test_schema1, table=test_table1)") .add("ConnectorMetadata.getMaterializedView(schema=test_schema1, table=test_table2)") .add("ConnectorMetadata.getMaterializedView(schema=test_schema2, table=test_table2)") @@ -276,14 +266,14 @@ public void testMetadataCalls() .add("ConnectorMetadata.getMaterializedView(schema=test_schema3_empty, table=test_table1)") .add("ConnectorMetadata.getMaterializedView(schema=test_schema4_empty, table=test_table2)") .add("ConnectorMetadata.getMaterializedView(schema=test_schema4_empty, table=test_table1)") - .addCopies("ConnectorMetadata.getView(schema=test_schema1, table=test_table1)", 2) - .addCopies("ConnectorMetadata.getView(schema=test_schema1, table=test_table2)", 2) - .addCopies("ConnectorMetadata.getView(schema=test_schema2, table=test_table1)", 2) - .addCopies("ConnectorMetadata.getView(schema=test_schema2, table=test_table2)", 2) - .addCopies("ConnectorMetadata.getView(schema=test_schema3_empty, table=test_table1)", 2) - .addCopies("ConnectorMetadata.getView(schema=test_schema3_empty, table=test_table2)", 2) - .addCopies("ConnectorMetadata.getView(schema=test_schema4_empty, table=test_table1)", 2) - .addCopies("ConnectorMetadata.getView(schema=test_schema4_empty, table=test_table2)", 2) + .add("ConnectorMetadata.getView(schema=test_schema1, table=test_table1)") + .add("ConnectorMetadata.getView(schema=test_schema1, table=test_table2)") + .add("ConnectorMetadata.getView(schema=test_schema2, table=test_table1)") + .add("ConnectorMetadata.getView(schema=test_schema2, table=test_table2)") + .add("ConnectorMetadata.getView(schema=test_schema3_empty, table=test_table1)") + .add("ConnectorMetadata.getView(schema=test_schema3_empty, table=test_table2)") + .add("ConnectorMetadata.getView(schema=test_schema4_empty, table=test_table1)") + .add("ConnectorMetadata.getView(schema=test_schema4_empty, table=test_table2)") .add("ConnectorMetadata.redirectTable(schema=test_schema1, table=test_table1)") .add("ConnectorMetadata.redirectTable(schema=test_schema1, table=test_table2)") .add("ConnectorMetadata.redirectTable(schema=test_schema2, table=test_table1)") From 3d0ee03fc27803daeaee50d84af2b990a304c1d4 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Mon, 20 Nov 2023 16:35:08 +0100 Subject: [PATCH 413/587] Implement getRelationTypes for Hive This reduces number of Glue calls when reading from `information_schema.tables` or `system.jdbc.tables`. --- .../io/trino/plugin/hive/HiveMetadata.java | 34 ++++++- .../plugin/hive/HiveMetastoreClosure.java | 11 +++ .../metastore/ForwardingHiveMetastore.java | 13 +++ .../plugin/hive/metastore/HiveMetastore.java | 8 ++ .../SemiTransactionalHiveMetastore.java | 19 ++++ .../metastore/cache/CachingHiveMetastore.java | 67 +++++++++++++- .../cache/SharedHiveMetastoreCache.java | 14 +++ .../metastore/file/FileHiveMetastore.java | 16 ++++ .../metastore/glue/GlueHiveMetastore.java | 32 +++++++ .../thrift/BridgingHiveMetastore.java | 21 +++++ .../tracing/TracingHiveMetastore.java | 26 ++++++ .../CountingAccessHiveMetastore.java | 22 +++++ .../metastore/UnimplementedHiveMetastore.java | 13 +++ ...astoreMetadataQueriesAccessOperations.java | 88 +++++++++---------- 14 files changed, 337 insertions(+), 47 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java index 71bc4e010046..596550932425 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java @@ -88,6 +88,7 @@ import io.trino.spi.connector.MaterializedViewFreshness; import io.trino.spi.connector.MetadataProvider; import io.trino.spi.connector.ProjectionApplicationResult; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.RetryMode; import io.trino.spi.connector.RowChangeParadigm; import io.trino.spi.connector.SchemaNotFoundException; @@ -137,6 +138,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.Optional; import java.util.OptionalInt; @@ -814,6 +816,34 @@ public List listTables(ConnectorSession session, Optional getRelationTypes(ConnectorSession session, Optional optionalSchemaName) + { + ImmutableMap.Builder result = ImmutableMap.builder(); + + boolean fetched = false; + if (optionalSchemaName.isEmpty()) { + Optional> relationTypes = metastore.getRelationTypes(); + if (relationTypes.isPresent()) { + relationTypes.get().entrySet().stream() + .filter(entry -> !isHiveSystemSchema(entry.getKey().getSchemaName())) + .forEach(result::put); + fetched = true; + } + } + if (!fetched) { + for (String schemaName : listSchemas(session, optionalSchemaName)) { + for (Entry entry : metastore.getRelationTypes(schemaName).entrySet()) { + result.put(new SchemaTableName(schemaName, entry.getKey()), entry.getValue()); + } + } + } + + listMaterializedViews(session, optionalSchemaName) + .forEach(name -> result.put(name, RelationType.MATERIALIZED_VIEW)); + return result.buildKeepingLast(); + } + private List listSchemas(ConnectorSession session, Optional schemaName) { if (schemaName.isPresent()) { @@ -892,7 +922,7 @@ public TableStatistics getTableStatistics(ConnectorSession session, ConnectorTab .collect(toImmutableMap(HiveColumnHandle::getName, Function.identity())); Map columnTypes = columns.entrySet().stream() - .collect(toImmutableMap(Map.Entry::getKey, entry -> getColumnMetadata(session, tableHandle, entry.getValue()).getType())); + .collect(toImmutableMap(Entry::getKey, entry -> getColumnMetadata(session, tableHandle, entry.getValue()).getType())); HivePartitionResult partitionResult = partitionManager.getPartitions(metastore, tableHandle, new Constraint(hiveTableHandle.getEnforcedConstraint())); // If partitions are not loaded, then don't generate table statistics. // Note that the computation is not persisted in the table handle, so can be redone many times @@ -3075,7 +3105,7 @@ public Optional> applyProjecti ImmutableMap.Builder newVariablesBuilder = ImmutableMap.builder(); ImmutableSet.Builder projectedColumnsBuilder = ImmutableSet.builder(); - for (Map.Entry entry : columnProjections.entrySet()) { + for (Entry entry : columnProjections.entrySet()) { ConnectorExpression expression = entry.getKey(); ProjectedColumnRepresentation projectedColumn = entry.getValue(); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetastoreClosure.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetastoreClosure.java index 190e81bd28fe..cdbeee24b11a 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetastoreClosure.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetastoreClosure.java @@ -30,6 +30,7 @@ import io.trino.plugin.hive.metastore.Table; import io.trino.plugin.hive.projection.PartitionProjection; import io.trino.spi.TrinoException; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.connector.TableNotFoundException; import io.trino.spi.function.LanguageFunction; @@ -164,6 +165,16 @@ public Optional> getAllTables() return delegate.getAllTables(); } + public Map getRelationTypes(String databaseName) + { + return delegate.getRelationTypes(databaseName); + } + + public Optional> getRelationTypes() + { + return delegate.getRelationTypes(); + } + public List getAllViews(String databaseName) { return delegate.getAllViews(databaseName); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/ForwardingHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/ForwardingHiveMetastore.java index f8b3fcd5926c..2a4c7a2701d6 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/ForwardingHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/ForwardingHiveMetastore.java @@ -21,6 +21,7 @@ import io.trino.plugin.hive.acid.AcidOperation; import io.trino.plugin.hive.acid.AcidTransaction; import io.trino.plugin.hive.metastore.HivePrivilegeInfo.HivePrivilege; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.function.LanguageFunction; import io.trino.spi.predicate.TupleDomain; @@ -122,6 +123,18 @@ public Optional> getAllTables() return delegate.getAllTables(); } + @Override + public Map getRelationTypes(String databaseName) + { + return delegate.getRelationTypes(databaseName); + } + + @Override + public Optional> getRelationTypes() + { + return delegate.getRelationTypes(); + } + @Override public List getTablesWithParameter(String databaseName, String parameterKey, String parameterValue) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastore.java index 30b91704acea..2a821d23f29e 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastore.java @@ -23,6 +23,7 @@ import io.trino.plugin.hive.acid.AcidTransaction; import io.trino.plugin.hive.metastore.HivePrivilegeInfo.HivePrivilege; import io.trino.spi.TrinoException; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.function.LanguageFunction; import io.trino.spi.predicate.TupleDomain; @@ -69,6 +70,13 @@ default void updatePartitionStatistics(Table table, String partitionName, Functi */ Optional> getAllTables(); + Map getRelationTypes(String databaseName); + + /** + * @return empty if operation is not supported + */ + Optional> getRelationTypes(); + List getTablesWithParameter(String databaseName, String parameterKey, String parameterValue); /** diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/SemiTransactionalHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/SemiTransactionalHiveMetastore.java index 8a377fa75504..81eb6abafce7 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/SemiTransactionalHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/SemiTransactionalHiveMetastore.java @@ -52,6 +52,7 @@ import io.trino.plugin.hive.util.ValidTxnWriteIdList; import io.trino.spi.TrinoException; import io.trino.spi.connector.ConnectorSession; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.SchemaNotFoundException; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.connector.TableNotFoundException; @@ -252,6 +253,24 @@ public synchronized Optional> getAllTables() return delegate.getAllTables(); } + public synchronized Map getRelationTypes(String databaseName) + { + checkReadable(); + if (!tableActions.isEmpty()) { + throw new UnsupportedOperationException("Listing all relations after adding/dropping/altering tables/views in a transaction is not supported"); + } + return delegate.getRelationTypes(databaseName); + } + + public synchronized Optional> getRelationTypes() + { + checkReadable(); + if (!tableActions.isEmpty()) { + throw new UnsupportedOperationException("Listing all relations after adding/dropping/altering tables/views in a transaction is not supported"); + } + return delegate.getRelationTypes(); + } + public synchronized Optional
    getTable(String databaseName, String tableName) { checkReadable(); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java index b7e817737454..478c865c3700 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java @@ -17,6 +17,7 @@ import com.google.common.cache.CacheLoader; import com.google.common.cache.CacheLoader.InvalidCacheLoadException; import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Sets.SetView; @@ -49,6 +50,7 @@ import io.trino.plugin.hive.metastore.PrincipalPrivileges; import io.trino.plugin.hive.metastore.Table; import io.trino.spi.TrinoException; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.function.LanguageFunction; import io.trino.spi.predicate.TupleDomain; @@ -116,6 +118,8 @@ public enum StatsRecording private final LoadingCache> tableCache; private final LoadingCache> tableNamesCache; private final LoadingCache>> allTableNamesCache; + private final LoadingCache> relationTypesCache; + private final LoadingCache>> allRelationTypesCache; private final LoadingCache> tablesWithParameterCache; private final Cache> tableStatisticsCache; private final Cache> partitionStatisticsCache; @@ -202,6 +206,8 @@ private CachingHiveMetastore( databaseCache = cacheFactory.buildCache(this::loadDatabase); tableNamesCache = cacheFactory.buildCache(this::loadAllTables); allTableNamesCache = cacheFactory.buildCache(ignore -> loadAllTables()); + relationTypesCache = cacheFactory.buildCache(this::loadRelationTypes); + allRelationTypesCache = cacheFactory.buildCache(ignore -> loadRelationTypes()); tablesWithParameterCache = cacheFactory.buildCache(this::loadTablesMatchingParameter); tableStatisticsCache = statsCacheFactory.buildCache(this::refreshTableStatistics); tableCache = cacheFactory.buildCache(this::loadTable); @@ -223,6 +229,8 @@ public void flushCache() databaseNamesCache.invalidateAll(); tableNamesCache.invalidateAll(); allTableNamesCache.invalidateAll(); + relationTypesCache.invalidateAll(); + allRelationTypesCache.invalidateAll(); viewNamesCache.invalidateAll(); allViewNamesCache.invalidateAll(); databaseCache.invalidateAll(); @@ -572,6 +580,10 @@ public void updatePartitionStatistics(Table table, Map getAllTables(String databaseName) { + Map relationTypes = relationTypesCache.getIfPresent(databaseName); + if (relationTypes != null) { + return ImmutableList.copyOf(relationTypes.keySet()); + } return get(tableNamesCache, databaseName); } @@ -583,6 +595,10 @@ private List loadAllTables(String databaseName) @Override public Optional> getAllTables() { + Optional> relationTypes = allRelationTypesCache.getIfPresent(SingletonCacheKey.INSTANCE); + if (relationTypes != null && relationTypes.isPresent()) { + return Optional.of(ImmutableList.copyOf(relationTypes.get().keySet())); + } return getOptional(allTableNamesCache, SingletonCacheKey.INSTANCE); } @@ -591,6 +607,28 @@ private Optional> loadAllTables() return delegate.getAllTables(); } + @Override + public Map getRelationTypes(String databaseName) + { + return get(relationTypesCache, databaseName); + } + + private Map loadRelationTypes(String databaseName) + { + return delegate.getRelationTypes(databaseName); + } + + @Override + public Optional> getRelationTypes() + { + return getOptional(allRelationTypesCache, SingletonCacheKey.INSTANCE); + } + + private Optional> loadRelationTypes() + { + return delegate.getRelationTypes(); + } + @Override public List getTablesWithParameter(String databaseName, String parameterKey, String parameterValue) { @@ -794,6 +832,8 @@ public void invalidateTable(String databaseName, String tableName) tableCache.invalidate(hiveTableName); tableNamesCache.invalidate(databaseName); allTableNamesCache.invalidateAll(); + relationTypesCache.invalidate(databaseName); + allRelationTypesCache.invalidateAll(); viewNamesCache.invalidate(databaseName); allViewNamesCache.invalidateAll(); invalidateAllIf(tablePrivilegesCache, userTableKey -> userTableKey.matches(databaseName, tableName)); @@ -1290,6 +1330,20 @@ public CacheStatsMBean getAllTableNamesStats() return new CacheStatsMBean(allTableNamesCache); } + @Managed + @Nested + public CacheStatsMBean getRelationTypesStats() + { + return new CacheStatsMBean(relationTypesCache); + } + + @Managed + @Nested + public CacheStatsMBean getAllRelationTypesStats() + { + return new CacheStatsMBean(allRelationTypesCache); + } + @Managed @Nested public CacheStatsMBean getTableWithParameterStats() @@ -1395,6 +1449,16 @@ LoadingCache>> getAllTableName return allTableNamesCache; } + LoadingCache> getRelationTypesCache() + { + return relationTypesCache; + } + + LoadingCache>> getAllRelationTypesCache() + { + return allRelationTypesCache; + } + LoadingCache> getTablesWithParameterCache() { return tablesWithParameterCache; @@ -1450,7 +1514,8 @@ LoadingCache> getConfigValuesCache() return configValuesCache; } - private record CacheFactory(OptionalLong expiresAfterWriteMillis, OptionalLong refreshMillis, Optional refreshExecutor, long maximumSize, StatsRecording statsRecording) + private record CacheFactory(OptionalLong expiresAfterWriteMillis, OptionalLong refreshMillis, Optional refreshExecutor, long maximumSize, + StatsRecording statsRecording) { private static final CacheFactory NEVER_CACHE = new CacheFactory(OptionalLong.empty(), OptionalLong.empty(), Optional.empty(), 0, StatsRecording.DISABLED); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/SharedHiveMetastoreCache.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/SharedHiveMetastoreCache.java index 0ac7025a4a92..9eba6ae621e8 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/SharedHiveMetastoreCache.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/SharedHiveMetastoreCache.java @@ -255,6 +255,20 @@ public AggregateCacheStatsMBean getAllTableNamesStats() return new AggregateCacheStatsMBean(CachingHiveMetastore::getAllTableNamesCache); } + @Managed + @Nested + public AggregateCacheStatsMBean getRelationTypesStats() + { + return new AggregateCacheStatsMBean(CachingHiveMetastore::getRelationTypesCache); + } + + @Managed + @Nested + public AggregateCacheStatsMBean getAllRelationTypesStats() + { + return new AggregateCacheStatsMBean(CachingHiveMetastore::getAllRelationTypesCache); + } + @Managed @Nested public AggregateCacheStatsMBean getTableWithParameterStats() diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/file/FileHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/file/FileHiveMetastore.java index d529b1e22d5b..994b7174b268 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/file/FileHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/file/FileHiveMetastore.java @@ -56,6 +56,7 @@ import io.trino.plugin.hive.metastore.thrift.ThriftMetastoreUtil; import io.trino.spi.TrinoException; import io.trino.spi.connector.ColumnNotFoundException; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.SchemaNotFoundException; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.connector.TableNotFoundException; @@ -523,6 +524,21 @@ public Optional> getAllTables() return Optional.empty(); } + @Override + public synchronized Map getRelationTypes(String databaseName) + { + ImmutableMap.Builder relationTypes = ImmutableMap.builder(); + getAllTables(databaseName).forEach(name -> relationTypes.put(name, RelationType.TABLE)); + getAllViews(databaseName).forEach(name -> relationTypes.put(name, RelationType.VIEW)); + return relationTypes.buildKeepingLast(); + } + + @Override + public Optional> getRelationTypes() + { + return Optional.empty(); + } + @Override public synchronized List getTablesWithParameter(String databaseName, String parameterKey, String parameterValue) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java index fc6c078e9a65..c4941e9cd0c7 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java @@ -102,6 +102,7 @@ import io.trino.plugin.hive.util.HiveUtil; import io.trino.spi.TrinoException; import io.trino.spi.connector.ColumnNotFoundException; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.SchemaNotFoundException; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.connector.TableNotFoundException; @@ -434,6 +435,37 @@ public Optional> getAllTables() return Optional.empty(); } + @Override + public Map getRelationTypes(String databaseName) + { + try { + return getGlueTables(databaseName) + .filter(tableFilter.or(SOME_KIND_OF_VIEW_FILTER)) + .collect(toImmutableMap( + com.amazonaws.services.glue.model.Table::getName, + table -> { + // GlueHiveMetastore currently does not distinguish views and materialized views, see getAllViews(). + if (SOME_KIND_OF_VIEW_FILTER.test(table)) { + return RelationType.VIEW; + } + return RelationType.TABLE; + })); + } + catch (EntityNotFoundException | AccessDeniedException e) { + // database does not exist or permission denied + return ImmutableMap.of(); + } + catch (AmazonServiceException e) { + throw new TrinoException(HIVE_METASTORE_ERROR, e); + } + } + + @Override + public Optional> getRelationTypes() + { + return Optional.empty(); + } + @Override public List getTablesWithParameter(String databaseName, String parameterKey, String parameterValue) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/BridgingHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/BridgingHiveMetastore.java index d5926bb35155..bf015ef3af10 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/BridgingHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/BridgingHiveMetastore.java @@ -34,6 +34,7 @@ import io.trino.plugin.hive.metastore.Table; import io.trino.plugin.hive.util.HiveUtil; import io.trino.spi.TrinoException; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.SchemaNotFoundException; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.connector.TableNotFoundException; @@ -146,6 +147,15 @@ public List getAllTables(String databaseName) return delegate.getAllTables(databaseName); } + @Override + public Map getRelationTypes(String databaseName) + { + ImmutableMap.Builder relationTypes = ImmutableMap.builder(); + getAllTables(databaseName).forEach(name -> relationTypes.put(name, RelationType.TABLE)); + getAllViews(databaseName).forEach(name -> relationTypes.put(name, RelationType.VIEW)); + return relationTypes.buildKeepingLast(); + } + @Override public List getTablesWithParameter(String databaseName, String parameterKey, String parameterValue) { @@ -164,6 +174,17 @@ public Optional> getAllTables() return delegate.getAllTables(); } + @Override + public Optional> getRelationTypes() + { + return getAllTables().flatMap(relations -> getAllViews().map(views -> { + ImmutableMap.Builder relationTypes = ImmutableMap.builder(); + relations.forEach(name -> relationTypes.put(name, RelationType.TABLE)); + views.forEach(name -> relationTypes.put(name, RelationType.VIEW)); + return relationTypes.buildKeepingLast(); + })); + } + @Override public Optional> getAllViews() { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/tracing/TracingHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/tracing/TracingHiveMetastore.java index dea02fb2ffd1..c5d9fb8b95b8 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/tracing/TracingHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/tracing/TracingHiveMetastore.java @@ -31,6 +31,7 @@ import io.trino.plugin.hive.metastore.PartitionWithStatistics; import io.trino.plugin.hive.metastore.PrincipalPrivileges; import io.trino.plugin.hive.metastore.Table; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.function.LanguageFunction; import io.trino.spi.predicate.TupleDomain; @@ -193,6 +194,31 @@ public Optional> getAllTables() }); } + @Override + public Map getRelationTypes(String databaseName) + { + Span span = tracer.spanBuilder("HiveMetastore.getRelationTypes") + .setAttribute(SCHEMA, databaseName) + .startSpan(); + return withTracing(span, () -> { + Map relationTypes = delegate.getRelationTypes(databaseName); + span.setAttribute(TABLE_RESPONSE_COUNT, relationTypes.size()); + return relationTypes; + }); + } + + @Override + public Optional> getRelationTypes() + { + Span span = tracer.spanBuilder("HiveMetastore.getRelations") + .startSpan(); + return withTracing(span, () -> { + Optional> relationTypes = delegate.getRelationTypes(); + relationTypes.ifPresent(map -> span.setAttribute(TABLE_RESPONSE_COUNT, map.size())); + return relationTypes; + }); + } + @Override public List getTablesWithParameter(String databaseName, String parameterKey, String parameterValue) { diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/CountingAccessHiveMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/CountingAccessHiveMetastore.java index 1630456391e6..a1737c3ad01f 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/CountingAccessHiveMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/CountingAccessHiveMetastore.java @@ -22,6 +22,7 @@ import io.trino.plugin.hive.PartitionStatistics; import io.trino.plugin.hive.acid.AcidTransaction; import io.trino.plugin.hive.metastore.HivePrivilegeInfo.HivePrivilege; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.function.LanguageFunction; import io.trino.spi.predicate.TupleDomain; @@ -35,8 +36,10 @@ import java.util.Set; import java.util.function.Function; +import static io.trino.plugin.hive.metastore.CountingAccessHiveMetastore.Method.GET_ALL_RELATION_TYPES; import static io.trino.plugin.hive.metastore.CountingAccessHiveMetastore.Method.GET_ALL_TABLES; import static io.trino.plugin.hive.metastore.CountingAccessHiveMetastore.Method.GET_ALL_VIEWS; +import static io.trino.plugin.hive.metastore.CountingAccessHiveMetastore.Method.GET_RELATION_TYPES_FROM_DATABASE; @ThreadSafe public class CountingAccessHiveMetastore @@ -52,6 +55,8 @@ public enum Method GET_TABLE, GET_ALL_TABLES, GET_ALL_TABLES_FROM_DATABASE, + GET_RELATION_TYPES_FROM_DATABASE, + GET_ALL_RELATION_TYPES, GET_TABLES_WITH_PARAMETER, GET_TABLE_STATISTICS, GET_ALL_VIEWS, @@ -371,6 +376,23 @@ public Optional> getAllTables() return allTables; } + @Override + public Map getRelationTypes(String databaseName) + { + methodInvocations.add(GET_RELATION_TYPES_FROM_DATABASE); + return delegate.getRelationTypes(databaseName); + } + + @Override + public Optional> getRelationTypes() + { + Optional> relationTypes = delegate.getRelationTypes(); + if (relationTypes.isPresent()) { + methodInvocations.add(GET_ALL_RELATION_TYPES); + } + return relationTypes; + } + @Override public boolean functionExists(String databaseName, String functionName, String signatureToken) { diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/UnimplementedHiveMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/UnimplementedHiveMetastore.java index cb7023359ce7..ca2fd5dba8c8 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/UnimplementedHiveMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/UnimplementedHiveMetastore.java @@ -18,6 +18,7 @@ import io.trino.plugin.hive.PartitionStatistics; import io.trino.plugin.hive.acid.AcidTransaction; import io.trino.plugin.hive.metastore.HivePrivilegeInfo.HivePrivilege; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.function.LanguageFunction; import io.trino.spi.predicate.TupleDomain; @@ -97,6 +98,18 @@ public Optional> getAllTables() throw new UnsupportedOperationException(); } + @Override + public Map getRelationTypes(String databaseName) + { + throw new UnsupportedOperationException(); + } + + @Override + public Optional> getRelationTypes() + { + throw new UnsupportedOperationException(); + } + @Override public List getTablesWithParameter(String databaseName, String parameterKey, String parameterValue) { diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestHiveMetastoreMetadataQueriesAccessOperations.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestHiveMetastoreMetadataQueriesAccessOperations.java index 9fa42c8e0c25..348f971f3229 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestHiveMetastoreMetadataQueriesAccessOperations.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestHiveMetastoreMetadataQueriesAccessOperations.java @@ -25,6 +25,7 @@ import io.trino.plugin.hive.metastore.CountingAccessHiveMetastoreUtil; import io.trino.plugin.hive.metastore.Table; import io.trino.plugin.hive.metastore.UnimplementedHiveMetastore; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.SchemaTableName; import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.DistributedQueryRunner; @@ -36,16 +37,20 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.function.Function; import java.util.stream.IntStream; import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableMap.toImmutableMap; import static io.trino.plugin.hive.HiveStorageFormat.ORC; import static io.trino.plugin.hive.TableType.MANAGED_TABLE; import static io.trino.plugin.hive.metastore.CountingAccessHiveMetastore.Method.GET_ALL_DATABASES; +import static io.trino.plugin.hive.metastore.CountingAccessHiveMetastore.Method.GET_ALL_RELATION_TYPES; import static io.trino.plugin.hive.metastore.CountingAccessHiveMetastore.Method.GET_ALL_TABLES; import static io.trino.plugin.hive.metastore.CountingAccessHiveMetastore.Method.GET_ALL_TABLES_FROM_DATABASE; import static io.trino.plugin.hive.metastore.CountingAccessHiveMetastore.Method.GET_ALL_VIEWS; import static io.trino.plugin.hive.metastore.CountingAccessHiveMetastore.Method.GET_ALL_VIEWS_FROM_DATABASE; +import static io.trino.plugin.hive.metastore.CountingAccessHiveMetastore.Method.GET_RELATION_TYPES_FROM_DATABASE; import static io.trino.plugin.hive.metastore.CountingAccessHiveMetastore.Method.GET_TABLE; import static io.trino.plugin.hive.metastore.StorageFormat.fromHiveStorageFormat; import static io.trino.testing.TestingSession.testSessionBuilder; @@ -123,8 +128,7 @@ public void testSelectTablesWithoutPredicate() mockMetastore.setAllTablesViewsImplemented(true); Multiset tables = ImmutableMultiset.builder() - .add(GET_ALL_TABLES) - .add(GET_ALL_VIEWS) + .add(GET_ALL_RELATION_TYPES) .build(); assertMetastoreInvocations("SELECT * FROM information_schema.tables", tables); assertMetastoreInvocations("SELECT * FROM system.jdbc.tables", tables); @@ -132,8 +136,7 @@ public void testSelectTablesWithoutPredicate() mockMetastore.setAllTablesViewsImplemented(false); tables = ImmutableMultiset.builder() .add(GET_ALL_DATABASES) - .addCopies(GET_ALL_TABLES_FROM_DATABASE, TEST_SCHEMAS_COUNT) - .addCopies(GET_ALL_VIEWS_FROM_DATABASE, TEST_SCHEMAS_COUNT) + .addCopies(GET_RELATION_TYPES_FROM_DATABASE, TEST_SCHEMAS_COUNT) .build(); assertMetastoreInvocations("SELECT * FROM information_schema.tables", tables); assertMetastoreInvocations("SELECT * FROM system.jdbc.tables", tables); @@ -156,14 +159,12 @@ public void testSelectTablesWithFilterBySchema() assertMetastoreInvocations( "SELECT * FROM information_schema.tables WHERE table_schema = 'test_schema_0'", ImmutableMultiset.builder() - .add(GET_ALL_TABLES_FROM_DATABASE) - .add(GET_ALL_VIEWS_FROM_DATABASE) + .add(GET_RELATION_TYPES_FROM_DATABASE) .build()); assertMetastoreInvocations( "SELECT * FROM system.jdbc.tables WHERE table_schem = 'test_schema_0'", ImmutableMultiset.builder() - .add(GET_ALL_TABLES_FROM_DATABASE) - .add(GET_ALL_VIEWS_FROM_DATABASE) + .add(GET_RELATION_TYPES_FROM_DATABASE) .build()); } @@ -177,21 +178,18 @@ public void testSelectTablesWithLikeOverSchema() "SELECT * FROM information_schema.tables WHERE table_schema LIKE 'test%'", ImmutableMultiset.builder() .add(GET_ALL_DATABASES) - .add(GET_ALL_TABLES) - .add(GET_ALL_VIEWS) + .add(GET_ALL_RELATION_TYPES) .build()); assertMetastoreInvocations( "SELECT * FROM system.jdbc.tables WHERE table_schem LIKE 'test%'", ImmutableMultiset.builder() - .add(GET_ALL_TABLES) - .add(GET_ALL_VIEWS) + .add(GET_ALL_RELATION_TYPES) .build()); mockMetastore.setAllTablesViewsImplemented(false); Multiset tables = ImmutableMultiset.builder() .add(GET_ALL_DATABASES) - .addCopies(GET_ALL_TABLES_FROM_DATABASE, TEST_SCHEMAS_COUNT) - .addCopies(GET_ALL_VIEWS_FROM_DATABASE, TEST_SCHEMAS_COUNT) + .addCopies(GET_RELATION_TYPES_FROM_DATABASE, TEST_SCHEMAS_COUNT) .build(); assertMetastoreInvocations("SELECT * FROM information_schema.tables WHERE table_schema LIKE 'test%'", tables); assertMetastoreInvocations("SELECT * FROM system.jdbc.tables WHERE table_schem LIKE 'test%'", tables); @@ -207,13 +205,11 @@ public void testSelectTablesWithFilterByTableName() "SELECT * FROM information_schema.tables WHERE table_name = 'test_table_0'", ImmutableMultiset.builder() .add(GET_ALL_DATABASES) - .add(GET_ALL_TABLES) - .add(GET_ALL_VIEWS) + .add(GET_ALL_RELATION_TYPES) .build()); Multiset tables = ImmutableMultiset.builder() - .add(GET_ALL_TABLES) - .add(GET_ALL_VIEWS) + .add(GET_ALL_RELATION_TYPES) .build(); assertMetastoreInvocations("SELECT * FROM system.jdbc.tables WHERE table_name = 'test_table_0'", tables); assertMetastoreInvocations("SELECT * FROM system.jdbc.tables WHERE table_name LIKE 'test\\_table\\_0' ESCAPE '\\'", tables); @@ -222,8 +218,7 @@ public void testSelectTablesWithFilterByTableName() mockMetastore.setAllTablesViewsImplemented(false); tables = ImmutableMultiset.builder() .add(GET_ALL_DATABASES) - .addCopies(GET_ALL_TABLES_FROM_DATABASE, TEST_SCHEMAS_COUNT) - .addCopies(GET_ALL_VIEWS_FROM_DATABASE, TEST_SCHEMAS_COUNT) + .addCopies(GET_RELATION_TYPES_FROM_DATABASE, TEST_SCHEMAS_COUNT) .build(); assertMetastoreInvocations("SELECT * FROM information_schema.tables WHERE table_name = 'test_table_0'", tables); assertMetastoreInvocations("SELECT * FROM system.jdbc.tables WHERE table_name = 'test_table_0'", tables); @@ -241,21 +236,18 @@ public void testSelectTablesWithLikeOverTableName() "SELECT * FROM information_schema.tables WHERE table_name LIKE 'test%'", ImmutableMultiset.builder() .add(GET_ALL_DATABASES) - .add(GET_ALL_TABLES) - .add(GET_ALL_VIEWS) + .add(GET_ALL_RELATION_TYPES) .build()); assertMetastoreInvocations( "SELECT * FROM system.jdbc.tables WHERE table_name LIKE 'test%'", ImmutableMultiset.builder() - .add(GET_ALL_TABLES) - .add(GET_ALL_VIEWS) + .add(GET_ALL_RELATION_TYPES) .build()); mockMetastore.setAllTablesViewsImplemented(false); Multiset tables = ImmutableMultiset.builder() .add(GET_ALL_DATABASES) - .addCopies(GET_ALL_TABLES_FROM_DATABASE, TEST_SCHEMAS_COUNT) - .addCopies(GET_ALL_VIEWS_FROM_DATABASE, TEST_SCHEMAS_COUNT) + .addCopies(GET_RELATION_TYPES_FROM_DATABASE, TEST_SCHEMAS_COUNT) .build(); assertMetastoreInvocations("SELECT * FROM information_schema.tables WHERE table_name LIKE 'test%'", tables); assertMetastoreInvocations("SELECT * FROM system.jdbc.tables WHERE table_name LIKE 'test%'", tables); @@ -271,8 +263,7 @@ public void testSelectViewsWithoutPredicate() assertMetastoreInvocations( "SELECT * FROM system.jdbc.tables WHERE table_type = 'VIEW'", ImmutableMultiset.builder() - .add(GET_ALL_TABLES) - .add(GET_ALL_VIEWS) + .add(GET_ALL_RELATION_TYPES) .build()); mockMetastore.setAllTablesViewsImplemented(false); @@ -286,8 +277,7 @@ public void testSelectViewsWithoutPredicate() "SELECT * FROM system.jdbc.tables WHERE table_type = 'VIEW'", ImmutableMultiset.builder() .add(GET_ALL_DATABASES) - .addCopies(GET_ALL_TABLES_FROM_DATABASE, TEST_SCHEMAS_COUNT) - .addCopies(GET_ALL_VIEWS_FROM_DATABASE, TEST_SCHEMAS_COUNT) + .addCopies(GET_RELATION_TYPES_FROM_DATABASE, TEST_SCHEMAS_COUNT) .build()); } @@ -308,8 +298,7 @@ public void testSelectViewsWithFilterBySchema() assertMetastoreInvocations("SELECT * FROM information_schema.views WHERE table_schema = 'test_schema_0'", ImmutableMultiset.of(GET_ALL_VIEWS_FROM_DATABASE)); assertMetastoreInvocations("SELECT * FROM system.jdbc.tables WHERE table_type = 'VIEW' AND table_schem = 'test_schema_0'", ImmutableMultiset.builder() - .add(GET_ALL_TABLES_FROM_DATABASE) - .add(GET_ALL_VIEWS_FROM_DATABASE) + .add(GET_RELATION_TYPES_FROM_DATABASE) .build()); } @@ -328,8 +317,7 @@ public void testSelectViewsWithLikeOverSchema() assertMetastoreInvocations( "SELECT * FROM system.jdbc.tables WHERE table_type = 'VIEW' AND table_schem LIKE 'test%'", ImmutableMultiset.builder() - .add(GET_ALL_TABLES) - .add(GET_ALL_VIEWS) + .add(GET_ALL_RELATION_TYPES) .build()); mockMetastore.setAllTablesViewsImplemented(false); @@ -343,8 +331,7 @@ public void testSelectViewsWithLikeOverSchema() "SELECT * FROM system.jdbc.tables WHERE table_type = 'VIEW' AND table_schem LIKE 'test%'", ImmutableMultiset.builder() .add(GET_ALL_DATABASES) - .addCopies(GET_ALL_TABLES_FROM_DATABASE, TEST_SCHEMAS_COUNT) - .addCopies(GET_ALL_VIEWS_FROM_DATABASE, TEST_SCHEMAS_COUNT) + .addCopies(GET_RELATION_TYPES_FROM_DATABASE, TEST_SCHEMAS_COUNT) .build()); } @@ -363,8 +350,7 @@ public void testSelectViewsWithFilterByTableName() assertMetastoreInvocations( "SELECT * FROM system.jdbc.tables WHERE table_type = 'VIEW' AND table_name = 'test_table_0'", ImmutableMultiset.builder() - .add(GET_ALL_TABLES) - .add(GET_ALL_VIEWS) + .add(GET_ALL_RELATION_TYPES) .build()); mockMetastore.setAllTablesViewsImplemented(false); @@ -378,8 +364,7 @@ public void testSelectViewsWithFilterByTableName() "SELECT * FROM system.jdbc.tables WHERE table_type = 'VIEW' AND table_name = 'test_table_0'", ImmutableMultiset.builder() .add(GET_ALL_DATABASES) - .addCopies(GET_ALL_TABLES_FROM_DATABASE, TEST_SCHEMAS_COUNT) - .addCopies(GET_ALL_VIEWS_FROM_DATABASE, TEST_SCHEMAS_COUNT) + .addCopies(GET_RELATION_TYPES_FROM_DATABASE, TEST_SCHEMAS_COUNT) .build()); } @@ -398,8 +383,7 @@ public void testSelectViewsWithLikeOverTableName() assertMetastoreInvocations( "SELECT * FROM system.jdbc.tables WHERE table_type = 'VIEW' AND table_name LIKE 'test%'", ImmutableMultiset.builder() - .add(GET_ALL_TABLES) - .add(GET_ALL_VIEWS) + .add(GET_ALL_RELATION_TYPES) .build()); mockMetastore.setAllTablesViewsImplemented(false); @@ -413,8 +397,7 @@ public void testSelectViewsWithLikeOverTableName() "SELECT * FROM system.jdbc.tables WHERE table_type = 'VIEW' AND table_name LIKE 'test%'", ImmutableMultiset.builder() .add(GET_ALL_DATABASES) - .addCopies(GET_ALL_TABLES_FROM_DATABASE, TEST_SCHEMAS_COUNT) - .addCopies(GET_ALL_VIEWS_FROM_DATABASE, TEST_SCHEMAS_COUNT) + .addCopies(GET_RELATION_TYPES_FROM_DATABASE, TEST_SCHEMAS_COUNT) .build()); } @@ -769,6 +752,23 @@ public Optional> getAllTables() return Optional.empty(); } + @Override + public Map getRelationTypes(String databaseName) + { + return TABLES_PER_SCHEMA.stream() + .collect(toImmutableMap(Function.identity(), ignore -> RelationType.TABLE)); + } + + @Override + public Optional> getRelationTypes() + { + if (allTablesViewsImplemented) { + return Optional.of(ALL_TABLES.stream() + .collect(toImmutableMap(Function.identity(), ignore -> RelationType.TABLE))); + } + return Optional.empty(); + } + @Override public List getAllViews(String databaseName) { From 51c04c48a8455d98f9a15190cbab5699ea5169d3 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Thu, 23 Nov 2023 13:10:45 +0100 Subject: [PATCH 414/587] Implement getRelationTypes for Iceberg This reduces number of Glue calls when reading from `information_schema.tables` or `system.jdbc.tables`. --- .../trino/plugin/iceberg/IcebergMetadata.java | 7 + .../plugin/iceberg/catalog/TrinoCatalog.java | 3 + .../catalog/glue/TrinoGlueCatalog.java | 147 +++++++++++------- .../iceberg/catalog/hms/TrinoHiveCatalog.java | 23 +++ .../catalog/jdbc/TrinoJdbcCatalog.java | 13 ++ .../catalog/nessie/TrinoNessieCatalog.java | 14 ++ .../catalog/rest/TrinoRestCatalog.java | 14 ++ ...estIcebergGlueCatalogAccessOperations.java | 2 +- 8 files changed, 163 insertions(+), 60 deletions(-) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java index 41cd8c7bdd93..4f69e72796b5 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java @@ -80,6 +80,7 @@ import io.trino.spi.connector.ProjectionApplicationResult; import io.trino.spi.connector.RelationColumnsMetadata; import io.trino.spi.connector.RelationCommentMetadata; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.RetryMode; import io.trino.spi.connector.RowChangeParadigm; import io.trino.spi.connector.SaveMode; @@ -685,6 +686,12 @@ public List listTables(ConnectorSession session, Optional getRelationTypes(ConnectorSession session, Optional schemaName) + { + return catalog.getRelationTypes(session, schemaName); + } + @Override public Map getColumnHandles(ConnectorSession session, ConnectorTableHandle tableHandle) { diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/TrinoCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/TrinoCatalog.java index 05ca9a5a6739..8d44190ee307 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/TrinoCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/TrinoCatalog.java @@ -22,6 +22,7 @@ import io.trino.spi.connector.ConnectorViewDefinition; import io.trino.spi.connector.RelationColumnsMetadata; import io.trino.spi.connector.RelationCommentMetadata; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.security.TrinoPrincipal; import org.apache.iceberg.BaseTable; @@ -75,6 +76,8 @@ public interface TrinoCatalog List listTables(ConnectorSession session, Optional namespace); + Map getRelationTypes(ConnectorSession session, Optional namespace); + Optional> streamRelationColumns( ConnectorSession session, Optional namespace, diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java index 6761c555c00c..b07c2cb90d0a 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java @@ -34,6 +34,7 @@ import com.amazonaws.services.glue.model.TableInput; import com.amazonaws.services.glue.model.UpdateTableRequest; import com.google.common.cache.Cache; +import com.google.common.collect.AbstractIterator; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.UncheckedExecutionException; @@ -66,6 +67,7 @@ import io.trino.spi.connector.MaterializedViewNotFoundException; import io.trino.spi.connector.RelationColumnsMetadata; import io.trino.spi.connector.RelationCommentMetadata; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.SchemaNotFoundException; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.connector.TableNotFoundException; @@ -92,6 +94,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -107,7 +110,9 @@ import static com.google.common.base.Throwables.throwIfUnchecked; import static com.google.common.base.Verify.verify; import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static com.google.common.collect.Streams.stream; import static io.trino.cache.CacheUtils.uncheckedCacheGet; import static io.trino.filesystem.Locations.appendPath; import static io.trino.plugin.hive.HiveErrorCode.HIVE_DATABASE_LOCATION_ERROR; @@ -156,6 +161,7 @@ import static java.lang.Boolean.parseBoolean; import static java.lang.String.format; import static java.util.Locale.ENGLISH; +import static java.util.Map.entry; import static java.util.Objects.requireNonNull; import static org.apache.iceberg.BaseMetastoreTableOperations.METADATA_LOCATION_PROP; import static org.apache.iceberg.CatalogUtil.dropTableData; @@ -358,25 +364,16 @@ public void renameNamespace(ConnectorSession session, String source, String targ @Override public List listTables(ConnectorSession session, Optional namespace) { - ImmutableList.Builder tables = ImmutableList.builder(); - try { - List namespaces = listNamespaces(session, namespace); - for (String glueNamespace : namespaces) { - try { - // Add all tables from a namespace together, in case it is removed while fetching paginated results - tables.addAll(getGlueTables(glueNamespace) - .map(table -> new SchemaTableName(glueNamespace, table.getName())) - .collect(toImmutableList())); - } - catch (EntityNotFoundException | AccessDeniedException e) { - // Namespace may have been deleted or permission denied - } - } - } - catch (AmazonServiceException e) { - throw new TrinoException(ICEBERG_CATALOG_ERROR, e); - } - return tables.build(); + return listRelations(session, namespace) + .map(Entry::getKey) + .collect(toImmutableList()); + } + + @Override + public Map getRelationTypes(ConnectorSession session, Optional namespace) + { + return listRelations(session, namespace) + .collect(toImmutableMap(Entry::getKey, Entry::getValue)); } @Override @@ -392,7 +389,7 @@ public Optional> streamRelationColumns( listNamespaces(session, namespace).stream() .flatMap(glueNamespace -> getGlueTables(glueNamespace) - .map(table -> Map.entry(new SchemaTableName(glueNamespace, table.getName()), table))) + .map(table -> entry(new SchemaTableName(glueNamespace, table.getName()), table))) .forEach(entry -> { SchemaTableName name = entry.getKey(); com.amazonaws.services.glue.model.Table table = entry.getValue(); @@ -484,7 +481,7 @@ public Optional> streamRelationComments( listNamespaces(session, namespace).stream() .flatMap(glueNamespace -> getGlueTables(glueNamespace) - .map(table -> Map.entry(new SchemaTableName(glueNamespace, table.getName()), table))) + .map(table -> entry(new SchemaTableName(glueNamespace, table.getName()), table))) .forEach(entry -> { SchemaTableName name = entry.getKey(); com.amazonaws.services.glue.model.Table table = entry.getValue(); @@ -1029,25 +1026,10 @@ public void dropView(ConnectorSession session, SchemaTableName schemaViewName) @Override public List listViews(ConnectorSession session, Optional namespace) { - ImmutableList.Builder views = ImmutableList.builder(); - try { - List namespaces = listNamespaces(session, namespace); - for (String glueNamespace : namespaces) { - try { - views.addAll(getGlueTables(glueNamespace) - .filter(table -> isTrinoView(getTableType(table), getTableParameters(table))) - .map(table -> new SchemaTableName(glueNamespace, table.getName())) - .collect(toImmutableList())); - } - catch (EntityNotFoundException | AccessDeniedException e) { - // Namespace may have been deleted or permission denied - } - } - } - catch (AmazonServiceException e) { - throw new TrinoException(ICEBERG_CATALOG_ERROR, e); - } - return views.build(); + return listRelations(session, namespace) + .filter(entry -> entry.getValue() == RelationType.VIEW) + .map(Entry::getKey) + .collect(toImmutableList()); } @Override @@ -1132,25 +1114,10 @@ private void updateView(ConnectorSession session, SchemaTableName viewName, Conn @Override public List listMaterializedViews(ConnectorSession session, Optional namespace) { - ImmutableList.Builder materializedViews = ImmutableList.builder(); - try { - List namespaces = listNamespaces(session, namespace); - for (String glueNamespace : namespaces) { - try { - materializedViews.addAll(getGlueTables(glueNamespace) - .filter(table -> isTrinoMaterializedView(getTableType(table), getTableParameters(table))) - .map(table -> new SchemaTableName(glueNamespace, table.getName())) - .collect(toImmutableList())); - } - catch (EntityNotFoundException | AccessDeniedException e) { - // Namespace may have been deleted or permission denied - } - } - } - catch (AmazonServiceException e) { - throw new TrinoException(ICEBERG_CATALOG_ERROR, e); - } - return materializedViews.build(); + return listRelations(session, namespace) + .filter(entry -> entry.getValue() == RelationType.MATERIALIZED_VIEW) + .map(Entry::getKey) + .collect(toImmutableList()); } @Override @@ -1543,6 +1510,68 @@ com.amazonaws.services.glue.model.Table getTable(SchemaTableName tableName, bool } } + private Stream> listRelations(ConnectorSession session, Optional namespace) + { + List namespaces = listNamespaces(session, namespace); + return namespaces.stream() + .flatMap(glueNamespace -> getGlueTablesWithExceptionHandling(glueNamespace) + .map(table -> { + String tableType = getTableType(table); + Map tableParameters = getTableParameters(table); + RelationType relationType; + if (isTrinoView(tableType, tableParameters)) { + relationType = RelationType.VIEW; + } + else if (isTrinoMaterializedView(tableType, tableParameters)) { + relationType = RelationType.MATERIALIZED_VIEW; + } + else { + relationType = RelationType.TABLE; + } + + return entry(new SchemaTableName(glueNamespace, table.getName()), relationType); + })); + } + + private Stream getGlueTablesWithExceptionHandling(String glueNamespace) + { + return stream(new AbstractIterator<>() + { + private Iterator delegate; + + @Override + protected com.amazonaws.services.glue.model.Table computeNext() + { + boolean firstCall = (delegate == null); + try { + if (delegate == null) { + delegate = getGlueTables(glueNamespace) + .iterator(); + } + + if (!delegate.hasNext()) { + return endOfData(); + } + return delegate.next(); + } + catch (EntityNotFoundException e) { + // database does not exist or deleted during iteration + return endOfData(); + } + catch (AccessDeniedException e) { + // permission denied may actually mean "does not exist" + if (!firstCall) { + LOG.warn(e, "Permission denied when getting next batch of tables from namespace %s", glueNamespace); + } + return endOfData(); + } + catch (AmazonServiceException e) { + throw new TrinoException(ICEBERG_CATALOG_ERROR, e); + } + } + }); + } + private Stream getGlueTables(String glueNamespace) { return getPaginatedResults( diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java index a0a02eb6b1ee..5e29362ed6f9 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java @@ -49,6 +49,7 @@ import io.trino.spi.connector.MaterializedViewNotFoundException; import io.trino.spi.connector.RelationColumnsMetadata; import io.trino.spi.connector.RelationCommentMetadata; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.SchemaNotFoundException; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.connector.TableNotFoundException; @@ -119,6 +120,8 @@ import static java.lang.String.format; import static java.util.Locale.ENGLISH; import static java.util.Objects.requireNonNull; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; import static org.apache.iceberg.BaseMetastoreTableOperations.ICEBERG_TABLE_TYPE_VALUE; import static org.apache.iceberg.BaseMetastoreTableOperations.METADATA_LOCATION_PROP; import static org.apache.iceberg.BaseMetastoreTableOperations.TABLE_TYPE_PROP; @@ -385,6 +388,26 @@ public List listTables(ConnectorSession session, Optional getRelationTypes(ConnectorSession session, Optional namespace) + { + Set materializedViews = ImmutableSet.copyOf(listMaterializedViews(session, namespace)); + Set views = ImmutableSet.copyOf(listViews(session, namespace)); + + return listTables(session, namespace).stream() + .collect(toMap( + identity(), + relation -> { + if (materializedViews.contains(relation)) { + return RelationType.MATERIALIZED_VIEW; + } + if (views.contains(relation)) { + return RelationType.VIEW; + } + return RelationType.TABLE; + })); + } + @Override public Optional> streamRelationColumns( ConnectorSession session, diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/jdbc/TrinoJdbcCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/jdbc/TrinoJdbcCatalog.java index fae4f5507cb1..86a8d3191e9a 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/jdbc/TrinoJdbcCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/jdbc/TrinoJdbcCatalog.java @@ -32,6 +32,7 @@ import io.trino.spi.connector.ConnectorViewDefinition; import io.trino.spi.connector.RelationColumnsMetadata; import io.trino.spi.connector.RelationCommentMetadata; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.connector.TableNotFoundException; import io.trino.spi.security.TrinoPrincipal; @@ -57,6 +58,7 @@ import java.util.function.UnaryOperator; import static com.google.common.base.Throwables.throwIfUnchecked; +import static com.google.common.base.Verify.verify; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.collect.Maps.transformValues; @@ -72,6 +74,7 @@ import static java.lang.String.format; import static java.util.Locale.ENGLISH; import static java.util.Objects.requireNonNull; +import static java.util.function.Function.identity; import static org.apache.iceberg.CatalogUtil.dropTableData; public class TrinoJdbcCatalog @@ -180,6 +183,16 @@ public List listTables(ConnectorSession session, Optional getRelationTypes(ConnectorSession session, Optional namespace) + { + // views and materialized views are currently not supported + verify(listViews(session, namespace).isEmpty(), "Unexpected views support"); + verify(listMaterializedViews(session, namespace).isEmpty(), "Unexpected views support"); + return listTables(session, namespace).stream() + .collect(toImmutableMap(identity(), ignore -> RelationType.TABLE)); + } + @Override public Optional> streamRelationColumns( ConnectorSession session, diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/TrinoNessieCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/TrinoNessieCatalog.java index ab0a40a3ef9a..361c2cfd6986 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/TrinoNessieCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/nessie/TrinoNessieCatalog.java @@ -31,6 +31,7 @@ import io.trino.spi.connector.ConnectorViewDefinition; import io.trino.spi.connector.RelationColumnsMetadata; import io.trino.spi.connector.RelationCommentMetadata; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.SchemaNotFoundException; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.security.TrinoPrincipal; @@ -56,7 +57,9 @@ import java.util.function.UnaryOperator; import static com.google.common.base.Throwables.throwIfUnchecked; +import static com.google.common.base.Verify.verify; import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableMap.toImmutableMap; import static io.trino.cache.CacheUtils.uncheckedCacheGet; import static io.trino.filesystem.Locations.appendPath; import static io.trino.plugin.iceberg.IcebergSchemaProperties.LOCATION_PROPERTY; @@ -67,6 +70,7 @@ import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; import static io.trino.spi.connector.SchemaTableName.schemaTableName; import static java.util.Objects.requireNonNull; +import static java.util.function.Function.identity; public class TrinoNessieCatalog extends AbstractTrinoCatalog @@ -170,6 +174,16 @@ public List listTables(ConnectorSession session, Optional getRelationTypes(ConnectorSession session, Optional namespace) + { + // views and materialized views are currently not supported + verify(listViews(session, namespace).isEmpty(), "Unexpected views support"); + verify(listMaterializedViews(session, namespace).isEmpty(), "Unexpected views support"); + return listTables(session, namespace).stream() + .collect(toImmutableMap(identity(), ignore -> RelationType.TABLE)); + } + @Override public Optional> streamRelationColumns( ConnectorSession session, diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/rest/TrinoRestCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/rest/TrinoRestCatalog.java index 9357a08c158a..74d296d574ed 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/rest/TrinoRestCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/rest/TrinoRestCatalog.java @@ -34,6 +34,7 @@ import io.trino.spi.connector.ConnectorViewDefinition; import io.trino.spi.connector.RelationColumnsMetadata; import io.trino.spi.connector.RelationCommentMetadata; +import io.trino.spi.connector.RelationType; import io.trino.spi.connector.SchemaNotFoundException; import io.trino.spi.connector.SchemaTableName; import io.trino.spi.connector.TableNotFoundException; @@ -65,7 +66,9 @@ import java.util.function.UnaryOperator; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Verify.verify; import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableMap.toImmutableMap; import static io.trino.cache.CacheUtils.uncheckedCacheGet; import static io.trino.filesystem.Locations.appendPath; import static io.trino.plugin.hive.HiveMetadata.TABLE_COMMENT; @@ -75,6 +78,7 @@ import static java.lang.String.format; import static java.util.Objects.requireNonNull; import static java.util.UUID.randomUUID; +import static java.util.function.Function.identity; public class TrinoRestCatalog implements TrinoCatalog @@ -205,6 +209,16 @@ public List listTables(ConnectorSession session, Optional getRelationTypes(ConnectorSession session, Optional namespace) + { + // views and materialized views are currently not supported + verify(listViews(session, namespace).isEmpty(), "Unexpected views support"); + verify(listMaterializedViews(session, namespace).isEmpty(), "Unexpected views support"); + return listTables(session, namespace).stream() + .collect(toImmutableMap(identity(), ignore -> RelationType.TABLE)); + } + @Override public Optional> streamRelationColumns( ConnectorSession session, diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogAccessOperations.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogAccessOperations.java index 4d87b0f51a5b..4b1ec1908c47 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogAccessOperations.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogAccessOperations.java @@ -498,7 +498,7 @@ public void testInformationSchemaTableAndColumns() session, "SELECT * FROM information_schema.tables WHERE table_schema = CURRENT_SCHEMA", ImmutableMultiset.builder() - .addCopies(GET_TABLES, 3) // TODO regression, it used to be 2 + .add(GET_TABLES) .build(), ImmutableMultiset.of()); From d32454fc00bbcf9d15bd9051b18392729d659316 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Thu, 23 Nov 2023 12:54:24 +0100 Subject: [PATCH 415/587] Rename test class to match tested class name There is no `HiveGlueMetastore`, but `GlueHiveMetastore` does exist. --- plugin/trino-hive/pom.xml | 4 ++-- ...estHiveGlueMetastore.java => TestGlueHiveMetastore.java} | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) rename plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/{TestHiveGlueMetastore.java => TestGlueHiveMetastore.java} (99%) diff --git a/plugin/trino-hive/pom.xml b/plugin/trino-hive/pom.xml index 3d37acc7756a..469310d48400 100644 --- a/plugin/trino-hive/pom.xml +++ b/plugin/trino-hive/pom.xml @@ -537,7 +537,7 @@ maven-surefire-plugin - **/TestHiveGlueMetastore.java + **/TestGlueHiveMetastore.java **/TestHiveS3AndGlueMetastoreTest.java **/TestHiveConcurrentModificationGlueMetastore.java **/TestFullParquetReader.java @@ -594,7 +594,7 @@ maven-surefire-plugin - **/TestHiveGlueMetastore.java + **/TestGlueHiveMetastore.java **/TestHiveS3AndGlueMetastoreTest.java **/TestHiveConcurrentModificationGlueMetastore.java diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestHiveGlueMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestGlueHiveMetastore.java similarity index 99% rename from plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestHiveGlueMetastore.java rename to plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestGlueHiveMetastore.java index 5ef50056585c..a81c4374cd45 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestHiveGlueMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestGlueHiveMetastore.java @@ -132,10 +132,10 @@ * See https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html#credentials-default * on ways to set your AWS credentials which will be needed to run this test. */ -public class TestHiveGlueMetastore +public class TestGlueHiveMetastore extends AbstractTestHiveLocal { - private static final Logger log = Logger.get(TestHiveGlueMetastore.class); + private static final Logger log = Logger.get(TestGlueHiveMetastore.class); private static final String PARTITION_KEY = "part_key_1"; private static final String PARTITION_KEY2 = "part_key_2"; @@ -191,7 +191,7 @@ public class TestHiveGlueMetastore private HiveMetastoreClosure metastore; private AWSGlueAsync glueClient; - public TestHiveGlueMetastore() + public TestGlueHiveMetastore() { super(TEST_DATABASE_NAME_PREFIX + randomUUID().toString().toLowerCase(ENGLISH).replace("-", "")); } From 12065f8dc943691091a88025d691353501a65005 Mon Sep 17 00:00:00 2001 From: Krzysztof Sobolewski Date: Fri, 24 Nov 2023 14:58:38 +0100 Subject: [PATCH 416/587] Move a comment to its right place Supposedly it got misplaced when the properties were sorted. --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index fd4924d7c4f4..7e8f6cf5fc41 100644 --- a/pom.xml +++ b/pom.xml @@ -147,6 +147,8 @@ 17.0.5 -missing ${project.basedir} + 8 + clean verify -DskipTests - 8 - clean verify -DskipTests America/Bahia_Banderas methods 2 From ddb4f47fb93ba8d27b70f781d032e51c144dcd8a Mon Sep 17 00:00:00 2001 From: Manfred Moser Date: Thu, 23 Nov 2023 11:41:09 -0800 Subject: [PATCH 417/587] Add docs for JDBC metadata caching props --- .../connector/jdbc-common-configurations.fragment | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/src/main/sphinx/connector/jdbc-common-configurations.fragment b/docs/src/main/sphinx/connector/jdbc-common-configurations.fragment index dc30d1a1a671..4e8bea02967f 100644 --- a/docs/src/main/sphinx/connector/jdbc-common-configurations.fragment +++ b/docs/src/main/sphinx/connector/jdbc-common-configurations.fragment @@ -31,8 +31,17 @@ connector: - `0s` (caching disabled) * - `metadata.cache-missing` - Cache the fact that metadata, including table and column statistics, is - not available + not available. - `false` +* - `metadata.schemas.cache-ttl` + - [The duration](prop-type-duration) for which schema metadata is cached. + - Defaults to the value of `metadata.cache-ttl`. +* - `metadata.tables.cache-ttl` + - [The duration](prop-type-duration) for which table metadata is cached. + - Defaults to the value of `metadata.cache-ttl`. +* - `metadata.statistics.cache-ttl` + - [The duration](prop-type-duration) for which tables statistics are cached. + - Defaults to the value of `metadata.cache-ttl`. * - `metadata.cache-maximum-size` - Maximum number of objects stored in the metadata cache - `10000` From ba8cde104cf36778b185f8b95443aafa74cef1e8 Mon Sep 17 00:00:00 2001 From: Manfred Moser Date: Fri, 24 Nov 2023 09:14:27 -0800 Subject: [PATCH 418/587] Improve grammar in JDBC config table --- .../jdbc-common-configurations.fragment | 50 ++++++++----------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/docs/src/main/sphinx/connector/jdbc-common-configurations.fragment b/docs/src/main/sphinx/connector/jdbc-common-configurations.fragment index 4e8bea02967f..5f7fe4f63772 100644 --- a/docs/src/main/sphinx/connector/jdbc-common-configurations.fragment +++ b/docs/src/main/sphinx/connector/jdbc-common-configurations.fragment @@ -4,59 +4,51 @@ The following table describes general catalog configuration properties for the connector: :::{list-table} -:widths: 30, 40, 30 +:widths: 35, 65 :header-rows: 1 * - Property name - Description - - Default value * - `case-insensitive-name-matching` - - Support case insensitive schema and table names. - - `false` + - Support case insensitive schema and table names. Defaults to `false`. * - `case-insensitive-name-matching.cache-ttl` - - This value should be a [](prop-type-duration). - - `1m` + - [Duration](prop-type-duration) for which case insensitive schema and table + names are cached. Defaults to `1m`. * - `case-insensitive-name-matching.config-file` - Path to a name mapping configuration file in JSON format that allows Trino to disambiguate between schemas and tables with similar names in - different cases. - - `null` + different cases. Defaults to `null`. * - `case-insensitive-name-matching.config-file.refresh-period` - Frequency with which Trino checks the name matching configuration file - for changes. This value should be a [](prop-type-duration). - - (refresh disabled) + for changes. The [duration value](prop-type-duration) defaults to `0s` + (refresh disabled). * - `metadata.cache-ttl` - - [The duration](prop-type-duration) for which metadata, including - table and column statistics, is cached. - - `0s` (caching disabled) + - [Duration](prop-type-duration) for which metadata, including table and + column statistics, is cached. Defaults to `0s` (caching disabled). * - `metadata.cache-missing` - Cache the fact that metadata, including table and column statistics, is - not available. - - `false` + not available. Defaults to `false`. * - `metadata.schemas.cache-ttl` - - [The duration](prop-type-duration) for which schema metadata is cached. - - Defaults to the value of `metadata.cache-ttl`. + - [Duration](prop-type-duration) for which schema metadata is cached. + Defaults to the value of `metadata.cache-ttl`. * - `metadata.tables.cache-ttl` - - [The duration](prop-type-duration) for which table metadata is cached. - - Defaults to the value of `metadata.cache-ttl`. + - [Duration](prop-type-duration) for which table metadata is cached. + Defaults to the value of `metadata.cache-ttl`. * - `metadata.statistics.cache-ttl` - - [The duration](prop-type-duration) for which tables statistics are cached. - - Defaults to the value of `metadata.cache-ttl`. + - [Duration](prop-type-duration) for which tables statistics are cached. + Defaults to the value of `metadata.cache-ttl`. * - `metadata.cache-maximum-size` - - Maximum number of objects stored in the metadata cache - - `10000` + - Maximum number of objects stored in the metadata cache. Defaults to `10000`. * - `write.batch-size` - Maximum number of statements in a batched execution. Do not change this setting from the default. Non-default values may negatively - impact performance. - - `1000` + impact performance. Defaults to `1000`. * - `dynamic-filtering.enabled` - - Push down dynamic filters into JDBC queries - - `true` + - Push down dynamic filters into JDBC queries. Defaults to `true`. * - `dynamic-filtering.wait-timeout` - - Maximum [](prop-type-duration) for which Trino waits for dynamic + - Maximum [duration](prop-type-duration) for which Trino waits for dynamic filters to be collected from the build side of joins before starting a JDBC query. Using a large timeout can potentially result in more detailed dynamic filters. However, it can also increase latency for some queries. - - `20s` + Defaults to `20s`. ::: From 6c369ef5ae687ffc8dcdaae6ff47107045c6fe1f Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Fri, 24 Nov 2023 17:34:57 +0100 Subject: [PATCH 419/587] Add missing `@Test` annotations For TestNG we have a detection reporting unannotated test methods (`ReportBadTestAnnotations`), but apparently the annotation disappeared when migrating to JUnit. --- .../src/test/java/io/trino/cache/TestEvictableCache.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/trino-cache/src/test/java/io/trino/cache/TestEvictableCache.java b/lib/trino-cache/src/test/java/io/trino/cache/TestEvictableCache.java index a8015fd203f0..5fd8d27e2ba5 100644 --- a/lib/trino-cache/src/test/java/io/trino/cache/TestEvictableCache.java +++ b/lib/trino-cache/src/test/java/io/trino/cache/TestEvictableCache.java @@ -286,6 +286,7 @@ public void testLoadStats() assertThat(value).isEqualTo("abc"); } + @Test @Timeout(TEST_TIMEOUT_SECONDS) public void testLoadFailure() throws Exception @@ -475,6 +476,7 @@ public void testInvalidateOngoingLoad() /** * Covers https://github.com/google/guava/issues/1881 */ + @Test @Timeout(TEST_TIMEOUT_SECONDS) public void testInvalidateAndLoadConcurrently() throws Exception From 0a77e6c8dffe886f0bc9986692839f39ac8aac5d Mon Sep 17 00:00:00 2001 From: David Phillips Date: Tue, 14 Nov 2023 16:20:23 -0800 Subject: [PATCH 420/587] Remove usage of attribute utility methods --- .../tracing/TracingConnectorMetadata.java | 7 ++--- .../io/trino/tracing/TracingMetadata.java | 28 +++++++++---------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/tracing/TracingConnectorMetadata.java b/core/trino-main/src/main/java/io/trino/tracing/TracingConnectorMetadata.java index 26cfe77b0373..89de7ad8879e 100644 --- a/core/trino-main/src/main/java/io/trino/tracing/TracingConnectorMetadata.java +++ b/core/trino-main/src/main/java/io/trino/tracing/TracingConnectorMetadata.java @@ -97,7 +97,6 @@ import java.util.concurrent.CompletableFuture; import java.util.function.UnaryOperator; -import static io.airlift.tracing.Tracing.attribute; import static io.trino.tracing.ScopedSpan.scopedSpan; import static java.util.Objects.requireNonNull; @@ -1378,7 +1377,7 @@ private Span startSpan(String methodName, String schemaName) private Span startSpan(String methodName, Optional schemaName) { return startSpan(methodName) - .setAllAttributes(attribute(TrinoAttributes.SCHEMA, schemaName)); + .setAttribute(TrinoAttributes.SCHEMA, schemaName.orElse(null)); } private Span startSpan(String methodName, SchemaTableName table) @@ -1391,8 +1390,8 @@ private Span startSpan(String methodName, SchemaTableName table) private Span startSpan(String methodName, SchemaTablePrefix prefix) { return startSpan(methodName) - .setAllAttributes(attribute(TrinoAttributes.SCHEMA, prefix.getSchema())) - .setAllAttributes(attribute(TrinoAttributes.TABLE, prefix.getTable())); + .setAttribute(TrinoAttributes.SCHEMA, prefix.getSchema().orElse(null)) + .setAttribute(TrinoAttributes.TABLE, prefix.getTable().orElse(null)); } private Span startSpan(String methodName, ConnectorTableHandle handle) diff --git a/core/trino-main/src/main/java/io/trino/tracing/TracingMetadata.java b/core/trino-main/src/main/java/io/trino/tracing/TracingMetadata.java index 6d669bd5e7a9..09e35c8ef453 100644 --- a/core/trino-main/src/main/java/io/trino/tracing/TracingMetadata.java +++ b/core/trino-main/src/main/java/io/trino/tracing/TracingMetadata.java @@ -111,7 +111,6 @@ import java.util.Set; import java.util.function.UnaryOperator; -import static io.airlift.tracing.Tracing.attribute; import static io.trino.tracing.ScopedSpan.scopedSpan; import static java.util.Objects.requireNonNull; @@ -1031,7 +1030,7 @@ public boolean isCatalogManagedSecurity(Session session, String catalog) @Override public boolean roleExists(Session session, String role, Optional catalog) { - Span span = getStartSpan("roleExists", catalog); + Span span = startSpan("roleExists", catalog); try (var ignored = scopedSpan(span)) { return delegate.roleExists(session, role, catalog); } @@ -1040,7 +1039,7 @@ public boolean roleExists(Session session, String role, Optional catalog @Override public void createRole(Session session, String role, Optional grantor, Optional catalog) { - Span span = getStartSpan("createRole", catalog); + Span span = startSpan("createRole", catalog); try (var ignored = scopedSpan(span)) { delegate.createRole(session, role, grantor, catalog); } @@ -1049,7 +1048,7 @@ public void createRole(Session session, String role, Optional gr @Override public void dropRole(Session session, String role, Optional catalog) { - Span span = getStartSpan("dropRole", catalog); + Span span = startSpan("dropRole", catalog); try (var ignored = scopedSpan(span)) { delegate.dropRole(session, role, catalog); } @@ -1058,7 +1057,7 @@ public void dropRole(Session session, String role, Optional catalog) @Override public Set listRoles(Session session, Optional catalog) { - Span span = getStartSpan("listRoles", catalog); + Span span = startSpan("listRoles", catalog); try (var ignored = scopedSpan(span)) { return delegate.listRoles(session, catalog); } @@ -1067,7 +1066,7 @@ public Set listRoles(Session session, Optional catalog) @Override public Set listRoleGrants(Session session, Optional catalog, TrinoPrincipal principal) { - Span span = getStartSpan("listRoleGrants", catalog); + Span span = startSpan("listRoleGrants", catalog); try (var ignored = scopedSpan(span)) { return delegate.listRoleGrants(session, catalog, principal); } @@ -1076,7 +1075,7 @@ public Set listRoleGrants(Session session, Optional catalog, @Override public void grantRoles(Session session, Set roles, Set grantees, boolean adminOption, Optional grantor, Optional catalog) { - Span span = getStartSpan("grantRoles", catalog); + Span span = startSpan("grantRoles", catalog); try (var ignored = scopedSpan(span)) { delegate.grantRoles(session, roles, grantees, adminOption, grantor, catalog); } @@ -1085,7 +1084,7 @@ public void grantRoles(Session session, Set roles, Set g @Override public void revokeRoles(Session session, Set roles, Set grantees, boolean adminOption, Optional grantor, Optional catalog) { - Span span = getStartSpan("revokeRoles", catalog); + Span span = startSpan("revokeRoles", catalog); try (var ignored = scopedSpan(span)) { delegate.revokeRoles(session, roles, grantees, adminOption, grantor, catalog); } @@ -1094,7 +1093,7 @@ public void revokeRoles(Session session, Set roles, Set @Override public Set listApplicableRoles(Session session, TrinoPrincipal principal, Optional catalog) { - Span span = getStartSpan("listApplicableRoles", catalog); + Span span = startSpan("listApplicableRoles", catalog); try (var ignored = scopedSpan(span)) { return delegate.listApplicableRoles(session, principal, catalog); } @@ -1219,7 +1218,7 @@ public Collection getFunctions(Session session, Catalog public ResolvedFunction resolveBuiltinFunction(String name, List parameterTypes) { Span span = startSpan("resolveBuiltinFunction") - .setAllAttributes(attribute(TrinoAttributes.FUNCTION, name)); + .setAttribute(TrinoAttributes.FUNCTION, name); try (var ignored = scopedSpan(span)) { return delegate.resolveBuiltinFunction(name, parameterTypes); } @@ -1467,10 +1466,9 @@ private Span startSpan(String methodName, String catalogName) .setAttribute(TrinoAttributes.CATALOG, catalogName); } - private Span getStartSpan(String methodName, Optional catalog) + private Span startSpan(String methodName, Optional catalog) { - return startSpan(methodName) - .setAllAttributes(attribute(TrinoAttributes.CATALOG, catalog)); + return startSpan(methodName, catalog.orElse(null)); } private Span startSpan(String methodName, CatalogSchemaName schema) @@ -1500,8 +1498,8 @@ private Span startSpan(String methodName, QualifiedTablePrefix prefix) { return startSpan(methodName) .setAttribute(TrinoAttributes.CATALOG, prefix.getCatalogName()) - .setAllAttributes(attribute(TrinoAttributes.SCHEMA, prefix.getSchemaName())) - .setAllAttributes(attribute(TrinoAttributes.TABLE, prefix.getTableName())); + .setAttribute(TrinoAttributes.SCHEMA, prefix.getSchemaName().orElse(null)) + .setAttribute(TrinoAttributes.TABLE, prefix.getTableName().orElse(null)); } private Span startSpan(String methodName, String catalogName, ConnectorTableMetadata tableMetadata) From 0826e179d0cbc4fa385cecaacedf1c595b350eaa Mon Sep 17 00:00:00 2001 From: David Phillips Date: Wed, 15 Nov 2023 11:46:39 -0800 Subject: [PATCH 421/587] Remove extra metastore calls in HudiSplitManager --- .../java/io/trino/plugin/hudi/HudiModule.java | 1 - .../plugin/hudi/HudiPartitionManager.java | 60 ------------- .../trino/plugin/hudi/HudiSplitManager.java | 39 +++++--- .../plugin/hudi/TestHudiPartitionManager.java | 88 ------------------- 4 files changed, 27 insertions(+), 161 deletions(-) delete mode 100644 plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiPartitionManager.java delete mode 100644 plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiPartitionManager.java diff --git a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiModule.java b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiModule.java index 279e395a986c..1e2f7b37ddba 100644 --- a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiModule.java +++ b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiModule.java @@ -66,7 +66,6 @@ public void configure(Binder binder) configBinder(binder).bindConfig(ParquetReaderConfig.class); configBinder(binder).bindConfig(ParquetWriterConfig.class); - binder.bind(HudiPartitionManager.class).in(Scopes.SINGLETON); binder.bind(HudiMetadataFactory.class).in(Scopes.SINGLETON); binder.bind(FileFormatDataSourceStats.class).in(Scopes.SINGLETON); diff --git a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiPartitionManager.java b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiPartitionManager.java deleted file mode 100644 index 089de797a342..000000000000 --- a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiPartitionManager.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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 io.trino.plugin.hudi; - -import com.google.common.collect.ImmutableList; -import com.google.inject.Inject; -import io.trino.plugin.hive.HiveColumnHandle; -import io.trino.plugin.hive.metastore.Column; -import io.trino.plugin.hive.metastore.HiveMetastore; -import io.trino.plugin.hive.metastore.Table; -import io.trino.spi.connector.TableNotFoundException; -import io.trino.spi.type.TypeManager; - -import java.util.List; -import java.util.stream.Collectors; - -import static io.trino.plugin.hive.metastore.MetastoreUtil.computePartitionKeyFilter; -import static io.trino.plugin.hive.util.HiveUtil.getPartitionKeyColumnHandles; -import static java.util.Objects.requireNonNull; - -public class HudiPartitionManager -{ - private final TypeManager typeManager; - - @Inject - public HudiPartitionManager(TypeManager typeManager) - { - this.typeManager = requireNonNull(typeManager, "typeManager is null"); - } - - public List getEffectivePartitions(HudiTableHandle tableHandle, HiveMetastore metastore) - { - Table table = metastore.getTable(tableHandle.getSchemaName(), tableHandle.getTableName()) - .orElseThrow(() -> new TableNotFoundException(tableHandle.getSchemaTableName())); - List partitionColumns = table.getPartitionColumns(); - if (partitionColumns.isEmpty()) { - return ImmutableList.of(""); - } - - List partitionColumnHandles = getPartitionKeyColumnHandles(table, typeManager); - - return metastore.getPartitionNamesByFilter( - tableHandle.getSchemaName(), - tableHandle.getTableName(), - partitionColumns.stream().map(Column::getName).collect(Collectors.toList()), - computePartitionKeyFilter(partitionColumnHandles, tableHandle.getPartitionPredicates())) - .orElseThrow(() -> new TableNotFoundException(tableHandle.getSchemaTableName())); - } -} diff --git a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiSplitManager.java b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiSplitManager.java index 8b87e0245037..86cc9085d5ea 100644 --- a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiSplitManager.java +++ b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiSplitManager.java @@ -13,6 +13,7 @@ */ package io.trino.plugin.hudi; +import com.google.common.collect.ImmutableList; import com.google.inject.Inject; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.plugin.base.classloader.ClassLoaderSafeConnectorSplitSource; @@ -29,6 +30,7 @@ import io.trino.spi.connector.DynamicFilter; import io.trino.spi.connector.TableNotFoundException; import io.trino.spi.security.ConnectorIdentity; +import io.trino.spi.type.TypeManager; import jakarta.annotation.PreDestroy; import java.util.List; @@ -36,8 +38,11 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledExecutorService; import java.util.function.BiFunction; +import java.util.stream.Collectors; import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static io.trino.plugin.hive.metastore.MetastoreUtil.computePartitionKeyFilter; +import static io.trino.plugin.hive.util.HiveUtil.getPartitionKeyColumnHandles; import static io.trino.plugin.hudi.HudiSessionProperties.getMaxOutstandingSplits; import static io.trino.plugin.hudi.HudiSessionProperties.getMaxSplitsPerSecond; import static io.trino.spi.connector.SchemaTableName.schemaTableName; @@ -47,8 +52,7 @@ public class HudiSplitManager implements ConnectorSplitManager { - private final HudiTransactionManager transactionManager; - private final HudiPartitionManager partitionManager; + private final TypeManager typeManager; private final BiFunction metastoreProvider; private final TrinoFileSystemFactory fileSystemFactory; private final ExecutorService executor; @@ -56,15 +60,13 @@ public class HudiSplitManager @Inject public HudiSplitManager( - HudiTransactionManager transactionManager, - HudiPartitionManager partitionManager, + TypeManager typeManager, BiFunction metastoreProvider, @ForHudiSplitManager ExecutorService executor, TrinoFileSystemFactory fileSystemFactory, @ForHudiSplitSource ScheduledExecutorService splitLoaderExecutorService) { - this.transactionManager = requireNonNull(transactionManager, "transactionManager is null"); - this.partitionManager = requireNonNull(partitionManager, "partitionManager is null"); + this.typeManager = requireNonNull(typeManager, "typeManager is null"); this.metastoreProvider = requireNonNull(metastoreProvider, "metastoreProvider is null"); this.executor = requireNonNull(executor, "executor is null"); this.fileSystemFactory = requireNonNull(fileSystemFactory, "fileSystemFactory is null"); @@ -86,15 +88,14 @@ public ConnectorSplitSource getSplits( Constraint constraint) { HudiTableHandle hudiTableHandle = (HudiTableHandle) tableHandle; - HudiMetadata hudiMetadata = transactionManager.get(transaction, session.getIdentity()); - Map partitionColumnHandles = hudiMetadata.getColumnHandles(session, tableHandle) - .values().stream().map(HiveColumnHandle.class::cast) - .filter(HiveColumnHandle::isPartitionKey) - .collect(toImmutableMap(HiveColumnHandle::getName, identity())); HiveMetastore metastore = metastoreProvider.apply(session.getIdentity(), (HiveTransactionHandle) transaction); Table table = metastore.getTable(hudiTableHandle.getSchemaName(), hudiTableHandle.getTableName()) .orElseThrow(() -> new TableNotFoundException(schemaTableName(hudiTableHandle.getSchemaName(), hudiTableHandle.getTableName()))); - List partitions = partitionManager.getEffectivePartitions(hudiTableHandle, metastore); + + List partitionColumns = getPartitionKeyColumnHandles(table, typeManager); + Map partitionColumnHandles = partitionColumns.stream() + .collect(toImmutableMap(HiveColumnHandle::getName, identity())); + List partitions = getPartitions(metastore, hudiTableHandle, partitionColumns); HudiSplitSource splitSource = new HudiSplitSource( session, @@ -110,4 +111,18 @@ public ConnectorSplitSource getSplits( partitions); return new ClassLoaderSafeConnectorSplitSource(splitSource, HudiSplitManager.class.getClassLoader()); } + + private static List getPartitions(HiveMetastore metastore, HudiTableHandle table, List partitionColumns) + { + if (partitionColumns.isEmpty()) { + return ImmutableList.of(""); + } + + return metastore.getPartitionNamesByFilter( + table.getSchemaName(), + table.getTableName(), + partitionColumns.stream().map(HiveColumnHandle::getName).collect(Collectors.toList()), + computePartitionKeyFilter(partitionColumns, table.getPartitionPredicates())) + .orElseThrow(() -> new TableNotFoundException(table.getSchemaTableName())); + } } diff --git a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiPartitionManager.java b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiPartitionManager.java deleted file mode 100644 index fc3357e7b681..000000000000 --- a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiPartitionManager.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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 io.trino.plugin.hudi; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import io.trino.plugin.hive.HiveBucketProperty; -import io.trino.plugin.hive.metastore.Column; -import io.trino.plugin.hive.metastore.Storage; -import io.trino.plugin.hive.metastore.Table; -import io.trino.spi.predicate.TupleDomain; -import io.trino.spi.type.TestingTypeManager; -import org.junit.jupiter.api.Test; - -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.OptionalLong; - -import static io.trino.plugin.hive.HiveStorageFormat.PARQUET; -import static io.trino.plugin.hive.HiveType.HIVE_INT; -import static io.trino.plugin.hive.HiveType.HIVE_STRING; -import static io.trino.plugin.hive.TableType.MANAGED_TABLE; -import static io.trino.plugin.hive.metastore.StorageFormat.fromHiveStorageFormat; -import static io.trino.plugin.hive.util.HiveBucketing.BucketingVersion.BUCKETING_V1; -import static io.trino.plugin.hudi.model.HudiTableType.COPY_ON_WRITE; -import static org.assertj.core.api.Assertions.assertThat; - -public class TestHudiPartitionManager -{ - private static final String SCHEMA_NAME = "schema"; - private static final String TABLE_NAME = "table"; - private static final String USER_NAME = "user"; - private static final String LOCATION = "somewhere/over/the/rainbow"; - private static final Column PARTITION_COLUMN = new Column("ds", HIVE_STRING, Optional.empty(), Map.of()); - private static final Column BUCKET_COLUMN = new Column("c1", HIVE_INT, Optional.empty(), Map.of()); - private static final Table TABLE = new Table( - SCHEMA_NAME, - TABLE_NAME, - Optional.of(USER_NAME), - MANAGED_TABLE.name(), - new Storage( - fromHiveStorageFormat(PARQUET), - Optional.of(LOCATION), - Optional.of(new HiveBucketProperty( - ImmutableList.of(BUCKET_COLUMN.getName()), - BUCKETING_V1, - 2, - ImmutableList.of())), - false, - ImmutableMap.of()), - ImmutableList.of(BUCKET_COLUMN), - ImmutableList.of(PARTITION_COLUMN), - ImmutableMap.of(), - Optional.empty(), - Optional.empty(), - OptionalLong.empty()); - private static final List PARTITIONS = ImmutableList.of("ds=2019-07-23", "ds=2019-08-23"); - private final HudiPartitionManager hudiPartitionManager = new HudiPartitionManager(new TestingTypeManager()); - private final TestingExtendedHiveMetastore metastore = new TestingExtendedHiveMetastore(TABLE, PARTITIONS); - - @Test - public void testParseValuesAndFilterPartition() - { - HudiTableHandle tableHandle = new HudiTableHandle( - SCHEMA_NAME, - TABLE_NAME, - TABLE.getStorage().getLocation(), - COPY_ON_WRITE, - TupleDomain.all(), - TupleDomain.all()); - List actualPartitions = hudiPartitionManager.getEffectivePartitions( - tableHandle, - metastore); - assertThat(actualPartitions).isEqualTo(PARTITIONS); - } -} From 479308c79304519483e366a9def50d0bc8013efe Mon Sep 17 00:00:00 2001 From: David Phillips Date: Thu, 16 Nov 2023 12:35:07 -0800 Subject: [PATCH 422/587] Remove debug logging in tests for Hive RetryDriver --- .../src/main/java/io/trino/testing/DistributedQueryRunner.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/testing/trino-testing/src/main/java/io/trino/testing/DistributedQueryRunner.java b/testing/trino-testing/src/main/java/io/trino/testing/DistributedQueryRunner.java index e77f27a55e36..1f2a040a7c98 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/DistributedQueryRunner.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/DistributedQueryRunner.java @@ -78,7 +78,6 @@ import static com.google.common.base.Throwables.throwIfUnchecked; import static com.google.common.base.Verify.verify; import static com.google.inject.util.Modules.EMPTY_MODULE; -import static io.airlift.log.Level.DEBUG; import static io.airlift.log.Level.ERROR; import static io.airlift.log.Level.WARN; import static io.airlift.testing.Closeables.closeAllSuppress; @@ -230,7 +229,6 @@ private static void setupLogging() logging.setLevel("Bootstrap", WARN); logging.setLevel("org.glassfish", ERROR); logging.setLevel("org.eclipse.jetty.server", WARN); - logging.setLevel("io.trino.plugin.hive.util.RetryDriver", DEBUG); } private static TestingTrinoServer createTestingTrinoServer( From 2745dadbbfc8ad7b55416fb889ca210583e479f4 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Thu, 16 Nov 2023 14:13:49 -0800 Subject: [PATCH 423/587] Reduce startup logging for tests --- .../test/java/io/trino/plugin/hive/HiveQueryRunner.java | 7 +------ .../java/io/trino/testing/AbstractTestQueryFramework.java | 5 +++++ .../main/java/io/trino/testing/DistributedQueryRunner.java | 7 +++++-- .../src/main/java/io/trino/testing/QueryAssertions.java | 6 +----- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/HiveQueryRunner.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/HiveQueryRunner.java index 93f9ff35d614..04eb692950ac 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/HiveQueryRunner.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/HiveQueryRunner.java @@ -63,7 +63,6 @@ import static java.nio.file.Files.createDirectories; import static java.util.Locale.ENGLISH; import static java.util.Objects.requireNonNull; -import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; public final class HiveQueryRunner @@ -360,18 +359,14 @@ private static void copyTpchTablesBucketed( Iterable> tables, ColumnNaming columnNaming) { - log.info("Loading data from %s.%s...", sourceCatalog, sourceSchema); - long startTime = System.nanoTime(); for (TpchTable table : tables) { copyTableBucketed(queryRunner, new QualifiedObjectName(sourceCatalog, sourceSchema, table.getTableName().toLowerCase(ENGLISH)), table, session, columnNaming); } - log.info("Loading from %s.%s complete in %s", sourceCatalog, sourceSchema, nanosSince(startTime).toString(SECONDS)); } private static void copyTableBucketed(QueryRunner queryRunner, QualifiedObjectName tableName, TpchTable table, Session session, ColumnNaming columnNaming) { long start = System.nanoTime(); - log.info("Running import for %s", tableName.getObjectName()); @Language("SQL") String sql; switch (tableName.getObjectName()) { case "part": @@ -400,7 +395,7 @@ private static void copyTableBucketed(QueryRunner queryRunner, QualifiedObjectNa throw new UnsupportedOperationException(); } long rows = (Long) queryRunner.execute(session, sql).getMaterializedRows().get(0).getField(0); - log.info("Imported %s rows for %s in %s", rows, tableName.getObjectName(), nanosSince(start).convertToMostSuccinctTimeUnit()); + log.info("Imported %s rows from %s in %s", rows, tableName, nanosSince(start)); } public static void main(String[] args) diff --git a/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestQueryFramework.java b/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestQueryFramework.java index 7593b180dc6d..00c67b1dca14 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestQueryFramework.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/AbstractTestQueryFramework.java @@ -16,6 +16,8 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.MoreCollectors; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import io.airlift.log.Level; +import io.airlift.log.Logging; import io.airlift.units.Duration; import io.trino.Session; import io.trino.execution.QueryInfo; @@ -107,6 +109,9 @@ public abstract class AbstractTestQueryFramework public void init() throws Exception { + Logging logging = Logging.initialize(); + logging.setLevel("org.testcontainers", Level.WARN); + afterClassCloser = AutoCloseableCloser.create(); queryRunner = afterClassCloser.register(createQueryRunner()); h2QueryRunner = afterClassCloser.register(new H2QueryRunner()); diff --git a/testing/trino-testing/src/main/java/io/trino/testing/DistributedQueryRunner.java b/testing/trino-testing/src/main/java/io/trino/testing/DistributedQueryRunner.java index 1f2a040a7c98..453ed0219004 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/DistributedQueryRunner.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/DistributedQueryRunner.java @@ -24,6 +24,7 @@ import io.airlift.log.Logging; import io.trino.Session; import io.trino.Session.SessionBuilder; +import io.trino.connector.CoordinatorDynamicCatalogManager; import io.trino.cost.StatsCalculator; import io.trino.execution.FailureInjector.InjectedFailureType; import io.trino.execution.QueryManager; @@ -36,6 +37,7 @@ import io.trino.metadata.QualifiedObjectName; import io.trino.metadata.SessionPropertyManager; import io.trino.server.BasicQueryInfo; +import io.trino.server.PluginManager; import io.trino.server.SessionPropertyDefaults; import io.trino.server.testing.FactoryConfiguration; import io.trino.server.testing.TestingTrinoServer; @@ -229,6 +231,9 @@ private static void setupLogging() logging.setLevel("Bootstrap", WARN); logging.setLevel("org.glassfish", ERROR); logging.setLevel("org.eclipse.jetty.server", WARN); + logging.setLevel("org.hibernate.validator.internal.util.Version", WARN); + logging.setLevel(PluginManager.class.getName(), WARN); + logging.setLevel(CoordinatorDynamicCatalogManager.class.getName(), WARN); } private static TestingTrinoServer createTestingTrinoServer( @@ -425,9 +430,7 @@ public List getServers() public void installPlugin(Plugin plugin) { plugins.add(plugin); - long start = System.nanoTime(); servers.forEach(server -> server.installPlugin(plugin)); - log.info("Installed plugin %s in %s", plugin.getClass().getSimpleName(), nanosSince(start).convertToMostSuccinctTimeUnit()); } @Override diff --git a/testing/trino-testing/src/main/java/io/trino/testing/QueryAssertions.java b/testing/trino-testing/src/main/java/io/trino/testing/QueryAssertions.java index 4d4a6b546eae..c40b5541a997 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/QueryAssertions.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/QueryAssertions.java @@ -511,12 +511,9 @@ public static void copyTpchTables( Session session, Iterable> tables) { - log.info("Loading data from %s.%s...", sourceCatalog, sourceSchema); - long startTime = System.nanoTime(); for (TpchTable table : tables) { copyTable(queryRunner, sourceCatalog, sourceSchema, table.getTableName().toLowerCase(ENGLISH), session); } - log.info("Loading from %s.%s complete in %s", sourceCatalog, sourceSchema, nanosSince(startTime).toString(SECONDS)); } public static void copyTable(QueryRunner queryRunner, String sourceCatalog, String sourceSchema, String sourceTable, Session session) @@ -528,10 +525,9 @@ public static void copyTable(QueryRunner queryRunner, String sourceCatalog, Stri public static void copyTable(QueryRunner queryRunner, QualifiedObjectName table, Session session) { long start = System.nanoTime(); - log.info("Running import for %s", table.getObjectName()); @Language("SQL") String sql = format("CREATE TABLE IF NOT EXISTS %s AS SELECT * FROM %s", table.getObjectName(), table); long rows = (Long) queryRunner.execute(session, sql).getMaterializedRows().get(0).getField(0); - log.info("Imported %s rows for %s in %s", rows, table.getObjectName(), nanosSince(start).convertToMostSuccinctTimeUnit()); + log.info("Imported %s rows from %s in %s", rows, table, nanosSince(start)); assertThat(queryRunner.execute(session, "SELECT count(*) FROM " + table.getObjectName()).getOnlyValue()) .as("Table is not loaded properly: %s", table.getObjectName()) From b7f0383406e9dfdf3e284de7de730ba9479a83ee Mon Sep 17 00:00:00 2001 From: David Phillips Date: Thu, 16 Nov 2023 16:35:04 -0800 Subject: [PATCH 424/587] Always initialize logging in HiveQueryRunner --- .../java/io/trino/plugin/hive/HiveQueryRunner.java | 13 +++++-------- .../io/trino/plugin/hive/s3/S3HiveQueryRunner.java | 5 +++++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/HiveQueryRunner.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/HiveQueryRunner.java index 04eb692950ac..3910565ffa73 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/HiveQueryRunner.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/HiveQueryRunner.java @@ -67,6 +67,11 @@ public final class HiveQueryRunner { + static { + Logging logging = Logging.initialize(); + logging.setLevel("org.apache.parquet.hadoop", WARN); + } + private static final Logger log = Logger.get(HiveQueryRunner.class); private HiveQueryRunner() {} @@ -234,8 +239,6 @@ public SELF setTpchDecimalTypeMapping(DecimalTypeMapping tpchDecimalTypeMapping) public DistributedQueryRunner build() throws Exception { - setupLogging(); - DistributedQueryRunner queryRunner = super.build(); try { @@ -309,12 +312,6 @@ private void populateData(DistributedQueryRunner queryRunner, HiveMetastore meta } } - private static void setupLogging() - { - Logging logging = Logging.initialize(); - logging.setLevel("org.apache.parquet.hadoop", WARN); - } - private static Database createDatabaseMetastoreObject(String name, Optional locationBase) { return Database.builder() diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/S3HiveQueryRunner.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/S3HiveQueryRunner.java index 54e927776348..fdd2d6a865f5 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/S3HiveQueryRunner.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/S3HiveQueryRunner.java @@ -17,6 +17,7 @@ import com.google.common.net.HostAndPort; import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.airlift.log.Logger; +import io.airlift.log.Logging; import io.airlift.units.Duration; import io.trino.plugin.hive.HiveQueryRunner; import io.trino.plugin.hive.containers.HiveMinioDataLake; @@ -39,6 +40,10 @@ public final class S3HiveQueryRunner { + static { + Logging.initialize(); + } + private S3HiveQueryRunner() {} public static DistributedQueryRunner create( From d9e98b21dc81340d75b62f73fa6d9befaee55199 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Thu, 16 Nov 2023 16:44:36 -0800 Subject: [PATCH 425/587] Add tracing for S3 file system --- lib/trino-filesystem-s3/pom.xml | 10 ++++++++++ .../io/trino/filesystem/s3/S3FileSystemFactory.java | 12 +++++++++++- .../trino/filesystem/s3/TestS3FileSystemAwsS3.java | 3 ++- .../filesystem/s3/TestS3FileSystemLocalStack.java | 3 ++- .../trino/filesystem/s3/TestS3FileSystemMinIo.java | 3 ++- .../trino/filesystem/s3/TestS3FileSystemS3Mock.java | 3 ++- 6 files changed, 29 insertions(+), 5 deletions(-) diff --git a/lib/trino-filesystem-s3/pom.xml b/lib/trino-filesystem-s3/pom.xml index 77c262b50d05..a8b22e4a7311 100644 --- a/lib/trino-filesystem-s3/pom.xml +++ b/lib/trino-filesystem-s3/pom.xml @@ -37,6 +37,16 @@ units + + io.opentelemetry + opentelemetry-api + + + + io.opentelemetry.instrumentation + opentelemetry-aws-sdk-2.2 + + io.trino trino-filesystem diff --git a/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3FileSystemFactory.java b/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3FileSystemFactory.java index 065ede41c99f..2b525d12ba70 100644 --- a/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3FileSystemFactory.java +++ b/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3FileSystemFactory.java @@ -14,12 +14,15 @@ package io.trino.filesystem.s3; import com.google.inject.Inject; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.awssdk.v2_2.AwsSdkTelemetry; import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.spi.security.ConnectorIdentity; import jakarta.annotation.PreDestroy; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; import software.amazon.awssdk.http.apache.ApacheHttpClient; import software.amazon.awssdk.http.apache.ProxyConfiguration; import software.amazon.awssdk.regions.Region; @@ -41,10 +44,17 @@ public final class S3FileSystemFactory private final S3Context context; @Inject - public S3FileSystemFactory(S3FileSystemConfig config) + public S3FileSystemFactory(OpenTelemetry openTelemetry, S3FileSystemConfig config) { S3ClientBuilder s3 = S3Client.builder(); + s3.overrideConfiguration(ClientOverrideConfiguration.builder() + .addExecutionInterceptor(AwsSdkTelemetry.builder(openTelemetry) + .setCaptureExperimentalSpanAttributes(true) + .setRecordIndividualHttpError(true) + .build().newExecutionInterceptor()) + .build()); + if ((config.getAwsAccessKey() != null) && (config.getAwsSecretKey() != null)) { s3.credentialsProvider(StaticCredentialsProvider.create( AwsBasicCredentials.create(config.getAwsAccessKey(), config.getAwsSecretKey()))); diff --git a/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/TestS3FileSystemAwsS3.java b/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/TestS3FileSystemAwsS3.java index 1e5543146dfa..a68493519974 100644 --- a/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/TestS3FileSystemAwsS3.java +++ b/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/TestS3FileSystemAwsS3.java @@ -14,6 +14,7 @@ package io.trino.filesystem.s3; import io.airlift.units.DataSize; +import io.opentelemetry.api.OpenTelemetry; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.regions.Region; @@ -56,7 +57,7 @@ protected S3Client createS3Client() @Override protected S3FileSystemFactory createS3FileSystemFactory() { - return new S3FileSystemFactory(new S3FileSystemConfig() + return new S3FileSystemFactory(OpenTelemetry.noop(), new S3FileSystemConfig() .setAwsAccessKey(accessKey) .setAwsSecretKey(secretKey) .setRegion(region) diff --git a/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/TestS3FileSystemLocalStack.java b/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/TestS3FileSystemLocalStack.java index 213a16f991e8..c77a6d9595fa 100644 --- a/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/TestS3FileSystemLocalStack.java +++ b/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/TestS3FileSystemLocalStack.java @@ -14,6 +14,7 @@ package io.trino.filesystem.s3; import io.airlift.units.DataSize; +import io.opentelemetry.api.OpenTelemetry; import org.testcontainers.containers.localstack.LocalStackContainer; import org.testcontainers.containers.localstack.LocalStackContainer.Service; import org.testcontainers.junit.jupiter.Container; @@ -62,7 +63,7 @@ protected S3Client createS3Client() @Override protected S3FileSystemFactory createS3FileSystemFactory() { - return new S3FileSystemFactory(new S3FileSystemConfig() + return new S3FileSystemFactory(OpenTelemetry.noop(), new S3FileSystemConfig() .setAwsAccessKey(LOCALSTACK.getAccessKey()) .setAwsSecretKey(LOCALSTACK.getSecretKey()) .setEndpoint(LOCALSTACK.getEndpointOverride(Service.S3).toString()) diff --git a/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/TestS3FileSystemMinIo.java b/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/TestS3FileSystemMinIo.java index f60672a4d92b..b1ecf78c5b63 100644 --- a/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/TestS3FileSystemMinIo.java +++ b/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/TestS3FileSystemMinIo.java @@ -14,6 +14,7 @@ package io.trino.filesystem.s3; import io.airlift.units.DataSize; +import io.opentelemetry.api.OpenTelemetry; import io.trino.testing.containers.Minio; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; @@ -72,7 +73,7 @@ protected S3Client createS3Client() @Override protected S3FileSystemFactory createS3FileSystemFactory() { - return new S3FileSystemFactory(new S3FileSystemConfig() + return new S3FileSystemFactory(OpenTelemetry.noop(), new S3FileSystemConfig() .setEndpoint(minio.getMinioAddress()) .setRegion(Minio.MINIO_REGION) .setPathStyleAccess(true) diff --git a/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/TestS3FileSystemS3Mock.java b/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/TestS3FileSystemS3Mock.java index 785a68dea1e8..ec2819697d34 100644 --- a/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/TestS3FileSystemS3Mock.java +++ b/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/TestS3FileSystemS3Mock.java @@ -15,6 +15,7 @@ import com.adobe.testing.s3mock.testcontainers.S3MockContainer; import io.airlift.units.DataSize; +import io.opentelemetry.api.OpenTelemetry; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; @@ -55,7 +56,7 @@ protected S3Client createS3Client() @Override protected S3FileSystemFactory createS3FileSystemFactory() { - return new S3FileSystemFactory(new S3FileSystemConfig() + return new S3FileSystemFactory(OpenTelemetry.noop(), new S3FileSystemConfig() .setAwsAccessKey("accesskey") .setAwsSecretKey("secretkey") .setEndpoint(S3_MOCK.getHttpEndpoint()) From 7bbf6b4db0e905c5e10ac4c4926a50a0ebf090b1 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Thu, 16 Nov 2023 22:40:16 -0800 Subject: [PATCH 426/587] Use native S3 for Hive S3 access operations test --- plugin/trino-hive/pom.xml | 6 ++ ... => TestS3FileSystemAccessOperations.java} | 58 +++++++++++++------ 2 files changed, 45 insertions(+), 19 deletions(-) rename plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/{TestTrinoS3FileSystemAccessOperations.java => TestS3FileSystemAccessOperations.java} (78%) diff --git a/plugin/trino-hive/pom.xml b/plugin/trino-hive/pom.xml index 469310d48400..e75013a5a62c 100644 --- a/plugin/trino-hive/pom.xml +++ b/plugin/trino-hive/pom.xml @@ -288,6 +288,12 @@ runtime + + io.trino + trino-filesystem-s3 + runtime + + jakarta.xml.bind jakarta.xml.bind-api diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestTrinoS3FileSystemAccessOperations.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestS3FileSystemAccessOperations.java similarity index 78% rename from plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestTrinoS3FileSystemAccessOperations.java rename to plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestS3FileSystemAccessOperations.java index 32083e867c6c..a57b47e67f5b 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestTrinoS3FileSystemAccessOperations.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/s3/TestS3FileSystemAccessOperations.java @@ -25,11 +25,14 @@ import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import io.trino.Session; +import io.trino.filesystem.s3.S3FileSystemConfig; +import io.trino.filesystem.s3.S3FileSystemFactory; import io.trino.plugin.hive.HiveQueryRunner; import io.trino.plugin.hive.NodeVersion; import io.trino.plugin.hive.metastore.HiveMetastoreConfig; import io.trino.plugin.hive.metastore.file.FileHiveMetastore; import io.trino.plugin.hive.metastore.file.FileHiveMetastoreConfig; +import io.trino.plugin.hive.metastore.tracing.TracingHiveMetastore; import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.DistributedQueryRunner; import io.trino.testing.QueryRunner; @@ -41,21 +44,24 @@ import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; -import java.io.File; +import java.util.List; +import java.util.Map; +import java.util.Optional; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.Maps.uniqueIndex; import static io.trino.plugin.hive.HiveQueryRunner.TPCH_SCHEMA; -import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; import static io.trino.testing.MultisetAssertions.assertMultisetsEqual; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.testing.containers.Minio.MINIO_ACCESS_KEY; +import static io.trino.testing.containers.Minio.MINIO_REGION; import static io.trino.testing.containers.Minio.MINIO_SECRET_KEY; import static java.util.stream.Collectors.toCollection; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; @TestInstance(PER_CLASS) @Execution(ExecutionMode.SAME_THREAD) // S3 request counters shares mutable state so can't be run from many threads simultaneously -public class TestTrinoS3FileSystemAccessOperations +public class TestS3FileSystemAccessOperations extends AbstractTestQueryFramework { private static final String BUCKET = "test-bucket"; @@ -82,22 +88,29 @@ protected QueryRunner createQueryRunner() .build(); return HiveQueryRunner.builder() - .setMetastore(distributedQueryRunner -> { - File baseDir = distributedQueryRunner.getCoordinator().getBaseDataDir().resolve("hive_data").toFile(); - return new FileHiveMetastore( - new NodeVersion("testversion"), - HDFS_FILE_SYSTEM_FACTORY, - new HiveMetastoreConfig().isHideDeltaLakeTables(), - new FileHiveMetastoreConfig() - .setCatalogDirectory(baseDir.toURI().toString()) - .setDisableLocationChecks(true) // matches Glue behavior - .setMetastoreUser("test")); - }) + .setMetastore(ignored -> new TracingHiveMetastore( + openTelemetry.getTracer("test"), + new FileHiveMetastore( + new NodeVersion("testversion"), + new S3FileSystemFactory(openTelemetry, new S3FileSystemConfig() + .setAwsAccessKey(MINIO_ACCESS_KEY) + .setAwsSecretKey(MINIO_SECRET_KEY) + .setRegion(MINIO_REGION) + .setEndpoint(minio.getMinioAddress()) + .setPathStyleAccess(true)), + new HiveMetastoreConfig().isHideDeltaLakeTables(), + new FileHiveMetastoreConfig() + .setCatalogDirectory("s3://%s/catalog".formatted(BUCKET)) + .setDisableLocationChecks(true) // matches Glue behavior + .setMetastoreUser("test")))) .setHiveProperties(ImmutableMap.builder() - .put("hive.s3.aws-access-key", MINIO_ACCESS_KEY) - .put("hive.s3.aws-secret-key", MINIO_SECRET_KEY) - .put("hive.s3.endpoint", minio.getMinioAddress()) - .put("hive.s3.path-style-access", "true") + .put("fs.hadoop.enabled", "false") + .put("fs.native-s3.enabled", "true") + .put("s3.aws-access-key", MINIO_ACCESS_KEY) + .put("s3.aws-secret-key", MINIO_SECRET_KEY) + .put("s3.region", MINIO_REGION) + .put("s3.endpoint", minio.getMinioAddress()) + .put("s3.path-style-access", "true") .put("hive.non-managed-table-writes-enabled", "true") .buildOrThrow()) .setOpenTelemetry(openTelemetry) @@ -196,7 +209,14 @@ private void assertFileSystemAccesses(Session session, @Language("SQL") String q private Multiset getOperations() { - return spanExporter.getFinishedSpanItems().stream() + List items = spanExporter.getFinishedSpanItems(); + Map spansById = uniqueIndex(items, SpanData::getSpanId); + return items.stream() + .filter(span -> span.getName().startsWith("S3.")) + .filter(span -> Optional.ofNullable(span.getParentSpanId()) + .map(spansById::get) + .map(parent -> !parent.getName().startsWith("HiveMetastore.")) + .orElse(true)) .map(SpanData::getName) .collect(toCollection(HashMultiset::create)); } From cfce507aaafb5a5d300e420b8cde9ad27aa1f21a Mon Sep 17 00:00:00 2001 From: David Phillips Date: Thu, 16 Nov 2023 23:41:06 -0800 Subject: [PATCH 427/587] Remove unused test dependencies for file system modules --- lib/trino-filesystem-azure/pom.xml | 42 ---------------------------- lib/trino-filesystem-gcs/pom.xml | 12 -------- lib/trino-filesystem-manager/pom.xml | 6 ---- lib/trino-filesystem-s3/pom.xml | 6 ---- lib/trino-filesystem/pom.xml | 6 ---- 5 files changed, 72 deletions(-) diff --git a/lib/trino-filesystem-azure/pom.xml b/lib/trino-filesystem-azure/pom.xml index bef5ce798b50..3d6ff6e53885 100644 --- a/lib/trino-filesystem-azure/pom.xml +++ b/lib/trino-filesystem-azure/pom.xml @@ -130,30 +130,12 @@ jakarta.validation-api - - io.airlift - slice - runtime - - io.airlift junit-extensions test - - io.airlift - log-manager - test - - - - io.airlift - testing - test - - io.trino trino-filesystem @@ -162,24 +144,6 @@ test - - io.trino - trino-main - test - - - - io.trino.hive - hive-apache - test - - - - it.unimi.dsi - fastutil - test - - org.assertj assertj-core @@ -191,12 +155,6 @@ junit-jupiter-api test - - - org.testcontainers - testcontainers - test - diff --git a/lib/trino-filesystem-gcs/pom.xml b/lib/trino-filesystem-gcs/pom.xml index 573302a50bdc..bab94c48560a 100644 --- a/lib/trino-filesystem-gcs/pom.xml +++ b/lib/trino-filesystem-gcs/pom.xml @@ -129,24 +129,12 @@ provided - - org.jetbrains - annotations - provided - - io.airlift junit-extensions test - - io.airlift - testing - test - - io.trino trino-filesystem diff --git a/lib/trino-filesystem-manager/pom.xml b/lib/trino-filesystem-manager/pom.xml index b82ba37f2891..70bbc4ac4356 100644 --- a/lib/trino-filesystem-manager/pom.xml +++ b/lib/trino-filesystem-manager/pom.xml @@ -78,12 +78,6 @@ test - - org.assertj - assertj-core - test - - org.junit.jupiter junit-jupiter-api diff --git a/lib/trino-filesystem-s3/pom.xml b/lib/trino-filesystem-s3/pom.xml index a8b22e4a7311..2157886d776b 100644 --- a/lib/trino-filesystem-s3/pom.xml +++ b/lib/trino-filesystem-s3/pom.xml @@ -160,12 +160,6 @@ test - - io.trino - trino-testing-services - test - - org.assertj assertj-core diff --git a/lib/trino-filesystem/pom.xml b/lib/trino-filesystem/pom.xml index a50bb94038a5..b0e98999fb83 100644 --- a/lib/trino-filesystem/pom.xml +++ b/lib/trino-filesystem/pom.xml @@ -58,12 +58,6 @@ provided - - com.google.errorprone - error_prone_annotations - runtime - - io.airlift junit-extensions From 728714a20bc0e5b7fb19ecbd366ad5e460f9a728 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Fri, 17 Nov 2023 00:21:31 -0800 Subject: [PATCH 428/587] Add tracing for Azure file system --- lib/trino-filesystem-azure/pom.xml | 10 ++++++++ .../filesystem/azure/AzureFileSystem.java | 15 +++++++++++- .../azure/AzureFileSystemFactory.java | 23 +++++++++++++++---- .../azure/AbstractTestAzureFileSystem.java | 6 ++++- pom.xml | 12 ++++++++++ 5 files changed, 59 insertions(+), 7 deletions(-) diff --git a/lib/trino-filesystem-azure/pom.xml b/lib/trino-filesystem-azure/pom.xml index 3d6ff6e53885..e54358a42d3f 100644 --- a/lib/trino-filesystem-azure/pom.xml +++ b/lib/trino-filesystem-azure/pom.xml @@ -34,6 +34,11 @@ azure-core-http-okhttp + + com.azure + azure-core-tracing-opentelemetry + + com.azure azure-identity @@ -110,6 +115,11 @@ units + + io.opentelemetry + opentelemetry-api + + io.trino trino-filesystem diff --git a/lib/trino-filesystem-azure/src/main/java/io/trino/filesystem/azure/AzureFileSystem.java b/lib/trino-filesystem-azure/src/main/java/io/trino/filesystem/azure/AzureFileSystem.java index 33bfad0703de..5327332d0d07 100644 --- a/lib/trino-filesystem-azure/src/main/java/io/trino/filesystem/azure/AzureFileSystem.java +++ b/lib/trino-filesystem-azure/src/main/java/io/trino/filesystem/azure/AzureFileSystem.java @@ -15,6 +15,8 @@ import com.azure.core.http.HttpClient; import com.azure.core.http.rest.PagedIterable; +import com.azure.core.util.ClientOptions; +import com.azure.core.util.TracingOptions; import com.azure.storage.blob.BlobClient; import com.azure.storage.blob.BlobContainerClient; import com.azure.storage.blob.BlobContainerClientBuilder; @@ -60,15 +62,24 @@ public class AzureFileSystem implements TrinoFileSystem { private final HttpClient httpClient; + private final TracingOptions tracingOptions; private final AzureAuth azureAuth; private final int readBlockSizeBytes; private final long writeBlockSizeBytes; private final int maxWriteConcurrency; private final long maxSingleUploadSizeBytes; - public AzureFileSystem(HttpClient httpClient, AzureAuth azureAuth, DataSize readBlockSize, DataSize writeBlockSize, int maxWriteConcurrency, DataSize maxSingleUploadSize) + public AzureFileSystem( + HttpClient httpClient, + TracingOptions tracingOptions, + AzureAuth azureAuth, + DataSize readBlockSize, + DataSize writeBlockSize, + int maxWriteConcurrency, + DataSize maxSingleUploadSize) { this.httpClient = requireNonNull(httpClient, "httpClient is null"); + this.tracingOptions = requireNonNull(tracingOptions, "tracingOptions is null"); this.azureAuth = requireNonNull(azureAuth, "azureAuth is null"); this.readBlockSizeBytes = toIntExact(readBlockSize.toBytes()); this.writeBlockSizeBytes = writeBlockSize.toBytes(); @@ -466,6 +477,7 @@ private BlobContainerClient createBlobContainerClient(AzureLocation location) BlobContainerClientBuilder builder = new BlobContainerClientBuilder() .httpClient(httpClient) + .clientOptions(new ClientOptions().setTracingOptions(tracingOptions)) .endpoint(String.format("https://%s.blob.core.windows.net", location.account())); azureAuth.setAuth(location.account(), builder); location.container().ifPresent(builder::containerName); @@ -478,6 +490,7 @@ private DataLakeFileSystemClient createFileSystemClient(AzureLocation location) DataLakeServiceClientBuilder builder = new DataLakeServiceClientBuilder() .httpClient(httpClient) + .clientOptions(new ClientOptions().setTracingOptions(tracingOptions)) .endpoint(String.format("https://%s.dfs.core.windows.net", location.account())); azureAuth.setAuth(location.account(), builder); DataLakeServiceClient client = builder.buildClient(); diff --git a/lib/trino-filesystem-azure/src/main/java/io/trino/filesystem/azure/AzureFileSystemFactory.java b/lib/trino-filesystem-azure/src/main/java/io/trino/filesystem/azure/AzureFileSystemFactory.java index ea6345760993..3f62ae955e19 100644 --- a/lib/trino-filesystem-azure/src/main/java/io/trino/filesystem/azure/AzureFileSystemFactory.java +++ b/lib/trino-filesystem-azure/src/main/java/io/trino/filesystem/azure/AzureFileSystemFactory.java @@ -15,9 +15,12 @@ import com.azure.core.http.HttpClient; import com.azure.core.http.okhttp.OkHttpAsyncClientProvider; +import com.azure.core.tracing.opentelemetry.OpenTelemetryTracingOptions; import com.azure.core.util.HttpClientOptions; +import com.azure.core.util.TracingOptions; import com.google.inject.Inject; import io.airlift.units.DataSize; +import io.opentelemetry.api.OpenTelemetry; import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.spi.security.ConnectorIdentity; @@ -33,12 +36,13 @@ public class AzureFileSystemFactory private final DataSize writeBlockSize; private final int maxWriteConcurrency; private final DataSize maxSingleUploadSize; + private final TracingOptions tracingOptions; private final HttpClient httpClient; @Inject - public AzureFileSystemFactory(AzureAuth azureAuth, AzureFileSystemConfig config) + public AzureFileSystemFactory(OpenTelemetry openTelemetry, AzureAuth azureAuth, AzureFileSystemConfig config) { - this( + this(openTelemetry, azureAuth, config.getReadBlockSize(), config.getWriteBlockSize(), @@ -46,7 +50,13 @@ public AzureFileSystemFactory(AzureAuth azureAuth, AzureFileSystemConfig config) config.getMaxSingleUploadSize()); } - public AzureFileSystemFactory(AzureAuth azureAuth, DataSize readBlockSize, DataSize writeBlockSize, int maxWriteConcurrency, DataSize maxSingleUploadSize) + public AzureFileSystemFactory( + OpenTelemetry openTelemetry, + AzureAuth azureAuth, + DataSize readBlockSize, + DataSize writeBlockSize, + int maxWriteConcurrency, + DataSize maxSingleUploadSize) { this.auth = requireNonNull(azureAuth, "azureAuth is null"); this.readBlockSize = requireNonNull(readBlockSize, "readBlockSize is null"); @@ -54,12 +64,15 @@ public AzureFileSystemFactory(AzureAuth azureAuth, DataSize readBlockSize, DataS checkArgument(maxWriteConcurrency >= 0, "maxWriteConcurrency is negative"); this.maxWriteConcurrency = maxWriteConcurrency; this.maxSingleUploadSize = requireNonNull(maxSingleUploadSize, "maxSingleUploadSize is null"); - this.httpClient = HttpClient.createDefault(new HttpClientOptions().setHttpClientProvider(OkHttpAsyncClientProvider.class)); + this.tracingOptions = new OpenTelemetryTracingOptions().setOpenTelemetry(openTelemetry); + this.httpClient = HttpClient.createDefault((HttpClientOptions) new HttpClientOptions() + .setHttpClientProvider(OkHttpAsyncClientProvider.class) + .setTracingOptions(tracingOptions)); } @Override public TrinoFileSystem create(ConnectorIdentity identity) { - return new AzureFileSystem(httpClient, auth, readBlockSize, writeBlockSize, maxWriteConcurrency, maxSingleUploadSize); + return new AzureFileSystem(httpClient, tracingOptions, auth, readBlockSize, writeBlockSize, maxWriteConcurrency, maxSingleUploadSize); } } diff --git a/lib/trino-filesystem-azure/src/test/java/io/trino/filesystem/azure/AbstractTestAzureFileSystem.java b/lib/trino-filesystem-azure/src/test/java/io/trino/filesystem/azure/AbstractTestAzureFileSystem.java index d7dd40ef416f..91f142324f4c 100644 --- a/lib/trino-filesystem-azure/src/test/java/io/trino/filesystem/azure/AbstractTestAzureFileSystem.java +++ b/lib/trino-filesystem-azure/src/test/java/io/trino/filesystem/azure/AbstractTestAzureFileSystem.java @@ -22,6 +22,7 @@ import com.azure.storage.file.datalake.DataLakeFileSystemClientBuilder; import com.azure.storage.file.datalake.models.PathItem; import com.azure.storage.file.datalake.options.DataLakePathDeleteOptions; +import io.opentelemetry.api.OpenTelemetry; import io.trino.filesystem.AbstractTestTrinoFileSystem; import io.trino.filesystem.Location; import io.trino.filesystem.TrinoFileSystem; @@ -84,7 +85,10 @@ protected void initialize(String account, String accountKey, AccountKind expecte // this will fail if the container already exists, which is what we want blobContainerClient.create(); - fileSystem = new AzureFileSystemFactory(new AzureAuthAccessKey(accountKey), new AzureFileSystemConfig()).create(ConnectorIdentity.ofUser("test")); + fileSystem = new AzureFileSystemFactory( + OpenTelemetry.noop(), + new AzureAuthAccessKey(accountKey), + new AzureFileSystemConfig()).create(ConnectorIdentity.ofUser("test")); cleanupFiles(); } diff --git a/pom.xml b/pom.xml index 7e8f6cf5fc41..474b89efb8e0 100644 --- a/pom.xml +++ b/pom.xml @@ -426,6 +426,18 @@ + + com.azure + azure-core-tracing-opentelemetry + 1.0.0-beta.42 + + + com.azure + azure-core + + + + com.clearspring.analytics stream From f62c23d092c7b20aeaf8c8c7f90418be824e2fdf Mon Sep 17 00:00:00 2001 From: David Phillips Date: Fri, 17 Nov 2023 13:10:59 -0800 Subject: [PATCH 429/587] Add tracing for GCS file system --- lib/trino-filesystem-gcs/pom.xml | 6 ++++++ plugin/trino-hive/pom.xml | 24 ++++++++++++------------ plugin/trino-iceberg/pom.xml | 24 ++++++++++++------------ 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/lib/trino-filesystem-gcs/pom.xml b/lib/trino-filesystem-gcs/pom.xml index bab94c48560a..1afd968fff64 100644 --- a/lib/trino-filesystem-gcs/pom.xml +++ b/lib/trino-filesystem-gcs/pom.xml @@ -129,6 +129,12 @@ provided + + io.opentelemetry + opentelemetry-opencensus-shim + runtime + + io.airlift junit-extensions diff --git a/plugin/trino-hive/pom.xml b/plugin/trino-hive/pom.xml index e75013a5a62c..7e4cddc5c53e 100644 --- a/plugin/trino-hive/pom.xml +++ b/plugin/trino-hive/pom.xml @@ -288,6 +288,18 @@ runtime + + io.opentelemetry + opentelemetry-sdk + runtime + + + + io.opentelemetry + opentelemetry-sdk-trace + runtime + + io.trino trino-filesystem-s3 @@ -334,24 +346,12 @@ - - io.opentelemetry - opentelemetry-sdk - test - - io.opentelemetry opentelemetry-sdk-testing test - - io.opentelemetry - opentelemetry-sdk-trace - test - - io.trino diff --git a/plugin/trino-iceberg/pom.xml b/plugin/trino-iceberg/pom.xml index cdec3dfeaae3..95efd5f19a9f 100644 --- a/plugin/trino-iceberg/pom.xml +++ b/plugin/trino-iceberg/pom.xml @@ -332,6 +332,18 @@ runtime + + io.opentelemetry + opentelemetry-sdk + runtime + + + + io.opentelemetry + opentelemetry-sdk-trace + runtime + + io.opentelemetry.instrumentation opentelemetry-aws-sdk-1.11 @@ -405,24 +417,12 @@ - - io.opentelemetry - opentelemetry-sdk - test - - io.opentelemetry opentelemetry-sdk-testing test - - io.opentelemetry - opentelemetry-sdk-trace - test - - io.trino trino-blackhole From 8bb2daa31f57c853f64ca3bc5813110beb77e6a7 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Fri, 17 Nov 2023 23:50:33 -0800 Subject: [PATCH 430/587] Move AWS classes used for Glue metastore --- .../metastore/glue/TestDeltaLakeCleanUpGlueMetastore.java | 2 +- .../plugin/hive/{aws => metastore/glue}/AwsApiCallStats.java | 2 +- .../hive/{aws => metastore/glue}/AwsSdkClientCoreStats.java | 2 +- .../java/io/trino/plugin/hive/metastore/glue/AwsSdkUtil.java | 1 - .../io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java | 1 - .../io/trino/plugin/hive/metastore/glue/GlueMetastoreStats.java | 2 -- .../trino/plugin/iceberg/catalog/glue/GlueMetastoreMethod.java | 2 +- .../catalog/glue/TestIcebergGlueCatalogConnectorSmokeTest.java | 2 +- .../catalog/glue/TestIcebergGlueCatalogMaterializedView.java | 2 +- .../iceberg/catalog/glue/TestIcebergGlueCatalogSkipArchive.java | 2 +- .../deltalake/TestDeltaLakeDatabricksCleanUpGlueMetastore.java | 2 +- 11 files changed, 8 insertions(+), 12 deletions(-) rename plugin/trino-hive/src/main/java/io/trino/plugin/hive/{aws => metastore/glue}/AwsApiCallStats.java (97%) rename plugin/trino-hive/src/main/java/io/trino/plugin/hive/{aws => metastore/glue}/AwsSdkClientCoreStats.java (99%) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeCleanUpGlueMetastore.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeCleanUpGlueMetastore.java index 7b8f34702341..ff763fd2e6cf 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeCleanUpGlueMetastore.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeCleanUpGlueMetastore.java @@ -20,7 +20,7 @@ import com.amazonaws.services.glue.model.GetDatabasesRequest; import com.amazonaws.services.glue.model.GetDatabasesResult; import io.airlift.log.Logger; -import io.trino.plugin.hive.aws.AwsApiCallStats; +import io.trino.plugin.hive.metastore.glue.AwsApiCallStats; import org.junit.jupiter.api.Test; import java.util.List; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/AwsApiCallStats.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/AwsApiCallStats.java similarity index 97% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/AwsApiCallStats.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/AwsApiCallStats.java index ff650da566cb..8b45d5bc4af0 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/AwsApiCallStats.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/AwsApiCallStats.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.aws; +package io.trino.plugin.hive.metastore.glue; import com.google.errorprone.annotations.ThreadSafe; import io.airlift.stats.CounterStat; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/AwsSdkClientCoreStats.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/AwsSdkClientCoreStats.java similarity index 99% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/AwsSdkClientCoreStats.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/AwsSdkClientCoreStats.java index 0fb4d6aa918e..a3df4f3732ab 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/aws/AwsSdkClientCoreStats.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/AwsSdkClientCoreStats.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.hive.aws; +package io.trino.plugin.hive.metastore.glue; import com.amazonaws.Request; import com.amazonaws.Response; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/AwsSdkUtil.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/AwsSdkUtil.java index 646252513c11..0e15916e6e45 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/AwsSdkUtil.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/AwsSdkUtil.java @@ -14,7 +14,6 @@ package io.trino.plugin.hive.metastore.glue; import com.google.common.collect.AbstractIterator; -import io.trino.plugin.hive.aws.AwsApiCallStats; import java.util.Iterator; import java.util.function.BiConsumer; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java index c4941e9cd0c7..141b33b30d3b 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java @@ -84,7 +84,6 @@ import io.trino.plugin.hive.SchemaAlreadyExistsException; import io.trino.plugin.hive.TableAlreadyExistsException; import io.trino.plugin.hive.acid.AcidTransaction; -import io.trino.plugin.hive.aws.AwsApiCallStats; import io.trino.plugin.hive.metastore.Column; import io.trino.plugin.hive.metastore.Database; import io.trino.plugin.hive.metastore.HiveColumnStatistics; diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueMetastoreStats.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueMetastoreStats.java index 4f714a4b95d2..67f09bc21b17 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueMetastoreStats.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueMetastoreStats.java @@ -14,8 +14,6 @@ package io.trino.plugin.hive.metastore.glue; import com.amazonaws.metrics.RequestMetricCollector; -import io.trino.plugin.hive.aws.AwsApiCallStats; -import io.trino.plugin.hive.aws.AwsSdkClientCoreStats; import org.weakref.jmx.Flatten; import org.weakref.jmx.Managed; import org.weakref.jmx.Nested; diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/GlueMetastoreMethod.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/GlueMetastoreMethod.java index b9ef1975c10f..7e69a37a32de 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/GlueMetastoreMethod.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/GlueMetastoreMethod.java @@ -14,7 +14,7 @@ package io.trino.plugin.iceberg.catalog.glue; import com.google.common.math.DoubleMath; -import io.trino.plugin.hive.aws.AwsApiCallStats; +import io.trino.plugin.hive.metastore.glue.AwsApiCallStats; import io.trino.plugin.hive.metastore.glue.GlueMetastoreStats; import java.math.RoundingMode; diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogConnectorSmokeTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogConnectorSmokeTest.java index a00fa9a368c4..1e5f60ba8d54 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogConnectorSmokeTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogConnectorSmokeTest.java @@ -37,7 +37,7 @@ import io.trino.hdfs.HdfsEnvironment; import io.trino.hdfs.TrinoHdfsFileSystemStats; import io.trino.hdfs.authentication.NoHdfsAuthentication; -import io.trino.plugin.hive.aws.AwsApiCallStats; +import io.trino.plugin.hive.metastore.glue.AwsApiCallStats; import io.trino.plugin.iceberg.BaseIcebergConnectorSmokeTest; import io.trino.plugin.iceberg.IcebergQueryRunner; import io.trino.plugin.iceberg.SchemaInitializer; diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogMaterializedView.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogMaterializedView.java index 5bb341e92173..02d0e4e6ac4d 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogMaterializedView.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogMaterializedView.java @@ -23,7 +23,7 @@ import com.amazonaws.services.glue.model.Table; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import io.trino.plugin.hive.aws.AwsApiCallStats; +import io.trino.plugin.hive.metastore.glue.AwsApiCallStats; import io.trino.plugin.iceberg.BaseIcebergMaterializedViewTest; import io.trino.plugin.iceberg.IcebergQueryRunner; import io.trino.plugin.iceberg.SchemaInitializer; diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogSkipArchive.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogSkipArchive.java index 629e97e1a847..178b5f7ae50e 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogSkipArchive.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogSkipArchive.java @@ -23,7 +23,7 @@ import com.amazonaws.services.glue.model.TableVersion; import com.amazonaws.services.glue.model.UpdateTableRequest; import com.google.common.collect.ImmutableMap; -import io.trino.plugin.hive.aws.AwsApiCallStats; +import io.trino.plugin.hive.metastore.glue.AwsApiCallStats; import io.trino.plugin.iceberg.IcebergQueryRunner; import io.trino.plugin.iceberg.SchemaInitializer; import io.trino.plugin.iceberg.fileio.ForwardingFileIo; diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeDatabricksCleanUpGlueMetastore.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeDatabricksCleanUpGlueMetastore.java index b08200cd6ae1..cf43abce0cef 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeDatabricksCleanUpGlueMetastore.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeDatabricksCleanUpGlueMetastore.java @@ -21,7 +21,7 @@ import com.amazonaws.services.glue.model.GetDatabasesRequest; import com.amazonaws.services.glue.model.GetDatabasesResult; import io.airlift.log.Logger; -import io.trino.plugin.hive.aws.AwsApiCallStats; +import io.trino.plugin.hive.metastore.glue.AwsApiCallStats; import io.trino.tempto.ProductTest; import org.testng.annotations.Test; From eacbd089f8a13fcb3131b185812d480f569cfad4 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sat, 18 Nov 2023 11:04:57 -0800 Subject: [PATCH 431/587] Remove deprecated method in InvocationConvention --- .../java/io/trino/spi/function/InvocationConvention.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/core/trino-spi/src/main/java/io/trino/spi/function/InvocationConvention.java b/core/trino-spi/src/main/java/io/trino/spi/function/InvocationConvention.java index a494839c5253..74b9f266ce16 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/function/InvocationConvention.java +++ b/core/trino-spi/src/main/java/io/trino/spi/function/InvocationConvention.java @@ -63,13 +63,6 @@ public boolean supportsSession() return supportsSession; } - // TODO remove some time after nearest release - @Deprecated - public boolean supportsInstanceFactor() - { - return supportsInstanceFactory(); - } - public boolean supportsInstanceFactory() { return supportsInstanceFactory; From e9fd236551de04a759924b9be2d51dbd7b98a255 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sat, 18 Nov 2023 13:34:54 -0800 Subject: [PATCH 432/587] Inline unnecessary checkArgument methods --- .../spi/resourcegroups/ResourceGroupId.java | 16 ++++++--------- .../type/QuantileDigestParametricType.java | 20 ++++++------------- .../java/io/trino/spi/type/TimeZoneKey.java | 16 ++++++--------- 3 files changed, 18 insertions(+), 34 deletions(-) diff --git a/core/trino-spi/src/main/java/io/trino/spi/resourcegroups/ResourceGroupId.java b/core/trino-spi/src/main/java/io/trino/spi/resourcegroups/ResourceGroupId.java index b589686aed03..8b0e15adf258 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/resourcegroups/ResourceGroupId.java +++ b/core/trino-spi/src/main/java/io/trino/spi/resourcegroups/ResourceGroupId.java @@ -21,7 +21,6 @@ import java.util.Objects; import java.util.Optional; -import static java.lang.String.format; import static java.util.Collections.singletonList; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.joining; @@ -50,9 +49,13 @@ private static List append(List list, String element) @JsonCreator public ResourceGroupId(List segments) { - checkArgument(!segments.isEmpty(), "Resource group id is empty"); + if (segments.isEmpty()) { + throw new IllegalArgumentException("Resource group id is empty"); + } for (String segment : segments) { - checkArgument(!segment.isEmpty(), "Empty segment in resource group id"); + if (segment.isEmpty()) { + throw new IllegalArgumentException("Empty segment in resource group id"); + } } this.segments = segments; } @@ -90,13 +93,6 @@ public boolean isAncestorOf(ResourceGroupId descendant) return descendantSegments.subList(0, segments.size()).equals(segments); } - private static void checkArgument(boolean argument, String format, Object... args) - { - if (!argument) { - throw new IllegalArgumentException(format(format, args)); - } - } - @Override public String toString() { diff --git a/core/trino-spi/src/main/java/io/trino/spi/type/QuantileDigestParametricType.java b/core/trino-spi/src/main/java/io/trino/spi/type/QuantileDigestParametricType.java index 90b1d6a8f92e..e8201cb61562 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/type/QuantileDigestParametricType.java +++ b/core/trino-spi/src/main/java/io/trino/spi/type/QuantileDigestParametricType.java @@ -15,8 +15,6 @@ import java.util.List; -import static java.lang.String.format; - public class QuantileDigestParametricType implements ParametricType { @@ -31,20 +29,14 @@ public String getName() @Override public Type createType(TypeManager typeManager, List parameters) { - checkArgument(parameters.size() == 1, "QDIGEST type expects exactly one type as a parameter, got %s", parameters); - checkArgument( - parameters.get(0).getKind() == ParameterKind.TYPE, - "QDIGEST expects type as a parameter, got %s", - parameters); + if (parameters.size() != 1) { + throw new IllegalArgumentException("QDIGEST type expects exactly one type as a parameter, got " + parameters); + } + if (parameters.get(0).getKind() != ParameterKind.TYPE) { + throw new IllegalArgumentException("QDIGEST expects type as a parameter, got " + parameters); + } // Validation check on the acceptable type (bigint, real, double) intentionally omitted // because this is validated in each function and to allow for consistent error messaging return new QuantileDigestType(parameters.get(0).getType()); } - - private static void checkArgument(boolean argument, String format, Object... args) - { - if (!argument) { - throw new IllegalArgumentException(format(format, args)); - } - } } diff --git a/core/trino-spi/src/main/java/io/trino/spi/type/TimeZoneKey.java b/core/trino-spi/src/main/java/io/trino/spi/type/TimeZoneKey.java index 647e4a994669..8b4af5a4edcd 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/type/TimeZoneKey.java +++ b/core/trino-spi/src/main/java/io/trino/spi/type/TimeZoneKey.java @@ -35,7 +35,6 @@ import static java.lang.Math.abs; import static java.lang.Math.max; import static java.lang.Math.toIntExact; -import static java.lang.String.format; import static java.util.Objects.requireNonNull; public final class TimeZoneKey @@ -157,7 +156,9 @@ public static Set getTimeZoneKeys() @JsonCreator public static TimeZoneKey getTimeZoneKey(short timeZoneKey) { - checkArgument(timeZoneKey < TIME_ZONE_KEYS.length && TIME_ZONE_KEYS[timeZoneKey] != null, "Invalid time zone key %s", timeZoneKey); + if ((timeZoneKey >= TIME_ZONE_KEYS.length) || (TIME_ZONE_KEYS[timeZoneKey] == null)) { + throw new IllegalArgumentException("Invalid time zone key: " + timeZoneKey); + } return TIME_ZONE_KEYS[timeZoneKey]; } @@ -167,7 +168,9 @@ public static TimeZoneKey getTimeZoneKey(short timeZoneKey) public static TimeZoneKey getTimeZoneKey(String zoneId) { requireNonNull(zoneId, "zoneId is null"); - checkArgument(!zoneId.isEmpty(), "Zone id is an empty string"); + if (zoneId.isEmpty()) { + throw new IllegalArgumentException("Zone id is an empty string"); + } TimeZoneKey zoneKey = ZONE_ID_TO_KEY.get(zoneId); if (zoneKey == null) { @@ -347,11 +350,4 @@ private static String zoneIdForOffset(long offsetMinutes) { return formatZoneOffset(offsetMinutes >= 0, toIntExact(abs(offsetMinutes / 60)), (int) abs(offsetMinutes % 60)); } - - private static void checkArgument(boolean check, String message, Object... args) - { - if (!check) { - throw new IllegalArgumentException(format(message, args)); - } - } } From 2e590ea8c5b2ab705f6247c5e4b4aea25f689e69 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sat, 18 Nov 2023 13:40:52 -0800 Subject: [PATCH 433/587] Disable Error Prone DoNotCallSuggester --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 474b89efb8e0..e135738bbdf0 100644 --- a/pom.xml +++ b/pom.xml @@ -2719,6 +2719,7 @@ -Xep:CompareToZero:ERROR \ -Xep:DefaultCharset:ERROR \ -Xep:DistinctVarargsChecker:ERROR \ + -Xep:DoNotCallSuggester:OFF \ -Xep:EmptyBlockTag:ERROR \ -Xep:EqualsGetClass:OFF \ From 123757c5b0a17ef91ca306dc018b2b77bd06058c Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sat, 18 Nov 2023 15:15:19 -0800 Subject: [PATCH 434/587] Remove unused parameters from QueryRewriteException --- .../src/main/java/io/trino/verifier/QueryRewriter.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/service/trino-verifier/src/main/java/io/trino/verifier/QueryRewriter.java b/service/trino-verifier/src/main/java/io/trino/verifier/QueryRewriter.java index a7b1c684194a..fef6c77f68e5 100644 --- a/service/trino-verifier/src/main/java/io/trino/verifier/QueryRewriter.java +++ b/service/trino-verifier/src/main/java/io/trino/verifier/QueryRewriter.java @@ -59,7 +59,6 @@ import static io.trino.sql.tree.SaveMode.IGNORE; import static io.trino.verifier.QueryType.READ; import static io.trino.verifier.VerifyCommand.statementToQueryType; -import static java.lang.String.format; import static java.util.Objects.requireNonNull; import static java.util.concurrent.Executors.newSingleThreadExecutor; @@ -270,9 +269,9 @@ private static String escapeLikeExpression(Connection connection, String value) public static class QueryRewriteException extends Exception { - public QueryRewriteException(String messageFormat, Object... args) + public QueryRewriteException(String message) { - super(format(messageFormat, args)); + super(message); } } From 4b48a594574fe7b47c5596b9697a71a41f59c172 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sat, 18 Nov 2023 15:55:25 -0800 Subject: [PATCH 435/587] Rename exception classes to end in Exception --- .../java/io/trino/jdbc/TrinoSqlWarning.java | 1 + .../main/java/io/trino/execution/Failure.java | 1 + .../io/trino/json/PathEvaluationContext.java | 4 +- ...rror.java => PathEvaluationException.java} | 10 +-- .../io/trino/json/PathEvaluationVisitor.java | 61 ++++++++++--------- .../json/PathPredicateEvaluationVisitor.java | 14 ++--- .../json/ir/SqlJsonLiteralConverter.java | 15 ++--- .../scalar/json/JsonExistsFunction.java | 8 +-- ...java => JsonInputConversionException.java} | 6 +- .../scalar/json/JsonInputFunctions.java | 2 +- ...ava => JsonOutputConversionException.java} | 6 +- .../scalar/json/JsonOutputFunctions.java | 2 +- .../scalar/json/JsonQueryFunction.java | 14 ++--- .../scalar/json/JsonValueFunction.java | 30 ++++----- .../main/java/io/trino/type/Json2016Type.java | 8 +-- .../io/trino/json/TestJsonPathEvaluator.java | 42 ++++++------- .../json/ir/TestSqlJsonLiteralConverter.java | 6 +- .../sql/query/TestJsonExistsFunction.java | 10 +-- .../sql/query/TestJsonQueryFunction.java | 16 ++--- .../sql/query/TestJsonValueFunction.java | 18 +++--- 20 files changed, 136 insertions(+), 138 deletions(-) rename core/trino-main/src/main/java/io/trino/json/{PathEvaluationError.java => PathEvaluationException.java} (84%) rename core/trino-main/src/main/java/io/trino/operator/scalar/json/{JsonInputConversionError.java => JsonInputConversionException.java} (85%) rename core/trino-main/src/main/java/io/trino/operator/scalar/json/{JsonOutputConversionError.java => JsonOutputConversionException.java} (85%) diff --git a/client/trino-jdbc/src/main/java/io/trino/jdbc/TrinoSqlWarning.java b/client/trino-jdbc/src/main/java/io/trino/jdbc/TrinoSqlWarning.java index d6170ba1cd94..bf68d6f0172a 100644 --- a/client/trino-jdbc/src/main/java/io/trino/jdbc/TrinoSqlWarning.java +++ b/client/trino-jdbc/src/main/java/io/trino/jdbc/TrinoSqlWarning.java @@ -17,6 +17,7 @@ import java.sql.SQLWarning; +@SuppressWarnings("ExceptionClassNameDoesntEndWithException") public class TrinoSqlWarning extends SQLWarning { diff --git a/core/trino-main/src/main/java/io/trino/execution/Failure.java b/core/trino-main/src/main/java/io/trino/execution/Failure.java index 34061b0d64af..7b773851df5f 100644 --- a/core/trino-main/src/main/java/io/trino/execution/Failure.java +++ b/core/trino-main/src/main/java/io/trino/execution/Failure.java @@ -18,6 +18,7 @@ import static java.util.Objects.requireNonNull; +@SuppressWarnings("ExceptionClassNameDoesntEndWithException") public class Failure extends RuntimeException { diff --git a/core/trino-main/src/main/java/io/trino/json/PathEvaluationContext.java b/core/trino-main/src/main/java/io/trino/json/PathEvaluationContext.java index d55e724a47e6..d080b797b9bd 100644 --- a/core/trino-main/src/main/java/io/trino/json/PathEvaluationContext.java +++ b/core/trino-main/src/main/java/io/trino/json/PathEvaluationContext.java @@ -53,7 +53,7 @@ public PathEvaluationContext withCurrentItem(Object currentItem) public TypedValue getLast() { if (last.getLongValue() < 0) { - throw new PathEvaluationError("accessing the last array index with no enclosing array"); + throw new PathEvaluationException("accessing the last array index with no enclosing array"); } return last; } @@ -61,7 +61,7 @@ public TypedValue getLast() public Object getCurrentItem() { if (currentItem == null) { - throw new PathEvaluationError("accessing current filter item with no enclosing filter"); + throw new PathEvaluationException("accessing current filter item with no enclosing filter"); } return currentItem; } diff --git a/core/trino-main/src/main/java/io/trino/json/PathEvaluationError.java b/core/trino-main/src/main/java/io/trino/json/PathEvaluationException.java similarity index 84% rename from core/trino-main/src/main/java/io/trino/json/PathEvaluationError.java rename to core/trino-main/src/main/java/io/trino/json/PathEvaluationException.java index 4cf246f43958..170908fe67a1 100644 --- a/core/trino-main/src/main/java/io/trino/json/PathEvaluationError.java +++ b/core/trino-main/src/main/java/io/trino/json/PathEvaluationException.java @@ -18,15 +18,15 @@ import static io.trino.spi.StandardErrorCode.PATH_EVALUATION_ERROR; import static java.lang.String.format; -public class PathEvaluationError +public class PathEvaluationException extends TrinoException { - public PathEvaluationError(String message) + public PathEvaluationException(String message) { super(PATH_EVALUATION_ERROR, "path evaluation failed: " + message); } - public PathEvaluationError(Throwable cause) + public PathEvaluationException(Throwable cause) { super(PATH_EVALUATION_ERROR, "path evaluation failed: ", cause); } @@ -46,11 +46,11 @@ public PathEvaluationError(Throwable cause) */ public static TrinoException structuralError(String format, Object... arguments) { - return new PathEvaluationError("structural error: " + format(format, arguments)); + return new PathEvaluationException("structural error: " + format(format, arguments)); } public static TrinoException itemTypeError(String expected, String actual) { - return new PathEvaluationError(format("invalid item type. Expected: %s, actual: %s", expected, actual)); + return new PathEvaluationException(format("invalid item type. Expected: %s, actual: %s", expected, actual)); } } diff --git a/core/trino-main/src/main/java/io/trino/json/PathEvaluationVisitor.java b/core/trino-main/src/main/java/io/trino/json/PathEvaluationVisitor.java index 84299d759ed8..83fe30693b60 100644 --- a/core/trino-main/src/main/java/io/trino/json/PathEvaluationVisitor.java +++ b/core/trino-main/src/main/java/io/trino/json/PathEvaluationVisitor.java @@ -52,6 +52,7 @@ import io.trino.json.ir.IrSizeMethod; import io.trino.json.ir.IrTypeMethod; import io.trino.json.ir.SqlJsonLiteralConverter; +import io.trino.json.ir.SqlJsonLiteralConverter.JsonLiteralConversionException; import io.trino.json.ir.TypedValue; import io.trino.spi.function.OperatorType; import io.trino.spi.type.CharType; @@ -83,8 +84,8 @@ import static io.airlift.slice.Slices.utf8Slice; import static io.trino.json.CachingResolver.ResolvedOperatorAndCoercions.RESOLUTION_ERROR; import static io.trino.json.JsonEmptySequenceNode.EMPTY_SEQUENCE; -import static io.trino.json.PathEvaluationError.itemTypeError; -import static io.trino.json.PathEvaluationError.structuralError; +import static io.trino.json.PathEvaluationException.itemTypeError; +import static io.trino.json.PathEvaluationException.structuralError; import static io.trino.json.PathEvaluationUtil.unwrapArrays; import static io.trino.json.ir.IrArithmeticUnary.Sign.PLUS; import static io.trino.json.ir.SqlJsonLiteralConverter.getTextTypedValue; @@ -182,7 +183,7 @@ private static TypedValue getAbsoluteValue(TypedValue typedValue) absValue = abs(value); } catch (Exception e) { - throw new PathEvaluationError(e); + throw new PathEvaluationException(e); } return new TypedValue(type, absValue); } @@ -196,7 +197,7 @@ private static TypedValue getAbsoluteValue(TypedValue typedValue) absValue = absInteger(value); } catch (Exception e) { - throw new PathEvaluationError(e); + throw new PathEvaluationException(e); } return new TypedValue(type, absValue); } @@ -210,7 +211,7 @@ private static TypedValue getAbsoluteValue(TypedValue typedValue) absValue = absSmallint(value); } catch (Exception e) { - throw new PathEvaluationError(e); + throw new PathEvaluationException(e); } return new TypedValue(type, absValue); } @@ -224,7 +225,7 @@ private static TypedValue getAbsoluteValue(TypedValue typedValue) absValue = absTinyint(value); } catch (Exception e) { - throw new PathEvaluationError(e); + throw new PathEvaluationException(e); } return new TypedValue(type, absValue); } @@ -257,7 +258,7 @@ private static TypedValue getAbsoluteValue(TypedValue typedValue) result = DecimalOperators.Negation.negate((Int128) typedValue.getObjectValue()); } catch (Exception e) { - throw new PathEvaluationError(e); + throw new PathEvaluationException(e); } return new TypedValue(type, result); } @@ -279,7 +280,7 @@ protected List visitIrArithmeticBinary(IrArithmeticBinary node, PathEval } if (leftSequence.size() != 1 || rightSequence.size() != 1) { - throw new PathEvaluationError("arithmetic binary expression requires singleton operands"); + throw new PathEvaluationException("arithmetic binary expression requires singleton operands"); } TypedValue left; @@ -304,7 +305,7 @@ protected List visitIrArithmeticBinary(IrArithmeticBinary node, PathEval ResolvedOperatorAndCoercions operators = resolver.getOperators(node, OperatorType.valueOf(node.operator().name()), left.getType(), right.getType()); if (operators == RESOLUTION_ERROR) { - throw new PathEvaluationError(format("invalid operand types to %s operator (%s, %s)", node.operator().name(), left.getType(), right.getType())); + throw new PathEvaluationException(format("invalid operand types to %s operator (%s, %s)", node.operator().name(), left.getType(), right.getType())); } Object leftInput = left.getValueAsObject(); @@ -313,7 +314,7 @@ protected List visitIrArithmeticBinary(IrArithmeticBinary node, PathEval leftInput = invoker.invoke(operators.getLeftCoercion().get(), ImmutableList.of(leftInput)); } catch (RuntimeException e) { - throw new PathEvaluationError(e); + throw new PathEvaluationException(e); } } @@ -323,7 +324,7 @@ protected List visitIrArithmeticBinary(IrArithmeticBinary node, PathEval rightInput = invoker.invoke(operators.getRightCoercion().get(), ImmutableList.of(rightInput)); } catch (RuntimeException e) { - throw new PathEvaluationError(e); + throw new PathEvaluationException(e); } } @@ -332,7 +333,7 @@ protected List visitIrArithmeticBinary(IrArithmeticBinary node, PathEval result = invoker.invoke(operators.getOperator(), ImmutableList.of(leftInput, rightInput)); } catch (RuntimeException e) { - throw new PathEvaluationError(e); + throw new PathEvaluationException(e); } return ImmutableList.of(TypedValue.fromValueAsObject(operators.getOperator().getSignature().getReturnType(), result)); @@ -382,7 +383,7 @@ private static TypedValue negate(TypedValue typedValue) negatedValue = BigintOperators.negate(typedValue.getLongValue()); } catch (Exception e) { - throw new PathEvaluationError(e); + throw new PathEvaluationException(e); } return new TypedValue(type, negatedValue); } @@ -392,7 +393,7 @@ private static TypedValue negate(TypedValue typedValue) negatedValue = IntegerOperators.negate(typedValue.getLongValue()); } catch (Exception e) { - throw new PathEvaluationError(e); + throw new PathEvaluationException(e); } return new TypedValue(type, negatedValue); } @@ -402,7 +403,7 @@ private static TypedValue negate(TypedValue typedValue) negatedValue = SmallintOperators.negate(typedValue.getLongValue()); } catch (Exception e) { - throw new PathEvaluationError(e); + throw new PathEvaluationException(e); } return new TypedValue(type, negatedValue); } @@ -412,7 +413,7 @@ private static TypedValue negate(TypedValue typedValue) negatedValue = TinyintOperators.negate(typedValue.getLongValue()); } catch (Exception e) { - throw new PathEvaluationError(e); + throw new PathEvaluationException(e); } return new TypedValue(type, negatedValue); } @@ -431,7 +432,7 @@ private static TypedValue negate(TypedValue typedValue) negatedValue = DecimalOperators.Negation.negate((Int128) typedValue.getObjectValue()); } catch (Exception e) { - throw new PathEvaluationError(e); + throw new PathEvaluationException(e); } return new TypedValue(type, negatedValue); } @@ -484,10 +485,10 @@ else if (lax) { List from = process(subscript.from(), arrayContext); Optional> to = subscript.to().map(path -> process(path, arrayContext)); if (from.size() != 1) { - throw new PathEvaluationError("array subscript 'from' value must be singleton numeric"); + throw new PathEvaluationException("array subscript 'from' value must be singleton numeric"); } if (to.isPresent() && to.get().size() != 1) { - throw new PathEvaluationError("array subscript 'to' value must be singleton numeric"); + throw new PathEvaluationException("array subscript 'to' value must be singleton numeric"); } long fromIndex = asArrayIndex(getOnlyElement(from)); long toIndex = to @@ -524,7 +525,7 @@ private static long asArrayIndex(Object object) throw itemTypeError("NUMBER", (jsonNode.getNodeType().name())); } if (!jsonNode.canConvertToLong()) { - throw new PathEvaluationError(format("cannot convert value %s to long", jsonNode)); + throw new PathEvaluationException(format("cannot convert value %s to long", jsonNode)); } return jsonNode.longValue(); } @@ -538,7 +539,7 @@ private static long asArrayIndex(Object object) return DoubleOperators.castToLong(value.getDoubleValue()); } catch (Exception e) { - throw new PathEvaluationError(e); + throw new PathEvaluationException(e); } } if (type.equals(REAL)) { @@ -546,7 +547,7 @@ private static long asArrayIndex(Object object) return RealOperators.castToLong(value.getLongValue()); } catch (Exception e) { - throw new PathEvaluationError(e); + throw new PathEvaluationException(e); } } if (type instanceof DecimalType decimalType) { @@ -561,7 +562,7 @@ private static long asArrayIndex(Object object) return DecimalCasts.longDecimalToBigint((Int128) value.getObjectValue(), precision, scale, tenToScale); } catch (Exception e) { - throw new PathEvaluationError(e); + throw new PathEvaluationException(e); } } @@ -617,14 +618,14 @@ private static TypedValue getCeiling(TypedValue typedValue) return new TypedValue(resultType, ceilingLongShort(scale, (Int128) typedValue.getObjectValue())); } catch (Exception e) { - throw new PathEvaluationError(e); + throw new PathEvaluationException(e); } } try { return new TypedValue(resultType, ceilingLong(scale, (Int128) typedValue.getObjectValue())); } catch (Exception e) { - throw new PathEvaluationError(e); + throw new PathEvaluationException(e); } } @@ -734,7 +735,7 @@ private static TypedValue getDouble(TypedValue typedValue) return new TypedValue(DOUBLE, VarcharOperators.castToDouble((Slice) typedValue.getObjectValue())); } catch (Exception e) { - throw new PathEvaluationError(e); + throw new PathEvaluationException(e); } } @@ -811,14 +812,14 @@ private static TypedValue getFloor(TypedValue typedValue) return new TypedValue(resultType, floorLongShort(scale, (Int128) typedValue.getObjectValue())); } catch (Exception e) { - throw new PathEvaluationError(e); + throw new PathEvaluationException(e); } } try { return new TypedValue(resultType, floorLong(scale, (Int128) typedValue.getObjectValue())); } catch (Exception e) { - throw new PathEvaluationError(e); + throw new PathEvaluationException(e); } } @@ -1055,8 +1056,8 @@ private static Optional getNumericTypedValue(JsonNode jsonNode) try { return SqlJsonLiteralConverter.getNumericTypedValue(jsonNode); } - catch (SqlJsonLiteralConverter.JsonLiteralConversionError e) { - throw new PathEvaluationError(e); + catch (JsonLiteralConversionException e) { + throw new PathEvaluationException(e); } } } diff --git a/core/trino-main/src/main/java/io/trino/json/PathPredicateEvaluationVisitor.java b/core/trino-main/src/main/java/io/trino/json/PathPredicateEvaluationVisitor.java index b5d043be36ab..db178616a516 100644 --- a/core/trino-main/src/main/java/io/trino/json/PathPredicateEvaluationVisitor.java +++ b/core/trino-main/src/main/java/io/trino/json/PathPredicateEvaluationVisitor.java @@ -29,7 +29,7 @@ import io.trino.json.ir.IrPathNode; import io.trino.json.ir.IrPredicate; import io.trino.json.ir.IrStartsWithPredicate; -import io.trino.json.ir.SqlJsonLiteralConverter; +import io.trino.json.ir.SqlJsonLiteralConverter.JsonLiteralConversionException; import io.trino.json.ir.TypedValue; import io.trino.operator.scalar.StringFunctions; import io.trino.spi.function.OperatorType; @@ -137,7 +137,7 @@ protected Boolean visitIrComparisonPredicate(IrComparisonPredicate node, PathEva try { leftSequence = pathVisitor.process(node.left(), context); } - catch (PathEvaluationError e) { + catch (PathEvaluationException e) { return null; } @@ -145,7 +145,7 @@ protected Boolean visitIrComparisonPredicate(IrComparisonPredicate node, PathEva try { rightSequence = pathVisitor.process(node.right(), context); } - catch (PathEvaluationError e) { + catch (PathEvaluationException e) { return null; } @@ -366,7 +366,7 @@ protected Boolean visitIrExistsPredicate(IrExistsPredicate node, PathEvaluationC try { sequence = pathVisitor.process(node.path(), context); } - catch (PathEvaluationError e) { + catch (PathEvaluationException e) { return null; } @@ -396,7 +396,7 @@ protected Boolean visitIrStartsWithPredicate(IrStartsWithPredicate node, PathEva try { valueSequence = pathVisitor.process(node.value(), context); } - catch (PathEvaluationError e) { + catch (PathEvaluationException e) { return null; } @@ -404,7 +404,7 @@ protected Boolean visitIrStartsWithPredicate(IrStartsWithPredicate node, PathEva try { prefixSequence = pathVisitor.process(node.prefix(), context); } - catch (PathEvaluationError e) { + catch (PathEvaluationException e) { return null; } if (prefixSequence.size() != 1) { @@ -453,7 +453,7 @@ private static List getScalars(List sequence) try { typedValue = getTypedValue(jsonNode); } - catch (SqlJsonLiteralConverter.JsonLiteralConversionError e) { + catch (JsonLiteralConversionException e) { return null; } if (typedValue.isEmpty()) { diff --git a/core/trino-main/src/main/java/io/trino/json/ir/SqlJsonLiteralConverter.java b/core/trino-main/src/main/java/io/trino/json/ir/SqlJsonLiteralConverter.java index f74331d65069..fce2a137f994 100644 --- a/core/trino-main/src/main/java/io/trino/json/ir/SqlJsonLiteralConverter.java +++ b/core/trino-main/src/main/java/io/trino/json/ir/SqlJsonLiteralConverter.java @@ -90,13 +90,13 @@ public static Optional getNumericTypedValue(JsonNode jsonNode) if (jsonNode.canConvertToLong()) { return Optional.of(new TypedValue(BIGINT, jsonNode.longValue())); } - throw conversionError(jsonNode, "value too big"); + throw new JsonLiteralConversionException(jsonNode, "value too big"); } if (jsonNode instanceof DecimalNode) { BigDecimal jsonDecimal = jsonNode.decimalValue(); int precision = jsonDecimal.precision(); if (precision > MAX_PRECISION) { - throw conversionError(jsonNode, "precision too big"); + throw new JsonLiteralConversionException(jsonNode, "precision too big"); } int scale = jsonDecimal.scale(); DecimalType decimalType = createDecimalType(precision, scale); @@ -167,17 +167,12 @@ public static Optional getJsonNode(TypedValue typedValue) return Optional.empty(); } - public static TrinoException conversionError(JsonNode jsonNode, String cause) - { - return new JsonLiteralConversionError(jsonNode, cause); - } - - public static class JsonLiteralConversionError + public static class JsonLiteralConversionException extends TrinoException { - public JsonLiteralConversionError(JsonNode jsonNode, String cause) + public JsonLiteralConversionException(JsonNode jsonNode, String message) { - super(INVALID_JSON_LITERAL, format("cannot convert %s to Trino value (%s)", jsonNode, cause)); + super(INVALID_JSON_LITERAL, format("cannot convert %s to Trino value (%s)", jsonNode, message)); } } } diff --git a/core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonExistsFunction.java b/core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonExistsFunction.java index 341ff0557f05..581597f0cd03 100644 --- a/core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonExistsFunction.java +++ b/core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonExistsFunction.java @@ -18,7 +18,7 @@ import io.trino.annotation.UsedByGeneratedCode; import io.trino.json.JsonPathEvaluator; import io.trino.json.JsonPathInvocationContext; -import io.trino.json.PathEvaluationError; +import io.trino.json.PathEvaluationException; import io.trino.json.ir.IrJsonPath; import io.trino.metadata.FunctionManager; import io.trino.metadata.Metadata; @@ -115,12 +115,12 @@ public static Boolean jsonExists( long errorBehavior) { if (inputExpression.equals(JSON_ERROR)) { - return handleError(errorBehavior, () -> new JsonInputConversionError("malformed input argument to JSON_EXISTS function")); // ERROR ON ERROR was already handled by the input function + return handleError(errorBehavior, () -> new JsonInputConversionException("malformed input argument to JSON_EXISTS function")); // ERROR ON ERROR was already handled by the input function } Object[] parameters = getParametersArray(parametersRowType, parametersRow); for (Object parameter : parameters) { if (parameter.equals(JSON_ERROR)) { - return handleError(errorBehavior, () -> new JsonInputConversionError("malformed JSON path parameter to JSON_EXISTS function")); // ERROR ON ERROR was already handled by the input function + return handleError(errorBehavior, () -> new JsonInputConversionException("malformed JSON path parameter to JSON_EXISTS function")); // ERROR ON ERROR was already handled by the input function } } // The jsonPath argument is constant for every row. We use the first incoming jsonPath argument to initialize @@ -135,7 +135,7 @@ public static Boolean jsonExists( try { pathResult = evaluator.evaluate(inputExpression, parameters); } - catch (PathEvaluationError e) { + catch (PathEvaluationException e) { return handleError(errorBehavior, () -> e); } diff --git a/core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonInputConversionError.java b/core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonInputConversionException.java similarity index 85% rename from core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonInputConversionError.java rename to core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonInputConversionException.java index c230d4eb58da..106094d1f7b4 100644 --- a/core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonInputConversionError.java +++ b/core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonInputConversionException.java @@ -17,15 +17,15 @@ import static io.trino.spi.StandardErrorCode.JSON_INPUT_CONVERSION_ERROR; -public class JsonInputConversionError +public class JsonInputConversionException extends TrinoException { - public JsonInputConversionError(String message) + public JsonInputConversionException(String message) { super(JSON_INPUT_CONVERSION_ERROR, "conversion to JSON failed: " + message); } - public JsonInputConversionError(Throwable cause) + public JsonInputConversionException(Throwable cause) { super(JSON_INPUT_CONVERSION_ERROR, "conversion to JSON failed: ", cause); } diff --git a/core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonInputFunctions.java b/core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonInputFunctions.java index 1d838044a1df..c2e31998721c 100644 --- a/core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonInputFunctions.java +++ b/core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonInputFunctions.java @@ -107,7 +107,7 @@ private static JsonNode toJson(Reader reader, boolean failOnError) } catch (JsonProcessingException e) { if (failOnError) { - throw new JsonInputConversionError(e); + throw new JsonInputConversionException(e); } return JSON_ERROR; } diff --git a/core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonOutputConversionError.java b/core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonOutputConversionException.java similarity index 85% rename from core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonOutputConversionError.java rename to core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonOutputConversionException.java index 3cec7b802337..ad64788bc672 100644 --- a/core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonOutputConversionError.java +++ b/core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonOutputConversionException.java @@ -17,15 +17,15 @@ import static io.trino.spi.StandardErrorCode.JSON_OUTPUT_CONVERSION_ERROR; -public class JsonOutputConversionError +public class JsonOutputConversionException extends TrinoException { - public JsonOutputConversionError(String message) + public JsonOutputConversionException(String message) { super(JSON_OUTPUT_CONVERSION_ERROR, "conversion from JSON failed: " + message); } - public JsonOutputConversionError(Throwable cause) + public JsonOutputConversionException(Throwable cause) { super(JSON_OUTPUT_CONVERSION_ERROR, "conversion from JSON failed: ", cause); } diff --git a/core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonOutputFunctions.java b/core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonOutputFunctions.java index 3ba498f8abdd..a50326ad3f59 100644 --- a/core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonOutputFunctions.java +++ b/core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonOutputFunctions.java @@ -134,7 +134,7 @@ private static Slice serialize(JsonNode json, EncodingSpecificConstants constant return null; } if (errorBehavior == ERROR.ordinal()) { - throw new JsonOutputConversionError(e); + throw new JsonOutputConversionException(e); } if (errorBehavior == EMPTY_ARRAY.ordinal()) { return constants.emptyArray; diff --git a/core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonQueryFunction.java b/core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonQueryFunction.java index b0329137ff25..e47d602b975b 100644 --- a/core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonQueryFunction.java +++ b/core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonQueryFunction.java @@ -21,7 +21,7 @@ import io.trino.annotation.UsedByGeneratedCode; import io.trino.json.JsonPathEvaluator; import io.trino.json.JsonPathInvocationContext; -import io.trino.json.PathEvaluationError; +import io.trino.json.PathEvaluationException; import io.trino.json.ir.IrJsonPath; import io.trino.json.ir.TypedValue; import io.trino.metadata.FunctionManager; @@ -131,12 +131,12 @@ public static JsonNode jsonQuery( long errorBehavior) { if (inputExpression.equals(JSON_ERROR)) { - return handleSpecialCase(errorBehavior, () -> new JsonInputConversionError("malformed input argument to JSON_QUERY function")); // ERROR ON ERROR was already handled by the input function + return handleSpecialCase(errorBehavior, () -> new JsonInputConversionException("malformed input argument to JSON_QUERY function")); // ERROR ON ERROR was already handled by the input function } Object[] parameters = getParametersArray(parametersRowType, parametersRow); for (Object parameter : parameters) { if (parameter.equals(JSON_ERROR)) { - return handleSpecialCase(errorBehavior, () -> new JsonInputConversionError("malformed JSON path parameter to JSON_QUERY function")); // ERROR ON ERROR was already handled by the input function + return handleSpecialCase(errorBehavior, () -> new JsonInputConversionException("malformed JSON path parameter to JSON_QUERY function")); // ERROR ON ERROR was already handled by the input function } } // The jsonPath argument is constant for every row. We use the first incoming jsonPath argument to initialize @@ -151,13 +151,13 @@ public static JsonNode jsonQuery( try { pathResult = evaluator.evaluate(inputExpression, parameters); } - catch (PathEvaluationError e) { + catch (PathEvaluationException e) { return handleSpecialCase(errorBehavior, () -> e); } // handle empty sequence if (pathResult.isEmpty()) { - return handleSpecialCase(emptyBehavior, () -> new JsonOutputConversionError("JSON path found no items")); + return handleSpecialCase(emptyBehavior, () -> new JsonOutputConversionException("JSON path found no items")); } // translate sequence to JSON items @@ -166,7 +166,7 @@ public static JsonNode jsonQuery( if (item instanceof TypedValue) { Optional jsonNode = getJsonNode((TypedValue) item); if (jsonNode.isEmpty()) { - return handleSpecialCase(errorBehavior, () -> new JsonOutputConversionError(format( + return handleSpecialCase(errorBehavior, () -> new JsonOutputConversionException(format( "JSON path returned a scalar SQL value of type %s that cannot be represented as JSON", ((TypedValue) item).getType()))); } @@ -201,7 +201,7 @@ public static JsonNode jsonQuery( // if the only item is a TextNode, need to apply the KEEP / OMIT QUOTES behavior. this is done by the JSON output function } - return handleSpecialCase(errorBehavior, () -> new JsonOutputConversionError("JSON path found multiple items")); + return handleSpecialCase(errorBehavior, () -> new JsonOutputConversionException("JSON path found multiple items")); } private static JsonNode handleSpecialCase(long behavior, Supplier error) diff --git a/core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonValueFunction.java b/core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonValueFunction.java index ca2985ff1b85..82c7a787d7bf 100644 --- a/core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonValueFunction.java +++ b/core/trino-main/src/main/java/io/trino/operator/scalar/json/JsonValueFunction.java @@ -20,9 +20,9 @@ import io.trino.annotation.UsedByGeneratedCode; import io.trino.json.JsonPathEvaluator; import io.trino.json.JsonPathInvocationContext; -import io.trino.json.PathEvaluationError; +import io.trino.json.PathEvaluationException; import io.trino.json.ir.IrJsonPath; -import io.trino.json.ir.SqlJsonLiteralConverter.JsonLiteralConversionError; +import io.trino.json.ir.SqlJsonLiteralConverter.JsonLiteralConversionException; import io.trino.json.ir.TypedValue; import io.trino.metadata.FunctionManager; import io.trino.metadata.Metadata; @@ -240,12 +240,12 @@ public static Object jsonValue( Object errorDefault) { if (inputExpression.equals(JSON_ERROR)) { - return handleSpecialCase(errorBehavior, errorDefault, () -> new JsonInputConversionError("malformed input argument to JSON_VALUE function")); // ERROR ON ERROR was already handled by the input function + return handleSpecialCase(errorBehavior, errorDefault, () -> new JsonInputConversionException("malformed input argument to JSON_VALUE function")); // ERROR ON ERROR was already handled by the input function } Object[] parameters = getParametersArray(parametersRowType, parametersRow); for (Object parameter : parameters) { if (parameter.equals(JSON_ERROR)) { - return handleSpecialCase(errorBehavior, errorDefault, () -> new JsonInputConversionError("malformed JSON path parameter to JSON_VALUE function")); // ERROR ON ERROR was already handled by the input function + return handleSpecialCase(errorBehavior, errorDefault, () -> new JsonInputConversionException("malformed JSON path parameter to JSON_VALUE function")); // ERROR ON ERROR was already handled by the input function } } // The jsonPath argument is constant for every row. We use the first incoming jsonPath argument to initialize @@ -260,16 +260,16 @@ public static Object jsonValue( try { pathResult = evaluator.evaluate(inputExpression, parameters); } - catch (PathEvaluationError e) { + catch (PathEvaluationException e) { return handleSpecialCase(errorBehavior, errorDefault, () -> e); // TODO by spec, we should cast the defaults only if they are used } if (pathResult.isEmpty()) { - return handleSpecialCase(emptyBehavior, emptyDefault, () -> new JsonValueResultError("JSON path found no items")); + return handleSpecialCase(emptyBehavior, emptyDefault, () -> new JsonValueResultException("JSON path found no items")); } if (pathResult.size() > 1) { - return handleSpecialCase(errorBehavior, errorDefault, () -> new JsonValueResultError("JSON path found multiple items")); + return handleSpecialCase(errorBehavior, errorDefault, () -> new JsonValueResultException("JSON path found multiple items")); } Object item = getOnlyElement(pathResult); @@ -282,11 +282,11 @@ public static Object jsonValue( try { itemValue = getTypedValue((JsonNode) item); } - catch (JsonLiteralConversionError e) { - return handleSpecialCase(errorBehavior, errorDefault, () -> new JsonValueResultError("JSON path found an item that cannot be converted to an SQL value", e)); + catch (JsonLiteralConversionException e) { + return handleSpecialCase(errorBehavior, errorDefault, () -> new JsonValueResultException("JSON path found an item that cannot be converted to an SQL value", e)); } if (itemValue.isEmpty()) { - return handleSpecialCase(errorBehavior, errorDefault, () -> new JsonValueResultError("JSON path found an item that cannot be converted to an SQL value")); + return handleSpecialCase(errorBehavior, errorDefault, () -> new JsonValueResultException("JSON path found an item that cannot be converted to an SQL value")); } typedValue = itemValue.get(); } @@ -301,7 +301,7 @@ public static Object jsonValue( coercion = metadata.getCoercion(typedValue.getType(), returnType); } catch (OperatorNotFoundException e) { - return handleSpecialCase(errorBehavior, errorDefault, () -> new JsonValueResultError(format( + return handleSpecialCase(errorBehavior, errorDefault, () -> new JsonValueResultException(format( "Cannot cast value of type %s to declared return type of function JSON_VALUE: %s", typedValue.getType(), returnType))); @@ -310,7 +310,7 @@ public static Object jsonValue( return new InterpretedFunctionInvoker(functionManager).invoke(coercion, session, ImmutableList.of(typedValue.getValueAsObject())); } catch (RuntimeException e) { - return handleSpecialCase(errorBehavior, errorDefault, () -> new JsonValueResultError(format( + return handleSpecialCase(errorBehavior, errorDefault, () -> new JsonValueResultException(format( "Cannot cast value of type %s to declared return type of function JSON_VALUE: %s", typedValue.getType(), returnType))); @@ -330,15 +330,15 @@ private static Object handleSpecialCase(long behavior, Object defaultValue, Supp throw new IllegalStateException("unexpected behavior"); } - public static class JsonValueResultError + public static class JsonValueResultException extends TrinoException { - public JsonValueResultError(String message) + public JsonValueResultException(String message) { super(JSON_VALUE_RESULT_ERROR, "cannot extract SQL scalar from JSON: " + message); } - public JsonValueResultError(String message, Throwable cause) + public JsonValueResultException(String message, Throwable cause) { super(JSON_VALUE_RESULT_ERROR, "cannot extract SQL scalar from JSON: " + message, cause); } diff --git a/core/trino-main/src/main/java/io/trino/type/Json2016Type.java b/core/trino-main/src/main/java/io/trino/type/Json2016Type.java index f028551ddc32..ba8819f5c8cf 100644 --- a/core/trino-main/src/main/java/io/trino/type/Json2016Type.java +++ b/core/trino-main/src/main/java/io/trino/type/Json2016Type.java @@ -17,8 +17,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import io.airlift.slice.Slice; -import io.trino.operator.scalar.json.JsonInputConversionError; -import io.trino.operator.scalar.json.JsonOutputConversionError; +import io.trino.operator.scalar.json.JsonInputConversionException; +import io.trino.operator.scalar.json.JsonOutputConversionException; import io.trino.spi.block.Block; import io.trino.spi.block.BlockBuilder; import io.trino.spi.block.VariableWidthBlock; @@ -65,7 +65,7 @@ public Object getObject(Block block, int position) return MAPPER.readTree(json); } catch (JsonProcessingException e) { - throw new JsonInputConversionError(e); + throw new JsonInputConversionException(e); } } @@ -81,7 +81,7 @@ public void writeObject(BlockBuilder blockBuilder, Object value) json = MAPPER.writeValueAsString(value); } catch (JsonProcessingException e) { - throw new JsonOutputConversionError(e); + throw new JsonOutputConversionException(e); } } Slice bytes = utf8Slice(json); diff --git a/core/trino-main/src/test/java/io/trino/json/TestJsonPathEvaluator.java b/core/trino-main/src/test/java/io/trino/json/TestJsonPathEvaluator.java index d465ea1ed9cf..14704b91a339 100644 --- a/core/trino-main/src/test/java/io/trino/json/TestJsonPathEvaluator.java +++ b/core/trino-main/src/test/java/io/trino/json/TestJsonPathEvaluator.java @@ -253,13 +253,13 @@ public void testAbsMethod() assertThatThrownBy(() -> evaluate( IntNode.valueOf(-5), path(true, abs(literal(TINYINT, -128L))))) - .isInstanceOf(PathEvaluationError.class); + .isInstanceOf(PathEvaluationException.class); // type mismatch assertThatThrownBy(() -> evaluate( IntNode.valueOf(-5), path(true, abs(jsonVariable("null_parameter"))))) - .isInstanceOf(PathEvaluationError.class) + .isInstanceOf(PathEvaluationException.class) .hasMessage("path evaluation failed: invalid item type. Expected: NUMBER, actual: NULL"); } @@ -290,20 +290,20 @@ public void testArithmeticBinary() assertThatThrownBy(() -> evaluate( IntNode.valueOf(-5), path(true, divide(jsonVariable("json_number_parameter"), literal(BIGINT, 0L))))) - .isInstanceOf(PathEvaluationError.class); + .isInstanceOf(PathEvaluationException.class); // type mismatch assertThatThrownBy(() -> evaluate( IntNode.valueOf(-5), path(true, modulus(jsonVariable("json_number_parameter"), literal(BOOLEAN, true))))) - .isInstanceOf(PathEvaluationError.class) + .isInstanceOf(PathEvaluationException.class) .hasMessage("path evaluation failed: invalid operand types to MODULUS operator (integer, boolean)"); // left operand is not singleton assertThatThrownBy(() -> evaluate( IntNode.valueOf(-5), path(true, add(wildcardArrayAccessor(jsonVariable("json_array_parameter")), literal(BIGINT, 0L))))) - .isInstanceOf(PathEvaluationError.class) + .isInstanceOf(PathEvaluationException.class) .hasMessage("path evaluation failed: arithmetic binary expression requires singleton operands"); // array is automatically unwrapped in lax mode @@ -347,13 +347,13 @@ public void testArithmeticUnary() assertThatThrownBy(() -> evaluate( IntNode.valueOf(-5), path(true, minus(literal(TINYINT, -128L))))) - .isInstanceOf(PathEvaluationError.class); + .isInstanceOf(PathEvaluationException.class); // type mismatch assertThatThrownBy(() -> evaluate( IntNode.valueOf(-5), path(true, plus(jsonVariable("null_parameter"))))) - .isInstanceOf(PathEvaluationError.class) + .isInstanceOf(PathEvaluationException.class) .hasMessage("path evaluation failed: invalid item type. Expected: NUMBER, actual: NULL"); } @@ -413,7 +413,7 @@ public void testArrayAccessor() assertThatThrownBy(() -> evaluate( IntNode.valueOf(-5), path(true, last()))) - .isInstanceOf(PathEvaluationError.class) + .isInstanceOf(PathEvaluationException.class) .hasMessage("path evaluation failed: accessing the last array index with no enclosing array"); // last variable in nested arrays @@ -449,20 +449,20 @@ public void testArrayAccessor() assertThatThrownBy(() -> evaluate( new ArrayNode(JsonNodeFactory.instance, ImmutableList.of(TextNode.valueOf("first"), TextNode.valueOf("second"), TextNode.valueOf("third"), TextNode.valueOf("fourth"), TextNode.valueOf("fifth"))), path(false, arrayAccessor(contextVariable(), at(literal(INTEGER, 100L)))))) - .isInstanceOf(PathEvaluationError.class) + .isInstanceOf(PathEvaluationException.class) .hasMessage("path evaluation failed: structural error: invalid array subscript: [100, 100] for array of size 5"); assertThatThrownBy(() -> evaluate( new ArrayNode(JsonNodeFactory.instance, ImmutableList.of(TextNode.valueOf("first"), TextNode.valueOf("second"), TextNode.valueOf("third"), TextNode.valueOf("fourth"), TextNode.valueOf("fifth"))), path(false, arrayAccessor(contextVariable(), range(literal(INTEGER, 3L), literal(INTEGER, 100L)))))) - .isInstanceOf(PathEvaluationError.class) + .isInstanceOf(PathEvaluationException.class) .hasMessage("path evaluation failed: structural error: invalid array subscript: [3, 100] for array of size 5"); // incorrect subscript: from > to (strict mode) assertThatThrownBy(() -> evaluate( new ArrayNode(JsonNodeFactory.instance, ImmutableList.of(TextNode.valueOf("first"), TextNode.valueOf("second"), TextNode.valueOf("third"), TextNode.valueOf("fourth"), TextNode.valueOf("fifth"))), path(false, arrayAccessor(contextVariable(), range(literal(INTEGER, 3L), literal(INTEGER, 2L)))))) - .isInstanceOf(PathEvaluationError.class) + .isInstanceOf(PathEvaluationException.class) .hasMessage("path evaluation failed: structural error: invalid array subscript: [3, 2] for array of size 5"); // type mismatch (lax mode) -> the value is wrapped in a singleton array @@ -475,7 +475,7 @@ public void testArrayAccessor() assertThatThrownBy(() -> evaluate( IntNode.valueOf(-5), path(false, arrayAccessor(contextVariable(), at(literal(INTEGER, 0L)))))) - .isInstanceOf(PathEvaluationError.class) + .isInstanceOf(PathEvaluationException.class) .hasMessage("path evaluation failed: invalid item type. Expected: ARRAY, actual: NUMBER"); } @@ -513,7 +513,7 @@ public void testCeilingMethod() assertThatThrownBy(() -> evaluate( IntNode.valueOf(-5), path(true, ceiling(jsonVariable("null_parameter"))))) - .isInstanceOf(PathEvaluationError.class) + .isInstanceOf(PathEvaluationException.class) .hasMessage("path evaluation failed: invalid item type. Expected: NUMBER, actual: NULL"); } @@ -632,7 +632,7 @@ public void testDoubleMethod() assertThatThrownBy(() -> evaluate( IntNode.valueOf(-5), path(true, toDouble(jsonVariable("null_parameter"))))) - .isInstanceOf(PathEvaluationError.class) + .isInstanceOf(PathEvaluationException.class) .hasMessage("path evaluation failed: invalid item type. Expected: NUMBER or TEXT, actual: NULL"); } @@ -670,7 +670,7 @@ public void testFloorMethod() assertThatThrownBy(() -> evaluate( IntNode.valueOf(-5), path(true, floor(jsonVariable("null_parameter"))))) - .isInstanceOf(PathEvaluationError.class) + .isInstanceOf(PathEvaluationException.class) .hasMessage("path evaluation failed: invalid item type. Expected: NUMBER, actual: NULL"); } @@ -808,7 +808,7 @@ public void testKeyValueMethod() assertThatThrownBy(() -> evaluate( IntNode.valueOf(-5), path(true, keyValue(jsonVariable("null_parameter"))))) - .isInstanceOf(PathEvaluationError.class) + .isInstanceOf(PathEvaluationException.class) .hasMessage("path evaluation failed: invalid item type. Expected: OBJECT, actual: NULL"); } @@ -856,7 +856,7 @@ public void testMemberAccessor() assertThatThrownBy(() -> evaluate( new ObjectNode(JsonNodeFactory.instance, ImmutableMap.of("key1", TextNode.valueOf("bound_value"), "key2", NullNode.instance)), path(false, memberAccessor(contextVariable(), "wrong_key")))) - .isInstanceOf(PathEvaluationError.class) + .isInstanceOf(PathEvaluationException.class) .hasMessage("path evaluation failed: structural error: missing member 'wrong_key' in JSON object"); // multiple input objects, key not found in one of them -- lax mode @@ -877,14 +877,14 @@ public void testMemberAccessor() new ObjectNode(JsonNodeFactory.instance, ImmutableMap.of("key1", TextNode.valueOf("first"), "key2", BooleanNode.TRUE)), new ObjectNode(JsonNodeFactory.instance, ImmutableMap.of("key3", IntNode.valueOf(1), "key4", NullNode.instance)))), path(false, memberAccessor(wildcardArrayAccessor(contextVariable()), "key2")))) - .isInstanceOf(PathEvaluationError.class) + .isInstanceOf(PathEvaluationException.class) .hasMessage("path evaluation failed: structural error: missing member 'key2' in JSON object"); // type mismatch assertThatThrownBy(() -> evaluate( IntNode.valueOf(-5), path(true, keyValue(jsonVariable("null_parameter"))))) - .isInstanceOf(PathEvaluationError.class) + .isInstanceOf(PathEvaluationException.class) .hasMessage("path evaluation failed: invalid item type. Expected: OBJECT, actual: NULL"); } @@ -931,7 +931,7 @@ public void testSizeMethod() assertThatThrownBy(() -> evaluate( IntNode.valueOf(-5), path(false, size(contextVariable())))) - .isInstanceOf(PathEvaluationError.class) + .isInstanceOf(PathEvaluationException.class) .hasMessage("path evaluation failed: invalid item type. Expected: ARRAY, actual: NUMBER"); } @@ -1437,7 +1437,7 @@ public void testCurrentItemVariable() assertThatThrownBy(() -> evaluate( IntNode.valueOf(-5), path(true, currentItem()))) - .isInstanceOf(PathEvaluationError.class) + .isInstanceOf(PathEvaluationException.class) .hasMessage("path evaluation failed: accessing current filter item with no enclosing filter"); } diff --git a/core/trino-main/src/test/java/io/trino/json/ir/TestSqlJsonLiteralConverter.java b/core/trino-main/src/test/java/io/trino/json/ir/TestSqlJsonLiteralConverter.java index 3d350d7fa3fa..9cd3a827071b 100644 --- a/core/trino-main/src/test/java/io/trino/json/ir/TestSqlJsonLiteralConverter.java +++ b/core/trino-main/src/test/java/io/trino/json/ir/TestSqlJsonLiteralConverter.java @@ -29,7 +29,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ShortNode; import com.fasterxml.jackson.databind.node.TextNode; -import io.trino.json.ir.SqlJsonLiteralConverter.JsonLiteralConversionError; +import io.trino.json.ir.SqlJsonLiteralConverter.JsonLiteralConversionException; import io.trino.spi.type.Int128; import org.assertj.core.api.AssertProvider; import org.assertj.core.api.RecursiveComparisonAssert; @@ -154,7 +154,7 @@ public void testJsonToNumber() .isEqualTo(new TypedValue(BIGINT, 1000000000000000000L)); assertThatThrownBy(() -> getTypedValue(BigIntegerNode.valueOf(bigValue))) - .isInstanceOf(JsonLiteralConversionError.class) + .isInstanceOf(JsonLiteralConversionException.class) .hasMessage("cannot convert 1000000000000000000000000000000000000 to Trino value (value too big)"); assertThat(typedValueResult(DecimalNode.valueOf(BigDecimal.ONE))) @@ -164,7 +164,7 @@ public void testJsonToNumber() .isEqualTo(new TypedValue(createDecimalType(37, 20), Int128.valueOf(bigValue))); assertThatThrownBy(() -> getTypedValue(BigIntegerNode.valueOf(bigValue.multiply(bigValue)))) - .isInstanceOf(JsonLiteralConversionError.class) + .isInstanceOf(JsonLiteralConversionException.class) .hasMessage("cannot convert 1000000000000000000000000000000000000000000000000000000000000000000000000 to Trino value (value too big)"); assertThat(typedValueResult(DoubleNode.valueOf(1e0))) diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestJsonExistsFunction.java b/core/trino-main/src/test/java/io/trino/sql/query/TestJsonExistsFunction.java index bc51646b523c..12c1f159219d 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestJsonExistsFunction.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestJsonExistsFunction.java @@ -13,8 +13,8 @@ */ package io.trino.sql.query; -import io.trino.json.PathEvaluationError; -import io.trino.operator.scalar.json.JsonInputConversionError; +import io.trino.json.PathEvaluationException; +import io.trino.operator.scalar.json.JsonInputConversionException; import io.trino.sql.parser.ParsingException; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; @@ -82,7 +82,7 @@ public void testJsonExists() assertThatThrownBy(() -> assertions.query( "SELECT json_exists('" + INPUT + "', 'strict $[100]' ERROR ON ERROR)")) - .isInstanceOf(PathEvaluationError.class) + .isInstanceOf(PathEvaluationException.class) .hasMessage("path evaluation failed: structural error: invalid array subscript: [100, 100] for array of size 3"); } @@ -165,7 +165,7 @@ public void testInputConversionError() assertThatThrownBy(() -> assertions.query( "SELECT json_exists('" + INCORRECT_INPUT + "', 'strict $[1]' ERROR ON ERROR)")) - .isInstanceOf(JsonInputConversionError.class) + .isInstanceOf(JsonInputConversionException.class) .hasMessage("conversion to JSON failed: "); } @@ -193,7 +193,7 @@ public void testPassingClause() assertThatThrownBy(() -> assertions.query( "SELECT json_exists('" + INPUT + "', 'lax $array[0]' PASSING '[...' FORMAT JSON AS \"array\" ERROR ON ERROR)")) - .isInstanceOf(JsonInputConversionError.class) + .isInstanceOf(JsonInputConversionException.class) .hasMessage("conversion to JSON failed: "); // array index out of bounds diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestJsonQueryFunction.java b/core/trino-main/src/test/java/io/trino/sql/query/TestJsonQueryFunction.java index 9a575945de25..06e2cef464ae 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestJsonQueryFunction.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestJsonQueryFunction.java @@ -13,9 +13,9 @@ */ package io.trino.sql.query; -import io.trino.json.PathEvaluationError; -import io.trino.operator.scalar.json.JsonInputConversionError; -import io.trino.operator.scalar.json.JsonOutputConversionError; +import io.trino.json.PathEvaluationException; +import io.trino.operator.scalar.json.JsonInputConversionException; +import io.trino.operator.scalar.json.JsonOutputConversionException; import io.trino.sql.parser.ParsingException; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; @@ -79,7 +79,7 @@ public void testJsonQuery() assertThatThrownBy(() -> assertions.query( "SELECT json_query('" + INPUT + "', 'strict $[100]' ERROR ON ERROR)")) - .isInstanceOf(PathEvaluationError.class) + .isInstanceOf(PathEvaluationException.class) .hasMessage("path evaluation failed: structural error: invalid array subscript: [100, 100] for array of size 3"); // structural error suppressed by the path engine in lax mode. empty sequence is returned, so ON EMPTY behavior is applied @@ -103,7 +103,7 @@ public void testJsonQuery() assertThatThrownBy(() -> assertions.query( "SELECT json_query('" + INPUT + "', 'lax $[100]' ERROR ON EMPTY)")) - .isInstanceOf(JsonOutputConversionError.class) + .isInstanceOf(JsonOutputConversionException.class) .hasMessage("conversion from JSON failed: JSON path found no items"); // path returns multiple items (no array wrapper specified). this case is handled accordingly to the ON ERROR clause @@ -127,7 +127,7 @@ public void testJsonQuery() assertThatThrownBy(() -> assertions.query( "SELECT json_query('" + INPUT + "', 'lax $[0 to 2]' ERROR ON ERROR)")) - .isInstanceOf(JsonOutputConversionError.class) + .isInstanceOf(JsonOutputConversionException.class) .hasMessage("conversion from JSON failed: JSON path found multiple items"); } @@ -210,7 +210,7 @@ public void testInputConversionError() assertThatThrownBy(() -> assertions.query( "SELECT json_query('" + INCORRECT_INPUT + "', 'lax $[1]' ERROR ON ERROR)")) - .isInstanceOf(JsonInputConversionError.class) + .isInstanceOf(JsonInputConversionException.class) .hasMessage("conversion to JSON failed: "); } @@ -238,7 +238,7 @@ public void testPassingClause() assertThatThrownBy(() -> assertions.query( "SELECT json_query('" + INPUT + "', 'lax $array[0]' PASSING '[...' FORMAT JSON AS \"array\" ERROR ON ERROR)")) - .isInstanceOf(JsonInputConversionError.class) + .isInstanceOf(JsonInputConversionException.class) .hasMessage("conversion to JSON failed: "); // array index out of bounds diff --git a/core/trino-main/src/test/java/io/trino/sql/query/TestJsonValueFunction.java b/core/trino-main/src/test/java/io/trino/sql/query/TestJsonValueFunction.java index 76e8aa2a7e89..877829a12321 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/TestJsonValueFunction.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/TestJsonValueFunction.java @@ -13,9 +13,9 @@ */ package io.trino.sql.query; -import io.trino.json.PathEvaluationError; -import io.trino.operator.scalar.json.JsonInputConversionError; -import io.trino.operator.scalar.json.JsonValueFunction.JsonValueResultError; +import io.trino.json.PathEvaluationException; +import io.trino.operator.scalar.json.JsonInputConversionException; +import io.trino.operator.scalar.json.JsonValueFunction.JsonValueResultException; import io.trino.sql.parser.ParsingException; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; @@ -79,7 +79,7 @@ public void testJsonValue() assertThatThrownBy(() -> assertions.query( "SELECT json_value('" + INPUT + "', 'strict $[100]' ERROR ON ERROR)")) - .isInstanceOf(PathEvaluationError.class) + .isInstanceOf(PathEvaluationException.class) .hasMessage("path evaluation failed: structural error: invalid array subscript: [100, 100] for array of size 3"); // structural error suppressed by the path engine in lax mode. empty sequence is returned, so ON EMPTY behavior is applied @@ -99,7 +99,7 @@ public void testJsonValue() assertThatThrownBy(() -> assertions.query( "SELECT json_value('" + INPUT + "', 'lax $[100]' ERROR ON EMPTY)")) - .isInstanceOf(JsonValueResultError.class) + .isInstanceOf(JsonValueResultException.class) .hasMessage("cannot extract SQL scalar from JSON: JSON path found no items"); // path returns multiple items. this case is handled accordingly to the ON ERROR clause @@ -119,7 +119,7 @@ public void testJsonValue() assertThatThrownBy(() -> assertions.query( "SELECT json_value('" + INPUT + "', 'lax $[0 to 2]' ERROR ON ERROR)")) - .isInstanceOf(JsonValueResultError.class) + .isInstanceOf(JsonValueResultException.class) .hasMessage("cannot extract SQL scalar from JSON: JSON path found multiple items"); } @@ -198,7 +198,7 @@ public void testInputConversionError() assertThatThrownBy(() -> assertions.query( "SELECT json_value('" + INCORRECT_INPUT + "', 'lax $[1]' ERROR ON ERROR)")) - .isInstanceOf(JsonInputConversionError.class) + .isInstanceOf(JsonInputConversionException.class) .hasMessage("conversion to JSON failed: "); } @@ -226,7 +226,7 @@ public void testPassingClause() assertThatThrownBy(() -> assertions.query( "SELECT json_value('" + INPUT + "', 'lax $array[0]' PASSING '[...' FORMAT JSON AS \"array\" ERROR ON ERROR)")) - .isInstanceOf(JsonInputConversionError.class) + .isInstanceOf(JsonInputConversionException.class) .hasMessage("conversion to JSON failed: "); // array index out of bounds @@ -300,7 +300,7 @@ public void testPathResultNonScalar() assertThatThrownBy(() -> assertions.query( "SELECT json_value('" + INPUT + "', 'lax $' ERROR ON ERROR)")) - .isInstanceOf(JsonValueResultError.class) + .isInstanceOf(JsonValueResultException.class) .hasMessage("cannot extract SQL scalar from JSON: JSON path found an item that cannot be converted to an SQL value"); } From 18111cb46701253ba0c85082ac6009bf0657ce60 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sat, 18 Nov 2023 18:21:27 -0800 Subject: [PATCH 436/587] Add @FormatString and enforce AnnotateFormatMethod --- .../src/main/java/io/trino/Session.java | 2 +- .../io/trino/execution/SetColumnTypeTask.java | 2 +- .../io/trino/execution/SetSessionTask.java | 2 +- .../trino/json/PathEvaluationException.java | 2 ++ .../io/trino/metadata/FunctionManager.java | 6 ++-- .../operator/scalar/ParametricScalar.java | 2 +- .../operator/scalar/SequenceFunction.java | 2 +- .../HttpRequestSessionContextFactory.java | 2 ++ .../trino/server/TrinoSystemRequirements.java | 3 ++ .../sql/analyzer/AggregationAnalyzer.java | 7 ++-- .../sql/analyzer/ExpressionAnalyzer.java | 36 +++++++++---------- .../trino/sql/analyzer/JsonPathAnalyzer.java | 5 ++- .../sql/analyzer/SemanticExceptions.java | 4 +++ .../trino/sql/analyzer/StatementAnalyzer.java | 4 +-- .../sql/gen/LambdaBytecodeGenerator.java | 2 +- .../iterative/rule/ExtractSpatialJoins.java | 2 ++ .../trino/sql/routine/SqlRoutineAnalyzer.java | 6 ++-- .../src/main/java/io/trino/util/Failures.java | 2 ++ .../java/io/trino/cost/EstimateAssertion.java | 2 ++ .../src/main/java/io/trino/spi/QueryId.java | 2 ++ .../io/trino/spi/connector/Preconditions.java | 4 +++ .../java/io/trino/spi/type/DecimalType.java | 2 ++ .../spi/type/TypeOperatorDeclaration.java | 2 ++ .../java/io/trino/spi/type/TypeSignature.java | 2 ++ .../hive/formats/FileCorruptionException.java | 4 +++ .../hive/formats/rcfile/RcFileReader.java | 5 +++ .../InvalidCheckpointException.java | 3 ++ plugin/trino-geospatial/pom.xml | 5 +++ .../plugin/geospatial/BingTileFunctions.java | 7 ++++ .../AbstractHiveStatisticsProvider.java | 8 +++-- .../io/trino/plugin/hive/util/HiveUtil.java | 2 ++ pom.xml | 1 + 32 files changed, 99 insertions(+), 41 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/Session.java b/core/trino-main/src/main/java/io/trino/Session.java index bacd9bf60bf1..4f885098ab6e 100644 --- a/core/trino-main/src/main/java/io/trino/Session.java +++ b/core/trino-main/src/main/java/io/trino/Session.java @@ -299,7 +299,7 @@ public String getPreparedStatementFromExecute(Execute execute) public String getPreparedStatement(String name) { String sql = preparedStatements.get(name); - checkCondition(sql != null, NOT_FOUND, "Prepared statement not found: " + name); + checkCondition(sql != null, NOT_FOUND, "Prepared statement not found: %s", name); return sql; } diff --git a/core/trino-main/src/main/java/io/trino/execution/SetColumnTypeTask.java b/core/trino-main/src/main/java/io/trino/execution/SetColumnTypeTask.java index fab3a71a84eb..29598537002f 100644 --- a/core/trino-main/src/main/java/io/trino/execution/SetColumnTypeTask.java +++ b/core/trino-main/src/main/java/io/trino/execution/SetColumnTypeTask.java @@ -93,7 +93,7 @@ else if (metadata.getView(session, qualifiedObjectName).isPresent()) { exceptionMessage += ", but a view with that name exists."; } if (!statement.isTableExists()) { - throw semanticException(TABLE_NOT_FOUND, statement, exceptionMessage); + throw semanticException(TABLE_NOT_FOUND, statement, "%s", exceptionMessage); } return immediateVoidFuture(); } diff --git a/core/trino-main/src/main/java/io/trino/execution/SetSessionTask.java b/core/trino-main/src/main/java/io/trino/execution/SetSessionTask.java index 87b01124a2d1..c81bd8024834 100644 --- a/core/trino-main/src/main/java/io/trino/execution/SetSessionTask.java +++ b/core/trino-main/src/main/java/io/trino/execution/SetSessionTask.java @@ -109,7 +109,7 @@ public ListenableFuture execute( propertyMetadata.decode(objectValue); } catch (RuntimeException e) { - throw semanticException(INVALID_SESSION_PROPERTY, statement, e.getMessage()); + throw semanticException(INVALID_SESSION_PROPERTY, statement, "%s", e.getMessage()); } stateMachine.addSetSessionProperties(propertyName.toString(), value); diff --git a/core/trino-main/src/main/java/io/trino/json/PathEvaluationException.java b/core/trino-main/src/main/java/io/trino/json/PathEvaluationException.java index 170908fe67a1..3b9bb711ae95 100644 --- a/core/trino-main/src/main/java/io/trino/json/PathEvaluationException.java +++ b/core/trino-main/src/main/java/io/trino/json/PathEvaluationException.java @@ -13,6 +13,7 @@ */ package io.trino.json; +import com.google.errorprone.annotations.FormatMethod; import io.trino.spi.TrinoException; import static io.trino.spi.StandardErrorCode.PATH_EVALUATION_ERROR; @@ -44,6 +45,7 @@ public PathEvaluationException(Throwable cause) * to the chosen `ON ERROR` option. Non-structural errors (e.g. numeric exceptions) * are not suppressed in `lax` or `strict` mode. */ + @FormatMethod public static TrinoException structuralError(String format, Object... arguments) { return new PathEvaluationException("structural error: " + format(format, arguments)); diff --git a/core/trino-main/src/main/java/io/trino/metadata/FunctionManager.java b/core/trino-main/src/main/java/io/trino/metadata/FunctionManager.java index 0c309d73e5f4..23e071d2ecc2 100644 --- a/core/trino-main/src/main/java/io/trino/metadata/FunctionManager.java +++ b/core/trino-main/src/main/java/io/trino/metadata/FunctionManager.java @@ -15,6 +15,7 @@ import com.google.common.cache.CacheBuilder; import com.google.common.util.concurrent.UncheckedExecutionException; +import com.google.errorprone.annotations.FormatMethod; import com.google.inject.Inject; import io.trino.FeaturesConfig; import io.trino.cache.NonEvictableCache; @@ -246,12 +247,12 @@ private static void verifyMethodHandleSignature(BoundSignature boundSignature, S case BLOCK_POSITION_NOT_NULL: case BLOCK_POSITION: verifyFunctionSignature(parameterType.equals(Block.class) && methodType.parameterType(parameterIndex + 1).equals(int.class), - "Expected %s argument types to be Block and int".formatted(argumentConvention)); + "Expected %s argument types to be Block and int", argumentConvention); break; case VALUE_BLOCK_POSITION: case VALUE_BLOCK_POSITION_NOT_NULL: verifyFunctionSignature(ValueBlock.class.isAssignableFrom(parameterType) && methodType.parameterType(parameterIndex + 1).equals(int.class), - "Expected %s argument types to be ValueBlock and int".formatted(argumentConvention)); + "Expected %s argument types to be ValueBlock and int", argumentConvention); break; case FLAT: verifyFunctionSignature(parameterType.equals(byte[].class) && @@ -303,6 +304,7 @@ private static void verifyMethodHandleSignature(BoundSignature boundSignature, S } } + @FormatMethod private static void verifyFunctionSignature(boolean check, String message, Object... args) { if (!check) { diff --git a/core/trino-main/src/main/java/io/trino/operator/scalar/ParametricScalar.java b/core/trino-main/src/main/java/io/trino/operator/scalar/ParametricScalar.java index 8f5fa61d026c..3710e9c05bc0 100644 --- a/core/trino-main/src/main/java/io/trino/operator/scalar/ParametricScalar.java +++ b/core/trino-main/src/main/java/io/trino/operator/scalar/ParametricScalar.java @@ -127,7 +127,7 @@ public SpecializedSqlScalarFunction specialize(BoundSignature boundSignature, Fu ParametricScalarImplementation exactImplementation = implementations.getExactImplementations().get(boundSignature.toSignature()); if (exactImplementation != null) { Optional scalarFunctionImplementation = exactImplementation.specialize(functionBinding, functionDependencies); - checkCondition(scalarFunctionImplementation.isPresent(), FUNCTION_IMPLEMENTATION_ERROR, format("Exact implementation of %s do not match expected java types.", boundSignature.getName())); + checkCondition(scalarFunctionImplementation.isPresent(), FUNCTION_IMPLEMENTATION_ERROR, "Exact implementation of %s do not match expected java types", boundSignature.getName()); return scalarFunctionImplementation.get(); } diff --git a/core/trino-main/src/main/java/io/trino/operator/scalar/SequenceFunction.java b/core/trino-main/src/main/java/io/trino/operator/scalar/SequenceFunction.java index 49b9b086149a..88c1c1e6bf07 100644 --- a/core/trino-main/src/main/java/io/trino/operator/scalar/SequenceFunction.java +++ b/core/trino-main/src/main/java/io/trino/operator/scalar/SequenceFunction.java @@ -166,7 +166,7 @@ public static int checkMaxEntry(long length) checkCondition( -MAX_RESULT_ENTRIES <= length && length <= MAX_RESULT_ENTRIES, INVALID_FUNCTION_ARGUMENT, - "result of sequence function must not have more than %d entries".formatted(MAX_RESULT_ENTRIES)); + "result of sequence function must not have more than %d entries", MAX_RESULT_ENTRIES); return toIntExact(length); } diff --git a/core/trino-main/src/main/java/io/trino/server/HttpRequestSessionContextFactory.java b/core/trino-main/src/main/java/io/trino/server/HttpRequestSessionContextFactory.java index be3c0b7ceb76..acaf7874a666 100644 --- a/core/trino-main/src/main/java/io/trino/server/HttpRequestSessionContextFactory.java +++ b/core/trino-main/src/main/java/io/trino/server/HttpRequestSessionContextFactory.java @@ -17,6 +17,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.FormatMethod; import com.google.inject.Inject; import io.airlift.units.DataSize; import io.airlift.units.Duration; @@ -393,6 +394,7 @@ private static ResourceEstimates parseResourceEstimate(ProtocolHeaders protocolH return builder.build(); } + @FormatMethod private static void assertRequest(boolean expression, String format, Object... args) { if (!expression) { diff --git a/core/trino-main/src/main/java/io/trino/server/TrinoSystemRequirements.java b/core/trino-main/src/main/java/io/trino/server/TrinoSystemRequirements.java index 3d1ca4de857f..7abb563cc108 100644 --- a/core/trino-main/src/main/java/io/trino/server/TrinoSystemRequirements.java +++ b/core/trino-main/src/main/java/io/trino/server/TrinoSystemRequirements.java @@ -15,6 +15,7 @@ import com.google.common.base.StandardSystemProperty; import com.google.common.collect.ImmutableSet; +import com.google.errorprone.annotations.FormatMethod; import com.sun.management.UnixOperatingSystemMXBean; import io.airlift.slice.Slice; import io.airlift.slice.Slices; @@ -173,12 +174,14 @@ public static void verifySystemTimeIsReasonable() } } + @FormatMethod private static void failRequirement(String format, Object... args) { System.err.println("ERROR: " + format(format, args)); System.exit(100); } + @FormatMethod private static void warnRequirement(String format, Object... args) { System.err.println("WARNING: " + format(format, args)); diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/AggregationAnalyzer.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/AggregationAnalyzer.java index 4e68a3a1a593..318be337ea6e 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/AggregationAnalyzer.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/AggregationAnalyzer.java @@ -447,10 +447,7 @@ protected Boolean visitFunctionCall(FunctionCall node, Void context) } else { if (node.getFilter().isPresent()) { - throw semanticException(FUNCTION_NOT_AGGREGATE, - node, - "Filter is only valid for aggregation functions", - node); + throw semanticException(FUNCTION_NOT_AGGREGATE, node, "Filter is only valid for aggregation functions"); } if (node.getOrderBy().isPresent()) { throw semanticException(FUNCTION_NOT_AGGREGATE, node, "ORDER BY is only valid for aggregation functions"); @@ -803,7 +800,7 @@ private void verifyNoOrderByReferencesToOutputColumns(Node node, StandardErrorCo getReferencesToScope(node, analysis, orderByScope.get()) .findFirst() .ifPresent(expression -> { - throw semanticException(errorCode, expression, errorString); + throw semanticException(errorCode, expression, "%s", errorString); }); } } diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/ExpressionAnalyzer.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/ExpressionAnalyzer.java index 16c53150c043..4b3fab8618f4 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/ExpressionAnalyzer.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/ExpressionAnalyzer.java @@ -761,7 +761,7 @@ protected Type visitDereferenceExpression(DereferenceExpression node, StackableA for (RowType.Field rowField : rowType.getFields()) { if (fieldName.equalsIgnoreCase(rowField.getName().orElse(null))) { if (foundFieldName) { - throw semanticException(AMBIGUOUS_NAME, field, "Ambiguous row field reference: " + fieldName); + throw semanticException(AMBIGUOUS_NAME, field, "Ambiguous row field reference: %s", fieldName); } foundFieldName = true; rowFieldType = rowField.getType(); @@ -842,7 +842,7 @@ protected Type visitIfExpression(IfExpression node, StackableAstVisitorContext fields = ImmutableList.builder(); @@ -2550,7 +2550,7 @@ public Type visitJsonValue(JsonValue node, StackableAstVisitorContext c !isDateTimeType(returnedType) || returnedType.equals(INTERVAL_DAY_TIME) || returnedType.equals(INTERVAL_YEAR_MONTH)) { - throw semanticException(TYPE_MISMATCH, node, "Invalid return type of function JSON_VALUE: " + node.getReturnedType().get()); + throw semanticException(TYPE_MISMATCH, node, "Invalid return type of function JSON_VALUE: %s", node.getReturnedType().get()); } JsonPathAnalysis pathAnalysis = jsonPathAnalyses.get(NodeRef.of(node)); @@ -2797,7 +2797,7 @@ private ResolvedFunction getInputFunction(Type type, JsonFormat format, Node nod if (isStringType(type)) { yield VARBINARY_TO_JSON; } - throw semanticException(TYPE_MISMATCH, node, format("Cannot read input of type %s as JSON using formatting %s", type, format)); + throw semanticException(TYPE_MISMATCH, node, "Cannot read input of type %s as JSON using formatting %s", type, format); } case UTF8 -> VARBINARY_UTF8_TO_JSON; case UTF16 -> VARBINARY_UTF16_TO_JSON; @@ -2822,23 +2822,23 @@ private ResolvedFunction getOutputFunction(Type type, JsonFormat format, Node no if (isStringType(type)) { yield JSON_TO_VARBINARY; } - throw semanticException(TYPE_MISMATCH, node, format("Cannot output JSON value as %s using formatting %s", type, format)); + throw semanticException(TYPE_MISMATCH, node, "Cannot output JSON value as %s using formatting %s", type, format); } case UTF8 -> { if (!VARBINARY.equals(type)) { - throw semanticException(TYPE_MISMATCH, node, format("Cannot output JSON value as %s using formatting %s", type, format)); + throw semanticException(TYPE_MISMATCH, node, "Cannot output JSON value as %s using formatting %s", type, format); } yield JSON_TO_VARBINARY_UTF8; } case UTF16 -> { if (!VARBINARY.equals(type)) { - throw semanticException(TYPE_MISMATCH, node, format("Cannot output JSON value as %s using formatting %s", type, format)); + throw semanticException(TYPE_MISMATCH, node, "Cannot output JSON value as %s using formatting %s", type, format); } yield JSON_TO_VARBINARY_UTF16; } case UTF32 -> { if (!VARBINARY.equals(type)) { - throw semanticException(TYPE_MISMATCH, node, format("Cannot output JSON value as %s using formatting %s", type, format)); + throw semanticException(TYPE_MISMATCH, node, "Cannot output JSON value as %s using formatting %s", type, format); } yield JSON_TO_VARBINARY_UTF32; } @@ -3155,7 +3155,7 @@ private Type coerceToSingleType(StackableAstVisitorContext context, Nod return superType; } - throw semanticException(TYPE_MISMATCH, node, message, firstType, secondType); + throw semanticException(TYPE_MISMATCH, node, "%s: %s vs %s", message, firstType, secondType); } private Type coerceToSingleType(StackableAstVisitorContext context, String description, List expressions) @@ -3176,12 +3176,12 @@ private Type coerceToSingleType(StackableAstVisitorContext context, Str for (Type type : types) { Optional newSuperType = typeCoercion.getCommonSuperType(superType, type); if (newSuperType.isEmpty()) { - throw semanticException(TYPE_MISMATCH, Iterables.get(typeExpressions.get(type), 0).getNode(), format( + throw semanticException(TYPE_MISMATCH, Iterables.get(typeExpressions.get(type), 0).getNode(), "%s must be the same type or coercible to a common type. Cannot find common type between %s and %s, all types (without duplicates): %s", description, superType, type, - typeExpressions.keySet())); + typeExpressions.keySet()); } superType = newSuperType.get(); } @@ -3192,12 +3192,12 @@ private Type coerceToSingleType(StackableAstVisitorContext context, Str if (!type.equals(superType)) { if (!typeCoercion.canCoerce(type, superType)) { - throw semanticException(TYPE_MISMATCH, Iterables.get(coercionCandidates, 0).getNode(), format( + throw semanticException(TYPE_MISMATCH, Iterables.get(coercionCandidates, 0).getNode(), "%s must be the same type or coercible to a common type. Cannot find common type between %s and %s, all types (without duplicates): %s", description, superType, type, - typeExpressions.keySet())); + typeExpressions.keySet()); } addOrReplaceExpressionsCoercion(coercionCandidates, type, superType); } @@ -3460,7 +3460,7 @@ public static void analyzeExpressionWithoutSubqueries( plannerContext, accessControl, (node, ignored) -> { - throw semanticException(errorCode, node, message); + throw semanticException(errorCode, node, "%s", message); }, session, TypeProvider.empty(), @@ -3582,7 +3582,7 @@ public static ExpressionAnalyzer createWithoutSubqueries( session, TypeProvider.empty(), parameters, - node -> semanticException(errorCode, node, message), + node -> semanticException(errorCode, node, "%s", message), warningCollector, isDescribe); } diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/JsonPathAnalyzer.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/JsonPathAnalyzer.java index edabd1f19bfe..093d9f016cbe 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/JsonPathAnalyzer.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/JsonPathAnalyzer.java @@ -83,7 +83,6 @@ import static io.trino.sql.analyzer.TypeSignatureProvider.fromTypes; import static io.trino.sql.jsonpath.tree.ArithmeticUnary.Sign.PLUS; import static io.trino.type.Json2016Type.JSON_2016; -import static java.lang.String.format; import static java.util.Objects.requireNonNull; public class JsonPathAnalyzer @@ -368,9 +367,9 @@ protected Type visitNamedVariable(NamedVariable node, Void context) .filter(name -> name.equalsIgnoreCase(node.getName())) .findFirst(); if (similarName.isPresent()) { - throw semanticException(INVALID_PATH, pathNode, format("no value passed for parameter %s. Try quoting \"%s\" in the PASSING clause to match case", node.getName(), node.getName())); + throw semanticException(INVALID_PATH, pathNode, "no value passed for parameter %s. Try quoting \"%s\" in the PASSING clause to match case", node.getName(), node.getName()); } - throw semanticException(INVALID_PATH, pathNode, "no value passed for parameter " + node.getName()); + throw semanticException(INVALID_PATH, pathNode, "no value passed for parameter %s", node.getName()); } if (parameterType.equals(JSON_2016)) { diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/SemanticExceptions.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/SemanticExceptions.java index 49f4f57fd9d9..db93edb8d510 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/SemanticExceptions.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/SemanticExceptions.java @@ -13,6 +13,7 @@ */ package io.trino.sql.analyzer; +import com.google.errorprone.annotations.FormatMethod; import io.trino.spi.ErrorCodeSupplier; import io.trino.spi.TrinoException; import io.trino.sql.tree.Expression; @@ -38,11 +39,14 @@ public static TrinoException ambiguousAttributeException(Expression node, Qualif throw semanticException(AMBIGUOUS_NAME, node, "Column '%s' is ambiguous", name); } + @SuppressWarnings("FormatStringAnnotation") + @FormatMethod public static TrinoException semanticException(ErrorCodeSupplier code, Node node, String format, Object... args) { return semanticException(code, node, null, format, args); } + @FormatMethod public static TrinoException semanticException(ErrorCodeSupplier code, Node node, Throwable cause, String format, Object... args) { throw new TrinoException(code, extractLocation(node), format(format, args), cause); diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java index 484d28d0e851..53bebde8bc9f 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java @@ -1519,7 +1519,7 @@ protected Scope visitQuery(Query node, Optional scope) { for (FunctionSpecification function : node.getFunctions()) { if (function.getName().getPrefix().isPresent()) { - throw semanticException(SYNTAX_ERROR, function, "Inline function names cannot be qualified: " + function.getName()); + throw semanticException(SYNTAX_ERROR, function, "Inline function names cannot be qualified: %s", function.getName()); } function.getRoutineCharacteristics().stream() .filter(SecurityCharacteristic.class::isInstance) @@ -2500,7 +2500,7 @@ private Scope createScopeForView( Query query = parseView(originalSql, name, table); if (!query.getFunctions().isEmpty()) { - throw semanticException(NOT_SUPPORTED, table, "View contains inline function: " + name); + throw semanticException(NOT_SUPPORTED, table, "View contains inline function: %s", name); } analysis.registerTableForView(table); diff --git a/core/trino-main/src/main/java/io/trino/sql/gen/LambdaBytecodeGenerator.java b/core/trino-main/src/main/java/io/trino/sql/gen/LambdaBytecodeGenerator.java index 55b0cc4ab935..c6ebfbe16ec2 100644 --- a/core/trino-main/src/main/java/io/trino/sql/gen/LambdaBytecodeGenerator.java +++ b/core/trino-main/src/main/java/io/trino/sql/gen/LambdaBytecodeGenerator.java @@ -308,7 +308,7 @@ private static Method getSingleApplyMethod(Class lambdaFunctionInterface) .filter(method -> method.getName().equals("apply")) .collect(toImmutableList()); - checkCondition(applyMethods.size() == 1, COMPILER_ERROR, "Expect to have exactly 1 method with name 'apply' in interface " + lambdaFunctionInterface.getName()); + checkCondition(applyMethods.size() == 1, COMPILER_ERROR, "Expect to have exactly 1 method with name 'apply' in interface %s", lambdaFunctionInterface.getName()); return applyMethods.get(0); } diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/ExtractSpatialJoins.java b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/ExtractSpatialJoins.java index 6d5361e48124..9b6d32905072 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/ExtractSpatialJoins.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/ExtractSpatialJoins.java @@ -19,6 +19,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; +import com.google.errorprone.annotations.FormatMethod; import io.trino.Session; import io.trino.geospatial.KdbTree; import io.trino.geospatial.KdbTreeUtils; @@ -508,6 +509,7 @@ private static KdbTree loadKdbTree(String tableName, Session session, Metadata m return kdbTree.get(); } + @FormatMethod private static void checkSpatialPartitioningTable(boolean condition, String message, Object... arguments) { if (!condition) { diff --git a/core/trino-main/src/main/java/io/trino/sql/routine/SqlRoutineAnalyzer.java b/core/trino-main/src/main/java/io/trino/sql/routine/SqlRoutineAnalyzer.java index ab6280785dae..a01b20968db7 100644 --- a/core/trino-main/src/main/java/io/trino/sql/routine/SqlRoutineAnalyzer.java +++ b/core/trino-main/src/main/java/io/trino/sql/routine/SqlRoutineAnalyzer.java @@ -192,7 +192,7 @@ private Type getType(Node node, DataType type) return plannerContext.getTypeManager().getType(toTypeSignature(type)); } catch (TypeNotFoundException e) { - throw semanticException(TYPE_MISMATCH, node, "Unknown type: " + type); + throw semanticException(TYPE_MISMATCH, node, "Unknown type: %s", type); } } @@ -218,7 +218,7 @@ private static void validateArguments(FunctionSpecification function) } String name = identifierValue(parameter.getName().get()); if (!argumentNames.add(name)) { - throw semanticException(INVALID_ARGUMENTS, parameter, "Duplicate function parameter name: " + name); + throw semanticException(INVALID_ARGUMENTS, parameter, "Duplicate function parameter name: %s", name); } } } @@ -514,7 +514,7 @@ private void analyzeExpression(Context context, Expression expression, Type expe return; } if (!typeCoercion.canCoerce(actualType, expectedType)) { - throw semanticException(TYPE_MISMATCH, expression, message + " must evaluate to %s (actual: %s)", expectedType, actualType); + throw semanticException(TYPE_MISMATCH, expression, "%s must evaluate to %s (actual: %s)", message, expectedType, actualType); } addCoercion(expression, actualType, expectedType); diff --git a/core/trino-main/src/main/java/io/trino/util/Failures.java b/core/trino-main/src/main/java/io/trino/util/Failures.java index 99a330c78b51..f6e7eee9a159 100644 --- a/core/trino-main/src/main/java/io/trino/util/Failures.java +++ b/core/trino-main/src/main/java/io/trino/util/Failures.java @@ -14,6 +14,7 @@ package io.trino.util; import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.FormatMethod; import io.trino.client.ErrorLocation; import io.trino.execution.ExecutionFailureInfo; import io.trino.execution.Failure; @@ -57,6 +58,7 @@ public static ExecutionFailureInfo toFailure(Throwable failure) return toFailure(failure, newIdentityHashSet()); } + @FormatMethod public static void checkCondition(boolean condition, ErrorCodeSupplier errorCode, String formatString, Object... args) { if (!condition) { diff --git a/core/trino-main/src/test/java/io/trino/cost/EstimateAssertion.java b/core/trino-main/src/test/java/io/trino/cost/EstimateAssertion.java index 8d231a6559cd..8636a26d2480 100644 --- a/core/trino-main/src/test/java/io/trino/cost/EstimateAssertion.java +++ b/core/trino-main/src/test/java/io/trino/cost/EstimateAssertion.java @@ -13,6 +13,7 @@ */ package io.trino.cost; +import com.google.errorprone.annotations.FormatMethod; import io.trino.util.MoreMath; import static java.lang.Double.isNaN; @@ -24,6 +25,7 @@ private EstimateAssertion() {} private static final double TOLERANCE = 0.0000001; + @FormatMethod public static void assertEstimateEquals(double actual, double expected, String messageFormat, Object... messageObjects) { if (isNaN(actual) && isNaN(expected)) { diff --git a/core/trino-spi/src/main/java/io/trino/spi/QueryId.java b/core/trino-spi/src/main/java/io/trino/spi/QueryId.java index ad1d9c58329b..3ea4f1b3ba93 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/QueryId.java +++ b/core/trino-spi/src/main/java/io/trino/spi/QueryId.java @@ -15,6 +15,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; +import com.google.errorprone.annotations.FormatMethod; import java.util.List; import java.util.Objects; @@ -108,6 +109,7 @@ public static List parseDottedId(String id, int expectedParts, String na return ids; } + @FormatMethod private static void checkArgument(boolean condition, String message, Object... messageArgs) { if (!condition) { diff --git a/core/trino-spi/src/main/java/io/trino/spi/connector/Preconditions.java b/core/trino-spi/src/main/java/io/trino/spi/connector/Preconditions.java index 0012d0faa2a2..c7c1ad3fd56c 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/connector/Preconditions.java +++ b/core/trino-spi/src/main/java/io/trino/spi/connector/Preconditions.java @@ -13,12 +13,15 @@ */ package io.trino.spi.connector; +import com.google.errorprone.annotations.FormatMethod; + import static java.lang.String.format; final class Preconditions { private Preconditions() {} + @FormatMethod static void checkArgument(boolean test, String message, Object... args) { if (!test) { @@ -26,6 +29,7 @@ static void checkArgument(boolean test, String message, Object... args) } } + @FormatMethod static void checkState(boolean test, String message, Object... args) { if (!test) { diff --git a/core/trino-spi/src/main/java/io/trino/spi/type/DecimalType.java b/core/trino-spi/src/main/java/io/trino/spi/type/DecimalType.java index 828780d5ba16..8c4aa27c4136 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/type/DecimalType.java +++ b/core/trino-spi/src/main/java/io/trino/spi/type/DecimalType.java @@ -13,6 +13,7 @@ */ package io.trino.spi.type; +import com.google.errorprone.annotations.FormatMethod; import io.trino.spi.TrinoException; import io.trino.spi.block.ValueBlock; @@ -100,6 +101,7 @@ private static List buildTypeParameters(int precision, i return List.of(numericParameter(precision), numericParameter(scale)); } + @FormatMethod static void checkArgument(boolean condition, String format, Object... args) { if (!condition) { diff --git a/core/trino-spi/src/main/java/io/trino/spi/type/TypeOperatorDeclaration.java b/core/trino-spi/src/main/java/io/trino/spi/type/TypeOperatorDeclaration.java index bc604ded7dcd..fd9244f412ff 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/type/TypeOperatorDeclaration.java +++ b/core/trino-spi/src/main/java/io/trino/spi/type/TypeOperatorDeclaration.java @@ -13,6 +13,7 @@ */ package io.trino.spi.type; +import com.google.errorprone.annotations.FormatMethod; import io.trino.spi.block.Block; import io.trino.spi.block.BlockBuilder; import io.trino.spi.block.ValueBlock; @@ -628,6 +629,7 @@ else if (parameterTypes.size() > 1 && isAnnotationPresent(parameterAnnotations.g throw new IllegalArgumentException(format("Unexpected parameters for %s operator: %s", operatorType, method)); } + @FormatMethod private static void checkArgument(boolean test, String message, Object... arguments) { if (!test) { diff --git a/core/trino-spi/src/main/java/io/trino/spi/type/TypeSignature.java b/core/trino-spi/src/main/java/io/trino/spi/type/TypeSignature.java index 3beba11937ba..70fe8f484f2f 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/type/TypeSignature.java +++ b/core/trino-spi/src/main/java/io/trino/spi/type/TypeSignature.java @@ -14,6 +14,7 @@ package io.trino.spi.type; import com.fasterxml.jackson.annotation.JsonValue; +import com.google.errorprone.annotations.FormatMethod; import com.google.errorprone.annotations.Immutable; import java.util.ArrayList; @@ -133,6 +134,7 @@ private String formatValue(boolean json) return typeName.toString(); } + @FormatMethod private static void checkArgument(boolean argument, String format, Object... args) { if (!argument) { diff --git a/lib/trino-hive-formats/src/main/java/io/trino/hive/formats/FileCorruptionException.java b/lib/trino-hive-formats/src/main/java/io/trino/hive/formats/FileCorruptionException.java index c2779feb94ce..44725eebe2cf 100644 --- a/lib/trino-hive-formats/src/main/java/io/trino/hive/formats/FileCorruptionException.java +++ b/lib/trino-hive-formats/src/main/java/io/trino/hive/formats/FileCorruptionException.java @@ -13,6 +13,8 @@ */ package io.trino.hive.formats; +import com.google.errorprone.annotations.FormatMethod; + import java.io.IOException; import static java.lang.String.format; @@ -25,11 +27,13 @@ public FileCorruptionException(String message) super(message); } + @FormatMethod public FileCorruptionException(String messageFormat, Object... args) { super(format(messageFormat, args)); } + @FormatMethod public FileCorruptionException(Throwable cause, String messageFormat, Object... args) { super(format(messageFormat, args), cause); diff --git a/lib/trino-hive-formats/src/main/java/io/trino/hive/formats/rcfile/RcFileReader.java b/lib/trino-hive-formats/src/main/java/io/trino/hive/formats/rcfile/RcFileReader.java index 0195905dd50b..198203619551 100644 --- a/lib/trino-hive-formats/src/main/java/io/trino/hive/formats/rcfile/RcFileReader.java +++ b/lib/trino-hive-formats/src/main/java/io/trino/hive/formats/rcfile/RcFileReader.java @@ -14,6 +14,7 @@ package io.trino.hive.formats.rcfile; import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.FormatMethod; import io.airlift.slice.BasicSliceInput; import io.airlift.slice.Slice; import io.airlift.slice.Slices; @@ -468,6 +469,7 @@ private Slice readLengthPrefixedString(TrinoDataInputStream in) return in.readSlice(length); } + @FormatMethod private void verify(boolean expression, String messageFormat, Object... args) throws FileCorruptionException { @@ -476,12 +478,15 @@ private void verify(boolean expression, String messageFormat, Object... args) } } + @FormatMethod private FileCorruptionException corrupt(String messageFormat, Object... args) { closeQuietly(); return new FileCorruptionException(messageFormat, args); } + @SuppressWarnings("FormatStringAnnotation") + @FormatMethod private void validateWrite(Predicate test, String messageFormat, Object... args) throws FileCorruptionException { diff --git a/lib/trino-orc/src/main/java/io/trino/orc/checkpoint/InvalidCheckpointException.java b/lib/trino-orc/src/main/java/io/trino/orc/checkpoint/InvalidCheckpointException.java index 0d3a0b51414f..116c845430cb 100644 --- a/lib/trino-orc/src/main/java/io/trino/orc/checkpoint/InvalidCheckpointException.java +++ b/lib/trino-orc/src/main/java/io/trino/orc/checkpoint/InvalidCheckpointException.java @@ -13,11 +13,14 @@ */ package io.trino.orc.checkpoint; +import com.google.errorprone.annotations.FormatMethod; + import static java.lang.String.format; public class InvalidCheckpointException extends RuntimeException { + @FormatMethod public InvalidCheckpointException(String message, Object... arguments) { super(format(message, arguments)); diff --git a/plugin/trino-geospatial/pom.xml b/plugin/trino-geospatial/pom.xml index 61fa6c3bdf2d..68b09de8ed92 100644 --- a/plugin/trino-geospatial/pom.xml +++ b/plugin/trino-geospatial/pom.xml @@ -22,6 +22,11 @@ esri-geometry-api + + com.google.errorprone + error_prone_annotations + + com.google.guava guava diff --git a/plugin/trino-geospatial/src/main/java/io/trino/plugin/geospatial/BingTileFunctions.java b/plugin/trino-geospatial/src/main/java/io/trino/plugin/geospatial/BingTileFunctions.java index 23c0422ed378..e11e08e7d17c 100644 --- a/plugin/trino-geospatial/src/main/java/io/trino/plugin/geospatial/BingTileFunctions.java +++ b/plugin/trino-geospatial/src/main/java/io/trino/plugin/geospatial/BingTileFunctions.java @@ -17,6 +17,7 @@ import com.esri.core.geometry.Point; import com.esri.core.geometry.ogc.OGCGeometry; import com.google.common.collect.ImmutableList; +import com.google.errorprone.annotations.FormatMethod; import io.airlift.slice.Slice; import io.trino.spi.TrinoException; import io.trino.spi.block.Block; @@ -700,6 +701,12 @@ public double distance(double latitude2, double longitude2) } } + private static void checkCondition(boolean condition, String message) + { + checkCondition(condition, "%s", message); + } + + @FormatMethod private static void checkCondition(boolean condition, String formatString, Object... args) { if (!condition) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/statistics/AbstractHiveStatisticsProvider.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/statistics/AbstractHiveStatisticsProvider.java index bfaa9129f620..1547ddf05cb3 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/statistics/AbstractHiveStatisticsProvider.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/statistics/AbstractHiveStatisticsProvider.java @@ -20,6 +20,8 @@ import com.google.common.primitives.Ints; import com.google.common.primitives.Shorts; import com.google.common.primitives.SignedBytes; +import com.google.errorprone.annotations.FormatMethod; +import com.google.errorprone.annotations.FormatString; import io.airlift.log.Logger; import io.airlift.slice.Slice; import io.trino.plugin.hive.HiveBasicStatistics; @@ -331,7 +333,8 @@ private static void validateColumnStatistics(SchemaTableName table, String parti }); } - private static void checkStatistics(boolean expression, SchemaTableName table, String partition, String column, String message, Object... args) + @FormatMethod + private static void checkStatistics(boolean expression, SchemaTableName table, String partition, String column, @FormatString String message, Object... args) { if (!expression) { throw new TrinoException( @@ -340,7 +343,8 @@ private static void checkStatistics(boolean expression, SchemaTableName table, S } } - private static void checkStatistics(boolean expression, SchemaTableName table, String partition, String message, Object... args) + @FormatMethod + private static void checkStatistics(boolean expression, SchemaTableName table, String partition, @FormatString String message, Object... args) { if (!expression) { throw new TrinoException( diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveUtil.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveUtil.java index 061b08e14e03..d1f0984ce9dc 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveUtil.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/util/HiveUtil.java @@ -20,6 +20,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; +import com.google.errorprone.annotations.FormatMethod; import io.airlift.slice.Slice; import io.airlift.slice.SliceUtf8; import io.trino.filesystem.Location; @@ -578,6 +579,7 @@ public static List getPartitionKeyColumnHandles(Table table, T return columns.build(); } + @FormatMethod public static void checkCondition(boolean condition, ErrorCodeSupplier errorCode, String formatString, Object... args) { if (!condition) { diff --git a/pom.xml b/pom.xml index e135738bbdf0..748da83ff235 100644 --- a/pom.xml +++ b/pom.xml @@ -2712,6 +2712,7 @@ -XDcompilePolicy=simple -Xplugin:ErrorProne \ + -Xep:AnnotateFormatMethod:ERROR \ -Xep:BadComparable:ERROR \ -Xep:BadInstanceof:ERROR \ -Xep:BoxedPrimitiveConstructor:ERROR \ From 07d85f295743362e3c72dfc81742064ed0cbb080 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sat, 18 Nov 2023 18:34:10 -0800 Subject: [PATCH 437/587] Fix warnings in TestHttpEventListener --- .../httpquery/TestHttpEventListener.java | 78 ++++++++++--------- 1 file changed, 42 insertions(+), 36 deletions(-) diff --git a/plugin/trino-http-event-listener/src/test/java/io/trino/plugin/httpquery/TestHttpEventListener.java b/plugin/trino-http-event-listener/src/test/java/io/trino/plugin/httpquery/TestHttpEventListener.java index 9e99370f754d..0cc3270b0213 100644 --- a/plugin/trino-http-event-listener/src/test/java/io/trino/plugin/httpquery/TestHttpEventListener.java +++ b/plugin/trino-http-event-listener/src/test/java/io/trino/plugin/httpquery/TestHttpEventListener.java @@ -19,6 +19,7 @@ import io.airlift.json.JsonCodec; import io.trino.operator.RetryPolicy; import io.trino.spi.eventlistener.EventListener; +import io.trino.spi.eventlistener.EventListenerFactory; import io.trino.spi.eventlistener.QueryCompletedEvent; import io.trino.spi.eventlistener.QueryContext; import io.trino.spi.eventlistener.QueryCreatedEvent; @@ -31,6 +32,7 @@ import io.trino.spi.resourcegroups.QueryType; import io.trino.spi.resourcegroups.ResourceGroupId; import io.trino.spi.session.ResourceEstimates; +import okhttp3.TlsVersion; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; @@ -50,7 +52,6 @@ import java.io.File; import java.io.IOException; import java.net.URI; -import java.net.URL; import java.security.KeyStore; import java.time.Duration; import java.time.Instant; @@ -70,19 +71,21 @@ import static io.trino.spi.type.TimeZoneKey.UTC_KEY; import static java.lang.String.format; import static java.time.Duration.ofMillis; +import static java.time.Duration.ofSeconds; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD; +@SuppressWarnings("FieldNamingConvention") @TestInstance(PER_METHOD) -public class TestHttpEventListener +class TestHttpEventListener { private MockWebServer server; - private final HttpEventListenerFactory factory = new HttpEventListenerFactory(); + private final EventListenerFactory factory = new HttpEventListenerFactory(); - private static JsonCodec queryCompleteEventJsonCodec = jsonCodec(QueryCompletedEvent.class); - private static JsonCodec queryCreateEventJsonCodec = jsonCodec(QueryCreatedEvent.class); - private static JsonCodec splitCompleteEventJsonCodec = jsonCodec(SplitCompletedEvent.class); + private static final JsonCodec queryCompleteEventJsonCodec = jsonCodec(QueryCompletedEvent.class); + private static final JsonCodec queryCreateEventJsonCodec = jsonCodec(QueryCreatedEvent.class); + private static final JsonCodec splitCompleteEventJsonCodec = jsonCodec(SplitCompletedEvent.class); private static final QueryIOMetadata queryIOMetadata; private static final QueryContext queryContext; @@ -144,14 +147,14 @@ public class TestHttpEventListener ofMillis(4000), 1, 2, - Optional.of(Duration.ofMillis(100)), - Optional.of(Duration.ofMillis(200))); + Optional.of(ofMillis(100)), + Optional.of(ofMillis(200))); queryStatistics = new QueryStatistics( - ofMillis(1000), - ofMillis(1000), - ofMillis(1000), - ofMillis(1000), + ofSeconds(1), + ofSeconds(1), + ofSeconds(1), + ofSeconds(1), Optional.empty(), Optional.empty(), Optional.empty(), @@ -186,7 +189,7 @@ public class TestHttpEventListener 0, true, Collections.emptyList(), - List.of(new StageOutputBufferUtilization(0, 10, 0.1, 0.5, 0.10, 0.25, 0.50, 0.75, 0.90, 0.95, 0.99, 0.0, 1.0, Duration.ofSeconds(1234))), + List.of(new StageOutputBufferUtilization(0, 10, 0.1, 0.5, 0.10, 0.25, 0.50, 0.75, 0.90, 0.95, 0.99, 0.0, 1.0, ofSeconds(1234))), Collections.emptyList(), Collections.emptyList(), Optional.empty()); @@ -225,7 +228,7 @@ public class TestHttpEventListener } @BeforeEach - public void setup() + void setup() throws IOException { server = new MockWebServer(); @@ -233,7 +236,7 @@ public void setup() } @AfterEach - public void teardown() + void teardown() { try { server.close(); @@ -248,7 +251,7 @@ public void teardown() * Listener created without exceptions but not requests sent */ @Test - public void testAllLoggingDisabledShouldTimeout() + void testAllLoggingDisabledShouldTimeout() throws Exception { server.enqueue(new MockResponse() @@ -265,7 +268,7 @@ public void testAllLoggingDisabledShouldTimeout() } @Test - public void testAllLoggingEnabledShouldSendCorrectEvent() + void testAllLoggingEnabledShouldSendCorrectEvent() throws Exception { EventListener eventListener = factory.create(Map.of( @@ -289,7 +292,7 @@ public void testAllLoggingEnabledShouldSendCorrectEvent() } @Test - public void testContentTypeDefaultHeaderShouldAlwaysBeSet() + void testContentTypeDefaultHeaderShouldAlwaysBeSet() throws Exception { EventListener eventListener = factory.create(Map.of( @@ -300,11 +303,13 @@ public void testContentTypeDefaultHeaderShouldAlwaysBeSet() eventListener.queryCompleted(queryCompleteEvent); - assertThat(server.takeRequest(5, TimeUnit.SECONDS).getHeader("Content-Type")).isEqualTo("application/json; charset=utf-8"); + assertThat(server.takeRequest(5, TimeUnit.SECONDS)) + .extracting(request -> request.getHeader("Content-Type")) + .isEqualTo("application/json; charset=utf-8"); } @Test - public void testHttpHeadersShouldBePresent() + void testHttpHeadersShouldBePresent() throws Exception { EventListener eventListener = factory.create(Map.of( @@ -322,7 +327,7 @@ public void testHttpHeadersShouldBePresent() } @Test - public void testHttpsEnabledShouldUseTLSv13() + void testHttpsEnabledShouldUseTLSv13() throws Exception { setupServerTLSCertificate(); @@ -338,15 +343,16 @@ public void testHttpsEnabledShouldUseTLSv13() RecordedRequest recordedRequest = server.takeRequest(5, TimeUnit.SECONDS); assertThat(recordedRequest) - .describedAs("Handshake probably failed") - .isNotNull(); - assertThat(recordedRequest.getTlsVersion().javaName()).isEqualTo("TLSv1.3"); + .as("Handshake probably failed") + .extracting(RecordedRequest::getTlsVersion) + .extracting(TlsVersion::javaName) + .isEqualTo("TLSv1.3"); checkRequest(recordedRequest, queryCompleteEventJson); } @Test - public void testDifferentCertificatesShouldNotSendRequest() + void testDifferentCertificatesShouldNotSendRequest() throws Exception { setupServerTLSCertificate(); @@ -367,13 +373,13 @@ public void testDifferentCertificatesShouldNotSendRequest() } @Test - public void testNoServerCertificateShouldNotSendRequest() + void testNoServerCertificateShouldNotSendRequest() throws Exception { server.enqueue(new MockResponse().setResponseCode(200)); EventListener eventListener = factory.create(Map.of( - "http-event-listener.connect-ingest-uri", new URL("https", server.getHostName(), server.getPort(), "/").toString(), + "http-event-listener.connect-ingest-uri", "https://%s:%s/".formatted(server.getHostName(), server.getPort()), "http-event-listener.log-completed", "true", "http-event-listener.http-client.key-store-path", "src/test/resources/trino-httpquery-test.p12", "http-event-listener.http-client.key-store-password", "testing-ssl")); @@ -387,7 +393,7 @@ public void testNoServerCertificateShouldNotSendRequest() } @Test - public void testServerShoudRetry() + void testServerShoudRetry() throws Exception { testServerShouldRetry(503); @@ -414,7 +420,7 @@ private void testServerShouldRetry(int responseCode) } @Test - public void testServerDisconnectShouldRetry() + void testServerDisconnectShouldRetry() throws Exception { EventListener eventListener = factory.create(Map.of( @@ -434,7 +440,7 @@ public void testServerDisconnectShouldRetry() } @Test - public void testServerDelayDoesNotBlock() + void testServerDelayDoesNotBlock() throws Exception { EventListener eventListener = factory.create(Map.of( @@ -454,26 +460,26 @@ public void testServerDelayDoesNotBlock() checkRequest(server.takeRequest(5, TimeUnit.SECONDS), queryCompleteEventJson); } - private void checkRequest(RecordedRequest recordedRequest, String eventJson) + private static void checkRequest(RecordedRequest recordedRequest, String eventJson) throws JsonProcessingException { checkRequest(recordedRequest, ImmutableMap.of(), eventJson); } - private void checkRequest(RecordedRequest recordedRequest, Map customHeaders, String eventJson) + private static void checkRequest(RecordedRequest recordedRequest, Map customHeaders, String eventJson) throws JsonProcessingException { assertThat(recordedRequest) .describedAs("No request sent when logging is enabled") .isNotNull(); - for (String key : customHeaders.keySet()) { + customHeaders.forEach((key, value) -> { assertThat(recordedRequest.getHeader(key)) .describedAs(format("Custom header %s not present in request", key)) .isNotNull(); assertThat(recordedRequest.getHeader(key)) .describedAs(format("Expected value %s for header %s but got %s", customHeaders.get(key), key, recordedRequest.getHeader(key))) .isEqualTo(customHeaders.get(key)); - } + }); String body = recordedRequest.getBody().readUtf8(); assertThat(body.isEmpty()) .describedAs("Body is empty") @@ -493,8 +499,8 @@ private void setupServerTLSCertificate() TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(keyStore); - X509TrustManager x509TrustManager = (X509TrustManager) Stream.of(trustManagerFactory.getTrustManagers()) - .filter(tm -> tm instanceof X509TrustManager) + TrustManager x509TrustManager = Stream.of(trustManagerFactory.getTrustManagers()) + .filter(X509TrustManager.class::isInstance) .collect(onlyElement()); KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); From c59056c599ccaad5c2c740dd74ab9ca2dc97c5ce Mon Sep 17 00:00:00 2001 From: David Phillips Date: Mon, 20 Nov 2023 10:56:13 -0600 Subject: [PATCH 438/587] Disable RethrowReflectiveOperationExceptionAsLinkageError --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 748da83ff235..0f263fd3357b 100644 --- a/pom.xml +++ b/pom.xml @@ -2755,6 +2755,7 @@ -Xep:PreferredInterfaceType:OFF \ -Xep:PrimitiveArrayPassedToVarargsMethod:ERROR \ + -Xep:RethrowReflectiveOperationExceptionAsLinkageError:OFF \ -Xep:StaticAssignmentOfThrowable:ERROR \ -Xep:StreamResourceLeak:ERROR \ -Xep:UnnecessaryMethodReference:ERROR \ From b7a4fc238ca1e1a1600666dabf0b8066b55fa31d Mon Sep 17 00:00:00 2001 From: David Phillips Date: Tue, 21 Nov 2023 16:24:41 -0600 Subject: [PATCH 439/587] Fix and enforce UnicodeEscape --- .../io/trino/sql/planner/TestDomainTranslator.java | 12 ++++++------ pom.xml | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestDomainTranslator.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestDomainTranslator.java index 4f5d036289c2..4bad8335dab9 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestDomainTranslator.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestDomainTranslator.java @@ -1939,12 +1939,12 @@ public void testLikePredicate() // non-ASCII prefix testSimpleComparison( - like(C_VARCHAR, "abc\u0123\ud83d\ude80def\u007e\u007f\u00ff\u0123\uccf0%"), + like(C_VARCHAR, "abc\u0123\ud83d\ude80def~\u007f\u00ff\u0123\uccf0%"), C_VARCHAR, - like(C_VARCHAR, "abc\u0123\ud83d\ude80def\u007e\u007f\u00ff\u0123\uccf0%"), + like(C_VARCHAR, "abc\u0123\ud83d\ude80def~\u007f\u00ff\u0123\uccf0%"), Domain.create( ValueSet.ofRanges(Range.range(varcharType, - utf8Slice("abc\u0123\ud83d\ude80def\u007e\u007f\u00ff\u0123\uccf0"), true, + utf8Slice("abc\u0123\ud83d\ude80def~\u007f\u00ff\u0123\uccf0"), true, utf8Slice("abc\u0123\ud83d\ude80def\u007f"), false)), false)); @@ -1997,12 +1997,12 @@ public void testStartsWithFunction() // non-ASCII testSimpleComparison( - startsWith(C_VARCHAR, stringLiteral("abc\u0123\ud83d\ude80def\u007e\u007f\u00ff\u0123\uccf0")), + startsWith(C_VARCHAR, stringLiteral("abc\u0123\ud83d\ude80def~\u007f\u00ff\u0123\uccf0")), C_VARCHAR, - startsWith(C_VARCHAR, stringLiteral("abc\u0123\ud83d\ude80def\u007e\u007f\u00ff\u0123\uccf0")), + startsWith(C_VARCHAR, stringLiteral("abc\u0123\ud83d\ude80def~\u007f\u00ff\u0123\uccf0")), Domain.create( ValueSet.ofRanges(Range.range(varcharType, - utf8Slice("abc\u0123\ud83d\ude80def\u007e\u007f\u00ff\u0123\uccf0"), true, + utf8Slice("abc\u0123\ud83d\ude80def~\u007f\u00ff\u0123\uccf0"), true, utf8Slice("abc\u0123\ud83d\ude80def\u007f"), false)), false)); } diff --git a/pom.xml b/pom.xml index 0f263fd3357b..741803ba87cf 100644 --- a/pom.xml +++ b/pom.xml @@ -2758,6 +2758,7 @@ -Xep:RethrowReflectiveOperationExceptionAsLinkageError:OFF \ -Xep:StaticAssignmentOfThrowable:ERROR \ -Xep:StreamResourceLeak:ERROR \ + -Xep:UnicodeEscape:ERROR \ -Xep:UnnecessaryMethodReference:ERROR \ -Xep:UnnecessaryOptionalGet:ERROR \ From d4e3d73380b1c24823cafc7c7ffe61e3bdd95a66 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Tue, 21 Nov 2023 16:19:18 -0600 Subject: [PATCH 440/587] Fix and enforce UnnecessaryParentheses --- .../uri/AbstractConnectionProperty.java | 2 +- .../internal/tls/LegacyHostnameVerifier.java | 6 +- .../io/trino/jdbc/AbstractTrinoResultSet.java | 4 +- .../java/io/trino/jdbc/TypeConversions.java | 2 +- .../trino/jdbc/TestTrinoDatabaseMetaData.java | 4 +- .../io/trino/jdbc/TestTrinoResultSet.java | 4 +- .../java/io/trino/cost/TopNStatsRule.java | 2 +- .../io/trino/execution/SplitAssignment.java | 2 +- .../execution/buffer/PageCodecMarker.java | 4 +- .../execution/buffer/PageDeserializer.java | 2 +- ...ventDrivenFaultTolerantQueryScheduler.java | 2 +- .../io/trino/json/PathEvaluationVisitor.java | 6 +- .../io/trino/memory/ClusterMemoryManager.java | 2 +- .../operator/SpatialIndexBuilderOperator.java | 2 +- .../trino/operator/TopNPeerGroupLookup.java | 2 +- .../AbstractMapAggregationState.java | 6 +- .../io/trino/operator/index/IndexLoader.java | 2 +- .../join/PartitionedLookupSource.java | 2 +- .../unspilled/PartitionedLookupSource.java | 2 +- .../trino/operator/scalar/JsonFunctions.java | 4 +- .../trino/operator/scalar/MathFunctions.java | 2 +- .../timetz/VarcharToTimeWithTimeZoneCast.java | 4 +- .../HttpRequestSessionContextFactory.java | 2 +- .../java/io/trino/sql/analyzer/Analysis.java | 2 +- .../sql/analyzer/ExpressionAnalyzer.java | 6 +- .../trino/sql/analyzer/StatementAnalyzer.java | 8 +- .../trino/sql/planner/DomainTranslator.java | 6 +- .../trino/sql/planner/EqualityInference.java | 2 +- .../sql/planner/ExpressionInterpreter.java | 4 +- .../sql/planner/LocalExecutionPlanner.java | 2 +- .../planner/iterative/IterativeOptimizer.java | 2 +- .../iterative/rule/DereferencePushdown.java | 2 +- .../iterative/rule/GatherAndMergeWindows.java | 2 +- .../OptimizeMixedDistinctAggregations.java | 2 +- .../optimizations/PredicatePushDown.java | 4 +- .../optimizations/WindowFilterPushDown.java | 4 +- .../io/trino/sql/planner/plan/SampleNode.java | 2 +- .../planner/planprinter/GraphvizPrinter.java | 2 +- .../planprinter/WindowOperatorStats.java | 4 +- .../main/java/io/trino/type/DateTimes.java | 8 +- .../java/io/trino/type/VarcharOperators.java | 2 +- .../trino/util/Long2LongOpenBigHashMap.java | 192 +++++++++--------- .../LongLong2LongOpenCustomBigHashMap.java | 114 +++++------ .../src/main/java/io/trino/util/MoreMath.java | 4 +- .../ThreadLocalCompressorDecompressor.java | 2 +- .../trino/connector/MockConnectorFactory.java | 2 +- .../trino/cost/TestFilterStatsCalculator.java | 4 +- .../dispatcher/TestLocalDispatchQuery.java | 2 +- .../TestCreateTableColumnTypeCoercion.java | 2 +- .../execution/buffer/BenchmarkBlockSerde.java | 2 +- .../TestEventDrivenTaskSource.java | 8 +- .../operator/TestTableWriterOperator.java | 2 +- .../operator/join/TestHashJoinOperator.java | 24 +-- .../join/TestLookupJoinPageBuilder.java | 2 +- .../join/unspilled/TestHashJoinOperator.java | 24 +-- .../unspilled/TestLookupJoinPageBuilder.java | 2 +- .../BenchmarkJsonPathBinaryOperators.java | 12 +- .../operator/scalar/TestMathFunctions.java | 2 +- .../io/trino/sql/analyzer/TestAnalyzer.java | 4 +- .../TestConnectorExpressionTranslator.java | 2 +- .../trino/sql/planner/TestDomainCoercer.java | 2 +- .../assertions/NotPlanNodeMatcher.java | 2 +- .../rule/TestExpressionRewriteRuleSet.java | 4 +- ...mCorrelatedSingleRowSubqueryToProject.java | 2 +- .../optimizations/TestAddExchangesPlans.java | 4 +- .../TestAddExchangesScaledWriters.java | 2 +- ...ExchangesForPartitionedInsertAndMerge.java | 4 +- ...tAddLocalExchangesForTaskScaleWriters.java | 4 +- .../optimizations/TestColocatedJoin.java | 4 +- .../TestDeterminePartitionCount.java | 4 +- .../TestLimitMaxWriterNodesCount.java | 4 +- .../TestValidateScaledWritersUsage.java | 2 +- .../io/trino/type/TestArrayOperators.java | 2 +- .../java/io/trino/type/TestIpAddressType.java | 2 +- .../io/trino/sql/ExpressionFormatter.java | 2 +- .../src/main/java/io/trino/sql/QueryUtil.java | 2 +- .../main/java/io/trino/sql/SqlFormatter.java | 4 +- .../java/io/trino/sql/parser/AstBuilder.java | 4 +- .../io/trino/sql/tree/DescriptorField.java | 2 +- .../trino/spi/block/Int2IntOpenHashMap.java | 44 ++-- .../io/trino/spi/block/LongArrayBlock.java | 4 +- .../java/io/trino/spi/type/Int128Math.java | 2 +- .../io/trino/array/TestBlockBigArray.java | 2 +- .../filesystem/azure/AzureFileSystem.java | 4 +- .../io/trino/geospatial/GeometryUtils.java | 2 +- .../hive/formats/avro/AvroPageDataReader.java | 2 +- .../NativeLogicalTypesAvroTypeManager.java | 8 +- .../hive/formats/rcfile/RcFileReader.java | 2 +- .../trino/hive/formats/avro/TestAvroBase.java | 2 +- .../shaded/internal/util/GridUnsafe.java | 48 ++--- .../java/io/trino/orc/OrcWriteValidation.java | 2 +- .../orc/metadata/statistics/BloomFilter.java | 2 +- .../trino/orc/stream/BooleanInputStream.java | 2 +- .../trino/orc/stream/LongInputStreamV1.java | 2 +- .../trino/orc/stream/LongOutputStreamV2.java | 6 +- .../io/trino/orc/BenchmarkColumnReaders.java | 2 +- .../io/trino/orc/stream/TestLongStreamV1.java | 2 +- .../io/trino/parquet/ParquetTypeUtils.java | 4 +- .../decoder/avro/AvroRowDecoderFactory.java | 2 +- .../decoder/json/ISO8601JsonFieldDecoder.java | 2 +- .../json/TestDefaultJsonFieldDecoder.java | 4 +- .../java/io/trino/plugin/atop/AtopTable.java | 12 +- .../io/trino/plugin/jdbc/BaseJdbcClient.java | 2 +- .../plugin/bigquery/BigQueryMetadata.java | 2 +- .../plugin/bigquery/BigQueryProxyConfig.java | 2 +- .../bigquery/BigQueryQueryPageSource.java | 2 +- .../cassandra/CassandraClientConfig.java | 2 +- ...assandraClusteringPredicatesExtractor.java | 2 +- .../cassandra/TestJsonCassandraHandles.java | 4 +- .../clickhouse/BaseClickHouseTypeMapping.java | 2 +- .../deltalake/AbstractDeltaLakePageSink.java | 4 +- .../elasticsearch/ElasticsearchMetadata.java | 4 +- .../ElasticsearchQueryBuilder.java | 2 +- .../AzureBlobFileSystemExchangeStorage.java | 2 +- .../s3/S3FileSystemExchangeStorage.java | 8 +- .../trino/plugin/geospatial/GeoFunctions.java | 2 +- .../geospatial/TestSpatialJoinOperator.java | 4 +- .../trino/plugin/google/sheets/ptf/Sheet.java | 2 +- .../plugin/hive/HiveTableProperties.java | 2 +- .../io/trino/plugin/hive/MergeFileWriter.java | 8 +- .../plugin/hive/avro/HiveAvroTypeManager.java | 8 +- .../DecoratedHiveMetastoreModule.java | 2 +- .../metastore/thrift/ThriftHiveMetastore.java | 6 +- .../plugin/hive/AbstractTestHiveLocal.java | 4 +- .../hive/TestReaderProjectionsAdapter.java | 4 +- ...ransactionScopeCachingDirectoryLister.java | 2 +- .../metastore/glue/TestGlueHiveMetastore.java | 2 +- .../thrift/TestThriftSparkMetastoreUtil.java | 4 +- .../io/trino/plugin/hudi/files/FileSlice.java | 2 +- .../hudi/table/HudiTableFileSystemView.java | 2 +- .../plugin/ignite/TestingIgniteContainer.java | 2 +- .../plugin/jmx/JmxRecordSetProvider.java | 2 +- .../trino/plugin/kinesis/KinesisMetadata.java | 2 +- .../kinesis/s3config/S3TableConfigClient.java | 2 +- .../kinesis/util/MockKinesisClient.java | 6 +- .../plugin/kudu/KerberizedKuduClient.java | 2 +- .../oracle/BaseOracleConnectorTest.java | 2 +- .../TestSalesforceBasicAuthenticator.java | 10 +- .../salesforce/TestSalesforceConfig.java | 4 +- .../pinot/BasePinotConnectorSmokeTest.java | 2 +- .../plugin/postgresql/PostgreSqlClient.java | 4 +- .../io/trino/plugin/redis/RedisMetadata.java | 2 +- .../trino/plugin/redis/RedisRecordCursor.java | 2 +- .../DbResourceGroupConfigurationManager.java | 2 +- ...stDbSessionPropertyManagerIntegration.java | 2 +- .../plugin/sqlserver/SqlServerClient.java | 2 +- .../trino/plugin/tpch/TpchBucketFunction.java | 2 +- .../io/trino/plugin/tpch/TpchMetadata.java | 2 +- pom.xml | 1 + .../hive/TestHiveRedirectionToHudi.java | 4 +- .../TestIcebergSparkCompatibility.java | 12 +- .../services/ProgressLoggingListener.java | 2 +- .../TestRefreshMaterializedView.java | 4 +- .../trino/execution/TestTableRedirection.java | 20 +- .../io/trino/security/TestAccessControl.java | 10 +- .../TestAccessControlTableRedirection.java | 6 +- 156 files changed, 481 insertions(+), 480 deletions(-) diff --git a/client/trino-client/src/main/java/io/trino/client/uri/AbstractConnectionProperty.java b/client/trino-client/src/main/java/io/trino/client/uri/AbstractConnectionProperty.java index 9e0ca1ca4fff..a9f9f2151666 100644 --- a/client/trino-client/src/main/java/io/trino/client/uri/AbstractConnectionProperty.java +++ b/client/trino-client/src/main/java/io/trino/client/uri/AbstractConnectionProperty.java @@ -196,7 +196,7 @@ default Validator and(Validator other) { requireNonNull(other, "other is null"); // return the first non-empty optional - return (t) -> { + return t -> { Optional result = validate(t); if (result.isPresent()) { return result; diff --git a/client/trino-client/src/main/java/okhttp3/internal/tls/LegacyHostnameVerifier.java b/client/trino-client/src/main/java/okhttp3/internal/tls/LegacyHostnameVerifier.java index 5ac8aca7b769..89ccfdade01f 100644 --- a/client/trino-client/src/main/java/okhttp3/internal/tls/LegacyHostnameVerifier.java +++ b/client/trino-client/src/main/java/okhttp3/internal/tls/LegacyHostnameVerifier.java @@ -85,11 +85,11 @@ private boolean verifyHostname(String hostName, String pattern) { // Basic sanity checks // Check length == 0 instead of .isEmpty() to support Java 5. - if ((hostName == null) || (hostName.length() == 0) || (hostName.startsWith(".")) || (hostName.endsWith(".."))) { + if ((hostName == null) || (hostName.length() == 0) || hostName.startsWith(".") || hostName.endsWith("..")) { // Invalid domain name return false; } - if ((pattern == null) || (pattern.length() == 0) || (pattern.startsWith(".")) || (pattern.endsWith(".."))) { + if ((pattern == null) || (pattern.length() == 0) || pattern.startsWith(".") || pattern.endsWith("..")) { // Invalid pattern/domain name return false; } @@ -130,7 +130,7 @@ private boolean verifyHostname(String hostName, String pattern) // sub.test.example.com. // 3. Wildcard patterns for single-label domain names are not permitted. - if ((!pattern.startsWith("*.")) || (pattern.indexOf('*', 1) != -1)) { + if (!pattern.startsWith("*.") || (pattern.indexOf('*', 1) != -1)) { // Asterisk (*) is only permitted in the left-most domain name label and must be the only // character in that label return false; diff --git a/client/trino-jdbc/src/main/java/io/trino/jdbc/AbstractTrinoResultSet.java b/client/trino-jdbc/src/main/java/io/trino/jdbc/AbstractTrinoResultSet.java index 03a1a4cfaca3..ad42b3ddba4a 100644 --- a/client/trino-jdbc/src/main/java/io/trino/jdbc/AbstractTrinoResultSet.java +++ b/client/trino-jdbc/src/main/java/io/trino/jdbc/AbstractTrinoResultSet.java @@ -2112,8 +2112,8 @@ private static Time parseTimeWithTimeZone(String value) int minute = Integer.parseInt(matcher.group("minute")); int second = matcher.group("second") == null ? 0 : Integer.parseInt(matcher.group("second")); int offsetSign = matcher.group("sign").equals("+") ? 1 : -1; - int offsetHour = Integer.parseInt((matcher.group("offsetHour"))); - int offsetMinute = Integer.parseInt((matcher.group("offsetMinute"))); + int offsetHour = Integer.parseInt(matcher.group("offsetHour")); + int offsetMinute = Integer.parseInt(matcher.group("offsetMinute")); if (hour > 23 || minute > 59 || second > 59 || !isValidOffset(offsetHour, offsetMinute)) { throw new IllegalArgumentException("Invalid time with time zone: " + value); diff --git a/client/trino-jdbc/src/main/java/io/trino/jdbc/TypeConversions.java b/client/trino-jdbc/src/main/java/io/trino/jdbc/TypeConversions.java index 6ea4ee004107..b0db39288572 100644 --- a/client/trino-jdbc/src/main/java/io/trino/jdbc/TypeConversions.java +++ b/client/trino-jdbc/src/main/java/io/trino/jdbc/TypeConversions.java @@ -69,7 +69,7 @@ private Builder() {} public Builder add(String sourceRawType, Class sourceClass, Class target, SimpleTypeConversion conversion) { requireNonNull(conversion, "conversion is null"); - return add(sourceRawType, sourceClass, target, ((type, value) -> conversion.apply(value))); + return add(sourceRawType, sourceClass, target, (type, value) -> conversion.apply(value)); } public Builder add(String sourceRawType, Class sourceClass, Class target, TypeConversion conversion) diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDatabaseMetaData.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDatabaseMetaData.java index 584fe873db70..160c2be110ca 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDatabaseMetaData.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDatabaseMetaData.java @@ -165,11 +165,11 @@ public void testGetClientInfoProperties() .hasColumn(2, "MAX_LEN", Types.INTEGER) .hasColumn(3, "DEFAULT_VALUE", Types.VARCHAR) .hasColumn(4, "DESCRIPTION", Types.VARCHAR) - .hasRows((list( + .hasRows(list( list("ApplicationName", Integer.MAX_VALUE, null, null), list("ClientInfo", Integer.MAX_VALUE, null, null), list("ClientTags", Integer.MAX_VALUE, null, null), - list("TraceToken", Integer.MAX_VALUE, null, null)))); + list("TraceToken", Integer.MAX_VALUE, null, null))); } } } diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoResultSet.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoResultSet.java index af70c4e84bb2..a5bb3e041747 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoResultSet.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoResultSet.java @@ -69,7 +69,7 @@ public Iterable> next() catch (InterruptedException e) { interruptedButSwallowedLatch.countDown(); } - return ImmutableList.of((ImmutableList.of(new Object()))); + return ImmutableList.of(ImmutableList.of(new Object())); } }, new ArrayBlockingQueue<>(100)); @@ -107,7 +107,7 @@ public boolean hasNext() public Iterable> next() { thread.compareAndSet(null, Thread.currentThread()); - return ImmutableList.of((ImmutableList.of(new Object()))); + return ImmutableList.of(ImmutableList.of(new Object())); } }, queue); diff --git a/core/trino-main/src/main/java/io/trino/cost/TopNStatsRule.java b/core/trino-main/src/main/java/io/trino/cost/TopNStatsRule.java index defa2a3b6717..dfcf842a4cfd 100644 --- a/core/trino-main/src/main/java/io/trino/cost/TopNStatsRule.java +++ b/core/trino-main/src/main/java/io/trino/cost/TopNStatsRule.java @@ -89,7 +89,7 @@ protected Optional doCalculate(TopNNode node, StatsProvid } } else { - double nonNullCount = (rowCount - nullCount); + double nonNullCount = rowCount - nullCount; if (nonNullCount > limitCount) { newStats.setNullsFraction(0.0); } diff --git a/core/trino-main/src/main/java/io/trino/execution/SplitAssignment.java b/core/trino-main/src/main/java/io/trino/execution/SplitAssignment.java index 77cf862590d8..4a1cd8d14328 100644 --- a/core/trino-main/src/main/java/io/trino/execution/SplitAssignment.java +++ b/core/trino-main/src/main/java/io/trino/execution/SplitAssignment.java @@ -87,7 +87,7 @@ private boolean isNewer(SplitAssignment assignment) // the specified assignment is newer if it changes the no more // splits flag or if it contains new splits return (!noMoreSplits && assignment.isNoMoreSplits()) || - (!splits.containsAll(assignment.getSplits())); + !splits.containsAll(assignment.getSplits()); } @Override diff --git a/core/trino-main/src/main/java/io/trino/execution/buffer/PageCodecMarker.java b/core/trino-main/src/main/java/io/trino/execution/buffer/PageCodecMarker.java index 972e0fd5ba9a..0c96e94f495a 100644 --- a/core/trino-main/src/main/java/io/trino/execution/buffer/PageCodecMarker.java +++ b/core/trino-main/src/main/java/io/trino/execution/buffer/PageCodecMarker.java @@ -33,7 +33,7 @@ enum PageCodecMarker PageCodecMarker(int bit) { checkArgument(bit > 0 && bit <= 8, "PageCodecMarker bit must be between 1 and 8. Found: %s", bit); - this.mask = (1 << (bit - 1)); + this.mask = 1 << (bit - 1); } public boolean isSet(byte value) @@ -48,7 +48,7 @@ public byte set(byte value) public byte unset(byte value) { - return (byte) (Byte.toUnsignedInt(value) & (~mask)); + return (byte) (Byte.toUnsignedInt(value) & ~mask); } /** diff --git a/core/trino-main/src/main/java/io/trino/execution/buffer/PageDeserializer.java b/core/trino-main/src/main/java/io/trino/execution/buffer/PageDeserializer.java index 062396000d24..9fb386d00383 100644 --- a/core/trino-main/src/main/java/io/trino/execution/buffer/PageDeserializer.java +++ b/core/trino-main/src/main/java/io/trino/execution/buffer/PageDeserializer.java @@ -424,7 +424,7 @@ private void decompress() private static int getCompressedBlockSize(int compressedBlockMarker) { - return compressedBlockMarker & (~SERIALIZED_PAGE_COMPRESSED_BLOCK_MASK); + return compressedBlockMarker & ~SERIALIZED_PAGE_COMPRESSED_BLOCK_MASK; } private static boolean isCompressed(int compressedBlockMarker) diff --git a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java index 4af6fe5d9f18..069b0e638283 100644 --- a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java +++ b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java @@ -1696,7 +1696,7 @@ public void addPartition(int partitionId, NodeRequirements nodeRequirements) initialMemoryRequirements, maxTaskExecutionAttempts); checkState(partitions.putIfAbsent(partitionId, partition) == null, "partition with id %s already exist in stage %s", partitionId, stage.getStageId()); - getSourceOutputSelectors().forEach((partition::updateExchangeSourceOutputSelector)); + getSourceOutputSelectors().forEach(partition::updateExchangeSourceOutputSelector); remainingPartitions.add(partitionId); } diff --git a/core/trino-main/src/main/java/io/trino/json/PathEvaluationVisitor.java b/core/trino-main/src/main/java/io/trino/json/PathEvaluationVisitor.java index 83fe30693b60..703149dc0d1a 100644 --- a/core/trino-main/src/main/java/io/trino/json/PathEvaluationVisitor.java +++ b/core/trino-main/src/main/java/io/trino/json/PathEvaluationVisitor.java @@ -453,14 +453,14 @@ protected List visitIrArrayAccessor(IrArrayAccessor node, PathEvaluation elements = ImmutableList.copyOf(((JsonNode) object).elements()); } else if (lax) { - elements = ImmutableList.of((object)); + elements = ImmutableList.of(object); } else { throw itemTypeError("ARRAY", ((JsonNode) object).getNodeType().name()); } } else if (lax) { - elements = ImmutableList.of((object)); + elements = ImmutableList.of(object); } else { throw itemTypeError("ARRAY", ((TypedValue) object).getType().getDisplayName()); @@ -522,7 +522,7 @@ private static long asArrayIndex(Object object) { if (object instanceof JsonNode jsonNode) { if (jsonNode.getNodeType() != JsonNodeType.NUMBER) { - throw itemTypeError("NUMBER", (jsonNode.getNodeType().name())); + throw itemTypeError("NUMBER", jsonNode.getNodeType().name()); } if (!jsonNode.canConvertToLong()) { throw new PathEvaluationException(format("cannot convert value %s to long", jsonNode)); diff --git a/core/trino-main/src/main/java/io/trino/memory/ClusterMemoryManager.java b/core/trino-main/src/main/java/io/trino/memory/ClusterMemoryManager.java index 629dcafd3dad..a36e04cdec8b 100644 --- a/core/trino-main/src/main/java/io/trino/memory/ClusterMemoryManager.java +++ b/core/trino-main/src/main/java/io/trino/memory/ClusterMemoryManager.java @@ -392,7 +392,7 @@ private String formatKillScenario(Map nodes) stringBuilder.append("MaxBytes ").append(memoryPoolInfo.getMaxBytes()).append(' '); stringBuilder.append("FreeBytes ").append(memoryPoolInfo.getFreeBytes() + memoryPoolInfo.getReservedRevocableBytes()).append(' '); stringBuilder.append("Queries "); - Joiner.on(",").withKeyValueSeparator("=").appendTo(stringBuilder, memoryPoolInfo.getQueryMemoryReservations()).append((' ')); + Joiner.on(",").withKeyValueSeparator("=").appendTo(stringBuilder, memoryPoolInfo.getQueryMemoryReservations()).append(' '); stringBuilder.append("Tasks "); Joiner.on(",").withKeyValueSeparator("=").appendTo(stringBuilder, memoryPoolInfo.getTaskMemoryReservations()); stringBuilder.append('\n'); diff --git a/core/trino-main/src/main/java/io/trino/operator/SpatialIndexBuilderOperator.java b/core/trino-main/src/main/java/io/trino/operator/SpatialIndexBuilderOperator.java index 6dec3045941b..f436983bb6f1 100644 --- a/core/trino-main/src/main/java/io/trino/operator/SpatialIndexBuilderOperator.java +++ b/core/trino-main/src/main/java/io/trino/operator/SpatialIndexBuilderOperator.java @@ -199,7 +199,7 @@ public void addInput(Page page) index.addPage(page); - if (!localUserMemoryContext.trySetBytes((index.getEstimatedSize().toBytes()))) { + if (!localUserMemoryContext.trySetBytes(index.getEstimatedSize().toBytes())) { index.compact(); localUserMemoryContext.setBytes(index.getEstimatedSize().toBytes()); } diff --git a/core/trino-main/src/main/java/io/trino/operator/TopNPeerGroupLookup.java b/core/trino-main/src/main/java/io/trino/operator/TopNPeerGroupLookup.java index 6ca4eee989ec..a183fbde3396 100644 --- a/core/trino-main/src/main/java/io/trino/operator/TopNPeerGroupLookup.java +++ b/core/trino-main/src/main/java/io/trino/operator/TopNPeerGroupLookup.java @@ -281,7 +281,7 @@ private void shiftKeys(long index) long currentHash; long initialIndex = index; - index = ((index) + 1) & mask; + index = (index + 1) & mask; while (true) { if (buffer.isEmptySlot(index)) { buffer.clear(initialIndex); diff --git a/core/trino-main/src/main/java/io/trino/operator/aggregation/AbstractMapAggregationState.java b/core/trino-main/src/main/java/io/trino/operator/aggregation/AbstractMapAggregationState.java index e410366b95db..4d4bfc76934f 100644 --- a/core/trino-main/src/main/java/io/trino/operator/aggregation/AbstractMapAggregationState.java +++ b/core/trino-main/src/main/java/io/trino/operator/aggregation/AbstractMapAggregationState.java @@ -133,7 +133,7 @@ public AbstractMapAggregationState( boolean variableWidth = keyType.isFlatVariableWidth() || valueType.isFlatVariableWidth(); variableWidthData = variableWidth ? new VariableWidthData() : null; if (grouped) { - recordGroupIdOffset = (variableWidth ? POINTER_SIZE : 0); + recordGroupIdOffset = variableWidth ? POINTER_SIZE : 0; recordNextIndexOffset = recordGroupIdOffset + Integer.BYTES; recordKeyOffset = recordNextIndexOffset + Integer.BYTES; } @@ -141,7 +141,7 @@ public AbstractMapAggregationState( // use MIN_VALUE so that when it is added to the record offset we get a negative value, and thus an ArrayIndexOutOfBoundsException recordGroupIdOffset = Integer.MIN_VALUE; recordNextIndexOffset = Integer.MIN_VALUE; - recordKeyOffset = (variableWidth ? POINTER_SIZE : 0); + recordKeyOffset = variableWidth ? POINTER_SIZE : 0; } recordValueNullOffset = recordKeyOffset + keyType.getFlatFixedSize(); recordValueOffset = recordValueNullOffset + 1; @@ -544,7 +544,7 @@ private boolean keyNotDistinctFrom(int leftPosition, ValueBlock right, int right private static long repeat(byte value) { - return ((value & 0xFF) * 0x01_01_01_01_01_01_01_01L); + return (value & 0xFF) * 0x01_01_01_01_01_01_01_01L; } private static long match(long vector, long repeatedValue) diff --git a/core/trino-main/src/main/java/io/trino/operator/index/IndexLoader.java b/core/trino-main/src/main/java/io/trino/operator/index/IndexLoader.java index 2eef30a052d3..c23a357edda4 100644 --- a/core/trino-main/src/main/java/io/trino/operator/index/IndexLoader.java +++ b/core/trino-main/src/main/java/io/trino/operator/index/IndexLoader.java @@ -344,7 +344,7 @@ public boolean load(List requests) } // Generate a RecordSet that presents unique index keys that have not been cached - UnloadedIndexKeyRecordSet indexKeysRecordSet = (lookupSourceInputChannels.equals(allInputChannels)) + UnloadedIndexKeyRecordSet indexKeysRecordSet = lookupSourceInputChannels.equals(allInputChannels) ? recordSetForLookupSource : new UnloadedIndexKeyRecordSet(pipelineContext.getSession(), indexSnapshotReference.get(), allInputChannels, indexTypes, requests, joinCompiler); diff --git a/core/trino-main/src/main/java/io/trino/operator/join/PartitionedLookupSource.java b/core/trino-main/src/main/java/io/trino/operator/join/PartitionedLookupSource.java index 3ad6e7268b6b..ed99ffa0b849 100644 --- a/core/trino-main/src/main/java/io/trino/operator/join/PartitionedLookupSource.java +++ b/core/trino-main/src/main/java/io/trino/operator/join/PartitionedLookupSource.java @@ -210,7 +210,7 @@ private int decodeJoinPosition(long partitionedJoinPosition) private long encodePartitionedJoinPosition(int partition, int joinPosition) { - return (((long) joinPosition) << shiftSize) | (partition); + return (((long) joinPosition) << shiftSize) | partition; } private static class PartitionedLookupOuterPositionIterator diff --git a/core/trino-main/src/main/java/io/trino/operator/join/unspilled/PartitionedLookupSource.java b/core/trino-main/src/main/java/io/trino/operator/join/unspilled/PartitionedLookupSource.java index 9708ae7092e4..b5d671924fdb 100644 --- a/core/trino-main/src/main/java/io/trino/operator/join/unspilled/PartitionedLookupSource.java +++ b/core/trino-main/src/main/java/io/trino/operator/join/unspilled/PartitionedLookupSource.java @@ -269,7 +269,7 @@ private int decodeJoinPosition(long partitionedJoinPosition) private long encodePartitionedJoinPosition(int partition, int joinPosition) { - return (((long) joinPosition) << shiftSize) | (partition); + return (((long) joinPosition) << shiftSize) | partition; } private static class PartitionedLookupOuterPositionIterator diff --git a/core/trino-main/src/main/java/io/trino/operator/scalar/JsonFunctions.java b/core/trino-main/src/main/java/io/trino/operator/scalar/JsonFunctions.java index 0bcda5aa9254..0569400bcee9 100644 --- a/core/trino-main/src/main/java/io/trino/operator/scalar/JsonFunctions.java +++ b/core/trino-main/src/main/java/io/trino/operator/scalar/JsonFunctions.java @@ -199,7 +199,7 @@ public static Boolean jsonArrayContains(@SqlType(StandardTypes.JSON) Slice json, parser.skipChildren(); if (((token == VALUE_TRUE) && value) || - ((token == VALUE_FALSE) && (!value))) { + ((token == VALUE_FALSE) && !value)) { return true; } } @@ -285,7 +285,7 @@ public static Boolean jsonArrayContains(@SqlType(StandardTypes.JSON) Slice json, // noinspection FloatingPointEquality if ((token == VALUE_NUMBER_FLOAT) && (parser.getDoubleValue() == value) && - (Doubles.isFinite(parser.getDoubleValue()))) { + Doubles.isFinite(parser.getDoubleValue())) { return true; } } diff --git a/core/trino-main/src/main/java/io/trino/operator/scalar/MathFunctions.java b/core/trino-main/src/main/java/io/trino/operator/scalar/MathFunctions.java index a427da713631..ad3ff0c012d1 100644 --- a/core/trino-main/src/main/java/io/trino/operator/scalar/MathFunctions.java +++ b/core/trino-main/src/main/java/io/trino/operator/scalar/MathFunctions.java @@ -1150,7 +1150,7 @@ public static double sign(@SqlType(StandardTypes.DOUBLE) double num) @SqlType(StandardTypes.REAL) public static long signFloat(@SqlType(StandardTypes.REAL) long num) { - return floatToRawIntBits((Math.signum(intBitsToFloat((int) num)))); + return floatToRawIntBits(Math.signum(intBitsToFloat((int) num))); } @Description("Sine") diff --git a/core/trino-main/src/main/java/io/trino/operator/scalar/timetz/VarcharToTimeWithTimeZoneCast.java b/core/trino-main/src/main/java/io/trino/operator/scalar/timetz/VarcharToTimeWithTimeZoneCast.java index 23a197830fa5..b778ef6135ac 100644 --- a/core/trino-main/src/main/java/io/trino/operator/scalar/timetz/VarcharToTimeWithTimeZoneCast.java +++ b/core/trino-main/src/main/java/io/trino/operator/scalar/timetz/VarcharToTimeWithTimeZoneCast.java @@ -112,8 +112,8 @@ private static int parseOffset(ConnectorSession session, Matcher matcher) { if (matcher.group("offsetHour") != null && matcher.group("offsetMinute") != null) { int offsetSign = matcher.group("sign").equals("+") ? 1 : -1; - int offsetHour = Integer.parseInt((matcher.group("offsetHour"))); - int offsetMinute = Integer.parseInt((matcher.group("offsetMinute"))); + int offsetHour = Integer.parseInt(matcher.group("offsetHour")); + int offsetMinute = Integer.parseInt(matcher.group("offsetMinute")); if (!isValidOffset(offsetHour, offsetMinute)) { throw new IllegalArgumentException("Invalid time"); diff --git a/core/trino-main/src/main/java/io/trino/server/HttpRequestSessionContextFactory.java b/core/trino-main/src/main/java/io/trino/server/HttpRequestSessionContextFactory.java index acaf7874a666..cf821e762210 100644 --- a/core/trino-main/src/main/java/io/trino/server/HttpRequestSessionContextFactory.java +++ b/core/trino-main/src/main/java/io/trino/server/HttpRequestSessionContextFactory.java @@ -102,7 +102,7 @@ public SessionContext createSessionContext( Optional catalog = Optional.ofNullable(trimEmptyToNull(headers.getFirst(protocolHeaders.requestCatalog()))); Optional schema = Optional.ofNullable(trimEmptyToNull(headers.getFirst(protocolHeaders.requestSchema()))); Optional path = Optional.ofNullable(trimEmptyToNull(headers.getFirst(protocolHeaders.requestPath()))); - assertRequest((catalog.isPresent()) || (schema.isEmpty()), "Schema is set but catalog is not"); + assertRequest(catalog.isPresent() || schema.isEmpty(), "Schema is set but catalog is not"); requireNonNull(authenticatedIdentity, "authenticatedIdentity is null"); Identity identity = buildSessionIdentity(authenticatedIdentity, protocolHeaders, headers); diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/Analysis.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/Analysis.java index cb25fc769147..96b29a1b31cd 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/Analysis.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/Analysis.java @@ -1776,7 +1776,7 @@ public MergeAnalysis( this.mergeRowType = requireNonNull(mergeRowType, "mergeRowType is null"); this.insertLayout = requireNonNull(insertLayout, "insertLayout is null"); this.updateLayout = requireNonNull(updateLayout, "updateLayout is null"); - this.insertPartitioningArgumentIndexes = (requireNonNull(insertPartitioningArgumentIndexes, "insertPartitioningArgumentIndexes is null")); + this.insertPartitioningArgumentIndexes = requireNonNull(insertPartitioningArgumentIndexes, "insertPartitioningArgumentIndexes is null"); this.targetTableScope = requireNonNull(targetTableScope, "targetTableScope is null"); this.joinScope = requireNonNull(joinScope, "joinScope is null"); } diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/ExpressionAnalyzer.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/ExpressionAnalyzer.java index 4b3fab8618f4..0ef7db9ef384 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/ExpressionAnalyzer.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/ExpressionAnalyzer.java @@ -2085,7 +2085,7 @@ protected Type visitExtract(Extract node, StackableAstVisitorContext co if (!(type instanceof DateType) && !(type instanceof TimestampType) && !(type instanceof TimestampWithTimeZoneType) && - !(type.equals(INTERVAL_YEAR_MONTH))) { + !type.equals(INTERVAL_YEAR_MONTH)) { throw semanticException(TYPE_MISMATCH, node.getExpression(), "Cannot extract %s from %s", field, type); } break; @@ -2093,7 +2093,7 @@ protected Type visitExtract(Extract node, StackableAstVisitorContext co if (!(type instanceof DateType) && !(type instanceof TimestampType) && !(type instanceof TimestampWithTimeZoneType) && - !(type.equals(INTERVAL_DAY_TIME))) { + !type.equals(INTERVAL_DAY_TIME)) { throw semanticException(TYPE_MISMATCH, node.getExpression(), "Cannot extract %s from %s", field, type); } break; @@ -2109,7 +2109,7 @@ protected Type visitExtract(Extract node, StackableAstVisitorContext co !(type instanceof TimestampWithTimeZoneType) && !(type instanceof TimeType) && !(type instanceof TimeWithTimeZoneType) && - !(type.equals(INTERVAL_DAY_TIME))) { + !type.equals(INTERVAL_DAY_TIME)) { throw semanticException(TYPE_MISMATCH, node.getExpression(), "Cannot extract %s from %s", field, type); } break; diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java index 53bebde8bc9f..faf80eed5418 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java @@ -4238,7 +4238,7 @@ private Scope computeAndAssignOutputScope(QuerySpecification node, Optional name; if (!allColumns.getAliases().isEmpty()) { - name = Optional.of((allColumns.getAliases().get(i)).getCanonicalValue()); + name = Optional.of(allColumns.getAliases().get(i).getCanonicalValue()); } else { name = field.getName(); @@ -4468,7 +4468,7 @@ private void analyzeAllColumnsFromTable( Optional alias = field.getName(); if (!allColumns.getAliases().isEmpty()) { - alias = Optional.of((allColumns.getAliases().get(i)).getValue()); + alias = Optional.of(allColumns.getAliases().get(i).getValue()); } Field newField = new Field( @@ -4519,13 +4519,13 @@ private void analyzeAllFieldsFromRowTypeExpression( unfoldedExpressionsBuilder.add(outputExpression); Type outputExpressionType = type.getTypeParameters().get(i); - if (node.getSelect().isDistinct() && !(outputExpressionType.isComparable())) { + if (node.getSelect().isDistinct() && !outputExpressionType.isComparable()) { throw semanticException(TYPE_MISMATCH, node.getSelect(), "DISTINCT can only be applied to comparable types (actual: %s)", type.getTypeParameters().get(i)); } Optional name = ((RowType) type).getFields().get(i).getName(); if (!allColumns.getAliases().isEmpty()) { - name = Optional.of((allColumns.getAliases().get(i)).getValue()); + name = Optional.of(allColumns.getAliases().get(i).getValue()); } itemOutputFieldBuilder.add(Field.newUnqualified(name, outputExpressionType)); } diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/DomainTranslator.java b/core/trino-main/src/main/java/io/trino/sql/planner/DomainTranslator.java index f9bdb00f37c1..c53942b4f6d8 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/DomainTranslator.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/DomainTranslator.java @@ -641,7 +641,7 @@ private Optional createVarcharCastToDateComparisonExtractionRe return Optional.empty(); } - LocalDate date = LocalDate.ofEpochDay(((long) value.getValue())); + LocalDate date = LocalDate.ofEpochDay((long) value.getValue()); if (date.getYear() < 1001 || date.getYear() > 9998) { // Edge cases. 1-year margin so that we can go to next/prev year for < or > comparisons return Optional.empty(); @@ -662,7 +662,7 @@ private Optional createVarcharCastToDateComparisonExtractionRe return Optional.empty(); } valueSet = ValueSet.all(sourceType).subtract(dateStringRanges(date, sourceType)); - nullAllowed = (operator == IS_DISTINCT_FROM); + nullAllowed = operator == IS_DISTINCT_FROM; break; case LESS_THAN: case LESS_THAN_OR_EQUAL: @@ -1016,7 +1016,7 @@ private Optional processSimpleInPredicate(InPredicate node, Bo return Optional.of(new ExtractionResult(TupleDomain.all(), node)); } // in case of NOT IN, expression on the right side still allows determining values that are *not* part of the final domain - excludedExpressions.add(((Expression) value)); + excludedExpressions.add((Expression) value); continue; } if (isFloatingPointNaN(type, value)) { diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/EqualityInference.java b/core/trino-main/src/main/java/io/trino/sql/planner/EqualityInference.java index 18ce357056ab..a9cfc0289d35 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/EqualityInference.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/EqualityInference.java @@ -116,7 +116,7 @@ public EqualityInference(Metadata metadata, Collection expressions) // 2) Prefer smaller expression trees // 3) Sort the expressions alphabetically - creates a stable consistent ordering (extremely useful for unit testing) // TODO: be more precise in determining the cost of an expression - .comparingInt((ToIntFunction) (expression -> extractAllSymbols(expression).size())) + .comparingInt((ToIntFunction) expression -> extractAllSymbols(expression).size()) .thenComparingLong(expression -> extractSubExpressions(expression).size()) .thenComparing(Expression::toString); diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/ExpressionInterpreter.java b/core/trino-main/src/main/java/io/trino/sql/planner/ExpressionInterpreter.java index 9ef8e33ce66a..ebfb26d7209e 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/ExpressionInterpreter.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/ExpressionInterpreter.java @@ -204,7 +204,7 @@ public ExpressionInterpreter(Expression expression, PlannerContext plannerContex this.session = requireNonNull(session, "session is null"); this.connectorSession = session.toConnectorSession(); this.expressionTypes = ImmutableMap.copyOf(requireNonNull(expressionTypes, "expressionTypes is null")); - verify((expressionTypes.containsKey(NodeRef.of(expression)))); + verify(expressionTypes.containsKey(NodeRef.of(expression))); this.functionInvoker = new InterpretedFunctionInvoker(plannerContext.getFunctionManager()); this.typeCoercion = new TypeCoercion(plannerContext.getTypeManager()::getType); } @@ -721,7 +721,7 @@ else if (!found && result) { .filter(expression -> isDeterministic(expression, metadata)) .distinct(), expressionValues.stream() - .filter((expression -> !isDeterministic(expression, metadata)))) + .filter(expression -> !isDeterministic(expression, metadata))) .collect(toImmutableList()); if (simplifiedExpressionValues.size() == 1) { diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/LocalExecutionPlanner.java b/core/trino-main/src/main/java/io/trino/sql/planner/LocalExecutionPlanner.java index a4d67d8e88d4..fe5bab206118 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/LocalExecutionPlanner.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/LocalExecutionPlanner.java @@ -3907,7 +3907,7 @@ private List> makeLambdaProviders(List lambda LambdaDefinitionExpression lambda = (LambdaDefinitionExpression) toRowExpression(lambdaExpression, expressionTypes, ImmutableMap.of()); Class> lambdaProviderClass = compileLambdaProvider(lambda, plannerContext.getFunctionManager(), lambdaInterfaces.get(i)); try { - lambdaProviders.add((lambdaProviderClass.getConstructor(ConnectorSession.class).newInstance(session.toConnectorSession()))); + lambdaProviders.add(lambdaProviderClass.getConstructor(ConnectorSession.class).newInstance(session.toConnectorSession())); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/IterativeOptimizer.java b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/IterativeOptimizer.java index e3e0853571a7..055e4e9c0130 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/IterativeOptimizer.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/IterativeOptimizer.java @@ -362,7 +362,7 @@ public Context( public void checkTimeoutNotExhausted() { - if ((NANOSECONDS.toMillis(nanoTime() - startTimeInNanos)) >= timeoutInMilliseconds) { + if (NANOSECONDS.toMillis(nanoTime() - startTimeInNanos) >= timeoutInMilliseconds) { String message = format("The optimizer exhausted the time limit of %d ms", timeoutInMilliseconds); List topRulesByTime = iterativeOptimizerStatsCollector.getTopRuleStats(5); if (topRulesByTime.isEmpty()) { diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/DereferencePushdown.java b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/DereferencePushdown.java index 464e96aba68f..9b1454d9120f 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/DereferencePushdown.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/DereferencePushdown.java @@ -119,7 +119,7 @@ private static boolean isRowSubscriptChain(SubscriptExpression expression, Sessi } return (expression.getBase() instanceof SymbolReference) || - ((expression.getBase() instanceof SubscriptExpression) && isRowSubscriptChain((SubscriptExpression) (expression.getBase()), session, typeAnalyzer, types)); + ((expression.getBase() instanceof SubscriptExpression) && isRowSubscriptChain((SubscriptExpression) expression.getBase(), session, typeAnalyzer, types)); } private static boolean prefixExists(Expression expression, Set expressions) diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/GatherAndMergeWindows.java b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/GatherAndMergeWindows.java index c72e921438c9..048125c7adc9 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/GatherAndMergeWindows.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/GatherAndMergeWindows.java @@ -215,7 +215,7 @@ public SwapAdjacentWindowsBySpecifications(int numProjects) @Override protected Optional manipulateAdjacentWindowNodes(WindowNode parent, WindowNode child, Context context) { - if ((compare(parent, child) < 0) && (!dependsOn(parent, child))) { + if ((compare(parent, child) < 0) && !dependsOn(parent, child)) { PlanNode transposedWindows = transpose(parent, child); return Optional.of( restrictOutputs(context.getIdAllocator(), transposedWindows, ImmutableSet.copyOf(parent.getOutputSymbols())) diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/OptimizeMixedDistinctAggregations.java b/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/OptimizeMixedDistinctAggregations.java index 991ea2f25b8f..54d37d8a0116 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/OptimizeMixedDistinctAggregations.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/OptimizeMixedDistinctAggregations.java @@ -413,7 +413,7 @@ private GroupIdNode createGroupIdNode( groups, allSymbols.stream().collect(Collectors.toMap( symbol -> symbol, - symbol -> (symbol.equals(duplicatedDistinctSymbol) ? distinctSymbol : symbol))), + symbol -> symbol.equals(duplicatedDistinctSymbol) ? distinctSymbol : symbol)), ImmutableList.of(), groupSymbol); } diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/PredicatePushDown.java b/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/PredicatePushDown.java index bc4338bf5151..e4bb453baf72 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/PredicatePushDown.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/PredicatePushDown.java @@ -511,8 +511,8 @@ public PlanNode visitJoin(JoinNode node, RewriteContext context) ComparisonExpression equality = (ComparisonExpression) conjunct; boolean alignedComparison = node.getLeft().getOutputSymbols().containsAll(extractUnique(equality.getLeft())); - Expression leftExpression = (alignedComparison) ? equality.getLeft() : equality.getRight(); - Expression rightExpression = (alignedComparison) ? equality.getRight() : equality.getLeft(); + Expression leftExpression = alignedComparison ? equality.getLeft() : equality.getRight(); + Expression rightExpression = alignedComparison ? equality.getRight() : equality.getLeft(); Symbol leftSymbol = symbolForExpression(leftExpression); if (!node.getLeft().getOutputSymbols().contains(leftSymbol)) { diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/WindowFilterPushDown.java b/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/WindowFilterPushDown.java index d4f4da8de30b..82bbdfeee292 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/WindowFilterPushDown.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/optimizations/WindowFilterPushDown.java @@ -150,7 +150,7 @@ public PlanNode visitLimit(LimitNode node, RewriteContext context) PlanNode source = context.rewrite(node.getSource()); int limit = toIntExact(node.getCount()); if (source instanceof RowNumberNode) { - RowNumberNode rowNumberNode = mergeLimit(((RowNumberNode) source), limit); + RowNumberNode rowNumberNode = mergeLimit((RowNumberNode) source, limit); if (rowNumberNode.getPartitionBy().isEmpty()) { return rowNumberNode; } @@ -184,7 +184,7 @@ public PlanNode visitFilter(FilterNode node, RewriteContext context) if (upperBound.getAsInt() <= 0) { return new ValuesNode(node.getId(), node.getOutputSymbols(), ImmutableList.of()); } - source = mergeLimit(((RowNumberNode) source), upperBound.getAsInt()); + source = mergeLimit((RowNumberNode) source, upperBound.getAsInt()); return rewriteFilterSource(node, source, rowNumberSymbol, ((RowNumberNode) source).getMaxRowCountPerPartition().get()); } } diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/plan/SampleNode.java b/core/trino-main/src/main/java/io/trino/sql/planner/plan/SampleNode.java index 01bfb64bb730..90f4094bc353 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/plan/SampleNode.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/plan/SampleNode.java @@ -58,7 +58,7 @@ public SampleNode( super(id); checkArgument(sampleRatio >= 0.0, "sample ratio must be greater than or equal to 0"); - checkArgument((sampleRatio <= 1.0), "sample ratio must be less than or equal to 1"); + checkArgument(sampleRatio <= 1.0, "sample ratio must be less than or equal to 1"); this.sampleType = requireNonNull(sampleType, "sampleType is null"); this.source = requireNonNull(source, "source is null"); diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/GraphvizPrinter.java b/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/GraphvizPrinter.java index a04795eb2849..32d933947d2f 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/GraphvizPrinter.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/GraphvizPrinter.java @@ -726,7 +726,7 @@ public String getNodeId(PlanNode from) planNodeIds.put(from, idCount); nodeId = idCount; } - return ("plannode_" + nodeId); + return "plannode_" + nodeId; } } } diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/WindowOperatorStats.java b/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/WindowOperatorStats.java index 1d81aff13833..f8868aa3d634 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/WindowOperatorStats.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/planprinter/WindowOperatorStats.java @@ -72,8 +72,8 @@ public static WindowOperatorStats create(WindowInfo info) sizeOfIndexesSumSquaredDiffs += driverWindowInfo.getSumSquaredDifferencesSizeOfIndex(); totalIndexesCount += numberOfIndexes; - indexCountPerDriverSumSquaredDiffs += (Math.pow(numberOfIndexes - averageNumberOfIndexes, 2)); - rowCountPerDriverSumSquaredDiffs += (Math.pow(driverTotalRowsCount - averageNumberOfRows, 2)); + indexCountPerDriverSumSquaredDiffs += Math.pow(numberOfIndexes - averageNumberOfIndexes, 2); + rowCountPerDriverSumSquaredDiffs += Math.pow(driverTotalRowsCount - averageNumberOfRows, 2); activeDrivers++; } } diff --git a/core/trino-main/src/main/java/io/trino/type/DateTimes.java b/core/trino-main/src/main/java/io/trino/type/DateTimes.java index 051b004d3e7c..bb6ad4514926 100644 --- a/core/trino-main/src/main/java/io/trino/type/DateTimes.java +++ b/core/trino-main/src/main/java/io/trino/type/DateTimes.java @@ -580,8 +580,8 @@ public static long parseShortTimeWithTimeZone(String value) int minute = Integer.parseInt(matcher.group("minute")); int second = matcher.group("second") == null ? 0 : Integer.parseInt(matcher.group("second")); int offsetSign = matcher.group("sign").equals("+") ? 1 : -1; - int offsetHour = Integer.parseInt((matcher.group("offsetHour"))); - int offsetMinute = Integer.parseInt((matcher.group("offsetMinute"))); + int offsetHour = Integer.parseInt(matcher.group("offsetHour")); + int offsetMinute = Integer.parseInt(matcher.group("offsetMinute")); if (hour > 23 || minute > 59 || second > 59 || !isValidOffset(offsetHour, offsetMinute)) { throw new IllegalArgumentException("Invalid TIME WITH TIME ZONE: " + value); @@ -610,8 +610,8 @@ public static LongTimeWithTimeZone parseLongTimeWithTimeZone(String value) int minute = Integer.parseInt(matcher.group("minute")); int second = matcher.group("second") == null ? 0 : Integer.parseInt(matcher.group("second")); int offsetSign = matcher.group("sign").equals("+") ? 1 : -1; - int offsetHour = Integer.parseInt((matcher.group("offsetHour"))); - int offsetMinute = Integer.parseInt((matcher.group("offsetMinute"))); + int offsetHour = Integer.parseInt(matcher.group("offsetHour")); + int offsetMinute = Integer.parseInt(matcher.group("offsetMinute")); if (hour > 23 || minute > 59 || second > 59 || !isValidOffset(offsetHour, offsetMinute)) { throw new IllegalArgumentException("Invalid TIME WITH TIME ZONE: " + value); diff --git a/core/trino-main/src/main/java/io/trino/type/VarcharOperators.java b/core/trino-main/src/main/java/io/trino/type/VarcharOperators.java index 16c800fae951..2eaafacd4ad0 100644 --- a/core/trino-main/src/main/java/io/trino/type/VarcharOperators.java +++ b/core/trino-main/src/main/java/io/trino/type/VarcharOperators.java @@ -62,7 +62,7 @@ public static boolean castToBoolean(@SqlType("varchar(x)") Slice value) private static byte toUpperCase(byte b) { - return isLowerCase(b) ? ((byte) (b - 32)) : b; + return isLowerCase(b) ? (byte) (b - 32) : b; } private static boolean isLowerCase(byte b) diff --git a/core/trino-main/src/main/java/io/trino/util/Long2LongOpenBigHashMap.java b/core/trino-main/src/main/java/io/trino/util/Long2LongOpenBigHashMap.java index 8b53cb7fdb6a..2cb3f2e4f4bb 100644 --- a/core/trino-main/src/main/java/io/trino/util/Long2LongOpenBigHashMap.java +++ b/core/trino-main/src/main/java/io/trino/util/Long2LongOpenBigHashMap.java @@ -256,27 +256,27 @@ public void putAll(Map m) private long find(final long k) { - if (((k) == (0))) { + if (k == 0) { return containsNullKey ? n : -(n + 1); } final LongBigArray key = this.key; // The starting point. - long pos = it.unimi.dsi.fastutil.HashCommon.mix((k)) & mask; + long pos = it.unimi.dsi.fastutil.HashCommon.mix(k) & mask; long curr = key.get(pos); - if (((curr) == (0))) { + if (curr == 0) { return -(pos + 1); } - if (((k) == (curr))) { + if (k == curr) { return pos; } // There's always an unused entry. while (true) { pos = (pos + 1) & mask; curr = key.get(pos); - if (((curr) == (0))) { + if (curr == 0) { return -(pos + 1); } - if (((k) == (curr))) { + if (k == curr) { return pos; } } @@ -334,7 +334,7 @@ private long addToValue(final long pos, final long incr) public long addTo(final long k, final long incr) { long pos; - if (((k) == (0))) { + if (k == 0) { if (containsNullKey) { return addToValue(n, incr); } @@ -344,16 +344,16 @@ public long addTo(final long k, final long incr) else { final LongBigArray key = this.key; // The starting point. - pos = it.unimi.dsi.fastutil.HashCommon.mix((k)) & mask; + pos = it.unimi.dsi.fastutil.HashCommon.mix(k) & mask; long curr = key.get(pos); - if (!((curr) == (0))) { - if (((curr) == (k))) { + if (!(curr == 0)) { + if (curr == k) { return addToValue(pos, incr); } pos = (pos + 1) & mask; curr = key.get(pos); - while (!((curr) == (0))) { - if (((curr) == (k))) { + while (!(curr == 0)) { + if (curr == k) { return addToValue(pos, incr); } pos = (pos + 1) & mask; @@ -390,11 +390,11 @@ protected final void shiftKeys(long pos) pos = (pos + 1) & mask; for (; ; ) { curr = key.get(pos); - if (((curr) == (0))) { - key.set(last, (0)); + if (curr == 0) { + key.set(last, 0); return; } - slot = it.unimi.dsi.fastutil.HashCommon.mix((curr)) & mask; + slot = it.unimi.dsi.fastutil.HashCommon.mix(curr) & mask; if (last <= pos ? last >= slot || slot > pos : last >= slot && slot > pos) { break; } @@ -408,7 +408,7 @@ protected final void shiftKeys(long pos) @Override public long remove(final long k) { - if (((k) == (0))) { + if (k == 0) { if (containsNullKey) { return removeNullEntry(); } @@ -416,21 +416,21 @@ public long remove(final long k) } final LongBigArray key = this.key; // The starting point. - long pos = it.unimi.dsi.fastutil.HashCommon.mix((k)) & mask; + long pos = it.unimi.dsi.fastutil.HashCommon.mix(k) & mask; long curr = key.get(pos); - if (((curr) == (0))) { + if (curr == 0) { return defRetValue; } - if (((k) == (curr))) { + if (k == curr) { return removeEntry(pos); } while (true) { pos = (pos + 1) & mask; curr = key.get(pos); - if (((curr) == (0))) { + if (curr == 0) { return defRetValue; } - if (((k) == (curr))) { + if (k == curr) { return removeEntry(pos); } } @@ -439,27 +439,27 @@ public long remove(final long k) @Override public long get(final long k) { - if (((k) == (0))) { + if (k == 0) { return containsNullKey ? value.get(n) : defRetValue; } final LongBigArray key = this.key; // The starting point. - long pos = it.unimi.dsi.fastutil.HashCommon.mix((k)) & mask; + long pos = it.unimi.dsi.fastutil.HashCommon.mix(k) & mask; long curr = key.get(pos); - if (((curr) == (0))) { + if (curr == 0) { return defRetValue; } - if (((k) == (curr))) { + if (k == curr) { return value.get(pos); } // There's always an unused entry. while (true) { pos = (pos + 1) & mask; curr = key.get(pos); - if (((curr) == (0))) { + if (curr == 0) { return defRetValue; } - if (((k) == (curr))) { + if (k == curr) { return value.get(pos); } } @@ -468,27 +468,27 @@ public long get(final long k) @Override public boolean containsKey(final long k) { - if (((k) == (0))) { + if (k == 0) { return containsNullKey; } final LongBigArray key = this.key; // The starting point. - long pos = it.unimi.dsi.fastutil.HashCommon.mix((k)) & mask; + long pos = it.unimi.dsi.fastutil.HashCommon.mix(k) & mask; long curr = key.get(pos); - if (((curr) == (0))) { + if (curr == 0) { return false; } - if (((k) == (curr))) { + if (k == curr) { return true; } // There's always an unused entry. while (true) { pos = (pos + 1) & mask; curr = key.get(pos); - if (((curr) == (0))) { + if (curr == 0) { return false; } - if (((k) == (curr))) { + if (k == curr) { return true; } } @@ -499,11 +499,11 @@ public boolean containsValue(final long v) { final LongBigArray value = this.value; final LongBigArray key = this.key; - if (containsNullKey && ((value.get(n)) == (v))) { + if (containsNullKey && (value.get(n) == v)) { return true; } for (long i = n; i-- != 0; ) { - if (!((key.get(i)) == (0)) && ((value.get(i)) == (v))) { + if (!(key.get(i) == 0) && (value.get(i) == v)) { return true; } } @@ -516,27 +516,27 @@ public boolean containsValue(final long v) @Override public long getOrDefault(final long k, final long defaultValue) { - if (((k) == (0))) { + if (k == 0) { return containsNullKey ? value.get(n) : defaultValue; } final LongBigArray key = this.key; // The starting point. - long pos = it.unimi.dsi.fastutil.HashCommon.mix((k)) & mask; + long pos = it.unimi.dsi.fastutil.HashCommon.mix(k) & mask; long curr = key.get(pos); - if (((curr) == (0))) { + if (curr == 0) { return defaultValue; } - if (((k) == (curr))) { + if (k == curr) { return value.get(pos); } // There's always an unused entry. while (true) { pos = (pos + 1) & mask; curr = key.get(pos); - if (((curr) == (0))) { + if (curr == 0) { return defaultValue; } - if (((k) == (curr))) { + if (k == curr) { return value.get(pos); } } @@ -562,8 +562,8 @@ public long putIfAbsent(final long k, final long v) @Override public boolean remove(final long k, final long v) { - if (((k) == (0))) { - if (containsNullKey && ((v) == (value.get(n)))) { + if (k == 0) { + if (containsNullKey && (v == value.get(n))) { removeNullEntry(); return true; } @@ -571,22 +571,22 @@ public boolean remove(final long k, final long v) } final LongBigArray key = this.key; // The starting point. - long pos = it.unimi.dsi.fastutil.HashCommon.mix((k)) & mask; + long pos = it.unimi.dsi.fastutil.HashCommon.mix(k) & mask; long curr = key.get(pos); - if (((curr) == (0))) { + if (curr == 0) { return false; } - if (((k) == (curr)) && ((v) == (value.get(pos)))) { + if ((k == curr) && (v == value.get(pos))) { removeEntry(pos); return true; } while (true) { pos = (pos + 1) & mask; curr = key.get(pos); - if (((curr) == (0))) { + if (curr == 0) { return false; } - if (((k) == (curr)) && ((v) == (value.get(pos)))) { + if ((k == curr) && (v == value.get(pos))) { removeEntry(pos); return true; } @@ -600,7 +600,7 @@ public boolean remove(final long k, final long v) public boolean replace(final long k, final long oldValue, final long v) { final long pos = find(k); - if (pos < 0 || !((oldValue) == (value.get(pos)))) { + if (pos < 0 || !(oldValue == value.get(pos))) { return false; } value.set(pos, v); @@ -654,7 +654,7 @@ public long computeIfAbsentNullable(final long k, if (newValue == null) { return defRetValue; } - final long v = (newValue).longValue(); + final long v = newValue.longValue(); insert(-pos - 1, k, v); return v; } @@ -673,7 +673,7 @@ public long computeIfPresent(final long k, } final Long newValue = remappingFunction.apply(Long.valueOf(k), Long.valueOf(value.get(pos))); if (newValue == null) { - if (((k) == (0))) { + if (k == 0) { removeNullEntry(); } else { @@ -681,8 +681,8 @@ public long computeIfPresent(final long k, } return defRetValue; } - value.set(pos, (newValue).longValue()); - return (newValue).longValue(); + value.set(pos, newValue.longValue()); + return newValue.longValue(); } /** @@ -697,7 +697,7 @@ public long compute(final long k, final Long newValue = remappingFunction.apply(Long.valueOf(k), pos >= 0 ? Long.valueOf(value.get(pos)) : null); if (newValue == null) { if (pos >= 0) { - if (((k) == (0))) { + if (k == 0) { removeNullEntry(); } else { @@ -706,7 +706,7 @@ public long compute(final long k, } return defRetValue; } - long newVal = (newValue).longValue(); + long newVal = newValue.longValue(); if (pos < 0) { insert(-pos - 1, k, newVal); return newVal; @@ -730,7 +730,7 @@ public long merge(final long k, final long v, } final Long newValue = remappingFunction.apply(Long.valueOf(value.get(pos)), Long.valueOf(v)); if (newValue == null) { - if (((k) == (0))) { + if (k == 0) { removeNullEntry(); } else { @@ -738,8 +738,8 @@ public long merge(final long k, final long v, } return defRetValue; } - value.set(pos, (newValue).longValue()); - return (newValue).longValue(); + value.set(pos, newValue.longValue()); + return newValue.longValue(); } /* @@ -846,7 +846,7 @@ public Long getValue() @Override public Long setValue(final Long v) { - return Long.valueOf(setValue((v).longValue())); + return Long.valueOf(setValue(v.longValue())); } @SuppressWarnings("unchecked") @@ -857,7 +857,7 @@ public boolean equals(final Object o) return false; } Map.Entry e = (Map.Entry) o; - return ((key.get(index)) == ((e.getKey()).longValue())) && ((value.get(index)) == ((e.getValue()).longValue())); + return (key.get(index) == e.getKey().longValue()) && (value.get(index) == e.getValue().longValue()); } @Override @@ -928,13 +928,13 @@ public long nextEntry() // We are just enumerating elements from the wrapped list. last = Long.MIN_VALUE; final long k = wrapped.getLong(-pos - 1); - long p = it.unimi.dsi.fastutil.HashCommon.mix((k)) & mask; - while (!((k) == (key.get(p)))) { + long p = it.unimi.dsi.fastutil.HashCommon.mix(k) & mask; + while (!(k == key.get(p))) { p = (p + 1) & mask; } return p; } - if (!((key.get(pos)) == (0))) { + if (!(key.get(pos) == 0)) { last = pos; return pos; } @@ -959,11 +959,11 @@ private void shiftKeys(long pos) pos = (pos + 1) & mask; for (; ; ) { curr = key.get(pos); - if (((curr) == (0))) { - key.set(last, (0)); + if (curr == 0) { + key.set(last, 0); return; } - slot = it.unimi.dsi.fastutil.HashCommon.mix((curr)) & mask; + slot = it.unimi.dsi.fastutil.HashCommon.mix(curr) & mask; if (last <= pos ? last >= slot || slot > pos : last >= slot && slot > pos) { break; } @@ -1078,30 +1078,30 @@ public boolean contains(final Object o) if (e.getValue() == null || !(e.getValue() instanceof Long)) { return false; } - final long k = ((Long) (e.getKey())).longValue(); - final long v = ((Long) (e.getValue())).longValue(); - if (((k) == (0))) { - return Long2LongOpenBigHashMap.this.containsNullKey && ((value.get(n)) == (v)); + final long k = ((Long) e.getKey()).longValue(); + final long v = ((Long) e.getValue()).longValue(); + if (k == 0) { + return Long2LongOpenBigHashMap.this.containsNullKey && (value.get(n) == v); } final LongBigArray key = Long2LongOpenBigHashMap.this.key; // The starting point. - long pos = it.unimi.dsi.fastutil.HashCommon.mix((k)) & mask; + long pos = it.unimi.dsi.fastutil.HashCommon.mix(k) & mask; long curr = key.get(pos); - if (((curr) == (0))) { + if (curr == 0) { return false; } - if (((k) == (curr))) { - return ((value.get(pos)) == (v)); + if (k == curr) { + return value.get(pos) == v; } // There's always an unused entry. while (true) { pos = (pos + 1) & mask; curr = key.get(pos); - if (((curr) == (0))) { + if (curr == 0) { return false; } - if (((k) == (curr))) { - return ((value.get(pos)) == (v)); + if (k == curr) { + return value.get(pos) == v; } } } @@ -1119,10 +1119,10 @@ public boolean remove(final Object o) if (e.getValue() == null || !(e.getValue() instanceof Long)) { return false; } - final long k = ((Long) (e.getKey())).longValue(); - final long v = ((Long) (e.getValue())).longValue(); - if (((k) == (0))) { - if (containsNullKey && ((value.get(n)) == (v))) { + final long k = ((Long) e.getKey()).longValue(); + final long v = ((Long) e.getValue()).longValue(); + if (k == 0) { + if (containsNullKey && (value.get(n) == v)) { removeNullEntry(); return true; } @@ -1130,13 +1130,13 @@ public boolean remove(final Object o) } final LongBigArray key = Long2LongOpenBigHashMap.this.key; // The starting point. - long pos = it.unimi.dsi.fastutil.HashCommon.mix((k)) & mask; + long pos = it.unimi.dsi.fastutil.HashCommon.mix(k) & mask; long curr = key.get(pos); - if (((curr) == (0))) { + if (curr == 0) { return false; } - if (((curr) == (k))) { - if (((value.get(pos)) == (v))) { + if (curr == k) { + if (value.get(pos) == v) { removeEntry(pos); return true; } @@ -1145,11 +1145,11 @@ public boolean remove(final Object o) while (true) { pos = (pos + 1) & mask; curr = key.get(pos); - if (((curr) == (0))) { + if (curr == 0) { return false; } - if (((curr) == (k))) { - if (((value.get(pos)) == (v))) { + if (curr == k) { + if (value.get(pos) == v) { removeEntry(pos); return true; } @@ -1179,7 +1179,7 @@ public void forEach(final Consumer consumer) consumer.accept(new AbstractLong2LongMap.BasicEntry(key.get(n), value.get(n))); } for (long pos = n; pos-- != 0; ) { - if (!((key.get(pos)) == (0))) { + if (!(key.get(pos) == 0)) { consumer.accept(new AbstractLong2LongMap.BasicEntry(key.get(pos), value.get(pos))); } } @@ -1195,7 +1195,7 @@ public void fastForEach(final Consumer consumer) consumer.accept(new AbstractLong2LongMap.BasicEntry(key.get(n), value.get(n))); } for (long pos = n; pos-- != 0; ) { - if (!((key.get(pos)) == (0))) { + if (!(key.get(pos) == 0)) { consumer.accept(new AbstractLong2LongMap.BasicEntry(key.get(pos), value.get(pos))); } } @@ -1256,7 +1256,7 @@ public void forEach(final java.util.function.LongConsumer consumer) } for (long pos = n; pos-- != 0; ) { final long k = key.get(pos); - if (!((k) == (0))) { + if (!(k == 0)) { consumer.accept(k); } } @@ -1361,7 +1361,7 @@ public void forEach(final java.util.function.LongConsumer consumer) consumer.accept(value.get(n)); } for (long pos = n; pos-- != 0; ) { - if (!((key.get(pos)) == (0))) { + if (!(key.get(pos) == 0)) { consumer.accept(value.get(pos)); } } @@ -1448,13 +1448,13 @@ protected void rehash(final long newN) long i = n; long pos; for (long j = realSize(); j-- != 0; ) { - while (((key.get(--i)) == (0))) { + while (key.get(--i) == 0) { // Skip } - pos = it.unimi.dsi.fastutil.HashCommon.mix((key.get(i))) & mask; - if (!((newKey.get(pos)) == (0))) { + pos = it.unimi.dsi.fastutil.HashCommon.mix(key.get(i)) & mask; + if (!(newKey.get(pos) == 0)) { pos = (pos + 1) & mask; - while (!((newKey.get(pos)) == (0))) { + while (!(newKey.get(pos) == 0)) { pos = (pos + 1) & mask; } } diff --git a/core/trino-main/src/main/java/io/trino/util/LongLong2LongOpenCustomBigHashMap.java b/core/trino-main/src/main/java/io/trino/util/LongLong2LongOpenCustomBigHashMap.java index df4ec7cb854e..a726eafb064a 100644 --- a/core/trino-main/src/main/java/io/trino/util/LongLong2LongOpenCustomBigHashMap.java +++ b/core/trino-main/src/main/java/io/trino/util/LongLong2LongOpenCustomBigHashMap.java @@ -276,7 +276,7 @@ private long removeNullEntry() private long find(final long k1, final long k2) { - if ((strategy.equals((k1), (k2), (nullKey1), (nullKey2)))) { + if (strategy.equals(k1, k2, nullKey1, nullKey2)) { return containsNullKey ? n : -(n + 1); } long curr1; @@ -287,10 +287,10 @@ private long find(final long k1, final long k2) long pos = HashCommon.mix(strategy.hashCode(k1, k2)) & mask; curr1 = key1.get(pos); curr2 = key2.get(pos); - if ((curr1 == (nullKey1) && curr2 == (nullKey2))) { + if (curr1 == nullKey1 && curr2 == nullKey2) { return -(pos + 1); } - if ((strategy.equals((k1), (k2), (curr1), (curr2)))) { + if (strategy.equals(k1, k2, curr1, curr2)) { return pos; } // There's always an unused entry. @@ -298,10 +298,10 @@ private long find(final long k1, final long k2) pos = (pos + 1) & mask; curr1 = key1.get(pos); curr2 = key2.get(pos); - if ((curr1 == (nullKey1) && curr2 == (nullKey2))) { + if (curr1 == nullKey1 && curr2 == nullKey2) { return -(pos + 1); } - if ((strategy.equals((k1), (k2), (curr1), (curr2)))) { + if (strategy.equals(k1, k2, curr1, curr2)) { return pos; } } @@ -360,7 +360,7 @@ private long addToValue(final long pos, final long incr) public long addTo(final long k1, final long k2, final long incr) { long pos; - if ((strategy.equals((k1), (k2), (nullKey1), (nullKey2)))) { + if (strategy.equals(k1, k2, nullKey1, nullKey2)) { if (containsNullKey) { return addToValue(n, incr); } @@ -371,18 +371,18 @@ public long addTo(final long k1, final long k2, final long incr) final LongBigArray key1 = this.key1; final LongBigArray key2 = this.key2; // The starting point. - pos = (it.unimi.dsi.fastutil.HashCommon.mix(strategy.hashCode(k1, k2))) & mask; + pos = HashCommon.mix(strategy.hashCode(k1, k2)) & mask; long curr1 = key1.get(pos); long curr2 = key2.get(pos); - if (!((curr1) == (nullKey1) && (curr2) == (nullKey2))) { - if ((strategy.equals((curr1), (curr2), (k1), (k2)))) { + if (!(curr1 == nullKey1 && curr2 == nullKey2)) { + if (strategy.equals(curr1, curr2, k1, k2)) { return addToValue(pos, incr); } pos = (pos + 1) & mask; curr1 = key1.get(pos); curr2 = key2.get(pos); - while (!((curr1) == (nullKey1) && (curr2) == (nullKey2))) { - if ((strategy.equals((curr1), (curr2), (k1), (k2)))) { + while (!(curr1 == nullKey1 && curr2 == nullKey2)) { + if (strategy.equals(curr1, curr2, k1, k2)) { return addToValue(pos, incr); } pos = (pos + 1) & mask; @@ -420,16 +420,16 @@ protected final void shiftKeys(long pos) final LongBigArray key2 = this.key2; for (; ; ) { last = pos; - pos = ((pos) + 1) & mask; + pos = (pos + 1) & mask; for (; ; ) { curr1 = key1.get(pos); curr2 = key2.get(pos); - if (((curr1) == (nullKey1)) && ((curr2) == (nullKey2))) { + if ((curr1 == nullKey1) && (curr2 == nullKey2)) { key1.set(last, nullKey1); key2.set(last, nullKey2); return; } - slot = (it.unimi.dsi.fastutil.HashCommon.mix(strategy.hashCode(curr1, curr2))) & mask; + slot = HashCommon.mix(strategy.hashCode(curr1, curr2)) & mask; if (last <= pos ? last >= slot || slot > pos : last >= slot && slot > pos) { break; } @@ -443,7 +443,7 @@ protected final void shiftKeys(long pos) public long remove(final long k1, final long k2) { - if ((strategy.equals((k1), (k2), (nullKey1), (nullKey2)))) { + if (strategy.equals(k1, k2, nullKey1, nullKey2)) { if (containsNullKey) { return removeNullEntry(); } @@ -452,23 +452,23 @@ public long remove(final long k1, final long k2) final LongBigArray key1 = this.key1; final LongBigArray key2 = this.key2; // The starting point. - long pos = (it.unimi.dsi.fastutil.HashCommon.mix(strategy.hashCode(k1, k2))) & mask; + long pos = HashCommon.mix(strategy.hashCode(k1, k2)) & mask; long curr1 = key1.get(pos); long curr2 = key2.get(pos); - if (((curr1) == (nullKey1)) && ((curr2) == (nullKey2))) { + if ((curr1 == nullKey1) && (curr2 == nullKey2)) { return defRetValue; } - if ((strategy.equals((k1), (k2), (curr1), (curr2)))) { + if (strategy.equals(k1, k2, curr1, curr2)) { return removeEntry(pos); } while (true) { pos = (pos + 1) & mask; curr1 = key1.get(pos); curr2 = key2.get(pos); - if (((curr1) == (nullKey1)) && ((curr2) == (nullKey2))) { + if ((curr1 == nullKey1) && (curr2 == nullKey2)) { return defRetValue; } - if ((strategy.equals((k1), (k2), (curr1), (curr2)))) { + if (strategy.equals(k1, k2, curr1, curr2)) { return removeEntry(pos); } } @@ -476,19 +476,19 @@ public long remove(final long k1, final long k2) public long get(final long k1, final long k2) { - if ((strategy.equals((k1), (k2), (nullKey1), (nullKey2)))) { + if (strategy.equals(k1, k2, nullKey1, nullKey2)) { return containsNullKey ? value.get(n) : defRetValue; } final LongBigArray key1 = this.key1; final LongBigArray key2 = this.key2; // The starting point. - long pos = (it.unimi.dsi.fastutil.HashCommon.mix(strategy.hashCode(k1, k2))) & mask; + long pos = HashCommon.mix(strategy.hashCode(k1, k2)) & mask; long curr1 = key1.get(pos); long curr2 = key2.get(pos); - if (((curr1) == (nullKey1)) && ((curr2) == (nullKey2))) { + if ((curr1 == nullKey1) && (curr2 == nullKey2)) { return defRetValue; } - if ((strategy.equals((k1), (k2), (curr1), (curr2)))) { + if (strategy.equals(k1, k2, curr1, curr2)) { return value.get(pos); } // There's always an unused entry. @@ -496,10 +496,10 @@ public long get(final long k1, final long k2) pos = (pos + 1) & mask; curr1 = key1.get(pos); curr2 = key2.get(pos); - if (((curr1) == (nullKey1)) && ((curr2) == (nullKey2))) { + if ((curr1 == nullKey1) && (curr2 == nullKey2)) { return defRetValue; } - if ((strategy.equals((k1), (k2), (curr1), (curr2)))) { + if (strategy.equals(k1, k2, curr1, curr2)) { return value.get(pos); } } @@ -507,19 +507,19 @@ public long get(final long k1, final long k2) public boolean containsKey(final long k1, final long k2) { - if ((strategy.equals((k1), (k2), (nullKey1), (nullKey2)))) { + if (strategy.equals(k1, k2, nullKey1, nullKey2)) { return containsNullKey; } final LongBigArray key1 = this.key1; final LongBigArray key2 = this.key2; // The starting point. - long pos = (it.unimi.dsi.fastutil.HashCommon.mix(strategy.hashCode(k1, k2))) & mask; + long pos = HashCommon.mix(strategy.hashCode(k1, k2)) & mask; long curr1 = key1.get(pos); long curr2 = key2.get(pos); - if (((curr1) == (nullKey1)) && ((curr2) == (nullKey2))) { + if ((curr1 == nullKey1) && (curr2 == nullKey2)) { return false; } - if ((strategy.equals((k1), (k2), (curr1), (curr2)))) { + if (strategy.equals(k1, k2, curr1, curr2)) { return true; } // There's always an unused entry. @@ -527,10 +527,10 @@ public boolean containsKey(final long k1, final long k2) pos = (pos + 1) & mask; curr1 = key1.get(pos); curr2 = key2.get(pos); - if (((curr1) == (nullKey1)) && ((curr2) == (nullKey2))) { + if ((curr1 == nullKey1) && (curr2 == nullKey2)) { return false; } - if ((strategy.equals((k1), (k2), (curr1), (curr2)))) { + if (strategy.equals(k1, k2, curr1, curr2)) { return true; } } @@ -541,11 +541,11 @@ public boolean containsValue(final long v) final LongBigArray value = this.value; final LongBigArray key1 = this.key1; final LongBigArray key2 = this.key2; - if (containsNullKey && ((value.get(n)) == (v))) { + if (containsNullKey && (value.get(n) == v)) { return true; } for (long i = n; i-- != 0; ) { - if (!((key1.get(i)) == (nullKey1) && (key2.get(i)) == (nullKey2)) && ((value.get(i)) == (v))) { + if (!(key1.get(i) == nullKey1 && key2.get(i) == nullKey2) && (value.get(i) == v)) { return true; } } @@ -554,19 +554,19 @@ public boolean containsValue(final long v) public long getOrDefault(final long k1, final long k2, final long defaultValue) { - if ((strategy.equals((k1), (k2), (nullKey1), (nullKey2)))) { + if (strategy.equals(k1, k2, nullKey1, nullKey2)) { return containsNullKey ? value.get(n) : defaultValue; } final LongBigArray key1 = this.key1; final LongBigArray key2 = this.key2; // The starting point. - long pos = (it.unimi.dsi.fastutil.HashCommon.mix(strategy.hashCode(k1, k2))) & mask; + long pos = HashCommon.mix(strategy.hashCode(k1, k2)) & mask; long curr1 = key1.get(pos); long curr2 = key2.get(pos); - if (((curr1) == (nullKey1)) && ((curr2) == (nullKey2))) { + if ((curr1 == nullKey1) && (curr2 == nullKey2)) { return defaultValue; } - if ((strategy.equals((k1), (k2), (curr1), (curr2)))) { + if (strategy.equals(k1, k2, curr1, curr2)) { return value.get(pos); } // There's always an unused entry. @@ -574,10 +574,10 @@ public long getOrDefault(final long k1, final long k2, final long defaultValue) pos = (pos + 1) & mask; curr1 = key1.get(pos); curr2 = key2.get(pos); - if (((curr1) == (nullKey1)) && ((curr2) == (nullKey2))) { + if ((curr1 == nullKey1) && (curr2 == nullKey2)) { return defaultValue; } - if ((strategy.equals((k1), (k2), (curr1), (curr2)))) { + if (strategy.equals(k1, k2, curr1, curr2)) { return value.get(pos); } } @@ -595,8 +595,8 @@ public long putIfAbsent(final long k1, final long k2, final long v) public boolean remove(final long k1, final long k2, final long v) { - if ((strategy.equals((k1), (k2), (nullKey1), (nullKey2)))) { - if (containsNullKey && ((v) == (value.get(n)))) { + if (strategy.equals(k1, k2, nullKey1, nullKey2)) { + if (containsNullKey && (v == value.get(n))) { removeNullEntry(); return true; } @@ -605,13 +605,13 @@ public boolean remove(final long k1, final long k2, final long v) final LongBigArray key1 = this.key1; final LongBigArray key2 = this.key2; // The starting point. - long pos = (it.unimi.dsi.fastutil.HashCommon.mix(strategy.hashCode(k1, k2))) & mask; + long pos = HashCommon.mix(strategy.hashCode(k1, k2)) & mask; long curr1 = key1.get(pos); long curr2 = key2.get(pos); - if (((curr1) == (nullKey1)) && ((curr2) == (nullKey2))) { + if ((curr1 == nullKey1) && (curr2 == nullKey2)) { return false; } - if ((strategy.equals((k1), (k2), (curr1), (curr2))) && ((v) == (value.get(pos)))) { + if (strategy.equals(k1, k2, curr1, curr2) && (v == value.get(pos))) { removeEntry(pos); return true; } @@ -619,10 +619,10 @@ public boolean remove(final long k1, final long k2, final long v) pos = (pos + 1) & mask; curr1 = key1.get(pos); curr2 = key2.get(pos); - if (((curr1) == (nullKey1)) && ((curr2) == (nullKey2))) { + if ((curr1 == nullKey1) && (curr2 == nullKey2)) { return false; } - if ((strategy.equals((k1), (k2), (curr1), (curr2))) && ((v) == (value.get(pos)))) { + if (strategy.equals(k1, k2, curr1, curr2) && (v == value.get(pos))) { removeEntry(pos); return true; } @@ -632,7 +632,7 @@ public boolean remove(final long k1, final long k2, final long v) public boolean replace(final long k1, final long k2, final long oldValue, final long v) { final long pos = find(k1, k2); - if (pos < 0 || !((oldValue) == (value.get(pos)))) { + if (pos < 0 || !(oldValue == value.get(pos))) { return false; } value.set(pos, v); @@ -673,7 +673,7 @@ public long merge(final long k1, final long k2, final long v, } final Long newValue = remappingFunction.apply(Long.valueOf(value.get(pos)), Long.valueOf(v)); if (newValue == null) { - if ((strategy.equals((k1), (k2), (nullKey1), (nullKey2)))) { + if (strategy.equals(k1, k2, nullKey1, nullKey2)) { removeNullEntry(); } else { @@ -681,8 +681,8 @@ public long merge(final long k1, final long k2, final long v, } return defRetValue; } - value.set(pos, (newValue).longValue()); - return (newValue).longValue(); + value.set(pos, newValue.longValue()); + return newValue.longValue(); } /* @@ -786,22 +786,22 @@ protected void rehash(final long newN) final LongBigArray value = this.value; final long mask = newN - 1; // Note that this is used by the hashing macro final LongBigArray newKey1 = new LongBigArray(nullKey1); - newKey1.ensureCapacity((newN + 1)); + newKey1.ensureCapacity(newN + 1); final LongBigArray newKey2 = new LongBigArray(nullKey2); - newKey2.ensureCapacity((newN + 1)); + newKey2.ensureCapacity(newN + 1); final LongBigArray newValue = new LongBigArray(); newValue.ensureCapacity(newN + 1); long i = n; long pos; for (long j = realSize(); j-- != 0; ) { --i; - while (((key1.get(i)) == (nullKey1)) && ((key2.get(i)) == (nullKey2))) { + while ((key1.get(i) == nullKey1) && (key2.get(i) == nullKey2)) { --i; } - pos = (it.unimi.dsi.fastutil.HashCommon.mix(strategy.hashCode(key1.get(i), key2.get(i)))) & mask; - if (!((newKey1.get(pos)) == (nullKey1) && (newKey2.get(pos)) == (nullKey2))) { + pos = HashCommon.mix(strategy.hashCode(key1.get(i), key2.get(i))) & mask; + if (!(newKey1.get(pos) == nullKey1 && newKey2.get(pos) == nullKey2)) { pos = (pos + 1) & mask; - while (!((newKey1.get(pos)) == (nullKey1) && (newKey2.get(pos)) == (nullKey2))) { + while (!(newKey1.get(pos) == nullKey1 && newKey2.get(pos) == nullKey2)) { pos = (pos + 1) & mask; } } diff --git a/core/trino-main/src/main/java/io/trino/util/MoreMath.java b/core/trino-main/src/main/java/io/trino/util/MoreMath.java index e2e3042815b9..3e1ef3dd8c4d 100644 --- a/core/trino-main/src/main/java/io/trino/util/MoreMath.java +++ b/core/trino-main/src/main/java/io/trino/util/MoreMath.java @@ -39,7 +39,7 @@ public static boolean nearlyEqual(double a, double b, double epsilon) // relative error is less meaningful here return diff < (epsilon * Double.MIN_NORMAL); } // use relative error - return diff / Math.min((absA + absB), Double.MAX_VALUE) < epsilon; + return diff / Math.min(absA + absB, Double.MAX_VALUE) < epsilon; } /** @@ -59,7 +59,7 @@ public static boolean nearlyEqual(float a, float b, float epsilon) // relative error is less meaningful here return diff < (epsilon * Float.MIN_NORMAL); } // use relative error - return diff / Math.min((absA + absB), Float.MAX_VALUE) < epsilon; + return diff / Math.min(absA + absB, Float.MAX_VALUE) < epsilon; } public static double min(double... values) diff --git a/core/trino-main/src/main/java/io/trino/util/ThreadLocalCompressorDecompressor.java b/core/trino-main/src/main/java/io/trino/util/ThreadLocalCompressorDecompressor.java index 6c7b631e2a05..1c73ac299e54 100644 --- a/core/trino-main/src/main/java/io/trino/util/ThreadLocalCompressorDecompressor.java +++ b/core/trino-main/src/main/java/io/trino/util/ThreadLocalCompressorDecompressor.java @@ -31,7 +31,7 @@ public class ThreadLocalCompressorDecompressor public ThreadLocalCompressorDecompressor(Supplier compressorFactory, Supplier decompressorFactory) { this.compressor = ThreadLocal.withInitial(requireNonNull(compressorFactory, "compressorFactory is null")); - this.decompressor = ThreadLocal.withInitial((requireNonNull(decompressorFactory, "decompressorFactory is null"))); + this.decompressor = ThreadLocal.withInitial(requireNonNull(decompressorFactory, "decompressorFactory is null")); } @Override diff --git a/core/trino-main/src/test/java/io/trino/connector/MockConnectorFactory.java b/core/trino-main/src/test/java/io/trino/connector/MockConnectorFactory.java index 52d98475fb46..c033c038b5cb 100644 --- a/core/trino-main/src/test/java/io/trino/connector/MockConnectorFactory.java +++ b/core/trino-main/src/test/java/io/trino/connector/MockConnectorFactory.java @@ -423,7 +423,7 @@ public static final class Builder private Function> getColumns = defaultGetColumns(); private Function> getComment = schemaTableName -> Optional.empty(); private Function getTableStatistics = schemaTableName -> empty(); - private Function> checkConstraints = (schemaTableName -> ImmutableList.of()); + private Function> checkConstraints = schemaTableName -> ImmutableList.of(); private ApplyProjection applyProjection = (session, handle, projections, assignments) -> Optional.empty(); private ApplyAggregation applyAggregation = (session, handle, aggregates, assignments, groupingSets) -> Optional.empty(); private ApplyJoin applyJoin = (session, joinType, left, right, joinConditions, leftAssignments, rightAssignments) -> Optional.empty(); diff --git a/core/trino-main/src/test/java/io/trino/cost/TestFilterStatsCalculator.java b/core/trino-main/src/test/java/io/trino/cost/TestFilterStatsCalculator.java index bddaa92e48b1..2f52d3163670 100644 --- a/core/trino-main/src/test/java/io/trino/cost/TestFilterStatsCalculator.java +++ b/core/trino-main/src/test/java/io/trino/cost/TestFilterStatsCalculator.java @@ -358,7 +358,7 @@ public void testAndStats() assertExpression( "(x BETWEEN -5 AND 5) AND y > 1", Session.builder(session).setSystemProperty(FILTER_CONJUNCTION_INDEPENDENCE_FACTOR, "0.5").build()) - .outputRowsCount(filterSelectivityX * (Math.pow(inequalityFilterSelectivityY, 0.5)) * inputRowCount) + .outputRowsCount(filterSelectivityX * Math.pow(inequalityFilterSelectivityY, 0.5) * inputRowCount) .symbolStats("x", symbolAssertX) .symbolStats("y", symbolAssertY); @@ -397,7 +397,7 @@ public void testAndStats() assertExpression( "x > 0 AND (y < 1 OR y > 2)", Session.builder(session).setSystemProperty(FILTER_CONJUNCTION_INDEPENDENCE_FACTOR, "0.5").build()) - .outputRowsCount(filterSelectivityX * (Math.pow(inequalityFilterSelectivityY, 0.5)) * inputRowCount) + .outputRowsCount(filterSelectivityX * Math.pow(inequalityFilterSelectivityY, 0.5) * inputRowCount) .symbolStats("x", symbolAssert -> symbolAssert.averageRowSize(4.0) .lowValue(0.0) .highValue(10.0) diff --git a/core/trino-main/src/test/java/io/trino/dispatcher/TestLocalDispatchQuery.java b/core/trino-main/src/test/java/io/trino/dispatcher/TestLocalDispatchQuery.java index df08712c2977..d74a931f7b1e 100644 --- a/core/trino-main/src/test/java/io/trino/dispatcher/TestLocalDispatchQuery.java +++ b/core/trino-main/src/test/java/io/trino/dispatcher/TestLocalDispatchQuery.java @@ -152,7 +152,7 @@ public void testSubmittedForDispatchedQuery() queryMonitor, new TestClusterSizeMonitor(new InMemoryNodeManager(ImmutableSet.of()), new NodeSchedulerConfig()), executor, - (queryExecution -> dataDefinitionExecution.start())); + queryExecution -> dataDefinitionExecution.start()); queryStateMachine.addStateChangeListener(state -> { if (state.ordinal() >= QueryState.PLANNING.ordinal()) { countDownLatch.countDown(); diff --git a/core/trino-main/src/test/java/io/trino/execution/TestCreateTableColumnTypeCoercion.java b/core/trino-main/src/test/java/io/trino/execution/TestCreateTableColumnTypeCoercion.java index 8dfbee1f2488..6e84ae079d0b 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestCreateTableColumnTypeCoercion.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestCreateTableColumnTypeCoercion.java @@ -59,7 +59,7 @@ private MockConnectorFactory prepareConnectorFactory(String catalogName) { return MockConnectorFactory.builder() .withName(catalogName) - .withGetTableHandle(((session, schemaTableName) -> null)) + .withGetTableHandle((session, schemaTableName) -> null) .withGetSupportedType((session, type) -> { if (type instanceof TimestampType) { return Optional.of(VARCHAR); diff --git a/core/trino-main/src/test/java/io/trino/execution/buffer/BenchmarkBlockSerde.java b/core/trino-main/src/test/java/io/trino/execution/buffer/BenchmarkBlockSerde.java index a54bdaf5d848..a74249f2d615 100644 --- a/core/trino-main/src/test/java/io/trino/execution/buffer/BenchmarkBlockSerde.java +++ b/core/trino-main/src/test/java/io/trino/execution/buffer/BenchmarkBlockSerde.java @@ -422,7 +422,7 @@ public static class RowTypeBenchmarkData public void setup() { RowType type = RowType.anonymous(ImmutableList.of(BIGINT)); - super.setup(type, (random -> BenchmarkDataGenerator.randomRow(type.getTypeParameters(), random))); + super.setup(type, random -> BenchmarkDataGenerator.randomRow(type.getTypeParameters(), random)); } } diff --git a/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestEventDrivenTaskSource.java b/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestEventDrivenTaskSource.java index fe1220840c2b..3ce6406dfc3d 100644 --- a/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestEventDrivenTaskSource.java +++ b/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestEventDrivenTaskSource.java @@ -284,11 +284,11 @@ private void testStageTaskSourceSuccess( { List handleSources = new ArrayList<>(); Map exchanges = new HashMap<>(); - Multimaps.asMap(sourceHandles).forEach(((fragmentId, handles) -> { + Multimaps.asMap(sourceHandles).forEach((fragmentId, handles) -> { TestingExchangeSourceHandleSource handleSource = new TestingExchangeSourceHandleSource(executor, handles); handleSources.add(handleSource); exchanges.put(fragmentId, new TestingExchange(handleSource)); - })); + }); remoteSources.keySet().forEach(fragmentId -> { if (!exchanges.containsKey(fragmentId)) { TestingExchangeSourceHandleSource handleSource = new TestingExchangeSourceHandleSource(executor, ImmutableList.of()); @@ -298,7 +298,7 @@ private void testStageTaskSourceSuccess( }); Map splitSources = new HashMap<>(); - Multimaps.asMap(splits).forEach(((planNodeId, connectorSplits) -> splitSources.put(planNodeId, new TestingSplitSource(executor, connectorSplits)))); + Multimaps.asMap(splits).forEach((planNodeId, connectorSplits) -> splitSources.put(planNodeId, new TestingSplitSource(executor, connectorSplits))); SplitAssignerTester tester = new SplitAssignerTester(); int partitionCount = getPartitionCount(sourceHandles.values(), splits.values()); @@ -322,7 +322,7 @@ private void testStageTaskSourceSuccess( 1, 1, partitioningScheme, - (getSplitDuration) -> getSplitInvocations.incrementAndGet())) { + getSplitDuration -> getSplitInvocations.incrementAndGet())) { while (tester.getTaskDescriptors().isEmpty()) { AssignmentResult result = taskSource.process().get(10, SECONDS); tester.update(result); diff --git a/core/trino-main/src/test/java/io/trino/operator/TestTableWriterOperator.java b/core/trino-main/src/test/java/io/trino/operator/TestTableWriterOperator.java index be14d2fb93b5..ac5c5621eacd 100644 --- a/core/trino-main/src/test/java/io/trino/operator/TestTableWriterOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/TestTableWriterOperator.java @@ -188,7 +188,7 @@ public void testTableWriterInfo() tableWriterOperator.addInput(page); TableWriterInfo info = tableWriterOperator.getInfo(); assertThat(info.getPageSinkPeakMemoryUsage()).isEqualTo(peakMemoryUsage); - assertThat((long) (info.getValidationCpuTime().getValue(NANOSECONDS))).isEqualTo(validationCpuNanos); + assertThat((long) info.getValidationCpuTime().getValue(NANOSECONDS)).isEqualTo(validationCpuNanos); } } diff --git a/core/trino-main/src/test/java/io/trino/operator/join/TestHashJoinOperator.java b/core/trino-main/src/test/java/io/trino/operator/join/TestHashJoinOperator.java index b5d13371690e..091b7f3b4973 100644 --- a/core/trino-main/src/test/java/io/trino/operator/join/TestHashJoinOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/join/TestHashJoinOperator.java @@ -262,12 +262,12 @@ public void testUnwrapsLazyBlocks() TaskContext taskContext = createTaskContext(); DriverContext driverContext = taskContext.addPipelineContext(0, true, true, false).addDriverContext(); - InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction(( + InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction( (leftPosition, leftPage, rightPosition, rightPage) -> { // force loading of probe block rightPage.getBlock(1).getLoadedBlock(); return true; - })); + }); RowPagesBuilder buildPages = rowPagesBuilder(false, Ints.asList(0), ImmutableList.of(BIGINT)).addSequencePage(1, 0); BuildSideSetup buildSideSetup = setupBuildSide(nodePartitioningManager, true, taskContext, buildPages, Optional.of(filterFunction), false, SINGLE_STREAM_SPILLER_FACTORY); @@ -315,12 +315,12 @@ public void testYield() // force a yield for every match AtomicInteger filterFunctionCalls = new AtomicInteger(); - InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction(( + InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction( (leftPosition, leftPage, rightPosition, rightPage) -> { filterFunctionCalls.incrementAndGet(); driverContext.getYieldSignal().forceYieldForTesting(); return true; - })); + }); // build with 40 entries int entries = 40; @@ -887,8 +887,8 @@ public void testProbeOuterJoinWithFilterFunction(boolean parallelBuild, boolean { TaskContext taskContext = createTaskContext(); - InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction(( - (leftPosition, leftPage, rightPosition, rightPage) -> BIGINT.getLong(rightPage.getBlock(1), rightPosition) >= 1025)); + InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction( + (leftPosition, leftPage, rightPosition, rightPage) -> BIGINT.getLong(rightPage.getBlock(1), rightPosition) >= 1025); // build factory List buildTypes = ImmutableList.of(VARCHAR, BIGINT, BIGINT); @@ -978,8 +978,8 @@ public void testOuterJoinWithNullProbeAndFilterFunction(boolean parallelBuild, b { TaskContext taskContext = createTaskContext(); - InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction(( - (leftPosition, leftPage, rightPosition, rightPage) -> VARCHAR.getSlice(rightPage.getBlock(0), rightPosition).toStringAscii().equals("a"))); + InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction( + (leftPosition, leftPage, rightPosition, rightPage) -> VARCHAR.getSlice(rightPage.getBlock(0), rightPosition).toStringAscii().equals("a")); // build factory List buildTypes = ImmutableList.of(VARCHAR); @@ -1064,9 +1064,9 @@ public void testOuterJoinWithNullBuildAndFilterFunction(boolean parallelBuild, b { TaskContext taskContext = createTaskContext(); - InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction(( + InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction( (leftPosition, leftPage, rightPosition, rightPage) -> - ImmutableSet.of("a", "c").contains(VARCHAR.getSlice(rightPage.getBlock(0), rightPosition).toStringAscii()))); + ImmutableSet.of("a", "c").contains(VARCHAR.getSlice(rightPage.getBlock(0), rightPosition).toStringAscii())); // build factory List buildTypes = ImmutableList.of(VARCHAR); @@ -1151,9 +1151,9 @@ public void testOuterJoinWithNullOnBothSidesAndFilterFunction(boolean parallelBu { TaskContext taskContext = createTaskContext(); - InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction(( + InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction( (leftPosition, leftPage, rightPosition, rightPage) -> - ImmutableSet.of("a", "c").contains(VARCHAR.getSlice(rightPage.getBlock(0), rightPosition).toStringAscii()))); + ImmutableSet.of("a", "c").contains(VARCHAR.getSlice(rightPage.getBlock(0), rightPosition).toStringAscii())); // build factory RowPagesBuilder buildPages = rowPagesBuilder(buildHashEnabled, Ints.asList(0), ImmutableList.of(VARCHAR)) diff --git a/core/trino-main/src/test/java/io/trino/operator/join/TestLookupJoinPageBuilder.java b/core/trino-main/src/test/java/io/trino/operator/join/TestLookupJoinPageBuilder.java index 02472c33a669..8d0eca559b97 100644 --- a/core/trino-main/src/test/java/io/trino/operator/join/TestLookupJoinPageBuilder.java +++ b/core/trino-main/src/test/java/io/trino/operator/join/TestLookupJoinPageBuilder.java @@ -163,7 +163,7 @@ public void testCrossJoinWithEmptyBuild() // nothing on the build side so we don't append anything LookupSource lookupSource = new TestLookupSource(ImmutableList.of(), page); - JoinProbe probe = (new JoinProbeFactory(new int[] {0}, ImmutableList.of(0), OptionalInt.empty())).createJoinProbe(page); + JoinProbe probe = new JoinProbeFactory(new int[] {0}, ImmutableList.of(0), OptionalInt.empty()).createJoinProbe(page); LookupJoinPageBuilder lookupJoinPageBuilder = new LookupJoinPageBuilder(ImmutableList.of(BIGINT)); // append the same row many times should also flush in the end diff --git a/core/trino-main/src/test/java/io/trino/operator/join/unspilled/TestHashJoinOperator.java b/core/trino-main/src/test/java/io/trino/operator/join/unspilled/TestHashJoinOperator.java index c51cc67701d7..da5dc1232729 100644 --- a/core/trino-main/src/test/java/io/trino/operator/join/unspilled/TestHashJoinOperator.java +++ b/core/trino-main/src/test/java/io/trino/operator/join/unspilled/TestHashJoinOperator.java @@ -264,12 +264,12 @@ public void testUnwrapsLazyBlocks(boolean singleBigintLookupSource) TaskContext taskContext = createTaskContext(); DriverContext driverContext = taskContext.addPipelineContext(0, true, true, false).addDriverContext(); - InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction(( + InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction( (leftPosition, leftPage, rightPosition, rightPage) -> { // force loading of probe block rightPage.getBlock(1).getLoadedBlock(); return true; - })); + }); RowPagesBuilder buildPages = rowPagesBuilder(false, Ints.asList(0), ImmutableList.of(BIGINT)).addSequencePage(1, 0); BuildSideSetup buildSideSetup = setupBuildSide(nodePartitioningManager, true, taskContext, buildPages, Optional.of(filterFunction), singleBigintLookupSource); @@ -315,12 +315,12 @@ public void testYield(boolean singleBigintLookupSource) // force a yield for every match AtomicInteger filterFunctionCalls = new AtomicInteger(); - InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction(( + InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction( (leftPosition, leftPage, rightPosition, rightPage) -> { filterFunctionCalls.incrementAndGet(); driverContext.getYieldSignal().forceYieldForTesting(); return true; - })); + }); // build with 40 entries int entries = 40; @@ -583,8 +583,8 @@ public void testProbeOuterJoinWithFilterFunction(boolean parallelBuild, boolean { TaskContext taskContext = createTaskContext(); - InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction(( - (leftPosition, leftPage, rightPosition, rightPage) -> BIGINT.getLong(rightPage.getBlock(1), rightPosition) >= 1025)); + InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction( + (leftPosition, leftPage, rightPosition, rightPage) -> BIGINT.getLong(rightPage.getBlock(1), rightPosition) >= 1025); // build factory List buildTypes = ImmutableList.of(VARCHAR, BIGINT, BIGINT); @@ -674,8 +674,8 @@ public void testOuterJoinWithNullProbeAndFilterFunction(boolean parallelBuild, b { TaskContext taskContext = createTaskContext(); - InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction(( - (leftPosition, leftPage, rightPosition, rightPage) -> BIGINT.getLong(rightPage.getBlock(0), rightPosition) == 1L)); + InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction( + (leftPosition, leftPage, rightPosition, rightPage) -> BIGINT.getLong(rightPage.getBlock(0), rightPosition) == 1L); // build factory List buildTypes = ImmutableList.of(BIGINT); @@ -760,9 +760,9 @@ public void testOuterJoinWithNullBuildAndFilterFunction(boolean parallelBuild, b { TaskContext taskContext = createTaskContext(); - InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction(( + InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction( (leftPosition, leftPage, rightPosition, rightPage) -> - ImmutableSet.of(1L, 3L).contains(BIGINT.getLong(rightPage.getBlock(0), rightPosition)))); + ImmutableSet.of(1L, 3L).contains(BIGINT.getLong(rightPage.getBlock(0), rightPosition))); // build factory List buildTypes = ImmutableList.of(BIGINT); @@ -847,9 +847,9 @@ public void testOuterJoinWithNullOnBothSidesAndFilterFunction(boolean parallelBu { TaskContext taskContext = createTaskContext(); - InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction(( + InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction( (leftPosition, leftPage, rightPosition, rightPage) -> - ImmutableSet.of(1L, 3L).contains(BIGINT.getLong(rightPage.getBlock(0), rightPosition)))); + ImmutableSet.of(1L, 3L).contains(BIGINT.getLong(rightPage.getBlock(0), rightPosition))); // build factory RowPagesBuilder buildPages = rowPagesBuilder(buildHashEnabled, Ints.asList(0), ImmutableList.of(BIGINT)) diff --git a/core/trino-main/src/test/java/io/trino/operator/join/unspilled/TestLookupJoinPageBuilder.java b/core/trino-main/src/test/java/io/trino/operator/join/unspilled/TestLookupJoinPageBuilder.java index a6e9fcaa2304..f8f09420bf17 100644 --- a/core/trino-main/src/test/java/io/trino/operator/join/unspilled/TestLookupJoinPageBuilder.java +++ b/core/trino-main/src/test/java/io/trino/operator/join/unspilled/TestLookupJoinPageBuilder.java @@ -164,7 +164,7 @@ public void testCrossJoinWithEmptyBuild() // nothing on the build side so we don't append anything LookupSource lookupSource = new TestLookupSource(ImmutableList.of(), page); - JoinProbe probe = (new JoinProbeFactory(ImmutableList.of(0), ImmutableList.of(0), OptionalInt.empty(), false)).createJoinProbe(page, lookupSource); + JoinProbe probe = new JoinProbeFactory(ImmutableList.of(0), ImmutableList.of(0), OptionalInt.empty(), false).createJoinProbe(page, lookupSource); LookupJoinPageBuilder lookupJoinPageBuilder = new LookupJoinPageBuilder(ImmutableList.of(BIGINT)); // append the same row many times should also flush in the end diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/BenchmarkJsonPathBinaryOperators.java b/core/trino-main/src/test/java/io/trino/operator/scalar/BenchmarkJsonPathBinaryOperators.java index bce3212f89ff..90a4e4d01cf6 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/BenchmarkJsonPathBinaryOperators.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/BenchmarkJsonPathBinaryOperators.java @@ -179,9 +179,9 @@ private static Block createChannelConstantTypes(int positionCount) BlockBuilder blockBuilder = VARCHAR.createBlockBuilder(null, positionCount); for (int position = 0; position < positionCount; position++) { SliceOutput slice = new DynamicSliceOutput(20); - slice.appendBytes(("{\"first\" : ").getBytes(UTF_8)) + slice.appendBytes("{\"first\" : ".getBytes(UTF_8)) .appendBytes(format("%e", (double) position % 100).getBytes(UTF_8)) // real - .appendBytes((", \"second\" : ").getBytes(UTF_8)) + .appendBytes(", \"second\" : ".getBytes(UTF_8)) .appendBytes(format("%s", position % 10).getBytes(UTF_8)) // int .appendByte('}'); VARCHAR.writeSlice(blockBuilder, slice.slice()); @@ -194,7 +194,7 @@ private static Block createChannelVaryingTypes(int positionCount) BlockBuilder blockBuilder = VARCHAR.createBlockBuilder(null, positionCount); for (int position = 0; position < positionCount; position++) { SliceOutput slice = new DynamicSliceOutput(20); - slice.appendBytes(("{\"first\" : ").getBytes(UTF_8)); + slice.appendBytes("{\"first\" : ".getBytes(UTF_8)); if (position % 3 == 0) { slice.appendBytes(format("%e", (double) position % 100).getBytes(UTF_8)); // real } @@ -204,7 +204,7 @@ else if (position % 3 == 1) { else { slice.appendBytes(format("%s", position % 100).getBytes(UTF_8)); // int } - slice.appendBytes((", \"second\" : ").getBytes(UTF_8)) + slice.appendBytes(", \"second\" : ".getBytes(UTF_8)) .appendBytes(format("%s", position % 10).getBytes(UTF_8)) // int .appendByte('}'); VARCHAR.writeSlice(blockBuilder, slice.slice()); @@ -218,7 +218,7 @@ private static Block createChannelMultipleVaryingTypes(int positionCount) for (int position = 0; position < positionCount; position++) { SliceOutput slice = new DynamicSliceOutput(20); - slice.appendBytes(("{\"first\" : ").getBytes(UTF_8)); + slice.appendBytes("{\"first\" : ".getBytes(UTF_8)); if (position % 3 == 0) { slice.appendBytes(format("%e", (double) position % 100).getBytes(UTF_8)); // real } @@ -229,7 +229,7 @@ else if (position % 3 == 1) { slice.appendBytes(format("%s", position % 100).getBytes(UTF_8)); // int } - slice.appendBytes((", \"second\" : ").getBytes(UTF_8)); + slice.appendBytes(", \"second\" : ".getBytes(UTF_8)); if (position % 4 == 0) { slice.appendBytes(format("%e", (double) position % 10).getBytes(UTF_8)); // real } diff --git a/core/trino-main/src/test/java/io/trino/operator/scalar/TestMathFunctions.java b/core/trino-main/src/test/java/io/trino/operator/scalar/TestMathFunctions.java index 5f65653b52b7..ba0e533fa116 100644 --- a/core/trino-main/src/test/java/io/trino/operator/scalar/TestMathFunctions.java +++ b/core/trino-main/src/test/java/io/trino/operator/scalar/TestMathFunctions.java @@ -3445,7 +3445,7 @@ public void testCosineSimilarity() .isEqualTo(2 * 3 / (Math.sqrt(5) * Math.sqrt(10))); assertThat(assertions.function("cosine_similarity", "map(ARRAY['a', 'b', 'c'], ARRAY[1.0E0, 2.0E0, -1.0E0])", "map(ARRAY['c', 'b'], ARRAY[1.0E0, 3.0E0])")) - .isEqualTo((2 * 3 + (-1) * 1) / (Math.sqrt(1 + 4 + 1) * Math.sqrt(1 + 9))); + .isEqualTo((2 * 3 + -1 * 1) / (Math.sqrt(1 + 4 + 1) * Math.sqrt(1 + 9))); assertThat(assertions.function("cosine_similarity", "map(ARRAY['a', 'b', 'c'], ARRAY[1.0E0, 2.0E0, -1.0E0])", "map(ARRAY['d', 'e'], ARRAY[1.0E0, 3.0E0])")) .isEqualTo(0.0); diff --git a/core/trino-main/src/test/java/io/trino/sql/analyzer/TestAnalyzer.java b/core/trino-main/src/test/java/io/trino/sql/analyzer/TestAnalyzer.java index a492399247b8..fb8f3dac141f 100644 --- a/core/trino-main/src/test/java/io/trino/sql/analyzer/TestAnalyzer.java +++ b/core/trino-main/src/test/java/io/trino/sql/analyzer/TestAnalyzer.java @@ -4128,11 +4128,11 @@ public void testQuantifiedComparisonExpression() .hasMessage("line 1:29: Value expression and result of subquery must be of the same type: row(bigint) vs row(varchar(3))"); // map is not orderable - assertFails(("SELECT map(ARRAY[1], ARRAY['hello']) < ALL (VALUES map(ARRAY[1], ARRAY['hello']))")) + assertFails("SELECT map(ARRAY[1], ARRAY['hello']) < ALL (VALUES map(ARRAY[1], ARRAY['hello']))") .hasErrorCode(TYPE_MISMATCH) .hasMessage("line 1:38: Type [row(map(integer, varchar(5)))] must be orderable in order to be used in quantified comparison"); // but map is comparable - analyze(("SELECT map(ARRAY[1], ARRAY['hello']) = ALL (VALUES map(ARRAY[1], ARRAY['hello']))")); + analyze("SELECT map(ARRAY[1], ARRAY['hello']) = ALL (VALUES map(ARRAY[1], ARRAY['hello']))"); // HLL is neither orderable nor comparable assertFails("SELECT cast(NULL AS HyperLogLog) < ALL (VALUES cast(NULL AS HyperLogLog))") diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestConnectorExpressionTranslator.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestConnectorExpressionTranslator.java index ecec430e74c9..fa755e6a50ca 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestConnectorExpressionTranslator.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestConnectorExpressionTranslator.java @@ -412,7 +412,7 @@ public void testTranslateResolvedFunction() assertTranslationRoundTrips( transactionSession, BuiltinFunctionCallBuilder.resolve(PLANNER_CONTEXT.getMetadata()) - .setName(("lower")) + .setName("lower") .addArgument(VARCHAR_TYPE, new SymbolReference("varchar_symbol_1")) .build(), new Call(VARCHAR_TYPE, diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/TestDomainCoercer.java b/core/trino-main/src/test/java/io/trino/sql/planner/TestDomainCoercer.java index c62d597a9bc6..4288407b24e0 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/TestDomainCoercer.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/TestDomainCoercer.java @@ -88,7 +88,7 @@ public void testOutsideTargetTypeRange() ValueSet.ofRanges(range(DOUBLE, 0.0, true, ((double) Float.MAX_VALUE) * 10, true)), true), REAL)).isEqualTo(Domain.create( - ValueSet.ofRanges((range(REAL, (long) floatToIntBits(0.0f), true, (long) floatToIntBits(Float.MAX_VALUE), true))), + ValueSet.ofRanges(range(REAL, (long) floatToIntBits(0.0f), true, (long) floatToIntBits(Float.MAX_VALUE), true)), true)); // low below and high above target type range diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/assertions/NotPlanNodeMatcher.java b/core/trino-main/src/test/java/io/trino/sql/planner/assertions/NotPlanNodeMatcher.java index b72b3501c935..290acca75ba3 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/assertions/NotPlanNodeMatcher.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/assertions/NotPlanNodeMatcher.java @@ -36,7 +36,7 @@ final class NotPlanNodeMatcher @Override public boolean shapeMatches(PlanNode node) { - return (!node.getClass().equals(excludedNodeClass)); + return !node.getClass().equals(excludedNodeClass); } @Override diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestExpressionRewriteRuleSet.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestExpressionRewriteRuleSet.java index 84aff40e390d..e9dddf2e2910 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestExpressionRewriteRuleSet.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestExpressionRewriteRuleSet.java @@ -149,7 +149,7 @@ public void testValueExpressionRewrite() tester().assertThat(zeroRewriter.valuesExpressionRewrite()) .on(p -> p.values( ImmutableList.of(p.symbol("a")), - ImmutableList.of((ImmutableList.of(PlanBuilder.expression("1")))))) + ImmutableList.of(ImmutableList.of(PlanBuilder.expression("1"))))) .matches( values(ImmutableList.of("a"), ImmutableList.of(ImmutableList.of(new LongLiteral("0"))))); } @@ -160,7 +160,7 @@ public void testValueExpressionNotRewritten() tester().assertThat(zeroRewriter.valuesExpressionRewrite()) .on(p -> p.values( ImmutableList.of(p.symbol("a")), - ImmutableList.of((ImmutableList.of(PlanBuilder.expression("0")))))) + ImmutableList.of(ImmutableList.of(PlanBuilder.expression("0"))))) .doesNotFire(); } diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestTransformCorrelatedSingleRowSubqueryToProject.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestTransformCorrelatedSingleRowSubqueryToProject.java index 0cae9d80e200..9ff1733f58e2 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestTransformCorrelatedSingleRowSubqueryToProject.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestTransformCorrelatedSingleRowSubqueryToProject.java @@ -61,7 +61,7 @@ public void testRewrite() ImmutableList.of()))))) .matches(project( ImmutableMap.of( - ("l_expr2"), PlanMatchPattern.expression("l_nationkey + 1"), + "l_expr2", PlanMatchPattern.expression("l_nationkey + 1"), "l_nationkey", PlanMatchPattern.expression("l_nationkey")), tableScan("nation", ImmutableMap.of("l_nationkey", "nationkey")))); } diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestAddExchangesPlans.java b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestAddExchangesPlans.java index a7efeeb847df..97c5047e2f14 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestAddExchangesPlans.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestAddExchangesPlans.java @@ -1127,10 +1127,10 @@ public void testUnionAllOnDifferentCatalogs() MockConnectorFactory connectorFactory = MockConnectorFactory.builder() .withGetColumns(schemaTableName -> ImmutableList.of( new ColumnMetadata("nationkey", BigintType.BIGINT))) - .withGetTableHandle(((session, schemaTableName) -> new MockConnectorTableHandle( + .withGetTableHandle((session, schemaTableName) -> new MockConnectorTableHandle( SchemaTableName.schemaTableName("default", "nation"), TupleDomain.all(), - Optional.of(ImmutableList.of(new MockConnectorColumnHandle("nationkey", BigintType.BIGINT)))))) + Optional.of(ImmutableList.of(new MockConnectorColumnHandle("nationkey", BigintType.BIGINT))))) .withName("mock") .build(); getQueryRunner().createCatalog("mock", connectorFactory, ImmutableMap.of()); diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestAddExchangesScaledWriters.java b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestAddExchangesScaledWriters.java index b760c56f4902..dc0d2c24119f 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestAddExchangesScaledWriters.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestAddExchangesScaledWriters.java @@ -52,7 +52,7 @@ protected LocalQueryRunner createLocalQueryRunner() private MockConnectorFactory createConnectorFactory(String name, boolean writerScalingEnabledAcrossTasks) { return MockConnectorFactory.builder() - .withGetTableHandle(((session, schemaTableName) -> null)) + .withGetTableHandle((session, schemaTableName) -> null) .withName(name) .withWriterScalingOptions(new WriterScalingOptions(writerScalingEnabledAcrossTasks, true)) .build(); diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestAddLocalExchangesForPartitionedInsertAndMerge.java b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestAddLocalExchangesForPartitionedInsertAndMerge.java index ab71fb8c7931..a0e211bcbdf4 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestAddLocalExchangesForPartitionedInsertAndMerge.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestAddLocalExchangesForPartitionedInsertAndMerge.java @@ -85,7 +85,7 @@ protected LocalQueryRunner createLocalQueryRunner() private MockConnectorFactory createMergeConnectorFactory() { return MockConnectorFactory.builder() - .withGetTableHandle(((session, schemaTableName) -> { + .withGetTableHandle((session, schemaTableName) -> { if (schemaTableName.getTableName().equals("source_table")) { return new MockConnectorTableHandle(schemaTableName); } @@ -93,7 +93,7 @@ private MockConnectorFactory createMergeConnectorFactory() return new MockConnectorTableHandle(schemaTableName); } return null; - })) + }) .withGetColumns(schemaTableName -> ImmutableList.of( new ColumnMetadata("customer", INTEGER), new ColumnMetadata("year", INTEGER))) diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestAddLocalExchangesForTaskScaleWriters.java b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestAddLocalExchangesForTaskScaleWriters.java index 1f2140612228..5d8c6e3f2c01 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestAddLocalExchangesForTaskScaleWriters.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestAddLocalExchangesForTaskScaleWriters.java @@ -95,7 +95,7 @@ private MockConnectorFactory createConnectorFactory( boolean writerScalingEnabledWithinTask) { return MockConnectorFactory.builder() - .withGetTableHandle(((session, tableName) -> { + .withGetTableHandle((session, tableName) -> { if (tableName.getTableName().equals("source_table") || tableName.getTableName().equals("system_partitioned_table") || tableName.getTableName().equals("connector_partitioned_table") @@ -103,7 +103,7 @@ private MockConnectorFactory createConnectorFactory( return new MockConnectorTableHandle(tableName); } return null; - })) + }) .withWriterScalingOptions(new WriterScalingOptions(true, writerScalingEnabledWithinTask)) .withGetTableStatistics(tableName -> { if (tableName.getTableName().equals("source_table")) { diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestColocatedJoin.java b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestColocatedJoin.java index 0470a9e70ddc..6f7c5fca07b3 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestColocatedJoin.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestColocatedJoin.java @@ -72,12 +72,12 @@ public class TestColocatedJoin protected LocalQueryRunner createLocalQueryRunner() { MockConnectorFactory connectorFactory = MockConnectorFactory.builder() - .withGetTableHandle(((session, tableName) -> { + .withGetTableHandle((session, tableName) -> { if (tableName.getTableName().equals(TABLE_NAME)) { return new MockConnectorTableHandle(tableName); } return null; - })) + }) .withPartitionProvider(new TestPartitioningProvider()) .withGetColumns(schemaTableName -> ImmutableList.of( new ColumnMetadata(COLUMN_A, BIGINT), diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestDeterminePartitionCount.java b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestDeterminePartitionCount.java index 439052f93e2f..fd1fbff833b6 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestDeterminePartitionCount.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestDeterminePartitionCount.java @@ -64,7 +64,7 @@ protected LocalQueryRunner createLocalQueryRunner() { String catalogName = "mock"; MockConnectorFactory connectorFactory = MockConnectorFactory.builder() - .withGetTableHandle(((session, tableName) -> { + .withGetTableHandle((session, tableName) -> { if (tableName.getTableName().equals("table_with_stats_a") || tableName.getTableName().equals("table_with_stats_b") || tableName.getTableName().equals("table_without_stats_a") @@ -72,7 +72,7 @@ protected LocalQueryRunner createLocalQueryRunner() return new MockConnectorTableHandle(tableName); } return null; - })) + }) .withGetColumns(schemaTableName -> ImmutableList.of( new ColumnMetadata("column_a", VARCHAR), new ColumnMetadata("column_b", VARCHAR))) diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestLimitMaxWriterNodesCount.java b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestLimitMaxWriterNodesCount.java index e51f24a75272..2824e1471ed4 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestLimitMaxWriterNodesCount.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestLimitMaxWriterNodesCount.java @@ -93,12 +93,12 @@ protected LocalQueryRunner createLocalQueryRunner() private MockConnectorFactory prepareConnectorFactory(String catalogName, OptionalInt maxWriterTasks, List tables) { return MockConnectorFactory.builder() - .withGetTableHandle(((session, tableName) -> { + .withGetTableHandle((session, tableName) -> { if (tables.contains(tableName.getTableName())) { return new MockConnectorTableHandle(tableName); } return null; - })) + }) .withWriterScalingOptions(WriterScalingOptions.ENABLED) .withGetInsertLayout((session, tableMetadata) -> { if (tableMetadata.getTableName().equals(partitionedTable)) { diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/sanity/TestValidateScaledWritersUsage.java b/core/trino-main/src/test/java/io/trino/sql/planner/sanity/TestValidateScaledWritersUsage.java index 5063fe6b30f5..b980fe60c51e 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/sanity/TestValidateScaledWritersUsage.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/sanity/TestValidateScaledWritersUsage.java @@ -99,7 +99,7 @@ public void tearDown() private MockConnectorFactory createConnectorFactory(String name) { return MockConnectorFactory.builder() - .withGetTableHandle(((session, schemaTableName) -> null)) + .withGetTableHandle((session, schemaTableName) -> null) .withName(name) .build(); } diff --git a/core/trino-main/src/test/java/io/trino/type/TestArrayOperators.java b/core/trino-main/src/test/java/io/trino/type/TestArrayOperators.java index 8249c4601580..0d06322c124d 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestArrayOperators.java +++ b/core/trino-main/src/test/java/io/trino/type/TestArrayOperators.java @@ -2873,7 +2873,7 @@ public void testArrayIntersect() .isEqualTo(ImmutableList.of(asList(123, 456), asList(123, 789))); assertThat(assertions.function("array_intersect", "ARRAY[ARRAY[123, 456], ARRAY[123, 789]]", "ARRAY[ARRAY[123, 456], ARRAY[123, 456], ARRAY[123, 789]]")) - .hasType(new ArrayType(new ArrayType((INTEGER)))) + .hasType(new ArrayType(new ArrayType(INTEGER))) .isEqualTo(ImmutableList.of(asList(123, 456), asList(123, 789))); assertThat(assertions.function("array_intersect", "ARRAY[(123, 'abc'), (123, 'cde')]", "ARRAY[(123, 'abc'), (123, 'cde')]")) diff --git a/core/trino-main/src/test/java/io/trino/type/TestIpAddressType.java b/core/trino-main/src/test/java/io/trino/type/TestIpAddressType.java index 8c172e041d5d..c946da258e5f 100644 --- a/core/trino-main/src/test/java/io/trino/type/TestIpAddressType.java +++ b/core/trino-main/src/test/java/io/trino/type/TestIpAddressType.java @@ -65,7 +65,7 @@ protected Object getNonNullValue() @Test public void testDisplayName() { - assertThat((IPADDRESS).getDisplayName()).isEqualTo("ipaddress"); + assertThat(IPADDRESS.getDisplayName()).isEqualTo("ipaddress"); } private static Slice getSliceForAddress(String address) diff --git a/core/trino-parser/src/main/java/io/trino/sql/ExpressionFormatter.java b/core/trino-parser/src/main/java/io/trino/sql/ExpressionFormatter.java index ad0b99952256..e073c48593e3 100644 --- a/core/trino-parser/src/main/java/io/trino/sql/ExpressionFormatter.java +++ b/core/trino-parser/src/main/java/io/trino/sql/ExpressionFormatter.java @@ -887,7 +887,7 @@ protected String visitJsonQuery(JsonQuery node, Void context) builder.append(switch (node.getWrapperBehavior()) { case WITHOUT -> " WITHOUT ARRAY WRAPPER"; case CONDITIONAL -> " WITH CONDITIONAL ARRAY WRAPPER"; - case UNCONDITIONAL -> (" WITH UNCONDITIONAL ARRAY WRAPPER"); + case UNCONDITIONAL -> " WITH UNCONDITIONAL ARRAY WRAPPER"; }); if (node.getQuotesBehavior().isPresent()) { diff --git a/core/trino-parser/src/main/java/io/trino/sql/QueryUtil.java b/core/trino-parser/src/main/java/io/trino/sql/QueryUtil.java index 194de410efdc..17119ff9cc61 100644 --- a/core/trino-parser/src/main/java/io/trino/sql/QueryUtil.java +++ b/core/trino-parser/src/main/java/io/trino/sql/QueryUtil.java @@ -253,7 +253,7 @@ public static Query simpleQuery( public static Query singleValueQuery(String columnName, String value) { - Relation values = values(row(new StringLiteral((value)))); + Relation values = values(row(new StringLiteral(value))); return simpleQuery( selectList(new AllColumns()), aliased(values, "t", ImmutableList.of(columnName))); diff --git a/core/trino-parser/src/main/java/io/trino/sql/SqlFormatter.java b/core/trino-parser/src/main/java/io/trino/sql/SqlFormatter.java index 4d4299e39768..3c98a8da3140 100644 --- a/core/trino-parser/src/main/java/io/trino/sql/SqlFormatter.java +++ b/core/trino-parser/src/main/java/io/trino/sql/SqlFormatter.java @@ -379,7 +379,7 @@ protected Void visitQueryColumn(QueryColumn node, Integer indent) builder.append(switch (node.getWrapperBehavior()) { case WITHOUT -> " WITHOUT ARRAY WRAPPER"; case CONDITIONAL -> " WITH CONDITIONAL ARRAY WRAPPER"; - case UNCONDITIONAL -> (" WITH UNCONDITIONAL ARRAY WRAPPER"); + case UNCONDITIONAL -> " WITH UNCONDITIONAL ARRAY WRAPPER"; }); if (node.getQuotesBehavior().isPresent()) { @@ -2608,7 +2608,7 @@ private void formatDefinitionList(List elements, int indent) private static void appendAliasColumns(Formatter.SqlBuilder builder, List columns) { - if ((columns != null) && (!columns.isEmpty())) { + if ((columns != null) && !columns.isEmpty()) { String formattedColumns = columns.stream() .map(SqlFormatter::formatName) .collect(joining(", ")); diff --git a/core/trino-parser/src/main/java/io/trino/sql/parser/AstBuilder.java b/core/trino-parser/src/main/java/io/trino/sql/parser/AstBuilder.java index fd66be00a9ff..54c710f4fff4 100644 --- a/core/trino-parser/src/main/java/io/trino/sql/parser/AstBuilder.java +++ b/core/trino-parser/src/main/java/io/trino/sql/parser/AstBuilder.java @@ -2425,7 +2425,7 @@ public Node visitListagg(SqlBaseParser.ListaggContext context) boolean distinct = isDistinct(context.setQuantifier()); Expression expression = (Expression) visit(context.expression()); - StringLiteral separator = context.string() == null ? new StringLiteral(getLocation(context), "") : (StringLiteral) (visit(context.string())); + StringLiteral separator = context.string() == null ? new StringLiteral(getLocation(context), "") : (StringLiteral) visit(context.string()); BooleanLiteral overflowError = new BooleanLiteral(getLocation(context), "true"); StringLiteral overflowFiller = new StringLiteral(getLocation(context), "..."); BooleanLiteral showOverflowEntryCount = new BooleanLiteral(getLocation(context), "false"); @@ -2438,7 +2438,7 @@ public Node visitListagg(SqlBaseParser.ListaggContext context) else if (overflowBehavior.TRUNCATE() != null) { overflowError = new BooleanLiteral(getLocation(context), "false"); if (overflowBehavior.string() != null) { - overflowFiller = (StringLiteral) (visit(overflowBehavior.string())); + overflowFiller = (StringLiteral) visit(overflowBehavior.string()); } SqlBaseParser.ListaggCountIndicationContext listaggCountIndicationContext = overflowBehavior.listaggCountIndication(); if (listaggCountIndicationContext.WITH() != null) { diff --git a/core/trino-parser/src/main/java/io/trino/sql/tree/DescriptorField.java b/core/trino-parser/src/main/java/io/trino/sql/tree/DescriptorField.java index 349c98444163..729eb8a52224 100644 --- a/core/trino-parser/src/main/java/io/trino/sql/tree/DescriptorField.java +++ b/core/trino-parser/src/main/java/io/trino/sql/tree/DescriptorField.java @@ -68,7 +68,7 @@ public boolean equals(Object o) } DescriptorField field = (DescriptorField) o; return Objects.equals(name, field.name) && - Objects.equals(type, (field.type)); + Objects.equals(type, field.type); } @Override diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/Int2IntOpenHashMap.java b/core/trino-spi/src/main/java/io/trino/spi/block/Int2IntOpenHashMap.java index 1ec7823f3c67..79f4924f3a9e 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/Int2IntOpenHashMap.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/Int2IntOpenHashMap.java @@ -110,27 +110,27 @@ public int putIfAbsent(final int k, final int v) public int get(final int k) { - if (((k) == (0))) { + if (k == 0) { return containsNullKey ? value[n] : DEFAULT_RETURN_VALUE; } final int[] key = this.key; // The starting point. - int pos = (mix((k))) & mask; + int pos = mix(k) & mask; int curr = key[pos]; - if (curr == (0)) { + if (curr == 0) { return DEFAULT_RETURN_VALUE; } - if (((k) == (curr))) { + if (k == curr) { return value[pos]; } // There's always an unused entry. while (true) { pos = (pos + 1) & mask; curr = key[pos]; - if (curr == (0)) { + if (curr == 0) { return DEFAULT_RETURN_VALUE; } - if (((k) == (curr))) { + if (k == curr) { return value[pos]; } } @@ -138,27 +138,27 @@ public int get(final int k) public boolean containsKey(final int k) { - if (((k) == (0))) { + if (k == 0) { return containsNullKey; } final int[] key = this.key; // The starting point. - int pos = (mix((k))) & mask; + int pos = mix(k) & mask; int curr = key[pos]; - if (curr == (0)) { + if (curr == 0) { return false; } - if (((k) == (curr))) { + if (k == curr) { return true; } // There's always an unused entry. while (true) { pos = (pos + 1) & mask; curr = key[pos]; - if (curr == (0)) { + if (curr == 0) { return false; } - if (((k) == (curr))) { + if (k == curr) { return true; } } @@ -178,27 +178,27 @@ private void insert(final int pos, final int k, final int v) private int find(final int k) { - if (((k) == (0))) { + if (k == 0) { return containsNullKey ? n : -(n + 1); } final int[] key = this.key; - int pos = (mix((k))) & mask; + int pos = mix(k) & mask; int curr = key[pos]; // The starting point. - if (curr == (0)) { + if (curr == 0) { return -(pos + 1); } - if (((k) == (curr))) { + if (k == curr) { return pos; } // There's always an unused entry. while (true) { pos = (pos + 1) & mask; curr = key[pos]; - if (curr == (0)) { + if (curr == 0) { return -(pos + 1); } - if (((k) == (curr))) { + if (k == curr) { return pos; } } @@ -226,13 +226,13 @@ private void rehash(final int newN) int pos; for (int j = realSize(); j-- != 0; ) { --i; - while (((key[i]) == (0))) { + while (key[i] == 0) { --i; } - pos = (mix((key[i]))) & mask; - if (!(newKey[pos] == (0))) { + pos = mix(key[i]) & mask; + if (!(newKey[pos] == 0)) { pos = (pos + 1) & mask; - while (!((newKey[pos]) == (0))) { + while (!(newKey[pos] == 0)) { pos = (pos + 1) & mask; } } diff --git a/core/trino-spi/src/main/java/io/trino/spi/block/LongArrayBlock.java b/core/trino-spi/src/main/java/io/trino/spi/block/LongArrayBlock.java index b79e5939ba79..3966eb62f22b 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/block/LongArrayBlock.java +++ b/core/trino-spi/src/main/java/io/trino/spi/block/LongArrayBlock.java @@ -161,7 +161,7 @@ public short getShort(int position, int offset) throw new IllegalArgumentException("offset must be zero"); } - short value = (short) (values[position + arrayOffset]); + short value = (short) values[position + arrayOffset]; if (value != values[position + arrayOffset]) { throw new ArithmeticException("short overflow"); } @@ -178,7 +178,7 @@ public byte getByte(int position, int offset) throw new IllegalArgumentException("offset must be zero"); } - byte value = (byte) (values[position + arrayOffset]); + byte value = (byte) values[position + arrayOffset]; if (value != values[position + arrayOffset]) { throw new ArithmeticException("byte overflow"); } diff --git a/core/trino-spi/src/main/java/io/trino/spi/type/Int128Math.java b/core/trino-spi/src/main/java/io/trino/spi/type/Int128Math.java index 8fbab65ca4c8..2c8042841a20 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/type/Int128Math.java +++ b/core/trino-spi/src/main/java/io/trino/spi/type/Int128Math.java @@ -1306,7 +1306,7 @@ private static long low(long value) private static int highInt(long val) { - return (int) (high(val)); + return (int) high(val); } private static int lowInt(long val) diff --git a/lib/trino-array/src/test/java/io/trino/array/TestBlockBigArray.java b/lib/trino-array/src/test/java/io/trino/array/TestBlockBigArray.java index 37b769ef7928..a44ab67b125d 100644 --- a/lib/trino-array/src/test/java/io/trino/array/TestBlockBigArray.java +++ b/lib/trino-array/src/test/java/io/trino/array/TestBlockBigArray.java @@ -45,7 +45,7 @@ public void testRetainedSizeWithOverlappingBlocks() referenceCountMap.incrementAndGet(block); long expectedSize = instanceSize(BlockBigArray.class) + referenceCountMap.sizeOf() - + (new ObjectBigArray<>()).sizeOf() + + new ObjectBigArray<>().sizeOf() + block.getRetainedSizeInBytes() + (arraySize - 1L) * instanceSize(block.getClass()); assertThat(blockBigArray.sizeOf()).isEqualTo(expectedSize); } diff --git a/lib/trino-filesystem-azure/src/main/java/io/trino/filesystem/azure/AzureFileSystem.java b/lib/trino-filesystem-azure/src/main/java/io/trino/filesystem/azure/AzureFileSystem.java index 5327332d0d07..702b68a38a3c 100644 --- a/lib/trino-filesystem-azure/src/main/java/io/trino/filesystem/azure/AzureFileSystem.java +++ b/lib/trino-filesystem-azure/src/main/java/io/trino/filesystem/azure/AzureFileSystem.java @@ -236,7 +236,7 @@ public FileIterator listFiles(Location location) AzureLocation azureLocation = new AzureLocation(location); try { // blob API returns directories as blobs, so it cannot be used when Gen2 is enabled - return (isHierarchicalNamespaceEnabled(azureLocation)) + return isHierarchicalNamespaceEnabled(azureLocation) ? listGen2Files(azureLocation) : listBlobFiles(azureLocation); } @@ -373,7 +373,7 @@ public Set listDirectories(Location location) AzureLocation azureLocation = new AzureLocation(location); try { // blob API returns directories as blobs, so it cannot be used when Gen2 is enabled - return (isHierarchicalNamespaceEnabled(azureLocation)) + return isHierarchicalNamespaceEnabled(azureLocation) ? listGen2Directories(azureLocation) : listBlobDirectories(azureLocation); } diff --git a/lib/trino-geospatial-toolkit/src/main/java/io/trino/geospatial/GeometryUtils.java b/lib/trino-geospatial-toolkit/src/main/java/io/trino/geospatial/GeometryUtils.java index ee75c2aa10e2..77353f215af5 100644 --- a/lib/trino-geospatial-toolkit/src/main/java/io/trino/geospatial/GeometryUtils.java +++ b/lib/trino-geospatial-toolkit/src/main/java/io/trino/geospatial/GeometryUtils.java @@ -55,7 +55,7 @@ private static double translateFromAVNaN(double n) */ public static double translateToAVNaN(double n) { - return (Double.isNaN(n)) ? -Double.MAX_VALUE : n; + return Double.isNaN(n) ? -Double.MAX_VALUE : n; } public static boolean isEsriNaN(double d) diff --git a/lib/trino-hive-formats/src/main/java/io/trino/hive/formats/avro/AvroPageDataReader.java b/lib/trino-hive-formats/src/main/java/io/trino/hive/formats/avro/AvroPageDataReader.java index f21299ef643b..2d3f1bfb4acc 100644 --- a/lib/trino-hive-formats/src/main/java/io/trino/hive/formats/avro/AvroPageDataReader.java +++ b/lib/trino-hive-formats/src/main/java/io/trino/hive/formats/avro/AvroPageDataReader.java @@ -434,7 +434,7 @@ public EnumBlockBuildingDecoder(Resolver.EnumAdjust action) List symbolsList = requireNonNull(action, "action is null").reader.getEnumSymbols(); symbols = symbolsList.stream().map(Slices::utf8Slice).toArray(Slice[]::new); if (!action.noAdjustmentsNeeded) { - Slice[] adjustedSymbols = new Slice[(action.writer.getEnumSymbols().size())]; + Slice[] adjustedSymbols = new Slice[action.writer.getEnumSymbols().size()]; for (int i = 0; i < action.adjustments.length; i++) { if (action.adjustments[i] < 0) { throw new AvroTypeException("No reader Enum value for writer Enum value " + action.writer.getEnumSymbols().get(i)); diff --git a/lib/trino-hive-formats/src/main/java/io/trino/hive/formats/avro/NativeLogicalTypesAvroTypeManager.java b/lib/trino-hive-formats/src/main/java/io/trino/hive/formats/avro/NativeLogicalTypesAvroTypeManager.java index 8c61632a4e77..84a72f09d10a 100644 --- a/lib/trino-hive-formats/src/main/java/io/trino/hive/formats/avro/NativeLogicalTypesAvroTypeManager.java +++ b/lib/trino-hive-formats/src/main/java/io/trino/hive/formats/avro/NativeLogicalTypesAvroTypeManager.java @@ -225,11 +225,11 @@ private static BiFunction getAvroFunction(LogicalType lo yield (block, integer) -> timestampType.getLong(block, integer) / Timestamps.MICROSECONDS_PER_MILLISECOND; } else { - yield ((block, integer) -> + yield (block, integer) -> { SqlTimestamp timestamp = (SqlTimestamp) timestampType.getObject(block, integer); return timestamp.roundTo(3).getMillis(); - }); + }; } } case TIMESTAMP_MICROS -> { @@ -241,11 +241,11 @@ private static BiFunction getAvroFunction(LogicalType lo yield (block, position) -> timestampType.getLong(block, position); } else { - yield ((block, position) -> + yield (block, position) -> { SqlTimestamp timestamp = (SqlTimestamp) timestampType.getObject(block, position); return timestamp.roundTo(6).getEpochMicros(); - }); + }; } } case DECIMAL -> { diff --git a/lib/trino-hive-formats/src/main/java/io/trino/hive/formats/rcfile/RcFileReader.java b/lib/trino-hive-formats/src/main/java/io/trino/hive/formats/rcfile/RcFileReader.java index 198203619551..75f2f3308c95 100644 --- a/lib/trino-hive-formats/src/main/java/io/trino/hive/formats/rcfile/RcFileReader.java +++ b/lib/trino-hive-formats/src/main/java/io/trino/hive/formats/rcfile/RcFileReader.java @@ -638,7 +638,7 @@ private int readNextValueLength() if (lastValueLength == -1) { throw new FileCorruptionException("First column value length is negative"); } - runLength = (~valueLength) - 1; + runLength = ~valueLength - 1; return lastValueLength; } diff --git a/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/avro/TestAvroBase.java b/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/avro/TestAvroBase.java index 6a2fff33bdd8..81426283e012 100644 --- a/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/avro/TestAvroBase.java +++ b/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/avro/TestAvroBase.java @@ -279,7 +279,7 @@ public void testSerdeCycles() public void cleanup() { try { - trinoLocalFilesystem.deleteDirectory((Location.of("local:///"))); + trinoLocalFilesystem.deleteDirectory(Location.of("local:///")); Files.deleteIfExists(tempDirectory.toAbsolutePath()); } catch (IOException e) { diff --git a/lib/trino-ignite-patched/src/main/java/org/apache/ignite/shaded/internal/util/GridUnsafe.java b/lib/trino-ignite-patched/src/main/java/org/apache/ignite/shaded/internal/util/GridUnsafe.java index e50109eefb01..e8661bd6204e 100644 --- a/lib/trino-ignite-patched/src/main/java/org/apache/ignite/shaded/internal/util/GridUnsafe.java +++ b/lib/trino-ignite-patched/src/main/java/org/apache/ignite/shaded/internal/util/GridUnsafe.java @@ -2028,16 +2028,16 @@ private static void putCharByByte(Object obj, long addr, char val, boolean bigEn private static int getIntByByte(Object obj, long addr, boolean bigEndian) { if (bigEndian) { - return (((int) UNSAFE.getByte(obj, addr)) << 24) | + return ((int) UNSAFE.getByte(obj, addr) << 24) | (((int) UNSAFE.getByte(obj, addr + 1) & 0xff) << 16) | (((int) UNSAFE.getByte(obj, addr + 2) & 0xff) << 8) | - (((int) UNSAFE.getByte(obj, addr + 3) & 0xff)); + ((int) UNSAFE.getByte(obj, addr + 3) & 0xff); } else { - return (((int) UNSAFE.getByte(obj, addr + 3)) << 24) | + return ((int) UNSAFE.getByte(obj, addr + 3) << 24) | (((int) UNSAFE.getByte(obj, addr + 2) & 0xff) << 16) | (((int) UNSAFE.getByte(obj, addr + 1) & 0xff) << 8) | - (((int) UNSAFE.getByte(obj, addr) & 0xff)); + ((int) UNSAFE.getByte(obj, addr) & 0xff); } } @@ -2053,13 +2053,13 @@ private static void putIntByByte(Object obj, long addr, int val, boolean bigEndi UNSAFE.putByte(obj, addr, (byte) (val >> 24)); UNSAFE.putByte(obj, addr + 1, (byte) (val >> 16)); UNSAFE.putByte(obj, addr + 2, (byte) (val >> 8)); - UNSAFE.putByte(obj, addr + 3, (byte) (val)); + UNSAFE.putByte(obj, addr + 3, (byte) val); } else { UNSAFE.putByte(obj, addr + 3, (byte) (val >> 24)); UNSAFE.putByte(obj, addr + 2, (byte) (val >> 16)); UNSAFE.putByte(obj, addr + 1, (byte) (val >> 8)); - UNSAFE.putByte(obj, addr, (byte) (val)); + UNSAFE.putByte(obj, addr, (byte) val); } } @@ -2071,24 +2071,24 @@ private static void putIntByByte(Object obj, long addr, int val, boolean bigEndi private static long getLongByByte(Object obj, long addr, boolean bigEndian) { if (bigEndian) { - return (((long) UNSAFE.getByte(obj, addr)) << 56) | + return ((long) UNSAFE.getByte(obj, addr) << 56) | (((long) UNSAFE.getByte(obj, addr + 1) & 0xff) << 48) | (((long) UNSAFE.getByte(obj, addr + 2) & 0xff) << 40) | (((long) UNSAFE.getByte(obj, addr + 3) & 0xff) << 32) | (((long) UNSAFE.getByte(obj, addr + 4) & 0xff) << 24) | (((long) UNSAFE.getByte(obj, addr + 5) & 0xff) << 16) | (((long) UNSAFE.getByte(obj, addr + 6) & 0xff) << 8) | - (((long) UNSAFE.getByte(obj, addr + 7) & 0xff)); + ((long) UNSAFE.getByte(obj, addr + 7) & 0xff); } else { - return (((long) UNSAFE.getByte(obj, addr + 7)) << 56) | + return ((long) UNSAFE.getByte(obj, addr + 7) << 56) | (((long) UNSAFE.getByte(obj, addr + 6) & 0xff) << 48) | (((long) UNSAFE.getByte(obj, addr + 5) & 0xff) << 40) | (((long) UNSAFE.getByte(obj, addr + 4) & 0xff) << 32) | (((long) UNSAFE.getByte(obj, addr + 3) & 0xff) << 24) | (((long) UNSAFE.getByte(obj, addr + 2) & 0xff) << 16) | (((long) UNSAFE.getByte(obj, addr + 1) & 0xff) << 8) | - (((long) UNSAFE.getByte(obj, addr) & 0xff)); + ((long) UNSAFE.getByte(obj, addr) & 0xff); } } @@ -2108,7 +2108,7 @@ private static void putLongByByte(Object obj, long addr, long val, boolean bigEn UNSAFE.putByte(obj, addr + 4, (byte) (val >> 24)); UNSAFE.putByte(obj, addr + 5, (byte) (val >> 16)); UNSAFE.putByte(obj, addr + 6, (byte) (val >> 8)); - UNSAFE.putByte(obj, addr + 7, (byte) (val)); + UNSAFE.putByte(obj, addr + 7, (byte) val); } else { UNSAFE.putByte(obj, addr + 7, (byte) (val >> 56)); @@ -2118,7 +2118,7 @@ private static void putLongByByte(Object obj, long addr, long val, boolean bigEn UNSAFE.putByte(obj, addr + 3, (byte) (val >> 24)); UNSAFE.putByte(obj, addr + 2, (byte) (val >> 16)); UNSAFE.putByte(obj, addr + 1, (byte) (val >> 8)); - UNSAFE.putByte(obj, addr, (byte) (val)); + UNSAFE.putByte(obj, addr, (byte) val); } } @@ -2191,16 +2191,16 @@ private static void putCharByByte(long addr, char val, boolean bigEndian) private static int getIntByByte(long addr, boolean bigEndian) { if (bigEndian) { - return (((int) UNSAFE.getByte(addr)) << 24) | + return ((int) UNSAFE.getByte(addr) << 24) | (((int) UNSAFE.getByte(addr + 1) & 0xff) << 16) | (((int) UNSAFE.getByte(addr + 2) & 0xff) << 8) | - (((int) UNSAFE.getByte(addr + 3) & 0xff)); + ((int) UNSAFE.getByte(addr + 3) & 0xff); } else { - return (((int) UNSAFE.getByte(addr + 3)) << 24) | + return ((int) UNSAFE.getByte(addr + 3) << 24) | (((int) UNSAFE.getByte(addr + 2) & 0xff) << 16) | (((int) UNSAFE.getByte(addr + 1) & 0xff) << 8) | - (((int) UNSAFE.getByte(addr) & 0xff)); + ((int) UNSAFE.getByte(addr) & 0xff); } } @@ -2215,13 +2215,13 @@ private static void putIntByByte(long addr, int val, boolean bigEndian) UNSAFE.putByte(addr, (byte) (val >> 24)); UNSAFE.putByte(addr + 1, (byte) (val >> 16)); UNSAFE.putByte(addr + 2, (byte) (val >> 8)); - UNSAFE.putByte(addr + 3, (byte) (val)); + UNSAFE.putByte(addr + 3, (byte) val); } else { UNSAFE.putByte(addr + 3, (byte) (val >> 24)); UNSAFE.putByte(addr + 2, (byte) (val >> 16)); UNSAFE.putByte(addr + 1, (byte) (val >> 8)); - UNSAFE.putByte(addr, (byte) (val)); + UNSAFE.putByte(addr, (byte) val); } } @@ -2232,24 +2232,24 @@ private static void putIntByByte(long addr, int val, boolean bigEndian) private static long getLongByByte(long addr, boolean bigEndian) { if (bigEndian) { - return (((long) UNSAFE.getByte(addr)) << 56) | + return ((long) UNSAFE.getByte(addr) << 56) | (((long) UNSAFE.getByte(addr + 1) & 0xff) << 48) | (((long) UNSAFE.getByte(addr + 2) & 0xff) << 40) | (((long) UNSAFE.getByte(addr + 3) & 0xff) << 32) | (((long) UNSAFE.getByte(addr + 4) & 0xff) << 24) | (((long) UNSAFE.getByte(addr + 5) & 0xff) << 16) | (((long) UNSAFE.getByte(addr + 6) & 0xff) << 8) | - (((long) UNSAFE.getByte(addr + 7) & 0xff)); + ((long) UNSAFE.getByte(addr + 7) & 0xff); } else { - return (((long) UNSAFE.getByte(addr + 7)) << 56) | + return ((long) UNSAFE.getByte(addr + 7) << 56) | (((long) UNSAFE.getByte(addr + 6) & 0xff) << 48) | (((long) UNSAFE.getByte(addr + 5) & 0xff) << 40) | (((long) UNSAFE.getByte(addr + 4) & 0xff) << 32) | (((long) UNSAFE.getByte(addr + 3) & 0xff) << 24) | (((long) UNSAFE.getByte(addr + 2) & 0xff) << 16) | (((long) UNSAFE.getByte(addr + 1) & 0xff) << 8) | - (((long) UNSAFE.getByte(addr) & 0xff)); + ((long) UNSAFE.getByte(addr) & 0xff); } } @@ -2268,7 +2268,7 @@ private static void putLongByByte(long addr, long val, boolean bigEndian) UNSAFE.putByte(addr + 4, (byte) (val >> 24)); UNSAFE.putByte(addr + 5, (byte) (val >> 16)); UNSAFE.putByte(addr + 6, (byte) (val >> 8)); - UNSAFE.putByte(addr + 7, (byte) (val)); + UNSAFE.putByte(addr + 7, (byte) val); } else { UNSAFE.putByte(addr + 7, (byte) (val >> 56)); @@ -2278,7 +2278,7 @@ private static void putLongByByte(long addr, long val, boolean bigEndian) UNSAFE.putByte(addr + 3, (byte) (val >> 24)); UNSAFE.putByte(addr + 2, (byte) (val >> 16)); UNSAFE.putByte(addr + 1, (byte) (val >> 8)); - UNSAFE.putByte(addr, (byte) (val)); + UNSAFE.putByte(addr, (byte) val); } } } diff --git a/lib/trino-orc/src/main/java/io/trino/orc/OrcWriteValidation.java b/lib/trino-orc/src/main/java/io/trino/orc/OrcWriteValidation.java index a86292c4d579..1b53f5efb462 100644 --- a/lib/trino-orc/src/main/java/io/trino/orc/OrcWriteValidation.java +++ b/lib/trino-orc/src/main/java/io/trino/orc/OrcWriteValidation.java @@ -662,7 +662,7 @@ else if (TIMESTAMP_TZ_MICROS.equals(type) || TIMESTAMP_TZ_NANOS.equals(type)) { } else if (type instanceof DecimalType decimalType) { if (decimalType.isShort()) { - statisticsBuilder = new ShortDecimalStatisticsBuilder((decimalType).getScale()); + statisticsBuilder = new ShortDecimalStatisticsBuilder(decimalType.getScale()); } else { statisticsBuilder = new LongDecimalStatisticsBuilder(); diff --git a/lib/trino-orc/src/main/java/io/trino/orc/metadata/statistics/BloomFilter.java b/lib/trino-orc/src/main/java/io/trino/orc/metadata/statistics/BloomFilter.java index 480d09aa1d8e..21649346a372 100644 --- a/lib/trino-orc/src/main/java/io/trino/orc/metadata/statistics/BloomFilter.java +++ b/lib/trino-orc/src/main/java/io/trino/orc/metadata/statistics/BloomFilter.java @@ -213,7 +213,7 @@ public boolean testLong(long val) // http://web.archive.org/web/20071223173210/http://www.concentric.net/~Ttwang/tech/inthash.htm private static long getLongHash(long key) { - key = (~key) + (key << 21); // key = (key << 21) - key - 1; + key = ~key + (key << 21); // key = (key << 21) - key - 1; key ^= (key >> 24); key = (key + (key << 3)) + (key << 8); // key * 265 key ^= (key >> 14); diff --git a/lib/trino-orc/src/main/java/io/trino/orc/stream/BooleanInputStream.java b/lib/trino-orc/src/main/java/io/trino/orc/stream/BooleanInputStream.java index 7137d97595b8..32969063c034 100644 --- a/lib/trino-orc/src/main/java/io/trino/orc/stream/BooleanInputStream.java +++ b/lib/trino-orc/src/main/java/io/trino/orc/stream/BooleanInputStream.java @@ -181,7 +181,7 @@ public void getSetBits(byte[] vector, int batchSize) vector[offset + 4] = (byte) ((value & 8) >>> 3); vector[offset + 5] = (byte) ((value & 4) >>> 2); vector[offset + 6] = (byte) ((value & 2) >>> 1); - vector[offset + 7] = (byte) ((value & 1)); + vector[offset + 7] = (byte) (value & 1); offset += 8; } diff --git a/lib/trino-orc/src/main/java/io/trino/orc/stream/LongInputStreamV1.java b/lib/trino-orc/src/main/java/io/trino/orc/stream/LongInputStreamV1.java index 8beaf52fcec5..fb8594501211 100644 --- a/lib/trino-orc/src/main/java/io/trino/orc/stream/LongInputStreamV1.java +++ b/lib/trino-orc/src/main/java/io/trino/orc/stream/LongInputStreamV1.java @@ -88,7 +88,7 @@ public long next() readValues(); } if (repeat) { - result = literals[0] + (used++) * (long) delta; + result = literals[0] + used++ * (long) delta; } else { result = literals[used++]; diff --git a/lib/trino-orc/src/main/java/io/trino/orc/stream/LongOutputStreamV2.java b/lib/trino-orc/src/main/java/io/trino/orc/stream/LongOutputStreamV2.java index 5e4bbe95c7d0..b262fc7c3992 100644 --- a/lib/trino-orc/src/main/java/io/trino/orc/stream/LongOutputStreamV2.java +++ b/lib/trino-orc/src/main/java/io/trino/orc/stream/LongOutputStreamV2.java @@ -1096,7 +1096,7 @@ private static void unrolledBitPack1(long[] input, int offset, int len, SliceOut | ((input[i + 4] & 1) << 3) | ((input[i + 5] & 1) << 2) | ((input[i + 6] & 1) << 1) - | (input[i + 7]) & 1); + | (input[i + 7] & 1)); output.write(val); val = 0; } @@ -1122,7 +1122,7 @@ private static void unrolledBitPack2(long[] input, int offset, int len, SliceOut val = (int) (val | ((input[i] & 3) << 6) | ((input[i + 1] & 3) << 4) | ((input[i + 2] & 3) << 2) - | (input[i + 3]) & 3); + | (input[i + 3] & 3)); output.write(val); val = 0; } @@ -1145,7 +1145,7 @@ private static void unrolledBitPack4(long[] input, int offset, int len, SliceOut final int endUnroll = endOffset - remainder; int val = 0; for (int i = offset; i < endUnroll; i = i + numHops) { - val = (int) (val | ((input[i] & 15) << 4) | (input[i + 1]) & 15); + val = (int) (val | ((input[i] & 15) << 4) | input[i + 1] & 15); output.write(val); val = 0; } diff --git a/lib/trino-orc/src/test/java/io/trino/orc/BenchmarkColumnReaders.java b/lib/trino-orc/src/test/java/io/trino/orc/BenchmarkColumnReaders.java index 0aa631aae234..c54da217b737 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/BenchmarkColumnReaders.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/BenchmarkColumnReaders.java @@ -1167,7 +1167,7 @@ private Iterator createValues() { List values = new ArrayList<>(); for (int i = 0; i < ROWS; ++i) { - values.add(SqlTimestamp.fromMillis(3, (random.nextLong()))); + values.add(SqlTimestamp.fromMillis(3, random.nextLong())); } return values.iterator(); } diff --git a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongStreamV1.java b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongStreamV1.java index 18283d4252e3..c39a7e1e3dcc 100644 --- a/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongStreamV1.java +++ b/lib/trino-orc/src/test/java/io/trino/orc/stream/TestLongStreamV1.java @@ -42,7 +42,7 @@ public void test() group = new ArrayList<>(); for (int i = 0; i < 1000; i++) { - group.add((long) (i)); + group.add((long) i); } groups.add(group); diff --git a/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetTypeUtils.java b/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetTypeUtils.java index 4797f6526a92..cc59706413ca 100644 --- a/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetTypeUtils.java +++ b/lib/trino-parquet/src/main/java/io/trino/parquet/ParquetTypeUtils.java @@ -53,12 +53,12 @@ private ParquetTypeUtils() {} public static List getColumns(MessageType fileSchema, MessageType requestedSchema) { - return ImmutableList.copyOf((new ColumnIOFactory()).getColumnIO(requestedSchema, fileSchema, true).getLeaves()); + return ImmutableList.copyOf(new ColumnIOFactory().getColumnIO(requestedSchema, fileSchema, true).getLeaves()); } public static MessageColumnIO getColumnIO(MessageType fileSchema, MessageType requestedSchema) { - return (new ColumnIOFactory()).getColumnIO(requestedSchema, fileSchema, true); + return new ColumnIOFactory().getColumnIO(requestedSchema, fileSchema, true); } public static GroupColumnIO getMapKeyValueColumn(GroupColumnIO groupColumnIO) diff --git a/lib/trino-record-decoder/src/main/java/io/trino/decoder/avro/AvroRowDecoderFactory.java b/lib/trino-record-decoder/src/main/java/io/trino/decoder/avro/AvroRowDecoderFactory.java index a02aebd590c1..bf5bd6010385 100644 --- a/lib/trino-record-decoder/src/main/java/io/trino/decoder/avro/AvroRowDecoderFactory.java +++ b/lib/trino-record-decoder/src/main/java/io/trino/decoder/avro/AvroRowDecoderFactory.java @@ -51,7 +51,7 @@ public RowDecoder create(ConnectorSession session, RowDecoderSpec rowDecoderSpec } String dataSchema = requireNonNull(rowDecoderSpec.decoderParams().get(DATA_SCHEMA), format("%s cannot be null", DATA_SCHEMA)); - Schema parsedSchema = (new Schema.Parser()).parse(dataSchema); + Schema parsedSchema = new Schema.Parser().parse(dataSchema); if (parsedSchema.getType().equals(Schema.Type.RECORD)) { AvroReaderSupplier avroReaderSupplier = avroReaderSupplierFactory.create(parsedSchema); AvroDeserializer dataDecoder = avroDeserializerFactory.create(avroReaderSupplier); diff --git a/lib/trino-record-decoder/src/main/java/io/trino/decoder/json/ISO8601JsonFieldDecoder.java b/lib/trino-record-decoder/src/main/java/io/trino/decoder/json/ISO8601JsonFieldDecoder.java index 9054fd75115b..4acf2da02d85 100644 --- a/lib/trino-record-decoder/src/main/java/io/trino/decoder/json/ISO8601JsonFieldDecoder.java +++ b/lib/trino-record-decoder/src/main/java/io/trino/decoder/json/ISO8601JsonFieldDecoder.java @@ -131,7 +131,7 @@ public long getLong() } if (columnType.equals(TIME_TZ_MILLIS)) { TemporalAccessor parseResult = ISO_OFFSET_TIME.parse(textValue); - return packTimeWithTimeZone((long) (parseResult.get(MILLI_OF_DAY)) * NANOSECONDS_PER_MILLISECOND, ZoneOffset.from(parseResult).getTotalSeconds() / 60); + return packTimeWithTimeZone((long) parseResult.get(MILLI_OF_DAY) * NANOSECONDS_PER_MILLISECOND, ZoneOffset.from(parseResult).getTotalSeconds() / 60); } if (columnType == DATE) { return ISO_DATE.parse(textValue).getLong(EPOCH_DAY); diff --git a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestDefaultJsonFieldDecoder.java b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestDefaultJsonFieldDecoder.java index 82d97e5f8baa..efd62feb550d 100644 --- a/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestDefaultJsonFieldDecoder.java +++ b/lib/trino-record-decoder/src/test/java/io/trino/decoder/json/TestDefaultJsonFieldDecoder.java @@ -65,8 +65,8 @@ public void testDecode() tester.assertDecodedAs("" + Long.MAX_VALUE, BIGINT, Long.MAX_VALUE); tester.assertDecodedAs("0", BIGINT, 0); tester.assertDecodedAs("\"1000\"", BIGINT, 1000); - assertCouldNotParse("" + (BigInteger.valueOf(Long.MIN_VALUE).subtract(BigInteger.ONE)), BIGINT); - assertCouldNotParse("" + (BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE)), BIGINT); + assertCouldNotParse("" + BigInteger.valueOf(Long.MIN_VALUE).subtract(BigInteger.ONE), BIGINT); + assertCouldNotParse("" + BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE), BIGINT); tester.assertDecodedAs("false", BOOLEAN, false); tester.assertDecodedAs("true", BOOLEAN, true); diff --git a/plugin/trino-atop/src/main/java/io/trino/plugin/atop/AtopTable.java b/plugin/trino-atop/src/main/java/io/trino/plugin/atop/AtopTable.java index 04e642513005..26902c63a29c 100644 --- a/plugin/trino-atop/src/main/java/io/trino/plugin/atop/AtopTable.java +++ b/plugin/trino-atop/src/main/java/io/trino/plugin/atop/AtopTable.java @@ -123,17 +123,17 @@ public static class AtopColumn throw new UnsupportedOperationException(); }); - public static final AtopColumn START_TIME = new AtopColumn("start_time", TIMESTAMP_TZ_MILLIS.getTypeSignature(), ((fields, type, builder, session) -> { + public static final AtopColumn START_TIME = new AtopColumn("start_time", TIMESTAMP_TZ_MILLIS.getTypeSignature(), (fields, type, builder, session) -> { long millisUtc = Long.valueOf(fields.get(2)) * 1000; long durationMillis = Long.valueOf(fields.get(5)) * 1000; long value = packDateTimeWithZone(millisUtc - durationMillis, session.getTimeZoneKey()); type.writeLong(builder, value); - })); + }); - public static final AtopColumn END_TIME = new AtopColumn("end_time", TIMESTAMP_TZ_MILLIS.getTypeSignature(), ((fields, type, builder, session) -> { + public static final AtopColumn END_TIME = new AtopColumn("end_time", TIMESTAMP_TZ_MILLIS.getTypeSignature(), (fields, type, builder, session) -> { long millisUtc = Long.valueOf(fields.get(2)) * 1000; type.writeLong(builder, packDateTimeWithZone(millisUtc, session.getTimeZoneKey())); - })); + }); private final String name; private final TypeSignature type; @@ -168,12 +168,12 @@ public interface AtopColumnParser static AtopColumnParser varcharParser(int index) { - return ((fields, type, builder, session) -> type.writeSlice(builder, Slices.utf8Slice(fields.get(index)))); + return (fields, type, builder, session) -> type.writeSlice(builder, Slices.utf8Slice(fields.get(index))); } static AtopColumnParser bigintParser(int index) { - return ((fields, type, builder, session) -> type.writeLong(builder, Long.valueOf(fields.get(index)))); + return (fields, type, builder, session) -> type.writeLong(builder, Long.valueOf(fields.get(index))); } } } diff --git a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/BaseJdbcClient.java b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/BaseJdbcClient.java index c4a208d0ea65..a53f6651b8a3 100644 --- a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/BaseJdbcClient.java +++ b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/BaseJdbcClient.java @@ -304,7 +304,7 @@ public List getColumns(ConnectorSession session, JdbcTableHand List columns = new ArrayList<>(); while (resultSet.next()) { // skip if table doesn't match expected - if (!(Objects.equals(remoteTableName, getRemoteTable(resultSet)))) { + if (!Objects.equals(remoteTableName, getRemoteTable(resultSet))) { continue; } allColumns++; diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryMetadata.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryMetadata.java index 295bbc2f9f8f..c0ad16f924a0 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryMetadata.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryMetadata.java @@ -289,7 +289,7 @@ public ConnectorTableMetadata getTableMetadata(ConnectorSession session, Connect { BigQueryClient client = bigQueryClientFactory.create(session); log.debug("getTableMetadata(session=%s, tableHandle=%s)", session, tableHandle); - BigQueryTableHandle handle = ((BigQueryTableHandle) tableHandle); + BigQueryTableHandle handle = (BigQueryTableHandle) tableHandle; List columns = client.getColumns(handle).stream() .map(BigQueryColumnHandle::getColumnMetadata) diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryProxyConfig.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryProxyConfig.java index fa84553a8553..dcf56974c478 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryProxyConfig.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryProxyConfig.java @@ -143,7 +143,7 @@ void validate() throw exception("BigQuery RPC proxy URI cannot specify path"); } - if ((username.isPresent() && password.isEmpty())) { + if (username.isPresent() && password.isEmpty()) { throw exception("bigquery.rpc-proxy.username was set but bigquery.rpc-proxy.password is empty"); } diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryQueryPageSource.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryQueryPageSource.java index 315bab7086ec..cdfef35e3e8c 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryQueryPageSource.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryQueryPageSource.java @@ -194,7 +194,7 @@ else if (type.equals(TIME_MICROS)) { type.writeLong(output, rounded); } else if (type.equals(TIMESTAMP_MICROS)) { - type.writeLong(output, toTrinoTimestamp((value.getStringValue()))); + type.writeLong(output, toTrinoTimestamp(value.getStringValue())); } else { throw new TrinoException(GENERIC_INTERNAL_ERROR, format("Unhandled type for %s: %s", javaType.getSimpleName(), type)); diff --git a/plugin/trino-cassandra/src/main/java/io/trino/plugin/cassandra/CassandraClientConfig.java b/plugin/trino-cassandra/src/main/java/io/trino/plugin/cassandra/CassandraClientConfig.java index 46de506aff0d..c179894712a1 100644 --- a/plugin/trino-cassandra/src/main/java/io/trino/plugin/cassandra/CassandraClientConfig.java +++ b/plugin/trino-cassandra/src/main/java/io/trino/plugin/cassandra/CassandraClientConfig.java @@ -103,7 +103,7 @@ public int getNativeProtocolPort() return nativeProtocolPort; } - @Config(("cassandra.native-protocol-port")) + @Config("cassandra.native-protocol-port") public CassandraClientConfig setNativeProtocolPort(int nativeProtocolPort) { this.nativeProtocolPort = nativeProtocolPort; diff --git a/plugin/trino-cassandra/src/main/java/io/trino/plugin/cassandra/CassandraClusteringPredicatesExtractor.java b/plugin/trino-cassandra/src/main/java/io/trino/plugin/cassandra/CassandraClusteringPredicatesExtractor.java index 2e3e5db9c98f..f59baa2c6109 100644 --- a/plugin/trino-cassandra/src/main/java/io/trino/plugin/cassandra/CassandraClusteringPredicatesExtractor.java +++ b/plugin/trino-cassandra/src/main/java/io/trino/plugin/cassandra/CassandraClusteringPredicatesExtractor.java @@ -51,7 +51,7 @@ public String getClusteringKeyPredicates() public TupleDomain getUnenforcedConstraints() { - return predicates.filter(((columnHandle, domain) -> !clusteringPushDownResult.hasBeenFullyPushed(columnHandle))); + return predicates.filter((columnHandle, domain) -> !clusteringPushDownResult.hasBeenFullyPushed(columnHandle)); } private ClusteringPushDownResult getClusteringKeysSet(List clusteringColumns, TupleDomain predicates, Version cassandraVersion) diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestJsonCassandraHandles.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestJsonCassandraHandles.java index a4c430a0b505..81be9abd7a5f 100644 --- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestJsonCassandraHandles.java +++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestJsonCassandraHandles.java @@ -123,7 +123,7 @@ public void testTableHandleDeserialize() { String json = OBJECT_MAPPER.writeValueAsString(TABLE_HANDLE_AS_MAP); - CassandraNamedRelationHandle tableHandle = (OBJECT_MAPPER.readValue(json, CassandraTableHandle.class)).getRequiredNamedRelation(); + CassandraNamedRelationHandle tableHandle = OBJECT_MAPPER.readValue(json, CassandraTableHandle.class).getRequiredNamedRelation(); assertThat(tableHandle.getSchemaName()).isEqualTo("cassandra_schema"); assertThat(tableHandle.getTableName()).isEqualTo("cassandra_table"); @@ -137,7 +137,7 @@ public void testTable2HandleDeserialize() { String json = OBJECT_MAPPER.writeValueAsString(TABLE2_HANDLE_AS_MAP); - CassandraNamedRelationHandle tableHandle = (OBJECT_MAPPER.readValue(json, CassandraTableHandle.class)).getRequiredNamedRelation(); + CassandraNamedRelationHandle tableHandle = OBJECT_MAPPER.readValue(json, CassandraTableHandle.class).getRequiredNamedRelation(); assertThat(tableHandle.getSchemaName()).isEqualTo("cassandra_schema"); assertThat(tableHandle.getTableName()).isEqualTo("cassandra_table"); diff --git a/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/BaseClickHouseTypeMapping.java b/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/BaseClickHouseTypeMapping.java index 12a08e8dc9c2..784c908f3369 100644 --- a/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/BaseClickHouseTypeMapping.java +++ b/plugin/trino-clickhouse/src/test/java/io/trino/plugin/clickhouse/BaseClickHouseTypeMapping.java @@ -740,7 +740,7 @@ private void testClickHouseDateTimeMinMaxValues(String timestamp) for (ZoneId timeZoneId : timezones()) { Session session = Session.builder(getSession()) - .setTimeZoneKey(TimeZoneKey.getTimeZoneKey((timeZoneId).getId())) + .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(timeZoneId.getId())) .build(); dateTests1 .execute(getQueryRunner(), session, trinoCreateAsSelect(session, "test_timestamp")) diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/AbstractDeltaLakePageSink.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/AbstractDeltaLakePageSink.java index e8b7b8e42f43..b4f6c3e86c4b 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/AbstractDeltaLakePageSink.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/AbstractDeltaLakePageSink.java @@ -318,8 +318,8 @@ private void writePage(Page page) writer.appendRows(pageForWriter); - writtenBytes += (writer.getWrittenBytes() - currentWritten); - memoryUsage += (writer.getMemoryUsage() - currentMemory); + writtenBytes += writer.getWrittenBytes() - currentWritten; + memoryUsage += writer.getMemoryUsage() - currentMemory; } } diff --git a/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/ElasticsearchMetadata.java b/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/ElasticsearchMetadata.java index adcf2a1e0bce..ef06f4372aae 100644 --- a/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/ElasticsearchMetadata.java +++ b/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/ElasticsearchMetadata.java @@ -529,7 +529,7 @@ public Optional> applyFilter(C Object pattern = ((Constant) arguments.get(1)).getValue(); Optional escape = Optional.empty(); if (arguments.size() == 3) { - escape = Optional.of((Slice) (((Constant) arguments.get(2)).getValue())); + escape = Optional.of((Slice) ((Constant) arguments.get(2)).getValue()); } if (!newRegexes.containsKey(columnName) && pattern instanceof Slice) { @@ -537,7 +537,7 @@ public Optional> applyFilter(C if (metadata.getSchema() .getFields().stream() .anyMatch(field -> columnName.equals(field.getName()) && field.getType() instanceof PrimitiveType && "keyword".equals(((PrimitiveType) field.getType()).getName()))) { - newRegexes.put(columnName, likeToRegexp(((Slice) pattern), escape)); + newRegexes.put(columnName, likeToRegexp((Slice) pattern, escape)); continue; } } diff --git a/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/ElasticsearchQueryBuilder.java b/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/ElasticsearchQueryBuilder.java index 86c24f572c18..9cbad441af70 100644 --- a/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/ElasticsearchQueryBuilder.java +++ b/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/ElasticsearchQueryBuilder.java @@ -70,7 +70,7 @@ public static QueryBuilder buildSearchQuery(TupleDomain queryBuilder.filter(new BoolQueryBuilder().must(((new RegexpQueryBuilder(name, value)))))); + regexes.forEach((name, value) -> queryBuilder.filter(new BoolQueryBuilder().must(new RegexpQueryBuilder(name, value)))); query.map(QueryStringQueryBuilder::new) .ifPresent(queryBuilder::must); diff --git a/plugin/trino-exchange-filesystem/src/main/java/io/trino/plugin/exchange/filesystem/azure/AzureBlobFileSystemExchangeStorage.java b/plugin/trino-exchange-filesystem/src/main/java/io/trino/plugin/exchange/filesystem/azure/AzureBlobFileSystemExchangeStorage.java index a72761317ade..4683ad8920e5 100644 --- a/plugin/trino-exchange-filesystem/src/main/java/io/trino/plugin/exchange/filesystem/azure/AzureBlobFileSystemExchangeStorage.java +++ b/plugin/trino-exchange-filesystem/src/main/java/io/trino/plugin/exchange/filesystem/azure/AzureBlobFileSystemExchangeStorage.java @@ -221,7 +221,7 @@ private ListenableFuture>> listObjectsRecursively(U return toListenableFuture(blobServiceAsyncClient .getBlobContainerAsyncClient(containerName) - .listBlobsByHierarchy(null, (new ListBlobsOptions()).setPrefix(directoryPath)) + .listBlobsByHierarchy(null, new ListBlobsOptions().setPrefix(directoryPath)) .byPage() .collectList() .toFuture()); diff --git a/plugin/trino-exchange-filesystem/src/main/java/io/trino/plugin/exchange/filesystem/s3/S3FileSystemExchangeStorage.java b/plugin/trino-exchange-filesystem/src/main/java/io/trino/plugin/exchange/filesystem/s3/S3FileSystemExchangeStorage.java index 39a7c666d88c..74054d729301 100644 --- a/plugin/trino-exchange-filesystem/src/main/java/io/trino/plugin/exchange/filesystem/s3/S3FileSystemExchangeStorage.java +++ b/plugin/trino-exchange-filesystem/src/main/java/io/trino/plugin/exchange/filesystem/s3/S3FileSystemExchangeStorage.java @@ -263,10 +263,10 @@ public ListenableFuture deleteRecursively(List directories) for (URI dir : directories) { ImmutableList.Builder keys = ImmutableList.builder(); ListenableFuture> listObjectsFuture = Futures.transform( - toListenableFuture((listObjectsRecursively(dir) + toListenableFuture(listObjectsRecursively(dir) .subscribe(listObjectsV2Response -> listObjectsV2Response.contents().stream() .map(S3Object::key) - .forEach(keys::add)))), + .forEach(keys::add))), ignored -> keys.build(), directExecutor()); bucketToListObjectsFuturesBuilder.put(getBucketName(dir), listObjectsFuture); @@ -309,7 +309,7 @@ public ListenableFuture> listFilesRecursively(URI dir) { ImmutableList.Builder fileStatuses = ImmutableList.builder(); return stats.getListFilesRecursively().record(Futures.transform( - toListenableFuture((listObjectsRecursively(dir) + toListenableFuture(listObjectsRecursively(dir) .subscribe(listObjectsV2Response -> { for (S3Object s3Object : listObjectsV2Response.contents()) { URI uri; @@ -321,7 +321,7 @@ public ListenableFuture> listFilesRecursively(URI dir) } fileStatuses.add(new FileStatus(uri.toString(), s3Object.size())); } - }))), + })), ignored -> fileStatuses.build(), directExecutor())); } diff --git a/plugin/trino-geospatial/src/main/java/io/trino/plugin/geospatial/GeoFunctions.java b/plugin/trino-geospatial/src/main/java/io/trino/plugin/geospatial/GeoFunctions.java index 716bcce64cb1..cd36a36e0044 100644 --- a/plugin/trino-geospatial/src/main/java/io/trino/plugin/geospatial/GeoFunctions.java +++ b/plugin/trino-geospatial/src/main/java/io/trino/plugin/geospatial/GeoFunctions.java @@ -700,7 +700,7 @@ private static List interpolatePoints(OGCGeometry geometry, double fracti return Collections.singletonList(path.getPoint(0)); } if (fractionStep == 1) { - return Collections.singletonList((path.getPoint(path.getPointCount() - 1))); + return Collections.singletonList(path.getPoint(path.getPointCount() - 1)); } int pointCount = repeated ? (int) Math.floor(1 / fractionStep) : 1; diff --git a/plugin/trino-geospatial/src/test/java/io/trino/plugin/geospatial/TestSpatialJoinOperator.java b/plugin/trino-geospatial/src/test/java/io/trino/plugin/geospatial/TestSpatialJoinOperator.java index 1fc1156ea8a4..e10057da4dc4 100644 --- a/plugin/trino-geospatial/src/test/java/io/trino/plugin/geospatial/TestSpatialJoinOperator.java +++ b/plugin/trino-geospatial/src/test/java/io/trino/plugin/geospatial/TestSpatialJoinOperator.java @@ -270,12 +270,12 @@ public void testYield() // force a yield for every match AtomicInteger filterFunctionCalls = new AtomicInteger(); - InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction(( + InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction( (leftPosition, leftPage, rightPosition, rightPage) -> { filterFunctionCalls.incrementAndGet(); driverContext.getYieldSignal().forceYieldForTesting(); return true; - })); + }); RowPagesBuilder buildPages = rowPagesBuilder(ImmutableList.of(GEOMETRY, VARCHAR)) .row(POLYGON_A, "A") diff --git a/plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/ptf/Sheet.java b/plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/ptf/Sheet.java index 3bb9e6d7996e..e693a666e539 100644 --- a/plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/ptf/Sheet.java +++ b/plugin/trino-google-sheets/src/main/java/io/trino/plugin/google/sheets/ptf/Sheet.java @@ -114,7 +114,7 @@ public TableFunctionAnalysis analyze( List fields = metadata.getColumnHandles(session, tableHandle).entrySet().stream() .map(entry -> new Descriptor.Field( entry.getKey(), - Optional.of(((SheetsColumnHandle) (entry.getValue())).getColumnType()))) + Optional.of(((SheetsColumnHandle) entry.getValue()).getColumnType()))) .collect(toImmutableList()); Descriptor returnedType = new Descriptor(fields); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveTableProperties.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveTableProperties.java index 321a57bb977a..4b2a5d4478e4 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveTableProperties.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveTableProperties.java @@ -249,7 +249,7 @@ public static Optional getBucketProperty(Map List bucketedBy = getBucketedBy(tableProperties); List sortedBy = getSortedBy(tableProperties); int bucketCount = (Integer) tableProperties.get(BUCKET_COUNT_PROPERTY); - if ((bucketedBy.isEmpty()) && (bucketCount == 0)) { + if (bucketedBy.isEmpty() && (bucketCount == 0)) { if (!sortedBy.isEmpty()) { throw new TrinoException(INVALID_TABLE_PROPERTY, format("%s may be specified only when %s is specified", SORTED_BY_PROPERTY, BUCKETED_BY_PROPERTY)); } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/MergeFileWriter.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/MergeFileWriter.java index 0a27cb663f75..78c1cf6ce0f3 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/MergeFileWriter.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/MergeFileWriter.java @@ -177,8 +177,8 @@ public long getWrittenBytes() @Override public long getMemoryUsage() { - return (deleteFileWriter.map(FileWriter::getMemoryUsage).orElse(0L)) + - (insertFileWriter.map(FileWriter::getMemoryUsage).orElse(0L)); + return deleteFileWriter.map(FileWriter::getMemoryUsage).orElse(0L) + + insertFileWriter.map(FileWriter::getMemoryUsage).orElse(0L); } @Override @@ -210,8 +210,8 @@ public void rollback() @Override public long getValidationCpuNanos() { - return (deleteFileWriter.map(FileWriter::getValidationCpuNanos).orElse(0L)) + - (insertFileWriter.map(FileWriter::getValidationCpuNanos).orElse(0L)); + return deleteFileWriter.map(FileWriter::getValidationCpuNanos).orElse(0L) + + insertFileWriter.map(FileWriter::getValidationCpuNanos).orElse(0L); } public PartitionUpdateAndMergeResults getPartitionUpdateAndMergeResults(PartitionUpdate partitionUpdate) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/HiveAvroTypeManager.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/HiveAvroTypeManager.java index adf95a909e8b..9c1bf3af943d 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/HiveAvroTypeManager.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/HiveAvroTypeManager.java @@ -133,14 +133,14 @@ public Optional> overrideBuildingFunctionForSch case VARCHAR_TYPE_LOGICAL_NAME, CHAR_TYPE_LOGICAL_NAME -> { Type type = getHiveLogicalVarCharOrCharType(schema, nonNativeAvroLogicalType); if (nonNativeAvroLogicalType.getLogicalTypeName().equals(VARCHAR_TYPE_LOGICAL_NAME)) { - yield Optional.of(((blockBuilder, obj) -> { + yield Optional.of((blockBuilder, obj) -> { type.writeSlice(blockBuilder, Varchars.truncateToLength(Slices.utf8Slice(obj.toString()), type)); - })); + }); } else { - yield Optional.of(((blockBuilder, obj) -> { + yield Optional.of((blockBuilder, obj) -> { type.writeSlice(blockBuilder, Chars.truncateToLengthAndTrimSpaces(Slices.utf8Slice(obj.toString()), type)); - })); + }); } } default -> Optional.empty(); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/DecoratedHiveMetastoreModule.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/DecoratedHiveMetastoreModule.java index d7b5cd995868..59a948125eb5 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/DecoratedHiveMetastoreModule.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/DecoratedHiveMetastoreModule.java @@ -118,7 +118,7 @@ public HiveMetastore createMetastore(Optional identity) public static Optional createHiveMetastore(HiveMetastoreFactory metastoreFactory) { if (metastoreFactory instanceof CachingHiveMetastoreFactory) { - return Optional.of((((CachingHiveMetastoreFactory) metastoreFactory).getMetastore())); + return Optional.of(((CachingHiveMetastoreFactory) metastoreFactory).getMetastore()); } return Optional.empty(); } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftHiveMetastore.java index 78342b38b327..0ae82fd22182 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/ThriftHiveMetastore.java @@ -1467,7 +1467,7 @@ public void grantTablePrivileges(String databaseName, String tableName, String t HivePrivilegeInfo requestedPrivilege = getOnlyElement(parsePrivilege(iterator.next(), Optional.empty())); for (HivePrivilegeInfo existingPrivilege : existingPrivileges) { - if ((requestedPrivilege.isContainedIn(existingPrivilege))) { + if (requestedPrivilege.isContainedIn(existingPrivilege)) { iterator.remove(); } else if (existingPrivilege.isContainedIn(requestedPrivilege)) { @@ -1650,12 +1650,12 @@ public void sendTransactionHeartbeat(long transactionId) try { retry() .stopOnIllegalExceptions() - .run("sendTransactionHeartbeat", (() -> { + .run("sendTransactionHeartbeat", () -> { try (ThriftMetastoreClient metastoreClient = createMetastoreClient()) { metastoreClient.sendTransactionHeartbeat(transactionId); } return null; - })); + }); } catch (TException e) { throw new TrinoException(HIVE_METASTORE_ERROR, e); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveLocal.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveLocal.java index ea4989f2e928..7348fd743b1c 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveLocal.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveLocal.java @@ -121,8 +121,8 @@ public void initialize() protected void createTestTables() throws Exception { - Location location = Location.of((metastoreClient.getDatabase(database).orElseThrow() - .getLocation().orElseThrow())); + Location location = Location.of(metastoreClient.getDatabase(database).orElseThrow() + .getLocation().orElseThrow()); createTestTable( // Matches create-test.sql » trino_test_partition_format diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestReaderProjectionsAdapter.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestReaderProjectionsAdapter.java index e4bd2959a706..bc22b46993f6 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestReaderProjectionsAdapter.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestReaderProjectionsAdapter.java @@ -103,7 +103,7 @@ public void testLazyDereferenceProjectionLoading() Block lazyBlockLevel1 = inputPage.getBlock(0); assertThat(lazyBlockLevel1 instanceof LazyBlock).isTrue(); assertThat(lazyBlockLevel1.isLoaded()).isFalse(); - RowBlock rowBlockLevel1 = ((RowBlock) (((LazyBlock) lazyBlockLevel1).getBlock())); + RowBlock rowBlockLevel1 = (RowBlock) ((LazyBlock) lazyBlockLevel1).getBlock(); assertThat(rowBlockLevel1.isLoaded()).isFalse(); // Assertion for "col.f_row_0" and col.f_bigint_0" @@ -112,7 +112,7 @@ public void testLazyDereferenceProjectionLoading() Block lazyBlockLevel2 = rowBlockLevel1.getFieldBlock(0); assertThat(lazyBlockLevel2 instanceof LazyBlock).isTrue(); - RowBlock rowBlockLevel2 = ((RowBlock) (((LazyBlock) lazyBlockLevel2).getBlock())); + RowBlock rowBlockLevel2 = ((RowBlock) ((LazyBlock) lazyBlockLevel2).getBlock()); assertThat(rowBlockLevel2.isLoaded()).isFalse(); // Assertion for "col.f_row_0.f_bigint_0" and "col.f_row_0.f_bigint_1" assertThat(rowBlockLevel2.getFieldBlock(0).isLoaded()).isTrue(); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/TestTransactionScopeCachingDirectoryLister.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/TestTransactionScopeCachingDirectoryLister.java index a34467a51076..f6a7008c6ded 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/TestTransactionScopeCachingDirectoryLister.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/fs/TestTransactionScopeCachingDirectoryLister.java @@ -104,7 +104,7 @@ public void testConcurrentDirectoryListing() // due to Token being a key in segmented cache. TransactionScopeCachingDirectoryLister cachingLister = (TransactionScopeCachingDirectoryLister) new TransactionScopeCachingDirectoryListerFactory(DataSize.ofBytes(500), Optional.of(1)).get(countingLister); - assertFiles(new DirectoryListingFilter(path2, (cachingLister.listFilesRecursively(null, TABLE, path2)), true), ImmutableList.of(thirdFile)); + assertFiles(new DirectoryListingFilter(path2, cachingLister.listFilesRecursively(null, TABLE, path2), true), ImmutableList.of(thirdFile)); assertThat(countingLister.getListCount()).isEqualTo(1); // listing path2 again shouldn't increase listing count diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestGlueHiveMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestGlueHiveMetastore.java index a81c4374cd45..5a1586a6eb6a 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestGlueHiveMetastore.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/glue/TestGlueHiveMetastore.java @@ -1510,7 +1510,7 @@ private void doGetPartitionsFilterTest( List> expectedValuesList) throws Exception { - try (CloseableSchamaTableName closeableTableName = new CloseableSchamaTableName(temporaryTable(("get_partitions")))) { + try (CloseableSchamaTableName closeableTableName = new CloseableSchamaTableName(temporaryTable("get_partitions"))) { SchemaTableName tableName = closeableTableName.getSchemaTableName(); createDummyPartitionedTable(tableName, columnMetadata, partitionColumnNames, partitionValues); HiveMetastore metastoreClient = getMetastoreClient(); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestThriftSparkMetastoreUtil.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestThriftSparkMetastoreUtil.java index ebc67f7d3f1f..6b356bfb27a5 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestThriftSparkMetastoreUtil.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/thrift/TestThriftSparkMetastoreUtil.java @@ -144,12 +144,12 @@ public void testSparkDateStatsToColumnStatistics() "spark.sql.statistics.colStats.c_date.max", "2030-12-31"); HiveColumnStatistics actualNotExists = fromMetastoreColumnStatistics(new FieldSchema("c_date_not_exists", DATE_TYPE_NAME, null), columnStatistics, 10); assertThat(actualNotExists).isEqualTo(HiveColumnStatistics.builder() - .setDateStatistics((new DateStatistics(Optional.empty(), Optional.empty()))) + .setDateStatistics(new DateStatistics(Optional.empty(), Optional.empty())) .build()); HiveColumnStatistics actual = fromMetastoreColumnStatistics(new FieldSchema("c_date", DATE_TYPE_NAME, null), columnStatistics, 10); assertThat(actual).isEqualTo(HiveColumnStatistics.builder() - .setDateStatistics((new DateStatistics(Optional.of(LocalDate.of(2000, 1, 1)), Optional.of(LocalDate.of(2030, 12, 31))))) + .setDateStatistics(new DateStatistics(Optional.of(LocalDate.of(2000, 1, 1)), Optional.of(LocalDate.of(2030, 12, 31)))) .setNullsCount(3) .setDistinctValuesCount(7) .build()); diff --git a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/files/FileSlice.java b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/files/FileSlice.java index a9d93624465d..3956bd2f6064 100644 --- a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/files/FileSlice.java +++ b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/files/FileSlice.java @@ -56,7 +56,7 @@ public Optional getBaseFile() public boolean isEmpty() { - return (baseFile == null) && (logFiles.isEmpty()); + return (baseFile == null) && logFiles.isEmpty(); } @Override diff --git a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/table/HudiTableFileSystemView.java b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/table/HudiTableFileSystemView.java index 6840be3b3b36..e041cf018be3 100644 --- a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/table/HudiTableFileSystemView.java +++ b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/table/HudiTableFileSystemView.java @@ -452,7 +452,7 @@ private boolean isBaseFileDueToPendingCompaction(HudiBaseFile baseFile) Optional> compactionWithInstantTime = getPendingCompactionOperationWithInstant(new HudiFileGroupId(partitionPath, baseFile.getFileId())); - return (compactionWithInstantTime.isPresent()) && (null != compactionWithInstantTime.get().getKey()) + return compactionWithInstantTime.isPresent() && (null != compactionWithInstantTime.get().getKey()) && baseFile.getCommitTime().equals(compactionWithInstantTime.get().getKey()); } diff --git a/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestingIgniteContainer.java b/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestingIgniteContainer.java index 56a99c84395b..ec8a29edad66 100644 --- a/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestingIgniteContainer.java +++ b/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestingIgniteContainer.java @@ -31,7 +31,7 @@ public TestingIgniteContainer() super(DockerImageName.parse("apacheignite/ignite:2.9.0")); this.withExposedPorts(10800); this.withEnv("IGNITE_SQL_MERGE_TABLE_MAX_SIZE", IGNITE_SQL_MERGE_TABLE_MAX_SIZE).withStartupAttempts(10); - this.waitingFor((new HttpWaitStrategy()).forStatusCode(200).forResponsePredicate("Ok."::equals).withStartupTimeout(Duration.ofMinutes(1L))); + this.waitingFor(new HttpWaitStrategy().forStatusCode(200).forResponsePredicate("Ok."::equals).withStartupTimeout(Duration.ofMinutes(1L))); } @Override diff --git a/plugin/trino-jmx/src/main/java/io/trino/plugin/jmx/JmxRecordSetProvider.java b/plugin/trino-jmx/src/main/java/io/trino/plugin/jmx/JmxRecordSetProvider.java index 14189fd42f35..d80409e0b71d 100644 --- a/plugin/trino-jmx/src/main/java/io/trino/plugin/jmx/JmxRecordSetProvider.java +++ b/plugin/trino-jmx/src/main/java/io/trino/plugin/jmx/JmxRecordSetProvider.java @@ -186,7 +186,7 @@ private List calculateSelectedColumns(List columnHandl ImmutableList.Builder selectedColumns = ImmutableList.builder(); for (int i = 0; i < columnHandles.size(); i++) { JmxColumnHandle column = columnHandles.get(i); - if (selectedColumnNames.contains((column.getColumnName()))) { + if (selectedColumnNames.contains(column.getColumnName())) { selectedColumns.add(i); } } diff --git a/plugin/trino-kinesis/src/main/java/io/trino/plugin/kinesis/KinesisMetadata.java b/plugin/trino-kinesis/src/main/java/io/trino/plugin/kinesis/KinesisMetadata.java index 6f29ad35d03d..7c996163ccca 100644 --- a/plugin/trino-kinesis/src/main/java/io/trino/plugin/kinesis/KinesisMetadata.java +++ b/plugin/trino-kinesis/src/main/java/io/trino/plugin/kinesis/KinesisMetadata.java @@ -89,7 +89,7 @@ public List listTables(ConnectorSession session, Optional builder = ImmutableList.builder(); for (SchemaTableName tableName : tableDescriptionSupplier.get().keySet()) { - if ((schemaName.isEmpty()) || tableName.getSchemaName().equals(schemaName.get())) { + if (schemaName.isEmpty() || tableName.getSchemaName().equals(schemaName.get())) { builder.add(tableName); } } diff --git a/plugin/trino-kinesis/src/main/java/io/trino/plugin/kinesis/s3config/S3TableConfigClient.java b/plugin/trino-kinesis/src/main/java/io/trino/plugin/kinesis/s3config/S3TableConfigClient.java index 6806bd63a65f..4f1629d350d2 100644 --- a/plugin/trino-kinesis/src/main/java/io/trino/plugin/kinesis/s3config/S3TableConfigClient.java +++ b/plugin/trino-kinesis/src/main/java/io/trino/plugin/kinesis/s3config/S3TableConfigClient.java @@ -106,7 +106,7 @@ protected void startS3Updates() */ public boolean isUsingS3() { - return bucketUrl.isPresent() && (bucketUrl.get().startsWith("s3://")); + return bucketUrl.isPresent() && bucketUrl.get().startsWith("s3://"); } /** diff --git a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/util/MockKinesisClient.java b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/util/MockKinesisClient.java index 8d762746ed96..d58ecdc68034 100644 --- a/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/util/MockKinesisClient.java +++ b/plugin/trino-kinesis/src/test/java/io/trino/plugin/kinesis/util/MockKinesisClient.java @@ -128,7 +128,7 @@ public InternalStream(String streamName, int shardCount, boolean isActive) for (int i = 0; i < shardCount; i++) { InternalShard newShard = new InternalShard(this.streamName, i); - newShard.setSequenceNumberRange((new SequenceNumberRange()).withStartingSequenceNumber("100").withEndingSequenceNumber("999")); + newShard.setSequenceNumberRange(new SequenceNumberRange().withStartingSequenceNumber("100").withEndingSequenceNumber("999")); this.shards.add(newShard); } } @@ -304,7 +304,7 @@ public CreateStreamResult createStream(CreateStreamRequest createStreamRequest) public CreateStreamResult createStream(String streamName, Integer integer) throws AmazonClientException { - return this.createStream((new CreateStreamRequest()).withStreamName(streamName).withShardCount(integer)); + return this.createStream(new CreateStreamRequest().withStreamName(streamName).withShardCount(integer)); } @Override @@ -318,7 +318,7 @@ public PutRecordsResult putRecords(PutRecordsRequest putRecordsRequest) List resultList = new ArrayList<>(); for (PutRecordsRequestEntry entry : putRecordsRequest.getRecords()) { PutRecordResult putResult = theStream.putRecord(entry.getData(), entry.getPartitionKey()); - resultList.add((new PutRecordsResultEntry()).withShardId(putResult.getShardId()).withSequenceNumber(putResult.getSequenceNumber())); + resultList.add(new PutRecordsResultEntry().withShardId(putResult.getShardId()).withSequenceNumber(putResult.getSequenceNumber())); } result.setRecords(resultList); diff --git a/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/KerberizedKuduClient.java b/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/KerberizedKuduClient.java index c82bb585ca97..75b7f2167cc0 100644 --- a/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/KerberizedKuduClient.java +++ b/plugin/trino-kudu/src/main/java/io/trino/plugin/kudu/KerberizedKuduClient.java @@ -33,7 +33,7 @@ public class KerberizedKuduClient { requireNonNull(kuduClientBuilder, "kuduClientBuilder is null"); this.cachingKerberosAuthentication = requireNonNull(cachingKerberosAuthentication, "cachingKerberosAuthentication is null"); - kuduClient = Subject.doAs(cachingKerberosAuthentication.getSubject(), (PrivilegedAction) (kuduClientBuilder::build)); + kuduClient = Subject.doAs(cachingKerberosAuthentication.getSubject(), (PrivilegedAction) kuduClientBuilder::build); } @Override diff --git a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/BaseOracleConnectorTest.java b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/BaseOracleConnectorTest.java index 183f451aa0d6..1a8e67ec7385 100644 --- a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/BaseOracleConnectorTest.java +++ b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/BaseOracleConnectorTest.java @@ -380,7 +380,7 @@ public void testTooLargeDomainCompactionThreshold() public void testNativeQuerySimple() { // override because Oracle requires the FROM clause, and it needs explicit type - assertQuery("SELECT * FROM TABLE(system.query(query => 'SELECT CAST(1 AS number(2, 1)) FROM DUAL'))", ("VALUES 1")); + assertQuery("SELECT * FROM TABLE(system.query(query => 'SELECT CAST(1 AS number(2, 1)) FROM DUAL'))", "VALUES 1"); } @Test diff --git a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/salesforce/TestSalesforceBasicAuthenticator.java b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/salesforce/TestSalesforceBasicAuthenticator.java index 254592148cf2..06ffcfe3720a 100644 --- a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/salesforce/TestSalesforceBasicAuthenticator.java +++ b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/salesforce/TestSalesforceBasicAuthenticator.java @@ -61,7 +61,7 @@ public void createAuthenticatedPrincipalSuccess() String xmlResponse = format(successResponse, org, username); - HttpClient testHttpClient = new TestingHttpClient((request -> mockResponse(OK, ANY_TEXT_TYPE, xmlResponse))); + HttpClient testHttpClient = new TestingHttpClient(request -> mockResponse(OK, ANY_TEXT_TYPE, xmlResponse)); SalesforceBasicAuthenticator authenticator = new SalesforceBasicAuthenticator(config, testHttpClient); Principal principal = authenticator.createAuthenticatedPrincipal(username, password); @@ -93,7 +93,7 @@ public void createAuthenticatedPrincipalWrongOrg() String xmlResponse = format(successResponse, "NotMyOrg", username); - HttpClient testHttpClient = new TestingHttpClient((request -> mockResponse(OK, ANY_TEXT_TYPE, xmlResponse))); + HttpClient testHttpClient = new TestingHttpClient(request -> mockResponse(OK, ANY_TEXT_TYPE, xmlResponse)); SalesforceBasicAuthenticator authenticator = new SalesforceBasicAuthenticator(config, testHttpClient); assertThatThrownBy(() -> authenticator.createAuthenticatedPrincipal(username, password)) .isInstanceOf(AccessDeniedException.class) @@ -112,7 +112,7 @@ public void createAuthenticatedPrincipalBadPass() String xmlResponse = failedResponse; - HttpClient testHttpClient = new TestingHttpClient((request -> mockResponse(HttpStatus.INTERNAL_SERVER_ERROR, ANY_TEXT_TYPE, xmlResponse))); + HttpClient testHttpClient = new TestingHttpClient(request -> mockResponse(HttpStatus.INTERNAL_SERVER_ERROR, ANY_TEXT_TYPE, xmlResponse)); SalesforceBasicAuthenticator authenticator = new SalesforceBasicAuthenticator(config, testHttpClient); assertThatThrownBy(() -> authenticator.createAuthenticatedPrincipal(username, password)) .isInstanceOf(AccessDeniedException.class) @@ -131,7 +131,7 @@ public void createAuthenticatedPrincipalAllOrgs() String xmlResponse = format(successResponse, "some18CharOrgId", username); - HttpClient testHttpClient = new TestingHttpClient((request -> mockResponse(OK, ANY_TEXT_TYPE, xmlResponse))); + HttpClient testHttpClient = new TestingHttpClient(request -> mockResponse(OK, ANY_TEXT_TYPE, xmlResponse)); SalesforceBasicAuthenticator authenticator = new SalesforceBasicAuthenticator(config, testHttpClient); Principal principal = authenticator.createAuthenticatedPrincipal(username, password); @@ -152,7 +152,7 @@ public void createAuthenticatedPrincipalFewOrgs() String xmlResponse = format(successResponse, "my18CharOrgId", username); - HttpClient testHttpClient = new TestingHttpClient((request -> mockResponse(OK, ANY_TEXT_TYPE, xmlResponse))); + HttpClient testHttpClient = new TestingHttpClient(request -> mockResponse(OK, ANY_TEXT_TYPE, xmlResponse)); SalesforceBasicAuthenticator authenticator = new SalesforceBasicAuthenticator(config, testHttpClient); Principal principal = authenticator.createAuthenticatedPrincipal(username, password); diff --git a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/salesforce/TestSalesforceConfig.java b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/salesforce/TestSalesforceConfig.java index 513315f712fb..8b3ab62f71a9 100644 --- a/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/salesforce/TestSalesforceConfig.java +++ b/plugin/trino-password-authenticators/src/test/java/io/trino/plugin/password/salesforce/TestSalesforceConfig.java @@ -65,8 +65,8 @@ public void testGetOrgSet() { String orgs = "my18CharOrgId,your18CharOrgId, his18CharOrgId ,her18CharOrgId"; int expected = (int) orgs.chars().filter(sep -> sep == ',').count() + 1; - int actual = (new SalesforceConfig() - .setAllowedOrganizations(orgs)).getOrgSet().size(); + int actual = new SalesforceConfig() + .setAllowedOrganizations(orgs).getOrgSet().size(); assertThat(expected).isEqualTo(actual); } } diff --git a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/BasePinotConnectorSmokeTest.java b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/BasePinotConnectorSmokeTest.java index 515de1a97be1..e978cfe639b4 100644 --- a/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/BasePinotConnectorSmokeTest.java +++ b/plugin/trino-pinot/src/test/java/io/trino/plugin/pinot/BasePinotConnectorSmokeTest.java @@ -188,7 +188,7 @@ private void createAndPopulateAllTypesTopic(TestingKafka kafka, TestingPinotClus int offset = i * step; allTypesRecordsBuilder.add(new ProducerRecord<>(ALL_TYPES_TABLE, "key" + i * step, createTestRecord( - Arrays.asList("string_" + (offset), "string1_" + (offset + 1), "string2_" + (offset + 2)), + Arrays.asList("string_" + offset, "string1_" + (offset + 1), "string2_" + (offset + 2)), true, Arrays.asList(54 + i / 3, -10001, 1000), Arrays.asList(-7.33F + i, Float.POSITIVE_INFINITY, 17.034F + i), diff --git a/plugin/trino-postgresql/src/main/java/io/trino/plugin/postgresql/PostgreSqlClient.java b/plugin/trino-postgresql/src/main/java/io/trino/plugin/postgresql/PostgreSqlClient.java index c4a87c4df937..d062c849ad17 100644 --- a/plugin/trino-postgresql/src/main/java/io/trino/plugin/postgresql/PostgreSqlClient.java +++ b/plugin/trino-postgresql/src/main/java/io/trino/plugin/postgresql/PostgreSqlClient.java @@ -1193,7 +1193,7 @@ private static void shortTimestampWriteFunction(PreparedStatement statement, int private static ObjectWriteFunction longTimestampWriteFunction() { - return ObjectWriteFunction.of(LongTimestamp.class, ((statement, index, timestamp) -> { + return ObjectWriteFunction.of(LongTimestamp.class, (statement, index, timestamp) -> { // PostgreSQL supports up to 6 digits of precision //noinspection ConstantConditions verify(POSTGRESQL_MAX_SUPPORTED_TIMESTAMP_PRECISION == 6); @@ -1203,7 +1203,7 @@ private static ObjectWriteFunction longTimestampWriteFunction() epochMicros++; } shortTimestampWriteFunction(statement, index, epochMicros); - })); + }); } @Override diff --git a/plugin/trino-redis/src/main/java/io/trino/plugin/redis/RedisMetadata.java b/plugin/trino-redis/src/main/java/io/trino/plugin/redis/RedisMetadata.java index 3fa639caece2..2b2afe4ebd83 100644 --- a/plugin/trino-redis/src/main/java/io/trino/plugin/redis/RedisMetadata.java +++ b/plugin/trino-redis/src/main/java/io/trino/plugin/redis/RedisMetadata.java @@ -147,7 +147,7 @@ public List listTables(ConnectorSession session, Optional getColumnHandles(ConnectorSession session, ConnectorTableHandle tableHandle) { - RedisTableHandle redisTableHandle = ((RedisTableHandle) tableHandle); + RedisTableHandle redisTableHandle = (RedisTableHandle) tableHandle; RedisTableDescription redisTableDescription = getDefinedTables().get(redisTableHandle.toSchemaTableName()); if (redisTableDescription == null) { diff --git a/plugin/trino-redis/src/main/java/io/trino/plugin/redis/RedisRecordCursor.java b/plugin/trino-redis/src/main/java/io/trino/plugin/redis/RedisRecordCursor.java index 7e9f24ae87dc..897b94ee778b 100644 --- a/plugin/trino-redis/src/main/java/io/trino/plugin/redis/RedisRecordCursor.java +++ b/plugin/trino-redis/src/main/java/io/trino/plugin/redis/RedisRecordCursor.java @@ -142,7 +142,7 @@ public boolean hasUnscannedData() // no more keys are unscanned when // when redis scan command // returns 0 string cursor - return (!redisCursor.getCursor().equals("0")); + return !redisCursor.getCursor().equals("0"); } @Override diff --git a/plugin/trino-resource-group-managers/src/main/java/io/trino/plugin/resourcegroups/db/DbResourceGroupConfigurationManager.java b/plugin/trino-resource-group-managers/src/main/java/io/trino/plugin/resourcegroups/db/DbResourceGroupConfigurationManager.java index 09393d3252ab..6d555bc30b9d 100644 --- a/plugin/trino-resource-group-managers/src/main/java/io/trino/plugin/resourcegroups/db/DbResourceGroupConfigurationManager.java +++ b/plugin/trino-resource-group-managers/src/main/java/io/trino/plugin/resourcegroups/db/DbResourceGroupConfigurationManager.java @@ -180,7 +180,7 @@ private synchronized Optional getCpuQuotaPeriodFromDb() { List globalProperties = dao.getResourceGroupGlobalProperties(); checkState(globalProperties.size() <= 1, "There is more than one cpu_quota_period"); - return (!globalProperties.isEmpty()) ? globalProperties.get(0).getCpuQuotaPeriod() : Optional.empty(); + return !globalProperties.isEmpty() ? globalProperties.get(0).getCpuQuotaPeriod() : Optional.empty(); } @VisibleForTesting diff --git a/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/db/TestDbSessionPropertyManagerIntegration.java b/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/db/TestDbSessionPropertyManagerIntegration.java index 33751da94278..dd5bce845e6d 100644 --- a/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/db/TestDbSessionPropertyManagerIntegration.java +++ b/plugin/trino-session-property-managers/src/test/java/io/trino/plugin/session/db/TestDbSessionPropertyManagerIntegration.java @@ -167,7 +167,7 @@ private void assertSessionPropertyValue(String user, Duration expectedValue) MaterializedResult result = queryRunner.execute(session, "SHOW SESSION"); String actualValueString = (String) result.getMaterializedRows().stream() - .filter(row -> (row.getField(0).equals(EXAMPLE_PROPERTY))) + .filter(row -> row.getField(0).equals(EXAMPLE_PROPERTY)) .collect(onlyElement()) .getField(1); diff --git a/plugin/trino-sqlserver/src/main/java/io/trino/plugin/sqlserver/SqlServerClient.java b/plugin/trino-sqlserver/src/main/java/io/trino/plugin/sqlserver/SqlServerClient.java index 7a1e196087b9..29d9f937511f 100644 --- a/plugin/trino-sqlserver/src/main/java/io/trino/plugin/sqlserver/SqlServerClient.java +++ b/plugin/trino-sqlserver/src/main/java/io/trino/plugin/sqlserver/SqlServerClient.java @@ -1227,7 +1227,7 @@ public static T retryOnDeadlock(CheckedSupplier supplier, String attemptL { Throwable rootCause = Throwables.getRootCause(throwable); return rootCause instanceof SQLServerException && - ((SQLServerException) (rootCause)).getSQLServerError().getErrorNumber() == SQL_SERVER_DEADLOCK_ERROR_CODE; + ((SQLServerException) rootCause).getSQLServerError().getErrorNumber() == SQL_SERVER_DEADLOCK_ERROR_CODE; }) .onFailedAttempt(event -> log.warn(event.getLastException(), "Attempt %d of %d: %s", event.getAttemptCount(), maxAttemptCount, attemptLogMessage)) .build(); diff --git a/plugin/trino-tpch/src/main/java/io/trino/plugin/tpch/TpchBucketFunction.java b/plugin/trino-tpch/src/main/java/io/trino/plugin/tpch/TpchBucketFunction.java index 61f4966bcd49..57706a525caf 100644 --- a/plugin/trino-tpch/src/main/java/io/trino/plugin/tpch/TpchBucketFunction.java +++ b/plugin/trino-tpch/src/main/java/io/trino/plugin/tpch/TpchBucketFunction.java @@ -64,6 +64,6 @@ public String toString() private static long rowNumberFromOrderKey(long orderKey) { // remove bits 3 and 4 - return (((orderKey & ~(0b11_111)) >>> 2) | orderKey & 0b111) - 1; + return (((orderKey & ~0b11_111) >>> 2) | orderKey & 0b111) - 1; } } diff --git a/plugin/trino-tpch/src/main/java/io/trino/plugin/tpch/TpchMetadata.java b/plugin/trino-tpch/src/main/java/io/trino/plugin/tpch/TpchMetadata.java index f1aa34153273..c414ff501368 100644 --- a/plugin/trino-tpch/src/main/java/io/trino/plugin/tpch/TpchMetadata.java +++ b/plugin/trino-tpch/src/main/java/io/trino/plugin/tpch/TpchMetadata.java @@ -537,7 +537,7 @@ private static TupleDomain toTupleDomain(Map Domain.singleValue(type, nullableValue.getValue())) - .reduce((Domain::union)) + .reduce(Domain::union) .orElseGet(() -> Domain.none(type)); }))); } diff --git a/pom.xml b/pom.xml index 741803ba87cf..e4f007e808c8 100644 --- a/pom.xml +++ b/pom.xml @@ -2761,6 +2761,7 @@ -Xep:UnicodeEscape:ERROR \ -Xep:UnnecessaryMethodReference:ERROR \ -Xep:UnnecessaryOptionalGet:ERROR \ + -Xep:UnnecessaryParentheses:ERROR \ -Xep:UseEnumSwitch:ERROR \ diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveRedirectionToHudi.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveRedirectionToHudi.java index d5a199b7cd06..b1a947079076 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveRedirectionToHudi.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveRedirectionToHudi.java @@ -241,11 +241,11 @@ public void testMerge() createHudiCowTable(sourceSchemaTableName, false); createHudiMorTable(targetSchemaTableName, false); - assertQueryFailure(() -> (onTrino().executeQuery("" + + assertQueryFailure(() -> onTrino().executeQuery("" + "MERGE INTO " + hiveTargetTableName + " t USING " + hiveSourceTableName + " s ON t.id = s.id " + "WHEN NOT MATCHED " + " THEN INSERT (id, name, price, ts) " + - " VALUES (s.id, s.name, s.price, s.ts)"))) + " VALUES (s.id, s.name, s.price, s.ts)")) .hasMessageMatching("\\QQuery failed (#\\E\\S+\\Q): This connector does not support modifying table rows"); onHudi().executeQuery("DROP TABLE " + sourceSchemaTableName); diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergSparkCompatibility.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergSparkCompatibility.java index 9d51741b127e..43c77e287ed7 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergSparkCompatibility.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/iceberg/TestIcebergSparkCompatibility.java @@ -1072,15 +1072,15 @@ public void testTrinoWritingDataWithWriterDataPathSet(StorageFormat storageForma "👨‍🏭 "); private static final String TRINO_INSERTED_PARTITION_VALUES = - Streams.mapWithIndex(SPECIAL_CHARACTER_VALUES.stream(), ((value, index) -> format("(%d, '%s')", index, escapeTrinoString(value)))) + Streams.mapWithIndex(SPECIAL_CHARACTER_VALUES.stream(), (value, index) -> format("(%d, '%s')", index, escapeTrinoString(value))) .collect(Collectors.joining(", ")); private static final String SPARK_INSERTED_PARTITION_VALUES = - Streams.mapWithIndex(SPECIAL_CHARACTER_VALUES.stream(), ((value, index) -> format("(%d, '%s')", index, escapeSparkString(value)))) + Streams.mapWithIndex(SPECIAL_CHARACTER_VALUES.stream(), (value, index) -> format("(%d, '%s')", index, escapeSparkString(value))) .collect(Collectors.joining(", ")); private static final List EXPECTED_PARTITION_VALUES = - Streams.mapWithIndex(SPECIAL_CHARACTER_VALUES.stream(), ((value, index) -> row((int) index, value))) + Streams.mapWithIndex(SPECIAL_CHARACTER_VALUES.stream(), (value, index) -> row((int) index, value)) .collect(toImmutableList()); @Test(groups = {ICEBERG, PROFILE_SPECIFIC_TESTS, ICEBERG_REST, ICEBERG_JDBC, ICEBERG_NESSIE}) @@ -1456,7 +1456,7 @@ public void testSparkReadingTrinoCompressedData(StorageFormat storageFormat, Str .hasMessageMatching("\\QQuery failed (#\\E\\S+\\Q): Unsupported codec: LZ4"); return; } - if (storageFormat == StorageFormat.AVRO && (compressionCodec.equals("LZ4"))) { + if (storageFormat == StorageFormat.AVRO && compressionCodec.equals("LZ4")) { assertQueryFailure(() -> onTrino().executeQuery(createTable)) .hasMessageMatching("\\QQuery failed (#\\E\\S+\\Q): Unsupported compression codec: " + compressionCodec); return; @@ -2021,7 +2021,7 @@ public void testSparkReadsTrinoTableAfterCleaningUp(StorageFormat storageFormat) onTrino().executeQuery(format("INSERT INTO %s VALUES ('c', 1007)", trinoTableName)); onTrino().executeQuery(format("INSERT INTO %s VALUES ('c', 1008)", trinoTableName)); onTrino().executeQuery(format("INSERT INTO %s VALUES ('c', 1009)", trinoTableName)); - onTrino().executeQuery((format("DELETE FROM %s WHERE _string = '%s'", trinoTableName, 'b'))); + onTrino().executeQuery(format("DELETE FROM %s WHERE _string = '%s'", trinoTableName, 'b')); int initialNumberOfMetadataFiles = calculateMetadataFilesForPartitionedTable(baseTableName); onTrino().executeQuery("SET SESSION iceberg.expire_snapshots_min_retention = '0s'"); @@ -2061,7 +2061,7 @@ public void testSparkReadsTrinoTableAfterOptimizeAndCleaningUp(StorageFormat sto onTrino().executeQuery(format("INSERT INTO %s VALUES ('c', 1007)", trinoTableName)); onTrino().executeQuery(format("INSERT INTO %s VALUES ('c', 1008)", trinoTableName)); onTrino().executeQuery(format("INSERT INTO %s VALUES ('c', 1009)", trinoTableName)); - onTrino().executeQuery((format("DELETE FROM %s WHERE _string = '%s'", trinoTableName, 'b'))); + onTrino().executeQuery(format("DELETE FROM %s WHERE _string = '%s'", trinoTableName, 'b')); int initialNumberOfFiles = onTrino().executeQuery(format("SELECT * FROM iceberg.default.\"%s$files\"", baseTableName)).getRowsCount(); int initialNumberOfMetadataFiles = calculateMetadataFilesForPartitionedTable(baseTableName); diff --git a/testing/trino-testing-services/src/main/java/io/trino/testng/services/ProgressLoggingListener.java b/testing/trino-testing-services/src/main/java/io/trino/testng/services/ProgressLoggingListener.java index b436c82c4375..3f61b2496044 100644 --- a/testing/trino-testing-services/src/main/java/io/trino/testng/services/ProgressLoggingListener.java +++ b/testing/trino-testing-services/src/main/java/io/trino/testng/services/ProgressLoggingListener.java @@ -158,6 +158,6 @@ private static String formatDuration(long durationInMillis) private static BigDecimal durationInSeconds(long millis) { - return (new BigDecimal(millis)).divide(new BigDecimal(1000), 1, RoundingMode.HALF_UP); + return new BigDecimal(millis).divide(new BigDecimal(1000), 1, RoundingMode.HALF_UP); } } diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestRefreshMaterializedView.java b/testing/trino-tests/src/test/java/io/trino/execution/TestRefreshMaterializedView.java index b805209c0b6b..3d26bf9bf5af 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestRefreshMaterializedView.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestRefreshMaterializedView.java @@ -109,13 +109,13 @@ protected QueryRunner createQueryRunner() ImmutableList.of(), ImmutableMap.of()))) .withDelegateMaterializedViewRefreshToConnector((connectorSession, schemaTableName) -> true) - .withRefreshMaterializedView(((connectorSession, schemaTableName) -> { + .withRefreshMaterializedView((connectorSession, schemaTableName) -> { startRefreshMaterializedView.set(null); SettableFuture refreshMaterializedView = SettableFuture.create(); finishRefreshMaterializedView.addListener(() -> refreshMaterializedView.set(null), directExecutor()); addExceptionCallback(refreshMaterializedView, () -> refreshInterrupted.set(null)); return toCompletableFuture(refreshMaterializedView); - })) + }) .build())); queryRunner.createCatalog("mock", "mock"); return queryRunner; diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestTableRedirection.java b/testing/trino-tests/src/test/java/io/trino/execution/TestTableRedirection.java index 7f795e9cac35..61b1e4da3065 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestTableRedirection.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestTableRedirection.java @@ -183,7 +183,7 @@ private MockConnectorFactory createMockConnectorFactory() } return null; }) - .withGetViews(((connectorSession, prefix) -> ImmutableMap.of())) + .withGetViews((connectorSession, prefix) -> ImmutableMap.of()) .withGetColumns(schemaTableName -> { if (!REDIRECTIONS.containsKey(schemaTableName)) { return columnsGetter.apply(schemaTableName); @@ -191,10 +191,10 @@ private MockConnectorFactory createMockConnectorFactory() throw new RuntimeException("Columns do not exist for: " + schemaTableName); }) - .withRedirectTable(((connectorSession, schemaTableName) -> { + .withRedirectTable((connectorSession, schemaTableName) -> { return Optional.ofNullable(REDIRECTIONS.get(schemaTableName)) .map(target -> new CatalogSchemaTableName(CATALOG_NAME, target)); - })) + }) .build(); } @@ -206,7 +206,7 @@ public void testTableScans() "SELECT 1 WHERE 1=0", verifySingleTableScan(SCHEMA_TWO, VALID_REDIRECTION_TARGET)); - assertThatThrownBy(() -> query((format("SELECT c0 FROM %s.%s", SCHEMA_ONE, BAD_REDIRECTION_SRC)))) + assertThatThrownBy(() -> query(format("SELECT c0 FROM %s.%s", SCHEMA_ONE, BAD_REDIRECTION_SRC))) .hasMessageContaining( "Table '%s' redirected to '%s', but the target table '%s' does not exist", new CatalogSchemaTableName(CATALOG_NAME, SCHEMA_ONE, BAD_REDIRECTION_SRC), @@ -342,14 +342,14 @@ public void testShowCreate() String showCreateValidTarget = (String) computeScalar(format("SHOW CREATE TABLE %s.%s", SCHEMA_TWO, VALID_REDIRECTION_TARGET)); assertThat(showCreateValidTarget).isEqualTo(showCreateValidSource.replace(SCHEMA_ONE + "." + VALID_REDIRECTION_SRC, SCHEMA_TWO + "." + VALID_REDIRECTION_TARGET)); - assertThatThrownBy(() -> query((format("SHOW CREATE TABLE %s.%s", SCHEMA_ONE, BAD_REDIRECTION_SRC)))) + assertThatThrownBy(() -> query(format("SHOW CREATE TABLE %s.%s", SCHEMA_ONE, BAD_REDIRECTION_SRC))) .hasMessageContaining( "Table '%s' redirected to '%s', but the target table '%s' does not exist", new CatalogSchemaTableName(CATALOG_NAME, SCHEMA_ONE, BAD_REDIRECTION_SRC), new CatalogSchemaTableName(CATALOG_NAME, SCHEMA_TWO, NON_EXISTENT_TABLE), new CatalogSchemaTableName(CATALOG_NAME, SCHEMA_TWO, NON_EXISTENT_TABLE)); - assertThatThrownBy(() -> query((format("SHOW CREATE TABLE %s.%s", SCHEMA_ONE, REDIRECTION_LOOP_PING)))) + assertThatThrownBy(() -> query(format("SHOW CREATE TABLE %s.%s", SCHEMA_ONE, REDIRECTION_LOOP_PING))) .hasMessageContaining("Table redirections form a loop"); } @@ -359,14 +359,14 @@ public void testDescribeTable() assertThat(query(format("DESCRIBE %s.%s", SCHEMA_ONE, VALID_REDIRECTION_SRC))) .matches(format("DESCRIBE %s.%s", SCHEMA_TWO, VALID_REDIRECTION_TARGET)); - assertThatThrownBy(() -> query((format("DESCRIBE %s.%s", SCHEMA_ONE, BAD_REDIRECTION_SRC)))) + assertThatThrownBy(() -> query(format("DESCRIBE %s.%s", SCHEMA_ONE, BAD_REDIRECTION_SRC))) .hasMessageContaining( "Table '%s' redirected to '%s', but the target table '%s' does not exist", new CatalogSchemaTableName(CATALOG_NAME, SCHEMA_ONE, BAD_REDIRECTION_SRC), new CatalogSchemaTableName(CATALOG_NAME, SCHEMA_TWO, NON_EXISTENT_TABLE), new CatalogSchemaTableName(CATALOG_NAME, SCHEMA_TWO, NON_EXISTENT_TABLE)); - assertThatThrownBy(() -> query((format("DESCRIBE %s.%s", SCHEMA_ONE, REDIRECTION_LOOP_PING)))) + assertThatThrownBy(() -> query(format("DESCRIBE %s.%s", SCHEMA_ONE, REDIRECTION_LOOP_PING))) .hasMessageContaining("Table redirections form a loop"); } @@ -379,14 +379,14 @@ public void testShowColumns() + row(C2, BIGINT.getDisplayName(), "", "") + "," + row(C3, BIGINT.getDisplayName(), "", "")); - assertThatThrownBy(() -> query((format("SHOW COLUMNS FROM %s.%s", SCHEMA_ONE, BAD_REDIRECTION_SRC)))) + assertThatThrownBy(() -> query(format("SHOW COLUMNS FROM %s.%s", SCHEMA_ONE, BAD_REDIRECTION_SRC))) .hasMessageContaining( "Table '%s' redirected to '%s', but the target table '%s' does not exist", new CatalogSchemaTableName(CATALOG_NAME, SCHEMA_ONE, BAD_REDIRECTION_SRC), new CatalogSchemaTableName(CATALOG_NAME, SCHEMA_TWO, NON_EXISTENT_TABLE), new CatalogSchemaTableName(CATALOG_NAME, SCHEMA_TWO, NON_EXISTENT_TABLE)); - assertThatThrownBy(() -> query((format("SHOW COLUMNS FROM %s.%s", SCHEMA_ONE, REDIRECTION_LOOP_PING)))) + assertThatThrownBy(() -> query(format("SHOW COLUMNS FROM %s.%s", SCHEMA_ONE, REDIRECTION_LOOP_PING))) .hasMessageContaining("Table redirections form a loop"); } diff --git a/testing/trino-tests/src/test/java/io/trino/security/TestAccessControl.java b/testing/trino-tests/src/test/java/io/trino/security/TestAccessControl.java index 6624bfbbf9db..8daed713e759 100644 --- a/testing/trino-tests/src/test/java/io/trino/security/TestAccessControl.java +++ b/testing/trino-tests/src/test/java/io/trino/security/TestAccessControl.java @@ -170,7 +170,7 @@ protected SystemAccessControl delegate() } return new MockConnectorTableHandle(schemaTableName); }) - .withListSchemaNames((connectorSession -> ImmutableList.of(DEFAULT_SCHEMA))) + .withListSchemaNames(connectorSession -> ImmutableList.of(DEFAULT_SCHEMA)) .withListTables((connectorSession, schemaName) -> { if (schemaName.equals(DEFAULT_SCHEMA)) { return ImmutableList.of(REDIRECTED_SOURCE); @@ -236,19 +236,19 @@ public Map apply(Connector .withColumnProperties(() -> ImmutableList.of( integerProperty("another_property", "description", 0, false), stringProperty("string_column_property", "description", "", false))) - .withRedirectTable(((connectorSession, schemaTableName) -> { + .withRedirectTable((connectorSession, schemaTableName) -> { if (schemaTableName.equals(SchemaTableName.schemaTableName(DEFAULT_SCHEMA, REDIRECTED_SOURCE))) { return Optional.of( new CatalogSchemaTableName("mock", SchemaTableName.schemaTableName(DEFAULT_SCHEMA, REDIRECTED_TARGET))); } return Optional.empty(); - })) - .withGetComment((schemaTableName -> { + }) + .withGetComment(schemaTableName -> { if (schemaTableName.getTableName().equals(REDIRECTED_TARGET)) { return Optional.of("this is a redirected table"); } return Optional.empty(); - })) + }) .withFunctions(ImmutableList.builder() .add(FunctionMetadata.scalarBuilder("my_function") .signature(Signature.builder().argumentType(BIGINT).returnType(BIGINT).build()) diff --git a/testing/trino-tests/src/test/java/io/trino/security/TestAccessControlTableRedirection.java b/testing/trino-tests/src/test/java/io/trino/security/TestAccessControlTableRedirection.java index bb6154d57392..a6e87ebd1cbe 100644 --- a/testing/trino-tests/src/test/java/io/trino/security/TestAccessControlTableRedirection.java +++ b/testing/trino-tests/src/test/java/io/trino/security/TestAccessControlTableRedirection.java @@ -264,9 +264,9 @@ private static MockConnectorFactory createMockConnectorFactory() } return null; }) - .withGetViews(((connectorSession, prefix) -> ImmutableMap.of())) - .withRedirectTable(((connectorSession, schemaTableName) -> Optional.ofNullable(TABLE_REDIRECTIONS.get(schemaTableName)) - .map(target -> new CatalogSchemaTableName(CATALOG_NAME, target)))) + .withGetViews((connectorSession, prefix) -> ImmutableMap.of()) + .withRedirectTable((connectorSession, schemaTableName) -> Optional.ofNullable(TABLE_REDIRECTIONS.get(schemaTableName)) + .map(target -> new CatalogSchemaTableName(CATALOG_NAME, target))) .withGetColumns(schemaTableName -> { if (REDIRECTION_TARGET_SCHEMA_TABLE_NAME.equals(schemaTableName)) { return ImmutableList.of(new ColumnMetadata(ID_COLUMN_NAME, INTEGER), new ColumnMetadata(DATA_COLUMN_NAME, VARCHAR)); From 6fbafb10bcd38c5737593cd3968ab3c2568d8deb Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sat, 25 Nov 2023 16:08:25 -0600 Subject: [PATCH 441/587] Remove useless POM comments --- plugin/trino-hive/pom.xml | 1 - plugin/trino-raptor-legacy/pom.xml | 1 - testing/trino-faulttolerant-tests/pom.xml | 1 - testing/trino-testing/pom.xml | 1 - testing/trino-tests/pom.xml | 1 - 5 files changed, 5 deletions(-) diff --git a/plugin/trino-hive/pom.xml b/plugin/trino-hive/pom.xml index 7e4cddc5c53e..5131125ab190 100644 --- a/plugin/trino-hive/pom.xml +++ b/plugin/trino-hive/pom.xml @@ -352,7 +352,6 @@ test - io.trino trino-benchmark diff --git a/plugin/trino-raptor-legacy/pom.xml b/plugin/trino-raptor-legacy/pom.xml index f223d3ff9bc2..6771b9407ed4 100644 --- a/plugin/trino-raptor-legacy/pom.xml +++ b/plugin/trino-raptor-legacy/pom.xml @@ -216,7 +216,6 @@ test - io.trino trino-benchmark diff --git a/testing/trino-faulttolerant-tests/pom.xml b/testing/trino-faulttolerant-tests/pom.xml index 511f248352a2..ee315e8bd08f 100644 --- a/testing/trino-faulttolerant-tests/pom.xml +++ b/testing/trino-faulttolerant-tests/pom.xml @@ -387,7 +387,6 @@ test - org.openjdk.jmh jmh-core diff --git a/testing/trino-testing/pom.xml b/testing/trino-testing/pom.xml index ef4edb6d5711..2d696e2b6f14 100644 --- a/testing/trino-testing/pom.xml +++ b/testing/trino-testing/pom.xml @@ -208,7 +208,6 @@ test - org.openjdk.jmh jmh-core diff --git a/testing/trino-tests/pom.xml b/testing/trino-tests/pom.xml index 43f9532505a8..c6efd0dbaa9e 100644 --- a/testing/trino-tests/pom.xml +++ b/testing/trino-tests/pom.xml @@ -301,7 +301,6 @@ test - org.openjdk.jmh jmh-core From e2f94aa4d6ffd4b8a16a71b2970f77135b7067a6 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sat, 25 Nov 2023 16:12:00 -0600 Subject: [PATCH 442/587] Move Hadoop dependency for hive-formats to test scope --- lib/trino-hive-formats/pom.xml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/trino-hive-formats/pom.xml b/lib/trino-hive-formats/pom.xml index 546be8bbdd15..d915a8e9d527 100644 --- a/lib/trino-hive-formats/pom.xml +++ b/lib/trino-hive-formats/pom.xml @@ -100,13 +100,6 @@ modernizer-maven-annotations - - - io.trino.hadoop - hadoop-apache - provided - - org.jetbrains annotations @@ -188,6 +181,12 @@ test + + io.trino.hadoop + hadoop-apache + test + + io.trino.hive hive-apache From 2a340694ff17b03efaf4634c97098eb0f64a5238 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sat, 25 Nov 2023 20:28:50 -0600 Subject: [PATCH 443/587] Turn off bootstrap logging in TestHttpEventListener --- .../httpquery/TestHttpEventListener.java | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/plugin/trino-http-event-listener/src/test/java/io/trino/plugin/httpquery/TestHttpEventListener.java b/plugin/trino-http-event-listener/src/test/java/io/trino/plugin/httpquery/TestHttpEventListener.java index 0cc3270b0213..24a465b71365 100644 --- a/plugin/trino-http-event-listener/src/test/java/io/trino/plugin/httpquery/TestHttpEventListener.java +++ b/plugin/trino-http-event-listener/src/test/java/io/trino/plugin/httpquery/TestHttpEventListener.java @@ -257,7 +257,7 @@ void testAllLoggingDisabledShouldTimeout() server.enqueue(new MockResponse() .setResponseCode(200)); - EventListener eventListener = factory.create(Map.of( + EventListener eventListener = createEventListener(Map.of( "http-event-listener.connect-ingest-uri", server.url("/").toString())); eventListener.queryCreated(null); @@ -271,7 +271,7 @@ void testAllLoggingDisabledShouldTimeout() void testAllLoggingEnabledShouldSendCorrectEvent() throws Exception { - EventListener eventListener = factory.create(Map.of( + EventListener eventListener = createEventListener(Map.of( "http-event-listener.connect-ingest-uri", server.url("/").toString(), "http-event-listener.log-completed", "true", "http-event-listener.log-created", "true", @@ -295,7 +295,7 @@ void testAllLoggingEnabledShouldSendCorrectEvent() void testContentTypeDefaultHeaderShouldAlwaysBeSet() throws Exception { - EventListener eventListener = factory.create(Map.of( + EventListener eventListener = createEventListener(Map.of( "http-event-listener.connect-ingest-uri", server.url("/").toString(), "http-event-listener.log-completed", "true")); @@ -312,7 +312,7 @@ void testContentTypeDefaultHeaderShouldAlwaysBeSet() void testHttpHeadersShouldBePresent() throws Exception { - EventListener eventListener = factory.create(Map.of( + EventListener eventListener = createEventListener(Map.of( "http-event-listener.connect-ingest-uri", server.url("/").toString(), "http-event-listener.log-completed", "true", "http-event-listener.connect-http-headers", "Authorization: Trust Me!, Cache-Control: no-cache")); @@ -333,7 +333,7 @@ void testHttpsEnabledShouldUseTLSv13() setupServerTLSCertificate(); server.enqueue(new MockResponse().setResponseCode(200)); - EventListener eventListener = factory.create(Map.of( + EventListener eventListener = createEventListener(Map.of( "http-event-listener.connect-ingest-uri", server.url("/").toString(), "http-event-listener.log-completed", "true", "http-event-listener.http-client.key-store-path", "src/test/resources/trino-httpquery-test.p12", @@ -358,7 +358,7 @@ void testDifferentCertificatesShouldNotSendRequest() setupServerTLSCertificate(); server.enqueue(new MockResponse().setResponseCode(200)); - EventListener eventListener = factory.create(Map.of( + EventListener eventListener = createEventListener(Map.of( "http-event-listener.connect-ingest-uri", server.url("/").toString(), "http-event-listener.log-completed", "true", "http-event-listener.http-client.key-store-path", "src/test/resources/trino-httpquery-test2.p12", @@ -378,7 +378,7 @@ void testNoServerCertificateShouldNotSendRequest() { server.enqueue(new MockResponse().setResponseCode(200)); - EventListener eventListener = factory.create(Map.of( + EventListener eventListener = createEventListener(Map.of( "http-event-listener.connect-ingest-uri", "https://%s:%s/".formatted(server.getHostName(), server.getPort()), "http-event-listener.log-completed", "true", "http-event-listener.http-client.key-store-path", "src/test/resources/trino-httpquery-test.p12", @@ -405,7 +405,7 @@ void testServerShoudRetry() private void testServerShouldRetry(int responseCode) throws Exception { - EventListener eventListener = factory.create(Map.of( + EventListener eventListener = createEventListener(Map.of( "http-event-listener.connect-ingest-uri", server.url("/").toString(), "http-event-listener.log-completed", "true", "http-event-listener.connect-retry-count", "1")); @@ -423,7 +423,7 @@ private void testServerShouldRetry(int responseCode) void testServerDisconnectShouldRetry() throws Exception { - EventListener eventListener = factory.create(Map.of( + EventListener eventListener = createEventListener(Map.of( "http-event-listener.connect-ingest-uri", server.url("/").toString(), "http-event-listener.log-completed", "true", "http-event-listener.connect-retry-count", "1", @@ -443,7 +443,7 @@ void testServerDisconnectShouldRetry() void testServerDelayDoesNotBlock() throws Exception { - EventListener eventListener = factory.create(Map.of( + EventListener eventListener = createEventListener(Map.of( "http-event-listener.connect-ingest-uri", server.url("/").toString(), "http-event-listener.log-completed", "true")); @@ -512,4 +512,12 @@ private void setupServerTLSCertificate() SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); server.useHttps(sslSocketFactory, false); } + + private EventListener createEventListener(Map config) + { + return factory.create(ImmutableMap.builder() + .putAll(config) + .put("bootstrap.quiet", "true") + .buildOrThrow()); + } } From e45ef19d5c1dc8f70d66b3442a42ce0e65c48781 Mon Sep 17 00:00:00 2001 From: Ziva Li Date: Mon, 27 Nov 2023 07:50:36 +0800 Subject: [PATCH 444/587] Add docs for array_join(x, delimiter) Co-authored-by: Manfred Moser --- docs/src/main/sphinx/functions/array.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/src/main/sphinx/functions/array.md b/docs/src/main/sphinx/functions/array.md index 0b4252284d2f..bb402689cfd5 100644 --- a/docs/src/main/sphinx/functions/array.md +++ b/docs/src/main/sphinx/functions/array.md @@ -77,7 +77,14 @@ SELECT array_histogram(ARRAY[NULL, NULL]); ``` ::: +:::{function} array_join(x, delimiter) -> varchar +Concatenates the elements of the given array using the delimiter. +Null elements are omitted in the result. +::: + :::{function} array_join(x, delimiter, null_replacement) -> varchar +:noindex: true + Concatenates the elements of the given array using the delimiter and an optional string to replace nulls. ::: From b21637f4b987e95a0cc68d0a58989ef0dd020df1 Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Thu, 6 Oct 2022 18:54:30 +0900 Subject: [PATCH 445/587] Add support for DELETE statement in BigQuery --- docs/src/main/sphinx/connector/bigquery.md | 4 ++ .../trino/plugin/bigquery/BigQueryClient.java | 4 +- .../plugin/bigquery/BigQueryMetadata.java | 30 +++++++++++-- .../BaseBigQueryConnectorSmokeTest.java | 1 - .../bigquery/BaseBigQueryConnectorTest.java | 42 ++++++++++++++++++- .../BaseBigQueryFailureRecoveryTest.java | 16 +++++-- 6 files changed, 86 insertions(+), 11 deletions(-) diff --git a/docs/src/main/sphinx/connector/bigquery.md b/docs/src/main/sphinx/connector/bigquery.md index 2bae6d8a7a99..af864518c8c9 100644 --- a/docs/src/main/sphinx/connector/bigquery.md +++ b/docs/src/main/sphinx/connector/bigquery.md @@ -315,6 +315,7 @@ BigQuery database. In addition to the the following features: - {doc}`/sql/insert` +- {doc}`/sql/delete` - {doc}`/sql/truncate` - {doc}`/sql/create-table` - {doc}`/sql/create-table-as` @@ -323,6 +324,9 @@ the following features: - {doc}`/sql/drop-schema` - {doc}`/sql/comment` +```{include} sql-delete-limitation.fragment +``` + (bigquery-fte-support)= ## Fault-tolerant execution support diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryClient.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryClient.java index df8201fed086..9b151f24b05d 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryClient.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryClient.java @@ -280,10 +280,10 @@ Job create(JobInfo jobInfo) return bigQuery.create(jobInfo); } - public void executeUpdate(ConnectorSession session, QueryJobConfiguration job) + public long executeUpdate(ConnectorSession session, QueryJobConfiguration job) { log.debug("Execute query: %s", job.getQuery()); - execute(session, job); + return execute(session, job).getTotalRows(); } public TableResult executeQuery(ConnectorSession session, String sql) diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryMetadata.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryMetadata.java index c0ad16f924a0..aea995ebb639 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryMetadata.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryMetadata.java @@ -93,6 +93,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static com.google.cloud.bigquery.StandardSQLTypeName.INT64; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableList.toImmutableList; @@ -110,6 +111,7 @@ import static io.trino.plugin.bigquery.BigQueryUtil.quote; import static io.trino.plugin.bigquery.BigQueryUtil.quoted; import static io.trino.spi.StandardErrorCode.GENERIC_INTERNAL_ERROR; +import static io.trino.spi.type.BigintType.BIGINT; import static java.lang.String.format; import static java.util.Locale.ENGLISH; import static java.util.Objects.requireNonNull; @@ -671,18 +673,38 @@ public Optional finishInsert(ConnectorSession session, return finishInsert(session, handle.getRemoteTableName(), handle.getTemporaryRemoteTableName(), handle.getPageSinkIdColumnName(), handle.getColumnNames(), fragments); } + @Override + public ColumnHandle getMergeRowIdColumnHandle(ConnectorSession session, ConnectorTableHandle tableHandle) + { + return new BigQueryColumnHandle("$merge_row_id", BIGINT, INT64, true, Field.Mode.REQUIRED, ImmutableList.of(), null, true); + } + @Override public Optional applyDelete(ConnectorSession session, ConnectorTableHandle handle) { - // TODO Fix BaseBigQueryFailureRecoveryTest when implementing this method - return ConnectorMetadata.super.applyDelete(session, handle); + return Optional.of(handle); } @Override public OptionalLong executeDelete(ConnectorSession session, ConnectorTableHandle handle) { - // TODO Fix BaseBigQueryFailureRecoveryTest when implementing this method - return ConnectorMetadata.super.executeDelete(session, handle); + BigQueryTableHandle tableHandle = ((BigQueryTableHandle) handle); + checkArgument(tableHandle.isNamedRelation(), "Unable to delete from synthetic table: %s", tableHandle); + TupleDomain tableConstraint = tableHandle.getConstraint(); + Optional filter = BigQueryFilterQueryBuilder.buildFilter(tableConstraint); + + RemoteTableName remoteTableName = tableHandle.asPlainTable().getRemoteTableName(); + String sql = format( + "DELETE FROM %s.%s.%s WHERE %s", + quote(remoteTableName.getProjectId()), + quote(remoteTableName.getDatasetName()), + quote(remoteTableName.getTableName()), + filter.orElse("true")); + BigQueryClient client = bigQueryClientFactory.create(session); + long rows = client.executeUpdate(session, QueryJobConfiguration.newBuilder(sql) + .setQuery(sql) + .build()); + return OptionalLong.of(rows); } @Override diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorSmokeTest.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorSmokeTest.java index d8e31af5d289..67ff4d68e98a 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorSmokeTest.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorSmokeTest.java @@ -26,7 +26,6 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) case SUPPORTS_TRUNCATE -> true; case SUPPORTS_CREATE_MATERIALIZED_VIEW, SUPPORTS_CREATE_VIEW, - SUPPORTS_DELETE, SUPPORTS_MERGE, SUPPORTS_RENAME_SCHEMA, SUPPORTS_RENAME_TABLE, diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java index e0d11eecf419..22097631bc4f 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java @@ -36,6 +36,7 @@ import static com.google.common.base.Strings.nullToEmpty; import static io.trino.plugin.bigquery.BigQueryQueryRunner.BigQuerySqlExecutor; import static io.trino.plugin.bigquery.BigQueryQueryRunner.TEST_SCHEMA; +import static io.trino.spi.connector.ConnectorMetadata.MODIFYING_ROWS_MESSAGE; import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.testing.MaterializedResult.resultBuilder; import static io.trino.testing.TestingNames.randomNameSuffix; @@ -70,7 +71,6 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) case SUPPORTS_ADD_COLUMN, SUPPORTS_CREATE_MATERIALIZED_VIEW, SUPPORTS_CREATE_VIEW, - SUPPORTS_DELETE, SUPPORTS_DEREFERENCE_PUSHDOWN, SUPPORTS_MERGE, SUPPORTS_NEGATIVE_DATE, @@ -214,6 +214,46 @@ public void testCreateTableAlreadyExists() } } + @Test + @Override + public void testDeleteWithComplexPredicate() + { + assertThatThrownBy(super::testDeleteWithComplexPredicate) + .hasStackTraceContaining("TrinoException: " + MODIFYING_ROWS_MESSAGE); + } + + @Test + @Override + public void testDeleteWithLike() + { + assertThatThrownBy(super::testDeleteWithLike) + .hasStackTraceContaining("TrinoException: " + MODIFYING_ROWS_MESSAGE); + } + + @Test + @Override + public void testDeleteWithSemiJoin() + { + assertThatThrownBy(super::testDeleteWithSemiJoin) + .hasStackTraceContaining("TrinoException: " + MODIFYING_ROWS_MESSAGE); + } + + @Test + @Override + public void testDeleteWithSubquery() + { + assertThatThrownBy(super::testDeleteWithSubquery) + .hasStackTraceContaining("TrinoException: " + MODIFYING_ROWS_MESSAGE); + } + + @Test + @Override + public void testExplainAnalyzeWithDeleteWithSubquery() + { + assertThatThrownBy(super::testExplainAnalyzeWithDeleteWithSubquery) + .hasStackTraceContaining("TrinoException: " + MODIFYING_ROWS_MESSAGE); + } + @Test public void testEmptyProjectionTable() { diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryFailureRecoveryTest.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryFailureRecoveryTest.java index e03130d0c6b4..d5eec271b417 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryFailureRecoveryTest.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryFailureRecoveryTest.java @@ -23,7 +23,9 @@ import java.util.List; import java.util.Map; +import java.util.Optional; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assumptions.abort; public abstract class BaseBigQueryFailureRecoveryTest @@ -73,15 +75,23 @@ protected void testAnalyzeTable() @Override protected void testDelete() { - // This connector does not support modifying table rows - abort("skipped"); + // This simple delete on BigQuery ends up as a very simple, single-fragment, coordinator-only plan, + // which has no ability to recover from errors. This test simply verifies that's still the case. + Optional setupQuery = Optional.of("CREATE TABLE
    AS SELECT * FROM orders"); + String testQuery = "DELETE FROM
    WHERE orderkey = 1"; + Optional cleanupQuery = Optional.of("DROP TABLE
    "); + + assertThatQuery(testQuery) + .withSetupQuery(setupQuery) + .withCleanupQuery(cleanupQuery) + .isCoordinatorOnly(); } @Test @Override protected void testDeleteWithSubquery() { - // This connector does not support modifying table rows + assertThatThrownBy(super::testDeleteWithSubquery).hasMessageContaining("This connector does not support modifying table rows"); abort("skipped"); } From 18e1a79eb3f10d4daac76349aa56940739750351 Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Mon, 27 Nov 2023 09:51:06 +0900 Subject: [PATCH 446/587] Fix TestMySqlTypeMapping.testTimeFromMySql --- .../io/trino/plugin/mysql/TestMySqlTypeMapping.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlTypeMapping.java b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlTypeMapping.java index a650a0c742cc..2f99cd90d93b 100644 --- a/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlTypeMapping.java +++ b/plugin/trino-mysql/src/test/java/io/trino/plugin/mysql/TestMySqlTypeMapping.java @@ -599,13 +599,13 @@ private void testDate(ZoneId sessionZone) @Test public void testTimeFromMySql() { - testTimestampFromMySql(UTC); - testTimestampFromMySql(ZoneId.systemDefault()); + testTimeFromMySql(UTC); + testTimeFromMySql(ZoneId.systemDefault()); // no DST in 1970, but has DST in later years (e.g. 2018) - testTimestampFromMySql(ZoneId.of("Europe/Vilnius")); + testTimeFromMySql(ZoneId.of("Europe/Vilnius")); // minutes offset change since 1970-01-01, no DST - testTimestampFromMySql(ZoneId.of("Asia/Kathmandu")); - testTimestampFromMySql(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + testTimeFromMySql(ZoneId.of("Asia/Kathmandu")); + testTimeFromMySql(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); } private void testTimeFromMySql(ZoneId sessionZone) From e32dec4ee3ac678a9a9ed493c2667170508b7758 Mon Sep 17 00:00:00 2001 From: kasiafi <30203062+kasiafi@users.noreply.github.com> Date: Fri, 24 Nov 2023 20:49:50 +0100 Subject: [PATCH 447/587] Remove dependency on trino-main from TestConstraintExtractor --- .../iceberg/TestConstraintExtractor.java | 144 +++++++----------- 1 file changed, 57 insertions(+), 87 deletions(-) diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestConstraintExtractor.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestConstraintExtractor.java index 0d2bc2cedef3..fb7507840f60 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestConstraintExtractor.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestConstraintExtractor.java @@ -15,11 +15,13 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import io.trino.security.AllowAllAccessControl; import io.trino.spi.connector.ColumnHandle; import io.trino.spi.connector.Constraint; +import io.trino.spi.expression.Call; import io.trino.spi.expression.ConnectorExpression; import io.trino.spi.expression.Constant; +import io.trino.spi.expression.FunctionName; +import io.trino.spi.expression.Variable; import io.trino.spi.predicate.Domain; import io.trino.spi.predicate.Range; import io.trino.spi.predicate.TupleDomain; @@ -29,61 +31,42 @@ import io.trino.spi.type.TimestampType; import io.trino.spi.type.TimestampWithTimeZoneType; import io.trino.spi.type.Type; -import io.trino.sql.planner.ConnectorExpressionTranslator; -import io.trino.sql.planner.LiteralEncoder; -import io.trino.sql.planner.Symbol; -import io.trino.sql.planner.TypeProvider; import io.trino.sql.planner.iterative.rule.UnwrapCastInComparison; import io.trino.sql.planner.iterative.rule.UnwrapDateTruncInComparison; import io.trino.sql.planner.iterative.rule.UnwrapYearInComparison; -import io.trino.sql.tree.Cast; -import io.trino.sql.tree.ComparisonExpression; -import io.trino.sql.tree.Expression; -import io.trino.sql.tree.FunctionCall; -import io.trino.sql.tree.SymbolReference; -import io.trino.transaction.NoOpTransactionManager; -import io.trino.transaction.TransactionId; import org.junit.jupiter.api.Test; import java.time.LocalDate; -import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; -import static com.google.common.collect.ImmutableMap.toImmutableMap; import static io.airlift.slice.Slices.utf8Slice; -import static io.trino.SessionTestUtils.TEST_SESSION; import static io.trino.plugin.iceberg.ColumnIdentity.primitiveColumnIdentity; import static io.trino.plugin.iceberg.ConstraintExtractor.extractTupleDomain; +import static io.trino.spi.expression.StandardFunctions.CAST_FUNCTION_NAME; +import static io.trino.spi.expression.StandardFunctions.EQUAL_OPERATOR_FUNCTION_NAME; +import static io.trino.spi.expression.StandardFunctions.GREATER_THAN_OPERATOR_FUNCTION_NAME; +import static io.trino.spi.expression.StandardFunctions.GREATER_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME; +import static io.trino.spi.expression.StandardFunctions.IS_DISTINCT_FROM_OPERATOR_FUNCTION_NAME; +import static io.trino.spi.expression.StandardFunctions.LESS_THAN_OPERATOR_FUNCTION_NAME; +import static io.trino.spi.expression.StandardFunctions.LESS_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME; +import static io.trino.spi.expression.StandardFunctions.NOT_EQUAL_OPERATOR_FUNCTION_NAME; import static io.trino.spi.type.BigintType.BIGINT; +import static io.trino.spi.type.BooleanType.BOOLEAN; import static io.trino.spi.type.DateType.DATE; import static io.trino.spi.type.TimeZoneKey.UTC_KEY; import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MICROS; import static io.trino.spi.type.Timestamps.MILLISECONDS_PER_DAY; import static io.trino.spi.type.Timestamps.MILLISECONDS_PER_SECOND; import static io.trino.spi.type.Timestamps.PICOSECONDS_PER_MICROSECOND; -import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.spi.type.VarcharType.createVarcharType; -import static io.trino.sql.analyzer.TypeSignatureProvider.fromTypes; -import static io.trino.sql.analyzer.TypeSignatureTranslator.toSqlType; -import static io.trino.sql.planner.TestingPlannerContext.PLANNER_CONTEXT; -import static io.trino.sql.planner.TypeAnalyzer.createTestingTypeAnalyzer; -import static io.trino.sql.tree.ComparisonExpression.Operator.EQUAL; -import static io.trino.sql.tree.ComparisonExpression.Operator.GREATER_THAN; -import static io.trino.sql.tree.ComparisonExpression.Operator.GREATER_THAN_OR_EQUAL; -import static io.trino.sql.tree.ComparisonExpression.Operator.IS_DISTINCT_FROM; -import static io.trino.sql.tree.ComparisonExpression.Operator.LESS_THAN; -import static io.trino.sql.tree.ComparisonExpression.Operator.LESS_THAN_OR_EQUAL; -import static io.trino.sql.tree.ComparisonExpression.Operator.NOT_EQUAL; import static java.time.ZoneOffset.UTC; import static org.assertj.core.api.Assertions.assertThat; public class TestConstraintExtractor { - private static final LiteralEncoder LITERAL_ENCODER = new LiteralEncoder(PLANNER_CONTEXT); - private static final AtomicInteger nextColumnId = new AtomicInteger(1); private static final IcebergColumnHandle A_BIGINT = newPrimitiveColumn(BIGINT); @@ -115,10 +98,10 @@ public void testExtractSummary() public void testExtractTimestampTzDateComparison() { String timestampTzColumnSymbol = "timestamp_tz_symbol"; - Cast castOfColumn = new Cast(new SymbolReference(timestampTzColumnSymbol), toSqlType(DATE)); + ConnectorExpression castOfColumn = new Call(DATE, CAST_FUNCTION_NAME, ImmutableList.of(new Variable(timestampTzColumnSymbol, TIMESTAMP_TZ_MICROS))); LocalDate someDate = LocalDate.of(2005, 9, 10); - Expression someDateExpression = LITERAL_ENCODER.toExpression(someDate.toEpochDay(), DATE); + ConnectorExpression someDateExpression = new Constant(someDate.toEpochDay(), DATE); long startOfDateUtcEpochMillis = someDate.atStartOfDay().toEpochSecond(UTC) * MILLISECONDS_PER_SECOND; LongTimestampWithTimeZone startOfDateUtc = timestampTzFromEpochMillis(startOfDateUtcEpochMillis); @@ -126,13 +109,13 @@ public void testExtractTimestampTzDateComparison() assertThat(extract( constraint( - new ComparisonExpression(EQUAL, castOfColumn, someDateExpression), + new Call(BOOLEAN, EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(castOfColumn, someDateExpression)), Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.range(TIMESTAMP_TZ_MICROS, startOfDateUtc, true, startOfNextDateUtc, false))))); assertThat(extract( constraint( - new ComparisonExpression(NOT_EQUAL, castOfColumn, someDateExpression), + new Call(BOOLEAN, NOT_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(castOfColumn, someDateExpression)), Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain( Range.lessThan(TIMESTAMP_TZ_MICROS, startOfDateUtc), @@ -140,31 +123,31 @@ public void testExtractTimestampTzDateComparison() assertThat(extract( constraint( - new ComparisonExpression(LESS_THAN, castOfColumn, someDateExpression), + new Call(BOOLEAN, LESS_THAN_OPERATOR_FUNCTION_NAME, ImmutableList.of(castOfColumn, someDateExpression)), Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.lessThan(TIMESTAMP_TZ_MICROS, startOfDateUtc))))); assertThat(extract( constraint( - new ComparisonExpression(LESS_THAN_OR_EQUAL, castOfColumn, someDateExpression), + new Call(BOOLEAN, LESS_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(castOfColumn, someDateExpression)), Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.lessThan(TIMESTAMP_TZ_MICROS, startOfNextDateUtc))))); assertThat(extract( constraint( - new ComparisonExpression(GREATER_THAN, castOfColumn, someDateExpression), + new Call(BOOLEAN, GREATER_THAN_OPERATOR_FUNCTION_NAME, ImmutableList.of(castOfColumn, someDateExpression)), Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.greaterThanOrEqual(TIMESTAMP_TZ_MICROS, startOfNextDateUtc))))); assertThat(extract( constraint( - new ComparisonExpression(GREATER_THAN_OR_EQUAL, castOfColumn, someDateExpression), + new Call(BOOLEAN, GREATER_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(castOfColumn, someDateExpression)), Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.greaterThanOrEqual(TIMESTAMP_TZ_MICROS, startOfDateUtc))))); assertThat(extract( constraint( - new ComparisonExpression(IS_DISTINCT_FROM, castOfColumn, someDateExpression), + new Call(BOOLEAN, IS_DISTINCT_FROM_OPERATOR_FUNCTION_NAME, ImmutableList.of(castOfColumn, someDateExpression)), Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, Domain.create( ValueSet.ofRanges( @@ -184,17 +167,18 @@ public void testExtractTimestampTzDateComparison() public void testExtractDateTruncTimestampTzComparison() { String timestampTzColumnSymbol = "timestamp_tz_symbol"; - FunctionCall truncateToDay = new FunctionCall( - PLANNER_CONTEXT.getMetadata().resolveBuiltinFunction("date_trunc", fromTypes(VARCHAR, TIMESTAMP_TZ_MICROS)).toQualifiedName(), - List.of( - LITERAL_ENCODER.toExpression(utf8Slice("day"), createVarcharType(17)), - new SymbolReference(timestampTzColumnSymbol))); + ConnectorExpression truncateToDay = new Call( + TIMESTAMP_TZ_MICROS, + new FunctionName("date_trunc"), + ImmutableList.of( + new Constant(utf8Slice("day"), createVarcharType(17)), + new Variable(timestampTzColumnSymbol, TIMESTAMP_TZ_MICROS))); LocalDate someDate = LocalDate.of(2005, 9, 10); - Expression someMidnightExpression = LITERAL_ENCODER.toExpression( + ConnectorExpression someMidnightExpression = new Constant( LongTimestampWithTimeZone.fromEpochMillisAndFraction(someDate.toEpochDay() * MILLISECONDS_PER_DAY, 0, UTC_KEY), TIMESTAMP_TZ_MICROS); - Expression someMiddayExpression = LITERAL_ENCODER.toExpression( + ConnectorExpression someMiddayExpression = new Constant( LongTimestampWithTimeZone.fromEpochMillisAndFraction(someDate.toEpochDay() * MILLISECONDS_PER_DAY, PICOSECONDS_PER_MICROSECOND, UTC_KEY), TIMESTAMP_TZ_MICROS); @@ -204,19 +188,19 @@ public void testExtractDateTruncTimestampTzComparison() assertThat(extract( constraint( - new ComparisonExpression(EQUAL, truncateToDay, someMidnightExpression), + new Call(BOOLEAN, EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(truncateToDay, someMidnightExpression)), Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.range(TIMESTAMP_TZ_MICROS, startOfDateUtc, true, startOfNextDateUtc, false))))); assertThat(extract( constraint( - new ComparisonExpression(EQUAL, truncateToDay, someMiddayExpression), + new Call(BOOLEAN, EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(truncateToDay, someMiddayExpression)), Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) .isEqualTo(TupleDomain.none()); assertThat(extract( constraint( - new ComparisonExpression(NOT_EQUAL, truncateToDay, someMidnightExpression), + new Call(BOOLEAN, NOT_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(truncateToDay, someMidnightExpression)), Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain( Range.lessThan(TIMESTAMP_TZ_MICROS, startOfDateUtc), @@ -224,31 +208,31 @@ public void testExtractDateTruncTimestampTzComparison() assertThat(extract( constraint( - new ComparisonExpression(LESS_THAN, truncateToDay, someMidnightExpression), + new Call(BOOLEAN, LESS_THAN_OPERATOR_FUNCTION_NAME, ImmutableList.of(truncateToDay, someMidnightExpression)), Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.lessThan(TIMESTAMP_TZ_MICROS, startOfDateUtc))))); assertThat(extract( constraint( - new ComparisonExpression(LESS_THAN_OR_EQUAL, truncateToDay, someMidnightExpression), + new Call(BOOLEAN, LESS_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(truncateToDay, someMidnightExpression)), Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.lessThan(TIMESTAMP_TZ_MICROS, startOfNextDateUtc))))); assertThat(extract( constraint( - new ComparisonExpression(GREATER_THAN, truncateToDay, someMidnightExpression), + new Call(BOOLEAN, GREATER_THAN_OPERATOR_FUNCTION_NAME, ImmutableList.of(truncateToDay, someMidnightExpression)), Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.greaterThanOrEqual(TIMESTAMP_TZ_MICROS, startOfNextDateUtc))))); assertThat(extract( constraint( - new ComparisonExpression(GREATER_THAN_OR_EQUAL, truncateToDay, someMidnightExpression), + new Call(BOOLEAN, GREATER_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(truncateToDay, someMidnightExpression)), Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.greaterThanOrEqual(TIMESTAMP_TZ_MICROS, startOfDateUtc))))); assertThat(extract( constraint( - new ComparisonExpression(IS_DISTINCT_FROM, truncateToDay, someMidnightExpression), + new Call(BOOLEAN, IS_DISTINCT_FROM_OPERATOR_FUNCTION_NAME, ImmutableList.of(truncateToDay, someMidnightExpression)), Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, Domain.create( ValueSet.ofRanges( @@ -268,12 +252,13 @@ public void testExtractDateTruncTimestampTzComparison() public void testExtractYearTimestampTzComparison() { String timestampTzColumnSymbol = "timestamp_tz_symbol"; - FunctionCall extractYear = new FunctionCall( - PLANNER_CONTEXT.getMetadata().resolveBuiltinFunction("year", fromTypes(TIMESTAMP_TZ_MICROS)).toQualifiedName(), - List.of(new SymbolReference(timestampTzColumnSymbol))); + ConnectorExpression extractYear = new Call( + BIGINT, + new FunctionName("year"), + ImmutableList.of(new Variable(timestampTzColumnSymbol, TIMESTAMP_TZ_MICROS))); LocalDate someDate = LocalDate.of(2005, 9, 10); - Expression yearExpression = LITERAL_ENCODER.toExpression(2005L, BIGINT); + ConnectorExpression yearExpression = new Constant(2005L, BIGINT); long startOfYearUtcEpochMillis = someDate.withDayOfYear(1).atStartOfDay().toEpochSecond(UTC) * MILLISECONDS_PER_SECOND; LongTimestampWithTimeZone startOfYearUtc = timestampTzFromEpochMillis(startOfYearUtcEpochMillis); @@ -281,13 +266,13 @@ public void testExtractYearTimestampTzComparison() assertThat(extract( constraint( - new ComparisonExpression(EQUAL, extractYear, yearExpression), + new Call(BOOLEAN, EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(extractYear, yearExpression)), Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.range(TIMESTAMP_TZ_MICROS, startOfYearUtc, true, startOfNextDateUtc, false))))); assertThat(extract( constraint( - new ComparisonExpression(NOT_EQUAL, extractYear, yearExpression), + new Call(BOOLEAN, NOT_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(extractYear, yearExpression)), Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain( Range.lessThan(TIMESTAMP_TZ_MICROS, startOfYearUtc), @@ -295,31 +280,31 @@ public void testExtractYearTimestampTzComparison() assertThat(extract( constraint( - new ComparisonExpression(LESS_THAN, extractYear, yearExpression), + new Call(BOOLEAN, LESS_THAN_OPERATOR_FUNCTION_NAME, ImmutableList.of(extractYear, yearExpression)), Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.lessThan(TIMESTAMP_TZ_MICROS, startOfYearUtc))))); assertThat(extract( constraint( - new ComparisonExpression(LESS_THAN_OR_EQUAL, extractYear, yearExpression), + new Call(BOOLEAN, LESS_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(extractYear, yearExpression)), Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.lessThan(TIMESTAMP_TZ_MICROS, startOfNextDateUtc))))); assertThat(extract( constraint( - new ComparisonExpression(GREATER_THAN, extractYear, yearExpression), + new Call(BOOLEAN, GREATER_THAN_OPERATOR_FUNCTION_NAME, ImmutableList.of(extractYear, yearExpression)), Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.greaterThanOrEqual(TIMESTAMP_TZ_MICROS, startOfNextDateUtc))))); assertThat(extract( constraint( - new ComparisonExpression(GREATER_THAN_OR_EQUAL, extractYear, yearExpression), + new Call(BOOLEAN, GREATER_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(extractYear, yearExpression)), Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.greaterThanOrEqual(TIMESTAMP_TZ_MICROS, startOfYearUtc))))); assertThat(extract( constraint( - new ComparisonExpression(IS_DISTINCT_FROM, extractYear, yearExpression), + new Call(BOOLEAN, IS_DISTINCT_FROM_OPERATOR_FUNCTION_NAME, ImmutableList.of(extractYear, yearExpression)), Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, Domain.create( ValueSet.ofRanges( @@ -332,10 +317,10 @@ public void testExtractYearTimestampTzComparison() public void testIntersectSummaryAndExpressionExtraction() { String timestampTzColumnSymbol = "timestamp_tz_symbol"; - Cast castOfColumn = new Cast(new SymbolReference(timestampTzColumnSymbol), toSqlType(DATE)); + ConnectorExpression castOfColumn = new Call(DATE, CAST_FUNCTION_NAME, ImmutableList.of(new Variable(timestampTzColumnSymbol, TIMESTAMP_TZ_MICROS))); LocalDate someDate = LocalDate.of(2005, 9, 10); - Expression someDateExpression = LITERAL_ENCODER.toExpression(someDate.toEpochDay(), DATE); + ConnectorExpression someDateExpression = new Constant(someDate.toEpochDay(), DATE); long startOfDateUtcEpochMillis = someDate.atStartOfDay().toEpochSecond(UTC) * MILLISECONDS_PER_SECOND; LongTimestampWithTimeZone startOfDateUtc = timestampTzFromEpochMillis(startOfDateUtcEpochMillis); @@ -345,7 +330,7 @@ public void testIntersectSummaryAndExpressionExtraction() assertThat(extract( constraint( TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.lessThan(TIMESTAMP_TZ_MICROS, startOfNextNextDateUtc)))), - new ComparisonExpression(NOT_EQUAL, castOfColumn, someDateExpression), + new Call(BOOLEAN, NOT_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(castOfColumn, someDateExpression)), Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) .isEqualTo(TupleDomain.withColumnDomains(Map.of( A_TIMESTAMP_TZ, domain( @@ -355,14 +340,14 @@ A_TIMESTAMP_TZ, domain( assertThat(extract( constraint( TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.lessThan(TIMESTAMP_TZ_MICROS, startOfNextDateUtc)))), - new ComparisonExpression(GREATER_THAN, castOfColumn, someDateExpression), + new Call(BOOLEAN, GREATER_THAN_OPERATOR_FUNCTION_NAME, ImmutableList.of(castOfColumn, someDateExpression)), Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) .isEqualTo(TupleDomain.none()); assertThat(extract( constraint( TupleDomain.withColumnDomains(Map.of(A_BIGINT, Domain.singleValue(BIGINT, 1L))), - new ComparisonExpression(GREATER_THAN_OR_EQUAL, castOfColumn, someDateExpression), + new Call(BOOLEAN, GREATER_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(castOfColumn, someDateExpression)), Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) .isEqualTo(TupleDomain.withColumnDomains(Map.of( A_BIGINT, Domain.singleValue(BIGINT, 1L), @@ -388,29 +373,14 @@ private static TupleDomain extract(Constraint constraint) return result.tupleDomain(); } - private static Constraint constraint(Expression expression, Map assignments) + private static Constraint constraint(ConnectorExpression expression, Map assignments) { return constraint(TupleDomain.all(), expression, assignments); } - private static Constraint constraint(TupleDomain summary, Expression expression, Map assignments) - { - Map symbolTypes = assignments.entrySet().stream() - .collect(toImmutableMap(Map.Entry::getKey, entry -> entry.getValue().getType())); - ConnectorExpression connectorExpression = connectorExpression(expression, symbolTypes); - return new Constraint(summary, connectorExpression, ImmutableMap.copyOf(assignments)); - } - - private static ConnectorExpression connectorExpression(Expression expression, Map symbolTypes) + private static Constraint constraint(TupleDomain summary, ConnectorExpression expression, Map assignments) { - return ConnectorExpressionTranslator.translate( - TEST_SESSION.beginTransactionId(TransactionId.create(), new NoOpTransactionManager(), new AllowAllAccessControl()), - expression, - TypeProvider.viewOf(symbolTypes.entrySet().stream() - .collect(toImmutableMap(entry -> new Symbol(entry.getKey()), Map.Entry::getValue))), - PLANNER_CONTEXT, - createTestingTypeAnalyzer(PLANNER_CONTEXT)) - .orElseThrow(() -> new RuntimeException("Translation to ConnectorExpression failed for: " + expression)); + return new Constraint(summary, expression, ImmutableMap.copyOf(assignments)); } private static LongTimestampWithTimeZone timestampTzFromEpochMillis(long epochMillis) From c3b5c2e0110be8577f7c68546d4d720388361894 Mon Sep 17 00:00:00 2001 From: kasiafi <30203062+kasiafi@users.noreply.github.com> Date: Wed, 22 Nov 2023 11:37:04 +0100 Subject: [PATCH 448/587] Generalize ConstraintExtractor to use arbitrary ColumnHandle --- .../plugin/iceberg/ConstraintExtractor.java | 48 +++++++++---------- .../trino/plugin/iceberg/IcebergMetadata.java | 3 +- .../iceberg/TestConstraintExtractor.java | 31 +++--------- 3 files changed, 32 insertions(+), 50 deletions(-) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/ConstraintExtractor.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/ConstraintExtractor.java index 7e84a774db6f..576c38f4fbd7 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/ConstraintExtractor.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/ConstraintExtractor.java @@ -68,11 +68,10 @@ private ConstraintExtractor() {} public static ExtractionResult extractTupleDomain(Constraint constraint) { - TupleDomain result = constraint.getSummary() - .transformKeys(IcebergColumnHandle.class::cast); + TupleDomain result = constraint.getSummary(); ImmutableList.Builder remainingExpressions = ImmutableList.builder(); for (ConnectorExpression conjunct : extractConjuncts(constraint.getExpression())) { - Optional> converted = toTupleDomain(conjunct, constraint.getAssignments()); + Optional> converted = toTupleDomain(conjunct, constraint.getAssignments()); if (converted.isEmpty()) { remainingExpressions.add(conjunct); } @@ -86,7 +85,7 @@ public static ExtractionResult extractTupleDomain(Constraint constraint) return new ExtractionResult(result, and(remainingExpressions.build())); } - private static Optional> toTupleDomain(ConnectorExpression expression, Map assignments) + private static Optional> toTupleDomain(ConnectorExpression expression, Map assignments) { if (expression instanceof Call call) { return toTupleDomain(call, assignments); @@ -94,7 +93,7 @@ private static Optional> toTupleDomain(Connecto return Optional.empty(); } - private static Optional> toTupleDomain(Call call, Map assignments) + private static Optional> toTupleDomain(Call call, Map assignments) { if (call.getArguments().size() == 2) { ConnectorExpression firstArgument = call.getArguments().get(0); @@ -145,7 +144,7 @@ private static Optional> toTupleDomain(Call cal return Optional.empty(); } - private static Optional> unwrapCastInComparison( + private static Optional> unwrapCastInComparison( // upon invocation, we don't know if this really is a comparison FunctionName functionName, ConnectorExpression castSource, @@ -164,13 +163,13 @@ private static Optional> unwrapCastInComparison return Optional.empty(); } - IcebergColumnHandle column = resolve(sourceVariable, assignments); - if (column.getType() instanceof TimestampWithTimeZoneType sourceType) { + ColumnHandle column = resolve(sourceVariable, assignments); + if (sourceVariable.getType() instanceof TimestampWithTimeZoneType columnType) { // Iceberg supports only timestamp(6) with time zone - checkArgument(sourceType.getPrecision() == 6, "Unexpected type: %s", column.getType()); + checkArgument(columnType.getPrecision() == 6, "Unexpected type: %s", columnType); if (constant.getType() == DateType.DATE) { - return unwrapTimestampTzToDateCast(column, functionName, (long) constant.getValue()) + return unwrapTimestampTzToDateCast(column, columnType, functionName, (long) constant.getValue()) .map(domain -> TupleDomain.withColumnDomains(ImmutableMap.of(column, domain))); } // TODO support timestamp constant @@ -179,10 +178,9 @@ private static Optional> unwrapCastInComparison return Optional.empty(); } - private static Optional unwrapTimestampTzToDateCast(IcebergColumnHandle column, FunctionName functionName, long date) + private static Optional unwrapTimestampTzToDateCast(ColumnHandle column, Type columnType, FunctionName functionName, long date) { - Type type = column.getType(); - checkArgument(type.equals(TIMESTAMP_TZ_MICROS), "Column of unexpected type %s: %s", type, column); + checkArgument(columnType.equals(TIMESTAMP_TZ_MICROS), "Column of unexpected type %s: %s", columnType, column); // Verify no overflow. Date values must be in integer range. verify(date <= Integer.MAX_VALUE, "Date value out of range: %s", date); @@ -192,7 +190,7 @@ private static Optional unwrapTimestampTzToDateCast(IcebergColumnHandle LongTimestampWithTimeZone startOfDate = LongTimestampWithTimeZone.fromEpochMillisAndFraction(date * MILLISECONDS_PER_DAY, 0, UTC_KEY); LongTimestampWithTimeZone startOfNextDate = LongTimestampWithTimeZone.fromEpochMillisAndFraction((date + 1) * MILLISECONDS_PER_DAY, 0, UTC_KEY); - return createDomain(functionName, type, startOfDate, startOfNextDate); + return createDomain(functionName, columnType, startOfDate, startOfNextDate); } private static Optional unwrapYearInTimestampTzComparison(FunctionName functionName, Type type, Constant constant) @@ -237,7 +235,7 @@ private static Optional createDomain(FunctionName functionName, Type typ return Optional.empty(); } - private static Optional> unwrapDateTruncInComparison( + private static Optional> unwrapDateTruncInComparison( // upon invocation, we don't know if this really is a comparison FunctionName functionName, Constant unit, @@ -261,10 +259,10 @@ private static Optional> unwrapDateTruncInCompa return Optional.empty(); } - IcebergColumnHandle column = resolve(sourceVariable, assignments); - if (column.getType() instanceof TimestampWithTimeZoneType type) { + ColumnHandle column = resolve(sourceVariable, assignments); + if (sourceVariable.getType() instanceof TimestampWithTimeZoneType type) { // Iceberg supports only timestamp(6) with time zone - checkArgument(type.getPrecision() == 6, "Unexpected type: %s", column.getType()); + checkArgument(type.getPrecision() == 6, "Unexpected type: %s", type); verify(constant.getType().equals(type), "This method should not be invoked when type mismatch (i.e. surely not a comparison)"); return unwrapDateTruncInComparison(((Slice) unit.getValue()).toStringUtf8(), functionName, constant) @@ -352,7 +350,7 @@ private static Optional unwrapDateTruncInComparison(String unit, Functio return Optional.empty(); } - private static Optional> unwrapYearInTimestampTzComparison( + private static Optional> unwrapYearInTimestampTzComparison( // upon invocation, we don't know if this really is a comparison FunctionName functionName, ConnectorExpression yearSource, @@ -371,10 +369,10 @@ private static Optional> unwrapYearInTimestampT return Optional.empty(); } - IcebergColumnHandle column = resolve(sourceVariable, assignments); - if (column.getType() instanceof TimestampWithTimeZoneType type) { + ColumnHandle column = resolve(sourceVariable, assignments); + if (sourceVariable.getType() instanceof TimestampWithTimeZoneType type) { // Iceberg supports only timestamp(6) with time zone - checkArgument(type.getPrecision() == 6, "Unexpected type: %s", column.getType()); + checkArgument(type.getPrecision() == 6, "Unexpected type: %s", type); return unwrapYearInTimestampTzComparison(functionName, type, constant) .map(domain -> TupleDomain.withColumnDomains(ImmutableMap.of(column, domain))); @@ -383,14 +381,14 @@ private static Optional> unwrapYearInTimestampT return Optional.empty(); } - private static IcebergColumnHandle resolve(Variable variable, Map assignments) + private static ColumnHandle resolve(Variable variable, Map assignments) { ColumnHandle columnHandle = assignments.get(variable.getName()); checkArgument(columnHandle != null, "No assignment for %s", variable); - return (IcebergColumnHandle) columnHandle; + return columnHandle; } - public record ExtractionResult(TupleDomain tupleDomain, ConnectorExpression remainingExpression) + public record ExtractionResult(TupleDomain tupleDomain, ConnectorExpression remainingExpression) { public ExtractionResult { diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java index 4f69e72796b5..f054eabb58dd 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java @@ -2478,7 +2478,8 @@ public Optional> applyFilter(C { IcebergTableHandle table = (IcebergTableHandle) handle; ConstraintExtractor.ExtractionResult extractionResult = extractTupleDomain(constraint); - TupleDomain predicate = extractionResult.tupleDomain(); + TupleDomain predicate = extractionResult.tupleDomain() + .transformKeys(IcebergColumnHandle.class::cast); if (predicate.isAll() && constraint.getPredicateColumns().isEmpty()) { return Optional.empty(); } diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestConstraintExtractor.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestConstraintExtractor.java index fb7507840f60..32ec74c40cb6 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestConstraintExtractor.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestConstraintExtractor.java @@ -14,9 +14,9 @@ package io.trino.plugin.iceberg; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import io.trino.spi.connector.ColumnHandle; import io.trino.spi.connector.Constraint; +import io.trino.spi.connector.TestingColumnHandle; import io.trino.spi.expression.Call; import io.trino.spi.expression.ConnectorExpression; import io.trino.spi.expression.Constant; @@ -30,7 +30,6 @@ import io.trino.spi.type.LongTimestampWithTimeZone; import io.trino.spi.type.TimestampType; import io.trino.spi.type.TimestampWithTimeZoneType; -import io.trino.spi.type.Type; import io.trino.sql.planner.iterative.rule.UnwrapCastInComparison; import io.trino.sql.planner.iterative.rule.UnwrapDateTruncInComparison; import io.trino.sql.planner.iterative.rule.UnwrapYearInComparison; @@ -38,12 +37,9 @@ import java.time.LocalDate; import java.util.Map; -import java.util.Optional; import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; import static io.airlift.slice.Slices.utf8Slice; -import static io.trino.plugin.iceberg.ColumnIdentity.primitiveColumnIdentity; import static io.trino.plugin.iceberg.ConstraintExtractor.extractTupleDomain; import static io.trino.spi.expression.StandardFunctions.CAST_FUNCTION_NAME; import static io.trino.spi.expression.StandardFunctions.EQUAL_OPERATOR_FUNCTION_NAME; @@ -67,10 +63,8 @@ public class TestConstraintExtractor { - private static final AtomicInteger nextColumnId = new AtomicInteger(1); - - private static final IcebergColumnHandle A_BIGINT = newPrimitiveColumn(BIGINT); - private static final IcebergColumnHandle A_TIMESTAMP_TZ = newPrimitiveColumn(TIMESTAMP_TZ_MICROS); + private static final ColumnHandle A_BIGINT = new TestingColumnHandle("a_bigint"); + private static final ColumnHandle A_TIMESTAMP_TZ = new TestingColumnHandle("a_timestamp_tz"); @Test public void testExtractSummary() @@ -354,18 +348,7 @@ A_TIMESTAMP_TZ, domain( A_TIMESTAMP_TZ, domain(Range.greaterThanOrEqual(TIMESTAMP_TZ_MICROS, startOfDateUtc))))); } - private static IcebergColumnHandle newPrimitiveColumn(Type type) - { - int id = nextColumnId.getAndIncrement(); - return new IcebergColumnHandle( - primitiveColumnIdentity(id, "column_" + id), - type, - ImmutableList.of(), - type, - Optional.empty()); - } - - private static TupleDomain extract(Constraint constraint) + private static TupleDomain extract(Constraint constraint) { ConstraintExtractor.ExtractionResult result = extractTupleDomain(constraint); assertThat(result.remainingExpression()) @@ -373,14 +356,14 @@ private static TupleDomain extract(Constraint constraint) return result.tupleDomain(); } - private static Constraint constraint(ConnectorExpression expression, Map assignments) + private static Constraint constraint(ConnectorExpression expression, Map assignments) { return constraint(TupleDomain.all(), expression, assignments); } - private static Constraint constraint(TupleDomain summary, ConnectorExpression expression, Map assignments) + private static Constraint constraint(TupleDomain summary, ConnectorExpression expression, Map assignments) { - return new Constraint(summary, expression, ImmutableMap.copyOf(assignments)); + return new Constraint(summary, expression, assignments); } private static LongTimestampWithTimeZone timestampTzFromEpochMillis(long epochMillis) From 577f2cd138c2415ee46366932f73dd76ecef1b42 Mon Sep 17 00:00:00 2001 From: kasiafi <30203062+kasiafi@users.noreply.github.com> Date: Fri, 24 Nov 2023 21:15:59 +0100 Subject: [PATCH 449/587] Move ConstraintExtractor to plugin-toolkit Rename ConstraintExtractor to UtcConstraintExtractor --- .../base/filter/UtcConstraintExtractor.java | 12 +++++-- .../filter/TestUtcConstraintExtractor.java | 35 +++++++++---------- .../trino/plugin/iceberg/IcebergMetadata.java | 5 +-- 3 files changed, 28 insertions(+), 24 deletions(-) rename plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/ConstraintExtractor.java => lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/filter/UtcConstraintExtractor.java (97%) rename plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestConstraintExtractor.java => lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/filter/TestUtcConstraintExtractor.java (95%) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/ConstraintExtractor.java b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/filter/UtcConstraintExtractor.java similarity index 97% rename from plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/ConstraintExtractor.java rename to lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/filter/UtcConstraintExtractor.java index 576c38f4fbd7..f188b28c4cf0 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/ConstraintExtractor.java +++ b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/filter/UtcConstraintExtractor.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.iceberg; +package io.trino.plugin.base.filter; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -62,9 +62,15 @@ import static java.util.Locale.ENGLISH; import static java.util.Objects.requireNonNull; -public final class ConstraintExtractor +/** + * Some expressions involving the TIMESTAMP WITH TIME ZONE type can be optimized when the time zone is known. + * It is not possible in the engine, but can be possible in the connector if the connector follows some + * convention regarding time zones. In some connectors, like the Delta Lake connector, or the Iceberg connector, + * all values of TIMESTAMP WITH TIME ZONE type are represented using the UTC time zone. + */ +public final class UtcConstraintExtractor { - private ConstraintExtractor() {} + private UtcConstraintExtractor() {} public static ExtractionResult extractTupleDomain(Constraint constraint) { diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestConstraintExtractor.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/filter/TestUtcConstraintExtractor.java similarity index 95% rename from plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestConstraintExtractor.java rename to lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/filter/TestUtcConstraintExtractor.java index 32ec74c40cb6..648acad765ef 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestConstraintExtractor.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/filter/TestUtcConstraintExtractor.java @@ -11,7 +11,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.trino.plugin.iceberg; +package io.trino.plugin.base.filter; import com.google.common.collect.ImmutableList; import io.trino.spi.connector.ColumnHandle; @@ -30,9 +30,6 @@ import io.trino.spi.type.LongTimestampWithTimeZone; import io.trino.spi.type.TimestampType; import io.trino.spi.type.TimestampWithTimeZoneType; -import io.trino.sql.planner.iterative.rule.UnwrapCastInComparison; -import io.trino.sql.planner.iterative.rule.UnwrapDateTruncInComparison; -import io.trino.sql.planner.iterative.rule.UnwrapYearInComparison; import org.junit.jupiter.api.Test; import java.time.LocalDate; @@ -40,7 +37,7 @@ import java.util.Set; import static io.airlift.slice.Slices.utf8Slice; -import static io.trino.plugin.iceberg.ConstraintExtractor.extractTupleDomain; +import static io.trino.plugin.base.filter.UtcConstraintExtractor.extractTupleDomain; import static io.trino.spi.expression.StandardFunctions.CAST_FUNCTION_NAME; import static io.trino.spi.expression.StandardFunctions.EQUAL_OPERATOR_FUNCTION_NAME; import static io.trino.spi.expression.StandardFunctions.GREATER_THAN_OPERATOR_FUNCTION_NAME; @@ -61,7 +58,7 @@ import static java.time.ZoneOffset.UTC; import static org.assertj.core.api.Assertions.assertThat; -public class TestConstraintExtractor +public class TestUtcConstraintExtractor { private static final ColumnHandle A_BIGINT = new TestingColumnHandle("a_bigint"); private static final ColumnHandle A_TIMESTAMP_TZ = new TestingColumnHandle("a_timestamp_tz"); @@ -82,11 +79,11 @@ public void testExtractSummary() } /** - * Test equivalent of {@link UnwrapCastInComparison} for {@link TimestampWithTimeZoneType}. - * {@link UnwrapCastInComparison} handles {@link DateType} and {@link TimestampType}, but cannot handle - * {@link TimestampWithTimeZoneType}. Such unwrap would not be monotonic. Within Iceberg, we know + * Test equivalent of {@code io.trino.sql.planner.iterative.rule.UnwrapCastInComparison} for {@link TimestampWithTimeZoneType}. + * {@code UnwrapCastInComparison} handles {@link DateType} and {@link TimestampType}, but cannot handle + * {@link TimestampWithTimeZoneType}. Such unwrap would not be monotonic. If we know * that {@link TimestampWithTimeZoneType} is always in UTC zone (point in time, with no time zone information), - * so we can unwrap. + * we can unwrap. */ @Test public void testExtractTimestampTzDateComparison() @@ -151,11 +148,11 @@ public void testExtractTimestampTzDateComparison() } /** - * Test equivalent of {@link UnwrapDateTruncInComparison} for {@link TimestampWithTimeZoneType}. - * {@link UnwrapDateTruncInComparison} handles {@link DateType} and {@link TimestampType}, but cannot handle - * {@link TimestampWithTimeZoneType}. Such unwrap would not be monotonic. Within Iceberg, we know + * Test equivalent of {@code io.trino.sql.planner.iterative.rule.UnwrapDateTruncInComparison} for {@link TimestampWithTimeZoneType}. + * {@code UnwrapDateTruncInComparison} handles {@link DateType} and {@link TimestampType}, but cannot handle + * {@link TimestampWithTimeZoneType}. Such unwrap would not be monotonic. If we know * that {@link TimestampWithTimeZoneType} is always in UTC zone (point in time, with no time zone information), - * so we can unwrap. + * we can unwrap. */ @Test public void testExtractDateTruncTimestampTzComparison() @@ -236,11 +233,11 @@ public void testExtractDateTruncTimestampTzComparison() } /** - * Test equivalent of {@link UnwrapYearInComparison} for {@link TimestampWithTimeZoneType}. - * {@link UnwrapYearInComparison} handles {@link DateType} and {@link TimestampType}, but cannot handle - * {@link TimestampWithTimeZoneType}. Such unwrap would not be monotonic. Within Iceberg, we know + * Test equivalent of {@code io.trino.sql.planner.iterative.rule.UnwrapYearInComparison} for {@link TimestampWithTimeZoneType}. + * {@code UnwrapYearInComparison} handles {@link DateType} and {@link TimestampType}, but cannot handle + * {@link TimestampWithTimeZoneType}. Such unwrap would not be monotonic. If we know * that {@link TimestampWithTimeZoneType} is always in UTC zone (point in time, with no time zone information), - * so we can unwrap. + * we can unwrap. */ @Test public void testExtractYearTimestampTzComparison() @@ -350,7 +347,7 @@ A_TIMESTAMP_TZ, domain( private static TupleDomain extract(Constraint constraint) { - ConstraintExtractor.ExtractionResult result = extractTupleDomain(constraint); + UtcConstraintExtractor.ExtractionResult result = extractTupleDomain(constraint); assertThat(result.remainingExpression()) .isEqualTo(Constant.TRUE); return result.tupleDomain(); diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java index f054eabb58dd..e1835cfe88f9 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java @@ -34,6 +34,7 @@ import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.plugin.base.classloader.ClassLoaderSafeSystemTable; +import io.trino.plugin.base.filter.UtcConstraintExtractor; import io.trino.plugin.base.projection.ApplyProjectionUtil; import io.trino.plugin.base.projection.ApplyProjectionUtil.ProjectedColumnRepresentation; import io.trino.plugin.hive.HiveWrittenPartitions; @@ -196,10 +197,10 @@ import static com.google.common.collect.Iterables.getLast; import static com.google.common.collect.Maps.transformValues; import static com.google.common.collect.Sets.difference; +import static io.trino.plugin.base.filter.UtcConstraintExtractor.extractTupleDomain; import static io.trino.plugin.base.projection.ApplyProjectionUtil.extractSupportedProjectedColumns; import static io.trino.plugin.base.projection.ApplyProjectionUtil.replaceWithNewVariables; import static io.trino.plugin.base.util.Procedures.checkProcedureArgument; -import static io.trino.plugin.iceberg.ConstraintExtractor.extractTupleDomain; import static io.trino.plugin.iceberg.ExpressionConverter.isConvertableToIcebergExpression; import static io.trino.plugin.iceberg.ExpressionConverter.toIcebergExpression; import static io.trino.plugin.iceberg.IcebergAnalyzeProperties.getColumnNames; @@ -2477,7 +2478,7 @@ public Optional> applyLimit(Connect public Optional> applyFilter(ConnectorSession session, ConnectorTableHandle handle, Constraint constraint) { IcebergTableHandle table = (IcebergTableHandle) handle; - ConstraintExtractor.ExtractionResult extractionResult = extractTupleDomain(constraint); + UtcConstraintExtractor.ExtractionResult extractionResult = extractTupleDomain(constraint); TupleDomain predicate = extractionResult.tupleDomain() .transformKeys(IcebergColumnHandle.class::cast); if (predicate.isAll() && constraint.getPredicateColumns().isEmpty()) { From d0baa04b0818f667ecb447080a57f6f46f2e103e Mon Sep 17 00:00:00 2001 From: kasiafi <30203062+kasiafi@users.noreply.github.com> Date: Wed, 22 Nov 2023 13:31:12 +0100 Subject: [PATCH 450/587] Generalize ConstraintExtractor to handle TimestempWithTimezoneType of all precisions up to nanos --- .../base/filter/UtcConstraintExtractor.java | 79 ++-- .../filter/TestUtcConstraintExtractor.java | 423 ++++++++++++++---- 2 files changed, 391 insertions(+), 111 deletions(-) diff --git a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/filter/UtcConstraintExtractor.java b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/filter/UtcConstraintExtractor.java index f188b28c4cf0..5b8f19c39472 100644 --- a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/filter/UtcConstraintExtractor.java +++ b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/filter/UtcConstraintExtractor.java @@ -52,9 +52,12 @@ import static io.trino.spi.expression.StandardFunctions.LESS_THAN_OPERATOR_FUNCTION_NAME; import static io.trino.spi.expression.StandardFunctions.LESS_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME; import static io.trino.spi.expression.StandardFunctions.NOT_EQUAL_OPERATOR_FUNCTION_NAME; +import static io.trino.spi.type.DateTimeEncoding.packDateTimeWithZone; +import static io.trino.spi.type.DateTimeEncoding.unpackMillisUtc; import static io.trino.spi.type.TimeZoneKey.UTC_KEY; -import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MICROS; +import static io.trino.spi.type.TimestampWithTimeZoneType.MAX_SHORT_PRECISION; import static io.trino.spi.type.Timestamps.MILLISECONDS_PER_DAY; +import static io.trino.spi.type.Timestamps.MILLISECONDS_PER_SECOND; import static io.trino.spi.type.Timestamps.PICOSECONDS_PER_NANOSECOND; import static java.lang.Math.toIntExact; import static java.math.RoundingMode.UNNECESSARY; @@ -171,9 +174,6 @@ private static Optional> unwrapCastInComparison( ColumnHandle column = resolve(sourceVariable, assignments); if (sourceVariable.getType() instanceof TimestampWithTimeZoneType columnType) { - // Iceberg supports only timestamp(6) with time zone - checkArgument(columnType.getPrecision() == 6, "Unexpected type: %s", columnType); - if (constant.getType() == DateType.DATE) { return unwrapTimestampTzToDateCast(column, columnType, functionName, (long) constant.getValue()) .map(domain -> TupleDomain.withColumnDomains(ImmutableMap.of(column, domain))); @@ -186,15 +186,20 @@ private static Optional> unwrapCastInComparison( private static Optional unwrapTimestampTzToDateCast(ColumnHandle column, Type columnType, FunctionName functionName, long date) { - checkArgument(columnType.equals(TIMESTAMP_TZ_MICROS), "Column of unexpected type %s: %s", columnType, column); - // Verify no overflow. Date values must be in integer range. verify(date <= Integer.MAX_VALUE, "Date value out of range: %s", date); - // In Iceberg, timestamp with time zone values are all in UTC - - LongTimestampWithTimeZone startOfDate = LongTimestampWithTimeZone.fromEpochMillisAndFraction(date * MILLISECONDS_PER_DAY, 0, UTC_KEY); - LongTimestampWithTimeZone startOfNextDate = LongTimestampWithTimeZone.fromEpochMillisAndFraction((date + 1) * MILLISECONDS_PER_DAY, 0, UTC_KEY); + Object startOfDate; + Object startOfNextDate; + int precision = ((TimestampWithTimeZoneType) columnType).getPrecision(); + if (precision <= MAX_SHORT_PRECISION) { + startOfDate = packDateTimeWithZone(date * MILLISECONDS_PER_DAY, UTC_KEY); + startOfNextDate = packDateTimeWithZone((date + 1) * MILLISECONDS_PER_DAY, UTC_KEY); + } + else { + startOfDate = LongTimestampWithTimeZone.fromEpochMillisAndFraction(date * MILLISECONDS_PER_DAY, 0, UTC_KEY); + startOfNextDate = LongTimestampWithTimeZone.fromEpochMillisAndFraction((date + 1) * MILLISECONDS_PER_DAY, 0, UTC_KEY); + } return createDomain(functionName, columnType, startOfDate, startOfNextDate); } @@ -202,19 +207,27 @@ private static Optional unwrapTimestampTzToDateCast(ColumnHandle column, private static Optional unwrapYearInTimestampTzComparison(FunctionName functionName, Type type, Constant constant) { checkArgument(constant.getValue() != null, "Unexpected constant: %s", constant); - checkArgument(type.equals(TIMESTAMP_TZ_MICROS), "Unexpected type: %s", type); int year = toIntExact((Long) constant.getValue()); ZonedDateTime periodStart = ZonedDateTime.of(year, 1, 1, 0, 0, 0, 0, UTC); ZonedDateTime periodEnd = periodStart.plusYears(1); - LongTimestampWithTimeZone start = LongTimestampWithTimeZone.fromEpochSecondsAndFraction(periodStart.toEpochSecond(), 0, UTC_KEY); - LongTimestampWithTimeZone end = LongTimestampWithTimeZone.fromEpochSecondsAndFraction(periodEnd.toEpochSecond(), 0, UTC_KEY); + Object start; + Object end; + int precision = ((TimestampWithTimeZoneType) type).getPrecision(); + if (precision <= MAX_SHORT_PRECISION) { + start = packDateTimeWithZone(periodStart.toEpochSecond() * MILLISECONDS_PER_SECOND, UTC_KEY); + end = packDateTimeWithZone(periodEnd.toEpochSecond() * MILLISECONDS_PER_SECOND, UTC_KEY); + } + else { + start = LongTimestampWithTimeZone.fromEpochSecondsAndFraction(periodStart.toEpochSecond(), 0, UTC_KEY); + end = LongTimestampWithTimeZone.fromEpochSecondsAndFraction(periodEnd.toEpochSecond(), 0, UTC_KEY); + } return createDomain(functionName, type, start, end); } - private static Optional createDomain(FunctionName functionName, Type type, LongTimestampWithTimeZone startOfDate, LongTimestampWithTimeZone startOfNextDate) + private static Optional createDomain(FunctionName functionName, Type type, Object startOfDate, Object startOfNextDate) { if (functionName.equals(EQUAL_OPERATOR_FUNCTION_NAME)) { return Optional.of(Domain.create(ValueSet.ofRanges(Range.range(type, startOfDate, true, startOfNextDate, false)), false)); @@ -267,8 +280,6 @@ private static Optional> unwrapDateTruncInComparison( ColumnHandle column = resolve(sourceVariable, assignments); if (sourceVariable.getType() instanceof TimestampWithTimeZoneType type) { - // Iceberg supports only timestamp(6) with time zone - checkArgument(type.getPrecision() == 6, "Unexpected type: %s", type); verify(constant.getType().equals(type), "This method should not be invoked when type mismatch (i.e. surely not a comparison)"); return unwrapDateTruncInComparison(((Slice) unit.getValue()).toStringUtf8(), functionName, constant) @@ -282,12 +293,23 @@ private static Optional unwrapDateTruncInComparison(String unit, Functio { Type type = constant.getType(); checkArgument(constant.getValue() != null, "Unexpected constant: %s", constant); - checkArgument(type.equals(TIMESTAMP_TZ_MICROS), "Unexpected type: %s", type); - // Normalized to UTC because for comparisons the zone is irrelevant - ZonedDateTime dateTime = Instant.ofEpochMilli(((LongTimestampWithTimeZone) constant.getValue()).getEpochMillis()) - .plusNanos(LongMath.divide(((LongTimestampWithTimeZone) constant.getValue()).getPicosOfMilli(), PICOSECONDS_PER_NANOSECOND, UNNECESSARY)) - .atZone(UTC); + ZonedDateTime dateTime; + int precision = ((TimestampWithTimeZoneType) type).getPrecision(); + if (precision <= MAX_SHORT_PRECISION) { + // Normalized to UTC because for comparisons the zone is irrelevant + dateTime = Instant.ofEpochMilli(unpackMillisUtc((long) constant.getValue())) + .atZone(UTC); + } + else { + if (precision > 9) { + return Optional.empty(); + } + // Normalized to UTC because for comparisons the zone is irrelevant + dateTime = Instant.ofEpochMilli(((LongTimestampWithTimeZone) constant.getValue()).getEpochMillis()) + .plusNanos(LongMath.divide(((LongTimestampWithTimeZone) constant.getValue()).getPicosOfMilli(), PICOSECONDS_PER_NANOSECOND, UNNECESSARY)) + .atZone(UTC); + } ZonedDateTime periodStart; ZonedDateTime nextPeriodStart; @@ -314,8 +336,16 @@ private static Optional unwrapDateTruncInComparison(String unit, Functio } boolean constantAtPeriodStart = dateTime.equals(periodStart); - LongTimestampWithTimeZone start = LongTimestampWithTimeZone.fromEpochSecondsAndFraction(periodStart.toEpochSecond(), 0, UTC_KEY); - LongTimestampWithTimeZone end = LongTimestampWithTimeZone.fromEpochSecondsAndFraction(nextPeriodStart.toEpochSecond(), 0, UTC_KEY); + Object start; + Object end; + if (precision <= MAX_SHORT_PRECISION) { + start = packDateTimeWithZone(periodStart.toEpochSecond() * MILLISECONDS_PER_SECOND, UTC_KEY); + end = packDateTimeWithZone(nextPeriodStart.toEpochSecond() * MILLISECONDS_PER_SECOND, UTC_KEY); + } + else { + start = LongTimestampWithTimeZone.fromEpochSecondsAndFraction(periodStart.toEpochSecond(), 0, UTC_KEY); + end = LongTimestampWithTimeZone.fromEpochSecondsAndFraction(nextPeriodStart.toEpochSecond(), 0, UTC_KEY); + } if (functionName.equals(EQUAL_OPERATOR_FUNCTION_NAME)) { if (!constantAtPeriodStart) { @@ -377,9 +407,6 @@ private static Optional> unwrapYearInTimestampTzCompar ColumnHandle column = resolve(sourceVariable, assignments); if (sourceVariable.getType() instanceof TimestampWithTimeZoneType type) { - // Iceberg supports only timestamp(6) with time zone - checkArgument(type.getPrecision() == 6, "Unexpected type: %s", type); - return unwrapYearInTimestampTzComparison(functionName, type, constant) .map(domain -> TupleDomain.withColumnDomains(ImmutableMap.of(column, domain))); } diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/filter/TestUtcConstraintExtractor.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/filter/TestUtcConstraintExtractor.java index 648acad765ef..3bb4c0722e7e 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/filter/TestUtcConstraintExtractor.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/filter/TestUtcConstraintExtractor.java @@ -48,9 +48,11 @@ import static io.trino.spi.expression.StandardFunctions.NOT_EQUAL_OPERATOR_FUNCTION_NAME; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.BooleanType.BOOLEAN; +import static io.trino.spi.type.DateTimeEncoding.packDateTimeWithZone; import static io.trino.spi.type.DateType.DATE; import static io.trino.spi.type.TimeZoneKey.UTC_KEY; import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MICROS; +import static io.trino.spi.type.TimestampWithTimeZoneType.TIMESTAMP_TZ_MILLIS; import static io.trino.spi.type.Timestamps.MILLISECONDS_PER_DAY; import static io.trino.spi.type.Timestamps.MILLISECONDS_PER_SECOND; import static io.trino.spi.type.Timestamps.PICOSECONDS_PER_MICROSECOND; @@ -61,7 +63,6 @@ public class TestUtcConstraintExtractor { private static final ColumnHandle A_BIGINT = new TestingColumnHandle("a_bigint"); - private static final ColumnHandle A_TIMESTAMP_TZ = new TestingColumnHandle("a_timestamp_tz"); @Test public void testExtractSummary() @@ -86,64 +87,227 @@ public void testExtractSummary() * we can unwrap. */ @Test - public void testExtractTimestampTzDateComparison() + public void testExtractTimestampTzMillisDateComparison() { String timestampTzColumnSymbol = "timestamp_tz_symbol"; - ConnectorExpression castOfColumn = new Call(DATE, CAST_FUNCTION_NAME, ImmutableList.of(new Variable(timestampTzColumnSymbol, TIMESTAMP_TZ_MICROS))); + TimestampWithTimeZoneType columnType = TIMESTAMP_TZ_MILLIS; + ColumnHandle columnHandle = new TestingColumnHandle(timestampTzColumnSymbol); + + ConnectorExpression castOfColumn = new Call(DATE, CAST_FUNCTION_NAME, ImmutableList.of(new Variable(timestampTzColumnSymbol, columnType))); + + LocalDate someDate = LocalDate.of(2005, 9, 10); + ConnectorExpression someDateExpression = new Constant(someDate.toEpochDay(), DATE); + + long startOfDateUtcEpochMillis = someDate.atStartOfDay().toEpochSecond(UTC) * MILLISECONDS_PER_SECOND; + long startOfDateUtc = timestampTzMillisFromEpochMillis(startOfDateUtcEpochMillis); + long startOfNextDateUtc = timestampTzMillisFromEpochMillis(startOfDateUtcEpochMillis + MILLISECONDS_PER_DAY); + + assertThat(extract( + constraint( + new Call(BOOLEAN, EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(castOfColumn, someDateExpression)), + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of(columnHandle, domain(Range.range(columnType, startOfDateUtc, true, startOfNextDateUtc, false))))); + + assertThat(extract( + constraint( + new Call(BOOLEAN, NOT_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(castOfColumn, someDateExpression)), + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of(columnHandle, domain( + Range.lessThan(columnType, startOfDateUtc), + Range.greaterThanOrEqual(columnType, startOfNextDateUtc))))); + + assertThat(extract( + constraint( + new Call(BOOLEAN, LESS_THAN_OPERATOR_FUNCTION_NAME, ImmutableList.of(castOfColumn, someDateExpression)), + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of(columnHandle, domain(Range.lessThan(columnType, startOfDateUtc))))); + + assertThat(extract( + constraint( + new Call(BOOLEAN, LESS_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(castOfColumn, someDateExpression)), + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of(columnHandle, domain(Range.lessThan(columnType, startOfNextDateUtc))))); + + assertThat(extract( + constraint( + new Call(BOOLEAN, GREATER_THAN_OPERATOR_FUNCTION_NAME, ImmutableList.of(castOfColumn, someDateExpression)), + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of(columnHandle, domain(Range.greaterThanOrEqual(columnType, startOfNextDateUtc))))); + + assertThat(extract( + constraint( + new Call(BOOLEAN, GREATER_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(castOfColumn, someDateExpression)), + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of(columnHandle, domain(Range.greaterThanOrEqual(columnType, startOfDateUtc))))); + + assertThat(extract( + constraint( + new Call(BOOLEAN, IS_DISTINCT_FROM_OPERATOR_FUNCTION_NAME, ImmutableList.of(castOfColumn, someDateExpression)), + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of(columnHandle, Domain.create( + ValueSet.ofRanges( + Range.lessThan(columnType, startOfDateUtc), + Range.greaterThanOrEqual(columnType, startOfNextDateUtc)), + true)))); + } + + /** + * Test equivalent of {@code io.trino.sql.planner.iterative.rule.UnwrapCastInComparison} for {@link TimestampWithTimeZoneType}. + * {@code UnwrapCastInComparison} handles {@link DateType} and {@link TimestampType}, but cannot handle + * {@link TimestampWithTimeZoneType}. Such unwrap would not be monotonic. If we know + * that {@link TimestampWithTimeZoneType} is always in UTC zone (point in time, with no time zone information), + * we can unwrap. + */ + @Test + public void testExtractTimestampTzMicrosDateComparison() + { + String timestampTzColumnSymbol = "timestamp_tz_symbol"; + TimestampWithTimeZoneType columnType = TIMESTAMP_TZ_MICROS; + ColumnHandle columnHandle = new TestingColumnHandle(timestampTzColumnSymbol); + + ConnectorExpression castOfColumn = new Call(DATE, CAST_FUNCTION_NAME, ImmutableList.of(new Variable(timestampTzColumnSymbol, columnType))); LocalDate someDate = LocalDate.of(2005, 9, 10); ConnectorExpression someDateExpression = new Constant(someDate.toEpochDay(), DATE); long startOfDateUtcEpochMillis = someDate.atStartOfDay().toEpochSecond(UTC) * MILLISECONDS_PER_SECOND; - LongTimestampWithTimeZone startOfDateUtc = timestampTzFromEpochMillis(startOfDateUtcEpochMillis); - LongTimestampWithTimeZone startOfNextDateUtc = timestampTzFromEpochMillis(startOfDateUtcEpochMillis + MILLISECONDS_PER_DAY); + LongTimestampWithTimeZone startOfDateUtc = timestampTzMicrosFromEpochMillis(startOfDateUtcEpochMillis); + LongTimestampWithTimeZone startOfNextDateUtc = timestampTzMicrosFromEpochMillis(startOfDateUtcEpochMillis + MILLISECONDS_PER_DAY); assertThat(extract( constraint( new Call(BOOLEAN, EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(castOfColumn, someDateExpression)), - Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) - .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.range(TIMESTAMP_TZ_MICROS, startOfDateUtc, true, startOfNextDateUtc, false))))); + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of(columnHandle, domain(Range.range(columnType, startOfDateUtc, true, startOfNextDateUtc, false))))); assertThat(extract( constraint( new Call(BOOLEAN, NOT_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(castOfColumn, someDateExpression)), - Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) - .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain( - Range.lessThan(TIMESTAMP_TZ_MICROS, startOfDateUtc), - Range.greaterThanOrEqual(TIMESTAMP_TZ_MICROS, startOfNextDateUtc))))); + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of(columnHandle, domain( + Range.lessThan(columnType, startOfDateUtc), + Range.greaterThanOrEqual(columnType, startOfNextDateUtc))))); assertThat(extract( constraint( new Call(BOOLEAN, LESS_THAN_OPERATOR_FUNCTION_NAME, ImmutableList.of(castOfColumn, someDateExpression)), - Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) - .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.lessThan(TIMESTAMP_TZ_MICROS, startOfDateUtc))))); + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of(columnHandle, domain(Range.lessThan(columnType, startOfDateUtc))))); assertThat(extract( constraint( new Call(BOOLEAN, LESS_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(castOfColumn, someDateExpression)), - Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) - .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.lessThan(TIMESTAMP_TZ_MICROS, startOfNextDateUtc))))); + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of(columnHandle, domain(Range.lessThan(columnType, startOfNextDateUtc))))); assertThat(extract( constraint( new Call(BOOLEAN, GREATER_THAN_OPERATOR_FUNCTION_NAME, ImmutableList.of(castOfColumn, someDateExpression)), - Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) - .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.greaterThanOrEqual(TIMESTAMP_TZ_MICROS, startOfNextDateUtc))))); + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of(columnHandle, domain(Range.greaterThanOrEqual(columnType, startOfNextDateUtc))))); assertThat(extract( constraint( new Call(BOOLEAN, GREATER_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(castOfColumn, someDateExpression)), - Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) - .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.greaterThanOrEqual(TIMESTAMP_TZ_MICROS, startOfDateUtc))))); + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of(columnHandle, domain(Range.greaterThanOrEqual(columnType, startOfDateUtc))))); assertThat(extract( constraint( new Call(BOOLEAN, IS_DISTINCT_FROM_OPERATOR_FUNCTION_NAME, ImmutableList.of(castOfColumn, someDateExpression)), - Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) - .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, Domain.create( + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of(columnHandle, Domain.create( + ValueSet.ofRanges( + Range.lessThan(columnType, startOfDateUtc), + Range.greaterThanOrEqual(columnType, startOfNextDateUtc)), + true)))); + } + + /** + * Test equivalent of {@code io.trino.sql.planner.iterative.rule.UnwrapDateTruncInComparison} for {@link TimestampWithTimeZoneType}. + * {@code UnwrapDateTruncInComparison} handles {@link DateType} and {@link TimestampType}, but cannot handle + * {@link TimestampWithTimeZoneType}. Such unwrap would not be monotonic. If we know + * that {@link TimestampWithTimeZoneType} is always in UTC zone (point in time, with no time zone information), + * we can unwrap. + */ + @Test + public void testExtractDateTruncTimestampTzMillisComparison() + { + String timestampTzColumnSymbol = "timestamp_tz_symbol"; + TimestampWithTimeZoneType columnType = TIMESTAMP_TZ_MILLIS; + ColumnHandle columnHandle = new TestingColumnHandle(timestampTzColumnSymbol); + + ConnectorExpression truncateToDay = new Call( + columnType, + new FunctionName("date_trunc"), + ImmutableList.of( + new Constant(utf8Slice("day"), createVarcharType(17)), + new Variable(timestampTzColumnSymbol, columnType))); + + LocalDate someDate = LocalDate.of(2005, 9, 10); + ConnectorExpression someMidnightExpression = new Constant( + timestampTzMillisFromEpochMillis(someDate.toEpochDay() * MILLISECONDS_PER_DAY), + columnType); + ConnectorExpression someMiddayExpression = new Constant( + timestampTzMillisFromEpochMillis(someDate.toEpochDay() * MILLISECONDS_PER_DAY + MILLISECONDS_PER_DAY / 2), + columnType); + + long startOfDateUtcEpochMillis = someDate.atStartOfDay().toEpochSecond(UTC) * MILLISECONDS_PER_SECOND; + long startOfDateUtc = timestampTzMillisFromEpochMillis(startOfDateUtcEpochMillis); + long startOfNextDateUtc = timestampTzMillisFromEpochMillis(startOfDateUtcEpochMillis + MILLISECONDS_PER_DAY); + + assertThat(extract( + constraint( + new Call(BOOLEAN, EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(truncateToDay, someMidnightExpression)), + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of(columnHandle, domain(Range.range(columnType, startOfDateUtc, true, startOfNextDateUtc, false))))); + + assertThat(extract( + constraint( + new Call(BOOLEAN, EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(truncateToDay, someMiddayExpression)), + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.none()); + + assertThat(extract( + constraint( + new Call(BOOLEAN, NOT_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(truncateToDay, someMidnightExpression)), + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of(columnHandle, domain( + Range.lessThan(columnType, startOfDateUtc), + Range.greaterThanOrEqual(columnType, startOfNextDateUtc))))); + + assertThat(extract( + constraint( + new Call(BOOLEAN, LESS_THAN_OPERATOR_FUNCTION_NAME, ImmutableList.of(truncateToDay, someMidnightExpression)), + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of(columnHandle, domain(Range.lessThan(columnType, startOfDateUtc))))); + + assertThat(extract( + constraint( + new Call(BOOLEAN, LESS_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(truncateToDay, someMidnightExpression)), + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of(columnHandle, domain(Range.lessThan(columnType, startOfNextDateUtc))))); + + assertThat(extract( + constraint( + new Call(BOOLEAN, GREATER_THAN_OPERATOR_FUNCTION_NAME, ImmutableList.of(truncateToDay, someMidnightExpression)), + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of(columnHandle, domain(Range.greaterThanOrEqual(columnType, startOfNextDateUtc))))); + + assertThat(extract( + constraint( + new Call(BOOLEAN, GREATER_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(truncateToDay, someMidnightExpression)), + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of(columnHandle, domain(Range.greaterThanOrEqual(columnType, startOfDateUtc))))); + + assertThat(extract( + constraint( + new Call(BOOLEAN, IS_DISTINCT_FROM_OPERATOR_FUNCTION_NAME, ImmutableList.of(truncateToDay, someMidnightExpression)), + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of(columnHandle, Domain.create( ValueSet.ofRanges( - Range.lessThan(TIMESTAMP_TZ_MICROS, startOfDateUtc), - Range.greaterThanOrEqual(TIMESTAMP_TZ_MICROS, startOfNextDateUtc)), + Range.lessThan(columnType, startOfDateUtc), + Range.greaterThanOrEqual(columnType, startOfNextDateUtc)), true)))); } @@ -155,80 +319,158 @@ public void testExtractTimestampTzDateComparison() * we can unwrap. */ @Test - public void testExtractDateTruncTimestampTzComparison() + public void testExtractDateTruncTimestampTzMicrosComparison() { String timestampTzColumnSymbol = "timestamp_tz_symbol"; + TimestampWithTimeZoneType columnType = TIMESTAMP_TZ_MICROS; + ColumnHandle columnHandle = new TestingColumnHandle(timestampTzColumnSymbol); + ConnectorExpression truncateToDay = new Call( - TIMESTAMP_TZ_MICROS, + columnType, new FunctionName("date_trunc"), ImmutableList.of( new Constant(utf8Slice("day"), createVarcharType(17)), - new Variable(timestampTzColumnSymbol, TIMESTAMP_TZ_MICROS))); + new Variable(timestampTzColumnSymbol, columnType))); LocalDate someDate = LocalDate.of(2005, 9, 10); ConnectorExpression someMidnightExpression = new Constant( LongTimestampWithTimeZone.fromEpochMillisAndFraction(someDate.toEpochDay() * MILLISECONDS_PER_DAY, 0, UTC_KEY), - TIMESTAMP_TZ_MICROS); + columnType); ConnectorExpression someMiddayExpression = new Constant( LongTimestampWithTimeZone.fromEpochMillisAndFraction(someDate.toEpochDay() * MILLISECONDS_PER_DAY, PICOSECONDS_PER_MICROSECOND, UTC_KEY), - TIMESTAMP_TZ_MICROS); + columnType); long startOfDateUtcEpochMillis = someDate.atStartOfDay().toEpochSecond(UTC) * MILLISECONDS_PER_SECOND; - LongTimestampWithTimeZone startOfDateUtc = timestampTzFromEpochMillis(startOfDateUtcEpochMillis); - LongTimestampWithTimeZone startOfNextDateUtc = timestampTzFromEpochMillis(startOfDateUtcEpochMillis + MILLISECONDS_PER_DAY); + LongTimestampWithTimeZone startOfDateUtc = timestampTzMicrosFromEpochMillis(startOfDateUtcEpochMillis); + LongTimestampWithTimeZone startOfNextDateUtc = timestampTzMicrosFromEpochMillis(startOfDateUtcEpochMillis + MILLISECONDS_PER_DAY); assertThat(extract( constraint( new Call(BOOLEAN, EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(truncateToDay, someMidnightExpression)), - Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) - .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.range(TIMESTAMP_TZ_MICROS, startOfDateUtc, true, startOfNextDateUtc, false))))); + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of(columnHandle, domain(Range.range(columnType, startOfDateUtc, true, startOfNextDateUtc, false))))); assertThat(extract( constraint( new Call(BOOLEAN, EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(truncateToDay, someMiddayExpression)), - Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) + Map.of(timestampTzColumnSymbol, columnHandle)))) .isEqualTo(TupleDomain.none()); assertThat(extract( constraint( new Call(BOOLEAN, NOT_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(truncateToDay, someMidnightExpression)), - Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) - .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain( - Range.lessThan(TIMESTAMP_TZ_MICROS, startOfDateUtc), - Range.greaterThanOrEqual(TIMESTAMP_TZ_MICROS, startOfNextDateUtc))))); + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of(columnHandle, domain( + Range.lessThan(columnType, startOfDateUtc), + Range.greaterThanOrEqual(columnType, startOfNextDateUtc))))); assertThat(extract( constraint( new Call(BOOLEAN, LESS_THAN_OPERATOR_FUNCTION_NAME, ImmutableList.of(truncateToDay, someMidnightExpression)), - Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) - .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.lessThan(TIMESTAMP_TZ_MICROS, startOfDateUtc))))); + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of(columnHandle, domain(Range.lessThan(columnType, startOfDateUtc))))); assertThat(extract( constraint( new Call(BOOLEAN, LESS_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(truncateToDay, someMidnightExpression)), - Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) - .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.lessThan(TIMESTAMP_TZ_MICROS, startOfNextDateUtc))))); + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of(columnHandle, domain(Range.lessThan(columnType, startOfNextDateUtc))))); assertThat(extract( constraint( new Call(BOOLEAN, GREATER_THAN_OPERATOR_FUNCTION_NAME, ImmutableList.of(truncateToDay, someMidnightExpression)), - Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) - .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.greaterThanOrEqual(TIMESTAMP_TZ_MICROS, startOfNextDateUtc))))); + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of(columnHandle, domain(Range.greaterThanOrEqual(columnType, startOfNextDateUtc))))); assertThat(extract( constraint( new Call(BOOLEAN, GREATER_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(truncateToDay, someMidnightExpression)), - Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) - .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.greaterThanOrEqual(TIMESTAMP_TZ_MICROS, startOfDateUtc))))); + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of(columnHandle, domain(Range.greaterThanOrEqual(columnType, startOfDateUtc))))); assertThat(extract( constraint( new Call(BOOLEAN, IS_DISTINCT_FROM_OPERATOR_FUNCTION_NAME, ImmutableList.of(truncateToDay, someMidnightExpression)), - Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) - .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, Domain.create( + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of(columnHandle, Domain.create( + ValueSet.ofRanges( + Range.lessThan(columnType, startOfDateUtc), + Range.greaterThanOrEqual(columnType, startOfNextDateUtc)), + true)))); + } + + /** + * Test equivalent of {@code io.trino.sql.planner.iterative.rule.UnwrapYearInComparison} for {@link TimestampWithTimeZoneType}. + * {@code UnwrapYearInComparison} handles {@link DateType} and {@link TimestampType}, but cannot handle + * {@link TimestampWithTimeZoneType}. Such unwrap would not be monotonic. If we know + * that {@link TimestampWithTimeZoneType} is always in UTC zone (point in time, with no time zone information), + * we can unwrap. + */ + @Test + public void testExtractYearTimestampTzMicrosComparison() + { + String timestampTzColumnSymbol = "timestamp_tz_symbol"; + TimestampWithTimeZoneType columnType = TIMESTAMP_TZ_MICROS; + TestingColumnHandle columnHandle = new TestingColumnHandle(timestampTzColumnSymbol); + + ConnectorExpression extractYear = new Call( + BIGINT, + new FunctionName("year"), + ImmutableList.of(new Variable(timestampTzColumnSymbol, columnType))); + + LocalDate someDate = LocalDate.of(2005, 9, 10); + ConnectorExpression yearExpression = new Constant(2005L, BIGINT); + + long startOfYearUtcEpochMillis = someDate.withDayOfYear(1).atStartOfDay().toEpochSecond(UTC) * MILLISECONDS_PER_SECOND; + LongTimestampWithTimeZone startOfYearUtc = timestampTzMicrosFromEpochMillis(startOfYearUtcEpochMillis); + LongTimestampWithTimeZone startOfNextDateUtc = timestampTzMicrosFromEpochMillis(someDate.plusYears(1).withDayOfYear(1).atStartOfDay().toEpochSecond(UTC) * MILLISECONDS_PER_SECOND); + + assertThat(extract( + constraint( + new Call(BOOLEAN, EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(extractYear, yearExpression)), + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of((ColumnHandle) columnHandle, domain(Range.range(columnType, startOfYearUtc, true, startOfNextDateUtc, false))))); + + assertThat(extract( + constraint( + new Call(BOOLEAN, NOT_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(extractYear, yearExpression)), + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of((ColumnHandle) columnHandle, domain( + Range.lessThan(columnType, startOfYearUtc), + Range.greaterThanOrEqual(columnType, startOfNextDateUtc))))); + + assertThat(extract( + constraint( + new Call(BOOLEAN, LESS_THAN_OPERATOR_FUNCTION_NAME, ImmutableList.of(extractYear, yearExpression)), + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of((ColumnHandle) columnHandle, domain(Range.lessThan(columnType, startOfYearUtc))))); + + assertThat(extract( + constraint( + new Call(BOOLEAN, LESS_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(extractYear, yearExpression)), + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of((ColumnHandle) columnHandle, domain(Range.lessThan(columnType, startOfNextDateUtc))))); + + assertThat(extract( + constraint( + new Call(BOOLEAN, GREATER_THAN_OPERATOR_FUNCTION_NAME, ImmutableList.of(extractYear, yearExpression)), + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of((ColumnHandle) columnHandle, domain(Range.greaterThanOrEqual(columnType, startOfNextDateUtc))))); + + assertThat(extract( + constraint( + new Call(BOOLEAN, GREATER_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(extractYear, yearExpression)), + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of((ColumnHandle) columnHandle, domain(Range.greaterThanOrEqual(columnType, startOfYearUtc))))); + + assertThat(extract( + constraint( + new Call(BOOLEAN, IS_DISTINCT_FROM_OPERATOR_FUNCTION_NAME, ImmutableList.of(extractYear, yearExpression)), + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of((ColumnHandle) columnHandle, Domain.create( ValueSet.ofRanges( - Range.lessThan(TIMESTAMP_TZ_MICROS, startOfDateUtc), - Range.greaterThanOrEqual(TIMESTAMP_TZ_MICROS, startOfNextDateUtc)), + Range.lessThan(columnType, startOfYearUtc), + Range.greaterThanOrEqual(columnType, startOfNextDateUtc)), true)))); } @@ -240,67 +482,70 @@ public void testExtractDateTruncTimestampTzComparison() * we can unwrap. */ @Test - public void testExtractYearTimestampTzComparison() + public void testExtractYearTimestampTzMillisComparison() { String timestampTzColumnSymbol = "timestamp_tz_symbol"; + TimestampWithTimeZoneType columnType = TIMESTAMP_TZ_MILLIS; + TestingColumnHandle columnHandle = new TestingColumnHandle(timestampTzColumnSymbol); + ConnectorExpression extractYear = new Call( BIGINT, new FunctionName("year"), - ImmutableList.of(new Variable(timestampTzColumnSymbol, TIMESTAMP_TZ_MICROS))); + ImmutableList.of(new Variable(timestampTzColumnSymbol, columnType))); LocalDate someDate = LocalDate.of(2005, 9, 10); ConnectorExpression yearExpression = new Constant(2005L, BIGINT); long startOfYearUtcEpochMillis = someDate.withDayOfYear(1).atStartOfDay().toEpochSecond(UTC) * MILLISECONDS_PER_SECOND; - LongTimestampWithTimeZone startOfYearUtc = timestampTzFromEpochMillis(startOfYearUtcEpochMillis); - LongTimestampWithTimeZone startOfNextDateUtc = timestampTzFromEpochMillis(someDate.plusYears(1).withDayOfYear(1).atStartOfDay().toEpochSecond(UTC) * MILLISECONDS_PER_SECOND); + long startOfYearUtc = timestampTzMillisFromEpochMillis(startOfYearUtcEpochMillis); + long startOfNextDateUtc = timestampTzMillisFromEpochMillis(someDate.plusYears(1).withDayOfYear(1).atStartOfDay().toEpochSecond(UTC) * MILLISECONDS_PER_SECOND); assertThat(extract( constraint( new Call(BOOLEAN, EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(extractYear, yearExpression)), - Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) - .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.range(TIMESTAMP_TZ_MICROS, startOfYearUtc, true, startOfNextDateUtc, false))))); + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of((ColumnHandle) columnHandle, domain(Range.range(columnType, startOfYearUtc, true, startOfNextDateUtc, false))))); assertThat(extract( constraint( new Call(BOOLEAN, NOT_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(extractYear, yearExpression)), - Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) - .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain( - Range.lessThan(TIMESTAMP_TZ_MICROS, startOfYearUtc), - Range.greaterThanOrEqual(TIMESTAMP_TZ_MICROS, startOfNextDateUtc))))); + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of((ColumnHandle) columnHandle, domain( + Range.lessThan(columnType, startOfYearUtc), + Range.greaterThanOrEqual(columnType, startOfNextDateUtc))))); assertThat(extract( constraint( new Call(BOOLEAN, LESS_THAN_OPERATOR_FUNCTION_NAME, ImmutableList.of(extractYear, yearExpression)), - Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) - .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.lessThan(TIMESTAMP_TZ_MICROS, startOfYearUtc))))); + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of((ColumnHandle) columnHandle, domain(Range.lessThan(columnType, startOfYearUtc))))); assertThat(extract( constraint( new Call(BOOLEAN, LESS_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(extractYear, yearExpression)), - Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) - .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.lessThan(TIMESTAMP_TZ_MICROS, startOfNextDateUtc))))); + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of((ColumnHandle) columnHandle, domain(Range.lessThan(columnType, startOfNextDateUtc))))); assertThat(extract( constraint( new Call(BOOLEAN, GREATER_THAN_OPERATOR_FUNCTION_NAME, ImmutableList.of(extractYear, yearExpression)), - Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) - .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.greaterThanOrEqual(TIMESTAMP_TZ_MICROS, startOfNextDateUtc))))); + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of((ColumnHandle) columnHandle, domain(Range.greaterThanOrEqual(columnType, startOfNextDateUtc))))); assertThat(extract( constraint( new Call(BOOLEAN, GREATER_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(extractYear, yearExpression)), - Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) - .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.greaterThanOrEqual(TIMESTAMP_TZ_MICROS, startOfYearUtc))))); + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of((ColumnHandle) columnHandle, domain(Range.greaterThanOrEqual(columnType, startOfYearUtc))))); assertThat(extract( constraint( new Call(BOOLEAN, IS_DISTINCT_FROM_OPERATOR_FUNCTION_NAME, ImmutableList.of(extractYear, yearExpression)), - Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) - .isEqualTo(TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, Domain.create( + Map.of(timestampTzColumnSymbol, columnHandle)))) + .isEqualTo(TupleDomain.withColumnDomains(Map.of((ColumnHandle) columnHandle, Domain.create( ValueSet.ofRanges( - Range.lessThan(TIMESTAMP_TZ_MICROS, startOfYearUtc), - Range.greaterThanOrEqual(TIMESTAMP_TZ_MICROS, startOfNextDateUtc)), + Range.lessThan(columnType, startOfYearUtc), + Range.greaterThanOrEqual(columnType, startOfNextDateUtc)), true)))); } @@ -308,41 +553,44 @@ public void testExtractYearTimestampTzComparison() public void testIntersectSummaryAndExpressionExtraction() { String timestampTzColumnSymbol = "timestamp_tz_symbol"; - ConnectorExpression castOfColumn = new Call(DATE, CAST_FUNCTION_NAME, ImmutableList.of(new Variable(timestampTzColumnSymbol, TIMESTAMP_TZ_MICROS))); + TimestampWithTimeZoneType columnType = TIMESTAMP_TZ_MICROS; + TestingColumnHandle columnHandle = new TestingColumnHandle(timestampTzColumnSymbol); + + ConnectorExpression castOfColumn = new Call(DATE, CAST_FUNCTION_NAME, ImmutableList.of(new Variable(timestampTzColumnSymbol, columnType))); LocalDate someDate = LocalDate.of(2005, 9, 10); ConnectorExpression someDateExpression = new Constant(someDate.toEpochDay(), DATE); long startOfDateUtcEpochMillis = someDate.atStartOfDay().toEpochSecond(UTC) * MILLISECONDS_PER_SECOND; - LongTimestampWithTimeZone startOfDateUtc = timestampTzFromEpochMillis(startOfDateUtcEpochMillis); - LongTimestampWithTimeZone startOfNextDateUtc = timestampTzFromEpochMillis(startOfDateUtcEpochMillis + MILLISECONDS_PER_DAY); - LongTimestampWithTimeZone startOfNextNextDateUtc = timestampTzFromEpochMillis(startOfDateUtcEpochMillis + MILLISECONDS_PER_DAY * 2); + LongTimestampWithTimeZone startOfDateUtc = timestampTzMicrosFromEpochMillis(startOfDateUtcEpochMillis); + LongTimestampWithTimeZone startOfNextDateUtc = timestampTzMicrosFromEpochMillis(startOfDateUtcEpochMillis + MILLISECONDS_PER_DAY); + LongTimestampWithTimeZone startOfNextNextDateUtc = timestampTzMicrosFromEpochMillis(startOfDateUtcEpochMillis + MILLISECONDS_PER_DAY * 2); assertThat(extract( constraint( - TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.lessThan(TIMESTAMP_TZ_MICROS, startOfNextNextDateUtc)))), + TupleDomain.withColumnDomains(Map.of(columnHandle, domain(Range.lessThan(columnType, startOfNextNextDateUtc)))), new Call(BOOLEAN, NOT_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(castOfColumn, someDateExpression)), - Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) + Map.of(timestampTzColumnSymbol, columnHandle)))) .isEqualTo(TupleDomain.withColumnDomains(Map.of( - A_TIMESTAMP_TZ, domain( - Range.lessThan(TIMESTAMP_TZ_MICROS, startOfDateUtc), - Range.range(TIMESTAMP_TZ_MICROS, startOfNextDateUtc, true, startOfNextNextDateUtc, false))))); + (ColumnHandle) columnHandle, domain( + Range.lessThan(columnType, startOfDateUtc), + Range.range(columnType, startOfNextDateUtc, true, startOfNextNextDateUtc, false))))); assertThat(extract( constraint( - TupleDomain.withColumnDomains(Map.of(A_TIMESTAMP_TZ, domain(Range.lessThan(TIMESTAMP_TZ_MICROS, startOfNextDateUtc)))), + TupleDomain.withColumnDomains(Map.of(columnHandle, domain(Range.lessThan(columnType, startOfNextDateUtc)))), new Call(BOOLEAN, GREATER_THAN_OPERATOR_FUNCTION_NAME, ImmutableList.of(castOfColumn, someDateExpression)), - Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) + Map.of(timestampTzColumnSymbol, columnHandle)))) .isEqualTo(TupleDomain.none()); assertThat(extract( constraint( TupleDomain.withColumnDomains(Map.of(A_BIGINT, Domain.singleValue(BIGINT, 1L))), new Call(BOOLEAN, GREATER_THAN_OR_EQUAL_OPERATOR_FUNCTION_NAME, ImmutableList.of(castOfColumn, someDateExpression)), - Map.of(timestampTzColumnSymbol, A_TIMESTAMP_TZ)))) + Map.of(timestampTzColumnSymbol, columnHandle)))) .isEqualTo(TupleDomain.withColumnDomains(Map.of( A_BIGINT, Domain.singleValue(BIGINT, 1L), - A_TIMESTAMP_TZ, domain(Range.greaterThanOrEqual(TIMESTAMP_TZ_MICROS, startOfDateUtc))))); + columnHandle, domain(Range.greaterThanOrEqual(columnType, startOfDateUtc))))); } private static TupleDomain extract(Constraint constraint) @@ -363,7 +611,12 @@ private static Constraint constraint(TupleDomain summary, Connecto return new Constraint(summary, expression, assignments); } - private static LongTimestampWithTimeZone timestampTzFromEpochMillis(long epochMillis) + private static long timestampTzMillisFromEpochMillis(long epochMillis) + { + return packDateTimeWithZone(epochMillis, UTC_KEY); + } + + private static LongTimestampWithTimeZone timestampTzMicrosFromEpochMillis(long epochMillis) { return LongTimestampWithTimeZone.fromEpochMillisAndFraction(epochMillis, 0, UTC_KEY); } From 69759e61e7f03b5861fdbc7215f299e8aa7c254f Mon Sep 17 00:00:00 2001 From: kasiafi <30203062+kasiafi@users.noreply.github.com> Date: Wed, 22 Nov 2023 14:04:59 +0100 Subject: [PATCH 451/587] Add UTC-based optimizations in DeltaLakeMetadata --- .../io/trino/sql/query/QueryAssertions.java | 29 ++++ .../plugin/deltalake/DeltaLakeMetadata.java | 73 ++++++--- .../deltalake/TestDeltaLakeConnectorTest.java | 140 +++++++++++++----- .../trino/tests/BaseQueryAssertionsTest.java | 19 +++ .../trino/tests/TestLocalQueryAssertions.java | 9 ++ 5 files changed, 211 insertions(+), 59 deletions(-) diff --git a/core/trino-main/src/test/java/io/trino/sql/query/QueryAssertions.java b/core/trino-main/src/test/java/io/trino/sql/query/QueryAssertions.java index a39fdf8123fa..e50954ddbba7 100644 --- a/core/trino-main/src/test/java/io/trino/sql/query/QueryAssertions.java +++ b/core/trino-main/src/test/java/io/trino/sql/query/QueryAssertions.java @@ -33,6 +33,7 @@ import io.trino.sql.planner.plan.JoinNode; import io.trino.sql.planner.plan.PlanNode; import io.trino.sql.planner.plan.TableScanNode; +import io.trino.sql.planner.plan.ValuesNode; import io.trino.testing.LocalQueryRunner; import io.trino.testing.MaterializedResult; import io.trino.testing.MaterializedRow; @@ -501,6 +502,34 @@ public QueryAssert isFullyPushedDown() return this; } + /** + * Verifies query is fully pushed down and Table Scan is replaced with empty Values. + * Verifies that results are the same as when pushdown is fully disabled. + */ + @CanIgnoreReturnValue + public QueryAssert isReplacedWithEmptyValues() + { + checkState(!(runner instanceof LocalQueryRunner), "isReplacedWithEmptyValues() currently does not work with LocalQueryRunner"); + + transaction(runner.getTransactionManager(), runner.getMetadata(), runner.getAccessControl()) + .execute(session, session -> { + Plan plan = runner.createPlan(session, query); + assertPlan( + session, + runner.getMetadata(), + runner.getFunctionManager(), + noopStatsCalculator(), + plan, + PlanMatchPattern.output(PlanMatchPattern.node(ValuesNode.class).with(ValuesNode.class, valuesNode -> valuesNode.getRowCount() == 0))); + }); + + if (!skipResultsCorrectnessCheckForPushdown) { + // Compare the results with pushdown disabled, so that explicit matches() call is not needed + hasCorrectResultsRegardlessOfPushdown(); + } + return this; + } + /** * Verifies query is not fully pushed down and that results are the same as when pushdown is fully disabled. *

    diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java index 42f790c003eb..6d663016204c 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java @@ -32,6 +32,7 @@ import io.trino.filesystem.TrinoFileSystem; import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.plugin.base.classloader.ClassLoaderSafeSystemTable; +import io.trino.plugin.base.filter.UtcConstraintExtractor; import io.trino.plugin.base.projection.ApplyProjectionUtil; import io.trino.plugin.deltalake.DeltaLakeAnalyzeProperties.AnalyzeMode; import io.trino.plugin.deltalake.expression.ParsingException; @@ -175,6 +176,7 @@ import static com.google.common.collect.Sets.difference; import static com.google.common.primitives.Ints.max; import static io.trino.filesystem.Locations.appendPath; +import static io.trino.plugin.base.filter.UtcConstraintExtractor.extractTupleDomain; import static io.trino.plugin.base.projection.ApplyProjectionUtil.ProjectedColumnRepresentation; import static io.trino.plugin.base.projection.ApplyProjectionUtil.extractSupportedProjectedColumns; import static io.trino.plugin.base.projection.ApplyProjectionUtil.replaceWithNewVariables; @@ -2694,31 +2696,56 @@ public Optional> applyFilter(C DeltaLakeTableHandle tableHandle = (DeltaLakeTableHandle) handle; SchemaTableName tableName = tableHandle.getSchemaTableName(); - Set partitionColumns = ImmutableSet.copyOf(extractPartitionColumns(tableHandle.getMetadataEntry(), tableHandle.getProtocolEntry(), typeManager)); - Map constraintDomains = constraint.getSummary().getDomains().orElseThrow(() -> new IllegalArgumentException("constraint summary is NONE")); + checkArgument(constraint.getSummary().getDomains().isPresent(), "constraint summary is NONE"); - ImmutableMap.Builder enforceableDomains = ImmutableMap.builder(); - ImmutableMap.Builder unenforceableDomains = ImmutableMap.builder(); - ImmutableSet.Builder constraintColumns = ImmutableSet.builder(); - // We need additional field to track partition columns used in queries as enforceDomains seem to be not catching - // cases when partition columns is used within complex filter as 'partitionColumn % 2 = 0' - constraint.getPredicateColumns().stream() - .flatMap(Collection::stream) - .map(DeltaLakeColumnHandle.class::cast) - .forEach(constraintColumns::add); - for (Entry domainEntry : constraintDomains.entrySet()) { - DeltaLakeColumnHandle column = (DeltaLakeColumnHandle) domainEntry.getKey(); - if (!partitionColumns.contains(column)) { - unenforceableDomains.put(column, domainEntry.getValue()); - } - else { - enforceableDomains.put(column, domainEntry.getValue()); + UtcConstraintExtractor.ExtractionResult extractionResult = extractTupleDomain(constraint); + TupleDomain predicate = extractionResult.tupleDomain(); + + if (predicate.isAll() && constraint.getPredicateColumns().isEmpty()) { + return Optional.empty(); + } + + TupleDomain newEnforcedConstraint; + TupleDomain newUnenforcedConstraint; + Set newConstraintColumns; + if (predicate.isNone()) { + // Engine does not pass none Constraint.summary. It can become none when combined with the expression and connector's domain knowledge. + newEnforcedConstraint = TupleDomain.none(); + newUnenforcedConstraint = TupleDomain.all(); + newConstraintColumns = constraint.getPredicateColumns().stream() + .flatMap(Collection::stream) + .map(DeltaLakeColumnHandle.class::cast) + .collect(toImmutableSet()); + } + else { + Set partitionColumns = ImmutableSet.copyOf(extractPartitionColumns(tableHandle.getMetadataEntry(), tableHandle.getProtocolEntry(), typeManager)); + Map constraintDomains = predicate.getDomains().orElseThrow(); + + ImmutableMap.Builder enforceableDomains = ImmutableMap.builder(); + ImmutableMap.Builder unenforceableDomains = ImmutableMap.builder(); + ImmutableSet.Builder constraintColumns = ImmutableSet.builder(); + // We need additional field to track partition columns used in queries as enforceDomains seem to be not catching + // cases when partition columns is used within complex filter as 'partitionColumn % 2 = 0' + constraint.getPredicateColumns().stream() + .flatMap(Collection::stream) + .map(DeltaLakeColumnHandle.class::cast) + .forEach(constraintColumns::add); + for (Entry domainEntry : constraintDomains.entrySet()) { + DeltaLakeColumnHandle column = (DeltaLakeColumnHandle) domainEntry.getKey(); + if (!partitionColumns.contains(column)) { + unenforceableDomains.put(column, domainEntry.getValue()); + } + else { + enforceableDomains.put(column, domainEntry.getValue()); + } + constraintColumns.add(column); } - constraintColumns.add(column); + + newEnforcedConstraint = TupleDomain.withColumnDomains(enforceableDomains.buildOrThrow()); + newUnenforcedConstraint = TupleDomain.withColumnDomains(unenforceableDomains.buildOrThrow()); + newConstraintColumns = constraintColumns.build(); } - TupleDomain newEnforcedConstraint = TupleDomain.withColumnDomains(enforceableDomains.buildOrThrow()); - TupleDomain newUnenforcedConstraint = TupleDomain.withColumnDomains(unenforceableDomains.buildOrThrow()); DeltaLakeTableHandle newHandle = new DeltaLakeTableHandle( tableName.getSchemaName(), tableName.getTableName(), @@ -2733,7 +2760,7 @@ public Optional> applyFilter(C tableHandle.getNonPartitionConstraint() .intersect(newUnenforcedConstraint) .simplify(domainCompactionThreshold), - Sets.union(tableHandle.getConstraintColumns(), constraintColumns.build()), + Sets.union(tableHandle.getConstraintColumns(), newConstraintColumns), tableHandle.getWriteType(), tableHandle.getProjectedColumns(), tableHandle.getUpdatedColumns(), @@ -2753,7 +2780,7 @@ public Optional> applyFilter(C return Optional.of(new ConstraintApplicationResult<>( newHandle, newUnenforcedConstraint.transformKeys(ColumnHandle.class::cast), - constraint.getExpression(), + extractionResult.remainingExpression(), false)); } diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConnectorTest.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConnectorTest.java index 5a18909846b4..52b6e166372c 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConnectorTest.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConnectorTest.java @@ -23,6 +23,7 @@ import io.trino.plugin.deltalake.transactionlog.DeltaLakeSchemaSupport.ColumnMappingMode; import io.trino.plugin.hive.metastore.HiveMetastore; import io.trino.plugin.tpch.TpchPlugin; +import io.trino.sql.planner.plan.FilterNode; import io.trino.sql.planner.plan.TableDeleteNode; import io.trino.sql.planner.plan.TableFinishNode; import io.trino.sql.planner.plan.TableWriterNode; @@ -61,6 +62,7 @@ import static io.trino.plugin.deltalake.transactionlog.TransactionLogUtil.TRANSACTION_LOG_DIRECTORY; import static io.trino.plugin.hive.metastore.file.TestingFileHiveMetastore.createTestingFileHiveMetastore; import static io.trino.plugin.tpch.TpchMetadata.TINY_SCHEMA_NAME; +import static io.trino.spi.type.TimeZoneKey.getTimeZoneKey; import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.sql.planner.optimizations.PlanNodeSearcher.searchFrom; import static io.trino.testing.MaterializedResult.resultBuilder; @@ -629,6 +631,72 @@ public void testTimestampWithTimeZonePartition() assertUpdate("DROP TABLE " + tableName); } + @Test + public void testTimestampWithTimeZoneOptimization() + { + String tableName = "test_timestamp_tz_optimization_" + randomNameSuffix(); + + assertUpdate("CREATE TABLE " + tableName + "(id INT, part TIMESTAMP WITH TIME ZONE) WITH (partitioned_by = ARRAY['part'])"); + assertUpdate( + "INSERT INTO " + tableName + " VALUES " + + "(1, NULL)," + + "(2, TIMESTAMP '0001-01-01 00:00:00.000 UTC')," + + "(3, TIMESTAMP '2023-11-21 09:19:00.000 +02:00')," + + "(4, TIMESTAMP '2005-09-10 13:00:00.000 UTC')", + 4); + + // date_trunc optimization + assertThat(query("SELECT * FROM " + tableName + " WHERE date_trunc('day', part) >= TIMESTAMP '2005-09-10 07:00:00.000 +07:00'")) + .isFullyPushedDown() + .matches("VALUES " + + "(3, TIMESTAMP '2023-11-21 07:19:00.000 UTC')," + + "(4, TIMESTAMP '2005-09-10 13:00:00.000 UTC')"); + + assertThat(query("SELECT * FROM " + tableName + " WHERE date_trunc('day', part) = TIMESTAMP '2005-09-10 00:00:00.000 +07:00'")) + .isReplacedWithEmptyValues(); + + assertThat(query("SELECT * FROM " + tableName + " WHERE date_trunc('hour', part) >= TIMESTAMP '2005-09-10 13:00:00.001 +00:00'")) + .isFullyPushedDown() + .matches("VALUES " + + "(3, TIMESTAMP '2023-11-21 07:19:00.000 UTC')"); + + // the DATE is upcast to timestamp_tz using the session time zone (Asia/Kathmandu). + // part is in UTC, so there is no match for date_trunc. + assertThat(query( + Session.builder(getSession()) + .setTimeZoneKey(getTimeZoneKey("Asia/Kathmandu")) + .build(), + "SELECT * FROM " + tableName + " WHERE date_trunc('day', part) = DATE '2005-09-10'")) + .isReplacedWithEmptyValues(); + + assertThat(query("SELECT * FROM " + tableName + " WHERE date_trunc('week', part) >= TIMESTAMP '2005-09-10 00:00:00.000 +00:00'")) + .isNotFullyPushedDown(FilterNode.class); + + // cast timestamp_tz as DATE optimization + assertThat(query("SELECT * FROM " + tableName + " WHERE cast(part AS date) >= DATE '2005-09-10'")) + .isFullyPushedDown() + .matches("VALUES " + + "(3, TIMESTAMP '2023-11-21 07:19:00.000 UTC')," + + "(4, TIMESTAMP '2005-09-10 13:00:00.000 UTC')"); + + assertThat(query("SELECT * FROM " + tableName + " WHERE cast(part AS date) = DATE '2005-10-10'")) + .isFullyPushedDown() + .returnsEmptyResult(); + + // year function optimization + assertThat(query("SELECT * FROM " + tableName + " WHERE year(part) >= 2005")) + .isFullyPushedDown() + .matches("VALUES " + + "(3, TIMESTAMP '2023-11-21 07:19:00.000 UTC')," + + "(4, TIMESTAMP '2005-09-10 13:00:00.000 UTC')"); + + assertThat(query("SELECT * FROM " + tableName + " WHERE year(part) = 2006")) + .isFullyPushedDown() + .returnsEmptyResult(); + + assertUpdate("DROP TABLE " + tableName); + } + @Test public void testAddColumnToPartitionedTable() { @@ -2851,11 +2919,11 @@ public void testRequiredPartitionFilterOnJoin() "test_partition_left_", "(x varchar, part varchar)", ImmutableList.of("('a', 'part_a')")); - TestTable rightTable = new TestTable( - new TrinoSqlExecutor(getQueryRunner(), session), - "test_partition_right_", - "(x varchar, part varchar) WITH (partitioned_by = ARRAY['part'])", - ImmutableList.of("('a', 'part_a')"))) { + TestTable rightTable = new TestTable( + new TrinoSqlExecutor(getQueryRunner(), session), + "test_partition_right_", + "(x varchar, part varchar) WITH (partitioned_by = ARRAY['part'])", + ImmutableList.of("('a', 'part_a')"))) { assertQueryFails( session, "SELECT a.x, b.x from %s a JOIN %s b on (a.x = b.x) where a.x = 'a'".formatted(leftTable.getName(), rightTable.getName()), @@ -2877,11 +2945,11 @@ public void testRequiredPartitionFilterOnJoinBothTablePartitioned() "test_partition_inferred_left_", "(x varchar, part varchar) WITH (partitioned_by = ARRAY['part'])", ImmutableList.of("('a', 'part_a')")); - TestTable rightTable = new TestTable( - new TrinoSqlExecutor(getQueryRunner(), session), - "test_partition_inferred_right_", - "(x varchar, part varchar) WITH (partitioned_by = ARRAY['part'])", - ImmutableList.of("('a', 'part_a')"))) { + TestTable rightTable = new TestTable( + new TrinoSqlExecutor(getQueryRunner(), session), + "test_partition_inferred_right_", + "(x varchar, part varchar) WITH (partitioned_by = ARRAY['part'])", + ImmutableList.of("('a', 'part_a')"))) { assertQueryFails( session, "SELECT a.x, b.x from %s a JOIN %s b on (a.x = b.x) where a.x = 'a'".formatted(leftTable.getName(), rightTable.getName()), @@ -2942,7 +3010,7 @@ public void testPartitionPredicateFilterAndAnalyzeOnPartitionedTable() "(x integer, part integer) WITH (partitioned_by = ARRAY['part'])", ImmutableList.of("(1, 11)", "(2, 22)"))) { String expectedMessageRegExp = "ANALYZE statement can not be performed on partitioned tables because filtering is required on at least one partition." + - " However, the partition filtering check can be disabled with the catalog session property 'query_partition_filter_required'."; + " However, the partition filtering check can be disabled with the catalog session property 'query_partition_filter_required'."; assertQueryFails(session, "ANALYZE " + table.getName(), expectedMessageRegExp); assertQueryFails(session, "EXPLAIN ANALYZE " + table.getName(), expectedMessageRegExp); } @@ -3068,23 +3136,23 @@ public void testPartitionFilterRequiredAndWriteOperation() assertUpdate(session, "UPDATE " + table.getName() + " SET x = 20 WHERE part = 22", 1); assertQueryFails(session, "MERGE INTO " + table.getName() + " t " + - "USING (SELECT * FROM (VALUES (3, 99), (4,44))) AS s(x, part) " + - "ON t.x = s.x " + - "WHEN MATCHED THEN DELETE ", expectedMessageRegExp); + "USING (SELECT * FROM (VALUES (3, 99), (4,44))) AS s(x, part) " + + "ON t.x = s.x " + + "WHEN MATCHED THEN DELETE ", expectedMessageRegExp); assertUpdate(session, "MERGE INTO " + table.getName() + " t " + - "USING (SELECT * FROM (VALUES (2, 22), (4 , 44))) AS s(x, part) " + - "ON (t.part = s.part) " + - "WHEN MATCHED THEN UPDATE " + - " SET x = t.x + s.x, part = t.part ", 1); + "USING (SELECT * FROM (VALUES (2, 22), (4 , 44))) AS s(x, part) " + + "ON (t.part = s.part) " + + "WHEN MATCHED THEN UPDATE " + + " SET x = t.x + s.x, part = t.part ", 1); assertQueryFails(session, "MERGE INTO " + table.getName() + " t " + - "USING (SELECT * FROM (VALUES (4,44))) AS s(x, part) " + - "ON t.x = s.x " + - "WHEN NOT MATCHED THEN INSERT (x, part) VALUES(s.x, s.part) ", expectedMessageRegExp); + "USING (SELECT * FROM (VALUES (4,44))) AS s(x, part) " + + "ON t.x = s.x " + + "WHEN NOT MATCHED THEN INSERT (x, part) VALUES(s.x, s.part) ", expectedMessageRegExp); assertUpdate(session, "MERGE INTO " + table.getName() + " t " + - "USING (SELECT * FROM (VALUES (4, 44))) AS s(x, part) " + - "ON (t.part = s.part) " + - "WHEN NOT MATCHED THEN INSERT (x, part) VALUES(s.x, s.part) ", 1); + "USING (SELECT * FROM (VALUES (4, 44))) AS s(x, part) " + + "ON (t.part = s.part) " + + "WHEN NOT MATCHED THEN INSERT (x, part) VALUES(s.x, s.part) ", 1); assertQueryFails(session, "DELETE FROM " + table.getName() + " WHERE x = 3", expectedMessageRegExp); assertUpdate(session, "DELETE FROM " + table.getName() + " WHERE part = 33 and x = 3", 1); @@ -3201,11 +3269,11 @@ public void testTrinoCacheInvalidatedOnCreateTable() String tableLocation = "s3://%s/%s/%s".formatted(bucketName, SCHEMA, tableName); String initialValues = "VALUES" + - " (1, BOOLEAN 'false', TINYINT '-128')" + - ",(2, BOOLEAN 'true', TINYINT '127')" + - ",(3, BOOLEAN 'false', TINYINT '0')" + - ",(4, BOOLEAN 'false', TINYINT '1')" + - ",(5, BOOLEAN 'true', TINYINT '37')"; + " (1, BOOLEAN 'false', TINYINT '-128')" + + ",(2, BOOLEAN 'true', TINYINT '127')" + + ",(3, BOOLEAN 'false', TINYINT '0')" + + ",(4, BOOLEAN 'false', TINYINT '1')" + + ",(5, BOOLEAN 'true', TINYINT '37')"; assertUpdate("CREATE TABLE " + tableName + "(id, boolean, tinyint) WITH (location = '" + tableLocation + "') AS " + initialValues, 5); assertThat(query("SELECT * FROM " + tableName)).matches(initialValues); @@ -3215,13 +3283,13 @@ public void testTrinoCacheInvalidatedOnCreateTable() } String newValues = "VALUES" + - " (1, BOOLEAN 'true', TINYINT '1')" + - ",(2, BOOLEAN 'true', TINYINT '1')" + - ",(3, BOOLEAN 'false', TINYINT '2')" + - ",(4, BOOLEAN 'true', TINYINT '3')" + - ",(5, BOOLEAN 'true', TINYINT '5')" + - ",(6, BOOLEAN 'false', TINYINT '8')" + - ",(7, BOOLEAN 'true', TINYINT '13')"; + " (1, BOOLEAN 'true', TINYINT '1')" + + ",(2, BOOLEAN 'true', TINYINT '1')" + + ",(3, BOOLEAN 'false', TINYINT '2')" + + ",(4, BOOLEAN 'true', TINYINT '3')" + + ",(5, BOOLEAN 'true', TINYINT '5')" + + ",(6, BOOLEAN 'false', TINYINT '8')" + + ",(7, BOOLEAN 'true', TINYINT '13')"; assertUpdate("CREATE TABLE " + tableName + "(id, boolean, tinyint) WITH (location = '" + tableLocation + "') AS " + newValues, 7); assertThat(query("SELECT * FROM " + tableName)).matches(newValues); diff --git a/testing/trino-tests/src/test/java/io/trino/tests/BaseQueryAssertionsTest.java b/testing/trino-tests/src/test/java/io/trino/tests/BaseQueryAssertionsTest.java index 68a95015e993..d9bd147f9cd8 100644 --- a/testing/trino-tests/src/test/java/io/trino/tests/BaseQueryAssertionsTest.java +++ b/testing/trino-tests/src/test/java/io/trino/tests/BaseQueryAssertionsTest.java @@ -333,6 +333,25 @@ public void testIsFullyPushedDownWithSession() "Output[columnNames = [_col0]]\n"); } + @Test + public void testIsReplacedWithEmptyValues() + { + assertThat(query("SELECT 1 WHERE false")).isReplacedWithEmptyValues(); + + // Test that, in case of failure, there is no failure when rendering expected and actual plans + assertThatThrownBy(() -> assertThat(query("SELECT 1 WHERE true")).isReplacedWithEmptyValues()) + .hasMessageContaining( + "Plan does not match, expected [\n" + + "\n" + + "- node(OutputNode)\n") + .hasMessageContaining( + "\n" + + "\n" + + "] but found [\n" + + "\n" + + "Output[columnNames = [_col0]]\n"); + } + @Test public void testIsNotFullyPushedDown() { diff --git a/testing/trino-tests/src/test/java/io/trino/tests/TestLocalQueryAssertions.java b/testing/trino-tests/src/test/java/io/trino/tests/TestLocalQueryAssertions.java index 2a22eda30e60..f5854d1923ce 100644 --- a/testing/trino-tests/src/test/java/io/trino/tests/TestLocalQueryAssertions.java +++ b/testing/trino-tests/src/test/java/io/trino/tests/TestLocalQueryAssertions.java @@ -66,6 +66,15 @@ public void testIsFullyPushedDownWithSession() .hasMessage("isFullyPushedDown() currently does not work with LocalQueryRunner"); } + @Test + @Override + public void testIsReplacedWithEmptyValues() + { + assertThatThrownBy(() -> assertThat(query("SELECT 1 WHERE false")).isReplacedWithEmptyValues()) + .isInstanceOf(IllegalStateException.class) + .hasMessage("isReplacedWithEmptyValues() currently does not work with LocalQueryRunner"); + } + @Test public void testNullInErrorMessage() { From b2efce76b2e2e6e65da281cc9e9add31ed85a01b Mon Sep 17 00:00:00 2001 From: Kamil Endruszkiewicz Date: Thu, 23 Nov 2023 19:00:43 +0100 Subject: [PATCH 452/587] Partial revert of increase split scheduling config --- .../src/main/java/io/trino/execution/TaskManagerConfig.java | 4 ++-- .../test/java/io/trino/execution/TestTaskManagerConfig.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/execution/TaskManagerConfig.java b/core/trino-main/src/main/java/io/trino/execution/TaskManagerConfig.java index 2d964eb0d00a..4d584c36034a 100644 --- a/core/trino-main/src/main/java/io/trino/execution/TaskManagerConfig.java +++ b/core/trino-main/src/main/java/io/trino/execution/TaskManagerConfig.java @@ -56,10 +56,10 @@ public class TaskManagerConfig private boolean shareIndexLoading; private int maxWorkerThreads = Runtime.getRuntime().availableProcessors() * 2; private Integer minDrivers; - private int initialSplitsPerNode = Runtime.getRuntime().availableProcessors() * 4; + private int initialSplitsPerNode = maxWorkerThreads; private int minDriversPerTask = 3; private int maxDriversPerTask = Integer.MAX_VALUE; - private Duration splitConcurrencyAdjustmentInterval = new Duration(1, TimeUnit.SECONDS); + private Duration splitConcurrencyAdjustmentInterval = new Duration(100, TimeUnit.MILLISECONDS); private DataSize sinkMaxBufferSize = DataSize.of(32, Unit.MEGABYTE); private DataSize sinkMaxBroadcastBufferSize = DataSize.of(200, Unit.MEGABYTE); diff --git a/core/trino-main/src/test/java/io/trino/execution/TestTaskManagerConfig.java b/core/trino-main/src/test/java/io/trino/execution/TestTaskManagerConfig.java index d48d2fde1082..73685aa8f09b 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestTaskManagerConfig.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestTaskManagerConfig.java @@ -41,8 +41,8 @@ public void testDefaults() { assertRecordedDefaults(recordDefaults(TaskManagerConfig.class) .setThreadPerDriverSchedulerEnabled(false) - .setInitialSplitsPerNode(Runtime.getRuntime().availableProcessors() * 4) - .setSplitConcurrencyAdjustmentInterval(new Duration(1, TimeUnit.SECONDS)) + .setInitialSplitsPerNode(Runtime.getRuntime().availableProcessors() * 2) + .setSplitConcurrencyAdjustmentInterval(new Duration(100, TimeUnit.MILLISECONDS)) .setStatusRefreshMaxWait(new Duration(1, TimeUnit.SECONDS)) .setInfoUpdateInterval(new Duration(3, TimeUnit.SECONDS)) .setTaskTerminationTimeout(new Duration(1, TimeUnit.MINUTES)) From 95e81269d3eda5d94f1211a4fa1280b5566dd7de Mon Sep 17 00:00:00 2001 From: Marius Grama Date: Fri, 24 Nov 2023 17:53:42 +0100 Subject: [PATCH 453/587] Remove duplicated initialization statement for `nullCount` --- .../transactionlog/checkpoint/CheckpointEntryIterator.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointEntryIterator.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointEntryIterator.java index ebf150012df4..95b4ddb1fe26 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointEntryIterator.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointEntryIterator.java @@ -522,11 +522,8 @@ private DeltaLakeParquetFileStatistics parseStatisticsFromParquet(ConnectorSessi if (!columnsWithMinMaxStats.isEmpty()) { minValues = Optional.of(parseMinMax(stats.getRow("minValues"), columnsWithMinMaxStats)); maxValues = Optional.of(parseMinMax(stats.getRow("maxValues"), columnsWithMinMaxStats)); - nullCount = Optional.of(parseNullCount(stats.getRow("nullCount"), schema)); - } - else { - nullCount = Optional.of(parseNullCount(stats.getRow("nullCount"), schema)); } + nullCount = Optional.of(parseNullCount(stats.getRow("nullCount"), schema)); return new DeltaLakeParquetFileStatistics( Optional.of(numRecords), From f6f76462c24ded8fea8e6ca5f9792809d5e566d6 Mon Sep 17 00:00:00 2001 From: Marius Grama Date: Tue, 21 Nov 2023 11:00:55 +0100 Subject: [PATCH 454/587] Prune unused stats columns when reading Delta checkpoint Add support for stats projection in Delta checkpoint iterator --- .../plugin/deltalake/DeltaLakeMetadata.java | 8 +- .../deltalake/DeltaLakeSplitManager.java | 8 +- .../FileBasedTableStatisticsProvider.java | 8 +- .../transactionlog/TableSnapshot.java | 13 +- .../transactionlog/TransactionLogAccess.java | 49 ++++- .../checkpoint/CheckpointEntryIterator.java | 27 ++- .../checkpoint/CheckpointSchemaManager.java | 12 +- .../checkpoint/CheckpointWriter.java | 9 +- .../checkpoint/CheckpointWriterManager.java | 7 +- .../plugin/deltalake/TestDeltaLakeBasic.java | 74 +++++++ .../deltalake/TestDeltaLakeSplitManager.java | 9 +- .../transactionlog/TestTableSnapshot.java | 21 +- .../TestCheckpointEntryIterator.java | 203 ++++++++++++++++-- .../checkpoint/TestCheckpointWriter.java | 4 +- .../TestDeltaLakeFileStatistics.java | 10 +- .../parsed_stats_case_sensitive/README.txt | 13 ++ .../_delta_log/00000000000000000000.json | 3 + .../_delta_log/00000000000000000001.json | 3 + .../00000000000000000002.checkpoint.parquet | Bin 0 -> 20131 bytes .../_delta_log/00000000000000000002.json | 2 + .../_delta_log/00000000000000000003.json | 2 + .../_delta_log/_last_checkpoint | 1 + ...4722-9837-4b11599ea66d.c000.snappy.parquet | Bin 0 -> 832 bytes ...4e0a-bf25-eba86bfdce11.c000.snappy.parquet | Bin 0 -> 839 bytes ...4a58-8c2c-d775a1203dd3.c000.snappy.parquet | Bin 0 -> 839 bytes ...4d32-be2f-9051e90920ce.c000.snappy.parquet | Bin 0 -> 839 bytes .../parsed_stats_struct/README.txt | 14 ++ .../_delta_log/00000000000000000000.json | 3 + .../_delta_log/00000000000000000001.json | 3 + .../00000000000000000002.checkpoint.parquet | Bin 0 -> 21976 bytes .../_delta_log/00000000000000000002.json | 2 + .../_delta_log/00000000000000000003.json | 2 + .../_delta_log/_last_checkpoint | 1 + ...4039-920b-d1f24022685e.c000.snappy.parquet | Bin 0 -> 1161 bytes ...44b1-a897-4b0e7d009ae4.c000.snappy.parquet | Bin 0 -> 1168 bytes ...4848-9d25-03962fc0e540.c000.snappy.parquet | Bin 0 -> 1167 bytes ...4848-9853-f82afd408710.c000.snappy.parquet | Bin 0 -> 1168 bytes 37 files changed, 463 insertions(+), 48 deletions(-) create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/README.txt create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/_delta_log/00000000000000000000.json create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/_delta_log/00000000000000000001.json create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/_delta_log/00000000000000000002.checkpoint.parquet create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/_delta_log/00000000000000000002.json create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/_delta_log/00000000000000000003.json create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/_delta_log/_last_checkpoint create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/part=100/part-00000-c707739f-45c0-4722-9837-4b11599ea66d.c000.snappy.parquet create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/part=200/part-00000-9cfa2c9c-efae-4e0a-bf25-eba86bfdce11.c000.snappy.parquet create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/part=300/part-00000-7b5e9086-abfc-4a58-8c2c-d775a1203dd3.c000.snappy.parquet create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/part=400/part-00000-e90e48ef-2b52-4d32-be2f-9051e90920ce.c000.snappy.parquet create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/README.txt create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/_delta_log/00000000000000000000.json create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/_delta_log/00000000000000000001.json create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/_delta_log/00000000000000000002.checkpoint.parquet create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/_delta_log/00000000000000000002.json create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/_delta_log/00000000000000000003.json create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/_delta_log/_last_checkpoint create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/part=100/part-00000-7d5755b3-20b1-4039-920b-d1f24022685e.c000.snappy.parquet create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/part=200/part-00000-22aeb477-f838-44b1-a897-4b0e7d009ae4.c000.snappy.parquet create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/part=300/part-00000-d9fc223e-4feb-4848-9d25-03962fc0e540.c000.snappy.parquet create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/part=400/part-00000-da80387a-f286-4848-9853-f82afd408710.c000.snappy.parquet diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java index 6d663016204c..8f9b8f5eb07d 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java @@ -3546,7 +3546,13 @@ private OptionalLong executeDelete(ConnectorSession session, ConnectorTableHandl private List getAddFileEntriesMatchingEnforcedPartitionConstraint(ConnectorSession session, DeltaLakeTableHandle tableHandle) { TableSnapshot tableSnapshot = getSnapshot(session, tableHandle); - List validDataFiles = transactionLogAccess.getActiveFiles(tableSnapshot, tableHandle.getMetadataEntry(), tableHandle.getProtocolEntry(), tableHandle.getEnforcedPartitionConstraint(), session); + List validDataFiles = transactionLogAccess.getActiveFiles( + tableSnapshot, + tableHandle.getMetadataEntry(), + tableHandle.getProtocolEntry(), + tableHandle.getEnforcedPartitionConstraint(), + tableHandle.getProjectedColumns(), + session); TupleDomain enforcedPartitionConstraint = tableHandle.getEnforcedPartitionConstraint(); if (enforcedPartitionConstraint.isAll()) { return validDataFiles; diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeSplitManager.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeSplitManager.java index 606ab5d55ce1..8f6f48f247db 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeSplitManager.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeSplitManager.java @@ -154,7 +154,13 @@ private Stream getSplits( { TableSnapshot tableSnapshot = deltaLakeTransactionManager.get(transaction, session.getIdentity()) .getSnapshot(session, tableHandle.getSchemaTableName(), tableHandle.getLocation(), tableHandle.getReadVersion()); - List validDataFiles = transactionLogAccess.getActiveFiles(tableSnapshot, tableHandle.getMetadataEntry(), tableHandle.getProtocolEntry(), tableHandle.getEnforcedPartitionConstraint(), session); + List validDataFiles = transactionLogAccess.getActiveFiles( + tableSnapshot, + tableHandle.getMetadataEntry(), + tableHandle.getProtocolEntry(), + tableHandle.getEnforcedPartitionConstraint(), + tableHandle.getProjectedColumns(), + session); TupleDomain enforcedPartitionConstraint = tableHandle.getEnforcedPartitionConstraint(); TupleDomain nonPartitionConstraint = tableHandle.getNonPartitionConstraint(); Domain pathDomain = getPathDomain(nonPartitionConstraint); diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/statistics/FileBasedTableStatisticsProvider.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/statistics/FileBasedTableStatisticsProvider.java index eddcfcb99f84..d91b428fc2c6 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/statistics/FileBasedTableStatisticsProvider.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/statistics/FileBasedTableStatisticsProvider.java @@ -110,7 +110,13 @@ public TableStatistics getTableStatistics(ConnectorSession session, DeltaLakeTab .filter(column -> predicatedColumnNames.contains(column.getName())) .collect(toImmutableList()); - for (AddFileEntry addEntry : transactionLogAccess.getActiveFiles(tableSnapshot, tableHandle.getMetadataEntry(), tableHandle.getProtocolEntry(), session)) { + for (AddFileEntry addEntry : transactionLogAccess.getActiveFiles( + tableSnapshot, + tableHandle.getMetadataEntry(), + tableHandle.getProtocolEntry(), + tableHandle.getEnforcedPartitionConstraint(), + tableHandle.getProjectedColumns(), + session)) { Optional fileStatistics = addEntry.getStats(); if (fileStatistics.isEmpty()) { // Open source Delta Lake does not collect stats diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/TableSnapshot.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/TableSnapshot.java index ba4fc8c37a40..bdf2f428dc0f 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/TableSnapshot.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/TableSnapshot.java @@ -36,6 +36,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Stream; import static com.google.common.base.Preconditions.checkState; @@ -181,7 +182,8 @@ public Stream getCheckpointTransactionLogEntries( TrinoFileSystem fileSystem, FileFormatDataSourceStats stats, Optional metadataAndProtocol, - TupleDomain partitionConstraint) + TupleDomain partitionConstraint, + Optional> addStatsMinMaxColumnFilter) throws IOException { if (lastCheckpoint.isEmpty()) { @@ -210,7 +212,8 @@ public Stream getCheckpointTransactionLogEntries( stats, checkpoint, checkpointFile, - partitionConstraint))); + partitionConstraint, + addStatsMinMaxColumnFilter))); } return resultStream; } @@ -230,7 +233,8 @@ private Iterator getCheckpointTransactionLogEntrie FileFormatDataSourceStats stats, LastCheckpoint checkpoint, TrinoInputFile checkpointFile, - TupleDomain partitionConstraint) + TupleDomain partitionConstraint, + Optional> addStatsMinMaxColumnFilter) throws IOException { long fileSize; @@ -253,7 +257,8 @@ private Iterator getCheckpointTransactionLogEntrie parquetReaderOptions, checkpointRowStatisticsWritingEnabled, domainCompactionThreshold, - partitionConstraint); + partitionConstraint, + addStatsMinMaxColumnFilter); } public record MetadataAndProtocolEntry(MetadataEntry metadataEntry, ProtocolEntry protocolEntry) diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/TransactionLogAccess.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/TransactionLogAccess.java index b503a27c28cb..f308865e90b4 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/TransactionLogAccess.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/TransactionLogAccess.java @@ -68,11 +68,15 @@ import java.util.concurrent.TimeUnit; import java.util.function.BiFunction; import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Stream; +import static com.google.common.base.Predicates.alwaysFalse; +import static com.google.common.base.Predicates.alwaysTrue; import static com.google.common.base.Throwables.throwIfUnchecked; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static com.google.common.collect.ImmutableSet.toImmutableSet; import static io.airlift.slice.SizeOf.estimatedSizeOf; import static io.airlift.slice.SizeOf.instanceSize; import static io.trino.cache.CacheUtils.invalidateAllIf; @@ -223,17 +227,48 @@ public MetadataEntry getMetadataEntry(TableSnapshot tableSnapshot, ConnectorSess .orElseThrow(() -> new TrinoException(DELTA_LAKE_INVALID_SCHEMA, "Metadata not found in transaction log for " + tableSnapshot.getTable())); } + // Deprecated in favor of the namesake method which allows checkpoint filtering + // to be able to perform partition pruning and stats projection on the `add` entries + // from the checkpoint. + /** + * @see #getActiveFiles(TableSnapshot, MetadataEntry, ProtocolEntry, TupleDomain, Optional, ConnectorSession) + */ @Deprecated public List getActiveFiles(TableSnapshot tableSnapshot, MetadataEntry metadataEntry, ProtocolEntry protocolEntry, ConnectorSession session) { - return getActiveFiles(tableSnapshot, metadataEntry, protocolEntry, TupleDomain.all(), session); + return retrieveActiveFiles(tableSnapshot, metadataEntry, protocolEntry, TupleDomain.all(), Optional.empty(), session); + } + + public List getActiveFiles( + TableSnapshot tableSnapshot, + MetadataEntry metadataEntry, + ProtocolEntry protocolEntry, + TupleDomain partitionConstraint, + Optional> projectedColumns, + ConnectorSession session) + { + Optional> addStatsMinMaxColumnFilter = Optional.of(alwaysFalse()); + if (projectedColumns.isPresent()) { + Set baseColumnNames = projectedColumns.get().stream() + .filter(DeltaLakeColumnHandle::isBaseColumn) // Only base column stats are supported + .map(DeltaLakeColumnHandle::getColumnName) + .collect(toImmutableSet()); + addStatsMinMaxColumnFilter = Optional.of(baseColumnNames::contains); + } + return retrieveActiveFiles(tableSnapshot, metadataEntry, protocolEntry, partitionConstraint, addStatsMinMaxColumnFilter, session); } - public List getActiveFiles(TableSnapshot tableSnapshot, MetadataEntry metadataEntry, ProtocolEntry protocolEntry, TupleDomain partitionConstraint, ConnectorSession session) + private List retrieveActiveFiles( + TableSnapshot tableSnapshot, + MetadataEntry metadataEntry, + ProtocolEntry protocolEntry, + TupleDomain partitionConstraint, + Optional> addStatsMinMaxColumnFilter, + ConnectorSession session) { try { if (isCheckpointFilteringEnabled(session)) { - return loadActiveFiles(tableSnapshot, metadataEntry, protocolEntry, partitionConstraint, session).stream() + return loadActiveFiles(tableSnapshot, metadataEntry, protocolEntry, partitionConstraint, addStatsMinMaxColumnFilter, session).stream() .collect(toImmutableList()); } @@ -264,7 +299,7 @@ public List getActiveFiles(TableSnapshot tableSnapshot, MetadataEn } } - List activeFiles = loadActiveFiles(tableSnapshot, metadataEntry, protocolEntry, TupleDomain.all(), session); + List activeFiles = loadActiveFiles(tableSnapshot, metadataEntry, protocolEntry, TupleDomain.all(), Optional.of(alwaysTrue()), session); return new DeltaLakeDataFileCacheEntry(tableSnapshot.getVersion(), activeFiles); }); return cacheEntry.getActiveFiles(); @@ -279,6 +314,7 @@ private List loadActiveFiles( MetadataEntry metadataEntry, ProtocolEntry protocolEntry, TupleDomain partitionConstraint, + Optional> addStatsMinMaxColumnFilter, ConnectorSession session) { List transactions = tableSnapshot.getTransactions(); @@ -290,7 +326,8 @@ private List loadActiveFiles( fileSystemFactory.create(session), fileFormatDataSourceStats, Optional.of(new MetadataAndProtocolEntry(metadataEntry, protocolEntry)), - partitionConstraint)) { + partitionConstraint, + addStatsMinMaxColumnFilter)) { return activeAddEntries(checkpointEntries, transactions) .filter(partitionConstraint.isAll() ? addAction -> true @@ -433,7 +470,7 @@ private Stream getEntries( List transactions = tableSnapshot.getTransactions(); // Passing TupleDomain.all() because this method is used for getting all entries Stream checkpointEntries = tableSnapshot.getCheckpointTransactionLogEntries( - session, entryTypes, checkpointSchemaManager, typeManager, fileSystem, stats, Optional.empty(), TupleDomain.all()); + session, entryTypes, checkpointSchemaManager, typeManager, fileSystem, stats, Optional.empty(), TupleDomain.all(), Optional.of(alwaysTrue())); return entryMapper.apply( checkpointEntries, diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointEntryIterator.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointEntryIterator.java index 95b4ddb1fe26..9f0e4ea608e1 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointEntryIterator.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointEntryIterator.java @@ -69,6 +69,7 @@ import java.util.OptionalLong; import java.util.Queue; import java.util.Set; +import java.util.function.Predicate; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Verify.verify; @@ -159,7 +160,8 @@ public CheckpointEntryIterator( ParquetReaderOptions parquetReaderOptions, boolean checkpointRowStatisticsWritingEnabled, int domainCompactionThreshold, - TupleDomain partitionConstraint) + TupleDomain partitionConstraint, + Optional> addStatsMinMaxColumnFilter) { this.checkpointPath = checkpoint.location().toString(); this.session = requireNonNull(session, "session is null"); @@ -167,6 +169,7 @@ public CheckpointEntryIterator( this.stringMap = (MapType) typeManager.getType(TypeSignature.mapType(VARCHAR.getTypeSignature(), VARCHAR.getTypeSignature())); this.checkpointRowStatisticsWritingEnabled = checkpointRowStatisticsWritingEnabled; this.partitionConstraint = requireNonNull(partitionConstraint, "partitionConstraint is null"); + requireNonNull(addStatsMinMaxColumnFilter, "addStatsMinMaxColumnFilter is null"); checkArgument(!fields.isEmpty(), "fields is empty"); Map extractors = ImmutableMap.builder() .put(TRANSACTION, this::buildTxnEntry) @@ -182,14 +185,19 @@ public CheckpointEntryIterator( this.metadataEntry = metadataEntry.get(); checkArgument(protocolEntry.isPresent(), "Protocol entry must be provided when reading ADD entries from Checkpoint files"); this.protocolEntry = protocolEntry.get(); + checkArgument(addStatsMinMaxColumnFilter.isPresent(), "addStatsMinMaxColumnFilter must be provided when reading ADD entries from Checkpoint files"); this.schema = extractSchema(this.metadataEntry, this.protocolEntry, typeManager); this.columnsWithMinMaxStats = columnsWithStats(schema, this.metadataEntry.getOriginalPartitionColumns()); + Predicate columnStatsFilterFunction = addStatsMinMaxColumnFilter.orElseThrow(); + this.columnsWithMinMaxStats = columnsWithMinMaxStats.stream() + .filter(column -> columnStatsFilterFunction.test(column.getName())) + .collect(toImmutableList()); } ImmutableList.Builder columnsBuilder = ImmutableList.builderWithExpectedSize(fields.size()); ImmutableList.Builder> disjunctDomainsBuilder = ImmutableList.builderWithExpectedSize(fields.size()); for (EntryType field : fields) { - HiveColumnHandle column = buildColumnHandle(field, checkpointSchemaManager, this.metadataEntry, this.protocolEntry).toHiveColumnHandle(); + HiveColumnHandle column = buildColumnHandle(field, checkpointSchemaManager, this.metadataEntry, this.protocolEntry, addStatsMinMaxColumnFilter).toHiveColumnHandle(); columnsBuilder.add(column); disjunctDomainsBuilder.add(buildTupleDomainColumnHandle(field, column)); } @@ -220,11 +228,16 @@ public CheckpointEntryIterator( .collect(toImmutableList()); } - private DeltaLakeColumnHandle buildColumnHandle(EntryType entryType, CheckpointSchemaManager schemaManager, MetadataEntry metadataEntry, ProtocolEntry protocolEntry) + private DeltaLakeColumnHandle buildColumnHandle( + EntryType entryType, + CheckpointSchemaManager schemaManager, + MetadataEntry metadataEntry, + ProtocolEntry protocolEntry, + Optional> addStatsMinMaxColumnFilter) { Type type = switch (entryType) { case TRANSACTION -> schemaManager.getTxnEntryType(); - case ADD -> schemaManager.getAddEntryType(metadataEntry, protocolEntry, true, true, true); + case ADD -> schemaManager.getAddEntryType(metadataEntry, protocolEntry, addStatsMinMaxColumnFilter.orElseThrow(), true, true, true); case REMOVE -> schemaManager.getRemoveEntryType(); case METADATA -> schemaManager.getMetadataEntryType(); case PROTOCOL -> schemaManager.getProtocolEntryType(true, true); @@ -696,6 +709,12 @@ OptionalLong getCompletedPositions() return pageSource.getCompletedPositions(); } + @VisibleForTesting + long getCompletedBytes() + { + return pageSource.getCompletedBytes(); + } + @FunctionalInterface public interface CheckPointFieldExtractor { diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointSchemaManager.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointSchemaManager.java index 3fb8df2d5b69..73a9fa04ad74 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointSchemaManager.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointSchemaManager.java @@ -29,6 +29,7 @@ import java.util.List; import java.util.Optional; +import java.util.function.Predicate; import static com.google.common.collect.ImmutableList.toImmutableList; import static io.trino.plugin.deltalake.transactionlog.DeltaLakeSchemaSupport.extractPartitionColumns; @@ -114,10 +115,19 @@ public RowType getMetadataEntryType() return metadataEntryType; } - public RowType getAddEntryType(MetadataEntry metadataEntry, ProtocolEntry protocolEntry, boolean requireWriteStatsAsJson, boolean requireWriteStatsAsStruct, boolean usePartitionValuesParsed) + public RowType getAddEntryType( + MetadataEntry metadataEntry, + ProtocolEntry protocolEntry, + Predicate addStatsMinMaxColumnFilter, + boolean requireWriteStatsAsJson, + boolean requireWriteStatsAsStruct, + boolean usePartitionValuesParsed) { List allColumns = extractSchema(metadataEntry, protocolEntry, typeManager); List minMaxColumns = columnsWithStats(metadataEntry, protocolEntry, typeManager); + minMaxColumns = minMaxColumns.stream() + .filter(column -> addStatsMinMaxColumnFilter.test(column.getName())) + .collect(toImmutableList()); boolean deletionVectorEnabled = isDeletionVectorEnabled(metadataEntry, protocolEntry); ImmutableList.Builder minMaxFields = ImmutableList.builder(); diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointWriter.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointWriter.java index b8d0c1e38c74..18f522fabb57 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointWriter.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointWriter.java @@ -52,6 +52,7 @@ import java.util.Optional; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Predicates.alwaysTrue; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableMap.toImmutableMap; import static io.airlift.slice.Slices.utf8Slice; @@ -112,7 +113,13 @@ public void write(CheckpointEntries entries, TrinoOutputFile outputFile) RowType protocolEntryType = checkpointSchemaManager.getProtocolEntryType(protocolEntry.getReaderFeatures().isPresent(), protocolEntry.getWriterFeatures().isPresent()); RowType txnEntryType = checkpointSchemaManager.getTxnEntryType(); // TODO https://github.com/trinodb/trino/issues/19586 Add support for writing 'partitionValues_parsed' field - RowType addEntryType = checkpointSchemaManager.getAddEntryType(entries.getMetadataEntry(), entries.getProtocolEntry(), writeStatsAsJson, writeStatsAsStruct, false); + RowType addEntryType = checkpointSchemaManager.getAddEntryType( + entries.getMetadataEntry(), + entries.getProtocolEntry(), + alwaysTrue(), + writeStatsAsJson, + writeStatsAsStruct, + false); RowType removeEntryType = checkpointSchemaManager.getRemoveEntryType(); List columnNames = ImmutableList.of( diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointWriterManager.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointWriterManager.java index 0c797dc87c22..f70a24c22ebf 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointWriterManager.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointWriterManager.java @@ -39,6 +39,7 @@ import java.util.Optional; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Predicates.alwaysTrue; import static com.google.common.collect.ImmutableList.toImmutableList; import static io.trino.plugin.deltalake.DeltaLakeErrorCode.DELTA_LAKE_INVALID_SCHEMA; import static io.trino.plugin.deltalake.transactionlog.TransactionLogParser.LAST_CHECKPOINT_FILENAME; @@ -105,7 +106,8 @@ public void writeCheckpoint(ConnectorSession session, TableSnapshot snapshot) fileSystem, fileFormatDataSourceStats, Optional.empty(), - TupleDomain.all()) + TupleDomain.all(), + Optional.empty()) .filter(entry -> entry.getMetaData() != null || entry.getProtocol() != null) .collect(toImmutableList()); @@ -138,7 +140,8 @@ public void writeCheckpoint(ConnectorSession session, TableSnapshot snapshot) fileSystem, fileFormatDataSourceStats, Optional.of(new MetadataAndProtocolEntry(metadataLogEntry.getMetaData(), protocolLogEntry.getProtocol())), - TupleDomain.all()) + TupleDomain.all(), + Optional.of(alwaysTrue())) .forEach(checkpointBuilder::addLogEntry); } diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeBasic.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeBasic.java index 63083a1e4a14..4f3f55503043 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeBasic.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeBasic.java @@ -1084,6 +1084,80 @@ private void testPartitionValuesParsed(String resourceName) .returnsEmptyResult(); } + /** + * @see databricks133.parsed_stats_struct + */ + @Test + public void testCheckpointFilteringForParsedStatsContainingNestedRows() + throws Exception + { + String tableName = "test_parsed_stats_struct_" + randomNameSuffix(); + Path tableLocation = Files.createTempFile(tableName, null); + copyDirectoryContents(new File(Resources.getResource("databricks133/parsed_stats_struct").toURI()).toPath(), tableLocation); + + assertUpdate("CALL system.register_table('%s', '%s', '%s')".formatted(getSession().getSchema().orElseThrow(), tableName, tableLocation.toUri())); + assertThat(query("SELECT * FROM " + tableName)) + .skippingTypesCheck() + .matches(""" + VALUES + (100, 1, row(1, 'ala')), + (200, 2, row(2, 'kota')), + (300, 3, row(3, 'osla')), + (400, 4, row(4, 'zulu'))"""); + + Session session = Session.builder(getQueryRunner().getDefaultSession()) + .setCatalogSessionProperty("delta", "checkpoint_filtering_enabled", "true") + .build(); + assertThat(query(session, "SELECT id FROM " + tableName + " WHERE part BETWEEN 100 AND 300")).matches("VALUES 1, 2, 3"); + assertThat(query(session, "SELECT root.entry_two FROM " + tableName + " WHERE part BETWEEN 100 AND 300")) + .skippingTypesCheck() + .matches("VALUES 'ala', 'kota', 'osla'"); + // show stats with predicate + assertThat(query(session, "SHOW STATS FOR (SELECT id FROM " + tableName + " WHERE part = 100)")) + .skippingTypesCheck() + .matches(""" + VALUES + ('id', NULL, NULL, DOUBLE '0.0' , NULL, '1', '1'), + (NULL, NULL, NULL, NULL, DOUBLE '1.0', NULL, NULL)"""); + } + + /** + * @see databricks133.parsed_stats_case_sensitive + */ + @Test + public void testCheckpointFilteringForParsedStatsWithCaseSensitiveColumnNames() + throws Exception + { + String tableName = "test_parsed_stats_case_sensitive_" + randomNameSuffix(); + Path tableLocation = Files.createTempFile(tableName, null); + copyDirectoryContents(new File(Resources.getResource("databricks133/parsed_stats_case_sensitive").toURI()).toPath(), tableLocation); + + assertUpdate("CALL system.register_table('%s', '%s', '%s')".formatted(getSession().getSchema().orElseThrow(), tableName, tableLocation.toUri())); + assertThat(query("SELECT * FROM " + tableName)) + .skippingTypesCheck() + .matches(""" + VALUES + (100, 1, 'ala'), + (200, 2, 'kota'), + (300, 3, 'osla'), + (400, 4, 'zulu')"""); + + Session session = Session.builder(getQueryRunner().getDefaultSession()) + .setCatalogSessionProperty("delta", "checkpoint_filtering_enabled", "true") + .build(); + assertThat(query(session, "SELECT a_NuMbEr FROM " + tableName + " WHERE part BETWEEN 100 AND 300")).matches("VALUES 1, 2, 3"); + assertThat(query(session, "SELECT a_StRiNg FROM " + tableName + " WHERE part BETWEEN 100 AND 300")) + .skippingTypesCheck() + .matches("VALUES 'ala', 'kota', 'osla'"); + // show stats with predicate + assertThat(query(session, "SHOW STATS FOR (SELECT a_NuMbEr FROM " + tableName + " WHERE part BETWEEN 100 AND 300)")) + .skippingTypesCheck() + .matches(""" + VALUES + ('a_NuMbEr', NULL, NULL, DOUBLE '0.0' , NULL, '1', '3'), + (NULL, NULL, NULL, NULL, DOUBLE '3.0', NULL, NULL)"""); + } + /** * @see deltalake.partition_values_parsed_all_types */ diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSplitManager.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSplitManager.java index 5c9f3dd08da9..20ba4c7e2343 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSplitManager.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSplitManager.java @@ -56,6 +56,7 @@ import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; @@ -185,7 +186,13 @@ private DeltaLakeSplitManager setupSplitManager(List addFileEntrie new ParquetReaderConfig()) { @Override - public List getActiveFiles(TableSnapshot tableSnapshot, MetadataEntry metadataEntry, ProtocolEntry protocolEntry, TupleDomain partitionConstraint, ConnectorSession session) + public List getActiveFiles( + TableSnapshot tableSnapshot, + MetadataEntry metadataEntry, + ProtocolEntry protocolEntry, + TupleDomain partitionConstraint, + Optional> projectedColumns, + ConnectorSession session) { return addFileEntries; } diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/TestTableSnapshot.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/TestTableSnapshot.java index 95c1869a3d37..dbc19f558368 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/TestTableSnapshot.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/TestTableSnapshot.java @@ -43,6 +43,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; +import static com.google.common.base.Predicates.alwaysTrue; import static com.google.common.collect.ImmutableList.toImmutableList; import static io.trino.filesystem.TrackingFileSystemFactory.OperationType.INPUT_FILE_NEW_STREAM; import static io.trino.plugin.deltalake.transactionlog.TableSnapshot.MetadataAndProtocolEntry; @@ -142,7 +143,15 @@ public void readsCheckpointFile() ProtocolEntry protocolEntry = transactionLogAccess.getProtocolEntry(SESSION, tableSnapshot); tableSnapshot.setCachedMetadata(Optional.of(metadataEntry)); try (Stream stream = tableSnapshot.getCheckpointTransactionLogEntries( - SESSION, ImmutableSet.of(ADD), checkpointSchemaManager, TESTING_TYPE_MANAGER, trackingFileSystem, new FileFormatDataSourceStats(), Optional.of(new MetadataAndProtocolEntry(metadataEntry, protocolEntry)), TupleDomain.all())) { + SESSION, + ImmutableSet.of(ADD), + checkpointSchemaManager, + TESTING_TYPE_MANAGER, + trackingFileSystem, + new FileFormatDataSourceStats(), + Optional.of(new MetadataAndProtocolEntry(metadataEntry, protocolEntry)), + TupleDomain.all(), + Optional.of(alwaysTrue()))) { List entries = stream.collect(toImmutableList()); assertThat(entries).hasSize(9); @@ -184,7 +193,15 @@ public void readsCheckpointFile() // lets read two entry types in one call; add and protocol try (Stream stream = tableSnapshot.getCheckpointTransactionLogEntries( - SESSION, ImmutableSet.of(ADD, PROTOCOL), checkpointSchemaManager, TESTING_TYPE_MANAGER, trackingFileSystem, new FileFormatDataSourceStats(), Optional.of(new MetadataAndProtocolEntry(metadataEntry, protocolEntry)), TupleDomain.all())) { + SESSION, + ImmutableSet.of(ADD, PROTOCOL), + checkpointSchemaManager, + TESTING_TYPE_MANAGER, + trackingFileSystem, + new FileFormatDataSourceStats(), + Optional.of(new MetadataAndProtocolEntry(metadataEntry, protocolEntry)), + TupleDomain.all(), + Optional.of(alwaysTrue()))) { List entries = stream.collect(toImmutableList()); assertThat(entries).hasSize(10); diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointEntryIterator.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointEntryIterator.java index 245b602ad40b..3857f0e72d7d 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointEntryIterator.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointEntryIterator.java @@ -31,6 +31,7 @@ import io.trino.plugin.deltalake.transactionlog.MetadataEntry; import io.trino.plugin.deltalake.transactionlog.ProtocolEntry; import io.trino.plugin.deltalake.transactionlog.RemoveFileEntry; +import io.trino.plugin.deltalake.transactionlog.statistics.DeltaLakeParquetFileStatistics; import io.trino.plugin.hive.FileFormatDataSourceStats; import io.trino.plugin.hive.parquet.ParquetReaderConfig; import io.trino.spi.predicate.TupleDomain; @@ -53,14 +54,20 @@ import java.util.Objects; import java.util.Optional; import java.util.OptionalInt; +import java.util.Random; import java.util.Set; import java.util.UUID; +import java.util.function.Predicate; +import java.util.stream.Collectors; import java.util.stream.IntStream; +import static com.google.common.base.Predicates.alwaysTrue; +import static com.google.common.collect.ImmutableMap.toImmutableMap; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.io.Resources.getResource; import static com.google.common.math.LongMath.divide; import static io.airlift.slice.Slices.utf8Slice; +import static io.airlift.units.DataSize.Unit.KILOBYTE; import static io.trino.plugin.deltalake.DeltaLakeColumnType.REGULAR; import static io.trino.plugin.deltalake.DeltaTestingConnectorSession.SESSION; import static io.trino.plugin.deltalake.transactionlog.checkpoint.CheckpointEntryIterator.EntryType.ADD; @@ -124,7 +131,7 @@ public void testReadNoEntries() throws Exception { URI checkpointUri = getResource(TEST_CHECKPOINT).toURI(); - assertThatThrownBy(() -> createCheckpointEntryIterator(checkpointUri, ImmutableSet.of(), Optional.empty(), Optional.empty(), TupleDomain.all())) + assertThatThrownBy(() -> createCheckpointEntryIterator(checkpointUri, ImmutableSet.of(), Optional.empty(), Optional.empty(), TupleDomain.all(), Optional.empty())) .isInstanceOf(IllegalArgumentException.class) .hasMessage("fields is empty"); } @@ -168,7 +175,7 @@ public void testReadProtocolEntries() throws Exception { URI checkpointUri = getResource(TEST_CHECKPOINT).toURI(); - CheckpointEntryIterator checkpointEntryIterator = createCheckpointEntryIterator(checkpointUri, ImmutableSet.of(PROTOCOL), Optional.empty(), Optional.empty(), TupleDomain.all()); + CheckpointEntryIterator checkpointEntryIterator = createCheckpointEntryIterator(checkpointUri, ImmutableSet.of(PROTOCOL), Optional.empty(), Optional.empty(), TupleDomain.all(), Optional.empty()); List entries = ImmutableList.copyOf(checkpointEntryIterator); assertThat(entries).hasSize(1); @@ -186,7 +193,13 @@ public void testReadMetadataAndProtocolEntry() throws Exception { URI checkpointUri = getResource(TEST_CHECKPOINT).toURI(); - CheckpointEntryIterator checkpointEntryIterator = createCheckpointEntryIterator(checkpointUri, ImmutableSet.of(METADATA, PROTOCOL), Optional.empty(), Optional.empty(), TupleDomain.all()); + CheckpointEntryIterator checkpointEntryIterator = createCheckpointEntryIterator( + checkpointUri, + ImmutableSet.of(METADATA, PROTOCOL), + Optional.empty(), + Optional.empty(), + TupleDomain.all(), + Optional.empty()); List entries = ImmutableList.copyOf(checkpointEntryIterator); assertThat(entries).hasSize(2); @@ -229,7 +242,13 @@ public void testReadAddEntries() throws Exception { URI checkpointUri = getResource(TEST_CHECKPOINT).toURI(); - CheckpointEntryIterator checkpointEntryIterator = createCheckpointEntryIterator(checkpointUri, ImmutableSet.of(ADD), Optional.of(readMetadataEntry(checkpointUri)), Optional.of(readProtocolEntry(checkpointUri)), TupleDomain.all()); + CheckpointEntryIterator checkpointEntryIterator = createCheckpointEntryIterator( + checkpointUri, + ImmutableSet.of(ADD), + Optional.of(readMetadataEntry(checkpointUri)), + Optional.of(readProtocolEntry(checkpointUri)), + TupleDomain.all(), + Optional.of(alwaysTrue())); List entries = ImmutableList.copyOf(checkpointEntryIterator); assertThat(entries).hasSize(9); @@ -300,7 +319,8 @@ public void testReadAddEntriesPartitionPruning() ImmutableSet.of(ADD), Optional.of(readMetadataEntry(checkpointUri)), Optional.of(readProtocolEntry(checkpointUri)), - TupleDomain.withColumnDomains(ImmutableMap.of(intPartField, singleValue(BIGINT, 10L), stringPartField, singleValue(VARCHAR, utf8Slice("part1"))))); + TupleDomain.withColumnDomains(ImmutableMap.of(intPartField, singleValue(BIGINT, 10L), stringPartField, singleValue(VARCHAR, utf8Slice("part1")))), + Optional.of(alwaysTrue())); List partitionsEntries = ImmutableList.copyOf(partitionsEntryIterator); assertThat(partitionsEntryIterator.getCompletedPositions().orElseThrow()).isEqualTo(5); @@ -315,7 +335,8 @@ public void testReadAddEntriesPartitionPruning() ImmutableSet.of(ADD), Optional.of(readMetadataEntry(checkpointUri)), Optional.of(readProtocolEntry(checkpointUri)), - TupleDomain.withColumnDomains(ImmutableMap.of(intPartField, singleValue(BIGINT, 10L)))); + TupleDomain.withColumnDomains(ImmutableMap.of(intPartField, singleValue(BIGINT, 10L))), + Optional.of(alwaysTrue())); List partitionEntries = ImmutableList.copyOf(partitionEntryIterator); assertThat(partitionEntryIterator.getCompletedPositions().orElseThrow()).isEqualTo(5); @@ -332,7 +353,8 @@ public void testReadAddEntriesPartitionPruning() Optional.of(readProtocolEntry(checkpointUri)), TupleDomain.withColumnDomains(ImmutableMap.of( intPartField, singleValue(BIGINT, 10L), - stringPartField, singleValue(VARCHAR, utf8Slice("unmatched partition condition"))))); + stringPartField, singleValue(VARCHAR, utf8Slice("unmatched partition condition")))), + Optional.of(alwaysTrue())); assertThat(ImmutableList.copyOf(emptyIterator)).isEmpty(); // Verify IS NULL condition @@ -343,7 +365,8 @@ intPartField, singleValue(BIGINT, 10L), Optional.of(readProtocolEntry(checkpointUri)), TupleDomain.withColumnDomains(ImmutableMap.of( intPartField, onlyNull(BIGINT), - stringPartField, onlyNull(VARCHAR)))); + stringPartField, onlyNull(VARCHAR))), + Optional.of(alwaysTrue())); assertThat(ImmutableList.copyOf(isNullIterator)) .hasSize(1) .extracting(entry -> entry.getAdd().getPath()) @@ -357,7 +380,8 @@ intPartField, onlyNull(BIGINT), Optional.of(readProtocolEntry(checkpointUri)), TupleDomain.withColumnDomains(ImmutableMap.of( intPartField, notNull(BIGINT), - stringPartField, notNull(VARCHAR)))); + stringPartField, notNull(VARCHAR))), + Optional.of(alwaysTrue())); assertThat(ImmutableList.copyOf(isNotNullIterator)) .hasSize(2) .extracting(entry -> entry.getAdd().getPath()) @@ -402,13 +426,133 @@ private void assertPartitionValuesParsedCondition(URI checkpointUri, String colu ImmutableSet.of(ADD), Optional.of(readMetadataEntry(checkpointUri)), Optional.of(readProtocolEntry(checkpointUri)), - TupleDomain.withColumnDomains(ImmutableMap.of(intPartField, singleValue(type, value)))); + TupleDomain.withColumnDomains(ImmutableMap.of(intPartField, singleValue(type, value))), + Optional.of(alwaysTrue())); List partitionEntries = ImmutableList.copyOf(partitionEntryIterator); assertThat(partitionEntryIterator.getCompletedPositions().orElseThrow()).isEqualTo(5); assertThat(partitionEntries).hasSize(1); } + @Test + public void testReadAddEntriesStatsProjection() + throws Exception + { + int countIntegerColumns = 20; + int countStringColumns = 20; + MetadataEntry metadataEntry = new MetadataEntry( + "metadataId", + "metadataName", + "metadataDescription", + new MetadataEntry.Format( + "metadataFormatProvider", + ImmutableMap.of()), + "{\"type\":\"struct\",\"fields\":" + + "[" + + IntStream.rangeClosed(1, countIntegerColumns) + .boxed() + .map("{\"name\":\"intcol%s\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}"::formatted) + .collect(Collectors.joining(",", "", ",")) + + IntStream.rangeClosed(1, countStringColumns) + .boxed() + .map("{\"name\":\"stringcol%s\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}"::formatted) + .collect(Collectors.joining(",", "", ",")) + + "{\"name\":\"part_key\",\"type\":\"timestamp\",\"nullable\":true,\"metadata\":{}}]}", + ImmutableList.of("part_key"), + ImmutableMap.of("delta.checkpoint.writeStatsAsJson", "false", "delta.checkpoint.writeStatsAsStruct", "true"), + 1000); + ProtocolEntry protocolEntry = new ProtocolEntry(10, 20, Optional.empty(), Optional.empty()); + + int countAddEntries = 30; + Set addFileEntries = IntStream.rangeClosed(1, countAddEntries).mapToObj(fileIndex -> new AddFileEntry( + "addFilePathParquetStats" + fileIndex, + ImmutableMap.of(), + 1000, + 1001, + true, + Optional.empty(), + Optional.of(createDeltaLakeParquetFileStatistics(countIntegerColumns, countStringColumns)), + ImmutableMap.of(), + Optional.empty())) + .collect(toImmutableSet()); + + CheckpointEntries entries = new CheckpointEntries( + metadataEntry, + protocolEntry, + ImmutableSet.of(), + addFileEntries, + ImmutableSet.of()); + + CheckpointWriter writer = new CheckpointWriter( + TESTING_TYPE_MANAGER, + checkpointSchemaManager, + "test", + ParquetWriterOptions.builder().build()); + + File targetFile = File.createTempFile("testAddStatsProjection-", ".checkpoint.parquet"); + targetFile.deleteOnExit(); + + String targetPath = "file://" + targetFile.getAbsolutePath(); + targetFile.delete(); // file must not exist when writer is called + writer.write(entries, createOutputFile(targetPath)); + + MetadataEntry readMetadataEntry = readMetadataEntry(URI.create(targetPath)); + ProtocolEntry readProtocolEntry = readProtocolEntry(URI.create(targetPath)); + CheckpointEntryIterator checkpointEntryWithNameColumnStatsIterator = createCheckpointEntryIterator( + URI.create(targetPath), + ImmutableSet.of(ADD), + Optional.of(readMetadataEntry), + Optional.of(readProtocolEntry), + TupleDomain.all(), + Optional.of(columnName -> ImmutableList.of("intcol1", "stringcol1").contains(columnName))); + List checkpointEntryWithNameColumnStatsEntries = ImmutableList.copyOf(checkpointEntryWithNameColumnStatsIterator); + assertThat(checkpointEntryWithNameColumnStatsEntries).hasSize(countAddEntries); + CheckpointEntryIterator checkpointEntryWithAllColumnStatsIterator = createCheckpointEntryIterator( + URI.create(targetPath), + ImmutableSet.of(ADD), + Optional.of(readMetadataEntry), + Optional.of(readProtocolEntry), + TupleDomain.all(), + Optional.of(alwaysTrue())); + List checkpointEntryWithAllStatsEntries = ImmutableList.copyOf(checkpointEntryWithAllColumnStatsIterator); + assertThat(checkpointEntryWithAllStatsEntries).hasSize(countAddEntries); + assertThat(checkpointEntryWithNameColumnStatsIterator.getCompletedBytes()).isLessThan(checkpointEntryWithAllColumnStatsIterator.getCompletedBytes() * 9 / 10); + } + + private static DeltaLakeParquetFileStatistics createDeltaLakeParquetFileStatistics(int countIntegerColumns, int countStringColumns) + { + Random random = new Random(); + Map minValues = ImmutableMap.builder() + .putAll(IntStream.rangeClosed(1, countIntegerColumns) + .boxed() + .collect(toImmutableMap("intcol%s"::formatted, columnIndex -> random.nextLong(0, 1000)))) + .putAll(IntStream.rangeClosed(1, countStringColumns) + .boxed() + .collect(toImmutableMap("stringcol%s"::formatted, columnIndex -> "A".repeat(random.nextInt(0, 10)) + UUID.randomUUID()))) + .buildOrThrow(); + Map maxValues = ImmutableMap.builder() + .putAll(IntStream.rangeClosed(1, countIntegerColumns) + .boxed() + .collect(toImmutableMap("intcol%s"::formatted, columnIndex -> 1000L + random.nextLong(0, 1000)))) + .putAll(IntStream.rangeClosed(1, countStringColumns) + .boxed() + .collect(toImmutableMap("stringcol%s"::formatted, columnIndex -> "Z".repeat(random.nextInt(0, 10)) + UUID.randomUUID()))) + .buildOrThrow(); + Map nullCount = ImmutableMap.builder() + .putAll(IntStream.rangeClosed(1, countIntegerColumns) + .boxed() + .collect(toImmutableMap("intcol%s"::formatted, columnIndex -> random.nextLong(0, 1000)))) + .putAll(IntStream.rangeClosed(1, countStringColumns) + .boxed() + .collect(toImmutableMap("stringcol%s"::formatted, columnIndex -> random.nextLong(0, 1000)))) + .buildOrThrow(); + return new DeltaLakeParquetFileStatistics( + Optional.of(1000L), + Optional.of(minValues), + Optional.of(maxValues), + Optional.of(nullCount)); + } + @Test public void testReadAllEntries() throws Exception @@ -420,7 +564,8 @@ public void testReadAllEntries() ImmutableSet.of(METADATA, PROTOCOL, TRANSACTION, ADD, REMOVE, COMMIT), Optional.of(readMetadataEntry(checkpointUri)), Optional.of(readProtocolEntry(checkpointUri)), - TupleDomain.all()); + TupleDomain.all(), + Optional.of(alwaysTrue())); List entries = ImmutableList.copyOf(checkpointEntryIterator); assertThat(entries).hasSize(17); @@ -542,17 +687,18 @@ public void testSkipRemoveEntries() writer.write(entries, createOutputFile(targetPath)); CheckpointEntryIterator metadataAndProtocolEntryIterator = - createCheckpointEntryIterator(URI.create(targetPath), ImmutableSet.of(METADATA, PROTOCOL), Optional.empty(), Optional.empty(), TupleDomain.all()); + createCheckpointEntryIterator(URI.create(targetPath), ImmutableSet.of(METADATA, PROTOCOL), Optional.empty(), Optional.empty(), TupleDomain.all(), Optional.empty()); CheckpointEntryIterator addEntryIterator = createCheckpointEntryIterator( URI.create(targetPath), ImmutableSet.of(ADD), Optional.of(metadataEntry), Optional.of(protocolEntry), - TupleDomain.all()); + TupleDomain.all(), + Optional.of(alwaysTrue())); CheckpointEntryIterator removeEntryIterator = - createCheckpointEntryIterator(URI.create(targetPath), ImmutableSet.of(REMOVE), Optional.empty(), Optional.empty(), TupleDomain.all()); + createCheckpointEntryIterator(URI.create(targetPath), ImmutableSet.of(REMOVE), Optional.empty(), Optional.empty(), TupleDomain.all(), Optional.empty()); CheckpointEntryIterator txnEntryIterator = - createCheckpointEntryIterator(URI.create(targetPath), ImmutableSet.of(TRANSACTION), Optional.empty(), Optional.empty(), TupleDomain.all()); + createCheckpointEntryIterator(URI.create(targetPath), ImmutableSet.of(TRANSACTION), Optional.empty(), Optional.empty(), TupleDomain.all(), Optional.empty()); assertThat(Iterators.size(metadataAndProtocolEntryIterator)).isEqualTo(2); assertThat(Iterators.size(addEntryIterator)).isEqualTo(1); @@ -568,14 +714,26 @@ public void testSkipRemoveEntries() private MetadataEntry readMetadataEntry(URI checkpointUri) throws IOException { - CheckpointEntryIterator checkpointEntryIterator = createCheckpointEntryIterator(checkpointUri, ImmutableSet.of(METADATA), Optional.empty(), Optional.empty(), TupleDomain.all()); + CheckpointEntryIterator checkpointEntryIterator = createCheckpointEntryIterator( + checkpointUri, + ImmutableSet.of(METADATA), + Optional.empty(), + Optional.empty(), + TupleDomain.all(), + Optional.empty()); return Iterators.getOnlyElement(checkpointEntryIterator).getMetaData(); } private ProtocolEntry readProtocolEntry(URI checkpointUri) throws IOException { - CheckpointEntryIterator checkpointEntryIterator = createCheckpointEntryIterator(checkpointUri, ImmutableSet.of(PROTOCOL), Optional.empty(), Optional.empty(), TupleDomain.all()); + CheckpointEntryIterator checkpointEntryIterator = createCheckpointEntryIterator( + checkpointUri, + ImmutableSet.of(PROTOCOL), + Optional.empty(), + Optional.empty(), + TupleDomain.all(), + Optional.empty()); return Iterators.getOnlyElement(checkpointEntryIterator).getProtocol(); } @@ -584,7 +742,8 @@ private CheckpointEntryIterator createCheckpointEntryIterator( Set entryTypes, Optional metadataEntry, Optional protocolEntry, - TupleDomain partitionConstraint) + TupleDomain partitionConstraint, + Optional> addStatsMinMaxColumnFilter) throws IOException { TrinoFileSystem fileSystem = new HdfsFileSystemFactory(HDFS_ENVIRONMENT, HDFS_FILE_SYSTEM_STATS).create(SESSION); @@ -600,10 +759,14 @@ private CheckpointEntryIterator createCheckpointEntryIterator( metadataEntry, protocolEntry, new FileFormatDataSourceStats(), - new ParquetReaderConfig().toParquetReaderOptions(), + new ParquetReaderConfig() + .setMaxBufferSize(DataSize.ofBytes(500)) + .setSmallFileThreshold(DataSize.of(1, KILOBYTE)) + .toParquetReaderOptions(), true, new DeltaLakeConfig().getDomainCompactionThreshold(), - partitionConstraint); + partitionConstraint, + addStatsMinMaxColumnFilter); } private static TrinoOutputFile createOutputFile(String path) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointWriter.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointWriter.java index 31aec13a79c3..4a2354fb137b 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointWriter.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointWriter.java @@ -50,6 +50,7 @@ import java.util.Map; import java.util.Optional; +import static com.google.common.base.Predicates.alwaysTrue; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.collect.Iterables.getOnlyElement; @@ -481,7 +482,8 @@ private CheckpointEntries readCheckpoint(String checkpointPath, MetadataEntry me new ParquetReaderConfig().toParquetReaderOptions(), rowStatisticsEnabled, new DeltaLakeConfig().getDomainCompactionThreshold(), - TupleDomain.all()); + TupleDomain.all(), + Optional.of(alwaysTrue())); CheckpointBuilder checkpointBuilder = new CheckpointBuilder(); while (checkpointEntryIterator.hasNext()) { diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/statistics/TestDeltaLakeFileStatistics.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/statistics/TestDeltaLakeFileStatistics.java index 5e785a542f4c..8159b9b9fce2 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/statistics/TestDeltaLakeFileStatistics.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/statistics/TestDeltaLakeFileStatistics.java @@ -46,6 +46,7 @@ import java.util.Optional; import java.util.OptionalInt; +import static com.google.common.base.Predicates.alwaysTrue; import static com.google.common.collect.Iterators.getOnlyElement; import static io.airlift.slice.Slices.utf8Slice; import static io.trino.plugin.deltalake.DeltaLakeColumnType.REGULAR; @@ -106,7 +107,8 @@ public void testParseParquetStatistics() new ParquetReaderConfig().toParquetReaderOptions(), true, new DeltaLakeConfig().getDomainCompactionThreshold(), - TupleDomain.all()); + TupleDomain.all(), + Optional.empty()); MetadataEntry metadataEntry = getOnlyElement(metadataEntryIterator).getMetaData(); CheckpointEntryIterator protocolEntryIterator = new CheckpointEntryIterator( checkpointFile, @@ -121,7 +123,8 @@ public void testParseParquetStatistics() new ParquetReaderConfig().toParquetReaderOptions(), true, new DeltaLakeConfig().getDomainCompactionThreshold(), - TupleDomain.all()); + TupleDomain.all(), + Optional.empty()); ProtocolEntry protocolEntry = getOnlyElement(protocolEntryIterator).getProtocol(); CheckpointEntryIterator checkpointEntryIterator = new CheckpointEntryIterator( @@ -137,7 +140,8 @@ public void testParseParquetStatistics() new ParquetReaderConfig().toParquetReaderOptions(), true, new DeltaLakeConfig().getDomainCompactionThreshold(), - TupleDomain.all()); + TupleDomain.all(), + Optional.of(alwaysTrue())); DeltaLakeTransactionLogEntry matchingAddFileEntry = null; while (checkpointEntryIterator.hasNext()) { DeltaLakeTransactionLogEntry entry = checkpointEntryIterator.next(); diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/README.txt b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/README.txt new file mode 100644 index 000000000000..c87762ee3782 --- /dev/null +++ b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/README.txt @@ -0,0 +1,13 @@ +Data generated using Databricks 13.3 LTS + +``` +CREATE TABLE default.parsed_stats_case_sensitive (part INT, a_NuMbEr INT, a_StRiNg STRING) +USING DELTA +PARTITIONED BY (part) +LOCATION 's3://.../parsed_stats_case_sensitive' +TBLPROPERTIES (delta.checkpointInterval = 2); + +insert into default.parsed_stats_case_sensitive VALUES (100, 1,'ala'), (200, 2, 'kota'); +insert into default.parsed_stats_case_sensitive VALUES (300, 3, 'osla'); +insert into default.parsed_stats_case_sensitive VALUES (400, 4, 'zulu'); +``` diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/_delta_log/00000000000000000000.json b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/_delta_log/00000000000000000000.json new file mode 100644 index 000000000000..b39a26449787 --- /dev/null +++ b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/_delta_log/00000000000000000000.json @@ -0,0 +1,3 @@ +{"commitInfo":{"timestamp":1700647167062,"userId":"615774135840106","userName":"marius.grama@starburstdata.com","operation":"CREATE TABLE","operationParameters":{"partitionBy":"[\"part\"]","description":null,"isManaged":"false","properties":"{\"delta.checkpointInterval\":\"2\"}","statsOnLoad":false},"notebook":{"notebookId":"2900318529393697"},"clusterId":"0905-151610-v55wl6f5","isolationLevel":"WriteSerializable","isBlindAppend":true,"operationMetrics":{},"tags":{"restoresDeletedRows":"false"},"engineInfo":"Databricks-Runtime/13.3.x-scala2.12","txnId":"756c1d88-796f-4bb1-8076-3128c860cc51"}} +{"metaData":{"id":"480a0934-0451-4c37-b4bf-ce84c878e145","format":{"provider":"parquet","options":{}},"schemaString":"{\"type\":\"struct\",\"fields\":[{\"name\":\"part\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"a_NuMbEr\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"a_StRiNg\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]}","partitionColumns":["part"],"configuration":{"delta.checkpointInterval":"2"},"createdTime":1700647166713}} +{"protocol":{"minReaderVersion":1,"minWriterVersion":2}} diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/_delta_log/00000000000000000001.json b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/_delta_log/00000000000000000001.json new file mode 100644 index 000000000000..9417e7c1643e --- /dev/null +++ b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/_delta_log/00000000000000000001.json @@ -0,0 +1,3 @@ +{"commitInfo":{"timestamp":1700647172873,"userId":"615774135840106","userName":"marius.grama@starburstdata.com","operation":"WRITE","operationParameters":{"mode":"Append","statsOnLoad":false,"partitionBy":"[]"},"notebook":{"notebookId":"2900318529393697"},"clusterId":"0905-151610-v55wl6f5","readVersion":0,"isolationLevel":"WriteSerializable","isBlindAppend":true,"operationMetrics":{"numFiles":"2","numOutputRows":"2","numOutputBytes":"1671"},"tags":{"restoresDeletedRows":"false"},"engineInfo":"Databricks-Runtime/13.3.x-scala2.12","txnId":"24dc16da-5d97-48e8-a9dd-e5700cf49f7b"}} +{"add":{"path":"part=100/part-00000-c707739f-45c0-4722-9837-4b11599ea66d.c000.snappy.parquet","partitionValues":{"part":"100"},"size":832,"modificationTime":1700647173000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"a_NuMbEr\":1,\"a_StRiNg\":\"ala\"},\"maxValues\":{\"a_NuMbEr\":1,\"a_StRiNg\":\"ala\"},\"nullCount\":{\"a_NuMbEr\":0,\"a_StRiNg\":0}}","tags":{"INSERTION_TIME":"1700647173000000","MIN_INSERTION_TIME":"1700647173000000","MAX_INSERTION_TIME":"1700647173000000","OPTIMIZE_TARGET_SIZE":"268435456"}}} +{"add":{"path":"part=200/part-00000-9cfa2c9c-efae-4e0a-bf25-eba86bfdce11.c000.snappy.parquet","partitionValues":{"part":"200"},"size":839,"modificationTime":1700647173000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"a_NuMbEr\":2,\"a_StRiNg\":\"kota\"},\"maxValues\":{\"a_NuMbEr\":2,\"a_StRiNg\":\"kota\"},\"nullCount\":{\"a_NuMbEr\":0,\"a_StRiNg\":0}}","tags":{"INSERTION_TIME":"1700647173000001","MIN_INSERTION_TIME":"1700647173000001","MAX_INSERTION_TIME":"1700647173000001","OPTIMIZE_TARGET_SIZE":"268435456"}}} diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/_delta_log/00000000000000000002.checkpoint.parquet b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/_delta_log/00000000000000000002.checkpoint.parquet new file mode 100644 index 0000000000000000000000000000000000000000..4091b2c9990462f7980adffb75b9c85e923a239d GIT binary patch literal 20131 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 literal 0 HcmV?d00001 diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/_delta_log/00000000000000000002.json b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/_delta_log/00000000000000000002.json new file mode 100644 index 000000000000..e8ea7c46663b --- /dev/null +++ b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/_delta_log/00000000000000000002.json @@ -0,0 +1,2 @@ +{"commitInfo":{"timestamp":1700647175783,"userId":"615774135840106","userName":"marius.grama@starburstdata.com","operation":"WRITE","operationParameters":{"mode":"Append","statsOnLoad":false,"partitionBy":"[]"},"notebook":{"notebookId":"2900318529393697"},"clusterId":"0905-151610-v55wl6f5","readVersion":1,"isolationLevel":"WriteSerializable","isBlindAppend":true,"operationMetrics":{"numFiles":"1","numOutputRows":"1","numOutputBytes":"839"},"tags":{"restoresDeletedRows":"false"},"engineInfo":"Databricks-Runtime/13.3.x-scala2.12","txnId":"81f1dc19-e075-4b65-8a4c-78e592a200d1"}} +{"add":{"path":"part=300/part-00000-7b5e9086-abfc-4a58-8c2c-d775a1203dd3.c000.snappy.parquet","partitionValues":{"part":"300"},"size":839,"modificationTime":1700647176000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"a_NuMbEr\":3,\"a_StRiNg\":\"osla\"},\"maxValues\":{\"a_NuMbEr\":3,\"a_StRiNg\":\"osla\"},\"nullCount\":{\"a_NuMbEr\":0,\"a_StRiNg\":0}}","tags":{"INSERTION_TIME":"1700647176000000","MIN_INSERTION_TIME":"1700647176000000","MAX_INSERTION_TIME":"1700647176000000","OPTIMIZE_TARGET_SIZE":"268435456"}}} diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/_delta_log/00000000000000000003.json b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/_delta_log/00000000000000000003.json new file mode 100644 index 000000000000..8253647a5984 --- /dev/null +++ b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/_delta_log/00000000000000000003.json @@ -0,0 +1,2 @@ +{"commitInfo":{"timestamp":1700647181681,"userId":"615774135840106","userName":"marius.grama@starburstdata.com","operation":"WRITE","operationParameters":{"mode":"Append","statsOnLoad":false,"partitionBy":"[]"},"notebook":{"notebookId":"2900318529393697"},"clusterId":"0905-151610-v55wl6f5","readVersion":2,"isolationLevel":"WriteSerializable","isBlindAppend":true,"operationMetrics":{"numFiles":"1","numOutputRows":"1","numOutputBytes":"839"},"tags":{"restoresDeletedRows":"false"},"engineInfo":"Databricks-Runtime/13.3.x-scala2.12","txnId":"4400b746-d6d0-44c4-badf-803a8e3e815e"}} +{"add":{"path":"part=400/part-00000-e90e48ef-2b52-4d32-be2f-9051e90920ce.c000.snappy.parquet","partitionValues":{"part":"400"},"size":839,"modificationTime":1700647182000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"a_NuMbEr\":4,\"a_StRiNg\":\"zulu\"},\"maxValues\":{\"a_NuMbEr\":4,\"a_StRiNg\":\"zulu\"},\"nullCount\":{\"a_NuMbEr\":0,\"a_StRiNg\":0}}","tags":{"INSERTION_TIME":"1700647182000000","MIN_INSERTION_TIME":"1700647182000000","MAX_INSERTION_TIME":"1700647182000000","OPTIMIZE_TARGET_SIZE":"268435456"}}} diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/_delta_log/_last_checkpoint b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/_delta_log/_last_checkpoint new file mode 100644 index 000000000000..04273e03e014 --- /dev/null +++ b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/_delta_log/_last_checkpoint @@ -0,0 +1 @@ +{"version":2,"size":5,"sizeInBytes":20131,"numOfAddFiles":3,"checkpointSchema":{"type":"struct","fields":[{"name":"txn","type":{"type":"struct","fields":[{"name":"appId","type":"string","nullable":true,"metadata":{}},{"name":"version","type":"long","nullable":true,"metadata":{}},{"name":"lastUpdated","type":"long","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"add","type":{"type":"struct","fields":[{"name":"path","type":"string","nullable":true,"metadata":{}},{"name":"partitionValues","type":{"type":"map","keyType":"string","valueType":"string","valueContainsNull":true},"nullable":true,"metadata":{}},{"name":"size","type":"long","nullable":true,"metadata":{}},{"name":"modificationTime","type":"long","nullable":true,"metadata":{}},{"name":"dataChange","type":"boolean","nullable":true,"metadata":{}},{"name":"tags","type":{"type":"map","keyType":"string","valueType":"string","valueContainsNull":true},"nullable":true,"metadata":{}},{"name":"deletionVector","type":{"type":"struct","fields":[{"name":"storageType","type":"string","nullable":true,"metadata":{}},{"name":"pathOrInlineDv","type":"string","nullable":true,"metadata":{}},{"name":"offset","type":"integer","nullable":true,"metadata":{}},{"name":"sizeInBytes","type":"integer","nullable":true,"metadata":{}},{"name":"cardinality","type":"long","nullable":true,"metadata":{}},{"name":"maxRowIndex","type":"long","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"baseRowId","type":"long","nullable":true,"metadata":{}},{"name":"defaultRowCommitVersion","type":"long","nullable":true,"metadata":{}},{"name":"stats","type":"string","nullable":true,"metadata":{}},{"name":"partitionValues_parsed","type":{"type":"struct","fields":[{"name":"part","type":"integer","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"stats_parsed","type":{"type":"struct","fields":[{"name":"numRecords","type":"long","nullable":true,"metadata":{}},{"name":"minValues","type":{"type":"struct","fields":[{"name":"a_NuMbEr","type":"integer","nullable":true,"metadata":{}},{"name":"a_StRiNg","type":"string","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"maxValues","type":{"type":"struct","fields":[{"name":"a_NuMbEr","type":"integer","nullable":true,"metadata":{}},{"name":"a_StRiNg","type":"string","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"nullCount","type":{"type":"struct","fields":[{"name":"a_NuMbEr","type":"long","nullable":true,"metadata":{}},{"name":"a_StRiNg","type":"long","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"remove","type":{"type":"struct","fields":[{"name":"path","type":"string","nullable":true,"metadata":{}},{"name":"deletionTimestamp","type":"long","nullable":true,"metadata":{}},{"name":"dataChange","type":"boolean","nullable":true,"metadata":{}},{"name":"extendedFileMetadata","type":"boolean","nullable":true,"metadata":{}},{"name":"partitionValues","type":{"type":"map","keyType":"string","valueType":"string","valueContainsNull":true},"nullable":true,"metadata":{}},{"name":"size","type":"long","nullable":true,"metadata":{}},{"name":"deletionVector","type":{"type":"struct","fields":[{"name":"storageType","type":"string","nullable":true,"metadata":{}},{"name":"pathOrInlineDv","type":"string","nullable":true,"metadata":{}},{"name":"offset","type":"integer","nullable":true,"metadata":{}},{"name":"sizeInBytes","type":"integer","nullable":true,"metadata":{}},{"name":"cardinality","type":"long","nullable":true,"metadata":{}},{"name":"maxRowIndex","type":"long","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"baseRowId","type":"long","nullable":true,"metadata":{}},{"name":"defaultRowCommitVersion","type":"long","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"metaData","type":{"type":"struct","fields":[{"name":"id","type":"string","nullable":true,"metadata":{}},{"name":"name","type":"string","nullable":true,"metadata":{}},{"name":"description","type":"string","nullable":true,"metadata":{}},{"name":"format","type":{"type":"struct","fields":[{"name":"provider","type":"string","nullable":true,"metadata":{}},{"name":"options","type":{"type":"map","keyType":"string","valueType":"string","valueContainsNull":true},"nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"schemaString","type":"string","nullable":true,"metadata":{}},{"name":"partitionColumns","type":{"type":"array","elementType":"string","containsNull":true},"nullable":true,"metadata":{}},{"name":"configuration","type":{"type":"map","keyType":"string","valueType":"string","valueContainsNull":true},"nullable":true,"metadata":{}},{"name":"createdTime","type":"long","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"protocol","type":{"type":"struct","fields":[{"name":"minReaderVersion","type":"integer","nullable":true,"metadata":{}},{"name":"minWriterVersion","type":"integer","nullable":true,"metadata":{}},{"name":"readerFeatures","type":{"type":"array","elementType":"string","containsNull":true},"nullable":true,"metadata":{}},{"name":"writerFeatures","type":{"type":"array","elementType":"string","containsNull":true},"nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"domainMetadata","type":{"type":"struct","fields":[{"name":"domain","type":"string","nullable":true,"metadata":{}},{"name":"configuration","type":"string","nullable":true,"metadata":{}},{"name":"removed","type":"boolean","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}}]},"checksum":"f454180a2c72b7503ff84cf161c8fb9e"} diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/part=100/part-00000-c707739f-45c0-4722-9837-4b11599ea66d.c000.snappy.parquet b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/part=100/part-00000-c707739f-45c0-4722-9837-4b11599ea66d.c000.snappy.parquet new file mode 100644 index 0000000000000000000000000000000000000000..79484e2495300c9fdb0db1f29d4cf62f1c8956f6 GIT binary patch literal 832 zcmah|O>fgc5M4W^wICdjva2<+AhKxGB4>${w8c>_e6%W3u&NR+1yrq_O={I~QhNic zqFh1Z$O&=djKqyICnSCkXM`BrDUFl^m+{Wbn>X{uy9W;rZH#aUQ~cxI*FV<`tb)6W zHH5C-Q4vA~MF9)3oA?&~{_Xe2;sWG)Er%2U!b7g#P<6VJ;^!Z%>I9rLTuAeood*q~ z;XTMA-5@URq$kL<_lwDx$EU}MeK9h%mJ6J~@0}6?% z8@g4|U^IPeRfG2b3b0C4@{sIRw01sPZVB4R2F*Q~0TevMXt9haQ#&9T}0k1M6GD#gHi&;FR4C?2aJz7sAQR7CWE0k z8byMx?MnaN^X?-=jU`Yh2HZb?;&z>FiV&qsfY%~Uf>A`TG+2|_h;A++lCzM(hR-HK zvUv1{4Ta=AE;;?IOig({7G}##WIXYuX_@^%guTRUy_lL2AEv2`9|6fZIEbVeh!_IV zBn-J10u1VfWnNI5t?6uLEl}jEyrh7BK{WUu!>bvhx0m$fD|yVN=)wlmtsKqeRflnT zzK^oa>^f^yQ=wiQ_@_y=R;$&^8{Q-cd*yobev>;}Jx|!%9O4bp5MJH!>ovRYIQ_mO X_|A?k#HMb+bxUvD7q}`1@OJ(J7$3=O literal 0 HcmV?d00001 diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/part=200/part-00000-9cfa2c9c-efae-4e0a-bf25-eba86bfdce11.c000.snappy.parquet b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/part=200/part-00000-9cfa2c9c-efae-4e0a-bf25-eba86bfdce11.c000.snappy.parquet new file mode 100644 index 0000000000000000000000000000000000000000..35e6edf6cc93bd433b7f4d6d4637a3fdce60adfc GIT binary patch literal 839 zcmah|&x_MQ6rRLdqZC2t3=;@gf?IZ>9hOs}_Cn>mPXtufNGh{2T4rb4o0 z{DF;xgs1eazPGbDFz||Af84M z_amS|y{Oq&(spMypVw9-@^LOD;Jh#%{7>QS9MQXLe)5q#;Zk&Af@&(q3w!lZQd!KS z;xMPlT6JaAPr_iB)*FpRv$E+=!>CtjwjZ>)yWR7Jv%>*ziI(u2ZqRHveb?>xUBP#E U9U->N8eBPb>wds>IfN(l7m%yW$N&HU literal 0 HcmV?d00001 diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/part=300/part-00000-7b5e9086-abfc-4a58-8c2c-d775a1203dd3.c000.snappy.parquet b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_case_sensitive/part=300/part-00000-7b5e9086-abfc-4a58-8c2c-d775a1203dd3.c000.snappy.parquet new file mode 100644 index 0000000000000000000000000000000000000000..a09b84c57fed74e458a77c8a241f5884c040245f GIT binary patch literal 839 zcmah|&2G~`5MH}!u~3ABlwGYQOIQ|-TFF`BByDlj3qP#@fmM}oDWGcYY=TwCN$oYL zit+|Mazvn>xFGQWya9JEJPAj}c1j}Uz-8An^UXK&jc1P@96K1{8m9Q$?=M%1fmJX! zu!hjhyDCCxO<98zdTUp7~s1Z@?qmKO8?BB2ye{ZVj@d{Qt&bfgc5M7&4tSTTOWmjv-5|%}yR!WvQNn0HC!bdAWU{xhtN>R0THpZ&sr1lzA zqMW$)$dOCM58xkgMdHq(|D-n<$0>=F1D9RT%$qm!#A&DVL%P6U?V_IFnZ&B<5@$ zrp@doYt?h1UK08zX}!^CG|hD{3!|RdY(H#sXS3%Cdy9j=C0fF3I)1ZZ_Z_F-cLd+w UwuRWxYp`) +USING DELTA +PARTITIONED BY (part) +LOCATION 's3://?/parsed_stats_struct' +TBLPROPERTIES (delta.checkpointInterval = 2); + + +INSERT INTO default.parsed_stats_struct VALUES (100, 1, STRUCT(1,'ala')), (200, 2, STRUCT(2, 'kota')); +INSERT INTO default.parsed_stats_struct VALUES (300, 3, STRUCT(3, 'osla')); +INSERT INTO default.parsed_stats_struct VALUES (400, 4, STRUCT(4, 'zulu')); +``` diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/_delta_log/00000000000000000000.json b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/_delta_log/00000000000000000000.json new file mode 100644 index 000000000000..87703a34a689 --- /dev/null +++ b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/_delta_log/00000000000000000000.json @@ -0,0 +1,3 @@ +{"commitInfo":{"timestamp":1700645572508,"userId":"615774135840106","userName":"marius.grama@starburstdata.com","operation":"CREATE TABLE","operationParameters":{"partitionBy":"[\"part\"]","description":null,"isManaged":"false","properties":"{\"delta.checkpointInterval\":\"2\"}","statsOnLoad":false},"notebook":{"notebookId":"2900318529393697"},"clusterId":"0905-151610-v55wl6f5","isolationLevel":"WriteSerializable","isBlindAppend":true,"operationMetrics":{},"tags":{"restoresDeletedRows":"false"},"engineInfo":"Databricks-Runtime/13.3.x-scala2.12","txnId":"f65a01af-26a6-461a-9582-1e662891f5e3"}} +{"metaData":{"id":"5a789ae0-5f44-4922-aedd-a959411e05f4","format":{"provider":"parquet","options":{}},"schemaString":"{\"type\":\"struct\",\"fields\":[{\"name\":\"part\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"id\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"root\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"entry_one\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"entry_two\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]}","partitionColumns":["part"],"configuration":{"delta.checkpointInterval":"2"},"createdTime":1700645572108}} +{"protocol":{"minReaderVersion":1,"minWriterVersion":2}} diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/_delta_log/00000000000000000001.json b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/_delta_log/00000000000000000001.json new file mode 100644 index 000000000000..d7fc9666087c --- /dev/null +++ b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/_delta_log/00000000000000000001.json @@ -0,0 +1,3 @@ +{"commitInfo":{"timestamp":1700645578101,"userId":"615774135840106","userName":"marius.grama@starburstdata.com","operation":"WRITE","operationParameters":{"mode":"Append","statsOnLoad":false,"partitionBy":"[]"},"notebook":{"notebookId":"2900318529393697"},"clusterId":"0905-151610-v55wl6f5","readVersion":0,"isolationLevel":"WriteSerializable","isBlindAppend":true,"operationMetrics":{"numFiles":"2","numOutputRows":"2","numOutputBytes":"2329"},"tags":{"restoresDeletedRows":"false"},"engineInfo":"Databricks-Runtime/13.3.x-scala2.12","txnId":"ce3db1cf-1d23-4d68-8d05-8c20f22e0b88"}} +{"add":{"path":"part=100/part-00000-7d5755b3-20b1-4039-920b-d1f24022685e.c000.snappy.parquet","partitionValues":{"part":"100"},"size":1161,"modificationTime":1700645578000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"id\":1,\"root\":{\"entry_one\":1,\"entry_two\":\"ala\"}},\"maxValues\":{\"id\":1,\"root\":{\"entry_one\":1,\"entry_two\":\"ala\"}},\"nullCount\":{\"id\":0,\"root\":{\"entry_one\":0,\"entry_two\":0}}}","tags":{"INSERTION_TIME":"1700645578000000","MIN_INSERTION_TIME":"1700645578000000","MAX_INSERTION_TIME":"1700645578000000","OPTIMIZE_TARGET_SIZE":"268435456"}}} +{"add":{"path":"part=200/part-00000-22aeb477-f838-44b1-a897-4b0e7d009ae4.c000.snappy.parquet","partitionValues":{"part":"200"},"size":1168,"modificationTime":1700645578000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"id\":2,\"root\":{\"entry_one\":2,\"entry_two\":\"kota\"}},\"maxValues\":{\"id\":2,\"root\":{\"entry_one\":2,\"entry_two\":\"kota\"}},\"nullCount\":{\"id\":0,\"root\":{\"entry_one\":0,\"entry_two\":0}}}","tags":{"INSERTION_TIME":"1700645578000001","MIN_INSERTION_TIME":"1700645578000001","MAX_INSERTION_TIME":"1700645578000001","OPTIMIZE_TARGET_SIZE":"268435456"}}} diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/_delta_log/00000000000000000002.checkpoint.parquet b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/_delta_log/00000000000000000002.checkpoint.parquet new file mode 100644 index 0000000000000000000000000000000000000000..20aa1e4ff52c73bfbb7acb1fe72b8aeecca333f3 GIT binary patch literal 21976 zcmeHP4RBLec7Bp&TQo;VviwKPPr{!~9mnf9gtAH4XX}}0ktIix z2?R)K%48ju$$D6Zux^IXOp@7BHc3-8WztT-}w&gd@dd8o74*m%quelR|jRBUX;f z7jig`ga0?+{ly%zGl!h{%i|wdbR4djnkkEY=4Rm3BSvELD?OgMrUHx0V={f}s`Xdb zR#$gaxxAi^N|(=5Rqv{Yq|0B~<@I^I-kQ2<$=&Jkc-+yD*weGo4G>TDN-@&Pd23p^ zPrY7I>hRUpy1MGB>Rdh_fQWVVwJu+WN2>LEJoTdFOGV#2bstv+0R8^@u1>GFN^<$S zqz;#_&R6HE_j{{dAW4n4tJ5P@`#kOytsJqC5>iTD{PoYz7d8+xv2dK#4!>xfKz%ca zgZ%!3V?Uymw}5){IBrUvnW~p+;8G5k1Lc;%zgK^9>IcQDlAzo@f?Up$o42Y7n}Y)# z;g?tbs`7Y%asm8%>-9sgfHP+)+I_*o<(eA$Q6TvINcje#%g8J;aPmZ-r2!rw zyQHCH_V@nqLlein?UTO&x|9fH$KWr9DKrWX(1j|rDaQguu=2nC9||%3U}1b z0TbCwJ~;dGht>vSMNTXI#UM&k%fn0C+M6C~S<~M1;L>T3^I%iUniV`}xqsC>o_nkm zavo}hcTKC8u4%u&ZTZsnH4j4?SXK}Z`HTPjdXt^l0iSA=Z%2-~2k*XBUh>J6FHc!@Oa)p^V482rui%QfD~gk2>a6Ryeu?p_O8= zSAwTaj(|V6v?vk|$4pboq);rfaZNZRZ0Rf9Bp@$bEH;d*&F& zRkh)-nq!rXz^52S)J1&EISa_H7C?EIU*)=!e9%{X3jxF!yv%W zkqIO#_;2_8-6@zy6a|B@^aIl3Oto#`{PjYtB%7&J^?`;np#S;RAO6ggOb<%B0rWTD z(55rwcY-ZC^~%40Y*zaLSeVHkoh71O%MO;|!Yi+TrY}|OQ|u6E8~Y3BI~}wC@9&== z9pvvn+WPAvRoP%U=N_a3-pb7a9af9Ab>M_}T-9AZpUYS8^}0mK?{|sy)%Cv0O34HH zik>eI{ncCgSyZtj7PTG5dOr)s@h8WBJD_(CrY)gz6mj_=hw3+RU`)%%^^@C|s}e)^ zNN9M0^^f$WuI7>>W^(xGTOaEEg6n2^`nPgV!GyNS5!=`!!Au^FMS449jtWOtKnkKM z_%2umvDKLVJ#pmh+{zr zOjJ(_>*7Rj+$NN!biFl% z5}o@@^=13mD-Z=!dJt|oIYX)b-r2%@UOkpcp85#kwD4cN4P zXe{`muA-(zD-LV?Kr^XHJr(8w6%O0<**W0FL1F5>X)G)^00T^y`~fl5q)r}|m;LJ- z*yw+pP|)_XGO_d-7DKIDGnv}*VJ^XNh)qya8I!`$8QU(S$;PP)Y_U=>(KyqgGa-m> zRoE1>B32)=h0zS^7_W4wCNIo@UR{P&HJ;Fk)uisVI*f2M>9f&H!l0wpGNZQ1rkmP% zD_O)N2Rq-$Qhg?t95Ru?9HqdKT+*r}cju9qlH8t8f=cp`l`K<|&livfl;nj%@|co5 zSwz04B;%7Aj}a{yFV=FkWPFO2t0m(lTCSFiPbE84fe)3ERSLTkge*~#yLqx&NnX;D zNADmZCHJ7f3hcD8O=<<2C{3gb7*NQ;sj zDPslVceCV(mW(^JTrC;DN6Xcc@mX4~mWlaOB$8J*Rh z2}9+2M`KYGri*oJqTu~@i=>FFHVZki4IzkxlA_sWAs6DeP5#+jvmFPQ1#(y6gc$F9K>WWG+n6Ths5>}-t^uWfL6jk)vDMtK(kQfZaHYzy~OKS^1-4yam8|0jV z4lycWmS29p!!LD-y}=k{Ee>~g2VyJLmZA!b#>7}u5do^g8=7McJVzxzG0P%RhZnV0 zMCsJGR+&|y797Mf1uQs-&4)EI2WWS&pbMtv6);y$)|&DRt36Gs0;r|ZJtm5z1|2DX z>@Y@|Fi$U&?ipmldgVo=?(lkvOiS+2bSz9&y94$)&{KC$a>t`Ru~ga+lc44N%K|~^ zL1j^l9B$5_0N`(CPX)oP7&BM`1DBM@T8Cy+2M(Q8Ao`Fn$R_!vXlEqQgTqqmNO@i1 zNViDa9;vK7k?{I}Uy5iY^23y5A~OOV{6+HI`7jMvXz(R$d@T+Kd%Hu(C$G6dp6@Kd zKs1I!4Q0!RIi(xMhDJNNGaTv)tnH1^32g#X7VV{zIwKO!&var$4TCOX;m&Xnc8us? zErH?NCW)XOHe=uz#_UHU0f>dD+1RfD3kog+#NLRMO{I#TrdZ>l^oP5}Ku8@os%^@X zvlX>bRVFGBffAd%X;A#E5qp@%GHa+Qwm{MHx`3M4P1ZnHgSuib1}Gmal}#M~9>4pfmD#i9wt=l3$j^F_4eu&oi(QBiK;{>%Wic zYAWk#RF+)o##7R5h5U{jXM8HoVMC_$!Pqe)sTzs0y58ND&l?+l z9GIgl;HdGV?cC0Xtd8t0au^qj6@*Eu#Ht>Othx+7Z^I?NJQfV!0* zWwM@s76_bs2$&WOz~M(r0lyTvDdxx*+kpMG`Dj{p^Q@kYI7cS1*H)UaET96c}$!U&+_(G8b3vzA8Hx-89f=dgWZ(wszT2B|c|AdR{LQo=LZA_(23;pqkodmf3OdjO5% zzzn%6xad=elS4Rkydl!S3)opobCk8U=l$EOSuiEf?}D_!j3IWd>P4M%X=z zsnZIu>bgj6LbfzQt&JFoFCy`~lsJAjYyUchI3p+eZi77A9s$UcvOH-aNA;tPr~`<4 zSr*j6=EqS4O>(6j1{vQ*wEiVjXKB$=d(%e9BMA8>g^b_Bnqd?HC#7_#?=eVu8X=F# zQl^JY=~EjaFC*l6S;|>VN_EJla;s+XH%Cd)7w0y@>Y)#f#5oJ+Pbw zD^+W-gRZsXvl}woTF$H#Z!OPgqdGG*z1t993I65aQu3yWa}GLF)XRd=RyG3-It^+* zhHAOI4As2t-W25e*epZYcCP{DETZ%;N0dPgrH(0AiRH2wE=6kNS3>CqwujCVpe=P@ zo#pB4d=SbcivK1&8;T>ITMgJjD9?9^fxfXM?F+FI+b2spQE}i ztsu|BzKlC_@pa}I!)e>yu%+RJ9}tLs>sf4F1sb~eC%C0?7FeiD9M&TGeORjhX1*0l zHfV?1v=rd8qi$NRp4)EZ*58aP^mpd6rA9g1+)dilr7V~5jZhq#9AkA#v<#YDd(yKw zfGj?0#wNcu8jFMtV#eYcvcQ9P@^7e(?n%$$FtWH#SseCg&7$s58+MzMyD+Xb4|@#0 z*vAOE|3UPb`YTg_CT>Kh2JNpjfL=tkDZreg zFnhfyTS}P3(Zta9dJRGiAlkMTtmzp*QzH@vq13%^hI7V<^ETqVia7jN4Sb=~%I~g% zOP;pJ1Y0<=)-Cpka6ZgUkBYftaMootT=x_5m&Q8hKGMFJ6YNQag3?;Cb7NCWdwmVu zun>f#0?H{eA-Bryb60Z0oTL)br-JImVRt0_v|GJPBK(&-6HgKL^qG&CNqKtEjZtLi z5mwO4Fz5y=_0a%=;q(rrMy@+XmmNe}pH$-kv2=!=iAM178Y9SBA?)~zvWKGj+Kh9z z*aPUWosXyHmQ)Vs>hhlILB-#Q_xO~MZokyr2P#{KmTt+Z-dfgcD&0LACUMpv^0gaApGl3?t2vA)Yo^Lg z9tOa;-jX>jtx?o^g#M}!lQ!$>vZ-9TS;UZ7y~RjG+z8pq%_3tk1+Ev3JC2fYH7S|% zEz!89P~FugCQC-fI*UeAgVBruQ}YtR=+ijX08TyB5mGt-)PMDb#o0r4;a7$A{Ul}9( zt6@^YKWgFXj+7~}@UlvGrPp2MLT|Vu66jnP#mi&fvht4JK+s?2g}>V=)>r#Gq{Gf20)z^1*)k|V+ZKWj5f!lg;`}`aI!*ws@xK{X^uKxpB2&TyZ literal 0 HcmV?d00001 diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/_delta_log/00000000000000000002.json b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/_delta_log/00000000000000000002.json new file mode 100644 index 000000000000..e92c4c7cb22a --- /dev/null +++ b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/_delta_log/00000000000000000002.json @@ -0,0 +1,2 @@ +{"commitInfo":{"timestamp":1700645581464,"userId":"615774135840106","userName":"marius.grama@starburstdata.com","operation":"WRITE","operationParameters":{"mode":"Append","statsOnLoad":false,"partitionBy":"[]"},"notebook":{"notebookId":"2900318529393697"},"clusterId":"0905-151610-v55wl6f5","readVersion":1,"isolationLevel":"WriteSerializable","isBlindAppend":true,"operationMetrics":{"numFiles":"1","numOutputRows":"1","numOutputBytes":"1167"},"tags":{"restoresDeletedRows":"false"},"engineInfo":"Databricks-Runtime/13.3.x-scala2.12","txnId":"c77efc08-5797-458e-8b55-11b4ff6d1325"}} +{"add":{"path":"part=300/part-00000-d9fc223e-4feb-4848-9d25-03962fc0e540.c000.snappy.parquet","partitionValues":{"part":"300"},"size":1167,"modificationTime":1700645582000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"id\":3,\"root\":{\"entry_one\":3,\"entry_two\":\"osla\"}},\"maxValues\":{\"id\":3,\"root\":{\"entry_one\":3,\"entry_two\":\"osla\"}},\"nullCount\":{\"id\":0,\"root\":{\"entry_one\":0,\"entry_two\":0}}}","tags":{"INSERTION_TIME":"1700645582000000","MIN_INSERTION_TIME":"1700645582000000","MAX_INSERTION_TIME":"1700645582000000","OPTIMIZE_TARGET_SIZE":"268435456"}}} diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/_delta_log/00000000000000000003.json b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/_delta_log/00000000000000000003.json new file mode 100644 index 000000000000..8051b7571f4c --- /dev/null +++ b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/_delta_log/00000000000000000003.json @@ -0,0 +1,2 @@ +{"commitInfo":{"timestamp":1700645587551,"userId":"615774135840106","userName":"marius.grama@starburstdata.com","operation":"WRITE","operationParameters":{"mode":"Append","statsOnLoad":false,"partitionBy":"[]"},"notebook":{"notebookId":"2900318529393697"},"clusterId":"0905-151610-v55wl6f5","readVersion":2,"isolationLevel":"WriteSerializable","isBlindAppend":true,"operationMetrics":{"numFiles":"1","numOutputRows":"1","numOutputBytes":"1168"},"tags":{"restoresDeletedRows":"false"},"engineInfo":"Databricks-Runtime/13.3.x-scala2.12","txnId":"71e67df6-676b-43b3-81f7-666b34d65aa1"}} +{"add":{"path":"part=400/part-00000-da80387a-f286-4848-9853-f82afd408710.c000.snappy.parquet","partitionValues":{"part":"400"},"size":1168,"modificationTime":1700645588000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"id\":4,\"root\":{\"entry_one\":4,\"entry_two\":\"zulu\"}},\"maxValues\":{\"id\":4,\"root\":{\"entry_one\":4,\"entry_two\":\"zulu\"}},\"nullCount\":{\"id\":0,\"root\":{\"entry_one\":0,\"entry_two\":0}}}","tags":{"INSERTION_TIME":"1700645588000000","MIN_INSERTION_TIME":"1700645588000000","MAX_INSERTION_TIME":"1700645588000000","OPTIMIZE_TARGET_SIZE":"268435456"}}} diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/_delta_log/_last_checkpoint b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/_delta_log/_last_checkpoint new file mode 100644 index 000000000000..0c989170ebd7 --- /dev/null +++ b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/_delta_log/_last_checkpoint @@ -0,0 +1 @@ +{"version":2,"size":5,"sizeInBytes":21976,"numOfAddFiles":3,"checkpointSchema":{"type":"struct","fields":[{"name":"txn","type":{"type":"struct","fields":[{"name":"appId","type":"string","nullable":true,"metadata":{}},{"name":"version","type":"long","nullable":true,"metadata":{}},{"name":"lastUpdated","type":"long","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"add","type":{"type":"struct","fields":[{"name":"path","type":"string","nullable":true,"metadata":{}},{"name":"partitionValues","type":{"type":"map","keyType":"string","valueType":"string","valueContainsNull":true},"nullable":true,"metadata":{}},{"name":"size","type":"long","nullable":true,"metadata":{}},{"name":"modificationTime","type":"long","nullable":true,"metadata":{}},{"name":"dataChange","type":"boolean","nullable":true,"metadata":{}},{"name":"tags","type":{"type":"map","keyType":"string","valueType":"string","valueContainsNull":true},"nullable":true,"metadata":{}},{"name":"deletionVector","type":{"type":"struct","fields":[{"name":"storageType","type":"string","nullable":true,"metadata":{}},{"name":"pathOrInlineDv","type":"string","nullable":true,"metadata":{}},{"name":"offset","type":"integer","nullable":true,"metadata":{}},{"name":"sizeInBytes","type":"integer","nullable":true,"metadata":{}},{"name":"cardinality","type":"long","nullable":true,"metadata":{}},{"name":"maxRowIndex","type":"long","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"baseRowId","type":"long","nullable":true,"metadata":{}},{"name":"defaultRowCommitVersion","type":"long","nullable":true,"metadata":{}},{"name":"stats","type":"string","nullable":true,"metadata":{}},{"name":"partitionValues_parsed","type":{"type":"struct","fields":[{"name":"part","type":"integer","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"stats_parsed","type":{"type":"struct","fields":[{"name":"numRecords","type":"long","nullable":true,"metadata":{}},{"name":"minValues","type":{"type":"struct","fields":[{"name":"id","type":"integer","nullable":true,"metadata":{}},{"name":"root","type":{"type":"struct","fields":[{"name":"entry_one","type":"integer","nullable":true,"metadata":{}},{"name":"entry_two","type":"string","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"maxValues","type":{"type":"struct","fields":[{"name":"id","type":"integer","nullable":true,"metadata":{}},{"name":"root","type":{"type":"struct","fields":[{"name":"entry_one","type":"integer","nullable":true,"metadata":{}},{"name":"entry_two","type":"string","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"nullCount","type":{"type":"struct","fields":[{"name":"id","type":"long","nullable":true,"metadata":{}},{"name":"root","type":{"type":"struct","fields":[{"name":"entry_one","type":"long","nullable":true,"metadata":{}},{"name":"entry_two","type":"long","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"remove","type":{"type":"struct","fields":[{"name":"path","type":"string","nullable":true,"metadata":{}},{"name":"deletionTimestamp","type":"long","nullable":true,"metadata":{}},{"name":"dataChange","type":"boolean","nullable":true,"metadata":{}},{"name":"extendedFileMetadata","type":"boolean","nullable":true,"metadata":{}},{"name":"partitionValues","type":{"type":"map","keyType":"string","valueType":"string","valueContainsNull":true},"nullable":true,"metadata":{}},{"name":"size","type":"long","nullable":true,"metadata":{}},{"name":"deletionVector","type":{"type":"struct","fields":[{"name":"storageType","type":"string","nullable":true,"metadata":{}},{"name":"pathOrInlineDv","type":"string","nullable":true,"metadata":{}},{"name":"offset","type":"integer","nullable":true,"metadata":{}},{"name":"sizeInBytes","type":"integer","nullable":true,"metadata":{}},{"name":"cardinality","type":"long","nullable":true,"metadata":{}},{"name":"maxRowIndex","type":"long","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"baseRowId","type":"long","nullable":true,"metadata":{}},{"name":"defaultRowCommitVersion","type":"long","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"metaData","type":{"type":"struct","fields":[{"name":"id","type":"string","nullable":true,"metadata":{}},{"name":"name","type":"string","nullable":true,"metadata":{}},{"name":"description","type":"string","nullable":true,"metadata":{}},{"name":"format","type":{"type":"struct","fields":[{"name":"provider","type":"string","nullable":true,"metadata":{}},{"name":"options","type":{"type":"map","keyType":"string","valueType":"string","valueContainsNull":true},"nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"schemaString","type":"string","nullable":true,"metadata":{}},{"name":"partitionColumns","type":{"type":"array","elementType":"string","containsNull":true},"nullable":true,"metadata":{}},{"name":"configuration","type":{"type":"map","keyType":"string","valueType":"string","valueContainsNull":true},"nullable":true,"metadata":{}},{"name":"createdTime","type":"long","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"protocol","type":{"type":"struct","fields":[{"name":"minReaderVersion","type":"integer","nullable":true,"metadata":{}},{"name":"minWriterVersion","type":"integer","nullable":true,"metadata":{}},{"name":"readerFeatures","type":{"type":"array","elementType":"string","containsNull":true},"nullable":true,"metadata":{}},{"name":"writerFeatures","type":{"type":"array","elementType":"string","containsNull":true},"nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"domainMetadata","type":{"type":"struct","fields":[{"name":"domain","type":"string","nullable":true,"metadata":{}},{"name":"configuration","type":"string","nullable":true,"metadata":{}},{"name":"removed","type":"boolean","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}}]},"checksum":"94cfd4172a07fe6113ed85a12cb38d74"} diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/part=100/part-00000-7d5755b3-20b1-4039-920b-d1f24022685e.c000.snappy.parquet b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/part=100/part-00000-7d5755b3-20b1-4039-920b-d1f24022685e.c000.snappy.parquet new file mode 100644 index 0000000000000000000000000000000000000000..a081afcdf18b51d5e02a5524922922b843340ea8 GIT binary patch literal 1161 zcmb7EL2DC16rP>L8rvcWonclM42un^v}-m=TN83oYpEikNWIjSGD#+J>1MaxomfK1 zQS{tH|Aij>1wH89i#Ly+ym%Fa;+x%VS_#+|mbdfXe(#(2zBfDT4`0{>kr~3smv$CKwEQ~8vFV3c4Ck|l3{|N4_Thq+l!FzUr! zuHV8sORI$BMX*l6`A;~d(wMRd)}R@(3Q0&eXq~Jjt0Rrn)e=BkYt!K#4|aFL=4;XA zBn`n?%&^l!inp%;(mUOye6QpC;pUCF@mm7}}GsZx!=%z#1LX^4Ddz-q2qh(TFEL~ zHez#Qh;ZMvV4nLuA+5kau)0F>7MGlT!UI$G_k>wBLmBj%(wsNjj&NI{S$#DyJ>FHH zoEC%=nBz&&5dj3ep6l|43joLz^X4h3Sse_A^P^;SSVrlW*==;7Mp@>SH;p-9c4r*W zpW}5ds(X8QL1WuP#O{m<9M5QlOR)hXRb$#r^j_!&*#RX+bI92%P3_xKJn* zirIXl=eVtGvHYOSD~qj$u$MT*OQIwi#Y(eSu-lbNyIm1{dD#|XPOrg#6+h?$Tnp=P G3H=3O9u`Xg literal 0 HcmV?d00001 diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/part=200/part-00000-22aeb477-f838-44b1-a897-4b0e7d009ae4.c000.snappy.parquet b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/part=200/part-00000-22aeb477-f838-44b1-a897-4b0e7d009ae4.c000.snappy.parquet new file mode 100644 index 0000000000000000000000000000000000000000..63e610a2c59922707c9e37f06bdfeab08f70ffe8 GIT binary patch literal 1168 zcmb7EL2DC16rN0CO>IGh&M+&BhQ)>yk~Nz&ZBuelYpGB|kb0>tWs*$dlFe?rJF$e4 zlU}?_(I4Qwdh+Z~5WITqMFj6&e3RWKk$|nR%Gtvz1gijcxn+u76>EnKfn4m zs}l_23`rt%;~qu`#bauP93{8Oo8KRPBqtzqnYe;r6k+ZVgPi?5KbxDt++2$=34nGy z$+cTpV+mCe-3Znw1pi78>sUhFBD7AEWE0Aerqe3fik?nmq^2GLw6`@M9CE*N5Hwzh zE++{{E@Fo5CMsc;@I6mn%>DL&==_LIgTL8QFnCAIy zlOJ;M)f`%EO1mp|!9I5Vv25Ibw(|%veG24=HgEKwRCmfN3?as*0563f*q+O-70qSS zLTqUY5ga)t%=D-yr0ILdW>-kwEFgG_pp^7EUuT)}NmkF7K*W zb`#PO%yy+{3m+10&vAI&0RU8rtZ_kVte*@9*-^1NFr)IT>^3@7qblRSHw`&peq#>k z&vx6F>E0h)(%Ak0vAa_ShcnvXQtZG;)tGi8zjIwb6%8n5nO4Cp=9Kew-)?k*Tt1&K zq%!rM?KD$`<+WvAE;Z}ITH%l{ilV3&%8f$aYL&~aR$1`XRZEB^tpeXxc%hH*lc>SJ F=nw0#8A<>E literal 0 HcmV?d00001 diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/part=300/part-00000-d9fc223e-4feb-4848-9d25-03962fc0e540.c000.snappy.parquet b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/part=300/part-00000-d9fc223e-4feb-4848-9d25-03962fc0e540.c000.snappy.parquet new file mode 100644 index 0000000000000000000000000000000000000000..b7fb0541e9da1cb9de7c1c2b616a47465aa67120 GIT binary patch literal 1167 zcmb7E&ubGw6rN0CU9})WXPA{>!(u}U>6%T_A6t6yS5YV-NW9dRGD#+J>1H?GomfiA zMf?k-2%f~d7sX4#gMWYr&*IUue}iwb+f5{3D=f47zWv@e?|pA}>vx}61d%zy$cG>A z-lwtzLpVb;gs$Gk2%&T;4J%-j+$3M#p3YA&t`*d~G)6Jy7BR@H({G=(3C{Jkn3Dmj zAG%z>fpwNqV0&%6 zrkLKNn|W;`YA0GLDvX+oA}9~6gRMd2HzsrRSBjV@UrUqT9f2 zPhfhpQGtKSLL(#DEm{W-=qewhRaHBVCaZmuCX{|vy7YUB;=D$Grslxz9j5z1*W`T; zzM3P8P3iQ+HrU55Ka{OIPaF3U%TAG8(dDh9$F+vN!VqF?its`Nq2qh(QpK#A7GjH2 zjPSrUVWtNIAI-At2xTy6Nuy|V9O1S@WBu8&;qjh&<+K5g zX^tmFR|Eif1J~tE7YL9kipH6!v3@)p7L#OkV3PEU?3Ns=B+K~kO(PAM-&h0sbG+_( zb?*+(X>50h*!+~i(TpB&DHnI=0>E*n+RFT0$)98}MyK7y1bQgF5_*{s0av B7wG^1 literal 0 HcmV?d00001 diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/part=400/part-00000-da80387a-f286-4848-9853-f82afd408710.c000.snappy.parquet b/plugin/trino-delta-lake/src/test/resources/databricks133/parsed_stats_struct/part=400/part-00000-da80387a-f286-4848-9853-f82afd408710.c000.snappy.parquet new file mode 100644 index 0000000000000000000000000000000000000000..eb85efaaad2646c8f7903947fb90a882e2722e5d GIT binary patch literal 1168 zcmb7EO>fgc5M6IV8z2ZFWmjutL1fV=m0A<$qphP}fKnByiXv1lrB$^_Ho>Z6r}l=b zR5_8KfGV#101oAjgt&0w#ElC_&in%y$8H)Fi5AIPd&Y0xzIn5D{lQa{Ad(@By!-O~ z^$m?+2xmwNp=A zM3<8!B0b_-Q7OZuKC&vI9VEB&KA&lQNI0Taja(3?Y>8qiU!YU*k(1#PL7nG^^I z8iFaN59xX?wHmb(trX=)%|#K&1MgsKAo+z!hW=EDfpWDmQ+}l6T)~KSjc(*>mt-9# zfb|UySx*#=q$7cU(Ly65+D%#q4Dg0NMr)|{JepMdHbE%;9P-j{3dOk;{h?&Q?j5Fj ze#hWF4!#;Aiw$Xa#WvW-W*^Ju{byT`5YwhWj_B~_(UaPiwZsr&YzpvF_<`-Y>`KWf z8zy4&Q;6WeF<_<#eIX6sJ2bjN@)nnzeZa@MJn9L(st3~VH>JLyw{79H0=@eDSa*3h zd}X&F9m8x_ijMFh;r1PeHyi*!rC89APDRZmRY6x?QL%t+RqEWP(#e&(ktajTHe0kXvVqRT^Z!5acDf}er H@GtrUD90Jo literal 0 HcmV?d00001 From 76320c5c18096dd8c313fb9c5f0f50341bc53851 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Fri, 24 Nov 2023 19:01:19 +0100 Subject: [PATCH 455/587] Update AWS SDK v1 to 1.12.595 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e4f007e808c8..98b385e285d7 100644 --- a/pom.xml +++ b/pom.xml @@ -178,7 +178,7 @@ 4.13.1 14.0.0 1.11.3 - 1.12.590 + 1.12.595 4.17.0 7.4.1 87 From e9975601b5d95e0d9aea72d9c38a43c156e8bc5b Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Fri, 24 Nov 2023 19:02:51 +0100 Subject: [PATCH 456/587] Upgrade AWS SK v2 to 2.21.29 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 98b385e285d7..594f0b5b9780 100644 --- a/pom.xml +++ b/pom.xml @@ -297,7 +297,7 @@ software.amazon.awssdk bom - 2.21.24 + 2.21.29 pom import From 4bd319b2827099709a67750fa4ade72b6682f69c Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Fri, 24 Nov 2023 19:03:47 +0100 Subject: [PATCH 457/587] Update redshift-jdbc to 2.1.0.23 --- plugin/trino-redshift/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/trino-redshift/pom.xml b/plugin/trino-redshift/pom.xml index e49a5418184b..eeb7ee0b20b8 100644 --- a/plugin/trino-redshift/pom.xml +++ b/plugin/trino-redshift/pom.xml @@ -21,7 +21,7 @@ com.amazon.redshift redshift-jdbc42 - 2.1.0.22 + 2.1.0.23 From 0df9bd9d0d5de25b0ca6225b2062ca346c5e7f9f Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Fri, 24 Nov 2023 19:04:25 +0100 Subject: [PATCH 458/587] Upate flyway to 10.1.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 594f0b5b9780..e09be1d79805 100644 --- a/pom.xml +++ b/pom.xml @@ -185,7 +185,7 @@ 1.21 1.0.8 2.23.0 - 10.0.1 + 10.1.0 1.43.3 1.4.2 5.13.0 From 11d79f0d3eeaa1ad69b6d768e271b391eb77a42c Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Fri, 24 Nov 2023 19:05:02 +0100 Subject: [PATCH 459/587] Update arrow to 14.0.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e09be1d79805..c9586e6e0f6d 100644 --- a/pom.xml +++ b/pom.xml @@ -176,7 +176,7 @@ 1.10.2 239 4.13.1 - 14.0.0 + 14.0.1 1.11.3 1.12.595 4.17.0 From b6efa84d9407c62e3f58dbaa3ba188992b5a085d Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Fri, 24 Nov 2023 19:11:33 +0100 Subject: [PATCH 460/587] Update commons-lang3 to 3.14.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c9586e6e0f6d..92931edceb5b 100644 --- a/pom.xml +++ b/pom.xml @@ -1740,7 +1740,7 @@ org.apache.commons commons-lang3 - 3.13.0 + 3.14.0 From 997c047363c5ab24f6adc832222b4cf3262d3440 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Fri, 24 Nov 2023 19:11:57 +0100 Subject: [PATCH 461/587] Update commons-compress to 1.25.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 92931edceb5b..c8a58c681f04 100644 --- a/pom.xml +++ b/pom.xml @@ -1734,7 +1734,7 @@ org.apache.commons commons-compress - 1.24.0 + 1.25.0 From 1d25ebfe139f3c4866ccc9564fa6af44c157a169 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Fri, 24 Nov 2023 19:13:15 +0100 Subject: [PATCH 462/587] Update byte-buddy to 1.14.10 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index c8a58c681f04..78f3ec5d7342 100644 --- a/pom.xml +++ b/pom.xml @@ -1627,11 +1627,11 @@ 4.13.2 - + net.bytebuddy byte-buddy - 1.14.9 + 1.14.10 From c39e94ec228f29056807806dd4d3be30a4171bad Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Fri, 24 Nov 2023 19:13:58 +0100 Subject: [PATCH 463/587] Update postgresql to 42.7.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 78f3ec5d7342..1cde58ca6d30 100644 --- a/pom.xml +++ b/pom.xml @@ -2044,7 +2044,7 @@ org.postgresql postgresql - 42.6.0 + 42.7.0 From 586bb840ab3e7db6fc9842dd3c4a0f888abf2058 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Fri, 24 Nov 2023 19:17:18 +0100 Subject: [PATCH 464/587] Update jetbrains annotations to 24.1.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1cde58ca6d30..ff10ad0159bb 100644 --- a/pom.xml +++ b/pom.xml @@ -2002,7 +2002,7 @@ org.jetbrains annotations - 19.0.0 + 24.1.0 From 461f87d793e624ca7aeb8d3aaa53a2e9537679da Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Fri, 24 Nov 2023 19:20:56 +0100 Subject: [PATCH 465/587] Upgrade swagger to 2.2.19 --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index ff10ad0159bb..6ca05282af2e 100644 --- a/pom.xml +++ b/pom.xml @@ -196,7 +196,7 @@ ${dep.airlift.version} 1.13.1 3.25.1 - 1.6.12 + 2.2.19 2.1.1 2.0.62.Final 201 @@ -937,14 +937,14 @@ - io.swagger + io.swagger.core.v3 swagger-annotations ${dep.swagger.version} - io.swagger + io.swagger.core.v3 swagger-core ${dep.swagger.version} From de0f1c11dfe435d622a349b1b3204c17ec93306b Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Fri, 24 Nov 2023 19:22:23 +0100 Subject: [PATCH 466/587] Upgrade jedis to 5.1.0 --- plugin/trino-redis/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/trino-redis/pom.xml b/plugin/trino-redis/pom.xml index 4142d0de508b..7b79892ff96c 100644 --- a/plugin/trino-redis/pom.xml +++ b/plugin/trino-redis/pom.xml @@ -87,7 +87,7 @@ redis.clients jedis - 5.0.2 + 5.1.0 From a878d3778d7836a2cb9b94321b2a34101f5c8e25 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Fri, 24 Nov 2023 19:26:36 +0100 Subject: [PATCH 467/587] Update confluent platform to 7.5.1 and wire schema to 4.8.0 --- .../confluent/ClassLoaderSafeSchemaRegistryClient.java | 9 +++++++++ .../plugin/kafka/schema/confluent/ConfluentModule.java | 9 +++++++++ pom.xml | 4 ++-- .../tests/product/kafka/TestKafkaAvroReadsSmokeTest.java | 2 +- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/schema/confluent/ClassLoaderSafeSchemaRegistryClient.java b/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/schema/confluent/ClassLoaderSafeSchemaRegistryClient.java index b564a62a08c5..210d32894220 100644 --- a/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/schema/confluent/ClassLoaderSafeSchemaRegistryClient.java +++ b/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/schema/confluent/ClassLoaderSafeSchemaRegistryClient.java @@ -588,4 +588,13 @@ public String setMode(String mode, String subject, boolean force) return delegate.setMode(mode, subject, force); } } + + @Override + public void close() + throws IOException + { + try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(classLoader)) { + delegate.close(); + } + } } diff --git a/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/schema/confluent/ConfluentModule.java b/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/schema/confluent/ConfluentModule.java index e66d0752d1df..a3003c135e96 100644 --- a/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/schema/confluent/ConfluentModule.java +++ b/plugin/trino-kafka/src/main/java/io/trino/plugin/kafka/schema/confluent/ConfluentModule.java @@ -57,7 +57,9 @@ import io.trino.spi.HostAddress; import io.trino.spi.TrinoException; import io.trino.spi.type.TypeManager; +import jakarta.annotation.PreDestroy; +import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Optional; @@ -136,6 +138,13 @@ public static SchemaRegistryClient createSchemaRegistryClient( classLoader); } + @PreDestroy + public void destroy(SchemaRegistryClient client) + throws IOException + { + client.close(); + } + private class ConfluentDecoderModule implements Module { diff --git a/pom.xml b/pom.xml index 6ca05282af2e..dd42024d8045 100644 --- a/pom.xml +++ b/pom.xml @@ -180,7 +180,7 @@ 1.11.3 1.12.595 4.17.0 - 7.4.1 + 7.5.1 87 1.21 1.0.8 @@ -200,7 +200,7 @@ 2.1.1 2.0.62.Final 201 - 4.5.0 + 4.8.0 diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/kafka/TestKafkaAvroReadsSmokeTest.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/kafka/TestKafkaAvroReadsSmokeTest.java index 438990ec3d20..fdb089e22766 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/kafka/TestKafkaAvroReadsSmokeTest.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/kafka/TestKafkaAvroReadsSmokeTest.java @@ -18,9 +18,9 @@ import com.google.common.primitives.Ints; import io.airlift.units.Duration; import io.confluent.kafka.schemaregistry.ParsedSchema; -import io.confluent.kafka.schemaregistry.SchemaEntity; import io.confluent.kafka.schemaregistry.client.rest.entities.Metadata; import io.confluent.kafka.schemaregistry.client.rest.entities.RuleSet; +import io.confluent.kafka.schemaregistry.client.rest.entities.SchemaEntity; import io.confluent.kafka.schemaregistry.client.rest.entities.SchemaReference; import io.confluent.kafka.schemaregistry.client.rest.exceptions.RestClientException; import io.trino.tempto.ProductTest; From 62b0372173ec96b46f19014e37bd6edda116b71e Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Fri, 24 Nov 2023 19:28:51 +0100 Subject: [PATCH 468/587] Update metrics-core to 4.2.22 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index dd42024d8045..9ef2d304d3fe 100644 --- a/pom.xml +++ b/pom.xml @@ -827,7 +827,7 @@ io.dropwizard.metrics metrics-core - 4.2.21 + 4.2.22 From 43c79da13945ff663c8aed1525790bcc692c356c Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Fri, 24 Nov 2023 19:32:14 +0100 Subject: [PATCH 469/587] Update exec-maven-plugin to 3.1.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9ef2d304d3fe..7316db3d5a4d 100644 --- a/pom.xml +++ b/pom.xml @@ -2192,7 +2192,7 @@ org.codehaus.mojo exec-maven-plugin - 3.1.0 + 3.1.1 From fc390281a2fe3f0fce8f2685c88dd10617c09dec Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Fri, 24 Nov 2023 19:33:40 +0100 Subject: [PATCH 470/587] Update takari-lifecycle-plugin to 2.1.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7316db3d5a4d..28513842b13d 100644 --- a/pom.xml +++ b/pom.xml @@ -197,7 +197,7 @@ 1.13.1 3.25.1 2.2.19 - 2.1.1 + 2.1.2 2.0.62.Final 201 4.8.0 From 0570702a324471e17faea0b6765cace2e3721cb1 Mon Sep 17 00:00:00 2001 From: Manfred Moser Date: Thu, 23 Nov 2023 14:31:10 -0800 Subject: [PATCH 471/587] Correct secret usage with dynamic catalogs --- docs/src/main/sphinx/sql/create-catalog.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/main/sphinx/sql/create-catalog.md b/docs/src/main/sphinx/sql/create-catalog.md index 727e96d7e0e1..665f99666e2e 100644 --- a/docs/src/main/sphinx/sql/create-catalog.md +++ b/docs/src/main/sphinx/sql/create-catalog.md @@ -73,8 +73,8 @@ WITH ( ``` This example assumes that the `POSTGRES_USER` and `POSTGRES_PASSWORD` -environmental variables are set as [secrets](/security/secrets) on the -coordinator node. +environmental variables are set as [secrets](/security/secrets) on all nodes of +the cluster. ## See also From b825b965abc3b1301c50495ad9fbf832e09cbd12 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Mon, 27 Nov 2023 15:09:11 +0100 Subject: [PATCH 472/587] Handle S3 paths where bucket name contains "dot digit" `HadoopPaths` compatibility layer was failing when S3 bucket name contains a dot followed by a digit: ``` java.net.URISyntaxException: Illegal character in hostname at index 10: s3://test.123/abc//xyz.csv#abc//xyz.csv at java.base/java.net.URI$Parser.fail(URI.java:2974) at java.base/java.net.URI$Parser.parseHostname(URI.java:3517) at java.base/java.net.URI$Parser.parseServer(URI.java:3358) at java.base/java.net.URI$Parser.parseAuthority(URI.java:3277) at java.base/java.net.URI$Parser.parseHierarchical(URI.java:3219) at java.base/java.net.URI$Parser.parse(URI.java:3175) at java.base/java.net.URI.(URI.java:708) at java.base/java.net.URI.(URI.java:809) at io.trino.filesystem.hdfs.HadoopPaths.toPathEncodedUri(HadoopPaths.java:46) ``` Using a different `URI` constructor avoids the problem. --- .../java/io/trino/filesystem/hdfs/HadoopPaths.java | 1 + .../test/java/io/trino/hdfs/s3/TestS3HadoopPaths.java | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/lib/trino-hdfs/src/main/java/io/trino/filesystem/hdfs/HadoopPaths.java b/lib/trino-hdfs/src/main/java/io/trino/filesystem/hdfs/HadoopPaths.java index 0e42a5fe4faf..28bc715c647e 100644 --- a/lib/trino-hdfs/src/main/java/io/trino/filesystem/hdfs/HadoopPaths.java +++ b/lib/trino-hdfs/src/main/java/io/trino/filesystem/hdfs/HadoopPaths.java @@ -42,6 +42,7 @@ private static URI toPathEncodedUri(Location location) location.scheme().orElse(null), location.host().orElse(null), "/" + location.path(), + null, location.path()); } catch (URISyntaxException e) { diff --git a/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestS3HadoopPaths.java b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestS3HadoopPaths.java index 7e0435cab4a9..ba4265ff915d 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestS3HadoopPaths.java +++ b/lib/trino-hdfs/src/test/java/io/trino/hdfs/s3/TestS3HadoopPaths.java @@ -68,4 +68,14 @@ public void testS3NonCanonicalPathWithInvalidUriEscape() .extracting(TrinoS3FileSystem::keyFromPath) .isEqualTo("abc%xyz//test"); } + + @Test + public void testS3NonCanonicalPathWithDotDigitBucketName() + { + assertThat(hadoopPath(Location.of("s3://test.123/abc//xyz.csv"))) + .isEqualTo(new Path(URI.create("s3://test.123/abc/xyz.csv#abc//xyz.csv"))) + .hasToString("s3://test.123/abc/xyz.csv#abc//xyz.csv") + .extracting(TrinoS3FileSystem::keyFromPath) + .isEqualTo("abc//xyz.csv"); + } } From ca96069a0d7c7d960bf51f04f5fc221f135482e3 Mon Sep 17 00:00:00 2001 From: Karol Sobczak Date: Mon, 27 Nov 2023 17:15:44 +0100 Subject: [PATCH 473/587] Convert left correlated join to inner join during transformation It's possible to simplify outer join to inner join when subquery side is at least scalar. This makes sure dynamic filters are propagated to left join side and more efficient (cross join) implementaiton is used. --- .../TransformUncorrelatedSubqueryToJoin.java | 13 +++++++--- ...stTransformUncorrelatedSubqueryToJoin.java | 25 ++++++++++++++++--- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/TransformUncorrelatedSubqueryToJoin.java b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/TransformUncorrelatedSubqueryToJoin.java index ea7a5458e893..987d8946f5fd 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/TransformUncorrelatedSubqueryToJoin.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/TransformUncorrelatedSubqueryToJoin.java @@ -21,6 +21,7 @@ import io.trino.matching.Pattern; import io.trino.spi.type.Type; import io.trino.sql.planner.Symbol; +import io.trino.sql.planner.iterative.Lookup; import io.trino.sql.planner.iterative.Rule; import io.trino.sql.planner.plan.Assignments; import io.trino.sql.planner.plan.CorrelatedJoinNode; @@ -36,6 +37,7 @@ import static com.google.common.base.Preconditions.checkState; import static io.trino.matching.Pattern.empty; import static io.trino.sql.analyzer.TypeSignatureTranslator.toSqlType; +import static io.trino.sql.planner.optimizations.QueryCardinalityUtil.extractCardinality; import static io.trino.sql.planner.plan.CorrelatedJoinNode.Type.FULL; import static io.trino.sql.planner.plan.CorrelatedJoinNode.Type.INNER; import static io.trino.sql.planner.plan.CorrelatedJoinNode.Type.LEFT; @@ -64,7 +66,8 @@ public Result apply(CorrelatedJoinNode correlatedJoinNode, Captures captures, Co return Result.ofPlanNode(rewriteToJoin( correlatedJoinNode, correlatedJoinNode.getType().toJoinNodeType(), - correlatedJoinNode.getFilter())); + correlatedJoinNode.getFilter(), + context.getLookup())); } checkState( @@ -79,7 +82,7 @@ public Result apply(CorrelatedJoinNode correlatedJoinNode, Captures captures, Co else { type = JoinNode.Type.LEFT; } - JoinNode joinNode = rewriteToJoin(correlatedJoinNode, type, TRUE_LITERAL); + JoinNode joinNode = rewriteToJoin(correlatedJoinNode, type, TRUE_LITERAL, context.getLookup()); if (correlatedJoinNode.getFilter().equals(TRUE_LITERAL)) { return Result.ofPlanNode(joinNode); @@ -109,8 +112,12 @@ public Result apply(CorrelatedJoinNode correlatedJoinNode, Captures captures, Co return Result.empty(); } - private JoinNode rewriteToJoin(CorrelatedJoinNode parent, JoinNode.Type type, Expression filter) + private JoinNode rewriteToJoin(CorrelatedJoinNode parent, JoinNode.Type type, Expression filter, Lookup lookup) { + if (type == JoinNode.Type.LEFT && extractCardinality(parent.getSubquery(), lookup).isAtLeastScalar() && filter.equals(TRUE_LITERAL)) { + // input rows will always be matched against subquery rows + type = JoinNode.Type.INNER; + } return new JoinNode( parent.getId(), type, diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestTransformUncorrelatedSubqueryToJoin.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestTransformUncorrelatedSubqueryToJoin.java index f88bde2a1f88..38854596bc1e 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestTransformUncorrelatedSubqueryToJoin.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestTransformUncorrelatedSubqueryToJoin.java @@ -25,7 +25,6 @@ import static io.trino.sql.planner.assertions.PlanMatchPattern.project; import static io.trino.sql.planner.assertions.PlanMatchPattern.values; import static io.trino.sql.planner.plan.CorrelatedJoinNode.Type.FULL; -import static io.trino.sql.planner.plan.CorrelatedJoinNode.Type.INNER; import static io.trino.sql.planner.plan.CorrelatedJoinNode.Type.LEFT; import static io.trino.sql.planner.plan.CorrelatedJoinNode.Type.RIGHT; import static io.trino.sql.planner.plan.JoinNode.Type; @@ -36,6 +35,26 @@ public class TestTransformUncorrelatedSubqueryToJoin extends BaseRuleTest { + @Test + public void testRewriteLeftCorrelatedJoinWithScalarSubquery() + { + tester().assertThat(new TransformUncorrelatedSubqueryToJoin()) + .on(p -> { + Symbol a = p.symbol("a"); + Symbol b = p.symbol("b"); + return p.correlatedJoin( + emptyList(), + p.values(a), + LEFT, + TRUE_LITERAL, + p.values(1, b)); + }) + .matches( + join(Type.INNER, builder -> builder + .left(values("a")) + .right(values("b")))); + } + @Test public void testRewriteInnerCorrelatedJoin() { @@ -46,7 +65,7 @@ public void testRewriteInnerCorrelatedJoin() return p.correlatedJoin( emptyList(), p.values(a), - INNER, + LEFT, new ComparisonExpression( GREATER_THAN, b.toSymbolReference(), @@ -54,7 +73,7 @@ public void testRewriteInnerCorrelatedJoin() p.values(b)); }) .matches( - join(Type.INNER, builder -> builder + join(Type.LEFT, builder -> builder .filter("b > a") .left(values("a")) .right(values("b")))); From 1378fbe49de3930a4fa8452c8090c0df9035f620 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Fri, 24 Nov 2023 16:56:07 +0100 Subject: [PATCH 474/587] Add or update code comments in EventDrivenFaultTolerantQueryScheduler --- .../EventDrivenFaultTolerantQueryScheduler.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java index 069b0e638283..4e6b7a05b131 100644 --- a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java +++ b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java @@ -286,7 +286,7 @@ public synchronized void start() } }); - // when query is done or any time a stage completes, attempt to transition query to "final query info ready" + // when query is done, attempt to transition query to "final query info ready" queryStateMachine.addStateChangeListener(state -> { if (!state.isDone()) { return; @@ -629,6 +629,8 @@ public void run() Optional failure = Optional.empty(); try { + // schedule() is the main logic, but expensive, so we do not want to call it after every event. + // Process events for some time (measured by schedulingDelayer) before invoking schedule() next time. if (schedule()) { while (processEvents()) { if (schedulingDelayer.getRemainingDelayInMillis() > 0) { @@ -672,6 +674,9 @@ private Optional closeAndAddSuppressed(Optional existingFa return existingFailure; } + /** + * @return whether processing should continue + */ private boolean processEvents() { try { @@ -705,6 +710,9 @@ private boolean processEvents() } } + /** + * @return whether processing should continue + */ private boolean schedule() { if (checkComplete()) { @@ -1452,6 +1460,7 @@ else if (taskState == TaskState.FAILED) { List replacementTasks = stageExecution.taskFailed(taskId, failureInfo, taskStatus); replacementTasks.forEach(schedulingQueue::addOrUpdate); + // When tasks fail for some intermittent reason, delay scheduling retries if (shouldDelayScheduling(failureInfo.getErrorCode())) { schedulingDelayer.startOrProlongDelayIfNecessary(); scheduledExecutorService.schedule(() -> eventQueue.add(Event.WAKE_UP), schedulingDelayer.getRemainingDelayInMillis(), MILLISECONDS); From 56323f7a56f6050a223ff29a99709df988992375 Mon Sep 17 00:00:00 2001 From: Anu Sudarsan Date: Mon, 27 Nov 2023 11:21:15 -0500 Subject: [PATCH 475/587] Fix diststyle check in redshift test to include AUTO(KEY) kind Redshift seems to have introduced a third kind of AUTO diststyle , so include that in the check where we evaluate the current diststyle for the table from Redshift --- .../io/trino/plugin/redshift/TestRedshiftConnectorTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftConnectorTest.java b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftConnectorTest.java index cebbc170e7d6..2e709b6adf2f 100644 --- a/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftConnectorTest.java +++ b/plugin/trino-redshift/src/test/java/io/trino/plugin/redshift/TestRedshiftConnectorTest.java @@ -551,8 +551,8 @@ private static void copyWithDistStyle(String sourceTableName, String destTableNa .mapTo(Long.class) .findOne(); - // 10 means AUTO(ALL) and 11 means AUTO(EVEN). See https://docs.aws.amazon.com/redshift/latest/dg/r_PG_CLASS_INFO.html. - return currentDistStyle.isPresent() && (currentDistStyle.get() == 10 || currentDistStyle.get() == 11); + // 10 means AUTO(ALL), 11 means AUTO(EVEN) and 12 means AUTO(KEY). See https://docs.aws.amazon.com/redshift/latest/dg/r_PG_CLASS_INFO.html. + return currentDistStyle.isPresent() && (currentDistStyle.get() == 10 || currentDistStyle.get() == 11 || currentDistStyle.get() == 12); }); if (!isDistStyleAuto) { executeInRedshift("ALTER TABLE " + destTableName + " ALTER DISTSTYLE " + distStyle); From 73e9775fe459064116f2a18e4518dd48a1c5a81a Mon Sep 17 00:00:00 2001 From: Manfred Moser Date: Tue, 21 Nov 2023 15:31:39 -0800 Subject: [PATCH 476/587] Remove misleading header in Iceberg docs --- docs/src/main/sphinx/connector/iceberg.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/docs/src/main/sphinx/connector/iceberg.md b/docs/src/main/sphinx/connector/iceberg.md index 14e341256d57..69c8c3cb81c7 100644 --- a/docs/src/main/sphinx/connector/iceberg.md +++ b/docs/src/main/sphinx/connector/iceberg.md @@ -1407,13 +1407,8 @@ CREATE TABLE example_table ( When trying to insert/update data in the table, the query fails if trying to set `NULL` value on a column having the `NOT NULL` constraint. -### View management - -Trino allows reading from Iceberg materialized views. - (iceberg-materialized-views)= - -#### Materialized views +### Materialized views The Iceberg connector supports {ref}`sql-materialized-view-management`. In the underlying system, each materialized view consists of a view definition and an From db8aa6b96e37aed76eecbd831ebde4d829926aa3 Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Tue, 28 Nov 2023 09:58:29 +0900 Subject: [PATCH 477/587] Fix typo in pom.xml --- plugin/trino-cassandra/pom.xml | 2 +- pom.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin/trino-cassandra/pom.xml b/plugin/trino-cassandra/pom.xml index e125d36ecf2b..b9d33987cf84 100644 --- a/plugin/trino-cassandra/pom.xml +++ b/plugin/trino-cassandra/pom.xml @@ -27,7 +27,7 @@ com.datastax.oss java-driver-query-builder - ${dep.casandra.version} + ${dep.cassandra.version} com.github.spotbugs diff --git a/pom.xml b/pom.xml index 28513842b13d..137b4ffdaea2 100644 --- a/pom.xml +++ b/pom.xml @@ -179,7 +179,7 @@ 14.0.1 1.11.3 1.12.595 - 4.17.0 + 4.17.0 7.5.1 87 1.21 @@ -454,7 +454,7 @@ com.datastax.oss java-driver-core - ${dep.casandra.version} + ${dep.cassandra.version} org.ow2.asm From 8771e14ae7136009ea14b17e44a579e6907c426e Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Tue, 28 Nov 2023 09:15:07 +0900 Subject: [PATCH 478/587] Fix flaky TestIcebergFileMetastoreCreateTableFailure --- .../file/TestIcebergFileMetastoreCreateTableFailure.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestIcebergFileMetastoreCreateTableFailure.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestIcebergFileMetastoreCreateTableFailure.java index 47761caea762..373b44231e54 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestIcebergFileMetastoreCreateTableFailure.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestIcebergFileMetastoreCreateTableFailure.java @@ -28,6 +28,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.nio.file.Files; import java.nio.file.Path; @@ -43,8 +44,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; @TestInstance(PER_CLASS) +@Execution(SAME_THREAD) // testException is shared mutable state public class TestIcebergFileMetastoreCreateTableFailure extends AbstractTestQueryFramework { From 282be5355e7d5273104d7f802ca41f901c56d3fc Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Tue, 28 Nov 2023 12:06:51 +0900 Subject: [PATCH 479/587] Use CassandraQueryWaitStrategy in CassandraServer --- plugin/trino-cassandra/pom.xml | 6 +++ .../plugin/cassandra/CassandraServer.java | 50 +++++-------------- .../TestDatastaxConnectorSmokeTest.java | 3 +- .../src/test/resources/cassandra-dse.yaml | 2 - .../test/resources/cu-cassandra-latest.yaml | 1 - .../src/test/resources/cu-cassandra.yaml | 2 - 6 files changed, 21 insertions(+), 43 deletions(-) diff --git a/plugin/trino-cassandra/pom.xml b/plugin/trino-cassandra/pom.xml index b9d33987cf84..52bac96f2809 100644 --- a/plugin/trino-cassandra/pom.xml +++ b/plugin/trino-cassandra/pom.xml @@ -240,6 +240,12 @@ test + + org.testcontainers + cassandra + test + + org.testcontainers testcontainers diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/CassandraServer.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/CassandraServer.java index 7c2ee1b906be..611efa200a9d 100644 --- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/CassandraServer.java +++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/CassandraServer.java @@ -18,20 +18,19 @@ import com.datastax.oss.driver.api.core.ProtocolVersion; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.config.ProgrammaticDriverConfigLoaderBuilder; -import com.datastax.oss.driver.api.core.cql.ResultSet; -import com.datastax.oss.driver.api.core.cql.Row; import com.google.common.collect.ImmutableMap; import com.google.common.io.Resources; import io.airlift.json.JsonCodec; import io.airlift.log.Logger; import io.airlift.units.Duration; import io.trino.testing.ResourcePresence; -import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.CassandraContainer; +import org.testcontainers.containers.wait.CassandraQueryWaitStrategy; +import org.testcontainers.utility.DockerImageName; import java.io.Closeable; import java.io.File; import java.io.IOException; -import java.net.InetSocketAddress; import java.nio.file.Path; import java.util.List; import java.util.Map; @@ -52,7 +51,7 @@ import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.assertj.core.api.Assertions.assertThat; +import static org.testcontainers.containers.CassandraContainer.CQL_PORT; import static org.testcontainers.utility.MountableFile.forHostPath; public class CassandraServer @@ -60,11 +59,9 @@ public class CassandraServer { private static final Logger log = Logger.get(CassandraServer.class); - private static final int PORT = 9142; - private static final Duration REFRESH_SIZE_ESTIMATES_TIMEOUT = new Duration(1, MINUTES); - private final GenericContainer dockerContainer; + private final CassandraContainer dockerContainer; private final CassandraSession session; public CassandraServer() @@ -76,18 +73,18 @@ public CassandraServer() public CassandraServer(String imageName, String configFileName) throws Exception { - this(imageName, ImmutableMap.of(), "/etc/cassandra/cassandra.yaml", configFileName); + this(DockerImageName.parse(imageName), ImmutableMap.of(), "/etc/cassandra/cassandra.yaml", configFileName); } - public CassandraServer(String imageName, Map environmentVariables, String configPath, String configFileName) + public CassandraServer(DockerImageName imageName, Map environmentVariables, String configPath, String configFileName) throws Exception { log.debug("Starting cassandra..."); - this.dockerContainer = new GenericContainer<>(imageName) - .withExposedPorts(PORT) + this.dockerContainer = new CassandraContainer<>(imageName) .withCopyFileToContainer(forHostPath(prepareCassandraYaml(configFileName)), configPath) .withEnv(environmentVariables) + .waitingFor(new CassandraQueryWaitStrategy()) .withStartupTimeout(java.time.Duration.ofMinutes(10)); this.dockerContainer.start(); @@ -99,27 +96,15 @@ public CassandraServer(String imageName, Map environmentVariable driverConfigLoaderBuilder.withStringList(METADATA_SCHEMA_REFRESHED_KEYSPACES, List.of()); CqlSessionBuilder cqlSessionBuilder = CqlSession.builder() - .withApplicationName("TestCluster") - .addContactPoint(new InetSocketAddress(this.dockerContainer.getHost(), this.dockerContainer.getMappedPort(PORT))) - .withLocalDatacenter("datacenter1") + .addContactPoint(dockerContainer.getContactPoint()) + .withLocalDatacenter(dockerContainer.getLocalDatacenter()) .withConfigLoader(driverConfigLoaderBuilder.build()); - CassandraSession session = new CassandraSession( + this.session = new CassandraSession( CASSANDRA_TYPE_MANAGER, JsonCodec.listJsonCodec(ExtraColumnMetadata.class), cqlSessionBuilder::build, new Duration(1, MINUTES)); - - try { - checkConnectivity(session); - } - catch (RuntimeException e) { - session.close(); - this.dockerContainer.stop(); - throw e; - } - - this.session = session; } private static String prepareCassandraYaml(String fileName) @@ -152,16 +137,7 @@ public String getHost() public int getPort() { - return dockerContainer.getMappedPort(PORT); - } - - private static void checkConnectivity(CassandraSession session) - { - ResultSet result = session.execute("SELECT release_version FROM system.local"); - List rows = result.all(); - assertThat(rows.size()).isEqualTo(1); - String version = rows.get(0).getString(0); - log.debug("Cassandra version: %s", version); + return dockerContainer.getMappedPort(CQL_PORT); } public void refreshSizeEstimates(String keyspace, String table) diff --git a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestDatastaxConnectorSmokeTest.java b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestDatastaxConnectorSmokeTest.java index a784aeee5f2f..032639e31995 100644 --- a/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestDatastaxConnectorSmokeTest.java +++ b/plugin/trino-cassandra/src/test/java/io/trino/plugin/cassandra/TestDatastaxConnectorSmokeTest.java @@ -15,6 +15,7 @@ import com.google.common.collect.ImmutableMap; import io.trino.testing.QueryRunner; +import org.testcontainers.utility.DockerImageName; import java.sql.Timestamp; import java.util.Map; @@ -30,7 +31,7 @@ protected QueryRunner createQueryRunner() throws Exception { CassandraServer server = closeAfterClass(new CassandraServer( - "datastax/dse-server:6.8.25", + DockerImageName.parse("datastax/dse-server:6.8.25").asCompatibleSubstituteFor("cassandra"), Map.of( "DS_LICENSE", "accept", "DC", "datacenter1"), diff --git a/plugin/trino-cassandra/src/test/resources/cassandra-dse.yaml b/plugin/trino-cassandra/src/test/resources/cassandra-dse.yaml index d92c3c73d413..d9e3e6976cd0 100644 --- a/plugin/trino-cassandra/src/test/resources/cassandra-dse.yaml +++ b/plugin/trino-cassandra/src/test/resources/cassandra-dse.yaml @@ -10,8 +10,6 @@ seed_provider: parameters: - seeds: "127.0.0.1" -native_transport_port: 9142 - read_request_timeout_in_ms: 30000 range_request_timeout_in_ms: 30000 write_request_timeout_in_ms: 30000 diff --git a/plugin/trino-cassandra/src/test/resources/cu-cassandra-latest.yaml b/plugin/trino-cassandra/src/test/resources/cu-cassandra-latest.yaml index 5123f75c0f5e..9013e04645c9 100644 --- a/plugin/trino-cassandra/src/test/resources/cu-cassandra-latest.yaml +++ b/plugin/trino-cassandra/src/test/resources/cu-cassandra-latest.yaml @@ -22,7 +22,6 @@ concurrent_counter_writes: 2 concurrent_materialized_view_writes: 2 listen_address: 127.0.0.1 -native_transport_port: 9142 rpc_address: localhost broadcast_rpc_address: localhost diff --git a/plugin/trino-cassandra/src/test/resources/cu-cassandra.yaml b/plugin/trino-cassandra/src/test/resources/cu-cassandra.yaml index 1bb73b383190..122daa13db27 100644 --- a/plugin/trino-cassandra/src/test/resources/cu-cassandra.yaml +++ b/plugin/trino-cassandra/src/test/resources/cu-cassandra.yaml @@ -260,8 +260,6 @@ ssl_storage_port: 7011 listen_address: 127.0.0.1 start_native_transport: true -# port for the CQL native transport to listen for clients on -native_transport_port: 9142 # Whether to start the thrift rpc server. start_rpc: false From 6758b791f7ea5528892f711bcb5858a664cd438c Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sun, 26 Nov 2023 23:17:53 -0800 Subject: [PATCH 480/587] Fix Javadoc for file system listDirectories --- .../src/main/java/io/trino/filesystem/TrinoFileSystem.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/trino-filesystem/src/main/java/io/trino/filesystem/TrinoFileSystem.java b/lib/trino-filesystem/src/main/java/io/trino/filesystem/TrinoFileSystem.java index 97babd4aee16..9692154a39d0 100644 --- a/lib/trino-filesystem/src/main/java/io/trino/filesystem/TrinoFileSystem.java +++ b/lib/trino-filesystem/src/main/java/io/trino/filesystem/TrinoFileSystem.java @@ -194,8 +194,8 @@ void renameDirectory(Location source, Location target) /** * Lists all directories that are direct descendants of the specified directory. - * The location can be empty, which lists all directories at the root of the file system, - * otherwise the location otherwise the location must end with a slash. + * If the path is empty, all directories at the root of the file system are returned. + * Otherwise, the path must end with a slash. * If the location does not exist, an empty set is returned. *

    * For hierarchical file systems, if the path is not a directory, an exception is raised. From fc37834ad10b30402393e22cb5c06c245b654ab2 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sun, 26 Nov 2023 23:22:45 -0800 Subject: [PATCH 481/587] Add clarifying comment to S3 trailing slash test --- .../java/io/trino/filesystem/s3/AbstractTestS3FileSystem.java | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/AbstractTestS3FileSystem.java b/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/AbstractTestS3FileSystem.java index 6704cccc0bcc..b755f5a403dc 100644 --- a/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/AbstractTestS3FileSystem.java +++ b/lib/trino-filesystem-s3/src/test/java/io/trino/filesystem/s3/AbstractTestS3FileSystem.java @@ -189,6 +189,7 @@ void testExistingFileWithTrailingSlash() assertThat(fileSystem.listDirectories(getRootLocation())).containsExactly(data); assertThat(fileSystem.listDirectories(data)).containsExactly(data.appendPath("file/")); + // blobs ending in slash are invisible to S3FileSystem and will not be deleted fileSystem.deleteDirectory(data); assertThat(fileSystem.listDirectories(getRootLocation())).containsExactly(data); From 8960933077d4d14c72c69906ca55f9545519b1d9 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sun, 26 Nov 2023 22:47:40 -0800 Subject: [PATCH 482/587] Flip node role logging in DistributedQueryRunner --- .../src/main/java/io/trino/testing/DistributedQueryRunner.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/trino-testing/src/main/java/io/trino/testing/DistributedQueryRunner.java b/testing/trino-testing/src/main/java/io/trino/testing/DistributedQueryRunner.java index 453ed0219004..693b9c0bd7e8 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/DistributedQueryRunner.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/DistributedQueryRunner.java @@ -283,7 +283,7 @@ private static TestingTrinoServer createTestingTrinoServer( .build(); String nodeRole = coordinator ? "coordinator" : "worker"; - log.info("Created %s TestingTrinoServer in %s: %s", nodeRole, nanosSince(start).convertToMostSuccinctTimeUnit(), server.getBaseUrl()); + log.info("Created TestingTrinoServer %s in %s: %s", nodeRole, nanosSince(start).convertToMostSuccinctTimeUnit(), server.getBaseUrl()); return server; } From 8a281eac6ccf486689bca93928fd2fb82f5d61f4 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sun, 26 Nov 2023 22:26:41 -0800 Subject: [PATCH 483/587] Cleanup logging for Hadoop containers --- .../test/java/io/trino/filesystem/hdfs/Hadoop.java | 2 +- .../io/trino/plugin/hive/containers/HiveHadoop.java | 4 +--- .../testing/containers/PrintingLogConsumer.java | 13 +++++-------- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/lib/trino-hdfs/src/test/java/io/trino/filesystem/hdfs/Hadoop.java b/lib/trino-hdfs/src/test/java/io/trino/filesystem/hdfs/Hadoop.java index 8b7b3ec66d0c..d2049adfba90 100644 --- a/lib/trino-hdfs/src/test/java/io/trino/filesystem/hdfs/Hadoop.java +++ b/lib/trino-hdfs/src/test/java/io/trino/filesystem/hdfs/Hadoop.java @@ -52,7 +52,7 @@ public Hadoop() protected void setupContainer() { super.setupContainer(); - withLogConsumer(new PrintingLogConsumer("hadoop | ")); + withLogConsumer(new PrintingLogConsumer("Hadoop")); withRunCommand(List.of("bash", "-e", "-c", """ rm /etc/supervisord.d/{hive*,mysql*,socks*,sshd*,yarn*}.conf supervisord -c /etc/supervisord.conf diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/containers/HiveHadoop.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/containers/HiveHadoop.java index 7959030b8661..3e54a41b5c53 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/containers/HiveHadoop.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/containers/HiveHadoop.java @@ -26,8 +26,6 @@ import java.util.Optional; import java.util.Set; -import static java.lang.String.format; - public class HiveHadoop extends BaseTestContainer { @@ -73,7 +71,7 @@ protected void setupContainer() ImmutableList.of( "/bin/bash", runCmd)); - withLogConsumer(new PrintingLogConsumer(format("%-20s| ", "hadoop"))); + withLogConsumer(new PrintingLogConsumer("Hadoop")); } @Override diff --git a/testing/trino-testing-containers/src/main/java/io/trino/testing/containers/PrintingLogConsumer.java b/testing/trino-testing-containers/src/main/java/io/trino/testing/containers/PrintingLogConsumer.java index 7ef003707185..fc25d7c39986 100644 --- a/testing/trino-testing-containers/src/main/java/io/trino/testing/containers/PrintingLogConsumer.java +++ b/testing/trino-testing-containers/src/main/java/io/trino/testing/containers/PrintingLogConsumer.java @@ -17,19 +17,16 @@ import org.testcontainers.containers.output.BaseConsumer; import org.testcontainers.containers.output.OutputFrame; -import static java.util.Objects.requireNonNull; import static org.testcontainers.containers.output.OutputFrame.OutputType.END; public final class PrintingLogConsumer extends BaseConsumer { - private static final Logger log = Logger.get(PrintingLogConsumer.class); + private final Logger log; - private final String prefix; - - public PrintingLogConsumer(String prefix) + public PrintingLogConsumer(String name) { - this.prefix = requireNonNull(prefix, "prefix is null"); + this.log = Logger.get("container." + name); } @Override @@ -41,10 +38,10 @@ public void accept(OutputFrame outputFrame) // remove new line characters String message = outputFrame.getUtf8String().replaceAll("\\r?\\n?$", ""); if (!message.isEmpty() || outputFrame.getType() != END) { - log.info("%s%s", prefix, message); + log.info(message); } if (outputFrame.getType() == END) { - log.info("%s(exited)", prefix); + log.info("(exited)"); } } } From 38a5e269d9c40c6b211aa3851d2b7bbc280819b6 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sun, 26 Nov 2023 22:41:54 -0800 Subject: [PATCH 484/587] Make JUnit configuration parameters work in IDEA --- pom.xml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 137b4ffdaea2..f77a9541ab3f 100644 --- a/pom.xml +++ b/pom.xml @@ -2671,13 +2671,15 @@ org.apache.maven.plugins maven-surefire-plugin - - junit.jupiter.execution.timeout.thread.mode.default = SEPARATE_THREAD - junit.jupiter.extensions.autodetection.enabled = true - junit.jupiter.execution.parallel.enabled = true - junit.jupiter.execution.parallel.mode.default = concurrent - junit.jupiter.execution.parallel.mode.classes.default = concurrent - + + + + SEPARATE_THREAD + true + true + concurrent + concurrent + **/Test*.java From 19bd82c5776ed7dee79f637f21eb85eed89d7ea2 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sun, 26 Nov 2023 14:03:30 -0600 Subject: [PATCH 485/587] Fix static class references for ClassPath --- .../plugin/deltalake/TestDeltaLakeAdlsConnectorSmokeTest.java | 2 +- .../plugin/deltalake/TestDeltaLakeGcsConnectorSmokeTest.java | 2 +- .../src/main/java/io/trino/testing/containers/Minio.java | 2 +- .../src/main/java/io/trino/testing/minio/MinioClient.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeAdlsConnectorSmokeTest.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeAdlsConnectorSmokeTest.java index af5126549cf5..1ef3541c3701 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeAdlsConnectorSmokeTest.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeAdlsConnectorSmokeTest.java @@ -126,7 +126,7 @@ protected void registerTableFromResources(String table, String resourcePath, Que String targetDirectory = bucketName + "/" + table; try { - List resources = ClassPath.from(TestDeltaLakeAdlsConnectorSmokeTest.class.getClassLoader()) + List resources = ClassPath.from(getClass().getClassLoader()) .getResources() .stream() .filter(resourceInfo -> resourceInfo.getResourceName().startsWith(resourcePath + "/")) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeGcsConnectorSmokeTest.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeGcsConnectorSmokeTest.java index c99d9616e964..ef236bd21c21 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeGcsConnectorSmokeTest.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeGcsConnectorSmokeTest.java @@ -165,7 +165,7 @@ protected void registerTableFromResources(String table, String resourcePath, Que String targetDirectory = bucketUrl() + table; try { - List resources = ClassPath.from(TestDeltaLakeAdlsConnectorSmokeTest.class.getClassLoader()) + List resources = ClassPath.from(getClass().getClassLoader()) .getResources() .stream() .filter(resourceInfo -> resourceInfo.getResourceName().startsWith(resourcePath + "/")) diff --git a/testing/trino-testing-containers/src/main/java/io/trino/testing/containers/Minio.java b/testing/trino-testing-containers/src/main/java/io/trino/testing/containers/Minio.java index 624d7c5f9ac4..2bd06c07e606 100644 --- a/testing/trino-testing-containers/src/main/java/io/trino/testing/containers/Minio.java +++ b/testing/trino-testing-containers/src/main/java/io/trino/testing/containers/Minio.java @@ -127,7 +127,7 @@ public void createBucket(String bucketName) public void copyResources(String resourcePath, String bucketName, String target) { try (MinioClient minioClient = createMinioClient()) { - for (ClassPath.ResourceInfo resourceInfo : ClassPath.from(MinioClient.class.getClassLoader()) + for (ClassPath.ResourceInfo resourceInfo : ClassPath.from(getClass().getClassLoader()) .getResources()) { if (resourceInfo.getResourceName().startsWith(resourcePath)) { String fileName = resourceInfo.getResourceName().replaceFirst("^" + Pattern.quote(resourcePath), quoteReplacement(target)); diff --git a/testing/trino-testing-containers/src/main/java/io/trino/testing/minio/MinioClient.java b/testing/trino-testing-containers/src/main/java/io/trino/testing/minio/MinioClient.java index 62eaabd1078b..41d4d6fc78bb 100644 --- a/testing/trino-testing-containers/src/main/java/io/trino/testing/minio/MinioClient.java +++ b/testing/trino-testing-containers/src/main/java/io/trino/testing/minio/MinioClient.java @@ -91,7 +91,7 @@ public void copyResourcePath(String bucket, String resourcePath, String target) ensureBucketExists(bucket); try { - ClassPath.from(MinioClient.class.getClassLoader()) + ClassPath.from(getClass().getClassLoader()) .getResources().stream() .filter(resourceInfo -> resourceInfo.getResourceName().startsWith(resourcePath)) .forEach(resourceInfo -> { From 230c18503cc006c90cd9d776b8abaf7da87a956a Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sun, 26 Nov 2023 12:42:15 -0600 Subject: [PATCH 486/587] Fix grammar for GCS error messages --- .../main/java/io/trino/filesystem/gcs/GcsFileIterator.java | 4 ++-- .../main/java/io/trino/filesystem/gcs/GcsFileSystem.java | 6 +++--- .../src/main/java/io/trino/filesystem/gcs/GcsInput.java | 2 +- .../main/java/io/trino/filesystem/gcs/GcsInputStream.java | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileIterator.java b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileIterator.java index 5b3f4e42fa4d..b06eee7598d2 100644 --- a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileIterator.java +++ b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileIterator.java @@ -49,7 +49,7 @@ public boolean hasNext() return blobIterator.hasNext(); } catch (RuntimeException e) { - throw handleGcsException(e, "iterate files", location); + throw handleGcsException(e, "listing files", location); } } @@ -63,7 +63,7 @@ public FileEntry next() return new FileEntry(Location.of(location.getBase() + blob.getName()), length, Instant.from(blob.getUpdateTimeOffsetDateTime()), Optional.empty()); } catch (RuntimeException e) { - throw handleGcsException(e, "iterate files", location); + throw handleGcsException(e, "listing files", location); } } } diff --git a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileSystem.java b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileSystem.java index fcb5c14d3754..3b7f2383cf4c 100644 --- a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileSystem.java +++ b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileSystem.java @@ -141,7 +141,7 @@ public void deleteDirectory(Location location) getFutureValue(Futures.allAsList(batchFutures)); } catch (RuntimeException e) { - throw handleGcsException(e, "delete directory", gcsLocation); + throw handleGcsException(e, "deleting directory", gcsLocation); } } @@ -161,7 +161,7 @@ public FileIterator listFiles(Location location) return new GcsFileIterator(gcsLocation, getPage(gcsLocation)); } catch (RuntimeException e) { - throw handleGcsException(e, "list files", gcsLocation); + throw handleGcsException(e, "listing files", gcsLocation); } } @@ -258,7 +258,7 @@ public Set listDirectories(Location location) return locationBuilder.build(); } catch (RuntimeException e) { - throw handleGcsException(e, "list directories", gcsLocation); + throw handleGcsException(e, "listing directories", gcsLocation); } } diff --git a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsInput.java b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsInput.java index 89600751777b..138380dc9bb5 100644 --- a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsInput.java +++ b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsInput.java @@ -84,7 +84,7 @@ public int readTail(byte[] buffer, int bufferOffset, int bufferLength) return readNBytes(readChannel, buffer, bufferOffset, bufferLength); } catch (RuntimeException e) { - throw handleGcsException(e, "read file", location); + throw handleGcsException(e, "reading file", location); } } diff --git a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsInputStream.java b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsInputStream.java index 4f6f39fae3d7..e7bb42b4481d 100644 --- a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsInputStream.java +++ b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsInputStream.java @@ -185,7 +185,7 @@ private void openStream() this.readChannel = getReadChannel(blob, location, 0L, readBlockSizeBytes, predeclaredLength); } catch (RuntimeException e) { - throw handleGcsException(e, "read file", location); + throw handleGcsException(e, "reading file", location); } } From 1ff1e01b776b3354059bfcaffd29841d84472f11 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sun, 26 Nov 2023 13:36:04 -0600 Subject: [PATCH 487/587] Fix large writes for GCS --- .../trino/filesystem/gcs/GcsOutputStream.java | 4 +-- .../AbstractTestTrinoFileSystem.java | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsOutputStream.java b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsOutputStream.java index 9b081dc66912..f9cb4b1a0960 100644 --- a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsOutputStream.java +++ b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsOutputStream.java @@ -87,8 +87,8 @@ private void writeDirect(ByteBuffer buffer) int bytesWritten = 0; try { bytesWritten = writeChannel.write(buffer); - if (bytesWritten != buffer.remaining()) { - throw new IOException("Unexpected bytes written length: %s should be %s".formatted(bytesWritten, buffer.remaining())); + if (buffer.remaining() != 0) { + throw new IOException("Unexpected partial write (written=%s, remaining=%s)".formatted(bytesWritten, buffer.remaining())); } } catch (IOException e) { diff --git a/lib/trino-filesystem/src/test/java/io/trino/filesystem/AbstractTestTrinoFileSystem.java b/lib/trino-filesystem/src/test/java/io/trino/filesystem/AbstractTestTrinoFileSystem.java index 82b42b052158..b11890c86b0f 100644 --- a/lib/trino-filesystem/src/test/java/io/trino/filesystem/AbstractTestTrinoFileSystem.java +++ b/lib/trino-filesystem/src/test/java/io/trino/filesystem/AbstractTestTrinoFileSystem.java @@ -34,6 +34,7 @@ import java.nio.file.FileAlreadyExistsException; import java.time.Instant; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -559,6 +560,31 @@ void testOutputStreamByteAtATime() } } + @Test + void testOutputStreamLargeWrites() + throws IOException + { + try (TempBlob tempBlob = randomBlobLocation("inputStream")) { + try (OutputStream outputStream = tempBlob.outputFile().create()) { + for (int i = 0; i < 8; i++) { + byte[] bytes = new byte[MEGABYTE / 2]; + Arrays.fill(bytes, (byte) i); + outputStream.write(bytes); + } + } + + try (TrinoInputStream inputStream = tempBlob.inputFile().newStream()) { + for (int i = 0; i < 8; i++) { + byte[] expected = new byte[MEGABYTE / 2]; + Arrays.fill(expected, (byte) i); + byte[] actual = inputStream.readNBytes(expected.length); + assertThat(actual.length).isEqualTo(expected.length); + assertThat(actual).isEqualTo(expected); + } + } + } + } + @Test public void testPaths() throws IOException From 7e0b8fe7a03ad9386798f9ecdbd665ecbc55b6e4 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sun, 26 Nov 2023 23:02:34 -0800 Subject: [PATCH 488/587] Filter GCS objects that end in slash when listing files --- .../trino/filesystem/gcs/GcsFileIterator.java | 7 ++--- .../gcs/AbstractTestGcsFileSystem.java | 26 +++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileIterator.java b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileIterator.java index b06eee7598d2..f5315a708a60 100644 --- a/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileIterator.java +++ b/lib/trino-filesystem-gcs/src/main/java/io/trino/filesystem/gcs/GcsFileIterator.java @@ -15,7 +15,6 @@ import com.google.api.gax.paging.Page; import com.google.cloud.storage.Blob; -import com.google.common.collect.Iterators; import io.trino.filesystem.FileEntry; import io.trino.filesystem.FileIterator; import io.trino.filesystem.Location; @@ -37,8 +36,10 @@ public class GcsFileIterator public GcsFileIterator(GcsLocation location, Page page) { this.location = requireNonNull(location, "location is null"); - // Page::iterateAll handles paging internally - this.blobIterator = Iterators.filter(page.iterateAll().iterator(), blob -> !blob.isDirectory()); + this.blobIterator = page.streamAll() + .filter(blob -> !blob.isDirectory()) + .filter(blob -> !blob.getName().endsWith("/")) + .iterator(); } @Override diff --git a/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/AbstractTestGcsFileSystem.java b/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/AbstractTestGcsFileSystem.java index 5cc129bd6029..b830f893a904 100644 --- a/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/AbstractTestGcsFileSystem.java +++ b/lib/trino-filesystem-gcs/src/test/java/io/trino/filesystem/gcs/AbstractTestGcsFileSystem.java @@ -13,6 +13,8 @@ */ package io.trino.filesystem.gcs; +import com.google.cloud.storage.BlobId; +import com.google.cloud.storage.BlobInfo; import com.google.cloud.storage.BucketInfo; import com.google.cloud.storage.Storage; import com.google.cloud.storage.testing.RemoteStorageHelper; @@ -22,11 +24,13 @@ import io.trino.spi.security.ConnectorIdentity; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import java.io.IOException; import java.util.Base64; +import static com.google.cloud.storage.Storage.BlobTargetOption.doesNotExist; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.Assertions.assertThat; @@ -126,4 +130,26 @@ protected final boolean supportsRenameFile() { return false; } + + @Test + void testExistingFileWithTrailingSlash() + throws IOException + { + BlobId blobId = BlobId.of(new GcsLocation(rootLocation).bucket(), "data/file/"); + storage.create(BlobInfo.newBuilder(blobId).build(), new byte[0], doesNotExist()); + try { + assertThat(fileSystem.listFiles(getRootLocation()).hasNext()).isFalse(); + + Location data = getRootLocation().appendPath("data/"); + assertThat(fileSystem.listDirectories(getRootLocation())).containsExactly(data); + assertThat(fileSystem.listDirectories(data)).containsExactly(data.appendPath("file/")); + + // blobs ending in slash are deleted, even though they are not visible to listings + fileSystem.deleteDirectory(data); + assertThat(fileSystem.listDirectories(getRootLocation())).isEmpty(); + } + finally { + storage.delete(blobId); + } + } } From 4a0294c2f149832d90c94b47691e8bedf90817ac Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sun, 26 Nov 2023 13:20:45 -0600 Subject: [PATCH 489/587] Use native GCS for Iceberg and Delta Lake tests --- .../TestDeltaLakeGcsConnectorSmokeTest.java | 12 +++++++++--- .../iceberg/TestIcebergGcsConnectorSmokeTest.java | 4 +++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeGcsConnectorSmokeTest.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeGcsConnectorSmokeTest.java index ef236bd21c21..3571155cc668 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeGcsConnectorSmokeTest.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeGcsConnectorSmokeTest.java @@ -23,10 +23,13 @@ import io.trino.filesystem.FileIterator; import io.trino.filesystem.Location; import io.trino.filesystem.TrinoFileSystem; +import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.filesystem.TrinoOutputFile; import io.trino.hdfs.gcs.GoogleGcsConfigurationInitializer; import io.trino.hdfs.gcs.HiveGcsConfig; import io.trino.plugin.hive.containers.HiveHadoop; +import io.trino.spi.security.ConnectorIdentity; +import io.trino.testing.DistributedQueryRunner; import io.trino.testing.QueryRunner; import org.apache.hadoop.conf.Configuration; import org.junit.jupiter.api.AfterAll; @@ -47,7 +50,7 @@ import java.util.regex.Pattern; import static com.google.common.collect.ImmutableList.toImmutableList; -import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; +import static io.trino.plugin.deltalake.TestingDeltaLakeUtils.getConnectorService; import static io.trino.plugin.hive.containers.HiveHadoop.HIVE3_IMAGE; import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; @@ -143,7 +146,9 @@ protected HiveHadoop createHiveHadoop() protected Map hiveStorageConfiguration() { return ImmutableMap.builder() - .put("hive.gcs.json-key", gcpCredentials) + .put("fs.hadoop.enabled", "false") + .put("fs.native-gcs.enabled", "true") + .put("gcs.json-key", gcpCredentials) .buildOrThrow(); } @@ -160,7 +165,8 @@ protected Map deltaStorageConfiguration() @Override protected void registerTableFromResources(String table, String resourcePath, QueryRunner queryRunner) { - this.fileSystem = HDFS_FILE_SYSTEM_FACTORY.create(queryRunner.getDefaultSession().toConnectorSession()); + this.fileSystem = getConnectorService((DistributedQueryRunner) queryRunner, TrinoFileSystemFactory.class) + .create(ConnectorIdentity.ofUser("test")); String targetDirectory = bucketUrl() + table; diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergGcsConnectorSmokeTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergGcsConnectorSmokeTest.java index 34248811d9fb..0cd5258140b2 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergGcsConnectorSmokeTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergGcsConnectorSmokeTest.java @@ -94,7 +94,9 @@ protected QueryRunner createQueryRunner() return IcebergQueryRunner.builder() .setIcebergProperties(ImmutableMap.builder() .put("iceberg.catalog.type", "hive_metastore") - .put("hive.gcs.json-key", gcpCredentials) + .put("fs.hadoop.enabled", "false") + .put("fs.native-gcs.enabled", "true") + .put("gcs.json-key", gcpCredentials) .put("hive.metastore.uri", "thrift://" + hiveHadoop.getHiveMetastoreEndpoint()) .put("iceberg.file-format", format.name()) .put("iceberg.register-table-procedure.enabled", "true") From f2b97e5ff04872668f2116906afa159bdaa95862 Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Fri, 17 Nov 2023 17:25:12 +0900 Subject: [PATCH 490/587] Disable compaction in Raptor connector test --- .../trino/plugin/raptor/legacy/BaseRaptorConnectorTest.java | 4 ---- .../plugin/raptor/legacy/TestRaptorBucketedConnectorTest.java | 2 +- .../trino/plugin/raptor/legacy/TestRaptorConnectorTest.java | 2 +- .../plugin/raptor/legacy/TestRaptorMySqlConnectorTest.java | 1 + 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/BaseRaptorConnectorTest.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/BaseRaptorConnectorTest.java index effb15e3b1a7..b49b9024a150 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/BaseRaptorConnectorTest.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/BaseRaptorConnectorTest.java @@ -23,7 +23,6 @@ import io.trino.testing.MaterializedRow; import io.trino.testing.TestingConnectorBehavior; import io.trino.testing.sql.TestTable; -import io.trino.testng.services.Flaky; import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.Test; @@ -526,9 +525,6 @@ public void testColumnRangesSystemTable() } @Test - @Flaky( - issue = "https://github.com/trinodb/trino/issues/1977", - match = "(?s)AssertionError.*query.*SELECT count\\(DISTINCT \"\\$shard_uuid\"\\) FROM orders_bucketed.*Actual rows.*\\[\\d\\d\\].*Expected rows.*\\[100\\]") public void testCreateBucketedTable() { assertUpdate("DROP TABLE IF EXISTS orders_bucketed"); diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorBucketedConnectorTest.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorBucketedConnectorTest.java index 4ac6ac60687a..5a64ebc8e9a9 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorBucketedConnectorTest.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorBucketedConnectorTest.java @@ -27,7 +27,7 @@ public class TestRaptorBucketedConnectorTest protected QueryRunner createQueryRunner() throws Exception { - return createRaptorQueryRunner(ImmutableMap.of(), REQUIRED_TPCH_TABLES, true, ImmutableMap.of()); + return createRaptorQueryRunner(ImmutableMap.of(), REQUIRED_TPCH_TABLES, true, ImmutableMap.of("storage.compaction-enabled", "false")); } @Test diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorConnectorTest.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorConnectorTest.java index ce99a6be5ca7..b5625b8edb30 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorConnectorTest.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorConnectorTest.java @@ -25,6 +25,6 @@ public class TestRaptorConnectorTest protected QueryRunner createQueryRunner() throws Exception { - return createRaptorQueryRunner(ImmutableMap.of(), REQUIRED_TPCH_TABLES, false, ImmutableMap.of()); + return createRaptorQueryRunner(ImmutableMap.of(), REQUIRED_TPCH_TABLES, false, ImmutableMap.of("storage.compaction-enabled", "false")); } } diff --git a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorMySqlConnectorTest.java b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorMySqlConnectorTest.java index b67827072044..00e3019c34a9 100644 --- a/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorMySqlConnectorTest.java +++ b/plugin/trino-raptor-legacy/src/test/java/io/trino/plugin/raptor/legacy/TestRaptorMySqlConnectorTest.java @@ -72,6 +72,7 @@ private static QueryRunner createRaptorMySqlQueryRunner(String mysqlUrl) Map raptorProperties = ImmutableMap.builder() .put("metadata.db.type", "mysql") .put("metadata.db.url", mysqlUrl) + .put("storage.compaction-enabled", "false") .put("storage.data-directory", new File(baseDir, "data").getAbsolutePath()) .put("storage.max-shard-rows", "2000") .put("backup.provider", "file") From e7627a16e487bd83c00a6d071c5659328e11281f Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Sat, 18 Nov 2023 12:26:33 -0800 Subject: [PATCH 491/587] Remove HiveMetastoreDecorator and simplify metastore creation --- .../metastore/DeltaLakeMetastoreModule.java | 4 +- .../TestingDeltaLakeMetastoreModule.java | 4 +- ...e.java => CachingHiveMetastoreModule.java} | 49 +------------------ .../metastore/HiveMetastoreDecorator.java | 24 --------- .../hive/metastore/HiveMetastoreModule.java | 2 +- .../TracingHiveMetastoreDecorator.java | 45 ----------------- .../IcebergFileMetastoreCatalogModule.java | 4 +- .../IcebergHiveMetastoreCatalogModule.java | 4 +- ...tingIcebergFileMetastoreCatalogModule.java | 4 +- ...tingIcebergHiveMetastoreCatalogModule.java | 4 +- 10 files changed, 15 insertions(+), 129 deletions(-) rename plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/{DecoratedHiveMetastoreModule.java => CachingHiveMetastoreModule.java} (63%) delete mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastoreDecorator.java delete mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/tracing/TracingHiveMetastoreDecorator.java diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/metastore/DeltaLakeMetastoreModule.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/metastore/DeltaLakeMetastoreModule.java index 616084931cfc..2f49aed37824 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/metastore/DeltaLakeMetastoreModule.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/metastore/DeltaLakeMetastoreModule.java @@ -21,7 +21,7 @@ import io.trino.plugin.deltalake.metastore.glue.DeltaLakeGlueMetastoreModule; import io.trino.plugin.deltalake.metastore.thrift.DeltaLakeThriftMetastoreModule; import io.trino.plugin.hive.HideDeltaLakeTables; -import io.trino.plugin.hive.metastore.DecoratedHiveMetastoreModule; +import io.trino.plugin.hive.metastore.CachingHiveMetastoreModule; import io.trino.plugin.hive.metastore.MetastoreTypeConfig; import static io.airlift.configuration.ConditionalModule.conditionalModule; @@ -37,7 +37,7 @@ protected void setup(Binder binder) bindMetastoreModule("file", new DeltaLakeFileMetastoreModule()); bindMetastoreModule("glue", new DeltaLakeGlueMetastoreModule()); - install(new DecoratedHiveMetastoreModule(false)); + install(new CachingHiveMetastoreModule(false)); } private void bindMetastoreModule(String name, Module module) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/TestingDeltaLakeMetastoreModule.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/TestingDeltaLakeMetastoreModule.java index 8db4f0ba31f6..175c0dcea7fc 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/TestingDeltaLakeMetastoreModule.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/TestingDeltaLakeMetastoreModule.java @@ -18,7 +18,7 @@ import io.airlift.configuration.AbstractConfigurationAwareModule; import io.trino.plugin.deltalake.AllowDeltaLakeManagedTableRename; import io.trino.plugin.hive.HideDeltaLakeTables; -import io.trino.plugin.hive.metastore.DecoratedHiveMetastoreModule; +import io.trino.plugin.hive.metastore.CachingHiveMetastoreModule; import io.trino.plugin.hive.metastore.HiveMetastore; import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.plugin.hive.metastore.RawHiveMetastoreFactory; @@ -39,7 +39,7 @@ public TestingDeltaLakeMetastoreModule(HiveMetastore metastore) public void setup(Binder binder) { binder.bind(HiveMetastoreFactory.class).annotatedWith(RawHiveMetastoreFactory.class).toInstance(HiveMetastoreFactory.ofInstance(metastore)); - install(new DecoratedHiveMetastoreModule(false)); + install(new CachingHiveMetastoreModule(false)); binder.bind(Key.get(boolean.class, HideDeltaLakeTables.class)).toInstance(false); binder.bind(Key.get(boolean.class, AllowDeltaLakeManagedTableRename.class)).toInstance(true); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/DecoratedHiveMetastoreModule.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/CachingHiveMetastoreModule.java similarity index 63% rename from plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/DecoratedHiveMetastoreModule.java rename to plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/CachingHiveMetastoreModule.java index 59a948125eb5..8ee07eb47df5 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/DecoratedHiveMetastoreModule.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/CachingHiveMetastoreModule.java @@ -24,27 +24,20 @@ import io.trino.plugin.hive.metastore.cache.SharedHiveMetastoreCache; import io.trino.plugin.hive.metastore.cache.SharedHiveMetastoreCache.CachingHiveMetastoreFactory; import io.trino.plugin.hive.metastore.procedure.FlushMetadataCacheProcedure; -import io.trino.plugin.hive.metastore.tracing.TracingHiveMetastoreDecorator; import io.trino.spi.procedure.Procedure; -import io.trino.spi.security.ConnectorIdentity; -import java.util.Comparator; -import java.util.List; import java.util.Optional; -import java.util.Set; -import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.inject.multibindings.Multibinder.newSetBinder; import static io.airlift.configuration.ConfigBinder.configBinder; -import static java.util.Objects.requireNonNull; import static org.weakref.jmx.guice.ExportBinder.newExporter; -public class DecoratedHiveMetastoreModule +public class CachingHiveMetastoreModule extends AbstractConfigurationAwareModule { private final boolean installFlushMetadataCacheProcedure; - public DecoratedHiveMetastoreModule(boolean installFlushMetadataCacheProcedure) + public CachingHiveMetastoreModule(boolean installFlushMetadataCacheProcedure) { this.installFlushMetadataCacheProcedure = installFlushMetadataCacheProcedure; } @@ -52,9 +45,6 @@ public DecoratedHiveMetastoreModule(boolean installFlushMetadataCacheProcedure) @Override protected void setup(Binder binder) { - newSetBinder(binder, HiveMetastoreDecorator.class) - .addBinding().to(TracingHiveMetastoreDecorator.class).in(Scopes.SINGLETON); - configBinder(binder).bindConfig(CachingHiveMetastoreConfig.class); // TODO this should only be bound when impersonation is actually enabled configBinder(binder).bindConfig(ImpersonationCachingConfig.class); @@ -72,47 +62,12 @@ protected void setup(Binder binder) @Singleton public static HiveMetastoreFactory createHiveMetastore( @RawHiveMetastoreFactory HiveMetastoreFactory metastoreFactory, - Set decorators, SharedHiveMetastoreCache sharedHiveMetastoreCache) { - metastoreFactory = new DecoratingHiveMetastoreFactory(metastoreFactory, decorators); - // cross TX metastore cache is enabled wrapper with caching metastore return sharedHiveMetastoreCache.createCachingHiveMetastoreFactory(metastoreFactory); } - private static class DecoratingHiveMetastoreFactory - implements HiveMetastoreFactory - { - private final HiveMetastoreFactory delegate; - private final List sortedDecorators; - - public DecoratingHiveMetastoreFactory(HiveMetastoreFactory delegate, Set decorators) - { - this.delegate = requireNonNull(delegate, "delegate is null"); - - this.sortedDecorators = decorators.stream() - .sorted(Comparator.comparing(HiveMetastoreDecorator::getPriority)) - .collect(toImmutableList()); - } - - @Override - public boolean isImpersonationEnabled() - { - return delegate.isImpersonationEnabled(); - } - - @Override - public HiveMetastore createMetastore(Optional identity) - { - HiveMetastore metastore = delegate.createMetastore(identity); - for (HiveMetastoreDecorator decorator : sortedDecorators) { - metastore = decorator.decorate(metastore); - } - return metastore; - } - } - @Provides @Singleton public static Optional createHiveMetastore(HiveMetastoreFactory metastoreFactory) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastoreDecorator.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastoreDecorator.java deleted file mode 100644 index db7cd2305a9e..000000000000 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastoreDecorator.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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 io.trino.plugin.hive.metastore; - -public interface HiveMetastoreDecorator -{ - int PRIORITY_TRACING = 100; - - int getPriority(); - - HiveMetastore decorate(HiveMetastore hiveMetastore); -} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastoreModule.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastoreModule.java index 7735f2cb2bb7..b300c93ca874 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastoreModule.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HiveMetastoreModule.java @@ -52,7 +52,7 @@ protected void setup(Binder binder) bindMetastoreModule("glue", new GlueMetastoreModule()); } - install(new DecoratedHiveMetastoreModule(true)); + install(new CachingHiveMetastoreModule(true)); } private void bindMetastoreModule(String name, Module module) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/tracing/TracingHiveMetastoreDecorator.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/tracing/TracingHiveMetastoreDecorator.java deleted file mode 100644 index 54aa31bdfe2f..000000000000 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/tracing/TracingHiveMetastoreDecorator.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 io.trino.plugin.hive.metastore.tracing; - -import com.google.inject.Inject; -import io.opentelemetry.api.trace.Tracer; -import io.trino.plugin.hive.metastore.HiveMetastore; -import io.trino.plugin.hive.metastore.HiveMetastoreDecorator; - -import static java.util.Objects.requireNonNull; - -public class TracingHiveMetastoreDecorator - implements HiveMetastoreDecorator -{ - private final Tracer tracer; - - @Inject - public TracingHiveMetastoreDecorator(Tracer tracer) - { - this.tracer = requireNonNull(tracer, "tracer is null"); - } - - @Override - public int getPriority() - { - return PRIORITY_TRACING; - } - - @Override - public HiveMetastore decorate(HiveMetastore hiveMetastore) - { - return new TracingHiveMetastore(tracer, hiveMetastore); - } -} diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/file/IcebergFileMetastoreCatalogModule.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/file/IcebergFileMetastoreCatalogModule.java index e937260c7c86..cef2b7927262 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/file/IcebergFileMetastoreCatalogModule.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/file/IcebergFileMetastoreCatalogModule.java @@ -20,7 +20,7 @@ import io.airlift.configuration.AbstractConfigurationAwareModule; import io.airlift.units.Duration; import io.trino.plugin.hive.HideDeltaLakeTables; -import io.trino.plugin.hive.metastore.DecoratedHiveMetastoreModule; +import io.trino.plugin.hive.metastore.CachingHiveMetastoreModule; import io.trino.plugin.hive.metastore.cache.CachingHiveMetastoreConfig; import io.trino.plugin.hive.metastore.file.FileMetastoreModule; import io.trino.plugin.iceberg.catalog.IcebergTableOperationsProvider; @@ -47,7 +47,7 @@ protected void setup(Binder binder) binder.bind(TrinoCatalogFactory.class).to(TrinoHiveCatalogFactory.class).in(Scopes.SINGLETON); binder.bind(MetastoreValidator.class).asEagerSingleton(); binder.bind(Key.get(boolean.class, HideDeltaLakeTables.class)).toInstance(HIDE_DELTA_LAKE_TABLES_IN_ICEBERG); - install(new DecoratedHiveMetastoreModule(false)); + install(new CachingHiveMetastoreModule(false)); configBinder(binder).bindConfigDefaults(CachingHiveMetastoreConfig.class, config -> { // ensure caching metastore wrapper isn't created, as it's not leveraged by Iceberg diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/IcebergHiveMetastoreCatalogModule.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/IcebergHiveMetastoreCatalogModule.java index 2c385032d152..b254247f9828 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/IcebergHiveMetastoreCatalogModule.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/IcebergHiveMetastoreCatalogModule.java @@ -20,7 +20,7 @@ import io.airlift.configuration.AbstractConfigurationAwareModule; import io.airlift.units.Duration; import io.trino.plugin.hive.HideDeltaLakeTables; -import io.trino.plugin.hive.metastore.DecoratedHiveMetastoreModule; +import io.trino.plugin.hive.metastore.CachingHiveMetastoreModule; import io.trino.plugin.hive.metastore.cache.CachingHiveMetastoreConfig; import io.trino.plugin.hive.metastore.thrift.ThriftMetastoreModule; import io.trino.plugin.hive.metastore.thrift.TranslateHiveViews; @@ -49,7 +49,7 @@ protected void setup(Binder binder) binder.bind(MetastoreValidator.class).asEagerSingleton(); binder.bind(Key.get(boolean.class, TranslateHiveViews.class)).toInstance(false); binder.bind(Key.get(boolean.class, HideDeltaLakeTables.class)).toInstance(HIDE_DELTA_LAKE_TABLES_IN_ICEBERG); - install(new DecoratedHiveMetastoreModule(false)); + install(new CachingHiveMetastoreModule(false)); configBinder(binder).bindConfigDefaults(CachingHiveMetastoreConfig.class, config -> { // ensure caching metastore wrapper isn't created, as it's not leveraged by Iceberg diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestingIcebergFileMetastoreCatalogModule.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestingIcebergFileMetastoreCatalogModule.java index 79228aefa6ef..cda4d55660e6 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestingIcebergFileMetastoreCatalogModule.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestingIcebergFileMetastoreCatalogModule.java @@ -17,7 +17,7 @@ import com.google.inject.Scopes; import io.airlift.configuration.AbstractConfigurationAwareModule; import io.airlift.units.Duration; -import io.trino.plugin.hive.metastore.DecoratedHiveMetastoreModule; +import io.trino.plugin.hive.metastore.CachingHiveMetastoreModule; import io.trino.plugin.hive.metastore.HiveMetastore; import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.plugin.hive.metastore.RawHiveMetastoreFactory; @@ -45,7 +45,7 @@ public TestingIcebergFileMetastoreCatalogModule(HiveMetastore metastore) protected void setup(Binder binder) { binder.bind(HiveMetastoreFactory.class).annotatedWith(RawHiveMetastoreFactory.class).toInstance(HiveMetastoreFactory.ofInstance(metastore)); - install(new DecoratedHiveMetastoreModule(false)); + install(new CachingHiveMetastoreModule(false)); binder.bind(IcebergTableOperationsProvider.class).to(FileMetastoreTableOperationsProvider.class).in(Scopes.SINGLETON); binder.bind(TrinoCatalogFactory.class).to(TrinoHiveCatalogFactory.class).in(Scopes.SINGLETON); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/hms/TestingIcebergHiveMetastoreCatalogModule.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/hms/TestingIcebergHiveMetastoreCatalogModule.java index 15edd94e2431..80ea2534b35b 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/hms/TestingIcebergHiveMetastoreCatalogModule.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/hms/TestingIcebergHiveMetastoreCatalogModule.java @@ -17,7 +17,7 @@ import com.google.inject.Scopes; import io.airlift.configuration.AbstractConfigurationAwareModule; import io.airlift.units.Duration; -import io.trino.plugin.hive.metastore.DecoratedHiveMetastoreModule; +import io.trino.plugin.hive.metastore.CachingHiveMetastoreModule; import io.trino.plugin.hive.metastore.HiveMetastore; import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.plugin.hive.metastore.RawHiveMetastoreFactory; @@ -46,7 +46,7 @@ public TestingIcebergHiveMetastoreCatalogModule(HiveMetastore hiveMetastore, Thr @Override protected void setup(Binder binder) { - install(new DecoratedHiveMetastoreModule(false)); + install(new CachingHiveMetastoreModule(false)); binder.bind(ThriftMetastoreFactory.class).toInstance(this.thriftMetastoreFactory); binder.bind(HiveMetastoreFactory.class).annotatedWith(RawHiveMetastoreFactory.class).toInstance(HiveMetastoreFactory.ofInstance(this.hiveMetastore)); binder.bind(IcebergTableOperationsProvider.class).to(HiveMetastoreTableOperationsProvider.class).in(Scopes.SINGLETON); From 8381a93c2f4ee96bb82cfb0cb76592b1e8d91c2d Mon Sep 17 00:00:00 2001 From: Raunaq Morarka Date: Tue, 28 Nov 2023 10:21:57 +0530 Subject: [PATCH 492/587] Implement missing overrides in IcebergPageSource --- .../plugin/iceberg/IcebergPageSource.java | 7 ++++ .../plugin/iceberg/TestIcebergPageSource.java | 34 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergPageSource.java diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergPageSource.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergPageSource.java index 32342239c5f5..7a2730d5d1b6 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergPageSource.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergPageSource.java @@ -29,6 +29,7 @@ import java.util.Map; import java.util.Optional; import java.util.OptionalLong; +import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import static com.google.common.base.Preconditions.checkArgument; @@ -113,6 +114,12 @@ public boolean isFinished() return delegate.isFinished(); } + @Override + public CompletableFuture isBlocked() + { + return delegate.isBlocked(); + } + @Override public Page getNextPage() { diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergPageSource.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergPageSource.java new file mode 100644 index 000000000000..15fe7fda0f9b --- /dev/null +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergPageSource.java @@ -0,0 +1,34 @@ +/* + * 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 io.trino.plugin.iceberg; + +import io.trino.spi.connector.ConnectorPageSource; +import org.junit.jupiter.api.Test; + +import static io.trino.spi.testing.InterfaceTestUtils.assertAllMethodsOverridden; + +public class TestIcebergPageSource +{ + @Test + public void testEverythingImplemented() + { + assertAllMethodsOverridden(ConnectorPageSource.class, IcebergPageSource.class); + } + + @Test + public void testEverythingImplementedConstantPopulatingPageSource() + { + assertAllMethodsOverridden(ConnectorPageSource.class, ConstantPopulatingPageSource.class); + } +} From a66d62395b0abdb4a58885c929eaba3850933259 Mon Sep 17 00:00:00 2001 From: Raunaq Morarka Date: Tue, 28 Nov 2023 10:25:28 +0530 Subject: [PATCH 493/587] Implement missing overrides in DeltaLakePageSource --- .../plugin/deltalake/DeltaLakePageSource.java | 7 +++++ .../deltalake/TestDeltaLakePageSource.java | 28 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakePageSource.java diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakePageSource.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakePageSource.java index 8eff63a9ffe4..9cd618593c3c 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakePageSource.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakePageSource.java @@ -34,6 +34,7 @@ import java.util.Optional; import java.util.OptionalLong; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import static com.google.common.base.Throwables.throwIfInstanceOf; @@ -162,6 +163,12 @@ public boolean isFinished() return delegate.isFinished(); } + @Override + public CompletableFuture isBlocked() + { + return delegate.isBlocked(); + } + @Override public Page getNextPage() { diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakePageSource.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakePageSource.java new file mode 100644 index 000000000000..022467bd0bb2 --- /dev/null +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakePageSource.java @@ -0,0 +1,28 @@ +/* + * 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 io.trino.plugin.deltalake; + +import io.trino.spi.connector.ConnectorPageSource; +import org.junit.jupiter.api.Test; + +import static io.trino.spi.testing.InterfaceTestUtils.assertAllMethodsOverridden; + +public class TestDeltaLakePageSource +{ + @Test + public void testEverythingImplemented() + { + assertAllMethodsOverridden(ConnectorPageSource.class, DeltaLakePageSource.class); + } +} From 39069fe76e56d7f11fba6463cc85e6ec7e8d2e13 Mon Sep 17 00:00:00 2001 From: Raunaq Morarka Date: Tue, 28 Nov 2023 10:27:11 +0530 Subject: [PATCH 494/587] Implement missing overrides in HivePageSource --- .../io/trino/plugin/hive/HivePageSource.java | 7 +++++ .../trino/plugin/hive/TestHivePageSource.java | 28 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSource.java diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePageSource.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePageSource.java index b951f6527afd..0c3c6bfff40a 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePageSource.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HivePageSource.java @@ -40,6 +40,7 @@ import java.util.List; import java.util.Optional; import java.util.OptionalLong; +import java.util.concurrent.CompletableFuture; import java.util.function.Function; import static com.google.common.base.Preconditions.checkArgument; @@ -152,6 +153,12 @@ public boolean isFinished() return delegate.isFinished(); } + @Override + public CompletableFuture isBlocked() + { + return delegate.isBlocked(); + } + @Override public Page getNextPage() { diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSource.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSource.java new file mode 100644 index 000000000000..c36be5c41eb4 --- /dev/null +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHivePageSource.java @@ -0,0 +1,28 @@ +/* + * 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 io.trino.plugin.hive; + +import io.trino.spi.connector.ConnectorPageSource; +import org.junit.jupiter.api.Test; + +import static io.trino.spi.testing.InterfaceTestUtils.assertAllMethodsOverridden; + +public class TestHivePageSource +{ + @Test + public void testEverythingImplemented() + { + assertAllMethodsOverridden(ConnectorPageSource.class, HivePageSource.class); + } +} From 1a89f5eb93867747ef3a513b9a680dbd14a22070 Mon Sep 17 00:00:00 2001 From: Raunaq Morarka Date: Tue, 28 Nov 2023 10:29:37 +0530 Subject: [PATCH 495/587] Implement missing overrides in HudiPageSource --- .../io/trino/plugin/hudi/HudiPageSource.java | 21 ++++++++++++++ .../trino/plugin/hudi/TestHudiPageSource.java | 28 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiPageSource.java diff --git a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiPageSource.java b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiPageSource.java index 9c2edec73309..337ad783db8e 100644 --- a/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiPageSource.java +++ b/plugin/trino-hudi/src/main/java/io/trino/plugin/hudi/HudiPageSource.java @@ -18,10 +18,13 @@ import io.trino.spi.block.Block; import io.trino.spi.block.RunLengthEncodedBlock; import io.trino.spi.connector.ConnectorPageSource; +import io.trino.spi.metrics.Metrics; import java.io.IOException; import java.util.List; import java.util.Map; +import java.util.OptionalLong; +import java.util.concurrent.CompletableFuture; import static io.airlift.slice.Slices.utf8Slice; import static io.trino.plugin.base.util.Closables.closeAllSuppress; @@ -113,6 +116,24 @@ public boolean isFinished() return dataPageSource.isFinished(); } + @Override + public CompletableFuture isBlocked() + { + return dataPageSource.isBlocked(); + } + + @Override + public OptionalLong getCompletedPositions() + { + return dataPageSource.getCompletedPositions(); + } + + @Override + public Metrics getMetrics() + { + return dataPageSource.getMetrics(); + } + @Override public Page getNextPage() { diff --git a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiPageSource.java b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiPageSource.java new file mode 100644 index 000000000000..8bb46f8fb3f5 --- /dev/null +++ b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/TestHudiPageSource.java @@ -0,0 +1,28 @@ +/* + * 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 io.trino.plugin.hudi; + +import io.trino.spi.connector.ConnectorPageSource; +import org.junit.jupiter.api.Test; + +import static io.trino.spi.testing.InterfaceTestUtils.assertAllMethodsOverridden; + +public class TestHudiPageSource +{ + @Test + public void testEverythingImplemented() + { + assertAllMethodsOverridden(ConnectorPageSource.class, HudiPageSource.class); + } +} From 44767ebf3c33fcf616b8b4af3e30cc4645b6a582 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Mon, 27 Nov 2023 10:42:15 +0100 Subject: [PATCH 496/587] Fix lack of test timeout --- .../src/test/java/io/trino/cache/TestEmptyCache.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/trino-cache/src/test/java/io/trino/cache/TestEmptyCache.java b/lib/trino-cache/src/test/java/io/trino/cache/TestEmptyCache.java index b0706b8e7db0..0572a9e3ab6e 100644 --- a/lib/trino-cache/src/test/java/io/trino/cache/TestEmptyCache.java +++ b/lib/trino-cache/src/test/java/io/trino/cache/TestEmptyCache.java @@ -16,6 +16,7 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheLoader; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; import java.util.ArrayList; import java.util.List; @@ -31,9 +32,10 @@ public class TestEmptyCache { - private static final int TEST_TIMEOUT_MILLIS = 10_000; + private static final int TEST_TIMEOUT_SECONDS = 10; @Test + @Timeout(TEST_TIMEOUT_SECONDS) public void testLoadFailure() throws Exception { From de26c1e7208d819092c5382e8961ef0e3a4450c7 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Mon, 27 Nov 2023 10:33:06 +0100 Subject: [PATCH 497/587] Move Assert util to testing --- core/trino-server-rpm/pom.xml | 6 ++++++ plugin/trino-google-sheets/pom.xml | 6 ++++++ .../src/main/java/io/trino/testing/assertions/Assert.java | 0 3 files changed, 12 insertions(+) rename {core/trino-main => testing/trino-testing-services}/src/main/java/io/trino/testing/assertions/Assert.java (100%) diff --git a/core/trino-server-rpm/pom.xml b/core/trino-server-rpm/pom.xml index 5871b37d5e6f..a30446f6421c 100644 --- a/core/trino-server-rpm/pom.xml +++ b/core/trino-server-rpm/pom.xml @@ -55,6 +55,12 @@ test + + io.trino + trino-testing-services + test + + org.assertj assertj-core diff --git a/plugin/trino-google-sheets/pom.xml b/plugin/trino-google-sheets/pom.xml index 5b177c27fc2b..70c9d22461d1 100644 --- a/plugin/trino-google-sheets/pom.xml +++ b/plugin/trino-google-sheets/pom.xml @@ -191,6 +191,12 @@ test + + io.trino + trino-testing-services + test + + io.trino trino-tpch diff --git a/core/trino-main/src/main/java/io/trino/testing/assertions/Assert.java b/testing/trino-testing-services/src/main/java/io/trino/testing/assertions/Assert.java similarity index 100% rename from core/trino-main/src/main/java/io/trino/testing/assertions/Assert.java rename to testing/trino-testing-services/src/main/java/io/trino/testing/assertions/Assert.java From 891ad8bf33d468896f8b89632a58575fee8da82f Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Mon, 27 Nov 2023 10:32:12 +0100 Subject: [PATCH 498/587] Fix TestEvictableCache.testLoadFailure flakiness The test relied on sleep for synchronization. --- .../java/io/trino/cache/TestEmptyCache.java | 25 +++++++++++++------ .../io/trino/cache/TestEvictableCache.java | 23 +++++++++++------ 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/lib/trino-cache/src/test/java/io/trino/cache/TestEmptyCache.java b/lib/trino-cache/src/test/java/io/trino/cache/TestEmptyCache.java index 0572a9e3ab6e..4e26d40ae36d 100644 --- a/lib/trino-cache/src/test/java/io/trino/cache/TestEmptyCache.java +++ b/lib/trino-cache/src/test/java/io/trino/cache/TestEmptyCache.java @@ -20,12 +20,13 @@ import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Exchanger; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicBoolean; +import static io.trino.testing.assertions.Assert.assertEventually; import static java.util.concurrent.Executors.newFixedThreadPool; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; @@ -48,17 +49,25 @@ public void testLoadFailure() ExecutorService executor = newFixedThreadPool(2); try { - AtomicBoolean first = new AtomicBoolean(true); - CyclicBarrier barrier = new CyclicBarrier(2); + Exchanger exchanger = new Exchanger<>(); + CountDownLatch secondUnblocked = new CountDownLatch(1); List> futures = new ArrayList<>(); for (int i = 0; i < 2; i++) { + boolean first = i == 0; futures.add(executor.submit(() -> { - barrier.await(10, SECONDS); + if (!first) { + // Wait for the first one to start the call + exchanger.exchange(Thread.currentThread(), 10, SECONDS); + // Prove that we are back in RUNNABLE state. + secondUnblocked.countDown(); + } return cache.get(key, () -> { - if (first.compareAndSet(true, false)) { - // first - Thread.sleep(1); // increase chances that second thread calls cache.get before we return + if (first) { + Thread secondThread = exchanger.exchange(null, 10, SECONDS); + assertThat(secondUnblocked.await(10, SECONDS)).isTrue(); + // Wait for the second one to hang inside the cache.get call. + assertEventually(() -> assertThat(secondThread.getState()).isNotEqualTo(Thread.State.RUNNABLE)); throw new RuntimeException("first attempt is poised to fail"); } return "success"; diff --git a/lib/trino-cache/src/test/java/io/trino/cache/TestEvictableCache.java b/lib/trino-cache/src/test/java/io/trino/cache/TestEvictableCache.java index 5fd8d27e2ba5..0c589503bea7 100644 --- a/lib/trino-cache/src/test/java/io/trino/cache/TestEvictableCache.java +++ b/lib/trino-cache/src/test/java/io/trino/cache/TestEvictableCache.java @@ -31,11 +31,11 @@ import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.Exchanger; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.IntStream; @@ -44,6 +44,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static io.trino.cache.CacheStatsAssertions.assertCacheStats; +import static io.trino.testing.assertions.Assert.assertEventually; import static java.lang.Math.toIntExact; import static java.lang.String.format; import static java.util.concurrent.Executors.newFixedThreadPool; @@ -300,17 +301,25 @@ public void testLoadFailure() ExecutorService executor = newFixedThreadPool(2); try { - AtomicBoolean first = new AtomicBoolean(true); - CyclicBarrier barrier = new CyclicBarrier(2); + Exchanger exchanger = new Exchanger<>(); + CountDownLatch secondUnblocked = new CountDownLatch(1); List> futures = new ArrayList<>(); for (int i = 0; i < 2; i++) { + boolean first = i == 0; futures.add(executor.submit(() -> { - barrier.await(10, SECONDS); + if (!first) { + // Wait for the first one to start the call + exchanger.exchange(Thread.currentThread(), 10, SECONDS); + // Prove that we are back in RUNNABLE state. + secondUnblocked.countDown(); + } return cache.get(key, () -> { - if (first.compareAndSet(true, false)) { - // first - Thread.sleep(1); // increase chances that second thread calls cache.get before we return + if (first) { + Thread secondThread = exchanger.exchange(null, 10, SECONDS); + assertThat(secondUnblocked.await(10, SECONDS)).isTrue(); + // Wait for the second one to hang inside the cache.get call. + assertEventually(() -> assertThat(secondThread.getState()).isNotEqualTo(Thread.State.RUNNABLE)); throw new RuntimeException("first attempt is poised to fail"); } return "success"; From 2fbbd41ff0298c7c2f3acfe8e2d49af627ec63c0 Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Tue, 28 Nov 2023 17:00:40 +0900 Subject: [PATCH 499/587] Run BigQuery connector test concurrently --- .../io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java index 22097631bc4f..22dc47413307 100644 --- a/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java +++ b/plugin/trino-bigquery/src/test/java/io/trino/plugin/bigquery/BaseBigQueryConnectorTest.java @@ -26,6 +26,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; import java.util.List; import java.util.Optional; @@ -47,8 +48,10 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assumptions.abort; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; @TestInstance(PER_CLASS) +@Execution(CONCURRENT) public abstract class BaseBigQueryConnectorTest extends BaseConnectorTest { From 5d404831c6259beaf58115693190d9cbd3a8b027 Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Tue, 28 Nov 2023 18:32:29 +0900 Subject: [PATCH 500/587] Close BigQueryWriteClient in BigQueryPageSink --- .../java/io/trino/plugin/bigquery/BigQueryPageSink.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryPageSink.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryPageSink.java index 7a969aad5547..bf378f654eb9 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryPageSink.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryPageSink.java @@ -119,11 +119,15 @@ private void insertWithCommitted(JSONArray batch) @Override public CompletableFuture> finish() { + client.close(); Slice value = Slices.allocate(Long.BYTES); value.setLong(0, pageSinkId.getId()); return completedFuture(ImmutableList.of(value)); } @Override - public void abort() {} + public void abort() + { + client.close(); + } } From 154e994433ec7d8f4bec5572a82d0d59c3695f9c Mon Sep 17 00:00:00 2001 From: Ashhar Hasan Date: Tue, 10 Oct 2023 14:22:57 +0530 Subject: [PATCH 501/587] Rename method to clarify scope DefaultJdbcMetadata#createSyntheticColumn is meant to only be used to build the projections for join pushdown and not in other places. This commit renames the method to createSyntheticJoinProjectionColumn to clarify this since in the next commit a similar method is being introduced for aggregation pushdown and the existing name causes confusion. --- .../java/io/trino/plugin/jdbc/DefaultJdbcMetadata.java | 6 +++--- .../io/trino/plugin/jdbc/TestDefaultJdbcMetadata.java | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadata.java b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadata.java index 13c2e7f75fd8..24a8613c1f5e 100644 --- a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadata.java +++ b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadata.java @@ -460,14 +460,14 @@ public Optional> applyJoin( ImmutableMap.Builder newLeftColumnsBuilder = ImmutableMap.builder(); for (JdbcColumnHandle column : jdbcClient.getColumns(session, leftHandle)) { - newLeftColumnsBuilder.put(column, createSyntheticColumn(column, nextSyntheticColumnId)); + newLeftColumnsBuilder.put(column, createSyntheticJoinProjectionColumn(column, nextSyntheticColumnId)); nextSyntheticColumnId++; } Map newLeftColumns = newLeftColumnsBuilder.buildOrThrow(); ImmutableMap.Builder newRightColumnsBuilder = ImmutableMap.builder(); for (JdbcColumnHandle column : jdbcClient.getColumns(session, rightHandle)) { - newRightColumnsBuilder.put(column, createSyntheticColumn(column, nextSyntheticColumnId)); + newRightColumnsBuilder.put(column, createSyntheticJoinProjectionColumn(column, nextSyntheticColumnId)); nextSyntheticColumnId++; } Map newRightColumns = newRightColumnsBuilder.buildOrThrow(); @@ -525,7 +525,7 @@ public Optional> applyJoin( } @VisibleForTesting - static JdbcColumnHandle createSyntheticColumn(JdbcColumnHandle column, int nextSyntheticColumnId) + static JdbcColumnHandle createSyntheticJoinProjectionColumn(JdbcColumnHandle column, int nextSyntheticColumnId) { verify(nextSyntheticColumnId >= 0, "nextSyntheticColumnId rolled over and is not monotonically increasing any more"); diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestDefaultJdbcMetadata.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestDefaultJdbcMetadata.java index 81b09fba8f79..8aaaa77b0710 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestDefaultJdbcMetadata.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestDefaultJdbcMetadata.java @@ -44,7 +44,7 @@ import java.util.function.Function; import static io.airlift.slice.Slices.utf8Slice; -import static io.trino.plugin.jdbc.DefaultJdbcMetadata.createSyntheticColumn; +import static io.trino.plugin.jdbc.DefaultJdbcMetadata.createSyntheticJoinProjectionColumn; import static io.trino.plugin.jdbc.TestingJdbcTypeHandle.JDBC_BIGINT; import static io.trino.plugin.jdbc.TestingJdbcTypeHandle.JDBC_VARCHAR; import static io.trino.spi.StandardErrorCode.NOT_FOUND; @@ -402,11 +402,11 @@ public void testMultiGroupKeyPredicatePushdown() @Test public void testColumnAliasTruncation() { - assertThat(createSyntheticColumn(column("column_0"), 999).getColumnName()) + assertThat(createSyntheticJoinProjectionColumn(column("column_0"), 999).getColumnName()) .isEqualTo("column_0_999"); - assertThat(createSyntheticColumn(column("column_with_over_twenty_characters"), 100).getColumnName()) + assertThat(createSyntheticJoinProjectionColumn(column("column_with_over_twenty_characters"), 100).getColumnName()) .isEqualTo("column_with_over_twenty_ch_100"); - assertThat(createSyntheticColumn(column("column_with_over_twenty_characters"), Integer.MAX_VALUE).getColumnName()) + assertThat(createSyntheticJoinProjectionColumn(column("column_with_over_twenty_characters"), Integer.MAX_VALUE).getColumnName()) .isEqualTo("column_with_over_tw_2147483647"); } @@ -415,7 +415,7 @@ public void testNegativeSyntheticId() { JdbcColumnHandle column = column("column_0"); - assertThatThrownBy(() -> createSyntheticColumn(column, -2147483648)).isInstanceOf(VerifyException.class); + assertThatThrownBy(() -> createSyntheticJoinProjectionColumn(column, -2147483648)).isInstanceOf(VerifyException.class); } private static JdbcColumnHandle column(String columnName) From d876e9de300f5cea10f37c51893570a1b00ced4a Mon Sep 17 00:00:00 2001 From: Ashhar Hasan Date: Sun, 10 Sep 2023 15:28:54 +0530 Subject: [PATCH 502/587] Verify aggregation pushdown isn't affected by column name length limits In 5e520fcea34149c223090b51f781e93f73165a76 we introduced changes which prevent a long synthetic column name to be pushed down into remote database during join pushdown leading to query failures because for example Oracle 12 and older have a small limit of 30 characters. The only other pushdown operation which adds synthetic columns in the pushed down part is aggregation pushdown. However it's not practically affected since the synthetic aggregation columns are always of the form `_pfgnrtd_ + nextSyntheticColumnId` which means it's limited to 9 + 10 characters which is small enough to be supported by all databases. --- .../plugin/jdbc/DefaultJdbcMetadata.java | 23 ++++++++++++------- .../plugin/jdbc/TestDefaultJdbcMetadata.java | 13 +++++++++++ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadata.java b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadata.java index 24a8613c1f5e..9567c967e5cb 100644 --- a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadata.java +++ b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadata.java @@ -391,19 +391,13 @@ public Optional> applyAggrega return Optional.empty(); } - String columnName = SYNTHETIC_COLUMN_NAME_PREFIX + nextSyntheticColumnId; + JdbcColumnHandle newColumn = createSyntheticAggregationColumn(aggregate, expression.get().getJdbcTypeHandle(), nextSyntheticColumnId); nextSyntheticColumnId++; - JdbcColumnHandle newColumn = JdbcColumnHandle.builder() - .setColumnName(columnName) - .setJdbcTypeHandle(expression.get().getJdbcTypeHandle()) - .setColumnType(aggregate.getOutputType()) - .setComment(Optional.of("synthetic")) - .build(); newColumns.add(newColumn); projections.add(new Variable(newColumn.getColumnName(), aggregate.getOutputType())); resultAssignments.add(new Assignment(newColumn.getColumnName(), newColumn, aggregate.getOutputType())); - expressions.put(columnName, new ParameterizedExpression(expression.get().getExpression(), expression.get().getParameters())); + expressions.put(newColumn.getColumnName(), new ParameterizedExpression(expression.get().getExpression(), expression.get().getParameters())); } List newColumnsList = newColumns.build(); @@ -431,6 +425,19 @@ public Optional> applyAggrega return Optional.of(new AggregationApplicationResult<>(handle, projections.build(), resultAssignments.build(), ImmutableMap.of(), precalculateStatisticsForPushdown)); } + @VisibleForTesting + static JdbcColumnHandle createSyntheticAggregationColumn(AggregateFunction aggregate, JdbcTypeHandle typeHandle, int nextSyntheticColumnId) + { + // the new column can be max len(SYNTHETIC_COLUMN_NAME_PREFIX) + len(Integer.MAX_VALUE) = 9 + 10 = 19 characters which is small enough to be supported by all databases + String columnName = SYNTHETIC_COLUMN_NAME_PREFIX + nextSyntheticColumnId; + return JdbcColumnHandle.builder() + .setColumnName(columnName) + .setJdbcTypeHandle(typeHandle) + .setColumnType(aggregate.getOutputType()) + .setComment(Optional.of("synthetic")) + .build(); + } + @Override public Optional> applyJoin( ConnectorSession session, diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestDefaultJdbcMetadata.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestDefaultJdbcMetadata.java index 8aaaa77b0710..39877d6257ad 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestDefaultJdbcMetadata.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestDefaultJdbcMetadata.java @@ -44,6 +44,7 @@ import java.util.function.Function; import static io.airlift.slice.Slices.utf8Slice; +import static io.trino.plugin.jdbc.DefaultJdbcMetadata.createSyntheticAggregationColumn; import static io.trino.plugin.jdbc.DefaultJdbcMetadata.createSyntheticJoinProjectionColumn; import static io.trino.plugin.jdbc.TestingJdbcTypeHandle.JDBC_BIGINT; import static io.trino.plugin.jdbc.TestingJdbcTypeHandle.JDBC_VARCHAR; @@ -418,6 +419,18 @@ public void testNegativeSyntheticId() assertThatThrownBy(() -> createSyntheticJoinProjectionColumn(column, -2147483648)).isInstanceOf(VerifyException.class); } + @Test + public void testAggregationColumnAliasMaxLength() + { + // this test is to ensure that the generated names for aggregation pushdown are short enough to be supported by all databases. + // Oracle has the smallest limit at 30 so any value smaller than that is acceptable. + assertThat(createSyntheticAggregationColumn( + new AggregateFunction("count", BIGINT, List.of(), List.of(), false, Optional.empty()), + JDBC_BIGINT, + Integer.MAX_VALUE).getColumnName().length()) + .isEqualTo(19); + } + private static JdbcColumnHandle column(String columnName) { return JdbcColumnHandle.builder() From 5c66b8b6ddf2871af36f5d4964c6789e59a9344b Mon Sep 17 00:00:00 2001 From: Ashhar Hasan Date: Sun, 10 Sep 2023 21:14:36 +0530 Subject: [PATCH 503/587] Introduce JdbcClient#getMaxColumnNameLength This will be used to customise behaviour of DefaultJdbcMetadata#createSyntheticJoinProjectionColumn according to specific connector being used in the next commit. --- .../main/java/io/trino/plugin/jdbc/CachingJdbcClient.java | 6 ++++++ .../java/io/trino/plugin/jdbc/ForwardingJdbcClient.java | 6 ++++++ .../src/main/java/io/trino/plugin/jdbc/JdbcClient.java | 5 +++++ .../io/trino/plugin/jdbc/jmx/StatisticsAwareJdbcClient.java | 6 ++++++ 4 files changed, 23 insertions(+) diff --git a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/CachingJdbcClient.java b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/CachingJdbcClient.java index 1a2ed089609f..032d9895ca1b 100644 --- a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/CachingJdbcClient.java +++ b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/CachingJdbcClient.java @@ -560,6 +560,12 @@ public OptionalInt getMaxWriteParallelism(ConnectorSession session) return delegate.getMaxWriteParallelism(session); } + @Override + public OptionalInt getMaxColumnNameLength(ConnectorSession session) + { + return delegate.getMaxColumnNameLength(session); + } + public void onDataChanged(SchemaTableName table) { invalidateAllIf(statisticsCache, key -> key.mayReference(table)); diff --git a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/ForwardingJdbcClient.java b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/ForwardingJdbcClient.java index 0e70ec7beb94..2f784634a44b 100644 --- a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/ForwardingJdbcClient.java +++ b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/ForwardingJdbcClient.java @@ -450,4 +450,10 @@ public OptionalInt getMaxWriteParallelism(ConnectorSession session) { return delegate().getMaxWriteParallelism(session); } + + @Override + public OptionalInt getMaxColumnNameLength(ConnectorSession session) + { + return delegate().getMaxColumnNameLength(session); + } } diff --git a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/JdbcClient.java b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/JdbcClient.java index 9cb1e5ed7b43..38e6f7e80114 100644 --- a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/JdbcClient.java +++ b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/JdbcClient.java @@ -239,4 +239,9 @@ default Optional getTableScanRedirection(Con OptionalLong update(ConnectorSession session, JdbcTableHandle handle); OptionalInt getMaxWriteParallelism(ConnectorSession session); + + default OptionalInt getMaxColumnNameLength(ConnectorSession session) + { + return OptionalInt.empty(); + } } diff --git a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/jmx/StatisticsAwareJdbcClient.java b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/jmx/StatisticsAwareJdbcClient.java index ae1e01d029e9..d07bcfa5178c 100644 --- a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/jmx/StatisticsAwareJdbcClient.java +++ b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/jmx/StatisticsAwareJdbcClient.java @@ -470,4 +470,10 @@ public OptionalInt getMaxWriteParallelism(ConnectorSession session) { return delegate().getMaxWriteParallelism(session); } + + @Override + public OptionalInt getMaxColumnNameLength(ConnectorSession session) + { + return delegate().getMaxColumnNameLength(session); + } } From 5ad2c4450e2a62d3c2fc603963d2ee7f3f3e26c8 Mon Sep 17 00:00:00 2001 From: Ashhar Hasan Date: Wed, 11 Oct 2023 12:27:02 +0530 Subject: [PATCH 504/587] Allow having different column length limits per connector Before this change we hard-coded the column name length limits to that of Oracle 11g. This change allows each connector to provide their own length limits so that instead of always eagerly truncating synthetic columns we only do so when required. --- .../plugin/jdbc/DefaultJdbcMetadata.java | 16 ++++++++----- .../plugin/jdbc/TestDefaultJdbcMetadata.java | 9 +++---- .../io/trino/plugin/oracle/OracleClient.java | 24 +++++++++++++++++++ .../oracle/TestOracleConnectorTest.java | 4 ++-- 4 files changed, 41 insertions(+), 12 deletions(-) diff --git a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadata.java b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadata.java index 9567c967e5cb..c652ac374c82 100644 --- a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadata.java +++ b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadata.java @@ -107,8 +107,6 @@ public class DefaultJdbcMetadata implements JdbcMetadata { - public static final int DEFAULT_COLUMN_ALIAS_LENGTH = 30; - private static final String SYNTHETIC_COLUMN_NAME_PREFIX = "_pfgnrtd_"; private static final String DELETE_ROW_ID = "_trino_artificial_column_handle_for_delete_row_id_"; private static final String MERGE_ROW_ID = "$merge_row_id"; @@ -466,15 +464,16 @@ public Optional> applyJoin( int nextSyntheticColumnId = max(leftHandle.getNextSyntheticColumnId(), rightHandle.getNextSyntheticColumnId()); ImmutableMap.Builder newLeftColumnsBuilder = ImmutableMap.builder(); + OptionalInt maxColumnNameLength = jdbcClient.getMaxColumnNameLength(session); for (JdbcColumnHandle column : jdbcClient.getColumns(session, leftHandle)) { - newLeftColumnsBuilder.put(column, createSyntheticJoinProjectionColumn(column, nextSyntheticColumnId)); + newLeftColumnsBuilder.put(column, createSyntheticJoinProjectionColumn(column, nextSyntheticColumnId, maxColumnNameLength)); nextSyntheticColumnId++; } Map newLeftColumns = newLeftColumnsBuilder.buildOrThrow(); ImmutableMap.Builder newRightColumnsBuilder = ImmutableMap.builder(); for (JdbcColumnHandle column : jdbcClient.getColumns(session, rightHandle)) { - newRightColumnsBuilder.put(column, createSyntheticJoinProjectionColumn(column, nextSyntheticColumnId)); + newRightColumnsBuilder.put(column, createSyntheticJoinProjectionColumn(column, nextSyntheticColumnId, maxColumnNameLength)); nextSyntheticColumnId++; } Map newRightColumns = newRightColumnsBuilder.buildOrThrow(); @@ -532,12 +531,17 @@ public Optional> applyJoin( } @VisibleForTesting - static JdbcColumnHandle createSyntheticJoinProjectionColumn(JdbcColumnHandle column, int nextSyntheticColumnId) + static JdbcColumnHandle createSyntheticJoinProjectionColumn(JdbcColumnHandle column, int nextSyntheticColumnId, OptionalInt optionalMaxColumnNameLength) { verify(nextSyntheticColumnId >= 0, "nextSyntheticColumnId rolled over and is not monotonically increasing any more"); + if (optionalMaxColumnNameLength.isEmpty()) { + return JdbcColumnHandle.builderFrom(column) + .setColumnName(column.getColumnName() + "_" + nextSyntheticColumnId) + .build(); + } int sequentialNumberLength = String.valueOf(nextSyntheticColumnId).length(); - int originalColumnNameLength = DEFAULT_COLUMN_ALIAS_LENGTH - sequentialNumberLength - "_".length(); + int originalColumnNameLength = optionalMaxColumnNameLength.getAsInt() - sequentialNumberLength - "_".length(); String columnNameTruncated = fixedLength(originalColumnNameLength) .split(column.getColumnName()) diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestDefaultJdbcMetadata.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestDefaultJdbcMetadata.java index 39877d6257ad..93a09e45d89f 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestDefaultJdbcMetadata.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestDefaultJdbcMetadata.java @@ -41,6 +41,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.OptionalInt; import java.util.function.Function; import static io.airlift.slice.Slices.utf8Slice; @@ -403,11 +404,11 @@ public void testMultiGroupKeyPredicatePushdown() @Test public void testColumnAliasTruncation() { - assertThat(createSyntheticJoinProjectionColumn(column("column_0"), 999).getColumnName()) + assertThat(createSyntheticJoinProjectionColumn(column("column_0"), 999, OptionalInt.of(30)).getColumnName()) .isEqualTo("column_0_999"); - assertThat(createSyntheticJoinProjectionColumn(column("column_with_over_twenty_characters"), 100).getColumnName()) + assertThat(createSyntheticJoinProjectionColumn(column("column_with_over_twenty_characters"), 100, OptionalInt.of(30)).getColumnName()) .isEqualTo("column_with_over_twenty_ch_100"); - assertThat(createSyntheticJoinProjectionColumn(column("column_with_over_twenty_characters"), Integer.MAX_VALUE).getColumnName()) + assertThat(createSyntheticJoinProjectionColumn(column("column_with_over_twenty_characters"), Integer.MAX_VALUE, OptionalInt.of(30)).getColumnName()) .isEqualTo("column_with_over_tw_2147483647"); } @@ -416,7 +417,7 @@ public void testNegativeSyntheticId() { JdbcColumnHandle column = column("column_0"); - assertThatThrownBy(() -> createSyntheticJoinProjectionColumn(column, -2147483648)).isInstanceOf(VerifyException.class); + assertThatThrownBy(() -> createSyntheticJoinProjectionColumn(column, -2147483648, OptionalInt.of(30))).isInstanceOf(VerifyException.class); } @Test diff --git a/plugin/trino-oracle/src/main/java/io/trino/plugin/oracle/OracleClient.java b/plugin/trino-oracle/src/main/java/io/trino/plugin/oracle/OracleClient.java index eeeaccb62ac9..b252cabe552d 100644 --- a/plugin/trino-oracle/src/main/java/io/trino/plugin/oracle/OracleClient.java +++ b/plugin/trino-oracle/src/main/java/io/trino/plugin/oracle/OracleClient.java @@ -89,6 +89,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.OptionalInt; import java.util.Set; import java.util.function.BiFunction; @@ -215,6 +216,8 @@ public class OracleClient private final ConnectorExpressionRewriter connectorExpressionRewriter; private final AggregateFunctionRewriter aggregateFunctionRewriter; + private Integer maxColumnNameLength; + @Inject public OracleClient( BaseJdbcConfig config, @@ -348,6 +351,27 @@ public void renameSchema(ConnectorSession session, String schemaName, String new throw new TrinoException(NOT_SUPPORTED, "This connector does not support renaming schemas"); } + @Override + public OptionalInt getMaxColumnNameLength(ConnectorSession session) + { + if (maxColumnNameLength != null) { + // According to JavaDoc of DatabaseMetaData#getMaxColumnNameLength a value of 0 signifies that the limit is unknown + if (maxColumnNameLength == 0) { + return OptionalInt.empty(); + } + + return OptionalInt.of(maxColumnNameLength); + } + + try (Connection connection = connectionFactory.openConnection(session)) { + maxColumnNameLength = connection.getMetaData().getMaxColumnNameLength(); + return OptionalInt.of(maxColumnNameLength); + } + catch (SQLException e) { + throw new TrinoException(JDBC_ERROR, e); + } + } + @Override public Optional getTableComment(ResultSet resultSet) throws SQLException diff --git a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleConnectorTest.java b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleConnectorTest.java index a372c5171e48..d213183f9396 100644 --- a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleConnectorTest.java +++ b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleConnectorTest.java @@ -22,7 +22,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; -import static io.trino.plugin.jdbc.DefaultJdbcMetadata.DEFAULT_COLUMN_ALIAS_LENGTH; import static io.trino.plugin.oracle.TestingOracleServer.TEST_PASS; import static io.trino.plugin.oracle.TestingOracleServer.TEST_SCHEMA; import static io.trino.plugin.oracle.TestingOracleServer.TEST_USER; @@ -36,7 +35,8 @@ public class TestOracleConnectorTest extends BaseOracleConnectorTest { - private static final String MAXIMUM_LENGTH_COLUMN_IDENTIFIER = "z".repeat(DEFAULT_COLUMN_ALIAS_LENGTH); + // older Oracle versions are limited to 30 character identifier names + private static final String MAXIMUM_LENGTH_COLUMN_IDENTIFIER = "z".repeat(30); private TestingOracleServer oracleServer; From 1c17c95a72537d432924a31bcb40e7fb3250e06b Mon Sep 17 00:00:00 2001 From: Ashhar Hasan Date: Wed, 11 Oct 2023 20:53:08 +0530 Subject: [PATCH 505/587] Handle edge cases in createSyntheticJoinProjectionColumn In the previous implementation some conditions didn't need to be handled because the max length limit was known statically which made some conditions impossible. --- .../plugin/jdbc/DefaultJdbcMetadata.java | 27 +++++++++++---- .../plugin/jdbc/TestDefaultJdbcMetadata.java | 33 ++++++++++++++----- 2 files changed, 45 insertions(+), 15 deletions(-) diff --git a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadata.java b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadata.java index c652ac374c82..cee54f86096c 100644 --- a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadata.java +++ b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/DefaultJdbcMetadata.java @@ -534,22 +534,37 @@ public Optional> applyJoin( static JdbcColumnHandle createSyntheticJoinProjectionColumn(JdbcColumnHandle column, int nextSyntheticColumnId, OptionalInt optionalMaxColumnNameLength) { verify(nextSyntheticColumnId >= 0, "nextSyntheticColumnId rolled over and is not monotonically increasing any more"); + + final String separator = "_"; if (optionalMaxColumnNameLength.isEmpty()) { return JdbcColumnHandle.builderFrom(column) - .setColumnName(column.getColumnName() + "_" + nextSyntheticColumnId) + .setColumnName(column.getColumnName() + separator + nextSyntheticColumnId) + .build(); + } + + int maxColumnNameLength = optionalMaxColumnNameLength.getAsInt(); + int nextSyntheticColumnIdLength = String.valueOf(nextSyntheticColumnId).length(); + verify(maxColumnNameLength >= nextSyntheticColumnIdLength, "Maximum allowed column name length is %s but next synthetic id has length %s", maxColumnNameLength, nextSyntheticColumnIdLength); + + if (nextSyntheticColumnIdLength == maxColumnNameLength) { + return JdbcColumnHandle.builderFrom(column) + .setColumnName(String.valueOf(nextSyntheticColumnId)) .build(); } - int sequentialNumberLength = String.valueOf(nextSyntheticColumnId).length(); - int originalColumnNameLength = optionalMaxColumnNameLength.getAsInt() - sequentialNumberLength - "_".length(); + if (nextSyntheticColumnIdLength + separator.length() == maxColumnNameLength) { + return JdbcColumnHandle.builderFrom(column) + .setColumnName(separator + nextSyntheticColumnId) + .build(); + } - String columnNameTruncated = fixedLength(originalColumnNameLength) + // fixedLength only accepts values > 0, so the cases where the value would be <= 0 are handled above explicitly + String truncatedColumnName = fixedLength(maxColumnNameLength - separator.length() - nextSyntheticColumnIdLength) .split(column.getColumnName()) .iterator() .next(); - String columnName = columnNameTruncated + "_" + nextSyntheticColumnId; return JdbcColumnHandle.builderFrom(column) - .setColumnName(columnName) + .setColumnName(truncatedColumnName + separator + nextSyntheticColumnId) .build(); } diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestDefaultJdbcMetadata.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestDefaultJdbcMetadata.java index 93a09e45d89f..971049d827dc 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestDefaultJdbcMetadata.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/TestDefaultJdbcMetadata.java @@ -404,20 +404,35 @@ public void testMultiGroupKeyPredicatePushdown() @Test public void testColumnAliasTruncation() { - assertThat(createSyntheticJoinProjectionColumn(column("column_0"), 999, OptionalInt.of(30)).getColumnName()) - .isEqualTo("column_0_999"); - assertThat(createSyntheticJoinProjectionColumn(column("column_with_over_twenty_characters"), 100, OptionalInt.of(30)).getColumnName()) - .isEqualTo("column_with_over_twenty_ch_100"); - assertThat(createSyntheticJoinProjectionColumn(column("column_with_over_twenty_characters"), Integer.MAX_VALUE, OptionalInt.of(30)).getColumnName()) - .isEqualTo("column_with_over_tw_2147483647"); + OptionalInt maxLength = OptionalInt.of(30); + assertThat(createSyntheticJoinProjectionColumn(column("no_truncation"), 123, maxLength).getColumnName()) + .isEqualTo("no_truncation_123"); + assertThat(createSyntheticJoinProjectionColumn(column("long_column_name_gets_truncated"), 123, maxLength).getColumnName()) + .isEqualTo("long_column_name_gets_trun_123"); + assertThat(createSyntheticJoinProjectionColumn(column("long_id_causes_truncation"), Integer.MAX_VALUE, maxLength).getColumnName()) + .isEqualTo("long_id_causes_trun_2147483647"); + + assertThat(createSyntheticJoinProjectionColumn(column("id_equals_max_length"), 1234, OptionalInt.of(4)).getColumnName()) + .isEqualTo("1234"); + assertThat(createSyntheticJoinProjectionColumn(column("id_and_separator_equals_max_length"), 1234, OptionalInt.of(5)).getColumnName()) + .isEqualTo("_1234"); } @Test - public void testNegativeSyntheticId() + public void testSyntheticIdExceedsLength() { - JdbcColumnHandle column = column("column_0"); + assertThatThrownBy(() -> createSyntheticJoinProjectionColumn(column("id_exceeds_max_length"), 1234, OptionalInt.of(3))) + .isInstanceOf(VerifyException.class) + .hasMessage("Maximum allowed column name length is 3 but next synthetic id has length 4"); + } - assertThatThrownBy(() -> createSyntheticJoinProjectionColumn(column, -2147483648, OptionalInt.of(30))).isInstanceOf(VerifyException.class); + @Test + public void testNegativeSyntheticId() + { + //noinspection NumericOverflow + assertThatThrownBy(() -> createSyntheticJoinProjectionColumn(column("negative_id"), Integer.MAX_VALUE + 1, OptionalInt.of(30))) + .isInstanceOf(VerifyException.class) + .hasMessage("nextSyntheticColumnId rolled over and is not monotonically increasing any more"); } @Test From 21c6dcbe27e2ba3c27d8c807ad9ef698fd5df8ad Mon Sep 17 00:00:00 2001 From: Ashhar Hasan Date: Fri, 13 Oct 2023 21:03:02 +0530 Subject: [PATCH 506/587] Apply generated column name truncation to all affected connectors This change also applies the generated column name truncation for join pushdown to Postgres, Redshift and SQL Server. Postgres and Redshift silently truncate long identifiers unlike Oracle and SQL Server which fail on long identifiers. However note that there was no correctness issue because even after silently truncating an identifier Postgres and Redshift can identify that ambiguous identifiers exist and fail the query. --- .../io/trino/plugin/jdbc/BaseJdbcClient.java | 21 +++++++++++++++++++ .../plugin/jdbc/BaseJdbcConnectorTest.java | 20 ++++++++++++++++++ .../io/trino/plugin/oracle/OracleClient.java | 19 +---------------- .../oracle/TestOracleConnectorTest.java | 17 --------------- .../plugin/postgresql/PostgreSqlClient.java | 7 +++++++ .../trino/plugin/redshift/RedshiftClient.java | 7 +++++++ .../plugin/sqlserver/SqlServerClient.java | 7 +++++++ 7 files changed, 63 insertions(+), 35 deletions(-) diff --git a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/BaseJdbcClient.java b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/BaseJdbcClient.java index a53f6651b8a3..a8150d624563 100644 --- a/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/BaseJdbcClient.java +++ b/plugin/trino-base-jdbc/src/main/java/io/trino/plugin/jdbc/BaseJdbcClient.java @@ -114,6 +114,7 @@ public abstract class BaseJdbcClient private final IdentifierMapping identifierMapping; private final boolean supportsRetries; private final JdbcRemoteIdentifiersFactory jdbcRemoteIdentifiersFactory = new JdbcRemoteIdentifiersFactory(this); + private Integer maxColumnNameLength; public BaseJdbcClient( String identifierQuote, @@ -1435,6 +1436,26 @@ public OptionalInt getMaxWriteParallelism(ConnectorSession session) return OptionalInt.of(getWriteParallelism(session)); } + protected OptionalInt getMaxColumnNameLengthFromDatabaseMetaData(ConnectorSession session) + { + if (maxColumnNameLength != null) { + // According to JavaDoc of DatabaseMetaData#getMaxColumnNameLength a value of 0 signifies that the limit is unknown + if (maxColumnNameLength == 0) { + return OptionalInt.empty(); + } + + return OptionalInt.of(maxColumnNameLength); + } + + try (Connection connection = connectionFactory.openConnection(session)) { + maxColumnNameLength = connection.getMetaData().getMaxColumnNameLength(); + return OptionalInt.of(maxColumnNameLength); + } + catch (SQLException e) { + throw new TrinoException(JDBC_ERROR, e); + } + } + protected void verifySchemaName(DatabaseMetaData databaseMetadata, String schemaName) throws SQLException { diff --git a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/BaseJdbcConnectorTest.java b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/BaseJdbcConnectorTest.java index 4c1f14eacdb0..e2ea22c7d46a 100644 --- a/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/BaseJdbcConnectorTest.java +++ b/plugin/trino-base-jdbc/src/test/java/io/trino/plugin/jdbc/BaseJdbcConnectorTest.java @@ -2041,6 +2041,26 @@ protected TestTable simpleTable() return new TestTable(onRemoteDatabase(), format("%s.simple_table", getSession().getSchema().orElseThrow()), "(col BIGINT)", ImmutableList.of("1", "2")); } + @Test + public void testJoinPushdownWithLongIdentifiers() + { + skipTestUnless(hasBehavior(SUPPORTS_CREATE_TABLE) && hasBehavior(SUPPORTS_JOIN_PUSHDOWN)); + + String baseColumnName = "col"; + int maxLength = maxColumnNameLength() + // Assume 2^16 is enough for most use cases. Add a bit more to ensure 2^16 isn't actual limit. + .orElse(65536 + 5); + + String validColumnName = baseColumnName + "z".repeat(maxLength - baseColumnName.length()); + try (TestTable left = new TestTable(getQueryRunner()::execute, "test_long_id_l", format("(%s BIGINT)", validColumnName)); + TestTable right = new TestTable(getQueryRunner()::execute, "test_long_id_r", format("(%s BIGINT)", validColumnName))) { + assertThat(query(joinPushdownEnabled(getSession()), """ + SELECT l.%1$s, r.%1$s + FROM %2$s l JOIN %3$s r ON l.%1$s = r.%1$s""".formatted(validColumnName, left.getName(), right.getName()))) + .isFullyPushedDown(); + } + } + @Test public void testDynamicFiltering() { diff --git a/plugin/trino-oracle/src/main/java/io/trino/plugin/oracle/OracleClient.java b/plugin/trino-oracle/src/main/java/io/trino/plugin/oracle/OracleClient.java index b252cabe552d..47837cff4e3e 100644 --- a/plugin/trino-oracle/src/main/java/io/trino/plugin/oracle/OracleClient.java +++ b/plugin/trino-oracle/src/main/java/io/trino/plugin/oracle/OracleClient.java @@ -216,8 +216,6 @@ public class OracleClient private final ConnectorExpressionRewriter connectorExpressionRewriter; private final AggregateFunctionRewriter aggregateFunctionRewriter; - private Integer maxColumnNameLength; - @Inject public OracleClient( BaseJdbcConfig config, @@ -354,22 +352,7 @@ public void renameSchema(ConnectorSession session, String schemaName, String new @Override public OptionalInt getMaxColumnNameLength(ConnectorSession session) { - if (maxColumnNameLength != null) { - // According to JavaDoc of DatabaseMetaData#getMaxColumnNameLength a value of 0 signifies that the limit is unknown - if (maxColumnNameLength == 0) { - return OptionalInt.empty(); - } - - return OptionalInt.of(maxColumnNameLength); - } - - try (Connection connection = connectionFactory.openConnection(session)) { - maxColumnNameLength = connection.getMetaData().getMaxColumnNameLength(); - return OptionalInt.of(maxColumnNameLength); - } - catch (SQLException e) { - throw new TrinoException(JDBC_ERROR, e); - } + return getMaxColumnNameLengthFromDatabaseMetaData(session); } @Override diff --git a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleConnectorTest.java b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleConnectorTest.java index d213183f9396..440fdfcd25e7 100644 --- a/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleConnectorTest.java +++ b/plugin/trino-oracle/src/test/java/io/trino/plugin/oracle/TestOracleConnectorTest.java @@ -17,7 +17,6 @@ import io.airlift.testing.Closeables; import io.trino.testing.QueryRunner; import io.trino.testing.sql.SqlExecutor; -import io.trino.testing.sql.TestTable; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; @@ -28,16 +27,12 @@ import static java.lang.String.format; import static java.util.stream.Collectors.joining; import static java.util.stream.IntStream.range; -import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; @TestInstance(PER_CLASS) public class TestOracleConnectorTest extends BaseOracleConnectorTest { - // older Oracle versions are limited to 30 character identifier names - private static final String MAXIMUM_LENGTH_COLUMN_IDENTIFIER = "z".repeat(30); - private TestingOracleServer oracleServer; @Override @@ -103,16 +98,4 @@ public void execute(String sql) } }; } - - @Test - public void testPushdownJoinWithLongNameSucceeds() - { - try (TestTable table = new TestTable(getQueryRunner()::execute, "long_identifier", "(%s bigint)".formatted(MAXIMUM_LENGTH_COLUMN_IDENTIFIER))) { - assertThat(query(joinPushdownEnabled(getSession()), """ - SELECT r.name, t.%s, n.name - FROM %s t JOIN region r ON r.regionkey = t.%s - JOIN nation n ON r.regionkey = n.regionkey""".formatted(MAXIMUM_LENGTH_COLUMN_IDENTIFIER, table.getName(), MAXIMUM_LENGTH_COLUMN_IDENTIFIER))) - .isFullyPushedDown(); - } - } } diff --git a/plugin/trino-postgresql/src/main/java/io/trino/plugin/postgresql/PostgreSqlClient.java b/plugin/trino-postgresql/src/main/java/io/trino/plugin/postgresql/PostgreSqlClient.java index d062c849ad17..affd261a925b 100644 --- a/plugin/trino-postgresql/src/main/java/io/trino/plugin/postgresql/PostgreSqlClient.java +++ b/plugin/trino-postgresql/src/main/java/io/trino/plugin/postgresql/PostgreSqlClient.java @@ -131,6 +131,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.OptionalInt; import java.util.OptionalLong; import java.util.UUID; import java.util.function.BiFunction; @@ -918,6 +919,12 @@ public OptionalLong update(ConnectorSession session, JdbcTableHandle handle) } } + @Override + public OptionalInt getMaxColumnNameLength(ConnectorSession session) + { + return getMaxColumnNameLengthFromDatabaseMetaData(session); + } + @Override public TableStatistics getTableStatistics(ConnectorSession session, JdbcTableHandle handle) { diff --git a/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftClient.java b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftClient.java index 67184ff6604c..ba04e2693f02 100644 --- a/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftClient.java +++ b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/RedshiftClient.java @@ -102,6 +102,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.OptionalInt; import java.util.OptionalLong; import java.util.function.BiFunction; @@ -507,6 +508,12 @@ public OptionalLong update(ConnectorSession session, JdbcTableHandle handle) } } + @Override + public OptionalInt getMaxColumnNameLength(ConnectorSession session) + { + return getMaxColumnNameLengthFromDatabaseMetaData(session); + } + @Override protected void addColumn(ConnectorSession session, Connection connection, RemoteTableName table, ColumnMetadata column) throws SQLException diff --git a/plugin/trino-sqlserver/src/main/java/io/trino/plugin/sqlserver/SqlServerClient.java b/plugin/trino-sqlserver/src/main/java/io/trino/plugin/sqlserver/SqlServerClient.java index 29d9f937511f..f182a7f4e278 100644 --- a/plugin/trino-sqlserver/src/main/java/io/trino/plugin/sqlserver/SqlServerClient.java +++ b/plugin/trino-sqlserver/src/main/java/io/trino/plugin/sqlserver/SqlServerClient.java @@ -114,6 +114,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.OptionalInt; import java.util.function.BiFunction; import java.util.stream.Stream; @@ -1098,6 +1099,12 @@ public Map getTableProperties(ConnectorSession session, JdbcTabl } } + @Override + public OptionalInt getMaxColumnNameLength(ConnectorSession session) + { + return getMaxColumnNameLengthFromDatabaseMetaData(session); + } + @Override public void abortReadConnection(Connection connection, ResultSet resultSet) throws SQLException From 5b092b113f335dbb472ffbab334f372ceb430019 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Fri, 24 Nov 2023 12:53:43 +0100 Subject: [PATCH 507/587] Fix timestamp precision in Iceberg testTableChangesFunction The ISO_INSTANT use before the change does not render seconds fraction when it is zero, so the test failed when snapshot happened to be committed at a whole second. --- .../plugin/iceberg/BaseIcebergConnectorSmokeTest.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorSmokeTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorSmokeTest.java index b499a7309f15..df10f40055a1 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorSmokeTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorSmokeTest.java @@ -36,6 +36,7 @@ import org.junit.jupiter.api.Timeout; import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Optional; import java.util.concurrent.CyclicBarrier; @@ -53,7 +54,7 @@ import static io.trino.testing.TestingConnectorSession.SESSION; import static io.trino.testing.TestingNames.randomNameSuffix; import static java.lang.String.format; -import static java.time.format.DateTimeFormatter.ISO_INSTANT; +import static java.time.ZoneOffset.UTC; import static java.util.Objects.requireNonNull; import static java.util.concurrent.Executors.newFixedThreadPool; import static java.util.concurrent.TimeUnit.SECONDS; @@ -727,6 +728,8 @@ public void testPartitionFilterRequired() @Test public void testTableChangesFunction() { + DateTimeFormatter instantMillisFormatter = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSVV").withZone(UTC); + try (TestTable table = new TestTable( getQueryRunner()::execute, "test_table_changes_function_", @@ -734,7 +737,7 @@ public void testTableChangesFunction() long initialSnapshot = getMostRecentSnapshotId(table.getName()); assertUpdate("INSERT INTO " + table.getName() + " SELECT nationkey, name FROM nation", 25); long snapshotAfterInsert = getMostRecentSnapshotId(table.getName()); - String snapshotAfterInsertTime = getSnapshotTime(table.getName(), snapshotAfterInsert).format(ISO_INSTANT); + String snapshotAfterInsertTime = getSnapshotTime(table.getName(), snapshotAfterInsert).format(instantMillisFormatter); assertQuery( "SELECT nationkey, name, _change_type, _change_version_id, to_iso8601(_change_timestamp), _change_ordinal " + @@ -743,7 +746,7 @@ public void testTableChangesFunction() assertUpdate("DELETE FROM " + table.getName(), 25); long snapshotAfterDelete = getMostRecentSnapshotId(table.getName()); - String snapshotAfterDeleteTime = getSnapshotTime(table.getName(), snapshotAfterDelete).format(ISO_INSTANT); + String snapshotAfterDeleteTime = getSnapshotTime(table.getName(), snapshotAfterDelete).format(instantMillisFormatter); assertQuery( "SELECT nationkey, name, _change_type, _change_version_id, to_iso8601(_change_timestamp), _change_ordinal " + From 8f0fe79a7d9f07c0d707103b793eb8ca3fd945d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Osipiuk?= Date: Sun, 26 Nov 2023 13:24:08 +0100 Subject: [PATCH 508/587] Add AssignmentResult.isEmpty() --- .../EventDrivenFaultTolerantQueryScheduler.java | 3 +++ .../execution/scheduler/faulttolerant/SplitAssigner.java | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java index 4e6b7a05b131..67d7900161df 100644 --- a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java +++ b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java @@ -1422,6 +1422,9 @@ private void loadMoreTaskDescriptorsIfNecessary() @Override public void onSuccess(AssignmentResult result) { + // We need to process even empty events here so stageExecution.taskDescriptorLoadingComplete() + // is called in event handler. Otherwise, IdempotentSplitSource may be not called again + // if there is no other SplitAssignmentEvent for this stage in queue. eventQueue.add(new SplitAssignmentEvent(stageExecution.getStageId(), result)); } diff --git a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/SplitAssigner.java b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/SplitAssigner.java index 161bf7e01642..6fbeb6884d98 100644 --- a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/SplitAssigner.java +++ b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/SplitAssigner.java @@ -75,6 +75,11 @@ record AssignmentResult( partitionUpdates = ImmutableList.copyOf(requireNonNull(partitionUpdates, "partitionUpdates is null")); } + boolean isEmpty() + { + return partitionsAdded.isEmpty() && !noMorePartitions && partitionUpdates.isEmpty() && sealedPartitions.isEmpty(); + } + public static AssignmentResult.Builder builder() { return new AssignmentResult.Builder(); From e5060887ae3568c09f76da5ae85d21018550dec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Osipiuk?= Date: Sat, 25 Nov 2023 16:08:56 +0100 Subject: [PATCH 509/587] Add return type for EventDrivenFaultTolerantQueryScheduler.EventListener --- ...ventDrivenFaultTolerantQueryScheduler.java | 76 +++++++++++-------- 1 file changed, 45 insertions(+), 31 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java index 67d7900161df..3f99a198db58 100644 --- a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java +++ b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java @@ -495,7 +495,7 @@ public void failTaskRemotely(TaskId taskId, Throwable failureCause) } private static class Scheduler - implements EventListener + implements EventListener { private static final int EVENT_BUFFER_CAPACITY = 100; @@ -1351,7 +1351,7 @@ private void updateMemoryRequirements() } @Override - public void onSinkInstanceHandleAcquired(SinkInstanceHandleAcquiredEvent sinkInstanceHandleAcquiredEvent) + public Void onSinkInstanceHandleAcquired(SinkInstanceHandleAcquiredEvent sinkInstanceHandleAcquiredEvent) { ScheduledTask scheduledTask = new ScheduledTask(sinkInstanceHandleAcquiredEvent.getStageId(), sinkInstanceHandleAcquiredEvent.getPartitionId()); PreSchedulingTaskContext context = preSchedulingTaskContexts.remove(scheduledTask); @@ -1384,6 +1384,7 @@ public void onSinkInstanceHandleAcquired(SinkInstanceHandleAcquiredEvent sinkIns if (remoteTask.isEmpty()) { nodeLease.release(); } + return null; } private StateChangeListener createExchangeSinkInstanceHandleUpdateRequiredListener() @@ -1445,7 +1446,7 @@ public void abort() } @Override - public void onRemoteTaskCompleted(RemoteTaskCompletedEvent event) + public Void onRemoteTaskCompleted(RemoteTaskCompletedEvent event) { TaskStatus taskStatus = event.getTaskStatus(); TaskId taskId = taskStatus.getTaskId(); @@ -1475,26 +1476,29 @@ else if (taskState == TaskState.FAILED) { for (StageId consumerStageId : stageConsumers.get(stageExecution.getStageId())) { getStageExecution(consumerStageId).setSourceOutputSelector(stageExecution.getStageFragmentId(), outputSelector); } + return null; } @Override - public void onRemoteTaskExchangeSinkUpdateRequired(RemoteTaskExchangeSinkUpdateRequiredEvent event) + public Void onRemoteTaskExchangeSinkUpdateRequired(RemoteTaskExchangeSinkUpdateRequiredEvent event) { TaskId taskId = event.getTaskStatus().getTaskId(); StageExecution stageExecution = getStageExecution(taskId.getStageId()); stageExecution.initializeUpdateOfExchangeSinkInstanceHandle(taskId, eventQueue); + return null; } @Override - public void onRemoteTaskExchangeUpdatedSinkAcquired(RemoteTaskExchangeUpdatedSinkAcquired event) + public Void onRemoteTaskExchangeUpdatedSinkAcquired(RemoteTaskExchangeUpdatedSinkAcquired event) { TaskId taskId = event.getTaskId(); StageExecution stageExecution = getStageExecution(taskId.getStageId()); stageExecution.finalizeUpdateOfExchangeSinkInstanceHandle(taskId, event.getExchangeSinkInstanceHandle()); + return null; } @Override - public void onSplitAssignment(SplitAssignmentEvent event) + public Void onSplitAssignment(SplitAssignmentEvent event) { StageExecution stageExecution = getStageExecution(event.getStageId()); AssignmentResult assignment = event.getAssignmentResult(); @@ -1528,13 +1532,15 @@ public void onSplitAssignment(SplitAssignmentEvent event) stageExecution.noMorePartitions(); } stageExecution.taskDescriptorLoadingComplete(); + return null; } @Override - public void onStageFailure(StageFailureEvent event) + public Void onStageFailure(StageFailureEvent event) { StageExecution stageExecution = getStageExecution(event.getStageId()); stageExecution.fail(event.getFailure()); + return null; } private StageExecution getStageExecution(StageId stageId) @@ -2652,30 +2658,38 @@ public long getRemainingDelayInMillis() private interface Event { - Event ABORT = listener -> { - throw new UnsupportedOperationException(); + Event ABORT = new Event() { + @Override + public T accept(EventListener listener) + { + throw new UnsupportedOperationException(); + } }; - Event WAKE_UP = listener -> { - throw new UnsupportedOperationException(); + Event WAKE_UP = new Event() { + @Override + public T accept(EventListener listener) + { + throw new UnsupportedOperationException(); + } }; - void accept(EventListener listener); + T accept(EventListener listener); } - private interface EventListener + private interface EventListener { - void onRemoteTaskCompleted(RemoteTaskCompletedEvent event); + T onRemoteTaskCompleted(RemoteTaskCompletedEvent event); - void onRemoteTaskExchangeSinkUpdateRequired(RemoteTaskExchangeSinkUpdateRequiredEvent event); + T onRemoteTaskExchangeSinkUpdateRequired(RemoteTaskExchangeSinkUpdateRequiredEvent event); - void onRemoteTaskExchangeUpdatedSinkAcquired(RemoteTaskExchangeUpdatedSinkAcquired event); + T onRemoteTaskExchangeUpdatedSinkAcquired(RemoteTaskExchangeUpdatedSinkAcquired event); - void onSplitAssignment(SplitAssignmentEvent event); + T onSplitAssignment(SplitAssignmentEvent event); - void onStageFailure(StageFailureEvent event); + T onStageFailure(StageFailureEvent event); - void onSinkInstanceHandleAcquired(SinkInstanceHandleAcquiredEvent sinkInstanceHandleAcquiredEvent); + T onSinkInstanceHandleAcquired(SinkInstanceHandleAcquiredEvent sinkInstanceHandleAcquiredEvent); } private static class SinkInstanceHandleAcquiredEvent @@ -2722,9 +2736,9 @@ public ExchangeSinkInstanceHandle getSinkInstanceHandle() } @Override - public void accept(EventListener listener) + public T accept(EventListener listener) { - listener.onSinkInstanceHandleAcquired(this); + return listener.onSinkInstanceHandleAcquired(this); } } @@ -2737,9 +2751,9 @@ public RemoteTaskCompletedEvent(TaskStatus taskStatus) } @Override - public void accept(EventListener listener) + public T accept(EventListener listener) { - listener.onRemoteTaskCompleted(this); + return listener.onRemoteTaskCompleted(this); } } @@ -2752,9 +2766,9 @@ protected RemoteTaskExchangeSinkUpdateRequiredEvent(TaskStatus taskStatus) } @Override - public void accept(EventListener listener) + public T accept(EventListener listener) { - listener.onRemoteTaskExchangeSinkUpdateRequired(this); + return listener.onRemoteTaskExchangeSinkUpdateRequired(this); } } @@ -2771,9 +2785,9 @@ private RemoteTaskExchangeUpdatedSinkAcquired(TaskId taskId, ExchangeSinkInstanc } @Override - public void accept(EventListener listener) + public T accept(EventListener listener) { - listener.onRemoteTaskExchangeUpdatedSinkAcquired(this); + return listener.onRemoteTaskExchangeUpdatedSinkAcquired(this); } public TaskId getTaskId() @@ -2820,9 +2834,9 @@ public AssignmentResult getAssignmentResult() } @Override - public void accept(EventListener listener) + public T accept(EventListener listener) { - listener.onSplitAssignment(this); + return listener.onSplitAssignment(this); } } @@ -2843,9 +2857,9 @@ public Throwable getFailure() } @Override - public void accept(EventListener listener) + public T accept(EventListener listener) { - listener.onStageFailure(this); + return listener.onStageFailure(this); } } From 21a94dc9b831deac4534b0bfdfe6251d45fee968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Osipiuk?= Date: Sat, 25 Nov 2023 16:25:27 +0100 Subject: [PATCH 510/587] Add intermediate methods to EventListener Also add default implementations for EventListener methods which delegate to appropriate intermediate method according to class hierachy. --- ...ventDrivenFaultTolerantQueryScheduler.java | 45 ++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java index 3f99a198db58..4045fe35c6b4 100644 --- a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java +++ b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java @@ -2679,17 +2679,50 @@ public T accept(EventListener listener) private interface EventListener { - T onRemoteTaskCompleted(RemoteTaskCompletedEvent event); + default T onRemoteTaskCompleted(RemoteTaskCompletedEvent event) + { + return onRemoteTaskEvent(event); + } - T onRemoteTaskExchangeSinkUpdateRequired(RemoteTaskExchangeSinkUpdateRequiredEvent event); + default T onRemoteTaskExchangeSinkUpdateRequired(RemoteTaskExchangeSinkUpdateRequiredEvent event) + { + return onRemoteTaskEvent(event); + } - T onRemoteTaskExchangeUpdatedSinkAcquired(RemoteTaskExchangeUpdatedSinkAcquired event); + default T onRemoteTaskEvent(RemoteTaskEvent event) + { + return onEvent(event); + } - T onSplitAssignment(SplitAssignmentEvent event); + default T onRemoteTaskExchangeUpdatedSinkAcquired(RemoteTaskExchangeUpdatedSinkAcquired event) + { + return onEvent(event); + } - T onStageFailure(StageFailureEvent event); + default T onSplitAssignment(SplitAssignmentEvent event) + { + return onStageEvent(event); + } - T onSinkInstanceHandleAcquired(SinkInstanceHandleAcquiredEvent sinkInstanceHandleAcquiredEvent); + default T onStageFailure(StageFailureEvent event) + { + return onStageEvent(event); + } + + default T onStageEvent(StageEvent event) + { + return onEvent(event); + } + + default T onSinkInstanceHandleAcquired(SinkInstanceHandleAcquiredEvent event) + { + return onEvent(event); + } + + default T onEvent(Event event) + { + throw new RuntimeException("EventListener no implemented"); + } } private static class SinkInstanceHandleAcquiredEvent From dd1a0bc81b3980496c0547057c4bb1f796b7ea21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Osipiuk?= Date: Sun, 26 Nov 2023 13:32:34 +0100 Subject: [PATCH 511/587] Introduce constant --- .../faulttolerant/EventDrivenFaultTolerantQueryScheduler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java index 4045fe35c6b4..753f2a5707a6 100644 --- a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java +++ b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java @@ -498,6 +498,7 @@ private static class Scheduler implements EventListener { private static final int EVENT_BUFFER_CAPACITY = 100; + private static final long EVENT_PROCESSING_ENFORCED_FREQUENCY_MILLIS = new Duration(1, MINUTES).toMillis(); private final QueryStateMachine queryStateMachine; private final Metadata metadata; @@ -680,7 +681,7 @@ private Optional closeAndAddSuppressed(Optional existingFa private boolean processEvents() { try { - Event event = eventQueue.poll(1, MINUTES); + Event event = eventQueue.poll(EVENT_PROCESSING_ENFORCED_FREQUENCY_MILLIS, MILLISECONDS); if (event == null) { return true; } From 91e99e2537907217fb38c3306696b0115fd32c10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Osipiuk?= Date: Thu, 23 Nov 2023 17:51:37 +0100 Subject: [PATCH 512/587] Add diagnostics for stuck FTE scheduler Add code which will dump log debug information in case FTE scheduler is not getting any events for 10 minutes. This is to track rare bug where we observe queries running with retry_policy set to FALSE stuck sometimes. --- .../java/io/trino/execution/SqlStage.java | 14 +- .../buffer/SpoolingOutputBuffers.java | 10 + .../ArbitraryDistributionSplitAssigner.java | 38 ++ .../BinPackingNodeAllocatorService.java | 13 + ...ventDrivenFaultTolerantQueryScheduler.java | 393 +++++++++++++++++- .../faulttolerant/EventDrivenTaskSource.java | 41 ++ .../FaultTolerantPartitioningScheme.java | 12 + .../HashDistributionSplitAssigner.java | 33 ++ .../SingleDistributionSplitAssigner.java | 12 + .../io/trino/split/BufferingSplitSource.java | 10 + .../io/trino/split/TracingSplitSource.java | 9 + .../TestExchangeSourceOutputSelector.java | 14 + .../trino/spi/exchange/ExchangeContext.java | 11 + .../ExchangeSourceOutputSelector.java | 56 ++- .../filesystem/FileSystemExchange.java | 21 + .../FileSystemExchangeSinkInstanceHandle.java | 12 + 16 files changed, 689 insertions(+), 10 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/execution/SqlStage.java b/core/trino-main/src/main/java/io/trino/execution/SqlStage.java index 0d8a146ee925..b1a61d9a7582 100644 --- a/core/trino-main/src/main/java/io/trino/execution/SqlStage.java +++ b/core/trino-main/src/main/java/io/trino/execution/SqlStage.java @@ -40,6 +40,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; +import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableList.toImmutableList; import static io.trino.SystemSessionProperties.isEnableCoordinatorDynamicFiltersDistribution; @@ -329,9 +330,18 @@ private void checkAllTaskFinal() } @Override - public String toString() + // for debugging + public synchronized String toString() { - return stateMachine.toString(); + return toStringHelper(this) + .add("stateMachine", stateMachine) + .add("summarizeTaskInfo", summarizeTaskInfo) + .add("outboundDynamicFilterIds", outboundDynamicFilterIds) + .add("tasks", tasks) + .add("allTasks", allTasks) + .add("finishedTasks", finishedTasks) + .add("tasksWithFinalInfo", tasksWithFinalInfo) + .toString(); } private class MemoryUsageListener diff --git a/core/trino-main/src/main/java/io/trino/execution/buffer/SpoolingOutputBuffers.java b/core/trino-main/src/main/java/io/trino/execution/buffer/SpoolingOutputBuffers.java index 21244b328649..fa2cd5bebc27 100644 --- a/core/trino-main/src/main/java/io/trino/execution/buffer/SpoolingOutputBuffers.java +++ b/core/trino-main/src/main/java/io/trino/execution/buffer/SpoolingOutputBuffers.java @@ -17,6 +17,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import io.trino.spi.exchange.ExchangeSinkInstanceHandle; +import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; import static java.util.Objects.requireNonNull; @@ -76,4 +77,13 @@ public SpoolingOutputBuffers withExchangeSinkInstanceHandle(ExchangeSinkInstance { return new SpoolingOutputBuffers(getVersion() + 1, handle, outputPartitionCount); } + + @Override + public String toString() + { + return toStringHelper(this) + .add("exchangeSinkInstanceHandle", exchangeSinkInstanceHandle) + .add("outputPartitionCount", outputPartitionCount) + .toString(); + } } diff --git a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/ArbitraryDistributionSplitAssigner.java b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/ArbitraryDistributionSplitAssigner.java index 89d70cf94cbe..e875afba7845 100644 --- a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/ArbitraryDistributionSplitAssigner.java +++ b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/ArbitraryDistributionSplitAssigner.java @@ -35,6 +35,7 @@ import java.util.Optional; import java.util.Set; +import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Verify.verify; @@ -314,6 +315,32 @@ private AssignmentResult assignPartitionedSplits(PlanNodeId planNodeId, List getHostRequirement(Split split) { if (split.getConnectorSplit().isRemotelyAccessible()) { @@ -396,5 +423,16 @@ public void setFull(boolean full) { this.full = full; } + + @Override + public String toString() + { + return toStringHelper(this) + .add("partitionId", partitionId) + .add("assignedDataSizeInBytes", assignedDataSizeInBytes) + .add("assignedSplitCount", assignedSplitCount) + .add("full", full) + .toString(); + } } } diff --git a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/BinPackingNodeAllocatorService.java b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/BinPackingNodeAllocatorService.java index c77fab6592cf..d6757fbce2d2 100644 --- a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/BinPackingNodeAllocatorService.java +++ b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/BinPackingNodeAllocatorService.java @@ -59,6 +59,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; +import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableList.toImmutableList; @@ -459,6 +460,18 @@ public void release() throw new IllegalStateException("Node " + node + " already released"); } } + + @Override + public String toString() + { + return toStringHelper(this) + .add("node", node) + .add("released", released) + .add("memoryLease", memoryLease) + .add("taskId", taskId) + .add("executionClass", executionClass) + .toString(); + } } private static class BinPackingSimulation diff --git a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java index 753f2a5707a6..5789a6b7a679 100644 --- a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java +++ b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java @@ -20,6 +20,7 @@ import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.ListMultimap; import com.google.common.collect.Multimaps; import com.google.common.collect.SetMultimap; @@ -108,6 +109,7 @@ import java.io.Closeable; import java.io.IOException; import java.io.UncheckedIOException; +import java.lang.ref.SoftReference; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -129,6 +131,7 @@ import java.util.function.Function; import java.util.function.IntConsumer; +import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Verify.verify; @@ -181,6 +184,7 @@ import static java.lang.Math.round; import static java.lang.Math.toIntExact; import static java.lang.String.format; +import static java.util.Map.Entry.comparingByKey; import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MINUTES; @@ -492,13 +496,163 @@ public void failTaskRemotely(TaskId taskId, Throwable failureCause) SqlStage sqlStage = requireNonNull(stages.get(taskId.getStageId()), () -> "stage not found: %s" + taskId.getStageId()); sqlStage.failTaskRemotely(taskId, failureCause); } + + public void logDebugInfo() + { + if (!log.isDebugEnabled()) { + return; + } + log.debug("SqlStages:"); + stages.forEach((stageId, stage) -> { + log.debug("SqlStage %s: %s", stageId, stage); + }); + } + } + + private static class EventDebugInfos + { + private static final String GLOBAL_EVENTS_BUCKET = "GLOBAL"; + private static final EventListener GET_BUCKET_LISTENER = new EventListener<>() + { + @Override + public String onRemoteTaskEvent(RemoteTaskEvent event) + { + return "task_" + event.getTaskStatus().getTaskId().getStageId().toString(); + } + + @Override + public String onRemoteTaskExchangeUpdatedSinkAcquired(RemoteTaskExchangeUpdatedSinkAcquired event) + { + return "task_" + event.getTaskId().getStageId().toString(); + } + + @Override + public String onStageEvent(StageEvent event) + { + return event.getStageId().toString(); + } + + @Override + public String onEvent(Event event) + { + return GLOBAL_EVENTS_BUCKET; + } + }; + + private final String queryId; + private final int eventsPerBucket; + private long eventsCounter; + private long filteredEventsCounter; + + // Using SoftReference to prevent OOM in an unexpected case when this collection grow to substantial size + // and VM is short on memory. + private SoftReference> eventsDebugInfosReference; + + private EventDebugInfos(String queryId, int eventsPerBucket) + { + this.queryId = requireNonNull(queryId, "queryId is null"); + this.eventsPerBucket = eventsPerBucket; + eventsDebugInfosReference = new SoftReference<>(LinkedListMultimap.create()); + } + + /** + * @return true if event was recorded; false if it was filtered out + */ + private boolean add(Event event) + { + ListMultimap eventsDebugInfos = getEventsDebugInfos(); + String bucket = getBucket(event); + Optional debugInfo = getFullDebugInfo(eventsCounter, event); + eventsCounter++; + if (debugInfo.isEmpty()) { + filteredEventsCounter++; + return false; + } + + List bucketDebugInfos = eventsDebugInfos.get(bucket); + bucketDebugInfos.add(debugInfo.get()); + if (bucketDebugInfos.size() > eventsPerBucket) { + Iterator iterator = bucketDebugInfos.iterator(); + iterator.next(); + iterator.remove(); + } + return true; + } + + private ListMultimap getEventsDebugInfos() + { + ListMultimap eventsDebugInfos = eventsDebugInfosReference.get(); + if (eventsDebugInfos == null) { + log.debug("eventsDebugInfos for %s has been cleared", queryId); + eventsDebugInfos = LinkedListMultimap.create(); + eventsDebugInfosReference = new SoftReference<>(eventsDebugInfos); + } + return eventsDebugInfos; + } + + private String getBucket(Event event) + { + if (event == Event.WAKE_UP || event == Event.ABORT) { + return GLOBAL_EVENTS_BUCKET; + } + return event.accept(GET_BUCKET_LISTENER); + } + + private Optional getFullDebugInfo(long eventId, Event event) + { + return getEventDebugInfo(event).map(info -> "[" + eventId + "/" + System.currentTimeMillis() + "/" + info + "]"); + } + + private static Optional getEventDebugInfo(Event event) + { + if (event == Event.WAKE_UP) { + return Optional.of("WAKE_UP"); + } + if (event == Event.ABORT) { + return Optional.of("ABORT"); + } + if (event instanceof SplitAssignmentEvent splitAssignmentEvent) { + if (splitAssignmentEvent.getAssignmentResult().isEmpty()) { + // There may be significant amount of empty AssignmentResults so lets skip processing of those. + // It could be that scheduler loop is not really stuck per se. But we are getting empty events all the time - and it just looks like stuck. + // We need to notice that, and still log debug information. + // Also empty events can push important events out of recorded debug information - making debug logs less useful. + return Optional.empty(); + } + } + + return Optional.of(event.toString()); + } + + public void log() + { + if (!log.isDebugEnabled()) { + return; + } + ListMultimap eventsDebugInfos = getEventsDebugInfos(); + eventsDebugInfos.asMap().entrySet().stream() + .sorted(comparingByKey()) + .forEachOrdered(entry -> { + log.debug("Recent events for " + entry.getKey()); + for (String eventDebugInfo : entry.getValue()) { + // logging events in separate log events as some events may be huge and otherwise rarely we could hit logging framework constraints + log.debug(" " + eventDebugInfo); + } + }); + log.debug("Filtered events count " + filteredEventsCounter); + } } private static class Scheduler implements EventListener { private static final int EVENT_BUFFER_CAPACITY = 100; - private static final long EVENT_PROCESSING_ENFORCED_FREQUENCY_MILLIS = new Duration(1, MINUTES).toMillis(); + private static final long EVENT_PROCESSING_ENFORCED_FREQUENCY_MILLIS = MINUTES.toMillis(1); + // If scheduler is stalled for SCHEDULER_STALLED_DURATION_THRESHOLD debug log will be emitted. + // This value must be larger than EVENT_PROCESSING_ENFORCED_FREQUENCY as prerequiste for processing is + // that there are no events in the event queue. + private static final long SCHEDULER_STALLED_DURATION_THRESHOLD_MILLIS = new Duration(5, MINUTES).toMillis(); + private static final int EVENTS_DEBUG_INFOS_PER_BUCKET = 10; private final QueryStateMachine queryStateMachine; private final Metadata metadata; @@ -531,6 +685,8 @@ private static class Scheduler private final BlockingQueue eventQueue = new LinkedBlockingQueue<>(); private final List eventBuffer = new ArrayList<>(EVENT_BUFFER_CAPACITY); + private final Stopwatch eventDebugInfoStopwatch = Stopwatch.createUnstarted(); + private final Optional eventDebugInfos; private boolean started; private boolean runtimeAdaptivePartitioningApplied; @@ -614,7 +770,15 @@ public Scheduler( this.runtimeAdaptivePartitioningMaxTaskSizeInBytes = requireNonNull(runtimeAdaptivePartitioningMaxTaskSize, "runtimeAdaptivePartitioningMaxTaskSize is null").toBytes(); this.stageEstimationForEagerParentEnabled = stageEstimationForEagerParentEnabled; + if (log.isDebugEnabled()) { + eventDebugInfos = Optional.of(new EventDebugInfos(queryStateMachine.getQueryId().toString(), EVENTS_DEBUG_INFOS_PER_BUCKET)); + } + else { + eventDebugInfos = Optional.empty(); + } + planInTopologicalOrder = sortPlanInTopologicalOrder(plan); + eventDebugInfoStopwatch.start(); } public void run() @@ -682,33 +846,107 @@ private boolean processEvents() { try { Event event = eventQueue.poll(EVENT_PROCESSING_ENFORCED_FREQUENCY_MILLIS, MILLISECONDS); - if (event == null) { - return true; + if (event != null) { + eventBuffer.add(event); } - eventBuffer.add(event); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } - while (true) { + boolean eventDebugInfoRecorded = false; + boolean aborted = false; + while (!aborted) { // poll multiple events from the queue in one shot to improve efficiency eventQueue.drainTo(eventBuffer, EVENT_BUFFER_CAPACITY - eventBuffer.size()); if (eventBuffer.isEmpty()) { - return true; + break; } + for (Event e : eventBuffer) { + eventDebugInfoRecorded |= recordEventsDebugInfo(e); if (e == Event.ABORT) { - return false; + aborted = true; + break; } if (e == Event.WAKE_UP) { continue; } e.accept(this); } + eventBuffer.clear(); } + + if (eventDebugInfoRecorded) { + // mark that we processed some events; we filter out some no-op events. + // If only no-op events appear in event queue we still treat scheduler as stuck + eventDebugInfoStopwatch.reset().start(); + } + else { + // if no events were recorded there is a chance scheduler is stalled + if (log.isDebugEnabled() && eventDebugInfoStopwatch.elapsed().toMillis() > SCHEDULER_STALLED_DURATION_THRESHOLD_MILLIS) { + logDebugInfoSafe("Scheduler stalled for %s".formatted(eventDebugInfoStopwatch.elapsed())); + eventDebugInfoStopwatch.reset().start(); // reset to prevent extensive logging + } + } + + return !aborted; + } + + private boolean recordEventsDebugInfo(Event event) + { + if (eventDebugInfos.isEmpty()) { + return false; + } + return eventDebugInfos.orElseThrow().add(event); + } + + private void logDebugInfoSafe(String reason) + { + try { + logDebugInfo(reason); + } + catch (Throwable e) { + log.error(e, "Unexpected error while logging debug info for %s", reason); + } + } + + private void logDebugInfo(String reason) + { + if (!log.isDebugEnabled()) { + return; + } + + log.debug("Scheduler debug info for %s START; reason=%s", queryStateMachine.getQueryId(), reason); + log.debug("General state: %s", toStringHelper(this) + .add("maxTaskExecutionAttempts", maxTaskExecutionAttempts) + .add("maxTasksWaitingForNode", maxTasksWaitingForNode) + .add("maxTasksWaitingForExecution", maxTasksWaitingForExecution) + .add("maxPartitionCount", maxPartitionCount) + .add("runtimeAdaptivePartitioningEnabled", runtimeAdaptivePartitioningEnabled) + .add("runtimeAdaptivePartitioningPartitionCount", runtimeAdaptivePartitioningPartitionCount) + .add("runtimeAdaptivePartitioningMaxTaskSizeInBytes", runtimeAdaptivePartitioningMaxTaskSizeInBytes) + .add("stageEstimationForEagerParentEnabled", stageEstimationForEagerParentEnabled) + .add("started", started) + .add("runtimeAdaptivePartitioningApplied", runtimeAdaptivePartitioningApplied) + .add("nextSchedulingPriority", nextSchedulingPriority) + .add("preSchedulingTaskContexts", preSchedulingTaskContexts) + .add("schedulingDelayer", schedulingDelayer) + .add("queryOutputSet", queryOutputSet) + .toString()); + + stageRegistry.logDebugInfo(); + + log.debug("StageExecutions:"); + stageExecutions.forEach((stageId, stageExecution) -> { + stageExecution.logDebugInfo(); + }); + + eventDebugInfos.ifPresent(EventDebugInfos::log); + + log.debug("Scheduler debug info for %s END", queryStateMachine.getQueryId()); } /** @@ -2202,6 +2440,40 @@ public FaultTolerantPartitioningScheme getSinkPartitioningScheme() { return sinkPartitioningScheme; } + + public void logDebugInfo() + { + if (!log.isDebugEnabled()) { + return; + } + + log.debug("StageExecution %s: %s", + stage.getStageId(), + toStringHelper(this) + .add("taskDescriptorStorage.getReservedBytes()", taskDescriptorStorage.getReservedBytes()) + .add("taskSource", taskSource.getDebugInfo()) + .add("sinkPartitioningScheme", sinkPartitioningScheme) + .add("exchange", exchange) + .add("schedulingPriority", schedulingPriority) + .add("eager", eager) + .add("outputDataSize", outputDataSize) + .add("noMorePartitions", noMorePartitions) + .add("runningPartitions", runningPartitions) + .add("remainingPartitions", remainingPartitions) + .add("sinkOutputSelectorBuilder", sinkOutputSelectorBuilder == null ? null : sinkOutputSelectorBuilder.build()) + .add("finalSinkOutputSelector", finalSinkOutputSelector) + .add("remoteSourceIds", remoteSourceIds) + .add("remoteSources", remoteSources) + .add("sourceOutputSelectors", sourceOutputSelectors) + .add("taskDescriptorLoadingActive", taskDescriptorLoadingActive) + .add("exchangeClosed", exchangeClosed) + .add("initialMemoryRequirements", initialMemoryRequirements) + .toString()); + + partitions.forEach((partitionId, stagePartition) -> { + log.debug(" StagePartition %s.%s: %s", stage.getStageId(), partitionId, stagePartition.getDebugInfo()); + }); + } } private static class StagePartition @@ -2456,6 +2728,28 @@ public boolean isFinished() { return finished; } + + public String getDebugInfo() + { + return toStringHelper(this) + .add("stageId", stageId) + .add("partitionId", partitionId) + .add("exchangeSinkHandle", exchangeSinkHandle) + .add("remoteSourceIds", remoteSourceIds) + .add("openTaskDescriptor", openTaskDescriptor) + .add("memoryRequirements", memoryRequirements) + .add("failureObserved", failureObserved) + .add("remainingAttempts", remainingAttempts) + .add("tasks", tasks) + .add("taskOutputBuffers", taskOutputBuffers) + .add("runningTasks", runningTasks) + .add("taskNodeLeases", taskNodeLeases) + .add("finalSelectors", finalSelectors) + .add("noMoreSplits", noMoreSplits) + .add("taskScheduled", taskScheduled) + .add("finished", finished) + .toString(); + } } private static Split createOutputSelectorSplit(ExchangeSourceOutputSelector selector) @@ -2655,6 +2949,18 @@ public long getRemainingDelayInMillis() } return 0; } + + @Override + public String toString() + { + return toStringHelper(this) + .add("minRetryDelayInMillis", minRetryDelayInMillis) + .add("maxRetryDelayInMillis", maxRetryDelayInMillis) + .add("retryDelayScaleFactor", retryDelayScaleFactor) + .add("stopwatch", stopwatch) + .add("currentDelayInMillis", currentDelayInMillis) + .toString(); + } } private interface Event @@ -2774,6 +3080,18 @@ public T accept(EventListener listener) { return listener.onSinkInstanceHandleAcquired(this); } + + @Override + public String toString() + { + return toStringHelper(this) + .add("stageId", stageId) + .add("partitionId", partitionId) + .add("nodeLease", nodeLease) + .add("attempt", attempt) + .add("sinkInstanceHandle", sinkInstanceHandle) + .toString(); + } } private static class RemoteTaskCompletedEvent @@ -2789,6 +3107,14 @@ public T accept(EventListener listener) { return listener.onRemoteTaskCompleted(this); } + + @Override + public String toString() + { + return toStringHelper(this) + .add("taskStatus", getTaskStatus()) + .toString(); + } } private static class RemoteTaskExchangeSinkUpdateRequiredEvent @@ -2804,6 +3130,14 @@ public T accept(EventListener listener) { return listener.onRemoteTaskExchangeSinkUpdateRequired(this); } + + @Override + public String toString() + { + return toStringHelper(this) + .add("taskStatus", getTaskStatus()) + .toString(); + } } private static class RemoteTaskExchangeUpdatedSinkAcquired @@ -2833,6 +3167,15 @@ public ExchangeSinkInstanceHandle getExchangeSinkInstanceHandle() { return exchangeSinkInstanceHandle; } + + @Override + public String toString() + { + return toStringHelper(this) + .add("taskId", taskId) + .add("exchangeSinkInstanceHandle", exchangeSinkInstanceHandle) + .toString(); + } } private abstract static class RemoteTaskEvent @@ -2849,6 +3192,14 @@ public TaskStatus getTaskStatus() { return taskStatus; } + + @Override + public String toString() + { + return toStringHelper(this) + .add("taskStatus", taskStatus) + .toString(); + } } private static class SplitAssignmentEvent @@ -2872,6 +3223,15 @@ public T accept(EventListener listener) { return listener.onSplitAssignment(this); } + + @Override + public String toString() + { + return toStringHelper(this) + .add("stageId", getStageId()) + .add("assignmentResult", assignmentResult) + .toString(); + } } private static class StageFailureEvent @@ -2895,6 +3255,15 @@ public T accept(EventListener listener) { return listener.onStageFailure(this); } + + @Override + public String toString() + { + return toStringHelper(this) + .add("stageId", getStageId()) + .add("failure", failure) + .toString(); + } } private abstract static class StageEvent @@ -2958,5 +3327,15 @@ public void setWaitingForSinkInstanceHandle(boolean waitingForSinkInstanceHandle { this.waitingForSinkInstanceHandle = waitingForSinkInstanceHandle; } + + @Override + public String toString() + { + return toStringHelper(this) + .add("nodeLease", nodeLease) + .add("executionClass", executionClass) + .add("waitingForSinkInstanceHandle", waitingForSinkInstanceHandle) + .toString(); + } } } diff --git a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenTaskSource.java b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenTaskSource.java index d6e6d74582f8..0d21d1a24f7d 100644 --- a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenTaskSource.java +++ b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenTaskSource.java @@ -56,10 +56,12 @@ import java.util.function.LongConsumer; import java.util.function.Supplier; +import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Verify.verify; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableListMultimap.toImmutableListMultimap; +import static com.google.common.collect.Maps.transformValues; import static com.google.common.util.concurrent.Futures.immediateFuture; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static io.airlift.concurrent.MoreFutures.toListenableFuture; @@ -221,6 +223,23 @@ public synchronized void close() } } + public String getDebugInfo() + { + return toStringHelper(this) + .add("sourceExchanges", transformValues(sourceExchanges, Exchange::getId)) + .add("remoteSources", remoteSources) + .add("assigner", assigner) + .add("splitBatchSize", splitBatchSize) + .add("targetExchangeSplitSizeInBytes", targetExchangeSplitSizeInBytes) + .add("sourcePartitioningScheme", sourcePartitioningScheme) + .add("initialized", initialized) + .add("splitSources", splitSources) + .add("completedFragments", completedFragments) + .add("future", future) + .add("closed", closed) + .toString(); + } + private static class IdempotentSplitSource implements Closeable { @@ -296,6 +315,19 @@ private synchronized void advance(boolean lastBatch) future = Optional.empty(); } + @Override + public synchronized String toString() + { + return toStringHelper(this) + .add("planNodeId", planNodeId) + .add("sourceFragmentId", sourceFragmentId) + .add("splitSource", splitSource) + .add("splitBatchSize", splitBatchSize) + .add("closed", closed) + .add("finished", finished) + .toString(); + } + public class SplitBatchReference { private final SplitBatch splitBatch; @@ -404,6 +436,15 @@ public Optional> getTableExecuteSplitsInfo() { return Optional.empty(); } + + @Override + public String toString() + { + return toStringHelper(this) + .add("handleSource", handleSource) + .add("targetSplitSizeInBytes", targetSplitSizeInBytes) + .toString(); + } } /** diff --git a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/FaultTolerantPartitioningScheme.java b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/FaultTolerantPartitioningScheme.java index d64cae61d71f..9f4ff0d00b7a 100644 --- a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/FaultTolerantPartitioningScheme.java +++ b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/FaultTolerantPartitioningScheme.java @@ -22,6 +22,7 @@ import java.util.Optional; import java.util.function.ToIntFunction; +import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static java.util.Objects.requireNonNull; @@ -103,4 +104,15 @@ public FaultTolerantPartitioningScheme withPartitionCount(int partitionCount) this.splitToBucketFunction, this.partitionToNodeMap); } + + @Override + public String toString() + { + return toStringHelper(this) + .add("partitionCount", partitionCount) + .add("bucketToPartitionMap", bucketToPartitionMap.isPresent() ? "present" : "empty") + .add("splitToBucketFunction", splitToBucketFunction.isPresent() ? "present" : "empty") + .add("partitionToNodeMap", partitionToNodeMap) + .toString(); + } } diff --git a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/HashDistributionSplitAssigner.java b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/HashDistributionSplitAssigner.java index db0e693e0fd6..fe424f9ce050 100644 --- a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/HashDistributionSplitAssigner.java +++ b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/HashDistributionSplitAssigner.java @@ -42,6 +42,7 @@ import java.util.function.Predicate; import java.util.stream.IntStream; +import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Verify.verify; @@ -353,6 +354,16 @@ public Optional getSplitBy() { return splitBy; } + + @Override + public String toString() + { + return toStringHelper(this) + .add("subPartitions", subPartitions) + .add("splitBy", splitBy) + .add("nextSubPartition", nextSubPartition) + .toString(); + } } @VisibleForTesting @@ -376,6 +387,12 @@ public int getId() checkState(id.isPresent(), "id is expected to be assigned"); return id.getAsInt(); } + + @Override + public String toString() + { + return id.toString(); + } } private static boolean isWriteFragment(PlanFragment fragment) @@ -402,4 +419,20 @@ public Boolean visitTableWriter(TableWriterNode node, Void context) return fragment.getRoot().accept(visitor, null); } + + @Override + public String toString() + { + return toStringHelper(this) + .add("catalogRequirement", catalogRequirement) + .add("replicatedSources", replicatedSources) + .add("allSources", allSources) + .add("sourcePartitioningScheme", sourcePartitioningScheme) + .add("sourcePartitionToTaskPartition", sourcePartitionToTaskPartition) + .add("createdTaskPartitions", createdTaskPartitions) + .add("completedSources", completedSources) + .add("replicatedSplits.size()", replicatedSplits.size()) + .add("allTaskPartitionsCreated", allTaskPartitionsCreated) + .toString(); + } } diff --git a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/SingleDistributionSplitAssigner.java b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/SingleDistributionSplitAssigner.java index 3f1801c382b6..a9854e692ab3 100644 --- a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/SingleDistributionSplitAssigner.java +++ b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/SingleDistributionSplitAssigner.java @@ -24,6 +24,7 @@ import java.util.Optional; import java.util.Set; +import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.base.Preconditions.checkState; import static java.util.Objects.requireNonNull; @@ -88,4 +89,15 @@ public AssignmentResult finish() } return result.build(); } + + @Override + public String toString() + { + return toStringHelper(this) + .add("hostRequirement", hostRequirement) + .add("allSources", allSources) + .add("partitionAdded", partitionAdded) + .add("completedSources", completedSources) + .toString(); + } } diff --git a/core/trino-main/src/main/java/io/trino/split/BufferingSplitSource.java b/core/trino-main/src/main/java/io/trino/split/BufferingSplitSource.java index da721ce70660..d7cc2611792c 100644 --- a/core/trino-main/src/main/java/io/trino/split/BufferingSplitSource.java +++ b/core/trino-main/src/main/java/io/trino/split/BufferingSplitSource.java @@ -27,6 +27,7 @@ import java.util.Optional; import java.util.concurrent.Executor; +import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.google.common.util.concurrent.Futures.addCallback; @@ -78,6 +79,15 @@ public Optional> getTableExecuteSplitsInfo() return source.getTableExecuteSplitsInfo(); } + @Override + public String toString() + { + return toStringHelper(this) + .add("bufferSize", bufferSize) + .add("source", source) + .toString(); + } + private static class GetNextBatch extends AbstractFuture { diff --git a/core/trino-main/src/main/java/io/trino/split/TracingSplitSource.java b/core/trino-main/src/main/java/io/trino/split/TracingSplitSource.java index 8df2cc09b5b1..349487592caf 100644 --- a/core/trino-main/src/main/java/io/trino/split/TracingSplitSource.java +++ b/core/trino-main/src/main/java/io/trino/split/TracingSplitSource.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Optional; +import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; import static java.util.Objects.requireNonNull; @@ -105,4 +106,12 @@ public Optional> getTableExecuteSplitsInfo() { return source.getTableExecuteSplitsInfo(); } + + @Override + public String toString() + { + return toStringHelper(this) + .add("source", source) + .toString(); + } } diff --git a/core/trino-main/src/test/java/io/trino/exchange/TestExchangeSourceOutputSelector.java b/core/trino-main/src/test/java/io/trino/exchange/TestExchangeSourceOutputSelector.java index 4affb429c24e..9161b2d4f5c9 100644 --- a/core/trino-main/src/test/java/io/trino/exchange/TestExchangeSourceOutputSelector.java +++ b/core/trino-main/src/test/java/io/trino/exchange/TestExchangeSourceOutputSelector.java @@ -195,6 +195,20 @@ public void testIncompatibleTransitions() .hasMessage("decision for partition 0 is already made: 0"); } + @Test + public void testToString() + { + ExchangeSourceOutputSelector selector = ExchangeSourceOutputSelector.builder(ImmutableSet.of(EXCHANGE_ID_1, EXCHANGE_ID_2)) + .include(EXCHANGE_ID_1, 0, 1) + .exclude(EXCHANGE_ID_1, 1) + .include(EXCHANGE_ID_1, 2, 0) + .exclude(EXCHANGE_ID_2, 3) + .setPartitionCount(EXCHANGE_ID_2, 4) + .build(); + + assertThat(selector).hasToString("ExchangeSourceOutputSelector[version=0, values={exchange_1=[0=1,1=E,2=0], exchange_2=[0=U,1=U,2=U,3=E]}, finalSelector=false]"); + } + private ExchangeSourceOutputSelector serializeDeserialize(ExchangeSourceOutputSelector selector) { return codec.fromJson(codec.toJson(selector)); diff --git a/core/trino-spi/src/main/java/io/trino/spi/exchange/ExchangeContext.java b/core/trino-spi/src/main/java/io/trino/spi/exchange/ExchangeContext.java index 2f3a1604d64f..6143746a0b47 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/exchange/ExchangeContext.java +++ b/core/trino-spi/src/main/java/io/trino/spi/exchange/ExchangeContext.java @@ -16,6 +16,8 @@ import io.trino.spi.Experimental; import io.trino.spi.QueryId; +import java.util.StringJoiner; + import static java.util.Objects.requireNonNull; @Experimental(eta = "2023-09-01") @@ -39,4 +41,13 @@ public ExchangeId getExchangeId() { return exchangeId; } + + @Override + public String toString() + { + return new StringJoiner(", ", ExchangeContext.class.getSimpleName() + "[", "]") + .add("queryId=" + queryId) + .add("exchangeId=" + exchangeId) + .toString(); + } } diff --git a/core/trino-spi/src/main/java/io/trino/spi/exchange/ExchangeSourceOutputSelector.java b/core/trino-spi/src/main/java/io/trino/spi/exchange/ExchangeSourceOutputSelector.java index 6b7c0067a54a..14ee2c3870a0 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/exchange/ExchangeSourceOutputSelector.java +++ b/core/trino-spi/src/main/java/io/trino/spi/exchange/ExchangeSourceOutputSelector.java @@ -15,6 +15,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import io.airlift.slice.BasicSliceInput; import io.airlift.slice.SizeOf; import io.airlift.slice.Slice; import io.airlift.slice.Slices; @@ -22,7 +23,10 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; +import java.util.StringJoiner; +import java.util.TreeMap; import java.util.function.Function; import static io.airlift.slice.SizeOf.instanceSize; @@ -31,6 +35,7 @@ import static io.trino.spi.exchange.ExchangeSourceOutputSelector.Selection.UNKNOWN; import static java.lang.Math.max; import static java.util.Arrays.fill; +import static java.util.Map.entry; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toMap; import static java.util.stream.Collectors.toUnmodifiableMap; @@ -157,6 +162,55 @@ public ExchangeSourceOutputSelector merge(ExchangeSourceOutputSelector other) this.finalSelector && other.finalSelector); } + @Override + public String toString() + { + return new StringJoiner(", ", ExchangeSourceOutputSelector.class.getSimpleName() + "[", "]") + .add("version=" + version) + .add("values=" + values.entrySet().stream() + .map(e -> entry(e.getKey().toString(), valuesSliceToString(e.getValue()))) + // collect to TreeMap to ensure ordering of keys + .collect(toMap( + Entry::getKey, + Entry::getValue, + (a, b) -> { throw new IllegalArgumentException("got duplicate key " + a + ", " + b); }, + TreeMap::new))) + .add("finalSelector=" + finalSelector) + .toString(); + } + + private String valuesSliceToString(Slice values) + { + StringBuilder builder = new StringBuilder(); + builder.append("["); + try (BasicSliceInput input = new BasicSliceInput(values)) { + int taskPartitionId = 0; + while (true) { + int value = input.read(); + if (value == -1) { + break; + } + if (taskPartitionId != 0) { + builder.append(","); + } + builder.append(taskPartitionId); + builder.append("="); + if ((byte) value == EXCLUDED.value) { + builder.append("E"); + } + else if ((byte) value == UNKNOWN.value) { + builder.append("U"); + } + else { + builder.append(value); + } + taskPartitionId++; + } + } + builder.append("]"); + return builder.toString(); + } + private int getPartitionCount(ExchangeId exchangeId) { Slice values = this.values.get(exchangeId); @@ -272,7 +326,7 @@ public ExchangeSourceOutputSelector build() return new ExchangeSourceOutputSelector( nextVersion++, exchangeValues.entrySet().stream() - .collect(toMap(Map.Entry::getKey, entry -> { + .collect(toMap(Entry::getKey, entry -> { ExchangeId exchangeId = entry.getKey(); ValuesBuilder valuesBuilder = entry.getValue(); if (finalSelector) { diff --git a/plugin/trino-exchange-filesystem/src/main/java/io/trino/plugin/exchange/filesystem/FileSystemExchange.java b/plugin/trino-exchange-filesystem/src/main/java/io/trino/plugin/exchange/filesystem/FileSystemExchange.java index f5c386fafab3..4d5ac7635c68 100644 --- a/plugin/trino-exchange-filesystem/src/main/java/io/trino/plugin/exchange/filesystem/FileSystemExchange.java +++ b/plugin/trino-exchange-filesystem/src/main/java/io/trino/plugin/exchange/filesystem/FileSystemExchange.java @@ -49,6 +49,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Verify.verify; @@ -337,4 +338,24 @@ private record CommittedTaskAttempt(int partitionId, int attemptId) checkArgument(attemptId >= 0, "attemptId is expected to be greater than or equal to zero: %s", attemptId); } } + + @Override + public synchronized String toString() + { + return toStringHelper(this) + .add("baseDirectories", baseDirectories) + .add("exchangeStorage", exchangeStorage.getClass().getName()) + .add("exchangeContext", exchangeContext) + .add("outputPartitionCount", outputPartitionCount) + .add("preserveOrderWithinPartition", preserveOrderWithinPartition) + .add("fileListingParallelism", fileListingParallelism) + .add("exchangeSourceHandleTargetDataSizeInBytes", exchangeSourceHandleTargetDataSizeInBytes) + .add("outputDirectories", outputDirectories) + .add("allSinks", allSinks) + .add("finishedSinks", finishedSinks) + .add("noMoreSinks", noMoreSinks) + .add("exchangeSourceHandlesCreationStarted", exchangeSourceHandlesCreationStarted) + .add("exchangeSourceHandlesFuture", exchangeSourceHandlesFuture) + .toString(); + } } diff --git a/plugin/trino-exchange-filesystem/src/main/java/io/trino/plugin/exchange/filesystem/FileSystemExchangeSinkInstanceHandle.java b/plugin/trino-exchange-filesystem/src/main/java/io/trino/plugin/exchange/filesystem/FileSystemExchangeSinkInstanceHandle.java index 666afd36e0b6..a262b9f04149 100644 --- a/plugin/trino-exchange-filesystem/src/main/java/io/trino/plugin/exchange/filesystem/FileSystemExchangeSinkInstanceHandle.java +++ b/plugin/trino-exchange-filesystem/src/main/java/io/trino/plugin/exchange/filesystem/FileSystemExchangeSinkInstanceHandle.java @@ -19,6 +19,7 @@ import java.net.URI; +import static com.google.common.base.MoreObjects.toStringHelper; import static java.util.Objects.requireNonNull; public class FileSystemExchangeSinkInstanceHandle @@ -65,4 +66,15 @@ public boolean isPreserveOrderWithinPartition() { return preserveOrderWithinPartition; } + + @Override + public String toString() + { + return toStringHelper(this) + .add("sinkHandle", sinkHandle) + .add("outputDirectory", outputDirectory) + .add("outputPartitionCount", outputPartitionCount) + .add("preserveOrderWithinPartition", preserveOrderWithinPartition) + .toString(); + } } From 100b4531fcb9a6e5fc727cb92890ce8f9f47a44e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Osipiuk?= Date: Sun, 26 Nov 2023 13:45:39 +0100 Subject: [PATCH 513/587] Extend diagnostics for stuck FTE on EXCEEDED_TIME_LIMIT --- .../EventDrivenFaultTolerantQueryScheduler.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java index 5789a6b7a679..88db82b7ad7b 100644 --- a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java +++ b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java @@ -167,6 +167,7 @@ import static io.trino.spi.ErrorType.EXTERNAL; import static io.trino.spi.ErrorType.INTERNAL_ERROR; import static io.trino.spi.ErrorType.USER_ERROR; +import static io.trino.spi.StandardErrorCode.EXCEEDED_TIME_LIMIT; import static io.trino.spi.StandardErrorCode.GENERIC_INTERNAL_ERROR; import static io.trino.spi.StandardErrorCode.REMOTE_HOST_GONE; import static io.trino.spi.exchange.Exchange.SourceHandlesDeliveryMode.EAGER; @@ -188,6 +189,7 @@ import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.SECONDS; public class EventDrivenFaultTolerantQueryScheduler implements QueryScheduler @@ -651,7 +653,8 @@ private static class Scheduler // If scheduler is stalled for SCHEDULER_STALLED_DURATION_THRESHOLD debug log will be emitted. // This value must be larger than EVENT_PROCESSING_ENFORCED_FREQUENCY as prerequiste for processing is // that there are no events in the event queue. - private static final long SCHEDULER_STALLED_DURATION_THRESHOLD_MILLIS = new Duration(5, MINUTES).toMillis(); + private static final long SCHEDULER_STALLED_DURATION_THRESHOLD_MILLIS = MINUTES.toMillis(5); + private static final long SCHEDULER_STALLED_DURATION_ON_TIME_EXCEEDED_THRESHOLD_MILLIS = SECONDS.toMillis(30); private static final int EVENTS_DEBUG_INFOS_PER_BUCKET = 10; private final QueryStateMachine queryStateMachine; @@ -792,6 +795,17 @@ public void run() } }); + queryStateMachine.addQueryInfoStateChangeListener(queryInfo -> { + if (!queryInfo.isFinalQueryInfo()) { + return; + } + if (queryInfo.getState() == QueryState.FAILED + && queryInfo.getErrorCode() == EXCEEDED_TIME_LIMIT.toErrorCode() + && eventDebugInfoStopwatch.elapsed().toMillis() > SCHEDULER_STALLED_DURATION_ON_TIME_EXCEEDED_THRESHOLD_MILLIS) { + logDebugInfoSafe(format("Scheduler stalled for %s on EXCEEDED_TIME_LIMIT", eventDebugInfoStopwatch.elapsed())); + } + }); + Optional failure = Optional.empty(); try { // schedule() is the main logic, but expensive, so we do not want to call it after every event. From 01595bde9ed7846914996dfcfcae35c0a5991d26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Osipiuk?= Date: Tue, 28 Nov 2023 10:31:36 +0100 Subject: [PATCH 514/587] Enable debug logging for fault tolerant execution in tests --- .../src/main/java/io/trino/testing/DistributedQueryRunner.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/testing/trino-testing/src/main/java/io/trino/testing/DistributedQueryRunner.java b/testing/trino-testing/src/main/java/io/trino/testing/DistributedQueryRunner.java index 693b9c0bd7e8..e5a8b1b3084b 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/DistributedQueryRunner.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/DistributedQueryRunner.java @@ -80,6 +80,7 @@ import static com.google.common.base.Throwables.throwIfUnchecked; import static com.google.common.base.Verify.verify; import static com.google.inject.util.Modules.EMPTY_MODULE; +import static io.airlift.log.Level.DEBUG; import static io.airlift.log.Level.ERROR; import static io.airlift.log.Level.WARN; import static io.airlift.testing.Closeables.closeAllSuppress; @@ -234,6 +235,7 @@ private static void setupLogging() logging.setLevel("org.hibernate.validator.internal.util.Version", WARN); logging.setLevel(PluginManager.class.getName(), WARN); logging.setLevel(CoordinatorDynamicCatalogManager.class.getName(), WARN); + logging.setLevel("io.trino.execution.scheduler.faulttolerant", DEBUG); } private static TestingTrinoServer createTestingTrinoServer( From c77bd9385d12a81c8225cfa4d0156d18e422a4ca Mon Sep 17 00:00:00 2001 From: Krzysztof Sobolewski Date: Wed, 22 Nov 2023 12:17:52 +0100 Subject: [PATCH 515/587] Move a paragraph slightly higher in FBAC docs The information in that paragraph are important to fully understand the semantics of FBAC, but several bug reports indicate that it's easy to miss. Perhaps moving it before the `:::{note}` section will make it harder to miss. --- .../main/sphinx/security/file-system-access-control.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/main/sphinx/security/file-system-access-control.md b/docs/src/main/sphinx/security/file-system-access-control.md index f8cdf5612208..efeb89e11808 100644 --- a/docs/src/main/sphinx/security/file-system-access-control.md +++ b/docs/src/main/sphinx/security/file-system-access-control.md @@ -72,15 +72,15 @@ any specific schema or table permissions. The table and schema rules are used to specify who can create, drop, alter, select, insert, delete, etc. for schemas and tables. +For each rule set, permission is based on the first matching rule read from top +to bottom. If no rule matches, access is denied. If no rules are provided at +all, then access is granted. + :::{note} These rules do not apply to system-defined tables in the `information_schema` schema. ::: -For each rule set, permission is based on the first matching rule read from top -to bottom. If no rule matches, access is denied. If no rules are provided at -all, then access is granted. - The following table summarizes the permissions required for each SQL command: | SQL command | Catalog | Schema | Table | Note | From 95482f737e8febb2d8b27efc27b92029c5508fa7 Mon Sep 17 00:00:00 2001 From: Krzysztof Sobolewski Date: Wed, 22 Nov 2023 12:24:07 +0100 Subject: [PATCH 516/587] Emphasize and clarify behavior when there are no FBAC schema rules Including some instructions on how to remove that behavior if it's not desired. --- .../security/file-system-access-control.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/src/main/sphinx/security/file-system-access-control.md b/docs/src/main/sphinx/security/file-system-access-control.md index efeb89e11808..9c26d6a5f224 100644 --- a/docs/src/main/sphinx/security/file-system-access-control.md +++ b/docs/src/main/sphinx/security/file-system-access-control.md @@ -72,9 +72,22 @@ any specific schema or table permissions. The table and schema rules are used to specify who can create, drop, alter, select, insert, delete, etc. for schemas and tables. -For each rule set, permission is based on the first matching rule read from top -to bottom. If no rule matches, access is denied. If no rules are provided at -all, then access is granted. +For each rule set, permission is based on the first matching rule, read from the +top to the bottom of the configuration file. If no rule matches, access is +denied. + +If no rules are provided at all, then access is granted. You can remove +access grant by adding a section with an empty set of rules at that particular +level, for example: + +```json +{ + "schemas": [] +} +``` + +At the catalog level you have to add a single "dummy" rule for each accessible +catalog. :::{note} These rules do not apply to system-defined tables in the From aceb19e1371e2b9e9f5a7eb97fc2a473cec31e57 Mon Sep 17 00:00:00 2001 From: "Min(Dongmin Yu)" Date: Mon, 27 Nov 2023 23:58:56 +0900 Subject: [PATCH 517/587] Remove redundant ORDER BY in INSERT queries --- .../src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java index faf80eed5418..3641b35579ee 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java @@ -550,7 +550,7 @@ protected Scope visitInsert(Insert insert, Optional scope) } // analyze the query that creates the data - Scope queryScope = analyze(insert.getQuery()); + Scope queryScope = analyze(insert.getQuery(), Optional.empty(), false); // verify the insert destination columns match the query RedirectionAwareTableHandle redirection = metadata.getRedirectionAwareTableHandle(session, targetTable); From 01acd18c97595c5e50edb76f114c634f16fae441 Mon Sep 17 00:00:00 2001 From: Konrad Dziedzic Date: Thu, 9 Nov 2023 00:41:17 +0100 Subject: [PATCH 518/587] Don't set materialized view owner for IcebergGlueCatalog for system security --- .../trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java | 5 ++++- .../iceberg/catalog/glue/TrinoGlueCatalogFactory.java | 6 ++++++ .../plugin/iceberg/catalog/glue/TestTrinoGlueCatalog.java | 2 ++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java index b07c2cb90d0a..d5f7205805ba 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java @@ -181,6 +181,7 @@ public class TrinoGlueCatalog private final AWSGlueAsync glueClient; private final GlueMetastoreStats stats; private final boolean hideMaterializedViewStorageTable; + private final boolean isUsingSystemSecurity; private final Cache glueTableCache = EvictableCacheBuilder.newBuilder() // Even though this is query-scoped, this still needs to be bounded. information_schema queries can access large number of tables. @@ -206,6 +207,7 @@ public TrinoGlueCatalog( String trinoVersion, AWSGlueAsync glueClient, GlueMetastoreStats stats, + boolean isUsingSystemSecurity, Optional defaultSchemaLocation, boolean useUniqueTableLocation, boolean hideMaterializedViewStorageTable) @@ -217,6 +219,7 @@ public TrinoGlueCatalog( this.fileSystemFactory = requireNonNull(fileSystemFactory, "fileSystemFactory is null"); this.glueClient = requireNonNull(glueClient, "glueClient is null"); this.stats = requireNonNull(stats, "stats is null"); + this.isUsingSystemSecurity = isUsingSystemSecurity; this.defaultSchemaLocation = requireNonNull(defaultSchemaLocation, "defaultSchemaLocation is null"); this.hideMaterializedViewStorageTable = hideMaterializedViewStorageTable; } @@ -1173,7 +1176,7 @@ private void createMaterializedViewWithStorageTable( TableInput materializedViewTableInput = getMaterializedViewTableInput( viewName.getTableName(), encodeMaterializedViewData(fromConnectorMaterializedViewDefinition(definition)), - session.getUser(), + isUsingSystemSecurity ? null : session.getUser(), createMaterializedViewProperties(session, storageTable)); if (existing.isPresent()) { diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalogFactory.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalogFactory.java index 2a76128e0290..89e844d7ac47 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalogFactory.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalogFactory.java @@ -21,6 +21,7 @@ import io.trino.plugin.hive.metastore.glue.GlueHiveMetastoreConfig; import io.trino.plugin.hive.metastore.glue.GlueMetastoreStats; import io.trino.plugin.iceberg.IcebergConfig; +import io.trino.plugin.iceberg.IcebergSecurityConfig; import io.trino.plugin.iceberg.catalog.IcebergTableOperationsProvider; import io.trino.plugin.iceberg.catalog.TrinoCatalog; import io.trino.plugin.iceberg.catalog.TrinoCatalogFactory; @@ -31,6 +32,7 @@ import java.util.Optional; +import static io.trino.plugin.iceberg.IcebergSecurityConfig.IcebergSecurity.SYSTEM; import static java.util.Objects.requireNonNull; public class TrinoGlueCatalogFactory @@ -47,6 +49,7 @@ public class TrinoGlueCatalogFactory private final boolean isUniqueTableLocation; private final boolean hideMaterializedViewStorageTable; private final GlueMetastoreStats stats; + private final boolean isUsingSystemSecurity; @Inject public TrinoGlueCatalogFactory( @@ -58,6 +61,7 @@ public TrinoGlueCatalogFactory( GlueHiveMetastoreConfig glueConfig, IcebergConfig icebergConfig, IcebergGlueCatalogConfig catalogConfig, + IcebergSecurityConfig securityConfig, GlueMetastoreStats stats, AWSGlueAsync glueClient) { @@ -72,6 +76,7 @@ public TrinoGlueCatalogFactory( this.isUniqueTableLocation = icebergConfig.isUniqueTableLocation(); this.hideMaterializedViewStorageTable = icebergConfig.isHideMaterializedViewStorageTable(); this.stats = requireNonNull(stats, "stats is null"); + this.isUsingSystemSecurity = securityConfig.getSecuritySystem() == SYSTEM; } @Managed @@ -93,6 +98,7 @@ public TrinoCatalog create(ConnectorIdentity identity) trinoVersion, glueClient, stats, + isUsingSystemSecurity, defaultSchemaLocation, isUniqueTableLocation, hideMaterializedViewStorageTable); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestTrinoGlueCatalog.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestTrinoGlueCatalog.java index 3bb113369859..e22ca627521e 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestTrinoGlueCatalog.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestTrinoGlueCatalog.java @@ -78,6 +78,7 @@ protected TrinoCatalog createTrinoCatalog(boolean useUniqueTableLocations) "test", glueClient, new GlueMetastoreStats(), + false, Optional.empty(), useUniqueTableLocations, new IcebergConfig().isHideMaterializedViewStorageTable()); @@ -157,6 +158,7 @@ public void testDefaultLocation() "test", glueClient, new GlueMetastoreStats(), + false, Optional.of(tmpDirectory.toAbsolutePath().toString()), false, new IcebergConfig().isHideMaterializedViewStorageTable()); From 4e17461910d322bca026aeef0648b6de7edd28c8 Mon Sep 17 00:00:00 2001 From: Kamil Endruszkiewicz Date: Fri, 24 Nov 2023 12:30:31 +0100 Subject: [PATCH 519/587] Avoid large DF domains unions during add partition --- .../io/trino/server/DynamicFilterService.java | 14 +++++++------- .../planner/LocalDynamicFilterConsumer.java | 18 ++++++++++-------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/server/DynamicFilterService.java b/core/trino-main/src/main/java/io/trino/server/DynamicFilterService.java index c8d91598de9e..fdd2b0dc3531 100644 --- a/core/trino-main/src/main/java/io/trino/server/DynamicFilterService.java +++ b/core/trino-main/src/main/java/io/trino/server/DynamicFilterService.java @@ -764,17 +764,13 @@ private void collectPartitioned(TaskId taskId, Domain domain) Domain summary = summaryDomains.poll(); // summary can be null as another concurrent summary compaction may be running if (summary != null) { - long originalSize = summary.getRetainedSizeInBytes(); - if (summary.getRetainedSizeInBytes() > domainSizeLimitInBytes) { - summary = summary.simplify(1); - } - if (summary.getRetainedSizeInBytes() > domainSizeLimitInBytes) { + long summarySize = summary.getRetainedSizeInBytes(); + if (summarySize > domainSizeLimitInBytes) { sizeLimitExceeded = true; allDomain = Domain.all(summary.getType()); - summaryDomainsRetainedSizeInBytes.addAndGet(-originalSize); + summaryDomainsRetainedSizeInBytes.addAndGet(-summarySize); } else { - summaryDomainsRetainedSizeInBytes.addAndGet(summary.getRetainedSizeInBytes() - originalSize); summaryDomains.add(summary); } } @@ -828,6 +824,10 @@ private void unionSummaryDomainsIfNecessary(boolean force) } Domain union = union(domains); + // Avoid large unions with domains that exceed size limit + if ((summaryDomainsRetainedSizeInBytes.get() - domainsRetainedSizeInBytes + union.getRetainedSizeInBytes()) > domainSizeLimitInBytes) { + union = union.simplify(1); + } summaryDomainsRetainedSizeInBytes.addAndGet(union.getRetainedSizeInBytes() - domainsRetainedSizeInBytes); long currentSize = summaryDomainsRetainedSizeInBytes.get(); verify(currentSize >= 0, "currentSize is expected to be greater than or equal to zero: %s", currentSize); diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/LocalDynamicFilterConsumer.java b/core/trino-main/src/main/java/io/trino/sql/planner/LocalDynamicFilterConsumer.java index aacc1691608c..6317dc7a3929 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/LocalDynamicFilterConsumer.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/LocalDynamicFilterConsumer.java @@ -112,16 +112,12 @@ public void addPartition(TupleDomain domain) TupleDomain summary = summaryDomains.poll(); // summary can be null as another concurrent summary compaction may be running if (summary != null) { - long originalSize = getRetainedSizeInBytes(summary); - if (originalSize > domainSizeLimitInBytes) { - summary = summary.simplify(1); - } - if (getRetainedSizeInBytes(summary) > domainSizeLimitInBytes) { - summaryDomainsRetainedSizeInBytes.addAndGet(-originalSize); + long summarySize = getRetainedSizeInBytes(summary); + if (summarySize > domainSizeLimitInBytes) { + summaryDomainsRetainedSizeInBytes.addAndGet(-summarySize); sizeLimitExceeded = true; } else { - summaryDomainsRetainedSizeInBytes.addAndGet(getRetainedSizeInBytes(summary) - originalSize); summaryDomains.add(summary); } } @@ -201,7 +197,13 @@ private void unionSummaryDomainsIfNecessary(boolean force) } TupleDomain union = columnWiseUnion(domains); - summaryDomainsRetainedSizeInBytes.addAndGet(getRetainedSizeInBytes(union) - domainsRetainedSizeInBytes); + long unionSize = getRetainedSizeInBytes(union); + // Avoid large unions with domains that exceed size limit + if ((summaryDomainsRetainedSizeInBytes.get() - domainsRetainedSizeInBytes + unionSize) > domainSizeLimitInBytes) { + union = union.simplify(1); + unionSize = getRetainedSizeInBytes(union); + } + summaryDomainsRetainedSizeInBytes.addAndGet(unionSize - domainsRetainedSizeInBytes); long currentSize = summaryDomainsRetainedSizeInBytes.get(); verify(currentSize >= 0, "currentSize is expected to be greater than or equal to zero: %s", currentSize); summaryDomains.add(union); From f75d149ce9eeef8c13bc877088943cb7069ec198 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 29 Nov 2023 10:42:15 +0100 Subject: [PATCH 520/587] Test materialized view metadata listing with Glue --- ...estIcebergGlueCatalogAccessOperations.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogAccessOperations.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogAccessOperations.java index 4b1ec1908c47..317441595a76 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogAccessOperations.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/glue/TestIcebergGlueCatalogAccessOperations.java @@ -315,6 +315,55 @@ public void testRefreshMaterializedView() } } + @Test + public void testMaterializedViewMetadata() + { + try { + assertUpdate("CREATE TABLE test_mview_metadata_table (id VARCHAR, age INT)"); + assertUpdate("CREATE MATERIALIZED VIEW test_mview_metadata_view AS SELECT id, age FROM test_mview_metadata_table"); + + // listing + assertGlueMetastoreApiInvocations( + "SELECT * FROM system.metadata.materialized_views WHERE catalog_name = CURRENT_CATALOG AND schema_name = CURRENT_SCHEMA", + ImmutableMultiset.builder() + .add(GET_TABLES) + .add(GET_TABLE) + .build()); + + // pointed lookup + assertGlueMetastoreApiInvocations( + "SELECT * FROM system.metadata.materialized_views WHERE catalog_name = CURRENT_CATALOG AND schema_name = CURRENT_SCHEMA AND name = 'test_mview_metadata_view'", + ImmutableMultiset.builder() + .add(GET_TABLE) + .build()); + + // just names + assertGlueMetastoreApiInvocations( + "SELECT name FROM system.metadata.materialized_views WHERE catalog_name = CURRENT_CATALOG AND schema_name = CURRENT_SCHEMA", + ImmutableMultiset.builder() + .add(GET_TABLES) + .add(GET_TABLE) + .build()); + + // getting relations with their types, like some tools do + assertGlueMetastoreApiInvocations( + """ + SELECT table_name, IF(mv.name IS NOT NULL, 'MATERIALIZED VIEW', table_type) AS table_type + FROM information_schema.tables t + JOIN system.metadata.materialized_views mv ON t.table_schema = mv.schema_name AND t.table_name = mv.name + WHERE t.table_schema = CURRENT_SCHEMA AND mv.catalog_name = CURRENT_CATALOG + """, + ImmutableMultiset.builder() + .addCopies(GET_TABLES, 2) + .add(GET_TABLE) + .build()); + } + finally { + getQueryRunner().execute("DROP MATERIALIZED VIEW IF EXISTS test_mview_metadata_view"); + getQueryRunner().execute("DROP TABLE IF EXISTS test_mview_metadata_table"); + } + } + @Test public void testJoin() { From dc42520d05b37db4f17afa6cea3c634c79d2f5b8 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 29 Nov 2023 10:52:56 +0100 Subject: [PATCH 521/587] Inline logic in MaterializedViewSystemTable Handle optional freshness and "need freshness" within one method. Let `createMaterializedViewRow` be a trivial helper just for formatting the output. --- .../system/MaterializedViewSystemTable.java | 78 +++++++++++-------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/connector/system/MaterializedViewSystemTable.java b/core/trino-main/src/main/java/io/trino/connector/system/MaterializedViewSystemTable.java index 8af514cbaa46..b5d964e2fa9d 100644 --- a/core/trino-main/src/main/java/io/trino/connector/system/MaterializedViewSystemTable.java +++ b/core/trino-main/src/main/java/io/trino/connector/system/MaterializedViewSystemTable.java @@ -20,7 +20,6 @@ import io.trino.metadata.Metadata; import io.trino.metadata.QualifiedObjectName; import io.trino.metadata.QualifiedTablePrefix; -import io.trino.metadata.ViewInfo; import io.trino.security.AccessControl; import io.trino.spi.connector.CatalogSchemaTableName; import io.trino.spi.connector.ConnectorSession; @@ -154,42 +153,59 @@ private void addMaterializedViewForCatalog(Session session, InMemoryRecordSet.Bu } } - Object[] materializedViewRow = createMaterializedViewRow(name, freshness, definition); - displayTable.addRow(materializedViewRow); + displayTable.addRow(createMaterializedViewRow( + name.getCatalogName(), + name.getSchemaName(), + name.getObjectName(), + definition.getStorageTable() + .map(CatalogSchemaTableName::getCatalogName) + .orElse(""), + definition.getStorageTable() + .map(storageTable -> storageTable.getSchemaTableName().getSchemaName()) + .orElse(""), + definition.getStorageTable() + .map(storageTable -> storageTable.getSchemaTableName().getTableName()) + .orElse(""), + // freshness + freshness.map(MaterializedViewFreshness::getFreshness) + .map(Enum::name) + .orElse(null), + // last_fresh_time + freshness.flatMap(MaterializedViewFreshness::getLastFreshTime) + .map(instant -> LongTimestampWithTimeZone.fromEpochSecondsAndFraction( + instant.getEpochSecond(), + (long) instant.getNano() * PICOSECONDS_PER_NANOSECOND, + UTC_KEY)) + .orElse(null), + definition.getComment().orElse(""), + definition.getOriginalSql())); }); } + // Table schema as a Java method signature private static Object[] createMaterializedViewRow( - QualifiedObjectName name, - Optional freshness, - ViewInfo definition) + String catalogName, + String schemaName, + String name, + String storageCatalog, + String storageSchema, + String storageTable, + String freshness, + LongTimestampWithTimeZone lastFreshTime, + String comment, + String definition) { return new Object[] { - name.getCatalogName(), - name.getSchemaName(), - name.getObjectName(), - definition.getStorageTable() - .map(CatalogSchemaTableName::getCatalogName) - .orElse(""), - definition.getStorageTable() - .map(storageTable -> storageTable.getSchemaTableName().getSchemaName()) - .orElse(""), - definition.getStorageTable() - .map(storageTable -> storageTable.getSchemaTableName().getTableName()) - .orElse(""), - // freshness - freshness.map(MaterializedViewFreshness::getFreshness) - .map(Enum::name) - .orElse(null), - // last_fresh_time - freshness.flatMap(MaterializedViewFreshness::getLastFreshTime) - .map(instant -> LongTimestampWithTimeZone.fromEpochSecondsAndFraction( - instant.getEpochSecond(), - (long) instant.getNano() * PICOSECONDS_PER_NANOSECOND, - UTC_KEY)) - .orElse(null), - definition.getComment().orElse(""), - definition.getOriginalSql() + catalogName, + schemaName, + name, + storageCatalog, + storageSchema, + storageTable, + freshness, + lastFreshTime, + comment, + definition, }; } From 9a15a560a01ec38a6480085e763ff3cfae4643ab Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 29 Nov 2023 10:57:57 +0100 Subject: [PATCH 522/587] Use column names in MaterializedViewSystemTable Operate on column names, not indexes. More readable, much like in `InformationSchemaPageSource`. --- .../system/MaterializedViewSystemTable.java | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/connector/system/MaterializedViewSystemTable.java b/core/trino-main/src/main/java/io/trino/connector/system/MaterializedViewSystemTable.java index b5d964e2fa9d..e17d62ebf10a 100644 --- a/core/trino-main/src/main/java/io/trino/connector/system/MaterializedViewSystemTable.java +++ b/core/trino-main/src/main/java/io/trino/connector/system/MaterializedViewSystemTable.java @@ -22,6 +22,7 @@ import io.trino.metadata.QualifiedTablePrefix; import io.trino.security.AccessControl; import io.trino.spi.connector.CatalogSchemaTableName; +import io.trino.spi.connector.ColumnMetadata; import io.trino.spi.connector.ConnectorSession; import io.trino.spi.connector.ConnectorTableMetadata; import io.trino.spi.connector.ConnectorTransactionHandle; @@ -35,12 +36,10 @@ import io.trino.spi.predicate.TupleDomain; import io.trino.spi.type.LongTimestampWithTimeZone; -import java.util.Map.Entry; import java.util.Optional; import java.util.Set; -import static com.google.common.collect.MoreCollectors.onlyElement; -import static com.google.common.collect.Streams.mapWithIndex; +import static com.google.common.collect.ImmutableSet.toImmutableSet; import static io.trino.connector.system.jdbc.FilterUtil.isImpossibleObjectName; import static io.trino.connector.system.jdbc.FilterUtil.tablePrefix; import static io.trino.connector.system.jdbc.FilterUtil.tryGetSingleVarcharValue; @@ -53,8 +52,6 @@ import static io.trino.spi.type.Timestamps.PICOSECONDS_PER_NANOSECOND; import static io.trino.spi.type.VarcharType.VARCHAR; import static io.trino.spi.type.VarcharType.createUnboundedVarcharType; -import static java.lang.Math.toIntExact; -import static java.util.Map.entry; import static java.util.Objects.requireNonNull; public class MaterializedViewSystemTable @@ -104,6 +101,11 @@ public RecordCursor cursor( Set requiredColumns) { Session session = ((FullConnectorSession) connectorSession).getSession(); + Set requiredColumnNames = requiredColumns.stream() + .map(TABLE_DEFINITION.getColumns()::get) + .map(ColumnMetadata::getName) + .collect(toImmutableSet()); + InMemoryRecordSet.Builder displayTable = InMemoryRecordSet.builder(getTableMetadata()); Domain catalogDomain = constraint.getDomain(0, VARCHAR); @@ -115,7 +117,7 @@ public RecordCursor cursor( } Optional tableFilter = tryGetSingleVarcharValue(tableDomain); - boolean needFreshness = requiredColumns.contains(columnIndex("freshness")) || requiredColumns.contains(columnIndex("last_fresh_time")); + boolean needFreshness = requiredColumnNames.contains("freshness") || requiredColumnNames.contains("last_fresh_time"); listCatalogNames(session, metadata, accessControl, catalogDomain).forEach(catalogName -> { // TODO A connector may be able to pull information from multiple schemas at once, so pass the schema filter to the connector instead. @@ -208,12 +210,4 @@ private static Object[] createMaterializedViewRow( definition, }; } - - private static int columnIndex(String columnName) - { - return toIntExact(mapWithIndex(TABLE_DEFINITION.getColumns().stream(), (column, index) -> entry(column.getName(), index)) - .filter(entry -> entry.getKey().equals(columnName)) - .map(Entry::getValue) - .collect(onlyElement())); - } } From 5311b4e99ee3f2c700468bc8d8f0714b758bc7fe Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 29 Nov 2023 11:05:34 +0100 Subject: [PATCH 523/587] Rename constant for readability It's size of multiple caches, so plural. --- .../iceberg/catalog/glue/TrinoGlueCatalog.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java index d5f7205805ba..fd79f33587d3 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java @@ -171,7 +171,7 @@ public class TrinoGlueCatalog { private static final Logger LOG = Logger.get(TrinoGlueCatalog.class); - private static final int PER_QUERY_CACHE_SIZE = 1000; + private static final int PER_QUERY_CACHES_SIZE = 1000; private final String trinoVersion; private final TypeManager typeManager; @@ -185,17 +185,17 @@ public class TrinoGlueCatalog private final Cache glueTableCache = EvictableCacheBuilder.newBuilder() // Even though this is query-scoped, this still needs to be bounded. information_schema queries can access large number of tables. - .maximumSize(Math.max(PER_QUERY_CACHE_SIZE, IcebergMetadata.GET_METADATA_BATCH_SIZE)) + .maximumSize(Math.max(PER_QUERY_CACHES_SIZE, IcebergMetadata.GET_METADATA_BATCH_SIZE)) .build(); private final Cache tableMetadataCache = EvictableCacheBuilder.newBuilder() - .maximumSize(PER_QUERY_CACHE_SIZE) + .maximumSize(PER_QUERY_CACHES_SIZE) .build(); private final Cache viewCache = EvictableCacheBuilder.newBuilder() - .maximumSize(PER_QUERY_CACHE_SIZE) + .maximumSize(PER_QUERY_CACHES_SIZE) .build(); private final Cache materializedViewCache = EvictableCacheBuilder.newBuilder() - .maximumSize(PER_QUERY_CACHE_SIZE) + .maximumSize(PER_QUERY_CACHES_SIZE) .build(); public TrinoGlueCatalog( @@ -419,7 +419,7 @@ else if (!isIcebergTable(tableParameters)) { } else { unprocessed.put(name, table); - if (unprocessed.size() >= PER_QUERY_CACHE_SIZE) { + if (unprocessed.size() >= PER_QUERY_CACHES_SIZE) { getColumnsFromIcebergMetadata(session, unprocessed, relationFilter, filteredResult::add); unprocessed.clear(); } @@ -514,7 +514,7 @@ else if (!isIcebergTable(tableParameters)) { } else { unprocessed.put(name, table); - if (unprocessed.size() >= PER_QUERY_CACHE_SIZE) { + if (unprocessed.size() >= PER_QUERY_CACHES_SIZE) { getCommentsFromIcebergMetadata(session, unprocessed, relationFilter, filteredResult::add); unprocessed.clear(); } From f113c815a0634749a11534fcf3e00664ddb30f01 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 29 Nov 2023 11:25:05 +0100 Subject: [PATCH 524/587] Fix code style --- .../plugin/hive/metastore/cache/CachingHiveMetastore.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java index 478c865c3700..595e4f684e85 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/cache/CachingHiveMetastore.java @@ -1514,8 +1514,12 @@ LoadingCache> getConfigValuesCache() return configValuesCache; } - private record CacheFactory(OptionalLong expiresAfterWriteMillis, OptionalLong refreshMillis, Optional refreshExecutor, long maximumSize, - StatsRecording statsRecording) + private record CacheFactory( + OptionalLong expiresAfterWriteMillis, + OptionalLong refreshMillis, + Optional refreshExecutor, + long maximumSize, + StatsRecording statsRecording) { private static final CacheFactory NEVER_CACHE = new CacheFactory(OptionalLong.empty(), OptionalLong.empty(), Optional.empty(), 0, StatsRecording.DISABLED); From ff3704ce2742ea1d0cc0b476dada2d96dea896d7 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 29 Nov 2023 14:59:07 +0100 Subject: [PATCH 525/587] Remove useless `@Flaky` from JUnit tests The `@Flaky` was honored by TestNG runner (via `FlakyTestRetryAnalyzer`), but is ignored on JUnit tests, so should be removed. --- .../ignite/TestIgniteConnectorTest.java | 39 ------------------- .../memory/TestMemoryConnectorTest.java | 4 -- .../sqlserver/BaseSqlServerConnectorTest.java | 10 ----- .../sqlserver/TestSqlServerConnectorTest.java | 2 - .../tests/TestQuerySerializationFailures.java | 2 - 5 files changed, 57 deletions(-) diff --git a/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteConnectorTest.java b/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteConnectorTest.java index f932948ccc2f..8e834d8c2beb 100644 --- a/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteConnectorTest.java +++ b/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteConnectorTest.java @@ -22,8 +22,6 @@ import io.trino.testing.TestingConnectorBehavior; import io.trino.testing.sql.SqlExecutor; import io.trino.testing.sql.TestTable; -import io.trino.testng.services.Flaky; -import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.Test; import java.util.List; @@ -42,10 +40,6 @@ public class TestIgniteConnectorTest extends BaseJdbcConnectorTest { - private static final String SCHEMA_CHANGE_OPERATION_FAIL_ISSUE = "https://github.com/trinodb/trino/issues/14391"; - @Language("RegExp") - private static final String SCHEMA_CHANGE_OPERATION_FAIL_MATCH = "Schema change operation failed: Thread got interrupted while trying to acquire table lock."; - private TestingIgniteServer igniteServer; @Override @@ -338,7 +332,6 @@ protected void verifyConcurrentAddColumnFailurePermissible(Exception e) @Test @Override - @Flaky(issue = SCHEMA_CHANGE_OPERATION_FAIL_ISSUE, match = SCHEMA_CHANGE_OPERATION_FAIL_MATCH) public void testDropAndAddColumnWithSameName() { // Override because Ignite can access old data after dropping and adding a column with same name @@ -351,38 +344,6 @@ public void testDropAndAddColumnWithSameName() } } - @Test - @Override - @Flaky(issue = SCHEMA_CHANGE_OPERATION_FAIL_ISSUE, match = SCHEMA_CHANGE_OPERATION_FAIL_MATCH) - public void testAddColumn() - { - super.testAddColumn(); - } - - @Test - @Override - @Flaky(issue = SCHEMA_CHANGE_OPERATION_FAIL_ISSUE, match = SCHEMA_CHANGE_OPERATION_FAIL_MATCH) - public void testDropColumn() - { - super.testDropColumn(); - } - - @Test - @Override - @Flaky(issue = SCHEMA_CHANGE_OPERATION_FAIL_ISSUE, match = SCHEMA_CHANGE_OPERATION_FAIL_MATCH) - public void testAlterTableAddLongColumnName() - { - super.testAlterTableAddLongColumnName(); - } - - @Test - @Override - @Flaky(issue = SCHEMA_CHANGE_OPERATION_FAIL_ISSUE, match = SCHEMA_CHANGE_OPERATION_FAIL_MATCH) - public void testAddAndDropColumnName() - { - super.testAddAndDropColumnName(); - } - @Override protected TestTable simpleTable() { diff --git a/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryConnectorTest.java b/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryConnectorTest.java index ed70900f8e81..3241d3b3ec8f 100644 --- a/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryConnectorTest.java +++ b/plugin/trino-memory/src/test/java/io/trino/plugin/memory/TestMemoryConnectorTest.java @@ -29,7 +29,6 @@ import io.trino.testing.QueryRunner; import io.trino.testing.TestingConnectorBehavior; import io.trino.testing.sql.TestTable; -import io.trino.testng.services.Flaky; import io.trino.tpch.TpchTable; import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.Test; @@ -134,8 +133,6 @@ public void testSelect() } @Test - // TODO (https://github.com/trinodb/trino/issues/8691) fix the test - @Flaky(issue = "https://github.com/trinodb/trino/issues/8691", match = "ComparisonFailure: expected: but was:<(LongCount\\{total=\\[\\d+]}|null)>") public void testCustomMetricsScanFilter() { Metrics metrics = collectCustomMetrics("SELECT partkey FROM part WHERE partkey % 1000 > 0"); @@ -145,7 +142,6 @@ public void testCustomMetricsScanFilter() } @Test - @Flaky(issue = "https://github.com/trinodb/trino/issues/8691", match = "ComparisonFailure: expected: but was:<(LongCount\\{total=\\[\\d+]}|null)>") public void testCustomMetricsScanOnly() { Metrics metrics = collectCustomMetrics("SELECT partkey FROM part"); diff --git a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerConnectorTest.java b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerConnectorTest.java index be0109175c48..93a487e70201 100644 --- a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerConnectorTest.java +++ b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerConnectorTest.java @@ -24,7 +24,6 @@ import io.trino.sql.planner.plan.FilterNode; import io.trino.testing.TestingConnectorBehavior; import io.trino.testing.sql.TestTable; -import io.trino.testng.services.Flaky; import org.junit.jupiter.api.Test; import java.util.List; @@ -124,15 +123,6 @@ public void testReadFromView() onRemoteDatabase().execute("DROP VIEW IF EXISTS test_view"); } - // TODO (https://github.com/trinodb/trino/issues/10846): Test is expected to be flaky because tests execute in parallel - @Flaky(issue = "https://github.com/trinodb/trino/issues/10846", match = "was deadlocked on lock resources with another process and has been chosen as the deadlock victim") - @Test - @Override - public void testSelectInformationSchemaColumns() - { - super.testSelectInformationSchemaColumns(); - } - @Override protected void verifyAddNotNullColumnToNonEmptyTableFailurePermissible(Throwable e) { diff --git a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerConnectorTest.java b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerConnectorTest.java index f859fed7a306..ea24c422795e 100644 --- a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerConnectorTest.java +++ b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/TestSqlServerConnectorTest.java @@ -19,7 +19,6 @@ import io.trino.testing.QueryRunner; import io.trino.testing.sql.SqlExecutor; import io.trino.testing.sql.TestTable; -import io.trino.testng.services.Flaky; import org.jdbi.v3.core.Handle; import org.jdbi.v3.core.Jdbi; import org.junit.jupiter.api.Test; @@ -91,7 +90,6 @@ private void testCreateTableAsSelectWriteBulkiness(boolean bulkCopyForWrite, boo assertUpdate("DROP TABLE " + table); } - @Flaky(issue = "fn_dblog() returns information only about the active portion of the transaction log, therefore it is flaky", match = ".*") @Test public void testInsertWriteBulkiness() throws SQLException diff --git a/testing/trino-tests/src/test/java/io/trino/tests/TestQuerySerializationFailures.java b/testing/trino-tests/src/test/java/io/trino/tests/TestQuerySerializationFailures.java index 19cf90a7b95c..ed3b24d126b6 100644 --- a/testing/trino-tests/src/test/java/io/trino/tests/TestQuerySerializationFailures.java +++ b/testing/trino-tests/src/test/java/io/trino/tests/TestQuerySerializationFailures.java @@ -23,7 +23,6 @@ import io.trino.testing.AbstractTestQueryFramework; import io.trino.testing.DistributedQueryRunner; import io.trino.testing.QueryRunner; -import io.trino.testng.services.Flaky; import org.junit.jupiter.api.Test; import java.util.Set; @@ -48,7 +47,6 @@ protected QueryRunner createQueryRunner() } @Test - @Flaky(issue = "https://github.com/trinodb/trino/issues/4173", match = "\\QExpected query to fail: SELECT * FROM (VALUES BOGUS(true), BOGUS(false), BOGUS(true))") public void shouldFailOnFirstSerializationError() { // BOGUS(value) returns BogusType that fails to serialize when value is true From 7948f0a34b4df547d6174016307ace525ed4cbf7 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 29 Nov 2023 15:23:27 +0100 Subject: [PATCH 526/587] Fix TestingConnectorSplit equality It was incorrectly comparing `SplitWeight` objects by reference. --- .../scheduler/faulttolerant/TestingConnectorSplit.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestingConnectorSplit.java b/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestingConnectorSplit.java index 6aba490cc553..89f0e84758e9 100644 --- a/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestingConnectorSplit.java +++ b/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestingConnectorSplit.java @@ -105,7 +105,7 @@ public boolean equals(Object o) return false; } TestingConnectorSplit that = (TestingConnectorSplit) o; - return id == that.id && weight == that.weight && Objects.equals(bucket, that.bucket) && Objects.equals(addresses, that.addresses); + return id == that.id && weight.equals(that.weight) && Objects.equals(bucket, that.bucket) && Objects.equals(addresses, that.addresses); } @Override From b3bee8e943b1e8565c9b1929bdee04cc982b1117 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 29 Nov 2023 15:27:35 +0100 Subject: [PATCH 527/587] Remove constant field from TestingConnectorSplit --- .../faulttolerant/TestingConnectorSplit.java | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestingConnectorSplit.java b/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestingConnectorSplit.java index 89f0e84758e9..94dedfe60901 100644 --- a/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestingConnectorSplit.java +++ b/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestingConnectorSplit.java @@ -16,7 +16,6 @@ import com.google.common.collect.ImmutableList; import io.trino.metadata.Split; import io.trino.spi.HostAddress; -import io.trino.spi.SplitWeight; import io.trino.spi.connector.ConnectorSplit; import java.util.List; @@ -38,19 +37,12 @@ class TestingConnectorSplit private final int id; private final OptionalInt bucket; private final Optional> addresses; - private final SplitWeight weight; public TestingConnectorSplit(int id, OptionalInt bucket, Optional> addresses) - { - this(id, bucket, addresses, SplitWeight.standard().getRawValue()); - } - - public TestingConnectorSplit(int id, OptionalInt bucket, Optional> addresses, long weight) { this.id = id; this.bucket = requireNonNull(bucket, "bucket is null"); this.addresses = addresses.map(ImmutableList::copyOf); - this.weight = SplitWeight.fromRawValue(weight); } public int getId() @@ -75,12 +67,6 @@ public List getAddresses() return addresses.orElse(ImmutableList.of()); } - @Override - public SplitWeight getSplitWeight() - { - return weight; - } - @Override public Object getInfo() { @@ -105,13 +91,13 @@ public boolean equals(Object o) return false; } TestingConnectorSplit that = (TestingConnectorSplit) o; - return id == that.id && weight.equals(that.weight) && Objects.equals(bucket, that.bucket) && Objects.equals(addresses, that.addresses); + return id == that.id && Objects.equals(bucket, that.bucket) && Objects.equals(addresses, that.addresses); } @Override public int hashCode() { - return Objects.hash(id, bucket, addresses, weight); + return Objects.hash(id, bucket, addresses); } @Override @@ -121,7 +107,6 @@ public String toString() .add("id", id) .add("bucket", bucket) .add("addresses", addresses) - .add("weight", weight) .toString(); } From fade04690f7e6c9d1168a2246cec0d896d2ec0b4 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 29 Nov 2023 15:21:47 +0100 Subject: [PATCH 528/587] Seal SplitWeight API Prevent calls to a non-API method that exists for JSON serialization only. --- .../src/main/java/io/trino/spi/SplitWeight.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/core/trino-spi/src/main/java/io/trino/spi/SplitWeight.java b/core/trino-spi/src/main/java/io/trino/spi/SplitWeight.java index 3d9c96ada266..d24faffc345c 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/SplitWeight.java +++ b/core/trino-spi/src/main/java/io/trino/spi/SplitWeight.java @@ -15,6 +15,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonValue; +import com.google.errorprone.annotations.DoNotCall; import java.math.BigDecimal; import java.util.Collection; @@ -82,10 +83,10 @@ public String toString() * to avoid breakages that could arise if {@link SplitWeight#UNIT_VALUE} changes in the future. */ @JsonCreator - // TODO Mark with @DoNotCall + @DoNotCall // For JSON serialization only public static SplitWeight fromRawValue(long value) { - return value == UNIT_VALUE ? STANDARD_WEIGHT : new SplitWeight(value); + return fromRawValueInternal(value); } /** @@ -105,7 +106,12 @@ public static SplitWeight fromProportion(double weight) throw new IllegalArgumentException("Invalid weight: " + weight); } // Must round up to avoid small weights rounding to 0 - return fromRawValue((long) Math.ceil(weight * UNIT_VALUE)); + return fromRawValueInternal((long) Math.ceil(weight * UNIT_VALUE)); + } + + private static SplitWeight fromRawValueInternal(long value) + { + return value == UNIT_VALUE ? STANDARD_WEIGHT : new SplitWeight(value); } public static SplitWeight standard() From f94416d4a00ac6582a0b88cff3b698a26486886a Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 29 Nov 2023 15:28:52 +0100 Subject: [PATCH 529/587] Remove redundant else --- .../faulttolerant/TestArbitraryDistributionSplitAssigner.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestArbitraryDistributionSplitAssigner.java b/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestArbitraryDistributionSplitAssigner.java index 9357db58b121..ce179d10727e 100644 --- a/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestArbitraryDistributionSplitAssigner.java +++ b/core/trino-main/src/test/java/io/trino/execution/scheduler/faulttolerant/TestArbitraryDistributionSplitAssigner.java @@ -683,7 +683,7 @@ private static void testAssigner( hostRequirement = Optional.of(hostAddress); break; } - else if (currentAssignment.getSplits().size() < splitCount) { + if (currentAssignment.getSplits().size() < splitCount) { splitCount = currentAssignment.getSplits().size(); hostRequirement = Optional.of(hostAddress); } From 4345db97b92666e9cf8238eb28e5da5e1795d391 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 29 Nov 2023 20:34:47 +0100 Subject: [PATCH 530/587] Fix indentation --- .../tests/product/hive/TestHiveTransactionalTable.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveTransactionalTable.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveTransactionalTable.java index d51aa798fb66..fbf11075f1e9 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveTransactionalTable.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveTransactionalTable.java @@ -2146,10 +2146,10 @@ private static void compactTableAndWait(CompactionMode compactMode, String table log.info("Running %s compaction on %s", compactMode, tableName); Failsafe.with( - RetryPolicy.builder() - .withMaxDuration(java.time.Duration.ofMillis(timeout.toMillis())) - .withMaxAttempts(Integer.MAX_VALUE) // limited by MaxDuration - .build()) + RetryPolicy.builder() + .withMaxDuration(java.time.Duration.ofMillis(timeout.toMillis())) + .withMaxAttempts(Integer.MAX_VALUE) // limited by MaxDuration + .build()) .onFailure(event -> { throw new IllegalStateException(format("Could not compact table %s in %d retries", tableName, event.getAttemptCount()), event.getException()); }) From 737a7a808e096cdcd8fb2a5721c98dc02101a18a Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 29 Nov 2023 20:37:39 +0100 Subject: [PATCH 531/587] Fix test groups for some Hive ACID product tests --- .../tests/product/hive/TestHiveTransactionalTable.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveTransactionalTable.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveTransactionalTable.java index fbf11075f1e9..78bde2e95511 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveTransactionalTable.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveTransactionalTable.java @@ -1954,7 +1954,7 @@ private void validateFileIsDirectlyUnderTableLocation(String tableName) "files in %s are not directly under table location", path)); } - @Test + @Test(groups = HIVE_TRANSACTIONAL) public void testDeleteAfterMajorCompaction() { withTemporaryTable("test_delete_after_major_compaction", true, false, NONE, tableName -> { @@ -1965,13 +1965,13 @@ public void testDeleteAfterMajorCompaction() }); } - @Test + @Test(groups = HIVE_TRANSACTIONAL) public void testUnbucketedPartitionedTransactionalTableWithTaskWriterCountGreaterThanOne() { unbucketedTransactionalTableWithTaskWriterCountGreaterThanOne(true); } - @Test + @Test(groups = HIVE_TRANSACTIONAL) public void testUnbucketedTransactionalTableWithTaskWriterCountGreaterThanOne() { unbucketedTransactionalTableWithTaskWriterCountGreaterThanOne(false); From 8b215c270737bbc988decd9173d55ef4af33b9f7 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 29 Nov 2023 20:33:55 +0100 Subject: [PATCH 532/587] Annotate more TestHiveTransactionalTable methods for retries Some test were marked as flaky and had retries enabled, but many others are equally affected. This also updates links from https://github.com/trinodb/trino/issues/4927 to https://github.com/trinodb/trino/issues/16315. --- .../hive/TestHiveTransactionalTable.java | 93 +++++++++++++++++-- 1 file changed, 85 insertions(+), 8 deletions(-) diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveTransactionalTable.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveTransactionalTable.java index 78bde2e95511..1d38d71c9665 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveTransactionalTable.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHiveTransactionalTable.java @@ -89,6 +89,9 @@ public class TestHiveTransactionalTable // Older Trino path ends look like /20210416_190616_00000_fsymd_af6f0a3d-5449-4478-a53d-9f9f99c07ed9 private static final Pattern ORIGINAL_FILE_MATCHER = Pattern.compile(".*/\\d+_\\d+(_[^/]+)?$"); + private static final String ACID_CORRUPTION_DIRECTORY_ISSUE = "https://github.com/trinodb/trino/issues/16315"; + private static final String ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN = "Hive table .* is corrupt. Found sub-directory in bucket directory for partition"; + @Inject private TestHiveMetastoreClientFactory testHiveMetastoreClientFactory; @@ -101,7 +104,7 @@ public void testReadFullAcid() doTestReadFullAcid(false, BucketingType.NONE); } - @Flaky(issue = "https://github.com/trinodb/trino/issues/4927", match = "Hive table .* is corrupt. Found sub-directory in bucket directory for partition") + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) public void testReadFullAcidBucketed() { @@ -109,6 +112,7 @@ public void testReadFullAcidBucketed() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testReadFullAcidPartitioned() { doTestReadFullAcid(true, BucketingType.NONE); @@ -117,19 +121,20 @@ public void testReadFullAcidPartitioned() // This test is in STORAGE_FORMATS group to ensure test coverage of transactional tables with various // metastore and HDFS setups (kerberized or not, impersonation or not). @Test(groups = {HIVE_TRANSACTIONAL, STORAGE_FORMATS}, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testReadFullAcidPartitionedBucketed() { doTestReadFullAcid(true, BucketingType.BUCKETED_DEFAULT); } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) - @Flaky(issue = "https://github.com/trinodb/trino/issues/4927", match = "Hive table .* is corrupt. Found sub-directory in bucket directory for partition") + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testReadFullAcidBucketedV1() { doTestReadFullAcid(false, BucketingType.BUCKETED_V1); } - @Flaky(issue = "https://github.com/trinodb/trino/issues/4927", match = "Hive table .* is corrupt. Found sub-directory in bucket directory for partition") + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) public void testReadFullAcidBucketedV2() { @@ -196,27 +201,28 @@ private void doTestReadFullAcid(boolean isPartitioned, BucketingType bucketingTy } @Test(groups = HIVE_TRANSACTIONAL, dataProvider = "partitioningAndBucketingTypeDataProvider", timeOut = TEST_TIMEOUT) - @Flaky(issue = "https://github.com/trinodb/trino/issues/4927", match = "Hive table .* is corrupt. Found sub-directory in bucket directory for partition") + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testReadInsertOnlyOrc(boolean isPartitioned, BucketingType bucketingType) { testReadInsertOnly(isPartitioned, bucketingType, "STORED AS ORC"); } @Test(groups = HIVE_TRANSACTIONAL, dataProvider = "partitioningAndBucketingTypeSmokeDataProvider", timeOut = TEST_TIMEOUT) - @Flaky(issue = "https://github.com/trinodb/trino/issues/4927", match = "Hive table .* is corrupt. Found sub-directory in bucket directory for partition") + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testReadInsertOnlyParquet(boolean isPartitioned, BucketingType bucketingType) { testReadInsertOnly(isPartitioned, bucketingType, "STORED AS PARQUET"); } @Test(groups = HIVE_TRANSACTIONAL, dataProvider = "partitioningAndBucketingTypeSmokeDataProvider", timeOut = TEST_TIMEOUT) - @Flaky(issue = "https://github.com/trinodb/trino/issues/4927", match = "Hive table .* is corrupt. Found sub-directory in bucket directory for partition") + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testReadInsertOnlyText(boolean isPartitioned, BucketingType bucketingType) { testReadInsertOnly(isPartitioned, bucketingType, "STORED AS TEXTFILE"); } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testReadInsertOnlyTextWithCustomFormatProperties() { testReadInsertOnly( @@ -276,7 +282,7 @@ private void testReadInsertOnly(boolean isPartitioned, BucketingType bucketingTy } @Test(groups = {STORAGE_FORMATS, HIVE_TRANSACTIONAL}, dataProvider = "partitioningAndBucketingTypeDataProvider", timeOut = TEST_TIMEOUT) - @Flaky(issue = "https://github.com/trinodb/trino/issues/4927", match = "Hive table .* is corrupt. Found sub-directory in bucket directory for partition") + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testReadFullAcidWithOriginalFiles(boolean isPartitioned, BucketingType bucketingType) { if (getHiveVersionMajor() < 3) { @@ -426,7 +432,7 @@ String makeValues(int colStart, int colCount, int fcol, boolean isPartitioned, i } @Test(groups = {STORAGE_FORMATS, HIVE_TRANSACTIONAL}, dataProvider = "partitioningAndBucketingTypeDataProvider", timeOut = TEST_TIMEOUT) - @Flaky(issue = "https://github.com/trinodb/trino/issues/4927", match = "Hive table .* is corrupt. Found sub-directory in bucket directory for partition") + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testReadInsertOnlyWithOriginalFiles(boolean isPartitioned, BucketingType bucketingType) { if (getHiveVersionMajor() < 3) { @@ -465,6 +471,7 @@ public void testReadInsertOnlyWithOriginalFiles(boolean isPartitioned, Bucketing } @Test(groups = HIVE_TRANSACTIONAL) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testFailAcidBeforeHive3() { if (getHiveVersionMajor() >= 3) { @@ -505,6 +512,7 @@ public Object[][] partitioningAndBucketingTypeSmokeDataProvider() } @Test(groups = HIVE_TRANSACTIONAL, dataProvider = "testCreateAcidTableDataProvider") + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testCtasAcidTable(boolean isPartitioned, BucketingType bucketingType) { if (getHiveVersionMajor() < 3) { @@ -528,6 +536,7 @@ public void testCtasAcidTable(boolean isPartitioned, BucketingType bucketingType } @Test(groups = HIVE_TRANSACTIONAL, dataProvider = "testCreateAcidTableDataProvider") + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testCreateAcidTable(boolean isPartitioned, BucketingType bucketingType) { withTemporaryTable("create_transactional", true, isPartitioned, bucketingType, tableName -> { @@ -540,6 +549,7 @@ public void testCreateAcidTable(boolean isPartitioned, BucketingType bucketingTy } @Test(groups = HIVE_TRANSACTIONAL, dataProvider = "acidFormatColumnNames") + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testAcidTableColumnNameConflict(String columnName) { withTemporaryTable("acid_column_name_conflict", true, true, NONE, tableName -> { @@ -563,6 +573,7 @@ public Object[][] acidFormatColumnNames() } @Test(groups = HIVE_TRANSACTIONAL) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testSimpleUnpartitionedTransactionalInsert() { withTemporaryTable("unpartitioned_transactional_insert", true, false, NONE, tableName -> { @@ -585,6 +596,7 @@ public void testSimpleUnpartitionedTransactionalInsert() } @Test(groups = HIVE_TRANSACTIONAL) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testTransactionalPartitionInsert() { withTemporaryTable("transactional_partition_insert", true, true, NONE, tableName -> { @@ -618,12 +630,14 @@ public void testTransactionalPartitionInsert() } @Test(groups = HIVE_TRANSACTIONAL) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testTransactionalBucketedPartitionedInsert() { testTransactionalBucketedPartitioned(false); } @Test(groups = HIVE_TRANSACTIONAL) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testTransactionalBucketedPartitionedInsertOnly() { testTransactionalBucketedPartitioned(true); @@ -656,6 +670,7 @@ private void testTransactionalBucketedPartitioned(boolean insertOnly) } @Test(groups = HIVE_TRANSACTIONAL, dataProvider = "inserterAndDeleterProvider", timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testTransactionalUnpartitionedDelete(Engine inserter, Engine deleter) { withTemporaryTable("unpartitioned_delete", true, false, NONE, tableName -> { @@ -674,6 +689,7 @@ public void testTransactionalUnpartitionedDelete(Engine inserter, Engine deleter } @Test(groups = HIVE_TRANSACTIONAL, dataProvider = "inserterAndDeleterProvider", timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testMultiDelete(Engine inserter, Engine deleter) { withTemporaryTable("unpartitioned_multi_delete", true, false, NONE, tableName -> { @@ -688,6 +704,7 @@ public void testMultiDelete(Engine inserter, Engine deleter) } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testReadAfterMultiInsertAndDelete() { // Test reading from a table after Hive multi-insert. Multi-insert involves non-zero statement ID, encoded @@ -717,6 +734,7 @@ public void testReadAfterMultiInsertAndDelete() } @Test(groups = HIVE_TRANSACTIONAL, dataProvider = "inserterAndDeleterProvider", timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testTransactionalMetadataDelete(Engine inserter, Engine deleter) { withTemporaryTable("metadata_delete", true, true, NONE, tableName -> { @@ -754,6 +772,7 @@ public void testNonTransactionalMetadataDelete() } @Test(groups = HIVE_TRANSACTIONAL, dataProvider = "inserterAndDeleterProvider", timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testUnpartitionedDeleteAll(Engine inserter, Engine deleter) { withTemporaryTable("unpartitioned_delete_all", true, false, NONE, tableName -> { @@ -765,6 +784,7 @@ public void testUnpartitionedDeleteAll(Engine inserter, Engine deleter) } @Test(groups = HIVE_TRANSACTIONAL, dataProvider = "inserterAndDeleterProvider", timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testMultiColumnDelete(Engine inserter, Engine deleter) { withTemporaryTable("multi_column_delete", true, false, NONE, tableName -> { @@ -777,6 +797,7 @@ public void testMultiColumnDelete(Engine inserter, Engine deleter) } @Test(groups = HIVE_TRANSACTIONAL, dataProvider = "inserterAndDeleterProvider", timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testPartitionAndRowsDelete(Engine inserter, Engine deleter) { withTemporaryTable("partition_and_rows_delete", true, true, NONE, tableName -> { @@ -790,6 +811,7 @@ public void testPartitionAndRowsDelete(Engine inserter, Engine deleter) } @Test(groups = HIVE_TRANSACTIONAL, dataProvider = "inserterAndDeleterProvider", timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testPartitionedInsertAndRowLevelDelete(Engine inserter, Engine deleter) { withTemporaryTable("partitioned_row_level_delete", true, true, NONE, tableName -> { @@ -844,6 +866,7 @@ public void testBucketedPartitionedDelete(Engine inserter, Engine deleter) } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testDeleteAllRowsInPartition() { withTemporaryTable("bucketed_partitioned_delete", true, true, NONE, tableName -> { @@ -861,6 +884,7 @@ public void testDeleteAllRowsInPartition() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testDeleteAfterDelete() { withTemporaryTable("delete_after_delete", true, false, NONE, tableName -> { @@ -879,6 +903,7 @@ public void testDeleteAfterDelete() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testDeleteAfterDeleteWithPredicate() { withTemporaryTable("delete_after_delete_predicate", true, false, NONE, tableName -> { @@ -898,6 +923,7 @@ public void testDeleteAfterDeleteWithPredicate() } @Test(groups = HIVE_TRANSACTIONAL, dataProvider = "inserterAndDeleterProvider", timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testBucketedUnpartitionedDelete(Engine inserter, Engine deleter) { withTemporaryTable("bucketed_unpartitioned_delete", true, true, NONE, tableName -> { @@ -928,6 +954,7 @@ public void testBucketedUnpartitionedDelete(Engine inserter, Engine deleter) } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testDeleteOverManySplits() { withTemporaryTable("delete_select", true, false, NONE, tableName -> { @@ -941,6 +968,7 @@ public void testDeleteOverManySplits() } @Test(groups = HIVE_TRANSACTIONAL, dataProvider = "inserterAndDeleterProvider", timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testCorrectSelectCountStar(Engine inserter, Engine deleter) { withTemporaryTable("select_count_star_delete", true, true, NONE, tableName -> { @@ -953,6 +981,7 @@ public void testCorrectSelectCountStar(Engine inserter, Engine deleter) } @Test(groups = HIVE_TRANSACTIONAL, dataProvider = "insertersProvider", timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testInsertOnlyMultipleWriters(boolean bucketed, Engine inserter1, Engine inserter2) { log.info("testInsertOnlyMultipleWriters bucketed %s, inserter1 %s, inserter2 %s", bucketed, inserter1, inserter2); @@ -977,6 +1006,7 @@ public void testInsertOnlyMultipleWriters(boolean bucketed, Engine inserter1, En } @Test(groups = HIVE_TRANSACTIONAL) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testInsertFailsInExplicitTrinoTransaction() { withTemporaryTable("insert_fail_explicit_transaction", true, false, NONE, tableName -> { @@ -988,6 +1018,7 @@ public void testInsertFailsInExplicitTrinoTransaction() } @Test(groups = HIVE_TRANSACTIONAL) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testUpdateFailsInExplicitTrinoTransaction() { withTemporaryTable("update_fail_explicit_transaction", true, false, NONE, tableName -> { @@ -999,6 +1030,7 @@ public void testUpdateFailsInExplicitTrinoTransaction() } @Test(groups = HIVE_TRANSACTIONAL) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testDeleteFailsInExplicitTrinoTransaction() { withTemporaryTable("delete_fail_explicit_transaction", true, false, NONE, tableName -> { @@ -1010,6 +1042,7 @@ public void testDeleteFailsInExplicitTrinoTransaction() } @Test(groups = HIVE_TRANSACTIONAL, dataProvider = "transactionModeProvider") + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testColumnRenamesOrcPartitioned(boolean transactional) { ensureSchemaEvolutionSupported(); @@ -1025,6 +1058,7 @@ public void testColumnRenamesOrcPartitioned(boolean transactional) } @Test(groups = HIVE_TRANSACTIONAL, dataProvider = "transactionModeProvider") + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testColumnRenamesOrcNotPartitioned(boolean transactional) { ensureSchemaEvolutionSupported(); @@ -1058,6 +1092,7 @@ private void testOrcColumnRenames(String tableName) } @Test(groups = HIVE_TRANSACTIONAL, dataProvider = "transactionModeProvider") + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testOrcColumnSwap(boolean transactional) { ensureSchemaEvolutionSupported(); @@ -1075,6 +1110,7 @@ public void testOrcColumnSwap(boolean transactional) } @Test(groups = HIVE_TRANSACTIONAL) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testBehaviorOnParquetColumnRenames() { ensureSchemaEvolutionSupported(); @@ -1098,6 +1134,7 @@ public void testBehaviorOnParquetColumnRenames() } @Test(groups = HIVE_TRANSACTIONAL, dataProvider = "transactionModeProvider") + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testOrcColumnDropAdd(boolean transactional) { ensureSchemaEvolutionSupported(); @@ -1120,6 +1157,7 @@ public void testOrcColumnDropAdd(boolean transactional) } @Test(groups = HIVE_TRANSACTIONAL, dataProvider = "transactionModeProvider") + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testOrcColumnTypeChange(boolean transactional) { ensureSchemaEvolutionSupported(); @@ -1139,6 +1177,7 @@ public void testOrcColumnTypeChange(boolean transactional) } @Test(groups = HIVE_TRANSACTIONAL) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testParquetColumnDropAdd() { ensureSchemaEvolutionSupported(); @@ -1175,6 +1214,7 @@ public Object[][] transactionModeProvider() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testAcidUpdateFailNonTransactional() { withTemporaryTable("update_fail_nontransactional", true, true, NONE, tableName -> { @@ -1190,6 +1230,7 @@ public void testAcidUpdateFailNonTransactional() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testAcidUpdateFailInsertOnlyTable() { withTemporaryTable("update_fail_insert_only", true, false, NONE, tableName -> { @@ -1207,6 +1248,7 @@ public void testAcidUpdateFailInsertOnlyTable() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testAcidDeleteFailNonTransactional() { withTemporaryTable("delete_fail_nontransactional", true, true, NONE, tableName -> { @@ -1222,6 +1264,7 @@ public void testAcidDeleteFailNonTransactional() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testAcidDeleteFailInsertOnlyTable() { withTemporaryTable("delete_fail_insert_only", true, false, NONE, tableName -> { @@ -1239,6 +1282,7 @@ public void testAcidDeleteFailInsertOnlyTable() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testAcidUpdateSucceedUpdatingPartitionKey() { withTemporaryTable("fail_update_partition_key", true, true, NONE, tableName -> { @@ -1256,6 +1300,7 @@ public void testAcidUpdateSucceedUpdatingPartitionKey() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testAcidUpdateSucceedUpdatingBucketColumn() { withTemporaryTable("fail_update_bucket_column", true, true, NONE, tableName -> { @@ -1273,6 +1318,7 @@ public void testAcidUpdateSucceedUpdatingBucketColumn() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testAcidUpdateFailOnIllegalCast() { withTemporaryTable("fail_update_on_illegal_cast", true, true, NONE, tableName -> { @@ -1288,6 +1334,7 @@ public void testAcidUpdateFailOnIllegalCast() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testAcidUpdateSimple() { withTemporaryTable("acid_update_simple", true, true, NONE, tableName -> { @@ -1302,6 +1349,7 @@ public void testAcidUpdateSimple() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testAcidUpdateSelectedValues() { withTemporaryTable("acid_update_simple_selected", true, true, NONE, tableName -> { @@ -1316,6 +1364,7 @@ public void testAcidUpdateSelectedValues() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testAcidUpdateCopyColumn() { withTemporaryTable("acid_update_copy_column", true, true, NONE, tableName -> { @@ -1330,6 +1379,7 @@ public void testAcidUpdateCopyColumn() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testAcidUpdateSomeLiteralNullColumnValues() { withTemporaryTable("update_some_literal_null_columns", true, true, NONE, tableName -> { @@ -1348,6 +1398,7 @@ public void testAcidUpdateSomeLiteralNullColumnValues() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testAcidUpdateSomeComputedNullColumnValues() { withTemporaryTable("update_some_computed_null_columns", true, true, NONE, tableName -> { @@ -1367,6 +1418,7 @@ public void testAcidUpdateSomeComputedNullColumnValues() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testAcidUpdateAllLiteralNullColumnValues() { withTemporaryTable("update_all_literal_null_columns", true, true, NONE, tableName -> { @@ -1381,6 +1433,7 @@ public void testAcidUpdateAllLiteralNullColumnValues() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testAcidUpdateAllComputedNullColumnValues() { withTemporaryTable("update_all_computed_null_columns", true, true, NONE, tableName -> { @@ -1396,6 +1449,7 @@ public void testAcidUpdateAllComputedNullColumnValues() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testAcidUpdateReversed() { withTemporaryTable("update_reversed", true, true, NONE, tableName -> { @@ -1410,6 +1464,7 @@ public void testAcidUpdateReversed() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testAcidUpdatePermuted() { withTemporaryTable("update_permuted", true, true, NONE, tableName -> { @@ -1424,6 +1479,7 @@ public void testAcidUpdatePermuted() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testAcidUpdateAllColumnsSetAndDependencies() { withTemporaryTable("update_all_columns_set", true, true, NONE, tableName -> { @@ -1438,6 +1494,7 @@ public void testAcidUpdateAllColumnsSetAndDependencies() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testAcidUpdatePartitioned() { withTemporaryTable("update_partitioned", true, true, NONE, tableName -> { @@ -1454,6 +1511,7 @@ public void testAcidUpdatePartitioned() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testAcidUpdateBucketed() { withTemporaryTable("update_bucketed", true, true, NONE, tableName -> { @@ -1470,6 +1528,7 @@ public void testAcidUpdateBucketed() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testAcidUpdateMajorCompaction() { withTemporaryTable("schema_evolution_column_addition", true, false, NONE, tableName -> { @@ -1491,6 +1550,7 @@ public void testAcidUpdateMajorCompaction() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testAcidUpdateWithSubqueryPredicate() { withTemporaryTable("test_update_subquery", true, false, NONE, tableName -> { @@ -1521,6 +1581,7 @@ public void testAcidUpdateWithSubqueryPredicate() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testAcidUpdateWithSubqueryAssignment() { withTemporaryTable("test_update_subquery", true, false, NONE, tableName -> { @@ -1551,6 +1612,7 @@ public void testAcidUpdateWithSubqueryAssignment() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testAcidUpdateDuplicateUpdateValue() { withTemporaryTable("test_update_bug", true, false, NONE, tableName -> { @@ -1586,6 +1648,7 @@ public void testAcidUpdateDuplicateUpdateValue() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testAcidUpdateMultipleDuplicateValues() { withTemporaryTable("test_update_multiple", true, false, NONE, tableName -> { @@ -1649,6 +1712,7 @@ public void testInsertDeleteUpdateWithTrinoAndHive() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testDeleteFromOriginalFiles() { withTemporaryTable("delete_original_files", true, true, NONE, tableName -> { @@ -1664,12 +1728,14 @@ public void testDeleteFromOriginalFiles() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testDeleteWholePartition() { testDeleteWholePartition(false); } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testDeleteWholePartitionWithOriginalFiles() { testDeleteWholePartition(true); @@ -1709,6 +1775,7 @@ private void testDeleteWholePartition(boolean withOriginalFiles) } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testUpdateOriginalFilesPartitioned() { withTemporaryTable("update_original_files", true, true, NONE, tableName -> { @@ -1722,6 +1789,7 @@ public void testUpdateOriginalFilesPartitioned() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testUpdateOriginalFilesUnpartitioned() { withTemporaryTable("update_original_files", true, true, NONE, tableName -> { @@ -1735,6 +1803,7 @@ public void testUpdateOriginalFilesUnpartitioned() } @Test(groups = HIVE_TRANSACTIONAL, timeOut = TEST_TIMEOUT) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testInsertRowIdCorrectness() { withTemporaryTable("test_insert_row_id_correctness", true, false, NONE, tableName -> { @@ -1877,6 +1946,7 @@ public void testFilesForAbortedTransactionsIgnored() } @Test(groups = HIVE_TRANSACTIONAL) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testDoubleUpdateAndThenReadFromHive() { withTemporaryTable("test_double_update", true, false, NONE, tableName -> { @@ -1897,6 +1967,7 @@ public void testDoubleUpdateAndThenReadFromHive() } @Test(groups = HIVE_TRANSACTIONAL) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testDeleteWithOriginalFiles() { withTemporaryTable("test_delete_with_original_files", true, false, NONE, tableName -> { @@ -1918,6 +1989,7 @@ public void testDeleteWithOriginalFiles() } @Test(groups = HIVE_TRANSACTIONAL) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testDeleteWithOriginalFilesWithWhereClause() { withTemporaryTable("test_delete_with_original_files_with_where_clause", true, false, NONE, tableName -> { @@ -1955,6 +2027,7 @@ private void validateFileIsDirectlyUnderTableLocation(String tableName) } @Test(groups = HIVE_TRANSACTIONAL) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testDeleteAfterMajorCompaction() { withTemporaryTable("test_delete_after_major_compaction", true, false, NONE, tableName -> { @@ -1966,12 +2039,14 @@ public void testDeleteAfterMajorCompaction() } @Test(groups = HIVE_TRANSACTIONAL) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testUnbucketedPartitionedTransactionalTableWithTaskWriterCountGreaterThanOne() { unbucketedTransactionalTableWithTaskWriterCountGreaterThanOne(true); } @Test(groups = HIVE_TRANSACTIONAL) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testUnbucketedTransactionalTableWithTaskWriterCountGreaterThanOne() { unbucketedTransactionalTableWithTaskWriterCountGreaterThanOne(false); @@ -2020,6 +2095,7 @@ private void unbucketedTransactionalTableWithTaskWriterCountGreaterThanOne(boole } @Test(groups = HIVE_TRANSACTIONAL) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testLargePartitionedDelete() { if (getHiveVersionMajor() < 3) { @@ -2053,6 +2129,7 @@ public void testLargePartitionedDelete() } @Test(groups = HIVE_TRANSACTIONAL) + @Flaky(issue = ACID_CORRUPTION_DIRECTORY_ISSUE, match = ACID_CORRUPTION_DIRECTORY_RETRY_PATTERN) public void testLargePartitionedUpdate() { if (getHiveVersionMajor() < 3) { From 2f5722676ef3f6c7bc63a0984dd96f28fb833306 Mon Sep 17 00:00:00 2001 From: Manfred Moser Date: Thu, 23 Nov 2023 10:42:52 -0800 Subject: [PATCH 533/587] Improve SQL routine docs --- docs/src/main/sphinx/routines/function.md | 2 +- docs/src/main/sphinx/routines/introduction.md | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/docs/src/main/sphinx/routines/function.md b/docs/src/main/sphinx/routines/function.md index 151578437c0b..21df3a95c392 100644 --- a/docs/src/main/sphinx/routines/function.md +++ b/docs/src/main/sphinx/routines/function.md @@ -5,7 +5,7 @@ ```text FUNCTION name ( [ parameter_name data_type [, ...] ] ) RETURNS type - [ LANGUAGE langauge] + [ LANGUAGE language] [ NOT? DETERMINISTIC ] [ RETURNS NULL ON NULL INPUT ] [ CALLED ON NULL INPUT ] diff --git a/docs/src/main/sphinx/routines/introduction.md b/docs/src/main/sphinx/routines/introduction.md index d1191ae7dab1..ab3377ba75a1 100644 --- a/docs/src/main/sphinx/routines/introduction.md +++ b/docs/src/main/sphinx/routines/introduction.md @@ -125,11 +125,11 @@ before the following keywords: * `REPEAT` * `WHILE` -The label is used to name the block in order to continue processing with the -`ITERATE` statement or exit the block with the `LEAVE` statement. This flow -control is supported for nested blocks, allowing to continue or exit an outer -block, not just the innermost block. For example, the following snippet uses the -label `top` to name the complete block from `REPEAT` to `END REPEAT`: +The label is used to name the block to continue processing with the `ITERATE` +statement or exit the block with the `LEAVE` statement. This flow control is +supported for nested blocks, allowing to continue or exit an outer block, not +just the innermost block. For example, the following snippet uses the label +`top` to name the complete block from `REPEAT` to `END REPEAT`: ```sql top: REPEAT @@ -171,7 +171,12 @@ when writing and running SQL routines: The following limitations apply to SQL routines. -* Routines must be declared before than can be referenced. +* Routines must be declared before they are referenced. * Recursion cannot be declared or processed. * Mutual recursion can not be declared or processed. * Queries cannot be processed in a routine. + +Specifically this means that routines can not use `SELECT` queries to retrieve +data or any other queries to process data within the routine. Instead queries +can use routines to process data. Routines only work on data provided as input +values and only provide output data from the `RETURN` statement. From e5da79732ccb01baeedf4819f3239a6bcf88a955 Mon Sep 17 00:00:00 2001 From: Manfred Moser Date: Wed, 29 Nov 2023 11:24:39 -0800 Subject: [PATCH 534/587] Add docs for hiding MV storage table --- docs/src/main/sphinx/connector/iceberg.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/src/main/sphinx/connector/iceberg.md b/docs/src/main/sphinx/connector/iceberg.md index 69c8c3cb81c7..869be0bbafd9 100644 --- a/docs/src/main/sphinx/connector/iceberg.md +++ b/docs/src/main/sphinx/connector/iceberg.md @@ -159,6 +159,10 @@ implementation is used: materialized view definition. When the `storage_schema` materialized view property is specified, it takes precedence over this catalog property. - Empty +* - `iceberg.materialized-views.hide-storage-table` + - Hide the information about the storage table backing the materialized view + in the metastore. + - `true` * - `iceberg.register-table-procedure.enabled` - Enable to allow user to call `register_table` procedure. - `false` From 8a354b26ba466041500f2b719e40e4d06d1c2004 Mon Sep 17 00:00:00 2001 From: Manfred Moser Date: Thu, 23 Nov 2023 15:32:29 -0800 Subject: [PATCH 535/587] Improve table function docs --- docs/src/main/sphinx/functions/table.md | 30 ++++++++++++------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/docs/src/main/sphinx/functions/table.md b/docs/src/main/sphinx/functions/table.md index a06fa4f13efb..4030a97c0b62 100644 --- a/docs/src/main/sphinx/functions/table.md +++ b/docs/src/main/sphinx/functions/table.md @@ -3,7 +3,7 @@ A table function is a function returning a table. It can be invoked inside the `FROM` clause of a query: -``` +```sql SELECT * FROM TABLE(my_function(1, 100)) ``` @@ -15,25 +15,24 @@ Polymorphic table functions allow you to dynamically invoke custom logic from within the SQL query. They can be used for working with external systems as well as for enhancing Trino with capabilities going beyond the SQL standard. -For the list of built-in table functions available in Trino, see {ref}`built in -table functions`. +For the list of built-in table functions available in Trino, see [built in +table functions](built-in-table-functions). Trino supports adding custom table functions. They are declared by connectors through implementing dedicated interfaces. For guidance on adding new table -functions, see the {doc}`developer guide`. +functions, see the [developer guide](/develop/table-functions). Connectors offer support for different functions on a per-connector basis. For -more information about supported table functions, refer to the {doc}`connector -documentation <../../connector>`. +more information about supported table functions, refer to the [connector +documentation](/connector). (built-in-table-functions)= - ## Built-in table functions :::{function} exclude_columns(input => table, columns => descriptor) -> table Excludes from `table` all columns listed in `descriptor`: -``` +```sql SELECT * FROM TABLE(exclude_columns( input => TABLE(orders), @@ -45,14 +44,13 @@ The argument `columns` is a descriptor without types. ::: (sequence-table-function)= - :::{function} sequence(start => bigint, stop => bigint, step => bigint) -> table(sequential_number bigint) :noindex: true Returns a single column `sequential_number` containing a sequence of bigint: -``` +```sql SELECT * FROM TABLE(sequence( start => 1000000, @@ -60,7 +58,7 @@ FROM TABLE(sequence( step => -3)) ``` -`start` is the first element in te sequence. The default value is `0`. +`start` is the first element in the sequence. The default value is `0`. `stop` is the end of the range, inclusive. The last element in the sequence is equal to `stop`, or it is the last value within range, @@ -85,7 +83,7 @@ Every table function is provided by a catalog, and it belongs to a schema in the catalog. You can qualify the function name with a schema name, or with catalog and schema names: -``` +```sql SELECT * FROM TABLE(schema_name.my_function(1, 100)) SELECT * FROM TABLE(catalog_name.schema_name.my_function(1, 100)) ``` @@ -143,7 +141,7 @@ argument is processed as a single partition. You can also specify declare that you are not interested in the function result if the argument is empty. This information is used by the Trino engine to optimize the query. The `KEEP WHEN EMPTY` option indicates that the function should be executed even -if the table argument is empty. Note that by specifying `KEEP WHEN EMPTY` or +if the table argument is empty. By specifying `KEEP WHEN EMPTY` or `PRUNE WHEN EMPTY`, you override the property set for the argument by the function author. @@ -162,7 +160,7 @@ There are two conventions of passing arguments to a table function: - **Arguments passed by name**: - ``` + ```sql SELECT * FROM TABLE(my_function(row_count => 100, column_count => 1)) ``` @@ -172,7 +170,7 @@ case-sensitive, and with automatic uppercasing of unquoted names. - **Arguments passed positionally**: - ``` + ```sql SELECT * FROM TABLE(my_function(1, 100)) ``` @@ -184,7 +182,7 @@ You cannot mix the argument conventions in one invocation. You can also use parameters in arguments: -``` +```sql PREPARE stmt FROM SELECT * FROM TABLE(my_function(row_count => ? + 1, column_count => ?)); From adf80ebfed21b6bfd99edd217b9dd5caecd560b8 Mon Sep 17 00:00:00 2001 From: Manfred Moser Date: Mon, 20 Nov 2023 16:19:06 -0800 Subject: [PATCH 536/587] Link to custom types and functions --- docs/src/main/sphinx/functions.md | 5 +++++ docs/src/main/sphinx/language/types.md | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/src/main/sphinx/functions.md b/docs/src/main/sphinx/functions.md index 3478e1a92786..c445861ecce0 100644 --- a/docs/src/main/sphinx/functions.md +++ b/docs/src/main/sphinx/functions.md @@ -9,6 +9,11 @@ Refer to the following sections for further details: * [SQL data types and other general aspects](/language) * [SQL statement and syntax reference](/sql) +In addition, Trino supports implementation of [custom +functions](/develop/functions) or [custom table +functions](/develop/table-functions) provided by a plugin, and creation of +user-defined functions as [SQL routines](/routines). + ## Functions by name If you are looking for a specific function or operator by name use diff --git a/docs/src/main/sphinx/language/types.md b/docs/src/main/sphinx/language/types.md index bf95d07ff3ba..c69c8202eb4d 100644 --- a/docs/src/main/sphinx/language/types.md +++ b/docs/src/main/sphinx/language/types.md @@ -1,7 +1,7 @@ # Data types -Trino has a set of built-in data types, described below. -Additional types can be provided by plugins. +Trino has a set of built-in data types, described below. Additional types can be +[provided by plugins](/develop/types). (type-mapping-overview)= From 3f24cf9438501149178b04269541faa827a9dc2a Mon Sep 17 00:00:00 2001 From: Manfred Moser Date: Mon, 20 Nov 2023 15:34:31 -0800 Subject: [PATCH 537/587] Clarify json_path requirement --- docs/src/main/sphinx/functions/json.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/src/main/sphinx/functions/json.md b/docs/src/main/sphinx/functions/json.md index 249db0af144c..e38cd090dfc4 100644 --- a/docs/src/main/sphinx/functions/json.md +++ b/docs/src/main/sphinx/functions/json.md @@ -796,8 +796,9 @@ JSON_QUERY( ) ``` -The `json_path` is evaluated using the `json_input` as the context variable -(`$`), and the passed arguments as the named variables (`$variable_name`). +The constant string `json_path` is evaluated using the `json_input` as the +context variable (`$`), and the passed arguments as the named variables +(`$variable_name`). The returned value is a JSON item returned by the path. By default, it is represented as a character string (`varchar`). In the `RETURNING` clause, From bf0ccbe71ca75ae3daa2169e519d1f18dae9f1e2 Mon Sep 17 00:00:00 2001 From: Manfred Moser Date: Mon, 20 Nov 2023 14:52:42 -0800 Subject: [PATCH 538/587] Improve ordering of SQL support overview --- .../main/sphinx/admin/properties-catalog.md | 3 ++ docs/src/main/sphinx/language/sql-support.md | 29 +++++++------------ 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/docs/src/main/sphinx/admin/properties-catalog.md b/docs/src/main/sphinx/admin/properties-catalog.md index 52dddec22617..f04a562b1235 100644 --- a/docs/src/main/sphinx/admin/properties-catalog.md +++ b/docs/src/main/sphinx/admin/properties-catalog.md @@ -1,5 +1,8 @@ # Catalog management properties +The following properties are used to configure catalog management with further +controls for dynamic catalog management. + (prop-catalog-management)= ## `catalog.management` diff --git a/docs/src/main/sphinx/language/sql-support.md b/docs/src/main/sphinx/language/sql-support.md index a30b3d1c59d5..0535f0a9dbe2 100644 --- a/docs/src/main/sphinx/language/sql-support.md +++ b/docs/src/main/sphinx/language/sql-support.md @@ -24,7 +24,6 @@ Details of the support for specific statements is available with the documentation for each connector. (sql-globally-available)= - ## Globally available statements The following statements are implemented in the core engine and available with @@ -48,7 +47,6 @@ any connector: - {doc}`/sql/values` (sql-read-operations)= - ## Read operations The following statements provide read access to data and meta data exposed by a @@ -69,7 +67,6 @@ connector accessing a data source. They are supported by all connectors: - {doc}`/sql/show-stats` (sql-write-operations)= - ## Write operations The following statements provide write access to data and meta data exposed @@ -77,7 +74,6 @@ by a connector accessing a data source. Availability varies widely from connector to connector: (sql-data-management)= - ### Data management - {doc}`/sql/insert` @@ -86,24 +82,16 @@ connector to connector: - {doc}`/sql/truncate` - {doc}`/sql/merge` -(sql-materialized-view-management)= - -### Materialized view management - -- {doc}`/sql/create-materialized-view` -- {doc}`/sql/alter-materialized-view` -- {doc}`/sql/drop-materialized-view` -- {doc}`/sql/refresh-materialized-view` - (sql-catalog-management)= - ### Catalog management +The following statements are used to [manage dynamic +catalogs](/admin/properties-catalog): + - {doc}`/sql/create-catalog` - {doc}`/sql/drop-catalog` (sql-schema-table-management)= - ### Schema and table management - {doc}`/sql/create-table` @@ -116,13 +104,20 @@ connector to connector: - {doc}`/sql/comment` (sql-view-management)= - ### View management - {doc}`/sql/create-view` - {doc}`/sql/drop-view` - {doc}`/sql/alter-view` +(sql-materialized-view-management)= +### Materialized view management + +- {doc}`/sql/create-materialized-view` +- {doc}`/sql/alter-materialized-view` +- {doc}`/sql/drop-materialized-view` +- {doc}`/sql/refresh-materialized-view` + (sql-routine-management)= ### Routine management @@ -133,7 +128,6 @@ The following statements are used to manage [catalog routines](routine-catalog): - [](/sql/show-functions) (sql-security-operations)= - ## Security operations The following statements provide security-related operations to security @@ -156,7 +150,6 @@ Grants management: - {doc}`/sql/revoke` (sql-transactions)= - ## Transactions The following statements manage transactions. Most connectors do not support From d8adf5760e1a6a8fe1452b78bd215d5502c51fef Mon Sep 17 00:00:00 2001 From: Manfred Moser Date: Mon, 20 Nov 2023 15:03:17 -0800 Subject: [PATCH 539/587] Change to exact numerics data type term --- docs/src/main/sphinx/language/types.md | 11 +++++------ docs/src/main/sphinx/routines/declare.md | 2 +- docs/src/main/sphinx/sql/show-stats.md | 4 ++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/src/main/sphinx/language/types.md b/docs/src/main/sphinx/language/types.md index c69c8202eb4d..e4acfe02a0ce 100644 --- a/docs/src/main/sphinx/language/types.md +++ b/docs/src/main/sphinx/language/types.md @@ -104,11 +104,10 @@ IEEE Standard 754 for Binary Floating-Point Arithmetic. Example literals: `DOUBLE '10.3'`, `DOUBLE '1.03e1'`, `10.3e0`, `1.03e1` -(fixed-precision-data-types)= +(exact-numeric-data-types)= +## Exact numeric -## Fixed-precision - -Fixed-precision numbers can be expressed as numeric literals such as `1.1`, and +Exact numeric values can be expressed as numeric literals such as `1.1`, and are supported by the `DECIMAL` data type. Underscore characters are ignored within literal values, and can be used to @@ -121,8 +120,8 @@ Leading zeros in literal values are permitted and ignored. For example, ### `DECIMAL` -A fixed-precision decimal number. Precision up to 38 digits is supported -but performance is best up to 18 digits. +A exact decimal number. Precision up to 38 digits is supported but performance +is best up to 18 digits. The decimal type takes two literal parameters: diff --git a/docs/src/main/sphinx/routines/declare.md b/docs/src/main/sphinx/routines/declare.md index 4ddcddf24e3a..2238ca2de0c0 100644 --- a/docs/src/main/sphinx/routines/declare.md +++ b/docs/src/main/sphinx/routines/declare.md @@ -30,7 +30,7 @@ characters: DECLARE first_name, last_name, middle_name varchar(25); ``` -A declaration of a fixed-precision decimal number with a default value: +A declaration of an exact decimal number with a default value: ```sql DECLARE uptime_requirement decimal DEFAULT 99.999; diff --git a/docs/src/main/sphinx/sql/show-stats.md b/docs/src/main/sphinx/sql/show-stats.md index 2c5f27af0123..0f6b020ccd02 100644 --- a/docs/src/main/sphinx/sql/show-stats.md +++ b/docs/src/main/sphinx/sql/show-stats.md @@ -47,11 +47,11 @@ here, are not included. - `NULL` in the table summary row. Available for columns of [DATE](date-data-type), [integer](integer-data-types), [floating-point](floating-point-data-types), and - [fixed-precision](fixed-precision-data-types) data types. + [exact numeric](exact-numeric-data-types) data types. * - `high_value` - The highest value found in this column - `NULL` in the table summary row. Available for columns of [DATE](date-data-type), [integer](integer-data-types), [floating-point](floating-point-data-types), and - [fixed-precision](fixed-precision-data-types) data types. + [exact numeric](exact-numeric-data-types) data types. ::: From 8010d6d5097274072a3453f6066f7c12ef7540d2 Mon Sep 17 00:00:00 2001 From: Manfred Moser Date: Mon, 20 Nov 2023 10:25:17 -0800 Subject: [PATCH 540/587] Fix vertical alignment for anchor links --- docs/src/main/sphinx/static/trino.css | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/src/main/sphinx/static/trino.css b/docs/src/main/sphinx/static/trino.css index 301374dcf71d..5afc4388af0c 100644 --- a/docs/src/main/sphinx/static/trino.css +++ b/docs/src/main/sphinx/static/trino.css @@ -18,6 +18,7 @@ div.highlight { color: #F57C00; } +/* Fix vertical alignment for links to header from other documents */ .md-typeset span[id]:target:before { display:block; margin-top:-40px; @@ -25,10 +26,20 @@ div.highlight { content:""; } +/* Fix vertical alignment for links to header from right hand nav */ +.md-typeset h1[id]:before, +.md-typeset h2[id]:before, +.md-typeset h3[id]:before, +.md-typeset h4[id]:before, +.md-typeset h5[id]:before, +.md-typeset h6[id]:before, +.md-typeset dl[id]:before { + margin-top:-3.45rem; + padding-top:3.45rem; +} + .md-typeset dl[id]:target:before { display:block; - margin-top:-82px; - padding-top:82px; content:""; } From 504b906acb05b464835c280eba42dbb83b88980a Mon Sep 17 00:00:00 2001 From: Manfred Moser Date: Wed, 15 Nov 2023 14:05:08 -0800 Subject: [PATCH 541/587] Add Trino 434 release notes --- docs/src/main/sphinx/release.md | 1 + docs/src/main/sphinx/release/release-434.md | 113 ++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 docs/src/main/sphinx/release/release-434.md diff --git a/docs/src/main/sphinx/release.md b/docs/src/main/sphinx/release.md index 6c70108306bc..056cfe9d7f9b 100644 --- a/docs/src/main/sphinx/release.md +++ b/docs/src/main/sphinx/release.md @@ -7,6 +7,7 @@ ```{toctree} :maxdepth: 1 +release/release-434 release/release-433 release/release-432 release/release-431 diff --git a/docs/src/main/sphinx/release/release-434.md b/docs/src/main/sphinx/release/release-434.md new file mode 100644 index 000000000000..2c62dcd348ee --- /dev/null +++ b/docs/src/main/sphinx/release/release-434.md @@ -0,0 +1,113 @@ +# Release 434 (29 Nov 2023) + +## General + +* Add support for a `FILTER` clause to the `LISTAGG` function. ({issue}`19869`) +* {{breaking}} Rename the `query.max-writer-tasks-count` configuration property + and the related `max_writer_tasks_count` session property to + `query.max-writer-task-count` and `max_writer_task_count`. ({issue}`19793`) +* Improve performance of `INSERT ... SELECT` queries that contain a redundant + `ORDER BY` clause. ({issue}`19916`) +* Fix incorrect results for queries involving comparisons between `double` and + `real` zero and negative zero. ({issue}`19828`) +* Fix performance regression caused by suboptimal scalar subqueries planning. ({issue}`19922`) +* Fix failure when queries on data stored on HDFS involve table functions. ({issue}`19849`) +* Prevent sudden increases in memory consumption in some queries with + joins involving `UNNEST`. ({issue}`19762`) + +## BigQuery connector + +* Add support for reading `json` columns. ({issue}`19790`) +* Add support for `DELETE` statement. ({issue}`6870`) +* Improve performance when writing rows. ({issue}`18897`) + +## ClickHouse connector + +* Add support for separate metadata caching configuration for schemas, tables, + and metadata. ({issue}`19859`) + +## Delta Lake connector + +* {{breaking}} Disallow invalid configuration options. Previously, they were + silently ignored. ({issue}`19735`) +* Improve performance when reading large checkpoint files on partitioned tables. + ({issue}`19588`, {issue}`19848`) +* Push down filters involving columns of type `timestamp(p) with time zone`. ({issue}`18664`) +* Fix query failure when reading Parquet column index for timestamp columns. ({issue}`16801`) + +## Druid connector + +* Add support for separate metadata caching configuration for schemas, tables, + and metadata. ({issue}`19859`) + +## Hive connector + +* Add support for columns that changed from `timestamp` to `date` type. ({issue}`19513`) +* Fix query failure when reading Parquet column index for timestamp columns. ({issue}`16801`) + +## Hudi connector + +* Fix query failure when reading Parquet column index for timestamp columns. ({issue}`16801`) + +## Iceberg connector + +* {{breaking}} Remove support for legacy table statistics tracking. ({issue}`19803`) +* {{breaking}} Disallow invalid configuration options. Previously, they were + silently ignored. ({issue}`19735`) +* Fix query failure when reading Parquet column index for timestamp columns. ({issue}`16801`) +* Don't set owner for Glue materialized views when system security is enabled. ({issue}`19681`) + +## Ignite connector + +* Add support for separate metadata caching configuration for schemas, tables, + and metadata. ({issue}`19859`) + +## MariaDB connector + +* Add support for separate metadata caching configuration for schemas, tables, + and metadata. ({issue}`19859`) + +## MySQl connector + +* Add support for separate metadata caching configuration for schemas, tables, + and metadata. ({issue}`19859`) + +## Oracle connector + +* Add support for separate metadata caching configuration for schemas, tables, + and metadata. ({issue}`19859`) + +## Phoenix connector + +* Add support for separate metadata caching configuration for schemas, tables, + and metadata. ({issue}`19859`) + +## PostgreSQL connector + +* Add support for separate metadata caching configuration for schemas, tables, + and metadata. ({issue}`19859`) +* Prevent possible query failures when join is pushed down. ({issue}`18984`) + +## Redshift connector + +* Add support for separate metadata caching configuration for schemas, tables, + and metadata. ({issue}`19859`) +* Prevent possible query failures when join is pushed down. ({issue}`18984`) + +## SingleStore connector + +* Add support for separate metadata caching configuration for schemas, tables, + and metadata. ({issue}`19859`) + +## SQL Server connector + +* Add support for separate metadata caching configuration for schemas, tables, + and metadata. ({issue}`19859`) +* Prevent possible query failures when join is pushed down. ({issue}`18984`) + +## SPI + +* Add bulk append methods to `BlockBuilder`. ({issue}`19577`) +* {{breaking}} Remove the `VariableWidthBlockBuilder.buildEntry` method. ({issue}`19577`) +* {{breaking}} Add required `ConnectorSession` parameter to the method + `TableFunctionProcessorProvider.getDataProcessor`. ({issue}`19778`) From aa431c77a6a187920f5d6433532a19647d901742 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Thu, 30 Nov 2023 00:26:20 +0000 Subject: [PATCH 542/587] [maven-release-plugin] prepare release 434 --- client/trino-cli/pom.xml | 2 +- client/trino-client/pom.xml | 2 +- client/trino-jdbc/pom.xml | 2 +- core/trino-grammar/pom.xml | 2 +- core/trino-main/pom.xml | 2 +- core/trino-parser/pom.xml | 2 +- core/trino-server-main/pom.xml | 2 +- core/trino-server-rpm/pom.xml | 2 +- core/trino-server/pom.xml | 2 +- core/trino-spi/pom.xml | 2 +- docs/pom.xml | 2 +- lib/trino-array/pom.xml | 2 +- lib/trino-cache/pom.xml | 2 +- lib/trino-filesystem-azure/pom.xml | 2 +- lib/trino-filesystem-gcs/pom.xml | 2 +- lib/trino-filesystem-manager/pom.xml | 2 +- lib/trino-filesystem-s3/pom.xml | 2 +- lib/trino-filesystem/pom.xml | 2 +- lib/trino-geospatial-toolkit/pom.xml | 2 +- lib/trino-hdfs/pom.xml | 2 +- lib/trino-hive-formats/pom.xml | 2 +- lib/trino-ignite-patched/pom.xml | 2 +- lib/trino-matching/pom.xml | 2 +- lib/trino-memory-context/pom.xml | 2 +- lib/trino-orc/pom.xml | 2 +- lib/trino-parquet/pom.xml | 2 +- lib/trino-phoenix5-patched/pom.xml | 2 +- lib/trino-plugin-toolkit/pom.xml | 2 +- lib/trino-record-decoder/pom.xml | 2 +- plugin/trino-accumulo-iterators/pom.xml | 2 +- plugin/trino-accumulo/pom.xml | 2 +- plugin/trino-atop/pom.xml | 2 +- plugin/trino-base-jdbc/pom.xml | 2 +- plugin/trino-bigquery/pom.xml | 2 +- plugin/trino-blackhole/pom.xml | 2 +- plugin/trino-cassandra/pom.xml | 2 +- plugin/trino-clickhouse/pom.xml | 2 +- plugin/trino-delta-lake/pom.xml | 2 +- plugin/trino-druid/pom.xml | 2 +- plugin/trino-elasticsearch/pom.xml | 2 +- plugin/trino-example-http/pom.xml | 2 +- plugin/trino-example-jdbc/pom.xml | 2 +- plugin/trino-exchange-filesystem/pom.xml | 2 +- plugin/trino-exchange-hdfs/pom.xml | 2 +- plugin/trino-geospatial/pom.xml | 2 +- plugin/trino-google-sheets/pom.xml | 2 +- plugin/trino-hive-hadoop2/pom.xml | 2 +- plugin/trino-hive/pom.xml | 2 +- plugin/trino-http-event-listener/pom.xml | 2 +- plugin/trino-hudi/pom.xml | 2 +- plugin/trino-iceberg/pom.xml | 2 +- plugin/trino-ignite/pom.xml | 2 +- plugin/trino-jmx/pom.xml | 2 +- plugin/trino-kafka/pom.xml | 2 +- plugin/trino-kinesis/pom.xml | 2 +- plugin/trino-kudu/pom.xml | 2 +- plugin/trino-local-file/pom.xml | 2 +- plugin/trino-mariadb/pom.xml | 2 +- plugin/trino-memory/pom.xml | 2 +- plugin/trino-ml/pom.xml | 2 +- plugin/trino-mongodb/pom.xml | 2 +- plugin/trino-mysql-event-listener/pom.xml | 2 +- plugin/trino-mysql/pom.xml | 2 +- plugin/trino-oracle/pom.xml | 2 +- plugin/trino-password-authenticators/pom.xml | 2 +- plugin/trino-phoenix5/pom.xml | 2 +- plugin/trino-pinot/pom.xml | 2 +- plugin/trino-postgresql/pom.xml | 2 +- plugin/trino-prometheus/pom.xml | 2 +- plugin/trino-raptor-legacy/pom.xml | 2 +- plugin/trino-redis/pom.xml | 2 +- plugin/trino-redshift/pom.xml | 2 +- plugin/trino-resource-group-managers/pom.xml | 2 +- plugin/trino-session-property-managers/pom.xml | 2 +- plugin/trino-singlestore/pom.xml | 2 +- plugin/trino-sqlserver/pom.xml | 2 +- plugin/trino-teradata-functions/pom.xml | 2 +- plugin/trino-thrift-api/pom.xml | 2 +- plugin/trino-thrift-testing-server/pom.xml | 2 +- plugin/trino-thrift/pom.xml | 2 +- plugin/trino-tpcds/pom.xml | 2 +- plugin/trino-tpch/pom.xml | 2 +- pom.xml | 4 ++-- service/trino-proxy/pom.xml | 2 +- service/trino-verifier/pom.xml | 2 +- testing/trino-benchmark-queries/pom.xml | 2 +- testing/trino-benchmark/pom.xml | 2 +- testing/trino-benchto-benchmarks/pom.xml | 2 +- testing/trino-faulttolerant-tests/pom.xml | 2 +- testing/trino-plugin-reader/pom.xml | 2 +- testing/trino-product-tests-launcher/pom.xml | 2 +- testing/trino-product-tests/pom.xml | 2 +- testing/trino-server-dev/pom.xml | 2 +- testing/trino-test-jdbc-compatibility-old-driver/pom.xml | 4 ++-- testing/trino-test-jdbc-compatibility-old-server/pom.xml | 2 +- testing/trino-testing-containers/pom.xml | 2 +- testing/trino-testing-kafka/pom.xml | 2 +- testing/trino-testing-resources/pom.xml | 2 +- testing/trino-testing-services/pom.xml | 2 +- testing/trino-testing/pom.xml | 2 +- testing/trino-tests/pom.xml | 2 +- 101 files changed, 103 insertions(+), 103 deletions(-) diff --git a/client/trino-cli/pom.xml b/client/trino-cli/pom.xml index 7d9505ff0ed2..ed1fffbee3d4 100644 --- a/client/trino-cli/pom.xml +++ b/client/trino-cli/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/client/trino-client/pom.xml b/client/trino-client/pom.xml index 24f8af336230..6a38000106e8 100644 --- a/client/trino-client/pom.xml +++ b/client/trino-client/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/client/trino-jdbc/pom.xml b/client/trino-jdbc/pom.xml index 1cfe51f6f30d..3b761043e3bc 100644 --- a/client/trino-jdbc/pom.xml +++ b/client/trino-jdbc/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/core/trino-grammar/pom.xml b/core/trino-grammar/pom.xml index 947776a7c73f..d11cb2b00322 100644 --- a/core/trino-grammar/pom.xml +++ b/core/trino-grammar/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/core/trino-main/pom.xml b/core/trino-main/pom.xml index f23fc75b47b6..9d930fc36b64 100644 --- a/core/trino-main/pom.xml +++ b/core/trino-main/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/core/trino-parser/pom.xml b/core/trino-parser/pom.xml index e49585ea2a30..75b71bc7f6b6 100644 --- a/core/trino-parser/pom.xml +++ b/core/trino-parser/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/core/trino-server-main/pom.xml b/core/trino-server-main/pom.xml index a479f818e695..d997330a73bf 100644 --- a/core/trino-server-main/pom.xml +++ b/core/trino-server-main/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/core/trino-server-rpm/pom.xml b/core/trino-server-rpm/pom.xml index a30446f6421c..8be1570b668d 100644 --- a/core/trino-server-rpm/pom.xml +++ b/core/trino-server-rpm/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/core/trino-server/pom.xml b/core/trino-server/pom.xml index 8151dce764d6..1b1eec987f7c 100644 --- a/core/trino-server/pom.xml +++ b/core/trino-server/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/core/trino-spi/pom.xml b/core/trino-spi/pom.xml index eb497d8c8ffc..5c080c0013ea 100644 --- a/core/trino-spi/pom.xml +++ b/core/trino-spi/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/docs/pom.xml b/docs/pom.xml index 2e6bfa8b5f52..7eacf5ca3c46 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 trino-docs diff --git a/lib/trino-array/pom.xml b/lib/trino-array/pom.xml index 46a55ab0baef..f8b047dd793c 100644 --- a/lib/trino-array/pom.xml +++ b/lib/trino-array/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/lib/trino-cache/pom.xml b/lib/trino-cache/pom.xml index 85e21dda64ff..a88b3e206294 100644 --- a/lib/trino-cache/pom.xml +++ b/lib/trino-cache/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/lib/trino-filesystem-azure/pom.xml b/lib/trino-filesystem-azure/pom.xml index e54358a42d3f..534419b8006d 100644 --- a/lib/trino-filesystem-azure/pom.xml +++ b/lib/trino-filesystem-azure/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/lib/trino-filesystem-gcs/pom.xml b/lib/trino-filesystem-gcs/pom.xml index 1afd968fff64..5324c64e4a19 100644 --- a/lib/trino-filesystem-gcs/pom.xml +++ b/lib/trino-filesystem-gcs/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/lib/trino-filesystem-manager/pom.xml b/lib/trino-filesystem-manager/pom.xml index 70bbc4ac4356..9afef58be77e 100644 --- a/lib/trino-filesystem-manager/pom.xml +++ b/lib/trino-filesystem-manager/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/lib/trino-filesystem-s3/pom.xml b/lib/trino-filesystem-s3/pom.xml index 2157886d776b..39410d964aaf 100644 --- a/lib/trino-filesystem-s3/pom.xml +++ b/lib/trino-filesystem-s3/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/lib/trino-filesystem/pom.xml b/lib/trino-filesystem/pom.xml index b0e98999fb83..7ae1c9c1eb3a 100644 --- a/lib/trino-filesystem/pom.xml +++ b/lib/trino-filesystem/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/lib/trino-geospatial-toolkit/pom.xml b/lib/trino-geospatial-toolkit/pom.xml index 51a494da461b..7b0f9fdacb34 100644 --- a/lib/trino-geospatial-toolkit/pom.xml +++ b/lib/trino-geospatial-toolkit/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/lib/trino-hdfs/pom.xml b/lib/trino-hdfs/pom.xml index 220906b25c2e..66acd344496c 100644 --- a/lib/trino-hdfs/pom.xml +++ b/lib/trino-hdfs/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/lib/trino-hive-formats/pom.xml b/lib/trino-hive-formats/pom.xml index d915a8e9d527..7b16843d430b 100644 --- a/lib/trino-hive-formats/pom.xml +++ b/lib/trino-hive-formats/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/lib/trino-ignite-patched/pom.xml b/lib/trino-ignite-patched/pom.xml index 963b281d63da..688599f9eebc 100644 --- a/lib/trino-ignite-patched/pom.xml +++ b/lib/trino-ignite-patched/pom.xml @@ -6,7 +6,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/lib/trino-matching/pom.xml b/lib/trino-matching/pom.xml index d6c4649d9135..fa330276101b 100644 --- a/lib/trino-matching/pom.xml +++ b/lib/trino-matching/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/lib/trino-memory-context/pom.xml b/lib/trino-memory-context/pom.xml index 699c27c1dbfe..c27a632480a1 100644 --- a/lib/trino-memory-context/pom.xml +++ b/lib/trino-memory-context/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/lib/trino-orc/pom.xml b/lib/trino-orc/pom.xml index 299c25ca57d0..444b0f786b85 100644 --- a/lib/trino-orc/pom.xml +++ b/lib/trino-orc/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/lib/trino-parquet/pom.xml b/lib/trino-parquet/pom.xml index 389b9f7d3131..db26692233c1 100644 --- a/lib/trino-parquet/pom.xml +++ b/lib/trino-parquet/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/lib/trino-phoenix5-patched/pom.xml b/lib/trino-phoenix5-patched/pom.xml index cdbce121d01a..6512d9469f7f 100644 --- a/lib/trino-phoenix5-patched/pom.xml +++ b/lib/trino-phoenix5-patched/pom.xml @@ -6,7 +6,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/lib/trino-plugin-toolkit/pom.xml b/lib/trino-plugin-toolkit/pom.xml index cbdc83dd8351..d17ccb08ca34 100644 --- a/lib/trino-plugin-toolkit/pom.xml +++ b/lib/trino-plugin-toolkit/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/lib/trino-record-decoder/pom.xml b/lib/trino-record-decoder/pom.xml index 96d06eb4caa6..ca51cac8112d 100644 --- a/lib/trino-record-decoder/pom.xml +++ b/lib/trino-record-decoder/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-accumulo-iterators/pom.xml b/plugin/trino-accumulo-iterators/pom.xml index 036f65981a72..c1a896003661 100644 --- a/plugin/trino-accumulo-iterators/pom.xml +++ b/plugin/trino-accumulo-iterators/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-accumulo/pom.xml b/plugin/trino-accumulo/pom.xml index 8d4b56b7952c..ff004c998acb 100644 --- a/plugin/trino-accumulo/pom.xml +++ b/plugin/trino-accumulo/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-atop/pom.xml b/plugin/trino-atop/pom.xml index c84aeefa8586..17ddcda162be 100644 --- a/plugin/trino-atop/pom.xml +++ b/plugin/trino-atop/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-base-jdbc/pom.xml b/plugin/trino-base-jdbc/pom.xml index 6210dac719e1..936a7ae4629d 100644 --- a/plugin/trino-base-jdbc/pom.xml +++ b/plugin/trino-base-jdbc/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-bigquery/pom.xml b/plugin/trino-bigquery/pom.xml index 428667093582..6315bae0f3f7 100644 --- a/plugin/trino-bigquery/pom.xml +++ b/plugin/trino-bigquery/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-blackhole/pom.xml b/plugin/trino-blackhole/pom.xml index 921a84fdc143..ed4d4fb31aa8 100644 --- a/plugin/trino-blackhole/pom.xml +++ b/plugin/trino-blackhole/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-cassandra/pom.xml b/plugin/trino-cassandra/pom.xml index 52bac96f2809..c4df9b0a5d45 100644 --- a/plugin/trino-cassandra/pom.xml +++ b/plugin/trino-cassandra/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-clickhouse/pom.xml b/plugin/trino-clickhouse/pom.xml index d2b332a954fc..e1c04ab97f97 100644 --- a/plugin/trino-clickhouse/pom.xml +++ b/plugin/trino-clickhouse/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-delta-lake/pom.xml b/plugin/trino-delta-lake/pom.xml index 3e6d398acbdb..da9a6e4192ba 100644 --- a/plugin/trino-delta-lake/pom.xml +++ b/plugin/trino-delta-lake/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-druid/pom.xml b/plugin/trino-druid/pom.xml index b9256f5ae8d1..d83f913ebffd 100644 --- a/plugin/trino-druid/pom.xml +++ b/plugin/trino-druid/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-elasticsearch/pom.xml b/plugin/trino-elasticsearch/pom.xml index 667fd0e799af..9f2b6cfb63b2 100644 --- a/plugin/trino-elasticsearch/pom.xml +++ b/plugin/trino-elasticsearch/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-example-http/pom.xml b/plugin/trino-example-http/pom.xml index 3224a64921e9..a0edd990314f 100644 --- a/plugin/trino-example-http/pom.xml +++ b/plugin/trino-example-http/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-example-jdbc/pom.xml b/plugin/trino-example-jdbc/pom.xml index b9c25edd1dc9..c6983ae9e665 100644 --- a/plugin/trino-example-jdbc/pom.xml +++ b/plugin/trino-example-jdbc/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-exchange-filesystem/pom.xml b/plugin/trino-exchange-filesystem/pom.xml index 7c41064d5e05..4dc568d8e671 100644 --- a/plugin/trino-exchange-filesystem/pom.xml +++ b/plugin/trino-exchange-filesystem/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-exchange-hdfs/pom.xml b/plugin/trino-exchange-hdfs/pom.xml index f1dfb810ed67..4632e70ee337 100644 --- a/plugin/trino-exchange-hdfs/pom.xml +++ b/plugin/trino-exchange-hdfs/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-geospatial/pom.xml b/plugin/trino-geospatial/pom.xml index 68b09de8ed92..3067e4018215 100644 --- a/plugin/trino-geospatial/pom.xml +++ b/plugin/trino-geospatial/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-google-sheets/pom.xml b/plugin/trino-google-sheets/pom.xml index 70c9d22461d1..53575994e080 100644 --- a/plugin/trino-google-sheets/pom.xml +++ b/plugin/trino-google-sheets/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-hive-hadoop2/pom.xml b/plugin/trino-hive-hadoop2/pom.xml index 1069cda0c04a..bad23bca8bed 100644 --- a/plugin/trino-hive-hadoop2/pom.xml +++ b/plugin/trino-hive-hadoop2/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-hive/pom.xml b/plugin/trino-hive/pom.xml index 5131125ab190..c87993f109e7 100644 --- a/plugin/trino-hive/pom.xml +++ b/plugin/trino-hive/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-http-event-listener/pom.xml b/plugin/trino-http-event-listener/pom.xml index f7b33e5a2f2e..eb314900c4d2 100644 --- a/plugin/trino-http-event-listener/pom.xml +++ b/plugin/trino-http-event-listener/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-hudi/pom.xml b/plugin/trino-hudi/pom.xml index 377ddb7edd5b..3e2db17a32dc 100644 --- a/plugin/trino-hudi/pom.xml +++ b/plugin/trino-hudi/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-iceberg/pom.xml b/plugin/trino-iceberg/pom.xml index 95efd5f19a9f..015e626bd25e 100644 --- a/plugin/trino-iceberg/pom.xml +++ b/plugin/trino-iceberg/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-ignite/pom.xml b/plugin/trino-ignite/pom.xml index e38de38ee251..51e9d5a5675c 100644 --- a/plugin/trino-ignite/pom.xml +++ b/plugin/trino-ignite/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-jmx/pom.xml b/plugin/trino-jmx/pom.xml index 4216f7e105e2..97480b802444 100644 --- a/plugin/trino-jmx/pom.xml +++ b/plugin/trino-jmx/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-kafka/pom.xml b/plugin/trino-kafka/pom.xml index 02496a2e1be3..db39ce9b89a1 100644 --- a/plugin/trino-kafka/pom.xml +++ b/plugin/trino-kafka/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-kinesis/pom.xml b/plugin/trino-kinesis/pom.xml index df6234712f55..c270909ddfc1 100644 --- a/plugin/trino-kinesis/pom.xml +++ b/plugin/trino-kinesis/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-kudu/pom.xml b/plugin/trino-kudu/pom.xml index b2f0c17510ed..708da51c8a87 100644 --- a/plugin/trino-kudu/pom.xml +++ b/plugin/trino-kudu/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-local-file/pom.xml b/plugin/trino-local-file/pom.xml index 81fc487e685b..a7b059b5a8dc 100644 --- a/plugin/trino-local-file/pom.xml +++ b/plugin/trino-local-file/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-mariadb/pom.xml b/plugin/trino-mariadb/pom.xml index 8f8b8ce15e84..a05fe4804ffb 100644 --- a/plugin/trino-mariadb/pom.xml +++ b/plugin/trino-mariadb/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-memory/pom.xml b/plugin/trino-memory/pom.xml index 9900c9b1e51c..c73e4ea60c11 100644 --- a/plugin/trino-memory/pom.xml +++ b/plugin/trino-memory/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-ml/pom.xml b/plugin/trino-ml/pom.xml index fc3ddee19a0e..d5ef9b85b2a0 100644 --- a/plugin/trino-ml/pom.xml +++ b/plugin/trino-ml/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-mongodb/pom.xml b/plugin/trino-mongodb/pom.xml index 3a2abec52f53..c049c620ddfb 100644 --- a/plugin/trino-mongodb/pom.xml +++ b/plugin/trino-mongodb/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-mysql-event-listener/pom.xml b/plugin/trino-mysql-event-listener/pom.xml index 379f74264580..bf2831665968 100644 --- a/plugin/trino-mysql-event-listener/pom.xml +++ b/plugin/trino-mysql-event-listener/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-mysql/pom.xml b/plugin/trino-mysql/pom.xml index e446e34f5fec..8919c5662116 100644 --- a/plugin/trino-mysql/pom.xml +++ b/plugin/trino-mysql/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-oracle/pom.xml b/plugin/trino-oracle/pom.xml index c9950bfc9545..be95fe4d43f8 100644 --- a/plugin/trino-oracle/pom.xml +++ b/plugin/trino-oracle/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-password-authenticators/pom.xml b/plugin/trino-password-authenticators/pom.xml index 5917227dc985..96cc5a2760f7 100644 --- a/plugin/trino-password-authenticators/pom.xml +++ b/plugin/trino-password-authenticators/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-phoenix5/pom.xml b/plugin/trino-phoenix5/pom.xml index c2af43e4b25d..4e50c8acfa3e 100644 --- a/plugin/trino-phoenix5/pom.xml +++ b/plugin/trino-phoenix5/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-pinot/pom.xml b/plugin/trino-pinot/pom.xml index ec26b1e8977c..a3c92a2e6170 100755 --- a/plugin/trino-pinot/pom.xml +++ b/plugin/trino-pinot/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-postgresql/pom.xml b/plugin/trino-postgresql/pom.xml index ab6716e0d5ac..f25392a6ddb1 100644 --- a/plugin/trino-postgresql/pom.xml +++ b/plugin/trino-postgresql/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-prometheus/pom.xml b/plugin/trino-prometheus/pom.xml index 2ba32444f669..8b25242acee1 100644 --- a/plugin/trino-prometheus/pom.xml +++ b/plugin/trino-prometheus/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-raptor-legacy/pom.xml b/plugin/trino-raptor-legacy/pom.xml index 6771b9407ed4..c4bf5c7ccedd 100644 --- a/plugin/trino-raptor-legacy/pom.xml +++ b/plugin/trino-raptor-legacy/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-redis/pom.xml b/plugin/trino-redis/pom.xml index 7b79892ff96c..f52c07f654c4 100644 --- a/plugin/trino-redis/pom.xml +++ b/plugin/trino-redis/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-redshift/pom.xml b/plugin/trino-redshift/pom.xml index eeb7ee0b20b8..e2948e88e581 100644 --- a/plugin/trino-redshift/pom.xml +++ b/plugin/trino-redshift/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-resource-group-managers/pom.xml b/plugin/trino-resource-group-managers/pom.xml index 87a56ac2f1ce..c00ef6e5126d 100644 --- a/plugin/trino-resource-group-managers/pom.xml +++ b/plugin/trino-resource-group-managers/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-session-property-managers/pom.xml b/plugin/trino-session-property-managers/pom.xml index 1deddf09d9b1..0fd8079be13b 100644 --- a/plugin/trino-session-property-managers/pom.xml +++ b/plugin/trino-session-property-managers/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-singlestore/pom.xml b/plugin/trino-singlestore/pom.xml index 0ef29ecc78b4..f9a04c282e30 100644 --- a/plugin/trino-singlestore/pom.xml +++ b/plugin/trino-singlestore/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-sqlserver/pom.xml b/plugin/trino-sqlserver/pom.xml index 0787eceaaa62..45b23a2f87e7 100644 --- a/plugin/trino-sqlserver/pom.xml +++ b/plugin/trino-sqlserver/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-teradata-functions/pom.xml b/plugin/trino-teradata-functions/pom.xml index 9b2f36af51bd..f9d4d2aa5890 100644 --- a/plugin/trino-teradata-functions/pom.xml +++ b/plugin/trino-teradata-functions/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-thrift-api/pom.xml b/plugin/trino-thrift-api/pom.xml index 137a65517a6b..b2f275c9b6c7 100644 --- a/plugin/trino-thrift-api/pom.xml +++ b/plugin/trino-thrift-api/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-thrift-testing-server/pom.xml b/plugin/trino-thrift-testing-server/pom.xml index 0fd32725cf0b..eb79a6e47688 100644 --- a/plugin/trino-thrift-testing-server/pom.xml +++ b/plugin/trino-thrift-testing-server/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-thrift/pom.xml b/plugin/trino-thrift/pom.xml index c1aefe764262..7c7367a81aaa 100644 --- a/plugin/trino-thrift/pom.xml +++ b/plugin/trino-thrift/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-tpcds/pom.xml b/plugin/trino-tpcds/pom.xml index 48ef05bdc128..66d54d925ff5 100644 --- a/plugin/trino-tpcds/pom.xml +++ b/plugin/trino-tpcds/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/plugin/trino-tpch/pom.xml b/plugin/trino-tpch/pom.xml index cd38bf7d17f4..73731a57b595 100644 --- a/plugin/trino-tpch/pom.xml +++ b/plugin/trino-tpch/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/pom.xml b/pom.xml index f77a9541ab3f..6bc990e43d77 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 pom ${project.artifactId} @@ -134,7 +134,7 @@ scm:git:git://github.com/trinodb/trino.git - HEAD + 434 https://github.com/trinodb/trino diff --git a/service/trino-proxy/pom.xml b/service/trino-proxy/pom.xml index 0eb97deb6968..651398af3fbb 100644 --- a/service/trino-proxy/pom.xml +++ b/service/trino-proxy/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/service/trino-verifier/pom.xml b/service/trino-verifier/pom.xml index a0b7df542328..0ad636c5049f 100644 --- a/service/trino-verifier/pom.xml +++ b/service/trino-verifier/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/testing/trino-benchmark-queries/pom.xml b/testing/trino-benchmark-queries/pom.xml index 00bf340cac69..3c14736c75dd 100644 --- a/testing/trino-benchmark-queries/pom.xml +++ b/testing/trino-benchmark-queries/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/testing/trino-benchmark/pom.xml b/testing/trino-benchmark/pom.xml index 2e715baae1e6..b7c9560ce0d5 100644 --- a/testing/trino-benchmark/pom.xml +++ b/testing/trino-benchmark/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/testing/trino-benchto-benchmarks/pom.xml b/testing/trino-benchto-benchmarks/pom.xml index 1142f3cc06dd..3190d112a130 100644 --- a/testing/trino-benchto-benchmarks/pom.xml +++ b/testing/trino-benchto-benchmarks/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/testing/trino-faulttolerant-tests/pom.xml b/testing/trino-faulttolerant-tests/pom.xml index ee315e8bd08f..053130f1be0a 100644 --- a/testing/trino-faulttolerant-tests/pom.xml +++ b/testing/trino-faulttolerant-tests/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/testing/trino-plugin-reader/pom.xml b/testing/trino-plugin-reader/pom.xml index 6970b8f4d9ea..2eee485ad8e7 100644 --- a/testing/trino-plugin-reader/pom.xml +++ b/testing/trino-plugin-reader/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/testing/trino-product-tests-launcher/pom.xml b/testing/trino-product-tests-launcher/pom.xml index bb633ab5c769..6e42894053c5 100644 --- a/testing/trino-product-tests-launcher/pom.xml +++ b/testing/trino-product-tests-launcher/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/testing/trino-product-tests/pom.xml b/testing/trino-product-tests/pom.xml index 3e499b85abff..ce894982fd4e 100644 --- a/testing/trino-product-tests/pom.xml +++ b/testing/trino-product-tests/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/testing/trino-server-dev/pom.xml b/testing/trino-server-dev/pom.xml index 3e8636a9417b..cae601d756d7 100644 --- a/testing/trino-server-dev/pom.xml +++ b/testing/trino-server-dev/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/testing/trino-test-jdbc-compatibility-old-driver/pom.xml b/testing/trino-test-jdbc-compatibility-old-driver/pom.xml index c740576d92a5..01122c9dba6b 100644 --- a/testing/trino-test-jdbc-compatibility-old-driver/pom.xml +++ b/testing/trino-test-jdbc-compatibility-old-driver/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml @@ -15,7 +15,7 @@ ${project.parent.basedir} - 434-SNAPSHOT + 434 diff --git a/testing/trino-test-jdbc-compatibility-old-server/pom.xml b/testing/trino-test-jdbc-compatibility-old-server/pom.xml index 9cd6a17b2dd0..5fd1ae77263c 100644 --- a/testing/trino-test-jdbc-compatibility-old-server/pom.xml +++ b/testing/trino-test-jdbc-compatibility-old-server/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/testing/trino-testing-containers/pom.xml b/testing/trino-testing-containers/pom.xml index fb18ab47fcc1..d7b5545221e4 100644 --- a/testing/trino-testing-containers/pom.xml +++ b/testing/trino-testing-containers/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/testing/trino-testing-kafka/pom.xml b/testing/trino-testing-kafka/pom.xml index 5c240f4d9723..f7cd1562dff6 100644 --- a/testing/trino-testing-kafka/pom.xml +++ b/testing/trino-testing-kafka/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/testing/trino-testing-resources/pom.xml b/testing/trino-testing-resources/pom.xml index 5b47b511eac1..3487cab3e763 100644 --- a/testing/trino-testing-resources/pom.xml +++ b/testing/trino-testing-resources/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/testing/trino-testing-services/pom.xml b/testing/trino-testing-services/pom.xml index d5f6ac13c4ec..d0fc137d5a08 100644 --- a/testing/trino-testing-services/pom.xml +++ b/testing/trino-testing-services/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/testing/trino-testing/pom.xml b/testing/trino-testing/pom.xml index 2d696e2b6f14..3dc291fed0b8 100644 --- a/testing/trino-testing/pom.xml +++ b/testing/trino-testing/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml diff --git a/testing/trino-tests/pom.xml b/testing/trino-tests/pom.xml index c6efd0dbaa9e..7b8d4df57c66 100644 --- a/testing/trino-tests/pom.xml +++ b/testing/trino-tests/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434-SNAPSHOT + 434 ../../pom.xml From 24ffbaa11fbc30439e5ae345662376ed871da2e4 Mon Sep 17 00:00:00 2001 From: Martin Traverso Date: Thu, 30 Nov 2023 00:26:21 +0000 Subject: [PATCH 543/587] [maven-release-plugin] prepare for next development iteration --- client/trino-cli/pom.xml | 2 +- client/trino-client/pom.xml | 2 +- client/trino-jdbc/pom.xml | 2 +- core/trino-grammar/pom.xml | 2 +- core/trino-main/pom.xml | 2 +- core/trino-parser/pom.xml | 2 +- core/trino-server-main/pom.xml | 2 +- core/trino-server-rpm/pom.xml | 2 +- core/trino-server/pom.xml | 2 +- core/trino-spi/pom.xml | 2 +- docs/pom.xml | 2 +- lib/trino-array/pom.xml | 2 +- lib/trino-cache/pom.xml | 2 +- lib/trino-filesystem-azure/pom.xml | 2 +- lib/trino-filesystem-gcs/pom.xml | 2 +- lib/trino-filesystem-manager/pom.xml | 2 +- lib/trino-filesystem-s3/pom.xml | 2 +- lib/trino-filesystem/pom.xml | 2 +- lib/trino-geospatial-toolkit/pom.xml | 2 +- lib/trino-hdfs/pom.xml | 2 +- lib/trino-hive-formats/pom.xml | 2 +- lib/trino-ignite-patched/pom.xml | 2 +- lib/trino-matching/pom.xml | 2 +- lib/trino-memory-context/pom.xml | 2 +- lib/trino-orc/pom.xml | 2 +- lib/trino-parquet/pom.xml | 2 +- lib/trino-phoenix5-patched/pom.xml | 2 +- lib/trino-plugin-toolkit/pom.xml | 2 +- lib/trino-record-decoder/pom.xml | 2 +- plugin/trino-accumulo-iterators/pom.xml | 2 +- plugin/trino-accumulo/pom.xml | 2 +- plugin/trino-atop/pom.xml | 2 +- plugin/trino-base-jdbc/pom.xml | 2 +- plugin/trino-bigquery/pom.xml | 2 +- plugin/trino-blackhole/pom.xml | 2 +- plugin/trino-cassandra/pom.xml | 2 +- plugin/trino-clickhouse/pom.xml | 2 +- plugin/trino-delta-lake/pom.xml | 2 +- plugin/trino-druid/pom.xml | 2 +- plugin/trino-elasticsearch/pom.xml | 2 +- plugin/trino-example-http/pom.xml | 2 +- plugin/trino-example-jdbc/pom.xml | 2 +- plugin/trino-exchange-filesystem/pom.xml | 2 +- plugin/trino-exchange-hdfs/pom.xml | 2 +- plugin/trino-geospatial/pom.xml | 2 +- plugin/trino-google-sheets/pom.xml | 2 +- plugin/trino-hive-hadoop2/pom.xml | 2 +- plugin/trino-hive/pom.xml | 2 +- plugin/trino-http-event-listener/pom.xml | 2 +- plugin/trino-hudi/pom.xml | 2 +- plugin/trino-iceberg/pom.xml | 2 +- plugin/trino-ignite/pom.xml | 2 +- plugin/trino-jmx/pom.xml | 2 +- plugin/trino-kafka/pom.xml | 2 +- plugin/trino-kinesis/pom.xml | 2 +- plugin/trino-kudu/pom.xml | 2 +- plugin/trino-local-file/pom.xml | 2 +- plugin/trino-mariadb/pom.xml | 2 +- plugin/trino-memory/pom.xml | 2 +- plugin/trino-ml/pom.xml | 2 +- plugin/trino-mongodb/pom.xml | 2 +- plugin/trino-mysql-event-listener/pom.xml | 2 +- plugin/trino-mysql/pom.xml | 2 +- plugin/trino-oracle/pom.xml | 2 +- plugin/trino-password-authenticators/pom.xml | 2 +- plugin/trino-phoenix5/pom.xml | 2 +- plugin/trino-pinot/pom.xml | 2 +- plugin/trino-postgresql/pom.xml | 2 +- plugin/trino-prometheus/pom.xml | 2 +- plugin/trino-raptor-legacy/pom.xml | 2 +- plugin/trino-redis/pom.xml | 2 +- plugin/trino-redshift/pom.xml | 2 +- plugin/trino-resource-group-managers/pom.xml | 2 +- plugin/trino-session-property-managers/pom.xml | 2 +- plugin/trino-singlestore/pom.xml | 2 +- plugin/trino-sqlserver/pom.xml | 2 +- plugin/trino-teradata-functions/pom.xml | 2 +- plugin/trino-thrift-api/pom.xml | 2 +- plugin/trino-thrift-testing-server/pom.xml | 2 +- plugin/trino-thrift/pom.xml | 2 +- plugin/trino-tpcds/pom.xml | 2 +- plugin/trino-tpch/pom.xml | 2 +- pom.xml | 4 ++-- service/trino-proxy/pom.xml | 2 +- service/trino-verifier/pom.xml | 2 +- testing/trino-benchmark-queries/pom.xml | 2 +- testing/trino-benchmark/pom.xml | 2 +- testing/trino-benchto-benchmarks/pom.xml | 2 +- testing/trino-faulttolerant-tests/pom.xml | 2 +- testing/trino-plugin-reader/pom.xml | 2 +- testing/trino-product-tests-launcher/pom.xml | 2 +- testing/trino-product-tests/pom.xml | 2 +- testing/trino-server-dev/pom.xml | 2 +- testing/trino-test-jdbc-compatibility-old-driver/pom.xml | 4 ++-- testing/trino-test-jdbc-compatibility-old-server/pom.xml | 2 +- testing/trino-testing-containers/pom.xml | 2 +- testing/trino-testing-kafka/pom.xml | 2 +- testing/trino-testing-resources/pom.xml | 2 +- testing/trino-testing-services/pom.xml | 2 +- testing/trino-testing/pom.xml | 2 +- testing/trino-tests/pom.xml | 2 +- 101 files changed, 103 insertions(+), 103 deletions(-) diff --git a/client/trino-cli/pom.xml b/client/trino-cli/pom.xml index ed1fffbee3d4..7720045b6687 100644 --- a/client/trino-cli/pom.xml +++ b/client/trino-cli/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/client/trino-client/pom.xml b/client/trino-client/pom.xml index 6a38000106e8..27587ca34d0a 100644 --- a/client/trino-client/pom.xml +++ b/client/trino-client/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/client/trino-jdbc/pom.xml b/client/trino-jdbc/pom.xml index 3b761043e3bc..9312577b7979 100644 --- a/client/trino-jdbc/pom.xml +++ b/client/trino-jdbc/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/core/trino-grammar/pom.xml b/core/trino-grammar/pom.xml index d11cb2b00322..75cf06246cc9 100644 --- a/core/trino-grammar/pom.xml +++ b/core/trino-grammar/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/core/trino-main/pom.xml b/core/trino-main/pom.xml index 9d930fc36b64..e2d416b17845 100644 --- a/core/trino-main/pom.xml +++ b/core/trino-main/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/core/trino-parser/pom.xml b/core/trino-parser/pom.xml index 75b71bc7f6b6..fa2fe342cbcc 100644 --- a/core/trino-parser/pom.xml +++ b/core/trino-parser/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/core/trino-server-main/pom.xml b/core/trino-server-main/pom.xml index d997330a73bf..247c27159853 100644 --- a/core/trino-server-main/pom.xml +++ b/core/trino-server-main/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/core/trino-server-rpm/pom.xml b/core/trino-server-rpm/pom.xml index 8be1570b668d..29c66bd4f0fd 100644 --- a/core/trino-server-rpm/pom.xml +++ b/core/trino-server-rpm/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/core/trino-server/pom.xml b/core/trino-server/pom.xml index 1b1eec987f7c..b538b06c7047 100644 --- a/core/trino-server/pom.xml +++ b/core/trino-server/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/core/trino-spi/pom.xml b/core/trino-spi/pom.xml index 5c080c0013ea..b4b6e9f9982a 100644 --- a/core/trino-spi/pom.xml +++ b/core/trino-spi/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/docs/pom.xml b/docs/pom.xml index 7eacf5ca3c46..fb14778bf5b9 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT trino-docs diff --git a/lib/trino-array/pom.xml b/lib/trino-array/pom.xml index f8b047dd793c..b3a566e618fb 100644 --- a/lib/trino-array/pom.xml +++ b/lib/trino-array/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/lib/trino-cache/pom.xml b/lib/trino-cache/pom.xml index a88b3e206294..63848a176b00 100644 --- a/lib/trino-cache/pom.xml +++ b/lib/trino-cache/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/lib/trino-filesystem-azure/pom.xml b/lib/trino-filesystem-azure/pom.xml index 534419b8006d..f637bfa33017 100644 --- a/lib/trino-filesystem-azure/pom.xml +++ b/lib/trino-filesystem-azure/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/lib/trino-filesystem-gcs/pom.xml b/lib/trino-filesystem-gcs/pom.xml index 5324c64e4a19..33acd16414f3 100644 --- a/lib/trino-filesystem-gcs/pom.xml +++ b/lib/trino-filesystem-gcs/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/lib/trino-filesystem-manager/pom.xml b/lib/trino-filesystem-manager/pom.xml index 9afef58be77e..902503eab98e 100644 --- a/lib/trino-filesystem-manager/pom.xml +++ b/lib/trino-filesystem-manager/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/lib/trino-filesystem-s3/pom.xml b/lib/trino-filesystem-s3/pom.xml index 39410d964aaf..b8c1165d8d11 100644 --- a/lib/trino-filesystem-s3/pom.xml +++ b/lib/trino-filesystem-s3/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/lib/trino-filesystem/pom.xml b/lib/trino-filesystem/pom.xml index 7ae1c9c1eb3a..7f78e77bcd86 100644 --- a/lib/trino-filesystem/pom.xml +++ b/lib/trino-filesystem/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/lib/trino-geospatial-toolkit/pom.xml b/lib/trino-geospatial-toolkit/pom.xml index 7b0f9fdacb34..181ce54f0bf3 100644 --- a/lib/trino-geospatial-toolkit/pom.xml +++ b/lib/trino-geospatial-toolkit/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/lib/trino-hdfs/pom.xml b/lib/trino-hdfs/pom.xml index 66acd344496c..70fec6c06ea4 100644 --- a/lib/trino-hdfs/pom.xml +++ b/lib/trino-hdfs/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/lib/trino-hive-formats/pom.xml b/lib/trino-hive-formats/pom.xml index 7b16843d430b..a96634b81a87 100644 --- a/lib/trino-hive-formats/pom.xml +++ b/lib/trino-hive-formats/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/lib/trino-ignite-patched/pom.xml b/lib/trino-ignite-patched/pom.xml index 688599f9eebc..3b3c7d32d77e 100644 --- a/lib/trino-ignite-patched/pom.xml +++ b/lib/trino-ignite-patched/pom.xml @@ -6,7 +6,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/lib/trino-matching/pom.xml b/lib/trino-matching/pom.xml index fa330276101b..c53931f7d6a9 100644 --- a/lib/trino-matching/pom.xml +++ b/lib/trino-matching/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/lib/trino-memory-context/pom.xml b/lib/trino-memory-context/pom.xml index c27a632480a1..63f297577970 100644 --- a/lib/trino-memory-context/pom.xml +++ b/lib/trino-memory-context/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/lib/trino-orc/pom.xml b/lib/trino-orc/pom.xml index 444b0f786b85..c18b8d87eda0 100644 --- a/lib/trino-orc/pom.xml +++ b/lib/trino-orc/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/lib/trino-parquet/pom.xml b/lib/trino-parquet/pom.xml index db26692233c1..62ff5f1405b4 100644 --- a/lib/trino-parquet/pom.xml +++ b/lib/trino-parquet/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/lib/trino-phoenix5-patched/pom.xml b/lib/trino-phoenix5-patched/pom.xml index 6512d9469f7f..b5d69023d0ef 100644 --- a/lib/trino-phoenix5-patched/pom.xml +++ b/lib/trino-phoenix5-patched/pom.xml @@ -6,7 +6,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/lib/trino-plugin-toolkit/pom.xml b/lib/trino-plugin-toolkit/pom.xml index d17ccb08ca34..74332aa4cb8e 100644 --- a/lib/trino-plugin-toolkit/pom.xml +++ b/lib/trino-plugin-toolkit/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/lib/trino-record-decoder/pom.xml b/lib/trino-record-decoder/pom.xml index ca51cac8112d..5e7a0548029b 100644 --- a/lib/trino-record-decoder/pom.xml +++ b/lib/trino-record-decoder/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-accumulo-iterators/pom.xml b/plugin/trino-accumulo-iterators/pom.xml index c1a896003661..dbe4589d0ac4 100644 --- a/plugin/trino-accumulo-iterators/pom.xml +++ b/plugin/trino-accumulo-iterators/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-accumulo/pom.xml b/plugin/trino-accumulo/pom.xml index ff004c998acb..5d07c63c1927 100644 --- a/plugin/trino-accumulo/pom.xml +++ b/plugin/trino-accumulo/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-atop/pom.xml b/plugin/trino-atop/pom.xml index 17ddcda162be..032fc348bb4f 100644 --- a/plugin/trino-atop/pom.xml +++ b/plugin/trino-atop/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-base-jdbc/pom.xml b/plugin/trino-base-jdbc/pom.xml index 936a7ae4629d..7d9e8a828e8e 100644 --- a/plugin/trino-base-jdbc/pom.xml +++ b/plugin/trino-base-jdbc/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-bigquery/pom.xml b/plugin/trino-bigquery/pom.xml index 6315bae0f3f7..2e339e1349ce 100644 --- a/plugin/trino-bigquery/pom.xml +++ b/plugin/trino-bigquery/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-blackhole/pom.xml b/plugin/trino-blackhole/pom.xml index ed4d4fb31aa8..d76dc1989adc 100644 --- a/plugin/trino-blackhole/pom.xml +++ b/plugin/trino-blackhole/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-cassandra/pom.xml b/plugin/trino-cassandra/pom.xml index c4df9b0a5d45..16c5f45ada00 100644 --- a/plugin/trino-cassandra/pom.xml +++ b/plugin/trino-cassandra/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-clickhouse/pom.xml b/plugin/trino-clickhouse/pom.xml index e1c04ab97f97..90082d9a937b 100644 --- a/plugin/trino-clickhouse/pom.xml +++ b/plugin/trino-clickhouse/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-delta-lake/pom.xml b/plugin/trino-delta-lake/pom.xml index da9a6e4192ba..fe8c8910cde7 100644 --- a/plugin/trino-delta-lake/pom.xml +++ b/plugin/trino-delta-lake/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-druid/pom.xml b/plugin/trino-druid/pom.xml index d83f913ebffd..cc686502ecb4 100644 --- a/plugin/trino-druid/pom.xml +++ b/plugin/trino-druid/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-elasticsearch/pom.xml b/plugin/trino-elasticsearch/pom.xml index 9f2b6cfb63b2..326020108295 100644 --- a/plugin/trino-elasticsearch/pom.xml +++ b/plugin/trino-elasticsearch/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-example-http/pom.xml b/plugin/trino-example-http/pom.xml index a0edd990314f..fdb209737132 100644 --- a/plugin/trino-example-http/pom.xml +++ b/plugin/trino-example-http/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-example-jdbc/pom.xml b/plugin/trino-example-jdbc/pom.xml index c6983ae9e665..2e84219b1e0e 100644 --- a/plugin/trino-example-jdbc/pom.xml +++ b/plugin/trino-example-jdbc/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-exchange-filesystem/pom.xml b/plugin/trino-exchange-filesystem/pom.xml index 4dc568d8e671..2ebb14c8aeb3 100644 --- a/plugin/trino-exchange-filesystem/pom.xml +++ b/plugin/trino-exchange-filesystem/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-exchange-hdfs/pom.xml b/plugin/trino-exchange-hdfs/pom.xml index 4632e70ee337..a9300c086d2c 100644 --- a/plugin/trino-exchange-hdfs/pom.xml +++ b/plugin/trino-exchange-hdfs/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-geospatial/pom.xml b/plugin/trino-geospatial/pom.xml index 3067e4018215..b1a115e8085a 100644 --- a/plugin/trino-geospatial/pom.xml +++ b/plugin/trino-geospatial/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-google-sheets/pom.xml b/plugin/trino-google-sheets/pom.xml index 53575994e080..4ff813f9718e 100644 --- a/plugin/trino-google-sheets/pom.xml +++ b/plugin/trino-google-sheets/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-hive-hadoop2/pom.xml b/plugin/trino-hive-hadoop2/pom.xml index bad23bca8bed..790c47ff3e7a 100644 --- a/plugin/trino-hive-hadoop2/pom.xml +++ b/plugin/trino-hive-hadoop2/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-hive/pom.xml b/plugin/trino-hive/pom.xml index c87993f109e7..2b28b5738f9c 100644 --- a/plugin/trino-hive/pom.xml +++ b/plugin/trino-hive/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-http-event-listener/pom.xml b/plugin/trino-http-event-listener/pom.xml index eb314900c4d2..4f81b6d59a6c 100644 --- a/plugin/trino-http-event-listener/pom.xml +++ b/plugin/trino-http-event-listener/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-hudi/pom.xml b/plugin/trino-hudi/pom.xml index 3e2db17a32dc..5e6c486cf3e1 100644 --- a/plugin/trino-hudi/pom.xml +++ b/plugin/trino-hudi/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-iceberg/pom.xml b/plugin/trino-iceberg/pom.xml index 015e626bd25e..ee25b3175678 100644 --- a/plugin/trino-iceberg/pom.xml +++ b/plugin/trino-iceberg/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-ignite/pom.xml b/plugin/trino-ignite/pom.xml index 51e9d5a5675c..4c122a6c4f41 100644 --- a/plugin/trino-ignite/pom.xml +++ b/plugin/trino-ignite/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-jmx/pom.xml b/plugin/trino-jmx/pom.xml index 97480b802444..ad2464afa78a 100644 --- a/plugin/trino-jmx/pom.xml +++ b/plugin/trino-jmx/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-kafka/pom.xml b/plugin/trino-kafka/pom.xml index db39ce9b89a1..879684c3ced7 100644 --- a/plugin/trino-kafka/pom.xml +++ b/plugin/trino-kafka/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-kinesis/pom.xml b/plugin/trino-kinesis/pom.xml index c270909ddfc1..5988c20a55e2 100644 --- a/plugin/trino-kinesis/pom.xml +++ b/plugin/trino-kinesis/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-kudu/pom.xml b/plugin/trino-kudu/pom.xml index 708da51c8a87..0e944903a4f0 100644 --- a/plugin/trino-kudu/pom.xml +++ b/plugin/trino-kudu/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-local-file/pom.xml b/plugin/trino-local-file/pom.xml index a7b059b5a8dc..cea93e66eb00 100644 --- a/plugin/trino-local-file/pom.xml +++ b/plugin/trino-local-file/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-mariadb/pom.xml b/plugin/trino-mariadb/pom.xml index a05fe4804ffb..4116f5e8ccd4 100644 --- a/plugin/trino-mariadb/pom.xml +++ b/plugin/trino-mariadb/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-memory/pom.xml b/plugin/trino-memory/pom.xml index c73e4ea60c11..860f99a46ddd 100644 --- a/plugin/trino-memory/pom.xml +++ b/plugin/trino-memory/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-ml/pom.xml b/plugin/trino-ml/pom.xml index d5ef9b85b2a0..5e85c2fe5b0e 100644 --- a/plugin/trino-ml/pom.xml +++ b/plugin/trino-ml/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-mongodb/pom.xml b/plugin/trino-mongodb/pom.xml index c049c620ddfb..ef823ea6c80e 100644 --- a/plugin/trino-mongodb/pom.xml +++ b/plugin/trino-mongodb/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-mysql-event-listener/pom.xml b/plugin/trino-mysql-event-listener/pom.xml index bf2831665968..b7904f9a4516 100644 --- a/plugin/trino-mysql-event-listener/pom.xml +++ b/plugin/trino-mysql-event-listener/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-mysql/pom.xml b/plugin/trino-mysql/pom.xml index 8919c5662116..bdf9e9b22a3c 100644 --- a/plugin/trino-mysql/pom.xml +++ b/plugin/trino-mysql/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-oracle/pom.xml b/plugin/trino-oracle/pom.xml index be95fe4d43f8..19f7be5c195a 100644 --- a/plugin/trino-oracle/pom.xml +++ b/plugin/trino-oracle/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-password-authenticators/pom.xml b/plugin/trino-password-authenticators/pom.xml index 96cc5a2760f7..6306c9f15c13 100644 --- a/plugin/trino-password-authenticators/pom.xml +++ b/plugin/trino-password-authenticators/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-phoenix5/pom.xml b/plugin/trino-phoenix5/pom.xml index 4e50c8acfa3e..97a834b0a75b 100644 --- a/plugin/trino-phoenix5/pom.xml +++ b/plugin/trino-phoenix5/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-pinot/pom.xml b/plugin/trino-pinot/pom.xml index a3c92a2e6170..7d2e84cf6060 100755 --- a/plugin/trino-pinot/pom.xml +++ b/plugin/trino-pinot/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-postgresql/pom.xml b/plugin/trino-postgresql/pom.xml index f25392a6ddb1..cd51a4ee159e 100644 --- a/plugin/trino-postgresql/pom.xml +++ b/plugin/trino-postgresql/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-prometheus/pom.xml b/plugin/trino-prometheus/pom.xml index 8b25242acee1..083b7ddf1644 100644 --- a/plugin/trino-prometheus/pom.xml +++ b/plugin/trino-prometheus/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-raptor-legacy/pom.xml b/plugin/trino-raptor-legacy/pom.xml index c4bf5c7ccedd..75ba8ae77e02 100644 --- a/plugin/trino-raptor-legacy/pom.xml +++ b/plugin/trino-raptor-legacy/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-redis/pom.xml b/plugin/trino-redis/pom.xml index f52c07f654c4..e85e49cda7f9 100644 --- a/plugin/trino-redis/pom.xml +++ b/plugin/trino-redis/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-redshift/pom.xml b/plugin/trino-redshift/pom.xml index e2948e88e581..3535e4ab43bb 100644 --- a/plugin/trino-redshift/pom.xml +++ b/plugin/trino-redshift/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-resource-group-managers/pom.xml b/plugin/trino-resource-group-managers/pom.xml index c00ef6e5126d..97e276cf212d 100644 --- a/plugin/trino-resource-group-managers/pom.xml +++ b/plugin/trino-resource-group-managers/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-session-property-managers/pom.xml b/plugin/trino-session-property-managers/pom.xml index 0fd8079be13b..7838a5bb9aac 100644 --- a/plugin/trino-session-property-managers/pom.xml +++ b/plugin/trino-session-property-managers/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-singlestore/pom.xml b/plugin/trino-singlestore/pom.xml index f9a04c282e30..1b46a9468077 100644 --- a/plugin/trino-singlestore/pom.xml +++ b/plugin/trino-singlestore/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-sqlserver/pom.xml b/plugin/trino-sqlserver/pom.xml index 45b23a2f87e7..7e6e87f22753 100644 --- a/plugin/trino-sqlserver/pom.xml +++ b/plugin/trino-sqlserver/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-teradata-functions/pom.xml b/plugin/trino-teradata-functions/pom.xml index f9d4d2aa5890..9f6091c6856f 100644 --- a/plugin/trino-teradata-functions/pom.xml +++ b/plugin/trino-teradata-functions/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-thrift-api/pom.xml b/plugin/trino-thrift-api/pom.xml index b2f275c9b6c7..1ccccd4bb56e 100644 --- a/plugin/trino-thrift-api/pom.xml +++ b/plugin/trino-thrift-api/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-thrift-testing-server/pom.xml b/plugin/trino-thrift-testing-server/pom.xml index eb79a6e47688..9ac075a82a1d 100644 --- a/plugin/trino-thrift-testing-server/pom.xml +++ b/plugin/trino-thrift-testing-server/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-thrift/pom.xml b/plugin/trino-thrift/pom.xml index 7c7367a81aaa..ba7678428056 100644 --- a/plugin/trino-thrift/pom.xml +++ b/plugin/trino-thrift/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-tpcds/pom.xml b/plugin/trino-tpcds/pom.xml index 66d54d925ff5..eb484191d85b 100644 --- a/plugin/trino-tpcds/pom.xml +++ b/plugin/trino-tpcds/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/plugin/trino-tpch/pom.xml b/plugin/trino-tpch/pom.xml index 73731a57b595..721574b2eab7 100644 --- a/plugin/trino-tpch/pom.xml +++ b/plugin/trino-tpch/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/pom.xml b/pom.xml index 6bc990e43d77..810b049af50d 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT pom ${project.artifactId} @@ -134,7 +134,7 @@ scm:git:git://github.com/trinodb/trino.git - 434 + HEAD https://github.com/trinodb/trino diff --git a/service/trino-proxy/pom.xml b/service/trino-proxy/pom.xml index 651398af3fbb..c147ba483865 100644 --- a/service/trino-proxy/pom.xml +++ b/service/trino-proxy/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/service/trino-verifier/pom.xml b/service/trino-verifier/pom.xml index 0ad636c5049f..a8ab89b6bfa6 100644 --- a/service/trino-verifier/pom.xml +++ b/service/trino-verifier/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/testing/trino-benchmark-queries/pom.xml b/testing/trino-benchmark-queries/pom.xml index 3c14736c75dd..11777cbd2b33 100644 --- a/testing/trino-benchmark-queries/pom.xml +++ b/testing/trino-benchmark-queries/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/testing/trino-benchmark/pom.xml b/testing/trino-benchmark/pom.xml index b7c9560ce0d5..798004a533da 100644 --- a/testing/trino-benchmark/pom.xml +++ b/testing/trino-benchmark/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/testing/trino-benchto-benchmarks/pom.xml b/testing/trino-benchto-benchmarks/pom.xml index 3190d112a130..3c31fa6350dc 100644 --- a/testing/trino-benchto-benchmarks/pom.xml +++ b/testing/trino-benchto-benchmarks/pom.xml @@ -4,7 +4,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/testing/trino-faulttolerant-tests/pom.xml b/testing/trino-faulttolerant-tests/pom.xml index 053130f1be0a..bce534c1e5e3 100644 --- a/testing/trino-faulttolerant-tests/pom.xml +++ b/testing/trino-faulttolerant-tests/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/testing/trino-plugin-reader/pom.xml b/testing/trino-plugin-reader/pom.xml index 2eee485ad8e7..078ca5cd85d5 100644 --- a/testing/trino-plugin-reader/pom.xml +++ b/testing/trino-plugin-reader/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/testing/trino-product-tests-launcher/pom.xml b/testing/trino-product-tests-launcher/pom.xml index 6e42894053c5..03eb0334fe48 100644 --- a/testing/trino-product-tests-launcher/pom.xml +++ b/testing/trino-product-tests-launcher/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/testing/trino-product-tests/pom.xml b/testing/trino-product-tests/pom.xml index ce894982fd4e..8aaf3b31cfbd 100644 --- a/testing/trino-product-tests/pom.xml +++ b/testing/trino-product-tests/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/testing/trino-server-dev/pom.xml b/testing/trino-server-dev/pom.xml index cae601d756d7..ac80013894a4 100644 --- a/testing/trino-server-dev/pom.xml +++ b/testing/trino-server-dev/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/testing/trino-test-jdbc-compatibility-old-driver/pom.xml b/testing/trino-test-jdbc-compatibility-old-driver/pom.xml index 01122c9dba6b..999d56505a12 100644 --- a/testing/trino-test-jdbc-compatibility-old-driver/pom.xml +++ b/testing/trino-test-jdbc-compatibility-old-driver/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml @@ -15,7 +15,7 @@ ${project.parent.basedir} - 434 + 435-SNAPSHOT diff --git a/testing/trino-test-jdbc-compatibility-old-server/pom.xml b/testing/trino-test-jdbc-compatibility-old-server/pom.xml index 5fd1ae77263c..c8c837e69e73 100644 --- a/testing/trino-test-jdbc-compatibility-old-server/pom.xml +++ b/testing/trino-test-jdbc-compatibility-old-server/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/testing/trino-testing-containers/pom.xml b/testing/trino-testing-containers/pom.xml index d7b5545221e4..ff83b97339de 100644 --- a/testing/trino-testing-containers/pom.xml +++ b/testing/trino-testing-containers/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/testing/trino-testing-kafka/pom.xml b/testing/trino-testing-kafka/pom.xml index f7cd1562dff6..ccc9d5c58620 100644 --- a/testing/trino-testing-kafka/pom.xml +++ b/testing/trino-testing-kafka/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/testing/trino-testing-resources/pom.xml b/testing/trino-testing-resources/pom.xml index 3487cab3e763..307463e255df 100644 --- a/testing/trino-testing-resources/pom.xml +++ b/testing/trino-testing-resources/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/testing/trino-testing-services/pom.xml b/testing/trino-testing-services/pom.xml index d0fc137d5a08..0a29fa6069c8 100644 --- a/testing/trino-testing-services/pom.xml +++ b/testing/trino-testing-services/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/testing/trino-testing/pom.xml b/testing/trino-testing/pom.xml index 3dc291fed0b8..0e3bba286700 100644 --- a/testing/trino-testing/pom.xml +++ b/testing/trino-testing/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml diff --git a/testing/trino-tests/pom.xml b/testing/trino-tests/pom.xml index 7b8d4df57c66..3d58172bd50e 100644 --- a/testing/trino-tests/pom.xml +++ b/testing/trino-tests/pom.xml @@ -5,7 +5,7 @@ io.trino trino-root - 434 + 435-SNAPSHOT ../../pom.xml From c3cac47610d294749f6714b41a96880afbf9c56c Mon Sep 17 00:00:00 2001 From: Xiang Fu Date: Tue, 28 Nov 2023 15:25:11 -0800 Subject: [PATCH 544/587] Support bytes reading for DataTable V4 in Pinot --- .../plugin/pinot/PinotSegmentPageSource.java | 25 ++++++++++++++++--- .../resources/pinot-server/pinot-server.conf | 1 + 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/PinotSegmentPageSource.java b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/PinotSegmentPageSource.java index bfc36fcfa20a..cc9d08e3481d 100755 --- a/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/PinotSegmentPageSource.java +++ b/plugin/trino-pinot/src/main/java/io/trino/plugin/pinot/PinotSegmentPageSource.java @@ -29,6 +29,7 @@ import io.trino.spi.type.Type; import io.trino.spi.type.VarbinaryType; import io.trino.spi.type.VarcharType; +import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; @@ -342,15 +343,31 @@ private Block getArrayBlock(int rowIndex, int columnIndex) private Slice getSlice(int rowIndex, int columnIndex) { Type trinoType = getType(columnIndex); + DataTable dataTable = currentDataTable.getDataTable(); + if (trinoType instanceof VarcharType) { - String field = currentDataTable.getDataTable().getString(rowIndex, columnIndex); + String field = dataTable.getString(rowIndex, columnIndex); return getUtf8Slice(field); } if (trinoType instanceof VarbinaryType) { - return Slices.wrappedBuffer(toBytes(currentDataTable.getDataTable().getString(rowIndex, columnIndex))); + // Pinot 0.11.0 and 0.12.1 default to use V3 data table for server response. + // Pinot 1.0.0 and above default to use V4 data table. + // Pinot v4 data table uses variable length encoding for bytes instead of hex string representation in v3. + // In order to change the data table version, users need to explicitly set: + // `pinot.server.instance.currentDataTableVersion=3` in pinot server config. + if (dataTable.getVersion() >= 4) { + try { + return Slices.wrappedBuffer(dataTable.getBytes(rowIndex, columnIndex).getBytes()); + } + catch (NullPointerException e) { + // Pinot throws NPE when the entry is null. + return Slices.wrappedBuffer(); + } + } + return Slices.wrappedBuffer(toBytes(dataTable.getString(rowIndex, columnIndex))); } - if (trinoType.getTypeSignature().getBase() == StandardTypes.JSON) { - String field = currentDataTable.getDataTable().getString(rowIndex, columnIndex); + if (trinoType.getTypeSignature().getBase().equalsIgnoreCase(StandardTypes.JSON)) { + String field = dataTable.getString(rowIndex, columnIndex); return jsonParse(getUtf8Slice(field)); } return Slices.EMPTY_SLICE; diff --git a/plugin/trino-pinot/src/test/resources/pinot-server/pinot-server.conf b/plugin/trino-pinot/src/test/resources/pinot-server/pinot-server.conf index 642d46031e11..f1f1550351e3 100644 --- a/plugin/trino-pinot/src/test/resources/pinot-server/pinot-server.conf +++ b/plugin/trino-pinot/src/test/resources/pinot-server/pinot-server.conf @@ -4,3 +4,4 @@ pinot.server.instance.dataDir=/var/pinot/server/data/index pinot.server.instance.segmentTarDir=/var/pinot/server/data/segment pinot.set.instance.id.to.hostname=true pinot.server.grpc.enable=true +pinot.server.instance.currentDataTableVersion=4 From 6bf970b512d06f33e67641496a9104cee89e1d61 Mon Sep 17 00:00:00 2001 From: Marius Grama Date: Tue, 28 Nov 2023 12:41:49 +0100 Subject: [PATCH 545/587] Fix test by using metadata entry consistent with the add entries --- .../checkpoint/TestCheckpointEntryIterator.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointEntryIterator.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointEntryIterator.java index 3857f0e72d7d..5e3e8edeed49 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointEntryIterator.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointEntryIterator.java @@ -456,9 +456,9 @@ public void testReadAddEntriesStatsProjection() IntStream.rangeClosed(1, countStringColumns) .boxed() .map("{\"name\":\"stringcol%s\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}"::formatted) - .collect(Collectors.joining(",", "", ",")) + - "{\"name\":\"part_key\",\"type\":\"timestamp\",\"nullable\":true,\"metadata\":{}}]}", - ImmutableList.of("part_key"), + .collect(Collectors.joining(",")) + + "]}", + ImmutableList.of(), ImmutableMap.of("delta.checkpoint.writeStatsAsJson", "false", "delta.checkpoint.writeStatsAsStruct", "true"), 1000); ProtocolEntry protocolEntry = new ProtocolEntry(10, 20, Optional.empty(), Optional.empty()); From 951bd4bea0b47fc75fbcf2c93014aa3aa051499f Mon Sep 17 00:00:00 2001 From: Marius Grama Date: Mon, 27 Nov 2023 09:14:37 +0100 Subject: [PATCH 546/587] Add import for QueryAssert.Row --- .../TestDeltaLakeCheckpointsCompatibility.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeCheckpointsCompatibility.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeCheckpointsCompatibility.java index 49d8cce5ed4f..5488e40ea5e7 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeCheckpointsCompatibility.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestDeltaLakeCheckpointsCompatibility.java @@ -20,7 +20,7 @@ import com.google.inject.Inject; import com.google.inject.name.Named; import io.trino.tempto.BeforeMethodWithContext; -import io.trino.tempto.assertions.QueryAssert; +import io.trino.tempto.assertions.QueryAssert.Row; import io.trino.testng.services.Flaky; import io.trino.tests.product.deltalake.util.DatabricksVersion; import org.testng.SkipException; @@ -95,7 +95,7 @@ public void testSparkCanReadTrinoCheckpoint() onDelta().executeQuery("DELETE FROM default." + tableName + " WHERE a_string = 'jeza'"); onTrino().executeQuery("DELETE FROM delta.default." + tableName + " WHERE a_string = 'bobra'"); - List expectedRows = ImmutableList.of( + List expectedRows = ImmutableList.of( row(1, "ala"), row(2, "kota"), row(3, "osla"), @@ -250,7 +250,7 @@ public void testDatabricksCheckpointMinMaxStatisticsForRowType() private void testCheckpointMinMaxStatisticsForRowType(Consumer sqlExecutor, String tableName, String qualifiedTableName) { - List expectedRows = ImmutableList.of( + List expectedRows = ImmutableList.of( row(1, "ala"), row(2, "kota"), row(3, "osla"), @@ -287,7 +287,7 @@ private void testCheckpointMinMaxStatisticsForRowType(Consumer sqlExecut assertThat(explainSelectMax).matches("== Physical Plan ==\\s*LocalTableScan \\[max\\(" + column + "\\).*]\\s*"); // check both engines can read both tables - List maxMin = ImmutableList.of(row(3, "ala")); + List maxMin = ImmutableList.of(row(3, "ala")); assertThat(onDelta().executeQuery("SELECT max(root.entry_one), min(root.entry_two) FROM default." + tableName)) .containsOnly(maxMin); assertThat(onTrino().executeQuery("SELECT max(root.entry_one), min(root.entry_two) FROM delta.default." + tableName)) @@ -317,7 +317,7 @@ public void testDatabricksCheckpointNullStatisticsForRowType() private void testCheckpointNullStatisticsForRowType(Consumer sqlExecutor, String tableName, String qualifiedTableName) { - List expectedRows = ImmutableList.of( + List expectedRows = ImmutableList.of( row(1, "ala"), row(2, "kota"), row(null, null), From 686092470a2206d99a46843d08c2a27d08cf990e Mon Sep 17 00:00:00 2001 From: Marius Grama Date: Tue, 7 Nov 2023 23:04:30 +0100 Subject: [PATCH 547/587] Write `partitionValues_parsed` field for `add` entries in the checkpoint --- .../checkpoint/CheckpointEntryIterator.java | 2 +- .../checkpoint/CheckpointSchemaManager.java | 10 +- .../checkpoint/CheckpointWriter.java | 55 +++++++++- .../plugin/deltalake/TestDeltaLakeBasic.java | 96 +++++++++++++++++ .../deltalake/TestDeltaLakeConnectorTest.java | 98 ++++++++++++++++++ .../TestCheckpointEntryIterator.java | 2 +- .../checkpoint/TestCheckpointWriter.java | 11 +- .../README.md | 14 +++ .../_delta_log/00000000000000000000.json | 3 + .../_delta_log/00000000000000000001.json | 2 + .../00000000000000000002.checkpoint.parquet | Bin 0 -> 19390 bytes .../_delta_log/00000000000000000002.json | 2 + .../_delta_log/00000000000000000003.json | 2 + .../_delta_log/_last_checkpoint | 1 + ...425c-9f33-1296f2b97058.c000.snappy.parquet | Bin 0 -> 574 bytes ...4588-97f8-e219a1c20133.c000.snappy.parquet | Bin 0 -> 574 bytes ...4e97-ae34-1ae6ade088d2.c000.snappy.parquet | Bin 0 -> 574 bytes ...TestDeltaLakeCheckpointsCompatibility.java | 87 ++++++++++++++++ 18 files changed, 368 insertions(+), 17 deletions(-) create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/README.md create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/_delta_log/00000000000000000000.json create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/_delta_log/00000000000000000001.json create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/_delta_log/00000000000000000002.checkpoint.parquet create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/_delta_log/00000000000000000002.json create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/_delta_log/00000000000000000003.json create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/_delta_log/_last_checkpoint create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/part_NuMbEr=1/part_StRiNg=ala/part-00000-da29c0d3-b5d6-425c-9f33-1296f2b97058.c000.snappy.parquet create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/part_NuMbEr=2/part_StRiNg=kota/part-00000-7a66d694-3b49-4588-97f8-e219a1c20133.c000.snappy.parquet create mode 100644 plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/part_NuMbEr=3/part_StRiNg=osla/part-00000-51bd41c3-6a8e-4e97-ae34-1ae6ade088d2.c000.snappy.parquet diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointEntryIterator.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointEntryIterator.java index 9f0e4ea608e1..9b90eaf50930 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointEntryIterator.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointEntryIterator.java @@ -237,7 +237,7 @@ private DeltaLakeColumnHandle buildColumnHandle( { Type type = switch (entryType) { case TRANSACTION -> schemaManager.getTxnEntryType(); - case ADD -> schemaManager.getAddEntryType(metadataEntry, protocolEntry, addStatsMinMaxColumnFilter.orElseThrow(), true, true, true); + case ADD -> schemaManager.getAddEntryType(metadataEntry, protocolEntry, addStatsMinMaxColumnFilter.orElseThrow(), true, true); case REMOVE -> schemaManager.getRemoveEntryType(); case METADATA -> schemaManager.getMetadataEntryType(); case PROTOCOL -> schemaManager.getProtocolEntryType(true, true); diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointSchemaManager.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointSchemaManager.java index 73a9fa04ad74..c2ab94fe4adb 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointSchemaManager.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointSchemaManager.java @@ -15,6 +15,7 @@ import com.google.common.collect.ImmutableList; import com.google.inject.Inject; +import io.trino.plugin.deltalake.DeltaHiveTypeTranslator; import io.trino.plugin.deltalake.DeltaLakeColumnHandle; import io.trino.plugin.deltalake.DeltaLakeColumnMetadata; import io.trino.plugin.deltalake.transactionlog.MetadataEntry; @@ -120,8 +121,7 @@ public RowType getAddEntryType( ProtocolEntry protocolEntry, Predicate addStatsMinMaxColumnFilter, boolean requireWriteStatsAsJson, - boolean requireWriteStatsAsStruct, - boolean usePartitionValuesParsed) + boolean requireWriteStatsAsStruct) { List allColumns = extractSchema(metadataEntry, protocolEntry, typeManager); List minMaxColumns = columnsWithStats(metadataEntry, protocolEntry, typeManager); @@ -168,16 +168,14 @@ public RowType getAddEntryType( if (requireWriteStatsAsJson) { addFields.add(RowType.field("stats", VARCHAR)); } - if (usePartitionValuesParsed) { + if (requireWriteStatsAsStruct) { List partitionColumns = extractPartitionColumns(metadataEntry, protocolEntry, typeManager); if (!partitionColumns.isEmpty()) { List partitionValuesParsed = partitionColumns.stream() - .map(column -> RowType.field(column.getColumnName(), column.getType())) + .map(column -> RowType.field(column.getColumnName(), typeManager.getType(DeltaHiveTypeTranslator.toHiveType(column.getType()).getTypeSignature()))) .collect(toImmutableList()); addFields.add(RowType.field("partitionValues_parsed", RowType.from(partitionValuesParsed))); } - } - if (requireWriteStatsAsStruct) { addFields.add(RowType.field("stats_parsed", RowType.from(statsColumns.build()))); } addFields.add(RowType.field("tags", stringMap)); diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointWriter.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointWriter.java index 18f522fabb57..0cc8c1041431 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointWriter.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/transactionlog/checkpoint/CheckpointWriter.java @@ -20,6 +20,7 @@ import io.trino.parquet.writer.ParquetSchemaConverter; import io.trino.parquet.writer.ParquetWriter; import io.trino.parquet.writer.ParquetWriterOptions; +import io.trino.plugin.deltalake.DeltaLakeColumnHandle; import io.trino.plugin.deltalake.DeltaLakeColumnMetadata; import io.trino.plugin.deltalake.transactionlog.AddFileEntry; import io.trino.plugin.deltalake.transactionlog.MetadataEntry; @@ -59,10 +60,12 @@ import static io.trino.plugin.deltalake.transactionlog.DeltaLakeParquetStatisticsUtils.jsonValueToTrinoValue; import static io.trino.plugin.deltalake.transactionlog.DeltaLakeParquetStatisticsUtils.toJsonValues; import static io.trino.plugin.deltalake.transactionlog.DeltaLakeParquetStatisticsUtils.toNullCounts; +import static io.trino.plugin.deltalake.transactionlog.DeltaLakeSchemaSupport.extractPartitionColumns; import static io.trino.plugin.deltalake.transactionlog.DeltaLakeSchemaSupport.extractSchema; import static io.trino.plugin.deltalake.transactionlog.DeltaLakeSchemaSupport.serializeStatsAsJson; import static io.trino.plugin.deltalake.transactionlog.MetadataEntry.DELTA_CHECKPOINT_WRITE_STATS_AS_JSON_PROPERTY; import static io.trino.plugin.deltalake.transactionlog.MetadataEntry.DELTA_CHECKPOINT_WRITE_STATS_AS_STRUCT_PROPERTY; +import static io.trino.plugin.deltalake.transactionlog.TransactionLogParser.deserializePartitionValue; import static io.trino.spi.type.Timestamps.MICROSECONDS_PER_MILLISECOND; import static io.trino.spi.type.TypeUtils.writeNativeValue; import static java.lang.Math.multiplyExact; @@ -112,14 +115,12 @@ public void write(CheckpointEntries entries, TrinoOutputFile outputFile) RowType metadataEntryType = checkpointSchemaManager.getMetadataEntryType(); RowType protocolEntryType = checkpointSchemaManager.getProtocolEntryType(protocolEntry.getReaderFeatures().isPresent(), protocolEntry.getWriterFeatures().isPresent()); RowType txnEntryType = checkpointSchemaManager.getTxnEntryType(); - // TODO https://github.com/trinodb/trino/issues/19586 Add support for writing 'partitionValues_parsed' field RowType addEntryType = checkpointSchemaManager.getAddEntryType( entries.getMetadataEntry(), entries.getProtocolEntry(), alwaysTrue(), writeStatsAsJson, - writeStatsAsStruct, - false); + writeStatsAsStruct); RowType removeEntryType = checkpointSchemaManager.getRemoveEntryType(); List columnNames = ImmutableList.of( @@ -154,8 +155,12 @@ public void write(CheckpointEntries entries, TrinoOutputFile outputFile) for (TransactionEntry transactionEntry : entries.getTransactionEntries()) { writeTransactionEntry(pageBuilder, txnEntryType, transactionEntry); } + List partitionColumns = extractPartitionColumns(entries.getMetadataEntry(), entries.getProtocolEntry(), typeManager); + List partitionValuesParsedFieldTypes = partitionColumns.stream() + .map(column -> RowType.field(column.getColumnName(), column.getType())) + .collect(toImmutableList()); for (AddFileEntry addFileEntry : entries.getAddFileEntries()) { - writeAddFileEntry(pageBuilder, addEntryType, addFileEntry, entries.getMetadataEntry(), entries.getProtocolEntry(), writeStatsAsJson, writeStatsAsStruct); + writeAddFileEntry(pageBuilder, addEntryType, addFileEntry, entries.getMetadataEntry(), entries.getProtocolEntry(), partitionColumns, partitionValuesParsedFieldTypes, writeStatsAsJson, writeStatsAsStruct); } for (RemoveFileEntry removeFileEntry : entries.getRemoveFileEntries()) { writeRemoveFileEntry(pageBuilder, removeEntryType, removeFileEntry); @@ -228,7 +233,16 @@ private void writeTransactionEntry(PageBuilder pageBuilder, RowType entryType, T appendNullOtherBlocks(pageBuilder, TXN_BLOCK_CHANNEL); } - private void writeAddFileEntry(PageBuilder pageBuilder, RowType entryType, AddFileEntry addFileEntry, MetadataEntry metadataEntry, ProtocolEntry protocolEntry, boolean writeStatsAsJson, boolean writeStatsAsStruct) + private void writeAddFileEntry( + PageBuilder pageBuilder, + RowType entryType, + AddFileEntry addFileEntry, + MetadataEntry metadataEntry, + ProtocolEntry protocolEntry, + List partitionColumns, + List partitionValuesParsedFieldTypes, + boolean writeStatsAsJson, + boolean writeStatsAsStruct) { pageBuilder.declarePosition(); RowBlockBuilder blockBuilder = (RowBlockBuilder) pageBuilder.getBlockBuilder(ADD_BLOCK_CHANNEL); @@ -255,6 +269,11 @@ private void writeAddFileEntry(PageBuilder pageBuilder, RowType entryType, AddFi } if (writeStatsAsStruct) { + if (!addFileEntry.getPartitionValues().isEmpty()) { + writeParsedPartitionValues(fieldBuilders.get(fieldId), entryType, addFileEntry, partitionColumns, partitionValuesParsedFieldTypes, fieldId); + fieldId++; + } + writeParsedStats(fieldBuilders.get(fieldId), entryType, addFileEntry, fieldId); fieldId++; } @@ -303,6 +322,32 @@ private Optional getStatsString(DeltaLakeJsonFileStatistics parsedStats) } } + private void writeParsedPartitionValues( + BlockBuilder entryBlockBuilder, + RowType entryType, + AddFileEntry addFileEntry, + List partitionColumns, + List partitionValuesParsedFieldTypes, + int fieldId) + { + RowType partitionValuesParsedType = getInternalRowType(entryType, fieldId, "partitionValues_parsed"); + ((RowBlockBuilder) entryBlockBuilder).buildEntry(fieldBuilders -> { + for (int i = 0; i < partitionValuesParsedFieldTypes.size(); i++) { + RowType.Field partitionValueField = partitionValuesParsedFieldTypes.get(i); + String partitionColumnName = partitionValueField.getName().orElseThrow(); + String partitionValue = addFileEntry.getPartitionValues().get(partitionColumnName); + validateAndGetField(partitionValuesParsedType, i, partitionColumnName); + if (partitionValue == null) { + fieldBuilders.get(i).appendNull(); + continue; + } + DeltaLakeColumnHandle partitionColumn = partitionColumns.get(i); + Object deserializedPartitionValue = deserializePartitionValue(partitionColumn, Optional.of(partitionValue)); + writeNativeValue(partitionValueField.getType(), fieldBuilders.get(i), deserializedPartitionValue); + } + }); + } + private void writeParsedStats(BlockBuilder entryBlockBuilder, RowType entryType, AddFileEntry addFileEntry, int fieldId) { RowType statsType = getInternalRowType(entryType, fieldId, "stats_parsed"); diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeBasic.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeBasic.java index 4f3f55503043..2d74baea49f7 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeBasic.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeBasic.java @@ -1199,6 +1199,102 @@ public void testDeltaLakeWithPartitionValuesParsedAllTypes() assertPartitionValuesParsedCondition(tableName, 3, "part_timestamp_ntz IS NULL"); } + /** + * @see deltalake.partition_values_parsed_all_types + */ + @Test + public void testDeltaLakeWritePartitionValuesParsedAllTypesInCheckpoint() + throws Exception + { + String tableName = "test_write_partition_values_parsed_checkpoint_" + randomNameSuffix(); + Path tableLocation = Files.createTempFile(tableName, null); + copyDirectoryContents(new File(Resources.getResource("deltalake/partition_values_parsed_all_types").toURI()).toPath(), tableLocation); + assertUpdate("CALL system.register_table('%s', '%s', '%s')".formatted(getSession().getSchema().orElseThrow(), tableName, tableLocation.toUri())); + + assertThat(query("SELECT * FROM " + tableName)) + .skippingTypesCheck() + .matches(""" + VALUES + (1, true, TINYINT '1', SMALLINT '10', 100, BIGINT '1000', CAST('123.12' AS DECIMAL(5,2)), CAST('123456789012345678.123' AS DECIMAL(21,3)), DOUBLE '1.2', REAL '3.4', 'a', DATE '2020-08-21', TIMESTAMP '2020-10-21 01:00:00.123 UTC', TIMESTAMP '2023-01-02 01:02:03.456'), + (2, false, TINYINT '2', SMALLINT '20', 200, BIGINT '2000', CAST('223.12' AS DECIMAL (5,2)), CAST('223456789012345678.123' AS DECIMAL(21,3)), DOUBLE '10.2', REAL '30.4', 'b', DATE '2020-08-22', TIMESTAMP '2020-10-22 01:00:00.123 UTC', TIMESTAMP '2023-01-03 01:02:03.456'), + (3, null, null, null, null, null, null, null, null, null, null, null, null, null)"""); + + // Create a new checkpoint + assertUpdate("INSERT INTO " + tableName + " VALUES (4, false, TINYINT '4', SMALLINT '40', 400, BIGINT '4000', CAST('444.44' AS DECIMAL(5,2)), CAST('4444444.444' AS DECIMAL(21,3)), DOUBLE '4.4', REAL '4.4', 'd', DATE '2020-08-24', TIMESTAMP '2020-10-24 01:00:00.123 UTC', TIMESTAMP '2023-01-04 01:02:03.456')", 1); + assertUpdate("INSERT INTO " + tableName + " VALUES (5, false, TINYINT '5', SMALLINT '50', 500, BIGINT '5000', CAST('555.5' AS DECIMAL(5,2)), CAST('55555.55' AS DECIMAL(21,3)), DOUBLE '5.55', REAL '5.5555', 'd', DATE '2020-08-25', TIMESTAMP '2020-10-25 01:00:00.123 UTC', TIMESTAMP '2023-01-05 01:02:03.456')", 1); + assertUpdate("INSERT INTO " + tableName + " VALUES (6, null, null, null, null, null, null, null, null, null, null, null, null, null)", 1); + + assertThat(query("SELECT * FROM " + tableName)) + .skippingTypesCheck() + .matches(""" + VALUES + (1, true, TINYINT '1', SMALLINT '10', 100, BIGINT '1000', CAST('123.12' AS DECIMAL(5,2)), CAST('123456789012345678.123' AS DECIMAL(21,3)), DOUBLE '1.2', REAL '3.4', 'a', DATE '2020-08-21', TIMESTAMP '2020-10-21 01:00:00.123 UTC', TIMESTAMP '2023-01-02 01:02:03.456'), + (2, false, TINYINT '2', SMALLINT '20', 200, BIGINT '2000', CAST('223.12' AS DECIMAL (5,2)), CAST('223456789012345678.123' AS DECIMAL(21,3)), DOUBLE '10.2', REAL '30.4', 'b', DATE '2020-08-22', TIMESTAMP '2020-10-22 01:00:00.123 UTC', TIMESTAMP '2023-01-03 01:02:03.456'), + (3, null, null, null, null, null, null, null, null, null, null, null, null, null), + (4, false, TINYINT '4', SMALLINT '40', 400, BIGINT '4000', CAST('444.44' AS DECIMAL(5,2)), CAST('4444444.444' AS DECIMAL(21,3)), DOUBLE '4.4', REAL '4.4', 'd', DATE '2020-08-24', TIMESTAMP '2020-10-24 01:00:00.123 UTC', TIMESTAMP '2023-01-04 01:02:03.456'), + (5, false, TINYINT '5', SMALLINT '50', 500, BIGINT '5000', CAST('555.5' AS DECIMAL(5,2)), CAST('55555.55' AS DECIMAL(21,3)), DOUBLE '5.55', REAL '5.5555', 'd', DATE '2020-08-25', TIMESTAMP '2020-10-25 01:00:00.123 UTC', TIMESTAMP '2023-01-05 01:02:03.456'), + (6, null, null, null, null, null, null, null, null, null, null, null, null, null)"""); + + Session session = Session.builder(getQueryRunner().getDefaultSession()) + .setCatalogSessionProperty("delta", "checkpoint_filtering_enabled", "true") + .build(); + assertThat(query(session, """ + SELECT id + FROM %s + WHERE + part_boolean = true AND + part_tinyint = TINYINT '1' AND + part_smallint= SMALLINT '10' AND + part_int = 100 AND + part_bigint = BIGINT '1000' AND + part_short_decimal = CAST('123.12' AS DECIMAL(5,2)) AND + part_long_decimal = CAST('123456789012345678.123' AS DECIMAL(21,3)) AND + part_double = DOUBLE '1.2' AND + part_float = REAL '3.4' AND + part_varchar = 'a' AND + part_date = DATE '2020-08-21' AND + part_timestamp = TIMESTAMP '2020-10-21 01:00:00.123 UTC' AND + part_timestamp_ntz =TIMESTAMP '2023-01-02 01:02:03.456'""".formatted(tableName))) + .matches("VALUES 1"); + } + + /** + * @see databricks133.partition_values_parsed_case_sensitive + */ + @Test + public void testDeltaLakeWritePartitionValuesParsedCaseSensitiveInCheckpoint() + throws Exception + { + String tableName = "test_write_partition_values_parsed_checkpoint_" + randomNameSuffix(); + Path tableLocation = Files.createTempFile(tableName, null); + copyDirectoryContents(new File(Resources.getResource("databricks133/partition_values_parsed_case_sensitive").toURI()).toPath(), tableLocation); + assertUpdate("CALL system.register_table('%s', '%s', '%s')".formatted(getSession().getSchema().orElseThrow(), tableName, tableLocation.toUri())); + + assertThat(query("SELECT * FROM " + tableName)) + .skippingTypesCheck() + .matches(""" + VALUES + (100, 1, 'ala'), + (200, 2,'kota'), + (300, 3, 'osla')"""); + + // Create a new checkpoint + assertUpdate("INSERT INTO " + tableName + " VALUES (400, 4, 'kon')", 1); + assertThat(query("SELECT * FROM " + tableName)) + .skippingTypesCheck() + .matches(""" + VALUES + (100, 1, 'ala'), + (200, 2,'kota'), + (300, 3, 'osla'), + (400, 4, 'kon')"""); + Session session = Session.builder(getQueryRunner().getDefaultSession()) + .setCatalogSessionProperty("delta", "checkpoint_filtering_enabled", "true") + .build(); + assertThat(query(session, "SELECT id FROM " + tableName + " WHERE part_NuMbEr = 1 AND part_StRiNg = 'ala'")) + .matches("VALUES 100"); + } + private void assertPartitionValuesParsedCondition(String tableName, int id, @Language("SQL") String condition) { Session session = Session.builder(getQueryRunner().getDefaultSession()) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConnectorTest.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConnectorTest.java index 52b6e166372c..40776b359d1e 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConnectorTest.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConnectorTest.java @@ -1876,6 +1876,104 @@ private void testSupportedPartitionedColumnMappingWrites(String resourceName, bo assertUpdate("DROP TABLE " + tableName); } + @Test + public void testDeltaColumnMappingModeAllPartitionTypesCheckpointing() + { + testDeltaColumnMappingModeAllPartitionTypesCheckpointing(ColumnMappingMode.NONE); + testDeltaColumnMappingModeAllPartitionTypesCheckpointing(ColumnMappingMode.ID); + testDeltaColumnMappingModeAllPartitionTypesCheckpointing(ColumnMappingMode.NAME); + } + + private void testDeltaColumnMappingModeAllPartitionTypesCheckpointing(ColumnMappingMode mode) + { + String tableName = "test_column_mapping_mode_name_all_types_" + randomNameSuffix(); + + assertUpdate(""" + CREATE TABLE %s ( + data INT, + part_boolean BOOLEAN, + part_tinyint TINYINT, + part_smallint SMALLINT, + part_int INT, + part_bigint BIGINT, + part_decimal_5_2 DECIMAL(5,2), + part_decimal_21_3 DECIMAL(21,3), + part_double DOUBLE, + part_float REAL, + part_varchar VARCHAR, + part_date DATE, + part_timestamp TIMESTAMP(3) WITH TIME ZONE + ) + WITH ( + partitioned_by = ARRAY['part_boolean', 'part_tinyint', 'part_smallint', 'part_int', 'part_bigint', 'part_decimal_5_2', 'part_decimal_21_3', 'part_double', 'part_float', 'part_varchar', 'part_date', 'part_timestamp'], + column_mapping_mode = '%s', + checkpoint_interval = 3 + )""".formatted(tableName, mode)); + + assertUpdate(""" + INSERT INTO %s + VALUES ( + 1, + true, + 1, + 10, + 100, + 1000, + CAST('123.12' AS DECIMAL(5,2)), + CAST('123456789012345678.123' AS DECIMAL(21,3)), + DOUBLE '0', + REAL '0', + 'a', + DATE '2020-08-21', + TIMESTAMP '2020-10-21 01:00:00.123 UTC')""".formatted(tableName), 1); + assertUpdate(""" + INSERT INTO %s + VALUES ( + 2, + true, + 2, + 20, + 200, + 2000, + CAST('223.12' AS DECIMAL(5,2)), + CAST('223456789012345678.123' AS DECIMAL(21,3)), + DOUBLE '0', + REAL '0', + 'b', + DATE '2020-08-22', + TIMESTAMP '2020-10-22 02:00:00.456 UTC')""".formatted(tableName), 1); + assertUpdate(""" + INSERT INTO %s + VALUES ( + 3, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL)""".formatted(tableName), 1); + + // Make sure that the checkpoint is being processed + assertUpdate("CALL system.flush_metadata_cache(schema_name => CURRENT_SCHEMA, table_name => '" + tableName + "')"); + assertThat(query(""" + SELECT data, part_boolean, part_tinyint, part_smallint, part_int, part_bigint, part_decimal_5_2, part_decimal_21_3, part_double , part_float, part_varchar, part_date, part_timestamp + FROM %s""".formatted(tableName))) + .skippingTypesCheck() + .matches(""" + VALUES + (1, true, tinyint '1', smallint '10', integer '100', bigint '1000', decimal '123.12', decimal '123456789012345678.123', double '0', real '0', 'a', date '2020-08-21', TIMESTAMP '2020-10-21 01:00:00.123 UTC'), + (2, true, tinyint '2', smallint '20', integer '200', bigint '2000', decimal '223.12', decimal '223456789012345678.123', double '0.0', real '0.0', 'b', date '2020-08-22', TIMESTAMP '2020-10-22 02:00:00.456 UTC'), + (3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL)"""); + + assertUpdate("DROP TABLE " + tableName); + } + @Test public void testCreateTableUnsupportedColumnMappingMode() { diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointEntryIterator.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointEntryIterator.java index 5e3e8edeed49..8cf38548491b 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointEntryIterator.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointEntryIterator.java @@ -636,7 +636,7 @@ public void testSkipRemoveEntries() ProtocolEntry protocolEntry = new ProtocolEntry(10, 20, Optional.empty(), Optional.empty()); AddFileEntry addFileEntryJsonStats = new AddFileEntry( "addFilePathJson", - ImmutableMap.of(), + ImmutableMap.of("part_key", "2023-01-01 00:00:00"), 1000, 1001, true, diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointWriter.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointWriter.java index 4a2354fb137b..b928ae46be54 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointWriter.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointWriter.java @@ -89,7 +89,8 @@ public void testCheckpointWriteReadJsonRoundtrip() "formatOptionX", "blah", "fomatOptionY", "plah")), "{\"type\":\"struct\",\"fields\":" + - "[{\"name\":\"ts\",\"type\":\"timestamp\",\"nullable\":true,\"metadata\":{}}," + + "[{\"name\":\"part_key\",\"type\":\"double\",\"nullable\":true,\"metadata\":{}}," + + "{\"name\":\"ts\",\"type\":\"timestamp\",\"nullable\":true,\"metadata\":{}}," + "{\"name\":\"str\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}," + "{\"name\":\"dec_short\",\"type\":\"decimal(5,1)\",\"nullable\":true,\"metadata\":{}}," + "{\"name\":\"dec_long\",\"type\":\"decimal(25,3)\",\"nullable\":true,\"metadata\":{}}," + @@ -215,7 +216,8 @@ public void testCheckpointWriteReadParquetStatisticsRoundtrip() "formatOptionX", "blah", "fomatOptionY", "plah")), "{\"type\":\"struct\",\"fields\":" + - "[{\"name\":\"ts\",\"type\":\"timestamp\",\"nullable\":true,\"metadata\":{}}," + + "[{\"name\":\"part_key\",\"type\":\"double\",\"nullable\":true,\"metadata\":{}}," + + "{\"name\":\"ts\",\"type\":\"timestamp\",\"nullable\":true,\"metadata\":{}}," + "{\"name\":\"str\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}," + "{\"name\":\"dec_short\",\"type\":\"decimal(5,1)\",\"nullable\":true,\"metadata\":{}}," + "{\"name\":\"dec_long\",\"type\":\"decimal(25,3)\",\"nullable\":true,\"metadata\":{}}," + @@ -350,9 +352,10 @@ public void testDisablingRowStatistics() "formatOptionX", "blah", "fomatOptionY", "plah")), "{\"type\":\"struct\",\"fields\":" + - "[{\"name\":\"row\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"s1\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}," + + "[{\"name\":\"part_key\",\"type\":\"double\",\"nullable\":true,\"metadata\":{}}," + + "{\"name\":\"row\",\"type\":{\"type\":\"struct\",\"fields\":[{\"name\":\"s1\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}," + "{\"name\":\"s2\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]},\"nullable\":true,\"metadata\":{}}]}", - ImmutableList.of(), + ImmutableList.of("part_key"), ImmutableMap.of(), 1000); ProtocolEntry protocolEntry = new ProtocolEntry(10, 20, Optional.empty(), Optional.empty()); diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/README.md b/plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/README.md new file mode 100644 index 000000000000..40b06134b12e --- /dev/null +++ b/plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/README.md @@ -0,0 +1,14 @@ +Data generated with Databricks 13.3 LTS: + + +``` +CREATE TABLE partition_values_parsed_case_sensitive (id INT, part_NuMbEr INT, part_StRiNg STRING) +USING DELTA +PARTITIONED BY (part_NuMbEr, part_StRiNg) +LOCATION 's3://bucket/table' +TBLPROPERTIES (delta.checkpointInterval = 2); + +INSERT INTO partition_values_parsed_case_sensitive VALUES (100, 1,'ala'); +INSERT INTO partition_values_parsed_case_sensitive VALUES (200, 2,'kota'); +INSERT INTO partition_values_parsed_case_sensitive VALUES (300, 3, 'osla'); +``` diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/_delta_log/00000000000000000000.json b/plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/_delta_log/00000000000000000000.json new file mode 100644 index 000000000000..82033c2c5b67 --- /dev/null +++ b/plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/_delta_log/00000000000000000000.json @@ -0,0 +1,3 @@ +{"commitInfo":{"timestamp":1701257537163,"userId":"615774135840106","userName":"marius.grama@starburstdata.com","operation":"CREATE TABLE","operationParameters":{"partitionBy":"[\"part_NuMbEr\",\"part_StRiNg\"]","description":null,"isManaged":"false","properties":"{\"delta.checkpointInterval\":\"2\"}","statsOnLoad":false},"notebook":{"notebookId":"3966548513639247"},"clusterId":"0905-151610-v55wl6f5","isolationLevel":"WriteSerializable","isBlindAppend":true,"operationMetrics":{},"tags":{"restoresDeletedRows":"false"},"engineInfo":"Databricks-Runtime/13.3.x-scala2.12","txnId":"2cceb5a6-5c85-41ca-a800-e2e0130ffa53"}} +{"metaData":{"id":"57f66085-222a-43db-86b0-ad65aec921a9","format":{"provider":"parquet","options":{}},"schemaString":"{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"part_NuMbEr\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"part_StRiNg\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]}","partitionColumns":["part_NuMbEr","part_StRiNg"],"configuration":{"delta.checkpointInterval":"2"},"createdTime":1701257536829}} +{"protocol":{"minReaderVersion":1,"minWriterVersion":2}} diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/_delta_log/00000000000000000001.json b/plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/_delta_log/00000000000000000001.json new file mode 100644 index 000000000000..ff06388a9aa6 --- /dev/null +++ b/plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/_delta_log/00000000000000000001.json @@ -0,0 +1,2 @@ +{"commitInfo":{"timestamp":1701257541157,"userId":"615774135840106","userName":"marius.grama@starburstdata.com","operation":"WRITE","operationParameters":{"mode":"Append","statsOnLoad":false,"partitionBy":"[]"},"notebook":{"notebookId":"3966548513639247"},"clusterId":"0905-151610-v55wl6f5","readVersion":0,"isolationLevel":"WriteSerializable","isBlindAppend":true,"operationMetrics":{"numFiles":"1","numOutputRows":"1","numOutputBytes":"574"},"tags":{"restoresDeletedRows":"false"},"engineInfo":"Databricks-Runtime/13.3.x-scala2.12","txnId":"25429e35-4c3b-47ce-a129-e9e30147e3b4"}} +{"add":{"path":"part_NuMbEr=1/part_StRiNg=ala/part-00000-da29c0d3-b5d6-425c-9f33-1296f2b97058.c000.snappy.parquet","partitionValues":{"part_NuMbEr":"1","part_StRiNg":"ala"},"size":574,"modificationTime":1701257542000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"id\":100},\"maxValues\":{\"id\":100},\"nullCount\":{\"id\":0}}","tags":{"INSERTION_TIME":"1701257542000000","MIN_INSERTION_TIME":"1701257542000000","MAX_INSERTION_TIME":"1701257542000000","OPTIMIZE_TARGET_SIZE":"268435456"}}} diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/_delta_log/00000000000000000002.checkpoint.parquet b/plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/_delta_log/00000000000000000002.checkpoint.parquet new file mode 100644 index 0000000000000000000000000000000000000000..c861aeadabd1873776f9ea52d7891aceb42e537d GIT binary patch literal 19390 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 literal 0 HcmV?d00001 diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/_delta_log/00000000000000000002.json b/plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/_delta_log/00000000000000000002.json new file mode 100644 index 000000000000..7d2240d45bb8 --- /dev/null +++ b/plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/_delta_log/00000000000000000002.json @@ -0,0 +1,2 @@ +{"commitInfo":{"timestamp":1701257543063,"userId":"615774135840106","userName":"marius.grama@starburstdata.com","operation":"WRITE","operationParameters":{"mode":"Append","statsOnLoad":false,"partitionBy":"[]"},"notebook":{"notebookId":"3966548513639247"},"clusterId":"0905-151610-v55wl6f5","readVersion":1,"isolationLevel":"WriteSerializable","isBlindAppend":true,"operationMetrics":{"numFiles":"1","numOutputRows":"1","numOutputBytes":"574"},"tags":{"restoresDeletedRows":"false"},"engineInfo":"Databricks-Runtime/13.3.x-scala2.12","txnId":"963d3ee2-c210-499c-b15d-e982b2b3f2a6"}} +{"add":{"path":"part_NuMbEr=2/part_StRiNg=kota/part-00000-7a66d694-3b49-4588-97f8-e219a1c20133.c000.snappy.parquet","partitionValues":{"part_NuMbEr":"2","part_StRiNg":"kota"},"size":574,"modificationTime":1701257543000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"id\":200},\"maxValues\":{\"id\":200},\"nullCount\":{\"id\":0}}","tags":{"INSERTION_TIME":"1701257543000000","MIN_INSERTION_TIME":"1701257543000000","MAX_INSERTION_TIME":"1701257543000000","OPTIMIZE_TARGET_SIZE":"268435456"}}} diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/_delta_log/00000000000000000003.json b/plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/_delta_log/00000000000000000003.json new file mode 100644 index 000000000000..3502a364f03b --- /dev/null +++ b/plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/_delta_log/00000000000000000003.json @@ -0,0 +1,2 @@ +{"commitInfo":{"timestamp":1701257547498,"userId":"615774135840106","userName":"marius.grama@starburstdata.com","operation":"WRITE","operationParameters":{"mode":"Append","statsOnLoad":false,"partitionBy":"[]"},"notebook":{"notebookId":"3966548513639247"},"clusterId":"0905-151610-v55wl6f5","readVersion":2,"isolationLevel":"WriteSerializable","isBlindAppend":true,"operationMetrics":{"numFiles":"1","numOutputRows":"1","numOutputBytes":"574"},"tags":{"restoresDeletedRows":"false"},"engineInfo":"Databricks-Runtime/13.3.x-scala2.12","txnId":"72257c1c-34ad-4df2-8d4b-14bfdb496356"}} +{"add":{"path":"part_NuMbEr=3/part_StRiNg=osla/part-00000-51bd41c3-6a8e-4e97-ae34-1ae6ade088d2.c000.snappy.parquet","partitionValues":{"part_NuMbEr":"3","part_StRiNg":"osla"},"size":574,"modificationTime":1701257548000,"dataChange":true,"stats":"{\"numRecords\":1,\"minValues\":{\"id\":300},\"maxValues\":{\"id\":300},\"nullCount\":{\"id\":0}}","tags":{"INSERTION_TIME":"1701257548000000","MIN_INSERTION_TIME":"1701257548000000","MAX_INSERTION_TIME":"1701257548000000","OPTIMIZE_TARGET_SIZE":"268435456"}}} diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/_delta_log/_last_checkpoint b/plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/_delta_log/_last_checkpoint new file mode 100644 index 000000000000..5444b9bd88a5 --- /dev/null +++ b/plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/_delta_log/_last_checkpoint @@ -0,0 +1 @@ +{"version":2,"size":4,"sizeInBytes":19390,"numOfAddFiles":2,"checkpointSchema":{"type":"struct","fields":[{"name":"txn","type":{"type":"struct","fields":[{"name":"appId","type":"string","nullable":true,"metadata":{}},{"name":"version","type":"long","nullable":true,"metadata":{}},{"name":"lastUpdated","type":"long","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"add","type":{"type":"struct","fields":[{"name":"path","type":"string","nullable":true,"metadata":{}},{"name":"partitionValues","type":{"type":"map","keyType":"string","valueType":"string","valueContainsNull":true},"nullable":true,"metadata":{}},{"name":"size","type":"long","nullable":true,"metadata":{}},{"name":"modificationTime","type":"long","nullable":true,"metadata":{}},{"name":"dataChange","type":"boolean","nullable":true,"metadata":{}},{"name":"tags","type":{"type":"map","keyType":"string","valueType":"string","valueContainsNull":true},"nullable":true,"metadata":{}},{"name":"deletionVector","type":{"type":"struct","fields":[{"name":"storageType","type":"string","nullable":true,"metadata":{}},{"name":"pathOrInlineDv","type":"string","nullable":true,"metadata":{}},{"name":"offset","type":"integer","nullable":true,"metadata":{}},{"name":"sizeInBytes","type":"integer","nullable":true,"metadata":{}},{"name":"cardinality","type":"long","nullable":true,"metadata":{}},{"name":"maxRowIndex","type":"long","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"baseRowId","type":"long","nullable":true,"metadata":{}},{"name":"defaultRowCommitVersion","type":"long","nullable":true,"metadata":{}},{"name":"stats","type":"string","nullable":true,"metadata":{}},{"name":"partitionValues_parsed","type":{"type":"struct","fields":[{"name":"part_NuMbEr","type":"integer","nullable":true,"metadata":{}},{"name":"part_StRiNg","type":"string","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"stats_parsed","type":{"type":"struct","fields":[{"name":"numRecords","type":"long","nullable":true,"metadata":{}},{"name":"minValues","type":{"type":"struct","fields":[{"name":"id","type":"integer","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"maxValues","type":{"type":"struct","fields":[{"name":"id","type":"integer","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"nullCount","type":{"type":"struct","fields":[{"name":"id","type":"long","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"remove","type":{"type":"struct","fields":[{"name":"path","type":"string","nullable":true,"metadata":{}},{"name":"deletionTimestamp","type":"long","nullable":true,"metadata":{}},{"name":"dataChange","type":"boolean","nullable":true,"metadata":{}},{"name":"extendedFileMetadata","type":"boolean","nullable":true,"metadata":{}},{"name":"partitionValues","type":{"type":"map","keyType":"string","valueType":"string","valueContainsNull":true},"nullable":true,"metadata":{}},{"name":"size","type":"long","nullable":true,"metadata":{}},{"name":"deletionVector","type":{"type":"struct","fields":[{"name":"storageType","type":"string","nullable":true,"metadata":{}},{"name":"pathOrInlineDv","type":"string","nullable":true,"metadata":{}},{"name":"offset","type":"integer","nullable":true,"metadata":{}},{"name":"sizeInBytes","type":"integer","nullable":true,"metadata":{}},{"name":"cardinality","type":"long","nullable":true,"metadata":{}},{"name":"maxRowIndex","type":"long","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"baseRowId","type":"long","nullable":true,"metadata":{}},{"name":"defaultRowCommitVersion","type":"long","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"metaData","type":{"type":"struct","fields":[{"name":"id","type":"string","nullable":true,"metadata":{}},{"name":"name","type":"string","nullable":true,"metadata":{}},{"name":"description","type":"string","nullable":true,"metadata":{}},{"name":"format","type":{"type":"struct","fields":[{"name":"provider","type":"string","nullable":true,"metadata":{}},{"name":"options","type":{"type":"map","keyType":"string","valueType":"string","valueContainsNull":true},"nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"schemaString","type":"string","nullable":true,"metadata":{}},{"name":"partitionColumns","type":{"type":"array","elementType":"string","containsNull":true},"nullable":true,"metadata":{}},{"name":"configuration","type":{"type":"map","keyType":"string","valueType":"string","valueContainsNull":true},"nullable":true,"metadata":{}},{"name":"createdTime","type":"long","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"protocol","type":{"type":"struct","fields":[{"name":"minReaderVersion","type":"integer","nullable":true,"metadata":{}},{"name":"minWriterVersion","type":"integer","nullable":true,"metadata":{}},{"name":"readerFeatures","type":{"type":"array","elementType":"string","containsNull":true},"nullable":true,"metadata":{}},{"name":"writerFeatures","type":{"type":"array","elementType":"string","containsNull":true},"nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}},{"name":"domainMetadata","type":{"type":"struct","fields":[{"name":"domain","type":"string","nullable":true,"metadata":{}},{"name":"configuration","type":"string","nullable":true,"metadata":{}},{"name":"removed","type":"boolean","nullable":true,"metadata":{}}]},"nullable":true,"metadata":{}}]},"checksum":"8966215d3d1ef7810ee4a116a2615d57"} diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/part_NuMbEr=1/part_StRiNg=ala/part-00000-da29c0d3-b5d6-425c-9f33-1296f2b97058.c000.snappy.parquet b/plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/part_NuMbEr=1/part_StRiNg=ala/part-00000-da29c0d3-b5d6-425c-9f33-1296f2b97058.c000.snappy.parquet new file mode 100644 index 0000000000000000000000000000000000000000..a7173967619e7db865607e9cbd443dba6c85bb06 GIT binary patch literal 574 zcmZWn!EVz)5S?zJSXD1b+10LOsgXsaR!WxGNn0Fop+E$eszTvF0im@$Nv+yWYHvVQ z$d~W|J@aGz1}1Sh+cuT=IuMkAL@F&Zyku4#55k1^~M|2}Sjs zDfwib)dy!v5bO$v4=Dkv+aC_9nJI67RPhbwhBE?>m}F@Jf+<(IEr+J7QwR3c3gt{N zaUq5+=XE`^jslL?i|UG2kRF-@Ab#_Pxa1nBiM1RtY>=4f>} z{iSoXCNb8?FALA!&(Irql`ZG7_1fMz#Vo13!PkXXn0$4WCIoM2X<>1KB>}}e%S@CJ zfjY76{h#R#7R%-LCCI}q8MTvl#^Q*KugqL;RCV&Qxv|mowIAqSXQeMH)A+XPbi3W4 zc^u8tENKS)SA7$nCK38)hVUNtFbcvr==$R@9FIdZ=jT4+iE>?f?Z+K}EBZPA0Jjj5 AQUCw| literal 0 HcmV?d00001 diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/part_NuMbEr=2/part_StRiNg=kota/part-00000-7a66d694-3b49-4588-97f8-e219a1c20133.c000.snappy.parquet b/plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/part_NuMbEr=2/part_StRiNg=kota/part-00000-7a66d694-3b49-4588-97f8-e219a1c20133.c000.snappy.parquet new file mode 100644 index 0000000000000000000000000000000000000000..7939b1c579bd258e3da48091de1a8cb999990af9 GIT binary patch literal 574 zcmZWnO>fgc5S?zJSQQtf>}prC)X0LUm69cP(ho;m_=@0CRVW-NAhfn8!K&?~_NJ%` zIaK{~i2uM};5RUd!zq{9nYV9d-qY^I%MU&W-r$n|xx4?p?{Y@v9(MrtpE3a0-T6gP zJz+{dSZDRYnGyuM!r?TGf0=`H!EhpN{3@z2`{o1BE zUQP>?kKWq&`Nz>~knR>K!-QC(-63}4UD z>T>!+=V(o0tdXAW_JQh9^V3$HNw>MBhL-q6y*;si?qig}iq zC?f)OV%z&a(;F<7%dbn22U{{~CvS|!5gA{Zx!kDglu(7nz|UsR^?Rn_Ts zyFv3Xnx|RP4Ekq%6CNiK`X`3)9`-N_!Z_&q<1ieLLo}zSKH`ybU3%@mI{+8-bN&HK C7L?ck literal 0 HcmV?d00001 diff --git a/plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/part_NuMbEr=3/part_StRiNg=osla/part-00000-51bd41c3-6a8e-4e97-ae34-1ae6ade088d2.c000.snappy.parquet b/plugin/trino-delta-lake/src/test/resources/databricks133/partition_values_parsed_case_sensitive/part_NuMbEr=3/part_StRiNg=osla/part-00000-51bd41c3-6a8e-4e97-ae34-1ae6ade088d2.c000.snappy.parquet new file mode 100644 index 0000000000000000000000000000000000000000..fbc7aba1a8f13e6f1a630ff8c03f69a0486934ce GIT binary patch literal 574 zcmZWn!EVz)5S?tHSQQtf>}oApB3ePzO34yCX^SH+6o}wbRVW-NAhfn8!K&?~_NJ%` z`M!Ql5nq6q#Nm|7?#$aaGw*5V;`K+L18;K4@BjS0Yj~Vdy3ZYegJ%o?_8OfAfcu2G z^1&vmFV0*+uqPZoq5vrGynkHHO?mabihp2kI3w^mlPpa@FxPXvwnN?BV+Z!#HOiS_ z;!KR%&a1j-eF`|-45~94L3*eYfcV9m;+$)sBGz`qs7V68N_SRHwV4|lsyFksO>?}Q z6(}FQv+;{h<2N9^9a4r<6W@L~ABQIrK*}A$4VG0pE9Aqz9%vur(GH`!&NLamUZBW}=J; zREZt+zo!~5SF3L;kWY7H)K1P+)<(-B=x zVYEoIq!kQa4orBQMChLw!u#0AC expectedRows = ImmutableList.of(firstRow, secondRow); + assertThat(onDelta().executeQuery(selectValues)).containsOnly(expectedRows); + // Make sure that the checkpoint is being processed + onTrino().executeQuery("CALL delta.system.flush_metadata_cache(schema_name => 'default', table_name => '" + tableName + "')"); + assertThat(onTrino().executeQuery(selectValues)).containsOnly(expectedRows); + QueryResult selectSparkTimestamps = onDelta().executeQuery("SELECT date_format(part_timestamp, \"yyyy-MM-dd HH:mm:ss.SSS\") FROM default." + tableName); + QueryResult selectTrinoTimestamps = onTrino().executeQuery("SELECT format_datetime(part_timestamp, 'yyyy-MM-dd HH:mm:ss.SSS') FROM delta.default." + tableName); + assertThat(selectSparkTimestamps).containsOnly(selectTrinoTimestamps.rows().stream() + .map(Row::new) + .collect(toImmutableList())); + } + finally { + dropDeltaTableWithRetry("default." + tableName); + } + } + @Test(groups = {DELTA_LAKE_OSS, PROFILE_SPECIFIC_TESTS}) public void testTrinoUsesCheckpointInterval() { From 312980d2b3ef9af5a9635a7de3d17f6433962fb8 Mon Sep 17 00:00:00 2001 From: Marius Grama Date: Mon, 20 Nov 2023 13:39:51 +0100 Subject: [PATCH 548/587] Test partition pruning in CheckpointEntryIterator --- .../TestCheckpointEntryIterator.java | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointEntryIterator.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointEntryIterator.java index 8cf38548491b..2a8d84a65b73 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointEntryIterator.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/transactionlog/checkpoint/TestCheckpointEntryIterator.java @@ -34,7 +34,9 @@ import io.trino.plugin.deltalake.transactionlog.statistics.DeltaLakeParquetFileStatistics; import io.trino.plugin.hive.FileFormatDataSourceStats; import io.trino.plugin.hive.parquet.ParquetReaderConfig; +import io.trino.spi.predicate.Domain; import io.trino.spi.predicate.TupleDomain; +import io.trino.spi.predicate.ValueSet; import io.trino.spi.type.Int128; import io.trino.spi.type.Type; import org.junit.jupiter.api.AfterAll; @@ -81,6 +83,7 @@ import static io.trino.spi.predicate.Domain.notNull; import static io.trino.spi.predicate.Domain.onlyNull; import static io.trino.spi.predicate.Domain.singleValue; +import static io.trino.spi.predicate.Range.range; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.BooleanType.BOOLEAN; import static io.trino.spi.type.DateTimeEncoding.packDateTimeWithZone; @@ -616,6 +619,127 @@ public void testReadAllEntries() .isEmpty(); } + @Test + public void testSkipAddEntriesThroughPartitionPruning() + throws IOException + { + MetadataEntry metadataEntry = new MetadataEntry( + "metadataId", + "metadataName", + "metadataDescription", + new MetadataEntry.Format( + "metadataFormatProvider", + ImmutableMap.of()), + "{\"type\":\"struct\",\"fields\":" + + "[{\"name\":\"ts\",\"type\":\"timestamp\",\"nullable\":true,\"metadata\":{}}," + + "{\"name\":\"part_key\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}}]}", + ImmutableList.of("part_key"), + ImmutableMap.of(), + 1000); + ProtocolEntry protocolEntry = new ProtocolEntry(1, 1, Optional.empty(), Optional.empty()); + int numAddEntries = 100; + Set addFileEntries = IntStream.rangeClosed(1, numAddEntries).mapToObj(value -> + new AddFileEntry( + "addFilePath", + ImmutableMap.of("part_key", Integer.toString(value)), + 1000, + 1001, + true, + Optional.of("{" + + "\"numRecords\":20," + + "\"minValues\":{" + + "\"ts\":\"1990-10-31T01:00:00.000Z\"" + + "}," + + "\"maxValues\":{" + + "\"ts\":\"1990-10-31T02:00:00.000Z\"" + + "}," + + "\"nullCount\":{" + + "\"ts\":1" + + "}}"), + Optional.empty(), + ImmutableMap.of(), + Optional.empty())) + .collect(toImmutableSet()); + + CheckpointEntries entries = new CheckpointEntries( + metadataEntry, + protocolEntry, + ImmutableSet.of(), + addFileEntries, + ImmutableSet.of()); + + CheckpointWriter writer = new CheckpointWriter( + TESTING_TYPE_MANAGER, + checkpointSchemaManager, + "test", + ParquetWriterOptions.builder() // approximately 2 rows per row group + .setMaxBlockSize(DataSize.ofBytes(64L)) + .setMaxPageSize(DataSize.ofBytes(64L)) + .build()); + + File targetFile = File.createTempFile("testSkipAddEntries-", ".checkpoint.parquet"); + targetFile.deleteOnExit(); + + String targetPath = "file://" + targetFile.getAbsolutePath(); + targetFile.delete(); // file must not exist when writer is called + writer.write(entries, createOutputFile(targetPath)); + + CheckpointEntryIterator metadataAndProtocolEntryIterator = createCheckpointEntryIterator( + URI.create(targetPath), + ImmutableSet.of(METADATA, PROTOCOL), + Optional.empty(), + Optional.empty(), + TupleDomain.all(), + Optional.empty()); + + DeltaLakeColumnHandle partitionKeyField = new DeltaLakeColumnHandle( + "part_key", + INTEGER, + OptionalInt.empty(), + "part_key", + INTEGER, + REGULAR, + Optional.empty()); + + CheckpointEntryIterator addEntryIterator = createCheckpointEntryIterator( + URI.create(targetPath), + ImmutableSet.of(ADD), + Optional.of(metadataEntry), + Optional.of(protocolEntry), + TupleDomain.all(), + Optional.of(alwaysTrue())); + + CheckpointEntryIterator addEntryIteratorWithEqualityPartitionFilter = createCheckpointEntryIterator( + URI.create(targetPath), + ImmutableSet.of(ADD), + Optional.of(metadataEntry), + Optional.of(protocolEntry), + TupleDomain.withColumnDomains(ImmutableMap.of(partitionKeyField, singleValue(INTEGER, 10L))), + Optional.of(alwaysTrue())); + + CheckpointEntryIterator addEntryIteratorWithRangePartitionFilter = createCheckpointEntryIterator( + URI.create(targetPath), + ImmutableSet.of(ADD), + Optional.of(metadataEntry), + Optional.of(protocolEntry), + TupleDomain.withColumnDomains(ImmutableMap.of( + partitionKeyField, + Domain.create(ValueSet.ofRanges(range(INTEGER, 9L, true, 11L, true)), false))), + Optional.of(alwaysTrue())); + + assertThat(Iterators.size(metadataAndProtocolEntryIterator)).isEqualTo(2); + assertThat(metadataAndProtocolEntryIterator.getCompletedPositions().orElseThrow()).isEqualTo(3L); + + assertThat(Iterators.size(addEntryIterator)).isEqualTo(numAddEntries); + assertThat(addEntryIterator.getCompletedPositions().orElseThrow()).isEqualTo(101L); + + assertThat(Iterators.size(addEntryIteratorWithEqualityPartitionFilter)).isEqualTo(1); + assertThat(addEntryIteratorWithEqualityPartitionFilter.getCompletedPositions().orElseThrow()).isEqualTo(1L); + + assertThat(Iterators.size(addEntryIteratorWithRangePartitionFilter)).isEqualTo(3); + assertThat(addEntryIteratorWithRangePartitionFilter.getCompletedPositions().orElseThrow()).isEqualTo(3L); + } + @Test public void testSkipRemoveEntries() throws IOException From 585cd4f330ec3362a241df9f4729a5996db31b0f Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 29 Nov 2023 15:14:56 +0100 Subject: [PATCH 549/587] Seal TableScanNode API Remove public constructor that is not supposed to be called and replace it with a JSON-serialization-only factory method. Factory method access can be controlled with `@DoNotCall`. --- .../trino/sql/planner/plan/TableScanNode.java | 26 ++++++++++++++----- .../TestMultiSourcePartitionedScheduler.java | 4 +-- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/plan/TableScanNode.java b/core/trino-main/src/main/java/io/trino/sql/planner/plan/TableScanNode.java index 0fa645de6762..88e25a45b927 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/plan/TableScanNode.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/plan/TableScanNode.java @@ -19,6 +19,7 @@ import com.google.common.base.VerifyException; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.DoNotCall; import com.google.errorprone.annotations.Immutable; import io.trino.cost.PlanNodeStatsEstimate; import io.trino.metadata.TableHandle; @@ -75,19 +76,32 @@ public static TableScanNode newInstance( return new TableScanNode(id, table, outputs, assignments, TupleDomain.all(), Optional.empty(), updateTarget, useConnectorNodePartitioning); } - /* - * This constructor is for JSON deserialization only. Do not use. - * It's marked as @Deprecated to help avoid usage, and not because we plan to remove it. - */ - /* TODO @DoNotCall once it's applicable to constructors */ + @DoNotCall // For JSON serialization only @JsonCreator - public TableScanNode( + public static TableScanNode fromJson( @JsonProperty("id") PlanNodeId id, @JsonProperty("table") TableHandle table, @JsonProperty("outputSymbols") List outputs, @JsonProperty("assignments") Map assignments, @JsonProperty("updateTarget") boolean updateTarget, @JsonProperty("useConnectorNodePartitioning") Optional useConnectorNodePartitioning) + { + return new TableScanNode( + id, + table, + outputs, + assignments, + updateTarget, + useConnectorNodePartitioning); + } + + private TableScanNode( + PlanNodeId id, + TableHandle table, + List outputs, + Map assignments, + boolean updateTarget, + Optional useConnectorNodePartitioning) { super(id); this.table = requireNonNull(table, "table is null"); diff --git a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestMultiSourcePartitionedScheduler.java b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestMultiSourcePartitionedScheduler.java index 749be8e011b4..884cf4ac8636 100644 --- a/core/trino-main/src/test/java/io/trino/execution/scheduler/TestMultiSourcePartitionedScheduler.java +++ b/core/trino-main/src/test/java/io/trino/execution/scheduler/TestMultiSourcePartitionedScheduler.java @@ -523,7 +523,7 @@ private PlanFragment createFragment(TableHandle firstTableHandle, TableHandle se Symbol symbol = new Symbol("column"); Symbol buildSymbol = new Symbol("buildColumn"); - TableScanNode tableScanOne = new TableScanNode( + TableScanNode tableScanOne = TableScanNode.newInstance( TABLE_SCAN_1_NODE_ID, firstTableHandle, ImmutableList.of(symbol), @@ -534,7 +534,7 @@ private PlanFragment createFragment(TableHandle firstTableHandle, TableHandle se new PlanNodeId("filter_node_id"), tableScanOne, createDynamicFilterExpression(createTestMetadataManager(), DYNAMIC_FILTER_ID, VARCHAR, symbol.toSymbolReference())); - TableScanNode tableScanTwo = new TableScanNode( + TableScanNode tableScanTwo = TableScanNode.newInstance( TABLE_SCAN_2_NODE_ID, secondTableHandle, ImmutableList.of(symbol), From 02ce7e6b253f2a35f2a8272d60b032c7806b7729 Mon Sep 17 00:00:00 2001 From: Marius Grama Date: Thu, 30 Nov 2023 09:21:58 +0100 Subject: [PATCH 550/587] Enhance description for `delta.checkpoint-filtering.enabled` --- docs/src/main/sphinx/connector/delta-lake.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/src/main/sphinx/connector/delta-lake.md b/docs/src/main/sphinx/connector/delta-lake.md index 3661c98a75ca..d9c86b6c2452 100644 --- a/docs/src/main/sphinx/connector/delta-lake.md +++ b/docs/src/main/sphinx/connector/delta-lake.md @@ -125,7 +125,9 @@ values. Typical usage does not require you to configure them. - Enable writing row statistics to checkpoint files. - `true` * - ``delta.checkpoint-filtering.enabled`` - - Enable partition pruning when reading checkpoint files. + - Enable pruning of data file entries as well as data file statistics + columns which are irrelevant for the query when reading Delta Lake + checkpoint files. The equivalent catalog session property is ``checkpoint_filtering_enabled``. - ``false`` * - `delta.dynamic-filtering.wait-timeout` From 7c28ebb0a129852be3a90f91a1fef71a3cc0ee92 Mon Sep 17 00:00:00 2001 From: Dominik Zalewski Date: Thu, 30 Nov 2023 08:38:12 +0100 Subject: [PATCH 551/587] Add shutdown to EventListener --- .../eventlistener/EventListenerManager.java | 14 ++++++ .../TestEventListenerManager.java | 45 +++++++++++++++++++ .../spi/eventlistener/EventListener.java | 9 ++++ .../ClassLoaderSafeEventListener.java | 8 ++++ 4 files changed, 76 insertions(+) create mode 100644 core/trino-main/src/test/java/io/trino/eventlistener/TestEventListenerManager.java diff --git a/core/trino-main/src/main/java/io/trino/eventlistener/EventListenerManager.java b/core/trino-main/src/main/java/io/trino/eventlistener/EventListenerManager.java index 8f50f8b8fdad..a232c502a850 100644 --- a/core/trino-main/src/main/java/io/trino/eventlistener/EventListenerManager.java +++ b/core/trino-main/src/main/java/io/trino/eventlistener/EventListenerManager.java @@ -24,6 +24,7 @@ import io.trino.spi.eventlistener.QueryCompletedEvent; import io.trino.spi.eventlistener.QueryCreatedEvent; import io.trino.spi.eventlistener.SplitCompletedEvent; +import jakarta.annotation.PreDestroy; import org.weakref.jmx.Managed; import org.weakref.jmx.Nested; @@ -218,4 +219,17 @@ public TimeStat getSplitCompletedTime() { return splitCompletedTime; } + + @PreDestroy + public void shutdown() + { + for (EventListener listener : configuredEventListeners.getAndSet(List.of())) { + try { + listener.shutdown(); + } + catch (Throwable e) { + log.warn(e, "Failed to shutdown event listener: " + listener.getClass().getCanonicalName()); + } + } + } } diff --git a/core/trino-main/src/test/java/io/trino/eventlistener/TestEventListenerManager.java b/core/trino-main/src/test/java/io/trino/eventlistener/TestEventListenerManager.java new file mode 100644 index 000000000000..d31353539930 --- /dev/null +++ b/core/trino-main/src/test/java/io/trino/eventlistener/TestEventListenerManager.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 io.trino.eventlistener; + +import io.trino.spi.eventlistener.EventListener; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.assertj.core.api.Assertions.assertThat; + +class TestEventListenerManager +{ + @Test + public void testShutdownIsForwardedToListeners() + { + EventListenerManager eventListenerManager = new EventListenerManager(new EventListenerConfig()); + AtomicBoolean wasCalled = new AtomicBoolean(false); + EventListener listener = new EventListener() + { + @Override + public void shutdown() + { + wasCalled.set(true); + } + }; + + eventListenerManager.addEventListener(listener); + eventListenerManager.loadEventListeners(); + eventListenerManager.shutdown(); + + assertThat(wasCalled.get()).isTrue(); + } +} diff --git a/core/trino-spi/src/main/java/io/trino/spi/eventlistener/EventListener.java b/core/trino-spi/src/main/java/io/trino/spi/eventlistener/EventListener.java index 78cfe7f08f0f..9044f75ed5dc 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/eventlistener/EventListener.java +++ b/core/trino-spi/src/main/java/io/trino/spi/eventlistener/EventListener.java @@ -34,4 +34,13 @@ default boolean requiresAnonymizedPlan() { return false; } + + /** + * Shutdown the event listener by releasing any held resources such as + * threads, sockets, etc. After this method is called, + * no methods will be called on the event listener. + */ + default void shutdown() + { + } } diff --git a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeEventListener.java b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeEventListener.java index b507dc40be11..df905fb57de5 100644 --- a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeEventListener.java +++ b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeEventListener.java @@ -66,4 +66,12 @@ public boolean requiresAnonymizedPlan() return delegate.requiresAnonymizedPlan(); } } + + @Override + public void shutdown() + { + try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(classLoader)) { + delegate.shutdown(); + } + } } From ad659bf146b2c460b93c147706e38231957b2c43 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Wed, 29 Nov 2023 23:52:36 -0800 Subject: [PATCH 552/587] Fix Hive properties table test --- .../io/trino/tests/product/hive/TestHivePropertiesTable.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHivePropertiesTable.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHivePropertiesTable.java index 5febb4c55440..b9e6a5492173 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHivePropertiesTable.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHivePropertiesTable.java @@ -39,7 +39,7 @@ public void testTrinoViewPropertiesTable() onTrino().executeQuery("CREATE VIEW test_trino_view_properties AS SELECT * FROM test_trino_view_properties_base"); assertThat(onTrino().executeQuery("SHOW COLUMNS FROM \"test_trino_view_properties$properties\"")) - .containsExactlyInOrder( + .containsOnly( row("comment", "varchar", "", ""), row("presto_query_id", "varchar", "", ""), row("presto_version", "varchar", "", ""), From f84f9e80e0ed668e23659281bf3c64eb55821bb1 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Wed, 29 Nov 2023 19:02:03 -0800 Subject: [PATCH 553/587] Rename presto_version table property to trino_version --- docs/src/main/sphinx/connector/hive.md | 6 +++--- .../io/trino/plugin/hive/HiveMetadata.java | 18 +++++++++--------- .../plugin/hive/RcFileFileWriterFactory.java | 4 ++-- .../io/trino/plugin/hive/TrinoViewUtil.java | 4 ++-- .../hive/avro/AvroFileWriterFactory.java | 4 ++-- .../plugin/hive/orc/OrcFileWriterFactory.java | 4 ++-- .../io/trino/plugin/hive/AbstractTestHive.java | 18 +++++++++--------- .../plugin/hive/AbstractTestHiveLocal.java | 4 ++-- .../plugin/hive/BaseHiveConnectorTest.java | 2 +- .../iceberg/IcebergFileWriterFactory.java | 4 ++-- .../product/hive/TestHivePropertiesTable.java | 2 +- 11 files changed, 35 insertions(+), 35 deletions(-) diff --git a/docs/src/main/sphinx/connector/hive.md b/docs/src/main/sphinx/connector/hive.md index 32bc0490df4d..9dfa4352e648 100644 --- a/docs/src/main/sphinx/connector/hive.md +++ b/docs/src/main/sphinx/connector/hive.md @@ -909,9 +909,9 @@ SELECT * FROM example.web."page_views$properties"; ``` ```text - stats_generated_via_stats_task | auto.purge | presto_query_id | presto_version | transactional ----------------------------------------------+------------+-----------------------------+----------------+--------------- - workaround for potential lack of HIVE-12730 | false | 20230705_152456_00001_nfugi | 423 | false + stats_generated_via_stats_task | auto.purge | presto_query_id | trino_version | transactional +---------------------------------------------+------------+-----------------------------+---------------+--------------- + workaround for potential lack of HIVE-12730 | false | 20230705_152456_00001_nfugi | 434 | false ``` ##### `$partitions` table diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java index 596550932425..8b12ed0499c1 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java @@ -332,7 +332,7 @@ public class HiveMetadata { private static final Logger log = Logger.get(HiveMetadata.class); - public static final String PRESTO_VERSION_NAME = "presto_version"; + public static final String TRINO_VERSION_NAME = "trino_version"; public static final String TRINO_CREATED_BY = "trino_created_by"; public static final String PRESTO_QUERY_ID_NAME = "presto_query_id"; public static final String BUCKETING_VERSION = "bucketing_version"; @@ -381,7 +381,7 @@ public class HiveMetadata private final boolean translateHiveViews; private final boolean hiveViewsRunAsInvoker; private final boolean hideDeltaLakeTables; - private final String prestoVersion; + private final String trinoVersion; private final HiveStatisticsProvider hiveStatisticsProvider; private final HiveRedirectionsProvider hiveRedirectionsProvider; private final Set systemTableProviders; @@ -436,7 +436,7 @@ public HiveMetadata( this.translateHiveViews = translateHiveViews; this.hiveViewsRunAsInvoker = hiveViewsRunAsInvoker; this.hideDeltaLakeTables = hideDeltaLakeTables; - this.prestoVersion = requireNonNull(trinoVersion, "trinoVersion is null"); + this.trinoVersion = requireNonNull(trinoVersion, "trinoVersion is null"); this.hiveStatisticsProvider = requireNonNull(hiveStatisticsProvider, "hiveStatisticsProvider is null"); this.hiveRedirectionsProvider = requireNonNull(hiveRedirectionsProvider, "hiveRedirectionsProvider is null"); this.systemTableProviders = requireNonNull(systemTableProviders, "systemTableProviders is null"); @@ -1105,7 +1105,7 @@ public void createTable(ConnectorSession session, ConnectorTableMetadata tableMe tableProperties, targetPath, external, - prestoVersion, + trinoVersion, accessControlMetadata.isUsingSystemSecurity()); PrincipalPrivileges principalPrivileges = accessControlMetadata.isUsingSystemSecurity() ? NO_PRIVILEGES : buildInitialPrivilegeSet(session.getUser()); HiveBasicStatistics basicStatistics = (!external && table.getPartitionColumns().isEmpty()) ? createZeroStatistics() : createEmptyStatistics(); @@ -1412,7 +1412,7 @@ private static Table buildTableObject( Map additionalTableParameters, Optional targetPath, boolean external, - String prestoVersion, + String trinoVersion, boolean usingSystemSecurity) { Map columnHandlesByName = Maps.uniqueIndex(columnHandles, HiveColumnHandle::getName); @@ -1437,7 +1437,7 @@ private static Table buildTableObject( } ImmutableMap.Builder tableParameters = ImmutableMap.builder() - .put(PRESTO_VERSION_NAME, prestoVersion) + .put(TRINO_VERSION_NAME, trinoVersion) .put(PRESTO_QUERY_ID_NAME, queryId) .putAll(additionalTableParameters); @@ -1827,7 +1827,7 @@ public Optional finishCreateTable(ConnectorSession sess handle.getAdditionalTableParameters(), Optional.of(writeInfo.targetPath()), handle.isExternal(), - prestoVersion, + trinoVersion, accessControlMetadata.isUsingSystemSecurity()); PrincipalPrivileges principalPrivileges = accessControlMetadata.isUsingSystemSecurity() ? NO_PRIVILEGES : buildInitialPrivilegeSet(handle.getTableOwner()); @@ -2375,7 +2375,7 @@ private Partition buildPartitionObject(ConnectorSession session, Table table, Pa .setColumns(table.getDataColumns()) .setValues(extractPartitionValues(partitionUpdate.getName())) .setParameters(ImmutableMap.builder() - .put(PRESTO_VERSION_NAME, prestoVersion) + .put(TRINO_VERSION_NAME, trinoVersion) .put(PRESTO_QUERY_ID_NAME, session.getQueryId()) .buildOrThrow()) .withStorage(storage -> storage @@ -2691,7 +2691,7 @@ public void createView(ConnectorSession session, SchemaTableName viewName, Conne .put(TABLE_COMMENT, PRESTO_VIEW_COMMENT) .put(PRESTO_VIEW_FLAG, "true") .put(TRINO_CREATED_BY, "Trino Hive connector") - .put(PRESTO_VERSION_NAME, prestoVersion) + .put(TRINO_VERSION_NAME, trinoVersion) .put(PRESTO_QUERY_ID_NAME, session.getQueryId()) .buildOrThrow(); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/RcFileFileWriterFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/RcFileFileWriterFactory.java index d35a6a863807..d7c6c2a13641 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/RcFileFileWriterFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/RcFileFileWriterFactory.java @@ -43,7 +43,7 @@ import static io.trino.memory.context.AggregatedMemoryContext.newSimpleAggregatedMemoryContext; import static io.trino.plugin.hive.HiveErrorCode.HIVE_WRITER_OPEN_ERROR; import static io.trino.plugin.hive.HiveMetadata.PRESTO_QUERY_ID_NAME; -import static io.trino.plugin.hive.HiveMetadata.PRESTO_VERSION_NAME; +import static io.trino.plugin.hive.HiveMetadata.TRINO_VERSION_NAME; import static io.trino.plugin.hive.HiveSessionProperties.getTimestampPrecision; import static io.trino.plugin.hive.HiveSessionProperties.isRcfileOptimizedWriterValidate; import static io.trino.plugin.hive.util.HiveClassNames.COLUMNAR_SERDE_CLASS; @@ -144,7 +144,7 @@ else if (COLUMNAR_SERDE_CLASS.equals(storageFormat.getSerde())) { compressionCodec.getHiveCompressionKind(), fileInputColumnIndexes, ImmutableMap.builder() - .put(PRESTO_VERSION_NAME, nodeVersion.toString()) + .put(TRINO_VERSION_NAME, nodeVersion.toString()) .put(PRESTO_QUERY_ID_NAME, session.getQueryId()) .buildOrThrow(), validationInputFactory)); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/TrinoViewUtil.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/TrinoViewUtil.java index 1ae2b734c609..b3153371386e 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/TrinoViewUtil.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/TrinoViewUtil.java @@ -22,10 +22,10 @@ import static com.google.common.base.Preconditions.checkArgument; import static io.trino.plugin.hive.HiveMetadata.PRESTO_QUERY_ID_NAME; -import static io.trino.plugin.hive.HiveMetadata.PRESTO_VERSION_NAME; import static io.trino.plugin.hive.HiveMetadata.PRESTO_VIEW_COMMENT; import static io.trino.plugin.hive.HiveMetadata.TABLE_COMMENT; import static io.trino.plugin.hive.HiveMetadata.TRINO_CREATED_BY; +import static io.trino.plugin.hive.HiveMetadata.TRINO_VERSION_NAME; import static io.trino.plugin.hive.ViewReaderUtil.PRESTO_VIEW_FLAG; import static io.trino.plugin.hive.ViewReaderUtil.isTrinoView; @@ -66,7 +66,7 @@ public static Map createViewProperties(ConnectorSession session, return ImmutableMap.builder() .put(PRESTO_VIEW_FLAG, "true") .put(TRINO_CREATED_BY, connectorName) - .put(PRESTO_VERSION_NAME, trinoVersion) + .put(TRINO_VERSION_NAME, trinoVersion) .put(PRESTO_QUERY_ID_NAME, session.getQueryId()) .put(TABLE_COMMENT, PRESTO_VIEW_COMMENT) .buildOrThrow(); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroFileWriterFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroFileWriterFactory.java index 53ff9b636361..87b81e66e28e 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroFileWriterFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroFileWriterFactory.java @@ -47,7 +47,7 @@ import static io.trino.memory.context.AggregatedMemoryContext.newSimpleAggregatedMemoryContext; import static io.trino.plugin.hive.HiveErrorCode.HIVE_WRITER_OPEN_ERROR; import static io.trino.plugin.hive.HiveMetadata.PRESTO_QUERY_ID_NAME; -import static io.trino.plugin.hive.HiveMetadata.PRESTO_VERSION_NAME; +import static io.trino.plugin.hive.HiveMetadata.TRINO_VERSION_NAME; import static io.trino.plugin.hive.HiveSessionProperties.getTimestampPrecision; import static io.trino.plugin.hive.util.HiveClassNames.AVRO_CONTAINER_OUTPUT_FORMAT_CLASS; import static io.trino.plugin.hive.util.HiveUtil.getColumnNames; @@ -126,7 +126,7 @@ public Optional createFileWriter( inputColumnTypes, compressionKind, ImmutableMap.builder() - .put(PRESTO_VERSION_NAME, nodeVersion.toString()) + .put(TRINO_VERSION_NAME, nodeVersion.toString()) .put(PRESTO_QUERY_ID_NAME, session.getQueryId()) .buildOrThrow())); } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcFileWriterFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcFileWriterFactory.java index 35359facdadd..6270d2545c19 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcFileWriterFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcFileWriterFactory.java @@ -53,7 +53,7 @@ import static io.trino.plugin.hive.HiveErrorCode.HIVE_WRITER_OPEN_ERROR; import static io.trino.plugin.hive.HiveErrorCode.HIVE_WRITE_VALIDATION_FAILED; import static io.trino.plugin.hive.HiveMetadata.PRESTO_QUERY_ID_NAME; -import static io.trino.plugin.hive.HiveMetadata.PRESTO_VERSION_NAME; +import static io.trino.plugin.hive.HiveMetadata.TRINO_VERSION_NAME; import static io.trino.plugin.hive.HiveSessionProperties.getOrcOptimizedWriterMaxDictionaryMemory; import static io.trino.plugin.hive.HiveSessionProperties.getOrcOptimizedWriterMaxStripeRows; import static io.trino.plugin.hive.HiveSessionProperties.getOrcOptimizedWriterMaxStripeSize; @@ -199,7 +199,7 @@ public Optional createFileWriter( .withMaxStringStatisticsLimit(getOrcStringStatisticsLimit(session)), fileInputColumnIndexes, ImmutableMap.builder() - .put(PRESTO_VERSION_NAME, nodeVersion.toString()) + .put(TRINO_VERSION_NAME, nodeVersion.toString()) .put(PRESTO_QUERY_ID_NAME, session.getQueryId()) .buildOrThrow(), validationInputFactory, diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java index 19c219dd9161..1c9fc729d674 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java @@ -217,7 +217,7 @@ import static io.trino.plugin.hive.HiveErrorCode.HIVE_INVALID_PARTITION_VALUE; import static io.trino.plugin.hive.HiveErrorCode.HIVE_PARTITION_SCHEMA_MISMATCH; import static io.trino.plugin.hive.HiveMetadata.PRESTO_QUERY_ID_NAME; -import static io.trino.plugin.hive.HiveMetadata.PRESTO_VERSION_NAME; +import static io.trino.plugin.hive.HiveMetadata.TRINO_VERSION_NAME; import static io.trino.plugin.hive.HiveStorageFormat.AVRO; import static io.trino.plugin.hive.HiveStorageFormat.CSV; import static io.trino.plugin.hive.HiveStorageFormat.JSON; @@ -2776,7 +2776,7 @@ private static Table createSimpleTable(SchemaTableName schemaTableName, List

    storage @@ -3086,7 +3086,7 @@ public void testCreateEmptyTableShouldNotCreateStagingDirectory() .setOwner(Optional.of(tableOwner)) .setTableType(MANAGED_TABLE.name()) .setParameters(ImmutableMap.of( - PRESTO_VERSION_NAME, TEST_SERVER_VERSION, + TRINO_VERSION_NAME, TEST_SERVER_VERSION, PRESTO_QUERY_ID_NAME, session.getQueryId())) .setDataColumns(columns); tableBuilder.getStorageBuilder() @@ -3556,7 +3556,7 @@ public void testIllegalStorageFormatDuringTableScan() .setOwner(Optional.of(tableOwner)) .setTableType(MANAGED_TABLE.name()) .setParameters(ImmutableMap.of( - PRESTO_VERSION_NAME, TEST_SERVER_VERSION, + TRINO_VERSION_NAME, TEST_SERVER_VERSION, PRESTO_QUERY_ID_NAME, session.getQueryId())) .setDataColumns(columns) .withStorage(storage -> storage @@ -3762,7 +3762,7 @@ protected Partition createDummyPartition(Table table, String partitionName) .setStorageFormat(fromHiveStorageFormat(ORC)) .setLocation(partitionTargetPath(new SchemaTableName(table.getDatabaseName(), table.getTableName()), partitionName))) .setParameters(ImmutableMap.of( - PRESTO_VERSION_NAME, "testversion", + TRINO_VERSION_NAME, "testversion", PRESTO_QUERY_ID_NAME, "20180101_123456_00001_x1y2z")) .build(); } @@ -4218,7 +4218,7 @@ protected void doCreateTable(SchemaTableName tableName, HiveStorageFormat storag // verify the node version and query ID in table Table table = getMetastoreClient().getTable(tableName.getSchemaName(), tableName.getTableName()).get(); - assertThat(table.getParameters()).containsEntry(PRESTO_VERSION_NAME, TEST_SERVER_VERSION); + assertThat(table.getParameters()).containsEntry(TRINO_VERSION_NAME, TEST_SERVER_VERSION); assertThat(table.getParameters()).containsEntry(PRESTO_QUERY_ID_NAME, queryId); // verify basic statistics @@ -4282,7 +4282,7 @@ protected void doCreateEmptyTable(SchemaTableName tableName, HiveStorageFormat s assertThat(table.getStorage().getStorageFormat().getInputFormat()).isEqualTo(storageFormat.getInputFormat()); // verify the node version and query ID - assertThat(table.getParameters()).containsEntry(PRESTO_VERSION_NAME, TEST_SERVER_VERSION); + assertThat(table.getParameters()).containsEntry(TRINO_VERSION_NAME, TEST_SERVER_VERSION); assertThat(table.getParameters()).containsEntry(PRESTO_QUERY_ID_NAME, queryId); // verify the table is empty @@ -4643,7 +4643,7 @@ private void doInsertIntoNewPartition(HiveStorageFormat storageFormat, SchemaTab assertThat(partitions.size()).isEqualTo(partitionNames.size()); for (String partitionName : partitionNames) { Partition partition = partitions.get(partitionName).get(); - assertThat(partition.getParameters()).containsEntry(PRESTO_VERSION_NAME, TEST_SERVER_VERSION); + assertThat(partition.getParameters()).containsEntry(TRINO_VERSION_NAME, TEST_SERVER_VERSION); assertThat(partition.getParameters()).containsEntry(PRESTO_QUERY_ID_NAME, queryId); } @@ -5630,7 +5630,7 @@ protected void createEmptyTable( targetPath = new Path(locationService.forNewTable(transaction.getMetastore(), session, schemaName, tableName).toString()); ImmutableMap.Builder tableParamBuilder = ImmutableMap.builder() - .put(PRESTO_VERSION_NAME, TEST_SERVER_VERSION) + .put(TRINO_VERSION_NAME, TEST_SERVER_VERSION) .put(PRESTO_QUERY_ID_NAME, session.getQueryId()); if (isTransactional) { tableParamBuilder.put(TRANSACTIONAL, "true"); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveLocal.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveLocal.java index 7348fd743b1c..70936d16c7d0 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveLocal.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveLocal.java @@ -53,8 +53,8 @@ import static com.google.common.io.MoreFiles.deleteRecursively; import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; import static io.trino.plugin.hive.HiveMetadata.PRESTO_QUERY_ID_NAME; -import static io.trino.plugin.hive.HiveMetadata.PRESTO_VERSION_NAME; import static io.trino.plugin.hive.HiveMetadata.TABLE_COMMENT; +import static io.trino.plugin.hive.HiveMetadata.TRINO_VERSION_NAME; import static io.trino.plugin.hive.HiveStorageFormat.ORC; import static io.trino.plugin.hive.HiveStorageFormat.TEXTFILE; import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; @@ -308,7 +308,7 @@ private void createExternalTable(SchemaTableName schemaTableName, HiveStorageFor .setOwner(Optional.of(tableOwner)) .setTableType(EXTERNAL_TABLE.name()) .setParameters(ImmutableMap.of( - PRESTO_VERSION_NAME, TEST_SERVER_VERSION, + TRINO_VERSION_NAME, TEST_SERVER_VERSION, PRESTO_QUERY_ID_NAME, session.getQueryId())) .setDataColumns(columns) .setPartitionColumns(partitionColumns); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java index 324e496a2303..553e68b8896e 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java @@ -2058,7 +2058,7 @@ public void testPropertiesTable() assertUpdate(createTable, "SELECT count(*) FROM orders"); String queryId = (String) computeScalar("SELECT query_id FROM system.runtime.queries WHERE query LIKE 'CREATE TABLE test_show_properties%'"); String nodeVersion = (String) computeScalar("SELECT node_version FROM system.runtime.nodes WHERE coordinator"); - assertQuery("SELECT \"orc.bloom.filter.columns\", \"orc.bloom.filter.fpp\", presto_query_id, presto_version, transactional FROM \"test_show_properties$properties\"", + assertQuery("SELECT \"orc.bloom.filter.columns\", \"orc.bloom.filter.fpp\", presto_query_id, trino_version, transactional FROM \"test_show_properties$properties\"", format("SELECT 'ship_priority,order_status', '0.5', '%s', '%s', 'false'", queryId, nodeVersion)); assertUpdate("DROP TABLE test_show_properties"); } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergFileWriterFactory.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergFileWriterFactory.java index 8da4680c3747..81b335444889 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergFileWriterFactory.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergFileWriterFactory.java @@ -53,7 +53,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableList.toImmutableList; import static io.trino.plugin.hive.HiveMetadata.PRESTO_QUERY_ID_NAME; -import static io.trino.plugin.hive.HiveMetadata.PRESTO_VERSION_NAME; +import static io.trino.plugin.hive.HiveMetadata.TRINO_VERSION_NAME; import static io.trino.plugin.iceberg.IcebergErrorCode.ICEBERG_INVALID_METADATA; import static io.trino.plugin.iceberg.IcebergErrorCode.ICEBERG_WRITER_OPEN_ERROR; import static io.trino.plugin.iceberg.IcebergErrorCode.ICEBERG_WRITE_VALIDATION_FAILED; @@ -251,7 +251,7 @@ private IcebergFileWriter createOrcWriter( .withMaxStringStatisticsLimit(stringStatisticsLimit), IntStream.range(0, fileColumnNames.size()).toArray(), ImmutableMap.builder() - .put(PRESTO_VERSION_NAME, nodeVersion.toString()) + .put(TRINO_VERSION_NAME, nodeVersion.toString()) .put(PRESTO_QUERY_ID_NAME, session.getQueryId()) .buildOrThrow(), validationInputFactory, diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHivePropertiesTable.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHivePropertiesTable.java index b9e6a5492173..ce303fc0bc42 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHivePropertiesTable.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHivePropertiesTable.java @@ -42,7 +42,7 @@ public void testTrinoViewPropertiesTable() .containsOnly( row("comment", "varchar", "", ""), row("presto_query_id", "varchar", "", ""), - row("presto_version", "varchar", "", ""), + row("trino_version", "varchar", "", ""), row("presto_view", "varchar", "", ""), row("transient_lastddltime", "varchar", "", ""), row("trino_created_by", "varchar", "", "")); From d3c8396829ea5f3fcfdd71285bd2f24ea3664d5d Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Wed, 29 Nov 2023 19:51:02 -0800 Subject: [PATCH 554/587] Rename presto_query_id table property to trino_query_id --- docs/src/main/sphinx/connector/hive.md | 2 +- docs/src/main/sphinx/release/release-0.153.md | 2 +- .../plugin/deltalake/DeltaLakeMetadata.java | 10 +++++----- ...TestDeltaLakeCreateSchemaInternalRetry.java | 4 ++-- .../TestDeltaLakeCreateTableInternalRetry.java | 4 ++-- .../io/trino/plugin/hive/HiveMetadata.java | 10 +++++----- .../plugin/hive/RcFileFileWriterFactory.java | 4 ++-- .../io/trino/plugin/hive/TrinoViewUtil.java | 4 ++-- .../hive/avro/AvroFileWriterFactory.java | 4 ++-- .../SemiTransactionalHiveMetastore.java | 8 ++++---- .../plugin/hive/orc/OrcFileWriterFactory.java | 4 ++-- .../procedure/RegisterPartitionProcedure.java | 4 ++-- .../SyncPartitionMetadataProcedure.java | 4 ++-- .../io/trino/plugin/hive/AbstractTestHive.java | 18 +++++++++--------- .../plugin/hive/AbstractTestHiveLocal.java | 4 ++-- .../plugin/hive/BaseHiveConnectorTest.java | 2 +- .../TestHiveCreateSchemaInternalRetry.java | 4 ++-- .../iceberg/IcebergFileWriterFactory.java | 4 ++-- .../iceberg/catalog/AbstractTrinoCatalog.java | 6 +++--- .../iceberg/catalog/hms/TrinoHiveCatalog.java | 4 ++-- .../TestIcebergCreateTableInternalRetry.java | 4 ++-- .../product/hive/TestHivePropertiesTable.java | 2 +- 22 files changed, 56 insertions(+), 56 deletions(-) diff --git a/docs/src/main/sphinx/connector/hive.md b/docs/src/main/sphinx/connector/hive.md index 9dfa4352e648..9697b8a92791 100644 --- a/docs/src/main/sphinx/connector/hive.md +++ b/docs/src/main/sphinx/connector/hive.md @@ -909,7 +909,7 @@ SELECT * FROM example.web."page_views$properties"; ``` ```text - stats_generated_via_stats_task | auto.purge | presto_query_id | trino_version | transactional + stats_generated_via_stats_task | auto.purge | trino_query_id | trino_version | transactional ---------------------------------------------+------------+-----------------------------+---------------+--------------- workaround for potential lack of HIVE-12730 | false | 20230705_152456_00001_nfugi | 434 | false ``` diff --git a/docs/src/main/sphinx/release/release-0.153.md b/docs/src/main/sphinx/release/release-0.153.md index 6491bf47c7c1..c63a6619f877 100644 --- a/docs/src/main/sphinx/release/release-0.153.md +++ b/docs/src/main/sphinx/release/release-0.153.md @@ -93,7 +93,7 @@ and {doc}`/admin/resource-groups` for more details. The filesystem location can be specified when creating a schema, which allows, for example, easily creating tables on S3. - Record Presto query ID for tables or partitions written by Presto - using the `presto_query_id` table or partition property. + using the `trino_query_id` table or partition property. - Include path name in error message when listing a directory fails. - Rename `allow-all` authorization method to `legacy`. This method is deprecated and will be removed in a future release. diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java index 8f9b8f5eb07d..4cf3e67d2b0d 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java @@ -255,7 +255,7 @@ import static io.trino.plugin.deltalake.transactionlog.TransactionLogUtil.getTransactionLogDir; import static io.trino.plugin.deltalake.transactionlog.checkpoint.CheckpointEntryIterator.EntryType.METADATA; import static io.trino.plugin.deltalake.transactionlog.checkpoint.CheckpointEntryIterator.EntryType.PROTOCOL; -import static io.trino.plugin.hive.HiveMetadata.PRESTO_QUERY_ID_NAME; +import static io.trino.plugin.hive.HiveMetadata.TRINO_QUERY_ID_NAME; import static io.trino.plugin.hive.TableType.EXTERNAL_TABLE; import static io.trino.plugin.hive.TableType.MANAGED_TABLE; import static io.trino.plugin.hive.TableType.VIRTUAL_VIEW; @@ -828,7 +828,7 @@ public void createSchema(ConnectorSession session, String schemaName, Map deltaTableProperties(ConnectorSession session, String location, boolean external) { ImmutableMap.Builder properties = ImmutableMap.builder() - .put(PRESTO_QUERY_ID_NAME, session.getQueryId()) + .put(TRINO_QUERY_ID_NAME, session.getQueryId()) .put(LOCATION_PROPERTY, location) .put(TABLE_PROVIDER_PROPERTY, TABLE_PROVIDER_VALUE) // Set bogus table stats to prevent Hive 3.x from gathering these stats at table creation. @@ -3791,11 +3791,11 @@ private static DeltaLakeColumnHandle toColumnHandle(String originalName, Type ty private static Optional getQueryId(Database database) { - return Optional.ofNullable(database.getParameters().get(PRESTO_QUERY_ID_NAME)); + return Optional.ofNullable(database.getParameters().get(TRINO_QUERY_ID_NAME)); } public static Optional getQueryId(Table table) { - return Optional.ofNullable(table.getParameters().get(PRESTO_QUERY_ID_NAME)); + return Optional.ofNullable(table.getParameters().get(TRINO_QUERY_ID_NAME)); } } diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeCreateSchemaInternalRetry.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeCreateSchemaInternalRetry.java index 33dc016113dc..6fca8fde9f12 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeCreateSchemaInternalRetry.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeCreateSchemaInternalRetry.java @@ -39,7 +39,7 @@ import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; import static com.google.inject.util.Modules.EMPTY_MODULE; import static io.trino.plugin.deltalake.DeltaLakeConnectorFactory.CONNECTOR_NAME; -import static io.trino.plugin.hive.HiveMetadata.PRESTO_QUERY_ID_NAME; +import static io.trino.plugin.hive.HiveMetadata.TRINO_QUERY_ID_NAME; import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.testing.TestingSession.testSessionBuilder; @@ -80,7 +80,7 @@ public synchronized void createDatabase(Database database) if (database.getDatabaseName().equals(TEST_SCHEMA_DIFFERENT_SESSION)) { // By modifying query id test simulates that schema was created from different session. database = Database.builder(database) - .setParameters(ImmutableMap.of(PRESTO_QUERY_ID_NAME, "new_query_id")) + .setParameters(ImmutableMap.of(TRINO_QUERY_ID_NAME, "new_query_id")) .build(); } // Simulate retry mechanism with timeout failure. diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeCreateTableInternalRetry.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeCreateTableInternalRetry.java index 5bc25447ad46..5e2dac8c5364 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeCreateTableInternalRetry.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeCreateTableInternalRetry.java @@ -43,7 +43,7 @@ import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; import static com.google.inject.util.Modules.EMPTY_MODULE; import static io.trino.plugin.deltalake.DeltaLakeConnectorFactory.CONNECTOR_NAME; -import static io.trino.plugin.hive.HiveMetadata.PRESTO_QUERY_ID_NAME; +import static io.trino.plugin.hive.HiveMetadata.TRINO_QUERY_ID_NAME; import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; import static io.trino.testing.TestingSession.testSessionBuilder; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; @@ -82,7 +82,7 @@ public synchronized void createTable(Table table, PrincipalPrivileges principalP if (table.getTableName().startsWith("test_different_session")) { // By modifying query id test simulates that table was created from different session. table = Table.builder(table) - .setParameters(ImmutableMap.of(PRESTO_QUERY_ID_NAME, "new_query_id")) + .setParameters(ImmutableMap.of(TRINO_QUERY_ID_NAME, "new_query_id")) .build(); } // Simulate retry mechanism with timeout failure of ThriftHiveMetastore. diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java index 8b12ed0499c1..a6c53d5543a5 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java @@ -334,7 +334,7 @@ public class HiveMetadata public static final String TRINO_VERSION_NAME = "trino_version"; public static final String TRINO_CREATED_BY = "trino_created_by"; - public static final String PRESTO_QUERY_ID_NAME = "presto_query_id"; + public static final String TRINO_QUERY_ID_NAME = "trino_query_id"; public static final String BUCKETING_VERSION = "bucketing_version"; public static final String TABLE_COMMENT = "comment"; public static final String STORAGE_TABLE = "storage_table"; @@ -985,7 +985,7 @@ public void createSchema(ConnectorSession session, String schemaName, Map tableParameters = ImmutableMap.builder() .put(TRINO_VERSION_NAME, trinoVersion) - .put(PRESTO_QUERY_ID_NAME, queryId) + .put(TRINO_QUERY_ID_NAME, queryId) .putAll(additionalTableParameters); if (external) { @@ -2376,7 +2376,7 @@ private Partition buildPartitionObject(ConnectorSession session, Table table, Pa .setValues(extractPartitionValues(partitionUpdate.getName())) .setParameters(ImmutableMap.builder() .put(TRINO_VERSION_NAME, trinoVersion) - .put(PRESTO_QUERY_ID_NAME, session.getQueryId()) + .put(TRINO_QUERY_ID_NAME, session.getQueryId()) .buildOrThrow()) .withStorage(storage -> storage .setStorageFormat(isRespectTableFormat(session) ? @@ -2692,7 +2692,7 @@ public void createView(ConnectorSession session, SchemaTableName viewName, Conne .put(PRESTO_VIEW_FLAG, "true") .put(TRINO_CREATED_BY, "Trino Hive connector") .put(TRINO_VERSION_NAME, trinoVersion) - .put(PRESTO_QUERY_ID_NAME, session.getQueryId()) + .put(TRINO_QUERY_ID_NAME, session.getQueryId()) .buildOrThrow(); Column dummyColumn = new Column("dummy", HIVE_STRING, Optional.empty(), ImmutableMap.of()); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/RcFileFileWriterFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/RcFileFileWriterFactory.java index d7c6c2a13641..7db3d6b9b960 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/RcFileFileWriterFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/RcFileFileWriterFactory.java @@ -42,7 +42,7 @@ import static io.trino.memory.context.AggregatedMemoryContext.newSimpleAggregatedMemoryContext; import static io.trino.plugin.hive.HiveErrorCode.HIVE_WRITER_OPEN_ERROR; -import static io.trino.plugin.hive.HiveMetadata.PRESTO_QUERY_ID_NAME; +import static io.trino.plugin.hive.HiveMetadata.TRINO_QUERY_ID_NAME; import static io.trino.plugin.hive.HiveMetadata.TRINO_VERSION_NAME; import static io.trino.plugin.hive.HiveSessionProperties.getTimestampPrecision; import static io.trino.plugin.hive.HiveSessionProperties.isRcfileOptimizedWriterValidate; @@ -145,7 +145,7 @@ else if (COLUMNAR_SERDE_CLASS.equals(storageFormat.getSerde())) { fileInputColumnIndexes, ImmutableMap.builder() .put(TRINO_VERSION_NAME, nodeVersion.toString()) - .put(PRESTO_QUERY_ID_NAME, session.getQueryId()) + .put(TRINO_QUERY_ID_NAME, session.getQueryId()) .buildOrThrow(), validationInputFactory)); } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/TrinoViewUtil.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/TrinoViewUtil.java index b3153371386e..61fc730ad446 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/TrinoViewUtil.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/TrinoViewUtil.java @@ -21,10 +21,10 @@ import java.util.Optional; import static com.google.common.base.Preconditions.checkArgument; -import static io.trino.plugin.hive.HiveMetadata.PRESTO_QUERY_ID_NAME; import static io.trino.plugin.hive.HiveMetadata.PRESTO_VIEW_COMMENT; import static io.trino.plugin.hive.HiveMetadata.TABLE_COMMENT; import static io.trino.plugin.hive.HiveMetadata.TRINO_CREATED_BY; +import static io.trino.plugin.hive.HiveMetadata.TRINO_QUERY_ID_NAME; import static io.trino.plugin.hive.HiveMetadata.TRINO_VERSION_NAME; import static io.trino.plugin.hive.ViewReaderUtil.PRESTO_VIEW_FLAG; import static io.trino.plugin.hive.ViewReaderUtil.isTrinoView; @@ -67,7 +67,7 @@ public static Map createViewProperties(ConnectorSession session, .put(PRESTO_VIEW_FLAG, "true") .put(TRINO_CREATED_BY, connectorName) .put(TRINO_VERSION_NAME, trinoVersion) - .put(PRESTO_QUERY_ID_NAME, session.getQueryId()) + .put(TRINO_QUERY_ID_NAME, session.getQueryId()) .put(TABLE_COMMENT, PRESTO_VIEW_COMMENT) .buildOrThrow(); } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroFileWriterFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroFileWriterFactory.java index 87b81e66e28e..fe00fca0fa33 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroFileWriterFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/avro/AvroFileWriterFactory.java @@ -46,7 +46,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static io.trino.memory.context.AggregatedMemoryContext.newSimpleAggregatedMemoryContext; import static io.trino.plugin.hive.HiveErrorCode.HIVE_WRITER_OPEN_ERROR; -import static io.trino.plugin.hive.HiveMetadata.PRESTO_QUERY_ID_NAME; +import static io.trino.plugin.hive.HiveMetadata.TRINO_QUERY_ID_NAME; import static io.trino.plugin.hive.HiveMetadata.TRINO_VERSION_NAME; import static io.trino.plugin.hive.HiveSessionProperties.getTimestampPrecision; import static io.trino.plugin.hive.util.HiveClassNames.AVRO_CONTAINER_OUTPUT_FORMAT_CLASS; @@ -127,7 +127,7 @@ public Optional createFileWriter( compressionKind, ImmutableMap.builder() .put(TRINO_VERSION_NAME, nodeVersion.toString()) - .put(PRESTO_QUERY_ID_NAME, session.getQueryId()) + .put(TRINO_QUERY_ID_NAME, session.getQueryId()) .buildOrThrow())); } catch (Exception e) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/SemiTransactionalHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/SemiTransactionalHiveMetastore.java index 81eb6abafce7..833229fbcb28 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/SemiTransactionalHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/SemiTransactionalHiveMetastore.java @@ -102,7 +102,7 @@ import static io.trino.plugin.hive.HiveErrorCode.HIVE_METASTORE_ERROR; import static io.trino.plugin.hive.HiveErrorCode.HIVE_PATH_ALREADY_EXISTS; import static io.trino.plugin.hive.HiveErrorCode.HIVE_TABLE_DROPPED_DURING_QUERY; -import static io.trino.plugin.hive.HiveMetadata.PRESTO_QUERY_ID_NAME; +import static io.trino.plugin.hive.HiveMetadata.TRINO_QUERY_ID_NAME; import static io.trino.plugin.hive.LocationHandle.WriteMode.DIRECT_TO_TARGET_NEW_DIRECTORY; import static io.trino.plugin.hive.TableType.MANAGED_TABLE; import static io.trino.plugin.hive.ViewReaderUtil.isTrinoMaterializedView; @@ -2754,17 +2754,17 @@ private static boolean directoryExists(TrinoFileSystem fileSystem, Location dire private static Optional getQueryId(Database database) { - return Optional.ofNullable(database.getParameters().get(PRESTO_QUERY_ID_NAME)); + return Optional.ofNullable(database.getParameters().get(TRINO_QUERY_ID_NAME)); } private static Optional getQueryId(Table table) { - return Optional.ofNullable(table.getParameters().get(PRESTO_QUERY_ID_NAME)); + return Optional.ofNullable(table.getParameters().get(TRINO_QUERY_ID_NAME)); } private static Optional getQueryId(Partition partition) { - return Optional.ofNullable(partition.getParameters().get(PRESTO_QUERY_ID_NAME)); + return Optional.ofNullable(partition.getParameters().get(TRINO_QUERY_ID_NAME)); } private static Location asFileLocation(Location location) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcFileWriterFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcFileWriterFactory.java index 6270d2545c19..247f7881824e 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcFileWriterFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcFileWriterFactory.java @@ -52,7 +52,7 @@ import static io.trino.orc.metadata.OrcType.createRootOrcType; import static io.trino.plugin.hive.HiveErrorCode.HIVE_WRITER_OPEN_ERROR; import static io.trino.plugin.hive.HiveErrorCode.HIVE_WRITE_VALIDATION_FAILED; -import static io.trino.plugin.hive.HiveMetadata.PRESTO_QUERY_ID_NAME; +import static io.trino.plugin.hive.HiveMetadata.TRINO_QUERY_ID_NAME; import static io.trino.plugin.hive.HiveMetadata.TRINO_VERSION_NAME; import static io.trino.plugin.hive.HiveSessionProperties.getOrcOptimizedWriterMaxDictionaryMemory; import static io.trino.plugin.hive.HiveSessionProperties.getOrcOptimizedWriterMaxStripeRows; @@ -200,7 +200,7 @@ public Optional createFileWriter( fileInputColumnIndexes, ImmutableMap.builder() .put(TRINO_VERSION_NAME, nodeVersion.toString()) - .put(PRESTO_QUERY_ID_NAME, session.getQueryId()) + .put(TRINO_QUERY_ID_NAME, session.getQueryId()) .buildOrThrow(), validationInputFactory, getOrcOptimizedWriterValidateMode(session), diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/RegisterPartitionProcedure.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/RegisterPartitionProcedure.java index 2dc267fcfe05..8b82a44a1095 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/RegisterPartitionProcedure.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/RegisterPartitionProcedure.java @@ -44,7 +44,7 @@ import static io.trino.plugin.base.util.Procedures.checkProcedureArgument; import static io.trino.plugin.hive.HiveErrorCode.HIVE_FILESYSTEM_ERROR; -import static io.trino.plugin.hive.HiveMetadata.PRESTO_QUERY_ID_NAME; +import static io.trino.plugin.hive.HiveMetadata.TRINO_QUERY_ID_NAME; import static io.trino.plugin.hive.procedure.Procedures.checkIsPartitionedTable; import static io.trino.plugin.hive.procedure.Procedures.checkPartitionColumns; import static io.trino.plugin.hive.util.HiveUtil.makePartName; @@ -171,7 +171,7 @@ private static Partition buildPartitionObject(ConnectorSession session, Table ta .setTableName(table.getTableName()) .setColumns(table.getDataColumns()) .setValues(partitionValues) - .setParameters(ImmutableMap.of(PRESTO_QUERY_ID_NAME, session.getQueryId())) + .setParameters(ImmutableMap.of(TRINO_QUERY_ID_NAME, session.getQueryId())) .withStorage(storage -> storage .setStorageFormat(table.getStorage().getStorageFormat()) .setLocation(location.toString()) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/SyncPartitionMetadataProcedure.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/SyncPartitionMetadataProcedure.java index 47282bd11a27..ae428b6f9b19 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/SyncPartitionMetadataProcedure.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/procedure/SyncPartitionMetadataProcedure.java @@ -48,7 +48,7 @@ import static com.google.common.collect.Sets.difference; import static io.trino.plugin.base.util.Procedures.checkProcedureArgument; import static io.trino.plugin.hive.HiveErrorCode.HIVE_FILESYSTEM_ERROR; -import static io.trino.plugin.hive.HiveMetadata.PRESTO_QUERY_ID_NAME; +import static io.trino.plugin.hive.HiveMetadata.TRINO_QUERY_ID_NAME; import static io.trino.plugin.hive.HivePartitionManager.extractPartitionValues; import static io.trino.spi.StandardErrorCode.INVALID_PROCEDURE_ARGUMENT; import static io.trino.spi.type.BooleanType.BOOLEAN; @@ -267,7 +267,7 @@ private static Partition buildPartitionObject(ConnectorSession session, Table ta .setTableName(table.getTableName()) .setColumns(table.getDataColumns()) .setValues(extractPartitionValues(partitionName)) - .setParameters(ImmutableMap.of(PRESTO_QUERY_ID_NAME, session.getQueryId())) + .setParameters(ImmutableMap.of(TRINO_QUERY_ID_NAME, session.getQueryId())) .withStorage(storage -> storage .setStorageFormat(table.getStorage().getStorageFormat()) .setLocation(Location.of(table.getStorage().getLocation()).appendPath(partitionName).toString()) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java index 1c9fc729d674..b34d9fba2427 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java @@ -216,7 +216,7 @@ import static io.trino.plugin.hive.HiveErrorCode.HIVE_INVALID_BUCKET_FILES; import static io.trino.plugin.hive.HiveErrorCode.HIVE_INVALID_PARTITION_VALUE; import static io.trino.plugin.hive.HiveErrorCode.HIVE_PARTITION_SCHEMA_MISMATCH; -import static io.trino.plugin.hive.HiveMetadata.PRESTO_QUERY_ID_NAME; +import static io.trino.plugin.hive.HiveMetadata.TRINO_QUERY_ID_NAME; import static io.trino.plugin.hive.HiveMetadata.TRINO_VERSION_NAME; import static io.trino.plugin.hive.HiveStorageFormat.AVRO; import static io.trino.plugin.hive.HiveStorageFormat.CSV; @@ -2777,7 +2777,7 @@ private static Table createSimpleTable(SchemaTableName schemaTableName, Liststorage .setLocation(targetPath.toString()) @@ -3087,7 +3087,7 @@ public void testCreateEmptyTableShouldNotCreateStagingDirectory() .setTableType(MANAGED_TABLE.name()) .setParameters(ImmutableMap.of( TRINO_VERSION_NAME, TEST_SERVER_VERSION, - PRESTO_QUERY_ID_NAME, session.getQueryId())) + TRINO_QUERY_ID_NAME, session.getQueryId())) .setDataColumns(columns); tableBuilder.getStorageBuilder() .setLocation(targetPath.toString()) @@ -3557,7 +3557,7 @@ public void testIllegalStorageFormatDuringTableScan() .setTableType(MANAGED_TABLE.name()) .setParameters(ImmutableMap.of( TRINO_VERSION_NAME, TEST_SERVER_VERSION, - PRESTO_QUERY_ID_NAME, session.getQueryId())) + TRINO_QUERY_ID_NAME, session.getQueryId())) .setDataColumns(columns) .withStorage(storage -> storage .setLocation(targetPath.toString()) @@ -3763,7 +3763,7 @@ protected Partition createDummyPartition(Table table, String partitionName) .setLocation(partitionTargetPath(new SchemaTableName(table.getDatabaseName(), table.getTableName()), partitionName))) .setParameters(ImmutableMap.of( TRINO_VERSION_NAME, "testversion", - PRESTO_QUERY_ID_NAME, "20180101_123456_00001_x1y2z")) + TRINO_QUERY_ID_NAME, "20180101_123456_00001_x1y2z")) .build(); } @@ -4219,7 +4219,7 @@ protected void doCreateTable(SchemaTableName tableName, HiveStorageFormat storag // verify the node version and query ID in table Table table = getMetastoreClient().getTable(tableName.getSchemaName(), tableName.getTableName()).get(); assertThat(table.getParameters()).containsEntry(TRINO_VERSION_NAME, TEST_SERVER_VERSION); - assertThat(table.getParameters()).containsEntry(PRESTO_QUERY_ID_NAME, queryId); + assertThat(table.getParameters()).containsEntry(TRINO_QUERY_ID_NAME, queryId); // verify basic statistics HiveBasicStatistics statistics = getBasicStatisticsForTable(transaction, tableName); @@ -4283,7 +4283,7 @@ protected void doCreateEmptyTable(SchemaTableName tableName, HiveStorageFormat s // verify the node version and query ID assertThat(table.getParameters()).containsEntry(TRINO_VERSION_NAME, TEST_SERVER_VERSION); - assertThat(table.getParameters()).containsEntry(PRESTO_QUERY_ID_NAME, queryId); + assertThat(table.getParameters()).containsEntry(TRINO_QUERY_ID_NAME, queryId); // verify the table is empty List columnHandles = filterNonHiddenColumnHandles(metadata.getColumnHandles(session, tableHandle).values()); @@ -4644,7 +4644,7 @@ private void doInsertIntoNewPartition(HiveStorageFormat storageFormat, SchemaTab for (String partitionName : partitionNames) { Partition partition = partitions.get(partitionName).get(); assertThat(partition.getParameters()).containsEntry(TRINO_VERSION_NAME, TEST_SERVER_VERSION); - assertThat(partition.getParameters()).containsEntry(PRESTO_QUERY_ID_NAME, queryId); + assertThat(partition.getParameters()).containsEntry(TRINO_QUERY_ID_NAME, queryId); } // load the new table @@ -5631,7 +5631,7 @@ protected void createEmptyTable( ImmutableMap.Builder tableParamBuilder = ImmutableMap.builder() .put(TRINO_VERSION_NAME, TEST_SERVER_VERSION) - .put(PRESTO_QUERY_ID_NAME, session.getQueryId()); + .put(TRINO_QUERY_ID_NAME, session.getQueryId()); if (isTransactional) { tableParamBuilder.put(TRANSACTIONAL, "true"); } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveLocal.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveLocal.java index 70936d16c7d0..a2cd60052b20 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveLocal.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveLocal.java @@ -52,8 +52,8 @@ import static com.google.common.io.MoreFiles.deleteRecursively; import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; -import static io.trino.plugin.hive.HiveMetadata.PRESTO_QUERY_ID_NAME; import static io.trino.plugin.hive.HiveMetadata.TABLE_COMMENT; +import static io.trino.plugin.hive.HiveMetadata.TRINO_QUERY_ID_NAME; import static io.trino.plugin.hive.HiveMetadata.TRINO_VERSION_NAME; import static io.trino.plugin.hive.HiveStorageFormat.ORC; import static io.trino.plugin.hive.HiveStorageFormat.TEXTFILE; @@ -309,7 +309,7 @@ private void createExternalTable(SchemaTableName schemaTableName, HiveStorageFor .setTableType(EXTERNAL_TABLE.name()) .setParameters(ImmutableMap.of( TRINO_VERSION_NAME, TEST_SERVER_VERSION, - PRESTO_QUERY_ID_NAME, session.getQueryId())) + TRINO_QUERY_ID_NAME, session.getQueryId())) .setDataColumns(columns) .setPartitionColumns(partitionColumns); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java index 553e68b8896e..39a6d4e50572 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/BaseHiveConnectorTest.java @@ -2058,7 +2058,7 @@ public void testPropertiesTable() assertUpdate(createTable, "SELECT count(*) FROM orders"); String queryId = (String) computeScalar("SELECT query_id FROM system.runtime.queries WHERE query LIKE 'CREATE TABLE test_show_properties%'"); String nodeVersion = (String) computeScalar("SELECT node_version FROM system.runtime.nodes WHERE coordinator"); - assertQuery("SELECT \"orc.bloom.filter.columns\", \"orc.bloom.filter.fpp\", presto_query_id, trino_version, transactional FROM \"test_show_properties$properties\"", + assertQuery("SELECT \"orc.bloom.filter.columns\", \"orc.bloom.filter.fpp\", trino_query_id, trino_version, transactional FROM \"test_show_properties$properties\"", format("SELECT 'ship_priority,order_status', '0.5', '%s', '%s', 'false'", queryId, nodeVersion)); assertUpdate("DROP TABLE test_show_properties"); } diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveCreateSchemaInternalRetry.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveCreateSchemaInternalRetry.java index 876f729fe3e7..2aca3f200752 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveCreateSchemaInternalRetry.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveCreateSchemaInternalRetry.java @@ -27,7 +27,7 @@ import java.io.IOException; -import static io.trino.plugin.hive.HiveMetadata.PRESTO_QUERY_ID_NAME; +import static io.trino.plugin.hive.HiveMetadata.TRINO_QUERY_ID_NAME; import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; import static io.trino.testing.TestingNames.randomNameSuffix; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; @@ -67,7 +67,7 @@ public synchronized void createDatabase(Database database) if (database.getDatabaseName().equals(TEST_SCHEMA_DIFFERENT_SESSION)) { // By modifying query id test simulates that schema was created from different session. database = Database.builder(database) - .setParameters(ImmutableMap.of(PRESTO_QUERY_ID_NAME, "new_query_id")) + .setParameters(ImmutableMap.of(TRINO_QUERY_ID_NAME, "new_query_id")) .build(); } // Simulate retry mechanism with timeout failure. diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergFileWriterFactory.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergFileWriterFactory.java index 81b335444889..bc6cf15c7d2c 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergFileWriterFactory.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergFileWriterFactory.java @@ -52,7 +52,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableList.toImmutableList; -import static io.trino.plugin.hive.HiveMetadata.PRESTO_QUERY_ID_NAME; +import static io.trino.plugin.hive.HiveMetadata.TRINO_QUERY_ID_NAME; import static io.trino.plugin.hive.HiveMetadata.TRINO_VERSION_NAME; import static io.trino.plugin.iceberg.IcebergErrorCode.ICEBERG_INVALID_METADATA; import static io.trino.plugin.iceberg.IcebergErrorCode.ICEBERG_WRITER_OPEN_ERROR; @@ -252,7 +252,7 @@ private IcebergFileWriter createOrcWriter( IntStream.range(0, fileColumnNames.size()).toArray(), ImmutableMap.builder() .put(TRINO_VERSION_NAME, nodeVersion.toString()) - .put(PRESTO_QUERY_ID_NAME, session.getQueryId()) + .put(TRINO_QUERY_ID_NAME, session.getQueryId()) .buildOrThrow(), validationInputFactory, getOrcWriterValidateMode(session), diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractTrinoCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractTrinoCatalog.java index 769f1c041992..f9e80d3eb42f 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractTrinoCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractTrinoCatalog.java @@ -118,7 +118,7 @@ public abstract class AbstractTrinoCatalog { public static final String TRINO_CREATED_BY_VALUE = "Trino Iceberg connector"; protected static final String TRINO_CREATED_BY = HiveMetadata.TRINO_CREATED_BY; - protected static final String PRESTO_QUERY_ID_NAME = HiveMetadata.PRESTO_QUERY_ID_NAME; + protected static final String TRINO_QUERY_ID_NAME = HiveMetadata.TRINO_QUERY_ID_NAME; private final CatalogName catalogName; private final TypeManager typeManager; @@ -454,7 +454,7 @@ protected List toSpiMaterializedView protected Map createMaterializedViewProperties(ConnectorSession session, SchemaTableName storageTableName) { return ImmutableMap.builder() - .put(PRESTO_QUERY_ID_NAME, session.getQueryId()) + .put(TRINO_QUERY_ID_NAME, session.getQueryId()) .put(STORAGE_SCHEMA, storageTableName.getSchemaName()) .put(STORAGE_TABLE, storageTableName.getTableName()) .put(PRESTO_VIEW_FLAG, "true") @@ -466,7 +466,7 @@ protected Map createMaterializedViewProperties(ConnectorSession protected Map createMaterializedViewProperties(ConnectorSession session, Location storageMetadataLocation) { return ImmutableMap.builder() - .put(PRESTO_QUERY_ID_NAME, session.getQueryId()) + .put(TRINO_QUERY_ID_NAME, session.getQueryId()) .put(METADATA_LOCATION_PROP, storageMetadataLocation.toString()) .put(PRESTO_VIEW_FLAG, "true") .put(TRINO_CREATED_BY, TRINO_CREATED_BY_VALUE) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java index 5e29362ed6f9..6fdf7cff121b 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java @@ -341,7 +341,7 @@ public void registerTable(ConnectorSession session, SchemaTableName schemaTableN .withStorage(storage -> storage.setStorageFormat(ICEBERG_METASTORE_STORAGE_FORMAT)) // This is a must-have property for the EXTERNAL_TABLE table type .setParameter("EXTERNAL", "TRUE") - .setParameter(PRESTO_QUERY_ID_NAME, session.getQueryId()) + .setParameter(TRINO_QUERY_ID_NAME, session.getQueryId()) .setParameter(TABLE_TYPE_PROP, ICEBERG_TABLE_TYPE_VALUE.toUpperCase(ENGLISH)) .setParameter(METADATA_LOCATION_PROP, tableMetadata.metadataFileLocation()); @@ -368,7 +368,7 @@ public static boolean isCreatedBy(io.trino.plugin.hive.metastore.Table table, St private static Optional getQueryId(io.trino.plugin.hive.metastore.Table table) { - return Optional.ofNullable(table.getParameters().get(PRESTO_QUERY_ID_NAME)); + return Optional.ofNullable(table.getParameters().get(TRINO_QUERY_ID_NAME)); } @Override diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergCreateTableInternalRetry.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergCreateTableInternalRetry.java index fce50616513a..94f3535b3ac4 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergCreateTableInternalRetry.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergCreateTableInternalRetry.java @@ -41,7 +41,7 @@ import static com.google.common.io.MoreFiles.deleteRecursively; import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; import static com.google.inject.util.Modules.EMPTY_MODULE; -import static io.trino.plugin.hive.HiveMetadata.PRESTO_QUERY_ID_NAME; +import static io.trino.plugin.hive.HiveMetadata.TRINO_QUERY_ID_NAME; import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; import static io.trino.plugin.iceberg.IcebergQueryRunner.ICEBERG_CATALOG; import static io.trino.testing.TestingSession.testSessionBuilder; @@ -79,7 +79,7 @@ public synchronized void createTable(Table table, PrincipalPrivileges principalP if (table.getTableName().startsWith("test_different_session")) { // By modifying query id test simulates that table was created from different session. table = Table.builder(table) - .setParameters(ImmutableMap.of(PRESTO_QUERY_ID_NAME, "new_query_id")) + .setParameters(ImmutableMap.of(TRINO_QUERY_ID_NAME, "new_query_id")) .build(); } // Simulate retry mechanism with timeout failure of ThriftHiveMetastore. diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHivePropertiesTable.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHivePropertiesTable.java index ce303fc0bc42..f31e56a8b33c 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHivePropertiesTable.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/hive/TestHivePropertiesTable.java @@ -41,7 +41,7 @@ public void testTrinoViewPropertiesTable() assertThat(onTrino().executeQuery("SHOW COLUMNS FROM \"test_trino_view_properties$properties\"")) .containsOnly( row("comment", "varchar", "", ""), - row("presto_query_id", "varchar", "", ""), + row("trino_query_id", "varchar", "", ""), row("trino_version", "varchar", "", ""), row("presto_view", "varchar", "", ""), row("transient_lastddltime", "varchar", "", ""), From 145c7d377df68a353baa4a26c47f487b0f20f06b Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Wed, 29 Nov 2023 20:15:36 -0800 Subject: [PATCH 555/587] Rename RCFile presto.writer.version property to trino.writer.version --- .../io/trino/hive/formats/rcfile/RcFileWriter.java | 10 +++++----- .../io/trino/hive/formats/rcfile/RcFileTester.java | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/trino-hive-formats/src/main/java/io/trino/hive/formats/rcfile/RcFileWriter.java b/lib/trino-hive-formats/src/main/java/io/trino/hive/formats/rcfile/RcFileWriter.java index b6ad13a25710..2c8b67db1f49 100644 --- a/lib/trino-hive-formats/src/main/java/io/trino/hive/formats/rcfile/RcFileWriter.java +++ b/lib/trino-hive-formats/src/main/java/io/trino/hive/formats/rcfile/RcFileWriter.java @@ -68,12 +68,12 @@ public class RcFileWriter private static final DataSize MIN_BUFFER_SIZE = DataSize.of(4, KILOBYTE); private static final DataSize MAX_BUFFER_SIZE = DataSize.of(1, MEGABYTE); - static final String PRESTO_RCFILE_WRITER_VERSION_METADATA_KEY = "presto.writer.version"; - static final String PRESTO_RCFILE_WRITER_VERSION; + static final String TRINO_RCFILE_WRITER_VERSION_METADATA_KEY = "trino.writer.version"; + static final String TRINO_RCFILE_WRITER_VERSION; static { String version = RcFileWriter.class.getPackage().getImplementationVersion(); - PRESTO_RCFILE_WRITER_VERSION = version == null ? "UNKNOWN" : version; + TRINO_RCFILE_WRITER_VERSION = version == null ? "UNKNOWN" : version; } private final DataOutputStream output; @@ -135,7 +135,7 @@ public RcFileWriter( requireNonNull(compressionKind, "compressionKind is null"); checkArgument(!compressionKind.equals(Optional.of(LZOP)), "LZOP cannot be use with RCFile. LZO compression can be used, but LZ4 is preferred."); requireNonNull(metadata, "metadata is null"); - checkArgument(!metadata.containsKey(PRESTO_RCFILE_WRITER_VERSION_METADATA_KEY), "Cannot set property %s", PRESTO_RCFILE_WRITER_VERSION_METADATA_KEY); + checkArgument(!metadata.containsKey(TRINO_RCFILE_WRITER_VERSION_METADATA_KEY), "Cannot set property %s", TRINO_RCFILE_WRITER_VERSION_METADATA_KEY); checkArgument(!metadata.containsKey(COLUMN_COUNT_METADATA_KEY), "Cannot set property %s", COLUMN_COUNT_METADATA_KEY); requireNonNull(targetMinRowGroupSize, "targetMinRowGroupSize is null"); requireNonNull(targetMaxRowGroupSize, "targetMaxRowGroupSize is null"); @@ -162,7 +162,7 @@ public RcFileWriter( // write metadata output.writeInt(Integer.reverseBytes(metadata.size() + 2)); writeMetadataProperty(COLUMN_COUNT_METADATA_KEY, Integer.toString(types.size())); - writeMetadataProperty(PRESTO_RCFILE_WRITER_VERSION_METADATA_KEY, PRESTO_RCFILE_WRITER_VERSION); + writeMetadataProperty(TRINO_RCFILE_WRITER_VERSION_METADATA_KEY, TRINO_RCFILE_WRITER_VERSION); for (Entry entry : metadata.entrySet()) { writeMetadataProperty(entry.getKey(), entry.getValue()); } diff --git a/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/rcfile/RcFileTester.java b/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/rcfile/RcFileTester.java index bd8ea5f9b13f..af2c10223a76 100644 --- a/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/rcfile/RcFileTester.java +++ b/lib/trino-hive-formats/src/test/java/io/trino/hive/formats/rcfile/RcFileTester.java @@ -94,8 +94,8 @@ import static io.trino.hive.formats.FormatTestUtils.writeTrinoValue; import static io.trino.hive.formats.ReadWriteUtils.findFirstSyncPosition; import static io.trino.hive.formats.compression.CompressionKind.LZOP; -import static io.trino.hive.formats.rcfile.RcFileWriter.PRESTO_RCFILE_WRITER_VERSION; -import static io.trino.hive.formats.rcfile.RcFileWriter.PRESTO_RCFILE_WRITER_VERSION_METADATA_KEY; +import static io.trino.hive.formats.rcfile.RcFileWriter.TRINO_RCFILE_WRITER_VERSION; +import static io.trino.hive.formats.rcfile.RcFileWriter.TRINO_RCFILE_WRITER_VERSION_METADATA_KEY; import static io.trino.spi.type.StandardTypes.MAP; import static io.trino.testing.TestingConnectorSession.SESSION; import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; @@ -340,7 +340,7 @@ private void assertRoundTrip(Type type, Iterable writeValues, Set ski Map expectedMetadata = ImmutableMap.builder() .putAll(metadata) - .put(PRESTO_RCFILE_WRITER_VERSION_METADATA_KEY, PRESTO_RCFILE_WRITER_VERSION) + .put(TRINO_RCFILE_WRITER_VERSION_METADATA_KEY, TRINO_RCFILE_WRITER_VERSION) .buildOrThrow(); assertFileContentsNew(type, tempFile, format, finalValues, false, expectedMetadata); From 86c0c19cad9edfb26097ae4da80dbf5ab459977a Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Wed, 29 Nov 2023 20:12:06 -0800 Subject: [PATCH 556/587] Rename presto to trino in methods names and comments --- .../trino/plugin/accumulo/conf/AccumuloTableProperties.java | 2 +- .../src/main/java/io/trino/plugin/hive/acid/AcidSchema.java | 2 +- .../java/io/trino/plugin/hive/metastore/HivePrincipal.java | 6 +++--- .../java/io/trino/plugin/hive/orc/OrcFileWriterFactory.java | 4 ++-- .../trino/plugin/redshift/ImplementRedshiftAvgDecimal.java | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/plugin/trino-accumulo/src/main/java/io/trino/plugin/accumulo/conf/AccumuloTableProperties.java b/plugin/trino-accumulo/src/main/java/io/trino/plugin/accumulo/conf/AccumuloTableProperties.java index 8dcbfe351a23..8ec8b25c528f 100644 --- a/plugin/trino-accumulo/src/main/java/io/trino/plugin/accumulo/conf/AccumuloTableProperties.java +++ b/plugin/trino-accumulo/src/main/java/io/trino/plugin/accumulo/conf/AccumuloTableProperties.java @@ -138,7 +138,7 @@ public static Optional>> getColumnMapping( } // Parse out the column mapping - // This is a comma-delimited list of "(presto, column:accumulo, fam:accumulo qualifier)" triplets + // This is a comma-delimited list of "(trinoName, column:accumulo, fam:accumulo qualifier)" triplets ImmutableMap.Builder> mapping = ImmutableMap.builder(); for (String m : COMMA_SPLITTER.split(strMapping)) { String[] tokens = Iterables.toArray(COLON_SPLITTER.split(m), String.class); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/acid/AcidSchema.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/acid/AcidSchema.java index 149cdb2a2c3e..b0bba893d299 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/acid/AcidSchema.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/acid/AcidSchema.java @@ -92,7 +92,7 @@ public static List createAcidColumnHiveTypes(HiveType rowType) return ImmutableList.of(HIVE_INT, HIVE_LONG, HIVE_INT, HIVE_LONG, HIVE_LONG, rowType); } - public static List createAcidColumnPrestoTypes(Type rowType) + public static List createAcidColumnTrinoTypes(Type rowType) { return ImmutableList.of(INTEGER, BIGINT, INTEGER, BIGINT, BIGINT, rowType); } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HivePrincipal.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HivePrincipal.java index f3ae27ea0257..246b603365db 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HivePrincipal.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/HivePrincipal.java @@ -110,9 +110,9 @@ public boolean equals(Object o) if (o == null || getClass() != o.getClass()) { return false; } - HivePrincipal prestoPrincipal = (HivePrincipal) o; - return type == prestoPrincipal.type && - Objects.equals(name, prestoPrincipal.name); + HivePrincipal hivePrincipal = (HivePrincipal) o; + return type == hivePrincipal.type && + Objects.equals(name, hivePrincipal.name); } @Override diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcFileWriterFactory.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcFileWriterFactory.java index 247f7881824e..808a252e4189 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcFileWriterFactory.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/orc/OrcFileWriterFactory.java @@ -63,7 +63,7 @@ import static io.trino.plugin.hive.HiveSessionProperties.getTimestampPrecision; import static io.trino.plugin.hive.HiveSessionProperties.isOrcOptimizedWriterValidate; import static io.trino.plugin.hive.acid.AcidSchema.ACID_COLUMN_NAMES; -import static io.trino.plugin.hive.acid.AcidSchema.createAcidColumnPrestoTypes; +import static io.trino.plugin.hive.acid.AcidSchema.createAcidColumnTrinoTypes; import static io.trino.plugin.hive.acid.AcidSchema.createRowType; import static io.trino.plugin.hive.util.HiveClassNames.ORC_OUTPUT_FORMAT_CLASS; import static io.trino.plugin.hive.util.HiveUtil.getColumnNames; @@ -177,7 +177,7 @@ public Optional createFileWriter( // by bucket and writeId. Type rowType = createRowType(fileColumnNames, fileColumnTypes); fileColumnNames = ACID_COLUMN_NAMES; - fileColumnTypes = createAcidColumnPrestoTypes(rowType); + fileColumnTypes = createAcidColumnTrinoTypes(rowType); } return Optional.of(new OrcFileWriter( diff --git a/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/ImplementRedshiftAvgDecimal.java b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/ImplementRedshiftAvgDecimal.java index 4b8fa78c330d..50d5ff7a238a 100644 --- a/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/ImplementRedshiftAvgDecimal.java +++ b/plugin/trino-redshift/src/main/java/io/trino/plugin/redshift/ImplementRedshiftAvgDecimal.java @@ -62,7 +62,7 @@ public Optional rewrite(AggregateFunction aggregateFunction, Cap ParameterizedExpression rewrittenArgument = context.rewriteExpression(input).orElseThrow(); - // When decimal type has maximum precision we can get result that is not matching Presto avg semantics. + // When decimal type has maximum precision we can get result that is not matching Trino avg semantics. if (type.getPrecision() == REDSHIFT_MAX_DECIMAL_PRECISION) { return Optional.of(new JdbcExpression( format("avg(CAST(%s AS decimal(%s, %s)))", rewrittenArgument.expression(), type.getPrecision(), type.getScale()), @@ -71,7 +71,7 @@ public Optional rewrite(AggregateFunction aggregateFunction, Cap } // Redshift avg function rounds down resulting decimal. - // To match Presto avg semantics, we extend scale by 1 and round result to target scale. + // To match Trino avg semantics, we extend scale by 1 and round result to target scale. return Optional.of(new JdbcExpression( format("round(avg(CAST(%s AS decimal(%s, %s))), %s)", rewrittenArgument.expression(), type.getPrecision() + 1, type.getScale() + 1, type.getScale()), rewrittenArgument.parameters(), From 62cef1a807fae7f4891a122d2f88968389312cd4 Mon Sep 17 00:00:00 2001 From: Marcin Rusek Date: Tue, 7 Nov 2023 12:35:12 +0100 Subject: [PATCH 557/587] Removed support for creating table with location in DeltaLake --- docs/src/main/sphinx/connector/delta-lake.md | 19 +--- .../plugin/deltalake/DeltaLakeConfig.java | 20 +--- .../plugin/deltalake/DeltaLakeMetadata.java | 11 +-- .../deltalake/DeltaLakeSessionProperties.java | 12 --- .../plugin/deltalake/TestDeltaLakeConfig.java | 3 - .../deltalake/TestDeltaLakeConnectorTest.java | 15 +++ ...LegacyCreateTableWithExistingLocation.java | 96 ------------------- 7 files changed, 25 insertions(+), 151 deletions(-) delete mode 100644 plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeLegacyCreateTableWithExistingLocation.java diff --git a/docs/src/main/sphinx/connector/delta-lake.md b/docs/src/main/sphinx/connector/delta-lake.md index d9c86b6c2452..a77464664f87 100644 --- a/docs/src/main/sphinx/connector/delta-lake.md +++ b/docs/src/main/sphinx/connector/delta-lake.md @@ -516,27 +516,16 @@ When Delta Lake tables exist in storage but not in the metastore, Trino can be used to register the tables: ``` -CREATE TABLE example.default.example_table ( - dummy BIGINT -) -WITH ( - location = '...' -) +CALL example.system.register_table(schema_name => 'testdb', table_name => 'example_table', table_location => 's3://my-bucket/a/path') ``` -Columns listed in the DDL, such as `dummy` in the preceding example, are -ignored. The table schema is read from the transaction log instead. If the +The table schema is read from the transaction log instead. If the schema is changed by an external system, Trino automatically uses the new schema. :::{warning} -Using `CREATE TABLE` with an existing table content is deprecated, instead -use the `system.register_table` procedure. The `CREATE TABLE ... WITH -(location=...)` syntax can be temporarily re-enabled using the -`delta.legacy-create-table-with-existing-location.enabled` catalog -configuration property or -`legacy_create_table_with_existing_location_enabled` catalog session -property. +Using ``CREATE TABLE`` with an existing table content is disallowed, +use the ``system.register_table`` procedure instead. ::: If the specified location does not already contain a Delta table, the connector diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeConfig.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeConfig.java index f92a5b893b11..87ff4190d60f 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeConfig.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeConfig.java @@ -36,7 +36,9 @@ import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.SECONDS; -@DefunctConfig("delta.experimental.ignore-checkpoint-write-failures") +@DefunctConfig({ + "delta.experimental.ignore-checkpoint-write-failures", + "delta.legacy-create-table-with-existing-location.enabled"}) public class DeltaLakeConfig { public static final String EXTENDED_STATISTICS_ENABLED = "delta.extended-statistics.enabled"; @@ -75,7 +77,6 @@ public class DeltaLakeConfig private String parquetTimeZone = TimeZone.getDefault().getID(); private DataSize targetMaxFileSize = DataSize.of(1, GIGABYTE); private boolean uniqueTableLocation = true; - private boolean legacyCreateTableWithExistingLocationEnabled; private boolean registerTableProcedureEnabled; private boolean projectionPushdownEnabled = true; private boolean queryPartitionFilterRequired; @@ -462,21 +463,6 @@ public DeltaLakeConfig setUniqueTableLocation(boolean uniqueTableLocation) return this; } - @Deprecated - public boolean isLegacyCreateTableWithExistingLocationEnabled() - { - return legacyCreateTableWithExistingLocationEnabled; - } - - @Deprecated - @Config("delta.legacy-create-table-with-existing-location.enabled") - @ConfigDescription("Enable using the CREATE TABLE statement to register an existing table") - public DeltaLakeConfig setLegacyCreateTableWithExistingLocationEnabled(boolean legacyCreateTableWithExistingLocationEnabled) - { - this.legacyCreateTableWithExistingLocationEnabled = legacyCreateTableWithExistingLocationEnabled; - return this; - } - public boolean isRegisterTableProcedureEnabled() { return registerTableProcedureEnabled; diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java index 4cf3e67d2b0d..6c71ba67e8f3 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java @@ -201,7 +201,6 @@ import static io.trino.plugin.deltalake.DeltaLakeSessionProperties.getHiveCatalogName; import static io.trino.plugin.deltalake.DeltaLakeSessionProperties.isCollectExtendedStatisticsColumnStatisticsOnWrite; import static io.trino.plugin.deltalake.DeltaLakeSessionProperties.isExtendedStatisticsEnabled; -import static io.trino.plugin.deltalake.DeltaLakeSessionProperties.isLegacyCreateTableWithExistingLocationEnabled; import static io.trino.plugin.deltalake.DeltaLakeSessionProperties.isProjectionPushdownEnabled; import static io.trino.plugin.deltalake.DeltaLakeSessionProperties.isQueryPartitionFilterRequired; import static io.trino.plugin.deltalake.DeltaLakeSessionProperties.isTableStatisticsEnabled; @@ -968,13 +967,9 @@ public void createTable(ConnectorSession session, ConnectorTableMetadata tableMe transactionLogWriter.flush(); } else { - if (!isLegacyCreateTableWithExistingLocationEnabled(session)) { - throw new TrinoException( - NOT_SUPPORTED, - "Using CREATE TABLE with an existing table content is deprecated, instead use the system.register_table() procedure." + - " The CREATE TABLE syntax can be temporarily re-enabled using the 'delta.legacy-create-table-with-existing-location.enabled' config property" + - " or 'legacy_create_table_with_existing_location_enabled' session property."); - } + throw new TrinoException( + NOT_SUPPORTED, + "Using CREATE TABLE with an existing table content is disallowed, instead use the system.register_table() procedure."); } } catch (IOException e) { diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeSessionProperties.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeSessionProperties.java index 1179ccec7cb7..8cfb02a3988b 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeSessionProperties.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeSessionProperties.java @@ -67,7 +67,6 @@ public final class DeltaLakeSessionProperties private static final String TABLE_STATISTICS_ENABLED = "statistics_enabled"; public static final String EXTENDED_STATISTICS_ENABLED = "extended_statistics_enabled"; public static final String EXTENDED_STATISTICS_COLLECT_ON_WRITE = "extended_statistics_collect_on_write"; - public static final String LEGACY_CREATE_TABLE_WITH_EXISTING_LOCATION_ENABLED = "legacy_create_table_with_existing_location_enabled"; private static final String PROJECTION_PUSHDOWN_ENABLED = "projection_pushdown_enabled"; private static final String QUERY_PARTITION_FILTER_REQUIRED = "query_partition_filter_required"; private static final String CHECKPOINT_FILTERING_ENABLED = "checkpoint_filtering_enabled"; @@ -173,11 +172,6 @@ public DeltaLakeSessionProperties( "Enable collection (ANALYZE) and use of extended statistics.", deltaLakeConfig.isExtendedStatisticsEnabled(), false), - booleanProperty( - LEGACY_CREATE_TABLE_WITH_EXISTING_LOCATION_ENABLED, - "Enable using the CREATE TABLE statement to register an existing table", - deltaLakeConfig.isLegacyCreateTableWithExistingLocationEnabled(), - false), booleanProperty( EXTENDED_STATISTICS_COLLECT_ON_WRITE, "Enables automatic column level extended statistics collection on write", @@ -287,12 +281,6 @@ public static boolean isExtendedStatisticsEnabled(ConnectorSession session) return session.getProperty(EXTENDED_STATISTICS_ENABLED, Boolean.class); } - @Deprecated - public static boolean isLegacyCreateTableWithExistingLocationEnabled(ConnectorSession session) - { - return session.getProperty(LEGACY_CREATE_TABLE_WITH_EXISTING_LOCATION_ENABLED, Boolean.class); - } - public static boolean isCollectExtendedStatisticsColumnStatisticsOnWrite(ConnectorSession session) { return session.getProperty(EXTENDED_STATISTICS_COLLECT_ON_WRITE, Boolean.class); diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConfig.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConfig.java index c2ddffcc048f..4a6306dc524f 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConfig.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConfig.java @@ -67,7 +67,6 @@ public void testDefaults() .setPerTransactionMetastoreCacheMaximumSize(1000) .setTargetMaxFileSize(DataSize.of(1, GIGABYTE)) .setUniqueTableLocation(true) - .setLegacyCreateTableWithExistingLocationEnabled(false) .setRegisterTableProcedureEnabled(false) .setProjectionPushdownEnabled(true) .setQueryPartitionFilterRequired(false)); @@ -105,7 +104,6 @@ public void testExplicitPropertyMappings() .put("delta.parquet.time-zone", nonDefaultTimeZone().getID()) .put("delta.target-max-file-size", "2 GB") .put("delta.unique-table-location", "false") - .put("delta.legacy-create-table-with-existing-location.enabled", "true") .put("delta.register-table-procedure.enabled", "true") .put("delta.projection-pushdown-enabled", "false") .put("delta.query-partition-filter-required", "true") @@ -140,7 +138,6 @@ public void testExplicitPropertyMappings() .setPerTransactionMetastoreCacheMaximumSize(500) .setTargetMaxFileSize(DataSize.of(2, GIGABYTE)) .setUniqueTableLocation(false) - .setLegacyCreateTableWithExistingLocationEnabled(true) .setRegisterTableProcedureEnabled(true) .setProjectionPushdownEnabled(false) .setQueryPartitionFilterRequired(true); diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConnectorTest.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConnectorTest.java index 40776b359d1e..fc51ec0c7639 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConnectorTest.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeConnectorTest.java @@ -2030,6 +2030,21 @@ public void testSettingChangeDataFeedEnabledProperty() .contains("change_data_feed_enabled = true"); } + @Test + public void testCreateTableWithExistingLocation() + { + String tableName = "test_legacy_create_table_" + randomNameSuffix(); + + assertQuerySucceeds("CREATE TABLE " + tableName + " AS SELECT 1 as a, 'INDIA' as b, true as c"); + assertQuery("SELECT * FROM " + tableName, "VALUES (1, 'INDIA', true)"); + + String tableLocation = (String) computeScalar("SELECT DISTINCT regexp_replace(\"$path\", '/[^/]*$', '') FROM " + tableName); + assertUpdate("CALL system.unregister_table(CURRENT_SCHEMA, '" + tableName + "')"); + + assertQueryFails(format("CREATE TABLE %s (dummy int) with (location = '%s')", tableName, tableLocation), + ".*Using CREATE TABLE with an existing table content is disallowed.*"); + } + @Test public void testProjectionPushdownOnPartitionedTables() { diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeLegacyCreateTableWithExistingLocation.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeLegacyCreateTableWithExistingLocation.java deleted file mode 100644 index eb7e873011d2..000000000000 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeLegacyCreateTableWithExistingLocation.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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 io.trino.plugin.deltalake; - -import com.google.common.collect.ImmutableMap; -import io.trino.Session; -import io.trino.plugin.hive.metastore.HiveMetastore; -import io.trino.testing.AbstractTestQueryFramework; -import io.trino.testing.QueryRunner; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; - -import static com.google.common.io.MoreFiles.deleteRecursively; -import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; -import static io.trino.plugin.deltalake.DeltaLakeQueryRunner.DELTA_CATALOG; -import static io.trino.plugin.deltalake.DeltaLakeQueryRunner.createDeltaLakeQueryRunner; -import static io.trino.plugin.hive.metastore.file.TestingFileHiveMetastore.createTestingFileHiveMetastore; -import static io.trino.testing.TestingNames.randomNameSuffix; -import static java.lang.String.format; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; - -@TestInstance(PER_CLASS) -public class TestDeltaLakeLegacyCreateTableWithExistingLocation - extends AbstractTestQueryFramework -{ - private File dataDirectory; - private HiveMetastore metastore; - - @Override - protected QueryRunner createQueryRunner() - throws Exception - { - this.dataDirectory = Files.createTempDirectory("test_delta_lake").toFile(); - this.metastore = createTestingFileHiveMetastore(dataDirectory); - - return createDeltaLakeQueryRunner( - DELTA_CATALOG, - ImmutableMap.of(), - ImmutableMap.of( - "delta.unique-table-location", "true", - "hive.metastore", "file", - "hive.metastore.catalog.dir", dataDirectory.getPath())); - } - - @AfterAll - public void tearDown() - throws IOException - { - if (dataDirectory != null) { - deleteRecursively(dataDirectory.toPath(), ALLOW_INSECURE); - } - } - - @Test - public void testLegacyCreateTable() - { - String tableName = "test_legacy_create_table_" + randomNameSuffix(); - - assertQuerySucceeds("CREATE TABLE " + tableName + " AS SELECT 1 as a, 'INDIA' as b, true as c"); - assertQuery("SELECT * FROM " + tableName, "VALUES (1, 'INDIA', true)"); - - String tableLocation = (String) computeScalar("SELECT DISTINCT regexp_replace(\"$path\", '/[^/]*$', '') FROM " + tableName); - metastore.dropTable("tpch", tableName, false); - - assertQueryFails(format("CREATE TABLE %s.%s.%s (dummy int) with (location = '%s')", DELTA_CATALOG, "tpch", tableName, tableLocation), - ".*Using CREATE TABLE with an existing table content is deprecated.*"); - - Session sessionWithLegacyCreateTableEnabled = Session - .builder(getSession()) - .setCatalogSessionProperty(DELTA_CATALOG, "legacy_create_table_with_existing_location_enabled", "true") - .build(); - assertQuerySucceeds(sessionWithLegacyCreateTableEnabled, format("CREATE TABLE %s.%s.%s (dummy int) with (location = '%s')", DELTA_CATALOG, "tpch", tableName, tableLocation)); - - assertQuery("SELECT * FROM " + tableName, "VALUES (1, 'INDIA', true)"); - - assertUpdate("DROP TABLE " + tableName); - assertThat(metastore.getTable("tpch", tableName)).as("Table should be dropped from metastore").isEmpty(); - } -} From 78ccc2db5271925ae0b9d49a094c5e615d832c76 Mon Sep 17 00:00:00 2001 From: chenjian2664 Date: Thu, 19 Oct 2023 21:56:36 +0800 Subject: [PATCH 558/587] Implement NOT, IS NULL, NOT IS NULL pushdown in Ignite connector Co-Authored-By: Yuya Ebihara --- plugin/trino-ignite/pom.xml | 6 ++ .../io/trino/plugin/ignite/IgniteClient.java | 5 ++ .../trino/plugin/ignite/TestIgniteClient.java | 80 +++++++++++++++++++ .../ignite/TestIgniteConnectorTest.java | 56 +++++++++++++ 4 files changed, 147 insertions(+) diff --git a/plugin/trino-ignite/pom.xml b/plugin/trino-ignite/pom.xml index 4c122a6c4f41..211ae7925dc0 100644 --- a/plugin/trino-ignite/pom.xml +++ b/plugin/trino-ignite/pom.xml @@ -171,6 +171,12 @@ test + + io.trino + trino-parser + test + + io.trino trino-plugin-toolkit diff --git a/plugin/trino-ignite/src/main/java/io/trino/plugin/ignite/IgniteClient.java b/plugin/trino-ignite/src/main/java/io/trino/plugin/ignite/IgniteClient.java index ea579992270b..9309cd5f259c 100644 --- a/plugin/trino-ignite/src/main/java/io/trino/plugin/ignite/IgniteClient.java +++ b/plugin/trino-ignite/src/main/java/io/trino/plugin/ignite/IgniteClient.java @@ -47,6 +47,7 @@ import io.trino.plugin.jdbc.aggregation.ImplementSum; import io.trino.plugin.jdbc.expression.JdbcConnectorExpressionRewriterBuilder; import io.trino.plugin.jdbc.expression.ParameterizedExpression; +import io.trino.plugin.jdbc.expression.RewriteComparison; import io.trino.plugin.jdbc.logging.RemoteQueryModifier; import io.trino.spi.TrinoException; import io.trino.spi.connector.AggregateFunction; @@ -162,8 +163,12 @@ public IgniteClient( JdbcTypeHandle bigintTypeHandle = new JdbcTypeHandle(Types.BIGINT, Optional.of("bigint"), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty()); this.connectorExpressionRewriter = JdbcConnectorExpressionRewriterBuilder.newBuilder() + .add(new RewriteComparison(ImmutableSet.of(RewriteComparison.ComparisonOperator.EQUAL, RewriteComparison.ComparisonOperator.NOT_EQUAL))) .addStandardRules(this::quoted) .map("$like(value: varchar, pattern: varchar): boolean").to("value LIKE pattern") + .map("$not($is_null(value))").to("value IS NOT NULL") + .map("$not(value: boolean)").to("NOT value") + .map("$is_null(value)").to("value IS NULL") .build(); this.aggregateFunctionRewriter = new AggregateFunctionRewriter<>( connectorExpressionRewriter, diff --git a/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteClient.java b/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteClient.java index c7a491594cac..955d0519177d 100644 --- a/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteClient.java +++ b/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteClient.java @@ -21,11 +21,21 @@ import io.trino.plugin.jdbc.JdbcColumnHandle; import io.trino.plugin.jdbc.JdbcExpression; import io.trino.plugin.jdbc.JdbcTypeHandle; +import io.trino.plugin.jdbc.expression.ParameterizedExpression; import io.trino.plugin.jdbc.logging.RemoteQueryModifier; import io.trino.spi.connector.AggregateFunction; import io.trino.spi.connector.ColumnHandle; import io.trino.spi.expression.ConnectorExpression; import io.trino.spi.expression.Variable; +import io.trino.spi.type.Type; +import io.trino.sql.planner.ConnectorExpressionTranslator; +import io.trino.sql.planner.Symbol; +import io.trino.sql.planner.TypeProvider; +import io.trino.sql.tree.Expression; +import io.trino.sql.tree.IsNotNullPredicate; +import io.trino.sql.tree.IsNullPredicate; +import io.trino.sql.tree.NotExpression; +import io.trino.sql.tree.SymbolReference; import org.junit.jupiter.api.Test; import java.sql.Types; @@ -33,9 +43,14 @@ import java.util.Map; import java.util.Optional; +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static io.trino.SessionTestUtils.TEST_SESSION; import static io.trino.spi.type.BigintType.BIGINT; import static io.trino.spi.type.BooleanType.BOOLEAN; import static io.trino.spi.type.DoubleType.DOUBLE; +import static io.trino.spi.type.VarcharType.createVarcharType; +import static io.trino.sql.planner.TestingPlannerContext.PLANNER_CONTEXT; +import static io.trino.sql.planner.TypeAnalyzer.createTestingTypeAnalyzer; import static io.trino.testing.TestingConnectorSession.SESSION; import static org.assertj.core.api.Assertions.assertThat; @@ -55,6 +70,13 @@ public class TestIgniteClient .setJdbcTypeHandle(new JdbcTypeHandle(Types.DOUBLE, Optional.of("double"), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty())) .build(); + private static final JdbcColumnHandle VARCHAR_COLUMN = + JdbcColumnHandle.builder() + .setColumnName("c_varchar") + .setColumnType(createVarcharType(10)) + .setJdbcTypeHandle(new JdbcTypeHandle(Types.VARCHAR, Optional.of("varchar"), Optional.of(10), Optional.empty(), Optional.empty(), Optional.empty())) + .build(); + public static final JdbcClient JDBC_CLIENT = new IgniteClient( new BaseJdbcConfig(), session -> { throw new UnsupportedOperationException(); }, @@ -144,6 +166,64 @@ public void testImplementSum() Optional.empty()); // filter not supported } + @Test + public void testConvertIsNull() + { + // c_varchar IS NULL + ParameterizedExpression converted = JDBC_CLIENT.convertPredicate(SESSION, + translateToConnectorExpression( + new IsNullPredicate( + new SymbolReference("c_varchar_symbol")), + Map.of("c_varchar_symbol", VARCHAR_COLUMN.getColumnType())), + Map.of("c_varchar_symbol", VARCHAR_COLUMN)) + .orElseThrow(); + assertThat(converted.expression()).isEqualTo("(`c_varchar`) IS NULL"); + assertThat(converted.parameters()).isEmpty(); + } + + @Test + public void testConvertIsNotNull() + { + // c_varchar IS NOT NULL + ParameterizedExpression converted = JDBC_CLIENT.convertPredicate(SESSION, + translateToConnectorExpression( + new IsNotNullPredicate( + new SymbolReference("c_varchar_symbol")), + Map.of("c_varchar_symbol", VARCHAR_COLUMN.getColumnType())), + Map.of("c_varchar_symbol", VARCHAR_COLUMN)) + .orElseThrow(); + assertThat(converted.expression()).isEqualTo("(`c_varchar`) IS NOT NULL"); + assertThat(converted.parameters()).isEmpty(); + } + + @Test + public void testConvertNotExpression() + { + // NOT(expression) + ParameterizedExpression converted = JDBC_CLIENT.convertPredicate(SESSION, + translateToConnectorExpression( + new NotExpression( + new IsNotNullPredicate( + new SymbolReference("c_varchar_symbol"))), + Map.of("c_varchar_symbol", VARCHAR_COLUMN.getColumnType())), + Map.of("c_varchar_symbol", VARCHAR_COLUMN)) + .orElseThrow(); + assertThat(converted.expression()).isEqualTo("NOT ((`c_varchar`) IS NOT NULL)"); + assertThat(converted.parameters()).isEmpty(); + } + + private ConnectorExpression translateToConnectorExpression(Expression expression, Map symbolTypes) + { + return ConnectorExpressionTranslator.translate( + TEST_SESSION, + expression, + TypeProvider.viewOf(symbolTypes.entrySet().stream() + .collect(toImmutableMap(entry -> new Symbol(entry.getKey()), Map.Entry::getValue))), + PLANNER_CONTEXT, + createTestingTypeAnalyzer(PLANNER_CONTEXT)) + .orElseThrow(); + } + private static void testImplementAggregation(AggregateFunction aggregateFunction, Map assignments, Optional expectedExpression) { Optional result = JDBC_CLIENT.implementAggregation(SESSION, aggregateFunction, assignments); diff --git a/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteConnectorTest.java b/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteConnectorTest.java index 8e834d8c2beb..0f054ccc0f5a 100644 --- a/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteConnectorTest.java +++ b/plugin/trino-ignite/src/test/java/io/trino/plugin/ignite/TestIgniteConnectorTest.java @@ -121,6 +121,62 @@ public void testLikeWithEscape() } } + @Test + public void testIsNullPredicatePushdown() + { + assertThat(query("SELECT nationkey FROM nation WHERE name IS NULL")).isFullyPushedDown(); + assertThat(query("SELECT nationkey FROM nation WHERE name IS NULL OR name = 'a' OR regionkey = 4")).isFullyPushedDown(); + + try (TestTable table = new TestTable( + getQueryRunner()::execute, + "test_is_null_predicate_pushdown", + "(a_int integer, a_varchar varchar(1))", + List.of( + "1, 'A'", + "2, 'B'", + "1, NULL", + "2, NULL"))) { + assertThat(query("SELECT a_int FROM " + table.getName() + " WHERE a_varchar IS NULL OR a_int = 1")).isFullyPushedDown(); + } + } + + @Test + public void testIsNotNullPredicatePushdown() + { + assertThat(query("SELECT nationkey FROM nation WHERE name IS NOT NULL OR regionkey = 4")).isFullyPushedDown(); + + try (TestTable table = new TestTable( + getQueryRunner()::execute, + "test_is_not_null_predicate_pushdown", + "(a_int integer, a_varchar varchar(1))", + List.of( + "1, 'A'", + "2, 'B'", + "1, NULL", + "2, NULL"))) { + assertThat(query("SELECT a_int FROM " + table.getName() + " WHERE a_varchar IS NOT NULL OR a_int = 1")).isFullyPushedDown(); + } + } + + @Test + public void testNotExpressionPushdown() + { + assertThat(query("SELECT nationkey FROM nation WHERE NOT(name LIKE '%A%')")).isFullyPushedDown(); + + try (TestTable table = new TestTable( + getQueryRunner()::execute, + "test_is_not_predicate_pushdown", + "(a_int integer, a_varchar varchar(2))", + List.of( + "1, 'Aa'", + "2, 'Bb'", + "1, NULL", + "2, NULL"))) { + assertThat(query("SELECT a_int FROM " + table.getName() + " WHERE NOT(a_varchar LIKE 'A%') OR a_int = 2")).isFullyPushedDown(); + assertThat(query("SELECT a_int FROM " + table.getName() + " WHERE NOT(a_varchar LIKE 'A%' OR a_int = 2)")).isFullyPushedDown(); + } + } + @Test public void testDatabaseMetadataSearchEscapedWildCardCharacters() { From 1c5e5eb0d3949ff5d6f6bfd42a72428b4a2d5668 Mon Sep 17 00:00:00 2001 From: Piotr Findeisen Date: Wed, 29 Nov 2023 10:08:42 +0100 Subject: [PATCH 559/587] Allow differentiating materialized views from tables efficiently Tools often need to list tables/relations and know whether they are regular tables, views, or materialized views. For example, on a table one can `SHOW CREATE TABLE` or `DROP TABLE`, but syntax for views and materialized views is different. Before this change, to differentiate different relations types, tools need to join `information_schema.tables` with `system.metadata.materialized_views`. This is effective, but not efficient: `system.metadata.materialized_views` contains much more information. To address the inefficiency, this commit introduces a new column to `information_schema.tables`. To guarantee SQL spec future-compatibility, the column is hidden and prefixed with `trino_` --- .../InformationSchemaPageSource.java | 31 ++++++++++++------- .../InformationSchemaTable.java | 1 + .../io/trino/testing/BaseConnectorTest.java | 8 ++--- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/connector/informationschema/InformationSchemaPageSource.java b/core/trino-main/src/main/java/io/trino/connector/informationschema/InformationSchemaPageSource.java index 143cc6493c63..b7a7f6ee4186 100644 --- a/core/trino-main/src/main/java/io/trino/connector/informationschema/InformationSchemaPageSource.java +++ b/core/trino-main/src/main/java/io/trino/connector/informationschema/InformationSchemaPageSource.java @@ -277,34 +277,43 @@ private void addColumnsRecords(QualifiedTablePrefix prefix) private void addTablesRecords(QualifiedTablePrefix prefix) { - boolean needsTableType = requiredColumns.contains("table_type"); + boolean needsTableType = requiredColumns.contains("table_type") || requiredColumns.contains("trino_relation_type"); Set relations; - Set views; + Map relationTypes; if (needsTableType) { - Map relationTypes = getRelationTypes(session, metadata, accessControl, prefix); + relationTypes = getRelationTypes(session, metadata, accessControl, prefix); relations = relationTypes.keySet(); - views = relationTypes.entrySet().stream() - .filter(entry -> entry.getValue() == RelationType.VIEW) - .map(Entry::getKey) - .collect(toImmutableSet()); } else { relations = listTables(session, metadata, accessControl, prefix); - views = Set.of(); + relationTypes = null; } - // TODO (https://github.com/trinodb/trino/issues/8207) define a type for materialized views for (SchemaTableName name : relations) { String type = null; + String trinoRelationType = null; if (needsTableType) { - // if table and view names overlap, the view wins - type = views.contains(name) ? "VIEW" : "BASE TABLE"; + switch (relationTypes.get(name)) { + case TABLE -> { + type = "BASE TABLE"; + trinoRelationType = type; + } + case VIEW -> { + type = "VIEW"; + trinoRelationType = type; + } + case MATERIALIZED_VIEW -> { + type = "BASE TABLE"; + trinoRelationType = "MATERIALIZED VIEW"; + } + } } addRecord( prefix.getCatalogName(), name.getSchemaName(), name.getTableName(), type, + trinoRelationType, null); if (isLimitExhausted()) { return; diff --git a/core/trino-main/src/main/java/io/trino/connector/informationschema/InformationSchemaTable.java b/core/trino-main/src/main/java/io/trino/connector/informationschema/InformationSchemaTable.java index 605076aa3841..e45bb8594d74 100644 --- a/core/trino-main/src/main/java/io/trino/connector/informationschema/InformationSchemaTable.java +++ b/core/trino-main/src/main/java/io/trino/connector/informationschema/InformationSchemaTable.java @@ -47,6 +47,7 @@ public enum InformationSchemaTable .column("table_schema", createUnboundedVarcharType()) .column("table_name", createUnboundedVarcharType()) .column("table_type", createUnboundedVarcharType()) + .hiddenColumn("trino_relation_type", createUnboundedVarcharType()) .hiddenColumn("table_comment", createUnboundedVarcharType()) // MySQL compatible .build()), VIEWS(table("views") diff --git a/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java b/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java index dd00a554460a..214130af3770 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java @@ -1052,15 +1052,15 @@ public void testMaterializedView() .containsAll("VALUES '" + view.getObjectName() + "'"); // information_schema.tables without table_name filter so that ConnectorMetadata.listViews is exercised assertThat(query( - "SELECT table_name, table_type FROM information_schema.tables " + + "SELECT table_name, table_type, trino_relation_type FROM information_schema.tables " + "WHERE table_schema = '" + view.getSchemaName() + "'")) .skippingTypesCheck() - .containsAll("VALUES ('" + view.getObjectName() + "', 'BASE TABLE')"); + .containsAll("VALUES ('" + view.getObjectName() + "', 'BASE TABLE', 'MATERIALIZED VIEW')"); // information_schema.tables with table_name filter assertQuery( - "SELECT table_name, table_type FROM information_schema.tables " + + "SELECT table_name, table_type, trino_relation_type FROM information_schema.tables " + "WHERE table_schema = '" + view.getSchemaName() + "' and table_name = '" + view.getObjectName() + "'", - "VALUES ('" + view.getObjectName() + "', 'BASE TABLE')"); + "VALUES ('" + view.getObjectName() + "', 'BASE TABLE', 'MATERIALIZED VIEW')"); // system.jdbc.tables without filter assertThat(query("SELECT table_schem, table_name, table_type FROM system.jdbc.tables")) From 89a703e182e4810c902e79527d4a5fdba124315c Mon Sep 17 00:00:00 2001 From: Grant Nicholas Date: Thu, 30 Nov 2023 15:03:57 -0600 Subject: [PATCH 560/587] Make S3FileSystemFactory use configured AWS access keys for STS calls --- .../trino/filesystem/s3/S3FileSystemFactory.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3FileSystemFactory.java b/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3FileSystemFactory.java index 2b525d12ba70..81dac4930095 100644 --- a/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3FileSystemFactory.java +++ b/lib/trino-filesystem-s3/src/main/java/io/trino/filesystem/s3/S3FileSystemFactory.java @@ -55,10 +55,8 @@ public S3FileSystemFactory(OpenTelemetry openTelemetry, S3FileSystemConfig confi .build().newExecutionInterceptor()) .build()); - if ((config.getAwsAccessKey() != null) && (config.getAwsSecretKey() != null)) { - s3.credentialsProvider(StaticCredentialsProvider.create( - AwsBasicCredentials.create(config.getAwsAccessKey(), config.getAwsSecretKey()))); - } + Optional staticCredentialsProvider = getStaticCredentialsProvider(config); + staticCredentialsProvider.ifPresent(s3::credentialsProvider); Optional.ofNullable(config.getRegion()).map(Region::of).ifPresent(s3::region); Optional.ofNullable(config.getEndpoint()).map(URI::create).ifPresent(s3::endpointOverride); @@ -70,6 +68,7 @@ public S3FileSystemFactory(OpenTelemetry openTelemetry, S3FileSystemConfig confi Optional.ofNullable(config.getStsRegion()) .or(() -> Optional.ofNullable(config.getRegion())) .map(Region::of).ifPresent(sts::region); + staticCredentialsProvider.ifPresent(sts::credentialsProvider); s3.credentialsProvider(StsAssumeRoleCredentialsProvider.builder() .refreshRequest(request -> request @@ -115,4 +114,13 @@ public TrinoFileSystem create(ConnectorIdentity identity) { return new S3FileSystem(client, context); } + + private static Optional getStaticCredentialsProvider(S3FileSystemConfig config) + { + if ((config.getAwsAccessKey() != null) || (config.getAwsSecretKey() != null)) { + return Optional.of(StaticCredentialsProvider.create( + AwsBasicCredentials.create(config.getAwsAccessKey(), config.getAwsSecretKey()))); + } + return Optional.empty(); + } } From e0ad7bd291d0bc10662c18189d3a4096ba4a4042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Osipiuk?= Date: Thu, 30 Nov 2023 19:09:39 +0100 Subject: [PATCH 561/587] Fix FaultTolerantPartitioningScheme obtaining logic With caching logic used when FaultTolerantPartitioningScheme was obtained it was possible to get FaultTolerantPartitioningScheme with wrongly set partition count. In cache keyed on PartitioningHandle we could store a FaultTolerantPartitioningScheme object obtained for specific partitionCount value. Later on when we asked for same handle but without specifying partitionCount we still got cached FaultTolerantPartitioningScheme. This commit changes cache key to inclue partitionCount. --- .../FaultTolerantPartitioningScheme.java | 9 -------- ...aultTolerantPartitioningSchemeFactory.java | 22 +++++++++++-------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/FaultTolerantPartitioningScheme.java b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/FaultTolerantPartitioningScheme.java index 9f4ff0d00b7a..04bc2a290071 100644 --- a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/FaultTolerantPartitioningScheme.java +++ b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/FaultTolerantPartitioningScheme.java @@ -96,15 +96,6 @@ public Optional> getPartitionToNodeMap() return partitionToNodeMap; } - public FaultTolerantPartitioningScheme withPartitionCount(int partitionCount) - { - return new FaultTolerantPartitioningScheme( - partitionCount, - this.bucketToPartitionMap, - this.splitToBucketFunction, - this.partitionToNodeMap); - } - @Override public String toString() { diff --git a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/FaultTolerantPartitioningSchemeFactory.java b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/FaultTolerantPartitioningSchemeFactory.java index d81a4f3195ed..8709cd368f20 100644 --- a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/FaultTolerantPartitioningSchemeFactory.java +++ b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/FaultTolerantPartitioningSchemeFactory.java @@ -43,7 +43,7 @@ public class FaultTolerantPartitioningSchemeFactory private final Session session; private final int maxPartitionCount; - private final Map cache = new HashMap<>(); + private final Map cache = new HashMap<>(); public FaultTolerantPartitioningSchemeFactory(NodePartitioningManager nodePartitioningManager, Session session, int maxPartitionCount) { @@ -54,18 +54,13 @@ public FaultTolerantPartitioningSchemeFactory(NodePartitioningManager nodePartit public FaultTolerantPartitioningScheme get(PartitioningHandle handle, Optional partitionCount) { - FaultTolerantPartitioningScheme result = cache.get(handle); + CacheKey cacheKey = new CacheKey(handle, partitionCount); + FaultTolerantPartitioningScheme result = cache.get(cacheKey); if (result == null) { // Avoid using computeIfAbsent as the "get" method is called recursively from the "create" method result = create(handle, partitionCount); - cache.put(handle, result); + cache.put(cacheKey, result); } - else if (partitionCount.isPresent()) { - // With runtime adaptive partitioning, it's no longer guaranteed that the same handle will always map to - // the same partition count. Therefore, use the supplied `partitionCount` as the source of truth. - result = result.withPartitionCount(partitionCount.get()); - } - return result; } @@ -143,4 +138,13 @@ private static FaultTolerantPartitioningScheme createArbitraryConnectorSpecificS Optional.of(splitToBucket), Optional.empty()); } + + private record CacheKey(PartitioningHandle handle, Optional partitionCount) + { + private CacheKey + { + requireNonNull(handle, "handle is null"); + requireNonNull(partitionCount, "partitionCount is null"); + } + } } From 3801f547f25ca12b1fe6c00ecabe6f13d03cc263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Osipiuk?= Date: Fri, 1 Dec 2023 12:35:07 +0100 Subject: [PATCH 562/587] Add sanity check --- .../FaultTolerantPartitioningSchemeFactory.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/FaultTolerantPartitioningSchemeFactory.java b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/FaultTolerantPartitioningSchemeFactory.java index 8709cd368f20..509a8c4af086 100644 --- a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/FaultTolerantPartitioningSchemeFactory.java +++ b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/FaultTolerantPartitioningSchemeFactory.java @@ -32,6 +32,7 @@ import java.util.function.ToIntFunction; import java.util.stream.IntStream; +import static com.google.common.base.Verify.verify; import static io.trino.sql.planner.SystemPartitioningHandle.FIXED_HASH_DISTRIBUTION; import static io.trino.sql.planner.SystemPartitioningHandle.SCALED_WRITER_HASH_DISTRIBUTION; import static java.util.Objects.requireNonNull; @@ -59,6 +60,13 @@ public FaultTolerantPartitioningScheme get(PartitioningHandle handle, Optional Date: Fri, 1 Dec 2023 13:07:19 +0100 Subject: [PATCH 563/587] Extend set of test FTE config parameters The FTE configuration used for tests was missing fault-tolerant-execution-min-partition-count-for-write, the default value (50) was not compatible with fault-tolerant-execution-max-partition-count which was explicitly set to 5. Adding fault-tolerant-execution-min-partition-count-for-write set to 2 and also fault-tolerant-execution-min-partition-count for completeness --- .../testing/FaultTolerantExecutionConnectorTestHelper.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/testing/trino-testing/src/main/java/io/trino/testing/FaultTolerantExecutionConnectorTestHelper.java b/testing/trino-testing/src/main/java/io/trino/testing/FaultTolerantExecutionConnectorTestHelper.java index 96f2adffd60b..00856e88b842 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/FaultTolerantExecutionConnectorTestHelper.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/FaultTolerantExecutionConnectorTestHelper.java @@ -27,7 +27,9 @@ public static Map getExtraProperties() .put("retry-policy", "TASK") .put("retry-initial-delay", "50ms") .put("retry-max-delay", "100ms") + .put("fault-tolerant-execution-min-partition-count", "4") .put("fault-tolerant-execution-max-partition-count", "5") + .put("fault-tolerant-execution-min-partition-count-for-write", "4") .put("fault-tolerant-execution-arbitrary-distribution-compute-task-target-size-min", "5MB") .put("fault-tolerant-execution-arbitrary-distribution-compute-task-target-size-max", "10MB") .put("fault-tolerant-execution-arbitrary-distribution-write-task-target-size-min", "10MB") From d8ddf9d2ef6e03329c13e0f60846e032b05a52a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Osipiuk?= Date: Fri, 1 Dec 2023 13:12:15 +0100 Subject: [PATCH 564/587] Test merge with cost based partition count determination --- .../iceberg/BaseIcebergConnectorTest.java | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java index 95929f03fd16..0d6f2c444951 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java @@ -100,6 +100,7 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.collect.MoreCollectors.onlyElement; import static com.google.common.util.concurrent.Uninterruptibles.sleepUninterruptibly; +import static io.trino.SystemSessionProperties.DETERMINE_PARTITION_COUNT_FOR_WRITE_ENABLED; import static io.trino.SystemSessionProperties.SCALE_WRITERS; import static io.trino.SystemSessionProperties.TASK_MAX_WRITER_COUNT; import static io.trino.SystemSessionProperties.TASK_MIN_WRITER_COUNT; @@ -6428,23 +6429,28 @@ private void testMergeUpdateWithVariousLayouts(int writers, String partitioning) @Override public void testMergeMultipleOperations() { - testMergeMultipleOperations(1, ""); - testMergeMultipleOperations(4, ""); - testMergeMultipleOperations(1, "WITH (partitioning = ARRAY['customer'])"); - testMergeMultipleOperations(4, "WITH (partitioning = ARRAY['customer'])"); - testMergeMultipleOperations(1, "WITH (partitioning = ARRAY['purchase'])"); - testMergeMultipleOperations(4, "WITH (partitioning = ARRAY['purchase'])"); - testMergeMultipleOperations(1, "WITH (partitioning = ARRAY['bucket(customer, 3)'])"); - testMergeMultipleOperations(4, "WITH (partitioning = ARRAY['bucket(customer, 3)'])"); - testMergeMultipleOperations(1, "WITH (partitioning = ARRAY['bucket(purchase, 4)'])"); - testMergeMultipleOperations(4, "WITH (partitioning = ARRAY['bucket(purchase, 4)'])"); + testMergeMultipleOperations(1, "", false); + testMergeMultipleOperations(4, "", false); + testMergeMultipleOperations(1, "WITH (partitioning = ARRAY['customer'])", false); + testMergeMultipleOperations(4, "WITH (partitioning = ARRAY['customer'])", false); + testMergeMultipleOperations(1, "WITH (partitioning = ARRAY['purchase'])", false); + testMergeMultipleOperations(4, "WITH (partitioning = ARRAY['purchase'])", false); + testMergeMultipleOperations(1, "WITH (partitioning = ARRAY['bucket(customer, 3)'])", false); + testMergeMultipleOperations(4, "WITH (partitioning = ARRAY['bucket(customer, 3)'])", false); + testMergeMultipleOperations(1, "WITH (partitioning = ARRAY['bucket(purchase, 4)'])", false); + testMergeMultipleOperations(4, "WITH (partitioning = ARRAY['bucket(purchase, 4)'])", false); + testMergeMultipleOperations(1, "", true); + testMergeMultipleOperations(4, "WITH (partitioning = ARRAY['customer'])", true); + testMergeMultipleOperations(1, "WITH (partitioning = ARRAY['bucket(customer, 3)'])", true); + testMergeMultipleOperations(4, "WITH (partitioning = ARRAY['bucket(purchase, 4)'])", true); } - public void testMergeMultipleOperations(int writers, String partitioning) + public void testMergeMultipleOperations(int writers, String partitioning, boolean determinePartitionCountForWrite) { Session session = Session.builder(getSession()) .setSystemProperty(TASK_MIN_WRITER_COUNT, String.valueOf(writers)) .setSystemProperty(TASK_MAX_WRITER_COUNT, String.valueOf(writers)) + .setSystemProperty(DETERMINE_PARTITION_COUNT_FOR_WRITE_ENABLED, Boolean.toString(determinePartitionCountForWrite)) .build(); int targetCustomerCount = 32; From 10f3f3b2da9d5fd2d06c89839423aada8b501665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Osipiuk?= Date: Fri, 1 Dec 2023 15:49:19 +0100 Subject: [PATCH 565/587] Make method private --- .../java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java index 0d6f2c444951..12f91b2b51aa 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java @@ -6445,7 +6445,7 @@ public void testMergeMultipleOperations() testMergeMultipleOperations(4, "WITH (partitioning = ARRAY['bucket(purchase, 4)'])", true); } - public void testMergeMultipleOperations(int writers, String partitioning, boolean determinePartitionCountForWrite) + private void testMergeMultipleOperations(int writers, String partitioning, boolean determinePartitionCountForWrite) { Session session = Session.builder(getSession()) .setSystemProperty(TASK_MIN_WRITER_COUNT, String.valueOf(writers)) From a302185c4df99210646ef1c6f10e131694a67c18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Wa=C5=9B?= Date: Wed, 22 Nov 2023 12:24:18 +0100 Subject: [PATCH 566/587] Make mapping of Trino schema to BigQuery dataset more explicit This is only a refactor to make it easier to spot when Trino schemas are mapped to BigQuery datasets and the other way around. --- .../trino/plugin/bigquery/BigQueryClient.java | 15 ++++ .../plugin/bigquery/BigQueryMetadata.java | 78 ++++++++++--------- 2 files changed, 58 insertions(+), 35 deletions(-) diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryClient.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryClient.java index 9b151f24b05d..97044f9d06a4 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryClient.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryClient.java @@ -112,6 +112,11 @@ public BigQueryClient( this.configProjectId = requireNonNull(configProjectId, "projectId is null"); } + public Optional toRemoteDataset(DatasetId datasetId) + { + return toRemoteDataset(datasetId.getProject(), datasetId.getDataset()); + } + public Optional toRemoteDataset(String projectId, String datasetName) { requireNonNull(projectId, "projectId is null"); @@ -221,6 +226,16 @@ public String getProjectId() return projectId; } + public DatasetId toDatasetId(String schemaName) + { + return DatasetId.of(getProjectId(), schemaName); + } + + public String toSchemaName(DatasetId datasetId) + { + return datasetId.getDataset(); + } + public Iterable listDatasets(String projectId) { try { diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryMetadata.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryMetadata.java index aea995ebb639..fd5e88a633ee 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryMetadata.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryMetadata.java @@ -171,13 +171,13 @@ private List listRemoteSchemaNames(ConnectorSession session) public boolean schemaExists(ConnectorSession session, String schemaName) { BigQueryClient client = bigQueryClientFactory.create(session); + DatasetId localDatasetId = client.toDatasetId(schemaName); // Overridden to make sure an error message is returned in case of an ambiguous schema name log.debug("schemaExists(session=%s)", session); - String projectId = client.getProjectId(); - return client.toRemoteDataset(projectId, schemaName) + return client.toRemoteDataset(localDatasetId) .map(RemoteDatabaseObject::getOnlyRemoteName) - .filter(remoteSchema -> client.getDataset(DatasetId.of(projectId, remoteSchema)) != null) + .filter(remoteSchema -> client.getDataset(DatasetId.of(localDatasetId.getProject(), remoteSchema)) != null) .isPresent(); } @@ -187,15 +187,23 @@ public List listTables(ConnectorSession session, Optional remoteSchema = schemaName.flatMap(schema -> client.toRemoteDataset(projectId, schema) - .filter(dataset -> !dataset.isAmbiguous()) - .map(RemoteDatabaseObject::getOnlyRemoteName)); - - Set remoteSchemaNames = remoteSchema.map(ImmutableSet::of) - .orElseGet(() -> ImmutableSet.copyOf(listRemoteSchemaNames(session))); + String projectId; + + Set remoteSchemaNames; + if (schemaName.isPresent()) { + DatasetId localDatasetId = client.toDatasetId(schemaName.get()); + projectId = localDatasetId.getProject(); + // filter ambiguous schemas + Optional remoteSchema = client.toRemoteDataset(localDatasetId) + .filter(dataset -> !dataset.isAmbiguous()) + .map(RemoteDatabaseObject::getOnlyRemoteName); + + remoteSchemaNames = remoteSchema.map(ImmutableSet::of).orElse(ImmutableSet.of()); + } + else { + projectId = client.getProjectId(); + remoteSchemaNames = ImmutableSet.copyOf(listRemoteSchemaNames(session)); + } return processInParallel(remoteSchemaNames.stream().toList(), remoteSchemaName -> listTablesInRemoteSchema(client, projectId, remoteSchemaName)) .flatMap(Collection::stream) @@ -213,7 +221,7 @@ private List listTablesInRemoteSchema(BigQueryClient client, St .filter(RemoteDatabaseObject::isAmbiguous) .ifPresentOrElse( remoteTable -> log.debug("Filtered out [%s.%s] from list of tables due to ambiguous name", remoteSchemaName, table.getTableId().getTable()), - () -> tableNames.add(new SchemaTableName(table.getTableId().getDataset(), table.getTableId().getTable()))); + () -> tableNames.add(new SchemaTableName(client.toSchemaName(DatasetId.of(projectId, table.getTableId().getDataset())), table.getTableId().getTable()))); } } catch (BigQueryException e) { @@ -232,15 +240,15 @@ private List listTablesInRemoteSchema(BigQueryClient client, St public ConnectorTableHandle getTableHandle(ConnectorSession session, SchemaTableName schemaTableName) { BigQueryClient client = bigQueryClientFactory.create(session); - String projectId = client.getProjectId(); log.debug("getTableHandle(session=%s, schemaTableName=%s)", session, schemaTableName); - String remoteSchemaName = client.toRemoteDataset(projectId, schemaTableName.getSchemaName()) + DatasetId localDatasetId = client.toDatasetId(schemaTableName.getSchemaName()); + String remoteSchemaName = client.toRemoteDataset(localDatasetId) .map(RemoteDatabaseObject::getOnlyRemoteName) - .orElse(schemaTableName.getSchemaName()); - String remoteTableName = client.toRemoteTable(projectId, remoteSchemaName, schemaTableName.getTableName()) + .orElse(localDatasetId.getDataset()); + String remoteTableName = client.toRemoteTable(localDatasetId.getProject(), remoteSchemaName, schemaTableName.getTableName()) .map(RemoteDatabaseObject::getOnlyRemoteName) .orElse(schemaTableName.getTableName()); - Optional tableInfo = client.getTable(TableId.of(projectId, remoteSchemaName, remoteTableName)); + Optional tableInfo = client.getTable(TableId.of(localDatasetId.getProject(), remoteSchemaName, remoteTableName)); if (tableInfo.isEmpty()) { log.debug("Table [%s.%s] was not found", schemaTableName.getSchemaName(), schemaTableName.getTableName()); return null; @@ -265,14 +273,14 @@ public ConnectorTableHandle getTableHandle(ConnectorSession session, SchemaTable private ConnectorTableHandle getTableHandleIgnoringConflicts(ConnectorSession session, SchemaTableName schemaTableName) { BigQueryClient client = bigQueryClientFactory.create(session); - String projectId = client.getProjectId(); - String remoteSchemaName = client.toRemoteDataset(projectId, schemaTableName.getSchemaName()) + DatasetId localDatasetId = client.toDatasetId(schemaTableName.getSchemaName()); + String remoteSchemaName = client.toRemoteDataset(localDatasetId) .map(RemoteDatabaseObject::getAnyRemoteName) - .orElse(schemaTableName.getSchemaName()); - String remoteTableName = client.toRemoteTable(projectId, remoteSchemaName, schemaTableName.getTableName()) + .orElse(localDatasetId.getDataset()); + String remoteTableName = client.toRemoteTable(localDatasetId.getProject(), remoteSchemaName, schemaTableName.getTableName()) .map(RemoteDatabaseObject::getAnyRemoteName) .orElse(schemaTableName.getTableName()); - Optional tableInfo = client.getTable(TableId.of(projectId, remoteSchemaName, remoteTableName)); + Optional tableInfo = client.getTable(TableId.of(localDatasetId.getProject(), remoteSchemaName, remoteTableName)); if (tableInfo.isEmpty()) { log.debug("Table [%s.%s] was not found", schemaTableName.getSchemaName(), schemaTableName.getTableName()); return null; @@ -311,14 +319,14 @@ public Optional getSystemTable(ConnectorSession session, SchemaTabl private Optional getViewDefinitionSystemTable(ConnectorSession session, SchemaTableName viewDefinitionTableName, SchemaTableName sourceTableName) { BigQueryClient client = bigQueryClientFactory.create(session); - String projectId = client.getProjectId(); - String remoteSchemaName = client.toRemoteDataset(projectId, sourceTableName.getSchemaName()) + DatasetId localDatasetId = client.toDatasetId(sourceTableName.getSchemaName()); + String remoteSchemaName = client.toRemoteDataset(localDatasetId) .map(RemoteDatabaseObject::getOnlyRemoteName) .orElseThrow(() -> new TableNotFoundException(viewDefinitionTableName)); - String remoteTableName = client.toRemoteTable(projectId, remoteSchemaName, sourceTableName.getTableName()) + String remoteTableName = client.toRemoteTable(localDatasetId.getProject(), remoteSchemaName, sourceTableName.getTableName()) .map(RemoteDatabaseObject::getOnlyRemoteName) .orElseThrow(() -> new TableNotFoundException(viewDefinitionTableName)); - TableInfo tableInfo = client.getTable(TableId.of(projectId, remoteSchemaName, remoteTableName)) + TableInfo tableInfo = client.getTable(TableId.of(localDatasetId.getProject(), remoteSchemaName, remoteTableName)) .orElseThrow(() -> new TableNotFoundException(viewDefinitionTableName)); if (!(tableInfo.getDefinition() instanceof ViewDefinition)) { throw new TableNotFoundException(viewDefinitionTableName); @@ -424,7 +432,7 @@ public void createSchema(ConnectorSession session, String schemaName, Map { @@ -507,13 +515,13 @@ private BigQueryOutputTableHandle createTable(ConnectorSession session, Connecto } }); - TableId tableId = createTable(client, projectId, remoteSchemaName, tableName, fields.build(), tableMetadata.getComment()); + TableId tableId = createTable(client, localDatasetId.getProject(), remoteSchemaName, tableName, fields.build(), tableMetadata.getComment()); closer.register(() -> bigQueryClientFactory.create(session).dropTable(tableId)); Optional temporaryTableName = pageSinkIdColumn.map(column -> { tempFields.add(typeManager.toField(column.getName(), column.getType(), column.getComment())); String tempTableName = generateTemporaryTableName(session); - TableId tempTableId = createTable(client, projectId, remoteSchemaName, tempTableName, tempFields.build(), tableMetadata.getComment()); + TableId tempTableId = createTable(client, localDatasetId.getProject(), remoteSchemaName, tempTableName, tempFields.build(), tableMetadata.getComment()); closer.register(() -> bigQueryClientFactory.create(session).dropTable(tempTableId)); return tempTableName; }); From 1b15b1d675a7d894d0d67185f8d3bb9a7e33663a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Wa=C5=9B?= Date: Wed, 22 Nov 2023 12:25:48 +0100 Subject: [PATCH 567/587] Make injectable interface public --- .../main/java/io/trino/plugin/bigquery/BigQueryClient.java | 4 ++-- .../io/trino/plugin/bigquery/BigQueryOptionsConfigurer.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryClient.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryClient.java index 97044f9d06a4..573096ba27a3 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryClient.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryClient.java @@ -226,12 +226,12 @@ public String getProjectId() return projectId; } - public DatasetId toDatasetId(String schemaName) + protected DatasetId toDatasetId(String schemaName) { return DatasetId.of(getProjectId(), schemaName); } - public String toSchemaName(DatasetId datasetId) + protected String toSchemaName(DatasetId datasetId) { return datasetId.getDataset(); } diff --git a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryOptionsConfigurer.java b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryOptionsConfigurer.java index 1543a2c9f535..ba8c56b3d542 100644 --- a/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryOptionsConfigurer.java +++ b/plugin/trino-bigquery/src/main/java/io/trino/plugin/bigquery/BigQueryOptionsConfigurer.java @@ -18,7 +18,7 @@ import com.google.cloud.bigquery.storage.v1.BigQueryWriteSettings; import io.trino.spi.connector.ConnectorSession; -interface BigQueryOptionsConfigurer +public interface BigQueryOptionsConfigurer { BigQueryOptions.Builder configure(BigQueryOptions.Builder builder, ConnectorSession session); From 5bd784e8e7cbe413d4041ec643e6e78a632713eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Osipiuk?= Date: Fri, 1 Dec 2023 16:41:12 +0100 Subject: [PATCH 568/587] Add queryState to debug info in EventDrivenFaultTolerantQueryScheduler --- .../faulttolerant/EventDrivenFaultTolerantQueryScheduler.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java index 88db82b7ad7b..6692e2ebfb93 100644 --- a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java +++ b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java @@ -935,6 +935,7 @@ private void logDebugInfo(String reason) log.debug("Scheduler debug info for %s START; reason=%s", queryStateMachine.getQueryId(), reason); log.debug("General state: %s", toStringHelper(this) + .add("queryState", queryStateMachine.getQueryState()) .add("maxTaskExecutionAttempts", maxTaskExecutionAttempts) .add("maxTasksWaitingForNode", maxTasksWaitingForNode) .add("maxTasksWaitingForExecution", maxTasksWaitingForExecution) From bcddbcf0c88ada1323b039e8c1e663277f2b4da9 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Wed, 29 Nov 2023 18:58:25 -0800 Subject: [PATCH 569/587] Remove outdated SPI revapi exclusions --- core/trino-spi/pom.xml | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/core/trino-spi/pom.xml b/core/trino-spi/pom.xml index b4b6e9f9982a..1f76552d4fc1 100644 --- a/core/trino-spi/pom.xml +++ b/core/trino-spi/pom.xml @@ -212,41 +212,6 @@ - - java.class.removed - interface io.trino.spi.block.VariableWidthBlockBuilder.VariableWidthEntryBuilder<E extends java.lang.Throwable> - - - java.method.removed - method <E extends java.lang.Throwable> void io.trino.spi.block.VariableWidthBlockBuilder::buildEntry(io.trino.spi.block.VariableWidthBlockBuilder.VariableWidthEntryBuilder<E>) throws E - - - true - java.method.addedToInterface - method void io.trino.spi.block.BlockBuilder::append(io.trino.spi.block.ValueBlock, int) - - - java.method.addedToInterface - method void io.trino.spi.block.BlockBuilder::appendPositions(io.trino.spi.block.ValueBlock, int[], int, int) - - - java.method.addedToInterface - method void io.trino.spi.block.BlockBuilder::appendRange(io.trino.spi.block.ValueBlock, int, int) - - - java.method.addedToInterface - method void io.trino.spi.block.BlockBuilder::appendRepeated(io.trino.spi.block.ValueBlock, int, int) - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.BlockBuilder io.trino.spi.block.ShortArrayBlockBuilder::appendNull() - method io.trino.spi.block.ShortArrayBlockBuilder io.trino.spi.block.ShortArrayBlockBuilder::appendNull() - - - java.method.returnTypeChangedCovariantly - method io.trino.spi.block.BlockBuilder io.trino.spi.block.ShortArrayBlockBuilder::writeShort(short) - method io.trino.spi.block.ShortArrayBlockBuilder io.trino.spi.block.ShortArrayBlockBuilder::writeShort(short) - From 5667b1eed068394f1d7f87e261377e5977efdc44 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Wed, 29 Nov 2023 19:14:43 -0800 Subject: [PATCH 570/587] Fix warnings in HandleResolver --- .../main/java/io/trino/metadata/HandleResolver.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/metadata/HandleResolver.java b/core/trino-main/src/main/java/io/trino/metadata/HandleResolver.java index 452a568f1783..67690b4b7cd3 100644 --- a/core/trino-main/src/main/java/io/trino/metadata/HandleResolver.java +++ b/core/trino-main/src/main/java/io/trino/metadata/HandleResolver.java @@ -46,11 +46,9 @@ public void unregisterClassLoader(PluginClassLoader classLoader) checkState(result, "Class loader not registered: %s", classLoader.getId()); } - @SuppressWarnings("MethodMayBeStatic") - public String getId(Object tableHandle) + public String getId(Object handle) { - Class handleClass = tableHandle.getClass(); - return classId(handleClass); + return classId(handle.getClass()); } public Class getHandleClass(String id) @@ -80,8 +78,8 @@ private static String classId(Class handleClass) private static String classLoaderId(Class handleClass) { ClassLoader classLoader = handleClass.getClassLoader(); - if (classLoader instanceof PluginClassLoader) { - return ((PluginClassLoader) classLoader).getId(); + if (classLoader instanceof PluginClassLoader pluginClassLoader) { + return pluginClassLoader.getId(); } checkArgument(classLoader == HandleResolver.class.getClassLoader(), "Handle [%s] has unknown class loader [%s]", From 98366618a9bbbfacd5dda44a0b65028b44675d73 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Wed, 29 Nov 2023 19:24:25 -0800 Subject: [PATCH 571/587] Remove deprecated duplicatePluginClassLoader --- .../io/trino/connector/CatalogFactory.java | 4 +- .../connector/ConnectorContextInstance.java | 17 +- .../io/trino/connector/ConnectorServices.java | 7 +- .../connector/DefaultCatalogFactory.java | 161 +++--------------- .../trino/connector/LazyCatalogFactory.java | 6 +- .../io/trino/metadata/HandleResolver.java | 6 - .../io/trino/server/PluginClassLoader.java | 26 +-- .../java/io/trino/server/PluginInstaller.java | 5 +- .../java/io/trino/server/PluginManager.java | 12 +- .../server/testing/TestingTrinoServer.java | 2 +- .../io/trino/testing/LocalQueryRunner.java | 11 +- .../testing/TestingConnectorContext.java | 6 - ...estSqlTaskManagerRaceWithCatalogPrune.java | 6 +- .../trino/spi/connector/ConnectorContext.java | 6 - 14 files changed, 47 insertions(+), 228 deletions(-) diff --git a/core/trino-main/src/main/java/io/trino/connector/CatalogFactory.java b/core/trino-main/src/main/java/io/trino/connector/CatalogFactory.java index fc1cce6cc606..98af95c81089 100644 --- a/core/trino-main/src/main/java/io/trino/connector/CatalogFactory.java +++ b/core/trino-main/src/main/java/io/trino/connector/CatalogFactory.java @@ -18,12 +18,10 @@ import io.trino.spi.connector.Connector; import io.trino.spi.connector.ConnectorFactory; -import java.util.function.Function; - @ThreadSafe public interface CatalogFactory { - void addConnectorFactory(ConnectorFactory connectorFactory, Function duplicatePluginClassLoaderFactory); + void addConnectorFactory(ConnectorFactory connectorFactory); CatalogConnector createCatalog(CatalogProperties catalogProperties); diff --git a/core/trino-main/src/main/java/io/trino/connector/ConnectorContextInstance.java b/core/trino-main/src/main/java/io/trino/connector/ConnectorContextInstance.java index 677a4626ba95..e7da31cb64e9 100644 --- a/core/trino-main/src/main/java/io/trino/connector/ConnectorContextInstance.java +++ b/core/trino-main/src/main/java/io/trino/connector/ConnectorContextInstance.java @@ -24,10 +24,6 @@ import io.trino.spi.connector.MetadataProvider; import io.trino.spi.type.TypeManager; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Supplier; - -import static com.google.common.base.Preconditions.checkState; import static java.util.Objects.requireNonNull; public class ConnectorContextInstance @@ -41,8 +37,6 @@ public class ConnectorContextInstance private final MetadataProvider metadataProvider; private final PageSorter pageSorter; private final PageIndexerFactory pageIndexerFactory; - private final Supplier duplicatePluginClassLoaderFactory; - private final AtomicBoolean pluginClassLoaderDuplicated = new AtomicBoolean(); private final CatalogHandle catalogHandle; public ConnectorContextInstance( @@ -54,8 +48,7 @@ public ConnectorContextInstance( TypeManager typeManager, MetadataProvider metadataProvider, PageSorter pageSorter, - PageIndexerFactory pageIndexerFactory, - Supplier duplicatePluginClassLoaderFactory) + PageIndexerFactory pageIndexerFactory) { this.openTelemetry = requireNonNull(openTelemetry, "openTelemetry is null"); this.tracer = requireNonNull(tracer, "tracer is null"); @@ -65,7 +58,6 @@ public ConnectorContextInstance( this.metadataProvider = requireNonNull(metadataProvider, "metadataProvider is null"); this.pageSorter = requireNonNull(pageSorter, "pageSorter is null"); this.pageIndexerFactory = requireNonNull(pageIndexerFactory, "pageIndexerFactory is null"); - this.duplicatePluginClassLoaderFactory = requireNonNull(duplicatePluginClassLoaderFactory, "duplicatePluginClassLoaderFactory is null"); this.catalogHandle = requireNonNull(catalogHandle, "catalogHandle is null"); } @@ -122,11 +114,4 @@ public PageIndexerFactory getPageIndexerFactory() { return pageIndexerFactory; } - - @Override - public ClassLoader duplicatePluginClassLoader() - { - checkState(!pluginClassLoaderDuplicated.getAndSet(true), "plugin class loader already duplicated"); - return duplicatePluginClassLoaderFactory.get(); - } } diff --git a/core/trino-main/src/main/java/io/trino/connector/ConnectorServices.java b/core/trino-main/src/main/java/io/trino/connector/ConnectorServices.java index fc1a4d5650ef..416e0bc0e049 100644 --- a/core/trino-main/src/main/java/io/trino/connector/ConnectorServices.java +++ b/core/trino-main/src/main/java/io/trino/connector/ConnectorServices.java @@ -72,7 +72,6 @@ public class ConnectorServices private final Tracer tracer; private final CatalogHandle catalogHandle; private final Connector connector; - private final Runnable afterShutdown; private final Set systemTables; private final CatalogProcedures procedures; private final CatalogTableProcedures tableProcedures; @@ -95,12 +94,11 @@ public class ConnectorServices private final AtomicBoolean shutdown = new AtomicBoolean(); - public ConnectorServices(Tracer tracer, CatalogHandle catalogHandle, Connector connector, Runnable afterShutdown) + public ConnectorServices(Tracer tracer, CatalogHandle catalogHandle, Connector connector) { this.tracer = requireNonNull(tracer, "tracer is null"); this.catalogHandle = requireNonNull(catalogHandle, "catalogHandle is null"); this.connector = requireNonNull(connector, "connector is null"); - this.afterShutdown = requireNonNull(afterShutdown, "afterShutdown is null"); Set systemTables = connector.getSystemTables(); requireNonNull(systemTables, format("Connector '%s' returned a null system tables set", catalogHandle)); @@ -345,9 +343,6 @@ public void shutdown() catch (Throwable t) { log.error(t, "Error shutting down catalog: %s", catalogHandle); } - finally { - afterShutdown.run(); - } } private static void validateTableFunction(ConnectorTableFunction tableFunction) diff --git a/core/trino-main/src/main/java/io/trino/connector/DefaultCatalogFactory.java b/core/trino-main/src/main/java/io/trino/connector/DefaultCatalogFactory.java index f544ef90e27a..121e8527830a 100644 --- a/core/trino-main/src/main/java/io/trino/connector/DefaultCatalogFactory.java +++ b/core/trino-main/src/main/java/io/trino/connector/DefaultCatalogFactory.java @@ -14,7 +14,6 @@ package io.trino.connector; import com.google.errorprone.annotations.ThreadSafe; -import com.google.errorprone.annotations.concurrent.GuardedBy; import com.google.inject.Inject; import io.airlift.node.NodeInfo; import io.opentelemetry.api.OpenTelemetry; @@ -25,11 +24,9 @@ import io.trino.connector.system.SystemConnector; import io.trino.connector.system.SystemTablesProvider; import io.trino.execution.scheduler.NodeSchedulerConfig; -import io.trino.metadata.HandleResolver; import io.trino.metadata.InternalNodeManager; import io.trino.metadata.Metadata; import io.trino.security.AccessControl; -import io.trino.server.PluginClassLoader; import io.trino.spi.PageIndexerFactory; import io.trino.spi.PageSorter; import io.trino.spi.VersionEmbedder; @@ -46,11 +43,8 @@ import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.function.Function; -import java.util.function.Supplier; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkState; import static io.trino.spi.connector.CatalogHandle.createInformationSchemaCatalogHandle; import static io.trino.spi.connector.CatalogHandle.createSystemTablesCatalogHandle; import static java.util.Objects.requireNonNull; @@ -61,7 +55,6 @@ public class DefaultCatalogFactory { private final Metadata metadata; private final AccessControl accessControl; - private final HandleResolver handleResolver; private final InternalNodeManager nodeManager; private final PageSorter pageSorter; @@ -75,13 +68,12 @@ public class DefaultCatalogFactory private final boolean schedulerIncludeCoordinator; private final int maxPrefetchedInformationSchemaPrefixes; - private final ConcurrentMap connectorFactories = new ConcurrentHashMap<>(); + private final ConcurrentMap connectorFactories = new ConcurrentHashMap<>(); @Inject public DefaultCatalogFactory( Metadata metadata, AccessControl accessControl, - HandleResolver handleResolver, InternalNodeManager nodeManager, PageSorter pageSorter, PageIndexerFactory pageIndexerFactory, @@ -95,7 +87,6 @@ public DefaultCatalogFactory( { this.metadata = requireNonNull(metadata, "metadata is null"); this.accessControl = requireNonNull(accessControl, "accessControl is null"); - this.handleResolver = requireNonNull(handleResolver, "handleResolver is null"); this.nodeManager = requireNonNull(nodeManager, "nodeManager is null"); this.pageSorter = requireNonNull(pageSorter, "pageSorter is null"); this.pageIndexerFactory = requireNonNull(pageIndexerFactory, "pageIndexerFactory is null"); @@ -109,11 +100,10 @@ public DefaultCatalogFactory( } @Override - public synchronized void addConnectorFactory(ConnectorFactory connectorFactory, Function duplicatePluginClassLoaderFactory) + public synchronized void addConnectorFactory(ConnectorFactory connectorFactory) { - InternalConnectorFactory existingConnectorFactory = connectorFactories.putIfAbsent( - new ConnectorName(connectorFactory.getName()), - new InternalConnectorFactory(connectorFactory, duplicatePluginClassLoaderFactory)); + ConnectorFactory existingConnectorFactory = connectorFactories.putIfAbsent( + new ConnectorName(connectorFactory.getName()), connectorFactory); checkArgument(existingConnectorFactory == null, "Connector '%s' is already registered", connectorFactory.getName()); } @@ -122,54 +112,43 @@ public CatalogConnector createCatalog(CatalogProperties catalogProperties) { requireNonNull(catalogProperties, "catalogProperties is null"); - InternalConnectorFactory factory = connectorFactories.get(catalogProperties.getConnectorName()); - checkArgument(factory != null, "No factory for connector '%s'. Available factories: %s", catalogProperties.getConnectorName(), connectorFactories.keySet()); + ConnectorFactory connectorFactory = connectorFactories.get(catalogProperties.getConnectorName()); + checkArgument(connectorFactory != null, "No factory for connector '%s'. Available factories: %s", catalogProperties.getConnectorName(), connectorFactories.keySet()); - CatalogClassLoaderSupplier duplicatePluginClassLoaderFactory = new CatalogClassLoaderSupplier( + Connector connector = createConnector( + catalogProperties.getCatalogHandle().getCatalogName(), catalogProperties.getCatalogHandle(), - factory.getDuplicatePluginClassLoaderFactory(), - handleResolver); - try { - Connector connector = createConnector( - catalogProperties.getCatalogHandle().getCatalogName(), - catalogProperties.getCatalogHandle(), - factory.getConnectorFactory(), - duplicatePluginClassLoaderFactory, - catalogProperties.getProperties()); - return createCatalog( - catalogProperties.getCatalogHandle(), - catalogProperties.getConnectorName(), - connector, - duplicatePluginClassLoaderFactory::destroy, - Optional.of(catalogProperties)); - } - catch (Throwable e) { - duplicatePluginClassLoaderFactory.destroy(); - throw e; - } + connectorFactory, + catalogProperties.getProperties()); + + return createCatalog( + catalogProperties.getCatalogHandle(), + catalogProperties.getConnectorName(), + connector, + Optional.of(catalogProperties)); } @Override public CatalogConnector createCatalog(CatalogHandle catalogHandle, ConnectorName connectorName, Connector connector) { - return createCatalog(catalogHandle, connectorName, connector, () -> {}, Optional.empty()); + return createCatalog(catalogHandle, connectorName, connector, Optional.empty()); } - private CatalogConnector createCatalog(CatalogHandle catalogHandle, ConnectorName connectorName, Connector connector, Runnable destroy, Optional catalogProperties) + private CatalogConnector createCatalog(CatalogHandle catalogHandle, ConnectorName connectorName, Connector connector, Optional catalogProperties) { Tracer tracer = createTracer(catalogHandle); - ConnectorServices catalogConnector = new ConnectorServices( - tracer, - catalogHandle, - connector, - destroy); + ConnectorServices catalogConnector = new ConnectorServices(tracer, catalogHandle, connector); ConnectorServices informationSchemaConnector = new ConnectorServices( tracer, createInformationSchemaCatalogHandle(catalogHandle), - new InformationSchemaConnector(catalogHandle.getCatalogName(), nodeManager, metadata, accessControl, maxPrefetchedInformationSchemaPrefixes), - () -> {}); + new InformationSchemaConnector( + catalogHandle.getCatalogName(), + nodeManager, + metadata, + accessControl, + maxPrefetchedInformationSchemaPrefixes)); SystemTablesProvider systemTablesProvider; if (nodeManager.getCurrentNode().isCoordinator()) { @@ -189,8 +168,7 @@ private CatalogConnector createCatalog(CatalogHandle catalogHandle, ConnectorNam new SystemConnector( nodeManager, systemTablesProvider, - transactionId -> transactionManager.getConnectorTransaction(transactionId, catalogHandle)), - () -> {}); + transactionId -> transactionManager.getConnectorTransaction(transactionId, catalogHandle))); return new CatalogConnector( catalogHandle, @@ -205,7 +183,6 @@ private Connector createConnector( String catalogName, CatalogHandle catalogHandle, ConnectorFactory connectorFactory, - Supplier duplicatePluginClassLoaderFactory, Map properties) { ConnectorContext context = new ConnectorContextInstance( @@ -217,8 +194,7 @@ private Connector createConnector( typeManager, new InternalMetadataProvider(metadata, typeManager), pageSorter, - pageIndexerFactory, - duplicatePluginClassLoaderFactory); + pageIndexerFactory); try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(connectorFactory.getClass().getClassLoader())) { return connectorFactory.create(catalogName, properties, context); @@ -229,87 +205,4 @@ private Tracer createTracer(CatalogHandle catalogHandle) { return openTelemetry.getTracer("trino.catalog." + catalogHandle.getCatalogName()); } - - private static class InternalConnectorFactory - { - private final ConnectorFactory connectorFactory; - private final Function duplicatePluginClassLoaderFactory; - - public InternalConnectorFactory(ConnectorFactory connectorFactory, Function duplicatePluginClassLoaderFactory) - { - this.connectorFactory = connectorFactory; - this.duplicatePluginClassLoaderFactory = duplicatePluginClassLoaderFactory; - } - - public ConnectorFactory getConnectorFactory() - { - return connectorFactory; - } - - public Function getDuplicatePluginClassLoaderFactory() - { - return duplicatePluginClassLoaderFactory; - } - - @Override - public String toString() - { - return connectorFactory.getName(); - } - } - - private static class CatalogClassLoaderSupplier - implements Supplier - { - private final CatalogHandle catalogHandle; - private final Function duplicatePluginClassLoaderFactory; - private final HandleResolver handleResolver; - - @GuardedBy("this") - private boolean destroyed; - - @GuardedBy("this") - private ClassLoader classLoader; - - public CatalogClassLoaderSupplier( - CatalogHandle catalogHandle, - Function duplicatePluginClassLoaderFactory, - HandleResolver handleResolver) - { - this.catalogHandle = requireNonNull(catalogHandle, "catalogHandle is null"); - this.duplicatePluginClassLoaderFactory = requireNonNull(duplicatePluginClassLoaderFactory, "duplicatePluginClassLoaderFactory is null"); - this.handleResolver = requireNonNull(handleResolver, "handleResolver is null"); - } - - @Override - public ClassLoader get() - { - ClassLoader classLoader = duplicatePluginClassLoaderFactory.apply(catalogHandle); - - synchronized (this) { - // we check this after class loader creation because it reduces the complexity of the synchronization, and this shouldn't happen - checkState(this.classLoader == null, "class loader is already a duplicated for catalog " + catalogHandle); - checkState(!destroyed, "catalog has been shutdown"); - this.classLoader = classLoader; - } - - if (classLoader instanceof PluginClassLoader) { - handleResolver.registerClassLoader((PluginClassLoader) classLoader); - } - return classLoader; - } - - public void destroy() - { - ClassLoader classLoader; - synchronized (this) { - checkState(!destroyed, "catalog has been shutdown"); - classLoader = this.classLoader; - destroyed = true; - } - if (classLoader instanceof PluginClassLoader) { - handleResolver.unregisterClassLoader((PluginClassLoader) classLoader); - } - } - } } diff --git a/core/trino-main/src/main/java/io/trino/connector/LazyCatalogFactory.java b/core/trino-main/src/main/java/io/trino/connector/LazyCatalogFactory.java index e3dadd0199a2..3d8d2964e181 100644 --- a/core/trino-main/src/main/java/io/trino/connector/LazyCatalogFactory.java +++ b/core/trino-main/src/main/java/io/trino/connector/LazyCatalogFactory.java @@ -18,7 +18,6 @@ import io.trino.spi.connector.ConnectorFactory; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; import static com.google.common.base.Preconditions.checkState; @@ -33,10 +32,9 @@ public void setCatalogFactory(CatalogFactory catalogFactory) } @Override - public void addConnectorFactory(ConnectorFactory connectorFactory, - Function duplicatePluginClassLoaderFactory) + public void addConnectorFactory(ConnectorFactory connectorFactory) { - getDelegate().addConnectorFactory(connectorFactory, duplicatePluginClassLoaderFactory); + getDelegate().addConnectorFactory(connectorFactory); } @Override diff --git a/core/trino-main/src/main/java/io/trino/metadata/HandleResolver.java b/core/trino-main/src/main/java/io/trino/metadata/HandleResolver.java index 67690b4b7cd3..c523183d1704 100644 --- a/core/trino-main/src/main/java/io/trino/metadata/HandleResolver.java +++ b/core/trino-main/src/main/java/io/trino/metadata/HandleResolver.java @@ -40,12 +40,6 @@ public void registerClassLoader(PluginClassLoader classLoader) checkState(existingClassLoader == null, "Class loader already registered: %s", classLoader.getId()); } - public void unregisterClassLoader(PluginClassLoader classLoader) - { - boolean result = classLoaders.remove(classLoader.getId(), classLoader); - checkState(result, "Class loader not registered: %s", classLoader.getId()); - } - public String getId(Object handle) { return classId(handle.getClass()); diff --git a/core/trino-main/src/main/java/io/trino/server/PluginClassLoader.java b/core/trino-main/src/main/java/io/trino/server/PluginClassLoader.java index 3311346378e3..370a0ae2e5f7 100644 --- a/core/trino-main/src/main/java/io/trino/server/PluginClassLoader.java +++ b/core/trino-main/src/main/java/io/trino/server/PluginClassLoader.java @@ -14,17 +14,14 @@ package io.trino.server; import com.google.common.collect.ImmutableList; -import io.trino.spi.connector.CatalogHandle; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; import java.util.Enumeration; import java.util.List; -import java.util.Optional; import static com.google.common.base.MoreObjects.toStringHelper; -import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableList.toImmutableList; import static java.lang.System.identityHashCode; import static java.util.Objects.requireNonNull; @@ -32,9 +29,7 @@ public class PluginClassLoader extends URLClassLoader { - private final String id; private final String pluginName; - private final Optional catalogHandle; private final ClassLoader spiClassLoader; private final List spiPackages; private final List spiResources; @@ -46,7 +41,6 @@ public PluginClassLoader( List spiPackages) { this(pluginName, - Optional.empty(), urls, spiClassLoader, spiPackages, @@ -57,7 +51,6 @@ public PluginClassLoader( private PluginClassLoader( String pluginName, - Optional catalogHandle, List urls, ClassLoader spiClassLoader, Iterable spiPackages, @@ -66,34 +59,20 @@ private PluginClassLoader( // plugins should not have access to the system (application) class loader super(urls.toArray(new URL[0]), getPlatformClassLoader()); this.pluginName = requireNonNull(pluginName, "pluginName is null"); - this.catalogHandle = requireNonNull(catalogHandle, "catalogHandle is null"); this.spiClassLoader = requireNonNull(spiClassLoader, "spiClassLoader is null"); this.spiPackages = ImmutableList.copyOf(spiPackages); this.spiResources = ImmutableList.copyOf(spiResources); - this.id = pluginName + catalogHandle.map(name -> ":%s:%s".formatted(name.getCatalogName(), name.getVersion())).orElse(""); - } - - public PluginClassLoader duplicate(CatalogHandle catalogHandle) - { - checkState(this.catalogHandle.isEmpty(), "class loader is already a duplicate"); - return new PluginClassLoader( - pluginName, - Optional.of(requireNonNull(catalogHandle, "catalogHandle is null")), - ImmutableList.copyOf(getURLs()), - spiClassLoader, - spiPackages, - spiResources); } public PluginClassLoader withUrl(URL url) { List urls = ImmutableList.builder().add(getURLs()).add(url).build(); - return new PluginClassLoader(pluginName, catalogHandle, urls, spiClassLoader, spiPackages, spiResources); + return new PluginClassLoader(pluginName, urls, spiClassLoader, spiPackages, spiResources); } public String getId() { - return id; + return pluginName; } @Override @@ -102,7 +81,6 @@ public String toString() return toStringHelper(this) .omitNullValues() .add("plugin", pluginName) - .add("catalog", catalogHandle.orElse(null)) .add("identityHash", "@" + Integer.toHexString(identityHashCode(this))) .toString(); } diff --git a/core/trino-main/src/main/java/io/trino/server/PluginInstaller.java b/core/trino-main/src/main/java/io/trino/server/PluginInstaller.java index 452fa0f158ed..0cc6b2354110 100644 --- a/core/trino-main/src/main/java/io/trino/server/PluginInstaller.java +++ b/core/trino-main/src/main/java/io/trino/server/PluginInstaller.java @@ -14,13 +14,10 @@ package io.trino.server; import io.trino.spi.Plugin; -import io.trino.spi.connector.CatalogHandle; - -import java.util.function.Function; public interface PluginInstaller { void loadPlugins(); - void installPlugin(Plugin plugin, Function duplicatePluginClassLoaderFactory); + void installPlugin(Plugin plugin); } diff --git a/core/trino-main/src/main/java/io/trino/server/PluginManager.java b/core/trino-main/src/main/java/io/trino/server/PluginManager.java index c3d7cd866f80..4181aab50ea8 100644 --- a/core/trino-main/src/main/java/io/trino/server/PluginManager.java +++ b/core/trino-main/src/main/java/io/trino/server/PluginManager.java @@ -35,7 +35,6 @@ import io.trino.spi.Plugin; import io.trino.spi.block.BlockEncoding; import io.trino.spi.classloader.ThreadContextClassLoader; -import io.trino.spi.connector.CatalogHandle; import io.trino.spi.connector.ConnectorFactory; import io.trino.spi.eventlistener.EventListenerFactory; import io.trino.spi.exchange.ExchangeManagerFactory; @@ -55,7 +54,6 @@ import java.util.ServiceLoader; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Function; import java.util.function.Supplier; import static com.google.common.base.Preconditions.checkState; @@ -168,18 +166,18 @@ private void loadPlugin(PluginClassLoader pluginClassLoader) for (Plugin plugin : plugins) { log.info("Installing %s", plugin.getClass().getName()); - installPlugin(plugin, pluginClassLoader::duplicate); + installPlugin(plugin); } } @Override - public void installPlugin(Plugin plugin, Function duplicatePluginClassLoaderFactory) + public void installPlugin(Plugin plugin) { - installPluginInternal(plugin, duplicatePluginClassLoaderFactory); + installPluginInternal(plugin); typeRegistry.verifyTypes(); } - private void installPluginInternal(Plugin plugin, Function duplicatePluginClassLoaderFactory) + private void installPluginInternal(Plugin plugin) { for (BlockEncoding blockEncoding : plugin.getBlockEncodings()) { log.info("Registering block encoding %s", blockEncoding.getName()); @@ -198,7 +196,7 @@ private void installPluginInternal(Plugin plugin, Function> functions = plugin.getFunctions(); diff --git a/core/trino-main/src/main/java/io/trino/server/testing/TestingTrinoServer.java b/core/trino-main/src/main/java/io/trino/server/testing/TestingTrinoServer.java index b2da13f8aa15..672b7497b352 100644 --- a/core/trino-main/src/main/java/io/trino/server/testing/TestingTrinoServer.java +++ b/core/trino-main/src/main/java/io/trino/server/testing/TestingTrinoServer.java @@ -428,7 +428,7 @@ public void close() public void installPlugin(Plugin plugin) { - pluginInstaller.installPlugin(plugin, ignored -> plugin.getClass().getClassLoader()); + pluginInstaller.installPlugin(plugin); } public DispatchManager getDispatchManager() diff --git a/core/trino-main/src/main/java/io/trino/testing/LocalQueryRunner.java b/core/trino-main/src/main/java/io/trino/testing/LocalQueryRunner.java index b7cdd40c0839..cf607bef7119 100644 --- a/core/trino-main/src/main/java/io/trino/testing/LocalQueryRunner.java +++ b/core/trino-main/src/main/java/io/trino/testing/LocalQueryRunner.java @@ -394,13 +394,10 @@ private LocalQueryRunner( this.accessControl = new TestingAccessControlManager(transactionManager, eventListenerManager); accessControl.loadSystemAccessControl(AllowAllSystemAccessControl.NAME, ImmutableMap.of()); - HandleResolver handleResolver = new HandleResolver(); - NodeInfo nodeInfo = new NodeInfo("test"); catalogFactory.setCatalogFactory(new DefaultCatalogFactory( metadata, accessControl, - handleResolver, nodeManager, pageSorter, pageIndexerFactory, @@ -490,7 +487,7 @@ private LocalQueryRunner( new SessionPropertyDefaults(nodeInfo, accessControl), typeRegistry, blockEncodingManager, - handleResolver, + new HandleResolver(), exchangeManagerRegistry); catalogManager.registerGlobalSystemConnector(globalSystemConnector); @@ -757,19 +754,19 @@ public ExpressionCompiler getExpressionCompiler() public void createCatalog(String catalogName, ConnectorFactory connectorFactory, Map properties) { - catalogFactory.addConnectorFactory(connectorFactory, ignored -> connectorFactory.getClass().getClassLoader()); + catalogFactory.addConnectorFactory(connectorFactory); catalogManager.createCatalog(catalogName, new ConnectorName(connectorFactory.getName()), properties, false); } public void registerCatalogFactory(ConnectorFactory connectorFactory) { - catalogFactory.addConnectorFactory(connectorFactory, ignored -> connectorFactory.getClass().getClassLoader()); + catalogFactory.addConnectorFactory(connectorFactory); } @Override public void installPlugin(Plugin plugin) { - pluginManager.installPlugin(plugin, ignored -> plugin.getClass().getClassLoader()); + pluginManager.installPlugin(plugin); } @Override diff --git a/core/trino-main/src/main/java/io/trino/testing/TestingConnectorContext.java b/core/trino-main/src/main/java/io/trino/testing/TestingConnectorContext.java index 0d096a44235b..d7ff18054488 100644 --- a/core/trino-main/src/main/java/io/trino/testing/TestingConnectorContext.java +++ b/core/trino-main/src/main/java/io/trino/testing/TestingConnectorContext.java @@ -104,10 +104,4 @@ public PageIndexerFactory getPageIndexerFactory() { return pageIndexerFactory; } - - @Override - public ClassLoader duplicatePluginClassLoader() - { - return getClass().getClassLoader(); - } } diff --git a/core/trino-main/src/test/java/io/trino/execution/TestSqlTaskManagerRaceWithCatalogPrune.java b/core/trino-main/src/test/java/io/trino/execution/TestSqlTaskManagerRaceWithCatalogPrune.java index 815c8dd85a8d..7fe6e9e4b997 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestSqlTaskManagerRaceWithCatalogPrune.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestSqlTaskManagerRaceWithCatalogPrune.java @@ -71,7 +71,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.DoubleSupplier; -import java.util.function.Function; import java.util.function.Predicate; import static io.airlift.tracing.Tracing.noopTracer; @@ -112,7 +111,7 @@ public ConnectorServices getConnectorServices(CatalogHandle catalogHandle) private static final CatalogFactory MOCK_CATALOG_FACTORY = new CatalogFactory() { @Override - public void addConnectorFactory(ConnectorFactory connectorFactory, Function duplicatePluginClassLoaderFactory) {} + public void addConnectorFactory(ConnectorFactory connectorFactory) {} @Override public CatalogConnector createCatalog(CatalogProperties catalogProperties) @@ -121,8 +120,7 @@ public CatalogConnector createCatalog(CatalogProperties catalogProperties) ConnectorServices noOpConnectorService = new ConnectorServices( Tracing.noopTracer(), catalogProperties.getCatalogHandle(), - connector, - () -> {}); + connector); return new CatalogConnector( catalogProperties.getCatalogHandle(), new ConnectorName("mock"), diff --git a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorContext.java b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorContext.java index db8c9d653b98..c009e49d7794 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorContext.java +++ b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorContext.java @@ -78,10 +78,4 @@ default PageIndexerFactory getPageIndexerFactory() { throw new UnsupportedOperationException(); } - - @Deprecated(forRemoval = true) - default ClassLoader duplicatePluginClassLoader() - { - throw new UnsupportedOperationException(); - } } From b3cb26b83a5a34b546acd07bdadf4f48784654f7 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Wed, 29 Nov 2023 18:33:39 -0800 Subject: [PATCH 572/587] Fix removal of stats files in Iceberg orpan file cleanup --- .../main/java/io/trino/plugin/iceberg/IcebergMetadata.java | 5 +++++ .../io/trino/plugin/iceberg/BaseIcebergConnectorTest.java | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java index e1835cfe88f9..f13626380ea6 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java @@ -288,6 +288,7 @@ import static java.util.function.Function.identity; import static java.util.stream.Collectors.joining; import static org.apache.iceberg.ReachableFileUtil.metadataFileLocations; +import static org.apache.iceberg.ReachableFileUtil.statisticsFilesLocations; import static org.apache.iceberg.SnapshotSummary.DELETED_RECORDS_PROP; import static org.apache.iceberg.SnapshotSummary.REMOVED_EQ_DELETES_PROP; import static org.apache.iceberg.SnapshotSummary.REMOVED_POS_DELETES_PROP; @@ -1650,6 +1651,10 @@ private void removeOrphanFiles(Table table, ConnectorSession session, SchemaTabl .map(IcebergUtil::fileName) .forEach(validMetadataFileNames::add); + statisticsFilesLocations(table).stream() + .map(IcebergUtil::fileName) + .forEach(validMetadataFileNames::add); + validMetadataFileNames.add("version-hint.text"); scanAndDeleteInvalidFiles(table, session, schemaTableName, expiration, validDataFileNames.build(), "data"); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java index 12f91b2b51aa..958a82ea15c0 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java @@ -5745,7 +5745,7 @@ private void testCleaningUpWithTableWithSpecifiedLocation(String suffix) List prunedMetadataFiles = getAllMetadataFilesFromTableDirectory(tableDirectory); List prunedSnapshots = getSnapshotIds(tableName); assertThat(prunedMetadataFiles).as("prunedMetadataFiles") - .hasSize(initialMetadataFiles.size() - 3); + .hasSize(initialMetadataFiles.size() - 2); assertThat(prunedSnapshots).as("prunedSnapshots") .hasSizeLessThan(initialSnapshots.size()) .hasSize(1); From dd80510e632b045143c091ae93ab85e50062e2c1 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Fri, 1 Dec 2023 18:04:58 -0800 Subject: [PATCH 573/587] Remove trino-benchmarks module --- core/trino-main/pom.xml | 32 -- lib/trino-parquet/pom.xml | 6 - plugin/trino-hive/pom.xml | 6 - .../plugin/hive/HiveBenchmarkQueryRunner.java | 85 ----- plugin/trino-raptor-legacy/pom.xml | 6 - pom.xml | 7 - testing/trino-benchmark/pom.xml | 149 -------- .../io/trino/benchmark/AbstractBenchmark.java | 131 ------- .../benchmark/AbstractOperatorBenchmark.java | 343 ------------------ .../AbstractSimpleOperatorBenchmark.java | 61 ---- .../trino/benchmark/AbstractSqlBenchmark.java | 46 --- .../benchmark/ArrayAggregationBenchmark.java | 32 -- .../benchmark/ArrayComparisonBenchmark.java | 86 ----- .../benchmark/AverageBenchmarkResults.java | 61 ---- .../BenchmarkAggregationFunction.java | 57 --- .../trino/benchmark/BenchmarkQueryRunner.java | 57 --- .../trino/benchmark/BenchmarkResultHook.java | 23 -- .../io/trino/benchmark/BenchmarkSuite.java | 178 --------- .../benchmark/CastJsonParseBenchmark.java | 37 -- .../benchmark/CountAggregationBenchmark.java | 52 --- .../CountAggregationSqlBenchmark.java | 32 -- .../CountWithFilterSqlBenchmark.java | 32 -- .../DoubleSumAggregationBenchmark.java | 53 --- .../java/io/trino/benchmark/FormatUtils.java | 146 -------- .../GroupByAggregationSqlBenchmark.java | 32 -- .../GroupBySumWithArithmeticSqlBenchmark.java | 36 -- .../io/trino/benchmark/HandTpchQuery1.java | 331 ----------------- .../io/trino/benchmark/HandTpchQuery6.java | 138 ------- .../benchmark/HashAggregationBenchmark.java | 73 ---- .../benchmark/HashBuildAndJoinBenchmark.java | 164 --------- .../trino/benchmark/HashBuildBenchmark.java | 121 ------ .../io/trino/benchmark/HashJoinBenchmark.java | 136 ------- .../JsonAvgBenchmarkResultWriter.java | 110 ------ .../benchmark/JsonBenchmarkResultWriter.java | 74 ---- .../benchmark/JsonFunctionsBenchmark.java | 66 ---- .../LongMaxAggregationSqlBenchmark.java | 32 -- .../benchmark/OdsBenchmarkResultWriter.java | 77 ---- .../io/trino/benchmark/OrderByBenchmark.java | 70 ---- .../benchmark/PredicateFilterBenchmark.java | 72 ---- .../PredicateFilterSqlBenchmark.java | 32 -- .../benchmark/RawStreamingBenchmark.java | 45 --- .../benchmark/RawStreamingSqlBenchmark.java | 32 -- .../SimpleLineBenchmarkResultWriter.java | 58 --- ...proximateCountDistinctDoubleBenchmark.java | 32 -- ...ApproximateCountDistinctLongBenchmark.java | 32 -- ...ximateCountDistinctVarBinaryBenchmark.java | 32 -- .../SqlApproximatePercentileBenchmark.java | 32 -- .../trino/benchmark/SqlBetweenBenchmark.java | 32 -- .../SqlConsecutiveJoinBenchmark.java | 53 --- .../benchmark/SqlDistinctMultipleFields.java | 32 -- .../benchmark/SqlDistinctSingleField.java | 32 -- .../SqlDoubleSumAggregationBenchmark.java | 32 -- .../trino/benchmark/SqlHashJoinBenchmark.java | 36 -- .../io/trino/benchmark/SqlInBenchmark.java | 36 -- .../SqlJoinWithPredicateBenchmark.java | 36 -- .../io/trino/benchmark/SqlLikeBenchmark.java | 32 -- .../benchmark/SqlRegexpLikeBenchmark.java | 32 -- .../SqlSemiJoinInPredicateBenchmark.java | 36 -- .../io/trino/benchmark/SqlTopNBenchmark.java | 42 --- .../benchmark/SqlTopNRankingBenchmark.java | 50 --- .../io/trino/benchmark/SqlTpchQuery1.java | 53 --- .../io/trino/benchmark/SqlTpchQuery6.java | 39 -- .../trino/benchmark/StatisticsBenchmark.java | 106 ------ .../benchmark/StructuredTypesBenchmark.java | 53 --- .../io/trino/benchmark/Top100Benchmark.java | 56 --- .../trino/benchmark/Top100SqlBenchmark.java | 32 -- .../VarBinaryMaxAggregationSqlBenchmark.java | 32 -- .../trino/benchmark/BenchmarkCPUCounters.java | 92 ----- .../BenchmarkDecimalAggregation.java | 93 ----- .../benchmark/BenchmarkInequalityJoin.java | 160 -------- .../benchmark/MemoryLocalQueryRunner.java | 117 ------ .../io/trino/benchmark/TestBenchmarks.java | 38 -- 72 files changed, 4897 deletions(-) delete mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/HiveBenchmarkQueryRunner.java delete mode 100644 testing/trino-benchmark/pom.xml delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/AbstractBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/AbstractOperatorBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/AbstractSimpleOperatorBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/AbstractSqlBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/ArrayAggregationBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/ArrayComparisonBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/AverageBenchmarkResults.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/BenchmarkAggregationFunction.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/BenchmarkQueryRunner.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/BenchmarkResultHook.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/BenchmarkSuite.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/CastJsonParseBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/CountAggregationBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/CountAggregationSqlBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/CountWithFilterSqlBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/DoubleSumAggregationBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/FormatUtils.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/GroupByAggregationSqlBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/GroupBySumWithArithmeticSqlBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/HandTpchQuery1.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/HandTpchQuery6.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/HashAggregationBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/HashBuildAndJoinBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/HashBuildBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/HashJoinBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/JsonAvgBenchmarkResultWriter.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/JsonBenchmarkResultWriter.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/JsonFunctionsBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/LongMaxAggregationSqlBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/OdsBenchmarkResultWriter.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/OrderByBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/PredicateFilterBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/PredicateFilterSqlBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/RawStreamingBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/RawStreamingSqlBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/SimpleLineBenchmarkResultWriter.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlApproximateCountDistinctDoubleBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlApproximateCountDistinctLongBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlApproximateCountDistinctVarBinaryBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlApproximatePercentileBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlBetweenBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlConsecutiveJoinBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlDistinctMultipleFields.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlDistinctSingleField.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlDoubleSumAggregationBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlHashJoinBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlInBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlJoinWithPredicateBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlLikeBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlRegexpLikeBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlSemiJoinInPredicateBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlTopNBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlTopNRankingBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlTpchQuery1.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlTpchQuery6.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/StatisticsBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/StructuredTypesBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/Top100Benchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/Top100SqlBenchmark.java delete mode 100644 testing/trino-benchmark/src/main/java/io/trino/benchmark/VarBinaryMaxAggregationSqlBenchmark.java delete mode 100644 testing/trino-benchmark/src/test/java/io/trino/benchmark/BenchmarkCPUCounters.java delete mode 100644 testing/trino-benchmark/src/test/java/io/trino/benchmark/BenchmarkDecimalAggregation.java delete mode 100644 testing/trino-benchmark/src/test/java/io/trino/benchmark/BenchmarkInequalityJoin.java delete mode 100644 testing/trino-benchmark/src/test/java/io/trino/benchmark/MemoryLocalQueryRunner.java delete mode 100644 testing/trino-benchmark/src/test/java/io/trino/benchmark/TestBenchmarks.java diff --git a/core/trino-main/pom.xml b/core/trino-main/pom.xml index e2d416b17845..1363a9fa9a7c 100644 --- a/core/trino-main/pom.xml +++ b/core/trino-main/pom.xml @@ -554,36 +554,4 @@ - - - - benchmarks - - - - org.codehaus.mojo - exec-maven-plugin - - ${java.home}/bin/java - - -DoutputDirectory=benchmark_outputs - -classpath - - io.trino.benchmark.BenchmarkSuite - - test - - - - benchmarks - - exec - - - - - - - - diff --git a/lib/trino-parquet/pom.xml b/lib/trino-parquet/pom.xml index 62ff5f1405b4..4bf90122f230 100644 --- a/lib/trino-parquet/pom.xml +++ b/lib/trino-parquet/pom.xml @@ -139,12 +139,6 @@ test - - io.trino - trino-benchmark - test - - io.trino trino-main diff --git a/plugin/trino-hive/pom.xml b/plugin/trino-hive/pom.xml index 2b28b5738f9c..76f7d9f3fbdd 100644 --- a/plugin/trino-hive/pom.xml +++ b/plugin/trino-hive/pom.xml @@ -352,12 +352,6 @@ test - - io.trino - trino-benchmark - test - - io.trino trino-exchange-filesystem diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/HiveBenchmarkQueryRunner.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/HiveBenchmarkQueryRunner.java deleted file mode 100644 index d91e3629c2d0..000000000000 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/HiveBenchmarkQueryRunner.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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 io.trino.plugin.hive; - -import com.google.common.collect.ImmutableMap; -import io.trino.Session; -import io.trino.benchmark.BenchmarkSuite; -import io.trino.plugin.hive.metastore.Database; -import io.trino.plugin.hive.metastore.HiveMetastore; -import io.trino.plugin.tpch.TpchConnectorFactory; -import io.trino.spi.security.PrincipalType; -import io.trino.testing.LocalQueryRunner; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.util.Map; -import java.util.Optional; - -import static com.google.common.io.MoreFiles.deleteRecursively; -import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; -import static io.trino.plugin.hive.metastore.file.TestingFileHiveMetastore.createTestingFileHiveMetastore; -import static io.trino.testing.TestingSession.testSessionBuilder; -import static java.util.Objects.requireNonNull; - -public final class HiveBenchmarkQueryRunner -{ - private HiveBenchmarkQueryRunner() {} - - public static void main(String[] args) - throws IOException - { - String outputDirectory = requireNonNull(System.getProperty("outputDirectory"), "Must specify -DoutputDirectory=..."); - File tempDir = Files.createTempDirectory(null).toFile(); - try (LocalQueryRunner localQueryRunner = createLocalQueryRunner(tempDir)) { - new BenchmarkSuite(localQueryRunner, outputDirectory).runAllBenchmarks(); - } - finally { - deleteRecursively(tempDir.toPath(), ALLOW_INSECURE); - } - } - - public static LocalQueryRunner createLocalQueryRunner(File tempDir) - { - Session session = testSessionBuilder() - .setCatalog("hive") - .setSchema("tpch") - .build(); - - LocalQueryRunner localQueryRunner = LocalQueryRunner.create(session); - - // add tpch - localQueryRunner.createCatalog("tpch", new TpchConnectorFactory(1), ImmutableMap.of()); - - // add hive - File hiveDir = new File(tempDir, "hive_data"); - HiveMetastore metastore = createTestingFileHiveMetastore(hiveDir); - - metastore.createDatabase( - Database.builder() - .setDatabaseName("tpch") - .setOwnerName(Optional.of("public")) - .setOwnerType(Optional.of(PrincipalType.ROLE)) - .build()); - - Map hiveCatalogConfig = ImmutableMap.of("hive.max-split-size", "10GB"); - - localQueryRunner.createCatalog("hive", new TestingHiveConnectorFactory(metastore), hiveCatalogConfig); - - localQueryRunner.execute("CREATE TABLE orders AS SELECT * FROM tpch.sf1.orders"); - localQueryRunner.execute("CREATE TABLE lineitem AS SELECT * FROM tpch.sf1.lineitem"); - return localQueryRunner; - } -} diff --git a/plugin/trino-raptor-legacy/pom.xml b/plugin/trino-raptor-legacy/pom.xml index 75ba8ae77e02..47e4c7585392 100644 --- a/plugin/trino-raptor-legacy/pom.xml +++ b/plugin/trino-raptor-legacy/pom.xml @@ -216,12 +216,6 @@ test - - io.trino - trino-benchmark - test - - io.trino trino-client diff --git a/pom.xml b/pom.xml index 810b049af50d..979c3cd3d601 100644 --- a/pom.xml +++ b/pom.xml @@ -114,7 +114,6 @@ plugin/trino-tpch service/trino-proxy service/trino-verifier - testing/trino-benchmark testing/trino-benchmark-queries testing/trino-benchto-benchmarks testing/trino-faulttolerant-tests @@ -972,12 +971,6 @@ test-jar - - io.trino - trino-benchmark - ${project.version} - - io.trino trino-benchmark-queries diff --git a/testing/trino-benchmark/pom.xml b/testing/trino-benchmark/pom.xml deleted file mode 100644 index 798004a533da..000000000000 --- a/testing/trino-benchmark/pom.xml +++ /dev/null @@ -1,149 +0,0 @@ - - - 4.0.0 - - - io.trino - trino-root - 435-SNAPSHOT - ../../pom.xml - - - trino-benchmark - - - ${project.parent.basedir} - - - - - com.fasterxml.jackson.core - jackson-annotations - - - - com.fasterxml.jackson.core - jackson-core - - - - com.google.guava - guava - - - - io.airlift - concurrent - - - - io.airlift - json - - - - io.airlift - log - - - - io.airlift - stats - - - - io.airlift - units - - - - io.opentelemetry - opentelemetry-api - - - - io.trino - trino-main - - - - io.trino - trino-parser - - - - io.trino - trino-plugin-toolkit - - - - io.trino - trino-spi - - - - io.trino - trino-tpch - - - - jakarta.annotation - jakarta.annotation-api - - - - org.jetbrains - annotations - provided - - - - - io.airlift - bootstrap - runtime - - - - jakarta.ws.rs - jakarta.ws.rs-api - runtime - - - - io.airlift - junit-extensions - test - - - - io.trino - trino-memory - test - - - - io.trino - trino-testing-services - test - - - - org.junit.jupiter - junit-jupiter-api - test - - - - org.openjdk.jmh - jmh-core - test - - - - org.openjdk.jmh - jmh-generator-annprocess - test - - - diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/AbstractBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/AbstractBenchmark.java deleted file mode 100644 index 0d93d5e799c2..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/AbstractBenchmark.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.airlift.units.DataSize; -import io.airlift.units.Duration; -import jakarta.annotation.Nullable; - -import java.util.Map; - -import static com.google.common.base.Preconditions.checkArgument; -import static io.trino.benchmark.FormatUtils.formatCount; -import static io.trino.benchmark.FormatUtils.formatCountRate; -import static io.trino.benchmark.FormatUtils.formatDataRate; -import static io.trino.benchmark.FormatUtils.formatDataSize; -import static java.util.Objects.requireNonNull; -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static java.util.concurrent.TimeUnit.NANOSECONDS; - -public abstract class AbstractBenchmark -{ - private final String benchmarkName; - private final int warmupIterations; - private final int measuredIterations; - - protected AbstractBenchmark(String benchmarkName, int warmupIterations, int measuredIterations) - { - requireNonNull(benchmarkName, "benchmarkName is null"); - checkArgument(warmupIterations >= 0, "warmupIterations must not be negative"); - checkArgument(measuredIterations >= 0, "measuredIterations must not be negative"); - - this.benchmarkName = benchmarkName; - this.warmupIterations = warmupIterations; - this.measuredIterations = measuredIterations; - } - - public String getBenchmarkName() - { - return benchmarkName; - } - - /** - * Initialize any state necessary to run benchmark. This is run once at start up. - */ - protected void setUp() - { - // Default: no-op - } - - /** - * Runs the benchmark and returns the result metrics - */ - protected abstract Map runOnce(); - - /** - * Clean up any state from the benchmark. This is run once after all the iterations are complete. - */ - protected void tearDown() - { - // Default: no-op - } - - public void runBenchmark() - { - runBenchmark(null); - } - - public void runBenchmark(@Nullable BenchmarkResultHook benchmarkResultHook) - { - AverageBenchmarkResults averageBenchmarkResults = new AverageBenchmarkResults(); - setUp(); - try { - for (int i = 0; i < warmupIterations; i++) { - runOnce(); - } - for (int i = 0; i < measuredIterations; i++) { - Map results = runOnce(); - if (benchmarkResultHook != null) { - benchmarkResultHook.addResults(results); - } - averageBenchmarkResults.addResults(results); - } - } - catch (Throwable t) { - throw new RuntimeException("Exception in " + getBenchmarkName(), t); - } - finally { - tearDown(); - } - if (benchmarkResultHook != null) { - benchmarkResultHook.finished(); - } - - Map resultsAvg = averageBenchmarkResults.getAverageResultsValues(); - Duration cpuNanos = new Duration(resultsAvg.get("cpu_nanos"), NANOSECONDS); - - long inputRows = resultsAvg.get("input_rows").longValue(); - DataSize inputBytes = DataSize.ofBytes(Math.round(resultsAvg.get("input_bytes"))); - - long outputRows = resultsAvg.get("output_rows").longValue(); - DataSize outputBytes = DataSize.ofBytes(Math.round(resultsAvg.get("output_bytes"))); - - DataSize memory = DataSize.ofBytes(Math.round(resultsAvg.get("peak_memory"))); - System.out.printf("%35s :: %8.3f cpu ms :: %5s peak memory :: in %5s, %6s, %8s, %8s :: out %5s, %6s, %8s, %8s%n", - getBenchmarkName(), - cpuNanos.getValue(MILLISECONDS), - - formatDataSize(memory, true), - - formatCount(inputRows), - formatDataSize(inputBytes, true), - formatCountRate(inputRows, cpuNanos, true), - formatDataRate(inputBytes, cpuNanos, true), - - formatCount(outputRows), - formatDataSize(outputBytes, true), - formatCountRate(outputRows, cpuNanos, true), - formatDataRate(outputBytes, cpuNanos, true)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/AbstractOperatorBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/AbstractOperatorBenchmark.java deleted file mode 100644 index 43d2f892ea93..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/AbstractOperatorBenchmark.java +++ /dev/null @@ -1,343 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Iterables; -import io.airlift.stats.CpuTimer; -import io.airlift.stats.TestingGcMonitor; -import io.airlift.units.DataSize; -import io.opentelemetry.api.trace.Span; -import io.trino.Session; -import io.trino.execution.StageId; -import io.trino.execution.TaskId; -import io.trino.execution.TaskStateMachine; -import io.trino.memory.MemoryPool; -import io.trino.memory.QueryContext; -import io.trino.metadata.Metadata; -import io.trino.metadata.QualifiedObjectName; -import io.trino.metadata.ResolvedFunction; -import io.trino.metadata.Split; -import io.trino.metadata.TableHandle; -import io.trino.operator.Driver; -import io.trino.operator.DriverContext; -import io.trino.operator.FilterAndProjectOperator; -import io.trino.operator.Operator; -import io.trino.operator.OperatorContext; -import io.trino.operator.OperatorFactory; -import io.trino.operator.PageSourceOperator; -import io.trino.operator.TaskContext; -import io.trino.operator.TaskStats; -import io.trino.operator.project.InputPageProjection; -import io.trino.operator.project.PageProcessor; -import io.trino.operator.project.PageProjection; -import io.trino.security.AllowAllAccessControl; -import io.trino.spi.QueryId; -import io.trino.spi.connector.ColumnHandle; -import io.trino.spi.connector.ConnectorPageSource; -import io.trino.spi.connector.DynamicFilter; -import io.trino.spi.function.AggregationImplementation; -import io.trino.spi.type.Type; -import io.trino.spiller.SpillSpaceTracker; -import io.trino.split.SplitSource; -import io.trino.sql.gen.PageFunctionCompiler; -import io.trino.sql.planner.Symbol; -import io.trino.sql.planner.SymbolAllocator; -import io.trino.sql.planner.TypeProvider; -import io.trino.sql.planner.optimizations.HashGenerationOptimizer; -import io.trino.sql.planner.plan.PlanNodeId; -import io.trino.sql.relational.RowExpression; -import io.trino.sql.tree.Expression; -import io.trino.sql.tree.NodeRef; -import io.trino.testing.LocalQueryRunner; -import io.trino.transaction.TransactionId; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.base.Verify.verify; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static io.airlift.concurrent.MoreFutures.getFutureValue; -import static io.airlift.stats.CpuTimer.CpuDuration; -import static io.airlift.units.DataSize.Unit.GIGABYTE; -import static io.airlift.units.DataSize.Unit.MEGABYTE; -import static io.trino.SystemSessionProperties.getFilterAndProjectMinOutputPageRowCount; -import static io.trino.SystemSessionProperties.getFilterAndProjectMinOutputPageSize; -import static io.trino.execution.executor.timesharing.PrioritizedSplitRunner.SPLIT_RUN_QUANTA; -import static io.trino.spi.connector.Constraint.alwaysTrue; -import static io.trino.spi.type.BigintType.BIGINT; -import static io.trino.sql.analyzer.TypeSignatureProvider.fromTypes; -import static io.trino.sql.planner.TypeAnalyzer.createTestingTypeAnalyzer; -import static io.trino.sql.relational.SqlToRowExpressionTranslator.translate; -import static io.trino.testing.TestingSession.testSessionBuilder; -import static java.lang.String.format; -import static java.util.Objects.requireNonNull; -import static java.util.concurrent.TimeUnit.NANOSECONDS; -import static java.util.concurrent.TimeUnit.SECONDS; - -/** - * Abstract template for benchmarks that want to test the performance of an Operator. - */ -public abstract class AbstractOperatorBenchmark - extends AbstractBenchmark -{ - protected final LocalQueryRunner localQueryRunner; - protected final Session nonTransactionSession; - protected final Session session; - - protected AbstractOperatorBenchmark( - LocalQueryRunner localQueryRunner, - String benchmarkName, - int warmupIterations, - int measuredIterations) - { - this(localQueryRunner.getDefaultSession(), localQueryRunner, benchmarkName, warmupIterations, measuredIterations); - } - - protected AbstractOperatorBenchmark( - Session session, - LocalQueryRunner localQueryRunner, - String benchmarkName, - int warmupIterations, - int measuredIterations) - { - super(benchmarkName, warmupIterations, measuredIterations); - this.nonTransactionSession = requireNonNull(session, "session is null"); - this.localQueryRunner = requireNonNull(localQueryRunner, "localQueryRunner is null"); - - TransactionId transactionId = localQueryRunner.getTransactionManager().beginTransaction(false); - this.session = session.beginTransactionId( - transactionId, - localQueryRunner.getTransactionManager(), - new AllowAllAccessControl()); - } - - @Override - protected void tearDown() - { - localQueryRunner.getTransactionManager().asyncAbort(session.getRequiredTransactionId()); - super.tearDown(); - } - - protected final List getColumnTypes(String tableName, String... columnNames) - { - checkState(session.getCatalog().isPresent(), "catalog not set"); - checkState(session.getSchema().isPresent(), "schema not set"); - - // look up the table - Metadata metadata = localQueryRunner.getMetadata(); - QualifiedObjectName qualifiedTableName = new QualifiedObjectName(session.getCatalog().get(), session.getSchema().get(), tableName); - TableHandle tableHandle = metadata.getTableHandle(session, qualifiedTableName) - .orElseThrow(() -> new IllegalArgumentException(format("Table '%s' does not exist", qualifiedTableName))); - - Map allColumnHandles = metadata.getColumnHandles(session, tableHandle); - return Arrays.stream(columnNames) - .map(allColumnHandles::get) - .map(columnHandle -> metadata.getColumnMetadata(session, tableHandle, columnHandle).getType()) - .collect(toImmutableList()); - } - - protected final BenchmarkAggregationFunction createAggregationFunction(String name, Type... argumentTypes) - { - ResolvedFunction resolvedFunction = localQueryRunner.getMetadata().resolveBuiltinFunction(name, fromTypes(argumentTypes)); - AggregationImplementation aggregationImplementation = localQueryRunner.getFunctionManager().getAggregationImplementation(resolvedFunction); - return new BenchmarkAggregationFunction(resolvedFunction, aggregationImplementation); - } - - protected final OperatorFactory createTableScanOperator(int operatorId, PlanNodeId planNodeId, String tableName, String... columnNames) - { - checkArgument(session.getCatalog().isPresent(), "catalog not set"); - checkArgument(session.getSchema().isPresent(), "schema not set"); - - // look up the table - Metadata metadata = localQueryRunner.getMetadata(); - QualifiedObjectName qualifiedTableName = new QualifiedObjectName(session.getCatalog().get(), session.getSchema().get(), tableName); - TableHandle tableHandle = metadata.getTableHandle(session, qualifiedTableName).orElse(null); - checkArgument(tableHandle != null, "Table '%s' does not exist", qualifiedTableName); - - // lookup the columns - Map allColumnHandles = metadata.getColumnHandles(session, tableHandle); - ImmutableList.Builder columnHandlesBuilder = ImmutableList.builder(); - for (String columnName : columnNames) { - ColumnHandle columnHandle = allColumnHandles.get(columnName); - checkArgument(columnHandle != null, "Table '%s' does not have a column '%s'", tableName, columnName); - columnHandlesBuilder.add(columnHandle); - } - List columnHandles = columnHandlesBuilder.build(); - - // get the split for this table - Split split = getLocalQuerySplit(session, tableHandle); - - return new OperatorFactory() - { - @Override - public Operator createOperator(DriverContext driverContext) - { - OperatorContext operatorContext = driverContext.addOperatorContext(operatorId, planNodeId, "BenchmarkSource"); - ConnectorPageSource pageSource = localQueryRunner.getPageSourceManager().createPageSource(session, split, tableHandle, columnHandles, DynamicFilter.EMPTY); - return new PageSourceOperator(pageSource, operatorContext); - } - - @Override - public void noMoreOperators() - { - } - - @Override - public OperatorFactory duplicate() - { - throw new UnsupportedOperationException(); - } - }; - } - - private Split getLocalQuerySplit(Session session, TableHandle handle) - { - SplitSource splitSource = localQueryRunner.getSplitManager().getSplits(session, Span.getInvalid(), handle, DynamicFilter.EMPTY, alwaysTrue()); - List splits = new ArrayList<>(); - while (!splitSource.isFinished()) { - splits.addAll(getNextBatch(splitSource)); - } - checkArgument(splits.size() == 1, "Expected only one split for a local query, but got %s splits", splits.size()); - return splits.get(0); - } - - private static List getNextBatch(SplitSource splitSource) - { - return getFutureValue(splitSource.getNextBatch(1000)).getSplits(); - } - - protected final OperatorFactory createHashProjectOperator(int operatorId, PlanNodeId planNodeId, List types) - { - SymbolAllocator symbolAllocator = new SymbolAllocator(); - ImmutableMap.Builder symbolToInputMapping = ImmutableMap.builder(); - ImmutableList.Builder projections = ImmutableList.builder(); - for (int channel = 0; channel < types.size(); channel++) { - Symbol symbol = symbolAllocator.newSymbol("h" + channel, types.get(channel)); - symbolToInputMapping.put(symbol, channel); - projections.add(new InputPageProjection(channel, types.get(channel))); - } - - Map symbolTypes = symbolAllocator.getTypes().allTypes(); - Optional hashExpression = HashGenerationOptimizer.getHashExpression( - localQueryRunner.getMetadata(), - symbolAllocator, - ImmutableList.copyOf(symbolTypes.keySet())); - verify(hashExpression.isPresent()); - - Map, Type> expressionTypes = createTestingTypeAnalyzer(localQueryRunner.getPlannerContext()) - .getTypes(session, TypeProvider.copyOf(symbolTypes), hashExpression.get()); - - RowExpression translated = translate(hashExpression.get(), expressionTypes, symbolToInputMapping.buildOrThrow(), localQueryRunner.getMetadata(), localQueryRunner.getFunctionManager(), session, false); - - PageFunctionCompiler functionCompiler = new PageFunctionCompiler(localQueryRunner.getFunctionManager(), 0); - projections.add(functionCompiler.compileProjection(translated, Optional.empty()).get()); - - return FilterAndProjectOperator.createOperatorFactory( - operatorId, - planNodeId, - () -> new PageProcessor(Optional.empty(), projections.build()), - ImmutableList.copyOf(Iterables.concat(types, ImmutableList.of(BIGINT))), - getFilterAndProjectMinOutputPageSize(session), - getFilterAndProjectMinOutputPageRowCount(session)); - } - - protected abstract List createDrivers(TaskContext taskContext); - - protected Map execute(TaskContext taskContext) - { - List drivers = createDrivers(taskContext); - - long peakMemory = 0; - boolean done = false; - while (!done) { - boolean processed = false; - for (Driver driver : drivers) { - if (!driver.isFinished()) { - driver.processForDuration(SPLIT_RUN_QUANTA); - long lastPeakMemory = peakMemory; - peakMemory = taskContext.getTaskStats().getUserMemoryReservation().toBytes(); - if (peakMemory <= lastPeakMemory) { - peakMemory = lastPeakMemory; - } - processed = true; - } - } - done = !processed; - } - return ImmutableMap.of("peak_memory", peakMemory); - } - - @Override - protected Map runOnce() - { - Session session = testSessionBuilder() - .setSystemProperty("optimizer.optimize-hash-generation", "true") - .setTransactionId(this.session.getRequiredTransactionId()) - .build(); - MemoryPool memoryPool = new MemoryPool(DataSize.of(1, GIGABYTE)); - SpillSpaceTracker spillSpaceTracker = new SpillSpaceTracker(DataSize.of(1, GIGABYTE)); - - TaskContext taskContext = new QueryContext( - new QueryId("test"), - DataSize.of(256, MEGABYTE), - memoryPool, - new TestingGcMonitor(), - localQueryRunner.getExecutor(), - localQueryRunner.getScheduler(), - DataSize.of(256, MEGABYTE), - spillSpaceTracker) - .addTaskContext(new TaskStateMachine(new TaskId(new StageId("query", 0), 0, 0), localQueryRunner.getExecutor()), - session, - () -> {}, - false, - false); - - CpuTimer cpuTimer = new CpuTimer(); - Map executionStats = execute(taskContext); - CpuDuration executionTime = cpuTimer.elapsedTime(); - - TaskStats taskStats = taskContext.getTaskStats(); - long inputRows = taskStats.getRawInputPositions(); - long inputBytes = taskStats.getRawInputDataSize().toBytes(); - long outputRows = taskStats.getOutputPositions(); - long outputBytes = taskStats.getOutputDataSize().toBytes(); - - double inputMegaBytes = ((double) inputBytes) / MEGABYTE.inBytes(); - - return ImmutableMap.builder() - // legacy computed values - .putAll(executionStats) - .put("elapsed_millis", executionTime.getWall().toMillis()) - .put("input_rows_per_second", (long) (inputRows / executionTime.getWall().getValue(SECONDS))) - .put("output_rows_per_second", (long) (outputRows / executionTime.getWall().getValue(SECONDS))) - .put("input_megabytes", (long) inputMegaBytes) - .put("input_megabytes_per_second", (long) (inputMegaBytes / executionTime.getWall().getValue(SECONDS))) - - .put("wall_nanos", executionTime.getWall().roundTo(NANOSECONDS)) - .put("cpu_nanos", executionTime.getCpu().roundTo(NANOSECONDS)) - .put("user_nanos", executionTime.getUser().roundTo(NANOSECONDS)) - .put("input_rows", inputRows) - .put("input_bytes", inputBytes) - .put("output_rows", outputRows) - .put("output_bytes", outputBytes) - .buildOrThrow(); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/AbstractSimpleOperatorBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/AbstractSimpleOperatorBenchmark.java deleted file mode 100644 index d3e7453131fd..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/AbstractSimpleOperatorBenchmark.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import com.google.common.collect.ImmutableList; -import io.trino.operator.Driver; -import io.trino.operator.DriverContext; -import io.trino.operator.DriverFactory; -import io.trino.operator.OperatorFactory; -import io.trino.operator.TaskContext; -import io.trino.sql.planner.plan.PlanNodeId; -import io.trino.testing.LocalQueryRunner; -import io.trino.testing.NullOutputOperator.NullOutputOperatorFactory; - -import java.util.ArrayList; -import java.util.List; -import java.util.OptionalInt; - -public abstract class AbstractSimpleOperatorBenchmark - extends AbstractOperatorBenchmark -{ - protected AbstractSimpleOperatorBenchmark( - LocalQueryRunner localQueryRunner, - String benchmarkName, - int warmupIterations, - int measuredIterations) - { - super(localQueryRunner, benchmarkName, warmupIterations, measuredIterations); - } - - protected abstract List createOperatorFactories(); - - protected DriverFactory createDriverFactory() - { - List operatorFactories = new ArrayList<>(createOperatorFactories()); - - operatorFactories.add(new NullOutputOperatorFactory(999, new PlanNodeId("test"))); - - return new DriverFactory(0, true, true, operatorFactories, OptionalInt.empty()); - } - - @Override - protected List createDrivers(TaskContext taskContext) - { - DriverFactory driverFactory = createDriverFactory(); - DriverContext driverContext = taskContext.addPipelineContext(0, true, true, false).addDriverContext(); - Driver driver = driverFactory.createDriver(driverContext); - return ImmutableList.of(driver); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/AbstractSqlBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/AbstractSqlBenchmark.java deleted file mode 100644 index 93ce22b3a34e..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/AbstractSqlBenchmark.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.operator.Driver; -import io.trino.operator.TaskContext; -import io.trino.testing.LocalQueryRunner; -import io.trino.testing.NullOutputOperator.NullOutputFactory; -import org.intellij.lang.annotations.Language; - -import java.util.List; - -public abstract class AbstractSqlBenchmark - extends AbstractOperatorBenchmark -{ - @Language("SQL") - private final String query; - - protected AbstractSqlBenchmark( - LocalQueryRunner localQueryRunner, - String benchmarkName, - int warmupIterations, - int measuredIterations, - @Language("SQL") String query) - { - super(localQueryRunner, benchmarkName, warmupIterations, measuredIterations); - this.query = query; - } - - @Override - protected List createDrivers(TaskContext taskContext) - { - return localQueryRunner.createDrivers(nonTransactionSession, query, new NullOutputFactory(), taskContext); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/ArrayAggregationBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/ArrayAggregationBenchmark.java deleted file mode 100644 index 9945d7dc5281..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/ArrayAggregationBenchmark.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class ArrayAggregationBenchmark - extends AbstractSqlBenchmark -{ - public ArrayAggregationBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "sql_double_array_agg", 10, 100, "select array_agg(totalprice) from orders group by orderkey"); - } - - public static void main(String[] args) - { - new ArrayAggregationBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/ArrayComparisonBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/ArrayComparisonBenchmark.java deleted file mode 100644 index 9af9cf6c2aa5..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/ArrayComparisonBenchmark.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public abstract class ArrayComparisonBenchmark -{ - public static void main(String... args) - { - LocalQueryRunner localQueryRunner = createLocalQueryRunner(); - new ArrayEqualsBenchmark(localQueryRunner).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - new ArrayLessThanBenchmark(localQueryRunner).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - new ArrayGreaterThanBenchmark(localQueryRunner).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - new ArrayNotEqualBenchmark(localQueryRunner).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - new ArrayLessThanOrEqualBenchmark(localQueryRunner).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - new ArrayGreaterThanOrEqualBenchmark(localQueryRunner).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } - - public static class ArrayEqualsBenchmark - extends AbstractSqlBenchmark - { - public ArrayEqualsBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "array_equals", 5, 50, "SELECT COUNT_IF(ARRAY [orderkey, orderkey + 1, orderkey + 2] = ARRAY[orderkey + 1, orderkey + 2, orderkey + 3]) FROM orders"); - } - } - - public static class ArrayLessThanBenchmark - extends AbstractSqlBenchmark - { - public ArrayLessThanBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "array_less_than", 5, 50, "SELECT COUNT_IF(ARRAY [quantity, quantity + 5] < ARRAY[quantity, quantity + 5, quantity + 3]) FROM lineitem"); - } - } - - public static class ArrayGreaterThanBenchmark - extends AbstractSqlBenchmark - { - public ArrayGreaterThanBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "array_greater_than", 5, 50, "SELECT COUNT_IF(ARRAY [quantity, quantity + 6] > ARRAY[quantity, quantity + 5, quantity + 3]) FROM lineitem"); - } - } - - public static class ArrayNotEqualBenchmark - extends AbstractSqlBenchmark - { - public ArrayNotEqualBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "array_not_equal", 5, 50, "SELECT COUNT_IF(ARRAY [orderkey, orderkey + 1, orderkey + 2] != ARRAY[orderkey + 1, orderkey + 2, orderkey + 3]) FROM orders"); - } - } - - public static class ArrayLessThanOrEqualBenchmark - extends AbstractSqlBenchmark - { - public ArrayLessThanOrEqualBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "array_less_than_or_equal", 5, 50, "SELECT COUNT_IF(ARRAY [quantity, quantity + 5, quantity + 2] <= ARRAY[quantity, quantity + 5, quantity + 3]) FROM lineitem"); - } - } - - public static class ArrayGreaterThanOrEqualBenchmark - extends AbstractSqlBenchmark - { - public ArrayGreaterThanOrEqualBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "array_greater_than_or_equal", 5, 50, "SELECT COUNT_IF(ARRAY [quantity, quantity + 6] >= ARRAY[quantity, quantity + 5, quantity + 3]) FROM lineitem"); - } - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/AverageBenchmarkResults.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/AverageBenchmarkResults.java deleted file mode 100644 index 14d5a87c0bf8..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/AverageBenchmarkResults.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import com.google.common.collect.Maps; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Map.Entry; - -import static java.lang.String.format; -import static java.util.Objects.requireNonNull; - -public class AverageBenchmarkResults - implements BenchmarkResultHook -{ - private final Map resultsSum = new LinkedHashMap<>(); - private int resultsCount; - - @Override - public BenchmarkResultHook addResults(Map results) - { - requireNonNull(results, "results is null"); - for (Entry entry : results.entrySet()) { - Long currentSum = resultsSum.get(entry.getKey()); - if (currentSum == null) { - currentSum = 0L; - } - resultsSum.put(entry.getKey(), currentSum + entry.getValue()); - } - resultsCount++; - - return this; - } - - public Map getAverageResultsValues() - { - return Maps.transformValues(resultsSum, input -> 1.0 * input / resultsCount); - } - - public Map getAverageResultsStrings() - { - return Maps.transformValues(resultsSum, input -> format("%,3.2f", 1.0 * input / resultsCount)); - } - - @Override - public void finished() - { - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/BenchmarkAggregationFunction.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/BenchmarkAggregationFunction.java deleted file mode 100644 index 9f48902a4bf5..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/BenchmarkAggregationFunction.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import com.google.common.collect.ImmutableList; -import io.trino.metadata.ResolvedFunction; -import io.trino.operator.aggregation.AccumulatorFactory; -import io.trino.operator.aggregation.AggregatorFactory; -import io.trino.spi.function.AggregationImplementation; -import io.trino.spi.function.BoundSignature; -import io.trino.spi.type.Type; -import io.trino.sql.planner.plan.AggregationNode.Step; - -import java.util.List; -import java.util.OptionalInt; - -import static com.google.common.collect.Iterables.getOnlyElement; -import static io.trino.operator.aggregation.AccumulatorCompiler.generateAccumulatorFactory; - -public class BenchmarkAggregationFunction -{ - private final Type intermediateType; - private final AccumulatorFactory accumulatorFactory; - private final Type finalType; - - public BenchmarkAggregationFunction(ResolvedFunction resolvedFunction, AggregationImplementation aggregationImplementation) - { - BoundSignature signature = resolvedFunction.getSignature(); - intermediateType = getOnlyElement(aggregationImplementation.getAccumulatorStateDescriptors()).getSerializer().getSerializedType(); - finalType = signature.getReturnType(); - accumulatorFactory = generateAccumulatorFactory(signature, aggregationImplementation, resolvedFunction.getFunctionNullability(), true); - } - - public AggregatorFactory bind(List inputChannels) - { - return new AggregatorFactory( - accumulatorFactory, - Step.SINGLE, - intermediateType, - finalType, - inputChannels, - OptionalInt.empty(), - true, - ImmutableList.of()); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/BenchmarkQueryRunner.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/BenchmarkQueryRunner.java deleted file mode 100644 index bbb52d36189f..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/BenchmarkQueryRunner.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import com.google.common.collect.ImmutableMap; -import io.trino.Session; -import io.trino.plugin.tpch.TpchConnectorFactory; -import io.trino.testing.LocalQueryRunner; - -import java.util.Map; - -import static io.trino.Session.SessionBuilder; -import static io.trino.plugin.tpch.TpchMetadata.TINY_SCHEMA_NAME; -import static io.trino.testing.TestingSession.testSessionBuilder; - -public final class BenchmarkQueryRunner -{ - private BenchmarkQueryRunner() {} - - public static LocalQueryRunner createLocalQueryRunnerHashEnabled() - { - return createLocalQueryRunner(ImmutableMap.of("optimizer.optimize_hash_generation", "true")); - } - - public static LocalQueryRunner createLocalQueryRunner() - { - return createLocalQueryRunner(ImmutableMap.of()); - } - - public static LocalQueryRunner createLocalQueryRunner(Map extraSessionProperties) - { - SessionBuilder sessionBuilder = testSessionBuilder() - .setCatalog("tpch") - .setSchema(TINY_SCHEMA_NAME); - - extraSessionProperties.forEach(sessionBuilder::setSystemProperty); - - Session session = sessionBuilder.build(); - LocalQueryRunner localQueryRunner = LocalQueryRunner.create(session); - - // add tpch - localQueryRunner.createCatalog("tpch", new TpchConnectorFactory(1), ImmutableMap.of()); - - return localQueryRunner; - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/BenchmarkResultHook.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/BenchmarkResultHook.java deleted file mode 100644 index 1f10550a94a2..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/BenchmarkResultHook.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import java.util.Map; - -public interface BenchmarkResultHook -{ - BenchmarkResultHook addResults(Map results); - - void finished(); -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/BenchmarkSuite.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/BenchmarkSuite.java deleted file mode 100644 index 6f69607eb772..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/BenchmarkSuite.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import com.google.common.collect.ImmutableList; -import com.google.common.io.Files; -import io.airlift.log.Logger; -import io.trino.Session; -import io.trino.testing.LocalQueryRunner; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.List; -import java.util.Map; - -import static io.trino.SystemSessionProperties.OPTIMIZE_HASH_GENERATION; -import static java.lang.String.format; -import static java.util.Objects.requireNonNull; - -public class BenchmarkSuite -{ - private static final Logger LOGGER = Logger.get(BenchmarkSuite.class); - - public static List createBenchmarks(LocalQueryRunner localQueryRunner) - { - Session optimizeHashSession = Session.builder(localQueryRunner.getDefaultSession()) - .setSystemProperty(OPTIMIZE_HASH_GENERATION, "true") - .build(); - return ImmutableList.of( - // hand built benchmarks - new CountAggregationBenchmark(localQueryRunner), - new DoubleSumAggregationBenchmark(localQueryRunner), - new HashAggregationBenchmark(localQueryRunner), - new PredicateFilterBenchmark(localQueryRunner), - new RawStreamingBenchmark(localQueryRunner), - new Top100Benchmark(localQueryRunner), - new OrderByBenchmark(localQueryRunner), - new HashBuildBenchmark(localQueryRunner), - new HashJoinBenchmark(localQueryRunner), - new HashBuildAndJoinBenchmark(localQueryRunner.getDefaultSession(), localQueryRunner), - new HashBuildAndJoinBenchmark(optimizeHashSession, localQueryRunner), - new HandTpchQuery1(localQueryRunner), - new HandTpchQuery6(localQueryRunner), - - // sql benchmarks - new GroupBySumWithArithmeticSqlBenchmark(localQueryRunner), - new CountAggregationSqlBenchmark(localQueryRunner), - new SqlDoubleSumAggregationBenchmark(localQueryRunner), - new CountWithFilterSqlBenchmark(localQueryRunner), - new GroupByAggregationSqlBenchmark(localQueryRunner), - new PredicateFilterSqlBenchmark(localQueryRunner), - new RawStreamingSqlBenchmark(localQueryRunner), - new Top100SqlBenchmark(localQueryRunner), - new SqlHashJoinBenchmark(localQueryRunner), - new SqlJoinWithPredicateBenchmark(localQueryRunner), - new LongMaxAggregationSqlBenchmark(localQueryRunner), - new VarBinaryMaxAggregationSqlBenchmark(localQueryRunner), - new SqlDistinctMultipleFields(localQueryRunner), - new SqlDistinctSingleField(localQueryRunner), - new SqlTpchQuery1(localQueryRunner), - new SqlTpchQuery6(localQueryRunner), - new SqlLikeBenchmark(localQueryRunner), - new SqlInBenchmark(localQueryRunner), - new SqlSemiJoinInPredicateBenchmark(localQueryRunner), - new SqlRegexpLikeBenchmark(localQueryRunner), - new SqlApproximatePercentileBenchmark(localQueryRunner), - new SqlBetweenBenchmark(localQueryRunner), - - // statistics benchmarks - new StatisticsBenchmark.LongVarianceBenchmark(localQueryRunner), - new StatisticsBenchmark.LongVariancePopBenchmark(localQueryRunner), - new StatisticsBenchmark.DoubleVarianceBenchmark(localQueryRunner), - new StatisticsBenchmark.DoubleVariancePopBenchmark(localQueryRunner), - new StatisticsBenchmark.LongStdDevBenchmark(localQueryRunner), - new StatisticsBenchmark.LongStdDevPopBenchmark(localQueryRunner), - new StatisticsBenchmark.DoubleStdDevBenchmark(localQueryRunner), - new StatisticsBenchmark.DoubleStdDevPopBenchmark(localQueryRunner), - - // array comparison benchmarks - new ArrayComparisonBenchmark.ArrayEqualsBenchmark(localQueryRunner), - new ArrayComparisonBenchmark.ArrayLessThanBenchmark(localQueryRunner), - new ArrayComparisonBenchmark.ArrayGreaterThanBenchmark(localQueryRunner), - new ArrayComparisonBenchmark.ArrayNotEqualBenchmark(localQueryRunner), - new ArrayComparisonBenchmark.ArrayLessThanOrEqualBenchmark(localQueryRunner), - new ArrayComparisonBenchmark.ArrayGreaterThanOrEqualBenchmark(localQueryRunner), - - new SqlApproximateCountDistinctLongBenchmark(localQueryRunner), - new SqlApproximateCountDistinctDoubleBenchmark(localQueryRunner), - new SqlApproximateCountDistinctVarBinaryBenchmark(localQueryRunner)); - } - - private final LocalQueryRunner localQueryRunner; - private final String outputDirectory; - - public BenchmarkSuite(LocalQueryRunner localQueryRunner, String outputDirectory) - { - this.localQueryRunner = localQueryRunner; - this.outputDirectory = requireNonNull(outputDirectory, "outputDirectory is null"); - } - - private static File createOutputFile(String fileName) - throws IOException - { - File outputFile = new File(fileName); - Files.createParentDirs(outputFile); - return outputFile; - } - - public void runAllBenchmarks() - throws IOException - { - List benchmarks = createBenchmarks(localQueryRunner); - - LOGGER.info("=== Pre-running all benchmarks for JVM warmup ==="); - for (AbstractBenchmark benchmark : benchmarks) { - benchmark.runBenchmark(); - } - - LOGGER.info("=== Actually running benchmarks for metrics ==="); - for (AbstractBenchmark benchmark : benchmarks) { - try (OutputStream jsonOut = new FileOutputStream(createOutputFile(format("%s/json/%s.json", outputDirectory, benchmark.getBenchmarkName()))); - OutputStream jsonAvgOut = new FileOutputStream(createOutputFile(format("%s/json-avg/%s.json", outputDirectory, benchmark.getBenchmarkName()))); - OutputStream csvOut = new FileOutputStream(createOutputFile(format("%s/csv/%s.csv", outputDirectory, benchmark.getBenchmarkName()))); - OutputStream odsOut = new FileOutputStream(createOutputFile(format("%s/ods/%s.json", outputDirectory, benchmark.getBenchmarkName())))) { - benchmark.runBenchmark( - new ForwardingBenchmarkResultWriter( - ImmutableList.of( - new JsonBenchmarkResultWriter(jsonOut), - new JsonAvgBenchmarkResultWriter(jsonAvgOut), - new SimpleLineBenchmarkResultWriter(csvOut), - new OdsBenchmarkResultWriter("trino.benchmark." + benchmark.getBenchmarkName(), odsOut)))); - } - } - } - - private static class ForwardingBenchmarkResultWriter - implements BenchmarkResultHook - { - private final List benchmarkResultHooks; - - private ForwardingBenchmarkResultWriter(List benchmarkResultHooks) - { - requireNonNull(benchmarkResultHooks, "benchmarkResultHooks is null"); - this.benchmarkResultHooks = ImmutableList.copyOf(benchmarkResultHooks); - } - - @Override - public BenchmarkResultHook addResults(Map results) - { - requireNonNull(results, "results is null"); - for (BenchmarkResultHook benchmarkResultHook : benchmarkResultHooks) { - benchmarkResultHook.addResults(results); - } - return this; - } - - @Override - public void finished() - { - for (BenchmarkResultHook benchmarkResultHook : benchmarkResultHooks) { - benchmarkResultHook.finished(); - } - } - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/CastJsonParseBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/CastJsonParseBenchmark.java deleted file mode 100644 index fb3f9b8e5dc0..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/CastJsonParseBenchmark.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class CastJsonParseBenchmark - extends AbstractSqlBenchmark -{ - public CastJsonParseBenchmark(LocalQueryRunner localQueryRunner) - { - super( - localQueryRunner, - "sql_cast_json_parse", - 10, - 100, - "select cast(json_parse('[' || array_join(repeat(totalprice, 100), ',') || ']') as array(real)) from orders"); - } - - public static void main(String[] args) - { - new CastJsonParseBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/CountAggregationBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/CountAggregationBenchmark.java deleted file mode 100644 index 2211dc5fbbe1..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/CountAggregationBenchmark.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import com.google.common.collect.ImmutableList; -import io.trino.operator.AggregationOperator.AggregationOperatorFactory; -import io.trino.operator.OperatorFactory; -import io.trino.sql.planner.plan.PlanNodeId; -import io.trino.testing.LocalQueryRunner; - -import java.util.List; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class CountAggregationBenchmark - extends AbstractSimpleOperatorBenchmark -{ - private final BenchmarkAggregationFunction countFunction; - - public CountAggregationBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "count_agg", 10, 100); - countFunction = createAggregationFunction("count"); - } - - @Override - protected List createOperatorFactories() - { - OperatorFactory tableScanOperator = createTableScanOperator(0, new PlanNodeId("test"), "orders", "orderkey"); - AggregationOperatorFactory aggregationOperator = new AggregationOperatorFactory( - 1, - new PlanNodeId("test"), - ImmutableList.of(countFunction.bind(ImmutableList.of(0)))); - return ImmutableList.of(tableScanOperator, aggregationOperator); - } - - public static void main(String[] args) - { - new CountAggregationBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/CountAggregationSqlBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/CountAggregationSqlBenchmark.java deleted file mode 100644 index d0d750a6a1a3..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/CountAggregationSqlBenchmark.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class CountAggregationSqlBenchmark - extends AbstractSqlBenchmark -{ - public CountAggregationSqlBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "sql_count_agg", 10, 100, "select count(*) from orders"); - } - - public static void main(String[] args) - { - new CountAggregationSqlBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/CountWithFilterSqlBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/CountWithFilterSqlBenchmark.java deleted file mode 100644 index 257e8acd6eba..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/CountWithFilterSqlBenchmark.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class CountWithFilterSqlBenchmark - extends AbstractSqlBenchmark -{ - public CountWithFilterSqlBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "sql_count_with_filter", 10, 100, "SELECT count(*) from orders where orderstatus = 'F'"); - } - - public static void main(String[] args) - { - new CountWithFilterSqlBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/DoubleSumAggregationBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/DoubleSumAggregationBenchmark.java deleted file mode 100644 index 98e41577e354..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/DoubleSumAggregationBenchmark.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import com.google.common.collect.ImmutableList; -import io.trino.operator.AggregationOperator.AggregationOperatorFactory; -import io.trino.operator.OperatorFactory; -import io.trino.sql.planner.plan.PlanNodeId; -import io.trino.testing.LocalQueryRunner; - -import java.util.List; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; -import static io.trino.spi.type.DoubleType.DOUBLE; - -public class DoubleSumAggregationBenchmark - extends AbstractSimpleOperatorBenchmark -{ - private final BenchmarkAggregationFunction doubleSum; - - public DoubleSumAggregationBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "double_sum_agg", 10, 100); - doubleSum = createAggregationFunction("sum", DOUBLE); - } - - @Override - protected List createOperatorFactories() - { - OperatorFactory tableScanOperator = createTableScanOperator(0, new PlanNodeId("test"), "orders", "totalprice"); - AggregationOperatorFactory aggregationOperator = new AggregationOperatorFactory( - 1, - new PlanNodeId("test"), - ImmutableList.of(doubleSum.bind(ImmutableList.of(0)))); - return ImmutableList.of(tableScanOperator, aggregationOperator); - } - - public static void main(String[] args) - { - new DoubleSumAggregationBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/FormatUtils.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/FormatUtils.java deleted file mode 100644 index 3d495d25cbc8..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/FormatUtils.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.airlift.units.DataSize; -import io.airlift.units.Duration; - -import java.math.RoundingMode; -import java.text.DecimalFormat; - -import static java.lang.String.format; -import static java.util.concurrent.TimeUnit.SECONDS; - -// TODO: these should be in airlift -final class FormatUtils -{ - private FormatUtils() {} - - public static String formatCount(long count) - { - double fractional = count; - String unit = ""; - if (fractional > 1000) { - fractional /= 1000; - unit = "K"; - } - if (fractional > 1000) { - fractional /= 1000; - unit = "M"; - } - if (fractional > 1000) { - fractional /= 1000; - unit = "B"; - } - if (fractional > 1000) { - fractional /= 1000; - unit = "T"; - } - if (fractional > 1000) { - fractional /= 1000; - unit = "Q"; - } - - return format("%s%s", getFormat(fractional).format(fractional), unit); - } - - public static String formatCountRate(double count, Duration duration, boolean longForm) - { - double rate = count / duration.getValue(SECONDS); - if (Double.isNaN(rate) || Double.isInfinite(rate)) { - rate = 0; - } - - String rateString = formatCount((long) rate); - if (longForm) { - if (rateString.endsWith(" ")) { - rateString = rateString.substring(0, rateString.length() - 1); - } - rateString += "/s"; - } - return rateString; - } - - public static String formatDataSize(DataSize size, boolean longForm) - { - double fractional = size.toBytes(); - String unit = null; - if (fractional >= 1024) { - fractional /= 1024; - unit = "K"; - } - if (fractional >= 1024) { - fractional /= 1024; - unit = "M"; - } - if (fractional >= 1024) { - fractional /= 1024; - unit = "G"; - } - if (fractional >= 1024) { - fractional /= 1024; - unit = "T"; - } - if (fractional >= 1024) { - fractional /= 1024; - unit = "P"; - } - - if (unit == null) { - unit = "B"; - } - else if (longForm) { - unit += "B"; - } - - return format("%s%s", getFormat(fractional).format(fractional), unit); - } - - public static String formatDataRate(DataSize dataSize, Duration duration, boolean longForm) - { - double rate = dataSize.toBytes() / duration.getValue(SECONDS); - if (Double.isNaN(rate) || Double.isInfinite(rate)) { - rate = 0; - } - - String rateString = formatDataSize(DataSize.ofBytes(Math.round(rate)), false); - if (longForm) { - if (!rateString.endsWith("B")) { - rateString += "B"; - } - rateString += "/s"; - } - return rateString; - } - - public static DecimalFormat getFormat(double value) - { - DecimalFormat format; - if (value < 10) { - // show up to two decimals to get 3 significant digits - format = new DecimalFormat("#.##"); - } - else if (value < 100) { - // show up to one decimal to get 3 significant digits - format = new DecimalFormat("#.#"); - } - else { - // show no decimals -- we have enough digits in the integer part - format = new DecimalFormat("#"); - } - - format.setRoundingMode(RoundingMode.HALF_UP); - return format; - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/GroupByAggregationSqlBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/GroupByAggregationSqlBenchmark.java deleted file mode 100644 index f20240dec3f5..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/GroupByAggregationSqlBenchmark.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class GroupByAggregationSqlBenchmark - extends AbstractSqlBenchmark -{ - public GroupByAggregationSqlBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "sql_groupby_agg", 15, 100, "select orderstatus, sum(totalprice) from orders group by orderstatus"); - } - - public static void main(String[] args) - { - new GroupByAggregationSqlBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/GroupBySumWithArithmeticSqlBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/GroupBySumWithArithmeticSqlBenchmark.java deleted file mode 100644 index 2a58625be5a0..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/GroupBySumWithArithmeticSqlBenchmark.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class GroupBySumWithArithmeticSqlBenchmark - extends AbstractSqlBenchmark -{ - public GroupBySumWithArithmeticSqlBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, - "sql_groupby_agg_with_arithmetic", - 1, - 4, - "select linestatus, sum(orderkey - partkey) from lineitem group by linestatus"); - } - - public static void main(String[] args) - { - new GroupBySumWithArithmeticSqlBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/HandTpchQuery1.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/HandTpchQuery1.java deleted file mode 100644 index 9e7d6bd4fdca..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/HandTpchQuery1.java +++ /dev/null @@ -1,331 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import com.google.common.collect.ImmutableList; -import com.google.common.primitives.Ints; -import io.airlift.units.DataSize; -import io.trino.benchmark.HandTpchQuery1.TpchQuery1Operator.TpchQuery1OperatorFactory; -import io.trino.operator.DriverContext; -import io.trino.operator.HashAggregationOperator.HashAggregationOperatorFactory; -import io.trino.operator.Operator; -import io.trino.operator.OperatorContext; -import io.trino.operator.OperatorFactory; -import io.trino.spi.Page; -import io.trino.spi.PageBuilder; -import io.trino.spi.block.Block; -import io.trino.spi.type.Type; -import io.trino.sql.gen.JoinCompiler; -import io.trino.sql.planner.plan.AggregationNode.Step; -import io.trino.sql.planner.plan.PlanNodeId; -import io.trino.testing.LocalQueryRunner; -import io.trino.util.DateTimeUtils; - -import java.util.List; -import java.util.Optional; - -import static com.google.common.base.Preconditions.checkState; -import static io.airlift.units.DataSize.Unit.MEGABYTE; -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; -import static io.trino.spi.type.BigintType.BIGINT; -import static io.trino.spi.type.DateType.DATE; -import static io.trino.spi.type.DoubleType.DOUBLE; -import static io.trino.spi.type.VarcharType.VARCHAR; -import static java.util.Objects.requireNonNull; - -public class HandTpchQuery1 - extends AbstractSimpleOperatorBenchmark -{ - private final BenchmarkAggregationFunction longAverage; - private final BenchmarkAggregationFunction doubleAverage; - private final BenchmarkAggregationFunction doubleSum; - private final BenchmarkAggregationFunction countFunction; - - public HandTpchQuery1(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "hand_tpch_query_1", 1, 5); - - longAverage = createAggregationFunction("avg", BIGINT); - doubleAverage = createAggregationFunction("avg", DOUBLE); - doubleSum = createAggregationFunction("sum", DOUBLE); - countFunction = createAggregationFunction("count"); - } - - @Override - protected List createOperatorFactories() - { - // select - // returnflag, - // linestatus, - // sum(quantity) as sum_qty, - // sum(extendedprice) as sum_base_price, - // sum(extendedprice * (1 - discount)) as sum_disc_price, - // sum(extendedprice * (1 - discount) * (1 + tax)) as sum_charge, - // avg(quantity) as avg_qty, - // avg(extendedprice) as avg_price, - // avg(discount) as avg_disc, - // count(*) as count_order - // from - // lineitem - // where - // shipdate <= '1998-09-02' - // group by - // returnflag, - // linestatus - // order by - // returnflag, - // linestatus - - OperatorFactory tableScanOperator = createTableScanOperator( - 0, - new PlanNodeId("test"), - "lineitem", - "returnflag", - "linestatus", - "quantity", - "extendedprice", - "discount", - "tax", - "shipdate"); - - TpchQuery1OperatorFactory tpchQuery1Operator = new TpchQuery1OperatorFactory(1); - HashAggregationOperatorFactory aggregationOperator = new HashAggregationOperatorFactory( - 2, - new PlanNodeId("test"), - getColumnTypes("lineitem", "returnflag", "linestatus"), - Ints.asList(0, 1), - ImmutableList.of(), - Step.SINGLE, - ImmutableList.of( - doubleSum.bind(ImmutableList.of(2)), - doubleSum.bind(ImmutableList.of(3)), - doubleSum.bind(ImmutableList.of(4)), - longAverage.bind(ImmutableList.of(2)), - doubleAverage.bind(ImmutableList.of(5)), - doubleAverage.bind(ImmutableList.of(6)), - countFunction.bind(ImmutableList.of(2))), - Optional.empty(), - Optional.empty(), - 10_000, - Optional.of(DataSize.of(16, MEGABYTE)), - new JoinCompiler(localQueryRunner.getTypeOperators()), - localQueryRunner.getTypeOperators(), - Optional.empty()); - - return ImmutableList.of(tableScanOperator, tpchQuery1Operator, aggregationOperator); - } - - public static class TpchQuery1Operator - implements io.trino.operator.Operator // TODO: use import when Java 7 compiler bug is fixed - { - private static final ImmutableList TYPES = ImmutableList.of( - VARCHAR, - VARCHAR, - DOUBLE, - DOUBLE, - DOUBLE, - DOUBLE, - DOUBLE); - - public static class TpchQuery1OperatorFactory - implements OperatorFactory - { - private final int operatorId; - - public TpchQuery1OperatorFactory(int operatorId) - { - this.operatorId = operatorId; - } - - @Override - public Operator createOperator(DriverContext driverContext) - { - OperatorContext operatorContext = driverContext.addOperatorContext(operatorId, new PlanNodeId("test"), TpchQuery1Operator.class.getSimpleName()); - return new TpchQuery1Operator(operatorContext); - } - - @Override - public void noMoreOperators() - { - } - - @Override - public OperatorFactory duplicate() - { - throw new UnsupportedOperationException(); - } - } - - private final OperatorContext operatorContext; - private final PageBuilder pageBuilder; - private boolean finishing; - - public TpchQuery1Operator(OperatorContext operatorContext) - { - this.operatorContext = requireNonNull(operatorContext, "operatorContext is null"); - this.pageBuilder = new PageBuilder(TYPES); - } - - @Override - public OperatorContext getOperatorContext() - { - return operatorContext; - } - - @Override - public void finish() - { - finishing = true; - } - - @Override - public boolean isFinished() - { - return finishing && pageBuilder.isEmpty(); - } - - @Override - public boolean needsInput() - { - return !pageBuilder.isFull(); - } - - @Override - public void addInput(Page page) - { - requireNonNull(page, "page is null"); - checkState(!pageBuilder.isFull(), "Output buffer is full"); - checkState(!finishing, "Operator is finished"); - - filterAndProjectRowOriented(pageBuilder, - page.getBlock(0), - page.getBlock(1), - page.getBlock(2), - page.getBlock(3), - page.getBlock(4), - page.getBlock(5), - page.getBlock(6)); - } - - @Override - public Page getOutput() - { - // only return a page if the page buffer isFull or we are finishing and the page buffer has data - if (pageBuilder.isFull() || (finishing && !pageBuilder.isEmpty())) { - Page page = pageBuilder.build(); - pageBuilder.reset(); - return page; - } - return null; - } - - private static final int MAX_SHIP_DATE = DateTimeUtils.parseDate("1998-09-02"); - - private static void filterAndProjectRowOriented( - PageBuilder pageBuilder, - Block returnFlagBlock, - Block lineStatusBlock, - Block quantityBlock, - Block extendedPriceBlock, - Block discountBlock, - Block taxBlock, - Block shipDateBlock) - { - int rows = returnFlagBlock.getPositionCount(); - for (int position = 0; position < rows; position++) { - if (shipDateBlock.isNull(position)) { - continue; - } - - int shipDate = DATE.getInt(shipDateBlock, position); - - // where - // shipdate <= '1998-09-02' - if (shipDate <= MAX_SHIP_DATE) { - // returnflag, - // linestatus - // quantity - // extendedprice - // extendedprice * (1 - discount) - // extendedprice * (1 - discount) * (1 + tax) - // discount - - pageBuilder.declarePosition(); - if (returnFlagBlock.isNull(position)) { - pageBuilder.getBlockBuilder(0).appendNull(); - } - else { - VARCHAR.appendTo(returnFlagBlock, position, pageBuilder.getBlockBuilder(0)); - } - if (lineStatusBlock.isNull(position)) { - pageBuilder.getBlockBuilder(1).appendNull(); - } - else { - VARCHAR.appendTo(lineStatusBlock, position, pageBuilder.getBlockBuilder(1)); - } - - double quantity = DOUBLE.getDouble(quantityBlock, position); - double extendedPrice = DOUBLE.getDouble(extendedPriceBlock, position); - double discount = DOUBLE.getDouble(discountBlock, position); - double tax = DOUBLE.getDouble(taxBlock, position); - - boolean quantityIsNull = quantityBlock.isNull(position); - boolean extendedPriceIsNull = extendedPriceBlock.isNull(position); - boolean discountIsNull = discountBlock.isNull(position); - boolean taxIsNull = taxBlock.isNull(position); - - if (quantityIsNull) { - pageBuilder.getBlockBuilder(2).appendNull(); - } - else { - DOUBLE.writeDouble(pageBuilder.getBlockBuilder(2), quantity); - } - - if (extendedPriceIsNull) { - pageBuilder.getBlockBuilder(3).appendNull(); - } - else { - DOUBLE.writeDouble(pageBuilder.getBlockBuilder(3), extendedPrice); - } - - if (extendedPriceIsNull || discountIsNull) { - pageBuilder.getBlockBuilder(4).appendNull(); - } - else { - DOUBLE.writeDouble(pageBuilder.getBlockBuilder(4), extendedPrice * (1 - discount)); - } - - if (extendedPriceIsNull || discountIsNull || taxIsNull) { - pageBuilder.getBlockBuilder(5).appendNull(); - } - else { - DOUBLE.writeDouble(pageBuilder.getBlockBuilder(5), extendedPrice * (1 - discount) * (1 + tax)); - } - - if (discountIsNull) { - pageBuilder.getBlockBuilder(6).appendNull(); - } - else { - DOUBLE.writeDouble(pageBuilder.getBlockBuilder(6), discount); - } - } - } - } - } - - public static void main(String[] args) - { - new HandTpchQuery1(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/HandTpchQuery6.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/HandTpchQuery6.java deleted file mode 100644 index 62058f5124c9..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/HandTpchQuery6.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import com.google.common.collect.ImmutableList; -import io.airlift.units.DataSize; -import io.trino.operator.AggregationOperator.AggregationOperatorFactory; -import io.trino.operator.FilterAndProjectOperator; -import io.trino.operator.OperatorFactory; -import io.trino.operator.project.InputChannels; -import io.trino.operator.project.PageFilter; -import io.trino.operator.project.PageProcessor; -import io.trino.operator.project.PageProjection; -import io.trino.operator.project.SelectedPositions; -import io.trino.spi.Page; -import io.trino.spi.block.Block; -import io.trino.spi.connector.ConnectorSession; -import io.trino.sql.gen.PageFunctionCompiler; -import io.trino.sql.planner.plan.PlanNodeId; -import io.trino.testing.LocalQueryRunner; -import io.trino.util.DateTimeUtils; - -import java.util.List; -import java.util.Optional; -import java.util.function.Supplier; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; -import static io.trino.spi.type.BigintType.BIGINT; -import static io.trino.spi.type.DateType.DATE; -import static io.trino.spi.type.DoubleType.DOUBLE; -import static io.trino.sql.relational.Expressions.field; - -public class HandTpchQuery6 - extends AbstractSimpleOperatorBenchmark -{ - private final BenchmarkAggregationFunction doubleSum; - - public HandTpchQuery6(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "hand_tpch_query_6", 10, 100); - doubleSum = createAggregationFunction("sum", DOUBLE); - } - - @Override - protected List createOperatorFactories() - { - // select sum(extendedprice * discount) as revenue - // from lineitem - // where shipdate >= '1994-01-01' - // and shipdate < '1995-01-01' - // and discount >= 0.05 - // and discount <= 0.07 - // and quantity < 24; - OperatorFactory tableScanOperator = createTableScanOperator(0, new PlanNodeId("test"), "lineitem", "extendedprice", "discount", "shipdate", "quantity"); - - Supplier projection = new PageFunctionCompiler(localQueryRunner.getFunctionManager(), 0).compileProjection(field(0, BIGINT), Optional.empty()); - - OperatorFactory tpchQuery6Operator = FilterAndProjectOperator.createOperatorFactory( - 1, - new PlanNodeId("test"), - () -> new PageProcessor(Optional.of(new TpchQuery6Filter()), ImmutableList.of(projection.get())), - ImmutableList.of(DOUBLE), - DataSize.ofBytes(0), - 0); - - AggregationOperatorFactory aggregationOperator = new AggregationOperatorFactory( - 2, - new PlanNodeId("test"), - ImmutableList.of( - doubleSum.bind(ImmutableList.of(0)))); - - return ImmutableList.of(tableScanOperator, tpchQuery6Operator, aggregationOperator); - } - - public static class TpchQuery6Filter - implements PageFilter - { - private static final int MIN_SHIP_DATE = DateTimeUtils.parseDate("1994-01-01"); - private static final int MAX_SHIP_DATE = DateTimeUtils.parseDate("1995-01-01"); - private static final InputChannels INPUT_CHANNELS = new InputChannels(1, 2, 3); - - private boolean[] selectedPositions = new boolean[0]; - - @Override - public boolean isDeterministic() - { - return true; - } - - @Override - public InputChannels getInputChannels() - { - return INPUT_CHANNELS; - } - - @Override - public SelectedPositions filter(ConnectorSession session, Page page) - { - if (selectedPositions.length < page.getPositionCount()) { - selectedPositions = new boolean[page.getPositionCount()]; - } - - for (int position = 0; position < page.getPositionCount(); position++) { - selectedPositions[position] = filter(page, position); - } - - return PageFilter.positionsArrayToSelectedPositions(selectedPositions, page.getPositionCount()); - } - - private static boolean filter(Page page, int position) - { - Block discountBlock = page.getBlock(0); - Block shipDateBlock = page.getBlock(1); - Block quantityBlock = page.getBlock(2); - return !shipDateBlock.isNull(position) && DATE.getInt(shipDateBlock, position) >= MIN_SHIP_DATE && - !shipDateBlock.isNull(position) && DATE.getInt(shipDateBlock, position) < MAX_SHIP_DATE && - !discountBlock.isNull(position) && DOUBLE.getDouble(discountBlock, position) >= 0.05 && - !discountBlock.isNull(position) && DOUBLE.getDouble(discountBlock, position) <= 0.07 && - !quantityBlock.isNull(position) && BIGINT.getLong(quantityBlock, position) < 24; - } - } - - public static void main(String[] args) - { - new HandTpchQuery6(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/HashAggregationBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/HashAggregationBenchmark.java deleted file mode 100644 index 021a30a1861a..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/HashAggregationBenchmark.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import com.google.common.collect.ImmutableList; -import com.google.common.primitives.Ints; -import io.airlift.units.DataSize; -import io.trino.operator.HashAggregationOperator.HashAggregationOperatorFactory; -import io.trino.operator.OperatorFactory; -import io.trino.spi.type.Type; -import io.trino.sql.gen.JoinCompiler; -import io.trino.sql.planner.plan.AggregationNode.Step; -import io.trino.sql.planner.plan.PlanNodeId; -import io.trino.testing.LocalQueryRunner; - -import java.util.List; -import java.util.Optional; - -import static io.airlift.units.DataSize.Unit.MEGABYTE; -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; -import static io.trino.spi.type.DoubleType.DOUBLE; - -public class HashAggregationBenchmark - extends AbstractSimpleOperatorBenchmark -{ - private final BenchmarkAggregationFunction doubleSum; - - public HashAggregationBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "hash_agg", 5, 25); - - doubleSum = createAggregationFunction("sum", DOUBLE); - } - - @Override - protected List createOperatorFactories() - { - List tableTypes = getColumnTypes("orders", "orderstatus", "totalprice"); - OperatorFactory tableScanOperator = createTableScanOperator(0, new PlanNodeId("test"), "orders", "orderstatus", "totalprice"); - HashAggregationOperatorFactory aggregationOperator = new HashAggregationOperatorFactory( - 1, - new PlanNodeId("test"), - ImmutableList.of(tableTypes.get(0)), - Ints.asList(0), - ImmutableList.of(), - Step.SINGLE, - ImmutableList.of(doubleSum.bind(ImmutableList.of(1))), - Optional.empty(), - Optional.empty(), - 100_000, - Optional.of(DataSize.of(16, MEGABYTE)), - new JoinCompiler(localQueryRunner.getTypeOperators()), - localQueryRunner.getTypeOperators(), - Optional.empty()); - return ImmutableList.of(tableScanOperator, aggregationOperator); - } - - public static void main(String[] args) - { - new HashAggregationBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/HashBuildAndJoinBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/HashBuildAndJoinBenchmark.java deleted file mode 100644 index a6dfef0a9b1d..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/HashBuildAndJoinBenchmark.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import com.google.common.collect.ImmutableList; -import com.google.common.primitives.Ints; -import io.trino.Session; -import io.trino.SystemSessionProperties; -import io.trino.operator.Driver; -import io.trino.operator.DriverFactory; -import io.trino.operator.OperatorFactory; -import io.trino.operator.PagesIndex; -import io.trino.operator.TaskContext; -import io.trino.operator.join.HashBuilderOperator.HashBuilderOperatorFactory; -import io.trino.operator.join.JoinBridgeManager; -import io.trino.operator.join.PartitionedLookupSourceFactory; -import io.trino.spi.type.Type; -import io.trino.spi.type.TypeOperators; -import io.trino.spiller.SingleStreamSpillerFactory; -import io.trino.sql.planner.plan.PlanNodeId; -import io.trino.testing.LocalQueryRunner; -import io.trino.testing.NullOutputOperator.NullOutputOperatorFactory; - -import java.util.List; -import java.util.Optional; -import java.util.OptionalInt; - -import static com.google.common.collect.ImmutableList.toImmutableList; -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunnerHashEnabled; -import static io.trino.operator.HashArraySizeSupplier.incrementalLoadFactorHashArraySizeSupplier; -import static io.trino.operator.JoinOperatorType.innerJoin; -import static io.trino.operator.OperatorFactories.spillingJoin; -import static io.trino.spi.type.BigintType.BIGINT; -import static io.trino.spiller.PartitioningSpillerFactory.unsupportedPartitioningSpillerFactory; -import static io.trino.testing.TestingSession.testSessionBuilder; - -public class HashBuildAndJoinBenchmark - extends AbstractOperatorBenchmark -{ - private final boolean hashEnabled; - private final List ordersTableTypes = getColumnTypes("orders", "orderkey", "totalprice"); - private final OperatorFactory ordersTableScan = createTableScanOperator(0, new PlanNodeId("test"), "orders", "orderkey", "totalprice"); - private final List lineItemTableTypes = getColumnTypes("lineitem", "orderkey", "quantity"); - private final OperatorFactory lineItemTableScan = createTableScanOperator(0, new PlanNodeId("test"), "lineitem", "orderkey", "quantity"); - - public HashBuildAndJoinBenchmark(Session session, LocalQueryRunner localQueryRunner) - { - super(session, localQueryRunner, "hash_build_and_join_hash_enabled_" + isHashEnabled(session), 4, 5); - this.hashEnabled = isHashEnabled(session); - } - - private static boolean isHashEnabled(Session session) - { - return SystemSessionProperties.isOptimizeHashGenerationEnabled(session); - } - - /* - select orderkey, quantity, totalprice - from lineitem join orders using (orderkey) - */ - @Override - protected List createDrivers(TaskContext taskContext) - { - ImmutableList.Builder driversBuilder = ImmutableList.builder(); - driversBuilder.add(ordersTableScan); - List sourceTypes = ordersTableTypes; - OptionalInt hashChannel = OptionalInt.empty(); - if (hashEnabled) { - driversBuilder.add(createHashProjectOperator(1, new PlanNodeId("test"), sourceTypes)); - sourceTypes = ImmutableList.builder() - .addAll(sourceTypes) - .add(BIGINT) - .build(); - hashChannel = OptionalInt.of(sourceTypes.size() - 1); - } - - // hash build - TypeOperators typeOperators = new TypeOperators(); - JoinBridgeManager lookupSourceFactoryManager = JoinBridgeManager.lookupAllAtOnce(new PartitionedLookupSourceFactory( - sourceTypes, - ImmutableList.of(0, 1).stream() - .map(sourceTypes::get) - .collect(toImmutableList()), - Ints.asList(0).stream() - .map(sourceTypes::get) - .collect(toImmutableList()), - 1, - false, - typeOperators)); - HashBuilderOperatorFactory hashBuilder = new HashBuilderOperatorFactory( - 2, - new PlanNodeId("test"), - lookupSourceFactoryManager, - ImmutableList.of(0, 1), - Ints.asList(0), - hashChannel, - Optional.empty(), - Optional.empty(), - ImmutableList.of(), - 1_500_000, - new PagesIndex.TestingFactory(false), - false, - SingleStreamSpillerFactory.unsupportedSingleStreamSpillerFactory(), - incrementalLoadFactorHashArraySizeSupplier(session)); - driversBuilder.add(hashBuilder); - DriverFactory hashBuildDriverFactory = new DriverFactory(0, true, false, driversBuilder.build(), OptionalInt.empty()); - - // join - ImmutableList.Builder joinDriversBuilder = ImmutableList.builder(); - joinDriversBuilder.add(lineItemTableScan); - sourceTypes = lineItemTableTypes; - hashChannel = OptionalInt.empty(); - if (hashEnabled) { - joinDriversBuilder.add(createHashProjectOperator(1, new PlanNodeId("test"), sourceTypes)); - sourceTypes = ImmutableList.builder() - .addAll(sourceTypes) - .add(BIGINT) - .build(); - hashChannel = OptionalInt.of(sourceTypes.size() - 1); - } - - OperatorFactory joinOperator = spillingJoin( - innerJoin(false, false), - 2, - new PlanNodeId("test"), - lookupSourceFactoryManager, - false, - sourceTypes, - Ints.asList(0), - hashChannel, - Optional.empty(), - OptionalInt.empty(), - unsupportedPartitioningSpillerFactory(), - typeOperators); - joinDriversBuilder.add(joinOperator); - joinDriversBuilder.add(new NullOutputOperatorFactory(3, new PlanNodeId("test"))); - DriverFactory joinDriverFactory = new DriverFactory(1, true, true, joinDriversBuilder.build(), OptionalInt.empty()); - - Driver hashBuildDriver = hashBuildDriverFactory.createDriver(taskContext.addPipelineContext(0, true, false, false).addDriverContext()); - hashBuildDriverFactory.noMoreDrivers(); - Driver joinDriver = joinDriverFactory.createDriver(taskContext.addPipelineContext(1, true, true, false).addDriverContext()); - joinDriverFactory.noMoreDrivers(); - - return ImmutableList.of(hashBuildDriver, joinDriver); - } - - public static void main(String[] args) - { - new HashBuildAndJoinBenchmark(testSessionBuilder().build(), createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - new HashBuildAndJoinBenchmark(testSessionBuilder().build(), createLocalQueryRunnerHashEnabled()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/HashBuildBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/HashBuildBenchmark.java deleted file mode 100644 index 377cd114587c..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/HashBuildBenchmark.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import com.google.common.collect.ImmutableList; -import com.google.common.primitives.Ints; -import io.trino.operator.Driver; -import io.trino.operator.DriverFactory; -import io.trino.operator.OperatorFactory; -import io.trino.operator.PagesIndex; -import io.trino.operator.TaskContext; -import io.trino.operator.ValuesOperator.ValuesOperatorFactory; -import io.trino.operator.join.HashBuilderOperator.HashBuilderOperatorFactory; -import io.trino.operator.join.JoinBridgeManager; -import io.trino.operator.join.PartitionedLookupSourceFactory; -import io.trino.spi.type.Type; -import io.trino.spi.type.TypeOperators; -import io.trino.spiller.SingleStreamSpillerFactory; -import io.trino.sql.planner.plan.PlanNodeId; -import io.trino.testing.LocalQueryRunner; -import io.trino.testing.NullOutputOperator.NullOutputOperatorFactory; - -import java.util.List; -import java.util.Optional; -import java.util.OptionalInt; - -import static com.google.common.collect.ImmutableList.toImmutableList; -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; -import static io.trino.operator.HashArraySizeSupplier.incrementalLoadFactorHashArraySizeSupplier; -import static io.trino.operator.JoinOperatorType.innerJoin; -import static io.trino.operator.OperatorFactories.spillingJoin; -import static io.trino.spi.type.BigintType.BIGINT; -import static io.trino.spiller.PartitioningSpillerFactory.unsupportedPartitioningSpillerFactory; - -public class HashBuildBenchmark - extends AbstractOperatorBenchmark -{ - public HashBuildBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "hash_build", 4, 5); - } - - @Override - protected List createDrivers(TaskContext taskContext) - { - // hash build - List ordersTypes = getColumnTypes("orders", "orderkey", "totalprice"); - OperatorFactory ordersTableScan = createTableScanOperator(0, new PlanNodeId("test"), "orders", "orderkey", "totalprice"); - TypeOperators typeOperators = new TypeOperators(); - JoinBridgeManager lookupSourceFactoryManager = JoinBridgeManager.lookupAllAtOnce(new PartitionedLookupSourceFactory( - ordersTypes, - ImmutableList.of(0, 1).stream() - .map(ordersTypes::get) - .collect(toImmutableList()), - Ints.asList(0).stream() - .map(ordersTypes::get) - .collect(toImmutableList()), - 1, - false, - typeOperators)); - HashBuilderOperatorFactory hashBuilder = new HashBuilderOperatorFactory( - 1, - new PlanNodeId("test"), - lookupSourceFactoryManager, - ImmutableList.of(0, 1), - Ints.asList(0), - OptionalInt.empty(), - Optional.empty(), - Optional.empty(), - ImmutableList.of(), - 1_500_000, - new PagesIndex.TestingFactory(false), - false, - SingleStreamSpillerFactory.unsupportedSingleStreamSpillerFactory(), - incrementalLoadFactorHashArraySizeSupplier(session)); - DriverFactory hashBuildDriverFactory = new DriverFactory(0, true, true, ImmutableList.of(ordersTableScan, hashBuilder), OptionalInt.empty()); - - // empty join so build finishes - ImmutableList.Builder joinDriversBuilder = ImmutableList.builder(); - joinDriversBuilder.add(new ValuesOperatorFactory(0, new PlanNodeId("values"), ImmutableList.of())); - OperatorFactory joinOperator = spillingJoin( - innerJoin(false, false), - 2, - new PlanNodeId("test"), - lookupSourceFactoryManager, - false, - ImmutableList.of(BIGINT), - Ints.asList(0), - OptionalInt.empty(), - Optional.empty(), - OptionalInt.empty(), - unsupportedPartitioningSpillerFactory(), - typeOperators); - joinDriversBuilder.add(joinOperator); - joinDriversBuilder.add(new NullOutputOperatorFactory(3, new PlanNodeId("test"))); - DriverFactory joinDriverFactory = new DriverFactory(1, true, true, joinDriversBuilder.build(), OptionalInt.empty()); - - Driver hashBuildDriver = hashBuildDriverFactory.createDriver(taskContext.addPipelineContext(0, true, true, false).addDriverContext()); - hashBuildDriverFactory.noMoreDrivers(); - Driver joinDriver = joinDriverFactory.createDriver(taskContext.addPipelineContext(1, true, true, false).addDriverContext()); - joinDriverFactory.noMoreDrivers(); - - return ImmutableList.of(hashBuildDriver, joinDriver); - } - - public static void main(String[] args) - { - new HashBuildBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/HashJoinBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/HashJoinBenchmark.java deleted file mode 100644 index f2d5eed3f20d..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/HashJoinBenchmark.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import com.google.common.collect.ImmutableList; -import com.google.common.primitives.Ints; -import io.trino.operator.Driver; -import io.trino.operator.DriverContext; -import io.trino.operator.DriverFactory; -import io.trino.operator.OperatorFactory; -import io.trino.operator.PagesIndex; -import io.trino.operator.TaskContext; -import io.trino.operator.join.HashBuilderOperator.HashBuilderOperatorFactory; -import io.trino.operator.join.JoinBridgeManager; -import io.trino.operator.join.LookupSourceProvider; -import io.trino.operator.join.PartitionedLookupSourceFactory; -import io.trino.spi.type.Type; -import io.trino.spi.type.TypeOperators; -import io.trino.spiller.SingleStreamSpillerFactory; -import io.trino.sql.planner.plan.PlanNodeId; -import io.trino.testing.LocalQueryRunner; -import io.trino.testing.NullOutputOperator.NullOutputOperatorFactory; - -import java.util.List; -import java.util.Optional; -import java.util.OptionalInt; -import java.util.concurrent.Future; - -import static com.google.common.collect.ImmutableList.toImmutableList; -import static io.airlift.concurrent.MoreFutures.getFutureValue; -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; -import static io.trino.execution.executor.timesharing.PrioritizedSplitRunner.SPLIT_RUN_QUANTA; -import static io.trino.operator.HashArraySizeSupplier.incrementalLoadFactorHashArraySizeSupplier; -import static io.trino.operator.JoinOperatorType.innerJoin; -import static io.trino.operator.OperatorFactories.spillingJoin; -import static io.trino.spiller.PartitioningSpillerFactory.unsupportedPartitioningSpillerFactory; - -public class HashJoinBenchmark - extends AbstractOperatorBenchmark -{ - private DriverFactory probeDriverFactory; - - public HashJoinBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "hash_join", 4, 50); - } - - /* - select orderkey, quantity, totalprice - from lineitem join orders using (orderkey) - */ - - @Override - protected List createDrivers(TaskContext taskContext) - { - if (probeDriverFactory == null) { - List ordersTypes = getColumnTypes("orders", "orderkey", "totalprice"); - OperatorFactory ordersTableScan = createTableScanOperator(0, new PlanNodeId("test"), "orders", "orderkey", "totalprice"); - TypeOperators typeOperators = new TypeOperators(); - JoinBridgeManager lookupSourceFactoryManager = JoinBridgeManager.lookupAllAtOnce(new PartitionedLookupSourceFactory( - ordersTypes, - ImmutableList.of(0, 1).stream() - .map(ordersTypes::get) - .collect(toImmutableList()), - Ints.asList(0).stream() - .map(ordersTypes::get) - .collect(toImmutableList()), - 1, - false, - typeOperators)); - HashBuilderOperatorFactory hashBuilder = new HashBuilderOperatorFactory( - 1, - new PlanNodeId("test"), - lookupSourceFactoryManager, - ImmutableList.of(0, 1), - Ints.asList(0), - OptionalInt.empty(), - Optional.empty(), - Optional.empty(), - ImmutableList.of(), - 1_500_000, - new PagesIndex.TestingFactory(false), - false, - SingleStreamSpillerFactory.unsupportedSingleStreamSpillerFactory(), - incrementalLoadFactorHashArraySizeSupplier(session)); - - DriverContext driverContext = taskContext.addPipelineContext(0, false, false, false).addDriverContext(); - DriverFactory buildDriverFactory = new DriverFactory(0, false, false, ImmutableList.of(ordersTableScan, hashBuilder), OptionalInt.empty()); - - List lineItemTypes = getColumnTypes("lineitem", "orderkey", "quantity"); - OperatorFactory lineItemTableScan = createTableScanOperator(0, new PlanNodeId("test"), "lineitem", "orderkey", "quantity"); - OperatorFactory joinOperator = spillingJoin( - innerJoin(false, false), - 1, - new PlanNodeId("test"), - lookupSourceFactoryManager, - false, - lineItemTypes, - Ints.asList(0), - OptionalInt.empty(), - Optional.empty(), - OptionalInt.empty(), - unsupportedPartitioningSpillerFactory(), - typeOperators); - NullOutputOperatorFactory output = new NullOutputOperatorFactory(2, new PlanNodeId("test")); - this.probeDriverFactory = new DriverFactory(1, true, true, ImmutableList.of(lineItemTableScan, joinOperator, output), OptionalInt.empty()); - - Driver driver = buildDriverFactory.createDriver(driverContext); - Future lookupSourceProvider = lookupSourceFactoryManager.getJoinBridge().createLookupSourceProvider(); - while (!lookupSourceProvider.isDone()) { - driver.processForDuration(SPLIT_RUN_QUANTA); - } - getFutureValue(lookupSourceProvider).close(); - } - - DriverContext driverContext = taskContext.addPipelineContext(1, true, true, false).addDriverContext(); - Driver driver = probeDriverFactory.createDriver(driverContext); - return ImmutableList.of(driver); - } - - public static void main(String[] args) - { - new HashJoinBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/JsonAvgBenchmarkResultWriter.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/JsonAvgBenchmarkResultWriter.java deleted file mode 100644 index 1c401b2010f9..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/JsonAvgBenchmarkResultWriter.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import com.fasterxml.jackson.annotation.JsonProperty; -import io.airlift.json.JsonCodec; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.UncheckedIOException; -import java.util.Map; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.Objects.requireNonNull; - -public class JsonAvgBenchmarkResultWriter - implements BenchmarkResultHook -{ - private static final JsonCodec JSON_CODEC = JsonCodec.jsonCodec(BuildResult.class); - private final OutputStream outputStream; - - private int sampleCount; - public long totalElapsedMillis; - public long totalInputRows; - public long totalInputRowsPerSecond; - public long totalOutputRows; - public long totalOutputRowsPerSecond; - public long totalInputMegabytes; - public long totalInputMegabytesPerSecond; - - public JsonAvgBenchmarkResultWriter(OutputStream outputStream) - { - requireNonNull(outputStream, "outputStream is null"); - this.outputStream = outputStream; - } - - @Override - public BenchmarkResultHook addResults(Map results) - { - requireNonNull(results, "results is null"); - sampleCount++; - totalElapsedMillis += getValue(results, "elapsed_millis"); - totalInputRows += getValue(results, "input_rows;"); - totalInputRowsPerSecond += getValue(results, "input_rows_per_second"); - totalOutputRows += getValue(results, "output_rows"); - totalOutputRowsPerSecond += getValue(results, "output_rows_per_second"); - totalInputMegabytes += getValue(results, "input_megabytes"); - totalInputMegabytesPerSecond += getValue(results, "input_megabytes_per_second"); - return this; - } - - private long getValue(Map results, String name) - { - Long value = results.get(name); - if (value == null) { - return 0; - } - return value; - } - - @Override - public void finished() - { - BuildResult average = new BuildResult(); - average.elapsedMillis += totalElapsedMillis / sampleCount; - average.inputRows += totalInputRows / sampleCount; - average.inputRowsPerSecond += totalInputRowsPerSecond / sampleCount; - average.outputRows += totalOutputRows / sampleCount; - average.outputRowsPerSecond += totalOutputRowsPerSecond / sampleCount; - average.inputMegabytes += totalInputRows / sampleCount; - average.inputMegabytesPerSecond += totalInputMegabytesPerSecond / sampleCount; - - String json = JSON_CODEC.toJson(average); - try { - outputStream.write(json.getBytes(UTF_8)); - } - catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public static class BuildResult - { - @JsonProperty - public long elapsedMillis; - @JsonProperty - public long inputRows; - @JsonProperty - public long inputRowsPerSecond; - @JsonProperty - public long outputRows; - @JsonProperty - public long outputRowsPerSecond; - @JsonProperty - public long inputMegabytes; - @JsonProperty - public long inputMegabytesPerSecond; - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/JsonBenchmarkResultWriter.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/JsonBenchmarkResultWriter.java deleted file mode 100644 index c54cc896b112..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/JsonBenchmarkResultWriter.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import com.fasterxml.jackson.core.JsonEncoding; -import com.fasterxml.jackson.core.JsonGenerator; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.UncheckedIOException; -import java.util.Map; - -import static io.trino.plugin.base.util.JsonUtils.jsonFactory; -import static java.util.Objects.requireNonNull; - -public class JsonBenchmarkResultWriter - implements BenchmarkResultHook -{ - private final JsonGenerator jsonGenerator; - - public JsonBenchmarkResultWriter(OutputStream outputStream) - { - requireNonNull(outputStream, "outputStream is null"); - try { - jsonGenerator = jsonFactory().createGenerator(outputStream, JsonEncoding.UTF8); - jsonGenerator.writeStartObject(); - jsonGenerator.writeArrayFieldStart("samples"); - } - catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - @Override - public BenchmarkResultHook addResults(Map results) - { - requireNonNull(results, "results is null"); - try { - jsonGenerator.writeStartObject(); - for (Map.Entry entry : results.entrySet()) { - jsonGenerator.writeNumberField(entry.getKey(), entry.getValue()); - } - jsonGenerator.writeEndObject(); - } - catch (IOException e) { - throw new UncheckedIOException(e); - } - return this; - } - - @Override - public void finished() - { - try { - jsonGenerator.writeEndArray(); - jsonGenerator.writeEndObject(); - jsonGenerator.close(); - } - catch (IOException e) { - throw new UncheckedIOException(e); - } - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/JsonFunctionsBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/JsonFunctionsBenchmark.java deleted file mode 100644 index 5e7c05aaaaf5..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/JsonFunctionsBenchmark.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public abstract class JsonFunctionsBenchmark -{ - public static void main(String... args) - { - LocalQueryRunner localQueryRunner = createLocalQueryRunner(); - new JsonExtractBenchmark(localQueryRunner).runBenchmark(new JsonAvgBenchmarkResultWriter(System.out)); - new JsonQueryBenchmark(localQueryRunner).runBenchmark(new JsonAvgBenchmarkResultWriter(System.out)); - new JsonExtractScalarBenchmark(localQueryRunner).runBenchmark(new JsonAvgBenchmarkResultWriter(System.out)); - new JsonValueBenchmark(localQueryRunner).runBenchmark(new JsonAvgBenchmarkResultWriter(System.out)); - } - - public static class JsonExtractBenchmark - extends AbstractSqlBenchmark - { - public JsonExtractBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "json_extract", 5, 50, "SELECT json_extract(format('{name : %s, random_number : %s}', partkey, random()), '$.name') FROM lineitem"); - } - } - - public static class JsonQueryBenchmark - extends AbstractSqlBenchmark - { - public JsonQueryBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "json_query", 5, 50, "SELECT json_query(format('{name : %s, random_number : %s}', partkey, random()), 'strict $.name') FROM lineitem"); - } - } - - public static class JsonExtractScalarBenchmark - extends AbstractSqlBenchmark - { - public JsonExtractScalarBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "json_extract_scalar", 5, 50, "SELECT json_extract_scalar(format('{comment : %s, random_number : %s}', comment, random()), '$.comment') FROM lineitem"); - } - } - - public static class JsonValueBenchmark - extends AbstractSqlBenchmark - { - public JsonValueBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "json_value", 5, 50, "SELECT json_value(format('{comment : %s, random_number : %s}', comment, random()), 'strict $.comment') FROM lineitem"); - } - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/LongMaxAggregationSqlBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/LongMaxAggregationSqlBenchmark.java deleted file mode 100644 index 45967f373599..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/LongMaxAggregationSqlBenchmark.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class LongMaxAggregationSqlBenchmark - extends AbstractSqlBenchmark -{ - public LongMaxAggregationSqlBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "sql_long_max", 40, 200, "select max(partkey) from lineitem"); - } - - public static void main(String[] args) - { - new LongMaxAggregationSqlBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/OdsBenchmarkResultWriter.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/OdsBenchmarkResultWriter.java deleted file mode 100644 index 6730e02bd8ea..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/OdsBenchmarkResultWriter.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import com.fasterxml.jackson.core.JsonEncoding; -import com.fasterxml.jackson.core.JsonGenerator; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.UncheckedIOException; -import java.util.Map; - -import static io.trino.plugin.base.util.JsonUtils.jsonFactory; -import static java.util.Objects.requireNonNull; - -public class OdsBenchmarkResultWriter - implements BenchmarkResultHook -{ - private final String entity; - private final JsonGenerator jsonGenerator; - - public OdsBenchmarkResultWriter(String entity, OutputStream outputStream) - { - requireNonNull(entity, "entity is null"); - requireNonNull(outputStream, "outputStream is null"); - this.entity = entity; - try { - jsonGenerator = jsonFactory().createGenerator(outputStream, JsonEncoding.UTF8); - jsonGenerator.writeStartArray(); - } - catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - @Override - public BenchmarkResultHook addResults(Map results) - { - requireNonNull(results, "results is null"); - try { - for (Map.Entry entry : results.entrySet()) { - jsonGenerator.writeStartObject(); - jsonGenerator.writeStringField("entity", entity); - jsonGenerator.writeStringField("key", entry.getKey()); - jsonGenerator.writeNumberField("value", entry.getValue()); - jsonGenerator.writeEndObject(); - } - } - catch (IOException e) { - throw new UncheckedIOException(e); - } - return this; - } - - @Override - public void finished() - { - try { - jsonGenerator.writeEndArray(); - jsonGenerator.close(); - } - catch (IOException e) { - throw new UncheckedIOException(e); - } - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/OrderByBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/OrderByBenchmark.java deleted file mode 100644 index a69d2a01d790..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/OrderByBenchmark.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import com.google.common.collect.ImmutableList; -import io.trino.operator.LimitOperator.LimitOperatorFactory; -import io.trino.operator.OperatorFactory; -import io.trino.operator.OrderByOperator.OrderByOperatorFactory; -import io.trino.operator.PagesIndex; -import io.trino.spi.type.Type; -import io.trino.sql.gen.OrderingCompiler; -import io.trino.sql.planner.plan.PlanNodeId; -import io.trino.testing.LocalQueryRunner; - -import java.util.List; -import java.util.Optional; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; -import static io.trino.spi.connector.SortOrder.ASC_NULLS_LAST; - -public class OrderByBenchmark - extends AbstractSimpleOperatorBenchmark -{ - private static final int ROWS = 1_500_000; - - public OrderByBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "in_memory_orderby_1.5M", 5, 10); - } - - @Override - protected List createOperatorFactories() - { - List tableScanTypes = getColumnTypes("orders", "totalprice", "clerk"); - OperatorFactory tableScanOperator = createTableScanOperator(0, new PlanNodeId("test"), "orders", "totalprice", "clerk"); - - LimitOperatorFactory limitOperator = new LimitOperatorFactory(1, new PlanNodeId("test"), ROWS); - - OrderByOperatorFactory orderByOperator = new OrderByOperatorFactory( - 2, - new PlanNodeId("test"), - tableScanTypes, - ImmutableList.of(1), - ROWS, - ImmutableList.of(0), - ImmutableList.of(ASC_NULLS_LAST), - new PagesIndex.TestingFactory(false), - false, - Optional.empty(), - new OrderingCompiler(localQueryRunner.getTypeOperators())); - - return ImmutableList.of(tableScanOperator, limitOperator, orderByOperator); - } - - public static void main(String[] args) - { - new OrderByBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/PredicateFilterBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/PredicateFilterBenchmark.java deleted file mode 100644 index 06d88bf9da20..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/PredicateFilterBenchmark.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import com.google.common.collect.ImmutableList; -import io.airlift.units.DataSize; -import io.trino.operator.FilterAndProjectOperator; -import io.trino.operator.OperatorFactory; -import io.trino.operator.project.PageProcessor; -import io.trino.sql.gen.ExpressionCompiler; -import io.trino.sql.gen.PageFunctionCompiler; -import io.trino.sql.planner.plan.PlanNodeId; -import io.trino.sql.relational.RowExpression; -import io.trino.testing.LocalQueryRunner; - -import java.util.List; -import java.util.Optional; -import java.util.function.Supplier; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; -import static io.trino.spi.function.OperatorType.LESS_THAN_OR_EQUAL; -import static io.trino.spi.type.DoubleType.DOUBLE; -import static io.trino.sql.relational.Expressions.call; -import static io.trino.sql.relational.Expressions.constant; -import static io.trino.sql.relational.Expressions.field; - -public class PredicateFilterBenchmark - extends AbstractSimpleOperatorBenchmark -{ - public PredicateFilterBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "predicate_filter", 5, 50); - } - - @Override - protected List createOperatorFactories() - { - OperatorFactory tableScanOperator = createTableScanOperator(0, new PlanNodeId("test"), "orders", "totalprice"); - RowExpression filter = call( - localQueryRunner.getMetadata().resolveOperator(LESS_THAN_OR_EQUAL, ImmutableList.of(DOUBLE, DOUBLE)), - constant(50000.0, DOUBLE), - field(0, DOUBLE)); - ExpressionCompiler expressionCompiler = new ExpressionCompiler(localQueryRunner.getFunctionManager(), new PageFunctionCompiler(localQueryRunner.getFunctionManager(), 0)); - Supplier pageProcessor = expressionCompiler.compilePageProcessor(Optional.of(filter), ImmutableList.of(field(0, DOUBLE))); - - OperatorFactory filterAndProjectOperator = FilterAndProjectOperator.createOperatorFactory( - 1, - new PlanNodeId("test"), - pageProcessor, - ImmutableList.of(DOUBLE), - DataSize.ofBytes(0), - 0); - - return ImmutableList.of(tableScanOperator, filterAndProjectOperator); - } - - public static void main(String[] args) - { - new PredicateFilterBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/PredicateFilterSqlBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/PredicateFilterSqlBenchmark.java deleted file mode 100644 index e96e9d3d15f8..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/PredicateFilterSqlBenchmark.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class PredicateFilterSqlBenchmark - extends AbstractSqlBenchmark -{ - public PredicateFilterSqlBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "sql_predicate_filter", 5, 50, "select totalprice from orders where totalprice > 50000"); - } - - public static void main(String[] args) - { - new PredicateFilterSqlBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/RawStreamingBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/RawStreamingBenchmark.java deleted file mode 100644 index b7dca8cb3a18..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/RawStreamingBenchmark.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import com.google.common.collect.ImmutableList; -import io.trino.operator.OperatorFactory; -import io.trino.sql.planner.plan.PlanNodeId; -import io.trino.testing.LocalQueryRunner; - -import java.util.List; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class RawStreamingBenchmark - extends AbstractSimpleOperatorBenchmark -{ - public RawStreamingBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "raw_stream", 10, 100); - } - - @Override - protected List createOperatorFactories() - { - OperatorFactory tableScanOperator = createTableScanOperator(0, new PlanNodeId("test"), "orders", "totalprice"); - - return ImmutableList.of(tableScanOperator); - } - - public static void main(String[] args) - { - new RawStreamingBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/RawStreamingSqlBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/RawStreamingSqlBenchmark.java deleted file mode 100644 index f49e887f638b..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/RawStreamingSqlBenchmark.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class RawStreamingSqlBenchmark - extends AbstractSqlBenchmark -{ - public RawStreamingSqlBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "sql_raw_stream", 10, 100, "select totalprice from orders"); - } - - public static void main(String[] args) - { - new RawStreamingSqlBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SimpleLineBenchmarkResultWriter.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/SimpleLineBenchmarkResultWriter.java deleted file mode 100644 index 9667d288e4d8..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SimpleLineBenchmarkResultWriter.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import com.google.common.base.Joiner; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.UncheckedIOException; -import java.io.Writer; -import java.util.Map; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.Objects.requireNonNull; - -public class SimpleLineBenchmarkResultWriter - implements BenchmarkResultHook -{ - private final Writer writer; - - public SimpleLineBenchmarkResultWriter(OutputStream outputStream) - { - writer = new OutputStreamWriter(requireNonNull(outputStream, "outputStream is null"), UTF_8); - } - - @Override - public BenchmarkResultHook addResults(Map results) - { - requireNonNull(results, "results is null"); - try { - Joiner.on(",").withKeyValueSeparator(":").appendTo(writer, results); - writer.write('\n'); - writer.flush(); - } - catch (IOException e) { - throw new UncheckedIOException(e); - } - return this; - } - - @Override - public void finished() - { - // No-op - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlApproximateCountDistinctDoubleBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlApproximateCountDistinctDoubleBenchmark.java deleted file mode 100644 index db3aa4a16937..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlApproximateCountDistinctDoubleBenchmark.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class SqlApproximateCountDistinctDoubleBenchmark - extends AbstractSqlBenchmark -{ - public SqlApproximateCountDistinctDoubleBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "sql_approx_count_distinct_double", 10, 50, "select approx_distinct(totalprice) from orders"); - } - - public static void main(String[] args) - { - new SqlApproximateCountDistinctDoubleBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlApproximateCountDistinctLongBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlApproximateCountDistinctLongBenchmark.java deleted file mode 100644 index b851a8c1777c..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlApproximateCountDistinctLongBenchmark.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class SqlApproximateCountDistinctLongBenchmark - extends AbstractSqlBenchmark -{ - public SqlApproximateCountDistinctLongBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "sql_approx_count_distinct_long", 10, 50, "select approx_distinct(custkey) from orders"); - } - - public static void main(String[] args) - { - new SqlApproximateCountDistinctLongBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlApproximateCountDistinctVarBinaryBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlApproximateCountDistinctVarBinaryBenchmark.java deleted file mode 100644 index e67f55ef8284..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlApproximateCountDistinctVarBinaryBenchmark.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class SqlApproximateCountDistinctVarBinaryBenchmark - extends AbstractSqlBenchmark -{ - public SqlApproximateCountDistinctVarBinaryBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "sql_approx_count_distinct_varbinary", 10, 50, "select approx_distinct(clerk) from orders"); - } - - public static void main(String[] args) - { - new SqlApproximateCountDistinctVarBinaryBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlApproximatePercentileBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlApproximatePercentileBenchmark.java deleted file mode 100644 index 793901fbcc11..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlApproximatePercentileBenchmark.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class SqlApproximatePercentileBenchmark - extends AbstractSqlBenchmark -{ - public SqlApproximatePercentileBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "sql_approx_percentile_long", 10, 30, "select approx_percentile(custkey, 0.9) from orders"); - } - - public static void main(String[] args) - { - new SqlApproximatePercentileBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlBetweenBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlBetweenBenchmark.java deleted file mode 100644 index 21b47310e777..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlBetweenBenchmark.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class SqlBetweenBenchmark - extends AbstractSqlBenchmark -{ - public SqlBetweenBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "sql_between_long", 10, 30, "SELECT COUNT(*) FROM orders WHERE custkey BETWEEN 10000 AND 20000 OR custkey BETWEEN 30000 AND 35000 OR custkey BETWEEN 50000 AND 51000"); - } - - public static void main(String[] args) - { - new SqlBetweenBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlConsecutiveJoinBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlConsecutiveJoinBenchmark.java deleted file mode 100644 index a95564af015d..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlConsecutiveJoinBenchmark.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import com.google.common.collect.ImmutableMap; -import io.trino.testing.LocalQueryRunner; - -import static io.trino.SystemSessionProperties.JOIN_REORDERING_STRATEGY; -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; -import static io.trino.sql.planner.OptimizerConfig.JoinReorderingStrategy.NONE; - -public class SqlConsecutiveJoinBenchmark - extends AbstractSqlBenchmark -{ - public SqlConsecutiveJoinBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, - "sql_consecutive_join", - 4, - 5, - "SELECT * FROM " + - "( " + - " SELECT orders.orderkey as key, * FROM " + - " customer JOIN orders " + - " ON customer.custkey = orders.custkey " + - ") t1 " + - "JOIN " + - "( " + - " SELECT lineitem.orderkey as key, * FROM " + - " part JOIN lineitem " + - " ON part.partkey = lineitem.partkey " + - ") t2 " + - "ON t1.key = t2.key"); - } - - public static void main(String[] args) - { - try (LocalQueryRunner queryRunner = createLocalQueryRunner(ImmutableMap.of(JOIN_REORDERING_STRATEGY, NONE.toString()))) { - new SqlConsecutiveJoinBenchmark(queryRunner).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlDistinctMultipleFields.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlDistinctMultipleFields.java deleted file mode 100644 index 286efdd0a973..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlDistinctMultipleFields.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class SqlDistinctMultipleFields - extends AbstractSqlBenchmark -{ - public SqlDistinctMultipleFields(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "sql_distinct_multi", 4, 10, "SELECT DISTINCT orderpriority, shippriority FROM orders"); - } - - public static void main(String[] args) - { - new SqlDistinctMultipleFields(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlDistinctSingleField.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlDistinctSingleField.java deleted file mode 100644 index 38563ae896e5..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlDistinctSingleField.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class SqlDistinctSingleField - extends AbstractSqlBenchmark -{ - public SqlDistinctSingleField(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "sql_distinct_single", 10, 20, "SELECT DISTINCT shippriority FROM orders"); - } - - public static void main(String[] args) - { - new SqlDistinctSingleField(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlDoubleSumAggregationBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlDoubleSumAggregationBenchmark.java deleted file mode 100644 index be4ee6ed5621..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlDoubleSumAggregationBenchmark.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class SqlDoubleSumAggregationBenchmark - extends AbstractSqlBenchmark -{ - public SqlDoubleSumAggregationBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "sql_double_sum_agg", 10, 100, "select sum(totalprice) from orders"); - } - - public static void main(String[] args) - { - new SqlDoubleSumAggregationBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlHashJoinBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlHashJoinBenchmark.java deleted file mode 100644 index 4b818414c92b..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlHashJoinBenchmark.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class SqlHashJoinBenchmark - extends AbstractSqlBenchmark -{ - public SqlHashJoinBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, - "sql_hash_join", - 4, - 5, - "select orderkey, lineitem.quantity, orders.totalprice from lineitem join orders using (orderkey)"); - } - - public static void main(String[] args) - { - new SqlHashJoinBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlInBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlInBenchmark.java deleted file mode 100644 index 3873092636b6..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlInBenchmark.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class SqlInBenchmark - extends AbstractSqlBenchmark -{ - public SqlInBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, - "sql_in", - 10, - 50, - "SELECT orderkey FROM lineitem WHERE orderkey IN (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30)"); - } - - public static void main(String[] args) - { - new SqlInBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlJoinWithPredicateBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlJoinWithPredicateBenchmark.java deleted file mode 100644 index bf4e22059808..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlJoinWithPredicateBenchmark.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class SqlJoinWithPredicateBenchmark - extends AbstractSqlBenchmark -{ - public SqlJoinWithPredicateBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, - "sql_join_with_predicate", - 1, - 5, - "select count(*) from lineitem l join orders o on l.orderkey = o.orderkey and l.partkey % 2 = 0 and o.orderkey % 2 = 0\n"); - } - - public static void main(String[] args) - { - new SqlJoinWithPredicateBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlLikeBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlLikeBenchmark.java deleted file mode 100644 index f260f743ed81..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlLikeBenchmark.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class SqlLikeBenchmark - extends AbstractSqlBenchmark -{ - public SqlLikeBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "sql_like", 4, 5, "SELECT orderkey FROM lineitem WHERE comment LIKE '%ly%ly%'"); - } - - public static void main(String[] args) - { - new SqlLikeBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlRegexpLikeBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlRegexpLikeBenchmark.java deleted file mode 100644 index 717407bbce4d..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlRegexpLikeBenchmark.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class SqlRegexpLikeBenchmark - extends AbstractSqlBenchmark -{ - public SqlRegexpLikeBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "sql_regexp_like", 4, 5, "SELECT count(*) FROM orders WHERE regexp_like(comment, '\\b[a-z]{5}ly\\b')"); - } - - public static void main(String[] args) - { - new SqlRegexpLikeBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlSemiJoinInPredicateBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlSemiJoinInPredicateBenchmark.java deleted file mode 100644 index 224585cb2b87..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlSemiJoinInPredicateBenchmark.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class SqlSemiJoinInPredicateBenchmark - extends AbstractSqlBenchmark -{ - public SqlSemiJoinInPredicateBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, - "sql_semijoin_in", - 2, - 4, - "SELECT orderkey FROM lineitem WHERE orderkey IN (SELECT orderkey FROM orders WHERE orderkey % 2 = 0)"); - } - - public static void main(String[] args) - { - new SqlSemiJoinInPredicateBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlTopNBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlTopNBenchmark.java deleted file mode 100644 index d4cb2b5f5b3c..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlTopNBenchmark.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import com.google.common.collect.ImmutableMap; -import com.google.common.math.IntMath; -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; -import static java.lang.String.format; - -public class SqlTopNBenchmark - extends AbstractSqlBenchmark -{ - public SqlTopNBenchmark(LocalQueryRunner localQueryRunner, int topN) - { - super(localQueryRunner, - format("sql_top_%s", topN), - 4, - 5, - format("select * from tpch.sf1.orders order by orderdate desc, totalprice, orderstatus, orderpriority desc limit %s", topN)); - } - - public static void main(String[] args) - { - LocalQueryRunner localQueryRunner = createLocalQueryRunner(ImmutableMap.of("resource_overcommit", "true")); - for (int i = 0; i < 11; i++) { - new SqlTopNBenchmark(localQueryRunner, IntMath.pow(4, i)).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlTopNRankingBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlTopNRankingBenchmark.java deleted file mode 100644 index 96fab8fa5099..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlTopNRankingBenchmark.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; -import static java.lang.String.format; - -public class SqlTopNRankingBenchmark - extends AbstractSqlBenchmark -{ - public SqlTopNRankingBenchmark(LocalQueryRunner localQueryRunner, String function, String partitions, int topN) - { - super(localQueryRunner, - format("sql_%s_partition_by_(%s)_top_%s", function, partitions, topN), - 4, - 5, - format("WITH t AS (" + - " SELECT *, %s() OVER (PARTITION BY %s ORDER BY shipdate DESC) AS rnk" + - " FROM lineitem" + - ")" + - "SELECT * FROM t WHERE rnk <= %s", function, partitions, topN)); - } - - public static void main(String[] args) - { - LocalQueryRunner localQueryRunner = createLocalQueryRunner(ImmutableMap.of("resource_overcommit", "true")); - for (String function : ImmutableList.of("row_number", "rank")) { - for (String partitions : ImmutableList.of("orderkey, partkey", "partkey", "linestatus")) { - for (int topN : ImmutableList.of(1, 100, 10_000)) { - new SqlTopNRankingBenchmark(localQueryRunner, function, partitions, topN).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } - } - } - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlTpchQuery1.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlTpchQuery1.java deleted file mode 100644 index bf62673e0a26..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlTpchQuery1.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class SqlTpchQuery1 - extends AbstractSqlBenchmark -{ - public SqlTpchQuery1(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "sql_tpch_query_1", 1, 5, "" + - "select\n" + - " returnflag,\n" + - " linestatus,\n" + - " sum(quantity) as sum_qty,\n" + - " sum(extendedprice) as sum_base_price,\n" + - " sum(extendedprice * (1 - discount)) as sum_disc_price,\n" + - " sum(extendedprice * (1 - discount) * (1 + tax)) as sum_charge,\n" + - " avg(quantity) as avg_qty,\n" + - " avg(extendedprice) as avg_price,\n" + - " avg(discount) as avg_disc,\n" + - " count(*) as count_order\n" + - "from\n" + - " lineitem\n" + - "where\n" + - " shipdate <= DATE '1998-09-02'\n" + - "group by\n" + - " returnflag,\n" + - " linestatus\n" + - "order by\n" + - " returnflag,\n" + - " linestatus"); - } - - public static void main(String[] args) - { - new SqlTpchQuery1(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlTpchQuery6.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlTpchQuery6.java deleted file mode 100644 index 52fce6533be9..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/SqlTpchQuery6.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class SqlTpchQuery6 - extends AbstractSqlBenchmark -{ - public SqlTpchQuery6(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "sql_tpch_query_6", 4, 20, "" + - "select sum(extendedprice * discount) as revenue \n" + - "from lineitem \n" + - "where shipdate >= DATE '1994-01-01' \n" + - " and shipdate < DATE '1995-01-01' \n" + - " and discount >= 0.05 \n" + - " and discount <= 0.07 \n" + - " and quantity < 24"); - } - - public static void main(String[] args) - { - new SqlTpchQuery6(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/StatisticsBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/StatisticsBenchmark.java deleted file mode 100644 index c4853a6558b9..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/StatisticsBenchmark.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public abstract class StatisticsBenchmark -{ - public static void main(String... args) - { - LocalQueryRunner localQueryRunner = createLocalQueryRunner(); - new LongVarianceBenchmark(localQueryRunner).runBenchmark(new AverageBenchmarkResults()); - new LongVariancePopBenchmark(localQueryRunner).runBenchmark(new AverageBenchmarkResults()); - new DoubleVarianceBenchmark(localQueryRunner).runBenchmark(new AverageBenchmarkResults()); - new DoubleVariancePopBenchmark(localQueryRunner).runBenchmark(new AverageBenchmarkResults()); - new LongStdDevBenchmark(localQueryRunner).runBenchmark(new AverageBenchmarkResults()); - new LongStdDevPopBenchmark(localQueryRunner).runBenchmark(new AverageBenchmarkResults()); - new DoubleStdDevBenchmark(localQueryRunner).runBenchmark(new AverageBenchmarkResults()); - new DoubleStdDevPopBenchmark(localQueryRunner).runBenchmark(new AverageBenchmarkResults()); - } - - public static class LongVarianceBenchmark - extends AbstractSqlBenchmark - { - public LongVarianceBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "stat_long_variance", 25, 150, "select var_samp(orderkey) from orders"); - } - } - - public static class LongVariancePopBenchmark - extends AbstractSqlBenchmark - { - public LongVariancePopBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "stat_long_variance_pop", 25, 150, "select var_pop(orderkey) from orders"); - } - } - - public static class DoubleVarianceBenchmark - extends AbstractSqlBenchmark - { - public DoubleVarianceBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "stat_double_variance", 25, 150, "select var_samp(totalprice) from orders"); - } - } - - public static class DoubleVariancePopBenchmark - extends AbstractSqlBenchmark - { - public DoubleVariancePopBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "stat_double_variance_pop", 25, 150, "select var_pop(totalprice) from orders"); - } - } - - public static class LongStdDevBenchmark - extends AbstractSqlBenchmark - { - public LongStdDevBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "stat_long_stddev", 25, 150, "select stddev_samp(orderkey) from orders"); - } - } - - public static class LongStdDevPopBenchmark - extends AbstractSqlBenchmark - { - public LongStdDevPopBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "stat_long_stddev_pop", 25, 150, "select stddev_pop(orderkey) from orders"); - } - } - - public static class DoubleStdDevBenchmark - extends AbstractSqlBenchmark - { - public DoubleStdDevBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "stat_double_stddev", 25, 150, "select stddev_samp(totalprice) from orders"); - } - } - - public static class DoubleStdDevPopBenchmark - extends AbstractSqlBenchmark - { - public DoubleStdDevPopBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "stat_double_stddev_pop", 25, 150, "select stddev_pop(totalprice) from orders"); - } - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/StructuredTypesBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/StructuredTypesBenchmark.java deleted file mode 100644 index c9c69783bd0c..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/StructuredTypesBenchmark.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class StructuredTypesBenchmark - extends AbstractSqlBenchmark -{ - // Benchmark is modeled after TPCH query 1, with array/map creation added in - public StructuredTypesBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "structured_types", 4, 5, "" + - "select\n" + - " returnflag,\n" + - " linestatus,\n" + - " sum(array[quantity][1]) as sum_qty,\n" + - " sum(array[extendedprice][1]) as sum_base_price,\n" + - " sum(array[extendedprice][1] * (1 - map(array['key'], array[discount])['key'])) as sum_disc_price,\n" + - " sum(array[extendedprice][1] * (1 - map(array['key'], array[discount])['key']) * (1 + tax)) as sum_charge,\n" + - " avg(map(array['key'], array[quantity])['key']) as avg_qty,\n" + - " avg(map(array['key'], array[extendedprice])['key']) as avg_price,\n" + - " avg(map(array['key'], array[discount])['key']) as avg_disc\n" + - "from\n" + - " lineitem\n" + - "where\n" + - " shipdate <= DATE '1998-09-02'\n" + - "group by\n" + - " returnflag,\n" + - " linestatus\n" + - "order by\n" + - " returnflag,\n" + - " linestatus"); - } - - public static void main(String[] args) - { - new StructuredTypesBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/Top100Benchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/Top100Benchmark.java deleted file mode 100644 index 0f38b93c016b..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/Top100Benchmark.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import com.google.common.collect.ImmutableList; -import io.trino.operator.OperatorFactory; -import io.trino.operator.TopNOperator; -import io.trino.spi.type.Type; -import io.trino.sql.planner.plan.PlanNodeId; -import io.trino.testing.LocalQueryRunner; - -import java.util.List; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; -import static io.trino.spi.connector.SortOrder.ASC_NULLS_LAST; - -public class Top100Benchmark - extends AbstractSimpleOperatorBenchmark -{ - public Top100Benchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "top100", 5, 50); - } - - @Override - protected List createOperatorFactories() - { - List tableScanTypes = getColumnTypes("orders", "totalprice"); - OperatorFactory tableScanOperator = createTableScanOperator(0, new PlanNodeId("test"), "orders", "totalprice"); - OperatorFactory topNOperator = TopNOperator.createOperatorFactory( - 1, - new PlanNodeId("test"), - tableScanTypes, - 100, - ImmutableList.of(0), - ImmutableList.of(ASC_NULLS_LAST), - localQueryRunner.getTypeOperators()); - return ImmutableList.of(tableScanOperator, topNOperator); - } - - public static void main(String[] args) - { - new Top100Benchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/Top100SqlBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/Top100SqlBenchmark.java deleted file mode 100644 index 8bebaf0f1315..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/Top100SqlBenchmark.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class Top100SqlBenchmark - extends AbstractSqlBenchmark -{ - public Top100SqlBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "sql_top_100", 5, 50, "select totalprice from orders order by totalprice desc limit 100"); - } - - public static void main(String[] args) - { - new Top100SqlBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/main/java/io/trino/benchmark/VarBinaryMaxAggregationSqlBenchmark.java b/testing/trino-benchmark/src/main/java/io/trino/benchmark/VarBinaryMaxAggregationSqlBenchmark.java deleted file mode 100644 index 8cd6be3df3e3..000000000000 --- a/testing/trino-benchmark/src/main/java/io/trino/benchmark/VarBinaryMaxAggregationSqlBenchmark.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; - -public class VarBinaryMaxAggregationSqlBenchmark - extends AbstractSqlBenchmark -{ - public VarBinaryMaxAggregationSqlBenchmark(LocalQueryRunner localQueryRunner) - { - super(localQueryRunner, "sql_varbinary_max", 4, 20, "select max(shipinstruct) from lineitem"); - } - - public static void main(String[] args) - { - new VarBinaryMaxAggregationSqlBenchmark(createLocalQueryRunner()).runBenchmark(new SimpleLineBenchmarkResultWriter(System.out)); - } -} diff --git a/testing/trino-benchmark/src/test/java/io/trino/benchmark/BenchmarkCPUCounters.java b/testing/trino-benchmark/src/test/java/io/trino/benchmark/BenchmarkCPUCounters.java deleted file mode 100644 index e4fd6b3a614a..000000000000 --- a/testing/trino-benchmark/src/test/java/io/trino/benchmark/BenchmarkCPUCounters.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.BenchmarkMode; -import org.openjdk.jmh.annotations.Fork; -import org.openjdk.jmh.annotations.Measurement; -import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.OperationsPerInvocation; -import org.openjdk.jmh.annotations.OutputTimeUnit; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.State; -import org.openjdk.jmh.annotations.Warmup; -import org.openjdk.jmh.infra.Blackhole; -import org.openjdk.jmh.runner.RunnerException; - -import java.lang.management.ManagementFactory; -import java.lang.management.ThreadMXBean; -import java.util.concurrent.TimeUnit; - -import static io.trino.jmh.Benchmarks.benchmark; -import static java.lang.Integer.getInteger; - -@State(Scope.Thread) -@OutputTimeUnit(TimeUnit.NANOSECONDS) -@Fork(2) -@Warmup(iterations = 10, time = 2, timeUnit = TimeUnit.SECONDS) -@Measurement(iterations = 10, time = 2, timeUnit = TimeUnit.SECONDS) -@BenchmarkMode(Mode.AverageTime) -public class BenchmarkCPUCounters -{ - private static final ThreadMXBean THREAD_MX_BEAN = ManagementFactory.getThreadMXBean(); - - private static final int ITERATIONS = 1000; - - @Benchmark - @OperationsPerInvocation(ITERATIONS) - public void nanoTime(Blackhole blackhole) - { - for (int i = 0; i < ITERATIONS; i++) { - blackhole.consume(System.nanoTime()); - } - } - - @Benchmark - @OperationsPerInvocation(ITERATIONS) - public void cpuTime(Blackhole blackhole) - { - for (int i = 0; i < ITERATIONS; i++) { - blackhole.consume(currentThreadCpuTime()); - } - } - - @Benchmark - @OperationsPerInvocation(ITERATIONS) - public void userTime(Blackhole blackhole) - { - for (int i = 0; i < ITERATIONS; i++) { - blackhole.consume(currentThreadUserTime()); - } - } - - private static long currentThreadCpuTime() - { - return THREAD_MX_BEAN.getCurrentThreadCpuTime(); - } - - private static long currentThreadUserTime() - { - return THREAD_MX_BEAN.getCurrentThreadUserTime(); - } - - public static void main(String[] args) - throws RunnerException - { - benchmark(BenchmarkCPUCounters.class) - .withOptions(optionsBuilder -> optionsBuilder.threads(getInteger("threads", 1))) - .run(); - } -} diff --git a/testing/trino-benchmark/src/test/java/io/trino/benchmark/BenchmarkDecimalAggregation.java b/testing/trino-benchmark/src/test/java/io/trino/benchmark/BenchmarkDecimalAggregation.java deleted file mode 100644 index f189a062b419..000000000000 --- a/testing/trino-benchmark/src/test/java/io/trino/benchmark/BenchmarkDecimalAggregation.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.spi.Page; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.BenchmarkMode; -import org.openjdk.jmh.annotations.Fork; -import org.openjdk.jmh.annotations.Measurement; -import org.openjdk.jmh.annotations.OutputTimeUnit; -import org.openjdk.jmh.annotations.Param; -import org.openjdk.jmh.annotations.Setup; -import org.openjdk.jmh.annotations.State; -import org.openjdk.jmh.annotations.TearDown; -import org.openjdk.jmh.annotations.Warmup; -import org.openjdk.jmh.runner.RunnerException; - -import java.util.List; - -import static io.trino.jmh.Benchmarks.benchmark; -import static java.lang.String.format; -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.openjdk.jmh.annotations.Mode.AverageTime; -import static org.openjdk.jmh.annotations.Scope.Thread; - -@SuppressWarnings("MethodMayBeStatic") -@State(Thread) -@OutputTimeUnit(MILLISECONDS) -@BenchmarkMode(AverageTime) -@Fork(3) -@Warmup(iterations = 10) -@Measurement(iterations = 10) -public class BenchmarkDecimalAggregation -{ - @State(Thread) - public static class AggregationContext - { - @Param({"orderstatus, avg(totalprice)", - "orderstatus, min(totalprice)", - "orderstatus, sum(totalprice), avg(totalprice), min(totalprice), max(totalprice)"}) - private String project; - - @Param({"double", "decimal(14,2)", "decimal(30,10)"}) - private String type; - - private MemoryLocalQueryRunner queryRunner; - - public final MemoryLocalQueryRunner getQueryRunner() - { - return queryRunner; - } - - @Setup - public void setUp() - { - queryRunner = new MemoryLocalQueryRunner(); - queryRunner.execute(format( - "CREATE TABLE memory.default.orders AS SELECT orderstatus, cast(totalprice as %s) totalprice FROM tpch.sf1.orders", - type)); - } - - @TearDown - public void tearDown() - { - queryRunner.close(); - queryRunner = null; - } - } - - @Benchmark - public List benchmarkBuildHash(AggregationContext context) - { - return context.getQueryRunner() - .execute(format("SELECT %s FROM orders GROUP BY orderstatus", context.project)); - } - - public static void main(String[] args) - throws RunnerException - { - benchmark(BenchmarkDecimalAggregation.class).run(); - } -} diff --git a/testing/trino-benchmark/src/test/java/io/trino/benchmark/BenchmarkInequalityJoin.java b/testing/trino-benchmark/src/test/java/io/trino/benchmark/BenchmarkInequalityJoin.java deleted file mode 100644 index 1b79e398ac36..000000000000 --- a/testing/trino-benchmark/src/test/java/io/trino/benchmark/BenchmarkInequalityJoin.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.spi.Page; -import org.junit.jupiter.api.Test; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.BenchmarkMode; -import org.openjdk.jmh.annotations.Fork; -import org.openjdk.jmh.annotations.Measurement; -import org.openjdk.jmh.annotations.OutputTimeUnit; -import org.openjdk.jmh.annotations.Param; -import org.openjdk.jmh.annotations.Setup; -import org.openjdk.jmh.annotations.State; -import org.openjdk.jmh.annotations.TearDown; -import org.openjdk.jmh.annotations.Warmup; -import org.openjdk.jmh.runner.RunnerException; - -import java.util.List; - -import static io.trino.jmh.Benchmarks.benchmark; -import static java.lang.String.format; -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.openjdk.jmh.annotations.Mode.AverageTime; -import static org.openjdk.jmh.annotations.Scope.Thread; - -/** - * This benchmark a case when there is almost like a cross join query - * but with very selective inequality join condition. In other words - * for each probe position there are lots of matching build positions - * which are filtered out by filtering function. - */ -@SuppressWarnings("MethodMayBeStatic") -@State(Thread) -@OutputTimeUnit(MILLISECONDS) -@BenchmarkMode(AverageTime) -@Fork(3) -@Warmup(iterations = 10) -@Measurement(iterations = 10) -public class BenchmarkInequalityJoin -{ - @State(Thread) - public static class Context - { - private MemoryLocalQueryRunner queryRunner; - - // number of buckets. The smaller number of buckets, the longer position links chain - @Param({"100", "1000", "10000", "60000"}) - private int buckets; - - // How many positions out of 1000 will be actually joined - // 10 means 1 - 10/1000 = 99/100 positions will be filtered out - @Param("10") - private int filterOutCoefficient; - - public MemoryLocalQueryRunner getQueryRunner() - { - return queryRunner; - } - - @Setup - public void setUp() - { - queryRunner = new MemoryLocalQueryRunner(); - - // t1.val1 is in range [0, 1000) - // t1.bucket is in [0, 1000) - queryRunner.execute(format( - "CREATE TABLE memory.default.t1 AS SELECT " + - "orderkey %% %d bucket, " + - "(orderkey * 13) %% 1000 val1 " + - "FROM tpch.tiny.lineitem", - buckets)); - // t2.val2 is in range [0, 10) - // t2.bucket is in [0, 1000) - queryRunner.execute(format( - "CREATE TABLE memory.default.t2 AS SELECT " + - "orderkey %% %d bucket, " + - "(orderkey * 379) %% %d val2 " + - "FROM tpch.tiny.lineitem", - buckets, - filterOutCoefficient)); - } - - @TearDown - public void tearDown() - { - queryRunner.close(); - queryRunner = null; - } - } - - @Benchmark - public List benchmarkJoin(Context context) - { - return context.getQueryRunner() - .execute("SELECT count(*) FROM t1 JOIN t2 on (t1.bucket = t2.bucket) WHERE t1.val1 < t2.val2"); - } - - @Benchmark - public List benchmarkJoinWithArithmeticInPredicate(Context context) - { - return context.getQueryRunner() - .execute("SELECT count(*) FROM t1 JOIN t2 on (t1.bucket = t2.bucket) AND t1.val1 < t2.val2 + 10"); - } - - @Benchmark - public List benchmarkJoinWithFunctionPredicate(Context context) - { - return context.getQueryRunner() - .execute("SELECT count(*) FROM t1 JOIN t2 on (t1.bucket = t2.bucket) AND t1.val1 < sin(t2.val2)"); - } - - @Benchmark - public List benchmarkRangePredicateJoin(Context context) - { - return context.getQueryRunner() - .execute("SELECT count(*) FROM t1 JOIN t2 on (t1.bucket = t2.bucket) AND t1.val1 + 1 < t2.val2 AND t2.val2 < t1.val1 + 5 "); - } - - @Benchmark - public List benchmarkBetweenPredicateJoin(Context context) - { - return context.getQueryRunner() - .execute("SELECT count(*) FROM t1 JOIN t2 on (t1.bucket = t2.bucket) AND t2.val2 BETWEEN t1.val1 + 1 AND t1.val1 + 5"); - } - - @Test - public void verifyJoinBenchmark() - { - Context context = new Context(); - try { - // Contrive a cheap benchmark setup for use in testing - context.buckets = 10; - context.filterOutCoefficient = 10; - context.setUp(); - benchmarkJoin(context); - } - finally { - context.tearDown(); - } - } - - public static void main(String[] args) - throws RunnerException - { - benchmark(BenchmarkInequalityJoin.class).run(); - } -} diff --git a/testing/trino-benchmark/src/test/java/io/trino/benchmark/MemoryLocalQueryRunner.java b/testing/trino-benchmark/src/test/java/io/trino/benchmark/MemoryLocalQueryRunner.java deleted file mode 100644 index 8385f5bcb040..000000000000 --- a/testing/trino-benchmark/src/test/java/io/trino/benchmark/MemoryLocalQueryRunner.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import io.airlift.stats.TestingGcMonitor; -import io.airlift.units.DataSize; -import io.trino.Session; -import io.trino.execution.StageId; -import io.trino.execution.TaskId; -import io.trino.execution.TaskStateMachine; -import io.trino.memory.MemoryPool; -import io.trino.memory.QueryContext; -import io.trino.operator.Driver; -import io.trino.operator.TaskContext; -import io.trino.plugin.memory.MemoryConnectorFactory; -import io.trino.plugin.tpch.TpchConnectorFactory; -import io.trino.spi.Page; -import io.trino.spiller.SpillSpaceTracker; -import io.trino.testing.LocalQueryRunner; -import io.trino.testing.PageConsumerOperator; -import org.intellij.lang.annotations.Language; - -import java.util.List; - -import static io.airlift.units.DataSize.Unit.GIGABYTE; -import static io.trino.testing.TestingSession.testSessionBuilder; - -public class MemoryLocalQueryRunner - implements AutoCloseable -{ - private final LocalQueryRunner localQueryRunner; - - public MemoryLocalQueryRunner() - { - Session.SessionBuilder sessionBuilder = testSessionBuilder() - .setCatalog("memory") - .setSchema("default"); - - localQueryRunner = LocalQueryRunner.create(sessionBuilder.build()); - - // add tpch - localQueryRunner.createCatalog("tpch", new TpchConnectorFactory(1), ImmutableMap.of()); - localQueryRunner.createCatalog( - "memory", - new MemoryConnectorFactory(), - ImmutableMap.of("memory.max-data-per-node", "4GB")); - } - - public List execute(@Language("SQL") String query) - { - return localQueryRunner.inTransaction(session -> { - // enroll the memory and tpch connectors in the transaction - localQueryRunner.getMetadata().getCatalogHandle(session, "tpch"); - localQueryRunner.getMetadata().getCatalogHandle(session, "memory"); - - MemoryPool memoryPool = new MemoryPool(DataSize.of(2, GIGABYTE)); - SpillSpaceTracker spillSpaceTracker = new SpillSpaceTracker(DataSize.of(1, GIGABYTE)); - QueryContext queryContext = new QueryContext( - session.getQueryId(), - DataSize.of(1, GIGABYTE), - memoryPool, - new TestingGcMonitor(), - localQueryRunner.getExecutor(), - localQueryRunner.getScheduler(), - DataSize.of(4, GIGABYTE), - spillSpaceTracker); - - TaskContext taskContext = queryContext - .addTaskContext( - new TaskStateMachine(new TaskId(new StageId("query", 0), 0, 0), localQueryRunner.getExecutor()), - session, - () -> {}, - false, - false); - - // Use NullOutputFactory to avoid coping out results to avoid affecting benchmark results - ImmutableList.Builder output = ImmutableList.builder(); - List drivers = localQueryRunner.createDrivers( - query, - new PageConsumerOperator.PageConsumerOutputFactory(types -> output::add), - taskContext); - - boolean done = false; - while (!done) { - boolean processed = false; - for (Driver driver : drivers) { - if (!driver.isFinished()) { - driver.processForNumberOfIterations(1); - processed = true; - } - } - done = !processed; - } - - return output.build(); - }); - } - - @Override - public void close() - { - localQueryRunner.close(); - } -} diff --git a/testing/trino-benchmark/src/test/java/io/trino/benchmark/TestBenchmarks.java b/testing/trino-benchmark/src/test/java/io/trino/benchmark/TestBenchmarks.java deleted file mode 100644 index 23ba20f9d5a2..000000000000 --- a/testing/trino-benchmark/src/test/java/io/trino/benchmark/TestBenchmarks.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 io.trino.benchmark; - -import io.trino.testing.LocalQueryRunner; -import org.junit.jupiter.api.Test; - -import static io.trino.benchmark.BenchmarkQueryRunner.createLocalQueryRunner; -import static io.trino.benchmark.BenchmarkSuite.createBenchmarks; - -public class TestBenchmarks -{ - @Test - public void smokeTest() - { - try (LocalQueryRunner localQueryRunner = createLocalQueryRunner()) { - for (AbstractBenchmark benchmark : createBenchmarks(localQueryRunner)) { - try { - benchmark.runOnce(); - } - catch (RuntimeException e) { - throw new AssertionError("Error running " + benchmark.getBenchmarkName(), e); - } - } - } - } -} From 9da23863131b184c7e66c29cc76898dbe32c9751 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Thu, 30 Nov 2023 15:41:57 -0800 Subject: [PATCH 574/587] Convert TestingThriftHiveMetastoreBuilder to TrinoFileSystem --- .../java/io/trino/plugin/hive/AbstractTestHive.java | 2 +- .../trino/plugin/hive/AbstractTestHiveFileSystem.java | 2 +- .../plugin/hive/TestingThriftHiveMetastoreBuilder.java | 10 +++++----- .../java/io/trino/plugin/hudi/S3HudiQueryRunner.java | 4 +++- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java index b34d9fba2427..f5c2468d7137 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHive.java @@ -791,7 +791,7 @@ protected final void setup(HostAndPort metastoreAddress, String databaseName) .hiveConfig(hiveConfig) .thriftMetastoreConfig(new ThriftMetastoreConfig() .setAssumeCanonicalPartitionKeys(true)) - .hdfsEnvironment(hdfsEnvironment) + .fileSystemFactory(new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS)) .build()), new Duration(1, MINUTES), new Duration(1, MINUTES), diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java index 1fcd34fdc8f8..6010dfd0b3e5 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/AbstractTestHiveFileSystem.java @@ -207,7 +207,7 @@ protected void setup(String host, int port, String databaseName, HdfsConfigurati testingThriftHiveMetastoreBuilder() .metastoreClient(HostAndPort.fromParts(host, port)) .hiveConfig(config) - .hdfsEnvironment(hdfsEnvironment) + .fileSystemFactory(new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS)) .build()), getBasePath(), hdfsEnvironment); diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestingThriftHiveMetastoreBuilder.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestingThriftHiveMetastoreBuilder.java index 8f37c63020c7..dccc93a9768c 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestingThriftHiveMetastoreBuilder.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestingThriftHiveMetastoreBuilder.java @@ -15,8 +15,8 @@ import com.google.common.net.HostAndPort; import io.airlift.units.Duration; +import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.filesystem.hdfs.HdfsFileSystemFactory; -import io.trino.hdfs.HdfsEnvironment; import io.trino.plugin.hive.metastore.HiveMetastoreConfig; import io.trino.plugin.hive.metastore.thrift.TestingTokenAwareMetastoreClientFactory; import io.trino.plugin.hive.metastore.thrift.ThriftHiveMetastoreFactory; @@ -40,7 +40,7 @@ public final class TestingThriftHiveMetastoreBuilder private TokenAwareMetastoreClientFactory tokenAwareMetastoreClientFactory; private HiveConfig hiveConfig = new HiveConfig(); private ThriftMetastoreConfig thriftMetastoreConfig = new ThriftMetastoreConfig(); - private HdfsEnvironment hdfsEnvironment = HDFS_ENVIRONMENT; + private TrinoFileSystemFactory fileSystemFactory = new HdfsFileSystemFactory(HDFS_ENVIRONMENT, HDFS_FILE_SYSTEM_STATS); public static TestingThriftHiveMetastoreBuilder testingThriftHiveMetastoreBuilder() { @@ -86,9 +86,9 @@ public TestingThriftHiveMetastoreBuilder thriftMetastoreConfig(ThriftMetastoreCo return this; } - public TestingThriftHiveMetastoreBuilder hdfsEnvironment(HdfsEnvironment hdfsEnvironment) + public TestingThriftHiveMetastoreBuilder fileSystemFactory(TrinoFileSystemFactory fileSystemFactory) { - this.hdfsEnvironment = requireNonNull(hdfsEnvironment, "hdfsEnvironment is null"); + this.fileSystemFactory = requireNonNull(fileSystemFactory, "fileSystemFactory is null"); return this; } @@ -100,7 +100,7 @@ public ThriftMetastore build() new HiveMetastoreConfig().isHideDeltaLakeTables(), hiveConfig.isTranslateHiveViews(), thriftMetastoreConfig, - new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS), + fileSystemFactory, newFixedThreadPool(thriftMetastoreConfig.getWriteStatisticsThreads())); return metastoreFactory.createMetastore(Optional.empty()); } diff --git a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/S3HudiQueryRunner.java b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/S3HudiQueryRunner.java index ac470442c84b..6605ef23a31f 100644 --- a/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/S3HudiQueryRunner.java +++ b/plugin/trino-hudi/src/test/java/io/trino/plugin/hudi/S3HudiQueryRunner.java @@ -18,6 +18,7 @@ import io.airlift.log.Logger; import io.airlift.log.Logging; import io.trino.Session; +import io.trino.filesystem.hdfs.HdfsFileSystemFactory; import io.trino.hdfs.DynamicHdfsConfiguration; import io.trino.hdfs.HdfsConfig; import io.trino.hdfs.HdfsConfigurationInitializer; @@ -39,6 +40,7 @@ import java.util.Map; import java.util.Optional; +import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_STATS; import static io.trino.plugin.hive.HiveTestUtils.SOCKS_PROXY; import static io.trino.plugin.hive.TestingThriftHiveMetastoreBuilder.testingThriftHiveMetastoreBuilder; import static io.trino.testing.TestingSession.testSessionBuilder; @@ -66,7 +68,7 @@ public static DistributedQueryRunner create( HiveMetastore metastore = new BridgingHiveMetastore( testingThriftHiveMetastoreBuilder() .metastoreClient(hiveMinioDataLake.getHiveHadoop().getHiveMetastoreEndpoint()) - .hdfsEnvironment(hdfsEnvironment) + .fileSystemFactory(new HdfsFileSystemFactory(hdfsEnvironment, HDFS_FILE_SYSTEM_STATS)) .build()); Database database = Database.builder() .setDatabaseName(TPCH_SCHEMA) From 62acd1b1cd69c54fd50bc0a64afb68f50ec0f167 Mon Sep 17 00:00:00 2001 From: Dain Sundstrom Date: Wed, 29 Nov 2023 23:14:27 -0800 Subject: [PATCH 575/587] Move already created check inside of metastore implementations --- .../plugin/deltalake/DeltaLakeMetadata.java | 56 +----- .../procedure/RegisterTableProcedure.java | 18 +- ...estDeltaLakeCreateSchemaInternalRetry.java | 123 ------------- ...TestDeltaLakeCreateTableInternalRetry.java | 173 ------------------ .../SemiTransactionalHiveMetastore.java | 22 +-- .../metastore/file/FileHiveMetastore.java | 27 ++- .../metastore/glue/GlueHiveMetastore.java | 27 +++ .../thrift/BridgingHiveMetastore.java | 41 ++++- .../TestHiveCreateSchemaInternalRetry.java | 105 ----------- .../metastore/AbstractTestHiveMetastore.java | 100 ++++++++++ .../metastore/TestBridgingHiveMetastore.java | 50 +++++ .../hive/metastore/TestFileHiveMetastore.java | 57 ++++++ .../hive/metastore/TestGlueHiveMetastore.java | 45 +++++ .../hms/AbstractMetastoreTableOperations.java | 24 +-- .../iceberg/catalog/hms/TrinoHiveCatalog.java | 25 +-- .../TestIcebergCreateTableInternalRetry.java | 155 ---------------- 16 files changed, 350 insertions(+), 698 deletions(-) delete mode 100644 plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeCreateSchemaInternalRetry.java delete mode 100644 plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeCreateTableInternalRetry.java delete mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveCreateSchemaInternalRetry.java create mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/AbstractTestHiveMetastore.java create mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestBridgingHiveMetastore.java create mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestFileHiveMetastore.java create mode 100644 plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestGlueHiveMetastore.java delete mode 100644 plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergCreateTableInternalRetry.java diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java index 6c71ba67e8f3..49a639f1c0fb 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java @@ -66,8 +66,6 @@ import io.trino.plugin.deltalake.transactionlog.writer.TransactionLogWriter; import io.trino.plugin.deltalake.transactionlog.writer.TransactionLogWriterFactory; import io.trino.plugin.hive.HiveType; -import io.trino.plugin.hive.SchemaAlreadyExistsException; -import io.trino.plugin.hive.TableAlreadyExistsException; import io.trino.plugin.hive.TrinoViewHiveMetastore; import io.trino.plugin.hive.metastore.Column; import io.trino.plugin.hive.metastore.Database; @@ -836,18 +834,7 @@ public void createSchema(ConnectorSession session, String schemaName, Map existingDatabase = metastore.getDatabase(schemaName); - if (existingDatabase.isEmpty() || !isCreatedBy(existingDatabase.get(), queryId)) { - throw e; - } - } + metastore.createDatabase(database); } @Override @@ -989,21 +976,7 @@ public void createTable(ConnectorSession session, ConnectorTableMetadata tableMe // As a precaution, clear the caches statisticsAccess.invalidateCache(schemaTableName, Optional.of(location)); transactionLogAccess.invalidateCache(schemaTableName, Optional.of(location)); - try { - metastore.createTable( - session, - table, - principalPrivileges); - } - catch (TableAlreadyExistsException e) { - // Ignore TableAlreadyExistsException when table looks like created by us. - // This may happen when an actually successful metastore create call is retried - // e.g. because of a timeout on our side. - Optional
    existingTable = metastore.getRawMetastoreTable(schemaName, tableName); - if (existingTable.isEmpty() || !isCreatedBy(existingTable.get(), queryId)) { - throw e; - } - } + metastore.createTable(session, table, principalPrivileges); } public static Table buildTable(ConnectorSession session, SchemaTableName schemaTableName, String location, boolean isExternal) @@ -1317,18 +1290,7 @@ public Optional finishCreateTable( // As a precaution, clear the caches statisticsAccess.invalidateCache(schemaTableName, Optional.of(location)); transactionLogAccess.invalidateCache(schemaTableName, Optional.of(location)); - try { - metastore.createTable(session, table, principalPrivileges); - } - catch (TableAlreadyExistsException e) { - // Ignore TableAlreadyExistsException when table looks like created by us. - // This may happen when an actually successful metastore create call is retried - // e.g. because of a timeout on our side. - Optional
    existingTable = metastore.getRawMetastoreTable(schemaName, tableName); - if (existingTable.isEmpty() || !isCreatedBy(existingTable.get(), queryId)) { - throw e; - } - } + metastore.createTable(session, table, principalPrivileges); } catch (Exception e) { // Remove the transaction log entry if the table creation fails @@ -1346,18 +1308,6 @@ public Optional finishCreateTable( return Optional.empty(); } - private static boolean isCreatedBy(Database database, String queryId) - { - Optional databaseQueryId = getQueryId(database); - return databaseQueryId.isPresent() && databaseQueryId.get().equals(queryId); - } - - public static boolean isCreatedBy(Table table, String queryId) - { - Optional tableQueryId = getQueryId(table); - return tableQueryId.isPresent() && tableQueryId.get().equals(queryId); - } - @Override public void setTableComment(ConnectorSession session, ConnectorTableHandle tableHandle, Optional comment) { diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/procedure/RegisterTableProcedure.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/procedure/RegisterTableProcedure.java index 52429e5e891d..de3c38e216cb 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/procedure/RegisterTableProcedure.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/procedure/RegisterTableProcedure.java @@ -27,7 +27,6 @@ import io.trino.plugin.deltalake.statistics.CachingExtendedStatisticsAccess; import io.trino.plugin.deltalake.transactionlog.TableSnapshot; import io.trino.plugin.deltalake.transactionlog.TransactionLogAccess; -import io.trino.plugin.hive.TableAlreadyExistsException; import io.trino.plugin.hive.metastore.PrincipalPrivileges; import io.trino.plugin.hive.metastore.Table; import io.trino.spi.TrinoException; @@ -48,7 +47,6 @@ import static io.trino.plugin.deltalake.DeltaLakeErrorCode.DELTA_LAKE_INVALID_TABLE; import static io.trino.plugin.deltalake.DeltaLakeMetadata.buildTable; import static io.trino.plugin.deltalake.DeltaLakeMetadata.getQueryId; -import static io.trino.plugin.deltalake.DeltaLakeMetadata.isCreatedBy; import static io.trino.plugin.deltalake.transactionlog.TransactionLogUtil.getTransactionLogDir; import static io.trino.plugin.hive.metastore.MetastoreUtil.buildInitialPrivilegeSet; import static io.trino.spi.StandardErrorCode.GENERIC_USER_ERROR; @@ -185,21 +183,7 @@ private void doRegisterTable( getQueryId(table).orElseThrow(() -> new IllegalArgumentException("Query id is not present")).equals(queryId), "Table '%s' does not have correct query id set", table); - try { - metastore.createTable( - session, - table, - principalPrivileges); - } - catch (TableAlreadyExistsException e) { - // Ignore TableAlreadyExistsException when table looks like created by us. - // This may happen when an actually successful metastore create call is retried - // e.g. because of a timeout on our side. - Optional
    existingTable = metastore.getRawMetastoreTable(schemaName, tableName); - if (existingTable.isEmpty() || !isCreatedBy(existingTable.get(), queryId)) { - throw e; - } - } + metastore.createTable(session, table, principalPrivileges); } } } diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeCreateSchemaInternalRetry.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeCreateSchemaInternalRetry.java deleted file mode 100644 index 6fca8fde9f12..000000000000 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeCreateSchemaInternalRetry.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * 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 io.trino.plugin.deltalake; - -import com.google.common.collect.ImmutableMap; -import io.trino.Session; -import io.trino.plugin.deltalake.metastore.TestingDeltaLakeMetastoreModule; -import io.trino.plugin.hive.NodeVersion; -import io.trino.plugin.hive.SchemaAlreadyExistsException; -import io.trino.plugin.hive.metastore.Database; -import io.trino.plugin.hive.metastore.HiveMetastore; -import io.trino.plugin.hive.metastore.HiveMetastoreConfig; -import io.trino.plugin.hive.metastore.file.FileHiveMetastore; -import io.trino.plugin.hive.metastore.file.FileHiveMetastoreConfig; -import io.trino.testing.AbstractTestQueryFramework; -import io.trino.testing.DistributedQueryRunner; -import io.trino.testing.QueryRunner; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Map; -import java.util.Optional; - -import static com.google.common.io.MoreFiles.deleteRecursively; -import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; -import static com.google.inject.util.Modules.EMPTY_MODULE; -import static io.trino.plugin.deltalake.DeltaLakeConnectorFactory.CONNECTOR_NAME; -import static io.trino.plugin.hive.HiveMetadata.TRINO_QUERY_ID_NAME; -import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; -import static io.trino.testing.TestingNames.randomNameSuffix; -import static io.trino.testing.TestingSession.testSessionBuilder; -import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; - -@TestInstance(PER_CLASS) -public class TestDeltaLakeCreateSchemaInternalRetry - extends AbstractTestQueryFramework -{ - private static final String CATALOG_NAME = "delta_lake"; - private static final String TEST_SCHEMA_TIMEOUT = "test_delta_lake_schema_" + randomNameSuffix(); - private static final String TEST_SCHEMA_DIFFERENT_SESSION = "test_delta_lake_schema_" + randomNameSuffix(); - - private String dataDirectory; - private HiveMetastore metastore; - - @Override - protected QueryRunner createQueryRunner() - throws Exception - { - Session session = testSessionBuilder() - .setCatalog(CATALOG_NAME) - .build(); - DistributedQueryRunner queryRunner = DistributedQueryRunner.builder(session).build(); - - this.dataDirectory = queryRunner.getCoordinator().getBaseDataDir().resolve("delta_lake_data").toString(); - this.metastore = new FileHiveMetastore( - new NodeVersion("testversion"), - HDFS_FILE_SYSTEM_FACTORY, - new HiveMetastoreConfig().isHideDeltaLakeTables(), - new FileHiveMetastoreConfig() - .setCatalogDirectory(dataDirectory) - .setMetastoreUser("test")) - { - @Override - public synchronized void createDatabase(Database database) - { - if (database.getDatabaseName().equals(TEST_SCHEMA_DIFFERENT_SESSION)) { - // By modifying query id test simulates that schema was created from different session. - database = Database.builder(database) - .setParameters(ImmutableMap.of(TRINO_QUERY_ID_NAME, "new_query_id")) - .build(); - } - // Simulate retry mechanism with timeout failure. - // 1. createDatabase correctly create schema but timeout is triggered - // 2. Retry to createDatabase throws SchemaAlreadyExistsException - super.createDatabase(database); - throw new SchemaAlreadyExistsException(database.getDatabaseName()); - } - }; - - queryRunner.installPlugin(new TestingDeltaLakePlugin(Optional.of(new TestingDeltaLakeMetastoreModule(metastore)), Optional.empty(), EMPTY_MODULE)); - queryRunner.createCatalog(CATALOG_NAME, CONNECTOR_NAME, Map.of()); - return queryRunner; - } - - @AfterAll - public void tearDown() - throws IOException - { - if (metastore != null) { - metastore.dropDatabase(TEST_SCHEMA_TIMEOUT, false); - metastore.dropDatabase(TEST_SCHEMA_DIFFERENT_SESSION, false); - deleteRecursively(Path.of(dataDirectory), ALLOW_INSECURE); - } - } - - @Test - public void testSchemaCreationWithTimeout() - { - assertQuerySucceeds("CREATE SCHEMA " + TEST_SCHEMA_TIMEOUT); - assertQuery("SHOW SCHEMAS LIKE '" + TEST_SCHEMA_TIMEOUT + "'", "VALUES ('" + TEST_SCHEMA_TIMEOUT + "')"); - } - - @Test - public void testSchemaCreationFailsWhenCreatedWithDifferentSession() - { - assertQueryFails("CREATE SCHEMA " + TEST_SCHEMA_DIFFERENT_SESSION, "Schema already exists: '" + TEST_SCHEMA_DIFFERENT_SESSION + "'"); - assertQuery("SHOW SCHEMAS LIKE '" + TEST_SCHEMA_DIFFERENT_SESSION + "'", "VALUES ('" + TEST_SCHEMA_DIFFERENT_SESSION + "')"); - } -} diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeCreateTableInternalRetry.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeCreateTableInternalRetry.java deleted file mode 100644 index 5e2dac8c5364..000000000000 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeCreateTableInternalRetry.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * 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 io.trino.plugin.deltalake; - -import com.google.common.collect.ImmutableMap; -import io.trino.Session; -import io.trino.plugin.deltalake.metastore.TestingDeltaLakeMetastoreModule; -import io.trino.plugin.hive.NodeVersion; -import io.trino.plugin.hive.TableAlreadyExistsException; -import io.trino.plugin.hive.metastore.HiveMetastore; -import io.trino.plugin.hive.metastore.HiveMetastoreConfig; -import io.trino.plugin.hive.metastore.PrincipalPrivileges; -import io.trino.plugin.hive.metastore.Table; -import io.trino.plugin.hive.metastore.file.FileHiveMetastore; -import io.trino.plugin.hive.metastore.file.FileHiveMetastoreConfig; -import io.trino.testing.AbstractTestQueryFramework; -import io.trino.testing.DistributedQueryRunner; -import io.trino.testing.QueryRunner; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Map; -import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static com.google.common.base.Verify.verify; -import static com.google.common.io.MoreFiles.deleteRecursively; -import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; -import static com.google.inject.util.Modules.EMPTY_MODULE; -import static io.trino.plugin.deltalake.DeltaLakeConnectorFactory.CONNECTOR_NAME; -import static io.trino.plugin.hive.HiveMetadata.TRINO_QUERY_ID_NAME; -import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; -import static io.trino.testing.TestingSession.testSessionBuilder; -import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; - -@TestInstance(PER_CLASS) -public class TestDeltaLakeCreateTableInternalRetry - extends AbstractTestQueryFramework -{ - private static final String CATALOG_NAME = "delta_lake"; - private static final String SCHEMA_NAME = "test_create_table"; - - private String dataDirectory; - - @Override - protected QueryRunner createQueryRunner() - throws Exception - { - Session session = testSessionBuilder() - .setCatalog(CATALOG_NAME) - .setSchema(SCHEMA_NAME) - .build(); - DistributedQueryRunner queryRunner = DistributedQueryRunner.builder(session).build(); - - dataDirectory = queryRunner.getCoordinator().getBaseDataDir().resolve("delta_lake_data").toString(); - HiveMetastore metastore = new FileHiveMetastore( - new NodeVersion("testversion"), - HDFS_FILE_SYSTEM_FACTORY, - new HiveMetastoreConfig().isHideDeltaLakeTables(), - new FileHiveMetastoreConfig() - .setCatalogDirectory(dataDirectory) - .setMetastoreUser("test")) - { - @Override - public synchronized void createTable(Table table, PrincipalPrivileges principalPrivileges) - { - if (table.getTableName().startsWith("test_different_session")) { - // By modifying query id test simulates that table was created from different session. - table = Table.builder(table) - .setParameters(ImmutableMap.of(TRINO_QUERY_ID_NAME, "new_query_id")) - .build(); - } - // Simulate retry mechanism with timeout failure of ThriftHiveMetastore. - // 1. createTable correctly create table but timeout is triggered - // 2. Retry to createTable throws TableAlreadyExistsException - super.createTable(table, principalPrivileges); - throw new TableAlreadyExistsException(table.getSchemaTableName()); - } - }; - queryRunner.installPlugin(new TestingDeltaLakePlugin(Optional.of(new TestingDeltaLakeMetastoreModule(metastore)), Optional.empty(), EMPTY_MODULE)); - queryRunner.createCatalog(CATALOG_NAME, CONNECTOR_NAME, Map.of("delta.register-table-procedure.enabled", "true")); - queryRunner.execute("CREATE SCHEMA " + SCHEMA_NAME); - return queryRunner; - } - - @AfterAll - public void tearDown() - throws IOException - { - if (dataDirectory != null) { - deleteRecursively(Path.of(dataDirectory), ALLOW_INSECURE); - } - } - - @Test - public void testCreateTableInternalRetry() - { - assertQuerySucceeds("CREATE TABLE test_ct_internal_retry(a int)"); - assertQuery("SHOW TABLES LIKE 'test_ct_internal_retry'", "VALUES 'test_ct_internal_retry'"); - } - - @Test - public void testCreateTableAsSelectInternalRetry() - { - assertQuerySucceeds("CREATE TABLE test_ctas_internal_retry AS SELECT 1 a"); - assertQuery("SHOW TABLES LIKE 'test_ctas_internal_retry'", "VALUES 'test_ctas_internal_retry'"); - } - - @Test - public void testRegisterTableInternalRetry() - { - assertQuerySucceeds("CREATE TABLE test_register_table_internal_retry AS SELECT 1 a"); - String tableLocation = getTableLocation("test_register_table_internal_retry"); - assertUpdate("CALL system.unregister_table(current_schema, 'test_register_table_internal_retry')"); - - assertQuerySucceeds("CALL system.register_table(current_schema, 'test_register_table_internal_retry', '" + tableLocation + "')"); - assertQuery("SHOW TABLES LIKE 'test_register_table_internal_retry'", "VALUES 'test_register_table_internal_retry'"); - } - - @Test - public void testCreateTableFailureWithDifferentSession() - { - assertQueryFails("CREATE TABLE test_different_session_ct(a int)", "Table already exists: .*"); - assertQuery("SHOW TABLES LIKE 'test_different_session_ct'", "VALUES 'test_different_session_ct'"); - } - - @Test - public void testCreateTableAsSelectFailureWithDifferentSession() - { - assertQueryFails("CREATE TABLE test_different_session_ctas_failure AS SELECT 1 a", "Failed to write Delta Lake transaction log entry"); - assertQuery("SHOW TABLES LIKE 'test_different_session_ctas_failure'", "VALUES 'test_different_session_ctas_failure'"); - } - - @Test - public void testRegisterTableFailureWithDifferentSession() - { - assertQuerySucceeds("CREATE TABLE test_register_table_failure AS SELECT 1 a"); - String tableLocation = getTableLocation("test_register_table_failure"); - assertUpdate("CALL system.unregister_table(current_schema, 'test_register_table_failure')"); - - assertQueryFails( - "CALL system.register_table(current_schema, 'test_different_session_register_table_failure', '" + tableLocation + "')", - "Table already exists: .*"); - assertQuery("SHOW TABLES LIKE 'test_different_session_register_table_failure'", "VALUES 'test_different_session_register_table_failure'"); - } - - private String getTableLocation(String tableName) - { - Pattern locationPattern = Pattern.compile(".*location = '(.*?)'.*", Pattern.DOTALL); - Matcher m = locationPattern.matcher((String) computeActual("SHOW CREATE TABLE " + tableName).getOnlyValue()); - if (m.find()) { - String location = m.group(1); - verify(!m.find(), "Unexpected second match"); - return location; - } - throw new IllegalStateException("Location not found in SHOW CREATE TABLE result"); - } -} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/SemiTransactionalHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/SemiTransactionalHiveMetastore.java index 833229fbcb28..cabc1de51b45 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/SemiTransactionalHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/SemiTransactionalHiveMetastore.java @@ -42,7 +42,6 @@ import io.trino.plugin.hive.PartitionNotFoundException; import io.trino.plugin.hive.PartitionStatistics; import io.trino.plugin.hive.PartitionUpdateAndMergeResults; -import io.trino.plugin.hive.SchemaAlreadyExistsException; import io.trino.plugin.hive.TableAlreadyExistsException; import io.trino.plugin.hive.TableInvalidationCallback; import io.trino.plugin.hive.acid.AcidOperation; @@ -464,26 +463,7 @@ public synchronized void createDatabase(ConnectorSession session, Database datab "Database '%s' does not have correct query id set", database.getDatabaseName()); - setExclusive(delegate -> { - try { - delegate.createDatabase(database); - } - catch (SchemaAlreadyExistsException e) { - // Ignore SchemaAlreadyExistsException when database looks like created by us. - // This may happen when an actually successful metastore create call is retried - // e.g. because of a timeout on our side. - Optional existingDatabase = delegate.getDatabase(database.getDatabaseName()); - if (existingDatabase.isEmpty() || !isCreatedBy(existingDatabase.get(), queryId)) { - throw e; - } - } - }); - } - - private static boolean isCreatedBy(Database database, String queryId) - { - Optional databaseQueryId = getQueryId(database); - return databaseQueryId.isPresent() && databaseQueryId.get().equals(queryId); + setExclusive(delegate -> delegate.createDatabase(database)); } public synchronized void dropDatabase(ConnectorSession session, String schemaName) diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/file/FileHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/file/FileHiveMetastore.java index 994b7174b268..95d195032f72 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/file/FileHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/file/FileHiveMetastore.java @@ -96,6 +96,7 @@ import static io.trino.plugin.hive.HiveErrorCode.HIVE_CONCURRENT_MODIFICATION_DETECTED; import static io.trino.plugin.hive.HiveErrorCode.HIVE_METASTORE_ERROR; import static io.trino.plugin.hive.HiveMetadata.TABLE_COMMENT; +import static io.trino.plugin.hive.HiveMetadata.TRINO_QUERY_ID_NAME; import static io.trino.plugin.hive.HivePartitionManager.extractPartitionValues; import static io.trino.plugin.hive.TableType.EXTERNAL_TABLE; import static io.trino.plugin.hive.TableType.MANAGED_TABLE; @@ -194,7 +195,18 @@ public synchronized void createDatabase(Database database) database.getParameters()); verifyDatabaseNameLength(database.getDatabaseName()); - verifyDatabaseNotExists(database.getDatabaseName()); + + Optional existingDatabase = getDatabase(database.getDatabaseName()); + if (existingDatabase.isPresent()) { + // Do not throw SchemaAlreadyExistsException if this query has already created the database. + // This may happen when an actually successful metastore create call is retried, + // because of a timeout on our side. + String expectedQueryId = database.getParameters().get(TRINO_QUERY_ID_NAME); + if (expectedQueryId != null && expectedQueryId.equals(existingDatabase.get().getParameters().get(TRINO_QUERY_ID_NAME))) { + return; + } + throw new SchemaAlreadyExistsException(database.getDatabaseName()); + } Location databaseMetadataDirectory = getDatabaseMetadataDirectory(database.getDatabaseName()); writeSchemaFile(DATABASE, databaseMetadataDirectory, databaseCodec, new DatabaseMetadata(currentVersion, database), false); @@ -340,7 +352,18 @@ public synchronized void createTable(Table table, PrincipalPrivileges principalP { verifyTableNameLength(table.getTableName()); verifyDatabaseExists(table.getDatabaseName()); - verifyTableNotExists(table.getDatabaseName(), table.getTableName()); + + Optional
    existingTable = getTable(table.getDatabaseName(), table.getTableName()); + if (existingTable.isPresent()) { + // Do not throw TableAlreadyExistsException if this query has already created the table. + // This may happen when an actually successful metastore create call is retried, + // because of a timeout on our side. + String expectedQueryId = table.getParameters().get(TRINO_QUERY_ID_NAME); + if (expectedQueryId != null && expectedQueryId.equals(existingTable.get().getParameters().get(TRINO_QUERY_ID_NAME))) { + return; + } + throw new TableAlreadyExistsException(new SchemaTableName(table.getDatabaseName(), table.getTableName())); + } Location tableMetadataDirectory = getTableMetadataDirectory(table); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java index 141b33b30d3b..e1efe5654fcd 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java @@ -146,6 +146,7 @@ import static io.trino.plugin.hive.HiveErrorCode.HIVE_INVALID_METADATA; import static io.trino.plugin.hive.HiveErrorCode.HIVE_METASTORE_ERROR; import static io.trino.plugin.hive.HiveMetadata.TABLE_COMMENT; +import static io.trino.plugin.hive.HiveMetadata.TRINO_QUERY_ID_NAME; import static io.trino.plugin.hive.TableType.MANAGED_TABLE; import static io.trino.plugin.hive.TableType.VIRTUAL_VIEW; import static io.trino.plugin.hive.metastore.MetastoreUtil.makePartitionName; @@ -518,6 +519,19 @@ public void createDatabase(Database database) glueClient.createDatabase(new CreateDatabaseRequest().withDatabaseInput(databaseInput))); } catch (AlreadyExistsException e) { + // Do not throw SchemaAlreadyExistsException if this query has already created the database. + // This may happen when an actually successful metastore create call is retried, + // because of a timeout on our side. + String expectedQueryId = database.getParameters().get(TRINO_QUERY_ID_NAME); + if (expectedQueryId != null) { + String existingQueryId = getDatabase(database.getDatabaseName()) + .map(Database::getParameters) + .map(parameters -> parameters.get(TRINO_QUERY_ID_NAME)) + .orElse(null); + if (expectedQueryId.equals(existingQueryId)) { + return; + } + } throw new SchemaAlreadyExistsException(database.getDatabaseName(), e); } catch (AmazonServiceException e) { @@ -594,6 +608,19 @@ public void createTable(Table table, PrincipalPrivileges principalPrivileges) .withTableInput(input))); } catch (AlreadyExistsException e) { + // Do not throw TableAlreadyExistsException if this query has already created the table. + // This may happen when an actually successful metastore create call is retried, + // because of a timeout on our side. + String expectedQueryId = table.getParameters().get(TRINO_QUERY_ID_NAME); + if (expectedQueryId != null) { + String existingQueryId = getTable(table.getDatabaseName(), table.getTableName()) + .map(Table::getParameters) + .map(parameters -> parameters.get(TRINO_QUERY_ID_NAME)) + .orElse(null); + if (expectedQueryId.equals(existingQueryId)) { + return; + } + } throw new TableAlreadyExistsException(new SchemaTableName(table.getDatabaseName(), table.getTableName()), e); } catch (EntityNotFoundException e) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/BridgingHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/BridgingHiveMetastore.java index bf015ef3af10..5cd62ce4babf 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/BridgingHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/thrift/BridgingHiveMetastore.java @@ -20,6 +20,8 @@ import io.trino.plugin.hive.HivePartition; import io.trino.plugin.hive.HiveType; import io.trino.plugin.hive.PartitionStatistics; +import io.trino.plugin.hive.SchemaAlreadyExistsException; +import io.trino.plugin.hive.TableAlreadyExistsException; import io.trino.plugin.hive.acid.AcidOperation; import io.trino.plugin.hive.acid.AcidTransaction; import io.trino.plugin.hive.metastore.AcidTransactionOwner; @@ -54,6 +56,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static io.trino.plugin.hive.HiveMetadata.TABLE_COMMENT; +import static io.trino.plugin.hive.HiveMetadata.TRINO_QUERY_ID_NAME; import static io.trino.plugin.hive.metastore.MetastoreUtil.isAvroTableWithSchemaSet; import static io.trino.plugin.hive.metastore.MetastoreUtil.verifyCanDropColumn; import static io.trino.plugin.hive.metastore.thrift.ThriftMetastoreUtil.csvSchemaFields; @@ -194,7 +197,24 @@ public Optional> getAllViews() @Override public void createDatabase(Database database) { - delegate.createDatabase(toMetastoreApiDatabase(database)); + try { + delegate.createDatabase(toMetastoreApiDatabase(database)); + } + catch (SchemaAlreadyExistsException e) { + // Ignore SchemaAlreadyExistsException when this query has already created the database. + // This may happen when an actually successful metastore create call is retried, + // because of a timeout on our side. + String expectedQueryId = database.getParameters().get(TRINO_QUERY_ID_NAME); + if (expectedQueryId != null) { + String existingQueryId = getDatabase(database.getDatabaseName()) + .map(Database::getParameters) + .map(parameters -> parameters.get(TRINO_QUERY_ID_NAME)) + .orElse(null); + if (!expectedQueryId.equals(existingQueryId)) { + throw e; + } + } + } } @Override @@ -235,7 +255,24 @@ public void setDatabaseOwner(String databaseName, HivePrincipal principal) @Override public void createTable(Table table, PrincipalPrivileges principalPrivileges) { - delegate.createTable(toMetastoreApiTable(table, principalPrivileges)); + try { + delegate.createTable(toMetastoreApiTable(table, principalPrivileges)); + } + catch (TableAlreadyExistsException e) { + // Ignore TableAlreadyExistsException when this query has already created the table. + // This may happen when an actually successful metastore create call is retried, + // because of a timeout on our side. + String expectedQueryId = table.getParameters().get(TRINO_QUERY_ID_NAME); + if (expectedQueryId != null) { + String existingQueryId = getTable(table.getDatabaseName(), table.getTableName()) + .map(Table::getParameters) + .map(parameters -> parameters.get(TRINO_QUERY_ID_NAME)) + .orElse(null); + if (!expectedQueryId.equals(existingQueryId)) { + throw e; + } + } + } } @Override diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveCreateSchemaInternalRetry.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveCreateSchemaInternalRetry.java deleted file mode 100644 index 2aca3f200752..000000000000 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/TestHiveCreateSchemaInternalRetry.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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 io.trino.plugin.hive; - -import com.google.common.collect.ImmutableMap; -import io.trino.plugin.hive.metastore.Database; -import io.trino.plugin.hive.metastore.HiveMetastore; -import io.trino.plugin.hive.metastore.HiveMetastoreConfig; -import io.trino.plugin.hive.metastore.file.FileHiveMetastore; -import io.trino.plugin.hive.metastore.file.FileHiveMetastoreConfig; -import io.trino.testing.AbstractTestQueryFramework; -import io.trino.testing.QueryRunner; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; - -import java.io.IOException; - -import static io.trino.plugin.hive.HiveMetadata.TRINO_QUERY_ID_NAME; -import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; -import static io.trino.testing.TestingNames.randomNameSuffix; -import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; - -@TestInstance(PER_CLASS) -public class TestHiveCreateSchemaInternalRetry - extends AbstractTestQueryFramework -{ - private static final String TEST_SCHEMA_TIMEOUT = "test_hive_schema_" + randomNameSuffix(); - private static final String TEST_SCHEMA_DIFFERENT_SESSION = "test_hive_schema_" + randomNameSuffix(); - - private HiveMetastore metastore; - - @Override - protected QueryRunner createQueryRunner() - throws Exception - { - return HiveQueryRunner.builder() - .setCreateTpchSchemas(false) - .setMetastore(distributedQueryRunner -> metastore = createMetastore(distributedQueryRunner.getCoordinator().getBaseDataDir().resolve("hive_data").toString())) - .build(); - } - - private FileHiveMetastore createMetastore(String dataDirectory) - { - return new FileHiveMetastore( - new NodeVersion("testversion"), - HDFS_FILE_SYSTEM_FACTORY, - new HiveMetastoreConfig().isHideDeltaLakeTables(), - new FileHiveMetastoreConfig() - .setCatalogDirectory(dataDirectory) - .setMetastoreUser("test")) - { - @Override - public synchronized void createDatabase(Database database) - { - if (database.getDatabaseName().equals(TEST_SCHEMA_DIFFERENT_SESSION)) { - // By modifying query id test simulates that schema was created from different session. - database = Database.builder(database) - .setParameters(ImmutableMap.of(TRINO_QUERY_ID_NAME, "new_query_id")) - .build(); - } - // Simulate retry mechanism with timeout failure. - // 1. createDatabase correctly create schema but timeout is triggered - // 2. Retry to createDatabase throws SchemaAlreadyExistsException - super.createDatabase(database); - throw new SchemaAlreadyExistsException(database.getDatabaseName()); - } - }; - } - - @AfterAll - public void tearDown() - throws IOException - { - if (metastore != null) { - metastore.dropDatabase(TEST_SCHEMA_TIMEOUT, false); - metastore.dropDatabase(TEST_SCHEMA_DIFFERENT_SESSION, false); - } - } - - @Test - public void testSchemaCreationWithTimeout() - { - assertQuerySucceeds("CREATE SCHEMA " + TEST_SCHEMA_TIMEOUT); - assertQuery("SHOW SCHEMAS LIKE '" + TEST_SCHEMA_TIMEOUT + "'", "VALUES ('" + TEST_SCHEMA_TIMEOUT + "')"); - } - - @Test - public void testSchemaCreationFailsWhenCreatedWithDifferentSession() - { - assertQueryFails("CREATE SCHEMA " + TEST_SCHEMA_DIFFERENT_SESSION, "Schema already exists: '" + TEST_SCHEMA_DIFFERENT_SESSION + "'"); - assertQuery("SHOW SCHEMAS LIKE '" + TEST_SCHEMA_DIFFERENT_SESSION + "'", "VALUES ('" + TEST_SCHEMA_DIFFERENT_SESSION + "')"); - } -} diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/AbstractTestHiveMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/AbstractTestHiveMetastore.java new file mode 100644 index 000000000000..82460db7532c --- /dev/null +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/AbstractTestHiveMetastore.java @@ -0,0 +1,100 @@ +/* + * 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 io.trino.plugin.hive.metastore; + +import io.trino.plugin.hive.SchemaAlreadyExistsException; +import io.trino.plugin.hive.TableAlreadyExistsException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ThreadLocalRandom; + +import static io.trino.plugin.hive.HiveMetadata.TRINO_QUERY_ID_NAME; +import static io.trino.plugin.hive.HiveStorageFormat.PARQUET; +import static io.trino.plugin.hive.HiveType.HIVE_STRING; +import static io.trino.plugin.hive.TableType.EXTERNAL_TABLE; +import static io.trino.plugin.hive.metastore.PrincipalPrivileges.NO_PRIVILEGES; +import static io.trino.plugin.hive.metastore.StorageFormat.fromHiveStorageFormat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; + +@TestInstance(PER_CLASS) +@Execution(SAME_THREAD) +abstract class AbstractTestHiveMetastore +{ + private HiveMetastore metastore; + + public void setMetastore(HiveMetastore metastore) + { + this.metastore = metastore; + } + + @Test + void testCreateDatabase() + { + String databaseName = "test_database_" + ThreadLocalRandom.current().nextLong(Long.MAX_VALUE); + Database.Builder database = Database.builder() + .setDatabaseName(databaseName) + .setParameters(Map.of(TRINO_QUERY_ID_NAME, "query_id")) + .setOwnerName(Optional.empty()) + .setOwnerType(Optional.empty()); + metastore.createDatabase(database.build()); + // second call with the same query ID succeeds + metastore.createDatabase(database.build()); + + database.setParameters(Map.of(TRINO_QUERY_ID_NAME, "another_query_id")); + assertThatThrownBy(() -> metastore.createDatabase(database.build())) + .isInstanceOf(SchemaAlreadyExistsException.class); + + metastore.dropDatabase(databaseName, false); + } + + @Test + void testCreateTable() + { + String databaseName = "test_database_" + ThreadLocalRandom.current().nextLong(Long.MAX_VALUE); + Database.Builder database = Database.builder() + .setDatabaseName(databaseName) + .setOwnerName(Optional.empty()) + .setOwnerType(Optional.empty()); + metastore.createDatabase(database.build()); + + String tableName = "test_table"; + Table.Builder table = Table.builder() + .setDatabaseName(databaseName) + .setTableName(tableName) + .setParameters(Map.of(TRINO_QUERY_ID_NAME, "query_id")) + .setTableType(EXTERNAL_TABLE.name()) + .setDataColumns(List.of(new Column("test_column", HIVE_STRING, Optional.empty(), Map.of()))) + .setOwner(Optional.empty()); + table.getStorageBuilder() + .setLocation(Optional.of("/tmp/location")) + .setStorageFormat(fromHiveStorageFormat(PARQUET)); + metastore.createTable(table.build(), NO_PRIVILEGES); + // second call with the same query ID succeeds + metastore.createTable(table.build(), NO_PRIVILEGES); + + table.setParameters(Map.of(TRINO_QUERY_ID_NAME, "another_query_id")); + assertThatThrownBy(() -> metastore.createTable(table.build(), NO_PRIVILEGES)) + .isInstanceOf(TableAlreadyExistsException.class); + + metastore.dropTable(databaseName, tableName, false); + metastore.dropDatabase(databaseName, false); + } +} diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestBridgingHiveMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestBridgingHiveMetastore.java new file mode 100644 index 000000000000..f8242a403cfb --- /dev/null +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestBridgingHiveMetastore.java @@ -0,0 +1,50 @@ +/* + * 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 io.trino.plugin.hive.metastore; + +import io.trino.plugin.hive.containers.HiveHadoop; +import io.trino.plugin.hive.metastore.thrift.BridgingHiveMetastore; +import io.trino.plugin.hive.metastore.thrift.ThriftMetastoreConfig; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; + +import static io.trino.plugin.hive.TestingThriftHiveMetastoreBuilder.testingThriftHiveMetastoreBuilder; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; + +@TestInstance(PER_CLASS) +@Execution(SAME_THREAD) +final class TestBridgingHiveMetastore + extends AbstractTestHiveMetastore +{ + private final HiveHadoop hiveHadoop; + + TestBridgingHiveMetastore() + { + hiveHadoop = HiveHadoop.builder().build(); + hiveHadoop.start(); + + setMetastore(new BridgingHiveMetastore(testingThriftHiveMetastoreBuilder() + .metastoreClient(hiveHadoop.getHiveMetastoreEndpoint()) + .thriftMetastoreConfig(new ThriftMetastoreConfig().setDeleteFilesOnDrop(true)) + .build())); + } + + @AfterAll + void afterAll() + { + hiveHadoop.stop(); + } +} diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestFileHiveMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestFileHiveMetastore.java new file mode 100644 index 000000000000..61e389e84af3 --- /dev/null +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestFileHiveMetastore.java @@ -0,0 +1,57 @@ +/* + * 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 io.trino.plugin.hive.metastore; + +import io.trino.filesystem.local.LocalFileSystemFactory; +import io.trino.plugin.hive.NodeVersion; +import io.trino.plugin.hive.metastore.file.FileHiveMetastore; +import io.trino.plugin.hive.metastore.file.FileHiveMetastoreConfig; +import org.junit.jupiter.api.AfterAll; + +import java.io.IOException; +import java.nio.file.Path; + +import static com.google.common.io.MoreFiles.deleteRecursively; +import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; +import static java.nio.file.Files.createTempDirectory; + +final class TestFileHiveMetastore + extends AbstractTestHiveMetastore +{ + private final Path tempDir; + + TestFileHiveMetastore() + throws IOException + { + tempDir = createTempDirectory("test"); + tempDir.toFile().mkdirs(); + LocalFileSystemFactory fileSystemFactory = new LocalFileSystemFactory(tempDir); + + setMetastore(new FileHiveMetastore( + new NodeVersion("testversion"), + fileSystemFactory, + false, + new FileHiveMetastoreConfig() + .setCatalogDirectory("local:///") + .setMetastoreUser("test") + .setDisableLocationChecks(true))); + } + + @AfterAll + void tearDown() + throws IOException + { + deleteRecursively(tempDir, ALLOW_INSECURE); + } +} diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestGlueHiveMetastore.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestGlueHiveMetastore.java new file mode 100644 index 000000000000..c2ebc9590a3b --- /dev/null +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/metastore/TestGlueHiveMetastore.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 io.trino.plugin.hive.metastore; + +import org.junit.jupiter.api.AfterAll; + +import java.io.IOException; +import java.nio.file.Path; + +import static com.google.common.io.MoreFiles.deleteRecursively; +import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; +import static io.trino.plugin.hive.metastore.glue.TestingGlueHiveMetastore.createTestingGlueHiveMetastore; +import static java.nio.file.Files.createTempDirectory; + +final class TestGlueHiveMetastore + extends AbstractTestHiveMetastore +{ + private final Path tempDir; + + TestGlueHiveMetastore() + throws IOException + { + tempDir = createTempDirectory("test"); + tempDir.toFile().mkdirs(); + setMetastore(createTestingGlueHiveMetastore(tempDir)); + } + + @AfterAll + void tearDown() + throws IOException + { + deleteRecursively(tempDir, ALLOW_INSECURE); + } +} diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/AbstractMetastoreTableOperations.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/AbstractMetastoreTableOperations.java index c9458be31721..0fc67d7d0c56 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/AbstractMetastoreTableOperations.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/AbstractMetastoreTableOperations.java @@ -40,7 +40,6 @@ import static io.trino.plugin.iceberg.IcebergErrorCode.ICEBERG_INVALID_METADATA; import static io.trino.plugin.iceberg.IcebergTableName.isMaterializedViewStorage; import static io.trino.plugin.iceberg.IcebergTableName.tableNameFrom; -import static io.trino.plugin.iceberg.IcebergUtil.TRINO_QUERY_ID_NAME; import static java.lang.String.format; import static java.util.Locale.ENGLISH; import static java.util.Objects.requireNonNull; @@ -125,32 +124,11 @@ protected final void commitNewTable(TableMetadata metadata) try { metastore.createTable(table, privileges); } - catch (SchemaNotFoundException e) { + catch (SchemaNotFoundException | TableAlreadyExistsException e) { // clean up metadata files corresponding to the current transaction fileIo.deleteFile(newMetadataLocation); throw e; } - catch (TableAlreadyExistsException e) { - // Ignore TableAlreadyExistsException when table looks like created by us. - // This may happen when an actually successful metastore create call is retried - // e.g. because of a timeout on our side. - refreshFromMetadataLocation(getRefreshedLocation(true)); - if (!isCreatedBy(this.currentMetadata, session.getQueryId())) { - fileIo.deleteFile(newMetadataLocation); - throw e; - } - } - } - - public static boolean isCreatedBy(TableMetadata existingTableMetadata, String queryId) - { - Optional tableQueryId = getQueryId(existingTableMetadata); - return tableQueryId.isPresent() && tableQueryId.get().equals(queryId); - } - - private static Optional getQueryId(TableMetadata tableMetadata) - { - return Optional.ofNullable(tableMetadata.currentSnapshot().summary().get(TRINO_QUERY_ID_NAME)); } protected Table.Builder updateMetastoreTable(Table.Builder builder, TableMetadata metadata, String metadataLocation, Optional previousMetadataLocation) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java index 6fdf7cff121b..702fd0ccd699 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java @@ -25,7 +25,6 @@ import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.plugin.base.CatalogName; import io.trino.plugin.hive.HiveSchemaProperties; -import io.trino.plugin.hive.TableAlreadyExistsException; import io.trino.plugin.hive.TrinoViewHiveMetastore; import io.trino.plugin.hive.metastore.Column; import io.trino.plugin.hive.metastore.Database; @@ -346,29 +345,7 @@ public void registerTable(ConnectorSession session, SchemaTableName schemaTableN .setParameter(METADATA_LOCATION_PROP, tableMetadata.metadataFileLocation()); PrincipalPrivileges privileges = owner.map(MetastoreUtil::buildInitialPrivilegeSet).orElse(NO_PRIVILEGES); - try { - metastore.createTable(builder.build(), privileges); - } - catch (TableAlreadyExistsException e) { - // Ignore TableAlreadyExistsException when table looks like created by us. - // This may happen when an actually successful metastore create call is retried - // e.g. because of a timeout on our side. - Optional existingTable = metastore.getTable(schemaTableName.getSchemaName(), schemaTableName.getTableName()); - if (existingTable.isEmpty() || !isCreatedBy(existingTable.get(), session.getQueryId())) { - throw e; - } - } - } - - public static boolean isCreatedBy(io.trino.plugin.hive.metastore.Table table, String queryId) - { - Optional tableQueryId = getQueryId(table); - return tableQueryId.isPresent() && tableQueryId.get().equals(queryId); - } - - private static Optional getQueryId(io.trino.plugin.hive.metastore.Table table) - { - return Optional.ofNullable(table.getParameters().get(TRINO_QUERY_ID_NAME)); + metastore.createTable(builder.build(), privileges); } @Override diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergCreateTableInternalRetry.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergCreateTableInternalRetry.java deleted file mode 100644 index 94f3535b3ac4..000000000000 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergCreateTableInternalRetry.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * 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 io.trino.plugin.iceberg; - -import com.google.common.collect.ImmutableMap; -import io.trino.Session; -import io.trino.plugin.hive.NodeVersion; -import io.trino.plugin.hive.TableAlreadyExistsException; -import io.trino.plugin.hive.metastore.HiveMetastore; -import io.trino.plugin.hive.metastore.HiveMetastoreConfig; -import io.trino.plugin.hive.metastore.PrincipalPrivileges; -import io.trino.plugin.hive.metastore.Table; -import io.trino.plugin.hive.metastore.file.FileHiveMetastore; -import io.trino.plugin.hive.metastore.file.FileHiveMetastoreConfig; -import io.trino.plugin.iceberg.catalog.file.TestingIcebergFileMetastoreCatalogModule; -import io.trino.testing.AbstractTestQueryFramework; -import io.trino.testing.DistributedQueryRunner; -import io.trino.testing.QueryRunner; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; - -import java.io.File; -import java.io.IOException; -import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static com.google.common.base.Verify.verify; -import static com.google.common.io.MoreFiles.deleteRecursively; -import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; -import static com.google.inject.util.Modules.EMPTY_MODULE; -import static io.trino.plugin.hive.HiveMetadata.TRINO_QUERY_ID_NAME; -import static io.trino.plugin.hive.HiveTestUtils.HDFS_FILE_SYSTEM_FACTORY; -import static io.trino.plugin.iceberg.IcebergQueryRunner.ICEBERG_CATALOG; -import static io.trino.testing.TestingSession.testSessionBuilder; -import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; - -@TestInstance(PER_CLASS) -public class TestIcebergCreateTableInternalRetry - extends AbstractTestQueryFramework -{ - private static final String SCHEMA_NAME = "iceberg_internal_retry_schema"; - private File metastoreDir; - - @Override - protected QueryRunner createQueryRunner() - throws Exception - { - Session session = testSessionBuilder() - .setCatalog(ICEBERG_CATALOG) - .setSchema(SCHEMA_NAME) - .build(); - DistributedQueryRunner queryRunner = DistributedQueryRunner.builder(session).build(); - metastoreDir = queryRunner.getCoordinator().getBaseDataDir().resolve("test_iceberg_table_smoke_test").toFile(); - this.metastoreDir.deleteOnExit(); - HiveMetastore metastore = new FileHiveMetastore( - new NodeVersion("testversion"), - HDFS_FILE_SYSTEM_FACTORY, - new HiveMetastoreConfig().isHideDeltaLakeTables(), - new FileHiveMetastoreConfig() - .setCatalogDirectory(metastoreDir.toURI().toString()) - .setMetastoreUser("test")) - { - @Override - public synchronized void createTable(Table table, PrincipalPrivileges principalPrivileges) - { - if (table.getTableName().startsWith("test_different_session")) { - // By modifying query id test simulates that table was created from different session. - table = Table.builder(table) - .setParameters(ImmutableMap.of(TRINO_QUERY_ID_NAME, "new_query_id")) - .build(); - } - // Simulate retry mechanism with timeout failure of ThriftHiveMetastore. - // 1. createTable correctly create table but timeout is triggered - // 2. Retry to createTable throws TableAlreadyExistsException - super.createTable(table, principalPrivileges); - throw new TableAlreadyExistsException(table.getSchemaTableName()); - } - }; - - queryRunner.installPlugin(new TestingIcebergPlugin(Optional.of(new TestingIcebergFileMetastoreCatalogModule(metastore)), Optional.empty(), EMPTY_MODULE)); - queryRunner.createCatalog(ICEBERG_CATALOG, "iceberg", ImmutableMap.of("iceberg.register-table-procedure.enabled", "true")); - queryRunner.execute("CREATE SCHEMA " + SCHEMA_NAME); - return queryRunner; - } - - @AfterAll - public void tearDown() - throws IOException - { - deleteRecursively(metastoreDir.toPath(), ALLOW_INSECURE); - } - - @Test - public void testCreateTableInternalRetry() - { - assertQuerySucceeds("CREATE TABLE test_ct_internal_retry(a int)"); - assertQuery("SHOW TABLES LIKE 'test_ct_internal_retry'", "VALUES 'test_ct_internal_retry'"); - } - - @Test - public void testCreateTableAsSelectInternalRetry() - { - assertQuerySucceeds("CREATE TABLE test_ctas_internal_retry AS SELECT 1 a"); - assertQuery("SHOW TABLES LIKE 'test_ctas_internal_retry'", "VALUES 'test_ctas_internal_retry'"); - } - - @Test - public void testRegisterTableInternalRetry() - { - assertQuerySucceeds("CREATE TABLE test_register_table_internal_retry AS SELECT 1 a"); - String tableLocation = getTableLocation("test_register_table_internal_retry"); - assertUpdate("CALL system.unregister_table(current_schema, 'test_register_table_internal_retry')"); - - assertQuerySucceeds("CALL system.register_table(current_schema, 'test_register_table_internal_retry', '" + tableLocation + "')"); - assertQuery("SHOW TABLES LIKE 'test_register_table_internal_retry'", "VALUES 'test_register_table_internal_retry'"); - } - - @Test - public void testRegisterTableFailureWithDifferentSession() - { - assertQuerySucceeds("CREATE TABLE test_register_table_failure AS SELECT 1 a"); - String tableLocation = getTableLocation("test_register_table_failure"); - assertUpdate("CALL system.unregister_table(current_schema, 'test_register_table_failure')"); - - assertQueryFails( - "CALL system.register_table(current_schema, 'test_different_session_register_table_failure', '" + tableLocation + "')", - "Table already exists: .*"); - assertQuery("SHOW TABLES LIKE 'test_different_session_register_table_failure'", "VALUES 'test_different_session_register_table_failure'"); - } - - private String getTableLocation(String tableName) - { - Pattern locationPattern = Pattern.compile(".*location = '(.*?)'.*", Pattern.DOTALL); - Matcher m = locationPattern.matcher((String) computeActual("SHOW CREATE TABLE " + tableName).getOnlyValue()); - if (m.find()) { - String location = m.group(1); - verify(!m.find(), "Unexpected second match"); - return location; - } - throw new IllegalStateException("Location not found in SHOW CREATE TABLE result"); - } -} From 3c498b753926433988a437a90ba558c58bdf7a65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Osipiuk?= Date: Sat, 2 Dec 2023 11:30:37 +0100 Subject: [PATCH 576/587] Do not fail stage after already done Ignore failure within stage execution if it already done. This is to work around the problem which was introduced with https://github.com/trinodb/trino/commit/39b04491c05. With that change we close EventDrivenTaskSource when stage completes, and as a result of that it may emit an failure event due to internall processes being cancelled. Handling of event in `StageExecution.fail()` did close a number of objects needed for query completion (see createStageExecutionCloser()). As a result the query could hang. --- .../faulttolerant/EventDrivenFaultTolerantQueryScheduler.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java index 6692e2ebfb93..1c8f611b7d23 100644 --- a/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java +++ b/core/trino-main/src/main/java/io/trino/execution/scheduler/faulttolerant/EventDrivenFaultTolerantQueryScheduler.java @@ -2415,6 +2415,10 @@ public void abort() public void fail(Throwable t) { + if (stage.getState().isDone()) { + // stage already done; ignore + return; + } Closer closer = createStageExecutionCloser(); closer.register(() -> stage.fail(t)); try { From 717c223a815a429ee4677b2b5ca052cbeb6033f8 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Fri, 1 Dec 2023 16:25:15 +0100 Subject: [PATCH 577/587] Update Maven to 3.9.6 --- .mvn/wrapper/maven-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index eacdc9ed17a1..346d645fd06f 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -14,5 +14,5 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar From 19f9677c6c3e72d065d792d023b373dbab83909e Mon Sep 17 00:00:00 2001 From: Yuya Ebihara Date: Mon, 4 Dec 2023 12:58:52 +0900 Subject: [PATCH 578/587] Fix incorrect result when reading dates during calendar switch in SQL Server Co-Authored-By: Jonas Haag --- .../trino/plugin/sqlserver/SqlServerClient.java | 8 ++------ .../sqlserver/BaseSqlServerConnectorTest.java | 6 ------ .../sqlserver/BaseSqlServerTypeMapping.java | 15 +++------------ 3 files changed, 5 insertions(+), 24 deletions(-) diff --git a/plugin/trino-sqlserver/src/main/java/io/trino/plugin/sqlserver/SqlServerClient.java b/plugin/trino-sqlserver/src/main/java/io/trino/plugin/sqlserver/SqlServerClient.java index f182a7f4e278..a9dfb09b7d4a 100644 --- a/plugin/trino-sqlserver/src/main/java/io/trino/plugin/sqlserver/SqlServerClient.java +++ b/plugin/trino-sqlserver/src/main/java/io/trino/plugin/sqlserver/SqlServerClient.java @@ -139,6 +139,7 @@ import static io.trino.plugin.jdbc.StandardColumnMappings.booleanWriteFunction; import static io.trino.plugin.jdbc.StandardColumnMappings.charReadFunction; import static io.trino.plugin.jdbc.StandardColumnMappings.charWriteFunction; +import static io.trino.plugin.jdbc.StandardColumnMappings.dateReadFunctionUsingLocalDate; import static io.trino.plugin.jdbc.StandardColumnMappings.decimalColumnMapping; import static io.trino.plugin.jdbc.StandardColumnMappings.doubleColumnMapping; import static io.trino.plugin.jdbc.StandardColumnMappings.doubleWriteFunction; @@ -549,7 +550,7 @@ public Optional toColumnMapping(ConnectorSession session, Connect case Types.DATE: return Optional.of(ColumnMapping.longMapping( DATE, - sqlServerDateReadFunction(), + dateReadFunctionUsingLocalDate(), sqlServerDateWriteFunction())); case Types.TIME: @@ -886,11 +887,6 @@ private static LongWriteFunction sqlServerDateWriteFunction() return (statement, index, day) -> statement.setString(index, DATE_FORMATTER.format(LocalDate.ofEpochDay(day))); } - private static LongReadFunction sqlServerDateReadFunction() - { - return (resultSet, index) -> LocalDate.parse(resultSet.getString(index), DATE_FORMATTER).toEpochDay(); - } - private static ColumnMapping timestampWithTimeZoneColumnMapping(int precision) { checkArgument(precision <= MAX_SUPPORTED_TEMPORAL_PRECISION, "Unsupported datetimeoffset precision %s", precision); diff --git a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerConnectorTest.java b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerConnectorTest.java index 93a487e70201..1ed1ec95b8ee 100644 --- a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerConnectorTest.java +++ b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerConnectorTest.java @@ -100,12 +100,6 @@ protected TestTable createTableWithUnsupportedColumn() protected Optional filterDataMappingSmokeTestData(DataMappingTestSetup dataMappingTestSetup) { String typeName = dataMappingTestSetup.getTrinoTypeName(); - if (typeName.equals("date")) { - // SQL Server plus 10 days when the date is the range of 1582 Oct 5 and 14 - if (dataMappingTestSetup.getSampleValueLiteral().equals("DATE '1582-10-05'") || dataMappingTestSetup.getSampleValueLiteral().equals("DATE '1582-10-14'")) { - return Optional.empty(); - } - } if (typeName.equals("timestamp(3) with time zone") || typeName.equals("timestamp(6) with time zone")) { return Optional.of(dataMappingTestSetup.asUnsupported()); diff --git a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerTypeMapping.java b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerTypeMapping.java index c762e783337d..f6aae0bb6f1d 100644 --- a/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerTypeMapping.java +++ b/plugin/trino-sqlserver/src/test/java/io/trino/plugin/sqlserver/BaseSqlServerTypeMapping.java @@ -13,7 +13,6 @@ */ package io.trino.plugin.sqlserver; -import com.google.common.collect.ImmutableList; import io.trino.Session; import io.trino.spi.type.TimeZoneKey; import io.trino.testing.AbstractTestQueryFramework; @@ -481,6 +480,9 @@ private SqlDataTypeTest dateTest(Function inputLiteralFactory) .addRoundTrip("date", inputLiteralFactory.apply("'0012-12-12'"), DATE, "DATE '0012-12-12'") // before julian->gregorian switch .addRoundTrip("date", inputLiteralFactory.apply("'1500-01-01'"), DATE, "DATE '1500-01-01'") + // during julian->gregorian switch + .addRoundTrip("date", inputLiteralFactory.apply("'1582-10-05'"), DATE, "DATE '1582-10-05'") + .addRoundTrip("date", inputLiteralFactory.apply("'1582-10-14'"), DATE, "DATE '1582-10-14'") // before epoch .addRoundTrip("date", inputLiteralFactory.apply("'1952-04-03'"), DATE, "DATE '1952-04-03'") .addRoundTrip("date", inputLiteralFactory.apply("'1970-01-01'"), DATE, "DATE '1970-01-01'") @@ -493,17 +495,6 @@ private SqlDataTypeTest dateTest(Function inputLiteralFactory) .addRoundTrip("date", inputLiteralFactory.apply("'1983-10-01'"), DATE, "DATE '1983-10-01'"); } - @Test - public void testDateJulianGregorianCalendarSwitch() - { - try (TestTable table = new TestTable(getQueryRunner()::execute, "test_old_date", "(dt DATE)", ImmutableList.of("DATE '1582-10-05'", "DATE '1582-10-14'"))) { - // SQL Server returns +10 days when the date is in the range of 1582-10-05 and 1582-10-14, but we need to pass the original value in predicates - assertQuery("SELECT * FROM " + table.getName(), "VALUES DATE '1582-10-15', DATE '1582-10-24'"); - assertQuery("SELECT * FROM " + table.getName() + " WHERE dt = DATE '1582-10-05'", "VALUES DATE '1582-10-15'"); - assertQueryReturnsEmptyResult("SELECT * FROM " + table.getName() + " WHERE dt = DATE '1582-10-15'"); - } - } - @Test public void testSqlServerDateUnsupported() { From 82123da3543c8affbf2b8f8abad1d4634c8e2655 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Mon, 4 Dec 2023 10:27:55 +0100 Subject: [PATCH 579/587] Upgrade AWS SDK v1 to 1.12.604 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 979c3cd3d601..72c49e641d26 100644 --- a/pom.xml +++ b/pom.xml @@ -177,7 +177,7 @@ 4.13.1 14.0.1 1.11.3 - 1.12.595 + 1.12.604 4.17.0 7.5.1 87 From 26f7b445c36c7385151e4276c7c16d995e443343 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Mon, 4 Dec 2023 10:29:03 +0100 Subject: [PATCH 580/587] Upgrade nimbus-jose-jwt to 9.37.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 72c49e641d26..40821c9b05b6 100644 --- a/pom.xml +++ b/pom.xml @@ -587,7 +587,7 @@ com.nimbusds nimbus-jose-jwt - 9.37.1 + 9.37.2 From 36b07c5e051904a6a7e80a1ba2faeeee32777045 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Mon, 4 Dec 2023 10:29:23 +0100 Subject: [PATCH 581/587] Upgrade oauth2-oidc-sdk to 11.7 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 40821c9b05b6..75c857374507 100644 --- a/pom.xml +++ b/pom.xml @@ -593,7 +593,7 @@ com.nimbusds oauth2-oidc-sdk - 11.6 + 11.7 jdk11 From b4349611915562f650ac886f40f94c9532e5faff Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Mon, 4 Dec 2023 10:29:56 +0100 Subject: [PATCH 582/587] Upgrade grpc-bom to 1.59.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 75c857374507..f31da94e4b8e 100644 --- a/pom.xml +++ b/pom.xml @@ -248,7 +248,7 @@ io.grpc grpc-bom - 1.59.0 + 1.59.1 pom import From 76d2d34b2752844162ca36700b030a0e7e5650ed Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Mon, 4 Dec 2023 10:32:41 +0100 Subject: [PATCH 583/587] Update maven-model to 3.9.6 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f31da94e4b8e..1a2d98661c85 100644 --- a/pom.xml +++ b/pom.xml @@ -1824,7 +1824,7 @@ org.apache.maven maven-model - 3.9.5 + 3.9.6 From c2b72e1b783a3cae630d791b22cc85a7f37308a5 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Mon, 4 Dec 2023 10:33:23 +0100 Subject: [PATCH 584/587] Upgrade jdbi to 3.42.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1a2d98661c85..aa21878daf29 100644 --- a/pom.xml +++ b/pom.xml @@ -272,7 +272,7 @@ org.jdbi jdbi3-bom - 3.41.3 + 3.42.0 pom import From 6f749948f4a8853edfa73d9d55ec2ceef2dda1bc Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Mon, 4 Dec 2023 10:33:54 +0100 Subject: [PATCH 585/587] Upgrade AWS SDK v2 to 2.21.37 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index aa21878daf29..21cba4c84ad8 100644 --- a/pom.xml +++ b/pom.xml @@ -296,7 +296,7 @@ software.amazon.awssdk bom - 2.21.29 + 2.21.37 pom import From 37aaff07bf2c1f2019a310c72ac80452a4385d26 Mon Sep 17 00:00:00 2001 From: "Mateusz \"Serafin\" Gajewski" Date: Mon, 4 Dec 2023 10:40:20 +0100 Subject: [PATCH 586/587] Update mariadb-java-client to 3.3.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 21cba4c84ad8..84976e4108e4 100644 --- a/pom.xml +++ b/pom.xml @@ -2019,7 +2019,7 @@ org.mariadb.jdbc mariadb-java-client - 3.3.0 + 3.3.1 From c4ff5fb6e1906d9080ad624caa735985f1249df5 Mon Sep 17 00:00:00 2001 From: YU Teng Date: Wed, 22 Dec 2021 19:33:45 +0100 Subject: [PATCH 587/587] Add Snowflake JDBC Connector --- docs/src/main/sphinx/connector.md | 1 + docs/src/main/sphinx/connector/snowflake.md | 96 +++ docs/src/main/sphinx/static/img/snowflake.png | Bin 0 -> 93500 bytes plugin/trino-snowflake/pom.xml | 266 +++++++ .../plugin/snowflake/SnowflakeClient.java | 660 ++++++++++++++++++ .../snowflake/SnowflakeClientModule.java | 95 +++ .../plugin/snowflake/SnowflakeConfig.java | 100 +++ .../plugin/snowflake/SnowflakePlugin.java | 25 + .../snowflake/BaseSnowflakeConnectorTest.java | 622 +++++++++++++++++ .../snowflake/SnowflakeQueryRunner.java | 97 +++ .../plugin/snowflake/TestSnowflakeClient.java | 153 ++++ .../plugin/snowflake/TestSnowflakeConfig.java | 61 ++ .../snowflake/TestSnowflakeConnectorTest.java | 38 + .../plugin/snowflake/TestSnowflakePlugin.java | 33 + .../snowflake/TestSnowflakeTypeMapping.java | 411 +++++++++++ .../snowflake/TestingSnowflakeServer.java | 77 ++ pom.xml | 7 + .../EnvMultinodeAllConnectors.java | 1 + .../environment/EnvMultinodeSnowflake.java | 78 +++ .../launcher/suite/suites/SuiteSnowflake.java | 37 + .../multinode-all/snowflake.properties | 4 + .../multinode-snowflake/snowflake.properties | 4 + .../io/trino/tests/product/TestGroups.java | 1 + .../product/snowflake/TestSnowflake.java | 46 ++ .../trino-server-dev/etc/config.properties | 1 + 25 files changed, 2914 insertions(+) create mode 100644 docs/src/main/sphinx/connector/snowflake.md create mode 100644 docs/src/main/sphinx/static/img/snowflake.png create mode 100644 plugin/trino-snowflake/pom.xml create mode 100644 plugin/trino-snowflake/src/main/java/io/trino/plugin/snowflake/SnowflakeClient.java create mode 100644 plugin/trino-snowflake/src/main/java/io/trino/plugin/snowflake/SnowflakeClientModule.java create mode 100644 plugin/trino-snowflake/src/main/java/io/trino/plugin/snowflake/SnowflakeConfig.java create mode 100644 plugin/trino-snowflake/src/main/java/io/trino/plugin/snowflake/SnowflakePlugin.java create mode 100644 plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/BaseSnowflakeConnectorTest.java create mode 100644 plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/SnowflakeQueryRunner.java create mode 100644 plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/TestSnowflakeClient.java create mode 100644 plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/TestSnowflakeConfig.java create mode 100644 plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/TestSnowflakeConnectorTest.java create mode 100644 plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/TestSnowflakePlugin.java create mode 100644 plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/TestSnowflakeTypeMapping.java create mode 100644 plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/TestingSnowflakeServer.java create mode 100644 testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeSnowflake.java create mode 100644 testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/suite/suites/SuiteSnowflake.java create mode 100644 testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/environment/multinode-all/snowflake.properties create mode 100644 testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/environment/multinode-snowflake/snowflake.properties create mode 100644 testing/trino-product-tests/src/main/java/io/trino/tests/product/snowflake/TestSnowflake.java diff --git a/docs/src/main/sphinx/connector.md b/docs/src/main/sphinx/connector.md index 3b86e28f3d56..d8dd8253d6da 100644 --- a/docs/src/main/sphinx/connector.md +++ b/docs/src/main/sphinx/connector.md @@ -37,6 +37,7 @@ Prometheus Redis Redshift SingleStore +Snowflake SQL Server System Thrift diff --git a/docs/src/main/sphinx/connector/snowflake.md b/docs/src/main/sphinx/connector/snowflake.md new file mode 100644 index 000000000000..579764534e23 --- /dev/null +++ b/docs/src/main/sphinx/connector/snowflake.md @@ -0,0 +1,96 @@ +# Snowflake connector + +```{raw} html + +``` + +The Snowflake connector allows querying and creating tables in an +external [Snowflake](https://www.snowflake.com/) account. This can be used to join data between +different systems like Snowflake and Hive, or between two different +Snowflake accounts. + +## Configuration + +To configure the Snowflake connector, create a catalog properties file +in `etc/catalog` named, for example, `example.properties`, to +mount the Snowflake connector as the `snowflake` catalog. +Create the file with the following contents, replacing the +connection properties as appropriate for your setup: + +```none +connector.name=snowflake +connection-url=jdbc:snowflake://.snowflakecomputing.com +connection-user=root +connection-password=secret +snowflake.account=account +snowflake.database=database +snowflake.role=role +snowflake.warehouse=warehouse +``` + +### Arrow serialization support + +This is an experimental feature which introduces support for using Apache Arrow +as the serialization format when reading from Snowflake. Please note there are +a few caveats: + +- Using Apache Arrow serialization is disabled by default. In order to enable + it, add `--add-opens=java.base/java.nio=ALL-UNNAMED` to the Trino + {ref}`jvm-config`. + +### Multiple Snowflake databases or accounts + +The Snowflake connector can only access a single database within +a Snowflake account. Thus, if you have multiple Snowflake databases, +or want to connect to multiple Snowflake accounts, you must configure +multiple instances of the Snowflake connector. + +% snowflake-type-mapping: + +## Type mapping + +Trino supports the following Snowflake data types: + +| Snowflake Type | Trino Type | +| -------------- | -------------- | +| `boolean` | `boolean` | +| `tinyint` | `bigint` | +| `smallint` | `bigint` | +| `byteint` | `bigint` | +| `int` | `bigint` | +| `integer` | `bigint` | +| `bigint` | `bigint` | +| `float` | `real` | +| `real` | `real` | +| `double` | `double` | +| `decimal` | `decimal(P,S)` | +| `varchar(n)` | `varchar(n)` | +| `char(n)` | `varchar(n)` | +| `binary(n)` | `varbinary` | +| `varbinary` | `varbinary` | +| `date` | `date` | +| `time` | `time` | +| `timestampntz` | `timestamp` | +| `timestamptz` | `timestampTZ` | +| `timestampltz` | `timestampTZ` | + +Complete list of [Snowflake data types](https://docs.snowflake.com/en/sql-reference/intro-summary-data-types.html). + +(snowflake-sql-support)= + +## SQL support + +The connector provides read access and write access to data and metadata in +a Snowflake database. In addition to the {ref}`globally available +` and {ref}`read operation ` +statements, the connector supports the following features: + +- {doc}`/sql/insert` +- {doc}`/sql/delete` +- {doc}`/sql/truncate` +- {doc}`/sql/create-table` +- {doc}`/sql/create-table-as` +- {doc}`/sql/drop-table` +- {doc}`/sql/alter-table` +- {doc}`/sql/create-schema` +- {doc}`/sql/drop-schema` diff --git a/docs/src/main/sphinx/static/img/snowflake.png b/docs/src/main/sphinx/static/img/snowflake.png new file mode 100644 index 0000000000000000000000000000000000000000..b337bc4d5a779c77e0c63cdf302ba793d235cc2c GIT binary patch literal 93500 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 literal 0 HcmV?d00001 diff --git a/plugin/trino-snowflake/pom.xml b/plugin/trino-snowflake/pom.xml new file mode 100644 index 000000000000..16b198e10c05 --- /dev/null +++ b/plugin/trino-snowflake/pom.xml @@ -0,0 +1,266 @@ + + + 4.0.0 + + + io.trino + trino-root + 435-SNAPSHOT + ../../pom.xml + + + trino-snowflake + trino-plugin + Trino - Snowflake Connector + + + ${project.parent.basedir} + + + + + com.google.guava + guava + + + + com.google.inject + guice + + + + io.airlift + configuration + + + + io.airlift + log + + + + io.trino + trino-base-jdbc + + + + io.trino + trino-plugin-toolkit + + + + net.snowflake + snowflake-jdbc + 3.13.32 + + + + + com.fasterxml.jackson.core + jackson-annotations + provided + + + + io.airlift + slice + provided + + + + io.opentelemetry + opentelemetry-api + provided + + + + io.opentelemetry + opentelemetry-context + provided + + + + io.trino + trino-spi + provided + + + + org.openjdk.jol + jol-core + provided + + + + + io.airlift + testing + test + + + + io.trino + trino-base-jdbc + test-jar + test + + + + io.trino + trino-main + test + + + + io.trino + trino-main + test-jar + test + + + + io.trino + trino-testing + test + + + + io.trino + trino-testing-services + test + + + + io.trino + trino-tpch + test + + + + io.trino.tpch + tpch + test + + + + org.assertj + assertj-core + test + + + + org.jetbrains + annotations + test + + + + org.junit.jupiter + junit-jupiter-api + test + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + org.testcontainers + jdbc + test + + + + org.testcontainers + testcontainers + test + + + + org.testng + testng + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + --add-opens=java.base/java.nio=ALL-UNNAMED + + + + + + + + default + + true + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/TestSnowflakeClient.java + **/TestSnowflakeConfig.java + **/TestSnowflakeConnectorTest.java + **/TestSnowflakePlugin.java + **/TestSnowflakeTypeMapping.java + **/Test*FailureRecoveryTest.java + + + + + + + + + + cloud-tests + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/TestSnowflakeClient.java + **/TestSnowflakeConfig.java + **/TestSnowflakeConnectorTest.java + **/TestSnowflakePlugin.java + **/TestSnowflakeTypeMapping.java + **/Test*FailureRecoveryTest.java + + + + + + + + + fte-tests + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/Test*FailureRecoveryTest.java + + + + + + + + diff --git a/plugin/trino-snowflake/src/main/java/io/trino/plugin/snowflake/SnowflakeClient.java b/plugin/trino-snowflake/src/main/java/io/trino/plugin/snowflake/SnowflakeClient.java new file mode 100644 index 000000000000..949ec492561f --- /dev/null +++ b/plugin/trino-snowflake/src/main/java/io/trino/plugin/snowflake/SnowflakeClient.java @@ -0,0 +1,660 @@ +/* + * 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 io.trino.plugin.snowflake; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.inject.Inject; +import io.airlift.log.Logger; +import io.airlift.slice.Slices; +import io.trino.plugin.base.aggregation.AggregateFunctionRewriter; +import io.trino.plugin.base.aggregation.AggregateFunctionRule; +import io.trino.plugin.base.expression.ConnectorExpressionRewriter; +import io.trino.plugin.base.mapping.IdentifierMapping; +import io.trino.plugin.jdbc.BaseJdbcClient; +import io.trino.plugin.jdbc.BaseJdbcConfig; +import io.trino.plugin.jdbc.ColumnMapping; +import io.trino.plugin.jdbc.ConnectionFactory; +import io.trino.plugin.jdbc.JdbcColumnHandle; +import io.trino.plugin.jdbc.JdbcExpression; +import io.trino.plugin.jdbc.JdbcTableHandle; +import io.trino.plugin.jdbc.JdbcTypeHandle; +import io.trino.plugin.jdbc.LongWriteFunction; +import io.trino.plugin.jdbc.ObjectReadFunction; +import io.trino.plugin.jdbc.ObjectWriteFunction; +import io.trino.plugin.jdbc.PredicatePushdownController; +import io.trino.plugin.jdbc.QueryBuilder; +import io.trino.plugin.jdbc.SliceReadFunction; +import io.trino.plugin.jdbc.SliceWriteFunction; +import io.trino.plugin.jdbc.StandardColumnMappings; +import io.trino.plugin.jdbc.WriteMapping; +import io.trino.plugin.jdbc.aggregation.ImplementAvgDecimal; +import io.trino.plugin.jdbc.aggregation.ImplementAvgFloatingPoint; +import io.trino.plugin.jdbc.aggregation.ImplementCount; +import io.trino.plugin.jdbc.aggregation.ImplementCountAll; +import io.trino.plugin.jdbc.aggregation.ImplementMinMax; +import io.trino.plugin.jdbc.aggregation.ImplementSum; +import io.trino.plugin.jdbc.expression.JdbcConnectorExpressionRewriterBuilder; +import io.trino.plugin.jdbc.expression.ParameterizedExpression; +import io.trino.plugin.jdbc.logging.RemoteQueryModifier; +import io.trino.spi.TrinoException; +import io.trino.spi.connector.AggregateFunction; +import io.trino.spi.connector.ColumnHandle; +import io.trino.spi.connector.ConnectorSession; +import io.trino.spi.type.CharType; +import io.trino.spi.type.Chars; +import io.trino.spi.type.DateTimeEncoding; +import io.trino.spi.type.DateType; +import io.trino.spi.type.DecimalType; +import io.trino.spi.type.LongTimestamp; +import io.trino.spi.type.LongTimestampWithTimeZone; +import io.trino.spi.type.StandardTypes; +import io.trino.spi.type.TimeType; +import io.trino.spi.type.TimeZoneKey; +import io.trino.spi.type.TimestampType; +import io.trino.spi.type.TimestampWithTimeZoneType; +import io.trino.spi.type.Timestamps; +import io.trino.spi.type.Type; +import io.trino.spi.type.TypeManager; +import io.trino.spi.type.TypeSignature; +import io.trino.spi.type.VarcharType; + +import java.math.RoundingMode; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.TimeZone; +import java.util.function.BiFunction; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static io.airlift.slice.Slices.utf8Slice; +import static io.trino.plugin.base.util.JsonTypeUtil.jsonParse; +import static io.trino.plugin.jdbc.JdbcErrorCode.JDBC_ERROR; +import static io.trino.plugin.jdbc.PredicatePushdownController.DISABLE_PUSHDOWN; +import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; + +public class SnowflakeClient + extends BaseJdbcClient +{ + /* TIME supports an optional precision parameter for fractional seconds, e.g. TIME(3). Time precision can range from 0 (seconds) to 9 (nanoseconds). The default precision is 9. + All TIME values must be between 00:00:00 and 23:59:59.999999999. TIME internally stores “wallclock” time, and all operations on TIME values are performed without taking any time zone into consideration. + */ + private static final int SNOWFLAKE_MAX_SUPPORTED_TIMESTAMP_PRECISION = 9; + private static final Logger log = Logger.get(SnowflakeClient.class); + private static final DateTimeFormatter SNOWFLAKE_DATETIME_FORMATTER = DateTimeFormatter.ofPattern("y-MM-dd'T'HH:mm:ss.SSSSSSSSSXXX"); + private static final DateTimeFormatter SNOWFLAKE_DATE_FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd"); + private static final DateTimeFormatter SNOWFLAKE_TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("y-MM-dd'T'HH:mm:ss.SSSSSSSSS"); + private static final DateTimeFormatter SNOWFLAKE_TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss.SSSSSSSSS"); + private final Type jsonType; + private final AggregateFunctionRewriter aggregateFunctionRewriter; + + private interface WriteMappingFunction + { + WriteMapping convert(Type type); + } + + private interface ColumnMappingFunction + { + Optional convert(JdbcTypeHandle typeHandle); + } + + private static final TimeZone UTC_TZ = TimeZone.getTimeZone(ZoneId.of("UTC")); + // Mappings for JDBC column types to internal Trino types + private static final Map STANDARD_COLUMN_MAPPINGS = ImmutableMap.builder() + .put(Types.BOOLEAN, StandardColumnMappings.booleanColumnMapping()) + .put(Types.TINYINT, StandardColumnMappings.tinyintColumnMapping()) + .put(Types.SMALLINT, StandardColumnMappings.smallintColumnMapping()) + .put(Types.INTEGER, StandardColumnMappings.integerColumnMapping()) + .put(Types.BIGINT, StandardColumnMappings.bigintColumnMapping()) + .put(Types.REAL, StandardColumnMappings.realColumnMapping()) + .put(Types.DOUBLE, StandardColumnMappings.doubleColumnMapping()) + .put(Types.FLOAT, StandardColumnMappings.doubleColumnMapping()) + .put(Types.BINARY, StandardColumnMappings.varbinaryColumnMapping()) + .put(Types.VARBINARY, StandardColumnMappings.varbinaryColumnMapping()) + .put(Types.LONGVARBINARY, StandardColumnMappings.varbinaryColumnMapping()) + .buildOrThrow(); + + private static final Map SHOWFLAKE_COLUMN_MAPPINGS = ImmutableMap.builder() + .put("time", typeHandle -> { + //return Optional.of(columnMappingPushdown(timeColumnMapping(typeHandle))); + return Optional.of(timeColumnMapping(typeHandle)); + }) + .put("timestampntz", typeHandle -> { + return Optional.of(timestampColumnMapping(typeHandle)); + }) + .put("timestamptz", typeHandle -> { + return Optional.of(timestampTZColumnMapping(typeHandle)); + }) + .put("timestampltz", typeHandle -> { + return Optional.of(timestampTZColumnMapping(typeHandle)); + }) + .put("date", typeHandle -> { + return Optional.of(ColumnMapping.longMapping( + DateType.DATE, (resultSet, columnIndex) -> + LocalDate.ofEpochDay(resultSet.getLong(columnIndex)).toEpochDay(), + snowFlakeDateWriter())); + }) + .put("object", typeHandle -> { + return Optional.of(ColumnMapping.sliceMapping( + VarcharType.createUnboundedVarcharType(), + StandardColumnMappings.varcharReadFunction(VarcharType.createUnboundedVarcharType()), + StandardColumnMappings.varcharWriteFunction(), + PredicatePushdownController.DISABLE_PUSHDOWN)); + }) + .put("array", typeHandle -> { + return Optional.of(ColumnMapping.sliceMapping( + VarcharType.createUnboundedVarcharType(), + StandardColumnMappings.varcharReadFunction(VarcharType.createUnboundedVarcharType()), + StandardColumnMappings.varcharWriteFunction(), + PredicatePushdownController.DISABLE_PUSHDOWN)); + }) + .put("variant", typeHandle -> { + return Optional.of(ColumnMapping.sliceMapping( + VarcharType.createUnboundedVarcharType(), variantReadFunction(), StandardColumnMappings.varcharWriteFunction(), + PredicatePushdownController.FULL_PUSHDOWN)); + }) + .put("varchar", typeHandle -> { + return Optional.of(varcharColumnMapping(typeHandle.getRequiredColumnSize())); + }) + .put("number", typeHandle -> { + int decimalDigits = typeHandle.getRequiredDecimalDigits(); + int precision = typeHandle.getRequiredColumnSize() + Math.max(-decimalDigits, 0); + if (precision > 38) { + return Optional.empty(); + } + return Optional.of(columnMappingPushdown( + StandardColumnMappings.decimalColumnMapping(DecimalType.createDecimalType( + precision, Math.max(decimalDigits, 0)), RoundingMode.UNNECESSARY))); + }) + .buildOrThrow(); + + // Mappings for internal Trino types to JDBC column types + private static final Map STANDARD_WRITE_MAPPINGS = ImmutableMap.builder() + .put("BooleanType", WriteMapping.booleanMapping("boolean", StandardColumnMappings.booleanWriteFunction())) + .put("BigintType", WriteMapping.longMapping("number(19)", StandardColumnMappings.bigintWriteFunction())) + .put("IntegerType", WriteMapping.longMapping("number(10)", StandardColumnMappings.integerWriteFunction())) + .put("SmallintType", WriteMapping.longMapping("number(5)", StandardColumnMappings.smallintWriteFunction())) + .put("TinyintType", WriteMapping.longMapping("number(3)", StandardColumnMappings.tinyintWriteFunction())) + .put("DoubleType", WriteMapping.doubleMapping("double precision", StandardColumnMappings.doubleWriteFunction())) + .put("RealType", WriteMapping.longMapping("real", StandardColumnMappings.realWriteFunction())) + .put("VarbinaryType", WriteMapping.sliceMapping("varbinary", StandardColumnMappings.varbinaryWriteFunction())) + .put("DateType", WriteMapping.longMapping("date", snowFlakeDateWriter())) + .buildOrThrow(); + + private static final Map SNOWFLAKE_WRITE_MAPPINGS = ImmutableMap.builder() + .put("TimeType", type -> { + return WriteMapping.longMapping("time", SnowflakeClient.snowFlaketimeWriter(type)); + }) + .put("ShortTimestampType", type -> { + WriteMapping myMap = SnowflakeClient.snowFlakeTimestampWriter(type); + return myMap; + }) + .put("ShortTimestampWithTimeZoneType", type -> { + WriteMapping myMap = SnowflakeClient.snowFlakeTimestampWithTZWriter(type); + return myMap; + }) + .put("LongTimestampType", type -> { + WriteMapping myMap = SnowflakeClient.snowFlakeTimestampWithTZWriter(type); + return myMap; + }) + .put("LongTimestampWithTimeZoneType", type -> { + WriteMapping myMap = SnowflakeClient.snowFlakeTimestampWithTZWriter(type); + return myMap; + }) + .put("VarcharType", type -> { + WriteMapping myMap = SnowflakeClient.snowFlakeVarCharWriter(type); + return myMap; + }) + .put("CharType", type -> { + WriteMapping myMap = SnowflakeClient.snowFlakeCharWriter(type); + return myMap; + }) + .put("LongDecimalType", type -> { + WriteMapping myMap = SnowflakeClient.snowFlakeDecimalWriter(type); + return myMap; + }) + .put("ShortDecimalType", type -> { + WriteMapping myMap = SnowflakeClient.snowFlakeDecimalWriter(type); + return myMap; + }) + .buildOrThrow(); + + @Inject + public SnowflakeClient(BaseJdbcConfig config, ConnectionFactory connectionFactory, QueryBuilder queryBuilder, + TypeManager typeManager, IdentifierMapping identifierMapping, + RemoteQueryModifier remoteQueryModifier) + { + super("\"", connectionFactory, queryBuilder, config.getJdbcTypesMappedToVarchar(), identifierMapping, remoteQueryModifier, false); + this.jsonType = typeManager.getType(new TypeSignature(StandardTypes.JSON)); + + JdbcTypeHandle bigintTypeHandle = new JdbcTypeHandle(Types.BIGINT, Optional.of("bigint"), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty()); + ConnectorExpressionRewriter connectorExpressionRewriter = JdbcConnectorExpressionRewriterBuilder.newBuilder() + .addStandardRules(this::quoted) + .build(); + + this.aggregateFunctionRewriter = new AggregateFunctionRewriter<>( + connectorExpressionRewriter, + ImmutableSet.>builder() + .add(new ImplementCountAll(bigintTypeHandle)) + .add(new ImplementCount(bigintTypeHandle)) + .add(new ImplementMinMax(false)) + .add(new ImplementSum(SnowflakeClient::toTypeHandle)) + .add(new ImplementAvgFloatingPoint()) + .add(new ImplementAvgDecimal()) + .build()); + } + + @Override + public void abortReadConnection(Connection connection, ResultSet resultSet) + throws SQLException + { + // Abort connection before closing. Without this, the Snowflake driver + // attempts to drain the connection by reading all the results. + connection.abort(directExecutor()); + } + + @Override + public Optional toColumnMapping(ConnectorSession session, Connection connection, JdbcTypeHandle typeHandle) + { + String jdbcTypeName = typeHandle.getJdbcTypeName() + .orElseThrow(() -> new TrinoException(JDBC_ERROR, "Type name is missing: " + typeHandle)); + jdbcTypeName = jdbcTypeName.toLowerCase(Locale.ENGLISH); + int type = typeHandle.getJdbcType(); + + ColumnMapping columnMap = STANDARD_COLUMN_MAPPINGS.get(type); + if (columnMap != null) { + return Optional.of(columnMap); + } + + ColumnMappingFunction columnMappingFunction = SHOWFLAKE_COLUMN_MAPPINGS.get(jdbcTypeName); + if (columnMappingFunction != null) { + return columnMappingFunction.convert(typeHandle); + } + + // Code should never reach here so throw an error. + throw new TrinoException(NOT_SUPPORTED, "SNOWFLAKE_CONNECTOR_COLUMN_TYPE_NOT_SUPPORTED: Unsupported column type(" + type + + "):" + jdbcTypeName); + } + + @Override + public WriteMapping toWriteMapping(ConnectorSession session, Type type) + { + Class myClass = type.getClass(); + String simple = myClass.getSimpleName(); + + WriteMapping writeMapping = STANDARD_WRITE_MAPPINGS.get(simple); + if (writeMapping != null) { + return writeMapping; + } + + WriteMappingFunction writeMappingFunction = SNOWFLAKE_WRITE_MAPPINGS.get(simple); + if (writeMappingFunction != null) { + return writeMappingFunction.convert(type); + } + + log.debug("SnowflakeClient.toWriteMapping: SNOWFLAKE_CONNECTOR_COLUMN_TYPE_NOT_SUPPORTED: Unsupported column type: " + type.getDisplayName() + ", simple:" + simple); + + throw new TrinoException(NOT_SUPPORTED, "SNOWFLAKE_CONNECTOR_COLUMN_TYPE_NOT_SUPPORTED: Unsupported column type: " + type.getDisplayName() + ", simple:" + simple); + } + + @Override + public Optional implementAggregation(ConnectorSession session, AggregateFunction aggregate, Map assignments) + { + // TODO support complex ConnectorExpressions + return aggregateFunctionRewriter.rewrite(session, aggregate, assignments); + } + + private static Optional toTypeHandle(DecimalType decimalType) + { + return Optional.of(new JdbcTypeHandle(Types.NUMERIC, Optional.of("decimal"), Optional.of(decimalType.getPrecision()), Optional.of(decimalType.getScale()), Optional.empty(), Optional.empty())); + } + + @Override + protected Optional> limitFunction() + { + return Optional.of((sql, limit) -> sql + " LIMIT " + limit); + } + + @Override + public boolean isLimitGuaranteed(ConnectorSession session) + { + return true; + } + + private ColumnMapping jsonColumnMapping() + { + return ColumnMapping.sliceMapping( + jsonType, + (resultSet, columnIndex) -> jsonParse(utf8Slice(resultSet.getString(columnIndex))), + StandardColumnMappings.varcharWriteFunction(), + DISABLE_PUSHDOWN); + } + + @Override + public void setColumnType(ConnectorSession session, JdbcTableHandle handle, JdbcColumnHandle column, Type type) + { + throw new TrinoException(NOT_SUPPORTED, "This connector does not support setting column types"); + } + + private static SliceReadFunction variantReadFunction() + { + return (resultSet, columnIndex) -> Slices.utf8Slice(resultSet.getString(columnIndex).replaceAll("^\"|\"$", "")); + } + + private static ColumnMapping columnMappingPushdown(ColumnMapping mapping) + { + if (mapping.getPredicatePushdownController() == PredicatePushdownController.DISABLE_PUSHDOWN) { + log.debug("SnowflakeClient.columnMappingPushdown: NOT_SUPPORTED mapping.getPredicatePushdownController() is DISABLE_PUSHDOWN. Type was " + mapping.getType()); + throw new TrinoException(NOT_SUPPORTED, "mapping.getPredicatePushdownController() is DISABLE_PUSHDOWN. Type was " + mapping.getType()); + } + + return new ColumnMapping(mapping.getType(), mapping.getReadFunction(), mapping.getWriteFunction(), + PredicatePushdownController.FULL_PUSHDOWN); + } + + private static ColumnMapping timeColumnMapping(JdbcTypeHandle typeHandle) + { + int precision = typeHandle.getRequiredDecimalDigits(); + checkArgument((precision <= SNOWFLAKE_MAX_SUPPORTED_TIMESTAMP_PRECISION), + "The max timestamp precision in Snowflake is " + SNOWFLAKE_MAX_SUPPORTED_TIMESTAMP_PRECISION); + return ColumnMapping.longMapping( + TimeType.createTimeType(precision), + (resultSet, columnIndex) -> { + LocalTime time = SNOWFLAKE_TIME_FORMATTER.parse(resultSet.getString(columnIndex), LocalTime::from); + long nanosOfDay = time.toNanoOfDay(); + long picosOfDay = nanosOfDay * Timestamps.PICOSECONDS_PER_NANOSECOND; + return Timestamps.round(picosOfDay, 12 - precision); + }, + timeWriteFunction(precision), + PredicatePushdownController.FULL_PUSHDOWN); + } + + private static LongWriteFunction snowFlaketimeWriter(Type type) + { + TimeType timeType = (TimeType) type; + int precision = timeType.getPrecision(); + return timeWriteFunction(precision); + } + + private static LongWriteFunction timeWriteFunction(int precision) + { + checkArgument(precision <= SNOWFLAKE_MAX_SUPPORTED_TIMESTAMP_PRECISION, "Unsupported precision: %s", precision); + String bindExpression = String.format("CAST(? AS time(%s))", precision); + return new LongWriteFunction() + { + @Override + public String getBindExpression() + { + return bindExpression; + } + + @Override + public void set(PreparedStatement statement, int index, long picosOfDay) + throws SQLException + { + picosOfDay = Timestamps.round(picosOfDay, 12 - precision); + if (picosOfDay == Timestamps.PICOSECONDS_PER_DAY) { + picosOfDay = 0; + } + LocalTime localTime = LocalTime.ofNanoOfDay(picosOfDay / Timestamps.PICOSECONDS_PER_NANOSECOND); + // statement.setObject(.., localTime) would yield incorrect end result for 23:59:59.999000 + statement.setString(index, SNOWFLAKE_TIME_FORMATTER.format(localTime)); + } + }; + } + + private static long toTrinoTime(Time sqlTime) + { + return Timestamps.PICOSECONDS_PER_SECOND * sqlTime.getTime(); + } + + private static ColumnMapping timestampTZColumnMapping(JdbcTypeHandle typeHandle) + { + int precision = typeHandle.getRequiredDecimalDigits(); + String jdbcTypeName = typeHandle.getJdbcTypeName() + .orElseThrow(() -> new TrinoException(JDBC_ERROR, "Type name is missing: " + typeHandle)); + int type = typeHandle.getJdbcType(); + log.debug("timestampTZColumnMapping: jdbcTypeName(%s):%s precision:precision", type, jdbcTypeName, precision); + + if (precision <= 3) { + return ColumnMapping.longMapping(TimestampWithTimeZoneType.createTimestampWithTimeZoneType(precision), + (resultSet, columnIndex) -> { + ZonedDateTime timestamp = (ZonedDateTime) SNOWFLAKE_DATETIME_FORMATTER.parse(resultSet.getString(columnIndex), ZonedDateTime::from); + return DateTimeEncoding.packDateTimeWithZone(timestamp.toInstant().toEpochMilli(), timestamp.getZone().getId()); + }, + timestampWithTZWriter(), PredicatePushdownController.FULL_PUSHDOWN); + } + else { + return ColumnMapping.objectMapping(TimestampWithTimeZoneType.createTimestampWithTimeZoneType(precision), longTimestampWithTimezoneReadFunction(), longTimestampWithTZWriteFunction()); + } + } + + private static ColumnMapping varcharColumnMapping(int varcharLength) + { + VarcharType varcharType = varcharLength <= VarcharType.MAX_LENGTH + ? VarcharType.createVarcharType(varcharLength) + : VarcharType.createUnboundedVarcharType(); + return ColumnMapping.sliceMapping( + varcharType, + StandardColumnMappings.varcharReadFunction(varcharType), + StandardColumnMappings.varcharWriteFunction()); + } + + private static ObjectReadFunction longTimestampWithTimezoneReadFunction() + { + return ObjectReadFunction.of(LongTimestampWithTimeZone.class, (resultSet, columnIndex) -> { + ZonedDateTime timestamp = (ZonedDateTime) SNOWFLAKE_DATETIME_FORMATTER.parse(resultSet.getString(columnIndex), ZonedDateTime::from); + return LongTimestampWithTimeZone.fromEpochSecondsAndFraction(timestamp.toEpochSecond(), + (long) timestamp.getNano() * Timestamps.PICOSECONDS_PER_NANOSECOND, + TimeZoneKey.getTimeZoneKey(timestamp.getZone().getId())); + }); + } + + private static ObjectWriteFunction longTimestampWithTZWriteFunction() + { + return ObjectWriteFunction.of(LongTimestampWithTimeZone.class, (statement, index, value) -> { + long epoMilli = value.getEpochMillis(); + long epoSeconds = Math.floorDiv(epoMilli, Timestamps.MILLISECONDS_PER_SECOND); + long adjNano = Math.floorMod(epoMilli, Timestamps.MILLISECONDS_PER_SECOND) * + Timestamps.NANOSECONDS_PER_MILLISECOND + value.getPicosOfMilli() / Timestamps.PICOSECONDS_PER_NANOSECOND; + ZoneId zone = TimeZoneKey.getTimeZoneKey(value.getTimeZoneKey()).getZoneId(); + Instant timeI = Instant.ofEpochSecond(epoSeconds, adjNano); + statement.setString(index, SNOWFLAKE_DATETIME_FORMATTER.format(ZonedDateTime.ofInstant(timeI, zone))); + }); + } + + private static LongWriteFunction snowFlakeDateTimeWriter() + { + return (statement, index, encodedTimeWithZone) -> { + Instant time = Instant.ofEpochMilli(DateTimeEncoding.unpackMillisUtc(encodedTimeWithZone)); + ZoneId zone = ZoneId.of(DateTimeEncoding.unpackZoneKey(encodedTimeWithZone).getId()); + statement.setString(index, SNOWFLAKE_DATETIME_FORMATTER.format(time.atZone(zone))); + }; + } + + private static WriteMapping snowFlakeDecimalWriter(Type type) + { + DecimalType decimalType = (DecimalType) type; + String dataType = String.format("decimal(%s, %s)", new Object[] { + Integer.valueOf(decimalType.getPrecision()), Integer.valueOf(decimalType.getScale()) + }); + + if (decimalType.isShort()) { + return WriteMapping.longMapping(dataType, StandardColumnMappings.shortDecimalWriteFunction(decimalType)); + } + return WriteMapping.objectMapping(dataType, StandardColumnMappings.longDecimalWriteFunction(decimalType)); + } + + private static LongWriteFunction snowFlakeDateWriter() + { + return (statement, index, day) -> statement.setString(index, SNOWFLAKE_DATE_FORMATTER.format(LocalDate.ofEpochDay(day))); + } + + private static WriteMapping snowFlakeCharWriter(Type type) + { + CharType charType = (CharType) type; + return WriteMapping.sliceMapping("char(" + charType.getLength() + ")", + charWriteFunction(charType)); + } + + private static WriteMapping snowFlakeVarCharWriter(Type type) + { + String dataType; + VarcharType varcharType = (VarcharType) type; + + if (varcharType.isUnbounded()) { + dataType = "varchar"; + } + else { + dataType = "varchar(" + varcharType.getBoundedLength() + ")"; + } + return WriteMapping.sliceMapping(dataType, StandardColumnMappings.varcharWriteFunction()); + } + + private static SliceWriteFunction charWriteFunction(CharType charType) + { + return (statement, index, value) -> statement.setString(index, Chars.padSpaces(value, charType).toStringUtf8()); + } + + private static WriteMapping snowFlakeTimestampWriter(Type type) + { + TimestampType timestampType = (TimestampType) type; + checkArgument((timestampType.getPrecision() <= SNOWFLAKE_MAX_SUPPORTED_TIMESTAMP_PRECISION), + "The max timestamp precision in Snowflake is " + SNOWFLAKE_MAX_SUPPORTED_TIMESTAMP_PRECISION); + + if (timestampType.isShort()) { + return WriteMapping.longMapping( + String.format("timestamp_ntz(%d)", new Object[] {Integer.valueOf(timestampType.getPrecision()) }), + timestampWriteFunction()); + } + return WriteMapping.objectMapping( + String.format("timestamp_ntz(%d)", new Object[] {Integer.valueOf(timestampType.getPrecision()) }), + longTimestampWriter(timestampType.getPrecision())); + } + + private static LongWriteFunction timestampWriteFunction() + { + return (statement, index, value) -> statement.setString(index, + StandardColumnMappings.fromTrinoTimestamp(value).toString()); + } + + private static ObjectWriteFunction longTimestampWriter(int precision) + { + return ObjectWriteFunction.of(LongTimestamp.class, + (statement, index, value) -> statement.setString(index, + SNOWFLAKE_TIMESTAMP_FORMATTER.format(StandardColumnMappings.fromLongTrinoTimestamp(value, + precision)))); + } + + private static WriteMapping snowFlakeTimestampWithTZWriter(Type type) + { + TimestampWithTimeZoneType timeTZType = (TimestampWithTimeZoneType) type; + + checkArgument((timeTZType.getPrecision() <= SNOWFLAKE_MAX_SUPPORTED_TIMESTAMP_PRECISION), + "Max Snowflake precision is is " + SNOWFLAKE_MAX_SUPPORTED_TIMESTAMP_PRECISION); + if (timeTZType.isShort()) { + return WriteMapping.longMapping(String.format("timestamp_tz(%d)", + new Object[] {Integer.valueOf(timeTZType.getPrecision()) }), + timestampWithTZWriter()); + } + return WriteMapping.objectMapping( + String.format("timestamp_tz(%d)", new Object[] {Integer.valueOf(timeTZType.getPrecision()) }), + longTimestampWithTZWriteFunction()); + } + + private static LongWriteFunction timestampWithTZWriter() + { + return (statement, index, encodedTimeWithZone) -> { + Instant timeI = Instant.ofEpochMilli(DateTimeEncoding.unpackMillisUtc(encodedTimeWithZone)); + ZoneId zone = ZoneId.of(DateTimeEncoding.unpackZoneKey(encodedTimeWithZone).getId()); + statement.setString(index, SNOWFLAKE_DATETIME_FORMATTER.format(timeI.atZone(zone))); + }; + } + + private static ObjectReadFunction longTimestampWithTZReadFunction() + { + return ObjectReadFunction.of(LongTimestampWithTimeZone.class, (resultSet, columnIndex) -> { + ZonedDateTime timestamp = SNOWFLAKE_DATETIME_FORMATTER.parse(resultSet.getString(columnIndex), ZonedDateTime::from); + return LongTimestampWithTimeZone.fromEpochSecondsAndFraction(timestamp.toEpochSecond(), + timestamp.getNano() * Timestamps.PICOSECONDS_PER_NANOSECOND, TimeZoneKey.getTimeZoneKey(timestamp.getZone().getId())); + }); + } + + private static ObjectReadFunction longTimestampReader() + { + return ObjectReadFunction.of(LongTimestamp.class, (resultSet, columnIndex) -> { + Calendar calendar = new GregorianCalendar(UTC_TZ, Locale.ENGLISH); + calendar.setTime(new Date(0)); + Timestamp ts = resultSet.getTimestamp(columnIndex, calendar); + long epochMillis = ts.getTime(); + int nanosInTheSecond = ts.getNanos(); + int nanosInTheMilli = nanosInTheSecond % Timestamps.NANOSECONDS_PER_MILLISECOND; + long micro = epochMillis * Timestamps.MICROSECONDS_PER_MILLISECOND + (nanosInTheMilli / Timestamps.NANOSECONDS_PER_MICROSECOND); + int picosOfMicro = nanosInTheMilli % 1000 * 1000; + return new LongTimestamp(micro, picosOfMicro); + }); + } + + private static ColumnMapping timestampColumnMapping(JdbcTypeHandle typeHandle) + { + int precision = typeHandle.getRequiredDecimalDigits(); + String jdbcTypeName = typeHandle.getJdbcTypeName() + .orElseThrow(() -> new TrinoException(JDBC_ERROR, "Type name is missing: " + typeHandle)); + int type = typeHandle.getJdbcType(); + log.debug("timestampColumnMapping: jdbcTypeName(%s):%s precision:%s", type, jdbcTypeName, precision); + + // <= 6 fits into a long + if (precision <= 6) { + return ColumnMapping.longMapping( + (Type) TimestampType.createTimestampType(precision), (resultSet, columnIndex) -> + StandardColumnMappings.toTrinoTimestamp(TimestampType.createTimestampType(precision), + toLocalDateTime(resultSet, columnIndex)), + timestampWriteFunction()); + } + + // Too big. Put it in an object + return ColumnMapping.objectMapping( + (Type) TimestampType.createTimestampType(precision), + longTimestampReader(), + longTimestampWriter(precision)); + } + + private static LocalDateTime toLocalDateTime(ResultSet resultSet, int columnIndex) + throws SQLException + { + Calendar calendar = new GregorianCalendar(UTC_TZ, Locale.ENGLISH); + calendar.setTime(new Date(0)); + Timestamp ts = resultSet.getTimestamp(columnIndex, calendar); + return LocalDateTime.ofInstant(Instant.ofEpochMilli(ts.getTime()), ZoneOffset.UTC); + } +} diff --git a/plugin/trino-snowflake/src/main/java/io/trino/plugin/snowflake/SnowflakeClientModule.java b/plugin/trino-snowflake/src/main/java/io/trino/plugin/snowflake/SnowflakeClientModule.java new file mode 100644 index 000000000000..19fc35847191 --- /dev/null +++ b/plugin/trino-snowflake/src/main/java/io/trino/plugin/snowflake/SnowflakeClientModule.java @@ -0,0 +1,95 @@ +/* + * 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 io.trino.plugin.snowflake; + +import com.google.inject.Binder; +import com.google.inject.Module; +import com.google.inject.Provides; +import com.google.inject.Scopes; +import com.google.inject.Singleton; +import io.trino.plugin.jdbc.BaseJdbcConfig; +import io.trino.plugin.jdbc.ConnectionFactory; +import io.trino.plugin.jdbc.DriverConnectionFactory; +import io.trino.plugin.jdbc.ForBaseJdbc; +import io.trino.plugin.jdbc.JdbcClient; +import io.trino.plugin.jdbc.TypeHandlingJdbcConfig; +import io.trino.plugin.jdbc.credential.CredentialProvider; +import io.trino.spi.TrinoException; +import net.snowflake.client.jdbc.SnowflakeDriver; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Properties; + +import static io.airlift.configuration.ConfigBinder.configBinder; +import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; + +public class SnowflakeClientModule + implements Module +{ + @Override + public void configure(Binder binder) + { + binder.bind(JdbcClient.class).annotatedWith(ForBaseJdbc.class).to(SnowflakeClient.class).in(Scopes.SINGLETON); + configBinder(binder).bindConfig(SnowflakeConfig.class); + configBinder(binder).bindConfig(TypeHandlingJdbcConfig.class); + } + + @Singleton + @Provides + @ForBaseJdbc + public ConnectionFactory getConnectionFactory(BaseJdbcConfig baseJdbcConfig, SnowflakeConfig snowflakeConfig, CredentialProvider credentialProvider) + throws MalformedURLException + { + Properties properties = new Properties(); + snowflakeConfig.getAccount().ifPresent(account -> properties.setProperty("account", account)); + snowflakeConfig.getDatabase().ifPresent(database -> properties.setProperty("db", database)); + snowflakeConfig.getRole().ifPresent(role -> properties.setProperty("role", role)); + snowflakeConfig.getWarehouse().ifPresent(warehouse -> properties.setProperty("warehouse", warehouse)); + + // Set the expected date/time formatting we expect for our plugin to parse + properties.setProperty("TIMESTAMP_OUTPUT_FORMAT", "YYYY-MM-DD\"T\"HH24:MI:SS.FF9TZH:TZM"); + properties.setProperty("TIMESTAMP_NTZ_OUTPUT_FORMAT", "YYYY-MM-DD\"T\"HH24:MI:SS.FF9TZH:TZM"); + properties.setProperty("TIMESTAMP_TZ_OUTPUT_FORMAT", "YYYY-MM-DD\"T\"HH24:MI:SS.FF9TZH:TZM"); + properties.setProperty("TIMESTAMP_LTZ_OUTPUT_FORMAT", "YYYY-MM-DD\"T\"HH24:MI:SS.FF9TZH:TZM"); + properties.setProperty("TIME_OUTPUT_FORMAT", "HH24:MI:SS.FF9"); + snowflakeConfig.getTimestampNoTimezoneAsUTC().ifPresent(as_utc -> properties.setProperty("JDBC_TREAT_TIMESTAMP_NTZ_AS_UTC", as_utc ? "true" : "false")); + + // Support for Corporate proxies + if (snowflakeConfig.getHTTPProxy().isPresent()) { + String proxy = snowflakeConfig.getHTTPProxy().get(); + + URL url = new URL(proxy); + + properties.setProperty("useProxy", "true"); + properties.setProperty("proxyHost", url.getHost()); + properties.setProperty("proxyPort", Integer.toString(url.getPort())); + properties.setProperty("proxyProtocol", url.getProtocol()); + + String userInfo = url.getUserInfo(); + if (userInfo != null) { + String[] usernamePassword = userInfo.split(":", 2); + + if (usernamePassword.length != 2) { + throw new TrinoException(NOT_SUPPORTED, "Improper snowflake.http_proxy. username:password@ is optional but what was entered was not correct"); + } + + properties.setProperty("proxyUser", usernamePassword[0]); + properties.setProperty("proxyPassword", usernamePassword[1]); + } + } + + return new DriverConnectionFactory(new SnowflakeDriver(), baseJdbcConfig.getConnectionUrl(), properties, credentialProvider); + } +} diff --git a/plugin/trino-snowflake/src/main/java/io/trino/plugin/snowflake/SnowflakeConfig.java b/plugin/trino-snowflake/src/main/java/io/trino/plugin/snowflake/SnowflakeConfig.java new file mode 100644 index 000000000000..6dbf12520177 --- /dev/null +++ b/plugin/trino-snowflake/src/main/java/io/trino/plugin/snowflake/SnowflakeConfig.java @@ -0,0 +1,100 @@ +/* + * 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 io.trino.plugin.snowflake; + +import io.airlift.configuration.Config; + +import java.util.Optional; + +public class SnowflakeConfig +{ + private String account; + private String database; + private String role; + private String warehouse; + private Boolean timestampNoTimezoneAsUTC; + private String httpProxy; + + public Optional getAccount() + { + return Optional.ofNullable(account); + } + + @Config("snowflake.account") + public SnowflakeConfig setAccount(String account) + { + this.account = account; + return this; + } + + public Optional getDatabase() + { + return Optional.ofNullable(database); + } + + @Config("snowflake.database") + public SnowflakeConfig setDatabase(String database) + { + this.database = database; + return this; + } + + public Optional getRole() + { + return Optional.ofNullable(role); + } + + @Config("snowflake.role") + public SnowflakeConfig setRole(String role) + { + this.role = role; + return this; + } + + public Optional getWarehouse() + { + return Optional.ofNullable(warehouse); + } + + @Config("snowflake.warehouse") + public SnowflakeConfig setWarehouse(String warehouse) + { + this.warehouse = warehouse; + return this; + } + + public Optional getTimestampNoTimezoneAsUTC() + { + return Optional.ofNullable(timestampNoTimezoneAsUTC); + } + + @Config("snowflake.timestamp-no-timezone-as-utc") + public SnowflakeConfig setTimestampNoTimezoneAsUTC(Boolean timestampNoTimezoneAsUTC) + { + this.timestampNoTimezoneAsUTC = timestampNoTimezoneAsUTC; + return this; + } + + public Optional getHTTPProxy() + { + return Optional.ofNullable(httpProxy); + } + + @Config("snowflake.http-proxy") + public SnowflakeConfig setHTTPProxy(String httpProxy) + { + this.httpProxy = httpProxy; + return this; + } +} diff --git a/plugin/trino-snowflake/src/main/java/io/trino/plugin/snowflake/SnowflakePlugin.java b/plugin/trino-snowflake/src/main/java/io/trino/plugin/snowflake/SnowflakePlugin.java new file mode 100644 index 000000000000..728264d29778 --- /dev/null +++ b/plugin/trino-snowflake/src/main/java/io/trino/plugin/snowflake/SnowflakePlugin.java @@ -0,0 +1,25 @@ +/* + * 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 io.trino.plugin.snowflake; + +import io.trino.plugin.jdbc.JdbcPlugin; + +public class SnowflakePlugin + extends JdbcPlugin +{ + public SnowflakePlugin() + { + super("snowflake", new SnowflakeClientModule()); + } +} diff --git a/plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/BaseSnowflakeConnectorTest.java b/plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/BaseSnowflakeConnectorTest.java new file mode 100644 index 000000000000..e37fd36566c8 --- /dev/null +++ b/plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/BaseSnowflakeConnectorTest.java @@ -0,0 +1,622 @@ +/* + * 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 io.trino.plugin.snowflake; + +import io.trino.Session; +import io.trino.plugin.jdbc.BaseJdbcConnectorTest; +import io.trino.testing.MaterializedResult; +import io.trino.testing.TestingConnectorBehavior; +import io.trino.testing.sql.TestTable; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; + +import java.util.Optional; +import java.util.OptionalInt; + +import static com.google.common.base.Strings.nullToEmpty; +import static io.trino.plugin.snowflake.TestingSnowflakeServer.TEST_SCHEMA; +import static io.trino.spi.connector.ConnectorMetadata.MODIFYING_ROWS_MESSAGE; +import static io.trino.spi.type.VarcharType.VARCHAR; +import static io.trino.testing.MaterializedResult.resultBuilder; +import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_CREATE_TABLE; +import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_CREATE_TABLE_WITH_DATA; +import static io.trino.testing.TestingNames.randomNameSuffix; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.abort; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; + +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) +public abstract class BaseSnowflakeConnectorTest + extends BaseJdbcConnectorTest +{ + protected TestingSnowflakeServer server; + + @Override + protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) + { + switch (connectorBehavior) { + case SUPPORTS_AGGREGATION_PUSHDOWN: + case SUPPORTS_TOPN_PUSHDOWN: + case SUPPORTS_LIMIT_PUSHDOWN: + return false; + case SUPPORTS_COMMENT_ON_COLUMN: + case SUPPORTS_ADD_COLUMN_WITH_COMMENT: + case SUPPORTS_COMMENT_ON_TABLE: + case SUPPORTS_CREATE_TABLE_WITH_TABLE_COMMENT: + case SUPPORTS_CREATE_TABLE_WITH_COLUMN_COMMENT: + case SUPPORTS_SET_COLUMN_TYPE: + return false; + case SUPPORTS_DROP_FIELD: + case SUPPORTS_ROW_TYPE: + case SUPPORTS_ARRAY: + return false; + default: + return super.hasBehavior(connectorBehavior); + } + } + + @Override + protected TestTable createTableWithDefaultColumns() + { + return new TestTable( + onRemoteDatabase(), + TEST_SCHEMA, + "(col_required BIGINT NOT NULL," + + "col_nullable BIGINT," + + "col_default BIGINT DEFAULT 43," + + "col_nonnull_default BIGINT NOT NULL DEFAULT 42," + + "col_required2 BIGINT NOT NULL)"); + } + + @Override + protected TestTable createTableWithUnsupportedColumn() + { + return new TestTable( + onRemoteDatabase(), + TEST_SCHEMA, + "(one bigint, two decimal(38,0), three varchar(10))"); + } + + @Override + protected Optional filterDataMappingSmokeTestData(DataMappingTestSetup dataMappingTestSetup) + { + String typeName = dataMappingTestSetup.getTrinoTypeName(); + // TODO: Test fails with these types + // Error: No result for query: SELECT row_id FROM test_data_mapping_smoke_real_3u8xo6hp59 WHERE rand() = 42 OR value = REAL '567.123' + // In the testDataMappingSmokeTestDataProvider(), the type sampleValueLiteral of type real should be "DOUBLE" rather than "REAL". + if (typeName.equals("real")) { + return Optional.empty(); + } + // Error: Failed to insert data: SQL compilation error: error line 1 at position 130 + if (typeName.equals("time") + || typeName.equals("time(6)") + || typeName.equals("timestamp(6)")) { + return Optional.empty(); + } + // Error: not equal + if (typeName.equals("char(3)")) { + return Optional.empty(); + } + return Optional.of(dataMappingTestSetup); + } + + @Override + protected boolean isColumnNameRejected(Exception exception, String columnName, boolean delimited) + { + return nullToEmpty(exception.getMessage()).matches(".*(Incorrect column name).*"); + } + + @Override + protected MaterializedResult getDescribeOrdersResult() + { + // Override this test because the type of row "shippriority" should be bigint rather than integer for snowflake case + return resultBuilder(getSession(), VARCHAR, VARCHAR, VARCHAR, VARCHAR) + .row("orderkey", "bigint", "", "") + .row("custkey", "bigint", "", "") + .row("orderstatus", "varchar(1)", "", "") + .row("totalprice", "double", "", "") + .row("orderdate", "date", "", "") + .row("orderpriority", "varchar(15)", "", "") + .row("clerk", "varchar(15)", "", "") + .row("shippriority", "bigint", "", "") + .row("comment", "varchar(79)", "", "") + .build(); + } + + @Test + @Override + public void testShowColumns() + { + assertThat(query("SHOW COLUMNS FROM orders")).matches(getDescribeOrdersResult()); + } + + @Test + public void testViews() + { + String tableName = "test_view_" + randomNameSuffix(); + onRemoteDatabase().execute("CREATE OR REPLACE VIEW tpch." + tableName + " AS SELECT * FROM tpch.orders"); + assertQuery("SELECT orderkey FROM " + tableName, "SELECT orderkey FROM orders"); + onRemoteDatabase().execute("DROP VIEW IF EXISTS tpch." + tableName); + } + + @Test + @Override + public void testShowCreateTable() + { + // Override this test because the type of row "shippriority" should be bigint rather than integer for snowflake case + assertThat(computeActual("SHOW CREATE TABLE orders").getOnlyValue()) + .isEqualTo("CREATE TABLE snowflake.tpch.orders (\n" + + " orderkey bigint,\n" + + " custkey bigint,\n" + + " orderstatus varchar(1),\n" + + " totalprice double,\n" + + " orderdate date,\n" + + " orderpriority varchar(15),\n" + + " clerk varchar(15),\n" + + " shippriority bigint,\n" + + " comment varchar(79)\n" + + ")\n" + + "COMMENT ''"); + } + + @Test + @Override + public void testAddNotNullColumn() + { + assertThatThrownBy(super::testAddNotNullColumn) + .isInstanceOf(AssertionError.class) + .hasMessage("Unexpected failure when adding not null column"); + } + + @Test + @Override + public void testCharVarcharComparison() + { + assertThatThrownBy(super::testCharVarcharComparison) + .hasMessageContaining("For query") + .hasMessageContaining("Actual rows") + .hasMessageContaining("Expected rows"); + } + + @Test + @Override + public void testCountDistinctWithStringTypes() + { + abort("TODO"); + } + + @Test + @Override + public void testInsertInPresenceOfNotSupportedColumn() + { + abort("TODO"); + } + + @Test + @Override + public void testAggregationPushdown() + { + abort("TODO"); + } + + @Test + @Override + public void testDistinctAggregationPushdown() + { + abort("TODO"); + } + + @Test + @Override + public void testNumericAggregationPushdown() + { + abort("TODO"); + } + + @Test + @Override + public void testLimitPushdown() + { + abort("TODO"); + } + + @Test + @Override + public void testInsertIntoNotNullColumn() + { + // TODO: java.lang.UnsupportedOperationException: This method should be overridden + assertThatThrownBy(super::testInsertIntoNotNullColumn); + } + + @Test + @Override + public void testDeleteWithLike() + { + assertThatThrownBy(super::testDeleteWithLike) + .hasStackTraceContaining("TrinoException: " + MODIFYING_ROWS_MESSAGE); + } + + @Test + @Override + public void testCreateTableAsSelect() + { + String tableName = "test_ctas" + randomNameSuffix(); + if (!hasBehavior(SUPPORTS_CREATE_TABLE_WITH_DATA)) { + assertQueryFails("CREATE TABLE IF NOT EXISTS " + tableName + " AS SELECT name, regionkey FROM nation", "This connector does not support creating tables with data"); + return; + } + assertUpdate("CREATE TABLE IF NOT EXISTS " + tableName + " AS SELECT name, regionkey FROM nation", "SELECT count(*) FROM nation"); + assertTableColumnNames(tableName, "name", "regionkey"); + + assertEquals(getTableComment(getSession().getCatalog().orElseThrow(), getSession().getSchema().orElseThrow(), tableName), ""); + assertUpdate("DROP TABLE " + tableName); + + // Some connectors support CREATE TABLE AS but not the ordinary CREATE TABLE. Let's test CTAS IF NOT EXISTS with a table that is guaranteed to exist. + assertUpdate("CREATE TABLE IF NOT EXISTS nation AS SELECT nationkey, regionkey FROM nation", 0); + assertTableColumnNames("nation", "nationkey", "name", "regionkey", "comment"); + + assertCreateTableAsSelect( + "SELECT nationkey, name, regionkey FROM nation", + "SELECT count(*) FROM nation"); + + assertCreateTableAsSelect( + "SELECT mktsegment, sum(acctbal) x FROM customer GROUP BY mktsegment", + "SELECT count(DISTINCT mktsegment) FROM customer"); + + assertCreateTableAsSelect( + "SELECT count(*) x FROM nation JOIN region ON nation.regionkey = region.regionkey", + "SELECT 1"); + + assertCreateTableAsSelect( + "SELECT nationkey FROM nation ORDER BY nationkey LIMIT 10", + "SELECT 10"); + + assertCreateTableAsSelect( + "SELECT * FROM nation WITH DATA", + "SELECT * FROM nation", + "SELECT count(*) FROM nation"); + + assertCreateTableAsSelect( + "SELECT * FROM nation WITH NO DATA", + "SELECT * FROM nation LIMIT 0", + "SELECT 0"); + + // Tests for CREATE TABLE with UNION ALL: exercises PushTableWriteThroughUnion optimizer + + assertCreateTableAsSelect( + "SELECT name, nationkey, regionkey FROM nation WHERE nationkey % 2 = 0 UNION ALL " + + "SELECT name, nationkey, regionkey FROM nation WHERE nationkey % 2 = 1", + "SELECT name, nationkey, regionkey FROM nation", + "SELECT count(*) FROM nation"); + + assertCreateTableAsSelect( + Session.builder(getSession()).setSystemProperty("redistribute_writes", "true").build(), + "SELECT CAST(nationkey AS BIGINT) nationkey, regionkey FROM nation UNION ALL " + + "SELECT 1234567890, 123", + "SELECT nationkey, regionkey FROM nation UNION ALL " + + "SELECT 1234567890, 123", + "SELECT count(*) + 1 FROM nation"); + + assertCreateTableAsSelect( + Session.builder(getSession()).setSystemProperty("redistribute_writes", "false").build(), + "SELECT CAST(nationkey AS BIGINT) nationkey, regionkey FROM nation UNION ALL " + + "SELECT 1234567890, 123", + "SELECT nationkey, regionkey FROM nation UNION ALL " + + "SELECT 1234567890, 123", + "SELECT count(*) + 1 FROM nation"); + + // TODO: BigQuery throws table not found at BigQueryClient.insert if we reuse the same table name + tableName = "test_ctas" + randomNameSuffix(); + assertExplainAnalyze("EXPLAIN ANALYZE CREATE TABLE " + tableName + " AS SELECT name FROM nation"); + assertQuery("SELECT * from " + tableName, "SELECT name FROM nation"); + assertUpdate("DROP TABLE " + tableName); + } + + @Test + @Override + public void testCreateTable() + { + String tableName = "test_create_" + randomNameSuffix(); + if (!hasBehavior(SUPPORTS_CREATE_TABLE)) { + assertQueryFails("CREATE TABLE " + tableName + " (a bigint, b double, c varchar(50))", "This connector does not support creating tables"); + return; + } + + assertThat(computeActual("SHOW TABLES").getOnlyColumnAsSet()) // prime the cache, if any + .doesNotContain(tableName); + assertUpdate("CREATE TABLE " + tableName + " (a bigint, b double, c varchar(50))"); + assertTrue(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(computeActual("SHOW TABLES").getOnlyColumnAsSet()) + .contains(tableName); + assertTableColumnNames(tableName, "a", "b", "c"); + assertEquals(getTableComment(getSession().getCatalog().orElseThrow(), getSession().getSchema().orElseThrow(), tableName), ""); + + assertUpdate("DROP TABLE " + tableName); + assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + assertThat(computeActual("SHOW TABLES").getOnlyColumnAsSet()) + .doesNotContain(tableName); + + assertQueryFails("CREATE TABLE " + tableName + " (a bad_type)", ".* Unknown type 'bad_type' for column 'a'"); + assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + + // TODO (https://github.com/trinodb/trino/issues/5901) revert to longer name when Oracle version is updated + tableName = "test_cr_not_exists_" + randomNameSuffix(); + assertUpdate("CREATE TABLE " + tableName + " (a bigint, b varchar(50), c double)"); + assertTrue(getQueryRunner().tableExists(getSession(), tableName)); + assertTableColumnNames(tableName, "a", "b", "c"); + + assertUpdate("CREATE TABLE IF NOT EXISTS " + tableName + " (d bigint, e varchar(50))"); + assertTrue(getQueryRunner().tableExists(getSession(), tableName)); + assertTableColumnNames(tableName, "a", "b", "c"); + + assertUpdate("DROP TABLE " + tableName); + assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + + // Test CREATE TABLE LIKE + tableName = "test_create_orig_" + randomNameSuffix(); + assertUpdate("CREATE TABLE " + tableName + " (a bigint, b double, c varchar(50))"); + assertTrue(getQueryRunner().tableExists(getSession(), tableName)); + assertTableColumnNames(tableName, "a", "b", "c"); + + String tableNameLike = "test_create_like_" + randomNameSuffix(); + assertUpdate("CREATE TABLE " + tableNameLike + " (LIKE " + tableName + ", d bigint, e varchar(50))"); + assertTrue(getQueryRunner().tableExists(getSession(), tableNameLike)); + assertTableColumnNames(tableNameLike, "a", "b", "c", "d", "e"); + + assertUpdate("DROP TABLE " + tableName); + assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + + assertUpdate("DROP TABLE " + tableNameLike); + assertFalse(getQueryRunner().tableExists(getSession(), tableNameLike)); + } + + @Test + @Override + public void testNativeQueryCreateStatement() + { + abort("TODO"); + } + + @Test + @Override + public void testNativeQueryInsertStatementTableExists() + { + abort("TODO"); + } + + @Test + @Override + public void testNativeQuerySelectUnsupportedType() + { + abort("TODO"); + } + + @Test + @Override + public void testCreateTableWithLongColumnName() + { + String tableName = "test_long_column" + randomNameSuffix(); + String baseColumnName = "col"; + + int maxLength = maxColumnNameLength() + // Assume 2^16 is enough for most use cases. Add a bit more to ensure 2^16 isn't actual limit. + .orElse(65536 + 5); + + String validColumnName = baseColumnName + "z".repeat(maxLength - baseColumnName.length()); + assertUpdate("CREATE TABLE " + tableName + " (" + validColumnName + " bigint)"); + assertTrue(columnExists(tableName, validColumnName)); + assertUpdate("DROP TABLE " + tableName); + + if (maxColumnNameLength().isEmpty()) { + return; + } +// TODO: Expecting code to raise a throwable. +// String invalidColumnName = validColumnName + "z"; +// assertThatThrownBy(() -> assertUpdate("CREATE TABLE " + tableName + " (" + invalidColumnName + " bigint)")) +// .satisfies(this::verifyColumnNameLengthFailurePermissible); + assertFalse(getQueryRunner().tableExists(getSession(), tableName)); + } + + @Test + @Override + public void testCreateTableWithLongTableName() + { + // TODO: Find the maximum table name length in Snowflake and enable this test. + abort("TODO"); + } + + @Override + protected OptionalInt maxColumnNameLength() + { + return OptionalInt.of(251); + } + + @Test + @Override + public void testAlterTableAddLongColumnName() + { + String tableName = "test_long_column" + randomNameSuffix(); + assertUpdate("CREATE TABLE " + tableName + " AS SELECT 123 x", 1); + + String baseColumnName = "col"; + int maxLength = maxColumnNameLength() + // Assume 2^16 is enough for most use cases. Add a bit more to ensure 2^16 isn't actual limit. + .orElse(65536 + 5); + + String validTargetColumnName = baseColumnName + "z".repeat(maxLength - baseColumnName.length()); + assertUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + validTargetColumnName + " int"); + assertTrue(getQueryRunner().tableExists(getSession(), tableName)); + assertQuery("SELECT x FROM " + tableName, "VALUES 123"); + assertUpdate("DROP TABLE " + tableName); + + if (maxColumnNameLength().isEmpty()) { + return; + } + + assertUpdate("CREATE TABLE " + tableName + " AS SELECT 123 x", 1); +// TODO: Expecting code to raise a throwable. +// String invalidTargetColumnName = validTargetColumnName + "z"; +// assertThatThrownBy(() -> assertUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + invalidTargetColumnName + " int")) +// .satisfies(this::verifyColumnNameLengthFailurePermissible); + assertQuery("SELECT x FROM " + tableName, "VALUES 123"); + } + + @Test + @Override + public void testAlterTableRenameColumnToLongName() + { + String tableName = "test_long_column" + randomNameSuffix(); + assertUpdate("CREATE TABLE " + tableName + " AS SELECT 123 x", 1); + + String baseColumnName = "col"; + int maxLength = maxColumnNameLength() + // Assume 2^16 is enough for most use cases. Add a bit more to ensure 2^16 isn't actual limit. + .orElse(65536 + 5); + + String validTargetColumnName = baseColumnName + "z".repeat(maxLength - baseColumnName.length()); + assertUpdate("ALTER TABLE " + tableName + " RENAME COLUMN x TO " + validTargetColumnName); + assertQuery("SELECT " + validTargetColumnName + " FROM " + tableName, "VALUES 123"); + assertUpdate("DROP TABLE " + tableName); + + if (maxColumnNameLength().isEmpty()) { + return; + } + + assertUpdate("CREATE TABLE " + tableName + " AS SELECT 123 x", 1); +// TODO: Expecting code to raise a throwable. +// String invalidTargetTableName = validTargetColumnName + "z"; +// assertThatThrownBy(() -> assertUpdate("ALTER TABLE " + tableName + " RENAME COLUMN x TO " + invalidTargetTableName)) +// .satisfies(this::verifyColumnNameLengthFailurePermissible); + assertQuery("SELECT x FROM " + tableName, "VALUES 123"); + } + + @Test + @Override + public void testCreateSchemaWithLongName() + { + // TODO: Find the maximum table schema length in Snowflake and enable this test. + abort("TODO"); + } + + @Test + @Override + public void testInsertArray() + { + // Snowflake does not support this feature. + abort("Not supported"); + } + + @Test + @Override + public void testInsertRowConcurrently() + { + abort("TODO: Connection is already closed"); + } + + @Test + @Override + public void testNativeQueryColumnAlias() + { + abort("TODO: Table function system.query not registered"); + } + + @Test + @Override + public void testNativeQueryColumnAliasNotFound() + { + abort("TODO: Table function system.query not registered"); + } + + @Test + @Override + public void testNativeQueryIncorrectSyntax() + { + abort("TODO"); + } + + @Test + @Override + public void testNativeQueryInsertStatementTableDoesNotExist() + { + abort("TODO"); + } + + @Test + @Override + public void testNativeQueryParameters() + { + abort("TODO"); + } + + @Test + @Override + public void testNativeQuerySelectFromNation() + { + abort("TODO"); + } + + @Test + @Override + public void testNativeQuerySelectFromTestTable() + { + abort("TODO"); + } + + @Test + @Override + public void testNativeQuerySimple() + { + abort("TODO"); + } + + @Test + @Override + public void testRenameSchemaToLongName() + { + // TODO: Find the maximum table schema length in Snowflake and enable this test. + abort("TODO"); + } + + @Test + @Override + public void testRenameTableToLongTableName() + { + // TODO: Find the maximum table length in Snowflake and enable this test. + abort("TODO"); + } + + @Test + @Override + public void testCharTrailingSpace() + { + assertThatThrownBy(super::testCharVarcharComparison) + .hasMessageContaining("For query") + .hasMessageContaining("Actual rows") + .hasMessageContaining("Expected rows"); + } + + @Test + @Override + public void testDescribeTable() + { + assertThat(query("DESCRIBE orders")).matches(getDescribeOrdersResult()); + } +} diff --git a/plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/SnowflakeQueryRunner.java b/plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/SnowflakeQueryRunner.java new file mode 100644 index 000000000000..a50debaf003b --- /dev/null +++ b/plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/SnowflakeQueryRunner.java @@ -0,0 +1,97 @@ +/* + * 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 io.trino.plugin.snowflake; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.airlift.log.Logger; +import io.trino.Session; +import io.trino.plugin.tpch.TpchPlugin; +import io.trino.testing.DistributedQueryRunner; +import io.trino.tpch.TpchTable; + +import java.util.HashMap; +import java.util.Map; + +import static io.airlift.testing.Closeables.closeAllSuppress; +import static io.trino.plugin.tpch.TpchMetadata.TINY_SCHEMA_NAME; +import static io.trino.testing.QueryAssertions.copyTpchTables; +import static io.trino.testing.TestingSession.testSessionBuilder; + +public final class SnowflakeQueryRunner +{ + public static final String TPCH_SCHEMA = "tpch"; + + private SnowflakeQueryRunner() {} + + public static DistributedQueryRunner createSnowflakeQueryRunner( + TestingSnowflakeServer server, + Map extraProperties, + Map connectorProperties, + Iterable> tables) + throws Exception + { + DistributedQueryRunner queryRunner = DistributedQueryRunner.builder(createSession()) + .setExtraProperties(extraProperties) + .build(); + try { + queryRunner.installPlugin(new TpchPlugin()); + queryRunner.createCatalog("tpch", "tpch"); + + connectorProperties = new HashMap<>(ImmutableMap.copyOf(connectorProperties)); + connectorProperties.putIfAbsent("connection-url", TestingSnowflakeServer.TEST_URL); + connectorProperties.putIfAbsent("connection-user", TestingSnowflakeServer.TEST_USER); + connectorProperties.putIfAbsent("connection-password", TestingSnowflakeServer.TEST_PASSWORD); + connectorProperties.putIfAbsent("snowflake.database", TestingSnowflakeServer.TEST_DATABASE); + connectorProperties.putIfAbsent("snowflake.role", TestingSnowflakeServer.TEST_ROLE); + connectorProperties.putIfAbsent("snowflake.warehouse", TestingSnowflakeServer.TEST_WAREHOUSE); + if (TestingSnowflakeServer.TEST_PROXY != null) { + connectorProperties.putIfAbsent("snowflake.httpProxy", TestingSnowflakeServer.TEST_PROXY); + } + + queryRunner.installPlugin(new SnowflakePlugin()); + queryRunner.createCatalog("snowflake", "snowflake", connectorProperties); + + copyTpchTables(queryRunner, "tpch", TINY_SCHEMA_NAME, createSession(), tables); + + return queryRunner; + } + catch (Throwable e) { + closeAllSuppress(e, queryRunner); + throw e; + } + } + + public static Session createSession() + { + return testSessionBuilder() + .setCatalog("snowflake") + .setSchema(TPCH_SCHEMA) + .build(); + } + + public static void main(String[] args) + throws Exception + { + DistributedQueryRunner queryRunner = createSnowflakeQueryRunner( + new TestingSnowflakeServer(), + ImmutableMap.of("http-server.http.port", "8080"), + ImmutableMap.of(), + ImmutableList.of()); + + Logger log = Logger.get(SnowflakeQueryRunner.class); + log.info("======== SERVER STARTED ========"); + log.info("\n====\n%s\n====", queryRunner.getCoordinator().getBaseUrl()); + } +} diff --git a/plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/TestSnowflakeClient.java b/plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/TestSnowflakeClient.java new file mode 100644 index 000000000000..b743314af763 --- /dev/null +++ b/plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/TestSnowflakeClient.java @@ -0,0 +1,153 @@ +/* + * 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 io.trino.plugin.snowflake; + +import io.trino.plugin.base.mapping.DefaultIdentifierMapping; +import io.trino.plugin.jdbc.BaseJdbcConfig; +import io.trino.plugin.jdbc.ColumnMapping; +import io.trino.plugin.jdbc.DefaultQueryBuilder; +import io.trino.plugin.jdbc.JdbcClient; +import io.trino.plugin.jdbc.JdbcColumnHandle; +import io.trino.plugin.jdbc.JdbcExpression; +import io.trino.plugin.jdbc.JdbcTypeHandle; +import io.trino.plugin.jdbc.logging.RemoteQueryModifier; +import io.trino.spi.connector.AggregateFunction; +import io.trino.spi.connector.ColumnHandle; +import io.trino.spi.expression.ConnectorExpression; +import io.trino.spi.expression.Variable; +import org.testng.annotations.Test; + +import java.sql.Types; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static io.trino.spi.type.BigintType.BIGINT; +import static io.trino.spi.type.BooleanType.BOOLEAN; +import static io.trino.spi.type.DoubleType.DOUBLE; +import static io.trino.testing.TestingConnectorSession.SESSION; +import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; +import static org.assertj.core.api.Assertions.assertThat; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +public class TestSnowflakeClient +{ + private static final JdbcColumnHandle BIGINT_COLUMN = + JdbcColumnHandle.builder() + .setColumnName("c_bigint") + .setColumnType(BIGINT) + .setJdbcTypeHandle(new JdbcTypeHandle(Types.BIGINT, Optional.of("int8"), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty())) + .build(); + + private static final JdbcColumnHandle DOUBLE_COLUMN = + JdbcColumnHandle.builder() + .setColumnName("c_double") + .setColumnType(DOUBLE) + .setJdbcTypeHandle(new JdbcTypeHandle(Types.DOUBLE, Optional.of("double"), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty())) + .build(); + + private static final JdbcClient JDBC_CLIENT = new SnowflakeClient( + new BaseJdbcConfig(), + session -> { throw new UnsupportedOperationException(); }, + new DefaultQueryBuilder(RemoteQueryModifier.NONE), + TESTING_TYPE_MANAGER, + new DefaultIdentifierMapping(), + RemoteQueryModifier.NONE); + + @Test + public void testImplementCount() + { + Variable bigintVariable = new Variable("v_bigint", BIGINT); + Variable doubleVariable = new Variable("v_double", BIGINT); + Optional filter = Optional.of(new Variable("a_filter", BOOLEAN)); + + // count(*) + testImplementAggregation( + new AggregateFunction("count", BIGINT, List.of(), List.of(), false, Optional.empty()), + Map.of(), + Optional.of("count(*)")); + + // count(bigint) + testImplementAggregation( + new AggregateFunction("count", BIGINT, List.of(bigintVariable), List.of(), false, Optional.empty()), + Map.of(bigintVariable.getName(), BIGINT_COLUMN), + Optional.of("count(\"c_bigint\")")); + + // count(double) + testImplementAggregation( + new AggregateFunction("count", BIGINT, List.of(doubleVariable), List.of(), false, Optional.empty()), + Map.of(doubleVariable.getName(), DOUBLE_COLUMN), + Optional.of("count(\"c_double\")")); + + // count() FILTER (WHERE ...) + testImplementAggregation( + new AggregateFunction("count", BIGINT, List.of(), List.of(), false, filter), + Map.of(), + Optional.empty()); + + // count(bigint) FILTER (WHERE ...) + testImplementAggregation( + new AggregateFunction("count", BIGINT, List.of(bigintVariable), List.of(), false, filter), + Map.of(bigintVariable.getName(), BIGINT_COLUMN), + Optional.empty()); + } + + @Test + public void testImplementSum() + { + Variable bigintVariable = new Variable("v_bigint", BIGINT); + Variable doubleVariable = new Variable("v_double", DOUBLE); + Optional filter = Optional.of(new Variable("a_filter", BOOLEAN)); + + // sum(bigint) + testImplementAggregation( + new AggregateFunction("sum", BIGINT, List.of(bigintVariable), List.of(), false, Optional.empty()), + Map.of(bigintVariable.getName(), BIGINT_COLUMN), + Optional.of("sum(\"c_bigint\")")); + + // sum(double) + testImplementAggregation( + new AggregateFunction("sum", DOUBLE, List.of(doubleVariable), List.of(), false, Optional.empty()), + Map.of(doubleVariable.getName(), DOUBLE_COLUMN), + Optional.of("sum(\"c_double\")")); + + // sum(DISTINCT bigint) + testImplementAggregation( + new AggregateFunction("sum", BIGINT, List.of(bigintVariable), List.of(), true, Optional.empty()), + Map.of(bigintVariable.getName(), BIGINT_COLUMN), + Optional.of("sum(DISTINCT \"c_bigint\")")); + + // sum(bigint) FILTER (WHERE ...) + testImplementAggregation( + new AggregateFunction("sum", BIGINT, List.of(bigintVariable), List.of(), false, filter), + Map.of(bigintVariable.getName(), BIGINT_COLUMN), + Optional.empty()); // filter not supported + } + + private static void testImplementAggregation(AggregateFunction aggregateFunction, Map assignments, Optional expectedExpression) + { + Optional result = JDBC_CLIENT.implementAggregation(SESSION, aggregateFunction, assignments); + if (expectedExpression.isEmpty()) { + assertThat(result).isEmpty(); + } + else { + assertThat(result).isPresent(); + assertEquals(result.get().getExpression(), expectedExpression.get()); + Optional columnMapping = JDBC_CLIENT.toColumnMapping(SESSION, null, result.get().getJdbcTypeHandle()); + assertTrue(columnMapping.isPresent(), "No mapping for: " + result.get().getJdbcTypeHandle()); + assertEquals(columnMapping.get().getType(), aggregateFunction.getOutputType()); + } + } +} diff --git a/plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/TestSnowflakeConfig.java b/plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/TestSnowflakeConfig.java new file mode 100644 index 000000000000..eb5c32a3d063 --- /dev/null +++ b/plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/TestSnowflakeConfig.java @@ -0,0 +1,61 @@ +/* + * 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 io.trino.plugin.snowflake; + +import com.google.common.collect.ImmutableMap; +import org.testng.annotations.Test; + +import java.util.Map; + +import static io.airlift.configuration.testing.ConfigAssertions.assertFullMapping; +import static io.airlift.configuration.testing.ConfigAssertions.assertRecordedDefaults; +import static io.airlift.configuration.testing.ConfigAssertions.recordDefaults; + +public class TestSnowflakeConfig +{ + @Test + public void testDefaults() + { + assertRecordedDefaults(recordDefaults(SnowflakeConfig.class) + .setAccount(null) + .setDatabase(null) + .setRole(null) + .setWarehouse(null) + .setHTTPProxy(null) + .setTimestampNoTimezoneAsUTC(null)); + } + + @Test + public void testExplicitPropertyMappings() + { + Map properties = ImmutableMap.builder() + .put("snowflake.account", "MYACCOUNT") + .put("snowflake.database", "MYDATABASE") + .put("snowflake.role", "MYROLE") + .put("snowflake.warehouse", "MYWAREHOUSE") + .put("snowflake.http-proxy", "MYPROXY") + .put("snowflake.timestamp-no-timezone-as-utc", "true") + .buildOrThrow(); + + SnowflakeConfig expected = new SnowflakeConfig() + .setAccount("MYACCOUNT") + .setDatabase("MYDATABASE") + .setRole("MYROLE") + .setWarehouse("MYWAREHOUSE") + .setHTTPProxy("MYPROXY") + .setTimestampNoTimezoneAsUTC(true); + + assertFullMapping(properties, expected); + } +} diff --git a/plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/TestSnowflakeConnectorTest.java b/plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/TestSnowflakeConnectorTest.java new file mode 100644 index 000000000000..8b9b0c78c73b --- /dev/null +++ b/plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/TestSnowflakeConnectorTest.java @@ -0,0 +1,38 @@ +/* + * 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 io.trino.plugin.snowflake; + +import com.google.common.collect.ImmutableMap; +import io.trino.testing.QueryRunner; +import io.trino.testing.sql.SqlExecutor; + +import static io.trino.plugin.snowflake.SnowflakeQueryRunner.createSnowflakeQueryRunner; + +public class TestSnowflakeConnectorTest + extends BaseSnowflakeConnectorTest +{ + @Override + protected QueryRunner createQueryRunner() + throws Exception + { + server = closeAfterClass(new TestingSnowflakeServer()); + return createSnowflakeQueryRunner(server, ImmutableMap.of(), ImmutableMap.of(), REQUIRED_TPCH_TABLES); + } + + @Override + protected SqlExecutor onRemoteDatabase() + { + return server::execute; + } +} diff --git a/plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/TestSnowflakePlugin.java b/plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/TestSnowflakePlugin.java new file mode 100644 index 000000000000..26165c3f018c --- /dev/null +++ b/plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/TestSnowflakePlugin.java @@ -0,0 +1,33 @@ +/* + * 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 io.trino.plugin.snowflake; + +import com.google.common.collect.ImmutableMap; +import io.trino.spi.Plugin; +import io.trino.spi.connector.ConnectorFactory; +import io.trino.testing.TestingConnectorContext; +import org.testng.annotations.Test; + +import static com.google.common.collect.Iterables.getOnlyElement; + +public class TestSnowflakePlugin +{ + @Test + public void testCreateConnector() + { + Plugin plugin = new SnowflakePlugin(); + ConnectorFactory factory = getOnlyElement(plugin.getConnectorFactories()); + factory.create("test", ImmutableMap.of("connection-url", "jdbc:snowflake://test"), new TestingConnectorContext()).shutdown(); + } +} diff --git a/plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/TestSnowflakeTypeMapping.java b/plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/TestSnowflakeTypeMapping.java new file mode 100644 index 000000000000..8fd62bd10863 --- /dev/null +++ b/plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/TestSnowflakeTypeMapping.java @@ -0,0 +1,411 @@ +/* + * 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 io.trino.plugin.snowflake; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.trino.Session; +import io.trino.spi.type.TimeZoneKey; +import io.trino.testing.AbstractTestQueryFramework; +import io.trino.testing.QueryRunner; +import io.trino.testing.TestingSession; +import io.trino.testing.datatype.CreateAndInsertDataSetup; +import io.trino.testing.datatype.CreateAsSelectDataSetup; +import io.trino.testing.datatype.DataSetup; +import io.trino.testing.datatype.SqlDataTypeTest; +import io.trino.testing.sql.TrinoSqlExecutor; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.parallel.Execution; + +import java.time.LocalDate; +import java.time.ZoneId; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Verify.verify; +import static io.trino.plugin.snowflake.SnowflakeQueryRunner.createSnowflakeQueryRunner; +import static io.trino.spi.type.BigintType.BIGINT; +import static io.trino.spi.type.BooleanType.BOOLEAN; +import static io.trino.spi.type.DateType.DATE; +import static io.trino.spi.type.DecimalType.createDecimalType; +import static io.trino.spi.type.DoubleType.DOUBLE; +import static io.trino.spi.type.TimeZoneKey.getTimeZoneKey; +import static io.trino.spi.type.TimestampType.createTimestampType; +import static io.trino.spi.type.VarbinaryType.VARBINARY; +import static io.trino.spi.type.VarcharType.createVarcharType; +import static java.time.ZoneOffset.UTC; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; + +@TestInstance(PER_CLASS) +@Execution(CONCURRENT) +public class TestSnowflakeTypeMapping + extends AbstractTestQueryFramework +{ + protected TestingSnowflakeServer snowflakeServer; + + private final ZoneId jvmZone = ZoneId.systemDefault(); + // no DST in 1970, but has DST in later years (e.g. 2018) + private final ZoneId vilnius = ZoneId.of("Europe/Vilnius"); + // minutes offset change since 1970-01-01, no DST + private final ZoneId kathmandu = ZoneId.of("Asia/Kathmandu"); + + @BeforeAll + public void setUp() + { + String zone = jvmZone.getId(); + checkState(jvmZone.getId().equals("America/Bahia_Banderas"), "Timezone not configured correctly. Add -Duser.timezone=America/Bahia_Banderas to your JVM arguments"); + checkIsGap(jvmZone, LocalDate.of(1970, 1, 1)); + checkIsGap(vilnius, LocalDate.of(1983, 4, 1)); + verify(vilnius.getRules().getValidOffsets(LocalDate.of(1983, 10, 1).atStartOfDay().minusMinutes(1)).size() == 2); + } + + @Override + protected QueryRunner createQueryRunner() + throws Exception + { + snowflakeServer = new TestingSnowflakeServer(); + return createSnowflakeQueryRunner( + snowflakeServer, + ImmutableMap.of(), + ImmutableMap.of(), + ImmutableList.of()); + } + + @Test + public void testBoolean() + { + SqlDataTypeTest.create() + .addRoundTrip("boolean", "true", BOOLEAN, "BOOLEAN '1'") + .addRoundTrip("boolean", "false", BOOLEAN, "BOOLEAN '0'") + .addRoundTrip("boolean", "NULL", BOOLEAN, "CAST(NULL AS BOOLEAN)") + .execute(getQueryRunner(), snowflakeCreateAndInsert("tpch.test_boolean")) + .execute(getQueryRunner(), trinoCreateAsSelect("tpch.test_boolean")) + .execute(getQueryRunner(), trinoCreateAndInsert("tpch.test_boolean")); + } + + @Test + public void testInteger() + { + // INT , INTEGER , BIGINT , SMALLINT , TINYINT , BYTEINT, DECIMAL , NUMERIC are aliases for NUMBER(38, 0) in snowflake + // https://docs.snowflake.com/en/sql-reference/data-types-numeric.html#int-integer-bigint-smallint-tinyint-byteint + testInteger("INT"); + testInteger("INTEGER"); + testInteger("BIGINT"); + testInteger("SMALLINT"); + testInteger("TINYINT"); + testInteger("BYTEINT"); + } + + private void testInteger(String inputType) + { + SqlDataTypeTest.create() + .addRoundTrip(inputType, "-9223372036854775808", BIGINT, "-9223372036854775808") + .addRoundTrip(inputType, "9223372036854775807", BIGINT, "9223372036854775807") + .addRoundTrip(inputType, "0", BIGINT, "CAST(0 AS BIGINT)") + .addRoundTrip(inputType, "NULL", BIGINT, "CAST(NULL AS BIGINT)") + .execute(getQueryRunner(), snowflakeCreateAndInsert("tpch.integer")); + } + + @Test + public void testDecimal() + { + SqlDataTypeTest.create() + .addRoundTrip("decimal(3, 0)", "NULL", BIGINT, "CAST(NULL AS BIGINT)") + .addRoundTrip("decimal(3, 0)", "CAST('193' AS decimal(3, 0))", BIGINT, "CAST('193' AS BIGINT)") + .addRoundTrip("decimal(3, 0)", "CAST('19' AS decimal(3, 0))", BIGINT, "CAST('19' AS BIGINT)") + .addRoundTrip("decimal(3, 0)", "CAST('-193' AS decimal(3, 0))", BIGINT, "CAST('-193' AS BIGINT)") + .addRoundTrip("decimal(3, 1)", "CAST('10.0' AS decimal(3, 1))", createDecimalType(3, 1), "CAST('10.0' AS decimal(3, 1))") + .addRoundTrip("decimal(3, 1)", "CAST('10.1' AS decimal(3, 1))", createDecimalType(3, 1), "CAST('10.1' AS decimal(3, 1))") + .addRoundTrip("decimal(3, 1)", "CAST('-10.1' AS decimal(3, 1))", createDecimalType(3, 1), "CAST('-10.1' AS decimal(3, 1))") + .addRoundTrip("decimal(4, 2)", "CAST('2' AS decimal(4, 2))", createDecimalType(4, 2), "CAST('2' AS decimal(4, 2))") + .addRoundTrip("decimal(4, 2)", "CAST('2.3' AS decimal(4, 2))", createDecimalType(4, 2), "CAST('2.3' AS decimal(4, 2))") + .addRoundTrip("decimal(24, 2)", "CAST('2' AS decimal(24, 2))", createDecimalType(24, 2), "CAST('2' AS decimal(24, 2))") + .addRoundTrip("decimal(24, 2)", "CAST('2.3' AS decimal(24, 2))", createDecimalType(24, 2), "CAST('2.3' AS decimal(24, 2))") + .addRoundTrip("decimal(24, 2)", "CAST('123456789.3' AS decimal(24, 2))", createDecimalType(24, 2), "CAST('123456789.3' AS decimal(24, 2))") + .addRoundTrip("decimal(24, 4)", "CAST('12345678901234567890.31' AS decimal(24, 4))", createDecimalType(24, 4), "CAST('12345678901234567890.31' AS decimal(24, 4))") + .addRoundTrip("decimal(30, 5)", "CAST('3141592653589793238462643.38327' AS decimal(30, 5))", createDecimalType(30, 5), "CAST('3141592653589793238462643.38327' AS decimal(30, 5))") + .addRoundTrip("decimal(30, 5)", "CAST('-3141592653589793238462643.38327' AS decimal(30, 5))", createDecimalType(30, 5), "CAST('-3141592653589793238462643.38327' AS decimal(30, 5))") +// .addRoundTrip("decimal(38, 0)", "CAST('27182818284590452353602874713526624977' AS decimal(38, 0))", createDecimalType(38, 0), "CAST('27182818284590452353602874713526624977' AS decimal(38, 0))") +// .addRoundTrip("decimal(38, 0)", "CAST('-27182818284590452353602874713526624977' AS decimal(38, 0))", createDecimalType(38, 0), "CAST('-27182818284590452353602874713526624977' AS decimal(38, 0))") + .addRoundTrip("decimal(38, 0)", "CAST(NULL AS decimal(38, 0))", BIGINT, "CAST(NULL AS BIGINT)") + .execute(getQueryRunner(), snowflakeCreateAndInsert("tpch.test_decimal")) + .execute(getQueryRunner(), trinoCreateAsSelect("test_decimal")) + .execute(getQueryRunner(), trinoCreateAndInsert("test_decimal")); + } + + @Test + public void testFloat() + { + // https://docs.snowflake.com/en/sql-reference/data-types-numeric.html#float-float4-float8 + SqlDataTypeTest.create() + .addRoundTrip("real", "3.14", DOUBLE, "DOUBLE '3.14'") + .addRoundTrip("real", "10.3e0", DOUBLE, "DOUBLE '10.3e0'") + .addRoundTrip("real", "NULL", DOUBLE, "CAST(NULL AS DOUBLE)") + .addRoundTrip("real", "CAST('NaN' AS DOUBLE)", DOUBLE, "nan()") + .addRoundTrip("real", "CAST('Infinity' AS DOUBLE)", DOUBLE, "+infinity()") + .addRoundTrip("real", "CAST('-Infinity' AS DOUBLE)", DOUBLE, "-infinity()") + .execute(getQueryRunner(), trinoCreateAsSelect("tpch.test_real")) + .execute(getQueryRunner(), trinoCreateAndInsert("tpch.test_real")); + + SqlDataTypeTest.create() + .addRoundTrip("float", "3.14", DOUBLE, "DOUBLE '3.14'") + .addRoundTrip("float", "10.3e0", DOUBLE, "DOUBLE '10.3e0'") + .addRoundTrip("float", "NULL", DOUBLE, "CAST(NULL AS DOUBLE)") + .addRoundTrip("float", "CAST('NaN' AS float)", DOUBLE, "nan()") + .addRoundTrip("float", "CAST('Infinity' AS float)", DOUBLE, "+infinity()") + .addRoundTrip("float", "CAST('-Infinity' AS float)", DOUBLE, "-infinity()") + .execute(getQueryRunner(), snowflakeCreateAndInsert("tpch.test_float")); + } + + @Test + public void testDouble() + { + SqlDataTypeTest.create() + .addRoundTrip("double", "3.14", DOUBLE, "CAST(3.14 AS DOUBLE)") + .addRoundTrip("double", "1.0E100", DOUBLE, "1.0E100") + .addRoundTrip("double", "1.23456E12", DOUBLE, "1.23456E12") + .addRoundTrip("double", "NULL", DOUBLE, "CAST(NULL AS DOUBLE)") + .addRoundTrip("double", "CAST('NaN' AS DOUBLE)", DOUBLE, "nan()") + .addRoundTrip("double", "CAST('Infinity' AS DOUBLE)", DOUBLE, "+infinity()") + .addRoundTrip("double", "CAST('-Infinity' AS DOUBLE)", DOUBLE, "-infinity()") + .execute(getQueryRunner(), trinoCreateAsSelect("trino_test_double")) + .execute(getQueryRunner(), trinoCreateAndInsert("trino_test_double")) + .execute(getQueryRunner(), snowflakeCreateAndInsert("tpch.test_double")); + } + + @Test + public void testSnowflakeCreatedParameterizedVarchar() + { + SqlDataTypeTest.create() + .addRoundTrip("text", "'b'", createVarcharType(16777216), "CAST('b' AS VARCHAR(16777216))") + .addRoundTrip("varchar(32)", "'e'", createVarcharType(32), "CAST('e' AS VARCHAR(32))") + .addRoundTrip("varchar(15000)", "'f'", createVarcharType(15000), "CAST('f' AS VARCHAR(15000))") + .execute(getQueryRunner(), snowflakeCreateAndInsert("tpch.snowflake_test_parameterized_varchar")); + } + + @Test + public void testSnowflakeCreatedParameterizedVarcharUnicode() + { + SqlDataTypeTest.create() + .addRoundTrip("text collate \'utf8\'", "'攻殻機動隊'", createVarcharType(16777216), "CAST('攻殻機動隊' AS VARCHAR(16777216))") + .addRoundTrip("varchar(5) collate \'utf8\'", "'攻殻機動隊'", createVarcharType(5), "CAST('攻殻機動隊' AS VARCHAR(5))") + .addRoundTrip("varchar(32) collate \'utf8\'", "'攻殻機動隊'", createVarcharType(32), "CAST('攻殻機動隊' AS VARCHAR(32))") + .addRoundTrip("varchar(20000) collate \'utf8\'", "'攻殻機動隊'", createVarcharType(20000), "CAST('攻殻機動隊' AS VARCHAR(20000))") + .addRoundTrip("varchar(1) collate \'utf8mb4\'", "'😂'", createVarcharType(1), "CAST('😂' AS VARCHAR(1))") + .addRoundTrip("varchar(77) collate \'utf8mb4\'", "'Ну, погоди!'", createVarcharType(77), "CAST('Ну, погоди!' AS VARCHAR(77))") + .execute(getQueryRunner(), snowflakeCreateAndInsert("tpch.snowflake_test_parameterized_varchar_unicode")); + } + + @Test + public void testParameterizedChar() + { + SqlDataTypeTest.create() + .addRoundTrip("char", "''", createVarcharType(1), "CAST(' ' AS varchar(1))") + .addRoundTrip("char", "'a'", createVarcharType(1), "CAST('a' AS varchar(1))") + .addRoundTrip("char(1)", "''", createVarcharType(1), "CAST(' ' AS varchar(1))") + .addRoundTrip("char(1)", "'a'", createVarcharType(1), "CAST('a' AS varchar(1))") + .addRoundTrip("char(8)", "'abc'", createVarcharType(8), "CAST('abc ' AS varchar(8))") + .addRoundTrip("char(8)", "'12345678'", createVarcharType(8), "CAST('12345678' AS varchar(8))") + .execute(getQueryRunner(), trinoCreateAsSelect("snowflake_test_parameterized_char")); + + SqlDataTypeTest.create() + .addRoundTrip("char", "''", createVarcharType(1), "CAST('' AS varchar(1))") + .addRoundTrip("char", "'a'", createVarcharType(1), "CAST('a' AS varchar(1))") + .addRoundTrip("char(1)", "''", createVarcharType(1), "CAST('' AS varchar(1))") + .addRoundTrip("char(1)", "'a'", createVarcharType(1), "CAST('a' AS varchar(1))") + .addRoundTrip("char(8)", "'abc'", createVarcharType(8), "CAST('abc' AS varchar(8))") + .addRoundTrip("char(8)", "'12345678'", createVarcharType(8), "CAST('12345678' AS varchar(8))") + .execute(getQueryRunner(), trinoCreateAndInsert("snowflake_test_parameterized_char")) + .execute(getQueryRunner(), snowflakeCreateAndInsert("tpch.snowflake_test_parameterized_char")); + } + + @Test + public void testSnowflakeParameterizedCharUnicode() + { + SqlDataTypeTest.create() + .addRoundTrip("char(1) collate \'utf8\'", "'攻'", createVarcharType(1), "CAST('攻' AS VARCHAR(1))") + .addRoundTrip("char(5) collate \'utf8\'", "'攻殻'", createVarcharType(5), "CAST('攻殻' AS VARCHAR(5))") + .addRoundTrip("char(5) collate \'utf8\'", "'攻殻機動隊'", createVarcharType(5), "CAST('攻殻機動隊' AS VARCHAR(5))") + .addRoundTrip("char(1)", "'😂'", createVarcharType(1), "CAST('😂' AS VARCHAR(1))") + .addRoundTrip("char(77)", "'Ну, погоди!'", createVarcharType(77), "CAST('Ну, погоди!' AS VARCHAR(77))") + .execute(getQueryRunner(), snowflakeCreateAndInsert("tpch.snowflake_test_parameterized_char")); + } + + @Test + public void testBinary() + { + SqlDataTypeTest.create() + .addRoundTrip("binary(18)", "NULL", VARBINARY, "CAST(NULL AS varbinary)") + .addRoundTrip("binary(18)", "X''", VARBINARY, "X''") + .addRoundTrip("binary(18)", "X'68656C6C6F'", VARBINARY, "to_utf8('hello')") + .addRoundTrip("binary(18)", "X'C582C4856B61207720E69DB1E4BAACE983BD'", VARBINARY, "to_utf8('łąka w 東京都')") // no trailing zeros + .addRoundTrip("binary(18)", "X'4261672066756C6C206F6620F09F92B0'", VARBINARY, "to_utf8('Bag full of 💰')") + .addRoundTrip("binary(18)", "X'0001020304050607080DF9367AA7000000'", VARBINARY, "X'0001020304050607080DF9367AA7000000'") // non-text prefix + .addRoundTrip("binary(18)", "X'000000000000'", VARBINARY, "X'000000000000'") + .execute(getQueryRunner(), snowflakeCreateAndInsert("tpch.test_binary")); + } + + @Test + public void testVarbinary() + { + SqlDataTypeTest.create() + .addRoundTrip("varbinary", "NULL", VARBINARY, "CAST(NULL AS varbinary)") + .addRoundTrip("varbinary", "X''", VARBINARY, "X''") + .addRoundTrip("varbinary", "X'68656C6C6F'", VARBINARY, "to_utf8('hello')") + .addRoundTrip("varbinary", "X'5069C4996B6E6120C582C4856B61207720E69DB1E4BAACE983BD'", VARBINARY, "to_utf8('Piękna łąka w 東京都')") + .addRoundTrip("varbinary", "X'4261672066756C6C206F6620F09F92B0'", VARBINARY, "to_utf8('Bag full of 💰')") + .addRoundTrip("varbinary", "X'0001020304050607080DF9367AA7000000'", VARBINARY, "X'0001020304050607080DF9367AA7000000'") // non-text + .addRoundTrip("varbinary", "X'000000000000'", VARBINARY, "X'000000000000'") + .execute(getQueryRunner(), trinoCreateAsSelect("test_varbinary")) + .execute(getQueryRunner(), trinoCreateAndInsert("test_varbinary")) + .execute(getQueryRunner(), snowflakeCreateAndInsert("tpch.test_varbinary")); + } + + @Test + public void testDate() + { + testDate(UTC); + testDate(jvmZone); + testDate(vilnius); + testDate(kathmandu); + testDate(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testDate(ZoneId sessionZone) + { + Session session = Session.builder(getSession()) + .setTimeZoneKey(getTimeZoneKey(sessionZone.getId())) + .build(); + + SqlDataTypeTest.create() + .addRoundTrip("date", "NULL", DATE, "CAST(NULL AS DATE)") + .addRoundTrip("date", "'-5877641-06-23'", DATE, "DATE '-5877641-06-23'") // min value in Trino + .addRoundTrip("date", "'0000-01-01'", DATE, "DATE '0000-01-01'") + .addRoundTrip("date", "DATE '0001-01-01'", DATE, "DATE '0001-01-01'") // Min value for the function Date. + .addRoundTrip("date", "DATE '1582-10-05'", DATE, "DATE '1582-10-05'") // begin julian->gregorian switch + .addRoundTrip("date", "DATE '1582-10-14'", DATE, "DATE '1582-10-14'") // end julian->gregorian switch + .addRoundTrip("date", "DATE '1983-04-01'", DATE, "DATE '1983-04-01'") + .addRoundTrip("date", "DATE '1983-10-01'", DATE, "DATE '1983-10-01'") + .addRoundTrip("date", "DATE '2017-07-01'", DATE, "DATE '2017-07-01'") // summer on northern hemisphere (possible DST) + .addRoundTrip("date", "DATE '2017-01-01'", DATE, "DATE '2017-01-01'") // winter on northern hemisphere (possible DST on southern hemisphere) + .addRoundTrip("date", "DATE '99999-12-31'", DATE, "DATE '99999-12-31'") + .addRoundTrip("date", "'5881580-07-11'", DATE, "DATE '5881580-07-11'") // max value in Trino + .execute(getQueryRunner(), session, trinoCreateAsSelect("test_date")) + .execute(getQueryRunner(), session, snowflakeCreateAndInsert("tpch.test_date")); + } + + @Test + public void testTimestamp() + { + testTimestamp(UTC); + testTimestamp(jvmZone); + testTimestamp(vilnius); + testTimestamp(kathmandu); + testTimestamp(TestingSession.DEFAULT_TIME_ZONE_KEY.getZoneId()); + } + + private void testTimestamp(ZoneId sessionZone) + { + Session session = Session.builder(getSession()) + .setTimeZoneKey(TimeZoneKey.getTimeZoneKey(sessionZone.getId())) + .build(); + + SqlDataTypeTest.create() + // after epoch (MariaDb's timestamp type doesn't support values <= epoch) + .addRoundTrip("timestamp(3)", "TIMESTAMP '2019-03-18 10:01:17.987'", createTimestampType(3), "TIMESTAMP '2019-03-18 10:01:17.987'") + // time doubled in JVM zone + .addRoundTrip("timestamp(3)", "TIMESTAMP '2018-10-28 01:33:17.456'", createTimestampType(3), "TIMESTAMP '2018-10-28 01:33:17.456'") + // time double in Vilnius + .addRoundTrip("timestamp(3)", "TIMESTAMP '2018-10-28 03:33:33.333'", createTimestampType(3), "TIMESTAMP '2018-10-28 03:33:33.333'") + .addRoundTrip("timestamp(3)", "TIMESTAMP '1970-01-01 00:13:42.000'", createTimestampType(3), "TIMESTAMP '1970-01-01 00:13:42.000'") + .addRoundTrip("timestamp(3)", "TIMESTAMP '2018-04-01 02:13:55.123'", createTimestampType(3), "TIMESTAMP '2018-04-01 02:13:55.123'") + // time gap in Vilnius + .addRoundTrip("timestamp(3)", "TIMESTAMP '2018-03-25 03:17:17.000'", createTimestampType(3), "TIMESTAMP '2018-03-25 03:17:17.000'") + // time gap in Kathmandu + .addRoundTrip("timestamp(3)", "TIMESTAMP '1986-01-01 00:13:07.000'", createTimestampType(3), "TIMESTAMP '1986-01-01 00:13:07.000'") + // max value 2038-01-19 03:14:07 + .addRoundTrip("timestamp(3)", "TIMESTAMP '2038-01-19 03:14:07.000'", createTimestampType(3), "TIMESTAMP '2038-01-19 03:14:07.000'") + +// TODO: Fix the precision > 3 tests + // same as above but with higher precision +// .addRoundTrip("timestamp(6)", "TIMESTAMP '2019-03-18 10:01:17.987654'", createTimestampType(6), "TIMESTAMP '2019-03-18 10:01:17.987654'") +// .addRoundTrip("timestamp(6)", "TIMESTAMP '2018-10-28 01:33:17.456789'", createTimestampType(6), "TIMESTAMP '2018-10-28 01:33:17.456789'") +// .addRoundTrip("timestamp(6)", "TIMESTAMP '2018-10-28 03:33:33.333333'", createTimestampType(6), "TIMESTAMP '2018-10-28 03:33:33.333333'") +// .addRoundTrip("timestamp(6)", "TIMESTAMP '1970-01-01 00:13:42.000000'", createTimestampType(6), "TIMESTAMP '1970-01-01 00:13:42.000000'") +// .addRoundTrip("timestamp(6)", "TIMESTAMP '2018-04-01 02:13:55.123456'", createTimestampType(6), "TIMESTAMP '2018-04-01 02:13:55.123456'") +// .addRoundTrip("timestamp(6)", "TIMESTAMP '2018-03-25 03:17:17.000000'", createTimestampType(6), "TIMESTAMP '2018-03-25 03:17:17.000000'") +// .addRoundTrip("timestamp(6)", "TIMESTAMP '1986-01-01 00:13:07.000000'", createTimestampType(6), "TIMESTAMP '1986-01-01 00:13:07.000000'") +// // max value 2038-01-19 03:14:07 +// .addRoundTrip("timestamp(6)", "TIMESTAMP '2038-01-19 03:14:07.000000'", createTimestampType(6), "TIMESTAMP '2038-01-19 03:14:07.000000'") + + // test arbitrary time for all supported precisions + .addRoundTrip("timestamp(0)", "TIMESTAMP '1970-01-01 00:00:01'", createTimestampType(0), "TIMESTAMP '1970-01-01 00:00:01'") + .addRoundTrip("timestamp(1)", "TIMESTAMP '1970-01-01 00:00:01.1'", createTimestampType(1), "TIMESTAMP '1970-01-01 00:00:01.1'") + .addRoundTrip("timestamp(1)", "TIMESTAMP '1970-01-01 00:00:01.9'", createTimestampType(1), "TIMESTAMP '1970-01-01 00:00:01.9'") + .addRoundTrip("timestamp(2)", "TIMESTAMP '1970-01-01 00:00:01.12'", createTimestampType(2), "TIMESTAMP '1970-01-01 00:00:01.12'") + .addRoundTrip("timestamp(3)", "TIMESTAMP '1970-01-01 00:00:01.123'", createTimestampType(3), "TIMESTAMP '1970-01-01 00:00:01.123'") + .addRoundTrip("timestamp(3)", "TIMESTAMP '1970-01-01 00:00:01.999'", createTimestampType(3), "TIMESTAMP '1970-01-01 00:00:01.999'") + + .addRoundTrip("timestamp(1)", "TIMESTAMP '2020-09-27 12:34:56.1'", createTimestampType(1), "TIMESTAMP '2020-09-27 12:34:56.1'") + .addRoundTrip("timestamp(1)", "TIMESTAMP '2020-09-27 12:34:56.9'", createTimestampType(1), "TIMESTAMP '2020-09-27 12:34:56.9'") + .addRoundTrip("timestamp(3)", "TIMESTAMP '2020-09-27 12:34:56.123'", createTimestampType(3), "TIMESTAMP '2020-09-27 12:34:56.123'") + .addRoundTrip("timestamp(3)", "TIMESTAMP '2020-09-27 12:34:56.999'", createTimestampType(3), "TIMESTAMP '2020-09-27 12:34:56.999'") +// .addRoundTrip("timestamp(4)", "TIMESTAMP '1970-01-01 00:00:01.1234'", createTimestampType(4), "TIMESTAMP '1970-01-01 00:00:01.1234'") +// .addRoundTrip("timestamp(5)", "TIMESTAMP '1970-01-01 00:00:01.12345'", createTimestampType(5), "TIMESTAMP '1970-01-01 00:00:01.12345'") +// .addRoundTrip("timestamp(6)", "TIMESTAMP '2020-09-27 12:34:56.123456'", createTimestampType(6), "TIMESTAMP '2020-09-27 12:34:56.123456'") + + .execute(getQueryRunner(), session, snowflakeCreateAndInsert("tpch.test_timestamp")) + .execute(getQueryRunner(), session, trinoCreateAsSelect(session, "test_timestamp")) + .execute(getQueryRunner(), session, trinoCreateAsSelect("test_timestamp")) + .execute(getQueryRunner(), session, trinoCreateAndInsert(session, "test_timestamp")) + .execute(getQueryRunner(), session, trinoCreateAndInsert("test_timestamp")); + } + + private DataSetup trinoCreateAsSelect(String tableNamePrefix) + { + return trinoCreateAsSelect(getSession(), tableNamePrefix); + } + + private DataSetup trinoCreateAsSelect(Session session, String tableNamePrefix) + { + return new CreateAsSelectDataSetup(new TrinoSqlExecutor(getQueryRunner(), session), tableNamePrefix); + } + + private DataSetup trinoCreateAndInsert(String tableNamePrefix) + { + return trinoCreateAndInsert(getSession(), tableNamePrefix); + } + + private DataSetup trinoCreateAndInsert(Session session, String tableNamePrefix) + { + return new CreateAndInsertDataSetup(new TrinoSqlExecutor(getQueryRunner(), session), tableNamePrefix); + } + + private DataSetup snowflakeCreateAndInsert(String tableNamePrefix) + { + return new CreateAndInsertDataSetup(snowflakeServer::execute, tableNamePrefix); + } + + private static void checkIsGap(ZoneId zone, LocalDate date) + { + verify(isGap(zone, date), "Expected %s to be a gap in %s", date, zone); + } + + private static boolean isGap(ZoneId zone, LocalDate date) + { + return zone.getRules().getValidOffsets(date.atStartOfDay()).isEmpty(); + } +} diff --git a/plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/TestingSnowflakeServer.java b/plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/TestingSnowflakeServer.java new file mode 100644 index 000000000000..bd64f3030759 --- /dev/null +++ b/plugin/trino-snowflake/src/test/java/io/trino/plugin/snowflake/TestingSnowflakeServer.java @@ -0,0 +1,77 @@ +/* + * 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 io.trino.plugin.snowflake; + +import org.intellij.lang.annotations.Language; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Properties; + +import static java.util.Objects.requireNonNull; + +public class TestingSnowflakeServer + implements AutoCloseable +{ + public static final String TEST_URL = requireNonNull(System.getProperty("snowflake.test.server.url"), "snowflake.test.server.url is not set"); + public static final String TEST_USER = requireNonNull(System.getProperty("snowflake.test.server.user"), "snowflake.test.server.user is not set"); + public static final String TEST_PASSWORD = requireNonNull(System.getProperty("snowflake.test.server.password"), "snowflake.test.server.password is not set"); + public static final String TEST_DATABASE = requireNonNull(System.getProperty("snowflake.test.server.database"), "snowflake.test.server.database is not set"); + public static final String TEST_WAREHOUSE = requireNonNull(System.getProperty("snowflake.test.server.warehouse"), "snowflake.test.server.warehouse is not set"); + public static final String TEST_ROLE = requireNonNull(System.getProperty("snowflake.test.server.role"), "snowflake.test.server.role is not set"); + public static final String TEST_PROXY = System.getProperty("snowflake.test.http_proxy"); + public static final String TEST_SCHEMA = "tpch"; + + public TestingSnowflakeServer() + { + execute("CREATE SCHEMA IF NOT EXISTS tpch"); + } + + public void execute(@Language("SQL") String sql) + { + execute(TEST_URL, getProperties(), sql); + } + + private static void execute(String url, Properties properties, String sql) + { + try (Connection connection = DriverManager.getConnection(url, properties); + Statement statement = connection.createStatement()) { + statement.execute(sql); + } + catch (SQLException e) { + throw new RuntimeException(e); + } + } + + public Properties getProperties() + { + Properties properties = new Properties(); + properties.setProperty("user", TEST_USER); + properties.setProperty("password", TEST_PASSWORD); + properties.setProperty("db", TEST_DATABASE); + properties.setProperty("schema", TEST_SCHEMA); + properties.setProperty("warehouse", TEST_WAREHOUSE); + properties.setProperty("role", TEST_ROLE); + return properties; + } + + @Override + public void close() + throws Exception + { + execute("DROP SCHEMA IF EXISTS tpch"); + } +} diff --git a/pom.xml b/pom.xml index 84976e4108e4..97d3d383ef68 100644 --- a/pom.xml +++ b/pom.xml @@ -105,6 +105,7 @@ plugin/trino-resource-group-managers plugin/trino-session-property-managers plugin/trino-singlestore + plugin/trino-snowflake plugin/trino-sqlserver plugin/trino-teradata-functions plugin/trino-thrift @@ -1404,6 +1405,12 @@ test-jar + + io.trino + trino-snowflake + ${project.version} + + io.trino trino-spi diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeAllConnectors.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeAllConnectors.java index 0ba9cf6edd05..7e289ee8583c 100644 --- a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeAllConnectors.java +++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeAllConnectors.java @@ -77,6 +77,7 @@ public void extendEnvironment(Environment.Builder builder) "raptor_legacy", "redis", "redshift", + "snowflake", "sqlserver", "trino_thrift", "tpcds") diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeSnowflake.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeSnowflake.java new file mode 100644 index 000000000000..ade68640bab2 --- /dev/null +++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/env/environment/EnvMultinodeSnowflake.java @@ -0,0 +1,78 @@ +/* + * 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 io.trino.tests.product.launcher.env.environment; + +import io.trino.tests.product.launcher.docker.DockerFiles; +import io.trino.tests.product.launcher.env.Environment; +import io.trino.tests.product.launcher.env.EnvironmentProvider; +import io.trino.tests.product.launcher.env.common.Standard; +import io.trino.tests.product.launcher.env.common.TestsEnvironment; + +import javax.inject.Inject; + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.PosixFilePermissions; + +import static java.nio.file.attribute.PosixFilePermissions.fromString; +import static java.util.Objects.requireNonNull; +import static org.testcontainers.utility.MountableFile.forHostPath; + +@TestsEnvironment +public class EnvMultinodeSnowflake + extends EnvironmentProvider +{ + private final DockerFiles.ResourceProvider configDir; + + @Inject + public EnvMultinodeSnowflake(DockerFiles dockerFiles, Standard standard) + { + super(standard); + configDir = requireNonNull(dockerFiles, "dockerFiles is null").getDockerFilesHostDirectory("conf/environment/multinode-snowflake"); + } + + @Override + public void extendEnvironment(Environment.Builder builder) + { + builder.addConnector("snowflake", forHostPath(getEnvProperties())); + } + + private Path getEnvProperties() + { + try { + String properties = Files.readString(configDir.getPath("snowflake.properties")) + .replace("${ENV:SNOWFLAKE_URL}", requireEnv("SNOWFLAKE_URL")) + .replace("${ENV:SNOWFLAKE_USER}", requireEnv("SNOWFLAKE_USER")) + .replace("${ENV:SNOWFLAKE_PASSWORD}", requireEnv("SNOWFLAKE_PASSWORD")) + .replace("${ENV:SNOWFLAKE_DATABASE}", requireEnv("SNOWFLAKE_DATABASE")) + .replace("${ENV:SNOWFLAKE_ROLE}", requireEnv("SNOWFLAKE_ROLE")) + .replace("${ENV:SNOWFLAKE_WAREHOUSE}", requireEnv("SNOWFLAKE_WAREHOUSE")); + File newProperties = Files.createTempFile("snowflake-replaced", ".properties", PosixFilePermissions.asFileAttribute(fromString("rwxrwxrwx"))).toFile(); + newProperties.deleteOnExit(); + Files.writeString(newProperties.toPath(), properties); + return newProperties.toPath(); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static String requireEnv(String variable) + { + return requireNonNull(System.getenv(variable), () -> "environment variable not set: " + variable); + } +} diff --git a/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/suite/suites/SuiteSnowflake.java b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/suite/suites/SuiteSnowflake.java new file mode 100644 index 000000000000..317d34817236 --- /dev/null +++ b/testing/trino-product-tests-launcher/src/main/java/io/trino/tests/product/launcher/suite/suites/SuiteSnowflake.java @@ -0,0 +1,37 @@ +/* + * 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 io.trino.tests.product.launcher.suite.suites; + +import com.google.common.collect.ImmutableList; +import io.trino.tests.product.launcher.env.EnvironmentConfig; +import io.trino.tests.product.launcher.env.environment.EnvMultinodeSnowflake; +import io.trino.tests.product.launcher.suite.Suite; +import io.trino.tests.product.launcher.suite.SuiteTestRun; + +import java.util.List; + +import static io.trino.tests.product.launcher.suite.SuiteTestRun.testOnEnvironment; + +public class SuiteSnowflake + extends Suite +{ + @Override + public List getTestRuns(EnvironmentConfig config) + { + return ImmutableList.of( + testOnEnvironment(EnvMultinodeSnowflake.class) + .withGroups("configured_features", "snowflake") + .build()); + } +} diff --git a/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/environment/multinode-all/snowflake.properties b/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/environment/multinode-all/snowflake.properties new file mode 100644 index 000000000000..669489ea4363 --- /dev/null +++ b/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/environment/multinode-all/snowflake.properties @@ -0,0 +1,4 @@ +connector.name=snowflake +connection-url=${ENV:SNOWFLAKE_URL} +connection-user=${ENV:SNOWFLAKE_USER} +connection-password=${ENV:SNOWFLAKE_PASSWORD} diff --git a/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/environment/multinode-snowflake/snowflake.properties b/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/environment/multinode-snowflake/snowflake.properties new file mode 100644 index 000000000000..669489ea4363 --- /dev/null +++ b/testing/trino-product-tests-launcher/src/main/resources/docker/presto-product-tests/conf/environment/multinode-snowflake/snowflake.properties @@ -0,0 +1,4 @@ +connector.name=snowflake +connection-url=${ENV:SNOWFLAKE_URL} +connection-user=${ENV:SNOWFLAKE_USER} +connection-password=${ENV:SNOWFLAKE_PASSWORD} diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/TestGroups.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/TestGroups.java index 3ab097d94fb4..1d4b1c7a32c0 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/TestGroups.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/TestGroups.java @@ -82,6 +82,7 @@ public final class TestGroups public static final String CLICKHOUSE = "clickhouse"; public static final String KUDU = "kudu"; public static final String MARIADB = "mariadb"; + public static final String SNOWFLAKE = "snowflake"; public static final String DELTA_LAKE_OSS = "delta-lake-oss"; public static final String DELTA_LAKE_HDFS = "delta-lake-hdfs"; public static final String DELTA_LAKE_MINIO = "delta-lake-minio"; diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/snowflake/TestSnowflake.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/snowflake/TestSnowflake.java new file mode 100644 index 000000000000..9ad47f213da3 --- /dev/null +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/snowflake/TestSnowflake.java @@ -0,0 +1,46 @@ +/* + * 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 io.trino.tests.product.snowflake; + +import io.trino.tempto.ProductTest; +import io.trino.tempto.query.QueryResult; +import org.testng.annotations.Test; + +import static io.trino.tempto.assertions.QueryAssert.Row.row; +import static io.trino.tempto.assertions.QueryAssert.assertThat; +import static io.trino.tests.product.TestGroups.PROFILE_SPECIFIC_TESTS; +import static io.trino.tests.product.TestGroups.SNOWFLAKE; +import static io.trino.testing.TestingNames.randomNameSuffix; +import static io.trino.tests.product.utils.QueryExecutors.onTrino; + +public class TestSnowflake + extends ProductTest +{ + @Test(groups = {SNOWFLAKE, PROFILE_SPECIFIC_TESTS}) + public void testCreateTableAsSelect() + { + String tableName = "snowflake.tpch.nation_" + randomNameSuffix(); + + onTrino().executeQuery("DROP TABLE IF EXISTS " + tableName); + QueryResult result = onTrino().executeQuery("CREATE TABLE " + tableName + " AS SELECT * FROM tpch.tiny.nation"); + try { + assertThat(result).updatedRowsCountIsEqualTo(25); + assertThat(onTrino().executeQuery("SELECT COUNT(*) FROM " + tableName)) + .containsOnly(row(25)); + } + finally { + onTrino().executeQuery("DROP TABLE " + tableName); + } + } +} diff --git a/testing/trino-server-dev/etc/config.properties b/testing/trino-server-dev/etc/config.properties index b786657e8d2b..be896f4a2670 100644 --- a/testing/trino-server-dev/etc/config.properties +++ b/testing/trino-server-dev/etc/config.properties @@ -46,6 +46,7 @@ plugin.bundles=\ ../../plugin/trino-mysql/pom.xml,\ ../../plugin/trino-mariadb/pom.xml,\ ../../plugin/trino-singlestore/pom.xml,\ + ../../plugin/trino-snowflake/pom.xml, \ ../../plugin/trino-sqlserver/pom.xml, \ ../../plugin/trino-prometheus/pom.xml, \ ../../plugin/trino-postgresql/pom.xml, \